I maintain some add-ins and some tools. Most of those addins and tools deal with parameters and iProperties. I have been putting it for a long time but lately, I have been updating these tools to work with model states. In this post, I want to share some key learnings. I found that there are some things that you always need to consider when working with model data (like parameters and iProperies) in combination with model states.
Consider the following situation in Inventor without model states. You have an assembly with 2 "Levels of detail". The first "Level of detail" is what you work on. In the second "Level of detail", you suppressed all parts that you don't want to send to customers. (If you export an assembly as a step file the suppressed parts don't get exported.) But there can be all sorts of other reasons why you want to use "Levels of detail". This also means that if you change a constraint dimension, the change will happen in all "levels of detail".
When you start working with a version of Inventor with model states, Then your "Levels of detail" are converted to model states. If you now change for example a constraint dimension between 2 parts. You may get unexpected results. The difference with the old situation is that the constraint only changes in one model state. Therefore the model state for the customer is not the same any more as the main model. (Which would happen with "Levels of detail".)
This change of behaviour (which is not limited to parameter changes) is something that all users need to be aware of. But If you create (automatisation) tools you might want to update all model states but don't want to change the workflow for the user. With that I mean we should not edit the model state edit scope without the knowledge of the user. (Or we should change it and return it to its original state.)
For any tool, I think you should ask yourself: "Do you update all model states, just the active model state(s) or do you ask the user which of those 2?" With that in mind, the tool needs to do the following:
- (Activate the required Model State.)
- Set the MemberEditScope to either kEditActiveMember or kEditAllMembers.
- Get the Factory document (and not the Model State document.)
- Make the changes.
- (Reset scope and active model state.)
(Also have a look at the blog post "Working with PropertySets and Model States")
In an example method for updating a parameter that would look like this:
Public Sub UpdateUSerParameter(doc As Document, name As String, expression As String)
Dim def = doc.ComponentDefinition
Dim modelStates As ModelStates = def.ModelStates
' Save for reset scope and active model state.
Dim startModelState = modelStates.ActiveModelState
Dim startScope = modelStates.MemberEditScope
' Activate the required Model State.
modelStates.Item(1).Activate()
' Set the MemberEditScope to either kEditActiveMember or kEditAllMembers.
modelStates.MemberEditScope = MemberEditScopeEnum.kEditAllMembers
' Get the Factory document (and not the Model State document.)
If def.IsModelStateMember Then
doc = def.FactoryDocument
def = doc.ComponentDefinition
modelStates = def.ModelStates
End If
' The only line of code that does work
def.Parameters.UserParameters.Item(name).Expression = expression
' Reset scope and active model state.
startModelState.Activate()
modelStates.MemberEditScope = startScope
End Sub
This goes against the best practice rule that a method should have only 1 function. And maybe more important I don't want to implement this each time I change an iProperty, parameter, material or iPart/iAssembly row. Therefore I made a class that I can call and do all the work for me. The "ModelStateManager" class is quite long there for you will find it at the bottom of this page. (At the time of this writing there is a bug in the API that is not solved by the ModelStateManager. When you try to update a parameter in all model states but the parameter in the active model state is already set then all other model state parameters will not be set!) But I would like to show some use case examples first. The following rule will do exactly the same as the method above but using the manager. That includes resting scope and active model state.
Public Sub UpdateUSerParameter(doc As Document, name As String, expression As String)
Using manager As New ModelStateManager(doc, MemberEditScopeEnum.kEditAllMembers)
Dim def = manager.FactoryDocument.ComponentDefinition
def.Parameters.UserParameters.Item(name).Expression = expression
End Using
End Sub
As you might notice I use the "Using" statement here. This is possible because the ModelStateManager implements the interface IDisposeble When the manager is "disposed of" it will reset the scope and active model state.
In its most simple form, you could use the ModelStateManager only to get the FactoryDocument:
Public Sub UpdateUSerParameter(doc As Document, name As String, expression As String)
Dim manager As New ModelStateManager(doc)
Dim def = manager.FactoryDocument.ComponentDefinition
def.Parameters.UserParameters.Item(name).Expression = expression
End Sub
You might have noticed that the constructor has multiple overloads. One is for normal use and the other will set the model state scope while the manager is initialized. Anyway here is the complete code for the ModelStateManager
Public Class ModelStateManager
Implements IDisposable
Private _doc As Document
Private _documentType As DocumentTypeEnum
Private _startActiveModelState As ModelState
Private _startMemberEditScope As MemberEditScopeEnum
Public Sub New(ByVal doc As Document)
If doc.DocumentType <> DocumentTypeEnum.kPartDocumentObject AndAlso doc.DocumentType <> DocumentTypeEnum.kAssemblyDocumentObject Then
Throw New ArgumentException("This is not a part or assembly document")
End If
If doc Is Nothing Then
Throw New ArgumentException("Document may not be NULL")
End If
_doc = doc
_documentType = _doc.DocumentType
_doc = FactoryDocument
If ModelStates.Count <> 0 Then
_startActiveModelState = ModelStates.ActiveModelState
End If
_startMemberEditScope = ModelStates.MemberEditScope
End Sub
Public Sub New(ByVal doc As Document, ByVal editScope As MemberEditScopeEnum)
Me.New(doc)
Me.EditScope = editScope
End Sub
Public ReadOnly Property FactoryDocument As Document
Get
If (_doc.ComponentDefinition.IsModelStateMember) Then
Return _doc.ComponentDefinition.FactoryDocument
End If
Return _doc
End Get
End Property
Public ReadOnly Property ModelStates As ModelStates
Get
Return _doc.ComponentDefinition.ModelStates
End Get
End Property
Public Property EditScope As MemberEditScopeEnum
Get
Return ModelStates.MemberEditScope
End Get
Set(ByVal value As MemberEditScopeEnum)
ModelStates.MemberEditScope = value
End Set
End Property
Public Sub Dispose() Implements IDisposable.Dispose
If (_startActiveModelState IsNot Nothing) Then
_startActiveModelState.Activate()
End If
ModelStates.MemberEditScope = _startMemberEditScope
End Sub
End Class