Files
OnebotCatalog/sample-data/tools/worker_journal.vb

442 lines
22 KiB
VB.net

' =============================================================================
' NX Journal: 按需生成服务 worker (二期 A 版, NX 2506 实测适配版, 2026-08-27)
'
' ▍干什么: 常驻轮询 GenServer 任务队列, 认领任务后在**同一 NX 会话内**逐项生成:
' 开母模副本 → 设表达式 → 更新 → Save 副本 → PRT SaveAs →
' CloseAll 释放句柄 → 外部独立翻译器导出 STEP/IGES → 关闭不保存。
' 结果写 jobs\job_<id>.done (行式键值格式, 无 JSON — NX journal 编译环境没有 JSON 库)。
'
' ▍关键改造 (2026-08-27): 批处理内 DexManager 导出判 0 solid (ST-DEVELOPER 翻译器
' 在 run_journal 无 GUI 下读不到几何), 改为 Save 副本后 spawn 独立命令行翻译器
' step214ug.exe (.prt→.step) / iges.exe (.prt→.igs), 实测成功。
'
' ▍任务文件格式 (GenServer 写入, 本脚本解析; 母模/映射/开关/导出配置全部烘进任务, 不读 masters.json):
' ITEM
' SERIES=KC
' CODE=KC0032-87
' PARAM type=00 (每参数一行, 值可为空)
' PARAM bore=32
' MASTER=KC_master.prt
' MAP bore=bore (目录参数代码=NX表达式名)
' SW magnet,=magOn,0 (参数代码,参数值=NX表达式名,表达式值)
' EXP step=1 (导出格式开关; step 恒 1)
' END
'
' ▍运行 (run_journal 只接受一个参数; 用 WorkingDirectory 定位配置):
' WorkingDirectory = GenServer 运行目录 (含 worker-config.txt + worker-no.txt)
' run_journal.exe <本文件完整路径>
'
' ▍注意
' - 中文注释需 UTF-8 带 BOM (改完手动恢复 BOM)
' - 母模副本现在必须 Save (独立翻译器从磁盘读); 但副本用完即删, 绝不保留脏副本
' - 导出格式 (masters.json exports): step 恒导出, iges/prt 可选, parasolid/stl 已按用户要求砍掉
' - NX 2506 关键 API (反射探针核对): OpenBaseDisplay(批处理必须, OpenBase 后 Work=NULL) /
' ExpressionCollection.CreateExpression("Number","名=值") / UF EditExp(仅编辑已存在) / CloseAll 两参数
' =============================================================================
Option Strict Off
Imports System
Imports System.IO
Imports System.Text
Imports System.Diagnostics
Imports System.Collections.Generic
Imports NXOpen
Imports NXOpen.UF
Module WorkerGen
Dim theSession As Session = Session.GetSession()
Dim lw As ListingWindow = theSession.ListingWindow
Dim jobsDir, outputDir, mastersDir, workDir, nxBin As String
Dim workerNo As String = "0"
Dim pollMs As Integer = 1500
Sub Main()
lw.Open()
' 配置 = 当前工作目录 worker-config.txt (key=value 行式)
Dim cfgPath As String = Path.Combine(Directory.GetCurrentDirectory(), "worker-config.txt")
If Not File.Exists(cfgPath) Then
lw.WriteLine("[worker] 未找到配置: " & cfgPath & " (请以 GenServer 运行目录为工作目录启动)")
Return
End If
For Each line As String In File.ReadAllLines(cfgPath, Encoding.UTF8)
Dim i As Integer = line.IndexOf("="c)
If i <= 0 Then Continue For
Dim k As String = line.Substring(0, i).Trim()
Dim v As String = line.Substring(i + 1).Trim()
If k = "jobsDir" Then jobsDir = v
If k = "outputDir" Then outputDir = v
If k = "mastersDir" Then mastersDir = v
If k = "workDir" Then workDir = v
If k = "nxBin" Then nxBin = v
Next
If jobsDir.Length = 0 OrElse outputDir.Length = 0 Then
lw.WriteLine("[worker] 配置缺少 jobsDir/outputDir")
Return
End If
If nxBin.Length = 0 Then
' 回退: 自动探测 NX 安装目录 (部署到新机器无需手填)
For Each root As String In New String() {
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Siemens"),
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Siemens"),
"C:\Program Files\Siemens"}
If Not Directory.Exists(root) Then Continue For
For Each ver As String In Directory.GetDirectories(root, "NX*")
Dim b As String = Path.Combine(ver, "NXBIN")
If File.Exists(Path.Combine(b, "run_journal.exe")) Then nxBin = b : Exit For
Next
If nxBin.Length > 0 Then Exit For
Next
End If
If nxBin.Length = 0 Then
lw.WriteLine("[worker] 警告: 未找到 NX NXBIN 目录 (nxBin 未配置且自动探测失败), STEP/IGES 导出将失败")
Else
lw.WriteLine("[worker] nxBin=" & nxBin)
End If
Dim noPath As String = Path.Combine(Directory.GetCurrentDirectory(), "worker-no.txt")
If File.Exists(noPath) Then
Dim noTxt As String = File.ReadAllText(noPath, Encoding.UTF8).Trim()
If noTxt.Length > 0 Then workerNo = noTxt
End If
For Each d As String In {jobsDir, outputDir, workDir}
If d.Length > 0 AndAlso Not Directory.Exists(d) Then Directory.CreateDirectory(d)
Next
lw.WriteLine("[worker" & workerNo & "] 启动: jobs=" & jobsDir & " 输出=" & outputDir)
While True
Try
TouchHeartbeat()
ProcessNextJob()
Catch ex As Exception
lw.WriteLine("[worker" & workerNo & "] 循环异常: " & ex.Message)
End Try
Threading.Thread.Sleep(pollMs)
End While
End Sub
' ---- 认领一个任务并处理 (原子改名防双 worker 抢同一任务) ----
Sub ProcessNextJob()
Dim jobFile As String = Nothing
Dim files() As String = Directory.GetFiles(jobsDir, "job_*.json")
If files.Length = 0 Then Return
Array.Sort(files)
For Each f As String In files
Dim running As String = f.Substring(0, f.Length - 5) & ".running"
Try
File.Move(f, running)
jobFile = running
Exit For
Catch
' 被其他 worker 抢走, 试下一个
End Try
Next
If jobFile Is Nothing Then Return
Dim jobId As String = Path.GetFileName(jobFile).Replace("job_", "").Replace(".running", "")
Dim workJobDir As String = Path.Combine(workDir, "job_" & jobId)
Directory.CreateDirectory(workJobDir)
lw.WriteLine("[worker" & workerNo & "] 认领任务 " & jobId & " " & DateTime.Now.ToString("HH:mm:ss"))
Dim okOverall As Boolean = True
Dim errOverall As String = ""
Dim doneSb As New StringBuilder()
Dim items As New List(Of Dictionary(Of String, Object))()
Dim masterCopies As New Dictionary(Of String, String)() ' 同系列共用一份母模副本 (副本从不保存)
Try
items = ParseJob(File.ReadAllText(jobFile, Encoding.UTF8))
doneSb.AppendLine("OK=1")
doneSb.AppendLine("ERROR=")
For idx As Integer = 0 To items.Count - 1
Dim it As Dictionary(Of String, Object) = items(idx)
Dim series As String = GetStr(it, "series")
Dim code As String = GetStr(it, "code")
Dim pars As Dictionary(Of String, Object) = GetDict(it, "params")
Dim outDir As String = Path.Combine(outputDir, series, code)
Directory.CreateDirectory(outDir)
Dim itemOk As Boolean = True
Dim itemErr As String = ""
Dim itemHit As Boolean = False
Dim filesList As New List(Of String)()
Try
' 检索先行 (认领时再查一次, 防并发窗口重复生成)
If OutputHit(series, code, it) Then
itemHit = True
filesList = ListOutputFiles(code, it)
lw.WriteLine(" [" & code & "] 已存在, 命中复用")
Else
' 拷母模副本 (同系列共用; 绝不保存)
If Not masterCopies.ContainsKey(series) Then
Dim src As String = Path.Combine(mastersDir, series, GetStr(it, "master"))
If Not File.Exists(src) Then Throw New Exception("母模缺失: " & src)
Dim dst As String = Path.Combine(workJobDir, series & "_master.prt")
File.Copy(src, dst, True)
' 清只读属性 (源母模可能带只读, NX 只读打开会拒绝保存/导出)
File.SetAttributes(dst, File.GetAttributes(dst) And Not FileAttributes.ReadOnly)
masterCopies(series) = dst
End If
GenerateOne(series, code, pars, it, masterCopies(series), outDir)
filesList = ListOutputFiles(code, it)
lw.WriteLine(" [" & code & "] 生成完成 → " & outDir)
End If
Catch ex As Exception
okOverall = False
itemOk = False
itemErr = ex.Message
lw.WriteLine(" [" & code & "] 失败: " & ex.Message)
End Try
doneSb.AppendLine("ITEM")
doneSb.AppendLine("SERIES=" & series)
doneSb.AppendLine("CODE=" & code)
doneSb.AppendLine("OK=" & (If(itemOk, "1", "0")))
doneSb.AppendLine("HIT=" & (If(itemHit, "1", "0")))
doneSb.AppendLine("ERROR=" & itemErr)
For Each f As String In filesList
doneSb.AppendLine("FILE=" & f)
Next
doneSb.AppendLine("END")
' 逐项进度 (批量打包 UI 显示 done/total)
File.WriteAllText(Path.Combine(jobsDir, "job_" & jobId & ".prog"),
"DONE=" & (idx + 1) & vbCrLf & "TOTAL=" & items.Count & vbCrLf, Encoding.UTF8)
' 关闭所有部件 (不保存), 会话内循环 (NX 2506: CloseAll 两参数)
Try
theSession.Parts.CloseAll(NXOpen.BasePart.CloseModified.CloseModified, Nothing)
Catch
End Try
Next
Catch ex As Exception
okOverall = False
errOverall = ex.Message
lw.WriteLine("[worker" & workerNo & "] 任务异常: " & ex.Message)
End Try
' 写结果 (原子: 先写临时再改名; 错误信息去换行防格式破坏)
Dim doneText As String = doneSb.ToString()
If Not okOverall Then
Dim errClean As String = errOverall.Replace(vbCr, " ").Replace(vbLf, " ")
If doneText.IndexOf("ITEM", StringComparison.OrdinalIgnoreCase) < 0 Then
doneText = "OK=0" & vbCrLf & "ERROR=" & errClean & vbCrLf
Else
doneText = doneText.Replace("OK=1" & vbCrLf & "ERROR=", "OK=0" & vbCrLf & "ERROR=" & errClean)
End If
End If
Dim donePath As String = Path.Combine(jobsDir, "job_" & jobId & ".done")
Dim tmpPath As String = donePath & ".tmp"
File.WriteAllText(tmpPath, doneText, New UTF8Encoding(True))
If File.Exists(donePath) Then File.Delete(donePath)
File.Move(tmpPath, donePath)
Try
File.Delete(jobFile) ' 删 .running
Directory.Delete(workJobDir, True) ' 任务私有工作副本即清 (输出留存不删)
Catch
End Try
lw.WriteLine("[worker" & workerNo & "] 任务 " & jobId & " 结束 " & (If(okOverall, "OK", "FAIL")))
End Sub
' ---- 任务文件解析 (行式键值; 返回每项一个字典: series/code/params/master/map/sw/exp) ----
Function ParseJob(text As String) As List(Of Dictionary(Of String, Object))
Dim result As New List(Of Dictionary(Of String, Object))()
Dim cur As Dictionary(Of String, Object) = Nothing
For Each raw As String In text.Split(New String() {vbCrLf, vbLf, vbCr}, StringSplitOptions.None)
Dim l As String = raw.Trim()
If l.Length = 0 Then Continue For
If l = "ITEM" Then
cur = New Dictionary(Of String, Object)()
cur("params") = New Dictionary(Of String, Object)()
cur("map") = New Dictionary(Of String, Object)()
cur("sw") = New List(Of Object)()
cur("exp") = New Dictionary(Of String, Object)()
result.Add(cur)
Continue For
End If
If l = "END" Then cur = Nothing : Continue For
If cur Is Nothing Then Continue For
If l.StartsWith("SERIES=") Then cur("series") = l.Substring(7).Trim() : Continue For
If l.StartsWith("CODE=") Then cur("code") = l.Substring(5).Trim() : Continue For
If l.StartsWith("MASTER=") Then cur("master") = l.Substring(7).Trim() : Continue For
If l.StartsWith("PARAM ") Then
Dim rest As String = l.Substring(6)
Dim j As Integer = rest.IndexOf("="c)
If j > 0 Then CType(cur("params"), Dictionary(Of String, Object))(rest.Substring(0, j).Trim()) = rest.Substring(j + 1).Trim()
Continue For
End If
If l.StartsWith("MAP ") Then
Dim rest As String = l.Substring(4)
Dim j As Integer = rest.IndexOf("="c)
If j > 0 Then CType(cur("map"), Dictionary(Of String, Object))(rest.Substring(0, j).Trim()) = rest.Substring(j + 1).Trim()
Continue For
End If
If l.StartsWith("SW ") Then
' SW 参数代码,参数值=表达式名,表达式值
Dim rest As String = l.Substring(3)
Dim j As Integer = rest.IndexOf("="c)
If j > 0 Then
Dim left() As String = rest.Substring(0, j).Split(","c)
Dim right() As String = rest.Substring(j + 1).Split(","c)
If left.Length >= 2 AndAlso right.Length >= 2 Then
Dim sw As New Dictionary(Of String, Object)()
sw("pc") = left(0).Trim() : sw("pv") = left(1).Trim()
sw("ex") = right(0).Trim() : sw("ev") = right(1).Trim()
CType(cur("sw"), List(Of Object)).Add(sw)
End If
End If
Continue For
End If
If l.StartsWith("EXP ") Then
Dim rest As String = l.Substring(4)
Dim j As Integer = rest.IndexOf("="c)
If j > 0 AndAlso rest.Substring(j + 1).Trim() = "1" Then
CType(cur("exp"), Dictionary(Of String, Object))(rest.Substring(0, j).Trim()) = "1"
End If
Continue For
End If
Next
Return result
End Function
Function GetStr(d As Dictionary(Of String, Object), key As String) As String
If d Is Nothing OrElse Not d.ContainsKey(key) OrElse d(key) Is Nothing Then Return ""
Return Convert.ToString(d(key))
End Function
Function GetDict(d As Dictionary(Of String, Object), key As String) As Dictionary(Of String, Object)
If d Is Nothing OrElse Not d.ContainsKey(key) OrElse d(key) Is Nothing Then Return New Dictionary(Of String, Object)()
Return CType(d(key), Dictionary(Of String, Object))
End Function
' 导出格式开关: job 里 EXP 行 (EXP parasolid=1); step 恒导出
Function ExpOn(it As Dictionary(Of String, Object), fmt As String) As Boolean
If fmt = "step" Then Return True
Dim exp As Dictionary(Of String, Object) = GetDict(it, "exp")
Return exp.ContainsKey(fmt)
End Function
' ---- 检索先行: 输出目录存在且全部配置格式文件齐全 ----
Function OutputHit(series As String, code As String, it As Dictionary(Of String, Object)) As Boolean
Dim dir As String = Path.Combine(outputDir, series, code)
If Not Directory.Exists(dir) Then Return False
For Each f As String In New String() {code & ".stp", code & ".x_t", code & ".igs", code & ".stl", code & ".prt"}
If f.EndsWith(".stp") AndAlso Not File.Exists(Path.Combine(dir, f)) Then Return False
If f.EndsWith(".x_t") AndAlso ExpOn(it, "parasolid") AndAlso Not File.Exists(Path.Combine(dir, f)) Then Return False
If f.EndsWith(".igs") AndAlso ExpOn(it, "iges") AndAlso Not File.Exists(Path.Combine(dir, f)) Then Return False
If f.EndsWith(".stl") AndAlso ExpOn(it, "stl") AndAlso Not File.Exists(Path.Combine(dir, f)) Then Return False
If f.EndsWith(".prt") AndAlso ExpOn(it, "prt") AndAlso Not File.Exists(Path.Combine(dir, f)) Then Return False
Next
Return True
End Function
Function ListOutputFiles(code As String, it As Dictionary(Of String, Object)) As List(Of String)
Dim list As New List(Of String)()
list.Add(code & ".stp")
If ExpOn(it, "parasolid") Then list.Add(code & ".x_t")
If ExpOn(it, "iges") Then list.Add(code & ".igs")
If ExpOn(it, "stl") Then list.Add(code & ".stl")
If ExpOn(it, "prt") Then list.Add(code & ".prt")
Return list
End Function
' ---- 单型号生成: 开副本 → 设表达式 → 更新 → 多格式导出 → 关闭 ----
Sub GenerateOne(series As String, code As String, pars As Dictionary(Of String, Object),
it As Dictionary(Of String, Object), masterCopy As String, outDir As String)
' 1) 打开母模副本 (OpenBaseDisplay 使其成为工作部件; 副本会 Save 供外部翻译器读盘, 用完即删)
Dim partLoadStatus1 As NXOpen.PartLoadStatus = Nothing
Dim basePart1 As NXOpen.BasePart = theSession.Parts.OpenBaseDisplay(masterCopy, partLoadStatus1)
If partLoadStatus1 IsNot Nothing Then partLoadStatus1.Dispose()
Dim workPart As NXOpen.Part = theSession.Parts.Work
If workPart Is Nothing Then Throw New Exception("打开母模失败: " & masterCopy)
' 2) 设表达式 (UF EditExp 只编辑已存在的表达式; 母模必须已建好这些表达式)
Dim ufsW As UFSession = UFSession.GetUFSession()
Dim exprMap As Dictionary(Of String, Object) = GetDict(it, "map")
For Each kv As KeyValuePair(Of String, Object) In exprMap
If pars.ContainsKey(kv.Key) Then
ufsW.Modl.EditExp(Convert.ToString(kv.Value) & "=" & ValueStr(pars(kv.Key)))
End If
Next
Dim swList As Object = Nothing
If it.ContainsKey("sw") Then swList = it("sw")
If swList IsNot Nothing Then
For Each swObj As Object In CType(swList, List(Of Object))
Dim sw As Dictionary(Of String, Object) = CType(swObj, Dictionary(Of String, Object))
If pars.ContainsKey(GetStr(sw, "pc")) AndAlso ValueStr(pars(GetStr(sw, "pc"))) = GetStr(sw, "pv") Then
ufsW.Modl.EditExp(GetStr(sw, "ex") & "=" & GetStr(sw, "ev"))
End If
Next
End If
' 3) Update model
ufsW.Modl.Update()
' 4) Save the copy to disk so independent translators can read it.
' (in-session DexManager export yields 0 solids in batch - verified; see dev log 4.5)
Dim pssSave As NXOpen.PartSaveStatus = workPart.Save(NXOpen.BasePart.SaveComponents.True, NXOpen.BasePart.CloseAfterSave.False)
If pssSave IsNot Nothing Then pssSave.Dispose()
' 5) PRT save-as (NX native parametric part; session SaveAs)
If ExpOn(it, "prt") Then ExportPrt(Path.Combine(outDir, code & ".prt"))
' 6) Close all parts to release the file handle so external translators can read
Try
theSession.Parts.CloseAll(NXOpen.BasePart.CloseModified.CloseModified, Nothing)
Catch
End Try
' 7) Export via independent CLI translators (bypass batch 0-solid dead end)
ExportStepCli(masterCopy, Path.Combine(outDir, code & ".stp"))
If ExpOn(it, "iges") Then ExportIgesCli(masterCopy, Path.Combine(outDir, code & ".igs"))
End Sub
' ---- Independent CLI translators (bypass batch 0-solid dead end) ----
' step214ug.exe: bidirectional. input .prt -> export STEP. (verified 2 solids, 7KB)
' Syntax: step214ug.exe <input> o=<output> d=<def> l=<log>
Sub ExportStepCli(inputPrt As String, fileName As String)
If File.Exists(fileName) Then File.Delete(fileName)
Dim psi As New ProcessStartInfo()
psi.FileName = Path.Combine(nxBin, "step214ug.exe")
psi.Arguments = """" & inputPrt & """ o=""" & fileName & """ l=""" & fileName & ".log"""
psi.WorkingDirectory = Path.GetDirectoryName(inputPrt)
psi.UseShellExecute = False
psi.CreateNoWindow = True
Dim p As Process = Process.Start(psi)
p.WaitForExit()
If Not File.Exists(fileName) OrElse (New FileInfo(fileName)).Length < 1000 Then
Throw New Exception("STEP export failed (empty/missing): " & fileName)
End If
End Sub
' iges.exe: input .prt -> export IGES. (verified Face=6)
' Syntax: iges.exe <input> o=<output> l=<log>
Sub ExportIgesCli(inputPrt As String, fileName As String)
If File.Exists(fileName) Then File.Delete(fileName)
Dim psi As New ProcessStartInfo()
psi.FileName = Path.Combine(nxBin, "iges.exe")
psi.Arguments = """" & inputPrt & """ o=""" & fileName & """ l=""" & fileName & ".log"""
psi.WorkingDirectory = Path.GetDirectoryName(inputPrt)
psi.UseShellExecute = False
psi.CreateNoWindow = True
Dim p As Process = Process.Start(psi)
p.WaitForExit()
If Not File.Exists(fileName) Then
Throw New Exception("IGES export failed (missing): " & fileName)
End If
End Sub
' NX native part export: save-as current work part (parametric exprs + feature tree; 2026-08-26 user request)
Sub ExportPrt(fileName As String)
Dim pss As NXOpen.PartSaveStatus = theSession.Parts.Work.SaveAs(fileName)
If pss IsNot Nothing Then pss.Dispose()
End Sub
' ---- 工具 ----
Function ValueStr(v As Object) As String
If v Is Nothing Then Return ""
Return Convert.ToString(v, System.Globalization.CultureInfo.InvariantCulture).Replace(",", ".")
End Function
Sub TouchHeartbeat()
Dim hb As String = Path.Combine(workDir, "heartbeat_" & workerNo & ".txt")
File.WriteAllText(hb, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), New UTF8Encoding(True))
End Sub
End Module