Author Topic: Alternative approaches for Get Dynamic Blocks by Name  (Read 3498 times)

0 Members and 1 Guest are viewing this topic.

kaefer

  • Guest
Alternative approaches for Get Dynamic Blocks by Name
« on: February 26, 2011, 11:43:42 AM »
Mark, I think, posted a snippet under http://www.theswamp.org/index.php?topic=31859.msg422435#msg422435 in which the dynamic block references are enumerated by GetBlockReferenceIds(). Which is often not what one would want, e.g. in case of nested and/or unreferenced blocks.

My suggestion uses a custom selection filter (which is one possible way to iterate the model space) and relies on the fact that the anonymous BlockTableRecord corresponding to a dynamic BlockReference has a XData entry which contains the Handle of the unparametrized BlockTableRecord. The upshot is that I'm not comparing names, but only Handles.

Implementation in F#, will port to C# if there's time for and interest in it.
Cheers, Thorsten

Code: [Select]
let (|TypedValue|) (tv: TypedValue) = tv.TypeCode, tv.Value

let tryPickFromXData f (xData: ResultBuffer) =
    if xData = null then None
    else xData.AsArray() |> Array.tryPick f

type Transaction with
    member tr.OpenForRead oid = tr.GetObject(oid, OpenMode.ForRead)
    member tr.TryGetBTRHandleFromDynamicBlockReference oid =
        let br = tr.OpenForRead oid :?> BlockReference

        if br.AnonymousBlockTableRecord.IsNull then
            let btr = tr.OpenForRead br.BlockTableRecord :?> BlockTableRecord
            btr.Handle |> string |> Some
        else
            let btr = tr.OpenForRead br.AnonymousBlockTableRecord :?> BlockTableRecord
            btr.GetXDataForApplication "AcDbBlockRepBTag"
            |> tryPickFromXData
                (fun (TypedValue(k, v)) ->
                    if k = int16 DxfCode.ExtendedDataHandle then string v |> Some
                    else None
                )

type Editor with
    member ed.TrySelectAnonymousBlocks btrHand =
        use tr = ed.Document.Database.TransactionManager.StartTransaction()
       
        let selectionFilter =
            Seq.cast<SelectedObject>
            >> Seq.mapi
                (fun i so ->
                    i, tr.TryGetBTRHandleFromDynamicBlockReference so.ObjectId
                )
            >> Seq.choose
                (function
                    | _, Some hand when hand = btrHand -> None
                    | i, _ -> Some i
                )

        use selectionAdded =
            ed.SelectionAdded
            |> Observable.subscribe
                (fun e ->
                    e.AddedObjects
                    |> selectionFilter
                    |> Seq.iter e.Remove
                )
       
        let sf =
            new SelectionFilter[|
                new TypedValue(int DxfCode.Start, "INSERT") |]
        let pso =
            new PromptSelectionOptions(
                MessageForAdding = "\nSelect block references: " )
        let psr = ed.GetSelection(pso, sf)
        let psr =
            if psr.Status = PromptStatus.Error then ed.SelectAll sf
            else psr
        tr.Commit()
        if psr.Status = PromptStatus.OK && psr.Value.Count > 0 then
            Some psr.Value
        else None

[<CommandMethod "DynBlkSel">]
let dynBlkSel() =
    let ed = acApp.DocumentManager.MdiActiveDocument.Editor
    let db = acApp.DocumentManager.MdiActiveDocument.Database

    use tr = db.TransactionManager.StartTransaction()

    let peo =
        new PromptEntityOptions(
            "\nSelect Blockreference: ", AllowNone = true )
    peo.SetRejectMessage("\nNo Blockreference. ")
    peo.AddAllowedClass(typeof<BlockReference>, false)
    let per = ed.GetEntity peo
   
    if per.Status = PromptStatus.OK then Some per.ObjectId
    else None
    |> Option.bind tr.TryGetBTRHandleFromDynamicBlockReference
    |> function
        | None ->
            ed.WriteMessage("\nCan't find named block. ")
        | Some btrHand ->
            ed.WriteMessage("\nBlock selected with Handle {0}. ", btrHand)
            ed.TrySelectAnonymousBlocks btrHand
            |> function
                | None -> ed.WriteMessage("\nNothing selected. ")
                | Some sset ->
                    ed.WriteMessage("\n{0} blocks selected. ", sset.Count)

    tr.Commit()

kaefer

  • Guest
Re: Alternative approaches for Get Dynamic Blocks by Name
« Reply #1 on: February 27, 2011, 01:47:48 PM »
will port to C#
Here we go.
Code: [Select]
    // Demo selection of dynamic blocks that are derived from the same block definition
    public class DynBlkSelCmd
    {
        // Let the user select a single block reference, filter their subsequent selection that
        // it only contains references of the same definition as the one picked first
        [CommandMethod("DynBlkSel")]
        public void DynBlkSel()
        {
            Database db = Application.DocumentManager.MdiActiveDocument.Database;
            Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
            PromptEntityOptions peo = new PromptEntityOptions("\nSelect Blockreference: ");
            peo.AllowNone = true;
            peo.SetRejectMessage("\nNo Blockreference. ");
            peo.AddAllowedClass(typeof(BlockReference), false);
            PromptEntityResult per = ed.GetEntity(peo);
            if (per.Status != PromptStatus.OK) return;
            using (Transaction tr = db.TransactionManager.StartTransaction())
            {
                if (!TryGetBTRHandleFromDynamicBlockReference(per.ObjectId, out btrHand))
                    ed.WriteMessage("\nCan't find named block. ");
                else
                {
                    ed.WriteMessage("\nBlock selected with Handle {0}. ", btrHand);
                    SelectionSet sset = SelectAnonymousBlocks(ed);
                    if (sset == null)
                        ed.WriteMessage("\nNothing selected. ");
                    else
                        ed.WriteMessage("\n{0} blocks selected. ", sset.Count);
                }
                tr.Commit();
            }
        }

        // Do the actual filtering with a SelectionAdded event handler
        SelectionSet SelectAnonymousBlocks(Editor ed)
        {
            TypedValue[] tva = new TypedValue[] { new TypedValue((int)DxfCode.Start, "INSERT") };
            SelectionFilter sf = new SelectionFilter(tva);
            PromptSelectionOptions pso = new PromptSelectionOptions();
            pso.MessageForAdding = "\nSelect block references (Enter for all): ";
            ed.SelectionAdded += new SelectionAddedEventHandler(SelectionAdded);
            PromptSelectionResult psr = ed.GetSelection(pso, sf);
            if(psr.Status == PromptStatus.Error) psr = ed.SelectAll(sf);
            ed.SelectionAdded -= new SelectionAddedEventHandler(SelectionAdded);
            if(psr.Status == PromptStatus.OK && psr.Value.Count > 0) return psr.Value;
            else return null;
        }

        // Return BTR handle of definition, from Handle property if not dynamic,
        // else from XData of anonymous BTR. Needs active transaction.
        bool TryGetBTRHandleFromDynamicBlockReference(ObjectId oid, out string hand)
        {
            BlockReference br = (BlockReference)oid.GetObject(OpenMode.ForRead);
            hand = null;
            if (br.AnonymousBlockTableRecord.IsNull)
            {
                // Here it's either not a dynamic block or has default parameters only
                BlockTableRecord btr = (BlockTableRecord)br.BlockTableRecord.GetObject(OpenMode.ForRead);
                hand = btr.Handle.ToString();
                return true;
            }
            BlockTableRecord abtr = (BlockTableRecord)br.AnonymousBlockTableRecord.GetObject(OpenMode.ForRead);
            ResultBuffer xData = abtr.GetXDataForApplication("AcDbBlockRepBTag");
            if (xData == null) return false;
            TypedValue[] tva = xData.AsArray();
            for (int i = 0; i < tva.Length; i++)
            {
                if (tva[i].TypeCode == (System.Int16)DxfCode.ExtendedDataHandle)
                {
                    hand = (string)tva[i].Value;
                    return true;
                }
            }
            return false;
        }
             
        void SelectionAdded(object sender, SelectionAddedEventArgs e)
        {
            for(int i = 0; i < e.AddedObjects.Count; i++){
                string hand;
                bool ok = TryGetBTRHandleFromDynamicBlockReference(e.AddedObjects[i].ObjectId, out hand);
                if (!ok || (hand != btrHand)) e.Remove(i);
            }
        }

        string btrHand = null;
    }

fixo

  • Guest
Re: Alternative approaches for Get Dynamic Blocks by Name
« Reply #2 on: February 28, 2011, 08:15:29 AM »
Hi Thorsten,
Big thank for the sharing
Regards,

Oleg

kaefer

  • Guest
Re: Alternative approaches for Get Dynamic Blocks by Name
« Reply #3 on: February 28, 2011, 08:43:07 AM »
Big thank for the sharing

Don't thank me yet, it's still not the best way. While I started with the Name property, then did away with that as the Handle was sufficient; finally I have found the DynamicBlockTableRecord property. Which can be used for the same end result, filtering block references that point to the same dynamic block definition. Here's the updated code:

Code: [Select]
    // Demo selection of dynamic blocks that are derived from the same block definition
    public class DynBlkSelCmd
    {
        // Let the user select a single block reference, filter their subsequent selection that
        // it only contains references of the same definition as the one picked first
        [CommandMethod("DynBlkSel")]
        public void DynBlkSel()
        {
            Database db = AcApp.DocumentManager.MdiActiveDocument.Database;
            Editor ed = AcApp.DocumentManager.MdiActiveDocument.Editor;
            PromptEntityOptions peo = new PromptEntityOptions("\nSelect Blockreference: ");
            peo.AllowNone = true;
            peo.SetRejectMessage("\nNo Blockreference. ");
            peo.AddAllowedClass(typeof(BlockReference), false);
            PromptEntityResult per = ed.GetEntity(peo);
            if (per.Status != PromptStatus.OK) return;
            using (Transaction tr = db.TransactionManager.StartTransaction())
            {
                btrOid = GetDynamicBlockTableRecordId(per.ObjectId);
                if(btrOid.IsNull)
                    ed.WriteMessage("\nCan't find named block. ");
                else
                {
                    ed.WriteMessage("\nBlock selected with ObjectId {0}. ", btrOid.ToString());
                    SelectionSet sset = SelectAnonymousBlocks(ed);
                    if (sset == null)
                        ed.WriteMessage("\nNothing selected. ");
                    else
                        ed.WriteMessage("\n{0} blocks selected. ", sset.Count);
                }
                tr.Commit();
            }
        }

        // Do the actual filtering with a SelectionAdded event handler
        SelectionSet SelectAnonymousBlocks(Editor ed)
        {
            TypedValue[] tva = new TypedValue[] { new TypedValue((int)DxfCode.Start, "INSERT") };
            SelectionFilter sf = new SelectionFilter(tva);
            PromptSelectionOptions pso = new PromptSelectionOptions();
            pso.MessageForAdding = "\nSelect block references (Enter for all): ";
            ed.SelectionAdded += new SelectionAddedEventHandler(SelectionAdded);
            PromptSelectionResult psr = ed.GetSelection(pso, sf);
            if(psr.Status == PromptStatus.Error) psr = ed.SelectAll(sf);
            ed.SelectionAdded -= new SelectionAddedEventHandler(SelectionAdded);
            if(psr.Status == PromptStatus.OK && psr.Value.Count > 0) return psr.Value;
            else return null;
        }

        // Return BTR Id if not dynamic, else DynamicBlockTableRecord. Needs active transaction.
        ObjectId GetDynamicBlockTableRecordId(ObjectId oid)
        {
            BlockReference br = oid.GetObject(OpenMode.ForRead) as BlockReference;
            if (br == null) return ObjectId.Null;
            if (br.IsDynamicBlock) return br.DynamicBlockTableRecord;
            return br.BlockTableRecord;
        }
             
        void SelectionAdded(object sender, SelectionAddedEventArgs e)
        {
            for(int i = 0; i < e.AddedObjects.Count; i++){
                ObjectId oid = GetDynamicBlockTableRecordId(e.AddedObjects[i].ObjectId);
                if (oid.IsNull || oid != btrOid) e.Remove(i);
            }
        }

        ObjectId btrOid = ObjectId.Null;
    }

mohnston

  • Bull Frog
  • Posts: 305
  • CAD Programmer
Re: Alternative approaches for Get Dynamic Blocks by Name
« Reply #4 on: February 28, 2011, 12:39:15 PM »
Thanks for your alternative approach. Good to know about the xdata.
And good to have another example of F#.

Your alternative does not get dynamic block references by name. You are asking the user to pick a block and then using that block.
My approach requires only the block name.
It's amazing what you can do when you don't know what you can't do.
CAD Programming Solutions

kaefer

  • Guest
Re: Alternative approaches for Get Dynamic Blocks by Name
« Reply #5 on: March 01, 2011, 03:26:54 AM »
Your alternative does not get dynamic block references by name. You are asking the user to pick a block and then using that block.

Why yes, that was left as an exercise for the reader: Just open the BlockTable and filter for the appropriate BTR ObjectId.

Another improvement may be wrapping the selection in a try-finally-block to ensure removal of the SelectionAdded event handler, however improbable it is that the selection throws an exception. That's why I like Observables: put them in a using statement and the same thing is achieved.

Code: [Select]
    // Demo selection of dynamic blocks that are derived from the same block definition
    public class DynBlkSelByNameCmd
    {
        // Let the user enter a block name, filter their subsequent selection that
        // it only contains references of the same definition
        [CommandMethod("DynBlkSelByName")]
        public void DynBlkSelByName()
        {
            Database db = AcApp.DocumentManager.MdiActiveDocument.Database;
            Editor ed = AcApp.DocumentManager.MdiActiveDocument.Editor;
            PromptStringOptions psto = new PromptStringOptions("\nEnter Block Name: ");
            psto.AllowSpaces = true;
            PromptResult pr = ed.GetString(psto);
            if (pr.Status != PromptStatus.OK) return;
            using (Transaction tr = db.TransactionManager.StartTransaction())
            {
                BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);
                if (!bt.Has(pr.StringResult))
                {
                    ed.WriteMessage("\n{0} not defined. ", pr.StringResult);
                    return;
                }
                btrOid = bt[pr.StringResult];
                SelectionSet sset = SelectAnonymousBlocks(ed);
                if (sset == null)
                    ed.WriteMessage("\nNothing selected. ");
                else
                    ed.WriteMessage("\n{0} blocks selected. ", sset.Count);
                tr.Commit();
            }
        }

        // Do the actual filtering with a SelectionAdded event handler
        SelectionSet SelectAnonymousBlocks(Editor ed)
        {
            TypedValue[] tva = new TypedValue[] { new TypedValue((int)DxfCode.Start, "INSERT") };
            SelectionFilter sf = new SelectionFilter(tva);
            PromptSelectionOptions pso = new PromptSelectionOptions();
            pso.MessageForAdding = "\nSelect block references (Enter for all): ";
            ed.SelectionAdded += new SelectionAddedEventHandler(SelectionAdded);
            try
            {
                PromptSelectionResult psr = ed.GetSelection(pso, sf);
                if (psr.Status == PromptStatus.Error) psr = ed.SelectAll(sf);
                if (psr.Status == PromptStatus.OK && psr.Value.Count > 0) return psr.Value;
                else return null;
            }
            finally
            {
                ed.SelectionAdded -= new SelectionAddedEventHandler(SelectionAdded);
            }
        }

        // Return BTR Id if not dynamic, else DynamicBlockTableRecord. Needs active transaction.
        ObjectId GetDynamicBlockTableRecordId(ObjectId oid)
        {
            BlockReference br = oid.GetObject(OpenMode.ForRead) as BlockReference;
            if (br == null) return ObjectId.Null;
            if (br.IsDynamicBlock) return br.DynamicBlockTableRecord;
            return br.BlockTableRecord;
        }
             
        void SelectionAdded(object sender, SelectionAddedEventArgs e)
        {
            for(int i = 0; i < e.AddedObjects.Count; i++){
                ObjectId oid = GetDynamicBlockTableRecordId(e.AddedObjects[i].ObjectId);
                if (oid.IsNull || oid != btrOid) e.Remove(i);
            }
        }

        ObjectId btrOid = ObjectId.Null;
    }