@faicad/faijs 0.19.0 → 0.22.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/brep-mirror/joinery-brep.d.ts.map +1 -1
- package/dist/api/brep-mirror/joinery-brep.js +3 -1
- package/dist/api/brep-mirror/joinery-brep.js.map +1 -1
- package/dist/api/generated/script-face-manifest.d.ts +4 -0
- package/dist/api/generated/script-face-manifest.d.ts.map +1 -1
- package/dist/api/generated/script-face-manifest.js.map +1 -1
- package/dist/api/knurl.d.ts.map +1 -1
- package/dist/api/knurl.js +1 -0
- package/dist/api/knurl.js.map +1 -1
- package/dist/api/primitives.d.ts +4 -3
- package/dist/api/primitives.d.ts.map +1 -1
- package/dist/api/primitives.js +9 -3
- package/dist/api/primitives.js.map +1 -1
- package/dist/api/profile.d.ts +34 -5
- package/dist/api/profile.d.ts.map +1 -1
- package/dist/api/profile.js +60 -9
- package/dist/api/profile.js.map +1 -1
- package/dist/api/punch-hole.d.ts +3 -0
- package/dist/api/punch-hole.d.ts.map +1 -1
- package/dist/api/punch-hole.js.map +1 -1
- package/dist/api/sketch-on-plane.d.ts +12 -6
- package/dist/api/sketch-on-plane.d.ts.map +1 -1
- package/dist/api/sketch-on-plane.js +11 -8
- package/dist/api/sketch-on-plane.js.map +1 -1
- package/dist/api/surface/arg-spec.d.ts +8 -0
- package/dist/api/surface/arg-spec.d.ts.map +1 -1
- package/dist/api/surface/arg-spec.js.map +1 -1
- package/dist/api/transform.d.ts +5 -5
- package/dist/api/transform.d.ts.map +1 -1
- package/dist/api/transform.js +12 -10
- package/dist/api/transform.js.map +1 -1
- package/dist/api/xml-dom.d.ts +27 -0
- package/dist/api/xml-dom.d.ts.map +1 -0
- package/dist/api/xml-dom.js +45 -0
- package/dist/api/xml-dom.js.map +1 -0
- package/dist/brep/brep-ops.d.ts +4 -4
- package/dist/brep/brep-ops.d.ts.map +1 -1
- package/dist/brep/brep-ops.js +8 -6
- package/dist/brep/brep-ops.js.map +1 -1
- package/dist/brep/brep-topology.d.ts.map +1 -1
- package/dist/brep/brep-topology.js +3 -1
- package/dist/brep/brep-topology.js.map +1 -1
- package/dist/brep/export/export-model.d.ts +79 -0
- package/dist/brep/export/export-model.d.ts.map +1 -0
- package/dist/brep/export/export-model.js +335 -0
- package/dist/brep/export/export-model.js.map +1 -0
- package/dist/brep/primitives-brep.d.ts.map +1 -1
- package/dist/brep/primitives-brep.js +3 -1
- package/dist/brep/primitives-brep.js.map +1 -1
- package/dist/brepkit-kernel/brepkitKernel.d.ts.map +1 -1
- package/dist/brepkit-kernel/brepkitKernel.js +4 -2
- package/dist/brepkit-kernel/brepkitKernel.js.map +1 -1
- package/dist/cad-runtime/direct-executor.d.ts +4 -0
- package/dist/cad-runtime/direct-executor.d.ts.map +1 -1
- package/dist/cad-runtime/direct-executor.js +17 -1
- package/dist/cad-runtime/direct-executor.js.map +1 -1
- package/dist/cad-runtime/runtime.d.ts +24 -1
- package/dist/cad-runtime/runtime.d.ts.map +1 -1
- package/dist/cad-runtime/runtime.js +53 -3
- package/dist/cad-runtime/runtime.js.map +1 -1
- package/dist/define-op.d.ts +22 -0
- package/dist/define-op.d.ts.map +1 -1
- package/dist/define-op.js +15 -0
- package/dist/define-op.js.map +1 -1
- package/dist/env-agnostic.d.ts +7 -7
- package/dist/env-agnostic.d.ts.map +1 -1
- package/dist/env-agnostic.js +3 -3
- package/dist/env-agnostic.js.map +1 -1
- package/dist/geometry2d/bridge/lift-on-plane.d.ts.map +1 -1
- package/dist/geometry2d/bridge/lift-on-plane.js +6 -0
- package/dist/geometry2d/bridge/lift-on-plane.js.map +1 -1
- package/dist/geometry2d/pen-sketcher.d.ts +25 -0
- package/dist/geometry2d/pen-sketcher.d.ts.map +1 -1
- package/dist/geometry2d/pen-sketcher.js +43 -0
- package/dist/geometry2d/pen-sketcher.js.map +1 -1
- package/dist/lang/code-to-args.d.ts +29 -3
- package/dist/lang/code-to-args.d.ts.map +1 -1
- package/dist/lang/code-to-args.js +41 -2
- package/dist/lang/code-to-args.js.map +1 -1
- package/dist/lang/codegen.d.ts +45 -1
- package/dist/lang/codegen.d.ts.map +1 -1
- package/dist/lang/codegen.js +135 -6
- package/dist/lang/codegen.js.map +1 -1
- package/dist/lang/host-arg.d.ts +6 -0
- package/dist/lang/host-arg.d.ts.map +1 -1
- package/dist/lang/host-arg.js.map +1 -1
- package/dist/lang/metadata-extractor.d.ts +25 -0
- package/dist/lang/metadata-extractor.d.ts.map +1 -1
- package/dist/lang/metadata-extractor.js +289 -25
- package/dist/lang/metadata-extractor.js.map +1 -1
- package/dist/lang/parse-error.d.ts +7 -1
- package/dist/lang/parse-error.d.ts.map +1 -1
- package/dist/lang/parse-error.js.map +1 -1
- package/dist/lang/security-scanner.d.ts +9 -1
- package/dist/lang/security-scanner.d.ts.map +1 -1
- package/dist/lang/security-scanner.js +38 -1
- package/dist/lang/security-scanner.js.map +1 -1
- package/dist/mesh/io.d.ts +62 -9
- package/dist/mesh/io.d.ts.map +1 -1
- package/dist/mesh/io.js +124 -17
- package/dist/mesh/io.js.map +1 -1
- package/dist/mesh/threemf-loader.d.ts +15 -0
- package/dist/mesh/threemf-loader.d.ts.map +1 -0
- package/dist/mesh/threemf-loader.js +163 -0
- package/dist/mesh/threemf-loader.js.map +1 -0
- package/dist/mesh/transform.d.ts +2 -2
- package/dist/mesh/transform.d.ts.map +1 -1
- package/dist/mesh/transform.js +3 -3
- package/dist/mesh/transform.js.map +1 -1
- package/dist/mesh/types.d.ts +1 -1
- package/dist/mesh/types.d.ts.map +1 -1
- package/dist/mesh/types.js.map +1 -1
- package/dist/node-host/cli.d.ts.map +1 -1
- package/dist/node-host/cli.js +53 -26
- package/dist/node-host/cli.js.map +1 -1
- package/dist/occt-kernel/occtKernel.d.ts.map +1 -1
- package/dist/occt-kernel/occtKernel.js +4 -2
- package/dist/occt-kernel/occtKernel.js.map +1 -1
- package/dist/occt-kernel/topologyExt.d.ts.map +1 -1
- package/dist/occt-kernel/topologyExt.js +4 -1
- package/dist/occt-kernel/topologyExt.js.map +1 -1
- package/dist/runtime-state.d.ts +18 -0
- package/dist/runtime-state.d.ts.map +1 -1
- package/dist/runtime-state.js +23 -0
- package/dist/runtime-state.js.map +1 -1
- package/dist/sdk.d.ts +1 -1
- package/dist/sdk.d.ts.map +1 -1
- package/dist/sdk.js +1 -1
- package/dist/sdk.js.map +1 -1
- package/dist/tolerance.d.ts +38 -0
- package/dist/tolerance.d.ts.map +1 -0
- package/dist/tolerance.js +40 -0
- package/dist/tolerance.js.map +1 -0
- package/dist/topology/naming/score.js +3 -3
- package/dist/topology/naming/score.js.map +1 -1
- package/dist/units.d.ts +248 -0
- package/dist/units.d.ts.map +1 -0
- package/dist/units.js +397 -0
- package/dist/units.js.map +1 -0
- package/package.json +11 -2
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* exportModel — 统一导出入口(unit-system §10.6 二 / R11 / 裁决 J)
|
|
3
|
+
*
|
|
4
|
+
* 单一真源:坐标单位换算与文件单位声明由**同一次换算**产出(§10.3 规则 3),
|
|
5
|
+
* 声明与坐标必须同源。CLI(writeOutput)与宿主(3d_editor)共用本入口。
|
|
6
|
+
*
|
|
7
|
+
* 格式分派(§10.6 表):
|
|
8
|
+
* - stl:坐标缩放到 unit 刻度,无声明(格式不支持)。
|
|
9
|
+
* - step:预缩放 + 文本层只改写单位实体(§10.3 规则 3a;OCCT 恒写
|
|
10
|
+
* SI_UNIT(.MILLI.,.METRE.),坐标预先缩放到目标刻度后声明必须跟上)。
|
|
11
|
+
* - 3mf:坐标缩放到 unit 刻度 + <model unit> 与坐标同源;yard 不在 3MF
|
|
12
|
+
* 枚举 → 成对回落到 mm(§10.3 规则 2)。
|
|
13
|
+
*
|
|
14
|
+
* 红线(§10.3 3a):绝不文本级改坐标 — 坐标只在几何层缩放,文本层只碰单位实体。
|
|
15
|
+
*/
|
|
16
|
+
import { buildStlBufferFromMesh } from './stl.js';
|
|
17
|
+
import { zipSync, strToU8 } from 'fflate';
|
|
18
|
+
import { UNIT_SCALE } from '../../units.js';
|
|
19
|
+
import { getBrepApi } from '../handle-bridge.js';
|
|
20
|
+
import { exportStepFromSolids } from './step.js';
|
|
21
|
+
import { detectStepUnit, STEP_UNIT_SCAN_PREFIX } from '../../mesh/io.js';
|
|
22
|
+
/** UnitName → 3MF <model unit> 枚举全名(唯一真源;yard 无对应值)。 */
|
|
23
|
+
export const UNIT_NAME_TO_3MF = {
|
|
24
|
+
mm: 'millimeter',
|
|
25
|
+
cm: 'centimeter',
|
|
26
|
+
m: 'meter',
|
|
27
|
+
micron: 'micron',
|
|
28
|
+
inch: 'inch',
|
|
29
|
+
foot: 'foot',
|
|
30
|
+
// yard 无 3MF 枚举值 — 调用方必须先成对回落到 mm(§10.3 规则 2)。
|
|
31
|
+
};
|
|
32
|
+
/** 缩放 Float32 positions 到目标单位刻度(基准值 ÷ unitScale(unit))。 */
|
|
33
|
+
function scalePositions(positions, scale) {
|
|
34
|
+
if (scale === 1)
|
|
35
|
+
return positions;
|
|
36
|
+
const out = new Float32Array(positions.length);
|
|
37
|
+
for (let i = 0; i < positions.length; i++)
|
|
38
|
+
out[i] = positions[i] * scale;
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
/** STEP 单位声明改写:SI_UNIT(.MILLI.,.METRE.) → 目标单位实体(只碰单位实体,红线)。 */
|
|
42
|
+
function rewriteStepUnitEntities(stepText, unit) {
|
|
43
|
+
// mm 是 OCCT 的原生写出形态,无需改写。
|
|
44
|
+
if (unit === 'mm')
|
|
45
|
+
return stepText;
|
|
46
|
+
// SI 面积/体积单位实体的前缀同样要跟着换(LENGTH/AREA/VOLUME 挂同一前缀链),
|
|
47
|
+
// 但坐标已缩放 → 只需把 LENGTH 链上的前缀换掉;面积/体积单位在 STEP 里由
|
|
48
|
+
// SI_UNIT 前缀 + 幂次表达,XCAF 写出的实体形态是 SI_UNIT(.MILLI.,.METRE.)
|
|
49
|
+
// 挂在长度、平方与立方三条依赖链上。为保守起见只改写显式 LENGTH_UNIT 链上的
|
|
50
|
+
// SI_UNIT —— 具体做法:全局替换 SI_UNIT 前缀(同一文件里坐标相关单位恒为
|
|
51
|
+
// 长度链;角度 SI_UNIT(.RADIAN.) 不含 METRE 不会被命中)。
|
|
52
|
+
let out = stepText;
|
|
53
|
+
if (unit === 'cm' || unit === 'm' || unit === 'micron') {
|
|
54
|
+
const prefix = unit === 'cm' ? '.CENTI.' : unit === 'm' ? '$' : '.MICRO.';
|
|
55
|
+
// OCCT 写出恒为 SI_UNIT(.MILLI.,.METRE.)(实测);$ 前缀形态(无前缀=米)
|
|
56
|
+
// 在 OCCT 输出中不会出现,但容错处理(若上游将来变化)。
|
|
57
|
+
out = out.replace(/SI_UNIT\s*\(\s*\.MILLI\.\s*,\s*\.METRE\.\s*\)/g, prefix === '$' ? 'SI_UNIT($,.METRE.)' : `SI_UNIT(${prefix},.METRE.)`);
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
// inch / foot:CONVERSION_BASED_UNIT 形态。需要注入换算因子实体并替换
|
|
61
|
+
// LENGTH_UNIT 依赖。最简形态:把 SI_UNIT(.MILLI.,.METRE.) 替换为
|
|
62
|
+
// CONVERSION_BASED_UNIT('INCH', #factor),并附加 MEASURE_WITH_UNIT 实体。
|
|
63
|
+
// 实体编号:取文件内最大 #n + 1、+2(附加实体挂在文件尾部 DATA 段内合法)。
|
|
64
|
+
const inchFactor = unit === 'inch' ? 25.4 : 304.8;
|
|
65
|
+
const name = unit === 'inch' ? 'INCH' : 'FOOT';
|
|
66
|
+
// 1) 收集现有最大实体编号
|
|
67
|
+
let maxId = 0;
|
|
68
|
+
for (const m of out.matchAll(/#(\d+)\s*=/g)) {
|
|
69
|
+
const id = Number(m[1]);
|
|
70
|
+
if (id > maxId)
|
|
71
|
+
maxId = id;
|
|
72
|
+
}
|
|
73
|
+
const lenId = maxId + 1;
|
|
74
|
+
const convId = maxId + 2;
|
|
75
|
+
// 2) 找到 LENGTH_UNIT() 里挂 SI_UNIT 的实体行,替换为 CONVERSION_BASED_UNIT
|
|
76
|
+
// 并把 SI_UNIT 挂到 LENGTH_MEASURE 上。OCCT 输出形态(实测):
|
|
77
|
+
// #n=(LENGTH_UNIT()NAMED_UNIT(*)SI_UNIT(.MILLI.,.METRE.));
|
|
78
|
+
const siLine = out.match(/#(\d+)\s*=\s*\([^)]*SI_UNIT\s*\(\s*\.MILLI\.\s*,\s*\.METRE\.\s*\)[^)]*\)\s*;/);
|
|
79
|
+
if (!siLine) {
|
|
80
|
+
throw new Error(`[export/step] no SI_UNIT(.MILLI.,.METRE.) entity found; cannot rewrite declaration to ${name}`);
|
|
81
|
+
}
|
|
82
|
+
// 3) 替换原 SI_UNIT 实体为 CONVERSION_BASED_UNIT,插入换算因子与基准米实体:
|
|
83
|
+
// #si=(LENGTH_UNIT()NAMED_UNIT(*)SI_UNIT(.MILLI.,.METRE.));
|
|
84
|
+
// → #si=(LENGTH_UNIT()NAMED_UNIT(*)CONVERSION_BASED_UNIT('INCH',#conv));
|
|
85
|
+
// #conv=LENGTH_MEASURE_WITH_UNIT(LENGTH_MEASURE(25.4),#len);
|
|
86
|
+
// #len=(LENGTH_UNIT()NAMED_UNIT(*)SI_UNIT($,.METRE.));
|
|
87
|
+
out = out.replace(siLine[0], siLine[0].replace(/SI_UNIT\s*\(\s*\.MILLI\.\s*,\s*\.METRE\.\s*\)/, `CONVERSION_BASED_UNIT('${name}',#${convId})`));
|
|
88
|
+
// 在 DATA 段结束前(ENDSEC;)插入新实体。
|
|
89
|
+
const insert = [
|
|
90
|
+
`#${convId}=LENGTH_MEASURE_WITH_UNIT(LENGTH_MEASURE(${inchFactor}),#${lenId});`,
|
|
91
|
+
`#${lenId}=(LENGTH_UNIT()NAMED_UNIT(*)SI_UNIT($,.METRE.));`,
|
|
92
|
+
].join('\n');
|
|
93
|
+
out = out.replace(/ENDSEC\s*;/, `${insert}\nENDSEC;`);
|
|
94
|
+
return out;
|
|
95
|
+
}
|
|
96
|
+
/** 3MF写出:最小 ZIP 容器(deflate),落 3D/3dmodel.model + [Content_Types].xml + _rels。 */
|
|
97
|
+
function build3mfBuffer(modelXml) {
|
|
98
|
+
const contentTypes = `<?xml version="1.0" encoding="UTF-8"?>
|
|
99
|
+
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="model" ContentType="application/vnd.ms-package.3dmanufacturing-3dmodel+xml"/></Types>`;
|
|
100
|
+
const rels = `<?xml version="1.0" encoding="UTF-8"?>
|
|
101
|
+
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Target="/3D/3dmodel.model" Id="rel0" Type="http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel"/></Relationships>`;
|
|
102
|
+
const zipped = zipSync({
|
|
103
|
+
'[Content_Types].xml': strToU8(contentTypes),
|
|
104
|
+
'_rels/.rels': strToU8(rels),
|
|
105
|
+
'3D/3dmodel.model': strToU8(modelXml),
|
|
106
|
+
});
|
|
107
|
+
return zipped.buffer.slice(zipped.byteOffset, zipped.byteOffset + zipped.byteLength);
|
|
108
|
+
}
|
|
109
|
+
/** 组 3MF <model> XML:单位声明与坐标由同一 scale 变量产出(同源,§10.3 规则 3)。 */
|
|
110
|
+
function build3mfModelXml(entries, unit, scale, printConfig) {
|
|
111
|
+
const unitAttr = UNIT_NAME_TO_3MF[unit];
|
|
112
|
+
if (!unitAttr) {
|
|
113
|
+
// 调用方(exportModel)保证 unit 已成对回落;直接到这里 = 内部错误。
|
|
114
|
+
throw new Error(`[export/3mf] unit ${unit} has no 3MF enum value`);
|
|
115
|
+
}
|
|
116
|
+
const objs = [];
|
|
117
|
+
const items = [];
|
|
118
|
+
entries.forEach((e, i) => {
|
|
119
|
+
if (!e.mesh)
|
|
120
|
+
return;
|
|
121
|
+
const pos = scalePositions(e.mesh.positions, scale);
|
|
122
|
+
const verts = [];
|
|
123
|
+
for (let v = 0; v < pos.length; v += 3) {
|
|
124
|
+
verts.push(`<vertex x="${pos[v]}" y="${pos[v + 1]}" z="${pos[v + 2]}"/>`);
|
|
125
|
+
}
|
|
126
|
+
const tris = [];
|
|
127
|
+
for (let t = 0; t < e.mesh.indices.length; t += 3) {
|
|
128
|
+
tris.push(`<triangle v1="${e.mesh.indices[t]}" v2="${e.mesh.indices[t + 1]}" v3="${e.mesh.indices[t + 2]}"/>`);
|
|
129
|
+
}
|
|
130
|
+
const colorXml = e.color
|
|
131
|
+
? `<basematerials><base name="${e.name ?? 'p' + i}" displaycolor="${hexColor(e.color)}"/></basematerials>`
|
|
132
|
+
: '';
|
|
133
|
+
objs.push(`<object id="${i + 1}" type="model" name="${xmlAttr(e.name ?? `part${i + 1}`)}">${colorXml}<mesh><vertices>${verts.join('')}</vertices><triangles>${tris.join('')}</triangles></mesh></object>`);
|
|
134
|
+
items.push(`<item objectid="${i + 1}"/>`);
|
|
135
|
+
});
|
|
136
|
+
const pc = printConfig ? `<metadata name="printConfig">${xmlAttr(String(printConfig))}</metadata>` : '';
|
|
137
|
+
return `<model unit="${unitAttr}" xml:lang="en-US" xmlns="http://schemas.microsoft.com/3dmanufacturing/core/2015/02">${pc}<resources>${objs.join('')}</resources><build>${items.join('')}</build></model>`;
|
|
138
|
+
}
|
|
139
|
+
function xmlAttr(s) {
|
|
140
|
+
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
141
|
+
}
|
|
142
|
+
function hexColor(c) {
|
|
143
|
+
const h = (v) => Math.round(Math.min(1, Math.max(0, v)) * 255).toString(16).padStart(2, '0');
|
|
144
|
+
return `#${h(c[0])}${h(c[1])}${h(c[2])}FF`;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Synchronous twin of {@link exportModel} for hosts already holding a live
|
|
148
|
+
* kernel (CLI, in-process consumers). Same invariants: declared unit ==
|
|
149
|
+
* written coordinate scale. Only 'stl' / 'step' (3mf is also sync but is
|
|
150
|
+
* routed through the same code path below).
|
|
151
|
+
*
|
|
152
|
+
* @param entries - the shapes to export (BREP solids preferred, mesh fallback).
|
|
153
|
+
* @param format - output format: 'stl' | 'step' | '3mf'.
|
|
154
|
+
* @param opts - optional target unit and 3MF print config.
|
|
155
|
+
* @returns the serialized file bytes (ArrayBuffer).
|
|
156
|
+
*/
|
|
157
|
+
export function exportModelSync(entries, format, opts) {
|
|
158
|
+
if (entries.length === 0)
|
|
159
|
+
throw new Error('[export] no exportable entries');
|
|
160
|
+
let unit = opts?.unit ?? 'mm';
|
|
161
|
+
if (format === '3mf' && !UNIT_NAME_TO_3MF[unit])
|
|
162
|
+
unit = 'mm';
|
|
163
|
+
const scale = 1 / UNIT_SCALE[unit];
|
|
164
|
+
if (format === 'stl') {
|
|
165
|
+
const bufs = entries.filter((e) => e.mesh).map((e) => ({ pos: scalePositions(e.mesh.positions, scale), idx: e.mesh.indices }));
|
|
166
|
+
if (bufs.length === 0)
|
|
167
|
+
throw new Error('[export/stl] no mesh entries');
|
|
168
|
+
if (bufs.length === 1)
|
|
169
|
+
return buildStlBufferFromMesh(bufs[0].pos, bufs[0].idx);
|
|
170
|
+
const totalTris = bufs.reduce((n, b) => n + b.idx.length / 3, 0);
|
|
171
|
+
const mergedPos = new Float32Array(bufs.reduce((n, b) => n + b.pos.length, 0));
|
|
172
|
+
const mergedIdx = new Uint32Array(totalTris * 3);
|
|
173
|
+
let po = 0;
|
|
174
|
+
let io2 = 0;
|
|
175
|
+
let base = 0;
|
|
176
|
+
for (const b of bufs) {
|
|
177
|
+
mergedPos.set(b.pos, po);
|
|
178
|
+
for (let i = 0; i < b.idx.length; i++)
|
|
179
|
+
mergedIdx[io2++] = b.idx[i] + base;
|
|
180
|
+
base += b.pos.length / 3;
|
|
181
|
+
po += b.pos.length;
|
|
182
|
+
}
|
|
183
|
+
return buildStlBufferFromMesh(mergedPos, mergedIdx);
|
|
184
|
+
}
|
|
185
|
+
if (format === 'step') {
|
|
186
|
+
const kernel = getBrepApi();
|
|
187
|
+
const ownedHandles = [];
|
|
188
|
+
const stepEntries = entries.map((e) => {
|
|
189
|
+
if (e.solid) {
|
|
190
|
+
if (scale === 1)
|
|
191
|
+
return { solid: e.solid, ...(e.name ? { name: e.name } : {}), ...(e.color ? { color: [...e.color] } : {}) };
|
|
192
|
+
const scaled = kernel.scale(e.solid, { x: 0, y: 0, z: 0 }, scale);
|
|
193
|
+
ownedHandles.push(scaled);
|
|
194
|
+
return { solid: scaled, ...(e.name ? { name: e.name } : {}), ...(e.color ? { color: [...e.color] } : {}) };
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
...(e.mesh ? { mesh: { positions: scalePositions(e.mesh.positions, scale), indices: e.mesh.indices } } : {}),
|
|
198
|
+
...(e.name ? { name: e.name } : {}),
|
|
199
|
+
...(e.color ? { color: [...e.color] } : {}),
|
|
200
|
+
};
|
|
201
|
+
});
|
|
202
|
+
let buffer = exportStepFromSolids(kernel, stepEntries);
|
|
203
|
+
for (const h of ownedHandles) {
|
|
204
|
+
try {
|
|
205
|
+
kernel.release(h);
|
|
206
|
+
}
|
|
207
|
+
catch { /* already released */ }
|
|
208
|
+
}
|
|
209
|
+
if (unit !== 'mm') {
|
|
210
|
+
const text = new TextDecoder().decode(new Uint8Array(buffer));
|
|
211
|
+
buffer = new TextEncoder().encode(rewriteStepUnitEntities(text, unit)).buffer;
|
|
212
|
+
}
|
|
213
|
+
return buffer;
|
|
214
|
+
}
|
|
215
|
+
if (format === '3mf') {
|
|
216
|
+
return build3mfBuffer(build3mfModelXml(entries, unit, scale, opts?.printConfig));
|
|
217
|
+
}
|
|
218
|
+
throw new Error(`[export] unsupported format: ${format}`);
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* 统一导出入口:按 format 分派到各写出器。
|
|
222
|
+
* **内部不变式:写出的坐标刻度 == 文件声明的单位。** 这是本 API 存在的唯一理由。
|
|
223
|
+
* @param entries - 待导出的几何条目(BREP solid 优先,缺则走 mesh)。
|
|
224
|
+
* @param format - 输出格式:'stl' | 'step' | '3mf'。
|
|
225
|
+
* @param opts - 可选的目标单位与 3MF 打印配置。
|
|
226
|
+
* @returns 文件字节
|
|
227
|
+
* @throws 目标单位该格式不支持且无法回落时(不静默降级)
|
|
228
|
+
*/
|
|
229
|
+
export async function exportModel(entries, format, opts) {
|
|
230
|
+
if (entries.length === 0)
|
|
231
|
+
throw new Error('[export] no exportable entries');
|
|
232
|
+
let unit = opts?.unit ?? 'mm';
|
|
233
|
+
// 3MF 枚举回落(§10.3 规则 2):yard 不在枚举 → 成对回落到 mm(坐标+声明)。
|
|
234
|
+
if (format === '3mf' && !UNIT_NAME_TO_3MF[unit]) {
|
|
235
|
+
unit = 'mm';
|
|
236
|
+
}
|
|
237
|
+
// scale = 基准值 → 目标单位刻度(fromBase(1, unit, 'length') 的表实现)。
|
|
238
|
+
const scale = 1 / UNIT_SCALE[unit];
|
|
239
|
+
if (format === 'stl') {
|
|
240
|
+
// STL 无声明:坐标按 unit 刻度写出,调用方保证 unit 语义(当前文档单位)。
|
|
241
|
+
const parts = [];
|
|
242
|
+
for (const e of entries) {
|
|
243
|
+
if (!e.mesh)
|
|
244
|
+
continue; // STL 只吃 mesh;solid 条目由宿主先三角化
|
|
245
|
+
parts.push(buildStlBufferFromMesh(scalePositions(e.mesh.positions, scale), e.mesh.indices));
|
|
246
|
+
}
|
|
247
|
+
if (parts.length === 0)
|
|
248
|
+
throw new Error('[export/stl] no mesh entries');
|
|
249
|
+
if (parts.length === 1)
|
|
250
|
+
return parts[0];
|
|
251
|
+
// 多 mesh:合并顶点后再写(binary STL 无对象概念,多实体合一个 soup)。
|
|
252
|
+
const bufs = entries.filter((e) => e.mesh).map((e) => {
|
|
253
|
+
const pos = scalePositions(e.mesh.positions, scale);
|
|
254
|
+
return { pos, idx: e.mesh.indices };
|
|
255
|
+
});
|
|
256
|
+
const totalTris = bufs.reduce((n, b) => n + b.idx.length / 3, 0);
|
|
257
|
+
const mergedPos = new Float32Array(bufs.reduce((n, b) => n + b.pos.length, 0));
|
|
258
|
+
const mergedIdx = new Uint32Array(totalTris * 3);
|
|
259
|
+
let po = 0;
|
|
260
|
+
let io2 = 0;
|
|
261
|
+
let base = 0;
|
|
262
|
+
for (const b of bufs) {
|
|
263
|
+
mergedPos.set(b.pos, po);
|
|
264
|
+
for (let i = 0; i < b.idx.length; i++)
|
|
265
|
+
mergedIdx[io2++] = b.idx[i] + base;
|
|
266
|
+
base += b.pos.length / 3;
|
|
267
|
+
po += b.pos.length;
|
|
268
|
+
}
|
|
269
|
+
return buildStlBufferFromMesh(mergedPos, mergedIdx);
|
|
270
|
+
}
|
|
271
|
+
if (format === 'step') {
|
|
272
|
+
// 同一 scale 变量喂两类条目(§10.3 规则 7,修 E4):mesh 缩放 positions,
|
|
273
|
+
// solid 用 owned copy 做 kernel.scale —— 不释放调用方缓存里的原句柄。
|
|
274
|
+
const kernel = (await import('../handle-bridge.js')).getBrepApi();
|
|
275
|
+
const ownedHandles = [];
|
|
276
|
+
const stepEntries = entries.map((e) => {
|
|
277
|
+
if (e.solid) {
|
|
278
|
+
if (scale === 1)
|
|
279
|
+
return { solid: e.solid, ...(e.name ? { name: e.name } : {}), ...(e.color ? { color: [...e.color] } : {}) };
|
|
280
|
+
const scaled = kernel.scale(e.solid, { x: 0, y: 0, z: 0 }, scale);
|
|
281
|
+
ownedHandles.push(scaled);
|
|
282
|
+
return { solid: scaled, ...(e.name ? { name: e.name } : {}), ...(e.color ? { color: [...e.color] } : {}) };
|
|
283
|
+
}
|
|
284
|
+
return {
|
|
285
|
+
...(e.mesh ? { mesh: { positions: scalePositions(e.mesh.positions, scale), indices: e.mesh.indices } } : {}),
|
|
286
|
+
...(e.name ? { name: e.name } : {}),
|
|
287
|
+
...(e.color ? { color: [...e.color] } : {}),
|
|
288
|
+
};
|
|
289
|
+
});
|
|
290
|
+
let buffer = exportStepFromSolids(kernel, stepEntries);
|
|
291
|
+
for (const h of ownedHandles) {
|
|
292
|
+
try {
|
|
293
|
+
kernel.release(h);
|
|
294
|
+
}
|
|
295
|
+
catch { /* already released */ }
|
|
296
|
+
}
|
|
297
|
+
// 文本层只替换单位实体(红线:绝不文本级改坐标——坐标已在几何层缩放)。
|
|
298
|
+
if (unit !== 'mm') {
|
|
299
|
+
const text = new TextDecoder().decode(new Uint8Array(buffer));
|
|
300
|
+
const rewritten = rewriteStepUnitEntities(text, unit);
|
|
301
|
+
buffer = new TextEncoder().encode(rewritten).buffer;
|
|
302
|
+
}
|
|
303
|
+
return buffer;
|
|
304
|
+
}
|
|
305
|
+
if (format === '3mf') {
|
|
306
|
+
return build3mfBuffer(build3mfModelXml(entries, unit, scale, opts?.printConfig));
|
|
307
|
+
}
|
|
308
|
+
throw new Error(`[export] unsupported format: ${format}`);
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* 导出后回读声明单位(自检与验收用;与 detectStepUnit 互为对偶)。
|
|
312
|
+
* @param data - the exported file bytes (ArrayBuffer or decoded Uint8Array).
|
|
313
|
+
* @param format - the format the bytes were produced in.
|
|
314
|
+
* @returns the declared unit, or null when the format cannot declare one (STL)
|
|
315
|
+
* or the declaration cannot be parsed.
|
|
316
|
+
*/
|
|
317
|
+
export function readDeclaredUnit(data, format) {
|
|
318
|
+
if (format === 'stl')
|
|
319
|
+
return null;
|
|
320
|
+
const bytes = data instanceof Uint8Array ? data : new Uint8Array(data);
|
|
321
|
+
const text = new TextDecoder().decode(bytes).slice(0, STEP_UNIT_SCAN_PREFIX);
|
|
322
|
+
if (format === '3mf') {
|
|
323
|
+
const m = text.match(/<model[^>]*\sunit="([^"]+)"/);
|
|
324
|
+
if (!m)
|
|
325
|
+
return null;
|
|
326
|
+
for (const [name, val] of Object.entries(UNIT_NAME_TO_3MF)) {
|
|
327
|
+
if (val === m[1])
|
|
328
|
+
return name;
|
|
329
|
+
}
|
|
330
|
+
return null;
|
|
331
|
+
}
|
|
332
|
+
// step
|
|
333
|
+
return detectStepUnit(text);
|
|
334
|
+
}
|
|
335
|
+
//# sourceMappingURL=export-model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"export-model.js","sourceRoot":"","sources":["../../../src/brep/export/export-model.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * exportModel — 统一导出入口(unit-system §10.6 二 / R11 / 裁决 J)\n *\n * 单一真源:坐标单位换算与文件单位声明由**同一次换算**产出(§10.3 规则 3),\n * 声明与坐标必须同源。CLI(writeOutput)与宿主(3d_editor)共用本入口。\n *\n * 格式分派(§10.6 表):\n * - stl:坐标缩放到 unit 刻度,无声明(格式不支持)。\n * - step:预缩放 + 文本层只改写单位实体(§10.3 规则 3a;OCCT 恒写\n * SI_UNIT(.MILLI.,.METRE.),坐标预先缩放到目标刻度后声明必须跟上)。\n * - 3mf:坐标缩放到 unit 刻度 + <model unit> 与坐标同源;yard 不在 3MF\n * 枚举 → 成对回落到 mm(§10.3 规则 2)。\n *\n * 红线(§10.3 3a):绝不文本级改坐标 — 坐标只在几何层缩放,文本层只碰单位实体。\n */\n\nimport { buildStlBufferFromMesh } from './stl'\nimport { zipSync, strToU8 } from 'fflate'\nimport { UNIT_SCALE, type UnitName } from '../../units'\nimport type { BrepHandle } from '../engine/types'\nimport type { BrepEngineApi } from '../engine/primitives'\nimport { getBrepApi } from '../handle-bridge'\nimport { exportStepFromSolids, type StepExportEntry } from './step'\nimport { detectStepUnit, STEP_UNIT_SCAN_PREFIX } from '../../mesh/io'\n\n/** 导出条目:一个 part 的几何来源。精确 BREP 与三角网格二选一,solid 优先。 */\nexport interface ExportEntry {\n /** 精确 BREP 句柄(来自 brep 链,导出 ADVANCED_FACE)。 */\n solid?: BrepHandle\n /** 三角网格(基准单位刻度;调用方只做世界变换烘焙,不做单位缩放)。 */\n mesh?: { positions: Float32Array; indices: Uint32Array }\n /** 实体名(写 STEP PRODUCT 名 / 3MF <object name>)。 */\n name?: string\n /** sRGB 0..1 颜色(STEP 走 XCAF COLOUR_RGB,3MF 走 basematerials)。 */\n color?: readonly [number, number, number]\n}\n\n/** Output formats supported by the unified {@link exportModel} entry. */\nexport type ExportFormat = 'stl' | 'step' | '3mf'\n\n/**\n * Options controlling {@link exportModel}/{@link exportModelSync} output\n * (unit-system §10.3): the declared target unit and optional 3MF print config.\n */\nexport interface ExportOptions {\n /**\n * 目标长度单位。缺省 'mm'。语义是「文件里声明的单位」——\n * 实现负责把坐标换算到该单位刻度并与声明成对写出(§10.3)。\n */\n unit?: UnitName\n /** 3MF 打印配置(可选,原样进 model 层)。 */\n printConfig?: unknown\n}\n\n/** UnitName → 3MF <model unit> 枚举全名(唯一真源;yard 无对应值)。 */\nexport const UNIT_NAME_TO_3MF: Partial<Record<UnitName, 'micron' | 'millimeter' | 'centimeter' | 'inch' | 'foot' | 'meter'>> = {\n mm: 'millimeter',\n cm: 'centimeter',\n m: 'meter',\n micron: 'micron',\n inch: 'inch',\n foot: 'foot',\n // yard 无 3MF 枚举值 — 调用方必须先成对回落到 mm(§10.3 规则 2)。\n}\n\n/** 缩放 Float32 positions 到目标单位刻度(基准值 ÷ unitScale(unit))。 */\nfunction scalePositions(positions: Float32Array, scale: number): Float32Array {\n if (scale === 1) return positions\n const out = new Float32Array(positions.length)\n for (let i = 0; i < positions.length; i++) out[i] = positions[i] * scale\n return out\n}\n\n/** STEP 单位声明改写:SI_UNIT(.MILLI.,.METRE.) → 目标单位实体(只碰单位实体,红线)。 */\nfunction rewriteStepUnitEntities(stepText: string, unit: UnitName): string {\n // mm 是 OCCT 的原生写出形态,无需改写。\n if (unit === 'mm') return stepText\n\n // SI 面积/体积单位实体的前缀同样要跟着换(LENGTH/AREA/VOLUME 挂同一前缀链),\n // 但坐标已缩放 → 只需把 LENGTH 链上的前缀换掉;面积/体积单位在 STEP 里由\n // SI_UNIT 前缀 + 幂次表达,XCAF 写出的实体形态是 SI_UNIT(.MILLI.,.METRE.)\n // 挂在长度、平方与立方三条依赖链上。为保守起见只改写显式 LENGTH_UNIT 链上的\n // SI_UNIT —— 具体做法:全局替换 SI_UNIT 前缀(同一文件里坐标相关单位恒为\n // 长度链;角度 SI_UNIT(.RADIAN.) 不含 METRE 不会被命中)。\n let out = stepText\n\n if (unit === 'cm' || unit === 'm' || unit === 'micron') {\n const prefix = unit === 'cm' ? '.CENTI.' : unit === 'm' ? '$' : '.MICRO.'\n // OCCT 写出恒为 SI_UNIT(.MILLI.,.METRE.)(实测);$ 前缀形态(无前缀=米)\n // 在 OCCT 输出中不会出现,但容错处理(若上游将来变化)。\n out = out.replace(/SI_UNIT\\s*\\(\\s*\\.MILLI\\.\\s*,\\s*\\.METRE\\.\\s*\\)/g,\n prefix === '$' ? 'SI_UNIT($,.METRE.)' : `SI_UNIT(${prefix},.METRE.)`)\n return out\n }\n\n // inch / foot:CONVERSION_BASED_UNIT 形态。需要注入换算因子实体并替换\n // LENGTH_UNIT 依赖。最简形态:把 SI_UNIT(.MILLI.,.METRE.) 替换为\n // CONVERSION_BASED_UNIT('INCH', #factor),并附加 MEASURE_WITH_UNIT 实体。\n // 实体编号:取文件内最大 #n + 1、+2(附加实体挂在文件尾部 DATA 段内合法)。\n const inchFactor = unit === 'inch' ? 25.4 : 304.8\n const name = unit === 'inch' ? 'INCH' : 'FOOT'\n\n // 1) 收集现有最大实体编号\n let maxId = 0\n for (const m of out.matchAll(/#(\\d+)\\s*=/g)) {\n const id = Number(m[1])\n if (id > maxId) maxId = id\n }\n const lenId = maxId + 1\n const convId = maxId + 2\n\n // 2) 找到 LENGTH_UNIT() 里挂 SI_UNIT 的实体行,替换为 CONVERSION_BASED_UNIT\n // 并把 SI_UNIT 挂到 LENGTH_MEASURE 上。OCCT 输出形态(实测):\n // #n=(LENGTH_UNIT()NAMED_UNIT(*)SI_UNIT(.MILLI.,.METRE.));\n const siLine = out.match(/#(\\d+)\\s*=\\s*\\([^)]*SI_UNIT\\s*\\(\\s*\\.MILLI\\.\\s*,\\s*\\.METRE\\.\\s*\\)[^)]*\\)\\s*;/)\n if (!siLine) {\n throw new Error(`[export/step] no SI_UNIT(.MILLI.,.METRE.) entity found; cannot rewrite declaration to ${name}`)\n }\n\n // 3) 替换原 SI_UNIT 实体为 CONVERSION_BASED_UNIT,插入换算因子与基准米实体:\n // #si=(LENGTH_UNIT()NAMED_UNIT(*)SI_UNIT(.MILLI.,.METRE.));\n // → #si=(LENGTH_UNIT()NAMED_UNIT(*)CONVERSION_BASED_UNIT('INCH',#conv));\n // #conv=LENGTH_MEASURE_WITH_UNIT(LENGTH_MEASURE(25.4),#len);\n // #len=(LENGTH_UNIT()NAMED_UNIT(*)SI_UNIT($,.METRE.));\n out = out.replace(siLine[0], siLine[0].replace(/SI_UNIT\\s*\\(\\s*\\.MILLI\\.\\s*,\\s*\\.METRE\\.\\s*\\)/, `CONVERSION_BASED_UNIT('${name}',#${convId})`))\n // 在 DATA 段结束前(ENDSEC;)插入新实体。\n const insert = [\n `#${convId}=LENGTH_MEASURE_WITH_UNIT(LENGTH_MEASURE(${inchFactor}),#${lenId});`,\n `#${lenId}=(LENGTH_UNIT()NAMED_UNIT(*)SI_UNIT($,.METRE.));`,\n ].join('\\n')\n out = out.replace(/ENDSEC\\s*;/, `${insert}\\nENDSEC;`)\n return out\n}\n\n/** 3MF写出:最小 ZIP 容器(deflate),落 3D/3dmodel.model + [Content_Types].xml + _rels。 */\nfunction build3mfBuffer(modelXml: string): ArrayBuffer {\n const contentTypes = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Types xmlns=\"http://schemas.openxmlformats.org/package/2006/content-types\"><Default Extension=\"rels\" ContentType=\"application/vnd.openxmlformats-package.relationships+xml\"/><Default Extension=\"model\" ContentType=\"application/vnd.ms-package.3dmanufacturing-3dmodel+xml\"/></Types>`\n const rels = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\"><Relationship Target=\"/3D/3dmodel.model\" Id=\"rel0\" Type=\"http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel\"/></Relationships>`\n\n const zipped = zipSync({\n '[Content_Types].xml': strToU8(contentTypes),\n '_rels/.rels': strToU8(rels),\n '3D/3dmodel.model': strToU8(modelXml),\n })\n return zipped.buffer.slice(zipped.byteOffset, zipped.byteOffset + zipped.byteLength) as ArrayBuffer\n}\n\n/** 组 3MF <model> XML:单位声明与坐标由同一 scale 变量产出(同源,§10.3 规则 3)。 */\nfunction build3mfModelXml(entries: ExportEntry[], unit: UnitName, scale: number, printConfig?: unknown): string {\n const unitAttr = UNIT_NAME_TO_3MF[unit]\n if (!unitAttr) {\n // 调用方(exportModel)保证 unit 已成对回落;直接到这里 = 内部错误。\n throw new Error(`[export/3mf] unit ${unit} has no 3MF enum value`)\n }\n const objs: string[] = []\n const items: string[] = []\n entries.forEach((e, i) => {\n if (!e.mesh) return\n const pos = scalePositions(e.mesh.positions, scale)\n const verts: string[] = []\n for (let v = 0; v < pos.length; v += 3) {\n verts.push(`<vertex x=\"${pos[v]}\" y=\"${pos[v + 1]}\" z=\"${pos[v + 2]}\"/>`)\n }\n const tris: string[] = []\n for (let t = 0; t < e.mesh.indices.length; t += 3) {\n tris.push(`<triangle v1=\"${e.mesh.indices[t]}\" v2=\"${e.mesh.indices[t + 1]}\" v3=\"${e.mesh.indices[t + 2]}\"/>`)\n }\n const colorXml = e.color\n ? `<basematerials><base name=\"${e.name ?? 'p' + i}\" displaycolor=\"${hexColor(e.color)}\"/></basematerials>`\n : ''\n objs.push(`<object id=\"${i + 1}\" type=\"model\" name=\"${xmlAttr(e.name ?? `part${i + 1}`)}\">${colorXml}<mesh><vertices>${verts.join('')}</vertices><triangles>${tris.join('')}</triangles></mesh></object>`)\n items.push(`<item objectid=\"${i + 1}\"/>`)\n })\n const pc = printConfig ? `<metadata name=\"printConfig\">${xmlAttr(String(printConfig))}</metadata>` : ''\n return `<model unit=\"${unitAttr}\" xml:lang=\"en-US\" xmlns=\"http://schemas.microsoft.com/3dmanufacturing/core/2015/02\">${pc}<resources>${objs.join('')}</resources><build>${items.join('')}</build></model>`\n}\n\nfunction xmlAttr(s: string): string {\n return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"')\n}\n\nfunction hexColor(c: readonly [number, number, number]): string {\n const h = (v: number) => Math.round(Math.min(1, Math.max(0, v)) * 255).toString(16).padStart(2, '0')\n return `#${h(c[0])}${h(c[1])}${h(c[2])}FF`\n}\n\n/**\n * Synchronous twin of {@link exportModel} for hosts already holding a live\n * kernel (CLI, in-process consumers). Same invariants: declared unit ==\n * written coordinate scale. Only 'stl' / 'step' (3mf is also sync but is\n * routed through the same code path below).\n *\n * @param entries - the shapes to export (BREP solids preferred, mesh fallback).\n * @param format - output format: 'stl' | 'step' | '3mf'.\n * @param opts - optional target unit and 3MF print config.\n * @returns the serialized file bytes (ArrayBuffer).\n */\nexport function exportModelSync(\n entries: ExportEntry[],\n format: ExportFormat,\n opts?: ExportOptions,\n): ArrayBuffer {\n if (entries.length === 0) throw new Error('[export] no exportable entries')\n\n let unit = opts?.unit ?? 'mm'\n if (format === '3mf' && !UNIT_NAME_TO_3MF[unit]) unit = 'mm'\n const scale = 1 / UNIT_SCALE[unit]\n\n if (format === 'stl') {\n const bufs = entries.filter((e) => e.mesh).map((e) => ({ pos: scalePositions(e.mesh!.positions, scale), idx: e.mesh!.indices }))\n if (bufs.length === 0) throw new Error('[export/stl] no mesh entries')\n if (bufs.length === 1) return buildStlBufferFromMesh(bufs[0]!.pos, bufs[0]!.idx)\n const totalTris = bufs.reduce((n, b) => n + b.idx.length / 3, 0)\n const mergedPos = new Float32Array(bufs.reduce((n, b) => n + b.pos.length, 0))\n const mergedIdx = new Uint32Array(totalTris * 3)\n let po = 0\n let io2 = 0\n let base = 0\n for (const b of bufs) {\n mergedPos.set(b.pos, po)\n for (let i = 0; i < b.idx.length; i++) mergedIdx[io2++] = b.idx[i] + base\n base += b.pos.length / 3\n po += b.pos.length\n }\n return buildStlBufferFromMesh(mergedPos, mergedIdx)\n }\n\n if (format === 'step') {\n const kernel = getBrepApi()\n const ownedHandles: BrepHandle[] = []\n const stepEntries: StepExportEntry[] = entries.map((e) => {\n if (e.solid) {\n if (scale === 1) return { solid: e.solid, ...(e.name ? { name: e.name } : {}), ...(e.color ? { color: [...e.color] as [number, number, number] } : {}) }\n const scaled = kernel.scale(e.solid, { x: 0, y: 0, z: 0 }, scale)\n ownedHandles.push(scaled)\n return { solid: scaled, ...(e.name ? { name: e.name } : {}), ...(e.color ? { color: [...e.color] as [number, number, number] } : {}) }\n }\n return {\n ...(e.mesh ? { mesh: { positions: scalePositions(e.mesh.positions, scale), indices: e.mesh.indices } } : {}),\n ...(e.name ? { name: e.name } : {}),\n ...(e.color ? { color: [...e.color] as [number, number, number] } : {}),\n }\n })\n let buffer = exportStepFromSolids(kernel, stepEntries)\n for (const h of ownedHandles) {\n try { kernel.release(h) } catch { /* already released */ }\n }\n if (unit !== 'mm') {\n const text = new TextDecoder().decode(new Uint8Array(buffer))\n buffer = new TextEncoder().encode(rewriteStepUnitEntities(text, unit)).buffer\n }\n return buffer\n }\n\n if (format === '3mf') {\n return build3mfBuffer(build3mfModelXml(entries, unit, scale, opts?.printConfig))\n }\n\n throw new Error(`[export] unsupported format: ${format}`)\n}\n\n/**\n * 统一导出入口:按 format 分派到各写出器。\n * **内部不变式:写出的坐标刻度 == 文件声明的单位。** 这是本 API 存在的唯一理由。\n * @param entries - 待导出的几何条目(BREP solid 优先,缺则走 mesh)。\n * @param format - 输出格式:'stl' | 'step' | '3mf'。\n * @param opts - 可选的目标单位与 3MF 打印配置。\n * @returns 文件字节\n * @throws 目标单位该格式不支持且无法回落时(不静默降级)\n */\nexport async function exportModel(\n entries: ExportEntry[],\n format: ExportFormat,\n opts?: ExportOptions,\n): Promise<ArrayBuffer> {\n if (entries.length === 0) throw new Error('[export] no exportable entries')\n\n let unit = opts?.unit ?? 'mm'\n\n // 3MF 枚举回落(§10.3 规则 2):yard 不在枚举 → 成对回落到 mm(坐标+声明)。\n if (format === '3mf' && !UNIT_NAME_TO_3MF[unit]) {\n unit = 'mm'\n }\n\n // scale = 基准值 → 目标单位刻度(fromBase(1, unit, 'length') 的表实现)。\n const scale = 1 / UNIT_SCALE[unit]\n\n if (format === 'stl') {\n // STL 无声明:坐标按 unit 刻度写出,调用方保证 unit 语义(当前文档单位)。\n const parts: ArrayBuffer[] = []\n for (const e of entries) {\n if (!e.mesh) continue // STL 只吃 mesh;solid 条目由宿主先三角化\n parts.push(buildStlBufferFromMesh(scalePositions(e.mesh.positions, scale), e.mesh.indices))\n }\n if (parts.length === 0) throw new Error('[export/stl] no mesh entries')\n if (parts.length === 1) return parts[0]!\n // 多 mesh:合并顶点后再写(binary STL 无对象概念,多实体合一个 soup)。\n const bufs = entries.filter((e) => e.mesh).map((e) => {\n const pos = scalePositions(e.mesh!.positions, scale)\n return { pos, idx: e.mesh!.indices }\n })\n const totalTris = bufs.reduce((n, b) => n + b.idx.length / 3, 0)\n const mergedPos = new Float32Array(bufs.reduce((n, b) => n + b.pos.length, 0))\n const mergedIdx = new Uint32Array(totalTris * 3)\n let po = 0\n let io2 = 0\n let base = 0\n for (const b of bufs) {\n mergedPos.set(b.pos, po)\n for (let i = 0; i < b.idx.length; i++) mergedIdx[io2++] = b.idx[i] + base\n base += b.pos.length / 3\n po += b.pos.length\n }\n return buildStlBufferFromMesh(mergedPos, mergedIdx)\n }\n\n if (format === 'step') {\n // 同一 scale 变量喂两类条目(§10.3 规则 7,修 E4):mesh 缩放 positions,\n // solid 用 owned copy 做 kernel.scale —— 不释放调用方缓存里的原句柄。\n const kernel = (await import('../handle-bridge')).getBrepApi() as BrepEngineApi\n const ownedHandles: BrepHandle[] = []\n const stepEntries: StepExportEntry[] = entries.map((e) => {\n if (e.solid) {\n if (scale === 1) return { solid: e.solid, ...(e.name ? { name: e.name } : {}), ...(e.color ? { color: [...e.color] as [number, number, number] } : {}) }\n const scaled = kernel.scale(e.solid, { x: 0, y: 0, z: 0 }, scale)\n ownedHandles.push(scaled)\n return { solid: scaled, ...(e.name ? { name: e.name } : {}), ...(e.color ? { color: [...e.color] as [number, number, number] } : {}) }\n }\n return {\n ...(e.mesh ? { mesh: { positions: scalePositions(e.mesh.positions, scale), indices: e.mesh.indices } } : {}),\n ...(e.name ? { name: e.name } : {}),\n ...(e.color ? { color: [...e.color] as [number, number, number] } : {}),\n }\n })\n let buffer = exportStepFromSolids(kernel, stepEntries)\n for (const h of ownedHandles) {\n try { kernel.release(h) } catch { /* already released */ }\n }\n // 文本层只替换单位实体(红线:绝不文本级改坐标——坐标已在几何层缩放)。\n if (unit !== 'mm') {\n const text = new TextDecoder().decode(new Uint8Array(buffer))\n const rewritten = rewriteStepUnitEntities(text, unit)\n buffer = new TextEncoder().encode(rewritten).buffer\n }\n return buffer\n }\n\n if (format === '3mf') {\n return build3mfBuffer(build3mfModelXml(entries, unit, scale, opts?.printConfig))\n }\n\n throw new Error(`[export] unsupported format: ${format}`)\n}\n\n/**\n * 导出后回读声明单位(自检与验收用;与 detectStepUnit 互为对偶)。\n * @param data - the exported file bytes (ArrayBuffer or decoded Uint8Array).\n * @param format - the format the bytes were produced in.\n * @returns the declared unit, or null when the format cannot declare one (STL)\n * or the declaration cannot be parsed.\n */\nexport function readDeclaredUnit(data: ArrayBuffer | Uint8Array, format: ExportFormat): UnitName | null {\n if (format === 'stl') return null\n const bytes = data instanceof Uint8Array ? data : new Uint8Array(data)\n const text = new TextDecoder().decode(bytes).slice(0, STEP_UNIT_SCAN_PREFIX)\n\n if (format === '3mf') {\n const m = text.match(/<model[^>]*\\sunit=\"([^\"]+)\"/)\n if (!m) return null\n for (const [name, val] of Object.entries(UNIT_NAME_TO_3MF)) {\n if (val === m[1]) return name as UnitName\n }\n return null\n }\n\n // step\n return detectStepUnit(text)\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"primitives-brep.d.ts","sourceRoot":"","sources":["../../src/brep/primitives-brep.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAMH,OAAO,KAAK,EACV,KAAK,EACL,SAAS,EACT,YAAY,EACZ,cAAc,EACd,UAAU,EACV,WAAW,EACZ,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"primitives-brep.d.ts","sourceRoot":"","sources":["../../src/brep/primitives-brep.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAMH,OAAO,KAAK,EACV,KAAK,EACL,SAAS,EACT,YAAY,EACZ,cAAc,EACd,UAAU,EACV,WAAW,EACZ,MAAM,eAAe,CAAA;AA2DtB;;;;GAIG;AACH,wBAAsB,OAAO,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,KAAK,CAAC,CAE/D;AAED;;;;GAIG;AACH,wBAAsB,UAAU,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,KAAK,CAAC,CAErE;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,KAAK,CAAC,CAEzE;AAED;;;;GAIG;AACH,wBAAsB,QAAQ,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,KAAK,CAAC,CAEjE;AAED;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,KAAK,CAAC,CAEnE"}
|
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
import { getBrepEngine } from './engine/registry.js';
|
|
22
22
|
import { primitiveToBrepSolid } from '../primitives/brep-primitives.js';
|
|
23
23
|
import { clampNRad } from '../mesh/types.js';
|
|
24
|
+
import { DEFAULT_LINEAR_DEFLECTION } from '../tolerance.js';
|
|
25
|
+
import { mm } from '../units.js';
|
|
24
26
|
// ─── 内部工具 ───
|
|
25
27
|
/**
|
|
26
28
|
* 将 segments 数转换为 OCCT meshShape 的 angularDeflection。
|
|
@@ -51,7 +53,7 @@ async function brepToShape(type, params, segments) {
|
|
|
51
53
|
// 三角化为显示 mesh
|
|
52
54
|
const angularDeflection = segmentsToAngularDeflection(segments);
|
|
53
55
|
const mesh = kernel.meshShape(solid, {
|
|
54
|
-
linearDeflection:
|
|
56
|
+
linearDeflection: DEFAULT_LINEAR_DEFLECTION.as(mm),
|
|
55
57
|
angularDeflection,
|
|
56
58
|
});
|
|
57
59
|
// 返回 Shape(与 mesh 路径格式一致)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"primitives-brep.js","sourceRoot":"","sources":["../../src/brep/primitives-brep.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AACjD,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAA;AAWpE,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"primitives-brep.js","sourceRoot":"","sources":["../../src/brep/primitives-brep.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AACjD,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAA;AAWpE,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AACzC,OAAO,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAA;AACxD,OAAO,EAAE,EAAE,EAAE,MAAM,UAAU,CAAA;AAE7B,eAAe;AAEf;;;;;GAKG;AACH,SAAS,2BAA2B,CAAC,QAAiB;IACpD,MAAM,IAAI,GAAG,QAAQ,IAAI,EAAE,CAAA,CAAC,wCAAwC;IACpE,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,CAAA;AAC1C,CAAC;AAED;;;;;;;;;GASG;AACH,KAAK,UAAU,WAAW,CACxB,IAAuD,EACvD,MAA4E,EAC5E,QAAiB;IAEjB,MAAM,MAAM,GAAkB,CAAC,MAAM,aAAa,EAAE,CAAC,CAAC,UAAU,CAAA;IAEhE,0CAA0C;IAC1C,MAAM,MAAM,GAAG,oBAAoB,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,CAAA;IACzD,MAAM,KAAK,GAAe,MAAM,CAAC,KAAK,CAAA;IAEtC,IAAI,CAAC;QACH,cAAc;QACd,MAAM,iBAAiB,GAAG,2BAA2B,CAAC,QAAQ,CAAC,CAAA;QAC/D,MAAM,IAAI,GAAG,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE;YACnC,gBAAgB,EAAE,yBAAyB,CAAC,EAAE,CAAC,EAAE,CAAC;YAClD,iBAAiB;SAClB,CAAC,CAAA;QAEF,0BAA0B;QAC1B,OAAO;YACL,SAAS,EAAE,IAAI,YAAY,CAAC,IAAI,CAAC,SAAS,CAAC;YAC3C,OAAO,EAAE,IAAI,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC;SACvC,CAAA;IACH,CAAC;YAAS,CAAC;QACT,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;IACvB,CAAC;AACH,CAAC;AAED,sBAAsB;AAEtB;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,MAAiB;IAC7C,OAAO,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;AACpC,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,MAAoB;IACrD,OAAO,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAA;AAC/E,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,MAAsB;IACzD,OAAO,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,CAAC,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAA;AACjF,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,MAAkB;IACjD,OAAO,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAA;AAC7E,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,MAAmB;IACjD,OAAO,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;AACrC,CAAC","sourcesContent":["/**\n * mesh BREP 基本体 API — 使用 OCCT 精确实体构造\n *\n * See docs/api-contract.md §8 (dual-path geometry contract: BREP / Mesh).\n *\n * 与 `primitives.ts`(mesh 路径)的对照:\n * - mesh 路径:THREE 参数几何 → geoToManifoldMesh → Shape(三角网格近似)\n * - BREP 路径:OCCT 精确实体 → meshShape 三角化 → Shape(显示用 mesh,但底层是精确 BREP)\n *\n * 两条路径返回的 Shape 都是 { positions, indices },显示管线一致。\n * 差异仅在:BREP 路径的 Shape 由 OCCT 精确实体三角化得到,曲面精度更高;\n * 且导出 STEP 时可短路为原生 ADVANCED_FACE(而非 faceted 重建)。\n *\n * segments 参数的处理:\n * - mesh 路径:segments 直接控制 THREE 几何体的分段数\n * - BREP 路径:segments 转换为 angularDeflection(≈2π/segments)控制 OCCT 三角化精度\n * BREP 实体本身是精确的,segments 只影响显示 mesh 的精度\n *\n * 坐标系:Z-up、毫米,与 mesh 路径一致。\n */\n\nimport { getBrepEngine } from './engine/registry'\nimport { primitiveToBrepSolid } from '../primitives/brep-primitives'\nimport type { BrepHandle } from './engine/types'\nimport type { BrepEngineApi } from './engine/primitives'\nimport type {\n Shape,\n BoxParams,\n SphereParams,\n CylinderParams,\n ConeParams,\n WedgeParams,\n} from '../mesh/types'\nimport { clampNRad } from '../mesh/types'\nimport { DEFAULT_LINEAR_DEFLECTION } from '../tolerance'\nimport { mm } from '../units'\n\n// ─── 内部工具 ───\n\n/**\n * 将 segments 数转换为 OCCT meshShape 的 angularDeflection。\n *\n * segments=32 → angularDeflection≈2π/32≈0.196 rad\n * segments 越大,三角化越精细。\n */\nfunction segmentsToAngularDeflection(segments?: number): number {\n const segs = segments ?? 64 // 缺省 64(= brepjs standard 等效,§5.0/§5.1)\n return (2 * Math.PI) / Math.max(3, segs)\n}\n\n/**\n * 用 OCCT 构造 BREP 实体并三角化为 Shape。\n *\n * 内部流程:注册表取当前 BREP 引擎 → primitiveToBrepSolid(引擎精确构造)→ meshShape → release → Shape\n *\n * @param type primitive 类型(cube/sphere/cylinder/cone/wedge)\n * @param params 完整参数对象\n * @param segments 可选分段数(影响三角化精度,不影响 BREP 实体精度)\n * @returns Shape(三角化 mesh,供显示用)\n */\nasync function brepToShape(\n type: 'cube' | 'sphere' | 'cylinder' | 'cone' | 'wedge',\n params: BoxParams | SphereParams | CylinderParams | ConeParams | WedgeParams,\n segments?: number,\n): Promise<Shape> {\n const kernel: BrepEngineApi = (await getBrepEngine()).primitives\n\n // 用升级后的 primitiveToBrepSolid 构造 OCCT 精确实体\n const result = primitiveToBrepSolid(kernel, type, params)\n const solid: BrepHandle = result.solid\n\n try {\n // 三角化为显示 mesh\n const angularDeflection = segmentsToAngularDeflection(segments)\n const mesh = kernel.meshShape(solid, {\n linearDeflection: DEFAULT_LINEAR_DEFLECTION.as(mm),\n angularDeflection,\n })\n\n // 返回 Shape(与 mesh 路径格式一致)\n return {\n positions: new Float32Array(mesh.positions),\n indices: new Uint32Array(mesh.indices),\n }\n } finally {\n kernel.release(solid)\n }\n}\n\n// ─── BREP 创建 API ───\n\n/**\n * Create a box with an OCCT exact solid and tessellate it into a Shape.\n * @param params - the box parameters.\n * @returns a Promise resolving to the tessellated Shape.\n */\nexport async function boxBrep(params: BoxParams): Promise<Shape> {\n return brepToShape('cube', params)\n}\n\n/**\n * Create a sphere with an OCCT exact solid and tessellate it into a Shape.\n * @param params - the sphere parameters.\n * @returns a Promise resolving to the tessellated Shape.\n */\nexport async function sphereBrep(params: SphereParams): Promise<Shape> {\nreturn brepToShape('sphere', params, clampNRad(params.nRad ?? params.segments))\n}\n\n/**\n * Create a cylinder with an OCCT exact solid and tessellate it into a Shape.\n * @param params - the cylinder parameters.\n * @returns a Promise resolving to the tessellated Shape.\n */\nexport async function cylinderBrep(params: CylinderParams): Promise<Shape> {\nreturn brepToShape('cylinder', params, clampNRad(params.nRad ?? params.segments))\n}\n\n/**\n * Create a cone with an OCCT exact solid and tessellate it into a Shape.\n * @param params - the cone parameters.\n * @returns a Promise resolving to the tessellated Shape.\n */\nexport async function coneBrep(params: ConeParams): Promise<Shape> {\nreturn brepToShape('cone', params, clampNRad(params.nRad ?? params.segments))\n}\n\n/**\n * Create a wedge with an OCCT exact solid and tessellate it into a Shape.\n * @param params - the wedge parameters.\n * @returns a Promise resolving to the tessellated Shape.\n */\nexport async function wedgeBrep(params: WedgeParams): Promise<Shape> {\n return brepToShape('wedge', params)\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"brepkitKernel.d.ts","sourceRoot":"","sources":["../../src/brepkit-kernel/brepkitKernel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAmB,KAAK,aAAa,EAAE,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"brepkitKernel.d.ts","sourceRoot":"","sources":["../../src/brepkit-kernel/brepkitKernel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAmB,KAAK,aAAa,EAAE,MAAM,eAAe,CAAA;AAGnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAA;AAwI9D,oDAAoD;AACpD,KAAK,mBAAmB,GAAG,aAAa,GAAG;IAAE,oBAAoB,IAAI,MAAM,CAAA;CAAE,CAAA;AAuC7E;;;;;GAKG;AACH,wBAAsB,uBAAuB,IAAI,OAAO,CAAC,mBAAmB,CAAC,CA65B5E;AAoDD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,IAAI,aAAa,CAKhD;AAED,8BAA8B;AAC9B,wBAAgB,cAAc,IAAI,IAAI,CAKrC;AAED,2EAA2E;AAC3E,MAAM,MAAM,iBAAiB,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,uBAAuB,CAAC,CAAC,CAAA"}
|
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
* 由宿主标记 BREP 链中断(不在此层静默处理)。
|
|
20
20
|
*/
|
|
21
21
|
import { initBrepkitWasm } from './brepkitWasm.js';
|
|
22
|
+
import { DEFAULT_LINEAR_DEFLECTION } from '../tolerance.js';
|
|
23
|
+
import { mm } from '../units.js';
|
|
22
24
|
// ── 句柄桥接:brepkit number 句柄 ↔ BrepHandle(零运行时成本,与 occt 适配器同构) ──
|
|
23
25
|
const asHandle = (n) => n;
|
|
24
26
|
const asNum = (h) => h;
|
|
@@ -664,7 +666,7 @@ export async function createBrepkitPrimitives() {
|
|
|
664
666
|
},
|
|
665
667
|
// ── 三角化(BREP→mesh 唯一出口) ──
|
|
666
668
|
meshShape(shape, options) {
|
|
667
|
-
const deflection = options?.linearDeflection ??
|
|
669
|
+
const deflection = options?.linearDeflection ?? DEFAULT_LINEAR_DEFLECTION.as(mm);
|
|
668
670
|
const angular = options?.angularDeflection ?? 0.5;
|
|
669
671
|
const s = asNum(shape);
|
|
670
672
|
// 非实体句柄按已知集合静态分发(见文件头 GOTCHA:brepkit 无 shape-type 分类 API)。
|
|
@@ -682,7 +684,7 @@ export async function createBrepkitPrimitives() {
|
|
|
682
684
|
return solidMesh(s, deflection, angular);
|
|
683
685
|
},
|
|
684
686
|
wireframe(shape, deflection) {
|
|
685
|
-
const d = deflection ??
|
|
687
|
+
const d = deflection ?? DEFAULT_LINEAR_DEFLECTION.as(mm);
|
|
686
688
|
const s = asNum(shape);
|
|
687
689
|
// GOTCHA:meshEdgesAll 是 solid-only API——buildTopologyFromMesh 会对 face/wire/edge
|
|
688
690
|
// 产物调 wireframe(...),传入非实体句柄即抛 "invalid solid handle"。按已知集合分发:
|