Export to flat pattern with specific file name

Export to flat pattern with specific file name

kresh.bell
Collaborator Collaborator
612 Views
19 Replies
Message 1 of 20

Export to flat pattern with specific file name

kresh.bell
Collaborator
Collaborator

Hi,

Is it possible for iLogic to generate a dxf flat pattern to recognize the total quantity, material and thickness? Also to include this data in the generated dxf file name. For example: GF_0235_Aluminium_2mm_14x.dxf
where:
GF_0235 - part name
Aluminium - added material
2 - material thickness
14 - total quantity of parts in the assembly

0 Likes
Accepted solutions (2)
613 Views
19 Replies
Replies (19)
Message 2 of 20

Ivan_Sinicyn
Advocate
Advocate

Hi,

10 minutes in Grok3

 

 

Sub Main()
    ' Configuration settings
    Dim config As New Configuration
    config.TargetDirectory = "C:\DXF" ' Directory where DXF files will be saved
    config.EnableLogging = True ' Set to False to disable logging
    config.LogFileName = "ExportLog.txt" ' Name of the log file
    config.FileNameFormat = "{partNumber}_{material}_{thickness}_{qty}" ' File name format, customizable order
    config.DXFOptions = "FLAT PATTERN DXF?AcadVersion=R12&RebaseGeometry=True&MergeProfilesIntoPolyline=True&SimplifySpline=True&InteriorProfilesLayer=IV_INTERIOR_PROFILES&InvisibleLayers=IV_TANGENT;IV_BEND;IV_BEND_DOWN;IV_TOOL_CENTER_DOWN;IV_ARC_CENTERS;IV_FEATURE_PROFILES;IV_FEATURE_PROFILES_DOWN;IV_UNCONSUMED_SKETCHES;IV_ROLL_TANGENT;IV_ROLL&SplineToleranceDouble=0.01"
    config.ThicknessRounding = 2 ' Number of decimal places for thickness (0 = no decimals, 1 = one decimal, etc.)
    
    ' Create target directory if it doesn't exist
    If Not System.IO.Directory.Exists(config.TargetDirectory) Then
        System.IO.Directory.CreateDirectory(config.TargetDirectory)
    End If

    ' Define log file path
    Dim logFilePath As String = System.IO.Path.Combine(config.TargetDirectory, config.LogFileName)

    ' Initialize log file with header if logging is enabled
    If config.EnableLogging Then
        Dim logHeader As String = "DXF Export Log" & vbCrLf & _
                                 "Started: " & DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") & vbCrLf & _
                                 "Export Options: " & config.DXFOptions & vbCrLf & vbCrLf
        System.IO.File.WriteAllText(logFilePath, logHeader)
    End If

    ' Get the active document
    Dim doc As Inventor.Document = ThisApplication.ActiveDocument
    If doc Is Nothing Then
        If config.EnableLogging Then LogError(logFilePath, "No document is open.")
        Exit Sub
    End If

    If doc.DocumentType = DocumentTypeEnum.kAssemblyDocumentObject Then
        ' Process assembly document
        Dim asmDoc As AssemblyDocument = CType(doc, AssemblyDocument)
        Dim sheetMetalParts As New Dictionary(Of String, Integer)
        ProcessComponentOccurrences(asmDoc.ComponentDefinition.Occurrences, sheetMetalParts, logFilePath, config)
        
        ' Initialize progress bar for assembly export
        Dim progressBar As Inventor.ProgressBar = ThisApplication.CreateProgressBar(False, sheetMetalParts.Count, "Exporting DXF Files")
        Dim currentStep As Integer = 0
        ExportDXFFiles(sheetMetalParts, config.TargetDirectory, logFilePath, config, progressBar, currentStep)
        progressBar.Close()
    ElseIf doc.DocumentType = DocumentTypeEnum.kPartDocumentObject Then
        ' Process individual part document
        Dim partDoc As PartDocument = CType(doc, PartDocument)
        If partDoc.SubType = "{9C464203-9BAE-11D3-8BAD-0060B0CE6BB4}" Then
            ' Initialize progress bar for single part export
            Dim progressBar As Inventor.ProgressBar = ThisApplication.CreateProgressBar(False, 1, "Exporting DXF File")
            Dim currentStep As Integer = 0
            ExportSinglePartDXF(partDoc, config.TargetDirectory, logFilePath, config, progressBar, currentStep)
            progressBar.Close()
        Else
            If config.EnableLogging Then LogError(logFilePath, "The active document is not a sheet metal part.")
        End If
    Else
        If config.EnableLogging Then LogError(logFilePath, "The active document is neither an assembly nor a sheet metal part.")
    End If

    MessageBox.Show("DXF export completed. Check the log file for details if logging is enabled.")
End Sub

' Configuration class for easy settings management
Class Configuration
    Public TargetDirectory As String
    Public EnableLogging As Boolean
    Public LogFileName As String
    Public FileNameFormat As String ' Format string for file naming, e.g., "{partNumber}_{material}_{thickness}_{qty}"
    Public DXFOptions As String ' DXF export options
    Public ThicknessRounding As Integer ' Number of decimal places for thickness rounding
End Class

Sub ProcessComponentOccurrences(occurrences As ComponentOccurrences, sheetMetalParts As Dictionary(Of String, Integer), logFilePath As String, config As Configuration)
    For Each occ As ComponentOccurrence In occurrences
        Try
            If occ.DefinitionDocumentType = DocumentTypeEnum.kPartDocumentObject Then
                Dim partDoc As PartDocument = CType(occ.Definition.Document, PartDocument)
                If partDoc.SubType = "{9C464203-9BAE-11D3-8BAD-0060B0CE6BB4}" Then
                    Dim smCompDef As SheetMetalComponentDefinition = CType(partDoc.ComponentDefinition, SheetMetalComponentDefinition)
                    If smCompDef.HasFlatPattern Then
                        Dim partNumber As String = partDoc.PropertySets.Item("Design Tracking Properties").Item("Part Number").Value.ToString()
                        If sheetMetalParts.ContainsKey(partNumber) Then
                            sheetMetalParts(partNumber) += 1
                        Else
                            sheetMetalParts.Add(partNumber, 1)
                        End If
                    Else
                        If config.EnableLogging Then LogError(logFilePath, "Skipping DXF file for: " & partDoc.FullFileName & " - Flat Pattern Missing")
                    End If
                End If
            ElseIf occ.DefinitionDocumentType = DocumentTypeEnum.kAssemblyDocumentObject Then
                ' Recursively process sub-assemblies
                ProcessComponentOccurrences(occ.SubOccurrences, sheetMetalParts, logFilePath, config)
            End If
        Catch ex As Exception
            If config.EnableLogging Then LogError(logFilePath, "Error processing occurrence: " & occ.Name & " - " & ex.Message)
        End Try
    Next
End Sub

Sub ExportDXFFiles(sheetMetalParts As Dictionary(Of String, Integer), targetDir As String, logFilePath As String, config As Configuration, progressBar As Inventor.ProgressBar, ByRef currentStep As Integer)
    For Each partNumber As String In sheetMetalParts.Keys
        currentStep += 1
        Dim qty As Integer = sheetMetalParts(partNumber)
        Dim partDoc As PartDocument = Nothing
        Try
            partDoc = OpenPartDocument(partNumber)
            If partDoc Is Nothing Then
                Throw New Exception("Part file not found or couldn't be opened")
            End If
            
            Dim material As String = GetMaterial(partDoc)
            Dim thickness As String = GetThickness(partDoc, config.ThicknessRounding)
            Dim fileName As String = FormatFileName(config.FileNameFormat, partNumber, material, thickness, qty)
            Dim filePath As String = System.IO.Path.Combine(targetDir, fileName & ".dxf")

            If config.EnableLogging Then
                LogError(logFilePath, "Processing part: " & partNumber)
                LogError(logFilePath, "File path: " & filePath)
            End If

            If Not IsValidPath(filePath) Then
                If config.EnableLogging Then LogError(logFilePath, "Invalid file path: " & filePath)
                Continue For
            End If

            If config.EnableLogging Then LogError(logFilePath, "Opened document: " & partDoc.FullFileName)
            Dim smCompDef As SheetMetalComponentDefinition = CType(partDoc.ComponentDefinition, SheetMetalComponentDefinition)

            If smCompDef.HasFlatPattern Then
                Dim flatPattern As FlatPattern = smCompDef.FlatPattern
                Dim oDataIO As DataIO = flatPattern.DataIO
                If config.EnableLogging Then LogError(logFilePath, "Prepared DataIO for: " & partDoc.FullFileName)

                Try
                    oDataIO.WriteDataToFile(config.DXFOptions, filePath)
                    If config.EnableLogging Then LogError(logFilePath, "Exported DXF for: " & partDoc.FullFileName & " to " & filePath)
                Catch ex As Exception
                    If config.EnableLogging Then LogError(logFilePath, "Error exporting DXF for: " & partDoc.FullFileName & " - " & ex.Message)
                End Try
            Else
                If config.EnableLogging Then LogError(logFilePath, "Skipping DXF file for: " & partDoc.FullFileName & " - Flat Pattern Missing")
            End If

        Catch ex As Exception
            If config.EnableLogging Then LogError(logFilePath, "Error processing part: " & partNumber & " - " & ex.Message)
        Finally
            If partDoc IsNot Nothing Then
                partDoc.Close(False)
                If config.EnableLogging Then LogError(logFilePath, "Closed document: " & partDoc.FullFileName)
            End If
            ' Update progress bar
            Dim progressPercent As Integer = CInt((currentStep / sheetMetalParts.Count) * 100)
            progressBar.Message = "Exporting: " & partNumber & " (" & progressPercent & "%)"
            progressBar.UpdateProgress()
        End Try
    Next
End Sub

Sub ExportSinglePartDXF(partDoc As PartDocument, targetDir As String, logFilePath As String, config As Configuration, progressBar As Inventor.ProgressBar, ByRef currentStep As Integer)
    currentStep += 1
    Dim partNumber As String = partDoc.PropertySets.Item("Design Tracking Properties").Item("Part Number").Value.ToString()
    Dim material As String = GetMaterial(partDoc)
    Dim thickness As String = GetThickness(partDoc, config.ThicknessRounding)
    Dim fileName As String = FormatFileName(config.FileNameFormat, partNumber, material, thickness, 1)
    Dim filePath As String = System.IO.Path.Combine(targetDir, fileName & ".dxf")

    If config.EnableLogging Then
        LogError(logFilePath, "Processing part: " & partNumber)
        LogError(logFilePath, "File path: " & filePath)
    End If

    If Not IsValidPath(filePath) Then
        If config.EnableLogging Then LogError(logFilePath, "Invalid file path: " & filePath)
        Exit Sub
    End If

    Try
        Dim smCompDef As SheetMetalComponentDefinition = CType(partDoc.ComponentDefinition, SheetMetalComponentDefinition)

        If smCompDef.HasFlatPattern Then
            Dim flatPattern As FlatPattern = smCompDef.FlatPattern
            Dim oDataIO As DataIO = flatPattern.DataIO
            If config.EnableLogging Then LogError(logFilePath, "Prepared DataIO for: " & partDoc.FullFileName)

            Try
                oDataIO.WriteDataToFile(config.DXFOptions, filePath)
                If config.EnableLogging Then LogError(logFilePath, "Exported DXF for: " & partDoc.FullFileName & " to " & filePath)
            Catch ex As Exception
                If config.EnableLogging Then LogError(logFilePath, "Error exporting DXF for: " & partDoc.FullFileName & " - " & ex.Message)
            End Try
        Else
            If config.EnableLogging Then LogError(logFilePath, "Skipping DXF file for: " & partDoc.FullFileName & " - Flat Pattern Missing")
        End If
    Catch ex As Exception
        If config.EnableLogging Then LogError(logFilePath, "Error processing part: " & partNumber & " - " & ex.Message)
    Finally
        ' Update progress bar for single part
        progressBar.Message = "Exporting: " & partNumber & " (100%)"
        progressBar.UpdateProgress()
    End Try
End Sub

Function FormatFileName(format As String, partNumber As String, material As String, thickness As String, qty As Integer) As String
    ' Replace placeholders with actual values
    Dim fileName As String = format
    fileName = fileName.Replace("{partNumber}", partNumber)
    fileName = fileName.Replace("{material}", material)
    fileName = fileName.Replace("{thickness}", thickness)
    fileName = fileName.Replace("{qty}", qty.ToString())
    Return fileName
End Function

Function OpenPartDocument(partNumber As String) As PartDocument
    Dim app As Inventor.Application = ThisApplication
    Dim docs As Documents = app.Documents
    Dim projectManager As DesignProjectManager = app.DesignProjectManager
    Dim activeProject As DesignProject = projectManager.ActiveDesignProject
    
    ' Try to find the part in already open documents
    For Each doc As Document In docs
        If TypeOf doc Is PartDocument AndAlso doc.PropertySets.Item("Design Tracking Properties").Item("Part Number").Value.ToString() = partNumber Then
            Return CType(doc, PartDocument)
        End If
    Next
    
    ' If not found in open documents, try to find and open from the project
    Dim fullFileName As String = activeProject.FindFiles(partNumber & ".ipt").Item(1)
    If Not String.IsNullOrEmpty(fullFileName) Then
        Return CType(docs.Open(fullFileName), PartDocument)
    End If
    
    Return Nothing
End Function

Function IsValidPath(path As String) As Boolean
    Dim isValid As Boolean = True
    Try
        Dim fileInfo As New System.IO.FileInfo(path)
    Catch ex As Exception
        isValid = False
    End Try
    Return isValid
End Function

Sub LogError(logFilePath As String, message As String)
    System.IO.File.AppendAllText(logFilePath, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") & " - " & message & vbCrLf)
End Sub

Function GetMaterial(partDoc As PartDocument) As String
    Try
        Dim material As String = partDoc.PropertySets.Item("Design Tracking Properties").Item("Material").Value.ToString()
        Return material.Replace(" ", "_")
    Catch
        Return "Unknown"
    End Try
End Function

Function GetThickness(partDoc As PartDocument, rounding As Integer) As String
    Try
        Dim smCompDef As SheetMetalComponentDefinition = CType(partDoc.ComponentDefinition, SheetMetalComponentDefinition)
        Dim thickness As Double = smCompDef.Thickness.Value ' Thickness in centimeters
        Dim thicknessMM As Double = thickness * 10 ' Convert to millimeters
        Dim thicknessFormatted As String = Math.Round(thicknessMM, rounding).ToString() & "mm" ' Apply rounding
        Return thicknessFormatted
    Catch
        Return "Unknown"
    End Try
End Function



INV 2025.3
0 Likes
Message 3 of 20

kresh.bell
Collaborator
Collaborator

Hi @Ivan_Sinicyn 

thank you very much, it doesn't show any error, but I can't find where the generated dxf files are saved

0 Likes
Message 4 of 20

DeptaM
Enthusiast
Enthusiast

Hello,
thanks for this code. Is it possible to generate files in the directory where the source is located?
Martin Depta

0 Likes
Message 5 of 20

Ivan_Sinicyn
Advocate
Advocate

There is a configurator at the beginning of the code where you can customize the upload folder and other options

config.TargetDirectory = "C:\DXF" ' Directory where DXF files will be saved

 

INV 2025.3
Message 6 of 20

kresh.bell
Collaborator
Collaborator

Hi,

thanks, i understand now. Is it possible to save the generated dxf files in a folder located in the same location as the assembly file? It would be ideal if starting iLogic would open a window where we want to save with the initial location of the assembly file.

 

Another small change, if possible, is it possible to add the letter "x" after the quantity?

kreshbell_0-1741458930832.png

 

Btw, your iLogic is really great.

 

Message 7 of 20

Ivan_Sinicyn
Advocate
Advocate
Accepted solution

@DeptaM @kresh.bell 
Finalizing the code took more time as usual.🤣

Take a look, I think I understood you correctly. And be sure to check the export results, because I can't guarantee that there are no errors in the code logic.

Now you have a dialog box where you can add your prefixes and suffixes to the file name.

The settings file is saved in the folder: %AppData%\Autodesk\Inventor\DXFExport


XXX.png


 

AddReference "System.Drawing"
AddReference "System.Xml"
AddReference "System.Windows.Forms"
Imports System.Drawing
Imports System.Windows.Forms
Imports System.Xml.Serialization
Imports System.IO
Imports TextBox = System.Windows.Forms.TextBox
Imports Point = System.Drawing.Point
Imports Color = System.Drawing.Color
Imports Path = System.IO.Path
Imports File = System.IO.File
Imports Environment = System.Environment



' Configuration class for use during export
Public Class Configuration
    Public TargetDirectory As String
    Public UseComponentFolder As Boolean
    Public EnableLogging As Boolean
    Public LogFileName As String
    Public FileNameFormat As String
    Public DXFOptions As String
    Public ThicknessRounding As Integer
End Class

' Serializable class to store user settings
<Serializable()> _
Public Class UserSettings
    Public Property TargetDirectory As String = "C:\DXF"
    Public Property UseComponentFolder As Boolean = False
    Public Property EnableLogging As Boolean = False
    Public Property FileNameFormat As String = "{partNumber}_{material}_{thickness}_{qty}"
    Public Property DXFOptions As String = "FLAT PATTERN DXF?AcadVersion=R12&RebaseGeometry=True&MergeProfilesIntoPolyline=True&SimplifySpline=True&InteriorProfilesLayer=IV_INTERIOR_PROFILES&InvisibleLayers=IV_TANGENT;IV_BEND;IV_BEND_DOWN;IV_TOOL_CENTER_DOWN;IV_ARC_CENTERS;IV_FEATURE_PROFILES;IV_FEATURE_PROFILES_DOWN;IV_UNCONSUMED_SKETCHES;IV_ROLL_TANGENT;IV_ROLL&SplineToleranceDouble=0.01"
    Public Property ThicknessRounding As Integer = 2
End Class

' Class for managing DXF export settings
Public Class SettingsManager
    ' Path to settings file
    Private Shared ReadOnly SettingsFileName As String = "DXFExportSettings.xml"
    
    ' Get settings directory in user's AppData folder
    Public Shared Function GetSettingsDirectory() As String
        Dim appDataPath As String = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)
        Dim settingsPath As String = Path.Combine(appDataPath, "Autodesk", "Inventor", "DXFExport")
        
        ' Create directory if it doesn't exist
        If Not Directory.Exists(settingsPath) Then
            Try
                Directory.CreateDirectory(settingsPath)
            Catch ex As Exception
                MessageBox.Show("Could not create settings directory: " & ex.Message, 
                    "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning)
                Return appDataPath
            End Try
        End If
        
        Return settingsPath
    End Function
    
    ' Get full path to settings file
    Public Shared Function GetSettingsFilePath() As String
        Return Path.Combine(GetSettingsDirectory(), SettingsFileName)
    End Function
    
    ' Save settings to XML file
    Public Shared Sub SaveSettings(settings As UserSettings)
        Try
            Dim serializer As New XmlSerializer(GetType(UserSettings))
            Using writer As New StreamWriter(GetSettingsFilePath())
                serializer.Serialize(writer, settings)
            End Using
        Catch ex As Exception
            MessageBox.Show("Could not save settings: " & ex.Message, 
                "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning)
        End Try
    End Sub
    
    ' Load settings from XML file
    Public Shared Function LoadSettings() As UserSettings
        Dim filePath As String = GetSettingsFilePath()
        
        ' If settings file doesn't exist, return default settings
        If Not File.Exists(filePath) Then
            Return New UserSettings()
        End If
        
        Try
            Dim serializer As New XmlSerializer(GetType(UserSettings))
            Using reader As New StreamReader(filePath)
                Return DirectCast(serializer.Deserialize(reader), UserSettings)
            End Using
        Catch ex As Exception
            MessageBox.Show("Could not load settings: " & ex.Message & vbCrLf &
                "Using default settings instead.", "Warning", 
                MessageBoxButtons.OK, MessageBoxIcon.Warning)
            Return New UserSettings()
        End Try
    End Function
End Class

' Settings Form Class
Public Class SettingsForm
    Inherits System.Windows.Forms.Form

    ' Form controls
    Private WithEvents btnOK As Button
    Private WithEvents btnCancel As Button
    Private WithEvents btnBrowse As Button
    Private WithEvents radStaticFolder As RadioButton
    Private WithEvents radComponentFolder As RadioButton
    Private txtTargetDir As TextBox
    Private chkLogging As CheckBox
    Private txtFileNameFormat As TextBox
    Private numThicknessRounding As NumericUpDown
    Private lblFileNameHelp As Label
    Private lblThicknessHelp As Label

    ' Properties to access settings
    Public Property TargetDirectory As String
    Public Property UseComponentFolder As Boolean
    Public Property EnableLogging As Boolean
    Public Property FileNameFormat As String
    Public Property DXFOptions As String
    Public Property ThicknessRounding As Integer

    ' Constants for UI layout
    Private Const FORM_PADDING As Integer = 12 ' Standard padding between controls
    Private Const GROUP_PADDING As Integer = 20 ' Padding inside group boxes
    Private Const LABEL_HEIGHT As Integer = 24 ' Standard height for labels
    Private Const CONTROL_HEIGHT As Integer = 28 ' Standard height for controls
    Private Const BUTTON_WIDTH As Integer = 100 ' Standard width for buttons
    Private Const BUTTON_HEIGHT As Integer = 36 ' Standard height for buttons
    Private Const BROWSE_BUTTON_WIDTH As Integer = 50 ' Width for browse button

    Public Sub New()
        ' Form setup with improved dimensions and appearance
        Me.Text = "DXF Export Settings"
        Me.FormBorderStyle = FormBorderStyle.FixedDialog
        Me.MaximizeBox = False
        Me.MinimizeBox = False
        Me.StartPosition = FormStartPosition.CenterScreen
        Me.ClientSize = New Size(540, 350) ' Explicitly set client area size
        Me.Font = New Font("Segoe UI", 9.5F) ' Slightly larger font for better readability
        Me.Padding = New Padding(FORM_PADDING)
        
        ' Create main GroupBox for export location
        Dim gbExportLocation As New GroupBox()
        gbExportLocation.Text = "Export Location"
        gbExportLocation.Location = New Point(FORM_PADDING, FORM_PADDING)
        gbExportLocation.Width = Me.ClientSize.Width - (FORM_PADDING * 2)
        gbExportLocation.Height = 110
        gbExportLocation.Padding = New Padding(GROUP_PADDING)
        
        ' Radio buttons for folder selection with improved spacing
        radStaticFolder = New RadioButton()
        radStaticFolder.Text = "Static Folder:"
        radStaticFolder.Location = New Point(GROUP_PADDING, 24)
        radStaticFolder.AutoSize = True
        radStaticFolder.Checked = True
        
        ' Target directory textbox and browse button with improved layout
        txtTargetDir = New TextBox()
        txtTargetDir.Location = New Point(GROUP_PADDING + 120, 22)
        txtTargetDir.Size = New Size(gbExportLocation.Width - 220, CONTROL_HEIGHT)
        txtTargetDir.Anchor = AnchorStyles.Left Or AnchorStyles.Right Or AnchorStyles.Top
        
        btnBrowse = New Button()
        btnBrowse.Text = "..."
        btnBrowse.Location = New Point(gbExportLocation.Width - BROWSE_BUTTON_WIDTH - GROUP_PADDING, 21)
        btnBrowse.Size = New Size(BROWSE_BUTTON_WIDTH, CONTROL_HEIGHT)
        btnBrowse.Anchor = AnchorStyles.Right Or AnchorStyles.Top
        
        radComponentFolder = New RadioButton()
        radComponentFolder.Text = "Main Component Folder"
        radComponentFolder.Location = New Point(GROUP_PADDING, 60)
        radComponentFolder.AutoSize = True
        
        ' Add controls to the GroupBox
        gbExportLocation.Controls.AddRange({radStaticFolder, radComponentFolder, txtTargetDir, btnBrowse})
        
        ' Create GroupBox for file options with improved layout
        Dim gbFileOptions As New GroupBox()
        gbFileOptions.Text = "File Options"
        gbFileOptions.Location = New Point(FORM_PADDING, gbExportLocation.Bottom + FORM_PADDING)
        gbFileOptions.Width = Me.ClientSize.Width - (FORM_PADDING * 2)
        gbFileOptions.Height = 150
        gbFileOptions.Padding = New Padding(GROUP_PADDING)
        
        ' Improved layout for file options controls
        ' Enable logging checkbox
        chkLogging = New CheckBox()
        chkLogging.Text = "Enable Logging"
        chkLogging.Location = New Point(GROUP_PADDING, 25)
        chkLogging.AutoSize = True
        
        ' File name format with better alignment
        Dim lblFileName As New Label()
        lblFileName.Text = "File Name Format:"
        lblFileName.Location = New Point(GROUP_PADDING, 60)
        lblFileName.AutoSize = True
        
        txtFileNameFormat = New TextBox()
        txtFileNameFormat.Location = New Point(GROUP_PADDING + 120, 58)
        txtFileNameFormat.Size = New Size(gbFileOptions.Width - 160, CONTROL_HEIGHT)
        txtFileNameFormat.Anchor = AnchorStyles.Left Or AnchorStyles.Right Or AnchorStyles.Top
        
        lblFileNameHelp = New Label()
        lblFileNameHelp.Text = "Available: {partNumber}, {material}, {thickness}, {qty}"
        lblFileNameHelp.Location = New Point(GROUP_PADDING + 120, 88)
        lblFileNameHelp.AutoSize = True
        lblFileNameHelp.ForeColor = Color.DarkGray
        
        ' Thickness rounding with improved layout
        Dim lblRounding As New Label()
        lblRounding.Text = "Thickness Rounding:"
        lblRounding.Location = New Point(GROUP_PADDING, 115)
        lblRounding.AutoSize = True
        lblRounding.Width = 120 ' Ensure enough width for the label
        
        numThicknessRounding = New NumericUpDown()
        numThicknessRounding.Value = 2
        numThicknessRounding.Minimum = 0
        numThicknessRounding.Maximum = 4
        numThicknessRounding.Location = New Point(GROUP_PADDING + 140, 113) ' Moved right to avoid overlap
        numThicknessRounding.Size = New Size(70, CONTROL_HEIGHT)
        
        lblThicknessHelp = New Label()
        lblThicknessHelp.Text = "decimal places (0-4)"
        lblThicknessHelp.Location = New Point(GROUP_PADDING + 220, 115) ' Adjusted to match new position
        lblThicknessHelp.AutoSize = True
        lblThicknessHelp.ForeColor = Color.DarkGray
        
        ' Add controls to file options GroupBox
        gbFileOptions.Controls.AddRange({chkLogging, lblFileName, txtFileNameFormat, lblFileNameHelp, 
                                        lblRounding, numThicknessRounding, lblThicknessHelp})
        
        ' Create button panel for more consistent button placement
        Dim buttonPanel As New Panel()
        buttonPanel.Width = Me.ClientSize.Width - (FORM_PADDING * 2)
        buttonPanel.Height = BUTTON_HEIGHT + 10
        buttonPanel.Location = New Point(FORM_PADDING, gbFileOptions.Bottom + FORM_PADDING)
        
        ' Create improved buttons
        btnCancel = New Button()
        btnCancel.Text = "Cancel"
        btnCancel.Location = New Point(buttonPanel.Width - BUTTON_WIDTH - 5, 0) ' Added 5px margin from right edge
        btnCancel.Size = New Size(BUTTON_WIDTH, BUTTON_HEIGHT)
        btnCancel.DialogResult = DialogResult.Cancel
        btnCancel.Anchor = AnchorStyles.Right
        
        btnOK = New Button()
        btnOK.Text = "OK"
        btnOK.Location = New Point(buttonPanel.Width - (BUTTON_WIDTH * 2) - 15, 0) ' Adjusted spacing
        btnOK.Size = New Size(BUTTON_WIDTH, BUTTON_HEIGHT)
        btnOK.DialogResult = DialogResult.OK
        btnOK.Anchor = AnchorStyles.Right
        
        buttonPanel.Controls.AddRange({btnOK, btnCancel})
        
        ' Add all control groups to the form
        Me.Controls.AddRange({gbExportLocation, gbFileOptions, buttonPanel})
        
        ' Set tooltips for controls with improved descriptions
        Dim toolTip As New ToolTip()
        toolTip.SetToolTip(txtTargetDir, "Directory where DXF files will be saved")
        toolTip.SetToolTip(btnBrowse, "Browse for target directory")
        toolTip.SetToolTip(radStaticFolder, "Always export to the specified folder")
        toolTip.SetToolTip(radComponentFolder, "Export to the folder containing the main component")
        toolTip.SetToolTip(chkLogging, "Create a log file with detailed information about the export process")
        toolTip.SetToolTip(txtFileNameFormat, "Format template for exported DXF filenames")
        toolTip.SetToolTip(numThicknessRounding, "Number of decimal places for thickness values in filenames")
        
        ' Set AcceptButton and CancelButton for keyboard navigation
        Me.AcceptButton = btnOK
        Me.CancelButton = btnCancel
        
        ' Load saved settings
        LoadSavedSettings()
        
        ' Adjust form height based on actual content
        Me.ClientSize = New Size(Me.ClientSize.Width, buttonPanel.Bottom + FORM_PADDING)
    End Sub
    
    ' Load previously saved settings
    Private Sub LoadSavedSettings()
        Try
            Dim settings As UserSettings = SettingsManager.LoadSettings()
            
            ' Apply loaded settings to form controls
            txtTargetDir.Text = settings.TargetDirectory
            radComponentFolder.Checked = settings.UseComponentFolder
            radStaticFolder.Checked = Not settings.UseComponentFolder
            chkLogging.Checked = settings.EnableLogging
            txtFileNameFormat.Text = settings.FileNameFormat
            numThicknessRounding.Value = settings.ThicknessRounding
            
            ' Set DXF options
            DXFOptions = settings.DXFOptions
            
            ' Update UI state based on folder choice
            txtTargetDir.Enabled = Not settings.UseComponentFolder
            btnBrowse.Enabled = Not settings.UseComponentFolder
            
        Catch ex As Exception
            ' If there's an error loading settings, just use the defaults already set in the form
            MessageBox.Show("Could not load saved settings. Using default settings.", 
                "Settings Error", MessageBoxButtons.OK, MessageBoxIcon.Warning)
        End Try
    End Sub

    Private Sub radStaticFolder_CheckedChanged(sender As Object, e As EventArgs) Handles radStaticFolder.CheckedChanged
        ' Make sure controls exist before trying to enable/disable them
        If txtTargetDir IsNot Nothing AndAlso btnBrowse IsNot Nothing Then
            ' Enable or disable the text box and browse button based on selection
            txtTargetDir.Enabled = radStaticFolder.Checked
            btnBrowse.Enabled = radStaticFolder.Checked
        End If
    End Sub

    Private Sub radComponentFolder_CheckedChanged(sender As Object, e As EventArgs) Handles radComponentFolder.CheckedChanged
        ' Make sure controls exist before trying to enable/disable them
        If txtTargetDir IsNot Nothing AndAlso btnBrowse IsNot Nothing Then
            ' This is the inverse of radStaticFolder_CheckedChanged, but keeping both for clarity
            txtTargetDir.Enabled = Not radComponentFolder.Checked
            btnBrowse.Enabled = Not radComponentFolder.Checked
        End If
    End Sub

    Private Sub btnBrowse_Click(sender As Object, e As EventArgs) Handles btnBrowse.Click
        Dim folderBrowser As New FolderBrowserDialog()
        folderBrowser.Description = "Select Target Directory for DXF Export"
        folderBrowser.ShowNewFolderButton = True
        
        If Not String.IsNullOrEmpty(txtTargetDir.Text) AndAlso System.IO.Directory.Exists(txtTargetDir.Text) Then
            folderBrowser.SelectedPath = txtTargetDir.Text
        End If
        
        If folderBrowser.ShowDialog() = DialogResult.OK Then
            txtTargetDir.Text = folderBrowser.SelectedPath
        End If
    End Sub

    Private Sub btnOK_Click(sender As Object, e As EventArgs) Handles btnOK.Click
        ' Validate inputs
        If radStaticFolder.Checked AndAlso String.IsNullOrEmpty(txtTargetDir.Text) Then
            MessageBox.Show("Target directory cannot be empty when using static folder option.", 
                "Validation Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
            txtTargetDir.Focus()
            Return
        End If
        
        If String.IsNullOrEmpty(txtFileNameFormat.Text) Then
            MessageBox.Show("File name format cannot be empty.", "Validation Error", 
                MessageBoxButtons.OK, MessageBoxIcon.Error)
            txtFileNameFormat.Focus()
            Return
        End If
        
        ' Save settings to properties
        SaveFormSettingsToProperties()
        
        ' Save settings to XML file
        SaveCurrentSettingsToXml(True)
        
        ' Close the form
        Me.DialogResult = DialogResult.OK
        Me.Close()
    End Sub
    
    ' Save form values to properties
    Private Sub SaveFormSettingsToProperties()
        UseComponentFolder = radComponentFolder.Checked
        TargetDirectory = txtTargetDir.Text
        EnableLogging = chkLogging.Checked
        FileNameFormat = txtFileNameFormat.Text
        ThicknessRounding = CInt(numThicknessRounding.Value)
    End Sub
    
    ' Save current settings to XML file
    Private Sub SaveCurrentSettingsToXml(showErrors As Boolean)
        Try
            ' Create settings object
            Dim settings As New UserSettings()
            settings.TargetDirectory = txtTargetDir.Text
            settings.UseComponentFolder = radComponentFolder.Checked
            settings.EnableLogging = chkLogging.Checked
            settings.FileNameFormat = txtFileNameFormat.Text
            settings.DXFOptions = DXFOptions
            settings.ThicknessRounding = CInt(numThicknessRounding.Value)
            
            ' Save to file
            SettingsManager.SaveSettings(settings)
        Catch ex As Exception
            ' Show error only if requested
            If showErrors Then
                MessageBox.Show("Could not save settings: " & ex.Message, 
                    "Settings Error", MessageBoxButtons.OK, MessageBoxIcon.Warning)
            End If
        End Try
    End Sub

    Private Sub btnCancel_Click(sender As Object, e As EventArgs) Handles btnCancel.Click
        ' Save settings to XML file even when canceling dialog
        SaveCurrentSettingsToXml(False)
        
        Me.DialogResult = DialogResult.Cancel
        Me.Close()
    End Sub
End Class

' Main method start
Sub Main()
    ' Create and show settings dialog
    Dim settingsForm As New SettingsForm
    If settingsForm.ShowDialog() <> DialogResult.OK Then
        Exit Sub
    End If

    ' Get active document to use for component folder if needed
    Dim doc As Inventor.Document = ThisApplication.ActiveDocument
    If doc Is Nothing Then
        MessageBox.Show("No document is open.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
        Exit Sub
    End If

    ' Get settings from dialog
    Dim config As New Configuration
    config.UseComponentFolder = settingsForm.UseComponentFolder
    
    ' Determine target directory based on settings
    If config.UseComponentFolder Then
        ' Use the directory of the active document
        Dim docPath As String = ""
        
        Try
            ' Get the document path
            docPath = System.IO.Path.GetDirectoryName(doc.FullFileName)
            
            ' Verify the path exists
            If String.IsNullOrEmpty(docPath) OrElse Not System.IO.Directory.Exists(docPath) Then
                Throw New Exception("Cannot access document folder")
            End If
            
            config.TargetDirectory = docPath
        Catch ex As Exception
            MessageBox.Show("Could not determine the component folder: " & ex.Message & vbCrLf & 
                "Falling back to default folder.", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning)
            config.TargetDirectory = settingsForm.TargetDirectory
        End Try
    Else
        ' Use the static folder from settings
        config.TargetDirectory = settingsForm.TargetDirectory
    End If
    
    ' Set other configuration options
    config.EnableLogging = settingsForm.EnableLogging
    config.LogFileName = "ExportLog.txt"
    config.FileNameFormat = settingsForm.FileNameFormat
    config.DXFOptions = settingsForm.DXFOptions
    config.ThicknessRounding = settingsForm.ThicknessRounding

    ' Create target directory if it doesn't exist
    If Not System.IO.Directory.Exists(config.TargetDirectory) Then
        Try
            System.IO.Directory.CreateDirectory(config.TargetDirectory)
        Catch ex As Exception
            MessageBox.Show("Could not create the target directory: " & ex.Message, "Error", 
                MessageBoxButtons.OK, MessageBoxIcon.Error)
            Exit Sub
        End Try
    End If

    ' Define log file path
    Dim logFilePath As String = System.IO.Path.Combine(config.TargetDirectory, config.LogFileName)

    ' Initialize log file with header if logging is enabled
    If config.EnableLogging Then
        Try
            Dim logHeader As String = "DXF Export Log" & vbCrLf & _
                                    "Started: " & DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") & vbCrLf & _
                                    "Target Directory: " & config.TargetDirectory & vbCrLf & _
                                    "Export Options: " & config.DXFOptions & vbCrLf & vbCrLf
            System.IO.File.WriteAllText(logFilePath, logHeader)
        Catch ex As Exception
            MessageBox.Show("Could not create log file: " & ex.Message, "Warning", 
                MessageBoxButtons.OK, MessageBoxIcon.Warning)
            config.EnableLogging = False
        End Try
    End If

    ' Process based on document type
    If doc.DocumentType = DocumentTypeEnum.kAssemblyDocumentObject Then
        Dim asmDoc As AssemblyDocument = CType(doc, AssemblyDocument)
        Dim sheetMetalParts As New Dictionary(Of String, Integer)
        ProcessComponentOccurrences(asmDoc.ComponentDefinition.Occurrences, sheetMetalParts, logFilePath, config)
        Dim progressBar As Inventor.ProgressBar = ThisApplication.CreateProgressBar(False, sheetMetalParts.Count, "Exporting DXF Files")
        Dim currentStep As Integer = 0
        ExportDXFFiles(sheetMetalParts, config.TargetDirectory, logFilePath, config, progressBar, currentStep)
        progressBar.Close()
    ElseIf doc.DocumentType = DocumentTypeEnum.kPartDocumentObject Then
        Dim partDoc As PartDocument = CType(doc, PartDocument)
        If partDoc.SubType = "{9C464203-9BAE-11D3-8BAD-0060B0CE6BB4}" Then
            Dim progressBar As Inventor.ProgressBar = ThisApplication.CreateProgressBar(False, 1, "Exporting DXF File")
            Dim currentStep As Integer = 0
            ExportSinglePartDXF(partDoc, config.TargetDirectory, logFilePath, config, progressBar, currentStep)
            progressBar.Close()
        Else
            If config.EnableLogging Then LogError(logFilePath, "The active document is not a sheet metal part.")
            MessageBox.Show("The active document is not a sheet metal part.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
        End If
    Else
        If config.EnableLogging Then LogError(logFilePath, "The active document is neither an assembly nor a sheet metal part.")
        MessageBox.Show("The active document is neither an assembly nor a sheet metal part.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
    End If

    MessageBox.Show("DXF export completed to folder: " & vbCrLf & config.TargetDirectory, "Export Complete", MessageBoxButtons.OK, MessageBoxIcon.Information)
End Sub

Sub ProcessComponentOccurrences(occurrences As ComponentOccurrences, sheetMetalParts As Dictionary(Of String, Integer), logFilePath As String, config As Configuration)
    For Each occ As ComponentOccurrence In occurrences
        Try
            If occ.DefinitionDocumentType = DocumentTypeEnum.kPartDocumentObject Then
                Dim partDoc As PartDocument = CType(occ.Definition.Document, PartDocument)
                If partDoc.SubType = "{9C464203-9BAE-11D3-8BAD-0060B0CE6BB4}" Then
                    Dim smCompDef As SheetMetalComponentDefinition = CType(partDoc.ComponentDefinition, SheetMetalComponentDefinition)
                    If smCompDef.HasFlatPattern Then
                        Dim partNumber As String = partDoc.PropertySets.Item("Design Tracking Properties").Item("Part Number").Value.ToString()
                        If sheetMetalParts.ContainsKey(partNumber) Then
                            sheetMetalParts(partNumber) += 1
                        Else
                            sheetMetalParts.Add(partNumber, 1)
                        End If
                    Else
                        If config.EnableLogging Then LogError(logFilePath, "Skipping DXF file for: " & partDoc.FullFileName & " - Flat Pattern Missing")
                    End If
                End If
            ElseIf occ.DefinitionDocumentType = DocumentTypeEnum.kAssemblyDocumentObject Then
                ' Recursively process sub-assemblies
                ProcessComponentOccurrences(occ.SubOccurrences, sheetMetalParts, logFilePath, config)
            End If
        Catch ex As Exception
            If config.EnableLogging Then LogError(logFilePath, "Error processing occurrence: " & occ.Name & " - " & ex.Message)
        End Try
    Next
End Sub

Sub ExportDXFFiles(sheetMetalParts As Dictionary(Of String, Integer), targetDir As String, logFilePath As String, config As Configuration, progressBar As Inventor.ProgressBar, ByRef currentStep As Integer)
    For Each partNumber As String In sheetMetalParts.Keys
        currentStep += 1
        Dim qty As Integer = sheetMetalParts(partNumber)
        Dim partDoc As PartDocument = Nothing
        Try
            partDoc = OpenPartDocument(partNumber)
            If partDoc Is Nothing Then
                Throw New Exception("Part file not found or couldn't be opened")
            End If

            Dim material As String = GetMaterial(partDoc)
            Dim thickness As String = GetThickness(partDoc, config.ThicknessRounding)
            Dim fileName As String = FormatFileName(config.FileNameFormat, partNumber, material, thickness, qty)
            Dim filePath As String = System.IO.Path.Combine(targetDir, fileName & ".dxf")

            If config.EnableLogging Then
                LogError(logFilePath, "Processing part: " & partNumber)
                LogError(logFilePath, "File path: " & filePath)
            End If

            If Not IsValidPath(filePath) Then
                If config.EnableLogging Then LogError(logFilePath, "Invalid file path: " & filePath)
                Continue For
            End If

            If config.EnableLogging Then LogError(logFilePath, "Opened document: " & partDoc.FullFileName)
            Dim smCompDef As SheetMetalComponentDefinition = CType(partDoc.ComponentDefinition, SheetMetalComponentDefinition)

            If smCompDef.HasFlatPattern Then
                Dim flatPattern As FlatPattern = smCompDef.FlatPattern
                Dim oDataIO As DataIO = flatPattern.DataIO
                If config.EnableLogging Then LogError(logFilePath, "Prepared DataIO for: " & partDoc.FullFileName)

                Try
                    oDataIO.WriteDataToFile(config.DXFOptions, filePath)
                    If config.EnableLogging Then LogError(logFilePath, "Exported DXF for: " & partDoc.FullFileName & " to " & filePath)
                Catch ex As Exception
                    If config.EnableLogging Then LogError(logFilePath, "Error exporting DXF for: " & partDoc.FullFileName & " - " & ex.Message)
                End Try
            Else
                If config.EnableLogging Then LogError(logFilePath, "Skipping DXF file for: " & partDoc.FullFileName & " - Flat Pattern Missing")
            End If

        Catch ex As Exception
            If config.EnableLogging Then LogError(logFilePath, "Error processing part: " & partNumber & " - " & ex.Message)
        Finally
            If partDoc IsNot Nothing Then
                partDoc.Close(False)
                If config.EnableLogging Then LogError(logFilePath, "Closed document: " & partDoc.FullFileName)
            End If
            ' Update progress bar
            Dim progressPercent As Integer = CInt((currentStep / sheetMetalParts.Count) * 100)
            progressBar.Message = "Exporting: " & partNumber & " (" & progressPercent & "%)"
            progressBar.UpdateProgress()
        End Try
    Next
End Sub

Sub ExportSinglePartDXF(partDoc As PartDocument, targetDir As String, logFilePath As String, config As Configuration, progressBar As Inventor.ProgressBar, ByRef currentStep As Integer)
    currentStep += 1
    Dim partNumber As String = partDoc.PropertySets.Item("Design Tracking Properties").Item("Part Number").Value.ToString()
    Dim material As String = GetMaterial(partDoc)
    Dim thickness As String = GetThickness(partDoc, config.ThicknessRounding)
    Dim fileName As String = FormatFileName(config.FileNameFormat, partNumber, material, thickness, 1)
    Dim filePath As String = System.IO.Path.Combine(targetDir, fileName & ".dxf")

    If config.EnableLogging Then
        LogError(logFilePath, "Processing part: " & partNumber)
        LogError(logFilePath, "File path: " & filePath)
    End If

    If Not IsValidPath(filePath) Then
        If config.EnableLogging Then LogError(logFilePath, "Invalid file path: " & filePath)
        Exit Sub
    End If

    Try
        Dim smCompDef As SheetMetalComponentDefinition = CType(partDoc.ComponentDefinition, SheetMetalComponentDefinition)

        If smCompDef.HasFlatPattern Then
            Dim flatPattern As FlatPattern = smCompDef.FlatPattern
            Dim oDataIO As DataIO = flatPattern.DataIO
            If config.EnableLogging Then LogError(logFilePath, "Prepared DataIO for: " & partDoc.FullFileName)

            Try
                oDataIO.WriteDataToFile(config.DXFOptions, filePath)
                If config.EnableLogging Then LogError(logFilePath, "Exported DXF for: " & partDoc.FullFileName & " to " & filePath)
            Catch ex As Exception
                If config.EnableLogging Then LogError(logFilePath, "Error exporting DXF for: " & partDoc.FullFileName & " - " & ex.Message)
            End Try
        Else
            If config.EnableLogging Then LogError(logFilePath, "Skipping DXF file for: " & partDoc.FullFileName & " - Flat Pattern Missing")
        End If
    Catch ex As Exception
        If config.EnableLogging Then LogError(logFilePath, "Error processing part: " & partNumber & " - " & ex.Message)
    Finally
        ' Update progress bar for single part
        progressBar.Message = "Exporting: " & partNumber & " (100%)"
        progressBar.UpdateProgress()
    End Try
End Sub

Function FormatFileName(format As String, partNumber As String, material As String, thickness As String, qty As Integer) As String
    ' Replace placeholders with actual values
    Dim fileName As String = format
    fileName = fileName.Replace("{partNumber}", partNumber)
    fileName = fileName.Replace("{material}", material)
    fileName = fileName.Replace("{thickness}", thickness)
    fileName = fileName.Replace("{qty}", qty.ToString())
    Return fileName
End Function

Function OpenPartDocument(partNumber As String) As PartDocument
    Dim app As Inventor.Application = ThisApplication
    Dim docs As Documents = app.Documents
    Dim projectManager As DesignProjectManager = app.DesignProjectManager
    Dim activeProject As DesignProject = projectManager.ActiveDesignProject

    ' Try to find the part in already open documents
    For Each doc As Document In docs
        If TypeOf doc Is PartDocument AndAlso doc.PropertySets.Item("Design Tracking Properties").Item("Part Number").Value.ToString() = partNumber Then
            Return CType(doc, PartDocument)
        End If
    Next

    ' If not found in open documents, try to find and open from the project
    Dim fullFileName As String = activeProject.FindFiles(partNumber & ".ipt").Item(1)
    If Not String.IsNullOrEmpty(fullFileName) Then
        Return CType(docs.Open(fullFileName), PartDocument)
    End If

    Return Nothing
End Function

Function IsValidPath(path As String) As Boolean
    Dim isValid As Boolean = True
    Try
        Dim fileInfo As New System.IO.FileInfo(path)
    Catch ex As Exception
        isValid = False
    End Try
    Return isValid
End Function

Sub LogError(logFilePath As String, message As String)
    System.IO.File.AppendAllText(logFilePath, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") & " - " & message & vbCrLf)
End Sub

Function GetMaterial(partDoc As PartDocument) As String
    Try
        Dim material As String = partDoc.PropertySets.Item("Design Tracking Properties").Item("Material").Value.ToString()
        Return material.Replace(" ", "_")
    Catch
        Return "Unknown"
    End Try
End Function

Function GetThickness(partDoc As PartDocument, rounding As Integer) As String
    Try
        Dim smCompDef As SheetMetalComponentDefinition = CType(partDoc.ComponentDefinition, SheetMetalComponentDefinition)
        Dim thickness As Double = smCompDef.Thickness.Value ' Thickness in centimeters
        Dim thicknessMM As Double = thickness * 10 ' Convert to millimeters
        Dim thicknessFormatted As String = Math.Round(thicknessMM, rounding).ToString() & "mm" ' Apply rounding
        Return thicknessFormatted
    Catch
        Return "Unknown"
    End Try
End Function

 

 

INV 2025.3
Message 8 of 20

kresh.bell
Collaborator
Collaborator

Hi @Ivan_Sinicyn 

absolutely fantastic, truly outstanding, works flawlessly, thanks again!!!

Message 9 of 20

DeptaM
Enthusiast
Enthusiast

Hello,
the code is great.

 

Martin Depta

Message 10 of 20

kresh.bell
Collaborator
Collaborator

Hi @Ivan_Sinicyn 
after a few days of using iLogic it really works great.

 

I have an idea, I don't know if it's possible. Is it too complicated for the generated dxf files to be divided according to thickness and material into separate subfolders?

0 Likes
Message 11 of 20

Ivan_Sinicyn
Advocate
Advocate
Accepted solution

@kresh.bell 

Here you go

DXE.png

 

AddReference "System.Drawing"
AddReference "System.Xml"
AddReference "System.Windows.Forms"
Imports System.Drawing
Imports System.Windows.Forms
Imports System.Xml.Serialization
Imports System.IO
Imports TextBox = System.Windows.Forms.TextBox
Imports Point = System.Drawing.Point
Imports Color = System.Drawing.Color
Imports Path = System.IO.Path
Imports File = System.IO.File
Imports Environment = System.Environment

' Configuration class for use during export
Public Class Configuration
    Public TargetDirectory As String
    Public UseComponentFolder As Boolean
    Public EnableLogging As Boolean
    Public LogFileName As String
    Public FileNameFormat As String
    Public DXFOptions As String
    Public ThicknessRounding As Integer
    Public SortByMaterial As Boolean
    Public SortByThickness As Boolean
    Public CleanFileNames As Boolean
End Class

' Serializable class to store user settings
<Serializable()> _
Public Class UserSettings
    Public Property TargetDirectory As String = "C:\DXF"
    Public Property UseComponentFolder As Boolean = False
    Public Property EnableLogging As Boolean = False
    Public Property FileNameFormat As String = "{partNumber}{material}{thickness}_{qty}"
    Public Property DXFOptions As String = "FLAT PATTERN DXF?AcadVersion=R12&RebaseGeometry=True&MergeProfilesIntoPolyline=True&SimplifySpline=True&InteriorProfilesLayer=IV_INTERIOR_PROFILES&InvisibleLayers=IV_TANGENT;IV_BEND;IV_BEND_DOWN;IV_TOOL_CENTER_DOWN;IV_ARC_CENTERS;IV_FEATURE_PROFILES;IV_FEATURE_PROFILES_DOWN;IV_UNCONSUMED_SKETCHES;IV_ROLL_TANGENT;IV_ROLL&SplineToleranceDouble=0.01"
    Public Property ThicknessRounding As Integer = 2
    Public Property SortByMaterial As Boolean = False
    Public Property SortByThickness As Boolean = False
    Public Property CleanFileNames As Boolean = True
End Class

' Class for managing DXF export settings
Public Class SettingsManager
    ' Path to settings file
    Private Shared ReadOnly SettingsFileName As String = "DXFExportSettings.xml"

    ' Get settings directory in user's AppData folder
    Public Shared Function GetSettingsDirectory() As String
        Dim appDataPath As String = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)
        Dim settingsPath As String = Path.Combine(appDataPath, "Autodesk", "Inventor", "DXFExport")

        ' Create directory if it doesn't exist
        If Not Directory.Exists(settingsPath) Then
            Try
                Directory.CreateDirectory(settingsPath)
            Catch ex As Exception
                MessageBox.Show("Could not create settings directory: " & ex.Message,
                    "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning)
                Return appDataPath
            End Try
        End If

        Return settingsPath
    End Function

    ' Get full path to settings file
    Public Shared Function GetSettingsFilePath() As String
        Return Path.Combine(GetSettingsDirectory(), SettingsFileName)
    End Function

    ' Save settings to XML file
    Public Shared Sub SaveSettings(settings As UserSettings)
        Try
            Dim serializer As New XmlSerializer(GetType(UserSettings))
            Using writer As New StreamWriter(GetSettingsFilePath())
                serializer.Serialize(writer, settings)
            End Using
        Catch ex As Exception
            MessageBox.Show("Could not save settings: " & ex.Message,
                "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning)
        End Try
    End Sub

    ' Load settings from XML file
    Public Shared Function LoadSettings() As UserSettings
        Dim filePath As String = GetSettingsFilePath()

        ' If settings file doesn't exist, return default settings
        If Not File.Exists(filePath) Then
            Return New UserSettings()
        End If

        Try
            Dim serializer As New XmlSerializer(GetType(UserSettings))
            Using reader As New StreamReader(filePath)
                Return DirectCast(serializer.Deserialize(reader), UserSettings)
            End Using
        Catch ex As Exception
            MessageBox.Show("Could not load settings: " & ex.Message & vbCrLf &
                "Using default settings instead.", "Warning",
                MessageBoxButtons.OK, MessageBoxIcon.Warning)
            Return New UserSettings()
        End Try
    End Function
End Class

' Settings Form Class
Public Class SettingsForm
    Inherits System.Windows.Forms.Form

    ' Form controls
    Private WithEvents btnOK As Button
    Private WithEvents btnCancel As Button
    Private WithEvents btnBrowse As Button
    Private WithEvents radStaticFolder As RadioButton
    Private WithEvents radComponentFolder As RadioButton
    Private txtTargetDir As TextBox
    Private chkLogging As CheckBox
    Private txtFileNameFormat As TextBox
    Private numThicknessRounding As NumericUpDown
    Private lblFileNameHelp As Label
    Private lblThicknessHelp As Label
    
    ' New controls for folder sorting and file name cleaning
    Private chkSortByMaterial As CheckBox
    Private chkSortByThickness As CheckBox
    Private chkCleanFileNames As CheckBox

    ' Properties to access settings
    Public Property TargetDirectory As String
    Public Property UseComponentFolder As Boolean
    Public Property EnableLogging As Boolean
    Public Property FileNameFormat As String
    Public Property DXFOptions As String
    Public Property ThicknessRounding As Integer
    
    ' New properties for folder sorting and file name cleaning
    Public Property SortByMaterial As Boolean
    Public Property SortByThickness As Boolean
    Public Property CleanFileNames As Boolean

    ' Constants for UI layout
    Private Const FORM_PADDING As Integer = 12 ' Standard padding between controls
    Private Const GROUP_PADDING As Integer = 20 ' Padding inside group boxes
    Private Const LABEL_HEIGHT As Integer = 24 ' Standard height for labels
    Private Const CONTROL_HEIGHT As Integer = 28 ' Standard height for controls
    Private Const BUTTON_WIDTH As Integer = 100 ' Standard width for buttons
    Private Const BUTTON_HEIGHT As Integer = 36 ' Standard height for buttons
    Private Const BROWSE_BUTTON_WIDTH As Integer = 50 ' Width for browse button

Public Sub New()
    ' Form setup with improved dimensions and appearance
    Me.Text = "DXF Export Settings"
    Me.FormBorderStyle = FormBorderStyle.FixedDialog
    Me.MaximizeBox = False
    Me.MinimizeBox = False
    Me.StartPosition = FormStartPosition.CenterScreen
    Me.ClientSize = New Size(540, 500) ' Увеличена высота для размещения всех элементов
    Me.Font = New Font("Segoe UI", 9.5F)
    Me.Padding = New Padding(FORM_PADDING)

    ' Create main GroupBox for export location
    Dim gbExportLocation As New GroupBox()
    gbExportLocation.Text = "Export Location"
    gbExportLocation.Location = New Point(FORM_PADDING, FORM_PADDING)
    gbExportLocation.Width = Me.ClientSize.Width - (FORM_PADDING * 2)
    gbExportLocation.Height = 110
    gbExportLocation.Padding = New Padding(GROUP_PADDING)

    ' Radio buttons for folder selection with improved spacing
    radStaticFolder = New RadioButton()
    radStaticFolder.Text = "Static Folder:"
    radStaticFolder.Location = New Point(GROUP_PADDING, 24)
    radStaticFolder.AutoSize = True
    radStaticFolder.Checked = True

    ' Target directory textbox and browse button with improved layout
    txtTargetDir = New TextBox()
    txtTargetDir.Location = New Point(GROUP_PADDING + 120, 22)
    txtTargetDir.Size = New Size(gbExportLocation.Width - 220, CONTROL_HEIGHT)
    txtTargetDir.Anchor = AnchorStyles.Left Or AnchorStyles.Right Or AnchorStyles.Top

    btnBrowse = New Button()
    btnBrowse.Text = "..."
    btnBrowse.Location = New Point(gbExportLocation.Width - BROWSE_BUTTON_WIDTH - GROUP_PADDING, 21)
    btnBrowse.Size = New Size(BROWSE_BUTTON_WIDTH, CONTROL_HEIGHT)
    btnBrowse.Anchor = AnchorStyles.Right Or AnchorStyles.Top

    radComponentFolder = New RadioButton()
    radComponentFolder.Text = "Main Component Folder"
    radComponentFolder.Location = New Point(GROUP_PADDING, 60)
    radComponentFolder.AutoSize = True

    ' Add controls to the GroupBox
    gbExportLocation.Controls.AddRange({radStaticFolder, radComponentFolder, txtTargetDir, btnBrowse})

    ' Create GroupBox for file options with improved layout and increased height
    Dim gbFileOptions As New GroupBox()
    gbFileOptions.Text = "File Options"
    gbFileOptions.Location = New Point(FORM_PADDING, gbExportLocation.Bottom + FORM_PADDING)
    gbFileOptions.Width = Me.ClientSize.Width - (FORM_PADDING * 2)
    gbFileOptions.Height = 300 ' Значительно увеличена высота для размещения всех элементов
    gbFileOptions.Padding = New Padding(GROUP_PADDING)

    ' Improved layout for file options controls
    ' Enable logging checkbox
    chkLogging = New CheckBox()
    chkLogging.Text = "Enable Logging"
    chkLogging.Location = New Point(GROUP_PADDING, 25)
    chkLogging.AutoSize = True

    ' File name format with better alignment
    Dim lblFileName As New Label()
    lblFileName.Text = "File Name Format:"
    lblFileName.Location = New Point(GROUP_PADDING, 60)
    lblFileName.AutoSize = True

    txtFileNameFormat = New TextBox()
    txtFileNameFormat.Location = New Point(GROUP_PADDING + 120, 58)
    txtFileNameFormat.Size = New Size(gbFileOptions.Width - 160, CONTROL_HEIGHT)
    txtFileNameFormat.Anchor = AnchorStyles.Left Or AnchorStyles.Right Or AnchorStyles.Top

    lblFileNameHelp = New Label()
    lblFileNameHelp.Text = "Available: {partNumber}, {material}, {thickness}, {qty}"
    lblFileNameHelp.Location = New Point(GROUP_PADDING + 120, 88)
    lblFileNameHelp.AutoSize = True
    lblFileNameHelp.ForeColor = Color.DarkGray

    ' Thickness rounding with improved layout
    Dim lblRounding As New Label()
    lblRounding.Text = "Thickness Rounding:"
    lblRounding.Location = New Point(GROUP_PADDING, 120)
    lblRounding.AutoSize = True

    numThicknessRounding = New NumericUpDown()
    numThicknessRounding.Value = 2
    numThicknessRounding.Minimum = 0
    numThicknessRounding.Maximum = 4
    numThicknessRounding.Location = New Point(GROUP_PADDING + 140, 118)
    numThicknessRounding.Size = New Size(70, CONTROL_HEIGHT)

    lblThicknessHelp = New Label()
    lblThicknessHelp.Text = "decimal places (0-4)"
    lblThicknessHelp.Location = New Point(GROUP_PADDING + 220, 120)
    lblThicknessHelp.AutoSize = True
    lblThicknessHelp.ForeColor = Color.DarkGray

    ' New controls for folder sorting - create separate group box with improved positioning
    Dim gbSortOptions As New GroupBox()
    gbSortOptions.Text = "Folder Sorting"
    gbSortOptions.Location = New Point(GROUP_PADDING, 155) ' Увеличено расстояние от предыдущих элементов
    gbSortOptions.Width = gbFileOptions.Width - (GROUP_PADDING * 2)
    gbSortOptions.Height = 90 ' Немного увеличена высота
    gbSortOptions.Padding = New Padding(10)

    chkSortByMaterial = New CheckBox()
    chkSortByMaterial.Text = "Sort by Material"
    chkSortByMaterial.Location = New Point(15, 25)
    chkSortByMaterial.AutoSize = True

    chkSortByThickness = New CheckBox()
    chkSortByThickness.Text = "Sort by Thickness"
    chkSortByThickness.Location = New Point(15, 55)
    chkSortByThickness.AutoSize = True

    ' Add controls to sort options GroupBox
    gbSortOptions.Controls.AddRange({chkSortByMaterial, chkSortByThickness})

    ' Add checkbox for file name cleaning with fixed position for better visibility
    chkCleanFileNames = New CheckBox()
    chkCleanFileNames.Text = "Clean Filename (Replace Spaces and Punctuation)"
    chkCleanFileNames.Location = New Point(GROUP_PADDING, 255) ' Фиксированное положение с запасом места
    chkCleanFileNames.AutoSize = True
    chkCleanFileNames.Checked = True

    ' Add controls to file options GroupBox
    gbFileOptions.Controls.AddRange({chkLogging, lblFileName, txtFileNameFormat, lblFileNameHelp,
        lblRounding, numThicknessRounding, lblThicknessHelp, gbSortOptions, chkCleanFileNames})

    ' Create button panel for more consistent button placement
    Dim buttonPanel As New Panel()
    buttonPanel.Width = Me.ClientSize.Width - (FORM_PADDING * 2)
    buttonPanel.Height = BUTTON_HEIGHT + 10
    buttonPanel.Location = New Point(FORM_PADDING, gbFileOptions.Bottom + FORM_PADDING)

    ' Create improved buttons
    btnCancel = New Button()
    btnCancel.Text = "Cancel"
    btnCancel.Location = New Point(buttonPanel.Width - BUTTON_WIDTH - 5, 0)
    btnCancel.Size = New Size(BUTTON_WIDTH, BUTTON_HEIGHT)
    btnCancel.DialogResult = DialogResult.Cancel
    btnCancel.Anchor = AnchorStyles.Right

    btnOK = New Button()
    btnOK.Text = "OK"
    btnOK.Location = New Point(buttonPanel.Width - (BUTTON_WIDTH * 2) - 15, 0)
    btnOK.Size = New Size(BUTTON_WIDTH, BUTTON_HEIGHT)
    btnOK.DialogResult = DialogResult.OK
    btnOK.Anchor = AnchorStyles.Right

    buttonPanel.Controls.AddRange({btnOK, btnCancel})

    ' Add all control groups to the form
    Me.Controls.AddRange({gbExportLocation, gbFileOptions, buttonPanel})

    ' Set tooltips for controls with improved descriptions
    Dim toolTip As New ToolTip()
    toolTip.SetToolTip(txtTargetDir, "Directory where DXF files will be saved")
    toolTip.SetToolTip(btnBrowse, "Browse for target directory")
    toolTip.SetToolTip(radStaticFolder, "Always export to the specified folder")
    toolTip.SetToolTip(radComponentFolder, "Export to the folder containing the main component")
    toolTip.SetToolTip(chkLogging, "Create a log file with detailed information about the export process")
    toolTip.SetToolTip(txtFileNameFormat, "Format template for exported DXF filenames")
    toolTip.SetToolTip(numThicknessRounding, "Number of decimal places for thickness values in filenames")
    
    ' New tooltips for folder sorting options
    toolTip.SetToolTip(chkSortByMaterial, "Create subfolders for each material type")
    toolTip.SetToolTip(chkSortByThickness, "Create subfolders for each thickness value")
    toolTip.SetToolTip(chkCleanFileNames, "Replace spaces and punctuation with underscores in filenames and folder names")

    ' Set AcceptButton and CancelButton for keyboard navigation
    Me.AcceptButton = btnOK
    Me.CancelButton = btnCancel

    ' Load saved settings
    LoadSavedSettings()

    ' Adjust form height based on actual content
    Me.ClientSize = New Size(Me.ClientSize.Width, buttonPanel.Bottom + FORM_PADDING)
End Sub	

    ' Load previously saved settings
    Private Sub LoadSavedSettings()
        Try
            Dim settings As UserSettings = SettingsManager.LoadSettings()

            ' Apply loaded settings to form controls
            txtTargetDir.Text = settings.TargetDirectory
            radComponentFolder.Checked = settings.UseComponentFolder
            radStaticFolder.Checked = Not settings.UseComponentFolder
            chkLogging.Checked = settings.EnableLogging
            txtFileNameFormat.Text = settings.FileNameFormat
            numThicknessRounding.Value = settings.ThicknessRounding
            
            ' Set new folder sorting options
            chkSortByMaterial.Checked = settings.SortByMaterial
            chkSortByThickness.Checked = settings.SortByThickness
            chkCleanFileNames.Checked = settings.CleanFileNames

            ' Set DXF options
            DXFOptions = settings.DXFOptions

            ' Update UI state based on folder choice
            txtTargetDir.Enabled = Not settings.UseComponentFolder
            btnBrowse.Enabled = Not settings.UseComponentFolder

        Catch ex As Exception
            ' If there's an error loading settings, just use the defaults already set in the form
            MessageBox.Show("Could not load saved settings. Using default settings.",
                "Settings Error", MessageBoxButtons.OK, MessageBoxIcon.Warning)
        End Try
    End Sub

    Private Sub radStaticFolder_CheckedChanged(sender As Object, e As EventArgs) Handles radStaticFolder.CheckedChanged
        ' Make sure controls exist before trying to enable/disable them
        If txtTargetDir IsNot Nothing AndAlso btnBrowse IsNot Nothing Then
            ' Enable or disable the text box and browse button based on selection
            txtTargetDir.Enabled = radStaticFolder.Checked
            btnBrowse.Enabled = radStaticFolder.Checked
        End If
    End Sub

    Private Sub radComponentFolder_CheckedChanged(sender As Object, e As EventArgs) Handles radComponentFolder.CheckedChanged
        ' Make sure controls exist before trying to enable/disable them
        If txtTargetDir IsNot Nothing AndAlso btnBrowse IsNot Nothing Then
            ' This is the inverse of radStaticFolder_CheckedChanged, but keeping both for clarity
            txtTargetDir.Enabled = Not radComponentFolder.Checked
            btnBrowse.Enabled = Not radComponentFolder.Checked
        End If
    End Sub

    Private Sub btnBrowse_Click(sender As Object, e As EventArgs) Handles btnBrowse.Click
        Dim folderBrowser As New FolderBrowserDialog()
        folderBrowser.Description = "Select Target Directory for DXF Export"
        folderBrowser.ShowNewFolderButton = True

        If Not String.IsNullOrEmpty(txtTargetDir.Text) AndAlso System.IO.Directory.Exists(txtTargetDir.Text) Then
            folderBrowser.SelectedPath = txtTargetDir.Text
        End If

        If folderBrowser.ShowDialog() = DialogResult.OK Then
            txtTargetDir.Text = folderBrowser.SelectedPath
        End If
    End Sub

    Private Sub btnOK_Click(sender As Object, e As EventArgs) Handles btnOK.Click
        ' Validate inputs
        If radStaticFolder.Checked AndAlso String.IsNullOrEmpty(txtTargetDir.Text) Then
            MessageBox.Show("Target directory cannot be empty when using static folder option.",
                "Validation Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
            txtTargetDir.Focus()
            Return
        End If

        If String.IsNullOrEmpty(txtFileNameFormat.Text) Then
            MessageBox.Show("File name format cannot be empty.", "Validation Error",
                MessageBoxButtons.OK, MessageBoxIcon.Error)
            txtFileNameFormat.Focus()
            Return
        End If

        ' Save settings to properties
        SaveFormSettingsToProperties()

        ' Save settings to XML file
        SaveCurrentSettingsToXml(True)

        ' Close the form
        Me.DialogResult = DialogResult.OK
        Me.Close()
    End Sub

    ' Save form values to properties
    Private Sub SaveFormSettingsToProperties()
        UseComponentFolder = radComponentFolder.Checked
        TargetDirectory = txtTargetDir.Text
        EnableLogging = chkLogging.Checked
        FileNameFormat = txtFileNameFormat.Text
        ThicknessRounding = CInt(numThicknessRounding.Value)
        SortByMaterial = chkSortByMaterial.Checked
        SortByThickness = chkSortByThickness.Checked
        CleanFileNames = chkCleanFileNames.Checked
    End Sub

    ' Save current settings to XML file
    Private Sub SaveCurrentSettingsToXml(showErrors As Boolean)
        Try
            ' Create settings object
            Dim settings As New UserSettings()
            settings.TargetDirectory = txtTargetDir.Text
            settings.UseComponentFolder = radComponentFolder.Checked
            settings.EnableLogging = chkLogging.Checked
            settings.FileNameFormat = txtFileNameFormat.Text
            settings.DXFOptions = DXFOptions
            settings.ThicknessRounding = CInt(numThicknessRounding.Value)
            settings.SortByMaterial = chkSortByMaterial.Checked
            settings.SortByThickness = chkSortByThickness.Checked
            settings.CleanFileNames = chkCleanFileNames.Checked

            ' Save to file
            SettingsManager.SaveSettings(settings)
        Catch ex As Exception
            ' Show error only if requested
            If showErrors Then
                MessageBox.Show("Could not save settings: " & ex.Message,
                    "Settings Error", MessageBoxButtons.OK, MessageBoxIcon.Warning)
            End If
        End Try
    End Sub

    Private Sub btnCancel_Click(sender As Object, e As EventArgs) Handles btnCancel.Click
        ' Save settings to XML file even when canceling dialog
        SaveCurrentSettingsToXml(False)

        Me.DialogResult = DialogResult.Cancel
        Me.Close()
    End Sub
End Class

' Main method start
Sub Main()
    ' Create and show settings dialog
    Dim settingsForm As New SettingsForm
    If settingsForm.ShowDialog() <> DialogResult.OK Then
        Exit Sub
    End If

    ' Get active document to use for component folder if needed
    Dim doc As Inventor.Document = ThisApplication.ActiveDocument
    If doc Is Nothing Then
        MessageBox.Show("No document is open.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
        Exit Sub
    End If

    ' Get settings from dialog
    Dim config As New Configuration
    config.UseComponentFolder = settingsForm.UseComponentFolder
    config.SortByMaterial = settingsForm.SortByMaterial
    config.SortByThickness = settingsForm.SortByThickness
    config.CleanFileNames = settingsForm.CleanFileNames

    ' Determine target directory based on settings
    If config.UseComponentFolder Then
        ' Use the directory of the active document
        Dim docPath As String = ""

        Try
            ' Get the document path
            docPath = System.IO.Path.GetDirectoryName(doc.FullFileName)

            ' Verify the path exists
            If String.IsNullOrEmpty(docPath) OrElse Not System.IO.Directory.Exists(docPath) Then
                Throw New Exception("Cannot access document folder")
            End If

            config.TargetDirectory = docPath
        Catch ex As Exception
            MessageBox.Show("Could not determine the component folder: " & ex.Message & vbCrLf &
                "Falling back to default folder.", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning)
            config.TargetDirectory = settingsForm.TargetDirectory
        End Try
    Else
        ' Use the static folder from settings
        config.TargetDirectory = settingsForm.TargetDirectory
    End If

    ' Set other configuration options
    config.EnableLogging = settingsForm.EnableLogging
    config.LogFileName = "ExportLog.txt"
    config.FileNameFormat = settingsForm.FileNameFormat
    config.DXFOptions = settingsForm.DXFOptions
    config.ThicknessRounding = settingsForm.ThicknessRounding

    ' Create target directory if it doesn't exist
    If Not System.IO.Directory.Exists(config.TargetDirectory) Then
        Try
            System.IO.Directory.CreateDirectory(config.TargetDirectory)
        Catch ex As Exception
            MessageBox.Show("Could not create the target directory: " & ex.Message, "Error",
                MessageBoxButtons.OK, MessageBoxIcon.Error)
            Exit Sub
        End Try
    End If

    ' Define log file path
    Dim logFilePath As String = System.IO.Path.Combine(config.TargetDirectory, config.LogFileName)

    ' Initialize log file with header if logging is enabled
    If config.EnableLogging Then
        Try
            Dim logHeader As String = "DXF Export Log" & vbCrLf & _
                "Started: " & DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") & vbCrLf & _
                "Target Directory: " & config.TargetDirectory & vbCrLf & _
                "Export Options: " & config.DXFOptions & vbCrLf & _
                "Sort by Material: " & config.SortByMaterial & vbCrLf & _
                "Sort by Thickness: " & config.SortByThickness & vbCrLf & _
                "Clean Filenames: " & config.CleanFileNames & vbCrLf & vbCrLf
            System.IO.File.WriteAllText(logFilePath, logHeader)
        Catch ex As Exception
            MessageBox.Show("Could not create log file: " & ex.Message, "Warning",
                MessageBoxButtons.OK, MessageBoxIcon.Warning)
            config.EnableLogging = False
        End Try
    End If

    ' Process based on document type
    If doc.DocumentType = DocumentTypeEnum.kAssemblyDocumentObject Then
        Dim asmDoc As AssemblyDocument = CType(doc, AssemblyDocument)
        Dim sheetMetalParts As New Dictionary(Of String, Integer)
        ProcessComponentOccurrences(asmDoc.ComponentDefinition.Occurrences, sheetMetalParts, logFilePath, config)
        Dim progressBar As Inventor.ProgressBar = ThisApplication.CreateProgressBar(False, sheetMetalParts.Count, "Exporting DXF Files")
        Dim currentStep As Integer = 0
        ExportDXFFiles(sheetMetalParts, config.TargetDirectory, logFilePath, config, progressBar, currentStep)
        progressBar.Close()
    ElseIf doc.DocumentType = DocumentTypeEnum.kPartDocumentObject Then
        Dim partDoc As PartDocument = CType(doc, PartDocument)
        If partDoc.SubType = "{9C464203-9BAE-11D3-8BAD-0060B0CE6BB4}" Then
            Dim progressBar As Inventor.ProgressBar = ThisApplication.CreateProgressBar(False, 1, "Exporting DXF File")
            Dim currentStep As Integer = 0
            ExportSinglePartDXF(partDoc, config.TargetDirectory, logFilePath, config, progressBar, currentStep)
            progressBar.Close()
        Else
            If config.EnableLogging Then LogError(logFilePath, "The active document is not a sheet metal part.")
            MessageBox.Show("The active document is not a sheet metal part.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
        End If
    Else
        If config.EnableLogging Then LogError(logFilePath, "The active document is neither an assembly nor a sheet metal part.")
        MessageBox.Show("The active document is neither an assembly nor a sheet metal part.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
    End If

    MessageBox.Show("DXF export completed to folder: " & vbCrLf & config.TargetDirectory, "Export Complete", MessageBoxButtons.OK, MessageBoxIcon.Information)
End Sub

Sub ProcessComponentOccurrences(occurrences As ComponentOccurrences, sheetMetalParts As Dictionary(Of String, Integer), logFilePath As String, config As Configuration)
    For Each occ As ComponentOccurrence In occurrences
        Try
            If occ.DefinitionDocumentType = DocumentTypeEnum.kPartDocumentObject Then
                Dim partDoc As PartDocument = CType(occ.Definition.Document, PartDocument)
                If partDoc.SubType = "{9C464203-9BAE-11D3-8BAD-0060B0CE6BB4}" Then
                    Dim smCompDef As SheetMetalComponentDefinition = CType(partDoc.ComponentDefinition, SheetMetalComponentDefinition)
                    If smCompDef.HasFlatPattern Then
                        Dim partNumber As String = partDoc.PropertySets.Item("Design Tracking Properties").Item("Part Number").Value.ToString()
                        If sheetMetalParts.ContainsKey(partNumber) Then
                            sheetMetalParts(partNumber) += 1
                        Else
                            sheetMetalParts.Add(partNumber, 1)
                        End If
                    Else
                        If config.EnableLogging Then LogError(logFilePath, "Skipping DXF file for: " & partDoc.FullFileName & " - Flat Pattern Missing")
                    End If
                End If
            ElseIf occ.DefinitionDocumentType = DocumentTypeEnum.kAssemblyDocumentObject Then
                ' Recursively process sub-assemblies
                ProcessComponentOccurrences(occ.SubOccurrences, sheetMetalParts, logFilePath, config)
            End If
        Catch ex As Exception
            If config.EnableLogging Then LogError(logFilePath, "Error processing occurrence: " & occ.Name & " - " & ex.Message)
        End Try
    Next
End Sub

' Function to get the export directory with optional subfolder structure based on sorting settings
Function GetExportDirectory(baseDir As String, material As String, thickness As String, config As Configuration) As String
    Dim exportDir As String = baseDir
    
    If config.SortByMaterial AndAlso Not String.IsNullOrEmpty(material) AndAlso material <> "Unknown" Then
        exportDir = Path.Combine(exportDir, material)
        
        ' If sorting by thickness is also enabled, create thickness subfolders within material folders
        If config.SortByThickness AndAlso Not String.IsNullOrEmpty(thickness) AndAlso thickness <> "Unknown" Then
            exportDir = Path.Combine(exportDir, thickness)
        End If
    ' If only sorting by thickness is enabled
    ElseIf config.SortByThickness AndAlso Not String.IsNullOrEmpty(thickness) AndAlso thickness <> "Unknown" Then
        exportDir = Path.Combine(exportDir, thickness)
    End If
    
    ' Create directory if it doesn't exist
    If Not Directory.Exists(exportDir) Then
        Try
            Directory.CreateDirectory(exportDir)
        Catch ex As Exception
            ' If directory creation fails, return the base directory
            Return baseDir
        End Try
    End If
    
    Return exportDir
End Function

Sub ExportDXFFiles(sheetMetalParts As Dictionary(Of String, Integer), targetDir As String, logFilePath As String, config As Configuration, progressBar As Inventor.ProgressBar, ByRef currentStep As Integer)
    For Each partNumber As String In sheetMetalParts.Keys
        currentStep += 1
        Dim qty As Integer = sheetMetalParts(partNumber)
        Dim partDoc As PartDocument = Nothing
        Try
            partDoc = OpenPartDocument(partNumber)
            If partDoc Is Nothing Then
                Throw New Exception("Part file not found or couldn't be opened")
            End If

            Dim material As String = GetMaterial(partDoc, config.CleanFileNames)
            Dim thickness As String = GetThickness(partDoc, config.ThicknessRounding)
            
            ' Get the export directory with subfolder structure based on sorting settings
            Dim exportDir As String = GetExportDirectory(targetDir, material, thickness, config)
            
            Dim fileName As String = FormatFileName(config.FileNameFormat, partNumber, material, thickness, qty)
            Dim filePath As String = Path.Combine(exportDir, fileName & ".dxf")

            If config.EnableLogging Then
                LogError(logFilePath, "Processing part: " & partNumber)
                LogError(logFilePath, "Export directory: " & exportDir)
                LogError(logFilePath, "File path: " & filePath)
            End If

            If Not IsValidPath(filePath) Then
                If config.EnableLogging Then LogError(logFilePath, "Invalid file path: " & filePath)
                Continue For
            End If

            If config.EnableLogging Then LogError(logFilePath, "Opened document: " & partDoc.FullFileName)
            Dim smCompDef As SheetMetalComponentDefinition = CType(partDoc.ComponentDefinition, SheetMetalComponentDefinition)

            If smCompDef.HasFlatPattern Then
                Dim flatPattern As FlatPattern = smCompDef.FlatPattern
                Dim oDataIO As DataIO = flatPattern.DataIO
                If config.EnableLogging Then LogError(logFilePath, "Prepared DataIO for: " & partDoc.FullFileName)

                Try
                    oDataIO.WriteDataToFile(config.DXFOptions, filePath)
                    If config.EnableLogging Then LogError(logFilePath, "Exported DXF for: " & partDoc.FullFileName & " to " & filePath)
                Catch ex As Exception
                    If config.EnableLogging Then LogError(logFilePath, "Error exporting DXF for: " & partDoc.FullFileName & " - " & ex.Message)
                End Try
            Else
                If config.EnableLogging Then LogError(logFilePath, "Skipping DXF file for: " & partDoc.FullFileName & " - Flat Pattern Missing")
            End If

        Catch ex As Exception
            If config.EnableLogging Then LogError(logFilePath, "Error processing part: " & partNumber & " - " & ex.Message)
        Finally
            If partDoc IsNot Nothing Then
                partDoc.Close(False)
                If config.EnableLogging Then LogError(logFilePath, "Closed document: " & partDoc.FullFileName)
            End If
            ' Update progress bar
            Dim progressPercent As Integer = CInt((currentStep / sheetMetalParts.Count) * 100)
            progressBar.Message = "Exporting: " & partNumber & " (" & progressPercent & "%)"
            progressBar.UpdateProgress()
        End Try
    Next
End Sub

Sub ExportSinglePartDXF(partDoc As PartDocument, targetDir As String, logFilePath As String, config As Configuration, progressBar As Inventor.ProgressBar, ByRef currentStep As Integer)
    currentStep += 1
    Dim partNumber As String = partDoc.PropertySets.Item("Design Tracking Properties").Item("Part Number").Value.ToString()
    Dim material As String = GetMaterial(partDoc, config.CleanFileNames)
    Dim thickness As String = GetThickness(partDoc, config.ThicknessRounding)
    
    ' Get the export directory with subfolder structure based on sorting settings
    Dim exportDir As String = GetExportDirectory(targetDir, material, thickness, config)
    
    Dim fileName As String = FormatFileName(config.FileNameFormat, partNumber, material, thickness, 1)
    Dim filePath As String = Path.Combine(exportDir, fileName & ".dxf")

    If config.EnableLogging Then
        LogError(logFilePath, "Processing part: " & partNumber)
        LogError(logFilePath, "Export directory: " & exportDir)
        LogError(logFilePath, "File path: " & filePath)
    End If

    If Not IsValidPath(filePath) Then
        If config.EnableLogging Then LogError(logFilePath, "Invalid file path: " & filePath)
        Exit Sub
    End If

    Try
        Dim smCompDef As SheetMetalComponentDefinition = CType(partDoc.ComponentDefinition, SheetMetalComponentDefinition)

        If smCompDef.HasFlatPattern Then
            Dim flatPattern As FlatPattern = smCompDef.FlatPattern
            Dim oDataIO As DataIO = flatPattern.DataIO
            If config.EnableLogging Then LogError(logFilePath, "Prepared DataIO for: " & partDoc.FullFileName)

            Try
                oDataIO.WriteDataToFile(config.DXFOptions, filePath)
                If config.EnableLogging Then LogError(logFilePath, "Exported DXF for: " & partDoc.FullFileName & " to " & filePath)
            Catch ex As Exception
                If config.EnableLogging Then LogError(logFilePath, "Error exporting DXF for: " & partDoc.FullFileName & " - " & ex.Message)
            End Try
        Else
            If config.EnableLogging Then LogError(logFilePath, "Skipping DXF file for: " & partDoc.FullFileName & " - Flat Pattern Missing")
        End If
    Catch ex As Exception
        If config.EnableLogging Then LogError(logFilePath, "Error processing part: " & partNumber & " - " & ex.Message)
    Finally
        ' Update progress bar for single part
        progressBar.Message = "Exporting: " & partNumber & " (100%)"
        progressBar.UpdateProgress()
    End Try
End Sub

Function FormatFileName(format As String, partNumber As String, material As String, thickness As String, qty As Integer) As String
    ' Replace placeholders with actual values
    Dim fileName As String = format
    fileName = fileName.Replace("{partNumber}", partNumber)
    fileName = fileName.Replace("{material}", material)
    fileName = fileName.Replace("{thickness}", thickness)
    fileName = fileName.Replace("{qty}", qty.ToString())
    Return fileName
End Function

Function OpenPartDocument(partNumber As String) As PartDocument
    Dim app As Inventor.Application = ThisApplication
    Dim docs As Documents = app.Documents
    Dim projectManager As DesignProjectManager = app.DesignProjectManager
    Dim activeProject As DesignProject = projectManager.ActiveDesignProject

    ' Try to find the part in already open documents
    For Each doc As Document In docs
        If TypeOf doc Is PartDocument AndAlso doc.PropertySets.Item("Design Tracking Properties").Item("Part Number").Value.ToString() = partNumber Then
            Return CType(doc, PartDocument)
        End If
    Next

    ' If not found in open documents, try to find and open from the project
    Dim fullFileName As String = activeProject.FindFiles(partNumber & ".ipt").Item(1)
    If Not String.IsNullOrEmpty(fullFileName) Then
        Return CType(docs.Open(fullFileName), PartDocument)
    End If

    Return Nothing
End Function

Function IsValidPath(path As String) As Boolean
    Dim isValid As Boolean = True
    Try
        Dim fileInfo As New System.IO.FileInfo(path)
    Catch ex As Exception
        isValid = False
    End Try
    Return isValid
End Function

Sub LogError(logFilePath As String, message As String)
    System.IO.File.AppendAllText(logFilePath, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") & " - " & message & vbCrLf)
End Sub

Function GetMaterial(partDoc As PartDocument, cleanNames As Boolean) As String
    Try
        Dim material As String = partDoc.PropertySets.Item("Design Tracking Properties").Item("Material").Value.ToString()
        
        ' Clean the material name if option is enabled
        If cleanNames Then
            ' Replace spaces and punctuation with underscores
            Dim cleanedMaterial As String = material
            
            ' Define punctuation characters to replace
            Dim punctuationChars() As Char = {" ", ",", ".", ";", ":", "'", """", "!", "?", "(", ")", "[", "]", "{", "}", "/", "\", "-", "+", "=", "*", "&", "^", "%", "$", "#", "@"}
            
            ' Replace each punctuation character with underscore
            For Each c As Char In punctuationChars
                cleanedMaterial = cleanedMaterial.Replace(c, "_"c)
            Next
            
            ' Replace multiple consecutive underscores with a single one
            While cleanedMaterial.Contains("__")
                cleanedMaterial = cleanedMaterial.Replace("__", "_")
            End While
            
            ' Remove leading or trailing underscores
            cleanedMaterial = cleanedMaterial.Trim("_"c)
            
            Return cleanedMaterial
        Else
            Return material
        End If
    Catch
        Return "Unknown"
    End Try
End Function

Function GetThickness(partDoc As PartDocument, rounding As Integer) As String
    Try
        Dim smCompDef As SheetMetalComponentDefinition = CType(partDoc.ComponentDefinition, SheetMetalComponentDefinition)
        Dim thickness As Double = smCompDef.Thickness.Value ' Thickness in centimeters
        Dim thicknessMM As Double = thickness * 10 ' Convert to millimeters
        Dim thicknessFormatted As String = Math.Round(thicknessMM, rounding).ToString() & "mm" ' Apply rounding
        Return thicknessFormatted
    Catch
        Return "Unknown"
    End Try
End Function

 

INV 2025.3
Message 12 of 20

kresh.bell
Collaborator
Collaborator

@Ivan_Sinicyn absolutely fantastic, thanks again!!!

Message 13 of 20

agunPXRZM
Contributor
Contributor

this is the greatest ilogic code i have ever seen.🙏

Message 14 of 20

GosponZ
Collaborator
Collaborator

Fantastic rule but wandering if qty could be replaced with custom properties TTL and also not to show thickness in description

0 Likes
Message 15 of 20

Ivan_Sinicyn
Advocate
Advocate

Updated. Now you can add custom properties using {prop:PropertyName}. For example, {prop:VGBT}.

The “File Name Format:” string is fully configurable. You can reorganize the placeholders, and remove or add what you need. All customizations are saved in the settings file.

CustomIPR.png



AddReference "System.Drawing"
AddReference "System.Xml"
AddReference "System.Windows.Forms"
Imports System.Drawing
Imports System.Windows.Forms
Imports System.Xml.Serialization
Imports System.IO
Imports TextBox = System.Windows.Forms.TextBox
Imports Point = System.Drawing.Point
Imports Color = System.Drawing.Color
Imports Path = System.IO.Path
Imports File = System.IO.File
Imports Environment = System.Environment

' Configuration class for use during export
Public Class Configuration
    Public TargetDirectory As String
    Public UseComponentFolder As Boolean
    Public EnableLogging As Boolean
    Public LogFileName As String
    Public FileNameFormat As String
    Public DXFOptions As String
    Public ThicknessRounding As Integer
    Public SortByMaterial As Boolean
    Public SortByThickness As Boolean
    Public CleanFileNames As Boolean
End Class

' Serializable class to store user settings
<Serializable()> _
Public Class UserSettings
    Public Property TargetDirectory As String = "C:\DXF"
    Public Property UseComponentFolder As Boolean = False
    Public Property EnableLogging As Boolean = False
    Public Property FileNameFormat As String = "{partNumber}{material}{thickness}_{qty}"
    Public Property DXFOptions As String = "FLAT PATTERN DXF?AcadVersion=R12&RebaseGeometry=True&MergeProfilesIntoPolyline=True&SimplifySpline=True&InteriorProfilesLayer=IV_INTERIOR_PROFILES&InvisibleLayers=IV_TANGENT;IV_BEND;IV_BEND_DOWN;IV_TOOL_CENTER_DOWN;IV_ARC_CENTERS;IV_FEATURE_PROFILES;IV_FEATURE_PROFILES_DOWN;IV_UNCONSUMED_SKETCHES;IV_ROLL_TANGENT;IV_ROLL&SplineToleranceDouble=0.01"
    Public Property ThicknessRounding As Integer = 2
    Public Property SortByMaterial As Boolean = False
    Public Property SortByThickness As Boolean = False
    Public Property CleanFileNames As Boolean = True
End Class

' Class for managing DXF export settings
Public Class SettingsManager
    ' Path to settings file
    Private Shared ReadOnly SettingsFileName As String = "DXFExportSettings.xml"

    ' Get settings directory in user's AppData folder
    Public Shared Function GetSettingsDirectory() As String
        Dim appDataPath As String = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)
        Dim settingsPath As String = Path.Combine(appDataPath, "Autodesk", "Inventor", "DXFExport")

        ' Create directory if it doesn't exist
        If Not Directory.Exists(settingsPath) Then
            Try
                Directory.CreateDirectory(settingsPath)
            Catch ex As Exception
                MessageBox.Show("Could not create settings directory: " & ex.Message,
                    "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning)
                Return appDataPath
            End Try
        End If

        Return settingsPath
    End Function

    ' Get full path to settings file
    Public Shared Function GetSettingsFilePath() As String
        Return Path.Combine(GetSettingsDirectory(), SettingsFileName)
    End Function

    ' Save settings to XML file
    Public Shared Sub SaveSettings(settings As UserSettings)
        Try
            Dim serializer As New XmlSerializer(GetType(UserSettings))
            Using writer As New StreamWriter(GetSettingsFilePath())
                serializer.Serialize(writer, settings)
            End Using
        Catch ex As Exception
            MessageBox.Show("Could not save settings: " & ex.Message,
                "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning)
        End Try
    End Sub

    ' Load settings from XML file
    Public Shared Function LoadSettings() As UserSettings
        Dim filePath As String = GetSettingsFilePath()

        ' If settings file doesn't exist, return default settings
        If Not File.Exists(filePath) Then
            Return New UserSettings()
        End If

        Try
            Dim serializer As New XmlSerializer(GetType(UserSettings))
            Using reader As New StreamReader(filePath)
                Return DirectCast(serializer.Deserialize(reader), UserSettings)
            End Using
        Catch ex As Exception
            MessageBox.Show("Could not load settings: " & ex.Message & vbCrLf &
                "Using default settings instead.", "Warning",
                MessageBoxButtons.OK, MessageBoxIcon.Warning)
            Return New UserSettings()
        End Try
    End Function
End Class

' Settings Form Class
Public Class SettingsForm
    Inherits System.Windows.Forms.Form

    ' Form controls
    Private WithEvents btnOK As Button
    Private WithEvents btnCancel As Button
    Private WithEvents btnBrowse As Button
    Private WithEvents radStaticFolder As RadioButton
    Private WithEvents radComponentFolder As RadioButton
    Private txtTargetDir As TextBox
    Private chkLogging As CheckBox
    Private txtFileNameFormat As TextBox
    Private numThicknessRounding As NumericUpDown
    Private lblFileNameHelp As Label
    Private lblThicknessHelp As Label
    
    ' New controls for folder sorting and file name cleaning
    Private chkSortByMaterial As CheckBox
    Private chkSortByThickness As CheckBox
    Private chkCleanFileNames As CheckBox

    ' Properties to access settings
    Public Property TargetDirectory As String
    Public Property UseComponentFolder As Boolean
    Public Property EnableLogging As Boolean
    Public Property FileNameFormat As String
    Public Property DXFOptions As String
    Public Property ThicknessRounding As Integer
    
    ' New properties for folder sorting and file name cleaning
    Public Property SortByMaterial As Boolean
    Public Property SortByThickness As Boolean
    Public Property CleanFileNames As Boolean

    ' Constants for UI layout
    Private Const FORM_PADDING As Integer = 12 ' Standard padding between controls
    Private Const GROUP_PADDING As Integer = 20 ' Padding inside group boxes
    Private Const LABEL_HEIGHT As Integer = 24 ' Standard height for labels
    Private Const CONTROL_HEIGHT As Integer = 28 ' Standard height for controls
    Private Const BUTTON_WIDTH As Integer = 100 ' Standard width for buttons
    Private Const BUTTON_HEIGHT As Integer = 36 ' Standard height for buttons
    Private Const BROWSE_BUTTON_WIDTH As Integer = 50 ' Width for browse button

    Public Sub New()
        ' Form setup with improved dimensions and appearance
        Me.Text = "DXF Export Settings"
        Me.FormBorderStyle = FormBorderStyle.FixedDialog
        Me.MaximizeBox = False
        Me.MinimizeBox = False
        Me.StartPosition = FormStartPosition.CenterScreen
        Me.ClientSize = New Size(580, 500)  ' Increased width from 540 to 580
        Me.Font = New Font("Segoe UI", 9.5F)
        Me.Padding = New Padding(FORM_PADDING)

        ' Create main GroupBox for export location
        Dim gbExportLocation As New GroupBox()
        gbExportLocation.Text = "Export Location"
        gbExportLocation.Location = New Point(FORM_PADDING, FORM_PADDING)
        gbExportLocation.Width = Me.ClientSize.Width - (FORM_PADDING * 2)
        gbExportLocation.Height = 110
        gbExportLocation.Padding = New Padding(GROUP_PADDING)

        ' Radio buttons for folder selection with improved spacing
        radStaticFolder = New RadioButton()
        radStaticFolder.Text = "Static Folder:"
        radStaticFolder.Location = New Point(GROUP_PADDING, 24)
        radStaticFolder.AutoSize = True
        radStaticFolder.Checked = True

        ' Target directory textbox and browse button with improved layout
        txtTargetDir = New TextBox()
        txtTargetDir.Location = New Point(GROUP_PADDING + 120, 22)
        txtTargetDir.Size = New Size(gbExportLocation.Width - 220, CONTROL_HEIGHT)
        txtTargetDir.Anchor = AnchorStyles.Left Or AnchorStyles.Right Or AnchorStyles.Top

        btnBrowse = New Button()
        btnBrowse.Text = "..."
        btnBrowse.Location = New Point(gbExportLocation.Width - BROWSE_BUTTON_WIDTH - GROUP_PADDING, 21)
        btnBrowse.Size = New Size(BROWSE_BUTTON_WIDTH, CONTROL_HEIGHT)
        btnBrowse.Anchor = AnchorStyles.Right Or AnchorStyles.Top

        radComponentFolder = New RadioButton()
        radComponentFolder.Text = "Main Component Folder"
        radComponentFolder.Location = New Point(GROUP_PADDING, 60)
        radComponentFolder.AutoSize = True

        ' Add controls to the GroupBox
        gbExportLocation.Controls.AddRange({radStaticFolder, radComponentFolder, txtTargetDir, btnBrowse})

        ' Create GroupBox for file options with improved layout and increased height
        Dim gbFileOptions As New GroupBox()
        gbFileOptions.Text = "File Options"
        gbFileOptions.Location = New Point(FORM_PADDING, gbExportLocation.Bottom + FORM_PADDING)
        gbFileOptions.Width = Me.ClientSize.Width - (FORM_PADDING * 2)
        gbFileOptions.Height = 300
        gbFileOptions.Padding = New Padding(GROUP_PADDING)

        ' Improved layout for file options controls
        ' Enable logging checkbox
        chkLogging = New CheckBox()
        chkLogging.Text = "Enable Logging"
        chkLogging.Location = New Point(GROUP_PADDING, 25)
        chkLogging.AutoSize = True

        ' File name format with better alignment
        Dim lblFileName As New Label()
        lblFileName.Text = "File Name Format:"
        lblFileName.Location = New Point(GROUP_PADDING, 60)
        lblFileName.AutoSize = True

        txtFileNameFormat = New TextBox()
        txtFileNameFormat.Location = New Point(GROUP_PADDING + 120, 58)
        ' Reduce width to leave space for help button
        txtFileNameFormat.Size = New Size(gbFileOptions.Width - 190, CONTROL_HEIGHT)
        txtFileNameFormat.Anchor = AnchorStyles.Left Or AnchorStyles.Right Or AnchorStyles.Top

        ' Create info-button for filename format with proper positioning
        Dim btnFileNameHelp As New Button()
        btnFileNameHelp.Text = "?"
        btnFileNameHelp.Font = New Font("Segoe UI", 9.5F, FontStyle.Bold)
        btnFileNameHelp.ForeColor = Color.RoyalBlue
        btnFileNameHelp.BackColor = Color.Transparent
        btnFileNameHelp.FlatStyle = FlatStyle.Flat
        btnFileNameHelp.FlatAppearance.BorderSize = 0
        btnFileNameHelp.Size = New Size(24, 24)
        ' Place button to the right of the textbox with fixed margin
        btnFileNameHelp.Location = New Point(txtFileNameFormat.Right + 10, txtFileNameFormat.Top)
        btnFileNameHelp.Cursor = Cursors.Help
        btnFileNameHelp.UseVisualStyleBackColor = False
        btnFileNameHelp.TabStop = False

        ' Thickness rounding with improved layout
        Dim lblRounding As New Label()
        lblRounding.Text = "Thickness Rounding:"
        lblRounding.Location = New Point(GROUP_PADDING, 120)
        lblRounding.AutoSize = True

        numThicknessRounding = New NumericUpDown()
        numThicknessRounding.Value = 2
        numThicknessRounding.Minimum = 0
        numThicknessRounding.Maximum = 4
        numThicknessRounding.Location = New Point(GROUP_PADDING + 140, 118)
        numThicknessRounding.Size = New Size(70, CONTROL_HEIGHT)

        lblThicknessHelp = New Label()
        lblThicknessHelp.Text = "decimal places (0-4)"
        lblThicknessHelp.Location = New Point(GROUP_PADDING + 220, 120)
        lblThicknessHelp.AutoSize = True
        lblThicknessHelp.ForeColor = Color.DarkGray

        ' New controls for folder sorting - separate group box with improved positioning
        Dim gbSortOptions As New GroupBox()
        gbSortOptions.Text = "Folder Sorting"
        gbSortOptions.Location = New Point(GROUP_PADDING, 155)
        gbSortOptions.Width = gbFileOptions.Width - (GROUP_PADDING * 2)
        gbSortOptions.Height = 90
        gbSortOptions.Padding = New Padding(10)

        chkSortByMaterial = New CheckBox()
        chkSortByMaterial.Text = "Sort by Material"
        chkSortByMaterial.Location = New Point(15, 25)
        chkSortByMaterial.AutoSize = True

        chkSortByThickness = New CheckBox()
        chkSortByThickness.Text = "Sort by Thickness"
        chkSortByThickness.Location = New Point(15, 55)
        chkSortByThickness.AutoSize = True

        ' Add controls to sort options GroupBox
        gbSortOptions.Controls.AddRange({chkSortByMaterial, chkSortByThickness})

        ' Add checkbox for file name cleaning with fixed position for better visibility
        chkCleanFileNames = New CheckBox()
        chkCleanFileNames.Text = "Clean Filename (Replace Spaces and Punctuation)"
        chkCleanFileNames.Location = New Point(GROUP_PADDING, 255)
        chkCleanFileNames.AutoSize = True
        chkCleanFileNames.Checked = True

        ' Add controls to file options GroupBox
        gbFileOptions.Controls.AddRange({chkLogging, lblFileName, txtFileNameFormat, btnFileNameHelp,
            lblRounding, numThicknessRounding, lblThicknessHelp, gbSortOptions, chkCleanFileNames})

        ' Create button panel for more consistent button placement
        Dim buttonPanel As New Panel()
        buttonPanel.Width = Me.ClientSize.Width - (FORM_PADDING * 2)
        buttonPanel.Height = BUTTON_HEIGHT + 10
        buttonPanel.Location = New Point(FORM_PADDING, gbFileOptions.Bottom + FORM_PADDING)

        ' Create improved buttons
        btnCancel = New Button()
        btnCancel.Text = "Cancel"
        btnCancel.Location = New Point(buttonPanel.Width - BUTTON_WIDTH - 5, 0)
        btnCancel.Size = New Size(BUTTON_WIDTH, BUTTON_HEIGHT)
        btnCancel.DialogResult = DialogResult.Cancel
        btnCancel.Anchor = AnchorStyles.Right

        btnOK = New Button()
        btnOK.Text = "OK"
        btnOK.Location = New Point(buttonPanel.Width - (BUTTON_WIDTH * 2) - 15, 0)
        btnOK.Size = New Size(BUTTON_WIDTH, BUTTON_HEIGHT)
        btnOK.DialogResult = DialogResult.OK
        btnOK.Anchor = AnchorStyles.Right

        buttonPanel.Controls.AddRange({btnOK, btnCancel})

        ' Add all control groups to the form
        Me.Controls.AddRange({gbExportLocation, gbFileOptions, buttonPanel})

        ' Create and configure tooltips for all controls
        Dim toolTip As New ToolTip()
        toolTip.SetToolTip(txtTargetDir, "Directory where DXF files will be saved")
        toolTip.SetToolTip(btnBrowse, "Browse for target directory")
        toolTip.SetToolTip(radStaticFolder, "Always export to the specified folder")
        toolTip.SetToolTip(radComponentFolder, "Export to the folder containing the main component")
        toolTip.SetToolTip(chkLogging, "Create a log file with detailed information about the export process")
        toolTip.SetToolTip(txtFileNameFormat, "Format template for exported DXF filenames")
        toolTip.SetToolTip(numThicknessRounding, "Number of decimal places for thickness values in filenames")
        toolTip.SetToolTip(chkSortByMaterial, "Create subfolders for each material type")
        toolTip.SetToolTip(chkSortByThickness, "Create subfolders for each thickness value")
        toolTip.SetToolTip(chkCleanFileNames, "Replace spaces and punctuation with underscores in filenames and folder names")
        
        ' Detailed tooltip for filename format button
        toolTip.SetToolTip(btnFileNameHelp, "Available placeholders for filename format:" & vbCrLf & 
            "{partNumber} - Part number from document properties" & vbCrLf & 
            "{material} - Material from document properties" & vbCrLf & 
            "{thickness} - Sheet metal thickness in mm" & vbCrLf & 
            "{qty} - Quantity of parts in assembly" & vbCrLf & 
            "{prop:PropertyName} - Any custom property value, e.g. {prop:Description}")
        
        ' Enhance tooltip behavior
        toolTip.AutoPopDelay = 15000  ' Show tooltip for 15 seconds
        toolTip.InitialDelay = 500    ' Delay before showing: 0.5 seconds
        toolTip.ReshowDelay = 200     ' Quick re-show
        toolTip.UseFading = True
        toolTip.UseAnimation = True

        ' Set AcceptButton and CancelButton for keyboard navigation
        Me.AcceptButton = btnOK
        Me.CancelButton = btnCancel

        ' Load saved settings
        LoadSavedSettings()

        ' Adjust form height based on actual content
        Me.ClientSize = New Size(Me.ClientSize.Width, buttonPanel.Bottom + FORM_PADDING)
    End Sub

    ' Load previously saved settings
    Private Sub LoadSavedSettings()
        Try
            Dim settings As UserSettings = SettingsManager.LoadSettings()

            ' Apply loaded settings to form controls
            txtTargetDir.Text = settings.TargetDirectory
            radComponentFolder.Checked = settings.UseComponentFolder
            radStaticFolder.Checked = Not settings.UseComponentFolder
            chkLogging.Checked = settings.EnableLogging
            txtFileNameFormat.Text = settings.FileNameFormat
            numThicknessRounding.Value = settings.ThicknessRounding
            
            ' Set new folder sorting options
            chkSortByMaterial.Checked = settings.SortByMaterial
            chkSortByThickness.Checked = settings.SortByThickness
            chkCleanFileNames.Checked = settings.CleanFileNames

            ' Set DXF options
            DXFOptions = settings.DXFOptions

            ' Update UI state based on folder choice
            txtTargetDir.Enabled = Not settings.UseComponentFolder
            btnBrowse.Enabled = Not settings.UseComponentFolder

        Catch ex As Exception
            ' If there's an error loading settings, just use the defaults already set in the form
            MessageBox.Show("Could not load saved settings. Using default settings.",
                "Settings Error", MessageBoxButtons.OK, MessageBoxIcon.Warning)
        End Try
    End Sub

    Private Sub radStaticFolder_CheckedChanged(sender As Object, e As EventArgs) Handles radStaticFolder.CheckedChanged
        ' Enable or disable the text box and browse button based on selection
        If txtTargetDir IsNot Nothing AndAlso btnBrowse IsNot Nothing Then
            txtTargetDir.Enabled = radStaticFolder.Checked
            btnBrowse.Enabled = radStaticFolder.Checked
        End If
    End Sub

    Private Sub radComponentFolder_CheckedChanged(sender As Object, e As EventArgs) Handles radComponentFolder.CheckedChanged
        ' Opposite of radStaticFolder_CheckedChanged for clarity
        If txtTargetDir IsNot Nothing AndAlso btnBrowse IsNot Nothing Then
            txtTargetDir.Enabled = Not radComponentFolder.Checked
            btnBrowse.Enabled = Not radComponentFolder.Checked
        End If
    End Sub

    Private Sub btnBrowse_Click(sender As Object, e As EventArgs) Handles btnBrowse.Click
        Dim folderBrowser As New FolderBrowserDialog()
        folderBrowser.Description = "Select Target Directory for DXF Export"
        folderBrowser.ShowNewFolderButton = True

        ' Initialize with current path if valid
        If Not String.IsNullOrEmpty(txtTargetDir.Text) AndAlso System.IO.Directory.Exists(txtTargetDir.Text) Then
            folderBrowser.SelectedPath = txtTargetDir.Text
        End If

        If folderBrowser.ShowDialog() = DialogResult.OK Then
            txtTargetDir.Text = folderBrowser.SelectedPath
        End If
    End Sub

    Private Sub btnOK_Click(sender As Object, e As EventArgs) Handles btnOK.Click
        ' Validate inputs
        If radStaticFolder.Checked AndAlso String.IsNullOrEmpty(txtTargetDir.Text) Then
            MessageBox.Show("Target directory cannot be empty when using static folder option.",
                "Validation Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
            txtTargetDir.Focus()
            Return
        End If

        If String.IsNullOrEmpty(txtFileNameFormat.Text) Then
            MessageBox.Show("File name format cannot be empty.", "Validation Error",
                MessageBoxButtons.OK, MessageBoxIcon.Error)
            txtFileNameFormat.Focus()
            Return
        End If

        ' Save settings to properties
        SaveFormSettingsToProperties()

        ' Save settings to XML file
        SaveCurrentSettingsToXml(True)

        ' Close the form
        Me.DialogResult = DialogResult.OK
        Me.Close()
    End Sub

    ' Save form values to properties
    Private Sub SaveFormSettingsToProperties()
        UseComponentFolder = radComponentFolder.Checked
        TargetDirectory = txtTargetDir.Text
        EnableLogging = chkLogging.Checked
        FileNameFormat = txtFileNameFormat.Text
        ThicknessRounding = CInt(numThicknessRounding.Value)
        SortByMaterial = chkSortByMaterial.Checked
        SortByThickness = chkSortByThickness.Checked
        CleanFileNames = chkCleanFileNames.Checked
    End Sub

    ' Save current settings to XML file
    Private Sub SaveCurrentSettingsToXml(showErrors As Boolean)
        Try
            ' Create settings object
            Dim settings As New UserSettings()
            settings.TargetDirectory = txtTargetDir.Text
            settings.UseComponentFolder = radComponentFolder.Checked
            settings.EnableLogging = chkLogging.Checked
            settings.FileNameFormat = txtFileNameFormat.Text
            settings.DXFOptions = DXFOptions
            settings.ThicknessRounding = CInt(numThicknessRounding.Value)
            settings.SortByMaterial = chkSortByMaterial.Checked
            settings.SortByThickness = chkSortByThickness.Checked
            settings.CleanFileNames = chkCleanFileNames.Checked

            ' Save to file
            SettingsManager.SaveSettings(settings)
        Catch ex As Exception
            ' Show error only if requested
            If showErrors Then
                MessageBox.Show("Could not save settings: " & ex.Message,
                    "Settings Error", MessageBoxButtons.OK, MessageBoxIcon.Warning)
            End If
        End Try
    End Sub

    Private Sub btnCancel_Click(sender As Object, e As EventArgs) Handles btnCancel.Click
        ' Save settings to XML file even when cancelling
        SaveCurrentSettingsToXml(False)

        Me.DialogResult = DialogResult.Cancel
        Me.Close()
    End Sub
End Class

' Main entry point
Sub Main()
    ' Create and show settings dialog
    Dim settingsForm As New SettingsForm
    If settingsForm.ShowDialog() <> DialogResult.OK Then
        Exit Sub
    End If

    ' Get active document to use for component folder if needed
    Dim doc As Inventor.Document = ThisApplication.ActiveDocument
    If doc Is Nothing Then
        MessageBox.Show("No document is open.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
        Exit Sub
    End If

    ' Get settings from dialog
    Dim config As New Configuration
    config.UseComponentFolder = settingsForm.UseComponentFolder
    config.SortByMaterial = settingsForm.SortByMaterial
    config.SortByThickness = settingsForm.SortByThickness
    config.CleanFileNames = settingsForm.CleanFileNames

    ' Determine target directory based on settings
    If config.UseComponentFolder Then
        ' Use the directory of the active document
        Dim docPath As String = ""
        Try
            ' Get the document path
            docPath = System.IO.Path.GetDirectoryName(doc.FullFileName)
            ' Verify the path exists
            If String.IsNullOrEmpty(docPath) OrElse Not System.IO.Directory.Exists(docPath) Then
                Throw New Exception("Cannot access document folder")
            End If
            config.TargetDirectory = docPath
        Catch ex As Exception
            MessageBox.Show("Could not determine the component folder: " & ex.Message & vbCrLf &
                "Falling back to default folder.", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning)
            config.TargetDirectory = settingsForm.TargetDirectory
        End Try
    Else
        ' Use the static folder from settings
        config.TargetDirectory = settingsForm.TargetDirectory
    End If

    ' Set other configuration options
    config.EnableLogging = settingsForm.EnableLogging
    config.LogFileName = "ExportLog.txt"
    config.FileNameFormat = settingsForm.FileNameFormat
    config.DXFOptions = settingsForm.DXFOptions
    config.ThicknessRounding = settingsForm.ThicknessRounding

    ' Create target directory if it doesn't exist
    If Not System.IO.Directory.Exists(config.TargetDirectory) Then
        Try
            System.IO.Directory.CreateDirectory(config.TargetDirectory)
        Catch ex As Exception
            MessageBox.Show("Could not create the target directory: " & ex.Message, "Error",
                MessageBoxButtons.OK, MessageBoxIcon.Error)
            Exit Sub
        End Try
    End If

    ' Define log file path
    Dim logFilePath As String = System.IO.Path.Combine(config.TargetDirectory, config.LogFileName)

    ' Initialize log file with header if logging is enabled
    If config.EnableLogging Then
        Try
            Dim logHeader As String = "DXF Export Log" & vbCrLf & _
                "Started: " & DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") & vbCrLf & _
                "Target Directory: " & config.TargetDirectory & vbCrLf & _
                "Export Options: " & config.DXFOptions & vbCrLf & _
                "Sort by Material: " & config.SortByMaterial & vbCrLf & _
                "Sort by Thickness: " & config.SortByThickness & vbCrLf & _
                "Clean Filenames: " & config.CleanFileNames & vbCrLf & vbCrLf
            System.IO.File.WriteAllText(logFilePath, logHeader)
        Catch ex As Exception
            MessageBox.Show("Could not create log file: " & ex.Message, "Warning",
                MessageBoxButtons.OK, MessageBoxIcon.Warning)
            config.EnableLogging = False
        End Try
    End If

    ' Process based on document type
    If doc.DocumentType = DocumentTypeEnum.kAssemblyDocumentObject Then
        Dim asmDoc As AssemblyDocument = CType(doc, AssemblyDocument)
        Dim sheetMetalParts As New Dictionary(Of String, Integer)
        ProcessComponentOccurrences(asmDoc.ComponentDefinition.Occurrences, sheetMetalParts, logFilePath, config)
        Dim progressBar As Inventor.ProgressBar = ThisApplication.CreateProgressBar(False, sheetMetalParts.Count, "Exporting DXF Files")
        Dim currentStep As Integer = 0
        ExportDXFFiles(sheetMetalParts, config.TargetDirectory, logFilePath, config, progressBar, currentStep)
        progressBar.Close()
    ElseIf doc.DocumentType = DocumentTypeEnum.kPartDocumentObject Then
        Dim partDoc As PartDocument = CType(doc, PartDocument)
        If partDoc.SubType = "{9C464203-9BAE-11D3-8BAD-0060B0CE6BB4}" Then
            Dim progressBar As Inventor.ProgressBar = ThisApplication.CreateProgressBar(False, 1, "Exporting DXF File")
            Dim currentStep As Integer = 0
            ExportSinglePartDXF(partDoc, config.TargetDirectory, logFilePath, config, progressBar, currentStep)
            progressBar.Close()
        Else
            If config.EnableLogging Then LogError(logFilePath, "The active document is not a sheet metal part.")
            MessageBox.Show("The active document is not a sheet metal part.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
        End If
    Else
        If config.EnableLogging Then LogError(logFilePath, "The active document is neither an assembly nor a sheet metal part.")
        MessageBox.Show("The active document is neither an assembly nor a sheet metal part.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
    End If

    MessageBox.Show("DXF export completed to folder: " & vbCrLf & config.TargetDirectory, "Export Complete", MessageBoxButtons.OK, MessageBoxIcon.Information)
End Sub

' Recursive processing of component occurrences
Sub ProcessComponentOccurrences(occurrences As ComponentOccurrences, sheetMetalParts As Dictionary(Of String, Integer), logFilePath As String, config As Configuration)
    For Each occ As ComponentOccurrence In occurrences
        Try
            If occ.DefinitionDocumentType = DocumentTypeEnum.kPartDocumentObject Then
                Dim partDoc As PartDocument = CType(occ.Definition.Document, PartDocument)
                If partDoc.SubType = "{9C464203-9BAE-11D3-8BAD-0060B0CE6BB4}" Then
                    Dim smCompDef As SheetMetalComponentDefinition = CType(partDoc.ComponentDefinition, SheetMetalComponentDefinition)
                    If smCompDef.HasFlatPattern Then
                        Dim partNumber As String = partDoc.PropertySets.Item("Design Tracking Properties").Item("Part Number").Value.ToString()
                        If sheetMetalParts.ContainsKey(partNumber) Then
                            sheetMetalParts(partNumber) += 1
                        Else
                            sheetMetalParts.Add(partNumber, 1)
                        End If
                    Else
                        If config.EnableLogging Then LogError(logFilePath, "Skipping DXF file for: " & partDoc.FullFileName & " - Flat Pattern Missing")
                    End If
                End If
            ElseIf occ.DefinitionDocumentType = DocumentTypeEnum.kAssemblyDocumentObject Then
                ' Recursively process sub-assemblies
                ProcessComponentOccurrences(occ.SubOccurrences, sheetMetalParts, logFilePath, config)
            End If
        Catch ex As Exception
            If config.EnableLogging Then LogError(logFilePath, "Error processing occurrence: " & occ.Name & " - " & ex.Message)
        End Try
    Next
End Sub

' Determine export directory based on sorting settings
Function GetExportDirectory(baseDir As String, material As String, thickness As String, config As Configuration) As String
    Dim exportDir As String = baseDir
    
    If config.SortByMaterial AndAlso Not String.IsNullOrEmpty(material) AndAlso material <> "Unknown" Then
        exportDir = Path.Combine(exportDir, material)
        
        ' If sorting by thickness is also enabled, create thickness subfolders within material folders
        If config.SortByThickness AndAlso Not String.IsNullOrEmpty(thickness) AndAlso thickness <> "Unknown" Then
            exportDir = Path.Combine(exportDir, thickness)
        End If
    ' If only sorting by thickness is enabled
    ElseIf config.SortByThickness AndAlso Not String.IsNullOrEmpty(thickness) AndAlso thickness <> "Unknown" Then
        exportDir = Path.Combine(exportDir, thickness)
    End If
    
    ' Create directory if it doesn't exist
    If Not Directory.Exists(exportDir) Then
        Try
            Directory.CreateDirectory(exportDir)
        Catch ex As Exception
            ' If directory creation fails, return the base directory
            Return baseDir
        End Try
    End If
    
    Return exportDir
End Function

' Export multiple parts to DXF
Sub ExportDXFFiles(sheetMetalParts As Dictionary(Of String, Integer), targetDir As String, logFilePath As String, config As Configuration, progressBar As Inventor.ProgressBar, ByRef currentStep As Integer)
    For Each partNumber As String In sheetMetalParts.Keys
        currentStep += 1
        Dim qty As Integer = sheetMetalParts(partNumber)
        Dim partDoc As PartDocument = Nothing
        Try
            partDoc = OpenPartDocument(partNumber)
            If partDoc Is Nothing Then
                Throw New Exception("Part file not found or couldn't be opened")
            End If

            Dim material As String = GetMaterial(partDoc, config.CleanFileNames)
            Dim thickness As String = GetThickness(partDoc, config.ThicknessRounding)
            
            ' Get the export directory with subfolder structure based on sorting settings
            Dim exportDir As String = GetExportDirectory(targetDir, material, thickness, config)
            
            Dim fileName As String = FormatFileName(config.FileNameFormat, partNumber, material, thickness, qty, partDoc)
            Dim filePath As String = Path.Combine(exportDir, fileName & ".dxf")

            If config.EnableLogging Then
                LogError(logFilePath, "Processing part: " & partNumber)
                LogError(logFilePath, "Export directory: " & exportDir)
                LogError(logFilePath, "File path: " & filePath)
            End If

            If Not IsValidPath(filePath) Then
                If config.EnableLogging Then LogError(logFilePath, "Invalid file path: " & filePath)
                Continue For
            End If

            If config.EnableLogging Then LogError(logFilePath, "Opened document: " & partDoc.FullFileName)
            Dim smCompDef As SheetMetalComponentDefinition = CType(partDoc.ComponentDefinition, SheetMetalComponentDefinition)

            If smCompDef.HasFlatPattern Then
                Dim flatPattern As FlatPattern = smCompDef.FlatPattern
                Dim oDataIO As DataIO = flatPattern.DataIO
                If config.EnableLogging Then LogError(logFilePath, "Prepared DataIO for: " & partDoc.FullFileName)

                Try
                    oDataIO.WriteDataToFile(config.DXFOptions, filePath)
                    If config.EnableLogging Then LogError(logFilePath, "Exported DXF for: " & partDoc.FullFileName & " to " & filePath)
                Catch ex As Exception
                    If config.EnableLogging Then LogError(logFilePath, "Error exporting DXF for: " & partDoc.FullFileName & " - " & ex.Message)
                End Try
            Else
                If config.EnableLogging Then LogError(logFilePath, "Skipping DXF file for: " & partDoc.FullFileName & " - Flat Pattern Missing")
            End If

        Catch ex As Exception
            If config.EnableLogging Then LogError(logFilePath, "Error processing part: " & partNumber & " - " & ex.Message)
        Finally
            If partDoc IsNot Nothing Then
                partDoc.Close(False)
                If config.EnableLogging Then LogError(logFilePath, "Closed document: " & partDoc.FullFileName)
            End If
            ' Update progress bar
            Dim progressPercent As Integer = CInt((currentStep / sheetMetalParts.Count) * 100)
            progressBar.Message = "Exporting: " & partNumber & " (" & progressPercent & "%)"
            progressBar.UpdateProgress()
        End Try
    Next
End Sub

' Export a single sheet metal part to DXF
Sub ExportSinglePartDXF(partDoc As PartDocument, targetDir As String, logFilePath As String, config As Configuration, progressBar As Inventor.ProgressBar, ByRef currentStep As Integer)
    currentStep += 1
    Dim partNumber As String = partDoc.PropertySets.Item("Design Tracking Properties").Item("Part Number").Value.ToString()
    Dim material As String = GetMaterial(partDoc, config.CleanFileNames)
    Dim thickness As String = GetThickness(partDoc, config.ThicknessRounding)
    
    ' Get the export directory with subfolder structure based on sorting settings
    Dim exportDir As String = GetExportDirectory(targetDir, material, thickness, config)
    
    Dim fileName As String = FormatFileName(config.FileNameFormat, partNumber, material, thickness, 1, partDoc)
    Dim filePath As String = Path.Combine(exportDir, fileName & ".dxf")

    If config.EnableLogging Then
        LogError(logFilePath, "Processing part: " & partNumber)
        LogError(logFilePath, "Export directory: " & exportDir)
        LogError(logFilePath, "File path: " & filePath)
    End If

    If Not IsValidPath(filePath) Then
        If config.EnableLogging Then LogError(logFilePath, "Invalid file path: " & filePath)
        Exit Sub
    End If

    Try
        Dim smCompDef As SheetMetalComponentDefinition = CType(partDoc.ComponentDefinition, SheetMetalComponentDefinition)

        If smCompDef.HasFlatPattern Then
            Dim flatPattern As FlatPattern = smCompDef.FlatPattern
            Dim oDataIO As DataIO = flatPattern.DataIO
            If config.EnableLogging Then LogError(logFilePath, "Prepared DataIO for: " & partDoc.FullFileName)

            Try
                oDataIO.WriteDataToFile(config.DXFOptions, filePath)
                If config.EnableLogging Then LogError(logFilePath, "Exported DXF for: " & partDoc.FullFileName & " to " & filePath)
            Catch ex As Exception
                If config.EnableLogging Then LogError(logFilePath, "Error exporting DXF for: " & partDoc.FullFileName & " - " & ex.Message)
            End Try
        Else
            If config.EnableLogging Then LogError(logFilePath, "Skipping DXF file for: " & partDoc.FullFileName & " - Flat Pattern Missing")
        End If
    Catch ex As Exception
        If config.EnableLogging Then LogError(logFilePath, "Error processing part: " & partNumber & " - " & ex.Message)
    Finally
        ' Update progress bar for single part
        progressBar.Message = "Exporting: " & partNumber & " (100%)"
        progressBar.UpdateProgress()
    End Try
End Sub

' Replace placeholders with actual values
Function FormatFileName(Format As String, partNumber As String, Material As String, thickness As String, qty As Integer, partDoc As PartDocument) As String
    Dim fileName As String = Format
    fileName = fileName.Replace("{partNumber}", partNumber)
    fileName = fileName.Replace("{material}", Material)
    fileName = fileName.Replace("{thickness}", thickness)
    fileName = fileName.Replace("{qty}", qty.ToString())
    
    ' Add support for custom properties with the format {prop:PropertyName}
    If fileName.Contains("{prop:") Then
        Dim propRegex As New System.Text.RegularExpressions.Regex("\{prop:(.*?)\}")
        Dim matches As System.Text.RegularExpressions.MatchCollection = propRegex.Matches(fileName)
        For Each match As System.Text.RegularExpressions.Match In matches
            Dim fullMatch As String = match.Groups(0).Value
            Dim propName As String = match.Groups(1).Value
            Dim propValue As String = GetCustomProperty(partDoc, propName)
            fileName = fileName.Replace(fullMatch, propValue)
        Next
    End If
    
    Return fileName
End Function

' Retrieve custom property value from document
Function GetCustomProperty(partDoc As PartDocument, propName As String) As String
    Try
        ' First check custom property sets
        For Each propSet As Inventor.PropertySet In partDoc.PropertySets
            For Each prop As Inventor.Property In propSet
                If prop.Name = propName Then
                    If prop.Value IsNot Nothing Then
                        Return prop.Value.ToString()
                    Else
                        Return ""
                    End If
                End If
            Next
        Next
        
        ' Then check standard iProperties
        For Each propSet As Inventor.PropertySet In partDoc.PropertySets
            If propSet.Count > 0 AndAlso propSet.Item(propName) IsNot Nothing Then
                Return propSet.Item(propName).Value.ToString()
            End If
        Next
        
        Return "N/A"
    Catch ex As Exception
        Return "Error"
    End Try
End Function

' Try to open part by part number, searching open docs then project files
Function OpenPartDocument(partNumber As String) As PartDocument
    Dim app As Inventor.Application = ThisApplication
    Dim docs As Documents = app.Documents
    Dim projectManager As DesignProjectManager = app.DesignProjectManager
    Dim activeProject As DesignProject = projectManager.ActiveDesignProject

    ' Try to find the part in already open documents
    For Each doc As Document In docs
        If TypeOf doc Is PartDocument AndAlso doc.PropertySets.Item("Design Tracking Properties").Item("Part Number").Value.ToString() = partNumber Then
            Return CType(doc, PartDocument)
        End If
    Next

    ' If not found in open documents, try to find and open from the project
    Dim fullFileName As String = activeProject.FindFiles(partNumber & ".ipt").Item(1)
    If Not String.IsNullOrEmpty(fullFileName) Then
        Return CType(docs.Open(fullFileName), PartDocument)
    End If

    Return Nothing
End Function

' Check if file path is valid
Function IsValidPath(path As String) As Boolean
    Dim isValid As Boolean = True
    Try
        Dim fileInfo As New System.IO.FileInfo(path)
    Catch ex As Exception
        isValid = False
    End Try
    Return isValid
End Function

' Append log entry with timestamp
Sub LogError(logFilePath As String, message As String)
    System.IO.File.AppendAllText(logFilePath, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") & " - " & message & vbCrLf)
End Sub

' Get material name and clean if needed
Function GetMaterial(partDoc As PartDocument, cleanNames As Boolean) As String
    Try
        Dim material As String = partDoc.PropertySets.Item("Design Tracking Properties").Item("Material").Value.ToString()
        
        ' Clean the material name if option is enabled
        If cleanNames Then
            ' Replace spaces and punctuation with underscores
            Dim cleanedMaterial As String = material
            
            ' Define punctuation characters to replace
            Dim punctuationChars() As Char = {" ", ",", ".", ";", ":", "'", """", "!", "?", "(", ")", "[", "]", "{", "}", "/", "\", "-", "+", "=", "*", "&", "^", "%", "$", "#", "@"}
            
            ' Replace each punctuation character with underscore
            For Each c As Char In punctuationChars
                cleanedMaterial = cleanedMaterial.Replace(c, "_"c)
            Next
            
            ' Replace multiple consecutive underscores with a single one
            While cleanedMaterial.Contains("__")
                cleanedMaterial = cleanedMaterial.Replace("__", "_")
            End While
            
            ' Remove leading or trailing underscores
            cleanedMaterial = cleanedMaterial.Trim("_"c)
            
            Return cleanedMaterial
        Else
            Return material
        End If
    Catch
        Return "Unknown"
    End Try
End Function

' Convert thickness from cm to mm and format
Function GetThickness(partDoc As PartDocument, rounding As Integer) As String
    Try
        Dim smCompDef As SheetMetalComponentDefinition = CType(partDoc.ComponentDefinition, SheetMetalComponentDefinition)
        Dim thickness As Double = smCompDef.Thickness.Value ' Thickness in centimeters
        Dim thicknessMM As Double = thickness * 10 ' Convert to millimeters
        Dim thicknessFormatted As String = Math.Round(thicknessMM, rounding).ToString() & "mm" ' Apply rounding
        Return thicknessFormatted
    Catch
        Return "Unknown"
    End Try
End Function

 

 

INV 2025.3
0 Likes
Message 16 of 20

GosponZ
Collaborator
Collaborator

thank you for fast respond. i did figure out how to add custom prop but still have problem to add Description of part. in my case i do not need material thickness since is in description of material. my order is material part number (description) and TTL . Description is for me very important but struggling for hours to fix. here is rule i fixed for my need.

GosponZ_0-1746642331101.png

AddReference "System.Drawing"
AddReference "System.Xml"
AddReference "System.Windows.Forms"
Imports System.Drawing
Imports System.Windows.Forms
Imports System.Xml.Serialization
Imports System.IO
Imports TextBox = System.Windows.Forms.TextBox
Imports Point = System.Drawing.Point
Imports Color = System.Drawing.Color
Imports Path = System.IO.Path
Imports File = System.IO.File
Imports Environment = System.Environment
 
' Configuration class for use during export
Public Class Configuration
    Public TargetDirectory As String
    Public UseComponentFolder As Boolean
    Public EnableLogging As Boolean
    Public LogFileName As String
    Public FileNameFormat As String
    Public DXFOptions As String
    Public SortByMaterial As Boolean
    Public SortByThickness As Boolean
    Public CleanFileNames As Boolean
End Class
 
' Serializable class to store user settings
<Serializable()> _
Public Class UserSettings
    Public Property TargetDirectory As String = "C:\DXF"
    Public Property UseComponentFolder As Boolean = False
    Public Property EnableLogging As Boolean = False
    Public Property FileNameFormat As String = "{material} {partNumber} TTL{TTL}" ' Added space and "TTL" prefix
    Public Property DXFOptions As String = "FLAT PATTERN DXF?AcadVersion=R12&RebaseGeometry=True&MergeProfilesIntoPolyline=True&SimplifySpline=True&InteriorProfilesLayer=IV_INTERIOR_PROFILES&InvisibleLayers=IV_TANGENT;IV_BEND;IV_BEND_DOWN;IV_TOOL_CENTER_DOWN;IV_ARC_CENTERS;IV_FEATURE_PROFILES;IV_FEATURE_PROFILES_DOWN;IV_UNCONSUMED_SKETCHES;IV_ROLL_TANGENT;IV_ROLL&SplineToleranceDouble=0.01"
    Public Property SortByMaterial As Boolean = False
    Public Property SortByThickness As Boolean = False
    Public Property CleanFileNames As Boolean = True
End Class
 
' Class for managing DXF export settings
Public Class SettingsManager
    Private Shared ReadOnly SettingsFileName As String = "DXFExportSettings.xml"
 
    Public Shared Function GetSettingsDirectory() As String
        Dim appDataPath As String = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)
        Dim settingsPath As String = Path.Combine(appDataPath, "Autodesk", "Inventor", "DXFExport")
        If Not Directory.Exists(settingsPath) Then
            Try
                Directory.CreateDirectory(settingsPath)
            Catch ex As Exception
                MessageBox.Show("Could not create settings directory: " & ex.Message & vbCrLf &
                    "Using default path: " & appDataPath, "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning)
                Return appDataPath
            End Try
        End If
        Return settingsPath
    End Function
 
    Public Shared Function GetSettingsFilePath() As String
        Return Path.Combine(GetSettingsDirectory(), SettingsFileName)
    End Function
 
    Public Shared Sub SaveSettings(settings As UserSettings)
        Try
            Dim filePath As String = GetSettingsFilePath()
            MessageBox.Show("Saving to: " & filePath) ' Debug message
            Dim serializer As New XmlSerializer(GetType(UserSettings))
            Using writer As New StreamWriter(filePath)
                serializer.Serialize(writer, settings)
            End Using
        Catch ex As Exception
            MessageBox.Show("Could not save settings: " & ex.Message,
                "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning)
        End Try
    End Sub
 
    Public Shared Function LoadSettings() As UserSettings
        Dim filePath As String = GetSettingsFilePath()
        If Not File.Exists(filePath) Then
            Return New UserSettings()
        End If
        Try
            Dim serializer As New XmlSerializer(GetType(UserSettings))
            Using reader As New StreamReader(filePath)
                Return DirectCast(serializer.Deserialize(reader), UserSettings)
            End Using
        Catch ex As Exception
            MessageBox.Show("Could not load settings: " & ex.Message & vbCrLf &
                "Using default settings instead.", "Warning",
                MessageBoxButtons.OK, MessageBoxIcon.Warning)
            Return New UserSettings()
        End Try
    End Function
End Class
 
' Settings Form Class
Public Class SettingsForm
    Inherits System.Windows.Forms.Form
 
    Private WithEvents btnOK As Button
    Private WithEvents btnCancel As Button
    Private WithEvents btnBrowse As Button
    Private WithEvents radStaticFolder As RadioButton
    Private WithEvents radComponentFolder As RadioButton
    Private txtTargetDir As TextBox
    Private chkLogging As CheckBox
    Private txtFileNameFormat As TextBox
    Private lblFileNameHelp As Label
    Private chkSortByMaterial As CheckBox
    Private chkSortByThickness As CheckBox
    Private chkCleanFileNames As CheckBox
 
    Public Property TargetDirectory As String
    Public Property UseComponentFolder As Boolean
    Public Property EnableLogging As Boolean
    Public Property FileNameFormat As String
    Public Property DXFOptions As String
    Public Property SortByMaterial As Boolean
    Public Property SortByThickness As Boolean
    Public Property CleanFileNames As Boolean
 
    Private Const FORM_PADDING As Integer = 12
    Private Const GROUP_PADDING As Integer = 20
    Private Const LABEL_HEIGHT As Integer = 24
    Private Const CONTROL_HEIGHT As Integer = 28
    Private Const BUTTON_WIDTH As Integer = 100
    Private Const BUTTON_HEIGHT As Integer = 36
    Private Const BROWSE_BUTTON_WIDTH As Integer = 50
 
    Public Sub New()
        Me.Text = "DXF Export Settings"
        Me.FormBorderStyle = FormBorderStyle.FixedDialog
        Me.MaximizeBox = False
        Me.MinimizeBox = False
        Me.StartPosition = FormStartPosition.CenterScreen
        Me.ClientSize = New Size(540, 400)
        Me.Font = New Font("Segoe UI", 9.5F)
        Me.Padding = New Padding(FORM_PADDING)
 
        Dim gbExportLocation As New GroupBox()
        gbExportLocation.Text = "Export Location"
        gbExportLocation.Location = New Point(FORM_PADDING, FORM_PADDING)
        gbExportLocation.Width = Me.ClientSize.Width - (FORM_PADDING * 2)
        gbExportLocation.Height = 110
        gbExportLocation.Padding = New Padding(GROUP_PADDING)
 
        radStaticFolder = New RadioButton()
        radStaticFolder.Text = "Static Folder:"
        radStaticFolder.Location = New Point(GROUP_PADDING, 24)
        radStaticFolder.AutoSize = True
        radStaticFolder.Checked = True
 
        txtTargetDir = New TextBox()
        txtTargetDir.Location = New Point(GROUP_PADDING + 120, 22)
        txtTargetDir.Size = New Size(gbExportLocation.Width - 220, CONTROL_HEIGHT)
        txtTargetDir.Anchor = AnchorStyles.Left Or AnchorStyles.Right Or AnchorStyles.Top
 
        btnBrowse = New Button()
        btnBrowse.Text = "..."
        btnBrowse.Location = New Point(gbExportLocation.Width - BROWSE_BUTTON_WIDTH - GROUP_PADDING, 21)
        btnBrowse.Size = New Size(BROWSE_BUTTON_WIDTH, CONTROL_HEIGHT)
        btnBrowse.Anchor = AnchorStyles.Right Or AnchorStyles.Top
 
        radComponentFolder = New RadioButton()
        radComponentFolder.Text = "Main Component Folder"
        radComponentFolder.Location = New Point(GROUP_PADDING, 60)
        radComponentFolder.AutoSize = True
 
        gbExportLocation.Controls.AddRange({radStaticFolder, radComponentFolder, txtTargetDir, btnBrowse})
 
        Dim gbFileOptions As New GroupBox()
        gbFileOptions.Text = "File Options"
        gbFileOptions.Location = New Point(FORM_PADDING, gbExportLocation.Bottom + FORM_PADDING)
        gbFileOptions.Width = Me.ClientSize.Width - (FORM_PADDING * 2)
        gbFileOptions.Height = 200
        gbFileOptions.Padding = New Padding(GROUP_PADDING)
 
        chkLogging = New CheckBox()
        chkLogging.Text = "Enable Logging"
        chkLogging.Location = New Point(GROUP_PADDING, 25)
        chkLogging.AutoSize = True
 
        Dim lblFileName As New Label()
        lblFileName.Text = "File Name Format:"
        lblFileName.Location = New Point(GROUP_PADDING, 60)
        lblFileName.AutoSize = True
 
        txtFileNameFormat = New TextBox()
        txtFileNameFormat.Location = New Point(GROUP_PADDING + 120, 58)
        txtFileNameFormat.Size = New Size(gbFileOptions.Width - 160, CONTROL_HEIGHT)
        txtFileNameFormat.Anchor = AnchorStyles.Left Or AnchorStyles.Right Or AnchorStyles.Top
 
        lblFileNameHelp = New Label()
        lblFileNameHelp.Text = "Available: {partNumber}, {material}, {TTL} (e.g., {material} {partNumber} TTL{TTL})"
        lblFileNameHelp.Location = New Point(GROUP_PADDING + 120, 88)
        lblFileNameHelp.AutoSize = True
        lblFileNameHelp.ForeColor = Color.DarkGray
 
        Dim gbSortOptions As New GroupBox()
        gbSortOptions.Text = "Folder Sorting"
        gbSortOptions.Location = New Point(GROUP_PADDING, 120)
        gbSortOptions.Width = gbFileOptions.Width - (GROUP_PADDING * 2)
        gbSortOptions.Height = 90
        gbSortOptions.Padding = New Padding(10)
 
        chkSortByMaterial = New CheckBox()
        chkSortByMaterial.Text = "Sort by Material"
        chkSortByMaterial.Location = New Point(15, 25)
        chkSortByMaterial.AutoSize = True
 
        chkSortByThickness = New CheckBox()
        chkSortByThickness.Text = "Sort by Thickness"
        chkSortByThickness.Location = New Point(15, 55)
        chkSortByThickness.AutoSize = True
 
        gbSortOptions.Controls.AddRange({chkSortByMaterial, chkSortByThickness})
 
        chkCleanFileNames = New CheckBox()
        chkCleanFileNames.Text = "Clean Filename (Replace Spaces and Punctuation)"
        chkCleanFileNames.Location = New Point(GROUP_PADDING, 155)
        chkCleanFileNames.AutoSize = True
        chkCleanFileNames.Checked = True
 
        gbFileOptions.Controls.AddRange({chkLogging, lblFileName, txtFileNameFormat, lblFileNameHelp, gbSortOptions, chkCleanFileNames})
 
        Dim buttonPanel As New Panel()
        buttonPanel.Width = Me.ClientSize.Width - (FORM_PADDING * 2)
        buttonPanel.Height = BUTTON_HEIGHT + 10
        buttonPanel.Location = New Point(FORM_PADDING, gbFileOptions.Bottom + FORM_PADDING)
 
        btnCancel = New Button()
        btnCancel.Text = "Cancel"
        btnCancel.Location = New Point(buttonPanel.Width - BUTTON_WIDTH - 5, 0)
        btnCancel.Size = New Size(BUTTON_WIDTH, BUTTON_HEIGHT)
        btnCancel.DialogResult = DialogResult.Cancel
        btnCancel.Anchor = AnchorStyles.Right
 
        btnOK = New Button()
        btnOK.Text = "OK"
        btnOK.Location = New Point(buttonPanel.Width - (BUTTON_WIDTH * 2) - 15, 0)
        btnOK.Size = New Size(BUTTON_WIDTH, BUTTON_HEIGHT)
        btnOK.DialogResult = DialogResult.OK
        btnOK.Anchor = AnchorStyles.Right
 
        buttonPanel.Controls.AddRange({btnOK, btnCancel})
 
        Me.Controls.AddRange({gbExportLocation, gbFileOptions, buttonPanel})
 
        Dim toolTip As New ToolTip()
        toolTip.SetToolTip(txtTargetDir, "Directory where DXF files will be saved")
        toolTip.SetToolTip(btnBrowse, "Browse for target directory")
        toolTip.SetToolTip(radStaticFolder, "Always export to the specified folder")
        toolTip.SetToolTip(radComponentFolder, "Export to the folder containing the main component")
        toolTip.SetToolTip(chkLogging, "Create a log file with detailed information about the export process")
        toolTip.SetToolTip(txtFileNameFormat, "Format template for exported DXF filenames")
        toolTip.SetToolTip(chkSortByMaterial, "Create subfolders for each material type")
        toolTip.SetToolTip(chkSortByThickness, "Create subfolders for each thickness value")
        toolTip.SetToolTip(chkCleanFileNames, "Replace spaces and punctuation with underscores in filenames and folder names")
 
        Me.AcceptButton = btnOK
        Me.CancelButton = btnCancel
 
        LoadSavedSettings()
 
        Me.ClientSize = New Size(Me.ClientSize.Width, buttonPanel.Bottom + FORM_PADDING)
    End Sub
 
    Private Sub LoadSavedSettings()
        Try
            Dim settings As UserSettings = SettingsManager.LoadSettings()
            txtTargetDir.Text = settings.TargetDirectory
            radComponentFolder.Checked = settings.UseComponentFolder
            radStaticFolder.Checked = Not settings.UseComponentFolder
            chkLogging.Checked = settings.EnableLogging
            txtFileNameFormat.Text = settings.FileNameFormat
            chkSortByMaterial.Checked = settings.SortByMaterial
            chkSortByThickness.Checked = settings.SortByThickness
            chkCleanFileNames.Checked = settings.CleanFileNames
            DXFOptions = settings.DXFOptions
            txtTargetDir.Enabled = Not settings.UseComponentFolder
            btnBrowse.Enabled = Not settings.UseComponentFolder
        Catch ex As Exception
            MessageBox.Show("Could not load saved settings. Using default settings.",
                "Settings Error", MessageBoxButtons.OK, MessageBoxIcon.Warning)
        End Try
    End Sub
 
    Private Sub radStaticFolder_CheckedChanged(sender As Object, e As EventArgs) Handles radStaticFolder.CheckedChanged
        If txtTargetDir IsNot Nothing AndAlso btnBrowse IsNot Nothing Then
            txtTargetDir.Enabled = radStaticFolder.Checked
            btnBrowse.Enabled = radStaticFolder.Checked
        End If
    End Sub
 
    Private Sub radComponentFolder_CheckedChanged(sender As Object, e As EventArgs) Handles radComponentFolder.CheckedChanged
        If txtTargetDir IsNot Nothing AndAlso btnBrowse IsNot Nothing Then
            txtTargetDir.Enabled = Not radComponentFolder.Checked
            btnBrowse.Enabled = Not radComponentFolder.Checked
        End If
    End Sub
 
    Private Sub btnBrowse_Click(sender As Object, e As EventArgs) Handles btnBrowse.Click
        Dim folderBrowser As New FolderBrowserDialog()
        folderBrowser.Description = "Select Target Directory for DXF Export"
        folderBrowser.ShowNewFolderButton = True
        If Not String.IsNullOrEmpty(txtTargetDir.Text) AndAlso System.IO.Directory.Exists(txtTargetDir.Text) Then
            folderBrowser.SelectedPath = txtTargetDir.Text
        End If
        If folderBrowser.ShowDialog() = DialogResult.OK Then
            txtTargetDir.Text = folderBrowser.SelectedPath
        End If
    End Sub
 
    Private Sub btnOK_Click(sender As Object, e As EventArgs) Handles btnOK.Click
        If radStaticFolder.Checked AndAlso String.IsNullOrEmpty(txtTargetDir.Text) Then
            MessageBox.Show("Target directory cannot be empty when using static folder option.",
                "Validation Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
            txtTargetDir.Focus()
            Return
        End If
        If String.IsNullOrEmpty(txtFileNameFormat.Text) Then
            MessageBox.Show("File name format cannot be empty.", "Validation Error",
                MessageBoxButtons.OK, MessageBoxIcon.Error)
            txtFileNameFormat.Focus()
            Return
        End If
        SaveFormSettingsToProperties()
        SaveCurrentSettingsToXml(True)
        Me.DialogResult = DialogResult.OK
        Me.Close()
    End Sub
 
    Private Sub SaveFormSettingsToProperties()
        UseComponentFolder = radComponentFolder.Checked
        TargetDirectory = txtTargetDir.Text
        EnableLogging = chkLogging.Checked
        FileNameFormat = txtFileNameFormat.Text
        SortByMaterial = chkSortByMaterial.Checked
        SortByThickness = chkSortByThickness.Checked
        CleanFileNames = chkCleanFileNames.Checked
    End Sub
 
    Private Sub SaveCurrentSettingsToXml(showErrors As Boolean)
        Try
            Dim settings As New UserSettings()
            settings.TargetDirectory = txtTargetDir.Text
            settings.UseComponentFolder = radComponentFolder.Checked
            settings.EnableLogging = chkLogging.Checked
            settings.FileNameFormat = txtFileNameFormat.Text
            settings.DXFOptions = DXFOptions
            settings.SortByMaterial = chkSortByMaterial.Checked
            settings.SortByThickness = chkSortByThickness.Checked
            settings.CleanFileNames = chkCleanFileNames.Checked
            SettingsManager.SaveSettings(settings)
        Catch ex As Exception
            If showErrors Then
                MessageBox.Show("Could not save settings: " & ex.Message,
                    "Settings Error", MessageBoxButtons.OK, MessageBoxIcon.Warning)
            End If
        End Try
    End Sub
 
    Private Sub btnCancel_Click(sender As Object, e As EventArgs) Handles btnCancel.Click
        SaveCurrentSettingsToXml(False)
        Me.DialogResult = DialogResult.Cancel
        Me.Close()
    End Sub
End Class
 
' Main method start
Sub Main()
    Dim settingsForm As New SettingsForm
    If settingsForm.ShowDialog() <> DialogResult.OK Then
        Exit Sub
    End If
 
    Dim doc As Inventor.Document = ThisApplication.ActiveDocument
    If doc Is Nothing Then
        MessageBox.Show("No document is open.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
        Exit Sub
    End If
 
    Dim config As New Configuration
    config.UseComponentFolder = settingsForm.UseComponentFolder
    config.SortByMaterial = settingsForm.SortByMaterial
    config.SortByThickness = settingsForm.SortByThickness
    config.CleanFileNames = settingsForm.CleanFileNames
 
    If config.UseComponentFolder Then
        Dim docPath As String = ""
        Try
            docPath = System.IO.Path.GetDirectoryName(doc.FullFileName)
            If String.IsNullOrEmpty(docPath) OrElse Not System.IO.Directory.Exists(docPath) Then
                Throw New Exception("Cannot access document folder")
            End If
            config.TargetDirectory = docPath
        Catch ex As Exception
            MessageBox.Show("Could not determine the component folder: " & ex.Message & vbCrLf &
                "Falling back to default folder.", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning)
            config.TargetDirectory = settingsForm.TargetDirectory
        End Try
    Else
        config.TargetDirectory = settingsForm.TargetDirectory
    End If
 
    config.EnableLogging = settingsForm.EnableLogging
    config.LogFileName = "ExportLog.txt"
    config.FileNameFormat = settingsForm.FileNameFormat
    config.DXFOptions = settingsForm.DXFOptions
 
    If Not System.IO.Directory.Exists(config.TargetDirectory) Then
        Try
            System.IO.Directory.CreateDirectory(config.TargetDirectory)
        Catch ex As Exception
            MessageBox.Show("Could not create the target directory: " & ex.Message, "Error",
                MessageBoxButtons.OK, MessageBoxIcon.Error)
            Exit Sub
        End Try
    End If
 
    Dim logFilePath As String = System.IO.Path.Combine(config.TargetDirectory, config.LogFileName)
 
    If config.EnableLogging Then
        Try
            Dim logHeader As String = "DXF Export Log" & vbCrLf & _
                "Started: " & DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") & vbCrLf & _
                "Target Directory: " & config.TargetDirectory & vbCrLf & _
                "Export Options: " & config.DXFOptions & vbCrLf & _
                "Sort by Material: " & config.SortByMaterial & vbCrLf & _
                "Sort by Thickness: " & config.SortByThickness & vbCrLf & _
                "Clean Filenames: " & config.CleanFileNames & vbCrLf & vbCrLf
            System.IO.File.WriteAllText(logFilePath, logHeader)
        Catch ex As Exception
            MessageBox.Show("Could not create log file: " & ex.Message, "Warning",
                MessageBoxButtons.OK, MessageBoxIcon.Warning)
            config.EnableLogging = False
        End Try
    End If
 
    If doc.DocumentType = DocumentTypeEnum.kAssemblyDocumentObject Then
        Dim asmDoc As AssemblyDocument = CType(doc, AssemblyDocument)
        Dim sheetMetalParts As New Dictionary(Of String, Integer)
        ProcessComponentOccurrences(asmDoc.ComponentDefinition.Occurrences, sheetMetalParts, logFilePath, config)
        Dim progressBar As Inventor.ProgressBar = ThisApplication.CreateProgressBar(False, sheetMetalParts.Count, "Exporting DXF Files")
        Dim currentStep As Integer = 0
        ExportDXFFiles(sheetMetalParts, config.TargetDirectory, logFilePath, config, progressBar, currentStep)
        progressBar.Close()
    ElseIf doc.DocumentType = DocumentTypeEnum.kPartDocumentObject Then
        Dim partDoc As PartDocument = CType(doc, PartDocument)
        If partDoc.SubType = "{9C464203-9BAE-11D3-8BAD-0060B0CE6BB4}" Then
            Dim progressBar As Inventor.ProgressBar = ThisApplication.CreateProgressBar(False, 1, "Exporting DXF File")
            Dim currentStep As Integer = 0
            ExportSinglePartDXF(partDoc, config.TargetDirectory, logFilePath, config, progressBar, currentStep)
            progressBar.Close()
        Else
            If config.EnableLogging Then LogError(logFilePath, "The active document is not a sheet metal part.")
            MessageBox.Show("The active document is not a sheet metal part.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
        End If
    Else
        If config.EnableLogging Then LogError(logFilePath, "The active document is neither an assembly nor a sheet metal part.")
        MessageBox.Show("The active document is neither an assembly nor a sheet metal part.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
    End If
 
    MessageBox.Show("DXF export completed to folder: " & vbCrLf & config.TargetDirectory, "Export Complete", MessageBoxButtons.OK, MessageBoxIcon.Information)
End Sub
 
Sub ProcessComponentOccurrences(occurrences As ComponentOccurrences, sheetMetalParts As Dictionary(Of String, Integer), logFilePath As String, config As Configuration)
    For Each occ As ComponentOccurrence In occurrences
        Try
            If occ.DefinitionDocumentType = DocumentTypeEnum.kPartDocumentObject Then
                Dim partDoc As PartDocument = CType(occ.Definition.Document, PartDocument)
                If partDoc.SubType = "{9C464203-9BAE-11D3-8BAD-0060B0CE6BB4}" Then
                    Dim smCompDef As SheetMetalComponentDefinition = CType(partDoc.ComponentDefinition, SheetMetalComponentDefinition)
                    If smCompDef.HasFlatPattern Then
                        Dim partNumber As String = partDoc.PropertySets.Item("Design Tracking Properties").Item("Part Number").Value.ToString()
                        Dim ttl As Integer = 1
                        Try
                            Dim customProps As PropertySet = partDoc.PropertySets.Item("Inventor User Defined Properties")
                            Dim ttlProp As Inventor.Property = customProps.Item("TTL")
                            ttl = CInt(ttlProp.Value)
                        Catch
                            If config.EnableLogging Then LogError(logFilePath, "TTL property not found for part: " & partNumber & ", using default TTL=1")
                        End Try
                        If sheetMetalParts.ContainsKey(partNumber) Then
                            sheetMetalParts(partNumber) += ttl
                        Else
                            sheetMetalParts.Add(partNumber, ttl)
                        End If
                    Else
                        If config.EnableLogging Then LogError(logFilePath, "Skipping DXF file for: " & partDoc.FullFileName & " - Flat Pattern Missing")
                    End If
                End If
            ElseIf occ.DefinitionDocumentType = DocumentTypeEnum.kAssemblyDocumentObject Then
                ProcessComponentOccurrences(occ.SubOccurrences, sheetMetalParts, logFilePath, config)
            End If
        Catch ex As Exception
            If config.EnableLogging Then LogError(logFilePath, "Error processing occurrence: " & occ.Name & " - " & ex.Message)
        End Try
    Next
End Sub
 
Function GetExportDirectory(baseDir As String, material As String, thickness As String, config As Configuration) As String
    Dim exportDir As String = baseDir
    If config.SortByMaterial AndAlso Not String.IsNullOrEmpty(material) AndAlso material <> "Unknown" Then
        exportDir = Path.Combine(exportDir, material)
        If config.SortByThickness AndAlso Not String.IsNullOrEmpty(thickness) AndAlso thickness <> "Unknown" Then
            exportDir = Path.Combine(exportDir, thickness)
        End If
    ElseIf config.SortByThickness AndAlso Not String.IsNullOrEmpty(thickness) AndAlso thickness <> "Unknown" Then
        exportDir = Path.Combine(exportDir, thickness)
    End If
    If Not Directory.Exists(exportDir) Then
        Try
            Directory.CreateDirectory(exportDir)
        Catch ex As Exception
            Return baseDir
        End Try
    End If
    Return exportDir
End Function
 
Sub ExportDXFFiles(sheetMetalParts As Dictionary(Of String, Integer), targetDir As String, logFilePath As String, config As Configuration, progressBar As Inventor.ProgressBar, ByRef currentStep As Integer)
    For Each partNumber As String In sheetMetalParts.Keys
        currentStep += 1
        Dim ttl As Integer = sheetMetalParts(partNumber)
        Dim partDoc As PartDocument = Nothing
        Try
            partDoc = OpenPartDocument(partNumber)
            If partDoc Is Nothing Then
                Throw New Exception("Part file not found or couldn't be opened")
            End If
 
            Dim material As String = GetMaterial(partDoc, config.CleanFileNames)
            Dim thickness As String = GetThickness(partDoc, 2)
 
            Dim exportDir As String = GetExportDirectory(targetDir, material, thickness, config)
            Dim fileName As String = FormatFileName(config.FileNameFormat, partNumber, material, ttl)
            Dim filePath As String = Path.Combine(exportDir, fileName & ".dxf")
 
            If config.EnableLogging Then
                LogError(logFilePath, "Processing part: " & partNumber)
                LogError(logFilePath, "Export directory: " & exportDir)
                LogError(logFilePath, "File path: " & filePath)
            End If
 
            If Not IsValidPath(filePath) Then
                If config.EnableLogging Then LogError(logFilePath, "Invalid file path: " & filePath)
                Continue For
            End If
 
            If config.EnableLogging Then LogError(logFilePath, "Opened document: " & partDoc.FullFileName)
            Dim smCompDef As SheetMetalComponentDefinition = CType(partDoc.ComponentDefinition, SheetMetalComponentDefinition)
 
            If smCompDef.HasFlatPattern Then
                Dim flatPattern As FlatPattern = smCompDef.FlatPattern
                Dim oDataIO As DataIO = flatPattern.DataIO
                If config.EnableLogging Then LogError(logFilePath, "Prepared DataIO for: " & partDoc.FullFileName)
 
                Try
                    oDataIO.WriteDataToFile(config.DXFOptions, filePath)
                    If config.EnableLogging Then LogError(logFilePath, "Exported DXF for: " & partDoc.FullFileName & " to " & filePath)
                Catch ex As Exception
                    If config.EnableLogging Then LogError(logFilePath, "Error exporting DXF for: " & partDoc.FullFileName & " - " & ex.Message)
                End Try
            Else
                If config.EnableLogging Then LogError(logFilePath, "Skipping DXF file for: " & partDoc.FullFileName & " - Flat Pattern Missing")
            End If
 
        Catch ex As Exception
            If config.EnableLogging Then LogError(logFilePath, "Error processing part: " & partNumber & " - " & ex.Message)
        Finally
            If partDoc IsNot Nothing Then
                partDoc.Close(False)
                If config.EnableLogging Then LogError(logFilePath, "Closed document: " & partDoc.FullFileName)
            End If
            Dim progressPercent As Integer = CInt((currentStep / sheetMetalParts.Count) * 100)
            progressBar.Message = "Exporting: " & partNumber & " (" & progressPercent & "%)"
            progressBar.UpdateProgress()
        End Try
    Next
End Sub
 
Sub ExportSinglePartDXF(partDoc As PartDocument, targetDir As String, logFilePath As String, config As Configuration, progressBar As Inventor.ProgressBar, ByRef currentStep As Integer)
    currentStep += 1
    Dim partNumber As String = partDoc.PropertySets.Item("Design Tracking Properties").Item("Part Number").Value.ToString()
    Dim material As String = GetMaterial(partDoc, config.CleanFileNames)
    Dim thickness As String = GetThickness(partDoc, 2)
    
    Dim ttl As Integer = 1
    Try
        Dim customProps As PropertySet = partDoc.PropertySets.Item("Inventor User Defined Properties")
        Dim ttlProp As Inventor.Property = customProps.Item("TTL")
        ttl = CInt(ttlProp.Value)
    Catch
        If config.EnableLogging Then LogError(logFilePath, "TTL property not found for part: " & partNumber & ", using default TTL=1")
    End Try
 
    Dim exportDir As String = GetExportDirectory(targetDir, material, thickness, config)
    Dim fileName As String = FormatFileName(config.FileNameFormat, partNumber, material, ttl)
    Dim filePath As String = Path.Combine(exportDir, fileName & ".dxf")
 
    If config.EnableLogging Then
        LogError(logFilePath, "Processing part: " & partNumber)
        LogError(logFilePath, "Export directory: " & exportDir)
        LogError(logFilePath, "File path: " & filePath)
    End If
 
    If Not IsValidPath(filePath) Then
        If config.EnableLogging Then LogError(logFilePath, "Invalid file path: " & filePath)
        Exit Sub
    End If
 
    Try
        Dim smCompDef As SheetMetalComponentDefinition = CType(partDoc.ComponentDefinition, SheetMetalComponentDefinition)
 
        If smCompDef.HasFlatPattern Then
            Dim flatPattern As FlatPattern = smCompDef.FlatPattern
            Dim oDataIO As DataIO = flatPattern.DataIO
            If config.EnableLogging Then LogError(logFilePath, "Prepared DataIO for: " & partDoc.FullFileName)
 
            Try
                oDataIO.WriteDataToFile(config.DXFOptions, filePath)
                If config.EnableLogging Then LogError(logFilePath, "Exported DXF for: " & partDoc.FullFileName & " to " & filePath)
            Catch ex As Exception
                If config.EnableLogging Then LogError(logFilePath, "Error exporting DXF for: " & partDoc.FullFileName & " - " & ex.Message)
            End Try
        Else
            If config.EnableLogging Then LogError(logFilePath, "Skipping DXF file for: " & partDoc.FullFileName & " - Flat Pattern Missing")
        End If
    Catch ex As Exception
        If config.EnableLogging Then LogError(logFilePath, "Error processing part: " & partNumber & " - " & ex.Message)
    Finally
        progressBar.Message = "Exporting: " & partNumber & " (100%)"
        progressBar.UpdateProgress()
    End Try
End Sub
 
Function FormatFileName(format As String, partNumber As String, material As String, ttl As Integer) As String
    Dim fileName As String = format
    fileName = fileName.Replace("{partNumber}", partNumber)
    fileName = fileName.Replace("{material}", material)
fileName = fileName.Replace("{description}", description)
    fileName = fileName.Replace("{TTL}", ttl.ToString())
    Return fileName
End Function
 
Function OpenPartDocument(partNumber As String) As PartDocument
    Dim app As Inventor.Application = ThisApplication
    Dim docs As Documents = app.Documents
    Dim projectManager As DesignProjectManager = app.DesignProjectManager
    Dim activeProject As DesignProject = projectManager.ActiveDesignProject
 
    For Each doc As Document In docs
        If TypeOf doc Is PartDocument AndAlso doc.PropertySets.Item("Design Tracking Properties").Item("Part Number").Value.ToString() = partNumber Then
            Return CType(doc, PartDocument)
        End If
    Next
 
    Dim fullFileName As String = activeProject.FindFiles(partNumber & ".ipt").Item(1)
    If Not String.IsNullOrEmpty(fullFileName) Then
        Return CType(docs.Open(fullFileName), PartDocument)
    End If
 
    Return Nothing
End Function
 
Function IsValidPath(path As String) As Boolean
    Dim isValid As Boolean = True
    Try
        Dim fileInfo As New System.IO.FileInfo(path)
    Catch ex As Exception
        isValid = False
    End Try
    Return isValid
End Function
 
Sub LogError(logFilePath As String, message As String)
    System.IO.File.AppendAllText(logFilePath, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") & " - " & message & vbCrLf)
End Sub
 
Function GetMaterial(partDoc As PartDocument, cleanNames As Boolean) As String
    Try
        Dim material As String = partDoc.PropertySets.Item("Design Tracking Properties").Item("Material").Value.ToString()
        If cleanNames Then
            Dim cleanedMaterial As String = material
            Dim punctuationChars() As Char = {" ", ",", ".", ";", ":", "'", """", "!", "?", "(", ")", "[", "]", "{", "}", "/", "\", "-", "+", "=", "*", "&", "^", "%", "$", "#", "@"}
            For Each c As Char In punctuationChars
                cleanedMaterial = cleanedMaterial.Replace(c, "_"c)
            Next
            While cleanedMaterial.Contains("__")
                cleanedMaterial = cleanedMaterial.Replace("__", "_")
            End While
            cleanedMaterial = cleanedMaterial.Trim("_"c)
            Return cleanedMaterial
        Else
            Return material
        End If
    Catch
        Return "Unknown"
    End Try
End Function
 
Function GetThickness(partDoc As PartDocument, rounding As Integer) As String
    Try
        Dim smCompDef As SheetMetalComponentDefinition = CType(partDoc.ComponentDefinition, SheetMetalComponentDefinition)
        Dim thickness As Double = smCompDef.Thickness.Value
        Dim thicknessMM As Double = thickness * 10
        Dim thicknessFormatted As String = Math.Round(thicknessMM, rounding).ToString() & "mm"
        Return thicknessFormatted
    Catch
        Return "Unknown"
    End Try
End Function

 

0 Likes
Message 17 of 20

Ivan_Sinicyn
Advocate
Advocate

I'm not quite sure why you were trying to modify the code. Just run the application code I added today and in the input field set the file name parameters you want.

{material} {partNumber} {prop:Description} _TTL_{prop:TTL}


If I still misunderstood you, please attach the part file with the necessary properties and the .dxf file whose name you want to get so I can understand the problem.

INV 2025.3
0 Likes
Message 18 of 20

GosponZ
Collaborator
Collaborator

didn't work

0 Likes
Message 19 of 20

GosponZ
Collaborator
Collaborator

and it is working perfect just need description so far no successes 

0 Likes
Message 20 of 20

GosponZ
Collaborator
Collaborator

ok Ivan, in your code it makes description but there is problem with custom That is what i got when run code above. Do not think is big issue since in code i paste below TTl is working. If you can help i would greatly appreciated.

GosponZ_0-1746648268261.png

 

0 Likes