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
@@ -3268,20 +3268,20 @@ function flatKnotsToKnotsMults(flatKnots) {
3268
3268
  return { knots, mults };
3269
3269
  }
3270
3270
  const KNOT_EPSILON = 1e-10;
3271
- function sameKnot(a2, b) {
3271
+ function sameKnot$1(a2, b) {
3272
3272
  return Math.abs(a2 - b) <= KNOT_EPSILON;
3273
3273
  }
3274
- function knotMultiplicity(knots, u2) {
3275
- return knots.reduce((count, knot) => count + (sameKnot(knot, u2) ? 1 : 0), 0);
3274
+ function knotMultiplicity$1(knots, u2) {
3275
+ return knots.reduce((count, knot) => count + (sameKnot$1(knot, u2) ? 1 : 0), 0);
3276
3276
  }
3277
3277
  function firstKnotIndex(knots, u2) {
3278
- const index2 = knots.findIndex((knot) => sameKnot(knot, u2));
3278
+ const index2 = knots.findIndex((knot) => sameKnot$1(knot, u2));
3279
3279
  if (index2 < 0) throw new Error(`NURBS subdomain extraction could not find boundary knot ${u2}.`);
3280
3280
  return index2;
3281
3281
  }
3282
3282
  function lastKnotIndex(knots, u2) {
3283
3283
  for (let index2 = knots.length - 1; index2 >= 0; index2 -= 1) {
3284
- if (sameKnot(knots[index2], u2)) return index2;
3284
+ if (sameKnot$1(knots[index2], u2)) return index2;
3285
3285
  }
3286
3286
  throw new Error(`NURBS subdomain extraction could not find boundary knot ${u2}.`);
3287
3287
  }
@@ -3294,7 +3294,7 @@ function insertKnotOnceHomogeneous(points, knots, degree, u2) {
3294
3294
  const lastPoint = count - 1;
3295
3295
  const lastKnot = knots.length - 1;
3296
3296
  const span = findSpan(count, degree, u2, knots);
3297
- const multiplicity = knotMultiplicity(knots, u2);
3297
+ const multiplicity = knotMultiplicity$1(knots, u2);
3298
3298
  if (multiplicity >= degree) {
3299
3299
  throw new Error(`NURBS subdomain extraction cannot insert knot ${u2}: multiplicity ${multiplicity} already reaches degree ${degree}.`);
3300
3300
  }
@@ -3318,7 +3318,7 @@ function insertKnotOnceHomogeneous(points, knots, degree, u2) {
3318
3318
  function insertBoundaryToMultiplicity(points, knots, degree, u2, targetMultiplicity) {
3319
3319
  let refinedPoints = points;
3320
3320
  let refinedKnots = knots;
3321
- while (knotMultiplicity(refinedKnots, u2) < targetMultiplicity) {
3321
+ while (knotMultiplicity$1(refinedKnots, u2) < targetMultiplicity) {
3322
3322
  const refined = insertKnotOnceHomogeneous(refinedPoints, refinedKnots, degree, u2);
3323
3323
  refinedPoints = refined.points;
3324
3324
  refinedKnots = refined.knots;
@@ -3338,8 +3338,8 @@ function extractNurbsCurveSubdomain(controlPoints, weights, knots, degree, uStar
3338
3338
  }
3339
3339
  const activeStart = knots[degree];
3340
3340
  const activeEnd = knots[controlPoints.length];
3341
- const start = sameKnot(uStart, activeStart) ? activeStart : sameKnot(uStart, activeEnd) ? activeEnd : uStart;
3342
- const end = sameKnot(uEnd, activeEnd) ? activeEnd : sameKnot(uEnd, activeStart) ? activeStart : uEnd;
3341
+ const start = sameKnot$1(uStart, activeStart) ? activeStart : sameKnot$1(uStart, activeEnd) ? activeEnd : uStart;
3342
+ const end = sameKnot$1(uEnd, activeEnd) ? activeEnd : sameKnot$1(uEnd, activeStart) ? activeStart : uEnd;
3343
3343
  if (start < activeStart - KNOT_EPSILON || end > activeEnd + KNOT_EPSILON) {
3344
3344
  throw new Error(`NURBS subdomain [${uStart}, ${uEnd}] must stay inside active knot domain [${activeStart}, ${activeEnd}].`);
3345
3345
  }
@@ -10242,7 +10242,7 @@ async function initTruckGeometryWasm() {
10242
10242
  if (_initPromise$2) return _initPromise$2;
10243
10243
  _initPromise$2 = (async () => {
10244
10244
  try {
10245
- const geometryModule = await __vitePreload(() => import("./forgecad_geometry-BlMtqluF.js"), true ? [] : void 0);
10245
+ const geometryModule = await __vitePreload(() => import("./forgecad_geometry-CZ_IfuvA.js"), true ? [] : void 0);
10246
10246
  const isNode = isNodeRuntime();
10247
10247
  if (isNode) {
10248
10248
  const { readFileSync, existsSync } = await __vitePreload(async () => {
@@ -11170,23 +11170,62 @@ class NurbsSurface {
11170
11170
  return [wx / wSum, wy / wSum, wz / wSum];
11171
11171
  }
11172
11172
  /**
11173
- * Evaluate the surface normal at (u, v) via cross product of partial derivatives.
11173
+ * Evaluate the surface unit normal at (u, v) from analytic first derivatives.
11174
+ *
11175
+ * Uses Algorithm A2.3 basis-function derivatives with the rational quotient
11176
+ * rule, so the normal is exact (no finite-difference epsilon, no error near
11177
+ * the boundary). Constant chain-rule factors from the parameter remap scale Su
11178
+ * and Sv positively and cancel under normalization, so they are omitted.
11174
11179
  */
11175
11180
  normalAt(u2, v) {
11176
- const eps = 1e-5;
11177
- const u0 = Math.max(0, u2 - eps), u1 = Math.min(1, u2 + eps);
11178
- const v0 = Math.max(0, v - eps), v1 = Math.min(1, v + eps);
11179
- const pu = this.pointAt(u1, v), pmu = this.pointAt(u0, v);
11180
- const pv = this.pointAt(u2, v1), pmv = this.pointAt(u2, v0);
11181
- const du = [pu[0] - pmu[0], pu[1] - pmu[1], pu[2] - pmu[2]];
11182
- const dv = [pv[0] - pmv[0], pv[1] - pmv[1], pv[2] - pmv[2]];
11183
- const nx = du[1] * dv[2] - du[2] * dv[1];
11184
- const ny = du[2] * dv[0] - du[0] * dv[2];
11185
- const nz = du[0] * dv[1] - du[1] * dv[0];
11181
+ const { Su, Sv } = this.derivativesAt(u2, v);
11182
+ const nx = Su[1] * Sv[2] - Su[2] * Sv[1];
11183
+ const ny = Su[2] * Sv[0] - Su[0] * Sv[2];
11184
+ const nz = Su[0] * Sv[1] - Su[1] * Sv[0];
11186
11185
  const len2 = Math.sqrt(nx * nx + ny * ny + nz * nz);
11187
11186
  if (len2 < 1e-12) return [0, 0, 1];
11188
11187
  return [nx / len2, ny / len2, nz / len2];
11189
11188
  }
11189
+ /** Analytic first partial derivatives S_u, S_v (rational quotient rule). */
11190
+ derivativesAt(u2, v) {
11191
+ const uu = this.remapU(Math.max(0, Math.min(1, u2)));
11192
+ const vv = this.remapV(Math.max(0, Math.min(1, v)));
11193
+ const spanU = findSpan(this.nU, this.degreeU, uu, this.knotsU);
11194
+ const spanV = findSpan(this.nV, this.degreeV, vv, this.knotsV);
11195
+ const dU = basisFunsDeriv(spanU, uu, this.degreeU, this.knotsU, 1);
11196
+ const dV = basisFunsDeriv(spanV, vv, this.degreeV, this.knotsV, 1);
11197
+ const A = [0, 0, 0];
11198
+ const Au = [0, 0, 0];
11199
+ const Av = [0, 0, 0];
11200
+ let w2 = 0;
11201
+ let wu = 0;
11202
+ let wv = 0;
11203
+ for (let i = 0; i <= this.degreeU; i++) {
11204
+ const rowIdx = spanU - this.degreeU + i;
11205
+ for (let j = 0; j <= this.degreeV; j++) {
11206
+ const colIdx = spanV - this.degreeV + j;
11207
+ const weight = this.weightsGrid[rowIdx][colIdx];
11208
+ const pt = this.controlGrid[rowIdx][colIdx];
11209
+ const n00 = dU[0][i] * dV[0][j] * weight;
11210
+ const n10 = dU[1][i] * dV[0][j] * weight;
11211
+ const n01 = dU[0][i] * dV[1][j] * weight;
11212
+ w2 += n00;
11213
+ wu += n10;
11214
+ wv += n01;
11215
+ for (let c2 = 0; c2 < 3; c2++) {
11216
+ A[c2] += n00 * pt[c2];
11217
+ Au[c2] += n10 * pt[c2];
11218
+ Av[c2] += n01 * pt[c2];
11219
+ }
11220
+ }
11221
+ }
11222
+ if (w2 === 0) return { S: [0, 0, 0], Su: [0, 0, 0], Sv: [0, 0, 0] };
11223
+ const invW = 1 / w2;
11224
+ const S = [A[0] * invW, A[1] * invW, A[2] * invW];
11225
+ const Su = [(Au[0] - wu * S[0]) * invW, (Au[1] - wu * S[1]) * invW, (Au[2] - wu * S[2]) * invW];
11226
+ const Sv = [(Av[0] - wv * S[0]) * invW, (Av[1] - wv * S[1]) * invW, (Av[2] - wv * S[2]) * invW];
11227
+ return { S, Su, Sv };
11228
+ }
11190
11229
  /**
11191
11230
  * Tessellate the surface into a triangle mesh.
11192
11231
  * Returns positions, normals, and triangle indices.
@@ -12557,7 +12596,7 @@ function maxQuadDeviation(rings, heights, colA, colB) {
12557
12596
  const b = [rings[i][colB][0], rings[i][colB][1], heights[i]];
12558
12597
  const c2 = [rings[i + 1][colB][0], rings[i + 1][colB][1], heights[i + 1]];
12559
12598
  const d2 = [rings[i + 1][colA][0], rings[i + 1][colA][1], heights[i + 1]];
12560
- const n = cross3$7(sub3$6(b, a2), sub3$6(d2, a2));
12599
+ const n = cross3$7(sub3$7(b, a2), sub3$7(d2, a2));
12561
12600
  const len2 = Math.hypot(n[0], n[1], n[2]);
12562
12601
  if (len2 < 1e-12) continue;
12563
12602
  const deviation = Math.abs((n[0] * (c2[0] - a2[0]) + n[1] * (c2[1] - a2[1]) + n[2] * (c2[2] - a2[2])) / len2);
@@ -12833,15 +12872,15 @@ function buildSpanRows(rings, heights) {
12833
12872
  function stationTangent(stations, t, i, j) {
12834
12873
  const R = stations.length;
12835
12874
  if (i === 0) {
12836
- return scale3$2(sub3$6(stations[1][j], stations[0][j]), 1 / (t[1] - t[0]));
12875
+ return scale3$2(sub3$7(stations[1][j], stations[0][j]), 1 / (t[1] - t[0]));
12837
12876
  }
12838
12877
  if (i === R - 1) {
12839
- return scale3$2(sub3$6(stations[R - 1][j], stations[R - 2][j]), 1 / (t[R - 1] - t[R - 2]));
12878
+ return scale3$2(sub3$7(stations[R - 1][j], stations[R - 2][j]), 1 / (t[R - 1] - t[R - 2]));
12840
12879
  }
12841
12880
  const hPrev = t[i] - t[i - 1];
12842
12881
  const hNext = t[i + 1] - t[i];
12843
- const dPrev = scale3$2(sub3$6(stations[i][j], stations[i - 1][j]), 1 / hPrev);
12844
- const dNext = scale3$2(sub3$6(stations[i + 1][j], stations[i][j]), 1 / hNext);
12882
+ const dPrev = scale3$2(sub3$7(stations[i][j], stations[i - 1][j]), 1 / hPrev);
12883
+ const dNext = scale3$2(sub3$7(stations[i + 1][j], stations[i][j]), 1 / hNext);
12845
12884
  return scale3$2(add3$2(scale3$2(dPrev, hNext), scale3$2(dNext, hPrev)), 1 / (hPrev + hNext));
12846
12885
  }
12847
12886
  function hermite(p0, m0, p1, m1, h, u2) {
@@ -12897,12 +12936,12 @@ function stitchSingleLoopLoft(loops, heights, wasm, options) {
12897
12936
  const curr = points[j];
12898
12937
  const next = points[(j + 1) % N];
12899
12938
  if (cornerSet.has(j)) {
12900
- const nFwd = surfaceNormal(sub3$6(next, curr), tangents[j]);
12901
- const nBwd = surfaceNormal(sub3$6(curr, prev), tangents[j]);
12939
+ const nFwd = surfaceNormal(sub3$7(next, curr), tangents[j]);
12940
+ const nBwd = surfaceNormal(sub3$7(curr, prev), tangents[j]);
12902
12941
  fwd[j] = pushVert(curr, nFwd);
12903
12942
  bwd[j] = pushVert(curr, nBwd);
12904
12943
  } else {
12905
- const idx = pushVert(curr, surfaceNormal(sub3$6(next, prev), tangents[j]));
12944
+ const idx = pushVert(curr, surfaceNormal(sub3$7(next, prev), tangents[j]));
12906
12945
  fwd[j] = idx;
12907
12946
  bwd[j] = idx;
12908
12947
  }
@@ -12963,7 +13002,7 @@ function surfaceNormal(chord, span) {
12963
13002
  }
12964
13003
  return [n[0] / len2, n[1] / len2, n[2] / len2];
12965
13004
  }
12966
- function sub3$6(a2, b) {
13005
+ function sub3$7(a2, b) {
12967
13006
  return [a2[0] - b[0], a2[1] - b[1], a2[2] - b[2]];
12968
13007
  }
12969
13008
  function add3$2(a2, b) {
@@ -12990,7 +13029,7 @@ async function initManifoldWasm() {
12990
13029
  if (_wasm) return _wasm;
12991
13030
  performance.mark("manifold:start");
12992
13031
  const Module = (await __vitePreload(async () => {
12993
- const { default: __vite_default__ } = await import("./manifold-C4r6B-XY.js");
13032
+ const { default: __vite_default__ } = await import("./manifold-CBry38ly.js");
12994
13033
  return { default: __vite_default__ };
12995
13034
  }, true ? [] : void 0)).default;
12996
13035
  performance.mark("manifold:imported");
@@ -13617,6 +13656,20 @@ function fromSlicesSingleSliceHalfExtentForManifold(plan) {
13617
13656
  }
13618
13657
  return Math.max(1, radius + maxOffsetMagnitude + plan.boundsPadding + plan.edgeLength * 3);
13619
13658
  }
13659
+ const BOOLEAN_OPERAND_NORMAL_SHARP_ANGLE_DEG = 60;
13660
+ function promoteBooleanOperandNormals(shapes) {
13661
+ let maxExtra = 0;
13662
+ for (const shape of shapes) maxExtra = Math.max(maxExtra, shape.numProp());
13663
+ if (maxExtra < 3) return { operands: shapes, created: [] };
13664
+ const created = [];
13665
+ const operands = shapes.map((shape) => {
13666
+ if (shape.numProp() >= 3) return shape;
13667
+ const promoted = shape.calculateNormals(0, BOOLEAN_OPERAND_NORMAL_SHARP_ANGLE_DEG);
13668
+ created.push(promoted);
13669
+ return promoted;
13670
+ });
13671
+ return { operands, created };
13672
+ }
13620
13673
  function lowerShapeBooleanCompilePlan(plan, wasm) {
13621
13674
  const shapes = plan.shapes.map((shape) => lowerShapeCompilePlanToManifold(shape, wasm));
13622
13675
  if (shapes.length === 0) {
@@ -13625,16 +13678,18 @@ function lowerShapeBooleanCompilePlan(plan, wasm) {
13625
13678
  if (shapes.length === 1) {
13626
13679
  return shapes[0];
13627
13680
  }
13681
+ const { operands, created } = promoteBooleanOperandNormals(shapes);
13628
13682
  try {
13629
13683
  switch (plan.op) {
13630
13684
  case "union":
13631
- return wasm.Manifold.union(shapes);
13685
+ return wasm.Manifold.union(operands);
13632
13686
  case "difference":
13633
- return wasm.Manifold.difference(shapes);
13687
+ return wasm.Manifold.difference(operands);
13634
13688
  case "intersection":
13635
- return wasm.Manifold.intersection(shapes);
13689
+ return wasm.Manifold.intersection(operands);
13636
13690
  }
13637
13691
  } finally {
13692
+ disposeWasmObjects(created);
13638
13693
  disposeWasmObjects(shapes);
13639
13694
  }
13640
13695
  }
@@ -14984,12 +15039,16 @@ function lowerNurbsSurfaceToManifold(plan, wasm) {
14984
15039
  const { positions, normals, indices } = surface.tessellate(res, res);
14985
15040
  const thickness = plan.thickness;
14986
15041
  const numVerts = positions.length;
14987
- const allPositions = [];
14988
- for (const [x2, y2, z2] of positions) allPositions.push(x2, y2, z2);
15042
+ const vertProps = [];
14989
15043
  for (let i = 0; i < numVerts; i++) {
14990
15044
  const [x2, y2, z2] = positions[i];
14991
15045
  const [nx, ny, nz] = normals[i];
14992
- allPositions.push(x2 - nx * thickness, y2 - ny * thickness, z2 - nz * thickness);
15046
+ vertProps.push(x2, y2, z2, nx, ny, nz);
15047
+ }
15048
+ for (let i = 0; i < numVerts; i++) {
15049
+ const [x2, y2, z2] = positions[i];
15050
+ const [nx, ny, nz] = normals[i];
15051
+ vertProps.push(x2 - nx * thickness, y2 - ny * thickness, z2 - nz * thickness, -nx, -ny, -nz);
14993
15052
  }
14994
15053
  const allIndices = [];
14995
15054
  for (const idx of indices) allIndices.push(idx);
@@ -15014,11 +15073,12 @@ function lowerNurbsSurfaceToManifold(plan, wasm) {
15014
15073
  allIndices.push(c2, c2 + numVerts, d2, d2, c2 + numVerts, d2 + numVerts);
15015
15074
  }
15016
15075
  const mesh = new wasm.Mesh({
15017
- numProp: 3,
15018
- vertProperties: new Float32Array(allPositions),
15076
+ numProp: 6,
15077
+ vertProperties: new Float32Array(vertProps),
15019
15078
  triVerts: new Uint32Array(allIndices)
15020
15079
  });
15021
15080
  try {
15081
+ mesh.merge();
15022
15082
  return new wasm.Manifold(mesh);
15023
15083
  } finally {
15024
15084
  disposeWasmObject(mesh);
@@ -59626,7 +59686,7 @@ function uvInsideTrim(uv, trim) {
59626
59686
  function add3$1(a2, b) {
59627
59687
  return [a2[0] + b[0], a2[1] + b[1], a2[2] + b[2]];
59628
59688
  }
59629
- function sub3$5(a2, b) {
59689
+ function sub3$6(a2, b) {
59630
59690
  return [a2[0] - b[0], a2[1] - b[1], a2[2] - b[2]];
59631
59691
  }
59632
59692
  function scale3$1(v, scale2) {
@@ -59672,7 +59732,7 @@ function resamplePolyline$1(points, count) {
59672
59732
  if (points.length === 1 || count <= 1) return [points[0]];
59673
59733
  const cumulative = [0];
59674
59734
  for (let index2 = 1; index2 < points.length; index2 += 1) {
59675
- cumulative.push(cumulative[index2 - 1] + Math.hypot(...sub3$5(points[index2], points[index2 - 1])));
59735
+ cumulative.push(cumulative[index2 - 1] + Math.hypot(...sub3$6(points[index2], points[index2 - 1])));
59676
59736
  }
59677
59737
  const total = cumulative[cumulative.length - 1];
59678
59738
  if (total <= 1e-12) return Array.from({ length: count }, () => points[0]);
@@ -59833,7 +59893,7 @@ function computeGridNormals(positions, indices) {
59833
59893
  const a2 = positions[indices[index2]];
59834
59894
  const b = positions[indices[index2 + 1]];
59835
59895
  const c2 = positions[indices[index2 + 2]];
59836
- const normal2 = cross3$5(sub3$5(b, a2), sub3$5(c2, a2));
59896
+ const normal2 = cross3$5(sub3$6(b, a2), sub3$6(c2, a2));
59837
59897
  for (const vertexIndex of [indices[index2], indices[index2 + 1], indices[index2 + 2]]) {
59838
59898
  normals[vertexIndex][0] += normal2[0];
59839
59899
  normals[vertexIndex][1] += normal2[1];
@@ -59926,16 +59986,16 @@ function planarProjectionFrame(points, context) {
59926
59986
  const tolerance = pointTolerance(points) * 100;
59927
59987
  const uSource = points.find((point2) => distance3$1(point2, origin) > tolerance);
59928
59988
  if (!uSource) throw new Error(`${context} boundary loop collapsed to one point.`);
59929
- const u2 = normalize3$4(sub3$5(uSource, origin), `${context} u axis`);
59989
+ const u2 = normalize3$4(sub3$6(uSource, origin), `${context} u axis`);
59930
59990
  const v = cross3$5(normal2, u2);
59931
- const maxPlaneError = points.reduce((max2, point2) => Math.max(max2, Math.abs(dot3$7(sub3$5(point2, origin), normal2))), 0);
59991
+ const maxPlaneError = points.reduce((max2, point2) => Math.max(max2, Math.abs(dot3$7(sub3$6(point2, origin), normal2))), 0);
59932
59992
  if (maxPlaneError > tolerance) throw new Error(`${context} boundary loop must be planar for sampled SDF triangulation.`);
59933
59993
  return { origin, u: u2, v, tolerance };
59934
59994
  }
59935
59995
  function meshFromPlanarBoundaryLoop(loop, context) {
59936
59996
  const frame = planarProjectionFrame(loop, context);
59937
59997
  const projected = loop.map((point2) => {
59938
- const relative = sub3$5(point2, frame.origin);
59998
+ const relative = sub3$6(point2, frame.origin);
59939
59999
  return new Vector2(dot3$7(relative, frame.u), dot3$7(relative, frame.v));
59940
60000
  });
59941
60001
  const triangles = ShapeUtils.triangulateShape(projected, []);
@@ -59944,7 +60004,7 @@ function meshFromPlanarBoundaryLoop(loop, context) {
59944
60004
  const indices = [];
59945
60005
  for (const triangle of triangles) {
59946
60006
  const [a2, b, c2] = triangle;
59947
- const areaNormal = cross3$5(sub3$5(positions[b], positions[a2]), sub3$5(positions[c2], positions[a2]));
60007
+ const areaNormal = cross3$5(sub3$6(positions[b], positions[a2]), sub3$6(positions[c2], positions[a2]));
59948
60008
  if (Math.hypot(...areaNormal) > frame.tolerance * frame.tolerance) indices.push(a2, b, c2);
59949
60009
  }
59950
60010
  if (indices.length === 0) throw new Error(`${context} boundary loop collapsed to zero area.`);
@@ -68462,7 +68522,7 @@ function reconstructSdfBackendFromMesh(mesh) {
68462
68522
  return wrapSdfMeshShapeBackend(boundsFromMeshBackend(runtimeMesh, edgeLength2), runtimeMesh);
68463
68523
  }
68464
68524
  const EPS$d = 1e-8;
68465
- function sub3$4(a2, b) {
68525
+ function sub3$5(a2, b) {
68466
68526
  return [a2[0] - b[0], a2[1] - b[1], a2[2] - b[2]];
68467
68527
  }
68468
68528
  function unit(v, context) {
@@ -68485,13 +68545,13 @@ function loopNormal(points) {
68485
68545
  }
68486
68546
  function firstEdgeDirection(points, filePath, context) {
68487
68547
  for (let index2 = 0; index2 < points.length; index2 += 1) {
68488
- const edge = sub3$4(points[(index2 + 1) % points.length], points[index2]);
68548
+ const edge = sub3$5(points[(index2 + 1) % points.length], points[index2]);
68489
68549
  if (Math.hypot(edge[0], edge[1], edge[2]) > EPS$d) return unit(edge, context);
68490
68550
  }
68491
68551
  failStepImport(filePath, `${context} face loop has no non-zero edges.`);
68492
68552
  }
68493
68553
  function project(point2, origin, u2, v) {
68494
- const local = sub3$4(point2, origin);
68554
+ const local = sub3$5(point2, origin);
68495
68555
  return new Vector2(dot3$7(local, u2), dot3$7(local, v));
68496
68556
  }
68497
68557
  function triangulateStepFace(face, filePath) {
@@ -68515,7 +68575,7 @@ function triangulateStepFace(face, filePath) {
68515
68575
  const a2 = allPoints[ia];
68516
68576
  const b = allPoints[ib];
68517
68577
  const c2 = allPoints[ic];
68518
- const triNormal = cross3$5(sub3$4(b, a2), sub3$4(c2, a2));
68578
+ const triNormal = cross3$5(sub3$5(b, a2), sub3$5(c2, a2));
68519
68579
  if (Math.hypot(triNormal[0], triNormal[1], triNormal[2]) <= EPS$d) {
68520
68580
  failStepImport(filePath, `${face.context} triangulates to a degenerate triangle.`);
68521
68581
  }
@@ -69557,7 +69617,7 @@ function angleDistance(a2, b) {
69557
69617
  return Math.abs(normalizeAngleDelta(a2 - b));
69558
69618
  }
69559
69619
  function torusMajorAngle(frame, point2, context) {
69560
- const local = sub3$3(point2, frame.origin);
69620
+ const local = sub3$4(point2, frame.origin);
69561
69621
  const x2 = dot3$7(local, frame.xAxis);
69562
69622
  const y2 = dot3$7(local, frame.yAxis);
69563
69623
  if (Math.hypot(x2, y2) <= 1e-12) throw new Error(`${context} torus major angle is degenerate.`);
@@ -69573,11 +69633,11 @@ function torusRadialDirection(frame, u2) {
69573
69633
  function torusTubeAngle(frame, majorRadius, u2, point2) {
69574
69634
  const radial = torusRadialDirection(frame, u2);
69575
69635
  const tubeCenter = addScaled$2(frame.origin, radial, majorRadius);
69576
- const local = sub3$3(point2, tubeCenter);
69636
+ const local = sub3$4(point2, tubeCenter);
69577
69637
  return Math.atan2(dot3$7(local, frame.axis), dot3$7(local, radial));
69578
69638
  }
69579
69639
  function rotateAroundAxis(point2, axis, angle) {
69580
- const local = sub3$3(point2, axis.origin);
69640
+ const local = sub3$4(point2, axis.origin);
69581
69641
  const cos2 = Math.cos(angle);
69582
69642
  const sin2 = Math.sin(angle);
69583
69643
  const cross4 = cross3$5(axis.axis, local);
@@ -69589,12 +69649,12 @@ function rotateAroundAxis(point2, axis, angle) {
69589
69649
  ];
69590
69650
  }
69591
69651
  function distanceFromAxis(point2, axis) {
69592
- const local = sub3$3(point2, axis.origin);
69652
+ const local = sub3$4(point2, axis.origin);
69593
69653
  const axial = dot3$7(local, axis.axis);
69594
69654
  return Math.hypot(local[0] - axis.axis[0] * axial, local[1] - axis.axis[1] * axial, local[2] - axis.axis[2] * axial);
69595
69655
  }
69596
69656
  function radialFromAxis(frame, point2, context) {
69597
- const local = sub3$3(point2, frame.origin);
69657
+ const local = sub3$4(point2, frame.origin);
69598
69658
  const axial = dot3$7(local, frame.axis);
69599
69659
  const radialVector = [local[0] - frame.axis[0] * axial, local[1] - frame.axis[1] * axial, local[2] - frame.axis[2] * axial];
69600
69660
  return { axial, radial: Math.hypot(radialVector[0], radialVector[1], radialVector[2]), direction: normalize3$4(radialVector, context) };
@@ -69663,9 +69723,9 @@ function triangulateBoundedLinearSurfaceOfRevolution(records, curveId, axis, loo
69663
69723
  return triangulateBoundedLineCurveSurfaceOfRevolution(line2, axis, loop, sameSense, filePath, context);
69664
69724
  }
69665
69725
  function triangulateBoundedLineCurveSurfaceOfRevolution(line2, axis, loop, sameSense, filePath, context) {
69666
- const startAxial = dot3$7(sub3$3(line2.point, axis.origin), axis.axis);
69726
+ const startAxial = dot3$7(sub3$4(line2.point, axis.origin), axis.axis);
69667
69727
  const end = linePoint(line2, 1);
69668
- const endAxial = dot3$7(sub3$3(end, axis.origin), axis.axis);
69728
+ const endAxial = dot3$7(sub3$4(end, axis.origin), axis.axis);
69669
69729
  const axialSpan = endAxial - startAxial;
69670
69730
  if (Math.abs(axialSpan) <= 1e-8)
69671
69731
  failStepImport(filePath, `${context} bounded SURFACE_OF_REVOLUTION LINE profile must change along the revolution axis.`);
@@ -69694,7 +69754,7 @@ function trimmedLineCurveForSurfaceOfRevolution(records, curve, filePath, contex
69694
69754
  const sameSense = stepBoolean(curve.args[4], `TRIMMED_CURVE #${curve.id} sense agreement`);
69695
69755
  const point2 = sameSense ? start : end;
69696
69756
  const target = sameSense ? end : start;
69697
- const delta = sub3$3(target, point2);
69757
+ const delta = sub3$4(target, point2);
69698
69758
  const magnitude = Math.hypot(delta[0], delta[1], delta[2]);
69699
69759
  if (magnitude <= 1e-10) failStepImport(filePath, `${context} TRIMMED_CURVE LINE profile has zero length.`);
69700
69760
  return {
@@ -69705,10 +69765,10 @@ function trimmedLineCurveForSurfaceOfRevolution(records, curve, filePath, contex
69705
69765
  }
69706
69766
  function revolvedCircleTorus(records, curveId, axis, filePath, context) {
69707
69767
  const circle2 = resolveCircle(records, curveId, filePath);
69708
- const centerOffset = sub3$3(circle2.frame.origin, axis.origin);
69768
+ const centerOffset = sub3$4(circle2.frame.origin, axis.origin);
69709
69769
  const axial = dot3$7(centerOffset, axis.axis);
69710
69770
  const axisPoint = addScaled$2(axis.origin, axis.axis, axial);
69711
- const radial = sub3$3(circle2.frame.origin, axisPoint);
69771
+ const radial = sub3$4(circle2.frame.origin, axisPoint);
69712
69772
  const majorRadius = Math.hypot(radial[0], radial[1], radial[2]);
69713
69773
  if (majorRadius <= 1e-8) failStepImport(filePath, `${context} SURFACE_OF_REVOLUTION circle profile is centered on the axis.`);
69714
69774
  if (majorRadius <= circle2.radius)
@@ -69720,7 +69780,7 @@ function revolvedCircleTorus(records, curveId, axis, filePath, context) {
69720
69780
  return { frame, majorRadius, minorRadius: circle2.radius };
69721
69781
  }
69722
69782
  function revolutionAngle(frame, point2, filePath, context) {
69723
- const local = sub3$3(point2, frame.origin);
69783
+ const local = sub3$4(point2, frame.origin);
69724
69784
  const x2 = dot3$7(local, frame.xAxis);
69725
69785
  const y2 = dot3$7(local, frame.yAxis);
69726
69786
  if (Math.hypot(x2, y2) <= 1e-12) failStepImport(filePath, `${context} major angle is degenerate.`);
@@ -69762,10 +69822,10 @@ function orientRevolvedProfileTriangles(triangles, frame, axis, profileCenter, p
69762
69822
  ];
69763
69823
  const angle = revolutionAngle(frame, sample, filePath, "SURFACE_OF_REVOLUTION");
69764
69824
  const center = rotateAroundAxis(profileCenter, axis, normalizeAngleDelta(angle - profileAngle));
69765
- const outward = sub3$3(sample, center);
69825
+ const outward = sub3$4(sample, center);
69766
69826
  if (Math.hypot(outward[0], outward[1], outward[2]) <= 1e-12)
69767
69827
  failStepImport(filePath, "SURFACE_OF_REVOLUTION sample hit degenerate profile centerline.");
69768
- const normal2 = cross3$5(sub3$3(triangle[1], triangle[0]), sub3$3(triangle[2], triangle[0]));
69828
+ const normal2 = cross3$5(sub3$4(triangle[1], triangle[0]), sub3$4(triangle[2], triangle[0]));
69769
69829
  const outwardFacing = dot3$7(normal2, outward) >= 0;
69770
69830
  return sameSense === outwardFacing ? triangle : reverseTriangle(triangle);
69771
69831
  });
@@ -69849,7 +69909,7 @@ function triangulateBoundedToroidalTubeSegment(frame, majorRadius, minorRadius,
69849
69909
  const loop = compactLoops[loopIndex];
69850
69910
  const majorAngle = majorAngles[loopIndex];
69851
69911
  for (const point2 of loop) {
69852
- const local = sub3$3(point2, frame.origin);
69912
+ const local = sub3$4(point2, frame.origin);
69853
69913
  const x2 = dot3$7(local, frame.xAxis);
69854
69914
  const y2 = dot3$7(local, frame.yAxis);
69855
69915
  const radialLength = Math.hypot(x2, y2);
@@ -70384,7 +70444,7 @@ function triangulateClosedVBSplineSurfaceBand(surface, loops, sameSense, filePat
70384
70444
  }
70385
70445
  return orientBridgeTriangles(triangles, centroid(a2.points), centroid(b.points), sameSense);
70386
70446
  }
70387
- function sub3$3(a2, b) {
70447
+ function sub3$4(a2, b) {
70388
70448
  return [a2[0] - b[0], a2[1] - b[1], a2[2] - b[2]];
70389
70449
  }
70390
70450
  function newellNormal(points) {
@@ -70423,9 +70483,9 @@ function triangulateBoundedSphericalCap(frame, radius, loop, sameSense, filePath
70423
70483
  }
70424
70484
  const boundaryCenter = centroid(boundary);
70425
70485
  const capDirection = normalize3$4(newellNormal(boundary), `${context} bounded SPHERICAL_SURFACE boundary normal`);
70426
- const maxPlaneError = boundary.reduce((max2, point2) => Math.max(max2, Math.abs(dot3$7(sub3$3(point2, boundaryCenter), capDirection))), 0);
70486
+ const maxPlaneError = boundary.reduce((max2, point2) => Math.max(max2, Math.abs(dot3$7(sub3$4(point2, boundaryCenter), capDirection))), 0);
70427
70487
  if (maxPlaneError > tolerance) failStepImport(filePath, `${context} bounded SPHERICAL_SURFACE boundary loop is not planar.`);
70428
- const boundaryDirections = boundary.map((point2) => normalize3$4(sub3$3(point2, frame.origin), `${context} bounded SPHERICAL_SURFACE point`));
70488
+ const boundaryDirections = boundary.map((point2) => normalize3$4(sub3$4(point2, frame.origin), `${context} bounded SPHERICAL_SURFACE point`));
70429
70489
  const maxAngle = boundaryDirections.reduce((max2, direction2) => Math.max(max2, Math.acos(clamp$8(dot3$7(direction2, capDirection), -1, 1))), 0);
70430
70490
  if (Math.PI - maxAngle <= 1e-6) failStepImport(filePath, `${context} bounded SPHERICAL_SURFACE cap is degenerate.`);
70431
70491
  const radialSegments = Math.max(4, Math.ceil(maxAngle / (Math.PI / 24)));
@@ -70464,17 +70524,17 @@ function sphericalLoopInfo(frame, radius, loop, filePath, context) {
70464
70524
  }
70465
70525
  const center = centroid(points);
70466
70526
  const normal2 = normalize3$4(newellNormal(points), `${context} bounded SPHERICAL_SURFACE boundary normal`);
70467
- const maxPlaneError = points.reduce((max2, point2) => Math.max(max2, Math.abs(dot3$7(sub3$3(point2, center), normal2))), 0);
70527
+ const maxPlaneError = points.reduce((max2, point2) => Math.max(max2, Math.abs(dot3$7(sub3$4(point2, center), normal2))), 0);
70468
70528
  if (maxPlaneError > tolerance) failStepImport(filePath, `${context} bounded SPHERICAL_SURFACE boundary loop is not planar.`);
70469
70529
  return { points, center, normal: normal2 };
70470
70530
  }
70471
70531
  function triangulateBoundedSphericalBand(frame, radius, loops, sameSense, filePath, context) {
70472
70532
  if (loops.length !== 2) failStepImport(filePath, `${context} bounded SPHERICAL_SURFACE band supports exactly two loops right now.`);
70473
70533
  const [aInfo, bInfo] = loops.map((loop) => sphericalLoopInfo(frame, radius, loop, filePath, context));
70474
- const axis = normalize3$4(sub3$3(bInfo.center, aInfo.center), `${context} bounded SPHERICAL_SURFACE band axis`);
70534
+ const axis = normalize3$4(sub3$4(bInfo.center, aInfo.center), `${context} bounded SPHERICAL_SURFACE band axis`);
70475
70535
  const tolerance = Math.max(1e-5, radius * 1e-4);
70476
70536
  const axisPointError = (point2) => {
70477
- const local = sub3$3(point2, frame.origin);
70537
+ const local = sub3$4(point2, frame.origin);
70478
70538
  const projected = addScaled$2(frame.origin, axis, dot3$7(local, axis));
70479
70539
  return Math.hypot(point2[0] - projected[0], point2[1] - projected[1], point2[2] - projected[2]);
70480
70540
  };
@@ -70483,12 +70543,12 @@ function triangulateBoundedSphericalBand(frame, radius, loops, sameSense, filePa
70483
70543
  const count = Math.max(aInfo.points.length, bInfo.points.length, 8);
70484
70544
  const aLoop = resampleClosedLoop(aInfo.points, count);
70485
70545
  let bLoop = resampleClosedLoop(bInfo.points, count);
70486
- const rawX = sub3$3(aLoop[0], aInfo.center);
70546
+ const rawX = sub3$4(aLoop[0], aInfo.center);
70487
70547
  const xProjection = dot3$7(rawX, axis);
70488
70548
  const xAxis = normalize3$4([rawX[0] - axis[0] * xProjection, rawX[1] - axis[1] * xProjection, rawX[2] - axis[2] * xProjection], context);
70489
70549
  const yAxis = cross3$5(axis, xAxis);
70490
70550
  const loopAngle = (point2, center) => {
70491
- const local = sub3$3(point2, center);
70551
+ const local = sub3$4(point2, center);
70492
70552
  return Math.atan2(dot3$7(local, yAxis), dot3$7(local, xAxis));
70493
70553
  };
70494
70554
  const rotateLoop = (loop, center, targetAngle) => {
@@ -70509,8 +70569,8 @@ function triangulateBoundedSphericalBand(frame, radius, loops, sameSense, filePa
70509
70569
  const reversed = rotateLoop([...bLoop].reverse(), bInfo.center, aAngles[0]);
70510
70570
  bLoop = sequenceMismatch(forward) <= sequenceMismatch(reversed) ? forward : reversed;
70511
70571
  const angularSpan = aLoop.reduce((max2, point2, index2) => {
70512
- const aDirection = normalize3$4(sub3$3(point2, frame.origin), `${context} bounded SPHERICAL_SURFACE band point`);
70513
- const bDirection = normalize3$4(sub3$3(bLoop[index2], frame.origin), `${context} bounded SPHERICAL_SURFACE band point`);
70572
+ const aDirection = normalize3$4(sub3$4(point2, frame.origin), `${context} bounded SPHERICAL_SURFACE band point`);
70573
+ const bDirection = normalize3$4(sub3$4(bLoop[index2], frame.origin), `${context} bounded SPHERICAL_SURFACE band point`);
70514
70574
  return Math.max(max2, Math.acos(clamp$8(dot3$7(aDirection, bDirection), -1, 1)));
70515
70575
  }, 0);
70516
70576
  if (angularSpan <= 1e-6) failStepImport(filePath, `${context} bounded SPHERICAL_SURFACE band has zero angular span.`);
@@ -70520,8 +70580,8 @@ function triangulateBoundedSphericalBand(frame, radius, loops, sameSense, filePa
70520
70580
  const t = ringIndex / radialSegments;
70521
70581
  rings.push(
70522
70582
  aLoop.map((point2, index2) => {
70523
- const aDirection = normalize3$4(sub3$3(point2, frame.origin), `${context} bounded SPHERICAL_SURFACE band point`);
70524
- const bDirection = normalize3$4(sub3$3(bLoop[index2], frame.origin), `${context} bounded SPHERICAL_SURFACE band point`);
70583
+ const aDirection = normalize3$4(sub3$4(point2, frame.origin), `${context} bounded SPHERICAL_SURFACE band point`);
70584
+ const bDirection = normalize3$4(sub3$4(bLoop[index2], frame.origin), `${context} bounded SPHERICAL_SURFACE band point`);
70525
70585
  return addScaled$2(frame.origin, slerpUnit(aDirection, bDirection, t), radius);
70526
70586
  })
70527
70587
  );
@@ -72570,14 +72630,14 @@ function cross$7(a2, b) {
72570
72630
  function isFiniteNumber$1(value) {
72571
72631
  return typeof value === "number" && Number.isFinite(value);
72572
72632
  }
72573
- function isVec3$2(value) {
72633
+ function isVec3$3(value) {
72574
72634
  return Array.isArray(value) && value.length === 3 && value.every(isFiniteNumber$1);
72575
72635
  }
72576
72636
  function isVec2(value) {
72577
72637
  return Array.isArray(value) && value.length === 2 && value.every(isFiniteNumber$1);
72578
72638
  }
72579
72639
  function isRuledRails(value) {
72580
- return Array.isArray(value) && value.length === 2 && value.every((rail2) => Array.isArray(rail2) && rail2.length === 2 && rail2.every(isVec3$2));
72640
+ return Array.isArray(value) && value.length === 2 && value.every((rail2) => Array.isArray(rail2) && rail2.length === 2 && rail2.every(isVec3$3));
72581
72641
  }
72582
72642
  function axisSpan(face, axis) {
72583
72643
  if (face.vertices.length === 0) return 0;
@@ -72594,14 +72654,14 @@ function isRationalWeights(weights) {
72594
72654
  return Boolean(weights == null ? void 0 : weights.some((row) => row.some((weight) => Math.abs(weight - 1) > 1e-9)));
72595
72655
  }
72596
72656
  function explicitGeometrySurface(geometry, face) {
72597
- if ("Plane" in geometry && isVec3$2(geometry.Plane.normal)) {
72657
+ if ("Plane" in geometry && isVec3$3(geometry.Plane.normal)) {
72598
72658
  return { kind: "plane", normal: normalizeVec3$3(geometry.Plane.normal) };
72599
72659
  }
72600
72660
  if ("AnalyticCylinder" in geometry) {
72601
72661
  const origin = geometry.AnalyticCylinder.axis_origin;
72602
72662
  const axis = geometry.AnalyticCylinder.axis_direction;
72603
72663
  const radius = geometry.AnalyticCylinder.radius;
72604
- if (isVec3$2(origin) && isVec3$2(axis) && isFiniteNumber$1(radius) && radius > 0) {
72664
+ if (isVec3$3(origin) && isVec3$3(axis) && isFiniteNumber$1(radius) && radius > 0) {
72605
72665
  const normalizedAxis = normalizeVec3$3(axis);
72606
72666
  return { kind: "cylinder", origin, axis: normalizedAxis, radius, height: axisSpan(face, normalizedAxis) };
72607
72667
  }
@@ -72611,7 +72671,7 @@ function explicitGeometrySurface(geometry, face) {
72611
72671
  const axis = geometry.AnalyticCone.axis_direction;
72612
72672
  const radiusBottom = geometry.AnalyticCone.radius_start;
72613
72673
  const radiusTop = geometry.AnalyticCone.radius_end;
72614
- if (isVec3$2(origin) && isVec3$2(axis) && isFiniteNumber$1(radiusBottom) && isFiniteNumber$1(radiusTop)) {
72674
+ if (isVec3$3(origin) && isVec3$3(axis) && isFiniteNumber$1(radiusBottom) && isFiniteNumber$1(radiusTop)) {
72615
72675
  const normalizedAxis = normalizeVec3$3(axis);
72616
72676
  return {
72617
72677
  kind: "cone",
@@ -72626,7 +72686,7 @@ function explicitGeometrySurface(geometry, face) {
72626
72686
  if ("AnalyticSphere" in geometry) {
72627
72687
  const center = geometry.AnalyticSphere.center;
72628
72688
  const radius = geometry.AnalyticSphere.radius;
72629
- if (isVec3$2(center) && isFiniteNumber$1(radius) && radius > 0) {
72689
+ if (isVec3$3(center) && isFiniteNumber$1(radius) && radius > 0) {
72630
72690
  return { kind: "sphere", center, radius };
72631
72691
  }
72632
72692
  }
@@ -72635,7 +72695,7 @@ function explicitGeometrySurface(geometry, face) {
72635
72695
  const axis = geometry.AnalyticTorus.axis;
72636
72696
  const majorRadius = geometry.AnalyticTorus.major_radius;
72637
72697
  const minorRadius = geometry.AnalyticTorus.minor_radius;
72638
- if (isVec3$2(center) && isVec3$2(axis) && isFiniteNumber$1(majorRadius) && isFiniteNumber$1(minorRadius) && majorRadius > 0 && minorRadius > 0) {
72698
+ if (isVec3$3(center) && isVec3$3(axis) && isFiniteNumber$1(majorRadius) && isFiniteNumber$1(minorRadius) && majorRadius > 0 && minorRadius > 0) {
72639
72699
  return { kind: "torus", center, axis: normalizeVec3$3(axis), majorRadius, minorRadius };
72640
72700
  }
72641
72701
  }
@@ -72733,7 +72793,7 @@ function explicitEdgeCurve(geometry, faceName) {
72733
72793
  const center = (_a3 = geometry.CircularArc) == null ? void 0 : _a3.center;
72734
72794
  const axis = (_b3 = geometry.CircularArc) == null ? void 0 : _b3.axis;
72735
72795
  const radius = (_c2 = geometry.CircularArc) == null ? void 0 : _c2.radius;
72736
- if (center !== void 0 && axis !== void 0 && radius !== void 0 && isVec3$2(center) && isVec3$2(axis) && isFiniteNumber$1(radius) && radius > 0) {
72796
+ if (center !== void 0 && axis !== void 0 && radius !== void 0 && isVec3$3(center) && isVec3$3(axis) && isFiniteNumber$1(radius) && radius > 0) {
72737
72797
  return {
72738
72798
  kind: "circle",
72739
72799
  center,
@@ -72744,7 +72804,7 @@ function explicitEdgeCurve(geometry, faceName) {
72744
72804
  }
72745
72805
  const start = (_d2 = geometry.Line) == null ? void 0 : _d2.start;
72746
72806
  const end = (_e2 = geometry.Line) == null ? void 0 : _e2.end;
72747
- if (isVec3$2(start) && isVec3$2(end)) return makeLineEdgeCurve(start, end, faceName);
72807
+ if (isVec3$3(start) && isVec3$3(end)) return makeLineEdgeCurve(start, end, faceName);
72748
72808
  const polyline = geometry.PolylineUv;
72749
72809
  if (polyline && Array.isArray(polyline.points)) {
72750
72810
  const points = polyline.points.filter(isVec2);
@@ -73089,7 +73149,7 @@ function topologyPayloadToTopology(payload) {
73089
73149
  if (explicitEdge.visual === false) continue;
73090
73150
  const start = explicitVertices[explicitEdge.vertices[0]];
73091
73151
  const end = explicitVertices[explicitEdge.vertices[1]];
73092
- if (!isVec3$2(start) || !isVec3$2(end)) continue;
73152
+ if (!isVec3$3(start) || !isVec3$3(end)) continue;
73093
73153
  const key2 = edgeKey(start, end);
73094
73154
  if (seenEdges.has(key2)) continue;
73095
73155
  seenEdges.set(key2, edges.size);
@@ -73253,6 +73313,7 @@ const _TruckShapeBackend = class _TruckShapeBackend {
73253
73313
  const payload = JSON.parse(getTruckGeometryWasm().geometry_mesh(this.getLiveHandle("getMesh()")));
73254
73314
  const numTri = payload.triangles.length / 3;
73255
73315
  const numVert = payload.positions.length / 3;
73316
+ const cornerNormals = payload.normals && payload.normals.length === numTri * 9 ? new Float32Array(payload.normals) : void 0;
73256
73317
  this.resource.mesh = {
73257
73318
  numProp: 3,
73258
73319
  numTri,
@@ -73266,7 +73327,8 @@ const _TruckShapeBackend = class _TruckShapeBackend {
73266
73327
  runTransform: new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0]),
73267
73328
  faceID: new Int32Array(payload.face_ids),
73268
73329
  faceIdNames: payload.face_id_names ?? [],
73269
- halfedgeTangent: new Float32Array(0)
73330
+ halfedgeTangent: new Float32Array(0),
73331
+ cornerNormals
73270
73332
  };
73271
73333
  return this.resource.mesh;
73272
73334
  }
@@ -74487,6 +74549,16 @@ function boundsInteriorOverlap(a2, b) {
74487
74549
  const tolerance = 1e-8;
74488
74550
  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;
74489
74551
  }
74552
+ function boundsFaceOrInteriorOverlap(a2, b) {
74553
+ const tolerance = 1e-8;
74554
+ const overlaps = [
74555
+ Math.min(a2.max[0], b.max[0]) - Math.max(a2.min[0], b.min[0]),
74556
+ Math.min(a2.max[1], b.max[1]) - Math.max(a2.min[1], b.min[1]),
74557
+ Math.min(a2.max[2], b.max[2]) - Math.max(a2.min[2], b.min[2])
74558
+ ];
74559
+ if (overlaps.some((overlap) => overlap < -tolerance)) return false;
74560
+ return overlaps.filter((overlap) => overlap <= tolerance).length <= 1;
74561
+ }
74490
74562
  function boundsFromPoints(points) {
74491
74563
  const first = points[0];
74492
74564
  if (!first) return null;
@@ -74569,16 +74641,16 @@ function shapePlanBounds(plan) {
74569
74641
  return null;
74570
74642
  }
74571
74643
  }
74572
- function hasPairwiseInteriorBoundsOverlap(shapes) {
74644
+ function hasPairwiseFaceOrInteriorBoundsOverlap(shapes) {
74573
74645
  const bounds = shapes.map((shape) => shape.boundingBox());
74574
74646
  for (let i = 0; i < bounds.length; i++) {
74575
74647
  for (let j = i + 1; j < bounds.length; j++) {
74576
- if (boundsInteriorOverlap(bounds[i], bounds[j])) return true;
74648
+ if (boundsFaceOrInteriorOverlap(bounds[i], bounds[j])) return true;
74577
74649
  }
74578
74650
  }
74579
74651
  return false;
74580
74652
  }
74581
- function interiorOverlapComponents(shapes) {
74653
+ function faceOrInteriorOverlapComponents(shapes) {
74582
74654
  const bounds = shapes.map((shape) => shape.boundingBox());
74583
74655
  const visited = /* @__PURE__ */ new Set();
74584
74656
  const components = [];
@@ -74592,7 +74664,7 @@ function interiorOverlapComponents(shapes) {
74592
74664
  component.push(current);
74593
74665
  for (let next = 0; next < shapes.length; next++) {
74594
74666
  if (visited.has(next)) continue;
74595
- if (boundsInteriorOverlap(bounds[current], bounds[next])) {
74667
+ if (boundsFaceOrInteriorOverlap(bounds[current], bounds[next])) {
74596
74668
  visited.add(next);
74597
74669
  queue.push(next);
74598
74670
  }
@@ -74637,10 +74709,10 @@ function lowerGenericBooleanPlan(plan) {
74637
74709
  let returned = null;
74638
74710
  try {
74639
74711
  if (plan.op === "union") {
74640
- if (!hasPairwiseInteriorBoundsOverlap(shapes)) {
74712
+ if (!hasPairwiseFaceOrInteriorBoundsOverlap(shapes)) {
74641
74713
  return null;
74642
74714
  }
74643
- const components = interiorOverlapComponents(shapes);
74715
+ const components = faceOrInteriorOverlapComponents(shapes);
74644
74716
  if (components.length > 1) {
74645
74717
  returned = lowerClusteredUnion(shapes, components);
74646
74718
  return returned;
@@ -75170,9 +75242,13 @@ function shapeHasClosedNativeTopology(shape) {
75170
75242
  function normalizeTruckShapeForBooleanInput(shape) {
75171
75243
  if (shapeHasClosedNativeTopology(shape)) return shape;
75172
75244
  if (!meshHasRawBoundaryEdges(shape.getMesh())) return shape;
75173
- const normalized = normalizeFacetedTruckShape(shape);
75174
- disposeShapeBackend(shape);
75175
- return normalized;
75245
+ try {
75246
+ const normalized = normalizeFacetedTruckShape(shape);
75247
+ disposeShapeBackend(shape);
75248
+ return normalized;
75249
+ } catch {
75250
+ return shape;
75251
+ }
75176
75252
  }
75177
75253
  function lowerSdfPlan(plan) {
75178
75254
  if (getUnsupportedSdfProgramReason(plan.tree) === void 0) {
@@ -77056,7 +77132,7 @@ function circleProfilePlan(radius, segments) {
77056
77132
  }
77057
77133
  function annulusProfilePlan(outerRadius, innerRadius, segments) {
77058
77134
  const outer = Math.abs(outerRadius);
77059
- const inner = Math.abs(innerRadius);
77135
+ const inner = Math.max(0, innerRadius);
77060
77136
  if (outer <= EXACT_PROFILE_EPS) return emptyProfilePlan();
77061
77137
  if (inner <= EXACT_PROFILE_EPS) return circleProfilePlan(outer, segments);
77062
77138
  return {
@@ -82015,11 +82091,11 @@ function requireFiniteVec3$3(v, label) {
82015
82091
  }
82016
82092
  return [x2, y2, z2];
82017
82093
  }
82018
- function len3$2(v) {
82094
+ function len3$3(v) {
82019
82095
  return Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
82020
82096
  }
82021
82097
  function normalize3$3(v) {
82022
- const l = len3$2(v);
82098
+ const l = len3$3(v);
82023
82099
  if (l < 1e-10) throw new Error("Cannot normalize zero-length vector");
82024
82100
  return [v[0] / l, v[1] / l, v[2] / l];
82025
82101
  }
@@ -82029,7 +82105,7 @@ function dot3$5(a2, b) {
82029
82105
  function cross3$2(a2, b) {
82030
82106
  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]];
82031
82107
  }
82032
- function sub3$2(a2, b) {
82108
+ function sub3$3(a2, b) {
82033
82109
  return [a2[0] - b[0], a2[1] - b[1], a2[2] - b[2]];
82034
82110
  }
82035
82111
  function negate3$1(v) {
@@ -82052,7 +82128,7 @@ function normalizePortInput(input) {
82052
82128
  const end = requireFiniteVec3$3(input.end, "port end");
82053
82129
  origin = [(start[0] + end[0]) / 2, (start[1] + end[1]) / 2, (start[2] + end[2]) / 2];
82054
82130
  const dir = [end[0] - start[0], end[1] - start[1], end[2] - start[2]];
82055
- const dirLen = len3$2(dir);
82131
+ const dirLen = len3$3(dir);
82056
82132
  if (dirLen < 1e-10) {
82057
82133
  throw new Error("Port start and end must not be the same point");
82058
82134
  }
@@ -82063,7 +82139,7 @@ function normalizePortInput(input) {
82063
82139
  } else if (hasOriginAxis) {
82064
82140
  origin = requireFiniteVec3$3(input.origin, "port origin");
82065
82141
  const rawAxis = requireFiniteVec3$3(input.axis, "port axis");
82066
- if (len3$2(rawAxis) < 1e-10) {
82142
+ if (len3$3(rawAxis) < 1e-10) {
82067
82143
  throw new Error("Port axis must be non-zero");
82068
82144
  }
82069
82145
  axis = normalize3$3(rawAxis);
@@ -82079,12 +82155,12 @@ function normalizePortInput(input) {
82079
82155
  let up;
82080
82156
  if (input.up != null) {
82081
82157
  const rawUp = requireFiniteVec3$3(input.up, "port up");
82082
- if (len3$2(rawUp) < 1e-10) {
82158
+ if (len3$3(rawUp) < 1e-10) {
82083
82159
  throw new Error("Port up vector must be non-zero");
82084
82160
  }
82085
82161
  const proj = dot3$5(rawUp, axis);
82086
82162
  const ortho = [rawUp[0] - proj * axis[0], rawUp[1] - proj * axis[1], rawUp[2] - proj * axis[2]];
82087
- if (len3$2(ortho) < 1e-10) {
82163
+ if (len3$3(ortho) < 1e-10) {
82088
82164
  throw new Error("Port up vector must not be parallel to axis");
82089
82165
  }
82090
82166
  up = normalize3$3(ortho);
@@ -82148,8 +82224,8 @@ function transformPort(port, matrix) {
82148
82224
  const newOrigin = tx.point(port.origin);
82149
82225
  const rawAxis = tx.vector(port.axis);
82150
82226
  const rawUp = tx.vector(port.up);
82151
- const axisLen = len3$2(rawAxis);
82152
- const upLen = len3$2(rawUp);
82227
+ const axisLen = len3$3(rawAxis);
82228
+ const upLen = len3$3(rawUp);
82153
82229
  const out = {
82154
82230
  origin: newOrigin,
82155
82231
  axis: axisLen > 1e-10 ? normalize3$3(rawAxis) : port.axis,
@@ -82201,7 +82277,7 @@ function computeConnectFrame(childBase, childPort, parentPort, _flip, childAlign
82201
82277
  r10 * cI[0] + r11 * cI[1] + r12 * cI[2],
82202
82278
  r20 * cI[0] + r21 * cI[1] + r22 * cI[2]
82203
82279
  ];
82204
- const t = sub3$2(pOrigin, rcI);
82280
+ const t = sub3$3(pOrigin, rcI);
82205
82281
  const frame = Transform.from([r00, r10, r20, 0, r01, r11, r21, 0, r02, r12, r22, 0, t[0], t[1], t[2], 1]);
82206
82282
  const axis = cAxis;
82207
82283
  return { frame, axis };
@@ -82286,18 +82362,18 @@ function validateConnectorMatch(selfName, selfPort, targetName, targetPort, forc
82286
82362
  }
82287
82363
  }
82288
82364
  }
82289
- function len3$1(v) {
82365
+ function len3$2(v) {
82290
82366
  return Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
82291
82367
  }
82292
82368
  function normalize3$2(v) {
82293
- const l = len3$1(v);
82369
+ const l = len3$2(v);
82294
82370
  if (l < 1e-10) throw new Error("Cannot normalize zero-length vector");
82295
82371
  return [v[0] / l, v[1] / l, v[2] / l];
82296
82372
  }
82297
82373
  function cross3$1(a2, b) {
82298
82374
  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]];
82299
82375
  }
82300
- function sub3$1(a2, b) {
82376
+ function sub3$2(a2, b) {
82301
82377
  return [a2[0] - b[0], a2[1] - b[1], a2[2] - b[2]];
82302
82378
  }
82303
82379
  function negate3(v) {
@@ -82320,7 +82396,7 @@ function alignmentMatrix(childOrigin, childAxis, childUp, parentOrigin, parentAx
82320
82396
  r10 * childOrigin[0] + r11 * childOrigin[1] + r12 * childOrigin[2],
82321
82397
  r20 * childOrigin[0] + r21 * childOrigin[1] + r22 * childOrigin[2]
82322
82398
  ];
82323
- const t = sub3$1(parentOrigin, rc);
82399
+ const t = sub3$2(parentOrigin, rc);
82324
82400
  return Transform.from([r00, r10, r20, 0, r01, r11, r21, 0, r02, r12, r22, 0, t[0], t[1], t[2], 1]);
82325
82401
  }
82326
82402
  function computeSinglePairAlignment(childPort, targetPort) {
@@ -82359,8 +82435,8 @@ function computeMultiPairAlignment(pairs, childPorts, targetPorts, tolerance = 0
82359
82435
  [0, 0, 0]
82360
82436
  ];
82361
82437
  for (const p2 of pairs) {
82362
- const s = sub3$1(p2.childOrigin, srcCentroid);
82363
- const t2 = sub3$1(p2.targetOrigin, tgtCentroid);
82438
+ const s = sub3$2(p2.childOrigin, srcCentroid);
82439
+ const t2 = sub3$2(p2.targetOrigin, tgtCentroid);
82364
82440
  for (let i = 0; i < 3; i++) {
82365
82441
  for (let j = 0; j < 3; j++) {
82366
82442
  h[i][j] += s[i] * t2[j];
@@ -82373,7 +82449,7 @@ function computeMultiPairAlignment(pairs, childPorts, targetPorts, tolerance = 0
82373
82449
  R[1][0] * srcCentroid[0] + R[1][1] * srcCentroid[1] + R[1][2] * srcCentroid[2],
82374
82450
  R[2][0] * srcCentroid[0] + R[2][1] * srcCentroid[1] + R[2][2] * srcCentroid[2]
82375
82451
  ];
82376
- const t = sub3$1(tgtCentroid, rSrc);
82452
+ const t = sub3$2(tgtCentroid, rSrc);
82377
82453
  const transform = Transform.from([
82378
82454
  R[0][0],
82379
82455
  R[1][0],
@@ -82395,8 +82471,8 @@ function computeMultiPairAlignment(pairs, childPorts, targetPorts, tolerance = 0
82395
82471
  const residuals = [];
82396
82472
  for (const p2 of pairs) {
82397
82473
  const transformed = transform.point(p2.childOrigin);
82398
- const diff = sub3$1(transformed, p2.targetOrigin);
82399
- residuals.push(len3$1(diff));
82474
+ const diff = sub3$2(transformed, p2.targetOrigin);
82475
+ residuals.push(len3$2(diff));
82400
82476
  }
82401
82477
  const maxResidual = Math.max(...residuals);
82402
82478
  if (maxResidual > tolerance) {
@@ -82596,8 +82672,8 @@ function getConnectorDistance(ports, nameA, nameB) {
82596
82672
  const b = ports[nameB];
82597
82673
  if (!a2) throw new Error(`connectorDistance: unknown connector "${nameA}"`);
82598
82674
  if (!b) throw new Error(`connectorDistance: unknown connector "${nameB}"`);
82599
- const d2 = sub3$1(a2.origin, b.origin);
82600
- return len3$1(d2);
82675
+ const d2 = sub3$2(a2.origin, b.origin);
82676
+ return len3$2(d2);
82601
82677
  }
82602
82678
  function getConnectorMeasurements(ports, name) {
82603
82679
  const p2 = ports[name];
@@ -87839,8 +87915,7 @@ function sculptLook(preset = "gallery") {
87839
87915
  { type: "directional", position: [90, -110, 150], color: "#ffffff", intensity: 1.4 },
87840
87916
  { type: "directional", position: [-120, 70, 80], color: "#dcecff", intensity: 0.65 },
87841
87917
  { type: "hemisphere", skyColor: "#d9edff", groundColor: "#f0e6d4", intensity: 0.45 }
87842
- ],
87843
- postProcessing: { toneMappingExposure: 1.12, vignette: { darkness: 0.16, offset: 0.6 } }
87918
+ ]
87844
87919
  };
87845
87920
  case "candy-shop":
87846
87921
  return {
@@ -87851,8 +87926,7 @@ function sculptLook(preset = "gallery") {
87851
87926
  { type: "point", position: [70, -60, 90], color: "#ff8ac8", intensity: 2.2, distance: 280, decay: 1.2 },
87852
87927
  { type: "point", position: [-85, 80, 70], color: "#70e1ff", intensity: 1.8, distance: 260, decay: 1.4 },
87853
87928
  { type: "directional", position: [30, -80, 140], color: "#fff6dd", intensity: 0.9 }
87854
- ],
87855
- postProcessing: { toneMappingExposure: 1.25, bloom: { intensity: 0.35, threshold: 0.78, radius: 0.45 } }
87929
+ ]
87856
87930
  };
87857
87931
  case "midnight":
87858
87932
  return {
@@ -87864,8 +87938,7 @@ function sculptLook(preset = "gallery") {
87864
87938
  { type: "point", position: [-90, 70, 50], color: "#ff7ac8", intensity: 1.4, distance: 300, decay: 1.3 },
87865
87939
  { type: "directional", position: [30, -30, 160], color: "#d7e9ff", intensity: 0.65 }
87866
87940
  ],
87867
- fog: { color: "#060814", near: 180, far: 520 },
87868
- postProcessing: { toneMappingExposure: 1.35, bloom: { intensity: 0.65, threshold: 0.65, radius: 0.6 } }
87941
+ fog: { color: "#060814", near: 180, far: 520 }
87869
87942
  };
87870
87943
  case "workbench":
87871
87944
  return {
@@ -87876,8 +87949,7 @@ function sculptLook(preset = "gallery") {
87876
87949
  { type: "directional", position: [80, -100, 130], color: "#fff5df", intensity: 1.25 },
87877
87950
  { type: "hemisphere", skyColor: "#eef6ff", groundColor: "#d8d0c0", intensity: 0.35 }
87878
87951
  ],
87879
- ground: { visible: true, color: "#d8d4ca", offset: 1, receiveShadow: true },
87880
- postProcessing: { toneMappingExposure: 1.05 }
87952
+ ground: { visible: true, color: "#d8d4ca", offset: 1, receiveShadow: true }
87881
87953
  };
87882
87954
  default:
87883
87955
  return {
@@ -87888,8 +87960,7 @@ function sculptLook(preset = "gallery") {
87888
87960
  { type: "directional", position: [110, -130, 150], color: "#ffffff", intensity: 1.5 },
87889
87961
  { type: "directional", position: [-90, 80, 90], color: "#b8d8ff", intensity: 0.55 },
87890
87962
  { type: "hemisphere", skyColor: "#ddefff", groundColor: "#e8e2d8", intensity: 0.45 }
87891
- ],
87892
- postProcessing: { toneMappingExposure: 1.18, vignette: { darkness: 0.18, offset: 0.55 } }
87963
+ ]
87893
87964
  };
87894
87965
  }
87895
87966
  }
@@ -89700,7 +89771,7 @@ class Shape2 {
89700
89771
  *
89701
89772
  * Use `.color()` to set the base diffuse color; `.material()` controls how that color behaves
89702
89773
  * under light (metalness, roughness, clearcoat) and can add emissive glow independent of
89703
- * lighting. Emissive glow pairs naturally with the `postProcessing.bloom` effect in `scene()`.
89774
+ * lighting.
89704
89775
  *
89705
89776
  * **Example**
89706
89777
  *
@@ -92096,14 +92167,14 @@ function dot3$3(a2, b) {
92096
92167
  function cross3(a2, b) {
92097
92168
  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]];
92098
92169
  }
92099
- function sub3(a2, b) {
92170
+ function sub3$1(a2, b) {
92100
92171
  return [a2[0] - b[0], a2[1] - b[1], a2[2] - b[2]];
92101
92172
  }
92102
- function len3(v) {
92173
+ function len3$1(v) {
92103
92174
  return Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
92104
92175
  }
92105
92176
  function normalize3$1(v) {
92106
- const l = len3(v);
92177
+ const l = len3$1(v);
92107
92178
  if (l < EPS$9) return [0, 0, 0];
92108
92179
  return [v[0] / l, v[1] / l, v[2] / l];
92109
92180
  }
@@ -92115,7 +92186,7 @@ const alignDef = {
92115
92186
  const faceB = ctx.worldFace(constraint.refB.bodyId, constraint.refB.featureName);
92116
92187
  const n1 = faceA.normal;
92117
92188
  const n2 = faceB.normal;
92118
- const delta = sub3(faceB.center, faceA.center);
92189
+ const delta = sub3$1(faceB.center, faceA.center);
92119
92190
  const parallel = dot3$3(n1, n2) - 1;
92120
92191
  const normalDist = dot3$3(delta, n1);
92121
92192
  const cx = n1[1] * n2[2] - n1[2] * n2[1];
@@ -92158,7 +92229,7 @@ const concentricDef = {
92158
92229
  const axisA = ctx.worldAxis(constraint.refA.bodyId, constraint.refA.featureName);
92159
92230
  const axisB = ctx.worldAxis(constraint.refB.bodyId, constraint.refB.featureName);
92160
92231
  const dirCross = cross3(axisA.direction, axisB.direction);
92161
- const delta = sub3(axisB.origin, axisA.origin);
92232
+ const delta = sub3$1(axisB.origin, axisA.origin);
92162
92233
  const offsetCross = cross3(delta, axisA.direction);
92163
92234
  const pickTwo = (v) => {
92164
92235
  const ax = Math.abs(v[0]);
@@ -92182,9 +92253,9 @@ const faceDistanceDef = {
92182
92253
  const distance2 = constraint.value ?? 0;
92183
92254
  const n1 = faceA.normal;
92184
92255
  const n2 = faceB.normal;
92185
- const delta = sub3(faceB.center, faceA.center);
92256
+ const delta = sub3$1(faceB.center, faceA.center);
92186
92257
  const antiParallel = dot3$3(n1, n2) + 1;
92187
- const crossMag = len3(cross3(n1, n2));
92258
+ const crossMag = len3$1(cross3(n1, n2));
92188
92259
  const signedDist = dot3$3(delta, n1) - distance2;
92189
92260
  return [antiParallel, crossMag, signedDist];
92190
92261
  }
@@ -92204,7 +92275,7 @@ const flushDef = {
92204
92275
  const faceB = ctx.worldFace(constraint.refB.bodyId, constraint.refB.featureName);
92205
92276
  const n1 = faceA.normal;
92206
92277
  const n2 = faceB.normal;
92207
- const delta = sub3(faceB.center, faceA.center);
92278
+ const delta = sub3$1(faceB.center, faceA.center);
92208
92279
  const antiParallel = dot3$3(n1, n2) + 1;
92209
92280
  const normalDist = dot3$3(delta, n1);
92210
92281
  const cx = n1[1] * n2[2] - n1[2] * n2[1];
@@ -92244,7 +92315,7 @@ const pointOnAxisDef = {
92244
92315
  residual(constraint, ctx) {
92245
92316
  const point2 = ctx.worldPoint(constraint.refA.bodyId, constraint.refA.featureName);
92246
92317
  const axis = ctx.worldAxis(constraint.refB.bodyId, constraint.refB.featureName);
92247
- const delta = sub3(point2, axis.origin);
92318
+ const delta = sub3$1(point2, axis.origin);
92248
92319
  const c2 = cross3(delta, axis.direction);
92249
92320
  const ax = Math.abs(c2[0]);
92250
92321
  const ay = Math.abs(c2[1]);
@@ -92260,7 +92331,7 @@ const pointOnFaceDef = {
92260
92331
  residual(constraint, ctx) {
92261
92332
  const point2 = ctx.worldPoint(constraint.refA.bodyId, constraint.refA.featureName);
92262
92333
  const face = ctx.worldFace(constraint.refB.bodyId, constraint.refB.featureName);
92263
- const delta = sub3(point2, face.center);
92334
+ const delta = sub3$1(point2, face.center);
92264
92335
  return [dot3$3(delta, face.normal)];
92265
92336
  }
92266
92337
  };
@@ -92809,7 +92880,7 @@ function runWithForgeValidationPolicy(policy, fn) {
92809
92880
  }
92810
92881
  let _collected$8 = null;
92811
92882
  const isFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value);
92812
- const isVec3$1 = (value) => Array.isArray(value) && value.length === 3 && isFiniteNumber(value[0]) && isFiniteNumber(value[1]) && isFiniteNumber(value[2]);
92883
+ const isVec3$2 = (value) => Array.isArray(value) && value.length === 3 && isFiniteNumber(value[0]) && isFiniteNumber(value[1]) && isFiniteNumber(value[2]);
92813
92884
  const normalizeAxis = (axis) => {
92814
92885
  const len2 = Math.hypot(axis[0], axis[1], axis[2]);
92815
92886
  if (len2 <= 1e-8) throw new Error("jointsView joint axis must be non-zero");
@@ -92844,10 +92915,10 @@ const normalizeJoint = (joint2) => {
92844
92915
  throw new Error(`jointsView joint "${name}" type must be "revolute" or "prismatic"`);
92845
92916
  }
92846
92917
  const axisRaw = joint2.axis ?? [0, 0, 1];
92847
- if (!isVec3$1(axisRaw)) throw new Error(`jointsView joint "${name}" axis must be [x, y, z]`);
92918
+ if (!isVec3$2(axisRaw)) throw new Error(`jointsView joint "${name}" axis must be [x, y, z]`);
92848
92919
  const axis = normalizeAxis([axisRaw[0], axisRaw[1], axisRaw[2]]);
92849
92920
  const pivotRaw = joint2.pivot ?? [0, 0, 0];
92850
- if (!isVec3$1(pivotRaw)) throw new Error(`jointsView joint "${name}" pivot must be [x, y, z]`);
92921
+ if (!isVec3$2(pivotRaw)) throw new Error(`jointsView joint "${name}" pivot must be [x, y, z]`);
92851
92922
  const pivot = [pivotRaw[0], pivotRaw[1], pivotRaw[2]];
92852
92923
  if (joint2.min !== void 0 && !isFiniteNumber(joint2.min)) {
92853
92924
  throw new Error(`jointsView joint "${name}" min must be a finite number`);
@@ -93097,8 +93168,8 @@ const clampJointValue$1 = (joint2, value) => {
93097
93168
  };
93098
93169
  function resolveJointViewValues(joints, couplings = [], baseValues = {}, options = {}) {
93099
93170
  const shouldClamp = options.clamp ?? false;
93100
- const jointByName2 = /* @__PURE__ */ new Map();
93101
- joints.forEach((joint2) => jointByName2.set(joint2.name, joint2));
93171
+ const jointByName = /* @__PURE__ */ new Map();
93172
+ joints.forEach((joint2) => jointByName.set(joint2.name, joint2));
93102
93173
  const couplingByJoint = /* @__PURE__ */ new Map();
93103
93174
  couplings.forEach((coupling) => couplingByJoint.set(coupling.joint, coupling));
93104
93175
  const cache = /* @__PURE__ */ new Map();
@@ -93106,7 +93177,7 @@ function resolveJointViewValues(joints, couplings = [], baseValues = {}, options
93106
93177
  const resolveValue = (jointName) => {
93107
93178
  const cached = cache.get(jointName);
93108
93179
  if (cached !== void 0) return cached;
93109
- const joint2 = jointByName2.get(jointName);
93180
+ const joint2 = jointByName.get(jointName);
93110
93181
  if (!joint2) return 0;
93111
93182
  if (resolving.has(jointName)) {
93112
93183
  const cycleFallback = baseValues[jointName] ?? joint2.defaultValue;
@@ -93809,7 +93880,7 @@ function deriveExplodeHintsFromMates(constraints, result, bodies, ctx) {
93809
93880
  const posA = result.transforms.get(c2.refA.bodyId);
93810
93881
  const posB = result.transforms.get(c2.refB.bodyId);
93811
93882
  if (posA && posB) {
93812
- const raw = sub3(bodyA.grounded ? posB.position : posA.position, bodyA.grounded ? posA.position : posB.position);
93883
+ const raw = sub3$1(bodyA.grounded ? posB.position : posA.position, bodyA.grounded ? posA.position : posB.position);
93813
93884
  dir = normalize3$1(raw);
93814
93885
  }
93815
93886
  break;
@@ -94335,8 +94406,8 @@ class Assembly {
94335
94406
  *
94336
94407
  * Use this after adding physical parts and joints. Robot-body profiles require
94337
94408
  * `rootPart`; asset profiles can describe one-part or multi-part physical assets.
94338
- * URDF/SDF exporters and `forgecad check simready` read this contract directly,
94339
- * so model files no longer need a separate `robotExport(...)` side effect.
94409
+ * URDF/SDF/MJCF/USD exporters and `forgecad check simready` read this
94410
+ * contract directly from the returned assembly.
94340
94411
  *
94341
94412
  * @category Assembly
94342
94413
  */
@@ -103450,7 +103521,7 @@ function beltDrive(options) {
103450
103521
  }
103451
103522
  const GEAR_META_KEY = Symbol.for("forgecad.library.gearMeta");
103452
103523
  const EPSILON$1 = 1e-9;
103453
- function clamp01$2(value) {
103524
+ function clamp01$4(value) {
103454
103525
  return Math.max(-1, Math.min(1, value));
103455
103526
  }
103456
103527
  function isFinitePositive(value) {
@@ -103470,7 +103541,7 @@ function addArcPoints(target, radius, startAngle, endAngle, steps, includeStart
103470
103541
  }
103471
103542
  }
103472
103543
  function flankAngleAtRadius(radius, baseRadius, halfThicknessAtPitch, pressureAngleRad) {
103473
- const alphaAtRadius = Math.acos(clamp01$2(baseRadius / Math.max(radius, baseRadius)));
103544
+ const alphaAtRadius = Math.acos(clamp01$4(baseRadius / Math.max(radius, baseRadius)));
103474
103545
  return halfThicknessAtPitch + involuteFn(pressureAngleRad) - involuteFn(alphaAtRadius);
103475
103546
  }
103476
103547
  function addRootFilletPoints(target, rootRadius, filletRadius, flankAngle, sign2, fromFlank, steps) {
@@ -104590,7 +104661,7 @@ function gearPair(options) {
104590
104661
  message: `Center distance ${centerDistance.toFixed(4)} exceeds addendum reach ${addendumReach.toFixed(4)} (no mesh contact)`
104591
104662
  });
104592
104663
  }
104593
- const cosWorking = clamp01$2(baseSum / Math.max(centerDistance, EPSILON$1));
104664
+ const cosWorking = clamp01$4(baseSum / Math.max(centerDistance, EPSILON$1));
104594
104665
  const alphaWorking = Math.acos(cosWorking);
104595
104666
  const basePitch = Math.PI * module * Math.cos(alpha);
104596
104667
  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);
@@ -105503,7 +105574,8 @@ class FrozenShape extends Shape2 {
105503
105574
  const EDGE_THRESHOLD_DOT = Math.cos(Math.PI / 180);
105504
105575
  const SMOOTH_THRESHOLD_DOT = Math.cos(30 * Math.PI / 180);
105505
105576
  function computeGeometryArrays(mesh, options = {}) {
105506
- const { numProp, numTri: triCount, triVerts, vertProperties, vertNormals } = mesh;
105577
+ const { numProp, numTri: triCount, triVerts, vertProperties, vertNormals, cornerNormals } = mesh;
105578
+ const useCornerNormals = !!cornerNormals && cornerNormals.length === triCount * 9;
105507
105579
  const positions = new Float32Array(triCount * 9);
105508
105580
  const normals = new Float32Array(triCount * 9);
105509
105581
  const faceNx = new Float32Array(triCount);
@@ -105535,7 +105607,9 @@ function computeGeometryArrays(mesh, options = {}) {
105535
105607
  positions[o + 6] = cx;
105536
105608
  positions[o + 7] = cy;
105537
105609
  positions[o + 8] = cz;
105538
- if (vertNormals) {
105610
+ if (useCornerNormals) {
105611
+ for (let k2 = 0; k2 < 9; k2++) normals[o + k2] = cornerNormals[o + k2];
105612
+ } else if (vertNormals) {
105539
105613
  normals[o] = vertNormals[i0 * 3];
105540
105614
  normals[o + 1] = vertNormals[i0 * 3 + 1];
105541
105615
  normals[o + 2] = vertNormals[i0 * 3 + 2];
@@ -105578,7 +105652,7 @@ function computeGeometryArrays(mesh, options = {}) {
105578
105652
  faceNy[t] = fny;
105579
105653
  faceNz[t] = fnz;
105580
105654
  }
105581
- if (!vertNormals && numProp < 6 && triCount > 0) {
105655
+ if (!useCornerNormals && !vertNormals && numProp < 6 && triCount > 0) {
105582
105656
  computeAutoSmoothNormals(
105583
105657
  triVerts,
105584
105658
  vertProperties,
@@ -105776,7 +105850,8 @@ function shapeToGeometryFallback(shape) {
105776
105850
  vertProperties: mesh.vertProperties,
105777
105851
  mergeFromVert: mesh.mergeFromVert,
105778
105852
  mergeToVert: mesh.mergeToVert,
105779
- vertNormals
105853
+ vertNormals,
105854
+ cornerNormals: mesh.cornerNormals && mesh.cornerNormals.length === mesh.numTri * 9 ? mesh.cornerNormals : void 0
105780
105855
  });
105781
105856
  const solid = new BufferGeometry();
105782
105857
  solid.setAttribute("position", new BufferAttribute(positions, 3));
@@ -105806,26 +105881,26 @@ function getPendingShapeHighlights() {
105806
105881
  function resetPendingShapeHighlights() {
105807
105882
  pendingShapeHighlights = [];
105808
105883
  }
105809
- function isVec3(v) {
105884
+ function isVec3$1(v) {
105810
105885
  return Array.isArray(v) && v.length === 3 && typeof v[0] === "number" && typeof v[1] === "number" && typeof v[2] === "number";
105811
105886
  }
105812
105887
  function isEdgePair(v) {
105813
- return Array.isArray(v) && v.length === 2 && isVec3(v[0]) && isVec3(v[1]);
105888
+ return Array.isArray(v) && v.length === 2 && isVec3$1(v[0]) && isVec3$1(v[1]);
105814
105889
  }
105815
105890
  function isPlaneSpec(v) {
105816
105891
  if (typeof v !== "object" || v === null) return false;
105817
105892
  const obj = v;
105818
- return isVec3(obj.normal) && (typeof obj.offset === "number" || isVec3(obj.point));
105893
+ return isVec3$1(obj.normal) && (typeof obj.offset === "number" || isVec3$1(obj.point));
105819
105894
  }
105820
105895
  function isFaceRef$1(v) {
105821
105896
  if (typeof v !== "object" || v === null) return false;
105822
105897
  const obj = v;
105823
- return isVec3(obj.normal) && isVec3(obj.center) && typeof obj.name === "string";
105898
+ return isVec3$1(obj.normal) && isVec3$1(obj.center) && typeof obj.name === "string";
105824
105899
  }
105825
105900
  function isEdgeRef(v) {
105826
105901
  if (typeof v !== "object" || v === null) return false;
105827
105902
  const obj = v;
105828
- return isVec3(obj.start) && isVec3(obj.end) && typeof obj.name === "string";
105903
+ return isVec3$1(obj.start) && isVec3$1(obj.end) && typeof obj.name === "string";
105829
105904
  }
105830
105905
  function requireFiniteVec3(v, name) {
105831
105906
  for (let i = 0; i < 3; i++) {
@@ -105864,7 +105939,7 @@ function highlight(target, opts) {
105864
105939
  });
105865
105940
  return;
105866
105941
  }
105867
- if (isVec3(target)) {
105942
+ if (isVec3$1(target)) {
105868
105943
  requireFiniteVec3(target, "point");
105869
105944
  collectedDebugHighlights3D.push({
105870
105945
  kind: "point",
@@ -106958,7 +107033,7 @@ function resolveForgeRenderStyle(value) {
106958
107033
  function getRenderStylePreset(style) {
106959
107034
  return PRESETS[resolveForgeRenderStyle(style)];
106960
107035
  }
106961
- function clamp01$1(value) {
107036
+ function clamp01$3(value) {
106962
107037
  return Math.max(0, Math.min(1, value));
106963
107038
  }
106964
107039
  function applyMaterial(shape, preset) {
@@ -106980,7 +107055,7 @@ const materials = {
106980
107055
  clearPolycarbonate(options = {}) {
106981
107056
  return {
106982
107057
  color: options.tint ?? "#bdefff",
106983
- material: { opacity: clamp01$1(options.opacity ?? 0.34), roughness: 0.08, metalness: 0, clearcoat: 1, clearcoatRoughness: 0.03 }
107058
+ material: { opacity: clamp01$3(options.opacity ?? 0.34), roughness: 0.08, metalness: 0, clearcoat: 1, clearcoatRoughness: 0.03 }
106984
107059
  };
106985
107060
  },
106986
107061
  /** Brushed steel-like material for trim, soleplates, and hardware. */
@@ -107457,31 +107532,6 @@ function validateFog(fog, label) {
107457
107532
  if (fog.density !== void 0) out.density = requireFinite$5(fog.density, `${label}.density`);
107458
107533
  return out;
107459
107534
  }
107460
- function validatePostProcessing(pp, label) {
107461
- const out = {};
107462
- if (pp.bloom !== void 0) {
107463
- if (!pp.bloom || typeof pp.bloom !== "object") throw new Error(`${label}.bloom must be an object`);
107464
- out.bloom = {};
107465
- if (pp.bloom.intensity !== void 0) out.bloom.intensity = requireFinite$5(pp.bloom.intensity, `${label}.bloom.intensity`);
107466
- if (pp.bloom.threshold !== void 0) out.bloom.threshold = requireFinite$5(pp.bloom.threshold, `${label}.bloom.threshold`);
107467
- if (pp.bloom.radius !== void 0) out.bloom.radius = requireFinite$5(pp.bloom.radius, `${label}.bloom.radius`);
107468
- }
107469
- if (pp.vignette !== void 0) {
107470
- if (!pp.vignette || typeof pp.vignette !== "object") throw new Error(`${label}.vignette must be an object`);
107471
- out.vignette = {};
107472
- if (pp.vignette.darkness !== void 0) out.vignette.darkness = requireFinite$5(pp.vignette.darkness, `${label}.vignette.darkness`);
107473
- if (pp.vignette.offset !== void 0) out.vignette.offset = requireFinite$5(pp.vignette.offset, `${label}.vignette.offset`);
107474
- }
107475
- if (pp.grain !== void 0) {
107476
- if (!pp.grain || typeof pp.grain !== "object") throw new Error(`${label}.grain must be an object`);
107477
- out.grain = {};
107478
- if (pp.grain.intensity !== void 0) out.grain.intensity = requireFinite$5(pp.grain.intensity, `${label}.grain.intensity`);
107479
- }
107480
- if (pp.toneMappingExposure !== void 0) {
107481
- out.toneMappingExposure = requireFinite$5(pp.toneMappingExposure, `${label}.toneMappingExposure`);
107482
- }
107483
- return out;
107484
- }
107485
107535
  function validateGround(ground, label) {
107486
107536
  const out = {};
107487
107537
  if (ground.visible !== void 0) {
@@ -107563,7 +107613,6 @@ function scene(options) {
107563
107613
  lights: null,
107564
107614
  environment: null,
107565
107615
  fog: null,
107566
- postProcessing: null,
107567
107616
  ground: null,
107568
107617
  capture: null
107569
107618
  };
@@ -107603,12 +107652,9 @@ function scene(options) {
107603
107652
  }
107604
107653
  current.fog = validateFog(options.fog, "scene.fog");
107605
107654
  }
107606
- if (options.postProcessing !== void 0) {
107607
- if (!options.postProcessing || typeof options.postProcessing !== "object") {
107608
- throw new Error("scene.postProcessing must be an object");
107609
- }
107610
- const validated = validatePostProcessing(options.postProcessing, "scene.postProcessing");
107611
- current.postProcessing = current.postProcessing ? { ...current.postProcessing, ...validated } : validated;
107655
+ const disabledPostProcessing = options.postProcessing;
107656
+ if (disabledPostProcessing !== void 0) {
107657
+ console.warn("scene.postProcessing is disabled for now while the browser post-processing path is being rebuilt.");
107612
107658
  }
107613
107659
  if (options.ground !== void 0) {
107614
107660
  if (!options.ground || typeof options.ground !== "object") {
@@ -107768,8 +107814,7 @@ function scenePreset(name) {
107768
107814
  { type: "hemisphere", skyColor: "#ffffff", groundColor: "#d5d9de", intensity: 0.75 },
107769
107815
  { type: "directional", position: [90, -120, 180], color: "#ffffff", intensity: 1.8, castShadow: true },
107770
107816
  { type: "directional", position: [-140, 100, 80], color: "#dfe9ff", intensity: 0.55 }
107771
- ],
107772
- postProcessing: { toneMappingExposure: 1.08 }
107817
+ ]
107773
107818
  });
107774
107819
  return;
107775
107820
  }
@@ -108011,8 +108056,8 @@ function shapeBoundsCenter(shape) {
108011
108056
  ];
108012
108057
  }
108013
108058
  class ProductSurfaceRef {
108014
- constructor(skin, query, name) {
108015
- this.skin = skin;
108059
+ constructor(skin2, query, name) {
108060
+ this.skin = skin2;
108016
108061
  this.query = query;
108017
108062
  this.name = name;
108018
108063
  }
@@ -108529,8 +108574,8 @@ class ProductHandleBuilder {
108529
108574
  }
108530
108575
  }
108531
108576
  class ProductSurfaceBuilder {
108532
- constructor(skin, side) {
108533
- this.skin = skin;
108577
+ constructor(skin2, side) {
108578
+ this.skin = skin2;
108534
108579
  this.side = side;
108535
108580
  }
108536
108581
  /** Create a ref on this skin side. */
@@ -108598,9 +108643,9 @@ class ProductRibbonBuilder {
108598
108643
  * ProductSkin.frame(), so the ribbon bends along the selected side as station width/depth changes.
108599
108644
  * All query path points must stay on one side; split side transitions into separate ribbons.
108600
108645
  */
108601
- on(skin, points, options = {}) {
108646
+ on(skin2, points, options = {}) {
108602
108647
  if (points.length < 2) throw new Error("Product.ribbon().on(skin, points) requires at least two path points");
108603
- this.skinValue = skin;
108648
+ this.skinValue = skin2;
108604
108649
  this.queryPath = resolvePathQueries(points);
108605
108650
  this.refPath = [];
108606
108651
  return this.applyOptions(options);
@@ -108723,7 +108768,7 @@ class ProductRibbonBuilder {
108723
108768
  const localT = scaled - segment;
108724
108769
  return interpolateQuery(this.queryPath[segment], this.queryPath[segment + 1], localT);
108725
108770
  }
108726
- buildSkinGrid(skin, path2) {
108771
+ buildSkinGrid(skin2, path2) {
108727
108772
  if (path2.length < 2) throw new Error("Product.ribbon().on(skin, points) must be called before .build()");
108728
108773
  const side = normalizedSide(path2[0].side);
108729
108774
  if (side === "front" || side === "rear") {
@@ -108742,7 +108787,7 @@ class ProductRibbonBuilder {
108742
108787
  for (let i = 0; i < this.samplesValue; i += 1) {
108743
108788
  const along = this.samplesValue === 1 ? 0 : i / (this.samplesValue - 1);
108744
108789
  const center = this.samplePathQuery(along);
108745
- const station = skin.stationAt(center.v ?? 0.5);
108790
+ const station = skin2.stationAt(center.v ?? 0.5);
108746
108791
  const span = sideSpan(side, station.width, station.depth);
108747
108792
  for (let j = 0; j < this.widthSamplesValue; j += 1) {
108748
108793
  const across = this.widthSamplesValue === 1 ? 0 : j / (this.widthSamplesValue - 1) - 0.5;
@@ -108759,13 +108804,13 @@ class ProductRibbonBuilder {
108759
108804
  u: u2,
108760
108805
  offset: (center.offset ?? 0) + this.offsetValue + this.thicknessValue
108761
108806
  };
108762
- rows[j].push(skin.frame(query).point);
108807
+ rows[j].push(skin2.frame(query).point);
108763
108808
  }
108764
108809
  }
108765
108810
  return {
108766
108811
  grid: rows,
108767
108812
  diagnostics: this.makeDiagnostics({
108768
- skin: skin.name,
108813
+ skin: skin2.name,
108769
108814
  side,
108770
108815
  pathPointCount: path2.length,
108771
108816
  clampedUCount,
@@ -108922,16 +108967,16 @@ const Product = {
108922
108967
  return scaleProfileTo(sketch, width, depth);
108923
108968
  },
108924
108969
  /** Create an ad-hoc ProductSurfaceRef from a skin and side/u/v query. */
108925
- ref(skin, query) {
108926
- return new ProductSurfaceRef(skin, query);
108970
+ ref(skin2, query) {
108971
+ return new ProductSurfaceRef(skin2, query);
108927
108972
  },
108928
108973
  /**
108929
108974
  * Create a fluent surface helper for refs and conformal features on one side of a skin.
108930
108975
  *
108931
108976
  * Equivalent to skin.surface(side), useful when writing in Product.* namespace style.
108932
108977
  */
108933
- surface(skin, side) {
108934
- return skin.surface(side);
108978
+ surface(skin2, side) {
108979
+ return skin2.surface(side);
108935
108980
  },
108936
108981
  /** Start a panel feature builder. */
108937
108982
  panel(name) {
@@ -109400,9 +109445,9 @@ function coordinateOnSide(coordinate, side, label) {
109400
109445
  return { ...coordinate, kind: "productSkin", side };
109401
109446
  }
109402
109447
  class ProductSkinCarrier {
109403
- constructor(skin, name = skin.name, sideValue2, offsetValue = 0) {
109448
+ constructor(skin2, name = skin2.name, sideValue2, offsetValue = 0) {
109404
109449
  __publicField(this, "kind", "productSkin");
109405
- this.skin = skin;
109450
+ this.skin = skin2;
109406
109451
  this.name = name;
109407
109452
  this.sideValue = sideValue2;
109408
109453
  this.offsetValue = offsetValue;
@@ -112336,8 +112381,8 @@ const Carrier = {
112336
112381
  return new PlaneCarrier(name);
112337
112382
  },
112338
112383
  /** Adapt an existing ProductSkin into the general surface-member carrier protocol. */
112339
- productSkin(skin) {
112340
- return new ProductSkinCarrier(skin);
112384
+ productSkin(skin2) {
112385
+ return new ProductSkinCarrier(skin2);
112341
112386
  },
112342
112387
  /** Reserved stub for future parameterized mesh carriers; arbitrary mesh parameterization is not implemented yet. */
112343
112388
  mesh(name) {
@@ -112501,206 +112546,6 @@ ${xrefPos}
112501
112546
  return encoder.encode(parts.join(""));
112502
112547
  }
112503
112548
  }
112504
- let _collectedRobotExport = null;
112505
- function cloneLinkOptions(input) {
112506
- if (!input) return {};
112507
- return Object.fromEntries(Object.entries(input).map(([name, opts]) => [name, { ...opts }]));
112508
- }
112509
- function cloneJointOptions(input) {
112510
- if (!input) return {};
112511
- return Object.fromEntries(Object.entries(input).map(([name, opts]) => [name, { ...opts }]));
112512
- }
112513
- function cloneDiffDrive(input) {
112514
- if (!input) return void 0;
112515
- return {
112516
- ...input,
112517
- leftJoints: [...input.leftJoints],
112518
- rightJoints: [...input.rightJoints]
112519
- };
112520
- }
112521
- function cloneJointStatePublisher(input) {
112522
- if (!input) return void 0;
112523
- return {
112524
- ...input,
112525
- joints: input.joints ? [...input.joints] : void 0
112526
- };
112527
- }
112528
- function cloneWorld(input) {
112529
- if (!input) return null;
112530
- return {
112531
- ...input,
112532
- spawnPose: input.spawnPose ? [...input.spawnPose] : void 0,
112533
- keyboardTeleop: input.keyboardTeleop ? { ...input.keyboardTeleop } : void 0
112534
- };
112535
- }
112536
- function assertFinite(value, label) {
112537
- if (value !== void 0 && !Number.isFinite(value)) {
112538
- throw new Error(`${label} must be finite`);
112539
- }
112540
- }
112541
- function metadataNumber(value) {
112542
- return typeof value === "number" ? value : void 0;
112543
- }
112544
- function metadataCollision(value) {
112545
- return value === "none" || value === "visual" || value === "box" || value === "convex" ? value : void 0;
112546
- }
112547
- function colliderCollision(collider2) {
112548
- if (!collider2) return void 0;
112549
- if (collider2.mode === "box") return "box";
112550
- if (collider2.mode === "visual") return "visual";
112551
- return collider2.mode;
112552
- }
112553
- function velocityDriveEffort(drive) {
112554
- return (drive == null ? void 0 : drive.kind) === "velocity" ? drive.maxTorqueNm : void 0;
112555
- }
112556
- function velocityDriveVelocityDegS(drive) {
112557
- return (drive == null ? void 0 : drive.kind) === "velocity" ? drive.maxSpeedRpm * 6 : void 0;
112558
- }
112559
- function driveDamping(drive) {
112560
- return drive == null ? void 0 : drive.damping;
112561
- }
112562
- function driveFriction(drive) {
112563
- return drive == null ? void 0 : drive.friction;
112564
- }
112565
- function jointByName(assembly2) {
112566
- return new Map(assembly2.joints.map((joint2) => [joint2.name, joint2]));
112567
- }
112568
- function diffDriveFromControllers(controllers) {
112569
- if (!controllers) return void 0;
112570
- const diffDrives = controllers.filter((controller) => controller.kind === "diffDrive");
112571
- if (diffDrives.length > 1) {
112572
- throw new Error("assembly.withSimulation(...) currently supports one Sim.controller.diffDrive(...) controller");
112573
- }
112574
- const diffDrive2 = diffDrives[0];
112575
- return diffDrive2 ? {
112576
- leftJoints: [...diffDrive2.leftJoints],
112577
- rightJoints: [...diffDrive2.rightJoints],
112578
- wheelSeparationMm: diffDrive2.wheelSeparationMm,
112579
- wheelRadiusMm: diffDrive2.wheelRadiusMm,
112580
- topic: diffDrive2.topic,
112581
- odomTopic: diffDrive2.odomTopic,
112582
- tfTopic: diffDrive2.tfTopic,
112583
- frameId: diffDrive2.frameId,
112584
- odomFrameId: diffDrive2.odomFrameId,
112585
- maxLinearVelocity: diffDrive2.maxLinearVelocity,
112586
- maxAngularVelocity: diffDrive2.maxAngularVelocity,
112587
- linearAcceleration: diffDrive2.linearAcceleration,
112588
- angularAcceleration: diffDrive2.angularAcceleration
112589
- } : void 0;
112590
- }
112591
- function resetRobotExport() {
112592
- _collectedRobotExport = null;
112593
- }
112594
- function getCollectedRobotExport() {
112595
- return _collectedRobotExport;
112596
- }
112597
- function collectSimulationModel(assemblyInput, options = {}) {
112598
- var _a3, _b3, _c2, _d2, _e2, _f3, _g2, _h2, _i2;
112599
- const assembly2 = typeof assemblyInput.describe === "function" ? assemblyInput.describe() : assemblyInput;
112600
- const partNames = new Set(assembly2.parts.map((part) => part.name));
112601
- const joints = jointByName(assembly2);
112602
- const links = cloneLinkOptions(options.links);
112603
- for (const part of assembly2.parts) {
112604
- const fromSim = part.sim;
112605
- const fromMaterialDensity = (_a3 = fromSim == null ? void 0 : fromSim.material) == null ? void 0 : _a3.densityKgM3;
112606
- const current = links[part.name] ?? {};
112607
- const next = {
112608
- massKg: current.massKg ?? (fromSim == null ? void 0 : fromSim.massKg) ?? metadataNumber((_b3 = part.metadata) == null ? void 0 : _b3.massKg),
112609
- densityKgM3: current.densityKgM3 ?? (fromSim == null ? void 0 : fromSim.densityKgM3) ?? fromMaterialDensity ?? metadataNumber((_c2 = part.metadata) == null ? void 0 : _c2.densityKgM3),
112610
- collision: current.collision ?? colliderCollision(fromSim == null ? void 0 : fromSim.collider) ?? metadataCollision((_d2 = part.metadata) == null ? void 0 : _d2.collision)
112611
- };
112612
- if (next.massKg !== void 0 || next.densityKgM3 !== void 0 || next.collision !== void 0) {
112613
- links[part.name] = next;
112614
- }
112615
- }
112616
- for (const [partName, link] of Object.entries(links)) {
112617
- if (!partNames.has(partName)) throw new Error(`simulation model references unknown link "${partName}"`);
112618
- assertFinite(link.massKg, `simulation model link "${partName}" massKg`);
112619
- assertFinite(link.densityKgM3, `simulation model link "${partName}" densityKgM3`);
112620
- }
112621
- const jointOpts = cloneJointOptions(options.joints);
112622
- for (const joint2 of assembly2.joints) {
112623
- const drive = (_e2 = joint2.sim) == null ? void 0 : _e2.drive;
112624
- const current = jointOpts[joint2.name] ?? {};
112625
- const next = {
112626
- effort: current.effort ?? velocityDriveEffort(drive) ?? joint2.effort,
112627
- velocity: current.velocity ?? velocityDriveVelocityDegS(drive) ?? joint2.velocity,
112628
- damping: current.damping ?? driveDamping(drive) ?? joint2.damping,
112629
- friction: current.friction ?? driveFriction(drive) ?? joint2.friction
112630
- };
112631
- if (next.effort !== void 0 || next.velocity !== void 0 || next.damping !== void 0 || next.friction !== void 0) {
112632
- jointOpts[joint2.name] = next;
112633
- }
112634
- }
112635
- for (const [jointName, joint2] of Object.entries(jointOpts)) {
112636
- if (!joints.has(jointName)) throw new Error(`simulation model references unknown joint "${jointName}"`);
112637
- assertFinite(joint2.effort, `simulation model joint "${jointName}" effort`);
112638
- assertFinite(joint2.velocity, `simulation model joint "${jointName}" velocity`);
112639
- assertFinite(joint2.damping, `simulation model joint "${jointName}" damping`);
112640
- assertFinite(joint2.friction, `simulation model joint "${jointName}" friction`);
112641
- }
112642
- const simulation = cloneSimAssemblySimulation(assembly2.sim) ?? null;
112643
- const simulationDiffDrive = diffDriveFromControllers(simulation == null ? void 0 : simulation.controllers);
112644
- const diffDrive2 = cloneDiffDrive((_f3 = options.plugins) == null ? void 0 : _f3.diffDrive) ?? simulationDiffDrive;
112645
- if (diffDrive2) {
112646
- if (diffDrive2.leftJoints.length === 0 || diffDrive2.rightJoints.length === 0) {
112647
- throw new Error("simulation model diffDrive requires at least one left joint and one right joint");
112648
- }
112649
- assertFinite(diffDrive2.wheelSeparationMm, "simulation model diffDrive wheelSeparationMm");
112650
- assertFinite(diffDrive2.wheelRadiusMm, "simulation model diffDrive wheelRadiusMm");
112651
- if (diffDrive2.wheelSeparationMm <= 0 || diffDrive2.wheelRadiusMm <= 0) {
112652
- throw new Error("simulation model diffDrive wheel separation and radius must be > 0");
112653
- }
112654
- [...diffDrive2.leftJoints, ...diffDrive2.rightJoints].forEach((jointName) => {
112655
- const joint2 = joints.get(jointName);
112656
- if (!joint2) throw new Error(`simulation model diffDrive references unknown joint "${jointName}"`);
112657
- if (joint2.type !== "revolute") {
112658
- throw new Error(`simulation model diffDrive joint "${jointName}" must be revolute`);
112659
- }
112660
- });
112661
- }
112662
- const jointStatePublisher = cloneJointStatePublisher((_g2 = options.plugins) == null ? void 0 : _g2.jointStatePublisher);
112663
- if (jointStatePublisher == null ? void 0 : jointStatePublisher.joints) {
112664
- jointStatePublisher.joints.forEach((jointName) => {
112665
- if (!joints.has(jointName)) {
112666
- throw new Error(`simulation model jointStatePublisher references unknown joint "${jointName}"`);
112667
- }
112668
- });
112669
- }
112670
- const world = cloneWorld(options.world);
112671
- if (world == null ? void 0 : world.spawnPose) {
112672
- world.spawnPose.forEach((value, index2) => assertFinite(value, `simulation model world spawnPose[${index2}]`));
112673
- }
112674
- assertFinite((_h2 = world == null ? void 0 : world.keyboardTeleop) == null ? void 0 : _h2.linearStep, "simulation model world keyboardTeleop.linearStep");
112675
- assertFinite((_i2 = world == null ? void 0 : world.keyboardTeleop) == null ? void 0 : _i2.angularStep, "simulation model world keyboardTeleop.angularStep");
112676
- return {
112677
- modelName: (options.modelName ?? assembly2.name ?? "ForgeCAD Simulation").trim() || "ForgeCAD Simulation",
112678
- assembly: assembly2,
112679
- simulation,
112680
- source: options.source ?? "assembly",
112681
- state: { ...options.state ?? {} },
112682
- static: options.static ?? false,
112683
- selfCollide: options.selfCollide ?? false,
112684
- allowAutoDisable: options.allowAutoDisable ?? true,
112685
- links,
112686
- joints: jointOpts,
112687
- plugins: {
112688
- diffDrive: diffDrive2,
112689
- jointStatePublisher
112690
- },
112691
- world
112692
- };
112693
- }
112694
- function robotExport(options) {
112695
- if (!options || typeof options !== "object") {
112696
- throw new Error("robotExport(...) expects an options object");
112697
- }
112698
- if (!options.assembly || typeof options.assembly.describe !== "function") {
112699
- throw new Error("robotExport(...) requires an assembly");
112700
- }
112701
- _collectedRobotExport = collectSimulationModel(options.assembly, { ...options, source: "robotExport" });
112702
- return _collectedRobotExport;
112703
- }
112704
112549
  class Point2D {
112705
112550
  constructor(x2, y2) {
112706
112551
  this.x = x2;
@@ -134990,6 +134835,847 @@ function getSketchWorldMatrix(sketch) {
134990
134835
  Sketch.prototype.onFace = function(parentOrFace, faceOrOpts, maybeOpts = {}) {
134991
134836
  return sketchOnFace(this, parentOrFace, faceOrOpts, maybeOpts);
134992
134837
  };
134838
+ const KNOT_EPS = 1e-10;
134839
+ const KNOT_MERGE_EPS = 1e-7;
134840
+ function sameKnot(a2, b) {
134841
+ return Math.abs(a2 - b) <= KNOT_EPS;
134842
+ }
134843
+ function knotMultiplicity(knots, u2) {
134844
+ let count = 0;
134845
+ for (const k2 of knots) if (sameKnot(k2, u2)) count++;
134846
+ return count;
134847
+ }
134848
+ function computeParamsCentripetal(points, alpha = 0.5) {
134849
+ const n = points.length;
134850
+ const params = new Array(n).fill(0);
134851
+ const seg = new Array(n).fill(0);
134852
+ let total = 0;
134853
+ for (let k2 = 1; k2 < n; k2++) {
134854
+ const dx = points[k2][0] - points[k2 - 1][0];
134855
+ const dy = points[k2][1] - points[k2 - 1][1];
134856
+ const dz = points[k2][2] - points[k2 - 1][2];
134857
+ seg[k2] = Math.hypot(dx, dy, dz) ** alpha;
134858
+ total += seg[k2];
134859
+ }
134860
+ if (total === 0) {
134861
+ for (let k2 = 0; k2 < n; k2++) params[k2] = n > 1 ? k2 / (n - 1) : 0;
134862
+ return params;
134863
+ }
134864
+ let acc = 0;
134865
+ for (let k2 = 1; k2 < n - 1; k2++) {
134866
+ acc += seg[k2];
134867
+ params[k2] = acc / total;
134868
+ }
134869
+ params[n - 1] = 1;
134870
+ return params;
134871
+ }
134872
+ function knotsFromParamsAveraging(params, degree) {
134873
+ const n = params.length - 1;
134874
+ const m2 = n + degree + 1;
134875
+ const knots = new Array(m2 + 1).fill(0);
134876
+ for (let i = m2 - degree; i <= m2; i++) knots[i] = 1;
134877
+ for (let j = 1; j <= n - degree; j++) {
134878
+ let s = 0;
134879
+ for (let i = j; i <= j + degree - 1; i++) s += params[i];
134880
+ knots[j + degree] = s / degree;
134881
+ }
134882
+ return knots;
134883
+ }
134884
+ function solveLinear(A, B2) {
134885
+ const n = A.length;
134886
+ const k2 = B2[0].length;
134887
+ const M = A.map((row, i) => [...row, ...B2[i]]);
134888
+ for (let col = 0; col < n; col++) {
134889
+ let piv = col;
134890
+ for (let r = col + 1; r < n; r++) if (Math.abs(M[r][col]) > Math.abs(M[piv][col])) piv = r;
134891
+ if (Math.abs(M[piv][col]) < 1e-14) throw new Error(`Singular interpolation matrix at column ${col}.`);
134892
+ [M[col], M[piv]] = [M[piv], M[col]];
134893
+ const inv = 1 / M[col][col];
134894
+ for (let j = col; j < n + k2; j++) M[col][j] *= inv;
134895
+ for (let r = 0; r < n; r++) {
134896
+ if (r === col) continue;
134897
+ const f3 = M[r][col];
134898
+ if (f3 === 0) continue;
134899
+ for (let j = col; j < n + k2; j++) M[r][j] -= f3 * M[col][j];
134900
+ }
134901
+ }
134902
+ return Array.from({ length: n }, (_2, i) => M[i].slice(n));
134903
+ }
134904
+ function globalCurveInterp(points, degree, paramsIn) {
134905
+ const last = points.length - 1;
134906
+ if (last < degree) throw new Error(`Curve interpolation needs at least ${degree + 1} points, got ${points.length}.`);
134907
+ const params = paramsIn ? paramsIn.slice() : computeParamsCentripetal(points, 0.5);
134908
+ const knots = knotsFromParamsAveraging(params, degree);
134909
+ const count = last + 1;
134910
+ const N = Array.from({ length: count }, () => new Array(count).fill(0));
134911
+ for (let i = 0; i <= last; i++) {
134912
+ const span = findSpan(count, degree, params[i], knots);
134913
+ const basis = basisFuns(span, params[i], degree, knots);
134914
+ for (let j = 0; j <= degree; j++) N[i][span - degree + j] = basis[j];
134915
+ }
134916
+ const Q = points.map((p2) => [p2[0], p2[1], p2[2]]);
134917
+ const cps = solveLinear(N, Q);
134918
+ return { cps, knots, degree };
134919
+ }
134920
+ function insertKnotCurve(cps, knots, degree, u2) {
134921
+ const n = cps.length;
134922
+ const span = findSpan(n, degree, u2, knots);
134923
+ const mult = knotMultiplicity(knots, u2);
134924
+ if (mult >= degree) return { cps: cps.slice(), knots: knots.slice() };
134925
+ const newCps = new Array(n + 1);
134926
+ const newKnots = new Array(knots.length + 1);
134927
+ for (let i = 0; i <= span; i++) newKnots[i] = knots[i];
134928
+ newKnots[span + 1] = u2;
134929
+ for (let i = span + 1; i < knots.length; i++) newKnots[i + 1] = knots[i];
134930
+ for (let i = 0; i <= span - degree; i++) newCps[i] = [...cps[i]];
134931
+ for (let i = span - mult; i < n; i++) newCps[i + 1] = [...cps[i]];
134932
+ for (let i = span - degree + 1; i <= span - mult; i++) {
134933
+ const denom = knots[i + degree] - knots[i];
134934
+ const alpha = denom === 0 ? 0 : (u2 - knots[i]) / denom;
134935
+ const a2 = cps[i - 1];
134936
+ const b = cps[i];
134937
+ newCps[i] = [(1 - alpha) * a2[0] + alpha * b[0], (1 - alpha) * a2[1] + alpha * b[1], (1 - alpha) * a2[2] + alpha * b[2]];
134938
+ }
134939
+ return { cps: newCps, knots: newKnots };
134940
+ }
134941
+ function interiorKnotBuckets(kv, mergeEps = KNOT_MERGE_EPS) {
134942
+ const buckets = [];
134943
+ for (const k2 of kv) {
134944
+ if (k2 <= KNOT_EPS || k2 >= 1 - KNOT_EPS) continue;
134945
+ const b = buckets.find((x2) => Math.abs(x2.value - k2) <= mergeEps);
134946
+ if (b) b.mult++;
134947
+ else buckets.push({ value: k2, mult: 1 });
134948
+ }
134949
+ return buckets;
134950
+ }
134951
+ function mergeKnotVectors(knotVectors, degree, mergeEps = KNOT_MERGE_EPS) {
134952
+ const merged = [];
134953
+ for (const kv of knotVectors) {
134954
+ for (const { value, mult } of interiorKnotBuckets(kv, mergeEps)) {
134955
+ const b = merged.find((x2) => Math.abs(x2.value - value) <= mergeEps);
134956
+ if (b) b.mult = Math.max(b.mult, mult);
134957
+ else merged.push({ value, mult });
134958
+ }
134959
+ }
134960
+ merged.sort((a2, b) => a2.value - b.value);
134961
+ const out = [];
134962
+ for (let i = 0; i <= degree; i++) out.push(0);
134963
+ for (const { value, mult } of merged) for (let i = 0; i < mult; i++) out.push(value);
134964
+ for (let i = 0; i <= degree; i++) out.push(1);
134965
+ return out;
134966
+ }
134967
+ function refineKnotsToTarget(cps, knots, degree, targetKnots, mergeEps = KNOT_MERGE_EPS) {
134968
+ let curCps = cps.map((p2) => [...p2]);
134969
+ let curKnots = knots.slice();
134970
+ for (const { value, mult } of interiorKnotBuckets(targetKnots, mergeEps)) {
134971
+ const curMult = () => curKnots.reduce((c2, k2) => c2 + (Math.abs(k2 - value) <= mergeEps ? 1 : 0), 0);
134972
+ let guard = 0;
134973
+ while (curMult() < mult) {
134974
+ const r = insertKnotCurve(curCps, curKnots, degree, value);
134975
+ if (r.cps.length === curCps.length) break;
134976
+ curCps = r.cps;
134977
+ curKnots = r.knots;
134978
+ if (++guard > degree + 2) break;
134979
+ }
134980
+ }
134981
+ return { cps: curCps, knots: curKnots };
134982
+ }
134983
+ function binomial(a2, b) {
134984
+ if (b < 0 || b > a2) return 0;
134985
+ let r = 1;
134986
+ for (let i = 0; i < b; i++) r = r * (a2 - i) / (i + 1);
134987
+ return r;
134988
+ }
134989
+ function bezierElevate(bez, p2, t) {
134990
+ const m2 = p2 + t;
134991
+ const out = [];
134992
+ for (let i = 0; i <= m2; i++) {
134993
+ const acc = [0, 0, 0];
134994
+ const lo = Math.max(0, i - t);
134995
+ const hi = Math.min(p2, i);
134996
+ for (let j = lo; j <= hi; j++) {
134997
+ const w2 = binomial(p2, j) * binomial(t, i - j) / binomial(m2, i);
134998
+ acc[0] += w2 * bez[j][0];
134999
+ acc[1] += w2 * bez[j][1];
135000
+ acc[2] += w2 * bez[j][2];
135001
+ }
135002
+ out.push(acc);
135003
+ }
135004
+ return out;
135005
+ }
135006
+ function elevateCurveDegree(cps, knots, degree, targetDegree) {
135007
+ if (targetDegree === degree) return { cps: cps.map((p2) => [...p2]), knots: knots.slice(), degree };
135008
+ if (targetDegree < degree) throw new Error("Degree reduction is not supported.");
135009
+ const t = targetDegree - degree;
135010
+ let curCps = cps.map((p2) => [...p2]);
135011
+ let curKnots = knots.slice();
135012
+ const breaks = interiorKnotBuckets(curKnots).map((b) => b.value);
135013
+ for (const v of breaks) {
135014
+ let guard = 0;
135015
+ while (knotMultiplicity(curKnots, v) < degree) {
135016
+ const r = insertKnotCurve(curCps, curKnots, degree, v);
135017
+ curCps = r.cps;
135018
+ curKnots = r.knots;
135019
+ if (++guard > degree + 2) break;
135020
+ }
135021
+ }
135022
+ const segVals = [0, ...breaks.slice().sort((a2, b) => a2 - b), 1];
135023
+ const nSeg = segVals.length - 1;
135024
+ const newCps = [];
135025
+ for (let s = 0; s < nSeg; s++) {
135026
+ const bez = curCps.slice(s * degree, s * degree + degree + 1);
135027
+ const elevated = bezierElevate(bez, degree, t);
135028
+ if (s === 0) newCps.push(...elevated);
135029
+ else newCps.push(...elevated.slice(1));
135030
+ }
135031
+ const newKnots = [];
135032
+ for (let i = 0; i <= targetDegree; i++) newKnots.push(0);
135033
+ for (let s = 1; s < nSeg; s++) for (let i = 0; i < targetDegree; i++) newKnots.push(segVals[s]);
135034
+ for (let i = 0; i <= targetDegree; i++) newKnots.push(1);
135035
+ if (newKnots.length !== newCps.length + targetDegree + 1) {
135036
+ throw new Error(
135037
+ `Degree elevation produced inconsistent knots (${newKnots.length}) for ${newCps.length} control points at degree ${targetDegree}.`
135038
+ );
135039
+ }
135040
+ return { cps: newCps, knots: newKnots, degree: targetDegree };
135041
+ }
135042
+ function unifyCurves(curves) {
135043
+ const targetDeg = Math.max(...curves.map((c2) => c2.degree));
135044
+ const elevated = curves.map((c2) => elevateCurveDegree(c2.cps, c2.knots, c2.degree, targetDeg));
135045
+ const common2 = mergeKnotVectors(
135046
+ elevated.map((c2) => c2.knots),
135047
+ targetDeg
135048
+ );
135049
+ const refined = elevated.map((c2) => refineKnotsToTarget(c2.cps, c2.knots, targetDeg, common2));
135050
+ const ncp = common2.length - targetDeg - 1;
135051
+ for (const r of refined) {
135052
+ if (r.cps.length !== ncp) throw new Error(`Curve unification produced ${r.cps.length} control points, expected ${ncp}.`);
135053
+ }
135054
+ return { degree: targetDeg, knots: common2, curves: refined.map((r) => r.cps) };
135055
+ }
135056
+ function transpose(g2) {
135057
+ const out = [];
135058
+ for (let j = 0; j < g2[0].length; j++) {
135059
+ const row = [];
135060
+ for (let i = 0; i < g2.length; i++) row.push(g2[i][j]);
135061
+ out.push(row);
135062
+ }
135063
+ return out;
135064
+ }
135065
+ function skin(curveCps, spanParams, spanDegree) {
135066
+ const ncp = curveCps[0].length;
135067
+ const cols = [];
135068
+ for (let j = 0; j < ncp; j++) {
135069
+ const pts = curveCps.map((row) => row[j]);
135070
+ cols.push(globalCurveInterp(pts, spanDegree, spanParams).cps);
135071
+ }
135072
+ const spanCp = cols[0].length;
135073
+ const grid = [];
135074
+ for (let s = 0; s < spanCp; s++) {
135075
+ const row = [];
135076
+ for (let j = 0; j < ncp; j++) row.push(cols[j][s]);
135077
+ grid.push(row);
135078
+ }
135079
+ const spanKnots = knotsFromParamsAveraging(spanParams, spanDegree);
135080
+ return { grid, spanKnots };
135081
+ }
135082
+ function tensorInterp(gridPts, uParams, vParams, degreeU, degreeV) {
135083
+ const rowInterp = gridPts.map((rowPts) => globalCurveInterp(rowPts, degreeV, vParams));
135084
+ const knotsV = rowInterp[0].knots;
135085
+ const ncpV = rowInterp[0].cps.length;
135086
+ const cols = [];
135087
+ for (let j = 0; j < ncpV; j++) {
135088
+ const pts = rowInterp.map((ri) => ri.cps[j]);
135089
+ cols.push(globalCurveInterp(pts, degreeU, uParams));
135090
+ }
135091
+ const knotsU = cols[0].knots;
135092
+ const cpU = cols[0].cps.length;
135093
+ const grid = [];
135094
+ for (let i = 0; i < cpU; i++) {
135095
+ const row = [];
135096
+ for (let j = 0; j < ncpV; j++) row.push(cols[j].cps[i]);
135097
+ grid.push(row);
135098
+ }
135099
+ return { grid, knotsU, knotsV };
135100
+ }
135101
+ function refineSurface(surf, targetKnotsU, targetKnotsV) {
135102
+ const { grid, knotsU, knotsV, degreeU, degreeV } = surf;
135103
+ const rows = grid.map((row) => refineKnotsToTarget(row, knotsV, degreeV, targetKnotsV).cps);
135104
+ const ncpV = rows[0].length;
135105
+ const cols = [];
135106
+ for (let j = 0; j < ncpV; j++) {
135107
+ const colPts = rows.map((r) => r[j]);
135108
+ cols.push(refineKnotsToTarget(colPts, knotsU, degreeU, targetKnotsU).cps);
135109
+ }
135110
+ const cpU = cols[0].length;
135111
+ const out = [];
135112
+ for (let i = 0; i < cpU; i++) {
135113
+ const row = [];
135114
+ for (let j = 0; j < ncpV; j++) row.push(cols[j][i]);
135115
+ out.push(row);
135116
+ }
135117
+ return { grid: out, knotsU: targetKnotsU, knotsV: targetKnotsV, degreeU, degreeV };
135118
+ }
135119
+ function averageParams(polylines, alpha = 0.5) {
135120
+ const count = polylines[0].length;
135121
+ const acc = new Array(count).fill(0);
135122
+ for (const pl of polylines) {
135123
+ const p2 = computeParamsCentripetal(pl, alpha);
135124
+ for (let i = 0; i < count; i++) acc[i] += p2[i];
135125
+ }
135126
+ return acc.map((s) => s / polylines.length);
135127
+ }
135128
+ function gordonGridParams(gridPts) {
135129
+ const rows = gridPts;
135130
+ const cols = gridPts[0].map((_2, l) => gridPts.map((r) => r[l]));
135131
+ return { vParams: averageParams(rows, 0.5), uParams: averageParams(cols, 0.5) };
135132
+ }
135133
+ function buildGordonFromGrid(gridPts, degreeU = 3, degreeV = 3) {
135134
+ const m2 = gridPts.length - 1;
135135
+ const n = gridPts[0].length - 1;
135136
+ if (m2 < 1 || n < 1) throw new Error("A Gordon surface needs at least a 2x2 curve network.");
135137
+ if (m2 < degreeU) degreeU = m2;
135138
+ if (n < degreeV) degreeV = n;
135139
+ const rows = gridPts;
135140
+ const cols = gridPts[0].map((_2, l) => gridPts.map((r) => r[l]));
135141
+ const { uParams, vParams } = gordonGridParams(gridPts);
135142
+ const uCurves = rows.map((rowPts) => globalCurveInterp(rowPts, degreeV, vParams));
135143
+ const vCurves = cols.map((colPts) => globalCurveInterp(colPts, degreeU, uParams));
135144
+ const uFam = unifyCurves(uCurves);
135145
+ const vFam = unifyCurves(vCurves);
135146
+ const Lu = skin(uFam.curves, uParams, degreeU);
135147
+ const LvRaw = skin(vFam.curves, vParams, degreeV);
135148
+ const Lv = { grid: transpose(LvRaw.grid), knotsU: vFam.knots, knotsV: LvRaw.spanKnots };
135149
+ const T = tensorInterp(gridPts, uParams, vParams, degreeU, degreeV);
135150
+ const knotsU = mergeKnotVectors([Lu.spanKnots, Lv.knotsU, T.knotsU], degreeU);
135151
+ const knotsV = mergeKnotVectors([uFam.knots, Lv.knotsV, T.knotsV], degreeV);
135152
+ const LuS = refineSurface({ grid: Lu.grid, knotsU: Lu.spanKnots, knotsV: uFam.knots, degreeU, degreeV }, knotsU, knotsV);
135153
+ const LvS = refineSurface({ grid: Lv.grid, knotsU: Lv.knotsU, knotsV: Lv.knotsV, degreeU, degreeV }, knotsU, knotsV);
135154
+ const TS = refineSurface({ grid: T.grid, knotsU: T.knotsU, knotsV: T.knotsV, degreeU, degreeV }, knotsU, knotsV);
135155
+ const nuc = LuS.grid.length;
135156
+ const nvc = LuS.grid[0].length;
135157
+ if (LvS.grid.length !== nuc || TS.grid.length !== nuc || LvS.grid[0].length !== nvc || TS.grid[0].length !== nvc) {
135158
+ throw new Error(
135159
+ `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}.`
135160
+ );
135161
+ }
135162
+ const grid = [];
135163
+ for (let i = 0; i < nuc; i++) {
135164
+ const row = [];
135165
+ for (let j = 0; j < nvc; j++) {
135166
+ const a2 = LuS.grid[i][j];
135167
+ const b = LvS.grid[i][j];
135168
+ const c2 = TS.grid[i][j];
135169
+ row.push([a2[0] + b[0] - c2[0], a2[1] + b[1] - c2[1], a2[2] + b[2] - c2[2]]);
135170
+ }
135171
+ grid.push(row);
135172
+ }
135173
+ return { grid, knotsU, knotsV, degreeU, degreeV };
135174
+ }
135175
+ function evalSurface(surf, u2, v) {
135176
+ const { grid, knotsU, knotsV, degreeU, degreeV } = surf;
135177
+ const nu = grid.length;
135178
+ const nv = grid[0].length;
135179
+ const su = findSpan(nu, degreeU, u2, knotsU);
135180
+ const sv = findSpan(nv, degreeV, v, knotsV);
135181
+ const Nu = basisFuns(su, u2, degreeU, knotsU);
135182
+ const Nv = basisFuns(sv, v, degreeV, knotsV);
135183
+ const out = [0, 0, 0];
135184
+ for (let i = 0; i <= degreeU; i++) {
135185
+ const ui = su - degreeU + i;
135186
+ for (let j = 0; j <= degreeV; j++) {
135187
+ const vj = sv - degreeV + j;
135188
+ const w2 = Nu[i] * Nv[j];
135189
+ const p2 = grid[ui][vj];
135190
+ out[0] += w2 * p2[0];
135191
+ out[1] += w2 * p2[1];
135192
+ out[2] += w2 * p2[2];
135193
+ }
135194
+ }
135195
+ return out;
135196
+ }
135197
+ function evalSurfaceJet(surf, u2, v) {
135198
+ const { grid, knotsU, knotsV, degreeU, degreeV } = surf;
135199
+ const nu = grid.length;
135200
+ const nv = grid[0].length;
135201
+ const su = findSpan(nu, degreeU, u2, knotsU);
135202
+ const sv = findSpan(nv, degreeV, v, knotsV);
135203
+ const dU = basisFunsDeriv(su, u2, degreeU, knotsU, 2);
135204
+ const dV = basisFunsDeriv(sv, v, degreeV, knotsV, 2);
135205
+ const S = [0, 0, 0];
135206
+ const Su = [0, 0, 0];
135207
+ const Sv = [0, 0, 0];
135208
+ const Suu = [0, 0, 0];
135209
+ const Suv = [0, 0, 0];
135210
+ const Svv = [0, 0, 0];
135211
+ for (let i = 0; i <= degreeU; i++) {
135212
+ const ui = su - degreeU + i;
135213
+ for (let j = 0; j <= degreeV; j++) {
135214
+ const vj = sv - degreeV + j;
135215
+ const p2 = grid[ui][vj];
135216
+ const w00 = dU[0][i] * dV[0][j];
135217
+ const w10 = dU[1][i] * dV[0][j];
135218
+ const w01 = dU[0][i] * dV[1][j];
135219
+ const w20 = dU[2][i] * dV[0][j];
135220
+ const w11 = dU[1][i] * dV[1][j];
135221
+ const w02 = dU[0][i] * dV[2][j];
135222
+ for (let c2 = 0; c2 < 3; c2++) {
135223
+ S[c2] += w00 * p2[c2];
135224
+ Su[c2] += w10 * p2[c2];
135225
+ Sv[c2] += w01 * p2[c2];
135226
+ Suu[c2] += w20 * p2[c2];
135227
+ Suv[c2] += w11 * p2[c2];
135228
+ Svv[c2] += w02 * p2[c2];
135229
+ }
135230
+ }
135231
+ }
135232
+ let nx = Su[1] * Sv[2] - Su[2] * Sv[1];
135233
+ let ny = Su[2] * Sv[0] - Su[0] * Sv[2];
135234
+ let nz = Su[0] * Sv[1] - Su[1] * Sv[0];
135235
+ const len2 = Math.hypot(nx, ny, nz) || 1;
135236
+ nx /= len2;
135237
+ ny /= len2;
135238
+ nz /= len2;
135239
+ return { S, Su, Sv, Suu, Suv, Svv, normal: [nx, ny, nz] };
135240
+ }
135241
+ function surfaceCurvature(jet) {
135242
+ const { Su, Sv, Suu, Suv, Svv, normal: normal2 } = jet;
135243
+ const E = Su[0] * Su[0] + Su[1] * Su[1] + Su[2] * Su[2];
135244
+ const F = Su[0] * Sv[0] + Su[1] * Sv[1] + Su[2] * Sv[2];
135245
+ const G = Sv[0] * Sv[0] + Sv[1] * Sv[1] + Sv[2] * Sv[2];
135246
+ const e = Suu[0] * normal2[0] + Suu[1] * normal2[1] + Suu[2] * normal2[2];
135247
+ const f3 = Suv[0] * normal2[0] + Suv[1] * normal2[1] + Suv[2] * normal2[2];
135248
+ const g2 = Svv[0] * normal2[0] + Svv[1] * normal2[1] + Svv[2] * normal2[2];
135249
+ const denom = E * G - F * F;
135250
+ if (Math.abs(denom) < 1e-20) return { k1: 0, k2: 0, K: 0, H: 0 };
135251
+ const K = (e * g2 - f3 * f3) / denom;
135252
+ const H = (e * G - 2 * f3 * F + g2 * E) / (2 * denom);
135253
+ const disc = Math.max(0, H * H - K);
135254
+ const root = Math.sqrt(disc);
135255
+ return { k1: H + root, k2: H - root, K, H };
135256
+ }
135257
+ function isVec3(v) {
135258
+ return Array.isArray(v) && v.length === 3 && typeof v[0] === "number" && typeof v[1] === "number" && typeof v[2] === "number";
135259
+ }
135260
+ function requireFiniteVec(p2, label) {
135261
+ for (let i = 0; i < 3; i++)
135262
+ if (!Number.isFinite(p2[i])) throw new Error(`Surface.Net: ${label} component ${i} must be finite, got ${p2[i]}`);
135263
+ return [p2[0], p2[1], p2[2]];
135264
+ }
135265
+ function toSampler(input, label) {
135266
+ if (input instanceof NurbsCurve3D) {
135267
+ return (t) => input.pointAt(t);
135268
+ }
135269
+ if (Array.isArray(input) && input.length > 0 && isVec3(input[0])) {
135270
+ const pts = input.map((p2, i) => requireFiniteVec(p2, `${label}[${i}]`));
135271
+ if (pts.length < 2) throw new Error(`Surface.Net: ${label} needs at least 2 points.`);
135272
+ if (pts.length === 2) {
135273
+ const [a2, b] = pts;
135274
+ return (t) => [a2[0] + (b[0] - a2[0]) * t, a2[1] + (b[1] - a2[1]) * t, a2[2] + (b[2] - a2[2]) * t];
135275
+ }
135276
+ const curve = globalCurveInterp(pts, Math.min(3, pts.length - 1));
135277
+ const nc = new NurbsCurve3D(curve.cps, { degree: curve.degree, knots: curve.knots });
135278
+ return (t) => nc.pointAt(t);
135279
+ }
135280
+ throw new Error(`Surface.Net: ${label} must be a Curve.Fit/Curve.Nurbs value or an array of [x,y,z] points.`);
135281
+ }
135282
+ function sampleCurve(sampler, count) {
135283
+ const out = [];
135284
+ for (let i = 0; i < count; i++) out.push(sampler(i / (count - 1)));
135285
+ return out;
135286
+ }
135287
+ const DEFAULT_THICKEN_RESOLUTION = 48;
135288
+ class Sheet {
135289
+ constructor(surface) {
135290
+ this.surface = surface;
135291
+ }
135292
+ /** Edge naming follows parameter direction (documented): front=v0, rear=v1, left=u0, right=u1. */
135293
+ get frontEdge() {
135294
+ return { sheet: this, fixed: "v", value: 0 };
135295
+ }
135296
+ get rearEdge() {
135297
+ return { sheet: this, fixed: "v", value: 1 };
135298
+ }
135299
+ get leftEdge() {
135300
+ return { sheet: this, fixed: "u", value: 0 };
135301
+ }
135302
+ get rightEdge() {
135303
+ return { sheet: this, fixed: "u", value: 1 };
135304
+ }
135305
+ pointAt(u2, v) {
135306
+ return evalSurface(this.surface, clamp01$2(u2), clamp01$2(v));
135307
+ }
135308
+ normalAt(u2, v) {
135309
+ return evalSurfaceJet(this.surface, clamp01$2(u2), clamp01$2(v)).normal;
135310
+ }
135311
+ curvatureAt(u2, v) {
135312
+ return surfaceCurvature(evalSurfaceJet(this.surface, clamp01$2(u2), clamp01$2(v)));
135313
+ }
135314
+ /** Largest principal curvature magnitude over a sampling grid (for offset safety). */
135315
+ maxAbsPrincipalCurvature(samples = 9) {
135316
+ let maxK = 0;
135317
+ for (let i = 0; i <= samples; i++) {
135318
+ for (let j = 0; j <= samples; j++) {
135319
+ const c2 = this.curvatureAt(i / samples, j / samples);
135320
+ maxK = Math.max(maxK, Math.abs(c2.k1), Math.abs(c2.k2));
135321
+ }
135322
+ }
135323
+ return maxK;
135324
+ }
135325
+ /**
135326
+ * Offset the sheet along its analytic normals into a watertight solid shell of
135327
+ * the given wall thickness. Throws if the wall would self-intersect on a
135328
+ * concave region (no silent degenerate solid).
135329
+ */
135330
+ thicken(wall, options = {}) {
135331
+ if (!Number.isFinite(wall) || wall <= 0) throw new Error(`Sheet.thicken: wall must be a positive finite number, got ${wall}`);
135332
+ const maxK = this.maxAbsPrincipalCurvature();
135333
+ if (wall * maxK >= 1) {
135334
+ throw new Error(
135335
+ `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.`
135336
+ );
135337
+ }
135338
+ const resolution = options.resolution ?? DEFAULT_THICKEN_RESOLUTION;
135339
+ return nurbsSurface(this.surface.grid, {
135340
+ degreeU: this.surface.degreeU,
135341
+ degreeV: this.surface.degreeV,
135342
+ knotsU: this.surface.knotsU,
135343
+ knotsV: this.surface.knotsV,
135344
+ thickness: wall,
135345
+ resolution
135346
+ });
135347
+ }
135348
+ /** Per-edge continuity match against a neighbor (returns a NEW Sheet). */
135349
+ matchEdge(edge) {
135350
+ if (edge.sheet !== this) throw new Error("Sheet.matchEdge: the edge must belong to this sheet.");
135351
+ return new MatchEdgeBuilder(this, edge);
135352
+ }
135353
+ }
135354
+ function clamp01$2(t) {
135355
+ return t < 0 ? 0 : t > 1 ? 1 : t;
135356
+ }
135357
+ class CurveNetBuilder {
135358
+ constructor() {
135359
+ __publicField(this, "lengthwiseCurves", []);
135360
+ __publicField(this, "crosswiseCurves", []);
135361
+ __publicField(this, "railCurves", []);
135362
+ __publicField(this, "cageGrid", null);
135363
+ __publicField(this, "degU");
135364
+ __publicField(this, "degV");
135365
+ __publicField(this, "built", null);
135366
+ }
135367
+ lengthwise(...curves) {
135368
+ this.lengthwiseCurves = curves;
135369
+ this.built = null;
135370
+ return this;
135371
+ }
135372
+ crosswise(...curves) {
135373
+ this.crosswiseCurves = curves;
135374
+ this.built = null;
135375
+ return this;
135376
+ }
135377
+ alongRails(railA, railB) {
135378
+ this.railCurves = [railA, railB];
135379
+ this.built = null;
135380
+ return this;
135381
+ }
135382
+ sections(...curves) {
135383
+ this.crosswiseCurves = curves;
135384
+ this.built = null;
135385
+ return this;
135386
+ }
135387
+ cage(grid) {
135388
+ if (!Array.isArray(grid) || grid.length < 2 || !Array.isArray(grid[0]) || grid[0].length < 2) {
135389
+ throw new Error("Surface.Net().cage: grid must be at least a 2x2 array of [x,y,z] points.");
135390
+ }
135391
+ const cols = grid[0].length;
135392
+ this.cageGrid = grid.map((row, k2) => {
135393
+ if (row.length !== cols) throw new Error(`Surface.Net().cage: row ${k2} has ${row.length} points, expected ${cols}.`);
135394
+ return row.map((p2, l) => requireFiniteVec(p2, `cage[${k2}][${l}]`));
135395
+ });
135396
+ this.built = null;
135397
+ return this;
135398
+ }
135399
+ degree(u2, v) {
135400
+ if (!Number.isInteger(u2) || u2 < 1) throw new Error(`Surface.Net().degree: degree must be a positive integer, got ${u2}`);
135401
+ this.degU = u2;
135402
+ this.degV = v === void 0 ? u2 : v;
135403
+ if (v !== void 0 && (!Number.isInteger(v) || v < 1))
135404
+ throw new Error(`Surface.Net().degree: degree must be a positive integer, got ${v}`);
135405
+ this.built = null;
135406
+ return this;
135407
+ }
135408
+ /** Build (once) and return the Sheet. */
135409
+ toSheet() {
135410
+ if (this.built) return this.built;
135411
+ const grid = this.buildGrid();
135412
+ const m2 = grid.length - 1;
135413
+ const n = grid[0].length - 1;
135414
+ const degreeU = Math.max(1, Math.min(this.degU ?? 3, m2));
135415
+ const degreeV = Math.max(1, Math.min(this.degV ?? 3, n));
135416
+ const surface = buildGordonFromGrid(grid, degreeU, degreeV);
135417
+ this.built = new Sheet(surface);
135418
+ return this.built;
135419
+ }
135420
+ buildGrid() {
135421
+ if (this.cageGrid) return this.cageGrid;
135422
+ const lengthwise = this.railCurves.length > 0 ? this.railCurves : this.lengthwiseCurves;
135423
+ const crosswise = this.crosswiseCurves;
135424
+ const SAMPLES = 17;
135425
+ if (lengthwise.length >= 2) {
135426
+ const samplers = lengthwise.map((c2, l) => toSampler(c2, `lengthwise[${l}]`));
135427
+ const grid = [];
135428
+ for (let i = 0; i < SAMPLES; i++) {
135429
+ const u2 = i / (SAMPLES - 1);
135430
+ grid.push(samplers.map((s) => s(u2)));
135431
+ }
135432
+ return grid;
135433
+ }
135434
+ if (crosswise.length >= 2) {
135435
+ const samplers = crosswise.map((c2, k2) => toSampler(c2, `crosswise[${k2}]`));
135436
+ const grid = [];
135437
+ for (const s of samplers) grid.push(sampleCurve(s, SAMPLES));
135438
+ return grid;
135439
+ }
135440
+ throw new Error(
135441
+ "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."
135442
+ );
135443
+ }
135444
+ // ── Sheet delegation (build on first access) ──────────────────────────────
135445
+ get frontEdge() {
135446
+ return this.toSheet().frontEdge;
135447
+ }
135448
+ get rearEdge() {
135449
+ return this.toSheet().rearEdge;
135450
+ }
135451
+ get leftEdge() {
135452
+ return this.toSheet().leftEdge;
135453
+ }
135454
+ get rightEdge() {
135455
+ return this.toSheet().rightEdge;
135456
+ }
135457
+ get surface() {
135458
+ return this.toSheet().surface;
135459
+ }
135460
+ pointAt(u2, v) {
135461
+ return this.toSheet().pointAt(u2, v);
135462
+ }
135463
+ normalAt(u2, v) {
135464
+ return this.toSheet().normalAt(u2, v);
135465
+ }
135466
+ curvatureAt(u2, v) {
135467
+ return this.toSheet().curvatureAt(u2, v);
135468
+ }
135469
+ thicken(wall, options) {
135470
+ return this.toSheet().thicken(wall, options);
135471
+ }
135472
+ matchEdge(edge) {
135473
+ return this.toSheet().matchEdge(edge);
135474
+ }
135475
+ }
135476
+ function createCurveNet() {
135477
+ return new CurveNetBuilder();
135478
+ }
135479
+ class MatchEdgeBuilder {
135480
+ constructor(sheet, edge) {
135481
+ this.sheet = sheet;
135482
+ this.edge = edge;
135483
+ }
135484
+ toG0(neighbor) {
135485
+ return applyEdgeMatch(this.sheet, this.edge, neighbor, 0);
135486
+ }
135487
+ toG1(neighbor) {
135488
+ return applyEdgeMatch(this.sheet, this.edge, neighbor, 1);
135489
+ }
135490
+ toG2(neighbor) {
135491
+ return applyEdgeMatch(this.sheet, this.edge, neighbor, 2);
135492
+ }
135493
+ }
135494
+ function boundaryRows(surf, fixed, value, depth) {
135495
+ const rows = [];
135496
+ if (fixed === "u") {
135497
+ const nU = surf.grid.length;
135498
+ for (let d2 = 0; d2 <= depth; d2++) {
135499
+ const i = value === 0 ? d2 : nU - 1 - d2;
135500
+ rows.push(surf.grid[i].map((p2) => [p2[0], p2[1], p2[2]]));
135501
+ }
135502
+ } else {
135503
+ const nV = surf.grid[0].length;
135504
+ for (let d2 = 0; d2 <= depth; d2++) {
135505
+ const j = value === 0 ? d2 : nV - 1 - d2;
135506
+ rows.push(surf.grid.map((p2) => [p2[j][0], p2[j][1], p2[j][2]]));
135507
+ }
135508
+ }
135509
+ return rows;
135510
+ }
135511
+ function setBoundaryRow(surf, fixed, value, depth, row) {
135512
+ if (fixed === "u") {
135513
+ const nU = surf.grid.length;
135514
+ const i = value === 0 ? depth : nU - 1 - depth;
135515
+ for (let l = 0; l < surf.grid[i].length; l++) surf.grid[i][l] = [row[l][0], row[l][1], row[l][2]];
135516
+ } else {
135517
+ const nV = surf.grid[0].length;
135518
+ const j = value === 0 ? depth : nV - 1 - depth;
135519
+ for (let k2 = 0; k2 < surf.grid.length; k2++) surf.grid[k2][j] = [row[k2][0], row[k2][1], row[k2][2]];
135520
+ }
135521
+ }
135522
+ function cloneSurface(surf) {
135523
+ return {
135524
+ grid: surf.grid.map((row) => row.map((p2) => [p2[0], p2[1], p2[2]])),
135525
+ knotsU: [...surf.knotsU],
135526
+ knotsV: [...surf.knotsV],
135527
+ degreeU: surf.degreeU,
135528
+ degreeV: surf.degreeV
135529
+ };
135530
+ }
135531
+ function applyEdgeMatch(sheet, edge, neighbor, order) {
135532
+ const result = cloneSurface(sheet.surface);
135533
+ const my = boundaryRows(result, edge.fixed, edge.value, order);
135534
+ const their = boundaryRows(neighbor.sheet.surface, neighbor.fixed, neighbor.value, order);
135535
+ if (my[0].length !== their[0].length) {
135536
+ throw new Error(
135537
+ `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.`
135538
+ );
135539
+ }
135540
+ const len2 = my[0].length;
135541
+ const b0 = their[0].map((p2) => [p2[0], p2[1], p2[2]]);
135542
+ setBoundaryRow(result, edge.fixed, edge.value, 0, b0);
135543
+ if (order === 0) return new Sheet(result);
135544
+ const b1 = [];
135545
+ for (let i = 0; i < len2; i++) {
135546
+ const p0 = their[0][i];
135547
+ const p1 = their[1][i];
135548
+ b1.push([2 * p0[0] - p1[0], 2 * p0[1] - p1[1], 2 * p0[2] - p1[2]]);
135549
+ }
135550
+ setBoundaryRow(result, edge.fixed, edge.value, 1, b1);
135551
+ if (order === 1) return new Sheet(result);
135552
+ const b22 = [];
135553
+ for (let i = 0; i < len2; i++) {
135554
+ const p0 = their[0][i];
135555
+ const p1 = their[1][i];
135556
+ const p2 = their[2][i];
135557
+ 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]]);
135558
+ }
135559
+ setBoundaryRow(result, edge.fixed, edge.value, 2, b22);
135560
+ return new Sheet(result);
135561
+ }
135562
+ function edgeJet(edge, t) {
135563
+ const surf = edge.sheet.surface;
135564
+ if (edge.fixed === "u") {
135565
+ const j2 = evalSurfaceJet(surf, edge.value, clamp01$2(t));
135566
+ return { point: j2.S, cross: j2.Su, cross2: j2.Suu };
135567
+ }
135568
+ const j = evalSurfaceJet(surf, clamp01$2(t), edge.value);
135569
+ return { point: j.S, cross: j.Sv, cross2: j.Svv };
135570
+ }
135571
+ function sub3(a2, b) {
135572
+ return [a2[0] - b[0], a2[1] - b[1], a2[2] - b[2]];
135573
+ }
135574
+ function len3(a2) {
135575
+ return Math.hypot(a2[0], a2[1], a2[2]);
135576
+ }
135577
+ function unit3(a2) {
135578
+ const l = len3(a2) || 1;
135579
+ return [a2[0] / l, a2[1] / l, a2[2] / l];
135580
+ }
135581
+ function edgeMatchReport(edgeA, edgeB, samples = 24) {
135582
+ let maxPositionGap = 0;
135583
+ let maxTangentAngleDeg = 0;
135584
+ let maxCurvatureRelErr = 0;
135585
+ for (let i = 0; i <= samples; i++) {
135586
+ const t = i / samples;
135587
+ const a2 = edgeJet(edgeA, t);
135588
+ const b = edgeJet(edgeB, t);
135589
+ maxPositionGap = Math.max(maxPositionGap, len3(sub3(a2.point, b.point)));
135590
+ const ua = unit3(a2.cross);
135591
+ const ub = unit3(b.cross);
135592
+ const dot2 = Math.max(-1, Math.min(1, ua[0] * ub[0] + ua[1] * ub[1] + ua[2] * ub[2]));
135593
+ const angle = Math.acos(Math.abs(dot2)) * 180 / Math.PI;
135594
+ maxTangentAngleDeg = Math.max(maxTangentAngleDeg, angle);
135595
+ const la = len3(a2.cross) ** 2 || 1;
135596
+ const lb = len3(b.cross) ** 2 || 1;
135597
+ const ka = (a2.cross2[0] * ua[0] + a2.cross2[1] * ua[1] + a2.cross2[2] * ua[2]) / la;
135598
+ const kb = (b.cross2[0] * ub[0] + b.cross2[1] * ub[1] + b.cross2[2] * ub[2]) / lb;
135599
+ maxCurvatureRelErr = Math.max(maxCurvatureRelErr, Math.abs(ka - kb) / (Math.abs(ka) + Math.abs(kb) + 1e-9));
135600
+ }
135601
+ return { maxPositionGap, maxTangentAngleDeg, maxCurvatureRelErr };
135602
+ }
135603
+ function bridgeBetween(edgeA, edgeB) {
135604
+ return new BridgeBuilder(edgeA, edgeB);
135605
+ }
135606
+ class BridgeBuilder {
135607
+ constructor(edgeA, edgeB) {
135608
+ __publicField(this, "bulgeA", 0.5);
135609
+ __publicField(this, "bulgeB", 0.5);
135610
+ this.edgeA = edgeA;
135611
+ this.edgeB = edgeB;
135612
+ }
135613
+ /** Tune the influence of each side (Rhino-style bulge). */
135614
+ bulge(a2, b) {
135615
+ if (!Number.isFinite(a2) || !Number.isFinite(b)) throw new Error("Surfaces.bridge.bulge: both factors must be finite.");
135616
+ this.bulgeA = a2;
135617
+ this.bulgeB = b;
135618
+ return this;
135619
+ }
135620
+ g0() {
135621
+ return this.build(0);
135622
+ }
135623
+ g1() {
135624
+ return this.build(1);
135625
+ }
135626
+ g2() {
135627
+ return this.build(2);
135628
+ }
135629
+ build(order) {
135630
+ const SAMPLES = 21;
135631
+ const CROSS = 11;
135632
+ const cage = [];
135633
+ for (let i = 0; i < SAMPLES; i++) {
135634
+ const t = i / (SAMPLES - 1);
135635
+ const a2 = edgeJet(this.edgeA, t);
135636
+ const b = edgeJet(this.edgeB, t);
135637
+ const chord = len3(sub3(b.point, a2.point));
135638
+ const toward = unit3(sub3(b.point, a2.point));
135639
+ const tA = orientToward(unit3(a2.cross), toward);
135640
+ const tB = orientToward(unit3(b.cross), [-toward[0], -toward[1], -toward[2]]);
135641
+ const poles = bridgePoles(a2.point, b.point, tA, tB, chord, this.bulgeA, this.bulgeB, order);
135642
+ const row = [];
135643
+ for (let c2 = 0; c2 < CROSS; c2++) row.push(deCasteljau(poles, c2 / (CROSS - 1)));
135644
+ cage.push(row);
135645
+ }
135646
+ return new CurveNetBuilder().cage(cage).degree(Math.min(3, SAMPLES - 1), Math.min(2 * order + 1 || 1, CROSS - 1)).toSheet();
135647
+ }
135648
+ }
135649
+ function orientToward(v, toward) {
135650
+ const dot2 = v[0] * toward[0] + v[1] * toward[1] + v[2] * toward[2];
135651
+ return dot2 < 0 ? [-v[0], -v[1], -v[2]] : v;
135652
+ }
135653
+ function bridgePoles(a2, b, tA, tB, chord, bulgeA, bulgeB, order) {
135654
+ if (order === 0) return [a2, b];
135655
+ const dA = chord * bulgeA / (order === 1 ? 3 : 5);
135656
+ const dB = chord * bulgeB / (order === 1 ? 3 : 5);
135657
+ const a1 = [a2[0] + tA[0] * dA, a2[1] + tA[1] * dA, a2[2] + tA[2] * dA];
135658
+ const b1 = [b[0] + tB[0] * dB, b[1] + tB[1] * dB, b[2] + tB[2] * dB];
135659
+ if (order === 1) return [a2, a1, b1, b];
135660
+ const a22 = [a1[0] + tA[0] * dA, a1[1] + tA[1] * dA, a1[2] + tA[2] * dA];
135661
+ const b22 = [b1[0] + tB[0] * dB, b1[1] + tB[1] * dB, b1[2] + tB[2] * dB];
135662
+ return [a2, a1, a22, b22, b1, b];
135663
+ }
135664
+ function deCasteljau(poles, s) {
135665
+ let pts = poles.map((p2) => [p2[0], p2[1], p2[2]]);
135666
+ while (pts.length > 1) {
135667
+ const next = [];
135668
+ for (let i = 0; i < pts.length - 1; i++) {
135669
+ next.push([
135670
+ pts[i][0] + (pts[i + 1][0] - pts[i][0]) * s,
135671
+ pts[i][1] + (pts[i + 1][1] - pts[i][1]) * s,
135672
+ pts[i][2] + (pts[i + 1][2] - pts[i][2]) * s
135673
+ ]);
135674
+ }
135675
+ pts = next;
135676
+ }
135677
+ return pts[0];
135678
+ }
134993
135679
  const CORNER_Y_ALPHA_ISSUE_URL = "https://github.com/KoStard/forgecad-private/issues/162";
134994
135680
  function isVec3Array(value) {
134995
135681
  return Array.isArray(value) && (value.length === 0 || Array.isArray(value[0]));
@@ -136381,7 +137067,16 @@ const Surface = {
136381
137067
  "Surface.MatchEdge",
136382
137068
  "Surface.Match()",
136383
137069
  (shape, options) => Surface.Match(shape, options)
136384
- )
137070
+ ),
137071
+ /**
137072
+ * Begin a curve-network (Gordon) surface — the class-A keystone. Chain
137073
+ * `.lengthwise(...)/.crosswise(...)` (or `.alongRails(a,b).sections(...)`, or
137074
+ * `.cage(grid)`), then `.thicken(wall)` to get a solid Shape. Returns a fluent
137075
+ * `Sheet` builder with analytic point/normal/curvature queries and named edges.
137076
+ */
137077
+ Net() {
137078
+ return createCurveNet();
137079
+ }
136385
137080
  };
136386
137081
  const Blend = {
136387
137082
  Edge(options) {
@@ -136401,6 +137096,14 @@ const Blend = {
136401
137096
  Surface(options) {
136402
137097
  return Surface.Fill(options);
136403
137098
  },
137099
+ /**
137100
+ * Build a transition strip between two `Surface.Net` sheet edges. Chain
137101
+ * `.bulge(a, b)` then `.g0()/.g1()/.g2()` for the continuity order. Returns a
137102
+ * `Sheet`; verify the seam with `Analysis.EdgeMatch`.
137103
+ */
137104
+ Bridge(edgeA, edgeB) {
137105
+ return bridgeBetween(edgeA, edgeB);
137106
+ },
136404
137107
  /**
136405
137108
  * @alpha
136406
137109
  * Current implementation uses continuity-controlled edge fillets on solid edges.
@@ -136435,6 +137138,14 @@ const Analysis = {
136435
137138
  SurfaceContinuity(shape, options = {}) {
136436
137139
  return evaluateEdgeContinuityReport(shape, options, "Analysis.SurfaceContinuity()");
136437
137140
  },
137141
+ /**
137142
+ * Measure G0/G1/G2 agreement between two `Surface.Net` sheet edges: worst
137143
+ * position gap, cross-boundary tangent angle (0 = G1), and normal-curvature
137144
+ * mismatch (0 = G2). The reflection/fairness check for matched panel seams.
137145
+ */
137146
+ EdgeMatch(edgeA, edgeB, options = {}) {
137147
+ return edgeMatchReport(edgeA, edgeB, options.samples);
137148
+ },
136438
137149
  CurvatureComb(input, options = {}) {
136439
137150
  if (input instanceof NurbsCurve3D) {
136440
137151
  const count = Math.max(8, options.samples ?? 32);
@@ -140329,22 +141040,10 @@ function roundNum(n, digits = 4) {
140329
141040
  return Number.isFinite(n) ? n.toFixed(digits).replace(/\.?0+$/, "") : String(n);
140330
141041
  }
140331
141042
  const COLLISION_OVERLAP_VOLUME_TOLERANCE = 1e-6;
140332
- function meshDerivedManifoldBackend(shape) {
140333
- const mesh = getShapeRuntimeBackend(shape).getMesh();
140334
- return reconstructBackendFromMesh({
140335
- numProp: mesh.numProp,
140336
- triVerts: mesh.triVerts,
140337
- vertProperties: mesh.vertProperties,
140338
- mergeFromVert: mesh.mergeFromVert ?? new Uint32Array(),
140339
- mergeToVert: mesh.mergeToVert ?? new Uint32Array()
140340
- });
140341
- }
140342
141043
  function backendForMinGap(shape) {
140343
141044
  const backend = getShapeRuntimeBackend(shape);
140344
141045
  if (isManifoldCapableBackend(backend)) return { kind: "manifold", backend, method: "exact", dispose: false };
140345
- if (isSdfCapableBackend(backend) || getActiveBackend() === "sdf")
140346
- return { kind: "mesh", backend, method: "mesh-derived", dispose: false };
140347
- return { kind: "manifold", backend: meshDerivedManifoldBackend(shape), method: "mesh-derived", dispose: true };
141046
+ return { kind: "mesh", backend, method: "mesh-derived", dispose: false };
140348
141047
  }
140349
141048
  function meshHasPointInsideSdf(source, target) {
140350
141049
  const mesh = source.getMesh();
@@ -141600,7 +142299,6 @@ function resetExecutionSession(logs) {
141600
142299
  resetHighlights();
141601
142300
  resetBom();
141602
142301
  resetSheetStock();
141603
- resetRobotExport();
141604
142302
  resetCutPlanes();
141605
142303
  resetRenderLabels();
141606
142304
  resetCameraTrajectory();
@@ -141683,7 +142381,6 @@ function collectSuccessfulExecutionSnapshot(args) {
141683
142381
  jointsView: getCollectedJointsView(),
141684
142382
  viewConfig: getCollectedViewConfig(),
141685
142383
  sceneConfig: getCollectedScene(),
141686
- robotExport: getCollectedRobotExport(),
141687
142384
  quality: args.quality,
141688
142385
  logs: args.logs.slice(),
141689
142386
  verifications: getCollectedVerifications(),
@@ -141708,7 +142405,6 @@ function collectFailedExecutionSnapshot(args) {
141708
142405
  jointsView: getCollectedJointsView(),
141709
142406
  viewConfig: getCollectedViewConfig(),
141710
142407
  sceneConfig: getCollectedScene(),
141711
- robotExport: getCollectedRobotExport(),
141712
142408
  quality: args.quality,
141713
142409
  logs: args.logs.slice(),
141714
142410
  verifications: getCollectedVerifications(),
@@ -355208,7 +355904,6 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
355208
355904
  sketchToDxf,
355209
355905
  bom,
355210
355906
  sheetStock,
355211
- robotExport,
355212
355907
  Sim,
355213
355908
  group,
355214
355909
  ShapeGroup,
@@ -356075,6 +356770,1135 @@ function pointFromProjectedTriangle(u2, v, a2, normal2, uAxis, vAxis, dropAxis,
356075
356770
  const dropped = axisValue(a2, dropAxis) - (axisValue(normal2, uAxis) * (u2 - axisValue(a2, uAxis)) + axisValue(normal2, vAxis) * (v - axisValue(a2, vAxis))) / axisValue(normal2, dropAxis);
356076
356771
  setAxisValue(target, dropAxis, dropped);
356077
356772
  }
356773
+ const VIRIDIS_256 = [
356774
+ [68, 1, 84],
356775
+ [68, 2, 86],
356776
+ [69, 4, 87],
356777
+ [69, 5, 89],
356778
+ [70, 7, 90],
356779
+ [70, 8, 92],
356780
+ [70, 10, 93],
356781
+ [70, 11, 94],
356782
+ [71, 13, 96],
356783
+ [71, 14, 97],
356784
+ [71, 16, 99],
356785
+ [71, 17, 100],
356786
+ [71, 19, 101],
356787
+ [72, 20, 103],
356788
+ [72, 22, 104],
356789
+ [72, 23, 105],
356790
+ [72, 24, 106],
356791
+ [72, 26, 108],
356792
+ [72, 27, 109],
356793
+ [72, 28, 110],
356794
+ [72, 29, 111],
356795
+ [72, 31, 112],
356796
+ [72, 32, 113],
356797
+ [72, 33, 115],
356798
+ [72, 35, 116],
356799
+ [72, 36, 117],
356800
+ [72, 37, 118],
356801
+ [72, 38, 119],
356802
+ [72, 40, 120],
356803
+ [72, 41, 121],
356804
+ [71, 42, 122],
356805
+ [71, 44, 122],
356806
+ [71, 45, 123],
356807
+ [71, 46, 124],
356808
+ [71, 47, 125],
356809
+ [70, 48, 126],
356810
+ [70, 50, 126],
356811
+ [70, 51, 127],
356812
+ [70, 52, 128],
356813
+ [69, 53, 129],
356814
+ [69, 55, 129],
356815
+ [69, 56, 130],
356816
+ [68, 57, 131],
356817
+ [68, 58, 131],
356818
+ [68, 59, 132],
356819
+ [67, 61, 132],
356820
+ [67, 62, 133],
356821
+ [66, 63, 133],
356822
+ [66, 64, 134],
356823
+ [66, 65, 134],
356824
+ [65, 66, 135],
356825
+ [65, 68, 135],
356826
+ [64, 69, 136],
356827
+ [64, 70, 136],
356828
+ [63, 71, 136],
356829
+ [63, 72, 137],
356830
+ [62, 73, 137],
356831
+ [62, 74, 137],
356832
+ [62, 76, 138],
356833
+ [61, 77, 138],
356834
+ [61, 78, 138],
356835
+ [60, 79, 138],
356836
+ [60, 80, 139],
356837
+ [59, 81, 139],
356838
+ [59, 82, 139],
356839
+ [58, 83, 139],
356840
+ [58, 84, 140],
356841
+ [57, 85, 140],
356842
+ [57, 86, 140],
356843
+ [56, 88, 140],
356844
+ [56, 89, 140],
356845
+ [55, 90, 140],
356846
+ [55, 91, 141],
356847
+ [54, 92, 141],
356848
+ [54, 93, 141],
356849
+ [53, 94, 141],
356850
+ [53, 95, 141],
356851
+ [52, 96, 141],
356852
+ [52, 97, 141],
356853
+ [51, 98, 141],
356854
+ [51, 99, 141],
356855
+ [50, 100, 142],
356856
+ [50, 101, 142],
356857
+ [49, 102, 142],
356858
+ [49, 103, 142],
356859
+ [49, 104, 142],
356860
+ [48, 105, 142],
356861
+ [48, 106, 142],
356862
+ [47, 107, 142],
356863
+ [47, 108, 142],
356864
+ [46, 109, 142],
356865
+ [46, 110, 142],
356866
+ [46, 111, 142],
356867
+ [45, 112, 142],
356868
+ [45, 113, 142],
356869
+ [44, 113, 142],
356870
+ [44, 114, 142],
356871
+ [44, 115, 142],
356872
+ [43, 116, 142],
356873
+ [43, 117, 142],
356874
+ [42, 118, 142],
356875
+ [42, 119, 142],
356876
+ [42, 120, 142],
356877
+ [41, 121, 142],
356878
+ [41, 122, 142],
356879
+ [41, 123, 142],
356880
+ [40, 124, 142],
356881
+ [40, 125, 142],
356882
+ [39, 126, 142],
356883
+ [39, 127, 142],
356884
+ [39, 128, 142],
356885
+ [38, 129, 142],
356886
+ [38, 130, 142],
356887
+ [38, 130, 142],
356888
+ [37, 131, 142],
356889
+ [37, 132, 142],
356890
+ [37, 133, 142],
356891
+ [36, 134, 142],
356892
+ [36, 135, 142],
356893
+ [35, 136, 142],
356894
+ [35, 137, 142],
356895
+ [35, 138, 141],
356896
+ [34, 139, 141],
356897
+ [34, 140, 141],
356898
+ [34, 141, 141],
356899
+ [33, 142, 141],
356900
+ [33, 143, 141],
356901
+ [33, 144, 141],
356902
+ [33, 145, 140],
356903
+ [32, 146, 140],
356904
+ [32, 146, 140],
356905
+ [32, 147, 140],
356906
+ [31, 148, 140],
356907
+ [31, 149, 139],
356908
+ [31, 150, 139],
356909
+ [31, 151, 139],
356910
+ [31, 152, 139],
356911
+ [31, 153, 138],
356912
+ [31, 154, 138],
356913
+ [30, 155, 138],
356914
+ [30, 156, 137],
356915
+ [30, 157, 137],
356916
+ [31, 158, 137],
356917
+ [31, 159, 136],
356918
+ [31, 160, 136],
356919
+ [31, 161, 136],
356920
+ [31, 161, 135],
356921
+ [31, 162, 135],
356922
+ [32, 163, 134],
356923
+ [32, 164, 134],
356924
+ [33, 165, 133],
356925
+ [33, 166, 133],
356926
+ [34, 167, 133],
356927
+ [34, 168, 132],
356928
+ [35, 169, 131],
356929
+ [36, 170, 131],
356930
+ [37, 171, 130],
356931
+ [37, 172, 130],
356932
+ [38, 173, 129],
356933
+ [39, 173, 129],
356934
+ [40, 174, 128],
356935
+ [41, 175, 127],
356936
+ [42, 176, 127],
356937
+ [44, 177, 126],
356938
+ [45, 178, 125],
356939
+ [46, 179, 124],
356940
+ [47, 180, 124],
356941
+ [49, 181, 123],
356942
+ [50, 182, 122],
356943
+ [52, 182, 121],
356944
+ [53, 183, 121],
356945
+ [55, 184, 120],
356946
+ [56, 185, 119],
356947
+ [58, 186, 118],
356948
+ [59, 187, 117],
356949
+ [61, 188, 116],
356950
+ [63, 188, 115],
356951
+ [64, 189, 114],
356952
+ [66, 190, 113],
356953
+ [68, 191, 112],
356954
+ [70, 192, 111],
356955
+ [72, 193, 110],
356956
+ [74, 193, 109],
356957
+ [76, 194, 108],
356958
+ [78, 195, 107],
356959
+ [80, 196, 106],
356960
+ [82, 197, 105],
356961
+ [84, 197, 104],
356962
+ [86, 198, 103],
356963
+ [88, 199, 101],
356964
+ [90, 200, 100],
356965
+ [92, 200, 99],
356966
+ [94, 201, 98],
356967
+ [96, 202, 96],
356968
+ [99, 203, 95],
356969
+ [101, 203, 94],
356970
+ [103, 204, 92],
356971
+ [105, 205, 91],
356972
+ [108, 205, 90],
356973
+ [110, 206, 88],
356974
+ [112, 207, 87],
356975
+ [115, 208, 86],
356976
+ [117, 208, 84],
356977
+ [119, 209, 83],
356978
+ [122, 209, 81],
356979
+ [124, 210, 80],
356980
+ [127, 211, 78],
356981
+ [129, 211, 77],
356982
+ [132, 212, 75],
356983
+ [134, 213, 73],
356984
+ [137, 213, 72],
356985
+ [139, 214, 70],
356986
+ [142, 214, 69],
356987
+ [144, 215, 67],
356988
+ [147, 215, 65],
356989
+ [149, 216, 64],
356990
+ [152, 216, 62],
356991
+ [155, 217, 60],
356992
+ [157, 217, 59],
356993
+ [160, 218, 57],
356994
+ [162, 218, 55],
356995
+ [165, 219, 54],
356996
+ [168, 219, 52],
356997
+ [170, 220, 50],
356998
+ [173, 220, 48],
356999
+ [176, 221, 47],
357000
+ [178, 221, 45],
357001
+ [181, 222, 43],
357002
+ [184, 222, 41],
357003
+ [186, 222, 40],
357004
+ [189, 223, 38],
357005
+ [192, 223, 37],
357006
+ [194, 223, 35],
357007
+ [197, 224, 33],
357008
+ [200, 224, 32],
357009
+ [202, 225, 31],
357010
+ [205, 225, 29],
357011
+ [208, 225, 28],
357012
+ [210, 226, 27],
357013
+ [213, 226, 26],
357014
+ [216, 226, 25],
357015
+ [218, 227, 25],
357016
+ [221, 227, 24],
357017
+ [223, 227, 24],
357018
+ [226, 228, 24],
357019
+ [229, 228, 25],
357020
+ [231, 228, 25],
357021
+ [234, 229, 26],
357022
+ [236, 229, 27],
357023
+ [239, 229, 28],
357024
+ [241, 229, 29],
357025
+ [244, 230, 30],
357026
+ [246, 230, 32],
357027
+ [248, 230, 33],
357028
+ [251, 231, 35],
357029
+ [253, 231, 37]
357030
+ ];
357031
+ const INFERNO_256 = [
357032
+ [0, 0, 4],
357033
+ [1, 0, 5],
357034
+ [1, 1, 6],
357035
+ [1, 1, 8],
357036
+ [2, 1, 10],
357037
+ [2, 2, 12],
357038
+ [2, 2, 14],
357039
+ [3, 2, 16],
357040
+ [4, 3, 18],
357041
+ [4, 3, 20],
357042
+ [5, 4, 23],
357043
+ [6, 4, 25],
357044
+ [7, 5, 27],
357045
+ [8, 5, 29],
357046
+ [9, 6, 31],
357047
+ [10, 7, 34],
357048
+ [11, 7, 36],
357049
+ [12, 8, 38],
357050
+ [13, 8, 41],
357051
+ [14, 9, 43],
357052
+ [16, 9, 45],
357053
+ [17, 10, 48],
357054
+ [18, 10, 50],
357055
+ [20, 11, 52],
357056
+ [21, 11, 55],
357057
+ [22, 11, 57],
357058
+ [24, 12, 60],
357059
+ [25, 12, 62],
357060
+ [27, 12, 65],
357061
+ [28, 12, 67],
357062
+ [30, 12, 69],
357063
+ [31, 12, 72],
357064
+ [33, 12, 74],
357065
+ [35, 12, 76],
357066
+ [36, 12, 79],
357067
+ [38, 12, 81],
357068
+ [40, 11, 83],
357069
+ [41, 11, 85],
357070
+ [43, 11, 87],
357071
+ [45, 11, 89],
357072
+ [47, 10, 91],
357073
+ [49, 10, 92],
357074
+ [50, 10, 94],
357075
+ [52, 10, 95],
357076
+ [54, 9, 97],
357077
+ [56, 9, 98],
357078
+ [57, 9, 99],
357079
+ [59, 9, 100],
357080
+ [61, 9, 101],
357081
+ [62, 9, 102],
357082
+ [64, 10, 103],
357083
+ [66, 10, 104],
357084
+ [68, 10, 104],
357085
+ [69, 10, 105],
357086
+ [71, 11, 106],
357087
+ [73, 11, 106],
357088
+ [74, 12, 107],
357089
+ [76, 12, 107],
357090
+ [77, 13, 108],
357091
+ [79, 13, 108],
357092
+ [81, 14, 108],
357093
+ [82, 14, 109],
357094
+ [84, 15, 109],
357095
+ [85, 15, 109],
357096
+ [87, 16, 110],
357097
+ [89, 16, 110],
357098
+ [90, 17, 110],
357099
+ [92, 18, 110],
357100
+ [93, 18, 110],
357101
+ [95, 19, 110],
357102
+ [97, 19, 110],
357103
+ [98, 20, 110],
357104
+ [100, 21, 110],
357105
+ [101, 21, 110],
357106
+ [103, 22, 110],
357107
+ [105, 22, 110],
357108
+ [106, 23, 110],
357109
+ [108, 24, 110],
357110
+ [109, 24, 110],
357111
+ [111, 25, 110],
357112
+ [113, 25, 110],
357113
+ [114, 26, 110],
357114
+ [116, 26, 110],
357115
+ [117, 27, 110],
357116
+ [119, 28, 109],
357117
+ [120, 28, 109],
357118
+ [122, 29, 109],
357119
+ [124, 29, 109],
357120
+ [125, 30, 109],
357121
+ [127, 30, 108],
357122
+ [128, 31, 108],
357123
+ [130, 32, 108],
357124
+ [132, 32, 107],
357125
+ [133, 33, 107],
357126
+ [135, 33, 107],
357127
+ [136, 34, 106],
357128
+ [138, 34, 106],
357129
+ [140, 35, 105],
357130
+ [141, 35, 105],
357131
+ [143, 36, 105],
357132
+ [144, 37, 104],
357133
+ [146, 37, 104],
357134
+ [147, 38, 103],
357135
+ [149, 38, 103],
357136
+ [151, 39, 102],
357137
+ [152, 39, 102],
357138
+ [154, 40, 101],
357139
+ [155, 41, 100],
357140
+ [157, 41, 100],
357141
+ [159, 42, 99],
357142
+ [160, 42, 99],
357143
+ [162, 43, 98],
357144
+ [163, 44, 97],
357145
+ [165, 44, 96],
357146
+ [166, 45, 96],
357147
+ [168, 46, 95],
357148
+ [169, 46, 94],
357149
+ [171, 47, 94],
357150
+ [173, 48, 93],
357151
+ [174, 48, 92],
357152
+ [176, 49, 91],
357153
+ [177, 50, 90],
357154
+ [179, 50, 90],
357155
+ [180, 51, 89],
357156
+ [182, 52, 88],
357157
+ [183, 53, 87],
357158
+ [185, 53, 86],
357159
+ [186, 54, 85],
357160
+ [188, 55, 84],
357161
+ [189, 56, 83],
357162
+ [191, 57, 82],
357163
+ [192, 58, 81],
357164
+ [193, 58, 80],
357165
+ [195, 59, 79],
357166
+ [196, 60, 78],
357167
+ [198, 61, 77],
357168
+ [199, 62, 76],
357169
+ [200, 63, 75],
357170
+ [202, 64, 74],
357171
+ [203, 65, 73],
357172
+ [204, 66, 72],
357173
+ [206, 67, 71],
357174
+ [207, 68, 70],
357175
+ [208, 69, 69],
357176
+ [210, 70, 68],
357177
+ [211, 71, 67],
357178
+ [212, 72, 66],
357179
+ [213, 74, 65],
357180
+ [215, 75, 63],
357181
+ [216, 76, 62],
357182
+ [217, 77, 61],
357183
+ [218, 78, 60],
357184
+ [219, 80, 59],
357185
+ [221, 81, 58],
357186
+ [222, 82, 56],
357187
+ [223, 83, 55],
357188
+ [224, 85, 54],
357189
+ [225, 86, 53],
357190
+ [226, 87, 52],
357191
+ [227, 89, 51],
357192
+ [228, 90, 49],
357193
+ [229, 92, 48],
357194
+ [230, 93, 47],
357195
+ [231, 94, 46],
357196
+ [232, 96, 45],
357197
+ [233, 97, 43],
357198
+ [234, 99, 42],
357199
+ [235, 100, 41],
357200
+ [235, 102, 40],
357201
+ [236, 103, 38],
357202
+ [237, 105, 37],
357203
+ [238, 106, 36],
357204
+ [239, 108, 35],
357205
+ [239, 110, 33],
357206
+ [240, 111, 32],
357207
+ [241, 113, 31],
357208
+ [241, 115, 29],
357209
+ [242, 116, 28],
357210
+ [243, 118, 27],
357211
+ [243, 120, 25],
357212
+ [244, 121, 24],
357213
+ [245, 123, 23],
357214
+ [245, 125, 21],
357215
+ [246, 126, 20],
357216
+ [246, 128, 19],
357217
+ [247, 130, 18],
357218
+ [247, 132, 16],
357219
+ [248, 133, 15],
357220
+ [248, 135, 14],
357221
+ [248, 137, 12],
357222
+ [249, 139, 11],
357223
+ [249, 140, 10],
357224
+ [249, 142, 9],
357225
+ [250, 144, 8],
357226
+ [250, 146, 7],
357227
+ [250, 148, 7],
357228
+ [251, 150, 6],
357229
+ [251, 151, 6],
357230
+ [251, 153, 6],
357231
+ [251, 155, 6],
357232
+ [251, 157, 7],
357233
+ [252, 159, 7],
357234
+ [252, 161, 8],
357235
+ [252, 163, 9],
357236
+ [252, 165, 10],
357237
+ [252, 166, 12],
357238
+ [252, 168, 13],
357239
+ [252, 170, 15],
357240
+ [252, 172, 17],
357241
+ [252, 174, 18],
357242
+ [252, 176, 20],
357243
+ [252, 178, 22],
357244
+ [252, 180, 24],
357245
+ [251, 182, 26],
357246
+ [251, 184, 29],
357247
+ [251, 186, 31],
357248
+ [251, 188, 33],
357249
+ [251, 190, 35],
357250
+ [250, 192, 38],
357251
+ [250, 194, 40],
357252
+ [250, 196, 42],
357253
+ [250, 198, 45],
357254
+ [249, 199, 47],
357255
+ [249, 201, 50],
357256
+ [249, 203, 53],
357257
+ [248, 205, 55],
357258
+ [248, 207, 58],
357259
+ [247, 209, 61],
357260
+ [247, 211, 64],
357261
+ [246, 213, 67],
357262
+ [246, 215, 70],
357263
+ [245, 217, 73],
357264
+ [245, 219, 76],
357265
+ [244, 221, 79],
357266
+ [244, 223, 83],
357267
+ [244, 225, 86],
357268
+ [243, 227, 90],
357269
+ [243, 229, 93],
357270
+ [242, 230, 97],
357271
+ [242, 232, 101],
357272
+ [242, 234, 105],
357273
+ [241, 236, 109],
357274
+ [241, 237, 113],
357275
+ [241, 239, 117],
357276
+ [241, 241, 121],
357277
+ [242, 242, 125],
357278
+ [242, 244, 130],
357279
+ [243, 245, 134],
357280
+ [243, 246, 138],
357281
+ [244, 248, 142],
357282
+ [245, 249, 146],
357283
+ [246, 250, 150],
357284
+ [248, 251, 154],
357285
+ [249, 252, 157],
357286
+ [250, 253, 161],
357287
+ [252, 255, 164]
357288
+ ];
357289
+ const TURBO_256 = [
357290
+ [48, 18, 59],
357291
+ [50, 21, 67],
357292
+ [51, 24, 74],
357293
+ [52, 27, 81],
357294
+ [53, 30, 88],
357295
+ [54, 33, 95],
357296
+ [55, 36, 102],
357297
+ [56, 39, 109],
357298
+ [57, 42, 115],
357299
+ [58, 45, 121],
357300
+ [59, 47, 128],
357301
+ [60, 50, 134],
357302
+ [61, 53, 139],
357303
+ [62, 56, 145],
357304
+ [63, 59, 151],
357305
+ [63, 62, 156],
357306
+ [64, 64, 162],
357307
+ [65, 67, 167],
357308
+ [65, 70, 172],
357309
+ [66, 73, 177],
357310
+ [66, 75, 181],
357311
+ [67, 78, 186],
357312
+ [68, 81, 191],
357313
+ [68, 84, 195],
357314
+ [68, 86, 199],
357315
+ [69, 89, 203],
357316
+ [69, 92, 207],
357317
+ [69, 94, 211],
357318
+ [70, 97, 214],
357319
+ [70, 100, 218],
357320
+ [70, 102, 221],
357321
+ [70, 105, 224],
357322
+ [70, 107, 227],
357323
+ [71, 110, 230],
357324
+ [71, 113, 233],
357325
+ [71, 115, 235],
357326
+ [71, 118, 238],
357327
+ [71, 120, 240],
357328
+ [71, 123, 242],
357329
+ [70, 125, 244],
357330
+ [70, 128, 246],
357331
+ [70, 130, 248],
357332
+ [70, 133, 250],
357333
+ [70, 135, 251],
357334
+ [69, 138, 252],
357335
+ [69, 140, 253],
357336
+ [68, 143, 254],
357337
+ [67, 145, 254],
357338
+ [66, 148, 255],
357339
+ [65, 150, 255],
357340
+ [64, 153, 255],
357341
+ [62, 155, 254],
357342
+ [61, 158, 254],
357343
+ [59, 160, 253],
357344
+ [58, 163, 252],
357345
+ [56, 165, 251],
357346
+ [55, 168, 250],
357347
+ [53, 171, 248],
357348
+ [51, 173, 247],
357349
+ [49, 175, 245],
357350
+ [47, 178, 244],
357351
+ [46, 180, 242],
357352
+ [44, 183, 240],
357353
+ [42, 185, 238],
357354
+ [40, 188, 235],
357355
+ [39, 190, 233],
357356
+ [37, 192, 231],
357357
+ [35, 195, 228],
357358
+ [34, 197, 226],
357359
+ [32, 199, 223],
357360
+ [31, 201, 221],
357361
+ [30, 203, 218],
357362
+ [28, 205, 216],
357363
+ [27, 208, 213],
357364
+ [26, 210, 210],
357365
+ [26, 212, 208],
357366
+ [25, 213, 205],
357367
+ [24, 215, 202],
357368
+ [24, 217, 200],
357369
+ [24, 219, 197],
357370
+ [24, 221, 194],
357371
+ [24, 222, 192],
357372
+ [24, 224, 189],
357373
+ [25, 226, 187],
357374
+ [25, 227, 185],
357375
+ [26, 228, 182],
357376
+ [28, 230, 180],
357377
+ [29, 231, 178],
357378
+ [31, 233, 175],
357379
+ [32, 234, 172],
357380
+ [34, 235, 170],
357381
+ [37, 236, 167],
357382
+ [39, 238, 164],
357383
+ [42, 239, 161],
357384
+ [44, 240, 158],
357385
+ [47, 241, 155],
357386
+ [50, 242, 152],
357387
+ [53, 243, 148],
357388
+ [56, 244, 145],
357389
+ [60, 245, 142],
357390
+ [63, 246, 138],
357391
+ [67, 247, 135],
357392
+ [70, 248, 132],
357393
+ [74, 248, 128],
357394
+ [78, 249, 125],
357395
+ [82, 250, 122],
357396
+ [85, 250, 118],
357397
+ [89, 251, 115],
357398
+ [93, 252, 111],
357399
+ [97, 252, 108],
357400
+ [101, 253, 105],
357401
+ [105, 253, 102],
357402
+ [109, 254, 98],
357403
+ [113, 254, 95],
357404
+ [117, 254, 92],
357405
+ [121, 254, 89],
357406
+ [125, 255, 86],
357407
+ [128, 255, 83],
357408
+ [132, 255, 81],
357409
+ [136, 255, 78],
357410
+ [139, 255, 75],
357411
+ [143, 255, 73],
357412
+ [146, 255, 71],
357413
+ [150, 254, 68],
357414
+ [153, 254, 66],
357415
+ [156, 254, 64],
357416
+ [159, 253, 63],
357417
+ [161, 253, 61],
357418
+ [164, 252, 60],
357419
+ [167, 252, 58],
357420
+ [169, 251, 57],
357421
+ [172, 251, 56],
357422
+ [175, 250, 55],
357423
+ [177, 249, 54],
357424
+ [180, 248, 54],
357425
+ [183, 247, 53],
357426
+ [185, 246, 53],
357427
+ [188, 245, 52],
357428
+ [190, 244, 52],
357429
+ [193, 243, 52],
357430
+ [195, 241, 52],
357431
+ [198, 240, 52],
357432
+ [200, 239, 52],
357433
+ [203, 237, 52],
357434
+ [205, 236, 52],
357435
+ [208, 234, 52],
357436
+ [210, 233, 53],
357437
+ [212, 231, 53],
357438
+ [215, 229, 53],
357439
+ [217, 228, 54],
357440
+ [219, 226, 54],
357441
+ [221, 224, 55],
357442
+ [223, 223, 55],
357443
+ [225, 221, 55],
357444
+ [227, 219, 56],
357445
+ [229, 217, 56],
357446
+ [231, 215, 57],
357447
+ [233, 213, 57],
357448
+ [235, 211, 57],
357449
+ [236, 209, 58],
357450
+ [238, 207, 58],
357451
+ [239, 205, 58],
357452
+ [241, 203, 58],
357453
+ [242, 201, 58],
357454
+ [244, 199, 58],
357455
+ [245, 197, 58],
357456
+ [246, 195, 58],
357457
+ [247, 193, 58],
357458
+ [248, 190, 57],
357459
+ [249, 188, 57],
357460
+ [250, 186, 57],
357461
+ [251, 184, 56],
357462
+ [251, 182, 55],
357463
+ [252, 179, 54],
357464
+ [252, 177, 54],
357465
+ [253, 174, 53],
357466
+ [253, 172, 52],
357467
+ [254, 169, 51],
357468
+ [254, 167, 50],
357469
+ [254, 164, 49],
357470
+ [254, 161, 48],
357471
+ [254, 158, 47],
357472
+ [254, 155, 45],
357473
+ [254, 153, 44],
357474
+ [254, 150, 43],
357475
+ [254, 147, 42],
357476
+ [254, 144, 41],
357477
+ [253, 141, 39],
357478
+ [253, 138, 38],
357479
+ [252, 135, 37],
357480
+ [252, 132, 35],
357481
+ [251, 129, 34],
357482
+ [251, 126, 33],
357483
+ [250, 123, 31],
357484
+ [249, 120, 30],
357485
+ [249, 117, 29],
357486
+ [248, 114, 28],
357487
+ [247, 111, 26],
357488
+ [246, 108, 25],
357489
+ [245, 105, 24],
357490
+ [244, 102, 23],
357491
+ [243, 99, 21],
357492
+ [242, 96, 20],
357493
+ [241, 93, 19],
357494
+ [240, 91, 18],
357495
+ [239, 88, 17],
357496
+ [237, 85, 16],
357497
+ [236, 83, 15],
357498
+ [235, 80, 14],
357499
+ [234, 78, 13],
357500
+ [232, 75, 12],
357501
+ [231, 73, 12],
357502
+ [229, 71, 11],
357503
+ [228, 69, 10],
357504
+ [226, 67, 10],
357505
+ [225, 65, 9],
357506
+ [223, 63, 8],
357507
+ [221, 61, 8],
357508
+ [220, 59, 7],
357509
+ [218, 57, 7],
357510
+ [216, 55, 6],
357511
+ [214, 53, 6],
357512
+ [212, 51, 5],
357513
+ [210, 49, 5],
357514
+ [208, 47, 5],
357515
+ [206, 45, 4],
357516
+ [204, 43, 4],
357517
+ [202, 42, 4],
357518
+ [200, 40, 3],
357519
+ [197, 38, 3],
357520
+ [195, 37, 3],
357521
+ [193, 35, 2],
357522
+ [190, 33, 2],
357523
+ [188, 32, 2],
357524
+ [185, 30, 2],
357525
+ [183, 29, 2],
357526
+ [180, 27, 1],
357527
+ [178, 26, 1],
357528
+ [175, 24, 1],
357529
+ [172, 23, 1],
357530
+ [169, 22, 1],
357531
+ [167, 20, 1],
357532
+ [164, 19, 1],
357533
+ [161, 18, 1],
357534
+ [158, 16, 1],
357535
+ [155, 15, 1],
357536
+ [152, 14, 1],
357537
+ [149, 13, 1],
357538
+ [146, 11, 1],
357539
+ [142, 10, 1],
357540
+ [139, 9, 2],
357541
+ [136, 8, 2],
357542
+ [133, 7, 2],
357543
+ [129, 6, 2],
357544
+ [126, 5, 2],
357545
+ [122, 4, 3]
357546
+ ];
357547
+ const COOLWARM_256 = [
357548
+ [59, 76, 192],
357549
+ [60, 78, 194],
357550
+ [61, 80, 195],
357551
+ [62, 81, 197],
357552
+ [63, 83, 198],
357553
+ [64, 85, 200],
357554
+ [66, 87, 201],
357555
+ [67, 88, 203],
357556
+ [68, 90, 204],
357557
+ [69, 92, 206],
357558
+ [70, 94, 207],
357559
+ [72, 95, 209],
357560
+ [73, 97, 210],
357561
+ [74, 99, 211],
357562
+ [75, 100, 213],
357563
+ [76, 102, 214],
357564
+ [78, 104, 216],
357565
+ [79, 105, 217],
357566
+ [80, 107, 218],
357567
+ [81, 109, 219],
357568
+ [83, 110, 221],
357569
+ [84, 112, 222],
357570
+ [85, 114, 223],
357571
+ [86, 115, 224],
357572
+ [88, 117, 225],
357573
+ [89, 119, 227],
357574
+ [90, 120, 228],
357575
+ [91, 122, 229],
357576
+ [93, 124, 230],
357577
+ [94, 125, 231],
357578
+ [95, 127, 232],
357579
+ [97, 128, 233],
357580
+ [98, 130, 234],
357581
+ [99, 132, 235],
357582
+ [100, 133, 236],
357583
+ [102, 135, 237],
357584
+ [103, 136, 238],
357585
+ [104, 138, 239],
357586
+ [106, 139, 239],
357587
+ [107, 141, 240],
357588
+ [108, 143, 241],
357589
+ [110, 144, 242],
357590
+ [111, 146, 243],
357591
+ [112, 147, 243],
357592
+ [114, 149, 244],
357593
+ [115, 150, 245],
357594
+ [117, 151, 246],
357595
+ [118, 153, 246],
357596
+ [119, 154, 247],
357597
+ [121, 156, 248],
357598
+ [122, 157, 248],
357599
+ [123, 159, 249],
357600
+ [125, 160, 249],
357601
+ [126, 161, 250],
357602
+ [128, 163, 250],
357603
+ [129, 164, 251],
357604
+ [130, 166, 251],
357605
+ [132, 167, 252],
357606
+ [133, 168, 252],
357607
+ [134, 169, 252],
357608
+ [136, 171, 253],
357609
+ [137, 172, 253],
357610
+ [139, 173, 253],
357611
+ [140, 175, 254],
357612
+ [141, 176, 254],
357613
+ [143, 177, 254],
357614
+ [144, 178, 254],
357615
+ [146, 180, 254],
357616
+ [147, 181, 254],
357617
+ [148, 182, 255],
357618
+ [150, 183, 255],
357619
+ [151, 184, 255],
357620
+ [152, 185, 255],
357621
+ [154, 187, 255],
357622
+ [155, 188, 255],
357623
+ [157, 189, 255],
357624
+ [158, 190, 255],
357625
+ [159, 191, 255],
357626
+ [161, 192, 255],
357627
+ [162, 193, 255],
357628
+ [163, 194, 254],
357629
+ [165, 195, 254],
357630
+ [166, 196, 254],
357631
+ [167, 197, 254],
357632
+ [169, 198, 253],
357633
+ [170, 199, 253],
357634
+ [171, 200, 253],
357635
+ [173, 201, 253],
357636
+ [174, 201, 252],
357637
+ [175, 202, 252],
357638
+ [177, 203, 252],
357639
+ [178, 204, 251],
357640
+ [179, 205, 251],
357641
+ [181, 205, 250],
357642
+ [182, 206, 250],
357643
+ [183, 207, 249],
357644
+ [185, 208, 249],
357645
+ [186, 208, 248],
357646
+ [187, 209, 248],
357647
+ [188, 210, 247],
357648
+ [190, 210, 246],
357649
+ [191, 211, 246],
357650
+ [192, 212, 245],
357651
+ [193, 212, 244],
357652
+ [195, 213, 244],
357653
+ [196, 213, 243],
357654
+ [197, 214, 242],
357655
+ [198, 214, 241],
357656
+ [199, 215, 240],
357657
+ [201, 215, 240],
357658
+ [202, 216, 239],
357659
+ [203, 216, 238],
357660
+ [204, 217, 237],
357661
+ [205, 217, 236],
357662
+ [206, 218, 235],
357663
+ [207, 218, 234],
357664
+ [209, 218, 233],
357665
+ [210, 219, 232],
357666
+ [211, 219, 231],
357667
+ [212, 219, 230],
357668
+ [213, 219, 229],
357669
+ [214, 220, 228],
357670
+ [215, 220, 227],
357671
+ [216, 220, 226],
357672
+ [217, 220, 225],
357673
+ [218, 220, 224],
357674
+ [219, 220, 222],
357675
+ [220, 221, 221],
357676
+ [221, 220, 220],
357677
+ [222, 220, 219],
357678
+ [223, 219, 217],
357679
+ [224, 219, 216],
357680
+ [225, 218, 214],
357681
+ [226, 218, 213],
357682
+ [227, 217, 211],
357683
+ [228, 217, 210],
357684
+ [229, 216, 209],
357685
+ [230, 215, 207],
357686
+ [231, 215, 206],
357687
+ [232, 214, 204],
357688
+ [233, 213, 203],
357689
+ [234, 213, 201],
357690
+ [234, 212, 200],
357691
+ [235, 211, 198],
357692
+ [236, 211, 197],
357693
+ [237, 210, 195],
357694
+ [237, 209, 194],
357695
+ [238, 208, 192],
357696
+ [239, 207, 191],
357697
+ [239, 206, 189],
357698
+ [240, 205, 187],
357699
+ [241, 205, 186],
357700
+ [241, 204, 184],
357701
+ [242, 203, 183],
357702
+ [242, 202, 181],
357703
+ [242, 201, 180],
357704
+ [243, 200, 178],
357705
+ [243, 199, 177],
357706
+ [244, 198, 175],
357707
+ [244, 197, 173],
357708
+ [245, 196, 172],
357709
+ [245, 194, 170],
357710
+ [245, 193, 169],
357711
+ [245, 192, 167],
357712
+ [246, 191, 166],
357713
+ [246, 190, 164],
357714
+ [246, 189, 162],
357715
+ [247, 188, 161],
357716
+ [247, 186, 159],
357717
+ [247, 185, 158],
357718
+ [247, 184, 156],
357719
+ [247, 183, 155],
357720
+ [247, 181, 153],
357721
+ [247, 180, 151],
357722
+ [247, 179, 150],
357723
+ [247, 177, 148],
357724
+ [247, 176, 147],
357725
+ [247, 175, 145],
357726
+ [247, 173, 144],
357727
+ [247, 172, 142],
357728
+ [247, 170, 140],
357729
+ [247, 169, 139],
357730
+ [247, 168, 137],
357731
+ [247, 166, 136],
357732
+ [246, 165, 134],
357733
+ [246, 163, 133],
357734
+ [246, 162, 131],
357735
+ [245, 160, 129],
357736
+ [245, 159, 128],
357737
+ [245, 157, 126],
357738
+ [245, 156, 125],
357739
+ [244, 154, 123],
357740
+ [244, 152, 122],
357741
+ [243, 151, 120],
357742
+ [243, 149, 119],
357743
+ [243, 148, 117],
357744
+ [242, 146, 116],
357745
+ [242, 144, 114],
357746
+ [241, 143, 113],
357747
+ [241, 141, 111],
357748
+ [240, 139, 110],
357749
+ [240, 138, 108],
357750
+ [239, 136, 107],
357751
+ [238, 134, 105],
357752
+ [238, 132, 104],
357753
+ [237, 131, 102],
357754
+ [236, 129, 101],
357755
+ [236, 127, 99],
357756
+ [235, 125, 98],
357757
+ [234, 123, 96],
357758
+ [233, 122, 95],
357759
+ [233, 120, 93],
357760
+ [232, 118, 92],
357761
+ [231, 116, 91],
357762
+ [230, 114, 89],
357763
+ [229, 112, 88],
357764
+ [228, 110, 86],
357765
+ [227, 108, 85],
357766
+ [227, 107, 84],
357767
+ [226, 105, 82],
357768
+ [225, 103, 81],
357769
+ [224, 101, 79],
357770
+ [223, 99, 78],
357771
+ [222, 97, 77],
357772
+ [221, 95, 75],
357773
+ [220, 93, 74],
357774
+ [218, 90, 73],
357775
+ [217, 88, 71],
357776
+ [216, 86, 70],
357777
+ [215, 84, 69],
357778
+ [214, 82, 68],
357779
+ [213, 80, 66],
357780
+ [212, 78, 65],
357781
+ [210, 75, 64],
357782
+ [209, 73, 63],
357783
+ [208, 71, 61],
357784
+ [207, 69, 60],
357785
+ [205, 66, 59],
357786
+ [204, 64, 58],
357787
+ [203, 62, 56],
357788
+ [202, 59, 55],
357789
+ [200, 56, 54],
357790
+ [199, 54, 53],
357791
+ [197, 51, 52],
357792
+ [196, 48, 50],
357793
+ [195, 46, 49],
357794
+ [193, 43, 48],
357795
+ [192, 40, 47],
357796
+ [190, 36, 46],
357797
+ [189, 31, 45],
357798
+ [187, 27, 44],
357799
+ [186, 22, 43],
357800
+ [184, 18, 42],
357801
+ [183, 13, 40],
357802
+ [181, 9, 39],
357803
+ [180, 4, 38]
357804
+ ];
357805
+ const COLOR_SCALE_LUT_SIZE = 256;
357806
+ const THICKNESS_CLASSIC_STOPS = [
357807
+ [255, 28, 28],
357808
+ [255, 222, 0],
357809
+ [60, 220, 90],
357810
+ [70, 145, 255]
357811
+ ];
357812
+ const COLORMAP_STOPS = {
357813
+ viridis: VIRIDIS_256,
357814
+ inferno: INFERNO_256,
357815
+ turbo: TURBO_256,
357816
+ coolwarm: COOLWARM_256,
357817
+ grayscale: [[0, 0, 0], [255, 255, 255]],
357818
+ "thickness-classic": THICKNESS_CLASSIC_STOPS
357819
+ };
357820
+ const COLORMAP_LABELS = {
357821
+ viridis: "Viridis",
357822
+ inferno: "Inferno",
357823
+ turbo: "Turbo",
357824
+ coolwarm: "Cool–Warm",
357825
+ grayscale: "Grayscale",
357826
+ "thickness-classic": "Thickness (classic)"
357827
+ };
357828
+ const COLORMAP_OPTIONS = [
357829
+ { name: "viridis", label: COLORMAP_LABELS.viridis },
357830
+ { name: "inferno", label: COLORMAP_LABELS.inferno },
357831
+ { name: "turbo", label: COLORMAP_LABELS.turbo },
357832
+ { name: "coolwarm", label: COLORMAP_LABELS.coolwarm },
357833
+ { name: "grayscale", label: COLORMAP_LABELS.grayscale },
357834
+ { name: "thickness-classic", label: COLORMAP_LABELS["thickness-classic"] }
357835
+ ];
357836
+ function clamp01$1(t) {
357837
+ if (t <= 0) return 0;
357838
+ if (t >= 1) return 1;
357839
+ return t;
357840
+ }
357841
+ function sampleStops(stops, t) {
357842
+ const clamped = clamp01$1(t);
357843
+ const scaled = clamped * (stops.length - 1);
357844
+ const leftIndex = Math.min(stops.length - 2, Math.floor(scaled));
357845
+ const frac = scaled - leftIndex;
357846
+ const left = stops[leftIndex];
357847
+ const right = stops[leftIndex + 1];
357848
+ return [
357849
+ Math.round(left[0] + (right[0] - left[0]) * frac),
357850
+ Math.round(left[1] + (right[1] - left[1]) * frac),
357851
+ Math.round(left[2] + (right[2] - left[2]) * frac)
357852
+ ];
357853
+ }
357854
+ function resolveStops(name) {
357855
+ const stops = COLORMAP_STOPS[name];
357856
+ if (!stops) {
357857
+ throw new Error(`Unknown colormap '${String(name)}'. Valid names: ${COLORMAP_OPTIONS.map((o) => o.name).join(", ")}.`);
357858
+ }
357859
+ return stops;
357860
+ }
357861
+ const DEFAULT_COLORMAP = "viridis";
357862
+ function resolveColormapName(value) {
357863
+ return typeof value === "string" && value in COLORMAP_STOPS ? value : DEFAULT_COLORMAP;
357864
+ }
357865
+ function colorScaleHexStops(name, n = 8) {
357866
+ const count = Math.max(2, Math.floor(n));
357867
+ const stops = resolveStops(name);
357868
+ const out = [];
357869
+ for (let i = 0; i < count; i++) {
357870
+ const [r, g2, b] = sampleStops(stops, i / (count - 1));
357871
+ out.push(`#${(1 << 24 | r << 16 | g2 << 8 | b).toString(16).slice(1)}`);
357872
+ }
357873
+ return out;
357874
+ }
357875
+ function colorScaleLUT(name) {
357876
+ const stops = resolveStops(name);
357877
+ const lut = new Uint8Array(COLOR_SCALE_LUT_SIZE * 4);
357878
+ for (let i = 0; i < COLOR_SCALE_LUT_SIZE; i++) {
357879
+ const [r, g2, b] = sampleStops(stops, i / (COLOR_SCALE_LUT_SIZE - 1));
357880
+ const o = i * 4;
357881
+ lut[o] = r;
357882
+ lut[o + 1] = g2;
357883
+ lut[o + 2] = b;
357884
+ lut[o + 3] = 255;
357885
+ }
357886
+ return lut;
357887
+ }
357888
+ function sampleColorScale(scale2, value) {
357889
+ const stops = resolveStops(scale2.colormap);
357890
+ const span = scale2.domainMax - scale2.domainMin;
357891
+ const t = span > 0 ? (value - scale2.domainMin) / span : 0;
357892
+ return sampleStops(stops, Number.isFinite(t) ? t : 0);
357893
+ }
357894
+ const DEFAULT_THICKNESS_COLOR_SCALE = {
357895
+ colormap: "viridis"
357896
+ };
357897
+ const DEFAULT_ROUGHNESS_COLOR_SCALE = {
357898
+ colormap: "viridis",
357899
+ domainMin: 0,
357900
+ domainMax: 1
357901
+ };
356078
357902
  const MIN_PATCH_TRIANGLES = 1;
356079
357903
  function finitePoint(point2) {
356080
357904
  return point2.point.every((value) => Number.isFinite(value));
@@ -356198,6 +358022,121 @@ const formatPerformanceCount = (value) => INTEGER_FORMATTER.format(Math.max(0, M
356198
358022
  const waitForAnimationFrame = () => new Promise((resolve) => {
356199
358023
  requestAnimationFrame(() => resolve());
356200
358024
  });
358025
+ const VECTOR_KEYS = /* @__PURE__ */ new Set(["pos", "position", "target", "lookat", "aim", "up"]);
358026
+ const roundNumber = (value, digits) => {
358027
+ const scale2 = 10 ** digits;
358028
+ return Math.round(value * scale2) / scale2;
358029
+ };
358030
+ const isFiniteTuple3 = (value) => Array.isArray(value) && value.length === 3 && value.every((entry) => typeof entry === "number" && Number.isFinite(entry));
358031
+ function parseViewportCameraState(value) {
358032
+ if (!value || typeof value !== "object") return null;
358033
+ const candidate = value;
358034
+ if (candidate.projectionMode !== "perspective" && candidate.projectionMode !== "orthographic") return null;
358035
+ if (!isFiniteTuple3(candidate.position)) return null;
358036
+ if (!isFiniteTuple3(candidate.target)) return null;
358037
+ if (!isFiniteTuple3(candidate.up)) return null;
358038
+ if (candidate.fov !== void 0 && (!Number.isFinite(candidate.fov) || candidate.fov <= 0 || candidate.fov >= 180)) {
358039
+ return null;
358040
+ }
358041
+ if (candidate.orthoZoom !== void 0 && (!Number.isFinite(candidate.orthoZoom) || candidate.orthoZoom <= 0)) {
358042
+ return null;
358043
+ }
358044
+ const parsed = {
358045
+ projectionMode: candidate.projectionMode,
358046
+ position: candidate.position,
358047
+ target: candidate.target,
358048
+ up: candidate.up
358049
+ };
358050
+ if (candidate.fov !== void 0) parsed.fov = candidate.fov;
358051
+ if (candidate.orthoZoom !== void 0) parsed.orthoZoom = candidate.orthoZoom;
358052
+ return parsed;
358053
+ }
358054
+ function parseVector(name, raw) {
358055
+ const parts = raw.split(",").map((entry) => Number.parseFloat(entry.trim()));
358056
+ if (parts.length !== 3 || parts.some((entry) => !Number.isFinite(entry))) {
358057
+ throw new Error(`Camera ${name} must be three comma-separated numbers.`);
358058
+ }
358059
+ return [parts[0], parts[1], parts[2]];
358060
+ }
358061
+ function parseCameraCliSpec(input) {
358062
+ const trimmed = input.trim();
358063
+ if (!trimmed) {
358064
+ throw new Error("Camera spec cannot be empty.");
358065
+ }
358066
+ if (trimmed.startsWith("{")) {
358067
+ const parsed2 = parseViewportCameraState(JSON.parse(trimmed));
358068
+ if (!parsed2) {
358069
+ throw new Error("Camera JSON does not match the expected shape.");
358070
+ }
358071
+ return parsed2;
358072
+ }
358073
+ const parsed = {};
358074
+ const segments = trimmed.split(";").map((segment) => segment.trim()).filter(Boolean);
358075
+ if (segments.length === 0) {
358076
+ throw new Error("Camera spec cannot be empty.");
358077
+ }
358078
+ for (const segment of segments) {
358079
+ const eqIndex = segment.indexOf("=");
358080
+ if (eqIndex === -1) {
358081
+ throw new Error(`Invalid camera segment "${segment}". Expected key=value.`);
358082
+ }
358083
+ const rawKey = segment.slice(0, eqIndex).trim().toLowerCase();
358084
+ const rawValue = segment.slice(eqIndex + 1).trim();
358085
+ if (!rawValue) {
358086
+ throw new Error(`Camera segment "${segment}" is missing a value.`);
358087
+ }
358088
+ if (rawKey === "proj" || rawKey === "projection" || rawKey === "projectionmode") {
358089
+ if (rawValue !== "perspective" && rawValue !== "orthographic") {
358090
+ throw new Error(`Camera projection must be "perspective" or "orthographic" (got "${rawValue}").`);
358091
+ }
358092
+ parsed.projectionMode = rawValue;
358093
+ continue;
358094
+ }
358095
+ if (rawKey === "zoom" || rawKey === "orthozoom") {
358096
+ const zoom = Number.parseFloat(rawValue);
358097
+ if (!Number.isFinite(zoom) || zoom <= 0) {
358098
+ throw new Error(`Camera zoom must be a positive number (got "${rawValue}").`);
358099
+ }
358100
+ parsed.orthoZoom = zoom;
358101
+ continue;
358102
+ }
358103
+ if (rawKey === "fov") {
358104
+ const fov2 = Number.parseFloat(rawValue);
358105
+ if (!Number.isFinite(fov2) || fov2 <= 0 || fov2 >= 180) {
358106
+ throw new Error(`Camera fov must be between 0 and 180 degrees (got "${rawValue}").`);
358107
+ }
358108
+ parsed.fov = fov2;
358109
+ continue;
358110
+ }
358111
+ if (VECTOR_KEYS.has(rawKey)) {
358112
+ const vector = parseVector(rawKey, rawValue);
358113
+ if (rawKey === "pos" || rawKey === "position") parsed.position = vector;
358114
+ if (rawKey === "target" || rawKey === "lookat" || rawKey === "aim") parsed.target = vector;
358115
+ if (rawKey === "up") parsed.up = vector;
358116
+ continue;
358117
+ }
358118
+ throw new Error(`Unknown camera key "${rawKey}".`);
358119
+ }
358120
+ const finalized = parseViewportCameraState({
358121
+ projectionMode: parsed.projectionMode ?? "perspective",
358122
+ position: parsed.position,
358123
+ target: parsed.target,
358124
+ up: parsed.up ?? [0, 0, 1],
358125
+ fov: parsed.fov,
358126
+ orthoZoom: parsed.orthoZoom
358127
+ });
358128
+ if (!finalized) {
358129
+ throw new Error("Camera spec must include position, target, and up vectors.");
358130
+ }
358131
+ return finalized;
358132
+ }
358133
+ function getCameraForwardVector(state, digits = 3) {
358134
+ const dx = state.target[0] - state.position[0];
358135
+ const dy = state.target[1] - state.position[1];
358136
+ const dz = state.target[2] - state.position[2];
358137
+ const length4 = Math.hypot(dx, dy, dz) || 1;
358138
+ return [roundNumber(dx / length4, digits), roundNumber(dy / length4, digits), roundNumber(dz / length4, digits)];
358139
+ }
356201
358140
  const SURFACE_FIELD_VERTEX_SHADER = `
356202
358141
  #include <common>
356203
358142
  #include <logdepthbuf_pars_vertex>
@@ -356506,79 +358445,6 @@ function localAabbPlaneRelation(min2, max2, plane, eps = 1e-7) {
356506
358445
  if (minDistance > -eps) return "positive";
356507
358446
  return "crossing";
356508
358447
  }
356509
- const DEFAULT_ROUGHNESS_INSPECTION_OPTIONS = {
356510
- smoothAngleDeg: 5,
356511
- sharpAngleDeg: 30,
356512
- harshAngleDeg: 90,
356513
- maxSamplesPerObject: 5e3
356514
- };
356515
- const ROUGHNESS_COLORS = {
356516
- smooth: [62, 72, 84],
356517
- moderate: [255, 214, 0],
356518
- sharp: [255, 124, 34],
356519
- harsh: [255, 42, 96]
356520
- };
356521
- function resolveRoughnessInspectionOptions(raw = {}) {
356522
- const options = {
356523
- smoothAngleDeg: raw.smoothAngleDeg ?? DEFAULT_ROUGHNESS_INSPECTION_OPTIONS.smoothAngleDeg,
356524
- sharpAngleDeg: raw.sharpAngleDeg ?? DEFAULT_ROUGHNESS_INSPECTION_OPTIONS.sharpAngleDeg,
356525
- harshAngleDeg: raw.harshAngleDeg ?? DEFAULT_ROUGHNESS_INSPECTION_OPTIONS.harshAngleDeg,
356526
- maxSamplesPerObject: raw.maxSamplesPerObject ?? DEFAULT_ROUGHNESS_INSPECTION_OPTIONS.maxSamplesPerObject
356527
- };
356528
- if (!Number.isFinite(options.smoothAngleDeg) || options.smoothAngleDeg < 0) {
356529
- throw new Error(`smoothAngleDeg must be a finite non-negative angle (got ${options.smoothAngleDeg}).`);
356530
- }
356531
- if (!Number.isFinite(options.sharpAngleDeg) || options.sharpAngleDeg <= options.smoothAngleDeg) {
356532
- throw new Error(`sharpAngleDeg must be greater than smoothAngleDeg (got ${options.sharpAngleDeg}).`);
356533
- }
356534
- if (!Number.isFinite(options.harshAngleDeg) || options.harshAngleDeg <= options.sharpAngleDeg || options.harshAngleDeg > 180) {
356535
- throw new Error(`harshAngleDeg must be greater than sharpAngleDeg and <= 180 (got ${options.harshAngleDeg}).`);
356536
- }
356537
- if (!Number.isFinite(options.maxSamplesPerObject) || options.maxSamplesPerObject <= 0) {
356538
- throw new Error(`maxSamplesPerObject must be a positive finite number (got ${options.maxSamplesPerObject}).`);
356539
- }
356540
- return {
356541
- ...options,
356542
- maxSamplesPerObject: Math.max(1, Math.floor(options.maxSamplesPerObject))
356543
- };
356544
- }
356545
- function roughnessClassForAngle(angleDeg, options) {
356546
- if (angleDeg >= options.harshAngleDeg) return "harsh";
356547
- if (angleDeg >= options.sharpAngleDeg) return "sharp";
356548
- if (angleDeg >= options.smoothAngleDeg) return "moderate";
356549
- return "smooth";
356550
- }
356551
- function roughnessScoreForAngle(angleDeg, options) {
356552
- if (angleDeg < options.sharpAngleDeg) return 0;
356553
- if (angleDeg < options.harshAngleDeg) {
356554
- return MathUtils.lerp(0.48, 0.82, (angleDeg - options.sharpAngleDeg) / (options.harshAngleDeg - options.sharpAngleDeg));
356555
- }
356556
- return 1;
356557
- }
356558
- function roughnessColorForAngle(angleDeg, options) {
356559
- const cls = roughnessClassForAngle(angleDeg, options);
356560
- if (cls === "smooth" || cls === "harsh") return ROUGHNESS_COLORS[cls];
356561
- if (cls === "moderate") {
356562
- return lerpRgb(
356563
- ROUGHNESS_COLORS.moderate,
356564
- ROUGHNESS_COLORS.sharp,
356565
- (angleDeg - options.smoothAngleDeg) / (options.sharpAngleDeg - options.smoothAngleDeg)
356566
- );
356567
- }
356568
- return lerpRgb(
356569
- ROUGHNESS_COLORS.sharp,
356570
- ROUGHNESS_COLORS.harsh,
356571
- (angleDeg - options.sharpAngleDeg) / (options.harshAngleDeg - options.sharpAngleDeg)
356572
- );
356573
- }
356574
- function lerpRgb(a2, b, t) {
356575
- const clamped = MathUtils.clamp(t, 0, 1);
356576
- return [
356577
- Math.round(MathUtils.lerp(a2[0], b[0], clamped)),
356578
- Math.round(MathUtils.lerp(a2[1], b[1], clamped)),
356579
- Math.round(MathUtils.lerp(a2[2], b[2], clamped))
356580
- ];
356581
- }
356582
358448
  const DEFAULT_LEAF_SIZE = 8;
356583
358449
  function cloneVec3$2(value) {
356584
358450
  return [value[0], value[1], value[2]];
@@ -357368,121 +359234,6 @@ function summarizeThicknessSamples(samples, options) {
357368
359234
  unresolvedAreaPercent: percent(unresolvedArea, totalArea)
357369
359235
  };
357370
359236
  }
357371
- const VECTOR_KEYS = /* @__PURE__ */ new Set(["pos", "position", "target", "lookat", "aim", "up"]);
357372
- const roundNumber = (value, digits) => {
357373
- const scale2 = 10 ** digits;
357374
- return Math.round(value * scale2) / scale2;
357375
- };
357376
- const isFiniteTuple3 = (value) => Array.isArray(value) && value.length === 3 && value.every((entry) => typeof entry === "number" && Number.isFinite(entry));
357377
- function parseViewportCameraState(value) {
357378
- if (!value || typeof value !== "object") return null;
357379
- const candidate = value;
357380
- if (candidate.projectionMode !== "perspective" && candidate.projectionMode !== "orthographic") return null;
357381
- if (!isFiniteTuple3(candidate.position)) return null;
357382
- if (!isFiniteTuple3(candidate.target)) return null;
357383
- if (!isFiniteTuple3(candidate.up)) return null;
357384
- if (candidate.fov !== void 0 && (!Number.isFinite(candidate.fov) || candidate.fov <= 0 || candidate.fov >= 180)) {
357385
- return null;
357386
- }
357387
- if (candidate.orthoZoom !== void 0 && (!Number.isFinite(candidate.orthoZoom) || candidate.orthoZoom <= 0)) {
357388
- return null;
357389
- }
357390
- const parsed = {
357391
- projectionMode: candidate.projectionMode,
357392
- position: candidate.position,
357393
- target: candidate.target,
357394
- up: candidate.up
357395
- };
357396
- if (candidate.fov !== void 0) parsed.fov = candidate.fov;
357397
- if (candidate.orthoZoom !== void 0) parsed.orthoZoom = candidate.orthoZoom;
357398
- return parsed;
357399
- }
357400
- function parseVector(name, raw) {
357401
- const parts = raw.split(",").map((entry) => Number.parseFloat(entry.trim()));
357402
- if (parts.length !== 3 || parts.some((entry) => !Number.isFinite(entry))) {
357403
- throw new Error(`Camera ${name} must be three comma-separated numbers.`);
357404
- }
357405
- return [parts[0], parts[1], parts[2]];
357406
- }
357407
- function parseCameraCliSpec(input) {
357408
- const trimmed = input.trim();
357409
- if (!trimmed) {
357410
- throw new Error("Camera spec cannot be empty.");
357411
- }
357412
- if (trimmed.startsWith("{")) {
357413
- const parsed2 = parseViewportCameraState(JSON.parse(trimmed));
357414
- if (!parsed2) {
357415
- throw new Error("Camera JSON does not match the expected shape.");
357416
- }
357417
- return parsed2;
357418
- }
357419
- const parsed = {};
357420
- const segments = trimmed.split(";").map((segment) => segment.trim()).filter(Boolean);
357421
- if (segments.length === 0) {
357422
- throw new Error("Camera spec cannot be empty.");
357423
- }
357424
- for (const segment of segments) {
357425
- const eqIndex = segment.indexOf("=");
357426
- if (eqIndex === -1) {
357427
- throw new Error(`Invalid camera segment "${segment}". Expected key=value.`);
357428
- }
357429
- const rawKey = segment.slice(0, eqIndex).trim().toLowerCase();
357430
- const rawValue = segment.slice(eqIndex + 1).trim();
357431
- if (!rawValue) {
357432
- throw new Error(`Camera segment "${segment}" is missing a value.`);
357433
- }
357434
- if (rawKey === "proj" || rawKey === "projection" || rawKey === "projectionmode") {
357435
- if (rawValue !== "perspective" && rawValue !== "orthographic") {
357436
- throw new Error(`Camera projection must be "perspective" or "orthographic" (got "${rawValue}").`);
357437
- }
357438
- parsed.projectionMode = rawValue;
357439
- continue;
357440
- }
357441
- if (rawKey === "zoom" || rawKey === "orthozoom") {
357442
- const zoom = Number.parseFloat(rawValue);
357443
- if (!Number.isFinite(zoom) || zoom <= 0) {
357444
- throw new Error(`Camera zoom must be a positive number (got "${rawValue}").`);
357445
- }
357446
- parsed.orthoZoom = zoom;
357447
- continue;
357448
- }
357449
- if (rawKey === "fov") {
357450
- const fov2 = Number.parseFloat(rawValue);
357451
- if (!Number.isFinite(fov2) || fov2 <= 0 || fov2 >= 180) {
357452
- throw new Error(`Camera fov must be between 0 and 180 degrees (got "${rawValue}").`);
357453
- }
357454
- parsed.fov = fov2;
357455
- continue;
357456
- }
357457
- if (VECTOR_KEYS.has(rawKey)) {
357458
- const vector = parseVector(rawKey, rawValue);
357459
- if (rawKey === "pos" || rawKey === "position") parsed.position = vector;
357460
- if (rawKey === "target" || rawKey === "lookat" || rawKey === "aim") parsed.target = vector;
357461
- if (rawKey === "up") parsed.up = vector;
357462
- continue;
357463
- }
357464
- throw new Error(`Unknown camera key "${rawKey}".`);
357465
- }
357466
- const finalized = parseViewportCameraState({
357467
- projectionMode: parsed.projectionMode ?? "perspective",
357468
- position: parsed.position,
357469
- target: parsed.target,
357470
- up: parsed.up ?? [0, 0, 1],
357471
- fov: parsed.fov,
357472
- orthoZoom: parsed.orthoZoom
357473
- });
357474
- if (!finalized) {
357475
- throw new Error("Camera spec must include position, target, and up vectors.");
357476
- }
357477
- return finalized;
357478
- }
357479
- function getCameraForwardVector(state, digits = 3) {
357480
- const dx = state.target[0] - state.position[0];
357481
- const dy = state.target[1] - state.position[1];
357482
- const dz = state.target[2] - state.position[2];
357483
- const length4 = Math.hypot(dx, dy, dz) || 1;
357484
- return [roundNumber(dx / length4, digits), roundNumber(dy / length4, digits), roundNumber(dz / length4, digits)];
357485
- }
357486
359237
  const SECTION_EXPLORER_PLANE_NAME = "__section_explorer__";
357487
359238
  function resolveSectionHatchMetrics(geometry) {
357488
359239
  const planeUv = geometry.getAttribute("planeUv");
@@ -359219,88 +360970,88 @@ export {
359219
360970
  Vector4 as Z,
359220
360971
  __vitePreload as _,
359221
360972
  Scene as a,
359222
- AlwaysDepth as a$,
360973
+ AmbientLight as a$,
359223
360974
  Matrix4 as a0,
359224
360975
  MathUtils as a1,
359225
360976
  Uniform as a2,
359226
360977
  WebGLRenderTarget as a3,
359227
360978
  DepthTexture as a4,
359228
360979
  BackSide as a5,
359229
- ClampToEdgeWrapping as a6,
359230
- PlaneGeometry as a7,
359231
- UVMapping as a8,
359232
- DataTexture as a9,
359233
- DEFAULT_ACTIVE_BACKEND as aA,
359234
- isConstraintSketch as aB,
359235
- updateConstraintValue as aC,
359236
- linearizeMultiObjectSteps as aD,
359237
- getShapeCompilePlan as aE,
359238
- SCAN_PROXY_GRANULARITY_DEFAULT as aF,
359239
- publishSolverWasmRunDebug as aG,
359240
- resolveForgeQualityPreset as aH,
359241
- SCAN_PROXY_MATRIX_GRANULARITY_MAX as aI,
359242
- findJointAnimationClip as aJ,
359243
- resolveJointAnimation as aK,
359244
- resolveJointViewValues as aL,
359245
- resolveImportPath as aM,
359246
- resolveScanProxyGranularity as aN,
359247
- BufferGeometry as aO,
359248
- LineBasicMaterial as aP,
359249
- Line as aQ,
359250
- LineDashedMaterial as aR,
359251
- DepthStencilFormat as aS,
359252
- UnsignedInt248Type as aT,
359253
- MeshNormalMaterial as aU,
359254
- NearestFilter as aV,
359255
- BasicDepthPacking as aW,
359256
- EventDispatcher as aX,
359257
- NoColorSpace as aY,
359258
- FrontSide as aZ,
359259
- Material as a_,
359260
- Texture as aa,
359261
- MeshBasicMaterial as ab,
359262
- IntType as ac,
359263
- ShortType as ad,
359264
- ByteType as ae,
359265
- UnsignedIntType as af,
359266
- Loader as ag,
359267
- LoadingManager as ah,
359268
- LinearMipMapLinearFilter as ai,
359269
- FileLoader as aj,
359270
- NoBlending as ak,
359271
- CubeReflectionMapping as al,
359272
- EquirectangularReflectionMapping as am,
359273
- CubeTextureLoader as an,
359274
- WebGLCubeRenderTarget as ao,
359275
- ConstraintSketch as ap,
359276
- setSketchPlacement3D as aq,
359277
- Sketch as ar,
359278
- PROFILE_BACKEND_MARKER as as,
359279
- FrozenShape as at,
359280
- setShapeCompilePlan as au,
359281
- hasAnyPorts as av,
359282
- setShapePortsInternal as aw,
359283
- markShapePortsUsed as ax,
359284
- setParamOverrides as ay,
359285
- resolveForgeRenderStyle as az,
360980
+ BufferAttribute as a6,
360981
+ Triangle as a7,
360982
+ FrontSide as a8,
360983
+ BatchedMesh as a9,
360984
+ setShapePortsInternal as aA,
360985
+ markShapePortsUsed as aB,
360986
+ resolveColormapName as aC,
360987
+ setParamOverrides as aD,
360988
+ resolveForgeRenderStyle as aE,
360989
+ DEFAULT_ACTIVE_BACKEND as aF,
360990
+ isConstraintSketch as aG,
360991
+ updateConstraintValue as aH,
360992
+ linearizeMultiObjectSteps as aI,
360993
+ getShapeCompilePlan as aJ,
360994
+ SCAN_PROXY_GRANULARITY_DEFAULT as aK,
360995
+ publishSolverWasmRunDebug as aL,
360996
+ resolveForgeQualityPreset as aM,
360997
+ SCAN_PROXY_MATRIX_GRANULARITY_MAX as aN,
360998
+ findJointAnimationClip as aO,
360999
+ resolveJointAnimation as aP,
361000
+ resolveJointViewValues as aQ,
361001
+ resolveImportPath as aR,
361002
+ resolveScanProxyGranularity as aS,
361003
+ BufferGeometry as aT,
361004
+ LineBasicMaterial as aU,
361005
+ Line as aV,
361006
+ LineDashedMaterial as aW,
361007
+ CanvasTexture as aX,
361008
+ Object3D as aY,
361009
+ FogExp2 as aZ,
361010
+ Fog as a_,
361011
+ ClampToEdgeWrapping as aa,
361012
+ PlaneGeometry as ab,
361013
+ UVMapping as ac,
361014
+ DataTexture as ad,
361015
+ Texture as ae,
361016
+ MeshBasicMaterial as af,
361017
+ IntType as ag,
361018
+ ShortType as ah,
361019
+ ByteType as ai,
361020
+ UnsignedIntType as aj,
361021
+ Loader as ak,
361022
+ LoadingManager as al,
361023
+ LinearMipMapLinearFilter as am,
361024
+ FileLoader as an,
361025
+ NoBlending as ao,
361026
+ CubeReflectionMapping as ap,
361027
+ EquirectangularReflectionMapping as aq,
361028
+ CubeTextureLoader as ar,
361029
+ WebGLCubeRenderTarget as as,
361030
+ ConstraintSketch as at,
361031
+ setSketchPlacement3D as au,
361032
+ Sketch as av,
361033
+ PROFILE_BACKEND_MARKER as aw,
361034
+ FrozenShape as ax,
361035
+ setShapeCompilePlan as ay,
361036
+ hasAnyPorts as az,
359286
361037
  PCFSoftShadowMap as b,
359287
- PERFORMANCE_SAMPLE_INTERVAL_SEC as b$,
359288
- BufferAttribute as b0,
359289
- CanvasTexture as b1,
359290
- Object3D as b2,
359291
- FogExp2 as b3,
359292
- Fog as b4,
359293
- AmbientLight as b5,
359294
- HemisphereLight as b6,
359295
- SpotLight as b7,
359296
- PointLight as b8,
359297
- DirectionalLight as b9,
359298
- SDF_RAYMARCH_PROXY_VERTEX_SHADER as bA,
359299
- scanProxySourceBytes as bB,
359300
- disposeScanProxyGeometry as bC,
359301
- scanProxyGeometryFromPayload as bD,
359302
- AdditiveBlending as bE,
359303
- geometryWithVisibleVertexColors as bF,
361038
+ formatPerformanceCount as b$,
361039
+ HemisphereLight as b0,
361040
+ SpotLight as b1,
361041
+ PointLight as b2,
361042
+ DirectionalLight as b3,
361043
+ heatPointsForSide as b4,
361044
+ COMPARISON_COLORS as b5,
361045
+ comparisonHeatDepthTest as b6,
361046
+ comparisonHeatEdgeOpacity as b7,
361047
+ comparisonHeatPatchOpacity as b8,
361048
+ shapeToGeometry as b9,
361049
+ scanProxySourceBytes as bA,
361050
+ disposeScanProxyGeometry as bB,
361051
+ scanProxyGeometryFromPayload as bC,
361052
+ AdditiveBlending as bD,
361053
+ geometryWithVisibleVertexColors as bE,
361054
+ colorScaleLUT as bF,
359304
361055
  getRenderStylePreset as bG,
359305
361056
  SCAN_RENDER_COLORS as bH,
359306
361057
  NormalBlending as bI,
@@ -359319,121 +361070,118 @@ export {
359319
361070
  WORLD_UP as bV,
359320
361071
  CatmullRomCurve3 as bW,
359321
361072
  TubeGeometry as bX,
359322
- THICKNESS_GRADIENT_COLORS as bY,
359323
- ROUGHNESS_COLORS as bZ,
359324
- DEFAULT_ROUGHNESS_INSPECTION_OPTIONS as b_,
359325
- heatPointsForSide as ba,
359326
- COMPARISON_COLORS as bb,
359327
- comparisonHeatDepthTest as bc,
359328
- comparisonHeatEdgeOpacity as bd,
359329
- comparisonHeatPatchOpacity as be,
359330
- shapeToGeometry as bf,
359331
- buildComparisonHeatPatchGeometry as bg,
359332
- EdgesGeometry as bh,
359333
- buildShapeFromCompilePlan as bi,
359334
- buildVisibleHistoryStacks as bj,
359335
- sketchToSvg as bk,
359336
- sketchToDxf as bl,
359337
- runScript as bm,
359338
- MeshPhysicalMaterial as bn,
359339
- LineSegments as bo,
359340
- findDesignTraceNodeForConstructionStep as bp,
359341
- formatDesignTraceAnchor as bq,
359342
- waitForAnimationFrame as br,
359343
- selectBuildLedgerNodes as bs,
359344
- worldAuthorPlaneToLocal as bt,
359345
- compileSdfProgramEvaluator3 as bu,
359346
- SDF_LINEAR_OUTPUT_COLOR_GLSL as bv,
359347
- GLSL3 as bw,
359348
- BoxGeometry as bx,
359349
- Data3DTexture as by,
359350
- buildSdfRaymarchFragmentShader as bz,
361073
+ DEFAULT_COLORMAP as bY,
361074
+ colorScaleHexStops as bZ,
361075
+ PERFORMANCE_SAMPLE_INTERVAL_SEC as b_,
361076
+ buildComparisonHeatPatchGeometry as ba,
361077
+ EdgesGeometry as bb,
361078
+ buildShapeFromCompilePlan as bc,
361079
+ VIEWPORT_CAMERA_STORAGE_KEY as bd,
361080
+ parseViewportCameraState as be,
361081
+ getKernelFaceNameForTriangle as bf,
361082
+ OBJECT_CONTEXT_MENU_MARGIN as bg,
361083
+ buildVisibleHistoryStacks as bh,
361084
+ sketchToSvg as bi,
361085
+ sketchToDxf as bj,
361086
+ runScript as bk,
361087
+ MeshPhysicalMaterial as bl,
361088
+ LineSegments as bm,
361089
+ findDesignTraceNodeForConstructionStep as bn,
361090
+ formatDesignTraceAnchor as bo,
361091
+ waitForAnimationFrame as bp,
361092
+ selectBuildLedgerNodes as bq,
361093
+ NoColorSpace as br,
361094
+ worldAuthorPlaneToLocal as bs,
361095
+ compileSdfProgramEvaluator3 as bt,
361096
+ SDF_LINEAR_OUTPUT_COLOR_GLSL as bu,
361097
+ GLSL3 as bv,
361098
+ BoxGeometry as bw,
361099
+ Data3DTexture as bx,
361100
+ buildSdfRaymarchFragmentShader as by,
361101
+ SDF_RAYMARCH_PROXY_VERTEX_SHADER as bz,
359351
361102
  PCFShadowMap as c,
359352
- setActiveBackend as c$,
359353
- formatPerformanceCount as c0,
359354
- NON_TEXT_INPUT_TYPES as c1,
359355
- MeshStandardMaterial as c2,
359356
- Shape$1 as c3,
359357
- ShapeGeometry as c4,
359358
- ShaderLib as c5,
359359
- CylinderGeometry as c6,
359360
- VIEWPORT_CAMERA_STORAGE_KEY as c7,
359361
- parseViewportCameraState as c8,
359362
- createResolvedExplodeConfig as c9,
359363
- buildGeometryComparisonPointCloud as cA,
359364
- aabbOverlaps as cB,
359365
- aabbOverlapVolume as cC,
359366
- DEFAULT_COLLISION_INSPECTION_OPTIONS as cD,
359367
- resolveScalarSceneSampleBudget as cE,
359368
- FOCUS_MODE_DIM_OPACITY as cF,
359369
- comparisonCandidateContextOpacity as cG,
359370
- Matrix3 as cH,
359371
- initBackendForEvaluation as cI,
359372
- localAabbPlaneRelation as cJ,
359373
- ShapeUtils as cK,
359374
- analyzePhysicalConnectivity as cL,
359375
- Frustum as cM,
359376
- meshContactDataFor as cN,
359377
- AabbSpatialIndex as cO,
359378
- detectPhysicalContact as cP,
359379
- resolveThicknessInspectionOptions as cQ,
359380
- thicknessColor as cR,
359381
- thicknessClass as cS,
359382
- roughnessClassForAngle as cT,
359383
- resolveRoughnessInspectionOptions as cU,
359384
- roughnessColorForAngle as cV,
359385
- roughnessScoreForAngle as cW,
359386
- Group as cX,
359387
- createScanProxyGeometry as cY,
359388
- resolveSectionHatchMetrics as cZ,
359389
- intersectWithPlane as c_,
359390
- explodeBoundsCenter as ca,
359391
- explodeMergeBounds as cb,
359392
- resolveExplodeDirective as cc,
359393
- computeExplodeMotion as cd,
359394
- getSketchWorldMatrix as ce,
359395
- explodeAdd as cf,
359396
- hasExplodeOverride as cg,
359397
- resolveExplodeLocalFanDirection as ch,
359398
- explodeMul as ci,
359399
- explodeLeafFanStage as cj,
359400
- normalizeCutPlane as ck,
359401
- toClippingPlane as cl,
359402
- isObjectExcludedFromCutPlane as cm,
359403
- getShapePorts as cn,
359404
- getShapeUsedPorts as co,
359405
- DEFAULT_VIEW_CONFIG as cp,
359406
- SECTION_EXPLORER_PLANE_NAME as cq,
359407
- ZERO_OFFSET as cr,
359408
- scanProxyGridForBounds as cs,
359409
- IDENTITY_MATRIX as ct,
359410
- OBJECT_CONTEXT_MENU_MARGIN as cu,
359411
- OBJECT_CONTEXT_MENU_WIDTH as cv,
359412
- OBJECT_CONTEXT_MENU_HEIGHT as cw,
359413
- getKernelFaceNameForTriangle as cx,
359414
- triangleSoupFromMeshes as cy,
359415
- compareTriangleSoups as cz,
361103
+ summarizeThicknessSamples as c$,
361104
+ NON_TEXT_INPUT_TYPES as c0,
361105
+ MeshStandardMaterial as c1,
361106
+ Shape$1 as c2,
361107
+ ShapeGeometry as c3,
361108
+ ShaderLib as c4,
361109
+ CylinderGeometry as c5,
361110
+ createResolvedExplodeConfig as c6,
361111
+ explodeBoundsCenter as c7,
361112
+ explodeMergeBounds as c8,
361113
+ resolveExplodeDirective as c9,
361114
+ FOCUS_MODE_DIM_OPACITY as cA,
361115
+ DEFAULT_THICKNESS_INSPECTION_OPTIONS as cB,
361116
+ comparisonCandidateContextOpacity as cC,
361117
+ Matrix3 as cD,
361118
+ initBackendForEvaluation as cE,
361119
+ localAabbPlaneRelation as cF,
361120
+ ShapeUtils as cG,
361121
+ analyzePhysicalConnectivity as cH,
361122
+ Frustum as cI,
361123
+ meshContactDataFor as cJ,
361124
+ AabbSpatialIndex as cK,
361125
+ detectPhysicalContact as cL,
361126
+ resolveThicknessInspectionOptions as cM,
361127
+ thicknessColor as cN,
361128
+ thicknessClass as cO,
361129
+ Group as cP,
361130
+ createScanProxyGeometry as cQ,
361131
+ resolveSectionHatchMetrics as cR,
361132
+ intersectWithPlane as cS,
361133
+ setActiveBackend as cT,
361134
+ parseCameraCliSpec as cU,
361135
+ PMREMGenerator as cV,
361136
+ DEFAULT_ROUGHNESS_COLOR_SCALE as cW,
361137
+ PointsMaterial as cX,
361138
+ Points$1 as cY,
361139
+ analyzeCollisionIntersections as cZ,
361140
+ serializeCollisionFinding as c_,
361141
+ computeExplodeMotion as ca,
361142
+ getSketchWorldMatrix as cb,
361143
+ explodeAdd as cc,
361144
+ hasExplodeOverride as cd,
361145
+ resolveExplodeLocalFanDirection as ce,
361146
+ explodeMul as cf,
361147
+ explodeLeafFanStage as cg,
361148
+ normalizeCutPlane as ch,
361149
+ toClippingPlane as ci,
361150
+ isObjectExcludedFromCutPlane as cj,
361151
+ getShapePorts as ck,
361152
+ getShapeUsedPorts as cl,
361153
+ DEFAULT_VIEW_CONFIG as cm,
361154
+ SECTION_EXPLORER_PLANE_NAME as cn,
361155
+ ZERO_OFFSET as co,
361156
+ scanProxyGridForBounds as cp,
361157
+ IDENTITY_MATRIX as cq,
361158
+ OBJECT_CONTEXT_MENU_WIDTH as cr,
361159
+ OBJECT_CONTEXT_MENU_HEIGHT as cs,
361160
+ triangleSoupFromMeshes as ct,
361161
+ compareTriangleSoups as cu,
361162
+ buildGeometryComparisonPointCloud as cv,
361163
+ aabbOverlaps as cw,
361164
+ aabbOverlapVolume as cx,
361165
+ DEFAULT_COLLISION_INSPECTION_OPTIONS as cy,
361166
+ resolveScalarSceneSampleBudget as cz,
359416
361167
  SRGBColorSpace as d,
359417
- parseCameraCliSpec as d0,
359418
- PMREMGenerator as d1,
359419
- PointsMaterial as d2,
359420
- Points$1 as d3,
359421
- analyzeCollisionIntersections as d4,
359422
- serializeCollisionFinding as d5,
359423
- summarizeThicknessSamples as d6,
359424
- THICKNESS_COLORS as d7,
359425
- TorusGeometry as d8,
359426
- SphereGeometry as d9,
359427
- ConeGeometry as da,
359428
- DEFAULT_COMPARISON_CANDIDATE_OPACITY as db,
359429
- computeCuttingLayout as dc,
359430
- generateCuttingLayoutPdf as dd,
359431
- buildDesignTraceNeighborhood as de,
359432
- getCameraForwardVector as df,
359433
- RENDER_STYLE_OPTIONS as dg,
359434
- SCAN_PROXY_GRANULARITY_MAX as dh,
359435
- SCAN_PROXY_GRANULARITY_MIN as di,
359436
- __viteBrowserExternal$1 as dj,
361168
+ THICKNESS_GRADIENT_COLORS as d0,
361169
+ THICKNESS_COLORS as d1,
361170
+ DEFAULT_THICKNESS_COLOR_SCALE as d2,
361171
+ TorusGeometry as d3,
361172
+ sampleColorScale as d4,
361173
+ SphereGeometry as d5,
361174
+ ConeGeometry as d6,
361175
+ DEFAULT_COMPARISON_CANDIDATE_OPACITY as d7,
361176
+ computeCuttingLayout as d8,
361177
+ generateCuttingLayoutPdf as d9,
361178
+ buildDesignTraceNeighborhood as da,
361179
+ getCameraForwardVector as db,
361180
+ RENDER_STYLE_OPTIONS as dc,
361181
+ SCAN_PROXY_GRANULARITY_MAX as dd,
361182
+ SCAN_PROXY_GRANULARITY_MIN as de,
361183
+ COLORMAP_OPTIONS as df,
361184
+ __viteBrowserExternal$1 as dg,
359437
361185
  Layers as e,
359438
361186
  Color as f,
359439
361187
  RGBAFormat as g,