forgecad 0.10.4 → 0.10.5

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 (64) hide show
  1. package/dist/assets/{AdminPage-B3L3W1Uo.js → AdminPage-raksfnNA.js} +1 -1
  2. package/dist/assets/{BenchmarkPage-DXKVXMrJ.js → BenchmarkPage-DP3RxhPs.js} +2 -2
  3. package/dist/assets/{BlogPage-B7BWxOCg.js → BlogPage-D7Dos-vl.js} +1 -1
  4. package/dist/assets/{DocsPage-BPGGwht1.js → DocsPage-DO1kvBns.js} +7 -1
  5. package/dist/assets/{EditorApp-BWUGCdD5.js → EditorApp-DQJmcmRT.js} +9 -8
  6. package/dist/assets/{EmbedViewer-DygByZS2.js → EmbedViewer-DFDUhOma.js} +2 -2
  7. package/dist/assets/{LandingPageProofDriven-BoVE7JGY.js → LandingPageProofDriven-DbE_tp8-.js} +2 -2
  8. package/dist/assets/{LegalPage-Din8wv8d.js → LegalPage-CominSso.js} +2 -2
  9. package/dist/assets/{PricingPage-C2PMzmDc.js → PricingPage-CcVIN9yj.js} +2 -2
  10. package/dist/assets/{SettingsPage-BlJDCRe8.js → SettingsPage-DLWcP289.js} +1 -1
  11. package/dist/assets/{app-BsRYSfxY.js → app-xW3hOdq9.js} +1135 -320
  12. package/dist/assets/{backendInit-6C0DLgH0.js → backendInit-mDHk97u7.js} +6630 -2493
  13. package/dist/assets/cli/{render-XXol_ET7.js → render--SIU27W_.js} +1263 -112
  14. package/dist/assets/{constructionHistoryWorker-cTHWRJEi.js → constructionHistoryWorker-uEe_Q7Kg.js} +1861 -610
  15. package/dist/assets/{evalWorker-BssDYW9u.js → evalWorker-BqyDHDcI.js} +6254 -2177
  16. package/dist/assets/{forgecad_geometry-CZ_IfuvA.js → forgecad_geometry-D8rWX7nQ.js} +1 -1
  17. package/dist/assets/{forgecad_geometry_bg-C3rQHfwg.wasm → forgecad_geometry_bg-ObqfqjJT.wasm} +0 -0
  18. package/dist/assets/{inspectWorker-ymhBV4Ll.js → inspectWorker-UXMxlcR8.js} +2738 -742
  19. package/dist/assets/{jointPose-B0blBj9A.js → jointPose-bYMlwU3v.js} +1 -1
  20. package/dist/assets/{landing-proof-driven-Cpf-MIbI.css → landing-proof-driven-_u4v_xQb.css} +2 -2
  21. package/dist/assets/{manifold-B_7QXpGB.js → manifold-BR7UYI4P.js} +1 -1
  22. package/dist/assets/{manifold-CYlIm-M6.js → manifold-CyOV5B9S.js} +2 -2
  23. package/dist/assets/{manifold-CNShmpEJ.js → manifold-D4d5NQst.js} +1 -1
  24. package/dist/assets/{reportWorker-Cb5eyM7D.js → reportWorker-DsaICZsn.js} +6010 -2032
  25. package/dist/cli/render.html +1 -1
  26. package/dist/docs/index.html +2 -2
  27. package/dist/docs-raw/CLI.md +4 -2
  28. package/dist/docs-raw/generated/assembly.md +76 -3
  29. package/dist/docs-raw/generated/concepts.md +31 -4
  30. package/dist/docs-raw/generated/core.md +159 -21
  31. package/dist/docs-raw/generated/curves.md +344 -6
  32. package/dist/docs-raw/generated/runtime-names.md +12 -12
  33. package/dist/docs-raw/generated/sketch.md +16 -3
  34. package/dist/docs-raw/guides/inspection-bundles.md +4 -2
  35. package/dist/docs-raw/guides/structural-fea.md +224 -0
  36. package/dist/docs-raw/skills/forgecad.md +1 -0
  37. package/dist/index.html +1 -1
  38. package/dist/sitemap.xml +15 -15
  39. package/dist-cli/{check-compiler-4RPB6SB5.js → check-compiler-7YAHVXYM.js} +1 -1
  40. package/dist-cli/{check-query-propagation-KN3DFQTX.js → check-query-propagation-ZRR6IOJW.js} +1 -1
  41. package/dist-cli/{chunk-UHBRMYA6.js → chunk-VNM67DIV.js} +6489 -2333
  42. package/dist-cli/forgecad.js +5258 -717
  43. package/dist-cli/forgecad_geometry_bg.wasm +0 -0
  44. package/dist-skill/CONTEXT.md +827 -45
  45. package/dist-skill/SKILL.md +1 -0
  46. package/dist-skill/docs/CLI.md +4 -2
  47. package/dist-skill/docs/generated/assembly.md +73 -3
  48. package/dist-skill/docs/generated/core.md +159 -21
  49. package/dist-skill/docs/generated/curves.md +343 -6
  50. package/dist-skill/docs/generated/runtime-names.md +12 -12
  51. package/dist-skill/docs/generated/sketch.md +16 -3
  52. package/dist-skill/docs/guides/inspection-bundles.md +4 -2
  53. package/dist-skill/docs/guides/structural-fea.md +224 -0
  54. package/dist-skill/website/skills/forgecad.md +1 -0
  55. package/examples/analysis/structural-stress-fea.forge.js +19 -0
  56. package/examples/api/blend-full-round.forge.js +37 -0
  57. package/examples/api/blend-variable-radius.forge.js +51 -0
  58. package/examples/api/curve-project-and-intersect.forge.js +59 -0
  59. package/examples/api/extrude-up-to-face.forge.js +47 -0
  60. package/examples/api/spoon-full-tang-handle.forge.js +148 -0
  61. package/examples/api/surface-boundarynet-dished-bowl.forge.js +63 -0
  62. package/examples/api/surface-fill-interior-constraints.forge.js +59 -0
  63. package/package.json +4 -1
  64. /package/dist/assets/{landing-proof-driven-BxZZh5r5.js → landing-proof-driven-DNPRKL_p.js} +0 -0
@@ -118,7 +118,7 @@ function denormalize(value, array) {
118
118
  throw new Error("Invalid component type.");
119
119
  }
120
120
  }
121
- function normalize$4(value, array) {
121
+ function normalize$5(value, array) {
122
122
  switch (array.constructor) {
123
123
  case Float32Array:
124
124
  return value;
@@ -1692,9 +1692,9 @@ class Quaternion {
1692
1692
  let s = 1 - t;
1693
1693
  const cos2 = x0 * x1 + y0 * y1 + z0 * z1 + w0 * w1, dir = cos2 >= 0 ? 1 : -1, sqrSin = 1 - cos2 * cos2;
1694
1694
  if (sqrSin > Number.EPSILON) {
1695
- const sin2 = Math.sqrt(sqrSin), len = Math.atan2(sin2, cos2 * dir);
1696
- s = Math.sin(s * len) / sin2;
1697
- t = Math.sin(t * len) / sin2;
1695
+ const sin2 = Math.sqrt(sqrSin), len2 = Math.atan2(sin2, cos2 * dir);
1696
+ s = Math.sin(s * len2) / sin2;
1697
+ t = Math.sin(t * len2) / sin2;
1698
1698
  }
1699
1699
  const tDir = t * dir;
1700
1700
  x0 = x0 * s + x1 * tDir;
@@ -6531,7 +6531,7 @@ class BufferAttribute {
6531
6531
  * @return {BufferAttribute} A reference to this instance.
6532
6532
  */
6533
6533
  setComponent(index2, component, value) {
6534
- if (this.normalized) value = normalize$4(value, this.array);
6534
+ if (this.normalized) value = normalize$5(value, this.array);
6535
6535
  this.array[index2 * this.itemSize + component] = value;
6536
6536
  return this;
6537
6537
  }
@@ -6554,7 +6554,7 @@ class BufferAttribute {
6554
6554
  * @return {BufferAttribute} A reference to this instance.
6555
6555
  */
6556
6556
  setX(index2, x2) {
6557
- if (this.normalized) x2 = normalize$4(x2, this.array);
6557
+ if (this.normalized) x2 = normalize$5(x2, this.array);
6558
6558
  this.array[index2 * this.itemSize] = x2;
6559
6559
  return this;
6560
6560
  }
@@ -6577,7 +6577,7 @@ class BufferAttribute {
6577
6577
  * @return {BufferAttribute} A reference to this instance.
6578
6578
  */
6579
6579
  setY(index2, y2) {
6580
- if (this.normalized) y2 = normalize$4(y2, this.array);
6580
+ if (this.normalized) y2 = normalize$5(y2, this.array);
6581
6581
  this.array[index2 * this.itemSize + 1] = y2;
6582
6582
  return this;
6583
6583
  }
@@ -6600,7 +6600,7 @@ class BufferAttribute {
6600
6600
  * @return {BufferAttribute} A reference to this instance.
6601
6601
  */
6602
6602
  setZ(index2, z2) {
6603
- if (this.normalized) z2 = normalize$4(z2, this.array);
6603
+ if (this.normalized) z2 = normalize$5(z2, this.array);
6604
6604
  this.array[index2 * this.itemSize + 2] = z2;
6605
6605
  return this;
6606
6606
  }
@@ -6623,7 +6623,7 @@ class BufferAttribute {
6623
6623
  * @return {BufferAttribute} A reference to this instance.
6624
6624
  */
6625
6625
  setW(index2, w2) {
6626
- if (this.normalized) w2 = normalize$4(w2, this.array);
6626
+ if (this.normalized) w2 = normalize$5(w2, this.array);
6627
6627
  this.array[index2 * this.itemSize + 3] = w2;
6628
6628
  return this;
6629
6629
  }
@@ -6638,8 +6638,8 @@ class BufferAttribute {
6638
6638
  setXY(index2, x2, y2) {
6639
6639
  index2 *= this.itemSize;
6640
6640
  if (this.normalized) {
6641
- x2 = normalize$4(x2, this.array);
6642
- y2 = normalize$4(y2, this.array);
6641
+ x2 = normalize$5(x2, this.array);
6642
+ y2 = normalize$5(y2, this.array);
6643
6643
  }
6644
6644
  this.array[index2 + 0] = x2;
6645
6645
  this.array[index2 + 1] = y2;
@@ -6657,9 +6657,9 @@ class BufferAttribute {
6657
6657
  setXYZ(index2, x2, y2, z2) {
6658
6658
  index2 *= this.itemSize;
6659
6659
  if (this.normalized) {
6660
- x2 = normalize$4(x2, this.array);
6661
- y2 = normalize$4(y2, this.array);
6662
- z2 = normalize$4(z2, this.array);
6660
+ x2 = normalize$5(x2, this.array);
6661
+ y2 = normalize$5(y2, this.array);
6662
+ z2 = normalize$5(z2, this.array);
6663
6663
  }
6664
6664
  this.array[index2 + 0] = x2;
6665
6665
  this.array[index2 + 1] = y2;
@@ -6679,10 +6679,10 @@ class BufferAttribute {
6679
6679
  setXYZW(index2, x2, y2, z2, w2) {
6680
6680
  index2 *= this.itemSize;
6681
6681
  if (this.normalized) {
6682
- x2 = normalize$4(x2, this.array);
6683
- y2 = normalize$4(y2, this.array);
6684
- z2 = normalize$4(z2, this.array);
6685
- w2 = normalize$4(w2, this.array);
6682
+ x2 = normalize$5(x2, this.array);
6683
+ y2 = normalize$5(y2, this.array);
6684
+ z2 = normalize$5(z2, this.array);
6685
+ w2 = normalize$5(w2, this.array);
6686
6686
  }
6687
6687
  this.array[index2 + 0] = x2;
6688
6688
  this.array[index2 + 1] = y2;
@@ -7688,9 +7688,9 @@ function splitEarcut(start, triangles, dim, minX, minY, invSize) {
7688
7688
  }
7689
7689
  function eliminateHoles(data, holeIndices, outerNode, dim) {
7690
7690
  const queue = [];
7691
- for (let i = 0, len = holeIndices.length; i < len; i++) {
7691
+ for (let i = 0, len2 = holeIndices.length; i < len2; i++) {
7692
7692
  const start = holeIndices[i] * dim;
7693
- const end = i < len - 1 ? holeIndices[i + 1] * dim : data.length;
7693
+ const end = i < len2 - 1 ? holeIndices[i + 1] * dim : data.length;
7694
7694
  const list = linkedList(data, start, end, dim, false);
7695
7695
  if (list === list.next) list.steiner = true;
7696
7696
  queue.push(getLeftmost(list));
@@ -8200,9 +8200,9 @@ function multiplyMat4(a2, b) {
8200
8200
  return out;
8201
8201
  }
8202
8202
  function normalizeVec3$3(v) {
8203
- const len = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
8204
- if (len < EPS$6) throw new Error("Axis must be non-zero");
8205
- return [v[0] / len, v[1] / len, v[2] / len];
8203
+ const len2 = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
8204
+ if (len2 < EPS$6) throw new Error("Axis must be non-zero");
8205
+ return [v[0] / len2, v[1] / len2, v[2] / len2];
8206
8206
  }
8207
8207
  function transformPoint$2(m2, p2, w2) {
8208
8208
  const x2 = p2[0], y2 = p2[1], z2 = p2[2];
@@ -10285,6 +10285,8 @@ function findShapePrimaryQueryOwner(plan) {
10285
10285
  case "chamfer":
10286
10286
  case "filletEdges":
10287
10287
  case "cornerYBlend":
10288
+ case "faceFillet":
10289
+ case "fullRound":
10288
10290
  case "chamferEdges":
10289
10291
  case "draft":
10290
10292
  case "offsetSolid":
@@ -10309,6 +10311,7 @@ function findShapePrimaryQueryOwner(plan) {
10309
10311
  case "importedMesh":
10310
10312
  case "sdf":
10311
10313
  case "fromSlices":
10314
+ case "fromSectionFrames":
10312
10315
  case "analyticSurface":
10313
10316
  case "nurbsSurface":
10314
10317
  case "surfaceRuled":
@@ -10331,6 +10334,8 @@ function visitBaseShape(plan, visit) {
10331
10334
  case "chamfer":
10332
10335
  case "filletEdges":
10333
10336
  case "cornerYBlend":
10337
+ case "faceFillet":
10338
+ case "fullRound":
10334
10339
  case "chamferEdges":
10335
10340
  case "draft":
10336
10341
  case "offsetSolid":
@@ -10356,6 +10361,7 @@ function visitBaseShape(plan, visit) {
10356
10361
  case "importedMesh":
10357
10362
  case "sdf":
10358
10363
  case "fromSlices":
10364
+ case "fromSectionFrames":
10359
10365
  case "analyticSurface":
10360
10366
  case "nurbsSurface":
10361
10367
  case "surfaceRuled":
@@ -10509,6 +10515,13 @@ function cloneAnalyticSurfaceCompilePlan(surface) {
10509
10515
  return assertExhaustive(surface);
10510
10516
  }
10511
10517
  }
10518
+ function cloneVariableRadiusSpec(spec) {
10519
+ if (!spec) return void 0;
10520
+ if ("stations" in spec) {
10521
+ return { kind: "stations", stations: spec.stations.map((s) => ({ at: canonicalNumber(s.at), radius: canonicalNumber(s.radius) })) };
10522
+ }
10523
+ return { kind: "linear", start: canonicalNumber(spec.start), end: canonicalNumber(spec.end) };
10524
+ }
10512
10525
  function cloneProfileTransform(step) {
10513
10526
  switch (step.kind) {
10514
10527
  case "translate":
@@ -10576,6 +10589,12 @@ function cloneSurfaceBoundaryCompilePlan(plan) {
10576
10589
  support: plan.support ? cloneSurfaceSupportCompilePlan(plan.support) : void 0
10577
10590
  };
10578
10591
  }
10592
+ function cloneSurfaceInteriorConstraintCompilePlan(plan) {
10593
+ return {
10594
+ curve: cloneSweepPathCompilePlan(plan.curve),
10595
+ continuity: plan.continuity
10596
+ };
10597
+ }
10579
10598
  function cloneHoleCounterboreCompilePlan(plan) {
10580
10599
  return {
10581
10600
  radius: canonicalNumber(plan.radius),
@@ -10899,7 +10918,7 @@ function cloneProfileEdge(edge) {
10899
10918
  }
10900
10919
  }
10901
10920
  function cloneShapeCompilePlan(plan) {
10902
- var _a3, _b3, _c2;
10921
+ var _a3, _b3, _c2, _d2, _e2;
10903
10922
  if (!plan) return null;
10904
10923
  let result;
10905
10924
  switch (plan.kind) {
@@ -11078,6 +11097,7 @@ function cloneShapeCompilePlan(plan) {
11078
11097
  kind: "filletEdges",
11079
11098
  base: cloneShapeCompilePlan(plan.base),
11080
11099
  radius: plan.radius,
11100
+ variableRadius: cloneVariableRadiusSpec(plan.variableRadius),
11081
11101
  segments: plan.segments,
11082
11102
  continuity: plan.continuity,
11083
11103
  edgeTargets: (_a3 = plan.edgeTargets) == null ? void 0 : _a3.map(cloneEdgeFeatureTarget),
@@ -11085,6 +11105,32 @@ function cloneShapeCompilePlan(plan) {
11085
11105
  edgeSelection: plan.edgeSelection
11086
11106
  };
11087
11107
  break;
11108
+ case "faceFillet":
11109
+ result = {
11110
+ kind: "faceFillet",
11111
+ base: cloneShapeCompilePlan(plan.base),
11112
+ radius: plan.radius,
11113
+ variableRadius: cloneVariableRadiusSpec(plan.variableRadius),
11114
+ segments: plan.segments,
11115
+ continuity: plan.continuity,
11116
+ faceA: cloneFaceQueryRef(plan.faceA),
11117
+ faceB: cloneFaceQueryRef(plan.faceB),
11118
+ edgeTargets: plan.edgeTargets.map(cloneEdgeFeatureTarget)
11119
+ };
11120
+ break;
11121
+ case "fullRound":
11122
+ result = {
11123
+ kind: "fullRound",
11124
+ base: cloneShapeCompilePlan(plan.base),
11125
+ radius: plan.radius,
11126
+ segments: plan.segments,
11127
+ continuity: plan.continuity,
11128
+ centerFace: cloneFaceQueryRef(plan.centerFace),
11129
+ sideFaceA: cloneFaceQueryRef(plan.sideFaceA),
11130
+ sideFaceB: cloneFaceQueryRef(plan.sideFaceB),
11131
+ edgeTargets: plan.edgeTargets.map(cloneEdgeFeatureTarget)
11132
+ };
11133
+ break;
11088
11134
  case "cornerYBlend":
11089
11135
  result = {
11090
11136
  kind: "cornerYBlend",
@@ -11156,6 +11202,20 @@ function cloneShapeCompilePlan(plan) {
11156
11202
  boundsPadding: canonicalNumber(plan.boundsPadding)
11157
11203
  };
11158
11204
  break;
11205
+ case "fromSectionFrames":
11206
+ result = {
11207
+ kind: "fromSectionFrames",
11208
+ slices: plan.slices.map((s) => ({
11209
+ origin: s.origin.map(canonicalNumber),
11210
+ normal: s.normal.map(canonicalNumber),
11211
+ tangentU: s.tangentU.map(canonicalNumber),
11212
+ tangentV: s.tangentV.map(canonicalNumber),
11213
+ profile: cloneProfileCompilePlan(s.profile)
11214
+ })),
11215
+ edgeLength: canonicalNumber(plan.edgeLength),
11216
+ boundsPadding: canonicalNumber(plan.boundsPadding)
11217
+ };
11218
+ break;
11159
11219
  case "importedStep":
11160
11220
  result = { kind: "importedStep", filePath: plan.filePath, fileData: plan.fileData };
11161
11221
  break;
@@ -11217,7 +11277,9 @@ function cloneShapeCompilePlan(plan) {
11217
11277
  boundaries: plan.boundaries.map(cloneSurfaceBoundaryCompilePlan),
11218
11278
  style: plan.style,
11219
11279
  resolution: plan.resolution,
11220
- allowApproximation: plan.allowApproximation
11280
+ allowApproximation: plan.allowApproximation,
11281
+ interiorCurves: (_d2 = plan.interiorCurves) == null ? void 0 : _d2.map(cloneSurfaceInteriorConstraintCompilePlan),
11282
+ interiorPoints: (_e2 = plan.interiorPoints) == null ? void 0 : _e2.map(canonicalVec3)
11221
11283
  };
11222
11284
  break;
11223
11285
  case "surfaceSew":
@@ -11344,12 +11406,12 @@ function profilePlanFromCrossSection(cross4) {
11344
11406
  return { kind: "boolean", op: "union", profiles: plans, transforms: [] };
11345
11407
  }
11346
11408
  function buildTrimByPlaneShapeCompilePlan(base, normal2, originOffset) {
11347
- const len = Math.sqrt(normal2[0] * normal2[0] + normal2[1] * normal2[1] + normal2[2] * normal2[2]);
11348
- if (len === 0) throw new Error("trimByPlane: normal vector must not be zero");
11349
- const nx = normal2[0] / len;
11350
- const ny = normal2[1] / len;
11351
- const nz = normal2[2] / len;
11352
- const adjustedOffset = originOffset / len;
11409
+ const len2 = Math.sqrt(normal2[0] * normal2[0] + normal2[1] * normal2[1] + normal2[2] * normal2[2]);
11410
+ if (len2 === 0) throw new Error("trimByPlane: normal vector must not be zero");
11411
+ const nx = normal2[0] / len2;
11412
+ const ny = normal2[1] / len2;
11413
+ const nz = normal2[2] / len2;
11414
+ const adjustedOffset = originOffset / len2;
11353
11415
  return {
11354
11416
  kind: "trimByPlane",
11355
11417
  base: cloneShapeCompilePlan(base),
@@ -11378,6 +11440,10 @@ function buildSurfaceThickenShapeCompilePlan(base, thickness) {
11378
11440
  thickness: canonicalNumber(thickness)
11379
11441
  };
11380
11442
  }
11443
+ function maxVariableRadius(spec) {
11444
+ if ("stations" in spec) return Math.max(...spec.stations.map((s) => s.radius));
11445
+ return Math.max(spec.start, spec.end);
11446
+ }
11381
11447
  function generateClampedKnots(n, degree) {
11382
11448
  const m2 = n + degree + 1;
11383
11449
  const knots = new Array(m2);
@@ -11531,32 +11597,32 @@ function remapToKnotDomain(t, n, degree, knots) {
11531
11597
  return uMin + Math.max(0, Math.min(1, t)) * (uMax - uMin);
11532
11598
  }
11533
11599
  const EPSILON$1 = 1e-9;
11534
- function add$2(a2, b) {
11600
+ function add$3(a2, b) {
11535
11601
  return [a2[0] + b[0], a2[1] + b[1], a2[2] + b[2]];
11536
11602
  }
11537
11603
  function scale$2(v, factor) {
11538
11604
  return [v[0] * factor, v[1] * factor, v[2] * factor];
11539
11605
  }
11540
- function sub$3(a2, b) {
11606
+ function sub$4(a2, b) {
11541
11607
  return [a2[0] - b[0], a2[1] - b[1], a2[2] - b[2]];
11542
11608
  }
11543
- function dot$3(a2, b) {
11609
+ function dot$4(a2, b) {
11544
11610
  return a2[0] * b[0] + a2[1] * b[1] + a2[2] * b[2];
11545
11611
  }
11546
- function cross$3(a2, b) {
11612
+ function cross$4(a2, b) {
11547
11613
  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]];
11548
11614
  }
11549
11615
  function rotateAroundAxis$1(v, axis, angleRad) {
11550
11616
  const c2 = Math.cos(angleRad);
11551
11617
  const s = Math.sin(angleRad);
11552
11618
  const term1 = scale$2(v, c2);
11553
- const term2 = scale$2(cross$3(axis, v), s);
11554
- const term3 = scale$2(axis, dot$3(axis, v) * (1 - c2));
11555
- return add$2(add$2(term1, term2), term3);
11619
+ const term2 = scale$2(cross$4(axis, v), s);
11620
+ const term3 = scale$2(axis, dot$4(axis, v) * (1 - c2));
11621
+ return add$3(add$3(term1, term2), term3);
11556
11622
  }
11557
11623
  function arcPointAt(segment, t) {
11558
11624
  const sweepRad = segment.sweepDeg * Math.PI / 180 * t;
11559
- return add$2(segment.center, rotateAroundAxis$1(sub$3(segment.start, segment.center), segment.axis, sweepRad));
11625
+ return add$3(segment.center, rotateAroundAxis$1(sub$4(segment.start, segment.center), segment.axis, sweepRad));
11560
11626
  }
11561
11627
  function pushUnique(points, point2) {
11562
11628
  const prev = points[points.length - 1];
@@ -11579,7 +11645,7 @@ function routePointAt(plan, t) {
11579
11645
  const next = station + segment.length;
11580
11646
  if (target <= next || segment === plan.segments[plan.segments.length - 1]) {
11581
11647
  const localT = segment.length <= EPSILON$1 ? 1 : Math.max(0, Math.min(1, (target - station) / segment.length));
11582
- if (segment.kind === "line") return add$2(segment.from, scale$2(sub$3(segment.to, segment.from), localT));
11648
+ if (segment.kind === "line") return add$3(segment.from, scale$2(sub$4(segment.to, segment.from), localT));
11583
11649
  return arcPointAt(segment, localT);
11584
11650
  }
11585
11651
  station = next;
@@ -11599,7 +11665,7 @@ function sampleRoute3DCompilePlan(plan, options) {
11599
11665
  pushUnique(points, start);
11600
11666
  for (let i = 1; i <= intervals; i += 1) {
11601
11667
  if (segment.kind === "line") {
11602
- pushUnique(points, add$2(segment.from, scale$2(sub$3(segment.to, segment.from), i / intervals)));
11668
+ pushUnique(points, add$3(segment.from, scale$2(sub$4(segment.to, segment.from), i / intervals)));
11603
11669
  } else {
11604
11670
  pushUnique(points, arcPointAt(segment, i / intervals));
11605
11671
  }
@@ -11830,10 +11896,10 @@ const EPS$4 = 1e-8;
11830
11896
  function midpoint$1(start, end) {
11831
11897
  return [(start[0] + end[0]) * 0.5, (start[1] + end[1]) * 0.5, (start[2] + end[2]) * 0.5];
11832
11898
  }
11833
- function normalize$3(v) {
11834
- const len = Math.hypot(v[0], v[1], v[2]);
11835
- if (len <= EPS$4) throw new Error("Edge feature selection requires a non-zero direction vector");
11836
- return [v[0] / len, v[1] / len, v[2] / len];
11899
+ function normalize$4(v) {
11900
+ const len2 = Math.hypot(v[0], v[1], v[2]);
11901
+ if (len2 <= EPS$4) throw new Error("Edge feature selection requires a non-zero direction vector");
11902
+ return [v[0] / len2, v[1] / len2, v[2] / len2];
11837
11903
  }
11838
11904
  function subtract(a2, b) {
11839
11905
  return [a2[0] - b[0], a2[1] - b[1], a2[2] - b[2]];
@@ -11910,11 +11976,11 @@ function rigidTransformForEdgeStep(step) {
11910
11976
  return Transform.rotationAxis([step.axisX, step.axisY, step.axisZ], step.degrees, [step.pivotX, step.pivotY, step.pivotZ]);
11911
11977
  case "mirror": {
11912
11978
  const [nx0, ny0, nz0] = [step.normalX, step.normalY, step.normalZ];
11913
- const len = Math.hypot(nx0, ny0, nz0);
11914
- if (len <= EPS$4) return Transform.identity();
11915
- const nx = nx0 / len;
11916
- const ny = ny0 / len;
11917
- const nz = nz0 / len;
11979
+ const len2 = Math.hypot(nx0, ny0, nz0);
11980
+ if (len2 <= EPS$4) return Transform.identity();
11981
+ const nx = nx0 / len2;
11982
+ const ny = ny0 / len2;
11983
+ const nz = nz0 / len2;
11918
11984
  return Transform.from([
11919
11985
  1 - 2 * nx * nx,
11920
11986
  -2 * ny * nx,
@@ -11994,6 +12060,8 @@ function searchOwnerMatch(plan, owner) {
11994
12060
  }
11995
12061
  case "filletEdges":
11996
12062
  case "cornerYBlend":
12063
+ case "faceFillet":
12064
+ case "fullRound":
11997
12065
  case "chamferEdges":
11998
12066
  case "draft":
11999
12067
  case "offsetSolid":
@@ -12027,6 +12095,7 @@ function searchOwnerMatch(plan, owner) {
12027
12095
  case "importedMesh":
12028
12096
  case "sdf":
12029
12097
  case "fromSlices":
12098
+ case "fromSectionFrames":
12030
12099
  case "analyticSurface":
12031
12100
  case "nurbsSurface":
12032
12101
  case "surfaceRuled":
@@ -12116,7 +12185,7 @@ function propagateCandidateAcrossRewrite(plan, candidate) {
12116
12185
  return edgeSuccess(candidate.selection, preservedEntry.query);
12117
12186
  }
12118
12187
  function resolvePropagatedEdgeQueryAtOwnerBase(ownerBase, ref) {
12119
- if (ownerBase.kind === "box" || ownerBase.kind === "cylinder" || ownerBase.kind === "sphere" || ownerBase.kind === "extrude" || ownerBase.kind === "sheetMetal" || ownerBase.kind === "revolve" || ownerBase.kind === "loft" || ownerBase.kind === "sweep" || ownerBase.kind === "variableSweep" || ownerBase.kind === "transform" || ownerBase.kind === "queryOwner" || ownerBase.kind === "filletEdges" || ownerBase.kind === "cornerYBlend" || ownerBase.kind === "chamferEdges" || ownerBase.kind === "importedMesh" || ownerBase.kind === "sdf" || ownerBase.kind === "fromSlices" || ownerBase.kind === "analyticSurface" || ownerBase.kind === "nurbsSurface" || ownerBase.kind === "surfaceRuled" || ownerBase.kind === "surfaceFill" || ownerBase.kind === "surfaceSew" || ownerBase.kind === "surfaceExtend" || ownerBase.kind === "surfaceThicken" || ownerBase.kind === "surfaceSolid" || ownerBase.kind === "importedStep" || ownerBase.kind === "torus" || ownerBase.kind === "draft" || ownerBase.kind === "offsetSolid") {
12188
+ if (ownerBase.kind === "box" || ownerBase.kind === "cylinder" || ownerBase.kind === "sphere" || ownerBase.kind === "extrude" || ownerBase.kind === "sheetMetal" || ownerBase.kind === "revolve" || ownerBase.kind === "loft" || ownerBase.kind === "sweep" || ownerBase.kind === "variableSweep" || ownerBase.kind === "transform" || ownerBase.kind === "queryOwner" || ownerBase.kind === "filletEdges" || ownerBase.kind === "cornerYBlend" || ownerBase.kind === "faceFillet" || ownerBase.kind === "fullRound" || ownerBase.kind === "chamferEdges" || ownerBase.kind === "importedMesh" || ownerBase.kind === "sdf" || ownerBase.kind === "fromSlices" || ownerBase.kind === "fromSectionFrames" || ownerBase.kind === "analyticSurface" || ownerBase.kind === "nurbsSurface" || ownerBase.kind === "surfaceRuled" || ownerBase.kind === "surfaceFill" || ownerBase.kind === "surfaceSew" || ownerBase.kind === "surfaceExtend" || ownerBase.kind === "surfaceThicken" || ownerBase.kind === "surfaceSolid" || ownerBase.kind === "importedStep" || ownerBase.kind === "torus" || ownerBase.kind === "draft" || ownerBase.kind === "offsetSolid") {
12120
12189
  return edgeIssue(
12121
12190
  "edge-query-propagation-mismatch",
12122
12191
  "The selected propagated edge query does not point at a topology-rewrite result on this target shape."
@@ -12249,8 +12318,8 @@ function extrudeEdgeSelection(plan, edgeName) {
12249
12318
  }
12250
12319
  const points = plan.profile.points;
12251
12320
  const [bl, br, _tr, tl] = points;
12252
- const u2 = normalize$3([br[0] - bl[0], br[1] - bl[1], 0]);
12253
- const v = normalize$3([tl[0] - bl[0], tl[1] - bl[1], 0]);
12321
+ const u2 = normalize$4([br[0] - bl[0], br[1] - bl[1], 0]);
12322
+ const v = normalize$4([tl[0] - bl[0], tl[1] - bl[1], 0]);
12254
12323
  const vertex2 = points[index2];
12255
12324
  const quadrant = index2 === 0 ? [1, -1] : index2 === 1 ? [-1, -1] : index2 === 2 ? [-1, 1] : [1, 1];
12256
12325
  return {
@@ -12271,9 +12340,9 @@ function extrudeEdgeSelection(plan, edgeName) {
12271
12340
  function applySelectionTransform(selection, transform) {
12272
12341
  const start = transform.point(selection.start);
12273
12342
  const end = transform.point(selection.end);
12274
- const basisX = normalize$3(transform.vector(selection.basisX));
12275
- const basisY = normalize$3(transform.vector(selection.basisY));
12276
- const axis = normalize$3(subtract(end, start));
12343
+ const basisX = normalize$4(transform.vector(selection.basisX));
12344
+ const basisY = normalize$4(transform.vector(selection.basisY));
12345
+ const axis = normalize$4(subtract(end, start));
12277
12346
  return {
12278
12347
  kind: "line-segment",
12279
12348
  edgeName: selection.edgeName,
@@ -12308,6 +12377,8 @@ function resolveSelectionFromOwnerBase(plan, edgeName) {
12308
12377
  case "chamfer":
12309
12378
  case "filletEdges":
12310
12379
  case "cornerYBlend":
12380
+ case "faceFillet":
12381
+ case "fullRound":
12311
12382
  case "chamferEdges":
12312
12383
  case "draft":
12313
12384
  case "offsetSolid":
@@ -12327,6 +12398,7 @@ function resolveSelectionFromOwnerBase(plan, edgeName) {
12327
12398
  case "importedMesh":
12328
12399
  case "sdf":
12329
12400
  case "fromSlices":
12401
+ case "fromSectionFrames":
12330
12402
  case "analyticSurface":
12331
12403
  case "nurbsSurface":
12332
12404
  case "surfaceRuled":
@@ -12370,7 +12442,7 @@ function resolveSupportedEdgeFeatureSelection(plan, ref) {
12370
12442
  return edgeIssue("edge-query-unsupported-after-rewrite", descendant.reason);
12371
12443
  }
12372
12444
  function resolveEdgeChainAtOwnerBase(ownerBase, ref) {
12373
- if (ownerBase.kind === "box" || ownerBase.kind === "cylinder" || ownerBase.kind === "sphere" || ownerBase.kind === "extrude" || ownerBase.kind === "sheetMetal" || ownerBase.kind === "revolve" || ownerBase.kind === "loft" || ownerBase.kind === "sweep" || ownerBase.kind === "variableSweep" || ownerBase.kind === "transform" || ownerBase.kind === "queryOwner" || ownerBase.kind === "filletEdges" || ownerBase.kind === "cornerYBlend" || ownerBase.kind === "chamferEdges" || ownerBase.kind === "importedMesh" || ownerBase.kind === "sdf" || ownerBase.kind === "fromSlices" || ownerBase.kind === "analyticSurface" || ownerBase.kind === "nurbsSurface" || ownerBase.kind === "surfaceRuled" || ownerBase.kind === "surfaceFill" || ownerBase.kind === "surfaceSew" || ownerBase.kind === "surfaceExtend" || ownerBase.kind === "surfaceThicken" || ownerBase.kind === "surfaceSolid" || ownerBase.kind === "importedStep" || ownerBase.kind === "torus" || ownerBase.kind === "draft" || ownerBase.kind === "offsetSolid") {
12445
+ if (ownerBase.kind === "box" || ownerBase.kind === "cylinder" || ownerBase.kind === "sphere" || ownerBase.kind === "extrude" || ownerBase.kind === "sheetMetal" || ownerBase.kind === "revolve" || ownerBase.kind === "loft" || ownerBase.kind === "sweep" || ownerBase.kind === "variableSweep" || ownerBase.kind === "transform" || ownerBase.kind === "queryOwner" || ownerBase.kind === "filletEdges" || ownerBase.kind === "cornerYBlend" || ownerBase.kind === "faceFillet" || ownerBase.kind === "fullRound" || ownerBase.kind === "chamferEdges" || ownerBase.kind === "importedMesh" || ownerBase.kind === "sdf" || ownerBase.kind === "fromSlices" || ownerBase.kind === "fromSectionFrames" || ownerBase.kind === "analyticSurface" || ownerBase.kind === "nurbsSurface" || ownerBase.kind === "surfaceRuled" || ownerBase.kind === "surfaceFill" || ownerBase.kind === "surfaceSew" || ownerBase.kind === "surfaceExtend" || ownerBase.kind === "surfaceThicken" || ownerBase.kind === "surfaceSolid" || ownerBase.kind === "importedStep" || ownerBase.kind === "torus" || ownerBase.kind === "draft" || ownerBase.kind === "offsetSolid") {
12374
12446
  return {
12375
12447
  kind: "unsupported",
12376
12448
  query: cloneEdgeQueryRef(ref),
@@ -12403,7 +12475,7 @@ function resolveEdgeChainAtOwnerBase(ownerBase, ref) {
12403
12475
  };
12404
12476
  }
12405
12477
  function resolveCreatedEdgeChainAtOwnerBase(ownerBase, ref) {
12406
- if (ownerBase.kind === "box" || ownerBase.kind === "cylinder" || ownerBase.kind === "sphere" || ownerBase.kind === "extrude" || ownerBase.kind === "sheetMetal" || ownerBase.kind === "revolve" || ownerBase.kind === "loft" || ownerBase.kind === "sweep" || ownerBase.kind === "variableSweep" || ownerBase.kind === "transform" || ownerBase.kind === "queryOwner" || ownerBase.kind === "filletEdges" || ownerBase.kind === "cornerYBlend" || ownerBase.kind === "chamferEdges" || ownerBase.kind === "importedMesh" || ownerBase.kind === "sdf" || ownerBase.kind === "fromSlices" || ownerBase.kind === "analyticSurface" || ownerBase.kind === "nurbsSurface" || ownerBase.kind === "surfaceRuled" || ownerBase.kind === "surfaceFill" || ownerBase.kind === "surfaceSew" || ownerBase.kind === "surfaceExtend" || ownerBase.kind === "surfaceThicken" || ownerBase.kind === "surfaceSolid" || ownerBase.kind === "importedStep" || ownerBase.kind === "torus" || ownerBase.kind === "draft" || ownerBase.kind === "offsetSolid") {
12478
+ if (ownerBase.kind === "box" || ownerBase.kind === "cylinder" || ownerBase.kind === "sphere" || ownerBase.kind === "extrude" || ownerBase.kind === "sheetMetal" || ownerBase.kind === "revolve" || ownerBase.kind === "loft" || ownerBase.kind === "sweep" || ownerBase.kind === "variableSweep" || ownerBase.kind === "transform" || ownerBase.kind === "queryOwner" || ownerBase.kind === "filletEdges" || ownerBase.kind === "cornerYBlend" || ownerBase.kind === "faceFillet" || ownerBase.kind === "fullRound" || ownerBase.kind === "chamferEdges" || ownerBase.kind === "importedMesh" || ownerBase.kind === "sdf" || ownerBase.kind === "fromSlices" || ownerBase.kind === "fromSectionFrames" || ownerBase.kind === "analyticSurface" || ownerBase.kind === "nurbsSurface" || ownerBase.kind === "surfaceRuled" || ownerBase.kind === "surfaceFill" || ownerBase.kind === "surfaceSew" || ownerBase.kind === "surfaceExtend" || ownerBase.kind === "surfaceThicken" || ownerBase.kind === "surfaceSolid" || ownerBase.kind === "importedStep" || ownerBase.kind === "torus" || ownerBase.kind === "draft" || ownerBase.kind === "offsetSolid") {
12407
12479
  return {
12408
12480
  kind: "unsupported",
12409
12481
  query: cloneEdgeQueryRef(ref),
@@ -12936,16 +13008,16 @@ function extractEdgeSegments(mesh) {
12936
13008
  let nx = e1y * e2z - e1z * e2y;
12937
13009
  let ny = e1z * e2x - e1x * e2z;
12938
13010
  let nz = e1x * e2y - e1y * e2x;
12939
- const len = Math.sqrt(nx * nx + ny * ny + nz * nz) || 1;
12940
- nx /= len;
12941
- ny /= len;
12942
- nz /= len;
13011
+ const len2 = Math.sqrt(nx * nx + ny * ny + nz * nz) || 1;
13012
+ nx /= len2;
13013
+ ny /= len2;
13014
+ nz /= len2;
12943
13015
  faceNx[t] = nx;
12944
13016
  faceNy[t] = ny;
12945
13017
  faceNz[t] = nz;
12946
13018
  }
12947
13019
  const numVerts = vertProperties.length / numProp;
12948
- const canon = buildCanonicalMap$2(numVerts, mesh.mergeFromVert, mesh.mergeToVert);
13020
+ const canon = buildCanonicalMap$3(numVerts, mesh.mergeFromVert, mesh.mergeToVert);
12949
13021
  const MAX_NUMERIC = 1 << 21;
12950
13022
  let maxCanon = 0;
12951
13023
  for (let i = 0; i < numVerts; i++) {
@@ -13034,7 +13106,7 @@ function buildEdgeSegment(index2, origVa, origVb, vertProperties, numProp, triA,
13034
13106
  boundary
13035
13107
  };
13036
13108
  }
13037
- function buildCanonicalMap$2(numVerts, mergeFromVert, mergeToVert) {
13109
+ function buildCanonicalMap$3(numVerts, mergeFromVert, mergeToVert) {
13038
13110
  const canon = new Uint32Array(numVerts);
13039
13111
  for (let i = 0; i < numVerts; i++) canon[i] = i;
13040
13112
  if (mergeFromVert && mergeToVert) {
@@ -14040,10 +14112,10 @@ function distSq$1(a2, b) {
14040
14112
  function vecLength$2(v) {
14041
14113
  return Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
14042
14114
  }
14043
- function normalize$2(v) {
14044
- const len = vecLength$2(v);
14045
- if (len < 1e-12) return [0, 0, 0];
14046
- return [v[0] / len, v[1] / len, v[2] / len];
14115
+ function normalize$3(v) {
14116
+ const len2 = vecLength$2(v);
14117
+ if (len2 < 1e-12) return [0, 0, 0];
14118
+ return [v[0] / len2, v[1] / len2, v[2] / len2];
14047
14119
  }
14048
14120
  function absDot$1(a2, b) {
14049
14121
  return Math.abs(a2[0] * b[0] + a2[1] * b[1] + a2[2] * b[2]);
@@ -14076,11 +14148,11 @@ function applyEdgeQueryFilters(edges, query) {
14076
14148
  result = result.filter((e) => e.length <= max2);
14077
14149
  }
14078
14150
  if (query.parallel) {
14079
- const dir = normalize$2(query.parallel);
14151
+ const dir = normalize$3(query.parallel);
14080
14152
  result = result.filter((e) => absDot$1(e.direction, dir) >= cosAngleTol);
14081
14153
  }
14082
14154
  if (query.perpendicular) {
14083
- const dir = normalize$2(query.perpendicular);
14155
+ const dir = normalize$3(query.perpendicular);
14084
14156
  const sinAngleTol = Math.sin(angleTol * Math.PI / 180);
14085
14157
  result = result.filter((e) => absDot$1(e.direction, dir) <= sinAngleTol);
14086
14158
  }
@@ -14328,6 +14400,8 @@ function lowerBaseShellPlanToConcretePlan(plan, thickness, openFaces) {
14328
14400
  case "chamfer":
14329
14401
  case "filletEdges":
14330
14402
  case "cornerYBlend":
14403
+ case "faceFillet":
14404
+ case "fullRound":
14331
14405
  case "chamferEdges":
14332
14406
  case "draft":
14333
14407
  case "offsetSolid":
@@ -14352,6 +14426,7 @@ function lowerBaseShellPlanToConcretePlan(plan, thickness, openFaces) {
14352
14426
  case "importedMesh":
14353
14427
  case "sdf":
14354
14428
  case "fromSlices":
14429
+ case "fromSectionFrames":
14355
14430
  case "analyticSurface":
14356
14431
  case "nurbsSurface":
14357
14432
  case "surfaceRuled":
@@ -14683,6 +14758,8 @@ function planComplexityScore(value) {
14683
14758
  case "fillet":
14684
14759
  case "filletEdges":
14685
14760
  case "cornerYBlend":
14761
+ case "faceFillet":
14762
+ case "fullRound":
14686
14763
  case "chamfer":
14687
14764
  case "chamferEdges":
14688
14765
  return 5 + childScore(record.base);
@@ -15482,8 +15559,8 @@ function clampUnit(v) {
15482
15559
  }
15483
15560
  function sphereUVLocal(lx, ly, lz, radius) {
15484
15561
  const u2 = atan2(ly, lx) * radius;
15485
- const len = sqrt$3(lx * lx + ly * ly + lz * lz);
15486
- const v = acos(clampUnit(lz / (len || 1))) * radius;
15562
+ const len2 = sqrt$3(lx * lx + ly * ly + lz * lz);
15563
+ const v = acos(clampUnit(lz / (len2 || 1))) * radius;
15487
15564
  return [u2, v];
15488
15565
  }
15489
15566
  function cylinderUVLocal(lx, ly, lz, radius) {
@@ -16939,7 +17016,7 @@ async function initTruckGeometryWasm() {
16939
17016
  if (_initPromise$1) return _initPromise$1;
16940
17017
  _initPromise$1 = (async () => {
16941
17018
  try {
16942
- const geometryModule = await import("./forgecad_geometry-CZ_IfuvA.js");
17019
+ const geometryModule = await import("./forgecad_geometry-D8rWX7nQ.js");
16943
17020
  const isNode = isNodeRuntime();
16944
17021
  if (isNode) {
16945
17022
  const { readFileSync, existsSync } = await import("./__vite-browser-external-Dhvy_jtL.js");
@@ -17302,6 +17379,9 @@ function topologyToPlacementReferences(topo) {
17302
17379
  }
17303
17380
  return { surfaces, edges };
17304
17381
  }
17382
+ function towardInterior(t, delta) {
17383
+ return t <= 0.5 ? Math.min(t + delta, 0.5) : Math.max(t - delta, 0.5);
17384
+ }
17305
17385
  function requireFinite(v, label) {
17306
17386
  if (!Number.isFinite(v)) throw new Error(`nurbsSurface: ${label} must be finite, got ${v}`);
17307
17387
  }
@@ -17412,13 +17492,32 @@ class NurbsSurface {
17412
17492
  * and Sv positively and cancel under normalization, so they are omitted.
17413
17493
  */
17414
17494
  normalAt(u2, v) {
17495
+ const direct = this.crossNormalAt(u2, v);
17496
+ if (direct) return direct;
17497
+ let delta = 2e-4;
17498
+ while (delta < 0.5) {
17499
+ const limit = this.crossNormalAt(towardInterior(u2, delta), towardInterior(v, delta));
17500
+ if (limit) return limit;
17501
+ delta *= 2;
17502
+ }
17503
+ return [0, 0, 1];
17504
+ }
17505
+ /**
17506
+ * Unit normal from the analytic partials, or null when the parameterization is
17507
+ * degenerate at (u, v) (a partial collapses, so the cross product is tiny
17508
+ * relative to the tangent spans — a pole, not a real normal).
17509
+ */
17510
+ crossNormalAt(u2, v) {
17415
17511
  const { Su, Sv } = this.derivativesAt(u2, v);
17512
+ const spanU = Math.hypot(Su[0], Su[1], Su[2]);
17513
+ const spanV = Math.hypot(Sv[0], Sv[1], Sv[2]);
17514
+ if (spanU <= 1e-6 * spanV || spanV <= 1e-6 * spanU) return null;
17416
17515
  const nx = Su[1] * Sv[2] - Su[2] * Sv[1];
17417
17516
  const ny = Su[2] * Sv[0] - Su[0] * Sv[2];
17418
17517
  const nz = Su[0] * Sv[1] - Su[1] * Sv[0];
17419
- const len = Math.sqrt(nx * nx + ny * ny + nz * nz);
17420
- if (len < 1e-12) return [0, 0, 1];
17421
- return [nx / len, ny / len, nz / len];
17518
+ const len2 = Math.sqrt(nx * nx + ny * ny + nz * nz);
17519
+ if (len2 <= 1e-6 * spanU * spanV) return null;
17520
+ return [nx / len2, ny / len2, nz / len2];
17422
17521
  }
17423
17522
  /** Analytic first partial derivatives S_u, S_v (rational quotient rule). */
17424
17523
  derivativesAt(u2, v) {
@@ -17891,9 +17990,9 @@ function vec3Lerp(a2, b, t) {
17891
17990
  return [a2[0] + (b[0] - a2[0]) * t, a2[1] + (b[1] - a2[1]) * t, a2[2] + (b[2] - a2[2]) * t];
17892
17991
  }
17893
17992
  function vec3Norm(v) {
17894
- const len = vec3Len(v);
17895
- if (len < 1e-9) return [0, 0, 1];
17896
- return [v[0] / len, v[1] / len, v[2] / len];
17993
+ const len2 = vec3Len(v);
17994
+ if (len2 < 1e-9) return [0, 0, 1];
17995
+ return [v[0] / len2, v[1] / len2, v[2] / len2];
17897
17996
  }
17898
17997
  function signedArea2D(loop) {
17899
17998
  let area2 = 0;
@@ -18077,22 +18176,22 @@ function buildSweepSegments(pathPoints, preferredUp) {
18077
18176
  const a2 = clean[index2];
18078
18177
  const b = clean[index2 + 1];
18079
18178
  const delta = vec3Sub(b, a2);
18080
- const len = vec3Len(delta);
18081
- if (len < 1e-6) continue;
18082
- const tangent = vec3Scale(delta, 1 / len);
18179
+ const len2 = vec3Len(delta);
18180
+ if (len2 < 1e-6) continue;
18181
+ const tangent = vec3Scale(delta, 1 / len2);
18083
18182
  segments.push({
18084
18183
  a: a2,
18085
18184
  b,
18086
18185
  t: tangent,
18087
18186
  x: frames[index2].x,
18088
18187
  y: frames[index2].y,
18089
- len,
18188
+ len: len2,
18090
18189
  arcStart: totalLen,
18091
- arcEnd: totalLen + len,
18190
+ arcEnd: totalLen + len2,
18092
18191
  frameA: frames[index2],
18093
18192
  frameB: frames[index2 + 1]
18094
18193
  });
18095
- totalLen += len;
18194
+ totalLen += len2;
18096
18195
  }
18097
18196
  if (segments.length === 0) {
18098
18197
  throw new Error("sweep path has no non-zero segments");
@@ -18579,6 +18678,58 @@ function loftStitched(profiles, heights, wasm, options = {}) {
18579
18678
  }
18580
18679
  return result;
18581
18680
  }
18681
+ function loftStitchedOnFrames(profiles, frames, wasm, options = {}) {
18682
+ if (profiles.length < 2 || profiles.length !== frames.length) return null;
18683
+ const classified = profiles.map((loops) => classifyLoops(loops));
18684
+ const outerCount = classified[0].outers.length;
18685
+ const holeCount = classified[0].holes.length;
18686
+ if (outerCount === 0) return null;
18687
+ for (let i = 1; i < classified.length; i++) {
18688
+ if (classified[i].outers.length !== outerCount || classified[i].holes.length !== holeCount) return null;
18689
+ }
18690
+ const outerGroups = matchLoopsAcrossProfiles(classified.map((c2) => c2.outers));
18691
+ const holeGroups = holeCount > 0 ? matchLoopsAcrossProfiles(classified.map((c2) => c2.holes)) : [];
18692
+ const outerSolids = [];
18693
+ for (const group of outerGroups) {
18694
+ const solid = stitchSingleLoopLoftOnFrames(group, frames, wasm, options);
18695
+ if (!solid) {
18696
+ for (const s of outerSolids) s.delete();
18697
+ return null;
18698
+ }
18699
+ outerSolids.push(solid);
18700
+ }
18701
+ let result;
18702
+ if (outerSolids.length === 1) {
18703
+ result = outerSolids[0];
18704
+ } else {
18705
+ result = wasm.Manifold.union(outerSolids);
18706
+ for (const s of outerSolids) s.delete();
18707
+ }
18708
+ if (holeGroups.length > 0) {
18709
+ const holeSolids = [];
18710
+ for (const group of holeGroups) {
18711
+ const solid = stitchSingleLoopLoftOnFrames(group, frames, wasm, options);
18712
+ if (!solid) {
18713
+ result.delete();
18714
+ for (const s of holeSolids) s.delete();
18715
+ return null;
18716
+ }
18717
+ holeSolids.push(solid);
18718
+ }
18719
+ let holeUnion;
18720
+ if (holeSolids.length === 1) {
18721
+ holeUnion = holeSolids[0];
18722
+ } else {
18723
+ holeUnion = wasm.Manifold.union(holeSolids);
18724
+ for (const s of holeSolids) s.delete();
18725
+ }
18726
+ const subtracted = wasm.Manifold.difference([result, holeUnion]);
18727
+ result.delete();
18728
+ holeUnion.delete();
18729
+ result = subtracted;
18730
+ }
18731
+ return result;
18732
+ }
18582
18733
  function classifyLoops(loops) {
18583
18734
  const outers = [];
18584
18735
  const holes = [];
@@ -18737,9 +18888,9 @@ function maxQuadDeviation(rings, heights, colA, colB) {
18737
18888
  const c2 = [rings[i + 1][colB][0], rings[i + 1][colB][1], heights[i + 1]];
18738
18889
  const d2 = [rings[i + 1][colA][0], rings[i + 1][colA][1], heights[i + 1]];
18739
18890
  const n = cross3$6(sub3$4(b, a2), sub3$4(d2, a2));
18740
- const len = Math.hypot(n[0], n[1], n[2]);
18741
- if (len < 1e-12) continue;
18742
- const deviation = Math.abs((n[0] * (c2[0] - a2[0]) + n[1] * (c2[1] - a2[1]) + n[2] * (c2[2] - a2[2])) / len);
18891
+ const len2 = Math.hypot(n[0], n[1], n[2]);
18892
+ if (len2 < 1e-12) continue;
18893
+ const deviation = Math.abs((n[0] * (c2[0] - a2[0]) + n[1] * (c2[1] - a2[1]) + n[2] * (c2[2] - a2[2])) / len2);
18743
18894
  if (deviation > worst) worst = deviation;
18744
18895
  }
18745
18896
  return worst;
@@ -18795,18 +18946,18 @@ function buildCornerAnchoredRings(loops, heights, cornerSets, edgeLength2) {
18795
18946
  const start = dists[from];
18796
18947
  let end = dists[to];
18797
18948
  if (to <= from) end += total;
18798
- const len = end - start;
18799
- if (len < 1e-9) return null;
18949
+ const len2 = end - start;
18950
+ if (len2 < 1e-9) return null;
18800
18951
  const interior = [];
18801
18952
  const n = loop.length;
18802
18953
  for (let v = (from + 1) % n; v !== to; v = (v + 1) % n) {
18803
18954
  let d2 = dists[v];
18804
18955
  if (d2 < start) d2 += total;
18805
- const p2 = (d2 - start) / len;
18956
+ const p2 = (d2 - start) / len2;
18806
18957
  if (p2 > 1e-9 && p2 < 1 - 1e-9) interior.push(p2);
18807
18958
  }
18808
18959
  stationSegs.push(interior);
18809
- stationLens.push(len);
18960
+ stationLens.push(len2);
18810
18961
  }
18811
18962
  segParams.push(stationSegs);
18812
18963
  segLengths.push(stationLens);
@@ -18832,12 +18983,12 @@ function buildCornerAnchoredRings(loops, heights, cornerSets, edgeLength2) {
18832
18983
  const start = dists[from];
18833
18984
  let end = dists[to];
18834
18985
  if (to <= from) end += total;
18835
- const len = end - start;
18986
+ const len2 = end - start;
18836
18987
  for (const p2 of paramsBySegment[s]) {
18837
18988
  if (p2 === 0) {
18838
18989
  ring.push([loop[from][0], loop[from][1]]);
18839
18990
  } else {
18840
- ring.push(pointAtArcLength(loop, dists, total, start + p2 * len));
18991
+ ring.push(pointAtArcLength(loop, dists, total, start + p2 * len2));
18841
18992
  }
18842
18993
  }
18843
18994
  }
@@ -18965,9 +19116,11 @@ function buildSeamAlignedRings(loops, _heights, edgeLength2) {
18965
19116
  return { rings, cornerColumns: [] };
18966
19117
  }
18967
19118
  function buildSpanRows(rings, heights) {
18968
- const R = rings.length;
18969
- const N = rings[0].length;
18970
- const stations = rings.map((ring, i) => ring.map(([x2, y2]) => [x2, y2, heights[i]]));
19119
+ return buildSpanRowsFromStations(rings.map((ring, i) => ring.map(([x2, y2]) => [x2, y2, heights[i]])));
19120
+ }
19121
+ function buildSpanRowsFromStations(stations) {
19122
+ const R = stations.length;
19123
+ const N = stations[0].length;
18971
19124
  const t = [0];
18972
19125
  for (let i = 0; i < R - 1; i++) {
18973
19126
  let sum2 = 0;
@@ -19132,15 +19285,117 @@ function stitchSingleLoopLoft(loops, heights, wasm, options) {
19132
19285
  return null;
19133
19286
  }
19134
19287
  }
19288
+ function stitchSingleLoopLoftOnFrames(loops, frames, wasm, options) {
19289
+ const normalizedLoops = loops.map((loop) => {
19290
+ const area2 = signedArea$3(loop);
19291
+ return area2 < 0 ? [...loop].reverse() : loop;
19292
+ });
19293
+ const stationDistances = cumulativeFrameDistances(frames);
19294
+ const compatible = buildCompatibleRings(normalizedLoops, stationDistances, options.edgeLength);
19295
+ if (!compatible) return null;
19296
+ const { rings, cornerColumns } = compatible;
19297
+ const N = rings[0].length;
19298
+ if (N < 3) return null;
19299
+ const stations = rings.map((ring, i) => ring.map(([x2, y2]) => pointOnFrame(frames[i], x2, y2)));
19300
+ const rows = buildSpanRowsFromStations(stations);
19301
+ const R = rows.length;
19302
+ const cornerSet = new Set(cornerColumns);
19303
+ const vertProps = [];
19304
+ let vertCount = 0;
19305
+ const fwdIdx = [];
19306
+ const bwdIdx = [];
19307
+ const pushVert = (p2, n) => {
19308
+ vertProps.push(p2[0], p2[1], p2[2], n[0], n[1], n[2]);
19309
+ return vertCount++;
19310
+ };
19311
+ for (let r = 0; r < R; r++) {
19312
+ const { points, tangents } = rows[r];
19313
+ const fwd = new Array(N);
19314
+ const bwd = new Array(N);
19315
+ for (let j = 0; j < N; j++) {
19316
+ const prev = points[(j - 1 + N) % N];
19317
+ const curr = points[j];
19318
+ const next = points[(j + 1) % N];
19319
+ if (cornerSet.has(j)) {
19320
+ const nFwd = surfaceNormal(sub3$4(next, curr), tangents[j]);
19321
+ const nBwd = surfaceNormal(sub3$4(curr, prev), tangents[j]);
19322
+ fwd[j] = pushVert(curr, nFwd);
19323
+ bwd[j] = pushVert(curr, nBwd);
19324
+ } else {
19325
+ const idx = pushVert(curr, surfaceNormal(sub3$4(next, prev), tangents[j]));
19326
+ fwd[j] = idx;
19327
+ bwd[j] = idx;
19328
+ }
19329
+ }
19330
+ fwdIdx.push(fwd);
19331
+ bwdIdx.push(bwd);
19332
+ }
19333
+ const triangles = [];
19334
+ for (let r = 0; r < R - 1; r++) {
19335
+ for (let j = 0; j < N; j++) {
19336
+ const j1 = (j + 1) % N;
19337
+ const v0 = fwdIdx[r][j];
19338
+ const v3 = bwdIdx[r][j1];
19339
+ const v2 = bwdIdx[r + 1][j1];
19340
+ const v1 = fwdIdx[r + 1][j];
19341
+ triangles.push(v0, v3, v2);
19342
+ triangles.push(v0, v2, v1);
19343
+ }
19344
+ }
19345
+ const bottomTris = wasm.triangulate([rings[0]]);
19346
+ const bottomBase = vertCount;
19347
+ const bottomNormal = scale3$2(frames[0].normal, -1);
19348
+ for (const p2 of rows[0].points) pushVert(p2, bottomNormal);
19349
+ for (const tri of bottomTris) {
19350
+ const [v0, v1, v2] = Array.isArray(tri) ? tri : [tri[0], tri[1], tri[2]];
19351
+ triangles.push(bottomBase + v0, bottomBase + v2, bottomBase + v1);
19352
+ }
19353
+ const topTris = wasm.triangulate([rings[rings.length - 1]]);
19354
+ const topBase = vertCount;
19355
+ const topNormal = frames[frames.length - 1].normal;
19356
+ for (const p2 of rows[R - 1].points) pushVert(p2, topNormal);
19357
+ for (const tri of topTris) {
19358
+ const [v0, v1, v2] = Array.isArray(tri) ? tri : [tri[0], tri[1], tri[2]];
19359
+ triangles.push(topBase + v0, topBase + v1, topBase + v2);
19360
+ }
19361
+ const mesh = new wasm.Mesh({
19362
+ numProp: 6,
19363
+ vertProperties: new Float32Array(vertProps),
19364
+ triVerts: new Uint32Array(triangles)
19365
+ });
19366
+ try {
19367
+ mesh.merge();
19368
+ return new wasm.Manifold(mesh);
19369
+ } catch (_e2) {
19370
+ return null;
19371
+ }
19372
+ }
19373
+ function pointOnFrame(frame, x2, y2) {
19374
+ return [
19375
+ frame.origin[0] + frame.xAxis[0] * x2 + frame.yAxis[0] * y2,
19376
+ frame.origin[1] + frame.xAxis[1] * x2 + frame.yAxis[1] * y2,
19377
+ frame.origin[2] + frame.xAxis[2] * x2 + frame.yAxis[2] * y2
19378
+ ];
19379
+ }
19380
+ function cumulativeFrameDistances(frames) {
19381
+ const distances = [0];
19382
+ for (let i = 1; i < frames.length; i++) {
19383
+ distances.push(distances[i - 1] + dist3(frames[i].origin, frames[i - 1].origin));
19384
+ }
19385
+ if (distances[distances.length - 1] < 1e-9) {
19386
+ for (let i = 0; i < frames.length; i++) distances[i] = i;
19387
+ }
19388
+ return distances;
19389
+ }
19135
19390
  function surfaceNormal(chord, span2) {
19136
19391
  const n = cross3$6(chord, span2);
19137
- const len = Math.hypot(n[0], n[1], n[2]);
19138
- if (len < 1e-12) {
19392
+ const len2 = Math.hypot(n[0], n[1], n[2]);
19393
+ if (len2 < 1e-12) {
19139
19394
  const radial = Math.hypot(chord[0], chord[1]);
19140
19395
  if (radial > 1e-12) return [chord[1] / radial, -chord[0] / radial, 0];
19141
19396
  return [0, 0, 1];
19142
19397
  }
19143
- return [n[0] / len, n[1] / len, n[2] / len];
19398
+ return [n[0] / len2, n[1] / len2, n[2] / len2];
19144
19399
  }
19145
19400
  function sub3$4(a2, b) {
19146
19401
  return [a2[0] - b[0], a2[1] - b[1], a2[2] - b[2]];
@@ -19428,32 +19683,32 @@ function sweepStitched(profilePolygons, pathPoints, up, wasm) {
19428
19683
  function computeParallelTransportFrames(path, preferredUp) {
19429
19684
  const n = path.length;
19430
19685
  const frames = [];
19431
- const firstTangent = normalize$1(sub$2(path[1], path[0]));
19686
+ const firstTangent = normalize$2(sub$3(path[1], path[0]));
19432
19687
  if (!firstTangent) return null;
19433
- let x2 = normalize$1(cross$2(preferredUp, firstTangent));
19688
+ let x2 = normalize$2(cross$3(preferredUp, firstTangent));
19434
19689
  if (!x2 || length$1(x2) < 1e-8) {
19435
19690
  const fallback = Math.abs(firstTangent[0]) < 0.9 ? [1, 0, 0] : [0, 1, 0];
19436
- x2 = normalize$1(cross$2(fallback, firstTangent));
19691
+ x2 = normalize$2(cross$3(fallback, firstTangent));
19437
19692
  if (!x2) return null;
19438
19693
  }
19439
- let y2 = normalize$1(cross$2(firstTangent, x2));
19694
+ let y2 = normalize$2(cross$3(firstTangent, x2));
19440
19695
  frames.push({ origin: path[0], x: x2, y: y2, t: firstTangent });
19441
19696
  for (let i = 1; i < n; i++) {
19442
19697
  const prevT = frames[i - 1].t;
19443
19698
  let nextT;
19444
19699
  if (i < n - 1) {
19445
- const t1 = normalize$1(sub$2(path[i], path[i - 1]));
19446
- const t2 = normalize$1(sub$2(path[i + 1], path[i]));
19700
+ const t1 = normalize$2(sub$3(path[i], path[i - 1]));
19701
+ const t2 = normalize$2(sub$3(path[i + 1], path[i]));
19447
19702
  if (!t1 || !t2) return null;
19448
- nextT = normalize$1(add$1(t1, t2)) || t1;
19703
+ nextT = normalize$2(add$2(t1, t2)) || t1;
19449
19704
  } else {
19450
- const nt = normalize$1(sub$2(path[i], path[i - 1]));
19705
+ const nt = normalize$2(sub$3(path[i], path[i - 1]));
19451
19706
  if (!nt) return null;
19452
19707
  nextT = nt;
19453
19708
  }
19454
- const v = cross$2(prevT, nextT);
19709
+ const v = cross$3(prevT, nextT);
19455
19710
  const vLen = length$1(v);
19456
- const c2 = dot$2(prevT, nextT);
19711
+ const c2 = dot$3(prevT, nextT);
19457
19712
  if (vLen > 1e-10) {
19458
19713
  const axis = scale$1(v, 1 / vLen);
19459
19714
  x2 = rotateVector(frames[i - 1].x, axis, c2, vLen);
@@ -19527,32 +19782,32 @@ function signedArea$2(loop) {
19527
19782
  }
19528
19783
  return area2 * 0.5;
19529
19784
  }
19530
- function sub$2(a2, b) {
19785
+ function sub$3(a2, b) {
19531
19786
  return [a2[0] - b[0], a2[1] - b[1], a2[2] - b[2]];
19532
19787
  }
19533
- function add$1(a2, b) {
19788
+ function add$2(a2, b) {
19534
19789
  return [a2[0] + b[0], a2[1] + b[1], a2[2] + b[2]];
19535
19790
  }
19536
19791
  function scale$1(v, s) {
19537
19792
  return [v[0] * s, v[1] * s, v[2] * s];
19538
19793
  }
19539
- function dot$2(a2, b) {
19794
+ function dot$3(a2, b) {
19540
19795
  return a2[0] * b[0] + a2[1] * b[1] + a2[2] * b[2];
19541
19796
  }
19542
- function cross$2(a2, b) {
19797
+ function cross$3(a2, b) {
19543
19798
  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]];
19544
19799
  }
19545
19800
  function length$1(v) {
19546
19801
  return Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
19547
19802
  }
19548
- function normalize$1(v) {
19549
- const len = length$1(v);
19550
- if (len < 1e-12) return null;
19551
- return [v[0] / len, v[1] / len, v[2] / len];
19803
+ function normalize$2(v) {
19804
+ const len2 = length$1(v);
19805
+ if (len2 < 1e-12) return null;
19806
+ return [v[0] / len2, v[1] / len2, v[2] / len2];
19552
19807
  }
19553
19808
  function rotateVector(v, axis, c2, s) {
19554
- const kDotV = dot$2(axis, v);
19555
- const kCrossV = cross$2(axis, v);
19809
+ const kDotV = dot$3(axis, v);
19810
+ const kCrossV = cross$3(axis, v);
19556
19811
  return [
19557
19812
  v[0] * c2 + kCrossV[0] * s + axis[0] * kDotV * (1 - c2),
19558
19813
  v[1] * c2 + kCrossV[1] * s + axis[1] * kDotV * (1 - c2),
@@ -19896,24 +20151,7 @@ function fromSlicesPlaneFrameForManifold(normalInput) {
19896
20151
  }
19897
20152
  function fromSlicesLocalToWorldMatrixForManifold(normal2) {
19898
20153
  const frame = fromSlicesPlaneFrameForManifold(normal2);
19899
- return [
19900
- frame.u[0],
19901
- frame.u[1],
19902
- frame.u[2],
19903
- 0,
19904
- frame.v[0],
19905
- frame.v[1],
19906
- frame.v[2],
19907
- 0,
19908
- frame.normal[0],
19909
- frame.normal[1],
19910
- frame.normal[2],
19911
- 0,
19912
- 0,
19913
- 0,
19914
- 0,
19915
- 1
19916
- ];
20154
+ return Transform.from(planeFrameToWorldToPlaneMatrix({ origin: [0, 0, 0], u: frame.u, v: frame.v, normal: frame.normal })).inverse().toArray();
19917
20155
  }
19918
20156
  function transformZAlignedFromSlicesPlanForManifold(base, normal2) {
19919
20157
  const matrix = fromSlicesLocalToWorldMatrixForManifold(normal2);
@@ -20502,35 +20740,41 @@ function lowerFromSlicesToManifold(plan, wasm) {
20502
20740
  solid = lowerSdfToManifold(levelSetFieldToStandardSdf3(input.sdf), input.bounds, input.edgeLength, wasm);
20503
20741
  }
20504
20742
  }
20505
- if (Math.abs(nx) > 1e-10 || Math.abs(ny) > 1e-10 || nz < 0) {
20506
- if (Math.abs(nx) < 1e-10 && Math.abs(ny) < 1e-10 && nz < 0) {
20507
- const prev = solid;
20508
- solid = solid.rotate([180, 0, 0]);
20509
- if (solid !== prev) disposeWasmObject(prev);
20510
- } else {
20511
- const ax = -ny;
20512
- const ay = nx;
20513
- const len = Math.sqrt(ax * ax + ay * ay);
20514
- const c2 = nz;
20515
- const s = len;
20516
- const ux = ax / len;
20517
- const uy = ay / len;
20518
- const m00 = c2 + ux * ux * (1 - c2);
20519
- const m01 = ux * uy * (1 - c2);
20520
- const m02 = uy * s;
20521
- const m10 = uy * ux * (1 - c2);
20522
- const m11 = c2 + uy * uy * (1 - c2);
20523
- const m12 = -ux * s;
20524
- const m20 = -uy * s;
20525
- const m21 = ux * s;
20526
- const m22 = c2;
20527
- const prev = solid;
20528
- solid = solid.transform([m00, m01, m02, 0, m10, m11, m12, 0, m20, m21, m22, 0, 0, 0, 0, 1]);
20529
- if (solid !== prev) disposeWasmObject(prev);
20530
- }
20743
+ if (Math.abs(nx) > 1e-10 || Math.abs(ny) > 1e-10 || Math.abs(nz - 1) > 1e-10) {
20744
+ const prev = solid;
20745
+ solid = solid.transform(fromSlicesLocalToWorldMatrixForManifold(group.normal));
20746
+ if (solid !== prev) disposeWasmObject(prev);
20531
20747
  }
20532
20748
  return solid;
20533
20749
  }
20750
+ function finiteVec3(value, context) {
20751
+ if (value.some((component) => !Number.isFinite(component))) throw new Error(`${context} must contain finite numbers.`);
20752
+ return [value[0], value[1], value[2]];
20753
+ }
20754
+ function lowerFromSectionFramesToManifold(plan, wasm) {
20755
+ if (plan.slices.length < 2) throw new Error("Shape.fromSlices: framed section loft requires at least two slices.");
20756
+ const profiles = plan.slices.map((s) => {
20757
+ const crossSection = lowerProfileCompilePlanToCrossSection(s.profile, wasm);
20758
+ try {
20759
+ return crossSection.toPolygons();
20760
+ } finally {
20761
+ disposeWasmObject(crossSection);
20762
+ }
20763
+ });
20764
+ const frames = plan.slices.map((s, index2) => ({
20765
+ origin: finiteVec3(s.origin, `Shape.fromSlices framed slice ${index2} origin`),
20766
+ normal: normalizeVec3$2(s.normal, `Shape.fromSlices framed slice ${index2} normal`),
20767
+ xAxis: normalizeVec3$2(s.tangentU, `Shape.fromSlices framed slice ${index2} tangentU`),
20768
+ yAxis: normalizeVec3$2(s.tangentV, `Shape.fromSlices framed slice ${index2} tangentV`)
20769
+ }));
20770
+ const stitched = loftStitchedOnFrames(profiles, frames, wasm, { edgeLength: plan.edgeLength });
20771
+ if (!stitched) {
20772
+ throw new Error(
20773
+ "Shape.fromSlices: framed section loft could not be stitched into a manifold mesh. Check that every section has compatible closed profile topology and that adjacent frames do not self-intersect."
20774
+ );
20775
+ }
20776
+ return stitched;
20777
+ }
20534
20778
  function lowerShapeVariableSweepCompilePlan(plan, wasm) {
20535
20779
  const sectionPolygons = plan.sections.map((s) => {
20536
20780
  const crossSection = lowerProfileCompilePlanToCrossSection(s.profile, wasm);
@@ -20725,6 +20969,9 @@ function selectEdgeSegmentsForFeature(segments, plan) {
20725
20969
  return matched;
20726
20970
  }
20727
20971
  function lowerFilletEdgesCompilePlan(plan, wasm) {
20972
+ if (plan.variableRadius) {
20973
+ throw new Error(`Variable-radius fillet requires the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
20974
+ }
20728
20975
  let manifold = lowerShapeCompilePlanToManifold(plan.base, wasm);
20729
20976
  const mesh = manifold.getMesh();
20730
20977
  const segments = extractEdgeSegments({
@@ -20861,6 +21108,10 @@ function lowerShapeCompilePlanToManifold(plan, wasm) {
20861
21108
  return lowerFilletEdgesCompilePlan(plan, wasm);
20862
21109
  case "cornerYBlend":
20863
21110
  throw new Error(`Blend.CornerY() requires the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
21111
+ case "faceFillet":
21112
+ throw new Error(`Blend.Face() requires the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
21113
+ case "fullRound":
21114
+ throw new Error(`Blend.FullRound() requires the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
20864
21115
  case "chamferEdges":
20865
21116
  return lowerChamferEdgesCompilePlan(plan, wasm);
20866
21117
  case "draft": {
@@ -20890,6 +21141,8 @@ function lowerShapeCompilePlanToManifold(plan, wasm) {
20890
21141
  }
20891
21142
  case "fromSlices":
20892
21143
  return lowerFromSlicesToManifold(plan, wasm);
21144
+ case "fromSectionFrames":
21145
+ return lowerFromSectionFramesToManifold(plan, wasm);
20893
21146
  case "nurbsSurface":
20894
21147
  return lowerNurbsSurfaceToManifold(plan, wasm);
20895
21148
  case "analyticSurface":
@@ -22117,6 +22370,27 @@ function occtEdgeTouchesPoint(oc, edge, target, tolerance) {
22117
22370
  const end = curve.get().Value(last.current);
22118
22371
  return Math.min(occtPointDistance(start, target), occtPointDistance(end, target)) <= tolerance;
22119
22372
  }
22373
+ function occtEdgeLength(oc, edge) {
22374
+ const adaptor = new oc.BRepAdaptor_Curve_2(edge);
22375
+ return oc.GCPnts_AbscissaPoint.Length_1(adaptor);
22376
+ }
22377
+ function addFilletEdge(oc, mkFillet, edge, radius, variableRadius) {
22378
+ if (!variableRadius) {
22379
+ mkFillet.Add_2(radius, edge);
22380
+ return;
22381
+ }
22382
+ if ("stations" in variableRadius) {
22383
+ const length4 = occtEdgeLength(oc, edge);
22384
+ const stations = variableRadius.stations;
22385
+ const arr = new oc.TColgp_Array1OfPnt2d_2(1, stations.length);
22386
+ for (let i = 0; i < stations.length; i++) {
22387
+ arr.SetValue(i + 1, new oc.gp_Pnt2d_3(stations[i].at * length4, stations[i].radius));
22388
+ }
22389
+ mkFillet.Add_5(arr, edge);
22390
+ return;
22391
+ }
22392
+ mkFillet.Add_3(variableRadius.start, variableRadius.end, edge);
22393
+ }
22120
22394
  function lowerFilletEdgesPlan$1(oc, plan) {
22121
22395
  const base = lowerShapeCompilePlanToOCCT(plan.base, oc);
22122
22396
  const mkFillet = new oc.BRepFilletAPI_MakeFillet(base, oc.ChFi3d_FilletShape.ChFi3d_Rational);
@@ -22127,7 +22401,7 @@ function lowerFilletEdgesPlan$1(oc, plan) {
22127
22401
  for (const target of selectOCCTEdgeFeatureTargets(base, plan)) {
22128
22402
  const matchedEdge = findOCCTEdgeByMidpoint(oc, base, target.midpoint);
22129
22403
  if (matchedEdge) {
22130
- mkFillet.Add_2(plan.radius, matchedEdge);
22404
+ addFilletEdge(oc, mkFillet, matchedEdge, plan.radius, plan.variableRadius);
22131
22405
  addedCount++;
22132
22406
  }
22133
22407
  }
@@ -22136,7 +22410,55 @@ function lowerFilletEdgesPlan$1(oc, plan) {
22136
22410
  }
22137
22411
  mkFillet.Build(new oc.Message_ProgressRange_1());
22138
22412
  if (!mkFillet.IsDone()) {
22139
- throw new Error(`filletEdges(): OCCT fillet operation failed (radius=${plan.radius}, ${addedCount} edges).`);
22413
+ const radiusDesc = plan.variableRadius ? `variableRadius(max=${maxVariableRadius(plan.variableRadius)})` : `radius=${plan.radius}`;
22414
+ throw new Error(`filletEdges(): OCCT fillet operation failed (${radiusDesc}, ${addedCount} edges).`);
22415
+ }
22416
+ return mkFillet.Shape();
22417
+ }
22418
+ function lowerFaceFilletPlan(oc, plan) {
22419
+ const base = lowerShapeCompilePlanToOCCT(plan.base, oc);
22420
+ const mkFillet = new oc.BRepFilletAPI_MakeFillet(base, oc.ChFi3d_FilletShape.ChFi3d_Rational);
22421
+ if (plan.continuity) {
22422
+ mkFillet.SetContinuity(mapSurfaceContinuityToOcct(oc, plan.continuity), 2 * Math.PI / 180);
22423
+ }
22424
+ let addedCount = 0;
22425
+ const touchedEdges = /* @__PURE__ */ new Set();
22426
+ for (const target of plan.edgeTargets) {
22427
+ const matchedEdge = findOCCTEdgeByMidpoint(oc, base, target.midpoint);
22428
+ if (!matchedEdge || touchedEdges.has(matchedEdge)) continue;
22429
+ addFilletEdge(oc, mkFillet, matchedEdge, plan.radius, plan.variableRadius);
22430
+ touchedEdges.add(matchedEdge);
22431
+ addedCount++;
22432
+ }
22433
+ if (addedCount === 0) {
22434
+ throw new Error("Blend.Face(): no matching OCCT edges found shared by the face pair.");
22435
+ }
22436
+ mkFillet.Build(new oc.Message_ProgressRange_1());
22437
+ if (!mkFillet.IsDone()) {
22438
+ const radiusDesc = plan.variableRadius ? `variableRadius(max=${maxVariableRadius(plan.variableRadius)})` : `radius=${plan.radius}`;
22439
+ throw new Error(`Blend.Face(): OCCT face fillet failed (${radiusDesc}, ${addedCount} shared edges).`);
22440
+ }
22441
+ return mkFillet.Shape();
22442
+ }
22443
+ function lowerFullRoundPlan(oc, plan) {
22444
+ const base = lowerShapeCompilePlanToOCCT(plan.base, oc);
22445
+ const mkFillet = new oc.BRepFilletAPI_MakeFillet(base, oc.ChFi3d_FilletShape.ChFi3d_Rational);
22446
+ mkFillet.SetContinuity(mapSurfaceContinuityToOcct(oc, plan.continuity ?? "G1"), 2 * Math.PI / 180);
22447
+ let addedCount = 0;
22448
+ const touchedEdges = /* @__PURE__ */ new Set();
22449
+ for (const target of plan.edgeTargets) {
22450
+ const matchedEdge = findOCCTEdgeByMidpoint(oc, base, target.midpoint);
22451
+ if (!matchedEdge || touchedEdges.has(matchedEdge)) continue;
22452
+ mkFillet.Add_2(plan.radius, matchedEdge);
22453
+ touchedEdges.add(matchedEdge);
22454
+ addedCount++;
22455
+ }
22456
+ if (addedCount === 0) {
22457
+ throw new Error("Blend.FullRound(): no matching OCCT edges found around the center face.");
22458
+ }
22459
+ mkFillet.Build(new oc.Message_ProgressRange_1());
22460
+ if (!mkFillet.IsDone()) {
22461
+ throw new Error(`Blend.FullRound(): OCCT full round failed (radius=${plan.radius}, ${addedCount} edges).`);
22140
22462
  }
22141
22463
  return mkFillet.Shape();
22142
22464
  }
@@ -22409,6 +22731,10 @@ function _lowerShapeCompilePlanToOCCTInner(plan, oc) {
22409
22731
  return lowerChamferPlan(oc, plan);
22410
22732
  case "filletEdges":
22411
22733
  return lowerFilletEdgesPlan$1(oc, plan);
22734
+ case "faceFillet":
22735
+ return lowerFaceFilletPlan(oc, plan);
22736
+ case "fullRound":
22737
+ return lowerFullRoundPlan(oc, plan);
22412
22738
  case "cornerYBlend":
22413
22739
  return lowerCornerYBlendPlan$1(oc, plan);
22414
22740
  case "chamferEdges":
@@ -22708,8 +23034,10 @@ function lowerSurfaceRuledPlan$1(oc, plan) {
22708
23034
  return sections.Shape();
22709
23035
  }
22710
23036
  function lowerSurfaceFillPlan$1(oc, plan) {
23037
+ var _a3, _b3;
22711
23038
  const hasSupport = plan.boundaries.some((boundary) => boundary.support != null);
22712
- if (!hasSupport && (plan.boundaries.length === 3 || plan.boundaries.length === 4)) {
23039
+ const hasInteriorConstraints = (((_a3 = plan.interiorCurves) == null ? void 0 : _a3.length) ?? 0) > 0 || (((_b3 = plan.interiorPoints) == null ? void 0 : _b3.length) ?? 0) > 0;
23040
+ if (!hasSupport && !hasInteriorConstraints && (plan.boundaries.length === 3 || plan.boundaries.length === 4)) {
22713
23041
  try {
22714
23042
  const handles = plan.boundaries.map(
22715
23043
  (boundary, index2) => buildBSplineCurveHandleFromSweepPathPlan(
@@ -22739,6 +23067,13 @@ function lowerSurfaceFillPlan$1(oc, plan) {
22739
23067
  filling.Add_1(edge, oc.GeomAbs_Shape.GeomAbs_C0, true);
22740
23068
  }
22741
23069
  }
23070
+ for (const interior of plan.interiorCurves ?? []) {
23071
+ const edge = buildCurveEdgeFromSweepPathPlan(oc, interior.curve, "Surface.Fill(through)", plan.allowApproximation);
23072
+ filling.Add_1(edge, mapSurfaceContinuityToOcct(oc, interior.continuity), false);
23073
+ }
23074
+ for (const point2 of plan.interiorPoints ?? []) {
23075
+ filling.Add_4(new oc.gp_Pnt_3(point2[0], point2[1], point2[2]));
23076
+ }
22742
23077
  filling.Build(new oc.Message_ProgressRange_1());
22743
23078
  if (!filling.IsDone()) {
22744
23079
  throw new Error("Surface.Fill(): OCCT failed to build the constrained filling surface.");
@@ -23288,9 +23623,9 @@ function crossVec3$1(a2, b) {
23288
23623
  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]];
23289
23624
  }
23290
23625
  function normalizeVec3$1(v) {
23291
- const len = Math.hypot(v[0], v[1], v[2]);
23292
- if (len < 1e-10) return null;
23293
- return [v[0] / len, v[1] / len, v[2] / len];
23626
+ const len2 = Math.hypot(v[0], v[1], v[2]);
23627
+ if (len2 < 1e-10) return null;
23628
+ return [v[0] / len2, v[1] / len2, v[2] / len2];
23294
23629
  }
23295
23630
  function buildSweepStartFrame(tangent, preferredUp) {
23296
23631
  let up = normalizeVec3$1(preferredUp) ?? [0, 0, 1];
@@ -23722,15 +24057,15 @@ function meshTriangles(mesh) {
23722
24057
  }
23723
24058
  return out;
23724
24059
  }
23725
- function sub$1(a2, b) {
24060
+ function sub$2(a2, b) {
23726
24061
  return [a2[0] - b[0], a2[1] - b[1], a2[2] - b[2]];
23727
24062
  }
23728
24063
  function scaleAdd(a2, b, scale2) {
23729
24064
  return [a2[0] + b[0] * scale2, a2[1] + b[1] * scale2, a2[2] + b[2] * scale2];
23730
24065
  }
23731
24066
  function distanceSqToSegment(p2, a2, b) {
23732
- const ab = sub$1(b, a2);
23733
- const t = Math.max(0, Math.min(1, dot3$3(sub$1(p2, a2), ab) / Math.max(dot3$3(ab, ab), 1e-20)));
24067
+ const ab = sub$2(b, a2);
24068
+ const t = Math.max(0, Math.min(1, dot3$3(sub$2(p2, a2), ab) / Math.max(dot3$3(ab, ab), 1e-20)));
23734
24069
  const q = scaleAdd(a2, ab, t);
23735
24070
  const dx = p2[0] - q[0];
23736
24071
  const dy = p2[1] - q[1];
@@ -23738,19 +24073,19 @@ function distanceSqToSegment(p2, a2, b) {
23738
24073
  return dx * dx + dy * dy + dz * dz;
23739
24074
  }
23740
24075
  function distanceSqToTriangle(p2, tri) {
23741
- const ab = sub$1(tri.b, tri.a);
23742
- const ac = sub$1(tri.c, tri.a);
23743
- const ap = sub$1(p2, tri.a);
24076
+ const ab = sub$2(tri.b, tri.a);
24077
+ const ac = sub$2(tri.c, tri.a);
24078
+ const ap = sub$2(p2, tri.a);
23744
24079
  const d1 = dot3$3(ab, ap);
23745
24080
  const d2 = dot3$3(ac, ap);
23746
24081
  if (d1 <= 0 && d2 <= 0) return dot3$3(ap, ap);
23747
- const bp = sub$1(p2, tri.b);
24082
+ const bp = sub$2(p2, tri.b);
23748
24083
  const d3 = dot3$3(ab, bp);
23749
24084
  const d4 = dot3$3(ac, bp);
23750
24085
  if (d3 >= 0 && d4 <= d3) return dot3$3(bp, bp);
23751
24086
  const vc = d1 * d4 - d3 * d2;
23752
24087
  if (vc <= 0 && d1 >= 0 && d3 <= 0) return distanceSqToSegment(p2, tri.a, tri.b);
23753
- const cp = sub$1(p2, tri.c);
24088
+ const cp = sub$2(p2, tri.c);
23754
24089
  const d5 = dot3$3(ab, cp);
23755
24090
  const d6 = dot3$3(ac, cp);
23756
24091
  if (d6 >= 0 && d5 <= d6) return dot3$3(cp, cp);
@@ -23765,13 +24100,13 @@ function distanceSqToTriangle(p2, tri) {
23765
24100
  }
23766
24101
  function rayHitsTriangleX(p2, tri) {
23767
24102
  const dir = [1, 0, 0];
23768
- const e1 = sub$1(tri.b, tri.a);
23769
- const e2 = sub$1(tri.c, tri.a);
24103
+ const e1 = sub$2(tri.b, tri.a);
24104
+ const e2 = sub$2(tri.c, tri.a);
23770
24105
  const h = [0, -e2[2], e2[1]];
23771
24106
  const det = dot3$3(e1, h);
23772
24107
  if (Math.abs(det) < 1e-10) return false;
23773
24108
  const inv = 1 / det;
23774
- const s = sub$1(p2, tri.a);
24109
+ const s = sub$2(p2, tri.a);
23775
24110
  const u2 = inv * dot3$3(s, h);
23776
24111
  if (u2 < 0 || u2 > 1) return false;
23777
24112
  const q = [s[1] * e1[2] - s[2] * e1[1], s[2] * e1[0] - s[0] * e1[2], s[0] * e1[1] - s[1] * e1[0]];
@@ -24146,7 +24481,16 @@ function stitchBoundaryLoop(boundaries, tolerance) {
24146
24481
  };
24147
24482
  return tryStitch(boundaries[0], boundaries.slice(1)) ?? tryStitch(reversePoints$1(boundaries[0]), boundaries.slice(1));
24148
24483
  }
24484
+ function rejectSdfInteriorConstraints(plan) {
24485
+ var _a3, _b3;
24486
+ if ((((_a3 = plan.interiorCurves) == null ? void 0 : _a3.length) ?? 0) > 0 || (((_b3 = plan.interiorPoints) == null ? void 0 : _b3.length) ?? 0) > 0) {
24487
+ throw new Error(
24488
+ "SDF backend cannot build a Surface.Fill() with interior constraint curves/points. Use the OCCT backend for constrained fills."
24489
+ );
24490
+ }
24491
+ }
24149
24492
  function sampledFillBoundaryLoop(plan) {
24493
+ rejectSdfInteriorConstraints(plan);
24150
24494
  if (plan.boundaries.length < 3) throw new Error("SDF backend sampled Surface.Fill() requires at least three boundary curves.");
24151
24495
  const rawBoundaries = plan.boundaries.map((boundary) => sampleSurfaceCurve$1(boundary.curve, plan.resolution));
24152
24496
  const tolerance = pointTolerance(rawBoundaries.flat());
@@ -24172,6 +24516,7 @@ function hasUnconstrainedCoonsFillBoundaries(plan) {
24172
24516
  return !!namedBoundaryCurve$1(plan, "v0") && !!namedBoundaryCurve$1(plan, "u1") && !!namedBoundaryCurve$1(plan, "v1") && !!namedBoundaryCurve$1(plan, "u0") && !plan.boundaries.some((boundary) => boundary.support);
24173
24517
  }
24174
24518
  function surfaceGridForFillPlan$1(plan) {
24519
+ rejectSdfInteriorConstraints(plan);
24175
24520
  const bottomCurve = namedBoundaryCurve$1(plan, "v0");
24176
24521
  const rightCurve = namedBoundaryCurve$1(plan, "u1");
24177
24522
  const topCurve = namedBoundaryCurve$1(plan, "v1");
@@ -28604,7 +28949,7 @@ function requireClipper() {
28604
28949
  ClipperLib2.Error("DoMaxima error");
28605
28950
  };
28606
28951
  ClipperLib2.Clipper.ReversePaths = function(polys) {
28607
- for (var i = 0, len = polys.length; i < len; i++)
28952
+ for (var i = 0, len2 = polys.length; i < len2; i++)
28608
28953
  polys[i].reverse();
28609
28954
  };
28610
28955
  ClipperLib2.Clipper.Orientation = function(poly) {
@@ -29599,11 +29944,11 @@ function requireClipper() {
29599
29944
  for (var i = 0; i < this.m_polyNodes.ChildCount(); i++) {
29600
29945
  var node = this.m_polyNodes.Childs()[i];
29601
29946
  this.m_srcPoly = node.m_polygon;
29602
- var len = this.m_srcPoly.length;
29603
- if (len === 0 || delta <= 0 && (len < 3 || node.m_endtype !== ClipperLib2.EndType.etClosedPolygon))
29947
+ var len2 = this.m_srcPoly.length;
29948
+ if (len2 === 0 || delta <= 0 && (len2 < 3 || node.m_endtype !== ClipperLib2.EndType.etClosedPolygon))
29604
29949
  continue;
29605
29950
  this.m_destPoly = new Array();
29606
- if (len === 1) {
29951
+ if (len2 === 1) {
29607
29952
  if (node.m_jointype === ClipperLib2.JoinType.jtRound) {
29608
29953
  var X = 1, Y = 0;
29609
29954
  for (var j = 1; j <= steps; j++) {
@@ -29628,45 +29973,45 @@ function requireClipper() {
29628
29973
  continue;
29629
29974
  }
29630
29975
  this.m_normals.length = 0;
29631
- for (var j = 0; j < len - 1; j++)
29976
+ for (var j = 0; j < len2 - 1; j++)
29632
29977
  this.m_normals.push(ClipperLib2.ClipperOffset.GetUnitNormal(this.m_srcPoly[j], this.m_srcPoly[j + 1]));
29633
29978
  if (node.m_endtype === ClipperLib2.EndType.etClosedLine || node.m_endtype === ClipperLib2.EndType.etClosedPolygon)
29634
- this.m_normals.push(ClipperLib2.ClipperOffset.GetUnitNormal(this.m_srcPoly[len - 1], this.m_srcPoly[0]));
29979
+ this.m_normals.push(ClipperLib2.ClipperOffset.GetUnitNormal(this.m_srcPoly[len2 - 1], this.m_srcPoly[0]));
29635
29980
  else
29636
- this.m_normals.push(new ClipperLib2.DoublePoint1(this.m_normals[len - 2]));
29981
+ this.m_normals.push(new ClipperLib2.DoublePoint1(this.m_normals[len2 - 2]));
29637
29982
  if (node.m_endtype === ClipperLib2.EndType.etClosedPolygon) {
29638
- var k2 = len - 1;
29639
- for (var j = 0; j < len; j++)
29983
+ var k2 = len2 - 1;
29984
+ for (var j = 0; j < len2; j++)
29640
29985
  k2 = this.OffsetPoint(j, k2, node.m_jointype);
29641
29986
  this.m_destPolys.push(this.m_destPoly);
29642
29987
  } else if (node.m_endtype === ClipperLib2.EndType.etClosedLine) {
29643
- var k2 = len - 1;
29644
- for (var j = 0; j < len; j++)
29988
+ var k2 = len2 - 1;
29989
+ for (var j = 0; j < len2; j++)
29645
29990
  k2 = this.OffsetPoint(j, k2, node.m_jointype);
29646
29991
  this.m_destPolys.push(this.m_destPoly);
29647
29992
  this.m_destPoly = new Array();
29648
- var n = this.m_normals[len - 1];
29649
- for (var j = len - 1; j > 0; j--)
29993
+ var n = this.m_normals[len2 - 1];
29994
+ for (var j = len2 - 1; j > 0; j--)
29650
29995
  this.m_normals[j] = new ClipperLib2.DoublePoint2(-this.m_normals[j - 1].X, -this.m_normals[j - 1].Y);
29651
29996
  this.m_normals[0] = new ClipperLib2.DoublePoint2(-n.X, -n.Y);
29652
29997
  k2 = 0;
29653
- for (var j = len - 1; j >= 0; j--)
29998
+ for (var j = len2 - 1; j >= 0; j--)
29654
29999
  k2 = this.OffsetPoint(j, k2, node.m_jointype);
29655
30000
  this.m_destPolys.push(this.m_destPoly);
29656
30001
  } else {
29657
30002
  var k2 = 0;
29658
- for (var j = 1; j < len - 1; ++j)
30003
+ for (var j = 1; j < len2 - 1; ++j)
29659
30004
  k2 = this.OffsetPoint(j, k2, node.m_jointype);
29660
30005
  var pt1;
29661
30006
  if (node.m_endtype === ClipperLib2.EndType.etOpenButt) {
29662
- var j = len - 1;
30007
+ var j = len2 - 1;
29663
30008
  pt1 = new ClipperLib2.IntPoint2(ClipperLib2.ClipperOffset.Round(this.m_srcPoly[j].X + this.m_normals[j].X * delta), ClipperLib2.ClipperOffset.Round(this.m_srcPoly[j].Y + this.m_normals[j].Y * delta));
29664
30009
  this.m_destPoly.push(pt1);
29665
30010
  pt1 = new ClipperLib2.IntPoint2(ClipperLib2.ClipperOffset.Round(this.m_srcPoly[j].X - this.m_normals[j].X * delta), ClipperLib2.ClipperOffset.Round(this.m_srcPoly[j].Y - this.m_normals[j].Y * delta));
29666
30011
  this.m_destPoly.push(pt1);
29667
30012
  } else {
29668
- var j = len - 1;
29669
- k2 = len - 2;
30013
+ var j = len2 - 1;
30014
+ k2 = len2 - 2;
29670
30015
  this.m_sinA = 0;
29671
30016
  this.m_normals[j] = new ClipperLib2.DoublePoint2(-this.m_normals[j].X, -this.m_normals[j].Y);
29672
30017
  if (node.m_endtype === ClipperLib2.EndType.etOpenSquare)
@@ -29674,10 +30019,10 @@ function requireClipper() {
29674
30019
  else
29675
30020
  this.DoRound(j, k2);
29676
30021
  }
29677
- for (var j = len - 1; j > 0; j--)
30022
+ for (var j = len2 - 1; j > 0; j--)
29678
30023
  this.m_normals[j] = new ClipperLib2.DoublePoint2(-this.m_normals[j - 1].X, -this.m_normals[j - 1].Y);
29679
30024
  this.m_normals[0] = new ClipperLib2.DoublePoint2(-this.m_normals[1].X, -this.m_normals[1].Y);
29680
- k2 = len - 1;
30025
+ k2 = len2 - 1;
29681
30026
  for (var j = k2 - 1; j > 0; --j)
29682
30027
  k2 = this.OffsetPoint(j, k2, node.m_jointype);
29683
30028
  if (node.m_endtype === ClipperLib2.EndType.etOpenButt) {
@@ -29881,13 +30226,13 @@ function requireClipper() {
29881
30226
  if (polygon.length === 0 || polygon.length === 1 && polygon[0].length === 0 || delta < 0) return polygon;
29882
30227
  if (!isPolygons) polygon = [polygon];
29883
30228
  var k_length = polygon.length;
29884
- var len, poly, result, d2, p2, j, i;
30229
+ var len2, poly, result, d2, p2, j, i;
29885
30230
  var results = [];
29886
30231
  for (var k2 = 0; k2 < k_length; k2++) {
29887
30232
  poly = polygon[k2];
29888
- len = poly.length;
29889
- if (len === 0) continue;
29890
- else if (len < 3) {
30233
+ len2 = poly.length;
30234
+ if (len2 === 0) continue;
30235
+ else if (len2 < 3) {
29891
30236
  result = poly;
29892
30237
  results.push(result);
29893
30238
  continue;
@@ -29896,7 +30241,7 @@ function requireClipper() {
29896
30241
  d2 = delta * delta;
29897
30242
  p2 = poly[0];
29898
30243
  j = 1;
29899
- for (i = 1; i < len; i++) {
30244
+ for (i = 1; i < len2; i++) {
29900
30245
  if ((poly[i].X - p2.X) * (poly[i].X - p2.X) + (poly[i].Y - p2.Y) * (poly[i].Y - p2.Y) <= d2)
29901
30246
  continue;
29902
30247
  result[j] = poly[i];
@@ -29906,8 +30251,8 @@ function requireClipper() {
29906
30251
  p2 = poly[j - 1];
29907
30252
  if ((poly[0].X - p2.X) * (poly[0].X - p2.X) + (poly[0].Y - p2.Y) * (poly[0].Y - p2.Y) <= d2)
29908
30253
  j--;
29909
- if (j < len)
29910
- result.splice(j, len - j);
30254
+ if (j < len2)
30255
+ result.splice(j, len2 - j);
29911
30256
  if (result.length) results.push(result);
29912
30257
  }
29913
30258
  if (!isPolygons && results.length) results = results[0];
@@ -29925,9 +30270,9 @@ function requireClipper() {
29925
30270
  ];
29926
30271
  var isPolygons = polygon[0] instanceof Array;
29927
30272
  if (!isPolygons) polygon = [polygon];
29928
- var len = polygon.length, plen, i, j, result;
29929
- var results = new Array(len);
29930
- for (i = 0; i < len; i++) {
30273
+ var len2 = polygon.length, plen, i, j, result;
30274
+ var results = new Array(len2);
30275
+ for (i = 0; i < len2; i++) {
29931
30276
  plen = polygon[i].length;
29932
30277
  result = new Array(plen);
29933
30278
  for (j = 0; j < plen; j++) {
@@ -29954,10 +30299,10 @@ function requireClipper() {
29954
30299
  if (!isPolygons) polygon = [polygon];
29955
30300
  var i, j, poly, k2, poly2, plen, A, B2, P, d2, rem, addlast;
29956
30301
  var bxax, byay, l, ax, ay;
29957
- var len = polygon.length;
30302
+ var len2 = polygon.length;
29958
30303
  var toleranceSq = tolerance * tolerance;
29959
30304
  var results = [];
29960
- for (i = 0; i < len; i++) {
30305
+ for (i = 0; i < len2; i++) {
29961
30306
  poly = polygon[i];
29962
30307
  plen = poly.length;
29963
30308
  if (plen === 0) continue;
@@ -32229,7 +32574,7 @@ function key(point2) {
32229
32574
  const clean = (value) => (Math.abs(value) < 5e-8 ? 0 : value).toFixed(7);
32230
32575
  return `${clean(point2[0])},${clean(point2[1])}`;
32231
32576
  }
32232
- function edgeKey$1(a2, b) {
32577
+ function edgeKey$2(a2, b) {
32233
32578
  return a2 < b ? `${a2}|${b}` : `${b}|${a2}`;
32234
32579
  }
32235
32580
  function loopsFromSegments$1(segments) {
@@ -32250,18 +32595,18 @@ function loopsFromSegments$1(segments) {
32250
32595
  const loops = [];
32251
32596
  for (const start of adjacency.keys()) {
32252
32597
  for (const first of adjacency.get(start) ?? []) {
32253
- if (visited.has(edgeKey$1(start, first))) continue;
32598
+ if (visited.has(edgeKey$2(start, first))) continue;
32254
32599
  const loop = [start];
32255
32600
  let previous = start;
32256
32601
  let current = first;
32257
- visited.add(edgeKey$1(start, first));
32602
+ visited.add(edgeKey$2(start, first));
32258
32603
  while (current !== start) {
32259
32604
  loop.push(current);
32260
32605
  const next = Array.from(adjacency.get(current) ?? []).find(
32261
- (candidate) => candidate !== previous && !visited.has(edgeKey$1(current, candidate))
32606
+ (candidate) => candidate !== previous && !visited.has(edgeKey$2(current, candidate))
32262
32607
  );
32263
32608
  if (!next) break;
32264
- visited.add(edgeKey$1(current, next));
32609
+ visited.add(edgeKey$2(current, next));
32265
32610
  previous = current;
32266
32611
  current = next;
32267
32612
  }
@@ -32948,11 +33293,11 @@ function triangulateStepFace(face, filePath) {
32948
33293
  const a2 = allPoints[ia];
32949
33294
  const b = allPoints[ib];
32950
33295
  const c2 = allPoints[ic];
32951
- const triNormal = cross3$4(sub3$2(b, a2), sub3$2(c2, a2));
32952
- if (Math.hypot(triNormal[0], triNormal[1], triNormal[2]) <= EPS$2) {
33296
+ const triNormal2 = cross3$4(sub3$2(b, a2), sub3$2(c2, a2));
33297
+ if (Math.hypot(triNormal2[0], triNormal2[1], triNormal2[2]) <= EPS$2) {
32953
33298
  failStepImport(filePath, `${face.context} triangulates to a degenerate triangle.`);
32954
33299
  }
32955
- return dot3$3(triNormal, normal2) >= 0 ? [a2, b, c2] : [a2, c2, b];
33300
+ return dot3$3(triNormal2, normal2) >= 0 ? [a2, b, c2] : [a2, c2, b];
32956
33301
  });
32957
33302
  }
32958
33303
  const EPS$1 = 1e-6;
@@ -36094,6 +36439,8 @@ function lowerShapeCompilePlanToSdfField(plan) {
36094
36439
  }
36095
36440
  case "fromSlices":
36096
36441
  return lowerFromSlices(plan);
36442
+ case "fromSectionFrames":
36443
+ throw new Error("Shape.fromSlices framed section lofts require the Manifold backend.");
36097
36444
  case "fillet":
36098
36445
  throwSdfEdgeFeatureUnsupported("fillet()");
36099
36446
  case "chamfer":
@@ -36102,6 +36449,10 @@ function lowerShapeCompilePlanToSdfField(plan) {
36102
36449
  throwSdfEdgeFeatureUnsupported("filletEdges()");
36103
36450
  case "cornerYBlend":
36104
36451
  throwSdfEdgeFeatureUnsupported("Blend.CornerY()");
36452
+ case "faceFillet":
36453
+ throwSdfEdgeFeatureUnsupported("Blend.Face()");
36454
+ case "fullRound":
36455
+ throwSdfEdgeFeatureUnsupported("Blend.FullRound()");
36105
36456
  case "chamferEdges":
36106
36457
  throwSdfEdgeFeatureUnsupported("chamferEdges()");
36107
36458
  case "nurbsSurface":
@@ -36404,10 +36755,10 @@ function transformProfilePoint(point2, transform) {
36404
36755
  case "mirror": {
36405
36756
  const nx = transform.normalX;
36406
36757
  const ny = transform.normalY;
36407
- const len = Math.hypot(nx, ny);
36408
- if (len <= EPS) return point2;
36409
- const ux = nx / len;
36410
- const uy = ny / len;
36758
+ const len2 = Math.hypot(nx, ny);
36759
+ if (len2 <= EPS) return point2;
36760
+ const ux = nx / len2;
36761
+ const uy = ny / len2;
36411
36762
  const d2 = point2[0] * ux + point2[1] * uy;
36412
36763
  return [point2[0] - 2 * d2 * ux, point2[1] - 2 * d2 * uy];
36413
36764
  }
@@ -36899,10 +37250,10 @@ class TruckPolygonProfileBackend {
36899
37250
  }
36900
37251
  mirror(ax) {
36901
37252
  const [nx0, ny0] = ax;
36902
- const len = Math.hypot(nx0, ny0);
36903
- if (len <= 1e-12) return wrapTruckProfileRegions(this.regions);
36904
- const nx = nx0 / len;
36905
- const ny = ny0 / len;
37253
+ const len2 = Math.hypot(nx0, ny0);
37254
+ if (len2 <= 1e-12) return wrapTruckProfileRegions(this.regions);
37255
+ const nx = nx0 / len2;
37256
+ const ny = ny0 / len2;
36906
37257
  return wrapTruckProfileRegions(
36907
37258
  transformRegions(this.regions, ([x2, y2]) => {
36908
37259
  const d2 = 2 * (x2 * nx + y2 * ny);
@@ -36981,23 +37332,23 @@ function boundsInteriorOverlap$1(a2, b) {
36981
37332
  return Math.min(a2.max[0], b.max[0]) - Math.max(a2.min[0], b.min[0]) > tolerance && Math.min(a2.max[1], b.max[1]) - Math.max(a2.min[1], b.min[1]) > tolerance && Math.min(a2.max[2], b.max[2]) - Math.max(a2.min[2], b.min[2]) > tolerance;
36982
37333
  }
36983
37334
  function normalizeVec3(v) {
36984
- const len = Math.hypot(v[0], v[1], v[2]);
36985
- if (len <= 1e-12) return [0, 0, 0];
36986
- return [v[0] / len, v[1] / len, v[2] / len];
37335
+ const len2 = Math.hypot(v[0], v[1], v[2]);
37336
+ if (len2 <= 1e-12) return [0, 0, 0];
37337
+ return [v[0] / len2, v[1] / len2, v[2] / len2];
36987
37338
  }
36988
- function dot$1(a2, b) {
37339
+ function dot$2(a2, b) {
36989
37340
  return a2[0] * b[0] + a2[1] * b[1] + a2[2] * b[2];
36990
37341
  }
36991
- function add(a2, b) {
37342
+ function add$1(a2, b) {
36992
37343
  return [a2[0] + b[0], a2[1] + b[1], a2[2] + b[2]];
36993
37344
  }
36994
- function sub(a2, b) {
37345
+ function sub$1(a2, b) {
36995
37346
  return [a2[0] - b[0], a2[1] - b[1], a2[2] - b[2]];
36996
37347
  }
36997
37348
  function scale(v, scalar) {
36998
37349
  return [v[0] * scalar, v[1] * scalar, v[2] * scalar];
36999
37350
  }
37000
- function cross$1(a2, b) {
37351
+ function cross$2(a2, b) {
37001
37352
  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]];
37002
37353
  }
37003
37354
  function isFiniteNumber(value) {
@@ -37017,7 +37368,7 @@ function axisSpan(face, axis) {
37017
37368
  let min2 = Infinity;
37018
37369
  let max2 = -Infinity;
37019
37370
  for (const vertex2 of face.vertices) {
37020
- const station = dot$1(vertex2, axis);
37371
+ const station = dot$2(vertex2, axis);
37021
37372
  min2 = Math.min(min2, station);
37022
37373
  max2 = Math.max(max2, station);
37023
37374
  }
@@ -37233,8 +37584,8 @@ function boundsCorners(bounds) {
37233
37584
  }
37234
37585
  function perpendicularAxes(normal2) {
37235
37586
  const seed = Math.abs(normal2[2]) < 0.9 ? [0, 0, 1] : [0, 1, 0];
37236
- const uAxis = normalizeVec3(cross$1(seed, normal2));
37237
- const vAxis = normalizeVec3(cross$1(normal2, uAxis));
37587
+ const uAxis = normalizeVec3(cross$2(seed, normal2));
37588
+ const vAxis = normalizeVec3(cross$2(normal2, uAxis));
37238
37589
  return { uAxis, vAxis };
37239
37590
  }
37240
37591
  function createBoundedHalfSpace(bounds, normal2, originOffset) {
@@ -37253,10 +37604,10 @@ function createBoundedHalfSpace(bounds, normal2, originOffset) {
37253
37604
  let maxV = -Infinity;
37254
37605
  let maxDistance = -Infinity;
37255
37606
  for (const corner of corners) {
37256
- const relative = sub(corner, planeOrigin);
37257
- const u2 = dot$1(relative, uAxis);
37258
- const v = dot$1(relative, vAxis);
37259
- const distance = dot$1(corner, n) - signedOffset;
37607
+ const relative = sub$1(corner, planeOrigin);
37608
+ const u2 = dot$2(relative, uAxis);
37609
+ const v = dot$2(relative, vAxis);
37610
+ const distance = dot$2(corner, n) - signedOffset;
37260
37611
  minU = Math.min(minU, u2);
37261
37612
  maxU = Math.max(maxU, u2);
37262
37613
  minV = Math.min(minV, v);
@@ -37268,7 +37619,7 @@ function createBoundedHalfSpace(bounds, normal2, originOffset) {
37268
37619
  const height = Math.max(maxDistance + margin, margin);
37269
37620
  const centerU = (minU + maxU) / 2;
37270
37621
  const centerV = (minV + maxV) / 2;
37271
- const translation = add(add(planeOrigin, scale(uAxis, centerU)), scale(vAxis, centerV));
37622
+ const translation = add$1(add$1(planeOrigin, scale(uAxis, centerU)), scale(vAxis, centerV));
37272
37623
  const matrix = [
37273
37624
  uAxis[0],
37274
37625
  uAxis[1],
@@ -37302,7 +37653,7 @@ function faceAxes(face) {
37302
37653
  for (const vertex2 of face.vertices.slice(1)) {
37303
37654
  const uAxis = normalizeVec3([vertex2[0] - origin[0], vertex2[1] - origin[1], vertex2[2] - origin[2]]);
37304
37655
  if (Math.hypot(...uAxis) <= 1e-12) continue;
37305
- const vAxis = normalizeVec3(cross$1(face.normal, uAxis));
37656
+ const vAxis = normalizeVec3(cross$2(face.normal, uAxis));
37306
37657
  if (Math.hypot(...vAxis) <= 1e-12) continue;
37307
37658
  return {
37308
37659
  uAxis,
@@ -37311,18 +37662,18 @@ function faceAxes(face) {
37311
37662
  }
37312
37663
  return {};
37313
37664
  }
37314
- function edgeKey(start, end) {
37665
+ function edgeKey$1(start, end) {
37315
37666
  const encode = (p2) => p2.map((value) => value.toFixed(9)).join(",");
37316
37667
  const a2 = encode(start);
37317
37668
  const b = encode(end);
37318
37669
  return a2 < b ? `${a2}|${b}` : `${b}|${a2}`;
37319
37670
  }
37320
37671
  function faceEdgeIndex(face, start, end) {
37321
- const target = edgeKey(start, end);
37672
+ const target = edgeKey$1(start, end);
37322
37673
  for (let i = 0; i < face.vertices.length; i++) {
37323
37674
  const faceStart = face.vertices[i];
37324
37675
  const faceEnd = face.vertices[(i + 1) % face.vertices.length];
37325
- if (faceStart && faceEnd && edgeKey(faceStart, faceEnd) === target) return i;
37676
+ if (faceStart && faceEnd && edgeKey$1(faceStart, faceEnd) === target) return i;
37326
37677
  }
37327
37678
  return null;
37328
37679
  }
@@ -37523,7 +37874,7 @@ function topologyPayloadToTopology(payload) {
37523
37874
  const start = explicitVertices[explicitEdge.vertices[0]];
37524
37875
  const end = explicitVertices[explicitEdge.vertices[1]];
37525
37876
  if (!isVec3(start) || !isVec3(end)) continue;
37526
- const key2 = edgeKey(start, end);
37877
+ const key2 = edgeKey$1(start, end);
37527
37878
  if (seenEdges.has(key2)) continue;
37528
37879
  seenEdges.set(key2, edges.size);
37529
37880
  const display = explicitEdgeDisplayName(payload, explicitEdge, explicitEdgeIndex, start, end);
@@ -37543,7 +37894,7 @@ function topologyPayloadToTopology(payload) {
37543
37894
  const start = face.vertices[i];
37544
37895
  const end = face.vertices[(i + 1) % face.vertices.length];
37545
37896
  if (!start || !end) continue;
37546
- const key2 = edgeKey(start, end);
37897
+ const key2 = edgeKey$1(start, end);
37547
37898
  if (seenEdges.has(key2)) continue;
37548
37899
  seenEdges.set(key2, edges.size);
37549
37900
  edges.set(`${face.id}:edge-${i}`, {
@@ -37592,11 +37943,11 @@ const _TruckShapeBackend = class _TruckShapeBackend {
37592
37943
  }
37593
37944
  mirror(normal2) {
37594
37945
  const [nx0, ny0, nz0] = normal2;
37595
- const len = Math.hypot(nx0, ny0, nz0);
37596
- if (len < 1e-12) return this.clone();
37597
- const nx = nx0 / len;
37598
- const ny = ny0 / len;
37599
- const nz = nz0 / len;
37946
+ const len2 = Math.hypot(nx0, ny0, nz0);
37947
+ if (len2 < 1e-12) return this.clone();
37948
+ const nx = nx0 / len2;
37949
+ const ny = ny0 / len2;
37950
+ const nz = nz0 / len2;
37600
37951
  const matrix = [
37601
37952
  1 - 2 * nx * nx,
37602
37953
  -2 * ny * nx,
@@ -37638,7 +37989,7 @@ const _TruckShapeBackend = class _TruckShapeBackend {
37638
37989
  if (normalLength <= 1e-12) throw new Error("Truck trimByPlane() normal vector must not be zero.");
37639
37990
  const n = [normal2[0] / normalLength, normal2[1] / normalLength, normal2[2] / normalLength];
37640
37991
  const signedOffset = originOffset / normalLength;
37641
- const distances = boundsCorners(bounds).map((corner) => dot$1(corner, n) - signedOffset);
37992
+ const distances = boundsCorners(bounds).map((corner) => dot$2(corner, n) - signedOffset);
37642
37993
  if (distances.every((distance) => distance >= -1e-8)) return this.clone();
37643
37994
  if (distances.every((distance) => distance <= 1e-8)) return new _TruckShapeBackend(wasm.geometry_create_empty());
37644
37995
  const halfSpace = createBoundedHalfSpace(bounds, normal2, originOffset);
@@ -37785,10 +38136,10 @@ function applyProfileTransformPoint(point2, step) {
37785
38136
  case "scale":
37786
38137
  return [x2 * step.x, y2 * step.y];
37787
38138
  case "mirror": {
37788
- const len = Math.hypot(step.normalX, step.normalY);
37789
- if (len < 1e-12) return [x2, y2];
37790
- const nx = step.normalX / len;
37791
- const ny = step.normalY / len;
38139
+ const len2 = Math.hypot(step.normalX, step.normalY);
38140
+ if (len2 < 1e-12) return [x2, y2];
38141
+ const nx = step.normalX / len2;
38142
+ const ny = step.normalY / len2;
37792
38143
  const d2 = x2 * nx + y2 * ny;
37793
38144
  return [x2 - 2 * d2 * nx, y2 - 2 * d2 * ny];
37794
38145
  }
@@ -39715,6 +40066,9 @@ function applyNativeTruckFilletTargets(initialShape, targets, radius, segments,
39715
40066
  }
39716
40067
  }
39717
40068
  function lowerFilletEdgesPlan(plan) {
40069
+ if (plan.variableRadius) {
40070
+ throw new Error("Variable-radius fillet requires the OCCT backend. Run the CLI with --backend occt.");
40071
+ }
39718
40072
  if (plan.edgeQuery) {
39719
40073
  throw new Error("filletEdges(): deferred edge queries are not supported by the Truck lowerer yet.");
39720
40074
  }
@@ -40250,11 +40604,11 @@ function applyShapeTransformToPoint(point2, step) {
40250
40604
  point2
40251
40605
  );
40252
40606
  case "mirror": {
40253
- const len = Math.hypot(step.normalX, step.normalY, step.normalZ);
40254
- if (len < 1e-12) return [point2[0], point2[1], point2[2]];
40255
- const nx = step.normalX / len;
40256
- const ny = step.normalY / len;
40257
- const nz = step.normalZ / len;
40607
+ const len2 = Math.hypot(step.normalX, step.normalY, step.normalZ);
40608
+ if (len2 < 1e-12) return [point2[0], point2[1], point2[2]];
40609
+ const nx = step.normalX / len2;
40610
+ const ny = step.normalY / len2;
40611
+ const nz = step.normalZ / len2;
40258
40612
  const d2 = point2[0] * nx + point2[1] * ny + point2[2] * nz;
40259
40613
  return [point2[0] - 2 * d2 * nx, point2[1] - 2 * d2 * ny, point2[2] - 2 * d2 * nz];
40260
40614
  }
@@ -40335,9 +40689,9 @@ function triangleNormal(vertices, triangle) {
40335
40689
  const ab = [b[0] - a2[0], b[1] - a2[1], b[2] - a2[2]];
40336
40690
  const ac = [c2[0] - a2[0], c2[1] - a2[1], c2[2] - a2[2]];
40337
40691
  const normal2 = cross3$3(ab, ac);
40338
- const len = Math.hypot(normal2[0], normal2[1], normal2[2]);
40339
- if (len <= 1e-12) return null;
40340
- return [normal2[0] / len, normal2[1] / len, normal2[2] / len];
40692
+ const len2 = Math.hypot(normal2[0], normal2[1], normal2[2]);
40693
+ if (len2 <= 1e-12) return null;
40694
+ return [normal2[0] / len2, normal2[1] / len2, normal2[2] / len2];
40341
40695
  }
40342
40696
  function offsetClosedSurfaceMesh(mesh, thickness) {
40343
40697
  let triangles;
@@ -40361,12 +40715,12 @@ function offsetClosedSurfaceMesh(mesh, thickness) {
40361
40715
  }
40362
40716
  const offsetVertices = mesh.vertices.map((vertex2, index2) => {
40363
40717
  const normal2 = normals[index2];
40364
- const len = Math.hypot(normal2[0], normal2[1], normal2[2]);
40365
- if (len <= 1e-12) return [vertex2[0], vertex2[1], vertex2[2]];
40718
+ const len2 = Math.hypot(normal2[0], normal2[1], normal2[2]);
40719
+ if (len2 <= 1e-12) return [vertex2[0], vertex2[1], vertex2[2]];
40366
40720
  return [
40367
- vertex2[0] - normal2[0] / len * thickness,
40368
- vertex2[1] - normal2[1] / len * thickness,
40369
- vertex2[2] - normal2[2] / len * thickness
40721
+ vertex2[0] - normal2[0] / len2 * thickness,
40722
+ vertex2[1] - normal2[1] / len2 * thickness,
40723
+ vertex2[2] - normal2[2] / len2 * thickness
40370
40724
  ];
40371
40725
  });
40372
40726
  const innerOffset = mesh.vertices.length;
@@ -40425,6 +40779,10 @@ function lowerSurfaceRuledPlan(plan) {
40425
40779
  return lowerSurfaceGrid(surfaceGridForRuledPlan(plan));
40426
40780
  }
40427
40781
  function lowerSurfaceFillPlan(plan) {
40782
+ var _a3, _b3;
40783
+ if ((((_a3 = plan.interiorCurves) == null ? void 0 : _a3.length) ?? 0) > 0 || (((_b3 = plan.interiorPoints) == null ? void 0 : _b3.length) ?? 0) > 0) {
40784
+ truckUnsupported("surface fill with interior constraint curves/points");
40785
+ }
40428
40786
  const grid = surfaceGridForFillPlan(plan);
40429
40787
  if (!grid) truckUnsupported("surface fill constraints beyond four-boundary Coons patches");
40430
40788
  return lowerSurfaceGrid(grid);
@@ -40701,6 +41059,10 @@ function lowerShapeCompilePlanToTruckBackendUncached(plan) {
40701
41059
  return lowerChamferEdgesPlan(plan);
40702
41060
  case "cornerYBlend":
40703
41061
  return lowerCornerYBlendPlan();
41062
+ case "faceFillet":
41063
+ throw new Error("Blend.Face() requires the OCCT backend. Run the CLI with --backend occt.");
41064
+ case "fullRound":
41065
+ throw new Error("Blend.FullRound() requires the OCCT backend. Run the CLI with --backend occt.");
40704
41066
  case "draft":
40705
41067
  return lowerDraftPlan(plan);
40706
41068
  case "shell":
@@ -41044,10 +41406,10 @@ function transformProfilePointForRadial(point2, transform) {
41044
41406
  return [point2[0] * c2 - point2[1] * s, point2[0] * s + point2[1] * c2];
41045
41407
  }
41046
41408
  case "mirror": {
41047
- const len = Math.hypot(transform.normalX, transform.normalY);
41048
- if (len <= EXACT_PROFILE_EPS) return null;
41049
- const nx = transform.normalX / len;
41050
- const ny = transform.normalY / len;
41409
+ const len2 = Math.hypot(transform.normalX, transform.normalY);
41410
+ if (len2 <= EXACT_PROFILE_EPS) return null;
41411
+ const nx = transform.normalX / len2;
41412
+ const ny = transform.normalY / len2;
41051
41413
  const d2 = point2[0] * nx + point2[1] * ny;
41052
41414
  return [point2[0] - 2 * d2 * nx, point2[1] - 2 * d2 * ny];
41053
41415
  }
@@ -43296,6 +43658,8 @@ function exactVerticalProjectionProfilePlan(plan) {
43296
43658
  const equivalent = zAlignedFromSlicesShapePlan(plan);
43297
43659
  return equivalent ? exactVerticalProjectionProfilePlan(equivalent) : null;
43298
43660
  }
43661
+ case "fromSectionFrames":
43662
+ return null;
43299
43663
  case "transform": {
43300
43664
  const base = exactVerticalProjectionProfilePlan(plan.base);
43301
43665
  if (!base) return null;
@@ -43736,6 +44100,8 @@ function exactTruckProjectedProfilePlan(plan) {
43736
44100
  const equivalent = zAlignedFromSlicesShapePlan(plan);
43737
44101
  return equivalent ? exactTruckProjectedProfilePlan(equivalent) : null;
43738
44102
  }
44103
+ case "fromSectionFrames":
44104
+ return null;
43739
44105
  case "sheetMetal":
43740
44106
  case "shell":
43741
44107
  case "hole":
@@ -43745,6 +44111,8 @@ function exactTruckProjectedProfilePlan(plan) {
43745
44111
  case "chamfer":
43746
44112
  case "filletEdges":
43747
44113
  case "cornerYBlend":
44114
+ case "faceFillet":
44115
+ case "fullRound":
43748
44116
  case "chamferEdges":
43749
44117
  case "draft":
43750
44118
  case "importedMesh":
@@ -43884,6 +44252,8 @@ function exactTruckSlicedProfilePlan(plan, offset) {
43884
44252
  const equivalent = zAlignedFromSlicesShapePlan(plan);
43885
44253
  return equivalent ? exactTruckSlicedProfilePlan(equivalent, offset) : null;
43886
44254
  }
44255
+ case "fromSectionFrames":
44256
+ return null;
43887
44257
  case "sheetMetal":
43888
44258
  case "shell":
43889
44259
  case "hole":
@@ -43893,6 +44263,8 @@ function exactTruckSlicedProfilePlan(plan, offset) {
43893
44263
  case "chamfer":
43894
44264
  case "filletEdges":
43895
44265
  case "cornerYBlend":
44266
+ case "faceFillet":
44267
+ case "fullRound":
43896
44268
  case "chamferEdges":
43897
44269
  case "draft":
43898
44270
  case "importedMesh":
@@ -44046,6 +44418,9 @@ function tracePlanTransformations(plan) {
44046
44418
  case "fillet":
44047
44419
  case "chamfer":
44048
44420
  case "filletEdges":
44421
+ case "faceFillet":
44422
+ case "fullRound":
44423
+ case "cornerYBlend":
44049
44424
  case "chamferEdges":
44050
44425
  case "draft":
44051
44426
  case "offsetSolid":
@@ -44059,24 +44434,27 @@ function findOriginOperation(plan) {
44059
44434
  if (!plan) return { operation: "unknown" };
44060
44435
  switch (plan.kind) {
44061
44436
  case "queryOwner":
44062
- return { operation: plan.owner.operation, owner: plan.owner };
44437
+ return { operation: plan.owner.operation, owner: plan.owner, originPlan: plan };
44063
44438
  case "transform":
44064
44439
  return findOriginOperation(plan.base);
44065
44440
  case "boolean":
44066
- return { operation: `${plan.op} (${plan.shapes.length} operands)` };
44441
+ return { operation: `${plan.op} (${plan.shapes.length} operands)`, originPlan: plan };
44067
44442
  case "shell":
44068
44443
  case "hole":
44069
44444
  case "cut":
44070
44445
  case "fillet":
44071
44446
  case "chamfer":
44072
44447
  case "filletEdges":
44448
+ case "faceFillet":
44449
+ case "fullRound":
44450
+ case "cornerYBlend":
44073
44451
  case "chamferEdges":
44074
44452
  case "draft":
44075
44453
  case "offsetSolid":
44076
44454
  case "trimByPlane":
44077
44455
  return findOriginOperation(plan.base);
44078
44456
  default:
44079
- return { operation: plan.kind };
44457
+ return { operation: plan.kind, originPlan: plan };
44080
44458
  }
44081
44459
  }
44082
44460
  function summarizeProfile(profile) {
@@ -44138,6 +44516,18 @@ function collectTimelineEntries(plan, entries) {
44138
44516
  collectTimelineEntries(plan.base, entries);
44139
44517
  entries.push({ kind: "filletEdges", label: "Fillet Edges", summary: `r = ${plan.radius}`, category: "modifier" });
44140
44518
  return;
44519
+ case "faceFillet":
44520
+ collectTimelineEntries(plan.base, entries);
44521
+ entries.push({ kind: "faceFillet", label: "Face Fillet", summary: `r = ${plan.radius}`, category: "modifier" });
44522
+ return;
44523
+ case "fullRound":
44524
+ collectTimelineEntries(plan.base, entries);
44525
+ entries.push({ kind: "fullRound", label: "Full Round", summary: `r = ${plan.radius}`, category: "modifier" });
44526
+ return;
44527
+ case "cornerYBlend":
44528
+ collectTimelineEntries(plan.base, entries);
44529
+ entries.push({ kind: "cornerYBlend", label: "Corner Y-Blend", summary: `r = ${plan.radius}`, category: "modifier" });
44530
+ return;
44141
44531
  case "chamferEdges":
44142
44532
  collectTimelineEntries(plan.base, entries);
44143
44533
  entries.push({ kind: "chamferEdges", label: "Chamfer Edges", summary: `size = ${plan.size}`, category: "modifier" });
@@ -44221,13 +44611,14 @@ function buildOperationTimeline(plan) {
44221
44611
  collectTimelineEntries(plan, entries);
44222
44612
  return entries;
44223
44613
  }
44224
- function traceFaceTransformationHistory(plan, face) {
44614
+ function traceFaceTransformationHistory(plan, face, sourceSpans) {
44225
44615
  const transformations = tracePlanTransformations(plan);
44226
- const origin = findOriginOperation(plan);
44616
+ const { originPlan, ...origin } = findOriginOperation(plan);
44227
44617
  const timeline = buildOperationTimeline(plan);
44618
+ const sourceSpan = originPlan && sourceSpans ? sourceSpans.get(shapeCompilePlanCacheKey(originPlan)) : void 0;
44228
44619
  return {
44229
44620
  faceName: face.name,
44230
- origin,
44621
+ origin: sourceSpan ? { ...origin, sourceSpan } : origin,
44231
44622
  transformations,
44232
44623
  query: face.query,
44233
44624
  timeline
@@ -44250,21 +44641,21 @@ function normalizeFaceSelector(selector) {
44250
44641
  }
44251
44642
  return { compilePlanName: null, query: selector };
44252
44643
  }
44253
- function cross(a2, b) {
44644
+ function cross$1(a2, b) {
44254
44645
  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]];
44255
44646
  }
44256
- function dot(a2, b) {
44647
+ function dot$1(a2, b) {
44257
44648
  return a2[0] * b[0] + a2[1] * b[1] + a2[2] * b[2];
44258
44649
  }
44259
44650
  function normVec3(v) {
44260
- const len = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
44261
- if (len < 1e-10) return null;
44262
- return [v[0] / len, v[1] / len, v[2] / len];
44651
+ const len2 = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
44652
+ if (len2 < 1e-10) return null;
44653
+ return [v[0] / len2, v[1] / len2, v[2] / len2];
44263
44654
  }
44264
44655
  function tangentFrame(normal2) {
44265
44656
  const ref = Math.abs(normal2[0]) < 0.9 ? [1, 0, 0] : [0, 1, 0];
44266
- const v = normVec3(cross(normal2, ref));
44267
- const u2 = normVec3(cross(v, normal2));
44657
+ const v = normVec3(cross$1(normal2, ref));
44658
+ const u2 = normVec3(cross$1(v, normal2));
44268
44659
  return { u: u2, v };
44269
44660
  }
44270
44661
  const NORMAL_COS_EPS$1 = 0.9998;
@@ -44283,19 +44674,19 @@ function clusterMeshFaces(shape) {
44283
44674
  const v2 = [vertProperties[i2 * numProp], vertProperties[i2 * numProp + 1], vertProperties[i2 * numProp + 2]];
44284
44675
  const e1 = [v1[0] - v0[0], v1[1] - v0[1], v1[2] - v0[2]];
44285
44676
  const e2 = [v2[0] - v0[0], v2[1] - v0[1], v2[2] - v0[2]];
44286
- const rawCross = cross(e1, e2);
44677
+ const rawCross = cross$1(e1, e2);
44287
44678
  const normal2 = normVec3(rawCross);
44288
44679
  if (!normal2) continue;
44289
44680
  const crossLen = Math.sqrt(rawCross[0] * rawCross[0] + rawCross[1] * rawCross[1] + rawCross[2] * rawCross[2]);
44290
44681
  const triArea = crossLen / 2;
44291
- const planeOffset = dot(normal2, v0);
44292
- const triCentroid = [(v0[0] + v1[0] + v2[0]) / 3, (v0[1] + v1[1] + v2[1]) / 3, (v0[2] + v1[2] + v2[2]) / 3];
44682
+ const planeOffset = dot$1(normal2, v0);
44683
+ const triCentroid2 = [(v0[0] + v1[0] + v2[0]) / 3, (v0[1] + v1[1] + v2[1]) / 3, (v0[2] + v1[2] + v2[2]) / 3];
44293
44684
  let merged = false;
44294
44685
  for (const c2 of clusters) {
44295
- if (dot(c2.normal, normal2) > NORMAL_COS_EPS$1 && Math.abs(c2.planeOffset - planeOffset) < PLANE_OFFSET_EPS$1) {
44296
- c2.centroidSum[0] += triCentroid[0];
44297
- c2.centroidSum[1] += triCentroid[1];
44298
- c2.centroidSum[2] += triCentroid[2];
44686
+ if (dot$1(c2.normal, normal2) > NORMAL_COS_EPS$1 && Math.abs(c2.planeOffset - planeOffset) < PLANE_OFFSET_EPS$1) {
44687
+ c2.centroidSum[0] += triCentroid2[0];
44688
+ c2.centroidSum[1] += triCentroid2[1];
44689
+ c2.centroidSum[2] += triCentroid2[2];
44299
44690
  c2.count++;
44300
44691
  c2.area += triArea;
44301
44692
  merged = true;
@@ -44306,7 +44697,7 @@ function clusterMeshFaces(shape) {
44306
44697
  clusters.push({
44307
44698
  normal: normal2,
44308
44699
  planeOffset,
44309
- centroidSum: [triCentroid[0], triCentroid[1], triCentroid[2]],
44700
+ centroidSum: [triCentroid2[0], triCentroid2[1], triCentroid2[2]],
44310
44701
  count: 1,
44311
44702
  area: triArea
44312
44703
  });
@@ -44334,7 +44725,7 @@ function queryMeshFaces(shape, query) {
44334
44725
  let clusters = clusterMeshFaces(shape);
44335
44726
  if (query.normal) {
44336
44727
  const qn = query.normal;
44337
- clusters = clusters.filter((c2) => dot(c2.normal, qn) > NORMAL_COS_EPS$1);
44728
+ clusters = clusters.filter((c2) => dot$1(c2.normal, qn) > NORMAL_COS_EPS$1);
44338
44729
  }
44339
44730
  if (query.planar !== false) {
44340
44731
  clusters = clusters.filter((c2) => c2.normal !== null);
@@ -44350,7 +44741,7 @@ function queryMeshFace(shape, query) {
44350
44741
  let clusters = clusterMeshFaces(shape);
44351
44742
  if (query.normal) {
44352
44743
  const qn = query.normal;
44353
- clusters = clusters.filter((c2) => dot(c2.normal, qn) > NORMAL_COS_EPS$1);
44744
+ clusters = clusters.filter((c2) => dot$1(c2.normal, qn) > NORMAL_COS_EPS$1);
44354
44745
  }
44355
44746
  if (query.planar !== false) {
44356
44747
  clusters = clusters.filter((c2) => c2.normal !== null);
@@ -44599,7 +44990,8 @@ function applyMatrixToFace(face, matrix) {
44599
44990
  normal: normalizeAxis(tx.vector(face.normal)),
44600
44991
  query: cloneFaceQueryRef(face.query),
44601
44992
  uAxis: face.uAxis ? normalizeAxis(tx.vector(face.uAxis)) : void 0,
44602
- vAxis: face.vAxis ? normalizeAxis(tx.vector(face.vAxis)) : void 0
44993
+ vAxis: face.vAxis ? normalizeAxis(tx.vector(face.vAxis)) : void 0,
44994
+ surface: transformFaceSurface(face.surface, tx)
44603
44995
  };
44604
44996
  }
44605
44997
  function applyMatrixToFaceTable(table, matrix) {
@@ -44622,11 +45014,11 @@ function canonicalShapeStepMatrix(step) {
44622
45014
  }
44623
45015
  }
44624
45016
  function mirrorMatrix$1(normal2) {
44625
- const len = Math.hypot(normal2[0], normal2[1], normal2[2]);
44626
- if (len < 1e-12) return Transform.identity().toArray();
44627
- const nx = normal2[0] / len;
44628
- const ny = normal2[1] / len;
44629
- const nz = normal2[2] / len;
45017
+ const len2 = Math.hypot(normal2[0], normal2[1], normal2[2]);
45018
+ if (len2 < 1e-12) return Transform.identity().toArray();
45019
+ const nx = normal2[0] / len2;
45020
+ const ny = normal2[1] / len2;
45021
+ const nz = normal2[2] / len2;
44630
45022
  return [
44631
45023
  1 - 2 * nx * nx,
44632
45024
  -2 * nx * ny,
@@ -44689,9 +45081,9 @@ function pointOnWorkplane(origin, workplaneU, workplaneV, u2, v, depthDir, depth
44689
45081
  ];
44690
45082
  }
44691
45083
  function normalize2d(vec2) {
44692
- const len = Math.hypot(vec2[0], vec2[1]);
44693
- if (len < 1e-12) return [1, 0];
44694
- return [vec2[0] / len, vec2[1] / len];
45084
+ const len2 = Math.hypot(vec2[0], vec2[1]);
45085
+ if (len2 < 1e-12) return [1, 0];
45086
+ return [vec2[0] / len2, vec2[1] / len2];
44695
45087
  }
44696
45088
  function cross3$2(a2, b) {
44697
45089
  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]];
@@ -44717,6 +45109,9 @@ function faceFrom2DEdge(name, start, end, zMid, ownerQuery) {
44717
45109
  query: cloneFaceQueryRef(ownerQuery)
44718
45110
  };
44719
45111
  }
45112
+ function withPlaneCarrier(face) {
45113
+ return { ...face, surface: { kind: "plane", normal: cloneVec3$1(face.normal) } };
45114
+ }
44720
45115
  function createTrackedFaceQuery(name, owner) {
44721
45116
  if (!owner) return void 0;
44722
45117
  return {
@@ -44780,6 +45175,7 @@ function buildBoxFaceTable(plan, owner) {
44780
45175
  planar: true,
44781
45176
  uAxis: [1, 0, 0],
44782
45177
  vAxis: [0, 1, 0],
45178
+ surface: { kind: "plane", normal: [0, 0, 1] },
44783
45179
  query: topQuery
44784
45180
  });
44785
45181
  registerFace(table, {
@@ -44789,22 +45185,23 @@ function buildBoxFaceTable(plan, owner) {
44789
45185
  planar: true,
44790
45186
  uAxis: [1, 0, 0],
44791
45187
  vAxis: [0, -1, 0],
45188
+ surface: { kind: "plane", normal: [0, 0, -1] },
44792
45189
  query: bottomQuery
44793
45190
  });
44794
- registerFace(table, {
44795
- ...faceFrom2DEdge("side-bottom", bl, br, (zTop + zBot) / 2, createTrackedFaceQuery("side-bottom", owner))
44796
- });
44797
- registerFace(table, {
44798
- ...faceFrom2DEdge("side-right", br, tr, (zTop + zBot) / 2, createTrackedFaceQuery("side-right", owner))
44799
- });
44800
- registerFace(table, {
44801
- ...faceFrom2DEdge("side-top", tr, tl, (zTop + zBot) / 2, createTrackedFaceQuery("side-top", owner))
44802
- });
44803
- registerFace(table, {
44804
- ...faceFrom2DEdge("side-left", tl, bl, (zTop + zBot) / 2, createTrackedFaceQuery("side-left", owner))
44805
- });
45191
+ registerFace(table, withPlaneCarrier(faceFrom2DEdge("side-bottom", bl, br, (zTop + zBot) / 2, createTrackedFaceQuery("side-bottom", owner))));
45192
+ registerFace(table, withPlaneCarrier(faceFrom2DEdge("side-right", br, tr, (zTop + zBot) / 2, createTrackedFaceQuery("side-right", owner))));
45193
+ registerFace(table, withPlaneCarrier(faceFrom2DEdge("side-top", tr, tl, (zTop + zBot) / 2, createTrackedFaceQuery("side-top", owner))));
45194
+ registerFace(table, withPlaneCarrier(faceFrom2DEdge("side-left", tl, bl, (zTop + zBot) / 2, createTrackedFaceQuery("side-left", owner))));
44806
45195
  return table;
44807
45196
  }
45197
+ function cylinderSideCarrier(plan) {
45198
+ const radiusBottom = Math.abs(plan.radius);
45199
+ const radiusTop = Math.abs(plan.radiusTop ?? plan.radius);
45200
+ const height = Math.abs(plan.height);
45201
+ const origin = [0, 0, Math.min(0, plan.height)];
45202
+ const axis = [0, 0, 1];
45203
+ return radiusBottom === radiusTop ? { kind: "cylinder", origin, axis, radius: radiusBottom, height } : { kind: "cone", origin, axis, radiusBottom, radiusTop, height };
45204
+ }
44808
45205
  function buildCylinderFaceTable(plan, owner) {
44809
45206
  const table = emptyFaceTable();
44810
45207
  const zBot = 0;
@@ -44818,6 +45215,7 @@ function buildCylinderFaceTable(plan, owner) {
44818
45215
  planar: true,
44819
45216
  uAxis: [1, 0, 0],
44820
45217
  vAxis: [0, 1, 0],
45218
+ surface: { kind: "plane", normal: [0, 0, 1] },
44821
45219
  query: createTrackedFaceQuery("top", owner)
44822
45220
  });
44823
45221
  registerFace(table, {
@@ -44827,6 +45225,7 @@ function buildCylinderFaceTable(plan, owner) {
44827
45225
  planar: true,
44828
45226
  uAxis: [1, 0, 0],
44829
45227
  vAxis: [0, -1, 0],
45228
+ surface: { kind: "plane", normal: [0, 0, -1] },
44830
45229
  query: createTrackedFaceQuery("bottom", owner)
44831
45230
  });
44832
45231
  registerFace(table, {
@@ -44834,10 +45233,38 @@ function buildCylinderFaceTable(plan, owner) {
44834
45233
  normal: [1, 0, 0],
44835
45234
  center: [sideRadius, 0, (zTop + zBot) / 2],
44836
45235
  planar: false,
45236
+ surface: cylinderSideCarrier(plan),
44837
45237
  query: createTrackedFaceQuery("side", owner)
44838
45238
  });
44839
45239
  return table;
44840
45240
  }
45241
+ function buildSphereFaceTable(plan, owner) {
45242
+ const table = emptyFaceTable();
45243
+ const radius = Math.abs(plan.radius);
45244
+ registerFace(table, {
45245
+ name: "surface",
45246
+ normal: [1, 0, 0],
45247
+ center: [radius, 0, 0],
45248
+ planar: false,
45249
+ surface: { kind: "sphere", center: [0, 0, 0], radius },
45250
+ query: createTrackedFaceQuery("surface", owner)
45251
+ });
45252
+ return table;
45253
+ }
45254
+ function buildTorusFaceTable(plan, owner) {
45255
+ const table = emptyFaceTable();
45256
+ const majorRadius = Math.abs(plan.majorRadius);
45257
+ const minorRadius = Math.abs(plan.minorRadius);
45258
+ registerFace(table, {
45259
+ name: "surface",
45260
+ normal: [1, 0, 0],
45261
+ center: [majorRadius + minorRadius, 0, 0],
45262
+ planar: false,
45263
+ surface: { kind: "torus", center: [0, 0, 0], axis: [0, 0, 1], majorRadius, minorRadius },
45264
+ query: createTrackedFaceQuery("surface", owner)
45265
+ });
45266
+ return table;
45267
+ }
44841
45268
  function buildRectExtrudeFaceTable(profile, height, owner) {
44842
45269
  const corners = rectLikeProfileCorners(profile);
44843
45270
  if (!corners) return emptyFaceTable();
@@ -44855,6 +45282,7 @@ function buildRectExtrudeFaceTable(profile, height, owner) {
44855
45282
  planar: true,
44856
45283
  uAxis: topU,
44857
45284
  vAxis: topV,
45285
+ surface: { kind: "plane", normal: [0, 0, 1] },
44858
45286
  query: createTrackedFaceQuery("top", owner)
44859
45287
  });
44860
45288
  registerFace(table, {
@@ -44864,12 +45292,13 @@ function buildRectExtrudeFaceTable(profile, height, owner) {
44864
45292
  planar: true,
44865
45293
  uAxis: topU,
44866
45294
  vAxis: [-topV[0], -topV[1], -topV[2]],
45295
+ surface: { kind: "plane", normal: [0, 0, -1] },
44867
45296
  query: createTrackedFaceQuery("bottom", owner)
44868
45297
  });
44869
- registerFace(table, faceFrom2DEdge("side-bottom", bl, br, (zTop + zBot) / 2, createTrackedFaceQuery("side-bottom", owner)));
44870
- registerFace(table, faceFrom2DEdge("side-right", br, tr, (zTop + zBot) / 2, createTrackedFaceQuery("side-right", owner)));
44871
- registerFace(table, faceFrom2DEdge("side-top", tr, tl, (zTop + zBot) / 2, createTrackedFaceQuery("side-top", owner)));
44872
- registerFace(table, faceFrom2DEdge("side-left", tl, bl, (zTop + zBot) / 2, createTrackedFaceQuery("side-left", owner)));
45298
+ registerFace(table, withPlaneCarrier(faceFrom2DEdge("side-bottom", bl, br, (zTop + zBot) / 2, createTrackedFaceQuery("side-bottom", owner))));
45299
+ registerFace(table, withPlaneCarrier(faceFrom2DEdge("side-right", br, tr, (zTop + zBot) / 2, createTrackedFaceQuery("side-right", owner))));
45300
+ registerFace(table, withPlaneCarrier(faceFrom2DEdge("side-top", tr, tl, (zTop + zBot) / 2, createTrackedFaceQuery("side-top", owner))));
45301
+ registerFace(table, withPlaneCarrier(faceFrom2DEdge("side-left", tl, bl, (zTop + zBot) / 2, createTrackedFaceQuery("side-left", owner))));
44873
45302
  return table;
44874
45303
  }
44875
45304
  function buildCircleExtrudeFaceTable(profile, height, owner) {
@@ -44889,6 +45318,7 @@ function buildCircleExtrudeFaceTable(profile, height, owner) {
44889
45318
  planar: true,
44890
45319
  uAxis: xAxis,
44891
45320
  vAxis: yAxis,
45321
+ surface: { kind: "plane", normal: [0, 0, 1] },
44892
45322
  query: createTrackedFaceQuery("top", owner)
44893
45323
  });
44894
45324
  registerFace(table, {
@@ -44898,6 +45328,7 @@ function buildCircleExtrudeFaceTable(profile, height, owner) {
44898
45328
  planar: true,
44899
45329
  uAxis: xAxis,
44900
45330
  vAxis: [-yAxis[0], -yAxis[1], -yAxis[2]],
45331
+ surface: { kind: "plane", normal: [0, 0, -1] },
44901
45332
  query: createTrackedFaceQuery("bottom", owner)
44902
45333
  });
44903
45334
  registerFace(table, {
@@ -44905,10 +45336,65 @@ function buildCircleExtrudeFaceTable(profile, height, owner) {
44905
45336
  normal: sideNormal,
44906
45337
  center: [sidePoint[0], sidePoint[1], (zTop + zBot) / 2],
44907
45338
  planar: false,
45339
+ surface: { kind: "cylinder", origin: [origin[0], origin[1], zBot], axis: [0, 0, 1], radius: Math.abs(profile.radius), height },
44908
45340
  query: createTrackedFaceQuery("side", owner)
44909
45341
  });
44910
45342
  return table;
44911
45343
  }
45344
+ function collectExtrudeSideCarriers(profile, height, parent, prefix) {
45345
+ switch (profile.kind) {
45346
+ case "circle": {
45347
+ const side = buildCircleExtrudeFaceTable(profile, height, null).faces.get("side");
45348
+ return side ? [applyMatrixToFace({ ...side, name: `${prefix}side` }, parent.toArray())] : [];
45349
+ }
45350
+ case "rect":
45351
+ case "roundedRect":
45352
+ case "polygon": {
45353
+ const leaf = buildRectExtrudeFaceTable(profile, height, null);
45354
+ const out = [];
45355
+ for (const name of ["side-bottom", "side-right", "side-top", "side-left"]) {
45356
+ const face = leaf.faces.get(name);
45357
+ if (face) out.push(applyMatrixToFace({ ...face, name: `${prefix}${name}` }, parent.toArray()));
45358
+ }
45359
+ return out;
45360
+ }
45361
+ case "boolean": {
45362
+ const composed = parent.mul(profileTransformMatrix(profile.transforms));
45363
+ return profile.profiles.flatMap((child, index2) => collectExtrudeSideCarriers(child, height, composed, `${prefix}op${index2}/`));
45364
+ }
45365
+ default:
45366
+ return [];
45367
+ }
45368
+ }
45369
+ function buildBooleanExtrudeFaceTable(profile, height, owner) {
45370
+ const table = emptyFaceTable();
45371
+ const zBot = 0;
45372
+ const zTop = zBot + height;
45373
+ registerFace(table, {
45374
+ name: "top",
45375
+ normal: [0, 0, 1],
45376
+ center: [0, 0, zTop],
45377
+ planar: true,
45378
+ uAxis: [1, 0, 0],
45379
+ vAxis: [0, 1, 0],
45380
+ surface: { kind: "plane", normal: [0, 0, 1] },
45381
+ query: createTrackedFaceQuery("top", owner)
45382
+ });
45383
+ registerFace(table, {
45384
+ name: "bottom",
45385
+ normal: [0, 0, -1],
45386
+ center: [0, 0, zBot],
45387
+ planar: true,
45388
+ uAxis: [1, 0, 0],
45389
+ vAxis: [0, -1, 0],
45390
+ surface: { kind: "plane", normal: [0, 0, -1] },
45391
+ query: createTrackedFaceQuery("bottom", owner)
45392
+ });
45393
+ for (const side of collectExtrudeSideCarriers(profile, height, Transform.identity(), "")) {
45394
+ registerFace(table, side);
45395
+ }
45396
+ return table;
45397
+ }
44912
45398
  function buildExtrudeFaceTable(plan, owner) {
44913
45399
  var _a3, _b3;
44914
45400
  let table;
@@ -44921,6 +45407,9 @@ function buildExtrudeFaceTable(plan, owner) {
44921
45407
  case "circle":
44922
45408
  table = buildCircleExtrudeFaceTable(plan.profile, plan.height, owner);
44923
45409
  break;
45410
+ case "boolean":
45411
+ table = buildBooleanExtrudeFaceTable(plan.profile, plan.height, owner);
45412
+ break;
44924
45413
  default:
44925
45414
  table = emptyFaceTable();
44926
45415
  break;
@@ -45602,7 +46091,9 @@ function resolveShapeFaceTableInternal(plan, owner) {
45602
46091
  return table;
45603
46092
  }
45604
46093
  case "sphere":
46094
+ return buildSphereFaceTable(plan, owner);
45605
46095
  case "torus":
46096
+ return buildTorusFaceTable(plan, owner);
45606
46097
  case "variableSweep":
45607
46098
  case "fillet":
45608
46099
  case "chamfer":
@@ -45610,8 +46101,13 @@ function resolveShapeFaceTableInternal(plan, owner) {
45610
46101
  case "offsetSolid":
45611
46102
  return emptyFaceTable();
45612
46103
  // Fillet/chamfer edges preserve all base faces — delegate to base plan.
46104
+ // Caveat: cornerYBlend and fullRound each CONSUME a base face (the Y-corner
46105
+ // region / the narrow center face), so the delegated base table is a known
46106
+ // provenance approximation for those two kinds.
45613
46107
  case "filletEdges":
45614
46108
  case "cornerYBlend":
46109
+ case "faceFillet":
46110
+ case "fullRound":
45615
46111
  case "chamferEdges":
45616
46112
  return resolveShapeFaceTableInternal(plan.base, owner);
45617
46113
  case "revolve":
@@ -45692,6 +46188,7 @@ function resolveShapeFaceTableInternal(plan, owner) {
45692
46188
  case "importedMesh":
45693
46189
  case "sdf":
45694
46190
  case "fromSlices":
46191
+ case "fromSectionFrames":
45695
46192
  case "analyticSurface":
45696
46193
  case "nurbsSurface":
45697
46194
  case "surfaceRuled":
@@ -45722,6 +46219,55 @@ function resolveShapeFace(plan, name) {
45722
46219
  if (face) return cloneFaceRefValue(face);
45723
46220
  return null;
45724
46221
  }
46222
+ function gatherShapeCarriers(plan) {
46223
+ const carriers = [];
46224
+ const seen = /* @__PURE__ */ new Set();
46225
+ const emit = (prefix, table, tx) => {
46226
+ for (const [name, raw] of table.faces.entries()) {
46227
+ const face = applyMatrixToFace(raw, tx.toArray());
46228
+ const surface = face.surface;
46229
+ if (!surface) continue;
46230
+ if (surface.kind === "ruled" || surface.kind === "nurbs") continue;
46231
+ const planePoint = surface.kind === "plane" ? cloneVec3$1(face.center) : void 0;
46232
+ const key2 = JSON.stringify([surface, planePoint ?? null]);
46233
+ if (seen.has(key2)) continue;
46234
+ seen.add(key2);
46235
+ carriers.push({ name: prefix ? `${prefix}${name}` : name, surface: cloneFaceSurface(surface), planePoint });
46236
+ }
46237
+ };
46238
+ const walk = (node, tx, prefix) => {
46239
+ if (!node) return;
46240
+ switch (node.kind) {
46241
+ case "queryOwner":
46242
+ case "surfaceExtend":
46243
+ walk(node.base, tx, prefix);
46244
+ return;
46245
+ case "transform": {
46246
+ let composed = tx;
46247
+ for (const step of node.steps) composed = composed.mul(canonicalShapeStepMatrix(step));
46248
+ walk(node.base, composed, prefix);
46249
+ return;
46250
+ }
46251
+ case "boolean": {
46252
+ emit(prefix, resolveShapeFaceTableInternal(node, null), tx);
46253
+ node.shapes.forEach((shape, index2) => walk(shape, tx, `${prefix}op${index2}/`));
46254
+ return;
46255
+ }
46256
+ case "filletEdges":
46257
+ case "cornerYBlend":
46258
+ case "faceFillet":
46259
+ case "fullRound":
46260
+ case "chamferEdges":
46261
+ walk(node.base, tx, prefix);
46262
+ return;
46263
+ default:
46264
+ emit(prefix, resolveShapeFaceTableInternal(node, null), tx);
46265
+ return;
46266
+ }
46267
+ };
46268
+ walk(plan, Transform.identity(), "");
46269
+ return carriers;
46270
+ }
45725
46271
  const DEPRECATED_SIDE_NAMES = {
45726
46272
  "side-left": "left",
45727
46273
  "side-right": "right",
@@ -45746,6 +46292,631 @@ function supportedShellCreatedFaceNames(basePlan, openFaces) {
45746
46292
  function preservedShapeFaceQueries(basePlan) {
45747
46293
  return listShapeFaceQueries(basePlan);
45748
46294
  }
46295
+ const EDGE_THRESHOLD_DOT = Math.cos(Math.PI / 180);
46296
+ const SMOOTH_THRESHOLD_DOT = Math.cos(30 * Math.PI / 180);
46297
+ function computeGeometryArrays(mesh, options = {}) {
46298
+ const { numProp, numTri: triCount, triVerts, vertProperties, vertNormals, cornerNormals } = mesh;
46299
+ if (numProp !== NUM_PROP_POSITION_ONLY && numProp !== NUM_PROP_WITH_NORMAL && numProp !== NUM_PROP_WITH_UV) {
46300
+ throw new Error(
46301
+ `computeGeometryArrays: illegal vertProperties numProp ${numProp}. Only ${NUM_PROP_POSITION_ONLY} (position), ${NUM_PROP_WITH_NORMAL} (position+normal), and ${NUM_PROP_WITH_UV} (position+normal+uv) are valid; ${numProp} (e.g. a truncated uv channel) is not.`
46302
+ );
46303
+ }
46304
+ const hasStoredNormals = numProp === NUM_PROP_WITH_NORMAL || numProp === NUM_PROP_WITH_UV;
46305
+ const hasStoredUvs = numProp === NUM_PROP_WITH_UV;
46306
+ const useCornerNormals = !!cornerNormals && cornerNormals.length === triCount * 9;
46307
+ const positions = new Float32Array(triCount * 9);
46308
+ const normals = new Float32Array(triCount * 9);
46309
+ const uvs = hasStoredUvs ? new Float32Array(triCount * 6) : void 0;
46310
+ const faceNx = new Float32Array(triCount);
46311
+ const faceNy = new Float32Array(triCount);
46312
+ const faceNz = new Float32Array(triCount);
46313
+ for (let t = 0; t < triCount; t++) {
46314
+ const i0 = triVerts[t * 3];
46315
+ const i1 = triVerts[t * 3 + 1];
46316
+ const i2 = triVerts[t * 3 + 2];
46317
+ const ax = vertProperties[i0 * numProp], ay = vertProperties[i0 * numProp + 1], az = vertProperties[i0 * numProp + 2];
46318
+ const bx = vertProperties[i1 * numProp], by = vertProperties[i1 * numProp + 1], bz = vertProperties[i1 * numProp + 2];
46319
+ const cx = vertProperties[i2 * numProp], cy = vertProperties[i2 * numProp + 1], cz = vertProperties[i2 * numProp + 2];
46320
+ const e1x = bx - ax, e1y = by - ay, e1z = bz - az;
46321
+ const e2x = cx - ax, e2y = cy - ay, e2z = cz - az;
46322
+ let fnx = e1y * e2z - e1z * e2y;
46323
+ let fny = e1z * e2x - e1x * e2z;
46324
+ let fnz = e1x * e2y - e1y * e2x;
46325
+ const len2 = Math.sqrt(fnx * fnx + fny * fny + fnz * fnz) || 1;
46326
+ fnx /= len2;
46327
+ fny /= len2;
46328
+ fnz /= len2;
46329
+ const o = t * 9;
46330
+ positions[o] = ax;
46331
+ positions[o + 1] = ay;
46332
+ positions[o + 2] = az;
46333
+ positions[o + 3] = bx;
46334
+ positions[o + 4] = by;
46335
+ positions[o + 5] = bz;
46336
+ positions[o + 6] = cx;
46337
+ positions[o + 7] = cy;
46338
+ positions[o + 8] = cz;
46339
+ if (uvs) {
46340
+ const u2 = t * 6;
46341
+ uvs[u2] = vertProperties[i0 * numProp + UV_OFFSET];
46342
+ uvs[u2 + 1] = vertProperties[i0 * numProp + UV_OFFSET + 1];
46343
+ uvs[u2 + 2] = vertProperties[i1 * numProp + UV_OFFSET];
46344
+ uvs[u2 + 3] = vertProperties[i1 * numProp + UV_OFFSET + 1];
46345
+ uvs[u2 + 4] = vertProperties[i2 * numProp + UV_OFFSET];
46346
+ uvs[u2 + 5] = vertProperties[i2 * numProp + UV_OFFSET + 1];
46347
+ }
46348
+ if (useCornerNormals) {
46349
+ for (let k2 = 0; k2 < 9; k2++) normals[o + k2] = cornerNormals[o + k2];
46350
+ } else if (vertNormals) {
46351
+ normals[o] = vertNormals[i0 * 3];
46352
+ normals[o + 1] = vertNormals[i0 * 3 + 1];
46353
+ normals[o + 2] = vertNormals[i0 * 3 + 2];
46354
+ normals[o + 3] = vertNormals[i1 * 3];
46355
+ normals[o + 4] = vertNormals[i1 * 3 + 1];
46356
+ normals[o + 5] = vertNormals[i1 * 3 + 2];
46357
+ normals[o + 6] = vertNormals[i2 * 3];
46358
+ normals[o + 7] = vertNormals[i2 * 3 + 1];
46359
+ normals[o + 8] = vertNormals[i2 * 3 + 2];
46360
+ } else if (hasStoredNormals) {
46361
+ const corners = [i0, i1, i2];
46362
+ for (let v = 0; v < 3; v++) {
46363
+ const base = corners[v] * numProp;
46364
+ const nx = vertProperties[base + NORMAL_OFFSET];
46365
+ const ny = vertProperties[base + NORMAL_OFFSET + 1];
46366
+ const nz = vertProperties[base + NORMAL_OFFSET + 2];
46367
+ const oc = o + v * 3;
46368
+ if (nx * nx + ny * ny + nz * nz > 1e-12) {
46369
+ normals[oc] = nx;
46370
+ normals[oc + 1] = ny;
46371
+ normals[oc + 2] = nz;
46372
+ } else {
46373
+ normals[oc] = fnx;
46374
+ normals[oc + 1] = fny;
46375
+ normals[oc + 2] = fnz;
46376
+ }
46377
+ }
46378
+ } else {
46379
+ normals[o] = fnx;
46380
+ normals[o + 1] = fny;
46381
+ normals[o + 2] = fnz;
46382
+ normals[o + 3] = fnx;
46383
+ normals[o + 4] = fny;
46384
+ normals[o + 5] = fnz;
46385
+ normals[o + 6] = fnx;
46386
+ normals[o + 7] = fny;
46387
+ normals[o + 8] = fnz;
46388
+ }
46389
+ faceNx[t] = fnx;
46390
+ faceNy[t] = fny;
46391
+ faceNz[t] = fnz;
46392
+ }
46393
+ if (!useCornerNormals && !vertNormals && !hasStoredNormals && triCount > 0) {
46394
+ computeAutoSmoothNormals(
46395
+ triVerts,
46396
+ vertProperties,
46397
+ numProp,
46398
+ triCount,
46399
+ faceNx,
46400
+ faceNy,
46401
+ faceNz,
46402
+ normals,
46403
+ mesh.mergeFromVert,
46404
+ mesh.mergeToVert
46405
+ );
46406
+ }
46407
+ const edgePositions = options.skipEdges ? new Float32Array(0) : computeSharpEdges(triVerts, vertProperties, numProp, triCount, faceNx, faceNy, faceNz, mesh.mergeFromVert, mesh.mergeToVert);
46408
+ return {
46409
+ positions,
46410
+ normals,
46411
+ edgePositions,
46412
+ hasSmoothNormals: triCount > 0,
46413
+ uvs
46414
+ };
46415
+ }
46416
+ function computeAutoSmoothNormals(triVerts, vertProperties, numProp, triCount, faceNx, faceNy, faceNz, normals, mergeFromVert, mergeToVert) {
46417
+ const numVerts = vertProperties.length / numProp;
46418
+ const canon = buildCanonicalMap$2(numVerts, mergeFromVert, mergeToVert);
46419
+ const vertToTris = /* @__PURE__ */ new Map();
46420
+ for (let t = 0; t < triCount; t++) {
46421
+ for (let v = 0; v < 3; v++) {
46422
+ const cv = canon[triVerts[t * 3 + v]];
46423
+ let list = vertToTris.get(cv);
46424
+ if (!list) {
46425
+ list = [];
46426
+ vertToTris.set(cv, list);
46427
+ }
46428
+ list.push(t);
46429
+ }
46430
+ }
46431
+ for (let t = 0; t < triCount; t++) {
46432
+ for (let v = 0; v < 3; v++) {
46433
+ const cv = canon[triVerts[t * 3 + v]];
46434
+ const adjacentTris = vertToTris.get(cv);
46435
+ if (!adjacentTris || adjacentTris.length <= 1) continue;
46436
+ let sx = 0, sy = 0, sz = 0;
46437
+ for (const adj of adjacentTris) {
46438
+ const dot2 = faceNx[t] * faceNx[adj] + faceNy[t] * faceNy[adj] + faceNz[t] * faceNz[adj];
46439
+ if (dot2 >= SMOOTH_THRESHOLD_DOT) {
46440
+ sx += faceNx[adj];
46441
+ sy += faceNy[adj];
46442
+ sz += faceNz[adj];
46443
+ }
46444
+ }
46445
+ const slen = Math.sqrt(sx * sx + sy * sy + sz * sz);
46446
+ if (slen > 1e-9) {
46447
+ sx /= slen;
46448
+ sy /= slen;
46449
+ sz /= slen;
46450
+ const o = t * 9 + v * 3;
46451
+ normals[o] = sx;
46452
+ normals[o + 1] = sy;
46453
+ normals[o + 2] = sz;
46454
+ }
46455
+ }
46456
+ }
46457
+ }
46458
+ function computeSharpEdges(triVerts, vertProperties, numProp, triCount, faceNx, faceNy, faceNz, mergeFromVert, mergeToVert) {
46459
+ const numVerts = vertProperties.length / numProp;
46460
+ const canon = buildCanonicalMap$2(numVerts, mergeFromVert, mergeToVert);
46461
+ const MAX_NUMERIC = 1 << 21;
46462
+ let maxCanon = 0;
46463
+ for (let i = 0; i < numVerts; i++) {
46464
+ if (canon[i] > maxCanon) maxCanon = canon[i];
46465
+ }
46466
+ const useNumeric = maxCanon < MAX_NUMERIC;
46467
+ const halfEdges = /* @__PURE__ */ new Map();
46468
+ const edgeList = [];
46469
+ for (let t = 0; t < triCount; t++) {
46470
+ const ca = canon[triVerts[t * 3]];
46471
+ const cb = canon[triVerts[t * 3 + 1]];
46472
+ const cc = canon[triVerts[t * 3 + 2]];
46473
+ const tv = [ca, cb, cc];
46474
+ for (let e = 0; e < 3; e++) {
46475
+ const va = tv[e], vb = tv[(e + 1) % 3];
46476
+ const fwdKey = useNumeric ? va * MAX_NUMERIC + vb : `${va},${vb}`;
46477
+ const revKey = useNumeric ? vb * MAX_NUMERIC + va : `${vb},${va}`;
46478
+ const adjTri = halfEdges.get(revKey);
46479
+ if (adjTri !== void 0) {
46480
+ const dot2 = faceNx[t] * faceNx[adjTri] + faceNy[t] * faceNy[adjTri] + faceNz[t] * faceNz[adjTri];
46481
+ if (dot2 <= EDGE_THRESHOLD_DOT) {
46482
+ const origVa = triVerts[t * 3 + e];
46483
+ const origVb = triVerts[t * 3 + (e + 1) % 3];
46484
+ edgeList.push(
46485
+ vertProperties[origVa * numProp],
46486
+ vertProperties[origVa * numProp + 1],
46487
+ vertProperties[origVa * numProp + 2],
46488
+ vertProperties[origVb * numProp],
46489
+ vertProperties[origVb * numProp + 1],
46490
+ vertProperties[origVb * numProp + 2]
46491
+ );
46492
+ }
46493
+ halfEdges.delete(revKey);
46494
+ } else {
46495
+ halfEdges.set(fwdKey, t);
46496
+ }
46497
+ }
46498
+ }
46499
+ for (const [key2, t] of halfEdges) {
46500
+ const ca = canon[triVerts[t * 3]];
46501
+ const cb = canon[triVerts[t * 3 + 1]];
46502
+ const cc = canon[triVerts[t * 3 + 2]];
46503
+ const tv = [ca, cb, cc];
46504
+ for (let e = 0; e < 3; e++) {
46505
+ const va = tv[e], vb = tv[(e + 1) % 3];
46506
+ const fwdKey = useNumeric ? va * MAX_NUMERIC + vb : `${va},${vb}`;
46507
+ if (fwdKey === key2) {
46508
+ const origVa = triVerts[t * 3 + e];
46509
+ const origVb = triVerts[t * 3 + (e + 1) % 3];
46510
+ edgeList.push(
46511
+ vertProperties[origVa * numProp],
46512
+ vertProperties[origVa * numProp + 1],
46513
+ vertProperties[origVa * numProp + 2],
46514
+ vertProperties[origVb * numProp],
46515
+ vertProperties[origVb * numProp + 1],
46516
+ vertProperties[origVb * numProp + 2]
46517
+ );
46518
+ break;
46519
+ }
46520
+ }
46521
+ }
46522
+ return new Float32Array(edgeList);
46523
+ }
46524
+ function buildCanonicalMap$2(numVerts, mergeFromVert, mergeToVert) {
46525
+ const canon = new Uint32Array(numVerts);
46526
+ for (let i = 0; i < numVerts; i++) canon[i] = i;
46527
+ if (mergeFromVert && mergeToVert) {
46528
+ for (let i = 0; i < mergeFromVert.length; i++) {
46529
+ canon[mergeFromVert[i]] = mergeToVert[i];
46530
+ }
46531
+ for (let i = 0; i < numVerts; i++) {
46532
+ let v = canon[i];
46533
+ while (canon[v] !== v) v = canon[v];
46534
+ canon[i] = v;
46535
+ }
46536
+ }
46537
+ return canon;
46538
+ }
46539
+ const sub = (a2, b) => [a2[0] - b[0], a2[1] - b[1], a2[2] - b[2]];
46540
+ const add = (a2, b) => [a2[0] + b[0], a2[1] + b[1], a2[2] + b[2]];
46541
+ const dot = (a2, b) => a2[0] * b[0] + a2[1] * b[1] + a2[2] * b[2];
46542
+ const len = (a2) => Math.hypot(a2[0], a2[1], a2[2]);
46543
+ const scl = (a2, s) => [a2[0] * s, a2[1] * s, a2[2] * s];
46544
+ const cross = (a2, b) => [
46545
+ a2[1] * b[2] - a2[2] * b[1],
46546
+ a2[2] * b[0] - a2[0] * b[2],
46547
+ a2[0] * b[1] - a2[1] * b[0]
46548
+ ];
46549
+ const DEGENERATE = 1e-12;
46550
+ function normalize$1(a2) {
46551
+ const L = len(a2);
46552
+ if (L < DEGENERATE) throw new Error("vec3exact.normalize: degenerate (zero-length) vector");
46553
+ return [a2[0] / L, a2[1] / L, a2[2] / L];
46554
+ }
46555
+ function carrierSignedDistance(surface, p2, planePoint) {
46556
+ switch (surface.kind) {
46557
+ case "plane": {
46558
+ if (!planePoint) throw new Error("plane carrier requires planePoint (FaceRef.center)");
46559
+ const n = normalize$1(surface.normal);
46560
+ return dot(n, sub(p2, planePoint));
46561
+ }
46562
+ case "cylinder": {
46563
+ const ax = normalize$1(surface.axis);
46564
+ const v = sub(p2, surface.origin);
46565
+ const radial = sub(v, scl(ax, dot(v, ax)));
46566
+ return len(radial) - surface.radius;
46567
+ }
46568
+ case "sphere": {
46569
+ return len(sub(p2, surface.center)) - surface.radius;
46570
+ }
46571
+ case "cone": {
46572
+ const ax = normalize$1(surface.axis);
46573
+ const v = sub(p2, surface.origin);
46574
+ const t = dot(v, ax);
46575
+ const rad = len(sub(v, scl(ax, t)));
46576
+ const surfaceR = surface.radiusBottom + (surface.radiusTop - surface.radiusBottom) * (t / surface.height);
46577
+ const slope = (surface.radiusTop - surface.radiusBottom) / surface.height;
46578
+ const cosHalf = 1 / Math.hypot(1, slope);
46579
+ return (rad - surfaceR) * cosHalf;
46580
+ }
46581
+ case "torus": {
46582
+ const ax = normalize$1(surface.axis);
46583
+ const v = sub(p2, surface.center);
46584
+ const z2 = dot(v, ax);
46585
+ const rho = len(sub(v, scl(ax, z2)));
46586
+ const dr = rho - surface.majorRadius;
46587
+ return Math.hypot(dr, z2) - surface.minorRadius;
46588
+ }
46589
+ case "ruled":
46590
+ case "nurbs":
46591
+ return null;
46592
+ }
46593
+ }
46594
+ function carrierNormalAt(surface, p2, planePoint) {
46595
+ switch (surface.kind) {
46596
+ case "plane":
46597
+ return normalize$1(surface.normal);
46598
+ case "cylinder": {
46599
+ const ax = normalize$1(surface.axis);
46600
+ const v = sub(p2, surface.origin);
46601
+ const radial = sub(v, scl(ax, dot(v, ax)));
46602
+ const r = len(radial);
46603
+ return r < DEGENERATE ? ax : scl(radial, 1 / r);
46604
+ }
46605
+ case "sphere": {
46606
+ const v = sub(p2, surface.center);
46607
+ const r = len(v);
46608
+ return r < DEGENERATE ? [1, 0, 0] : scl(v, 1 / r);
46609
+ }
46610
+ case "cone": {
46611
+ const ax = normalize$1(surface.axis);
46612
+ const v = sub(p2, surface.origin);
46613
+ const t = dot(v, ax);
46614
+ const radialVec = sub(v, scl(ax, t));
46615
+ const rad = len(radialVec);
46616
+ const slope = (surface.radiusTop - surface.radiusBottom) / surface.height;
46617
+ const h = Math.hypot(1, slope);
46618
+ const cosHalf = 1 / h;
46619
+ const sinHalf = slope / h;
46620
+ const rHat = rad < DEGENERATE ? ax : scl(radialVec, 1 / rad);
46621
+ return normalize$1([
46622
+ rHat[0] * cosHalf - ax[0] * sinHalf,
46623
+ rHat[1] * cosHalf - ax[1] * sinHalf,
46624
+ rHat[2] * cosHalf - ax[2] * sinHalf
46625
+ ]);
46626
+ }
46627
+ case "torus": {
46628
+ const ax = normalize$1(surface.axis);
46629
+ const v = sub(p2, surface.center);
46630
+ const z2 = dot(v, ax);
46631
+ const radialVec = sub(v, scl(ax, z2));
46632
+ const rho = len(radialVec);
46633
+ const rHat = rho < DEGENERATE ? [1, 0, 0] : scl(radialVec, 1 / rho);
46634
+ const dr = rho - surface.majorRadius;
46635
+ const tubeDist = Math.hypot(dr, z2);
46636
+ if (tubeDist < DEGENERATE) return rHat;
46637
+ return normalize$1([
46638
+ rHat[0] * dr + ax[0] * z2,
46639
+ rHat[1] * dr + ax[1] * z2,
46640
+ rHat[2] * dr + ax[2] * z2
46641
+ ]);
46642
+ }
46643
+ case "ruled":
46644
+ case "nurbs":
46645
+ return null;
46646
+ }
46647
+ }
46648
+ function minCurvatureRadius(surface, p2) {
46649
+ switch (surface.kind) {
46650
+ case "plane":
46651
+ return Number.POSITIVE_INFINITY;
46652
+ case "sphere":
46653
+ return surface.radius;
46654
+ case "cylinder":
46655
+ return surface.radius;
46656
+ case "cone": {
46657
+ const ax = normalize$1(surface.axis);
46658
+ const v = sub(p2, surface.origin);
46659
+ const t = dot(v, ax);
46660
+ const surfaceR = surface.radiusBottom + (surface.radiusTop - surface.radiusBottom) * (t / surface.height);
46661
+ return Math.max(Math.abs(surfaceR), DEGENERATE);
46662
+ }
46663
+ case "torus":
46664
+ return surface.minorRadius;
46665
+ default:
46666
+ return Number.POSITIVE_INFINITY;
46667
+ }
46668
+ }
46669
+ function triCentroid(mesh, t) {
46670
+ const { triVerts, vertProperties, numProp } = mesh;
46671
+ const i0 = triVerts[t * 3] * numProp;
46672
+ const i1 = triVerts[t * 3 + 1] * numProp;
46673
+ const i2 = triVerts[t * 3 + 2] * numProp;
46674
+ return [
46675
+ (vertProperties[i0] + vertProperties[i1] + vertProperties[i2]) / 3,
46676
+ (vertProperties[i0 + 1] + vertProperties[i1 + 1] + vertProperties[i2 + 1]) / 3,
46677
+ (vertProperties[i0 + 2] + vertProperties[i1 + 2] + vertProperties[i2 + 2]) / 3
46678
+ ];
46679
+ }
46680
+ function triNormal(mesh, t) {
46681
+ const { triVerts, vertProperties, numProp } = mesh;
46682
+ const i0 = triVerts[t * 3] * numProp;
46683
+ const i1 = triVerts[t * 3 + 1] * numProp;
46684
+ const i2 = triVerts[t * 3 + 2] * numProp;
46685
+ const v0 = [vertProperties[i0], vertProperties[i0 + 1], vertProperties[i0 + 2]];
46686
+ const v1 = [vertProperties[i1], vertProperties[i1 + 1], vertProperties[i1 + 2]];
46687
+ const v2 = [vertProperties[i2], vertProperties[i2 + 1], vertProperties[i2 + 2]];
46688
+ return cross(sub(v1, v0), sub(v2, v0));
46689
+ }
46690
+ function triMaxEdge(mesh, t) {
46691
+ const { triVerts, vertProperties, numProp } = mesh;
46692
+ const i0 = triVerts[t * 3] * numProp;
46693
+ const i1 = triVerts[t * 3 + 1] * numProp;
46694
+ const i2 = triVerts[t * 3 + 2] * numProp;
46695
+ const v0 = [vertProperties[i0], vertProperties[i0 + 1], vertProperties[i0 + 2]];
46696
+ const v1 = [vertProperties[i1], vertProperties[i1 + 1], vertProperties[i1 + 2]];
46697
+ const v2 = [vertProperties[i2], vertProperties[i2 + 1], vertProperties[i2 + 2]];
46698
+ return Math.max(len(sub(v1, v0)), len(sub(v2, v1)), len(sub(v0, v2)));
46699
+ }
46700
+ function chordSagitta(R, h) {
46701
+ if (!Number.isFinite(R)) return 0;
46702
+ const c2 = h / 2;
46703
+ if (c2 >= R) return R;
46704
+ return R - Math.sqrt(R * R - c2 * c2);
46705
+ }
46706
+ function triVertices(mesh, t) {
46707
+ const { triVerts, vertProperties, numProp } = mesh;
46708
+ const i0 = triVerts[t * 3] * numProp;
46709
+ const i1 = triVerts[t * 3 + 1] * numProp;
46710
+ const i2 = triVerts[t * 3 + 2] * numProp;
46711
+ return [
46712
+ [vertProperties[i0], vertProperties[i0 + 1], vertProperties[i0 + 2]],
46713
+ [vertProperties[i1], vertProperties[i1 + 1], vertProperties[i1 + 2]],
46714
+ [vertProperties[i2], vertProperties[i2 + 1], vertProperties[i2 + 2]]
46715
+ ];
46716
+ }
46717
+ function triTolerance(surface, p2, h, safety) {
46718
+ const R = minCurvatureRadius(surface, p2);
46719
+ const sagitta = chordSagitta(R, h);
46720
+ const floor = h * 1e-3;
46721
+ return safety * Math.max(sagitta, floor);
46722
+ }
46723
+ function triAngularCosMin(surface, p2, h, safety) {
46724
+ const R = minCurvatureRadius(surface, p2);
46725
+ const theta = Number.isFinite(R) ? Math.asin(Math.min(1, h / (2 * R))) : safety * 0.01;
46726
+ return Math.cos(Math.min(safety * theta, Math.PI / 2));
46727
+ }
46728
+ function classifyMeshFaces(mesh, carriers, opts = {}) {
46729
+ const safety = opts.safety ?? 2;
46730
+ const numTri = mesh.numTri;
46731
+ for (const c2 of carriers) {
46732
+ if (c2.surface.kind === "plane" && !c2.planePoint) {
46733
+ throw new Error(`carrier '${c2.name}' is a plane but has no planePoint (FaceRef.center)`);
46734
+ }
46735
+ }
46736
+ const assigned = new Array(numTri).fill(null);
46737
+ const deviation = new Array(numTri).fill(Number.POSITIVE_INFINITY);
46738
+ const unitNormal = new Array(numTri).fill(null);
46739
+ for (let t = 0; t < numTri; t++) {
46740
+ const p2 = triCentroid(mesh, t);
46741
+ const nRaw = triNormal(mesh, t);
46742
+ const nLen = len(nRaw);
46743
+ if (nLen < DEGENERATE) continue;
46744
+ const nTri = scl(nRaw, 1 / nLen);
46745
+ unitNormal[t] = nTri;
46746
+ const h = triMaxEdge(mesh, t);
46747
+ let bestName = null;
46748
+ let bestAbsD = Number.POSITIVE_INFINITY;
46749
+ const verts = triVertices(mesh, t);
46750
+ for (const c2 of carriers) {
46751
+ const d2 = carrierSignedDistance(c2.surface, p2, c2.planePoint);
46752
+ if (d2 === null) continue;
46753
+ const absD = Math.abs(d2);
46754
+ const tol = triTolerance(c2.surface, p2, h, safety);
46755
+ if (absD > tol) continue;
46756
+ let vertsOnCarrier = true;
46757
+ for (const vtx of verts) {
46758
+ const dv = carrierSignedDistance(c2.surface, vtx, c2.planePoint);
46759
+ if (dv === null || Math.abs(dv) > tol) {
46760
+ vertsOnCarrier = false;
46761
+ break;
46762
+ }
46763
+ }
46764
+ if (!vertsOnCarrier) continue;
46765
+ const nCarrier = carrierNormalAt(c2.surface, p2, c2.planePoint);
46766
+ if (!nCarrier) continue;
46767
+ const align = Math.abs(dot(nTri, nCarrier));
46768
+ if (align < triAngularCosMin(c2.surface, p2, h, safety)) continue;
46769
+ if (absD < bestAbsD) {
46770
+ bestAbsD = absD;
46771
+ bestName = c2.name;
46772
+ }
46773
+ }
46774
+ assigned[t] = bestName;
46775
+ if (bestName !== null) deviation[t] = bestAbsD;
46776
+ }
46777
+ const parent = new Int32Array(numTri);
46778
+ for (let i = 0; i < numTri; i++) parent[i] = i;
46779
+ const find = (x2) => {
46780
+ let r = x2;
46781
+ while (parent[r] !== r) r = parent[r];
46782
+ while (parent[x2] !== r) {
46783
+ const nx = parent[x2];
46784
+ parent[x2] = r;
46785
+ x2 = nx;
46786
+ }
46787
+ return r;
46788
+ };
46789
+ const unite = (a2, b) => {
46790
+ const ra = find(a2);
46791
+ const rb = find(b);
46792
+ if (ra !== rb) parent[ra] = rb;
46793
+ };
46794
+ const edgeMap = /* @__PURE__ */ new Map();
46795
+ const edgeKey2 = (a2, b) => a2 < b ? `${a2}_${b}` : `${b}_${a2}`;
46796
+ for (let t = 0; t < numTri; t++) {
46797
+ const a2 = mesh.triVerts[t * 3];
46798
+ const b = mesh.triVerts[t * 3 + 1];
46799
+ const c2 = mesh.triVerts[t * 3 + 2];
46800
+ for (const [u2, v] of [
46801
+ [a2, b],
46802
+ [b, c2],
46803
+ [c2, a2]
46804
+ ]) {
46805
+ const k2 = edgeKey2(u2, v);
46806
+ const other = edgeMap.get(k2);
46807
+ if (other === void 0) {
46808
+ edgeMap.set(k2, t);
46809
+ } else if (assigned[t] === assigned[other]) {
46810
+ if (assigned[t] !== null) {
46811
+ unite(t, other);
46812
+ } else {
46813
+ const na = unitNormal[t];
46814
+ const nb = unitNormal[other];
46815
+ if (na && nb && dot(na, nb) >= SMOOTH_THRESHOLD_DOT) unite(t, other);
46816
+ }
46817
+ }
46818
+ }
46819
+ }
46820
+ const groups = /* @__PURE__ */ new Map();
46821
+ for (let t = 0; t < numTri; t++) {
46822
+ const r = find(t);
46823
+ let g2 = groups.get(r);
46824
+ if (!g2) {
46825
+ g2 = [];
46826
+ groups.set(r, g2);
46827
+ }
46828
+ g2.push(t);
46829
+ }
46830
+ const faces = [];
46831
+ let unclassified = 0;
46832
+ for (const tris of groups.values()) {
46833
+ const name = assigned[tris[0]];
46834
+ if (name === null) {
46835
+ unclassified += tris.length;
46836
+ }
46837
+ const carrier = carriers.find((c2) => c2.name === name);
46838
+ let maxDev = 0;
46839
+ let maxTol = 0;
46840
+ for (const t of tris) {
46841
+ if (deviation[t] !== Number.POSITIVE_INFINITY) maxDev = Math.max(maxDev, deviation[t]);
46842
+ if (carrier) {
46843
+ const p2 = triCentroid(mesh, t);
46844
+ const h = triMaxEdge(mesh, t);
46845
+ maxTol = Math.max(maxTol, triTolerance(carrier.surface, p2, h, safety));
46846
+ }
46847
+ }
46848
+ faces.push({
46849
+ carrier: name,
46850
+ kind: carrier ? carrier.surface.kind : "unidentified",
46851
+ triangleIndices: tris,
46852
+ connected: true,
46853
+ maxMemberDeviation: maxDev,
46854
+ tol: maxTol
46855
+ });
46856
+ }
46857
+ return { faces, unclassifiedTriangleCount: unclassified };
46858
+ }
46859
+ const UNIDENTIFIED_FACE_NAME = "unidentified";
46860
+ function synthesizePerTriangleFaceIds(result, numTri) {
46861
+ const faceID = new Int32Array(numTri).fill(-1);
46862
+ const faceIdNames = [];
46863
+ for (const face of result.faces) {
46864
+ const id = faceIdNames.length;
46865
+ faceIdNames.push(face.carrier ?? UNIDENTIFIED_FACE_NAME);
46866
+ for (const t of face.triangleIndices) faceID[t] = id;
46867
+ }
46868
+ return { faceID, faceIdNames };
46869
+ }
46870
+ function buildCarrierFaceRefs(mesh, carriers) {
46871
+ const out = /* @__PURE__ */ new Map();
46872
+ const ids = mesh.faceID;
46873
+ const names = mesh.faceIdNames;
46874
+ if (!ids || !names || ids.length === 0 || names.length === 0) return out;
46875
+ const surfaceByName = /* @__PURE__ */ new Map();
46876
+ for (const c2 of carriers) surfaceByName.set(c2.name, c2.surface);
46877
+ const { triVerts, vertProperties, numProp, numTri } = mesh;
46878
+ const acc = /* @__PURE__ */ new Map();
46879
+ for (let t = 0; t < numTri; t++) {
46880
+ const id = ids[t];
46881
+ if (id < 0) continue;
46882
+ const name = names[id];
46883
+ if (!name || name === UNIDENTIFIED_FACE_NAME) continue;
46884
+ if (!surfaceByName.has(name)) continue;
46885
+ const i0 = triVerts[t * 3] * numProp;
46886
+ const i1 = triVerts[t * 3 + 1] * numProp;
46887
+ const i2 = triVerts[t * 3 + 2] * numProp;
46888
+ const ax = vertProperties[i0], ay = vertProperties[i0 + 1], az = vertProperties[i0 + 2];
46889
+ const bx = vertProperties[i1], by = vertProperties[i1 + 1], bz = vertProperties[i1 + 2];
46890
+ const cx = vertProperties[i2], cy = vertProperties[i2 + 1], cz = vertProperties[i2 + 2];
46891
+ const nx = (by - ay) * (cz - az) - (bz - az) * (cy - ay);
46892
+ const ny = (bz - az) * (cx - ax) - (bx - ax) * (cz - az);
46893
+ const nz = (bx - ax) * (cy - ay) - (by - ay) * (cx - ax);
46894
+ const twoArea = Math.hypot(nx, ny, nz);
46895
+ const area2 = twoArea * 0.5;
46896
+ let a2 = acc.get(name);
46897
+ if (!a2) {
46898
+ a2 = { area: 0, cx: 0, cy: 0, cz: 0, nx: 0, ny: 0, nz: 0, facetNormal: null };
46899
+ acc.set(name, a2);
46900
+ }
46901
+ a2.area += area2;
46902
+ a2.cx += (ax + bx + cx) / 3 * area2;
46903
+ a2.cy += (ay + by + cy) / 3 * area2;
46904
+ a2.cz += (az + bz + cz) / 3 * area2;
46905
+ a2.nx += nx * 0.5;
46906
+ a2.ny += ny * 0.5;
46907
+ a2.nz += nz * 0.5;
46908
+ if (!a2.facetNormal && twoArea > 1e-12) a2.facetNormal = [nx / twoArea, ny / twoArea, nz / twoArea];
46909
+ }
46910
+ for (const [name, a2] of acc) {
46911
+ if (a2.area <= 0) continue;
46912
+ const center2 = [a2.cx / a2.area, a2.cy / a2.area, a2.cz / a2.area];
46913
+ const nlen = Math.hypot(a2.nx, a2.ny, a2.nz);
46914
+ const normal2 = nlen > 1e-9 ? [a2.nx / nlen, a2.ny / nlen, a2.nz / nlen] : a2.facetNormal ?? [0, 0, 1];
46915
+ const surface = surfaceByName.get(name);
46916
+ out.set(name, { name, center: center2, normal: normal2, surface, planar: surface ? surface.kind === "plane" : void 0 });
46917
+ }
46918
+ return out;
46919
+ }
45749
46920
  const PLACEMENT_REFERENCE_KINDS = ["points", "edges", "surfaces", "objects"];
45750
46921
  function cloneVec3(value, label) {
45751
46922
  if (!Array.isArray(value) || value.length < 3) {
@@ -45763,9 +46934,9 @@ function midpoint(start, end) {
45763
46934
  return [(start[0] + end[0]) / 2, (start[1] + end[1]) / 2, (start[2] + end[2]) / 2];
45764
46935
  }
45765
46936
  function normalizeVector(value) {
45766
- const len = Math.hypot(value[0], value[1], value[2]);
45767
- if (len < 1e-10) return [0, 0, 1];
45768
- return [value[0] / len, value[1] / len, value[2] / len];
46937
+ const len2 = Math.hypot(value[0], value[1], value[2]);
46938
+ if (len2 < 1e-10) return [0, 0, 1];
46939
+ return [value[0] / len2, value[1] / len2, value[2] / len2];
45769
46940
  }
45770
46941
  function isBoundsObject(value) {
45771
46942
  return !!value && typeof value === "object" && "min" in value && "max" in value;
@@ -46578,9 +47749,9 @@ function requireVec3Pivot(v, method) {
46578
47749
  }
46579
47750
  function mirrorPlaneMatrix(normal2) {
46580
47751
  const [nx0, ny0, nz0] = normal2;
46581
- const len = Math.hypot(nx0, ny0, nz0);
46582
- if (len < 1e-12) return Transform.identity().toArray();
46583
- const nx = nx0 / len, ny = ny0 / len, nz = nz0 / len;
47752
+ const len2 = Math.hypot(nx0, ny0, nz0);
47753
+ if (len2 < 1e-12) return Transform.identity().toArray();
47754
+ const nx = nx0 / len2, ny = ny0 / len2, nz = nz0 / len2;
46584
47755
  const m00 = 1 - 2 * nx * nx, m01 = -2 * nx * ny, m02 = -2 * nx * nz;
46585
47756
  const m10 = -2 * ny * nx, m11 = 1 - 2 * ny * ny, m12 = -2 * ny * nz;
46586
47757
  const m20 = -2 * nz * nx, m21 = -2 * nz * ny, m22 = 1 - 2 * nz * nz;
@@ -46825,8 +47996,8 @@ class ShapeGroup {
46825
47996
  /** Reorient the group so its local Z axis points along `direction`. */
46826
47997
  pointAlong(direction) {
46827
47998
  const [dx, dy, dz] = requireNonZeroFiniteVec3(direction, "ShapeGroup.pointAlong() direction");
46828
- const len = Math.sqrt(dx * dx + dy * dy + dz * dz) || 1;
46829
- const nx = dx / len, ny = dy / len, nz = dz / len;
47999
+ const len2 = Math.sqrt(dx * dx + dy * dy + dz * dz) || 1;
48000
+ const nx = dx / len2, ny = dy / len2, nz = dz / len2;
46830
48001
  const cx = -ny, cy = nx, cz = 0;
46831
48002
  const sinA = Math.sqrt(cx * cx + cy * cy + cz * cz);
46832
48003
  const cosA = nz;
@@ -47037,9 +48208,9 @@ class ShapeGroup {
47037
48208
  if (firstPair) {
47038
48209
  const selfAxis = firstPair.selfPort.axis;
47039
48210
  const transformed = tx.vector(selfAxis);
47040
- const len = Math.hypot(transformed[0], transformed[1], transformed[2]);
47041
- if (len > 1e-10) {
47042
- _groupExplodeHint.set(result, [transformed[0] / len, transformed[1] / len, transformed[2] / len]);
48211
+ const len2 = Math.hypot(transformed[0], transformed[1], transformed[2]);
48212
+ if (len2 > 1e-10) {
48213
+ _groupExplodeHint.set(result, [transformed[0] / len2, transformed[1] / len2, transformed[2] / len2]);
47043
48214
  }
47044
48215
  }
47045
48216
  return result;
@@ -47296,12 +48467,15 @@ function rootTopologyRewritePropagation(plan) {
47296
48467
  case "variableSweep":
47297
48468
  case "filletEdges":
47298
48469
  case "cornerYBlend":
48470
+ case "faceFillet":
48471
+ case "fullRound":
47299
48472
  case "chamferEdges":
47300
48473
  case "draft":
47301
48474
  case "offsetSolid":
47302
48475
  case "importedMesh":
47303
48476
  case "sdf":
47304
48477
  case "fromSlices":
48478
+ case "fromSectionFrames":
47305
48479
  case "analyticSurface":
47306
48480
  case "nurbsSurface":
47307
48481
  case "surfaceRuled":
@@ -47729,12 +48903,15 @@ function rootPlanPropagation(plan) {
47729
48903
  case "variableSweep":
47730
48904
  case "filletEdges":
47731
48905
  case "cornerYBlend":
48906
+ case "faceFillet":
48907
+ case "fullRound":
47732
48908
  case "chamferEdges":
47733
48909
  case "draft":
47734
48910
  case "offsetSolid":
47735
48911
  case "importedMesh":
47736
48912
  case "sdf":
47737
48913
  case "fromSlices":
48914
+ case "fromSectionFrames":
47738
48915
  case "analyticSurface":
47739
48916
  case "nurbsSurface":
47740
48917
  case "surfaceRuled":
@@ -48069,11 +49246,11 @@ function clusterTriangles(mesh) {
48069
49246
  let nx = e1y * e2z - e1z * e2y;
48070
49247
  let ny = e1z * e2x - e1x * e2z;
48071
49248
  let nz = e1x * e2y - e1y * e2x;
48072
- const len = Math.sqrt(nx * nx + ny * ny + nz * nz);
48073
- if (len < 1e-10) continue;
48074
- nx /= len;
48075
- ny /= len;
48076
- nz /= len;
49249
+ const len2 = Math.sqrt(nx * nx + ny * ny + nz * nz);
49250
+ if (len2 < 1e-10) continue;
49251
+ nx /= len2;
49252
+ ny /= len2;
49253
+ nz /= len2;
48077
49254
  const normal2 = [nx, ny, nz];
48078
49255
  const planeOffset = nx * v0x + ny * v0y + nz * v0z;
48079
49256
  let matched = -1;
@@ -48161,10 +49338,10 @@ function extractEdgesWithFaces(mesh, triCluster) {
48161
49338
  const nx = e1y * e2z - e1z * e2y;
48162
49339
  const ny = e1z * e2x - e1x * e2z;
48163
49340
  const nz = e1x * e2y - e1y * e2x;
48164
- const len = Math.sqrt(nx * nx + ny * ny + nz * nz) || 1;
48165
- faceNx[t] = nx / len;
48166
- faceNy[t] = ny / len;
48167
- faceNz[t] = nz / len;
49341
+ const len2 = Math.sqrt(nx * nx + ny * ny + nz * nz) || 1;
49342
+ faceNx[t] = nx / len2;
49343
+ faceNy[t] = ny / len2;
49344
+ faceNz[t] = nz / len2;
48168
49345
  }
48169
49346
  const numVerts = vertProperties.length / numProp;
48170
49347
  const canon = buildCanonicalMap$1(numVerts, mesh.mergeFromVert, mesh.mergeToVert);
@@ -48424,9 +49601,9 @@ function extractFaceVertices(mesh, faceNormal, faceCenter, tolerance = 0.5) {
48424
49601
  return vertices;
48425
49602
  }
48426
49603
  function normalize(v) {
48427
- const len = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
48428
- if (len < 1e-12) throw new Error("Cannot normalize zero-length vector.");
48429
- return [v[0] / len, v[1] / len, v[2] / len];
49604
+ const len2 = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
49605
+ if (len2 < 1e-12) throw new Error("Cannot normalize zero-length vector.");
49606
+ return [v[0] / len2, v[1] / len2, v[2] / len2];
48430
49607
  }
48431
49608
  function computeSeatTranslation(faceVertices, faceNormal, targetMesh, options = {}) {
48432
49609
  const depth = options.depth ?? "full";
@@ -48713,10 +49890,10 @@ function rotationAroundAxisMatrix(axis, angleDeg, pivot) {
48713
49890
  const rad = angleDeg * Math.PI / 180;
48714
49891
  const cos2 = Math.cos(rad);
48715
49892
  const sin2 = Math.sin(rad);
48716
- const len = Math.sqrt(axis[0] ** 2 + axis[1] ** 2 + axis[2] ** 2) || 1;
48717
- const ux = axis[0] / len;
48718
- const uy = axis[1] / len;
48719
- const uz = axis[2] / len;
49893
+ const len2 = Math.sqrt(axis[0] ** 2 + axis[1] ** 2 + axis[2] ** 2) || 1;
49894
+ const ux = axis[0] / len2;
49895
+ const uy = axis[1] / len2;
49896
+ const uz = axis[2] / len2;
48720
49897
  const m00 = cos2 + ux * ux * (1 - cos2);
48721
49898
  const m01 = ux * uy * (1 - cos2) - uz * sin2;
48722
49899
  const m02 = ux * uz * (1 - cos2) + uy * sin2;
@@ -48733,11 +49910,11 @@ function rotationAroundAxisMatrix(axis, angleDeg, pivot) {
48733
49910
  }
48734
49911
  function mirrorMatrix(normal2) {
48735
49912
  const [nx0, ny0, nz0] = normal2;
48736
- const len = Math.hypot(nx0, ny0, nz0);
48737
- if (len < 1e-12) return Transform.identity().toArray();
48738
- const nx = nx0 / len;
48739
- const ny = ny0 / len;
48740
- const nz = nz0 / len;
49913
+ const len2 = Math.hypot(nx0, ny0, nz0);
49914
+ if (len2 < 1e-12) return Transform.identity().toArray();
49915
+ const nx = nx0 / len2;
49916
+ const ny = ny0 / len2;
49917
+ const nz = nz0 / len2;
48741
49918
  const m00 = 1 - 2 * nx * nx;
48742
49919
  const m01 = -2 * nx * ny;
48743
49920
  const m02 = -2 * nx * nz;
@@ -48821,6 +49998,7 @@ const _shapeTopology = /* @__PURE__ */ new WeakMap();
48821
49998
  const _shapeLineageTokens = /* @__PURE__ */ new WeakMap();
48822
49999
  const _shapeFaceLabels = /* @__PURE__ */ new WeakMap();
48823
50000
  const _shapeReferenceNames = /* @__PURE__ */ new WeakMap();
50001
+ const _shapeCarrierFaceRefs = /* @__PURE__ */ new WeakMap();
48824
50002
  const _shapeReferenceAliases = /* @__PURE__ */ new WeakMap();
48825
50003
  const _edgeRefOwners = /* @__PURE__ */ new WeakMap();
48826
50004
  const _faceRefOwners = /* @__PURE__ */ new WeakMap();
@@ -49034,6 +50212,32 @@ function getShapeRuntimeBackendInternal(shape) {
49034
50212
  if (!backend) throw new Error("Runtime backend missing on Shape");
49035
50213
  return backend;
49036
50214
  }
50215
+ function withCarrierFaceIds(mesh, shape) {
50216
+ if (mesh.faceID && mesh.faceID.length > 0 && mesh.faceIdNames && mesh.faceIdNames.length > 0) return mesh;
50217
+ if (mesh.numTri === 0) return mesh;
50218
+ const carriers = gatherShapeCarriers(getShapeCompilePlanInternal(shape));
50219
+ if (carriers.length === 0) return mesh;
50220
+ const result = classifyMeshFaces(mesh, carriers);
50221
+ const { faceID, faceIdNames } = synthesizePerTriangleFaceIds(result, mesh.numTri);
50222
+ if (faceIdNames.length === 0) return mesh;
50223
+ const writable = mesh;
50224
+ writable.faceID = faceID;
50225
+ writable.faceIdNames = faceIdNames;
50226
+ return mesh;
50227
+ }
50228
+ function getCarrierFaceRefs(shape) {
50229
+ const cached = _shapeCarrierFaceRefs.get(shape);
50230
+ if (cached) return cached;
50231
+ let refs;
50232
+ try {
50233
+ const mesh = shape.getMesh();
50234
+ refs = buildCarrierFaceRefs(mesh, gatherShapeCarriers(getShapeCompilePlanInternal(shape)));
50235
+ } catch {
50236
+ refs = /* @__PURE__ */ new Map();
50237
+ }
50238
+ _shapeCarrierFaceRefs.set(shape, refs);
50239
+ return refs;
50240
+ }
49037
50241
  function getShapeCompilePlanInternal(shape) {
49038
50242
  const stored = _shapeCompilePlans.get(shape);
49039
50243
  if (!stored) throw new Error("Shape has no compile plan — every Shape must have an explicit plan set via setShapeCompilePlanInternal()");
@@ -49791,9 +50995,9 @@ function withTransformedDimensions(source, out, m2) {
49791
50995
  if (sourceHint) {
49792
50996
  const tx = Transform.from(m2);
49793
50997
  const transformed = tx.vector(sourceHint);
49794
- const len = Math.hypot(transformed[0], transformed[1], transformed[2]);
49795
- if (len > 1e-10) {
49796
- setShapeExplodeHintInternal(out, [transformed[0] / len, transformed[1] / len, transformed[2] / len]);
50998
+ const len2 = Math.hypot(transformed[0], transformed[1], transformed[2]);
50999
+ if (len2 > 1e-10) {
51000
+ setShapeExplodeHintInternal(out, [transformed[0] / len2, transformed[1] / len2, transformed[2] / len2]);
49797
51001
  }
49798
51002
  }
49799
51003
  if (source.materialProps) out.materialProps = { ...source.materialProps };
@@ -49874,6 +51078,14 @@ function setShapePlacementReferences(shape, refs, options = {}) {
49874
51078
  function getShapeRuntimeBackend(shape) {
49875
51079
  return getShapeRuntimeBackendInternal(shape);
49876
51080
  }
51081
+ function getShapeCompilePlan(shape) {
51082
+ return getShapeCompilePlanInternal(shape);
51083
+ }
51084
+ function getShapeSourceSpans(shape) {
51085
+ const records = _shapeSourceSpans.get(shape);
51086
+ if (!records) return [];
51087
+ return [...records].map(([planCacheKey, sourceSpan]) => ({ planCacheKey, sourceSpan }));
51088
+ }
49877
51089
  function setShapeCompilePlan(shape, plan) {
49878
51090
  return setShapeCompilePlanInternal(shape, plan);
49879
51091
  }
@@ -50539,6 +51751,10 @@ class Shape {
50539
51751
  const detected = queryMeshFace(this, query);
50540
51752
  if (detected) return rememberFaceRefOwner(this, detected);
50541
51753
  }
51754
+ if (compilePlanName) {
51755
+ const carrierRef = getCarrierFaceRefs(this).get(compilePlanName);
51756
+ if (carrierRef) return rememberFaceRefOwner(this, { ...carrierRef }, compilePlanName);
51757
+ }
50542
51758
  if (compilePlanName) {
50543
51759
  const plan = getShapeCompilePlanInternal(this);
50544
51760
  throw new Error(explainMissingShapeFace(plan, compilePlanName));
@@ -50564,6 +51780,7 @@ class Shape {
50564
51780
  if (topo) {
50565
51781
  for (const topoName of topo.faces.keys()) all.add(topoName);
50566
51782
  }
51783
+ for (const carrierName of getCarrierFaceRefs(this).keys()) all.add(carrierName);
50567
51784
  if (!labels || labels.size === 0) return Array.from(all).sort();
50568
51785
  for (const userLabel of labels.keys()) all.add(userLabel);
50569
51786
  return Array.from(all).sort();
@@ -50793,7 +52010,8 @@ class Shape {
50793
52010
  if (!face) {
50794
52011
  throw new Error(explainMissingShapeFace(plan, name));
50795
52012
  }
50796
- return traceFaceTransformationHistory(plan, face);
52013
+ const sourceSpans = new Map(getShapeSourceSpans(this).map((record) => [record.planCacheKey, record.sourceSpan]));
52014
+ return traceFaceTransformationHistory(plan, face, sourceSpans);
50797
52015
  }
50798
52016
  /**
50799
52017
  * Translate the shape so the given anchor or reference lands on the target coordinate.
@@ -50995,8 +52213,8 @@ class Shape {
50995
52213
  */
50996
52214
  pointAlong(direction) {
50997
52215
  const [dx, dy, dz] = requireNonZeroFiniteVec3(direction, "Shape.pointAlong() direction");
50998
- const len = Math.sqrt(dx * dx + dy * dy + dz * dz) || 1;
50999
- const nx = dx / len, ny = dy / len, nz = dz / len;
52216
+ const len2 = Math.sqrt(dx * dx + dy * dy + dz * dz) || 1;
52217
+ const nx = dx / len2, ny = dy / len2, nz = dz / len2;
51000
52218
  const cx = -ny, cy = nx, cz = 0;
51001
52219
  const sinA = Math.sqrt(cx * cx + cy * cy + cz * cz);
51002
52220
  const cosA = nz;
@@ -51016,8 +52234,8 @@ class Shape {
51016
52234
  const rotateAxis = requireNonZeroFiniteVec3(axis, "Shape.rotateAroundAxis() axis");
51017
52235
  const degrees = requireFiniteNumber(angleDeg, "Shape.rotateAroundAxis() angleDeg");
51018
52236
  const rotatePivot = requireFiniteVec3$1(pivot, "Shape.rotateAroundAxis() pivot");
51019
- const len = Math.sqrt(rotateAxis[0] ** 2 + rotateAxis[1] ** 2 + rotateAxis[2] ** 2) || 1;
51020
- const normalizedAxis = [rotateAxis[0] / len, rotateAxis[1] / len, rotateAxis[2] / len];
52237
+ const len2 = Math.sqrt(rotateAxis[0] ** 2 + rotateAxis[1] ** 2 + rotateAxis[2] ** 2) || 1;
52238
+ const normalizedAxis = [rotateAxis[0] / len2, rotateAxis[1] / len2, rotateAxis[2] / len2];
51021
52239
  const matrix = rotationAroundAxisMatrix(normalizedAxis, degrees, rotatePivot);
51022
52240
  const nextPlan = appendShapeCompileTransform(getShapeCompilePlanInternal(this), {
51023
52241
  kind: "rotateAround",
@@ -51338,7 +52556,8 @@ class Shape {
51338
52556
  }
51339
52557
  /** Extract triangle mesh for Three.js rendering */
51340
52558
  getMesh() {
51341
- return getShapeRuntimeBackendInternal(this).getMesh();
52559
+ const mesh = getShapeRuntimeBackendInternal(this).getMesh();
52560
+ return withCarrierFaceIds(mesh, this);
51342
52561
  }
51343
52562
  /** Slice the runtime solid by a plane normal to local Z at the given offset. */
51344
52563
  slice(offset = 0) {
@@ -51558,9 +52777,9 @@ class Shape {
51558
52777
  if (firstPair) {
51559
52778
  const selfAxis = firstPair.selfPort.axis;
51560
52779
  const transformed = tx.vector(selfAxis);
51561
- const len = Math.hypot(transformed[0], transformed[1], transformed[2]);
51562
- if (len > 1e-10) {
51563
- setShapeExplodeHintInternal(result, [transformed[0] / len, transformed[1] / len, transformed[2] / len]);
52780
+ const len2 = Math.hypot(transformed[0], transformed[1], transformed[2]);
52781
+ if (len2 > 1e-10) {
52782
+ setShapeExplodeHintInternal(result, [transformed[0] / len2, transformed[1] / len2, transformed[2] / len2]);
51564
52783
  }
51565
52784
  }
51566
52785
  markPortsUsed(
@@ -51680,6 +52899,257 @@ function normalizeShapeOperands(apiName, inputs, minCount, usage) {
51680
52899
  coerce: (value) => unwrapShapeLike(value)
51681
52900
  });
51682
52901
  }
52902
+ const PARALLEL_DOT = Math.cos(0.5 * Math.PI / 180);
52903
+ function vertPos(mesh, vIdx) {
52904
+ const b = vIdx * mesh.numProp;
52905
+ return [mesh.vertProperties[b], mesh.vertProperties[b + 1], mesh.vertProperties[b + 2]];
52906
+ }
52907
+ const edgeKey = (a2, b) => a2 < b ? `${a2}_${b}` : `${b}_${a2}`;
52908
+ const pairKey = (a2, b) => JSON.stringify(a2 < b ? [a2, b] : [b, a2]);
52909
+ function buildCanonicalMap(mesh) {
52910
+ const numVerts = mesh.vertProperties.length / mesh.numProp;
52911
+ const canon = new Uint32Array(numVerts);
52912
+ for (let i = 0; i < numVerts; i++) canon[i] = i;
52913
+ const { mergeFromVert, mergeToVert } = mesh;
52914
+ if (mergeFromVert && mergeToVert) {
52915
+ for (let i = 0; i < mergeFromVert.length; i++) canon[mergeFromVert[i]] = mergeToVert[i];
52916
+ for (let i = 0; i < numVerts; i++) {
52917
+ let v = canon[i];
52918
+ while (canon[v] !== v) v = canon[v];
52919
+ canon[i] = v;
52920
+ }
52921
+ }
52922
+ return canon;
52923
+ }
52924
+ function buildEdgeClassification(mesh, carriers) {
52925
+ const ids = mesh.faceID;
52926
+ const names = mesh.faceIdNames;
52927
+ if (!ids || !names || ids.length === 0 || names.length === 0) return [];
52928
+ if (mesh.numTri === 0) return [];
52929
+ const surfaceByName = /* @__PURE__ */ new Map();
52930
+ const planePointByName = /* @__PURE__ */ new Map();
52931
+ for (const c2 of carriers) {
52932
+ surfaceByName.set(c2.name, c2.surface);
52933
+ if (c2.planePoint) planePointByName.set(c2.name, c2.planePoint);
52934
+ }
52935
+ const canon = buildCanonicalMap(mesh);
52936
+ const edgeOwner = /* @__PURE__ */ new Map();
52937
+ const byPair = /* @__PURE__ */ new Map();
52938
+ const triFaceName = (t) => {
52939
+ const id = ids[t];
52940
+ if (id < 0) return null;
52941
+ return names[id] ?? null;
52942
+ };
52943
+ for (let t = 0; t < mesh.numTri; t++) {
52944
+ const v0 = mesh.triVerts[t * 3];
52945
+ const v1 = mesh.triVerts[t * 3 + 1];
52946
+ const v2 = mesh.triVerts[t * 3 + 2];
52947
+ const ca = canon[v0];
52948
+ const cb = canon[v1];
52949
+ const cc = canon[v2];
52950
+ for (const [u2, v] of [
52951
+ [ca, cb],
52952
+ [cb, cc],
52953
+ [cc, ca]
52954
+ ]) {
52955
+ const k2 = edgeKey(u2, v);
52956
+ const other = edgeOwner.get(k2);
52957
+ if (other === void 0) {
52958
+ edgeOwner.set(k2, t);
52959
+ continue;
52960
+ }
52961
+ const nameThis = triFaceName(t);
52962
+ const nameOther = triFaceName(other);
52963
+ if (nameThis === null || nameOther === null) continue;
52964
+ if (nameThis === nameOther) continue;
52965
+ const pk = pairKey(nameThis, nameOther);
52966
+ let bucket = byPair.get(pk);
52967
+ if (!bucket) {
52968
+ const [fa, fb] = nameThis < nameOther ? [nameThis, nameOther] : [nameOther, nameThis];
52969
+ bucket = { faceA: fa, faceB: fb, edges: [] };
52970
+ byPair.set(pk, bucket);
52971
+ }
52972
+ bucket.edges.push({ a: u2, b: v });
52973
+ }
52974
+ }
52975
+ const result = [];
52976
+ for (const bucket of byPair.values()) {
52977
+ const chains = splitIntoChains(bucket.edges);
52978
+ for (const chainVerts of chains) {
52979
+ const chain = chainVerts.map((vi) => vertPos(mesh, vi));
52980
+ if (chain.length < 2) continue;
52981
+ const { curve, length: length4 } = deriveCurve(bucket.faceA, bucket.faceB, chain, surfaceByName, planePointByName);
52982
+ result.push({ faceA: bucket.faceA, faceB: bucket.faceB, curve, length: length4, chain });
52983
+ }
52984
+ }
52985
+ return result;
52986
+ }
52987
+ function splitIntoChains(edges) {
52988
+ const adj = /* @__PURE__ */ new Map();
52989
+ const addAdj = (x2, y2) => {
52990
+ let l = adj.get(x2);
52991
+ if (!l) {
52992
+ l = [];
52993
+ adj.set(x2, l);
52994
+ }
52995
+ l.push(y2);
52996
+ };
52997
+ const seenEdge = /* @__PURE__ */ new Set();
52998
+ for (const e of edges) {
52999
+ const k2 = edgeKey(e.a, e.b);
53000
+ if (seenEdge.has(k2)) continue;
53001
+ seenEdge.add(k2);
53002
+ addAdj(e.a, e.b);
53003
+ addAdj(e.b, e.a);
53004
+ }
53005
+ const visited = /* @__PURE__ */ new Set();
53006
+ const chains = [];
53007
+ for (const startSeed of adj.keys()) {
53008
+ if (visited.has(startSeed)) continue;
53009
+ const compVerts = [];
53010
+ const stack = [startSeed];
53011
+ visited.add(startSeed);
53012
+ while (stack.length) {
53013
+ const v = stack.pop();
53014
+ compVerts.push(v);
53015
+ for (const w2 of adj.get(v) ?? []) {
53016
+ if (!visited.has(w2)) {
53017
+ visited.add(w2);
53018
+ stack.push(w2);
53019
+ }
53020
+ }
53021
+ }
53022
+ chains.push(orderComponent(compVerts, adj));
53023
+ }
53024
+ return chains;
53025
+ }
53026
+ function orderComponent(compVerts, adj, seenEdge) {
53027
+ var _a3, _b3;
53028
+ if (compVerts.length <= 2) return compVerts;
53029
+ let start = compVerts[0];
53030
+ for (const v of compVerts) {
53031
+ if ((((_a3 = adj.get(v)) == null ? void 0 : _a3.length) ?? 0) === 1) {
53032
+ start = v;
53033
+ break;
53034
+ }
53035
+ }
53036
+ const ordered = [];
53037
+ const usedEdge = /* @__PURE__ */ new Set();
53038
+ let prev = -1;
53039
+ let cur = start;
53040
+ for (let guard = 0; guard <= compVerts.length; guard++) {
53041
+ ordered.push(cur);
53042
+ let next = -1;
53043
+ for (const w2 of adj.get(cur) ?? []) {
53044
+ const k2 = edgeKey(cur, w2);
53045
+ if (usedEdge.has(k2)) continue;
53046
+ if (w2 === prev && (((_b3 = adj.get(cur)) == null ? void 0 : _b3.length) ?? 0) > 1) continue;
53047
+ next = w2;
53048
+ usedEdge.add(k2);
53049
+ break;
53050
+ }
53051
+ if (next === -1) break;
53052
+ prev = cur;
53053
+ cur = next;
53054
+ if (cur === start) {
53055
+ ordered.push(cur);
53056
+ break;
53057
+ }
53058
+ }
53059
+ return ordered;
53060
+ }
53061
+ function polylineLength(chain) {
53062
+ let L = 0;
53063
+ for (let i = 1; i < chain.length; i++) L += len(sub(chain[i], chain[i - 1]));
53064
+ return L;
53065
+ }
53066
+ function deriveCurve(faceA, faceB, chain, surfaceByName, planePointByName) {
53067
+ const sA = surfaceByName.get(faceA);
53068
+ const sB = surfaceByName.get(faceB);
53069
+ if ((sA == null ? void 0 : sA.kind) === "plane" && (sB == null ? void 0 : sB.kind) === "plane") {
53070
+ const { start, end } = chainExtremes(chain);
53071
+ return {
53072
+ curve: { kind: "line", start, end, faceName: faceA },
53073
+ length: len(sub(end, start))
53074
+ };
53075
+ }
53076
+ const planeFirst = (sA == null ? void 0 : sA.kind) === "plane";
53077
+ const plane = planeFirst ? sA : (sB == null ? void 0 : sB.kind) === "plane" ? sB : void 0;
53078
+ const planeName = planeFirst ? faceA : faceB;
53079
+ const curved = planeFirst ? sB : sA;
53080
+ const curvedName = planeFirst ? faceB : faceA;
53081
+ if (plane && curved) {
53082
+ const circle = circleFromPlaneAndCarrier(plane, planeName, curved, curvedName, chain, planePointByName);
53083
+ if (circle) {
53084
+ return { curve: circle, length: 2 * Math.PI * circle.radius };
53085
+ }
53086
+ }
53087
+ return { curve: { kind: "unidentified" }, length: polylineLength(chain) };
53088
+ }
53089
+ function circleFromPlaneAndCarrier(plane, planeName, curved, curvedName, chain, planePointByName) {
53090
+ const planePoint = planePointByName.get(planeName);
53091
+ if (!planePoint) return null;
53092
+ const n = normalize$1(plane.normal);
53093
+ const onEdge = chainCentroid(chain);
53094
+ switch (curved.kind) {
53095
+ case "cylinder": {
53096
+ const ax = normalize$1(curved.axis);
53097
+ if (Math.abs(dot(ax, n)) < PARALLEL_DOT) return null;
53098
+ const center2 = axisPointAtPlane(curved.origin, ax, onEdge);
53099
+ return { kind: "circle", center: center2, axis: ax, radius: curved.radius, faceName: curvedName };
53100
+ }
53101
+ case "cone": {
53102
+ const ax = normalize$1(curved.axis);
53103
+ if (Math.abs(dot(ax, n)) < PARALLEL_DOT) return null;
53104
+ const center2 = axisPointAtPlane(curved.origin, ax, onEdge);
53105
+ const t = dot(sub(center2, curved.origin), ax);
53106
+ const radius = curved.radiusBottom + (curved.radiusTop - curved.radiusBottom) * (t / curved.height);
53107
+ if (!(radius > DEGENERATE)) return null;
53108
+ return { kind: "circle", center: center2, axis: ax, radius, faceName: curvedName };
53109
+ }
53110
+ case "sphere": {
53111
+ const dPlane = dot(n, sub(curved.center, planePoint));
53112
+ const r2 = curved.radius * curved.radius - dPlane * dPlane;
53113
+ if (!(r2 > DEGENERATE)) return null;
53114
+ const center2 = sub(curved.center, scl(n, dPlane));
53115
+ return { kind: "circle", center: center2, axis: n, radius: Math.sqrt(r2), faceName: curvedName };
53116
+ }
53117
+ default:
53118
+ return null;
53119
+ }
53120
+ }
53121
+ function axisPointAtPlane(origin, ax, onEdge) {
53122
+ const t = dot(sub(onEdge, origin), ax);
53123
+ return add(origin, scl(ax, t));
53124
+ }
53125
+ function chainCentroid(chain) {
53126
+ let x2 = 0;
53127
+ let y2 = 0;
53128
+ let z2 = 0;
53129
+ for (const p2 of chain) {
53130
+ x2 += p2[0];
53131
+ y2 += p2[1];
53132
+ z2 += p2[2];
53133
+ }
53134
+ const k2 = 1 / chain.length;
53135
+ return [x2 * k2, y2 * k2, z2 * k2];
53136
+ }
53137
+ function chainExtremes(chain) {
53138
+ let best = -1;
53139
+ let si = 0;
53140
+ let ei = chain.length - 1;
53141
+ for (let i = 0; i < chain.length; i++) {
53142
+ for (let j = i + 1; j < chain.length; j++) {
53143
+ const d2 = len(sub(chain[j], chain[i]));
53144
+ if (d2 > best) {
53145
+ best = d2;
53146
+ si = i;
53147
+ ei = j;
53148
+ }
53149
+ }
53150
+ }
53151
+ return { start: chain[si], end: chain[ei] };
53152
+ }
51683
53153
  function cloneSerializedFaceRef(face) {
51684
53154
  return {
51685
53155
  ...face,
@@ -51713,6 +53183,7 @@ class FrozenShapeBackend {
51713
53183
  edgePositions: this._data.geometryEdgePositions,
51714
53184
  triangleFaceIds: this._data.geometryTriangleFaceIds,
51715
53185
  faceIdNames: this._data.geometryFaceIdNames,
53186
+ forgeEdges: this._data.geometryEdges ?? [],
51716
53187
  hasSmoothNormals: this._data.hasSmoothNormals ?? false,
51717
53188
  uvs: this._data.geometryUvs
51718
53189
  };
@@ -51881,249 +53352,25 @@ class FrozenShape extends Shape {
51881
53352
  return history;
51882
53353
  }
51883
53354
  }
51884
- const EDGE_THRESHOLD_DOT = Math.cos(Math.PI / 180);
51885
- const SMOOTH_THRESHOLD_DOT = Math.cos(30 * Math.PI / 180);
51886
- function computeGeometryArrays(mesh, options = {}) {
51887
- const { numProp, numTri: triCount, triVerts, vertProperties, vertNormals, cornerNormals } = mesh;
51888
- if (numProp !== NUM_PROP_POSITION_ONLY && numProp !== NUM_PROP_WITH_NORMAL && numProp !== NUM_PROP_WITH_UV) {
51889
- throw new Error(
51890
- `computeGeometryArrays: illegal vertProperties numProp ${numProp}. Only ${NUM_PROP_POSITION_ONLY} (position), ${NUM_PROP_WITH_NORMAL} (position+normal), and ${NUM_PROP_WITH_UV} (position+normal+uv) are valid; ${numProp} (e.g. a truncated uv channel) is not.`
51891
- );
51892
- }
51893
- const hasStoredNormals = numProp === NUM_PROP_WITH_NORMAL || numProp === NUM_PROP_WITH_UV;
51894
- const hasStoredUvs = numProp === NUM_PROP_WITH_UV;
51895
- const useCornerNormals = !!cornerNormals && cornerNormals.length === triCount * 9;
51896
- const positions = new Float32Array(triCount * 9);
51897
- const normals = new Float32Array(triCount * 9);
51898
- const uvs = hasStoredUvs ? new Float32Array(triCount * 6) : void 0;
51899
- const faceNx = new Float32Array(triCount);
51900
- const faceNy = new Float32Array(triCount);
51901
- const faceNz = new Float32Array(triCount);
51902
- for (let t = 0; t < triCount; t++) {
51903
- const i0 = triVerts[t * 3];
51904
- const i1 = triVerts[t * 3 + 1];
51905
- const i2 = triVerts[t * 3 + 2];
51906
- const ax = vertProperties[i0 * numProp], ay = vertProperties[i0 * numProp + 1], az = vertProperties[i0 * numProp + 2];
51907
- const bx = vertProperties[i1 * numProp], by = vertProperties[i1 * numProp + 1], bz = vertProperties[i1 * numProp + 2];
51908
- const cx = vertProperties[i2 * numProp], cy = vertProperties[i2 * numProp + 1], cz = vertProperties[i2 * numProp + 2];
51909
- const e1x = bx - ax, e1y = by - ay, e1z = bz - az;
51910
- const e2x = cx - ax, e2y = cy - ay, e2z = cz - az;
51911
- let fnx = e1y * e2z - e1z * e2y;
51912
- let fny = e1z * e2x - e1x * e2z;
51913
- let fnz = e1x * e2y - e1y * e2x;
51914
- const len = Math.sqrt(fnx * fnx + fny * fny + fnz * fnz) || 1;
51915
- fnx /= len;
51916
- fny /= len;
51917
- fnz /= len;
51918
- const o = t * 9;
51919
- positions[o] = ax;
51920
- positions[o + 1] = ay;
51921
- positions[o + 2] = az;
51922
- positions[o + 3] = bx;
51923
- positions[o + 4] = by;
51924
- positions[o + 5] = bz;
51925
- positions[o + 6] = cx;
51926
- positions[o + 7] = cy;
51927
- positions[o + 8] = cz;
51928
- if (uvs) {
51929
- const u2 = t * 6;
51930
- uvs[u2] = vertProperties[i0 * numProp + UV_OFFSET];
51931
- uvs[u2 + 1] = vertProperties[i0 * numProp + UV_OFFSET + 1];
51932
- uvs[u2 + 2] = vertProperties[i1 * numProp + UV_OFFSET];
51933
- uvs[u2 + 3] = vertProperties[i1 * numProp + UV_OFFSET + 1];
51934
- uvs[u2 + 4] = vertProperties[i2 * numProp + UV_OFFSET];
51935
- uvs[u2 + 5] = vertProperties[i2 * numProp + UV_OFFSET + 1];
51936
- }
51937
- if (useCornerNormals) {
51938
- for (let k2 = 0; k2 < 9; k2++) normals[o + k2] = cornerNormals[o + k2];
51939
- } else if (vertNormals) {
51940
- normals[o] = vertNormals[i0 * 3];
51941
- normals[o + 1] = vertNormals[i0 * 3 + 1];
51942
- normals[o + 2] = vertNormals[i0 * 3 + 2];
51943
- normals[o + 3] = vertNormals[i1 * 3];
51944
- normals[o + 4] = vertNormals[i1 * 3 + 1];
51945
- normals[o + 5] = vertNormals[i1 * 3 + 2];
51946
- normals[o + 6] = vertNormals[i2 * 3];
51947
- normals[o + 7] = vertNormals[i2 * 3 + 1];
51948
- normals[o + 8] = vertNormals[i2 * 3 + 2];
51949
- } else if (hasStoredNormals) {
51950
- const corners = [i0, i1, i2];
51951
- for (let v = 0; v < 3; v++) {
51952
- const base = corners[v] * numProp;
51953
- const nx = vertProperties[base + NORMAL_OFFSET];
51954
- const ny = vertProperties[base + NORMAL_OFFSET + 1];
51955
- const nz = vertProperties[base + NORMAL_OFFSET + 2];
51956
- const oc = o + v * 3;
51957
- if (nx * nx + ny * ny + nz * nz > 1e-12) {
51958
- normals[oc] = nx;
51959
- normals[oc + 1] = ny;
51960
- normals[oc + 2] = nz;
51961
- } else {
51962
- normals[oc] = fnx;
51963
- normals[oc + 1] = fny;
51964
- normals[oc + 2] = fnz;
51965
- }
51966
- }
51967
- } else {
51968
- normals[o] = fnx;
51969
- normals[o + 1] = fny;
51970
- normals[o + 2] = fnz;
51971
- normals[o + 3] = fnx;
51972
- normals[o + 4] = fny;
51973
- normals[o + 5] = fnz;
51974
- normals[o + 6] = fnx;
51975
- normals[o + 7] = fny;
51976
- normals[o + 8] = fnz;
51977
- }
51978
- faceNx[t] = fnx;
51979
- faceNy[t] = fny;
51980
- faceNz[t] = fnz;
51981
- }
51982
- if (!useCornerNormals && !vertNormals && !hasStoredNormals && triCount > 0) {
51983
- computeAutoSmoothNormals(
51984
- triVerts,
51985
- vertProperties,
51986
- numProp,
51987
- triCount,
51988
- faceNx,
51989
- faceNy,
51990
- faceNz,
51991
- normals,
51992
- mesh.mergeFromVert,
51993
- mesh.mergeToVert
51994
- );
51995
- }
51996
- const edgePositions = options.skipEdges ? new Float32Array(0) : computeSharpEdges(triVerts, vertProperties, numProp, triCount, faceNx, faceNy, faceNz, mesh.mergeFromVert, mesh.mergeToVert);
51997
- return {
51998
- positions,
51999
- normals,
52000
- edgePositions,
52001
- hasSmoothNormals: triCount > 0,
52002
- uvs
52003
- };
52004
- }
52005
- function computeAutoSmoothNormals(triVerts, vertProperties, numProp, triCount, faceNx, faceNy, faceNz, normals, mergeFromVert, mergeToVert) {
52006
- const numVerts = vertProperties.length / numProp;
52007
- const canon = buildCanonicalMap(numVerts, mergeFromVert, mergeToVert);
52008
- const vertToTris = /* @__PURE__ */ new Map();
52009
- for (let t = 0; t < triCount; t++) {
52010
- for (let v = 0; v < 3; v++) {
52011
- const cv = canon[triVerts[t * 3 + v]];
52012
- let list = vertToTris.get(cv);
52013
- if (!list) {
52014
- list = [];
52015
- vertToTris.set(cv, list);
52016
- }
52017
- list.push(t);
52018
- }
52019
- }
52020
- for (let t = 0; t < triCount; t++) {
52021
- for (let v = 0; v < 3; v++) {
52022
- const cv = canon[triVerts[t * 3 + v]];
52023
- const adjacentTris = vertToTris.get(cv);
52024
- if (!adjacentTris || adjacentTris.length <= 1) continue;
52025
- let sx = 0, sy = 0, sz = 0;
52026
- for (const adj of adjacentTris) {
52027
- const dot2 = faceNx[t] * faceNx[adj] + faceNy[t] * faceNy[adj] + faceNz[t] * faceNz[adj];
52028
- if (dot2 >= SMOOTH_THRESHOLD_DOT) {
52029
- sx += faceNx[adj];
52030
- sy += faceNy[adj];
52031
- sz += faceNz[adj];
52032
- }
52033
- }
52034
- const slen = Math.sqrt(sx * sx + sy * sy + sz * sz);
52035
- if (slen > 1e-9) {
52036
- sx /= slen;
52037
- sy /= slen;
52038
- sz /= slen;
52039
- const o = t * 9 + v * 3;
52040
- normals[o] = sx;
52041
- normals[o + 1] = sy;
52042
- normals[o + 2] = sz;
52043
- }
52044
- }
52045
- }
52046
- }
52047
- function computeSharpEdges(triVerts, vertProperties, numProp, triCount, faceNx, faceNy, faceNz, mergeFromVert, mergeToVert) {
52048
- const numVerts = vertProperties.length / numProp;
52049
- const canon = buildCanonicalMap(numVerts, mergeFromVert, mergeToVert);
52050
- const MAX_NUMERIC = 1 << 21;
52051
- let maxCanon = 0;
52052
- for (let i = 0; i < numVerts; i++) {
52053
- if (canon[i] > maxCanon) maxCanon = canon[i];
52054
- }
52055
- const useNumeric = maxCanon < MAX_NUMERIC;
52056
- const halfEdges = /* @__PURE__ */ new Map();
52057
- const edgeList = [];
52058
- for (let t = 0; t < triCount; t++) {
52059
- const ca = canon[triVerts[t * 3]];
52060
- const cb = canon[triVerts[t * 3 + 1]];
52061
- const cc = canon[triVerts[t * 3 + 2]];
52062
- const tv = [ca, cb, cc];
52063
- for (let e = 0; e < 3; e++) {
52064
- const va = tv[e], vb = tv[(e + 1) % 3];
52065
- const fwdKey = useNumeric ? va * MAX_NUMERIC + vb : `${va},${vb}`;
52066
- const revKey = useNumeric ? vb * MAX_NUMERIC + va : `${vb},${va}`;
52067
- const adjTri = halfEdges.get(revKey);
52068
- if (adjTri !== void 0) {
52069
- const dot2 = faceNx[t] * faceNx[adjTri] + faceNy[t] * faceNy[adjTri] + faceNz[t] * faceNz[adjTri];
52070
- if (dot2 <= EDGE_THRESHOLD_DOT) {
52071
- const origVa = triVerts[t * 3 + e];
52072
- const origVb = triVerts[t * 3 + (e + 1) % 3];
52073
- edgeList.push(
52074
- vertProperties[origVa * numProp],
52075
- vertProperties[origVa * numProp + 1],
52076
- vertProperties[origVa * numProp + 2],
52077
- vertProperties[origVb * numProp],
52078
- vertProperties[origVb * numProp + 1],
52079
- vertProperties[origVb * numProp + 2]
52080
- );
52081
- }
52082
- halfEdges.delete(revKey);
52083
- } else {
52084
- halfEdges.set(fwdKey, t);
52085
- }
52086
- }
52087
- }
52088
- for (const [key2, t] of halfEdges) {
52089
- const ca = canon[triVerts[t * 3]];
52090
- const cb = canon[triVerts[t * 3 + 1]];
52091
- const cc = canon[triVerts[t * 3 + 2]];
52092
- const tv = [ca, cb, cc];
52093
- for (let e = 0; e < 3; e++) {
52094
- const va = tv[e], vb = tv[(e + 1) % 3];
52095
- const fwdKey = useNumeric ? va * MAX_NUMERIC + vb : `${va},${vb}`;
52096
- if (fwdKey === key2) {
52097
- const origVa = triVerts[t * 3 + e];
52098
- const origVb = triVerts[t * 3 + (e + 1) % 3];
52099
- edgeList.push(
52100
- vertProperties[origVa * numProp],
52101
- vertProperties[origVa * numProp + 1],
52102
- vertProperties[origVa * numProp + 2],
52103
- vertProperties[origVb * numProp],
52104
- vertProperties[origVb * numProp + 1],
52105
- vertProperties[origVb * numProp + 2]
52106
- );
52107
- break;
52108
- }
52109
- }
53355
+ function toBakedEdge(edge) {
53356
+ const points = [];
53357
+ for (const p2 of edge.chain) {
53358
+ points.push(p2[0], p2[1], p2[2]);
52110
53359
  }
52111
- return new Float32Array(edgeList);
53360
+ return { faceA: edge.faceA, faceB: edge.faceB, curve: edge.curve, length: edge.length, points };
52112
53361
  }
52113
- function buildCanonicalMap(numVerts, mergeFromVert, mergeToVert) {
52114
- const canon = new Uint32Array(numVerts);
52115
- for (let i = 0; i < numVerts; i++) canon[i] = i;
52116
- if (mergeFromVert && mergeToVert) {
52117
- for (let i = 0; i < mergeFromVert.length; i++) {
52118
- canon[mergeFromVert[i]] = mergeToVert[i];
52119
- }
52120
- for (let i = 0; i < numVerts; i++) {
52121
- let v = canon[i];
52122
- while (canon[v] !== v) v = canon[v];
52123
- canon[i] = v;
52124
- }
53362
+ function attachEdgeMetadata(geometry, mesh, shape) {
53363
+ let edges;
53364
+ try {
53365
+ const carriers = gatherShapeCarriers(getShapeCompilePlan(shape));
53366
+ if (carriers.length === 0) return;
53367
+ edges = buildEdgeClassification(mesh, carriers);
53368
+ } catch {
53369
+ return;
52125
53370
  }
52126
- return canon;
53371
+ if (edges.length === 0) return;
53372
+ const userData = geometry.userData;
53373
+ userData.forgeEdges = edges.map(toBakedEdge);
52127
53374
  }
52128
53375
  function attachKernelFaceMetadata(geometry, triangleFaceIds, faceIdNames) {
52129
53376
  if (!triangleFaceIds || triangleFaceIds.length === 0 || !faceIdNames || faceIdNames.length === 0) return;
@@ -52134,12 +53381,15 @@ function attachKernelFaceMetadata(geometry, triangleFaceIds, faceIdNames) {
52134
53381
  }
52135
53382
  function shapeToGeometry(shape) {
52136
53383
  if (shape instanceof FrozenShape) {
52137
- const { positions, normals, edgePositions, triangleFaceIds, faceIdNames, hasSmoothNormals, uvs } = shape.getPrecomputedGeometry();
53384
+ const { positions, normals, edgePositions, triangleFaceIds, faceIdNames, forgeEdges, hasSmoothNormals, uvs } = shape.getPrecomputedGeometry();
52138
53385
  const solid = new BufferGeometry();
52139
53386
  solid.setAttribute("position", new BufferAttribute(positions, 3));
52140
53387
  solid.setAttribute("normal", new BufferAttribute(normals, 3));
52141
53388
  if (uvs) solid.setAttribute("uv", new BufferAttribute(uvs, 2));
52142
53389
  attachKernelFaceMetadata(solid, triangleFaceIds, faceIdNames);
53390
+ if (forgeEdges && forgeEdges.length > 0) {
53391
+ solid.userData.forgeEdges = forgeEdges;
53392
+ }
52143
53393
  const edges = new BufferGeometry();
52144
53394
  edges.setAttribute("position", new BufferAttribute(edgePositions, 3));
52145
53395
  return { solid, edges, hasSmoothNormals };
@@ -52176,6 +53426,7 @@ function shapeToGeometryFallback(shape) {
52176
53426
  solid.setAttribute("normal", new BufferAttribute(normals, 3));
52177
53427
  if (uvs) solid.setAttribute("uv", new BufferAttribute(uvs, 2));
52178
53428
  attachKernelFaceMetadata(solid, mesh.faceID, mesh.faceIdNames);
53429
+ attachEdgeMetadata(solid, mesh, shape);
52179
53430
  const edges = new BufferGeometry();
52180
53431
  edges.setAttribute("position", new BufferAttribute(edgePositions, 3));
52181
53432
  return { solid, edges, hasSmoothNormals };