forgecad 0.9.5 → 0.9.6

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 (39) hide show
  1. package/dist/assets/{AdminPage-uTtcSXtn.js → AdminPage-Da6hhpJx.js} +1 -1
  2. package/dist/assets/{BlogPage-DYJMjWx3.js → BlogPage-Bl_sKeWb.js} +1 -1
  3. package/dist/assets/{DocsPage-C58f0K5v.js → DocsPage-Blz3Tp4j.js} +1 -1
  4. package/dist/assets/{EditorApp-DNH1TEz1.js → EditorApp-CuiPbtn5.js} +32 -7
  5. package/dist/assets/{EmbedViewer-CMXWA2LX.js → EmbedViewer-BFG6-Ufm.js} +2 -2
  6. package/dist/assets/{LandingPageProofDriven-CAu2OZFn.js → LandingPageProofDriven-DB9fQd5P.js} +1 -1
  7. package/dist/assets/{PricingPage-BIgW7m3X.js → PricingPage-BMxYT_F0.js} +1 -1
  8. package/dist/assets/{SettingsPage-N1l1tMXO.js → SettingsPage-VVQNrCAg.js} +1 -1
  9. package/dist/assets/{app-CFy7g5WP.js → app-Dl9ymBWC.js} +293 -36
  10. package/dist/assets/cli/{render-BrVVdj_T.js → render-CFtwKCCY.js} +10 -1081
  11. package/dist/assets/{sectionPlaneMath-CykEnkvQ.js → distance-BEC2RjJi.js} +1897 -288
  12. package/dist/assets/{evalWorker-c_SB9gg3.js → evalWorker-CRvbzTXm.js} +555 -83
  13. package/dist/assets/{manifold-Cjk7WhRs.js → manifold-B9QSr-qP.js} +1 -1
  14. package/dist/assets/{manifold-Dp6pvFr6.js → manifold-DpBXFS2K.js} +1 -1
  15. package/dist/assets/{manifold-CRoBhJKH.js → manifold-DzZ4VRPs.js} +2 -2
  16. package/dist/assets/{renderSceneState-3DfsSASX.js → renderSceneState-BuAXF2jh.js} +1 -1
  17. package/dist/assets/{reportWorker-BLkuIoS8.js → reportWorker-BNWEnRg1.js} +555 -83
  18. package/dist/cli/render.html +1 -1
  19. package/dist/docs/index.html +1 -1
  20. package/dist/docs-raw/beta-operations.md +4 -0
  21. package/dist/docs-raw/deployment.md +38 -23
  22. package/dist/docs-raw/generated/concepts.md +82 -5
  23. package/dist/docs-raw/generated/curves.md +97 -5
  24. package/dist/docs-raw/generated/sketch.md +9 -1
  25. package/dist/docs-raw/guides/inspection-bundles.md +9 -3
  26. package/dist/docs-raw/runbook.md +3 -3
  27. package/dist/index.html +1 -1
  28. package/dist/sitemap.xml +6 -6
  29. package/dist-cli/forgecad.js +828 -297
  30. package/dist-cli/forgecad.js.map +1 -1
  31. package/dist-skill/CONTEXT.md +115 -9
  32. package/dist-skill/docs/generated/curves.md +97 -5
  33. package/dist-skill/docs/generated/sketch.md +9 -1
  34. package/dist-skill/docs/guides/inspection-bundles.md +9 -3
  35. package/dist-skill/docs-dev/generated/curves.md +97 -5
  36. package/dist-skill/docs-dev/generated/sketch.md +9 -1
  37. package/dist-skill/docs-dev/guides/inspection-bundles.md +9 -3
  38. package/examples/api/guided-loft-olive-oil-bottle.forge.js +135 -0
  39. package/package.json +20 -2
@@ -799,18 +799,18 @@ function cloneSdfNode(node) {
799
799
  }
800
800
  }
801
801
  const SHEET_METAL_EDGES = ["top", "right", "bottom", "left"];
802
- const EPS$c = 1e-9;
802
+ const EPS$d = 1e-9;
803
803
  function isFinitePositive$3(value) {
804
804
  return Number.isFinite(value) && value > 0;
805
805
  }
806
806
  function isFiniteNonNegative(value) {
807
807
  return Number.isFinite(value) && value >= 0;
808
808
  }
809
- function cloneVec3$3(vec2) {
809
+ function cloneVec3$5(vec2) {
810
810
  return [vec2[0], vec2[1], vec2[2]];
811
811
  }
812
812
  function cloneFaceAxis(vec2) {
813
- return vec2 ? cloneVec3$3(vec2) : void 0;
813
+ return vec2 ? cloneVec3$5(vec2) : void 0;
814
814
  }
815
815
  function cloneSheetMetalModel(model) {
816
816
  if (!model) return null;
@@ -840,7 +840,7 @@ function edgeDisplayName(edge) {
840
840
  return `sheetMetal().flange("${edge}", ...)`;
841
841
  }
842
842
  function normalizeAngle(angleDeg) {
843
- return Math.abs(angleDeg) <= EPS$c ? 0 : angleDeg;
843
+ return Math.abs(angleDeg) <= EPS$d ? 0 : angleDeg;
844
844
  }
845
845
  function validateSheetMetalModel(model) {
846
846
  if (!isFinitePositive$3(model.panel.width) || !isFinitePositive$3(model.panel.height)) {
@@ -852,7 +852,7 @@ function validateSheetMetalModel(model) {
852
852
  if (!isFiniteNonNegative(model.bendRadius)) {
853
853
  return "sheetMetal() requires a finite non-negative bendRadius.";
854
854
  }
855
- if (model.bendRadius <= EPS$c) {
855
+ if (model.bendRadius <= EPS$d) {
856
856
  return "sheetMetal() v1 requires a positive bendRadius so the bend region stays explicit instead of collapsing into a sharp fold.";
857
857
  }
858
858
  if (model.bendAllowance.kind !== "k-factor") {
@@ -914,7 +914,7 @@ function deriveSheetMetalModel(model) {
914
914
  const trimEnd = flanges.has(adjacent.end) ? model.cornerRelief.size : 0;
915
915
  const fullLength = edge === "top" || edge === "bottom" ? model.panel.width : model.panel.height;
916
916
  const span = fullLength - trimStart - trimEnd;
917
- if (!(span > EPS$c)) {
917
+ if (!(span > EPS$d)) {
918
918
  throw new Error(
919
919
  `${edgeDisplayName(edge)} loses all usable span after applying the defended rectangular corner relief size ${model.cornerRelief.size}.`
920
920
  );
@@ -972,7 +972,7 @@ function transformPlacement(origin, u2, v, normal) {
972
972
  };
973
973
  }
974
974
  function translatePlan(plan, x2, y2, z2) {
975
- if (Math.abs(x2) <= EPS$c && Math.abs(y2) <= EPS$c && Math.abs(z2) <= EPS$c) return cloneShapeCompilePlan(plan);
975
+ if (Math.abs(x2) <= EPS$d && Math.abs(y2) <= EPS$d && Math.abs(z2) <= EPS$d) return cloneShapeCompilePlan(plan);
976
976
  return appendShapeCompileTransform(cloneShapeCompilePlan(plan), {
977
977
  kind: "translate",
978
978
  x: x2,
@@ -1120,8 +1120,8 @@ function lowerSheetMetalBasePlan(model, output) {
1120
1120
  function descriptor(name, center, normal, planar, uAxis, vAxis, semantic = "face", memberNames = [name], coplanar = planar) {
1121
1121
  return {
1122
1122
  name,
1123
- center: cloneVec3$3(center),
1124
- normal: cloneVec3$3(normal),
1123
+ center: cloneVec3$5(center),
1124
+ normal: cloneVec3$5(normal),
1125
1125
  planar,
1126
1126
  uAxis: cloneFaceAxis(uAxis),
1127
1127
  vAxis: cloneFaceAxis(vAxis),
@@ -1374,10 +1374,10 @@ function describeSheetMetalPlanarRegionFrames(model, output) {
1374
1374
  const size = sheetMetalPlanarRegionSize(derived, face.name);
1375
1375
  return {
1376
1376
  name: face.name,
1377
- center: cloneVec3$3(face.center),
1378
- normal: cloneVec3$3(face.normal),
1379
- uAxis: cloneVec3$3(face.uAxis),
1380
- vAxis: cloneVec3$3(face.vAxis),
1377
+ center: cloneVec3$5(face.center),
1378
+ normal: cloneVec3$5(face.normal),
1379
+ uAxis: cloneVec3$5(face.uAxis),
1380
+ vAxis: cloneVec3$5(face.vAxis),
1381
1381
  width: size.width,
1382
1382
  height: size.height,
1383
1383
  thickness: derived.thickness
@@ -1416,7 +1416,7 @@ function cloneShapeWorkplanePlacement(placement) {
1416
1416
  placement: cloneSketchPlacementModel(placement.placement)
1417
1417
  };
1418
1418
  }
1419
- const EPS$b = 1e-10;
1419
+ const EPS$c = 1e-10;
1420
1420
  function subVec3(a2, b) {
1421
1421
  return [a2[0] - b[0], a2[1] - b[1], a2[2] - b[2]];
1422
1422
  }
@@ -1442,7 +1442,7 @@ function projectRadial(v, axis) {
1442
1442
  function signedAngleAroundAxis(from, to, axis) {
1443
1443
  const fromLen = lengthVec3$1(from);
1444
1444
  const toLen = lengthVec3$1(to);
1445
- if (fromLen < EPS$b || toLen < EPS$b) return 0;
1445
+ if (fromLen < EPS$c || toLen < EPS$c) return 0;
1446
1446
  const fn = scaleVec3(from, 1 / fromLen);
1447
1447
  const tn = scaleVec3(to, 1 / toLen);
1448
1448
  const sin2 = dotVec3$4(axis, crossVec3$2(fn, tn));
@@ -1463,19 +1463,19 @@ function solveRotateAroundAngle(axis, pivot, movingPoint, targetPoint, options =
1463
1463
  const targetDecomp = projectRadial(target, unitAxis);
1464
1464
  const movingRadialLen = lengthVec3$1(movingDecomp.radial);
1465
1465
  const targetRadialLen = lengthVec3$1(targetDecomp.radial);
1466
- if (movingRadialLen < EPS$b) {
1467
- if (mode === "line" && targetRadialLen >= EPS$b) {
1466
+ if (movingRadialLen < EPS$c) {
1467
+ if (mode === "line" && targetRadialLen >= EPS$c) {
1468
1468
  throw new Error("rotateAroundTo(...): moving point lies on the rotation axis, so line alignment is impossible");
1469
1469
  }
1470
1470
  return 0;
1471
1471
  }
1472
1472
  if (mode === "plane") {
1473
- if (targetRadialLen < EPS$b) {
1473
+ if (targetRadialLen < EPS$c) {
1474
1474
  throw new Error("rotateAroundTo(...): target point lies on the rotation axis, so the target plane is undefined");
1475
1475
  }
1476
1476
  return signedAngleAroundAxis(movingDecomp.radial, targetDecomp.radial, unitAxis);
1477
1477
  }
1478
- if (targetRadialLen < EPS$b) {
1478
+ if (targetRadialLen < EPS$c) {
1479
1479
  throw new Error("rotateAroundTo(...): target line lies on the rotation axis, but the moving point does not");
1480
1480
  }
1481
1481
  const axialTol = 1e-8 * Math.max(1, Math.abs(movingDecomp.axial), Math.abs(targetDecomp.axial));
@@ -1515,10 +1515,10 @@ function multiplyMat4(a2, b) {
1515
1515
  }
1516
1516
  function normalizeVec3$5(v) {
1517
1517
  const len2 = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
1518
- if (len2 < EPS$b) throw new Error("Axis must be non-zero");
1518
+ if (len2 < EPS$c) throw new Error("Axis must be non-zero");
1519
1519
  return [v[0] / len2, v[1] / len2, v[2] / len2];
1520
1520
  }
1521
- function transformPoint$1(m2, p2, w2) {
1521
+ function transformPoint$2(m2, p2, w2) {
1522
1522
  const x2 = p2[0], y2 = p2[1], z2 = p2[2];
1523
1523
  return [
1524
1524
  m2[0] * x2 + m2[4] * y2 + m2[8] * z2 + m2[12] * w2,
@@ -1545,7 +1545,7 @@ function invertMat4(m2) {
1545
1545
  const b10 = a21 * a33 - a23 * a31;
1546
1546
  const b11 = a22 * a33 - a23 * a32;
1547
1547
  const det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
1548
- if (Math.abs(det) < EPS$b) throw new Error("Transform matrix is not invertible");
1548
+ if (Math.abs(det) < EPS$c) throw new Error("Transform matrix is not invertible");
1549
1549
  const invDet = 1 / det;
1550
1550
  out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * invDet;
1551
1551
  out[1] = (a02 * b10 - a01 * b11 - a03 * b09) * invDet;
@@ -1638,11 +1638,11 @@ class Transform {
1638
1638
  }
1639
1639
  /** Transform a point using homogeneous coordinates. */
1640
1640
  point(p2) {
1641
- return transformPoint$1(this.m, p2, 1);
1641
+ return transformPoint$2(this.m, p2, 1);
1642
1642
  }
1643
1643
  /** Transform a direction vector without translation. */
1644
1644
  vector(v) {
1645
- return transformPoint$1(this.m, v, 0);
1645
+ return transformPoint$2(this.m, v, 0);
1646
1646
  }
1647
1647
  /** Return the transform as a raw 4x4 matrix array. */
1648
1648
  toArray() {
@@ -3302,14 +3302,14 @@ function sweepPathToPolylineAdaptive(path2, baseSamples = 48) {
3302
3302
  pts.push(evalPathAt(path2, 1));
3303
3303
  return pts;
3304
3304
  }
3305
- const EPS$a = 1e-8;
3305
+ const EPS$b = 1e-8;
3306
3306
  const SUPPORTED_VERTICAL_EDGE_NAMES = ["vert-bl", "vert-br", "vert-tr", "vert-tl"];
3307
3307
  function midpoint$4(start, end) {
3308
3308
  return [(start[0] + end[0]) * 0.5, (start[1] + end[1]) * 0.5, (start[2] + end[2]) * 0.5];
3309
3309
  }
3310
3310
  function normalize$8(v) {
3311
3311
  const len2 = Math.hypot(v[0], v[1], v[2]);
3312
- if (len2 <= EPS$a) throw new Error("Edge feature selection requires a non-zero direction vector");
3312
+ if (len2 <= EPS$b) throw new Error("Edge feature selection requires a non-zero direction vector");
3313
3313
  return [v[0] / len2, v[1] / len2, v[2] / len2];
3314
3314
  }
3315
3315
  function subtract(a2, b) {
@@ -3391,7 +3391,7 @@ function rigidTransformForEdgeStep(step) {
3391
3391
  case "mirror": {
3392
3392
  const [nx0, ny0, nz0] = [step.normalX, step.normalY, step.normalZ];
3393
3393
  const len2 = Math.hypot(nx0, ny0, nz0);
3394
- if (len2 <= EPS$a) return Transform.identity();
3394
+ if (len2 <= EPS$b) return Transform.identity();
3395
3395
  const nx = nx0 / len2;
3396
3396
  const ny = ny0 / len2;
3397
3397
  const nz = nz0 / len2;
@@ -3702,7 +3702,7 @@ function isRectangleProfile(points) {
3702
3702
  return [next[0] - point2[0], next[1] - point2[1]];
3703
3703
  });
3704
3704
  const lengths2 = vectors.map(([x2, y2]) => Math.hypot(x2, y2));
3705
- if (lengths2.some((length4) => length4 <= EPS$a)) return false;
3705
+ if (lengths2.some((length4) => length4 <= EPS$b)) return false;
3706
3706
  const dot01 = vectors[0][0] * vectors[1][0] + vectors[0][1] * vectors[1][1];
3707
3707
  const dot12 = vectors[1][0] * vectors[2][0] + vectors[1][1] * vectors[2][1];
3708
3708
  const dot23 = vectors[2][0] * vectors[3][0] + vectors[2][1] * vectors[3][1];
@@ -5769,13 +5769,13 @@ function parseMeshFile(data, format) {
5769
5769
  return parse3mf(data);
5770
5770
  }
5771
5771
  }
5772
- const EPS$9 = 1e-8;
5772
+ const EPS$a = 1e-8;
5773
5773
  function length$3(v) {
5774
5774
  return Math.hypot(v[0], v[1], v[2]);
5775
5775
  }
5776
5776
  function normalize$7(v) {
5777
5777
  const len2 = length$3(v);
5778
- if (len2 < EPS$9) throw new Error("Plane normal must be non-zero");
5778
+ if (len2 < EPS$a) throw new Error("Plane normal must be non-zero");
5779
5779
  return [v[0] / len2, v[1] / len2, v[2] / len2];
5780
5780
  }
5781
5781
  function resolvePlaneOriginNormal(plane) {
@@ -5797,12 +5797,12 @@ function resolvePlaneOriginNormal(plane) {
5797
5797
  function rotationToPlaneSpace(normal) {
5798
5798
  const n = normalize$7(normal);
5799
5799
  const dot2 = n[2];
5800
- if (dot2 > 1 - EPS$9) {
5800
+ if (dot2 > 1 - EPS$a) {
5801
5801
  return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
5802
5802
  }
5803
5803
  let axis;
5804
5804
  let angle;
5805
- if (dot2 < -1 + EPS$9) {
5805
+ if (dot2 < -1 + EPS$a) {
5806
5806
  axis = [1, 0, 0];
5807
5807
  angle = Math.PI;
5808
5808
  } else {
@@ -8364,10 +8364,10 @@ function scale$6(v, s) {
8364
8364
  function sub$7(a2, b) {
8365
8365
  return [a2[0] - b[0], a2[1] - b[1], a2[2] - b[2]];
8366
8366
  }
8367
- function cross$7(a2, b) {
8367
+ function cross$8(a2, b) {
8368
8368
  return [a2[1] * b[2] - a2[2] * b[1], a2[2] * b[0] - a2[0] * b[2], a2[0] * b[1] - a2[1] * b[0]];
8369
8369
  }
8370
- function makeEdge(name, start, end, faceName, curve) {
8370
+ function makeEdge$1(name, start, end, faceName, curve) {
8371
8371
  return {
8372
8372
  name,
8373
8373
  start,
@@ -8400,7 +8400,7 @@ function buildSurfaceSheetTopology(boundaries, options = {}) {
8400
8400
  const center = options.center ?? average$1(corners);
8401
8401
  const uAxis = normalizeAxis$1(sub$7(midpoint$3(u1Start, u1End), midpoint$3(u0Start, u0End)));
8402
8402
  const vAxis = normalizeAxis$1(sub$7(midpoint$3(v1Start, v1End), midpoint$3(v0Start, v0End)));
8403
- const normal = normalizeAxis$1(options.normal ?? cross$7(uAxis, vAxis));
8403
+ const normal = normalizeAxis$1(options.normal ?? cross$8(uAxis, vAxis));
8404
8404
  const faces = /* @__PURE__ */ new Map();
8405
8405
  faces.set(faceName, {
8406
8406
  name: faceName,
@@ -8411,10 +8411,10 @@ function buildSurfaceSheetTopology(boundaries, options = {}) {
8411
8411
  surface: options.surface
8412
8412
  });
8413
8413
  const edges = /* @__PURE__ */ new Map();
8414
- edges.set("u0", makeEdge("u0", u0Start, u0End, faceName, (_a3 = options.edgeCurves) == null ? void 0 : _a3.u0));
8415
- edges.set("u1", makeEdge("u1", u1Start, u1End, faceName, (_b3 = options.edgeCurves) == null ? void 0 : _b3.u1));
8416
- edges.set("v0", makeEdge("v0", v0Start, v0End, faceName, (_c2 = options.edgeCurves) == null ? void 0 : _c2.v0));
8417
- edges.set("v1", makeEdge("v1", v1Start, v1End, faceName, (_d2 = options.edgeCurves) == null ? void 0 : _d2.v1));
8414
+ edges.set("u0", makeEdge$1("u0", u0Start, u0End, faceName, (_a3 = options.edgeCurves) == null ? void 0 : _a3.u0));
8415
+ edges.set("u1", makeEdge$1("u1", u1Start, u1End, faceName, (_b3 = options.edgeCurves) == null ? void 0 : _b3.u1));
8416
+ edges.set("v0", makeEdge$1("v0", v0Start, v0End, faceName, (_c2 = options.edgeCurves) == null ? void 0 : _c2.v0));
8417
+ edges.set("v1", makeEdge$1("v1", v1Start, v1End, faceName, (_d2 = options.edgeCurves) == null ? void 0 : _d2.v1));
8418
8418
  return { faces, edges };
8419
8419
  }
8420
8420
  function attachSurfaceSheetTopology(shape, boundaries, options = {}) {
@@ -8432,7 +8432,7 @@ function attachSurfaceSheetTopology(shape, boundaries, options = {}) {
8432
8432
  });
8433
8433
  return shape;
8434
8434
  }
8435
- function cloneVec3$2(value) {
8435
+ function cloneVec3$4(value) {
8436
8436
  return [value[0], value[1], value[2]];
8437
8437
  }
8438
8438
  function cloneNurbsFaceTrimLoop(loop) {
@@ -8454,38 +8454,38 @@ function cloneFaceSurface(surface) {
8454
8454
  if (!surface) return void 0;
8455
8455
  switch (surface.kind) {
8456
8456
  case "plane":
8457
- return { kind: "plane", normal: cloneVec3$2(surface.normal) };
8457
+ return { kind: "plane", normal: cloneVec3$4(surface.normal) };
8458
8458
  case "cylinder":
8459
8459
  return {
8460
8460
  kind: "cylinder",
8461
- origin: cloneVec3$2(surface.origin),
8462
- axis: cloneVec3$2(surface.axis),
8461
+ origin: cloneVec3$4(surface.origin),
8462
+ axis: cloneVec3$4(surface.axis),
8463
8463
  radius: surface.radius,
8464
8464
  height: surface.height
8465
8465
  };
8466
8466
  case "cone":
8467
8467
  return {
8468
8468
  kind: "cone",
8469
- origin: cloneVec3$2(surface.origin),
8470
- axis: cloneVec3$2(surface.axis),
8469
+ origin: cloneVec3$4(surface.origin),
8470
+ axis: cloneVec3$4(surface.axis),
8471
8471
  radiusBottom: surface.radiusBottom,
8472
8472
  radiusTop: surface.radiusTop,
8473
8473
  height: surface.height
8474
8474
  };
8475
8475
  case "sphere":
8476
- return { kind: "sphere", center: cloneVec3$2(surface.center), radius: surface.radius };
8476
+ return { kind: "sphere", center: cloneVec3$4(surface.center), radius: surface.radius };
8477
8477
  case "torus":
8478
8478
  return {
8479
8479
  kind: "torus",
8480
- center: cloneVec3$2(surface.center),
8481
- axis: cloneVec3$2(surface.axis),
8480
+ center: cloneVec3$4(surface.center),
8481
+ axis: cloneVec3$4(surface.axis),
8482
8482
  majorRadius: surface.majorRadius,
8483
8483
  minorRadius: surface.minorRadius
8484
8484
  };
8485
8485
  case "ruled":
8486
8486
  return {
8487
8487
  kind: "ruled",
8488
- rails: surface.rails.map((rail2) => rail2.map(cloneVec3$2))
8488
+ rails: surface.rails.map((rail2) => rail2.map(cloneVec3$4))
8489
8489
  };
8490
8490
  case "nurbs":
8491
8491
  return {
@@ -8498,9 +8498,9 @@ function cloneEdgeCurve(curve) {
8498
8498
  if (!curve) return void 0;
8499
8499
  switch (curve.kind) {
8500
8500
  case "line":
8501
- return { ...curve, start: cloneVec3$2(curve.start), end: cloneVec3$2(curve.end) };
8501
+ return { ...curve, start: cloneVec3$4(curve.start), end: cloneVec3$4(curve.end) };
8502
8502
  case "circle":
8503
- return { ...curve, center: cloneVec3$2(curve.center), axis: cloneVec3$2(curve.axis) };
8503
+ return { ...curve, center: cloneVec3$4(curve.center), axis: cloneVec3$4(curve.axis) };
8504
8504
  case "surfaceIso":
8505
8505
  return { ...curve, parameterRange: [curve.parameterRange[0], curve.parameterRange[1]] };
8506
8506
  case "nurbsUv":
@@ -8518,7 +8518,7 @@ function cloneEdgeCurve(curve) {
8518
8518
  }
8519
8519
  }
8520
8520
  function makeLineEdgeCurve(start, end, faceName) {
8521
- return faceName ? { kind: "line", start: cloneVec3$2(start), end: cloneVec3$2(end), faceName } : { kind: "line", start: cloneVec3$2(start), end: cloneVec3$2(end) };
8521
+ return faceName ? { kind: "line", start: cloneVec3$4(start), end: cloneVec3$4(end), faceName } : { kind: "line", start: cloneVec3$4(start), end: cloneVec3$4(end) };
8522
8522
  }
8523
8523
  function edgeCurveFaceName(curve) {
8524
8524
  switch (curve.kind) {
@@ -10302,6 +10302,7 @@ function buildSweepLevelSetInput(profilePolygons, pathInput, options) {
10302
10302
  edgeLength: options.edgeLength
10303
10303
  };
10304
10304
  }
10305
+ const EPS$9 = 1e-9;
10305
10306
  function resamplePolygon(poly, targetCount) {
10306
10307
  if (poly.length < 2) return poly;
10307
10308
  if (targetCount <= 0) return [];
@@ -10339,6 +10340,78 @@ function resamplePolygon(poly, targetCount) {
10339
10340
  }
10340
10341
  return out;
10341
10342
  }
10343
+ function resamplePolygonByAngle(poly, targetCount, center = polygonCentroid$2(poly)) {
10344
+ if (poly.length < 3 || targetCount <= 0) return null;
10345
+ if (!isConvexPolygon(poly)) return null;
10346
+ const out = [];
10347
+ for (let index2 = 0; index2 < targetCount; index2 += 1) {
10348
+ const angle = index2 / targetCount * Math.PI * 2;
10349
+ const point2 = rayPolygonIntersection(center, [Math.cos(angle), Math.sin(angle)], poly);
10350
+ if (!point2) return null;
10351
+ out.push(point2);
10352
+ }
10353
+ return out;
10354
+ }
10355
+ function rayPolygonIntersection(origin, direction2, poly) {
10356
+ let bestT = Infinity;
10357
+ let best = null;
10358
+ for (let index2 = 0; index2 < poly.length; index2 += 1) {
10359
+ const a2 = poly[index2];
10360
+ const b = poly[(index2 + 1) % poly.length];
10361
+ const edge = [b[0] - a2[0], b[1] - a2[1]];
10362
+ const denom = cross$7(direction2, edge);
10363
+ if (Math.abs(denom) < EPS$9) continue;
10364
+ const delta = [a2[0] - origin[0], a2[1] - origin[1]];
10365
+ const rayT = cross$7(delta, edge) / denom;
10366
+ const edgeT = cross$7(delta, direction2) / denom;
10367
+ if (rayT >= -EPS$9 && edgeT >= -EPS$9 && edgeT <= 1 + EPS$9 && rayT < bestT) {
10368
+ bestT = rayT;
10369
+ best = [origin[0] + direction2[0] * rayT, origin[1] + direction2[1] * rayT];
10370
+ }
10371
+ }
10372
+ return best;
10373
+ }
10374
+ function polygonCentroid$2(poly) {
10375
+ let area2 = 0;
10376
+ let cx = 0;
10377
+ let cy = 0;
10378
+ for (let index2 = 0; index2 < poly.length; index2 += 1) {
10379
+ const a2 = poly[index2];
10380
+ const b = poly[(index2 + 1) % poly.length];
10381
+ const crossValue = cross$7(a2, b);
10382
+ area2 += crossValue;
10383
+ cx += (a2[0] + b[0]) * crossValue;
10384
+ cy += (a2[1] + b[1]) * crossValue;
10385
+ }
10386
+ if (Math.abs(area2) < EPS$9) return averagePoint(poly);
10387
+ return [cx / (3 * area2), cy / (3 * area2)];
10388
+ }
10389
+ function averagePoint(poly) {
10390
+ let x2 = 0;
10391
+ let y2 = 0;
10392
+ for (const point2 of poly) {
10393
+ x2 += point2[0];
10394
+ y2 += point2[1];
10395
+ }
10396
+ return [x2 / poly.length, y2 / poly.length];
10397
+ }
10398
+ function isConvexPolygon(poly) {
10399
+ let sign2 = 0;
10400
+ for (let index2 = 0; index2 < poly.length; index2 += 1) {
10401
+ const a2 = poly[index2];
10402
+ const b = poly[(index2 + 1) % poly.length];
10403
+ const c2 = poly[(index2 + 2) % poly.length];
10404
+ const turn = cross$7([b[0] - a2[0], b[1] - a2[1]], [c2[0] - b[0], c2[1] - b[1]]);
10405
+ if (Math.abs(turn) < EPS$9) continue;
10406
+ const currentSign = Math.sign(turn);
10407
+ if (sign2 !== 0 && currentSign !== sign2) return false;
10408
+ sign2 = currentSign;
10409
+ }
10410
+ return sign2 !== 0;
10411
+ }
10412
+ function cross$7(a2, b) {
10413
+ return a2[0] * b[1] - a2[1] * b[0];
10414
+ }
10342
10415
  function loftStitched(profiles2, heights, wasm) {
10343
10416
  if (profiles2.length < 2) return null;
10344
10417
  const classified = profiles2.map((loops) => classifyLoops(loops));
@@ -10467,8 +10540,10 @@ function stitchSingleLoopLoft(loops, heights, wasm) {
10467
10540
  maxPoints = Math.max(maxPoints, loop.length);
10468
10541
  }
10469
10542
  const N = Math.max(maxPoints, 24);
10543
+ const angularSamples = normalizedLoops.map((loop) => resamplePolygonByAngle(loop, N));
10544
+ const useAngularSamples = angularSamples.every((samples) => samples != null);
10470
10545
  const resampled = normalizedLoops.map((loop, i) => {
10471
- const pts2d = resamplePolygon(loop, N);
10546
+ const pts2d = useAngularSamples ? angularSamples[i] : resamplePolygon(loop, N);
10472
10547
  const z2 = heights[i];
10473
10548
  return pts2d.map(([x2, y2]) => [x2, y2, z2]);
10474
10549
  });
@@ -10530,7 +10605,7 @@ async function initManifoldWasm() {
10530
10605
  if (_wasm$1) return _wasm$1;
10531
10606
  performance.mark("manifold:start");
10532
10607
  const Module = (await __vitePreload(async () => {
10533
- const { default: __vite_default__ } = await import("./manifold-CRoBhJKH.js");
10608
+ const { default: __vite_default__ } = await import("./manifold-DzZ4VRPs.js");
10534
10609
  return { default: __vite_default__ };
10535
10610
  }, true ? [] : void 0)).default;
10536
10611
  performance.mark("manifold:imported");
@@ -22414,7 +22489,7 @@ function marchingTetrahedra(sdfFn, bounds, edgeLength2) {
22414
22489
  };
22415
22490
  }
22416
22491
  const EPS$8 = 1e-9;
22417
- function finitePositive(value) {
22492
+ function finitePositive$1(value) {
22418
22493
  return Number.isFinite(value) && value > EPS$8;
22419
22494
  }
22420
22495
  function clampNonNegative(value) {
@@ -22433,7 +22508,7 @@ function distancePreservingMatrixScale(matrix) {
22433
22508
  const sx = length4(col0);
22434
22509
  const sy = length4(col1);
22435
22510
  const sz = length4(col2);
22436
- if (!finitePositive(sx) || !finitePositive(sy) || !finitePositive(sz)) return null;
22511
+ if (!finitePositive$1(sx) || !finitePositive$1(sy) || !finitePositive$1(sz)) return null;
22437
22512
  if (Math.abs(sx - sy) > EPS$8 || Math.abs(sx - sz) > EPS$8) return null;
22438
22513
  if (Math.abs(dot2(col0, col1)) > EPS$8 || Math.abs(dot2(col0, col2)) > EPS$8 || Math.abs(dot2(col1, col2)) > EPS$8) return null;
22439
22514
  return sx;
@@ -22450,7 +22525,7 @@ function transformStepDistanceScale(step) {
22450
22525
  const sx = Math.abs(step.x);
22451
22526
  const sy = Math.abs(step.y);
22452
22527
  const sz = Math.abs(step.z);
22453
- if (!finitePositive(sx) || !finitePositive(sy) || !finitePositive(sz)) return null;
22528
+ if (!finitePositive$1(sx) || !finitePositive$1(sy) || !finitePositive$1(sz)) return null;
22454
22529
  return Math.abs(sx - sy) <= EPS$8 && Math.abs(sx - sz) <= EPS$8 ? sx : null;
22455
22530
  }
22456
22531
  }
@@ -22490,7 +22565,7 @@ function translatedPlan(base, z2) {
22490
22565
  };
22491
22566
  }
22492
22567
  function offsetCylinderDimensions(plan, thickness) {
22493
- if (!finitePositive(plan.height)) return null;
22568
+ if (!finitePositive$1(plan.height)) return null;
22494
22569
  const radiusTop = plan.radiusTop ?? plan.radius;
22495
22570
  const slope = (radiusTop - plan.radius) / plan.height;
22496
22571
  const normalScale = Math.hypot(1, slope);
@@ -22499,7 +22574,7 @@ function offsetCylinderDimensions(plan, thickness) {
22499
22574
  const height = zMax - zMin;
22500
22575
  const radiusBottom = plan.radius + thickness * (normalScale - slope);
22501
22576
  const offsetRadiusTop = radiusTop + thickness * (normalScale + slope);
22502
- if (!finitePositive(height) || radiusBottom < -EPS$8 || offsetRadiusTop < -EPS$8) return null;
22577
+ if (!finitePositive$1(height) || radiusBottom < -EPS$8 || offsetRadiusTop < -EPS$8) return null;
22503
22578
  return {
22504
22579
  zMin,
22505
22580
  height,
@@ -22557,7 +22632,7 @@ function rectangleCandidatePointsFromProfile(plan) {
22557
22632
  case "rect": {
22558
22633
  const width = Math.abs(plan.width);
22559
22634
  const height = Math.abs(plan.height);
22560
- if (!finitePositive(width) || !finitePositive(height)) return null;
22635
+ if (!finitePositive$1(width) || !finitePositive$1(height)) return null;
22561
22636
  const halfWidth = width / 2;
22562
22637
  const halfHeight = height / 2;
22563
22638
  const points = [
@@ -22591,7 +22666,7 @@ function rectangleFootprintFromProfile(plan) {
22591
22666
  const [xMin, xMax] = xs;
22592
22667
  const [zMin, zMax] = zs;
22593
22668
  if (xMin == null || xMax == null || zMin == null || zMax == null) return null;
22594
- if (xMin < -EPS$8 || !finitePositive(xMax) || !finitePositive(zMax - zMin)) return null;
22669
+ if (xMin < -EPS$8 || !finitePositive$1(xMax) || !finitePositive$1(zMax - zMin)) return null;
22595
22670
  const hasCorner = (x2, z2) => points.some(([px2, pz2]) => sameScalar$1(px2, x2) && sameScalar$1(pz2, z2));
22596
22671
  if (!hasCorner(xMin, zMin) || !hasCorner(xMax, zMin) || !hasCorner(xMax, zMax) || !hasCorner(xMin, zMax)) return null;
22597
22672
  return {
@@ -22604,7 +22679,7 @@ function rectangleFootprintFromProfile(plan) {
22604
22679
  function circleFootprintFromProfile(plan) {
22605
22680
  if (plan.kind !== "circle") return null;
22606
22681
  const radius = Math.abs(plan.radius);
22607
- if (!finitePositive(radius)) return null;
22682
+ if (!finitePositive$1(radius)) return null;
22608
22683
  const center = transformProfilePointThrough$1([0, 0], plan.transforms);
22609
22684
  const xPoint = transformProfilePointThrough$1([1, 0], plan.transforms);
22610
22685
  const yPoint = transformProfilePointThrough$1([0, 1], plan.transforms);
@@ -22613,7 +22688,7 @@ function circleFootprintFromProfile(plan) {
22613
22688
  const xScale = Math.hypot(xAxis[0], xAxis[1]);
22614
22689
  const yScale = Math.hypot(yAxis[0], yAxis[1]);
22615
22690
  const dot2 = xAxis[0] * yAxis[0] + xAxis[1] * yAxis[1];
22616
- if (!finitePositive(xScale) || !finitePositive(yScale)) return null;
22691
+ if (!finitePositive$1(xScale) || !finitePositive$1(yScale)) return null;
22617
22692
  if (Math.abs(xScale - yScale) > EPS$8 || Math.abs(dot2) > EPS$8 * xScale * yScale) return null;
22618
22693
  return {
22619
22694
  center,
@@ -22626,7 +22701,7 @@ function fullCircleRevolveTorusPlan(plan, minorRadiusOffset = 0) {
22626
22701
  const circle2 = circleFootprintFromProfile(plan.profile);
22627
22702
  if (!circle2 || circle2.center[0] <= EPS$8) return null;
22628
22703
  const minorRadius = circle2.radius + minorRadiusOffset;
22629
- if (!finitePositive(minorRadius) || minorRadius >= circle2.center[0] - EPS$8) return null;
22704
+ if (!finitePositive$1(minorRadius) || minorRadius >= circle2.center[0] - EPS$8) return null;
22630
22705
  return translatedPlan(
22631
22706
  {
22632
22707
  kind: "torus",
@@ -22673,7 +22748,7 @@ function offsetFullRectRevolvePlan(plan, thickness) {
22673
22748
  const innerRadius = rectangle.innerRadius - thickness;
22674
22749
  const outerRadius = rectangle.outerRadius + thickness;
22675
22750
  const height = rectangle.zMax - rectangle.zMin + 2 * thickness;
22676
- if (innerRadius < -EPS$8 || !finitePositive(outerRadius) || !finitePositive(height) || outerRadius <= innerRadius + EPS$8) return null;
22751
+ if (innerRadius < -EPS$8 || !finitePositive$1(outerRadius) || !finitePositive$1(height) || outerRadius <= innerRadius + EPS$8) return null;
22677
22752
  const zCenter = (rectangle.zMin + rectangle.zMax) / 2;
22678
22753
  if (innerRadius <= EPS$8) {
22679
22754
  return translatedPlan(
@@ -22714,11 +22789,11 @@ function offsetSolidAnalyticPrimitivePlan(base, thickness) {
22714
22789
  };
22715
22790
  }
22716
22791
  case "box": {
22717
- if (!finitePositive(base.z)) return null;
22792
+ if (!finitePositive$1(base.z)) return null;
22718
22793
  const x2 = Math.abs(base.x) + 2 * thickness;
22719
22794
  const y2 = Math.abs(base.y) + 2 * thickness;
22720
22795
  const z2 = base.z + 2 * thickness;
22721
- if (!finitePositive(x2) || !finitePositive(y2) || !finitePositive(z2)) return null;
22796
+ if (!finitePositive$1(x2) || !finitePositive$1(y2) || !finitePositive$1(z2)) return null;
22722
22797
  return translatedPlan(
22723
22798
  {
22724
22799
  kind: "box",
@@ -22745,7 +22820,7 @@ function offsetSolidAnalyticPrimitivePlan(base, thickness) {
22745
22820
  }
22746
22821
  case "sphere": {
22747
22822
  const radius = base.radius + thickness;
22748
- if (!finitePositive(radius)) return null;
22823
+ if (!finitePositive$1(radius)) return null;
22749
22824
  return {
22750
22825
  kind: "sphere",
22751
22826
  radius,
@@ -22754,7 +22829,7 @@ function offsetSolidAnalyticPrimitivePlan(base, thickness) {
22754
22829
  }
22755
22830
  case "torus": {
22756
22831
  const minorRadius = base.minorRadius + thickness;
22757
- if (!finitePositive(minorRadius) || minorRadius >= base.majorRadius - EPS$8) return null;
22832
+ if (!finitePositive$1(minorRadius) || minorRadius >= base.majorRadius - EPS$8) return null;
22758
22833
  return {
22759
22834
  kind: "torus",
22760
22835
  majorRadius: base.majorRadius,
@@ -23494,18 +23569,18 @@ function faceAxes(face) {
23494
23569
  }
23495
23570
  return {};
23496
23571
  }
23497
- function edgeKey(start, end) {
23572
+ function edgeKey$1(start, end) {
23498
23573
  const encode2 = (p2) => p2.map((value) => value.toFixed(9)).join(",");
23499
23574
  const a2 = encode2(start);
23500
23575
  const b = encode2(end);
23501
23576
  return a2 < b ? `${a2}|${b}` : `${b}|${a2}`;
23502
23577
  }
23503
23578
  function faceEdgeIndex(face, start, end) {
23504
- const target = edgeKey(start, end);
23579
+ const target = edgeKey$1(start, end);
23505
23580
  for (let i = 0; i < face.vertices.length; i++) {
23506
23581
  const faceStart = face.vertices[i];
23507
23582
  const faceEnd = face.vertices[(i + 1) % face.vertices.length];
23508
- if (faceStart && faceEnd && edgeKey(faceStart, faceEnd) === target) return i;
23583
+ if (faceStart && faceEnd && edgeKey$1(faceStart, faceEnd) === target) return i;
23509
23584
  }
23510
23585
  return null;
23511
23586
  }
@@ -23706,7 +23781,7 @@ function topologyPayloadToTopology(payload) {
23706
23781
  const start = explicitVertices[explicitEdge.vertices[0]];
23707
23782
  const end = explicitVertices[explicitEdge.vertices[1]];
23708
23783
  if (!isVec3$2(start) || !isVec3$2(end)) continue;
23709
- const key = edgeKey(start, end);
23784
+ const key = edgeKey$1(start, end);
23710
23785
  if (seenEdges.has(key)) continue;
23711
23786
  seenEdges.set(key, edges.size);
23712
23787
  const display = explicitEdgeDisplayName(payload, explicitEdge, explicitEdgeIndex, start, end);
@@ -23726,7 +23801,7 @@ function topologyPayloadToTopology(payload) {
23726
23801
  const start = face.vertices[i];
23727
23802
  const end = face.vertices[(i + 1) % face.vertices.length];
23728
23803
  if (!start || !end) continue;
23729
- const key = edgeKey(start, end);
23804
+ const key = edgeKey$1(start, end);
23730
23805
  if (seenEdges.has(key)) continue;
23731
23806
  seenEdges.set(key, edges.size);
23732
23807
  edges.set(`${face.id}:edge-${i}`, {
@@ -30608,17 +30683,17 @@ function emptyFaceTable() {
30608
30683
  blockedQueries: []
30609
30684
  };
30610
30685
  }
30611
- function cloneVec3$1(vec2) {
30686
+ function cloneVec3$3(vec2) {
30612
30687
  return [vec2[0], vec2[1], vec2[2]];
30613
30688
  }
30614
30689
  function cloneFaceRefValue(face) {
30615
30690
  return {
30616
30691
  ...face,
30617
- normal: cloneVec3$1(face.normal),
30618
- center: cloneVec3$1(face.center),
30692
+ normal: cloneVec3$3(face.normal),
30693
+ center: cloneVec3$3(face.center),
30619
30694
  query: cloneFaceQueryRef(face.query),
30620
- uAxis: face.uAxis ? cloneVec3$1(face.uAxis) : void 0,
30621
- vAxis: face.vAxis ? cloneVec3$1(face.vAxis) : void 0,
30695
+ uAxis: face.uAxis ? cloneVec3$3(face.uAxis) : void 0,
30696
+ vAxis: face.vAxis ? cloneVec3$3(face.vAxis) : void 0,
30622
30697
  surface: cloneFaceSurface(face.surface),
30623
30698
  descendant: face.descendant ? cloneFaceDescendantMetadata(face.descendant) : void 0
30624
30699
  };
@@ -31473,11 +31548,11 @@ function resolveShapeFaceTableInternal(plan, owner) {
31473
31548
  for (const descriptor2 of describeSheetMetalFaces(plan.model, plan.output)) {
31474
31549
  registerFace(table2, {
31475
31550
  name: descriptor2.name,
31476
- normal: cloneVec3$1(descriptor2.normal),
31477
- center: cloneVec3$1(descriptor2.center),
31551
+ normal: cloneVec3$3(descriptor2.normal),
31552
+ center: cloneVec3$3(descriptor2.center),
31478
31553
  planar: descriptor2.planar,
31479
- uAxis: descriptor2.uAxis ? cloneVec3$1(descriptor2.uAxis) : void 0,
31480
- vAxis: descriptor2.vAxis ? cloneVec3$1(descriptor2.vAxis) : void 0,
31554
+ uAxis: descriptor2.uAxis ? cloneVec3$3(descriptor2.uAxis) : void 0,
31555
+ vAxis: descriptor2.vAxis ? cloneVec3$3(descriptor2.vAxis) : void 0,
31481
31556
  query: createTrackedFaceQuery(descriptor2.name, owner),
31482
31557
  descendant: createFaceDescendantMetadata(descriptor2.semantic, descriptor2.memberNames, descriptor2.coplanar)
31483
31558
  });
@@ -31504,7 +31579,7 @@ function resolveShapeFaceTableInternal(plan, owner) {
31504
31579
  baseFace.center[2] - baseFace.normal[2] * plan.thickness
31505
31580
  ],
31506
31581
  normal: [-baseFace.normal[0], -baseFace.normal[1], -baseFace.normal[2]],
31507
- uAxis: baseFace.uAxis ? cloneVec3$1(baseFace.uAxis) : void 0,
31582
+ uAxis: baseFace.uAxis ? cloneVec3$3(baseFace.uAxis) : void 0,
31508
31583
  vAxis: baseFace.vAxis ? [-baseFace.vAxis[0], -baseFace.vAxis[1], -baseFace.vAxis[2]] : void 0,
31509
31584
  query: createdQuery
31510
31585
  });
@@ -31570,15 +31645,15 @@ function resolveShapeFaceTableInternal(plan, owner) {
31570
31645
  if (counterboreFloorQuery && plan.hole.counterbore) {
31571
31646
  registerFace(table2, {
31572
31647
  name: "counterbore-floor",
31573
- normal: cloneVec3$1(workplane.normal),
31648
+ normal: cloneVec3$3(workplane.normal),
31574
31649
  center: [
31575
31650
  origin[0] + inward[0] * plan.hole.counterbore.depth,
31576
31651
  origin[1] + inward[1] * plan.hole.counterbore.depth,
31577
31652
  origin[2] + inward[2] * plan.hole.counterbore.depth
31578
31653
  ],
31579
31654
  planar: true,
31580
- uAxis: cloneVec3$1(workplane.u),
31581
- vAxis: cloneVec3$1(workplane.v),
31655
+ uAxis: cloneVec3$3(workplane.u),
31656
+ vAxis: cloneVec3$3(workplane.v),
31582
31657
  query: counterboreFloorQuery
31583
31658
  });
31584
31659
  }
@@ -31602,11 +31677,11 @@ function resolveShapeFaceTableInternal(plan, owner) {
31602
31677
  if (floorQuery) {
31603
31678
  registerFace(table2, {
31604
31679
  name: "floor",
31605
- normal: cloneVec3$1(workplane.normal),
31680
+ normal: cloneVec3$3(workplane.normal),
31606
31681
  center: [origin[0] + inward[0] * forward.depth, origin[1] + inward[1] * forward.depth, origin[2] + inward[2] * forward.depth],
31607
31682
  planar: true,
31608
- uAxis: cloneVec3$1(workplane.u),
31609
- vAxis: cloneVec3$1(workplane.v),
31683
+ uAxis: cloneVec3$3(workplane.u),
31684
+ vAxis: cloneVec3$3(workplane.v),
31610
31685
  query: floorQuery
31611
31686
  });
31612
31687
  }
@@ -31617,7 +31692,7 @@ function resolveShapeFaceTableInternal(plan, owner) {
31617
31692
  normal: [-workplane.normal[0], -workplane.normal[1], -workplane.normal[2]],
31618
31693
  center: [origin[0] - inward[0] * reverse.depth, origin[1] - inward[1] * reverse.depth, origin[2] - inward[2] * reverse.depth],
31619
31694
  planar: true,
31620
- uAxis: cloneVec3$1(workplane.u),
31695
+ uAxis: cloneVec3$3(workplane.u),
31621
31696
  vAxis: [-workplane.v[0], -workplane.v[1], -workplane.v[2]],
31622
31697
  query: capQuery
31623
31698
  });
@@ -31796,11 +31871,11 @@ function resolveShapeFaceTableInternal(plan, owner) {
31796
31871
  })();
31797
31872
  registerFace(table2, {
31798
31873
  name: "floor",
31799
- normal: cloneVec3$1(placement.workplane.normal),
31874
+ normal: cloneVec3$3(placement.workplane.normal),
31800
31875
  center: floorCenter,
31801
31876
  planar: true,
31802
- uAxis: cloneVec3$1(placement.workplane.u),
31803
- vAxis: cloneVec3$1(placement.workplane.v),
31877
+ uAxis: cloneVec3$3(placement.workplane.u),
31878
+ vAxis: cloneVec3$3(placement.workplane.v),
31804
31879
  query: floorQuery
31805
31880
  });
31806
31881
  }
@@ -31815,7 +31890,7 @@ function resolveShapeFaceTableInternal(plan, owner) {
31815
31890
  origin[2] - depthDir[2] * reverse.depth
31816
31891
  ],
31817
31892
  planar: true,
31818
- uAxis: cloneVec3$1(placement.workplane.u),
31893
+ uAxis: cloneVec3$3(placement.workplane.u),
31819
31894
  vAxis: [-placement.workplane.v[0], -placement.workplane.v[1], -placement.workplane.v[2]],
31820
31895
  query: capQuery
31821
31896
  });
@@ -32001,7 +32076,7 @@ function preservedShapeFaceQueries(basePlan) {
32001
32076
  return listShapeFaceQueries(basePlan);
32002
32077
  }
32003
32078
  const PLACEMENT_REFERENCE_KINDS = ["points", "edges", "surfaces", "objects"];
32004
- function cloneVec3(value, label) {
32079
+ function cloneVec3$2(value, label) {
32005
32080
  if (!Array.isArray(value) || value.length < 3) {
32006
32081
  throw new Error(`${label} must be a [x, y, z] tuple`);
32007
32082
  }
@@ -32027,23 +32102,23 @@ function isBoundsObject(value) {
32027
32102
  function toObjectBounds(value, label) {
32028
32103
  if (isBoundsObject(value)) {
32029
32104
  return {
32030
- min: cloneVec3(value.min, `${label}.min`),
32031
- max: cloneVec3(value.max, `${label}.max`)
32105
+ min: cloneVec3$2(value.min, `${label}.min`),
32106
+ max: cloneVec3$2(value.max, `${label}.max`)
32032
32107
  };
32033
32108
  }
32034
32109
  if (typeof value === "object" && value != null) {
32035
32110
  if ("boundingBox" in value && typeof value.boundingBox === "function") {
32036
32111
  const bounds = value.boundingBox();
32037
32112
  return {
32038
- min: cloneVec3(bounds.min, `${label}.min`),
32039
- max: cloneVec3(bounds.max, `${label}.max`)
32113
+ min: cloneVec3$2(bounds.min, `${label}.min`),
32114
+ max: cloneVec3$2(bounds.max, `${label}.max`)
32040
32115
  };
32041
32116
  }
32042
32117
  if ("_bbox" in value && typeof value._bbox === "function") {
32043
32118
  const bounds = value._bbox();
32044
32119
  return {
32045
- min: cloneVec3(bounds.min, `${label}.min`),
32046
- max: cloneVec3(bounds.max, `${label}.max`)
32120
+ min: cloneVec3$2(bounds.min, `${label}.min`),
32121
+ max: cloneVec3$2(bounds.max, `${label}.max`)
32047
32122
  };
32048
32123
  }
32049
32124
  }
@@ -32087,24 +32162,24 @@ function createPlacementReferences() {
32087
32162
  function clonePlacementReferences(refs) {
32088
32163
  const out = createPlacementReferences();
32089
32164
  for (const [name, point2] of Object.entries(refs.points)) {
32090
- out.points[name] = cloneVec3(point2, `points.${name}`);
32165
+ out.points[name] = cloneVec3$2(point2, `points.${name}`);
32091
32166
  }
32092
32167
  for (const [name, edge] of Object.entries(refs.edges)) {
32093
32168
  out.edges[name] = {
32094
- start: cloneVec3(edge.start, `edges.${name}.start`),
32095
- end: cloneVec3(edge.end, `edges.${name}.end`)
32169
+ start: cloneVec3$2(edge.start, `edges.${name}.start`),
32170
+ end: cloneVec3$2(edge.end, `edges.${name}.end`)
32096
32171
  };
32097
32172
  }
32098
32173
  for (const [name, surface] of Object.entries(refs.surfaces)) {
32099
32174
  out.surfaces[name] = {
32100
- center: cloneVec3(surface.center, `surfaces.${name}.center`),
32101
- normal: cloneVec3(surface.normal, `surfaces.${name}.normal`)
32175
+ center: cloneVec3$2(surface.center, `surfaces.${name}.center`),
32176
+ normal: cloneVec3$2(surface.normal, `surfaces.${name}.normal`)
32102
32177
  };
32103
32178
  }
32104
32179
  for (const [name, objectRef] of Object.entries(refs.objects)) {
32105
32180
  out.objects[name] = {
32106
- min: cloneVec3(objectRef.min, `objects.${name}.min`),
32107
- max: cloneVec3(objectRef.max, `objects.${name}.max`)
32181
+ min: cloneVec3$2(objectRef.min, `objects.${name}.min`),
32182
+ max: cloneVec3$2(objectRef.max, `objects.${name}.max`)
32108
32183
  };
32109
32184
  }
32110
32185
  return out;
@@ -32112,18 +32187,18 @@ function clonePlacementReferences(refs) {
32112
32187
  function normalizePlacementReferenceInput(input = {}) {
32113
32188
  const out = createPlacementReferences();
32114
32189
  for (const [name, point2] of Object.entries(input.points ?? {})) {
32115
- out.points[name] = cloneVec3(point2, `points.${name}`);
32190
+ out.points[name] = cloneVec3$2(point2, `points.${name}`);
32116
32191
  }
32117
32192
  for (const [name, edge] of Object.entries(input.edges ?? {})) {
32118
32193
  out.edges[name] = {
32119
- start: cloneVec3(edge.start, `edges.${name}.start`),
32120
- end: cloneVec3(edge.end, `edges.${name}.end`)
32194
+ start: cloneVec3$2(edge.start, `edges.${name}.start`),
32195
+ end: cloneVec3$2(edge.end, `edges.${name}.end`)
32121
32196
  };
32122
32197
  }
32123
32198
  for (const [name, surface] of Object.entries(input.surfaces ?? {})) {
32124
32199
  out.surfaces[name] = {
32125
- center: cloneVec3(surface.center, `surfaces.${name}.center`),
32126
- normal: normalizeVector$1(cloneVec3(surface.normal, `surfaces.${name}.normal`))
32200
+ center: cloneVec3$2(surface.center, `surfaces.${name}.center`),
32201
+ normal: normalizeVector$1(cloneVec3$2(surface.normal, `surfaces.${name}.normal`))
32127
32202
  };
32128
32203
  }
32129
32204
  for (const [name, objectRef] of Object.entries(input.objects ?? {})) {
@@ -32134,23 +32209,23 @@ function normalizePlacementReferenceInput(input = {}) {
32134
32209
  function mergePlacementReferences(...refsList) {
32135
32210
  const out = createPlacementReferences();
32136
32211
  for (const refs of refsList) {
32137
- for (const [name, point2] of Object.entries(refs.points)) out.points[name] = cloneVec3(point2, `points.${name}`);
32212
+ for (const [name, point2] of Object.entries(refs.points)) out.points[name] = cloneVec3$2(point2, `points.${name}`);
32138
32213
  for (const [name, edge] of Object.entries(refs.edges)) {
32139
32214
  out.edges[name] = {
32140
- start: cloneVec3(edge.start, `edges.${name}.start`),
32141
- end: cloneVec3(edge.end, `edges.${name}.end`)
32215
+ start: cloneVec3$2(edge.start, `edges.${name}.start`),
32216
+ end: cloneVec3$2(edge.end, `edges.${name}.end`)
32142
32217
  };
32143
32218
  }
32144
32219
  for (const [name, surface] of Object.entries(refs.surfaces)) {
32145
32220
  out.surfaces[name] = {
32146
- center: cloneVec3(surface.center, `surfaces.${name}.center`),
32147
- normal: cloneVec3(surface.normal, `surfaces.${name}.normal`)
32221
+ center: cloneVec3$2(surface.center, `surfaces.${name}.center`),
32222
+ normal: cloneVec3$2(surface.normal, `surfaces.${name}.normal`)
32148
32223
  };
32149
32224
  }
32150
32225
  for (const [name, objectRef] of Object.entries(refs.objects)) {
32151
32226
  out.objects[name] = {
32152
- min: cloneVec3(objectRef.min, `objects.${name}.min`),
32153
- max: cloneVec3(objectRef.max, `objects.${name}.max`)
32227
+ min: cloneVec3$2(objectRef.min, `objects.${name}.min`),
32228
+ max: cloneVec3$2(objectRef.max, `objects.${name}.max`)
32154
32229
  };
32155
32230
  }
32156
32231
  }
@@ -32194,7 +32269,7 @@ function resolvePointFromKind(refs, kind, name, selector, originalRef) {
32194
32269
  const point2 = refs.points[name];
32195
32270
  if (!point2) return null;
32196
32271
  if (selector != null) placementRefSelectorError(originalRef, "does not support selectors");
32197
- return cloneVec3(point2, `points.${name}`);
32272
+ return cloneVec3$2(point2, `points.${name}`);
32198
32273
  }
32199
32274
  case "edges": {
32200
32275
  const edge = refs.edges[name];
@@ -32202,8 +32277,8 @@ function resolvePointFromKind(refs, kind, name, selector, originalRef) {
32202
32277
  if (selector == null || selector === "midpoint" || selector === "center") {
32203
32278
  return midpoint$2(edge.start, edge.end);
32204
32279
  }
32205
- if (selector === "start") return cloneVec3(edge.start, `edges.${name}.start`);
32206
- if (selector === "end") return cloneVec3(edge.end, `edges.${name}.end`);
32280
+ if (selector === "start") return cloneVec3$2(edge.start, `edges.${name}.start`);
32281
+ if (selector === "end") return cloneVec3$2(edge.end, `edges.${name}.end`);
32207
32282
  placementRefSelectorError(originalRef, "supports only .start, .end, or .midpoint");
32208
32283
  }
32209
32284
  case "surfaces": {
@@ -32212,7 +32287,7 @@ function resolvePointFromKind(refs, kind, name, selector, originalRef) {
32212
32287
  if (selector != null && selector !== "center") {
32213
32288
  placementRefSelectorError(originalRef, "supports only .center");
32214
32289
  }
32215
- return cloneVec3(surface.center, `surfaces.${name}.center`);
32290
+ return cloneVec3$2(surface.center, `surfaces.${name}.center`);
32216
32291
  }
32217
32292
  case "objects": {
32218
32293
  const objectRef = refs.objects[name];
@@ -35822,7 +35897,7 @@ function edgesBetweenFaces(shape, faceA, faceBs) {
35822
35897
  });
35823
35898
  return coalesceEdges(result);
35824
35899
  }
35825
- const EPSILON$2 = 1e-8;
35900
+ const EPSILON$3 = 1e-8;
35826
35901
  function rayTriangle(ox, oy, oz, dx, dy, dz, ax, ay, az, bx, by, bz, cx, cy, cz) {
35827
35902
  const e1x = bx - ax, e1y = by - ay, e1z = bz - az;
35828
35903
  const e2x = cx - ax, e2y = cy - ay, e2z = cz - az;
@@ -35830,7 +35905,7 @@ function rayTriangle(ox, oy, oz, dx, dy, dz, ax, ay, az, bx, by, bz, cx, cy, cz)
35830
35905
  const py2 = dz * e2x - dx * e2z;
35831
35906
  const pz2 = dx * e2y - dy * e2x;
35832
35907
  const det = e1x * px2 + e1y * py2 + e1z * pz2;
35833
- if (det > -EPSILON$2 && det < EPSILON$2) return null;
35908
+ if (det > -EPSILON$3 && det < EPSILON$3) return null;
35834
35909
  const invDet = 1 / det;
35835
35910
  const tx = ox - ax, ty = oy - ay, tz = oz - az;
35836
35911
  const u2 = (tx * px2 + ty * py2 + tz * pz2) * invDet;
@@ -35869,7 +35944,7 @@ function rayMeshIntersect(mesh, origin, direction2) {
35869
35944
  hits.sort((a2, b) => a2.distance - b.distance);
35870
35945
  const deduped = [];
35871
35946
  for (const hit of hits) {
35872
- if (deduped.length === 0 || Math.abs(hit.distance - deduped[deduped.length - 1].distance) > EPSILON$2 * 100) {
35947
+ if (deduped.length === 0 || Math.abs(hit.distance - deduped[deduped.length - 1].distance) > EPSILON$3 * 100) {
35873
35948
  deduped.push(hit);
35874
35949
  }
35875
35950
  }
@@ -41412,7 +41487,7 @@ function rodrigues(rv) {
41412
41487
  function rotateVec3(R, v) {
41413
41488
  return [R[0] * v[0] + R[1] * v[1] + R[2] * v[2], R[3] * v[0] + R[4] * v[1] + R[5] * v[2], R[6] * v[0] + R[7] * v[1] + R[8] * v[2]];
41414
41489
  }
41415
- function transformPoint(rv, translation, point2) {
41490
+ function transformPoint$1(rv, translation, point2) {
41416
41491
  const R = rodrigues(rv);
41417
41492
  const rotated = rotateVec3(R, point2);
41418
41493
  return [rotated[0] + translation[0], rotated[1] + translation[1], rotated[2] + translation[2]];
@@ -41642,7 +41717,7 @@ function createContext(bodies) {
41642
41717
  toWorld(bodyId, point2) {
41643
41718
  const body = bodies.get(bodyId);
41644
41719
  if (!body) throw new Error(`Unknown body: ${bodyId}`);
41645
- return transformPoint(body.rotation, body.position, point2);
41720
+ return transformPoint$1(body.rotation, body.position, point2);
41646
41721
  },
41647
41722
  toWorldDir(bodyId, dir) {
41648
41723
  const body = bodies.get(bodyId);
@@ -41656,7 +41731,7 @@ function createContext(bodies) {
41656
41731
  if (!face) throw new Error(`Unknown face "${faceName}" on body "${bodyId}"`);
41657
41732
  return {
41658
41733
  normal: normalize3(transformDir(body.rotation, face.normal)),
41659
- center: transformPoint(body.rotation, body.position, face.center)
41734
+ center: transformPoint$1(body.rotation, body.position, face.center)
41660
41735
  };
41661
41736
  },
41662
41737
  worldAxis(bodyId, axisName) {
@@ -41665,7 +41740,7 @@ function createContext(bodies) {
41665
41740
  const axis = body.axes.get(axisName);
41666
41741
  if (!axis) throw new Error(`Unknown axis "${axisName}" on body "${bodyId}"`);
41667
41742
  return {
41668
- origin: transformPoint(body.rotation, body.position, axis.origin),
41743
+ origin: transformPoint$1(body.rotation, body.position, axis.origin),
41669
41744
  direction: normalize3(transformDir(body.rotation, axis.direction))
41670
41745
  };
41671
41746
  },
@@ -41674,7 +41749,7 @@ function createContext(bodies) {
41674
41749
  if (!body) throw new Error(`Unknown body: ${bodyId}`);
41675
41750
  const pt = body.points.get(pointName);
41676
41751
  if (!pt) throw new Error(`Unknown point "${pointName}" on body "${bodyId}"`);
41677
- return transformPoint(body.rotation, body.position, pt.position);
41752
+ return transformPoint$1(body.rotation, body.position, pt.position);
41678
41753
  }
41679
41754
  };
41680
41755
  }
@@ -47218,10 +47293,8 @@ class PathBuilder {
47218
47293
  if (radius <= 0) throw new Error("fillet: radius must be positive");
47219
47294
  const n = this.segs.length;
47220
47295
  if (n < 2) throw new Error("fillet: need at least 2 segments before a fillet");
47221
- const prev = this.segs[n - 2];
47222
47296
  const curr = this.segs[n - 1];
47223
- curr.kind === "line" || curr.kind === "move" ? prev.kind === "line" || prev.kind === "move" ? 0 : 0 : 0;
47224
- const { trimA, trimB, arcSeg } = this.computeFilletGeom(radius);
47297
+ const { trimA, arcSeg } = this.computeFilletGeom(radius);
47225
47298
  if (!arcSeg) throw new Error("fillet: cannot fillet these segments (parallel or degenerate)");
47226
47299
  this.trimLastSegEnd(n - 2, trimA[0], trimA[1]);
47227
47300
  const trimmedSeg = { ...curr };
@@ -47293,7 +47366,6 @@ class PathBuilder {
47293
47366
  }
47294
47367
  getSegDirAt(seg, which) {
47295
47368
  if (seg.kind === "line" || seg.kind === "move") {
47296
- this.segs.length;
47297
47369
  const idx = this.segs.indexOf(seg);
47298
47370
  if (seg.kind === "line") {
47299
47371
  let sx, sy;
@@ -47535,6 +47607,41 @@ class PathBuilder {
47535
47607
  }
47536
47608
  return pts;
47537
47609
  }
47610
+ /**
47611
+ * Return the open path as a sampled 2D polyline.
47612
+ *
47613
+ * This is for construction geometry such as guide rails, measured centerlines,
47614
+ * and curve-driven helpers where the authored path should stay open instead of
47615
+ * becoming a filled sketch or stroked profile.
47616
+ *
47617
+ * **Example**
47618
+ *
47619
+ * ```ts
47620
+ * const rail = path()
47621
+ * .moveTo(24, 0)
47622
+ * .bezierTo(32, 44, 28, 92, 18, 120)
47623
+ * .toPolyline();
47624
+ * ```
47625
+ *
47626
+ * @returns A sampled open polyline.
47627
+ * @category Path Builder
47628
+ */
47629
+ toPolyline() {
47630
+ const moveCount = this.segs.filter((seg) => seg.kind === "move").length;
47631
+ if (moveCount > 1) {
47632
+ throw new Error("path().toPolyline() supports one continuous open path. Use separate path() builders for separate rails.");
47633
+ }
47634
+ const pts = [];
47635
+ for (const point2 of this.tessellate()) {
47636
+ if (!point2.every(Number.isFinite)) throw new Error("path().toPolyline() produced a non-finite point");
47637
+ const previous = pts[pts.length - 1];
47638
+ if (!previous || Math.hypot(point2[0] - previous[0], point2[1] - previous[1]) > 1e-9) {
47639
+ pts.push(point2);
47640
+ }
47641
+ }
47642
+ if (pts.length < 2) throw new Error("path().toPolyline() needs at least 2 points");
47643
+ return pts;
47644
+ }
47538
47645
  // ── Output ────────────────────────────────────────────────────────────────
47539
47646
  /**
47540
47647
  * Close the path and return a filled `Sketch`.
@@ -49173,7 +49280,7 @@ function beltDrive(options) {
49173
49280
  };
49174
49281
  }
49175
49282
  const GEAR_META_KEY = Symbol.for("forgecad.library.gearMeta");
49176
- const EPSILON$1 = 1e-9;
49283
+ const EPSILON$2 = 1e-9;
49177
49284
  function clamp01$1(value) {
49178
49285
  return Math.max(-1, Math.min(1, value));
49179
49286
  }
@@ -49292,11 +49399,11 @@ function buildSpurGearMeta(options) {
49292
49399
  const pitchRadius = options.module * options.teeth * 0.5;
49293
49400
  const baseRadius = pitchRadius * Math.cos(options.pressureAngleRad);
49294
49401
  const outerRadius = pitchRadius + options.addendum;
49295
- const rootRadius = Math.max(EPSILON$1, pitchRadius - options.dedendum);
49296
- if (!(rootRadius < outerRadius - EPSILON$1)) {
49402
+ const rootRadius = Math.max(EPSILON$2, pitchRadius - options.dedendum);
49403
+ if (!(rootRadius < outerRadius - EPSILON$2)) {
49297
49404
  throw new Error("spurGear: invalid radii (root radius must be smaller than outer radius)");
49298
49405
  }
49299
- if (options.boreDiameter > 0 && options.boreDiameter * 0.5 >= rootRadius - EPSILON$1) {
49406
+ if (options.boreDiameter > 0 && options.boreDiameter * 0.5 >= rootRadius - EPSILON$2) {
49300
49407
  throw new Error("spurGear: bore is too large for the computed root radius");
49301
49408
  }
49302
49409
  return {
@@ -49319,7 +49426,7 @@ function buildSpurGearMeta(options) {
49319
49426
  function createSpurToothSketch(meta2, segmentsPerTooth) {
49320
49427
  const circularPitch = Math.PI * meta2.module;
49321
49428
  const thicknessAtPitch = circularPitch * 0.5 - meta2.backlash;
49322
- if (thicknessAtPitch <= EPSILON$1) {
49429
+ if (thicknessAtPitch <= EPSILON$2) {
49323
49430
  throw new Error("spurGear: backlash leaves no tooth thickness at pitch circle");
49324
49431
  }
49325
49432
  const halfThicknessAtPitch = thicknessAtPitch / (2 * meta2.pitchRadius);
@@ -49328,7 +49435,7 @@ function createSpurToothSketch(meta2, segmentsPerTooth) {
49328
49435
  const arcSteps = Math.max(3, Math.ceil(segmentsPerTooth * 0.6));
49329
49436
  const radialGap = flankStartRadius - meta2.rootRadius;
49330
49437
  const filletRadius = Math.min(0.38 * meta2.module, radialGap * 0.9);
49331
- const hasFillets = filletRadius > EPSILON$1;
49438
+ const hasFillets = filletRadius > EPSILON$2;
49332
49439
  const leftFlank = [];
49333
49440
  const rightFlank = [];
49334
49441
  for (let i = 0; i <= flankSteps; i++) {
@@ -49556,7 +49663,7 @@ function normalizedSweep(scope, fromAngleDeg, toAngleDeg) {
49556
49663
  if (!Number.isFinite(toAngleDeg)) throw new Error(`${scope}: "toAngleDeg" must be finite`);
49557
49664
  let sweep2 = toAngleDeg - fromAngleDeg;
49558
49665
  while (sweep2 <= 0) sweep2 += 360;
49559
- if (sweep2 > 360 + EPSILON$1) throw new Error(`${scope}: angular sweep must be <= 360 degrees`);
49666
+ if (sweep2 > 360 + EPSILON$2) throw new Error(`${scope}: angular sweep must be <= 360 degrees`);
49560
49667
  return Math.min(360, sweep2);
49561
49668
  }
49562
49669
  function buildSpurToothRegionProfile(meta2, firstTooth, toothCount, segmentsPerTooth) {
@@ -49571,7 +49678,7 @@ function buildSolidArcProfile(options, sweepDeg) {
49571
49678
  const innerRadius = options.innerRadius ?? 0;
49572
49679
  const segments = options.segments ?? Math.max(16, Math.ceil(sweepDeg / 6));
49573
49680
  if (!Number.isInteger(segments) || segments < 4) throw new Error('driveWheel.addSolidArcBetween: "segments" must be an integer >= 4');
49574
- if (Math.abs(sweepDeg - 360) < EPSILON$1) {
49681
+ if (Math.abs(sweepDeg - 360) < EPSILON$2) {
49575
49682
  const outer = circle2d(options.outerRadius, segments);
49576
49683
  return innerRadius > 0 ? difference2d(outer, circle2d(innerRadius, segments)) : outer;
49577
49684
  }
@@ -49655,7 +49762,7 @@ class DriveWheelBuilder {
49655
49762
  }
49656
49763
  const faceWidth = this.resolveBuildFaceWidth();
49657
49764
  const firstGearRegion = (_a3 = this.regions.find((region) => region.gearMeta)) == null ? void 0 : _a3.gearMeta;
49658
- if (firstGearRegion && this.boreDiameter * 0.5 >= firstGearRegion.rootRadius - EPSILON$1) {
49765
+ if (firstGearRegion && this.boreDiameter * 0.5 >= firstGearRegion.rootRadius - EPSILON$2) {
49659
49766
  throw new Error("driveWheel: bore is too large for the first spur-tooth region");
49660
49767
  }
49661
49768
  const body = ((_b3 = this.body) == null ? void 0 : _b3.clone()) ?? gearBodyDisk({ outerRadius: (firstGearRegion == null ? void 0 : firstGearRegion.rootRadius) ?? this.defaultBodyRadius(), faceWidth });
@@ -49701,7 +49808,7 @@ class DriveWheelBuilder {
49701
49808
  const faceWidth = localFaceWidth ?? this.faceWidth;
49702
49809
  if (faceWidth === void 0) throw new Error(`${scope}: "faceWidth" is required unless driveWheel({ faceWidth }) was set`);
49703
49810
  requirePositive$6(scope, "faceWidth", faceWidth);
49704
- if (this.faceWidth !== void 0 && localFaceWidth !== void 0 && Math.abs(this.faceWidth - localFaceWidth) > EPSILON$1) {
49811
+ if (this.faceWidth !== void 0 && localFaceWidth !== void 0 && Math.abs(this.faceWidth - localFaceWidth) > EPSILON$2) {
49705
49812
  throw new Error(`${scope}: region faceWidth must match driveWheel faceWidth`);
49706
49813
  }
49707
49814
  return faceWidth;
@@ -49924,7 +50031,7 @@ function normalizeRingGearOptions(options) {
49924
50031
  const rimWidth = options.rimWidth ?? module * 2;
49925
50032
  if (!isFinitePositive(rimWidth)) throw new Error('ringGear: "rimWidth" must be > 0');
49926
50033
  const outerRadius = options.outerDiameter != null ? options.outerDiameter * 0.5 : rootRadius + rimWidth;
49927
- if (!(outerRadius > rootRadius + EPSILON$1)) {
50034
+ if (!(outerRadius > rootRadius + EPSILON$2)) {
49928
50035
  throw new Error("ringGear: outer diameter/rim width leaves no ring body");
49929
50036
  }
49930
50037
  return {
@@ -49976,8 +50083,8 @@ function ringGear(options) {
49976
50083
  const baseRadius = pitchRadius * Math.cos(normalized.pressureAngleRad);
49977
50084
  const tipRadius = pitchRadius - normalized.addendum;
49978
50085
  const rootRadius = pitchRadius + normalized.dedendum;
49979
- if (!(tipRadius > EPSILON$1)) throw new Error("ringGear: addendum is too large for the tooth count/module");
49980
- if (!(tipRadius < rootRadius - EPSILON$1)) throw new Error("ringGear: invalid tip/root radius relationship");
50086
+ if (!(tipRadius > EPSILON$2)) throw new Error("ringGear: addendum is too large for the tooth count/module");
50087
+ if (!(tipRadius < rootRadius - EPSILON$2)) throw new Error("ringGear: invalid tip/root radius relationship");
49981
50088
  const meta2 = {
49982
50089
  kind: "ring",
49983
50090
  module: normalized.module,
@@ -50051,13 +50158,13 @@ function rackGear(options) {
50051
50158
  const baseHeight = options.baseHeight ?? module * 1.6;
50052
50159
  if (!isFinitePositive(baseHeight)) throw new Error('rackGear: "baseHeight" must be > 0');
50053
50160
  const thicknessAtPitch = pitch * 0.5 - backlash;
50054
- if (thicknessAtPitch <= EPSILON$1) throw new Error("rackGear: backlash leaves no tooth thickness");
50161
+ if (thicknessAtPitch <= EPSILON$2) throw new Error("rackGear: backlash leaves no tooth thickness");
50055
50162
  const halfPitchThickness = thicknessAtPitch * 0.5;
50056
50163
  const dxTip = addendum * Math.tan(pressureAngleRad);
50057
50164
  const dxRoot = dedendum * Math.tan(pressureAngleRad);
50058
50165
  const halfTip = halfPitchThickness - dxTip;
50059
50166
  const halfRoot = halfPitchThickness + dxRoot;
50060
- if (halfTip <= EPSILON$1) {
50167
+ if (halfTip <= EPSILON$2) {
50061
50168
  throw new Error("rackGear: tooth tip collapsed (increase module or lower pressure angle/addendum)");
50062
50169
  }
50063
50170
  const toothSketch = polygon([
@@ -50123,7 +50230,7 @@ function computeBevelPitchAngleDeg(teeth, mateTeeth, shaftAngleDeg) {
50123
50230
  const numerator = teeth * Math.sin(shaftAngleRad);
50124
50231
  const denominator = mateTeeth + teeth * Math.cos(shaftAngleRad);
50125
50232
  const angle = Math.atan2(numerator, denominator);
50126
- if (!(angle > EPSILON$1 && angle < shaftAngleRad - EPSILON$1)) {
50233
+ if (!(angle > EPSILON$2 && angle < shaftAngleRad - EPSILON$2)) {
50127
50234
  throw new Error("bevelGear: could not derive a valid pitch angle from teeth/shaft angle");
50128
50235
  }
50129
50236
  return angle * 180 / Math.PI;
@@ -50158,7 +50265,7 @@ function normalizeBevelGearOptions(options) {
50158
50265
  }
50159
50266
  const pitchAngleRad = pitchAngleDeg * Math.PI / 180;
50160
50267
  const pitchRadius = spur.module * spur.teeth * 0.5;
50161
- const coneDistance = pitchRadius / Math.max(EPSILON$1, Math.sin(pitchAngleRad));
50268
+ const coneDistance = pitchRadius / Math.max(EPSILON$2, Math.sin(pitchAngleRad));
50162
50269
  if (!isFinitePositive(coneDistance)) {
50163
50270
  throw new Error("bevelGear: invalid cone distance");
50164
50271
  }
@@ -50167,7 +50274,7 @@ function normalizeBevelGearOptions(options) {
50167
50274
  throw new Error("bevelGear: faceWidth is too large for the selected pitch angle");
50168
50275
  }
50169
50276
  const topScale = smallPitchRadius / pitchRadius;
50170
- if (!(topScale > EPSILON$1 && topScale <= 1)) {
50277
+ if (!(topScale > EPSILON$2 && topScale <= 1)) {
50171
50278
  throw new Error("bevelGear: computed top scale is invalid");
50172
50279
  }
50173
50280
  return {
@@ -50283,7 +50390,7 @@ function gearPair(options) {
50283
50390
  const alpha = pinion.meta.pressureAngleRad;
50284
50391
  const nominalCenterDistance = pinion.meta.pitchRadius + gear.meta.pitchRadius;
50285
50392
  const requestedBacklash = options.backlash ?? Math.max(pinion.meta.backlash, gear.meta.backlash, 0);
50286
- const autoCenterDistance = nominalCenterDistance + requestedBacklash / (2 * Math.max(EPSILON$1, Math.tan(alpha)));
50393
+ const autoCenterDistance = nominalCenterDistance + requestedBacklash / (2 * Math.max(EPSILON$2, Math.tan(alpha)));
50287
50394
  const centerDistance = options.centerDistance ?? autoCenterDistance;
50288
50395
  if (!Number.isFinite(centerDistance) || centerDistance <= 0) {
50289
50396
  throw new Error("gearPair: centerDistance must be > 0");
@@ -50312,11 +50419,11 @@ function gearPair(options) {
50312
50419
  message: `Center distance ${centerDistance.toFixed(4)} exceeds addendum reach ${addendumReach.toFixed(4)} (no mesh contact)`
50313
50420
  });
50314
50421
  }
50315
- const cosWorking = clamp01$1(baseSum / Math.max(centerDistance, EPSILON$1));
50422
+ const cosWorking = clamp01$1(baseSum / Math.max(centerDistance, EPSILON$2));
50316
50423
  const alphaWorking = Math.acos(cosWorking);
50317
50424
  const basePitch = Math.PI * module * Math.cos(alpha);
50318
50425
  const pathLength = Math.sqrt(Math.max(0, pinion.meta.outerRadius ** 2 - pinion.meta.baseRadius ** 2)) + Math.sqrt(Math.max(0, gear.meta.outerRadius ** 2 - gear.meta.baseRadius ** 2)) - centerDistance * Math.sin(alphaWorking);
50319
- const contactRatio = pathLength / Math.max(EPSILON$1, basePitch);
50426
+ const contactRatio = pathLength / Math.max(EPSILON$2, basePitch);
50320
50427
  if (contactRatio < 1) {
50321
50428
  diagnostics.push({
50322
50429
  level: "error",
@@ -50447,8 +50554,8 @@ function bevelGearPair(options) {
50447
50554
  message: `Gear pitch angle (${gearMetaPitch.toFixed(2)}deg) differs from tooth-derived ${gearPitchAngleDeg.toFixed(2)}deg`
50448
50555
  });
50449
50556
  }
50450
- const coneDistancePinion = pinion.meta.coneDistance ?? pinion.meta.pitchRadius / Math.max(EPSILON$1, Math.sin(pinionPitchAngleRad));
50451
- const coneDistanceGear = gear.meta.coneDistance ?? gear.meta.pitchRadius / Math.max(EPSILON$1, Math.sin(gearPitchAngleRad));
50557
+ const coneDistancePinion = pinion.meta.coneDistance ?? pinion.meta.pitchRadius / Math.max(EPSILON$2, Math.sin(pinionPitchAngleRad));
50558
+ const coneDistanceGear = gear.meta.coneDistance ?? gear.meta.pitchRadius / Math.max(EPSILON$2, Math.sin(gearPitchAngleRad));
50452
50559
  const coneDistance = (coneDistancePinion + coneDistanceGear) * 0.5;
50453
50560
  if (Math.abs(coneDistancePinion - coneDistanceGear) > pinion.meta.module * 0.5) {
50454
50561
  diagnostics.push({
@@ -50457,8 +50564,8 @@ function bevelGearPair(options) {
50457
50564
  message: `Pitch-cone distances differ: pinion=${coneDistancePinion.toFixed(3)}, gear=${coneDistanceGear.toFixed(3)}`
50458
50565
  });
50459
50566
  }
50460
- const pinionToApex = pinion.meta.pitchRadius / Math.max(EPSILON$1, Math.tan(pinionPitchAngleRad)) - pinion.meta.faceWidth * 0.5;
50461
- const gearToApex = gear.meta.pitchRadius / Math.max(EPSILON$1, Math.tan(gearPitchAngleRad)) - gear.meta.faceWidth * 0.5;
50567
+ const pinionToApex = pinion.meta.pitchRadius / Math.max(EPSILON$2, Math.tan(pinionPitchAngleRad)) - pinion.meta.faceWidth * 0.5;
50568
+ const gearToApex = gear.meta.pitchRadius / Math.max(EPSILON$2, Math.tan(gearPitchAngleRad)) - gear.meta.faceWidth * 0.5;
50462
50569
  if (pinionToApex <= 0 || gearToApex <= 0) {
50463
50570
  diagnostics.push({
50464
50571
  level: "warn",
@@ -51253,11 +51360,11 @@ function inverseLerp(x2, y2, value) {
51253
51360
  return 0;
51254
51361
  }
51255
51362
  }
51256
- function lerp$3(x2, y2, t) {
51363
+ function lerp$6(x2, y2, t) {
51257
51364
  return (1 - t) * x2 + t * y2;
51258
51365
  }
51259
51366
  function damp(x2, y2, lambda, dt) {
51260
- return lerp$3(x2, y2, 1 - Math.exp(-lambda * dt));
51367
+ return lerp$6(x2, y2, 1 - Math.exp(-lambda * dt));
51261
51368
  }
51262
51369
  function pingpong(x2, length4 = 1) {
51263
51370
  return length4 - Math.abs(euclideanModulo(x2, length4 * 2) - length4);
@@ -51450,7 +51557,7 @@ const MathUtils = {
51450
51557
  * @param {number} t - The interpolation factor in the closed interval `[0, 1]`.
51451
51558
  * @return {number} The interpolated value.
51452
51559
  */
51453
- lerp: lerp$3,
51560
+ lerp: lerp$6,
51454
51561
  /**
51455
51562
  * Smoothly interpolate a number from `x` to `y` in a spring-like manner using a delta
51456
51563
  * time to maintain frame rate independent movement. For details, see
@@ -60404,9 +60511,9 @@ class Color {
60404
60511
  lerpHSL(color, alpha) {
60405
60512
  this.getHSL(_hslA);
60406
60513
  color.getHSL(_hslB);
60407
- const h = lerp$3(_hslA.h, _hslB.h, alpha);
60408
- const s = lerp$3(_hslA.s, _hslB.s, alpha);
60409
- const l = lerp$3(_hslA.l, _hslB.l, alpha);
60514
+ const h = lerp$6(_hslA.h, _hslB.h, alpha);
60515
+ const s = lerp$6(_hslA.s, _hslB.s, alpha);
60516
+ const l = lerp$6(_hslA.l, _hslB.l, alpha);
60410
60517
  this.setHSL(h, s, l);
60411
60518
  return this;
60412
60519
  }
@@ -92404,7 +92511,7 @@ function requireFinite$7(value, label) {
92404
92511
  }
92405
92512
  return value;
92406
92513
  }
92407
- function requireVec3$2(value, label) {
92514
+ function requireVec3$3(value, label) {
92408
92515
  if (!Array.isArray(value) || value.length !== 3) {
92409
92516
  throw new Error(`${label} must be [x, y, z]`);
92410
92517
  }
@@ -92448,7 +92555,7 @@ function normalizeOptions(options) {
92448
92555
  out.size = requireFinite$7(options.size, "Viewport.label options.size");
92449
92556
  if (out.size <= 0) throw new Error("Viewport.label options.size must be positive");
92450
92557
  }
92451
- if (options.offset !== void 0) out.offset = requireVec3$2(options.offset, "Viewport.label options.offset");
92558
+ if (options.offset !== void 0) out.offset = requireVec3$3(options.offset, "Viewport.label options.offset");
92452
92559
  if (options.anchor !== void 0) {
92453
92560
  if (!VALID_ANCHORS.has(options.anchor)) {
92454
92561
  throw new Error(`Viewport.label options.anchor must be one of: ${Array.from(VALID_ANCHORS).join(", ")}`);
@@ -92465,7 +92572,7 @@ function collectRenderLabel(text, at, options) {
92465
92572
  if (typeof text !== "string" || text.trim().length === 0) {
92466
92573
  throw new Error("Viewport.label text must be a non-empty string");
92467
92574
  }
92468
- const normalizedAt = requireVec3$2(at, "Viewport.label at");
92575
+ const normalizedAt = requireVec3$3(at, "Viewport.label at");
92469
92576
  const normalizedOptions = normalizeOptions(options);
92470
92577
  _collected$4.push({
92471
92578
  id: `render-label-${_nextId++}`,
@@ -92884,7 +92991,7 @@ function requireFinite$6(value, label) {
92884
92991
  }
92885
92992
  return value;
92886
92993
  }
92887
- function requireVec3$1(value, label) {
92994
+ function requireVec3$2(value, label) {
92888
92995
  if (!Array.isArray(value) || value.length !== 3) {
92889
92996
  throw new Error(`${label} must be [x, y, z]`);
92890
92997
  }
@@ -92912,9 +93019,9 @@ const VALID_ENVIRONMENT_PRESETS = /* @__PURE__ */ new Set([
92912
93019
  ]);
92913
93020
  function validateCamera(cam, label) {
92914
93021
  const out = {};
92915
- if (cam.position !== void 0) out.position = requireVec3$1(cam.position, `${label}.position`);
92916
- if (cam.target !== void 0) out.target = requireVec3$1(cam.target, `${label}.target`);
92917
- if (cam.up !== void 0) out.up = requireVec3$1(cam.up, `${label}.up`);
93022
+ if (cam.position !== void 0) out.position = requireVec3$2(cam.position, `${label}.position`);
93023
+ if (cam.target !== void 0) out.target = requireVec3$2(cam.target, `${label}.target`);
93024
+ if (cam.up !== void 0) out.up = requireVec3$2(cam.up, `${label}.up`);
92918
93025
  if (cam.fov !== void 0) {
92919
93026
  out.fov = requireFinite$6(cam.fov, `${label}.fov`);
92920
93027
  if (out.fov <= 0 || out.fov >= 180) throw new Error(`${label}.fov must be between 0 and 180`);
@@ -93049,8 +93156,8 @@ function validateLight(light, label) {
93049
93156
  const out = { type: light.type };
93050
93157
  if (light.color !== void 0) out.color = requireColor(light.color, `${label}.color`);
93051
93158
  if (light.intensity !== void 0) out.intensity = requireFinite$6(light.intensity, `${label}.intensity`);
93052
- if (light.position !== void 0) out.position = requireVec3$1(light.position, `${label}.position`);
93053
- if (light.target !== void 0) out.target = requireVec3$1(light.target, `${label}.target`);
93159
+ if (light.position !== void 0) out.position = requireVec3$2(light.position, `${label}.position`);
93160
+ if (light.target !== void 0) out.target = requireVec3$2(light.target, `${label}.target`);
93054
93161
  if (light.groundColor !== void 0) out.groundColor = requireColor(light.groundColor, `${label}.groundColor`);
93055
93162
  if (light.skyColor !== void 0) out.skyColor = requireColor(light.skyColor, `${label}.skyColor`);
93056
93163
  if (light.angle !== void 0) out.angle = requireFinite$6(light.angle, `${label}.angle`);
@@ -94582,7 +94689,7 @@ function scale$1(v, s) {
94582
94689
  function dot$2(a2, b) {
94583
94690
  return a2[0] * b[0] + a2[1] * b[1] + a2[2] * b[2];
94584
94691
  }
94585
- function lerp$2(a2, b, t) {
94692
+ function lerp$5(a2, b, t) {
94586
94693
  return a2 + (b - a2) * t;
94587
94694
  }
94588
94695
  function frameMatrix$1(x2, y2, z2, p2) {
@@ -94593,7 +94700,7 @@ function axisVector(axis, sign2 = 1) {
94593
94700
  if (axis === "Y") return [0, sign2, 0];
94594
94701
  return [0, 0, sign2];
94595
94702
  }
94596
- function axisPosition(axis, point2) {
94703
+ function axisPosition$1(axis, point2) {
94597
94704
  return point2[AXIS_INDEX[axis]];
94598
94705
  }
94599
94706
  function crossPointForStation(axis, point2) {
@@ -94601,7 +94708,7 @@ function crossPointForStation(axis, point2) {
94601
94708
  if (axis === "Y") return [point2[0], -point2[2]];
94602
94709
  return [point2[1], point2[2]];
94603
94710
  }
94604
- function orientLoftToAxis(shape, axis) {
94711
+ function orientLoftToAxis$1(shape, axis) {
94605
94712
  if (axis === "Z") return shape;
94606
94713
  if (axis === "Y") return shape.rotateX(-90);
94607
94714
  return shape.transform([0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1]);
@@ -94658,9 +94765,9 @@ function interpolateQuery(a2, b, t) {
94658
94765
  }
94659
94766
  return {
94660
94767
  side: sideA,
94661
- u: lerp$2(a2.u ?? 0.5, b.u ?? 0.5, t),
94662
- v: lerp$2(a2.v ?? 0.5, b.v ?? 0.5, t),
94663
- offset: lerp$2(a2.offset ?? 0, b.offset ?? 0, t)
94768
+ u: lerp$5(a2.u ?? 0.5, b.u ?? 0.5, t),
94769
+ v: lerp$5(a2.v ?? 0.5, b.v ?? 0.5, t),
94770
+ offset: lerp$5(a2.offset ?? 0, b.offset ?? 0, t)
94664
94771
  };
94665
94772
  }
94666
94773
  function resolvePathQueries(points) {
@@ -94727,8 +94834,8 @@ class ProductSkin {
94727
94834
  this.stations = stations;
94728
94835
  this.rails = rails;
94729
94836
  for (const [name2, query] of Object.entries(refs)) this.refQueries.set(name2, cloneQuery(query));
94730
- this.axisMin = Math.min(...stations.map((station) => axisPosition(axis, station.center)));
94731
- this.axisMax = Math.max(...stations.map((station) => axisPosition(axis, station.center)));
94837
+ this.axisMin = Math.min(...stations.map((station) => axisPosition$1(axis, station.center)));
94838
+ this.axisMax = Math.max(...stations.map((station) => axisPosition$1(axis, station.center)));
94732
94839
  this.diagnosticsValue = {
94733
94840
  ...diagnostics,
94734
94841
  stationNames: stations.map((station) => station.name),
@@ -94785,24 +94892,24 @@ class ProductSkin {
94785
94892
  }
94786
94893
  /** Interpolate center, width, and depth at a normalized v or absolute axis value. */
94787
94894
  stationAt(vOrAxis) {
94788
- const axisValue = vOrAxis >= 0 && vOrAxis <= 1 ? lerp$2(this.axisMin, this.axisMax, vOrAxis) : clamp$5(vOrAxis, this.axisMin, this.axisMax);
94895
+ const axisValue = vOrAxis >= 0 && vOrAxis <= 1 ? lerp$5(this.axisMin, this.axisMax, vOrAxis) : clamp$5(vOrAxis, this.axisMin, this.axisMax);
94789
94896
  const sorted = this.stations;
94790
94897
  for (let index2 = 0; index2 < sorted.length - 1; index2 += 1) {
94791
94898
  const a2 = sorted[index2];
94792
94899
  const b = sorted[index2 + 1];
94793
- const aAxis = axisPosition(this.axis, a2.center);
94794
- const bAxis = axisPosition(this.axis, b.center);
94900
+ const aAxis = axisPosition$1(this.axis, a2.center);
94901
+ const bAxis = axisPosition$1(this.axis, b.center);
94795
94902
  if (axisValue < aAxis - EPS$5 || axisValue > bAxis + EPS$5) continue;
94796
94903
  const span = Math.max(EPS$5, bAxis - aAxis);
94797
94904
  const t = clamp$5((axisValue - aAxis) / span, 0, 1);
94798
94905
  return {
94799
94906
  axisValue,
94800
- center: [lerp$2(a2.center[0], b.center[0], t), lerp$2(a2.center[1], b.center[1], t), lerp$2(a2.center[2], b.center[2], t)],
94801
- width: lerp$2(a2.profile.width, b.profile.width, t),
94802
- depth: lerp$2(a2.profile.depth, b.profile.depth, t),
94907
+ center: [lerp$5(a2.center[0], b.center[0], t), lerp$5(a2.center[1], b.center[1], t), lerp$5(a2.center[2], b.center[2], t)],
94908
+ width: lerp$5(a2.profile.width, b.profile.width, t),
94909
+ depth: lerp$5(a2.profile.depth, b.profile.depth, t),
94803
94910
  dWidth: (b.profile.width - a2.profile.width) / span,
94804
94911
  dDepth: (b.profile.depth - a2.profile.depth) / span,
94805
- exponent: lerp$2(profileExponent(a2), profileExponent(b), t),
94912
+ exponent: lerp$5(profileExponent(a2), profileExponent(b), t),
94806
94913
  kind: a2.profile.kind === b.profile.kind ? a2.profile.kind : "custom"
94807
94914
  };
94808
94915
  }
@@ -94924,7 +95031,7 @@ class ProductSkinBuilder {
94924
95031
  }
94925
95032
  /** Set named cross-section stations for the product skin. */
94926
95033
  stations(stations) {
94927
- this.stationsValue = stations.map(toStationSpec).sort((a2, b) => axisPosition(this.axisValue, a2.center) - axisPosition(this.axisValue, b.center));
95034
+ this.stationsValue = stations.map(toStationSpec).sort((a2, b) => axisPosition$1(this.axisValue, a2.center) - axisPosition$1(this.axisValue, b.center));
94928
95035
  return this;
94929
95036
  }
94930
95037
  /** Attach named guide rails for product-skin construction and downstream surface references. */
@@ -94974,9 +95081,9 @@ class ProductSkinBuilder {
94974
95081
  const [x2, y2] = crossPointForStation(this.axisValue, station.center);
94975
95082
  return station.profile.sketch.translate(x2, y2);
94976
95083
  });
94977
- const heights = this.stationsValue.map((station) => axisPosition(this.axisValue, station.center));
95084
+ const heights = this.stationsValue.map((station) => axisPosition$1(this.axisValue, station.center));
94978
95085
  let shape = loft(localProfiles, heights, { edgeLength: this.edgeLengthValue });
94979
- shape = orientLoftToAxis(shape, this.axisValue);
95086
+ shape = orientLoftToAxis$1(shape, this.axisValue);
94980
95087
  if (this.colorValue) shape = shape.color(this.colorValue);
94981
95088
  shape = applyMaterial(shape, this.materialValue).as(this.name);
94982
95089
  const warnings = [];
@@ -95635,7 +95742,7 @@ function requirePositive$3(value, label) {
95635
95742
  function clamp$4(value, min2, max2) {
95636
95743
  return Math.max(min2, Math.min(max2, value));
95637
95744
  }
95638
- function lerp$1(a2, b, t) {
95745
+ function lerp$4(a2, b, t) {
95639
95746
  return a2 + (b - a2) * t;
95640
95747
  }
95641
95748
  function add(a2, b) {
@@ -95685,19 +95792,19 @@ function transformLocal(point2, tangentAcross, normal, tangentAlong, x2, y2, z2
95685
95792
  function interpolateCylinder(a2, b, t, mode) {
95686
95793
  let delta = b.angle - a2.angle;
95687
95794
  if (mode === "shortest" && Math.abs(delta) > 180) delta -= Math.sign(delta) * 360;
95688
- return { kind: "cylinder", angle: a2.angle + delta * t, z: lerp$1(a2.z, b.z, t), offset: lerp$1(a2.offset ?? 0, b.offset ?? 0, t) };
95795
+ return { kind: "cylinder", angle: a2.angle + delta * t, z: lerp$4(a2.z, b.z, t), offset: lerp$4(a2.offset ?? 0, b.offset ?? 0, t) };
95689
95796
  }
95690
95797
  function interpolatePlane(a2, b, t) {
95691
- return { kind: "plane", x: lerp$1(a2.x, b.x, t), y: lerp$1(a2.y, b.y, t), offset: lerp$1(a2.offset ?? 0, b.offset ?? 0, t) };
95798
+ return { kind: "plane", x: lerp$4(a2.x, b.x, t), y: lerp$4(a2.y, b.y, t), offset: lerp$4(a2.offset ?? 0, b.offset ?? 0, t) };
95692
95799
  }
95693
95800
  function interpolateProductSkin(a2, b, t) {
95694
95801
  if ((a2.side ?? b.side) !== (b.side ?? a2.side)) throw new Error("SurfacePath on ProductSkin currently supports one side per path; split side transitions into separate members.");
95695
95802
  return {
95696
95803
  kind: "productSkin",
95697
95804
  side: a2.side ?? b.side,
95698
- u: lerp$1(a2.u ?? 0.5, b.u ?? 0.5, t),
95699
- v: lerp$1(a2.v ?? 0.5, b.v ?? 0.5, t),
95700
- offset: lerp$1(a2.offset ?? 0, b.offset ?? 0, t)
95805
+ u: lerp$4(a2.u ?? 0.5, b.u ?? 0.5, t),
95806
+ v: lerp$4(a2.v ?? 0.5, b.v ?? 0.5, t),
95807
+ offset: lerp$4(a2.offset ?? 0, b.offset ?? 0, t)
95701
95808
  };
95702
95809
  }
95703
95810
  class SurfacePath {
@@ -96020,11 +96127,11 @@ function coordinateOnSide(coordinate, side, label) {
96020
96127
  return { ...coordinate, kind: "productSkin", side };
96021
96128
  }
96022
96129
  class ProductSkinCarrier {
96023
- constructor(skin, name = skin.name, sideValue, offsetValue = 0) {
96130
+ constructor(skin, name = skin.name, sideValue2, offsetValue = 0) {
96024
96131
  __publicField(this, "kind", "productSkin");
96025
96132
  this.skin = skin;
96026
96133
  this.name = name;
96027
- this.sideValue = sideValue;
96134
+ this.sideValue = sideValue2;
96028
96135
  this.offsetValue = offsetValue;
96029
96136
  }
96030
96137
  surface(side) {
@@ -96795,7 +96902,7 @@ function counterboresForPlate(spec2, width, height, thickness, diagnostics) {
96795
96902
  function minWidthAcrossAlongRange(widthAtT, length4, minAlong, maxAlong) {
96796
96903
  let minWidth = Number.POSITIVE_INFINITY;
96797
96904
  for (let index2 = 0; index2 <= 8; index2 += 1) {
96798
- const along = lerp$1(minAlong, maxAlong, index2 / 8);
96905
+ const along = lerp$4(minAlong, maxAlong, index2 / 8);
96799
96906
  const t = Math.max(0, Math.min(1, (along + length4 / 2) / Math.max(length4, 1e-8)));
96800
96907
  minWidth = Math.min(minWidth, widthAtT(t));
96801
96908
  }
@@ -97095,7 +97202,7 @@ function pathParameterAtDistance(samples, distance2) {
97095
97202
  const segmentLength = Math.hypot(b.point[0] - a2.point[0], b.point[1] - a2.point[1], b.point[2] - a2.point[2]);
97096
97203
  if (traveled + segmentLength >= distance2) {
97097
97204
  const localT = segmentLength <= 1e-8 ? 0 : (distance2 - traveled) / segmentLength;
97098
- return lerp$1(a2.t, b.t, localT);
97205
+ return lerp$4(a2.t, b.t, localT);
97099
97206
  }
97100
97207
  traveled += segmentLength;
97101
97208
  }
@@ -97148,7 +97255,7 @@ function compileBandFootprintMesh(path2, input) {
97148
97255
  const width = input.widthAt(t);
97149
97256
  const along = distance2 - length4 / 2;
97150
97257
  for (let acrossIndex = 0; acrossIndex <= acrossSegments; acrossIndex += 1) {
97151
- const across = lerp$1(-width / 2, width / 2, acrossIndex / acrossSegments);
97258
+ const across = lerp$4(-width / 2, width / 2, acrossIndex / acrossSegments);
97152
97259
  mesh.vertices.push(pointAtProfile([across, along], false));
97153
97260
  }
97154
97261
  }
@@ -97158,7 +97265,7 @@ function compileBandFootprintMesh(path2, input) {
97158
97265
  const width = input.widthAt(t);
97159
97266
  const along = distance2 - length4 / 2;
97160
97267
  for (let acrossIndex = 0; acrossIndex <= acrossSegments; acrossIndex += 1) {
97161
- const across = lerp$1(-width / 2, width / 2, acrossIndex / acrossSegments);
97268
+ const across = lerp$4(-width / 2, width / 2, acrossIndex / acrossSegments);
97162
97269
  mesh.vertices.push(pointAtProfile([across, along], true));
97163
97270
  }
97164
97271
  }
@@ -97170,7 +97277,7 @@ function compileBandFootprintMesh(path2, input) {
97170
97277
  const width = input.widthAt(t);
97171
97278
  const along = distance2 - length4 / 2;
97172
97279
  for (let acrossIndex = 0; acrossIndex < acrossSegments; acrossIndex += 1) {
97173
- const across = lerp$1(-width / 2, width / 2, (acrossIndex + 0.5) / acrossSegments);
97280
+ const across = lerp$4(-width / 2, width / 2, (acrossIndex + 0.5) / acrossSegments);
97174
97281
  filled[alongIndex][acrossIndex] = !holes.some((hole2) => pointInProfileLoop([across, along], hole2));
97175
97282
  }
97176
97283
  }
@@ -99348,7 +99455,7 @@ const Constraint = {
99348
99455
  return builder.constrain({ type: "length", line: resolveLineId(builder, line2), value });
99349
99456
  }
99350
99457
  };
99351
- function requireVec3(v, label) {
99458
+ function requireVec3$1(v, label) {
99352
99459
  if (!Array.isArray(v) || v.length !== 3 || !Number.isFinite(v[0]) || !Number.isFinite(v[1]) || !Number.isFinite(v[2])) {
99353
99460
  throw new Error(`${label} must be a [number, number, number] with finite values, got ${JSON.stringify(v)}`);
99354
99461
  }
@@ -99361,24 +99468,24 @@ function requireFiniteNumber(n, label) {
99361
99468
  return n;
99362
99469
  }
99363
99470
  function distance$1(a2, b) {
99364
- requireVec3(a2, "a");
99365
- requireVec3(b, "b");
99471
+ requireVec3$1(a2, "a");
99472
+ requireVec3$1(b, "b");
99366
99473
  return Math.hypot(b[0] - a2[0], b[1] - a2[1], b[2] - a2[2]);
99367
99474
  }
99368
99475
  function midpoint$1(a2, b) {
99369
- requireVec3(a2, "a");
99370
- requireVec3(b, "b");
99476
+ requireVec3$1(a2, "a");
99477
+ requireVec3$1(b, "b");
99371
99478
  return [(a2[0] + b[0]) / 2, (a2[1] + b[1]) / 2, (a2[2] + b[2]) / 2];
99372
99479
  }
99373
- function lerp(a2, b, t) {
99374
- requireVec3(a2, "a");
99375
- requireVec3(b, "b");
99480
+ function lerp$3(a2, b, t) {
99481
+ requireVec3$1(a2, "a");
99482
+ requireVec3$1(b, "b");
99376
99483
  requireFiniteNumber(t, "t");
99377
99484
  return [a2[0] + (b[0] - a2[0]) * t, a2[1] + (b[1] - a2[1]) * t, a2[2] + (b[2] - a2[2]) * t];
99378
99485
  }
99379
99486
  function direction(a2, b) {
99380
- requireVec3(a2, "a");
99381
- requireVec3(b, "b");
99487
+ requireVec3$1(a2, "a");
99488
+ requireVec3$1(b, "b");
99382
99489
  const dx = b[0] - a2[0];
99383
99490
  const dy = b[1] - a2[1];
99384
99491
  const dz = b[2] - a2[2];
@@ -99389,8 +99496,8 @@ function direction(a2, b) {
99389
99496
  return [dx / len2, dy / len2, dz / len2];
99390
99497
  }
99391
99498
  function offset(point2, dir, amount) {
99392
- requireVec3(point2, "point");
99393
- requireVec3(dir, "dir");
99499
+ requireVec3$1(point2, "point");
99500
+ requireVec3$1(dir, "dir");
99394
99501
  requireFiniteNumber(amount, "amount");
99395
99502
  return [point2[0] + dir[0] * amount, point2[1] + dir[1] * amount, point2[2] + dir[2] * amount];
99396
99503
  }
@@ -99400,7 +99507,7 @@ const Points2 = {
99400
99507
  /** Center point between two 3D points. */
99401
99508
  midpoint: midpoint$1,
99402
99509
  /** Linearly interpolate between two 3D points. t=0 returns a, t=1 returns b. */
99403
- lerp,
99510
+ lerp: lerp$3,
99404
99511
  /** Unit direction vector from a to b. Throws if a and b are the same point. */
99405
99512
  direction,
99406
99513
  /** Move a point along a direction vector by a given amount. */
@@ -104532,9 +104639,84 @@ class ConstraintSketch extends Sketch {
104532
104639
  * Select the single arrangement region that contains the given seed point.
104533
104640
  * Throws if no region contains the seed.
104534
104641
  */
104535
- detectArrangementRegion(seed) {
104642
+ detectArrangementRegion(_seed2) {
104536
104643
  throw new Error("Not implemented");
104537
104644
  }
104645
+ /**
104646
+ * Return the solved constrained path as a sampled 2D polyline.
104647
+ *
104648
+ * Use this when a construction rail was authored with `constrainedSketch()`
104649
+ * and should feed another operation such as `Loft.pathOnXz(...)`.
104650
+ * The sketch must contain exactly one profile path.
104651
+ *
104652
+ * @param samples - Samples per curved segment. Default 32.
104653
+ * @returns The solved path as an open polyline.
104654
+ */
104655
+ toPolyline(samples = 32) {
104656
+ if (!Number.isFinite(samples) || samples < 2) throw new Error("ConstraintSketch.toPolyline() samples must be at least 2");
104657
+ const profileLoops = this.definition.loops.filter((loop) => loop.type === "profile");
104658
+ if (profileLoops.length !== 1) {
104659
+ throw new Error("ConstraintSketch.toPolyline() requires exactly one profile path");
104660
+ }
104661
+ const sampleCount = Math.max(2, Math.round(samples));
104662
+ const pointMap = new Map(this.definition.points.map((point2) => [point2.id, point2]));
104663
+ const lineMap = new Map(this.definition.lines.map((line2) => [line2.id, line2]));
104664
+ const arcMap = new Map(this.definition.arcs.map((arc) => [arc.id, arc]));
104665
+ const bezierMap = new Map(this.definition.beziers.map((bezier) => [bezier.id, bezier]));
104666
+ const points = [];
104667
+ const appendStart = (point2, label) => {
104668
+ const previous = points[points.length - 1];
104669
+ if (!previous) {
104670
+ points.push(point2);
104671
+ return;
104672
+ }
104673
+ if (Math.hypot(point2[0] - previous[0], point2[1] - previous[1]) > 1e-6) {
104674
+ throw new Error(`ConstraintSketch.toPolyline() profile path is not continuous at ${label}`);
104675
+ }
104676
+ };
104677
+ const appendPoint = (point2) => {
104678
+ const previous = points[points.length - 1];
104679
+ if (!previous || Math.hypot(point2[0] - previous[0], point2[1] - previous[1]) > 1e-9) points.push(point2);
104680
+ };
104681
+ const requirePoint = (id, label) => {
104682
+ const point2 = pointMap.get(id);
104683
+ if (!point2) throw new Error(`ConstraintSketch.toPolyline() missing ${label}`);
104684
+ return [point2.x, point2.y];
104685
+ };
104686
+ for (const segment of profileLoops[0].segments) {
104687
+ if (segment.kind === "line") {
104688
+ const line2 = lineMap.get(segment.line);
104689
+ if (!line2) throw new Error(`ConstraintSketch.toPolyline() missing line "${segment.line}"`);
104690
+ appendStart(requirePoint(line2.a, `line "${segment.line}" start point`), `line "${segment.line}"`);
104691
+ appendPoint(requirePoint(line2.b, `line "${segment.line}" end point`));
104692
+ } else if (segment.kind === "arc") {
104693
+ const arc = arcMap.get(segment.arc);
104694
+ if (!arc) throw new Error(`ConstraintSketch.toPolyline() missing arc "${segment.arc}"`);
104695
+ const center = requirePoint(arc.center, `arc "${segment.arc}" center point`);
104696
+ const start = requirePoint(arc.start, `arc "${segment.arc}" start point`);
104697
+ const end = requirePoint(arc.end, `arc "${segment.arc}" end point`);
104698
+ appendStart(start, `arc "${segment.arc}"`);
104699
+ const startAngle = Math.atan2(start[1] - center[1], start[0] - center[0]);
104700
+ const endAngle = Math.atan2(end[1] - center[1], end[0] - center[0]);
104701
+ for (const point2 of tessellateArc(center[0], center[1], arc.radius, startAngle, endAngle, arc.clockwise, sampleCount)) {
104702
+ appendPoint(point2);
104703
+ }
104704
+ } else {
104705
+ const bezier = bezierMap.get(segment.bezier);
104706
+ if (!bezier) throw new Error(`ConstraintSketch.toPolyline() missing bezier "${segment.bezier}"`);
104707
+ const p0 = requirePoint(bezier.p0, `bezier "${segment.bezier}" start point`);
104708
+ const p1 = requirePoint(bezier.p1, `bezier "${segment.bezier}" first control point`);
104709
+ const p2 = requirePoint(bezier.p2, `bezier "${segment.bezier}" second control point`);
104710
+ const p3 = requirePoint(bezier.p3, `bezier "${segment.bezier}" end point`);
104711
+ appendStart(p0, `bezier "${segment.bezier}"`);
104712
+ for (const point2 of tessellateBezier(p0[0], p0[1], p1[0], p1[1], p2[0], p2[1], p3[0], p3[1], sampleCount)) {
104713
+ appendPoint(point2);
104714
+ }
104715
+ }
104716
+ }
104717
+ if (points.length < 2) throw new Error("ConstraintSketch.toPolyline() needs at least 2 points");
104718
+ return points;
104719
+ }
104538
104720
  /**
104539
104721
  * Re-solve the sketch after changing the value of one existing constraint.
104540
104722
  *
@@ -107224,7 +107406,7 @@ function chamferTrackedEdge(shape, edge, size, quadrant = [-1, -1]) {
107224
107406
  );
107225
107407
  return buildEdgeFeatureResult(target, plan, "chamfer");
107226
107408
  }
107227
- const EPSILON = 1e-8;
107409
+ const EPSILON$1 = 1e-8;
107228
107410
  function toTuple(point2) {
107229
107411
  return Array.isArray(point2) ? [point2[0], point2[1]] : [point2.x, point2.y];
107230
107412
  }
@@ -107235,7 +107417,7 @@ function distance(a2, b) {
107235
107417
  }
107236
107418
  function normalize(vx, vy) {
107237
107419
  const len2 = Math.hypot(vx, vy);
107238
- if (len2 <= EPSILON) throw new Error("filletCorners requires non-degenerate edges");
107420
+ if (len2 <= EPSILON$1) throw new Error("filletCorners requires non-degenerate edges");
107239
107421
  return [vx / len2, vy / len2];
107240
107422
  }
107241
107423
  function clamp$3(value, min2, max2) {
@@ -107272,19 +107454,19 @@ function buildCornerGeometry(points, spec2, winding) {
107272
107454
  const [inDirX, inDirY] = normalize(current[0] - prev[0], current[1] - prev[1]);
107273
107455
  const [outDirX, outDirY] = normalize(next[0] - current[0], next[1] - current[1]);
107274
107456
  const turn = inDirX * outDirY - inDirY * outDirX;
107275
- const isConvex = turn * winding > EPSILON;
107276
- const isConcave = turn * winding < -EPSILON;
107457
+ const isConvex = turn * winding > EPSILON$1;
107458
+ const isConcave = turn * winding < -EPSILON$1;
107277
107459
  if (!isConvex && !isConcave) {
107278
107460
  throw new Error(`filletCorners corner ${spec2.index} is collinear; cannot fillet a straight edge`);
107279
107461
  }
107280
107462
  const toPrev = [-inDirX, -inDirY];
107281
107463
  const toNext = [outDirX, outDirY];
107282
107464
  const interiorAngle = Math.acos(clamp$3(toPrev[0] * toNext[0] + toPrev[1] * toNext[1], -1, 1));
107283
- if (interiorAngle <= EPSILON || interiorAngle >= Math.PI - EPSILON) {
107465
+ if (interiorAngle <= EPSILON$1 || interiorAngle >= Math.PI - EPSILON$1) {
107284
107466
  throw new Error(`filletCorners corner ${spec2.index} has an unsupported angle`);
107285
107467
  }
107286
107468
  const tangentDistance = spec2.radius / Math.tan(interiorAngle / 2);
107287
- if (tangentDistance >= inLength - EPSILON || tangentDistance >= outLength - EPSILON) {
107469
+ if (tangentDistance >= inLength - EPSILON$1 || tangentDistance >= outLength - EPSILON$1) {
107288
107470
  const maxRadius = Math.min(inLength, outLength) * Math.tan(interiorAngle / 2);
107289
107471
  throw new Error(`filletCorners radius ${spec2.radius} is too large for corner ${spec2.index}; max is ${maxRadius.toFixed(3)}`);
107290
107472
  }
@@ -107314,7 +107496,7 @@ function filletCorners(points, corners) {
107314
107496
  if (corners.length === 0) return polygon(points);
107315
107497
  const tuples = points.map(toTuple);
107316
107498
  const area2 = signedArea$3(tuples);
107317
- if (Math.abs(area2) <= EPSILON) throw new Error("filletCorners requires a non-degenerate polygon");
107499
+ if (Math.abs(area2) <= EPSILON$1) throw new Error("filletCorners requires a non-degenerate polygon");
107318
107500
  const winding = Math.sign(area2);
107319
107501
  const geometryByIndex = /* @__PURE__ */ new Map();
107320
107502
  for (const spec2 of corners) {
@@ -107328,7 +107510,7 @@ function filletCorners(points, corners) {
107328
107510
  const edgeLength2 = distance(tuples[i], tuples[nextIndex]);
107329
107511
  const exitDistance = ((_a3 = geometryByIndex.get(i)) == null ? void 0 : _a3.tangentDistance) ?? 0;
107330
107512
  const entryDistance = ((_b3 = geometryByIndex.get(nextIndex)) == null ? void 0 : _b3.tangentDistance) ?? 0;
107331
- if (exitDistance + entryDistance >= edgeLength2 - EPSILON) {
107513
+ if (exitDistance + entryDistance >= edgeLength2 - EPSILON$1) {
107332
107514
  throw new Error(`filletCorners adjacent fillets overlap on edge ${i} -> ${nextIndex}; reduce one of the radii`);
107333
107515
  }
107334
107516
  }
@@ -119832,6 +120014,295 @@ function polygonVertices(sides, radius, options) {
119832
120014
  centerY: options == null ? void 0 : options.centerY
119833
120015
  });
119834
120016
  }
120017
+ const LOFT_GUIDE_EPS = 1e-8;
120018
+ function orientLoftToAxis(shape, axis) {
120019
+ if (axis === "Z") return shape;
120020
+ if (axis === "Y") return shape.rotateX(-90);
120021
+ return shape.transform([0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1]);
120022
+ }
120023
+ function buildRailEvaluators(rails, axis, start, end, railSamples) {
120024
+ const seen2 = /* @__PURE__ */ new Set();
120025
+ return rails.map((rail2) => {
120026
+ if (seen2.has(rail2.side)) throw new Error(`Loft.withGuideRails() received more than one ${rail2.side} rail`);
120027
+ seen2.add(rail2.side);
120028
+ const sampled = sampleRailPath(rail2.path, railSamples);
120029
+ if (sampled.length < 2) throw new Error("Loft guide rails require at least two points");
120030
+ const points = sampled.map((point2) => ({ position: axisPosition(axis, point2), cross: crossPointForAxis(axis, point2) }));
120031
+ const ordered = points[points.length - 1].position >= points[0].position ? points : [...points].reverse();
120032
+ validateRailCoverage(ordered, start, end);
120033
+ return { side: rail2.side, points: ordered };
120034
+ });
120035
+ }
120036
+ function railCrossAt(rail2, position) {
120037
+ const points = rail2.points;
120038
+ if (position <= points[0].position + LOFT_GUIDE_EPS) return points[0].cross;
120039
+ const last = points[points.length - 1];
120040
+ if (position >= last.position - LOFT_GUIDE_EPS) return last.cross;
120041
+ for (let index2 = 0; index2 < points.length - 1; index2 += 1) {
120042
+ const a2 = points[index2];
120043
+ const b = points[index2 + 1];
120044
+ if (position >= a2.position - LOFT_GUIDE_EPS && position <= b.position + LOFT_GUIDE_EPS) {
120045
+ const t = (position - a2.position) / (b.position - a2.position);
120046
+ return [lerp$2(a2.cross[0], b.cross[0], t), lerp$2(a2.cross[1], b.cross[1], t)];
120047
+ }
120048
+ }
120049
+ throw new Error("Loft guide rail does not cover requested station position");
120050
+ }
120051
+ function validateRailCoverage(points, start, end) {
120052
+ for (let index2 = 1; index2 < points.length; index2 += 1) {
120053
+ if (points[index2].position - points[index2 - 1].position < LOFT_GUIDE_EPS) {
120054
+ throw new Error("Loft guide rails must be monotone along the loft axis");
120055
+ }
120056
+ }
120057
+ if (points[0].position - start > LOFT_GUIDE_EPS || end - points[points.length - 1].position > LOFT_GUIDE_EPS) {
120058
+ throw new Error("Loft guide rails must cover the full station range");
120059
+ }
120060
+ }
120061
+ function sampleRailPath(path2, samples) {
120062
+ if (Array.isArray(path2)) return path2.map((point2, index2) => requireVec3(point2, `Loft guide rail point ${index2}`));
120063
+ if (path2 instanceof Curve3D || path2 instanceof HermiteCurve3D || path2 instanceof QuinticHermiteCurve3D || path2 instanceof NurbsCurve3D) {
120064
+ return path2.sample(Math.max(2, Math.round(samples))).map((point2, index2) => requireVec3(point2, `Loft guide rail sample ${index2}`));
120065
+ }
120066
+ throw new Error("Loft guide rail path must be a Vec3[] or ForgeCAD 3D curve");
120067
+ }
120068
+ function requireVec3(point2, label) {
120069
+ if (!Array.isArray(point2) || point2.length !== 3 || !point2.every(Number.isFinite)) {
120070
+ throw new Error(`${label} must be a finite [x, y, z] point`);
120071
+ }
120072
+ return [point2[0], point2[1], point2[2]];
120073
+ }
120074
+ function axisPosition(axis, point2) {
120075
+ if (axis === "X") return point2[0];
120076
+ if (axis === "Y") return point2[1];
120077
+ return point2[2];
120078
+ }
120079
+ function crossPointForAxis(axis, point2) {
120080
+ if (axis === "X") return [point2[1], point2[2]];
120081
+ if (axis === "Y") return [point2[0], -point2[2]];
120082
+ return [point2[0], point2[1]];
120083
+ }
120084
+ function lerp$2(a2, b, t) {
120085
+ return a2 + (b - a2) * t;
120086
+ }
120087
+ function loftWithGuideRails(stations, rails, options = {}) {
120088
+ if (stations.length < 2) throw new Error("Loft.withGuideRails() requires at least two stations");
120089
+ if (rails.length === 0) throw new Error("Loft.withGuideRails() requires at least one guide rail");
120090
+ const sortedStations = sortedValidStations(stations);
120091
+ const axis = options.axis ?? "Z";
120092
+ const start = sortedStations[0].position;
120093
+ const end = sortedStations[sortedStations.length - 1].position;
120094
+ const railEvaluators = buildRailEvaluators(rails, axis, start, end, options.railSamples ?? 64);
120095
+ const positions = generatedPositions(sortedStations, options.samples);
120096
+ const profiles2 = positions.map((position) => {
120097
+ const source = profileForPosition(sortedStations, position);
120098
+ const bounds = boundsForPosition(sortedStations, position);
120099
+ return fitProfileToBounds(source, applyRailsToBounds(bounds, railEvaluators, position));
120100
+ });
120101
+ const shape = loft(profiles2, positions, {
120102
+ edgeLength: options.edgeLength,
120103
+ boundsPadding: options.boundsPadding
120104
+ });
120105
+ return orientLoftToAxis(shape, axis);
120106
+ }
120107
+ function sortedValidStations(stations) {
120108
+ const sorted = [...stations].sort((a2, b) => a2.position - b.position);
120109
+ for (let index2 = 0; index2 < sorted.length; index2 += 1) {
120110
+ if (!Number.isFinite(sorted[index2].position)) throw new Error("Loft.withGuideRails station position must be finite");
120111
+ if (!(sorted[index2].profile instanceof Sketch)) throw new Error("Loft.withGuideRails() stations must use Sketch profiles");
120112
+ if (index2 > 0 && sorted[index2].position - sorted[index2 - 1].position < LOFT_GUIDE_EPS) {
120113
+ throw new Error("Loft.withGuideRails() requires unique, strictly increasing station positions");
120114
+ }
120115
+ }
120116
+ return sorted;
120117
+ }
120118
+ function generatedPositions(stations, samples) {
120119
+ const count = Math.max(2, Math.round(samples ?? Math.max(9, (stations.length - 1) * 8 + 1)));
120120
+ const start = stations[0].position;
120121
+ const end = stations[stations.length - 1].position;
120122
+ const values = /* @__PURE__ */ new Set();
120123
+ const positions = [];
120124
+ const addPosition = (position) => {
120125
+ const key = position.toFixed(9);
120126
+ if (!values.has(key)) {
120127
+ values.add(key);
120128
+ positions.push(position);
120129
+ }
120130
+ };
120131
+ for (let index2 = 0; index2 < count; index2 += 1) addPosition(start + (end - start) * index2 / (count - 1));
120132
+ for (const station of stations) addPosition(station.position);
120133
+ return positions.sort((a2, b) => a2 - b);
120134
+ }
120135
+ function profileForPosition(stations, position) {
120136
+ for (let index2 = 0; index2 < stations.length - 1; index2 += 1) {
120137
+ if (position <= stations[index2 + 1].position + LOFT_GUIDE_EPS) return stations[index2].profile;
120138
+ }
120139
+ return stations[stations.length - 1].profile;
120140
+ }
120141
+ function boundsForPosition(stations, position) {
120142
+ if (position <= stations[0].position + LOFT_GUIDE_EPS) return sketchBounds(stations[0].profile);
120143
+ const last = stations[stations.length - 1];
120144
+ if (position >= last.position - LOFT_GUIDE_EPS) return sketchBounds(last.profile);
120145
+ for (let index2 = 0; index2 < stations.length - 1; index2 += 1) {
120146
+ const a2 = stations[index2];
120147
+ const b = stations[index2 + 1];
120148
+ if (position >= a2.position - LOFT_GUIDE_EPS && position <= b.position + LOFT_GUIDE_EPS) {
120149
+ return lerpBounds(sketchBounds(a2.profile), sketchBounds(b.profile), (position - a2.position) / (b.position - a2.position));
120150
+ }
120151
+ }
120152
+ return sketchBounds(last.profile);
120153
+ }
120154
+ function applyRailsToBounds(bounds, rails, position) {
120155
+ const centerRail = rails.find((rail2) => rail2.side === "center");
120156
+ const center = centerRail ? railCrossAt(centerRail, position) : void 0;
120157
+ const next = { ...bounds };
120158
+ applyAxisRail(next, "X", sideValue(rails, "left", position, 0), sideValue(rails, "right", position, 0), center == null ? void 0 : center[0]);
120159
+ applyAxisRail(next, "Y", sideValue(rails, "back", position, 1), sideValue(rails, "front", position, 1), center == null ? void 0 : center[1]);
120160
+ if (next.maxX - next.minX < LOFT_GUIDE_EPS || next.maxY - next.minY < LOFT_GUIDE_EPS) {
120161
+ throw new Error("Loft.withGuideRails() guide rails produced a non-positive section size");
120162
+ }
120163
+ return next;
120164
+ }
120165
+ function sideValue(rails, side, position, crossIndex) {
120166
+ const rail2 = rails.find((entry) => entry.side === side);
120167
+ return rail2 ? railCrossAt(rail2, position)[crossIndex] : void 0;
120168
+ }
120169
+ function applyAxisRail(bounds, axis, minRail, maxRail, center) {
120170
+ const minKey = axis === "X" ? "minX" : "minY";
120171
+ const maxKey = axis === "X" ? "maxX" : "maxY";
120172
+ const width = bounds[maxKey] - bounds[minKey];
120173
+ if (minRail != null && maxRail != null) {
120174
+ if (maxRail - minRail < LOFT_GUIDE_EPS) throw new Error("Loft.withGuideRails() opposite guide rails crossed");
120175
+ if (center != null && Math.abs((minRail + maxRail) / 2 - center) > 1e-5) {
120176
+ throw new Error("Loft.withGuideRails() center rail conflicts with opposite side rails");
120177
+ }
120178
+ bounds[minKey] = minRail;
120179
+ bounds[maxKey] = maxRail;
120180
+ } else if (maxRail != null) {
120181
+ bounds[maxKey] = maxRail;
120182
+ bounds[minKey] = center != null ? 2 * center - maxRail : maxRail - width;
120183
+ } else if (minRail != null) {
120184
+ bounds[minKey] = minRail;
120185
+ bounds[maxKey] = center != null ? 2 * center - minRail : minRail + width;
120186
+ } else if (center != null) {
120187
+ bounds[minKey] = center - width / 2;
120188
+ bounds[maxKey] = center + width / 2;
120189
+ }
120190
+ }
120191
+ function fitProfileToBounds(profile, target) {
120192
+ const source = sketchBounds(profile);
120193
+ const sourceWidth = source.maxX - source.minX;
120194
+ const sourceDepth = source.maxY - source.minY;
120195
+ if (sourceWidth < LOFT_GUIDE_EPS || sourceDepth < LOFT_GUIDE_EPS) {
120196
+ throw new Error("Loft.withGuideRails() station profiles must have positive bounds");
120197
+ }
120198
+ const sourceCenter = [(source.minX + source.maxX) / 2, (source.minY + source.maxY) / 2];
120199
+ const targetCenter = [(target.minX + target.maxX) / 2, (target.minY + target.maxY) / 2];
120200
+ return profile.scaleAround(sourceCenter, [(target.maxX - target.minX) / sourceWidth, (target.maxY - target.minY) / sourceDepth]).translate(targetCenter[0] - sourceCenter[0], targetCenter[1] - sourceCenter[1]);
120201
+ }
120202
+ function sketchBounds(profile) {
120203
+ const bounds = profile.bounds();
120204
+ return { minX: bounds.min[0], maxX: bounds.max[0], minY: bounds.min[1], maxY: bounds.max[1] };
120205
+ }
120206
+ function lerpBounds(a2, b, t) {
120207
+ return {
120208
+ minX: lerp$1(a2.minX, b.minX, t),
120209
+ maxX: lerp$1(a2.maxX, b.maxX, t),
120210
+ minY: lerp$1(a2.minY, b.minY, t),
120211
+ maxY: lerp$1(a2.maxY, b.maxY, t)
120212
+ };
120213
+ }
120214
+ function lerp$1(a2, b, t) {
120215
+ return a2 + (b - a2) * t;
120216
+ }
120217
+ function mapLoftPath2D(path2, label, mapper) {
120218
+ const points = sampleLoftPath2D(path2, label);
120219
+ return points.map((point2, index2) => {
120220
+ if (!Array.isArray(point2) || point2.length !== 2 || !point2.every(Number.isFinite)) {
120221
+ throw new Error(`${label} point ${index2} must be a finite [x, y] point`);
120222
+ }
120223
+ return mapper([point2[0], point2[1]]);
120224
+ });
120225
+ }
120226
+ function sampleLoftPath2D(path2, label) {
120227
+ if (Array.isArray(path2)) {
120228
+ if (path2.length < 2) throw new Error(`${label} requires at least two [x, y] points`);
120229
+ return path2;
120230
+ }
120231
+ if (!path2 || typeof path2 !== "object" || typeof path2.toPolyline !== "function") {
120232
+ throw new Error(`${label} requires a 2D path, solved constrained path, or [x, y] point array`);
120233
+ }
120234
+ const points = path2.toPolyline();
120235
+ if (!Array.isArray(points) || points.length < 2) throw new Error(`${label} path must produce at least two [x, y] points`);
120236
+ return points;
120237
+ }
120238
+ const Loft = {
120239
+ /** Create a loft station from a 2D profile and an axis position. */
120240
+ station(profile, position) {
120241
+ if (!Number.isFinite(position)) throw new Error("Loft.station position must be finite");
120242
+ return { profile, position };
120243
+ },
120244
+ /** Create a guide rail that constrains the section-local negative-X side. */
120245
+ leftRail(path2) {
120246
+ return { side: "left", path: path2 };
120247
+ },
120248
+ /** Create a guide rail that constrains the section-local positive-X side. */
120249
+ rightRail(path2) {
120250
+ return { side: "right", path: path2 };
120251
+ },
120252
+ /** Create a guide rail that constrains the section-local positive-Y side. */
120253
+ frontRail(path2) {
120254
+ return { side: "front", path: path2 };
120255
+ },
120256
+ /** Create a guide rail that constrains the section-local negative-Y side. */
120257
+ backRail(path2) {
120258
+ return { side: "back", path: path2 };
120259
+ },
120260
+ /** Create a guide rail that moves section centers along the loft. */
120261
+ centerRail(path2) {
120262
+ return { side: "center", path: path2 };
120263
+ },
120264
+ /**
120265
+ * Place a 2D guide path onto the XZ plane.
120266
+ *
120267
+ * The path's first coordinate becomes X and its second coordinate becomes Z.
120268
+ * Use this for left/right silhouette rails authored with `path()` or `constrainedSketch()`.
120269
+ */
120270
+ pathOnXz(path2, y2 = 0) {
120271
+ if (!Number.isFinite(y2)) throw new Error("Loft.pathOnXz y must be finite");
120272
+ return mapLoftPath2D(path2, "Loft.pathOnXz", ([x2, z2]) => [x2, y2, z2]);
120273
+ },
120274
+ /**
120275
+ * Place a 2D guide path onto the YZ plane.
120276
+ *
120277
+ * The path's first coordinate becomes Y and its second coordinate becomes Z.
120278
+ * Use this for front/back crown rails authored with `path()` or `constrainedSketch()`.
120279
+ */
120280
+ pathOnYz(path2, x2 = 0) {
120281
+ if (!Number.isFinite(x2)) throw new Error("Loft.pathOnYz x must be finite");
120282
+ return mapLoftPath2D(path2, "Loft.pathOnYz", ([y2, z2]) => [x2, y2, z2]);
120283
+ },
120284
+ /**
120285
+ * Place a 2D guide path onto the XY plane.
120286
+ *
120287
+ * The path's first coordinate becomes X and its second coordinate becomes Y.
120288
+ * Use this when lofting along X or Y and a rail lives in a horizontal sketch plane.
120289
+ */
120290
+ pathOnXy(path2, z2 = 0) {
120291
+ if (!Number.isFinite(z2)) throw new Error("Loft.pathOnXy z must be finite");
120292
+ return mapLoftPath2D(path2, "Loft.pathOnXy", ([x2, y2]) => [x2, y2, z2]);
120293
+ },
120294
+ /**
120295
+ * Loft through profile stations while forcing generated sections to follow guide rails.
120296
+ *
120297
+ * Stations define the cross-section family. Guide rails define the side or center
120298
+ * paths the loft must pass through. With opposite side rails, the section is scaled
120299
+ * to touch both rails. With one side rail, the section keeps its interpolated size
120300
+ * unless a center rail is also present.
120301
+ */
120302
+ withGuideRails(stations, rails, options = {}) {
120303
+ return loftWithGuideRails(stations, rails, options);
120304
+ }
120305
+ };
119835
120306
  let collectedHighlights = [];
119836
120307
  function resetHighlights() {
119837
120308
  collectedHighlights = [];
@@ -120504,7 +120975,7 @@ function resolveCornerYSelection(edges, options = {}) {
120504
120975
  for (const candidate of candidates) {
120505
120976
  const selectedEndpoints = [];
120506
120977
  let totalGap = 0;
120507
- let maxGap = 0;
120978
+ let maxGap2 = 0;
120508
120979
  for (const edge of edges) {
120509
120980
  const start = edgeEndpoint(edge, "start");
120510
120981
  const end = edgeEndpoint(edge, "end");
@@ -120514,10 +120985,10 @@ function resolveCornerYSelection(edges, options = {}) {
120514
120985
  const gap = Math.min(startGap, endGap);
120515
120986
  selectedEndpoints.push(selected);
120516
120987
  totalGap += gap;
120517
- maxGap = Math.max(maxGap, gap);
120988
+ maxGap2 = Math.max(maxGap2, gap);
120518
120989
  }
120519
- if (maxGap <= cornerTolerance && (!best || totalGap < best.totalGap)) {
120520
- best = { totalGap, maxGap, selectedEndpoints };
120990
+ if (maxGap2 <= cornerTolerance && (!best || totalGap < best.totalGap)) {
120991
+ best = { totalGap, maxGap: maxGap2, selectedEndpoints };
120521
120992
  }
120522
120993
  }
120523
120994
  if (!best) {
@@ -338491,6 +338962,7 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
338491
338962
  nurbsSurface,
338492
338963
  spline2d,
338493
338964
  spline3d,
338965
+ Loft,
338494
338966
  loft,
338495
338967
  loftAlongSpine,
338496
338968
  sweep,
@@ -338946,6 +339418,644 @@ function runScript(code, fileName = "main.forge.js", allFiles = {}, options = {}
338946
339418
  }
338947
339419
  }
338948
339420
  }
339421
+ const DEFAULT_COLLISION_INSPECTION_OPTIONS = {
339422
+ minOverlapVolume: 0.1
339423
+ };
339424
+ function cloneVec3$1(value) {
339425
+ return [value[0], value[1], value[2]];
339426
+ }
339427
+ function isIdentityTransform(matrix) {
339428
+ if (!matrix) return true;
339429
+ const identity = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
339430
+ return identity.every((value, index2) => Math.abs(matrix[index2] - value) <= 1e-12);
339431
+ }
339432
+ function transformPoint(matrix, point2) {
339433
+ const [x2, y2, z2] = point2;
339434
+ return [
339435
+ matrix[0] * x2 + matrix[4] * y2 + matrix[8] * z2 + matrix[12],
339436
+ matrix[1] * x2 + matrix[5] * y2 + matrix[9] * z2 + matrix[13],
339437
+ matrix[2] * x2 + matrix[6] * y2 + matrix[10] * z2 + matrix[14]
339438
+ ];
339439
+ }
339440
+ function transformBBox(min2, max2, matrix) {
339441
+ const corners = [
339442
+ [min2[0], min2[1], min2[2]],
339443
+ [min2[0], min2[1], max2[2]],
339444
+ [min2[0], max2[1], min2[2]],
339445
+ [min2[0], max2[1], max2[2]],
339446
+ [max2[0], min2[1], min2[2]],
339447
+ [max2[0], min2[1], max2[2]],
339448
+ [max2[0], max2[1], min2[2]],
339449
+ [max2[0], max2[1], max2[2]]
339450
+ ];
339451
+ const outMin = [Infinity, Infinity, Infinity];
339452
+ const outMax = [-Infinity, -Infinity, -Infinity];
339453
+ for (const corner of corners) {
339454
+ const transformed = transformPoint(matrix, corner);
339455
+ for (let axis = 0; axis < 3; axis += 1) {
339456
+ outMin[axis] = Math.min(outMin[axis], transformed[axis]);
339457
+ outMax[axis] = Math.max(outMax[axis], transformed[axis]);
339458
+ }
339459
+ }
339460
+ return { min: outMin, max: outMax };
339461
+ }
339462
+ function prepareEntry(entry) {
339463
+ if (isIdentityTransform(entry.transform)) {
339464
+ return {
339465
+ ...entry,
339466
+ min: cloneVec3$1(entry.min),
339467
+ max: cloneVec3$1(entry.max)
339468
+ };
339469
+ }
339470
+ const bbox = transformBBox(entry.min, entry.max, entry.transform);
339471
+ return {
339472
+ ...entry,
339473
+ shape: entry.shape.transform(entry.transform),
339474
+ min: bbox.min,
339475
+ max: bbox.max
339476
+ };
339477
+ }
339478
+ function bboxOverlaps(a2, b) {
339479
+ return [0, 1, 2].every((axis) => a2.min[axis] < b.max[axis] && a2.max[axis] > b.min[axis]);
339480
+ }
339481
+ function collisionId(a2, b) {
339482
+ return `${a2.id}__${b.id}`;
339483
+ }
339484
+ function estimateSweepPairCount$1(entries, axis) {
339485
+ const ordered = entries.map((entry) => ({ min: entry.min[axis], max: entry.max[axis] })).sort((a2, b) => a2.min - b.min || a2.max - b.max);
339486
+ const endValues = ordered.map((entry) => entry.max).sort((a2, b) => a2 - b);
339487
+ let expired = 0;
339488
+ let count = 0;
339489
+ for (let seen2 = 0; seen2 < ordered.length; seen2 += 1) {
339490
+ const currentMin = ordered[seen2].min;
339491
+ while (expired < seen2 && endValues[expired] <= currentMin) expired += 1;
339492
+ count += seen2 - expired;
339493
+ }
339494
+ return count;
339495
+ }
339496
+ function chooseSweepAxis$1(entries) {
339497
+ let bestAxis = 0;
339498
+ let bestCount = estimateSweepPairCount$1(entries, bestAxis);
339499
+ for (const axis of [1, 2]) {
339500
+ const count = estimateSweepPairCount$1(entries, axis);
339501
+ if (count < bestCount) {
339502
+ bestAxis = axis;
339503
+ bestCount = count;
339504
+ }
339505
+ }
339506
+ return bestAxis;
339507
+ }
339508
+ function collectCandidatePairs$1(entries) {
339509
+ if (entries.length < 2) return { pairs: [], bboxPairChecks: 0 };
339510
+ const axis = chooseSweepAxis$1(entries);
339511
+ const ordered = entries.map((entry, index2) => ({ entry, index: index2 })).sort((a2, b) => a2.entry.min[axis] - b.entry.min[axis] || a2.entry.max[axis] - b.entry.max[axis] || a2.index - b.index);
339512
+ let active = [];
339513
+ const pairs = [];
339514
+ let bboxPairChecks = 0;
339515
+ for (const current of ordered) {
339516
+ active = active.filter((candidate) => candidate.entry.max[axis] > current.entry.min[axis]);
339517
+ for (const candidate of active) {
339518
+ bboxPairChecks += 1;
339519
+ if (!bboxOverlaps(candidate.entry, current.entry)) continue;
339520
+ const sourceIndex = Math.min(candidate.index, current.index);
339521
+ const targetIndex = Math.max(candidate.index, current.index);
339522
+ pairs.push({ sourceIndex, targetIndex });
339523
+ }
339524
+ active.push(current);
339525
+ }
339526
+ pairs.sort((a2, b) => a2.sourceIndex - b.sourceIndex || a2.targetIndex - b.targetIndex);
339527
+ return { pairs, bboxPairChecks };
339528
+ }
339529
+ function serializeCollisionFinding(finding) {
339530
+ return {
339531
+ index: finding.index,
339532
+ id: finding.id,
339533
+ sourceIndex: finding.sourceIndex,
339534
+ targetIndex: finding.targetIndex,
339535
+ sourceId: finding.sourceId,
339536
+ targetId: finding.targetId,
339537
+ sourceName: finding.sourceName,
339538
+ targetName: finding.targetName,
339539
+ overlapVolume: finding.overlapVolume
339540
+ };
339541
+ }
339542
+ function analyzeCollisionIntersections(entries, rawOptions = {}) {
339543
+ const options = {
339544
+ minOverlapVolume: rawOptions.minOverlapVolume ?? DEFAULT_COLLISION_INSPECTION_OPTIONS.minOverlapVolume
339545
+ };
339546
+ const warnings = [];
339547
+ const collisions = [];
339548
+ const preparedEntries = entries.map((entry) => prepareEntry(entry));
339549
+ const { pairs: candidatePairs, bboxPairChecks } = collectCandidatePairs$1(preparedEntries);
339550
+ for (const pair of candidatePairs) {
339551
+ const a2 = preparedEntries[pair.sourceIndex];
339552
+ const b = preparedEntries[pair.targetIndex];
339553
+ try {
339554
+ const hit = a2.shape.intersect(b.shape);
339555
+ if (hit.isEmpty()) continue;
339556
+ const overlapVolume = hit.volume();
339557
+ if (!Number.isFinite(overlapVolume) || overlapVolume <= options.minOverlapVolume) continue;
339558
+ collisions.push({
339559
+ index: collisions.length + 1,
339560
+ id: collisionId(a2, b),
339561
+ sourceIndex: pair.sourceIndex,
339562
+ targetIndex: pair.targetIndex,
339563
+ sourceId: a2.id,
339564
+ targetId: b.id,
339565
+ sourceName: a2.name,
339566
+ targetName: b.name,
339567
+ overlapVolume,
339568
+ shape: hit
339569
+ });
339570
+ } catch (err2) {
339571
+ const message = err2 instanceof Error ? err2.message : String(err2);
339572
+ warnings.push(`Could not boolean-test ${a2.name} against ${b.name}: ${message}`);
339573
+ }
339574
+ }
339575
+ const objects = preparedEntries.map((entry, index2) => ({
339576
+ index: index2,
339577
+ id: entry.id,
339578
+ name: entry.name,
339579
+ groupName: entry.groupName,
339580
+ treePath: entry.treePath,
339581
+ mock: entry.mock === true,
339582
+ bbox: {
339583
+ min: cloneVec3$1(entry.min),
339584
+ max: cloneVec3$1(entry.max)
339585
+ }
339586
+ }));
339587
+ return {
339588
+ method: "boolean-intersection",
339589
+ options,
339590
+ broadphase: {
339591
+ method: "sweep-and-prune",
339592
+ allPairCount: preparedEntries.length * (preparedEntries.length - 1) / 2,
339593
+ bboxPairChecks,
339594
+ booleanPairChecks: candidatePairs.length
339595
+ },
339596
+ objectCount: objects.length,
339597
+ collisionCount: collisions.length,
339598
+ objects,
339599
+ collisions,
339600
+ warnings
339601
+ };
339602
+ }
339603
+ const DEFAULT_ROUGHNESS_INSPECTION_OPTIONS = {
339604
+ smoothAngleDeg: 5,
339605
+ sharpAngleDeg: 30,
339606
+ harshAngleDeg: 90
339607
+ };
339608
+ const ROUGHNESS_COLORS = {
339609
+ smooth: [62, 72, 84],
339610
+ moderate: [255, 214, 0],
339611
+ sharp: [255, 124, 34],
339612
+ harsh: [255, 42, 96]
339613
+ };
339614
+ function resolveRoughnessInspectionOptions(raw = {}) {
339615
+ const options = {
339616
+ smoothAngleDeg: raw.smoothAngleDeg ?? DEFAULT_ROUGHNESS_INSPECTION_OPTIONS.smoothAngleDeg,
339617
+ sharpAngleDeg: raw.sharpAngleDeg ?? DEFAULT_ROUGHNESS_INSPECTION_OPTIONS.sharpAngleDeg,
339618
+ harshAngleDeg: raw.harshAngleDeg ?? DEFAULT_ROUGHNESS_INSPECTION_OPTIONS.harshAngleDeg
339619
+ };
339620
+ if (!Number.isFinite(options.smoothAngleDeg) || options.smoothAngleDeg < 0) {
339621
+ throw new Error(`smoothAngleDeg must be a finite non-negative angle (got ${options.smoothAngleDeg}).`);
339622
+ }
339623
+ if (!Number.isFinite(options.sharpAngleDeg) || options.sharpAngleDeg <= options.smoothAngleDeg) {
339624
+ throw new Error(`sharpAngleDeg must be greater than smoothAngleDeg (got ${options.sharpAngleDeg}).`);
339625
+ }
339626
+ if (!Number.isFinite(options.harshAngleDeg) || options.harshAngleDeg <= options.sharpAngleDeg || options.harshAngleDeg > 180) {
339627
+ throw new Error(`harshAngleDeg must be greater than sharpAngleDeg and <= 180 (got ${options.harshAngleDeg}).`);
339628
+ }
339629
+ return options;
339630
+ }
339631
+ function roughnessClassForAngle(angleDeg, options) {
339632
+ if (angleDeg >= options.harshAngleDeg) return "harsh";
339633
+ if (angleDeg >= options.sharpAngleDeg) return "sharp";
339634
+ if (angleDeg >= options.smoothAngleDeg) return "moderate";
339635
+ return "smooth";
339636
+ }
339637
+ function roughnessScoreForAngle(angleDeg, options) {
339638
+ if (angleDeg < options.sharpAngleDeg) return 0;
339639
+ if (angleDeg < options.harshAngleDeg) {
339640
+ return MathUtils.lerp(0.48, 0.82, (angleDeg - options.sharpAngleDeg) / (options.harshAngleDeg - options.sharpAngleDeg));
339641
+ }
339642
+ return 1;
339643
+ }
339644
+ function roughnessColorForAngle(angleDeg, options) {
339645
+ const cls = roughnessClassForAngle(angleDeg, options);
339646
+ if (cls === "smooth" || cls === "harsh") return ROUGHNESS_COLORS[cls];
339647
+ if (cls === "moderate") {
339648
+ return lerpRgb(
339649
+ ROUGHNESS_COLORS.moderate,
339650
+ ROUGHNESS_COLORS.sharp,
339651
+ (angleDeg - options.smoothAngleDeg) / (options.sharpAngleDeg - options.smoothAngleDeg)
339652
+ );
339653
+ }
339654
+ return lerpRgb(
339655
+ ROUGHNESS_COLORS.sharp,
339656
+ ROUGHNESS_COLORS.harsh,
339657
+ (angleDeg - options.sharpAngleDeg) / (options.harshAngleDeg - options.sharpAngleDeg)
339658
+ );
339659
+ }
339660
+ function lerpRgb(a2, b, t) {
339661
+ const clamped = MathUtils.clamp(t, 0, 1);
339662
+ return [
339663
+ Math.round(MathUtils.lerp(a2[0], b[0], clamped)),
339664
+ Math.round(MathUtils.lerp(a2[1], b[1], clamped)),
339665
+ Math.round(MathUtils.lerp(a2[2], b[2], clamped))
339666
+ ];
339667
+ }
339668
+ function emptyRoughnessSummary() {
339669
+ return {
339670
+ triangleCount: 0,
339671
+ edgeCount: 0,
339672
+ boundaryEdgeCount: 0,
339673
+ nonManifoldEdgeCount: 0,
339674
+ smoothAreaPercent: 0,
339675
+ moderateAreaPercent: 0,
339676
+ sharpAreaPercent: 0,
339677
+ harshAreaPercent: 0,
339678
+ roughAreaPercent: 0,
339679
+ meanAngleDeg: null,
339680
+ p50AngleDeg: null,
339681
+ p90AngleDeg: null,
339682
+ p95AngleDeg: null,
339683
+ p99AngleDeg: null,
339684
+ maxAngleDeg: null,
339685
+ qualityScore: 0
339686
+ };
339687
+ }
339688
+ function summarizeRoughnessTriangles(triangles, edgeAngles, edgeCount, boundaryEdgeCount, nonManifoldEdgeCount, options) {
339689
+ const areaByClass = {
339690
+ smooth: 0,
339691
+ moderate: 0,
339692
+ sharp: 0,
339693
+ harsh: 0
339694
+ };
339695
+ let totalArea = 0;
339696
+ for (const tri of triangles) {
339697
+ const area2 = Number.isFinite(tri.area) ? tri.area : 0;
339698
+ totalArea += area2;
339699
+ areaByClass[roughnessClassForAngle(tri.maxAngleDeg, options)] += area2;
339700
+ }
339701
+ const sortedAngles = [...edgeAngles].sort((lhs, rhs) => lhs - rhs);
339702
+ const meanAngleDeg = sortedAngles.length > 0 ? Number((sortedAngles.reduce((sum2, angle) => sum2 + angle, 0) / sortedAngles.length).toFixed(2)) : null;
339703
+ const qualityScore = totalArea > 0 ? Math.round(
339704
+ MathUtils.clamp(
339705
+ 100 * (areaByClass.smooth + areaByClass.moderate * 0.9) / totalArea - 50 * areaByClass.harsh / totalArea,
339706
+ 0,
339707
+ 100
339708
+ )
339709
+ ) : 0;
339710
+ const safePercent = (value) => totalArea > 0 ? Number((value / totalArea * 100).toFixed(2)) : 0;
339711
+ return {
339712
+ triangleCount: triangles.length,
339713
+ edgeCount,
339714
+ boundaryEdgeCount,
339715
+ nonManifoldEdgeCount,
339716
+ smoothAreaPercent: safePercent(areaByClass.smooth),
339717
+ moderateAreaPercent: safePercent(areaByClass.moderate),
339718
+ sharpAreaPercent: safePercent(areaByClass.sharp),
339719
+ harshAreaPercent: safePercent(areaByClass.harsh),
339720
+ roughAreaPercent: safePercent(areaByClass.sharp + areaByClass.harsh),
339721
+ meanAngleDeg,
339722
+ p50AngleDeg: percentile(sortedAngles, 0.5),
339723
+ p90AngleDeg: percentile(sortedAngles, 0.9),
339724
+ p95AngleDeg: percentile(sortedAngles, 0.95),
339725
+ p99AngleDeg: percentile(sortedAngles, 0.99),
339726
+ maxAngleDeg: sortedAngles.length > 0 ? Number(sortedAngles[sortedAngles.length - 1].toFixed(2)) : null,
339727
+ qualityScore
339728
+ };
339729
+ }
339730
+ function percentile(sorted, q) {
339731
+ if (sorted.length === 0) return null;
339732
+ const index2 = MathUtils.clamp(Math.floor(sorted.length * q), 0, sorted.length - 1);
339733
+ return Number(sorted[index2].toFixed(2));
339734
+ }
339735
+ const DEG_PER_RAD = 180 / Math.PI;
339736
+ function analyzeRoughnessGeometry(sourceGeometry, rawOptions = {}) {
339737
+ const options = resolveRoughnessInspectionOptions(rawOptions);
339738
+ const geometry = sourceGeometry.index ? sourceGeometry.toNonIndexed() : sourceGeometry.clone();
339739
+ const position = geometry.getAttribute("position");
339740
+ const warnings = [];
339741
+ if (!position || position.count < 3) {
339742
+ return { geometry, summary: emptyRoughnessSummary(), warnings: ["No triangle geometry."] };
339743
+ }
339744
+ const triangleCount = Math.floor(position.count / 3);
339745
+ const normals = new Array(triangleCount);
339746
+ const triangles = new Array(triangleCount);
339747
+ const edges = /* @__PURE__ */ new Map();
339748
+ const colors = new Float32Array(position.count * 3);
339749
+ const scores = new Float32Array(position.count);
339750
+ const a2 = new Vector3();
339751
+ const b = new Vector3();
339752
+ const c2 = new Vector3();
339753
+ const ac = new Vector3();
339754
+ const normal = new Vector3();
339755
+ const bbox = new Box3().setFromBufferAttribute(position);
339756
+ const snap = Math.max(1e-6, bbox.getSize(new Vector3()).length() * 1e-8);
339757
+ for (let tri = 0; tri < triangleCount; tri += 1) {
339758
+ const offset2 = tri * 3;
339759
+ readVertex(position, offset2, a2);
339760
+ readVertex(position, offset2 + 1, b);
339761
+ readVertex(position, offset2 + 2, c2);
339762
+ normal.subVectors(b, a2).cross(ac.subVectors(c2, a2));
339763
+ const areaTwice = normal.length();
339764
+ triangles[tri] = { area: areaTwice * 0.5, maxAngleDeg: 0 };
339765
+ normals[tri] = areaTwice > 1e-12 ? normal.multiplyScalar(1 / areaTwice).clone() : new Vector3(0, 0, 1);
339766
+ const keys = [vertexKey(a2, snap), vertexKey(b, snap), vertexKey(c2, snap)];
339767
+ for (let edge = 0; edge < 3; edge += 1) {
339768
+ const key = edgeKey(keys[edge], keys[(edge + 1) % 3]);
339769
+ let record = edges.get(key);
339770
+ if (!record) {
339771
+ record = { triangles: [] };
339772
+ edges.set(key, record);
339773
+ }
339774
+ record.triangles.push(tri);
339775
+ }
339776
+ }
339777
+ const { boundaryEdgeCount, nonManifoldEdgeCount, edgeAngles } = markTriangleRoughness(edges, triangles, normals);
339778
+ if (boundaryEdgeCount > 0) warnings.push(`${boundaryEdgeCount} boundary edge(s) were treated as harsh roughness.`);
339779
+ if (nonManifoldEdgeCount > 0) warnings.push(`${nonManifoldEdgeCount} non-manifold edge(s) were treated as harsh roughness.`);
339780
+ for (let tri = 0; tri < triangleCount; tri += 1) {
339781
+ const { maxAngleDeg } = triangles[tri];
339782
+ const color = roughnessColorForAngle(maxAngleDeg, options);
339783
+ const score = roughnessScoreForAngle(maxAngleDeg, options);
339784
+ const offset2 = tri * 3;
339785
+ for (let vertex2 = 0; vertex2 < 3; vertex2 += 1) {
339786
+ const colorOffset = (offset2 + vertex2) * 3;
339787
+ colors[colorOffset] = color[0] / 255;
339788
+ colors[colorOffset + 1] = color[1] / 255;
339789
+ colors[colorOffset + 2] = color[2] / 255;
339790
+ scores[offset2 + vertex2] = score;
339791
+ }
339792
+ }
339793
+ geometry.setAttribute("color", new BufferAttribute(colors, 3));
339794
+ geometry.setAttribute("roughnessScore", new BufferAttribute(scores, 1));
339795
+ geometry.computeBoundingBox();
339796
+ return {
339797
+ geometry,
339798
+ summary: summarizeRoughnessTriangles(triangles, edgeAngles, edges.size, boundaryEdgeCount, nonManifoldEdgeCount, options),
339799
+ warnings
339800
+ };
339801
+ }
339802
+ function markTriangleRoughness(edges, triangles, normals) {
339803
+ const edgeAngles = [];
339804
+ let boundaryEdgeCount = 0;
339805
+ let nonManifoldEdgeCount = 0;
339806
+ for (const edge of edges.values()) {
339807
+ if (edge.triangles.length === 1) {
339808
+ boundaryEdgeCount += 1;
339809
+ markTriangles(edge.triangles, triangles, 180);
339810
+ edgeAngles.push(180);
339811
+ continue;
339812
+ }
339813
+ if (edge.triangles.length > 2) {
339814
+ nonManifoldEdgeCount += 1;
339815
+ markTriangles(edge.triangles, triangles, 180);
339816
+ edgeAngles.push(180);
339817
+ continue;
339818
+ }
339819
+ const [first, second] = edge.triangles;
339820
+ const dot2 = MathUtils.clamp(normals[first].dot(normals[second]), -1, 1);
339821
+ const angleDeg = Math.acos(dot2) * DEG_PER_RAD;
339822
+ markTriangles(edge.triangles, triangles, angleDeg);
339823
+ edgeAngles.push(angleDeg);
339824
+ }
339825
+ return { boundaryEdgeCount, nonManifoldEdgeCount, edgeAngles };
339826
+ }
339827
+ function readVertex(position, index2, target) {
339828
+ target.set(position.getX(index2), position.getY(index2), position.getZ(index2));
339829
+ }
339830
+ function vertexKey(point2, snap) {
339831
+ return `${Math.round(point2.x / snap)},${Math.round(point2.y / snap)},${Math.round(point2.z / snap)}`;
339832
+ }
339833
+ function edgeKey(a2, b) {
339834
+ return a2 < b ? `${a2}|${b}` : `${b}|${a2}`;
339835
+ }
339836
+ function markTriangles(indices, triangles, angleDeg) {
339837
+ for (const index2 of indices) {
339838
+ triangles[index2].maxAngleDeg = Math.max(triangles[index2].maxAngleDeg, angleDeg);
339839
+ }
339840
+ }
339841
+ const DEFAULT_THICKNESS_INSPECTION_OPTIONS = {
339842
+ minThickness: 1.2,
339843
+ warnThickness: 2,
339844
+ maxThickness: 6,
339845
+ maxSamplesPerObject: 5e3
339846
+ };
339847
+ const THICKNESS_COLORS = {
339848
+ critical: [255, 28, 28],
339849
+ warning: [255, 150, 0],
339850
+ ok: [60, 220, 90],
339851
+ thick: [70, 145, 255],
339852
+ unknown: [90, 90, 90]
339853
+ };
339854
+ function finitePositive(value, fallback, label) {
339855
+ if (value === void 0) return fallback;
339856
+ if (!Number.isFinite(value) || value <= 0) {
339857
+ throw new Error(`${label} must be a positive finite number.`);
339858
+ }
339859
+ return value;
339860
+ }
339861
+ function resolveThicknessInspectionOptions(raw = {}) {
339862
+ const minThickness = finitePositive(raw.minThickness, DEFAULT_THICKNESS_INSPECTION_OPTIONS.minThickness, "minThickness");
339863
+ const warnThickness = finitePositive(raw.warnThickness, DEFAULT_THICKNESS_INSPECTION_OPTIONS.warnThickness, "warnThickness");
339864
+ const maxThickness = finitePositive(raw.maxThickness, DEFAULT_THICKNESS_INSPECTION_OPTIONS.maxThickness, "maxThickness");
339865
+ const maxSamplesPerObject = finitePositive(
339866
+ raw.maxSamplesPerObject,
339867
+ DEFAULT_THICKNESS_INSPECTION_OPTIONS.maxSamplesPerObject,
339868
+ "maxSamplesPerObject"
339869
+ );
339870
+ if (minThickness > warnThickness) {
339871
+ throw new Error("minThickness must be less than or equal to warnThickness.");
339872
+ }
339873
+ if (warnThickness > maxThickness) {
339874
+ throw new Error("warnThickness must be less than or equal to maxThickness.");
339875
+ }
339876
+ return {
339877
+ minThickness,
339878
+ warnThickness,
339879
+ maxThickness,
339880
+ maxSamplesPerObject: Math.max(1, Math.floor(maxSamplesPerObject))
339881
+ };
339882
+ }
339883
+ function lerp(a2, b, t) {
339884
+ return a2 + (b - a2) * Math.max(0, Math.min(1, t));
339885
+ }
339886
+ function lerpColor(a2, b, t) {
339887
+ return [Math.round(lerp(a2[0], b[0], t)), Math.round(lerp(a2[1], b[1], t)), Math.round(lerp(a2[2], b[2], t))];
339888
+ }
339889
+ function thicknessClass(thickness, options) {
339890
+ if (thickness == null || !Number.isFinite(thickness) || thickness <= 0) return "unknown";
339891
+ if (thickness <= options.minThickness) return "critical";
339892
+ if (thickness <= options.warnThickness) return "warning";
339893
+ if (thickness <= options.maxThickness) return "ok";
339894
+ return "thick";
339895
+ }
339896
+ function thicknessColor(thickness, options) {
339897
+ const cls = thicknessClass(thickness, options);
339898
+ if (cls === "unknown") return THICKNESS_COLORS.unknown;
339899
+ if (cls === "critical") return THICKNESS_COLORS.critical;
339900
+ if (cls === "warning") {
339901
+ const span = Math.max(1e-9, options.warnThickness - options.minThickness);
339902
+ return lerpColor(THICKNESS_COLORS.critical, THICKNESS_COLORS.warning, ((thickness ?? 0) - options.minThickness) / span);
339903
+ }
339904
+ if (cls === "ok") {
339905
+ const span = Math.max(1e-9, options.maxThickness - options.warnThickness);
339906
+ return lerpColor(THICKNESS_COLORS.ok, THICKNESS_COLORS.thick, ((thickness ?? 0) - options.warnThickness) / span);
339907
+ }
339908
+ return THICKNESS_COLORS.thick;
339909
+ }
339910
+ function sampleArea(sample) {
339911
+ const area2 = sample.area ?? 1;
339912
+ return Number.isFinite(area2) && area2 > 0 ? area2 : 1;
339913
+ }
339914
+ function weightedQuantile(samples, q) {
339915
+ if (samples.length === 0) return null;
339916
+ const sorted = [...samples].sort((a2, b) => a2.thickness - b.thickness);
339917
+ const totalArea = sorted.reduce((sum2, sample) => sum2 + sample.area, 0);
339918
+ const target = totalArea * Math.max(0, Math.min(1, q));
339919
+ let cumulative = 0;
339920
+ for (const sample of sorted) {
339921
+ cumulative += sample.area;
339922
+ if (cumulative >= target) return sample.thickness;
339923
+ }
339924
+ return sorted[sorted.length - 1].thickness;
339925
+ }
339926
+ function percent(part, total) {
339927
+ if (total <= 0) return 0;
339928
+ return part / total * 100;
339929
+ }
339930
+ function summarizeThicknessSamples(samples, options) {
339931
+ const resolved = [];
339932
+ let totalArea = 0;
339933
+ let resolvedArea = 0;
339934
+ let unresolvedArea = 0;
339935
+ let criticalArea = 0;
339936
+ let warningArea = 0;
339937
+ let weightedSum = 0;
339938
+ for (const sample of samples) {
339939
+ const area2 = sampleArea(sample);
339940
+ totalArea += area2;
339941
+ const value = sample.thickness;
339942
+ if (value == null || !Number.isFinite(value) || value <= 0) {
339943
+ unresolvedArea += area2;
339944
+ continue;
339945
+ }
339946
+ resolved.push({ thickness: value, area: area2 });
339947
+ resolvedArea += area2;
339948
+ weightedSum += value * area2;
339949
+ if (value <= options.minThickness) {
339950
+ criticalArea += area2;
339951
+ } else if (value <= options.warnThickness) {
339952
+ warningArea += area2;
339953
+ }
339954
+ }
339955
+ const values = resolved.map((sample) => sample.thickness);
339956
+ return {
339957
+ sampleCount: samples.length,
339958
+ resolvedCount: resolved.length,
339959
+ unresolvedCount: samples.length - resolved.length,
339960
+ minThickness: values.length > 0 ? Math.min(...values) : null,
339961
+ p05Thickness: weightedQuantile(resolved, 0.05),
339962
+ medianThickness: weightedQuantile(resolved, 0.5),
339963
+ meanThickness: resolvedArea > 0 ? weightedSum / resolvedArea : null,
339964
+ maxThickness: values.length > 0 ? Math.max(...values) : null,
339965
+ criticalAreaPercent: percent(criticalArea, resolvedArea),
339966
+ warningAreaPercent: percent(warningArea, resolvedArea),
339967
+ belowWarnAreaPercent: percent(criticalArea + warningArea, resolvedArea),
339968
+ unresolvedAreaPercent: percent(unresolvedArea, totalArea)
339969
+ };
339970
+ }
339971
+ function cloneGeometryForFaceColors(geometry) {
339972
+ return geometry.index ? geometry.toNonIndexed() : geometry.clone();
339973
+ }
339974
+ function geometryMaxDimension(geometry) {
339975
+ geometry.computeBoundingBox();
339976
+ const box2 = geometry.boundingBox;
339977
+ if (!box2) return 1;
339978
+ const size = new Vector3();
339979
+ box2.getSize(size);
339980
+ return Math.max(1, size.x, size.y, size.z);
339981
+ }
339982
+ function firstOppositeSurfaceDistance(raycaster, mesh, point2, direction2, epsilon2, far) {
339983
+ const origin = point2.clone().addScaledVector(direction2, epsilon2);
339984
+ raycaster.set(origin, direction2);
339985
+ raycaster.near = epsilon2;
339986
+ raycaster.far = far;
339987
+ const hit = raycaster.intersectObject(mesh, false).find((entry) => entry.distance > epsilon2);
339988
+ return hit ? hit.distance + epsilon2 : null;
339989
+ }
339990
+ function triangleThickness(raycaster, mesh, centroid, normal, epsilon2, far) {
339991
+ const forward = firstOppositeSurfaceDistance(raycaster, mesh, centroid, normal, epsilon2, far);
339992
+ const backward = firstOppositeSurfaceDistance(raycaster, mesh, centroid, normal.clone().negate(), epsilon2, far);
339993
+ if (forward == null) return backward;
339994
+ if (backward == null) return forward;
339995
+ return Math.min(forward, backward);
339996
+ }
339997
+ function analyzeThicknessGeometry(sourceGeometry, rawOptions = {}) {
339998
+ const options = resolveThicknessInspectionOptions(rawOptions);
339999
+ const geometry = cloneGeometryForFaceColors(sourceGeometry);
340000
+ const position = geometry.getAttribute("position");
340001
+ if (!position || position.count < 3) {
340002
+ return { geometry, samples: [], triangleCount: 0, sampledTriangleCount: 0, sampleStride: 1, warnings: ["No triangle geometry."] };
340003
+ }
340004
+ const triangleCount = Math.floor(position.count / 3);
340005
+ const sampleStride = Math.max(1, Math.ceil(triangleCount / options.maxSamplesPerObject));
340006
+ const maxDim = geometryMaxDimension(geometry);
340007
+ const epsilon2 = Math.max(1e-4, maxDim * 1e-6);
340008
+ const far = Math.max(maxDim * 4, options.maxThickness * 4, 1);
340009
+ const colors = new Float32Array(position.count * 3);
340010
+ const samples = [];
340011
+ const warnings = [];
340012
+ const rayMaterial = new MeshBasicMaterial({ side: DoubleSide });
340013
+ const rayMesh = new Mesh(geometry, rayMaterial);
340014
+ const raycaster = new Raycaster();
340015
+ if (sampleStride > 1) {
340016
+ warnings.push(`Triangle sampling stride ${sampleStride}; increase --thickness-samples for denser analysis.`);
340017
+ }
340018
+ const a2 = new Vector3();
340019
+ const b = new Vector3();
340020
+ const c2 = new Vector3();
340021
+ const ac = new Vector3();
340022
+ const normal = new Vector3();
340023
+ const centroid = new Vector3();
340024
+ let sampledTriangleCount = 0;
340025
+ let lastThickness = null;
340026
+ for (let tri = 0; tri < triangleCount; tri += 1) {
340027
+ const offset2 = tri * 3;
340028
+ a2.fromBufferAttribute(position, offset2);
340029
+ b.fromBufferAttribute(position, offset2 + 1);
340030
+ c2.fromBufferAttribute(position, offset2 + 2);
340031
+ normal.subVectors(b, a2).cross(ac.subVectors(c2, a2));
340032
+ const areaTwice = normal.length();
340033
+ const area2 = areaTwice * 0.5;
340034
+ let thickness = lastThickness;
340035
+ if (tri % sampleStride === 0) {
340036
+ sampledTriangleCount += 1;
340037
+ if (areaTwice <= 1e-12) {
340038
+ thickness = null;
340039
+ } else {
340040
+ normal.multiplyScalar(1 / areaTwice);
340041
+ centroid.copy(a2).add(b).add(c2).multiplyScalar(1 / 3);
340042
+ thickness = triangleThickness(raycaster, rayMesh, centroid, normal, epsilon2, far);
340043
+ }
340044
+ lastThickness = thickness;
340045
+ samples.push({ thickness, area: area2 });
340046
+ }
340047
+ const color = thicknessColor(thickness, options);
340048
+ for (let vertex2 = 0; vertex2 < 3; vertex2 += 1) {
340049
+ const colorOffset = (offset2 + vertex2) * 3;
340050
+ colors[colorOffset] = color[0] / 255;
340051
+ colors[colorOffset + 1] = color[1] / 255;
340052
+ colors[colorOffset + 2] = color[2] / 255;
340053
+ }
340054
+ }
340055
+ geometry.setAttribute("color", new BufferAttribute(colors, 3));
340056
+ rayMaterial.dispose();
340057
+ return { geometry, samples, triangleCount, sampledTriangleCount, sampleStride, warnings };
340058
+ }
338949
340059
  const VECTOR_KEYS = /* @__PURE__ */ new Set(["pos", "position", "target", "lookat", "aim", "up"]);
338950
340060
  const roundNumber = (value, digits) => {
338951
340061
  const scale2 = 10 ** digits;
@@ -339117,6 +340227,494 @@ function localAabbPlaneRelation(min2, max2, plane, eps = 1e-7) {
339117
340227
  if (minDistance > -eps) return "positive";
339118
340228
  return "crossing";
339119
340229
  }
340230
+ const DEFAULT_PHYSICAL_CONNECTIVITY_OPTIONS = {
340231
+ contactTolerance: 0.05,
340232
+ minOverlapVolume: 0.1,
340233
+ exactGeometry: false
340234
+ };
340235
+ const AXIS_NAMES = ["x", "y", "z"];
340236
+ class UnionFind {
340237
+ constructor(size) {
340238
+ __publicField(this, "parent");
340239
+ __publicField(this, "rank");
340240
+ this.parent = Array.from({ length: size }, (_2, index2) => index2);
340241
+ this.rank = Array.from({ length: size }, () => 0);
340242
+ }
340243
+ find(value) {
340244
+ const parent = this.parent[value];
340245
+ if (parent === value) return value;
340246
+ const root = this.find(parent);
340247
+ this.parent[value] = root;
340248
+ return root;
340249
+ }
340250
+ union(a2, b) {
340251
+ const rootA = this.find(a2);
340252
+ const rootB = this.find(b);
340253
+ if (rootA === rootB) return;
340254
+ if (this.rank[rootA] < this.rank[rootB]) {
340255
+ this.parent[rootA] = rootB;
340256
+ return;
340257
+ }
340258
+ if (this.rank[rootA] > this.rank[rootB]) {
340259
+ this.parent[rootB] = rootA;
340260
+ return;
340261
+ }
340262
+ this.parent[rootB] = rootA;
340263
+ this.rank[rootA] += 1;
340264
+ }
340265
+ }
340266
+ function cloneVec3(value) {
340267
+ return [value[0], value[1], value[2]];
340268
+ }
340269
+ function emptyBBox() {
340270
+ return {
340271
+ min: [Infinity, Infinity, Infinity],
340272
+ max: [-Infinity, -Infinity, -Infinity]
340273
+ };
340274
+ }
340275
+ function expandBBox(target, min2, max2) {
340276
+ for (let axis = 0; axis < 3; axis += 1) {
340277
+ target.min[axis] = Math.min(target.min[axis], min2[axis]);
340278
+ target.max[axis] = Math.max(target.max[axis], max2[axis]);
340279
+ }
340280
+ }
340281
+ function intervalGap$1(aMin, aMax, bMin, bMax) {
340282
+ if (aMax < bMin) return bMin - aMax;
340283
+ if (bMax < aMin) return aMin - bMax;
340284
+ return 0;
340285
+ }
340286
+ function nearestBoundaryGap(a2, b, axis) {
340287
+ return Math.min(Math.abs(a2.max[axis] - b.min[axis]), Math.abs(b.max[axis] - a2.min[axis]));
340288
+ }
340289
+ function bboxGaps(a2, b) {
340290
+ return [
340291
+ intervalGap$1(a2.min[0], a2.max[0], b.min[0], b.max[0]),
340292
+ intervalGap$1(a2.min[1], a2.max[1], b.min[1], b.max[1]),
340293
+ intervalGap$1(a2.min[2], a2.max[2], b.min[2], b.max[2])
340294
+ ];
340295
+ }
340296
+ function maxGap(gaps) {
340297
+ return Math.max(gaps[0], gaps[1], gaps[2]);
340298
+ }
340299
+ function hasPositiveGap(gaps) {
340300
+ return gaps[0] > 0 || gaps[1] > 0 || gaps[2] > 0;
340301
+ }
340302
+ function bboxInteriorOverlaps(a2, b) {
340303
+ for (let axis = 0; axis < 3; axis += 1) {
340304
+ if (Math.min(a2.max[axis], b.max[axis]) - Math.max(a2.min[axis], b.min[axis]) <= 0) return false;
340305
+ }
340306
+ return true;
340307
+ }
340308
+ function bboxOverlapVolume(a2, b) {
340309
+ let volume = 1;
340310
+ for (let axis = 0; axis < 3; axis += 1) {
340311
+ volume *= Math.max(0, Math.min(a2.max[axis], b.max[axis]) - Math.max(a2.min[axis], b.min[axis]));
340312
+ }
340313
+ return volume;
340314
+ }
340315
+ function estimateSweepPairCount(entries, axis, tolerance) {
340316
+ const ordered = entries.map((entry) => ({ min: entry.min[axis], max: entry.max[axis] })).sort((a2, b) => a2.min - b.min || a2.max - b.max);
340317
+ const endValues = ordered.map((entry) => entry.max + tolerance).sort((a2, b) => a2 - b);
340318
+ let expired = 0;
340319
+ let count = 0;
340320
+ for (let seen2 = 0; seen2 < ordered.length; seen2 += 1) {
340321
+ const currentMin = ordered[seen2].min;
340322
+ while (expired < seen2 && endValues[expired] < currentMin) expired += 1;
340323
+ count += seen2 - expired;
340324
+ }
340325
+ return count;
340326
+ }
340327
+ function chooseSweepAxis(entries, tolerance) {
340328
+ let bestAxis = 0;
340329
+ let bestCount = estimateSweepPairCount(entries, bestAxis, tolerance);
340330
+ for (const axis of [1, 2]) {
340331
+ const count = estimateSweepPairCount(entries, axis, tolerance);
340332
+ if (count < bestCount) {
340333
+ bestAxis = axis;
340334
+ bestCount = count;
340335
+ }
340336
+ }
340337
+ return bestAxis;
340338
+ }
340339
+ function collectCandidatePairs(entries, tolerance) {
340340
+ if (entries.length < 2) return [];
340341
+ const axis = chooseSweepAxis(entries, tolerance);
340342
+ const ordered = entries.map((entry, index2) => ({ entry, index: index2 })).sort((a2, b) => a2.entry.min[axis] - b.entry.min[axis] || a2.entry.max[axis] - b.entry.max[axis] || a2.index - b.index);
340343
+ let active = [];
340344
+ const pairs = [];
340345
+ for (const current of ordered) {
340346
+ active = active.filter((candidate) => candidate.entry.max[axis] + tolerance >= current.entry.min[axis]);
340347
+ for (const candidate of active) {
340348
+ const gaps = bboxGaps(candidate.entry, current.entry);
340349
+ if (maxGap(gaps) > tolerance) continue;
340350
+ const sourceIndex = Math.min(candidate.index, current.index);
340351
+ const targetIndex = Math.max(candidate.index, current.index);
340352
+ pairs.push({ sourceIndex, targetIndex, gaps });
340353
+ }
340354
+ active.push(current);
340355
+ }
340356
+ pairs.sort((a2, b) => a2.sourceIndex - b.sourceIndex || a2.targetIndex - b.targetIndex);
340357
+ return pairs;
340358
+ }
340359
+ function contactFromBBoxes(a2, b, tolerance) {
340360
+ const gaps = bboxGaps(a2, b);
340361
+ const largestGap = maxGap(gaps);
340362
+ if (largestGap > tolerance) return { touching: false, gap: largestGap };
340363
+ const separatedAxes = gaps.map((gap, axis) => ({ gap, axis })).filter((entry) => entry.gap > 0);
340364
+ if (separatedAxes.length > 0) {
340365
+ const nearest2 = separatedAxes.reduce((best, entry) => entry.gap > best.gap ? entry : best, separatedAxes[0]);
340366
+ return { touching: true, gap: nearest2.gap, axis: AXIS_NAMES[nearest2.axis] };
340367
+ }
340368
+ const boundaryAxes = AXIS_NAMES.map((axisName, axis) => ({
340369
+ axis,
340370
+ axisName,
340371
+ gap: nearestBoundaryGap(a2, b, axis)
340372
+ })).filter((entry) => entry.gap <= tolerance);
340373
+ if (boundaryAxes.length === 0) return { touching: false, gap: 0 };
340374
+ const nearest = boundaryAxes.reduce((best, entry) => entry.gap < best.gap ? entry : best, boundaryAxes[0]);
340375
+ return { touching: true, gap: nearest.gap, axis: nearest.axisName };
340376
+ }
340377
+ function intersectionVolume(a2, b) {
340378
+ try {
340379
+ const hit = a2.shape.intersect(b.shape);
340380
+ if (hit.isEmpty()) return { volume: 0 };
340381
+ const volume = hit.volume();
340382
+ return { volume: Number.isFinite(volume) ? volume : 0 };
340383
+ } catch (err2) {
340384
+ const message = err2 instanceof Error ? err2.message : String(err2);
340385
+ return { volume: null, warning: `Could not boolean-test ${a2.name} against ${b.name}: ${message}` };
340386
+ }
340387
+ }
340388
+ function bodyCountForEntry(entry) {
340389
+ if (typeof entry.bodyCount === "number" && Number.isFinite(entry.bodyCount)) {
340390
+ return Math.max(0, Math.round(entry.bodyCount));
340391
+ }
340392
+ return 1;
340393
+ }
340394
+ function makeEdge(entries, sourceIndex, targetIndex, edge) {
340395
+ const source = entries[sourceIndex];
340396
+ const target = entries[targetIndex];
340397
+ return {
340398
+ sourceIndex,
340399
+ targetIndex,
340400
+ sourceId: source.id,
340401
+ targetId: target.id,
340402
+ sourceName: source.name,
340403
+ targetName: target.name,
340404
+ ...edge
340405
+ };
340406
+ }
340407
+ function analyzePhysicalConnectivity(entries, rawOptions = {}) {
340408
+ const options = {
340409
+ contactTolerance: rawOptions.contactTolerance ?? DEFAULT_PHYSICAL_CONNECTIVITY_OPTIONS.contactTolerance,
340410
+ minOverlapVolume: rawOptions.minOverlapVolume ?? DEFAULT_PHYSICAL_CONNECTIVITY_OPTIONS.minOverlapVolume,
340411
+ exactGeometry: rawOptions.exactGeometry ?? DEFAULT_PHYSICAL_CONNECTIVITY_OPTIONS.exactGeometry
340412
+ };
340413
+ const warnings = [];
340414
+ const edges = [];
340415
+ const unionFind = new UnionFind(entries.length);
340416
+ for (const pair of collectCandidatePairs(entries, options.contactTolerance)) {
340417
+ const i = pair.sourceIndex;
340418
+ const j = pair.targetIndex;
340419
+ const a2 = entries[i];
340420
+ const b = entries[j];
340421
+ const bboxOverlaps2 = !hasPositiveGap(pair.gaps) && bboxInteriorOverlaps(a2, b);
340422
+ if (options.exactGeometry && bboxOverlaps2) {
340423
+ const overlap = intersectionVolume(a2, b);
340424
+ if (overlap.warning) warnings.push(overlap.warning);
340425
+ if (overlap.volume != null && overlap.volume > options.minOverlapVolume) {
340426
+ unionFind.union(i, j);
340427
+ edges.push(
340428
+ makeEdge(entries, i, j, {
340429
+ kind: "overlap",
340430
+ method: "boolean-intersection",
340431
+ gap: 0,
340432
+ overlapVolume: overlap.volume
340433
+ })
340434
+ );
340435
+ continue;
340436
+ }
340437
+ if (overlap.volume != null && overlap.volume > 0) {
340438
+ unionFind.union(i, j);
340439
+ edges.push(
340440
+ makeEdge(entries, i, j, {
340441
+ kind: "touching",
340442
+ method: "boolean-intersection",
340443
+ gap: 0,
340444
+ overlapVolume: overlap.volume
340445
+ })
340446
+ );
340447
+ continue;
340448
+ }
340449
+ }
340450
+ if (bboxOverlaps2) {
340451
+ unionFind.union(i, j);
340452
+ edges.push(
340453
+ makeEdge(entries, i, j, {
340454
+ kind: "overlap",
340455
+ method: "bbox-overlap",
340456
+ gap: 0,
340457
+ overlapVolume: bboxOverlapVolume(a2, b)
340458
+ })
340459
+ );
340460
+ } else {
340461
+ const contact = contactFromBBoxes(a2, b, options.contactTolerance);
340462
+ if (!contact.touching) continue;
340463
+ unionFind.union(i, j);
340464
+ edges.push(
340465
+ makeEdge(entries, i, j, {
340466
+ kind: "touching",
340467
+ method: "bbox-contact",
340468
+ gap: contact.gap,
340469
+ axis: contact.axis
340470
+ })
340471
+ );
340472
+ }
340473
+ }
340474
+ const objects = entries.map((entry, index2) => ({
340475
+ index: index2,
340476
+ id: entry.id,
340477
+ name: entry.name,
340478
+ groupName: entry.groupName,
340479
+ treePath: entry.treePath,
340480
+ mock: entry.mock === true,
340481
+ bodyCount: bodyCountForEntry(entry),
340482
+ bbox: {
340483
+ min: cloneVec3(entry.min),
340484
+ max: cloneVec3(entry.max)
340485
+ },
340486
+ componentIndex: 0
340487
+ }));
340488
+ const componentByRoot = /* @__PURE__ */ new Map();
340489
+ const rootToComponentIndex = /* @__PURE__ */ new Map();
340490
+ for (let objectIndex = 0; objectIndex < objects.length; objectIndex += 1) {
340491
+ const root = unionFind.find(objectIndex);
340492
+ let component = componentByRoot.get(root);
340493
+ if (!component) {
340494
+ component = {
340495
+ index: componentByRoot.size + 1,
340496
+ objectIndexes: [],
340497
+ objectIds: [],
340498
+ objectNames: [],
340499
+ objectCount: 0,
340500
+ bodyCount: 0,
340501
+ bbox: emptyBBox()
340502
+ };
340503
+ componentByRoot.set(root, component);
340504
+ rootToComponentIndex.set(root, component.index);
340505
+ }
340506
+ const object = objects[objectIndex];
340507
+ object.componentIndex = rootToComponentIndex.get(root) ?? component.index;
340508
+ component.objectIndexes.push(object.index);
340509
+ component.objectIds.push(object.id);
340510
+ component.objectNames.push(object.name);
340511
+ component.objectCount += 1;
340512
+ component.bodyCount += object.bodyCount;
340513
+ expandBBox(component.bbox, object.bbox.min, object.bbox.max);
340514
+ }
340515
+ const components = [...componentByRoot.values()];
340516
+ return {
340517
+ method: options.exactGeometry ? "boolean-overlap-plus-bbox-contact" : "bbox-neighborhood",
340518
+ options,
340519
+ objectCount: objects.length,
340520
+ componentCount: components.length,
340521
+ objects,
340522
+ components,
340523
+ edges,
340524
+ warnings
340525
+ };
340526
+ }
340527
+ const EPSILON = 1e-9;
340528
+ function intervalGap(aMin, aMax, bMin, bMax) {
340529
+ if (aMax < bMin) return bMin - aMax;
340530
+ if (bMax < aMin) return aMin - bMax;
340531
+ return 0;
340532
+ }
340533
+ function bboxGap(a2, b) {
340534
+ const axisGaps = [
340535
+ intervalGap(a2.bbox.min[0], a2.bbox.max[0], b.bbox.min[0], b.bbox.max[0]),
340536
+ intervalGap(a2.bbox.min[1], a2.bbox.max[1], b.bbox.min[1], b.bbox.max[1]),
340537
+ intervalGap(a2.bbox.min[2], a2.bbox.max[2], b.bbox.min[2], b.bbox.max[2])
340538
+ ];
340539
+ const gap = Math.sqrt(axisGaps[0] ** 2 + axisGaps[1] ** 2 + axisGaps[2] ** 2);
340540
+ return { gap, axisGaps };
340541
+ }
340542
+ function bboxVolume(component) {
340543
+ const dx = Math.max(0, component.bbox.max[0] - component.bbox.min[0]);
340544
+ const dy = Math.max(0, component.bbox.max[1] - component.bbox.min[1]);
340545
+ const dz = Math.max(0, component.bbox.max[2] - component.bbox.min[2]);
340546
+ return dx * dy * dz;
340547
+ }
340548
+ function compareDefaultRoot(a2, b) {
340549
+ if (a2.bodyCount !== b.bodyCount) return a2.bodyCount - b.bodyCount;
340550
+ if (a2.objectCount !== b.objectCount) return a2.objectCount - b.objectCount;
340551
+ const volumeDelta = bboxVolume(a2) - bboxVolume(b);
340552
+ if (Math.abs(volumeDelta) > EPSILON) return volumeDelta;
340553
+ return b.index - a2.index;
340554
+ }
340555
+ function defaultRootComponentIndex(components) {
340556
+ if (components.length === 0) return null;
340557
+ return components.reduce((best, component) => compareDefaultRoot(component, best) > 0 ? component : best, components[0]).index;
340558
+ }
340559
+ function componentPositionByIndex(components) {
340560
+ return new Map(components.map((component, position) => [component.index, position]));
340561
+ }
340562
+ function computeNearestComponents(components) {
340563
+ const nearest = components.map(() => ({ nearestGap: null, nearestComponentIndex: null }));
340564
+ for (let sourcePosition = 0; sourcePosition < components.length; sourcePosition += 1) {
340565
+ for (let targetPosition = sourcePosition + 1; targetPosition < components.length; targetPosition += 1) {
340566
+ const sourceComponent = components[sourcePosition];
340567
+ const targetComponent = components[targetPosition];
340568
+ const edge = bboxGap(sourceComponent, targetComponent);
340569
+ if (!Number.isFinite(edge.gap)) continue;
340570
+ const source = nearest[sourcePosition];
340571
+ if (source.nearestGap == null || edge.gap < source.nearestGap - EPSILON || Math.abs(edge.gap - source.nearestGap) <= EPSILON && targetComponent.index < (source.nearestComponentIndex ?? Infinity)) {
340572
+ source.nearestGap = edge.gap;
340573
+ source.nearestComponentIndex = targetComponent.index;
340574
+ }
340575
+ const target = nearest[targetPosition];
340576
+ if (target.nearestGap == null || edge.gap < target.nearestGap - EPSILON || Math.abs(edge.gap - target.nearestGap) <= EPSILON && sourceComponent.index < (target.nearestComponentIndex ?? Infinity)) {
340577
+ target.nearestGap = edge.gap;
340578
+ target.nearestComponentIndex = sourceComponent.index;
340579
+ }
340580
+ }
340581
+ }
340582
+ return nearest;
340583
+ }
340584
+ function computeRootDistances(components, rootComponentIndex) {
340585
+ if (rootComponentIndex == null) return [];
340586
+ const positions = componentPositionByIndex(components);
340587
+ const rootPosition = positions.get(rootComponentIndex);
340588
+ if (rootPosition == null) {
340589
+ throw new Error(`rootComponentIndex ${rootComponentIndex} does not match any physical component`);
340590
+ }
340591
+ const visited = components.map(() => false);
340592
+ const distances = components.map(() => Infinity);
340593
+ const parents = components.map(() => null);
340594
+ const parentGaps = components.map(() => null);
340595
+ distances[rootPosition] = 0;
340596
+ for (; ; ) {
340597
+ let current = -1;
340598
+ for (let i = 0; i < components.length; i += 1) {
340599
+ if (visited[i]) continue;
340600
+ if (current === -1 || distances[i] < distances[current] - EPSILON || Math.abs(distances[i] - distances[current]) <= EPSILON && components[i].index < components[current].index) {
340601
+ current = i;
340602
+ }
340603
+ }
340604
+ if (current === -1 || !Number.isFinite(distances[current])) break;
340605
+ visited[current] = true;
340606
+ for (let targetPosition = 0; targetPosition < components.length; targetPosition += 1) {
340607
+ if (visited[targetPosition] || targetPosition === current) continue;
340608
+ const edge = bboxGap(components[current], components[targetPosition]);
340609
+ if (!Number.isFinite(edge.gap)) continue;
340610
+ const nextDistance = distances[current] + edge.gap;
340611
+ if (nextDistance < distances[targetPosition] - EPSILON || Math.abs(nextDistance - distances[targetPosition]) <= EPSILON && components[current].index < (parents[targetPosition] ?? Infinity)) {
340612
+ distances[targetPosition] = nextDistance;
340613
+ parents[targetPosition] = components[current].index;
340614
+ parentGaps[targetPosition] = edge.gap;
340615
+ }
340616
+ }
340617
+ }
340618
+ return components.map((_2, position) => ({
340619
+ rootDistance: distances[position],
340620
+ parentComponentIndex: parents[position],
340621
+ parentGap: parentGaps[position]
340622
+ }));
340623
+ }
340624
+ function makeGapEdge(source, target) {
340625
+ const gap = bboxGap(source, target);
340626
+ return {
340627
+ sourceComponentIndex: source.index,
340628
+ targetComponentIndex: target.index,
340629
+ sourceObjectNames: [...source.objectNames],
340630
+ targetObjectNames: [...target.objectNames],
340631
+ gap: gap.gap,
340632
+ axisGaps: gap.axisGaps
340633
+ };
340634
+ }
340635
+ function compactGapEdges(components, nearest, rooted) {
340636
+ const componentByIndex = new Map(components.map((component) => [component.index, component]));
340637
+ const seen2 = /* @__PURE__ */ new Set();
340638
+ const edges = [];
340639
+ const add2 = (sourceIndex, targetIndex) => {
340640
+ if (targetIndex == null || sourceIndex === targetIndex) return;
340641
+ const source = componentByIndex.get(Math.min(sourceIndex, targetIndex));
340642
+ const target = componentByIndex.get(Math.max(sourceIndex, targetIndex));
340643
+ if (!source || !target) return;
340644
+ const key = `${source.index}:${target.index}`;
340645
+ if (seen2.has(key)) return;
340646
+ seen2.add(key);
340647
+ edges.push(makeGapEdge(source, target));
340648
+ };
340649
+ components.forEach((component, position) => {
340650
+ var _a3, _b3;
340651
+ add2(component.index, ((_a3 = nearest[position]) == null ? void 0 : _a3.nearestComponentIndex) ?? null);
340652
+ add2(component.index, ((_b3 = rooted[position]) == null ? void 0 : _b3.parentComponentIndex) ?? null);
340653
+ });
340654
+ return edges.sort((a2, b) => a2.sourceComponentIndex - b.sourceComponentIndex || a2.targetComponentIndex - b.targetComponentIndex);
340655
+ }
340656
+ function analyzeDistanceInspection(entries, rawOptions = {}) {
340657
+ const connectivity = analyzePhysicalConnectivity(entries, rawOptions);
340658
+ const rootComponentIndex = rawOptions.rootComponentIndex ?? defaultRootComponentIndex(connectivity.components);
340659
+ const nearest = computeNearestComponents(connectivity.components);
340660
+ const rooted = computeRootDistances(connectivity.components, rootComponentIndex);
340661
+ const gapEdges = compactGapEdges(connectivity.components, nearest, rooted);
340662
+ const componentByIndex = /* @__PURE__ */ new Map();
340663
+ const components = connectivity.components.map((component, position) => {
340664
+ var _a3, _b3;
340665
+ const rootData = rooted[position] ?? {
340666
+ rootDistance: rootComponentIndex === component.index ? 0 : Infinity,
340667
+ parentComponentIndex: null,
340668
+ parentGap: null
340669
+ };
340670
+ const decorated = {
340671
+ ...component,
340672
+ isRoot: component.index === rootComponentIndex,
340673
+ rootDistance: rootData.rootDistance,
340674
+ nearestGap: ((_a3 = nearest[position]) == null ? void 0 : _a3.nearestGap) ?? null,
340675
+ nearestComponentIndex: ((_b3 = nearest[position]) == null ? void 0 : _b3.nearestComponentIndex) ?? null,
340676
+ parentComponentIndex: rootData.parentComponentIndex,
340677
+ parentGap: rootData.parentGap
340678
+ };
340679
+ componentByIndex.set(component.index, decorated);
340680
+ return decorated;
340681
+ });
340682
+ const objects = connectivity.objects.map((object) => {
340683
+ const component = componentByIndex.get(object.componentIndex);
340684
+ return {
340685
+ ...object,
340686
+ rootDistance: (component == null ? void 0 : component.rootDistance) ?? Infinity,
340687
+ nearestGap: (component == null ? void 0 : component.nearestGap) ?? null,
340688
+ nearestComponentIndex: (component == null ? void 0 : component.nearestComponentIndex) ?? null,
340689
+ parentComponentIndex: (component == null ? void 0 : component.parentComponentIndex) ?? null,
340690
+ parentGap: (component == null ? void 0 : component.parentGap) ?? null
340691
+ };
340692
+ });
340693
+ const finiteDistances = components.map((component) => component.rootDistance).filter(Number.isFinite);
340694
+ const maxRootDistance = finiteDistances.length > 0 ? Math.max(...finiteDistances) : 0;
340695
+ return {
340696
+ method: "physical-component-bbox-gap-graph",
340697
+ distanceMethod: "axis-aligned-bbox-gap",
340698
+ options: {
340699
+ contactTolerance: connectivity.options.contactTolerance,
340700
+ minOverlapVolume: connectivity.options.minOverlapVolume,
340701
+ rootComponentIndex
340702
+ },
340703
+ objectCount: connectivity.objectCount,
340704
+ componentCount: connectivity.componentCount,
340705
+ rootComponentIndex,
340706
+ maxRootDistance,
340707
+ gapEdgeCount: connectivity.components.length * (connectivity.components.length - 1) / 2,
340708
+ objects,
340709
+ components,
340710
+ gapEdges,
340711
+ connectivity: {
340712
+ method: connectivity.method,
340713
+ edges: connectivity.edges
340714
+ },
340715
+ warnings: [...connectivity.warnings]
340716
+ };
340717
+ }
339120
340718
  export {
339121
340719
  Matrix4 as $,
339122
340720
  ACESFilmicToneMapping as A,
@@ -339212,64 +340810,75 @@ export {
339212
340810
  DEFAULT_ACTIVE_BACKEND as ay,
339213
340811
  isConstraintSketch as az,
339214
340812
  PCFShadowMap as b,
340813
+ generateCuttingLayoutPdf as b$,
339215
340814
  PointLight as b0,
339216
340815
  DirectionalLight as b1,
339217
- buildShapeFromCompilePlan as b2,
340816
+ analyzeCollisionIntersections as b2,
339218
340817
  shapeToGeometry as b3,
339219
- sketchToSvg as b4,
339220
- sketchToDxf as b5,
339221
- runScript as b6,
339222
- MeshPhysicalMaterial as b7,
339223
- LineSegments as b8,
339224
- getRenderStylePreset as b9,
339225
- resolveJointAnimation as bA,
339226
- resolveJointViewValues as bB,
339227
- getShapePorts as bC,
339228
- getShapeUsedPorts as bD,
339229
- DEFAULT_VIEW_CONFIG as bE,
339230
- getKernelFaceNameForTriangle as bF,
339231
- initKernel as bG,
339232
- initSolverWasm as bH,
339233
- BoxGeometry as bI,
339234
- localAabbPlaneRelation as bJ,
339235
- ShapeUtils as bK,
339236
- Group as bL,
339237
- intersectWithPlane as bM,
339238
- parseCameraCliSpec as bN,
339239
- PMREMGenerator as bO,
339240
- worldAuthorPlaneToLocal as bP,
339241
- generateCuttingLayoutPdf as bQ,
339242
- getCameraForwardVector as bR,
339243
- RENDER_STYLE_OPTIONS as bS,
339244
- initKernelManifoldOnly as bT,
339245
- __viteBrowserExternal$1 as bU,
339246
- AdditiveBlending as ba,
339247
- CatmullRomCurve3 as bb,
339248
- TubeGeometry as bc,
339249
- MeshStandardMaterial as bd,
339250
- compileSdfNode3 as be,
339251
- buildSdfRaymarchFragmentShader as bf,
339252
- SDF_RAYMARCH_PROXY_VERTEX_SHADER as bg,
339253
- Shape2 as bh,
339254
- ShapeGeometry as bi,
339255
- ShaderLib as bj,
339256
- CylinderGeometry as bk,
339257
- parseViewportCameraState as bl,
339258
- createResolvedExplodeConfig as bm,
339259
- explodeBoundsCenter as bn,
339260
- explodeMergeBounds as bo,
339261
- resolveExplodeDirective as bp,
339262
- computeExplodeMotion as bq,
339263
- getSketchWorldMatrix as br,
339264
- explodeAdd as bs,
339265
- hasExplodeOverride as bt,
339266
- resolveExplodeLocalFanDirection as bu,
339267
- explodeMul as bv,
339268
- explodeLeafFanStage as bw,
339269
- normalizeCutPlane as bx,
339270
- toClippingPlane as by,
339271
- findJointAnimationClip as bz,
340818
+ buildShapeFromCompilePlan as b4,
340819
+ sketchToSvg as b5,
340820
+ sketchToDxf as b6,
340821
+ runScript as b7,
340822
+ MeshPhysicalMaterial as b8,
340823
+ LineSegments as b9,
340824
+ normalizeCutPlane as bA,
340825
+ toClippingPlane as bB,
340826
+ findJointAnimationClip as bC,
340827
+ resolveJointAnimation as bD,
340828
+ resolveJointViewValues as bE,
340829
+ getShapePorts as bF,
340830
+ getShapeUsedPorts as bG,
340831
+ DEFAULT_VIEW_CONFIG as bH,
340832
+ getKernelFaceNameForTriangle as bI,
340833
+ analyzePhysicalConnectivity as bJ,
340834
+ analyzeDistanceInspection as bK,
340835
+ initKernel as bL,
340836
+ initSolverWasm as bM,
340837
+ BoxGeometry as bN,
340838
+ localAabbPlaneRelation as bO,
340839
+ ShapeUtils as bP,
340840
+ Group as bQ,
340841
+ resolveRoughnessInspectionOptions as bR,
340842
+ ROUGHNESS_COLORS as bS,
340843
+ resolveThicknessInspectionOptions as bT,
340844
+ summarizeThicknessSamples as bU,
340845
+ THICKNESS_COLORS as bV,
340846
+ intersectWithPlane as bW,
340847
+ parseCameraCliSpec as bX,
340848
+ PMREMGenerator as bY,
340849
+ serializeCollisionFinding as bZ,
340850
+ worldAuthorPlaneToLocal as b_,
340851
+ analyzeThicknessGeometry as ba,
340852
+ analyzeRoughnessGeometry as bb,
340853
+ getRenderStylePreset as bc,
340854
+ AdditiveBlending as bd,
340855
+ CatmullRomCurve3 as be,
340856
+ TubeGeometry as bf,
340857
+ MeshStandardMaterial as bg,
340858
+ compileSdfNode3 as bh,
340859
+ buildSdfRaymarchFragmentShader as bi,
340860
+ SDF_RAYMARCH_PROXY_VERTEX_SHADER as bj,
340861
+ Shape2 as bk,
340862
+ ShapeGeometry as bl,
340863
+ ShaderLib as bm,
340864
+ CylinderGeometry as bn,
340865
+ parseViewportCameraState as bo,
340866
+ createResolvedExplodeConfig as bp,
340867
+ explodeBoundsCenter as bq,
340868
+ explodeMergeBounds as br,
340869
+ resolveExplodeDirective as bs,
340870
+ computeExplodeMotion as bt,
340871
+ getSketchWorldMatrix as bu,
340872
+ explodeAdd as bv,
340873
+ hasExplodeOverride as bw,
340874
+ resolveExplodeLocalFanDirection as bx,
340875
+ explodeMul as by,
340876
+ explodeLeafFanStage as bz,
339272
340877
  SRGBColorSpace as c,
340878
+ getCameraForwardVector as c0,
340879
+ RENDER_STYLE_OPTIONS as c1,
340880
+ initKernelManifoldOnly as c2,
340881
+ __viteBrowserExternal$1 as c3,
339273
340882
  Layers as d,
339274
340883
  Color as e,
339275
340884
  RGBAFormat as f,