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 based 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 special (unsupported) case, as well as some kind of window selection of nested entities, which would be really handy.