Fixing block draw-order in AutoCAD drawings exported by SmartSketch using .NET

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

{

  public class Commands

  {

    [CommandMethod("WTB")]

    public static void 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 =

          new PromptEntityOptions("\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 = new ObjectIdCollection();

          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

 

    private static ObjectIdCollection MoveWipeoutsToBottom(

      Transaction tr, ObjectIdCollection ids

    )

    {

      // The IDs of an
y block references we find

      // to return to the call for updating

 

      var brIds = new ObjectIdCollection();

 

      // 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 = new ObjectId[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 = new ObjectIdCollection();

        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.

One response to “Fixing block draw-order in AutoCAD drawings exported by SmartSketch using .NET”

  1. Tony Tanzillo Avatar

    "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. :-)"

    Hi Kean. I can't seem to replicate the issue you cite with simply calling RecordGraphicsModified(true), when used in a Transaction that uses QueueForGraphicsFlush() and FlushGraphics() (as the AppTransaction returned by the Document's TransactionManager does implicitly).

    While what you do may work in this case, setting a property to its current value may not always have the intended effect, because of the potential that an underlying implementation might optimized to compare the current and proposed values, and do nothing if they are equal.

  2. Kean Walmsley Avatar

    Hi Tony,

    Hmm. The issue is certainly there for me, at least: it works fine when I run the WTB command, but when I UNDO it the graphics don't revert to their previous state until I REGEN. Even with explicit calls to QueueForGraphicsFlush() and FlushGraphics().

    I had thought it might in some way be drawing-specific (as at least one of the drawings Dustin provided was direct output from SmartSketch), but I just repro'ed it in a fresh drawing:

    1. Draw some lines.
    2. Use RECTANG to draw a border around them.
    3. Use WIPEOUT with the Polyline option (not erasing the boundary) to create a wipeout.
    4. BLOCK them all together (the wipeout should be in the foreground).
    5. Run the WTB command, selecting enter.
    6. UNDO - the graphics should remain the same.
    7. I found here that I had to REGEN for the graphics to reflect the change back.

    I agree that there's some non-zero probability that this might break (i.e. require a REGEN to display properly), should someone decide that checking the value of a Boolean property prior to changing it is a worthwhile optimisation, but that's also why I chose a simple type (and the probability of someone deeming it worth changing is pretty close to zero). An option would be to invert and revert, if you really wanted to be sure, I suppose.

    I do agree that it'd be marginally preferable not to have to rely on this - if you come up with a better solution (or notice where I've gone wrong), then please do let me know.

    Kean

  3. Tony Tanzillo Avatar

    Hi Kean. I wasn't able to replicate the issue because in my own code, there was a call to the SetUndoRequiresRegen() method (Autodesk.AutoCAD.Internal.Utils).

    The workaround you show also doesn't work for solving the more general problem of changing the draw order of entities in a paper space or the model space block, would would seem to explain why undoing the DRAWORDER command always results in a regen.

    In your call to GetBlockReferenceIds(), you pass false for the first argument, which is redundant if you are going to process all blocks. You also shouldn't be opening or updating nested block references if there are any returned by GetBlockReferenceIds(), only those inserted into a layout block.

    And last but not least, something that many seem to routinely overlook, is the need to deal with objects on locked layers.

  4. Kean Walmsley Avatar

    Hi Tony,

    I see - thanks for clarifying.

    The code in this post is intended to address a problem with a very specific subset of drawings - those exported by SmartSketch - so I have the luxury of not having to worry about layout blocks, nested blocks and locked layers. The code may "touch" a few more block definitions than strictly necessary, but nothing that should have a measurable impact on performance (it should still be cheaper than doing a full Regen()).

    It's good that you mention those points, though, in case someone gets to this page when looking for code to solve a larger problem.

    Kean

  5. IEnjoyBadMusic Avatar
    IEnjoyBadMusic

    OMG! This totally helped!! Thanks for this!

Leave a Reply to IEnjoyBadMusic Cancel reply

Your email address will not be published. Required fields are marked *