brepjs 20.13.0 → 20.13.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.
Files changed (100) hide show
  1. package/dist/2d.cjs +6 -6
  2. package/dist/2d.js +6 -6
  3. package/dist/{adjacencyFns-BXzEHSC5.cjs → adjacencyFns-BBugwmvl.cjs} +3 -3
  4. package/dist/{adjacencyFns-BYjzMejn.js → adjacencyFns-V29Gvfs9.js} +3 -3
  5. package/dist/{blueprint-464KGkzc.cjs → blueprint-BO8zuqoy.cjs} +5 -5
  6. package/dist/{blueprint-D-4-uTkr.js → blueprint-CDmOJybf.js} +5 -5
  7. package/dist/{blueprintFns-CE5RdX-E.js → blueprintFns-CEKZsu4N.js} +2 -2
  8. package/dist/{blueprintFns-BdyFiuiC.cjs → blueprintFns-Cki-iwEl.cjs} +2 -2
  9. package/dist/{blueprintSketcher-C4XHzqTw.cjs → blueprintSketcher-Bxkp-vjK.cjs} +3 -3
  10. package/dist/{blueprintSketcher-CQ9LxcIl.js → blueprintSketcher-DG_2zJg7.js} +3 -3
  11. package/dist/{boolean2D-CKE32vHV.cjs → boolean2D-C_sEMz0u.cjs} +4 -4
  12. package/dist/{boolean2D-DwJvQ4p_.js → boolean2D-DqCcYA2X.js} +4 -4
  13. package/dist/{booleanFns-MkzdfjDv.cjs → booleanFns-CA9tvtqr.cjs} +5 -5
  14. package/dist/{booleanFns-CsTz48A3.js → booleanFns-CopMFBLS.js} +5 -5
  15. package/dist/brepjs.cjs +33 -31
  16. package/dist/brepjs.js +33 -31
  17. package/dist/{cameraFns-BDl_D28X.js → cameraFns-BIIpsfds.js} +3 -3
  18. package/dist/{cameraFns-vHpapIZG.cjs → cameraFns-BJ35ht2H.cjs} +3 -3
  19. package/dist/core.cjs +2 -2
  20. package/dist/core.js +2 -2
  21. package/dist/{cornerFinder-Dyk5sKEN.js → cornerFinder-C8VCstc3.js} +1 -1
  22. package/dist/{cornerFinder-adDN0_C4.cjs → cornerFinder-D83OLL_I.cjs} +1 -1
  23. package/dist/{curveFns-RjNVzxNc.js → curveFns--TqXOi8f.js} +1 -1
  24. package/dist/{curveFns-HtjEh2W-.cjs → curveFns-B29Eot3K.cjs} +1 -1
  25. package/dist/{drawFns-B5gZtD_P.cjs → drawFns-Bqs4HPzo.cjs} +12 -12
  26. package/dist/{drawFns-stc8-Gma.js → drawFns-g9FrZ_P4.js} +12 -12
  27. package/dist/{faceFns-C7wBHMPv.cjs → faceFns-BeSpw0Dz.cjs} +2 -2
  28. package/dist/{faceFns-DaPoCzd-.js → faceFns-BzHfvPea.js} +2 -2
  29. package/dist/{healingFns-zYCbT8vR.cjs → healingFns-BtZg6i3J.cjs} +31 -14
  30. package/dist/{healingFns-C3Wq_hN3.js → healingFns-DtefNa-x.js} +31 -14
  31. package/dist/{helpers-BdtYVyP5.cjs → helpers-CFEuQ0ok.cjs} +6 -6
  32. package/dist/{helpers-CDzSlBi_.js → helpers-CHUowMmV.js} +6 -6
  33. package/dist/{importFns-DR7Adv-W.js → importFns-BLaneR8r.js} +34 -20
  34. package/dist/{importFns-CotbQ9l7.cjs → importFns-BX0866c6.cjs} +34 -20
  35. package/dist/io/importFns.d.ts +12 -5
  36. package/dist/io.cjs +2 -2
  37. package/dist/io.js +2 -2
  38. package/dist/kernel/brepkit/brepkitWasmTypes.d.ts +0 -4
  39. package/dist/kernel/brepkit/helpers.d.ts +15 -0
  40. package/dist/kernel/brepkit/igesMerge.d.ts +13 -0
  41. package/dist/kernel/brepkit/measureOps.d.ts +7 -0
  42. package/dist/kernel/brepkit/modifierOps.d.ts +11 -1
  43. package/dist/kernel/brepkit/stepMerge.d.ts +14 -0
  44. package/dist/kernel/occtWasm/occtWasmAdapter.cjs +1 -1
  45. package/dist/kernel/occtWasm/occtWasmAdapter.js +1 -1
  46. package/dist/{loftFns-C9k9UAKh.cjs → loftFns-Byjbneme.cjs} +31 -16
  47. package/dist/{loftFns-Cbnrz3p8.js → loftFns-Cth8Le9X.js} +32 -17
  48. package/dist/{measureFns-Bi62maOJ.cjs → measureFns-BlS2xJcn.cjs} +53 -9
  49. package/dist/{measureFns-DivR02Xb.js → measureFns-CLsSuByu.js} +49 -11
  50. package/dist/measurement/measureFns.d.ts +16 -1
  51. package/dist/measurement.cjs +2 -2
  52. package/dist/measurement.js +2 -2
  53. package/dist/{meshFns-CcuY52tt.js → meshFns-CzpP9SBf.js} +4 -4
  54. package/dist/{meshFns-8MTf0Lo_.cjs → meshFns-ECQqx-8Q.cjs} +4 -4
  55. package/dist/{occtWasmAdapter-BNOuP9DA.cjs → occtWasmAdapter-4YnvqZOW.cjs} +72 -3
  56. package/dist/{occtWasmAdapter-1k4udeb3.js → occtWasmAdapter-CGsnjMOe.js} +72 -3
  57. package/dist/operations/sweepFns.d.ts +7 -3
  58. package/dist/operations.cjs +2 -2
  59. package/dist/operations.js +2 -2
  60. package/dist/{primitiveFns-BifV4IGL.cjs → primitiveFns-CzhQz02F.cjs} +5 -5
  61. package/dist/{primitiveFns-uDvN1FH6.js → primitiveFns-Kph3YP3A.js} +5 -5
  62. package/dist/{principalBoxFns-ez0xXLyK.js → principalBoxFns-C8ZmZNxJ.js} +3 -3
  63. package/dist/{principalBoxFns-DjxE8Rym.cjs → principalBoxFns-Ck9rR3Op.cjs} +3 -3
  64. package/dist/projection.cjs +1 -1
  65. package/dist/projection.js +1 -1
  66. package/dist/query.cjs +2 -2
  67. package/dist/query.js +2 -2
  68. package/dist/{refResolveFns-CTkAPUwM.cjs → refResolveFns-CVUSuVee.cjs} +6 -6
  69. package/dist/{refResolveFns-CuaYXFya.js → refResolveFns-Cf16q6AH.js} +6 -6
  70. package/dist/{shapeFns-Rmgd4x9I.cjs → shapeFns-BEwoFgq2.cjs} +2 -2
  71. package/dist/{shapeFns-DgpWLKra.js → shapeFns-Bepwarf3.js} +2 -2
  72. package/dist/shapeRef.cjs +1 -1
  73. package/dist/shapeRef.js +1 -1
  74. package/dist/{shapeTypes-BjitsJRQ.cjs → shapeTypes-CIw6-GFy.cjs} +899 -198
  75. package/dist/{shapeTypes-DbPghQD3.js → shapeTypes-hLHmANUz.js} +899 -198
  76. package/dist/sketching.cjs +3 -3
  77. package/dist/sketching.js +3 -3
  78. package/dist/{solidBuilders-Bs2NA3xl.cjs → solidBuilders-BVHK-era.cjs} +3 -3
  79. package/dist/{solidBuilders-BUI_fGzR.js → solidBuilders-IA8xNuxJ.js} +3 -3
  80. package/dist/{surfaceBuilders-CO_DKhWA.js → surfaceBuilders-BYwkPzfM.js} +3 -3
  81. package/dist/{surfaceBuilders-Cn1mkQ3j.cjs → surfaceBuilders-DIM5GSAT.cjs} +3 -3
  82. package/dist/text.cjs +2 -2
  83. package/dist/text.js +2 -2
  84. package/dist/{textBlueprints-CT0Kbw1f.js → textBlueprints-B4w-_4_D.js} +8 -8
  85. package/dist/{textBlueprints-P_D0KUVx.cjs → textBlueprints-CPJ6sBZZ.cjs} +8 -8
  86. package/dist/{textMetrics-CVyX8GyL.cjs → textMetrics-Dfr_9G6g.cjs} +1 -1
  87. package/dist/{textMetrics-CXKdzefS.js → textMetrics-na1BYw65.js} +1 -1
  88. package/dist/{threadFns-COs6jmaf.js → threadFns-BEO0-PP6.js} +7 -7
  89. package/dist/{threadFns-BV4HLdPE.cjs → threadFns-CAsRIX6c.cjs} +7 -7
  90. package/dist/topology/apiTypes.d.ts +7 -3
  91. package/dist/topology/chamferAngleFns.d.ts +2 -2
  92. package/dist/topology/evolutionFns.d.ts +1 -1
  93. package/dist/topology/modifierFns.d.ts +11 -4
  94. package/dist/topology.cjs +9 -9
  95. package/dist/topology.js +9 -9
  96. package/dist/{topologyQueryFns-s0LddGkF.cjs → topologyQueryFns-BNDmjz7x.cjs} +1 -1
  97. package/dist/{topologyQueryFns-J2ae_Gvc.js → topologyQueryFns-BYqsNs6q.js} +1 -1
  98. package/dist/{validityTypes-CD_Xz0vZ.js → validityTypes-BYhzdgTM.js} +1 -1
  99. package/dist/{validityTypes-BVZ0lSNi.cjs → validityTypes-Gd5B_HcL.cjs} +1 -1
  100. package/package.json +2 -2
@@ -1,4 +1,4 @@
1
- import { A as UnsupportedKernelOperationError, C as reverseCurve2d$2, D as tangentCurve2d, E as serializeCurve2d$2, N as EXACT_BREP_CAPABILITIES, O as translateCurve2d$2, P as OCCT_CAPABILITIES, S as offsetCurve2d$2, T as scaleCurve2d$2, _ as makeCircle2d$2, a as buildAsciiSTL, b as mirrorAcrossAxis, c as createBBox2d, d as deserializeCurve2d$2, f as evaluateCurve2d$2, g as makeBezier2d$2, h as makeArc2dThreePoints, i as DEFAULT_STL_TOLERANCE, j as isUnsupportedKernelOperationError, k as quickHull, l as curveBounds, m as makeArc2dTangent, o as buildBinarySTL, p as intersectCurves2dFn, r as DEFAULT_STL_ANGULAR_TOLERANCE, s as addCurveToBBox, t as OcctWasmAdapter, u as curveTypeName, v as makeEllipse2d$2, w as rotateCurve2d$2, x as mirrorAtPoint, y as makeLine2d$2 } from "./occtWasmAdapter-1k4udeb3.js";
1
+ import { A as UnsupportedKernelOperationError, C as reverseCurve2d$2, D as tangentCurve2d, E as serializeCurve2d$2, N as EXACT_BREP_CAPABILITIES, O as translateCurve2d$2, P as OCCT_CAPABILITIES, S as offsetCurve2d$2, T as scaleCurve2d$2, _ as makeCircle2d$2, a as buildAsciiSTL, b as mirrorAcrossAxis, c as createBBox2d, d as deserializeCurve2d$2, f as evaluateCurve2d$2, g as makeBezier2d$2, h as makeArc2dThreePoints, i as DEFAULT_STL_TOLERANCE, j as isUnsupportedKernelOperationError, k as quickHull, l as curveBounds, m as makeArc2dTangent, o as buildBinarySTL, p as intersectCurves2dFn, r as DEFAULT_STL_ANGULAR_TOLERANCE, s as addCurveToBBox, t as OcctWasmAdapter, u as curveTypeName, v as makeEllipse2d$2, w as rotateCurve2d$2, x as mirrorAtPoint, y as makeLine2d$2 } from "./occtWasmAdapter-CGsnjMOe.js";
2
2
  import { i as wasmIndex, r as vec3At } from "./bug-ClMTAJhe.js";
3
3
  // src/kernel/quality.ts
4
4
  /**
@@ -5397,6 +5397,124 @@ function dist3(x1, y1, z1, x2, y2, z2) {
5397
5397
  const dx = x1 - x2, dy = y1 - y2, dz = z1 - z2;
5398
5398
  return Math.sqrt(dx * dx + dy * dy + dz * dz);
5399
5399
  }
5400
+ var TAU$1 = 2 * Math.PI;
5401
+ /**
5402
+ * The frame of an analytic circle or ellipse edge, evaluated at t as
5403
+ * center + U·cos t + V·sin t, recovered from its points at 0, π/2 and π.
5404
+ * brepkit reports such an edge's domain as exactly [0, 2π]; a NURBS edge that
5405
+ * happens to be a circle reports its knot domain and gets no frame.
5406
+ */
5407
+ function conicFrame(bk, edgeId) {
5408
+ const params = bk.getEdgeCurveParameters(edgeId);
5409
+ if (wasmIndex(params, 0) !== 0 || wasmIndex(params, 1) !== TAU$1) return void 0;
5410
+ const curveType = bk.getEdgeCurveType(edgeId);
5411
+ if (curveType !== "CIRCLE" && curveType !== "ELLIPSE") return void 0;
5412
+ const at = (t) => {
5413
+ const p = bk.evaluateEdgeCurve(edgeId, t);
5414
+ return [
5415
+ wasmIndex(p, 0),
5416
+ wasmIndex(p, 1),
5417
+ wasmIndex(p, 2)
5418
+ ];
5419
+ };
5420
+ const p0 = at(0);
5421
+ const pHalf = at(Math.PI / 2);
5422
+ const pPi = at(Math.PI);
5423
+ const center = [
5424
+ (p0[0] + pPi[0]) / 2,
5425
+ (p0[1] + pPi[1]) / 2,
5426
+ (p0[2] + pPi[2]) / 2
5427
+ ];
5428
+ return {
5429
+ center,
5430
+ u: [
5431
+ p0[0] - center[0],
5432
+ p0[1] - center[1],
5433
+ p0[2] - center[2]
5434
+ ],
5435
+ v: [
5436
+ pHalf[0] - center[0],
5437
+ pHalf[1] - center[1],
5438
+ pHalf[2] - center[2]
5439
+ ]
5440
+ };
5441
+ }
5442
+ function edgeEnds(bk, edgeId) {
5443
+ const verts = bk.getEdgeVertices(edgeId);
5444
+ return [[
5445
+ wasmIndex(verts, 0),
5446
+ wasmIndex(verts, 1),
5447
+ wasmIndex(verts, 2)
5448
+ ], [
5449
+ wasmIndex(verts, 3),
5450
+ wasmIndex(verts, 4),
5451
+ wasmIndex(verts, 5)
5452
+ ]];
5453
+ }
5454
+ /**
5455
+ * The curve parameters an edge spans. brepkit reports a circle or ellipse edge's
5456
+ * domain as the whole [0, 2π] even for an arc; the arc runs counterclockwise
5457
+ * about the curve's axis from the angle of its start vertex to that of its end
5458
+ * vertex (brepkit's own `circle_param_range`), angles read off the curve's frame.
5459
+ */
5460
+ function edgeParamRange(bk, edgeId) {
5461
+ const params = bk.getEdgeCurveParameters(edgeId);
5462
+ const t0 = wasmIndex(params, 0);
5463
+ const t1 = wasmIndex(params, 1);
5464
+ const frame = conicFrame(bk, edgeId);
5465
+ if (!frame) return [t0, t1];
5466
+ const [start, end] = edgeEnds(bk, edgeId);
5467
+ if (dist3(...start, ...end) < 1e-7) return [t0, t1];
5468
+ const { center, u, v } = frame;
5469
+ const uu = u[0] * u[0] + u[1] * u[1] + u[2] * u[2];
5470
+ const vv = v[0] * v[0] + v[1] * v[1] + v[2] * v[2];
5471
+ const angleOf = (p) => {
5472
+ const d = [
5473
+ p[0] - center[0],
5474
+ p[1] - center[1],
5475
+ p[2] - center[2]
5476
+ ];
5477
+ const cos = (d[0] * u[0] + d[1] * u[1] + d[2] * u[2]) / uu;
5478
+ const sin = (d[0] * v[0] + d[1] * v[1] + d[2] * v[2]) / vv;
5479
+ return Math.atan2(sin, cos);
5480
+ };
5481
+ const ts = angleOf(start);
5482
+ let te = angleOf(end);
5483
+ if (te <= ts) te += TAU$1;
5484
+ return [ts, te];
5485
+ }
5486
+ var CURVE_BOX_SAMPLES = 32;
5487
+ /**
5488
+ * Points whose bounding box is the edge's: its ends, plus for a circle or
5489
+ * ellipse the points where each coordinate peaks within the arc (exact), and
5490
+ * for any other curve samples along its parameter range.
5491
+ */
5492
+ function edgeBoxPoints(bk, edgeId) {
5493
+ const points = [...edgeEnds(bk, edgeId)];
5494
+ if (bk.getEdgeCurveType(edgeId) === "LINE") return points;
5495
+ const [t0, t1] = edgeParamRange(bk, edgeId);
5496
+ const at = (t) => {
5497
+ const p = bk.evaluateEdgeCurve(edgeId, t);
5498
+ return [
5499
+ wasmIndex(p, 0),
5500
+ wasmIndex(p, 1),
5501
+ wasmIndex(p, 2)
5502
+ ];
5503
+ };
5504
+ const frame = conicFrame(bk, edgeId);
5505
+ if (frame) {
5506
+ for (let k = 0; k < 3; k++) {
5507
+ const peak = Math.atan2(wasmIndex(frame.v, k), wasmIndex(frame.u, k));
5508
+ for (const base of [peak, peak + Math.PI]) {
5509
+ const t = base + TAU$1 * Math.ceil((t0 - base) / TAU$1);
5510
+ if (t <= t1) points.push(at(t));
5511
+ }
5512
+ }
5513
+ return points;
5514
+ }
5515
+ for (let i = 1; i < CURVE_BOX_SAMPLES; i++) points.push(at(t0 + (t1 - t0) * i / CURVE_BOX_SAMPLES));
5516
+ return points;
5517
+ }
5400
5518
  /** Copy a WASM-backed Uint8Array into an independent ArrayBuffer. */
5401
5519
  function copyWasmBytes(bytes) {
5402
5520
  return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
@@ -5866,7 +5984,7 @@ function curveTangent(bk, shape, param) {
5866
5984
  edgeId = wasmIndex(edgeIds, edgeIds.length - 1);
5867
5985
  let cumulative = 0;
5868
5986
  for (const eid of edgeIds) {
5869
- const p = bk.getEdgeCurveParameters(eid);
5987
+ const p = edgeParamRange(bk, eid);
5870
5988
  const span = wasmIndex(p, 1) - wasmIndex(p, 0);
5871
5989
  if (param <= cumulative + span || eid === edgeId) {
5872
5990
  edgeId = eid;
@@ -5889,13 +6007,12 @@ function curveParameters(bk, shape) {
5889
6007
  if (edgeIds.length === 0) return [0, 0];
5890
6008
  let total = 0;
5891
6009
  for (const eid of edgeIds) {
5892
- const p = bk.getEdgeCurveParameters(eid);
6010
+ const p = edgeParamRange(bk, eid);
5893
6011
  total += wasmIndex(p, 1) - wasmIndex(p, 0);
5894
6012
  }
5895
6013
  return [0, total];
5896
6014
  }
5897
- const params = bk.getEdgeCurveParameters(unwrap$1(shape, "edge"));
5898
- return [wasmIndex(params, 0), wasmIndex(params, 1)];
6015
+ return edgeParamRange(bk, unwrap$1(shape, "edge"));
5899
6016
  }
5900
6017
  function curvePointAtParam(bk, shape, param) {
5901
6018
  const h = shape;
@@ -5903,7 +6020,7 @@ function curvePointAtParam(bk, shape, param) {
5903
6020
  const edgeIds = toArray(bk.getWireEdges(h.id));
5904
6021
  let cumulative = 0;
5905
6022
  for (const eid of edgeIds) {
5906
- const p = bk.getEdgeCurveParameters(eid);
6023
+ const p = edgeParamRange(bk, eid);
5907
6024
  const span = wasmIndex(p, 1) - wasmIndex(p, 0);
5908
6025
  if (param <= cumulative + span || eid === edgeIds[edgeIds.length - 1]) {
5909
6026
  const localParam = wasmIndex(p, 0) + (param - cumulative);
@@ -7080,7 +7197,16 @@ function makeWireFromMixed(bk, items) {
7080
7197
  else if (h.type === "wire") for (const childEdgeId of toArray(bk.getWireEdges(h.id))) edgeIds.push(childEdgeId);
7081
7198
  }
7082
7199
  if (edgeIds.length === 0) throw new Error("brepkit: makeWireFromMixed requires at least one edge");
7083
- return wireHandle(bk.makeWire(edgeIds, false));
7200
+ return wireHandle(bk.makeWire(edgeIds, chainCloses(bk, edgeIds)));
7201
+ }
7202
+ /** Matches the 1e-7 linear tolerance brepkit's makeWire welds vertices within. */
7203
+ var WIRE_CLOSE_TOLERANCE = 1e-7;
7204
+ function chainCloses(bk, edgeIds) {
7205
+ const first = bk.getEdgeVertices(wasmIndex(edgeIds, 0));
7206
+ const last = bk.getEdgeVertices(wasmIndex(edgeIds, edgeIds.length - 1));
7207
+ const [sx, sy, sz] = vec3At(first, 0);
7208
+ const [ex, ey, ez] = vec3At(last, 3);
7209
+ return Math.hypot(ex - sx, ey - sy, ez - sz) < WIRE_CLOSE_TOLERANCE;
7084
7210
  }
7085
7211
  function makeCompound(bk, shapes) {
7086
7212
  const handles = shapes.filter(isBrepkitHandle);
@@ -7188,6 +7314,7 @@ function sewAndSolidify(bk, faces, tolerance) {
7188
7314
  function interpolatePoints(bk, points, options) {
7189
7315
  if (options?.tolerance !== void 0) warnOnce("interpolate-tolerance", "interpolatePoints() tolerance parameter is not supported; brepkit uses chord-length parameterisation.");
7190
7316
  if (points.length < 2) throw new Error("brepkit: need at least 2 points");
7317
+ if (options?.periodic) return interpolateClosed(bk, points);
7191
7318
  if (points.length === 2) return makeLineEdge(bk, wasmIndex(points, 0), wasmIndex(points, 1));
7192
7319
  const degree = Math.min(3, points.length - 1);
7193
7320
  const coords = points.flatMap(([x, y, z]) => [
@@ -7197,6 +7324,132 @@ function interpolatePoints(bk, points, options) {
7197
7324
  ]);
7198
7325
  return edgeHandle(bk.interpolatePoints(coords, degree));
7199
7326
  }
7327
+ /**
7328
+ * The closed cubic through `points` that OCCT's periodic GeomAPI_Interpolate
7329
+ * builds, which brepkit-wasm has no binding for. The points are parameterised
7330
+ * by chord length, the closing chord included; the spline is C2 at every point
7331
+ * but the first, where it closes C1 with the tangent of the Lagrange
7332
+ * polynomial through the first four points (three when there are only three).
7333
+ * It is built as one cubic Bezier segment per chord: a clamped NURBS whose
7334
+ * interior knots have multiplicity 3.
7335
+ */
7336
+ function interpolateClosed(bk, points) {
7337
+ if (points.length < 3) throw new UnsupportedKernelOperationError("brepkit: a periodic interpolation needs at least 3 points");
7338
+ const loop = [...points, wasmIndex(points, 0)];
7339
+ const h = loop.slice(1).map((p, i) => {
7340
+ const q = wasmIndex(loop, i);
7341
+ return Math.hypot(p[0] - q[0], p[1] - q[1], p[2] - q[2]);
7342
+ });
7343
+ if (h.some((len) => len === 0)) throw new Error("brepkit: periodic interpolation through repeated points");
7344
+ const t = [0];
7345
+ for (const len of h) t.push(wasmIndex(t, t.length - 1) + len);
7346
+ const tangent = lagrangeStartTangent(loop, t, points.length === 3 ? 2 : 3);
7347
+ const derivatives = [
7348
+ tangent,
7349
+ ...interiorDerivatives(loop, h, tangent),
7350
+ tangent
7351
+ ];
7352
+ const controlPoints = [...wasmIndex(loop, 0)];
7353
+ const knots = [
7354
+ 0,
7355
+ 0,
7356
+ 0,
7357
+ 0
7358
+ ];
7359
+ for (let i = 0; i < h.length; i++) {
7360
+ const third = wasmIndex(h, i) / 3;
7361
+ const p0 = wasmIndex(loop, i);
7362
+ const p1 = wasmIndex(loop, i + 1);
7363
+ const d0 = wasmIndex(derivatives, i);
7364
+ const d1 = wasmIndex(derivatives, i + 1);
7365
+ controlPoints.push(...p0.map((c, k) => c + third * wasmIndex(d0, k)));
7366
+ controlPoints.push(...p1.map((c, k) => c - third * wasmIndex(d1, k)));
7367
+ controlPoints.push(...p1);
7368
+ const ti = wasmIndex(t, i + 1);
7369
+ knots.push(ti, ti, ti);
7370
+ }
7371
+ knots.push(wasmIndex(t, t.length - 1));
7372
+ const [x, y, z] = wasmIndex(loop, 0);
7373
+ const weights = Array(controlPoints.length / 3).fill(1);
7374
+ return edgeHandle(bk.makeNurbsEdge(x, y, z, x, y, z, 3, knots, controlPoints, weights));
7375
+ }
7376
+ /** Derivative at t[0] of the Lagrange polynomial through the first degree + 1 points. */
7377
+ function lagrangeStartTangent(pts, t, degree) {
7378
+ const t0 = wasmIndex(t, 0);
7379
+ const tangent = [
7380
+ 0,
7381
+ 0,
7382
+ 0
7383
+ ];
7384
+ for (let j = 0; j <= degree; j++) {
7385
+ let weight = j === 0 ? 0 : 1;
7386
+ let den = 1;
7387
+ for (let m = 0; m <= degree; m++) {
7388
+ if (m === j) continue;
7389
+ if (j === 0) weight += 1 / (t0 - wasmIndex(t, m));
7390
+ else if (m !== 0) weight *= t0 - wasmIndex(t, m);
7391
+ den *= wasmIndex(t, j) - wasmIndex(t, m);
7392
+ }
7393
+ if (j !== 0) weight /= den;
7394
+ const p = wasmIndex(pts, j);
7395
+ for (let k = 0; k < 3; k++) tangent[k] = wasmIndex(tangent, k) + weight * wasmIndex(p, k);
7396
+ }
7397
+ return tangent;
7398
+ }
7399
+ /**
7400
+ * Derivatives at the interior points of a cubic spline through `pts` (the first
7401
+ * point repeated at the end), from C2 continuity at each, with `tangent` at
7402
+ * both ends: one tridiagonal system per coordinate.
7403
+ */
7404
+ function interiorDerivatives(pts, h, tangent) {
7405
+ const sub = [];
7406
+ const diag = [];
7407
+ const sup = [];
7408
+ const rhs = [
7409
+ [],
7410
+ [],
7411
+ []
7412
+ ];
7413
+ const last = h.length - 1;
7414
+ for (let i = 1; i <= last; i++) {
7415
+ const hp = wasmIndex(h, i - 1);
7416
+ const hn = wasmIndex(h, i);
7417
+ sub.push(hn);
7418
+ diag.push(2 * (hp + hn));
7419
+ sup.push(hp);
7420
+ const prev = wasmIndex(pts, i - 1);
7421
+ const cur = wasmIndex(pts, i);
7422
+ const next = wasmIndex(pts, i + 1);
7423
+ for (let k = 0; k < 3; k++) {
7424
+ const into = wasmIndex(cur, k) - wasmIndex(prev, k);
7425
+ const out = wasmIndex(next, k) - wasmIndex(cur, k);
7426
+ let r = 3 * (hn / hp * into + hp / hn * out);
7427
+ if (i === 1) r -= hn * wasmIndex(tangent, k);
7428
+ if (i === last) r -= hp * wasmIndex(tangent, k);
7429
+ wasmIndex(rhs, k).push(r);
7430
+ }
7431
+ }
7432
+ const [xs, ys, zs] = rhs.map((r) => solveTridiagonal(sub, diag, sup, r));
7433
+ return xs.map((x, i) => [
7434
+ x,
7435
+ wasmIndex(ys, i),
7436
+ wasmIndex(zs, i)
7437
+ ]);
7438
+ }
7439
+ /** Solve a tridiagonal system (sub-, main and super-diagonal) by the Thomas algorithm. */
7440
+ function solveTridiagonal(sub, diag, sup, rhs) {
7441
+ const n = diag.length;
7442
+ const c = [];
7443
+ const d = [];
7444
+ for (let i = 0; i < n; i++) {
7445
+ const den = wasmIndex(diag, i) - (i > 0 ? wasmIndex(sub, i) * wasmIndex(c, i - 1) : 0);
7446
+ c.push(wasmIndex(sup, i) / den);
7447
+ d.push((wasmIndex(rhs, i) - (i > 0 ? wasmIndex(sub, i) * wasmIndex(d, i - 1) : 0)) / den);
7448
+ }
7449
+ const x = Array(n).fill(0);
7450
+ for (let i = n - 1; i >= 0; i--) x[i] = wasmIndex(d, i) - (i < n - 1 ? wasmIndex(c, i) * wasmIndex(x, i + 1) : 0);
7451
+ return x;
7452
+ }
7200
7453
  function approximatePoints(bk, points, options) {
7201
7454
  const degree = options?.degMax ?? 3;
7202
7455
  const tol = options?.tolerance ?? 1e-6;
@@ -7481,59 +7734,96 @@ function fillet(bk, shape, edges, radius) {
7481
7734
  const solidId = unwrapSolidOrThrow(shape, "fillet");
7482
7735
  const edgeIds = edges.map((e) => unwrap$1(e, "edge"));
7483
7736
  if (typeof radius === "number") return solidHandle(bk.fillet(solidId, edgeIds, radius));
7484
- const spec = [];
7485
- for (const [i, edge] of edges.entries()) {
7486
- const edgeId = edgeIds[i] ?? 0;
7487
- let r;
7488
- if (typeof radius === "function") r = radius(edge);
7489
- else r = radius;
7490
- const [startR, endR] = Array.isArray(r) ? r : [r, r];
7491
- spec.push({
7492
- edge: edgeId,
7493
- startRadius: startR,
7494
- endRadius: endR
7495
- });
7737
+ const radii = /* @__PURE__ */ new Set();
7738
+ for (const edge of edges) {
7739
+ const r = typeof radius === "function" ? radius(edge) : radius;
7740
+ const [start, end] = Array.isArray(r) ? r : [r, r];
7741
+ if (start !== end) throw varyingFilletUnsupported();
7742
+ radii.add(start);
7496
7743
  }
7497
- return solidHandle(bk.filletVariable(solidId, JSON.stringify(spec)));
7744
+ if (radii.size > 1) throw perEdgeUnsupported("fillet radii");
7745
+ const [only] = radii;
7746
+ if (only === void 0) throw new Error("brepkit: fillet needs at least one edge");
7747
+ return solidHandle(bk.fillet(solidId, edgeIds, only));
7748
+ }
7749
+ /**
7750
+ * brepkit takes one value for every edge of a fillet or chamfer call, and a
7751
+ * second call on the first one's result cannot name the input's other edges:
7752
+ * a chamfer fails on them ("no manifold edges to chamfer"), and a fillet builds
7753
+ * the right body but leaves the input solid corrupt, so that filleting the
7754
+ * input's four vertical cube edges at 2 mm afterwards cuts a flat chamfer.
7755
+ */
7756
+ function perEdgeUnsupported(what) {
7757
+ return new UnsupportedKernelOperationError(`brepkit: different ${what} on different edges in one call is not supported`);
7758
+ }
7759
+ /**
7760
+ * brepkit-wasm's filletVariable does not build a rolling-ball blend: on the
7761
+ * 40 mm top edge of a 40 x 30 x 20 box, a radius running 2 to 5 removes 260 mm3
7762
+ * where the blend removes about 113, and even a constant law of 3 removes 180
7763
+ * (the flat chamfer) where fillet removes 77.26. A radius that varies along an
7764
+ * edge is refused rather than built that way.
7765
+ */
7766
+ function varyingFilletUnsupported() {
7767
+ return new UnsupportedKernelOperationError("brepkit: a fillet radius that varies along an edge is not supported");
7498
7768
  }
7499
- /** brepkit's two-distance chamfer, bound to `bk`; throws on builds without it. */
7500
- function requireChamferAsymmetric(bk, what) {
7501
- if (typeof bk.chamferAsymmetric !== "function") throw new UnsupportedKernelOperationError(`brepkit: ${what} requires chamferAsymmetric, which this brepkit-wasm build lacks`);
7502
- return bk.chamferAsymmetric.bind(bk);
7769
+ /** occt's hashCode upper bound, which the kernel-agnostic filletVariable spec hashes edges with. */
7770
+ var HASH_UPPER_BOUND = 2147483647;
7771
+ /**
7772
+ * A variable fillet spec on one edge: `{ edge: hashCode, radii: [{ param, radius }] }`
7773
+ * from variableFillet, or `{ edgeId, startRadius, endRadius }`. A constant radius is
7774
+ * an ordinary fillet; a radius that varies is unsupported (see
7775
+ * {@link varyingFilletUnsupported}).
7776
+ */
7777
+ function filletVariable$1(bk, shape, spec) {
7778
+ const solidId = unwrapSolidOrThrow(shape, "filletVariable");
7779
+ const parsed = JSON.parse(spec);
7780
+ let edgeId;
7781
+ let radii;
7782
+ if (parsed.edge !== void 0 && parsed.radii !== void 0 && parsed.radii.length > 0) {
7783
+ const hash = parsed.edge;
7784
+ edgeId = toArray(bk.getSolidEdges(solidId)).find((id) => id % HASH_UPPER_BOUND === hash);
7785
+ radii = parsed.radii.map((r) => r.radius);
7786
+ } else if (parsed.edgeId !== void 0 && parsed.startRadius !== void 0 && parsed.endRadius !== void 0) {
7787
+ edgeId = parsed.edgeId;
7788
+ radii = [parsed.startRadius, parsed.endRadius];
7789
+ } else throw new UnsupportedKernelOperationError("brepkit: filletVariable: unrecognized spec");
7790
+ if (edgeId === void 0) throw new Error("brepkit: filletVariable: the edge is not an edge of the shape");
7791
+ const [first = 0] = radii;
7792
+ if (radii.some((r) => r !== first)) throw varyingFilletUnsupported();
7793
+ return solidHandle(bk.fillet(solidId, [edgeId], first));
7794
+ }
7795
+ /**
7796
+ * A symmetric chamfer for two equal distances, brepkit's two-distance chamfer
7797
+ * otherwise. brepkit sets `d1` back on the edge's first face in its own face
7798
+ * order, which need not be the face occt-wasm picks for the same edge.
7799
+ */
7800
+ function chamferTwoDistances(bk, solidId, edgeIds, d1, d2) {
7801
+ return d1 === d2 ? bk.chamfer(solidId, edgeIds, d1) : bk.chamferV2(solidId, edgeIds, d1, d2);
7503
7802
  }
7504
7803
  function chamfer(bk, shape, edges, distance) {
7505
7804
  const solidId = unwrapSolidOrThrow(shape, "chamfer");
7506
7805
  const edgeIds = edges.map((e) => unwrap$1(e, "edge"));
7507
7806
  if (typeof distance === "number") return solidHandle(bk.chamfer(solidId, edgeIds, distance));
7508
- if (Array.isArray(distance)) {
7509
- const [d1, d2] = distance;
7510
- if (d1 === d2) return solidHandle(bk.chamfer(solidId, edgeIds, d1));
7511
- return solidHandle(requireChamferAsymmetric(bk, "an asymmetric chamfer")(solidId, edgeIds, d1, d2));
7512
- }
7513
- const groups = /* @__PURE__ */ new Map();
7514
- for (const [i, edge] of edges.entries()) {
7807
+ if (Array.isArray(distance)) return solidHandle(chamferTwoDistances(bk, solidId, edgeIds, distance[0], distance[1]));
7808
+ const pairs = /* @__PURE__ */ new Map();
7809
+ for (const edge of edges) {
7515
7810
  const r = distance(edge);
7516
- const eid = edgeIds[i];
7517
- if (eid === void 0) continue;
7518
7811
  const [d1, d2] = Array.isArray(r) ? r : [r, r];
7519
- const key = `${d1},${d2}`;
7520
- const group = groups.get(key);
7521
- if (group) group.ids.push(eid);
7522
- else groups.set(key, {
7523
- ids: [eid],
7524
- d1,
7525
- d2
7526
- });
7812
+ pairs.set(`${d1},${d2}`, [d1, d2]);
7527
7813
  }
7528
- const asymmetric = [...groups.values()].some((g) => g.d1 !== g.d2) ? requireChamferAsymmetric(bk, "an asymmetric chamfer") : void 0;
7529
- let result = solidId;
7530
- for (const group of groups.values()) result = asymmetric && group.d1 !== group.d2 ? asymmetric(result, group.ids, group.d1, group.d2) : bk.chamfer(result, group.ids, group.d1);
7531
- return solidHandle(result);
7814
+ if (pairs.size > 1) throw perEdgeUnsupported("chamfer distances");
7815
+ const [only] = pairs.values();
7816
+ if (only === void 0) throw new Error("brepkit: chamfer needs at least one edge");
7817
+ return solidHandle(chamferTwoDistances(bk, solidId, edgeIds, only[0], only[1]));
7532
7818
  }
7819
+ /**
7820
+ * brepkit sets `distance` back on the edge's first face in its own face order
7821
+ * and the other face back by distance·tan(angle).
7822
+ */
7533
7823
  function chamferDistAngle$1(bk, shape, edges, distance, angleDeg) {
7534
- const asymmetric = requireChamferAsymmetric(bk, "chamferDistAngle");
7535
- const d2 = distance * Math.tan(angleDeg * Math.PI / 180);
7536
- return solidHandle(asymmetric(unwrapSolidOrThrow(shape, "chamferDistAngle"), edges.map((e) => unwrap$1(e, "edge")), distance, d2));
7824
+ const solidId = unwrapSolidOrThrow(shape, "chamferDistAngle");
7825
+ const edgeIds = edges.map((e) => unwrap$1(e, "edge"));
7826
+ return solidHandle(bk.chamferDistanceAngle(solidId, edgeIds, distance, angleDeg * Math.PI / 180));
7537
7827
  }
7538
7828
  function faceCentroid(bk, faceId) {
7539
7829
  const verts = toArray(bk.getFaceVertices(faceId));
@@ -7616,10 +7906,6 @@ function offset$1(bk, shape, distance, tolerance) {
7616
7906
  const solidId = unwrapSolidOrThrow(shape, "offset");
7617
7907
  return solidHandle(bk.offsetSolidV2(solidId, distance));
7618
7908
  }
7619
- function filletVariable$1(bk, shape, spec) {
7620
- const solidId = unwrapSolidOrThrow(shape, "filletVariable");
7621
- return solidHandle(bk.filletVariable(solidId, spec));
7622
- }
7623
7909
  function draft$1(bk, shape, faces, pullDirection, neutralPlane, angleDeg) {
7624
7910
  const solidId = unwrapSolidOrThrow(shape, "draft");
7625
7911
  const faceIds = faces.map((f) => unwrap$1(f, "face"));
@@ -8016,22 +8302,151 @@ function fuseToolWithHistory(bk, toolSolidIds, accum, hashUpperBound) {
8016
8302
  }
8017
8303
  return union;
8018
8304
  }
8305
+ /** Whether this kernel can merge co-surface faces after `op` (brepkit-wasm >= 3.2). */
8306
+ function canSimplify(bk, op) {
8307
+ if (op === "fuse") return typeof bk.fuseWithOptions === "function";
8308
+ if (op === "cut") return typeof bk.cutWithOptions === "function";
8309
+ return typeof bk.intersectWithOptions === "function";
8310
+ }
8311
+ /** A point inside a face: the centroid of its largest tessellation triangle. */
8312
+ var FACE_POINT_DEFLECTION = .1;
8313
+ function interiorPoint(bk, faceId) {
8314
+ const mesh = bk.tessellateFace(faceId, FACE_POINT_DEFLECTION);
8315
+ const at = (t) => vec3At(mesh.positions, wasmIndex(mesh.indices, t) * 3);
8316
+ let best;
8317
+ let bestArea = -1;
8318
+ for (let t = 0; t + 2 < mesh.indices.length; t += 3) {
8319
+ const [ax, ay, az] = at(t);
8320
+ const [bx, by, bz] = at(t + 1);
8321
+ const [cx, cy, cz] = at(t + 2);
8322
+ const [ux, uy, uz] = [
8323
+ bx - ax,
8324
+ by - ay,
8325
+ bz - az
8326
+ ];
8327
+ const [vx, vy, vz] = [
8328
+ cx - ax,
8329
+ cy - ay,
8330
+ cz - az
8331
+ ];
8332
+ const area = Math.hypot(uy * vz - uz * vy, uz * vx - ux * vz, ux * vy - uy * vx);
8333
+ if (area > bestArea) {
8334
+ bestArea = area;
8335
+ best = [
8336
+ (ax + bx + cx) / 3,
8337
+ (ay + by + cy) / 3,
8338
+ (az + bz + cz) / 3
8339
+ ];
8340
+ }
8341
+ }
8342
+ return best;
8343
+ }
8344
+ /**
8345
+ * The box of a face's tessellation, grown by twice the deflection: the face
8346
+ * lies within one deflection of its mesh, and an `interiorPoint` within one
8347
+ * deflection of the face, so the box holds every interior point of the face.
8348
+ */
8349
+ function faceMeshBox(bk, faceId) {
8350
+ const { positions } = bk.tessellateFace(faceId, FACE_POINT_DEFLECTION);
8351
+ if (positions.length < 3) return void 0;
8352
+ const min = vec3At(positions, 0);
8353
+ const max = [...min];
8354
+ for (let i = 3; i + 2 < positions.length; i += 3) for (let k = 0; k < 3; k++) {
8355
+ const v = wasmIndex(positions, i + k);
8356
+ min[k] = Math.min(wasmIndex(min, k), v);
8357
+ max[k] = Math.max(wasmIndex(max, k), v);
8358
+ }
8359
+ const pad = 2 * FACE_POINT_DEFLECTION;
8360
+ for (let k = 0; k < 3; k++) {
8361
+ min[k] = wasmIndex(min, k) - pad;
8362
+ max[k] = wasmIndex(max, k) + pad;
8363
+ }
8364
+ return {
8365
+ min,
8366
+ max
8367
+ };
8368
+ }
8369
+ function boxHolds(box, p) {
8370
+ return p.every((v, k) => v >= wasmIndex(box.min, k) && v <= wasmIndex(box.max, k));
8371
+ }
8372
+ /**
8373
+ * Carry a boolean's face evolution over to the simplified result of the same
8374
+ * boolean. brepkit tracks face provenance only through its unsimplified
8375
+ * boolean, and merging co-surface faces makes new faces, so the boolean runs
8376
+ * twice: once with evolution, once simplified. Each face of the first result
8377
+ * lies inside one face of the second; a point inside it names that face.
8378
+ */
8379
+ function simplifiedWithHistory(bk, tracked, simplified, hashUpperBound) {
8380
+ const from = tracked.shape;
8381
+ const to = simplified;
8382
+ if (from.type !== "solid" || to.type !== "solid") return {
8383
+ shape: simplified,
8384
+ evolution: tracked.evolution
8385
+ };
8386
+ const targets = toArray(bk.getSolidFaces(to.id)).map((id) => ({
8387
+ id,
8388
+ box: faceMeshBox(bk, id)
8389
+ }));
8390
+ const into = /* @__PURE__ */ new Map();
8391
+ for (const faceId of toArray(bk.getSolidFaces(from.id))) {
8392
+ const p = interiorPoint(bk, faceId);
8393
+ if (!p) continue;
8394
+ const near = targets.filter(({ box }) => !box || boxHolds(box, p));
8395
+ let nearest;
8396
+ let nearestDistance = Infinity;
8397
+ for (const { id } of near.length > 0 ? near : targets) {
8398
+ const d = wasmIndex(bk.pointToFaceDistance(p[0], p[1], p[2], id), 0);
8399
+ if (d < nearestDistance) {
8400
+ nearestDistance = d;
8401
+ nearest = id;
8402
+ }
8403
+ }
8404
+ if (nearest !== void 0) into.set(faceId % hashUpperBound, nearest % hashUpperBound);
8405
+ }
8406
+ const remap = (m) => new Map([...m].map(([k, outs]) => [k, [...new Set(outs.map((o) => into.get(o) ?? o))]]));
8407
+ return {
8408
+ shape: simplified,
8409
+ evolution: {
8410
+ modified: remap(tracked.evolution.modified),
8411
+ generated: remap(tracked.evolution.generated),
8412
+ deleted: tracked.evolution.deleted
8413
+ }
8414
+ };
8415
+ }
8019
8416
  /**
8020
8417
  * Shared implementation for boolean-with-history operations (fuse, cut, intersect).
8418
+ * With `simplify`, the evolution comes from the unsimplified boolean and is
8419
+ * carried over to the simplified one (see {@link simplifiedWithHistory}).
8021
8420
  */
8022
8421
  function booleanWithHistoryImpl(bk, shape, tool, inputFaceHashes, hashUpperBound, options, nativeFn, fallbackFn, op) {
8422
+ const tracked = trackedBoolean(bk, shape, tool, inputFaceHashes, hashUpperBound, options, nativeFn, fallbackFn, op);
8023
8423
  const noDiagnostics = {
8024
8424
  hasErrors: false,
8025
8425
  hasWarnings: false,
8026
8426
  messages: []
8027
8427
  };
8428
+ if (!tracked.native || !options?.simplify || !canSimplify(bk, op)) return {
8429
+ ...tracked.result,
8430
+ diagnostics: noDiagnostics
8431
+ };
8432
+ return {
8433
+ ...simplifiedWithHistory(bk, tracked.result, fallbackFn(shape, tool, options), hashUpperBound),
8434
+ diagnostics: noDiagnostics
8435
+ };
8436
+ }
8437
+ /**
8438
+ * The boolean with its face evolution: brepkit's native evolution for a solid
8439
+ * base (`native: true`), geometric matching otherwise. The native path ignores
8440
+ * `simplify`; the fallback applies it.
8441
+ */
8442
+ function trackedBoolean(bk, shape, tool, inputFaceHashes, hashUpperBound, options, nativeFn, fallbackFn, op) {
8028
8443
  const sh = shape;
8029
8444
  const th = tool;
8030
8445
  if (inputFaceHashes.length > 0 && sh.type === "solid") {
8031
8446
  if (th.type === "solid") try {
8032
8447
  return {
8033
- ...parseNativeEvolution(nativeFn(sh.id, th.id), hashUpperBound),
8034
- diagnostics: noDiagnostics
8448
+ result: parseNativeEvolution(nativeFn(sh.id, th.id), hashUpperBound),
8449
+ native: true
8035
8450
  };
8036
8451
  } catch (e) {
8037
8452
  if (!isEmptyBooleanError(e)) throw e;
@@ -8047,19 +8462,21 @@ function booleanWithHistoryImpl(bk, shape, tool, inputFaceHashes, hashUpperBound
8047
8462
  let steps = toolSolidIds;
8048
8463
  if (op === "intersect" && toolSolidIds.length > 1) steps = [fuseToolWithHistory(bk, toolSolidIds, accum, hashUpperBound) ?? bk.fuseAll(toolSolidIds)];
8049
8464
  return {
8050
- shape: applyCompoundBooleanWithHistory(shape, steps, accum, hashUpperBound, nativeFn),
8051
- evolution: {
8052
- modified: accum.combinedModified,
8053
- generated: accum.combinedGenerated,
8054
- deleted: accum.combinedDeleted
8465
+ result: {
8466
+ shape: applyCompoundBooleanWithHistory(shape, steps, accum, hashUpperBound, nativeFn),
8467
+ evolution: {
8468
+ modified: accum.combinedModified,
8469
+ generated: accum.combinedGenerated,
8470
+ deleted: accum.combinedDeleted
8471
+ }
8055
8472
  },
8056
- diagnostics: noDiagnostics
8473
+ native: true
8057
8474
  };
8058
8475
  }
8059
8476
  }
8060
8477
  return {
8061
- ...buildEvolution(bk, fallbackFn(shape, tool, options), inputFaceHashes, hashUpperBound, false, shape),
8062
- diagnostics: noDiagnostics
8478
+ result: buildEvolution(bk, fallbackFn(shape, tool, options), inputFaceHashes, hashUpperBound, false, shape),
8479
+ native: false
8063
8480
  };
8064
8481
  }
8065
8482
  function translateWithHistory(bk, shape, x, y, z, inputFaceHashes, hashUpperBound) {
@@ -8161,36 +8578,45 @@ function mesh$1(bk, shape, options) {
8161
8578
  const angularTol = options.angularTolerance > 0 ? options.angularTolerance : void 0;
8162
8579
  let result;
8163
8580
  if (bkHandle.type === "solid") result = meshSolid(bk, h, deflection, !!options.includeUVs, angularTol);
8164
- else if (bkHandle.type === "face") result = meshSingleFace(bk, h, deflection, 0, angularTol);
8165
- else if (bkHandle.type === "compound") result = meshCompound(bk, h, deflection, !!options.includeUVs, angularTol);
8581
+ else if (bkHandle.type === "face") result = meshSingleFace(bk, h, deflection, angularTol);
8582
+ else if (bkHandle.type === "compound" || bkHandle.type === "shell") result = concatMeshes(meshParts(bk, bkHandle, deflection, !!options.includeUVs, angularTol));
8166
8583
  else throw new Error(`brepkit: cannot mesh shape of type '${bkHandle.type}'`);
8167
8584
  if (options.skipNormals) result.normals = /* @__PURE__ */ new Float32Array(0);
8168
8585
  if (!options.includeUVs) result.uvs = /* @__PURE__ */ new Float32Array(0);
8169
8586
  return result;
8170
8587
  }
8171
8588
  /**
8172
- * Mesh every solid in a compound and concatenate the results.
8589
+ * Mesh each solid and face inside a compound or shell, one part apiece.
8173
8590
  *
8174
- * Text solids are compounds — one solid per glyph — so without this the whole
8175
- * text path throws before any geometry runs, which reads as a geometry failure
8176
- * when it is really a missing adapter capability.
8591
+ * Text solids are compounds (one solid per glyph), and a sheet of loose faces
8592
+ * is a compound of faces or a sewn shell, so each child is meshed by what it
8593
+ * is: a solid as a solid, a shell as its faces, a face as itself.
8594
+ *
8595
+ * Compounds come in two flavours and only one is arena-backed. A synthetic
8596
+ * compound (non-solid children, id from `nextSyntheticId`) exists JS-side only,
8597
+ * so asking the kernel to resolve it raises an invalid-handle error; its
8598
+ * children are read from `syntheticCompounds` instead. Wires, edges and
8599
+ * vertices contribute no triangles and are skipped rather than throwing.
8600
+ */
8601
+ function meshParts(bk, h, deflection, includeUVs, angularTolerance) {
8602
+ switch (h.type) {
8603
+ case "solid": return [meshSolid(bk, h.id, deflection, includeUVs, angularTolerance)];
8604
+ case "face": return [meshSingleFace(bk, h.id, deflection, angularTolerance)];
8605
+ case "shell": return toArray(bk.getShellFaces(h.id)).map((faceId) => meshSingleFace(bk, faceId, deflection, angularTolerance));
8606
+ case "compound": return (syntheticCompounds.get(h.id) ?? toArray(bk.getCompoundSolids(h.id)).map(solidHandle)).flatMap((child) => meshParts(bk, child, deflection, includeUVs, angularTolerance));
8607
+ default: return [];
8608
+ }
8609
+ }
8610
+ /**
8611
+ * Concatenate meshes into one.
8177
8612
  *
8178
8613
  * Triangle indices are vertex-relative, so each part's are rebased by the
8179
8614
  * running vertex count; `faceGroups` index into the triangle list, so their
8180
8615
  * `start` is rebased by the running triangle count. `uvs` are only carried
8181
8616
  * when every part supplied them, since a partial UV array would misalign
8182
8617
  * against the concatenated vertices.
8183
- *
8184
- * Compounds come in two flavours and only one is arena-backed. A synthetic
8185
- * compound (non-solid children, id from `nextSyntheticId`) exists JS-side only,
8186
- * so asking the kernel to resolve it raises an invalid-handle error; its
8187
- * children are read from `syntheticCompounds` instead. Non-solid children
8188
- * contribute no triangles and are skipped rather than throwing, matching how a
8189
- * wire or edge meshes to nothing elsewhere in this adapter.
8190
8618
  */
8191
- function meshCompound(bk, compoundId, deflection, includeUVs, angularTolerance) {
8192
- const synthetic = syntheticCompounds.get(compoundId);
8193
- const parts = (synthetic ? synthetic.filter((c) => c.type === "solid").map((c) => c.id) : toArray(bk.getCompoundSolids(compoundId))).map((id) => meshSolid(bk, id, deflection, includeUVs, angularTolerance));
8619
+ function concatMeshes(parts) {
8194
8620
  if (parts.length === 1) return wasmIndex(parts, 0);
8195
8621
  if (parts.length === 0) return {
8196
8622
  vertices: /* @__PURE__ */ new Float32Array(0),
@@ -8237,12 +8663,21 @@ function meshCompound(bk, compoundId, deflection, includeUVs, angularTolerance)
8237
8663
  }
8238
8664
  function meshEdges$1(bk, shape, tolerance, angularTolerance) {
8239
8665
  const bkHandle = shape;
8240
- if (bkHandle.type !== "solid") return {
8241
- lines: /* @__PURE__ */ new Float32Array(0),
8242
- edgeGroups: []
8243
- };
8244
8666
  const angularTol = angularTolerance > 0 ? angularTolerance : void 0;
8667
+ if (bkHandle.type !== "solid") {
8668
+ const seen = /* @__PURE__ */ new Set();
8669
+ const edgeIds = [];
8670
+ for (const edge of iterShapes$1(bk, shape, "edge")) {
8671
+ const id = unwrap$1(edge);
8672
+ if (seen.has(id)) continue;
8673
+ seen.add(id);
8674
+ edgeIds.push(id);
8675
+ }
8676
+ return sampleEdges(bk, edgeIds, tolerance, angularTol ?? DEFAULT_ANGULAR_TOLERANCE);
8677
+ }
8678
+ const edgeIds = toArray(bk.getSolidEdges(bkHandle.id));
8245
8679
  const edgeLines = bk.meshEdgesAll(bkHandle.id, tolerance, angularTol);
8680
+ if (edgeLines.edgeCount !== edgeIds.length) return sampleEdges(bk, edgeIds, tolerance, angularTol ?? DEFAULT_ANGULAR_TOLERANCE);
8246
8681
  const positions = edgeLines.positions;
8247
8682
  const offsets = edgeLines.offsets;
8248
8683
  const edgeCount = edgeLines.edgeCount;
@@ -8262,7 +8697,7 @@ function meshEdges$1(bk, shape, tolerance, angularTolerance) {
8262
8697
  edgeGroups.push({
8263
8698
  start: segStart,
8264
8699
  count: segments.length / 3 - segStart,
8265
- edgeHash: i
8700
+ edgeHash: wasmIndex(edgeIds, i)
8266
8701
  });
8267
8702
  }
8268
8703
  return {
@@ -8270,6 +8705,78 @@ function meshEdges$1(bk, shape, tolerance, angularTolerance) {
8270
8705
  edgeGroups
8271
8706
  };
8272
8707
  }
8708
+ /** brepkit's own default angular deflection (`DEFAULT_ANGULAR_TOL`, ~20°). */
8709
+ var DEFAULT_ANGULAR_TOLERANCE = .35;
8710
+ var EDGE_SAMPLE_START_SEGMENTS = 4;
8711
+ var EDGE_SAMPLE_MAX_DEPTH = 8;
8712
+ /**
8713
+ * Sample edges one at a time into line-segment pairs labelled with each edge's
8714
+ * id. Edges outside a solid have no batch sampler, so each curve is split until
8715
+ * a segment's midpoint sits within `tolerance` of its chord and its end
8716
+ * tangents turn by no more than `angularTolerance`.
8717
+ */
8718
+ function sampleEdges(bk, edgeIds, tolerance, angularTolerance) {
8719
+ const segments = [];
8720
+ const edgeGroups = [];
8721
+ for (const edgeId of edgeIds) {
8722
+ const points = sampleEdgePoints(bk, edgeId, tolerance, angularTolerance);
8723
+ const segStart = segments.length / 3;
8724
+ for (let i = 0; i + 1 < points.length; i++) segments.push(...wasmIndex(points, i), ...wasmIndex(points, i + 1));
8725
+ edgeGroups.push({
8726
+ start: segStart,
8727
+ count: segments.length / 3 - segStart,
8728
+ edgeHash: edgeId
8729
+ });
8730
+ }
8731
+ return {
8732
+ lines: new Float32Array(segments),
8733
+ edgeGroups
8734
+ };
8735
+ }
8736
+ function sampleEdgePoints(bk, edgeId, tolerance, angularTolerance) {
8737
+ if (bk.getEdgeCurveType(edgeId) === "LINE") {
8738
+ const v = bk.getEdgeVertices(edgeId);
8739
+ return [vec3At(v, 0), vec3At(v, 3)];
8740
+ }
8741
+ const [t0, t1] = edgeParamRange(bk, edgeId);
8742
+ const at = (t) => {
8743
+ const r = bk.evaluateEdgeCurveD1(edgeId, t);
8744
+ return {
8745
+ p: vec3At(r, 0),
8746
+ d: vec3At(r, 3)
8747
+ };
8748
+ };
8749
+ const points = [];
8750
+ const refine = (a, pa, b, pb, depth) => {
8751
+ const m = (a + b) / 2;
8752
+ const pm = at(m);
8753
+ const sag = Math.hypot(pm.p[0] - (pa.p[0] + pb.p[0]) / 2, pm.p[1] - (pa.p[1] + pb.p[1]) / 2, pm.p[2] - (pa.p[2] + pb.p[2]) / 2);
8754
+ if (depth < EDGE_SAMPLE_MAX_DEPTH && (sag > tolerance || turnAngle(pa.d, pb.d) > angularTolerance)) {
8755
+ refine(a, pa, m, pm, depth + 1);
8756
+ refine(m, pm, b, pb, depth + 1);
8757
+ return;
8758
+ }
8759
+ points.push(pb.p);
8760
+ };
8761
+ let prevT = t0;
8762
+ let prev = at(t0);
8763
+ points.push(prev.p);
8764
+ for (let i = 1; i <= EDGE_SAMPLE_START_SEGMENTS; i++) {
8765
+ const t = t0 + (t1 - t0) * i / EDGE_SAMPLE_START_SEGMENTS;
8766
+ const next = at(t);
8767
+ refine(prevT, prev, t, next, 0);
8768
+ prevT = t;
8769
+ prev = next;
8770
+ }
8771
+ return points;
8772
+ }
8773
+ function turnAngle(a, b) {
8774
+ const la = Math.hypot(a[0], a[1], a[2]);
8775
+ const lb = Math.hypot(b[0], b[1], b[2]);
8776
+ if (la === 0 || lb === 0) return 0;
8777
+ const cos = (a[0] * b[0] + a[1] * b[1] + a[2] * b[2]) / (la * lb);
8778
+ return Math.acos(Math.min(1, Math.max(-1, cos)));
8779
+ }
8273
8780
  function hasTriangulation$1(_bk, _shape) {
8274
8781
  return false;
8275
8782
  }
@@ -8416,8 +8923,8 @@ function meshSolidPerFace(bk, solidId, deflection, angularTolerance) {
8416
8923
  faceGroups
8417
8924
  };
8418
8925
  }
8419
- /** Tessellate a single face and return brepjs mesh format. */
8420
- function meshSingleFace(bk, faceId, deflection, faceHash, angularTolerance) {
8926
+ /** Tessellate a single face, its triangles grouped under the face's own hash. */
8927
+ function meshSingleFace(bk, faceId, deflection, angularTolerance) {
8421
8928
  const faceMesh = bk.tessellateFace(faceId, deflection, angularTolerance);
8422
8929
  const positions = faceMesh.positions;
8423
8930
  const normals = faceMesh.normals;
@@ -8433,7 +8940,7 @@ function meshSingleFace(bk, faceId, deflection, faceHash, angularTolerance) {
8433
8940
  faceGroups: [{
8434
8941
  start: 0,
8435
8942
  count: indices.length,
8436
- faceHash
8943
+ faceHash: faceId
8437
8944
  }]
8438
8945
  };
8439
8946
  }
@@ -8446,22 +8953,219 @@ function makeMeshOps$1(bk) {
8446
8953
  meshShape: (shape, tolerance, angularTolerance) => {}
8447
8954
  };
8448
8955
  }
8956
+ // src/kernel/brepkit/igesMerge.ts
8957
+ /**
8958
+ * Merge single-solid brepkit IGES documents into one.
8959
+ *
8960
+ * brepkit-wasm's `exportIges` takes one solid, but an IGES file holds one
8961
+ * document. brepkit's writer emits only standalone entities that point at no
8962
+ * other entity, so the merge appends each later document's directory and
8963
+ * parameter entries, renumbering only the fixed-column fields that number or
8964
+ * link them, and keeps the first document's start and global sections.
8965
+ *
8966
+ * @module
8967
+ */
8968
+ /** Plane, line, B-spline curve and B-spline surface: the entities brepkit writes. */
8969
+ var POINTER_FREE = /* @__PURE__ */ new Set([
8970
+ 108,
8971
+ 110,
8972
+ 126,
8973
+ 128
8974
+ ]);
8975
+ var PLANE = 108;
8976
+ /** A plane's sixth parameter points at its bounding curve; brepkit writes none. */
8977
+ var PLANE_BOUND = 5;
8978
+ /** Each line by the section letter in column 73. */
8979
+ function sections(doc) {
8980
+ const bySection = /* @__PURE__ */ new Map();
8981
+ for (const line of doc.split("\n")) {
8982
+ if (line.length < 73) continue;
8983
+ const letter = line.charAt(72);
8984
+ const lines = bySection.get(letter) ?? [];
8985
+ lines.push(line.slice(0, 80));
8986
+ bySection.set(letter, lines);
8987
+ }
8988
+ return {
8989
+ start: bySection.get("S") ?? [],
8990
+ global: bySection.get("G") ?? [],
8991
+ directory: bySection.get("D") ?? [],
8992
+ parameter: bySection.get("P") ?? []
8993
+ };
8994
+ }
8995
+ /** The `index`th 8-column field of a directory line. */
8996
+ function field(line, index) {
8997
+ return Number(line.slice(index * 8, index * 8 + 8).trim() || "0");
8998
+ }
8999
+ function withField(line, index, value) {
9000
+ return line.slice(0, index * 8) + String(value).padStart(8) + line.slice(index * 8 + 8);
9001
+ }
9002
+ /** `line` with its section letter and sequence number in columns 73-80. */
9003
+ function withSequence(line, letter, sequence) {
9004
+ return line.slice(0, 72).padEnd(72) + letter + String(sequence).padStart(7);
9005
+ }
9006
+ /** Throw unless the entity a directory entry describes points at nothing. */
9007
+ function assertPointerFree(line1, line2, parameter) {
9008
+ const type = field(line1, 0);
9009
+ const linked = [
9010
+ 2,
9011
+ 3,
9012
+ 4,
9013
+ 5,
9014
+ 6,
9015
+ 7
9016
+ ].some((i) => field(line1, i) !== 0) || field(line2, 2) !== 0;
9017
+ let bounded = false;
9018
+ if (type === PLANE) {
9019
+ const first = field(line1, 1) - 1;
9020
+ bounded = (parameter.slice(first, first + field(line2, 3)).map((l) => l.slice(0, 64)).join("").split(/[,;]/)[PLANE_BOUND]?.trim() ?? "0") !== "0";
9021
+ }
9022
+ if (!POINTER_FREE.has(type) || linked || bounded) throw new Error(`brepkit: cannot merge IGES documents holding a linked entity (type ${type})`);
9023
+ }
9024
+ /** One IGES document holding the entities of every brepkit IGES document in `docs`. */
9025
+ function mergeIgesDocuments(docs) {
9026
+ const [first, ...rest] = docs;
9027
+ if (first === void 0) return "";
9028
+ if (rest.length === 0) return first;
9029
+ const base = sections(first);
9030
+ const directory = [];
9031
+ const parameter = [];
9032
+ for (const doc of docs) {
9033
+ const { directory: d, parameter: p } = sections(doc);
9034
+ const dOffset = directory.length;
9035
+ const pOffset = parameter.length;
9036
+ for (let i = 0; i + 1 < d.length; i += 2) {
9037
+ const line1 = d[i] ?? "";
9038
+ const line2 = d[i + 1] ?? "";
9039
+ assertPointerFree(line1, line2, p);
9040
+ const renumbered = withField(line1, 1, field(line1, 1) + pOffset);
9041
+ directory.push(withSequence(renumbered, "D", dOffset + i + 1));
9042
+ directory.push(withSequence(line2, "D", dOffset + i + 2));
9043
+ }
9044
+ p.forEach((line, j) => {
9045
+ const owner = Number(line.slice(64, 72).trim()) + dOffset;
9046
+ parameter.push(withSequence(line.slice(0, 64) + String(owner).padStart(8), "P", pOffset + j + 1));
9047
+ });
9048
+ }
9049
+ const count = (letter, n) => letter + String(n).padStart(7);
9050
+ const terminate = count("S", base.start.length) + count("G", base.global.length) + count("D", directory.length) + count("P", parameter.length);
9051
+ return [
9052
+ ...base.start,
9053
+ ...base.global,
9054
+ ...directory,
9055
+ ...parameter,
9056
+ withSequence(terminate, "T", 1),
9057
+ ""
9058
+ ].join("\n");
9059
+ }
9060
+ // src/kernel/brepkit/stepMerge.ts
9061
+ var REPRESENTATION = /^(ADVANCED_BREP_SHAPE_REPRESENTATION\s*\(\s*'(?:[^']|'')*'\s*,\s*\()([^)]*)\)/;
9062
+ /** A string literal, kept as is, or a `#n` entity reference. */
9063
+ var TOKEN = /'(?:[^']|'')*'|#(\d+)/g;
9064
+ /** Rewrite every `#n` reference outside a string literal to `#map(n)`. */
9065
+ function mapRefs(body, map) {
9066
+ return body.replace(TOKEN, (token, id) => id === void 0 ? token : `#${map(Number(id))}`);
9067
+ }
9068
+ function refsOf(body) {
9069
+ const refs = [];
9070
+ mapRefs(body, (id) => {
9071
+ refs.push(id);
9072
+ return id;
9073
+ });
9074
+ return refs;
9075
+ }
9076
+ /** Split the data section into records at semicolons outside string literals. */
9077
+ function parseStep(doc) {
9078
+ const dataStart = doc.indexOf("DATA;");
9079
+ if (dataStart < 0) throw new Error("brepkit: STEP document has no DATA section");
9080
+ const bodyStart = dataStart + 5;
9081
+ const records = [];
9082
+ let inString = false;
9083
+ let start = bodyStart;
9084
+ for (let i = bodyStart; i < doc.length; i++) {
9085
+ const ch = doc.charAt(i);
9086
+ if (ch === "'") inString = !inString;
9087
+ if (inString || ch !== ";") continue;
9088
+ const statement = doc.slice(start, i).trim();
9089
+ if (statement === "ENDSEC") return {
9090
+ head: doc.slice(0, bodyStart),
9091
+ tail: doc.slice(start).trimStart(),
9092
+ records
9093
+ };
9094
+ const match = /^#(\d+)\s*=\s*([\s\S]*)$/.exec(statement);
9095
+ if (!match) throw new Error(`brepkit: unreadable STEP record '${statement.slice(0, 40)}'`);
9096
+ records.push({
9097
+ id: Number(match[1]),
9098
+ body: match[2] ?? ""
9099
+ });
9100
+ start = i + 1;
9101
+ }
9102
+ throw new Error("brepkit: STEP document has an unterminated DATA section");
9103
+ }
9104
+ function representationIndex(records) {
9105
+ const index = records.findIndex((r) => REPRESENTATION.test(r.body));
9106
+ if (index < 0) throw new Error("brepkit: STEP document has no shape representation");
9107
+ return index;
9108
+ }
9109
+ /** The ids of the solids a document's shape representation lists. */
9110
+ function solidIds(records) {
9111
+ const rep = records[representationIndex(records)];
9112
+ return refsOf(REPRESENTATION.exec(rep?.body ?? "")?.[2] ?? "");
9113
+ }
9114
+ /** The records `roots` reach, in file order, renumbered consecutively from `firstId`. */
9115
+ function takeReachable(records, roots, firstId) {
9116
+ const byId = new Map(records.map((r) => [r.id, r.body]));
9117
+ const reached = /* @__PURE__ */ new Set();
9118
+ const pending = [...roots];
9119
+ while (pending.length > 0) {
9120
+ const id = pending.pop() ?? 0;
9121
+ if (reached.has(id)) continue;
9122
+ reached.add(id);
9123
+ for (const ref of refsOf(byId.get(id) ?? "")) pending.push(ref);
9124
+ }
9125
+ const kept = records.filter((r) => reached.has(r.id));
9126
+ const renumber = new Map(kept.map((r, i) => [r.id, firstId + i]));
9127
+ const remap = (id) => renumber.get(id) ?? id;
9128
+ return {
9129
+ lines: kept.map((r) => `#${remap(r.id)} = ${mapRefs(r.body, remap)};`),
9130
+ renumber
9131
+ };
9132
+ }
9133
+ /** One STEP document holding the solids of every brepkit STEP document in `docs`. */
9134
+ function mergeStepDocuments(docs) {
9135
+ const [first, ...rest] = docs;
9136
+ if (first === void 0) return "";
9137
+ if (rest.length === 0) return first;
9138
+ const base = parseStep(first);
9139
+ let nextId = base.records.reduce((max, r) => Math.max(max, r.id), 0) + 1;
9140
+ const added = [];
9141
+ const solids = [];
9142
+ for (const doc of rest) {
9143
+ const { records } = parseStep(doc);
9144
+ const roots = solidIds(records);
9145
+ const { lines, renumber } = takeReachable(records, roots, nextId);
9146
+ for (const line of lines) added.push(line);
9147
+ for (const id of roots) solids.push(renumber.get(id) ?? id);
9148
+ nextId += lines.length;
9149
+ }
9150
+ const repIndex = representationIndex(base.records);
9151
+ const extraItems = solids.map((id) => `, #${id}`).join("");
9152
+ const addItems = (_match, open, items) => `${open}${items}${extraItems})`;
9153
+ const lines = base.records.map(({ id, body }, i) => `#${id} = ${i === repIndex ? body.replace(REPRESENTATION, addItems) : body};`);
9154
+ const merged = [
9155
+ ...lines.slice(0, repIndex),
9156
+ ...added,
9157
+ ...lines.slice(repIndex)
9158
+ ];
9159
+ return `${base.head}\n${merged.join("\n")}\n${base.tail}`;
9160
+ }
8449
9161
  // src/kernel/brepkit/ioOps.ts
8450
9162
  function exportSTEP(bk, shapes) {
8451
9163
  if (shapes.length === 0) return "";
8452
- const parts = [];
8453
- for (const shape of shapes) {
8454
- const solidIds = unwrapSolidsForExport(bk, shape, "exportSTEP");
8455
- for (const sid of solidIds) {
8456
- const bytes = bk.exportStep(sid);
8457
- parts.push(new TextDecoder().decode(bytes));
8458
- }
8459
- }
8460
- return parts.join("\n");
9164
+ return mergeStepDocuments(shapes.flatMap((shape) => unwrapSolidsForExport(bk, shape, "exportSTEP")).map((sid) => new TextDecoder().decode(bk.exportStep(sid))));
8461
9165
  }
8462
9166
  function exportSTL$1(bk, shape, binary, tolerance = DEFAULT_STL_TOLERANCE, angularTolerance = DEFAULT_STL_ANGULAR_TOLERANCE) {
8463
- const solidIds = unwrapSolidsForExport(bk, shape, "exportSTL");
8464
- const { vertices, triangles } = mesh$1(bk, solidHandle(wasmIndex(solidIds, 0)), {
9167
+ unwrapSolidsForExport(bk, shape, "exportSTL");
9168
+ const { vertices, triangles } = mesh$1(bk, shape, {
8465
9169
  tolerance,
8466
9170
  angularTolerance,
8467
9171
  skipNormals: true
@@ -8478,15 +9182,7 @@ function importSTL(bk, data) {
8478
9182
  }
8479
9183
  function exportIGES(bk, shapes) {
8480
9184
  if (shapes.length === 0) return "";
8481
- const parts = [];
8482
- for (const shape of shapes) {
8483
- const solidIds = unwrapSolidsForExport(bk, shape, "exportIGES");
8484
- for (const sid of solidIds) {
8485
- const bytes = bk.exportIges(sid);
8486
- parts.push(new TextDecoder().decode(bytes));
8487
- }
8488
- }
8489
- return parts.join("\n");
9185
+ return mergeIgesDocuments(shapes.flatMap((shape) => unwrapSolidsForExport(bk, shape, "exportIGES")).map((sid) => new TextDecoder().decode(bk.exportIges(sid))));
8490
9186
  }
8491
9187
  function importIGES(bk, data) {
8492
9188
  const bytes = typeof data === "string" ? new TextEncoder().encode(data) : new Uint8Array(data);
@@ -8529,9 +9225,11 @@ function toBREP(bk, shape) {
8529
9225
  }
8530
9226
  function fromBREP(bk, data) {
8531
9227
  if (typeof bk.fromBREP === "function" && data.trimStart().startsWith("{")) return solidHandle(bk.fromBREP(data));
8532
- const first = importSTEP(bk, data)[0];
9228
+ const shapes = importSTEP(bk, data);
9229
+ const [first] = shapes;
8533
9230
  if (!first) throw new Error("brepkit: fromBREP produced no shapes");
8534
- return first;
9231
+ if (shapes.length === 1) return first;
9232
+ return compoundHandle(bk.makeCompound(shapes.map((s) => unwrap$1(s, "solid"))));
8535
9233
  }
8536
9234
  function createXCAFDocument(_bk, shapes) {
8537
9235
  return {
@@ -9222,19 +9920,9 @@ function liftCurve2dToPlane(bk, curve, origin, planeZ, planeX) {
9222
9920
  -planeZ[2]
9223
9921
  ];
9224
9922
  const bounds = curveBounds(c);
9225
- let angularSpan;
9226
- if (c.__bk2d === "trimmed") angularSpan = Math.abs(c.tEnd - c.tStart);
9227
- else angularSpan = 2 * Math.PI;
9228
- const nSegments = angularSpan > Math.PI ? 4 : angularSpan > Math.PI / 2 ? 2 : 1;
9229
- const segmentSpan = (bounds.last - bounds.first) / nSegments;
9230
- const edgeIds = [];
9231
- for (let seg = 0; seg < nSegments; seg++) {
9232
- const [su, sv] = evaluateCurve2d$2(c, bounds.first + seg * segmentSpan);
9233
- const [eu, ev] = evaluateCurve2d$2(c, bounds.first + (seg + 1) * segmentSpan);
9234
- edgeIds.push(bk.makeCircleArc3d(...lift(su, sv), ...lift(eu, ev), ...center3d, ...axis));
9235
- }
9236
- if (edgeIds.length === 1) return edgeHandle(wasmIndex(edgeIds, 0));
9237
- return wireHandle(bk.makeWire(edgeIds, false));
9923
+ const [su, sv] = evaluateCurve2d$2(c, bounds.first);
9924
+ const [eu, ev] = evaluateCurve2d$2(c, bounds.last);
9925
+ return edgeHandle(bk.makeCircleArc3d(...lift(su, sv), ...lift(eu, ev), ...center3d, ...axis));
9238
9926
  }
9239
9927
  if (basis.__bk2d === "ellipse") {
9240
9928
  const ell = basis;
@@ -9349,9 +10037,7 @@ function refineUVSamples(uvSamples, evaluateUV, maxSamples, threshold) {
9349
10037
  function extractCurve2dFromEdge(bk, edge, face) {
9350
10038
  const eid = unwrap$1(edge, "edge");
9351
10039
  const fid = unwrap$1(face, "face");
9352
- const params = bk.getEdgeCurveParameters(eid);
9353
- const tMin = params[0] ?? 0;
9354
- const tMax = params[1] ?? 1;
10040
+ const [tMin, tMax] = edgeParamRange(bk, eid);
9355
10041
  const BASE_N = 20;
9356
10042
  const MAX_N = 80;
9357
10043
  const REFINE_THRESHOLD = .05;
@@ -9388,9 +10074,7 @@ function fillSurface(bk, wires, _options) {
9388
10074
  const curveLengths = [];
9389
10075
  for (const edge of wireEdges) {
9390
10076
  const edgeId = unwrap$1(edge, "edge");
9391
- const params = bk.getEdgeCurveParameters(edgeId);
9392
- const tMin = wasmIndex(params, 0);
9393
- const tMax = wasmIndex(params, 1);
10077
+ const [tMin, tMax] = edgeParamRange(bk, edgeId);
9394
10078
  const N = 10;
9395
10079
  const pts = [];
9396
10080
  for (let i = 0; i <= N; i++) {
@@ -9565,23 +10249,9 @@ function linearCenterOfMass(bk, shape) {
9565
10249
  return centerOfMass$1(bk, shape);
9566
10250
  }
9567
10251
  function boundingBox$1(bk, shape) {
9568
- const h = shape;
9569
- if (h.type === "solid") {
9570
- const bb = bk.boundingBox(unwrap$1(shape));
9571
- return {
9572
- min: vec3At(bb, 0),
9573
- max: vec3At(bb, 3)
9574
- };
9575
- }
9576
- if (h.type === "vertex") {
9577
- const pos = vertexPosition(bk, shape);
9578
- return {
9579
- min: [...pos],
9580
- max: [...pos]
9581
- };
9582
- }
9583
- const vertices = iterShapes$1(bk, shape, "vertex");
9584
- if (vertices.length === 0) return {
10252
+ const points = boxPoints(bk, shape);
10253
+ const [first] = points;
10254
+ if (!first) return {
9585
10255
  min: [
9586
10256
  0,
9587
10257
  0,
@@ -9593,76 +10263,105 @@ function boundingBox$1(bk, shape) {
9593
10263
  0
9594
10264
  ]
9595
10265
  };
9596
- const first = vertexPosition(bk, vertices[0]);
9597
- let minX = first[0], minY = first[1], minZ = first[2];
9598
- let maxX = first[0], maxY = first[1], maxZ = first[2];
9599
- for (let i = 1; i < vertices.length; i++) {
9600
- const p = vertexPosition(bk, vertices[i]);
9601
- if (p[0] < minX) minX = p[0];
9602
- if (p[0] > maxX) maxX = p[0];
9603
- if (p[1] < minY) minY = p[1];
9604
- if (p[1] > maxY) maxY = p[1];
9605
- if (p[2] < minZ) minZ = p[2];
9606
- if (p[2] > maxZ) maxZ = p[2];
10266
+ const min = [...first];
10267
+ const max = [...first];
10268
+ for (const p of points) for (let k = 0; k < 3; k++) {
10269
+ min[k] = Math.min(wasmIndex(min, k), wasmIndex(p, k));
10270
+ max[k] = Math.max(wasmIndex(max, k), wasmIndex(p, k));
9607
10271
  }
9608
10272
  return {
9609
- min: [
9610
- minX,
9611
- minY,
9612
- minZ
9613
- ],
9614
- max: [
9615
- maxX,
9616
- maxY,
9617
- maxZ
9618
- ]
10273
+ min,
10274
+ max
9619
10275
  };
9620
10276
  }
10277
+ /**
10278
+ * Points whose bounding box is the shape's: a solid's own box corners, and for
10279
+ * anything else the box points of its edges (see `edgeBoxPoints`), so an arc's
10280
+ * bulge counts and not just its end vertices.
10281
+ */
10282
+ function boxPoints(bk, h) {
10283
+ switch (h.type) {
10284
+ case "solid": {
10285
+ const bb = bk.boundingBox(h.id);
10286
+ return [vec3At(bb, 0), vec3At(bb, 3)];
10287
+ }
10288
+ case "vertex": return [vec3At(bk.getVertexPosition(h.id))];
10289
+ case "edge": return edgeBoxPoints(bk, h.id);
10290
+ case "compound": return (syntheticCompounds.get(h.id) ?? compoundSolidIds(bk, h.id).map(solidHandle)).flatMap((child) => boxPoints(bk, child));
10291
+ default: {
10292
+ const edges = iterShapes$1(bk, h, "edge");
10293
+ if (edges.length > 0) return edges.flatMap((e) => edgeBoxPoints(bk, unwrap$1(e)));
10294
+ return iterShapes$1(bk, h, "vertex").map((v) => vertexPosition(bk, v));
10295
+ }
10296
+ }
10297
+ }
10298
+ /**
10299
+ * Minimum distance between two shapes. brepkit-wasm measures from a point to a
10300
+ * solid, face or edge, and between two solids; a wire, shell or compound is
10301
+ * measured through its edges, faces or children, taking the closest pair. Any
10302
+ * other pairing (face to face, edge to edge, face to solid, ...) has no exact
10303
+ * brepkit measure and throws {@link UnsupportedKernelOperationError}.
10304
+ */
9621
10305
  function distance$1(bk, shape1, shape2) {
9622
- const h1 = shape1;
9623
- const h2 = shape2;
9624
- if (h1.type === "solid" && h2.type === "solid") {
9625
- const buf = bk.solidToSolidDistance(h1.id, h2.id);
10306
+ const parts1 = distanceParts(bk, shape1);
10307
+ const parts2 = distanceParts(bk, shape2);
10308
+ let best;
10309
+ for (const a of parts1) for (const b of parts2) {
10310
+ const d = partDistance(bk, a, b);
10311
+ if (best === void 0 || d.value < best.value) best = d;
10312
+ }
10313
+ if (best === void 0) throw new Error("brepkit: distance needs two non-empty shapes");
10314
+ return best;
10315
+ }
10316
+ /** The solids, faces, edges and vertices a shape is measured through. */
10317
+ function distanceParts(bk, h) {
10318
+ switch (h.type) {
10319
+ case "wire": return toArray(bk.getWireEdges(h.id)).map(edgeHandle);
10320
+ case "shell": return toArray(bk.getShellFaces(h.id)).map(faceHandle);
10321
+ case "compound": return (syntheticCompounds.get(h.id) ?? compoundSolidIds(bk, h.id).map(solidHandle)).flatMap((child) => distanceParts(bk, child));
10322
+ default: return [h];
10323
+ }
10324
+ }
10325
+ function partDistance(bk, a, b) {
10326
+ if (a.type === "solid" && b.type === "solid") {
10327
+ const buf = bk.solidToSolidDistance(a.id, b.id);
9626
10328
  return {
9627
10329
  value: wasmIndex(buf, 0),
9628
10330
  point1: vec3At(buf, 1),
9629
10331
  point2: vec3At(buf, 4)
9630
10332
  };
9631
10333
  }
9632
- if (h1.type === "vertex" && (h2.type === "solid" || h2.type === "face" || h2.type === "edge")) {
9633
- const point1 = vec3At(bk.getVertexPosition(h1.id));
9634
- const result = h2.type === "solid" ? bk.pointToSolidDistance(point1[0], point1[1], point1[2], h2.id) : h2.type === "face" ? bk.pointToFaceDistance(point1[0], point1[1], point1[2], h2.id) : bk.pointToEdgeDistance(point1[0], point1[1], point1[2], h2.id);
10334
+ if (a.type === "vertex") return pointDistance(bk, a, b);
10335
+ if (b.type === "vertex") {
10336
+ const d = pointDistance(bk, b, a);
10337
+ return {
10338
+ value: d.value,
10339
+ point1: d.point2,
10340
+ point2: d.point1
10341
+ };
10342
+ }
10343
+ throw new UnsupportedKernelOperationError(`brepkit: no distance between a ${a.type} and a ${b.type}; brepkit-wasm measures from a point to a solid, face or edge, and between two solids`);
10344
+ }
10345
+ function pointDistance(bk, v, to) {
10346
+ const point1 = vec3At(bk.getVertexPosition(v.id));
10347
+ if (to.type === "vertex") {
10348
+ const point2 = vec3At(bk.getVertexPosition(to.id));
9635
10349
  return {
9636
- value: wasmIndex(result, 0),
10350
+ value: Math.hypot(point2[0] - point1[0], point2[1] - point1[1], point2[2] - point1[2]),
9637
10351
  point1,
9638
- point2: vec3At(result, 1)
10352
+ point2
9639
10353
  };
9640
10354
  }
9641
- const getPos = (s) => {
9642
- if (s.type === "vertex") return vec3At(bk.getVertexPosition(s.id));
9643
- if (s.type === "solid") {
9644
- const bb = bk.boundingBox(s.id);
9645
- return [
9646
- (wasmIndex(bb, 0) + wasmIndex(bb, 3)) / 2,
9647
- (wasmIndex(bb, 1) + wasmIndex(bb, 4)) / 2,
9648
- (wasmIndex(bb, 2) + wasmIndex(bb, 5)) / 2
9649
- ];
9650
- }
9651
- return [
9652
- 0,
9653
- 0,
9654
- 0
9655
- ];
9656
- };
9657
- const p1 = getPos(h1);
9658
- const p2 = getPos(h2);
9659
- const dx = p2[0] - p1[0];
9660
- const dy = p2[1] - p1[1];
9661
- const dz = p2[2] - p1[2];
10355
+ const [x, y, z] = point1;
10356
+ let result;
10357
+ if (to.type === "solid") result = bk.pointToSolidDistance(x, y, z, to.id);
10358
+ else if (to.type === "face") result = bk.pointToFaceDistance(x, y, z, to.id);
10359
+ else if (to.type === "edge") result = bk.pointToEdgeDistance(x, y, z, to.id);
10360
+ else throw new UnsupportedKernelOperationError(`brepkit: no distance from a vertex to a ${to.type}`);
9662
10361
  return {
9663
- value: Math.sqrt(dx * dx + dy * dy + dz * dz),
9664
- point1: p1,
9665
- point2: p2
10362
+ value: wasmIndex(result, 0),
10363
+ point1,
10364
+ point2: vec3At(result, 1)
9666
10365
  };
9667
10366
  }
9668
10367
  function surfaceCurvature$1(bk, face, u, v) {
@@ -9937,6 +10636,8 @@ function trySmoothPipeShell(bk, faceId, spine) {
9937
10636
  }
9938
10637
  }
9939
10638
  function sweepPipeShell(bk, profile, spine, options) {
10639
+ if (options?.["auxiliary"] !== void 0) throw new UnsupportedKernelOperationError("brepkit: sweepPipeShell has no auxiliary spine (twist or guided sweep)");
10640
+ if (options?.["law"] !== void 0) throw new UnsupportedKernelOperationError("brepkit: sweepPipeShell has no scaling law");
9940
10641
  const profileHandle = profile;
9941
10642
  const faceId = profileHandle.type === "wire" ? bk.makeFaceFromWire(profileHandle.id) : unwrap$1(profile, "face");
9942
10643
  const shellMode = !!(options && options["shellMode"]);