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

Skills:

Autodesk Inventor, Vault, Git, C#, vb, .net, php HTML, css, js

Education:

University computer science.
HBO Mechanical engineer.
MBO Fine mechanics.

Experience:

Programmer and Mechanical engineer at Kelvion
(2016 - 20..)

Mechanical engineer at Strukton
(2009 - 2016)

Mechanical engineer at RDG-engineering
(2007 - 2009)

CNC Programmer at VMC
(2005 - 2007)

volunteer at Taizé
(2007)

Certifications:

Objectgeoriënteerd analyseren en ontwerpen, Objectgeoriënteerd programmeren in Java, Webapplicaties: de clientkant, Databases, Security Aware Programmer, Web Security Specialist