forgecad 0.10.2 → 0.10.3

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 (95) hide show
  1. package/README.md +7 -6
  2. package/dist/assets/{AdminPage-CHY6ZN-p.js → AdminPage-CK7ObBz3.js} +1 -1
  3. package/dist/assets/{BenchmarkPage-BcRT5iGN.js → BenchmarkPage-Ds7Z2doN.js} +1 -1
  4. package/dist/assets/{BlogPage-BssBbnb-.js → BlogPage-DlPbpt6A.js} +1 -1
  5. package/dist/assets/{DocsPage-DsvdiRNK.js → DocsPage-vZb3b3Y0.js} +9 -14
  6. package/dist/assets/{EditorApp-BpjZgzk0.css → EditorApp-C5f24ZN9.css} +8 -0
  7. package/dist/assets/{EditorApp-Bfd3jbtC.js → EditorApp-HLoKfe15.js} +141 -12
  8. package/dist/assets/{EmbedViewer-D5t8WamV.js → EmbedViewer--KnqBKrJ.js} +2 -2
  9. package/dist/assets/{LandingPageProofDriven-DbN7o-Be.js → LandingPageProofDriven-C_LssmnA.js} +1 -1
  10. package/dist/assets/{LegalPage-DNGrrY0p.js → LegalPage-DGsyo4n1.js} +1 -1
  11. package/dist/assets/{PricingPage-Nczr3pRz.js → PricingPage-BOE27B-R.js} +1 -1
  12. package/dist/assets/{SettingsPage-DZlyu4d4.js → SettingsPage-f47cnk39.js} +1 -1
  13. package/dist/assets/{app-C9ct2hRD.js → app-D6ccu2Xx.js} +6854 -7373
  14. package/dist/assets/{backendInit-ymjonyQp.js → backendInit-DbTkQN9J.js} +2557 -809
  15. package/dist/assets/cli/{render-B_0lQwKU.js → render-BsngirjC.js} +114 -9
  16. package/dist/assets/{constructionHistoryWorker-CZ42Dksy.js → constructionHistoryWorker-PCwXrTDB.js} +175 -36
  17. package/dist/assets/{evalWorker-C2pm8LHP.js → evalWorker-CS63PfZu.js} +1125 -447
  18. package/dist/assets/{forgecad_geometry-BlMtqluF.js → forgecad_geometry-CZ_IfuvA.js} +1 -9
  19. package/dist/assets/{forgecad_geometry_bg-BllP_WiL.wasm → forgecad_geometry_bg-C3rQHfwg.wasm} +0 -0
  20. package/dist/assets/{inspectWorker-D5T5VbfK.js → inspectWorker-Y4cOzNyA.js} +4345 -373
  21. package/dist/assets/{jointPose-4r8ed8_5.js → jointPose-AMvCywzS.js} +1 -1
  22. package/dist/assets/{manifold-C4r6B-XY.js → manifold-CBry38ly.js} +2 -2
  23. package/dist/assets/{manifold-5PP1eGLN.js → manifold-Crd_F2qx.js} +1 -1
  24. package/dist/assets/{manifold-DjBkyIc8.js → manifold-k2kRcc85.js} +1 -1
  25. package/dist/assets/{reportWorker-CwenM7wB.js → reportWorker-CWvn0CEv.js} +1095 -400
  26. package/dist/cli/render.html +1 -1
  27. package/dist/docs/index.html +2 -2
  28. package/dist/docs-raw/AI/usage.md +2 -4
  29. package/dist/docs-raw/CLI.md +9 -7
  30. package/dist/docs-raw/README.md +1 -1
  31. package/dist/docs-raw/component-model.md +1 -1
  32. package/dist/docs-raw/generated/assembly.md +1 -1
  33. package/dist/docs-raw/generated/concepts.md +5 -3
  34. package/dist/docs-raw/generated/core.md +70 -1
  35. package/dist/docs-raw/generated/curves.md +8 -1
  36. package/dist/docs-raw/generated/output.md +0 -64
  37. package/dist/docs-raw/generated/runtime-names.md +6 -6
  38. package/dist/docs-raw/generated/viewport.md +3 -12
  39. package/dist/docs-raw/guides/inspection-bundles.md +1 -1
  40. package/dist/docs-raw/simulation-workflow.md +58 -0
  41. package/dist/docs-raw/skills/forgecad-blockout-model.md +1 -1
  42. package/dist/docs-raw/skills/forgecad-image-replicator.md +2 -2
  43. package/dist/docs-raw/skills/forgecad-mujoco-verify.md +78 -0
  44. package/dist/docs-raw/skills/forgecad-spec-by-walking-through-it.md +145 -0
  45. package/dist/docs-raw/skills/forgecad-visual-spec.md +1 -1
  46. package/dist/docs-raw/skills/forgecad.md +24 -24
  47. package/dist/docs-raw/skills/index.md +2 -3
  48. package/dist/index.html +1 -1
  49. package/dist/sitemap.xml +15 -15
  50. package/dist-cli/{check-compiler-SP7FAL7R.js → check-compiler-HPF2T2FS.js} +1 -1
  51. package/dist-cli/{check-query-propagation-BRLSHP22.js → check-query-propagation-HYSLTXAB.js} +1 -1
  52. package/dist-cli/{chunk-RQQ42YCP.js → chunk-WLUKAW3H.js} +1025 -158
  53. package/dist-cli/forgecad.js +2621 -232
  54. package/dist-cli/{forgecad_geometry-7TVSNVUB.js → forgecad_geometry-2IMYCUWW.js} +0 -8
  55. package/dist-cli/forgecad_geometry_bg.wasm +0 -0
  56. package/dist-skill/CONTEXT.md +85 -73
  57. package/dist-skill/SKILL.md +1 -1
  58. package/dist-skill/docs/CLI.md +9 -7
  59. package/dist-skill/docs/generated/assembly.md +1 -1
  60. package/dist-skill/docs/generated/core.md +70 -1
  61. package/dist-skill/docs/generated/curves.md +8 -1
  62. package/dist-skill/docs/generated/output.md +0 -64
  63. package/dist-skill/docs/generated/runtime-names.md +6 -6
  64. package/dist-skill/docs/generated/viewport.md +3 -12
  65. package/dist-skill/docs/guides/inspection-bundles.md +1 -1
  66. package/dist-skill/library/README.md +2 -3
  67. package/dist-skill/library/forgecad-blockout-model/SKILL.md +1 -1
  68. package/dist-skill/library/forgecad-image-replicator/SKILL.md +2 -2
  69. package/dist-skill/library/forgecad-mujoco-verify/SKILL.md +66 -0
  70. package/dist-skill/library/forgecad-mujoco-verify/scripts/mujoco_verify.py +385 -0
  71. package/dist-skill/library/forgecad-spec-by-walking-through-it/SKILL.md +132 -0
  72. package/dist-skill/library/forgecad-visual-spec/SKILL.md +1 -1
  73. package/dist-skill/website/skills/forgecad-blockout-model.md +1 -1
  74. package/dist-skill/website/skills/forgecad-image-replicator.md +2 -2
  75. package/dist-skill/website/skills/forgecad-mujoco-verify.md +78 -0
  76. package/dist-skill/website/skills/forgecad-spec-by-walking-through-it.md +145 -0
  77. package/dist-skill/website/skills/forgecad-visual-spec.md +1 -1
  78. package/dist-skill/website/skills/forgecad.md +24 -24
  79. package/dist-skill/website/skills/index.md +2 -3
  80. package/examples/analysis/clearance-fit.forge.js +31 -0
  81. package/examples/analysis/lever-arm-actuator.forge.js +43 -0
  82. package/examples/analysis/tipping-tripod.forge.js +35 -0
  83. package/examples/products/sportscar.forge.js +77 -0
  84. package/package.json +1 -3
  85. package/dist/docs-raw/skills/forgecad-high-level-spec.md +0 -101
  86. package/dist/docs-raw/skills/forgecad-lld.md +0 -41
  87. package/dist/docs-raw/skills/forgecad-prepare-prompt.md +0 -63
  88. package/dist-skill/library/forgecad-high-level-spec/SKILL.md +0 -94
  89. package/dist-skill/library/forgecad-lld/SKILL.md +0 -34
  90. package/dist-skill/library/forgecad-prepare-prompt/SKILL.md +0 -50
  91. package/dist-skill/website/skills/forgecad-high-level-spec.md +0 -101
  92. package/dist-skill/website/skills/forgecad-lld.md +0 -41
  93. package/dist-skill/website/skills/forgecad-prepare-prompt.md +0 -63
  94. /package/dist-skill/library/{forgecad-prepare-prompt → forgecad-spec-by-walking-through-it}/references/default-profiles.md +0 -0
  95. /package/dist-skill/library/{forgecad-prepare-prompt → forgecad-spec-by-walking-through-it}/references/master-prompt.md +0 -0
@@ -3175,20 +3175,20 @@ function generateClampedKnots(n, degree) {
3175
3175
  return knots;
3176
3176
  }
3177
3177
  const KNOT_EPSILON = 1e-10;
3178
- function sameKnot(a2, b) {
3178
+ function sameKnot$1(a2, b) {
3179
3179
  return Math.abs(a2 - b) <= KNOT_EPSILON;
3180
3180
  }
3181
- function knotMultiplicity(knots, u2) {
3182
- return knots.reduce((count, knot) => count + (sameKnot(knot, u2) ? 1 : 0), 0);
3181
+ function knotMultiplicity$1(knots, u2) {
3182
+ return knots.reduce((count, knot) => count + (sameKnot$1(knot, u2) ? 1 : 0), 0);
3183
3183
  }
3184
3184
  function firstKnotIndex(knots, u2) {
3185
- const index2 = knots.findIndex((knot) => sameKnot(knot, u2));
3185
+ const index2 = knots.findIndex((knot) => sameKnot$1(knot, u2));
3186
3186
  if (index2 < 0) throw new Error(`NURBS subdomain extraction could not find boundary knot ${u2}.`);
3187
3187
  return index2;
3188
3188
  }
3189
3189
  function lastKnotIndex(knots, u2) {
3190
3190
  for (let index2 = knots.length - 1; index2 >= 0; index2 -= 1) {
3191
- if (sameKnot(knots[index2], u2)) return index2;
3191
+ if (sameKnot$1(knots[index2], u2)) return index2;
3192
3192
  }
3193
3193
  throw new Error(`NURBS subdomain extraction could not find boundary knot ${u2}.`);
3194
3194
  }
@@ -3201,7 +3201,7 @@ function insertKnotOnceHomogeneous(points, knots, degree, u2) {
3201
3201
  const lastPoint = count - 1;
3202
3202
  const lastKnot = knots.length - 1;
3203
3203
  const span = findSpan(count, degree, u2, knots);
3204
- const multiplicity = knotMultiplicity(knots, u2);
3204
+ const multiplicity = knotMultiplicity$1(knots, u2);
3205
3205
  if (multiplicity >= degree) {
3206
3206
  throw new Error(`NURBS subdomain extraction cannot insert knot ${u2}: multiplicity ${multiplicity} already reaches degree ${degree}.`);
3207
3207
  }
@@ -3225,7 +3225,7 @@ function insertKnotOnceHomogeneous(points, knots, degree, u2) {
3225
3225
  function insertBoundaryToMultiplicity(points, knots, degree, u2, targetMultiplicity) {
3226
3226
  let refinedPoints = points;
3227
3227
  let refinedKnots = knots;
3228
- while (knotMultiplicity(refinedKnots, u2) < targetMultiplicity) {
3228
+ while (knotMultiplicity$1(refinedKnots, u2) < targetMultiplicity) {
3229
3229
  const refined = insertKnotOnceHomogeneous(refinedPoints, refinedKnots, degree, u2);
3230
3230
  refinedPoints = refined.points;
3231
3231
  refinedKnots = refined.knots;
@@ -3245,8 +3245,8 @@ function extractNurbsCurveSubdomain(controlPoints, weights, knots, degree, uStar
3245
3245
  }
3246
3246
  const activeStart = knots[degree];
3247
3247
  const activeEnd = knots[controlPoints.length];
3248
- const start = sameKnot(uStart, activeStart) ? activeStart : sameKnot(uStart, activeEnd) ? activeEnd : uStart;
3249
- const end = sameKnot(uEnd, activeEnd) ? activeEnd : sameKnot(uEnd, activeStart) ? activeStart : uEnd;
3248
+ const start = sameKnot$1(uStart, activeStart) ? activeStart : sameKnot$1(uStart, activeEnd) ? activeEnd : uStart;
3249
+ const end = sameKnot$1(uEnd, activeEnd) ? activeEnd : sameKnot$1(uEnd, activeStart) ? activeStart : uEnd;
3250
3250
  if (start < activeStart - KNOT_EPSILON || end > activeEnd + KNOT_EPSILON) {
3251
3251
  throw new Error(`NURBS subdomain [${uStart}, ${uEnd}] must stay inside active knot domain [${activeStart}, ${activeEnd}].`);
3252
3252
  }
@@ -9653,7 +9653,7 @@ async function initTruckGeometryWasm() {
9653
9653
  if (_initPromise$1) return _initPromise$1;
9654
9654
  _initPromise$1 = (async () => {
9655
9655
  try {
9656
- const geometryModule = await import("./forgecad_geometry-BlMtqluF.js");
9656
+ const geometryModule = await import("./forgecad_geometry-CZ_IfuvA.js");
9657
9657
  const isNode = isNodeRuntime();
9658
9658
  if (isNode) {
9659
9659
  const { readFileSync, existsSync } = await Promise.resolve().then(function() {
@@ -10451,23 +10451,62 @@ class NurbsSurface {
10451
10451
  return [wx / wSum, wy / wSum, wz / wSum];
10452
10452
  }
10453
10453
  /**
10454
- * Evaluate the surface normal at (u, v) via cross product of partial derivatives.
10454
+ * Evaluate the surface unit normal at (u, v) from analytic first derivatives.
10455
+ *
10456
+ * Uses Algorithm A2.3 basis-function derivatives with the rational quotient
10457
+ * rule, so the normal is exact (no finite-difference epsilon, no error near
10458
+ * the boundary). Constant chain-rule factors from the parameter remap scale Su
10459
+ * and Sv positively and cancel under normalization, so they are omitted.
10455
10460
  */
10456
10461
  normalAt(u2, v) {
10457
- const eps = 1e-5;
10458
- const u0 = Math.max(0, u2 - eps), u1 = Math.min(1, u2 + eps);
10459
- const v0 = Math.max(0, v - eps), v1 = Math.min(1, v + eps);
10460
- const pu = this.pointAt(u1, v), pmu = this.pointAt(u0, v);
10461
- const pv = this.pointAt(u2, v1), pmv = this.pointAt(u2, v0);
10462
- const du = [pu[0] - pmu[0], pu[1] - pmu[1], pu[2] - pmu[2]];
10463
- const dv = [pv[0] - pmv[0], pv[1] - pmv[1], pv[2] - pmv[2]];
10464
- const nx = du[1] * dv[2] - du[2] * dv[1];
10465
- const ny = du[2] * dv[0] - du[0] * dv[2];
10466
- const nz = du[0] * dv[1] - du[1] * dv[0];
10462
+ const { Su, Sv } = this.derivativesAt(u2, v);
10463
+ const nx = Su[1] * Sv[2] - Su[2] * Sv[1];
10464
+ const ny = Su[2] * Sv[0] - Su[0] * Sv[2];
10465
+ const nz = Su[0] * Sv[1] - Su[1] * Sv[0];
10467
10466
  const len2 = Math.sqrt(nx * nx + ny * ny + nz * nz);
10468
10467
  if (len2 < 1e-12) return [0, 0, 1];
10469
10468
  return [nx / len2, ny / len2, nz / len2];
10470
10469
  }
10470
+ /** Analytic first partial derivatives S_u, S_v (rational quotient rule). */
10471
+ derivativesAt(u2, v) {
10472
+ const uu = this.remapU(Math.max(0, Math.min(1, u2)));
10473
+ const vv = this.remapV(Math.max(0, Math.min(1, v)));
10474
+ const spanU = findSpan(this.nU, this.degreeU, uu, this.knotsU);
10475
+ const spanV = findSpan(this.nV, this.degreeV, vv, this.knotsV);
10476
+ const dU = basisFunsDeriv(spanU, uu, this.degreeU, this.knotsU, 1);
10477
+ const dV = basisFunsDeriv(spanV, vv, this.degreeV, this.knotsV, 1);
10478
+ const A = [0, 0, 0];
10479
+ const Au = [0, 0, 0];
10480
+ const Av = [0, 0, 0];
10481
+ let w2 = 0;
10482
+ let wu = 0;
10483
+ let wv = 0;
10484
+ for (let i = 0; i <= this.degreeU; i++) {
10485
+ const rowIdx = spanU - this.degreeU + i;
10486
+ for (let j = 0; j <= this.degreeV; j++) {
10487
+ const colIdx = spanV - this.degreeV + j;
10488
+ const weight = this.weightsGrid[rowIdx][colIdx];
10489
+ const pt = this.controlGrid[rowIdx][colIdx];
10490
+ const n00 = dU[0][i] * dV[0][j] * weight;
10491
+ const n10 = dU[1][i] * dV[0][j] * weight;
10492
+ const n01 = dU[0][i] * dV[1][j] * weight;
10493
+ w2 += n00;
10494
+ wu += n10;
10495
+ wv += n01;
10496
+ for (let c2 = 0; c2 < 3; c2++) {
10497
+ A[c2] += n00 * pt[c2];
10498
+ Au[c2] += n10 * pt[c2];
10499
+ Av[c2] += n01 * pt[c2];
10500
+ }
10501
+ }
10502
+ }
10503
+ if (w2 === 0) return { S: [0, 0, 0], Su: [0, 0, 0], Sv: [0, 0, 0] };
10504
+ const invW = 1 / w2;
10505
+ const S = [A[0] * invW, A[1] * invW, A[2] * invW];
10506
+ const Su = [(Au[0] - wu * S[0]) * invW, (Au[1] - wu * S[1]) * invW, (Au[2] - wu * S[2]) * invW];
10507
+ const Sv = [(Av[0] - wv * S[0]) * invW, (Av[1] - wv * S[1]) * invW, (Av[2] - wv * S[2]) * invW];
10508
+ return { S, Su, Sv };
10509
+ }
10471
10510
  /**
10472
10511
  * Tessellate the surface into a triangle mesh.
10473
10512
  * Returns positions, normals, and triangle indices.
@@ -11833,7 +11872,7 @@ function maxQuadDeviation(rings, heights, colA, colB) {
11833
11872
  const b = [rings[i][colB][0], rings[i][colB][1], heights[i]];
11834
11873
  const c2 = [rings[i + 1][colB][0], rings[i + 1][colB][1], heights[i + 1]];
11835
11874
  const d2 = [rings[i + 1][colA][0], rings[i + 1][colA][1], heights[i + 1]];
11836
- const n = cross3$7(sub3$4(b, a2), sub3$4(d2, a2));
11875
+ const n = cross3$7(sub3$5(b, a2), sub3$5(d2, a2));
11837
11876
  const len2 = Math.hypot(n[0], n[1], n[2]);
11838
11877
  if (len2 < 1e-12) continue;
11839
11878
  const deviation = Math.abs((n[0] * (c2[0] - a2[0]) + n[1] * (c2[1] - a2[1]) + n[2] * (c2[2] - a2[2])) / len2);
@@ -12109,15 +12148,15 @@ function buildSpanRows(rings, heights) {
12109
12148
  function stationTangent(stations, t, i, j) {
12110
12149
  const R = stations.length;
12111
12150
  if (i === 0) {
12112
- return scale3$1(sub3$4(stations[1][j], stations[0][j]), 1 / (t[1] - t[0]));
12151
+ return scale3$1(sub3$5(stations[1][j], stations[0][j]), 1 / (t[1] - t[0]));
12113
12152
  }
12114
12153
  if (i === R - 1) {
12115
- return scale3$1(sub3$4(stations[R - 1][j], stations[R - 2][j]), 1 / (t[R - 1] - t[R - 2]));
12154
+ return scale3$1(sub3$5(stations[R - 1][j], stations[R - 2][j]), 1 / (t[R - 1] - t[R - 2]));
12116
12155
  }
12117
12156
  const hPrev = t[i] - t[i - 1];
12118
12157
  const hNext = t[i + 1] - t[i];
12119
- const dPrev = scale3$1(sub3$4(stations[i][j], stations[i - 1][j]), 1 / hPrev);
12120
- const dNext = scale3$1(sub3$4(stations[i + 1][j], stations[i][j]), 1 / hNext);
12158
+ const dPrev = scale3$1(sub3$5(stations[i][j], stations[i - 1][j]), 1 / hPrev);
12159
+ const dNext = scale3$1(sub3$5(stations[i + 1][j], stations[i][j]), 1 / hNext);
12121
12160
  return scale3$1(add3$1(scale3$1(dPrev, hNext), scale3$1(dNext, hPrev)), 1 / (hPrev + hNext));
12122
12161
  }
12123
12162
  function hermite(p0, m0, p1, m1, h, u2) {
@@ -12173,12 +12212,12 @@ function stitchSingleLoopLoft(loops, heights, wasm, options) {
12173
12212
  const curr = points[j];
12174
12213
  const next = points[(j + 1) % N];
12175
12214
  if (cornerSet.has(j)) {
12176
- const nFwd = surfaceNormal(sub3$4(next, curr), tangents[j]);
12177
- const nBwd = surfaceNormal(sub3$4(curr, prev), tangents[j]);
12215
+ const nFwd = surfaceNormal(sub3$5(next, curr), tangents[j]);
12216
+ const nBwd = surfaceNormal(sub3$5(curr, prev), tangents[j]);
12178
12217
  fwd[j] = pushVert(curr, nFwd);
12179
12218
  bwd[j] = pushVert(curr, nBwd);
12180
12219
  } else {
12181
- const idx = pushVert(curr, surfaceNormal(sub3$4(next, prev), tangents[j]));
12220
+ const idx = pushVert(curr, surfaceNormal(sub3$5(next, prev), tangents[j]));
12182
12221
  fwd[j] = idx;
12183
12222
  bwd[j] = idx;
12184
12223
  }
@@ -12239,7 +12278,7 @@ function surfaceNormal(chord, span) {
12239
12278
  }
12240
12279
  return [n[0] / len2, n[1] / len2, n[2] / len2];
12241
12280
  }
12242
- function sub3$4(a2, b) {
12281
+ function sub3$5(a2, b) {
12243
12282
  return [a2[0] - b[0], a2[1] - b[1], a2[2] - b[2]];
12244
12283
  }
12245
12284
  function add3$1(a2, b) {
@@ -12265,7 +12304,7 @@ let _wasm = null;
12265
12304
  async function initManifoldWasm() {
12266
12305
  if (_wasm) return _wasm;
12267
12306
  performance.mark("manifold:start");
12268
- const Module = (await import("./manifold-5PP1eGLN.js")).default;
12307
+ const Module = (await import("./manifold-Crd_F2qx.js")).default;
12269
12308
  performance.mark("manifold:imported");
12270
12309
  const wasm = await Module();
12271
12310
  wasm.setup();
@@ -12890,6 +12929,20 @@ function fromSlicesSingleSliceHalfExtentForManifold(plan) {
12890
12929
  }
12891
12930
  return Math.max(1, radius + maxOffsetMagnitude + plan.boundsPadding + plan.edgeLength * 3);
12892
12931
  }
12932
+ const BOOLEAN_OPERAND_NORMAL_SHARP_ANGLE_DEG = 60;
12933
+ function promoteBooleanOperandNormals(shapes) {
12934
+ let maxExtra = 0;
12935
+ for (const shape of shapes) maxExtra = Math.max(maxExtra, shape.numProp());
12936
+ if (maxExtra < 3) return { operands: shapes, created: [] };
12937
+ const created = [];
12938
+ const operands = shapes.map((shape) => {
12939
+ if (shape.numProp() >= 3) return shape;
12940
+ const promoted = shape.calculateNormals(0, BOOLEAN_OPERAND_NORMAL_SHARP_ANGLE_DEG);
12941
+ created.push(promoted);
12942
+ return promoted;
12943
+ });
12944
+ return { operands, created };
12945
+ }
12893
12946
  function lowerShapeBooleanCompilePlan(plan, wasm) {
12894
12947
  const shapes = plan.shapes.map((shape) => lowerShapeCompilePlanToManifold(shape, wasm));
12895
12948
  if (shapes.length === 0) {
@@ -12898,16 +12951,18 @@ function lowerShapeBooleanCompilePlan(plan, wasm) {
12898
12951
  if (shapes.length === 1) {
12899
12952
  return shapes[0];
12900
12953
  }
12954
+ const { operands, created } = promoteBooleanOperandNormals(shapes);
12901
12955
  try {
12902
12956
  switch (plan.op) {
12903
12957
  case "union":
12904
- return wasm.Manifold.union(shapes);
12958
+ return wasm.Manifold.union(operands);
12905
12959
  case "difference":
12906
- return wasm.Manifold.difference(shapes);
12960
+ return wasm.Manifold.difference(operands);
12907
12961
  case "intersection":
12908
- return wasm.Manifold.intersection(shapes);
12962
+ return wasm.Manifold.intersection(operands);
12909
12963
  }
12910
12964
  } finally {
12965
+ disposeWasmObjects(created);
12911
12966
  disposeWasmObjects(shapes);
12912
12967
  }
12913
12968
  }
@@ -14257,12 +14312,16 @@ function lowerNurbsSurfaceToManifold(plan, wasm) {
14257
14312
  const { positions, normals, indices } = surface.tessellate(res, res);
14258
14313
  const thickness = plan.thickness;
14259
14314
  const numVerts = positions.length;
14260
- const allPositions = [];
14261
- for (const [x2, y2, z2] of positions) allPositions.push(x2, y2, z2);
14315
+ const vertProps = [];
14316
+ for (let i = 0; i < numVerts; i++) {
14317
+ const [x2, y2, z2] = positions[i];
14318
+ const [nx, ny, nz] = normals[i];
14319
+ vertProps.push(x2, y2, z2, nx, ny, nz);
14320
+ }
14262
14321
  for (let i = 0; i < numVerts; i++) {
14263
14322
  const [x2, y2, z2] = positions[i];
14264
14323
  const [nx, ny, nz] = normals[i];
14265
- allPositions.push(x2 - nx * thickness, y2 - ny * thickness, z2 - nz * thickness);
14324
+ vertProps.push(x2 - nx * thickness, y2 - ny * thickness, z2 - nz * thickness, -nx, -ny, -nz);
14266
14325
  }
14267
14326
  const allIndices = [];
14268
14327
  for (const idx of indices) allIndices.push(idx);
@@ -14287,11 +14346,12 @@ function lowerNurbsSurfaceToManifold(plan, wasm) {
14287
14346
  allIndices.push(c2, c2 + numVerts, d2, d2, c2 + numVerts, d2 + numVerts);
14288
14347
  }
14289
14348
  const mesh = new wasm.Mesh({
14290
- numProp: 3,
14291
- vertProperties: new Float32Array(allPositions),
14349
+ numProp: 6,
14350
+ vertProperties: new Float32Array(vertProps),
14292
14351
  triVerts: new Uint32Array(allIndices)
14293
14352
  });
14294
14353
  try {
14354
+ mesh.merge();
14295
14355
  return new wasm.Manifold(mesh);
14296
14356
  } finally {
14297
14357
  disposeWasmObject(mesh);
@@ -30984,14 +31044,14 @@ function cross$6(a2, b) {
30984
31044
  function isFiniteNumber$1(value) {
30985
31045
  return typeof value === "number" && Number.isFinite(value);
30986
31046
  }
30987
- function isVec3$2(value) {
31047
+ function isVec3$3(value) {
30988
31048
  return Array.isArray(value) && value.length === 3 && value.every(isFiniteNumber$1);
30989
31049
  }
30990
31050
  function isVec2(value) {
30991
31051
  return Array.isArray(value) && value.length === 2 && value.every(isFiniteNumber$1);
30992
31052
  }
30993
31053
  function isRuledRails(value) {
30994
- return Array.isArray(value) && value.length === 2 && value.every((rail2) => Array.isArray(rail2) && rail2.length === 2 && rail2.every(isVec3$2));
31054
+ return Array.isArray(value) && value.length === 2 && value.every((rail2) => Array.isArray(rail2) && rail2.length === 2 && rail2.every(isVec3$3));
30995
31055
  }
30996
31056
  function axisSpan(face, axis) {
30997
31057
  if (face.vertices.length === 0) return 0;
@@ -31008,14 +31068,14 @@ function isRationalWeights(weights) {
31008
31068
  return Boolean(weights == null ? void 0 : weights.some((row) => row.some((weight) => Math.abs(weight - 1) > 1e-9)));
31009
31069
  }
31010
31070
  function explicitGeometrySurface(geometry, face) {
31011
- if ("Plane" in geometry && isVec3$2(geometry.Plane.normal)) {
31071
+ if ("Plane" in geometry && isVec3$3(geometry.Plane.normal)) {
31012
31072
  return { kind: "plane", normal: normalizeVec3$3(geometry.Plane.normal) };
31013
31073
  }
31014
31074
  if ("AnalyticCylinder" in geometry) {
31015
31075
  const origin = geometry.AnalyticCylinder.axis_origin;
31016
31076
  const axis = geometry.AnalyticCylinder.axis_direction;
31017
31077
  const radius = geometry.AnalyticCylinder.radius;
31018
- if (isVec3$2(origin) && isVec3$2(axis) && isFiniteNumber$1(radius) && radius > 0) {
31078
+ if (isVec3$3(origin) && isVec3$3(axis) && isFiniteNumber$1(radius) && radius > 0) {
31019
31079
  const normalizedAxis = normalizeVec3$3(axis);
31020
31080
  return { kind: "cylinder", origin, axis: normalizedAxis, radius, height: axisSpan(face, normalizedAxis) };
31021
31081
  }
@@ -31025,7 +31085,7 @@ function explicitGeometrySurface(geometry, face) {
31025
31085
  const axis = geometry.AnalyticCone.axis_direction;
31026
31086
  const radiusBottom = geometry.AnalyticCone.radius_start;
31027
31087
  const radiusTop = geometry.AnalyticCone.radius_end;
31028
- if (isVec3$2(origin) && isVec3$2(axis) && isFiniteNumber$1(radiusBottom) && isFiniteNumber$1(radiusTop)) {
31088
+ if (isVec3$3(origin) && isVec3$3(axis) && isFiniteNumber$1(radiusBottom) && isFiniteNumber$1(radiusTop)) {
31029
31089
  const normalizedAxis = normalizeVec3$3(axis);
31030
31090
  return {
31031
31091
  kind: "cone",
@@ -31040,7 +31100,7 @@ function explicitGeometrySurface(geometry, face) {
31040
31100
  if ("AnalyticSphere" in geometry) {
31041
31101
  const center = geometry.AnalyticSphere.center;
31042
31102
  const radius = geometry.AnalyticSphere.radius;
31043
- if (isVec3$2(center) && isFiniteNumber$1(radius) && radius > 0) {
31103
+ if (isVec3$3(center) && isFiniteNumber$1(radius) && radius > 0) {
31044
31104
  return { kind: "sphere", center, radius };
31045
31105
  }
31046
31106
  }
@@ -31049,7 +31109,7 @@ function explicitGeometrySurface(geometry, face) {
31049
31109
  const axis = geometry.AnalyticTorus.axis;
31050
31110
  const majorRadius = geometry.AnalyticTorus.major_radius;
31051
31111
  const minorRadius = geometry.AnalyticTorus.minor_radius;
31052
- if (isVec3$2(center) && isVec3$2(axis) && isFiniteNumber$1(majorRadius) && isFiniteNumber$1(minorRadius) && majorRadius > 0 && minorRadius > 0) {
31112
+ if (isVec3$3(center) && isVec3$3(axis) && isFiniteNumber$1(majorRadius) && isFiniteNumber$1(minorRadius) && majorRadius > 0 && minorRadius > 0) {
31053
31113
  return { kind: "torus", center, axis: normalizeVec3$3(axis), majorRadius, minorRadius };
31054
31114
  }
31055
31115
  }
@@ -31147,7 +31207,7 @@ function explicitEdgeCurve(geometry, faceName) {
31147
31207
  const center = (_a3 = geometry.CircularArc) == null ? void 0 : _a3.center;
31148
31208
  const axis = (_b3 = geometry.CircularArc) == null ? void 0 : _b3.axis;
31149
31209
  const radius = (_c2 = geometry.CircularArc) == null ? void 0 : _c2.radius;
31150
- if (center !== void 0 && axis !== void 0 && radius !== void 0 && isVec3$2(center) && isVec3$2(axis) && isFiniteNumber$1(radius) && radius > 0) {
31210
+ if (center !== void 0 && axis !== void 0 && radius !== void 0 && isVec3$3(center) && isVec3$3(axis) && isFiniteNumber$1(radius) && radius > 0) {
31151
31211
  return {
31152
31212
  kind: "circle",
31153
31213
  center,
@@ -31158,7 +31218,7 @@ function explicitEdgeCurve(geometry, faceName) {
31158
31218
  }
31159
31219
  const start = (_d2 = geometry.Line) == null ? void 0 : _d2.start;
31160
31220
  const end = (_e2 = geometry.Line) == null ? void 0 : _e2.end;
31161
- if (isVec3$2(start) && isVec3$2(end)) return makeLineEdgeCurve(start, end, faceName);
31221
+ if (isVec3$3(start) && isVec3$3(end)) return makeLineEdgeCurve(start, end, faceName);
31162
31222
  const polyline = geometry.PolylineUv;
31163
31223
  if (polyline && Array.isArray(polyline.points)) {
31164
31224
  const points = polyline.points.filter(isVec2);
@@ -31503,7 +31563,7 @@ function topologyPayloadToTopology(payload) {
31503
31563
  if (explicitEdge.visual === false) continue;
31504
31564
  const start = explicitVertices[explicitEdge.vertices[0]];
31505
31565
  const end = explicitVertices[explicitEdge.vertices[1]];
31506
- if (!isVec3$2(start) || !isVec3$2(end)) continue;
31566
+ if (!isVec3$3(start) || !isVec3$3(end)) continue;
31507
31567
  const key = edgeKey(start, end);
31508
31568
  if (seenEdges.has(key)) continue;
31509
31569
  seenEdges.set(key, edges.size);
@@ -31667,6 +31727,7 @@ const _TruckShapeBackend = class _TruckShapeBackend {
31667
31727
  const payload = JSON.parse(getTruckGeometryWasm().geometry_mesh(this.getLiveHandle("getMesh()")));
31668
31728
  const numTri = payload.triangles.length / 3;
31669
31729
  const numVert = payload.positions.length / 3;
31730
+ const cornerNormals = payload.normals && payload.normals.length === numTri * 9 ? new Float32Array(payload.normals) : void 0;
31670
31731
  this.resource.mesh = {
31671
31732
  numProp: 3,
31672
31733
  numTri,
@@ -31680,7 +31741,8 @@ const _TruckShapeBackend = class _TruckShapeBackend {
31680
31741
  runTransform: new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0]),
31681
31742
  faceID: new Int32Array(payload.face_ids),
31682
31743
  faceIdNames: payload.face_id_names ?? [],
31683
- halfedgeTangent: new Float32Array(0)
31744
+ halfedgeTangent: new Float32Array(0),
31745
+ cornerNormals
31684
31746
  };
31685
31747
  return this.resource.mesh;
31686
31748
  }
@@ -32898,6 +32960,16 @@ function boundsInteriorOverlap(a2, b) {
32898
32960
  const tolerance = 1e-8;
32899
32961
  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;
32900
32962
  }
32963
+ function boundsFaceOrInteriorOverlap(a2, b) {
32964
+ const tolerance = 1e-8;
32965
+ const overlaps = [
32966
+ Math.min(a2.max[0], b.max[0]) - Math.max(a2.min[0], b.min[0]),
32967
+ Math.min(a2.max[1], b.max[1]) - Math.max(a2.min[1], b.min[1]),
32968
+ Math.min(a2.max[2], b.max[2]) - Math.max(a2.min[2], b.min[2])
32969
+ ];
32970
+ if (overlaps.some((overlap) => overlap < -tolerance)) return false;
32971
+ return overlaps.filter((overlap) => overlap <= tolerance).length <= 1;
32972
+ }
32901
32973
  function boundsFromPoints(points) {
32902
32974
  const first = points[0];
32903
32975
  if (!first) return null;
@@ -32980,16 +33052,16 @@ function shapePlanBounds(plan) {
32980
33052
  return null;
32981
33053
  }
32982
33054
  }
32983
- function hasPairwiseInteriorBoundsOverlap(shapes) {
33055
+ function hasPairwiseFaceOrInteriorBoundsOverlap(shapes) {
32984
33056
  const bounds = shapes.map((shape) => shape.boundingBox());
32985
33057
  for (let i = 0; i < bounds.length; i++) {
32986
33058
  for (let j = i + 1; j < bounds.length; j++) {
32987
- if (boundsInteriorOverlap(bounds[i], bounds[j])) return true;
33059
+ if (boundsFaceOrInteriorOverlap(bounds[i], bounds[j])) return true;
32988
33060
  }
32989
33061
  }
32990
33062
  return false;
32991
33063
  }
32992
- function interiorOverlapComponents(shapes) {
33064
+ function faceOrInteriorOverlapComponents(shapes) {
32993
33065
  const bounds = shapes.map((shape) => shape.boundingBox());
32994
33066
  const visited = /* @__PURE__ */ new Set();
32995
33067
  const components = [];
@@ -33003,7 +33075,7 @@ function interiorOverlapComponents(shapes) {
33003
33075
  component.push(current);
33004
33076
  for (let next = 0; next < shapes.length; next++) {
33005
33077
  if (visited.has(next)) continue;
33006
- if (boundsInteriorOverlap(bounds[current], bounds[next])) {
33078
+ if (boundsFaceOrInteriorOverlap(bounds[current], bounds[next])) {
33007
33079
  visited.add(next);
33008
33080
  queue.push(next);
33009
33081
  }
@@ -33048,10 +33120,10 @@ function lowerGenericBooleanPlan(plan) {
33048
33120
  let returned = null;
33049
33121
  try {
33050
33122
  if (plan.op === "union") {
33051
- if (!hasPairwiseInteriorBoundsOverlap(shapes)) {
33123
+ if (!hasPairwiseFaceOrInteriorBoundsOverlap(shapes)) {
33052
33124
  return null;
33053
33125
  }
33054
- const components = interiorOverlapComponents(shapes);
33126
+ const components = faceOrInteriorOverlapComponents(shapes);
33055
33127
  if (components.length > 1) {
33056
33128
  returned = lowerClusteredUnion(shapes, components);
33057
33129
  return returned;
@@ -33581,9 +33653,13 @@ function shapeHasClosedNativeTopology(shape) {
33581
33653
  function normalizeTruckShapeForBooleanInput(shape) {
33582
33654
  if (shapeHasClosedNativeTopology(shape)) return shape;
33583
33655
  if (!meshHasRawBoundaryEdges(shape.getMesh())) return shape;
33584
- const normalized = normalizeFacetedTruckShape(shape);
33585
- disposeShapeBackend(shape);
33586
- return normalized;
33656
+ try {
33657
+ const normalized = normalizeFacetedTruckShape(shape);
33658
+ disposeShapeBackend(shape);
33659
+ return normalized;
33660
+ } catch {
33661
+ return shape;
33662
+ }
33587
33663
  }
33588
33664
  function lowerSdfPlan(plan) {
33589
33665
  if (getUnsupportedSdfProgramReason(plan.tree) === void 0) {
@@ -35467,7 +35543,7 @@ function circleProfilePlan(radius, segments) {
35467
35543
  }
35468
35544
  function annulusProfilePlan(outerRadius, innerRadius, segments) {
35469
35545
  const outer = Math.abs(outerRadius);
35470
- const inner = Math.abs(innerRadius);
35546
+ const inner = Math.max(0, innerRadius);
35471
35547
  if (outer <= EXACT_PROFILE_EPS) return emptyProfilePlan();
35472
35548
  if (inner <= EXACT_PROFILE_EPS) return circleProfilePlan(outer, segments);
35473
35549
  return {
@@ -40405,11 +40481,11 @@ function requireFiniteVec3$3(v, label) {
40405
40481
  }
40406
40482
  return [x2, y2, z2];
40407
40483
  }
40408
- function len3$2(v) {
40484
+ function len3$3(v) {
40409
40485
  return Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
40410
40486
  }
40411
40487
  function normalize3$2(v) {
40412
- const l = len3$2(v);
40488
+ const l = len3$3(v);
40413
40489
  if (l < 1e-10) throw new Error("Cannot normalize zero-length vector");
40414
40490
  return [v[0] / l, v[1] / l, v[2] / l];
40415
40491
  }
@@ -40419,7 +40495,7 @@ function dot3$5(a2, b) {
40419
40495
  function cross3$3(a2, b) {
40420
40496
  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]];
40421
40497
  }
40422
- function sub3$3(a2, b) {
40498
+ function sub3$4(a2, b) {
40423
40499
  return [a2[0] - b[0], a2[1] - b[1], a2[2] - b[2]];
40424
40500
  }
40425
40501
  function negate3$1(v) {
@@ -40442,7 +40518,7 @@ function normalizePortInput(input) {
40442
40518
  const end = requireFiniteVec3$3(input.end, "port end");
40443
40519
  origin = [(start[0] + end[0]) / 2, (start[1] + end[1]) / 2, (start[2] + end[2]) / 2];
40444
40520
  const dir = [end[0] - start[0], end[1] - start[1], end[2] - start[2]];
40445
- const dirLen = len3$2(dir);
40521
+ const dirLen = len3$3(dir);
40446
40522
  if (dirLen < 1e-10) {
40447
40523
  throw new Error("Port start and end must not be the same point");
40448
40524
  }
@@ -40453,7 +40529,7 @@ function normalizePortInput(input) {
40453
40529
  } else if (hasOriginAxis) {
40454
40530
  origin = requireFiniteVec3$3(input.origin, "port origin");
40455
40531
  const rawAxis = requireFiniteVec3$3(input.axis, "port axis");
40456
- if (len3$2(rawAxis) < 1e-10) {
40532
+ if (len3$3(rawAxis) < 1e-10) {
40457
40533
  throw new Error("Port axis must be non-zero");
40458
40534
  }
40459
40535
  axis = normalize3$2(rawAxis);
@@ -40469,12 +40545,12 @@ function normalizePortInput(input) {
40469
40545
  let up;
40470
40546
  if (input.up != null) {
40471
40547
  const rawUp = requireFiniteVec3$3(input.up, "port up");
40472
- if (len3$2(rawUp) < 1e-10) {
40548
+ if (len3$3(rawUp) < 1e-10) {
40473
40549
  throw new Error("Port up vector must be non-zero");
40474
40550
  }
40475
40551
  const proj = dot3$5(rawUp, axis);
40476
40552
  const ortho = [rawUp[0] - proj * axis[0], rawUp[1] - proj * axis[1], rawUp[2] - proj * axis[2]];
40477
- if (len3$2(ortho) < 1e-10) {
40553
+ if (len3$3(ortho) < 1e-10) {
40478
40554
  throw new Error("Port up vector must not be parallel to axis");
40479
40555
  }
40480
40556
  up = normalize3$2(ortho);
@@ -40538,8 +40614,8 @@ function transformPort(port, matrix) {
40538
40614
  const newOrigin = tx.point(port.origin);
40539
40615
  const rawAxis = tx.vector(port.axis);
40540
40616
  const rawUp = tx.vector(port.up);
40541
- const axisLen = len3$2(rawAxis);
40542
- const upLen = len3$2(rawUp);
40617
+ const axisLen = len3$3(rawAxis);
40618
+ const upLen = len3$3(rawUp);
40543
40619
  const out = {
40544
40620
  origin: newOrigin,
40545
40621
  axis: axisLen > 1e-10 ? normalize3$2(rawAxis) : port.axis,
@@ -40591,7 +40667,7 @@ function computeConnectFrame(childBase, childPort, parentPort, _flip, childAlign
40591
40667
  r10 * cI[0] + r11 * cI[1] + r12 * cI[2],
40592
40668
  r20 * cI[0] + r21 * cI[1] + r22 * cI[2]
40593
40669
  ];
40594
- const t = sub3$3(pOrigin, rcI);
40670
+ const t = sub3$4(pOrigin, rcI);
40595
40671
  const frame = Transform.from([r00, r10, r20, 0, r01, r11, r21, 0, r02, r12, r22, 0, t[0], t[1], t[2], 1]);
40596
40672
  const axis = cAxis;
40597
40673
  return { frame, axis };
@@ -40676,18 +40752,18 @@ function validateConnectorMatch(selfName, selfPort, targetName, targetPort, forc
40676
40752
  }
40677
40753
  }
40678
40754
  }
40679
- function len3$1(v) {
40755
+ function len3$2(v) {
40680
40756
  return Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
40681
40757
  }
40682
40758
  function normalize3$1(v) {
40683
- const l = len3$1(v);
40759
+ const l = len3$2(v);
40684
40760
  if (l < 1e-10) throw new Error("Cannot normalize zero-length vector");
40685
40761
  return [v[0] / l, v[1] / l, v[2] / l];
40686
40762
  }
40687
40763
  function cross3$2(a2, b) {
40688
40764
  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]];
40689
40765
  }
40690
- function sub3$2(a2, b) {
40766
+ function sub3$3(a2, b) {
40691
40767
  return [a2[0] - b[0], a2[1] - b[1], a2[2] - b[2]];
40692
40768
  }
40693
40769
  function negate3(v) {
@@ -40710,7 +40786,7 @@ function alignmentMatrix(childOrigin, childAxis, childUp, parentOrigin, parentAx
40710
40786
  r10 * childOrigin[0] + r11 * childOrigin[1] + r12 * childOrigin[2],
40711
40787
  r20 * childOrigin[0] + r21 * childOrigin[1] + r22 * childOrigin[2]
40712
40788
  ];
40713
- const t = sub3$2(parentOrigin, rc);
40789
+ const t = sub3$3(parentOrigin, rc);
40714
40790
  return Transform.from([r00, r10, r20, 0, r01, r11, r21, 0, r02, r12, r22, 0, t[0], t[1], t[2], 1]);
40715
40791
  }
40716
40792
  function computeSinglePairAlignment(childPort, targetPort) {
@@ -40749,8 +40825,8 @@ function computeMultiPairAlignment(pairs, childPorts, targetPorts, tolerance = 0
40749
40825
  [0, 0, 0]
40750
40826
  ];
40751
40827
  for (const p2 of pairs) {
40752
- const s = sub3$2(p2.childOrigin, srcCentroid);
40753
- const t2 = sub3$2(p2.targetOrigin, tgtCentroid);
40828
+ const s = sub3$3(p2.childOrigin, srcCentroid);
40829
+ const t2 = sub3$3(p2.targetOrigin, tgtCentroid);
40754
40830
  for (let i = 0; i < 3; i++) {
40755
40831
  for (let j = 0; j < 3; j++) {
40756
40832
  h[i][j] += s[i] * t2[j];
@@ -40763,7 +40839,7 @@ function computeMultiPairAlignment(pairs, childPorts, targetPorts, tolerance = 0
40763
40839
  R[1][0] * srcCentroid[0] + R[1][1] * srcCentroid[1] + R[1][2] * srcCentroid[2],
40764
40840
  R[2][0] * srcCentroid[0] + R[2][1] * srcCentroid[1] + R[2][2] * srcCentroid[2]
40765
40841
  ];
40766
- const t = sub3$2(tgtCentroid, rSrc);
40842
+ const t = sub3$3(tgtCentroid, rSrc);
40767
40843
  const transform = Transform.from([
40768
40844
  R[0][0],
40769
40845
  R[1][0],
@@ -40785,8 +40861,8 @@ function computeMultiPairAlignment(pairs, childPorts, targetPorts, tolerance = 0
40785
40861
  const residuals = [];
40786
40862
  for (const p2 of pairs) {
40787
40863
  const transformed = transform.point(p2.childOrigin);
40788
- const diff = sub3$2(transformed, p2.targetOrigin);
40789
- residuals.push(len3$1(diff));
40864
+ const diff = sub3$3(transformed, p2.targetOrigin);
40865
+ residuals.push(len3$2(diff));
40790
40866
  }
40791
40867
  const maxResidual = Math.max(...residuals);
40792
40868
  if (maxResidual > tolerance) {
@@ -40986,8 +41062,8 @@ function getConnectorDistance(ports, nameA, nameB) {
40986
41062
  const b = ports[nameB];
40987
41063
  if (!a2) throw new Error(`connectorDistance: unknown connector "${nameA}"`);
40988
41064
  if (!b) throw new Error(`connectorDistance: unknown connector "${nameB}"`);
40989
- const d2 = sub3$2(a2.origin, b.origin);
40990
- return len3$1(d2);
41065
+ const d2 = sub3$3(a2.origin, b.origin);
41066
+ return len3$2(d2);
40991
41067
  }
40992
41068
  function getConnectorMeasurements(ports, name) {
40993
41069
  const p2 = ports[name];
@@ -46096,8 +46172,7 @@ function sculptLook(preset = "gallery") {
46096
46172
  { type: "directional", position: [90, -110, 150], color: "#ffffff", intensity: 1.4 },
46097
46173
  { type: "directional", position: [-120, 70, 80], color: "#dcecff", intensity: 0.65 },
46098
46174
  { type: "hemisphere", skyColor: "#d9edff", groundColor: "#f0e6d4", intensity: 0.45 }
46099
- ],
46100
- postProcessing: { toneMappingExposure: 1.12, vignette: { darkness: 0.16, offset: 0.6 } }
46175
+ ]
46101
46176
  };
46102
46177
  case "candy-shop":
46103
46178
  return {
@@ -46108,8 +46183,7 @@ function sculptLook(preset = "gallery") {
46108
46183
  { type: "point", position: [70, -60, 90], color: "#ff8ac8", intensity: 2.2, distance: 280, decay: 1.2 },
46109
46184
  { type: "point", position: [-85, 80, 70], color: "#70e1ff", intensity: 1.8, distance: 260, decay: 1.4 },
46110
46185
  { type: "directional", position: [30, -80, 140], color: "#fff6dd", intensity: 0.9 }
46111
- ],
46112
- postProcessing: { toneMappingExposure: 1.25, bloom: { intensity: 0.35, threshold: 0.78, radius: 0.45 } }
46186
+ ]
46113
46187
  };
46114
46188
  case "midnight":
46115
46189
  return {
@@ -46121,8 +46195,7 @@ function sculptLook(preset = "gallery") {
46121
46195
  { type: "point", position: [-90, 70, 50], color: "#ff7ac8", intensity: 1.4, distance: 300, decay: 1.3 },
46122
46196
  { type: "directional", position: [30, -30, 160], color: "#d7e9ff", intensity: 0.65 }
46123
46197
  ],
46124
- fog: { color: "#060814", near: 180, far: 520 },
46125
- postProcessing: { toneMappingExposure: 1.35, bloom: { intensity: 0.65, threshold: 0.65, radius: 0.6 } }
46198
+ fog: { color: "#060814", near: 180, far: 520 }
46126
46199
  };
46127
46200
  case "workbench":
46128
46201
  return {
@@ -46133,8 +46206,7 @@ function sculptLook(preset = "gallery") {
46133
46206
  { type: "directional", position: [80, -100, 130], color: "#fff5df", intensity: 1.25 },
46134
46207
  { type: "hemisphere", skyColor: "#eef6ff", groundColor: "#d8d0c0", intensity: 0.35 }
46135
46208
  ],
46136
- ground: { visible: true, color: "#d8d4ca", offset: 1, receiveShadow: true },
46137
- postProcessing: { toneMappingExposure: 1.05 }
46209
+ ground: { visible: true, color: "#d8d4ca", offset: 1, receiveShadow: true }
46138
46210
  };
46139
46211
  default:
46140
46212
  return {
@@ -46145,8 +46217,7 @@ function sculptLook(preset = "gallery") {
46145
46217
  { type: "directional", position: [110, -130, 150], color: "#ffffff", intensity: 1.5 },
46146
46218
  { type: "directional", position: [-90, 80, 90], color: "#b8d8ff", intensity: 0.55 },
46147
46219
  { type: "hemisphere", skyColor: "#ddefff", groundColor: "#e8e2d8", intensity: 0.45 }
46148
- ],
46149
- postProcessing: { toneMappingExposure: 1.18, vignette: { darkness: 0.18, offset: 0.55 } }
46220
+ ]
46150
46221
  };
46151
46222
  }
46152
46223
  }
@@ -47319,7 +47390,7 @@ class Shape {
47319
47390
  *
47320
47391
  * Use `.color()` to set the base diffuse color; `.material()` controls how that color behaves
47321
47392
  * under light (metalness, roughness, clearcoat) and can add emissive glow independent of
47322
- * lighting. Emissive glow pairs naturally with the `postProcessing.bloom` effect in `scene()`.
47393
+ * lighting.
47323
47394
  *
47324
47395
  * **Example**
47325
47396
  *
@@ -49693,14 +49764,14 @@ function dot3$3(a2, b) {
49693
49764
  function cross3$1(a2, b) {
49694
49765
  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]];
49695
49766
  }
49696
- function sub3$1(a2, b) {
49767
+ function sub3$2(a2, b) {
49697
49768
  return [a2[0] - b[0], a2[1] - b[1], a2[2] - b[2]];
49698
49769
  }
49699
- function len3(v) {
49770
+ function len3$1(v) {
49700
49771
  return Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
49701
49772
  }
49702
49773
  function normalize3(v) {
49703
- const l = len3(v);
49774
+ const l = len3$1(v);
49704
49775
  if (l < EPS$8) return [0, 0, 0];
49705
49776
  return [v[0] / l, v[1] / l, v[2] / l];
49706
49777
  }
@@ -49712,7 +49783,7 @@ const alignDef = {
49712
49783
  const faceB = ctx.worldFace(constraint.refB.bodyId, constraint.refB.featureName);
49713
49784
  const n1 = faceA.normal;
49714
49785
  const n2 = faceB.normal;
49715
- const delta = sub3$1(faceB.center, faceA.center);
49786
+ const delta = sub3$2(faceB.center, faceA.center);
49716
49787
  const parallel = dot3$3(n1, n2) - 1;
49717
49788
  const normalDist = dot3$3(delta, n1);
49718
49789
  const cx = n1[1] * n2[2] - n1[2] * n2[1];
@@ -49755,7 +49826,7 @@ const concentricDef = {
49755
49826
  const axisA = ctx.worldAxis(constraint.refA.bodyId, constraint.refA.featureName);
49756
49827
  const axisB = ctx.worldAxis(constraint.refB.bodyId, constraint.refB.featureName);
49757
49828
  const dirCross = cross3$1(axisA.direction, axisB.direction);
49758
- const delta = sub3$1(axisB.origin, axisA.origin);
49829
+ const delta = sub3$2(axisB.origin, axisA.origin);
49759
49830
  const offsetCross = cross3$1(delta, axisA.direction);
49760
49831
  const pickTwo = (v) => {
49761
49832
  const ax = Math.abs(v[0]);
@@ -49779,9 +49850,9 @@ const faceDistanceDef = {
49779
49850
  const distance2 = constraint.value ?? 0;
49780
49851
  const n1 = faceA.normal;
49781
49852
  const n2 = faceB.normal;
49782
- const delta = sub3$1(faceB.center, faceA.center);
49853
+ const delta = sub3$2(faceB.center, faceA.center);
49783
49854
  const antiParallel = dot3$3(n1, n2) + 1;
49784
- const crossMag = len3(cross3$1(n1, n2));
49855
+ const crossMag = len3$1(cross3$1(n1, n2));
49785
49856
  const signedDist = dot3$3(delta, n1) - distance2;
49786
49857
  return [antiParallel, crossMag, signedDist];
49787
49858
  }
@@ -49801,7 +49872,7 @@ const flushDef = {
49801
49872
  const faceB = ctx.worldFace(constraint.refB.bodyId, constraint.refB.featureName);
49802
49873
  const n1 = faceA.normal;
49803
49874
  const n2 = faceB.normal;
49804
- const delta = sub3$1(faceB.center, faceA.center);
49875
+ const delta = sub3$2(faceB.center, faceA.center);
49805
49876
  const antiParallel = dot3$3(n1, n2) + 1;
49806
49877
  const normalDist = dot3$3(delta, n1);
49807
49878
  const cx = n1[1] * n2[2] - n1[2] * n2[1];
@@ -49841,7 +49912,7 @@ const pointOnAxisDef = {
49841
49912
  residual(constraint, ctx) {
49842
49913
  const point2 = ctx.worldPoint(constraint.refA.bodyId, constraint.refA.featureName);
49843
49914
  const axis = ctx.worldAxis(constraint.refB.bodyId, constraint.refB.featureName);
49844
- const delta = sub3$1(point2, axis.origin);
49915
+ const delta = sub3$2(point2, axis.origin);
49845
49916
  const c2 = cross3$1(delta, axis.direction);
49846
49917
  const ax = Math.abs(c2[0]);
49847
49918
  const ay = Math.abs(c2[1]);
@@ -49857,7 +49928,7 @@ const pointOnFaceDef = {
49857
49928
  residual(constraint, ctx) {
49858
49929
  const point2 = ctx.worldPoint(constraint.refA.bodyId, constraint.refA.featureName);
49859
49930
  const face = ctx.worldFace(constraint.refB.bodyId, constraint.refB.featureName);
49860
- const delta = sub3$1(point2, face.center);
49931
+ const delta = sub3$2(point2, face.center);
49861
49932
  return [dot3$3(delta, face.normal)];
49862
49933
  }
49863
49934
  };
@@ -50406,7 +50477,7 @@ function runWithForgeValidationPolicy(policy, fn) {
50406
50477
  }
50407
50478
  let _collected$8 = null;
50408
50479
  const isFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value);
50409
- const isVec3$1 = (value) => Array.isArray(value) && value.length === 3 && isFiniteNumber(value[0]) && isFiniteNumber(value[1]) && isFiniteNumber(value[2]);
50480
+ const isVec3$2 = (value) => Array.isArray(value) && value.length === 3 && isFiniteNumber(value[0]) && isFiniteNumber(value[1]) && isFiniteNumber(value[2]);
50410
50481
  const normalizeAxis = (axis) => {
50411
50482
  const len2 = Math.hypot(axis[0], axis[1], axis[2]);
50412
50483
  if (len2 <= 1e-8) throw new Error("jointsView joint axis must be non-zero");
@@ -50441,10 +50512,10 @@ const normalizeJoint = (joint2) => {
50441
50512
  throw new Error(`jointsView joint "${name}" type must be "revolute" or "prismatic"`);
50442
50513
  }
50443
50514
  const axisRaw = joint2.axis ?? [0, 0, 1];
50444
- if (!isVec3$1(axisRaw)) throw new Error(`jointsView joint "${name}" axis must be [x, y, z]`);
50515
+ if (!isVec3$2(axisRaw)) throw new Error(`jointsView joint "${name}" axis must be [x, y, z]`);
50445
50516
  const axis = normalizeAxis([axisRaw[0], axisRaw[1], axisRaw[2]]);
50446
50517
  const pivotRaw = joint2.pivot ?? [0, 0, 0];
50447
- if (!isVec3$1(pivotRaw)) throw new Error(`jointsView joint "${name}" pivot must be [x, y, z]`);
50518
+ if (!isVec3$2(pivotRaw)) throw new Error(`jointsView joint "${name}" pivot must be [x, y, z]`);
50448
50519
  const pivot = [pivotRaw[0], pivotRaw[1], pivotRaw[2]];
50449
50520
  if (joint2.min !== void 0 && !isFiniteNumber(joint2.min)) {
50450
50521
  throw new Error(`jointsView joint "${name}" min must be a finite number`);
@@ -51363,7 +51434,7 @@ function deriveExplodeHintsFromMates(constraints, result, bodies, ctx) {
51363
51434
  const posA = result.transforms.get(c2.refA.bodyId);
51364
51435
  const posB = result.transforms.get(c2.refB.bodyId);
51365
51436
  if (posA && posB) {
51366
- const raw = sub3$1(bodyA.grounded ? posB.position : posA.position, bodyA.grounded ? posA.position : posB.position);
51437
+ const raw = sub3$2(bodyA.grounded ? posB.position : posA.position, bodyA.grounded ? posA.position : posB.position);
51367
51438
  dir = normalize3(raw);
51368
51439
  }
51369
51440
  break;
@@ -51889,8 +51960,8 @@ class Assembly {
51889
51960
  *
51890
51961
  * Use this after adding physical parts and joints. Robot-body profiles require
51891
51962
  * `rootPart`; asset profiles can describe one-part or multi-part physical assets.
51892
- * URDF/SDF exporters and `forgecad check simready` read this contract directly,
51893
- * so model files no longer need a separate `robotExport(...)` side effect.
51963
+ * URDF/SDF/MJCF/USD exporters and `forgecad check simready` read this
51964
+ * contract directly from the returned assembly.
51894
51965
  *
51895
51966
  * @category Assembly
51896
51967
  */
@@ -60937,7 +61008,7 @@ function beltDrive(options) {
60937
61008
  }
60938
61009
  const GEAR_META_KEY = Symbol.for("forgecad.library.gearMeta");
60939
61010
  const EPSILON$1 = 1e-9;
60940
- function clamp01$1(value) {
61011
+ function clamp01$2(value) {
60941
61012
  return Math.max(-1, Math.min(1, value));
60942
61013
  }
60943
61014
  function isFinitePositive(value) {
@@ -60957,7 +61028,7 @@ function addArcPoints(target, radius, startAngle, endAngle, steps, includeStart
60957
61028
  }
60958
61029
  }
60959
61030
  function flankAngleAtRadius(radius, baseRadius, halfThicknessAtPitch, pressureAngleRad) {
60960
- const alphaAtRadius = Math.acos(clamp01$1(baseRadius / Math.max(radius, baseRadius)));
61031
+ const alphaAtRadius = Math.acos(clamp01$2(baseRadius / Math.max(radius, baseRadius)));
60961
61032
  return halfThicknessAtPitch + involuteFn(pressureAngleRad) - involuteFn(alphaAtRadius);
60962
61033
  }
60963
61034
  function addRootFilletPoints(target, rootRadius, filletRadius, flankAngle, sign2, fromFlank, steps) {
@@ -62077,7 +62148,7 @@ function gearPair(options) {
62077
62148
  message: `Center distance ${centerDistance.toFixed(4)} exceeds addendum reach ${addendumReach.toFixed(4)} (no mesh contact)`
62078
62149
  });
62079
62150
  }
62080
- const cosWorking = clamp01$1(baseSum / Math.max(centerDistance, EPSILON$1));
62151
+ const cosWorking = clamp01$2(baseSum / Math.max(centerDistance, EPSILON$1));
62081
62152
  const alphaWorking = Math.acos(cosWorking);
62082
62153
  const basePitch = Math.PI * module * Math.cos(alpha);
62083
62154
  const pathLength = Math.sqrt(Math.max(0, pinion.meta.outerRadius ** 2 - pinion.meta.baseRadius ** 2)) + Math.sqrt(Math.max(0, gear.meta.outerRadius ** 2 - gear.meta.baseRadius ** 2)) - centerDistance * Math.sin(alphaWorking);
@@ -62987,7 +63058,8 @@ class FrozenShape extends Shape {
62987
63058
  const EDGE_THRESHOLD_DOT = Math.cos(Math.PI / 180);
62988
63059
  const SMOOTH_THRESHOLD_DOT = Math.cos(30 * Math.PI / 180);
62989
63060
  function computeGeometryArrays(mesh, options = {}) {
62990
- const { numProp, numTri: triCount, triVerts, vertProperties, vertNormals } = mesh;
63061
+ const { numProp, numTri: triCount, triVerts, vertProperties, vertNormals, cornerNormals } = mesh;
63062
+ const useCornerNormals = !!cornerNormals && cornerNormals.length === triCount * 9;
62991
63063
  const positions = new Float32Array(triCount * 9);
62992
63064
  const normals = new Float32Array(triCount * 9);
62993
63065
  const faceNx = new Float32Array(triCount);
@@ -63019,7 +63091,9 @@ function computeGeometryArrays(mesh, options = {}) {
63019
63091
  positions[o + 6] = cx;
63020
63092
  positions[o + 7] = cy;
63021
63093
  positions[o + 8] = cz;
63022
- if (vertNormals) {
63094
+ if (useCornerNormals) {
63095
+ for (let k2 = 0; k2 < 9; k2++) normals[o + k2] = cornerNormals[o + k2];
63096
+ } else if (vertNormals) {
63023
63097
  normals[o] = vertNormals[i0 * 3];
63024
63098
  normals[o + 1] = vertNormals[i0 * 3 + 1];
63025
63099
  normals[o + 2] = vertNormals[i0 * 3 + 2];
@@ -63062,7 +63136,7 @@ function computeGeometryArrays(mesh, options = {}) {
63062
63136
  faceNy[t] = fny;
63063
63137
  faceNz[t] = fnz;
63064
63138
  }
63065
- if (!vertNormals && numProp < 6 && triCount > 0) {
63139
+ if (!useCornerNormals && !vertNormals && numProp < 6 && triCount > 0) {
63066
63140
  computeAutoSmoothNormals(
63067
63141
  triVerts,
63068
63142
  vertProperties,
@@ -63249,7 +63323,8 @@ function shapeToGeometryFallback(shape) {
63249
63323
  vertProperties: mesh.vertProperties,
63250
63324
  mergeFromVert: mesh.mergeFromVert,
63251
63325
  mergeToVert: mesh.mergeToVert,
63252
- vertNormals
63326
+ vertNormals,
63327
+ cornerNormals: mesh.cornerNormals && mesh.cornerNormals.length === mesh.numTri * 9 ? mesh.cornerNormals : void 0
63253
63328
  });
63254
63329
  const solid = new BufferGeometry();
63255
63330
  solid.setAttribute("position", new BufferAttribute(positions, 3));
@@ -63279,26 +63354,26 @@ function getPendingShapeHighlights() {
63279
63354
  function resetPendingShapeHighlights() {
63280
63355
  pendingShapeHighlights = [];
63281
63356
  }
63282
- function isVec3(v) {
63357
+ function isVec3$1(v) {
63283
63358
  return Array.isArray(v) && v.length === 3 && typeof v[0] === "number" && typeof v[1] === "number" && typeof v[2] === "number";
63284
63359
  }
63285
63360
  function isEdgePair(v) {
63286
- return Array.isArray(v) && v.length === 2 && isVec3(v[0]) && isVec3(v[1]);
63361
+ return Array.isArray(v) && v.length === 2 && isVec3$1(v[0]) && isVec3$1(v[1]);
63287
63362
  }
63288
63363
  function isPlaneSpec(v) {
63289
63364
  if (typeof v !== "object" || v === null) return false;
63290
63365
  const obj = v;
63291
- return isVec3(obj.normal) && (typeof obj.offset === "number" || isVec3(obj.point));
63366
+ return isVec3$1(obj.normal) && (typeof obj.offset === "number" || isVec3$1(obj.point));
63292
63367
  }
63293
63368
  function isFaceRef$1(v) {
63294
63369
  if (typeof v !== "object" || v === null) return false;
63295
63370
  const obj = v;
63296
- return isVec3(obj.normal) && isVec3(obj.center) && typeof obj.name === "string";
63371
+ return isVec3$1(obj.normal) && isVec3$1(obj.center) && typeof obj.name === "string";
63297
63372
  }
63298
63373
  function isEdgeRef(v) {
63299
63374
  if (typeof v !== "object" || v === null) return false;
63300
63375
  const obj = v;
63301
- return isVec3(obj.start) && isVec3(obj.end) && typeof obj.name === "string";
63376
+ return isVec3$1(obj.start) && isVec3$1(obj.end) && typeof obj.name === "string";
63302
63377
  }
63303
63378
  function requireFiniteVec3(v, name) {
63304
63379
  for (let i = 0; i < 3; i++) {
@@ -63337,7 +63412,7 @@ function highlight(target, opts) {
63337
63412
  });
63338
63413
  return;
63339
63414
  }
63340
- if (isVec3(target)) {
63415
+ if (isVec3$1(target)) {
63341
63416
  requireFiniteVec3(target, "point");
63342
63417
  collectedDebugHighlights3D.push({
63343
63418
  kind: "point",
@@ -64381,7 +64456,7 @@ const PRESETS = {
64381
64456
  }
64382
64457
  };
64383
64458
  new Set(Object.keys(PRESETS));
64384
- function clamp01(value) {
64459
+ function clamp01$1(value) {
64385
64460
  return Math.max(0, Math.min(1, value));
64386
64461
  }
64387
64462
  function applyMaterial(shape, preset) {
@@ -64403,7 +64478,7 @@ const materials = {
64403
64478
  clearPolycarbonate(options = {}) {
64404
64479
  return {
64405
64480
  color: options.tint ?? "#bdefff",
64406
- material: { opacity: clamp01(options.opacity ?? 0.34), roughness: 0.08, metalness: 0, clearcoat: 1, clearcoatRoughness: 0.03 }
64481
+ material: { opacity: clamp01$1(options.opacity ?? 0.34), roughness: 0.08, metalness: 0, clearcoat: 1, clearcoatRoughness: 0.03 }
64407
64482
  };
64408
64483
  },
64409
64484
  /** Brushed steel-like material for trim, soleplates, and hardware. */
@@ -64880,31 +64955,6 @@ function validateFog(fog, label) {
64880
64955
  if (fog.density !== void 0) out.density = requireFinite$5(fog.density, `${label}.density`);
64881
64956
  return out;
64882
64957
  }
64883
- function validatePostProcessing(pp, label) {
64884
- const out = {};
64885
- if (pp.bloom !== void 0) {
64886
- if (!pp.bloom || typeof pp.bloom !== "object") throw new Error(`${label}.bloom must be an object`);
64887
- out.bloom = {};
64888
- if (pp.bloom.intensity !== void 0) out.bloom.intensity = requireFinite$5(pp.bloom.intensity, `${label}.bloom.intensity`);
64889
- if (pp.bloom.threshold !== void 0) out.bloom.threshold = requireFinite$5(pp.bloom.threshold, `${label}.bloom.threshold`);
64890
- if (pp.bloom.radius !== void 0) out.bloom.radius = requireFinite$5(pp.bloom.radius, `${label}.bloom.radius`);
64891
- }
64892
- if (pp.vignette !== void 0) {
64893
- if (!pp.vignette || typeof pp.vignette !== "object") throw new Error(`${label}.vignette must be an object`);
64894
- out.vignette = {};
64895
- if (pp.vignette.darkness !== void 0) out.vignette.darkness = requireFinite$5(pp.vignette.darkness, `${label}.vignette.darkness`);
64896
- if (pp.vignette.offset !== void 0) out.vignette.offset = requireFinite$5(pp.vignette.offset, `${label}.vignette.offset`);
64897
- }
64898
- if (pp.grain !== void 0) {
64899
- if (!pp.grain || typeof pp.grain !== "object") throw new Error(`${label}.grain must be an object`);
64900
- out.grain = {};
64901
- if (pp.grain.intensity !== void 0) out.grain.intensity = requireFinite$5(pp.grain.intensity, `${label}.grain.intensity`);
64902
- }
64903
- if (pp.toneMappingExposure !== void 0) {
64904
- out.toneMappingExposure = requireFinite$5(pp.toneMappingExposure, `${label}.toneMappingExposure`);
64905
- }
64906
- return out;
64907
- }
64908
64958
  function validateGround(ground, label) {
64909
64959
  const out = {};
64910
64960
  if (ground.visible !== void 0) {
@@ -64986,7 +65036,6 @@ function scene(options) {
64986
65036
  lights: null,
64987
65037
  environment: null,
64988
65038
  fog: null,
64989
- postProcessing: null,
64990
65039
  ground: null,
64991
65040
  capture: null
64992
65041
  };
@@ -65026,12 +65075,9 @@ function scene(options) {
65026
65075
  }
65027
65076
  current.fog = validateFog(options.fog, "scene.fog");
65028
65077
  }
65029
- if (options.postProcessing !== void 0) {
65030
- if (!options.postProcessing || typeof options.postProcessing !== "object") {
65031
- throw new Error("scene.postProcessing must be an object");
65032
- }
65033
- const validated = validatePostProcessing(options.postProcessing, "scene.postProcessing");
65034
- current.postProcessing = current.postProcessing ? { ...current.postProcessing, ...validated } : validated;
65078
+ const disabledPostProcessing = options.postProcessing;
65079
+ if (disabledPostProcessing !== void 0) {
65080
+ console.warn("scene.postProcessing is disabled for now while the browser post-processing path is being rebuilt.");
65035
65081
  }
65036
65082
  if (options.ground !== void 0) {
65037
65083
  if (!options.ground || typeof options.ground !== "object") {
@@ -65191,8 +65237,7 @@ function scenePreset(name) {
65191
65237
  { type: "hemisphere", skyColor: "#ffffff", groundColor: "#d5d9de", intensity: 0.75 },
65192
65238
  { type: "directional", position: [90, -120, 180], color: "#ffffff", intensity: 1.8, castShadow: true },
65193
65239
  { type: "directional", position: [-140, 100, 80], color: "#dfe9ff", intensity: 0.55 }
65194
- ],
65195
- postProcessing: { toneMappingExposure: 1.08 }
65240
+ ]
65196
65241
  });
65197
65242
  return;
65198
65243
  }
@@ -65434,8 +65479,8 @@ function shapeBoundsCenter(shape) {
65434
65479
  ];
65435
65480
  }
65436
65481
  class ProductSurfaceRef {
65437
- constructor(skin, query, name) {
65438
- this.skin = skin;
65482
+ constructor(skin2, query, name) {
65483
+ this.skin = skin2;
65439
65484
  this.query = query;
65440
65485
  this.name = name;
65441
65486
  }
@@ -65952,8 +65997,8 @@ class ProductHandleBuilder {
65952
65997
  }
65953
65998
  }
65954
65999
  class ProductSurfaceBuilder {
65955
- constructor(skin, side) {
65956
- this.skin = skin;
66000
+ constructor(skin2, side) {
66001
+ this.skin = skin2;
65957
66002
  this.side = side;
65958
66003
  }
65959
66004
  /** Create a ref on this skin side. */
@@ -66021,9 +66066,9 @@ class ProductRibbonBuilder {
66021
66066
  * ProductSkin.frame(), so the ribbon bends along the selected side as station width/depth changes.
66022
66067
  * All query path points must stay on one side; split side transitions into separate ribbons.
66023
66068
  */
66024
- on(skin, points, options = {}) {
66069
+ on(skin2, points, options = {}) {
66025
66070
  if (points.length < 2) throw new Error("Product.ribbon().on(skin, points) requires at least two path points");
66026
- this.skinValue = skin;
66071
+ this.skinValue = skin2;
66027
66072
  this.queryPath = resolvePathQueries(points);
66028
66073
  this.refPath = [];
66029
66074
  return this.applyOptions(options);
@@ -66146,7 +66191,7 @@ class ProductRibbonBuilder {
66146
66191
  const localT = scaled - segment;
66147
66192
  return interpolateQuery(this.queryPath[segment], this.queryPath[segment + 1], localT);
66148
66193
  }
66149
- buildSkinGrid(skin, path2) {
66194
+ buildSkinGrid(skin2, path2) {
66150
66195
  if (path2.length < 2) throw new Error("Product.ribbon().on(skin, points) must be called before .build()");
66151
66196
  const side = normalizedSide(path2[0].side);
66152
66197
  if (side === "front" || side === "rear") {
@@ -66165,7 +66210,7 @@ class ProductRibbonBuilder {
66165
66210
  for (let i = 0; i < this.samplesValue; i += 1) {
66166
66211
  const along = this.samplesValue === 1 ? 0 : i / (this.samplesValue - 1);
66167
66212
  const center = this.samplePathQuery(along);
66168
- const station = skin.stationAt(center.v ?? 0.5);
66213
+ const station = skin2.stationAt(center.v ?? 0.5);
66169
66214
  const span = sideSpan(side, station.width, station.depth);
66170
66215
  for (let j = 0; j < this.widthSamplesValue; j += 1) {
66171
66216
  const across = this.widthSamplesValue === 1 ? 0 : j / (this.widthSamplesValue - 1) - 0.5;
@@ -66182,13 +66227,13 @@ class ProductRibbonBuilder {
66182
66227
  u: u2,
66183
66228
  offset: (center.offset ?? 0) + this.offsetValue + this.thicknessValue
66184
66229
  };
66185
- rows[j].push(skin.frame(query).point);
66230
+ rows[j].push(skin2.frame(query).point);
66186
66231
  }
66187
66232
  }
66188
66233
  return {
66189
66234
  grid: rows,
66190
66235
  diagnostics: this.makeDiagnostics({
66191
- skin: skin.name,
66236
+ skin: skin2.name,
66192
66237
  side,
66193
66238
  pathPointCount: path2.length,
66194
66239
  clampedUCount,
@@ -66345,16 +66390,16 @@ const Product = {
66345
66390
  return scaleProfileTo(sketch, width, depth);
66346
66391
  },
66347
66392
  /** Create an ad-hoc ProductSurfaceRef from a skin and side/u/v query. */
66348
- ref(skin, query) {
66349
- return new ProductSurfaceRef(skin, query);
66393
+ ref(skin2, query) {
66394
+ return new ProductSurfaceRef(skin2, query);
66350
66395
  },
66351
66396
  /**
66352
66397
  * Create a fluent surface helper for refs and conformal features on one side of a skin.
66353
66398
  *
66354
66399
  * Equivalent to skin.surface(side), useful when writing in Product.* namespace style.
66355
66400
  */
66356
- surface(skin, side) {
66357
- return skin.surface(side);
66401
+ surface(skin2, side) {
66402
+ return skin2.surface(side);
66358
66403
  },
66359
66404
  /** Start a panel feature builder. */
66360
66405
  panel(name) {
@@ -66823,9 +66868,9 @@ function coordinateOnSide(coordinate, side, label) {
66823
66868
  return { ...coordinate, kind: "productSkin", side };
66824
66869
  }
66825
66870
  class ProductSkinCarrier {
66826
- constructor(skin, name = skin.name, sideValue2, offsetValue = 0) {
66871
+ constructor(skin2, name = skin2.name, sideValue2, offsetValue = 0) {
66827
66872
  __publicField(this, "kind", "productSkin");
66828
- this.skin = skin;
66873
+ this.skin = skin2;
66829
66874
  this.name = name;
66830
66875
  this.sideValue = sideValue2;
66831
66876
  this.offsetValue = offsetValue;
@@ -69759,8 +69804,8 @@ const Carrier = {
69759
69804
  return new PlaneCarrier(name);
69760
69805
  },
69761
69806
  /** Adapt an existing ProductSkin into the general surface-member carrier protocol. */
69762
- productSkin(skin) {
69763
- return new ProductSkinCarrier(skin);
69807
+ productSkin(skin2) {
69808
+ return new ProductSkinCarrier(skin2);
69764
69809
  },
69765
69810
  /** Reserved stub for future parameterized mesh carriers; arbitrary mesh parameterization is not implemented yet. */
69766
69811
  mesh(name) {
@@ -70017,7 +70062,7 @@ function norm(v) {
70017
70062
  if (len2 < 1e-12) return [0, 0, 1];
70018
70063
  return [v[0] / len2, v[1] / len2, v[2] / len2];
70019
70064
  }
70020
- function sub3(a2, b) {
70065
+ function sub3$1(a2, b) {
70021
70066
  return [a2[0] - b[0], a2[1] - b[1], a2[2] - b[2]];
70022
70067
  }
70023
70068
  function mul3(a2, s) {
@@ -70253,7 +70298,7 @@ function collectShapeTriangles(shape) {
70253
70298
  mesh.vertProperties[i2 * numProp + 1],
70254
70299
  mesh.vertProperties[i2 * numProp + 2]
70255
70300
  ];
70256
- const n = norm(cross3(sub3(b, a2), sub3(c2, a2)));
70301
+ const n = norm(cross3(sub3$1(b, a2), sub3$1(c2, a2)));
70257
70302
  tris.push({ a: a2, b, c: c2, normal: n });
70258
70303
  }
70259
70304
  return tris;
@@ -70323,7 +70368,7 @@ function summarizeMetricSeries(values) {
70323
70368
  return [signatureNumber(sum2), signatureNumber(sumSquares), signatureNumber(min2), signatureNumber(max2)].join(":");
70324
70369
  }
70325
70370
  function triangleArea(triangle) {
70326
- const c2 = cross3(sub3(triangle.b, triangle.a), sub3(triangle.c, triangle.a));
70371
+ const c2 = cross3(sub3$1(triangle.b, triangle.a), sub3$1(triangle.c, triangle.a));
70327
70372
  return Math.hypot(c2[0], c2[1], c2[2]) * 0.5;
70328
70373
  }
70329
70374
  function makeComponentPageSignature(object) {
@@ -70411,7 +70456,7 @@ function setReportLengthUnit(unit) {
70411
70456
  _reportLengthUnit = unit;
70412
70457
  }
70413
70458
  function projectPoint(point2, center, frame) {
70414
- const rel = sub3(point2, center);
70459
+ const rel = sub3$1(point2, center);
70415
70460
  return {
70416
70461
  x: dot3$1(rel, frame.right),
70417
70462
  y: dot3$1(rel, frame.up),
@@ -70432,7 +70477,7 @@ function makeViewFrame(view) {
70432
70477
  return { id: view, label: c2.label, right, up, forward };
70433
70478
  }
70434
70479
  function isDimensionVisibleInView(dim2, frame, toleranceDeg) {
70435
- const dir = sub3(dim2.to, dim2.from);
70480
+ const dir = sub3$1(dim2.to, dim2.from);
70436
70481
  const len2 = Math.hypot(dir[0], dir[1], dir[2]);
70437
70482
  if (len2 < 1e-9) return false;
70438
70483
  const d2 = [dir[0] / len2, dir[1] / len2, dir[2] / len2];
@@ -70468,7 +70513,7 @@ function pickDimensionOffsetBasis(dirModel, frame) {
70468
70513
  candidates.push({ dir3, proj, projDir, projLen });
70469
70514
  };
70470
70515
  worldAxes.forEach((axis) => {
70471
- const axisPerp = sub3(axis, mul3(dirModel, dot3$1(axis, dirModel)));
70516
+ const axisPerp = sub3$1(axis, mul3(dirModel, dot3$1(axis, dirModel)));
70472
70517
  pushCandidate(axisPerp);
70473
70518
  });
70474
70519
  if (candidates.length === 0) {
@@ -71008,7 +71053,7 @@ function drawDimension(dim2, frame, mapPoint, mapScale, color, cell, fromProject
71008
71053
  const dy = toProjected[1] - fromProjected[1];
71009
71054
  const len2 = Math.hypot(dx, dy);
71010
71055
  if (len2 < 1e-8) return { graphicsCmd: "", labelPlan: null, lineSegments: [] };
71011
- const modelDirRaw = sub3(dim2.to, dim2.from);
71056
+ const modelDirRaw = sub3$1(dim2.to, dim2.from);
71012
71057
  const modelLen = Math.hypot(modelDirRaw[0], modelDirRaw[1], modelDirRaw[2]);
71013
71058
  if (modelLen < 1e-9) return { graphicsCmd: "", labelPlan: null, lineSegments: [] };
71014
71059
  const modelDir = [modelDirRaw[0] / modelLen, modelDirRaw[1] / modelLen, modelDirRaw[2] / modelLen];
@@ -71642,206 +71687,6 @@ function generateReportPdf(result, options = {}) {
71642
71687
  bomItemCount: collectBomRows(bomEntries).length
71643
71688
  };
71644
71689
  }
71645
- let _collectedRobotExport = null;
71646
- function cloneLinkOptions(input) {
71647
- if (!input) return {};
71648
- return Object.fromEntries(Object.entries(input).map(([name, opts]) => [name, { ...opts }]));
71649
- }
71650
- function cloneJointOptions(input) {
71651
- if (!input) return {};
71652
- return Object.fromEntries(Object.entries(input).map(([name, opts]) => [name, { ...opts }]));
71653
- }
71654
- function cloneDiffDrive(input) {
71655
- if (!input) return void 0;
71656
- return {
71657
- ...input,
71658
- leftJoints: [...input.leftJoints],
71659
- rightJoints: [...input.rightJoints]
71660
- };
71661
- }
71662
- function cloneJointStatePublisher(input) {
71663
- if (!input) return void 0;
71664
- return {
71665
- ...input,
71666
- joints: input.joints ? [...input.joints] : void 0
71667
- };
71668
- }
71669
- function cloneWorld(input) {
71670
- if (!input) return null;
71671
- return {
71672
- ...input,
71673
- spawnPose: input.spawnPose ? [...input.spawnPose] : void 0,
71674
- keyboardTeleop: input.keyboardTeleop ? { ...input.keyboardTeleop } : void 0
71675
- };
71676
- }
71677
- function assertFinite(value, label) {
71678
- if (value !== void 0 && !Number.isFinite(value)) {
71679
- throw new Error(`${label} must be finite`);
71680
- }
71681
- }
71682
- function metadataNumber(value) {
71683
- return typeof value === "number" ? value : void 0;
71684
- }
71685
- function metadataCollision(value) {
71686
- return value === "none" || value === "visual" || value === "box" || value === "convex" ? value : void 0;
71687
- }
71688
- function colliderCollision(collider2) {
71689
- if (!collider2) return void 0;
71690
- if (collider2.mode === "box") return "box";
71691
- if (collider2.mode === "visual") return "visual";
71692
- return collider2.mode;
71693
- }
71694
- function velocityDriveEffort(drive) {
71695
- return (drive == null ? void 0 : drive.kind) === "velocity" ? drive.maxTorqueNm : void 0;
71696
- }
71697
- function velocityDriveVelocityDegS(drive) {
71698
- return (drive == null ? void 0 : drive.kind) === "velocity" ? drive.maxSpeedRpm * 6 : void 0;
71699
- }
71700
- function driveDamping(drive) {
71701
- return drive == null ? void 0 : drive.damping;
71702
- }
71703
- function driveFriction(drive) {
71704
- return drive == null ? void 0 : drive.friction;
71705
- }
71706
- function jointByName(assembly2) {
71707
- return new Map(assembly2.joints.map((joint2) => [joint2.name, joint2]));
71708
- }
71709
- function diffDriveFromControllers(controllers) {
71710
- if (!controllers) return void 0;
71711
- const diffDrives = controllers.filter((controller) => controller.kind === "diffDrive");
71712
- if (diffDrives.length > 1) {
71713
- throw new Error("assembly.withSimulation(...) currently supports one Sim.controller.diffDrive(...) controller");
71714
- }
71715
- const diffDrive2 = diffDrives[0];
71716
- return diffDrive2 ? {
71717
- leftJoints: [...diffDrive2.leftJoints],
71718
- rightJoints: [...diffDrive2.rightJoints],
71719
- wheelSeparationMm: diffDrive2.wheelSeparationMm,
71720
- wheelRadiusMm: diffDrive2.wheelRadiusMm,
71721
- topic: diffDrive2.topic,
71722
- odomTopic: diffDrive2.odomTopic,
71723
- tfTopic: diffDrive2.tfTopic,
71724
- frameId: diffDrive2.frameId,
71725
- odomFrameId: diffDrive2.odomFrameId,
71726
- maxLinearVelocity: diffDrive2.maxLinearVelocity,
71727
- maxAngularVelocity: diffDrive2.maxAngularVelocity,
71728
- linearAcceleration: diffDrive2.linearAcceleration,
71729
- angularAcceleration: diffDrive2.angularAcceleration
71730
- } : void 0;
71731
- }
71732
- function resetRobotExport() {
71733
- _collectedRobotExport = null;
71734
- }
71735
- function getCollectedRobotExport() {
71736
- return _collectedRobotExport;
71737
- }
71738
- function collectSimulationModel(assemblyInput, options = {}) {
71739
- var _a3, _b3, _c2, _d2, _e2, _f3, _g, _h, _i;
71740
- const assembly2 = typeof assemblyInput.describe === "function" ? assemblyInput.describe() : assemblyInput;
71741
- const partNames = new Set(assembly2.parts.map((part) => part.name));
71742
- const joints = jointByName(assembly2);
71743
- const links = cloneLinkOptions(options.links);
71744
- for (const part of assembly2.parts) {
71745
- const fromSim = part.sim;
71746
- const fromMaterialDensity = (_a3 = fromSim == null ? void 0 : fromSim.material) == null ? void 0 : _a3.densityKgM3;
71747
- const current = links[part.name] ?? {};
71748
- const next = {
71749
- massKg: current.massKg ?? (fromSim == null ? void 0 : fromSim.massKg) ?? metadataNumber((_b3 = part.metadata) == null ? void 0 : _b3.massKg),
71750
- densityKgM3: current.densityKgM3 ?? (fromSim == null ? void 0 : fromSim.densityKgM3) ?? fromMaterialDensity ?? metadataNumber((_c2 = part.metadata) == null ? void 0 : _c2.densityKgM3),
71751
- collision: current.collision ?? colliderCollision(fromSim == null ? void 0 : fromSim.collider) ?? metadataCollision((_d2 = part.metadata) == null ? void 0 : _d2.collision)
71752
- };
71753
- if (next.massKg !== void 0 || next.densityKgM3 !== void 0 || next.collision !== void 0) {
71754
- links[part.name] = next;
71755
- }
71756
- }
71757
- for (const [partName, link] of Object.entries(links)) {
71758
- if (!partNames.has(partName)) throw new Error(`simulation model references unknown link "${partName}"`);
71759
- assertFinite(link.massKg, `simulation model link "${partName}" massKg`);
71760
- assertFinite(link.densityKgM3, `simulation model link "${partName}" densityKgM3`);
71761
- }
71762
- const jointOpts = cloneJointOptions(options.joints);
71763
- for (const joint2 of assembly2.joints) {
71764
- const drive = (_e2 = joint2.sim) == null ? void 0 : _e2.drive;
71765
- const current = jointOpts[joint2.name] ?? {};
71766
- const next = {
71767
- effort: current.effort ?? velocityDriveEffort(drive) ?? joint2.effort,
71768
- velocity: current.velocity ?? velocityDriveVelocityDegS(drive) ?? joint2.velocity,
71769
- damping: current.damping ?? driveDamping(drive) ?? joint2.damping,
71770
- friction: current.friction ?? driveFriction(drive) ?? joint2.friction
71771
- };
71772
- if (next.effort !== void 0 || next.velocity !== void 0 || next.damping !== void 0 || next.friction !== void 0) {
71773
- jointOpts[joint2.name] = next;
71774
- }
71775
- }
71776
- for (const [jointName, joint2] of Object.entries(jointOpts)) {
71777
- if (!joints.has(jointName)) throw new Error(`simulation model references unknown joint "${jointName}"`);
71778
- assertFinite(joint2.effort, `simulation model joint "${jointName}" effort`);
71779
- assertFinite(joint2.velocity, `simulation model joint "${jointName}" velocity`);
71780
- assertFinite(joint2.damping, `simulation model joint "${jointName}" damping`);
71781
- assertFinite(joint2.friction, `simulation model joint "${jointName}" friction`);
71782
- }
71783
- const simulation = cloneSimAssemblySimulation(assembly2.sim) ?? null;
71784
- const simulationDiffDrive = diffDriveFromControllers(simulation == null ? void 0 : simulation.controllers);
71785
- const diffDrive2 = cloneDiffDrive((_f3 = options.plugins) == null ? void 0 : _f3.diffDrive) ?? simulationDiffDrive;
71786
- if (diffDrive2) {
71787
- if (diffDrive2.leftJoints.length === 0 || diffDrive2.rightJoints.length === 0) {
71788
- throw new Error("simulation model diffDrive requires at least one left joint and one right joint");
71789
- }
71790
- assertFinite(diffDrive2.wheelSeparationMm, "simulation model diffDrive wheelSeparationMm");
71791
- assertFinite(diffDrive2.wheelRadiusMm, "simulation model diffDrive wheelRadiusMm");
71792
- if (diffDrive2.wheelSeparationMm <= 0 || diffDrive2.wheelRadiusMm <= 0) {
71793
- throw new Error("simulation model diffDrive wheel separation and radius must be > 0");
71794
- }
71795
- [...diffDrive2.leftJoints, ...diffDrive2.rightJoints].forEach((jointName) => {
71796
- const joint2 = joints.get(jointName);
71797
- if (!joint2) throw new Error(`simulation model diffDrive references unknown joint "${jointName}"`);
71798
- if (joint2.type !== "revolute") {
71799
- throw new Error(`simulation model diffDrive joint "${jointName}" must be revolute`);
71800
- }
71801
- });
71802
- }
71803
- const jointStatePublisher = cloneJointStatePublisher((_g = options.plugins) == null ? void 0 : _g.jointStatePublisher);
71804
- if (jointStatePublisher == null ? void 0 : jointStatePublisher.joints) {
71805
- jointStatePublisher.joints.forEach((jointName) => {
71806
- if (!joints.has(jointName)) {
71807
- throw new Error(`simulation model jointStatePublisher references unknown joint "${jointName}"`);
71808
- }
71809
- });
71810
- }
71811
- const world = cloneWorld(options.world);
71812
- if (world == null ? void 0 : world.spawnPose) {
71813
- world.spawnPose.forEach((value, index2) => assertFinite(value, `simulation model world spawnPose[${index2}]`));
71814
- }
71815
- assertFinite((_h = world == null ? void 0 : world.keyboardTeleop) == null ? void 0 : _h.linearStep, "simulation model world keyboardTeleop.linearStep");
71816
- assertFinite((_i = world == null ? void 0 : world.keyboardTeleop) == null ? void 0 : _i.angularStep, "simulation model world keyboardTeleop.angularStep");
71817
- return {
71818
- modelName: (options.modelName ?? assembly2.name ?? "ForgeCAD Simulation").trim() || "ForgeCAD Simulation",
71819
- assembly: assembly2,
71820
- simulation,
71821
- source: options.source ?? "assembly",
71822
- state: { ...options.state ?? {} },
71823
- static: options.static ?? false,
71824
- selfCollide: options.selfCollide ?? false,
71825
- allowAutoDisable: options.allowAutoDisable ?? true,
71826
- links,
71827
- joints: jointOpts,
71828
- plugins: {
71829
- diffDrive: diffDrive2,
71830
- jointStatePublisher
71831
- },
71832
- world
71833
- };
71834
- }
71835
- function robotExport(options) {
71836
- if (!options || typeof options !== "object") {
71837
- throw new Error("robotExport(...) expects an options object");
71838
- }
71839
- if (!options.assembly || typeof options.assembly.describe !== "function") {
71840
- throw new Error("robotExport(...) requires an assembly");
71841
- }
71842
- _collectedRobotExport = collectSimulationModel(options.assembly, { ...options, source: "robotExport" });
71843
- return _collectedRobotExport;
71844
- }
71845
71690
  class Point2D {
71846
71691
  constructor(x2, y2) {
71847
71692
  this.x = x2;
@@ -94115,6 +93960,847 @@ function sketchOnFace(sketch, parentOrFace, faceOrOpts, maybeOpts = {}) {
94115
93960
  Sketch.prototype.onFace = function(parentOrFace, faceOrOpts, maybeOpts = {}) {
94116
93961
  return sketchOnFace(this, parentOrFace, faceOrOpts, maybeOpts);
94117
93962
  };
93963
+ const KNOT_EPS = 1e-10;
93964
+ const KNOT_MERGE_EPS = 1e-7;
93965
+ function sameKnot(a2, b) {
93966
+ return Math.abs(a2 - b) <= KNOT_EPS;
93967
+ }
93968
+ function knotMultiplicity(knots, u2) {
93969
+ let count = 0;
93970
+ for (const k2 of knots) if (sameKnot(k2, u2)) count++;
93971
+ return count;
93972
+ }
93973
+ function computeParamsCentripetal(points, alpha = 0.5) {
93974
+ const n = points.length;
93975
+ const params = new Array(n).fill(0);
93976
+ const seg = new Array(n).fill(0);
93977
+ let total = 0;
93978
+ for (let k2 = 1; k2 < n; k2++) {
93979
+ const dx = points[k2][0] - points[k2 - 1][0];
93980
+ const dy = points[k2][1] - points[k2 - 1][1];
93981
+ const dz = points[k2][2] - points[k2 - 1][2];
93982
+ seg[k2] = Math.hypot(dx, dy, dz) ** alpha;
93983
+ total += seg[k2];
93984
+ }
93985
+ if (total === 0) {
93986
+ for (let k2 = 0; k2 < n; k2++) params[k2] = n > 1 ? k2 / (n - 1) : 0;
93987
+ return params;
93988
+ }
93989
+ let acc = 0;
93990
+ for (let k2 = 1; k2 < n - 1; k2++) {
93991
+ acc += seg[k2];
93992
+ params[k2] = acc / total;
93993
+ }
93994
+ params[n - 1] = 1;
93995
+ return params;
93996
+ }
93997
+ function knotsFromParamsAveraging(params, degree) {
93998
+ const n = params.length - 1;
93999
+ const m2 = n + degree + 1;
94000
+ const knots = new Array(m2 + 1).fill(0);
94001
+ for (let i = m2 - degree; i <= m2; i++) knots[i] = 1;
94002
+ for (let j = 1; j <= n - degree; j++) {
94003
+ let s = 0;
94004
+ for (let i = j; i <= j + degree - 1; i++) s += params[i];
94005
+ knots[j + degree] = s / degree;
94006
+ }
94007
+ return knots;
94008
+ }
94009
+ function solveLinear(A, B2) {
94010
+ const n = A.length;
94011
+ const k2 = B2[0].length;
94012
+ const M = A.map((row, i) => [...row, ...B2[i]]);
94013
+ for (let col = 0; col < n; col++) {
94014
+ let piv = col;
94015
+ for (let r = col + 1; r < n; r++) if (Math.abs(M[r][col]) > Math.abs(M[piv][col])) piv = r;
94016
+ if (Math.abs(M[piv][col]) < 1e-14) throw new Error(`Singular interpolation matrix at column ${col}.`);
94017
+ [M[col], M[piv]] = [M[piv], M[col]];
94018
+ const inv = 1 / M[col][col];
94019
+ for (let j = col; j < n + k2; j++) M[col][j] *= inv;
94020
+ for (let r = 0; r < n; r++) {
94021
+ if (r === col) continue;
94022
+ const f3 = M[r][col];
94023
+ if (f3 === 0) continue;
94024
+ for (let j = col; j < n + k2; j++) M[r][j] -= f3 * M[col][j];
94025
+ }
94026
+ }
94027
+ return Array.from({ length: n }, (_2, i) => M[i].slice(n));
94028
+ }
94029
+ function globalCurveInterp(points, degree, paramsIn) {
94030
+ const last = points.length - 1;
94031
+ if (last < degree) throw new Error(`Curve interpolation needs at least ${degree + 1} points, got ${points.length}.`);
94032
+ const params = paramsIn ? paramsIn.slice() : computeParamsCentripetal(points, 0.5);
94033
+ const knots = knotsFromParamsAveraging(params, degree);
94034
+ const count = last + 1;
94035
+ const N = Array.from({ length: count }, () => new Array(count).fill(0));
94036
+ for (let i = 0; i <= last; i++) {
94037
+ const span = findSpan(count, degree, params[i], knots);
94038
+ const basis = basisFuns(span, params[i], degree, knots);
94039
+ for (let j = 0; j <= degree; j++) N[i][span - degree + j] = basis[j];
94040
+ }
94041
+ const Q = points.map((p2) => [p2[0], p2[1], p2[2]]);
94042
+ const cps = solveLinear(N, Q);
94043
+ return { cps, knots, degree };
94044
+ }
94045
+ function insertKnotCurve(cps, knots, degree, u2) {
94046
+ const n = cps.length;
94047
+ const span = findSpan(n, degree, u2, knots);
94048
+ const mult = knotMultiplicity(knots, u2);
94049
+ if (mult >= degree) return { cps: cps.slice(), knots: knots.slice() };
94050
+ const newCps = new Array(n + 1);
94051
+ const newKnots = new Array(knots.length + 1);
94052
+ for (let i = 0; i <= span; i++) newKnots[i] = knots[i];
94053
+ newKnots[span + 1] = u2;
94054
+ for (let i = span + 1; i < knots.length; i++) newKnots[i + 1] = knots[i];
94055
+ for (let i = 0; i <= span - degree; i++) newCps[i] = [...cps[i]];
94056
+ for (let i = span - mult; i < n; i++) newCps[i + 1] = [...cps[i]];
94057
+ for (let i = span - degree + 1; i <= span - mult; i++) {
94058
+ const denom = knots[i + degree] - knots[i];
94059
+ const alpha = denom === 0 ? 0 : (u2 - knots[i]) / denom;
94060
+ const a2 = cps[i - 1];
94061
+ const b = cps[i];
94062
+ newCps[i] = [(1 - alpha) * a2[0] + alpha * b[0], (1 - alpha) * a2[1] + alpha * b[1], (1 - alpha) * a2[2] + alpha * b[2]];
94063
+ }
94064
+ return { cps: newCps, knots: newKnots };
94065
+ }
94066
+ function interiorKnotBuckets(kv, mergeEps = KNOT_MERGE_EPS) {
94067
+ const buckets = [];
94068
+ for (const k2 of kv) {
94069
+ if (k2 <= KNOT_EPS || k2 >= 1 - KNOT_EPS) continue;
94070
+ const b = buckets.find((x2) => Math.abs(x2.value - k2) <= mergeEps);
94071
+ if (b) b.mult++;
94072
+ else buckets.push({ value: k2, mult: 1 });
94073
+ }
94074
+ return buckets;
94075
+ }
94076
+ function mergeKnotVectors(knotVectors, degree, mergeEps = KNOT_MERGE_EPS) {
94077
+ const merged = [];
94078
+ for (const kv of knotVectors) {
94079
+ for (const { value, mult } of interiorKnotBuckets(kv, mergeEps)) {
94080
+ const b = merged.find((x2) => Math.abs(x2.value - value) <= mergeEps);
94081
+ if (b) b.mult = Math.max(b.mult, mult);
94082
+ else merged.push({ value, mult });
94083
+ }
94084
+ }
94085
+ merged.sort((a2, b) => a2.value - b.value);
94086
+ const out = [];
94087
+ for (let i = 0; i <= degree; i++) out.push(0);
94088
+ for (const { value, mult } of merged) for (let i = 0; i < mult; i++) out.push(value);
94089
+ for (let i = 0; i <= degree; i++) out.push(1);
94090
+ return out;
94091
+ }
94092
+ function refineKnotsToTarget(cps, knots, degree, targetKnots, mergeEps = KNOT_MERGE_EPS) {
94093
+ let curCps = cps.map((p2) => [...p2]);
94094
+ let curKnots = knots.slice();
94095
+ for (const { value, mult } of interiorKnotBuckets(targetKnots, mergeEps)) {
94096
+ const curMult = () => curKnots.reduce((c2, k2) => c2 + (Math.abs(k2 - value) <= mergeEps ? 1 : 0), 0);
94097
+ let guard = 0;
94098
+ while (curMult() < mult) {
94099
+ const r = insertKnotCurve(curCps, curKnots, degree, value);
94100
+ if (r.cps.length === curCps.length) break;
94101
+ curCps = r.cps;
94102
+ curKnots = r.knots;
94103
+ if (++guard > degree + 2) break;
94104
+ }
94105
+ }
94106
+ return { cps: curCps, knots: curKnots };
94107
+ }
94108
+ function binomial(a2, b) {
94109
+ if (b < 0 || b > a2) return 0;
94110
+ let r = 1;
94111
+ for (let i = 0; i < b; i++) r = r * (a2 - i) / (i + 1);
94112
+ return r;
94113
+ }
94114
+ function bezierElevate(bez, p2, t) {
94115
+ const m2 = p2 + t;
94116
+ const out = [];
94117
+ for (let i = 0; i <= m2; i++) {
94118
+ const acc = [0, 0, 0];
94119
+ const lo = Math.max(0, i - t);
94120
+ const hi = Math.min(p2, i);
94121
+ for (let j = lo; j <= hi; j++) {
94122
+ const w2 = binomial(p2, j) * binomial(t, i - j) / binomial(m2, i);
94123
+ acc[0] += w2 * bez[j][0];
94124
+ acc[1] += w2 * bez[j][1];
94125
+ acc[2] += w2 * bez[j][2];
94126
+ }
94127
+ out.push(acc);
94128
+ }
94129
+ return out;
94130
+ }
94131
+ function elevateCurveDegree(cps, knots, degree, targetDegree) {
94132
+ if (targetDegree === degree) return { cps: cps.map((p2) => [...p2]), knots: knots.slice(), degree };
94133
+ if (targetDegree < degree) throw new Error("Degree reduction is not supported.");
94134
+ const t = targetDegree - degree;
94135
+ let curCps = cps.map((p2) => [...p2]);
94136
+ let curKnots = knots.slice();
94137
+ const breaks = interiorKnotBuckets(curKnots).map((b) => b.value);
94138
+ for (const v of breaks) {
94139
+ let guard = 0;
94140
+ while (knotMultiplicity(curKnots, v) < degree) {
94141
+ const r = insertKnotCurve(curCps, curKnots, degree, v);
94142
+ curCps = r.cps;
94143
+ curKnots = r.knots;
94144
+ if (++guard > degree + 2) break;
94145
+ }
94146
+ }
94147
+ const segVals = [0, ...breaks.slice().sort((a2, b) => a2 - b), 1];
94148
+ const nSeg = segVals.length - 1;
94149
+ const newCps = [];
94150
+ for (let s = 0; s < nSeg; s++) {
94151
+ const bez = curCps.slice(s * degree, s * degree + degree + 1);
94152
+ const elevated = bezierElevate(bez, degree, t);
94153
+ if (s === 0) newCps.push(...elevated);
94154
+ else newCps.push(...elevated.slice(1));
94155
+ }
94156
+ const newKnots = [];
94157
+ for (let i = 0; i <= targetDegree; i++) newKnots.push(0);
94158
+ for (let s = 1; s < nSeg; s++) for (let i = 0; i < targetDegree; i++) newKnots.push(segVals[s]);
94159
+ for (let i = 0; i <= targetDegree; i++) newKnots.push(1);
94160
+ if (newKnots.length !== newCps.length + targetDegree + 1) {
94161
+ throw new Error(
94162
+ `Degree elevation produced inconsistent knots (${newKnots.length}) for ${newCps.length} control points at degree ${targetDegree}.`
94163
+ );
94164
+ }
94165
+ return { cps: newCps, knots: newKnots, degree: targetDegree };
94166
+ }
94167
+ function unifyCurves(curves) {
94168
+ const targetDeg = Math.max(...curves.map((c2) => c2.degree));
94169
+ const elevated = curves.map((c2) => elevateCurveDegree(c2.cps, c2.knots, c2.degree, targetDeg));
94170
+ const common = mergeKnotVectors(
94171
+ elevated.map((c2) => c2.knots),
94172
+ targetDeg
94173
+ );
94174
+ const refined = elevated.map((c2) => refineKnotsToTarget(c2.cps, c2.knots, targetDeg, common));
94175
+ const ncp = common.length - targetDeg - 1;
94176
+ for (const r of refined) {
94177
+ if (r.cps.length !== ncp) throw new Error(`Curve unification produced ${r.cps.length} control points, expected ${ncp}.`);
94178
+ }
94179
+ return { degree: targetDeg, knots: common, curves: refined.map((r) => r.cps) };
94180
+ }
94181
+ function transpose(g2) {
94182
+ const out = [];
94183
+ for (let j = 0; j < g2[0].length; j++) {
94184
+ const row = [];
94185
+ for (let i = 0; i < g2.length; i++) row.push(g2[i][j]);
94186
+ out.push(row);
94187
+ }
94188
+ return out;
94189
+ }
94190
+ function skin(curveCps, spanParams, spanDegree) {
94191
+ const ncp = curveCps[0].length;
94192
+ const cols = [];
94193
+ for (let j = 0; j < ncp; j++) {
94194
+ const pts = curveCps.map((row) => row[j]);
94195
+ cols.push(globalCurveInterp(pts, spanDegree, spanParams).cps);
94196
+ }
94197
+ const spanCp = cols[0].length;
94198
+ const grid = [];
94199
+ for (let s = 0; s < spanCp; s++) {
94200
+ const row = [];
94201
+ for (let j = 0; j < ncp; j++) row.push(cols[j][s]);
94202
+ grid.push(row);
94203
+ }
94204
+ const spanKnots = knotsFromParamsAveraging(spanParams, spanDegree);
94205
+ return { grid, spanKnots };
94206
+ }
94207
+ function tensorInterp(gridPts, uParams, vParams, degreeU, degreeV) {
94208
+ const rowInterp = gridPts.map((rowPts) => globalCurveInterp(rowPts, degreeV, vParams));
94209
+ const knotsV = rowInterp[0].knots;
94210
+ const ncpV = rowInterp[0].cps.length;
94211
+ const cols = [];
94212
+ for (let j = 0; j < ncpV; j++) {
94213
+ const pts = rowInterp.map((ri) => ri.cps[j]);
94214
+ cols.push(globalCurveInterp(pts, degreeU, uParams));
94215
+ }
94216
+ const knotsU = cols[0].knots;
94217
+ const cpU = cols[0].cps.length;
94218
+ const grid = [];
94219
+ for (let i = 0; i < cpU; i++) {
94220
+ const row = [];
94221
+ for (let j = 0; j < ncpV; j++) row.push(cols[j].cps[i]);
94222
+ grid.push(row);
94223
+ }
94224
+ return { grid, knotsU, knotsV };
94225
+ }
94226
+ function refineSurface(surf, targetKnotsU, targetKnotsV) {
94227
+ const { grid, knotsU, knotsV, degreeU, degreeV } = surf;
94228
+ const rows = grid.map((row) => refineKnotsToTarget(row, knotsV, degreeV, targetKnotsV).cps);
94229
+ const ncpV = rows[0].length;
94230
+ const cols = [];
94231
+ for (let j = 0; j < ncpV; j++) {
94232
+ const colPts = rows.map((r) => r[j]);
94233
+ cols.push(refineKnotsToTarget(colPts, knotsU, degreeU, targetKnotsU).cps);
94234
+ }
94235
+ const cpU = cols[0].length;
94236
+ const out = [];
94237
+ for (let i = 0; i < cpU; i++) {
94238
+ const row = [];
94239
+ for (let j = 0; j < ncpV; j++) row.push(cols[j][i]);
94240
+ out.push(row);
94241
+ }
94242
+ return { grid: out, knotsU: targetKnotsU, knotsV: targetKnotsV, degreeU, degreeV };
94243
+ }
94244
+ function averageParams(polylines, alpha = 0.5) {
94245
+ const count = polylines[0].length;
94246
+ const acc = new Array(count).fill(0);
94247
+ for (const pl of polylines) {
94248
+ const p2 = computeParamsCentripetal(pl, alpha);
94249
+ for (let i = 0; i < count; i++) acc[i] += p2[i];
94250
+ }
94251
+ return acc.map((s) => s / polylines.length);
94252
+ }
94253
+ function gordonGridParams(gridPts) {
94254
+ const rows = gridPts;
94255
+ const cols = gridPts[0].map((_2, l) => gridPts.map((r) => r[l]));
94256
+ return { vParams: averageParams(rows, 0.5), uParams: averageParams(cols, 0.5) };
94257
+ }
94258
+ function buildGordonFromGrid(gridPts, degreeU = 3, degreeV = 3) {
94259
+ const m2 = gridPts.length - 1;
94260
+ const n = gridPts[0].length - 1;
94261
+ if (m2 < 1 || n < 1) throw new Error("A Gordon surface needs at least a 2x2 curve network.");
94262
+ if (m2 < degreeU) degreeU = m2;
94263
+ if (n < degreeV) degreeV = n;
94264
+ const rows = gridPts;
94265
+ const cols = gridPts[0].map((_2, l) => gridPts.map((r) => r[l]));
94266
+ const { uParams, vParams } = gordonGridParams(gridPts);
94267
+ const uCurves = rows.map((rowPts) => globalCurveInterp(rowPts, degreeV, vParams));
94268
+ const vCurves = cols.map((colPts) => globalCurveInterp(colPts, degreeU, uParams));
94269
+ const uFam = unifyCurves(uCurves);
94270
+ const vFam = unifyCurves(vCurves);
94271
+ const Lu = skin(uFam.curves, uParams, degreeU);
94272
+ const LvRaw = skin(vFam.curves, vParams, degreeV);
94273
+ const Lv = { grid: transpose(LvRaw.grid), knotsU: vFam.knots, knotsV: LvRaw.spanKnots };
94274
+ const T = tensorInterp(gridPts, uParams, vParams, degreeU, degreeV);
94275
+ const knotsU = mergeKnotVectors([Lu.spanKnots, Lv.knotsU, T.knotsU], degreeU);
94276
+ const knotsV = mergeKnotVectors([uFam.knots, Lv.knotsV, T.knotsV], degreeV);
94277
+ const LuS = refineSurface({ grid: Lu.grid, knotsU: Lu.spanKnots, knotsV: uFam.knots, degreeU, degreeV }, knotsU, knotsV);
94278
+ const LvS = refineSurface({ grid: Lv.grid, knotsU: Lv.knotsU, knotsV: Lv.knotsV, degreeU, degreeV }, knotsU, knotsV);
94279
+ const TS = refineSurface({ grid: T.grid, knotsU: T.knotsU, knotsV: T.knotsV, degreeU, degreeV }, knotsU, knotsV);
94280
+ const nuc = LuS.grid.length;
94281
+ const nvc = LuS.grid[0].length;
94282
+ if (LvS.grid.length !== nuc || TS.grid.length !== nuc || LvS.grid[0].length !== nvc || TS.grid[0].length !== nvc) {
94283
+ throw new Error(
94284
+ `Gordon blend grid mismatch: Lu ${nuc}x${nvc}, Lv ${LvS.grid.length}x${LvS.grid[0].length}, T ${TS.grid.length}x${TS.grid[0].length}.`
94285
+ );
94286
+ }
94287
+ const grid = [];
94288
+ for (let i = 0; i < nuc; i++) {
94289
+ const row = [];
94290
+ for (let j = 0; j < nvc; j++) {
94291
+ const a2 = LuS.grid[i][j];
94292
+ const b = LvS.grid[i][j];
94293
+ const c2 = TS.grid[i][j];
94294
+ row.push([a2[0] + b[0] - c2[0], a2[1] + b[1] - c2[1], a2[2] + b[2] - c2[2]]);
94295
+ }
94296
+ grid.push(row);
94297
+ }
94298
+ return { grid, knotsU, knotsV, degreeU, degreeV };
94299
+ }
94300
+ function evalSurface(surf, u2, v) {
94301
+ const { grid, knotsU, knotsV, degreeU, degreeV } = surf;
94302
+ const nu = grid.length;
94303
+ const nv = grid[0].length;
94304
+ const su = findSpan(nu, degreeU, u2, knotsU);
94305
+ const sv = findSpan(nv, degreeV, v, knotsV);
94306
+ const Nu = basisFuns(su, u2, degreeU, knotsU);
94307
+ const Nv = basisFuns(sv, v, degreeV, knotsV);
94308
+ const out = [0, 0, 0];
94309
+ for (let i = 0; i <= degreeU; i++) {
94310
+ const ui = su - degreeU + i;
94311
+ for (let j = 0; j <= degreeV; j++) {
94312
+ const vj = sv - degreeV + j;
94313
+ const w2 = Nu[i] * Nv[j];
94314
+ const p2 = grid[ui][vj];
94315
+ out[0] += w2 * p2[0];
94316
+ out[1] += w2 * p2[1];
94317
+ out[2] += w2 * p2[2];
94318
+ }
94319
+ }
94320
+ return out;
94321
+ }
94322
+ function evalSurfaceJet(surf, u2, v) {
94323
+ const { grid, knotsU, knotsV, degreeU, degreeV } = surf;
94324
+ const nu = grid.length;
94325
+ const nv = grid[0].length;
94326
+ const su = findSpan(nu, degreeU, u2, knotsU);
94327
+ const sv = findSpan(nv, degreeV, v, knotsV);
94328
+ const dU = basisFunsDeriv(su, u2, degreeU, knotsU, 2);
94329
+ const dV = basisFunsDeriv(sv, v, degreeV, knotsV, 2);
94330
+ const S = [0, 0, 0];
94331
+ const Su = [0, 0, 0];
94332
+ const Sv = [0, 0, 0];
94333
+ const Suu = [0, 0, 0];
94334
+ const Suv = [0, 0, 0];
94335
+ const Svv = [0, 0, 0];
94336
+ for (let i = 0; i <= degreeU; i++) {
94337
+ const ui = su - degreeU + i;
94338
+ for (let j = 0; j <= degreeV; j++) {
94339
+ const vj = sv - degreeV + j;
94340
+ const p2 = grid[ui][vj];
94341
+ const w00 = dU[0][i] * dV[0][j];
94342
+ const w10 = dU[1][i] * dV[0][j];
94343
+ const w01 = dU[0][i] * dV[1][j];
94344
+ const w20 = dU[2][i] * dV[0][j];
94345
+ const w11 = dU[1][i] * dV[1][j];
94346
+ const w02 = dU[0][i] * dV[2][j];
94347
+ for (let c2 = 0; c2 < 3; c2++) {
94348
+ S[c2] += w00 * p2[c2];
94349
+ Su[c2] += w10 * p2[c2];
94350
+ Sv[c2] += w01 * p2[c2];
94351
+ Suu[c2] += w20 * p2[c2];
94352
+ Suv[c2] += w11 * p2[c2];
94353
+ Svv[c2] += w02 * p2[c2];
94354
+ }
94355
+ }
94356
+ }
94357
+ let nx = Su[1] * Sv[2] - Su[2] * Sv[1];
94358
+ let ny = Su[2] * Sv[0] - Su[0] * Sv[2];
94359
+ let nz = Su[0] * Sv[1] - Su[1] * Sv[0];
94360
+ const len2 = Math.hypot(nx, ny, nz) || 1;
94361
+ nx /= len2;
94362
+ ny /= len2;
94363
+ nz /= len2;
94364
+ return { S, Su, Sv, Suu, Suv, Svv, normal: [nx, ny, nz] };
94365
+ }
94366
+ function surfaceCurvature(jet) {
94367
+ const { Su, Sv, Suu, Suv, Svv, normal } = jet;
94368
+ const E = Su[0] * Su[0] + Su[1] * Su[1] + Su[2] * Su[2];
94369
+ const F = Su[0] * Sv[0] + Su[1] * Sv[1] + Su[2] * Sv[2];
94370
+ const G = Sv[0] * Sv[0] + Sv[1] * Sv[1] + Sv[2] * Sv[2];
94371
+ const e = Suu[0] * normal[0] + Suu[1] * normal[1] + Suu[2] * normal[2];
94372
+ const f3 = Suv[0] * normal[0] + Suv[1] * normal[1] + Suv[2] * normal[2];
94373
+ const g2 = Svv[0] * normal[0] + Svv[1] * normal[1] + Svv[2] * normal[2];
94374
+ const denom = E * G - F * F;
94375
+ if (Math.abs(denom) < 1e-20) return { k1: 0, k2: 0, K: 0, H: 0 };
94376
+ const K = (e * g2 - f3 * f3) / denom;
94377
+ const H = (e * G - 2 * f3 * F + g2 * E) / (2 * denom);
94378
+ const disc = Math.max(0, H * H - K);
94379
+ const root = Math.sqrt(disc);
94380
+ return { k1: H + root, k2: H - root, K, H };
94381
+ }
94382
+ function isVec3(v) {
94383
+ return Array.isArray(v) && v.length === 3 && typeof v[0] === "number" && typeof v[1] === "number" && typeof v[2] === "number";
94384
+ }
94385
+ function requireFiniteVec(p2, label) {
94386
+ for (let i = 0; i < 3; i++)
94387
+ if (!Number.isFinite(p2[i])) throw new Error(`Surface.Net: ${label} component ${i} must be finite, got ${p2[i]}`);
94388
+ return [p2[0], p2[1], p2[2]];
94389
+ }
94390
+ function toSampler(input, label) {
94391
+ if (input instanceof NurbsCurve3D) {
94392
+ return (t) => input.pointAt(t);
94393
+ }
94394
+ if (Array.isArray(input) && input.length > 0 && isVec3(input[0])) {
94395
+ const pts = input.map((p2, i) => requireFiniteVec(p2, `${label}[${i}]`));
94396
+ if (pts.length < 2) throw new Error(`Surface.Net: ${label} needs at least 2 points.`);
94397
+ if (pts.length === 2) {
94398
+ const [a2, b] = pts;
94399
+ return (t) => [a2[0] + (b[0] - a2[0]) * t, a2[1] + (b[1] - a2[1]) * t, a2[2] + (b[2] - a2[2]) * t];
94400
+ }
94401
+ const curve = globalCurveInterp(pts, Math.min(3, pts.length - 1));
94402
+ const nc = new NurbsCurve3D(curve.cps, { degree: curve.degree, knots: curve.knots });
94403
+ return (t) => nc.pointAt(t);
94404
+ }
94405
+ throw new Error(`Surface.Net: ${label} must be a Curve.Fit/Curve.Nurbs value or an array of [x,y,z] points.`);
94406
+ }
94407
+ function sampleCurve(sampler, count) {
94408
+ const out = [];
94409
+ for (let i = 0; i < count; i++) out.push(sampler(i / (count - 1)));
94410
+ return out;
94411
+ }
94412
+ const DEFAULT_THICKEN_RESOLUTION = 48;
94413
+ class Sheet {
94414
+ constructor(surface) {
94415
+ this.surface = surface;
94416
+ }
94417
+ /** Edge naming follows parameter direction (documented): front=v0, rear=v1, left=u0, right=u1. */
94418
+ get frontEdge() {
94419
+ return { sheet: this, fixed: "v", value: 0 };
94420
+ }
94421
+ get rearEdge() {
94422
+ return { sheet: this, fixed: "v", value: 1 };
94423
+ }
94424
+ get leftEdge() {
94425
+ return { sheet: this, fixed: "u", value: 0 };
94426
+ }
94427
+ get rightEdge() {
94428
+ return { sheet: this, fixed: "u", value: 1 };
94429
+ }
94430
+ pointAt(u2, v) {
94431
+ return evalSurface(this.surface, clamp01(u2), clamp01(v));
94432
+ }
94433
+ normalAt(u2, v) {
94434
+ return evalSurfaceJet(this.surface, clamp01(u2), clamp01(v)).normal;
94435
+ }
94436
+ curvatureAt(u2, v) {
94437
+ return surfaceCurvature(evalSurfaceJet(this.surface, clamp01(u2), clamp01(v)));
94438
+ }
94439
+ /** Largest principal curvature magnitude over a sampling grid (for offset safety). */
94440
+ maxAbsPrincipalCurvature(samples = 9) {
94441
+ let maxK = 0;
94442
+ for (let i = 0; i <= samples; i++) {
94443
+ for (let j = 0; j <= samples; j++) {
94444
+ const c2 = this.curvatureAt(i / samples, j / samples);
94445
+ maxK = Math.max(maxK, Math.abs(c2.k1), Math.abs(c2.k2));
94446
+ }
94447
+ }
94448
+ return maxK;
94449
+ }
94450
+ /**
94451
+ * Offset the sheet along its analytic normals into a watertight solid shell of
94452
+ * the given wall thickness. Throws if the wall would self-intersect on a
94453
+ * concave region (no silent degenerate solid).
94454
+ */
94455
+ thicken(wall, options = {}) {
94456
+ if (!Number.isFinite(wall) || wall <= 0) throw new Error(`Sheet.thicken: wall must be a positive finite number, got ${wall}`);
94457
+ const maxK = this.maxAbsPrincipalCurvature();
94458
+ if (wall * maxK >= 1) {
94459
+ throw new Error(
94460
+ `Sheet.thicken: wall ${wall} exceeds the minimum radius of curvature (1/${maxK.toFixed(4)} = ${(1 / maxK).toFixed(3)}). The offset surface would self-intersect. Reduce the wall thickness or relax the curvature.`
94461
+ );
94462
+ }
94463
+ const resolution = options.resolution ?? DEFAULT_THICKEN_RESOLUTION;
94464
+ return nurbsSurface(this.surface.grid, {
94465
+ degreeU: this.surface.degreeU,
94466
+ degreeV: this.surface.degreeV,
94467
+ knotsU: this.surface.knotsU,
94468
+ knotsV: this.surface.knotsV,
94469
+ thickness: wall,
94470
+ resolution
94471
+ });
94472
+ }
94473
+ /** Per-edge continuity match against a neighbor (returns a NEW Sheet). */
94474
+ matchEdge(edge) {
94475
+ if (edge.sheet !== this) throw new Error("Sheet.matchEdge: the edge must belong to this sheet.");
94476
+ return new MatchEdgeBuilder(this, edge);
94477
+ }
94478
+ }
94479
+ function clamp01(t) {
94480
+ return t < 0 ? 0 : t > 1 ? 1 : t;
94481
+ }
94482
+ class CurveNetBuilder {
94483
+ constructor() {
94484
+ __publicField(this, "lengthwiseCurves", []);
94485
+ __publicField(this, "crosswiseCurves", []);
94486
+ __publicField(this, "railCurves", []);
94487
+ __publicField(this, "cageGrid", null);
94488
+ __publicField(this, "degU");
94489
+ __publicField(this, "degV");
94490
+ __publicField(this, "built", null);
94491
+ }
94492
+ lengthwise(...curves) {
94493
+ this.lengthwiseCurves = curves;
94494
+ this.built = null;
94495
+ return this;
94496
+ }
94497
+ crosswise(...curves) {
94498
+ this.crosswiseCurves = curves;
94499
+ this.built = null;
94500
+ return this;
94501
+ }
94502
+ alongRails(railA, railB) {
94503
+ this.railCurves = [railA, railB];
94504
+ this.built = null;
94505
+ return this;
94506
+ }
94507
+ sections(...curves) {
94508
+ this.crosswiseCurves = curves;
94509
+ this.built = null;
94510
+ return this;
94511
+ }
94512
+ cage(grid) {
94513
+ if (!Array.isArray(grid) || grid.length < 2 || !Array.isArray(grid[0]) || grid[0].length < 2) {
94514
+ throw new Error("Surface.Net().cage: grid must be at least a 2x2 array of [x,y,z] points.");
94515
+ }
94516
+ const cols = grid[0].length;
94517
+ this.cageGrid = grid.map((row, k2) => {
94518
+ if (row.length !== cols) throw new Error(`Surface.Net().cage: row ${k2} has ${row.length} points, expected ${cols}.`);
94519
+ return row.map((p2, l) => requireFiniteVec(p2, `cage[${k2}][${l}]`));
94520
+ });
94521
+ this.built = null;
94522
+ return this;
94523
+ }
94524
+ degree(u2, v) {
94525
+ if (!Number.isInteger(u2) || u2 < 1) throw new Error(`Surface.Net().degree: degree must be a positive integer, got ${u2}`);
94526
+ this.degU = u2;
94527
+ this.degV = v === void 0 ? u2 : v;
94528
+ if (v !== void 0 && (!Number.isInteger(v) || v < 1))
94529
+ throw new Error(`Surface.Net().degree: degree must be a positive integer, got ${v}`);
94530
+ this.built = null;
94531
+ return this;
94532
+ }
94533
+ /** Build (once) and return the Sheet. */
94534
+ toSheet() {
94535
+ if (this.built) return this.built;
94536
+ const grid = this.buildGrid();
94537
+ const m2 = grid.length - 1;
94538
+ const n = grid[0].length - 1;
94539
+ const degreeU = Math.max(1, Math.min(this.degU ?? 3, m2));
94540
+ const degreeV = Math.max(1, Math.min(this.degV ?? 3, n));
94541
+ const surface = buildGordonFromGrid(grid, degreeU, degreeV);
94542
+ this.built = new Sheet(surface);
94543
+ return this.built;
94544
+ }
94545
+ buildGrid() {
94546
+ if (this.cageGrid) return this.cageGrid;
94547
+ const lengthwise = this.railCurves.length > 0 ? this.railCurves : this.lengthwiseCurves;
94548
+ const crosswise = this.crosswiseCurves;
94549
+ const SAMPLES = 17;
94550
+ if (lengthwise.length >= 2) {
94551
+ const samplers = lengthwise.map((c2, l) => toSampler(c2, `lengthwise[${l}]`));
94552
+ const grid = [];
94553
+ for (let i = 0; i < SAMPLES; i++) {
94554
+ const u2 = i / (SAMPLES - 1);
94555
+ grid.push(samplers.map((s) => s(u2)));
94556
+ }
94557
+ return grid;
94558
+ }
94559
+ if (crosswise.length >= 2) {
94560
+ const samplers = crosswise.map((c2, k2) => toSampler(c2, `crosswise[${k2}]`));
94561
+ const grid = [];
94562
+ for (const s of samplers) grid.push(sampleCurve(s, SAMPLES));
94563
+ return grid;
94564
+ }
94565
+ throw new Error(
94566
+ "Surface.Net: provide a .cage(grid), or a family of at least 2 curves via .lengthwise(...) / .crosswise(...) / .alongRails(a,b).sections(...). Intersecting two independent hand-drawn families is not available yet — supply the denser family or a cage."
94567
+ );
94568
+ }
94569
+ // ── Sheet delegation (build on first access) ──────────────────────────────
94570
+ get frontEdge() {
94571
+ return this.toSheet().frontEdge;
94572
+ }
94573
+ get rearEdge() {
94574
+ return this.toSheet().rearEdge;
94575
+ }
94576
+ get leftEdge() {
94577
+ return this.toSheet().leftEdge;
94578
+ }
94579
+ get rightEdge() {
94580
+ return this.toSheet().rightEdge;
94581
+ }
94582
+ get surface() {
94583
+ return this.toSheet().surface;
94584
+ }
94585
+ pointAt(u2, v) {
94586
+ return this.toSheet().pointAt(u2, v);
94587
+ }
94588
+ normalAt(u2, v) {
94589
+ return this.toSheet().normalAt(u2, v);
94590
+ }
94591
+ curvatureAt(u2, v) {
94592
+ return this.toSheet().curvatureAt(u2, v);
94593
+ }
94594
+ thicken(wall, options) {
94595
+ return this.toSheet().thicken(wall, options);
94596
+ }
94597
+ matchEdge(edge) {
94598
+ return this.toSheet().matchEdge(edge);
94599
+ }
94600
+ }
94601
+ function createCurveNet() {
94602
+ return new CurveNetBuilder();
94603
+ }
94604
+ class MatchEdgeBuilder {
94605
+ constructor(sheet, edge) {
94606
+ this.sheet = sheet;
94607
+ this.edge = edge;
94608
+ }
94609
+ toG0(neighbor) {
94610
+ return applyEdgeMatch(this.sheet, this.edge, neighbor, 0);
94611
+ }
94612
+ toG1(neighbor) {
94613
+ return applyEdgeMatch(this.sheet, this.edge, neighbor, 1);
94614
+ }
94615
+ toG2(neighbor) {
94616
+ return applyEdgeMatch(this.sheet, this.edge, neighbor, 2);
94617
+ }
94618
+ }
94619
+ function boundaryRows(surf, fixed, value, depth) {
94620
+ const rows = [];
94621
+ if (fixed === "u") {
94622
+ const nU = surf.grid.length;
94623
+ for (let d2 = 0; d2 <= depth; d2++) {
94624
+ const i = value === 0 ? d2 : nU - 1 - d2;
94625
+ rows.push(surf.grid[i].map((p2) => [p2[0], p2[1], p2[2]]));
94626
+ }
94627
+ } else {
94628
+ const nV = surf.grid[0].length;
94629
+ for (let d2 = 0; d2 <= depth; d2++) {
94630
+ const j = value === 0 ? d2 : nV - 1 - d2;
94631
+ rows.push(surf.grid.map((p2) => [p2[j][0], p2[j][1], p2[j][2]]));
94632
+ }
94633
+ }
94634
+ return rows;
94635
+ }
94636
+ function setBoundaryRow(surf, fixed, value, depth, row) {
94637
+ if (fixed === "u") {
94638
+ const nU = surf.grid.length;
94639
+ const i = value === 0 ? depth : nU - 1 - depth;
94640
+ for (let l = 0; l < surf.grid[i].length; l++) surf.grid[i][l] = [row[l][0], row[l][1], row[l][2]];
94641
+ } else {
94642
+ const nV = surf.grid[0].length;
94643
+ const j = value === 0 ? depth : nV - 1 - depth;
94644
+ for (let k2 = 0; k2 < surf.grid.length; k2++) surf.grid[k2][j] = [row[k2][0], row[k2][1], row[k2][2]];
94645
+ }
94646
+ }
94647
+ function cloneSurface(surf) {
94648
+ return {
94649
+ grid: surf.grid.map((row) => row.map((p2) => [p2[0], p2[1], p2[2]])),
94650
+ knotsU: [...surf.knotsU],
94651
+ knotsV: [...surf.knotsV],
94652
+ degreeU: surf.degreeU,
94653
+ degreeV: surf.degreeV
94654
+ };
94655
+ }
94656
+ function applyEdgeMatch(sheet, edge, neighbor, order) {
94657
+ const result = cloneSurface(sheet.surface);
94658
+ const my = boundaryRows(result, edge.fixed, edge.value, order);
94659
+ const their = boundaryRows(neighbor.sheet.surface, neighbor.fixed, neighbor.value, order);
94660
+ if (my[0].length !== their[0].length) {
94661
+ throw new Error(
94662
+ `Sheet.matchEdge: edge control-point counts differ (${my[0].length} vs ${their[0].length}). Both sheets must share section sampling along the matched edge for continuity.`
94663
+ );
94664
+ }
94665
+ const len2 = my[0].length;
94666
+ const b0 = their[0].map((p2) => [p2[0], p2[1], p2[2]]);
94667
+ setBoundaryRow(result, edge.fixed, edge.value, 0, b0);
94668
+ if (order === 0) return new Sheet(result);
94669
+ const b1 = [];
94670
+ for (let i = 0; i < len2; i++) {
94671
+ const p0 = their[0][i];
94672
+ const p1 = their[1][i];
94673
+ b1.push([2 * p0[0] - p1[0], 2 * p0[1] - p1[1], 2 * p0[2] - p1[2]]);
94674
+ }
94675
+ setBoundaryRow(result, edge.fixed, edge.value, 1, b1);
94676
+ if (order === 1) return new Sheet(result);
94677
+ const b22 = [];
94678
+ for (let i = 0; i < len2; i++) {
94679
+ const p0 = their[0][i];
94680
+ const p1 = their[1][i];
94681
+ const p2 = their[2][i];
94682
+ b22.push([3 * p0[0] - 3 * p1[0] + p2[0], 3 * p0[1] - 3 * p1[1] + p2[1], 3 * p0[2] - 3 * p1[2] + p2[2]]);
94683
+ }
94684
+ setBoundaryRow(result, edge.fixed, edge.value, 2, b22);
94685
+ return new Sheet(result);
94686
+ }
94687
+ function edgeJet(edge, t) {
94688
+ const surf = edge.sheet.surface;
94689
+ if (edge.fixed === "u") {
94690
+ const j2 = evalSurfaceJet(surf, edge.value, clamp01(t));
94691
+ return { point: j2.S, cross: j2.Su, cross2: j2.Suu };
94692
+ }
94693
+ const j = evalSurfaceJet(surf, clamp01(t), edge.value);
94694
+ return { point: j.S, cross: j.Sv, cross2: j.Svv };
94695
+ }
94696
+ function sub3(a2, b) {
94697
+ return [a2[0] - b[0], a2[1] - b[1], a2[2] - b[2]];
94698
+ }
94699
+ function len3(a2) {
94700
+ return Math.hypot(a2[0], a2[1], a2[2]);
94701
+ }
94702
+ function unit3(a2) {
94703
+ const l = len3(a2) || 1;
94704
+ return [a2[0] / l, a2[1] / l, a2[2] / l];
94705
+ }
94706
+ function edgeMatchReport(edgeA, edgeB, samples = 24) {
94707
+ let maxPositionGap = 0;
94708
+ let maxTangentAngleDeg = 0;
94709
+ let maxCurvatureRelErr = 0;
94710
+ for (let i = 0; i <= samples; i++) {
94711
+ const t = i / samples;
94712
+ const a2 = edgeJet(edgeA, t);
94713
+ const b = edgeJet(edgeB, t);
94714
+ maxPositionGap = Math.max(maxPositionGap, len3(sub3(a2.point, b.point)));
94715
+ const ua = unit3(a2.cross);
94716
+ const ub = unit3(b.cross);
94717
+ const dot2 = Math.max(-1, Math.min(1, ua[0] * ub[0] + ua[1] * ub[1] + ua[2] * ub[2]));
94718
+ const angle = Math.acos(Math.abs(dot2)) * 180 / Math.PI;
94719
+ maxTangentAngleDeg = Math.max(maxTangentAngleDeg, angle);
94720
+ const la = len3(a2.cross) ** 2 || 1;
94721
+ const lb = len3(b.cross) ** 2 || 1;
94722
+ const ka = (a2.cross2[0] * ua[0] + a2.cross2[1] * ua[1] + a2.cross2[2] * ua[2]) / la;
94723
+ const kb = (b.cross2[0] * ub[0] + b.cross2[1] * ub[1] + b.cross2[2] * ub[2]) / lb;
94724
+ maxCurvatureRelErr = Math.max(maxCurvatureRelErr, Math.abs(ka - kb) / (Math.abs(ka) + Math.abs(kb) + 1e-9));
94725
+ }
94726
+ return { maxPositionGap, maxTangentAngleDeg, maxCurvatureRelErr };
94727
+ }
94728
+ function bridgeBetween(edgeA, edgeB) {
94729
+ return new BridgeBuilder(edgeA, edgeB);
94730
+ }
94731
+ class BridgeBuilder {
94732
+ constructor(edgeA, edgeB) {
94733
+ __publicField(this, "bulgeA", 0.5);
94734
+ __publicField(this, "bulgeB", 0.5);
94735
+ this.edgeA = edgeA;
94736
+ this.edgeB = edgeB;
94737
+ }
94738
+ /** Tune the influence of each side (Rhino-style bulge). */
94739
+ bulge(a2, b) {
94740
+ if (!Number.isFinite(a2) || !Number.isFinite(b)) throw new Error("Surfaces.bridge.bulge: both factors must be finite.");
94741
+ this.bulgeA = a2;
94742
+ this.bulgeB = b;
94743
+ return this;
94744
+ }
94745
+ g0() {
94746
+ return this.build(0);
94747
+ }
94748
+ g1() {
94749
+ return this.build(1);
94750
+ }
94751
+ g2() {
94752
+ return this.build(2);
94753
+ }
94754
+ build(order) {
94755
+ const SAMPLES = 21;
94756
+ const CROSS = 11;
94757
+ const cage = [];
94758
+ for (let i = 0; i < SAMPLES; i++) {
94759
+ const t = i / (SAMPLES - 1);
94760
+ const a2 = edgeJet(this.edgeA, t);
94761
+ const b = edgeJet(this.edgeB, t);
94762
+ const chord = len3(sub3(b.point, a2.point));
94763
+ const toward = unit3(sub3(b.point, a2.point));
94764
+ const tA = orientToward(unit3(a2.cross), toward);
94765
+ const tB = orientToward(unit3(b.cross), [-toward[0], -toward[1], -toward[2]]);
94766
+ const poles = bridgePoles(a2.point, b.point, tA, tB, chord, this.bulgeA, this.bulgeB, order);
94767
+ const row = [];
94768
+ for (let c2 = 0; c2 < CROSS; c2++) row.push(deCasteljau(poles, c2 / (CROSS - 1)));
94769
+ cage.push(row);
94770
+ }
94771
+ return new CurveNetBuilder().cage(cage).degree(Math.min(3, SAMPLES - 1), Math.min(2 * order + 1 || 1, CROSS - 1)).toSheet();
94772
+ }
94773
+ }
94774
+ function orientToward(v, toward) {
94775
+ const dot2 = v[0] * toward[0] + v[1] * toward[1] + v[2] * toward[2];
94776
+ return dot2 < 0 ? [-v[0], -v[1], -v[2]] : v;
94777
+ }
94778
+ function bridgePoles(a2, b, tA, tB, chord, bulgeA, bulgeB, order) {
94779
+ if (order === 0) return [a2, b];
94780
+ const dA = chord * bulgeA / (order === 1 ? 3 : 5);
94781
+ const dB = chord * bulgeB / (order === 1 ? 3 : 5);
94782
+ const a1 = [a2[0] + tA[0] * dA, a2[1] + tA[1] * dA, a2[2] + tA[2] * dA];
94783
+ const b1 = [b[0] + tB[0] * dB, b[1] + tB[1] * dB, b[2] + tB[2] * dB];
94784
+ if (order === 1) return [a2, a1, b1, b];
94785
+ const a22 = [a1[0] + tA[0] * dA, a1[1] + tA[1] * dA, a1[2] + tA[2] * dA];
94786
+ const b22 = [b1[0] + tB[0] * dB, b1[1] + tB[1] * dB, b1[2] + tB[2] * dB];
94787
+ return [a2, a1, a22, b22, b1, b];
94788
+ }
94789
+ function deCasteljau(poles, s) {
94790
+ let pts = poles.map((p2) => [p2[0], p2[1], p2[2]]);
94791
+ while (pts.length > 1) {
94792
+ const next = [];
94793
+ for (let i = 0; i < pts.length - 1; i++) {
94794
+ next.push([
94795
+ pts[i][0] + (pts[i + 1][0] - pts[i][0]) * s,
94796
+ pts[i][1] + (pts[i + 1][1] - pts[i][1]) * s,
94797
+ pts[i][2] + (pts[i + 1][2] - pts[i][2]) * s
94798
+ ]);
94799
+ }
94800
+ pts = next;
94801
+ }
94802
+ return pts[0];
94803
+ }
94118
94804
  const CORNER_Y_ALPHA_ISSUE_URL = "https://github.com/KoStard/forgecad-private/issues/162";
94119
94805
  function isVec3Array(value) {
94120
94806
  return Array.isArray(value) && (value.length === 0 || Array.isArray(value[0]));
@@ -95503,7 +96189,16 @@ const Surface = {
95503
96189
  "Surface.MatchEdge",
95504
96190
  "Surface.Match()",
95505
96191
  (shape, options) => Surface.Match(shape, options)
95506
- )
96192
+ ),
96193
+ /**
96194
+ * Begin a curve-network (Gordon) surface — the class-A keystone. Chain
96195
+ * `.lengthwise(...)/.crosswise(...)` (or `.alongRails(a,b).sections(...)`, or
96196
+ * `.cage(grid)`), then `.thicken(wall)` to get a solid Shape. Returns a fluent
96197
+ * `Sheet` builder with analytic point/normal/curvature queries and named edges.
96198
+ */
96199
+ Net() {
96200
+ return createCurveNet();
96201
+ }
95507
96202
  };
95508
96203
  const Blend = {
95509
96204
  Edge(options) {
@@ -95523,6 +96218,14 @@ const Blend = {
95523
96218
  Surface(options) {
95524
96219
  return Surface.Fill(options);
95525
96220
  },
96221
+ /**
96222
+ * Build a transition strip between two `Surface.Net` sheet edges. Chain
96223
+ * `.bulge(a, b)` then `.g0()/.g1()/.g2()` for the continuity order. Returns a
96224
+ * `Sheet`; verify the seam with `Analysis.EdgeMatch`.
96225
+ */
96226
+ Bridge(edgeA, edgeB) {
96227
+ return bridgeBetween(edgeA, edgeB);
96228
+ },
95526
96229
  /**
95527
96230
  * @alpha
95528
96231
  * Current implementation uses continuity-controlled edge fillets on solid edges.
@@ -95557,6 +96260,14 @@ const Analysis = {
95557
96260
  SurfaceContinuity(shape, options = {}) {
95558
96261
  return evaluateEdgeContinuityReport(shape, options, "Analysis.SurfaceContinuity()");
95559
96262
  },
96263
+ /**
96264
+ * Measure G0/G1/G2 agreement between two `Surface.Net` sheet edges: worst
96265
+ * position gap, cross-boundary tangent angle (0 = G1), and normal-curvature
96266
+ * mismatch (0 = G2). The reflection/fairness check for matched panel seams.
96267
+ */
96268
+ EdgeMatch(edgeA, edgeB, options = {}) {
96269
+ return edgeMatchReport(edgeA, edgeB, options.samples);
96270
+ },
95560
96271
  CurvatureComb(input, options = {}) {
95561
96272
  if (input instanceof NurbsCurve3D) {
95562
96273
  const count = Math.max(8, options.samples ?? 32);
@@ -99001,22 +99712,10 @@ function roundNum(n, digits = 4) {
99001
99712
  return Number.isFinite(n) ? n.toFixed(digits).replace(/\.?0+$/, "") : String(n);
99002
99713
  }
99003
99714
  const COLLISION_OVERLAP_VOLUME_TOLERANCE = 1e-6;
99004
- function meshDerivedManifoldBackend(shape) {
99005
- const mesh = getShapeRuntimeBackend(shape).getMesh();
99006
- return reconstructBackendFromMesh({
99007
- numProp: mesh.numProp,
99008
- triVerts: mesh.triVerts,
99009
- vertProperties: mesh.vertProperties,
99010
- mergeFromVert: mesh.mergeFromVert ?? new Uint32Array(),
99011
- mergeToVert: mesh.mergeToVert ?? new Uint32Array()
99012
- });
99013
- }
99014
99715
  function backendForMinGap(shape) {
99015
99716
  const backend = getShapeRuntimeBackend(shape);
99016
99717
  if (isManifoldCapableBackend(backend)) return { kind: "manifold", backend, method: "exact", dispose: false };
99017
- if (isSdfCapableBackend(backend) || getActiveBackend() === "sdf")
99018
- return { kind: "mesh", backend, method: "mesh-derived", dispose: false };
99019
- return { kind: "manifold", backend: meshDerivedManifoldBackend(shape), method: "mesh-derived", dispose: true };
99718
+ return { kind: "mesh", backend, method: "mesh-derived", dispose: false };
99020
99719
  }
99021
99720
  function meshHasPointInsideSdf(source, target) {
99022
99721
  const mesh = source.getMesh();
@@ -100189,7 +100888,6 @@ function resetExecutionSession(logs) {
100189
100888
  resetHighlights();
100190
100889
  resetBom();
100191
100890
  resetSheetStock();
100192
- resetRobotExport();
100193
100891
  resetCutPlanes();
100194
100892
  resetRenderLabels();
100195
100893
  resetCameraTrajectory();
@@ -100272,7 +100970,6 @@ function collectSuccessfulExecutionSnapshot(args) {
100272
100970
  jointsView: getCollectedJointsView(),
100273
100971
  viewConfig: getCollectedViewConfig(),
100274
100972
  sceneConfig: getCollectedScene(),
100275
- robotExport: getCollectedRobotExport(),
100276
100973
  quality: args.quality,
100277
100974
  logs: args.logs.slice(),
100278
100975
  verifications: getCollectedVerifications(),
@@ -100297,7 +100994,6 @@ function collectFailedExecutionSnapshot(args) {
100297
100994
  jointsView: getCollectedJointsView(),
100298
100995
  viewConfig: getCollectedViewConfig(),
100299
100996
  sceneConfig: getCollectedScene(),
100300
- robotExport: getCollectedRobotExport(),
100301
100997
  quality: args.quality,
100302
100998
  logs: args.logs.slice(),
100303
100999
  verifications: getCollectedVerifications(),
@@ -312858,7 +313554,6 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
312858
313554
  sketchToDxf,
312859
313555
  bom,
312860
313556
  sheetStock,
312861
- robotExport,
312862
313557
  Sim,
312863
313558
  group,
312864
313559
  ShapeGroup,