A really interesting problem came up during an internal discussion, this week: someone wanted to launch the REFEDIT command on a selected xref and pre-select the entity found at the picked point. The entity that’s part of the selected xref, of course.
This turned out to be quite a tricky problem and yet one that could be solved with relatively few lines of code. The tricky parts were finding the right entity in the nested selection and then the corresponding entity in the xref.
Here’s the approach I ended up taking:
- Ask the user to selected a nested entity
- Get the first entity contained by an xref in the container list
- We need a top-level entity that can be selected in the xref: this could well be the selected entity itself
- Add a handler responding to the “check out” event on the long transaction manager
- This handler will check the deep-clone mapping established for the current long transaction
- If it finds the source entity (the entity determined in step 2) then get the target entity and add that to the pickfirst selection set
- Call the REFEDIT command, passing through the point picked by the user when selecting the entity
- Detach the event handler
As we’re going to modify the pickfirst selection set we need to set the “redraw” flag on our command, of course.
Here’s the C# code defining the RS (RefeditSelect) command:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
namespace RefeditSelected
{
public static class Extensions
{
///<summary>
/// Get the child entity of the first xref in the nested selection.
///</summary>
///<returns>ObjectId of the top-level object from the outer xref.</returns>
public static ObjectId GetFirstXrefChild(this PromptNestedEntityResult res)
{
var retId = ObjectId.Null;
var selId = res.ObjectId;
var conts = res.GetContainers();
var db = selId.Database;
// Use an open-close transaction as we're in a utility function
using (var tr = db.TransactionManager.StartOpenCloseTransaction())
{
// Work backwards through the containers, looking for an xref
for (int i = conts.Length-1; i >= 0; i--)
{
var br = tr.GetObject(conts[i], OpenMode.ForRead) as BlockReference;
if (br != null)
{
var btr =
(BlockTableRecord)tr.GetObject(
br.BlockTableRecord, OpenMode.ForRead
);
// If we have an xref, we'll return the next container or the
// selected entity in the case we're at the innermost container
if (btr.IsFromExternalReference)
{
retId = i > 0 ? conts[i-1] : selId;
break;
}
}
}
tr.Commit();
}
return retId;
}
}
public class Commands
{
[CommandMethod("RS", CommandFlags.Redraw)]
public void RefeditSelected()
{
var doc = Application.DocumentManager.MdiActiveDocument;
if (doc == null) return;
var db = doc.Database;
var ed = doc.Editor;
// Select an entity within an xref
var pner = ed.GetNestedEntity("\nSelect entity on xref");
if (pner.Status != PromptStatus.OK)
return;
// Get the ID of the entity that we want to select in the xref
// (this is the first entity contained by an xref)
var selId = pner.GetFirstXrefChild();
// Only proceed if something is containing it
if (selId != ObjectId.Null)
{
// Define our event handler
LongTransactionEventHandler func = (s, e) =>
{
// Get the deepclone translation mapping from the long transaction
var map = e.Transaction.ActiveIdMap;
// If there's a mapping from our selected object...
if (map.Contains(selId))
{
// ... add it to the pickfirst selection set
ed.SetImpliedSelection(new ObjectId[] { map[selId].Value });
ed.WriteMessage("\nSelected one entity.");
}
};
// Attach our handler, call REFEDIT and then detach it
Application.LongTransactionManager.CheckedOut += func;
ed.Command("_.-REFEDIT", pner.PickedPoint, "_O", "_A", "_N");
Application.LongTransactionManager.CheckedOut -= func;
}
}
}
}
Here’s what happens when we run the RS command and select entities belonging to an external reference:
It’s important to note that not all the entities selected above are at the same level in the drawing hierarchy: when we select the door, for instance, the nested entity selection process actually finds a polyline component of our door block. We have to find the entity in the hierarchy that corresponds with the top-level entity in the xref – in this case the block reference of the door – and select that: if the entity doesn’t exist in the xref then we’ll get an eInvalidInput error when we call Editor.SetImpliedSelection().