Changing the layer or color of a nested entity using .NET (take 2)

A big thanks to Thorsten Meinecke for pointing out the obvious issue with my prior post (the nested entity selection loop which has frustrated me unnecessarily when developing the code for that post and another).

Here are the details of the fix: using Editor.GetNestedEntity() along with a PromptNestedEntityOptions object, instead of a direct message string, allows us to specify AllowNone to be true. Which, in turn, causes PromptStatus.None to be returned when the enter or space keys are used to terminate the selection loop.

An obvious omission, in hindsight, but anyway – I'm much obliged to you, Thorsten! 🙂

I've gone and updated the code – although I haven't marked the individual line changes, as I'm supposed to be on vacation (yes, again, although in fairness I only had one week off of the three spent in San Francisco 😉 - which you will find below. The only other real change was to call UnhighlightSubEntities() even when the selection process is cancelled, as not doing so seems to leave selection graphics in an unstable state. I also took the opportunity to maintain flags to indicate whether a REGEN is really required, or not. So if either command is cancelled, no REGEN is performed.

Here's the updated C# code:

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.Runtime;

using System.Collections.Generic;

 

namespace NestedObjectModification

{

  public class Commands

  {

    [CommandMethod("CNL")]

    static public void ChangeNestedLayer()

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Database db = doc.Database;

      Editor ed = doc.Editor;

 

      // Collection of our selected entities and their sub-ent paths

 

      ObjectIdCollection ids;

      List<FullSubentityPath> paths;

 

      // Flag for whether a REGEN will be required

 

      bool regenRequired = false;

 

      // Start a transaction... will initially be used

      // to highlight the selected entities and then to

      // modify their layer

 

      Transaction tr =

        doc.TransactionManager.StartTransaction();

      using (tr)

      {

        if (SelectNestedEntities(ed, out ids, out paths) &&

          
  ids.Count > 0)

        {

          // Get the name of our destination later

 

          PromptResult pr =

            ed.GetString("\nNew layer for these objects: ");

          if (pr.Status == PromptStatus.OK)

          {

            // Check that the layer exists

 

            string newLay = pr.StringResult;

 

            LayerTable lt =

              tr.GetObject(db.LayerTableId, OpenMode.ForRead)

                as LayerTable;

 

            if (lt.Has(newLay))

            {

              // If so, set the layer name to be the one chosen

              // on each of the selected entitires

 

              for (int i = 0; i < ids.Count; i++)

              {

                Entity ent =

                  tr.GetObject(ids[i], OpenMode.ForWrite) as Entity;

                if (ent != null)

                {

                  regenRequired = true;

                  ent.Layer = newLay;

                }

              }

            }

            else

            {

              ed.WriteMessage(

                "\nLayer not found in current drawing."

              );

            }

          }

        }

        UnhighlightSubEntities(paths);

        tr.Commit();

 

 
;      
// Regen reflects the new layer

 

        if (regenRequired)

          ed.Regen();

      }

    }

 

    [CommandMethod("CNC")]

    static public void ChangeNestedColor()

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Database db = doc.Database;

      Editor ed = doc.Editor;

 

      // Collection of our selected entities and their sub-ent paths

 

      ObjectIdCollection ids;

      List<FullSubentityPath> paths;

 

      // Flag for whether a REGEN will be required

 

      bool regenRequired = false;

 

      // Start a transaction... will initially be used

      // to highlight the selected entities and then to

      // modify their color

 

      Transaction tr =

        doc.TransactionManager.StartTransaction();

      using (tr)

      {

        if (SelectNestedEntities(ed, out ids, out paths) &&

            ids.Count > 0)

        {

          // Get the new color index

 

          PromptIntegerOptions pio =

            new PromptIntegerOptions(

              "\nNew color index for these objects: "

            );

          pio.AllowZero = true;

          pio.AllowNegative = false;

          pio.LowerLimit = 0;

          pio.UpperLimit = 256;

 

          PromptIntegerResult pir =

            ed.GetInteger(pio);

          if (pir.Status == PromptStatus.OK)

          {

            // If so, set the layer name to be the one chosen

            // on each of the selected entitires

 

            for (int i = 0; i < ids.Count; i++)

            {

              Entity ent =

                tr.GetObject(ids[i], OpenMode.ForWrite) as Entity;

              if (ent != null)

              {

                regenRequired = true;

                ent.ColorIndex = pir.Value;

              }

            }

          }

        }

        UnhighlightSubEntities(paths);

        tr.Commit();

      }

 

      // Regen reflects the new color

 

      if (regenRequired)

        ed.Regen();

    }

 

    // Ask the user to select a number of sub-entities.

    // These will have their ObjectIds and their sub-entity

    // paths (returned from the HighlightSubEntity() helper)

    // added to collections that are returned to the caller.

 

    private static bool SelectNestedEntities(

      Editor ed,

      out ObjectIdCollection ids,

      out List<FullSubentityPath> paths

    )

    {

      ids = new ObjectIdCollection();

      paths = new List<FullSubentityPath>();

 

      // Loop until cancelled or completed

 

      PromptNestedEntityResult rs;

      PromptNestedEntityOptions pneo =

        new PromptNestedEntityOptions(

          "\nSelect nested entity: "

        );

      pneo.AllowNone = true;

 

      do

      {

        rs = ed.GetNestedEntity(pneo);

 

        if (rs.Status == PromptStatus.OK)

        {

          ids.Add(rs.ObjectId);

 

          FullSubentityPath path = HighlightSubEntity(rs);

          if (path != FullSubentityPath.Null)

            paths.Add(path);

        }

      }

      while (rs.Status == PromptStatus.OK);

 

      // Return whether the function was cancelled

 

      return (rs.Status != PromptStatus.Cancel);

    }

 

    // Unhighlight a set of sub-entities

 

    private static void UnhighlightSubEntities(

      List<FullSubentityPath> paths

    )

    {

      for (int i = 0; i < paths.Count; i++)

      {

        ObjectId[] ids = paths[i].GetObjectIds();

        Entity ent =

          ids[0].GetObject(OpenMode.ForRead) as Entity;

        if (ent != null)

        {

          ent.Unhighlight(paths[i], false);

        }

      }

    }

 

    // Highlight a sub-entity b
ased on its nested

    // selection information.

    // Return the calculated sub-entity path, so

    // the calling application can later unhighlight.

 

    private static FullSubentityPath HighlightSubEntity(

      PromptNestedEntityResult rs

    )

    {

      // Extract relevant information from the prompt object

 

      ObjectId selId = rs.ObjectId;

      List<ObjectId> objIds =

        new List<ObjectId>(rs.GetContainers());

 

      // Reverse the "containers" list

 

      objIds.Reverse();

 

      // Now append the selected entity

 

      objIds.Add(selId);

 

      // Retrieve the sub-entity path for this entity

 

      SubentityId subEnt =

        new SubentityId(SubentityType.Null, System.IntPtr.Zero);

      FullSubentityPath path =

        new FullSubentityPath(objIds.ToArray(), subEnt);

 

      // Open the outermost container, relying on the open

      // transaction...

 

      Entity ent =

        objIds[0].GetObject(OpenMode.ForRead) as Entity;

 

      // ... and highlight the nested entity

 

      if (ent == null)

        return FullSubentityPath.Null;

 

      ent.Highlight(path, false);

 

      // Return the sub-entity path for later unhighlighting

 

      return path;

    }

  }

}

I do expect a few more updates of this code to come… I'd still like to address Xref selection, either implementing it via Long Transactions or calling it out as a spe
cial (unsupported) case, as well as some kind of window selection of nested entities, which would be really handy.

11 responses to “Changing the layer or color of a nested entity using .NET (take 2)”

  1. sorry for the newby dumbness, but why do I get an error from this line(when i cut and paste the code into express):
    SubentityId subEnt = new SubentityId(SubentityType.Null, System.IntPtr.Zero);

    the error is:

    Error 1 The best overloaded method match for
    'Autodesk.AutoCAD.DatabaseServices.SubentityId.SubentityId (Autodesk.AutoCAD.DatabaseServices.SubentityType, int)' has some invalid arguments

    Error2Argument '2': cannot convert from 'System.IntPtr' to 'int'

  2. It's probably due to the version of AutoCAD you're using (it's likely to be an older version than the one I've code against).

    Try just passing 0 instead of System.IntPtr.Zero.

    Regards,

    Kean

  3. Hello Kean,

    I´ve got a generally question to the use of block references and their attributes. I´ve developed a little interface with objectarx 2012 and C# for insuring and editing specific blocks which contain several text attributes. Most of them are not visible in the drawing. Two of them are called ZEILE_1 and ZEILE_2 and they are visible. We need these blocks to code the components of P&IDs. I´ve created two different kinds of blocks which are mostly the same the only difference is the position of the visible attributes.
    So now my questions:
    I´ve implemented a command to move the ZEILE_1 and ZEILE_2 of all block references in the drawing by setting a int value for x and y. This command works fine for one kind of my blocks for the other block I get no error message but the position didn't change. I´ve compare several attribute properties such as "lockposition" but for both blocks they have all the same values. I don´t know, why I can´t set the attribute position of the one block. Which attribute property should I change to make this work? Or could it be a property of block or the block reference? Or is their a simply way to set all properties of the one block to the other block?
    The second question is. Is their a switch to set the attributes only for the drawing invisible? I´ve I set the visible property of attributes "TRUE" I can´t see the attribute also in the attribute editor.

    I hope you can help me.

    Have a creat remaining christmas time!

    greetings robert

  4. Hi Robert,

    Happy New Year!

    This question doesn't relate directly to this post, and should therefore really be submitted via ADN (if you're a member) or otherwise to the AutoCAD .NET Discussion Group.

    Thanks for understanding,

    Kean

  5. Hi,
    instead of selecting the nested entities one by one, is it possible to make a "selectall" selection ?

    1. Hi Kenan,

      It is possible: you'll simply need to determine a point on the text to pass through (the centre of the extents should do it).

      I'll show how to do this next week in a blog post.

      Regards,

      Kean

      1. Thanks alot.

        1. I thought this comment was on a different, more recent post... do you really want to change the entity properties of all nested entities in a drawing? I'll show you how to process all the text in a drawing in a post next week, but I don't see the common use-case for what you're requesting.

          Kean

          1. Hi Mr. Kean,
            processing texts will be useful.
            what i was trying to do is changing the layers of all entities which resides in a blockreference, if there is a block in a block, also change the layer of the entities of nested block.
            instead of using the blocktablerecord of the blockreference, i was trying to reach the entities with promptnestedoption.
            right now i solved my issue with changing the layers.
            looking forward to see text processing code of yours.

  6. thanks man, there are alot to learn.

Leave a Reply to Robert Menger Cancel reply

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