Inventor VBA to Traverse Design Tree and Find Parts with Specific User Parameter String

Inventor VBA to Traverse Design Tree and Find Parts with Specific User Parameter String

eric.frissell26WKQ
Advocate Advocate
2,039 Views
10 Replies
Message 1 of 11

Inventor VBA to Traverse Design Tree and Find Parts with Specific User Parameter String

eric.frissell26WKQ
Advocate
Advocate

Hi, I'm looking for some information on what a basic macro (preferably not iLogic) would look like that traverses your design tree to find parts/assemblies which have a specific user parameter string.  I've written a dozen or so custom macros and am pretty good at modifying them to get what I need but I've never written anything that traverses your design tree.  If anyone has any example code which traverses the design tree I'd love to take a look at it to learn from it

0 Likes
Accepted solutions (1)
2,040 Views
10 Replies
Replies (10)
Message 2 of 11

andrewiv
Advisor
Advisor
Accepted solution

This example is straight out of the programming help in Inventor.

Public Sub AssemblyCount()
    ' Set reference to active document.
    ' This assumes the active document is an assembly
    Dim oDoc As Inventor.AssemblyDocument
    Set oDoc = ThisApplication.ActiveDocument
    
    ' Get assembly component definition
    Dim oCompDef As Inventor.ComponentDefinition
    Set oCompDef = oDoc.ComponentDefinition

    Dim sMsg As String
    Dim iLeafNodes As Long
    Dim iSubAssemblies As Long
    
    ' Get all occurrences from component definition for Assembly document
    Dim oCompOcc As ComponentOccurrence
    For Each oCompOcc In oCompDef.Occurrences
        ' Check if it's child occurrence (leaf node)
        If oCompOcc.SubOccurrences.Count = 0 Then
            Debug.Print oCompOcc.Name
            iLeafNodes = iLeafNodes + 1
        Else
            Debug.Print oCompOcc.Name
            iSubAssemblies = iSubAssemblies + 1
            Call processAllSubOcc(oCompOcc, _
                                sMsg, _
                                iLeafNodes, _
                                iSubAssemblies) ' subassembly
        End If
    Next
    
    Debug.Print "No of leaf nodes    : " + CStr(iLeafNodes)
    Debug.Print "No of sub assemblies: " + CStr(iSubAssemblies)
End Sub

' This function is called for processing sub assembly.  It is called recursively
' to iterate through the entire assembly tree.
Private Sub processAllSubOcc(ByVal oCompOcc As ComponentOccurrence, _
                             ByRef sMsg As String, _
                             ByRef iLeafNodes As Long, _
                             ByRef iSubAssemblies As Long)
    
    Dim oSubCompOcc As ComponentOccurrence
    For Each oSubCompOcc In oCompOcc.SubOccurrences
        ' Check if it's child occurrence (leaf node)
        If oSubCompOcc.SubOccurrences.Count = 0 Then
            Debug.Print oSubCompOcc.Name
            iLeafNodes = iLeafNodes + 1
        Else
            sMsg = sMsg + oSubCompOcc.Name + vbCr
            iSubAssemblies = iSubAssemblies + 1

            Call processAllSubOcc(oSubCompOcc, _
                                  sMsg, _
                                  iLeafNodes, _
                                  iSubAssemblies)
        End If
    Next
End Sub

Andrew In’t Veld
Designer / CAD Administrator

0 Likes
Message 3 of 11

eric.frissell26WKQ
Advocate
Advocate

Yep, should have looked there first, thanks

0 Likes
Message 4 of 11

_dscholtes_
Advocate
Advocate

The below code can be found on this website: https://modthemachine.typepad.com/my_weblog/2009/03/accessing-assembly-components.html . When you remove the stuff dealing with the 'Level' you get the minimum required to travel a model tree. The rest of the article is also worth a read.

Public Sub TraverseAssemblySample()
    ' Get the active assembly.
    Dim oAsmDoc As AssemblyDocument
    Set oAsmDoc = ThisApplication.ActiveDocument
    Debug.Print oAsmDoc.DisplayName

    ' Call the function that does the recursion.
    Call TraverseAssembly(oAsmDoc.ComponentDefinition.Occurrences, 1)
End Sub

Private Sub TraverseAssembly(Occurrences As ComponentOccurrences, Level As Integer)
    ' Iterate through all of the occurrence in this collection.  This
    ' represents the occurrences at the top level of an assembly.
    Dim oOcc As ComponentOccurrence
    For Each oOcc In Occurrences
        ' Print the name of the current occurrence.
        Debug.Print Space(Level * 3) & oOcc.Name

        ' Check to see if this occurrence represents a subassembly
        ' and recursively call this function to traverse through it.
        If oOcc.DefinitionDocumentType = kAssemblyDocumentObject Then
            Call TraverseAssembly(oOcc.SubOccurrences, Level + 1)
        End If
    Next
End Sub

 

0 Likes
Message 5 of 11

eric.frissell26WKQ
Advocate
Advocate

Hey, thanks for the replies.  From the example @_dscholtes_  posted I was able to build something similar to what I'm looking for except now I'm having a compile error

Public length As Double

Public Sub CompactorBuilder()
' Get compactor assembly.
Dim Compactor As AssemblyDocument
Set Compactor = ThisApplication.ActiveDocument

' Get length
'length = InputBox("Enter the length for compactor. I.e. 35.88", LengAssign)
length = 24.55


'Debug.Print Compactor.DisplayName
'Debug.Print "FTF Compactor length is " & length

' Call recursion function
Call TraverseAssembly(Compactor.ComponentDefinition.Occurrences, 1)
End Sub

Private Sub TraverseAssembly(Occurrences As ComponentOccurrences, Level As Integer)

' Iterate through assembly
Dim oOcc As ComponentOccurrence
Dim length As Double
length = 24.55

For Each oOcc In Occurrences

'FTF Paramater
Dim oDoc As Document

On Error Resume Next
If oDoc.DocumentType = kPartDocumentObject Then
Dim oPartDoc As PartDocument
Dim oUserParams As UserParameter
Set oPartDoc = oDoc
Set oUserParams = oPartDoc.ComponentDefinition.Parameters.UserParameters

If oUserParams.Count > 0 Then
Dim FTFParam As UserParameter
For Each FTFParam In oUserParams

If FTFParam.Name = "FTF" Then
Debug.Print Space(Level * 3) & oOcc.Name





ElseIf oDoc.DocumentType = kAssemblyDocumentObject Then
Dim oAsmDoc As AssemblyDocument
Dim oUserAsmParams As UserParameter
Set oAsmDoc = oDoc
Set oUserAsmParams = oAsmDoc.ComponentDefinition.Parameters.UserParameters

If oUserParams.Count > 0 Then
Dim FTFAsmParam As UserParameter
For Each FTFAsmParam In oUserParams

If FTFParam.Name = "FTF" Then
Debug.Print Space(Level * 3) & oOcc.Name


Else

End If


' Print the name of the current occurrence.
'Debug.Print Space(Level * 3) & oOcc.Name & length


' Get/verify material

' Check to see if this occurrence represents a subassembly
' and recursively call this function to traverse through it.
If oOcc.DefinitionDocumentType = kAssemblyDocumentObject Then
Call TraverseAssembly(oOcc.SubOccurrences, Level + 1)
End If

Next

End Sub

 

First error is it will say block if without end if.  If I solve that error I get a Next without For error.  Any help would be appreciated!

0 Likes
Message 6 of 11

eric.frissell26WKQ
Advocate
Advocate

Seems like it won't let me edit my reply - another thing I was wondering was if there's a different way I can apply the userParameters function rather than breaking it down into a kpartdoc or kassemblydoc as both parts and assemblies can have the FTF paramater

0 Likes
Message 7 of 11

_dscholtes_
Advocate
Advocate

 

This is not the solution to your block if without end if or Next without For errors, but I noticed:

 

For Each oOcc In Occurrences

'FTF Paramater
Dim oDoc As Document

On Error Resume Next
If oDoc.DocumentType = kPartDocumentObject Then
<rest of code>

 

 

In your code, you don't set oDoc (set oDoc = <whatever document>). You set oPartDoc later on, but  I don't see anything happening to oDoc. You're travelling through the model tree by occurrences. This means you need to get the document of the current occurrence:

 

set oDoc =  oOcc.Definition.Document

 


block if without end if or Next without For errors are the result of 'untidy' work. When you don't close your for-next and if-else-end if statements correctly you get them straight in the face as soon as you try to run your code.  I can spot a couple of them in the code you posted.
Indentation of sections of code is the biggest help to keep an overview. Additional methods I use is to add the component from the for-next loop to the next statement, e.g. Next oOcc, so I can see to which loop the Next statement belongs if the section of code in it is large. I sometimes also add the complete If statement as a comment behind the End If statement for the same reason, e.g. End If  'If oOcc Is Nothing.


You can always create a new sub and fill it by copying code of the non-working code piece by piece and try to run it. Start with copying the for-next and if-end statements (not the code in them) and you'll soon find where you missed one. It's my last resort especially with large sections of code, but I always find the error.

0 Likes
Message 8 of 11

eric.frissell26WKQ
Advocate
Advocate

Yeah, I agree with your comments about untidy work...bad habit I'm working on breaking.  Took it to heart though and tried working this through in what I think is the right way by using ModTheMachine's example you posted, and then breaking that down into a few private subs.  This one runs but it appears to be missing a lot of the components and I really don't understand why that's the case yet.  The intention is that I will be iterating through all parts and assemblies to find a user parameter called 'FTF' and change that parameter.  Currently I have one macro that is somewhat successful but it uses the 'AllReferencedDocuments' and isn't getting the assemblies (I use an if/else if for if document type is kpart or kassembly) and it's also not set up the way I'd like it to be, hence this updated one.  

 

Currently this macro is set up to print the activedocument name, FTF and length, then go into the traverseassembly.  As it's going through traverse assembly it prints out the name, determines whether it's a part or assembly document, goes to that sub, then prints FTF changed.   As I said it's missing a lot of components and I have a object (class?) issue.  oOcc defined as ComponentOccurrence and is passed from private sub TraverseAssembly to private sub AssemblyParameter or PartParameter.  Because it's passed as a ComponentOccurrence I'm not sure how to use that to get to the ComponentDefinition which is needed to get Parameters and UserParameters.  I don't think I can redefine ActiveDocument in the private sub as it would be looking at the open assembly, rather than the part or subassembly?

 

I thought the private sub might clean this up a bit if I could figure out how to define the part I'm working on to pass it

 

 

 

This is the code I'd like to run because I think it's simpler but it's missing components.

 

Public length As Double

Public Sub CompactorBuilder()
' Get compactor assembly.
Dim CompAsm As AssemblyDocument
Set CompAsm = ThisApplication.ActiveDocument

' Get length
'length = InputBox("Enter the length for compactor. I.e. 35.88", LengAssign)
length = 24.55


Debug.Print CompAsm.FullDocumentName
Debug.Print "FTF Compactor length is "

' Call recursion function
Call TraverseAssembly(CompAsm.ComponentDefinition.Occurrences, 1)

End Sub

Private Sub TraverseAssembly(Occurrences As ComponentOccurrences, Level As Integer)

' Iterate through assembly
Dim oOcc As ComponentOccurrence
For Each oOcc In Occurrences

' Print the name of the current occurrence.
Debug.Print Space(Level * 3) & oOcc.Name

'Verify part or assembly document

'Call appropriate part or assembly sub
If oOcc.DefinitionDocumentType = kPartDocumentObject Then
Call PartParameter(oOcc)

ElseIf oOcc.DefinitionDocumentType = kAssemblyDocumentObject Then
Call AssemblyParameter(oOcc)

End If

' Get/verify material

' Check to see if this occurrence represents a subassembly
' and recursively call this function to traverse through it.
If oOcc.DefinitionDocumentType = kAssemblyDocumentObject Then
Call TraverseAssembly(oOcc.SubOccurrences, Level + 1)
End If

Next

End Sub

Private Sub AssemblyParameter(oOcc As ComponentOccurrence)

Debug.Print oOcc.Name & "FTF changed"

End Sub

Private Sub PartParameter(oOcc As ComponentOccurrence)

Debug.Print oOcc.Name & "FTF changed"

End Sub

0 Likes
Message 9 of 11

_dscholtes_
Advocate
Advocate

@eric.frissell26WKQ wrote:

  Because it's passed as a ComponentOccurrence I'm not sure how to use that to get to the ComponentDefinition which is needed to get Parameters and UserParameters. 

This should get you on your way:

'Set reference to the document defining the occurrence
Dim oDoc As PartDocument
Set oDoc = oOcc.Definition.Document
                                    
'Set reference to property set of passed document
Dim oUDPropSet As PropertySet
Set oUDPropSet = oDoc.PropertySets.item("Inventor User Defined Properties")
0 Likes
Message 10 of 11

eric.frissell26WKQ
Advocate
Advocate

@_dscholtes_ Thanks, sorry to keep asking you questions, it's hard to get answers to VBA questions, but I do have one question regarding the method you posted.  I believe user parameters is only accessible through either AssemblyDocument -> AssemblyComponentDefinition or PartDocument-> PartComponentDefinition and the only way I can see this working is via an if statement but not sure the code to get it; i.e.

 

if oOcc.document.definition = (kpartdocument here?) then

     dim oPartDoc as partdocument

     proceed with user parameter change

else if oOcc.document.definition = (kassembly document?) then

     dim oAsmDoc as assemblydocument

     proceed with user parameter change

0 Likes
Message 11 of 11

_dscholtes_
Advocate
Advocate

@eric.frissell26WKQ 
1. Well, it is possible to use a 'dim oDoc as Document' for both part and assembly documents. In that case, the Document object only contains the properties that a part and assembly file have in common. The property sets are one of those, so you don't have to use the specific partdocument / assemblydocument types.

2. Property sets are not part of the component definition, but part of the document. 

3. Let me quote a previous post of yours, since the answer is already there:


@eric.frissell26WKQ wrote:

'Call appropriate part or assembly sub
If oOcc.DefinitionDocumentType = kPartDocumentObject Then
    Call PartParameter(oOcc)
ElseIf oOcc.DefinitionDocumentType = kAssemblyDocumentObject Then

    Call AssemblyParameter(oOcc)
End If

0 Likes