What's new

WorldTransform doesn't seem to transform all the way to the assembly root

I'm trying to re-assemble an assembly by transforming each part with its IADOccurrence.WorldTransform() matrix but parts in subassemblies only seem to be transformed to the subassembly level, not the root assembly. In other words, WorldTransform() appears to be the same as LocalTransform().

Any idea what I'm doing wrong?
 

Attachments

  • transforms.png
    transforms.png
    544.2 KB · Views: 18

stepalibre

Alibre Super User
Can you share your code? It is always best to share the actual code, at least the important lines.

The behavior of IADOccurrence.WorldTransform() and LocalTransform() is correct if I understand things correctly. Inside a part you can have bodies/lumps that are positioned anywhere in 3D space. The same is true at the assembly level for assemblies and parts. The localtransform is local to the part, body/lump which is not necessarily the same as the worldtransform. However, a part created @0,0,0 will have a local and world position @0,0,0. This is likely why the result of WorldTransform() and LocalTransform() are the same in your image. I'm mixing topology transforms and part/component transforms, but the point is it depends. You also need to apply transformations to the part for it to work. Are you doing this? A part inserted @0,5,100 has a worldtransform @0,5,100, with a localtransform @0,5,100. Perhaps LocalTransform() is only useful if it is called from inside a subassembly as the value at the root assembly level will be the same as WorldTransform().

Where does the data used to reassemble each part come from? If you want to use a matrix then, you'll typically create a new transformation and apply it to a component.

side note:
Alibre Script API has nice convenience methods for working with assemblies that can be used in C#.
 
Last edited:
Thanks @stepalibre.

I didn't know about part transforms. Maybe WorldTransform is just the transform from the part to its parent assembly and LocalTransform is within the part so there's no single transform that goes all the way to the root assembly?

Here's my code, edited to remove error handling for clarity.

Private Function GetOccurrencesRecursive(assemblySession As IADAssemblySession) As Generic.List(Of AlibreOccurrenceInfo) Dim result As New Generic.List(Of AlibreOccurrenceInfo) Dim parentOccurrence As IADOccurrence = assemblySession.RootOccurrence() For Each occurrence As IADOccurrence In parentOccurrence.Occurrences() Dim occurrenceSession As IADSession = occurrence.DesignSession If TypeOf occurrenceSession Is IADPartSession Then Dim partSession As IADPartSession = occurrenceSession Dim occurenceInfo As New AlibreOccurrenceInfo(occurrence.Key, occurrence.Name, occurrence.WorldTransform.Array, partSession.FilePath, Nothing) result.Add(occurenceInfo) ElseIf TypeOf occurrenceSession Is IADAssemblySession Then Dim subAssemblySession As IADAssemblySession = occurrenceSession Dim subOccurrences As Generic.List(Of AlibreOccurrenceInfo) = GetOccurrencesRecursive(subAssemblySession) Dim occurenceInfo As New AlibreOccurrenceInfo(occurrence.Key, occurrence.Name, occurrence.WorldTransform.Array, subAssemblySession.FilePath, subOccurrences) result.Add(occurenceInfo) End If occurrenceSession.Close() Next Return result End Function

I don't use the WorldTransforms of the assemblies for anything even though it reads them.

Where does the data used to reassemble each part come from? If you want to use a matrix then, you'll typically create a new transformation and apply it to a component.
I'm exporting parts as STEP files then reading them in my application. Since the part STEP files are not transformed to how they're positioned in their assemblies, I'm using these transformation matrices to do that.
 
Last edited:

stepalibre

Alibre Super User
I'm exporting parts as STEP files then reading them in my application.
This is exactly what I'm doing in my projects. I make use of part and assembly transformation data in my code, converting the 4x4 matrix into an x, y, z origin point and Euler angles (IADTransformation.Decompose) for simpler insertion methods in my app.

Other options:
- open the subassemblies directly to update the parts
- use headless mode in another process to update the subassembly and then saveall and regenerate the main assembly to load the changes.

Try to step into the code under this line : Dim subOccurrences As Generic.List(Of AlibreOccurrenceInfo) = GetOccurrencesRecursive(subAssemblySession) when an assembly is detected. You may need another loop, if the recursive function is not accessing the nested children. The way assemblies work isn't exactly traversable in the API in the way you have coded, given my limited understanding.

Test out the various scenarios that match your case:
Code:
Private Function GetOccurrencesRecursive(assemblySession As IADAssemblySession) As Generic.List(Of AlibreOccurrenceInfo)
    Dim result As New Generic.List(Of AlibreOccurrenceInfo)
    Dim parentOccurrence As IADOccurrence = assemblySession.RootOccurrence()
    For Each occurrence As IADOccurrence In parentOccurrence.Occurrences()
        Dim occurrenceSession As IADSession = occurrence.DesignSession
        If TypeOf occurrenceSession Is IADPartSession Then
            Dim partSession As IADPartSession = occurrenceSession
            Dim occurenceInfo As New AlibreOccurrenceInfo(occurrence.Key,
                               occurrence.Name,
                               occurrence.WorldTransform.Array,
                               partSession.FilePath,
                               Nothing)
            result.Add(occurenceInfo)
        ElseIf TypeOf occurrenceSession Is IADAssemblySession Then
            Dim subAssemblySession As IADAssemblySession = occurrenceSession
            For Each assem_occurrence As IADOccurrence In subAssemblySession.ActiveOccurrence.Occurrences
                Dim subOccurrencesA As Generic.List(Of AlibreOccurrenceInfo) = Nothing
                Dim occurenceInfoA As New AlibreOccurrenceInfo(occurrence.Key,
               assem_occurrence.Name,
               assem_occurrence.WorldTransform.Array,
               subAssemblySession.FilePath,
               subOccurrencesA)
                result.Add(occurenceInfoA)
            Next
            For Each assem_occurrence As IADOccurrence In subAssemblySession.RootOccurrence.Occurrences
                Dim subOccurrencesA As Generic.List(Of AlibreOccurrenceInfo) = Nothing
                Dim occurenceInfoA As New AlibreOccurrenceInfo(occurrence.Key,
               assem_occurrence.Name,
               assem_occurrence.WorldTransform.Array,
               subAssemblySession.FilePath,
               subOccurrencesA)
                result.Add(occurenceInfoA)
            Next
            For Each assem_occurrence As IADOccurrence In subAssemblySession.RootOccurrence.ParentOccurrence.Occurrences
                Dim subOccurrencesA As Generic.List(Of AlibreOccurrenceInfo) = Nothing
                Dim occurenceInfoA As New AlibreOccurrenceInfo(occurrence.Key,
               assem_occurrence.Name,
               assem_occurrence.WorldTransform.Array,
               subAssemblySession.FilePath,
               subOccurrencesA)
                result.Add(occurenceInfoA)
            Next
            Dim subOccurrences As Generic.List(Of AlibreOccurrenceInfo) = GetOccurrencesRecursive(subAssemblySession)
            Dim occurenceInfo As New AlibreOccurrenceInfo(occurrence.Key,
                               occurrence.Name,
                               occurrence.WorldTransform.Array,
                               subAssemblySession.FilePath,
                               subOccurrences)
            result.Add(occurenceInfo)
        End If
        occurrenceSession.Close()
    Next
    Return result
End Function

Public Class AlibreOccurrenceInfo
    Public Property Key As Array
    Public Property Name As String
    Public Property WorldTransformArray() As Array
    Public Property FilePath As String
    Public Property SubOccurrences As List(Of AlibreOccurrenceInfo)

    Public Sub New(key As Array, name As String, worldTransformArray() As Array, filePath As String)
        Me.Key = key
        Me.Name = name
        Me.WorldTransformArray = worldTransformArray
        Me.FilePath = filePath
        Me.SubOccurrences = Nothing
    End Sub

    Public Sub New(key As Array, name As String, worldTransformArray() As Array, filePath As String, subOccurrences As List(Of AlibreOccurrenceInfo))
        Me.Key = key
        Me.Name = name
        Me.WorldTransformArray = worldTransformArray
        Me.FilePath = filePath
        Me.SubOccurrences = subOccurrences
    End Sub
End Class
Hopefully something I said helps.
 

stepalibre

Alibre Super User
Here is a usercontrol that gets transformation data from assembly components and uses it inside a helixtoolkit 3D control. The part translation is represented by black spheres.

1713035723023.png

Console output:
Code:
WorldTransform.Array() : sphere-tank_0_3<1>
Rotate:    (0, 0, 0)
Scale:     (1, 1, 1)
Translate: (0.49186342305174, 169.199846361386, 5.78666769139897)
Shear:     (0, 0, 0)
LocalTransform.Array(): sphere-tank_0_3<1>
Rotate:    (0, 0, 0)
Scale:     (1, 1, 1)
Translate: (0.49186342305174, 169.199846361386, 5.78666769139897)
Shear:     (0, 0, 0)
WorldTransform.Array() : pressure_vessel_stress_testing<1>
Rotate:    (0, 0, 0)
Scale:     (1, 1, 1)
Translate: (370.694184384271, 0.93702097921414, -0.555943141843031)
Shear:     (0, 0, 0)
LocalTransform.Array(): pressure_vessel_stress_testing<1>
Rotate:    (0, 0, 0)
Scale:     (1, 1, 1)
Translate: (370.694184384271, 0.93702097921414, -0.555943141843031)
Shear:     (0, 0, 0)
WorldTransform.Array() : jb_zq_4321-2006_14<1>
Rotate:    (0, 0, 0)
Scale:     (0.1, 0.1, 0.1)
Translate: (0.0106564567574878, 0.0907638972230416, -0.153996305995292)
Shear:     (0, 0, 0)
LocalTransform.Array(): jb_zq_4321-2006_14<1>
Rotate:    (0, 0, 0)
Scale:     (0.1, 0.1, 0.1)
Translate: (0.0106564567574878, 0.0907638972230416, -0.153996305995292)
Shear:     (0, 0, 0)

Simplified code:
Code:
Public Class My3DViewPortControl
    Inherits System.Windows.Controls.UserControl
    Public AssemblySession As AlibreX.IADAssemblySession
    Public Hook As IAutomationHook
    Public Root As IADRoot
    Public Sub New()
        Me.Content = Create3DViewPort()
    End Sub
    Private Function Create3DViewPort() As Grid
        Dim myGrid As New Grid()
        Dim model_grp As New Model3DGroup()
        Dim meshBuilder As New MeshBuilder(False, False)
        meshBuilder.AddSphere(New Point3D(0, 0, 0),10,32,32)
        Dim mesh = meshBuilder.ToMesh(True)
        Dim materials As Material() = {
            MaterialHelper.CreateMaterial(Colors.Black),
            MaterialHelper.CreateMaterial(Colors.Blue)
        }
        Hook = GetObject(, "AlibreX.AutomationHook")
        Root = Hook.Root
        AssemblySession = Root.TopmostSession
        For Each item2 As IADOccurrence In AssemblySession.RootOccurrence.Occurrences
            Dim aa = item2.WorldTransform.Decompose()
            Console.WriteLine("WorldTransform.Array() : " & item2.Name)
            Dim transform As IADTransformation = item2.WorldTransform
            Dim decomposed As IDecomposedTransformData = transform.Decompose()
            Console.WriteLine("Rotate:    (" & decomposed.RotateX & ", " & decomposed.RotateY & ", " & decomposed.RotateZ & ")")
            Console.WriteLine("Scale:     (" & decomposed.ScaleX & ", " & decomposed.ScaleY & ", " & decomposed.ScaleZ & ")")
            Console.WriteLine("Translate: (" & decomposed.TranslateX & ", " & decomposed.TranslateY & ", " & decomposed.TranslateZ & ")")
            Console.WriteLine("Shear:     (" & decomposed.ShearXY & ", " & decomposed.ShearYZ & ", " & decomposed.ShearZX & ")")
            Dim bb = item2.LocalTransform.Decompose()
            Console.WriteLine("LocalTransform.Array(): " & item2.Name)
            Dim transformB As IADTransformation = item2.LocalTransform
            Dim decomposedB As IDecomposedTransformData = transformB.Decompose()
            Console.WriteLine("Rotate:    (" & decomposedB.RotateX & ", " & decomposedB.RotateY & ", " & decomposedB.RotateZ & ")")
            Console.WriteLine("Scale:     (" & decomposedB.ScaleX & ", " & decomposedB.ScaleY & ", " & decomposedB.ScaleZ & ")")
            Console.WriteLine("Translate: (" & decomposedB.TranslateX & ", " & decomposedB.TranslateY & ", " & decomposedB.TranslateZ & ")")
            Console.WriteLine("Shear:     (" & decomposedB.ShearXY & ", " & decomposedB.ShearYZ & ", " & decomposedB.ShearZX & ")")
            Dim translateTransform As New TranslateTransform3D(decomposedB.TranslateX, decomposedB.TranslateY, decomposedB.TranslateZ)
            Dim scaleTransform As New ScaleTransform3D(decomposedB.ScaleX, decomposedB.ScaleY, decomposedB.ScaleZ)
            Dim rotateTransformX As New RotateTransform3D(New AxisAngleRotation3D(New Vector3D(1, 0, 0), decomposedB.RotateX))
            Dim rotateTransformY As New RotateTransform3D(New AxisAngleRotation3D(New Vector3D(0, 1, 0), decomposedB.RotateY))
            Dim rotateTransformZ As New RotateTransform3D(New AxisAngleRotation3D(New Vector3D(0, 0, 1), decomposedB.RotateZ))
            Dim trans_g As New Transform3DGroup()
            trans_g.Children.Add(rotateTransformX)
            trans_g.Children.Add(rotateTransformY)
            trans_g.Children.Add(rotateTransformZ)
            trans_g.Children.Add(translateTransform)
            model_grp.Children.Add(New GeometryModel3D With {
                .Geometry = mesh,
                .Transform = trans_g,
                .Material = materials(0),
                .BackMaterial = materials(0)
            })
        Next  
        Dim hVp3D As New HelixViewport3D With {
            .ShowFrameRate = True,
            .Background = Brushes.Bisque,
            .HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch,
            .VerticalAlignment = System.Windows.VerticalAlignment.Stretch
        }
        hVp3D.Children.Add(New DefaultLights())
        hVp3D.Children.Add(New ModelVisual3D With {.Content = model_grp})
        Grid.SetRow(hVp3D, 0)
        Grid.SetColumn(hVp3D, 0)
        myGrid.Children.Add(hVp3D)
        Hook = Nothing
        Root = Nothing
        Return myGrid
    End Function
End Class

 
Last edited:
This is exactly what I'm doing in my projects. I make use of part and assembly transformation data in my code, converting the 4x4 matrix into an x, y, z origin point and Euler angles (IADTransformation.Decompose) for simpler insertion methods in my app.

Awesome. Are parts in subassemblies transformed correctly with just a single transformation matrix from Alibre? The code and screenshot you posted next seem to not do subassemblies, if I'm reading it right.

Other options:
- open the subassemblies directly to update the parts
- use headless mode in another process to update the subassembly and then saveall and regenerate the main assembly to load the changes.

"update", "regenerate"? Are these operations in Alibre? Perhaps I have to somehow update the fact that the subassemblies are all part of one big assembly after opening its file? I'm already using headless mode so perhaps it's missing out on something the GUI would do automatically. I might try heady mode.


Try to step into the code under this line : Dim subOccurrences As Generic.List(Of AlibreOccurrenceInfo) = GetOccurrencesRecursive(subAssemblySession) when an assembly is detected. You may need another loop, if the recursive function is not accessing the nested children. The way assemblies work isn't exactly traversable in the API in the way you have coded, given my limited understanding.

It is accessing all the children at all levels of subassembly, so that's not the issue.

Test out the various scenarios that match your case:

None of those go arbitrary levels deep. Are you saying try just importing the direct parts of subassemblies in a non-recursive way to see if they transform correctly?
 

stepalibre

Alibre Super User
Awesome. Are parts in subassemblies transformed correctly with just a single transformation matrix from Alibre? The code and screenshot you posted next seem to not do subassemblies, if I'm reading it right.
In your main assembly you have the get a direct reference to the subassembly as if you are working at the root assembly level. You can open the assembly and get a direct reference many ways.
"update", "regenerate"? Are these operations in Alibre? Perhaps I have to somehow update the fact that the subassemblies are all part of one big assembly after opening its file? I'm already using headless mode so perhaps it's missing out on something the GUI would do automatically. I might try heady mode.
Headless mode is fine.
It is accessing all the children at all levels of subassembly, so that's not the issue.
None of those go arbitrary levels deep. Are you saying try just importing the direct parts of subassemblies in a non-recursive way to see if they transform correctly?
Yes. Those are examples for you to review, run and debug to understand which will get the correct reference for the actual transform you need.

In my projects I never need to go more than 2 or 3 levels deep. For nested subassemblies this is essentially my solution:

1713047825366.png
Code:
        For Each top_assembly_level As IADOccurrence In AssemblySession.RootOccurrence.Occurrences
            Console.WriteLine(top_assembly_level.Name & " : top_assembly_level")
            Console.WriteLine("WorldTransform.Array() : " & top_assembly_level.Name)
            Dim transformA As IADTransformation = top_assembly_level.WorldTransform
            Dim decomposedA As IDecomposedTransformData = transformA.Decompose()
            Console.WriteLine("Rotate:    (" & decomposedA.RotateX & ", " & decomposedA.RotateY & ", " & decomposedA.RotateZ & ")")
            Console.WriteLine("Scale:     (" & decomposedA.ScaleX & ", " & decomposedA.ScaleY & ", " & decomposedA.ScaleZ & ")")
            Console.WriteLine("Translate: (" & decomposedA.TranslateX & ", " & decomposedA.TranslateY & ", " & decomposedA.TranslateZ & ")")
            Console.WriteLine("Shear:     (" & decomposedA.ShearXY & ", " & decomposedA.ShearYZ & ", " & decomposedA.ShearZX & ")")
            Console.WriteLine("LocalTransform.Array(): " & top_assembly_level.Name)
            Dim transformB As IADTransformation = top_assembly_level.LocalTransform
            Dim decomposedB As IDecomposedTransformData = transformB.Decompose()
            Console.WriteLine("Rotate:    (" & decomposedB.RotateX & ", " & decomposedB.RotateY & ", " & decomposedB.RotateZ & ")")
            Console.WriteLine("Scale:     (" & decomposedB.ScaleX & ", " & decomposedB.ScaleY & ", " & decomposedB.ScaleZ & ")")
            Console.WriteLine("Translate: (" & decomposedB.TranslateX & ", " & decomposedB.TranslateY & ", " & decomposedB.TranslateZ & ")")
            Console.WriteLine("Shear:     (" & decomposedB.ShearXY & ", " & decomposedB.ShearYZ & ", " & decomposedB.ShearZX & ")")
            For Each traverse_subassembly_to_get_parts As IADOccurrence In top_assembly_level.Occurrences
                Console.WriteLine(travse_subassembly_to_get_parts.Name & " : traverse_subassembly_to_get_parts")
                Console.WriteLine("WorldTransform.Array() : " & travse_subassembly_to_get_parts.Name)
                Dim transformC As IADTransformation = travse_subassembly_to_get_parts.WorldTransform
                Dim decomposedC As IDecomposedTransformData = transformC.Decompose()
                Console.WriteLine("Rotate:    (" & decomposedC.RotateX & ", " & decomposedC.RotateY & ", " & decomposedC.RotateZ & ")")
                Console.WriteLine("Scale:     (" & decomposedC.ScaleX & ", " & decomposedC.ScaleY & ", " & decomposedC.ScaleZ & ")")
                Console.WriteLine("Translate: (" & decomposedC.TranslateX & ", " & decomposedC.TranslateY & ", " & decomposedC.TranslateZ & ")")
                Console.WriteLine("Shear:     (" & decomposedC.ShearXY & ", " & decomposedC.ShearYZ & ", " & decomposedC.ShearZX & ")")
                Console.WriteLine("LocalTransform.Array(): " & travse_subassembly_to_get_parts.Name)
                Dim transformD As IADTransformation = travse_subassembly_to_get_parts.LocalTransform
                Dim decomposedD As IDecomposedTransformData = transformD.Decompose()
                Console.WriteLine("Rotate:    (" & decomposedD.RotateX & ", " & decomposedD.RotateY & ", " & decomposedD.RotateZ & ")")
                Console.WriteLine("Scale:     (" & decomposedD.ScaleX & ", " & decomposedD.ScaleY & ", " & decomposedD.ScaleZ & ")")
                Console.WriteLine("Translate: (" & decomposedD.TranslateX & ", " & decomposedD.TranslateY & ", " & decomposedD.TranslateZ & ")")
                Console.WriteLine("Shear:     (" & decomposedD.ShearXY & ", " & decomposedD.ShearYZ & ", " & decomposedD.ShearZX & ")")
            Next
        Next

Console output:

The key is top_assembly_level.Occurrences being nested inside RootOccurrence.Occurrences. I do more work up front to ensure code calling this For Each block is ready instead of doing type checking and other handling within the loops.
 

NateLiquidGravity

Alibre Super User
If you are only editing one level deep then WorldTransform and LocalTransform will be the same. Even if you have multiple levels deep they could be the same if every level is positioned at 0,0,0 without rotation - but you probably knew that. WorldTransform only goes to the root assembly of the assembly that was opened.

For this example if your top level assembly is structured like:
Code:
TopAssembly
├── SubAssemblyA
│   ├── Part1
│   └── SubAssemblyB
│       └── Part2
└── Part3
Lets also assume that nothing is perfectly located on 0,0,0 without rotation.
If you opened the file TopAssembly then:
TopAssembly​
NA (root assembly)​
NA​
NA​
SubAssemblyA​
WorldTransform​
=​
LocalTransform​
Part1​
WorldTransform​
≠​
LocalTransform​
SubAssemblyB​
WorldTransform​
≠​
LocalTransform​
Part2​
WorldTransform​
≠​
LocalTransform​
Part3​
WorldTransform​
=​
LocalTransform​

But if you opened the file SubAssemblyA then:
TopAssembly​
NA (not open)​
NA​
NA​
SubAssemblyA​
NA (root assembly)​
NA​
NA​
Part1​
WorldTransform​
=​
LocalTransform​
SubAssemblyB​
WorldTransform​
=​
LocalTransform​
Part2​
WorldTransform​
≠​
LocalTransform​
Part3​
NA (not open)​
NA​
NA​

So if you want the true position of someting in the TopAssembly you need to open that file.

Also if you have lots of code use the Code tool found in the second "More Options..."
1713060642449.png
 
Last edited:

stepalibre

Alibre Super User
The API is confusing. The code I've shared here and the Alibre Script assembly methods work perfectly and are reliable.
If you are only editing one level deep then WorldTransform and LocalTransform will be the same.
The World is the root assembly transformation and local is the local transformation relative to the occurrence parent I believe at any depth. When using top_assembly_level.Occurrences nested inside RootOccurrence.Occurrences, there is no distinction between a part or assembly in my code. LuminescentLoom's code is checking for a part or assembly. Compare our code.

Code:
WorldTransform.Array() : sphere-tank_0_3<1>
Rotate:    (0, 0, 0)
Scale:     (1, 1, 1)
Translate: (755.178877962063, 520.82184039513, 424.634928584439)
Shear:     (0, 0, 0)
LocalTransform.Array(): sphere-tank_0_3<1>
Rotate:    (0, 0, 0)
Scale:     (1, 1, 1)
Translate: (300.491863423052, 469.199846361386, 305.786667691399)
Shear:     (0, 0, 0)

1713062571135.png

WorldTransform ( parent root transformation - top_assembly_level [assembly]) and LocalTransform ( relative to sphere-tank_0<1> [assembly]) for sphere-tank_0_3<1> is correct and this is the case at any depth.

This works at least in my projects where I have assemblies made in top down and bottom up configurations. And similar to what LuminescentLoom mentioned about STEP files not being transformed in their positions, I have code to account for this exact issue and the math checks out.

This code works on parts and assemblies as an occurence can be either. My solution is hard coded for a specific depth level, which is handled in a class and includes other 3D APIs that loads the Alibre data and step files.

1713063119575.png

Code:
Code:
For Each top_assembly_level As IADOccurrence In AssemblySession.RootOccurrence.Occurrences
            Console.WriteLine(top_assembly_level.Name & " : top_assembly_level")
            Console.WriteLine("WorldTransform.Array() : " & top_assembly_level.Name)
            Dim transformA As IADTransformation = top_assembly_level.WorldTransform
            Dim decomposedA As IDecomposedTransformData = transformA.Decompose()
            Console.WriteLine("Rotate:    (" & decomposedA.RotateX & ", " & decomposedA.RotateY & ", " & decomposedA.RotateZ & ")")
            Console.WriteLine("Scale:     (" & decomposedA.ScaleX & ", " & decomposedA.ScaleY & ", " & decomposedA.ScaleZ & ")")
            Console.WriteLine("Translate: (" & decomposedA.TranslateX & ", " & decomposedA.TranslateY & ", " & decomposedA.TranslateZ & ")")
            Console.WriteLine("Shear:     (" & decomposedA.ShearXY & ", " & decomposedA.ShearYZ & ", " & decomposedA.ShearZX & ")")
            Console.WriteLine("LocalTransform.Array(): " & top_assembly_level.Name)
            Dim transformB As IADTransformation = top_assembly_level.LocalTransform
            Dim decomposedB As IDecomposedTransformData = transformB.Decompose()
            Console.WriteLine("Rotate:    (" & decomposedB.RotateX & ", " & decomposedB.RotateY & ", " & decomposedB.RotateZ & ")")
            Console.WriteLine("Scale:     (" & decomposedB.ScaleX & ", " & decomposedB.ScaleY & ", " & decomposedB.ScaleZ & ")")
            Console.WriteLine("Translate: (" & decomposedB.TranslateX & ", " & decomposedB.TranslateY & ", " & decomposedB.TranslateZ & ")")
            Console.WriteLine("Shear:     (" & decomposedB.ShearXY & ", " & decomposedB.ShearYZ & ", " & decomposedB.ShearZX & ")")
            For Each travse_subassembly_to_get_parts As IADOccurrence In top_assembly_level.Occurrences
                Console.WriteLine(travse_subassembly_to_get_parts.Name & " : travse_subassembly_to_get_parts")
                Console.WriteLine("WorldTransform.Array() : " & travse_subassembly_to_get_parts.Name)
                Dim transformC As IADTransformation = travse_subassembly_to_get_parts.WorldTransform
                Dim decomposedC As IDecomposedTransformData = transformC.Decompose()
                Console.WriteLine("Rotate:    (" & decomposedC.RotateX & ", " & decomposedC.RotateY & ", " & decomposedC.RotateZ & ")")
                Console.WriteLine("Scale:     (" & decomposedC.ScaleX & ", " & decomposedC.ScaleY & ", " & decomposedC.ScaleZ & ")")
                Console.WriteLine("Translate: (" & decomposedC.TranslateX & ", " & decomposedC.TranslateY & ", " & decomposedC.TranslateZ & ")")
                Console.WriteLine("Shear:     (" & decomposedC.ShearXY & ", " & decomposedC.ShearYZ & ", " & decomposedC.ShearZX & ")")
                Console.WriteLine("LocalTransform.Array(): " & travse_subassembly_to_get_parts.Name)
                Dim transformD As IADTransformation = travse_subassembly_to_get_parts.LocalTransform
                Dim decomposedD As IDecomposedTransformData = transformD.Decompose()
                Console.WriteLine("Rotate:    (" & decomposedD.RotateX & ", " & decomposedD.RotateY & ", " & decomposedD.RotateZ & ")")
                Console.WriteLine("Scale:     (" & decomposedD.ScaleX & ", " & decomposedD.ScaleY & ", " & decomposedD.ScaleZ & ")")
                Console.WriteLine("Translate: (" & decomposedD.TranslateX & ", " & decomposedD.TranslateY & ", " & decomposedD.TranslateZ & ")")
                Console.WriteLine("Shear:     (" & decomposedD.ShearXY & ", " & decomposedD.ShearYZ & ", " & decomposedD.ShearZX & ")")
            Next
        Next

Console output:
Code:
sphere-tank_0<1> : top_assembly_level
WorldTransform.Array() : sphere-tank_0<1>
Rotate:    (0, 0, 0)
Scale:     (1, 1, 1)
Translate: (454.687014539011, 51.6219940337439, 118.84826089304)
Shear:     (0, 0, 0)
LocalTransform.Array(): sphere-tank_0<1>
Rotate:    (0, 0, 0)
Scale:     (1, 1, 1)
Translate: (454.687014539011, 51.6219940337439, 118.84826089304)
Shear:     (0, 0, 0)
sphere-tank_0_3<1> : travse_subassembly_to_get_parts
WorldTransform.Array() : sphere-tank_0_3<1>
Rotate:    (0, 0, 0)
Scale:     (1, 1, 1)
Translate: (755.178877962063, 520.82184039513, 424.634928584439)
Shear:     (0, 0, 0)
LocalTransform.Array(): sphere-tank_0_3<1>
Rotate:    (0, 0, 0)
Scale:     (1, 1, 1)
Translate: (300.491863423052, 469.199846361386, 305.786667691399)
Shear:     (0, 0, 0)
pressure_vessel_stress_testing<1> : travse_subassembly_to_get_parts
WorldTransform.Array() : pressure_vessel_stress_testing<1>
Rotate:    (0, 0, 0)
Scale:     (1, 1, 1)
Translate: (1125.38119892328, 352.559015012958, 418.292317751197)
Shear:     (0, 0, 0)
LocalTransform.Array(): pressure_vessel_stress_testing<1>
Rotate:    (0, 0, 0)
Scale:     (1, 1, 1)
Translate: (670.694184384271, 300.937020979214, 299.444056858157)
Shear:     (0, 0, 0)
jb_zq_4321-2006_14<1> : travse_subassembly_to_get_parts
WorldTransform.Array() : jb_zq_4321-2006_14<1>
Rotate:    (0, 0, 0)
Scale:     (0.1, 0.1, 0.1)
Translate: (754.697670995768, 351.712757930967, 418.694264587045)
Shear:     (0, 0, 0)
LocalTransform.Array(): jb_zq_4321-2006_14<1>
Rotate:    (0, 0, 0)
Scale:     (0.1, 0.1, 0.1)
Translate: (300.010656456757, 300.090763897223, 299.846003694005)
Shear:     (0, 0, 0)
pressure_vessel_stress_testing<1> : top_assembly_level
WorldTransform.Array() : pressure_vessel_stress_testing<1>
Rotate:    (0, 0, 0)
Scale:     (1, 1, 1)
Translate: (1229.93996076611, 372.58447473981, 295.880770319118)
Shear:     (0, 0, 0)
LocalTransform.Array(): pressure_vessel_stress_testing<1>
Rotate:    (0, 0, 0)
Scale:     (1, 1, 1)
Translate: (1229.93996076611, 372.58447473981, 295.880770319118)
Shear:     (0, 0, 0)

So if you want the true position of someting in the TopAssembly you need to open that file.
Great way to visualize this. AssemblySession.RootOccurrence gives you complete access to everything in the assembly. You can traverse it as shown in my code. Can you explain what you mean by "open the file" and how to do it in code for each case? Those other files don't need to be opened in order to access them if the Top level assembly is open or referenced from headless mode. I'm not understanding the distinction here.
 
Awesome, got it working. Thanks @stepalibre.
transformed OK.png
The key was doing this:

Code:
For Each occurrence0 As IADOccurrence In AssemblySession.RootOccurrence.Occurrences
    For Each occurrence1 As IADOccurrence In occurrence0.Occurrences
        For Each occurrence2 As IADOccurrence In occurrence1.Occurrences
            For Each occurrence3 As IADOccurrence In occurrence2.Occurrences
              ...
            Next
        Next
    Next
Next

instead of this:

Code:
For Each occurrence0 As IADOccurrence In AssemblySession.RootOccurrence.Occurrences
    For Each occurrence1 As IADOccurrence In occurrence0.DesignSession.RootOccurrence.Occurrences
        For Each occurrence2 As IADOccurrence In occurrence1.DesignSession.RootOccurrence.Occurrences
            For Each occurrence3 As IADOccurrence In occurrence2.DesignSession.RootOccurrence.Occurrences
              ...
            Next
        Next
    Next
Next

I'm just bumbling my way through what a DesignSession is but it looks like getting the DesignSession of a subassembly make it behave like opening its file as the root assembly.
 

stepalibre

Alibre Super User
Great! This was more straightforward. Early on I asked if you were creating new transformations and applying them to components, which is an entirely different path.
 

NateLiquidGravity

Alibre Super User
Can you explain what you mean by "open the file" and how to do it in code for each case?
I'm not contradicting you. I'm fact your World Transform vs Local Transform results are exactly matching the example in my first chart.
No code needed to say a different way: The root occurrence is whatever assembly file is opened regardless of it's hierarchy in other files of the project.
 
No code needed to say a different way: The root occurrence is whatever assembly file is opened regardless of it's hierarchy in other files of the project.

Except that a suboccurrence's DesignSession.RootOccurrence() returns an IADOccurrence which is treated as the root by the WorldTransform of its parts even though it's not the file that was explicitly opened with IADRoot.OpenFileEx().

Your chart is how I assumed it worked before I ran into this problem but it doesn't really work that way unless IADOccurrence.DesignSession is "opening the file".
 
Last edited:

NateLiquidGravity

Alibre Super User
Sorry but there is no way a subassembly opened from it's own file by itself can tell you that. What would it say in the case that it is used in two assemblies or in two places in the same assembly?
 
Sorry but there is no way a subassembly opened from it's own file by itself can tell you that. What would it say in the case that it is used in two assemblies or in two places in the same assembly?

Sure. I'm pointing out that the WorldTransform doesn't go all the way to the root of the file you opened if you navigate to the occurrences in the way I was initially doing. It does seem natural that the root should be the file you explicitly opened, but it isn't.

In my current mental model, IADOccurrence.DesignSession acts like the session returned by IADRoot.openFileEx() and both are roots.

Sorry, you might have been replying to stepalibre there when he said "AssemblySession.RootOccurrence gives you complete access to everything in the assembly." That only includes the assembly described by the AssemblySession, not other ones closer to the "true" root, which as you point out is impossible if you didn't open the file.
 
Last edited:

stepalibre

Alibre Super User
Those are only snippets from larger projects. I'm integrating several 3D and CAD APIs and as I stated the math and logic all works. Maybe I need to be more precise in my code examples and explanations, but I try to keep it simple and was on limited time.

I'm not sharing all the code that makes my solutions work. I open/get a reference to a document all in code to obtain the occurrence a part or assembly (instances of an arrayed assemblies and their children) at any level. The IADOccurrence interface is powerfully yet is also confusing in the greater context, for me compared to other CAD APIs I use.

These properties below allow you to traverse the tree in any direction in combination with nesting logic and other techniques. You can open/reference Alibre documents at any point in your program which is why it doesn't matter if the file is open or not. You can do it all programmatically.

DesignSession Returns the design session of the occurrence.
Key Gets the Persistent Key property of this occurrence.
LocalTransform Returns local transformation of this occurrence that is relative to its immediate parent.
Name Returns the occurrence's name.
NestLevel Gets nest level of this occurrence.
Occurrences Returns child occurrences.
ParentAssemblySession Returns the assembly session of parent occurrence.
ParentOccurrence Returns the parent occurrence.
Path Gets path of occurrence from the root occurrence to this occurrence.
RootAssemblySession Returns the assembly session of root occurrence.
RootOccurrence Returns the root occurrence.
TotalLeafNodes Gets total number of leaf nodes under this occurrence.
Type Returns a pre-defined constant that identifies the type of this object. (AD_OCCURRENCE)
WorldTransform Returns world transformation of this occurrence that is relative to the root assembly.

Depending on where these properties are called at any level in the tree, determines what they return. That was the point of those For Each loops with the different collections I posted in GetOccurrencesRecursive.

My projects are integrated with devDept Eyeshot versions 10 and 2024 assembly APIs and Alibre's IADOccurrence interface is logically the same. Nested blocks from Rhino 8 (and rhino3dm library) and STEP assemblies also match my implementation using IADOccurrence interface. This all works perfectly with HelixToolkit and Assimp STEP exporter all integrated with my projects.


My point here is that a complete solution involves much more than what I posted earlier and Alibre APIs work as I stated in the context of a larger solution not in a few lines of code.
 
Last edited:

stepalibre

Alibre Super User
I'm not contradicting you. I'm fact your World Transform vs Local Transform results are exactly matching the example in my first chart.
No code needed to say a different way: The root occurrence is whatever assembly file is opened regardless of it's hierarchy in other files of the project.
I understand your points. My view is that the process shown in your chart can be driven or controlled programmatically.
 
Top