Apply Finish to Sub Assembly within Assembly

Apply Finish to Sub Assembly within Assembly

mitchELAHB
Advocate Advocate
300 Views
9 Replies
Message 1 of 10

Apply Finish to Sub Assembly within Assembly

mitchELAHB
Advocate
Advocate

I'm trying to add a finish to a sub assembly within an assembly. I have the following code that selects every face of a sub assembly when the user picks one in an assembly, but when I try to add those faces to a finish feature definition I get the error "The parameter is incorrect. (0x80070057 (E_INVALIDARG))", due to the line setting the finish definition. It seems as if something is incorrect with the face collection. Is there a better way of applying finishes, or what am I doing wrong here?

 

Sub Main()
	
	Dim oAsm As AssemblyDocument = ThisDoc.Document
	Dim oFaceCol As FaceCollection = ThisApplication.TransientObjects.CreateFaceCollection
	Dim oFaceProx As FaceProxy
	Dim oComponent As ComponentOccurrence = ThisApplication.CommandManager.Pick(SelectionFilterEnum.kAssemblyOccurrenceFilter, "select subassembly")
	
	If oComponent.Definition.Type = ObjectTypeEnum.kAssemblyComponentDefinitionObject
		Dim oDef As AssemblyComponentDefinition = oComponent.Definition
		For Each oFaceProx In GetProxies(oComponent)
			ThisApplication.CommandManager.DoSelect(oFaceProx)
			oFaceCol.Add(oFaceProx)
		Next
	End If
	
	MsgBox(oFaceCol.Count)
	
	'Create finish definition.
	Dim oFinishColour = iProperties.Value("Custom", "Box Colour")
	Dim oAppearance As Object = oAsm.AppearanceAssets.Item(oFinishColour)
	Dim oFinishFeatures As FinishFeatures = oAsm.ComponentDefinition.Features.FinishFeatures
	Dim oFinishDef As FinishDefinition = oFinishFeatures.CreateFinishDefinition(oFaceCol, FinishTypeEnum.kMaterialCoatingFinishType, "Powder Coat", oAppearance)

'Create finish feature 
	Try
	    Dim oFinish As FinishFeature = oFinishFeatures.Add(oFinishDef)
	Catch
		Logger.Trace("Couldn't create finish in " & oAsm.DisplayName & ".")
	End Try

End Sub

Function GetProxies(oComponent As ComponentOccurrence) As ObjectCollection
	
	Dim oProxCol As ObjectCollection = ThisApplication.TransientObjects.CreateObjectCollection
	Dim oDef As AssemblyComponentDefinition = oComponent.Definition
	
	For Each oComp As ComponentOccurrence In oDef.Occurrences
		If oComp.Definition.Type = ObjectTypeEnum.kAssemblyComponentDefinitionObject
			For Each oProx1 As FaceProxy In GetProxies(oComp)
				Dim oProx2 As FaceProxy
				Call oComponent.CreateGeometryProxy(oProx1, oProx2)
				oProxCol.Add(oProx2)
			Next
		Else
			Dim oCompDef As PartComponentDefinition = oComp.Definition
			For Each oBod As SurfaceBody In oCompDef.SurfaceBodies
				For Each oFace As Face In oBod.Faces
				Dim oProx1 As FaceProxy
				Dim oProx2 As FaceProxy
				Call oComp.CreateGeometryProxy(oFace, oProx1)
				Call oComponent.CreateGeometryProxy(oProx1, oProx2)
				oProxCol.Add(oProx2)
				Next
			Next
		End If
	Next
	
	Return oProxCol
	
End Function

 

0 Likes
301 Views
9 Replies
Replies (9)
Message 2 of 10

mitchELAHB
Advocate
Advocate

I've also tried setting it up surfacebodies.face instead of face proxies, but get stuck in the same place:

 

Sub Main()
	
	Dim oDoc As AssemblyDocument = ThisDoc.Document
	
	'set up face collection 
	Dim oFace As Face
	Dim oFaceCol As FaceCollection = ThisApplication.TransientObjects.CreateFaceCollection
	
	For Each oComponent In oDoc.ComponentDefinition.Occurrences
		
		If oComponent.Suppressed = True Then Continue For
			
		If TypeOf oComponent.Definition Is VirtualComponentDefinition Then Continue For
		
		GetFaces(oComponent, oFaceCol)
		
	Next
	
	For Each oFace In oFaceCol
		
		ThisApplication.CommandManager.DoSelect(oFace)
		
	Next
	
	MsgBox("Number of faces: " & oFaceCol.Count)
	
	'Create finish definition.
	Dim oFinish As FinishFeature
	Dim oFinishColour = iProperties.Value("Custom", "Box Colour")
	Dim oAppearance As Object = oDoc.AppearanceAssets.Item(oFinishColour)
	Dim oFinishFeatures As FinishFeatures = oDoc.ComponentDefinition.Features.FinishFeatures
    Dim oFinishDef As FinishDefinition = oFinishFeatures.CreateFinishDefinition(oFaceCol, FinishTypeEnum.kMaterialCoatingFinishType, "Powder Coat", oAppearance)
	
    'Create finish feature and rename
	Try
	    oFinish = oFinishFeatures.Add(oFinishDef)
		'oFinish.Name = oFinishName
	Catch
		Logger.Trace("Couldn't create finish in " & oDoc.DisplayName & ".")
	End Try
	
End Sub

Sub GetFaces(oOcc As ComponentOccurrence, oFaceCollection As FaceCollection)
		
		'check if occurance is assembly
		If oOcc.DefinitionDocumentType = DocumentTypeEnum.kAssemblyDocumentObject Then
			
			For Each oComponent In oOcc.SubOccurrences
				
				'skip if component component is suppressed
				If oComponent.Suppressed = True Then Continue For
				
				'skip if virtual component
				If TypeOf oComponent.Definition Is VirtualComponentDefinition Then Continue For
				
				GetFaces(oComponent, oFaceCollection) 
				
			Next
			
		Else
		
		    For Each oSurfaceBody In oOcc.SurfaceBodies
		        For Each oFace In oSurfaceBody.Faces
		            oFaceCollection.Add(oFace)
		        Next
		    Next
			
		End If
	
End Sub

 

0 Likes
Message 3 of 10

WCrihfield
Mentor
Mentor

Hi @mitchELAHB. Besides just 'Parameter is incorrect', is there anything else you can tell us about the error message, such as which line of code, or which method/property the error is originating from?  Knowing that would help us to be able to help you more efficiently.

I don't really do much with the new FinishFeatures yet, but it seems to me like you should probably just stick directly with the ComponentOccurrence.SurfaceBodies of the 'Picked' component object, and the faces of those bodies, without attempting to step down into its sub components recursively.  All the face proxies should most likely belong to the same top level component, and should likely all be in the 'context' (definition & 3D coordinate space) of the main assembly for them to be used by a feature being defined within the definition of the main assembly, and with an appearance asset saved within the main assembly.  So, you should probably just simplify your GetFaces routine, and try that.

Wesley Crihfield

EESignature

(Not an Autodesk Employee)

0 Likes
Message 4 of 10

WCrihfield
Mentor
Mentor

I just typed up a test iLogic code within an internal iLogic rule in an assembly to use for testing purposes.  After a little testing, I found out that when the component is a part, then just getting the faces of each body in the component.SurfaceBodies collection is enough, and works just fine.  But when the component is an assembly, then recursing into the component.SubOccurrences is necessary after all, and worked.  You may have just put the 'recursion' part of your routine in the wrong place (before collecting faces), when it should have been after you have collected the faces of the current occurrence, not sure.  There are certainly several opportunities in there for something to go wrong.

Below is the very similar code example I just tested with, and seemed to be working OK.  It could certainly be developed further though.  I left the old Function in there, just for reference, even though it would only work for parts as it is right now.  It would need to be able to pass the 'collection' on to recursion runs, which is awkward when the collection is created within the function and 'returned' at the end.  The Sub routine with ByRef collection passed in seemed like a better fit, as you were doing in your last example.

In this example, I just used the 'Appearance' named 'Red', because it is a very common one, but that Appearance 'needed' to be copied from the global appearance library into that main assembly first, or it would throw that same type of error.  Once I copied that appearance into the assembly, the error went away.  There is a way to automate retrieving that appearance from the global library, if it is not found in the local document, then copying it into the local document, then getting / using that local copy when it proceeds past that point.  The 'Asset' needs to exist in the Document before it can be applied to 'the model' in that document.

Sub Main
	Dim oInvApp As Inventor.Application = ThisApplication
	Dim oADoc As AssemblyDocument = TryCast(ThisDoc.FactoryDocument, Inventor.AssemblyDocument)
	If oADoc Is Nothing Then Return
	Dim oADef As AssemblyComponentDefinition = oADoc.ComponentDefinition
	
	Dim oPickedOcc As ComponentOccurrence = oInvApp.CommandManager.Pick(SelectionFilterEnum.kAssemblyOccurrenceFilter, "Select a Component.")
	If oPickedOcc Is Nothing Then Return
	
	Dim oFaceColl1 As Inventor.FaceCollection = oInvApp.TransientObjects.CreateFaceCollection()
	GetFaces2(oPickedOcc, oFaceColl1)
	
'	Dim oFaceColl2 As Inventor.FaceCollection = GetFaces(oPickedOcc)
	
	Dim oAppAsset As Inventor.Asset = oADoc.AppearanceAssets.Item("Red")
	
	Dim oFinishFeats As FinishFeatures = oADef.Features.FinishFeatures
	Dim oFinishDef As FinishDefinition = oFinishFeats.CreateFinishDefinition( _
	oFaceColl1, FinishTypeEnum.kMaterialCoatingFinishType, "Powder Coat", oAppAsset)
	Dim oFinishFeat As FinishFeature = oFinishFeats.Add(oFinishDef)
End Sub

'works OK when the component is a part, not a sub assembly
Function GetFaces(occ As Inventor.ComponentOccurrence) As Inventor.FaceCollection
	If (occ Is Nothing) OrElse occ.Suppressed Then Return Nothing
	If TypeOf occ.Definition Is VirtualComponentDefinition Then Return Nothing
	Dim oFaceColl As Inventor.FaceCollection = ThisApplication.TransientObjects.CreateFaceCollection()
	For Each oBody As SurfaceBody In occ.SurfaceBodies
		For Each oFace As Face In oBody.Faces
			oFaceColl.Add(oFace)
		Next 'oFace
	Next 'oBody
	Return oFaceColl
End Function

'works when the component is either a part, or sub assembly
Sub GetFaces2(occ As Inventor.ComponentOccurrence, ByRef faceColl As Inventor.FaceCollection)
	If (occ Is Nothing) OrElse occ.Suppressed Then Return
	If TypeOf occ.Definition Is VirtualComponentDefinition Then Return
	If faceColl Is Nothing Then faceColl = ThisApplication.TransientObjects.CreateFaceCollection()
	For Each oBody As SurfaceBody In occ.SurfaceBodies
		For Each oFace As Face In oBody.Faces
			faceColl.Add(oFace)
		Next 'oFace
	Next 'oBody
	If occ.SubOccurrences IsNot Nothing AndAlso occ.SubOccurrences.Count > 0 Then
		For Each oSubOcc As Inventor.ComponentOccurrence In occ.SubOccurrences
			GetFaces2(oSubOcc, faceColl)
		Next
	End If
End Sub

If this solved your problem, or answered your question, please click ACCEPT SOLUTION .
Or, if this helped you, please click (LIKE or KUDOS) 👍.

Wesley Crihfield

EESignature

(Not an Autodesk Employee)

0 Likes
Message 5 of 10

WCrihfield
Mentor
Mentor

I did a little more tweaking and made the 'Function' routine work just fine, by adding an 'Optional ByRef' input parameter for the collection.  This way, we do not need to 'create' the collection in the 'Main' routine then pass it to the routine, making it easier to use.  But the Function can 'pass' that variable to itself when doing 'recursion' runs, maintaining the same collection throughout the process, then 'Return' the collection it created at the end.  Below is that altered block of code which is almost identical to the previous Sub routine.

Function GetFaces(occ As Inventor.ComponentOccurrence, _
	Optional ByRef faceColl As Inventor.FaceCollection = Nothing) As Inventor.FaceCollection
	If (occ Is Nothing) OrElse occ.Suppressed Then Return Nothing
	If TypeOf occ.Definition Is VirtualComponentDefinition Then Return Nothing
	If faceColl Is Nothing Then faceColl = ThisApplication.TransientObjects.CreateFaceCollection()
	For Each oBody As SurfaceBody In occ.SurfaceBodies
		For Each oFace As Face In oBody.Faces
			faceColl.Add(oFace)
		Next 'oFace
	Next 'oBody
	If occ.SubOccurrences IsNot Nothing AndAlso occ.SubOccurrences.Count > 0 Then
		For Each oSubOcc As Inventor.ComponentOccurrence In occ.SubOccurrences
			GetFaces(oSubOcc, faceColl)
		Next 'oSubOcc
	End If
	Return faceColl
End Function

Wesley Crihfield

EESignature

(Not an Autodesk Employee)

0 Likes
Message 6 of 10

mitchELAHB
Advocate
Advocate

Hi Wesley, thank for taking the time to look into this for me. 

 

I should have mentioned that the error was 'The parameter is incorrect. (0x80070057 (E_INVALIDARG))' on the line:

Dim oFinishDef As FinishDefinition = oFinishFeatures.CreateFinishDefinition(oFaceCol, FinishTypeEnum.kMaterialCoatingFinishType, "Powder Coat", oAppearance)

My scripts would work fine for parts within the assembly, but not for sub assemblies.

 

I have tried out the snippets you have posted, and am running into issues at the same place, setting the finish definition. The code works fine if I select a part, but errors if I select a sub assembly. All my sub assemblies themselves have sub assemblies, I thought a recursive funtion would ensure every face was collected, but maybe that's where it's running into issues where yours isn't?

 

0 Likes
Message 7 of 10

WCrihfield
Mentor
Mentor

Hi @mitchELAHB.  I do have a folder containing some Inventor files that I do 'new' code testing on, but I do not have a lot of 'non-production' or 'unimportant' assemblies with that much depth to them that I am willing to test experimental code on that I have no use for.  However, I did find a couple that I took the risk on,  ran the rule on them, and it seemed to work just fine for me.  The component I chose in both cases was a sub assembly that had its own sub assemblies as well as parts.  Maybe there is just something special about your dataset that is different than the ones I was testing on.  Or maybe something in the Inventor version difference (I'm using 2024.3.5 at the moment).  I don't think the ones I tested on had any 'derived' stuff in them, no 'simplified' stuff or 'substitutes', and no components with multiple ModelStates.  Maybe if one or more of those variables were involved they could cause complications in this type of operation, but I am not sure.

Wesley Crihfield

EESignature

(Not an Autodesk Employee)

0 Likes
Message 8 of 10

mitchELAHB
Advocate
Advocate

Hi @WCrihfield. After much testing I have found out that having multiples of the same part within an assembly causes the issue, in my case rivets. I've currently got it set up to apply a finish to every part in the top assembly,  and skip the the rivets when adding to the face collection. This is currently working, but ideally I'd like the rivets included too.  Current code:

Sub Main
	
	Dim oInvApp As Inventor.Application = ThisApplication
	Dim oADoc As AssemblyDocument = TryCast(ThisDoc.FactoryDocument, Inventor.AssemblyDocument)
	If oADoc Is Nothing Then Return
	Dim oADef As AssemblyComponentDefinition = oADoc.ComponentDefinition

	'set list of components to skip
	Dim oSkipList As New ArrayList
	oSkipList.Clear 
	oSkipList.Add("20110")
	oSkipList.Add("20224")
	
	Dim oFaceColl As Inventor.FaceCollection
	
	For Each oComponent In oADoc.ComponentDefinition.Occurrences
		
		If oComponent.Suppressed = True Then Continue For
		If TypeOf oComponent.Definition Is VirtualComponentDefinition Then Continue For
		
		GetFaces(oComponent, oFaceColl, oSkipList)
		
	Next
	
	Dim oFinishColour = iProperties.Value("Custom", "Box Colour")
	Dim oAppearance As Object = oADoc.AppearanceAssets.Item(oFinishColour)
	
	Dim oFinishFeats As FinishFeatures = oADef.Features.FinishFeatures
	Dim oFinishDef As FinishDefinition = oFinishFeats.CreateFinishDefinition(oFaceColl, FinishTypeEnum.kMaterialCoatingFinishType, "Powder Coat", oAppearance)
	Dim oFinishFeat As FinishFeature = oFinishFeats.Add(oFinishDef)
	
End Sub


Function GetFaces(occ As Inventor.ComponentOccurrence, Optional ByRef faceColl As Inventor.FaceCollection = Nothing, Optional oSkipList As ArrayList = Nothing) As Inventor.FaceCollection
	
	If (occ Is Nothing) OrElse occ.Suppressed Then Return Nothing
	If TypeOf occ.Definition Is VirtualComponentDefinition Then Return Nothing
	
	Dim oOccName As String = Split(occ.Name, ":")(0)
	If oSkipList.Contains(oOccName) Then Return Nothing
	
	If faceColl Is Nothing Then faceColl = ThisApplication.TransientObjects.CreateFaceCollection()
		
	For Each oBody As SurfaceBody In occ.SurfaceBodies
		For Each oFace As Face In oBody.Faces
			faceColl.Add(oFace)
		Next 'oFace
	Next 'oBody
	
	If occ.SubOccurrences IsNot Nothing AndAlso occ.SubOccurrences.Count > 0 Then
		For Each oSubOcc As Inventor.ComponentOccurrence In occ.SubOccurrences
			GetFaces(oSubOcc, faceColl, oSkipList)
		Next 'oSubOcc
	End If
	
	Return faceColl
	
End Function
Message 9 of 10

WCrihfield
Mentor
Mentor

I modified a copy of my example code to get all faces of all top level components, with the Function working recursively to get faces of sub occurrences, just like your example.  Then I found one of my multi-level assemblies which had some rivets (and other types of fasteners) in it, and ran that code on it.  It seemed to work just fine, with no errors, and everything got turned red, even the rivets and screws.  Some of the fasteners were at the top level, while others were down one or two levels within sub assemblies.  There were areas on some fasteners where they had threads that did not turn red, but that is to be expected.  Some of those fasteners were still recognized as Content Center members, and others not, due to being converted and integrated into our regular, part numbered file system.  It seems like there may still be some quirks with these types of features to be figured out.

Wesley Crihfield

EESignature

(Not an Autodesk Employee)

0 Likes
Message 10 of 10

mitchELAHB
Advocate
Advocate

Hi @WCrihfield, thanks for confirming it works on your machine. I've found on mine that it fails in every assembly that has multiples of the same part within it. I wonder what's causing the difference in results. I haven't tried running this script on any other machines as of yet. We're running Inventor Professional 2025.3.1. It seems as if each script uploaded here will function correctly if the parts that have multiple instances are skipped, but from my testing yours runs the quickest, so I'll stick with that. I can work with ignoring multiple instances for now, but ultimately would like to find the root of the problem. 

0 Likes