In a recent comment, Adam requested the code from this previous post be modified to work in the same way as another, more recent, post:
Is it possible to get a version of this code where the user is prompted to create a selection set of nested objects first, and then being prompted for the new colour of the objects?
i.e is it possible to get the code for changing colour working the same way as it does for changing layer, as detailed here?
This would allow the user to pick and choose the objects affected, rather than making a blanket change to all nested objects.
The below code contains a new command – CNC, for Change Nested Color – which complements the existing CNL (Change Nested Layer) command. I took this opportunity to streamline the code based on previous feedback I’d received from Tony Tanzillo and Glenn Ryan (I was manually reversing a list rather than letting the CLR do it for me).
I’ve also adjusted the previous approach to unhighlight entities more carefully, rather than just REGENing the highlighting away, as I’ve found this can cause some minor issues with roll-over highlighting.
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;
// 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)
{
ent.Layer = newLay;
}
}
}
else
{
ed.WriteMessage(
"\nLayer not found in current drawing."
);
}
UnhighlightSubEntities(paths);
}
}
tr.Commit();
// Regen reflects the new layer
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;
// 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)
{
ent.ColorIndex = pir.Value;
}
}
}
UnhighlightSubEntities(paths);
}
tr.Commit();
}
// Regen reflects the new color
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;
do
{
rs = ed.GetNestedEntity("\nSelect nested entity: ");
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);
// Cancel is the status when "enter" is used to
// terminate the selection, which means we can't
// use it to distinguish from an actual
// cancellation.
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;
}
}
}
As we select the individual sub-entities they are highlighted appropriately:
And we can then choose out colour to which the various sub-entities will be changed:
I’m still moderately unhappy about the fact that cancelling the selection loop doesn’t drop out of the command directly (the user has to cancel the subsequent prompt as well, which is a little cumbersome). This is because the sub-entity selection method returns a “cancel” status when the user selects “enter” – which is how the code knows the selection loop should be exited. So when a genuine cancellation occurs, our code can’t tell. I expect that we could jump through some hoops (such as detecting key-presses, etc.) to avoid this, but I’m going to choose to continue to live with it, for now.