Open Modelstatetable for User Input

Open Modelstatetable for User Input

Michael.RostZ454J
Contributor Contributor
448 Views
7 Replies
Message 1 of 8

Open Modelstatetable for User Input

Michael.RostZ454J
Contributor
Contributor

Hi at all.

 

Making my first steps with model states and tried to open the model state table, so that the user can edit iProperties that are different in different model states. 

Opening the excel file works just fine except for the fact, that the table is "read only".

 

The ReadOnly property of the Workbooks.Open Method did not bring any difference.

Expression.Open (FileName, UpdateLinks, ReadOnly, ...

 

So here is my code. Maybe someone has an hint.

Dim oModelStates As ModelStates
oModelStates = doc.ComponentDefinition.ModelStates

Dim objExcel = CreateObject("Excel.Application")
Dim strModelStateTableFullName As String
strModelStateTableFullName = oModelStates.ExcelWorkSheet.Parent.Fullname
'MessageBox.Show(strModelStateTableFullName)
Dim objWorkbook = objExcel.Workbooks.Open(strModelStateTableFullName)
objExcel.Application.Visible = True
0 Likes
Accepted solutions (3)
449 Views
7 Replies
Replies (7)
Message 2 of 8

WCrihfield
Mentor
Mentor
Accepted solution

Hi @Michael.RostZ454J.  Below is a quickie iLogic rule that you can play around with for visibly opening the ModelStates 'ExcelWorkSheet' in Excel.  Be careful if you only have one monitor, because just after the rule shows it, it will also show a MessageBox, but that MessageBox may be under the Excel application.  If you manually close the Worksheet and/or Excel, before clicking the OK button on that MessageBox, it will throw an error.  This is because the code is designed to 'continue' after you click OK on that message, and proceed to close that Workbook, then quit the Excel application, then clear variable values.  I put a bunch of error proofing and iLogig Logger feedback in there, to give you as much information about what is going on as possible.

Dim oDoc As Document = ThisDoc.FactoryDocument
If oDoc Is Nothing Then
	Logger.Debug("No Initial Document Obtained!")
	Return 'if Drawing
End If
If (Not TypeOf oDoc Is PartDocument) AndAlso (Not TypeOf oDoc Is AssemblyDocument) Then
	Logger.Debug("Document Obtained Was Not A Part Or Assembly!")
	Return
End If
Dim oMSs As ModelStates = oDoc.ComponentDefinition.ModelStates
If oMSs Is Nothing Then
	Logger.Debug("ModelStates Is Nothing!")
	Return
ElseIf oMSs.Count < 2 Then
	Logger.Info("Fewer Than 2 ModelStates Found, So No 'Factory' Or WorkSheet.")
	Return
End If
Dim oMSsWS = oMSs.ExcelWorkSheet
If oMSsWS Is Nothing Then
	Logger.Debug("No Worksheet Obtained!")
Else
	Logger.Info("ModelStates.ExcelWorkSheet TypeName = " & TypeName(oMSsWS))
End If
Dim oMSsWB = oMSsWS.Parent 'Workbook
If oMSsWB Is Nothing Then
	Logger.Debug("No WorkBook Obtained!")
Else
	Logger.Info("Worksheet.Parent TypeName = " & TypeName(oMSsWB))
End If
Dim oExcel = oMSsWB.Parent 'Excel.Application
If oExcel Is Nothing Then
	Logger.Debug("Excel Instance Not Obtained!")
Else
	Logger.Info("Excel Object TypeName = " & TypeName(oExcel))
End If
oExcel.Visible = True
oExcel.DisplayAlerts = True
MessageBox.Show("Review Opened Excel Instance.  It will be closed after closing this.", "", _
MessageBoxButtons.OK, MessageBoxIcon.Information)
oMSsWS = Nothing
oMSsWB.Close() 'close Workbook
oMSsWB = Nothing
oExcel.Quit() 'quit Excel application
oExcel = Nothing

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 3 of 8

hollypapp65
Advocate
Advocate
Accepted solution

This is the command when user edit ModelState Table: AppLaunchViaSpreadSheetCmd

 

Dim oControlDef As Inventor.ControlDefinition
oControlDef = ThisApplication.CommandManager.ControlDefinitions.Item("AppLaunchViaSpreadSheetCmd")
oControlDef.Execute2(True)

 

https://help.autodesk.com/view/INVNTOR/2024/ENU/?guid=GUID-ControlDefinition

 

You'll need to see which "Execute" work better for you.

0 Likes
Message 4 of 8

Ivan_Sinicyn
Advocate
Advocate
Accepted solution
Sub Main()
    Try
        ' Check active document
        Dim oDoc As Document = ThisApplication.ActiveDocument
        If oDoc Is Nothing Then
            MessageBox.Show("No active document", "Error")
            Return
        End If
        
        ' Check if the document is an assembly or part
        If Not (TypeOf oDoc Is AssemblyDocument OrElse TypeOf oDoc Is PartDocument) Then
            MessageBox.Show("Current document does not support ModelStates", "Error")
            Return
        End If
        
        ' Launch the ModelStates table editing through the built-in Inventor command
        ThisApplication.CommandManager.ControlDefinitions.Item("AppLaunchViaSpreadSheetCmd").Execute()
        
    Catch ex As Exception
        MessageBox.Show("Error launching command: " & ex.Message, "Error")
    End Try
End Sub
INV 2025.3
0 Likes
Message 5 of 8

Michael.RostZ454J
Contributor
Contributor

@WCrihfield@hollypapp65@Ivan_Sinicyn 

 

 

All three options solve my problem. I wasn't familiar with the control definition object. I'll definitely experiment with that a bit more.

 

Thank you very much.  👍

0 Likes
Message 6 of 8

WCrihfield
Mentor
Mentor

If you do not need to interact with that Excel Worksheet by code, then executing the ControlDefinition would be the way to go, but I am not sure why you would want to do that by code, when it is so easy to do manually.  Executing that command is essentially simulating your manual user interface action of choosing that option within the context menu, after right-clicking on that node in the model browser tree.  But executing that command also takes control away from the rule.  However, if you want/need to have any control over that Worksheet by code, then you will want to do it similar to how I did it in my first example, which leaves you with controllable access to all the Excel objects involved, and the ability to interact with them.  But of course, you would have to know how to effectively interact with those Excel API based objects by code also.   Just so you are aware, so you can make a more informed decision.

Wesley Crihfield

EESignature

(Not an Autodesk Employee)

0 Likes
Message 7 of 8

Michael.RostZ454J
Contributor
Contributor

An interaction by code is not needed. The snippet is from a larger macro which changes some iProperties and the modelstatetable is only opened by user input when necessary and after a friendly reminder that there are different model states.

 

As I mentioned, the visible modelstatetable gives the user the possibility to edit some iProperties that may or may not be different. The problem here is, that there is not only on way they should be. All depends on the users needs and what the purpose of the different model states is. So sometimes they have to be different and sometimes not.

 

0 Likes
Message 8 of 8

Ivan_Sinicyn
Advocate
Advocate

Another option. It works like this: it extracts data from the active document (part or assembly), creates a temporary Excel file, moves the table to be edited there, and opens it in the foreground. After you modify and save the data in Excel and then close the application, the code imports the updated data back into Inventor and deletes the temporary file.

Imports System.Runtime.InteropServices
Sub Main()
    ' Get the active document
    Dim oDoc As Document = ThisApplication.ActiveDocument
    Dim oCompDef As ComponentDefinition = Nothing
    Dim oModelStates As ModelStates = Nothing
    
    ' Determine the document type and get ComponentDefinition
    If TypeOf oDoc Is AssemblyDocument Then
        oCompDef = DirectCast(oDoc, AssemblyDocument).ComponentDefinition
    ElseIf TypeOf oDoc Is PartDocument Then
        oCompDef = DirectCast(oDoc, PartDocument).ComponentDefinition
    Else
        MessageBox.Show("The current document does not support ModelStates", "Error")
        Return
    End If
    
    ' Get ModelStates
    oModelStates = oCompDef.ModelStates
    
    If oModelStates Is Nothing Then
        MessageBox.Show("Failed to retrieve ModelStates", "Error")
        Return
    End If
    
    Try
        ' Call the method to work with Excel, running in a separate scope
        ProcessModelStatesWrapper(oModelStates)
    Catch ex As Exception
        MessageBox.Show("Error while working with the ModelStates table: " & ex.Message, "Error")
    End Try
End Sub


' Declaration of the SetForegroundWindow function from user32.dll
<DllImport("user32.dll")> _
Private Shared Function SetForegroundWindow(ByVal hWnd As IntPtr) As Boolean
End Function

' Wrapper that isolates Excel operations and ensures garbage collection
Sub ProcessModelStatesWrapper(ByVal oModelStates As ModelStates)
    ' Call the method to work with Excel
    ProcessModelStates(oModelStates)
    
    ' Perform garbage collection twice to ensure cleanup
    ForcedGarbageCollection()
End Sub

Sub ProcessModelStates(ByVal oModelStates As ModelStates)
    ' Create a temporary copy of the ModelStates table for editing
    Dim tempFolder As String = System.IO.Path.GetTempPath()
    Dim tempFileName As String = "TempModelStates_" & Guid.NewGuid().ToString() & ".xlsx"
    Dim tempFilePath As String = System.IO.Path.Combine(tempFolder, tempFileName)
    
    ' Declare COM object variables
    Dim objExcel As Object = Nothing
    Dim objWorkbooks As Object = Nothing
    Dim objWorkbook As Object = Nothing
    Dim objWorksheet As Object = Nothing
    Dim objRange As Object = Nothing
    Dim modelStateTable As ModelStateTable = Nothing
    Dim tableRows As ModelStateTableRows = Nothing
    Dim tableColumns As ModelStateTableColumns = Nothing
    
    Try
        ' Create a new Excel file
        objExcel = CreateObject("Excel.Application")
        objWorkbooks = objExcel.Workbooks
        objWorkbook = objWorkbooks.Add()
        objWorksheet = objWorkbook.Sheets(1)
        
        ' Get ModelStateTable
        modelStateTable = oModelStates.ModelStateTable
        
        If modelStateTable Is Nothing Then
            MessageBox.Show("Failed to retrieve ModelStateTable", "Error")
            ' Release COM objects
            CleanupExcelObjects(objWorksheet, Nothing, objWorkbook, objWorkbooks, objExcel)
            Return
        End If
        
        ' Get all rows and columns
        tableRows = modelStateTable.TableRows
        tableColumns = modelStateTable.TableColumns
        
        ' Fill column headers
        For i As Integer = 1 To tableColumns.Count
            Dim column As ModelStateTableColumn = tableColumns.Item(i)
            objWorksheet.Cells(1, i).Value = column.Heading
            ' Bold the headers
            objWorksheet.Cells(1, i).Font.Bold = True
        Next
        
        ' Fill row data
        For i As Integer = 1 To tableRows.Count
            Dim row As ModelStateTableRow = tableRows.Item(i)
            
            ' Iterate through each column to retrieve cell data
            For j As Integer = 1 To tableColumns.Count
                Try
                    ' Get the cell from the ModelState table
                    Dim cell As ModelStateTableCell = row.Item(j)
                    If cell IsNot Nothing AndAlso cell.IsValid Then
                        objWorksheet.Cells(i + 1, j).Value = cell.Value
                    End If
                Catch ex As Exception
                    ' Skip cells with errors
                End Try
            Next
        Next
        
        ' Get the used range of cells
        objRange = objWorksheet.UsedRange
        objRange.Columns.AutoFit()
        NAR(objRange)
        
        ' Save the temporary file
        objWorkbook.SaveAs(tempFilePath)
        
        ' Show Excel and bring it to the foreground
        objExcel.Visible = True
        ' Get the Excel window handle and convert it to IntPtr
        Dim hWnd As Integer = objExcel.GetType().InvokeMember("HWnd", System.Reflection.BindingFlags.GetProperty, Nothing, objExcel, Nothing)
        SetForegroundWindow(New IntPtr(hWnd))
        
        ' Enable Excel alerts
        objExcel.DisplayAlerts = True
        
        ' Wait until the user closes Excel (closes all workbooks)
        While objExcel.Workbooks.Count > 0
            System.Threading.Thread.Sleep(1000)  ' Small delay to avoid overloading the CPU
        End While
        
        ' Close Excel after editing
        Try
            ' Use a constant to display the save prompt (xlPromptToSaveChanges = -1)
            objWorkbook.Close(-1)
        Catch
            ' The workbook might already be closed by the user
        End Try
        
        objExcel.Quit()
        
        ' Release all Excel COM objects
        CleanupExcelObjects(objWorksheet, objRange, objWorkbook, objWorkbooks, objExcel)
        
        ' Excel is closed, import the data back
        If System.IO.File.Exists(tempFilePath) Then
            ImportDataFromExcel(tempFilePath, modelStateTable, tableRows, tableColumns)
            MessageBox.Show("Data successfully imported into Inventor", "Completed")
        End If
    Finally
        ' Ensure all COM objects are released in case of an exception
        Try
            ' If Excel is still open, close it
            If objExcel IsNot Nothing Then
                objExcel.DisplayAlerts = True
                If objWorkbook IsNot Nothing Then
                    Try
                        objWorkbook.Close(-1)
                    Catch
                        ' Ignore errors during closure
                    End Try
                End If
                objExcel.Quit()
            End If
            
            ' Release COM objects
            CleanupExcelObjects(objWorksheet, objRange, objWorkbook, objWorkbooks, objExcel)
        Catch
            ' Ignore errors in the finally block
        End Try
        
        ' Delete the temporary file
        Try
            If System.IO.File.Exists(tempFilePath) Then
                System.IO.File.Delete(tempFilePath)
            End If
        Catch
            ' Ignore errors during deletion
        End Try
    End Try
End Sub

Sub ImportDataFromExcel(ByVal filePath As String, ByVal modelStateTable As ModelStateTable, ByVal tableRows As ModelStateTableRows, ByVal tableColumns As ModelStateTableColumns)
    ' Declare COM objects
    Dim importExcel As Object = Nothing
    Dim importWorkbooks As Object = Nothing
    Dim importWorkbook As Object = Nothing
    Dim importWorksheet As Object = Nothing
    Dim usedRange As Object = Nothing
    
    Try
        ' Open the file to read data
        importExcel = CreateObject("Excel.Application")
        importExcel.Visible = False
        importExcel.DisplayAlerts = False
        
        importWorkbooks = importExcel.Workbooks
        importWorkbook = importWorkbooks.Open(filePath)
        importWorksheet = importWorkbook.Sheets(1)
        
        ' Get the used range of cells
        usedRange = importWorksheet.UsedRange
        
        ' Update data in ModelStateTable
        For i As Integer = 1 To tableRows.Count
            Dim row As ModelStateTableRow = tableRows.Item(i)
            
            For j As Integer = 1 To tableColumns.Count
                Try
                    ' Ensure we do not exceed the data range
                    If i + 1 <= usedRange.Rows.Count And j <= usedRange.Columns.Count Then
                        Dim newValue As String = Convert.ToString(importWorksheet.Cells(i + 1, j).Value)
                        Dim cell As ModelStateTableCell = row.Item(j)
                        
                        ' Update only if the cell is editable
                        If cell IsNot Nothing AndAlso cell.IsValid Then
                            cell.Value = newValue
                        End If
                    End If
                Catch ex As Exception
                    ' Skip problematic cells
                End Try
            Next
        Next
    Finally
        ' Close and release all COM objects in reverse order of creation
        If importWorkbook IsNot Nothing Then
            Try
                importWorkbook.Close(False)
            Catch
                ' Ignore errors during closure
            End Try
        End If
        
        If importExcel IsNot Nothing Then
            Try
                importExcel.Quit()
            Catch
                ' Ignore errors during closure
            End Try
        End If
        
        ' Release all COM objects
        CleanupExcelObjects(importWorksheet, usedRange, importWorkbook, importWorkbooks, importExcel)
    End Try
End Sub

' Method to release all Excel COM objects in the correct order
Sub CleanupExcelObjects(ByVal worksheet As Object, ByVal range As Object, ByVal workbook As Object, ByVal workbooks As Object, ByVal excelApp As Object)
    ' Release objects in reverse order of creation
    If range IsNot Nothing Then NAR(range)
    If worksheet IsNot Nothing Then NAR(worksheet)
    If workbook IsNot Nothing Then NAR(workbook)
    If workbooks IsNot Nothing Then NAR(workbooks)
    If excelApp IsNot Nothing Then NAR(excelApp)
End Sub

' Method to release a COM object with a loop until fully released
Sub NAR(ByVal obj As Object)
    Try
        ' Call ReleaseComObject in a loop until all references are released
        While System.Runtime.InteropServices.Marshal.ReleaseComObject(obj) > 0
        End While
    Catch
        ' Ignore errors
    Finally
        obj = Nothing
    End Try
End Sub

' Method for forced garbage collection
Sub ForcedGarbageCollection()
    GC.Collect()
    GC.WaitForPendingFinalizers()
    GC.Collect()
    GC.WaitForPendingFinalizers()
End Sub
INV 2025.3