May 27, 2013

Here’s a question I received recently from Dustin Vest, who works as a PDMS administrator at Fluor:

I am having a problem with Intergraph’s SmartSketch dwg files it exports... I found some code on your site that got me into the blocks, but I can’t seem to change the draworder of entities within the block. SmartPlant 3D exports all the symbols as blocks but with wipeouts on top of the rest of the block.

Dustin provided some code that was very nearly working – it really didn’t need much work to get it in shape – and has kindly allowed me to share it, here.

The below C# code defines a WTB command (for “WipeoutsToBottom” – it’s not a new Internet swearcronym ;-) that moves any wipeouts to the bottom of the selected block (or of all blocks in the drawing if the user hits Enter).

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Runtime;

namespace BlockFixer

{

publicclassCommands

{

[CommandMethod("WTB")]

publicstaticvoid WipeoutsToBottom()

{

var doc = Application.DocumentManager.MdiActiveDocument;

var ed = doc.Editor;

var db = doc.Database;

try

{

// Ask the user to select a block or None for "all"

var peo =

newPromptEntityOptions("\nSelect block to fix <all>");

peo.SetRejectMessage("Must be a block.");

peo.AddAllowedClass(typeof(BlockReference), false);

peo.AllowNone = true;

var per = ed.GetEntity(peo);

if (

per.Status != PromptStatus.OK &&

per.Status != PromptStatus.None

)

return;

// If the user hit enter, run on all blocks in the drawing

bool allBlocks = per.Status == PromptStatus.None;

using (var tr = db.TransactionManager.StartTransaction())

{

var toProcess = newObjectIdCollection();

if (allBlocks)

{

var bt =

(BlockTable)tr.GetObject(

db.BlockTableId, OpenMode.ForRead

);

// Collect all the non-layout blocks in the drawing

foreach (ObjectId btrId in bt)

{

var btr =

(BlockTableRecord)tr.GetObject(

btrId, OpenMode.ForRead

);

if (!btr.IsLayout)

{

toProcess.Add(btrId);

}

}

}

else

{

// A specific block was selected, let's open it

var brId = per.ObjectId;

var br =

(BlockReference)tr.GetObject(brId, OpenMode.ForRead);

// Collect the ID of its underlying block definition

toProcess.Add(br.BlockTableRecord);

}

var brIds = MoveWipeoutsToBottom(tr, toProcess);

var count = toProcess.Count;

// Open each of the returned block references and

// request that they be redrawn

foreach (ObjectId brId in brIds)

{

var br =

(BlockReference)tr.GetObject(brId, OpenMode.ForWrite);

// We want to redraw a specific block, so let's modify a

// property on the selected block reference

// We might also have called this method:

// br.RecordGraphicsModified(true);

// but setting a property works better with undo

br.Visible = br.Visible;

}

// Report the number of blocks modified (after

// being filtered by MoveWipeoutsToBottom())

ed.WriteMessage(

"\nModified {0} block definition{1}.",

count, count == 1 ? "" : "s"

);

// Commit the transaction

tr.Commit();

}

}

catch (Autodesk.AutoCAD.Runtime.Exception e)

{

doc.Editor.WriteMessage(

"\nException: {0}", e.Message

);

}

}

// Move the wipeouts to the bottom of the specified

// block definitions

privatestaticObjectIdCollection MoveWipeoutsToBottom(

Transaction tr, ObjectIdCollection ids

)

{

// The IDs of any block references we find

// to return to the call for updating

var brIds = newObjectIdCollection();

// We only need to get this once

var wc = RXClass.GetClass(typeof(Wipeout));

// Take a copy of the IDs passed in, as we'll modify the

// original list for the caller to use

var btrIds = newObjectId[ids.Count];

ids.CopyTo(btrIds, 0);

// Loop through the blocks passed in, opening each one

foreach (var btrId in btrIds)

{

var btr =

(BlockTableRecord)tr.GetObject(

btrId, OpenMode.ForWrite

);

// Collect the wipeouts in the block

var wipeouts = newObjectIdCollection();

foreach (ObjectId id in btr)

{

var ent = (Entity)tr.GetObject(id, OpenMode.ForRead);

if (ent.GetRXClass().IsDerivedFrom(wc))

{

wipeouts.Add(id);

}

}

// Move the collected wipeouts to the bottom

if (wipeouts.Count > 0)

{

// Modify the draw order table, if we have wipepouts

var dot =

(DrawOrderTable)tr.GetObject(

btr.DrawOrderTableId, OpenMode.ForWrite

);

dot.MoveToBottom(wipeouts);

// Collect the block references to this block, to pass

// back to the calling function for updating

var btrBrIds = btr.GetBlockReferenceIds(false, false);

foreach (ObjectId btrBrId in btrBrIds)

{

brIds.Add(btrBrId);

}

}

else

{

ids.Remove(btrId);

}

}

return brIds;

}

}

}

The code is a bit longer than I had originally expected as I decided that, rather than relying on Editor.Regen() to reflect the changes to the drawing, it would be cleaner to modify each block reference in turn in order to force their graphics to be refreshed.

We’re not actually making a change to each block reference, but it’s not enough to just open them for write. The classic approach, here, would be to call RecordGraphicsModified(true) on each object, but I’ve actually found that making a property modification (even if to the same value) leads to undo working better – it forces a redraw even on undo. So I’ve chosen to set the Visible property of each block reference to the current value of the Visible property – I’m sure you’ll agree that’s as innocuous a modification as you can get. :-)

The function that checks each block for wipeouts and then modifies the draw order now collects the block references to these blocks and returns them to the caller. It’s clear that calling Regen() would have been simpler (i.e. taking fewer lines of code), but it would have exhibited similar behaviour to calling RecordGraphicsModified() with respect to undo. So while it takes more code, this approach is ultimately cleaner and works better, too.

Dustin has tried the application and tells me it works well for him – and it works well on the drawings Dustin provided for me to test with – but, beyond that, as I don’t personally use SmartSketch I have to take his word for it. If any other SmartSketch users who’ve hit this particular problem are able to give this a test, it’d be great to get some feedback in the comments.