' =============================================================================
' NX Journal: 参数化母模 + 参数表 → 批量导出 STEP (欧霓博目录软件专用)
'
' ▍这个脚本干什么
'   逐行读取 CSV 参数表 (每行 = 一个型号: 型号编码 + bore/stroke/magnet/mount... + step_file),
'   对每个型号: 修改母模表达式 → 更新模型 → 导出 STEP214 文件。
'   ★ 已按 step_file 去重: 多个型号行共用同一文件时只导出一次 (52,154 行型号
'     只会导出唯一几何文件, 例如 KC 系列 2,772 个)。断点续跑: 输出目录已存在的文件会跳过。
'
' ▍使用前置 (母模要求, 详见《NX母模建模操作说明书.md》)
'   1) 母模中所有"会变"尺寸必须用表达式驱动, 表达式名与 CSV 列名一字不差
'   2) 磁石槽/安装形式/双轴等"有无"特征 → 按表达式抑制 (Suppression by Expression)
'   3) 建模验收: 手动改 bore/stroke/magOn/mountIdx 各验证过重建无误
'
' ▍运行方法
'   NX 打开母模 → 菜单 工具 → 日记 → 播放 → 选本文件
'   (注意: Ctrl+U 只认 .dll, 本 .vb 必须走"日记→播放")
'
' ▍CONFIG 区修改清单 (建模工程师每次只改这里)
'   [1] csvPath / outDir 两个路径
'   [2] exprMap   — 尺寸映射: NX 表达式名 → CSV 列名 (新增独立尺寸表达式就加一行)
'   [3] switchRules — 枚举开关: {CSV列名, 列值, NX表达式名, 表达式值}
'       例: 磁石列 "M" → magOn=1; 固定形式 "FA" → mountIdx=1 (取值按母模约定)
'   [4] exportStl / stlOutDir — 同步导出 STL 网格 (桌面目录软件窗口内 3D 预览用, 开源 Helix 渲染)
'       STL 文件名与 STEP 同名 (.stl), 放在 catalog\mesh\ 目录; 不需要窗口内预览就 exportStl = False
'
' ▍注意
'   - DexBuilder 导出接口 NX 10 ~ NX 2506 兼容; 若你的 NX 版本 API 有差异,
'     用【工具→日记→录制】录一次 文件→导出→STEP214, 替换 ExportStep 函数体。
'   - 中文注释需 UTF-8 带 BOM (改完跑 fix-bom.ps1), 否则 NX 读乱。
'   - 导出失败的行不会中断整体, 最后汇总统计写进 NX 信息窗口 (ListingWindow)。
' =============================================================================
Option Strict Off
Imports System
Imports System.IO
Imports NXOpen
Imports NXOpen.UF

Module BatchStepExport

    Dim theSession As Session = Session.GetSession()
    Dim workPart As Part = theSession.Parts.Work
    Dim lw As ListingWindow = theSession.ListingWindow

    Sub Main()

        ' ╔════════════════════════════ CONFIG 区 (只改这里) ═══════════════════════════════╗
        ' ║ [1] 两个路径                                                                     ║
        ' ╚═════════════════════════════════════════════════════════════════════════════════╝
        Dim csvPath As String = "D:\catalog\params.csv"   ' 参数表路径 → 改为 catalog\csv\<系列>.csv
        Dim outDir As String = "D:\catalog\step_out\"     ' STEP 输出目录 → 改为 catalog\step\
                                                          ' (目录不存在会自动创建)

        ' ╔═════════════════════════════════════════════════════════════════════════════════╗
        ' ║ [4] STL 网格同步导出 (窗口内 3D 预览用; 不开就设 False)                             ║
        ' ╚═════════════════════════════════════════════════════════════════════════════════╝
        Dim exportStl As Boolean = True                    ' True = STEP 导出时同步导出同名 STL
        Dim stlOutDir As String = "D:\catalog\mesh\"       ' STL 输出目录 → 改为 catalog\mesh\

        ' ╔═════════════════════════════════════════════════════════════════════════════════╗
        ' ║ [2] 尺寸映射 exprMap: "NX母模表达式名" → "CSV列名"                               ║
        ' ║     母模里每个独立尺寸表达式加一行; 联动尺寸(公式)不用写                          ║
        ' ╚═════════════════════════════════════════════════════════════════════════════════╝
        Dim exprMap As New System.Collections.Generic.Dictionary(Of String, String)()
        exprMap.Add("bore", "bore")        ' 缸径: 母模表达式 "bore" ← CSV 列 "bore"
        exprMap.Add("stroke", "stroke")    ' 行程: 母模表达式 "stroke" ← CSV 列 "stroke"
        ' 示例(按需取消注释):
        ' exprMap.Add("magPos", "magpos")  ' 磁石位置是独立规格时: CSV 要有 magpos 列

        ' ╔═════════════════════════════════════════════════════════════════════════════════╗
        ' ║ [3] 枚举开关映射 switchRules: 每行 = {CSV列名, 列值, NX表达式名, 表达式值}        ║
        ' ║     作用: 该型号在这一列的值 = 列值 时, 把 NX 表达式设为 表达式值                ║
        ' ║     (驱动母模里"按表达式抑制"的开关, 空串列值写作 "")                            ║
        ' ╚═════════════════════════════════════════════════════════════════════════════════╝
        Dim switchRules As New System.Collections.Generic.List(Of String())()
        switchRules.Add({"magnet", "", "magOn", "0"})
        switchRules.Add({"magnet", "M", "magOn", "1"})
        switchRules.Add({"mount", "", "mountIdx", "0"})
        switchRules.Add({"mount", "FA", "mountIdx", "1"})
        switchRules.Add({"mount", "FB", "mountIdx", "2"})
        switchRules.Add({"mount", "CA", "mountIdx", "3"})
        switchRules.Add({"mount", "CB", "mountIdx", "4"})
        switchRules.Add({"mount", "LB", "mountIdx", "5"})
        switchRules.Add({"mount", "YB", "mountIdx", "6"})
        ' ================================================================

        lw.Open()
        lw.WriteLine("批量导出开始: " & csvPath)
        Directory.CreateDirectory(outDir)
        If exportStl Then
            Directory.CreateDirectory(stlOutDir)
            lw.WriteLine("同步导出 STL 网格: " & stlOutDir)
        End If

        Dim lines() As String = File.ReadAllLines(csvPath)
        If lines.Length < 2 Then
            lw.WriteLine("参数表为空!")
            Return
        End If

        Dim header() As String = lines(0).Split(","c)
        Dim colStep As Integer = Array.IndexOf(header, "step_file")
        If colStep < 0 Then
            lw.WriteLine("参数表缺少 step_file 列!")
            Return
        End If
        ' 预解析列号
        Dim colOf As New System.Collections.Generic.Dictionary(Of String, Integer)()
        For Each kv As KeyValuePair(Of String, String) In exprMap
            colOf(kv.Key) = Array.IndexOf(header, kv.Value)
            If colOf(kv.Key) < 0 Then
                lw.WriteLine("参数表缺少列: " & kv.Value)
                Return
            End If
        Next

        Dim okCount As Integer = 0
        Dim failCount As Integer = 0
        Dim skipCount As Integer = 0
        ' 去重: 多个型号行共用同一 step_file, 每个文件只导出一次 (本批内)
        Dim exported As New System.Collections.Generic.HashSet(Of String)()

        For i As Integer = 1 To lines.Length - 1
            If lines(i).Trim().Length = 0 Then Continue For
            Dim c() As String = lines(i).Split(","c)
            Dim model As String = c(0).Trim()
            Dim stepFile As String = c(colStep).Trim()
            If exported.Contains(stepFile) Then
                skipCount += 1
                Continue For
            End If
            ' 断点续跑: 输出目录已存在同名文件 → 跳过 (想强制重导, 先删该文件再跑)
            If File.Exists(Path.Combine(outDir, stepFile)) Then
                exported.Add(stepFile)
                skipCount += 1
                Continue For
            End If
            lw.Write("[" & i & "/" & (lines.Length - 1) & "] " & stepFile & " ... ")

            Try
                ' ---- 1) 设置表达式 (尺寸) ----
                For Each kv As KeyValuePair(Of String, String) In exprMap
                    workPart.Expressions.Edit(kv.Key, c(colOf(kv.Key)).Trim())
                Next

                ' ---- 1.5) 设置枚举开关 (抑制/特征组切换) ----
                For Each rule As String() In switchRules
                    Dim colIdx As Integer = Array.IndexOf(header, rule(0))
                    If colIdx >= 0 AndAlso c(colIdx).Trim() = rule(1) Then
                        workPart.Expressions.Edit(rule(2), rule(3))
                    End If
                Next

                ' ---- 2) 更新模型 ----
                Dim update As NXOpen.Update = theSession.CreateUpdate()
                Dim nErrs As Integer = 0
                update.InterpartDelay = False
                update.ModelingInterpartUpdate = True
                update.DoUpdate(nErrs)
                update.Dispose()
                If nErrs > 0 Then
                    lw.WriteLine("警告: 更新有 " & nErrs & " 个错误")
                End If

                ' ---- 3) 导出 STEP ----
                ExportStep(Path.Combine(outDir, stepFile))

                ' ---- 3.5) 同步导出 STL 网格 (窗口内 3D 预览; 同名 .stl) ----
                If exportStl Then
                    Dim stlFile As String = Path.ChangeExtension(stepFile, ".stl")
                    If Not File.Exists(Path.Combine(stlOutDir, stlFile)) Then
                        ExportStl(Path.Combine(stlOutDir, stlFile))
                    End If
                End If

                exported.Add(stepFile)
                okCount += 1
                lw.WriteLine("OK")

            Catch ex As Exception
                failCount += 1
                lw.WriteLine("FAIL: " & ex.Message)
            End Try
        Next

        lw.WriteLine("")
        lw.WriteLine("完成: " & okCount & " 个文件导出成功, " & failCount & " 失败, " & skipCount & " 行共用文件已跳过 (共扫描 " & (lines.Length - 1) & " 行型号), 输出目录: " & outDir)
    End Sub

    ' STEP214 导出 (NX 10+ DexBuilder; 旧版本请用录制日记替换此函数)
    Sub ExportStep(fileName As String)
        Dim dexBuilder As NXOpen.DexBuilder = workPart.DexManager.CreateBuilder()
        dexBuilder.PartType = NXOpen.DexBuilder.PartTypeOption.Part
        dexBuilder.OutputFile = fileName
        dexBuilder.Translator = NXOpen.DexBuilder.TranslatorType.Step214
        dexBuilder.ColorAndLayers = True
        dexBuilder.Commit()
        dexBuilder.Destroy()
    End Sub

    ' STL 网格导出 (窗口内 3D 预览用, 二进制格式体积小; 若 NX 版本 API 有差异,
    ' 用【工具→日记→录制】录一次 文件→导出→STL 替换本函数体)
    Sub ExportStl(fileName As String)
        Dim stlBuilder As NXOpen.StlBuilder = workPart.StlManager.CreateStlBuilder()
        stlBuilder.OutputFile = fileName
        stlBuilder.BinaryFormat = True          ' 二进制 STL
        stlBuilder.TriangleTolerance = 0.5     ' 三角网格公差 (mm), 越小越精细文件越大 (0.5 屏幕预览足够)
        stlBuilder.AdjacentTolerance = 0.1
        stlBuilder.AutoNormalGen = True
        stlBuilder.NormalDirection = 1          ' 1 = 朝外
        stlBuilder.Commit()
        stlBuilder.Destroy()
    End Sub

End Module
