How to deal with nested blocks in multiple occurrences of blockreferences with nested block references

Hi all,

Im having trouble getting ALL geometry i see in my CIVIL3D model space in Dynamo.
I can get part of it to work but for some reason not everything is showing.
Below is geometry preview in Dynamo, and the civil3D model space:


As you can see, a bunch of parts are missing.
We are looking at a solidworks step export of piping. The export puts all the assemblies from solidworks in blocks and nested blocks, which is fine but there seems to be an issue that recurring blockreferences dont get imported into Dynamo properly.
The most obvious example is this Solidworks pattern which contain a bunch of pipes:

The picture above was one block, if we go into that block we can see the following block:


Inside here we can see 2 blocks, both recurring multiple times:

Finally inside these blocks, we can see another blockreference, which is the pipe itself.

This blockreference is what i’m looking for. When we go a layer deeper we get a 3D solid, so there’s no more nested blockreferences after this one. These last block references in the entire project contain the information i need, as they tell me the position of the piping and the block description tells me the diameter/shape etc.

How do i go about getting all deepest nested block references in this file?

I thought i would just get the blocks in the project I need with this filter:


I then get the objects in a block, which are all solids, so that’s good. I get the geometry and translate it according to the blockreference coordinatesystem to get the correct position.

So, the idea of the script is good, it works how i want it to work, but apparantly you can’t do this when theres copied nested blockreferences involved, Dynamo can’t see the blockreference of the deepest block is occuring multiple times in copied nested blockreferences (if that makes sense)…

What would be the best way to go about this?
I was thinking i could create a python script that has a recursive function to check the content of a blockreference, and if its not a 3Dsolid, explode the blocklayer, if it is, stop exploding.
This way i should end up with only blockreferences that contain 3D solids without all the copied references?
Before i go this route i wanted to ask for input here, since I feel like there is probably something i’m missing…

Even after exploding the nested blocks manually, i only get the geometry of the first group:


Which is odd, since when I select the blockreferences, they all have the same name so i am sure they are the same, and i can see theres a lot of them…

Another edit…

Ok so i think i found part of the issue
I went ahead and focus on the missing groups for now, so i altered the filter in such a way that i only get this specific block.
In civil3D i get 72 occurances, but somehow in Dynamo i can get 96…
All the geometry is placed over eachother at the position of the first group. Theres 96 solids in the space of these 12:

Ok for some reason…

When i explode every ‘hosting blockreference’ so to speak… so only the final blockreferences are in the project, i am able to get the full geometry how i want:
I have no clue why it works now, when the blocks were already exploded for the recurring groups…

I’d rather not manually explode everything every time ofcourse…

Should be able to. Try this:

  1. Filter the nested block references separate from the standard objects.
  2. Pull the standard object geometry as you are now.
  3. Pull the coordinate system from the nested block reference in the block.
  4. Pull the block from the block reference.
  5. Get the objects in the nested block reference.
  6. Pull the geometry of each object found in the nested block reference.
  7. Transform the nested block reference geometry (#6) by the nested block reference’s coordinate system (#3).
  8. Add the transformed geometry objects to the standard object geometry (#2).
  9. Get the coordinate system of the primary block reference.
  10. Transform all the geometry in the joined list (#8) by the coordinate system of the primary block reference. This should be complete geometry.

Note that if you have nested nested blocks, or dynamic blocks, the process will have to change further.

1 Like

Hi Jacob,

Thanks for your thorough response as always.
I’ve looked at your suggestion but i fear that this won’t work as the ‘nest levels’ are unpredictable and change throughout the project. some blocks go 10 levels deep, others 2 and some none.

What do you think of writing a recursive python function that collects all block references in model space, checks if the content of the block == 3DSolid, if it is, do nothing, if it isn’t, explode the block and do this recursively until all block references are exploded until the final layer.
I assume this would should be fairly straightforward to do and side steps any unpredictable list depth from the STEP export. Do you see issues with this method?

Correct - this is why a custom node should be used to ensure you recursively mine the geometry. Doable with C#, Python or DYF using OOTB nodes. But not with just standard nodes.

I would not recommend doing any exploding though - you’ll just make things more painful.

And here I was, thinking I was almost done :sweat_smile:

I’ll give your suggestion a try, will report back

it actually doesn’t look too complex in python @jacob.small.
Just one small question…

Can you tell me how to deal with this:


Why does python return the Autodesk.Autocad.DatabaseServices.Bockreference instead of just Blockreference, like the Select Object node?
Is there a way to convert Autodesk.Autocad.DatabaseServices.Bockreference to plain old BlockReference we see in Dynamo?

Look at this for select

Hmm, i’m not looking to query an element by name… I just dont understand why the blockreference is different from the blockreference when selecting through the other node

Is there no way to simply convert my output?

Ok so i got the data mining part done

For anyone wondering, this script takes in a list of blockreferences, and returns a list of any blockreference that references a block that contains a 3Dsolid object

import clr
clr.AddReference('AutoCADNodes')
from Autodesk.AutoCAD.DynamoNodes import BlockReference

# Input: A list of BlockReference objects
blockReferenceQueue = IN[0]
solidBlockReferences = []  # To store 3D solids
invalidBlockReferences = []  # To store invalid objects

# Process the block reference queue
while len(blockReferenceQueue) > 0:
    currentBlockReference = blockReferenceQueue.pop(0)  # Dequeue the first item
    
    if isinstance(currentBlockReference, BlockReference):
        # Check the objects inside the block reference
        blockObjects = currentBlockReference.Block.Objects
        
        for obj in blockObjects:
            if obj.GetType().Name == "Solid":  # Check if the object is a 3D solid
                solidBlockReferences.append(currentBlockReference)
            elif isinstance(obj, BlockReference):  # Check if the object is another BlockReference
                blockReferenceQueue.append(obj)
            elif isinstance(obj, list):  # Check if the object is a list of BlockReferences
                blockReferenceQueue.extend(obj)
            else:  # Add to invalid objects if none of the above
                invalidBlockReferences.append(obj)
    else:
        invalidBlockReferences.append(currentBlockReference)

# Output the results
OUT = solidBlockReferences, invalidBlockReferences

However…
The issue still remains…
I now have 72 solids, occupying the space of 12 solids:


I’m really unsure why this is happening. I know that in the solidworks file (where this STEP export is made) this part of the assembly was made by using a pattern function if i recall correctly… So its an array made by copies of one assembly. (just mentioning this for troubleshooting…)

I can’t imagine it being caused by the array in solidworks as im simply getting the deepest nested blockreference and getting it’s location…

Do you have an idea what’s going on here? @jacob.small
Perhaps @Anton_Huizinga has a chapter in his book specifically about this issue heh…


If anyone wants to give this a shot…
Here is a cleaned up DWG file with the import and the dyn file
Civil3D Step v4.dyn (66.3 KB)
purged.dwg (1.1 MB)

The contents in the Dynamo environment are put into a wrapper, which shifts from a native Civil3D element to a Dynamo reference thereto. To convert to a Dynamo object see the linked post for one such example.

Instead of returning a flat list, return a list for each primary block. Those can be flat if you’d like, or you can keep nesting in your loop. My guess is some of the objects are actually multiple solids or multiple nested blocks. Solidworks DWGs are often a bit off that way.

Good to know, thank you!

Instead of returning a flat list, return a list for each primary block. Those can be flat if you’d like, or you can keep nesting in your loop. My guess is some of the objects are actually multiple solids or multiple nested blocks. Solidworks DWGs are often a bit off that way.’

But to transform the geometry i need the deepest nested blockreferences right? I don’t understand how the geometry can be in the same place when im using the deepest blockreference (that contains nothing but the actual solid). I would expect this blockreference to be in the location we see in the model space but for some reason it isn’t

My guess is some of the objects are actually multiple solids or multiple nested blocks…’

Yes, some blockreferences are nested blockreferences with multiple layers, but down the line, every blockreference is just one solid.

Close - you need to use each coordinate system you find for nested blocks until you get to the primary one.

I recommend doing a save as, deleting all but one with two or three nested blocks in it, and building your POC on that instead of the 12 you’re using. This will make it easier to confirm the geometry is correctly locating itself as that should be about 6 solids which you can validate one by one.

1 Like

hmm ok im getting close now…

Am i correct to assume the following:
based on this example non recursive:

The deepest nested blockreference gets transformed from(CoordinateSystem.Identity) to context(blockreference.Coordinatesystem)

And then, if there was a layer above this blockreference we take the solid blockreference and transform it from(blockreference.Coordinatesystem) to context(one level less deep blockreference.Coordinatesystem)

Or should the context coordinate system always be the coordinate system from the the blockreference that contains the solid?

Or… transform it from CoordinateSystem.Identity every iteration and the context coordinates are from the blockreference layers?

EDIT:
I realize my question here is probably hard to follow… perhaps could you explain what the geometry.transforn does and why I need to do it with Coordinate system.identy (which I assume is just the dynamo basepoint) and all the coordinate systems from the layered block references

To make it easier, let’s say I’m just stringing nodes together to do this for a depth of three.
The geometry from the deepest blockreference will be transformed from coordinatesystem.identy and context= the solid’s blockreference.coordinatesystem

The transform then outputs the geometry, which goes into the next transform node, what would the input be for the from and context?

Phew…
This was a steep learning curve, but I almost got it working…
Now all that is left is some troubleshooting…
I am able to get the full geometry visible now, but it’s flipped on its side, very far away from the origin in Dynamo and there are some solids very very far away from the group in the screenshot below, circled with blue pen…

This is the full python code, perhaps there’s something obvious im not seeing…

I think the issue is with how im walking through these lists with for in loops, instead of just starting at the top, walking to the deepest nest, remove the solid blockref from the list and keep doing it until the lists are empty. Or perhaps how i define the CoordinateSystem.Identity is wrong… I am not sure.

Since it doesnt matter if i explode the groups, it always ends up on its side, i assume that the origin i’m using in the processSolid function is missing something…

import clr
clr.AddReference('AutoCADNodes')
from Autodesk.AutoCAD.DynamoNodes import BlockReference, Object
from Autodesk.DesignScript.Geometry import Geometry
from Autodesk.DesignScript.Geometry import *  # Import everything
from Autodesk.DesignScript.Geometry import CoordinateSystem as DSG_CoordinateSystem  # Rename specific class

# Input: A list of BlockReference objects
blockReferenceQueue = IN[0]
solidBlockReferences = []  # To store 3D solids
invalidBlockReferences = []  # To store invalid objects, for later troubleshooting
solidGeometry = []
usedCoordinates = []


def processBlockReference(currentBlockReference, coordinates=[]):
    if coordinates is None:
        coordinates = []
    
    blockObjects = currentBlockReference.Block.Objects
    
    for obj in blockObjects:
        if obj.GetType().Name == "Solid":  # Check if the object is a 3D solid
            coordinates.append(currentBlockReference.CoordinateSystem)
            processSolid(obj, currentBlockReference, list(coordinates))
            coordinates.clear()
            
        elif isinstance(obj, BlockReference):  # Check if the object is another BlockReference
            coordinates.append(currentBlockReference.CoordinateSystem)
            processBlockReference(obj, coordinates)
        elif isinstance(obj, list):  # Check if the object is a list of BlockReferences
            for object in obj:
                if isinstance(object, BlockReference):  # Ensure it's a BlockReference
                    coordinates.append(currentBlockReference.CoordinateSystem)
                    processBlockReference(object, coordinates)
        else:  # Add to invalid objects if none of the above
            invalidBlockReferences.append(obj)
    
transformGeometryInputList = []        
def processSolid(solid, blockReference, coordinates):
    geometry = solid.Geometry
    origin = DSG_CoordinateSystem.ByOriginVectors(
    Point.ByCoordinates(0, 0, 0),
        DSG_CoordinateSystem.Identity().XAxis,
        DSG_CoordinateSystem.Identity().YAxis,
        DSG_CoordinateSystem.Identity().ZAxis
    )    
    coordinates.append(origin)
    
    # Initialize transformedGeometry with the original geometry
    transformedCoordinateSystem = None
    transformedGeometry = geometry
    
    # Apply successive transformations, in reverse since we need to transform from the deepest blockref all the way to the top
    for i in range(len(coordinates) - 1, 0, -1):
        
        current_coord = coordinates[i]
        next_coord = coordinates[i - 1]
        # transform the coordinate system from the origin by every blockreference
        transformedCoordinateSystem = CoordinateSystem.Transform(current_coord, next_coord)
        

    # Store the final transformed geometry and its block reference
    transformedGeometry = Geometry.Transform(geometry, transformedCoordinateSystem)  
    transformGeometryInputList.append([geometry, origin, transformedCoordinateSystem])
    solidGeometry.append(transformedGeometry)
    solidBlockReferences.append(blockReference)
    usedCoordinates.append(coordinates)
    
for ref in blockReferenceQueue:
    processBlockReference(ref, [])
# Output the results
OUT = solidBlockReferences, solidGeometry, invalidBlockReferences, usedCoordinates, transformGeometryInputList

At first glimpse it seems valid, but this isn’t how I would have built the code so it’s tough to know. Unfortunately I am away from the PC for the week and as I have ‘unplugged entirely’ I have no idea what I am coming back to…

That said, what results do you get if you explode all of the objects and then pull the solid?

When i explode it until the block references are not nested, my code returns the geometry seemingly correct, its not flipped and not incredibly far away from the origin.
So is it safe to assume there’s something wrong with how I’m transforming nested solids?

Perhaps I’m better off transforming the geometry in the loop directly, with Geometry.transform(geometry, current_coord).

My thinking is that i’m using the CoordinateSystem.transform wrong in combination with Geometry.Transform.
First, i take my list of coordinatesystems (all coordinatesystems from blockreference i encounter on the way down to the solid) then i take the CoordinateSystem.Identity and transform that by each of the CS’s in the list in reverse, starting at the deepest nest.
This returns a transformed coordinatesystem which i use in transformedGeometry = Geometry.Transform(geometry, transformedCoordinateSystem)

transformGeometryInputList = []        
def processSolid(solid, blockReference, coordinates):
    geometry = solid.Geometry
    origin = DSG_CoordinateSystem.ByOriginVectors(
    Point.ByCoordinates(0, 0, 0),
        DSG_CoordinateSystem.Identity().XAxis,
        DSG_CoordinateSystem.Identity().YAxis,
        DSG_CoordinateSystem.Identity().ZAxis
    )    
    coordinates.append(origin)
    
    # Initialize transformedGeometry with the original geometry
    transformedCoordinateSystem = None
    transformedGeometry = geometry
    
    # Apply successive transformations, in reverse since we need to transform from the deepest blockref all the way to the top
    for i in range(len(coordinates) - 1, 0, -1):
        
        current_coord = coordinates[i]
        next_coord = coordinates[i - 1]
        # transform the coordinate system from the origin by every blockreference
        transformedCoordinateSystem = CoordinateSystem.Transform(current_coord, next_coord)
        

    # Store the final transformed geometry and its block reference
    transformedGeometry = Geometry.Transform(geometry, transformedCoordinateSystem)  
    transformGeometryInputList.append([geometry, origin, transformedCoordinateSystem])
    solidGeometry.append(transformedGeometry)
    solidBlockReferences.append(blockReference)
    usedCoordinates.append(coordinates)

This is how I would have approached it. Get the block, pull it’s geometry, pull it’s coordinates, transform the geometry by the coordinates, move that geometry into the parent geometry list.

Your method might works, but if may be that you’re currently doing the transforms in reverse order, or that by transforming the coordinates you’re complicating things in the reverse order.

Alright I’ll try this, thank you.

Do I have to transform the geometry by Coordinate system.identity first before transforming it by the block references coordinate system?

No - and if you were doing this before it would explain the issue. The geometry would be at the identity coordinate system to begin with. :slight_smile:

1 Like