首次提交: OnebotCatalog 项目代码与文档(含 NX 按需生成服务二期、后台、一键启动)

This commit is contained in:
wangruiguo
2026-09-03 17:55:45 +08:00
commit fafa86d3a6
241 changed files with 78656 additions and 0 deletions

51
sample-data/README.md Normal file
View File

@@ -0,0 +1,51 @@
# 欧霓博目录软件 —— 示例数据包(自编,非真实产品数据)
> 用途供目录软件OnebotCatalog开发与测试。SC 系列为**虚构系列**,几何为简化模型。
## 目录结构
```
sample-data/
├── onb-sc/ # 示例系列主体
│ ├── step/ # 72 个 STEP 变体文件AP203 B-rep4 圆柱组合)
│ ├── params.csv # 参数表Excel 可直接打开model_code/bore/stroke/magnet/mount/step_file
│ ├── coding-rules.md # 型号编码规则 + 选型约束 R1~R3 + 几何约定
│ └── docs/
│ ├── dim-drawing/ # 2D 尺寸图 PNG中英双语标注×2
│ └── datasheet/ # 数据表 PDF中文/英文×2 + 源 JPEG
└── tools/ # 生成脚本(全流程可复现)
├── gen-step.ps1 # 生成 STEP 变体 + params.csv
├── verify-step.ps1 # STEP 结构自检(引用/括号/ASCII/实体数)
├── gen-images.ps1 # 生成尺寸图 PNG + 数据表 JPEG
├── gen-pdf.ps1 # JPEG → 极简 PDF含 xref 自检)
├── fix-bom.ps1 # .ps1 加 UTF-8 BOMPS 5.1 中文编码必需)
└── parse-check.ps1 # PowerShell 语法错误定位
```
## 快速复现
```powershell
# 1. 生成 72 个 STEP + params.csv
powershell -NoProfile -ExecutionPolicy Bypass -File tools\gen-step.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File tools\verify-step.ps1
# 2. 生成尺寸图与数据表
powershell -NoProfile -ExecutionPolicy Bypass -File tools\gen-images.ps1
```
## 数据规模
| 项 | 值 |
|---|---|
| 系列 | SC 标准气缸1 个系列) |
| 缸径 | 16 / 25 / 32 / 40 mm |
| 行程 | 25 / 50 / 75 / 100 mm缸径 16 仅 25/50 |
| 磁石 / 安装 | 无,S × FA,LB,CB受规则约束 |
| 合法变体 | 72 个 |
| STEP 格式 | ISO 10303-21 AP203 (CONFIG_CONTROL_DESIGN),毫米单位 |
## 注意事项
- STEP 为手写极简 B-rep每文件 126 实体),已通过结构自检;**几何有效性需在真实 CAD 中抽查**SolidWorks/NX 打开 `step\SC32x50S-LB.step` 确认 4 个圆柱实体与尺寸)
- `params.csv` 为 UTF-8 带 BOMExcel 直接打开不乱码
- 修改型号/规则后重跑 `gen-step.ps1` 即可整体重新生成

View File

@@ -0,0 +1,48 @@
# 欧霓博 SC 系列标准气缸 —— 型号编码规则(示例数据)
> 本文件为**自编示例数据**(虚构系列),用于目录软件开发测试,非真实产品数据。
## 1. 型号编码格式
```
SC{Bore}x{Stroke}{Magnet}-{Mount}
```
| 段 | 含义 | 取值 | 示例 |
|---|---|---|---|
| `SC` | 系列代码(标准气缸) | 固定 | SC |
| `{Bore}` | 缸径 (mm) | 16 / 25 / 32 / 40 | 32 |
| `x` | 分隔符 | 固定 | x |
| `{Stroke}` | 行程 (mm) | 25 / 50 / 75 / 100 | 50 |
| `{Magnet}` | 磁石 | 空 = 无磁石;`S` = 带磁石 | S |
| `-` | 分隔符 | 固定 | - |
| `{Mount}` | 安装方式 | `FA` = 前法兰 / `LB` = 脚座 / `CB` = 中摆 | LB |
**完整示例:`SC32x50S-LB`** = SC 系列,缸径 Ø32行程 50mm带磁石脚座安装。
## 2. 选型约束规则
| # | 规则 | 说明 |
|---|---|---|
| R1 | 缸径 16 → 行程仅 25 / 50 | 小缸径不支持长行程 |
| R2 | 磁石 `S` 要求缸径 ≥ 25 | 缸径 16 无磁石选项 |
| R3 | 中摆 `CB` 要求行程 ≤ 75 | 长行程不提供中摆安装 |
对应 [../params.csv](../params.csv) 中 72 个合法型号组合。
## 3. STEP 文件命名
STEP 文件名 = 型号 + `.step`,如 `SC32x50S-LB.step`,与 `params.csv``step_file` 列一一对应。
## 4. 简化几何约定(示例用)
模型为 4 个圆柱体组合(后盖 + 缸体 + 前盖 + 活塞杆),单位 mm沿 Z 轴:
| 零件 | 外径 | 长度/位置 |
|---|---|---|
| 缸体 | bore + 4 | 长 = stroke + 60z: 0 ~ stroke+60 |
| 后盖 | bore + 10 | 厚 8z: -8 ~ 0 |
| 前盖 | bore + 10 | 厚 10z: stroke+60 ~ stroke+70 |
| 活塞杆 | max(bore/5, 3) × 2 | 自 z = stroke+46 伸出至前盖上方 stroke+10 |
> 真实产品数据接入时,用 Builder 导入真实 STEP 模型替换即可,编码规则与参数表格式不变。

View File

@@ -0,0 +1,73 @@
model_code,bore,stroke,magnet,mount,step_file
SC16x25-FA,16,25,,FA,SC16x25-FA.step
SC16x25-LB,16,25,,LB,SC16x25-LB.step
SC16x25-CB,16,25,,CB,SC16x25-CB.step
SC16x50-FA,16,50,,FA,SC16x50-FA.step
SC16x50-LB,16,50,,LB,SC16x50-LB.step
SC16x50-CB,16,50,,CB,SC16x50-CB.step
SC25x25-FA,25,25,,FA,SC25x25-FA.step
SC25x25-LB,25,25,,LB,SC25x25-LB.step
SC25x25-CB,25,25,,CB,SC25x25-CB.step
SC25x25S-FA,25,25,S,FA,SC25x25S-FA.step
SC25x25S-LB,25,25,S,LB,SC25x25S-LB.step
SC25x25S-CB,25,25,S,CB,SC25x25S-CB.step
SC25x50-FA,25,50,,FA,SC25x50-FA.step
SC25x50-LB,25,50,,LB,SC25x50-LB.step
SC25x50-CB,25,50,,CB,SC25x50-CB.step
SC25x50S-FA,25,50,S,FA,SC25x50S-FA.step
SC25x50S-LB,25,50,S,LB,SC25x50S-LB.step
SC25x50S-CB,25,50,S,CB,SC25x50S-CB.step
SC25x75-FA,25,75,,FA,SC25x75-FA.step
SC25x75-LB,25,75,,LB,SC25x75-LB.step
SC25x75-CB,25,75,,CB,SC25x75-CB.step
SC25x75S-FA,25,75,S,FA,SC25x75S-FA.step
SC25x75S-LB,25,75,S,LB,SC25x75S-LB.step
SC25x75S-CB,25,75,S,CB,SC25x75S-CB.step
SC25x100-FA,25,100,,FA,SC25x100-FA.step
SC25x100-LB,25,100,,LB,SC25x100-LB.step
SC25x100S-FA,25,100,S,FA,SC25x100S-FA.step
SC25x100S-LB,25,100,S,LB,SC25x100S-LB.step
SC32x25-FA,32,25,,FA,SC32x25-FA.step
SC32x25-LB,32,25,,LB,SC32x25-LB.step
SC32x25-CB,32,25,,CB,SC32x25-CB.step
SC32x25S-FA,32,25,S,FA,SC32x25S-FA.step
SC32x25S-LB,32,25,S,LB,SC32x25S-LB.step
SC32x25S-CB,32,25,S,CB,SC32x25S-CB.step
SC32x50-FA,32,50,,FA,SC32x50-FA.step
SC32x50-LB,32,50,,LB,SC32x50-LB.step
SC32x50-CB,32,50,,CB,SC32x50-CB.step
SC32x50S-FA,32,50,S,FA,SC32x50S-FA.step
SC32x50S-LB,32,50,S,LB,SC32x50S-LB.step
SC32x50S-CB,32,50,S,CB,SC32x50S-CB.step
SC32x75-FA,32,75,,FA,SC32x75-FA.step
SC32x75-LB,32,75,,LB,SC32x75-LB.step
SC32x75-CB,32,75,,CB,SC32x75-CB.step
SC32x75S-FA,32,75,S,FA,SC32x75S-FA.step
SC32x75S-LB,32,75,S,LB,SC32x75S-LB.step
SC32x75S-CB,32,75,S,CB,SC32x75S-CB.step
SC32x100-FA,32,100,,FA,SC32x100-FA.step
SC32x100-LB,32,100,,LB,SC32x100-LB.step
SC32x100S-FA,32,100,S,FA,SC32x100S-FA.step
SC32x100S-LB,32,100,S,LB,SC32x100S-LB.step
SC40x25-FA,40,25,,FA,SC40x25-FA.step
SC40x25-LB,40,25,,LB,SC40x25-LB.step
SC40x25-CB,40,25,,CB,SC40x25-CB.step
SC40x25S-FA,40,25,S,FA,SC40x25S-FA.step
SC40x25S-LB,40,25,S,LB,SC40x25S-LB.step
SC40x25S-CB,40,25,S,CB,SC40x25S-CB.step
SC40x50-FA,40,50,,FA,SC40x50-FA.step
SC40x50-LB,40,50,,LB,SC40x50-LB.step
SC40x50-CB,40,50,,CB,SC40x50-CB.step
SC40x50S-FA,40,50,S,FA,SC40x50S-FA.step
SC40x50S-LB,40,50,S,LB,SC40x50S-LB.step
SC40x50S-CB,40,50,S,CB,SC40x50S-CB.step
SC40x75-FA,40,75,,FA,SC40x75-FA.step
SC40x75-LB,40,75,,LB,SC40x75-LB.step
SC40x75-CB,40,75,,CB,SC40x75-CB.step
SC40x75S-FA,40,75,S,FA,SC40x75S-FA.step
SC40x75S-LB,40,75,S,LB,SC40x75S-LB.step
SC40x75S-CB,40,75,S,CB,SC40x75S-CB.step
SC40x100-FA,40,100,,FA,SC40x100-FA.step
SC40x100-LB,40,100,,LB,SC40x100-LB.step
SC40x100S-FA,40,100,S,FA,SC40x100S-FA.step
SC40x100S-LB,40,100,S,LB,SC40x100S-LB.step
1 model_code bore stroke magnet mount step_file
2 SC16x25-FA 16 25 FA SC16x25-FA.step
3 SC16x25-LB 16 25 LB SC16x25-LB.step
4 SC16x25-CB 16 25 CB SC16x25-CB.step
5 SC16x50-FA 16 50 FA SC16x50-FA.step
6 SC16x50-LB 16 50 LB SC16x50-LB.step
7 SC16x50-CB 16 50 CB SC16x50-CB.step
8 SC25x25-FA 25 25 FA SC25x25-FA.step
9 SC25x25-LB 25 25 LB SC25x25-LB.step
10 SC25x25-CB 25 25 CB SC25x25-CB.step
11 SC25x25S-FA 25 25 S FA SC25x25S-FA.step
12 SC25x25S-LB 25 25 S LB SC25x25S-LB.step
13 SC25x25S-CB 25 25 S CB SC25x25S-CB.step
14 SC25x50-FA 25 50 FA SC25x50-FA.step
15 SC25x50-LB 25 50 LB SC25x50-LB.step
16 SC25x50-CB 25 50 CB SC25x50-CB.step
17 SC25x50S-FA 25 50 S FA SC25x50S-FA.step
18 SC25x50S-LB 25 50 S LB SC25x50S-LB.step
19 SC25x50S-CB 25 50 S CB SC25x50S-CB.step
20 SC25x75-FA 25 75 FA SC25x75-FA.step
21 SC25x75-LB 25 75 LB SC25x75-LB.step
22 SC25x75-CB 25 75 CB SC25x75-CB.step
23 SC25x75S-FA 25 75 S FA SC25x75S-FA.step
24 SC25x75S-LB 25 75 S LB SC25x75S-LB.step
25 SC25x75S-CB 25 75 S CB SC25x75S-CB.step
26 SC25x100-FA 25 100 FA SC25x100-FA.step
27 SC25x100-LB 25 100 LB SC25x100-LB.step
28 SC25x100S-FA 25 100 S FA SC25x100S-FA.step
29 SC25x100S-LB 25 100 S LB SC25x100S-LB.step
30 SC32x25-FA 32 25 FA SC32x25-FA.step
31 SC32x25-LB 32 25 LB SC32x25-LB.step
32 SC32x25-CB 32 25 CB SC32x25-CB.step
33 SC32x25S-FA 32 25 S FA SC32x25S-FA.step
34 SC32x25S-LB 32 25 S LB SC32x25S-LB.step
35 SC32x25S-CB 32 25 S CB SC32x25S-CB.step
36 SC32x50-FA 32 50 FA SC32x50-FA.step
37 SC32x50-LB 32 50 LB SC32x50-LB.step
38 SC32x50-CB 32 50 CB SC32x50-CB.step
39 SC32x50S-FA 32 50 S FA SC32x50S-FA.step
40 SC32x50S-LB 32 50 S LB SC32x50S-LB.step
41 SC32x50S-CB 32 50 S CB SC32x50S-CB.step
42 SC32x75-FA 32 75 FA SC32x75-FA.step
43 SC32x75-LB 32 75 LB SC32x75-LB.step
44 SC32x75-CB 32 75 CB SC32x75-CB.step
45 SC32x75S-FA 32 75 S FA SC32x75S-FA.step
46 SC32x75S-LB 32 75 S LB SC32x75S-LB.step
47 SC32x75S-CB 32 75 S CB SC32x75S-CB.step
48 SC32x100-FA 32 100 FA SC32x100-FA.step
49 SC32x100-LB 32 100 LB SC32x100-LB.step
50 SC32x100S-FA 32 100 S FA SC32x100S-FA.step
51 SC32x100S-LB 32 100 S LB SC32x100S-LB.step
52 SC40x25-FA 40 25 FA SC40x25-FA.step
53 SC40x25-LB 40 25 LB SC40x25-LB.step
54 SC40x25-CB 40 25 CB SC40x25-CB.step
55 SC40x25S-FA 40 25 S FA SC40x25S-FA.step
56 SC40x25S-LB 40 25 S LB SC40x25S-LB.step
57 SC40x25S-CB 40 25 S CB SC40x25S-CB.step
58 SC40x50-FA 40 50 FA SC40x50-FA.step
59 SC40x50-LB 40 50 LB SC40x50-LB.step
60 SC40x50-CB 40 50 CB SC40x50-CB.step
61 SC40x50S-FA 40 50 S FA SC40x50S-FA.step
62 SC40x50S-LB 40 50 S LB SC40x50S-LB.step
63 SC40x50S-CB 40 50 S CB SC40x50S-CB.step
64 SC40x75-FA 40 75 FA SC40x75-FA.step
65 SC40x75-LB 40 75 LB SC40x75-LB.step
66 SC40x75-CB 40 75 CB SC40x75-CB.step
67 SC40x75S-FA 40 75 S FA SC40x75S-FA.step
68 SC40x75S-LB 40 75 S LB SC40x75S-LB.step
69 SC40x75S-CB 40 75 S CB SC40x75S-CB.step
70 SC40x100-FA 40 100 FA SC40x100-FA.step
71 SC40x100-LB 40 100 LB SC40x100-LB.step
72 SC40x100S-FA 40 100 S FA SC40x100S-FA.step
73 SC40x100S-LB 40 100 S LB SC40x100S-LB.step

View File

@@ -0,0 +1,37 @@
model_code,bore,stroke,magnet,mount,step_file
MAQ6x10-FA,6,10,,FA,MAQ6x10-FA.step
MAQ6x10-LB,6,10,,LB,MAQ6x10-LB.step
MAQ6x20-FA,6,20,,FA,MAQ6x20-FA.step
MAQ6x20-LB,6,20,,LB,MAQ6x20-LB.step
MAQ10x10-FA,10,10,,FA,MAQ10x10-FA.step
MAQ10x10-LB,10,10,,LB,MAQ10x10-LB.step
MAQ10x10S-FA,10,10,S,FA,MAQ10x10S-FA.step
MAQ10x10S-LB,10,10,S,LB,MAQ10x10S-LB.step
MAQ10x20-FA,10,20,,FA,MAQ10x20-FA.step
MAQ10x20-LB,10,20,,LB,MAQ10x20-LB.step
MAQ10x20S-FA,10,20,S,FA,MAQ10x20S-FA.step
MAQ10x20S-LB,10,20,S,LB,MAQ10x20S-LB.step
MAQ10x30-FA,10,30,,FA,MAQ10x30-FA.step
MAQ10x30-LB,10,30,,LB,MAQ10x30-LB.step
MAQ10x30S-FA,10,30,S,FA,MAQ10x30S-FA.step
MAQ10x30S-LB,10,30,S,LB,MAQ10x30S-LB.step
MAQ10x40-FA,10,40,,FA,MAQ10x40-FA.step
MAQ10x40-LB,10,40,,LB,MAQ10x40-LB.step
MAQ10x40S-FA,10,40,S,FA,MAQ10x40S-FA.step
MAQ10x40S-LB,10,40,S,LB,MAQ10x40S-LB.step
MAQ16x10-FA,16,10,,FA,MAQ16x10-FA.step
MAQ16x10-LB,16,10,,LB,MAQ16x10-LB.step
MAQ16x10S-FA,16,10,S,FA,MAQ16x10S-FA.step
MAQ16x10S-LB,16,10,S,LB,MAQ16x10S-LB.step
MAQ16x20-FA,16,20,,FA,MAQ16x20-FA.step
MAQ16x20-LB,16,20,,LB,MAQ16x20-LB.step
MAQ16x20S-FA,16,20,S,FA,MAQ16x20S-FA.step
MAQ16x20S-LB,16,20,S,LB,MAQ16x20S-LB.step
MAQ16x30-FA,16,30,,FA,MAQ16x30-FA.step
MAQ16x30-LB,16,30,,LB,MAQ16x30-LB.step
MAQ16x30S-FA,16,30,S,FA,MAQ16x30S-FA.step
MAQ16x30S-LB,16,30,S,LB,MAQ16x30S-LB.step
MAQ16x40-FA,16,40,,FA,MAQ16x40-FA.step
MAQ16x40-LB,16,40,,LB,MAQ16x40-LB.step
MAQ16x40S-FA,16,40,S,FA,MAQ16x40S-FA.step
MAQ16x40S-LB,16,40,S,LB,MAQ16x40S-LB.step
1 model_code bore stroke magnet mount step_file
2 MAQ6x10-FA 6 10 FA MAQ6x10-FA.step
3 MAQ6x10-LB 6 10 LB MAQ6x10-LB.step
4 MAQ6x20-FA 6 20 FA MAQ6x20-FA.step
5 MAQ6x20-LB 6 20 LB MAQ6x20-LB.step
6 MAQ10x10-FA 10 10 FA MAQ10x10-FA.step
7 MAQ10x10-LB 10 10 LB MAQ10x10-LB.step
8 MAQ10x10S-FA 10 10 S FA MAQ10x10S-FA.step
9 MAQ10x10S-LB 10 10 S LB MAQ10x10S-LB.step
10 MAQ10x20-FA 10 20 FA MAQ10x20-FA.step
11 MAQ10x20-LB 10 20 LB MAQ10x20-LB.step
12 MAQ10x20S-FA 10 20 S FA MAQ10x20S-FA.step
13 MAQ10x20S-LB 10 20 S LB MAQ10x20S-LB.step
14 MAQ10x30-FA 10 30 FA MAQ10x30-FA.step
15 MAQ10x30-LB 10 30 LB MAQ10x30-LB.step
16 MAQ10x30S-FA 10 30 S FA MAQ10x30S-FA.step
17 MAQ10x30S-LB 10 30 S LB MAQ10x30S-LB.step
18 MAQ10x40-FA 10 40 FA MAQ10x40-FA.step
19 MAQ10x40-LB 10 40 LB MAQ10x40-LB.step
20 MAQ10x40S-FA 10 40 S FA MAQ10x40S-FA.step
21 MAQ10x40S-LB 10 40 S LB MAQ10x40S-LB.step
22 MAQ16x10-FA 16 10 FA MAQ16x10-FA.step
23 MAQ16x10-LB 16 10 LB MAQ16x10-LB.step
24 MAQ16x10S-FA 16 10 S FA MAQ16x10S-FA.step
25 MAQ16x10S-LB 16 10 S LB MAQ16x10S-LB.step
26 MAQ16x20-FA 16 20 FA MAQ16x20-FA.step
27 MAQ16x20-LB 16 20 LB MAQ16x20-LB.step
28 MAQ16x20S-FA 16 20 S FA MAQ16x20S-FA.step
29 MAQ16x20S-LB 16 20 S LB MAQ16x20S-LB.step
30 MAQ16x30-FA 16 30 FA MAQ16x30-FA.step
31 MAQ16x30-LB 16 30 LB MAQ16x30-LB.step
32 MAQ16x30S-FA 16 30 S FA MAQ16x30S-FA.step
33 MAQ16x30S-LB 16 30 S LB MAQ16x30S-LB.step
34 MAQ16x40-FA 16 40 FA MAQ16x40-FA.step
35 MAQ16x40-LB 16 40 LB MAQ16x40-LB.step
36 MAQ16x40S-FA 16 40 S FA MAQ16x40S-FA.step
37 MAQ16x40S-LB 16 40 S LB MAQ16x40S-LB.step

View File

@@ -0,0 +1,163 @@
# NX 2506 参数化母模建模操作说明书
> 目的:为欧霓博目录软件建立"一个母模覆盖全系列所有型号组合"的参数化零件,
> 配套 `nx-batch-export.vb` 批量导出 STEP。
> 读者NX 建模工程师。**建模前先通读一遍,严格按命名规范执行。**
---
## 0. 核心概念(先想清楚再动手)
```
CSV 参数表 (数值源头) ←── 列名必须与母模表达式名一致 ──→ NX 母模 (公式/特征)
↓ ↓
nx-batch-export.vb (翻译官) ── 改表达式 → 更新模型 ──→ 导出 STEP
```
- **表达式名 = CSV 列名**(一字不差,区分大小写):`bore``stroke``magnet``mount`
- 开关类表达式另起名字并在 journal CONFIG 区映射:`magOn``mountIdx`
- 所有"会变"的尺寸必须表达式化;所有"有无"的特征必须按表达式抑制
- 建模完成后验收标准:**手动改 3 组不同参数组合,模型能正确重建且无错误**
---
## 1. 建模前规划
### 1.1 确定本系列的几何键(决定哪些组合要分别导出)
几何键 = 影响外形的一切参数组合。以 KC 系列为例:
| 参数 | 是否影响外形 | 母模处理 |
|---|---|---|
| type 型号段 (00/02/03 单杆/双轴/双轴可调) | ✅ | 双轴 = 第二根活塞杆特征组按表达式抑制 |
| bore 缸径 | ✅ | 尺寸表达式 |
| stroke 行程 | ✅ | 尺寸表达式 |
| magnet 磁石 | ✅(磁石槽+开关导轨) | 特征组按表达式抑制 (magOn=0/1) |
| mount 固定形式 | ✅(端盖耳座/支座/法兰特征) | 特征组按表达式抑制 (mountIdx=0~6) |
### 1.2 表达式命名清单(先建全,再开始画)
在母模里**先建立全部表达式**(工具→表达式 Ctrl+E带注释
| 名称 | 类型/初值 | 公式 | 注释 |
|---|---|---|---|
| bore | Number / 32 | 32 | 缸径 mm基准值journal 会覆盖) |
| stroke | Number / 50 | 50 | 行程 mm |
| rodLen | Number | stroke + 60 | 活塞杆长(联动示例,按实际结构改) |
| bodyLen | Number | stroke + 缸体常数 | 缸筒长(联动) |
| magOn | Number / 1 | 1 | 磁石开关1=有磁石 0=无 |
| magPos | Number | stroke + 40 | 磁石中心位置(联动;若独立规格则写常数+journal 映射) |
| guideLen | Number | stroke + 30 | 开关导轨长(联动) |
| mountIdx | Number / 0 | 0 | 安装形式0=基本 1=FA 2=FB 3=CA 4=CB 5=LB 6=YB |
| typeTwin | Number / 0 | 0 | 双轴开关1=双轴(第二根杆特征组抑制式引用它) |
> 命名建议全小写、驼峰bore/stroke/magOn与 CSV 列名一致;开关类加 On/Idx 后缀。
---
## 2. 建模步骤KC 母模示例)
### 步骤 1建缸体主体
1. 新建零件 → 存为 `KC_master.prt`
2. 草图:缸筒截面(**全约束**!)——外径引用 `bodyR = bore/2 + 壁厚` 等派生表达式,不要写死数
3. 拉伸长度引用 `bodyLen` 表达式
### 步骤 2端盖与活塞杆
- 前盖/后盖草图定位引用**基准坐标系或已有表达式**(严禁引用会被抑制的特征面)
- 活塞杆:圆柱特征,直径/长度引用表达式,定位在轴心
- **双轴型号02/03**:把第二根活塞杆+对应孔做成**特征组**,按表达式抑制:
```
右键特征 → 特征组 → 命名为 group_twin
右键 group_twin → 抑制 → 按表达式抑制
→ 表达式列表里新建 twinOn = 1抑制表达式写 if(twinOn==1)(1)else(0)
```
### 步骤 3磁石槽有/无 切换)
1. 在缸筒上画磁石槽:草图 → 拉伸求差(布尔 Subtract
2. 槽位置引用 `magPos`(中心),长度引用 `guideLen`
3. 磁石槽+导轨特征全选 → 特征组 `group_magnet`
4. 按表达式抑制:`if(magOn==1)(1)else(0)`
5. 开关导轨(如有独立件外形)同样入组
> ⚠️ 若倒角/圆角引用磁石槽的边,把倒角也放进 group_magnet否则抑制后更新报错。
### 步骤 4安装形式特征组7 种切换)
1. 以"基本型"端盖为底
2. 每种形式单独建模FA 法兰、FB 后法兰、CA 单耳、CB 双耳、LB 脚架孔、YB 支座孔),每组做完立即:
- 全选该组特征 → 特征组 `group_mount_FA` 等 6 个组
- 按表达式抑制,条件式:`if(mountIdx==1)(1)else(0)`FA 对应 1依此类推
3. 建模次序建议:先建完**基本型全部结构**,再逐个叠加形式特征,最后统一分组抑制
> 特征跨实体布尔(求和/求差)时,目标体选基本体,别选会被抑制的特征体。
### 步骤 5验证必须做
| 验证项 | 操作 | 通过标准 |
|---|---|---|
| 尺寸联动 | 改 bore=125 → Ctrl+F7 更新 | 无报错,模型整体放大 |
| 行程联动 | 改 stroke=1000 | 缸筒/活塞杆/导轨跟着变 |
| 磁石切换 | magOn=0/1 | 槽消失/出现,无悬空特征 |
| 形式切换 | mountIdx=0~6 逐个试 | 每值对应正确外形 |
| 双轴切换 | typeTwin=0/1 | 第二杆出现/消失 |
| 极限组合 | bore=320 + stroke=1000 + magOn=1 + mountIdx=6 | 极端组合不破面 |
验收后再交给 journal 跑批量。
---
## 3. Journal 配置nx-batch-export.vb
文件头有完整注释。**建模工程师只需改 CONFIG 区**
```vb
' ============================ CONFIG 区 ============================
Dim csvPath As String = "D:\catalog\params.csv" ' ← 改成 catalog\csv\KC.csv
Dim outDir As String = "D:\catalog\step_out\" ' ← 改成 catalog\step\
' 1) 尺寸映射: NX 表达式名 → CSV 列名
exprMap.Add("bore", "bore")
exprMap.Add("stroke", "stroke")
' 有独立表达式就加: exprMap.Add("magPos", "magpos") ← CSV 里也要有这一列
' 2) 枚举开关映射: {CSV列名, 列值, NX表达式名, 表达式值}
switchRules.Add({"magnet", "", "magOn", "0"}) ' 无磁石
switchRules.Add({"magnet", "M", "magOn", "1"}) ' 有磁石
switchRules.Add({"mount", "", "mountIdx", "0"}) ' 基本型
switchRules.Add({"mount", "FA", "mountIdx", "1"}) ' 前盖固定
...(按你们母模实际的表达式名和取值改)
' ================================================================
```
**运行**NX 里 工具 → 日记 → 播放 → 选 `nx-batch-export.vb`(不是 Ctrl+UCtrl+U 只认 .dll
---
## 4. 全流程走查(建模 → 目录上线)
```
1. NX 母模验收 (步骤 5 的验证表)
2. journal CONFIG 改好 → 播放 → 挂机批量导出 (已去重, 同文件只导一次)
3. STEP 落盘 catalog\step\ → 双击 OnebotCatalog\bin\维护入口.bat
→ 目录制作器 → 【重建全量目录】
4. 自测 (发布门禁自动跑 25 项, 缺文件会拦)
5. 停预览服务器 → publish.ps1 → 三份产物
6. 网页 localhost:8080 验证 3D + 下载
```
---
## 5. 常见问题排查
| 现象 | 原因 | 处理 |
|---|---|---|
| 导出后模型没变化 | journal 里表达式名与母模对不上 | 对照 Ctrl+E 列表逐名核对 |
| 更新报"更新失败/内部错误" | 被抑制特征被其它特征引用(边/面) | 把引用它的特征也入同一个抑制组 |
| 改 mountIdx 后外形不对 | 抑制条件式写错(== 写成 = | 条件式格式: `if(mountIdx==1)(1)else(0)` |
| 抑制后草图悬空 | 草图定位引用了会被抑制的边 | 定位改引用基准轴/面 |
| journal 报"参数表缺少列" | CSV 表头与 exprMap/switchRules 不一致 | 表头名与 CONFIG 统一 |
| 导出的 STEP 在网页渲染异常 | 用了特殊特征(钣金/焊接件) | 导出前抽空合并实体,尽量常规特征 |
| 中文乱码 | 编辑 .vb 后没跑 fix-bom.ps1 | 每次改完 journal 跑一次 fix-bom |

View File

@@ -0,0 +1,45 @@
' 给现有部件注入参数化表达式 (真数模做母模的管线验证用; 不改几何, 只加 bore/stroke/magOn/mountIdx)
Option Strict Off
Imports System
Imports System.IO
Imports System.Text
Imports NXOpen
Imports NXOpen.UF
Module AddExpressions
Sub Main()
Dim lw As ListingWindow = Session.GetSession().ListingWindow
lw.Open()
Dim outPath As String = Path.Combine(Directory.GetCurrentDirectory(), "KC_master.prt")
If Not File.Exists(outPath) Then
lw.WriteLine("部件缺失: " & outPath)
Return
End If
Dim partLoadStatus1 As NXOpen.PartLoadStatus = Nothing
Session.GetSession().Parts.OpenBaseDisplay(outPath, partLoadStatus1)
Dim w As Part = Session.GetSession().Parts.Work
If w Is Nothing Then
lw.WriteLine("打开失败")
Return
End If
Dim mm As NXOpen.Unit = w.UnitCollection.FindObject("MilliMeter")
Try
If mm IsNot Nothing Then
w.Expressions.CreateExpressionWithUnit("Number", "bore=32", mm)
w.Expressions.CreateExpressionWithUnit("Number", "stroke=10", mm)
Else
w.Expressions.CreateExpression("Number", "bore=32")
w.Expressions.CreateExpression("Number", "stroke=10")
End If
w.Expressions.CreateExpression("Number", "magOn=0")
w.Expressions.CreateExpression("Number", "mountIdx=0")
lw.WriteLine("表达式已注入")
Catch ex As Exception
lw.WriteLine("表达式注入失败: " & ex.Message)
Return
End Try
Dim pss As NXOpen.PartSaveStatus = w.Save(NXOpen.BasePart.SaveComponents.True, NXOpen.BasePart.CloseAfterSave.False)
If pss IsNot Nothing Then pss.Dispose()
lw.WriteLine("已保存: " & outPath)
End Sub
End Module

View File

@@ -0,0 +1,69 @@
' 检查母模: 特征数 / 实体数 / 表达式值
Option Strict Off
Imports System
Imports System.IO
Imports System.Text
Imports NXOpen
Imports NXOpen.UF
Module CheckMaster
Sub Main()
Dim sb As New StringBuilder()
Dim outPath As String = Path.Combine(Directory.GetCurrentDirectory(), "KC_master.prt")
Dim partLoadStatus1 As NXOpen.PartLoadStatus = Nothing
Session.GetSession().Parts.OpenBaseDisplay(outPath, partLoadStatus1)
Dim w As Part = Session.GetSession().Parts.Work
If w Is Nothing Then
sb.AppendLine("Work=NULL")
Else
Dim featCount As Integer = 0
Try
For Each f As NXOpen.Features.Feature In w.Features.GetFeatures()
featCount += 1
Next
Catch
End Try
sb.AppendLine("特征数=" & featCount)
Dim bodyCount As Integer = 0
Try
For Each b As NXOpen.Body In w.Bodies
bodyCount += 1
Next
Catch
End Try
sb.AppendLine("实体数=" & bodyCount)
Dim bi As Integer = 0
Try
For Each b As NXOpen.Body In w.Bodies
Dim fc As Integer = 0
Try
For Each f As NXOpen.Face In b.GetFaces()
fc += 1
Next
Catch
End Try
bi += 1
Try
For Each f As NXOpen.Face In b.GetFaces()
fc += 1
Next
Catch
End Try
sb.AppendLine("实体" & bi & " 面数=" & fc)
Next
Catch ex As Exception
sb.AppendLine("实体检查异常: " & ex.Message)
End Try
For Each nm As String In New String() {"bore", "stroke", "magOn", "mountIdx"}
Try
Dim e As NXOpen.Expression = w.Expressions.FindObject(nm)
sb.AppendLine("表达式 " & nm & "=" & (If(e Is Nothing, "(未找到)", e.Value)))
Catch ex As Exception
sb.AppendLine("表达式 " & nm & " 异常: " & ex.Message)
End Try
Next
sb.AppendLine("IsModified=" & w.IsModified)
End If
File.WriteAllText(Path.Combine(Directory.GetCurrentDirectory(), "check-master.txt"), sb.ToString(), Encoding.UTF8)
End Sub
End Module

View File

@@ -0,0 +1,100 @@
' =============================================================================
' NX Journal: 冒烟测试母模创建 (按需生成服务联调用, 2026-08-26; NX 2506 实测修正版)
'
' ▍干什么: 打开"当前工作目录\KC_master.prt"(启动前由脚本从 NX 模板复制) →
' 建 bore/stroke/magOn/mountIdx 四个表达式 (UF 底层 API, 跨版本稳定) → 建方块实体 → 保存
' 用途: 验证 worker_journal.vb 全链路 (打开→设表达式→更新→导出 4 格式) 在无真实母模时跑通。
' 真母模建好后同名替换 catalog-masters\KC\KC_master.prt, 本脚本可弃。
'
' ▍运行 (run_journal 只接受一个参数, 用 WorkingDirectory 定位):
' WorkingDirectory = catalog-masters\KC
' run_journal.exe <本文件完整路径>
' =============================================================================
Option Strict Off
Imports System
Imports System.IO
Imports NXOpen
Imports NXOpen.UF
Module CreateTestMaster
Sub Main()
Dim theSession As Session = Session.GetSession()
Dim lw As ListingWindow = theSession.ListingWindow
lw.Open()
Dim outPath As String = Path.Combine(Directory.GetCurrentDirectory(), "KC_master.prt")
If Not File.Exists(outPath) Then
lw.WriteLine("部件文件缺失: " & outPath & " (请先从 NX 模板复制到当前目录)")
Return
End If
' 打开模板部件 (批处理必须用 OpenBaseDisplay 才能成为工作部件, OpenBase 打开后 Work=NULL)
Dim partLoadStatus1 As NXOpen.PartLoadStatus = Nothing
Dim basePart1 As NXOpen.BasePart = theSession.Parts.OpenBaseDisplay(outPath, partLoadStatus1)
If partLoadStatus1 IsNot Nothing Then partLoadStatus1.Dispose()
Dim part1 As Part = theSession.Parts.Work
If part1 Is Nothing Then
lw.WriteLine("打开失败: " & outPath)
Return
End If
lw.WriteLine("部件已打开: " & part1.FullPath)
' 表达式 (NX 2506: 用 ExpressionCollection.CreateExpression(值, 名) 创建无单位表达式;
' 特征尺寸按部件单位解释; EditExp 只编辑已存在的表达式)
Dim mm As NXOpen.Unit = part1.UnitCollection.FindObject("MilliMeter")
If mm Is Nothing Then
lw.WriteLine("警告: 未找到 MilliMeter 单位对象, 尝试 Millimetre")
mm = part1.UnitCollection.FindObject("Millimetre")
End If
' 缸径/行程带长度单位 (特征尺寸表达式需要); 开关无单位
If mm IsNot Nothing Then
part1.Expressions.CreateExpressionWithUnit("Number", "bore=32", mm)
part1.Expressions.CreateExpressionWithUnit("Number", "stroke=10", mm)
Else
part1.Expressions.CreateExpression("Number", "bore=32")
part1.Expressions.CreateExpression("Number", "stroke=10")
End If
part1.Expressions.CreateExpression("Number", "magOn=0")
part1.Expressions.CreateExpression("Number", "mountIdx=0")
lw.WriteLine("表达式已建: bore=32 stroke=10 magOn=0 mountIdx=0")
' 仿真气缸母模 (测试级真实感): 缸筒 + 活塞杆, 尺寸全部由表达式驱动
' 缸筒: 外径 bore+8 (壁厚4), 长度 stroke; 活塞杆: 直径 bore*0.33, 长度 stroke+30 (前伸 30)
Dim ufs As UFSession = UFSession.GetUFSession()
Dim origin(2) As Double
Dim dir(2) As Double
dir(2) = 1.0 ' +Z 轴向
' NX 2506 签名: CreateCyl1(sign, origin, height, diam, direction, ByRef tag)
Dim tagTube As Tag
ufs.Modl.CreateCyl1(NXOpen.UF.FeatureSigns.Nullsign, origin, "stroke", "bore+8", dir, tagTube)
Dim tagRod As Tag
ufs.Modl.CreateCyl1(NXOpen.UF.FeatureSigns.Nullsign, origin, "stroke+30", "bore*0.33", dir, tagRod)
lw.WriteLine("仿真气缸已建: 缸筒(bore+8 x stroke) + 活塞杆(bore*0.33 x stroke+30), 表达式驱动")
' 更新模型 (特征刚建, 必须 Update 后才成为实体几何)
ufs.Modl.Update()
lw.WriteLine("模型已更新")
' 把实体加进 Model 引用集 (UF 建的实体默认不进引用集 → 导出器按引用集过滤会导出空几何)
Dim bodyList As New System.Collections.Generic.List(Of NXOpen.NXObject)()
For Each b As NXOpen.Body In part1.Bodies
bodyList.Add(b)
Next
If bodyList.Count > 0 Then
For Each rs As NXOpen.ReferenceSet In part1.GetAllReferenceSets()
If rs.Name.ToUpperInvariant().Contains("MODEL") Then
rs.AddObjectsToReferenceSet(bodyList.ToArray())
lw.WriteLine("实体已加入引用集 " & rs.Name & " (" & bodyList.Count & " 个)")
Exit For
End If
Next
End If
' 保存 (NX 2506: Save 两参数, 返回 PartSaveStatus)
Dim pss As NXOpen.PartSaveStatus = part1.Save(NXOpen.BasePart.SaveComponents.True, NXOpen.BasePart.CloseAfterSave.False)
If pss IsNot Nothing Then pss.Dispose()
lw.WriteLine("已保存: " & outPath)
End Sub
End Module

View File

@@ -0,0 +1,91 @@
' 调试: 四格式导出交叉测试 (STEP/Parasolid/IGES/STL) — 判断是几何问题还是翻译器配置问题
Option Strict Off
Imports System
Imports System.IO
Imports System.Text
Imports NXOpen
Imports NXOpen.UF
Module DebugExportAll
Sub Main()
Dim sb As New StringBuilder()
Dim cur As String = Directory.GetCurrentDirectory()
Dim masterPath As String = Path.Combine(cur, "KC_master.prt")
Dim partLoadStatus1 As NXOpen.PartLoadStatus = Nothing
Session.GetSession().Parts.OpenBaseDisplay(masterPath, partLoadStatus1)
Dim w As Part = Session.GetSession().Parts.Work
Dim outDir As String = Path.Combine(cur, "dbg_out")
Directory.CreateDirectory(outDir)
For Each f As String In Directory.GetFiles(outDir)
File.Delete(f)
Next
' 保存工作部件 (导出器可能读文件; 也验证 Save 后实体仍在)
Try
Dim pss As NXOpen.PartSaveStatus = w.Save(NXOpen.BasePart.SaveComponents.True, NXOpen.BasePart.CloseAfterSave.False)
If pss IsNot Nothing Then pss.Dispose()
sb.AppendLine("Save OK")
Catch ex As Exception
sb.AppendLine("Save 失败: " & ex.Message)
End Try
' 1) STL (显示几何三角化; 若此也不出网格 → 几何本身有问题)
Try
Dim stc As NXOpen.STLCreator = Session.GetSession().DexManager.CreateStlCreator()
stc.OutputType = NXOpen.STLCreator.OutputTypeEnum.Binary
stc.ChordalTol = 0.5
stc.AdjacencyTol = 0.1
stc.AutoNormalGen = True
stc.OutputFile = Path.Combine(outDir, "T.stl")
stc.Commit()
stc.Destroy()
sb.AppendLine("STL: " & (New FileInfo(Path.Combine(outDir, "T.stl"))).Length & " 字节")
Catch ex As Exception
sb.AppendLine("STL 失败: " & ex.Message)
End Try
' 2) Parasolid (文件方式)
Try
Dim pe As NXOpen.ParasolidExporter = Session.GetSession().DexManager.CreateParasolidExporter()
pe.ExportFrom = NXOpen.ParasolidExporter.ExportFromOption.DisplayedPart
pe.ParasolidVersion = NXOpen.ParasolidExporter.ParasolidVersionOption.Current
pe.OutputFile = Path.Combine(outDir, "T.x_t")
pe.Commit()
pe.Destroy()
sb.AppendLine("Parasolid: " & (New FileInfo(Path.Combine(outDir, "T.x_t"))).Length & " 字节")
Catch ex As Exception
sb.AppendLine("Parasolid 失败: " & ex.Message)
End Try
' 3) IGES (文件方式 ExistingPart)
Try
Dim ic As NXOpen.IgesCreator = Session.GetSession().DexManager.CreateIgesCreator()
ic.ExportFrom = NXOpen.IgesCreator.ExportFromOption.ExistingPart
ic.InputFile = masterPath
ic.ExportModelData = True
ic.OutputFile = Path.Combine(outDir, "T.igs")
ic.Commit()
ic.Destroy()
sb.AppendLine("IGES: " & (New FileInfo(Path.Combine(outDir, "T.igs"))).Length & " 字节")
Catch ex As Exception
sb.AppendLine("IGES 失败: " & ex.Message)
End Try
' 4) STEP (文件方式 ExistingPart)
Try
Dim sc As NXOpen.StepCreator = Session.GetSession().DexManager.CreateStepCreator()
sc.ExportFrom = NXOpen.StepCreator.ExportFromOption.ExistingPart
sc.InputFile = masterPath
sc.ExportAs = NXOpen.StepCreator.ExportAsOption.Ap214
sc.OutputFile = Path.Combine(outDir, "T.stp")
sc.ColorAndLayers = True
sc.Commit()
sc.Destroy()
sb.AppendLine("STEP: " & (New FileInfo(Path.Combine(outDir, "T.stp"))).Length & " 字节")
Catch ex As Exception
sb.AppendLine("STEP 失败: " & ex.Message)
End Try
File.WriteAllText(Path.Combine(cur, "debug-export-all.txt"), sb.ToString(), Encoding.UTF8)
End Sub
End Module

View File

@@ -0,0 +1,69 @@
' 调试: 单步 STEP 导出定位 "File already exists" 来源
Option Strict Off
Imports System
Imports System.IO
Imports System.Text
Imports NXOpen
Imports NXOpen.UF
Module DebugExport
Sub Main()
Dim sb As New StringBuilder()
Dim cur As String = Directory.GetCurrentDirectory()
Dim masterPath As String = Path.Combine(cur, "KC_master.prt")
sb.AppendLine("CWD=" & cur & " masterExists=" & File.Exists(masterPath))
Dim partLoadStatus1 As NXOpen.PartLoadStatus = Nothing
Session.GetSession().Parts.OpenBaseDisplay(masterPath, partLoadStatus1)
Dim w As Part = Session.GetSession().Parts.Work
sb.AppendLine("Work=" & (If(w Is Nothing, "NULL", w.FullPath)))
If w IsNot Nothing Then
Dim ufs As UFSession = UFSession.GetUFSession()
Try
ufs.Modl.EditExp("stroke=87")
ufs.Modl.Update()
sb.AppendLine("EditExp+Update OK")
Catch ex As Exception
sb.AppendLine("EditExp/Update 失败: " & ex.Message)
End Try
' 诊断: 实体数与引用集
Dim bodyCount As Integer = 0
Try
For Each b As NXOpen.Body In w.Bodies
bodyCount += 1
Next
Catch
End Try
sb.AppendLine("实体数=" & bodyCount)
Try
For Each rs As NXOpen.ReferenceSet In w.GetAllReferenceSets()
sb.AppendLine("引用集: " & rs.Name)
Next
Catch ex As Exception
sb.AppendLine("引用集枚举失败: " & ex.Message)
End Try
Dim outDir As String = Path.Combine(cur, "dbg_out")
Directory.CreateDirectory(outDir)
Dim stpPath As String = Path.Combine(outDir, "DBG.stp")
If File.Exists(stpPath) Then File.Delete(stpPath)
Try
Dim sc As NXOpen.StepCreator = Session.GetSession().DexManager.CreateStepCreator()
sc.ExportFrom = NXOpen.StepCreator.ExportFromOption.ExistingPart ' 批处理无显示部件 → 直接处理部件文件
sc.InputFile = masterPath
sc.ExportAs = NXOpen.StepCreator.ExportAsOption.Ap214
sc.OutputFile = stpPath
sc.ColorAndLayers = True
sc.Commit()
sc.Destroy()
sb.AppendLine("STEP 导出成功: " & stpPath & " size=" & (New FileInfo(stpPath)).Length)
Catch ex As Exception
sb.AppendLine("STEP 导出失败: " & ex.GetType().Name & " / " & ex.Message)
Dim inner As Exception = ex.InnerException
While inner IsNot Nothing
sb.AppendLine(" inner: " & inner.Message)
inner = inner.InnerException
End While
End Try
End If
File.WriteAllText(Path.Combine(cur, "debug-export.txt"), sb.ToString(), Encoding.UTF8)
End Sub
End Module

View File

@@ -0,0 +1,46 @@
' 诊断 2: 批处理下 Work=NULL — 验证 (a) BasePart 直接转 Part 操作 (b) SetDisplay 签名
Option Strict Off
Imports System
Imports System.IO
Imports System.Text
Imports NXOpen
Imports NXOpen.UF
Module DebugSave
Sub Main()
Dim lw As ListingWindow = Session.GetSession().ListingWindow
lw.Open()
Dim sb As New StringBuilder()
Dim outPath As String = Path.Combine(Directory.GetCurrentDirectory(), "KC_master.prt")
sb.AppendLine("CWD=" & Directory.GetCurrentDirectory())
' (a) SetDisplay 签名反射
Dim pc As NXOpen.PartCollection = Session.GetSession().Parts
For Each mi As System.Reflection.MethodInfo In pc.GetType().GetMethods()
If mi.Name = "SetDisplay" OrElse mi.Name = "Open" Then sb.AppendLine("SIG: " & mi.ToString())
Next
Dim partLoadStatus1 As NXOpen.PartLoadStatus = Nothing
Dim basePart1 As NXOpen.BasePart = Session.GetSession().Parts.OpenBaseDisplay(outPath, partLoadStatus1)
sb.AppendLine("basePart1=" & (If(basePart1 Is Nothing, "NULL", basePart1.FullPath)))
sb.AppendLine("Work=" & (If(Session.GetSession().Parts.Work Is Nothing, "NULL", "非空")))
' (b) 直接转换 BasePart → Part 并操作
Dim p1 As Part = CType(basePart1, Part)
sb.AppendLine("p1 cast ok, FullPath=" & p1.FullPath & " IsModified(before)=" & p1.IsModified)
Dim ufs As UFSession = UFSession.GetUFSession()
ufs.Modl.EditExp("bore=50")
ufs.Modl.Update()
sb.AppendLine("p1.IsModified(after EditExp+Update)=" & p1.IsModified)
' 对比 Save 与 SaveAs 在批处理下的落盘行为
Dim pss As NXOpen.PartSaveStatus = p1.Save(NXOpen.BasePart.SaveComponents.True, NXOpen.BasePart.CloseAfterSave.False)
If pss IsNot Nothing Then pss.Dispose()
sb.AppendLine("after Save: size=" & (New FileInfo(outPath)).Length & " mtime=" & File.GetLastWriteTime(outPath))
Dim asPath As String = Path.Combine(Directory.GetCurrentDirectory(), "KC_master_as.prt")
Dim pss2 As NXOpen.PartSaveStatus = p1.SaveAs(asPath)
If pss2 IsNot Nothing Then pss2.Dispose()
sb.AppendLine("after SaveAs: exists=" & File.Exists(asPath) & " size=" & (If(File.Exists(asPath), (New FileInfo(asPath)).Length, 0)))
File.WriteAllText(Path.Combine(Directory.GetCurrentDirectory(), "debug-save.txt"), sb.ToString(), Encoding.UTF8)
lw.WriteLine(sb.ToString())
End Sub
End Module

View File

@@ -0,0 +1,18 @@
# Ensure each .ps1 has exactly ONE UTF-8 BOM so PowerShell 5.1 reads Chinese correctly.
# Idempotent: strips any leading BOMs first, then prepends a single one.
# (PS 5.1 treats BOM-less files as ANSI/GBK; this script is intentionally ASCII-only.)
param([string[]]$Files)
if (-not $Files) {
$Files = Get-ChildItem (Join-Path $PSScriptRoot '*.ps1') | ForEach-Object { $_.FullName }
}
foreach ($f in $Files) {
$bytes = [System.IO.File]::ReadAllBytes($f)
$i = 0
while (($i + 2) -lt $bytes.Length -and $bytes[$i] -eq 0xEF -and $bytes[$i + 1] -eq 0xBB -and $bytes[$i + 2] -eq 0xBF) { $i += 3 }
if ($i -gt 0) { $bytes = $bytes[$i..($bytes.Length - 1)] }
$out = New-Object byte[] ($bytes.Length + 3)
$out[0] = 0xEF; $out[1] = 0xBB; $out[2] = 0xBF
[Array]::Copy($bytes, 0, $out, 3, $bytes.Length)
[System.IO.File]::WriteAllBytes($f, $out)
Write-Output ("BOM fixed: " + $f)
}

View File

@@ -0,0 +1,235 @@
# 生成示例 2D 尺寸图 PNG 与数据表 JPEG (中/英)
# 用法: powershell -NoProfile -ExecutionPolicy Bypass -File tools\gen-images.ps1
# 依赖: 仅 Windows 自带 System.Drawing (PowerShell 5.1)
Add-Type -AssemblyName System.Drawing
$docsDir = Join-Path (Join-Path $PSScriptRoot '..\onb-sc') 'docs'
$dimDir = Join-Path $docsDir 'dim-drawing'
$dsDir = Join-Path $docsDir 'datasheet'
New-Item -ItemType Directory -Force -Path $dimDir | Out-Null
New-Item -ItemType Directory -Force -Path $dsDir | Out-Null
$fontTitle = New-Object System.Drawing.Font('Microsoft YaHei', 26, [System.Drawing.FontStyle]::Bold)
$fontH2 = New-Object System.Drawing.Font('Microsoft YaHei', 17, [System.Drawing.FontStyle]::Bold)
$fontText = New-Object System.Drawing.Font('Microsoft YaHei', 13)
$fontSmall = New-Object System.Drawing.Font('Microsoft YaHei', 11)
$penThin = New-Object System.Drawing.Pen([System.Drawing.Color]::Black, 1)
$penLine = New-Object System.Drawing.Pen([System.Drawing.Color]::Black, 2)
$penDash = New-Object System.Drawing.Pen([System.Drawing.Color]::Black, 1)
$penDash.DashStyle = [System.Drawing.Drawing2D.DashStyle]::Dash
$brush = [System.Drawing.Brushes]::Black
$fillGray = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::FromArgb(235, 235, 235))
function New-Canvas([int]$w, [int]$h) {
$bmp = New-Object System.Drawing.Bitmap($w, $h)
$g = [System.Drawing.Graphics]::FromImage($bmp)
$g.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::AntiAlias
$g.TextRenderingHint = [System.Drawing.Text.TextRenderingHint]::AntiAlias
$g.Clear([System.Drawing.Color]::White)
return ,@($bmp, $g)
}
# 尺寸线箭头 (dx,dy 为箭头指向)
function Draw-Arrow($g, [float]$x, [float]$y, [float]$dx, [float]$dy, [float]$size) {
$px = -$dy; $py = $dx
$p1 = New-Object System.Drawing.PointF(($x + $dx * $size), ($y + $dy * $size))
$p2 = New-Object System.Drawing.PointF(($x + $px * $size * 0.45), ($y + $py * $size * 0.45))
$p3 = New-Object System.Drawing.PointF(($x - $px * $size * 0.45), ($y - $py * $size * 0.45))
$g.FillPolygon($brush, [System.Drawing.PointF[]]@($p1, $p2, $p3))
}
# 表格: rows 为 string[] 数组, cols 为每列宽度
function Draw-Table($g, [float]$x, [float]$y, $rows, [float[]]$cols, [float]$rowH) {
$top = $y
foreach ($r in $rows) {
$xx = $x
for ($i = 0; $i -lt $cols.Count; $i++) {
$g.DrawRectangle($penThin, $xx, $top, $cols[$i], $rowH)
$g.DrawString($r[$i], $fontText, $brush, ($xx + 8), ($top + 10))
$xx += $cols[$i]
}
$top += $rowH
}
}
# ---- 2D 尺寸图 (中英双语标注) ----
function Draw-DimDrawing([string]$outPath, [int]$bore, [int]$stroke, [string]$magnet, [string]$mount) {
$c = New-Canvas 1000 1500
$bmp = $c[0]; $g = $c[1]
$s = 3.0 # px/mm
$cx = 500.0
$yTop = 320.0 # 前盖顶边
$capW = ($bore + 10) * $s
$bodyW = ($bore + 4) * $s
$rodD = [Math]::Max($bore / 5.0, 3.0) * 2
$rodW = $rodD * $s
$yFrontCap = $yTop + 10 * $s
$yBody = $yFrontCap + ($stroke + 60) * $s
$yRearCap = $yBody + 8 * $s
$yRodTop = $yTop - ($stroke + 10) * $s
$yRodBot = $yTop + 14 * $s
$model = "SC$bore`x$stroke$magnet-$mount"
$total = 2 * $stroke + 88
$g.DrawString('欧霓博 OUNIBO · SC 系列标准气缸 尺寸图', $fontTitle, $brush, 80, 40)
$g.DrawString('SC Series Standard Cylinder · Dimension Drawing', $fontText, $brush, 82, 92)
$g.DrawString("型号 Model: $model", $fontH2, $brush, 82, 128)
# 外形 (前盖/缸体/后盖/活塞杆)
$g.FillRectangle($fillGray, ($cx - $capW / 2), $yTop, $capW, (10 * $s))
$g.DrawRectangle($penLine, ($cx - $capW / 2), $yTop, $capW, (10 * $s))
$g.FillRectangle($fillGray, ($cx - $bodyW / 2), $yFrontCap, $bodyW, (($stroke + 60) * $s))
$g.DrawRectangle($penLine, ($cx - $bodyW / 2), $yFrontCap, $bodyW, (($stroke + 60) * $s))
$g.FillRectangle($fillGray, ($cx - $capW / 2), $yBody, $capW, (8 * $s))
$g.DrawRectangle($penLine, ($cx - $capW / 2), $yBody, $capW, (8 * $s))
$g.FillRectangle($fillGray, ($cx - $rodW / 2), $yRodTop, $rodW, ($yRodBot - $yRodTop))
$g.DrawRectangle($penLine, ($cx - $rodW / 2), $yRodTop, $rodW, ($yRodBot - $yRodTop))
# 中心线
$g.DrawLine($penDash, $cx, ($yRodTop - 20), $cx, ($yRearCap + 30))
# 缸径尺寸线 (跨前盖)
$dimY = $yFrontCap + 40
$g.DrawLine($penDash, ($cx - $capW / 2), ($yTop + 4), ($cx - $capW / 2), $dimY)
$g.DrawLine($penDash, ($cx + $capW / 2), ($yTop + 4), ($cx + $capW / 2), $dimY)
$g.DrawLine($penThin, ($cx - $capW / 2), $dimY, ($cx + $capW / 2), $dimY)
Draw-Arrow $g ($cx - $capW / 2) $dimY -1 0 9
Draw-Arrow $g ($cx + $capW / 2) $dimY 1 0 9
$g.DrawString("Ø$bore mm (缸径 Bore)", $fontH2, $brush, ($cx - 70), ($dimY + 10))
# 行程尺寸线 (活塞杆伸出段, 右侧)
$dimX = $cx + $capW / 2 + 80
$g.DrawLine($penDash, ($cx + $capW / 2 + 4), $yTop, $dimX, $yTop)
$g.DrawLine($penDash, ($cx + $rodW / 2 + 4), $yRodTop, $dimX, $yRodTop)
$g.DrawLine($penThin, $dimX, $yRodTop, $dimX, $yTop)
Draw-Arrow $g $dimX $yRodTop 0 1 9
Draw-Arrow $g $dimX $yTop 0 -1 9
$g.DrawString("$stroke mm (行程 Stroke)", $fontH2, $brush, ($dimX + 12), (($yTop + $yRodTop) / 2 - 8))
# 总长尺寸线 (左侧)
$dimX2 = $cx - $capW / 2 - 80
$g.DrawLine($penDash, ($cx - $capW / 2 - 4), $yRodTop, $dimX2, $yRodTop)
$g.DrawLine($penDash, ($cx - $capW / 2 - 4), $yRearCap, $dimX2, $yRearCap)
$g.DrawLine($penThin, $dimX2, $yRodTop, $dimX2, $yRearCap)
Draw-Arrow $g $dimX2 $yRodTop 0 1 9
Draw-Arrow $g $dimX2 $yRearCap 0 -1 9
$g.DrawString("总长 Total: $total mm", $fontH2, $brush, ($dimX2 - 180), (($yRodTop + $yRearCap) / 2 - 8))
# 磁石标注
if ($magnet -eq 'S') {
$g.DrawString('磁石 S / Magnet', $fontH2, $brush, ($cx + $bodyW / 2 + 20), ($yFrontCap + 60))
$g.DrawLine($penThin, ($cx + $bodyW / 2 + 6), ($yFrontCap + 66), ($cx + $bodyW / 2 + 18), ($yFrontCap + 66))
}
# 信息表
$mountDesc = 'FA=前法兰 Front flange / LB=脚座 Foot / CB=中摆 Clevis'
$tblRows = @()
$tblRows += ,@('项目 Item', '值 Value')
$tblRows += ,@('型号 Model', $model)
$tblRows += ,@('缸径 Bore', "Ø$bore mm")
$tblRows += ,@('行程 Stroke', "$stroke mm")
$tblRows += ,@('磁石 Magnet', $(if ($magnet -eq 'S') { 'S 带磁石' } else { '无 None' }))
$tblRows += ,@('安装 Mount', $mountDesc)
Draw-Table $g 150 1120 $tblRows @(200.0, 500.0) 40
$g.DrawString('示意图 · 自编示例数据,仅用于目录软件开发 / Sample drawing for development only', $fontSmall, $brush, 150, 1420)
$bmp.Save($outPath, [System.Drawing.Imaging.ImageFormat]::Png)
$g.Dispose(); $bmp.Dispose()
Write-Output ("尺寸图: " + $outPath)
}
# ---- 数据表 JPEG (zh/en) ----
function Draw-Datasheet([string]$outPath, [string]$lang) {
$c = New-Canvas 1190 1684
$bmp = $c[0]; $g = $c[1]
$zh = ($lang -eq 'zh')
if ($zh) {
$g.DrawString('欧霓博 OUNIBO 气动元件', $fontTitle, $brush, 70, 50)
$g.DrawString('SC 系列标准气缸 · 数据表 Datasheet', $fontH2, $brush, 70, 100)
$g.DrawString('型号编码 Order Code: SC{Bore}x{Stroke}{Magnet}-{Mount}', $fontH2, $brush, 70, 150)
$encRows = @()
$encRows += ,@('段 Segment', '含义 Meaning', '取值 Values')
$encRows += ,@('SC', '系列 Series', '固定 Fixed')
$encRows += ,@('{Bore}', '缸径 Bore (mm)', '16 / 25 / 32 / 40')
$encRows += ,@('{Stroke}', '行程 Stroke (mm)', '25 / 50 / 75 / 100')
$encRows += ,@('{Magnet}', '磁石 Magnet', '空=无 None / S=带磁石')
$encRows += ,@('{Mount}', '安装 Mount', 'FA=前法兰 / LB=脚座 / CB=中摆')
Draw-Table $g 70 200 $encRows @(150.0, 240.0, 360.0) 44
$g.DrawString('规格 Specifications', $fontH2, $brush, 70, 500)
$specRows = @()
$specRows += ,@('参数 Parameter', '取值 Values')
$specRows += ,@('缸径 Bore (mm)', '16 / 25 / 32 / 40')
$specRows += ,@('行程 Stroke (mm)', '25 / 50 / 75 / 100')
$specRows += ,@('磁石 Magnet', '无 None / S')
$specRows += ,@('安装 Mount', 'FA / LB / CB')
Draw-Table $g 70 550 $specRows @(260.0, 400.0) 44
$g.DrawString('选型约束 Selection Rules', $fontH2, $brush, 70, 780)
$g.DrawString('R1 缸径 16 时,行程仅可选 25 / 50', $fontText, $brush, 90, 830)
$g.DrawString('R2 磁石 S 仅缸径 >= 25 可选', $fontText, $brush, 90, 870)
$g.DrawString('R3 中摆 CB 仅行程 <= 75 可选', $fontText, $brush, 90, 910)
$g.DrawString('参考尺寸 Reference Dimensions', $fontH2, $brush, 70, 980)
$dimRows = @()
$dimRows += ,@('型号 Model', '缸径 Bore', '行程 Stroke', '总长 Overall (mm)', '缸体长 Body (mm)')
$dimRows += ,@('SC16x25-FA', '16', '25', '138', '85')
$dimRows += ,@('SC25x50S-FA', '25', '50', '188', '110')
$dimRows += ,@('SC32x50S-LB', '32', '50', '188', '110')
$dimRows += ,@('SC40x100S-LB', '40', '100', '288', '160')
Draw-Table $g 70 1030 $dimRows @(210.0, 120.0, 130.0, 200.0, 190.0) 44
$g.DrawString('注 Note: 本数据表为自编示例数据(虚构系列),仅用于目录软件开发测试,非真实产品数据。', $fontSmall, $brush, 70, 1290)
$g.DrawString('生成工具: sample-data/tools/gen-images.ps1', $fontSmall, $brush, 70, 1320)
} else {
$g.DrawString('OUNIBO Pneumatic Components', $fontTitle, $brush, 70, 50)
$g.DrawString('SC Series Standard Cylinder · Datasheet', $fontH2, $brush, 70, 100)
$g.DrawString('Order Code: SC{Bore}x{Stroke}{Magnet}-{Mount}', $fontH2, $brush, 70, 150)
$encRows = @()
$encRows += ,@('Segment', 'Meaning', 'Values')
$encRows += ,@('SC', 'Series', 'Fixed')
$encRows += ,@('{Bore}', 'Bore (mm)', '16 / 25 / 32 / 40')
$encRows += ,@('{Stroke}', 'Stroke (mm)', '25 / 50 / 75 / 100')
$encRows += ,@('{Magnet}', 'Magnet', 'blank=None / S=Magnet')
$encRows += ,@('{Mount}', 'Mounting', 'FA=Front flange / LB=Foot / CB=Clevis')
Draw-Table $g 70 200 $encRows @(150.0, 240.0, 360.0) 44
$g.DrawString('Specifications', $fontH2, $brush, 70, 500)
$specRows = @()
$specRows += ,@('Parameter', 'Values')
$specRows += ,@('Bore (mm)', '16 / 25 / 32 / 40')
$specRows += ,@('Stroke (mm)', '25 / 50 / 75 / 100')
$specRows += ,@('Magnet', 'None / S')
$specRows += ,@('Mounting', 'FA / LB / CB')
Draw-Table $g 70 550 $specRows @(260.0, 400.0) 44
$g.DrawString('Selection Rules', $fontH2, $brush, 70, 780)
$g.DrawString('R1 Bore 16: stroke limited to 25 / 50', $fontText, $brush, 90, 830)
$g.DrawString('R2 Magnet S requires bore >= 25', $fontText, $brush, 90, 870)
$g.DrawString('R3 Clevis CB requires stroke <= 75', $fontText, $brush, 90, 910)
$g.DrawString('Reference Dimensions', $fontH2, $brush, 70, 980)
$dimRows = @()
$dimRows += ,@('Model', 'Bore', 'Stroke', 'Overall (mm)', 'Body (mm)')
$dimRows += ,@('SC16x25-FA', '16', '25', '138', '85')
$dimRows += ,@('SC25x50S-FA', '25', '50', '188', '110')
$dimRows += ,@('SC32x50S-LB', '32', '50', '188', '110')
$dimRows += ,@('SC40x100S-LB', '40', '100', '288', '160')
Draw-Table $g 70 1030 $dimRows @(210.0, 120.0, 130.0, 200.0, 190.0) 44
$g.DrawString('Note: This datasheet contains sample data (fictional series) for catalog software development only, not real product data.', $fontSmall, $brush, 70, 1290)
$g.DrawString('Generated by: sample-data/tools/gen-images.ps1', $fontSmall, $brush, 70, 1320)
}
$bmp.Save($outPath, [System.Drawing.Imaging.ImageFormat]::Jpeg)
$g.Dispose(); $bmp.Dispose()
Write-Output ("数据表: " + $outPath)
}
# ---- 生成 ----
Draw-DimDrawing (Join-Path $dimDir 'SC32x50S-LB.png') 32 50 'S' 'LB'
Draw-DimDrawing (Join-Path $dimDir 'SC16x25-FA.png') 16 25 '' 'FA'
Draw-Datasheet (Join-Path $dsDir 'SC_datasheet_zh.jpg') 'zh'
Draw-Datasheet (Join-Path $dsDir 'SC_datasheet_en.jpg') 'en'

View File

@@ -0,0 +1,72 @@
# 将数据表 JPEG 包装成极简单页 A4 PDF (DCTDecode 直嵌, 字节级 xref 偏移)
# 用法: powershell -NoProfile -ExecutionPolicy Bypass -File tools\gen-pdf.ps1
# 说明: 中文内容以图片形式嵌入, 规避 PDF 中文字体嵌入问题; 结构最小化, 主流阅读器可打开。
param(
[string]$Jpg,
[string]$Pdf
)
Add-Type -AssemblyName System.Drawing
$img = [System.Drawing.Image]::FromFile($Jpg)
$w = $img.Width
$h = $img.Height
$img.Dispose()
$jpgBytes = [System.IO.File]::ReadAllBytes($Jpg)
$chunks = New-Object System.Collections.Generic.List[byte[]]
$offsets = New-Object System.Collections.Generic.List[int]
$pos = 0
function Add-Ascii([string]$t) {
$b = [System.Text.Encoding]::ASCII.GetBytes($t)
$chunks.Add($b)
$script:pos += $b.Length
}
function Mark-Obj() { $offsets.Add($script:pos) }
$imgLen = $jpgBytes.Length + 1
$content = 'q 595.28 0 0 841.89 0 0 cm /Im0 Do Q'
$contentLen = $content.Length + 1
Add-Ascii "%PDF-1.4`n"
Mark-Obj; Add-Ascii "1 0 obj<< /Type /Catalog /Pages 2 0 R >>endobj`n"
Mark-Obj; Add-Ascii "2 0 obj<< /Type /Pages /Kids [3 0 R] /Count 1 >>endobj`n"
Mark-Obj; Add-Ascii "3 0 obj<< /Type /Page /Parent 2 0 R /MediaBox [0 0 595.28 841.89] /Resources << /XObject << /Im0 4 0 R >> >> /Contents 5 0 R >>endobj`n"
Mark-Obj; Add-Ascii "4 0 obj<< /Type /XObject /Subtype /Image /Width $w /Height $h /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /DCTDecode /Length $imgLen >>stream`n"
$chunks.Add($jpgBytes); $pos += $jpgBytes.Length
Add-Ascii "`nendstream`nendobj`n"
Mark-Obj; Add-Ascii "5 0 obj<< /Length $contentLen >>stream`n"
Add-Ascii "$content`nendstream`nendobj`n"
$xrefOffset = $pos
$sb = New-Object System.Text.StringBuilder
[void]$sb.Append("xref`n0 6`n0000000000 65535 f `n")
foreach ($off in $offsets) {
[void]$sb.Append(("{0:0000000000} {1:00000} n `n" -f $off, 0))
}
Add-Ascii $sb.ToString()
Add-Ascii ("trailer`n<< /Size 6 /Root 1 0 R >>`nstartxref`n" + $xrefOffset + "`n%%EOF`n")
$fs = [System.IO.File]::Create($Pdf)
foreach ($b in $chunks) { $fs.Write($b, 0, $b.Length) }
$fs.Close()
# ---- 自检: startxref 指向 xref, 且每个对象偏移处是 "N 0 obj" ----
$check = [System.IO.File]::ReadAllBytes($Pdf)
$text = [System.Text.Encoding]::ASCII.GetString($check)
$m = [regex]::Match($text, 'startxref\s+(\d+)')
if (-not $m.Success) { Write-Output ("FAIL: 无 startxref - " + $Pdf); return }
$xref = [int64]$m.Groups[1].Value
$ok = ($text.Substring($xref, 4) -eq 'xref')
$lines = $text.Substring($xref + 4).Split("`n")
# xref 段: 行0='xref', 行1='0 6', 行2=第0号自由条目, 行3..7=对象1..5
for ($i = 3; $i -lt 8; $i++) {
$entry = $lines[$i]
$objNum = $i - 2
if ($entry -match '^(\d{10}) 00000 n') {
$off2 = [int64]$entry.Substring(0, 10)
if ($text.Substring($off2, 10) -notmatch ('^' + $objNum + ' 0 obj')) { $ok = $false }
} else { $ok = $false }
}
if ($ok) { Write-Output ("OK: " + $Pdf + " (" + (($check.Length / 1KB).ToString('0') + " KB)")) }
else { Write-Output ("FAIL: xref 校验失败 - " + $Pdf) }

View File

@@ -0,0 +1,230 @@
# 欧霓博 SC 系列示例数据生成器STEP 变体文件 + params.csv
# 用法: powershell -NoProfile -ExecutionPolicy Bypass -File tools\gen-step.ps1
# 说明: 以极简封闭 B-rep 圆柱MANIFOLD_SOLID_BREP组合出简化气缸模型
# (后盖 + 缸体 + 前盖 + 活塞杆 4 个实体), 尺寸随缸径/行程参数变化。
param(
[string]$OutDir = (Join-Path $PSScriptRoot '..\onb-sc')
)
$inv = [System.Globalization.CultureInfo]::InvariantCulture
$stepDir = Join-Path $OutDir 'step'
$csvPath = Join-Path $OutDir 'params.csv'
New-Item -ItemType Directory -Force -Path $stepDir | Out-Null
function Format-Num([double]$v) { return $v.ToString('0.###', $inv) }
# 单个圆柱实体的实体行 (OCCT 兼容拓扑: 顶点在圆周上+全圆闭边+接缝边, 共 36 个实体)
# 约定: 圆心点 = (x, 0, z) 是仅有的 y=0 点 (供解析器识别); 顶点 = (x, r, z) 在 +Y 方向
function New-CylinderEntities([int]$id, [double]$x, [double]$y, [double]$z, [double]$r, [double]$len) {
$X = Format-Num $x
$Y = Format-Num $y
$Z = Format-Num $z
$ZT = Format-Num ($z + $len)
$R = Format-Num $r
$VY = Format-Num ($y + $r) # 顶点在圆周 +Y 处 (y=0 留给圆心供解析)
$e = @(
"#$($id)=DIRECTION('',(0.,0.,1.));", # 轴
"#$($id+1)=CARTESIAN_POINT('',($X,$Y,$Z));", # 底圆心
"#$($id+2)=CARTESIAN_POINT('',($X,$Y,$ZT));", # 顶圆心
"#$($id+3)=DIRECTION('',(1.,0.,0.));", # 参考方向
"#$($id+4)=AXIS2_PLACEMENT_3D('',#$($id+1),#$($id),#$($id+3));", # 底放置
"#$($id+5)=CARTESIAN_POINT('',($X,$VY,$Z));", # 底顶点(圆周上)
"#$($id+6)=CARTESIAN_POINT('',($X,$VY,$ZT));", # 顶顶点(圆周上)
"#$($id+7)=VERTEX_POINT('',#$($id+5));",
"#$($id+8)=VERTEX_POINT('',#$($id+6));",
"#$($id+9)=CIRCLE('',#$($id+4),$R);", # 底圆
"#$($id+10)=EDGE_CURVE('',#$($id+7),#$($id+7),#$($id+9),.T.);", # 底圆闭边
"#$($id+11)=CYLINDRICAL_SURFACE('',#$($id+4),$R);",
"#$($id+12)=PLANE('',#$($id+4));", # 底面
"#$($id+13)=DIRECTION('',(0.,0.,1.));",
"#$($id+14)=AXIS2_PLACEMENT_3D('',#$($id+2),#$($id+13),#$($id+3));", # 顶放置
"#$($id+15)=PLANE('',#$($id+14));", # 顶面
"#$($id+16)=CIRCLE('',#$($id+14),$R);", # 顶圆
"#$($id+17)=EDGE_CURVE('',#$($id+8),#$($id+8),#$($id+16),.T.);", # 顶圆闭边
"#$($id+18)=DIRECTION('',(0.,0.,1.));",
"#$($id+19)=VECTOR('',#$($id+18),1.);",
"#$($id+20)=LINE('',#$($id+5),#$($id+19));", # 接缝线
"#$($id+21)=EDGE_CURVE('',#$($id+7),#$($id+8),#$($id+20),.T.);", # 接缝边
"#$($id+22)=ORIENTED_EDGE('',*,*,#$($id+10),.T.);", # 底环
"#$($id+23)=ORIENTED_EDGE('',*,*,#$($id+17),.T.);", # 顶环
"#$($id+24)=ORIENTED_EDGE('',*,*,#$($id+21),.T.);", # 壁环: 缝上
"#$($id+25)=ORIENTED_EDGE('',*,*,#$($id+17),.T.);",
"#$($id+26)=ORIENTED_EDGE('',*,*,#$($id+21),.F.);", # 缝下
"#$($id+27)=ORIENTED_EDGE('',*,*,#$($id+10),.F.);",
"#$($id+28)=EDGE_LOOP('',(#$($id+22)));",
"#$($id+29)=EDGE_LOOP('',(#$($id+23)));",
"#$($id+30)=EDGE_LOOP('',(#$($id+24),#$($id+25),#$($id+26),#$($id+27)));",
"#$($id+31)=ADVANCED_FACE('',(#$($id+30)),#$($id+11),.T.);", # 柱壁
"#$($id+32)=ADVANCED_FACE('',(#$($id+28)),#$($id+12),.F.);", # 底面(法线朝下)
"#$($id+33)=ADVANCED_FACE('',(#$($id+29)),#$($id+15),.T.);", # 顶面(法线朝上)
"#$($id+34)=CLOSED_SHELL('',(#$($id+31),#$($id+32),#$($id+33)));",
"#$($id+35)=MANIFOLD_SOLID_BREP('',#$($id+34));"
)
return $e
}
# 尾部共享实体: 每个实体注入 STYLED_ITEM 颜色 (occt-import-js 对无颜色模型会崩)
# 从 $id 开始: 每实体 7 个颜色实体 + 14 个尾部实体
function New-TailEntities([int]$id, [int[]]$solidIds) {
$t = New-Object System.Collections.Generic.List[string]
$styled = New-Object System.Collections.Generic.List[string]
$c = $id
foreach ($s in $solidIds) {
$t.Add("#$c=COLOUR_RGB('',0.65,0.72,0.85);")
$t.Add("#$($c+1)=FILL_AREA_STYLE_COLOUR('',#$c);")
$t.Add("#$($c+2)=SURFACE_STYLE_FILL_AREA(#$($c+1));")
$t.Add("#$($c+3)=SURFACE_SIDE_STYLE('',(#$($c+2)));")
$t.Add("#$($c+4)=SURFACE_STYLE_USAGE(.BOTH.,#$($c+3));")
$t.Add("#$($c+5)=PRESENTATION_STYLE_ASSIGNMENT((#$($c+4)));")
$t.Add("#$($c+6)=STYLED_ITEM('',(#$($c+5)),#$s);")
$styled.Add("#$($c+6)")
$c += 7
}
$items = (($solidIds | ForEach-Object { "#$_" }) + $styled) -join ','
$t.Add("#$c=SHAPE_REPRESENTATION('',($items),#$($c+1));")
$t.Add("#$($c+1)=(GEOMETRIC_REPRESENTATION_CONTEXT(3)GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((#$($c+2)))GLOBAL_UNIT_ASSIGNED_CONTEXT((#$($c+3),#$($c+4),#$($c+5)))REPRESENTATION_CONTEXT('',''));")
$t.Add("#$($c+2)=UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(1.E-06),#$($c+3),'');")
$t.Add("#$($c+3)=(LENGTH_UNIT()NAMED_UNIT(*)SI_UNIT(.MILLI.,.METRE.));")
$t.Add("#$($c+4)=(NAMED_UNIT(*)PLANE_ANGLE_UNIT()SI_UNIT($,.RADIAN.));")
$t.Add("#$($c+5)=(NAMED_UNIT(*)SI_UNIT($,.STERADIAN.)SOLID_ANGLE_UNIT());")
$t.Add("#$($c+6)=PRODUCT('OUNIBO_SC','OUNIBO_SC','',(#$($c+7)));")
$t.Add("#$($c+7)=PRODUCT_CONTEXT('',#$($c+8),'mechanical');")
$t.Add("#$($c+8)=APPLICATION_CONTEXT('configuration controlled 3d designs of mechanical parts and assemblies');")
$t.Add("#$($c+9)=PRODUCT_DEFINITION_FORMATION('','',#$($c+6));")
$t.Add("#$($c+10)=PRODUCT_DEFINITION('design','',#$($c+9),#$($c+11));")
$t.Add("#$($c+11)=PRODUCT_DEFINITION_CONTEXT('part definition',#$($c+8),'design');")
$t.Add("#$($c+12)=PRODUCT_DEFINITION_SHAPE('','',#$($c+10));")
$t.Add("#$($c+13)=SHAPE_DEFINITION_REPRESENTATION(#$($c+12),#$c);")
return $t
}
# ---- 型号与规则定义 (见 coding-rules.md) ----
# 缸径 16: 行程仅 25/50, 无磁石; 磁石 S 要求缸径>=25; 中摆 CB 要求行程<=75
$bores = @(16, 25, 32, 40)
$strokesAll = @(25, 50, 75, 100)
$mountsAll = @('FA', 'LB', 'CB')
$encNoBom = New-Object System.Text.UTF8Encoding($false)
$encBom = New-Object System.Text.UTF8Encoding($true)
$csvLines = New-Object System.Collections.Generic.List[string]
$csvLines.Add('model_code,bore,stroke,magnet,mount,step_file')
$count = 0
foreach ($bore in $bores) {
$strokes = if ($bore -eq 16) { @(25, 50) } else { $strokesAll }
$magnets = if ($bore -eq 16) { @('') } else { @('', 'S') }
foreach ($stroke in $strokes) {
$mounts = if ($stroke -gt 75) { @('FA', 'LB') } else { $mountsAll }
foreach ($magnet in $magnets) {
foreach ($mount in $mounts) {
$model = "SC$bore`x$stroke$magnet-$mount"
$stepFile = "$model.step"
# ---- 简化气缸尺寸 (mm): 后盖 + 缸体 + 前盖 + 活塞杆, 沿 Z 轴 ----
$bodyR = $bore / 2.0 + 2.0 # 缸体外径
$bodyLen = $stroke + 60.0 # 缸体长度(随行程)
$capR = $bore / 2.0 + 5.0 # 端盖外径
$capLen = 8.0 # 后盖厚度
$fcapLen = 10.0 # 前盖厚度
$rodR = [Math]::Max($bore / 5.0, 3.0)
$rodBot = $bodyLen - 14.0 # 活塞杆埋入缸体
$rodLen = $stroke + 34.0
$lines = New-Object System.Collections.Generic.List[string]
$lines.Add('ISO-10303-21;')
$lines.Add('HEADER;')
$lines.Add("FILE_DESCRIPTION(('OUNIBO SC series sample part $model'),'2;1');")
$lines.Add("FILE_NAME('$stepFile','2026-06-02T00:00:00',('OUNIBO'),('OUNIBO'),'','','');")
$lines.Add("FILE_SCHEMA(('CONFIG_CONTROL_DESIGN'));")
$lines.Add('ENDSEC;')
$lines.Add('DATA;')
$ids = New-Object System.Collections.Generic.List[int]
$nextId = 1
$cyls = New-Object System.Collections.Generic.List[object]
$cyls.Add(@{ z = -$capLen; r = $capR; len = $capLen }) # 后盖
$cyls.Add(@{ z = 0.0; r = $bodyR; len = $bodyLen }) # 缸体
$cyls.Add(@{ z = $bodyLen; r = $capR; len = $fcapLen }) # 前盖
$cyls.Add(@{ z = $rodBot; r = $rodR; len = $rodLen }) # 活塞杆
foreach ($cyl in $cyls) {
[string[]]$entities = New-CylinderEntities -id $nextId -x 0 -y 0 -z $cyl['z'] -r $cyl['r'] -len $cyl['len']
$lines.AddRange($entities)
$ids.Add($nextId + 35) # MANIFOLD_SOLID_BREP 实体号
$nextId += 36
}
[string[]]$tail = New-TailEntities -id $nextId -solidIds $ids.ToArray()
$lines.AddRange($tail)
$lines.Add('ENDSEC;')
$lines.Add('END-ISO-10303-21;')
[System.IO.File]::WriteAllLines((Join-Path $stepDir $stepFile), $lines.ToArray(), $encNoBom)
$csvLines.Add("$model,$bore,$stroke,$magnet,$mount,$stepFile")
$count++
}
}
}
}
# ---- MAQ 迷你气缸系列 (多系列演示): 缸径 6/10/16, 行程 10/20/30/40 ----
# 规则: 缸径 6 仅行程 10/20 且无磁石; 安装仅 FA/LB
$csvLinesM = New-Object System.Collections.Generic.List[string]
$csvLinesM.Add('model_code,bore,stroke,magnet,mount,step_file')
$countM = 0
foreach ($bore in @(6, 10, 16)) {
$strokes = if ($bore -eq 6) { @(10, 20) } else { @(10, 20, 30, 40) }
$magnets = if ($bore -eq 6) { @('') } else { @('', 'S') }
foreach ($stroke in $strokes) {
foreach ($magnet in $magnets) {
foreach ($mount in @('FA', 'LB')) {
$model = "MAQ$bore`x$stroke$magnet-$mount"
$stepFile = "$model.step"
$bodyR = $bore / 2.0 + 1.5
$bodyLen = $stroke + 40.0
$capR = $bore / 2.0 + 4.0
$capLen = 6.0
$fcapLen = 8.0
$rodR = [Math]::Max($bore / 5.0, 2.0)
$rodBot = $bodyLen - 10.0
$rodLen = $stroke + 24.0
$lines = New-Object System.Collections.Generic.List[string]
$lines.Add('ISO-10303-21;')
$lines.Add('HEADER;')
$lines.Add("FILE_DESCRIPTION(('OUNIBO MAQ series sample part $model'),'2;1');")
$lines.Add("FILE_NAME('$stepFile','2026-06-02T00:00:00',('OUNIBO'),('OUNIBO'),'','','');")
$lines.Add("FILE_SCHEMA(('CONFIG_CONTROL_DESIGN'));")
$lines.Add('ENDSEC;')
$lines.Add('DATA;')
$ids = New-Object System.Collections.Generic.List[int]
$nextId = 1
$cyls = New-Object System.Collections.Generic.List[object]
$cyls.Add(@{ z = -$capLen; r = $capR; len = $capLen })
$cyls.Add(@{ z = 0.0; r = $bodyR; len = $bodyLen })
$cyls.Add(@{ z = $bodyLen; r = $capR; len = $fcapLen })
$cyls.Add(@{ z = $rodBot; r = $rodR; len = $rodLen })
foreach ($cyl in $cyls) {
[string[]]$entities = New-CylinderEntities -id $nextId -x 0 -y 0 -z $cyl['z'] -r $cyl['r'] -len $cyl['len']
$lines.AddRange($entities)
$ids.Add($nextId + 35)
$nextId += 36
}
[string[]]$tail = New-TailEntities -id $nextId -solidIds $ids.ToArray()
$lines.AddRange($tail)
$lines.Add('ENDSEC;')
$lines.Add('END-ISO-10303-21;')
[System.IO.File]::WriteAllLines((Join-Path $stepDir $stepFile), $lines.ToArray(), $encNoBom)
$csvLinesM.Add("$model,$bore,$stroke,$magnet,$mount,$stepFile")
$countM++
}
}
}
}
[System.IO.File]::WriteAllLines($csvPath, $csvLines.ToArray(), $encBom)
[System.IO.File]::WriteAllLines((Join-Path $OutDir 'params_maq.csv'), $csvLinesM.ToArray(), $encBom)
Write-Output "生成完成: SC $count 个 + MAQ $countM 个 STEP 文件 -> $stepDir"
Write-Output "参数表: $csvPath / params_maq.csv"

View File

@@ -0,0 +1,103 @@
# 把 params.csv 生成一份真正的 .xlsx (最小 OOXML 结构, 免依赖 Excel)
# 用法: powershell -NoProfile -ExecutionPolicy Bypass -File tools\gen-xlsx.ps1
# 说明: 仅供查看格式参考; Builder 导入用 CSV (Excel 里另存为 CSV 即可)。
param(
[string]$Csv = (Join-Path $PSScriptRoot '..\onb-sc\params.csv'),
[string]$Out = (Join-Path $PSScriptRoot '..\onb-sc\params.xlsx')
)
Add-Type -AssemblyName System.IO.Compression
Add-Type -AssemblyName System.IO.Compression.FileSystem
function Xml-Esc([string]$s) {
return $s.Replace('&', '&amp;').Replace('<', '&lt;').Replace('>', '&gt;').Replace('"', '&quot;')
}
# 列号 → 列字母 (A..Z, AA..)
function Col-Name([int]$i) {
$name = ''
$n = $i + 1
while ($n -gt 0) {
$r = ($n - 1) % 26
$name = [char](65 + $r) + $name
$n = [int](($n - 1) / 26)
}
return $name
}
$rows = Get-Content $Csv | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }
if ($rows.Count -lt 1) { Write-Output "CSV 为空"; exit 1 }
# ---- sheet1.xml ----
$sb = New-Object System.Text.StringBuilder
[void]$sb.Append('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>')
[void]$sb.Append('<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetData>')
for ($r = 0; $r -lt $rows.Count; $r++) {
$cells = $rows[$r].Split(',')
[void]$sb.Append('<row r="' + ($r + 1) + '">')
for ($c = 0; $c -lt $cells.Count; $c++) {
$val = $cells[$c]
$ref = (Col-Name $c) + ($r + 1)
$num = 0.0
if ($r -gt 0 -and [double]::TryParse($val, [System.Globalization.NumberStyles]::Float, [System.Globalization.CultureInfo]::InvariantCulture, [ref]$num)) {
[void]$sb.Append('<c r="' + $ref + '"><v>' + $val + '</v></c>')
} else {
[void]$sb.Append('<c r="' + $ref + '" t="inlineStr"><is><t xml:space="preserve">' + (Xml-Esc $val) + '</t></is></c>')
}
}
[void]$sb.Append('</row>')
}
[void]$sb.Append('</sheetData></worksheet>')
$sheetXml = $sb.ToString()
# ---- 其余固定部件 ----
$contentTypes = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
'<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">' +
'<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>' +
'<Default Extension="xml" ContentType="application/xml"/>' +
'<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>' +
'<Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>' +
'</Types>'
$rels = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
'<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">' +
'<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>' +
'</Relationships>'
$workbook = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
'<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">' +
'<sheets><sheet name="SC系列" sheetId="1" r:id="rId1"/></sheets></workbook>'
$workbookRels = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
'<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">' +
'<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/>' +
'</Relationships>'
$enc = New-Object System.Text.UTF8Encoding($false)
# 手动 CreateEntry 控制条目名 (OOXML 规范要求正斜杠, CreateFromDirectory 会生成反斜杠)
if (Test-Path $Out) { Remove-Item $Out -Force }
$zip = [System.IO.Compression.ZipFile]::Open($Out, [System.IO.Compression.ZipArchiveMode]::Create)
function Add-Entry([string]$name, [string]$content) {
$e = $zip.CreateEntry($name)
$sw = New-Object System.IO.StreamWriter($e.Open(), $enc)
$sw.Write($content)
$sw.Close()
}
Add-Entry '[Content_Types].xml' $contentTypes
Add-Entry '_rels/.rels' $rels
Add-Entry 'xl/workbook.xml' $workbook
Add-Entry 'xl/_rels/workbook.xml.rels' $workbookRels
Add-Entry 'xl/worksheets/sheet1.xml' $sheetXml
$zip.Dispose()
# ---- 自检: 重新打开并校验 XML 结构 ----
$z = [System.IO.Compression.ZipFile]::OpenRead($Out)
$ok = $true
foreach ($part in @('[Content_Types].xml', '_rels/.rels', 'xl/workbook.xml', 'xl/_rels/workbook.xml.rels', 'xl/worksheets/sheet1.xml')) {
try {
$e = $z.GetEntry($part)
$sr = New-Object System.IO.StreamReader($e.Open())
$null = [xml]$sr.ReadToEnd()
$sr.Close()
} catch { Write-Output ("XML 无效: " + $part); $ok = $false }
}
$z.Dispose()
if ($ok) { Write-Output ("xlsx 生成并通过自检: " + $Out + " (" + $rows.Count + " 行)") }

View File

@@ -0,0 +1,58 @@
' 母模构建 (管线验证版): 模板部件 + STEP 导入真实几何 + 注入参数化表达式
' 用法: WorkingDirectory=母模目录, 目录内放 src.step (真实 STEP) 与 KC_master.prt (模板副本)
Option Strict Off
Imports System
Imports System.IO
Imports System.Text
Imports NXOpen
Imports NXOpen.UF
Module ImportMaster
Sub Main()
Dim lw As ListingWindow = Session.GetSession().ListingWindow
lw.Open()
Dim cur As String = Directory.GetCurrentDirectory()
Dim outPath As String = Path.Combine(cur, "KC_master.prt")
Dim srcStep As String = Path.Combine(cur, "src.step")
If Not File.Exists(outPath) OrElse Not File.Exists(srcStep) Then
lw.WriteLine("缺文件: " & outPath & " / " & srcStep)
Return
End If
Dim partLoadStatus1 As NXOpen.PartLoadStatus = Nothing
Session.GetSession().Parts.OpenBaseDisplay(outPath, partLoadStatus1)
Dim w As Part = Session.GetSession().Parts.Work
If w Is Nothing Then
lw.WriteLine("打开失败")
Return
End If
' 导入真实 STEP 到当前工作部件 (WorkPart; 前提: 模板副本必须可写, FileOpenFlag 打开源文件)
Dim imp As NXOpen.Step214Importer = Session.GetSession().DexManager.CreateStep214Importer()
imp.InputFile = srcStep
imp.ImportTo = NXOpen.Step214Importer.ImportToOption.WorkPart
imp.FileOpenFlag = True
imp.Commit()
imp.Destroy()
lw.WriteLine("STEP 已导入到工作部件")
Dim ufs As UFSession = UFSession.GetUFSession()
ufs.Modl.Update()
' 注入参数化表达式
Dim mm As NXOpen.Unit = w.UnitCollection.FindObject("MilliMeter")
If mm IsNot Nothing Then
w.Expressions.CreateExpressionWithUnit("Number", "bore=32", mm)
w.Expressions.CreateExpressionWithUnit("Number", "stroke=10", mm)
Else
w.Expressions.CreateExpression("Number", "bore=32")
w.Expressions.CreateExpression("Number", "stroke=10")
End If
w.Expressions.CreateExpression("Number", "magOn=0")
w.Expressions.CreateExpression("Number", "mountIdx=0")
lw.WriteLine("表达式已注入")
Dim pss As NXOpen.PartSaveStatus = w.Save(NXOpen.BasePart.SaveComponents.True, NXOpen.BasePart.CloseAfterSave.False)
If pss IsNot Nothing Then pss.Dispose()
lw.WriteLine("已保存: " & outPath)
End Sub
End Module

View File

@@ -0,0 +1,38 @@
# 实验: 给极简 STEP 注入 STYLED_ITEM 颜色实体 (测试 occt-import-js 对无颜色模型的兼容性)
# 用法: powershell -NoProfile -ExecutionPolicy Bypass -File tools\inject-colors.ps1 -In x.step -Out y.step
param([string]$In, [string]$Out)
$text = [System.IO.File]::ReadAllText($In)
$maxId = 0
foreach ($m in [regex]::Matches($text, '#(\d+)\s*=')) { $id = [int]$m.Groups[1].Value; if ($id -gt $maxId) { $maxId = $id } }
$next = $maxId + 1
# 收集实体 (保持文件顺序: 插入到 ENDSEC 前)
$solids = @()
foreach ($m in [regex]::Matches($text, '#(\d+)=MANIFOLD_SOLID_BREP')) { $solids += [int]$m.Groups[1].Value }
$newEntities = @()
$styledIds = @()
foreach ($s in $solids) {
$newEntities += "#$next=COLOUR_RGB('',0.65,0.72,0.85);"
$newEntities += "#$($next+1)=FILL_AREA_STYLE_COLOUR('',#$next);"
$newEntities += "#$($next+2)=SURFACE_STYLE_FILL_AREA(#$($next+1));"
$newEntities += "#$($next+3)=SURFACE_SIDE_STYLE('',(#$($next+2)));"
$newEntities += "#$($next+4)=SURFACE_STYLE_USAGE(.BOTH.,#$($next+3));"
$newEntities += "#$($next+5)=PRESENTATION_STYLE_ASSIGNMENT((#$($next+4)));"
$newEntities += "#$($next+6)=STYLED_ITEM('',(#$($next+5)),#$s);"
$styledIds += "#$($next+6)"
$next += 7
}
# 把 STYLED_ITEM 追加进 SHAPE_REPRESENTATION 的 items 列表
$shapeRe = [regex]::Match($text, '#\d+=SHAPE_REPRESENTATION\('''',\(([^)]*)\),#\d+\);')
if (-not $shapeRe.Success) { Write-Output "未找到 SHAPE_REPRESENTATION"; exit 1 }
$oldItems = $shapeRe.Groups[2].Value
$newItems = $oldItems + ',' + ($styledIds -join ',')
$text = $text.Remove($shapeRe.Groups[2].Index, $shapeRe.Groups[2].Length).Insert($shapeRe.Groups[2].Index, $newItems)
$insert = ($newEntities -join "`r`n") + "`r`n"
$ends = $text.IndexOf('ENDSEC;', $text.IndexOf('ENDSEC;') + 7) # 第二个 ENDSEC (DATA 结束)
$text = $text.Insert($ends, $insert)
[System.IO.File]::WriteAllText($Out, $text, (New-Object System.Text.UTF8Encoding($false)))
Write-Output ("已注入颜色: " + $solids.Count + " 个实体 → " + $Out)

View File

@@ -0,0 +1,122 @@
' =============================================================================
' NX Journal: 参数化母模 + 参数表 → 批量导出 GLB 轻量化网格 (供网页 3D 预览)
'
' 背景: 网页 3D 走"轻量网格"路线 (参照 CADENAS 服务器端轻量化思路, 但用 NX
' 离线预转换, 零服务器成本): 网页优先加载 GLB (快/稳/手机流畅),
' STEP 照旧提供下载。见 项目总览.md 待办 9。
'
' 使用方法:
' 1) 与 nx-batch-export.vb 相同: 母模用表达式驱动, 表达式名与 CSV 列名一致
' 2) 关键一步 (NX 2506): 菜单 工具→日记→录制 → 手动执行
' 文件→导出→Extended Reality (导出类型选 GLB, 单个二进制文件)
' → 停止录制 → 把录出的代码替换下方 ExportGlb 函数体
' (XR 导出的 API 与 DexBuilder 不同, 录出的代码就是你这台 NX 的真实接口,
' 这是 Siemens 官方推荐的获取方式; 不同小版本录出的代码可能不同,
' 所以模板不硬写该 API)
' 3) 工具→日记→播放 选本文件运行
'
' 输出: 每个变体一个 .glb 文件, 放到网页站点的 mesh/ 目录,
' app.js 优先用 three.js GLTFLoader 加载, 失败回退 STEP 查看器/Canvas。
'
' 注意: 中文注释需文件保存为 UTF-8 带 BOM (fix-bom.ps1 已处理)。
' =============================================================================
Option Strict Off
Imports System
Imports System.IO
Imports NXOpen
Imports NXOpen.UF
Module BatchGlbExport
Dim theSession As Session = Session.GetSession()
Dim workPart As Part = theSession.Parts.Work
Dim lw As ListingWindow = theSession.ListingWindow
Sub Main()
' ============================ CONFIG 区 ============================
Dim csvPath As String = "D:\catalog\params.csv" ' 参数表 (与 STEP 导出同一张表)
Dim outDir As String = "D:\catalog\glb_out\" ' GLB 输出目录
' 表达式名 → CSV 列名 映射 (母模表达式名 : 参数表列名)
Dim exprMap As New System.Collections.Generic.Dictionary(Of String, String)()
exprMap.Add("bore", "bore")
exprMap.Add("stroke", "stroke")
' ================================================================
lw.Open()
lw.WriteLine("GLB 批量导出开始: " & csvPath)
Directory.CreateDirectory(outDir)
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 列 (用于推导 GLB 文件名)!")
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
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()
' GLB 文件名与 STEP 同名不同后缀 (网页端按型号拼 glb 路径)
Dim glbFile As String = Path.ChangeExtension(c(colStep).Trim(), ".glb")
lw.Write("[" & i & "/" & (lines.Length - 1) & "] " & model & " ... ")
Try
' ---- 1) 设置表达式 ----
For Each kv As KeyValuePair(Of String, String) In exprMap
workPart.Expressions.Edit(kv.Key, c(colOf(kv.Key)).Trim())
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) 导出 GLB (用录制日记替换此函数体) ----
ExportGlb(Path.Combine(outDir, glbFile))
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 & " 失败, 输出目录: " & outDir)
End Sub
' GLB 导出: 用【工具→日记→录制】录一次 文件→导出→Extended Reality 后,
' 把录出的代码粘进这里, 并把其中写死的输出路径改成参数 fileName。
Sub ExportGlb(fileName As String)
lw.WriteLine("ExportGlb 未实现: 请按文件头说明录制导出操作并替换本函数")
Throw New Exception("ExportGlb 未实现")
End Sub
End Module

View File

@@ -0,0 +1,215 @@
' =============================================================================
' 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

View File

@@ -0,0 +1,20 @@
# 用真实解析器定位 .ps1 语法错误, 并打印出错行上下文
param([string]$File = (Join-Path $PSScriptRoot 'gen-step.ps1'))
$tokens = $null
$errors = $null
[System.Management.Automation.Language.Parser]::ParseFile($File, [ref]$tokens, [ref]$errors) | Out-Null
$lines = [System.IO.File]::ReadAllLines($File)
Write-Output ("总行数: " + $lines.Count)
if ($errors.Count -eq 0) {
Write-Output "语法 OK"
} else {
foreach ($e in $errors) {
$ln = $e.Extent.StartLineNumber
Write-Output ("--- 错误 行{0} 列{1}: {2}" -f $ln, $e.Extent.StartColumnNumber, $e.Message)
for ($i = $ln - 3; $i -le ($ln + 1); $i++) {
if ($i -ge 1 -and $i -le $lines.Count) {
Write-Output ("{0,4} | {1}" -f $i, $lines[$i - 1])
}
}
}
}

View File

@@ -0,0 +1,52 @@
' =============================================================================
' NX Journal: NX 2506 API 反射探针 (一次性诊断, 结果写当前工作目录 probe.txt)
' 摸清: Dex/Stl/Update/Translator 相关类型与成员、CloseAll 签名、Save 签名
' =============================================================================
Option Strict Off
Imports System
Imports System.IO
Imports System.Text
Imports NXOpen
Imports NXOpen.UF
Module ProbeApi
Sub Main()
Dim out As New StringBuilder()
Dim asm As System.Reflection.Assembly = GetType(Session).Assembly
out.AppendLine("=== 类型 (Dex/Stl/Update/Translator/Step/Iges) ===")
For Each t As System.Type In asm.GetTypes()
Dim n As String = t.FullName
If n.IndexOf("Dex", StringComparison.OrdinalIgnoreCase) >= 0 OrElse
n.IndexOf("Stl", StringComparison.OrdinalIgnoreCase) >= 0 OrElse
n.IndexOf("Update", StringComparison.OrdinalIgnoreCase) >= 0 OrElse
n.IndexOf("Translator", StringComparison.OrdinalIgnoreCase) >= 0 Then
out.AppendLine("T: " & n)
End If
Next
out.AppendLine("=== Session 成员 (Dex/Update/Export/Create) ===")
For Each mi As System.Reflection.MemberInfo In GetType(Session).GetMembers()
If mi.Name.IndexOf("Dex", StringComparison.OrdinalIgnoreCase) >= 0 OrElse
mi.Name.IndexOf("Update", StringComparison.OrdinalIgnoreCase) >= 0 OrElse
mi.Name.IndexOf("Export", StringComparison.OrdinalIgnoreCase) >= 0 Then
out.AppendLine("S: " & mi.MemberType & " " & mi.Name)
End If
Next
out.AppendLine("=== Part 成员 (Dex/Stl/Export) ===")
For Each mi As System.Reflection.MemberInfo In GetType(Part).GetMembers()
If mi.Name.IndexOf("Dex", StringComparison.OrdinalIgnoreCase) >= 0 OrElse
mi.Name.IndexOf("Stl", StringComparison.OrdinalIgnoreCase) >= 0 OrElse
mi.Name.IndexOf("Export", StringComparison.OrdinalIgnoreCase) >= 0 Then
out.AppendLine("P: " & mi.MemberType & " " & mi.Name)
End If
Next
out.AppendLine("=== PartCollection.CloseAll 重载 ===")
For Each mi As System.Reflection.MethodInfo In GetType(PartCollection).GetMethods()
If mi.Name = "CloseAll" Then out.AppendLine("CA: " & mi.ToString())
Next
out.AppendLine("=== Part.Save 重载 ===")
For Each mi As System.Reflection.MethodInfo In GetType(Part).GetMethods()
If mi.Name = "Save" OrElse mi.Name = "SaveAs" Then out.AppendLine("SV: " & mi.ToString())
Next
File.WriteAllText(Path.Combine(Directory.GetCurrentDirectory(), "probe.txt"), out.ToString(), Encoding.UTF8)
End Sub
End Module

View File

@@ -0,0 +1,59 @@
' NX 2506 API 反射探针 2 (稳健版): 跨全部已加载程序集查找类型成员与枚举
Option Strict Off
Imports System
Imports System.IO
Imports System.Text
Imports NXOpen
Imports NXOpen.UF
Module ProbeApi2
Function FindType(n As String) As System.Type
' 探针1已验证: NXOpen 类型都在 GetType(Session).Assembly 里 (运行时程序集名不含 NXOpen)
Dim t As System.Type = GetType(Session).Assembly.GetType(n, False)
If t IsNot Nothing Then Return t
For Each a As System.Reflection.Assembly In AppDomain.CurrentDomain.GetAssemblies()
t = a.GetType(n, False)
If t IsNot Nothing Then Return t
Next
Return Nothing
End Function
Sub Dump(out As StringBuilder, prefix As String, typeName As String)
Dim t As System.Type = FindType(typeName)
If t Is Nothing Then
out.AppendLine(prefix & " [类型未找到: " & typeName & "]")
Return
End If
out.AppendLine("=== " & typeName & " 成员 ===")
For Each mi As System.Reflection.MemberInfo In t.GetMembers()
If mi.MemberType = System.Reflection.MemberTypes.Method OrElse
mi.MemberType = System.Reflection.MemberTypes.Property Then
out.AppendLine(prefix & ": " & mi.Name)
End If
Next
For Each nt As System.Type In t.GetNestedTypes()
out.AppendLine(prefix & " NESTED: " & nt.Name)
For Each f As System.Reflection.FieldInfo In nt.GetFields()
If f.IsStatic Then out.AppendLine(prefix & " = " & f.Name)
Next
Next
End Sub
Sub Main()
Dim out As New StringBuilder()
Dump(out, "DM", "NXOpen.DexManager")
Dump(out, "DB", "NXOpen.DexBuilder")
Dump(out, "ST", "NXOpen.STLCreator")
Dump(out, "PS", "NXOpen.ParasolidExporter")
Dump(out, "UM", "NXOpen.UpdateManager")
Dump(out, "UP", "NXOpen.Update")
out.AppendLine("=== 全部已加载程序集 ===")
For Each a As System.Reflection.Assembly In AppDomain.CurrentDomain.GetAssemblies()
out.AppendLine("ASM: " & a.FullName)
Next
out.AppendLine("=== Session 所在程序集 ===")
out.AppendLine("SES: " & GetType(Session).Assembly.FullName)
File.WriteAllText(Path.Combine(Directory.GetCurrentDirectory(), "probe2.txt"), out.ToString(), Encoding.UTF8)
End Sub
End Module

View File

@@ -0,0 +1,70 @@
' NX 2506 API 反射探针 3: StepCreator / IgesCreator / Part.CreateUpdate 成员
Option Strict Off
Imports System
Imports System.IO
Imports System.Text
Imports NXOpen
Imports NXOpen.UF
Module ProbeApi3
Function FindType(n As String) As System.Type
Dim t As System.Type = GetType(Session).Assembly.GetType(n, False)
If t IsNot Nothing Then Return t
For Each a As System.Reflection.Assembly In AppDomain.CurrentDomain.GetAssemblies()
t = a.GetType(n, False)
If t IsNot Nothing Then Return t
Next
Return Nothing
End Function
Sub Dump(out As StringBuilder, prefix As String, typeName As String)
Dim t As System.Type = FindType(typeName)
If t Is Nothing Then
out.AppendLine(prefix & " [类型未找到: " & typeName & "]")
Return
End If
out.AppendLine("=== " & typeName & " ===")
For Each mi As System.Reflection.MemberInfo In t.GetMembers()
If mi.MemberType = System.Reflection.MemberTypes.Method OrElse
mi.MemberType = System.Reflection.MemberTypes.Property Then
out.AppendLine(prefix & ": " & mi.Name)
End If
Next
For Each nt As System.Type In t.GetNestedTypes()
out.AppendLine(prefix & " NESTED: " & nt.Name)
For Each f As System.Reflection.FieldInfo In nt.GetFields()
If f.IsStatic Then out.AppendLine(prefix & " = " & f.Name)
Next
Next
End Sub
Sub Main()
Dim out As New StringBuilder()
Dump(out, "SC", "NXOpen.StepCreator")
Dump(out, "IC", "NXOpen.IgesCreator")
Dump(out, "EX", "NXOpen.UpdateSession")
out.AppendLine("=== Part 成员 (Update/Create) ===")
For Each mi As System.Reflection.MemberInfo In GetType(Part).GetMembers()
If mi.Name.IndexOf("Update", StringComparison.OrdinalIgnoreCase) >= 0 OrElse
mi.Name.IndexOf("Create", StringComparison.OrdinalIgnoreCase) >= 0 Then
out.AppendLine("PT: " & mi.MemberType & " " & mi.Name)
End If
Next
out.AppendLine("=== PartCollection 成员 (Update/Create/New) ===")
For Each mi As System.Reflection.MemberInfo In GetType(PartCollection).GetMembers()
If mi.Name.IndexOf("Update", StringComparison.OrdinalIgnoreCase) >= 0 OrElse
mi.Name.IndexOf("New", StringComparison.OrdinalIgnoreCase) >= 0 Then
out.AppendLine("PC: " & mi.MemberType & " " & mi.Name)
End If
Next
out.AppendLine("=== UFModl 成员 (Update/Cyl) ===")
For Each mi As System.Reflection.MemberInfo In GetType(UFModl).GetMembers()
If mi.Name.IndexOf("Update", StringComparison.OrdinalIgnoreCase) >= 0 OrElse
mi.Name.IndexOf("Cyl", StringComparison.OrdinalIgnoreCase) >= 0 Then
out.AppendLine("UF: " & mi.MemberType & " " & mi.Name)
End If
Next
File.WriteAllText(Path.Combine(Directory.GetCurrentDirectory(), "probe3.txt"), out.ToString(), Encoding.UTF8)
End Sub
End Module

View File

@@ -0,0 +1,35 @@
' 探针 4: ExpressionCollection 创建/编辑方法签名
Option Strict Off
Imports System
Imports System.IO
Imports System.Text
Imports NXOpen
Imports NXOpen.UF
Module ProbeApi4
Sub Main()
Dim sb As New StringBuilder()
Dim outPath As String = Path.Combine(Directory.GetCurrentDirectory(), "KC_master.prt")
Dim partLoadStatus1 As NXOpen.PartLoadStatus = Nothing
Session.GetSession().Parts.OpenBaseDisplay(outPath, partLoadStatus1)
Dim w As Part = Session.GetSession().Parts.Work
sb.AppendLine("Work=" & (If(w Is Nothing, "NULL", w.FullPath)))
If w IsNot Nothing Then
Dim ec As System.Type = w.Expressions.GetType()
sb.AppendLine("ExpressionCollection type: " & ec.FullName)
For Each mi As System.Reflection.MethodInfo In ec.GetMethods()
If mi.Name.IndexOf("Create", StringComparison.OrdinalIgnoreCase) >= 0 OrElse
mi.Name.IndexOf("Edit", StringComparison.OrdinalIgnoreCase) >= 0 Then
sb.AppendLine("EC: " & mi.ToString())
End If
Next
Dim uc As System.Type = w.UnitCollection.GetType()
For Each mi As System.Reflection.MethodInfo In uc.GetMethods()
If mi.Name.IndexOf("Find", StringComparison.OrdinalIgnoreCase) >= 0 Then
sb.AppendLine("UC: " & mi.ToString())
End If
Next
End If
File.WriteAllText(Path.Combine(Directory.GetCurrentDirectory(), "probe4.txt"), sb.ToString(), Encoding.UTF8)
End Sub
End Module

View File

@@ -0,0 +1,51 @@
' 探针 5: ReferenceSet 相关类型/成员
Option Strict Off
Imports System
Imports System.IO
Imports System.Text
Imports NXOpen
Imports NXOpen.UF
Module ProbeApi5
Function FindType(n As String) As System.Type
Dim t As System.Type = GetType(Session).Assembly.GetType(n, False)
If t IsNot Nothing Then Return t
For Each a As System.Reflection.Assembly In AppDomain.CurrentDomain.GetAssemblies()
t = a.GetType(n, False)
If t IsNot Nothing Then Return t
Next
Return Nothing
End Function
Sub Main()
Dim sb As New StringBuilder()
Dim asm As System.Reflection.Assembly = GetType(Session).Assembly
sb.AppendLine("=== 含 ReferenceSet 的类型 ===")
For Each t As System.Type In asm.GetTypes()
If t.FullName.IndexOf("ReferenceSet", StringComparison.OrdinalIgnoreCase) >= 0 Then sb.AppendLine("T: " & t.FullName)
Next
sb.AppendLine("=== Part 成员 (Reference) ===")
For Each mi As System.Reflection.MemberInfo In GetType(Part).GetMembers()
If mi.Name.IndexOf("Reference", StringComparison.OrdinalIgnoreCase) >= 0 Then sb.AppendLine("P: " & mi.MemberType & " " & mi.Name)
Next
sb.AppendLine("=== BasePart 成员 (Reference) ===")
For Each mi As System.Reflection.MemberInfo In GetType(BasePart).GetMembers()
If mi.Name.IndexOf("Reference", StringComparison.OrdinalIgnoreCase) >= 0 Then sb.AppendLine("B: " & mi.MemberType & " " & mi.Name)
Next
Dim rsT As System.Type = FindType("NXOpen.ReferenceSet")
If rsT IsNot Nothing Then
sb.AppendLine("=== NXOpen.ReferenceSet 方法 ===")
For Each mi As System.Reflection.MethodInfo In rsT.GetMethods()
If mi.DeclaringType Is rsT Then sb.AppendLine("RS: " & mi.Name)
Next
End If
Dim rscT As System.Type = FindType("NXOpen.Assemblies.ReferenceSetCollection")
If rscT IsNot Nothing Then
sb.AppendLine("=== Assemblies.ReferenceSetCollection 方法 ===")
For Each mi As System.Reflection.MethodInfo In rscT.GetMethods()
If mi.DeclaringType Is rscT Then sb.AppendLine("RSC: " & mi.Name)
Next
End If
File.WriteAllText(Path.Combine(Directory.GetCurrentDirectory(), "probe5.txt"), sb.ToString(), Encoding.UTF8)
End Sub
End Module

View File

@@ -0,0 +1,33 @@
' 探针 6: Step214Importer 成员
Option Strict Off
Imports System
Imports System.IO
Imports System.Text
Imports NXOpen
Imports NXOpen.UF
Module ProbeApi6
Sub Main()
Dim sb As New StringBuilder()
Dim t As System.Type = GetType(Session).Assembly.GetType("NXOpen.Step214Importer", False)
If t Is Nothing Then
sb.AppendLine("Step214Importer 未找到; 列出 Step 相关类型:")
For Each tt As System.Type In GetType(Session).Assembly.GetTypes()
If tt.FullName.IndexOf("Step", StringComparison.OrdinalIgnoreCase) >= 0 Then sb.AppendLine("T: " & tt.FullName)
Next
Else
For Each mi As System.Reflection.MemberInfo In t.GetMembers()
If mi.MemberType = System.Reflection.MemberTypes.Method OrElse mi.MemberType = System.Reflection.MemberTypes.Property Then
sb.AppendLine("SI: " & mi.MemberType & " " & mi.Name)
End If
Next
For Each nt As System.Type In t.GetNestedTypes()
sb.AppendLine("SI NESTED: " & nt.Name)
For Each f As System.Reflection.FieldInfo In nt.GetFields()
If f.IsStatic Then sb.AppendLine(" = " & f.Name)
Next
Next
End If
File.WriteAllText(Path.Combine(Directory.GetCurrentDirectory(), "probe6.txt"), sb.ToString(), Encoding.UTF8)
End Sub
End Module

View File

@@ -0,0 +1,72 @@
' =============================================================================
' NX Journal: probe how to SET the display part + locate DexManager (NX 2506)
'
' Last test failed: Parts.Display is ReadOnly. Need to find the correct API to
' set the display part (so the STEP translator's DisplayPart mode can see the
' solids in batch mode). Also re-confirm where DexManager lives (Session vs
' Part vs BasePart) to know which export API to use.
'
' Dumps (reflection only, no risky calls):
' Session members containing "Display" / "Dex"
' PartCollection members containing "Display" / "Set" / "Work"
' Part members containing "Display" / "Dex"
' BasePart members containing "Display" / "Dex"
'
' Run: WorkingDirectory = catalog-masters\KC; run_journal.exe <this file>
' Result: <cwd>\probe-display-api.txt
'
' Note: English comments only (avoids GBK/BOM mojibake on no-BOM .vb files).
' =============================================================================
Option Strict Off
Imports System
Imports System.IO
Imports System.Text
Imports NXOpen
Imports NXOpen.UF
Module ProbeDisplayApi
Sub DumpMembers(sb As StringBuilder, prefix As String, t As System.Type, filter As String)
For Each mi As System.Reflection.MemberInfo In t.GetMembers()
If filter.Length = 0 OrElse mi.Name.IndexOf(filter, StringComparison.OrdinalIgnoreCase) >= 0 Then
If mi.MemberType = System.Reflection.MemberTypes.Method OrElse
mi.MemberType = System.Reflection.MemberTypes.Property Then
sb.AppendLine(prefix & ": " & mi.MemberType & " " & mi.Name)
End If
End If
Next
End Sub
Sub Main()
Dim sb As New StringBuilder()
sb.AppendLine("=== Session members (Display) ===")
DumpMembers(sb, "S", GetType(Session), "Display")
sb.AppendLine("=== Session members (Dex) ===")
DumpMembers(sb, "S", GetType(Session), "Dex")
sb.AppendLine("=== PartCollection members (Display) ===")
DumpMembers(sb, "PC", GetType(PartCollection), "Display")
sb.AppendLine("=== PartCollection members (Set) ===")
DumpMembers(sb, "PC", GetType(PartCollection), "Set")
sb.AppendLine("=== PartCollection members (Work) ===")
DumpMembers(sb, "PC", GetType(PartCollection), "Work")
sb.AppendLine("=== Part members (Display) ===")
DumpMembers(sb, "P", GetType(Part), "Display")
sb.AppendLine("=== Part members (Dex) ===")
DumpMembers(sb, "P", GetType(Part), "Dex")
sb.AppendLine("=== BasePart members (Display) ===")
DumpMembers(sb, "B", GetType(BasePart), "Display")
sb.AppendLine("=== BasePart members (Dex) ===")
DumpMembers(sb, "B", GetType(BasePart), "Dex")
' Also: does BasePart have a SetDisplay-style method?
sb.AppendLine("=== BasePart members (Set) ===")
DumpMembers(sb, "B", GetType(BasePart), "Set")
File.WriteAllText(Path.Combine(Directory.GetCurrentDirectory(), "probe-display-api.txt"), sb.ToString(), New UTF8Encoding(True))
End Sub
End Module

View File

@@ -0,0 +1,84 @@
' =============================================================================
' NX Journal: probe SelectionBlock / ScCollector API (NX 2506)
'
' Purpose: StepCreator has an ExportSelectionBlock property. If batch mode has
' no "DisplayPart", we may be able to export by EXPLICITLY selecting the
' bodies into the ExportSelectionBlock. This probe dumps the exact API so the
' next script can use it correctly (compile-safe).
'
' Dumps:
' 1. Type of StepCreator.ExportSelectionBlock
' 2. Members of that type
' 3. Members of NXOpen.SelectionBlock, NXOpen.ScCollector (if found)
' 4. All types whose name contains "Selection" or "Collector"
'
' Run: WorkingDirectory = catalog-masters\KC; run_journal.exe <this file>
' Result: <cwd>\probe-selection.txt
'
' Note: English comments only (avoids GBK/BOM mojibake on no-BOM .vb files).
' =============================================================================
Option Strict Off
Imports System
Imports System.IO
Imports System.Text
Imports NXOpen
Imports NXOpen.UF
Module ProbeSelection
Function FindType(n As String) As System.Type
Dim t As System.Type = GetType(Session).Assembly.GetType(n, False)
If t IsNot Nothing Then Return t
For Each a As System.Reflection.Assembly In AppDomain.CurrentDomain.GetAssemblies()
t = a.GetType(n, False)
If t IsNot Nothing Then Return t
Next
Return Nothing
End Function
Sub DumpMembers(sb As StringBuilder, prefix As String, t As System.Type)
If t Is Nothing Then
sb.AppendLine(prefix & " [NULL]")
Return
End If
sb.AppendLine("=== " & t.FullName & " ===")
For Each mi As System.Reflection.MemberInfo In t.GetMembers()
If mi.MemberType = System.Reflection.MemberTypes.Method OrElse
mi.MemberType = System.Reflection.MemberTypes.Property Then
sb.AppendLine(prefix & ": " & mi.MemberType & " " & mi.Name)
End If
Next
End Sub
Sub Main()
Dim sb As New StringBuilder()
' 1. Type of StepCreator.ExportSelectionBlock
Dim scT As System.Type = GetType(NXOpen.StepCreator)
Dim esb As System.Reflection.PropertyInfo = scT.GetProperty("ExportSelectionBlock")
If esb IsNot Nothing Then
sb.AppendLine("StepCreator.ExportSelectionBlock type = " & esb.PropertyType.FullName)
DumpMembers(sb, "ESB", esb.PropertyType)
Else
sb.AppendLine("StepCreator.ExportSelectionBlock NOT FOUND")
End If
' 2. SelectionBlock / ScCollector
DumpMembers(sb, "SELB", FindType("NXOpen.SelectionBlock"))
DumpMembers(sb, "SCCO", FindType("NXOpen.ScCollector"))
DumpMembers(sb, "SEL", FindType("NXOpen.Selection"))
' 3. All types containing Selection / Collector
sb.AppendLine("=== types containing Selection/Collector ===")
For Each t As System.Type In GetType(Session).Assembly.GetTypes()
Dim n As String = t.FullName
If n.IndexOf("Selection", StringComparison.OrdinalIgnoreCase) >= 0 OrElse
n.IndexOf("Collector", StringComparison.OrdinalIgnoreCase) >= 0 Then
sb.AppendLine("T: " & n)
End If
Next
File.WriteAllText(Path.Combine(Directory.GetCurrentDirectory(), "probe-selection.txt"), sb.ToString(), New UTF8Encoding(True))
End Sub
End Module

View File

@@ -0,0 +1,276 @@
' =============================================================================
' NX Journal: rebuild test master + diagnose why translator sees 0 solids (NX 2506)
'
' Hypothesis to test: UF CreateCyl1 built bodies that Part.Bodies counts (=2)
' but the STEP translator reports "0 solids". Suspect: unitless expressions
' ("bore=32" with no mm unit) produce degenerate/empty-shell bodies.
'
' This script:
' Phase 1 (single session, no reopen):
' OpenBaseDisplay -> ensure expressions WITH units (bore/stroke = mm) ->
' CreateCyl1 -> Update -> report body count AND face count per body ->
' add to MODEL refset -> export STEP (DisplayPart) -> Save ->
' export STEP again via ExistingPart (reads the saved file from disk).
' The face count tells us if bodies are real solids (cylinder ~3 faces)
' or empty shells (0 faces). The ExistingPart export tells us if the solids
' actually persisted to the .prt on disk.
'
' Run (run_journal takes one arg; use WorkingDirectory):
' WorkingDirectory = catalog-masters\KC (the .bat does "cd /d %~dp0")
' run_journal.exe <this file full path>
' Result log: <cwd>\rebuild-verify-master.txt Products: <cwd>\dbg_out\
'
' Note: English comments only (avoids GBK/BOM mojibake on no-BOM .vb files).
' =============================================================================
Option Strict Off
Imports System
Imports System.IO
Imports System.Text
Imports NXOpen
Imports NXOpen.UF
Module RebuildVerifyMaster
Dim theSession As Session = Session.GetSession()
Dim sb As New StringBuilder()
Sub Log(s As String)
sb.AppendLine(s)
End Sub
Sub Main()
Try
Run()
Catch ex As Exception
Log("[FATAL] " & ex.GetType().Name & " / " & ex.Message)
End Try
File.WriteAllText(Path.Combine(Directory.GetCurrentDirectory(), "rebuild-verify-master.txt"), sb.ToString(), New UTF8Encoding(True))
End Sub
Sub Run()
Dim cur As String = Directory.GetCurrentDirectory()
Dim masterPath As String = Path.Combine(cur, "KC_master.prt")
Log("CWD=" & cur)
Log("masterExists=" & File.Exists(masterPath))
If Not File.Exists(masterPath) Then
Log("[FAIL] master file missing: " & masterPath)
Return
End If
Try
File.SetAttributes(masterPath, File.GetAttributes(masterPath) And Not FileAttributes.ReadOnly)
Catch ex As Exception
Log("clear readonly: " & ex.Message)
End Try
Dim outDir As String = Path.Combine(cur, "dbg_out")
Directory.CreateDirectory(outDir)
Dim pls As NXOpen.PartLoadStatus = Nothing
Dim bp As NXOpen.BasePart = theSession.Parts.OpenBaseDisplay(masterPath, pls)
If pls IsNot Nothing Then pls.Dispose()
Dim w As Part = theSession.Parts.Work
If w Is Nothing Then
Log("[FAIL] OpenBaseDisplay -> Work=NULL")
Return
End If
Log("Work=" & w.FullPath)
Log("bodiesBefore=" & CountBodies(w))
' mm unit object (for length-typed expressions). Fall back to no unit.
Dim mm As NXOpen.Unit = w.UnitCollection.FindObject("MilliMeter")
If mm Is Nothing Then mm = w.UnitCollection.FindObject("Millimetre")
Log("unit mm=" & (If(mm Is Nothing, "NULL", mm.ToString())))
EnsureExprWithUnit(w, "bore", "32", mm)
EnsureExprWithUnit(w, "stroke", "10", mm)
EnsureExprNoUnit(w, "magOn", "0")
EnsureExprNoUnit(w, "mountIdx", "0")
Log("expr bore=" & ExprVal(w, "bore") & " stroke=" & ExprVal(w, "stroke") & " magOn=" & ExprVal(w, "magOn") & " mountIdx=" & ExprVal(w, "mountIdx"))
If CountBodies(w) = 0 Then
Dim ufs As UFSession = UFSession.GetUFSession()
Dim origin(2) As Double
Dim dir(2) As Double
dir(2) = 1.0
Dim t1 As Tag
Dim t2 As Tag
ufs.Modl.CreateCyl1(NXOpen.UF.FeatureSigns.Nullsign, origin, "stroke", "bore+8", dir, t1)
ufs.Modl.CreateCyl1(NXOpen.UF.FeatureSigns.Nullsign, origin, "stroke+30", "bore*0.33", dir, t2)
Log("created 2 cylinders (tags " & t1.ToString() & ", " & t2.ToString() & ")")
Else
Log("solids already exist, skip creation")
End If
Dim u2 As UFSession = UFSession.GetUFSession()
u2.Modl.Update()
Log("afterUpdate bodies=" & CountBodies(w))
DumpBodyFaces(w)
AddToModelRefset(w)
Log("afterRefset bodies=" & CountBodies(w))
' ---- TEST A: DisplayPart export of in-memory bodies ----
ExportStep(Path.Combine(outDir, "A_display.stp"))
Log("A_display.stp size=" & FileSize(Path.Combine(outDir, "A_display.stp")))
' ---- Save so the bodies (hopefully) persist to the .prt ----
Try
Dim pss As NXOpen.PartSaveStatus = w.Save(NXOpen.BasePart.SaveComponents.True, NXOpen.BasePart.CloseAfterSave.False)
If pss IsNot Nothing Then pss.Dispose()
Log("save OK")
Catch ex As Exception
Log("save FAIL: " & ex.Message)
End Try
' ---- TEST B: ExistingPart export reads the SAVED file from disk ----
ExportStepExisting(masterPath, Path.Combine(outDir, "B_existing.stp"))
Log("B_existing.stp size=" & FileSize(Path.Combine(outDir, "B_existing.stp")))
Log("DONE")
End Sub
' ---- helpers ----
Function CountBodies(w As Part) As Integer
Dim n As Integer = 0
Try
For Each b As NXOpen.Body In w.Bodies
n += 1
Next
Catch
End Try
Return n
End Function
' Count faces per body. A real cylinder ~3 faces; 0 faces = empty shell.
Sub DumpBodyFaces(w As Part)
Dim i As Integer = 0
Try
For Each b As NXOpen.Body In w.Bodies
i += 1
Dim fc As Integer = 0
Try
For Each f As NXOpen.Face In b.GetFaces()
fc += 1
Next
Catch
End Try
Log("body" & i & " faces=" & fc)
Next
Catch ex As Exception
Log("DumpBodyFaces FAIL: " & ex.Message)
End Try
End Sub
Sub EnsureExprWithUnit(w As Part, name As String, val As String, mm As NXOpen.Unit)
Try
If w.Expressions.FindObject(name) IsNot Nothing Then Return
Catch
End Try
Try
If mm IsNot Nothing Then
w.Expressions.CreateExpressionWithUnit("Number", name & "=" & val, mm)
Else
w.Expressions.CreateExpression("Number", name & "=" & val)
End If
Catch ex As Exception
Log("EnsureExprWithUnit " & name & ": " & ex.Message)
End Try
End Sub
Sub EnsureExprNoUnit(w As Part, name As String, val As String)
Try
If w.Expressions.FindObject(name) IsNot Nothing Then Return
Catch
End Try
Try
w.Expressions.CreateExpression("Number", name & "=" & val)
Catch ex As Exception
Log("EnsureExprNoUnit " & name & ": " & ex.Message)
End Try
End Sub
Function ExprVal(w As Part, name As String) As String
Try
Dim e As NXOpen.Expression = w.Expressions.FindObject(name)
If e IsNot Nothing Then Return e.Value
Catch
End Try
Return "(missing)"
End Function
Sub AddToModelRefset(w As Part)
Try
Dim bodyList As New System.Collections.Generic.List(Of NXOpen.NXObject)()
For Each b As NXOpen.Body In w.Bodies
bodyList.Add(b)
Next
If bodyList.Count = 0 Then
Log("AddToModelRefset: no bodies")
Return
End If
Dim added As Boolean = False
For Each rs As NXOpen.ReferenceSet In w.GetAllReferenceSets()
If rs.Name.ToUpperInvariant().Contains("MODEL") Then
rs.AddObjectsToReferenceSet(bodyList.ToArray())
Log("added " & bodyList.Count & " bodies to refset '" & rs.Name & "'")
added = True
Exit For
End If
Next
If Not added Then
Log("AddToModelRefset: no MODEL refset (refsets: " & RefsetNames(w) & ")")
End If
Catch ex As Exception
Log("AddToModelRefset FAIL: " & ex.Message)
End Try
End Sub
Function RefsetNames(w As Part) As String
Dim s As String = ""
Try
For Each rs As NXOpen.ReferenceSet In w.GetAllReferenceSets()
If s.Length > 0 Then s &= ", "
s &= rs.Name
Next
Catch
End Try
Return s
End Function
Function FileSize(p As String) As String
If Not File.Exists(p) Then Return "MISSING"
Return (New FileInfo(p)).Length & " bytes"
End Function
' ---- export functions ----
Sub ExportStep(fileName As String)
If File.Exists(fileName) Then File.Delete(fileName)
Try
Dim sc As NXOpen.StepCreator = theSession.DexManager.CreateStepCreator()
sc.ExportFrom = NXOpen.StepCreator.ExportFromOption.DisplayPart
sc.ExportAs = NXOpen.StepCreator.ExportAsOption.Ap214
sc.OutputFile = fileName
sc.ColorAndLayers = True
sc.Commit()
sc.Destroy()
Catch ex As Exception
Log("ExportStep FAIL: " & ex.Message)
End Try
End Sub
Sub ExportStepExisting(inputFile As String, fileName As String)
If File.Exists(fileName) Then File.Delete(fileName)
Try
Dim sc As NXOpen.StepCreator = theSession.DexManager.CreateStepCreator()
sc.ExportFrom = NXOpen.StepCreator.ExportFromOption.ExistingPart
sc.InputFile = inputFile
sc.ExportAs = NXOpen.StepCreator.ExportAsOption.Ap214
sc.OutputFile = fileName
sc.ColorAndLayers = True
sc.Commit()
sc.Destroy()
Catch ex As Exception
Log("ExportStepExisting FAIL: " & ex.Message)
End Try
End Sub
End Module

View File

@@ -0,0 +1,40 @@
# 实验: 把颜色实体块移到 DATA 段末尾 (复刻 inject-colors 的成功布局), 并全量重编号
# 用法: powershell -NoProfile -ExecutionPolicy Bypass -File tools\reorder-colors.ps1 -In a.step -Out b.step
param([string]$In, [string]$Out)
$lines = Get-Content $In
# 切分: 头部(至 DATA;) / 实体区 / 尾部(ENDSEC; 起)
$dataIdx = 0
for ($i = 0; $i -lt $lines.Count; $i++) { if ($lines[$i] -eq 'DATA;') { $dataIdx = $i; break } }
$endIdx = $lines.Count - 1
for ($i = $dataIdx; $i -lt $lines.Count; $i++) { if ($lines[$i] -eq 'ENDSEC;') { $endIdx = $i; break } }
$head = $lines[0..$dataIdx]
$ents = $lines[($dataIdx + 1)..($endIdx - 1)]
$foot = $lines[$endIdx..($lines.Count - 1)]
# 实体区再分: 几何区 / 颜色区 / 尾部区
$colorStart = -1; $tailStart = -1
for ($i = 0; $i -lt $ents.Count; $i++) {
if ($colorStart -lt 0 -and $ents[$i] -match '=COLOUR_RGB\(') { $colorStart = $i }
if ($tailStart -lt 0 -and $ents[$i] -match '=SHAPE_REPRESENTATION\(') { $tailStart = $i }
}
if ($colorStart -lt 0 -or $tailStart -lt 0) { Write-Output "结构不识别"; exit 1 }
$geo = $ents[0..($colorStart - 1)]
$colors = $ents[$colorStart..($tailStart - 1)]
$tail = $ents[$tailStart..($ents.Count - 1)]
$newEnts = @($geo) + @($tail) + @($colors) # 新顺序: 几何 → 尾部 → 颜色
# 全量重编号
$map = @{}
$next = 1
foreach ($l in $newEnts) {
$m = [regex]::Match($l, '^#(\d+)=')
if ($m.Success) { $map[$m.Groups[1].Value] = $next; $next++ }
}
$renum = $newEnts | ForEach-Object {
[regex]::Replace($_, '#\d+', { param($mm) $id = $mm.Value.Substring(1); if ($map.ContainsKey($id)) { return '#' + $map[$id] } else { return $mm.Value } })
}
$content = @($head) + @($renum) + @($foot)
[System.IO.File]::WriteAllLines($Out, [string[]]$content, (New-Object System.Text.UTF8Encoding($false)))
Write-Output ("重排完成: 几何 " + $geo.Count + " 行 + 尾部 " + $tail.Count + " 行 + 颜色 " + $colors.Count + " 行 → " + $Out)

View File

@@ -0,0 +1,34 @@
# 运行 rebuild-verify-master.vb (NX 2506 批处理): 重建含实体母模 + 验证导出
# 用法: powershell -NoProfile -ExecutionPolicy Bypass -File run-rebuild-verify.ps1
$ErrorActionPreference = 'Continue'
$nx = 'C:\Program Files\Siemens\NX2506\NXBIN\run_journal.exe'
$journal = 'd:\开发\OnebotCatalog\sample-data\tools\rebuild-verify-master.vb'
$workDir = 'd:\开发\OnebotCatalog\catalog-masters\KC'
if (-not (Test-Path $nx)) { Write-Output "[FAIL] NX run_journal 不存在: $nx"; exit 1 }
if (-not (Test-Path $journal)) { Write-Output "[FAIL] journal 不存在: $journal"; exit 1 }
if (-not (Test-Path $workDir)) { Write-Output "[FAIL] 工作目录不存在: $workDir"; exit 1 }
# 清理旧结果
Remove-Item (Join-Path $workDir 'rebuild-verify-master.txt') -ErrorAction SilentlyContinue
Remove-Item (Join-Path $workDir 'dbg_out\*') -ErrorAction SilentlyContinue
Write-Output "[RUN] run_journal 启动 (预计 30-90 秒, NX 会话冷启动)..."
$sw = [System.Diagnostics.Stopwatch]::StartNew()
$p = Start-Process -FilePath $nx -ArgumentList $journal -WorkingDirectory $workDir -PassThru -Wait
$sw.Stop()
Write-Output "[RUN] run_journal 退出, 用时 $([int]$sw.Elapsed.TotalSeconds)"
$log = Join-Path $workDir 'rebuild-verify-master.txt'
if (Test-Path $log) {
Write-Output "========== rebuild-verify-master.txt =========="
Get-Content $log -Encoding UTF8
Write-Output "==============================================="
} else {
Write-Output "[FAIL] 未生成结果日志: $log (NX 可能启动失败或被 license 占用)"
}
Write-Output "========== dbg_out 产物 =========="
Get-ChildItem (Join-Path $workDir 'dbg_out') -ErrorAction SilentlyContinue | Select-Object Name, Length | Format-Table -AutoSize

View File

@@ -0,0 +1,139 @@
' =============================================================================
' NX Journal: test whether batch-mode DisplayPart is empty (NX 2506)
'
' Hypothesis: ExportFrom=DisplayPart exports Session.Parts.Display, but in
' batch (run_journal) OpenBaseDisplay sets Work part WITHOUT setting the
' Display part -> Display is null -> translator sees 0 solids.
'
' This script:
' 1. OpenBaseDisplay -> print Work AND Display state
' 2. Try setting Display = workPart
' 3. Update + add to MODEL refset
' 4. DisplayPart STEP export (should now produce a real solid)
'
' Run: WorkingDirectory = catalog-masters\KC; run_journal.exe <this file>
' Result: <cwd>\test-display-part.txt Products: <cwd>\dbg_out\
'
' Note: English comments only (avoids GBK/BOM mojibake on no-BOM .vb files).
' =============================================================================
Option Strict Off
Imports System
Imports System.IO
Imports System.Text
Imports NXOpen
Imports NXOpen.UF
Module TestDisplayPart
Dim theSession As Session = Session.GetSession()
Dim sb As New StringBuilder()
Sub Log(s As String)
sb.AppendLine(s)
End Sub
Sub Main()
Try
Run()
Catch ex As Exception
Log("[FATAL] " & ex.GetType().Name & " / " & ex.Message)
End Try
File.WriteAllText(Path.Combine(Directory.GetCurrentDirectory(), "test-display-part.txt"), sb.ToString(), New UTF8Encoding(True))
End Sub
Function CountBodies(w As Part) As Integer
Dim n As Integer = 0
Try
For Each b As NXOpen.Body In w.Bodies
n += 1
Next
Catch
End Try
Return n
End Function
Function FileSize(p As String) As String
If Not File.Exists(p) Then Return "MISSING"
Return (New FileInfo(p)).Length & " bytes"
End Function
Sub Run()
Dim cur As String = Directory.GetCurrentDirectory()
Dim masterPath As String = Path.Combine(cur, "KC_master.prt")
Log("masterExists=" & File.Exists(masterPath))
If Not File.Exists(masterPath) Then Return
Dim pls As NXOpen.PartLoadStatus = Nothing
theSession.Parts.OpenBaseDisplay(masterPath, pls)
If pls IsNot Nothing Then pls.Dispose()
Dim w As Part = theSession.Parts.Work
If w Is Nothing Then
Log("[FAIL] Work=NULL")
Return
End If
Log("Work=" & w.FullPath)
' ---- KEY: state of the DISPLAY part ----
Try
Dim disp As BasePart = theSession.Parts.Display
Log("Display=" & (If(disp Is Nothing, "NULL", disp.FullPath)))
Catch ex As Exception
Log("Parts.Display get throws: " & ex.Message)
End Try
' ---- try to SET Display = work part ----
Try
theSession.Parts.Display = w
Log("set Display=work OK")
Catch ex As Exception
Log("set Display=work FAIL: " & ex.Message)
End Try
Try
Dim disp2 As BasePart = theSession.Parts.Display
Log("Display(after set)=" & (If(disp2 Is Nothing, "NULL", disp2.FullPath)))
Catch ex As Exception
Log("Parts.Display get2 throws: " & ex.Message)
End Try
' ---- Update + refset (align with the flow that produced a file) ----
Dim ufs As UFSession = UFSession.GetUFSession()
ufs.Modl.Update()
Log("afterUpdate bodies=" & CountBodies(w))
Try
Dim bodyList As New System.Collections.Generic.List(Of NXOpen.NXObject)()
For Each b As NXOpen.Body In w.Bodies
bodyList.Add(b)
Next
For Each rs As NXOpen.ReferenceSet In w.GetAllReferenceSets()
If rs.Name.ToUpperInvariant().Contains("MODEL") Then
rs.AddObjectsToReferenceSet(bodyList.ToArray())
Exit For
End If
Next
Log("refset bodies added")
Catch ex As Exception
Log("refset add FAIL: " & ex.Message)
End Try
' ---- DisplayPart STEP export ----
Dim f As String = Path.Combine(cur, "dbg_out", "D_display.stp")
Directory.CreateDirectory(Path.Combine(cur, "dbg_out"))
If File.Exists(f) Then File.Delete(f)
Try
Dim sc As NXOpen.StepCreator = theSession.DexManager.CreateStepCreator()
sc.ExportFrom = NXOpen.StepCreator.ExportFromOption.DisplayPart
sc.ExportAs = NXOpen.StepCreator.ExportAsOption.Ap214
sc.OutputFile = f
sc.ColorAndLayers = True
sc.Commit()
sc.Destroy()
Log("D_display.stp size=" & FileSize(f))
Catch ex As Exception
Log("D_display FAIL: " & ex.Message)
End Try
Log("DONE")
End Sub
End Module

View File

@@ -0,0 +1,134 @@
' =============================================================================
' NX Journal: inspect Display vs Work state in batch mode (NX 2506)
'
' Last attempt failed to compile: Parts.Display is ReadOnly. This version only
' READS and prints state (no assignment), so it compiles and tells us whether
' the batch-mode Display part is NULL / same as Work / different.
'
' Prints:
' Work.FullPath, Work bodies
' Display (NULL or FullPath), Display bodies
' Display Is Work?
' then a DisplayPart STEP export to see solids count.
'
' Run: WorkingDirectory = catalog-masters\KC; run_journal.exe <this file>
' Result: <cwd>\test-display-state.txt
'
' Note: English comments only (avoids GBK/BOM mojibake on no-BOM .vb files).
' =============================================================================
Option Strict Off
Imports System
Imports System.IO
Imports System.Text
Imports NXOpen
Imports NXOpen.UF
Module TestDisplayState
Dim theSession As Session = Session.GetSession()
Dim sb As New StringBuilder()
Sub Log(s As String)
sb.AppendLine(s)
End Sub
Sub Main()
Try
Run()
Catch ex As Exception
Log("[FATAL] " & ex.GetType().Name & " / " & ex.Message)
End Try
File.WriteAllText(Path.Combine(Directory.GetCurrentDirectory(), "test-display-state.txt"), sb.ToString(), New UTF8Encoding(True))
End Sub
Function CountBodies(p As BasePart) As Integer
Dim n As Integer = 0
Try
Dim pp As Part = CType(p, Part)
If pp IsNot Nothing Then
For Each b As NXOpen.Body In pp.Bodies
n += 1
Next
End If
Catch
End Try
Return n
End Function
Function FileSize(p As String) As String
If Not File.Exists(p) Then Return "MISSING"
Return (New FileInfo(p)).Length & " bytes"
End Function
Sub Run()
Dim cur As String = Directory.GetCurrentDirectory()
Dim masterPath As String = Path.Combine(cur, "KC_master.prt")
Log("masterExists=" & File.Exists(masterPath))
If Not File.Exists(masterPath) Then Return
Dim pls As NXOpen.PartLoadStatus = Nothing
theSession.Parts.OpenBaseDisplay(masterPath, pls)
If pls IsNot Nothing Then pls.Dispose()
Dim w As Part = theSession.Parts.Work
If w Is Nothing Then
Log("[FAIL] Work=NULL")
Return
End If
Log("Work=" & w.FullPath)
Log("Work.bodies=" & CountBodies(w))
' ---- READ Display state (no assignment) ----
Dim disp As BasePart = Nothing
Try
disp = theSession.Parts.Display
Catch ex As Exception
Log("Parts.Display get throws: " & ex.Message)
End Try
If disp Is Nothing Then
Log("Display=NULL")
Else
Log("Display=" & disp.FullPath)
Log("Display.bodies=" & CountBodies(disp))
Log("Display Is Work? " & (disp Is w))
End If
' ---- Update work + add bodies to MODEL refset (so the file is exportable) ----
Try
Dim ufs As UFSession = UFSession.GetUFSession()
ufs.Modl.Update()
Dim bodyList As New System.Collections.Generic.List(Of NXOpen.NXObject)()
For Each b As NXOpen.Body In w.Bodies
bodyList.Add(b)
Next
For Each rs As NXOpen.ReferenceSet In w.GetAllReferenceSets()
If rs.Name.ToUpperInvariant().Contains("MODEL") Then
rs.AddObjectsToReferenceSet(bodyList.ToArray())
Exit For
End If
Next
Log("refset added")
Catch ex As Exception
Log("refset FAIL: " & ex.Message)
End Try
' ---- DisplayPart export ----
Dim f As String = Path.Combine(cur, "dbg_out", "S_display.stp")
Directory.CreateDirectory(Path.Combine(cur, "dbg_out"))
If File.Exists(f) Then File.Delete(f)
Try
Dim sc As NXOpen.StepCreator = theSession.DexManager.CreateStepCreator()
sc.ExportFrom = NXOpen.StepCreator.ExportFromOption.DisplayPart
sc.ExportAs = NXOpen.StepCreator.ExportAsOption.Ap214
sc.OutputFile = f
sc.ColorAndLayers = True
sc.Commit()
sc.Destroy()
Log("S_display.stp size=" & FileSize(f))
Catch ex As Exception
Log("S_display FAIL: " & ex.Message)
End Try
Log("DONE")
End Sub
End Module

View File

@@ -0,0 +1,102 @@
' =============================================================================
' NX Journal: test ExistingPart (read-from-disk) STEP export, no open part.
'
' The last test proved the master is healthy (2 bodies, faces=3) but:
' - DisplayPart export -> "0 solids" (batch has no display part)
' - ExistingPart export -> MISSING (because KC_master.prt was locked by the
' open Work part when we tried to read it)
'
' Hypothesis: In batch (run_journal), the ONLY reliable way to export is
' ExistingPart + InputFile reading the SAVED .prt file, with NO part open.
'
' This script does NOT call OpenBaseDisplay. It only:
' 1. ExistingPart STEP export -> E1_existing.stp
' 2. ExistingPart IGES export -> E1_existing.igs
'
' Run (run_journal takes one arg; use WorkingDirectory):
' WorkingDirectory = catalog-masters\KC
' run_journal.exe <this file full path>
' Result log: <cwd>\test-existingpart.txt
'
' Note: English comments only (avoids GBK/BOM mojibake on no-BOM .vb files).
' =============================================================================
Option Strict Off
Imports System
Imports System.IO
Imports System.Text
Imports NXOpen
Imports NXOpen.UF
Module TestExistingPart
Dim theSession As Session = Session.GetSession()
Dim sb As New StringBuilder()
Sub Log(s As String)
sb.AppendLine(s)
End Sub
Sub Main()
Try
Run()
Catch ex As Exception
Log("[FATAL] " & ex.GetType().Name & " / " & ex.Message)
End Try
File.WriteAllText(Path.Combine(Directory.GetCurrentDirectory(), "test-existingpart.txt"), sb.ToString(), New UTF8Encoding(True))
End Sub
Sub Run()
Dim cur As String = Directory.GetCurrentDirectory()
Dim masterPath As String = Path.Combine(cur, "KC_master.prt")
Log("CWD=" & cur)
Log("masterExists=" & File.Exists(masterPath))
If Not File.Exists(masterPath) Then
Log("[FAIL] master missing: " & masterPath)
Return
End If
Dim outDir As String = Path.Combine(cur, "dbg_out")
Directory.CreateDirectory(outDir)
' 1) STEP via ExistingPart (reads saved file from disk, no open part)
Dim stp As String = Path.Combine(outDir, "E1_existing.stp")
If File.Exists(stp) Then File.Delete(stp)
Try
Dim sc As NXOpen.StepCreator = theSession.DexManager.CreateStepCreator()
sc.ExportFrom = NXOpen.StepCreator.ExportFromOption.ExistingPart
sc.InputFile = masterPath
sc.ExportAs = NXOpen.StepCreator.ExportAsOption.Ap214
sc.OutputFile = stp
sc.ColorAndLayers = True
sc.Commit()
sc.Destroy()
Log("E1_existing.stp size=" & FileSize(stp))
Catch ex As Exception
Log("STEP ExistingPart FAIL: " & ex.Message)
End Try
' 2) IGES via ExistingPart
Dim igs As String = Path.Combine(outDir, "E1_existing.igs")
If File.Exists(igs) Then File.Delete(igs)
Try
Dim ic As NXOpen.IgesCreator = theSession.DexManager.CreateIgesCreator()
ic.ExportFrom = NXOpen.IgesCreator.ExportFromOption.ExistingPart
ic.InputFile = masterPath
ic.ExportModelData = True
ic.OutputFile = igs
ic.Commit()
ic.Destroy()
Log("E1_existing.igs size=" & FileSize(igs))
Catch ex As Exception
Log("IGES ExistingPart FAIL: " & ex.Message)
End Try
Log("DONE")
End Sub
Function FileSize(p As String) As String
If Not File.Exists(p) Then Return "MISSING"
Return (New FileInfo(p)).Length & " bytes"
End Function
End Module

View File

@@ -0,0 +1,130 @@
' =============================================================================
' NX Journal: test StepCreator flags that likely cause "0 solids" in batch
'
' The master is healthy (2 bodies, faces=3) but DisplayPart export gives 0
' solids. Prime suspects are StepCreator properties whose batch-mode defaults
' differ from interactive mode:
'
' 1. ExportSolidsAndSurfacesAs (Precise vs Tessellated) <-- TOP suspect
' Tessellated needs display facets; batch has no rendering -> 0 solids.
' 2. ProcessHoldFlag (if True, translator may hold and not produce)
' 3. FileSaveFlag
'
' This script dumps the defaults of these properties, then tries exporting with
' ExportSolidsAndSurfacesAs = Precise (and, for contrast, Tessellated).
'
' Run: WorkingDirectory = catalog-masters\KC; run_journal.exe <this file>
' Result: <cwd>\test-fix-flags.txt Products: <cwd>\dbg_out\
'
' Note: English comments only (avoids GBK/BOM mojibake on no-BOM .vb files).
' =============================================================================
Option Strict Off
Imports System
Imports System.IO
Imports System.Text
Imports NXOpen
Imports NXOpen.UF
Module TestFixFlags
Dim theSession As Session = Session.GetSession()
Dim sb As New StringBuilder()
Sub Log(s As String)
sb.AppendLine(s)
End Sub
Sub Main()
Try
Run()
Catch ex As Exception
Log("[FATAL] " & ex.GetType().Name & " / " & ex.Message)
End Try
File.WriteAllText(Path.Combine(Directory.GetCurrentDirectory(), "test-fix-flags.txt"), sb.ToString(), New UTF8Encoding(True))
End Sub
Function CountBodies(w As Part) As Integer
Dim n As Integer = 0
Try
For Each b As NXOpen.Body In w.Bodies
n += 1
Next
Catch
End Try
Return n
End Function
Function FileSize(p As String) As String
If Not File.Exists(p) Then Return "MISSING"
Return (New FileInfo(p)).Length & " bytes"
End Function
Sub Run()
Dim cur As String = Directory.GetCurrentDirectory()
Dim masterPath As String = Path.Combine(cur, "KC_master.prt")
Log("masterExists=" & File.Exists(masterPath))
If Not File.Exists(masterPath) Then Return
Dim pls As NXOpen.PartLoadStatus = Nothing
theSession.Parts.OpenBaseDisplay(masterPath, pls)
If pls IsNot Nothing Then pls.Dispose()
Dim w As Part = theSession.Parts.Work
If w Is Nothing Then
Log("[FAIL] Work=NULL")
Return
End If
Log("bodies=" & CountBodies(w))
Dim outDir As String = Path.Combine(cur, "dbg_out")
Directory.CreateDirectory(outDir)
' ---- dump defaults of suspect properties ----
Dim sc0 As NXOpen.StepCreator = theSession.DexManager.CreateStepCreator()
Log("--- StepCreator defaults ---")
Try : Log("ProcessHoldFlag=" & sc0.ProcessHoldFlag) : Catch ex As Exception : Log("ProcessHoldFlag throws: " & ex.Message) : End Try
Try : Log("FileSaveFlag=" & sc0.FileSaveFlag) : Catch ex As Exception : Log("FileSaveFlag throws: " & ex.Message) : End Try
Try : Log("ColorAndLayers=" & sc0.ColorAndLayers) : Catch ex As Exception : Log("ColorAndLayers throws: " & ex.Message) : End Try
Try : Log("ExportFrom=" & sc0.ExportFrom.ToString()) : Catch ex As Exception : Log("ExportFrom throws: " & ex.Message) : End Try
Try : Log("ExportAs=" & sc0.ExportAs.ToString()) : Catch ex As Exception : Log("ExportAs throws: " & ex.Message) : End Try
Try : Log("ExportSolidsAndSurfacesAs=" & sc0.ExportSolidsAndSurfacesAs.ToString()) : Catch ex As Exception : Log("ESSAs throws: " & ex.Message) : End Try
Try : Log("ReferenceType=" & sc0.ReferenceType.ToString()) : Catch ex As Exception : Log("ReferenceType throws: " & ex.Message) : End Try
sc0.Destroy()
' ---- Attempt 1: DisplayPart + ESSAs=Precise (TOP hypothesis) ----
Dim f1 As String = Path.Combine(outDir, "F1_display_precise.stp")
If File.Exists(f1) Then File.Delete(f1)
Try
Dim sc As NXOpen.StepCreator = theSession.DexManager.CreateStepCreator()
sc.ExportFrom = NXOpen.StepCreator.ExportFromOption.DisplayPart
sc.ExportAs = NXOpen.StepCreator.ExportAsOption.Ap214
sc.OutputFile = f1
sc.ColorAndLayers = True
Try : sc.ExportSolidsAndSurfacesAs = NXOpen.StepCreator.ExportSolidsAndSurfacesAsOption.Precise : Catch ex As Exception : Log("set ESSAs=Precise: " & ex.Message) : End Try
sc.Commit()
sc.Destroy()
Log("F1_display_precise.stp size=" & FileSize(f1))
Catch ex As Exception
Log("F1 FAIL: " & ex.Message)
End Try
' ---- Attempt 2: DisplayPart + ESSAs=Tessellated (contrast) ----
Dim f2 As String = Path.Combine(outDir, "F2_display_tess.stp")
If File.Exists(f2) Then File.Delete(f2)
Try
Dim sc As NXOpen.StepCreator = theSession.DexManager.CreateStepCreator()
sc.ExportFrom = NXOpen.StepCreator.ExportFromOption.DisplayPart
sc.ExportAs = NXOpen.StepCreator.ExportAsOption.Ap214
sc.OutputFile = f2
sc.ColorAndLayers = True
Try : sc.ExportSolidsAndSurfacesAs = NXOpen.StepCreator.ExportSolidsAndSurfacesAsOption.Tessellated : Catch ex As Exception : Log("set ESSAs=Tessellated: " & ex.Message) : End Try
sc.Commit()
sc.Destroy()
Log("F2_display_tess.stp size=" & FileSize(f2))
Catch ex As Exception
Log("F2 FAIL: " & ex.Message)
End Try
Log("DONE")
End Sub
End Module

View File

@@ -0,0 +1,50 @@
# STEP 结构自检:引用完整性 / 括号配平 / 纯 ASCII / 头尾结构
# 用法: powershell -NoProfile -ExecutionPolicy Bypass -File tools\verify-step.ps1
param(
[string]$StepDir = (Join-Path $PSScriptRoot '..\onb-sc\step')
)
$files = Get-ChildItem $StepDir -Filter '*.step' | Sort-Object Name
$errors = @()
$checked = 0
foreach ($f in $files) {
$text = [System.IO.File]::ReadAllText($f.FullName)
$checked++
# 1. 头尾结构
if (-not $text.StartsWith('ISO-10303-21;')) { $errors += "$($f.Name): 缺少 ISO-10303-21 头" }
if (-not $text.TrimEnd().EndsWith('END-ISO-10303-21;')) { $errors += "$($f.Name): 缺少 END-ISO-10303-21 尾" }
if ($text -notmatch '(?s)HEADER;.*?ENDSEC;.*?DATA;.*?ENDSEC;') { $errors += "$($f.Name): HEADER/DATA 段结构异常" }
# 2. 引用完整性: 所有被引用的 #id 都已定义
$defined = @{}
foreach ($m in [regex]::Matches($text, '#(\d+)\s*=')) { $defined[[int]$m.Groups[1].Value] = $true }
foreach ($m in [regex]::Matches($text, '#(\d+)')) {
$id = [int]$m.Groups[1].Value
if (-not $defined.ContainsKey($id)) { $errors += "$($f.Name): 引用了未定义的 #$id"; break }
}
# 3. 括号配平 (排除文本单引号内的内容后逐字符统计)
$noStr = [regex]::Replace($text, "'[^']*'", "''")
$open = ([regex]::Matches($noStr, '\(')).Count
$close = ([regex]::Matches($noStr, '\)')).Count
if ($open -ne $close) { $errors += "$($f.Name): 括号不配平 ($open/$close)" }
# 4. 纯 ASCII (STEP 交换格式要求)
foreach ($ch in $text.ToCharArray()) {
if ([int]$ch -gt 127) { $errors += "$($f.Name): 存在非 ASCII 字符"; break }
}
# 5. 实体计数: 单杆 4x(36+7色) + 尾部14 = 186; 双杆 5x(36+7色) + 14 = 229
$entityCount = ([regex]::Matches($text, '#\d+\s*=')).Count
if ($entityCount -ne 186 -and $entityCount -ne 229) { $errors += "$($f.Name): 实体数异常 ($entityCount, 期望 186/229)" }
}
Write-Output "检查文件数: $checked"
if ($errors.Count -eq 0) {
Write-Output "全部通过: 引用完整 / 括号配平 / 纯 ASCII / 结构正常"
} else {
Write-Output "发现问题 $($errors.Count) 条:"
$errors | ForEach-Object { Write-Output " - $_" }
}

View File

@@ -0,0 +1,441 @@
' =============================================================================
' 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