@faicad/faijs 0.17.0 → 0.18.0
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/api-namespace.js +2 -2
- package/dist/api/api-namespace.js.map +1 -1
- package/dist/api/boolean.d.ts.map +1 -1
- package/dist/api/boolean.js +64 -26
- package/dist/api/boolean.js.map +1 -1
- package/dist/api/brep-mirror/booleanFns.d.ts.map +1 -1
- package/dist/api/brep-mirror/booleanFns.js +11 -6
- package/dist/api/brep-mirror/booleanFns.js.map +1 -1
- package/dist/api/brep-mirror/topologyFns.d.ts.map +1 -1
- package/dist/api/brep-mirror/topologyFns.js +4 -1
- package/dist/api/brep-mirror/topologyFns.js.map +1 -1
- package/dist/api/chamfer.d.ts +12 -8
- package/dist/api/chamfer.d.ts.map +1 -1
- package/dist/api/chamfer.js +52 -23
- package/dist/api/chamfer.js.map +1 -1
- package/dist/api/extrude.d.ts.map +1 -1
- package/dist/api/extrude.js +17 -2
- package/dist/api/extrude.js.map +1 -1
- package/dist/api/generated/operations.js +5 -5
- package/dist/api/generated/operations.js.map +1 -1
- package/dist/api/generated/script-face-manifest.js +12 -12
- package/dist/api/generated/script-face-manifest.js.map +1 -1
- package/dist/api/generated/sketching.js +1 -1
- package/dist/api/generated/sketching.js.map +1 -1
- package/dist/api/generated/topology.js +7 -7
- package/dist/api/generated/topology.js.map +1 -1
- package/dist/api/index.d.ts +2 -1
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +1 -1
- package/dist/api/index.js.map +1 -1
- package/dist/api/internal/profile-wire.d.ts +3 -3
- package/dist/api/internal/profile-wire.js +3 -3
- package/dist/api/internal/profile-wire.js.map +1 -1
- package/dist/api/loft.d.ts +3 -3
- package/dist/api/loft.js +3 -3
- package/dist/api/loft.js.map +1 -1
- package/dist/api/profile.d.ts +89 -0
- package/dist/api/profile.d.ts.map +1 -0
- package/dist/api/profile.js +194 -0
- package/dist/api/profile.js.map +1 -0
- package/dist/api/replicate.d.ts.map +1 -1
- package/dist/api/replicate.js +15 -14
- package/dist/api/replicate.js.map +1 -1
- package/dist/api/surface/arg-spec.d.ts.map +1 -1
- package/dist/api/surface/arg-spec.js +20 -15
- package/dist/api/surface/arg-spec.js.map +1 -1
- package/dist/api/sweep.d.ts +2 -2
- package/dist/api/sweep.js +2 -2
- package/dist/api/sweep.js.map +1 -1
- package/dist/api/thicken.d.ts +3 -3
- package/dist/api/thicken.js +3 -3
- package/dist/api/thicken.js.map +1 -1
- package/dist/brep/engine/adapters/brepkit.d.ts.map +1 -1
- package/dist/brep/engine/adapters/brepkit.js +28 -1
- package/dist/brep/engine/adapters/brepkit.js.map +1 -1
- package/dist/brep/engine/adapters/occt.d.ts.map +1 -1
- package/dist/brep/engine/adapters/occt.js +12 -0
- package/dist/brep/engine/adapters/occt.js.map +1 -1
- package/dist/brep/engine/types.d.ts +1 -1
- package/dist/brep/engine/types.d.ts.map +1 -1
- package/dist/brep/engine/types.js.map +1 -1
- package/dist/brepkit-kernel/brepkitKernel.d.ts.map +1 -1
- package/dist/brepkit-kernel/brepkitKernel.js +463 -91
- package/dist/brepkit-kernel/brepkitKernel.js.map +1 -1
- package/dist/brepkit-kernel/multi-version-test.d.ts +40 -0
- package/dist/brepkit-kernel/multi-version-test.d.ts.map +1 -0
- package/dist/brepkit-kernel/multi-version-test.js +89 -0
- package/dist/brepkit-kernel/multi-version-test.js.map +1 -0
- package/dist/lang/symbol-table.generated.d.ts +1 -1
- package/dist/lang/symbol-table.generated.js +1 -1
- package/dist/lang/symbol-table.generated.js.map +1 -1
- package/package.json +1 -1
|
@@ -102,21 +102,30 @@ function mapEvolution(kernel, raw, resultHandle, inputFaceHashes, hashUpperBound
|
|
|
102
102
|
// raw: {solid, evolution:{modified:{oldHandle:[newHandles]}, generated:{oldHandle:[new]}, deleted:[oldHandles]}}
|
|
103
103
|
const parsed = JSON.parse(raw);
|
|
104
104
|
const evo = parsed.evolution ?? { modified: {}, generated: {}, deleted: [] };
|
|
105
|
-
const modifiedKeys = new Set(Object.keys(evo.modified ?? {}).map(Number));
|
|
106
105
|
const deletedKeys = new Set((evo.deleted ?? []).map(Number));
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
//
|
|
106
|
+
// registry 登记的是 hash→handle(输入面)。下游 decodeEvolution/decodeHashEvolution 期望
|
|
107
|
+
// modified 为 OCCT 分段格式 [inHash, count, outHash1, outHash2, ...](见 face-evolution.ts),
|
|
108
|
+
// 不是扁平 hash 列表——GOTCHA:曾误返回扁平列表,把 modified[1](一个 hash ~1e9)当成
|
|
109
|
+
// count,导致 decodeHashEvolution 空转 17 亿次 push 抛 "Invalid array length"(2026-09-26 实证)。
|
|
110
|
+
// 故这里反向建 handle→hash 表,逐条目拼出 [inHash, outCount, ...outHashes]。
|
|
110
111
|
const registry = hashRegistry.get(kernel);
|
|
112
|
+
const handleToHash = new Map();
|
|
113
|
+
if (registry)
|
|
114
|
+
for (const [h, handle] of registry)
|
|
115
|
+
handleToHash.set(handle, h);
|
|
111
116
|
const modified = [];
|
|
112
117
|
const deleted = [];
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
118
|
+
for (const oldHandle of deletedKeys) {
|
|
119
|
+
const h = handleToHash.get(oldHandle);
|
|
120
|
+
if (h !== undefined)
|
|
121
|
+
deleted.push(h);
|
|
122
|
+
}
|
|
123
|
+
for (const [oldHandleStr, newHandles] of Object.entries(evo.modified ?? {})) {
|
|
124
|
+
const inHash = handleToHash.get(Number(oldHandleStr));
|
|
125
|
+
if (inHash === undefined)
|
|
126
|
+
continue;
|
|
127
|
+
const outHashes = newHandles.map((nh) => faceFingerprint(kernel, nh, hashUpperBound));
|
|
128
|
+
modified.push(inHash, outHashes.length, ...outHashes);
|
|
120
129
|
}
|
|
121
130
|
// generated:结果实体的新面 hash(数量按 generated 表展开)
|
|
122
131
|
const generated = [];
|
|
@@ -187,6 +196,41 @@ export async function createBrepkitPrimitives() {
|
|
|
187
196
|
// 返回的面在此登记;extrude/revolveVec 的 face 输入断言、getSubShapes 的
|
|
188
197
|
// edge/vertex 分发都据此判定(brepkit 无 shape-type 分类 API,见下方注释)。
|
|
189
198
|
const knownFaces = new Set();
|
|
199
|
+
// GOTCHA(2026-09-26 非实体句柄修复):brepkit 的 tessellateSolidGrouped / getSolidFaces
|
|
200
|
+
// 等 *Solid API 只吃 solid 句柄,传 face/wire/edge/compound 即抛
|
|
201
|
+
// "invalid solid handle: index N out of bounds"。而 brepkit **没有 shape-type 分类 API**
|
|
202
|
+
// (getFaceEdges(solid) 不抛错、会返回某一面的边,不能 try/catch 探测)。故 meshShape
|
|
203
|
+
// 必须靠这组「已知句柄集合」在执行前静态分发,而不是运行时探测:
|
|
204
|
+
// knownFaces → tessellateFace(JsMesh)
|
|
205
|
+
// knownWires → getWireEdges + tessellateEdge 逐边采样成线段
|
|
206
|
+
// knownEdges → tessellateEdge 直接采样成线段(wire.ts smooth 路径 interpolatePoints 返回 edge)
|
|
207
|
+
// knownCompounds→ 逐子句柄按其类型 tessellate 后合并(sectionByPlane 面组 / importStep 多实体)
|
|
208
|
+
// 不在任何集合里的句柄一律按 solid 走 tessellateSolidGrouped(实体创建不入集合,天然落此路径)。
|
|
209
|
+
const knownWires = new Set();
|
|
210
|
+
const knownEdges = new Set();
|
|
211
|
+
// GOTCHA: getSolidFaces returns GLOBAL face indices (0..5, 6..11, ...) that collide
|
|
212
|
+
// with solid handles (per-type namespace). Enumerated sub-faces must NOT pollute
|
|
213
|
+
// knownFaces, else measuring a later solid whose handle number equals an old
|
|
214
|
+
// enumerated face gets mis-tessellated as a face. derivedFaces is consulted ONLY by
|
|
215
|
+
// getSubShapes edge/vertex drilling, never by meshShape/getBoundingBox.
|
|
216
|
+
const derivedFaces = new Set();
|
|
217
|
+
// 虚拟 compound 句柄计数器(非实体子句柄不进内核 makeCompound,见 makeCompound GOTCHA)。
|
|
218
|
+
const knownCompounds = new Map();
|
|
219
|
+
let virtualCompoundCounter = 0;
|
|
220
|
+
// GOTCHA(2026-09-26):brepkit 句柄按**类型分命名空间**——solid/face/wire/edge 各自从 0 计数
|
|
221
|
+
// (实测 edge=wire=face=solid=0 可共存)。因此「某编号 N 在 knownFaces」与「某编号 N 是 solid」
|
|
222
|
+
// 并不互斥:新 solid 恰好复用了早先某 face 的编号时,knownFaces.has(N) 会把 solid 误判成 face
|
|
223
|
+
// (实测 box makeBox 返回 solid 2,撞 profile 早先的 face 2 → cloneShape 走 copyFace 崩溃)。
|
|
224
|
+
// 建 solid 时必须从 knownFaces/knownWires/knownEdges/derivedFaces 抹掉该编号,让默认 solid 路径生效。
|
|
225
|
+
// derivedFaces 也必须清:getSubShapes(solid,'edge') 会查 derivedFaces.has(s) 误走 getFaceEdges。
|
|
226
|
+
const markSolid = (h) => {
|
|
227
|
+
knownFaces.delete(h);
|
|
228
|
+
knownWires.delete(h);
|
|
229
|
+
knownEdges.delete(h);
|
|
230
|
+
knownCompounds.delete(h);
|
|
231
|
+
derivedFaces.delete(h);
|
|
232
|
+
return h;
|
|
233
|
+
};
|
|
190
234
|
const readFallback = () => {
|
|
191
235
|
try {
|
|
192
236
|
return typeof kernel.meshFallbackCount === 'function' ? Number(kernel.meshFallbackCount()) : 0;
|
|
@@ -205,6 +249,162 @@ export async function createBrepkitPrimitives() {
|
|
|
205
249
|
const v = ((d[2] ?? 0) + (d[3] ?? 1)) / 2;
|
|
206
250
|
return vec3Of(toNumArray(kernel.evaluateSurface(asNum(face), u, v)));
|
|
207
251
|
};
|
|
252
|
+
/** 单个 face → BrepMeshResult(tessellateFace)。 */
|
|
253
|
+
const faceMesh = (f, deflection, angular) => {
|
|
254
|
+
const jm = kernel.tessellateFace(f, deflection, angular);
|
|
255
|
+
const positions = arr(jm.positions);
|
|
256
|
+
const normals = arr(jm.normals);
|
|
257
|
+
const indices = arr(jm.indices);
|
|
258
|
+
const triCount = jm.triangleCount > 0 ? jm.triangleCount : indices.length / 3;
|
|
259
|
+
return {
|
|
260
|
+
positions: new Float32Array(positions),
|
|
261
|
+
normals: new Float32Array(normals),
|
|
262
|
+
indices: new Uint32Array(indices),
|
|
263
|
+
vertexCount: positions.length / 3,
|
|
264
|
+
triangleCount: triCount,
|
|
265
|
+
faceGroups: new Int32Array([0, triCount, faceFingerprint(kernel, f, 1e9)]),
|
|
266
|
+
faceCount: 1,
|
|
267
|
+
};
|
|
268
|
+
};
|
|
269
|
+
/** 一组 edge 句柄 → 线段 mesh(positions 为采样点、indices 为端点对)。 */
|
|
270
|
+
// GOTCHA:tessellateEdge(edge, N) 返回扁平 [x,y,z,...] 采样点(NURBS 才采样 N 点,
|
|
271
|
+
// 直线边只回端点),不是三角网格;wire/curve 显示吃「端点对」索引(与 mesh 路径 wireMesh 同构)。
|
|
272
|
+
const edgesToLineMesh = (edgeHandles, deflection) => {
|
|
273
|
+
const positions = [];
|
|
274
|
+
const indices = [];
|
|
275
|
+
// 采样密度:按 deflection 粗采样即可(1D 线框显示,非精确几何)。
|
|
276
|
+
const nPts = Math.max(12, Math.round(4 / Math.max(deflection, 0.01)));
|
|
277
|
+
for (const e of edgeHandles) {
|
|
278
|
+
const pts = arr(kernel.tessellateEdge(e, nPts));
|
|
279
|
+
const base = positions.length / 3;
|
|
280
|
+
for (let i = 0; i < pts.length; i++)
|
|
281
|
+
positions.push(pts[i]);
|
|
282
|
+
const verts = pts.length / 3;
|
|
283
|
+
for (let i = 0; i < verts - 1; i++)
|
|
284
|
+
indices.push(base + i, base + i + 1);
|
|
285
|
+
}
|
|
286
|
+
return {
|
|
287
|
+
positions: new Float32Array(positions),
|
|
288
|
+
normals: new Float32Array(positions.length),
|
|
289
|
+
indices: new Uint32Array(indices),
|
|
290
|
+
vertexCount: positions.length / 3,
|
|
291
|
+
triangleCount: 0,
|
|
292
|
+
faceGroups: new Int32Array(0),
|
|
293
|
+
faceCount: 0,
|
|
294
|
+
};
|
|
295
|
+
};
|
|
296
|
+
/** wire → 线段 mesh(getWireEdges 取边集后逐边采样)。 */
|
|
297
|
+
const wireMesh = (wire, deflection) => {
|
|
298
|
+
const edges = arr(kernel.getWireEdges(wire));
|
|
299
|
+
return edgesToLineMesh(edges, deflection);
|
|
300
|
+
};
|
|
301
|
+
/** solid → BrepMeshResult(tessellateSolidGrouped,原 meshShape 实体路径抽出)。 */
|
|
302
|
+
const solidMesh = (solid, deflection, angular) => {
|
|
303
|
+
const raw = kernel.tessellateSolidGrouped(solid, deflection, angular);
|
|
304
|
+
const m = JSON.parse(raw);
|
|
305
|
+
// faceOffsets 形如 [start0, start1, ..., totalEnd]:以「索引数组单位」计的每面偏移(2026-09-19 实测),
|
|
306
|
+
// 除以 3 换算为 faijs 契约的三角形单位 [triStart, triCount, faceHash]
|
|
307
|
+
const fo = m.faceOffsets;
|
|
308
|
+
const faceCount = Math.max(0, fo.length - 1);
|
|
309
|
+
const faceHashes = [];
|
|
310
|
+
try {
|
|
311
|
+
const faces = arr(kernel.getSolidFaces(solid));
|
|
312
|
+
for (let i = 0; i < faceCount && i < faces.length; i++)
|
|
313
|
+
faceHashes.push(faceFingerprint(kernel, faces[i], 1e9));
|
|
314
|
+
}
|
|
315
|
+
catch { /* 非实体:faceHash 置 0 */ }
|
|
316
|
+
const faceGroups = new Int32Array(faceCount * 3);
|
|
317
|
+
for (let i = 0; i < faceCount; i++) {
|
|
318
|
+
faceGroups[i * 3] = fo[i] / 3;
|
|
319
|
+
faceGroups[i * 3 + 1] = (fo[i + 1] - fo[i]) / 3;
|
|
320
|
+
faceGroups[i * 3 + 2] = faceHashes[i] ?? 0;
|
|
321
|
+
}
|
|
322
|
+
return {
|
|
323
|
+
positions: new Float32Array(m.positions),
|
|
324
|
+
normals: new Float32Array(m.normals),
|
|
325
|
+
indices: new Uint32Array(m.indices),
|
|
326
|
+
vertexCount: m.positions.length / 3,
|
|
327
|
+
triangleCount: m.indices.length / 3,
|
|
328
|
+
faceGroups,
|
|
329
|
+
faceCount,
|
|
330
|
+
};
|
|
331
|
+
};
|
|
332
|
+
/** compound → 逐子句柄 tessellate 后合并(sectionByPlane 面组 / importStep 多实体)。 */
|
|
333
|
+
const compoundMesh = (children, deflection, angular) => {
|
|
334
|
+
const positions = [];
|
|
335
|
+
const normals = [];
|
|
336
|
+
const indices = [];
|
|
337
|
+
const faceGroups = [];
|
|
338
|
+
let triOffset = 0;
|
|
339
|
+
for (const c of children) {
|
|
340
|
+
let sub;
|
|
341
|
+
if (knownFaces.has(c)) {
|
|
342
|
+
sub = faceMesh(c, deflection, angular);
|
|
343
|
+
}
|
|
344
|
+
else if (knownWires.has(c)) {
|
|
345
|
+
sub = wireMesh(c, deflection);
|
|
346
|
+
}
|
|
347
|
+
else if (knownEdges.has(c)) {
|
|
348
|
+
sub = edgesToLineMesh([c], deflection);
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
// 子句柄按 solid 处理(importStep 多实体 / makeCompound(solids))。
|
|
352
|
+
sub = solidMesh(c, deflection, angular);
|
|
353
|
+
}
|
|
354
|
+
const vOffset = positions.length / 3;
|
|
355
|
+
for (let i = 0; i < sub.positions.length; i++)
|
|
356
|
+
positions.push(sub.positions[i]);
|
|
357
|
+
for (let i = 0; i < sub.normals.length; i++)
|
|
358
|
+
normals.push(sub.normals[i] ?? 0);
|
|
359
|
+
for (let i = 0; i < sub.indices.length; i++)
|
|
360
|
+
indices.push(sub.indices[i] + vOffset);
|
|
361
|
+
if (sub.faceGroups) {
|
|
362
|
+
for (let i = 0; i < sub.faceGroups.length; i += 3) {
|
|
363
|
+
faceGroups.push((sub.faceGroups[i] ?? 0) + triOffset, sub.faceGroups[i + 1] ?? 0, sub.faceGroups[i + 2] ?? 0);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
triOffset += sub.triangleCount;
|
|
367
|
+
}
|
|
368
|
+
return {
|
|
369
|
+
positions: new Float32Array(positions),
|
|
370
|
+
normals: new Float32Array(normals),
|
|
371
|
+
indices: new Uint32Array(indices),
|
|
372
|
+
vertexCount: positions.length / 3,
|
|
373
|
+
triangleCount: indices.length / 3,
|
|
374
|
+
faceGroups: new Int32Array(faceGroups),
|
|
375
|
+
faceCount: faceGroups.length / 3,
|
|
376
|
+
};
|
|
377
|
+
};
|
|
378
|
+
// ── 按句柄类型深拷贝(transform/copy 族分发用) ──
|
|
379
|
+
// GOTCHA:brepkit copySolid/copyFace/copyWire 三者入参严格对应 solid/face/wire 句柄,
|
|
380
|
+
// 传错即崩溃;transformSolid/transformFace/transformWire 均为「原地修改」void。
|
|
381
|
+
// place/translate 等会作用在 profile 产出的 face 上(sew 用例 place 两张 face),
|
|
382
|
+
// 故必须按 knownFaces/knownWires 分发,不能一律 copySolid。
|
|
383
|
+
const cloneShape = (shape) => {
|
|
384
|
+
if (knownFaces.has(shape)) {
|
|
385
|
+
const c = kernel.copyFace(shape);
|
|
386
|
+
knownFaces.add(c);
|
|
387
|
+
return c;
|
|
388
|
+
}
|
|
389
|
+
if (knownWires.has(shape)) {
|
|
390
|
+
const c = kernel.copyWire(shape);
|
|
391
|
+
knownWires.add(c);
|
|
392
|
+
return c;
|
|
393
|
+
}
|
|
394
|
+
return markSolid(kernel.copySolid(shape));
|
|
395
|
+
};
|
|
396
|
+
/** 深拷贝 + 原地仿射变换(matrix 已经 toKernelMatrix 归一为 16 元素 4×4)。 */
|
|
397
|
+
const cloneAndTransform = (shape, matrix16) => {
|
|
398
|
+
const c = cloneShape(shape);
|
|
399
|
+
const m = Float64Array.from(matrix16);
|
|
400
|
+
if (knownFaces.has(c))
|
|
401
|
+
kernel.transformFace(c, m);
|
|
402
|
+
else if (knownWires.has(c))
|
|
403
|
+
kernel.transformWire(c, m);
|
|
404
|
+
else
|
|
405
|
+
kernel.transformSolid(c, m);
|
|
406
|
+
return c;
|
|
407
|
+
};
|
|
208
408
|
const api = {
|
|
209
409
|
// ── 生命周期 ──
|
|
210
410
|
release(_shape) { },
|
|
@@ -217,26 +417,26 @@ export async function createBrepkitPrimitives() {
|
|
|
217
417
|
return delta;
|
|
218
418
|
},
|
|
219
419
|
// ── 实体图元 ──
|
|
220
|
-
makeBox(dx, dy, dz) { return asHandle(kernel.makeBox(dx, dy, dz)); },
|
|
420
|
+
makeBox(dx, dy, dz) { return asHandle(markSolid(kernel.makeBox(dx, dy, dz))); },
|
|
221
421
|
makeBoxFromCorners(corner1, corner2) {
|
|
222
422
|
const dx = Math.abs(corner2.x - corner1.x), dy = Math.abs(corner2.y - corner1.y), dz = Math.abs(corner2.z - corner1.z);
|
|
223
|
-
const h = kernel.makeBox(dx, dy, dz);
|
|
423
|
+
const h = markSolid(kernel.makeBox(dx, dy, dz));
|
|
224
424
|
// ⚠️ transformSolid 是「原地修改、返回 undefined」——makeBox 的句柄本身即新实体,
|
|
225
425
|
// 直接原地变换后返回 h;若写 `asHandle(transformSolid(...))` 会拿到 undefined 句柄
|
|
226
426
|
// (2026-09-25 实证:导致 splitBrep 基于半空间盒的 common/cut 全部失效)。
|
|
227
427
|
kernel.transformSolid(h, toKernelMatrix(translationMatrix(Math.min(corner1.x, corner2.x), Math.min(corner1.y, corner2.y), Math.min(corner1.z, corner2.z))));
|
|
228
428
|
return asHandle(h);
|
|
229
429
|
},
|
|
230
|
-
makeCylinder(radius, height) { return asHandle(kernel.makeCylinder(radius, height)); },
|
|
231
|
-
makeSphere(radius) { return asHandle(kernel.makeSphere(radius, 32)); },
|
|
232
|
-
makeCone(r1, r2, height) { return asHandle(kernel.makeCone(r1, r2, height)); },
|
|
430
|
+
makeCylinder(radius, height) { return asHandle(markSolid(kernel.makeCylinder(radius, height))); },
|
|
431
|
+
makeSphere(radius) { return asHandle(markSolid(kernel.makeSphere(radius, 32))); },
|
|
432
|
+
makeCone(r1, r2, height) { return asHandle(markSolid(kernel.makeCone(r1, r2, height))); },
|
|
233
433
|
makeRectangle(width, height) {
|
|
234
434
|
const f = kernel.makeRectangle(width, height);
|
|
235
435
|
knownFaces.add(f);
|
|
236
436
|
return asHandle(f);
|
|
237
437
|
},
|
|
238
|
-
makeEllipsoid(rx, ry, rz) { return asHandle(kernel.makeEllipsoid(rx, ry, rz)); },
|
|
239
|
-
makeTorus(majorRadius, minorRadius) { return asHandle(kernel.makeTorus(majorRadius, minorRadius, 64)); },
|
|
438
|
+
makeEllipsoid(rx, ry, rz) { return asHandle(markSolid(kernel.makeEllipsoid(rx, ry, rz))); },
|
|
439
|
+
makeTorus(majorRadius, minorRadius) { return asHandle(markSolid(kernel.makeTorus(majorRadius, minorRadius, 64))); },
|
|
240
440
|
makeVertex(x, y, z) { return asHandle(kernel.makeVertex(x, y, z)); },
|
|
241
441
|
// ── 造型运算 ──
|
|
242
442
|
extrude(shape, dx, dy, dz) {
|
|
@@ -248,7 +448,7 @@ export async function createBrepkitPrimitives() {
|
|
|
248
448
|
const len = Math.hypot(dx, dy, dz);
|
|
249
449
|
if (len === 0)
|
|
250
450
|
fail('extrude: zero-length extrusion vector');
|
|
251
|
-
return asHandle(kernel.extrude(s, dx / len, dy / len, dz / len, len));
|
|
451
|
+
return asHandle(markSolid(kernel.extrude(s, dx / len, dy / len, dz / len, len)));
|
|
252
452
|
},
|
|
253
453
|
revolveVec(shape, center, direction, angleDeg) {
|
|
254
454
|
// 方言归一:brepkit revolve 只吃 face(与 extrude 同族断言)。
|
|
@@ -256,14 +456,14 @@ export async function createBrepkitPrimitives() {
|
|
|
256
456
|
if (!knownFaces.has(s)) {
|
|
257
457
|
fail('revolveVec requires a face input; got a handle that is not a known face');
|
|
258
458
|
}
|
|
259
|
-
return asHandle(kernel.revolve(s, center.x, center.y, center.z, direction.x, direction.y, direction.z, angleDeg));
|
|
459
|
+
return asHandle(markSolid(kernel.revolve(s, center.x, center.y, center.z, direction.x, direction.y, direction.z, angleDeg)));
|
|
260
460
|
},
|
|
261
461
|
sew(shapesList, tolerance) {
|
|
262
462
|
return asHandle(kernel.sewFaces(Uint32Array.from(shapesList.map(asNum)), tolerance ?? 1e-6));
|
|
263
463
|
},
|
|
264
464
|
sewAndSolidify(faces, _tolerance) {
|
|
265
465
|
// brepkit 的 makeSolid 即「缝合并固化成实体」(sewFaces + 建 solid)。
|
|
266
|
-
return asHandle(kernel.makeSolid(Uint32Array.from(faces.map(asNum))));
|
|
466
|
+
return asHandle(markSolid(kernel.makeSolid(Uint32Array.from(faces.map(asNum)))));
|
|
267
467
|
},
|
|
268
468
|
shell(solid, facesToRemove, thickness, _tolerance) {
|
|
269
469
|
// brepkit 参数序 (solid, thickness, open_faces);tolerance 内核固定。
|
|
@@ -273,19 +473,25 @@ export async function createBrepkitPrimitives() {
|
|
|
273
473
|
return asHandle(kernel.convexHull(flattenPoints(points)));
|
|
274
474
|
},
|
|
275
475
|
// ── 布尔与分割 ──
|
|
276
|
-
fuse(a, b) { return asHandle(trackFallback(kernel.fuse(asNum(a), asNum(b)))); },
|
|
277
|
-
cut(a, b) { return asHandle(trackFallback(kernel.cut(asNum(a), asNum(b)))); },
|
|
476
|
+
fuse(a, b) { return asHandle(markSolid(trackFallback(kernel.fuse(asNum(a), asNum(b))))); },
|
|
477
|
+
cut(a, b) { return asHandle(markSolid(trackFallback(kernel.cut(asNum(a), asNum(b))))); },
|
|
278
478
|
common(a, b) {
|
|
279
479
|
// brepkit 无 `common` 原生名;交集语义由 `intersect` 承担(method-map: dialect)。
|
|
280
|
-
return asHandle(trackFallback(kernel.intersect(asNum(a), asNum(b))));
|
|
480
|
+
return asHandle(markSolid(trackFallback(kernel.intersect(asNum(a), asNum(b)))));
|
|
281
481
|
},
|
|
282
482
|
intersect(a, b) { return asHandle(trackFallback(kernel.intersect(asNum(a), asNum(b)))); },
|
|
283
483
|
fuseAll(shapesList) {
|
|
284
|
-
return asHandle(trackFallback(kernel.fuseAll(Int32Array.from(shapesList.map(asNum)))));
|
|
484
|
+
return asHandle(markSolid(trackFallback(kernel.fuseAll(Int32Array.from(shapesList.map(asNum))))));
|
|
285
485
|
},
|
|
286
486
|
sectionByPlane(shape, point, normal) {
|
|
287
487
|
// brepkit 原生 section(solid, plane) 返回剖面 face 句柄组(§3.6-2 新中立名)。
|
|
288
|
-
|
|
488
|
+
// GOTCHA:brepkit section 返回的是 **face** 句柄(不是 edge/wire),必须登记进 knownFaces,
|
|
489
|
+
// 否则 op 层 makeCompound(这些 face) 后送 meshShape 会按 solid 路径崩溃。
|
|
490
|
+
const s = asNum(shape);
|
|
491
|
+
const faces = arr(kernel.section(s, point.x, point.y, point.z, normal.x, normal.y, normal.z));
|
|
492
|
+
for (const f of faces)
|
|
493
|
+
knownFaces.add(f);
|
|
494
|
+
return faces.map(asHandle);
|
|
289
495
|
},
|
|
290
496
|
splitByPlane(shape, point, normal) {
|
|
291
497
|
const parts = arr(kernel.split(asNum(shape), point.x, point.y, point.z, normal.x, normal.y, normal.z));
|
|
@@ -299,6 +505,8 @@ export async function createBrepkitPrimitives() {
|
|
|
299
505
|
return (c[0] ?? 0) * normal.x + (c[1] ?? 0) * normal.y + (c[2] ?? 0) * normal.z - originDot;
|
|
300
506
|
};
|
|
301
507
|
const [a, b] = parts;
|
|
508
|
+
markSolid(a);
|
|
509
|
+
markSolid(b);
|
|
302
510
|
return side(a) >= side(b)
|
|
303
511
|
? { positive: asHandle(a), negative: asHandle(b) }
|
|
304
512
|
: { positive: asHandle(b), negative: asHandle(a) };
|
|
@@ -335,37 +543,25 @@ export async function createBrepkitPrimitives() {
|
|
|
335
543
|
// brepkit transformSolid 吃 4×4 行主序 16 元素。所有 transformSolid 调用一律经
|
|
336
544
|
// toKernelMatrix 归一(下方唯一转换点),禁止直出 16 元素旁路。
|
|
337
545
|
translate(shape, dx, dy, dz) {
|
|
338
|
-
|
|
339
|
-
kernel.transformSolid(c, toKernelMatrix(translationMatrix(dx, dy, dz)));
|
|
340
|
-
return asHandle(c);
|
|
546
|
+
return asHandle(cloneAndTransform(asNum(shape), toKernelMatrix(translationMatrix(dx, dy, dz))));
|
|
341
547
|
},
|
|
342
548
|
scale(shape, center, factor) {
|
|
343
|
-
|
|
344
|
-
kernel.transformSolid(c, toKernelMatrix(scaleMatrix(center, factor)));
|
|
345
|
-
return asHandle(c);
|
|
549
|
+
return asHandle(cloneAndTransform(asNum(shape), toKernelMatrix(scaleMatrix(center, factor))));
|
|
346
550
|
},
|
|
347
551
|
transform(shape, matrix) {
|
|
348
|
-
|
|
349
|
-
kernel.transformSolid(c, toKernelMatrix(matrix));
|
|
350
|
-
return asHandle(c);
|
|
552
|
+
return asHandle(cloneAndTransform(asNum(shape), toKernelMatrix(matrix)));
|
|
351
553
|
},
|
|
352
554
|
located(shape, matrix) {
|
|
353
|
-
|
|
354
|
-
kernel.transformSolid(c, toKernelMatrix(matrix));
|
|
355
|
-
return asHandle(c);
|
|
555
|
+
return asHandle(cloneAndTransform(asNum(shape), toKernelMatrix(matrix)));
|
|
356
556
|
},
|
|
357
557
|
locate(shape, matrix) {
|
|
358
|
-
|
|
359
|
-
kernel.transformSolid(c, toKernelMatrix(matrix));
|
|
360
|
-
return asHandle(c);
|
|
558
|
+
return asHandle(cloneAndTransform(asNum(shape), toKernelMatrix(matrix)));
|
|
361
559
|
},
|
|
362
560
|
generalTransform(shape, matrix) {
|
|
363
|
-
|
|
364
|
-
kernel.transformSolid(c, toKernelMatrix(matrix));
|
|
365
|
-
return asHandle(c);
|
|
561
|
+
return asHandle(cloneAndTransform(asNum(shape), toKernelMatrix(matrix)));
|
|
366
562
|
},
|
|
367
|
-
copy(shape) { return asHandle(
|
|
368
|
-
copyShape(shape) { return asHandle(
|
|
563
|
+
copy(shape) { return asHandle(cloneShape(asNum(shape))); },
|
|
564
|
+
copyShape(shape) { return asHandle(cloneShape(asNum(shape))); },
|
|
369
565
|
composeTransform(m1, m2) {
|
|
370
566
|
// 方言:L1 是 3×4 行主序 12 元素;brepkit composeTransforms 吃/回 4×4 16 元素。
|
|
371
567
|
const r = kernel.composeTransforms(Float64Array.from(toKernelMatrix(m1)), Float64Array.from(toKernelMatrix(m2)));
|
|
@@ -392,12 +588,16 @@ export async function createBrepkitPrimitives() {
|
|
|
392
588
|
},
|
|
393
589
|
// ── 曲线构造 ──
|
|
394
590
|
makeLineEdge(start, end) {
|
|
395
|
-
|
|
591
|
+
const e = kernel.makeLineEdge(start.x, start.y, start.z, end.x, end.y, end.z);
|
|
592
|
+
knownEdges.add(e);
|
|
593
|
+
return asHandle(e);
|
|
396
594
|
},
|
|
397
595
|
makeArcEdge(start, mid, end) {
|
|
398
596
|
// 方言消化:occt 吃 3 点;brepkit makeCircleArc3d 吃 (start,end,center,axis)。
|
|
399
597
|
const { center, axis } = circumcircle(start, mid, end);
|
|
400
|
-
|
|
598
|
+
const e = kernel.makeCircleArc3d(start.x, start.y, start.z, end.x, end.y, end.z, center.x, center.y, center.z, axis.x, axis.y, axis.z);
|
|
599
|
+
knownEdges.add(e);
|
|
600
|
+
return asHandle(e);
|
|
401
601
|
},
|
|
402
602
|
makeBezierEdge(controlPoints) {
|
|
403
603
|
// 方言消化:L1 吃控制点(Bezier);brepkit 原生 makeNurbsEdge 吃
|
|
@@ -413,14 +613,20 @@ export async function createBrepkitPrimitives() {
|
|
|
413
613
|
const weights = new Float64Array(n).fill(1);
|
|
414
614
|
const first = controlPoints[0];
|
|
415
615
|
const last = controlPoints[n - 1];
|
|
416
|
-
|
|
616
|
+
const e = kernel.makeNurbsEdge(first.x, first.y, first.z, last.x, last.y, last.z, degree, knots, flattenPoints(controlPoints), weights);
|
|
617
|
+
knownEdges.add(e);
|
|
618
|
+
return asHandle(e);
|
|
417
619
|
},
|
|
418
620
|
makeCircleEdge(center, normal, radius) {
|
|
419
|
-
|
|
621
|
+
const e = kernel.makeCircleEdge(center.x, center.y, center.z, normal.x, normal.y, normal.z, radius);
|
|
622
|
+
knownEdges.add(e);
|
|
623
|
+
return asHandle(e);
|
|
420
624
|
},
|
|
421
625
|
// ── 拓扑构造 ──
|
|
422
626
|
makeWire(edges) {
|
|
423
|
-
|
|
627
|
+
const w = kernel.makeWire(Int32Array.from(edges.map(asNum)), false);
|
|
628
|
+
knownWires.add(w);
|
|
629
|
+
return asHandle(w);
|
|
424
630
|
},
|
|
425
631
|
makeFace(wire) {
|
|
426
632
|
const f = kernel.makeFaceFromWire(asNum(wire));
|
|
@@ -428,10 +634,28 @@ export async function createBrepkitPrimitives() {
|
|
|
428
634
|
return asHandle(f);
|
|
429
635
|
},
|
|
430
636
|
makeCompound(shapesList) {
|
|
431
|
-
|
|
637
|
+
const kids = arr(shapesList.map(asNum));
|
|
638
|
+
// GOTCHA(2026-09-26):brepkit makeCompound 只接受 **solid** 句柄——把 face/wire
|
|
639
|
+
// (sectionByPlane 截面面组)塞进去即抛 "invalid solid handle"。非实体子句柄时
|
|
640
|
+
// 不调内核,改用高段虚拟句柄(0x80000000+),仅靠 knownCompounds 表分发
|
|
641
|
+
// meshShape/wireframe/getSubShapes/getBoundingBox,不触达内核。
|
|
642
|
+
const hasNonSolid = kids.some((k) => knownFaces.has(k) || knownWires.has(k) || knownEdges.has(k) || knownCompounds.has(k));
|
|
643
|
+
if (hasNonSolid) {
|
|
644
|
+
virtualCompoundCounter++;
|
|
645
|
+
const v = 0x80000000 + virtualCompoundCounter;
|
|
646
|
+
knownCompounds.set(v, kids);
|
|
647
|
+
return asHandle(v);
|
|
648
|
+
}
|
|
649
|
+
const c = kernel.makeCompound(Int32Array.from(kids));
|
|
650
|
+
knownCompounds.set(c, kids);
|
|
651
|
+
return asHandle(c);
|
|
432
652
|
},
|
|
433
653
|
addHolesInFace(face, holeWires) {
|
|
434
|
-
|
|
654
|
+
// addHolesToFace 返回**新 face 句柄**(同曲面 + 内孔 wire),必须登记进 knownFaces,
|
|
655
|
+
// 否则 profile(op) 带孔面送 meshShape 会落到 solid 路径崩溃。
|
|
656
|
+
const f = kernel.addHolesToFace(asNum(face), Int32Array.from(holeWires.map(asNum)));
|
|
657
|
+
knownFaces.add(f);
|
|
658
|
+
return asHandle(f);
|
|
435
659
|
},
|
|
436
660
|
buildTriFace(a, b, c) {
|
|
437
661
|
const f = kernel.makePolygon(flattenPoints([a, b, c]));
|
|
@@ -442,39 +666,67 @@ export async function createBrepkitPrimitives() {
|
|
|
442
666
|
meshShape(shape, options) {
|
|
443
667
|
const deflection = options?.linearDeflection ?? 0.1;
|
|
444
668
|
const angular = options?.angularDeflection ?? 0.5;
|
|
445
|
-
const
|
|
446
|
-
|
|
447
|
-
//
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
for (let i = 0; i < faceCount; i++) {
|
|
460
|
-
faceGroups[i * 3] = fo[i] / 3;
|
|
461
|
-
faceGroups[i * 3 + 1] = (fo[i + 1] - fo[i]) / 3;
|
|
462
|
-
faceGroups[i * 3 + 2] = faceHashes[i] ?? 0;
|
|
463
|
-
}
|
|
464
|
-
return {
|
|
465
|
-
positions: new Float32Array(m.positions),
|
|
466
|
-
normals: new Float32Array(m.normals),
|
|
467
|
-
indices: new Uint32Array(m.indices),
|
|
468
|
-
vertexCount: m.positions.length / 3,
|
|
469
|
-
triangleCount: m.indices.length / 3,
|
|
470
|
-
faceGroups,
|
|
471
|
-
faceCount,
|
|
472
|
-
};
|
|
669
|
+
const s = asNum(shape);
|
|
670
|
+
// 非实体句柄按已知集合静态分发(见文件头 GOTCHA:brepkit 无 shape-type 分类 API)。
|
|
671
|
+
// 顺序:face → wire → edge → compound → solid。实体创建不入任何集合,天然落 solid 路径。
|
|
672
|
+
if (knownFaces.has(s))
|
|
673
|
+
return faceMesh(s, deflection, angular);
|
|
674
|
+
if (knownWires.has(s))
|
|
675
|
+
return wireMesh(s, deflection);
|
|
676
|
+
if (knownEdges.has(s))
|
|
677
|
+
return edgesToLineMesh([s], deflection);
|
|
678
|
+
const compoundChildren = knownCompounds.get(s);
|
|
679
|
+
if (compoundChildren)
|
|
680
|
+
return compoundMesh(compoundChildren, deflection, angular);
|
|
681
|
+
// solid(或未登记句柄):走原 tessellateSolidGrouped 路径。
|
|
682
|
+
return solidMesh(s, deflection, angular);
|
|
473
683
|
},
|
|
474
684
|
wireframe(shape, deflection) {
|
|
475
685
|
const d = deflection ?? 0.1;
|
|
686
|
+
const s = asNum(shape);
|
|
687
|
+
// GOTCHA:meshEdgesAll 是 solid-only API——buildTopologyFromMesh 会对 face/wire/edge
|
|
688
|
+
// 产物调 wireframe(...),传入非实体句柄即抛 "invalid solid handle"。按已知集合分发:
|
|
689
|
+
// face -> getFaceEdges;wire -> getWireEdges;edge -> [self];否则 solid 走 meshEdgesAll。
|
|
690
|
+
let edgeHandles = null;
|
|
691
|
+
if (knownFaces.has(s))
|
|
692
|
+
edgeHandles = arr(kernel.getFaceEdges(s));
|
|
693
|
+
else if (knownWires.has(s))
|
|
694
|
+
edgeHandles = arr(kernel.getWireEdges(s));
|
|
695
|
+
else if (knownEdges.has(s))
|
|
696
|
+
edgeHandles = [s];
|
|
697
|
+
else if (knownCompounds.has(s)) {
|
|
698
|
+
// compound(sectionByPlane 面组):逐子面收集边。
|
|
699
|
+
edgeHandles = [];
|
|
700
|
+
for (const k of knownCompounds.get(s)) {
|
|
701
|
+
if (knownFaces.has(k))
|
|
702
|
+
edgeHandles.push(...arr(kernel.getFaceEdges(k)));
|
|
703
|
+
else if (knownWires.has(k))
|
|
704
|
+
edgeHandles.push(...arr(kernel.getWireEdges(k)));
|
|
705
|
+
else if (knownEdges.has(k))
|
|
706
|
+
edgeHandles.push(k);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
if (edgeHandles) {
|
|
710
|
+
const nPts = Math.max(12, Math.round(4 / Math.max(d, 0.01)));
|
|
711
|
+
const positions = [];
|
|
712
|
+
const offsets = [0];
|
|
713
|
+
for (const e of edgeHandles) {
|
|
714
|
+
const pts = arr(kernel.tessellateEdge(e, nPts));
|
|
715
|
+
for (const p of pts)
|
|
716
|
+
positions.push(p);
|
|
717
|
+
offsets.push(positions.length / 3);
|
|
718
|
+
}
|
|
719
|
+
const edgeGroups = new Int32Array(edgeHandles.length * 3);
|
|
720
|
+
for (let i = 0; i < edgeHandles.length; i++) {
|
|
721
|
+
edgeGroups[i * 3] = offsets[i] ?? 0;
|
|
722
|
+
edgeGroups[i * 3 + 1] = (offsets[i + 1] ?? positions.length / 3) - (offsets[i] ?? 0);
|
|
723
|
+
edgeGroups[i * 3 + 2] = i;
|
|
724
|
+
}
|
|
725
|
+
return { points: new Float32Array(positions), edgeGroups, pointCount: positions.length / 3, edgeCount: edgeHandles.length };
|
|
726
|
+
}
|
|
727
|
+
// solid(或未登记句柄):走原 meshEdgesAll 路径。
|
|
476
728
|
// JsEdgeLines:edgeCount/offsets/positions 均为属性(offsets/positions 为 TypedArray,2026-09-19 实测)
|
|
477
|
-
const me = kernel.meshEdgesAll(
|
|
729
|
+
const me = kernel.meshEdgesAll(s, d, 0.5);
|
|
478
730
|
const edgeCount = Number(me.edgeCount);
|
|
479
731
|
const offsets = arr(me.offsets);
|
|
480
732
|
const positions = arr(me.positions);
|
|
@@ -497,9 +749,52 @@ export async function createBrepkitPrimitives() {
|
|
|
497
749
|
// edge/vertex 查询时按句柄是否在 knownFaces 里分发到 face 级或 solid 级 API。
|
|
498
750
|
getSubShapes(shape, type) {
|
|
499
751
|
const s = asNum(shape);
|
|
752
|
+
// GOTCHA(2026-09-26 非实体句柄修复):buildTopologyFromMesh 会对**面产物**
|
|
753
|
+
// 调 getSubShapes(face,'face')。brepkit 的 getSolidFaces/getShellFaces 是 solid-only,
|
|
754
|
+
// 对面句柄一个抛 "invalid solid handle"、catch 里再抛 "invalid shell handle"——
|
|
755
|
+
// 两跳都不被捕获即崩溃。brepkit 无 shape-type 分类 API,必须靠已知集合先分发:
|
|
756
|
+
// 输入本身就是 knownFaces 里的一张面时,'face' 返回 [s],edge/vertex 走 face 级 API。
|
|
757
|
+
if (knownFaces.has(s)) {
|
|
758
|
+
if (type === 'face')
|
|
759
|
+
return [asHandle(s)];
|
|
760
|
+
if (type === 'edge') {
|
|
761
|
+
const es = arr(kernel.getFaceEdges(s));
|
|
762
|
+
es.forEach((e) => knownEdges.add(e));
|
|
763
|
+
return es.map(asHandle);
|
|
764
|
+
}
|
|
765
|
+
if (type === 'vertex')
|
|
766
|
+
return arr(kernel.getFaceVertices(s)).map(asHandle);
|
|
767
|
+
return [];
|
|
768
|
+
}
|
|
769
|
+
if (knownWires.has(s)) {
|
|
770
|
+
if (type === 'edge') {
|
|
771
|
+
const es = arr(kernel.getWireEdges(s));
|
|
772
|
+
es.forEach((e) => knownEdges.add(e));
|
|
773
|
+
return es.map(asHandle);
|
|
774
|
+
}
|
|
775
|
+
return [];
|
|
776
|
+
}
|
|
777
|
+
if (knownEdges.has(s)) {
|
|
778
|
+
// input is itself an edge (wire.ts smooth path: interpolatePoints returns edge)
|
|
779
|
+
if (type === 'edge')
|
|
780
|
+
return [asHandle(s)];
|
|
781
|
+
return [];
|
|
782
|
+
}
|
|
783
|
+
const compKids = knownCompounds.get(s);
|
|
784
|
+
if (compKids) {
|
|
785
|
+
// compound(sectionByPlane 面组 / makeCompound 多实体 / importStep 多实体):
|
|
786
|
+
// 'face' 返回子面,'solid' 返回子实体,其余空。
|
|
787
|
+
if (type === 'face')
|
|
788
|
+
return compKids.filter((k) => knownFaces.has(k)).map(asHandle);
|
|
789
|
+
if (type === 'solid')
|
|
790
|
+
return compKids.filter((k) => !knownFaces.has(k) && !knownWires.has(k) && !knownEdges.has(k)).map(asHandle);
|
|
791
|
+
return [];
|
|
792
|
+
}
|
|
500
793
|
let list;
|
|
794
|
+
const isFaceLike = knownFaces.has(s) || derivedFaces.has(s);
|
|
501
795
|
if (type === 'face') {
|
|
502
|
-
// 输入可能是 solid 或 shell
|
|
796
|
+
// 输入可能是 solid 或 shell。枚举到的子面登记到 derivedFaces(不进 knownFaces,
|
|
797
|
+
// 避免与 solid 句柄撞号——见 derivedFaces 注释)。
|
|
503
798
|
try {
|
|
504
799
|
list = arr(kernel.getSolidFaces(s));
|
|
505
800
|
}
|
|
@@ -507,15 +802,15 @@ export async function createBrepkitPrimitives() {
|
|
|
507
802
|
list = arr(kernel.getShellFaces?.(s) ?? []);
|
|
508
803
|
}
|
|
509
804
|
for (const f of list)
|
|
510
|
-
|
|
805
|
+
derivedFaces.add(f);
|
|
511
806
|
}
|
|
512
807
|
else if (type === 'edge') {
|
|
513
|
-
list =
|
|
808
|
+
list = isFaceLike
|
|
514
809
|
? arr(kernel.getFaceEdges(s))
|
|
515
810
|
: arr(kernel.getSolidEdges(s));
|
|
516
811
|
}
|
|
517
812
|
else if (type === 'vertex') {
|
|
518
|
-
list =
|
|
813
|
+
list = isFaceLike
|
|
519
814
|
? arr(kernel.getFaceVertices(s))
|
|
520
815
|
: arr(kernel.getSolidVertices(s));
|
|
521
816
|
}
|
|
@@ -539,7 +834,20 @@ export async function createBrepkitPrimitives() {
|
|
|
539
834
|
subShapeHashes(shape, type, hashUpperBound) {
|
|
540
835
|
if (type !== 'face')
|
|
541
836
|
fail(`subShapeHashes(${type}): only 'face' is supported by brepkit`);
|
|
542
|
-
const
|
|
837
|
+
const s = asNum(shape);
|
|
838
|
+
// GOTCHA:buildTopologyFromMesh 会对面产物调 subShapeHashes(face,'face');
|
|
839
|
+
// getSolidFaces 是 solid-only,对面句柄崩溃。输入本身是已知面时,哈希即其自身指纹。
|
|
840
|
+
if (knownFaces.has(s))
|
|
841
|
+
return [faceFingerprint(kernel, s, hashUpperBound)];
|
|
842
|
+
// wire/edge 无面;compound(sectionByPlane 面组)的子句柄即面。
|
|
843
|
+
if (knownWires.has(s) || knownEdges.has(s))
|
|
844
|
+
return [];
|
|
845
|
+
const compKids = knownCompounds.get(s);
|
|
846
|
+
if (compKids) {
|
|
847
|
+
const faces = compKids.filter((k) => knownFaces.has(k));
|
|
848
|
+
return faces.map((f) => faceFingerprint(kernel, f, hashUpperBound));
|
|
849
|
+
}
|
|
850
|
+
const faces = arr(kernel.getSolidFaces(s));
|
|
543
851
|
const hashes = faces.map(f => faceFingerprint(kernel, f, hashUpperBound));
|
|
544
852
|
// 登记 hash↔handle(fuseWithHistory 溯源对齐依赖此表)
|
|
545
853
|
const reg = hashRegistry.get(kernel) ?? new Map();
|
|
@@ -665,7 +973,11 @@ export async function createBrepkitPrimitives() {
|
|
|
665
973
|
return null;
|
|
666
974
|
},
|
|
667
975
|
interpolatePoints(points, degree) {
|
|
668
|
-
|
|
976
|
+
// GOTCHA:interpolatePoints 返回 **edge** 句柄(不是 wire)——wire.ts smooth 路径把它
|
|
977
|
+
// 直接当 wire 送 solidToShape。登记进 knownEdges,meshShape 才能走 tessellateEdge 路径。
|
|
978
|
+
const e = kernel.interpolatePoints(flattenPoints(points), degree);
|
|
979
|
+
knownEdges.add(e);
|
|
980
|
+
return asHandle(e);
|
|
669
981
|
},
|
|
670
982
|
defeature(shape, faces) {
|
|
671
983
|
return asHandle(kernel.defeature(asNum(shape), Uint32Array.from(faces.map(asNum))));
|
|
@@ -674,7 +986,10 @@ export async function createBrepkitPrimitives() {
|
|
|
674
986
|
return asHandle(kernel.draft(asNum(shape), Uint32Array.from(faces.map(asNum)), pull.x, pull.y, pull.z, neutral.x, neutral.y, neutral.z, angleDeg));
|
|
675
987
|
},
|
|
676
988
|
removeHolesFromFace(face) {
|
|
677
|
-
|
|
989
|
+
// removeHolesFromFace 返回**新 face 句柄**(去掉内孔的同曲面 face),必须登记进 knownFaces。
|
|
990
|
+
const f = kernel.removeHolesFromFace(asNum(face));
|
|
991
|
+
knownFaces.add(f);
|
|
992
|
+
return asHandle(f);
|
|
678
993
|
},
|
|
679
994
|
reverseShape(shape) {
|
|
680
995
|
return asHandle(kernel.reverseShape(asNum(shape)));
|
|
@@ -685,8 +1000,65 @@ export async function createBrepkitPrimitives() {
|
|
|
685
1000
|
},
|
|
686
1001
|
// ── 测量 ──
|
|
687
1002
|
getBoundingBox(shape, _useTriangulation) {
|
|
688
|
-
const
|
|
689
|
-
|
|
1003
|
+
const s = asNum(shape);
|
|
1004
|
+
const bboxFromPoints = (pts) => {
|
|
1005
|
+
let xmin = Infinity, ymin = Infinity, zmin = Infinity, xmax = -Infinity, ymax = -Infinity, zmax = -Infinity;
|
|
1006
|
+
for (let i = 0; i + 2 < pts.length; i += 3) {
|
|
1007
|
+
xmin = Math.min(xmin, pts[i]);
|
|
1008
|
+
ymin = Math.min(ymin, pts[i + 1]);
|
|
1009
|
+
zmin = Math.min(zmin, pts[i + 2]);
|
|
1010
|
+
xmax = Math.max(xmax, pts[i]);
|
|
1011
|
+
ymax = Math.max(ymax, pts[i + 1]);
|
|
1012
|
+
zmax = Math.max(zmax, pts[i + 2]);
|
|
1013
|
+
}
|
|
1014
|
+
if (!Number.isFinite(xmin)) {
|
|
1015
|
+
xmin = ymin = zmin = 0;
|
|
1016
|
+
xmax = ymax = zmax = 0;
|
|
1017
|
+
}
|
|
1018
|
+
return { xmin, ymin, zmin, xmax, ymax, zmax };
|
|
1019
|
+
};
|
|
1020
|
+
const solidBbox = (h) => {
|
|
1021
|
+
const b = kernel.boundingBox(h);
|
|
1022
|
+
return { xmin: b[0], ymin: b[1], zmin: b[2], xmax: b[3], ymax: b[4], zmax: b[5] };
|
|
1023
|
+
};
|
|
1024
|
+
const kids = knownCompounds.get(s);
|
|
1025
|
+
if (kids) {
|
|
1026
|
+
let xmin = Infinity, ymin = Infinity, zmin = Infinity, xmax = -Infinity, ymax = -Infinity, zmax = -Infinity;
|
|
1027
|
+
for (const k of kids) {
|
|
1028
|
+
try {
|
|
1029
|
+
let b;
|
|
1030
|
+
if (knownFaces.has(k))
|
|
1031
|
+
b = bboxFromPoints(arr(kernel.tessellateFace(k, 0.1, 1).positions));
|
|
1032
|
+
else if (knownWires.has(k))
|
|
1033
|
+
b = bboxFromPoints(arr(kernel.getWireEdges(k)).flatMap((e) => arr(kernel.tessellateEdge(e, 16))));
|
|
1034
|
+
else if (knownEdges.has(k))
|
|
1035
|
+
b = bboxFromPoints(arr(kernel.tessellateEdge(k, 16)));
|
|
1036
|
+
else
|
|
1037
|
+
b = solidBbox(k);
|
|
1038
|
+
xmin = Math.min(xmin, b.xmin);
|
|
1039
|
+
ymin = Math.min(ymin, b.ymin);
|
|
1040
|
+
zmin = Math.min(zmin, b.zmin);
|
|
1041
|
+
xmax = Math.max(xmax, b.xmax);
|
|
1042
|
+
ymax = Math.max(ymax, b.ymax);
|
|
1043
|
+
zmax = Math.max(zmax, b.zmax);
|
|
1044
|
+
}
|
|
1045
|
+
catch { /* 子项 bbox 查询失败忽略 */ }
|
|
1046
|
+
}
|
|
1047
|
+
if (!Number.isFinite(xmin)) {
|
|
1048
|
+
xmin = ymin = zmin = 0;
|
|
1049
|
+
xmax = ymax = zmax = 0;
|
|
1050
|
+
}
|
|
1051
|
+
return { xmin, ymin, zmin, xmax, ymax, zmax };
|
|
1052
|
+
}
|
|
1053
|
+
// GOTCHA:kernel.boundingBox 是 solid-only。face/wire/edge 句柄必须走 tessellation 采样,
|
|
1054
|
+
// 否则 measure() 对 wire/profile 调 getBoundingBox 即崩 "invalid solid handle"。
|
|
1055
|
+
if (knownFaces.has(s))
|
|
1056
|
+
return bboxFromPoints(arr(kernel.tessellateFace(s, 0.1, 1).positions));
|
|
1057
|
+
if (knownWires.has(s))
|
|
1058
|
+
return bboxFromPoints(arr(kernel.getWireEdges(s)).flatMap((e) => arr(kernel.tessellateEdge(e, 16))));
|
|
1059
|
+
if (knownEdges.has(s))
|
|
1060
|
+
return bboxFromPoints(arr(kernel.tessellateEdge(s, 16)));
|
|
1061
|
+
return solidBbox(s);
|
|
690
1062
|
},
|
|
691
1063
|
getVolume(shape) { return Number(kernel.volume(asNum(shape), 0.05)); },
|
|
692
1064
|
getCenterOfMass(shape) {
|