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
@@ -542,6 +542,47 @@ function cloneSdfFunctionConstants(constants) {
542
542
  if (!constants) return void 0;
543
543
  return Object.fromEntries(Object.entries(constants).map(([key, value]) => [key, cloneSdfFunctionConstant(value)]));
544
544
  }
545
+ function cloneSdfSurfacePatternNode(pattern) {
546
+ switch (pattern.kind) {
547
+ case "surfacePattern:constant":
548
+ return { kind: "surfacePattern:constant", value: pattern.value };
549
+ case "surfacePattern:sineWave":
550
+ return {
551
+ kind: "surfacePattern:sineWave",
552
+ direction: [...pattern.direction],
553
+ wavelength: pattern.wavelength,
554
+ amplitude: pattern.amplitude,
555
+ phase: pattern.phase,
556
+ bias: pattern.bias
557
+ };
558
+ case "surfacePattern:stripes":
559
+ return {
560
+ kind: "surfacePattern:stripes",
561
+ direction: [...pattern.direction],
562
+ spacing: pattern.spacing,
563
+ width: pattern.width,
564
+ depth: pattern.depth
565
+ };
566
+ case "surfacePattern:overUnderWeave":
567
+ return {
568
+ kind: "surfacePattern:overUnderWeave",
569
+ spacing: [...pattern.spacing],
570
+ threadWidth: [...pattern.threadWidth],
571
+ depth: pattern.depth,
572
+ underScale: pattern.underScale
573
+ };
574
+ case "surfacePattern:abs":
575
+ case "surfacePattern:negate":
576
+ return { kind: pattern.kind, child: cloneSdfSurfacePatternNode(pattern.child) };
577
+ case "surfacePattern:add":
578
+ case "surfacePattern:multiply":
579
+ case "surfacePattern:min":
580
+ case "surfacePattern:max":
581
+ return { kind: pattern.kind, children: pattern.children.map(cloneSdfSurfacePatternNode) };
582
+ case "surfacePattern:clamp":
583
+ return { kind: "surfacePattern:clamp", child: cloneSdfSurfacePatternNode(pattern.child), min: pattern.min, max: pattern.max };
584
+ }
585
+ }
545
586
  function cloneSdfNode(node) {
546
587
  switch (node.kind) {
547
588
  // Primitives — plain value types
@@ -592,6 +633,8 @@ function cloneSdfNode(node) {
592
633
  return { kind: "sdf:bend", child: cloneSdfNode(node.child), radius: node.radius };
593
634
  case "sdf:repeat":
594
635
  return { kind: "sdf:repeat", child: cloneSdfNode(node.child), spacing: [...node.spacing], count: [...node.count] };
636
+ case "sdf:circularArray":
637
+ return { kind: "sdf:circularArray", child: cloneSdfNode(node.child), count: node.count, offset: node.offset };
595
638
  case "sdf:shell":
596
639
  return { kind: "sdf:shell", child: cloneSdfNode(node.child), thickness: node.thickness };
597
640
  case "sdf:displace":
@@ -605,6 +648,7 @@ function cloneSdfNode(node) {
605
648
  return {
606
649
  kind: "sdf:surfaceDisplace",
607
650
  child: cloneSdfNode(node.child),
651
+ ...node.pattern ? { pattern: cloneSdfSurfacePatternNode(node.pattern) } : {},
608
652
  patternBody: node.patternBody,
609
653
  ...node.constants ? { constants: cloneSdfFunctionConstants(node.constants) } : {},
610
654
  ...node.uvMode ? { uvMode: node.uvMode } : {},
@@ -4646,7 +4690,7 @@ for (var i = 0; i < 32; ++i)
4646
4690
  fdt[i] = 5;
4647
4691
  var flm = /* @__PURE__ */ hMap(flt, 9, 0), flrm = /* @__PURE__ */ hMap(flt, 9, 1);
4648
4692
  var fdm = /* @__PURE__ */ hMap(fdt, 5, 0), fdrm = /* @__PURE__ */ hMap(fdt, 5, 1);
4649
- var max$1 = function(a2) {
4693
+ var max$2 = function(a2) {
4650
4694
  var m2 = a2[0];
4651
4695
  for (var i = 1; i < a2.length; ++i) {
4652
4696
  if (a2[i] > m2)
@@ -4746,7 +4790,7 @@ var inflt = function(dat, st, buf, dict) {
4746
4790
  clt[clim[i]] = bits(dat, pos + i * 3, 7);
4747
4791
  }
4748
4792
  pos += hcLen * 3;
4749
- var clb = max$1(clt), clbmsk = (1 << clb) - 1;
4793
+ var clb = max$2(clt), clbmsk = (1 << clb) - 1;
4750
4794
  var clm = hMap(clt, clb, 1);
4751
4795
  for (var i = 0; i < tl; ) {
4752
4796
  var r = clm[bits(dat, pos, clbmsk)];
@@ -4767,8 +4811,8 @@ var inflt = function(dat, st, buf, dict) {
4767
4811
  }
4768
4812
  }
4769
4813
  var lt = ldt.subarray(0, hLit), dt = ldt.subarray(hLit);
4770
- lbt = max$1(lt);
4771
- dbt = max$1(dt);
4814
+ lbt = max$2(lt);
4815
+ dbt = max$2(dt);
4772
4816
  lm = hMap(lt, lbt, 1);
4773
4817
  dm = hMap(dt, dbt, 1);
4774
4818
  } else
@@ -5621,8 +5665,8 @@ function parse3mf(data) {
5621
5665
  while ((tMatch = trianglePattern.exec(meshXml)) !== null) {
5622
5666
  const v1 = parseInt(tMatch[1], 10) + vertexOffset;
5623
5667
  const v2 = parseInt(tMatch[2], 10) + vertexOffset;
5624
- const v3 = parseInt(tMatch[3], 10) + vertexOffset;
5625
- allTriIndices.push(v1, v2, v3);
5668
+ const v32 = parseInt(tMatch[3], 10) + vertexOffset;
5669
+ allTriIndices.push(v1, v2, v32);
5626
5670
  }
5627
5671
  for (let i = 0; i < meshVerts.length; i++) {
5628
5672
  allPositions.push(meshVerts[i]);
@@ -6005,6 +6049,287 @@ function lowerShellShapeCompilePlanToConcretePlan(plan) {
6005
6049
  }
6006
6050
  return lowerBaseShellPlanToConcretePlan(plan.base, plan.thickness, normalizeShellOpenFaces(plan.openFaces));
6007
6051
  }
6052
+ const DEFAULT_MAX_GRID_POINTS = 8e6;
6053
+ const DEFAULT_MIN_EDGE_LENGTH = 0.15;
6054
+ function resolveSdfMeshingSettings(tree, bounds, options = {}) {
6055
+ const quality = options.quality ?? "preview";
6056
+ const minEdgeLength = positiveOrDefault(options.minEdgeLength, DEFAULT_MIN_EDGE_LENGTH);
6057
+ const maxGridPoints = positiveOrDefault(options.maxGridPoints, DEFAULT_MAX_GRID_POINTS);
6058
+ const tolerance = options.tolerance !== void 0 ? requirePositiveFinite$3(options.tolerance, "SDF tolerance") : void 0;
6059
+ const minFeatureSize = options.minFeatureSize !== void 0 ? requirePositiveFinite$3(options.minFeatureSize, "SDF minFeatureSize") : void 0;
6060
+ const maxTriangles = options.maxTriangles !== void 0 ? Math.floor(requirePositiveFinite$3(options.maxTriangles, "SDF maxTriangles")) : void 0;
6061
+ const analysis = analyzeSdfTree(tree);
6062
+ const warnings = [];
6063
+ let edgeLength2;
6064
+ if (options.edgeLength !== void 0) {
6065
+ edgeLength2 = requirePositiveFinite$3(options.edgeLength, "SDF edgeLength");
6066
+ if (edgeLength2 < minEdgeLength) {
6067
+ warnings.push(`edgeLength ${formatMm(edgeLength2)} was clamped to minimum ${formatMm(minEdgeLength)}.`);
6068
+ edgeLength2 = minEdgeLength;
6069
+ }
6070
+ } else {
6071
+ edgeLength2 = resolveDefaultEdgeLength(bounds, quality, minEdgeLength, analysis, options);
6072
+ }
6073
+ if (analysis.minWallThickness < Infinity && analysis.minWallThickness < edgeLength2 * 2) {
6074
+ analysis.riskFlags.add("thin-shell");
6075
+ warnings.push(
6076
+ `shell/wall thickness ${formatMm(analysis.minWallThickness)} is below 2 x edgeLength ${formatMm(edgeLength2)}; thin features may be under-sampled.`
6077
+ );
6078
+ }
6079
+ if (!options.bounds && analysis.hasInfiniteRepeat) {
6080
+ warnings.push("infinite repeat bounds are heuristic; pass .toShape({ bounds }) for predictable clipping.");
6081
+ }
6082
+ if (!options.bounds && analysis.riskFlags.has("noise")) {
6083
+ warnings.push("noise field bounds are heuristic; pass .toShape({ bounds }) for predictable clipping.");
6084
+ }
6085
+ if (!options.bounds && (analysis.riskFlags.has("tpms") || analysis.riskFlags.has("voronoi"))) {
6086
+ warnings.push("TPMS/Voronoi bounds are heuristic unless clipped or passed explicitly.");
6087
+ }
6088
+ if (analysis.hasLegacyTpmsThreshold) {
6089
+ warnings.push("TPMS thickness is using legacy field-threshold units; use wallThickness for approximate millimeters.");
6090
+ }
6091
+ return {
6092
+ quality,
6093
+ edgeLength: edgeLength2,
6094
+ tolerance,
6095
+ minFeatureSize,
6096
+ minEdgeLength,
6097
+ simplify: resolveSimplificationMode(options.simplify, quality, analysis.riskFlags),
6098
+ maxTriangles,
6099
+ maxGridPoints,
6100
+ diagnostics: options.diagnostics === true,
6101
+ treeRiskFlags: [...analysis.riskFlags].sort(),
6102
+ warnings
6103
+ };
6104
+ }
6105
+ function withScaledSdfEdgeLength(settings, edgeLength2) {
6106
+ return { ...settings, edgeLength: Math.max(settings.minEdgeLength, edgeLength2) };
6107
+ }
6108
+ function createSdfMeshingDiagnostics(settings, bounds, paddedBounds) {
6109
+ const grid = estimateSdfGridDimensions(paddedBounds, settings.edgeLength);
6110
+ const estimatedSamples = grid[0] * grid[1] * grid[2];
6111
+ return {
6112
+ bounds: cloneBounds$2(bounds),
6113
+ paddedBounds: cloneBounds$2(paddedBounds),
6114
+ edgeLength: settings.edgeLength,
6115
+ grid,
6116
+ estimatedSamples,
6117
+ estimatedMemoryBytes: estimatedSamples * 8,
6118
+ treeRiskFlags: [...settings.treeRiskFlags],
6119
+ simplification: settings.simplify,
6120
+ capMode: "box",
6121
+ capInset: settings.edgeLength,
6122
+ warnings: [...settings.warnings]
6123
+ };
6124
+ }
6125
+ function assertSdfMeshingBudget(diagnostics, maxGridPoints) {
6126
+ if (diagnostics.estimatedSamples <= maxGridPoints) return;
6127
+ const suggestedEdge = suggestEdgeLengthForSampleBudget(diagnostics.paddedBounds, maxGridPoints);
6128
+ throw new Error(
6129
+ `SDF meshing would sample ${formatCount(diagnostics.estimatedSamples)} grid points (~${formatBytes(
6130
+ diagnostics.estimatedMemoryBytes
6131
+ )}). Reduce bounds or use edgeLength >= ${formatMm(suggestedEdge)}.`
6132
+ );
6133
+ }
6134
+ function estimateSdfGridDimensions(bounds, edgeLength2) {
6135
+ const dx = bounds.max[0] - bounds.min[0];
6136
+ const dy = bounds.max[1] - bounds.min[1];
6137
+ const dz = bounds.max[2] - bounds.min[2];
6138
+ return [
6139
+ Math.max(2, Math.ceil(dx / edgeLength2) + 1),
6140
+ Math.max(2, Math.ceil(dy / edgeLength2) + 1),
6141
+ Math.max(2, Math.ceil(dz / edgeLength2) + 1)
6142
+ ];
6143
+ }
6144
+ function logSdfMeshingDiagnostics(prefix, diagnostics) {
6145
+ const warnings = diagnostics.warnings.length > 0 ? `, warnings=${diagnostics.warnings.join(" | ")}` : "";
6146
+ const evaluator = diagnostics.evaluator ? `, evaluator=${diagnostics.evaluator}${diagnostics.evaluatorUnsupportedReason ? ` (${diagnostics.evaluatorUnsupportedReason})` : ""}` : "";
6147
+ console.info(
6148
+ `${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}`
6149
+ );
6150
+ }
6151
+ function resolveDefaultEdgeLength(bounds, quality, minEdgeLength, analysis, options) {
6152
+ const dx = bounds.max[0] - bounds.min[0];
6153
+ const dy = bounds.max[1] - bounds.min[1];
6154
+ const dz = bounds.max[2] - bounds.min[2];
6155
+ const maxDim = Math.max(dx, dy, dz, minEdgeLength);
6156
+ const divisor = quality === "draft" ? 60 : quality === "export" ? 160 : 100;
6157
+ const candidates = [maxDim / divisor];
6158
+ if (options.tolerance !== void 0) candidates.push(requirePositiveFinite$3(options.tolerance, "SDF tolerance") * 2);
6159
+ if (options.minFeatureSize !== void 0) candidates.push(requirePositiveFinite$3(options.minFeatureSize, "SDF minFeatureSize") / 2.5);
6160
+ if (analysis.minMetricTpmsThickness < Infinity) candidates.push(analysis.minMetricTpmsThickness / 2);
6161
+ if (analysis.minTpmsCellSize < Infinity) candidates.push(analysis.minTpmsCellSize / 10);
6162
+ if (analysis.minRepeatSpacing < Infinity) candidates.push(analysis.minRepeatSpacing / 8);
6163
+ if (analysis.minWallThickness < Infinity) candidates.push(analysis.minWallThickness / 2.5);
6164
+ return Math.max(minEdgeLength, Math.min(...candidates.filter((v) => Number.isFinite(v) && v > 0)));
6165
+ }
6166
+ function resolveSimplificationMode(simplify, quality, riskFlags) {
6167
+ if (simplify === false) return "off";
6168
+ if (simplify === true || simplify === "safe") return "safe";
6169
+ if (quality === "export" && riskFlags.size > 0) return "off";
6170
+ return "safe";
6171
+ }
6172
+ function analyzeSdfTree(tree) {
6173
+ const analysis = {
6174
+ riskFlags: /* @__PURE__ */ new Set(),
6175
+ minTpmsCellSize: Infinity,
6176
+ minMetricTpmsThickness: Infinity,
6177
+ minRepeatSpacing: Infinity,
6178
+ minWallThickness: Infinity,
6179
+ hasInfiniteRepeat: false,
6180
+ hasLegacyTpmsThreshold: false
6181
+ };
6182
+ visitSdfNode(tree, analysis);
6183
+ return analysis;
6184
+ }
6185
+ function minPositive(...values) {
6186
+ let result = Infinity;
6187
+ for (const value of values) {
6188
+ if (value !== null && value !== void 0 && Number.isFinite(value) && value > 0) {
6189
+ result = Math.min(result, value);
6190
+ }
6191
+ }
6192
+ return result === Infinity ? null : result;
6193
+ }
6194
+ function estimateSurfacePatternSpacing(pattern) {
6195
+ switch (pattern.kind) {
6196
+ case "surfacePattern:constant":
6197
+ return null;
6198
+ case "surfacePattern:sineWave":
6199
+ return pattern.wavelength;
6200
+ case "surfacePattern:stripes":
6201
+ return Math.min(pattern.spacing, pattern.width);
6202
+ case "surfacePattern:overUnderWeave":
6203
+ return Math.min(...pattern.spacing, ...pattern.threadWidth);
6204
+ case "surfacePattern:abs":
6205
+ case "surfacePattern:negate":
6206
+ return estimateSurfacePatternSpacing(pattern.child);
6207
+ case "surfacePattern:add":
6208
+ case "surfacePattern:multiply":
6209
+ case "surfacePattern:min":
6210
+ case "surfacePattern:max":
6211
+ return minPositive(...pattern.children.map(estimateSurfacePatternSpacing));
6212
+ case "surfacePattern:clamp":
6213
+ return estimateSurfacePatternSpacing(pattern.child);
6214
+ }
6215
+ }
6216
+ function visitSdfNode(node, analysis) {
6217
+ switch (node.kind) {
6218
+ case "sdf:union":
6219
+ case "sdf:difference":
6220
+ case "sdf:intersection":
6221
+ case "sdf:smoothUnion":
6222
+ case "sdf:smoothDifference":
6223
+ case "sdf:smoothIntersection":
6224
+ for (const child of node.children) visitSdfNode(child, analysis);
6225
+ break;
6226
+ case "sdf:morph":
6227
+ case "sdf:spatialBlend":
6228
+ visitSdfNode(node.a, analysis);
6229
+ visitSdfNode(node.b, analysis);
6230
+ break;
6231
+ case "sdf:translate":
6232
+ case "sdf:rotate":
6233
+ case "sdf:scale":
6234
+ case "sdf:twist":
6235
+ case "sdf:bend":
6236
+ case "sdf:onion":
6237
+ visitSdfNode(node.child, analysis);
6238
+ break;
6239
+ case "sdf:repeat":
6240
+ analysis.riskFlags.add("repeat");
6241
+ for (let i = 0; i < 3; i++) {
6242
+ const spacing = node.spacing[i];
6243
+ if (spacing > 0) {
6244
+ analysis.minRepeatSpacing = Math.min(analysis.minRepeatSpacing, spacing);
6245
+ if (node.count[i] <= 0) analysis.hasInfiniteRepeat = true;
6246
+ }
6247
+ }
6248
+ visitSdfNode(node.child, analysis);
6249
+ break;
6250
+ case "sdf:circularArray": {
6251
+ analysis.riskFlags.add("repeat");
6252
+ if (node.offset > 0) {
6253
+ analysis.minRepeatSpacing = Math.min(analysis.minRepeatSpacing, 2 * Math.PI * node.offset / node.count);
6254
+ }
6255
+ visitSdfNode(node.child, analysis);
6256
+ break;
6257
+ }
6258
+ case "sdf:shell":
6259
+ analysis.minWallThickness = Math.min(analysis.minWallThickness, node.thickness);
6260
+ visitSdfNode(node.child, analysis);
6261
+ break;
6262
+ case "sdf:displace":
6263
+ case "sdf:surfaceDisplace":
6264
+ analysis.riskFlags.add("displacement");
6265
+ if (node.kind === "sdf:surfaceDisplace" && node.pattern) {
6266
+ const spacing = estimateSurfacePatternSpacing(node.pattern);
6267
+ if (spacing !== null) analysis.minRepeatSpacing = Math.min(analysis.minRepeatSpacing, spacing);
6268
+ }
6269
+ visitSdfNode(node.child, analysis);
6270
+ break;
6271
+ case "sdf:gyroid":
6272
+ case "sdf:schwarzP":
6273
+ case "sdf:diamond":
6274
+ case "sdf:lidinoid":
6275
+ analysis.riskFlags.add("tpms");
6276
+ analysis.minTpmsCellSize = Math.min(analysis.minTpmsCellSize, node.cellSize);
6277
+ if (node.thicknessMode === "metric-approx") {
6278
+ analysis.minMetricTpmsThickness = Math.min(analysis.minMetricTpmsThickness, node.thickness);
6279
+ analysis.minWallThickness = Math.min(analysis.minWallThickness, node.thickness);
6280
+ } else {
6281
+ analysis.hasLegacyTpmsThreshold = true;
6282
+ }
6283
+ break;
6284
+ case "sdf:noise":
6285
+ analysis.riskFlags.add("noise");
6286
+ break;
6287
+ case "sdf:voronoi":
6288
+ analysis.riskFlags.add("voronoi");
6289
+ analysis.minWallThickness = Math.min(analysis.minWallThickness, node.wallThickness);
6290
+ if (node.surfaceChild) visitSdfNode(node.surfaceChild, analysis);
6291
+ break;
6292
+ case "sdf:custom":
6293
+ analysis.riskFlags.add("custom");
6294
+ break;
6295
+ }
6296
+ }
6297
+ function positiveOrDefault(value, fallback) {
6298
+ if (value === void 0) return fallback;
6299
+ return requirePositiveFinite$3(value, "SDF meshing option");
6300
+ }
6301
+ function requirePositiveFinite$3(value, name) {
6302
+ if (!Number.isFinite(value) || value <= 0) {
6303
+ throw new Error(`${name} must be a positive finite number.`);
6304
+ }
6305
+ return value;
6306
+ }
6307
+ function cloneBounds$2(bounds) {
6308
+ return { min: [...bounds.min], max: [...bounds.max] };
6309
+ }
6310
+ function suggestEdgeLengthForSampleBudget(bounds, maxGridPoints) {
6311
+ const dx = bounds.max[0] - bounds.min[0];
6312
+ const dy = bounds.max[1] - bounds.min[1];
6313
+ const dz = bounds.max[2] - bounds.min[2];
6314
+ const volume = Math.max(dx * dy * dz, 1);
6315
+ return Math.cbrt(volume / Math.max(maxGridPoints, 8));
6316
+ }
6317
+ function formatBounds(bounds) {
6318
+ return `[${bounds.min.map(formatNumber).join(",")}]-[${bounds.max.map(formatNumber).join(",")}]`;
6319
+ }
6320
+ function formatMm(value) {
6321
+ return `${formatNumber(value)}mm`;
6322
+ }
6323
+ function formatNumber(value) {
6324
+ return Number.isInteger(value) ? String(value) : value.toFixed(3).replace(/0+$/, "").replace(/\.$/, "");
6325
+ }
6326
+ function formatCount(value) {
6327
+ return Math.round(value).toLocaleString("en-US");
6328
+ }
6329
+ function formatBytes(bytes) {
6330
+ if (bytes < 1024 * 1024) return `${Math.ceil(bytes / 1024)} KB`;
6331
+ return `${Math.ceil(bytes / (1024 * 1024))} MB`;
6332
+ }
6008
6333
  const grad3 = new Float64Array([
6009
6334
  1,
6010
6335
  1,
@@ -6465,8 +6790,8 @@ function triplanarWeights(nx, ny, nz, sharpness) {
6465
6790
  const inv = 1 / sum2;
6466
6791
  return { wx: wx * inv, wy: wy * inv, wz: wz * inv };
6467
6792
  }
6468
- const { atan2, acos, cos: cos$2, sin: sin$2, sqrt: sqrt$2, PI: PI$2 } = Math;
6469
- const DEG$2 = PI$2 / 180;
6793
+ const { atan2, acos, cos: cos$3, sin: sin$3, sqrt: sqrt$3, PI: PI$3 } = Math;
6794
+ const DEG$3 = PI$3 / 180;
6470
6795
  const IDENTITY = (p2) => p2;
6471
6796
  function analyzeUV(node, override) {
6472
6797
  if (override) {
@@ -6493,10 +6818,10 @@ function analyzeNodeUV(node, toLocal) {
6493
6818
  return analyzeNodeUV(node.child, next);
6494
6819
  }
6495
6820
  case "sdf:rotate": {
6496
- const [rx, ry, rz] = node.degrees.map((d2) => d2 * DEG$2);
6497
- const cx = cos$2(rx), sx = sin$2(rx);
6498
- const cy = cos$2(ry), sy = sin$2(ry);
6499
- const cz = cos$2(rz), sz = sin$2(rz);
6821
+ const [rx, ry, rz] = node.degrees.map((d2) => d2 * DEG$3);
6822
+ const cx = cos$3(rx), sx = sin$3(rx);
6823
+ const cy = cos$3(ry), sy = sin$3(ry);
6824
+ const cz = cos$3(rz), sz = sin$3(rz);
6500
6825
  const prev = toLocal;
6501
6826
  const next = (p2) => {
6502
6827
  const pp = prev(p2);
@@ -6553,7 +6878,7 @@ function compileUVFunction(analysis) {
6553
6878
  return (p2) => {
6554
6879
  const lp = toLocal(p2);
6555
6880
  const u2 = atan2(lp[1], lp[0]) * R;
6556
- const len2 = sqrt$2(lp[0] * lp[0] + lp[1] * lp[1] + lp[2] * lp[2]);
6881
+ const len2 = sqrt$3(lp[0] * lp[0] + lp[1] * lp[1] + lp[2] * lp[2]);
6557
6882
  const v = acos(clampUnit(lp[2] / (len2 || 1))) * R;
6558
6883
  return [u2, v];
6559
6884
  };
@@ -6573,23 +6898,23 @@ function compileUVFunction(analysis) {
6573
6898
  return (p2) => {
6574
6899
  const lp = toLocal(p2);
6575
6900
  const u2 = atan2(lp[1], lp[0]) * R;
6576
- const xyDist = sqrt$2(lp[0] * lp[0] + lp[1] * lp[1]) - R;
6901
+ const xyDist = sqrt$3(lp[0] * lp[0] + lp[1] * lp[1]) - R;
6577
6902
  const v = atan2(lp[2], xyDist) * r;
6578
6903
  return [u2, v];
6579
6904
  };
6580
6905
  }
6581
6906
  }
6582
6907
  }
6583
- const { abs: abs$1, cos: cos$1, sin: sin$1, sqrt: sqrt$1, PI: PI$1 } = Math;
6584
- const TAU = 2 * PI$1;
6585
- const GRAD_EPS = 1e-9;
6908
+ const { abs: abs$1, cos: cos$2, sin: sin$2, sqrt: sqrt$2, PI: PI$2 } = Math;
6909
+ const TAU$1 = 2 * PI$2;
6910
+ const GRAD_EPS$1 = 1e-9;
6586
6911
  function gyroidValueAndGradient(x2, y2, z2, cellSize) {
6587
- const s = TAU / cellSize;
6912
+ const s = TAU$1 / cellSize;
6588
6913
  const xs = x2 * s;
6589
6914
  const ys = y2 * s;
6590
6915
  const zs = z2 * s;
6591
- const sx = sin$1(xs), sy = sin$1(ys), sz = sin$1(zs);
6592
- const cx = cos$1(xs), cy = cos$1(ys), cz = cos$1(zs);
6916
+ const sx = sin$2(xs), sy = sin$2(ys), sz = sin$2(zs);
6917
+ const cx = cos$2(xs), cy = cos$2(ys), cz = cos$2(zs);
6593
6918
  return {
6594
6919
  value: sx * cy + sy * cz + sz * cx,
6595
6920
  gx: s * (cx * cy - sz * sx),
@@ -6598,24 +6923,24 @@ function gyroidValueAndGradient(x2, y2, z2, cellSize) {
6598
6923
  };
6599
6924
  }
6600
6925
  function schwarzPValueAndGradient(x2, y2, z2, cellSize) {
6601
- const s = TAU / cellSize;
6926
+ const s = TAU$1 / cellSize;
6602
6927
  const xs = x2 * s;
6603
6928
  const ys = y2 * s;
6604
6929
  const zs = z2 * s;
6605
6930
  return {
6606
- value: cos$1(xs) + cos$1(ys) + cos$1(zs),
6607
- gx: -s * sin$1(xs),
6608
- gy: -s * sin$1(ys),
6609
- gz: -s * sin$1(zs)
6931
+ value: cos$2(xs) + cos$2(ys) + cos$2(zs),
6932
+ gx: -s * sin$2(xs),
6933
+ gy: -s * sin$2(ys),
6934
+ gz: -s * sin$2(zs)
6610
6935
  };
6611
6936
  }
6612
6937
  function diamondValueAndGradient(x2, y2, z2, cellSize) {
6613
- const s = TAU / cellSize;
6938
+ const s = TAU$1 / cellSize;
6614
6939
  const xs = x2 * s;
6615
6940
  const ys = y2 * s;
6616
6941
  const zs = z2 * s;
6617
- const sx = sin$1(xs), sy = sin$1(ys), sz = sin$1(zs);
6618
- const cx = cos$1(xs), cy = cos$1(ys), cz = cos$1(zs);
6942
+ const sx = sin$2(xs), sy = sin$2(ys), sz = sin$2(zs);
6943
+ const cx = cos$2(xs), cy = cos$2(ys), cz = cos$2(zs);
6619
6944
  return {
6620
6945
  value: sx * sy * sz + sx * cy * cz + cx * sy * cz + cx * cy * sz,
6621
6946
  gx: s * (cx * sy * sz + cx * cy * cz - sx * sy * cz - sx * cy * sz),
@@ -6624,12 +6949,12 @@ function diamondValueAndGradient(x2, y2, z2, cellSize) {
6624
6949
  };
6625
6950
  }
6626
6951
  function lidinoidValueAndGradient(x2, y2, z2, cellSize) {
6627
- const s = TAU / cellSize;
6952
+ const s = TAU$1 / cellSize;
6628
6953
  const sx2 = x2 * s, sy2 = y2 * s, sz2 = z2 * s;
6629
- const sx = sin$1(sx2), sy = sin$1(sy2), sz = sin$1(sz2);
6630
- const cx = cos$1(sx2), cy = cos$1(sy2), cz = cos$1(sz2);
6631
- const s2x = sin$1(2 * sx2), s2y = sin$1(2 * sy2), s2z = sin$1(2 * sz2);
6632
- const c2x = cos$1(2 * sx2), c2y = cos$1(2 * sy2), c2z = cos$1(2 * sz2);
6954
+ const sx = sin$2(sx2), sy = sin$2(sy2), sz = sin$2(sz2);
6955
+ const cx = cos$2(sx2), cy = cos$2(sy2), cz = cos$2(sz2);
6956
+ const s2x = sin$2(2 * sx2), s2y = sin$2(2 * sy2), s2z = sin$2(2 * sz2);
6957
+ const c2x = cos$2(2 * sx2), c2y = cos$2(2 * sy2), c2z = cos$2(2 * sz2);
6633
6958
  const val = s2x * cy * sz + s2y * cz * sx + s2z * cx * sy - c2x * c2y - c2y * c2z - c2z * c2x + 0.3;
6634
6959
  return {
6635
6960
  value: val,
@@ -6652,8 +6977,8 @@ function lidinoid$1(x2, y2, z2, cellSize, thickness, thicknessMode) {
6652
6977
  }
6653
6978
  function tpmsDistance({ value, gx, gy, gz }, thickness, thicknessMode) {
6654
6979
  if (thicknessMode !== "metric-approx") return abs$1(value) - thickness;
6655
- const grad = sqrt$1(gx * gx + gy * gy + gz * gz);
6656
- return abs$1(value) / Math.max(grad, GRAD_EPS) - thickness * 0.5;
6980
+ const grad = sqrt$2(gx * gx + gy * gy + gz * gz);
6981
+ return abs$1(value) / Math.max(grad, GRAD_EPS$1) - thickness * 0.5;
6657
6982
  }
6658
6983
  function mix(h) {
6659
6984
  h = (h ^ h >>> 16) * 2246822507 | 0;
@@ -6749,76 +7074,76 @@ function seededWorley3Surface(seed) {
6749
7074
  const s = seed | 0;
6750
7075
  return (x2, y2, z2, nx, ny, nz, threshold) => worleySurface(x2, y2, z2, s, nx, ny, nz, threshold);
6751
7076
  }
6752
- const { abs, cos, max, min, sin, sqrt, PI } = Math;
6753
- const DEG$1 = PI / 180;
7077
+ const { abs, cos: cos$1, max: max$1, min, sin: sin$1, sqrt: sqrt$1, PI: PI$1 } = Math;
7078
+ const DEG$2 = PI$1 / 180;
6754
7079
  function clamp$a(v, lo, hi) {
6755
7080
  return v < lo ? lo : v > hi ? hi : v;
6756
7081
  }
6757
- function length2(x2, y2) {
6758
- return sqrt(x2 * x2 + y2 * y2);
7082
+ function length2$1(x2, y2) {
7083
+ return sqrt$1(x2 * x2 + y2 * y2);
6759
7084
  }
6760
- function length3(x2, y2, z2) {
6761
- return sqrt(x2 * x2 + y2 * y2 + z2 * z2);
7085
+ function length3$1(x2, y2, z2) {
7086
+ return sqrt$1(x2 * x2 + y2 * y2 + z2 * z2);
6762
7087
  }
6763
- function sdSphere(px, py, pz, r) {
6764
- return length3(px, py, pz) - r;
7088
+ function sdSphere$1(px, py, pz, r) {
7089
+ return length3$1(px, py, pz) - r;
6765
7090
  }
6766
- function sdBox(px, py, pz, hx, hy, hz) {
7091
+ function sdBox$1(px, py, pz, hx, hy, hz) {
6767
7092
  const dx = abs(px) - hx;
6768
7093
  const dy = abs(py) - hy;
6769
7094
  const dz = abs(pz) - hz;
6770
- return length3(max(dx, 0), max(dy, 0), max(dz, 0)) + min(max(dx, dy, dz), 0);
7095
+ return length3$1(max$1(dx, 0), max$1(dy, 0), max$1(dz, 0)) + min(max$1(dx, dy, dz), 0);
6771
7096
  }
6772
- function sdCylinder(px, py, pz, h, r) {
6773
- const dx = length2(px, py) - r;
7097
+ function sdCylinder$1(px, py, pz, h, r) {
7098
+ const dx = length2$1(px, py) - r;
6774
7099
  const dz = abs(pz) - h * 0.5;
6775
- return length2(max(dx, 0), max(dz, 0)) + min(max(dx, dz), 0);
7100
+ return length2$1(max$1(dx, 0), max$1(dz, 0)) + min(max$1(dx, dz), 0);
6776
7101
  }
6777
- function sdTorus(px, py, pz, R, r) {
6778
- const qx = length2(px, py) - R;
6779
- return length2(qx, pz) - r;
7102
+ function sdTorus$1(px, py, pz, R, r) {
7103
+ const qx = length2$1(px, py) - R;
7104
+ return length2$1(qx, pz) - r;
6780
7105
  }
6781
- function sdCapsule(px, py, pz, h, r) {
7106
+ function sdCapsule$1(px, py, pz, h, r) {
6782
7107
  const halfH = h * 0.5;
6783
7108
  const cz = clamp$a(pz, -halfH, halfH);
6784
- return length3(px, py, pz - cz) - r;
7109
+ return length3$1(px, py, pz - cz) - r;
6785
7110
  }
6786
- function sdCone(px, py, pz, h, r) {
6787
- const q = length2(px, py);
6788
- const cLen = length2(h, r);
7111
+ function sdCone$1(px, py, pz, h, r) {
7112
+ const q = length2$1(px, py);
7113
+ const cLen = length2$1(h, r);
6789
7114
  const nx = h / cLen;
6790
7115
  const nz = -r / cLen;
6791
- const d2 = max(nx * q + nz * (pz - h), -pz, pz - h);
7116
+ const d2 = max$1(nx * q + nz * (pz - h), -pz, pz - h);
6792
7117
  return d2;
6793
7118
  }
6794
- function sdTaperedSegment(px, py, pz, ax, ay, az, bx, by, bz, ra, rb) {
7119
+ function sdTaperedSegment$1(px, py, pz, ax, ay, az, bx, by, bz, ra, rb) {
6795
7120
  const vx = bx - ax;
6796
7121
  const vy = by - ay;
6797
7122
  const vz = bz - az;
6798
7123
  const len2 = vx * vx + vy * vy + vz * vz;
6799
- if (len2 <= 1e-12) return sdSphere(px - ax, py - ay, pz - az, max(ra, rb));
7124
+ if (len2 <= 1e-12) return sdSphere$1(px - ax, py - ay, pz - az, max$1(ra, rb));
6800
7125
  const h = clamp$a(((px - ax) * vx + (py - ay) * vy + (pz - az) * vz) / len2, 0, 1);
6801
- return length3(px - (ax + vx * h), py - (ay + vy * h), pz - (az + vz * h)) - (ra + (rb - ra) * h);
7126
+ return length3$1(px - (ax + vx * h), py - (ay + vy * h), pz - (az + vz * h)) - (ra + (rb - ra) * h);
6802
7127
  }
6803
7128
  function sdPolylineSweep3(node, x2, y2, z2) {
6804
7129
  let d2 = 1e20;
6805
7130
  for (let i = 0; i < node.points.length - 1; i++) {
6806
7131
  const a2 = node.points[i];
6807
7132
  const b = node.points[i + 1];
6808
- 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]);
6809
- d2 = i === 0 ? segment : smin(d2, segment, node.blend);
7133
+ 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]);
7134
+ d2 = i === 0 ? segment : smin$1(d2, segment, node.blend);
6810
7135
  }
6811
7136
  return d2;
6812
7137
  }
6813
- function smin(a2, b, k2) {
7138
+ function smin$1(a2, b, k2) {
6814
7139
  if (k2 <= 0) return min(a2, b);
6815
- const h = max(k2 - abs(a2 - b), 0) / k2;
7140
+ const h = max$1(k2 - abs(a2 - b), 0) / k2;
6816
7141
  return min(a2, b) - h * h * h * k2 * (1 / 6);
6817
7142
  }
6818
- function smax(a2, b, k2) {
6819
- return -smin(-a2, -b, k2);
7143
+ function smax$1(a2, b, k2) {
7144
+ return -smin$1(-a2, -b, k2);
6820
7145
  }
6821
- function repeatCoord(v, spacing, count) {
7146
+ function repeatCoord$1(v, spacing, count) {
6822
7147
  if (spacing <= 0) return v;
6823
7148
  if (count > 0) {
6824
7149
  const center = (count - 1) * 0.5;
@@ -6827,31 +7152,138 @@ function repeatCoord(v, spacing, count) {
6827
7152
  }
6828
7153
  return v - spacing * Math.round(v / spacing);
6829
7154
  }
7155
+ function positiveMod(v, period) {
7156
+ return (v % period + period) % period;
7157
+ }
7158
+ function evalStripesPattern(u2, v, directionX, directionY, spacing, width, depth) {
7159
+ const coord = u2 * directionX + v * directionY;
7160
+ const d2 = abs(coord - Math.round(coord / spacing) * spacing);
7161
+ const profile = max$1(0, 1 - d2 / (width * 0.5));
7162
+ return -(profile * profile) * depth;
7163
+ }
7164
+ function evalOverUnderWeavePattern(u2, v, spacingX, spacingY, widthX, widthY, depth, underScale) {
7165
+ const su = u2 / spacingX;
7166
+ const sv = v / spacingY;
7167
+ let pU = max$1(0, 1 - abs(su - Math.round(su)) * spacingX / (widthX * 0.5));
7168
+ let pV = max$1(0, 1 - abs(sv - Math.round(sv)) * spacingY / (widthY * 0.5));
7169
+ pU *= pU;
7170
+ pV *= pV;
7171
+ const checker = (Math.round(su) & 65535) + (Math.round(sv) & 65535) & 1;
7172
+ const top = checker ? pV : pU;
7173
+ const bot = checker ? pU : pV;
7174
+ return -max$1(top, bot * underScale) * depth;
7175
+ }
7176
+ function compileTypedSurfacePattern(pattern) {
7177
+ switch (pattern.kind) {
7178
+ case "surfacePattern:constant":
7179
+ return () => pattern.value;
7180
+ case "surfacePattern:sineWave": {
7181
+ const { direction: direction2, wavelength, amplitude, phase, bias } = pattern;
7182
+ const frequency = 2 * PI$1 / wavelength;
7183
+ return (u2, v) => bias + sin$1((u2 * direction2[0] + v * direction2[1]) * frequency + phase) * amplitude;
7184
+ }
7185
+ case "surfacePattern:stripes": {
7186
+ const { direction: direction2, spacing, width, depth } = pattern;
7187
+ return (u2, v) => evalStripesPattern(u2, v, direction2[0], direction2[1], spacing, width, depth);
7188
+ }
7189
+ case "surfacePattern:overUnderWeave": {
7190
+ const { spacing, threadWidth, depth, underScale } = pattern;
7191
+ return (u2, v) => evalOverUnderWeavePattern(u2, v, spacing[0], spacing[1], threadWidth[0], threadWidth[1], depth, underScale);
7192
+ }
7193
+ case "surfacePattern:abs": {
7194
+ const child = compileTypedSurfacePattern(pattern.child);
7195
+ return (u2, v) => abs(child(u2, v));
7196
+ }
7197
+ case "surfacePattern:negate": {
7198
+ const child = compileTypedSurfacePattern(pattern.child);
7199
+ return (u2, v) => -child(u2, v);
7200
+ }
7201
+ case "surfacePattern:add": {
7202
+ const children = pattern.children.map(compileTypedSurfacePattern);
7203
+ return (u2, v) => children.reduce((sum2, child) => sum2 + child(u2, v), 0);
7204
+ }
7205
+ case "surfacePattern:multiply": {
7206
+ const children = pattern.children.map(compileTypedSurfacePattern);
7207
+ return (u2, v) => children.reduce((product, child) => product * child(u2, v), 1);
7208
+ }
7209
+ case "surfacePattern:min": {
7210
+ const children = pattern.children.map(compileTypedSurfacePattern);
7211
+ if (children.length === 0) return () => 0;
7212
+ return (u2, v) => children.reduce((value, child) => min(value, child(u2, v)), Infinity);
7213
+ }
7214
+ case "surfacePattern:max": {
7215
+ const children = pattern.children.map(compileTypedSurfacePattern);
7216
+ if (children.length === 0) return () => 0;
7217
+ return (u2, v) => children.reduce((value, child) => max$1(value, child(u2, v)), -Infinity);
7218
+ }
7219
+ case "surfacePattern:clamp": {
7220
+ const child = compileTypedSurfacePattern(pattern.child);
7221
+ return (u2, v) => clamp$a(child(u2, v), pattern.min, pattern.max);
7222
+ }
7223
+ }
7224
+ }
7225
+ function estimateSurfacePatternAmplitude(pattern) {
7226
+ switch (pattern.kind) {
7227
+ case "surfacePattern:constant":
7228
+ return abs(pattern.value);
7229
+ case "surfacePattern:sineWave":
7230
+ return abs(pattern.bias) + abs(pattern.amplitude);
7231
+ case "surfacePattern:stripes":
7232
+ case "surfacePattern:overUnderWeave":
7233
+ return pattern.depth;
7234
+ case "surfacePattern:abs":
7235
+ case "surfacePattern:negate":
7236
+ return estimateSurfacePatternAmplitude(pattern.child);
7237
+ case "surfacePattern:add": {
7238
+ const amplitudes = pattern.children.map(estimateSurfacePatternAmplitude);
7239
+ return amplitudes.every((value) => value !== null) ? amplitudes.reduce((sum2, value) => sum2 + value, 0) : null;
7240
+ }
7241
+ case "surfacePattern:multiply": {
7242
+ const amplitudes = pattern.children.map(estimateSurfacePatternAmplitude);
7243
+ return amplitudes.every((value) => value !== null) ? amplitudes.reduce((product, value) => product * value, 1) : null;
7244
+ }
7245
+ case "surfacePattern:min":
7246
+ case "surfacePattern:max": {
7247
+ const amplitudes = pattern.children.map(estimateSurfacePatternAmplitude);
7248
+ return amplitudes.every((value) => value !== null) ? max$1(...amplitudes) : null;
7249
+ }
7250
+ case "surfacePattern:clamp":
7251
+ return max$1(abs(pattern.min), abs(pattern.max));
7252
+ }
7253
+ }
7254
+ function compileSurfacePattern(node) {
7255
+ if (node.pattern) return compileTypedSurfacePattern(node.pattern);
7256
+ const constEntries = Object.entries(node.constants ?? {});
7257
+ const constNames = constEntries.map(([k2]) => k2);
7258
+ const constValues = constEntries.map(([, v]) => v);
7259
+ const patternFn = new Function("u", "v", ...constNames, `return (${node.patternBody});`);
7260
+ return (u2, v) => patternFn(u2, v, ...constValues);
7261
+ }
6830
7262
  function compileSdfNode3(node) {
6831
7263
  switch (node.kind) {
6832
7264
  case "sdf:sphere": {
6833
7265
  const r = node.radius;
6834
- return (x2, y2, z2) => sdSphere(x2, y2, z2, r);
7266
+ return (x2, y2, z2) => sdSphere$1(x2, y2, z2, r);
6835
7267
  }
6836
7268
  case "sdf:box": {
6837
7269
  const [hx, hy, hz] = node.halfExtents;
6838
- return (x2, y2, z2) => sdBox(x2, y2, z2, hx, hy, hz);
7270
+ return (x2, y2, z2) => sdBox$1(x2, y2, z2, hx, hy, hz);
6839
7271
  }
6840
7272
  case "sdf:cylinder": {
6841
7273
  const { height: h, radius: r } = node;
6842
- return (x2, y2, z2) => sdCylinder(x2, y2, z2, h, r);
7274
+ return (x2, y2, z2) => sdCylinder$1(x2, y2, z2, h, r);
6843
7275
  }
6844
7276
  case "sdf:torus": {
6845
7277
  const { majorRadius: R, minorRadius: r } = node;
6846
- return (x2, y2, z2) => sdTorus(x2, y2, z2, R, r);
7278
+ return (x2, y2, z2) => sdTorus$1(x2, y2, z2, R, r);
6847
7279
  }
6848
7280
  case "sdf:capsule": {
6849
7281
  const { height: h, radius: r } = node;
6850
- return (x2, y2, z2) => sdCapsule(x2, y2, z2, h, r);
7282
+ return (x2, y2, z2) => sdCapsule$1(x2, y2, z2, h, r);
6851
7283
  }
6852
7284
  case "sdf:cone": {
6853
7285
  const { height: h, radius: r } = node;
6854
- return (x2, y2, z2) => sdCone(x2, y2, z2, h, r);
7286
+ return (x2, y2, z2) => sdCone$1(x2, y2, z2, h, r);
6855
7287
  }
6856
7288
  case "sdf:polylineSweep": {
6857
7289
  return (x2, y2, z2) => sdPolylineSweep3(node, x2, y2, z2);
@@ -6868,7 +7300,7 @@ function compileSdfNode3(node) {
6868
7300
  const fns = node.children.map(compileSdfNode3);
6869
7301
  return (x2, y2, z2) => {
6870
7302
  let d2 = fns[0](x2, y2, z2);
6871
- for (let i = 1; i < fns.length; i++) d2 = max(d2, -fns[i](x2, y2, z2));
7303
+ for (let i = 1; i < fns.length; i++) d2 = max$1(d2, -fns[i](x2, y2, z2));
6872
7304
  return d2;
6873
7305
  };
6874
7306
  }
@@ -6876,7 +7308,7 @@ function compileSdfNode3(node) {
6876
7308
  const fns = node.children.map(compileSdfNode3);
6877
7309
  return (x2, y2, z2) => {
6878
7310
  let d2 = fns[0](x2, y2, z2);
6879
- for (let i = 1; i < fns.length; i++) d2 = max(d2, fns[i](x2, y2, z2));
7311
+ for (let i = 1; i < fns.length; i++) d2 = max$1(d2, fns[i](x2, y2, z2));
6880
7312
  return d2;
6881
7313
  };
6882
7314
  }
@@ -6885,7 +7317,7 @@ function compileSdfNode3(node) {
6885
7317
  const k2 = node.radius;
6886
7318
  return (x2, y2, z2) => {
6887
7319
  let d2 = fns[0](x2, y2, z2);
6888
- for (let i = 1; i < fns.length; i++) d2 = smin(d2, fns[i](x2, y2, z2), k2);
7320
+ for (let i = 1; i < fns.length; i++) d2 = smin$1(d2, fns[i](x2, y2, z2), k2);
6889
7321
  return d2;
6890
7322
  };
6891
7323
  }
@@ -6894,7 +7326,7 @@ function compileSdfNode3(node) {
6894
7326
  const k2 = node.radius;
6895
7327
  return (x2, y2, z2) => {
6896
7328
  let d2 = fns[0](x2, y2, z2);
6897
- for (let i = 1; i < fns.length; i++) d2 = smax(d2, -fns[i](x2, y2, z2), k2);
7329
+ for (let i = 1; i < fns.length; i++) d2 = smax$1(d2, -fns[i](x2, y2, z2), k2);
6898
7330
  return d2;
6899
7331
  };
6900
7332
  }
@@ -6903,7 +7335,7 @@ function compileSdfNode3(node) {
6903
7335
  const k2 = node.radius;
6904
7336
  return (x2, y2, z2) => {
6905
7337
  let d2 = fns[0](x2, y2, z2);
6906
- for (let i = 1; i < fns.length; i++) d2 = smax(d2, fns[i](x2, y2, z2), k2);
7338
+ for (let i = 1; i < fns.length; i++) d2 = smax$1(d2, fns[i](x2, y2, z2), k2);
6907
7339
  return d2;
6908
7340
  };
6909
7341
  }
@@ -6921,10 +7353,10 @@ function compileSdfNode3(node) {
6921
7353
  }
6922
7354
  case "sdf:rotate": {
6923
7355
  const fn = compileSdfNode3(node.child);
6924
- const [rx, ry, rz] = node.degrees.map((d2) => d2 * DEG$1);
6925
- const cx = cos(rx), sx = sin(rx);
6926
- const cy = cos(ry), sy = sin(ry);
6927
- const cz = cos(rz), sz = sin(rz);
7356
+ const [rx, ry, rz] = node.degrees.map((d2) => d2 * DEG$2);
7357
+ const cx = cos$1(rx), sx = sin$1(rx);
7358
+ const cy = cos$1(ry), sy = sin$1(ry);
7359
+ const cz = cos$1(rz), sz = sin$1(rz);
6928
7360
  return (x2, y2, z2) => {
6929
7361
  const x1 = cz * x2 + sz * y2;
6930
7362
  const y1 = -sz * x2 + cz * y2;
@@ -6943,10 +7375,10 @@ function compileSdfNode3(node) {
6943
7375
  }
6944
7376
  case "sdf:twist": {
6945
7377
  const fn = compileSdfNode3(node.child);
6946
- const k2 = node.degreesPerUnit * DEG$1;
7378
+ const k2 = node.degreesPerUnit * DEG$2;
6947
7379
  return (x2, y2, z2) => {
6948
7380
  const angle = k2 * z2;
6949
- const c2 = cos(angle), s = sin(angle);
7381
+ const c2 = cos$1(angle), s = sin$1(angle);
6950
7382
  return fn(c2 * x2 - s * y2, s * x2 + c2 * y2, z2);
6951
7383
  };
6952
7384
  }
@@ -6955,7 +7387,7 @@ function compileSdfNode3(node) {
6955
7387
  const r = node.radius;
6956
7388
  return (x2, y2, z2) => {
6957
7389
  const angle = x2 / r;
6958
- const c2 = cos(angle), s = sin(angle);
7390
+ const c2 = cos$1(angle), s = sin$1(angle);
6959
7391
  return fn((r + y2) * s, (r + y2) * c2 - r, z2);
6960
7392
  };
6961
7393
  }
@@ -6963,7 +7395,19 @@ function compileSdfNode3(node) {
6963
7395
  const fn = compileSdfNode3(node.child);
6964
7396
  const [sx, sy, sz] = node.spacing;
6965
7397
  const [cx, cy, cz] = node.count;
6966
- return (x2, y2, z2) => fn(repeatCoord(x2, sx, cx), repeatCoord(y2, sy, cy), repeatCoord(z2, sz, cz));
7398
+ return (x2, y2, z2) => fn(repeatCoord$1(x2, sx, cx), repeatCoord$1(y2, sy, cy), repeatCoord$1(z2, sz, cz));
7399
+ }
7400
+ case "sdf:circularArray": {
7401
+ const fn = compileSdfNode3(node.child);
7402
+ const da = 2 * PI$1 / node.count;
7403
+ const offset2 = node.offset;
7404
+ return (x2, y2, z2) => {
7405
+ const r = length2$1(x2, y2);
7406
+ const a2 = positiveMod(Math.atan2(y2, x2), da);
7407
+ const d1 = fn(cos$1(a2 - da) * r - offset2, sin$1(a2 - da) * r, z2);
7408
+ const d2 = fn(cos$1(a2) * r - offset2, sin$1(a2) * r, z2);
7409
+ return min(d1, d2);
7410
+ };
6967
7411
  }
6968
7412
  case "sdf:shell": {
6969
7413
  const fn = compileSdfNode3(node.child);
@@ -6980,10 +7424,7 @@ function compileSdfNode3(node) {
6980
7424
  }
6981
7425
  case "sdf:surfaceDisplace": {
6982
7426
  const childFn = compileSdfNode3(node.child);
6983
- const constEntries = Object.entries(node.constants ?? {});
6984
- const constNames = constEntries.map(([k2]) => k2);
6985
- const constValues = constEntries.map(([, v]) => v);
6986
- const patternFn = new Function("u", "v", ...constNames, `return (${node.patternBody});`);
7427
+ const patternFn = compileSurfacePattern(node);
6987
7428
  const uvMode = node.uvMode && node.uvMode !== "auto" ? node.uvMode : void 0;
6988
7429
  const analysis = analyzeUV(node.child, uvMode);
6989
7430
  const uvFn = compileUVFunction(analysis);
@@ -6995,7 +7436,7 @@ function compileSdfNode3(node) {
6995
7436
  p2[1] = y2;
6996
7437
  p2[2] = z2;
6997
7438
  const [u2, v] = uvFn(p2);
6998
- return d2 + patternFn(u2, v, ...constValues);
7439
+ return d2 + patternFn(u2, v);
6999
7440
  };
7000
7441
  }
7001
7442
  const sharpness = node.triplanarSharpness ?? 4;
@@ -7006,9 +7447,9 @@ function compileSdfNode3(node) {
7006
7447
  const gy = childFn(x2, y2 + eps, z2) - childFn(x2, y2 - eps, z2);
7007
7448
  const gz = childFn(x2, y2, z2 + eps) - childFn(x2, y2, z2 - eps);
7008
7449
  const { wx, wy, wz } = triplanarWeights(gx, gy, gz, sharpness);
7009
- const hX = patternFn(y2, z2, ...constValues);
7010
- const hY = patternFn(x2, z2, ...constValues);
7011
- const hZ = patternFn(x2, y2, ...constValues);
7450
+ const hX = patternFn(y2, z2);
7451
+ const hY = patternFn(x2, z2);
7452
+ const hZ = patternFn(x2, y2);
7012
7453
  return d2 + wx * hX + wy * hY + wz * hZ;
7013
7454
  };
7014
7455
  }
@@ -7078,7 +7519,7 @@ function compileSdfNode3(node) {
7078
7519
  const gx = gradFn(x2 + eps, y2, z2) - gradFn(x2 - eps, y2, z2);
7079
7520
  const gy = gradFn(x2, y2 + eps, z2) - gradFn(x2, y2 - eps, z2);
7080
7521
  const gz = gradFn(x2, y2, z2 + eps) - gradFn(x2, y2, z2 - eps);
7081
- const glen = sqrt(gx * gx + gy * gy + gz * gz);
7522
+ const glen = sqrt$1(gx * gx + gy * gy + gz * gz);
7082
7523
  let nx = 0, ny = 0, nz = 0;
7083
7524
  if (glen > 1e-10) {
7084
7525
  const invG = 1 / glen;
@@ -7140,7 +7581,7 @@ function estimateSdfBounds(node) {
7140
7581
  for (const point2 of node.points) {
7141
7582
  for (let i = 0; i < 3; i++) {
7142
7583
  minPoint[i] = min(minPoint[i], point2[i]);
7143
- maxPoint[i] = max(maxPoint[i], point2[i]);
7584
+ maxPoint[i] = max$1(maxPoint[i], point2[i]);
7144
7585
  }
7145
7586
  }
7146
7587
  return padBounds({ min: minPoint, max: maxPoint }, pad);
@@ -7171,7 +7612,7 @@ function estimateSdfBounds(node) {
7171
7612
  }
7172
7613
  case "sdf:rotate": {
7173
7614
  const b = estimateSdfBounds(node.child);
7174
- 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])));
7615
+ 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])));
7175
7616
  return { min: [-r, -r, -r], max: [r, r, r] };
7176
7617
  }
7177
7618
  case "sdf:scale": {
@@ -7185,7 +7626,7 @@ function estimateSdfBounds(node) {
7185
7626
  case "sdf:twist":
7186
7627
  case "sdf:bend": {
7187
7628
  const b = estimateSdfBounds(node.child);
7188
- 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;
7629
+ 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;
7189
7630
  return { min: [-r, -r, -r], max: [r, r, r] };
7190
7631
  }
7191
7632
  case "sdf:repeat": {
@@ -7206,16 +7647,29 @@ function estimateSdfBounds(node) {
7206
7647
  const [zMin, zMax] = expand(sz, cz, b.min[2], b.max[2]);
7207
7648
  return { min: [xMin, yMin, zMin], max: [xMax, yMax, zMax] };
7208
7649
  }
7650
+ case "sdf:circularArray": {
7651
+ const b = estimateSdfBounds(node.child);
7652
+ const x0 = b.min[0] + node.offset;
7653
+ const x1 = b.max[0] + node.offset;
7654
+ const y0 = b.min[1];
7655
+ const y1 = b.max[1];
7656
+ const r = max$1(length2$1(x0, y0), length2$1(x0, y1), length2$1(x1, y0), length2$1(x1, y1));
7657
+ return { min: [-r, -r, b.min[2]], max: [r, r, b.max[2]] };
7658
+ }
7209
7659
  case "sdf:shell": {
7210
7660
  const b = estimateSdfBounds(node.child);
7211
7661
  const t = node.thickness * 0.5;
7212
7662
  return padBounds(b, t);
7213
7663
  }
7214
- case "sdf:displace":
7215
- case "sdf:surfaceDisplace": {
7664
+ case "sdf:displace": {
7216
7665
  const b = estimateSdfBounds(node.child);
7217
7666
  return padBounds(b, 5);
7218
7667
  }
7668
+ case "sdf:surfaceDisplace": {
7669
+ const b = estimateSdfBounds(node.child);
7670
+ if (!node.pattern) return padBounds(b, 5);
7671
+ return padBounds(b, estimateSurfacePatternAmplitude(node.pattern) ?? 5);
7672
+ }
7219
7673
  case "sdf:onion": {
7220
7674
  const b = estimateSdfBounds(node.child);
7221
7675
  return padBounds(b, node.layers * node.thickness);
@@ -7252,7 +7706,7 @@ function unionBounds(bounds, pad) {
7252
7706
  for (const b of bounds) {
7253
7707
  for (let i = 0; i < 3; i++) {
7254
7708
  result.min[i] = min(result.min[i], b.min[i]);
7255
- result.max[i] = max(result.max[i], b.max[i]);
7709
+ result.max[i] = max$1(result.max[i], b.max[i]);
7256
7710
  }
7257
7711
  }
7258
7712
  if (pad > 0) return padBounds(result, pad);
@@ -7265,7 +7719,7 @@ function intersectBounds(bounds, pad) {
7265
7719
  };
7266
7720
  for (const b of bounds) {
7267
7721
  for (let i = 0; i < 3; i++) {
7268
- result.min[i] = max(result.min[i], b.min[i]);
7722
+ result.min[i] = max$1(result.min[i], b.min[i]);
7269
7723
  result.max[i] = min(result.max[i], b.max[i]);
7270
7724
  }
7271
7725
  }
@@ -7281,242 +7735,544 @@ function padBounds(b, pad) {
7281
7735
  max: [b.max[0] + pad, b.max[1] + pad, b.max[2] + pad]
7282
7736
  };
7283
7737
  }
7284
- const DEFAULT_MAX_GRID_POINTS = 8e6;
7285
- const DEFAULT_MIN_EDGE_LENGTH = 0.15;
7286
- function resolveSdfMeshingSettings(tree, bounds, options = {}) {
7287
- const quality = options.quality ?? "preview";
7288
- const minEdgeLength = positiveOrDefault(options.minEdgeLength, DEFAULT_MIN_EDGE_LENGTH);
7289
- const maxGridPoints = positiveOrDefault(options.maxGridPoints, DEFAULT_MAX_GRID_POINTS);
7290
- const tolerance = options.tolerance !== void 0 ? requirePositiveFinite$2(options.tolerance, "SDF tolerance") : void 0;
7291
- const minFeatureSize = options.minFeatureSize !== void 0 ? requirePositiveFinite$2(options.minFeatureSize, "SDF minFeatureSize") : void 0;
7292
- const maxTriangles = options.maxTriangles !== void 0 ? Math.floor(requirePositiveFinite$2(options.maxTriangles, "SDF maxTriangles")) : void 0;
7293
- const analysis = analyzeSdfTree(tree);
7294
- const warnings = [];
7295
- let edgeLength2;
7296
- if (options.edgeLength !== void 0) {
7297
- edgeLength2 = requirePositiveFinite$2(options.edgeLength, "SDF edgeLength");
7298
- if (edgeLength2 < minEdgeLength) {
7299
- warnings.push(`edgeLength ${formatMm(edgeLength2)} was clamped to minimum ${formatMm(minEdgeLength)}.`);
7300
- edgeLength2 = minEdgeLength;
7301
- }
7302
- } else {
7303
- edgeLength2 = resolveDefaultEdgeLength(bounds, quality, minEdgeLength, analysis, options);
7738
+ const { PI } = Math;
7739
+ const DEG$1 = PI / 180;
7740
+ const TAU = 2 * PI;
7741
+ const GRAD_EPS = 1e-9;
7742
+ const Op = {
7743
+ Const: 0,
7744
+ Neg: 1,
7745
+ Abs: 2,
7746
+ Sqrt: 3,
7747
+ Sin: 4,
7748
+ Cos: 5,
7749
+ Round: 6,
7750
+ Add: 7,
7751
+ Sub: 8,
7752
+ Mul: 9,
7753
+ Div: 10,
7754
+ Min: 11,
7755
+ Max: 12
7756
+ };
7757
+ class UnsupportedSdfProgramNodeError extends Error {
7758
+ constructor(message) {
7759
+ super(message);
7760
+ this.name = "UnsupportedSdfProgramNodeError";
7304
7761
  }
7305
- if (analysis.minWallThickness < Infinity && analysis.minWallThickness < edgeLength2 * 2) {
7306
- analysis.riskFlags.add("thin-shell");
7307
- warnings.push(
7308
- `shell/wall thickness ${formatMm(analysis.minWallThickness)} is below 2 x edgeLength ${formatMm(edgeLength2)}; thin features may be under-sampled.`
7309
- );
7762
+ }
7763
+ class SdfProgramBuilder {
7764
+ constructor() {
7765
+ __publicField(this, "opcodes", []);
7766
+ __publicField(this, "argA", []);
7767
+ __publicField(this, "argB", []);
7768
+ __publicField(this, "argC", []);
7769
+ __publicField(this, "constants", []);
7770
+ __publicField(this, "x", 0);
7771
+ __publicField(this, "y", 1);
7772
+ __publicField(this, "z", 2);
7310
7773
  }
7311
- if (!options.bounds && analysis.hasInfiniteRepeat) {
7312
- warnings.push("infinite repeat bounds are heuristic; pass .toShape({ bounds }) for predictable clipping.");
7774
+ constant(value) {
7775
+ const index2 = this.constants.length;
7776
+ this.constants.push(value);
7777
+ return this.push(Op.Const, 0, 0, index2);
7313
7778
  }
7314
- if (!options.bounds && analysis.riskFlags.has("noise")) {
7315
- warnings.push("noise field bounds are heuristic; pass .toShape({ bounds }) for predictable clipping.");
7779
+ neg(a2) {
7780
+ return this.push(Op.Neg, a2);
7316
7781
  }
7317
- if (!options.bounds && (analysis.riskFlags.has("tpms") || analysis.riskFlags.has("voronoi"))) {
7318
- warnings.push("TPMS/Voronoi bounds are heuristic unless clipped or passed explicitly.");
7782
+ abs(a2) {
7783
+ return this.push(Op.Abs, a2);
7319
7784
  }
7320
- if (analysis.hasLegacyTpmsThreshold) {
7321
- warnings.push("TPMS thickness is using legacy field-threshold units; use wallThickness for approximate millimeters.");
7785
+ sqrt(a2) {
7786
+ return this.push(Op.Sqrt, a2);
7322
7787
  }
7788
+ sin(a2) {
7789
+ return this.push(Op.Sin, a2);
7790
+ }
7791
+ cos(a2) {
7792
+ return this.push(Op.Cos, a2);
7793
+ }
7794
+ round(a2) {
7795
+ return this.push(Op.Round, a2);
7796
+ }
7797
+ add(a2, b) {
7798
+ return this.push(Op.Add, a2, b);
7799
+ }
7800
+ sub(a2, b) {
7801
+ return this.push(Op.Sub, a2, b);
7802
+ }
7803
+ mul(a2, b) {
7804
+ return this.push(Op.Mul, a2, b);
7805
+ }
7806
+ div(a2, b) {
7807
+ return this.push(Op.Div, a2, b);
7808
+ }
7809
+ min(a2, b) {
7810
+ return this.push(Op.Min, a2, b);
7811
+ }
7812
+ max(a2, b) {
7813
+ return this.push(Op.Max, a2, b);
7814
+ }
7815
+ finalize(output) {
7816
+ return {
7817
+ opcodes: Uint8Array.from(this.opcodes),
7818
+ argA: Int32Array.from(this.argA),
7819
+ argB: Int32Array.from(this.argB),
7820
+ argC: Int32Array.from(this.argC),
7821
+ constants: Float64Array.from(this.constants),
7822
+ output,
7823
+ slotCount: this.opcodes.length + 3
7824
+ };
7825
+ }
7826
+ push(op, a2 = 0, b = 0, c2 = 0) {
7827
+ const slot2 = this.opcodes.length + 3;
7828
+ this.opcodes.push(op);
7829
+ this.argA.push(a2);
7830
+ this.argB.push(b);
7831
+ this.argC.push(c2);
7832
+ return slot2;
7833
+ }
7834
+ }
7835
+ function compileSdfProgramEvaluator3(program) {
7836
+ const lines = ["const { abs, sqrt, sin, cos, round, min, max } = Math;", "let v0 = x;", "let v1 = y;", "let v2 = z;"];
7837
+ const { opcodes, argA, argB, argC, constants, output } = program;
7838
+ for (let i = 0; i < opcodes.length; i++) {
7839
+ const slot2 = `v${i + 3}`;
7840
+ const a2 = `v${argA[i]}`;
7841
+ const b = `v${argB[i]}`;
7842
+ switch (opcodes[i]) {
7843
+ case Op.Const:
7844
+ lines.push(`let ${slot2} = ${numberLiteral(constants[argC[i]])};`);
7845
+ break;
7846
+ case Op.Neg:
7847
+ lines.push(`let ${slot2} = -${a2};`);
7848
+ break;
7849
+ case Op.Abs:
7850
+ lines.push(`let ${slot2} = abs(${a2});`);
7851
+ break;
7852
+ case Op.Sqrt:
7853
+ lines.push(`let ${slot2} = sqrt(${a2});`);
7854
+ break;
7855
+ case Op.Sin:
7856
+ lines.push(`let ${slot2} = sin(${a2});`);
7857
+ break;
7858
+ case Op.Cos:
7859
+ lines.push(`let ${slot2} = cos(${a2});`);
7860
+ break;
7861
+ case Op.Round:
7862
+ lines.push(`let ${slot2} = round(${a2});`);
7863
+ break;
7864
+ case Op.Add:
7865
+ lines.push(`let ${slot2} = ${a2} + ${b};`);
7866
+ break;
7867
+ case Op.Sub:
7868
+ lines.push(`let ${slot2} = ${a2} - ${b};`);
7869
+ break;
7870
+ case Op.Mul:
7871
+ lines.push(`let ${slot2} = ${a2} * ${b};`);
7872
+ break;
7873
+ case Op.Div:
7874
+ lines.push(`let ${slot2} = ${a2} / ${b};`);
7875
+ break;
7876
+ case Op.Min:
7877
+ lines.push(`let ${slot2} = min(${a2}, ${b});`);
7878
+ break;
7879
+ case Op.Max:
7880
+ lines.push(`let ${slot2} = max(${a2}, ${b});`);
7881
+ break;
7882
+ default:
7883
+ throw new Error(`Unknown SdfProgram opcode ${opcodes[i]} at instruction ${i}.`);
7884
+ }
7885
+ }
7886
+ lines.push(`return v${output};`);
7887
+ return new Function("Math", `return function sdfProgramEval(x, y, z) {
7888
+ ${lines.join("\n")}
7889
+ };`)(Math);
7890
+ }
7891
+ function numberLiteral(value) {
7892
+ if (Number.isNaN(value)) return "NaN";
7893
+ if (value === Number.POSITIVE_INFINITY) return "Infinity";
7894
+ if (value === Number.NEGATIVE_INFINITY) return "-Infinity";
7895
+ return String(value);
7896
+ }
7897
+ function clampSlot(b, v, lo, hi) {
7898
+ return b.min(b.max(v, b.constant(lo)), b.constant(hi));
7899
+ }
7900
+ function length2(b, x2, y2) {
7901
+ return b.sqrt(b.add(b.mul(x2, x2), b.mul(y2, y2)));
7902
+ }
7903
+ function length3(b, x2, y2, z2) {
7904
+ return b.sqrt(b.add(b.add(b.mul(x2, x2), b.mul(y2, y2)), b.mul(z2, z2)));
7905
+ }
7906
+ function smin(b, a2, child, k2) {
7907
+ if (k2 <= 0) return b.min(a2, child);
7908
+ const h = b.div(b.max(b.sub(b.constant(k2), b.abs(b.sub(a2, child))), b.constant(0)), b.constant(k2));
7909
+ return b.sub(b.min(a2, child), b.mul(b.mul(b.mul(h, h), h), b.constant(k2 / 6)));
7910
+ }
7911
+ function smax(b, a2, child, k2) {
7912
+ return b.neg(smin(b, b.neg(a2), b.neg(child), k2));
7913
+ }
7914
+ function emitScaledTrig(b, x2, y2, z2, cellSize) {
7915
+ const s = b.constant(TAU / cellSize);
7916
+ const xs = b.mul(x2, s);
7917
+ const ys = b.mul(y2, s);
7918
+ const zs = b.mul(z2, s);
7323
7919
  return {
7324
- quality,
7325
- edgeLength: edgeLength2,
7326
- tolerance,
7327
- minFeatureSize,
7328
- minEdgeLength,
7329
- simplify: resolveSimplificationMode(options.simplify, quality, analysis.riskFlags),
7330
- maxTriangles,
7331
- maxGridPoints,
7332
- diagnostics: options.diagnostics === true,
7333
- treeRiskFlags: [...analysis.riskFlags].sort(),
7334
- warnings
7920
+ scale: s,
7921
+ sx: b.sin(xs),
7922
+ sy: b.sin(ys),
7923
+ sz: b.sin(zs),
7924
+ cx: b.cos(xs),
7925
+ cy: b.cos(ys),
7926
+ cz: b.cos(zs),
7927
+ sx2: b.sin(b.mul(b.constant(2), xs)),
7928
+ sy2: b.sin(b.mul(b.constant(2), ys)),
7929
+ sz2: b.sin(b.mul(b.constant(2), zs)),
7930
+ cx2: b.cos(b.mul(b.constant(2), xs)),
7931
+ cy2: b.cos(b.mul(b.constant(2), ys)),
7932
+ cz2: b.cos(b.mul(b.constant(2), zs))
7335
7933
  };
7336
7934
  }
7337
- function withScaledSdfEdgeLength(settings, edgeLength2) {
7338
- return { ...settings, edgeLength: Math.max(settings.minEdgeLength, edgeLength2) };
7935
+ function emitGyroidValueAndGradient(b, x2, y2, z2, cellSize) {
7936
+ const t = emitScaledTrig(b, x2, y2, z2, cellSize);
7937
+ return {
7938
+ value: b.add(b.add(b.mul(t.sx, t.cy), b.mul(t.sy, t.cz)), b.mul(t.sz, t.cx)),
7939
+ gx: b.mul(t.scale, b.sub(b.mul(t.cx, t.cy), b.mul(t.sz, t.sx))),
7940
+ gy: b.mul(t.scale, b.add(b.neg(b.mul(t.sx, t.sy)), b.mul(t.cy, t.cz))),
7941
+ gz: b.mul(t.scale, b.add(b.neg(b.mul(t.sy, t.sz)), b.mul(t.cz, t.cx)))
7942
+ };
7339
7943
  }
7340
- function createSdfMeshingDiagnostics(settings, bounds, paddedBounds) {
7341
- const grid = estimateSdfGridDimensions(paddedBounds, settings.edgeLength);
7342
- const estimatedSamples = grid[0] * grid[1] * grid[2];
7944
+ function emitSchwarzPValueAndGradient(b, x2, y2, z2, cellSize) {
7945
+ const t = emitScaledTrig(b, x2, y2, z2, cellSize);
7343
7946
  return {
7344
- bounds: cloneBounds$2(bounds),
7345
- paddedBounds: cloneBounds$2(paddedBounds),
7346
- edgeLength: settings.edgeLength,
7347
- grid,
7348
- estimatedSamples,
7349
- estimatedMemoryBytes: estimatedSamples * 8,
7350
- treeRiskFlags: [...settings.treeRiskFlags],
7351
- simplification: settings.simplify,
7352
- capMode: "box",
7353
- capInset: settings.edgeLength,
7354
- warnings: [...settings.warnings]
7947
+ value: b.add(b.add(t.cx, t.cy), t.cz),
7948
+ gx: b.neg(b.mul(t.scale, t.sx)),
7949
+ gy: b.neg(b.mul(t.scale, t.sy)),
7950
+ gz: b.neg(b.mul(t.scale, t.sz))
7355
7951
  };
7356
7952
  }
7357
- function assertSdfMeshingBudget(diagnostics, maxGridPoints) {
7358
- if (diagnostics.estimatedSamples <= maxGridPoints) return;
7359
- const suggestedEdge = suggestEdgeLengthForSampleBudget(diagnostics.paddedBounds, maxGridPoints);
7360
- throw new Error(
7361
- `SDF meshing would sample ${formatCount(diagnostics.estimatedSamples)} grid points (~${formatBytes(
7362
- diagnostics.estimatedMemoryBytes
7363
- )}). Reduce bounds or use edgeLength >= ${formatMm(suggestedEdge)}.`
7953
+ function emitDiamondValueAndGradient(b, x2, y2, z2, cellSize) {
7954
+ const t = emitScaledTrig(b, x2, y2, z2, cellSize);
7955
+ return {
7956
+ value: b.add(
7957
+ b.add(b.mul(b.mul(t.sx, t.sy), t.sz), b.mul(b.mul(t.sx, t.cy), t.cz)),
7958
+ b.add(b.mul(b.mul(t.cx, t.sy), t.cz), b.mul(b.mul(t.cx, t.cy), t.sz))
7959
+ ),
7960
+ gx: b.mul(
7961
+ t.scale,
7962
+ b.add(
7963
+ b.add(b.mul(b.mul(t.cx, t.sy), t.sz), b.mul(b.mul(t.cx, t.cy), t.cz)),
7964
+ 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)))
7965
+ )
7966
+ ),
7967
+ gy: b.mul(
7968
+ t.scale,
7969
+ b.add(
7970
+ b.add(b.mul(b.mul(t.sx, t.cy), t.sz), b.neg(b.mul(b.mul(t.sx, t.sy), t.cz))),
7971
+ b.add(b.mul(b.mul(t.cx, t.cy), t.cz), b.neg(b.mul(b.mul(t.cx, t.sy), t.sz)))
7972
+ )
7973
+ ),
7974
+ gz: b.mul(
7975
+ t.scale,
7976
+ b.add(
7977
+ b.add(b.mul(b.mul(t.sx, t.sy), t.cz), b.neg(b.mul(b.mul(t.sx, t.cy), t.sz))),
7978
+ b.add(b.neg(b.mul(b.mul(t.cx, t.sy), t.sz)), b.mul(b.mul(t.cx, t.cy), t.cz))
7979
+ )
7980
+ )
7981
+ };
7982
+ }
7983
+ function emitLidinoidValueAndGradient(b, x2, y2, z2, cellSize) {
7984
+ const t = emitScaledTrig(b, x2, y2, z2, cellSize);
7985
+ const value = b.add(
7986
+ b.sub(
7987
+ 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)),
7988
+ b.add(b.add(b.mul(t.cx2, t.cy2), b.mul(t.cy2, t.cz2)), b.mul(t.cz2, t.cx2))
7989
+ ),
7990
+ b.constant(0.3)
7364
7991
  );
7992
+ return {
7993
+ value,
7994
+ gx: b.mul(
7995
+ t.scale,
7996
+ b.add(
7997
+ b.add(
7998
+ 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)),
7999
+ b.neg(b.mul(b.mul(t.sz2, t.sx), t.sy))
8000
+ ),
8001
+ b.add(b.mul(b.mul(b.constant(2), t.sx2), t.cy2), b.mul(b.mul(b.constant(2), t.cz2), t.sx2))
8002
+ )
8003
+ ),
8004
+ gy: b.mul(
8005
+ t.scale,
8006
+ b.add(
8007
+ b.add(
8008
+ 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))),
8009
+ b.mul(b.mul(t.sz2, t.cx), t.cy)
8010
+ ),
8011
+ b.add(b.mul(b.mul(b.constant(2), t.cx2), t.sy2), b.mul(b.mul(b.constant(2), t.sy2), t.cz2))
8012
+ )
8013
+ ),
8014
+ gz: b.mul(
8015
+ t.scale,
8016
+ b.add(
8017
+ b.add(
8018
+ b.add(b.mul(b.mul(t.sx2, t.cy), t.cz), b.neg(b.mul(b.mul(t.sy2, t.sz), t.sx))),
8019
+ b.mul(b.mul(b.constant(2), t.cz2), b.mul(t.cx, t.sy))
8020
+ ),
8021
+ b.add(b.mul(b.mul(b.constant(2), t.cy2), t.sz2), b.mul(b.mul(b.constant(2), t.sz2), t.cx2))
8022
+ )
8023
+ )
8024
+ };
7365
8025
  }
7366
- function estimateSdfGridDimensions(bounds, edgeLength2) {
7367
- const dx = bounds.max[0] - bounds.min[0];
7368
- const dy = bounds.max[1] - bounds.min[1];
7369
- const dz = bounds.max[2] - bounds.min[2];
7370
- return [
7371
- Math.max(2, Math.ceil(dx / edgeLength2) + 1),
7372
- Math.max(2, Math.ceil(dy / edgeLength2) + 1),
7373
- Math.max(2, Math.ceil(dz / edgeLength2) + 1)
7374
- ];
8026
+ function emitTpmsDistance(b, { value, gx, gy, gz }, thickness, thicknessMode) {
8027
+ if (thicknessMode !== "metric-approx") return b.sub(b.abs(value), b.constant(thickness));
8028
+ const grad = length3(b, gx, gy, gz);
8029
+ return b.sub(b.div(b.abs(value), b.max(grad, b.constant(GRAD_EPS))), b.constant(thickness * 0.5));
7375
8030
  }
7376
- function logSdfMeshingDiagnostics(prefix, diagnostics) {
7377
- const warnings = diagnostics.warnings.length > 0 ? `, warnings=${diagnostics.warnings.join(" | ")}` : "";
7378
- console.info(
7379
- `${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}`
7380
- );
8031
+ const { cos, max, sin, sqrt } = Math;
8032
+ function emitSdfProgramNode(b, node, x2, y2, z2) {
8033
+ switch (node.kind) {
8034
+ case "sdf:sphere":
8035
+ return sdSphere(b, x2, y2, z2, node.radius);
8036
+ case "sdf:box": {
8037
+ const [hx, hy, hz] = node.halfExtents;
8038
+ return sdBox(b, x2, y2, z2, hx, hy, hz);
8039
+ }
8040
+ case "sdf:cylinder":
8041
+ return sdCylinder(b, x2, y2, z2, node.height, node.radius);
8042
+ case "sdf:torus":
8043
+ return sdTorus(b, x2, y2, z2, node.majorRadius, node.minorRadius);
8044
+ case "sdf:capsule":
8045
+ return sdCapsule(b, x2, y2, z2, node.height, node.radius);
8046
+ case "sdf:cone":
8047
+ return sdCone(b, x2, y2, z2, node.height, node.radius);
8048
+ case "sdf:polylineSweep":
8049
+ return sdPolylineSweep(b, node, x2, y2, z2);
8050
+ case "sdf:union":
8051
+ return foldChildren(b, node.children, x2, y2, z2, (a2, child) => b.min(a2, child));
8052
+ case "sdf:difference":
8053
+ return foldChildren(b, node.children, x2, y2, z2, (a2, child) => b.max(a2, b.neg(child)));
8054
+ case "sdf:intersection":
8055
+ return foldChildren(b, node.children, x2, y2, z2, (a2, child) => b.max(a2, child));
8056
+ case "sdf:smoothUnion":
8057
+ return foldChildren(b, node.children, x2, y2, z2, (a2, child) => smin(b, a2, child, node.radius));
8058
+ case "sdf:smoothDifference":
8059
+ return foldChildren(b, node.children, x2, y2, z2, (a2, child) => smax(b, a2, b.neg(child), node.radius));
8060
+ case "sdf:smoothIntersection":
8061
+ return foldChildren(b, node.children, x2, y2, z2, (a2, child) => smax(b, a2, child, node.radius));
8062
+ case "sdf:morph": {
8063
+ const a2 = emitSdfProgramNode(b, node.a, x2, y2, z2);
8064
+ const childB = emitSdfProgramNode(b, node.b, x2, y2, z2);
8065
+ return b.add(b.mul(a2, b.constant(1 - node.t)), b.mul(childB, b.constant(node.t)));
8066
+ }
8067
+ case "sdf:translate": {
8068
+ const [ox, oy, oz] = node.offset;
8069
+ return emitSdfProgramNode(b, node.child, b.sub(x2, b.constant(ox)), b.sub(y2, b.constant(oy)), b.sub(z2, b.constant(oz)));
8070
+ }
8071
+ case "sdf:rotate":
8072
+ return emitRotated(b, node, x2, y2, z2);
8073
+ case "sdf:scale": {
8074
+ const inv = 1 / node.factor;
8075
+ const child = emitSdfProgramNode(b, node.child, b.mul(x2, b.constant(inv)), b.mul(y2, b.constant(inv)), b.mul(z2, b.constant(inv)));
8076
+ return b.mul(child, b.constant(node.factor));
8077
+ }
8078
+ case "sdf:twist": {
8079
+ const angle = b.mul(b.constant(node.degreesPerUnit * DEG$1), z2);
8080
+ const c2 = b.cos(angle);
8081
+ const s = b.sin(angle);
8082
+ 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);
8083
+ }
8084
+ case "sdf:bend": {
8085
+ const angle = b.div(x2, b.constant(node.radius));
8086
+ const c2 = b.cos(angle);
8087
+ const s = b.sin(angle);
8088
+ const radiusPlusY = b.add(b.constant(node.radius), y2);
8089
+ return emitSdfProgramNode(b, node.child, b.mul(radiusPlusY, s), b.sub(b.mul(radiusPlusY, c2), b.constant(node.radius)), z2);
8090
+ }
8091
+ case "sdf:repeat": {
8092
+ const [sx, sy, sz] = node.spacing;
8093
+ const [cx, cy, cz] = node.count;
8094
+ return emitSdfProgramNode(b, node.child, repeatCoord(b, x2, sx, cx), repeatCoord(b, y2, sy, cy), repeatCoord(b, z2, sz, cz));
8095
+ }
8096
+ case "sdf:shell": {
8097
+ const child = emitSdfProgramNode(b, node.child, x2, y2, z2);
8098
+ return b.sub(b.abs(child), b.constant(node.thickness * 0.5));
8099
+ }
8100
+ case "sdf:onion": {
8101
+ let d2 = emitSdfProgramNode(b, node.child, x2, y2, z2);
8102
+ for (let i = 0; i < node.layers; i++) d2 = b.sub(b.abs(d2), b.constant(node.thickness));
8103
+ return d2;
8104
+ }
8105
+ case "sdf:gyroid":
8106
+ return emitTpmsDistance(b, emitGyroidValueAndGradient(b, x2, y2, z2, node.cellSize), node.thickness, node.thicknessMode);
8107
+ case "sdf:schwarzP":
8108
+ return emitTpmsDistance(b, emitSchwarzPValueAndGradient(b, x2, y2, z2, node.cellSize), node.thickness, node.thicknessMode);
8109
+ case "sdf:diamond":
8110
+ return emitTpmsDistance(b, emitDiamondValueAndGradient(b, x2, y2, z2, node.cellSize), node.thickness, node.thicknessMode);
8111
+ case "sdf:lidinoid":
8112
+ return emitTpmsDistance(b, emitLidinoidValueAndGradient(b, x2, y2, z2, node.cellSize), node.thickness, node.thicknessMode);
8113
+ default:
8114
+ throw new UnsupportedSdfProgramNodeError(`SdfProgram does not support node kind ${node.kind} yet.`);
8115
+ }
7381
8116
  }
7382
- function resolveDefaultEdgeLength(bounds, quality, minEdgeLength, analysis, options) {
7383
- const dx = bounds.max[0] - bounds.min[0];
7384
- const dy = bounds.max[1] - bounds.min[1];
7385
- const dz = bounds.max[2] - bounds.min[2];
7386
- const maxDim = Math.max(dx, dy, dz, minEdgeLength);
7387
- const divisor = quality === "draft" ? 60 : quality === "export" ? 160 : 100;
7388
- const candidates = [maxDim / divisor];
7389
- if (options.tolerance !== void 0) candidates.push(requirePositiveFinite$2(options.tolerance, "SDF tolerance") * 2);
7390
- if (options.minFeatureSize !== void 0) candidates.push(requirePositiveFinite$2(options.minFeatureSize, "SDF minFeatureSize") / 2.5);
7391
- if (analysis.minMetricTpmsThickness < Infinity) candidates.push(analysis.minMetricTpmsThickness / 2);
7392
- if (analysis.minTpmsCellSize < Infinity) candidates.push(analysis.minTpmsCellSize / 10);
7393
- if (analysis.minRepeatSpacing < Infinity) candidates.push(analysis.minRepeatSpacing / 8);
7394
- if (analysis.minWallThickness < Infinity) candidates.push(analysis.minWallThickness / 2.5);
7395
- return Math.max(minEdgeLength, Math.min(...candidates.filter((v) => Number.isFinite(v) && v > 0)));
8117
+ function foldChildren(b, children, x2, y2, z2, combine2) {
8118
+ let d2 = emitSdfProgramNode(b, children[0], x2, y2, z2);
8119
+ for (let i = 1; i < children.length; i++) d2 = combine2(d2, emitSdfProgramNode(b, children[i], x2, y2, z2));
8120
+ return d2;
7396
8121
  }
7397
- function resolveSimplificationMode(simplify, quality, riskFlags) {
7398
- if (simplify === false) return "off";
7399
- if (simplify === true || simplify === "safe") return "safe";
7400
- if (quality === "export" && riskFlags.size > 0) return "off";
7401
- return "safe";
8122
+ function emitRotated(b, node, x2, y2, z2) {
8123
+ const [rx, ry, rz] = node.degrees.map((d2) => d2 * DEG$1);
8124
+ const cx = cos(rx);
8125
+ const sx = sin(rx);
8126
+ const cy = cos(ry);
8127
+ const sy = sin(ry);
8128
+ const cz = cos(rz);
8129
+ const sz = sin(rz);
8130
+ const x1 = b.add(b.mul(b.constant(cz), x2), b.mul(b.constant(sz), y2));
8131
+ const y1 = b.sub(b.mul(b.constant(cz), y2), b.mul(b.constant(sz), x2));
8132
+ const x22 = b.sub(b.mul(b.constant(cy), x1), b.mul(b.constant(sy), z2));
8133
+ const z22 = b.add(b.mul(b.constant(sy), x1), b.mul(b.constant(cy), z2));
8134
+ const y22 = b.add(b.mul(b.constant(cx), y1), b.mul(b.constant(sx), z22));
8135
+ const z3 = b.sub(b.mul(b.constant(cx), z22), b.mul(b.constant(sx), y1));
8136
+ return emitSdfProgramNode(b, node.child, x22, y22, z3);
8137
+ }
8138
+ function sdSphere(b, x2, y2, z2, r) {
8139
+ return b.sub(length3(b, x2, y2, z2), b.constant(r));
8140
+ }
8141
+ function sdBox(b, x2, y2, z2, hx, hy, hz) {
8142
+ const dx = b.sub(b.abs(x2), b.constant(hx));
8143
+ const dy = b.sub(b.abs(y2), b.constant(hy));
8144
+ const dz = b.sub(b.abs(z2), b.constant(hz));
8145
+ const outside = length3(b, b.max(dx, b.constant(0)), b.max(dy, b.constant(0)), b.max(dz, b.constant(0)));
8146
+ const inside = b.min(b.max(b.max(dx, dy), dz), b.constant(0));
8147
+ return b.add(outside, inside);
8148
+ }
8149
+ function sdCylinder(b, x2, y2, z2, h, r) {
8150
+ const dx = b.sub(length2(b, x2, y2), b.constant(r));
8151
+ const dz = b.sub(b.abs(z2), b.constant(h * 0.5));
8152
+ 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)));
8153
+ }
8154
+ function sdTorus(b, x2, y2, z2, majorRadius, minorRadius) {
8155
+ return b.sub(length2(b, b.sub(length2(b, x2, y2), b.constant(majorRadius)), z2), b.constant(minorRadius));
8156
+ }
8157
+ function sdCapsule(b, x2, y2, z2, h, r) {
8158
+ const cz = clampSlot(b, z2, -h * 0.5, h * 0.5);
8159
+ return b.sub(length3(b, x2, y2, b.sub(z2, cz)), b.constant(r));
8160
+ }
8161
+ function sdCone(b, x2, y2, z2, h, r) {
8162
+ const q = length2(b, x2, y2);
8163
+ const cLen = sqrt(h * h + r * r);
8164
+ const side = b.add(b.mul(b.constant(h / cLen), q), b.mul(b.constant(-r / cLen), b.sub(z2, b.constant(h))));
8165
+ return b.max(b.max(side, b.neg(z2)), b.sub(z2, b.constant(h)));
8166
+ }
8167
+ function sdPolylineSweep(b, node, x2, y2, z2) {
8168
+ let d2 = sdTaperedSegment(b, x2, y2, z2, node.points[0], node.points[1], node.radii[0], node.radii[1]);
8169
+ for (let i = 1; i < node.points.length - 1; i++) {
8170
+ const segment = sdTaperedSegment(b, x2, y2, z2, node.points[i], node.points[i + 1], node.radii[i], node.radii[i + 1]);
8171
+ d2 = smin(b, d2, segment, node.blend);
8172
+ }
8173
+ return d2;
7402
8174
  }
7403
- function analyzeSdfTree(tree) {
7404
- const analysis = {
7405
- riskFlags: /* @__PURE__ */ new Set(),
7406
- minTpmsCellSize: Infinity,
7407
- minMetricTpmsThickness: Infinity,
7408
- minRepeatSpacing: Infinity,
7409
- minWallThickness: Infinity,
7410
- hasInfiniteRepeat: false,
7411
- hasLegacyTpmsThreshold: false
7412
- };
7413
- visitSdfNode(tree, analysis);
7414
- return analysis;
8175
+ function sdTaperedSegment(b, x2, y2, z2, a2, end, ra, rb) {
8176
+ const vx = end[0] - a2[0];
8177
+ const vy = end[1] - a2[1];
8178
+ const vz = end[2] - a2[2];
8179
+ const len2 = vx * vx + vy * vy + vz * vz;
8180
+ 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));
8181
+ const h = clampSlot(
8182
+ b,
8183
+ b.div(
8184
+ b.add(
8185
+ 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))),
8186
+ b.mul(b.sub(z2, b.constant(a2[2])), b.constant(vz))
8187
+ ),
8188
+ b.constant(len2)
8189
+ ),
8190
+ 0,
8191
+ 1
8192
+ );
8193
+ const sx = b.sub(x2, b.add(b.constant(a2[0]), b.mul(b.constant(vx), h)));
8194
+ const sy = b.sub(y2, b.add(b.constant(a2[1]), b.mul(b.constant(vy), h)));
8195
+ const sz = b.sub(z2, b.add(b.constant(a2[2]), b.mul(b.constant(vz), h)));
8196
+ const radius = b.add(b.constant(ra), b.mul(b.constant(rb - ra), h));
8197
+ return b.sub(length3(b, sx, sy, sz), radius);
7415
8198
  }
7416
- function visitSdfNode(node, analysis) {
8199
+ function repeatCoord(b, v, spacing, count) {
8200
+ if (spacing <= 0) return v;
8201
+ if (count > 0) {
8202
+ const center = (count - 1) * 0.5;
8203
+ const index2 = clampSlot(b, b.round(b.add(b.div(v, b.constant(spacing)), b.constant(center))), 0, count - 1);
8204
+ return b.sub(v, b.mul(b.sub(index2, b.constant(center)), b.constant(spacing)));
8205
+ }
8206
+ return b.sub(v, b.mul(b.constant(spacing), b.round(b.div(v, b.constant(spacing)))));
8207
+ }
8208
+ function getUnsupportedSdfProgramReason(node) {
7417
8209
  switch (node.kind) {
8210
+ case "sdf:displace":
8211
+ return "displace uses a dynamic JavaScript function body";
8212
+ case "sdf:surfaceDisplace":
8213
+ return "surfaceDisplace uses dynamic UV/pattern evaluation";
8214
+ case "sdf:spatialBlend":
8215
+ return "spatialBlend uses a dynamic JavaScript blend function";
8216
+ case "sdf:noise":
8217
+ return "noise depends on table-based simplex evaluation";
8218
+ case "sdf:voronoi":
8219
+ return "voronoi depends on table-based Worley evaluation";
8220
+ case "sdf:custom":
8221
+ return "custom uses a dynamic JavaScript function body";
8222
+ case "sdf:polylineSweep":
8223
+ if (node.points.length < 2) return "polylineSweep needs at least two points";
8224
+ if (node.points.length !== node.radii.length) return "polylineSweep point/radius counts differ";
8225
+ return void 0;
7418
8226
  case "sdf:union":
7419
8227
  case "sdf:difference":
7420
8228
  case "sdf:intersection":
7421
8229
  case "sdf:smoothUnion":
7422
8230
  case "sdf:smoothDifference":
7423
8231
  case "sdf:smoothIntersection":
7424
- for (const child of node.children) visitSdfNode(child, analysis);
7425
- break;
8232
+ for (const child of node.children) {
8233
+ const reason = getUnsupportedSdfProgramReason(child);
8234
+ if (reason) return reason;
8235
+ }
8236
+ return void 0;
7426
8237
  case "sdf:morph":
7427
- case "sdf:spatialBlend":
7428
- visitSdfNode(node.a, analysis);
7429
- visitSdfNode(node.b, analysis);
7430
- break;
8238
+ return getUnsupportedSdfProgramReason(node.a) ?? getUnsupportedSdfProgramReason(node.b);
7431
8239
  case "sdf:translate":
7432
8240
  case "sdf:rotate":
7433
8241
  case "sdf:scale":
7434
8242
  case "sdf:twist":
7435
8243
  case "sdf:bend":
7436
- case "sdf:onion":
7437
- visitSdfNode(node.child, analysis);
7438
- break;
7439
8244
  case "sdf:repeat":
7440
- analysis.riskFlags.add("repeat");
7441
- for (let i = 0; i < 3; i++) {
7442
- const spacing = node.spacing[i];
7443
- if (spacing > 0) {
7444
- analysis.minRepeatSpacing = Math.min(analysis.minRepeatSpacing, spacing);
7445
- if (node.count[i] <= 0) analysis.hasInfiniteRepeat = true;
7446
- }
7447
- }
7448
- visitSdfNode(node.child, analysis);
7449
- break;
7450
8245
  case "sdf:shell":
7451
- analysis.minWallThickness = Math.min(analysis.minWallThickness, node.thickness);
7452
- visitSdfNode(node.child, analysis);
7453
- break;
7454
- case "sdf:displace":
7455
- case "sdf:surfaceDisplace":
7456
- analysis.riskFlags.add("displacement");
7457
- visitSdfNode(node.child, analysis);
7458
- break;
7459
- case "sdf:gyroid":
7460
- case "sdf:schwarzP":
7461
- case "sdf:diamond":
7462
- case "sdf:lidinoid":
7463
- analysis.riskFlags.add("tpms");
7464
- analysis.minTpmsCellSize = Math.min(analysis.minTpmsCellSize, node.cellSize);
7465
- if (node.thicknessMode === "metric-approx") {
7466
- analysis.minMetricTpmsThickness = Math.min(analysis.minMetricTpmsThickness, node.thickness);
7467
- analysis.minWallThickness = Math.min(analysis.minWallThickness, node.thickness);
7468
- } else {
7469
- analysis.hasLegacyTpmsThreshold = true;
7470
- }
7471
- break;
7472
- case "sdf:noise":
7473
- analysis.riskFlags.add("noise");
7474
- break;
7475
- case "sdf:voronoi":
7476
- analysis.riskFlags.add("voronoi");
7477
- analysis.minWallThickness = Math.min(analysis.minWallThickness, node.wallThickness);
7478
- if (node.surfaceChild) visitSdfNode(node.surfaceChild, analysis);
7479
- break;
7480
- case "sdf:custom":
7481
- analysis.riskFlags.add("custom");
7482
- break;
8246
+ case "sdf:onion":
8247
+ return getUnsupportedSdfProgramReason(node.child);
8248
+ default:
8249
+ return void 0;
7483
8250
  }
7484
8251
  }
7485
- function positiveOrDefault(value, fallback) {
7486
- if (value === void 0) return fallback;
7487
- return requirePositiveFinite$2(value, "SDF meshing option");
7488
- }
7489
- function requirePositiveFinite$2(value, name) {
7490
- if (!Number.isFinite(value) || value <= 0) {
7491
- throw new Error(`${name} must be a positive finite number.`);
8252
+ function compileSdfProgram(node) {
8253
+ const unsupportedReason = getUnsupportedSdfProgramReason(node);
8254
+ if (unsupportedReason) {
8255
+ throw new UnsupportedSdfProgramNodeError(`SdfProgram does not support this tree yet: ${unsupportedReason}.`);
7492
8256
  }
7493
- return value;
7494
- }
7495
- function cloneBounds$2(bounds) {
7496
- return { min: [...bounds.min], max: [...bounds.max] };
7497
- }
7498
- function suggestEdgeLengthForSampleBudget(bounds, maxGridPoints) {
7499
- const dx = bounds.max[0] - bounds.min[0];
7500
- const dy = bounds.max[1] - bounds.min[1];
7501
- const dz = bounds.max[2] - bounds.min[2];
7502
- const volume = Math.max(dx * dy * dz, 1);
7503
- return Math.cbrt(volume / Math.max(maxGridPoints, 8));
8257
+ const builder = new SdfProgramBuilder();
8258
+ return builder.finalize(emitSdfProgramNode(builder, node, builder.x, builder.y, builder.z));
7504
8259
  }
7505
- function formatBounds(bounds) {
7506
- return `[${bounds.min.map(formatNumber).join(",")}]-[${bounds.max.map(formatNumber).join(",")}]`;
8260
+ function compileSdfProgram3(node) {
8261
+ return compileSdfProgramEvaluator3(compileSdfProgram(node));
7507
8262
  }
7508
- function formatMm(value) {
7509
- return `${formatNumber(value)}mm`;
7510
- }
7511
- function formatNumber(value) {
7512
- return Number.isInteger(value) ? String(value) : value.toFixed(3).replace(/0+$/, "").replace(/\.$/, "");
7513
- }
7514
- function formatCount(value) {
7515
- return Math.round(value).toLocaleString("en-US");
7516
- }
7517
- function formatBytes(bytes) {
7518
- if (bytes < 1024 * 1024) return `${Math.ceil(bytes / 1024)} KB`;
7519
- return `${Math.ceil(bytes / (1024 * 1024))} MB`;
8263
+ function compileSdfMaterializationEvaluator3(node) {
8264
+ const unsupportedReason = getUnsupportedSdfProgramReason(node);
8265
+ if (unsupportedReason) {
8266
+ return {
8267
+ fn: compileSdfNode3(node),
8268
+ engine: "closure",
8269
+ unsupportedReason
8270
+ };
8271
+ }
8272
+ return {
8273
+ fn: compileSdfProgram3(node),
8274
+ engine: "program"
8275
+ };
7520
8276
  }
7521
8277
  function midpoint$3(a2, b) {
7522
8278
  return [(a2[0] + b[0]) / 2, (a2[1] + b[1]) / 2, (a2[2] + b[2]) / 2];
@@ -7982,7 +8738,7 @@ function buildCircleExtrusionTopology(circ, height, center = false) {
7982
8738
  );
7983
8739
  return { faces, edges };
7984
8740
  }
7985
- function requireFinite$8(v, label) {
8741
+ function requireFinite$9(v, label) {
7986
8742
  if (!Number.isFinite(v)) throw new Error(`nurbsSurface: ${label} must be finite, got ${v}`);
7987
8743
  }
7988
8744
  function normalizeSurfaceTessellation(tessellation) {
@@ -7992,11 +8748,11 @@ function normalizeSurfaceTessellation(tessellation) {
7992
8748
  throw new Error(`nurbsSurface: tessellation.mode must be "uniform" or "adaptive", got ${mode}`);
7993
8749
  }
7994
8750
  if (tessellation.tolerance !== void 0) {
7995
- requireFinite$8(tessellation.tolerance, "tessellation.tolerance");
8751
+ requireFinite$9(tessellation.tolerance, "tessellation.tolerance");
7996
8752
  if (tessellation.tolerance <= 0) throw new Error("nurbsSurface: tessellation.tolerance must be > 0");
7997
8753
  }
7998
- if (tessellation.minResolution !== void 0) requireFinite$8(tessellation.minResolution, "tessellation.minResolution");
7999
- if (tessellation.maxResolution !== void 0) requireFinite$8(tessellation.maxResolution, "tessellation.maxResolution");
8754
+ if (tessellation.minResolution !== void 0) requireFinite$9(tessellation.minResolution, "tessellation.minResolution");
8755
+ if (tessellation.maxResolution !== void 0) requireFinite$9(tessellation.maxResolution, "tessellation.maxResolution");
8000
8756
  const minResolution = tessellation.minResolution === void 0 ? void 0 : Math.max(2, Math.round(tessellation.minResolution));
8001
8757
  const maxResolution = tessellation.maxResolution === void 0 ? void 0 : Math.max(2, Math.round(tessellation.maxResolution));
8002
8758
  if (minResolution !== void 0 && maxResolution !== void 0 && minResolution > maxResolution) {
@@ -8015,10 +8771,10 @@ function normalizeSurfaceDomain(domain) {
8015
8771
  const uMax = domain.uMax ?? 1;
8016
8772
  const vMin = domain.vMin ?? 0;
8017
8773
  const vMax = domain.vMax ?? 1;
8018
- requireFinite$8(uMin, "domain.uMin");
8019
- requireFinite$8(uMax, "domain.uMax");
8020
- requireFinite$8(vMin, "domain.vMin");
8021
- requireFinite$8(vMax, "domain.vMax");
8774
+ requireFinite$9(uMin, "domain.uMin");
8775
+ requireFinite$9(uMax, "domain.uMax");
8776
+ requireFinite$9(vMin, "domain.vMin");
8777
+ requireFinite$9(vMax, "domain.vMax");
8022
8778
  if (uMin < 0 || uMax > 1 || vMin < 0 || vMax > 1) {
8023
8779
  throw new Error("nurbsSurface: domain bounds must stay within [0, 1]");
8024
8780
  }
@@ -8030,8 +8786,8 @@ function normalizeSurfaceDomain(domain) {
8030
8786
  function normalizeTrimLoop(loop, label) {
8031
8787
  if (loop.length < 3) throw new Error(`nurbsSurface: ${label} requires at least 3 points`);
8032
8788
  const normalized = loop.map(([u2, v], idx) => {
8033
- requireFinite$8(u2, `${label}[${idx}][0]`);
8034
- requireFinite$8(v, `${label}[${idx}][1]`);
8789
+ requireFinite$9(u2, `${label}[${idx}][0]`);
8790
+ requireFinite$9(v, `${label}[${idx}][1]`);
8035
8791
  if (u2 < 0 || u2 > 1 || v < 0 || v > 1) throw new Error(`nurbsSurface: ${label}[${idx}] must stay within [0, 1]`);
8036
8792
  return [u2, v];
8037
8793
  });
@@ -8054,8 +8810,8 @@ function normalizeTrimCurve(curve, label) {
8054
8810
  throw new Error(`nurbsSurface: ${label} needs at least ${degree + 1} control points for degree=${degree}`);
8055
8811
  }
8056
8812
  const normalizedControlPoints = controlPoints.map(([u2, v], idx) => {
8057
- requireFinite$8(u2, `${label}.controlPoints[${idx}][0]`);
8058
- requireFinite$8(v, `${label}.controlPoints[${idx}][1]`);
8813
+ requireFinite$9(u2, `${label}.controlPoints[${idx}][0]`);
8814
+ requireFinite$9(v, `${label}.controlPoints[${idx}][1]`);
8059
8815
  if (u2 < 0 || u2 > 1 || v < 0 || v > 1) {
8060
8816
  throw new Error(`nurbsSurface: ${label}.controlPoints[${idx}] must stay within [0, 1]`);
8061
8817
  }
@@ -8066,7 +8822,7 @@ function normalizeTrimCurve(curve, label) {
8066
8822
  throw new Error(`nurbsSurface: ${label}.weights length must match controlPoints length`);
8067
8823
  }
8068
8824
  for (let idx = 0; idx < weights.length; idx += 1) {
8069
- requireFinite$8(weights[idx], `${label}.weights[${idx}]`);
8825
+ requireFinite$9(weights[idx], `${label}.weights[${idx}]`);
8070
8826
  if (weights[idx] <= 0) throw new Error(`nurbsSurface: ${label}.weights[${idx}] must be > 0`);
8071
8827
  }
8072
8828
  const knots = curve.knots ?? generateClampedKnots(controlPoints.length, degree);
@@ -8074,7 +8830,7 @@ function normalizeTrimCurve(curve, label) {
8074
8830
  throw new Error(`nurbsSurface: ${label}.knots.length should be ${controlPoints.length + degree + 1}, got ${knots.length}`);
8075
8831
  }
8076
8832
  for (let idx = 0; idx < knots.length; idx += 1) {
8077
- requireFinite$8(knots[idx], `${label}.knots[${idx}]`);
8833
+ requireFinite$9(knots[idx], `${label}.knots[${idx}]`);
8078
8834
  if (idx > 0 && knots[idx] < knots[idx - 1]) throw new Error(`nurbsSurface: ${label}.knots must be non-decreasing`);
8079
8835
  }
8080
8836
  if (knots[degree] >= knots[controlPoints.length]) {
@@ -8254,16 +9010,16 @@ class NurbsSurface {
8254
9010
  for (let i = 0; i < nU; i++) {
8255
9011
  if (controlGrid[i].length !== nV) throw new Error(`nurbsSurface: row ${i} has ${controlGrid[i].length} points, expected ${nV}`);
8256
9012
  for (let j = 0; j < nV; j++) {
8257
- requireFinite$8(controlGrid[i][j][0], `controlGrid[${i}][${j}][0]`);
8258
- requireFinite$8(controlGrid[i][j][1], `controlGrid[${i}][${j}][1]`);
8259
- requireFinite$8(controlGrid[i][j][2], `controlGrid[${i}][${j}][2]`);
9013
+ requireFinite$9(controlGrid[i][j][0], `controlGrid[${i}][${j}][0]`);
9014
+ requireFinite$9(controlGrid[i][j][1], `controlGrid[${i}][${j}][1]`);
9015
+ requireFinite$9(controlGrid[i][j][2], `controlGrid[${i}][${j}][2]`);
8260
9016
  }
8261
9017
  }
8262
9018
  const weightsGrid = options.weights ?? controlGrid.map((row) => row.map(() => 1));
8263
9019
  for (let i = 0; i < nU; i++) {
8264
9020
  if (weightsGrid[i].length !== nV) throw new Error(`nurbsSurface: weights row ${i} length mismatch`);
8265
9021
  for (let j = 0; j < nV; j++) {
8266
- requireFinite$8(weightsGrid[i][j], `weights[${i}][${j}]`);
9022
+ requireFinite$9(weightsGrid[i][j], `weights[${i}][${j}]`);
8267
9023
  if (weightsGrid[i][j] <= 0) throw new Error(`nurbsSurface: weights[${i}][${j}] must be > 0`);
8268
9024
  }
8269
9025
  }
@@ -9653,8 +10409,8 @@ function stitchSingleLoopLoft(loops, heights, wasm) {
9653
10409
  const v0 = baseIdx + j;
9654
10410
  const v1 = nextIdx + j;
9655
10411
  const v2 = nextIdx + j1;
9656
- const v3 = baseIdx + j1;
9657
- triangles.push(v0, v3, v2);
10412
+ const v32 = baseIdx + j1;
10413
+ triangles.push(v0, v32, v2);
9658
10414
  triangles.push(v0, v2, v1);
9659
10415
  }
9660
10416
  }
@@ -9695,7 +10451,7 @@ let _wasm$1 = null;
9695
10451
  async function initManifoldWasm() {
9696
10452
  if (_wasm$1) return _wasm$1;
9697
10453
  performance.mark("manifold:start");
9698
- const Module = (await import("./manifold-CwDdMKyc.js")).default;
10454
+ const Module = (await import("./manifold-Cjk7WhRs.js")).default;
9699
10455
  performance.mark("manifold:imported");
9700
10456
  const wasm = await Module();
9701
10457
  wasm.setup();
@@ -9965,8 +10721,8 @@ function stitchLoopAlongPath(loop, _path, frames, wasm) {
9965
10721
  const v0 = baseIdx + j;
9966
10722
  const v1 = nextIdx + j;
9967
10723
  const v2 = nextIdx + j1;
9968
- const v3 = baseIdx + j1;
9969
- triangles.push(v0, v3, v2);
10724
+ const v32 = baseIdx + j1;
10725
+ triangles.push(v0, v32, v2);
9970
10726
  triangles.push(v0, v2, v1);
9971
10727
  }
9972
10728
  }
@@ -11312,8 +12068,16 @@ function lowerShapeCompilePlanToManifold(plan, wasm) {
11312
12068
  case "importedMesh":
11313
12069
  return lowerImportedMeshToManifold(plan.fileData, plan.format, plan.filePath, wasm);
11314
12070
  case "sdf": {
11315
- const evalFn = compileSdfNode3(plan.tree);
11316
- return lowerSdfToManifold(evalFn, plan.bounds, plan.edgeLength, wasm, plan.meshing);
12071
+ const evaluator = compileSdfMaterializationEvaluator3(plan.tree);
12072
+ return lowerSdfToManifold(
12073
+ evaluator.fn,
12074
+ plan.bounds,
12075
+ plan.edgeLength,
12076
+ wasm,
12077
+ plan.meshing,
12078
+ evaluator.engine,
12079
+ evaluator.unsupportedReason
12080
+ );
11317
12081
  }
11318
12082
  case "fromSlices":
11319
12083
  return lowerFromSlicesToManifold(plan, wasm);
@@ -11331,8 +12095,12 @@ function lowerShapeCompilePlanToManifold(plan, wasm) {
11331
12095
  assertExhaustive(plan);
11332
12096
  }
11333
12097
  }
11334
- function lowerSdfToManifold(evalFn, bounds, edgeLength2, wasm, meshing) {
12098
+ function lowerSdfToManifold(evalFn, bounds, edgeLength2, wasm, meshing, evaluatorEngine, evaluatorUnsupportedReason) {
11335
12099
  const diagnostics = (meshing == null ? void 0 : meshing.diagnostics) ? { ...meshing.diagnostics } : void 0;
12100
+ if (diagnostics && evaluatorEngine) {
12101
+ diagnostics.evaluator = evaluatorEngine;
12102
+ if (evaluatorUnsupportedReason) diagnostics.evaluatorUnsupportedReason = evaluatorUnsupportedReason;
12103
+ }
11336
12104
  const inset = edgeLength2;
11337
12105
  const cappedEvalFn = (x2, y2, z2) => {
11338
12106
  const bx = Math.max(bounds.min[0] + inset - x2, x2 - bounds.max[0] + inset);
@@ -16783,9 +17551,9 @@ function requireClipper() {
16783
17551
  if (ClipperLib2.use_xyz) j.OffPt.Z = OffPt.Z;
16784
17552
  this.m_Joins.push(j);
16785
17553
  };
16786
- ClipperLib2.Clipper.prototype.AddGhostJoin = function(Op, OffPt) {
17554
+ ClipperLib2.Clipper.prototype.AddGhostJoin = function(Op2, OffPt) {
16787
17555
  var j = new ClipperLib2.Join();
16788
- j.OutPt1 = Op;
17556
+ j.OutPt1 = Op2;
16789
17557
  j.OffPt.X = OffPt.X;
16790
17558
  j.OffPt.Y = OffPt.Y;
16791
17559
  if (ClipperLib2.use_xyz) j.OffPt.Z = OffPt.Z;
@@ -19687,7 +20455,7 @@ function requireClipper() {
19687
20455
  }
19688
20456
  var clipperExports = requireClipper();
19689
20457
  var ClipperLib = /* @__PURE__ */ getDefaultExportFromCjs(clipperExports);
19690
- let f$1 = class f {
20458
+ let f$3 = class f {
19691
20459
  constructor(t, e) {
19692
20460
  this.next = null, this.key = t, this.data = e, this.left = null, this.right = null;
19693
20461
  }
@@ -19696,7 +20464,7 @@ function d(n, t) {
19696
20464
  return n > t ? 1 : n < t ? -1 : 0;
19697
20465
  }
19698
20466
  function u$1(n, t, e) {
19699
- const r = new f$1(null, null);
20467
+ const r = new f$3(null, null);
19700
20468
  let l = r, i = r;
19701
20469
  for (; ; ) {
19702
20470
  const o = e(n, t.key);
@@ -19719,7 +20487,7 @@ function u$1(n, t, e) {
19719
20487
  return l.right = t.left, i.left = t.right, t.left = r.right, t.right = r.left, t;
19720
20488
  }
19721
20489
  function c(n, t, e, r) {
19722
- const l = new f$1(n, t);
20490
+ const l = new f$3(n, t);
19723
20491
  if (e === null)
19724
20492
  return l.left = l.right = null, l;
19725
20493
  e = u$1(n, e, r);
@@ -19760,7 +20528,7 @@ class z {
19760
20528
  * Adds a key, if it is not present in the tree
19761
20529
  */
19762
20530
  add(t, e) {
19763
- const r = new f$1(t, e);
20531
+ const r = new f$3(t, e);
19764
20532
  this._root === null && (r.left = r.right = null, this._size++, this._root = r);
19765
20533
  const l = this._comparator, i = u$1(t, this._root, l), o = l(t, i.key);
19766
20534
  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;
@@ -19973,23 +20741,23 @@ class z {
19973
20741
  function a(n, t, e, r) {
19974
20742
  const l = r - e;
19975
20743
  if (l > 0) {
19976
- const i = e + Math.floor(l / 2), o = n[i], s = t[i], h = new f$1(o, s);
20744
+ const i = e + Math.floor(l / 2), o = n[i], s = t[i], h = new f$3(o, s);
19977
20745
  return h.left = a(n, t, e, i), h.right = a(n, t, i + 1, r), h;
19978
20746
  }
19979
20747
  return null;
19980
20748
  }
19981
20749
  function x(n, t) {
19982
- const e = new f$1(null, null);
20750
+ const e = new f$3(null, null);
19983
20751
  let r = e;
19984
20752
  for (let l = 0; l < n.length; l++)
19985
- r = r.next = new f$1(n[l], t[l]);
20753
+ r = r.next = new f$3(n[l], t[l]);
19986
20754
  return r.next = null, e.next;
19987
20755
  }
19988
20756
  function k(n) {
19989
20757
  let t = n;
19990
20758
  const e = [];
19991
20759
  let r = false;
19992
- const l = new f$1(null, null);
20760
+ const l = new f$3(null, null);
19993
20761
  let i = l;
19994
20762
  for (; !r; )
19995
20763
  t ? (e.push(t), t = t.left) : e.length > 0 ? (t = i = i.next = e.pop(), t = t.right) : r = true;
@@ -20004,7 +20772,7 @@ function p(n, t, e) {
20004
20772
  return null;
20005
20773
  }
20006
20774
  function y(n, t, e) {
20007
- const r = new f$1(null, null);
20775
+ const r = new f$3(null, null);
20008
20776
  let l = r, i = n, o = t;
20009
20777
  for (; i !== null && o !== null; )
20010
20778
  e(i.key, o.key) < 0 ? (l.next = i, i = i.next) : (l.next = o, o = o.next), l = l.next;
@@ -24992,7 +25760,13 @@ function normalizeTruckShapeForBooleanInput(shape) {
24992
25760
  return normalized;
24993
25761
  }
24994
25762
  function lowerSdfPlan(plan) {
24995
- const evalFn = compileSdfNode3(plan.tree);
25763
+ var _a3, _b3, _c2;
25764
+ const evaluator = compileSdfMaterializationEvaluator3(plan.tree);
25765
+ if ((_a3 = plan.meshing) == null ? void 0 : _a3.diagnostics) {
25766
+ plan.meshing.diagnostics.evaluator = evaluator.engine;
25767
+ if (evaluator.unsupportedReason) plan.meshing.diagnostics.evaluatorUnsupportedReason = evaluator.unsupportedReason;
25768
+ }
25769
+ const evalFn = evaluator.fn;
24996
25770
  const inset = plan.edgeLength;
24997
25771
  const cappedEvalFn = (x2, y2, z2) => {
24998
25772
  const bx = Math.max(plan.bounds.min[0] + inset - x2, x2 - plan.bounds.max[0] + inset);
@@ -25004,14 +25778,18 @@ function lowerSdfPlan(plan) {
25004
25778
  assertSdfMeshBudget(mesh, plan);
25005
25779
  let surfaceNetsError;
25006
25780
  try {
25007
- return lowerExtractedSdfMesh(mesh, cappedEvalFn, true);
25781
+ const shape = lowerExtractedSdfMesh(mesh, cappedEvalFn, true);
25782
+ if ((_b3 = plan.meshing) == null ? void 0 : _b3.diagnostics) logSdfMeshingDiagnostics("SDF meshing result", plan.meshing.diagnostics);
25783
+ return shape;
25008
25784
  } catch (error) {
25009
25785
  surfaceNetsError = error;
25010
25786
  }
25011
25787
  const tetraMesh = marchingTetrahedra(cappedEvalFn, plan.bounds, plan.edgeLength);
25012
25788
  assertSdfMeshBudget(tetraMesh, plan);
25013
25789
  try {
25014
- return lowerExtractedSdfMesh(tetraMesh, cappedEvalFn, false);
25790
+ const shape = lowerExtractedSdfMesh(tetraMesh, cappedEvalFn, false);
25791
+ if ((_c2 = plan.meshing) == null ? void 0 : _c2.diagnostics) logSdfMeshingDiagnostics("SDF meshing result", plan.meshing.diagnostics);
25792
+ return shape;
25015
25793
  } catch (error) {
25016
25794
  throw new Error(
25017
25795
  `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)}`
@@ -25375,7 +26153,9 @@ function lowerOffsetSolidPlan(plan) {
25375
26153
  if (base.kind === "transform") {
25376
26154
  return lowerTransformedOffsetSolidPlan(base, plan.thickness);
25377
26155
  }
25378
- return truckUnsupported(`compile plan "${plan.kind}" for non-vertical-prism/non-revolved/non-loft/non-straight-sweep/non-straight-variable-sweep solids`);
26156
+ return truckUnsupported(
26157
+ `compile plan "${plan.kind}" for non-vertical-prism/non-revolved/non-loft/non-straight-sweep/non-straight-variable-sweep solids`
26158
+ );
25379
26159
  }
25380
26160
  function lowerLoftPlan(plan) {
25381
26161
  return wrapTruckShapeBackend(
@@ -32677,6 +33457,37 @@ function mergeSketchPlacementModel(sketches) {
32677
33457
  }
32678
33458
  return first;
32679
33459
  }
33460
+ function normalizeSceneTags(value, label = "tags") {
33461
+ if (value == null) return [];
33462
+ const rawTags = typeof value === "string" ? [value] : value;
33463
+ if (!Array.isArray(rawTags)) {
33464
+ throw new Error(`${label} must be a string or array of strings`);
33465
+ }
33466
+ const out = [];
33467
+ const seen = /* @__PURE__ */ new Set();
33468
+ rawTags.forEach((tag, index2) => {
33469
+ if (typeof tag !== "string") {
33470
+ throw new Error(`${label}[${index2}] must be a string`);
33471
+ }
33472
+ const trimmed = tag.trim();
33473
+ if (!trimmed || seen.has(trimmed)) return;
33474
+ seen.add(trimmed);
33475
+ out.push(trimmed);
33476
+ });
33477
+ return out;
33478
+ }
33479
+ function mergeSceneTags(...values) {
33480
+ const out = [];
33481
+ const seen = /* @__PURE__ */ new Set();
33482
+ values.forEach((value) => {
33483
+ normalizeSceneTags(value).forEach((tag) => {
33484
+ if (seen.has(tag)) return;
33485
+ seen.add(tag);
33486
+ out.push(tag);
33487
+ });
33488
+ });
33489
+ return out;
33490
+ }
32680
33491
  const _groupPlacementRefs = /* @__PURE__ */ new WeakMap();
32681
33492
  const _groupExplodeHint = /* @__PURE__ */ new WeakMap();
32682
33493
  function getGroupRefs(g2) {
@@ -32700,7 +33511,7 @@ function transformGroupRefs(source, dest, matrix) {
32700
33511
  }
32701
33512
  return dest;
32702
33513
  }
32703
- function requireFiniteAngle(v, method) {
33514
+ function requireFiniteAngle$1(v, method) {
32704
33515
  if (typeof v !== "number" || !Number.isFinite(v))
32705
33516
  throw new Error(`${method} angleDeg must be a finite number, got ${typeof v === "number" ? v : typeof v}`);
32706
33517
  }
@@ -32761,31 +33572,46 @@ function resolveNamedGroupChild(item) {
32761
33572
  function normalizeGroupInputs(items) {
32762
33573
  const children = [];
32763
33574
  const childNames = [];
33575
+ const childTags = [];
32764
33576
  items.forEach((item) => {
32765
33577
  if (isNamedGroupChild(item)) {
32766
33578
  children.push(resolveNamedGroupChild(item));
32767
33579
  childNames.push(normalizeChildName(item.name));
33580
+ childTags.push(normalizeSceneTags(item.tags, `group(...) named item "${item.name}" tags`));
32768
33581
  return;
32769
33582
  }
32770
33583
  children.push(item);
32771
33584
  childNames.push(void 0);
33585
+ childTags.push([]);
32772
33586
  });
32773
- return { children, childNames };
33587
+ return { children, childNames, childTags };
32774
33588
  }
32775
33589
  class ShapeGroup {
32776
- constructor(children, childNames) {
33590
+ constructor(children, childNames, childTags) {
32777
33591
  __publicField(this, "children");
32778
33592
  __publicField(this, "childNames");
33593
+ __publicField(this, "childTags");
32779
33594
  if (childNames && childNames.length !== children.length) {
32780
33595
  throw new Error("ShapeGroup childNames must match children length");
32781
33596
  }
33597
+ if (childTags && childTags.length !== children.length) {
33598
+ throw new Error("ShapeGroup childTags must match children length");
33599
+ }
32782
33600
  this.children = [...children];
32783
33601
  this.childNames = this.children.map((_2, index2) => normalizeChildName(childNames == null ? void 0 : childNames[index2]));
33602
+ this.childTags = this.children.map((_2, index2) => normalizeSceneTags(childTags == null ? void 0 : childTags[index2], "ShapeGroup childTags"));
32784
33603
  }
32785
33604
  /** Return the optional name of the child at `index`. */
32786
33605
  childName(index2) {
32787
33606
  return this.childNames[index2];
32788
33607
  }
33608
+ /**
33609
+ * Return tags attached to the child at `index`.
33610
+ * @internal
33611
+ */
33612
+ tagsForChild(index2) {
33613
+ return [...this.childTags[index2] ?? []];
33614
+ }
32789
33615
  /**
32790
33616
  * Return the named child by name. Throws if not found.
32791
33617
  * Useful when importing a multipart group and working on components individually.
@@ -32800,13 +33626,13 @@ class ShapeGroup {
32800
33626
  }
32801
33627
  /** Apply fn to all children, producing a new ShapeGroup that also copies placement refs. */
32802
33628
  mapChildren(fn) {
32803
- const next = new ShapeGroup(this.children.map(fn), this.childNames);
33629
+ const next = new ShapeGroup(this.children.map(fn), this.childNames, this.childTags);
32804
33630
  copyGroupPorts(this, next);
32805
33631
  return copyGroupRefs(this, next);
32806
33632
  }
32807
33633
  /** Apply fn to all children and also transform placement refs by the given matrix. */
32808
33634
  mapChildrenTransform(fn, matrix) {
32809
- const next = new ShapeGroup(this.children.map(fn), this.childNames);
33635
+ const next = new ShapeGroup(this.children.map(fn), this.childNames, this.childTags);
32810
33636
  transformGroupPortsHelper(this, next, matrix);
32811
33637
  return transformGroupRefs(this, next, matrix);
32812
33638
  }
@@ -32933,25 +33759,25 @@ class ShapeGroup {
32933
33759
  /** Rotate the group around an arbitrary axis through the origin. */
32934
33760
  rotate(axis, angleDeg, options) {
32935
33761
  requireRotateAxis(axis, "ShapeGroup.rotate()");
32936
- requireFiniteAngle(angleDeg, "ShapeGroup.rotate()");
33762
+ requireFiniteAngle$1(angleDeg, "ShapeGroup.rotate()");
32937
33763
  if (options == null ? void 0 : options.pivot) requireVec3Pivot(options.pivot, "ShapeGroup.rotate()");
32938
33764
  return this.rotateAroundAxis(axis, angleDeg, options == null ? void 0 : options.pivot);
32939
33765
  }
32940
33766
  /** Rotate the group around the X axis. */
32941
33767
  rotateX(angleDeg, options) {
32942
- requireFiniteAngle(angleDeg, "ShapeGroup.rotateX()");
33768
+ requireFiniteAngle$1(angleDeg, "ShapeGroup.rotateX()");
32943
33769
  if (options == null ? void 0 : options.pivot) requireVec3Pivot(options.pivot, "ShapeGroup.rotateX()");
32944
33770
  return this.rotateAroundAxis([1, 0, 0], angleDeg, options == null ? void 0 : options.pivot);
32945
33771
  }
32946
33772
  /** Rotate the group around the Y axis. */
32947
33773
  rotateY(angleDeg, options) {
32948
- requireFiniteAngle(angleDeg, "ShapeGroup.rotateY()");
33774
+ requireFiniteAngle$1(angleDeg, "ShapeGroup.rotateY()");
32949
33775
  if (options == null ? void 0 : options.pivot) requireVec3Pivot(options.pivot, "ShapeGroup.rotateY()");
32950
33776
  return this.rotateAroundAxis([0, 1, 0], angleDeg, options == null ? void 0 : options.pivot);
32951
33777
  }
32952
33778
  /** Rotate the group around the Z axis. */
32953
33779
  rotateZ(angleDeg, options) {
32954
- requireFiniteAngle(angleDeg, "ShapeGroup.rotateZ()");
33780
+ requireFiniteAngle$1(angleDeg, "ShapeGroup.rotateZ()");
32955
33781
  if (options == null ? void 0 : options.pivot) requireVec3Pivot(options.pivot, "ShapeGroup.rotateZ()");
32956
33782
  return this.rotateAroundAxis([0, 0, 1], angleDeg, options == null ? void 0 : options.pivot);
32957
33783
  }
@@ -32994,7 +33820,8 @@ class ShapeGroup {
32994
33820
  "ShapeGroup.transform only supports 3D children (Shape/ShapeGroup). For Sketch children, use 2D transforms (translate/rotate/scale/mirror)."
32995
33821
  );
32996
33822
  }),
32997
- this.childNames
33823
+ this.childNames,
33824
+ this.childTags
32998
33825
  );
32999
33826
  transformGroupPortsHelper(this, next, matrix);
33000
33827
  return transformGroupRefs(this, next, matrix);
@@ -33062,7 +33889,7 @@ class ShapeGroup {
33062
33889
  * ```
33063
33890
  */
33064
33891
  withReferences(refs) {
33065
- const next = new ShapeGroup(this.children, this.childNames);
33892
+ const next = new ShapeGroup(this.children, this.childNames, this.childTags);
33066
33893
  const merged = applyPlacementReferenceInput(getGroupRefs(this), refs);
33067
33894
  return setGroupRefs(next, merged);
33068
33895
  }
@@ -33130,7 +33957,7 @@ class ShapeGroup {
33130
33957
  /** Attach named connectors — attachment points that survive transforms.
33131
33958
  * Connectors can be bare (position + orientation) or typed (with connectorType/gender for compatibility matching). */
33132
33959
  withConnectors(connectors) {
33133
- const next = new ShapeGroup(this.children, this.childNames);
33960
+ const next = new ShapeGroup(this.children, this.childNames, this.childTags);
33134
33961
  copyGroupRefs(this, next);
33135
33962
  const existing = getGroupPorts(this);
33136
33963
  const incoming = normalizeConnectorMapInput(connectors);
@@ -33180,7 +34007,7 @@ class ShapeGroup {
33180
34007
  }
33181
34008
  function group(...items) {
33182
34009
  const normalized = normalizeGroupInputs(items);
33183
- return new ShapeGroup(normalized.children, normalized.childNames);
34010
+ return new ShapeGroup(normalized.children, normalized.childNames, normalized.childTags);
33184
34011
  }
33185
34012
  function getTargetPortsForGroup(target) {
33186
34013
  if (target instanceof Shape) {
@@ -35355,7 +36182,7 @@ function buildSdfFunctionDefinition(source, options) {
35355
36182
  jsExpression: expression,
35356
36183
  ...shader.ok ? { shaderExpression: shader.expression } : { shaderUnsupportedReason: shader.reason },
35357
36184
  raymarchStepLimit: resolveRaymarchStepLimit(options.bounds, options.maxStep),
35358
- ...options.lipschitz !== void 0 ? { raymarchLipschitz: requirePositiveFinite$1(options.lipschitz, "sdf.fromFunction() lipschitz") } : {}
36185
+ ...options.lipschitz !== void 0 ? { raymarchLipschitz: requirePositiveFinite$2(options.lipschitz, "sdf.fromFunction() lipschitz") } : {}
35359
36186
  };
35360
36187
  }
35361
36188
  function extractSdfExpression(source) {
@@ -35523,7 +36350,7 @@ function formatNumericLiteralsForGlsl(source) {
35523
36350
  return result;
35524
36351
  }
35525
36352
  function resolveRaymarchStepLimit(bounds, maxStep) {
35526
- if (maxStep !== void 0) return requirePositiveFinite$1(maxStep, "sdf.fromFunction() maxStep");
36353
+ if (maxStep !== void 0) return requirePositiveFinite$2(maxStep, "sdf.fromFunction() maxStep");
35527
36354
  const dx = bounds.max[0] - bounds.min[0];
35528
36355
  const dy = bounds.max[1] - bounds.min[1];
35529
36356
  const dz = bounds.max[2] - bounds.min[2];
@@ -35531,7 +36358,7 @@ function resolveRaymarchStepLimit(bounds, maxStep) {
35531
36358
  if (!Number.isFinite(diagonal) || diagonal <= 0) return 0.1;
35532
36359
  return Math.max(0.025, Math.min(0.5, diagonal / 240));
35533
36360
  }
35534
- function requirePositiveFinite$1(value, label) {
36361
+ function requirePositiveFinite$2(value, label) {
35535
36362
  if (!Number.isFinite(value) || value <= 0) throw new Error(`${label} must be a positive finite number.`);
35536
36363
  return value;
35537
36364
  }
@@ -35558,6 +36385,199 @@ class SurfacePattern {
35558
36385
  this.constants = constants;
35559
36386
  }
35560
36387
  }
36388
+ const typedSurfacePatterns = /* @__PURE__ */ new WeakMap();
36389
+ function getTypedSurfacePattern(pattern) {
36390
+ return typedSurfacePatterns.get(pattern);
36391
+ }
36392
+ class Pattern2D extends SurfacePattern {
36393
+ constructor(body) {
36394
+ super(body);
36395
+ }
36396
+ /** Add this pattern to one or more patterns or constant height offsets. */
36397
+ add(...patterns) {
36398
+ throw new Error("Pattern2D values are created by sdf.pattern2d().");
36399
+ }
36400
+ /** Subtract another pattern or constant height offset from this pattern. */
36401
+ subtract(pattern) {
36402
+ throw new Error("Pattern2D values are created by sdf.pattern2d().");
36403
+ }
36404
+ /** Multiply this pattern by one or more patterns or numeric scale factors. */
36405
+ multiply(...patterns) {
36406
+ throw new Error("Pattern2D values are created by sdf.pattern2d().");
36407
+ }
36408
+ /** Keep the lower height between this pattern and one or more other patterns. */
36409
+ min(...patterns) {
36410
+ throw new Error("Pattern2D values are created by sdf.pattern2d().");
36411
+ }
36412
+ /** Keep the higher height between this pattern and one or more other patterns. */
36413
+ max(...patterns) {
36414
+ throw new Error("Pattern2D values are created by sdf.pattern2d().");
36415
+ }
36416
+ /** Limit pattern height to the inclusive `[min, max]` range in millimeters. */
36417
+ clamp(min2, max2) {
36418
+ throw new Error("Pattern2D values are created by sdf.pattern2d().");
36419
+ }
36420
+ /** Convert negative heights to positive heights. */
36421
+ abs() {
36422
+ throw new Error("Pattern2D values are created by sdf.pattern2d().");
36423
+ }
36424
+ /** Flip the pattern height sign. */
36425
+ negate() {
36426
+ throw new Error("Pattern2D values are created by sdf.pattern2d().");
36427
+ }
36428
+ }
36429
+ class Pattern2DImpl extends Pattern2D {
36430
+ constructor(node) {
36431
+ super(emitSurfacePatternJsExpression(node));
36432
+ __publicField(this, "node");
36433
+ this.node = node;
36434
+ typedSurfacePatterns.set(this, node);
36435
+ }
36436
+ add(...patterns) {
36437
+ return new Pattern2DImpl({ kind: "surfacePattern:add", children: [this.node, ...patterns.map(patternNodeFromInput)] });
36438
+ }
36439
+ subtract(pattern) {
36440
+ return this.add(new Pattern2DImpl({ kind: "surfacePattern:negate", child: patternNodeFromInput(pattern) }));
36441
+ }
36442
+ multiply(...patterns) {
36443
+ return new Pattern2DImpl({ kind: "surfacePattern:multiply", children: [this.node, ...patterns.map(patternNodeFromInput)] });
36444
+ }
36445
+ min(...patterns) {
36446
+ return new Pattern2DImpl({ kind: "surfacePattern:min", children: [this.node, ...patterns.map(patternNodeFromInput)] });
36447
+ }
36448
+ max(...patterns) {
36449
+ return new Pattern2DImpl({ kind: "surfacePattern:max", children: [this.node, ...patterns.map(patternNodeFromInput)] });
36450
+ }
36451
+ clamp(min2, max2) {
36452
+ const lo = requireFinite$8(min2, "Pattern2D.clamp() min");
36453
+ const hi = requireFinite$8(max2, "Pattern2D.clamp() max");
36454
+ if (lo > hi) throw new Error(`Pattern2D.clamp() min must be <= max. Received: ${lo} > ${hi}`);
36455
+ return new Pattern2DImpl({ kind: "surfacePattern:clamp", child: this.node, min: lo, max: hi });
36456
+ }
36457
+ abs() {
36458
+ return new Pattern2DImpl({ kind: "surfacePattern:abs", child: this.node });
36459
+ }
36460
+ negate() {
36461
+ return new Pattern2DImpl({ kind: "surfacePattern:negate", child: this.node });
36462
+ }
36463
+ }
36464
+ class Pattern2DBuilder {
36465
+ /** Create a constant-height pattern in millimeters. */
36466
+ constant(value = 0) {
36467
+ return new Pattern2DImpl({ kind: "surfacePattern:constant", value: requireFinite$8(value, "sdf.pattern2d().constant() value") });
36468
+ }
36469
+ /** Create a sinusoidal wave pattern in UV space. */
36470
+ sineWave(options) {
36471
+ return new Pattern2DImpl({
36472
+ kind: "surfacePattern:sineWave",
36473
+ direction: normalizeDirection$1(options.direction ?? [1, 0], "sdf.pattern2d().sineWave() direction"),
36474
+ wavelength: requirePositiveFinite$1(options.wavelength, "sdf.pattern2d().sineWave() wavelength"),
36475
+ amplitude: requireFinite$8(options.amplitude ?? 1, "sdf.pattern2d().sineWave() amplitude"),
36476
+ phase: requireFinite$8(options.phase ?? 0, "sdf.pattern2d().sineWave() phase"),
36477
+ bias: requireFinite$8(options.bias ?? 0, "sdf.pattern2d().sineWave() bias")
36478
+ });
36479
+ }
36480
+ /** Create recessed stripe bands in UV space. */
36481
+ stripes(options) {
36482
+ return new Pattern2DImpl({
36483
+ kind: "surfacePattern:stripes",
36484
+ direction: normalizeDirection$1(options.direction ?? [1, 0], "sdf.pattern2d().stripes() direction"),
36485
+ spacing: requirePositiveFinite$1(options.spacing, "sdf.pattern2d().stripes() spacing"),
36486
+ width: requirePositiveFinite$1(options.width, "sdf.pattern2d().stripes() width"),
36487
+ depth: requireNonNegativeFinite$1(options.depth ?? 1, "sdf.pattern2d().stripes() depth")
36488
+ });
36489
+ }
36490
+ /** Create an over-under woven relief pattern in UV space. */
36491
+ overUnderWeave(options) {
36492
+ return new Pattern2DImpl({
36493
+ kind: "surfacePattern:overUnderWeave",
36494
+ spacing: normalizeVec2(options.spacing, "sdf.pattern2d().overUnderWeave() spacing", requirePositiveFinite$1),
36495
+ threadWidth: normalizeVec2(options.threadWidth, "sdf.pattern2d().overUnderWeave() threadWidth", requirePositiveFinite$1),
36496
+ depth: requireNonNegativeFinite$1(options.depth ?? 0.8, "sdf.pattern2d().overUnderWeave() depth"),
36497
+ underScale: requireNonNegativeFinite$1(options.underScale ?? 0.15, "sdf.pattern2d().overUnderWeave() underScale")
36498
+ });
36499
+ }
36500
+ }
36501
+ function pattern2d() {
36502
+ return new Pattern2DBuilder();
36503
+ }
36504
+ function patternNodeFromInput(input) {
36505
+ if (input instanceof SurfacePattern) {
36506
+ const node = getTypedSurfacePattern(input);
36507
+ if (node) return node;
36508
+ }
36509
+ if (typeof input === "number") {
36510
+ return { kind: "surfacePattern:constant", value: requireFinite$8(input, "Pattern2D numeric input") };
36511
+ }
36512
+ throw new Error("Pattern2D composition expects another typed Pattern2D or a number.");
36513
+ }
36514
+ function requireFinite$8(value, label) {
36515
+ if (typeof value !== "number" || !Number.isFinite(value)) {
36516
+ throw new Error(`${label} must be a finite number. Received: ${String(value)}`);
36517
+ }
36518
+ return value;
36519
+ }
36520
+ function requirePositiveFinite$1(value, label) {
36521
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
36522
+ throw new Error(`${label} must be a positive finite number. Received: ${String(value)}`);
36523
+ }
36524
+ return value;
36525
+ }
36526
+ function requireNonNegativeFinite$1(value, label) {
36527
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
36528
+ throw new Error(`${label} must be a non-negative finite number. Received: ${String(value)}`);
36529
+ }
36530
+ return value;
36531
+ }
36532
+ function normalizeVec2(value, label, validate) {
36533
+ if (typeof value === "number") {
36534
+ const n = validate(value, label);
36535
+ return [n, n];
36536
+ }
36537
+ return [validate(value[0], `${label}[0]`), validate(value[1], `${label}[1]`)];
36538
+ }
36539
+ function normalizeDirection$1(value, label) {
36540
+ const x2 = requireFinite$8(value[0], `${label}[0]`);
36541
+ const y2 = requireFinite$8(value[1], `${label}[1]`);
36542
+ const length4 = Math.hypot(x2, y2);
36543
+ if (length4 <= 0) throw new Error(`${label} must not be the zero vector.`);
36544
+ return [x2 / length4, y2 / length4];
36545
+ }
36546
+ function f$2(value) {
36547
+ if (!Number.isFinite(value)) return "0";
36548
+ return Number(value.toPrecision(12)).toString();
36549
+ }
36550
+ function emitSurfacePatternJsExpression(node) {
36551
+ switch (node.kind) {
36552
+ case "surfacePattern:constant":
36553
+ return f$2(node.value);
36554
+ case "surfacePattern:sineWave": {
36555
+ const coord = `(u * ${f$2(node.direction[0])} + v * ${f$2(node.direction[1])})`;
36556
+ const phase = `(${coord} * ${f$2(2 * Math.PI / node.wavelength)} + ${f$2(node.phase)})`;
36557
+ return `(${f$2(node.bias)} + Math.sin(${phase}) * ${f$2(node.amplitude)})`;
36558
+ }
36559
+ case "surfacePattern:stripes": {
36560
+ const coord = `(u * ${f$2(node.direction[0])} + v * ${f$2(node.direction[1])})`;
36561
+ 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)};})()`;
36562
+ }
36563
+ case "surfacePattern:overUnderWeave":
36564
+ 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)};})()`;
36565
+ case "surfacePattern:abs":
36566
+ return `Math.abs(${emitSurfacePatternJsExpression(node.child)})`;
36567
+ case "surfacePattern:negate":
36568
+ return `(-(${emitSurfacePatternJsExpression(node.child)}))`;
36569
+ case "surfacePattern:add":
36570
+ return node.children.length === 0 ? "0" : `(${node.children.map(emitSurfacePatternJsExpression).join(" + ")})`;
36571
+ case "surfacePattern:multiply":
36572
+ return node.children.length === 0 ? "1" : `(${node.children.map(emitSurfacePatternJsExpression).join(" * ")})`;
36573
+ case "surfacePattern:min":
36574
+ return node.children.length === 0 ? "0" : `Math.min(${node.children.map(emitSurfacePatternJsExpression).join(", ")})`;
36575
+ case "surfacePattern:max":
36576
+ return node.children.length === 0 ? "0" : `Math.max(${node.children.map(emitSurfacePatternJsExpression).join(", ")})`;
36577
+ case "surfacePattern:clamp":
36578
+ return `Math.min(${f$2(node.max)}, Math.max(${f$2(node.min)}, ${emitSurfacePatternJsExpression(node.child)}))`;
36579
+ }
36580
+ }
35561
36581
  const SCULPT_MATERIAL_PRESETS = {
35562
36582
  ceramic: {
35563
36583
  color: "#f4f0e6",
@@ -35627,6 +36647,18 @@ function requirePositiveFinite(value, label) {
35627
36647
  }
35628
36648
  return value;
35629
36649
  }
36650
+ function requirePositiveInteger(value, label) {
36651
+ if (typeof value !== "number" || !Number.isFinite(value) || !Number.isInteger(value) || value < 1) {
36652
+ throw new Error(`${label} must be a positive integer. Received: ${String(value)}`);
36653
+ }
36654
+ return value;
36655
+ }
36656
+ function requireNonNegativeFinite(value, label) {
36657
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
36658
+ throw new Error(`${label} must be a non-negative finite number. Received: ${String(value)}`);
36659
+ }
36660
+ return value;
36661
+ }
35630
36662
  function resolveBlendRadius(input, label, fallback = 4) {
35631
36663
  if (typeof input === "number") return requirePositiveFinite(input, `${label} radius`);
35632
36664
  if ((input == null ? void 0 : input.radius) !== void 0) return requirePositiveFinite(input.radius, `${label} radius`);
@@ -35873,6 +36905,29 @@ class SdfShape {
35873
36905
  clipBox(x2, y2, z2) {
35874
36906
  return this.intersect(box$1(x2, y2, z2));
35875
36907
  }
36908
+ /** Keep only the material where this shape overlaps another SDF pattern. */
36909
+ fillWith(pattern) {
36910
+ if (!(pattern instanceof SdfShape)) {
36911
+ throw new Error("SdfShape.fillWith() expects an SdfShape pattern, such as sdf.gyroid({ cellSize, wallThickness }).");
36912
+ }
36913
+ return this.intersect(pattern);
36914
+ }
36915
+ /** Keep only the gyroid lattice inside this shape. */
36916
+ fillWithGyroid(options) {
36917
+ return this.fillWith(gyroid(options));
36918
+ }
36919
+ /** Keep only the Schwarz-P lattice inside this shape. */
36920
+ fillWithSchwarzP(options) {
36921
+ return this.fillWith(schwarzP(options));
36922
+ }
36923
+ /** Keep only the diamond TPMS lattice inside this shape. */
36924
+ fillWithDiamond(options) {
36925
+ return this.fillWith(diamond(options));
36926
+ }
36927
+ /** Keep only the lidinoid TPMS lattice inside this shape. */
36928
+ fillWithLidinoid(options) {
36929
+ return this.fillWith(lidinoid(options));
36930
+ }
35876
36931
  /** Smooth union — blends shapes together with a smooth radius. */
35877
36932
  smoothUnion(other, radius) {
35878
36933
  return this.withNode({ kind: "sdf:smoothUnion", children: [this._node, other._node], radius });
@@ -35933,6 +36988,21 @@ class SdfShape {
35933
36988
  repeat(spacing, count) {
35934
36989
  return this.withNode({ kind: "sdf:repeat", child: this._node, spacing, count: count ?? [0, 0, 0] });
35935
36990
  }
36991
+ /**
36992
+ * Arrange this SDF in a circular array around the Z axis.
36993
+ *
36994
+ * The source shape is translated by `offset` in +X before arraying. This uses
36995
+ * angular domain folding, so evaluation stays O(1): the source SDF is sampled
36996
+ * twice no matter how many copies are requested.
36997
+ */
36998
+ circularArray(count, offset2 = 0) {
36999
+ return this.withNode({
37000
+ kind: "sdf:circularArray",
37001
+ child: this._node,
37002
+ count: requirePositiveInteger(count, "SdfShape.circularArray() count"),
37003
+ offset: requireNonNegativeFinite(offset2, "SdfShape.circularArray() offset")
37004
+ });
37005
+ }
35936
37006
  /** Hollow out, keeping only a shell of given thickness. */
35937
37007
  shell(thickness) {
35938
37008
  return this.withNode({ kind: "sdf:shell", child: this._node, thickness });
@@ -35944,8 +37014,8 @@ class SdfShape {
35944
37014
  * // Function displacement
35945
37015
  * shape.displace((x, y, z) => Math.sin(x) * 0.5)
35946
37016
  *
35947
- * // Pattern displacement (e.g. basketWeave)
35948
- * shape.displace(sdf.basketWeave({ threads: 16, spacing: 3 }))
37017
+ * // Pattern displacement from a 3D SDF field
37018
+ * shape.displace(sdf.knurl({ pitch: 2, depth: 0.3 }))
35949
37019
  * ```
35950
37020
  */
35951
37021
  displace(fn, constants) {
@@ -35969,10 +37039,18 @@ class SdfShape {
35969
37039
  * UV coordinates are in **surface millimeters** — patterns defined with `spacing: 3`
35970
37040
  * always produce 3mm spacing, regardless of shape size.
35971
37041
  *
37042
+ * Prefer `sdf.pattern2d()` or built-in surface patterns when the relief should
37043
+ * stay on the native shader and meshing path. Callback functions are supported
37044
+ * for experimentation, but they are opaque to the typed pattern optimizer.
37045
+ *
35972
37046
  * ```js
35973
- * // Surface-following basket weave — auto-detects sphere UV
37047
+ * // Native typed pattern — auto-detects sphere UV
37048
+ * const p = sdf.pattern2d()
37049
+ * const ribs = p.stripes({ spacing: 3, width: 0.8, depth: 0.35 })
37050
+ * .add(p.sineWave({ direction: [0, 1], wavelength: 14, amplitude: 0.08 }))
37051
+ *
35974
37052
  * sdf.sphere(27).shell(3)
35975
- * .surfaceDisplace(sdf.basketWeave({ spacing: 3, depth: 0.8 }))
37053
+ * .surfaceDisplace(ribs)
35976
37054
  * .toShape()
35977
37055
  *
35978
37056
  * // Custom 2D pattern via function
@@ -35982,15 +37060,18 @@ class SdfShape {
35982
37060
  surfaceDisplace(pattern, options) {
35983
37061
  let body;
35984
37062
  let constants;
37063
+ let typedPattern;
35985
37064
  if (pattern instanceof SurfacePattern) {
35986
37065
  body = pattern.body;
35987
37066
  constants = pattern.constants;
37067
+ typedPattern = getTypedSurfacePattern(pattern);
35988
37068
  } else {
35989
37069
  body = extractFunctionBody(pattern);
35990
37070
  }
35991
37071
  return this.withNode({
35992
37072
  kind: "sdf:surfaceDisplace",
35993
37073
  child: this._node,
37074
+ ...typedPattern ? { pattern: typedPattern } : {},
35994
37075
  patternBody: body,
35995
37076
  constants,
35996
37077
  ...(options == null ? void 0 : options.uv) ? { uvMode: options.uv } : {},
@@ -36209,24 +37290,10 @@ function weave(options) {
36209
37290
  });
36210
37291
  }
36211
37292
  function basketWeave(options) {
36212
- const SP = (options == null ? void 0 : options.spacing) ?? 3;
36213
- const TW = (options == null ? void 0 : options.threadWidth) ?? 1.5;
36214
- const D2 = (options == null ? void 0 : options.depth) ?? 0.8;
36215
- const hw = TW * 0.5;
36216
- const body = `(function() {
36217
- var su = u / ${SP};
36218
- var sv = v / ${SP};
36219
- var du = Math.abs(su - Math.round(su)) * ${SP};
36220
- var dv = Math.abs(sv - Math.round(sv)) * ${SP};
36221
- var hw = ${hw};
36222
- var pU = Math.max(0, 1 - du / hw); pU *= pU;
36223
- var pV = Math.max(0, 1 - dv / hw); pV *= pV;
36224
- var checker = ((Math.round(su) & 65535) + (Math.round(sv) & 65535)) & 1;
36225
- var top = checker ? pV : pU;
36226
- var bot = checker ? pU : pV;
36227
- return -(top > bot * 0.15 ? top : bot * 0.15) * ${D2};
36228
- })()`;
36229
- return new SurfacePattern(body);
37293
+ const SP = requirePositiveFinite((options == null ? void 0 : options.spacing) ?? 3, "sdf.basketWeave() spacing");
37294
+ const TW = requirePositiveFinite((options == null ? void 0 : options.threadWidth) ?? 1.5, "sdf.basketWeave() threadWidth");
37295
+ const D2 = requireNonNegativeFinite((options == null ? void 0 : options.depth) ?? 0.8, "sdf.basketWeave() depth");
37296
+ return pattern2d().overUnderWeave({ spacing: SP, threadWidth: TW, depth: D2 });
36230
37297
  }
36231
37298
  function fromFunction(fn, options) {
36232
37299
  if (!options || typeof options !== "object") {
@@ -36259,6 +37326,9 @@ function bend(shape, radius) {
36259
37326
  function repeat(shape, spacing, count) {
36260
37327
  return shape.repeat(spacing, count);
36261
37328
  }
37329
+ function circularArray(shape, count, offset2 = 0) {
37330
+ return shape.circularArray(count, offset2);
37331
+ }
36262
37332
  function resolveTpmsOptions(options) {
36263
37333
  const wallThickness = options.wallThickness;
36264
37334
  const thickness = wallThickness ?? options.thickness;
@@ -36699,12 +37769,16 @@ const sdf = {
36699
37769
  weave,
36700
37770
  /** Basket weave surface pattern — threads with over-under crossings in UV space. Returns a SurfacePattern for use with `.surfaceDisplace()`. */
36701
37771
  basketWeave,
37772
+ /** Create typed, composable 2D surface patterns for `.surfaceDisplace()`. */
37773
+ pattern2d,
36702
37774
  /** Twist an SDF shape around the Z axis. */
36703
37775
  twist,
36704
37776
  /** Bend an SDF shape around the Z axis. */
36705
37777
  bend,
36706
37778
  /** Repeat an SDF shape in space. */
36707
37779
  repeat,
37780
+ /** Arrange an SDF shape in a circular array around the Z axis with O(1) folded-domain evaluation. */
37781
+ circularArray,
36708
37782
  /** A 2D surface pattern — a heightmap function for use with `.surfaceDisplace()`. */
36709
37783
  SurfacePattern,
36710
37784
  /** Create a custom SDF from one expression; shader-safe expressions raymarch directly. */
@@ -41638,13 +42712,16 @@ class SolvedAssembly {
41638
42712
  * @category Assembly
41639
42713
  */
41640
42714
  toGroup() {
42715
+ var _a3;
41641
42716
  const children = [];
41642
42717
  const childNames = [];
41643
- for (const [name] of this.parts) {
42718
+ const childTags = [];
42719
+ for (const [name, rec] of this.parts) {
41644
42720
  children.push(this.getPart(name));
41645
42721
  childNames.push(name);
42722
+ childTags.push(normalizeSceneTags((_a3 = rec.metadata) == null ? void 0 : _a3.tags, `Assembly part "${name}" metadata.tags`));
41646
42723
  }
41647
- return new ShapeGroup(children, childNames);
42724
+ return new ShapeGroup(children, childNames, childTags);
41648
42725
  }
41649
42726
  /**
41650
42727
  * Return an array of named scene objects for the viewport renderer.
@@ -41678,17 +42755,18 @@ class SolvedAssembly {
41678
42755
  const used = usedByPart.get(partName);
41679
42756
  if (used && used.length > 0) markShapePortsUsed(shape, used);
41680
42757
  };
41681
- const appendGroupChildren = (grp, prefix, partName, out2) => {
42758
+ const appendGroupChildren = (grp, prefix, partName, out2, inheritedTags = []) => {
41682
42759
  grp.children.forEach((child, index2) => {
41683
42760
  const childName = grp.childName(index2);
41684
42761
  const label = childName ? `${prefix}.${childName}` : `${prefix}.${index2 + 1}`;
42762
+ const tags = mergeSceneTags(inheritedTags, grp.tagsForChild(index2));
41685
42763
  if (child instanceof ShapeGroup) {
41686
- appendGroupChildren(child, label, partName, out2);
42764
+ appendGroupChildren(child, label, partName, out2, tags);
41687
42765
  return;
41688
42766
  }
41689
42767
  if (child instanceof Shape) {
41690
42768
  markUsedOnShape(child, partName);
41691
- out2.push({ name: label, shape: child });
42769
+ out2.push({ name: label, shape: child, ...tags.length > 0 ? { tags } : {} });
41692
42770
  }
41693
42771
  });
41694
42772
  };
@@ -42054,7 +43132,7 @@ class Assembly {
42054
43132
  *
42055
43133
  * @param name - Unique part name (must not already exist)
42056
43134
  * @param part - The `Shape` or `ShapeGroup` geometry
42057
- * @param options - Optional `{ transform, metadata }` (material, process, qty, etc.)
43135
+ * @param options - Optional `{ transform, metadata }` (material, process, qty, tags, etc.)
42058
43136
  * @returns `this` for chaining
42059
43137
  * @category Assembly
42060
43138
  */
@@ -43008,15 +44086,18 @@ class ImportedAssembly {
43008
44086
  * Any stored placement offset and placement references are forwarded to the group.
43009
44087
  */
43010
44088
  toGroup(state) {
44089
+ var _a3;
43011
44090
  const solved = this._assembly.solve(state);
43012
44091
  const def = this._assembly.describe();
43013
44092
  const children = [];
43014
44093
  const childNames = [];
44094
+ const childTags = [];
43015
44095
  for (const p2 of def.parts) {
43016
44096
  children.push(solved.getPart(p2.name));
43017
44097
  childNames.push(p2.name);
44098
+ childTags.push(normalizeSceneTags((_a3 = p2.metadata) == null ? void 0 : _a3.tags, `Assembly part "${p2.name}" metadata.tags`));
43018
44099
  }
43019
- let result = new ShapeGroup(children, childNames);
44100
+ let result = new ShapeGroup(children, childNames, childTags);
43020
44101
  const [dx, dy, dz] = this._offset;
43021
44102
  if (dx !== 0 || dy !== 0 || dz !== 0) {
43022
44103
  result = result.translate(dx, dy, dz);
@@ -43386,8 +44467,8 @@ function buildPure3mfBuffer(objects, options = {}) {
43386
44467
  for (let t = 0; t < numTri; t++) {
43387
44468
  const v1 = triVerts[t * 3];
43388
44469
  const v2 = triVerts[t * 3 + 1];
43389
- const v3 = triVerts[t * 3 + 2];
43390
- xmlParts.push(` <triangle v1="${v1}" v2="${v2}" v3="${v3}" />`);
44470
+ const v32 = triVerts[t * 3 + 2];
44471
+ xmlParts.push(` <triangle v1="${v1}" v2="${v2}" v3="${v32}" />`);
43391
44472
  }
43392
44473
  xmlParts.push(" </triangles>");
43393
44474
  xmlParts.push(" </mesh>");
@@ -43813,7 +44894,7 @@ class GCodeBuilder {
43813
44894
  this.lines.push("G1 E-0.8 F1800 ; retract");
43814
44895
  this.lines.push("");
43815
44896
  const safeZ = Math.min(maxZ + 5, p2.bedZ - 1);
43816
- this.lines.push(`G1 Z${f2(safeZ)} F900 ; lift nozzle above print`);
44897
+ this.lines.push(`G1 Z${f$1(safeZ)} F900 ; lift nozzle above print`);
43817
44898
  this.lines.push("");
43818
44899
  this.lines.push("M140 S0 ; bed off");
43819
44900
  this.lines.push("M104 S0 ; hotend off");
@@ -43857,7 +44938,7 @@ class GCodeBuilder {
43857
44938
  travelTo(x2, y2, z2) {
43858
44939
  this.retract();
43859
44940
  const from = [...this.pos];
43860
- this.lines.push(`G0 X${f2(x2)} Y${f2(y2)} Z${f2(z2)} F${Math.round(this.profile.travelSpeed)}`);
44941
+ this.lines.push(`G0 X${f$1(x2)} Y${f$1(y2)} Z${f$1(z2)} F${Math.round(this.profile.travelSpeed)}`);
43861
44942
  if (this.posInitialized) {
43862
44943
  this._segments.push({ from, to: [x2, y2, z2], extrude: false, speed: this.profile.travelSpeed });
43863
44944
  }
@@ -43889,7 +44970,7 @@ class GCodeBuilder {
43889
44970
  const beadArea = this.profile.layerHeight * this.profile.nozzle;
43890
44971
  const eIncrement = beadArea * dist4 / this.filamentArea;
43891
44972
  this.e += eIncrement;
43892
- this.lines.push(`G1 X${f2(x2)} Y${f2(y2)} Z${f2(z2)} E${f2(this.e)} F${Math.round(this.currentSpeed)}`);
44973
+ this.lines.push(`G1 X${f$1(x2)} Y${f$1(y2)} Z${f$1(z2)} E${f$1(this.e)} F${Math.round(this.currentSpeed)}`);
43893
44974
  if (this.posInitialized) {
43894
44975
  this._segments.push({ from, to: [x2, y2, z2], extrude: true, speed: this.currentSpeed });
43895
44976
  }
@@ -43987,13 +45068,13 @@ class GCodeBuilder {
43987
45068
  retract() {
43988
45069
  if (this.retracted) return;
43989
45070
  this.e -= this.profile.retractionDistance;
43990
- this.lines.push(`G1 E${f2(this.e)} F${Math.round(this.profile.retractionSpeed)}`);
45071
+ this.lines.push(`G1 E${f$1(this.e)} F${Math.round(this.profile.retractionSpeed)}`);
43991
45072
  this.retracted = true;
43992
45073
  }
43993
45074
  unretract() {
43994
45075
  if (!this.retracted) return;
43995
45076
  this.e += this.profile.retractionDistance;
43996
- this.lines.push(`G1 E${f2(this.e)} F${Math.round(this.profile.retractionSpeed)}`);
45077
+ this.lines.push(`G1 E${f$1(this.e)} F${Math.round(this.profile.retractionSpeed)}`);
43997
45078
  this.retracted = false;
43998
45079
  }
43999
45080
  // ---- Bounds tracking ----
@@ -44064,7 +45145,7 @@ class GCodeBuilder {
44064
45145
  return this.lines.join("\n") + "\n";
44065
45146
  }
44066
45147
  }
44067
- function f2(n) {
45148
+ function f$1(n) {
44068
45149
  return n.toFixed(5).replace(/\.?0+$/, "");
44069
45150
  }
44070
45151
  function bambuModelName(preset) {
@@ -47575,7 +48656,8 @@ function explode(items, options = {}) {
47575
48656
  if (child instanceof ShapeGroup) return explodeGroup(child, p2, depth + 1, total, groupCenter, motion.branchDirection);
47576
48657
  return explodeLeaf(child, explodeAdd(total, leafMotion(child, p2, depth + 1, groupCenter, motion.branchDirection).offset));
47577
48658
  }),
47578
- grp.childNames
48659
+ grp.childNames,
48660
+ grp.children.map((_2, i) => grp.tagsForChild(i))
47579
48661
  );
47580
48662
  };
47581
48663
  const explodeItemNode = (item, path2, depth, inherited, parentCenter, parentDirection) => {
@@ -47611,7 +48693,8 @@ function explode(items, options = {}) {
47611
48693
  if (child instanceof ShapeGroup) return explodeGroup(child, p2, 1, [0, 0, 0], rootCenter, void 0);
47612
48694
  return explodeLeaf(child, nodeMotion(child, p2, 1, rootCenter, void 0).offset);
47613
48695
  }),
47614
- items.childNames
48696
+ items.childNames,
48697
+ items.children.map((_2, i) => items.tagsForChild(i))
47615
48698
  );
47616
48699
  }
47617
48700
  return items.map((item, i) => {
@@ -48380,6 +49463,398 @@ function spurGear(options) {
48380
49463
  });
48381
49464
  return attachGearMeta(shapeWithConnectors, meta2);
48382
49465
  }
49466
+ function requirePositive$7(scope, name, value) {
49467
+ if (!isFinitePositive(value)) throw new Error(`${scope}: "${name}" must be > 0`);
49468
+ }
49469
+ function requireOptionalBore(scope, boreDiameter, maxDiameter) {
49470
+ const bore = boreDiameter ?? 0;
49471
+ if (!Number.isFinite(bore) || bore < 0) throw new Error(`${scope}: "boreDiameter" must be >= 0`);
49472
+ if (bore > 0 && bore >= maxDiameter) throw new Error(`${scope}: bore is too large for the body`);
49473
+ return bore;
49474
+ }
49475
+ function resolveSegments(segments) {
49476
+ if (segments === void 0) return void 0;
49477
+ if (!Number.isInteger(segments) || segments < 12) throw new Error('gear body: "segments" must be an integer >= 12');
49478
+ return segments;
49479
+ }
49480
+ function cutBore$1(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 gearBodyDisk(options) {
49488
+ requirePositive$7("gearBodyDisk", "outerRadius", options.outerRadius);
49489
+ requirePositive$7("gearBodyDisk", "faceWidth", options.faceWidth);
49490
+ const bore = requireOptionalBore("gearBodyDisk", options.boreDiameter, options.outerRadius * 2);
49491
+ const segments = resolveSegments(options.segments);
49492
+ const outer = circle2d(options.outerRadius, segments);
49493
+ const profile = bore > 0 ? difference2d(outer, circle2d(bore * 0.5, segments)) : outer;
49494
+ return sketchExtrude(profile, options.faceWidth);
49495
+ }
49496
+ function gearBodyDiskWithHub(options) {
49497
+ requirePositive$7("gearBodyDiskWithHub", "hubDiameter", options.hubDiameter);
49498
+ if (options.hubDiameter >= options.outerRadius * 2) {
49499
+ throw new Error('gearBodyDiskWithHub: "hubDiameter" must be smaller than the outer diameter');
49500
+ }
49501
+ const bore = requireOptionalBore("gearBodyDiskWithHub", options.boreDiameter, options.hubDiameter);
49502
+ const base = gearBodyDisk({ ...options, boreDiameter: 0 });
49503
+ const hubFaceWidth = options.hubFaceWidth ?? options.faceWidth * 1.5;
49504
+ requirePositive$7("gearBodyDiskWithHub", "hubFaceWidth", hubFaceWidth);
49505
+ const hub = cylinder(hubFaceWidth, options.hubDiameter * 0.5, void 0, options.segments).translate(
49506
+ 0,
49507
+ 0,
49508
+ (options.faceWidth - hubFaceWidth) * 0.5
49509
+ );
49510
+ return cutBore$1(base.add(hub), bore);
49511
+ }
49512
+ function gearBodySpoked(options) {
49513
+ requirePositive$7("gearBodySpoked", "outerRadius", options.outerRadius);
49514
+ requirePositive$7("gearBodySpoked", "faceWidth", options.faceWidth);
49515
+ requirePositive$7("gearBodySpoked", "rimWidth", options.rimWidth);
49516
+ requirePositive$7("gearBodySpoked", "hubDiameter", options.hubDiameter);
49517
+ requirePositive$7("gearBodySpoked", "spokeWidth", options.spokeWidth);
49518
+ if (!Number.isInteger(options.spokeCount) || options.spokeCount < 2) {
49519
+ throw new Error('gearBodySpoked: "spokeCount" must be an integer >= 2');
49520
+ }
49521
+ const hubRadius = options.hubDiameter * 0.5;
49522
+ const rimInnerRadius = options.outerRadius - options.rimWidth;
49523
+ if (rimInnerRadius <= hubRadius) throw new Error("gearBodySpoked: rim overlaps the hub");
49524
+ const bore = requireOptionalBore("gearBodySpoked", options.boreDiameter, options.hubDiameter);
49525
+ const segments = resolveSegments(options.segments);
49526
+ const rim = difference2d(circle2d(options.outerRadius, segments), circle2d(rimInnerRadius, segments));
49527
+ const hub = circle2d(hubRadius, segments);
49528
+ const spokeLength = rimInnerRadius - hubRadius + options.spokeWidth;
49529
+ const spokeCenter = hubRadius + spokeLength * 0.5 - options.spokeWidth * 0.5;
49530
+ const spoke = sketchTranslate(rect(spokeLength, options.spokeWidth), spokeCenter, 0);
49531
+ const spokes = [];
49532
+ for (let i = 0; i < options.spokeCount; i++) {
49533
+ spokes.push(sketchRotateAround(spoke, 360 / options.spokeCount * i, [0, 0]));
49534
+ }
49535
+ const profile = bore > 0 ? difference2d(union2d(rim, hub, ...spokes), circle2d(bore * 0.5, segments)) : union2d(rim, hub, ...spokes);
49536
+ return sketchExtrude(profile, options.faceWidth);
49537
+ }
49538
+ function gearBodyFromProfile(profile, options) {
49539
+ if (!(profile instanceof Sketch)) throw new Error('gearBodyFromProfile: "profile" must be a Sketch');
49540
+ requirePositive$7("gearBodyFromProfile", "faceWidth", options.faceWidth);
49541
+ const bore = options.boreDiameter ?? 0;
49542
+ if (!Number.isFinite(bore) || bore < 0) throw new Error('gearBodyFromProfile: "boreDiameter" must be >= 0');
49543
+ return cutBore$1(sketchExtrude(profile, options.faceWidth), bore);
49544
+ }
49545
+ function requirePositive$6(scope, name, value) {
49546
+ if (!isFinitePositive(value)) throw new Error(`${scope}: "${name}" must be > 0`);
49547
+ }
49548
+ function requireFiniteAngle(scope, name, value) {
49549
+ if (value !== void 0 && !Number.isFinite(value)) throw new Error(`${scope}: "${name}" must be finite`);
49550
+ }
49551
+ function cutBore(shape, boreDiameter) {
49552
+ if (boreDiameter <= 0) return shape;
49553
+ const bounds = shape.boundingBox();
49554
+ const height = bounds.max[2] - bounds.min[2] + 2;
49555
+ const cutter = cylinder(height, boreDiameter * 0.5, void 0, 64).translate(0, 0, bounds.min[2] - 1);
49556
+ return shape.subtract(cutter);
49557
+ }
49558
+ function bodyOuterRadius(shape) {
49559
+ const bounds = shape.boundingBox();
49560
+ return Math.max(Math.abs(bounds.min[0]), Math.abs(bounds.max[0]), Math.abs(bounds.min[1]), Math.abs(bounds.max[1]));
49561
+ }
49562
+ function buildSpurTeethRegion(options, name, faceWidth) {
49563
+ const scope = "driveWheel.addSpurTeethBetween";
49564
+ const teethOnFullCircle = options.teethOnFullCircle;
49565
+ if (!Number.isInteger(teethOnFullCircle) || teethOnFullCircle < 6) {
49566
+ throw new Error(`${scope}: "teethOnFullCircle" must be an integer >= 6`);
49567
+ }
49568
+ const toothCount = options.toothCount;
49569
+ if (!Number.isInteger(toothCount) || toothCount < 1 || toothCount > teethOnFullCircle) {
49570
+ throw new Error(`${scope}: "toothCount" must be an integer in [1, teethOnFullCircle]`);
49571
+ }
49572
+ const firstTooth = options.firstTooth ?? 0;
49573
+ if (!Number.isInteger(firstTooth) || firstTooth < 0 || firstTooth >= teethOnFullCircle) {
49574
+ throw new Error(`${scope}: "firstTooth" must be an integer in [0, teethOnFullCircle)`);
49575
+ }
49576
+ let normalized;
49577
+ try {
49578
+ normalized = normalizeSpurGearOptions({ ...options, teeth: teethOnFullCircle, faceWidth, boreDiameter: 0 });
49579
+ } catch (error) {
49580
+ remapErrorPrefix(error, "spurGear", scope);
49581
+ }
49582
+ const gearMeta = buildSpurGearMeta(normalized);
49583
+ const pitchStepDeg = 360 / teethOnFullCircle;
49584
+ const fromAngleDeg = firstTooth * pitchStepDeg - pitchStepDeg * 0.5;
49585
+ const toAngleDeg = (firstTooth + toothCount - 1) * pitchStepDeg + pitchStepDeg * 0.5;
49586
+ const profile = buildSpurToothRegionProfile(gearMeta, firstTooth, toothCount, normalized.segmentsPerTooth);
49587
+ return {
49588
+ shape: sketchExtrude(profile, faceWidth),
49589
+ gearMeta,
49590
+ meta: {
49591
+ name,
49592
+ kind: "spurTeeth",
49593
+ fromAngleDeg,
49594
+ toAngleDeg,
49595
+ outerRadius: gearMeta.outerRadius,
49596
+ rootRadius: gearMeta.rootRadius,
49597
+ pitchRadius: gearMeta.pitchRadius,
49598
+ module: normalized.module,
49599
+ teethOnFullCircle,
49600
+ toothCount,
49601
+ faceWidth
49602
+ }
49603
+ };
49604
+ }
49605
+ function buildSolidArcRegion(options, name, faceWidth) {
49606
+ const scope = "driveWheel.addSolidArcBetween";
49607
+ requirePositive$6(scope, "outerRadius", options.outerRadius);
49608
+ const innerRadius = options.innerRadius ?? 0;
49609
+ if (!Number.isFinite(innerRadius) || innerRadius < 0) throw new Error(`${scope}: "innerRadius" must be >= 0`);
49610
+ if (innerRadius >= options.outerRadius) throw new Error(`${scope}: "innerRadius" must be smaller than "outerRadius"`);
49611
+ const sweepDeg = normalizedSweep(scope, options.fromAngleDeg, options.toAngleDeg);
49612
+ return {
49613
+ shape: sketchExtrude(buildSolidArcProfile(options, sweepDeg), faceWidth),
49614
+ meta: {
49615
+ name,
49616
+ kind: "solidArc",
49617
+ fromAngleDeg: options.fromAngleDeg,
49618
+ toAngleDeg: options.fromAngleDeg + sweepDeg,
49619
+ innerRadius,
49620
+ outerRadius: options.outerRadius,
49621
+ faceWidth
49622
+ }
49623
+ };
49624
+ }
49625
+ function normalizedSweep(scope, fromAngleDeg, toAngleDeg) {
49626
+ if (!Number.isFinite(fromAngleDeg)) throw new Error(`${scope}: "fromAngleDeg" must be finite`);
49627
+ if (!Number.isFinite(toAngleDeg)) throw new Error(`${scope}: "toAngleDeg" must be finite`);
49628
+ let sweep2 = toAngleDeg - fromAngleDeg;
49629
+ while (sweep2 <= 0) sweep2 += 360;
49630
+ if (sweep2 > 360 + EPSILON$1) throw new Error(`${scope}: angular sweep must be <= 360 degrees`);
49631
+ return Math.min(360, sweep2);
49632
+ }
49633
+ function buildSpurToothRegionProfile(meta2, firstTooth, toothCount, segmentsPerTooth) {
49634
+ const tooth = createSpurToothSketch(meta2, segmentsPerTooth);
49635
+ const teeth = [];
49636
+ for (let i = 0; i < toothCount; i++) {
49637
+ teeth.push(sketchRotateAround(tooth, 360 / meta2.teeth * (firstTooth + i), [0, 0]));
49638
+ }
49639
+ return union2d(...teeth);
49640
+ }
49641
+ function buildSolidArcProfile(options, sweepDeg) {
49642
+ const innerRadius = options.innerRadius ?? 0;
49643
+ const segments = options.segments ?? Math.max(16, Math.ceil(sweepDeg / 6));
49644
+ if (!Number.isInteger(segments) || segments < 4) throw new Error('driveWheel.addSolidArcBetween: "segments" must be an integer >= 4');
49645
+ if (Math.abs(sweepDeg - 360) < EPSILON$1) {
49646
+ const outer = circle2d(options.outerRadius, segments);
49647
+ return innerRadius > 0 ? difference2d(outer, circle2d(innerRadius, segments)) : outer;
49648
+ }
49649
+ const start = options.fromAngleDeg * Math.PI / 180;
49650
+ const end = start + sweepDeg * Math.PI / 180;
49651
+ const pts = [];
49652
+ if (innerRadius <= 0) pts.push([0, 0]);
49653
+ addArcPoints(pts, options.outerRadius, start, end, segments, true, true);
49654
+ if (innerRadius > 0) addArcPoints(pts, innerRadius, end, start, segments, true, true);
49655
+ return polygon(pts);
49656
+ }
49657
+ const DRIVE_WHEEL_META_KEY = Symbol.for("forgecad.library.driveWheelMeta");
49658
+ function attachDriveWheelMeta(shape, meta2) {
49659
+ shape[DRIVE_WHEEL_META_KEY] = meta2;
49660
+ return shape;
49661
+ }
49662
+ function readDriveWheelMeta(shape) {
49663
+ const meta2 = shape[DRIVE_WHEEL_META_KEY];
49664
+ return meta2 ?? null;
49665
+ }
49666
+ class DriveWheelBuilder {
49667
+ constructor(options = {}) {
49668
+ __publicField(this, "body");
49669
+ __publicField(this, "faceWidth");
49670
+ __publicField(this, "boreDiameter");
49671
+ __publicField(this, "regions", []);
49672
+ if (options.body !== void 0 && !(options.body instanceof Shape)) throw new Error('driveWheel: "body" must be a Shape');
49673
+ if (options.faceWidth !== void 0) requirePositive$6("driveWheel", "faceWidth", options.faceWidth);
49674
+ const boreDiameter = options.boreDiameter ?? 0;
49675
+ if (!Number.isFinite(boreDiameter) || boreDiameter < 0) throw new Error('driveWheel: "boreDiameter" must be >= 0');
49676
+ this.body = options.body;
49677
+ this.faceWidth = options.faceWidth;
49678
+ this.boreDiameter = boreDiameter;
49679
+ }
49680
+ /**
49681
+ * Add an involute spur-tooth window on part of the pitch circle.
49682
+ */
49683
+ addSpurTeethBetween(options) {
49684
+ const faceWidth = this.resolveFaceWidth("driveWheel.addSpurTeethBetween", options.faceWidth);
49685
+ this.regions.push(buildSpurTeethRegion(options, this.resolveName("teeth", options.name), faceWidth));
49686
+ return this;
49687
+ }
49688
+ /**
49689
+ * Add a constant-radius solid arc region such as a dwell, stop, or pusher.
49690
+ */
49691
+ addSolidArcBetween(options) {
49692
+ const faceWidth = this.resolveFaceWidth("driveWheel.addSolidArcBetween", options.faceWidth);
49693
+ this.regions.push(buildSolidArcRegion(options, this.resolveName("arc", options.name), faceWidth));
49694
+ return this;
49695
+ }
49696
+ /**
49697
+ * Add a fully custom region shape while preserving region metadata.
49698
+ */
49699
+ addShapeRegion(name, shape, options = {}) {
49700
+ const scope = "driveWheel.addShapeRegion";
49701
+ if (typeof name !== "string" || name.trim().length === 0) throw new Error(`${scope}: "name" must be a non-empty string`);
49702
+ if (!(shape instanceof Shape)) throw new Error(`${scope}: "shape" must be a Shape`);
49703
+ requireFiniteAngle(scope, "fromAngleDeg", options.fromAngleDeg);
49704
+ requireFiniteAngle(scope, "toAngleDeg", options.toAngleDeg);
49705
+ if (options.innerRadius !== void 0 && (!Number.isFinite(options.innerRadius) || options.innerRadius < 0)) {
49706
+ throw new Error(`${scope}: "innerRadius" must be >= 0`);
49707
+ }
49708
+ if (options.outerRadius !== void 0) requirePositive$6(scope, "outerRadius", options.outerRadius);
49709
+ this.regions.push({
49710
+ shape: shape.clone(),
49711
+ meta: {
49712
+ name: this.resolveName("region", name),
49713
+ kind: "custom",
49714
+ ...options
49715
+ }
49716
+ });
49717
+ return this;
49718
+ }
49719
+ /**
49720
+ * Build the final wheel shape with a bore connector and region metadata.
49721
+ */
49722
+ build() {
49723
+ var _a3, _b3;
49724
+ if (this.regions.length === 0 && this.body === void 0) {
49725
+ throw new Error("driveWheel: add a body or at least one region before build()");
49726
+ }
49727
+ const faceWidth = this.resolveBuildFaceWidth();
49728
+ const firstGearRegion = (_a3 = this.regions.find((region) => region.gearMeta)) == null ? void 0 : _a3.gearMeta;
49729
+ if (firstGearRegion && this.boreDiameter * 0.5 >= firstGearRegion.rootRadius - EPSILON$1) {
49730
+ throw new Error("driveWheel: bore is too large for the first spur-tooth region");
49731
+ }
49732
+ const body = ((_b3 = this.body) == null ? void 0 : _b3.clone()) ?? gearBodyDisk({ outerRadius: (firstGearRegion == null ? void 0 : firstGearRegion.rootRadius) ?? this.defaultBodyRadius(), faceWidth });
49733
+ let combined = body;
49734
+ for (const region of this.regions) combined = combined.add(region.shape);
49735
+ combined = cutBore(combined, this.boreDiameter);
49736
+ const withConnectors = combined.withConnectors({
49737
+ bore: connectorFactory(
49738
+ "drive-wheel-bore",
49739
+ { origin: [0, 0, faceWidth / 2], axis: [0, 0, 1], kind: "revolute" },
49740
+ this.measurements(faceWidth)
49741
+ )
49742
+ });
49743
+ return attachDriveWheelMeta(withConnectors, {
49744
+ kind: "driveWheel",
49745
+ faceWidth,
49746
+ boreDiameter: this.boreDiameter,
49747
+ regions: this.regionMetadata(body, faceWidth)
49748
+ });
49749
+ }
49750
+ measurements(faceWidth) {
49751
+ var _a3;
49752
+ const firstGearRegion = (_a3 = this.regions.find((region) => region.gearMeta)) == null ? void 0 : _a3.gearMeta;
49753
+ return {
49754
+ faceWidth,
49755
+ boreDiameter: this.boreDiameter,
49756
+ regionCount: this.regions.length,
49757
+ ...firstGearRegion ? {
49758
+ module: firstGearRegion.module,
49759
+ teethOnFullCircle: firstGearRegion.teeth,
49760
+ pitchRadius: firstGearRegion.pitchRadius,
49761
+ outerRadius: firstGearRegion.outerRadius
49762
+ } : {}
49763
+ };
49764
+ }
49765
+ regionMetadata(body, faceWidth) {
49766
+ return [
49767
+ { name: "body", kind: "body", outerRadius: bodyOuterRadius(body), faceWidth },
49768
+ ...this.regions.map((region) => ({ ...region.meta }))
49769
+ ];
49770
+ }
49771
+ resolveFaceWidth(scope, localFaceWidth) {
49772
+ const faceWidth = localFaceWidth ?? this.faceWidth;
49773
+ if (faceWidth === void 0) throw new Error(`${scope}: "faceWidth" is required unless driveWheel({ faceWidth }) was set`);
49774
+ requirePositive$6(scope, "faceWidth", faceWidth);
49775
+ if (this.faceWidth !== void 0 && localFaceWidth !== void 0 && Math.abs(this.faceWidth - localFaceWidth) > EPSILON$1) {
49776
+ throw new Error(`${scope}: region faceWidth must match driveWheel faceWidth`);
49777
+ }
49778
+ return faceWidth;
49779
+ }
49780
+ resolveBuildFaceWidth() {
49781
+ var _a3;
49782
+ const faceWidth = this.faceWidth ?? ((_a3 = this.regions.find((region) => region.meta.faceWidth !== void 0)) == null ? void 0 : _a3.meta.faceWidth);
49783
+ if (faceWidth === void 0) throw new Error('driveWheel: "faceWidth" is required before build()');
49784
+ return faceWidth;
49785
+ }
49786
+ defaultBodyRadius() {
49787
+ const outerRadius = this.regions.reduce((max2, region) => Math.max(max2, region.meta.outerRadius ?? 0), 0);
49788
+ if (outerRadius <= 0) throw new Error('driveWheel: "body" is required when regions do not define an outer radius');
49789
+ return outerRadius;
49790
+ }
49791
+ resolveName(prefix, requested) {
49792
+ const base = (requested == null ? void 0 : requested.trim()) || prefix;
49793
+ if (this.regions.every((region) => region.meta.name !== base)) return base;
49794
+ for (let i = 2; ; i++) {
49795
+ const candidate = `${base}${i}`;
49796
+ if (this.regions.every((region) => region.meta.name !== candidate)) return candidate;
49797
+ }
49798
+ }
49799
+ }
49800
+ function driveWheel(options = {}) {
49801
+ return new DriveWheelBuilder(options);
49802
+ }
49803
+ function normalizeSectorGearOptions(options) {
49804
+ const teethOnFullCircle = options.teethOnFullCircle;
49805
+ if (!Number.isInteger(teethOnFullCircle) || teethOnFullCircle < 6) {
49806
+ throw new Error('sectorGear: "teethOnFullCircle" must be an integer >= 6');
49807
+ }
49808
+ const toothCount = options.toothCount;
49809
+ if (!Number.isInteger(toothCount) || toothCount < 1 || toothCount > teethOnFullCircle) {
49810
+ throw new Error('sectorGear: "toothCount" must be an integer in [1, teethOnFullCircle]');
49811
+ }
49812
+ const firstTooth = options.firstTooth ?? 0;
49813
+ if (!Number.isInteger(firstTooth) || firstTooth < 0 || firstTooth >= teethOnFullCircle) {
49814
+ throw new Error('sectorGear: "firstTooth" must be an integer in [0, teethOnFullCircle)');
49815
+ }
49816
+ return {
49817
+ ...normalizeSpurGearOptions({ ...options, teeth: teethOnFullCircle }),
49818
+ teethOnFullCircle,
49819
+ toothCount,
49820
+ firstTooth,
49821
+ boreDiameter: options.boreDiameter ?? 0
49822
+ };
49823
+ }
49824
+ function sectorGear(options) {
49825
+ const normalized = normalizeSectorGearOptions(options);
49826
+ if (options.body !== void 0 && !(options.body instanceof Shape)) {
49827
+ throw new Error('sectorGear: "body" must be a Shape');
49828
+ }
49829
+ const spurMeta = buildSpurGearMeta(normalized);
49830
+ const pitchStepDeg = 360 / normalized.teethOnFullCircle;
49831
+ const activeAngleStartDeg = normalized.firstTooth * pitchStepDeg - pitchStepDeg * 0.5;
49832
+ const activeAngleEndDeg = (normalized.firstTooth + normalized.toothCount - 1) * pitchStepDeg + pitchStepDeg * 0.5;
49833
+ const meta2 = {
49834
+ ...spurMeta,
49835
+ kind: "sector",
49836
+ teethOnFullCircle: normalized.teethOnFullCircle,
49837
+ firstTooth: normalized.firstTooth,
49838
+ toothCount: normalized.toothCount,
49839
+ activeAngleStartDeg,
49840
+ activeAngleEndDeg
49841
+ };
49842
+ const wheel = driveWheel({ body: options.body, faceWidth: normalized.faceWidth, boreDiameter: normalized.boreDiameter }).addSpurTeethBetween({
49843
+ name: "teeth",
49844
+ module: normalized.module,
49845
+ teethOnFullCircle: normalized.teethOnFullCircle,
49846
+ toothCount: normalized.toothCount,
49847
+ firstTooth: normalized.firstTooth,
49848
+ pressureAngleDeg: normalized.pressureAngleDeg,
49849
+ faceWidth: normalized.faceWidth,
49850
+ backlash: normalized.backlash,
49851
+ clearance: normalized.clearance,
49852
+ addendum: normalized.addendum,
49853
+ dedendum: normalized.dedendum,
49854
+ segmentsPerTooth: normalized.segmentsPerTooth
49855
+ }).build();
49856
+ return attachGearMeta(wheel, meta2);
49857
+ }
48383
49858
  function normalizeSideGearOptions(options) {
48384
49859
  let normalizedSpur;
48385
49860
  try {
@@ -49359,6 +50834,12 @@ function boltPattern(options) {
49359
50834
  }
49360
50835
  };
49361
50836
  }
50837
+ const gearBodies = {
50838
+ disk: gearBodyDisk,
50839
+ diskWithHub: gearBodyDiskWithHub,
50840
+ spoked: gearBodySpoked,
50841
+ fromProfile: gearBodyFromProfile
50842
+ };
49362
50843
  function thread(diameter, pitch, length4, options) {
49363
50844
  const r = diameter / 2;
49364
50845
  const depth = (options == null ? void 0 : options.depth) ?? pitch * 0.35;
@@ -49524,7 +51005,23 @@ const partLibrary = {
49524
51005
  gearRatio,
49525
51006
  rackRatio,
49526
51007
  planetaryRatio,
49527
- boltPattern
51008
+ boltPattern,
51009
+ /** Start a composable exceptional gear or drive wheel. */
51010
+ driveWheel,
51011
+ /** Read functional-region metadata from a drive wheel shape. */
51012
+ readDriveWheelMeta,
51013
+ /** Involute sector gear with teeth on only part of the pitch circle. */
51014
+ sectorGear,
51015
+ /** Gear body preset namespace: disk, diskWithHub, spoked, and fromProfile. */
51016
+ gearBodies,
51017
+ /** Solid disk/ring gear body, independent from any tooth geometry. */
51018
+ gearBodyDisk,
51019
+ /** Disk gear body with a raised center hub. */
51020
+ gearBodyDiskWithHub,
51021
+ /** Spoked gear body with an outer rim, center hub, and radial spokes. */
51022
+ gearBodySpoked,
51023
+ /** Extrude a custom 2D profile into a gear body. */
51024
+ gearBodyFromProfile
49528
51025
  };
49529
51026
  /**
49530
51027
  * @license
@@ -51849,7 +53346,7 @@ class ProductStationBuilder {
51849
53346
  this.profileValue = profileFromSketch(sketch, "custom", width, depth);
51850
53347
  return this;
51851
53348
  }
51852
- /** Stores a semantic crown amount for diagnostics and future rail solving. */
53349
+ /** Set the station crown amount for soft product-section intent. */
51853
53350
  crown(amount) {
51854
53351
  if (!Number.isFinite(amount)) throw new Error("station.crown(amount) requires a finite number");
51855
53352
  this.crownValue = amount;
@@ -53180,7 +54677,7 @@ class ProductSkinBuilder {
53180
54677
  this.stationsValue = stations.map(toStationSpec).sort((a2, b) => axisPosition(this.axisValue, a2.center) - axisPosition(this.axisValue, b.center));
53181
54678
  return this;
53182
54679
  }
53183
- /** Attach guide rails as ProductSkin IR metadata and diagnostics. */
54680
+ /** Attach named guide rails for product-skin construction and downstream surface references. */
53184
54681
  rails(rails) {
53185
54682
  this.railsValue = { ...rails };
53186
54683
  return this;
@@ -53214,7 +54711,7 @@ class ProductSkinBuilder {
53214
54711
  this.edgeLengthValue = value;
53215
54712
  return this;
53216
54713
  }
53217
- /** Records a target wall thickness; v1 keeps exterior skin lowering sampled and reports wall as a diagnostic. */
54714
+ /** Record intended wall thickness for product design metadata. Use explicit shelling when the model needs real inner-wall geometry. */
53218
54715
  wall(thickness) {
53219
54716
  if (!Number.isFinite(thickness) || thickness <= 0) throw new Error("Product.skin().wall(thickness) requires a positive finite number");
53220
54717
  this.wallValue = thickness;
@@ -56356,7 +57853,7 @@ class SurfaceMemberBuilder {
56356
57853
  this.record.features.push({ ...normalizeFeature(name, feature), type: "counterbore" });
56357
57854
  return this;
56358
57855
  }
56359
- /** Add a named anchor at a carrier surface coordinate for diagnostics, debug views, and future named-anchor joins. */
57856
+ /** Add a named anchor at a carrier surface coordinate for explicit member joins. */
56360
57857
  anchorAt(name, coordinate) {
56361
57858
  if (!name.trim()) throw new Error("SurfaceMemberBuilder.anchorAt(name, coordinate) requires a non-empty name");
56362
57859
  const explicitAnchors = this.record.spec.explicitAnchors ?? [];
@@ -65818,8 +67315,8 @@ tinf_build_bits_base(dist_bits, dist_base, 2, 1);
65818
67315
  length_bits[28] = 0;
65819
67316
  length_base[28] = 258;
65820
67317
  var tinyInflate = tinf_uncompress;
65821
- function derive(v0, v1, v2, v3, t) {
65822
- 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) * v3;
67318
+ function derive(v0, v1, v2, v32, t) {
67319
+ 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;
65823
67320
  }
65824
67321
  function BoundingBox() {
65825
67322
  this.x1 = Number.NaN;
@@ -294630,6 +296127,7 @@ function classifySdfPreviewNode(node) {
294630
296127
  case "sdf:twist":
294631
296128
  case "sdf:bend":
294632
296129
  case "sdf:repeat":
296130
+ case "sdf:circularArray":
294633
296131
  case "sdf:shell":
294634
296132
  case "sdf:onion":
294635
296133
  return classifySdfPreviewNode(node.child);
@@ -294639,10 +296137,7 @@ function classifySdfPreviewNode(node) {
294639
296137
  reason: "This SDF uses a custom JavaScript displacement function that cannot be compiled for raymarch preview."
294640
296138
  };
294641
296139
  case "sdf:surfaceDisplace":
294642
- return {
294643
- mode: "unsupported",
294644
- reason: "This SDF uses surface displacement that is not yet available in the raymarch shader."
294645
- };
296140
+ return classifySurfaceDisplacePreviewNode(node);
294646
296141
  case "sdf:spatialBlend":
294647
296142
  return {
294648
296143
  mode: "unsupported",
@@ -294668,6 +296163,79 @@ ${node.shaderUnsupportedReason}` : ""}`
294668
296163
  };
294669
296164
  }
294670
296165
  }
296166
+ function classifySurfaceDisplacePreviewNode(node) {
296167
+ if (!node.pattern) {
296168
+ return {
296169
+ mode: "unsupported",
296170
+ reason: "This SDF uses a custom JavaScript surface pattern that cannot be compiled for raymarch preview."
296171
+ };
296172
+ }
296173
+ const childResult = classifySdfPreviewNode(node.child);
296174
+ if (childResult.mode !== "raymarch") return childResult;
296175
+ const uv = analyzeShaderSurfaceUv(node.child, "p", node.uvMode);
296176
+ if (uv.mode === "triplanar") {
296177
+ return {
296178
+ mode: "unsupported",
296179
+ reason: "Typed surface displacement raymarch preview currently supports sphere, cylinder, and torus UV mappings."
296180
+ };
296181
+ }
296182
+ return { mode: "raymarch" };
296183
+ }
296184
+ function f2(value) {
296185
+ if (!Number.isFinite(value)) return "0.0";
296186
+ const text = Number(value.toPrecision(9)).toString();
296187
+ return text.includes(".") || text.includes("e") ? text : `${text}.0`;
296188
+ }
296189
+ function v3(value) {
296190
+ return `vec3(${f2(value[0])}, ${f2(value[1])}, ${f2(value[2])})`;
296191
+ }
296192
+ function analyzeShaderSurfaceUv(node, p2, override) {
296193
+ const analysis = analyzeShaderSurfaceUvNode(node, p2);
296194
+ if (!override || override === "auto") return analysis;
296195
+ if (override === "triplanar") return { mode: "triplanar" };
296196
+ if (analysis.mode === "triplanar") return analysis;
296197
+ if (override === analysis.mode) return analysis;
296198
+ if (override === "sphere" || override === "cylinder") {
296199
+ return { mode: override, localPoint: analysis.localPoint, radius: analysis.radius };
296200
+ }
296201
+ return analysis.mode === "torus" ? analysis : { mode: "triplanar" };
296202
+ }
296203
+ function analyzeShaderSurfaceUvNode(node, p2) {
296204
+ switch (node.kind) {
296205
+ case "sdf:sphere":
296206
+ return { mode: "sphere", localPoint: p2, radius: node.radius };
296207
+ case "sdf:cylinder":
296208
+ return { mode: "cylinder", localPoint: p2, radius: node.radius };
296209
+ case "sdf:torus":
296210
+ return { mode: "torus", localPoint: p2, radius: node.minorRadius, majorRadius: node.majorRadius };
296211
+ case "sdf:translate":
296212
+ return analyzeShaderSurfaceUvNode(node.child, `(${p2} - ${v3(node.offset)})`);
296213
+ case "sdf:rotate":
296214
+ return analyzeShaderSurfaceUvNode(node.child, `rotateInvEuler(${p2}, ${v3(node.degrees)})`);
296215
+ case "sdf:scale": {
296216
+ const result = analyzeShaderSurfaceUvNode(node.child, `(${p2} / ${f2(node.factor)})`);
296217
+ if (result.mode === "triplanar") return result;
296218
+ return {
296219
+ ...result,
296220
+ radius: result.radius * node.factor,
296221
+ ...result.mode === "torus" ? { majorRadius: result.majorRadius * node.factor } : {}
296222
+ };
296223
+ }
296224
+ case "sdf:shell":
296225
+ return analyzeShaderSurfaceUvNode(node.child, p2);
296226
+ case "sdf:union":
296227
+ case "sdf:smoothUnion":
296228
+ case "sdf:intersection":
296229
+ case "sdf:smoothIntersection":
296230
+ case "sdf:difference":
296231
+ case "sdf:smoothDifference":
296232
+ return node.children.length > 0 ? analyzeShaderSurfaceUvNode(node.children[0], p2) : { mode: "triplanar" };
296233
+ case "sdf:morph":
296234
+ return analyzeShaderSurfaceUvNode(node.a, p2);
296235
+ default:
296236
+ return { mode: "triplanar" };
296237
+ }
296238
+ }
294671
296239
  function describeScriptResultType(value) {
294672
296240
  var _a3, _b3;
294673
296241
  if (value == null) return String(value);
@@ -294728,7 +296296,7 @@ function mapScriptResultToScene(args) {
294728
296296
  var _a3;
294729
296297
  const objects = [];
294730
296298
  const shapeDimensions = [];
294731
- const pushShape = (shape, name, groupName, color, treePath) => {
296299
+ const pushShape = (shape, name, groupName, color, treePath, tags = []) => {
294732
296300
  const objectId = `obj-${objects.length + 1}`;
294733
296301
  objects.push({
294734
296302
  id: objectId,
@@ -294739,7 +296307,8 @@ function mapScriptResultToScene(args) {
294739
296307
  materialProps: shape.materialProps,
294740
296308
  geometryInfo: shape.geometryInfo(),
294741
296309
  groupName,
294742
- treePath: treePath && treePath.length > 0 ? [...treePath] : [name]
296310
+ treePath: treePath && treePath.length > 0 ? [...treePath] : [name],
296311
+ ...tags.length > 0 ? { tags: [...tags] } : {}
294743
296312
  });
294744
296313
  const dims = getShapeDimensions(shape);
294745
296314
  dims.forEach((dim2) => {
@@ -294763,7 +296332,7 @@ function mapScriptResultToScene(args) {
294763
296332
  });
294764
296333
  }
294765
296334
  };
294766
- const pushSketch = (sketch, name, groupName, treePath) => {
296335
+ const pushSketch = (sketch, name, groupName, treePath, tags = []) => {
294767
296336
  const meta2 = sketch instanceof ConstraintSketch ? sketch.constraintMeta : void 0;
294768
296337
  objects.push({
294769
296338
  id: `obj-${objects.length + 1}`,
@@ -294774,10 +296343,11 @@ function mapScriptResultToScene(args) {
294774
296343
  sketchMeta: meta2,
294775
296344
  color: sketch.colorHex,
294776
296345
  groupName,
294777
- treePath: treePath && treePath.length > 0 ? [...treePath] : [name]
296346
+ treePath: treePath && treePath.length > 0 ? [...treePath] : [name],
296347
+ ...tags.length > 0 ? { tags: [...tags] } : {}
294778
296348
  });
294779
296349
  };
294780
- const pushSdf = (sdfShape, name, groupName, treePath, color) => {
296350
+ const pushSdf = (sdfShape, name, groupName, treePath, color, tags = []) => {
294781
296351
  const preview = classifySdfPreviewNode(sdfShape._node);
294782
296352
  const displayColor = color || sdfShape.colorHex;
294783
296353
  const data = {
@@ -294797,7 +296367,8 @@ function mapScriptResultToScene(args) {
294797
296367
  materialProps: sdfShape.materialProps,
294798
296368
  geometryInfo: null,
294799
296369
  groupName,
294800
- treePath: treePath && treePath.length > 0 ? [...treePath] : [name]
296370
+ treePath: treePath && treePath.length > 0 ? [...treePath] : [name],
296371
+ ...tags.length > 0 ? { tags: [...tags] } : {}
294801
296372
  });
294802
296373
  };
294803
296374
  const isNamedObject = (item) => {
@@ -294814,18 +296385,24 @@ function mapScriptResultToScene(args) {
294814
296385
  const rootGroupChildLabel = (grp, index2) => {
294815
296386
  return shapeGroupChildSegment(grp, index2, true);
294816
296387
  };
294817
- const flattenGroupChild = (child, label, groupName, treePath) => {
296388
+ const flattenGroupChild = (child, label, groupName, treePath, tags = []) => {
294818
296389
  const resolvedTreePath = treePath && treePath.length > 0 ? treePath : [label];
294819
296390
  if (child instanceof ShapeGroup) {
294820
296391
  child.children.forEach((nested, i) => {
294821
- flattenGroupChild(nested, groupChildLabel(child, label, i), groupName, [...resolvedTreePath, shapeGroupChildSegment(child, i)]);
296392
+ flattenGroupChild(
296393
+ nested,
296394
+ groupChildLabel(child, label, i),
296395
+ groupName,
296396
+ [...resolvedTreePath, shapeGroupChildSegment(child, i)],
296397
+ mergeSceneTags(tags, child.tagsForChild(i))
296398
+ );
294822
296399
  });
294823
296400
  return;
294824
296401
  }
294825
296402
  if (child instanceof Shape) {
294826
- pushShape(child, label, groupName, void 0, resolvedTreePath);
296403
+ pushShape(child, label, groupName, void 0, resolvedTreePath, tags);
294827
296404
  } else if (child instanceof Sketch) {
294828
- pushSketch(child, label, groupName, resolvedTreePath);
296405
+ pushSketch(child, label, groupName, resolvedTreePath, tags);
294829
296406
  }
294830
296407
  };
294831
296408
  const isPlainObject2 = (value) => {
@@ -294834,34 +296411,40 @@ function mapScriptResultToScene(args) {
294834
296411
  return proto2 === Object.prototype || proto2 === null;
294835
296412
  };
294836
296413
  const joinName = (path2) => path2.join(".");
294837
- const processRenderableTree = (value, fallbackLabel, fallbackSegment, parentGroup, parentTreePath = [], seen = /* @__PURE__ */ new WeakSet()) => {
296414
+ const processRenderableTree = (value, fallbackLabel, fallbackSegment, parentGroup, parentTreePath = [], inheritedTags = [], seen = /* @__PURE__ */ new WeakSet()) => {
294838
296415
  const segment = fallbackSegment.trim().length > 0 ? fallbackSegment : fallbackLabel;
294839
296416
  const treePath = [...parentTreePath, segment];
294840
296417
  const name = joinName(treePath) || fallbackLabel;
294841
296418
  if (value instanceof Assembly) {
294842
- value.solve().toSceneObjects().forEach((item, index2) => processNamedItem(item, `${name}.${index2 + 1}`, `${index2 + 1}`, name, treePath));
296419
+ value.solve().toSceneObjects().forEach((item, index2) => processNamedItem(item, `${name}.${index2 + 1}`, `${index2 + 1}`, name, treePath, inheritedTags));
294843
296420
  return;
294844
296421
  }
294845
296422
  if (value instanceof SolvedAssembly) {
294846
- value.toSceneObjects().forEach((item, index2) => processNamedItem(item, `${name}.${index2 + 1}`, `${index2 + 1}`, name, treePath));
296423
+ value.toSceneObjects().forEach((item, index2) => processNamedItem(item, `${name}.${index2 + 1}`, `${index2 + 1}`, name, treePath, inheritedTags));
294847
296424
  return;
294848
296425
  }
294849
296426
  if (value instanceof ShapeGroup) {
294850
296427
  value.children.forEach((child, i) => {
294851
- flattenGroupChild(child, groupChildLabel(value, name, i), parentGroup, [...treePath, shapeGroupChildSegment(value, i)]);
296428
+ flattenGroupChild(
296429
+ child,
296430
+ groupChildLabel(value, name, i),
296431
+ parentGroup,
296432
+ [...treePath, shapeGroupChildSegment(value, i)],
296433
+ mergeSceneTags(inheritedTags, value.tagsForChild(i))
296434
+ );
294852
296435
  });
294853
296436
  return;
294854
296437
  }
294855
296438
  if (value instanceof Shape) {
294856
- pushShape(value, name, parentGroup, void 0, treePath);
296439
+ pushShape(value, name, parentGroup, void 0, treePath, inheritedTags);
294857
296440
  return;
294858
296441
  }
294859
296442
  if (value instanceof Sketch) {
294860
- pushSketch(value, name, parentGroup, treePath);
296443
+ pushSketch(value, name, parentGroup, treePath, inheritedTags);
294861
296444
  return;
294862
296445
  }
294863
296446
  if (value instanceof SdfShape) {
294864
- pushSdf(value, name, parentGroup, treePath);
296447
+ pushSdf(value, name, parentGroup, treePath, void 0, inheritedTags);
294865
296448
  return;
294866
296449
  }
294867
296450
  if (value instanceof GCodeBuilder) {
@@ -294872,7 +296455,8 @@ function mapScriptResultToScene(args) {
294872
296455
  sketch: null,
294873
296456
  toolpath: value.build(),
294874
296457
  geometryInfo: null,
294875
- treePath
296458
+ treePath,
296459
+ ...inheritedTags.length > 0 ? { tags: [...inheritedTags] } : {}
294876
296460
  });
294877
296461
  return;
294878
296462
  }
@@ -294882,30 +296466,38 @@ function mapScriptResultToScene(args) {
294882
296466
  value.forEach((item, index2) => {
294883
296467
  const childSegment = `${index2 + 1}`;
294884
296468
  const childLabel = `${name}.${childSegment}`;
294885
- processRenderableTree(item, childLabel, childSegment, parentGroup, treePath, seen);
296469
+ processRenderableTree(item, childLabel, childSegment, parentGroup, treePath, inheritedTags, seen);
294886
296470
  });
294887
296471
  return;
294888
296472
  }
294889
296473
  if (isNamedObject(value)) {
294890
- processNamedItem(value, fallbackLabel, fallbackSegment, parentGroup, parentTreePath);
296474
+ processNamedItem(value, fallbackLabel, fallbackSegment, parentGroup, parentTreePath, inheritedTags);
294891
296475
  return;
294892
296476
  }
294893
296477
  if (isPlainObject2(value)) {
294894
296478
  if (seen.has(value)) return;
294895
296479
  seen.add(value);
294896
296480
  Object.entries(value).forEach(([key, entry]) => {
294897
- processRenderableTree(entry, key, key, parentGroup, treePath, seen);
296481
+ processRenderableTree(entry, key, key, parentGroup, treePath, inheritedTags, seen);
294898
296482
  });
294899
296483
  }
294900
296484
  };
294901
- const processNamedItem = (item, fallbackLabel, fallbackSegment, parentGroup, parentTreePath = []) => {
296485
+ const processNamedItem = (item, fallbackLabel, fallbackSegment, parentGroup, parentTreePath = [], inheritedTags = []) => {
296486
+ var _a4;
294902
296487
  const name = typeof item.name === "string" && item.name.trim().length > 0 ? item.name : fallbackLabel;
294903
296488
  const localSegment = typeof item.name === "string" && item.name.trim().length > 0 ? item.name : fallbackSegment;
294904
296489
  const treePath = [...parentTreePath, localSegment];
294905
296490
  const grp = parentGroup;
296491
+ const tags = mergeSceneTags(inheritedTags, (_a4 = item.metadata) == null ? void 0 : _a4.tags, item.tags);
294906
296492
  if (item.group instanceof ShapeGroup) {
294907
296493
  item.group.children.forEach((child, i) => {
294908
- flattenGroupChild(child, groupChildLabel(item.group, name, i), name, [...treePath, shapeGroupChildSegment(item.group, i)]);
296494
+ flattenGroupChild(
296495
+ child,
296496
+ groupChildLabel(item.group, name, i),
296497
+ name,
296498
+ [...treePath, shapeGroupChildSegment(item.group, i)],
296499
+ mergeSceneTags(tags, item.group.tagsForChild(i))
296500
+ );
294909
296501
  });
294910
296502
  return;
294911
296503
  }
@@ -294915,39 +296507,48 @@ function mapScriptResultToScene(args) {
294915
296507
  const childTreePath = [...treePath, `${i + 1}`];
294916
296508
  if (child instanceof ShapeGroup) {
294917
296509
  child.children.forEach((nested, nestedIndex) => {
294918
- flattenGroupChild(nested, groupChildLabel(child, name, nestedIndex), name, [
294919
- ...treePath,
294920
- shapeGroupChildSegment(child, nestedIndex)
294921
- ]);
296510
+ flattenGroupChild(
296511
+ nested,
296512
+ groupChildLabel(child, name, nestedIndex),
296513
+ name,
296514
+ [...treePath, shapeGroupChildSegment(child, nestedIndex)],
296515
+ mergeSceneTags(tags, child.tagsForChild(nestedIndex))
296516
+ );
294922
296517
  });
294923
296518
  } else if (child instanceof Shape) {
294924
- pushShape(child, childLabel, name, void 0, childTreePath);
296519
+ pushShape(child, childLabel, name, void 0, childTreePath, tags);
294925
296520
  } else if (child instanceof Sketch) {
294926
- pushSketch(child, childLabel, name, childTreePath);
296521
+ pushSketch(child, childLabel, name, childTreePath, tags);
294927
296522
  } else if (child instanceof SdfShape) {
294928
- pushSdf(child, childLabel, name, childTreePath);
296523
+ pushSdf(child, childLabel, name, childTreePath, void 0, tags);
294929
296524
  } else if (isNamedObject(child)) {
294930
- processNamedItem(child, childLabel, `${i + 1}`, name, treePath);
296525
+ processNamedItem(child, childLabel, `${i + 1}`, name, treePath, tags);
294931
296526
  }
294932
296527
  });
294933
296528
  return;
294934
296529
  }
294935
296530
  if (item.shape instanceof ShapeGroup) {
294936
296531
  item.shape.children.forEach(
294937
- (child, i) => flattenGroupChild(child, groupChildLabel(item.shape, name, i), name, [...treePath, shapeGroupChildSegment(item.shape, i)])
296532
+ (child, i) => flattenGroupChild(
296533
+ child,
296534
+ groupChildLabel(item.shape, name, i),
296535
+ name,
296536
+ [...treePath, shapeGroupChildSegment(item.shape, i)],
296537
+ mergeSceneTags(tags, item.shape.tagsForChild(i))
296538
+ )
294938
296539
  );
294939
296540
  return;
294940
296541
  }
294941
296542
  if (item.shape instanceof Shape) {
294942
- pushShape(item.shape, name, grp, item.color, treePath);
296543
+ pushShape(item.shape, name, grp, item.color, treePath, tags);
294943
296544
  return;
294944
296545
  }
294945
296546
  if (item.shape instanceof SdfShape) {
294946
- pushSdf(item.shape, name, grp, treePath, item.color);
296547
+ pushSdf(item.shape, name, grp, treePath, item.color, tags);
294947
296548
  return;
294948
296549
  }
294949
296550
  if (item.sdf instanceof SdfShape) {
294950
- pushSdf(item.sdf, name, grp, treePath, item.color);
296551
+ pushSdf(item.sdf, name, grp, treePath, item.color, tags);
294951
296552
  return;
294952
296553
  }
294953
296554
  if (item.sketch instanceof Sketch) {
@@ -294961,7 +296562,8 @@ function mapScriptResultToScene(args) {
294961
296562
  sketchMeta: meta2,
294962
296563
  color: item.color || item.sketch.colorHex,
294963
296564
  groupName: grp,
294964
- treePath
296565
+ treePath,
296566
+ ...tags.length > 0 ? { tags: [...tags] } : {}
294965
296567
  });
294966
296568
  }
294967
296569
  };
@@ -294975,14 +296577,20 @@ function mapScriptResultToScene(args) {
294975
296577
  } else if (result instanceof ShapeGroup) {
294976
296578
  result.children.forEach((child, i) => {
294977
296579
  const label = rootGroupChildLabel(result, i);
294978
- flattenGroupChild(child, label, void 0, [label]);
296580
+ flattenGroupChild(child, label, void 0, [label], result.tagsForChild(i));
294979
296581
  });
294980
296582
  } else if (Array.isArray(result)) {
294981
296583
  result.forEach((item, index2) => {
294982
296584
  const label = `Object ${index2 + 1}`;
294983
296585
  if (item instanceof ShapeGroup) {
294984
296586
  item.children.forEach((child, i) => {
294985
- flattenGroupChild(child, groupChildLabel(item, label, i), void 0, [label, shapeGroupChildSegment(item, i)]);
296587
+ flattenGroupChild(
296588
+ child,
296589
+ groupChildLabel(item, label, i),
296590
+ void 0,
296591
+ [label, shapeGroupChildSegment(item, i)],
296592
+ item.tagsForChild(i)
296593
+ );
294986
296594
  });
294987
296595
  return;
294988
296596
  }
@@ -295015,7 +296623,7 @@ function mapScriptResultToScene(args) {
295015
296623
  } else if (defaultValue instanceof ShapeGroup) {
295016
296624
  defaultValue.children.forEach((child, i) => {
295017
296625
  const label = rootGroupChildLabel(defaultValue, i);
295018
- flattenGroupChild(child, label, void 0, [label]);
296626
+ flattenGroupChild(child, label, void 0, [label], defaultValue.tagsForChild(i));
295019
296627
  });
295020
296628
  } else if (defaultValue instanceof Shape) {
295021
296629
  pushShape(defaultValue, args.fileName, void 0, void 0, [args.fileName]);
@@ -295061,7 +296669,8 @@ function mapScriptResultToScene(args) {
295061
296669
  name: `${mock2.name} (mock)`,
295062
296670
  shape: mock2.shape,
295063
296671
  sketch: null,
295064
- mock: true
296672
+ mock: true,
296673
+ tags: ["mock"]
295065
296674
  });
295066
296675
  }
295067
296676
  const hasSdfLeaves = objects.some((obj) => obj.sdf);
@@ -296189,7 +297798,8 @@ function serializeRunResult(result, solverDebug = null) {
296189
297798
  geometryInfo: obj.geometryInfo,
296190
297799
  sketchMeta: obj.sketchMeta,
296191
297800
  groupName: obj.groupName,
296192
- treePath: obj.treePath
297801
+ treePath: obj.treePath,
297802
+ tags: obj.tags
296193
297803
  };
296194
297804
  if (serialized2.shapeData) {
296195
297805
  transferables.push(
@@ -296235,6 +297845,7 @@ function serializeRunMetadata(result) {
296235
297845
  sketchMeta: obj.sketchMeta,
296236
297846
  groupName: obj.groupName,
296237
297847
  treePath: obj.treePath,
297848
+ tags: obj.tags,
296238
297849
  mock: obj.mock,
296239
297850
  serverShapeRef: obj.serverShapeRef,
296240
297851
  exactState: obj.exactState