Author Topic: Collapsing Xrefs  (Read 3720 times)

0 Members and 1 Guest are viewing this topic.

Bashworth

  • Guest
Collapsing Xrefs
« on: April 18, 2014, 05:24:54 PM »
Hello all.  Long time no see for many of you.  Probably hello for the first time for most of you.

I have been out of the AutoCAD development (or any development) game for a number of years, and trying to knock the rust off.

I have a series of drawings that rely heavily on xrefs, many many nested tiers.  Options that I need to display are controlled via loading and unloading some of these nested and parent xrefs.

From time to time, we get a request to send the .DWG version of a file to a client.

I had a routine in VBA that would go through, gather a list of the names of all the loaded xrefs, then load all xrefs, bind, explode, and delete the blocks named according to the previously unloaded xrefs.  It worked alright, but since we're in 64bit land, it's SLOOOOWWWWWWWW.. Sometimes taking 10-15 minutes to run.   Granted, some of that is my fault.  It's ugly code, but I don't have the time to sit down and really pour a lot of effort into organizing it better, when I'd rather just scrap VBA all together.

I'm trying to work this into a .Net solution, and I'm struggling with the lack of documentation for how to accomplish what I'm doing.

I got some code, which returns to me the names of the loaded and unloaded PARENT xrefs, but isn't giving me the nested ones.

I'm primarily working in VB.Net, but I have several converters for C# to VB.Net as well.

I'm using AutoCAD 2013, and Visual Studio Express 2010 as my IDE.

Any pointers would be great, and thanks in advance.

Here's what I have so far:
Code: [Select]
Imports Autodesk.AutoCAD.Runtime
Imports Autodesk.AutoCAD.DatabaseServices
Imports Autodesk.AutoCAD.Geometry
Imports Autodesk.AutoCAD.ApplicationServices
Imports Autodesk.AutoCAD.EditorInput

Namespace GADWGCollapser

    Public Class Commands

        'define the command
        <CommandMethod("gacollapse")>
        Public Shared Sub GetListOfXrefs()
            Dim doc As Document =
                Application.DocumentManager.MdiActiveDocument
            Dim db As Database =
                doc.Database
            Dim xg As XrefGraph =
                db.GetHostDwgXrefGraph(True)
            Dim root As GraphNode =
                xg.RootNode
            Dim ed As Editor =
                doc.Editor
            Dim xrUnLoaded As New List(Of String)
            Dim xrLoaded As New List(Of String)
            Dim xrParents As New List(Of String)

            'Iterate through all xrefs in drawing
            For n As Integer = 0 To root.NumOut - 1
                Dim child As XrefGraphNode =
                    TryCast(root.Out(n), XrefGraphNode)

                'Discover if this is a parent xref, used for later binding and exploding
                If child.IsNested = False Then
                    xrParents.Add(child.Name)
                    'just for testing, comment the following line out prior to completion
                    ed.WriteMessage("Parent " + child.Name + vbLf)
                End If

                'Determine if the xref is loaded, or unloaded
                'Only loaded xrefs should be kept later, after
                'binding and converting to a block
                If child.XrefStatus = XrefStatus.Resolved Then
                    xrLoaded.Add(child.Name)
                    'just for testing, comment the following line out prior to completion
                    ed.WriteMessage("Loaded " + child.Name + vbLf)
                ElseIf child.XrefStatus = XrefStatus.Unloaded Then
                    xrUnLoaded.Add(child.Name)
                    'just for testing, comment the following line out prior to completion
                    ed.WriteMessage("Unloaded " + child.Name + vbLf)
                End If
            Next
        End Sub
    End Class
End Namespace

Jeff H

  • Needs a day job
  • Posts: 6144
Re: Collapsing Xrefs
« Reply #1 on: April 18, 2014, 06:34:40 PM »
Why not just etransmit and use insert and purge option?

Bashworth

  • Guest
Re: Collapsing Xrefs
« Reply #2 on: April 18, 2014, 07:02:31 PM »
Etransmit won't work if any of the xrefs are unloaded.

The clients want it all in one single DWG.

Bashworth

  • Guest
Re: Collapsing Xrefs
« Reply #3 on: May 08, 2014, 02:29:10 PM »
I've had some time today to work on this again, and I've made some progress.

I can now separate the parent and child xrefs into two separate lists, as well as getting the list of loaded vs unloaded.

It loads all unloaded xrefs, and binds everything up nicely.

Now what I need to do is get rid of the blocks that represent the xrefs that were previously unloaded.

Is there a way to just remove those from the blocktable, or do I have to open every parent block reference, and delete the sub block first?

Is there another alternative that might be easier?

Code so far:
Code: [Select]
Imports Autodesk.AutoCAD.Runtime
Imports Autodesk.AutoCAD.DatabaseServices
Imports Autodesk.AutoCAD.Geometry
Imports Autodesk.AutoCAD.ApplicationServices
Imports Autodesk.AutoCAD.EditorInput

Namespace GADWGCollapser

    Public Class Commands

        'define the command
        <CommandMethod("gacollapse")>
        Public Shared Sub CollapseDrawing()
            Dim doc As Document =
                Application.DocumentManager.MdiActiveDocument
            Dim xrUNLID As New ObjectIdCollection
            Dim xrPARID As New ObjectIdCollection
            Dim db As Database =
                doc.Database
            Dim xg As XrefGraph =
                db.GetHostDwgXrefGraph(True)
            Dim root As GraphNode =
                xg.RootNode
            Dim numOfnodes As Integer =
                xg.NumNodes
            Dim ed As Editor =
                doc.Editor
            Dim xrUnLoaded As New List(Of String)
            Dim xrLoaded As New List(Of String)
            Dim xrParents As New List(Of String)

            'ed.WriteMessage("Collecting XREF map of General Arrangement" + vbCrLf)
            'Iterate through all xrefs in the active drawing
            For n As Integer = 0 To numOfnodes - 1
                Dim child As XrefGraphNode =
                    TryCast(xg.GetXrefNode(n), XrefGraphNode)
                'Determine if this is a parent xref.
                If child.IsNested = False Then
                    'if parent, add xref file name to list
                    xrParents.Add(child.Name)
                    'if parent, add ID for later binding
                    xrPARID.Add(child.BlockTableRecordId)
                End If

                'Determine if the xref is loaded or unloaded
                'if loaded, add name to list for later use
                If child.XrefStatus = XrefStatus.Resolved Then
                    xrLoaded.Add(child.Name)
                    'if not loaded, add name to list for later purging
                ElseIf child.XrefStatus = XrefStatus.Unloaded Then
                    xrUnLoaded.Add(child.Name)
                    'add ID for loading all unloaded xrefs
                    'since unloaded xrefs cannot be bound
                    xrUNLID.Add(child.BlockTableRecordId)
                End If
            Next

            'ed.WriteMessage("Loading all unloaded XREFS" + vbCrLf)
            'load all unloaded xrefs
            db.ReloadXrefs(xrUNLID)

            'ed.WriteMessage("Binding XREFS" + vbCrLf)
            'bind all parent xrefs
            db.BindXrefs(xrPARID, True)

            'ed.WriteMessage("Sorting and Deleting Unneeded Blocks" + vbCrLf)
            'remove blocks from the drawing that used to be the unloaded xrefs

        End Sub
    End Class
End Namespace

Bashworth

  • Guest
Re: Collapsing Xrefs
« Reply #4 on: May 30, 2014, 03:18:48 PM »
Out of other projects to work on, so back onto this for the day.
I don't know if it's the slight headache, or the general IDGAF attitude I embody every day of my life, but I'm still not having much luck with finding any solutions, that I'm happy with, to accomplish this.
I'm tickling through a routine that goes through and finds every block in the current drawing that has blocks inside of it.  I can use that to flag blocks to explode, then run a deletion routine to eliminate any blocks that match the unloaded list.. but that seems very inelegant, and involves repeated throwing back into loops upon loops.   Is there a more concise way of accomplishing this?
One of the reasons I opted to take this routine from VBA to .NET, was the drool inducing time it took to run, so I'm all about any shortcuts that someone might know of.
 

WILL HATCH

  • Bull Frog
  • Posts: 450
Re: Collapsing Xrefs
« Reply #5 on: May 30, 2014, 03:48:06 PM »
since you have already compiled a list of the xrefs, then you have an easy path to their BlockTableRecord.  From there you can call the GetBlockReferenceIds method to get all block references, then you would just need to process them, explode them into their parent space and you're done.

Bashworth

  • Guest
Re: Collapsing Xrefs
« Reply #6 on: May 30, 2014, 04:48:54 PM »
Thanks.. I don't know why I didn't come across the GetBlockReferenceIds method before, but that simplifies things a bit.
Maybe when/if I get more savvy with this, I can figure out a method for redefining the parent block definition to remove the unloaded blocks, then refresh or reinsert the block reference to update it.  That's a battle for another day. 
Thank you again.

WILL HATCH

  • Bull Frog
  • Posts: 450
Re: Collapsing Xrefs
« Reply #7 on: May 30, 2014, 05:41:08 PM »
As in turn off visibility of certain xrefs afterwards?

Bashworth

  • Guest
Re: Collapsing Xrefs
« Reply #8 on: June 02, 2014, 04:47:24 PM »
Was more looking for a way to remove the nested block definitions entirely. 
End result, is a single DWG file with no xrefs, and only the blocks remaining that correspond to the xrefs that were loaded into the starting drawing.

WILL HATCH

  • Bull Frog
  • Posts: 450
Re: Collapsing Xrefs
« Reply #9 on: June 03, 2014, 01:25:00 AM »
That should be quite easy. For a nested block you must find all references to the parent block definition, and insert a copy into modelspace which is transformed by the block transform on each parent block reference. 

I was interested by this idea so put something together as a concept. I'm sure there are numerous cases where this would fail.

I can see that it needs code to handle 'empty' blocks which were only a container to other block references which have been transformed to modelspace.

Code - C#: [Select]
  1.         [CommandMethod("unnest")]
  2.         public void UnNestBlocks()
  3.         {
  4.             Document doc = Application.DocumentManager.MdiActiveDocument;
  5.             Editor ed = doc.Editor;
  6.             Database db = doc.Database;
  7.             PromptSelectionOptions pso = new PromptSelectionOptions();
  8.             pso.MessageForAdding = "\nSelect Blocks to Un-Nest";
  9.             SelectionFilter sf = new SelectionFilter(new TypedValue[1] { new TypedValue((int)DxfCode.Start, "INSERT") });
  10.             PromptSelectionResult psr = ed.GetSelection(pso, sf);
  11.             if (psr.Status != PromptStatus.OK) return;
  12.             Transaction tr = doc.TransactionManager.StartTransaction();
  13.             try
  14.             {
  15.                 _tr = tr;
  16.                 BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);
  17.                 _msId = bt[BlockTableRecord.ModelSpace];
  18.                 _ms = (BlockTableRecord)tr.GetObject(_msId, OpenMode.ForWrite);
  19.                 HashSet<BlockReference> blocksToErase = new HashSet<BlockReference>();
  20.  
  21.                 //get unique block defs from the block reference selection
  22.                 foreach (var id in psr.Value.GetObjectIds().Select(x => (BlockReference)tr.GetObject(x, OpenMode.ForRead)).Select(x => x.BlockTableRecord).Distinct())
  23.                 {
  24.                     BlockTableRecord btr = (BlockTableRecord)tr.GetObject(id, OpenMode.ForRead);
  25.                     foreach (ObjectId refId in btr.GetBlockReferenceIds(true, true)) //process all references to block of interest
  26.                     {
  27.                         BlockReference bRef = (BlockReference)tr.GetObject(refId, OpenMode.ForRead);
  28.                         if (bRef.OwnerId == _msId) continue;//if this block is in modelspace then skip it as we are at the desired result
  29.                         BlockReference cRef = (BlockReference)bRef.Clone();
  30.                         WalkTable(bRef.OwnerId, ref cRef);
  31.                         blocksToErase.Add(bRef);
  32.                     }
  33.                 }
  34.                 foreach (var block in blocksToErase)
  35.                 {
  36.                     block.UpgradeOpen();
  37.                     block.Erase();
  38.                 }
  39.             }
  40.             finally
  41.             {
  42.                 tr.Commit();
  43.                 tr.Dispose();
  44.                 _tr = null;
  45.                 _msId = ObjectId.Null;
  46.                 _ms = null; ;
  47.             }
  48.             ed.Regen();
  49.         }
  50.         private Transaction _tr;
  51.         private ObjectId _msId;
  52.         private BlockTableRecord _ms;
  53.         /// <summary>
  54.         /// Walks up the block table looking for the path(s) to modelspace.  Pass a clone of the inquiring block (we're going to transform up the tree, then erase original after.
  55.         /// </summary>
  56.         /// <param name="ownerId"></param>
  57.         /// <returns></returns>
  58.         private void WalkTable(ObjectId ownerId, ref BlockReference bRef)
  59.         {
  60.             if (ownerId == _msId)// if the owner is in modelspace then send copy into modelspace
  61.             {
  62.                 _ms.AppendEntity(bRef);
  63.                 _tr.AddNewlyCreatedDBObject(bRef, true);
  64.                 return;
  65.             }
  66.             else//otherwise let's see if this tree gets referenced to modelspace
  67.             {
  68.                 BlockTableRecord owner = (BlockTableRecord)_tr.GetObject(ownerId, OpenMode.ForWrite);
  69.                 foreach (ObjectId id in owner.GetBlockReferenceIds(true,true))
  70.                 {
  71.                     BlockReference oRef = (BlockReference)_tr.GetObject(id, OpenMode.ForRead);
  72.                     BlockReference cRef = (BlockReference)bRef.Clone();
  73.                     cRef.TransformBy(oRef.BlockTransform);
  74.                     WalkTable(oRef.OwnerId, ref cRef);
  75.                     if (cRef.OwnerId == ObjectId.Null) cRef.Dispose();//if ownerId is null then this object was not appended to database
  76.                 }
  77.             }
  78.         }