forgecad 0.9.4 → 0.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/dist/assets/{AdminPage-jwoEgwE_.js → AdminPage-uTtcSXtn.js} +1 -1
  2. package/dist/assets/{BlogPage-Ck7g3ue2.js → BlogPage-DYJMjWx3.js} +1 -1
  3. package/dist/assets/{DocsPage-9WaRC14b.js → DocsPage-C58f0K5v.js} +1 -6
  4. package/dist/assets/{EditorApp-Dja2jMmW.js → EditorApp-DNH1TEz1.js} +282 -62
  5. package/dist/assets/{EmbedViewer-37_PfMwv.js → EmbedViewer-CMXWA2LX.js} +2 -2
  6. package/dist/assets/{LandingPageProofDriven-CO8WL0CY.js → LandingPageProofDriven-CAu2OZFn.js} +1 -1
  7. package/dist/assets/{PricingPage-DADKGuOa.js → PricingPage-BIgW7m3X.js} +1 -1
  8. package/dist/assets/{SettingsPage-DKKI4W49.js → SettingsPage-N1l1tMXO.js} +1 -1
  9. package/dist/assets/{app-CwI02pTA.js → app-CFy7g5WP.js} +74 -12
  10. package/dist/assets/cli/{render-Kw5hLEcL.js → render-BrVVdj_T.js} +453 -41
  11. package/dist/assets/{evalWorker-D6ub3kfS.js → evalWorker-c_SB9gg3.js} +2057 -446
  12. package/dist/assets/{manifold-lru0jwVw.js → manifold-CRoBhJKH.js} +2 -2
  13. package/dist/assets/{manifold-CwDdMKyc.js → manifold-Cjk7WhRs.js} +1 -1
  14. package/dist/assets/{manifold-DTvmxSDf.js → manifold-Dp6pvFr6.js} +1 -1
  15. package/dist/assets/{renderSceneState-tvtNKNRi.js → renderSceneState-3DfsSASX.js} +1 -1
  16. package/dist/assets/{reportWorker-DeqktDGt.js → reportWorker-BLkuIoS8.js} +2052 -443
  17. package/dist/assets/{sectionPlaneMath-C8N0w8o3.js → sectionPlaneMath-CykEnkvQ.js} +2258 -518
  18. package/dist/cli/render.html +1 -1
  19. package/dist/docs/index.html +2 -2
  20. package/dist/docs-raw/AI/usage.md +0 -1
  21. package/dist/docs-raw/API/core/concepts.md +11 -1
  22. package/dist/docs-raw/CLI.md +64 -13
  23. package/dist/docs-raw/generated/assembly.md +8 -3
  24. package/dist/docs-raw/generated/concepts.md +44 -41
  25. package/dist/docs-raw/generated/core.md +97 -47
  26. package/dist/docs-raw/generated/curves.md +6 -580
  27. package/dist/docs-raw/generated/lib.md +40 -3
  28. package/dist/docs-raw/generated/output.md +6 -1
  29. package/dist/docs-raw/generated/sdf.md +50 -4
  30. package/dist/docs-raw/generated/viewport.md +1 -9
  31. package/dist/docs-raw/guides/inspection-bundles.md +31 -6
  32. package/dist/docs-raw/skills/forgecad-blockout-model.md +1 -0
  33. package/dist/docs-raw/skills/forgecad-image-replicator.md +3 -1
  34. package/dist/docs-raw/skills/forgecad-make-a-model.md +48 -4
  35. package/dist/docs-raw/skills/forgecad-render-inspect.md +3 -1
  36. package/dist/docs-raw/skills/forgecad-visual-spec.md +2 -0
  37. package/dist/docs-raw/skills/forgecad.md +2 -1
  38. package/dist/docs-raw/skills/index.md +0 -1
  39. package/dist/index.html +1 -1
  40. package/dist/sitemap.xml +6 -6
  41. package/dist-cli/blender/render.py +43 -8
  42. package/dist-cli/forgecad.js +4941 -1758
  43. package/dist-cli/forgecad.js.map +1 -1
  44. package/dist-skill/CONTEXT.md +255 -656
  45. package/dist-skill/SKILL-dev.md +2 -1
  46. package/dist-skill/SKILL.md +2 -1
  47. package/dist-skill/docs/API/core/concepts.md +11 -1
  48. package/dist-skill/docs/CLI.md +64 -13
  49. package/dist-skill/docs/generated/assembly.md +8 -3
  50. package/dist-skill/docs/generated/core.md +97 -47
  51. package/dist-skill/docs/generated/curves.md +6 -580
  52. package/dist-skill/docs/generated/lib.md +40 -3
  53. package/dist-skill/docs/generated/output.md +6 -1
  54. package/dist-skill/docs/generated/sdf.md +50 -4
  55. package/dist-skill/docs/generated/viewport.md +1 -9
  56. package/dist-skill/docs/guides/inspection-bundles.md +31 -6
  57. package/dist-skill/docs-dev/API/core/concepts.md +11 -1
  58. package/dist-skill/docs-dev/CLI.md +64 -13
  59. package/dist-skill/docs-dev/generated/assembly.md +8 -3
  60. package/dist-skill/docs-dev/generated/core.md +97 -47
  61. package/dist-skill/docs-dev/generated/curves.md +6 -580
  62. package/dist-skill/docs-dev/generated/lib.md +40 -3
  63. package/dist-skill/docs-dev/generated/output.md +6 -1
  64. package/dist-skill/docs-dev/generated/sdf.md +50 -4
  65. package/dist-skill/docs-dev/generated/viewport.md +1 -9
  66. package/dist-skill/docs-dev/guides/inspection-bundles.md +31 -6
  67. package/dist-skill/library/README.md +0 -1
  68. package/dist-skill/library/forgecad-blockout-model/SKILL.md +1 -0
  69. package/dist-skill/library/forgecad-image-replicator/SKILL.md +3 -1
  70. package/dist-skill/library/forgecad-make-a-model/SKILL.md +48 -4
  71. package/dist-skill/library/forgecad-render-inspect/SKILL.md +3 -1
  72. package/dist-skill/library/forgecad-visual-spec/SKILL.md +2 -0
  73. package/examples/api/drive-wheel-regions.forge.js +43 -0
  74. package/examples/api/sdf-circular-array-knurling.forge.js +19 -0
  75. package/examples/api/sdf-pattern2d-ceramic-ripple-set.forge.js +83 -0
  76. package/examples/api/sdf-pattern2d-grip-tread.forge.js +72 -0
  77. package/examples/api/sdf-pattern2d-orbital-jewelry.forge.js +62 -0
  78. package/examples/api/sdf-surface-basket-weave.forge.js +67 -0
  79. package/examples/api/sector-gear-body.forge.js +34 -0
  80. package/package.json +1 -1
  81. package/dist/docs-raw/skills/forgecad-api-dogfood.md +0 -130
  82. package/dist-skill/library/forgecad-api-dogfood/SKILL.md +0 -125
@@ -620,6 +620,47 @@ function cloneSdfFunctionConstants(constants) {
620
620
  if (!constants) return void 0;
621
621
  return Object.fromEntries(Object.entries(constants).map(([key, value]) => [key, cloneSdfFunctionConstant(value)]));
622
622
  }
623
+ function cloneSdfSurfacePatternNode(pattern) {
624
+ switch (pattern.kind) {
625
+ case "surfacePattern:constant":
626
+ return { kind: "surfacePattern:constant", value: pattern.value };
627
+ case "surfacePattern:sineWave":
628
+ return {
629
+ kind: "surfacePattern:sineWave",
630
+ direction: [...pattern.direction],
631
+ wavelength: pattern.wavelength,
632
+ amplitude: pattern.amplitude,
633
+ phase: pattern.phase,
634
+ bias: pattern.bias
635
+ };
636
+ case "surfacePattern:stripes":
637
+ return {
638
+ kind: "surfacePattern:stripes",
639
+ direction: [...pattern.direction],
640
+ spacing: pattern.spacing,
641
+ width: pattern.width,
642
+ depth: pattern.depth
643
+ };
644
+ case "surfacePattern:overUnderWeave":
645
+ return {
646
+ kind: "surfacePattern:overUnderWeave",
647
+ spacing: [...pattern.spacing],
648
+ threadWidth: [...pattern.threadWidth],
649
+ depth: pattern.depth,
650
+ underScale: pattern.underScale
651
+ };
652
+ case "surfacePattern:abs":
653
+ case "surfacePattern:negate":
654
+ return { kind: pattern.kind, child: cloneSdfSurfacePatternNode(pattern.child) };
655
+ case "surfacePattern:add":
656
+ case "surfacePattern:multiply":
657
+ case "surfacePattern:min":
658
+ case "surfacePattern:max":
659
+ return { kind: pattern.kind, children: pattern.children.map(cloneSdfSurfacePatternNode) };
660
+ case "surfacePattern:clamp":
661
+ return { kind: "surfacePattern:clamp", child: cloneSdfSurfacePatternNode(pattern.child), min: pattern.min, max: pattern.max };
662
+ }
663
+ }
623
664
  function cloneSdfNode(node) {
624
665
  switch (node.kind) {
625
666
  // Primitives — plain value types
@@ -670,6 +711,8 @@ function cloneSdfNode(node) {
670
711
  return { kind: "sdf:bend", child: cloneSdfNode(node.child), radius: node.radius };
671
712
  case "sdf:repeat":
672
713
  return { kind: "sdf:repeat", child: cloneSdfNode(node.child), spacing: [...node.spacing], count: [...node.count] };
714
+ case "sdf:circularArray":
715
+ return { kind: "sdf:circularArray", child: cloneSdfNode(node.child), count: node.count, offset: node.offset };
673
716
  case "sdf:shell":
674
717
  return { kind: "sdf:shell", child: cloneSdfNode(node.child), thickness: node.thickness };
675
718
  case "sdf:displace":
@@ -683,6 +726,7 @@ function cloneSdfNode(node) {
683
726
  return {
684
727
  kind: "sdf:surfaceDisplace",
685
728
  child: cloneSdfNode(node.child),
729
+ ...node.pattern ? { pattern: cloneSdfSurfacePatternNode(node.pattern) } : {},
686
730
  patternBody: node.patternBody,
687
731
  ...node.constants ? { constants: cloneSdfFunctionConstants(node.constants) } : {},
688
732
  ...node.uvMode ? { uvMode: node.uvMode } : {},
@@ -4724,7 +4768,7 @@ for (var i = 0; i < 32; ++i)
4724
4768
  fdt[i] = 5;
4725
4769
  var flm = /* @__PURE__ */ hMap(flt, 9, 0), flrm = /* @__PURE__ */ hMap(flt, 9, 1);
4726
4770
  var fdm = /* @__PURE__ */ hMap(fdt, 5, 0), fdrm = /* @__PURE__ */ hMap(fdt, 5, 1);
4727
- var max$1 = function(a2) {
4771
+ var max$2 = function(a2) {
4728
4772
  var m2 = a2[0];
4729
4773
  for (var i = 1; i < a2.length; ++i) {
4730
4774
  if (a2[i] > m2)
@@ -4824,7 +4868,7 @@ var inflt = function(dat, st, buf, dict) {
4824
4868
  clt[clim[i]] = bits(dat, pos + i * 3, 7);
4825
4869
  }
4826
4870
  pos += hcLen * 3;
4827
- var clb = max$1(clt), clbmsk = (1 << clb) - 1;
4871
+ var clb = max$2(clt), clbmsk = (1 << clb) - 1;
4828
4872
  var clm = hMap(clt, clb, 1);
4829
4873
  for (var i = 0; i < tl; ) {
4830
4874
  var r = clm[bits(dat, pos, clbmsk)];
@@ -4845,8 +4889,8 @@ var inflt = function(dat, st, buf, dict) {
4845
4889
  }
4846
4890
  }
4847
4891
  var lt = ldt.subarray(0, hLit), dt = ldt.subarray(hLit);
4848
- lbt = max$1(lt);
4849
- dbt = max$1(dt);
4892
+ lbt = max$2(lt);
4893
+ dbt = max$2(dt);
4850
4894
  lm = hMap(lt, lbt, 1);
4851
4895
  dm = hMap(dt, dbt, 1);
4852
4896
  } else
@@ -5698,9 +5742,9 @@ function parse3mf(data) {
5698
5742
  let tMatch;
5699
5743
  while ((tMatch = trianglePattern.exec(meshXml)) !== null) {
5700
5744
  const v1 = parseInt(tMatch[1], 10) + vertexOffset;
5701
- const v2 = parseInt(tMatch[2], 10) + vertexOffset;
5745
+ const v22 = parseInt(tMatch[2], 10) + vertexOffset;
5702
5746
  const v32 = parseInt(tMatch[3], 10) + vertexOffset;
5703
- allTriIndices.push(v1, v2, v32);
5747
+ allTriIndices.push(v1, v22, v32);
5704
5748
  }
5705
5749
  for (let i = 0; i < meshVerts.length; i++) {
5706
5750
  allPositions.push(meshVerts[i]);
@@ -6083,6 +6127,287 @@ function lowerShellShapeCompilePlanToConcretePlan(plan) {
6083
6127
  }
6084
6128
  return lowerBaseShellPlanToConcretePlan(plan.base, plan.thickness, normalizeShellOpenFaces(plan.openFaces));
6085
6129
  }
6130
+ const DEFAULT_MAX_GRID_POINTS = 8e6;
6131
+ const DEFAULT_MIN_EDGE_LENGTH = 0.15;
6132
+ function resolveSdfMeshingSettings(tree, bounds, options = {}) {
6133
+ const quality = options.quality ?? "preview";
6134
+ const minEdgeLength = positiveOrDefault(options.minEdgeLength, DEFAULT_MIN_EDGE_LENGTH);
6135
+ const maxGridPoints = positiveOrDefault(options.maxGridPoints, DEFAULT_MAX_GRID_POINTS);
6136
+ const tolerance = options.tolerance !== void 0 ? requirePositiveFinite$3(options.tolerance, "SDF tolerance") : void 0;
6137
+ const minFeatureSize = options.minFeatureSize !== void 0 ? requirePositiveFinite$3(options.minFeatureSize, "SDF minFeatureSize") : void 0;
6138
+ const maxTriangles = options.maxTriangles !== void 0 ? Math.floor(requirePositiveFinite$3(options.maxTriangles, "SDF maxTriangles")) : void 0;
6139
+ const analysis = analyzeSdfTree(tree);
6140
+ const warnings = [];
6141
+ let edgeLength2;
6142
+ if (options.edgeLength !== void 0) {
6143
+ edgeLength2 = requirePositiveFinite$3(options.edgeLength, "SDF edgeLength");
6144
+ if (edgeLength2 < minEdgeLength) {
6145
+ warnings.push(`edgeLength ${formatMm(edgeLength2)} was clamped to minimum ${formatMm(minEdgeLength)}.`);
6146
+ edgeLength2 = minEdgeLength;
6147
+ }
6148
+ } else {
6149
+ edgeLength2 = resolveDefaultEdgeLength(bounds, quality, minEdgeLength, analysis, options);
6150
+ }
6151
+ if (analysis.minWallThickness < Infinity && analysis.minWallThickness < edgeLength2 * 2) {
6152
+ analysis.riskFlags.add("thin-shell");
6153
+ warnings.push(
6154
+ `shell/wall thickness ${formatMm(analysis.minWallThickness)} is below 2 x edgeLength ${formatMm(edgeLength2)}; thin features may be under-sampled.`
6155
+ );
6156
+ }
6157
+ if (!options.bounds && analysis.hasInfiniteRepeat) {
6158
+ warnings.push("infinite repeat bounds are heuristic; pass .toShape({ bounds }) for predictable clipping.");
6159
+ }
6160
+ if (!options.bounds && analysis.riskFlags.has("noise")) {
6161
+ warnings.push("noise field bounds are heuristic; pass .toShape({ bounds }) for predictable clipping.");
6162
+ }
6163
+ if (!options.bounds && (analysis.riskFlags.has("tpms") || analysis.riskFlags.has("voronoi"))) {
6164
+ warnings.push("TPMS/Voronoi bounds are heuristic unless clipped or passed explicitly.");
6165
+ }
6166
+ if (analysis.hasLegacyTpmsThreshold) {
6167
+ warnings.push("TPMS thickness is using legacy field-threshold units; use wallThickness for approximate millimeters.");
6168
+ }
6169
+ return {
6170
+ quality,
6171
+ edgeLength: edgeLength2,
6172
+ tolerance,
6173
+ minFeatureSize,
6174
+ minEdgeLength,
6175
+ simplify: resolveSimplificationMode(options.simplify, quality, analysis.riskFlags),
6176
+ maxTriangles,
6177
+ maxGridPoints,
6178
+ diagnostics: options.diagnostics === true,
6179
+ treeRiskFlags: [...analysis.riskFlags].sort(),
6180
+ warnings
6181
+ };
6182
+ }
6183
+ function withScaledSdfEdgeLength(settings, edgeLength2) {
6184
+ return { ...settings, edgeLength: Math.max(settings.minEdgeLength, edgeLength2) };
6185
+ }
6186
+ function createSdfMeshingDiagnostics(settings, bounds, paddedBounds) {
6187
+ const grid = estimateSdfGridDimensions(paddedBounds, settings.edgeLength);
6188
+ const estimatedSamples = grid[0] * grid[1] * grid[2];
6189
+ return {
6190
+ bounds: cloneBounds$2(bounds),
6191
+ paddedBounds: cloneBounds$2(paddedBounds),
6192
+ edgeLength: settings.edgeLength,
6193
+ grid,
6194
+ estimatedSamples,
6195
+ estimatedMemoryBytes: estimatedSamples * 8,
6196
+ treeRiskFlags: [...settings.treeRiskFlags],
6197
+ simplification: settings.simplify,
6198
+ capMode: "box",
6199
+ capInset: settings.edgeLength,
6200
+ warnings: [...settings.warnings]
6201
+ };
6202
+ }
6203
+ function assertSdfMeshingBudget(diagnostics, maxGridPoints) {
6204
+ if (diagnostics.estimatedSamples <= maxGridPoints) return;
6205
+ const suggestedEdge = suggestEdgeLengthForSampleBudget(diagnostics.paddedBounds, maxGridPoints);
6206
+ throw new Error(
6207
+ `SDF meshing would sample ${formatCount(diagnostics.estimatedSamples)} grid points (~${formatBytes(
6208
+ diagnostics.estimatedMemoryBytes
6209
+ )}). Reduce bounds or use edgeLength >= ${formatMm(suggestedEdge)}.`
6210
+ );
6211
+ }
6212
+ function estimateSdfGridDimensions(bounds, edgeLength2) {
6213
+ const dx = bounds.max[0] - bounds.min[0];
6214
+ const dy = bounds.max[1] - bounds.min[1];
6215
+ const dz = bounds.max[2] - bounds.min[2];
6216
+ return [
6217
+ Math.max(2, Math.ceil(dx / edgeLength2) + 1),
6218
+ Math.max(2, Math.ceil(dy / edgeLength2) + 1),
6219
+ Math.max(2, Math.ceil(dz / edgeLength2) + 1)
6220
+ ];
6221
+ }
6222
+ function logSdfMeshingDiagnostics(prefix, diagnostics) {
6223
+ const warnings = diagnostics.warnings.length > 0 ? `, warnings=${diagnostics.warnings.join(" | ")}` : "";
6224
+ const evaluator = diagnostics.evaluator ? `, evaluator=${diagnostics.evaluator}${diagnostics.evaluatorUnsupportedReason ? ` (${diagnostics.evaluatorUnsupportedReason})` : ""}` : "";
6225
+ console.info(
6226
+ `${prefix}: bounds=${formatBounds(diagnostics.bounds)}, paddedBounds=${formatBounds(diagnostics.paddedBounds)}, edgeLength=${formatMm(diagnostics.edgeLength)}, grid=${diagnostics.grid.join("x")}, estimatedSamples=${formatCount(diagnostics.estimatedSamples)}, treeRisk=${diagnostics.treeRiskFlags.join("+") || "none"}, simplify=${diagnostics.simplification}${evaluator}, capMode=${diagnostics.capMode}, capInset=${formatMm(diagnostics.capInset)}${warnings}`
6227
+ );
6228
+ }
6229
+ function resolveDefaultEdgeLength(bounds, quality, minEdgeLength, analysis, options) {
6230
+ const dx = bounds.max[0] - bounds.min[0];
6231
+ const dy = bounds.max[1] - bounds.min[1];
6232
+ const dz = bounds.max[2] - bounds.min[2];
6233
+ const maxDim = Math.max(dx, dy, dz, minEdgeLength);
6234
+ const divisor = quality === "draft" ? 60 : quality === "export" ? 160 : 100;
6235
+ const candidates = [maxDim / divisor];
6236
+ if (options.tolerance !== void 0) candidates.push(requirePositiveFinite$3(options.tolerance, "SDF tolerance") * 2);
6237
+ if (options.minFeatureSize !== void 0) candidates.push(requirePositiveFinite$3(options.minFeatureSize, "SDF minFeatureSize") / 2.5);
6238
+ if (analysis.minMetricTpmsThickness < Infinity) candidates.push(analysis.minMetricTpmsThickness / 2);
6239
+ if (analysis.minTpmsCellSize < Infinity) candidates.push(analysis.minTpmsCellSize / 10);
6240
+ if (analysis.minRepeatSpacing < Infinity) candidates.push(analysis.minRepeatSpacing / 8);
6241
+ if (analysis.minWallThickness < Infinity) candidates.push(analysis.minWallThickness / 2.5);
6242
+ return Math.max(minEdgeLength, Math.min(...candidates.filter((v) => Number.isFinite(v) && v > 0)));
6243
+ }
6244
+ function resolveSimplificationMode(simplify, quality, riskFlags) {
6245
+ if (simplify === false) return "off";
6246
+ if (simplify === true || simplify === "safe") return "safe";
6247
+ if (quality === "export" && riskFlags.size > 0) return "off";
6248
+ return "safe";
6249
+ }
6250
+ function analyzeSdfTree(tree) {
6251
+ const analysis = {
6252
+ riskFlags: /* @__PURE__ */ new Set(),
6253
+ minTpmsCellSize: Infinity,
6254
+ minMetricTpmsThickness: Infinity,
6255
+ minRepeatSpacing: Infinity,
6256
+ minWallThickness: Infinity,
6257
+ hasInfiniteRepeat: false,
6258
+ hasLegacyTpmsThreshold: false
6259
+ };
6260
+ visitSdfNode(tree, analysis);
6261
+ return analysis;
6262
+ }
6263
+ function minPositive$1(...values) {
6264
+ let result = Infinity;
6265
+ for (const value of values) {
6266
+ if (value !== null && value !== void 0 && Number.isFinite(value) && value > 0) {
6267
+ result = Math.min(result, value);
6268
+ }
6269
+ }
6270
+ return result === Infinity ? null : result;
6271
+ }
6272
+ function estimateSurfacePatternSpacing(pattern) {
6273
+ switch (pattern.kind) {
6274
+ case "surfacePattern:constant":
6275
+ return null;
6276
+ case "surfacePattern:sineWave":
6277
+ return pattern.wavelength;
6278
+ case "surfacePattern:stripes":
6279
+ return Math.min(pattern.spacing, pattern.width);
6280
+ case "surfacePattern:overUnderWeave":
6281
+ return Math.min(...pattern.spacing, ...pattern.threadWidth);
6282
+ case "surfacePattern:abs":
6283
+ case "surfacePattern:negate":
6284
+ return estimateSurfacePatternSpacing(pattern.child);
6285
+ case "surfacePattern:add":
6286
+ case "surfacePattern:multiply":
6287
+ case "surfacePattern:min":
6288
+ case "surfacePattern:max":
6289
+ return minPositive$1(...pattern.children.map(estimateSurfacePatternSpacing));
6290
+ case "surfacePattern:clamp":
6291
+ return estimateSurfacePatternSpacing(pattern.child);
6292
+ }
6293
+ }
6294
+ function visitSdfNode(node, analysis) {
6295
+ switch (node.kind) {
6296
+ case "sdf:union":
6297
+ case "sdf:difference":
6298
+ case "sdf:intersection":
6299
+ case "sdf:smoothUnion":
6300
+ case "sdf:smoothDifference":
6301
+ case "sdf:smoothIntersection":
6302
+ for (const child of node.children) visitSdfNode(child, analysis);
6303
+ break;
6304
+ case "sdf:morph":
6305
+ case "sdf:spatialBlend":
6306
+ visitSdfNode(node.a, analysis);
6307
+ visitSdfNode(node.b, analysis);
6308
+ break;
6309
+ case "sdf:translate":
6310
+ case "sdf:rotate":
6311
+ case "sdf:scale":
6312
+ case "sdf:twist":
6313
+ case "sdf:bend":
6314
+ case "sdf:onion":
6315
+ visitSdfNode(node.child, analysis);
6316
+ break;
6317
+ case "sdf:repeat":
6318
+ analysis.riskFlags.add("repeat");
6319
+ for (let i = 0; i < 3; i++) {
6320
+ const spacing = node.spacing[i];
6321
+ if (spacing > 0) {
6322
+ analysis.minRepeatSpacing = Math.min(analysis.minRepeatSpacing, spacing);
6323
+ if (node.count[i] <= 0) analysis.hasInfiniteRepeat = true;
6324
+ }
6325
+ }
6326
+ visitSdfNode(node.child, analysis);
6327
+ break;
6328
+ case "sdf:circularArray": {
6329
+ analysis.riskFlags.add("repeat");
6330
+ if (node.offset > 0) {
6331
+ analysis.minRepeatSpacing = Math.min(analysis.minRepeatSpacing, 2 * Math.PI * node.offset / node.count);
6332
+ }
6333
+ visitSdfNode(node.child, analysis);
6334
+ break;
6335
+ }
6336
+ case "sdf:shell":
6337
+ analysis.minWallThickness = Math.min(analysis.minWallThickness, node.thickness);
6338
+ visitSdfNode(node.child, analysis);
6339
+ break;
6340
+ case "sdf:displace":
6341
+ case "sdf:surfaceDisplace":
6342
+ analysis.riskFlags.add("displacement");
6343
+ if (node.kind === "sdf:surfaceDisplace" && node.pattern) {
6344
+ const spacing = estimateSurfacePatternSpacing(node.pattern);
6345
+ if (spacing !== null) analysis.minRepeatSpacing = Math.min(analysis.minRepeatSpacing, spacing);
6346
+ }
6347
+ visitSdfNode(node.child, analysis);
6348
+ break;
6349
+ case "sdf:gyroid":
6350
+ case "sdf:schwarzP":
6351
+ case "sdf:diamond":
6352
+ case "sdf:lidinoid":
6353
+ analysis.riskFlags.add("tpms");
6354
+ analysis.minTpmsCellSize = Math.min(analysis.minTpmsCellSize, node.cellSize);
6355
+ if (node.thicknessMode === "metric-approx") {
6356
+ analysis.minMetricTpmsThickness = Math.min(analysis.minMetricTpmsThickness, node.thickness);
6357
+ analysis.minWallThickness = Math.min(analysis.minWallThickness, node.thickness);
6358
+ } else {
6359
+ analysis.hasLegacyTpmsThreshold = true;
6360
+ }
6361
+ break;
6362
+ case "sdf:noise":
6363
+ analysis.riskFlags.add("noise");
6364
+ break;
6365
+ case "sdf:voronoi":
6366
+ analysis.riskFlags.add("voronoi");
6367
+ analysis.minWallThickness = Math.min(analysis.minWallThickness, node.wallThickness);
6368
+ if (node.surfaceChild) visitSdfNode(node.surfaceChild, analysis);
6369
+ break;
6370
+ case "sdf:custom":
6371
+ analysis.riskFlags.add("custom");
6372
+ break;
6373
+ }
6374
+ }
6375
+ function positiveOrDefault(value, fallback) {
6376
+ if (value === void 0) return fallback;
6377
+ return requirePositiveFinite$3(value, "SDF meshing option");
6378
+ }
6379
+ function requirePositiveFinite$3(value, name) {
6380
+ if (!Number.isFinite(value) || value <= 0) {
6381
+ throw new Error(`${name} must be a positive finite number.`);
6382
+ }
6383
+ return value;
6384
+ }
6385
+ function cloneBounds$2(bounds) {
6386
+ return { min: [...bounds.min], max: [...bounds.max] };
6387
+ }
6388
+ function suggestEdgeLengthForSampleBudget(bounds, maxGridPoints) {
6389
+ const dx = bounds.max[0] - bounds.min[0];
6390
+ const dy = bounds.max[1] - bounds.min[1];
6391
+ const dz = bounds.max[2] - bounds.min[2];
6392
+ const volume = Math.max(dx * dy * dz, 1);
6393
+ return Math.cbrt(volume / Math.max(maxGridPoints, 8));
6394
+ }
6395
+ function formatBounds(bounds) {
6396
+ return `[${bounds.min.map(formatNumber$1).join(",")}]-[${bounds.max.map(formatNumber$1).join(",")}]`;
6397
+ }
6398
+ function formatMm(value) {
6399
+ return `${formatNumber$1(value)}mm`;
6400
+ }
6401
+ function formatNumber$1(value) {
6402
+ return Number.isInteger(value) ? String(value) : value.toFixed(3).replace(/0+$/, "").replace(/\.$/, "");
6403
+ }
6404
+ function formatCount(value) {
6405
+ return Math.round(value).toLocaleString("en-US");
6406
+ }
6407
+ function formatBytes(bytes) {
6408
+ if (bytes < 1024 * 1024) return `${Math.ceil(bytes / 1024)} KB`;
6409
+ return `${Math.ceil(bytes / (1024 * 1024))} MB`;
6410
+ }
6086
6411
  const grad3 = new Float64Array([
6087
6412
  1,
6088
6413
  1,
@@ -6543,8 +6868,8 @@ function triplanarWeights(nx, ny, nz, sharpness) {
6543
6868
  const inv = 1 / sum2;
6544
6869
  return { wx: wx * inv, wy: wy * inv, wz: wz * inv };
6545
6870
  }
6546
- const { atan2, acos, cos: cos$2, sin: sin$2, sqrt: sqrt$2, PI: PI$2 } = Math;
6547
- const DEG$2 = PI$2 / 180;
6871
+ const { atan2, acos, cos: cos$3, sin: sin$3, sqrt: sqrt$3, PI: PI$3 } = Math;
6872
+ const DEG$3 = PI$3 / 180;
6548
6873
  const IDENTITY = (p2) => p2;
6549
6874
  function analyzeUV(node, override) {
6550
6875
  if (override) {
@@ -6571,10 +6896,10 @@ function analyzeNodeUV(node, toLocal) {
6571
6896
  return analyzeNodeUV(node.child, next);
6572
6897
  }
6573
6898
  case "sdf:rotate": {
6574
- const [rx, ry, rz] = node.degrees.map((d2) => d2 * DEG$2);
6575
- const cx = cos$2(rx), sx = sin$2(rx);
6576
- const cy = cos$2(ry), sy = sin$2(ry);
6577
- const cz = cos$2(rz), sz = sin$2(rz);
6899
+ const [rx, ry, rz] = node.degrees.map((d2) => d2 * DEG$3);
6900
+ const cx = cos$3(rx), sx = sin$3(rx);
6901
+ const cy = cos$3(ry), sy = sin$3(ry);
6902
+ const cz = cos$3(rz), sz = sin$3(rz);
6578
6903
  const prev = toLocal;
6579
6904
  const next = (p2) => {
6580
6905
  const pp = prev(p2);
@@ -6631,7 +6956,7 @@ function compileUVFunction(analysis) {
6631
6956
  return (p2) => {
6632
6957
  const lp = toLocal(p2);
6633
6958
  const u2 = atan2(lp[1], lp[0]) * R;
6634
- const len2 = sqrt$2(lp[0] * lp[0] + lp[1] * lp[1] + lp[2] * lp[2]);
6959
+ const len2 = sqrt$3(lp[0] * lp[0] + lp[1] * lp[1] + lp[2] * lp[2]);
6635
6960
  const v = acos(clampUnit(lp[2] / (len2 || 1))) * R;
6636
6961
  return [u2, v];
6637
6962
  };
@@ -6651,23 +6976,23 @@ function compileUVFunction(analysis) {
6651
6976
  return (p2) => {
6652
6977
  const lp = toLocal(p2);
6653
6978
  const u2 = atan2(lp[1], lp[0]) * R;
6654
- const xyDist = sqrt$2(lp[0] * lp[0] + lp[1] * lp[1]) - R;
6979
+ const xyDist = sqrt$3(lp[0] * lp[0] + lp[1] * lp[1]) - R;
6655
6980
  const v = atan2(lp[2], xyDist) * r;
6656
6981
  return [u2, v];
6657
6982
  };
6658
6983
  }
6659
6984
  }
6660
6985
  }
6661
- const { abs: abs$1, cos: cos$1, sin: sin$1, sqrt: sqrt$1, PI: PI$1 } = Math;
6662
- const TAU = 2 * PI$1;
6663
- const GRAD_EPS = 1e-9;
6986
+ const { abs: abs$1, cos: cos$2, sin: sin$2, sqrt: sqrt$2, PI: PI$2 } = Math;
6987
+ const TAU$1 = 2 * PI$2;
6988
+ const GRAD_EPS$1 = 1e-9;
6664
6989
  function gyroidValueAndGradient(x2, y2, z2, cellSize) {
6665
- const s = TAU / cellSize;
6990
+ const s = TAU$1 / cellSize;
6666
6991
  const xs = x2 * s;
6667
6992
  const ys = y2 * s;
6668
6993
  const zs = z2 * s;
6669
- const sx = sin$1(xs), sy = sin$1(ys), sz = sin$1(zs);
6670
- const cx = cos$1(xs), cy = cos$1(ys), cz = cos$1(zs);
6994
+ const sx = sin$2(xs), sy = sin$2(ys), sz = sin$2(zs);
6995
+ const cx = cos$2(xs), cy = cos$2(ys), cz = cos$2(zs);
6671
6996
  return {
6672
6997
  value: sx * cy + sy * cz + sz * cx,
6673
6998
  gx: s * (cx * cy - sz * sx),
@@ -6676,24 +7001,24 @@ function gyroidValueAndGradient(x2, y2, z2, cellSize) {
6676
7001
  };
6677
7002
  }
6678
7003
  function schwarzPValueAndGradient(x2, y2, z2, cellSize) {
6679
- const s = TAU / cellSize;
7004
+ const s = TAU$1 / cellSize;
6680
7005
  const xs = x2 * s;
6681
7006
  const ys = y2 * s;
6682
7007
  const zs = z2 * s;
6683
7008
  return {
6684
- value: cos$1(xs) + cos$1(ys) + cos$1(zs),
6685
- gx: -s * sin$1(xs),
6686
- gy: -s * sin$1(ys),
6687
- gz: -s * sin$1(zs)
7009
+ value: cos$2(xs) + cos$2(ys) + cos$2(zs),
7010
+ gx: -s * sin$2(xs),
7011
+ gy: -s * sin$2(ys),
7012
+ gz: -s * sin$2(zs)
6688
7013
  };
6689
7014
  }
6690
7015
  function diamondValueAndGradient(x2, y2, z2, cellSize) {
6691
- const s = TAU / cellSize;
7016
+ const s = TAU$1 / cellSize;
6692
7017
  const xs = x2 * s;
6693
7018
  const ys = y2 * s;
6694
7019
  const zs = z2 * s;
6695
- const sx = sin$1(xs), sy = sin$1(ys), sz = sin$1(zs);
6696
- const cx = cos$1(xs), cy = cos$1(ys), cz = cos$1(zs);
7020
+ const sx = sin$2(xs), sy = sin$2(ys), sz = sin$2(zs);
7021
+ const cx = cos$2(xs), cy = cos$2(ys), cz = cos$2(zs);
6697
7022
  return {
6698
7023
  value: sx * sy * sz + sx * cy * cz + cx * sy * cz + cx * cy * sz,
6699
7024
  gx: s * (cx * sy * sz + cx * cy * cz - sx * sy * cz - sx * cy * sz),
@@ -6702,12 +7027,12 @@ function diamondValueAndGradient(x2, y2, z2, cellSize) {
6702
7027
  };
6703
7028
  }
6704
7029
  function lidinoidValueAndGradient(x2, y2, z2, cellSize) {
6705
- const s = TAU / cellSize;
7030
+ const s = TAU$1 / cellSize;
6706
7031
  const sx2 = x2 * s, sy2 = y2 * s, sz2 = z2 * s;
6707
- const sx = sin$1(sx2), sy = sin$1(sy2), sz = sin$1(sz2);
6708
- const cx = cos$1(sx2), cy = cos$1(sy2), cz = cos$1(sz2);
6709
- const s2x = sin$1(2 * sx2), s2y = sin$1(2 * sy2), s2z = sin$1(2 * sz2);
6710
- const c2x = cos$1(2 * sx2), c2y = cos$1(2 * sy2), c2z = cos$1(2 * sz2);
7032
+ const sx = sin$2(sx2), sy = sin$2(sy2), sz = sin$2(sz2);
7033
+ const cx = cos$2(sx2), cy = cos$2(sy2), cz = cos$2(sz2);
7034
+ const s2x = sin$2(2 * sx2), s2y = sin$2(2 * sy2), s2z = sin$2(2 * sz2);
7035
+ const c2x = cos$2(2 * sx2), c2y = cos$2(2 * sy2), c2z = cos$2(2 * sz2);
6711
7036
  const val = s2x * cy * sz + s2y * cz * sx + s2z * cx * sy - c2x * c2y - c2y * c2z - c2z * c2x + 0.3;
6712
7037
  return {
6713
7038
  value: val,
@@ -6730,8 +7055,8 @@ function lidinoid$1(x2, y2, z2, cellSize, thickness, thicknessMode) {
6730
7055
  }
6731
7056
  function tpmsDistance({ value, gx, gy, gz }, thickness, thicknessMode) {
6732
7057
  if (thicknessMode !== "metric-approx") return abs$1(value) - thickness;
6733
- const grad = sqrt$1(gx * gx + gy * gy + gz * gz);
6734
- return abs$1(value) / Math.max(grad, GRAD_EPS) - thickness * 0.5;
7058
+ const grad = sqrt$2(gx * gx + gy * gy + gz * gz);
7059
+ return abs$1(value) / Math.max(grad, GRAD_EPS$1) - thickness * 0.5;
6735
7060
  }
6736
7061
  function mix(h) {
6737
7062
  h = (h ^ h >>> 16) * 2246822507 | 0;
@@ -6827,76 +7152,76 @@ function seededWorley3Surface(seed) {
6827
7152
  const s = seed | 0;
6828
7153
  return (x2, y2, z2, nx, ny, nz, threshold) => worleySurface(x2, y2, z2, s, nx, ny, nz, threshold);
6829
7154
  }
6830
- const { abs, cos, max, min, sin, sqrt, PI } = Math;
6831
- const DEG$1 = PI / 180;
7155
+ const { abs, cos: cos$1, max: max$1, min, sin: sin$1, sqrt: sqrt$1, PI: PI$1 } = Math;
7156
+ const DEG$2 = PI$1 / 180;
6832
7157
  function clamp$a(v, lo, hi) {
6833
7158
  return v < lo ? lo : v > hi ? hi : v;
6834
7159
  }
6835
- function length2(x2, y2) {
6836
- return sqrt(x2 * x2 + y2 * y2);
7160
+ function length2$1(x2, y2) {
7161
+ return sqrt$1(x2 * x2 + y2 * y2);
6837
7162
  }
6838
- function length3(x2, y2, z2) {
6839
- return sqrt(x2 * x2 + y2 * y2 + z2 * z2);
7163
+ function length3$1(x2, y2, z2) {
7164
+ return sqrt$1(x2 * x2 + y2 * y2 + z2 * z2);
6840
7165
  }
6841
- function sdSphere(px2, py2, pz2, r) {
6842
- return length3(px2, py2, pz2) - r;
7166
+ function sdSphere$1(px2, py2, pz2, r) {
7167
+ return length3$1(px2, py2, pz2) - r;
6843
7168
  }
6844
- function sdBox(px2, py2, pz2, hx, hy, hz) {
7169
+ function sdBox$1(px2, py2, pz2, hx, hy, hz) {
6845
7170
  const dx = abs(px2) - hx;
6846
7171
  const dy = abs(py2) - hy;
6847
7172
  const dz = abs(pz2) - hz;
6848
- return length3(max(dx, 0), max(dy, 0), max(dz, 0)) + min(max(dx, dy, dz), 0);
7173
+ return length3$1(max$1(dx, 0), max$1(dy, 0), max$1(dz, 0)) + min(max$1(dx, dy, dz), 0);
6849
7174
  }
6850
- function sdCylinder(px2, py2, pz2, h, r) {
6851
- const dx = length2(px2, py2) - r;
7175
+ function sdCylinder$1(px2, py2, pz2, h, r) {
7176
+ const dx = length2$1(px2, py2) - r;
6852
7177
  const dz = abs(pz2) - h * 0.5;
6853
- return length2(max(dx, 0), max(dz, 0)) + min(max(dx, dz), 0);
7178
+ return length2$1(max$1(dx, 0), max$1(dz, 0)) + min(max$1(dx, dz), 0);
6854
7179
  }
6855
- function sdTorus(px2, py2, pz2, R, r) {
6856
- const qx = length2(px2, py2) - R;
6857
- return length2(qx, pz2) - r;
7180
+ function sdTorus$1(px2, py2, pz2, R, r) {
7181
+ const qx = length2$1(px2, py2) - R;
7182
+ return length2$1(qx, pz2) - r;
6858
7183
  }
6859
- function sdCapsule(px2, py2, pz2, h, r) {
7184
+ function sdCapsule$1(px2, py2, pz2, h, r) {
6860
7185
  const halfH = h * 0.5;
6861
7186
  const cz = clamp$a(pz2, -halfH, halfH);
6862
- return length3(px2, py2, pz2 - cz) - r;
7187
+ return length3$1(px2, py2, pz2 - cz) - r;
6863
7188
  }
6864
- function sdCone(px2, py2, pz2, h, r) {
6865
- const q = length2(px2, py2);
6866
- const cLen = length2(h, r);
7189
+ function sdCone$1(px2, py2, pz2, h, r) {
7190
+ const q = length2$1(px2, py2);
7191
+ const cLen = length2$1(h, r);
6867
7192
  const nx = h / cLen;
6868
7193
  const nz = -r / cLen;
6869
- const d2 = max(nx * q + nz * (pz2 - h), -pz2, pz2 - h);
7194
+ const d2 = max$1(nx * q + nz * (pz2 - h), -pz2, pz2 - h);
6870
7195
  return d2;
6871
7196
  }
6872
- function sdTaperedSegment(px2, py2, pz2, ax, ay, az, bx, by, bz, ra, rb) {
7197
+ function sdTaperedSegment$1(px2, py2, pz2, ax, ay, az, bx, by, bz, ra, rb) {
6873
7198
  const vx = bx - ax;
6874
7199
  const vy = by - ay;
6875
7200
  const vz = bz - az;
6876
7201
  const len2 = vx * vx + vy * vy + vz * vz;
6877
- if (len2 <= 1e-12) return sdSphere(px2 - ax, py2 - ay, pz2 - az, max(ra, rb));
7202
+ if (len2 <= 1e-12) return sdSphere$1(px2 - ax, py2 - ay, pz2 - az, max$1(ra, rb));
6878
7203
  const h = clamp$a(((px2 - ax) * vx + (py2 - ay) * vy + (pz2 - az) * vz) / len2, 0, 1);
6879
- return length3(px2 - (ax + vx * h), py2 - (ay + vy * h), pz2 - (az + vz * h)) - (ra + (rb - ra) * h);
7204
+ return length3$1(px2 - (ax + vx * h), py2 - (ay + vy * h), pz2 - (az + vz * h)) - (ra + (rb - ra) * h);
6880
7205
  }
6881
7206
  function sdPolylineSweep3(node, x2, y2, z2) {
6882
7207
  let d2 = 1e20;
6883
7208
  for (let i = 0; i < node.points.length - 1; i++) {
6884
7209
  const a2 = node.points[i];
6885
7210
  const b = node.points[i + 1];
6886
- const segment = sdTaperedSegment(x2, y2, z2, a2[0], a2[1], a2[2], b[0], b[1], b[2], node.radii[i], node.radii[i + 1]);
6887
- d2 = i === 0 ? segment : smin(d2, segment, node.blend);
7211
+ const segment = sdTaperedSegment$1(x2, y2, z2, a2[0], a2[1], a2[2], b[0], b[1], b[2], node.radii[i], node.radii[i + 1]);
7212
+ d2 = i === 0 ? segment : smin$1(d2, segment, node.blend);
6888
7213
  }
6889
7214
  return d2;
6890
7215
  }
6891
- function smin(a2, b, k2) {
7216
+ function smin$1(a2, b, k2) {
6892
7217
  if (k2 <= 0) return min(a2, b);
6893
- const h = max(k2 - abs(a2 - b), 0) / k2;
7218
+ const h = max$1(k2 - abs(a2 - b), 0) / k2;
6894
7219
  return min(a2, b) - h * h * h * k2 * (1 / 6);
6895
7220
  }
6896
- function smax(a2, b, k2) {
6897
- return -smin(-a2, -b, k2);
7221
+ function smax$1(a2, b, k2) {
7222
+ return -smin$1(-a2, -b, k2);
6898
7223
  }
6899
- function repeatCoord(v, spacing, count) {
7224
+ function repeatCoord$1(v, spacing, count) {
6900
7225
  if (spacing <= 0) return v;
6901
7226
  if (count > 0) {
6902
7227
  const center = (count - 1) * 0.5;
@@ -6905,31 +7230,138 @@ function repeatCoord(v, spacing, count) {
6905
7230
  }
6906
7231
  return v - spacing * Math.round(v / spacing);
6907
7232
  }
7233
+ function positiveMod(v, period) {
7234
+ return (v % period + period) % period;
7235
+ }
7236
+ function evalStripesPattern(u2, v, directionX, directionY, spacing, width, depth) {
7237
+ const coord = u2 * directionX + v * directionY;
7238
+ const d2 = abs(coord - Math.round(coord / spacing) * spacing);
7239
+ const profile = max$1(0, 1 - d2 / (width * 0.5));
7240
+ return -(profile * profile) * depth;
7241
+ }
7242
+ function evalOverUnderWeavePattern(u2, v, spacingX, spacingY, widthX, widthY, depth, underScale) {
7243
+ const su = u2 / spacingX;
7244
+ const sv = v / spacingY;
7245
+ let pU = max$1(0, 1 - abs(su - Math.round(su)) * spacingX / (widthX * 0.5));
7246
+ let pV = max$1(0, 1 - abs(sv - Math.round(sv)) * spacingY / (widthY * 0.5));
7247
+ pU *= pU;
7248
+ pV *= pV;
7249
+ const checker = (Math.round(su) & 65535) + (Math.round(sv) & 65535) & 1;
7250
+ const top = checker ? pV : pU;
7251
+ const bot = checker ? pU : pV;
7252
+ return -max$1(top, bot * underScale) * depth;
7253
+ }
7254
+ function compileTypedSurfacePattern(pattern) {
7255
+ switch (pattern.kind) {
7256
+ case "surfacePattern:constant":
7257
+ return () => pattern.value;
7258
+ case "surfacePattern:sineWave": {
7259
+ const { direction: direction2, wavelength, amplitude, phase, bias } = pattern;
7260
+ const frequency = 2 * PI$1 / wavelength;
7261
+ return (u2, v) => bias + sin$1((u2 * direction2[0] + v * direction2[1]) * frequency + phase) * amplitude;
7262
+ }
7263
+ case "surfacePattern:stripes": {
7264
+ const { direction: direction2, spacing, width, depth } = pattern;
7265
+ return (u2, v) => evalStripesPattern(u2, v, direction2[0], direction2[1], spacing, width, depth);
7266
+ }
7267
+ case "surfacePattern:overUnderWeave": {
7268
+ const { spacing, threadWidth, depth, underScale } = pattern;
7269
+ return (u2, v) => evalOverUnderWeavePattern(u2, v, spacing[0], spacing[1], threadWidth[0], threadWidth[1], depth, underScale);
7270
+ }
7271
+ case "surfacePattern:abs": {
7272
+ const child = compileTypedSurfacePattern(pattern.child);
7273
+ return (u2, v) => abs(child(u2, v));
7274
+ }
7275
+ case "surfacePattern:negate": {
7276
+ const child = compileTypedSurfacePattern(pattern.child);
7277
+ return (u2, v) => -child(u2, v);
7278
+ }
7279
+ case "surfacePattern:add": {
7280
+ const children = pattern.children.map(compileTypedSurfacePattern);
7281
+ return (u2, v) => children.reduce((sum2, child) => sum2 + child(u2, v), 0);
7282
+ }
7283
+ case "surfacePattern:multiply": {
7284
+ const children = pattern.children.map(compileTypedSurfacePattern);
7285
+ return (u2, v) => children.reduce((product, child) => product * child(u2, v), 1);
7286
+ }
7287
+ case "surfacePattern:min": {
7288
+ const children = pattern.children.map(compileTypedSurfacePattern);
7289
+ if (children.length === 0) return () => 0;
7290
+ return (u2, v) => children.reduce((value, child) => min(value, child(u2, v)), Infinity);
7291
+ }
7292
+ case "surfacePattern:max": {
7293
+ const children = pattern.children.map(compileTypedSurfacePattern);
7294
+ if (children.length === 0) return () => 0;
7295
+ return (u2, v) => children.reduce((value, child) => max$1(value, child(u2, v)), -Infinity);
7296
+ }
7297
+ case "surfacePattern:clamp": {
7298
+ const child = compileTypedSurfacePattern(pattern.child);
7299
+ return (u2, v) => clamp$a(child(u2, v), pattern.min, pattern.max);
7300
+ }
7301
+ }
7302
+ }
7303
+ function estimateSurfacePatternAmplitude(pattern) {
7304
+ switch (pattern.kind) {
7305
+ case "surfacePattern:constant":
7306
+ return abs(pattern.value);
7307
+ case "surfacePattern:sineWave":
7308
+ return abs(pattern.bias) + abs(pattern.amplitude);
7309
+ case "surfacePattern:stripes":
7310
+ case "surfacePattern:overUnderWeave":
7311
+ return pattern.depth;
7312
+ case "surfacePattern:abs":
7313
+ case "surfacePattern:negate":
7314
+ return estimateSurfacePatternAmplitude(pattern.child);
7315
+ case "surfacePattern:add": {
7316
+ const amplitudes = pattern.children.map(estimateSurfacePatternAmplitude);
7317
+ return amplitudes.every((value) => value !== null) ? amplitudes.reduce((sum2, value) => sum2 + value, 0) : null;
7318
+ }
7319
+ case "surfacePattern:multiply": {
7320
+ const amplitudes = pattern.children.map(estimateSurfacePatternAmplitude);
7321
+ return amplitudes.every((value) => value !== null) ? amplitudes.reduce((product, value) => product * value, 1) : null;
7322
+ }
7323
+ case "surfacePattern:min":
7324
+ case "surfacePattern:max": {
7325
+ const amplitudes = pattern.children.map(estimateSurfacePatternAmplitude);
7326
+ return amplitudes.every((value) => value !== null) ? max$1(...amplitudes) : null;
7327
+ }
7328
+ case "surfacePattern:clamp":
7329
+ return max$1(abs(pattern.min), abs(pattern.max));
7330
+ }
7331
+ }
7332
+ function compileSurfacePattern(node) {
7333
+ if (node.pattern) return compileTypedSurfacePattern(node.pattern);
7334
+ const constEntries = Object.entries(node.constants ?? {});
7335
+ const constNames = constEntries.map(([k2]) => k2);
7336
+ const constValues = constEntries.map(([, v]) => v);
7337
+ const patternFn = new Function("u", "v", ...constNames, `return (${node.patternBody});`);
7338
+ return (u2, v) => patternFn(u2, v, ...constValues);
7339
+ }
6908
7340
  function compileSdfNode3(node) {
6909
7341
  switch (node.kind) {
6910
7342
  case "sdf:sphere": {
6911
7343
  const r = node.radius;
6912
- return (x2, y2, z2) => sdSphere(x2, y2, z2, r);
7344
+ return (x2, y2, z2) => sdSphere$1(x2, y2, z2, r);
6913
7345
  }
6914
7346
  case "sdf:box": {
6915
7347
  const [hx, hy, hz] = node.halfExtents;
6916
- return (x2, y2, z2) => sdBox(x2, y2, z2, hx, hy, hz);
7348
+ return (x2, y2, z2) => sdBox$1(x2, y2, z2, hx, hy, hz);
6917
7349
  }
6918
7350
  case "sdf:cylinder": {
6919
7351
  const { height: h, radius: r } = node;
6920
- return (x2, y2, z2) => sdCylinder(x2, y2, z2, h, r);
7352
+ return (x2, y2, z2) => sdCylinder$1(x2, y2, z2, h, r);
6921
7353
  }
6922
7354
  case "sdf:torus": {
6923
7355
  const { majorRadius: R, minorRadius: r } = node;
6924
- return (x2, y2, z2) => sdTorus(x2, y2, z2, R, r);
7356
+ return (x2, y2, z2) => sdTorus$1(x2, y2, z2, R, r);
6925
7357
  }
6926
7358
  case "sdf:capsule": {
6927
7359
  const { height: h, radius: r } = node;
6928
- return (x2, y2, z2) => sdCapsule(x2, y2, z2, h, r);
7360
+ return (x2, y2, z2) => sdCapsule$1(x2, y2, z2, h, r);
6929
7361
  }
6930
7362
  case "sdf:cone": {
6931
7363
  const { height: h, radius: r } = node;
6932
- return (x2, y2, z2) => sdCone(x2, y2, z2, h, r);
7364
+ return (x2, y2, z2) => sdCone$1(x2, y2, z2, h, r);
6933
7365
  }
6934
7366
  case "sdf:polylineSweep": {
6935
7367
  return (x2, y2, z2) => sdPolylineSweep3(node, x2, y2, z2);
@@ -6946,7 +7378,7 @@ function compileSdfNode3(node) {
6946
7378
  const fns = node.children.map(compileSdfNode3);
6947
7379
  return (x2, y2, z2) => {
6948
7380
  let d2 = fns[0](x2, y2, z2);
6949
- for (let i = 1; i < fns.length; i++) d2 = max(d2, -fns[i](x2, y2, z2));
7381
+ for (let i = 1; i < fns.length; i++) d2 = max$1(d2, -fns[i](x2, y2, z2));
6950
7382
  return d2;
6951
7383
  };
6952
7384
  }
@@ -6954,7 +7386,7 @@ function compileSdfNode3(node) {
6954
7386
  const fns = node.children.map(compileSdfNode3);
6955
7387
  return (x2, y2, z2) => {
6956
7388
  let d2 = fns[0](x2, y2, z2);
6957
- for (let i = 1; i < fns.length; i++) d2 = max(d2, fns[i](x2, y2, z2));
7389
+ for (let i = 1; i < fns.length; i++) d2 = max$1(d2, fns[i](x2, y2, z2));
6958
7390
  return d2;
6959
7391
  };
6960
7392
  }
@@ -6963,7 +7395,7 @@ function compileSdfNode3(node) {
6963
7395
  const k2 = node.radius;
6964
7396
  return (x2, y2, z2) => {
6965
7397
  let d2 = fns[0](x2, y2, z2);
6966
- for (let i = 1; i < fns.length; i++) d2 = smin(d2, fns[i](x2, y2, z2), k2);
7398
+ for (let i = 1; i < fns.length; i++) d2 = smin$1(d2, fns[i](x2, y2, z2), k2);
6967
7399
  return d2;
6968
7400
  };
6969
7401
  }
@@ -6972,7 +7404,7 @@ function compileSdfNode3(node) {
6972
7404
  const k2 = node.radius;
6973
7405
  return (x2, y2, z2) => {
6974
7406
  let d2 = fns[0](x2, y2, z2);
6975
- for (let i = 1; i < fns.length; i++) d2 = smax(d2, -fns[i](x2, y2, z2), k2);
7407
+ for (let i = 1; i < fns.length; i++) d2 = smax$1(d2, -fns[i](x2, y2, z2), k2);
6976
7408
  return d2;
6977
7409
  };
6978
7410
  }
@@ -6981,7 +7413,7 @@ function compileSdfNode3(node) {
6981
7413
  const k2 = node.radius;
6982
7414
  return (x2, y2, z2) => {
6983
7415
  let d2 = fns[0](x2, y2, z2);
6984
- for (let i = 1; i < fns.length; i++) d2 = smax(d2, fns[i](x2, y2, z2), k2);
7416
+ for (let i = 1; i < fns.length; i++) d2 = smax$1(d2, fns[i](x2, y2, z2), k2);
6985
7417
  return d2;
6986
7418
  };
6987
7419
  }
@@ -6999,10 +7431,10 @@ function compileSdfNode3(node) {
6999
7431
  }
7000
7432
  case "sdf:rotate": {
7001
7433
  const fn = compileSdfNode3(node.child);
7002
- const [rx, ry, rz] = node.degrees.map((d2) => d2 * DEG$1);
7003
- const cx = cos(rx), sx = sin(rx);
7004
- const cy = cos(ry), sy = sin(ry);
7005
- const cz = cos(rz), sz = sin(rz);
7434
+ const [rx, ry, rz] = node.degrees.map((d2) => d2 * DEG$2);
7435
+ const cx = cos$1(rx), sx = sin$1(rx);
7436
+ const cy = cos$1(ry), sy = sin$1(ry);
7437
+ const cz = cos$1(rz), sz = sin$1(rz);
7006
7438
  return (x2, y2, z2) => {
7007
7439
  const x1 = cz * x2 + sz * y2;
7008
7440
  const y1 = -sz * x2 + cz * y2;
@@ -7021,10 +7453,10 @@ function compileSdfNode3(node) {
7021
7453
  }
7022
7454
  case "sdf:twist": {
7023
7455
  const fn = compileSdfNode3(node.child);
7024
- const k2 = node.degreesPerUnit * DEG$1;
7456
+ const k2 = node.degreesPerUnit * DEG$2;
7025
7457
  return (x2, y2, z2) => {
7026
7458
  const angle = k2 * z2;
7027
- const c2 = cos(angle), s = sin(angle);
7459
+ const c2 = cos$1(angle), s = sin$1(angle);
7028
7460
  return fn(c2 * x2 - s * y2, s * x2 + c2 * y2, z2);
7029
7461
  };
7030
7462
  }
@@ -7033,7 +7465,7 @@ function compileSdfNode3(node) {
7033
7465
  const r = node.radius;
7034
7466
  return (x2, y2, z2) => {
7035
7467
  const angle = x2 / r;
7036
- const c2 = cos(angle), s = sin(angle);
7468
+ const c2 = cos$1(angle), s = sin$1(angle);
7037
7469
  return fn((r + y2) * s, (r + y2) * c2 - r, z2);
7038
7470
  };
7039
7471
  }
@@ -7041,7 +7473,19 @@ function compileSdfNode3(node) {
7041
7473
  const fn = compileSdfNode3(node.child);
7042
7474
  const [sx, sy, sz] = node.spacing;
7043
7475
  const [cx, cy, cz] = node.count;
7044
- return (x2, y2, z2) => fn(repeatCoord(x2, sx, cx), repeatCoord(y2, sy, cy), repeatCoord(z2, sz, cz));
7476
+ return (x2, y2, z2) => fn(repeatCoord$1(x2, sx, cx), repeatCoord$1(y2, sy, cy), repeatCoord$1(z2, sz, cz));
7477
+ }
7478
+ case "sdf:circularArray": {
7479
+ const fn = compileSdfNode3(node.child);
7480
+ const da = 2 * PI$1 / node.count;
7481
+ const offset2 = node.offset;
7482
+ return (x2, y2, z2) => {
7483
+ const r = length2$1(x2, y2);
7484
+ const a2 = positiveMod(Math.atan2(y2, x2), da);
7485
+ const d1 = fn(cos$1(a2 - da) * r - offset2, sin$1(a2 - da) * r, z2);
7486
+ const d2 = fn(cos$1(a2) * r - offset2, sin$1(a2) * r, z2);
7487
+ return min(d1, d2);
7488
+ };
7045
7489
  }
7046
7490
  case "sdf:shell": {
7047
7491
  const fn = compileSdfNode3(node.child);
@@ -7058,10 +7502,7 @@ function compileSdfNode3(node) {
7058
7502
  }
7059
7503
  case "sdf:surfaceDisplace": {
7060
7504
  const childFn = compileSdfNode3(node.child);
7061
- const constEntries = Object.entries(node.constants ?? {});
7062
- const constNames = constEntries.map(([k2]) => k2);
7063
- const constValues = constEntries.map(([, v]) => v);
7064
- const patternFn = new Function("u", "v", ...constNames, `return (${node.patternBody});`);
7505
+ const patternFn = compileSurfacePattern(node);
7065
7506
  const uvMode = node.uvMode && node.uvMode !== "auto" ? node.uvMode : void 0;
7066
7507
  const analysis = analyzeUV(node.child, uvMode);
7067
7508
  const uvFn = compileUVFunction(analysis);
@@ -7073,7 +7514,7 @@ function compileSdfNode3(node) {
7073
7514
  p2[1] = y2;
7074
7515
  p2[2] = z2;
7075
7516
  const [u2, v] = uvFn(p2);
7076
- return d2 + patternFn(u2, v, ...constValues);
7517
+ return d2 + patternFn(u2, v);
7077
7518
  };
7078
7519
  }
7079
7520
  const sharpness = node.triplanarSharpness ?? 4;
@@ -7084,9 +7525,9 @@ function compileSdfNode3(node) {
7084
7525
  const gy = childFn(x2, y2 + eps, z2) - childFn(x2, y2 - eps, z2);
7085
7526
  const gz = childFn(x2, y2, z2 + eps) - childFn(x2, y2, z2 - eps);
7086
7527
  const { wx, wy, wz } = triplanarWeights(gx, gy, gz, sharpness);
7087
- const hX = patternFn(y2, z2, ...constValues);
7088
- const hY = patternFn(x2, z2, ...constValues);
7089
- const hZ = patternFn(x2, y2, ...constValues);
7528
+ const hX = patternFn(y2, z2);
7529
+ const hY = patternFn(x2, z2);
7530
+ const hZ = patternFn(x2, y2);
7090
7531
  return d2 + wx * hX + wy * hY + wz * hZ;
7091
7532
  };
7092
7533
  }
@@ -7156,7 +7597,7 @@ function compileSdfNode3(node) {
7156
7597
  const gx = gradFn(x2 + eps, y2, z2) - gradFn(x2 - eps, y2, z2);
7157
7598
  const gy = gradFn(x2, y2 + eps, z2) - gradFn(x2, y2 - eps, z2);
7158
7599
  const gz = gradFn(x2, y2, z2 + eps) - gradFn(x2, y2, z2 - eps);
7159
- const glen = sqrt(gx * gx + gy * gy + gz * gz);
7600
+ const glen = sqrt$1(gx * gx + gy * gy + gz * gz);
7160
7601
  let nx = 0, ny = 0, nz = 0;
7161
7602
  if (glen > 1e-10) {
7162
7603
  const invG = 1 / glen;
@@ -7218,7 +7659,7 @@ function estimateSdfBounds(node) {
7218
7659
  for (const point2 of node.points) {
7219
7660
  for (let i = 0; i < 3; i++) {
7220
7661
  minPoint[i] = min(minPoint[i], point2[i]);
7221
- maxPoint[i] = max(maxPoint[i], point2[i]);
7662
+ maxPoint[i] = max$1(maxPoint[i], point2[i]);
7222
7663
  }
7223
7664
  }
7224
7665
  return padBounds({ min: minPoint, max: maxPoint }, pad);
@@ -7249,7 +7690,7 @@ function estimateSdfBounds(node) {
7249
7690
  }
7250
7691
  case "sdf:rotate": {
7251
7692
  const b = estimateSdfBounds(node.child);
7252
- const r = length3(max(abs(b.min[0]), abs(b.max[0])), max(abs(b.min[1]), abs(b.max[1])), max(abs(b.min[2]), abs(b.max[2])));
7693
+ const r = length3$1(max$1(abs(b.min[0]), abs(b.max[0])), max$1(abs(b.min[1]), abs(b.max[1])), max$1(abs(b.min[2]), abs(b.max[2])));
7253
7694
  return { min: [-r, -r, -r], max: [r, r, r] };
7254
7695
  }
7255
7696
  case "sdf:scale": {
@@ -7263,7 +7704,7 @@ function estimateSdfBounds(node) {
7263
7704
  case "sdf:twist":
7264
7705
  case "sdf:bend": {
7265
7706
  const b = estimateSdfBounds(node.child);
7266
- const r = length3(max(abs(b.min[0]), abs(b.max[0])), max(abs(b.min[1]), abs(b.max[1])), max(abs(b.min[2]), abs(b.max[2]))) * 1.5;
7707
+ const r = length3$1(max$1(abs(b.min[0]), abs(b.max[0])), max$1(abs(b.min[1]), abs(b.max[1])), max$1(abs(b.min[2]), abs(b.max[2]))) * 1.5;
7267
7708
  return { min: [-r, -r, -r], max: [r, r, r] };
7268
7709
  }
7269
7710
  case "sdf:repeat": {
@@ -7284,16 +7725,29 @@ function estimateSdfBounds(node) {
7284
7725
  const [zMin, zMax] = expand(sz, cz, b.min[2], b.max[2]);
7285
7726
  return { min: [xMin, yMin, zMin], max: [xMax, yMax, zMax] };
7286
7727
  }
7728
+ case "sdf:circularArray": {
7729
+ const b = estimateSdfBounds(node.child);
7730
+ const x0 = b.min[0] + node.offset;
7731
+ const x1 = b.max[0] + node.offset;
7732
+ const y0 = b.min[1];
7733
+ const y1 = b.max[1];
7734
+ const r = max$1(length2$1(x0, y0), length2$1(x0, y1), length2$1(x1, y0), length2$1(x1, y1));
7735
+ return { min: [-r, -r, b.min[2]], max: [r, r, b.max[2]] };
7736
+ }
7287
7737
  case "sdf:shell": {
7288
7738
  const b = estimateSdfBounds(node.child);
7289
7739
  const t = node.thickness * 0.5;
7290
7740
  return padBounds(b, t);
7291
7741
  }
7292
- case "sdf:displace":
7293
- case "sdf:surfaceDisplace": {
7742
+ case "sdf:displace": {
7294
7743
  const b = estimateSdfBounds(node.child);
7295
7744
  return padBounds(b, 5);
7296
7745
  }
7746
+ case "sdf:surfaceDisplace": {
7747
+ const b = estimateSdfBounds(node.child);
7748
+ if (!node.pattern) return padBounds(b, 5);
7749
+ return padBounds(b, estimateSurfacePatternAmplitude(node.pattern) ?? 5);
7750
+ }
7297
7751
  case "sdf:onion": {
7298
7752
  const b = estimateSdfBounds(node.child);
7299
7753
  return padBounds(b, node.layers * node.thickness);
@@ -7330,7 +7784,7 @@ function unionBounds(bounds, pad) {
7330
7784
  for (const b of bounds) {
7331
7785
  for (let i = 0; i < 3; i++) {
7332
7786
  result.min[i] = min(result.min[i], b.min[i]);
7333
- result.max[i] = max(result.max[i], b.max[i]);
7787
+ result.max[i] = max$1(result.max[i], b.max[i]);
7334
7788
  }
7335
7789
  }
7336
7790
  if (pad > 0) return padBounds(result, pad);
@@ -7343,7 +7797,7 @@ function intersectBounds(bounds, pad) {
7343
7797
  };
7344
7798
  for (const b of bounds) {
7345
7799
  for (let i = 0; i < 3; i++) {
7346
- result.min[i] = max(result.min[i], b.min[i]);
7800
+ result.min[i] = max$1(result.min[i], b.min[i]);
7347
7801
  result.max[i] = min(result.max[i], b.max[i]);
7348
7802
  }
7349
7803
  }
@@ -7359,242 +7813,544 @@ function padBounds(b, pad) {
7359
7813
  max: [b.max[0] + pad, b.max[1] + pad, b.max[2] + pad]
7360
7814
  };
7361
7815
  }
7362
- const DEFAULT_MAX_GRID_POINTS = 8e6;
7363
- const DEFAULT_MIN_EDGE_LENGTH = 0.15;
7364
- function resolveSdfMeshingSettings(tree, bounds, options = {}) {
7365
- const quality = options.quality ?? "preview";
7366
- const minEdgeLength = positiveOrDefault(options.minEdgeLength, DEFAULT_MIN_EDGE_LENGTH);
7367
- const maxGridPoints = positiveOrDefault(options.maxGridPoints, DEFAULT_MAX_GRID_POINTS);
7368
- const tolerance = options.tolerance !== void 0 ? requirePositiveFinite$2(options.tolerance, "SDF tolerance") : void 0;
7369
- const minFeatureSize = options.minFeatureSize !== void 0 ? requirePositiveFinite$2(options.minFeatureSize, "SDF minFeatureSize") : void 0;
7370
- const maxTriangles = options.maxTriangles !== void 0 ? Math.floor(requirePositiveFinite$2(options.maxTriangles, "SDF maxTriangles")) : void 0;
7371
- const analysis = analyzeSdfTree(tree);
7372
- const warnings = [];
7373
- let edgeLength2;
7374
- if (options.edgeLength !== void 0) {
7375
- edgeLength2 = requirePositiveFinite$2(options.edgeLength, "SDF edgeLength");
7376
- if (edgeLength2 < minEdgeLength) {
7377
- warnings.push(`edgeLength ${formatMm(edgeLength2)} was clamped to minimum ${formatMm(minEdgeLength)}.`);
7378
- edgeLength2 = minEdgeLength;
7379
- }
7380
- } else {
7381
- edgeLength2 = resolveDefaultEdgeLength(bounds, quality, minEdgeLength, analysis, options);
7816
+ const { PI } = Math;
7817
+ const DEG$1 = PI / 180;
7818
+ const TAU = 2 * PI;
7819
+ const GRAD_EPS = 1e-9;
7820
+ const Op = {
7821
+ Const: 0,
7822
+ Neg: 1,
7823
+ Abs: 2,
7824
+ Sqrt: 3,
7825
+ Sin: 4,
7826
+ Cos: 5,
7827
+ Round: 6,
7828
+ Add: 7,
7829
+ Sub: 8,
7830
+ Mul: 9,
7831
+ Div: 10,
7832
+ Min: 11,
7833
+ Max: 12
7834
+ };
7835
+ class UnsupportedSdfProgramNodeError extends Error {
7836
+ constructor(message) {
7837
+ super(message);
7838
+ this.name = "UnsupportedSdfProgramNodeError";
7382
7839
  }
7383
- if (analysis.minWallThickness < Infinity && analysis.minWallThickness < edgeLength2 * 2) {
7384
- analysis.riskFlags.add("thin-shell");
7385
- warnings.push(
7386
- `shell/wall thickness ${formatMm(analysis.minWallThickness)} is below 2 x edgeLength ${formatMm(edgeLength2)}; thin features may be under-sampled.`
7387
- );
7840
+ }
7841
+ class SdfProgramBuilder {
7842
+ constructor() {
7843
+ __publicField(this, "opcodes", []);
7844
+ __publicField(this, "argA", []);
7845
+ __publicField(this, "argB", []);
7846
+ __publicField(this, "argC", []);
7847
+ __publicField(this, "constants", []);
7848
+ __publicField(this, "x", 0);
7849
+ __publicField(this, "y", 1);
7850
+ __publicField(this, "z", 2);
7388
7851
  }
7389
- if (!options.bounds && analysis.hasInfiniteRepeat) {
7390
- warnings.push("infinite repeat bounds are heuristic; pass .toShape({ bounds }) for predictable clipping.");
7852
+ constant(value) {
7853
+ const index2 = this.constants.length;
7854
+ this.constants.push(value);
7855
+ return this.push(Op.Const, 0, 0, index2);
7391
7856
  }
7392
- if (!options.bounds && analysis.riskFlags.has("noise")) {
7393
- warnings.push("noise field bounds are heuristic; pass .toShape({ bounds }) for predictable clipping.");
7857
+ neg(a2) {
7858
+ return this.push(Op.Neg, a2);
7394
7859
  }
7395
- if (!options.bounds && (analysis.riskFlags.has("tpms") || analysis.riskFlags.has("voronoi"))) {
7396
- warnings.push("TPMS/Voronoi bounds are heuristic unless clipped or passed explicitly.");
7860
+ abs(a2) {
7861
+ return this.push(Op.Abs, a2);
7397
7862
  }
7398
- if (analysis.hasLegacyTpmsThreshold) {
7399
- warnings.push("TPMS thickness is using legacy field-threshold units; use wallThickness for approximate millimeters.");
7863
+ sqrt(a2) {
7864
+ return this.push(Op.Sqrt, a2);
7865
+ }
7866
+ sin(a2) {
7867
+ return this.push(Op.Sin, a2);
7868
+ }
7869
+ cos(a2) {
7870
+ return this.push(Op.Cos, a2);
7871
+ }
7872
+ round(a2) {
7873
+ return this.push(Op.Round, a2);
7874
+ }
7875
+ add(a2, b) {
7876
+ return this.push(Op.Add, a2, b);
7877
+ }
7878
+ sub(a2, b) {
7879
+ return this.push(Op.Sub, a2, b);
7880
+ }
7881
+ mul(a2, b) {
7882
+ return this.push(Op.Mul, a2, b);
7883
+ }
7884
+ div(a2, b) {
7885
+ return this.push(Op.Div, a2, b);
7886
+ }
7887
+ min(a2, b) {
7888
+ return this.push(Op.Min, a2, b);
7889
+ }
7890
+ max(a2, b) {
7891
+ return this.push(Op.Max, a2, b);
7892
+ }
7893
+ finalize(output) {
7894
+ return {
7895
+ opcodes: Uint8Array.from(this.opcodes),
7896
+ argA: Int32Array.from(this.argA),
7897
+ argB: Int32Array.from(this.argB),
7898
+ argC: Int32Array.from(this.argC),
7899
+ constants: Float64Array.from(this.constants),
7900
+ output,
7901
+ slotCount: this.opcodes.length + 3
7902
+ };
7903
+ }
7904
+ push(op, a2 = 0, b = 0, c2 = 0) {
7905
+ const slot2 = this.opcodes.length + 3;
7906
+ this.opcodes.push(op);
7907
+ this.argA.push(a2);
7908
+ this.argB.push(b);
7909
+ this.argC.push(c2);
7910
+ return slot2;
7911
+ }
7912
+ }
7913
+ function compileSdfProgramEvaluator3(program) {
7914
+ const lines = ["const { abs, sqrt, sin, cos, round, min, max } = Math;", "let v0 = x;", "let v1 = y;", "let v2 = z;"];
7915
+ const { opcodes, argA, argB, argC, constants, output } = program;
7916
+ for (let i = 0; i < opcodes.length; i++) {
7917
+ const slot2 = `v${i + 3}`;
7918
+ const a2 = `v${argA[i]}`;
7919
+ const b = `v${argB[i]}`;
7920
+ switch (opcodes[i]) {
7921
+ case Op.Const:
7922
+ lines.push(`let ${slot2} = ${numberLiteral(constants[argC[i]])};`);
7923
+ break;
7924
+ case Op.Neg:
7925
+ lines.push(`let ${slot2} = -${a2};`);
7926
+ break;
7927
+ case Op.Abs:
7928
+ lines.push(`let ${slot2} = abs(${a2});`);
7929
+ break;
7930
+ case Op.Sqrt:
7931
+ lines.push(`let ${slot2} = sqrt(${a2});`);
7932
+ break;
7933
+ case Op.Sin:
7934
+ lines.push(`let ${slot2} = sin(${a2});`);
7935
+ break;
7936
+ case Op.Cos:
7937
+ lines.push(`let ${slot2} = cos(${a2});`);
7938
+ break;
7939
+ case Op.Round:
7940
+ lines.push(`let ${slot2} = round(${a2});`);
7941
+ break;
7942
+ case Op.Add:
7943
+ lines.push(`let ${slot2} = ${a2} + ${b};`);
7944
+ break;
7945
+ case Op.Sub:
7946
+ lines.push(`let ${slot2} = ${a2} - ${b};`);
7947
+ break;
7948
+ case Op.Mul:
7949
+ lines.push(`let ${slot2} = ${a2} * ${b};`);
7950
+ break;
7951
+ case Op.Div:
7952
+ lines.push(`let ${slot2} = ${a2} / ${b};`);
7953
+ break;
7954
+ case Op.Min:
7955
+ lines.push(`let ${slot2} = min(${a2}, ${b});`);
7956
+ break;
7957
+ case Op.Max:
7958
+ lines.push(`let ${slot2} = max(${a2}, ${b});`);
7959
+ break;
7960
+ default:
7961
+ throw new Error(`Unknown SdfProgram opcode ${opcodes[i]} at instruction ${i}.`);
7962
+ }
7400
7963
  }
7964
+ lines.push(`return v${output};`);
7965
+ return new Function("Math", `return function sdfProgramEval(x, y, z) {
7966
+ ${lines.join("\n")}
7967
+ };`)(Math);
7968
+ }
7969
+ function numberLiteral(value) {
7970
+ if (Number.isNaN(value)) return "NaN";
7971
+ if (value === Number.POSITIVE_INFINITY) return "Infinity";
7972
+ if (value === Number.NEGATIVE_INFINITY) return "-Infinity";
7973
+ return String(value);
7974
+ }
7975
+ function clampSlot(b, v, lo, hi) {
7976
+ return b.min(b.max(v, b.constant(lo)), b.constant(hi));
7977
+ }
7978
+ function length2(b, x2, y2) {
7979
+ return b.sqrt(b.add(b.mul(x2, x2), b.mul(y2, y2)));
7980
+ }
7981
+ function length3(b, x2, y2, z2) {
7982
+ return b.sqrt(b.add(b.add(b.mul(x2, x2), b.mul(y2, y2)), b.mul(z2, z2)));
7983
+ }
7984
+ function smin(b, a2, child, k2) {
7985
+ if (k2 <= 0) return b.min(a2, child);
7986
+ const h = b.div(b.max(b.sub(b.constant(k2), b.abs(b.sub(a2, child))), b.constant(0)), b.constant(k2));
7987
+ return b.sub(b.min(a2, child), b.mul(b.mul(b.mul(h, h), h), b.constant(k2 / 6)));
7988
+ }
7989
+ function smax(b, a2, child, k2) {
7990
+ return b.neg(smin(b, b.neg(a2), b.neg(child), k2));
7991
+ }
7992
+ function emitScaledTrig(b, x2, y2, z2, cellSize) {
7993
+ const s = b.constant(TAU / cellSize);
7994
+ const xs = b.mul(x2, s);
7995
+ const ys = b.mul(y2, s);
7996
+ const zs = b.mul(z2, s);
7401
7997
  return {
7402
- quality,
7403
- edgeLength: edgeLength2,
7404
- tolerance,
7405
- minFeatureSize,
7406
- minEdgeLength,
7407
- simplify: resolveSimplificationMode(options.simplify, quality, analysis.riskFlags),
7408
- maxTriangles,
7409
- maxGridPoints,
7410
- diagnostics: options.diagnostics === true,
7411
- treeRiskFlags: [...analysis.riskFlags].sort(),
7412
- warnings
7998
+ scale: s,
7999
+ sx: b.sin(xs),
8000
+ sy: b.sin(ys),
8001
+ sz: b.sin(zs),
8002
+ cx: b.cos(xs),
8003
+ cy: b.cos(ys),
8004
+ cz: b.cos(zs),
8005
+ sx2: b.sin(b.mul(b.constant(2), xs)),
8006
+ sy2: b.sin(b.mul(b.constant(2), ys)),
8007
+ sz2: b.sin(b.mul(b.constant(2), zs)),
8008
+ cx2: b.cos(b.mul(b.constant(2), xs)),
8009
+ cy2: b.cos(b.mul(b.constant(2), ys)),
8010
+ cz2: b.cos(b.mul(b.constant(2), zs))
7413
8011
  };
7414
8012
  }
7415
- function withScaledSdfEdgeLength(settings, edgeLength2) {
7416
- return { ...settings, edgeLength: Math.max(settings.minEdgeLength, edgeLength2) };
8013
+ function emitGyroidValueAndGradient(b, x2, y2, z2, cellSize) {
8014
+ const t = emitScaledTrig(b, x2, y2, z2, cellSize);
8015
+ return {
8016
+ value: b.add(b.add(b.mul(t.sx, t.cy), b.mul(t.sy, t.cz)), b.mul(t.sz, t.cx)),
8017
+ gx: b.mul(t.scale, b.sub(b.mul(t.cx, t.cy), b.mul(t.sz, t.sx))),
8018
+ gy: b.mul(t.scale, b.add(b.neg(b.mul(t.sx, t.sy)), b.mul(t.cy, t.cz))),
8019
+ gz: b.mul(t.scale, b.add(b.neg(b.mul(t.sy, t.sz)), b.mul(t.cz, t.cx)))
8020
+ };
7417
8021
  }
7418
- function createSdfMeshingDiagnostics(settings, bounds, paddedBounds) {
7419
- const grid = estimateSdfGridDimensions(paddedBounds, settings.edgeLength);
7420
- const estimatedSamples = grid[0] * grid[1] * grid[2];
8022
+ function emitSchwarzPValueAndGradient(b, x2, y2, z2, cellSize) {
8023
+ const t = emitScaledTrig(b, x2, y2, z2, cellSize);
7421
8024
  return {
7422
- bounds: cloneBounds$2(bounds),
7423
- paddedBounds: cloneBounds$2(paddedBounds),
7424
- edgeLength: settings.edgeLength,
7425
- grid,
7426
- estimatedSamples,
7427
- estimatedMemoryBytes: estimatedSamples * 8,
7428
- treeRiskFlags: [...settings.treeRiskFlags],
7429
- simplification: settings.simplify,
7430
- capMode: "box",
7431
- capInset: settings.edgeLength,
7432
- warnings: [...settings.warnings]
8025
+ value: b.add(b.add(t.cx, t.cy), t.cz),
8026
+ gx: b.neg(b.mul(t.scale, t.sx)),
8027
+ gy: b.neg(b.mul(t.scale, t.sy)),
8028
+ gz: b.neg(b.mul(t.scale, t.sz))
7433
8029
  };
7434
8030
  }
7435
- function assertSdfMeshingBudget(diagnostics, maxGridPoints) {
7436
- if (diagnostics.estimatedSamples <= maxGridPoints) return;
7437
- const suggestedEdge = suggestEdgeLengthForSampleBudget(diagnostics.paddedBounds, maxGridPoints);
7438
- throw new Error(
7439
- `SDF meshing would sample ${formatCount(diagnostics.estimatedSamples)} grid points (~${formatBytes(
7440
- diagnostics.estimatedMemoryBytes
7441
- )}). Reduce bounds or use edgeLength >= ${formatMm(suggestedEdge)}.`
8031
+ function emitDiamondValueAndGradient(b, x2, y2, z2, cellSize) {
8032
+ const t = emitScaledTrig(b, x2, y2, z2, cellSize);
8033
+ return {
8034
+ value: b.add(
8035
+ b.add(b.mul(b.mul(t.sx, t.sy), t.sz), b.mul(b.mul(t.sx, t.cy), t.cz)),
8036
+ b.add(b.mul(b.mul(t.cx, t.sy), t.cz), b.mul(b.mul(t.cx, t.cy), t.sz))
8037
+ ),
8038
+ gx: b.mul(
8039
+ t.scale,
8040
+ b.add(
8041
+ b.add(b.mul(b.mul(t.cx, t.sy), t.sz), b.mul(b.mul(t.cx, t.cy), t.cz)),
8042
+ b.add(b.neg(b.mul(b.mul(t.sx, t.sy), t.cz)), b.neg(b.mul(b.mul(t.sx, t.cy), t.sz)))
8043
+ )
8044
+ ),
8045
+ gy: b.mul(
8046
+ t.scale,
8047
+ b.add(
8048
+ b.add(b.mul(b.mul(t.sx, t.cy), t.sz), b.neg(b.mul(b.mul(t.sx, t.sy), t.cz))),
8049
+ b.add(b.mul(b.mul(t.cx, t.cy), t.cz), b.neg(b.mul(b.mul(t.cx, t.sy), t.sz)))
8050
+ )
8051
+ ),
8052
+ gz: b.mul(
8053
+ t.scale,
8054
+ b.add(
8055
+ b.add(b.mul(b.mul(t.sx, t.sy), t.cz), b.neg(b.mul(b.mul(t.sx, t.cy), t.sz))),
8056
+ b.add(b.neg(b.mul(b.mul(t.cx, t.sy), t.sz)), b.mul(b.mul(t.cx, t.cy), t.cz))
8057
+ )
8058
+ )
8059
+ };
8060
+ }
8061
+ function emitLidinoidValueAndGradient(b, x2, y2, z2, cellSize) {
8062
+ const t = emitScaledTrig(b, x2, y2, z2, cellSize);
8063
+ const value = b.add(
8064
+ b.sub(
8065
+ b.add(b.add(b.mul(b.mul(t.sx2, t.cy), t.sz), b.mul(b.mul(t.sy2, t.cz), t.sx)), b.mul(b.mul(t.sz2, t.cx), t.sy)),
8066
+ b.add(b.add(b.mul(t.cx2, t.cy2), b.mul(t.cy2, t.cz2)), b.mul(t.cz2, t.cx2))
8067
+ ),
8068
+ b.constant(0.3)
7442
8069
  );
8070
+ return {
8071
+ value,
8072
+ gx: b.mul(
8073
+ t.scale,
8074
+ b.add(
8075
+ b.add(
8076
+ b.add(b.mul(b.mul(b.constant(2), t.cx2), b.mul(t.cy, t.sz)), b.mul(b.mul(t.sy2, t.cz), t.cx)),
8077
+ b.neg(b.mul(b.mul(t.sz2, t.sx), t.sy))
8078
+ ),
8079
+ b.add(b.mul(b.mul(b.constant(2), t.sx2), t.cy2), b.mul(b.mul(b.constant(2), t.cz2), t.sx2))
8080
+ )
8081
+ ),
8082
+ gy: b.mul(
8083
+ t.scale,
8084
+ b.add(
8085
+ b.add(
8086
+ b.add(b.neg(b.mul(b.mul(t.sx2, t.sy), t.sz)), b.mul(b.mul(b.constant(2), t.cy2), b.mul(t.cz, t.sx))),
8087
+ b.mul(b.mul(t.sz2, t.cx), t.cy)
8088
+ ),
8089
+ b.add(b.mul(b.mul(b.constant(2), t.cx2), t.sy2), b.mul(b.mul(b.constant(2), t.sy2), t.cz2))
8090
+ )
8091
+ ),
8092
+ gz: b.mul(
8093
+ t.scale,
8094
+ b.add(
8095
+ b.add(
8096
+ b.add(b.mul(b.mul(t.sx2, t.cy), t.cz), b.neg(b.mul(b.mul(t.sy2, t.sz), t.sx))),
8097
+ b.mul(b.mul(b.constant(2), t.cz2), b.mul(t.cx, t.sy))
8098
+ ),
8099
+ b.add(b.mul(b.mul(b.constant(2), t.cy2), t.sz2), b.mul(b.mul(b.constant(2), t.sz2), t.cx2))
8100
+ )
8101
+ )
8102
+ };
7443
8103
  }
7444
- function estimateSdfGridDimensions(bounds, edgeLength2) {
7445
- const dx = bounds.max[0] - bounds.min[0];
7446
- const dy = bounds.max[1] - bounds.min[1];
7447
- const dz = bounds.max[2] - bounds.min[2];
7448
- return [
7449
- Math.max(2, Math.ceil(dx / edgeLength2) + 1),
7450
- Math.max(2, Math.ceil(dy / edgeLength2) + 1),
7451
- Math.max(2, Math.ceil(dz / edgeLength2) + 1)
7452
- ];
8104
+ function emitTpmsDistance(b, { value, gx, gy, gz }, thickness, thicknessMode) {
8105
+ if (thicknessMode !== "metric-approx") return b.sub(b.abs(value), b.constant(thickness));
8106
+ const grad = length3(b, gx, gy, gz);
8107
+ return b.sub(b.div(b.abs(value), b.max(grad, b.constant(GRAD_EPS))), b.constant(thickness * 0.5));
7453
8108
  }
7454
- function logSdfMeshingDiagnostics(prefix, diagnostics) {
7455
- const warnings = diagnostics.warnings.length > 0 ? `, warnings=${diagnostics.warnings.join(" | ")}` : "";
7456
- console.info(
7457
- `${prefix}: bounds=${formatBounds(diagnostics.bounds)}, paddedBounds=${formatBounds(diagnostics.paddedBounds)}, edgeLength=${formatMm(diagnostics.edgeLength)}, grid=${diagnostics.grid.join("x")}, estimatedSamples=${formatCount(diagnostics.estimatedSamples)}, treeRisk=${diagnostics.treeRiskFlags.join("+") || "none"}, simplify=${diagnostics.simplification}, capMode=${diagnostics.capMode}, capInset=${formatMm(diagnostics.capInset)}${warnings}`
7458
- );
8109
+ const { cos, max, sin, sqrt } = Math;
8110
+ function emitSdfProgramNode(b, node, x2, y2, z2) {
8111
+ switch (node.kind) {
8112
+ case "sdf:sphere":
8113
+ return sdSphere(b, x2, y2, z2, node.radius);
8114
+ case "sdf:box": {
8115
+ const [hx, hy, hz] = node.halfExtents;
8116
+ return sdBox(b, x2, y2, z2, hx, hy, hz);
8117
+ }
8118
+ case "sdf:cylinder":
8119
+ return sdCylinder(b, x2, y2, z2, node.height, node.radius);
8120
+ case "sdf:torus":
8121
+ return sdTorus(b, x2, y2, z2, node.majorRadius, node.minorRadius);
8122
+ case "sdf:capsule":
8123
+ return sdCapsule(b, x2, y2, z2, node.height, node.radius);
8124
+ case "sdf:cone":
8125
+ return sdCone(b, x2, y2, z2, node.height, node.radius);
8126
+ case "sdf:polylineSweep":
8127
+ return sdPolylineSweep(b, node, x2, y2, z2);
8128
+ case "sdf:union":
8129
+ return foldChildren$1(b, node.children, x2, y2, z2, (a2, child) => b.min(a2, child));
8130
+ case "sdf:difference":
8131
+ return foldChildren$1(b, node.children, x2, y2, z2, (a2, child) => b.max(a2, b.neg(child)));
8132
+ case "sdf:intersection":
8133
+ return foldChildren$1(b, node.children, x2, y2, z2, (a2, child) => b.max(a2, child));
8134
+ case "sdf:smoothUnion":
8135
+ return foldChildren$1(b, node.children, x2, y2, z2, (a2, child) => smin(b, a2, child, node.radius));
8136
+ case "sdf:smoothDifference":
8137
+ return foldChildren$1(b, node.children, x2, y2, z2, (a2, child) => smax(b, a2, b.neg(child), node.radius));
8138
+ case "sdf:smoothIntersection":
8139
+ return foldChildren$1(b, node.children, x2, y2, z2, (a2, child) => smax(b, a2, child, node.radius));
8140
+ case "sdf:morph": {
8141
+ const a2 = emitSdfProgramNode(b, node.a, x2, y2, z2);
8142
+ const childB = emitSdfProgramNode(b, node.b, x2, y2, z2);
8143
+ return b.add(b.mul(a2, b.constant(1 - node.t)), b.mul(childB, b.constant(node.t)));
8144
+ }
8145
+ case "sdf:translate": {
8146
+ const [ox, oy, oz] = node.offset;
8147
+ return emitSdfProgramNode(b, node.child, b.sub(x2, b.constant(ox)), b.sub(y2, b.constant(oy)), b.sub(z2, b.constant(oz)));
8148
+ }
8149
+ case "sdf:rotate":
8150
+ return emitRotated(b, node, x2, y2, z2);
8151
+ case "sdf:scale": {
8152
+ const inv = 1 / node.factor;
8153
+ const child = emitSdfProgramNode(b, node.child, b.mul(x2, b.constant(inv)), b.mul(y2, b.constant(inv)), b.mul(z2, b.constant(inv)));
8154
+ return b.mul(child, b.constant(node.factor));
8155
+ }
8156
+ case "sdf:twist": {
8157
+ const angle = b.mul(b.constant(node.degreesPerUnit * DEG$1), z2);
8158
+ const c2 = b.cos(angle);
8159
+ const s = b.sin(angle);
8160
+ return emitSdfProgramNode(b, node.child, b.sub(b.mul(c2, x2), b.mul(s, y2)), b.add(b.mul(s, x2), b.mul(c2, y2)), z2);
8161
+ }
8162
+ case "sdf:bend": {
8163
+ const angle = b.div(x2, b.constant(node.radius));
8164
+ const c2 = b.cos(angle);
8165
+ const s = b.sin(angle);
8166
+ const radiusPlusY = b.add(b.constant(node.radius), y2);
8167
+ return emitSdfProgramNode(b, node.child, b.mul(radiusPlusY, s), b.sub(b.mul(radiusPlusY, c2), b.constant(node.radius)), z2);
8168
+ }
8169
+ case "sdf:repeat": {
8170
+ const [sx, sy, sz] = node.spacing;
8171
+ const [cx, cy, cz] = node.count;
8172
+ return emitSdfProgramNode(b, node.child, repeatCoord(b, x2, sx, cx), repeatCoord(b, y2, sy, cy), repeatCoord(b, z2, sz, cz));
8173
+ }
8174
+ case "sdf:shell": {
8175
+ const child = emitSdfProgramNode(b, node.child, x2, y2, z2);
8176
+ return b.sub(b.abs(child), b.constant(node.thickness * 0.5));
8177
+ }
8178
+ case "sdf:onion": {
8179
+ let d2 = emitSdfProgramNode(b, node.child, x2, y2, z2);
8180
+ for (let i = 0; i < node.layers; i++) d2 = b.sub(b.abs(d2), b.constant(node.thickness));
8181
+ return d2;
8182
+ }
8183
+ case "sdf:gyroid":
8184
+ return emitTpmsDistance(b, emitGyroidValueAndGradient(b, x2, y2, z2, node.cellSize), node.thickness, node.thicknessMode);
8185
+ case "sdf:schwarzP":
8186
+ return emitTpmsDistance(b, emitSchwarzPValueAndGradient(b, x2, y2, z2, node.cellSize), node.thickness, node.thicknessMode);
8187
+ case "sdf:diamond":
8188
+ return emitTpmsDistance(b, emitDiamondValueAndGradient(b, x2, y2, z2, node.cellSize), node.thickness, node.thicknessMode);
8189
+ case "sdf:lidinoid":
8190
+ return emitTpmsDistance(b, emitLidinoidValueAndGradient(b, x2, y2, z2, node.cellSize), node.thickness, node.thicknessMode);
8191
+ default:
8192
+ throw new UnsupportedSdfProgramNodeError(`SdfProgram does not support node kind ${node.kind} yet.`);
8193
+ }
7459
8194
  }
7460
- function resolveDefaultEdgeLength(bounds, quality, minEdgeLength, analysis, options) {
7461
- const dx = bounds.max[0] - bounds.min[0];
7462
- const dy = bounds.max[1] - bounds.min[1];
7463
- const dz = bounds.max[2] - bounds.min[2];
7464
- const maxDim = Math.max(dx, dy, dz, minEdgeLength);
7465
- const divisor = quality === "draft" ? 60 : quality === "export" ? 160 : 100;
7466
- const candidates = [maxDim / divisor];
7467
- if (options.tolerance !== void 0) candidates.push(requirePositiveFinite$2(options.tolerance, "SDF tolerance") * 2);
7468
- if (options.minFeatureSize !== void 0) candidates.push(requirePositiveFinite$2(options.minFeatureSize, "SDF minFeatureSize") / 2.5);
7469
- if (analysis.minMetricTpmsThickness < Infinity) candidates.push(analysis.minMetricTpmsThickness / 2);
7470
- if (analysis.minTpmsCellSize < Infinity) candidates.push(analysis.minTpmsCellSize / 10);
7471
- if (analysis.minRepeatSpacing < Infinity) candidates.push(analysis.minRepeatSpacing / 8);
7472
- if (analysis.minWallThickness < Infinity) candidates.push(analysis.minWallThickness / 2.5);
7473
- return Math.max(minEdgeLength, Math.min(...candidates.filter((v) => Number.isFinite(v) && v > 0)));
8195
+ function foldChildren$1(b, children, x2, y2, z2, combine2) {
8196
+ let d2 = emitSdfProgramNode(b, children[0], x2, y2, z2);
8197
+ for (let i = 1; i < children.length; i++) d2 = combine2(d2, emitSdfProgramNode(b, children[i], x2, y2, z2));
8198
+ return d2;
7474
8199
  }
7475
- function resolveSimplificationMode(simplify, quality, riskFlags) {
7476
- if (simplify === false) return "off";
7477
- if (simplify === true || simplify === "safe") return "safe";
7478
- if (quality === "export" && riskFlags.size > 0) return "off";
7479
- return "safe";
8200
+ function emitRotated(b, node, x2, y2, z2) {
8201
+ const [rx, ry, rz] = node.degrees.map((d2) => d2 * DEG$1);
8202
+ const cx = cos(rx);
8203
+ const sx = sin(rx);
8204
+ const cy = cos(ry);
8205
+ const sy = sin(ry);
8206
+ const cz = cos(rz);
8207
+ const sz = sin(rz);
8208
+ const x1 = b.add(b.mul(b.constant(cz), x2), b.mul(b.constant(sz), y2));
8209
+ const y1 = b.sub(b.mul(b.constant(cz), y2), b.mul(b.constant(sz), x2));
8210
+ const x22 = b.sub(b.mul(b.constant(cy), x1), b.mul(b.constant(sy), z2));
8211
+ const z22 = b.add(b.mul(b.constant(sy), x1), b.mul(b.constant(cy), z2));
8212
+ const y22 = b.add(b.mul(b.constant(cx), y1), b.mul(b.constant(sx), z22));
8213
+ const z3 = b.sub(b.mul(b.constant(cx), z22), b.mul(b.constant(sx), y1));
8214
+ return emitSdfProgramNode(b, node.child, x22, y22, z3);
8215
+ }
8216
+ function sdSphere(b, x2, y2, z2, r) {
8217
+ return b.sub(length3(b, x2, y2, z2), b.constant(r));
8218
+ }
8219
+ function sdBox(b, x2, y2, z2, hx, hy, hz) {
8220
+ const dx = b.sub(b.abs(x2), b.constant(hx));
8221
+ const dy = b.sub(b.abs(y2), b.constant(hy));
8222
+ const dz = b.sub(b.abs(z2), b.constant(hz));
8223
+ const outside = length3(b, b.max(dx, b.constant(0)), b.max(dy, b.constant(0)), b.max(dz, b.constant(0)));
8224
+ const inside = b.min(b.max(b.max(dx, dy), dz), b.constant(0));
8225
+ return b.add(outside, inside);
8226
+ }
8227
+ function sdCylinder(b, x2, y2, z2, h, r) {
8228
+ const dx = b.sub(length2(b, x2, y2), b.constant(r));
8229
+ const dz = b.sub(b.abs(z2), b.constant(h * 0.5));
8230
+ return b.add(length2(b, b.max(dx, b.constant(0)), b.max(dz, b.constant(0))), b.min(b.max(dx, dz), b.constant(0)));
8231
+ }
8232
+ function sdTorus(b, x2, y2, z2, majorRadius, minorRadius) {
8233
+ return b.sub(length2(b, b.sub(length2(b, x2, y2), b.constant(majorRadius)), z2), b.constant(minorRadius));
8234
+ }
8235
+ function sdCapsule(b, x2, y2, z2, h, r) {
8236
+ const cz = clampSlot(b, z2, -h * 0.5, h * 0.5);
8237
+ return b.sub(length3(b, x2, y2, b.sub(z2, cz)), b.constant(r));
8238
+ }
8239
+ function sdCone(b, x2, y2, z2, h, r) {
8240
+ const q = length2(b, x2, y2);
8241
+ const cLen = sqrt(h * h + r * r);
8242
+ const side = b.add(b.mul(b.constant(h / cLen), q), b.mul(b.constant(-r / cLen), b.sub(z2, b.constant(h))));
8243
+ return b.max(b.max(side, b.neg(z2)), b.sub(z2, b.constant(h)));
8244
+ }
8245
+ function sdPolylineSweep(b, node, x2, y2, z2) {
8246
+ let d2 = sdTaperedSegment(b, x2, y2, z2, node.points[0], node.points[1], node.radii[0], node.radii[1]);
8247
+ for (let i = 1; i < node.points.length - 1; i++) {
8248
+ const segment = sdTaperedSegment(b, x2, y2, z2, node.points[i], node.points[i + 1], node.radii[i], node.radii[i + 1]);
8249
+ d2 = smin(b, d2, segment, node.blend);
8250
+ }
8251
+ return d2;
7480
8252
  }
7481
- function analyzeSdfTree(tree) {
7482
- const analysis = {
7483
- riskFlags: /* @__PURE__ */ new Set(),
7484
- minTpmsCellSize: Infinity,
7485
- minMetricTpmsThickness: Infinity,
7486
- minRepeatSpacing: Infinity,
7487
- minWallThickness: Infinity,
7488
- hasInfiniteRepeat: false,
7489
- hasLegacyTpmsThreshold: false
7490
- };
7491
- visitSdfNode(tree, analysis);
7492
- return analysis;
8253
+ function sdTaperedSegment(b, x2, y2, z2, a2, end, ra, rb) {
8254
+ const vx = end[0] - a2[0];
8255
+ const vy = end[1] - a2[1];
8256
+ const vz = end[2] - a2[2];
8257
+ const len2 = vx * vx + vy * vy + vz * vz;
8258
+ if (len2 <= 1e-12) return sdSphere(b, b.sub(x2, b.constant(a2[0])), b.sub(y2, b.constant(a2[1])), b.sub(z2, b.constant(a2[2])), max(ra, rb));
8259
+ const h = clampSlot(
8260
+ b,
8261
+ b.div(
8262
+ b.add(
8263
+ b.add(b.mul(b.sub(x2, b.constant(a2[0])), b.constant(vx)), b.mul(b.sub(y2, b.constant(a2[1])), b.constant(vy))),
8264
+ b.mul(b.sub(z2, b.constant(a2[2])), b.constant(vz))
8265
+ ),
8266
+ b.constant(len2)
8267
+ ),
8268
+ 0,
8269
+ 1
8270
+ );
8271
+ const sx = b.sub(x2, b.add(b.constant(a2[0]), b.mul(b.constant(vx), h)));
8272
+ const sy = b.sub(y2, b.add(b.constant(a2[1]), b.mul(b.constant(vy), h)));
8273
+ const sz = b.sub(z2, b.add(b.constant(a2[2]), b.mul(b.constant(vz), h)));
8274
+ const radius = b.add(b.constant(ra), b.mul(b.constant(rb - ra), h));
8275
+ return b.sub(length3(b, sx, sy, sz), radius);
7493
8276
  }
7494
- function visitSdfNode(node, analysis) {
8277
+ function repeatCoord(b, v, spacing, count) {
8278
+ if (spacing <= 0) return v;
8279
+ if (count > 0) {
8280
+ const center = (count - 1) * 0.5;
8281
+ const index2 = clampSlot(b, b.round(b.add(b.div(v, b.constant(spacing)), b.constant(center))), 0, count - 1);
8282
+ return b.sub(v, b.mul(b.sub(index2, b.constant(center)), b.constant(spacing)));
8283
+ }
8284
+ return b.sub(v, b.mul(b.constant(spacing), b.round(b.div(v, b.constant(spacing)))));
8285
+ }
8286
+ function getUnsupportedSdfProgramReason(node) {
7495
8287
  switch (node.kind) {
8288
+ case "sdf:displace":
8289
+ return "displace uses a dynamic JavaScript function body";
8290
+ case "sdf:surfaceDisplace":
8291
+ return "surfaceDisplace uses dynamic UV/pattern evaluation";
8292
+ case "sdf:spatialBlend":
8293
+ return "spatialBlend uses a dynamic JavaScript blend function";
8294
+ case "sdf:noise":
8295
+ return "noise depends on table-based simplex evaluation";
8296
+ case "sdf:voronoi":
8297
+ return "voronoi depends on table-based Worley evaluation";
8298
+ case "sdf:custom":
8299
+ return "custom uses a dynamic JavaScript function body";
8300
+ case "sdf:polylineSweep":
8301
+ if (node.points.length < 2) return "polylineSweep needs at least two points";
8302
+ if (node.points.length !== node.radii.length) return "polylineSweep point/radius counts differ";
8303
+ return void 0;
7496
8304
  case "sdf:union":
7497
8305
  case "sdf:difference":
7498
8306
  case "sdf:intersection":
7499
8307
  case "sdf:smoothUnion":
7500
8308
  case "sdf:smoothDifference":
7501
8309
  case "sdf:smoothIntersection":
7502
- for (const child of node.children) visitSdfNode(child, analysis);
7503
- break;
8310
+ for (const child of node.children) {
8311
+ const reason = getUnsupportedSdfProgramReason(child);
8312
+ if (reason) return reason;
8313
+ }
8314
+ return void 0;
7504
8315
  case "sdf:morph":
7505
- case "sdf:spatialBlend":
7506
- visitSdfNode(node.a, analysis);
7507
- visitSdfNode(node.b, analysis);
7508
- break;
8316
+ return getUnsupportedSdfProgramReason(node.a) ?? getUnsupportedSdfProgramReason(node.b);
7509
8317
  case "sdf:translate":
7510
8318
  case "sdf:rotate":
7511
8319
  case "sdf:scale":
7512
8320
  case "sdf:twist":
7513
8321
  case "sdf:bend":
7514
- case "sdf:onion":
7515
- visitSdfNode(node.child, analysis);
7516
- break;
7517
8322
  case "sdf:repeat":
7518
- analysis.riskFlags.add("repeat");
7519
- for (let i = 0; i < 3; i++) {
7520
- const spacing = node.spacing[i];
7521
- if (spacing > 0) {
7522
- analysis.minRepeatSpacing = Math.min(analysis.minRepeatSpacing, spacing);
7523
- if (node.count[i] <= 0) analysis.hasInfiniteRepeat = true;
7524
- }
7525
- }
7526
- visitSdfNode(node.child, analysis);
7527
- break;
7528
8323
  case "sdf:shell":
7529
- analysis.minWallThickness = Math.min(analysis.minWallThickness, node.thickness);
7530
- visitSdfNode(node.child, analysis);
7531
- break;
7532
- case "sdf:displace":
7533
- case "sdf:surfaceDisplace":
7534
- analysis.riskFlags.add("displacement");
7535
- visitSdfNode(node.child, analysis);
7536
- break;
7537
- case "sdf:gyroid":
7538
- case "sdf:schwarzP":
7539
- case "sdf:diamond":
7540
- case "sdf:lidinoid":
7541
- analysis.riskFlags.add("tpms");
7542
- analysis.minTpmsCellSize = Math.min(analysis.minTpmsCellSize, node.cellSize);
7543
- if (node.thicknessMode === "metric-approx") {
7544
- analysis.minMetricTpmsThickness = Math.min(analysis.minMetricTpmsThickness, node.thickness);
7545
- analysis.minWallThickness = Math.min(analysis.minWallThickness, node.thickness);
7546
- } else {
7547
- analysis.hasLegacyTpmsThreshold = true;
7548
- }
7549
- break;
7550
- case "sdf:noise":
7551
- analysis.riskFlags.add("noise");
7552
- break;
7553
- case "sdf:voronoi":
7554
- analysis.riskFlags.add("voronoi");
7555
- analysis.minWallThickness = Math.min(analysis.minWallThickness, node.wallThickness);
7556
- if (node.surfaceChild) visitSdfNode(node.surfaceChild, analysis);
7557
- break;
7558
- case "sdf:custom":
7559
- analysis.riskFlags.add("custom");
7560
- break;
8324
+ case "sdf:onion":
8325
+ return getUnsupportedSdfProgramReason(node.child);
8326
+ default:
8327
+ return void 0;
7561
8328
  }
7562
8329
  }
7563
- function positiveOrDefault(value, fallback) {
7564
- if (value === void 0) return fallback;
7565
- return requirePositiveFinite$2(value, "SDF meshing option");
7566
- }
7567
- function requirePositiveFinite$2(value, name) {
7568
- if (!Number.isFinite(value) || value <= 0) {
7569
- throw new Error(`${name} must be a positive finite number.`);
8330
+ function compileSdfProgram(node) {
8331
+ const unsupportedReason = getUnsupportedSdfProgramReason(node);
8332
+ if (unsupportedReason) {
8333
+ throw new UnsupportedSdfProgramNodeError(`SdfProgram does not support this tree yet: ${unsupportedReason}.`);
7570
8334
  }
7571
- return value;
7572
- }
7573
- function cloneBounds$2(bounds) {
7574
- return { min: [...bounds.min], max: [...bounds.max] };
7575
- }
7576
- function suggestEdgeLengthForSampleBudget(bounds, maxGridPoints) {
7577
- const dx = bounds.max[0] - bounds.min[0];
7578
- const dy = bounds.max[1] - bounds.min[1];
7579
- const dz = bounds.max[2] - bounds.min[2];
7580
- const volume = Math.max(dx * dy * dz, 1);
7581
- return Math.cbrt(volume / Math.max(maxGridPoints, 8));
7582
- }
7583
- function formatBounds(bounds) {
7584
- return `[${bounds.min.map(formatNumber$1).join(",")}]-[${bounds.max.map(formatNumber$1).join(",")}]`;
7585
- }
7586
- function formatMm(value) {
7587
- return `${formatNumber$1(value)}mm`;
8335
+ const builder = new SdfProgramBuilder();
8336
+ return builder.finalize(emitSdfProgramNode(builder, node, builder.x, builder.y, builder.z));
7588
8337
  }
7589
- function formatNumber$1(value) {
7590
- return Number.isInteger(value) ? String(value) : value.toFixed(3).replace(/0+$/, "").replace(/\.$/, "");
8338
+ function compileSdfProgram3(node) {
8339
+ return compileSdfProgramEvaluator3(compileSdfProgram(node));
7591
8340
  }
7592
- function formatCount(value) {
7593
- return Math.round(value).toLocaleString("en-US");
7594
- }
7595
- function formatBytes(bytes) {
7596
- if (bytes < 1024 * 1024) return `${Math.ceil(bytes / 1024)} KB`;
7597
- return `${Math.ceil(bytes / (1024 * 1024))} MB`;
8341
+ function compileSdfMaterializationEvaluator3(node) {
8342
+ const unsupportedReason = getUnsupportedSdfProgramReason(node);
8343
+ if (unsupportedReason) {
8344
+ return {
8345
+ fn: compileSdfNode3(node),
8346
+ engine: "closure",
8347
+ unsupportedReason
8348
+ };
8349
+ }
8350
+ return {
8351
+ fn: compileSdfProgram3(node),
8352
+ engine: "program"
8353
+ };
7598
8354
  }
7599
8355
  function midpoint$3(a2, b) {
7600
8356
  return [(a2[0] + b[0]) / 2, (a2[1] + b[1]) / 2, (a2[2] + b[2]) / 2];
@@ -8060,7 +8816,7 @@ function buildCircleExtrusionTopology(circ, height, center = false) {
8060
8816
  );
8061
8817
  return { faces, edges };
8062
8818
  }
8063
- function requireFinite$8(v, label) {
8819
+ function requireFinite$9(v, label) {
8064
8820
  if (!Number.isFinite(v)) throw new Error(`nurbsSurface: ${label} must be finite, got ${v}`);
8065
8821
  }
8066
8822
  function normalizeSurfaceTessellation(tessellation) {
@@ -8070,11 +8826,11 @@ function normalizeSurfaceTessellation(tessellation) {
8070
8826
  throw new Error(`nurbsSurface: tessellation.mode must be "uniform" or "adaptive", got ${mode}`);
8071
8827
  }
8072
8828
  if (tessellation.tolerance !== void 0) {
8073
- requireFinite$8(tessellation.tolerance, "tessellation.tolerance");
8829
+ requireFinite$9(tessellation.tolerance, "tessellation.tolerance");
8074
8830
  if (tessellation.tolerance <= 0) throw new Error("nurbsSurface: tessellation.tolerance must be > 0");
8075
8831
  }
8076
- if (tessellation.minResolution !== void 0) requireFinite$8(tessellation.minResolution, "tessellation.minResolution");
8077
- if (tessellation.maxResolution !== void 0) requireFinite$8(tessellation.maxResolution, "tessellation.maxResolution");
8832
+ if (tessellation.minResolution !== void 0) requireFinite$9(tessellation.minResolution, "tessellation.minResolution");
8833
+ if (tessellation.maxResolution !== void 0) requireFinite$9(tessellation.maxResolution, "tessellation.maxResolution");
8078
8834
  const minResolution = tessellation.minResolution === void 0 ? void 0 : Math.max(2, Math.round(tessellation.minResolution));
8079
8835
  const maxResolution = tessellation.maxResolution === void 0 ? void 0 : Math.max(2, Math.round(tessellation.maxResolution));
8080
8836
  if (minResolution !== void 0 && maxResolution !== void 0 && minResolution > maxResolution) {
@@ -8093,10 +8849,10 @@ function normalizeSurfaceDomain(domain) {
8093
8849
  const uMax = domain.uMax ?? 1;
8094
8850
  const vMin = domain.vMin ?? 0;
8095
8851
  const vMax = domain.vMax ?? 1;
8096
- requireFinite$8(uMin, "domain.uMin");
8097
- requireFinite$8(uMax, "domain.uMax");
8098
- requireFinite$8(vMin, "domain.vMin");
8099
- requireFinite$8(vMax, "domain.vMax");
8852
+ requireFinite$9(uMin, "domain.uMin");
8853
+ requireFinite$9(uMax, "domain.uMax");
8854
+ requireFinite$9(vMin, "domain.vMin");
8855
+ requireFinite$9(vMax, "domain.vMax");
8100
8856
  if (uMin < 0 || uMax > 1 || vMin < 0 || vMax > 1) {
8101
8857
  throw new Error("nurbsSurface: domain bounds must stay within [0, 1]");
8102
8858
  }
@@ -8108,8 +8864,8 @@ function normalizeSurfaceDomain(domain) {
8108
8864
  function normalizeTrimLoop(loop, label) {
8109
8865
  if (loop.length < 3) throw new Error(`nurbsSurface: ${label} requires at least 3 points`);
8110
8866
  const normalized = loop.map(([u2, v], idx) => {
8111
- requireFinite$8(u2, `${label}[${idx}][0]`);
8112
- requireFinite$8(v, `${label}[${idx}][1]`);
8867
+ requireFinite$9(u2, `${label}[${idx}][0]`);
8868
+ requireFinite$9(v, `${label}[${idx}][1]`);
8113
8869
  if (u2 < 0 || u2 > 1 || v < 0 || v > 1) throw new Error(`nurbsSurface: ${label}[${idx}] must stay within [0, 1]`);
8114
8870
  return [u2, v];
8115
8871
  });
@@ -8132,8 +8888,8 @@ function normalizeTrimCurve(curve, label) {
8132
8888
  throw new Error(`nurbsSurface: ${label} needs at least ${degree + 1} control points for degree=${degree}`);
8133
8889
  }
8134
8890
  const normalizedControlPoints = controlPoints.map(([u2, v], idx) => {
8135
- requireFinite$8(u2, `${label}.controlPoints[${idx}][0]`);
8136
- requireFinite$8(v, `${label}.controlPoints[${idx}][1]`);
8891
+ requireFinite$9(u2, `${label}.controlPoints[${idx}][0]`);
8892
+ requireFinite$9(v, `${label}.controlPoints[${idx}][1]`);
8137
8893
  if (u2 < 0 || u2 > 1 || v < 0 || v > 1) {
8138
8894
  throw new Error(`nurbsSurface: ${label}.controlPoints[${idx}] must stay within [0, 1]`);
8139
8895
  }
@@ -8144,7 +8900,7 @@ function normalizeTrimCurve(curve, label) {
8144
8900
  throw new Error(`nurbsSurface: ${label}.weights length must match controlPoints length`);
8145
8901
  }
8146
8902
  for (let idx = 0; idx < weights.length; idx += 1) {
8147
- requireFinite$8(weights[idx], `${label}.weights[${idx}]`);
8903
+ requireFinite$9(weights[idx], `${label}.weights[${idx}]`);
8148
8904
  if (weights[idx] <= 0) throw new Error(`nurbsSurface: ${label}.weights[${idx}] must be > 0`);
8149
8905
  }
8150
8906
  const knots = curve.knots ?? generateClampedKnots(controlPoints.length, degree);
@@ -8152,7 +8908,7 @@ function normalizeTrimCurve(curve, label) {
8152
8908
  throw new Error(`nurbsSurface: ${label}.knots.length should be ${controlPoints.length + degree + 1}, got ${knots.length}`);
8153
8909
  }
8154
8910
  for (let idx = 0; idx < knots.length; idx += 1) {
8155
- requireFinite$8(knots[idx], `${label}.knots[${idx}]`);
8911
+ requireFinite$9(knots[idx], `${label}.knots[${idx}]`);
8156
8912
  if (idx > 0 && knots[idx] < knots[idx - 1]) throw new Error(`nurbsSurface: ${label}.knots must be non-decreasing`);
8157
8913
  }
8158
8914
  if (knots[degree] >= knots[controlPoints.length]) {
@@ -8332,16 +9088,16 @@ class NurbsSurface {
8332
9088
  for (let i = 0; i < nU; i++) {
8333
9089
  if (controlGrid[i].length !== nV) throw new Error(`nurbsSurface: row ${i} has ${controlGrid[i].length} points, expected ${nV}`);
8334
9090
  for (let j = 0; j < nV; j++) {
8335
- requireFinite$8(controlGrid[i][j][0], `controlGrid[${i}][${j}][0]`);
8336
- requireFinite$8(controlGrid[i][j][1], `controlGrid[${i}][${j}][1]`);
8337
- requireFinite$8(controlGrid[i][j][2], `controlGrid[${i}][${j}][2]`);
9091
+ requireFinite$9(controlGrid[i][j][0], `controlGrid[${i}][${j}][0]`);
9092
+ requireFinite$9(controlGrid[i][j][1], `controlGrid[${i}][${j}][1]`);
9093
+ requireFinite$9(controlGrid[i][j][2], `controlGrid[${i}][${j}][2]`);
8338
9094
  }
8339
9095
  }
8340
9096
  const weightsGrid = options.weights ?? controlGrid.map((row) => row.map(() => 1));
8341
9097
  for (let i = 0; i < nU; i++) {
8342
9098
  if (weightsGrid[i].length !== nV) throw new Error(`nurbsSurface: weights row ${i} length mismatch`);
8343
9099
  for (let j = 0; j < nV; j++) {
8344
- requireFinite$8(weightsGrid[i][j], `weights[${i}][${j}]`);
9100
+ requireFinite$9(weightsGrid[i][j], `weights[${i}][${j}]`);
8345
9101
  if (weightsGrid[i][j] <= 0) throw new Error(`nurbsSurface: weights[${i}][${j}] must be > 0`);
8346
9102
  }
8347
9103
  }
@@ -9730,24 +10486,24 @@ function stitchSingleLoopLoft(loops, heights, wasm) {
9730
10486
  const j1 = (j + 1) % N;
9731
10487
  const v0 = baseIdx + j;
9732
10488
  const v1 = nextIdx + j;
9733
- const v2 = nextIdx + j1;
10489
+ const v22 = nextIdx + j1;
9734
10490
  const v32 = baseIdx + j1;
9735
- triangles.push(v0, v32, v2);
9736
- triangles.push(v0, v2, v1);
10491
+ triangles.push(v0, v32, v22);
10492
+ triangles.push(v0, v22, v1);
9737
10493
  }
9738
10494
  }
9739
10495
  const bottomResampled2D = resampled[0].map(([x2, y2]) => [x2, y2]);
9740
10496
  const bottomTrisResampled = wasm.triangulate([bottomResampled2D]);
9741
10497
  for (const tri of bottomTrisResampled) {
9742
- const [v0, v1, v2] = Array.isArray(tri) ? tri : [tri[0], tri[1], tri[2]];
9743
- triangles.push(v0, v2, v1);
10498
+ const [v0, v1, v22] = Array.isArray(tri) ? tri : [tri[0], tri[1], tri[2]];
10499
+ triangles.push(v0, v22, v1);
9744
10500
  }
9745
10501
  const topResampled2D = resampled[resampled.length - 1].map(([x2, y2]) => [x2, y2]);
9746
10502
  const topTrisResampled = wasm.triangulate([topResampled2D]);
9747
10503
  const topStartIdx = (resampled.length - 1) * N;
9748
10504
  for (const tri of topTrisResampled) {
9749
- const [v0, v1, v2] = Array.isArray(tri) ? tri : [tri[0], tri[1], tri[2]];
9750
- triangles.push(topStartIdx + v0, topStartIdx + v1, topStartIdx + v2);
10505
+ const [v0, v1, v22] = Array.isArray(tri) ? tri : [tri[0], tri[1], tri[2]];
10506
+ triangles.push(topStartIdx + v0, topStartIdx + v1, topStartIdx + v22);
9751
10507
  }
9752
10508
  const mesh = new wasm.Mesh({
9753
10509
  numProp: 3,
@@ -9774,7 +10530,7 @@ async function initManifoldWasm() {
9774
10530
  if (_wasm$1) return _wasm$1;
9775
10531
  performance.mark("manifold:start");
9776
10532
  const Module = (await __vitePreload(async () => {
9777
- const { default: __vite_default__ } = await import("./manifold-lru0jwVw.js");
10533
+ const { default: __vite_default__ } = await import("./manifold-CRoBhJKH.js");
9778
10534
  return { default: __vite_default__ };
9779
10535
  }, true ? [] : void 0)).default;
9780
10536
  performance.mark("manifold:imported");
@@ -10057,10 +10813,10 @@ function stitchLoopAlongPath(loop, _path, frames, wasm) {
10057
10813
  const j1 = (j + 1) % N;
10058
10814
  const v0 = baseIdx + j;
10059
10815
  const v1 = nextIdx + j;
10060
- const v2 = nextIdx + j1;
10816
+ const v22 = nextIdx + j1;
10061
10817
  const v32 = baseIdx + j1;
10062
- triangles.push(v0, v32, v2);
10063
- triangles.push(v0, v2, v1);
10818
+ triangles.push(v0, v32, v22);
10819
+ triangles.push(v0, v22, v1);
10064
10820
  }
10065
10821
  }
10066
10822
  const bottomPts = resampled.map(([u2, v]) => [u2, v]);
@@ -11405,8 +12161,16 @@ function lowerShapeCompilePlanToManifold(plan, wasm) {
11405
12161
  case "importedMesh":
11406
12162
  return lowerImportedMeshToManifold(plan.fileData, plan.format, plan.filePath, wasm);
11407
12163
  case "sdf": {
11408
- const evalFn = compileSdfNode3(plan.tree);
11409
- return lowerSdfToManifold(evalFn, plan.bounds, plan.edgeLength, wasm, plan.meshing);
12164
+ const evaluator = compileSdfMaterializationEvaluator3(plan.tree);
12165
+ return lowerSdfToManifold(
12166
+ evaluator.fn,
12167
+ plan.bounds,
12168
+ plan.edgeLength,
12169
+ wasm,
12170
+ plan.meshing,
12171
+ evaluator.engine,
12172
+ evaluator.unsupportedReason
12173
+ );
11410
12174
  }
11411
12175
  case "fromSlices":
11412
12176
  return lowerFromSlicesToManifold(plan, wasm);
@@ -11424,8 +12188,12 @@ function lowerShapeCompilePlanToManifold(plan, wasm) {
11424
12188
  assertExhaustive(plan);
11425
12189
  }
11426
12190
  }
11427
- function lowerSdfToManifold(evalFn, bounds, edgeLength2, wasm, meshing) {
12191
+ function lowerSdfToManifold(evalFn, bounds, edgeLength2, wasm, meshing, evaluatorEngine, evaluatorUnsupportedReason) {
11428
12192
  const diagnostics = (meshing == null ? void 0 : meshing.diagnostics) ? { ...meshing.diagnostics } : void 0;
12193
+ if (diagnostics && evaluatorEngine) {
12194
+ diagnostics.evaluator = evaluatorEngine;
12195
+ if (evaluatorUnsupportedReason) diagnostics.evaluatorUnsupportedReason = evaluatorUnsupportedReason;
12196
+ }
11429
12197
  const inset = edgeLength2;
11430
12198
  const cappedEvalFn = (x2, y2, z2) => {
11431
12199
  const bx = Math.max(bounds.min[0] + inset - x2, x2 - bounds.max[0] + inset);
@@ -16846,9 +17614,9 @@ function requireClipper() {
16846
17614
  if (ClipperLib2.use_xyz) j.OffPt.Z = OffPt.Z;
16847
17615
  this.m_Joins.push(j);
16848
17616
  };
16849
- ClipperLib2.Clipper.prototype.AddGhostJoin = function(Op, OffPt) {
17617
+ ClipperLib2.Clipper.prototype.AddGhostJoin = function(Op2, OffPt) {
16850
17618
  var j = new ClipperLib2.Join();
16851
- j.OutPt1 = Op;
17619
+ j.OutPt1 = Op2;
16852
17620
  j.OffPt.X = OffPt.X;
16853
17621
  j.OffPt.Y = OffPt.Y;
16854
17622
  if (ClipperLib2.use_xyz) j.OffPt.Z = OffPt.Z;
@@ -19750,7 +20518,7 @@ function requireClipper() {
19750
20518
  }
19751
20519
  var clipperExports = requireClipper();
19752
20520
  const ClipperLib = /* @__PURE__ */ getDefaultExportFromCjs(clipperExports);
19753
- let f$2 = class f {
20521
+ let f$3 = class f {
19754
20522
  constructor(t, e) {
19755
20523
  this.next = null, this.key = t, this.data = e, this.left = null, this.right = null;
19756
20524
  }
@@ -19759,7 +20527,7 @@ function d(n, t) {
19759
20527
  return n > t ? 1 : n < t ? -1 : 0;
19760
20528
  }
19761
20529
  function u$1(n, t, e) {
19762
- const r = new f$2(null, null);
20530
+ const r = new f$3(null, null);
19763
20531
  let l = r, i = r;
19764
20532
  for (; ; ) {
19765
20533
  const o = e(n, t.key);
@@ -19782,7 +20550,7 @@ function u$1(n, t, e) {
19782
20550
  return l.right = t.left, i.left = t.right, t.left = r.right, t.right = r.left, t;
19783
20551
  }
19784
20552
  function c(n, t, e, r) {
19785
- const l = new f$2(n, t);
20553
+ const l = new f$3(n, t);
19786
20554
  if (e === null)
19787
20555
  return l.left = l.right = null, l;
19788
20556
  e = u$1(n, e, r);
@@ -19823,7 +20591,7 @@ class z {
19823
20591
  * Adds a key, if it is not present in the tree
19824
20592
  */
19825
20593
  add(t, e) {
19826
- const r = new f$2(t, e);
20594
+ const r = new f$3(t, e);
19827
20595
  this._root === null && (r.left = r.right = null, this._size++, this._root = r);
19828
20596
  const l = this._comparator, i = u$1(t, this._root, l), o = l(t, i.key);
19829
20597
  return o === 0 ? this._root = i : (o < 0 ? (r.left = i.left, r.right = i, i.left = null) : o > 0 && (r.right = i.right, r.left = i, i.right = null), this._size++, this._root = r), this._root;
@@ -20036,23 +20804,23 @@ class z {
20036
20804
  function a(n, t, e, r) {
20037
20805
  const l = r - e;
20038
20806
  if (l > 0) {
20039
- const i = e + Math.floor(l / 2), o = n[i], s = t[i], h = new f$2(o, s);
20807
+ const i = e + Math.floor(l / 2), o = n[i], s = t[i], h = new f$3(o, s);
20040
20808
  return h.left = a(n, t, e, i), h.right = a(n, t, i + 1, r), h;
20041
20809
  }
20042
20810
  return null;
20043
20811
  }
20044
20812
  function x(n, t) {
20045
- const e = new f$2(null, null);
20813
+ const e = new f$3(null, null);
20046
20814
  let r = e;
20047
20815
  for (let l = 0; l < n.length; l++)
20048
- r = r.next = new f$2(n[l], t[l]);
20816
+ r = r.next = new f$3(n[l], t[l]);
20049
20817
  return r.next = null, e.next;
20050
20818
  }
20051
20819
  function k(n) {
20052
20820
  let t = n;
20053
20821
  const e = [];
20054
20822
  let r = false;
20055
- const l = new f$2(null, null);
20823
+ const l = new f$3(null, null);
20056
20824
  let i = l;
20057
20825
  for (; !r; )
20058
20826
  t ? (e.push(t), t = t.left) : e.length > 0 ? (t = i = i.next = e.pop(), t = t.right) : r = true;
@@ -20067,7 +20835,7 @@ function p(n, t, e) {
20067
20835
  return null;
20068
20836
  }
20069
20837
  function y(n, t, e) {
20070
- const r = new f$2(null, null);
20838
+ const r = new f$3(null, null);
20071
20839
  let l = r, i = n, o = t;
20072
20840
  for (; i !== null && o !== null; )
20073
20841
  e(i.key, o.key) < 0 ? (l.next = i, i = i.next) : (l.next = o, o = o.next), l = l.next;
@@ -20459,21 +21227,21 @@ const verticalIntersection = (pt, v, x2) => {
20459
21227
  y: pt.y + v.y / v.x * (x2 - pt.x)
20460
21228
  };
20461
21229
  };
20462
- const intersection$1 = (pt1, v1, pt2, v2) => {
20463
- if (v1.x === 0) return verticalIntersection(pt2, v2, pt1.x);
20464
- if (v2.x === 0) return verticalIntersection(pt1, v1, pt2.x);
20465
- if (v1.y === 0) return horizontalIntersection(pt2, v2, pt1.y);
20466
- if (v2.y === 0) return horizontalIntersection(pt1, v1, pt2.y);
20467
- const kross = crossProduct(v1, v2);
21230
+ const intersection$1 = (pt1, v1, pt2, v22) => {
21231
+ if (v1.x === 0) return verticalIntersection(pt2, v22, pt1.x);
21232
+ if (v22.x === 0) return verticalIntersection(pt1, v1, pt2.x);
21233
+ if (v1.y === 0) return horizontalIntersection(pt2, v22, pt1.y);
21234
+ if (v22.y === 0) return horizontalIntersection(pt1, v1, pt2.y);
21235
+ const kross = crossProduct(v1, v22);
20468
21236
  if (kross == 0) return null;
20469
21237
  const ve = {
20470
21238
  x: pt2.x - pt1.x,
20471
21239
  y: pt2.y - pt1.y
20472
21240
  };
20473
21241
  const d1 = crossProduct(ve, v1) / kross;
20474
- const d2 = crossProduct(ve, v2) / kross;
20475
- const x1 = pt1.x + d2 * v1.x, x2 = pt2.x + d1 * v2.x;
20476
- const y1 = pt1.y + d2 * v1.y, y2 = pt2.y + d1 * v2.y;
21242
+ const d2 = crossProduct(ve, v22) / kross;
21243
+ const x1 = pt1.x + d2 * v1.x, x2 = pt2.x + d1 * v22.x;
21244
+ const y1 = pt1.y + d2 * v1.y, y2 = pt2.y + d1 * v22.y;
20477
21245
  const x3 = (x1 + x2) / 2;
20478
21246
  const y3 = (y1 + y2) / 2;
20479
21247
  return {
@@ -25055,7 +25823,13 @@ function normalizeTruckShapeForBooleanInput(shape) {
25055
25823
  return normalized;
25056
25824
  }
25057
25825
  function lowerSdfPlan(plan) {
25058
- const evalFn = compileSdfNode3(plan.tree);
25826
+ var _a3, _b3, _c2;
25827
+ const evaluator = compileSdfMaterializationEvaluator3(plan.tree);
25828
+ if ((_a3 = plan.meshing) == null ? void 0 : _a3.diagnostics) {
25829
+ plan.meshing.diagnostics.evaluator = evaluator.engine;
25830
+ if (evaluator.unsupportedReason) plan.meshing.diagnostics.evaluatorUnsupportedReason = evaluator.unsupportedReason;
25831
+ }
25832
+ const evalFn = evaluator.fn;
25059
25833
  const inset = plan.edgeLength;
25060
25834
  const cappedEvalFn = (x2, y2, z2) => {
25061
25835
  const bx = Math.max(plan.bounds.min[0] + inset - x2, x2 - plan.bounds.max[0] + inset);
@@ -25067,14 +25841,18 @@ function lowerSdfPlan(plan) {
25067
25841
  assertSdfMeshBudget(mesh, plan);
25068
25842
  let surfaceNetsError;
25069
25843
  try {
25070
- return lowerExtractedSdfMesh(mesh, cappedEvalFn, true);
25844
+ const shape = lowerExtractedSdfMesh(mesh, cappedEvalFn, true);
25845
+ if ((_b3 = plan.meshing) == null ? void 0 : _b3.diagnostics) logSdfMeshingDiagnostics("SDF meshing result", plan.meshing.diagnostics);
25846
+ return shape;
25071
25847
  } catch (error) {
25072
25848
  surfaceNetsError = error;
25073
25849
  }
25074
25850
  const tetraMesh = marchingTetrahedra(cappedEvalFn, plan.bounds, plan.edgeLength);
25075
25851
  assertSdfMeshBudget(tetraMesh, plan);
25076
25852
  try {
25077
- return lowerExtractedSdfMesh(tetraMesh, cappedEvalFn, false);
25853
+ const shape = lowerExtractedSdfMesh(tetraMesh, cappedEvalFn, false);
25854
+ if ((_c2 = plan.meshing) == null ? void 0 : _c2.diagnostics) logSdfMeshingDiagnostics("SDF meshing result", plan.meshing.diagnostics);
25855
+ return shape;
25078
25856
  } catch (error) {
25079
25857
  throw new Error(
25080
25858
  `Truck backend does not support compile plan "sdf" for this materialized field yet: Surface Nets failed with ${surfaceNetsError instanceof Error ? surfaceNetsError.message : String(surfaceNetsError)}; marching tetrahedra failed with ${error instanceof Error ? error.message : String(error)}`
@@ -25438,7 +26216,9 @@ function lowerOffsetSolidPlan(plan) {
25438
26216
  if (base.kind === "transform") {
25439
26217
  return lowerTransformedOffsetSolidPlan(base, plan.thickness);
25440
26218
  }
25441
- return truckUnsupported(`compile plan "${plan.kind}" for non-vertical-prism/non-revolved/non-loft/non-straight-sweep/non-straight-variable-sweep solids`);
26219
+ return truckUnsupported(
26220
+ `compile plan "${plan.kind}" for non-vertical-prism/non-revolved/non-loft/non-straight-sweep/non-straight-variable-sweep solids`
26221
+ );
25442
26222
  }
25443
26223
  function lowerLoftPlan(plan) {
25444
26224
  return wrapTruckShapeBackend(
@@ -29668,16 +30448,16 @@ function clusterMeshFaces(shape) {
29668
30448
  const i2 = triVerts[i * 3 + 2];
29669
30449
  const v0 = [vertProperties[i0 * numProp], vertProperties[i0 * numProp + 1], vertProperties[i0 * numProp + 2]];
29670
30450
  const v1 = [vertProperties[i1 * numProp], vertProperties[i1 * numProp + 1], vertProperties[i1 * numProp + 2]];
29671
- const v2 = [vertProperties[i2 * numProp], vertProperties[i2 * numProp + 1], vertProperties[i2 * numProp + 2]];
30451
+ const v22 = [vertProperties[i2 * numProp], vertProperties[i2 * numProp + 1], vertProperties[i2 * numProp + 2]];
29672
30452
  const e1 = [v1[0] - v0[0], v1[1] - v0[1], v1[2] - v0[2]];
29673
- const e2 = [v2[0] - v0[0], v2[1] - v0[1], v2[2] - v0[2]];
30453
+ const e2 = [v22[0] - v0[0], v22[1] - v0[1], v22[2] - v0[2]];
29674
30454
  const rawCross = cross$4(e1, e2);
29675
30455
  const normal = normVec3(rawCross);
29676
30456
  if (!normal) continue;
29677
30457
  const crossLen = Math.sqrt(rawCross[0] * rawCross[0] + rawCross[1] * rawCross[1] + rawCross[2] * rawCross[2]);
29678
30458
  const triArea = crossLen / 2;
29679
30459
  const planeOffset = dot$5(normal, v0);
29680
- const triCentroid = [(v0[0] + v1[0] + v2[0]) / 3, (v0[1] + v1[1] + v2[1]) / 3, (v0[2] + v1[2] + v2[2]) / 3];
30460
+ const triCentroid = [(v0[0] + v1[0] + v22[0]) / 3, (v0[1] + v1[1] + v22[1]) / 3, (v0[2] + v1[2] + v22[2]) / 3];
29681
30461
  let merged = false;
29682
30462
  for (const c2 of clusters) {
29683
30463
  if (dot$5(c2.normal, normal) > NORMAL_COS_EPS$1 && Math.abs(c2.planeOffset - planeOffset) < PLANE_OFFSET_EPS$1) {
@@ -32740,6 +33520,37 @@ function mergeSketchPlacementModel(sketches) {
32740
33520
  }
32741
33521
  return first;
32742
33522
  }
33523
+ function normalizeSceneTags(value, label = "tags") {
33524
+ if (value == null) return [];
33525
+ const rawTags = typeof value === "string" ? [value] : value;
33526
+ if (!Array.isArray(rawTags)) {
33527
+ throw new Error(`${label} must be a string or array of strings`);
33528
+ }
33529
+ const out = [];
33530
+ const seen2 = /* @__PURE__ */ new Set();
33531
+ rawTags.forEach((tag, index2) => {
33532
+ if (typeof tag !== "string") {
33533
+ throw new Error(`${label}[${index2}] must be a string`);
33534
+ }
33535
+ const trimmed = tag.trim();
33536
+ if (!trimmed || seen2.has(trimmed)) return;
33537
+ seen2.add(trimmed);
33538
+ out.push(trimmed);
33539
+ });
33540
+ return out;
33541
+ }
33542
+ function mergeSceneTags(...values) {
33543
+ const out = [];
33544
+ const seen2 = /* @__PURE__ */ new Set();
33545
+ values.forEach((value) => {
33546
+ normalizeSceneTags(value).forEach((tag) => {
33547
+ if (seen2.has(tag)) return;
33548
+ seen2.add(tag);
33549
+ out.push(tag);
33550
+ });
33551
+ });
33552
+ return out;
33553
+ }
32743
33554
  const _groupPlacementRefs = /* @__PURE__ */ new WeakMap();
32744
33555
  const _groupExplodeHint = /* @__PURE__ */ new WeakMap();
32745
33556
  function getGroupRefs(g2) {
@@ -32763,7 +33574,7 @@ function transformGroupRefs(source, dest, matrix) {
32763
33574
  }
32764
33575
  return dest;
32765
33576
  }
32766
- function requireFiniteAngle(v, method) {
33577
+ function requireFiniteAngle$1(v, method) {
32767
33578
  if (typeof v !== "number" || !Number.isFinite(v))
32768
33579
  throw new Error(`${method} angleDeg must be a finite number, got ${typeof v === "number" ? v : typeof v}`);
32769
33580
  }
@@ -32824,31 +33635,46 @@ function resolveNamedGroupChild(item) {
32824
33635
  function normalizeGroupInputs(items) {
32825
33636
  const children = [];
32826
33637
  const childNames = [];
33638
+ const childTags = [];
32827
33639
  items.forEach((item) => {
32828
33640
  if (isNamedGroupChild(item)) {
32829
33641
  children.push(resolveNamedGroupChild(item));
32830
33642
  childNames.push(normalizeChildName(item.name));
33643
+ childTags.push(normalizeSceneTags(item.tags, `group(...) named item "${item.name}" tags`));
32831
33644
  return;
32832
33645
  }
32833
33646
  children.push(item);
32834
33647
  childNames.push(void 0);
33648
+ childTags.push([]);
32835
33649
  });
32836
- return { children, childNames };
33650
+ return { children, childNames, childTags };
32837
33651
  }
32838
33652
  class ShapeGroup {
32839
- constructor(children, childNames) {
33653
+ constructor(children, childNames, childTags) {
32840
33654
  __publicField(this, "children");
32841
33655
  __publicField(this, "childNames");
33656
+ __publicField(this, "childTags");
32842
33657
  if (childNames && childNames.length !== children.length) {
32843
33658
  throw new Error("ShapeGroup childNames must match children length");
32844
33659
  }
33660
+ if (childTags && childTags.length !== children.length) {
33661
+ throw new Error("ShapeGroup childTags must match children length");
33662
+ }
32845
33663
  this.children = [...children];
32846
33664
  this.childNames = this.children.map((_2, index2) => normalizeChildName(childNames == null ? void 0 : childNames[index2]));
33665
+ this.childTags = this.children.map((_2, index2) => normalizeSceneTags(childTags == null ? void 0 : childTags[index2], "ShapeGroup childTags"));
32847
33666
  }
32848
33667
  /** Return the optional name of the child at `index`. */
32849
33668
  childName(index2) {
32850
33669
  return this.childNames[index2];
32851
33670
  }
33671
+ /**
33672
+ * Return tags attached to the child at `index`.
33673
+ * @internal
33674
+ */
33675
+ tagsForChild(index2) {
33676
+ return [...this.childTags[index2] ?? []];
33677
+ }
32852
33678
  /**
32853
33679
  * Return the named child by name. Throws if not found.
32854
33680
  * Useful when importing a multipart group and working on components individually.
@@ -32863,13 +33689,13 @@ class ShapeGroup {
32863
33689
  }
32864
33690
  /** Apply fn to all children, producing a new ShapeGroup that also copies placement refs. */
32865
33691
  mapChildren(fn) {
32866
- const next = new ShapeGroup(this.children.map(fn), this.childNames);
33692
+ const next = new ShapeGroup(this.children.map(fn), this.childNames, this.childTags);
32867
33693
  copyGroupPorts(this, next);
32868
33694
  return copyGroupRefs(this, next);
32869
33695
  }
32870
33696
  /** Apply fn to all children and also transform placement refs by the given matrix. */
32871
33697
  mapChildrenTransform(fn, matrix) {
32872
- const next = new ShapeGroup(this.children.map(fn), this.childNames);
33698
+ const next = new ShapeGroup(this.children.map(fn), this.childNames, this.childTags);
32873
33699
  transformGroupPortsHelper(this, next, matrix);
32874
33700
  return transformGroupRefs(this, next, matrix);
32875
33701
  }
@@ -32984,37 +33810,37 @@ class ShapeGroup {
32984
33810
  const u2 = opts.u ?? 0, v = opts.v ?? 0, p2 = opts.protrude ?? 0;
32985
33811
  const opp = { front: "back", back: "front", left: "right", right: "left", top: "bottom", bottom: "top" };
32986
33812
  const uvMap = {
32987
- front: (u22, v2, p22) => [u22, -p22, v2],
32988
- back: (u22, v2, p22) => [u22, p22, v2],
32989
- left: (u22, v2, p22) => [-p22, u22, v2],
32990
- right: (u22, v2, p22) => [p22, u22, v2],
32991
- top: (u22, v2, p22) => [u22, v2, p22],
32992
- bottom: (u22, v2, p22) => [u22, v2, -p22]
33813
+ front: (u22, v22, p22) => [u22, -p22, v22],
33814
+ back: (u22, v22, p22) => [u22, p22, v22],
33815
+ left: (u22, v22, p22) => [-p22, u22, v22],
33816
+ right: (u22, v22, p22) => [p22, u22, v22],
33817
+ top: (u22, v22, p22) => [u22, v22, p22],
33818
+ bottom: (u22, v22, p22) => [u22, v22, -p22]
32993
33819
  };
32994
33820
  return this.attachTo(parent, face, opp[face], uvMap[face](u2, v, p2));
32995
33821
  }
32996
33822
  /** Rotate the group around an arbitrary axis through the origin. */
32997
33823
  rotate(axis, angleDeg, options) {
32998
33824
  requireRotateAxis(axis, "ShapeGroup.rotate()");
32999
- requireFiniteAngle(angleDeg, "ShapeGroup.rotate()");
33825
+ requireFiniteAngle$1(angleDeg, "ShapeGroup.rotate()");
33000
33826
  if (options == null ? void 0 : options.pivot) requireVec3Pivot(options.pivot, "ShapeGroup.rotate()");
33001
33827
  return this.rotateAroundAxis(axis, angleDeg, options == null ? void 0 : options.pivot);
33002
33828
  }
33003
33829
  /** Rotate the group around the X axis. */
33004
33830
  rotateX(angleDeg, options) {
33005
- requireFiniteAngle(angleDeg, "ShapeGroup.rotateX()");
33831
+ requireFiniteAngle$1(angleDeg, "ShapeGroup.rotateX()");
33006
33832
  if (options == null ? void 0 : options.pivot) requireVec3Pivot(options.pivot, "ShapeGroup.rotateX()");
33007
33833
  return this.rotateAroundAxis([1, 0, 0], angleDeg, options == null ? void 0 : options.pivot);
33008
33834
  }
33009
33835
  /** Rotate the group around the Y axis. */
33010
33836
  rotateY(angleDeg, options) {
33011
- requireFiniteAngle(angleDeg, "ShapeGroup.rotateY()");
33837
+ requireFiniteAngle$1(angleDeg, "ShapeGroup.rotateY()");
33012
33838
  if (options == null ? void 0 : options.pivot) requireVec3Pivot(options.pivot, "ShapeGroup.rotateY()");
33013
33839
  return this.rotateAroundAxis([0, 1, 0], angleDeg, options == null ? void 0 : options.pivot);
33014
33840
  }
33015
33841
  /** Rotate the group around the Z axis. */
33016
33842
  rotateZ(angleDeg, options) {
33017
- requireFiniteAngle(angleDeg, "ShapeGroup.rotateZ()");
33843
+ requireFiniteAngle$1(angleDeg, "ShapeGroup.rotateZ()");
33018
33844
  if (options == null ? void 0 : options.pivot) requireVec3Pivot(options.pivot, "ShapeGroup.rotateZ()");
33019
33845
  return this.rotateAroundAxis([0, 0, 1], angleDeg, options == null ? void 0 : options.pivot);
33020
33846
  }
@@ -33057,7 +33883,8 @@ class ShapeGroup {
33057
33883
  "ShapeGroup.transform only supports 3D children (Shape/ShapeGroup). For Sketch children, use 2D transforms (translate/rotate/scale/mirror)."
33058
33884
  );
33059
33885
  }),
33060
- this.childNames
33886
+ this.childNames,
33887
+ this.childTags
33061
33888
  );
33062
33889
  transformGroupPortsHelper(this, next, matrix);
33063
33890
  return transformGroupRefs(this, next, matrix);
@@ -33125,7 +33952,7 @@ class ShapeGroup {
33125
33952
  * ```
33126
33953
  */
33127
33954
  withReferences(refs) {
33128
- const next = new ShapeGroup(this.children, this.childNames);
33955
+ const next = new ShapeGroup(this.children, this.childNames, this.childTags);
33129
33956
  const merged = applyPlacementReferenceInput(getGroupRefs(this), refs);
33130
33957
  return setGroupRefs(next, merged);
33131
33958
  }
@@ -33193,7 +34020,7 @@ class ShapeGroup {
33193
34020
  /** Attach named connectors — attachment points that survive transforms.
33194
34021
  * Connectors can be bare (position + orientation) or typed (with connectorType/gender for compatibility matching). */
33195
34022
  withConnectors(connectors) {
33196
- const next = new ShapeGroup(this.children, this.childNames);
34023
+ const next = new ShapeGroup(this.children, this.childNames, this.childTags);
33197
34024
  copyGroupRefs(this, next);
33198
34025
  const existing = getGroupPorts(this);
33199
34026
  const incoming = normalizeConnectorMapInput(connectors);
@@ -33243,7 +34070,7 @@ class ShapeGroup {
33243
34070
  }
33244
34071
  function group(...items) {
33245
34072
  const normalized = normalizeGroupInputs(items);
33246
- return new ShapeGroup(normalized.children, normalized.childNames);
34073
+ return new ShapeGroup(normalized.children, normalized.childNames, normalized.childTags);
33247
34074
  }
33248
34075
  function getTargetPortsForGroup(target) {
33249
34076
  if (target instanceof Shape$1) {
@@ -35418,7 +36245,7 @@ function buildSdfFunctionDefinition(source, options) {
35418
36245
  jsExpression: expression,
35419
36246
  ...shader.ok ? { shaderExpression: shader.expression } : { shaderUnsupportedReason: shader.reason },
35420
36247
  raymarchStepLimit: resolveRaymarchStepLimit(options.bounds, options.maxStep),
35421
- ...options.lipschitz !== void 0 ? { raymarchLipschitz: requirePositiveFinite$1(options.lipschitz, "sdf.fromFunction() lipschitz") } : {}
36248
+ ...options.lipschitz !== void 0 ? { raymarchLipschitz: requirePositiveFinite$2(options.lipschitz, "sdf.fromFunction() lipschitz") } : {}
35422
36249
  };
35423
36250
  }
35424
36251
  function extractSdfExpression(source) {
@@ -35586,7 +36413,7 @@ function formatNumericLiteralsForGlsl(source) {
35586
36413
  return result;
35587
36414
  }
35588
36415
  function resolveRaymarchStepLimit(bounds, maxStep) {
35589
- if (maxStep !== void 0) return requirePositiveFinite$1(maxStep, "sdf.fromFunction() maxStep");
36416
+ if (maxStep !== void 0) return requirePositiveFinite$2(maxStep, "sdf.fromFunction() maxStep");
35590
36417
  const dx = bounds.max[0] - bounds.min[0];
35591
36418
  const dy = bounds.max[1] - bounds.min[1];
35592
36419
  const dz = bounds.max[2] - bounds.min[2];
@@ -35594,7 +36421,7 @@ function resolveRaymarchStepLimit(bounds, maxStep) {
35594
36421
  if (!Number.isFinite(diagonal) || diagonal <= 0) return 0.1;
35595
36422
  return Math.max(0.025, Math.min(0.5, diagonal / 240));
35596
36423
  }
35597
- function requirePositiveFinite$1(value, label) {
36424
+ function requirePositiveFinite$2(value, label) {
35598
36425
  if (!Number.isFinite(value) || value <= 0) throw new Error(`${label} must be a positive finite number.`);
35599
36426
  return value;
35600
36427
  }
@@ -35621,6 +36448,199 @@ class SurfacePattern {
35621
36448
  this.constants = constants;
35622
36449
  }
35623
36450
  }
36451
+ const typedSurfacePatterns = /* @__PURE__ */ new WeakMap();
36452
+ function getTypedSurfacePattern(pattern) {
36453
+ return typedSurfacePatterns.get(pattern);
36454
+ }
36455
+ class Pattern2D extends SurfacePattern {
36456
+ constructor(body) {
36457
+ super(body);
36458
+ }
36459
+ /** Add this pattern to one or more patterns or constant height offsets. */
36460
+ add(...patterns) {
36461
+ throw new Error("Pattern2D values are created by sdf.pattern2d().");
36462
+ }
36463
+ /** Subtract another pattern or constant height offset from this pattern. */
36464
+ subtract(pattern) {
36465
+ throw new Error("Pattern2D values are created by sdf.pattern2d().");
36466
+ }
36467
+ /** Multiply this pattern by one or more patterns or numeric scale factors. */
36468
+ multiply(...patterns) {
36469
+ throw new Error("Pattern2D values are created by sdf.pattern2d().");
36470
+ }
36471
+ /** Keep the lower height between this pattern and one or more other patterns. */
36472
+ min(...patterns) {
36473
+ throw new Error("Pattern2D values are created by sdf.pattern2d().");
36474
+ }
36475
+ /** Keep the higher height between this pattern and one or more other patterns. */
36476
+ max(...patterns) {
36477
+ throw new Error("Pattern2D values are created by sdf.pattern2d().");
36478
+ }
36479
+ /** Limit pattern height to the inclusive `[min, max]` range in millimeters. */
36480
+ clamp(min2, max2) {
36481
+ throw new Error("Pattern2D values are created by sdf.pattern2d().");
36482
+ }
36483
+ /** Convert negative heights to positive heights. */
36484
+ abs() {
36485
+ throw new Error("Pattern2D values are created by sdf.pattern2d().");
36486
+ }
36487
+ /** Flip the pattern height sign. */
36488
+ negate() {
36489
+ throw new Error("Pattern2D values are created by sdf.pattern2d().");
36490
+ }
36491
+ }
36492
+ class Pattern2DImpl extends Pattern2D {
36493
+ constructor(node) {
36494
+ super(emitSurfacePatternJsExpression(node));
36495
+ __publicField(this, "node");
36496
+ this.node = node;
36497
+ typedSurfacePatterns.set(this, node);
36498
+ }
36499
+ add(...patterns) {
36500
+ return new Pattern2DImpl({ kind: "surfacePattern:add", children: [this.node, ...patterns.map(patternNodeFromInput)] });
36501
+ }
36502
+ subtract(pattern) {
36503
+ return this.add(new Pattern2DImpl({ kind: "surfacePattern:negate", child: patternNodeFromInput(pattern) }));
36504
+ }
36505
+ multiply(...patterns) {
36506
+ return new Pattern2DImpl({ kind: "surfacePattern:multiply", children: [this.node, ...patterns.map(patternNodeFromInput)] });
36507
+ }
36508
+ min(...patterns) {
36509
+ return new Pattern2DImpl({ kind: "surfacePattern:min", children: [this.node, ...patterns.map(patternNodeFromInput)] });
36510
+ }
36511
+ max(...patterns) {
36512
+ return new Pattern2DImpl({ kind: "surfacePattern:max", children: [this.node, ...patterns.map(patternNodeFromInput)] });
36513
+ }
36514
+ clamp(min2, max2) {
36515
+ const lo = requireFinite$8(min2, "Pattern2D.clamp() min");
36516
+ const hi = requireFinite$8(max2, "Pattern2D.clamp() max");
36517
+ if (lo > hi) throw new Error(`Pattern2D.clamp() min must be <= max. Received: ${lo} > ${hi}`);
36518
+ return new Pattern2DImpl({ kind: "surfacePattern:clamp", child: this.node, min: lo, max: hi });
36519
+ }
36520
+ abs() {
36521
+ return new Pattern2DImpl({ kind: "surfacePattern:abs", child: this.node });
36522
+ }
36523
+ negate() {
36524
+ return new Pattern2DImpl({ kind: "surfacePattern:negate", child: this.node });
36525
+ }
36526
+ }
36527
+ class Pattern2DBuilder {
36528
+ /** Create a constant-height pattern in millimeters. */
36529
+ constant(value = 0) {
36530
+ return new Pattern2DImpl({ kind: "surfacePattern:constant", value: requireFinite$8(value, "sdf.pattern2d().constant() value") });
36531
+ }
36532
+ /** Create a sinusoidal wave pattern in UV space. */
36533
+ sineWave(options) {
36534
+ return new Pattern2DImpl({
36535
+ kind: "surfacePattern:sineWave",
36536
+ direction: normalizeDirection$1(options.direction ?? [1, 0], "sdf.pattern2d().sineWave() direction"),
36537
+ wavelength: requirePositiveFinite$1(options.wavelength, "sdf.pattern2d().sineWave() wavelength"),
36538
+ amplitude: requireFinite$8(options.amplitude ?? 1, "sdf.pattern2d().sineWave() amplitude"),
36539
+ phase: requireFinite$8(options.phase ?? 0, "sdf.pattern2d().sineWave() phase"),
36540
+ bias: requireFinite$8(options.bias ?? 0, "sdf.pattern2d().sineWave() bias")
36541
+ });
36542
+ }
36543
+ /** Create recessed stripe bands in UV space. */
36544
+ stripes(options) {
36545
+ return new Pattern2DImpl({
36546
+ kind: "surfacePattern:stripes",
36547
+ direction: normalizeDirection$1(options.direction ?? [1, 0], "sdf.pattern2d().stripes() direction"),
36548
+ spacing: requirePositiveFinite$1(options.spacing, "sdf.pattern2d().stripes() spacing"),
36549
+ width: requirePositiveFinite$1(options.width, "sdf.pattern2d().stripes() width"),
36550
+ depth: requireNonNegativeFinite$1(options.depth ?? 1, "sdf.pattern2d().stripes() depth")
36551
+ });
36552
+ }
36553
+ /** Create an over-under woven relief pattern in UV space. */
36554
+ overUnderWeave(options) {
36555
+ return new Pattern2DImpl({
36556
+ kind: "surfacePattern:overUnderWeave",
36557
+ spacing: normalizeVec2(options.spacing, "sdf.pattern2d().overUnderWeave() spacing", requirePositiveFinite$1),
36558
+ threadWidth: normalizeVec2(options.threadWidth, "sdf.pattern2d().overUnderWeave() threadWidth", requirePositiveFinite$1),
36559
+ depth: requireNonNegativeFinite$1(options.depth ?? 0.8, "sdf.pattern2d().overUnderWeave() depth"),
36560
+ underScale: requireNonNegativeFinite$1(options.underScale ?? 0.15, "sdf.pattern2d().overUnderWeave() underScale")
36561
+ });
36562
+ }
36563
+ }
36564
+ function pattern2d() {
36565
+ return new Pattern2DBuilder();
36566
+ }
36567
+ function patternNodeFromInput(input) {
36568
+ if (input instanceof SurfacePattern) {
36569
+ const node = getTypedSurfacePattern(input);
36570
+ if (node) return node;
36571
+ }
36572
+ if (typeof input === "number") {
36573
+ return { kind: "surfacePattern:constant", value: requireFinite$8(input, "Pattern2D numeric input") };
36574
+ }
36575
+ throw new Error("Pattern2D composition expects another typed Pattern2D or a number.");
36576
+ }
36577
+ function requireFinite$8(value, label) {
36578
+ if (typeof value !== "number" || !Number.isFinite(value)) {
36579
+ throw new Error(`${label} must be a finite number. Received: ${String(value)}`);
36580
+ }
36581
+ return value;
36582
+ }
36583
+ function requirePositiveFinite$1(value, label) {
36584
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
36585
+ throw new Error(`${label} must be a positive finite number. Received: ${String(value)}`);
36586
+ }
36587
+ return value;
36588
+ }
36589
+ function requireNonNegativeFinite$1(value, label) {
36590
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
36591
+ throw new Error(`${label} must be a non-negative finite number. Received: ${String(value)}`);
36592
+ }
36593
+ return value;
36594
+ }
36595
+ function normalizeVec2(value, label, validate) {
36596
+ if (typeof value === "number") {
36597
+ const n = validate(value, label);
36598
+ return [n, n];
36599
+ }
36600
+ return [validate(value[0], `${label}[0]`), validate(value[1], `${label}[1]`)];
36601
+ }
36602
+ function normalizeDirection$1(value, label) {
36603
+ const x2 = requireFinite$8(value[0], `${label}[0]`);
36604
+ const y2 = requireFinite$8(value[1], `${label}[1]`);
36605
+ const length4 = Math.hypot(x2, y2);
36606
+ if (length4 <= 0) throw new Error(`${label} must not be the zero vector.`);
36607
+ return [x2 / length4, y2 / length4];
36608
+ }
36609
+ function f$2(value) {
36610
+ if (!Number.isFinite(value)) return "0";
36611
+ return Number(value.toPrecision(12)).toString();
36612
+ }
36613
+ function emitSurfacePatternJsExpression(node) {
36614
+ switch (node.kind) {
36615
+ case "surfacePattern:constant":
36616
+ return f$2(node.value);
36617
+ case "surfacePattern:sineWave": {
36618
+ const coord = `(u * ${f$2(node.direction[0])} + v * ${f$2(node.direction[1])})`;
36619
+ const phase = `(${coord} * ${f$2(2 * Math.PI / node.wavelength)} + ${f$2(node.phase)})`;
36620
+ return `(${f$2(node.bias)} + Math.sin(${phase}) * ${f$2(node.amplitude)})`;
36621
+ }
36622
+ case "surfacePattern:stripes": {
36623
+ const coord = `(u * ${f$2(node.direction[0])} + v * ${f$2(node.direction[1])})`;
36624
+ return `(function(){var c=${coord};var d=Math.abs(c - Math.round(c / ${f$2(node.spacing)}) * ${f$2(node.spacing)});var p=Math.max(0, 1 - d / ${f$2(node.width * 0.5)});return -(p * p) * ${f$2(node.depth)};})()`;
36625
+ }
36626
+ case "surfacePattern:overUnderWeave":
36627
+ return `(function(){var su=u/${f$2(node.spacing[0])};var sv=v/${f$2(node.spacing[1])};var du=Math.abs(su - Math.round(su))*${f$2(node.spacing[0])};var dv=Math.abs(sv - Math.round(sv))*${f$2(node.spacing[1])};var pU=Math.max(0,1-du/${f$2(node.threadWidth[0] * 0.5)});pU*=pU;var pV=Math.max(0,1-dv/${f$2(node.threadWidth[1] * 0.5)});pV*=pV;var checker=((Math.round(su)&65535)+(Math.round(sv)&65535))&1;var top=checker?pV:pU;var bot=checker?pU:pV;return -Math.max(top,bot*${f$2(node.underScale)})*${f$2(node.depth)};})()`;
36628
+ case "surfacePattern:abs":
36629
+ return `Math.abs(${emitSurfacePatternJsExpression(node.child)})`;
36630
+ case "surfacePattern:negate":
36631
+ return `(-(${emitSurfacePatternJsExpression(node.child)}))`;
36632
+ case "surfacePattern:add":
36633
+ return node.children.length === 0 ? "0" : `(${node.children.map(emitSurfacePatternJsExpression).join(" + ")})`;
36634
+ case "surfacePattern:multiply":
36635
+ return node.children.length === 0 ? "1" : `(${node.children.map(emitSurfacePatternJsExpression).join(" * ")})`;
36636
+ case "surfacePattern:min":
36637
+ return node.children.length === 0 ? "0" : `Math.min(${node.children.map(emitSurfacePatternJsExpression).join(", ")})`;
36638
+ case "surfacePattern:max":
36639
+ return node.children.length === 0 ? "0" : `Math.max(${node.children.map(emitSurfacePatternJsExpression).join(", ")})`;
36640
+ case "surfacePattern:clamp":
36641
+ return `Math.min(${f$2(node.max)}, Math.max(${f$2(node.min)}, ${emitSurfacePatternJsExpression(node.child)}))`;
36642
+ }
36643
+ }
35624
36644
  const SCULPT_MATERIAL_PRESETS = {
35625
36645
  ceramic: {
35626
36646
  color: "#f4f0e6",
@@ -35690,6 +36710,18 @@ function requirePositiveFinite(value, label) {
35690
36710
  }
35691
36711
  return value;
35692
36712
  }
36713
+ function requirePositiveInteger(value, label) {
36714
+ if (typeof value !== "number" || !Number.isFinite(value) || !Number.isInteger(value) || value < 1) {
36715
+ throw new Error(`${label} must be a positive integer. Received: ${String(value)}`);
36716
+ }
36717
+ return value;
36718
+ }
36719
+ function requireNonNegativeFinite(value, label) {
36720
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
36721
+ throw new Error(`${label} must be a non-negative finite number. Received: ${String(value)}`);
36722
+ }
36723
+ return value;
36724
+ }
35693
36725
  function resolveBlendRadius(input, label, fallback = 4) {
35694
36726
  if (typeof input === "number") return requirePositiveFinite(input, `${label} radius`);
35695
36727
  if ((input == null ? void 0 : input.radius) !== void 0) return requirePositiveFinite(input.radius, `${label} radius`);
@@ -35936,6 +36968,29 @@ class SdfShape {
35936
36968
  clipBox(x2, y2, z2) {
35937
36969
  return this.intersect(box$1(x2, y2, z2));
35938
36970
  }
36971
+ /** Keep only the material where this shape overlaps another SDF pattern. */
36972
+ fillWith(pattern) {
36973
+ if (!(pattern instanceof SdfShape)) {
36974
+ throw new Error("SdfShape.fillWith() expects an SdfShape pattern, such as sdf.gyroid({ cellSize, wallThickness }).");
36975
+ }
36976
+ return this.intersect(pattern);
36977
+ }
36978
+ /** Keep only the gyroid lattice inside this shape. */
36979
+ fillWithGyroid(options) {
36980
+ return this.fillWith(gyroid(options));
36981
+ }
36982
+ /** Keep only the Schwarz-P lattice inside this shape. */
36983
+ fillWithSchwarzP(options) {
36984
+ return this.fillWith(schwarzP(options));
36985
+ }
36986
+ /** Keep only the diamond TPMS lattice inside this shape. */
36987
+ fillWithDiamond(options) {
36988
+ return this.fillWith(diamond(options));
36989
+ }
36990
+ /** Keep only the lidinoid TPMS lattice inside this shape. */
36991
+ fillWithLidinoid(options) {
36992
+ return this.fillWith(lidinoid(options));
36993
+ }
35939
36994
  /** Smooth union — blends shapes together with a smooth radius. */
35940
36995
  smoothUnion(other, radius) {
35941
36996
  return this.withNode({ kind: "sdf:smoothUnion", children: [this._node, other._node], radius });
@@ -35996,6 +37051,21 @@ class SdfShape {
35996
37051
  repeat(spacing, count) {
35997
37052
  return this.withNode({ kind: "sdf:repeat", child: this._node, spacing, count: count ?? [0, 0, 0] });
35998
37053
  }
37054
+ /**
37055
+ * Arrange this SDF in a circular array around the Z axis.
37056
+ *
37057
+ * The source shape is translated by `offset` in +X before arraying. This uses
37058
+ * angular domain folding, so evaluation stays O(1): the source SDF is sampled
37059
+ * twice no matter how many copies are requested.
37060
+ */
37061
+ circularArray(count, offset2 = 0) {
37062
+ return this.withNode({
37063
+ kind: "sdf:circularArray",
37064
+ child: this._node,
37065
+ count: requirePositiveInteger(count, "SdfShape.circularArray() count"),
37066
+ offset: requireNonNegativeFinite(offset2, "SdfShape.circularArray() offset")
37067
+ });
37068
+ }
35999
37069
  /** Hollow out, keeping only a shell of given thickness. */
36000
37070
  shell(thickness) {
36001
37071
  return this.withNode({ kind: "sdf:shell", child: this._node, thickness });
@@ -36007,8 +37077,8 @@ class SdfShape {
36007
37077
  * // Function displacement
36008
37078
  * shape.displace((x, y, z) => Math.sin(x) * 0.5)
36009
37079
  *
36010
- * // Pattern displacement (e.g. basketWeave)
36011
- * shape.displace(sdf.basketWeave({ threads: 16, spacing: 3 }))
37080
+ * // Pattern displacement from a 3D SDF field
37081
+ * shape.displace(sdf.knurl({ pitch: 2, depth: 0.3 }))
36012
37082
  * ```
36013
37083
  */
36014
37084
  displace(fn, constants) {
@@ -36032,10 +37102,18 @@ class SdfShape {
36032
37102
  * UV coordinates are in **surface millimeters** — patterns defined with `spacing: 3`
36033
37103
  * always produce 3mm spacing, regardless of shape size.
36034
37104
  *
37105
+ * Prefer `sdf.pattern2d()` or built-in surface patterns when the relief should
37106
+ * stay on the native shader and meshing path. Callback functions are supported
37107
+ * for experimentation, but they are opaque to the typed pattern optimizer.
37108
+ *
36035
37109
  * ```js
36036
- * // Surface-following basket weave — auto-detects sphere UV
37110
+ * // Native typed pattern — auto-detects sphere UV
37111
+ * const p = sdf.pattern2d()
37112
+ * const ribs = p.stripes({ spacing: 3, width: 0.8, depth: 0.35 })
37113
+ * .add(p.sineWave({ direction: [0, 1], wavelength: 14, amplitude: 0.08 }))
37114
+ *
36037
37115
  * sdf.sphere(27).shell(3)
36038
- * .surfaceDisplace(sdf.basketWeave({ spacing: 3, depth: 0.8 }))
37116
+ * .surfaceDisplace(ribs)
36039
37117
  * .toShape()
36040
37118
  *
36041
37119
  * // Custom 2D pattern via function
@@ -36045,15 +37123,18 @@ class SdfShape {
36045
37123
  surfaceDisplace(pattern, options) {
36046
37124
  let body;
36047
37125
  let constants;
37126
+ let typedPattern;
36048
37127
  if (pattern instanceof SurfacePattern) {
36049
37128
  body = pattern.body;
36050
37129
  constants = pattern.constants;
37130
+ typedPattern = getTypedSurfacePattern(pattern);
36051
37131
  } else {
36052
37132
  body = extractFunctionBody(pattern);
36053
37133
  }
36054
37134
  return this.withNode({
36055
37135
  kind: "sdf:surfaceDisplace",
36056
37136
  child: this._node,
37137
+ ...typedPattern ? { pattern: typedPattern } : {},
36057
37138
  patternBody: body,
36058
37139
  constants,
36059
37140
  ...(options == null ? void 0 : options.uv) ? { uvMode: options.uv } : {},
@@ -36272,24 +37353,10 @@ function weave(options) {
36272
37353
  });
36273
37354
  }
36274
37355
  function basketWeave(options) {
36275
- const SP = (options == null ? void 0 : options.spacing) ?? 3;
36276
- const TW = (options == null ? void 0 : options.threadWidth) ?? 1.5;
36277
- const D2 = (options == null ? void 0 : options.depth) ?? 0.8;
36278
- const hw = TW * 0.5;
36279
- const body = `(function() {
36280
- var su = u / ${SP};
36281
- var sv = v / ${SP};
36282
- var du = Math.abs(su - Math.round(su)) * ${SP};
36283
- var dv = Math.abs(sv - Math.round(sv)) * ${SP};
36284
- var hw = ${hw};
36285
- var pU = Math.max(0, 1 - du / hw); pU *= pU;
36286
- var pV = Math.max(0, 1 - dv / hw); pV *= pV;
36287
- var checker = ((Math.round(su) & 65535) + (Math.round(sv) & 65535)) & 1;
36288
- var top = checker ? pV : pU;
36289
- var bot = checker ? pU : pV;
36290
- return -(top > bot * 0.15 ? top : bot * 0.15) * ${D2};
36291
- })()`;
36292
- return new SurfacePattern(body);
37356
+ const SP = requirePositiveFinite((options == null ? void 0 : options.spacing) ?? 3, "sdf.basketWeave() spacing");
37357
+ const TW = requirePositiveFinite((options == null ? void 0 : options.threadWidth) ?? 1.5, "sdf.basketWeave() threadWidth");
37358
+ const D2 = requireNonNegativeFinite((options == null ? void 0 : options.depth) ?? 0.8, "sdf.basketWeave() depth");
37359
+ return pattern2d().overUnderWeave({ spacing: SP, threadWidth: TW, depth: D2 });
36293
37360
  }
36294
37361
  function fromFunction(fn, options) {
36295
37362
  if (!options || typeof options !== "object") {
@@ -36322,6 +37389,9 @@ function bend(shape, radius) {
36322
37389
  function repeat(shape, spacing, count) {
36323
37390
  return shape.repeat(spacing, count);
36324
37391
  }
37392
+ function circularArray(shape, count, offset2 = 0) {
37393
+ return shape.circularArray(count, offset2);
37394
+ }
36325
37395
  function resolveTpmsOptions(options) {
36326
37396
  const wallThickness = options.wallThickness;
36327
37397
  const thickness = wallThickness ?? options.thickness;
@@ -36762,12 +37832,16 @@ const sdf = {
36762
37832
  weave,
36763
37833
  /** Basket weave surface pattern — threads with over-under crossings in UV space. Returns a SurfacePattern for use with `.surfaceDisplace()`. */
36764
37834
  basketWeave,
37835
+ /** Create typed, composable 2D surface patterns for `.surfaceDisplace()`. */
37836
+ pattern2d,
36765
37837
  /** Twist an SDF shape around the Z axis. */
36766
37838
  twist,
36767
37839
  /** Bend an SDF shape around the Z axis. */
36768
37840
  bend,
36769
37841
  /** Repeat an SDF shape in space. */
36770
37842
  repeat,
37843
+ /** Arrange an SDF shape in a circular array around the Z axis with O(1) folded-domain evaluation. */
37844
+ circularArray,
36771
37845
  /** A 2D surface pattern — a heightmap function for use with `.surfaceDisplace()`. */
36772
37846
  SurfacePattern,
36773
37847
  /** Create a custom SDF from one expression; shader-safe expressions raymarch directly. */
@@ -39216,17 +40290,17 @@ let Shape$1 = class Shape {
39216
40290
  bottom: "top"
39217
40291
  };
39218
40292
  const uvMap = {
39219
- front: (u22, v2, p22) => [u22, -p22, v2],
40293
+ front: (u22, v22, p22) => [u22, -p22, v22],
39220
40294
  // front = −Y face, outward = −Y
39221
- back: (u22, v2, p22) => [u22, p22, v2],
40295
+ back: (u22, v22, p22) => [u22, p22, v22],
39222
40296
  // back = +Y face, outward = +Y
39223
- left: (u22, v2, p22) => [-p22, u22, v2],
40297
+ left: (u22, v22, p22) => [-p22, u22, v22],
39224
40298
  // left = −X face, outward = −X
39225
- right: (u22, v2, p22) => [p22, u22, v2],
40299
+ right: (u22, v22, p22) => [p22, u22, v22],
39226
40300
  // right = +X face, outward = +X
39227
- top: (u22, v2, p22) => [u22, v2, p22],
40301
+ top: (u22, v22, p22) => [u22, v22, p22],
39228
40302
  // top = +Z face, outward = +Z
39229
- bottom: (u22, v2, p22) => [u22, v2, -p22]
40303
+ bottom: (u22, v22, p22) => [u22, v22, -p22]
39230
40304
  // bottom = −Z face, outward = −Z
39231
40305
  };
39232
40306
  const selfAnchor = opposite[face];
@@ -41757,13 +42831,16 @@ class SolvedAssembly {
41757
42831
  * @category Assembly
41758
42832
  */
41759
42833
  toGroup() {
42834
+ var _a3;
41760
42835
  const children = [];
41761
42836
  const childNames = [];
41762
- for (const [name] of this.parts) {
42837
+ const childTags = [];
42838
+ for (const [name, rec] of this.parts) {
41763
42839
  children.push(this.getPart(name));
41764
42840
  childNames.push(name);
42841
+ childTags.push(normalizeSceneTags((_a3 = rec.metadata) == null ? void 0 : _a3.tags, `Assembly part "${name}" metadata.tags`));
41765
42842
  }
41766
- return new ShapeGroup(children, childNames);
42843
+ return new ShapeGroup(children, childNames, childTags);
41767
42844
  }
41768
42845
  /**
41769
42846
  * Return an array of named scene objects for the viewport renderer.
@@ -41797,17 +42874,18 @@ class SolvedAssembly {
41797
42874
  const used = usedByPart.get(partName);
41798
42875
  if (used && used.length > 0) markShapePortsUsed(shape, used);
41799
42876
  };
41800
- const appendGroupChildren = (grp, prefix, partName, out2) => {
42877
+ const appendGroupChildren = (grp, prefix, partName, out2, inheritedTags = []) => {
41801
42878
  grp.children.forEach((child, index2) => {
41802
42879
  const childName = grp.childName(index2);
41803
42880
  const label = childName ? `${prefix}.${childName}` : `${prefix}.${index2 + 1}`;
42881
+ const tags = mergeSceneTags(inheritedTags, grp.tagsForChild(index2));
41804
42882
  if (child instanceof ShapeGroup) {
41805
- appendGroupChildren(child, label, partName, out2);
42883
+ appendGroupChildren(child, label, partName, out2, tags);
41806
42884
  return;
41807
42885
  }
41808
42886
  if (child instanceof Shape$1) {
41809
42887
  markUsedOnShape(child, partName);
41810
- out2.push({ name: label, shape: child });
42888
+ out2.push({ name: label, shape: child, ...tags.length > 0 ? { tags } : {} });
41811
42889
  }
41812
42890
  });
41813
42891
  };
@@ -42173,7 +43251,7 @@ class Assembly {
42173
43251
  *
42174
43252
  * @param name - Unique part name (must not already exist)
42175
43253
  * @param part - The `Shape` or `ShapeGroup` geometry
42176
- * @param options - Optional `{ transform, metadata }` (material, process, qty, etc.)
43254
+ * @param options - Optional `{ transform, metadata }` (material, process, qty, tags, etc.)
42177
43255
  * @returns `this` for chaining
42178
43256
  * @category Assembly
42179
43257
  */
@@ -43127,15 +44205,18 @@ class ImportedAssembly {
43127
44205
  * Any stored placement offset and placement references are forwarded to the group.
43128
44206
  */
43129
44207
  toGroup(state) {
44208
+ var _a3;
43130
44209
  const solved = this._assembly.solve(state);
43131
44210
  const def = this._assembly.describe();
43132
44211
  const children = [];
43133
44212
  const childNames = [];
44213
+ const childTags = [];
43134
44214
  for (const p2 of def.parts) {
43135
44215
  children.push(solved.getPart(p2.name));
43136
44216
  childNames.push(p2.name);
44217
+ childTags.push(normalizeSceneTags((_a3 = p2.metadata) == null ? void 0 : _a3.tags, `Assembly part "${p2.name}" metadata.tags`));
43137
44218
  }
43138
- let result = new ShapeGroup(children, childNames);
44219
+ let result = new ShapeGroup(children, childNames, childTags);
43139
44220
  const [dx, dy, dz] = this._offset;
43140
44221
  if (dx !== 0 || dy !== 0 || dz !== 0) {
43141
44222
  result = result.translate(dx, dy, dz);
@@ -47504,7 +48585,8 @@ function explode(items, options = {}) {
47504
48585
  if (child instanceof ShapeGroup) return explodeGroup(child, p2, depth + 1, total, groupCenter, motion.branchDirection);
47505
48586
  return explodeLeaf(child, explodeAdd(total, leafMotion(child, p2, depth + 1, groupCenter, motion.branchDirection).offset));
47506
48587
  }),
47507
- grp.childNames
48588
+ grp.childNames,
48589
+ grp.children.map((_2, i) => grp.tagsForChild(i))
47508
48590
  );
47509
48591
  };
47510
48592
  const explodeItemNode = (item, path2, depth, inherited, parentCenter, parentDirection) => {
@@ -47540,7 +48622,8 @@ function explode(items, options = {}) {
47540
48622
  if (child instanceof ShapeGroup) return explodeGroup(child, p2, 1, [0, 0, 0], rootCenter, void 0);
47541
48623
  return explodeLeaf(child, nodeMotion(child, p2, 1, rootCenter, void 0).offset);
47542
48624
  }),
47543
- items.childNames
48625
+ items.childNames,
48626
+ items.children.map((_2, i) => items.tagsForChild(i))
47544
48627
  );
47545
48628
  }
47546
48629
  return items.map((item, i) => {
@@ -48309,6 +49392,398 @@ function spurGear(options) {
48309
49392
  });
48310
49393
  return attachGearMeta(shapeWithConnectors, meta2);
48311
49394
  }
49395
+ function requirePositive$7(scope, name, value) {
49396
+ if (!isFinitePositive(value)) throw new Error(`${scope}: "${name}" must be > 0`);
49397
+ }
49398
+ function requireOptionalBore(scope, boreDiameter, maxDiameter) {
49399
+ const bore = boreDiameter ?? 0;
49400
+ if (!Number.isFinite(bore) || bore < 0) throw new Error(`${scope}: "boreDiameter" must be >= 0`);
49401
+ if (bore > 0 && bore >= maxDiameter) throw new Error(`${scope}: bore is too large for the body`);
49402
+ return bore;
49403
+ }
49404
+ function resolveSegments(segments) {
49405
+ if (segments === void 0) return void 0;
49406
+ if (!Number.isInteger(segments) || segments < 12) throw new Error('gear body: "segments" must be an integer >= 12');
49407
+ return segments;
49408
+ }
49409
+ function cutBore$1(shape, boreDiameter) {
49410
+ if (boreDiameter <= 0) return shape;
49411
+ const bounds = shape.boundingBox();
49412
+ const height = bounds.max[2] - bounds.min[2] + 2;
49413
+ const cutter = cylinder(height, boreDiameter * 0.5, void 0, 64).translate(0, 0, bounds.min[2] - 1);
49414
+ return shape.subtract(cutter);
49415
+ }
49416
+ function gearBodyDisk(options) {
49417
+ requirePositive$7("gearBodyDisk", "outerRadius", options.outerRadius);
49418
+ requirePositive$7("gearBodyDisk", "faceWidth", options.faceWidth);
49419
+ const bore = requireOptionalBore("gearBodyDisk", options.boreDiameter, options.outerRadius * 2);
49420
+ const segments = resolveSegments(options.segments);
49421
+ const outer = circle2d(options.outerRadius, segments);
49422
+ const profile = bore > 0 ? difference2d(outer, circle2d(bore * 0.5, segments)) : outer;
49423
+ return sketchExtrude(profile, options.faceWidth);
49424
+ }
49425
+ function gearBodyDiskWithHub(options) {
49426
+ requirePositive$7("gearBodyDiskWithHub", "hubDiameter", options.hubDiameter);
49427
+ if (options.hubDiameter >= options.outerRadius * 2) {
49428
+ throw new Error('gearBodyDiskWithHub: "hubDiameter" must be smaller than the outer diameter');
49429
+ }
49430
+ const bore = requireOptionalBore("gearBodyDiskWithHub", options.boreDiameter, options.hubDiameter);
49431
+ const base = gearBodyDisk({ ...options, boreDiameter: 0 });
49432
+ const hubFaceWidth = options.hubFaceWidth ?? options.faceWidth * 1.5;
49433
+ requirePositive$7("gearBodyDiskWithHub", "hubFaceWidth", hubFaceWidth);
49434
+ const hub = cylinder(hubFaceWidth, options.hubDiameter * 0.5, void 0, options.segments).translate(
49435
+ 0,
49436
+ 0,
49437
+ (options.faceWidth - hubFaceWidth) * 0.5
49438
+ );
49439
+ return cutBore$1(base.add(hub), bore);
49440
+ }
49441
+ function gearBodySpoked(options) {
49442
+ requirePositive$7("gearBodySpoked", "outerRadius", options.outerRadius);
49443
+ requirePositive$7("gearBodySpoked", "faceWidth", options.faceWidth);
49444
+ requirePositive$7("gearBodySpoked", "rimWidth", options.rimWidth);
49445
+ requirePositive$7("gearBodySpoked", "hubDiameter", options.hubDiameter);
49446
+ requirePositive$7("gearBodySpoked", "spokeWidth", options.spokeWidth);
49447
+ if (!Number.isInteger(options.spokeCount) || options.spokeCount < 2) {
49448
+ throw new Error('gearBodySpoked: "spokeCount" must be an integer >= 2');
49449
+ }
49450
+ const hubRadius = options.hubDiameter * 0.5;
49451
+ const rimInnerRadius = options.outerRadius - options.rimWidth;
49452
+ if (rimInnerRadius <= hubRadius) throw new Error("gearBodySpoked: rim overlaps the hub");
49453
+ const bore = requireOptionalBore("gearBodySpoked", options.boreDiameter, options.hubDiameter);
49454
+ const segments = resolveSegments(options.segments);
49455
+ const rim = difference2d(circle2d(options.outerRadius, segments), circle2d(rimInnerRadius, segments));
49456
+ const hub = circle2d(hubRadius, segments);
49457
+ const spokeLength = rimInnerRadius - hubRadius + options.spokeWidth;
49458
+ const spokeCenter = hubRadius + spokeLength * 0.5 - options.spokeWidth * 0.5;
49459
+ const spoke = sketchTranslate(rect(spokeLength, options.spokeWidth), spokeCenter, 0);
49460
+ const spokes = [];
49461
+ for (let i = 0; i < options.spokeCount; i++) {
49462
+ spokes.push(sketchRotateAround(spoke, 360 / options.spokeCount * i, [0, 0]));
49463
+ }
49464
+ const profile = bore > 0 ? difference2d(union2d(rim, hub, ...spokes), circle2d(bore * 0.5, segments)) : union2d(rim, hub, ...spokes);
49465
+ return sketchExtrude(profile, options.faceWidth);
49466
+ }
49467
+ function gearBodyFromProfile(profile, options) {
49468
+ if (!(profile instanceof Sketch)) throw new Error('gearBodyFromProfile: "profile" must be a Sketch');
49469
+ requirePositive$7("gearBodyFromProfile", "faceWidth", options.faceWidth);
49470
+ const bore = options.boreDiameter ?? 0;
49471
+ if (!Number.isFinite(bore) || bore < 0) throw new Error('gearBodyFromProfile: "boreDiameter" must be >= 0');
49472
+ return cutBore$1(sketchExtrude(profile, options.faceWidth), bore);
49473
+ }
49474
+ function requirePositive$6(scope, name, value) {
49475
+ if (!isFinitePositive(value)) throw new Error(`${scope}: "${name}" must be > 0`);
49476
+ }
49477
+ function requireFiniteAngle(scope, name, value) {
49478
+ if (value !== void 0 && !Number.isFinite(value)) throw new Error(`${scope}: "${name}" must be finite`);
49479
+ }
49480
+ function cutBore(shape, boreDiameter) {
49481
+ if (boreDiameter <= 0) return shape;
49482
+ const bounds = shape.boundingBox();
49483
+ const height = bounds.max[2] - bounds.min[2] + 2;
49484
+ const cutter = cylinder(height, boreDiameter * 0.5, void 0, 64).translate(0, 0, bounds.min[2] - 1);
49485
+ return shape.subtract(cutter);
49486
+ }
49487
+ function bodyOuterRadius(shape) {
49488
+ const bounds = shape.boundingBox();
49489
+ return Math.max(Math.abs(bounds.min[0]), Math.abs(bounds.max[0]), Math.abs(bounds.min[1]), Math.abs(bounds.max[1]));
49490
+ }
49491
+ function buildSpurTeethRegion(options, name, faceWidth) {
49492
+ const scope = "driveWheel.addSpurTeethBetween";
49493
+ const teethOnFullCircle = options.teethOnFullCircle;
49494
+ if (!Number.isInteger(teethOnFullCircle) || teethOnFullCircle < 6) {
49495
+ throw new Error(`${scope}: "teethOnFullCircle" must be an integer >= 6`);
49496
+ }
49497
+ const toothCount = options.toothCount;
49498
+ if (!Number.isInteger(toothCount) || toothCount < 1 || toothCount > teethOnFullCircle) {
49499
+ throw new Error(`${scope}: "toothCount" must be an integer in [1, teethOnFullCircle]`);
49500
+ }
49501
+ const firstTooth = options.firstTooth ?? 0;
49502
+ if (!Number.isInteger(firstTooth) || firstTooth < 0 || firstTooth >= teethOnFullCircle) {
49503
+ throw new Error(`${scope}: "firstTooth" must be an integer in [0, teethOnFullCircle)`);
49504
+ }
49505
+ let normalized;
49506
+ try {
49507
+ normalized = normalizeSpurGearOptions({ ...options, teeth: teethOnFullCircle, faceWidth, boreDiameter: 0 });
49508
+ } catch (error) {
49509
+ remapErrorPrefix(error, "spurGear", scope);
49510
+ }
49511
+ const gearMeta = buildSpurGearMeta(normalized);
49512
+ const pitchStepDeg = 360 / teethOnFullCircle;
49513
+ const fromAngleDeg = firstTooth * pitchStepDeg - pitchStepDeg * 0.5;
49514
+ const toAngleDeg = (firstTooth + toothCount - 1) * pitchStepDeg + pitchStepDeg * 0.5;
49515
+ const profile = buildSpurToothRegionProfile(gearMeta, firstTooth, toothCount, normalized.segmentsPerTooth);
49516
+ return {
49517
+ shape: sketchExtrude(profile, faceWidth),
49518
+ gearMeta,
49519
+ meta: {
49520
+ name,
49521
+ kind: "spurTeeth",
49522
+ fromAngleDeg,
49523
+ toAngleDeg,
49524
+ outerRadius: gearMeta.outerRadius,
49525
+ rootRadius: gearMeta.rootRadius,
49526
+ pitchRadius: gearMeta.pitchRadius,
49527
+ module: normalized.module,
49528
+ teethOnFullCircle,
49529
+ toothCount,
49530
+ faceWidth
49531
+ }
49532
+ };
49533
+ }
49534
+ function buildSolidArcRegion(options, name, faceWidth) {
49535
+ const scope = "driveWheel.addSolidArcBetween";
49536
+ requirePositive$6(scope, "outerRadius", options.outerRadius);
49537
+ const innerRadius = options.innerRadius ?? 0;
49538
+ if (!Number.isFinite(innerRadius) || innerRadius < 0) throw new Error(`${scope}: "innerRadius" must be >= 0`);
49539
+ if (innerRadius >= options.outerRadius) throw new Error(`${scope}: "innerRadius" must be smaller than "outerRadius"`);
49540
+ const sweepDeg = normalizedSweep(scope, options.fromAngleDeg, options.toAngleDeg);
49541
+ return {
49542
+ shape: sketchExtrude(buildSolidArcProfile(options, sweepDeg), faceWidth),
49543
+ meta: {
49544
+ name,
49545
+ kind: "solidArc",
49546
+ fromAngleDeg: options.fromAngleDeg,
49547
+ toAngleDeg: options.fromAngleDeg + sweepDeg,
49548
+ innerRadius,
49549
+ outerRadius: options.outerRadius,
49550
+ faceWidth
49551
+ }
49552
+ };
49553
+ }
49554
+ function normalizedSweep(scope, fromAngleDeg, toAngleDeg) {
49555
+ if (!Number.isFinite(fromAngleDeg)) throw new Error(`${scope}: "fromAngleDeg" must be finite`);
49556
+ if (!Number.isFinite(toAngleDeg)) throw new Error(`${scope}: "toAngleDeg" must be finite`);
49557
+ let sweep2 = toAngleDeg - fromAngleDeg;
49558
+ while (sweep2 <= 0) sweep2 += 360;
49559
+ if (sweep2 > 360 + EPSILON$1) throw new Error(`${scope}: angular sweep must be <= 360 degrees`);
49560
+ return Math.min(360, sweep2);
49561
+ }
49562
+ function buildSpurToothRegionProfile(meta2, firstTooth, toothCount, segmentsPerTooth) {
49563
+ const tooth = createSpurToothSketch(meta2, segmentsPerTooth);
49564
+ const teeth = [];
49565
+ for (let i = 0; i < toothCount; i++) {
49566
+ teeth.push(sketchRotateAround(tooth, 360 / meta2.teeth * (firstTooth + i), [0, 0]));
49567
+ }
49568
+ return union2d(...teeth);
49569
+ }
49570
+ function buildSolidArcProfile(options, sweepDeg) {
49571
+ const innerRadius = options.innerRadius ?? 0;
49572
+ const segments = options.segments ?? Math.max(16, Math.ceil(sweepDeg / 6));
49573
+ if (!Number.isInteger(segments) || segments < 4) throw new Error('driveWheel.addSolidArcBetween: "segments" must be an integer >= 4');
49574
+ if (Math.abs(sweepDeg - 360) < EPSILON$1) {
49575
+ const outer = circle2d(options.outerRadius, segments);
49576
+ return innerRadius > 0 ? difference2d(outer, circle2d(innerRadius, segments)) : outer;
49577
+ }
49578
+ const start = options.fromAngleDeg * Math.PI / 180;
49579
+ const end = start + sweepDeg * Math.PI / 180;
49580
+ const pts = [];
49581
+ if (innerRadius <= 0) pts.push([0, 0]);
49582
+ addArcPoints(pts, options.outerRadius, start, end, segments, true, true);
49583
+ if (innerRadius > 0) addArcPoints(pts, innerRadius, end, start, segments, true, true);
49584
+ return polygon(pts);
49585
+ }
49586
+ const DRIVE_WHEEL_META_KEY = Symbol.for("forgecad.library.driveWheelMeta");
49587
+ function attachDriveWheelMeta(shape, meta2) {
49588
+ shape[DRIVE_WHEEL_META_KEY] = meta2;
49589
+ return shape;
49590
+ }
49591
+ function readDriveWheelMeta(shape) {
49592
+ const meta2 = shape[DRIVE_WHEEL_META_KEY];
49593
+ return meta2 ?? null;
49594
+ }
49595
+ class DriveWheelBuilder {
49596
+ constructor(options = {}) {
49597
+ __publicField(this, "body");
49598
+ __publicField(this, "faceWidth");
49599
+ __publicField(this, "boreDiameter");
49600
+ __publicField(this, "regions", []);
49601
+ if (options.body !== void 0 && !(options.body instanceof Shape$1)) throw new Error('driveWheel: "body" must be a Shape');
49602
+ if (options.faceWidth !== void 0) requirePositive$6("driveWheel", "faceWidth", options.faceWidth);
49603
+ const boreDiameter = options.boreDiameter ?? 0;
49604
+ if (!Number.isFinite(boreDiameter) || boreDiameter < 0) throw new Error('driveWheel: "boreDiameter" must be >= 0');
49605
+ this.body = options.body;
49606
+ this.faceWidth = options.faceWidth;
49607
+ this.boreDiameter = boreDiameter;
49608
+ }
49609
+ /**
49610
+ * Add an involute spur-tooth window on part of the pitch circle.
49611
+ */
49612
+ addSpurTeethBetween(options) {
49613
+ const faceWidth = this.resolveFaceWidth("driveWheel.addSpurTeethBetween", options.faceWidth);
49614
+ this.regions.push(buildSpurTeethRegion(options, this.resolveName("teeth", options.name), faceWidth));
49615
+ return this;
49616
+ }
49617
+ /**
49618
+ * Add a constant-radius solid arc region such as a dwell, stop, or pusher.
49619
+ */
49620
+ addSolidArcBetween(options) {
49621
+ const faceWidth = this.resolveFaceWidth("driveWheel.addSolidArcBetween", options.faceWidth);
49622
+ this.regions.push(buildSolidArcRegion(options, this.resolveName("arc", options.name), faceWidth));
49623
+ return this;
49624
+ }
49625
+ /**
49626
+ * Add a fully custom region shape while preserving region metadata.
49627
+ */
49628
+ addShapeRegion(name, shape, options = {}) {
49629
+ const scope = "driveWheel.addShapeRegion";
49630
+ if (typeof name !== "string" || name.trim().length === 0) throw new Error(`${scope}: "name" must be a non-empty string`);
49631
+ if (!(shape instanceof Shape$1)) throw new Error(`${scope}: "shape" must be a Shape`);
49632
+ requireFiniteAngle(scope, "fromAngleDeg", options.fromAngleDeg);
49633
+ requireFiniteAngle(scope, "toAngleDeg", options.toAngleDeg);
49634
+ if (options.innerRadius !== void 0 && (!Number.isFinite(options.innerRadius) || options.innerRadius < 0)) {
49635
+ throw new Error(`${scope}: "innerRadius" must be >= 0`);
49636
+ }
49637
+ if (options.outerRadius !== void 0) requirePositive$6(scope, "outerRadius", options.outerRadius);
49638
+ this.regions.push({
49639
+ shape: shape.clone(),
49640
+ meta: {
49641
+ name: this.resolveName("region", name),
49642
+ kind: "custom",
49643
+ ...options
49644
+ }
49645
+ });
49646
+ return this;
49647
+ }
49648
+ /**
49649
+ * Build the final wheel shape with a bore connector and region metadata.
49650
+ */
49651
+ build() {
49652
+ var _a3, _b3;
49653
+ if (this.regions.length === 0 && this.body === void 0) {
49654
+ throw new Error("driveWheel: add a body or at least one region before build()");
49655
+ }
49656
+ const faceWidth = this.resolveBuildFaceWidth();
49657
+ const firstGearRegion = (_a3 = this.regions.find((region) => region.gearMeta)) == null ? void 0 : _a3.gearMeta;
49658
+ if (firstGearRegion && this.boreDiameter * 0.5 >= firstGearRegion.rootRadius - EPSILON$1) {
49659
+ throw new Error("driveWheel: bore is too large for the first spur-tooth region");
49660
+ }
49661
+ const body = ((_b3 = this.body) == null ? void 0 : _b3.clone()) ?? gearBodyDisk({ outerRadius: (firstGearRegion == null ? void 0 : firstGearRegion.rootRadius) ?? this.defaultBodyRadius(), faceWidth });
49662
+ let combined = body;
49663
+ for (const region of this.regions) combined = combined.add(region.shape);
49664
+ combined = cutBore(combined, this.boreDiameter);
49665
+ const withConnectors = combined.withConnectors({
49666
+ bore: connectorFactory(
49667
+ "drive-wheel-bore",
49668
+ { origin: [0, 0, faceWidth / 2], axis: [0, 0, 1], kind: "revolute" },
49669
+ this.measurements(faceWidth)
49670
+ )
49671
+ });
49672
+ return attachDriveWheelMeta(withConnectors, {
49673
+ kind: "driveWheel",
49674
+ faceWidth,
49675
+ boreDiameter: this.boreDiameter,
49676
+ regions: this.regionMetadata(body, faceWidth)
49677
+ });
49678
+ }
49679
+ measurements(faceWidth) {
49680
+ var _a3;
49681
+ const firstGearRegion = (_a3 = this.regions.find((region) => region.gearMeta)) == null ? void 0 : _a3.gearMeta;
49682
+ return {
49683
+ faceWidth,
49684
+ boreDiameter: this.boreDiameter,
49685
+ regionCount: this.regions.length,
49686
+ ...firstGearRegion ? {
49687
+ module: firstGearRegion.module,
49688
+ teethOnFullCircle: firstGearRegion.teeth,
49689
+ pitchRadius: firstGearRegion.pitchRadius,
49690
+ outerRadius: firstGearRegion.outerRadius
49691
+ } : {}
49692
+ };
49693
+ }
49694
+ regionMetadata(body, faceWidth) {
49695
+ return [
49696
+ { name: "body", kind: "body", outerRadius: bodyOuterRadius(body), faceWidth },
49697
+ ...this.regions.map((region) => ({ ...region.meta }))
49698
+ ];
49699
+ }
49700
+ resolveFaceWidth(scope, localFaceWidth) {
49701
+ const faceWidth = localFaceWidth ?? this.faceWidth;
49702
+ if (faceWidth === void 0) throw new Error(`${scope}: "faceWidth" is required unless driveWheel({ faceWidth }) was set`);
49703
+ requirePositive$6(scope, "faceWidth", faceWidth);
49704
+ if (this.faceWidth !== void 0 && localFaceWidth !== void 0 && Math.abs(this.faceWidth - localFaceWidth) > EPSILON$1) {
49705
+ throw new Error(`${scope}: region faceWidth must match driveWheel faceWidth`);
49706
+ }
49707
+ return faceWidth;
49708
+ }
49709
+ resolveBuildFaceWidth() {
49710
+ var _a3;
49711
+ const faceWidth = this.faceWidth ?? ((_a3 = this.regions.find((region) => region.meta.faceWidth !== void 0)) == null ? void 0 : _a3.meta.faceWidth);
49712
+ if (faceWidth === void 0) throw new Error('driveWheel: "faceWidth" is required before build()');
49713
+ return faceWidth;
49714
+ }
49715
+ defaultBodyRadius() {
49716
+ const outerRadius = this.regions.reduce((max2, region) => Math.max(max2, region.meta.outerRadius ?? 0), 0);
49717
+ if (outerRadius <= 0) throw new Error('driveWheel: "body" is required when regions do not define an outer radius');
49718
+ return outerRadius;
49719
+ }
49720
+ resolveName(prefix, requested) {
49721
+ const base = (requested == null ? void 0 : requested.trim()) || prefix;
49722
+ if (this.regions.every((region) => region.meta.name !== base)) return base;
49723
+ for (let i = 2; ; i++) {
49724
+ const candidate = `${base}${i}`;
49725
+ if (this.regions.every((region) => region.meta.name !== candidate)) return candidate;
49726
+ }
49727
+ }
49728
+ }
49729
+ function driveWheel(options = {}) {
49730
+ return new DriveWheelBuilder(options);
49731
+ }
49732
+ function normalizeSectorGearOptions(options) {
49733
+ const teethOnFullCircle = options.teethOnFullCircle;
49734
+ if (!Number.isInteger(teethOnFullCircle) || teethOnFullCircle < 6) {
49735
+ throw new Error('sectorGear: "teethOnFullCircle" must be an integer >= 6');
49736
+ }
49737
+ const toothCount = options.toothCount;
49738
+ if (!Number.isInteger(toothCount) || toothCount < 1 || toothCount > teethOnFullCircle) {
49739
+ throw new Error('sectorGear: "toothCount" must be an integer in [1, teethOnFullCircle]');
49740
+ }
49741
+ const firstTooth = options.firstTooth ?? 0;
49742
+ if (!Number.isInteger(firstTooth) || firstTooth < 0 || firstTooth >= teethOnFullCircle) {
49743
+ throw new Error('sectorGear: "firstTooth" must be an integer in [0, teethOnFullCircle)');
49744
+ }
49745
+ return {
49746
+ ...normalizeSpurGearOptions({ ...options, teeth: teethOnFullCircle }),
49747
+ teethOnFullCircle,
49748
+ toothCount,
49749
+ firstTooth,
49750
+ boreDiameter: options.boreDiameter ?? 0
49751
+ };
49752
+ }
49753
+ function sectorGear(options) {
49754
+ const normalized = normalizeSectorGearOptions(options);
49755
+ if (options.body !== void 0 && !(options.body instanceof Shape$1)) {
49756
+ throw new Error('sectorGear: "body" must be a Shape');
49757
+ }
49758
+ const spurMeta = buildSpurGearMeta(normalized);
49759
+ const pitchStepDeg = 360 / normalized.teethOnFullCircle;
49760
+ const activeAngleStartDeg = normalized.firstTooth * pitchStepDeg - pitchStepDeg * 0.5;
49761
+ const activeAngleEndDeg = (normalized.firstTooth + normalized.toothCount - 1) * pitchStepDeg + pitchStepDeg * 0.5;
49762
+ const meta2 = {
49763
+ ...spurMeta,
49764
+ kind: "sector",
49765
+ teethOnFullCircle: normalized.teethOnFullCircle,
49766
+ firstTooth: normalized.firstTooth,
49767
+ toothCount: normalized.toothCount,
49768
+ activeAngleStartDeg,
49769
+ activeAngleEndDeg
49770
+ };
49771
+ const wheel = driveWheel({ body: options.body, faceWidth: normalized.faceWidth, boreDiameter: normalized.boreDiameter }).addSpurTeethBetween({
49772
+ name: "teeth",
49773
+ module: normalized.module,
49774
+ teethOnFullCircle: normalized.teethOnFullCircle,
49775
+ toothCount: normalized.toothCount,
49776
+ firstTooth: normalized.firstTooth,
49777
+ pressureAngleDeg: normalized.pressureAngleDeg,
49778
+ faceWidth: normalized.faceWidth,
49779
+ backlash: normalized.backlash,
49780
+ clearance: normalized.clearance,
49781
+ addendum: normalized.addendum,
49782
+ dedendum: normalized.dedendum,
49783
+ segmentsPerTooth: normalized.segmentsPerTooth
49784
+ }).build();
49785
+ return attachGearMeta(wheel, meta2);
49786
+ }
48312
49787
  function normalizeSideGearOptions(options) {
48313
49788
  let normalizedSpur;
48314
49789
  try {
@@ -49288,6 +50763,12 @@ function boltPattern(options) {
49288
50763
  }
49289
50764
  };
49290
50765
  }
50766
+ const gearBodies = {
50767
+ disk: gearBodyDisk,
50768
+ diskWithHub: gearBodyDiskWithHub,
50769
+ spoked: gearBodySpoked,
50770
+ fromProfile: gearBodyFromProfile
50771
+ };
49291
50772
  function thread(diameter, pitch, length4, options) {
49292
50773
  const r = diameter / 2;
49293
50774
  const depth = (options == null ? void 0 : options.depth) ?? pitch * 0.35;
@@ -49453,7 +50934,23 @@ const partLibrary = {
49453
50934
  gearRatio,
49454
50935
  rackRatio,
49455
50936
  planetaryRatio,
49456
- boltPattern
50937
+ boltPattern,
50938
+ /** Start a composable exceptional gear or drive wheel. */
50939
+ driveWheel,
50940
+ /** Read functional-region metadata from a drive wheel shape. */
50941
+ readDriveWheelMeta,
50942
+ /** Involute sector gear with teeth on only part of the pitch circle. */
50943
+ sectorGear,
50944
+ /** Gear body preset namespace: disk, diskWithHub, spoked, and fromProfile. */
50945
+ gearBodies,
50946
+ /** Solid disk/ring gear body, independent from any tooth geometry. */
50947
+ gearBodyDisk,
50948
+ /** Disk gear body with a raised center hub. */
50949
+ gearBodyDiskWithHub,
50950
+ /** Spoked gear body with an outer rim, center hub, and radial spokes. */
50951
+ gearBodySpoked,
50952
+ /** Extrude a custom 2D profile into a gear body. */
50953
+ gearBodyFromProfile
49457
50954
  };
49458
50955
  /**
49459
50956
  * @license
@@ -50651,9 +52148,9 @@ class Vector2 {
50651
52148
  * @param {number} alpha - The interpolation factor, typically in the closed interval `[0, 1]`.
50652
52149
  * @return {Vector2} A reference to this vector.
50653
52150
  */
50654
- lerpVectors(v1, v2, alpha) {
50655
- this.x = v1.x + (v2.x - v1.x) * alpha;
50656
- this.y = v1.y + (v2.y - v1.y) * alpha;
52151
+ lerpVectors(v1, v22, alpha) {
52152
+ this.x = v1.x + (v22.x - v1.x) * alpha;
52153
+ this.y = v1.y + (v22.y - v1.y) * alpha;
50657
52154
  return this;
50658
52155
  }
50659
52156
  /**
@@ -52465,11 +53962,11 @@ class Vector4 {
52465
53962
  * @param {number} alpha - The interpolation factor, typically in the closed interval `[0, 1]`.
52466
53963
  * @return {Vector4} A reference to this vector.
52467
53964
  */
52468
- lerpVectors(v1, v2, alpha) {
52469
- this.x = v1.x + (v2.x - v1.x) * alpha;
52470
- this.y = v1.y + (v2.y - v1.y) * alpha;
52471
- this.z = v1.z + (v2.z - v1.z) * alpha;
52472
- this.w = v1.w + (v2.w - v1.w) * alpha;
53965
+ lerpVectors(v1, v22, alpha) {
53966
+ this.x = v1.x + (v22.x - v1.x) * alpha;
53967
+ this.y = v1.y + (v22.y - v1.y) * alpha;
53968
+ this.z = v1.z + (v22.z - v1.z) * alpha;
53969
+ this.w = v1.w + (v22.w - v1.w) * alpha;
52473
53970
  return this;
52474
53971
  }
52475
53972
  /**
@@ -54049,10 +55546,10 @@ class Vector3 {
54049
55546
  * @param {number} alpha - The interpolation factor, typically in the closed interval `[0, 1]`.
54050
55547
  * @return {Vector3} A reference to this vector.
54051
55548
  */
54052
- lerpVectors(v1, v2, alpha) {
54053
- this.x = v1.x + (v2.x - v1.x) * alpha;
54054
- this.y = v1.y + (v2.y - v1.y) * alpha;
54055
- this.z = v1.z + (v2.z - v1.z) * alpha;
55549
+ lerpVectors(v1, v22, alpha) {
55550
+ this.x = v1.x + (v22.x - v1.x) * alpha;
55551
+ this.y = v1.y + (v22.y - v1.y) * alpha;
55552
+ this.z = v1.z + (v22.z - v1.z) * alpha;
54056
55553
  return this;
54057
55554
  }
54058
55555
  /**
@@ -54865,13 +56362,13 @@ const _center = /* @__PURE__ */ new Vector3();
54865
56362
  const _extents = /* @__PURE__ */ new Vector3();
54866
56363
  const _triangleNormal = /* @__PURE__ */ new Vector3();
54867
56364
  const _testAxis = /* @__PURE__ */ new Vector3();
54868
- function satForAxes(axes, v0, v1, v2, extents) {
56365
+ function satForAxes(axes, v0, v1, v22, extents) {
54869
56366
  for (let i = 0, j = axes.length - 3; i <= j; i += 3) {
54870
56367
  _testAxis.fromArray(axes, i);
54871
56368
  const r = extents.x * Math.abs(_testAxis.x) + extents.y * Math.abs(_testAxis.y) + extents.z * Math.abs(_testAxis.z);
54872
56369
  const p0 = v0.dot(_testAxis);
54873
56370
  const p1 = v1.dot(_testAxis);
54874
- const p2 = v2.dot(_testAxis);
56371
+ const p2 = v22.dot(_testAxis);
54875
56372
  if (Math.max(-Math.max(p0, p1, p2), Math.min(p0, p1, p2)) > r) {
54876
56373
  return false;
54877
56374
  }
@@ -57985,7 +59482,7 @@ class Triangle {
57985
59482
  * @param {Vector3} target - The target vector that is used to store the method's result.
57986
59483
  * @return {?Vector3} The interpolated value.
57987
59484
  */
57988
- static getInterpolation(point2, p1, p2, p3, v1, v2, v32, target) {
59485
+ static getInterpolation(point2, p1, p2, p3, v1, v22, v32, target) {
57989
59486
  if (this.getBarycoord(point2, p1, p2, p3, _v3$2) === null) {
57990
59487
  target.x = 0;
57991
59488
  target.y = 0;
@@ -57995,7 +59492,7 @@ class Triangle {
57995
59492
  }
57996
59493
  target.setScalar(0);
57997
59494
  target.addScaledVector(v1, _v3$2.x);
57998
- target.addScaledVector(v2, _v3$2.y);
59495
+ target.addScaledVector(v22, _v3$2.y);
57999
59496
  target.addScaledVector(v32, _v3$2.z);
58000
59497
  return target;
58001
59498
  }
@@ -58160,8 +59657,8 @@ class Triangle {
58160
59657
  * @param {Vector3} target - The target vector that is used to store the method's result.
58161
59658
  * @return {?Vector3} The interpolated value.
58162
59659
  */
58163
- getInterpolation(point2, v1, v2, v32, target) {
58164
- return Triangle.getInterpolation(point2, this.a, this.b, this.c, v1, v2, v32, target);
59660
+ getInterpolation(point2, v1, v22, v32, target) {
59661
+ return Triangle.getInterpolation(point2, this.a, this.b, this.c, v1, v22, v32, target);
58165
59662
  }
58166
59663
  /**
58167
59664
  * Returns `true` if the given point, when projected onto the plane of the
@@ -66438,13 +67935,13 @@ class CubicBezierCurve extends Curve {
66438
67935
  * @param {Vector2} [v2] - The second control point.
66439
67936
  * @param {Vector2} [v3] - The end point.
66440
67937
  */
66441
- constructor(v0 = new Vector2(), v1 = new Vector2(), v2 = new Vector2(), v32 = new Vector2()) {
67938
+ constructor(v0 = new Vector2(), v1 = new Vector2(), v22 = new Vector2(), v32 = new Vector2()) {
66442
67939
  super();
66443
67940
  this.isCubicBezierCurve = true;
66444
67941
  this.type = "CubicBezierCurve";
66445
67942
  this.v0 = v0;
66446
67943
  this.v1 = v1;
66447
- this.v2 = v2;
67944
+ this.v2 = v22;
66448
67945
  this.v3 = v32;
66449
67946
  }
66450
67947
  /**
@@ -66456,10 +67953,10 @@ class CubicBezierCurve extends Curve {
66456
67953
  */
66457
67954
  getPoint(t, optionalTarget = new Vector2()) {
66458
67955
  const point2 = optionalTarget;
66459
- const v0 = this.v0, v1 = this.v1, v2 = this.v2, v32 = this.v3;
67956
+ const v0 = this.v0, v1 = this.v1, v22 = this.v2, v32 = this.v3;
66460
67957
  point2.set(
66461
- CubicBezier(t, v0.x, v1.x, v2.x, v32.x),
66462
- CubicBezier(t, v0.y, v1.y, v2.y, v32.y)
67958
+ CubicBezier(t, v0.x, v1.x, v22.x, v32.x),
67959
+ CubicBezier(t, v0.y, v1.y, v22.y, v32.y)
66463
67960
  );
66464
67961
  return point2;
66465
67962
  }
@@ -66497,13 +67994,13 @@ class CubicBezierCurve3 extends Curve {
66497
67994
  * @param {Vector3} [v2] - The second control point.
66498
67995
  * @param {Vector3} [v3] - The end point.
66499
67996
  */
66500
- constructor(v0 = new Vector3(), v1 = new Vector3(), v2 = new Vector3(), v32 = new Vector3()) {
67997
+ constructor(v0 = new Vector3(), v1 = new Vector3(), v22 = new Vector3(), v32 = new Vector3()) {
66501
67998
  super();
66502
67999
  this.isCubicBezierCurve3 = true;
66503
68000
  this.type = "CubicBezierCurve3";
66504
68001
  this.v0 = v0;
66505
68002
  this.v1 = v1;
66506
- this.v2 = v2;
68003
+ this.v2 = v22;
66507
68004
  this.v3 = v32;
66508
68005
  }
66509
68006
  /**
@@ -66515,11 +68012,11 @@ class CubicBezierCurve3 extends Curve {
66515
68012
  */
66516
68013
  getPoint(t, optionalTarget = new Vector3()) {
66517
68014
  const point2 = optionalTarget;
66518
- const v0 = this.v0, v1 = this.v1, v2 = this.v2, v32 = this.v3;
68015
+ const v0 = this.v0, v1 = this.v1, v22 = this.v2, v32 = this.v3;
66519
68016
  point2.set(
66520
- CubicBezier(t, v0.x, v1.x, v2.x, v32.x),
66521
- CubicBezier(t, v0.y, v1.y, v2.y, v32.y),
66522
- CubicBezier(t, v0.z, v1.z, v2.z, v32.z)
68017
+ CubicBezier(t, v0.x, v1.x, v22.x, v32.x),
68018
+ CubicBezier(t, v0.y, v1.y, v22.y, v32.y),
68019
+ CubicBezier(t, v0.z, v1.z, v22.z, v32.z)
66523
68020
  );
66524
68021
  return point2;
66525
68022
  }
@@ -66555,12 +68052,12 @@ class LineCurve extends Curve {
66555
68052
  * @param {Vector2} [v1] - The start point.
66556
68053
  * @param {Vector2} [v2] - The end point.
66557
68054
  */
66558
- constructor(v1 = new Vector2(), v2 = new Vector2()) {
68055
+ constructor(v1 = new Vector2(), v22 = new Vector2()) {
66559
68056
  super();
66560
68057
  this.isLineCurve = true;
66561
68058
  this.type = "LineCurve";
66562
68059
  this.v1 = v1;
66563
- this.v2 = v2;
68060
+ this.v2 = v22;
66564
68061
  }
66565
68062
  /**
66566
68063
  * Returns a point on the line.
@@ -66615,12 +68112,12 @@ class LineCurve3 extends Curve {
66615
68112
  * @param {Vector3} [v1] - The start point.
66616
68113
  * @param {Vector3} [v2] - The end point.
66617
68114
  */
66618
- constructor(v1 = new Vector3(), v2 = new Vector3()) {
68115
+ constructor(v1 = new Vector3(), v22 = new Vector3()) {
66619
68116
  super();
66620
68117
  this.isLineCurve3 = true;
66621
68118
  this.type = "LineCurve3";
66622
68119
  this.v1 = v1;
66623
- this.v2 = v2;
68120
+ this.v2 = v22;
66624
68121
  }
66625
68122
  /**
66626
68123
  * Returns a point on the line.
@@ -66676,13 +68173,13 @@ class QuadraticBezierCurve extends Curve {
66676
68173
  * @param {Vector2} [v1] - The control point.
66677
68174
  * @param {Vector2} [v2] - The end point.
66678
68175
  */
66679
- constructor(v0 = new Vector2(), v1 = new Vector2(), v2 = new Vector2()) {
68176
+ constructor(v0 = new Vector2(), v1 = new Vector2(), v22 = new Vector2()) {
66680
68177
  super();
66681
68178
  this.isQuadraticBezierCurve = true;
66682
68179
  this.type = "QuadraticBezierCurve";
66683
68180
  this.v0 = v0;
66684
68181
  this.v1 = v1;
66685
- this.v2 = v2;
68182
+ this.v2 = v22;
66686
68183
  }
66687
68184
  /**
66688
68185
  * Returns a point on the curve.
@@ -66693,10 +68190,10 @@ class QuadraticBezierCurve extends Curve {
66693
68190
  */
66694
68191
  getPoint(t, optionalTarget = new Vector2()) {
66695
68192
  const point2 = optionalTarget;
66696
- const v0 = this.v0, v1 = this.v1, v2 = this.v2;
68193
+ const v0 = this.v0, v1 = this.v1, v22 = this.v2;
66697
68194
  point2.set(
66698
- QuadraticBezier(t, v0.x, v1.x, v2.x),
66699
- QuadraticBezier(t, v0.y, v1.y, v2.y)
68195
+ QuadraticBezier(t, v0.x, v1.x, v22.x),
68196
+ QuadraticBezier(t, v0.y, v1.y, v22.y)
66700
68197
  );
66701
68198
  return point2;
66702
68199
  }
@@ -66730,13 +68227,13 @@ class QuadraticBezierCurve3 extends Curve {
66730
68227
  * @param {Vector3} [v1] - The control point.
66731
68228
  * @param {Vector3} [v2] - The end point.
66732
68229
  */
66733
- constructor(v0 = new Vector3(), v1 = new Vector3(), v2 = new Vector3()) {
68230
+ constructor(v0 = new Vector3(), v1 = new Vector3(), v22 = new Vector3()) {
66734
68231
  super();
66735
68232
  this.isQuadraticBezierCurve3 = true;
66736
68233
  this.type = "QuadraticBezierCurve3";
66737
68234
  this.v0 = v0;
66738
68235
  this.v1 = v1;
66739
- this.v2 = v2;
68236
+ this.v2 = v22;
66740
68237
  }
66741
68238
  /**
66742
68239
  * Returns a point on the curve.
@@ -66747,11 +68244,11 @@ class QuadraticBezierCurve3 extends Curve {
66747
68244
  */
66748
68245
  getPoint(t, optionalTarget = new Vector3()) {
66749
68246
  const point2 = optionalTarget;
66750
- const v0 = this.v0, v1 = this.v1, v2 = this.v2;
68247
+ const v0 = this.v0, v1 = this.v1, v22 = this.v2;
66751
68248
  point2.set(
66752
- QuadraticBezier(t, v0.x, v1.x, v2.x),
66753
- QuadraticBezier(t, v0.y, v1.y, v2.y),
66754
- QuadraticBezier(t, v0.z, v1.z, v2.z)
68249
+ QuadraticBezier(t, v0.x, v1.x, v22.x),
68250
+ QuadraticBezier(t, v0.y, v1.y, v22.y),
68251
+ QuadraticBezier(t, v0.z, v1.z, v22.z)
66755
68252
  );
66756
68253
  return point2;
66757
68254
  }
@@ -92099,7 +93596,7 @@ class ProductStationBuilder {
92099
93596
  this.profileValue = profileFromSketch(sketch, "custom", width, depth);
92100
93597
  return this;
92101
93598
  }
92102
- /** Stores a semantic crown amount for diagnostics and future rail solving. */
93599
+ /** Set the station crown amount for soft product-section intent. */
92103
93600
  crown(amount) {
92104
93601
  if (!Number.isFinite(amount)) throw new Error("station.crown(amount) requires a finite number");
92105
93602
  this.crownValue = amount;
@@ -93430,7 +94927,7 @@ class ProductSkinBuilder {
93430
94927
  this.stationsValue = stations.map(toStationSpec).sort((a2, b) => axisPosition(this.axisValue, a2.center) - axisPosition(this.axisValue, b.center));
93431
94928
  return this;
93432
94929
  }
93433
- /** Attach guide rails as ProductSkin IR metadata and diagnostics. */
94930
+ /** Attach named guide rails for product-skin construction and downstream surface references. */
93434
94931
  rails(rails) {
93435
94932
  this.railsValue = { ...rails };
93436
94933
  return this;
@@ -93464,7 +94961,7 @@ class ProductSkinBuilder {
93464
94961
  this.edgeLengthValue = value;
93465
94962
  return this;
93466
94963
  }
93467
- /** Records a target wall thickness; v1 keeps exterior skin lowering sampled and reports wall as a diagnostic. */
94964
+ /** Record intended wall thickness for product design metadata. Use explicit shelling when the model needs real inner-wall geometry. */
93468
94965
  wall(thickness) {
93469
94966
  if (!Number.isFinite(thickness) || thickness <= 0) throw new Error("Product.skin().wall(thickness) requires a positive finite number");
93470
94967
  this.wallValue = thickness;
@@ -96606,7 +98103,7 @@ class SurfaceMemberBuilder {
96606
98103
  this.record.features.push({ ...normalizeFeature(name, feature), type: "counterbore" });
96607
98104
  return this;
96608
98105
  }
96609
- /** Add a named anchor at a carrier surface coordinate for diagnostics, debug views, and future named-anchor joins. */
98106
+ /** Add a named anchor at a carrier surface coordinate for explicit member joins. */
96610
98107
  anchorAt(name, coordinate) {
96611
98108
  if (!name.trim()) throw new Error("SurfaceMemberBuilder.anchorAt(name, coordinate) requires a non-empty name");
96612
98109
  const explicitAnchors = this.record.spec.explicitAnchors ?? [];
@@ -106177,8 +107674,8 @@ tinf_build_bits_base(dist_bits, dist_base, 2, 1);
106177
107674
  length_bits[28] = 0;
106178
107675
  length_base[28] = 258;
106179
107676
  var tinyInflate = tinf_uncompress;
106180
- function derive(v0, v1, v2, v32, t) {
106181
- return Math.pow(1 - t, 3) * v0 + 3 * Math.pow(1 - t, 2) * t * v1 + 3 * (1 - t) * Math.pow(t, 2) * v2 + Math.pow(t, 3) * v32;
107677
+ function derive(v0, v1, v22, v32, t) {
107678
+ return Math.pow(1 - t, 3) * v0 + 3 * Math.pow(1 - t, 2) * t * v1 + 3 * (1 - t) * Math.pow(t, 2) * v22 + Math.pow(t, 3) * v32;
106182
107679
  }
106183
107680
  function BoundingBox() {
106184
107681
  this.x1 = Number.NaN;
@@ -335397,6 +336894,8 @@ function createForgeRuntimeModule(bindings) {
335397
336894
  runtime.default = runtime;
335398
336895
  return runtime;
335399
336896
  }
336897
+ const DEFAULT_RAYMARCH_STEPS = 360;
336898
+ const MIN_RAYMARCH_STEP = 0.012;
335400
336899
  function classifySdfPreviewNode(node) {
335401
336900
  switch (node.kind) {
335402
336901
  case "sdf:sphere":
@@ -335434,6 +336933,7 @@ function classifySdfPreviewNode(node) {
335434
336933
  case "sdf:twist":
335435
336934
  case "sdf:bend":
335436
336935
  case "sdf:repeat":
336936
+ case "sdf:circularArray":
335437
336937
  case "sdf:shell":
335438
336938
  case "sdf:onion":
335439
336939
  return classifySdfPreviewNode(node.child);
@@ -335443,10 +336943,7 @@ function classifySdfPreviewNode(node) {
335443
336943
  reason: "This SDF uses a custom JavaScript displacement function that cannot be compiled for raymarch preview."
335444
336944
  };
335445
336945
  case "sdf:surfaceDisplace":
335446
- return {
335447
- mode: "unsupported",
335448
- reason: "This SDF uses surface displacement that is not yet available in the raymarch shader."
335449
- };
336946
+ return classifySurfaceDisplacePreviewNode(node);
335450
336947
  case "sdf:spatialBlend":
335451
336948
  return {
335452
336949
  mode: "unsupported",
@@ -335472,6 +336969,24 @@ ${node.shaderUnsupportedReason}` : ""}`
335472
336969
  };
335473
336970
  }
335474
336971
  }
336972
+ function classifySurfaceDisplacePreviewNode(node) {
336973
+ if (!node.pattern) {
336974
+ return {
336975
+ mode: "unsupported",
336976
+ reason: "This SDF uses a custom JavaScript surface pattern that cannot be compiled for raymarch preview."
336977
+ };
336978
+ }
336979
+ const childResult = classifySdfPreviewNode(node.child);
336980
+ if (childResult.mode !== "raymarch") return childResult;
336981
+ const uv = analyzeShaderSurfaceUv(node.child, "p", node.uvMode);
336982
+ if (uv.mode === "triplanar") {
336983
+ return {
336984
+ mode: "unsupported",
336985
+ reason: "Typed surface displacement raymarch preview currently supports sphere, cylinder, and torus UV mappings."
336986
+ };
336987
+ }
336988
+ return { mode: "raymarch" };
336989
+ }
335475
336990
  function f2(value) {
335476
336991
  if (!Number.isFinite(value)) return "0.0";
335477
336992
  const text = Number(value.toPrecision(9)).toString();
@@ -335480,6 +336995,9 @@ function f2(value) {
335480
336995
  function v3(value) {
335481
336996
  return `vec3(${f2(value[0])}, ${f2(value[1])}, ${f2(value[2])})`;
335482
336997
  }
336998
+ function v2(value) {
336999
+ return `vec2(${f2(value[0])}, ${f2(value[1])})`;
337000
+ }
335483
337001
  function metricFlag(mode) {
335484
337002
  return mode === "metric-approx" ? "1.0" : "0.0";
335485
337003
  }
@@ -335499,6 +337017,32 @@ function scaleLimit(limit, factor) {
335499
337017
  const scale2 = positiveFinite(Math.abs(factor));
335500
337018
  return limit !== null && scale2 !== null ? limit * scale2 : limit;
335501
337019
  }
337020
+ function surfacePatternStepLimit(pattern) {
337021
+ switch (pattern.kind) {
337022
+ case "surfacePattern:constant":
337023
+ return null;
337024
+ case "surfacePattern:sineWave":
337025
+ return minPositive(pattern.wavelength * 0.1, Math.abs(pattern.amplitude) * 0.5);
337026
+ case "surfacePattern:stripes":
337027
+ return minPositive(pattern.spacing * 0.25, pattern.width * 0.25, pattern.depth * 0.5);
337028
+ case "surfacePattern:overUnderWeave":
337029
+ return minPositive(
337030
+ ...pattern.spacing.map((value) => value * 0.25),
337031
+ ...pattern.threadWidth.map((value) => value * 0.25),
337032
+ pattern.depth * 0.5
337033
+ );
337034
+ case "surfacePattern:abs":
337035
+ case "surfacePattern:negate":
337036
+ return surfacePatternStepLimit(pattern.child);
337037
+ case "surfacePattern:add":
337038
+ case "surfacePattern:multiply":
337039
+ case "surfacePattern:min":
337040
+ case "surfacePattern:max":
337041
+ return minPositive(...pattern.children.map(surfacePatternStepLimit));
337042
+ case "surfacePattern:clamp":
337043
+ return surfacePatternStepLimit(pattern.child);
337044
+ }
337045
+ }
335502
337046
  function tpmsStepLimit(node) {
335503
337047
  const cellLimit = node.cellSize * 0.08;
335504
337048
  const thicknessLimit = node.thicknessMode === "metric-approx" ? node.thickness * 0.35 : cellLimit;
@@ -335533,6 +337077,10 @@ function estimateNodeStepLimit(node) {
335533
337077
  return minPositive(estimateNodeStepLimit(node.child), 0.35);
335534
337078
  case "sdf:repeat":
335535
337079
  return minPositive(estimateNodeStepLimit(node.child), ...node.spacing.map((value) => positiveFinite(value * 0.25)));
337080
+ case "sdf:circularArray": {
337081
+ const arcSpacing = node.offset > 0 ? 2 * Math.PI * node.offset / node.count : null;
337082
+ return minPositive(estimateNodeStepLimit(node.child), arcSpacing !== null ? arcSpacing * 0.25 : null);
337083
+ }
335536
337084
  case "sdf:shell":
335537
337085
  return minPositive(estimateNodeStepLimit(node.child), Math.max(0.04, node.thickness * 0.25));
335538
337086
  case "sdf:onion":
@@ -335543,11 +337091,12 @@ function estimateNodeStepLimit(node) {
335543
337091
  case "sdf:lidinoid":
335544
337092
  return tpmsStepLimit(node);
335545
337093
  case "sdf:displace":
335546
- case "sdf:surfaceDisplace":
335547
337094
  case "sdf:spatialBlend":
335548
337095
  case "sdf:noise":
335549
337096
  case "sdf:voronoi":
335550
337097
  return null;
337098
+ case "sdf:surfaceDisplace":
337099
+ return node.pattern ? minPositive(estimateNodeStepLimit(node.child), surfacePatternStepLimit(node.pattern)) : null;
335551
337100
  case "sdf:custom":
335552
337101
  return node.shaderBody ? positiveFinite(node.raymarchStepLimit ?? 0.25) ?? 0.25 : null;
335553
337102
  }
@@ -335572,6 +337121,98 @@ function emitPolylineSweepExpr(node, p2) {
335572
337121
  }
335573
337122
  return expr;
335574
337123
  }
337124
+ function analyzeShaderSurfaceUv(node, p2, override) {
337125
+ const analysis = analyzeShaderSurfaceUvNode(node, p2);
337126
+ if (!override || override === "auto") return analysis;
337127
+ if (override === "triplanar") return { mode: "triplanar" };
337128
+ if (analysis.mode === "triplanar") return analysis;
337129
+ if (override === analysis.mode) return analysis;
337130
+ if (override === "sphere" || override === "cylinder") {
337131
+ return { mode: override, localPoint: analysis.localPoint, radius: analysis.radius };
337132
+ }
337133
+ return analysis.mode === "torus" ? analysis : { mode: "triplanar" };
337134
+ }
337135
+ function analyzeShaderSurfaceUvNode(node, p2) {
337136
+ switch (node.kind) {
337137
+ case "sdf:sphere":
337138
+ return { mode: "sphere", localPoint: p2, radius: node.radius };
337139
+ case "sdf:cylinder":
337140
+ return { mode: "cylinder", localPoint: p2, radius: node.radius };
337141
+ case "sdf:torus":
337142
+ return { mode: "torus", localPoint: p2, radius: node.minorRadius, majorRadius: node.majorRadius };
337143
+ case "sdf:translate":
337144
+ return analyzeShaderSurfaceUvNode(node.child, `(${p2} - ${v3(node.offset)})`);
337145
+ case "sdf:rotate":
337146
+ return analyzeShaderSurfaceUvNode(node.child, `rotateInvEuler(${p2}, ${v3(node.degrees)})`);
337147
+ case "sdf:scale": {
337148
+ const result = analyzeShaderSurfaceUvNode(node.child, `(${p2} / ${f2(node.factor)})`);
337149
+ if (result.mode === "triplanar") return result;
337150
+ return {
337151
+ ...result,
337152
+ radius: result.radius * node.factor,
337153
+ ...result.mode === "torus" ? { majorRadius: result.majorRadius * node.factor } : {}
337154
+ };
337155
+ }
337156
+ case "sdf:shell":
337157
+ return analyzeShaderSurfaceUvNode(node.child, p2);
337158
+ case "sdf:union":
337159
+ case "sdf:smoothUnion":
337160
+ case "sdf:intersection":
337161
+ case "sdf:smoothIntersection":
337162
+ case "sdf:difference":
337163
+ case "sdf:smoothDifference":
337164
+ return node.children.length > 0 ? analyzeShaderSurfaceUvNode(node.children[0], p2) : { mode: "triplanar" };
337165
+ case "sdf:morph":
337166
+ return analyzeShaderSurfaceUvNode(node.a, p2);
337167
+ default:
337168
+ return { mode: "triplanar" };
337169
+ }
337170
+ }
337171
+ function emitSurfaceUvExpr(uv) {
337172
+ const p2 = uv.localPoint;
337173
+ switch (uv.mode) {
337174
+ case "sphere":
337175
+ return `surfaceUvSphere(${p2}, ${f2(uv.radius)})`;
337176
+ case "cylinder":
337177
+ return `surfaceUvCylinder(${p2}, ${f2(uv.radius)})`;
337178
+ case "torus":
337179
+ return `surfaceUvTorus(${p2}, ${f2(uv.majorRadius)}, ${f2(uv.radius)})`;
337180
+ }
337181
+ }
337182
+ function emitSurfacePatternExpr(pattern, uvExpr) {
337183
+ switch (pattern.kind) {
337184
+ case "surfacePattern:constant":
337185
+ return f2(pattern.value);
337186
+ case "surfacePattern:sineWave":
337187
+ return `surfacePatternSineWave(${uvExpr}, ${v2(pattern.direction)}, ${f2(pattern.wavelength)}, ${f2(pattern.amplitude)}, ${f2(pattern.phase)}, ${f2(pattern.bias)})`;
337188
+ case "surfacePattern:stripes":
337189
+ return `surfacePatternStripes(${uvExpr}, ${v2(pattern.direction)}, ${f2(pattern.spacing)}, ${f2(pattern.width)}, ${f2(pattern.depth)})`;
337190
+ case "surfacePattern:overUnderWeave":
337191
+ return `surfacePatternOverUnderWeave(${uvExpr}, ${v2(pattern.spacing)}, ${v2(pattern.threadWidth)}, ${f2(pattern.depth)}, ${f2(pattern.underScale)})`;
337192
+ case "surfacePattern:abs":
337193
+ return `abs(${emitSurfacePatternExpr(pattern.child, uvExpr)})`;
337194
+ case "surfacePattern:negate":
337195
+ return `(-(${emitSurfacePatternExpr(pattern.child, uvExpr)}))`;
337196
+ case "surfacePattern:add":
337197
+ return pattern.children.length === 0 ? "0.0" : `(${pattern.children.map((child) => emitSurfacePatternExpr(child, uvExpr)).join(" + ")})`;
337198
+ case "surfacePattern:multiply":
337199
+ return pattern.children.length === 0 ? "1.0" : `(${pattern.children.map((child) => emitSurfacePatternExpr(child, uvExpr)).join(" * ")})`;
337200
+ case "surfacePattern:min":
337201
+ return foldSurfacePatternChildren(pattern, uvExpr, "min", "0.0");
337202
+ case "surfacePattern:max":
337203
+ return foldSurfacePatternChildren(pattern, uvExpr, "max", "0.0");
337204
+ case "surfacePattern:clamp":
337205
+ return `clamp(${emitSurfacePatternExpr(pattern.child, uvExpr)}, ${f2(pattern.min)}, ${f2(pattern.max)})`;
337206
+ }
337207
+ }
337208
+ function foldSurfacePatternChildren(pattern, uvExpr, op, empty) {
337209
+ if (pattern.children.length === 0) return empty;
337210
+ let expr = emitSurfacePatternExpr(pattern.children[0], uvExpr);
337211
+ for (let i = 1; i < pattern.children.length; i++) {
337212
+ expr = `${op}(${expr}, ${emitSurfacePatternExpr(pattern.children[i], uvExpr)})`;
337213
+ }
337214
+ return expr;
337215
+ }
335575
337216
  function emitSdfExpr(node, p2) {
335576
337217
  switch (node.kind) {
335577
337218
  case "sdf:sphere":
@@ -335614,6 +337255,11 @@ function emitSdfExpr(node, p2) {
335614
337255
  return emitSdfExpr(node.child, `bendPoint(${p2}, ${f2(node.radius)})`);
335615
337256
  case "sdf:repeat":
335616
337257
  return emitSdfExpr(node.child, `repeatPoint(${p2}, ${v3(node.spacing)}, ${v3(node.count)})`);
337258
+ case "sdf:circularArray": {
337259
+ const a2 = emitSdfExpr(node.child, `circularArrayPoint(${p2}, ${f2(node.count)}, ${f2(node.offset)}, -1.0)`);
337260
+ const b = emitSdfExpr(node.child, `circularArrayPoint(${p2}, ${f2(node.count)}, ${f2(node.offset)}, 0.0)`);
337261
+ return `min(${a2}, ${b})`;
337262
+ }
335617
337263
  case "sdf:shell":
335618
337264
  return `(abs(${emitSdfExpr(node.child, p2)}) - ${f2(node.thickness * 0.5)})`;
335619
337265
  case "sdf:onion": {
@@ -335621,6 +337267,12 @@ function emitSdfExpr(node, p2) {
335621
337267
  for (let i = 0; i < node.layers; i++) expr = `(abs(${expr}) - ${f2(node.thickness)})`;
335622
337268
  return expr;
335623
337269
  }
337270
+ case "sdf:surfaceDisplace": {
337271
+ if (!node.pattern) return "1e20";
337272
+ const uv = analyzeShaderSurfaceUv(node.child, p2, node.uvMode);
337273
+ if (uv.mode === "triplanar") return "1e20";
337274
+ return `(${emitSdfExpr(node.child, p2)} + ${emitSurfacePatternExpr(node.pattern, emitSurfaceUvExpr(uv))})`;
337275
+ }
335624
337276
  case "sdf:gyroid":
335625
337277
  return `sdGyroid(${p2}, ${f2(node.cellSize)}, ${f2(node.thickness)}, ${metricFlag(node.thicknessMode)})`;
335626
337278
  case "sdf:schwarzP":
@@ -335636,7 +337288,6 @@ function emitSdfExpr(node, p2) {
335636
337288
  }
335637
337289
  return "1e20";
335638
337290
  case "sdf:displace":
335639
- case "sdf:surfaceDisplace":
335640
337291
  case "sdf:spatialBlend":
335641
337292
  case "sdf:noise":
335642
337293
  case "sdf:voronoi":
@@ -335710,9 +337361,9 @@ uniform vec3 uHoverColor;
335710
337361
  uniform float uHoverIntensity;
335711
337362
  uniform float uIsOrthographic;
335712
337363
 
335713
- const int MAX_STEPS = 360;
337364
+ const int MAX_STEPS = ${DEFAULT_RAYMARCH_STEPS};
335714
337365
  const int REFINE_STEPS = 7;
335715
- const float MIN_STEP = 0.012;
337366
+ const float MIN_STEP = ${f2(MIN_RAYMARCH_STEP)};
335716
337367
  const float HIT_EPS = 0.035;
335717
337368
  const float NORMAL_EPS = 0.12;
335718
337369
  const float FIELD_STEP_LIMIT = ${stepLimit === null ? "1e20" : f2(stepLimit)};
@@ -335801,6 +337452,53 @@ vec3 repeatPoint(vec3 p, vec3 spacing, vec3 count) {
335801
337452
  );
335802
337453
  }
335803
337454
 
337455
+ vec3 circularArrayPoint(vec3 p, float count, float offset, float sectorOffset) {
337456
+ float step = 6.283185307179586 / max(count, 1.0);
337457
+ float radius = length(p.xy);
337458
+ float angle = mod(atan(p.y, p.x), step);
337459
+ if (angle < 0.0) angle += step;
337460
+ angle += sectorOffset * step;
337461
+ return vec3(cos(angle) * radius - offset, sin(angle) * radius, p.z);
337462
+ }
337463
+
337464
+ vec2 surfaceUvSphere(vec3 p, float radius) {
337465
+ return vec2(atan(p.y, p.x) * radius, acos(clamp(p.z / max(length(p), 1e-9), -1.0, 1.0)) * radius);
337466
+ }
337467
+
337468
+ vec2 surfaceUvCylinder(vec3 p, float radius) {
337469
+ return vec2(atan(p.y, p.x) * radius, p.z);
337470
+ }
337471
+
337472
+ vec2 surfaceUvTorus(vec3 p, float majorRadius, float minorRadius) {
337473
+ return vec2(atan(p.y, p.x) * majorRadius, atan(p.z, length(p.xy) - majorRadius) * minorRadius);
337474
+ }
337475
+
337476
+ float surfacePatternSineWave(vec2 uv, vec2 direction, float wavelength, float amplitude, float phase, float bias) {
337477
+ return bias + sin(dot(uv, direction) * (6.283185307179586 / wavelength) + phase) * amplitude;
337478
+ }
337479
+
337480
+ float surfacePatternStripes(vec2 uv, vec2 direction, float spacing, float width, float depth) {
337481
+ float coord = dot(uv, direction);
337482
+ float d = abs(coord - floor(coord / spacing + 0.5) * spacing);
337483
+ float p = max(0.0, 1.0 - d / (width * 0.5));
337484
+ return -(p * p) * depth;
337485
+ }
337486
+
337487
+ float surfacePatternOverUnderWeave(vec2 uv, vec2 spacing, vec2 threadWidth, float depth, float underScale) {
337488
+ float su = uv.x / spacing.x;
337489
+ float sv = uv.y / spacing.y;
337490
+ float du = abs(su - floor(su + 0.5)) * spacing.x;
337491
+ float dv = abs(sv - floor(sv + 0.5)) * spacing.y;
337492
+ float pU = max(0.0, 1.0 - du / (threadWidth.x * 0.5));
337493
+ float pV = max(0.0, 1.0 - dv / (threadWidth.y * 0.5));
337494
+ pU *= pU;
337495
+ pV *= pV;
337496
+ float checker = mod(floor(su + 0.5) + floor(sv + 0.5), 2.0);
337497
+ float top = checker > 0.5 ? pV : pU;
337498
+ float bot = checker > 0.5 ? pU : pV;
337499
+ return -max(top, bot * underScale) * depth;
337500
+ }
337501
+
335804
337502
  vec3 rotateInvEuler(vec3 p, vec3 degrees) {
335805
337503
  vec3 r = radians(degrees);
335806
337504
  float cx = cos(r.x);
@@ -336001,9 +337699,9 @@ void main() {
336001
337699
  float bestAbsDist = 1e20;
336002
337700
  int bestIndex = -1;
336003
337701
  bool conservativeField = FIELD_STEP_LIMIT < 1e19;
336004
- float maxStep = conservativeField
336005
- ? max(MIN_STEP, min(max(sceneDiag / 260.0, MIN_STEP), FIELD_STEP_LIMIT))
336006
- : max(MIN_STEP, sceneDiag / 18.0);
337702
+ float coarseMaxStep = max(MIN_STEP, sceneDiag / 18.0);
337703
+ float conservativeMaxStep = max(MIN_STEP, min(max(sceneDiag / 260.0, MIN_STEP), FIELD_STEP_LIMIT));
337704
+ float localDetailBand = max(HIT_EPS * 4.0, FIELD_STEP_LIMIT * 4.0);
336007
337705
  float stepScale = conservativeField ? CONSERVATIVE_STEP_SCALE : SAFE_STEP_SCALE;
336008
337706
 
336009
337707
  for (int i = 0; i < MAX_STEPS; i++) {
@@ -336024,6 +337722,7 @@ void main() {
336024
337722
  hit = true;
336025
337723
  break;
336026
337724
  }
337725
+ float maxStep = conservativeField && absDist <= localDetailBand ? conservativeMaxStep : coarseMaxStep;
336027
337726
  float stepSize = clamp(absDist * stepScale, MIN_STEP, maxStep);
336028
337727
  prevT = t;
336029
337728
  prevDist = dist;
@@ -336147,7 +337846,7 @@ function mapScriptResultToScene(args) {
336147
337846
  var _a3;
336148
337847
  const objects = [];
336149
337848
  const shapeDimensions = [];
336150
- const pushShape = (shape, name, groupName, color, treePath) => {
337849
+ const pushShape = (shape, name, groupName, color, treePath, tags = []) => {
336151
337850
  const objectId = `obj-${objects.length + 1}`;
336152
337851
  objects.push({
336153
337852
  id: objectId,
@@ -336158,7 +337857,8 @@ function mapScriptResultToScene(args) {
336158
337857
  materialProps: shape.materialProps,
336159
337858
  geometryInfo: shape.geometryInfo(),
336160
337859
  groupName,
336161
- treePath: treePath && treePath.length > 0 ? [...treePath] : [name]
337860
+ treePath: treePath && treePath.length > 0 ? [...treePath] : [name],
337861
+ ...tags.length > 0 ? { tags: [...tags] } : {}
336162
337862
  });
336163
337863
  const dims = getShapeDimensions(shape);
336164
337864
  dims.forEach((dim2) => {
@@ -336182,7 +337882,7 @@ function mapScriptResultToScene(args) {
336182
337882
  });
336183
337883
  }
336184
337884
  };
336185
- const pushSketch = (sketch, name, groupName, treePath) => {
337885
+ const pushSketch = (sketch, name, groupName, treePath, tags = []) => {
336186
337886
  const meta2 = sketch instanceof ConstraintSketch ? sketch.constraintMeta : void 0;
336187
337887
  objects.push({
336188
337888
  id: `obj-${objects.length + 1}`,
@@ -336193,10 +337893,11 @@ function mapScriptResultToScene(args) {
336193
337893
  sketchMeta: meta2,
336194
337894
  color: sketch.colorHex,
336195
337895
  groupName,
336196
- treePath: treePath && treePath.length > 0 ? [...treePath] : [name]
337896
+ treePath: treePath && treePath.length > 0 ? [...treePath] : [name],
337897
+ ...tags.length > 0 ? { tags: [...tags] } : {}
336197
337898
  });
336198
337899
  };
336199
- const pushSdf = (sdfShape, name, groupName, treePath, color) => {
337900
+ const pushSdf = (sdfShape, name, groupName, treePath, color, tags = []) => {
336200
337901
  const preview = classifySdfPreviewNode(sdfShape._node);
336201
337902
  const displayColor = color || sdfShape.colorHex;
336202
337903
  const data = {
@@ -336216,7 +337917,8 @@ function mapScriptResultToScene(args) {
336216
337917
  materialProps: sdfShape.materialProps,
336217
337918
  geometryInfo: null,
336218
337919
  groupName,
336219
- treePath: treePath && treePath.length > 0 ? [...treePath] : [name]
337920
+ treePath: treePath && treePath.length > 0 ? [...treePath] : [name],
337921
+ ...tags.length > 0 ? { tags: [...tags] } : {}
336220
337922
  });
336221
337923
  };
336222
337924
  const isNamedObject = (item) => {
@@ -336233,18 +337935,24 @@ function mapScriptResultToScene(args) {
336233
337935
  const rootGroupChildLabel = (grp, index2) => {
336234
337936
  return shapeGroupChildSegment(grp, index2, true);
336235
337937
  };
336236
- const flattenGroupChild = (child, label, groupName, treePath) => {
337938
+ const flattenGroupChild = (child, label, groupName, treePath, tags = []) => {
336237
337939
  const resolvedTreePath = treePath && treePath.length > 0 ? treePath : [label];
336238
337940
  if (child instanceof ShapeGroup) {
336239
337941
  child.children.forEach((nested, i) => {
336240
- flattenGroupChild(nested, groupChildLabel(child, label, i), groupName, [...resolvedTreePath, shapeGroupChildSegment(child, i)]);
337942
+ flattenGroupChild(
337943
+ nested,
337944
+ groupChildLabel(child, label, i),
337945
+ groupName,
337946
+ [...resolvedTreePath, shapeGroupChildSegment(child, i)],
337947
+ mergeSceneTags(tags, child.tagsForChild(i))
337948
+ );
336241
337949
  });
336242
337950
  return;
336243
337951
  }
336244
337952
  if (child instanceof Shape$1) {
336245
- pushShape(child, label, groupName, void 0, resolvedTreePath);
337953
+ pushShape(child, label, groupName, void 0, resolvedTreePath, tags);
336246
337954
  } else if (child instanceof Sketch) {
336247
- pushSketch(child, label, groupName, resolvedTreePath);
337955
+ pushSketch(child, label, groupName, resolvedTreePath, tags);
336248
337956
  }
336249
337957
  };
336250
337958
  const isPlainObject2 = (value) => {
@@ -336253,34 +337961,40 @@ function mapScriptResultToScene(args) {
336253
337961
  return proto2 === Object.prototype || proto2 === null;
336254
337962
  };
336255
337963
  const joinName = (path2) => path2.join(".");
336256
- const processRenderableTree = (value, fallbackLabel, fallbackSegment, parentGroup, parentTreePath = [], seen2 = /* @__PURE__ */ new WeakSet()) => {
337964
+ const processRenderableTree = (value, fallbackLabel, fallbackSegment, parentGroup, parentTreePath = [], inheritedTags = [], seen2 = /* @__PURE__ */ new WeakSet()) => {
336257
337965
  const segment = fallbackSegment.trim().length > 0 ? fallbackSegment : fallbackLabel;
336258
337966
  const treePath = [...parentTreePath, segment];
336259
337967
  const name = joinName(treePath) || fallbackLabel;
336260
337968
  if (value instanceof Assembly) {
336261
- value.solve().toSceneObjects().forEach((item, index2) => processNamedItem(item, `${name}.${index2 + 1}`, `${index2 + 1}`, name, treePath));
337969
+ value.solve().toSceneObjects().forEach((item, index2) => processNamedItem(item, `${name}.${index2 + 1}`, `${index2 + 1}`, name, treePath, inheritedTags));
336262
337970
  return;
336263
337971
  }
336264
337972
  if (value instanceof SolvedAssembly) {
336265
- value.toSceneObjects().forEach((item, index2) => processNamedItem(item, `${name}.${index2 + 1}`, `${index2 + 1}`, name, treePath));
337973
+ value.toSceneObjects().forEach((item, index2) => processNamedItem(item, `${name}.${index2 + 1}`, `${index2 + 1}`, name, treePath, inheritedTags));
336266
337974
  return;
336267
337975
  }
336268
337976
  if (value instanceof ShapeGroup) {
336269
337977
  value.children.forEach((child, i) => {
336270
- flattenGroupChild(child, groupChildLabel(value, name, i), parentGroup, [...treePath, shapeGroupChildSegment(value, i)]);
337978
+ flattenGroupChild(
337979
+ child,
337980
+ groupChildLabel(value, name, i),
337981
+ parentGroup,
337982
+ [...treePath, shapeGroupChildSegment(value, i)],
337983
+ mergeSceneTags(inheritedTags, value.tagsForChild(i))
337984
+ );
336271
337985
  });
336272
337986
  return;
336273
337987
  }
336274
337988
  if (value instanceof Shape$1) {
336275
- pushShape(value, name, parentGroup, void 0, treePath);
337989
+ pushShape(value, name, parentGroup, void 0, treePath, inheritedTags);
336276
337990
  return;
336277
337991
  }
336278
337992
  if (value instanceof Sketch) {
336279
- pushSketch(value, name, parentGroup, treePath);
337993
+ pushSketch(value, name, parentGroup, treePath, inheritedTags);
336280
337994
  return;
336281
337995
  }
336282
337996
  if (value instanceof SdfShape) {
336283
- pushSdf(value, name, parentGroup, treePath);
337997
+ pushSdf(value, name, parentGroup, treePath, void 0, inheritedTags);
336284
337998
  return;
336285
337999
  }
336286
338000
  if (value instanceof GCodeBuilder) {
@@ -336291,7 +338005,8 @@ function mapScriptResultToScene(args) {
336291
338005
  sketch: null,
336292
338006
  toolpath: value.build(),
336293
338007
  geometryInfo: null,
336294
- treePath
338008
+ treePath,
338009
+ ...inheritedTags.length > 0 ? { tags: [...inheritedTags] } : {}
336295
338010
  });
336296
338011
  return;
336297
338012
  }
@@ -336301,30 +338016,38 @@ function mapScriptResultToScene(args) {
336301
338016
  value.forEach((item, index2) => {
336302
338017
  const childSegment = `${index2 + 1}`;
336303
338018
  const childLabel = `${name}.${childSegment}`;
336304
- processRenderableTree(item, childLabel, childSegment, parentGroup, treePath, seen2);
338019
+ processRenderableTree(item, childLabel, childSegment, parentGroup, treePath, inheritedTags, seen2);
336305
338020
  });
336306
338021
  return;
336307
338022
  }
336308
338023
  if (isNamedObject(value)) {
336309
- processNamedItem(value, fallbackLabel, fallbackSegment, parentGroup, parentTreePath);
338024
+ processNamedItem(value, fallbackLabel, fallbackSegment, parentGroup, parentTreePath, inheritedTags);
336310
338025
  return;
336311
338026
  }
336312
338027
  if (isPlainObject2(value)) {
336313
338028
  if (seen2.has(value)) return;
336314
338029
  seen2.add(value);
336315
338030
  Object.entries(value).forEach(([key, entry]) => {
336316
- processRenderableTree(entry, key, key, parentGroup, treePath, seen2);
338031
+ processRenderableTree(entry, key, key, parentGroup, treePath, inheritedTags, seen2);
336317
338032
  });
336318
338033
  }
336319
338034
  };
336320
- const processNamedItem = (item, fallbackLabel, fallbackSegment, parentGroup, parentTreePath = []) => {
338035
+ const processNamedItem = (item, fallbackLabel, fallbackSegment, parentGroup, parentTreePath = [], inheritedTags = []) => {
338036
+ var _a4;
336321
338037
  const name = typeof item.name === "string" && item.name.trim().length > 0 ? item.name : fallbackLabel;
336322
338038
  const localSegment = typeof item.name === "string" && item.name.trim().length > 0 ? item.name : fallbackSegment;
336323
338039
  const treePath = [...parentTreePath, localSegment];
336324
338040
  const grp = parentGroup;
338041
+ const tags = mergeSceneTags(inheritedTags, (_a4 = item.metadata) == null ? void 0 : _a4.tags, item.tags);
336325
338042
  if (item.group instanceof ShapeGroup) {
336326
338043
  item.group.children.forEach((child, i) => {
336327
- flattenGroupChild(child, groupChildLabel(item.group, name, i), name, [...treePath, shapeGroupChildSegment(item.group, i)]);
338044
+ flattenGroupChild(
338045
+ child,
338046
+ groupChildLabel(item.group, name, i),
338047
+ name,
338048
+ [...treePath, shapeGroupChildSegment(item.group, i)],
338049
+ mergeSceneTags(tags, item.group.tagsForChild(i))
338050
+ );
336328
338051
  });
336329
338052
  return;
336330
338053
  }
@@ -336334,39 +338057,48 @@ function mapScriptResultToScene(args) {
336334
338057
  const childTreePath = [...treePath, `${i + 1}`];
336335
338058
  if (child instanceof ShapeGroup) {
336336
338059
  child.children.forEach((nested, nestedIndex) => {
336337
- flattenGroupChild(nested, groupChildLabel(child, name, nestedIndex), name, [
336338
- ...treePath,
336339
- shapeGroupChildSegment(child, nestedIndex)
336340
- ]);
338060
+ flattenGroupChild(
338061
+ nested,
338062
+ groupChildLabel(child, name, nestedIndex),
338063
+ name,
338064
+ [...treePath, shapeGroupChildSegment(child, nestedIndex)],
338065
+ mergeSceneTags(tags, child.tagsForChild(nestedIndex))
338066
+ );
336341
338067
  });
336342
338068
  } else if (child instanceof Shape$1) {
336343
- pushShape(child, childLabel, name, void 0, childTreePath);
338069
+ pushShape(child, childLabel, name, void 0, childTreePath, tags);
336344
338070
  } else if (child instanceof Sketch) {
336345
- pushSketch(child, childLabel, name, childTreePath);
338071
+ pushSketch(child, childLabel, name, childTreePath, tags);
336346
338072
  } else if (child instanceof SdfShape) {
336347
- pushSdf(child, childLabel, name, childTreePath);
338073
+ pushSdf(child, childLabel, name, childTreePath, void 0, tags);
336348
338074
  } else if (isNamedObject(child)) {
336349
- processNamedItem(child, childLabel, `${i + 1}`, name, treePath);
338075
+ processNamedItem(child, childLabel, `${i + 1}`, name, treePath, tags);
336350
338076
  }
336351
338077
  });
336352
338078
  return;
336353
338079
  }
336354
338080
  if (item.shape instanceof ShapeGroup) {
336355
338081
  item.shape.children.forEach(
336356
- (child, i) => flattenGroupChild(child, groupChildLabel(item.shape, name, i), name, [...treePath, shapeGroupChildSegment(item.shape, i)])
338082
+ (child, i) => flattenGroupChild(
338083
+ child,
338084
+ groupChildLabel(item.shape, name, i),
338085
+ name,
338086
+ [...treePath, shapeGroupChildSegment(item.shape, i)],
338087
+ mergeSceneTags(tags, item.shape.tagsForChild(i))
338088
+ )
336357
338089
  );
336358
338090
  return;
336359
338091
  }
336360
338092
  if (item.shape instanceof Shape$1) {
336361
- pushShape(item.shape, name, grp, item.color, treePath);
338093
+ pushShape(item.shape, name, grp, item.color, treePath, tags);
336362
338094
  return;
336363
338095
  }
336364
338096
  if (item.shape instanceof SdfShape) {
336365
- pushSdf(item.shape, name, grp, treePath, item.color);
338097
+ pushSdf(item.shape, name, grp, treePath, item.color, tags);
336366
338098
  return;
336367
338099
  }
336368
338100
  if (item.sdf instanceof SdfShape) {
336369
- pushSdf(item.sdf, name, grp, treePath, item.color);
338101
+ pushSdf(item.sdf, name, grp, treePath, item.color, tags);
336370
338102
  return;
336371
338103
  }
336372
338104
  if (item.sketch instanceof Sketch) {
@@ -336380,7 +338112,8 @@ function mapScriptResultToScene(args) {
336380
338112
  sketchMeta: meta2,
336381
338113
  color: item.color || item.sketch.colorHex,
336382
338114
  groupName: grp,
336383
- treePath
338115
+ treePath,
338116
+ ...tags.length > 0 ? { tags: [...tags] } : {}
336384
338117
  });
336385
338118
  }
336386
338119
  };
@@ -336394,14 +338127,20 @@ function mapScriptResultToScene(args) {
336394
338127
  } else if (result instanceof ShapeGroup) {
336395
338128
  result.children.forEach((child, i) => {
336396
338129
  const label = rootGroupChildLabel(result, i);
336397
- flattenGroupChild(child, label, void 0, [label]);
338130
+ flattenGroupChild(child, label, void 0, [label], result.tagsForChild(i));
336398
338131
  });
336399
338132
  } else if (Array.isArray(result)) {
336400
338133
  result.forEach((item, index2) => {
336401
338134
  const label = `Object ${index2 + 1}`;
336402
338135
  if (item instanceof ShapeGroup) {
336403
338136
  item.children.forEach((child, i) => {
336404
- flattenGroupChild(child, groupChildLabel(item, label, i), void 0, [label, shapeGroupChildSegment(item, i)]);
338137
+ flattenGroupChild(
338138
+ child,
338139
+ groupChildLabel(item, label, i),
338140
+ void 0,
338141
+ [label, shapeGroupChildSegment(item, i)],
338142
+ item.tagsForChild(i)
338143
+ );
336405
338144
  });
336406
338145
  return;
336407
338146
  }
@@ -336434,7 +338173,7 @@ function mapScriptResultToScene(args) {
336434
338173
  } else if (defaultValue instanceof ShapeGroup) {
336435
338174
  defaultValue.children.forEach((child, i) => {
336436
338175
  const label = rootGroupChildLabel(defaultValue, i);
336437
- flattenGroupChild(child, label, void 0, [label]);
338176
+ flattenGroupChild(child, label, void 0, [label], defaultValue.tagsForChild(i));
336438
338177
  });
336439
338178
  } else if (defaultValue instanceof Shape$1) {
336440
338179
  pushShape(defaultValue, args.fileName, void 0, void 0, [args.fileName]);
@@ -336480,7 +338219,8 @@ function mapScriptResultToScene(args) {
336480
338219
  name: `${mock2.name} (mock)`,
336481
338220
  shape: mock2.shape,
336482
338221
  sketch: null,
336483
- mock: true
338222
+ mock: true,
338223
+ tags: ["mock"]
336484
338224
  });
336485
338225
  }
336486
338226
  const hasSdfLeaves = objects.some((obj) => obj.sdf);