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 flrm = /* @__PURE__ */ hMap(flt, 9, 1);
4648
4692
  var 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
@@ -5125,8 +5169,8 @@ function parse3mf(data) {
5125
5169
  while ((tMatch = trianglePattern.exec(meshXml)) !== null) {
5126
5170
  const v1 = parseInt(tMatch[1], 10) + vertexOffset;
5127
5171
  const v2 = parseInt(tMatch[2], 10) + vertexOffset;
5128
- const v3 = parseInt(tMatch[3], 10) + vertexOffset;
5129
- allTriIndices.push(v1, v2, v3);
5172
+ const v32 = parseInt(tMatch[3], 10) + vertexOffset;
5173
+ allTriIndices.push(v1, v2, v32);
5130
5174
  }
5131
5175
  for (let i = 0; i < meshVerts.length; i++) {
5132
5176
  allPositions.push(meshVerts[i]);
@@ -5509,6 +5553,287 @@ function lowerShellShapeCompilePlanToConcretePlan(plan) {
5509
5553
  }
5510
5554
  return lowerBaseShellPlanToConcretePlan(plan.base, plan.thickness, normalizeShellOpenFaces(plan.openFaces));
5511
5555
  }
5556
+ const DEFAULT_MAX_GRID_POINTS = 8e6;
5557
+ const DEFAULT_MIN_EDGE_LENGTH = 0.15;
5558
+ function resolveSdfMeshingSettings(tree, bounds, options = {}) {
5559
+ const quality = options.quality ?? "preview";
5560
+ const minEdgeLength = positiveOrDefault(options.minEdgeLength, DEFAULT_MIN_EDGE_LENGTH);
5561
+ const maxGridPoints = positiveOrDefault(options.maxGridPoints, DEFAULT_MAX_GRID_POINTS);
5562
+ const tolerance = options.tolerance !== void 0 ? requirePositiveFinite$3(options.tolerance, "SDF tolerance") : void 0;
5563
+ const minFeatureSize = options.minFeatureSize !== void 0 ? requirePositiveFinite$3(options.minFeatureSize, "SDF minFeatureSize") : void 0;
5564
+ const maxTriangles = options.maxTriangles !== void 0 ? Math.floor(requirePositiveFinite$3(options.maxTriangles, "SDF maxTriangles")) : void 0;
5565
+ const analysis = analyzeSdfTree(tree);
5566
+ const warnings = [];
5567
+ let edgeLength2;
5568
+ if (options.edgeLength !== void 0) {
5569
+ edgeLength2 = requirePositiveFinite$3(options.edgeLength, "SDF edgeLength");
5570
+ if (edgeLength2 < minEdgeLength) {
5571
+ warnings.push(`edgeLength ${formatMm(edgeLength2)} was clamped to minimum ${formatMm(minEdgeLength)}.`);
5572
+ edgeLength2 = minEdgeLength;
5573
+ }
5574
+ } else {
5575
+ edgeLength2 = resolveDefaultEdgeLength(bounds, quality, minEdgeLength, analysis, options);
5576
+ }
5577
+ if (analysis.minWallThickness < Infinity && analysis.minWallThickness < edgeLength2 * 2) {
5578
+ analysis.riskFlags.add("thin-shell");
5579
+ warnings.push(
5580
+ `shell/wall thickness ${formatMm(analysis.minWallThickness)} is below 2 x edgeLength ${formatMm(edgeLength2)}; thin features may be under-sampled.`
5581
+ );
5582
+ }
5583
+ if (!options.bounds && analysis.hasInfiniteRepeat) {
5584
+ warnings.push("infinite repeat bounds are heuristic; pass .toShape({ bounds }) for predictable clipping.");
5585
+ }
5586
+ if (!options.bounds && analysis.riskFlags.has("noise")) {
5587
+ warnings.push("noise field bounds are heuristic; pass .toShape({ bounds }) for predictable clipping.");
5588
+ }
5589
+ if (!options.bounds && (analysis.riskFlags.has("tpms") || analysis.riskFlags.has("voronoi"))) {
5590
+ warnings.push("TPMS/Voronoi bounds are heuristic unless clipped or passed explicitly.");
5591
+ }
5592
+ if (analysis.hasLegacyTpmsThreshold) {
5593
+ warnings.push("TPMS thickness is using legacy field-threshold units; use wallThickness for approximate millimeters.");
5594
+ }
5595
+ return {
5596
+ quality,
5597
+ edgeLength: edgeLength2,
5598
+ tolerance,
5599
+ minFeatureSize,
5600
+ minEdgeLength,
5601
+ simplify: resolveSimplificationMode(options.simplify, quality, analysis.riskFlags),
5602
+ maxTriangles,
5603
+ maxGridPoints,
5604
+ diagnostics: options.diagnostics === true,
5605
+ treeRiskFlags: [...analysis.riskFlags].sort(),
5606
+ warnings
5607
+ };
5608
+ }
5609
+ function withScaledSdfEdgeLength(settings, edgeLength2) {
5610
+ return { ...settings, edgeLength: Math.max(settings.minEdgeLength, edgeLength2) };
5611
+ }
5612
+ function createSdfMeshingDiagnostics(settings, bounds, paddedBounds) {
5613
+ const grid = estimateSdfGridDimensions(paddedBounds, settings.edgeLength);
5614
+ const estimatedSamples = grid[0] * grid[1] * grid[2];
5615
+ return {
5616
+ bounds: cloneBounds$2(bounds),
5617
+ paddedBounds: cloneBounds$2(paddedBounds),
5618
+ edgeLength: settings.edgeLength,
5619
+ grid,
5620
+ estimatedSamples,
5621
+ estimatedMemoryBytes: estimatedSamples * 8,
5622
+ treeRiskFlags: [...settings.treeRiskFlags],
5623
+ simplification: settings.simplify,
5624
+ capMode: "box",
5625
+ capInset: settings.edgeLength,
5626
+ warnings: [...settings.warnings]
5627
+ };
5628
+ }
5629
+ function assertSdfMeshingBudget(diagnostics, maxGridPoints) {
5630
+ if (diagnostics.estimatedSamples <= maxGridPoints) return;
5631
+ const suggestedEdge = suggestEdgeLengthForSampleBudget(diagnostics.paddedBounds, maxGridPoints);
5632
+ throw new Error(
5633
+ `SDF meshing would sample ${formatCount(diagnostics.estimatedSamples)} grid points (~${formatBytes(
5634
+ diagnostics.estimatedMemoryBytes
5635
+ )}). Reduce bounds or use edgeLength >= ${formatMm(suggestedEdge)}.`
5636
+ );
5637
+ }
5638
+ function estimateSdfGridDimensions(bounds, edgeLength2) {
5639
+ const dx = bounds.max[0] - bounds.min[0];
5640
+ const dy = bounds.max[1] - bounds.min[1];
5641
+ const dz = bounds.max[2] - bounds.min[2];
5642
+ return [
5643
+ Math.max(2, Math.ceil(dx / edgeLength2) + 1),
5644
+ Math.max(2, Math.ceil(dy / edgeLength2) + 1),
5645
+ Math.max(2, Math.ceil(dz / edgeLength2) + 1)
5646
+ ];
5647
+ }
5648
+ function logSdfMeshingDiagnostics(prefix, diagnostics) {
5649
+ const warnings = diagnostics.warnings.length > 0 ? `, warnings=${diagnostics.warnings.join(" | ")}` : "";
5650
+ const evaluator = diagnostics.evaluator ? `, evaluator=${diagnostics.evaluator}${diagnostics.evaluatorUnsupportedReason ? ` (${diagnostics.evaluatorUnsupportedReason})` : ""}` : "";
5651
+ console.info(
5652
+ `${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}`
5653
+ );
5654
+ }
5655
+ function resolveDefaultEdgeLength(bounds, quality, minEdgeLength, analysis, options) {
5656
+ const dx = bounds.max[0] - bounds.min[0];
5657
+ const dy = bounds.max[1] - bounds.min[1];
5658
+ const dz = bounds.max[2] - bounds.min[2];
5659
+ const maxDim = Math.max(dx, dy, dz, minEdgeLength);
5660
+ const divisor = quality === "draft" ? 60 : quality === "export" ? 160 : 100;
5661
+ const candidates = [maxDim / divisor];
5662
+ if (options.tolerance !== void 0) candidates.push(requirePositiveFinite$3(options.tolerance, "SDF tolerance") * 2);
5663
+ if (options.minFeatureSize !== void 0) candidates.push(requirePositiveFinite$3(options.minFeatureSize, "SDF minFeatureSize") / 2.5);
5664
+ if (analysis.minMetricTpmsThickness < Infinity) candidates.push(analysis.minMetricTpmsThickness / 2);
5665
+ if (analysis.minTpmsCellSize < Infinity) candidates.push(analysis.minTpmsCellSize / 10);
5666
+ if (analysis.minRepeatSpacing < Infinity) candidates.push(analysis.minRepeatSpacing / 8);
5667
+ if (analysis.minWallThickness < Infinity) candidates.push(analysis.minWallThickness / 2.5);
5668
+ return Math.max(minEdgeLength, Math.min(...candidates.filter((v) => Number.isFinite(v) && v > 0)));
5669
+ }
5670
+ function resolveSimplificationMode(simplify, quality, riskFlags) {
5671
+ if (simplify === false) return "off";
5672
+ if (simplify === true || simplify === "safe") return "safe";
5673
+ if (quality === "export" && riskFlags.size > 0) return "off";
5674
+ return "safe";
5675
+ }
5676
+ function analyzeSdfTree(tree) {
5677
+ const analysis = {
5678
+ riskFlags: /* @__PURE__ */ new Set(),
5679
+ minTpmsCellSize: Infinity,
5680
+ minMetricTpmsThickness: Infinity,
5681
+ minRepeatSpacing: Infinity,
5682
+ minWallThickness: Infinity,
5683
+ hasInfiniteRepeat: false,
5684
+ hasLegacyTpmsThreshold: false
5685
+ };
5686
+ visitSdfNode(tree, analysis);
5687
+ return analysis;
5688
+ }
5689
+ function minPositive(...values) {
5690
+ let result = Infinity;
5691
+ for (const value of values) {
5692
+ if (value !== null && value !== void 0 && Number.isFinite(value) && value > 0) {
5693
+ result = Math.min(result, value);
5694
+ }
5695
+ }
5696
+ return result === Infinity ? null : result;
5697
+ }
5698
+ function estimateSurfacePatternSpacing(pattern) {
5699
+ switch (pattern.kind) {
5700
+ case "surfacePattern:constant":
5701
+ return null;
5702
+ case "surfacePattern:sineWave":
5703
+ return pattern.wavelength;
5704
+ case "surfacePattern:stripes":
5705
+ return Math.min(pattern.spacing, pattern.width);
5706
+ case "surfacePattern:overUnderWeave":
5707
+ return Math.min(...pattern.spacing, ...pattern.threadWidth);
5708
+ case "surfacePattern:abs":
5709
+ case "surfacePattern:negate":
5710
+ return estimateSurfacePatternSpacing(pattern.child);
5711
+ case "surfacePattern:add":
5712
+ case "surfacePattern:multiply":
5713
+ case "surfacePattern:min":
5714
+ case "surfacePattern:max":
5715
+ return minPositive(...pattern.children.map(estimateSurfacePatternSpacing));
5716
+ case "surfacePattern:clamp":
5717
+ return estimateSurfacePatternSpacing(pattern.child);
5718
+ }
5719
+ }
5720
+ function visitSdfNode(node, analysis) {
5721
+ switch (node.kind) {
5722
+ case "sdf:union":
5723
+ case "sdf:difference":
5724
+ case "sdf:intersection":
5725
+ case "sdf:smoothUnion":
5726
+ case "sdf:smoothDifference":
5727
+ case "sdf:smoothIntersection":
5728
+ for (const child of node.children) visitSdfNode(child, analysis);
5729
+ break;
5730
+ case "sdf:morph":
5731
+ case "sdf:spatialBlend":
5732
+ visitSdfNode(node.a, analysis);
5733
+ visitSdfNode(node.b, analysis);
5734
+ break;
5735
+ case "sdf:translate":
5736
+ case "sdf:rotate":
5737
+ case "sdf:scale":
5738
+ case "sdf:twist":
5739
+ case "sdf:bend":
5740
+ case "sdf:onion":
5741
+ visitSdfNode(node.child, analysis);
5742
+ break;
5743
+ case "sdf:repeat":
5744
+ analysis.riskFlags.add("repeat");
5745
+ for (let i = 0; i < 3; i++) {
5746
+ const spacing = node.spacing[i];
5747
+ if (spacing > 0) {
5748
+ analysis.minRepeatSpacing = Math.min(analysis.minRepeatSpacing, spacing);
5749
+ if (node.count[i] <= 0) analysis.hasInfiniteRepeat = true;
5750
+ }
5751
+ }
5752
+ visitSdfNode(node.child, analysis);
5753
+ break;
5754
+ case "sdf:circularArray": {
5755
+ analysis.riskFlags.add("repeat");
5756
+ if (node.offset > 0) {
5757
+ analysis.minRepeatSpacing = Math.min(analysis.minRepeatSpacing, 2 * Math.PI * node.offset / node.count);
5758
+ }
5759
+ visitSdfNode(node.child, analysis);
5760
+ break;
5761
+ }
5762
+ case "sdf:shell":
5763
+ analysis.minWallThickness = Math.min(analysis.minWallThickness, node.thickness);
5764
+ visitSdfNode(node.child, analysis);
5765
+ break;
5766
+ case "sdf:displace":
5767
+ case "sdf:surfaceDisplace":
5768
+ analysis.riskFlags.add("displacement");
5769
+ if (node.kind === "sdf:surfaceDisplace" && node.pattern) {
5770
+ const spacing = estimateSurfacePatternSpacing(node.pattern);
5771
+ if (spacing !== null) analysis.minRepeatSpacing = Math.min(analysis.minRepeatSpacing, spacing);
5772
+ }
5773
+ visitSdfNode(node.child, analysis);
5774
+ break;
5775
+ case "sdf:gyroid":
5776
+ case "sdf:schwarzP":
5777
+ case "sdf:diamond":
5778
+ case "sdf:lidinoid":
5779
+ analysis.riskFlags.add("tpms");
5780
+ analysis.minTpmsCellSize = Math.min(analysis.minTpmsCellSize, node.cellSize);
5781
+ if (node.thicknessMode === "metric-approx") {
5782
+ analysis.minMetricTpmsThickness = Math.min(analysis.minMetricTpmsThickness, node.thickness);
5783
+ analysis.minWallThickness = Math.min(analysis.minWallThickness, node.thickness);
5784
+ } else {
5785
+ analysis.hasLegacyTpmsThreshold = true;
5786
+ }
5787
+ break;
5788
+ case "sdf:noise":
5789
+ analysis.riskFlags.add("noise");
5790
+ break;
5791
+ case "sdf:voronoi":
5792
+ analysis.riskFlags.add("voronoi");
5793
+ analysis.minWallThickness = Math.min(analysis.minWallThickness, node.wallThickness);
5794
+ if (node.surfaceChild) visitSdfNode(node.surfaceChild, analysis);
5795
+ break;
5796
+ case "sdf:custom":
5797
+ analysis.riskFlags.add("custom");
5798
+ break;
5799
+ }
5800
+ }
5801
+ function positiveOrDefault(value, fallback) {
5802
+ if (value === void 0) return fallback;
5803
+ return requirePositiveFinite$3(value, "SDF meshing option");
5804
+ }
5805
+ function requirePositiveFinite$3(value, name) {
5806
+ if (!Number.isFinite(value) || value <= 0) {
5807
+ throw new Error(`${name} must be a positive finite number.`);
5808
+ }
5809
+ return value;
5810
+ }
5811
+ function cloneBounds$2(bounds) {
5812
+ return { min: [...bounds.min], max: [...bounds.max] };
5813
+ }
5814
+ function suggestEdgeLengthForSampleBudget(bounds, maxGridPoints) {
5815
+ const dx = bounds.max[0] - bounds.min[0];
5816
+ const dy = bounds.max[1] - bounds.min[1];
5817
+ const dz = bounds.max[2] - bounds.min[2];
5818
+ const volume = Math.max(dx * dy * dz, 1);
5819
+ return Math.cbrt(volume / Math.max(maxGridPoints, 8));
5820
+ }
5821
+ function formatBounds(bounds) {
5822
+ return `[${bounds.min.map(formatNumber$1).join(",")}]-[${bounds.max.map(formatNumber$1).join(",")}]`;
5823
+ }
5824
+ function formatMm(value) {
5825
+ return `${formatNumber$1(value)}mm`;
5826
+ }
5827
+ function formatNumber$1(value) {
5828
+ return Number.isInteger(value) ? String(value) : value.toFixed(3).replace(/0+$/, "").replace(/\.$/, "");
5829
+ }
5830
+ function formatCount(value) {
5831
+ return Math.round(value).toLocaleString("en-US");
5832
+ }
5833
+ function formatBytes(bytes) {
5834
+ if (bytes < 1024 * 1024) return `${Math.ceil(bytes / 1024)} KB`;
5835
+ return `${Math.ceil(bytes / (1024 * 1024))} MB`;
5836
+ }
5512
5837
  const grad3 = new Float64Array([
5513
5838
  1,
5514
5839
  1,
@@ -5969,8 +6294,8 @@ function triplanarWeights(nx, ny, nz, sharpness) {
5969
6294
  const inv = 1 / sum2;
5970
6295
  return { wx: wx * inv, wy: wy * inv, wz: wz * inv };
5971
6296
  }
5972
- const { atan2, acos, cos: cos$2, sin: sin$2, sqrt: sqrt$2, PI: PI$2 } = Math;
5973
- const DEG$2 = PI$2 / 180;
6297
+ const { atan2, acos, cos: cos$3, sin: sin$3, sqrt: sqrt$3, PI: PI$3 } = Math;
6298
+ const DEG$3 = PI$3 / 180;
5974
6299
  const IDENTITY = (p2) => p2;
5975
6300
  function analyzeUV(node, override) {
5976
6301
  if (override) {
@@ -5997,10 +6322,10 @@ function analyzeNodeUV(node, toLocal) {
5997
6322
  return analyzeNodeUV(node.child, next);
5998
6323
  }
5999
6324
  case "sdf:rotate": {
6000
- const [rx, ry, rz] = node.degrees.map((d2) => d2 * DEG$2);
6001
- const cx = cos$2(rx), sx = sin$2(rx);
6002
- const cy = cos$2(ry), sy = sin$2(ry);
6003
- const cz = cos$2(rz), sz = sin$2(rz);
6325
+ const [rx, ry, rz] = node.degrees.map((d2) => d2 * DEG$3);
6326
+ const cx = cos$3(rx), sx = sin$3(rx);
6327
+ const cy = cos$3(ry), sy = sin$3(ry);
6328
+ const cz = cos$3(rz), sz = sin$3(rz);
6004
6329
  const prev = toLocal;
6005
6330
  const next = (p2) => {
6006
6331
  const pp = prev(p2);
@@ -6057,7 +6382,7 @@ function compileUVFunction(analysis) {
6057
6382
  return (p2) => {
6058
6383
  const lp = toLocal(p2);
6059
6384
  const u2 = atan2(lp[1], lp[0]) * R;
6060
- const len2 = sqrt$2(lp[0] * lp[0] + lp[1] * lp[1] + lp[2] * lp[2]);
6385
+ const len2 = sqrt$3(lp[0] * lp[0] + lp[1] * lp[1] + lp[2] * lp[2]);
6061
6386
  const v = acos(clampUnit(lp[2] / (len2 || 1))) * R;
6062
6387
  return [u2, v];
6063
6388
  };
@@ -6077,23 +6402,23 @@ function compileUVFunction(analysis) {
6077
6402
  return (p2) => {
6078
6403
  const lp = toLocal(p2);
6079
6404
  const u2 = atan2(lp[1], lp[0]) * R;
6080
- const xyDist = sqrt$2(lp[0] * lp[0] + lp[1] * lp[1]) - R;
6405
+ const xyDist = sqrt$3(lp[0] * lp[0] + lp[1] * lp[1]) - R;
6081
6406
  const v = atan2(lp[2], xyDist) * r;
6082
6407
  return [u2, v];
6083
6408
  };
6084
6409
  }
6085
6410
  }
6086
6411
  }
6087
- const { abs: abs$1, cos: cos$1, sin: sin$1, sqrt: sqrt$1, PI: PI$1 } = Math;
6088
- const TAU = 2 * PI$1;
6089
- const GRAD_EPS = 1e-9;
6412
+ const { abs: abs$1, cos: cos$2, sin: sin$2, sqrt: sqrt$2, PI: PI$2 } = Math;
6413
+ const TAU$1 = 2 * PI$2;
6414
+ const GRAD_EPS$1 = 1e-9;
6090
6415
  function gyroidValueAndGradient(x2, y2, z2, cellSize) {
6091
- const s = TAU / cellSize;
6416
+ const s = TAU$1 / cellSize;
6092
6417
  const xs = x2 * s;
6093
6418
  const ys = y2 * s;
6094
6419
  const zs = z2 * s;
6095
- const sx = sin$1(xs), sy = sin$1(ys), sz = sin$1(zs);
6096
- const cx = cos$1(xs), cy = cos$1(ys), cz = cos$1(zs);
6420
+ const sx = sin$2(xs), sy = sin$2(ys), sz = sin$2(zs);
6421
+ const cx = cos$2(xs), cy = cos$2(ys), cz = cos$2(zs);
6097
6422
  return {
6098
6423
  value: sx * cy + sy * cz + sz * cx,
6099
6424
  gx: s * (cx * cy - sz * sx),
@@ -6102,24 +6427,24 @@ function gyroidValueAndGradient(x2, y2, z2, cellSize) {
6102
6427
  };
6103
6428
  }
6104
6429
  function schwarzPValueAndGradient(x2, y2, z2, cellSize) {
6105
- const s = TAU / cellSize;
6430
+ const s = TAU$1 / cellSize;
6106
6431
  const xs = x2 * s;
6107
6432
  const ys = y2 * s;
6108
6433
  const zs = z2 * s;
6109
6434
  return {
6110
- value: cos$1(xs) + cos$1(ys) + cos$1(zs),
6111
- gx: -s * sin$1(xs),
6112
- gy: -s * sin$1(ys),
6113
- gz: -s * sin$1(zs)
6435
+ value: cos$2(xs) + cos$2(ys) + cos$2(zs),
6436
+ gx: -s * sin$2(xs),
6437
+ gy: -s * sin$2(ys),
6438
+ gz: -s * sin$2(zs)
6114
6439
  };
6115
6440
  }
6116
6441
  function diamondValueAndGradient(x2, y2, z2, cellSize) {
6117
- const s = TAU / cellSize;
6442
+ const s = TAU$1 / cellSize;
6118
6443
  const xs = x2 * s;
6119
6444
  const ys = y2 * s;
6120
6445
  const zs = z2 * s;
6121
- const sx = sin$1(xs), sy = sin$1(ys), sz = sin$1(zs);
6122
- const cx = cos$1(xs), cy = cos$1(ys), cz = cos$1(zs);
6446
+ const sx = sin$2(xs), sy = sin$2(ys), sz = sin$2(zs);
6447
+ const cx = cos$2(xs), cy = cos$2(ys), cz = cos$2(zs);
6123
6448
  return {
6124
6449
  value: sx * sy * sz + sx * cy * cz + cx * sy * cz + cx * cy * sz,
6125
6450
  gx: s * (cx * sy * sz + cx * cy * cz - sx * sy * cz - sx * cy * sz),
@@ -6128,12 +6453,12 @@ function diamondValueAndGradient(x2, y2, z2, cellSize) {
6128
6453
  };
6129
6454
  }
6130
6455
  function lidinoidValueAndGradient(x2, y2, z2, cellSize) {
6131
- const s = TAU / cellSize;
6456
+ const s = TAU$1 / cellSize;
6132
6457
  const sx2 = x2 * s, sy2 = y2 * s, sz2 = z2 * s;
6133
- const sx = sin$1(sx2), sy = sin$1(sy2), sz = sin$1(sz2);
6134
- const cx = cos$1(sx2), cy = cos$1(sy2), cz = cos$1(sz2);
6135
- const s2x = sin$1(2 * sx2), s2y = sin$1(2 * sy2), s2z = sin$1(2 * sz2);
6136
- const c2x = cos$1(2 * sx2), c2y = cos$1(2 * sy2), c2z = cos$1(2 * sz2);
6458
+ const sx = sin$2(sx2), sy = sin$2(sy2), sz = sin$2(sz2);
6459
+ const cx = cos$2(sx2), cy = cos$2(sy2), cz = cos$2(sz2);
6460
+ const s2x = sin$2(2 * sx2), s2y = sin$2(2 * sy2), s2z = sin$2(2 * sz2);
6461
+ const c2x = cos$2(2 * sx2), c2y = cos$2(2 * sy2), c2z = cos$2(2 * sz2);
6137
6462
  const val = s2x * cy * sz + s2y * cz * sx + s2z * cx * sy - c2x * c2y - c2y * c2z - c2z * c2x + 0.3;
6138
6463
  return {
6139
6464
  value: val,
@@ -6156,8 +6481,8 @@ function lidinoid$1(x2, y2, z2, cellSize, thickness, thicknessMode) {
6156
6481
  }
6157
6482
  function tpmsDistance({ value, gx, gy, gz }, thickness, thicknessMode) {
6158
6483
  if (thicknessMode !== "metric-approx") return abs$1(value) - thickness;
6159
- const grad = sqrt$1(gx * gx + gy * gy + gz * gz);
6160
- return abs$1(value) / Math.max(grad, GRAD_EPS) - thickness * 0.5;
6484
+ const grad = sqrt$2(gx * gx + gy * gy + gz * gz);
6485
+ return abs$1(value) / Math.max(grad, GRAD_EPS$1) - thickness * 0.5;
6161
6486
  }
6162
6487
  function mix(h) {
6163
6488
  h = (h ^ h >>> 16) * 2246822507 | 0;
@@ -6253,76 +6578,76 @@ function seededWorley3Surface(seed) {
6253
6578
  const s = seed | 0;
6254
6579
  return (x2, y2, z2, nx, ny, nz, threshold) => worleySurface(x2, y2, z2, s, nx, ny, nz, threshold);
6255
6580
  }
6256
- const { abs, cos, max, min, sin, sqrt, PI } = Math;
6257
- const DEG$1 = PI / 180;
6581
+ const { abs, cos: cos$1, max: max$1, min, sin: sin$1, sqrt: sqrt$1, PI: PI$1 } = Math;
6582
+ const DEG$2 = PI$1 / 180;
6258
6583
  function clamp$b(v, lo, hi) {
6259
6584
  return v < lo ? lo : v > hi ? hi : v;
6260
6585
  }
6261
- function length2(x2, y2) {
6262
- return sqrt(x2 * x2 + y2 * y2);
6586
+ function length2$1(x2, y2) {
6587
+ return sqrt$1(x2 * x2 + y2 * y2);
6263
6588
  }
6264
- function length3(x2, y2, z2) {
6265
- return sqrt(x2 * x2 + y2 * y2 + z2 * z2);
6589
+ function length3$1(x2, y2, z2) {
6590
+ return sqrt$1(x2 * x2 + y2 * y2 + z2 * z2);
6266
6591
  }
6267
- function sdSphere(px, py, pz, r) {
6268
- return length3(px, py, pz) - r;
6592
+ function sdSphere$1(px, py, pz, r) {
6593
+ return length3$1(px, py, pz) - r;
6269
6594
  }
6270
- function sdBox(px, py, pz, hx, hy, hz) {
6595
+ function sdBox$1(px, py, pz, hx, hy, hz) {
6271
6596
  const dx = abs(px) - hx;
6272
6597
  const dy = abs(py) - hy;
6273
6598
  const dz = abs(pz) - hz;
6274
- return length3(max(dx, 0), max(dy, 0), max(dz, 0)) + min(max(dx, dy, dz), 0);
6599
+ return length3$1(max$1(dx, 0), max$1(dy, 0), max$1(dz, 0)) + min(max$1(dx, dy, dz), 0);
6275
6600
  }
6276
- function sdCylinder(px, py, pz, h, r) {
6277
- const dx = length2(px, py) - r;
6601
+ function sdCylinder$1(px, py, pz, h, r) {
6602
+ const dx = length2$1(px, py) - r;
6278
6603
  const dz = abs(pz) - h * 0.5;
6279
- return length2(max(dx, 0), max(dz, 0)) + min(max(dx, dz), 0);
6604
+ return length2$1(max$1(dx, 0), max$1(dz, 0)) + min(max$1(dx, dz), 0);
6280
6605
  }
6281
- function sdTorus(px, py, pz, R, r) {
6282
- const qx = length2(px, py) - R;
6283
- return length2(qx, pz) - r;
6606
+ function sdTorus$1(px, py, pz, R, r) {
6607
+ const qx = length2$1(px, py) - R;
6608
+ return length2$1(qx, pz) - r;
6284
6609
  }
6285
- function sdCapsule(px, py, pz, h, r) {
6610
+ function sdCapsule$1(px, py, pz, h, r) {
6286
6611
  const halfH = h * 0.5;
6287
6612
  const cz = clamp$b(pz, -halfH, halfH);
6288
- return length3(px, py, pz - cz) - r;
6613
+ return length3$1(px, py, pz - cz) - r;
6289
6614
  }
6290
- function sdCone(px, py, pz, h, r) {
6291
- const q = length2(px, py);
6292
- const cLen = length2(h, r);
6615
+ function sdCone$1(px, py, pz, h, r) {
6616
+ const q = length2$1(px, py);
6617
+ const cLen = length2$1(h, r);
6293
6618
  const nx = h / cLen;
6294
6619
  const nz = -r / cLen;
6295
- const d2 = max(nx * q + nz * (pz - h), -pz, pz - h);
6620
+ const d2 = max$1(nx * q + nz * (pz - h), -pz, pz - h);
6296
6621
  return d2;
6297
6622
  }
6298
- function sdTaperedSegment(px, py, pz, ax, ay, az, bx, by, bz, ra, rb) {
6623
+ function sdTaperedSegment$1(px, py, pz, ax, ay, az, bx, by, bz, ra, rb) {
6299
6624
  const vx = bx - ax;
6300
6625
  const vy = by - ay;
6301
6626
  const vz = bz - az;
6302
6627
  const len2 = vx * vx + vy * vy + vz * vz;
6303
- if (len2 <= 1e-12) return sdSphere(px - ax, py - ay, pz - az, max(ra, rb));
6628
+ if (len2 <= 1e-12) return sdSphere$1(px - ax, py - ay, pz - az, max$1(ra, rb));
6304
6629
  const h = clamp$b(((px - ax) * vx + (py - ay) * vy + (pz - az) * vz) / len2, 0, 1);
6305
- return length3(px - (ax + vx * h), py - (ay + vy * h), pz - (az + vz * h)) - (ra + (rb - ra) * h);
6630
+ return length3$1(px - (ax + vx * h), py - (ay + vy * h), pz - (az + vz * h)) - (ra + (rb - ra) * h);
6306
6631
  }
6307
6632
  function sdPolylineSweep3(node, x2, y2, z2) {
6308
6633
  let d2 = 1e20;
6309
6634
  for (let i = 0; i < node.points.length - 1; i++) {
6310
6635
  const a2 = node.points[i];
6311
6636
  const b = node.points[i + 1];
6312
- 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]);
6313
- d2 = i === 0 ? segment : smin(d2, segment, node.blend);
6637
+ 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]);
6638
+ d2 = i === 0 ? segment : smin$1(d2, segment, node.blend);
6314
6639
  }
6315
6640
  return d2;
6316
6641
  }
6317
- function smin(a2, b, k2) {
6642
+ function smin$1(a2, b, k2) {
6318
6643
  if (k2 <= 0) return min(a2, b);
6319
- const h = max(k2 - abs(a2 - b), 0) / k2;
6644
+ const h = max$1(k2 - abs(a2 - b), 0) / k2;
6320
6645
  return min(a2, b) - h * h * h * k2 * (1 / 6);
6321
6646
  }
6322
- function smax(a2, b, k2) {
6323
- return -smin(-a2, -b, k2);
6647
+ function smax$1(a2, b, k2) {
6648
+ return -smin$1(-a2, -b, k2);
6324
6649
  }
6325
- function repeatCoord(v, spacing, count) {
6650
+ function repeatCoord$1(v, spacing, count) {
6326
6651
  if (spacing <= 0) return v;
6327
6652
  if (count > 0) {
6328
6653
  const center = (count - 1) * 0.5;
@@ -6331,31 +6656,138 @@ function repeatCoord(v, spacing, count) {
6331
6656
  }
6332
6657
  return v - spacing * Math.round(v / spacing);
6333
6658
  }
6659
+ function positiveMod(v, period) {
6660
+ return (v % period + period) % period;
6661
+ }
6662
+ function evalStripesPattern(u2, v, directionX, directionY, spacing, width, depth) {
6663
+ const coord = u2 * directionX + v * directionY;
6664
+ const d2 = abs(coord - Math.round(coord / spacing) * spacing);
6665
+ const profile = max$1(0, 1 - d2 / (width * 0.5));
6666
+ return -(profile * profile) * depth;
6667
+ }
6668
+ function evalOverUnderWeavePattern(u2, v, spacingX, spacingY, widthX, widthY, depth, underScale) {
6669
+ const su = u2 / spacingX;
6670
+ const sv = v / spacingY;
6671
+ let pU = max$1(0, 1 - abs(su - Math.round(su)) * spacingX / (widthX * 0.5));
6672
+ let pV = max$1(0, 1 - abs(sv - Math.round(sv)) * spacingY / (widthY * 0.5));
6673
+ pU *= pU;
6674
+ pV *= pV;
6675
+ const checker = (Math.round(su) & 65535) + (Math.round(sv) & 65535) & 1;
6676
+ const top = checker ? pV : pU;
6677
+ const bot = checker ? pU : pV;
6678
+ return -max$1(top, bot * underScale) * depth;
6679
+ }
6680
+ function compileTypedSurfacePattern(pattern) {
6681
+ switch (pattern.kind) {
6682
+ case "surfacePattern:constant":
6683
+ return () => pattern.value;
6684
+ case "surfacePattern:sineWave": {
6685
+ const { direction: direction2, wavelength, amplitude, phase, bias } = pattern;
6686
+ const frequency = 2 * PI$1 / wavelength;
6687
+ return (u2, v) => bias + sin$1((u2 * direction2[0] + v * direction2[1]) * frequency + phase) * amplitude;
6688
+ }
6689
+ case "surfacePattern:stripes": {
6690
+ const { direction: direction2, spacing, width, depth } = pattern;
6691
+ return (u2, v) => evalStripesPattern(u2, v, direction2[0], direction2[1], spacing, width, depth);
6692
+ }
6693
+ case "surfacePattern:overUnderWeave": {
6694
+ const { spacing, threadWidth, depth, underScale } = pattern;
6695
+ return (u2, v) => evalOverUnderWeavePattern(u2, v, spacing[0], spacing[1], threadWidth[0], threadWidth[1], depth, underScale);
6696
+ }
6697
+ case "surfacePattern:abs": {
6698
+ const child = compileTypedSurfacePattern(pattern.child);
6699
+ return (u2, v) => abs(child(u2, v));
6700
+ }
6701
+ case "surfacePattern:negate": {
6702
+ const child = compileTypedSurfacePattern(pattern.child);
6703
+ return (u2, v) => -child(u2, v);
6704
+ }
6705
+ case "surfacePattern:add": {
6706
+ const children = pattern.children.map(compileTypedSurfacePattern);
6707
+ return (u2, v) => children.reduce((sum2, child) => sum2 + child(u2, v), 0);
6708
+ }
6709
+ case "surfacePattern:multiply": {
6710
+ const children = pattern.children.map(compileTypedSurfacePattern);
6711
+ return (u2, v) => children.reduce((product, child) => product * child(u2, v), 1);
6712
+ }
6713
+ case "surfacePattern:min": {
6714
+ const children = pattern.children.map(compileTypedSurfacePattern);
6715
+ if (children.length === 0) return () => 0;
6716
+ return (u2, v) => children.reduce((value, child) => min(value, child(u2, v)), Infinity);
6717
+ }
6718
+ case "surfacePattern:max": {
6719
+ const children = pattern.children.map(compileTypedSurfacePattern);
6720
+ if (children.length === 0) return () => 0;
6721
+ return (u2, v) => children.reduce((value, child) => max$1(value, child(u2, v)), -Infinity);
6722
+ }
6723
+ case "surfacePattern:clamp": {
6724
+ const child = compileTypedSurfacePattern(pattern.child);
6725
+ return (u2, v) => clamp$b(child(u2, v), pattern.min, pattern.max);
6726
+ }
6727
+ }
6728
+ }
6729
+ function estimateSurfacePatternAmplitude(pattern) {
6730
+ switch (pattern.kind) {
6731
+ case "surfacePattern:constant":
6732
+ return abs(pattern.value);
6733
+ case "surfacePattern:sineWave":
6734
+ return abs(pattern.bias) + abs(pattern.amplitude);
6735
+ case "surfacePattern:stripes":
6736
+ case "surfacePattern:overUnderWeave":
6737
+ return pattern.depth;
6738
+ case "surfacePattern:abs":
6739
+ case "surfacePattern:negate":
6740
+ return estimateSurfacePatternAmplitude(pattern.child);
6741
+ case "surfacePattern:add": {
6742
+ const amplitudes = pattern.children.map(estimateSurfacePatternAmplitude);
6743
+ return amplitudes.every((value) => value !== null) ? amplitudes.reduce((sum2, value) => sum2 + value, 0) : null;
6744
+ }
6745
+ case "surfacePattern:multiply": {
6746
+ const amplitudes = pattern.children.map(estimateSurfacePatternAmplitude);
6747
+ return amplitudes.every((value) => value !== null) ? amplitudes.reduce((product, value) => product * value, 1) : null;
6748
+ }
6749
+ case "surfacePattern:min":
6750
+ case "surfacePattern:max": {
6751
+ const amplitudes = pattern.children.map(estimateSurfacePatternAmplitude);
6752
+ return amplitudes.every((value) => value !== null) ? max$1(...amplitudes) : null;
6753
+ }
6754
+ case "surfacePattern:clamp":
6755
+ return max$1(abs(pattern.min), abs(pattern.max));
6756
+ }
6757
+ }
6758
+ function compileSurfacePattern(node) {
6759
+ if (node.pattern) return compileTypedSurfacePattern(node.pattern);
6760
+ const constEntries = Object.entries(node.constants ?? {});
6761
+ const constNames = constEntries.map(([k2]) => k2);
6762
+ const constValues = constEntries.map(([, v]) => v);
6763
+ const patternFn = new Function("u", "v", ...constNames, `return (${node.patternBody});`);
6764
+ return (u2, v) => patternFn(u2, v, ...constValues);
6765
+ }
6334
6766
  function compileSdfNode3(node) {
6335
6767
  switch (node.kind) {
6336
6768
  case "sdf:sphere": {
6337
6769
  const r = node.radius;
6338
- return (x2, y2, z2) => sdSphere(x2, y2, z2, r);
6770
+ return (x2, y2, z2) => sdSphere$1(x2, y2, z2, r);
6339
6771
  }
6340
6772
  case "sdf:box": {
6341
6773
  const [hx, hy, hz] = node.halfExtents;
6342
- return (x2, y2, z2) => sdBox(x2, y2, z2, hx, hy, hz);
6774
+ return (x2, y2, z2) => sdBox$1(x2, y2, z2, hx, hy, hz);
6343
6775
  }
6344
6776
  case "sdf:cylinder": {
6345
6777
  const { height: h, radius: r } = node;
6346
- return (x2, y2, z2) => sdCylinder(x2, y2, z2, h, r);
6778
+ return (x2, y2, z2) => sdCylinder$1(x2, y2, z2, h, r);
6347
6779
  }
6348
6780
  case "sdf:torus": {
6349
6781
  const { majorRadius: R, minorRadius: r } = node;
6350
- return (x2, y2, z2) => sdTorus(x2, y2, z2, R, r);
6782
+ return (x2, y2, z2) => sdTorus$1(x2, y2, z2, R, r);
6351
6783
  }
6352
6784
  case "sdf:capsule": {
6353
6785
  const { height: h, radius: r } = node;
6354
- return (x2, y2, z2) => sdCapsule(x2, y2, z2, h, r);
6786
+ return (x2, y2, z2) => sdCapsule$1(x2, y2, z2, h, r);
6355
6787
  }
6356
6788
  case "sdf:cone": {
6357
6789
  const { height: h, radius: r } = node;
6358
- return (x2, y2, z2) => sdCone(x2, y2, z2, h, r);
6790
+ return (x2, y2, z2) => sdCone$1(x2, y2, z2, h, r);
6359
6791
  }
6360
6792
  case "sdf:polylineSweep": {
6361
6793
  return (x2, y2, z2) => sdPolylineSweep3(node, x2, y2, z2);
@@ -6372,7 +6804,7 @@ function compileSdfNode3(node) {
6372
6804
  const fns = node.children.map(compileSdfNode3);
6373
6805
  return (x2, y2, z2) => {
6374
6806
  let d2 = fns[0](x2, y2, z2);
6375
- for (let i = 1; i < fns.length; i++) d2 = max(d2, -fns[i](x2, y2, z2));
6807
+ for (let i = 1; i < fns.length; i++) d2 = max$1(d2, -fns[i](x2, y2, z2));
6376
6808
  return d2;
6377
6809
  };
6378
6810
  }
@@ -6380,7 +6812,7 @@ function compileSdfNode3(node) {
6380
6812
  const fns = node.children.map(compileSdfNode3);
6381
6813
  return (x2, y2, z2) => {
6382
6814
  let d2 = fns[0](x2, y2, z2);
6383
- for (let i = 1; i < fns.length; i++) d2 = max(d2, fns[i](x2, y2, z2));
6815
+ for (let i = 1; i < fns.length; i++) d2 = max$1(d2, fns[i](x2, y2, z2));
6384
6816
  return d2;
6385
6817
  };
6386
6818
  }
@@ -6389,7 +6821,7 @@ function compileSdfNode3(node) {
6389
6821
  const k2 = node.radius;
6390
6822
  return (x2, y2, z2) => {
6391
6823
  let d2 = fns[0](x2, y2, z2);
6392
- for (let i = 1; i < fns.length; i++) d2 = smin(d2, fns[i](x2, y2, z2), k2);
6824
+ for (let i = 1; i < fns.length; i++) d2 = smin$1(d2, fns[i](x2, y2, z2), k2);
6393
6825
  return d2;
6394
6826
  };
6395
6827
  }
@@ -6398,7 +6830,7 @@ function compileSdfNode3(node) {
6398
6830
  const k2 = node.radius;
6399
6831
  return (x2, y2, z2) => {
6400
6832
  let d2 = fns[0](x2, y2, z2);
6401
- for (let i = 1; i < fns.length; i++) d2 = smax(d2, -fns[i](x2, y2, z2), k2);
6833
+ for (let i = 1; i < fns.length; i++) d2 = smax$1(d2, -fns[i](x2, y2, z2), k2);
6402
6834
  return d2;
6403
6835
  };
6404
6836
  }
@@ -6407,7 +6839,7 @@ function compileSdfNode3(node) {
6407
6839
  const k2 = node.radius;
6408
6840
  return (x2, y2, z2) => {
6409
6841
  let d2 = fns[0](x2, y2, z2);
6410
- for (let i = 1; i < fns.length; i++) d2 = smax(d2, fns[i](x2, y2, z2), k2);
6842
+ for (let i = 1; i < fns.length; i++) d2 = smax$1(d2, fns[i](x2, y2, z2), k2);
6411
6843
  return d2;
6412
6844
  };
6413
6845
  }
@@ -6425,10 +6857,10 @@ function compileSdfNode3(node) {
6425
6857
  }
6426
6858
  case "sdf:rotate": {
6427
6859
  const fn = compileSdfNode3(node.child);
6428
- const [rx, ry, rz] = node.degrees.map((d2) => d2 * DEG$1);
6429
- const cx = cos(rx), sx = sin(rx);
6430
- const cy = cos(ry), sy = sin(ry);
6431
- const cz = cos(rz), sz = sin(rz);
6860
+ const [rx, ry, rz] = node.degrees.map((d2) => d2 * DEG$2);
6861
+ const cx = cos$1(rx), sx = sin$1(rx);
6862
+ const cy = cos$1(ry), sy = sin$1(ry);
6863
+ const cz = cos$1(rz), sz = sin$1(rz);
6432
6864
  return (x2, y2, z2) => {
6433
6865
  const x1 = cz * x2 + sz * y2;
6434
6866
  const y1 = -sz * x2 + cz * y2;
@@ -6447,10 +6879,10 @@ function compileSdfNode3(node) {
6447
6879
  }
6448
6880
  case "sdf:twist": {
6449
6881
  const fn = compileSdfNode3(node.child);
6450
- const k2 = node.degreesPerUnit * DEG$1;
6882
+ const k2 = node.degreesPerUnit * DEG$2;
6451
6883
  return (x2, y2, z2) => {
6452
6884
  const angle = k2 * z2;
6453
- const c2 = cos(angle), s = sin(angle);
6885
+ const c2 = cos$1(angle), s = sin$1(angle);
6454
6886
  return fn(c2 * x2 - s * y2, s * x2 + c2 * y2, z2);
6455
6887
  };
6456
6888
  }
@@ -6459,7 +6891,7 @@ function compileSdfNode3(node) {
6459
6891
  const r = node.radius;
6460
6892
  return (x2, y2, z2) => {
6461
6893
  const angle = x2 / r;
6462
- const c2 = cos(angle), s = sin(angle);
6894
+ const c2 = cos$1(angle), s = sin$1(angle);
6463
6895
  return fn((r + y2) * s, (r + y2) * c2 - r, z2);
6464
6896
  };
6465
6897
  }
@@ -6467,7 +6899,19 @@ function compileSdfNode3(node) {
6467
6899
  const fn = compileSdfNode3(node.child);
6468
6900
  const [sx, sy, sz] = node.spacing;
6469
6901
  const [cx, cy, cz] = node.count;
6470
- return (x2, y2, z2) => fn(repeatCoord(x2, sx, cx), repeatCoord(y2, sy, cy), repeatCoord(z2, sz, cz));
6902
+ return (x2, y2, z2) => fn(repeatCoord$1(x2, sx, cx), repeatCoord$1(y2, sy, cy), repeatCoord$1(z2, sz, cz));
6903
+ }
6904
+ case "sdf:circularArray": {
6905
+ const fn = compileSdfNode3(node.child);
6906
+ const da = 2 * PI$1 / node.count;
6907
+ const offset2 = node.offset;
6908
+ return (x2, y2, z2) => {
6909
+ const r = length2$1(x2, y2);
6910
+ const a2 = positiveMod(Math.atan2(y2, x2), da);
6911
+ const d1 = fn(cos$1(a2 - da) * r - offset2, sin$1(a2 - da) * r, z2);
6912
+ const d2 = fn(cos$1(a2) * r - offset2, sin$1(a2) * r, z2);
6913
+ return min(d1, d2);
6914
+ };
6471
6915
  }
6472
6916
  case "sdf:shell": {
6473
6917
  const fn = compileSdfNode3(node.child);
@@ -6484,10 +6928,7 @@ function compileSdfNode3(node) {
6484
6928
  }
6485
6929
  case "sdf:surfaceDisplace": {
6486
6930
  const childFn = compileSdfNode3(node.child);
6487
- const constEntries = Object.entries(node.constants ?? {});
6488
- const constNames = constEntries.map(([k2]) => k2);
6489
- const constValues = constEntries.map(([, v]) => v);
6490
- const patternFn = new Function("u", "v", ...constNames, `return (${node.patternBody});`);
6931
+ const patternFn = compileSurfacePattern(node);
6491
6932
  const uvMode = node.uvMode && node.uvMode !== "auto" ? node.uvMode : void 0;
6492
6933
  const analysis = analyzeUV(node.child, uvMode);
6493
6934
  const uvFn = compileUVFunction(analysis);
@@ -6499,7 +6940,7 @@ function compileSdfNode3(node) {
6499
6940
  p2[1] = y2;
6500
6941
  p2[2] = z2;
6501
6942
  const [u2, v] = uvFn(p2);
6502
- return d2 + patternFn(u2, v, ...constValues);
6943
+ return d2 + patternFn(u2, v);
6503
6944
  };
6504
6945
  }
6505
6946
  const sharpness = node.triplanarSharpness ?? 4;
@@ -6510,9 +6951,9 @@ function compileSdfNode3(node) {
6510
6951
  const gy = childFn(x2, y2 + eps, z2) - childFn(x2, y2 - eps, z2);
6511
6952
  const gz = childFn(x2, y2, z2 + eps) - childFn(x2, y2, z2 - eps);
6512
6953
  const { wx, wy, wz } = triplanarWeights(gx, gy, gz, sharpness);
6513
- const hX = patternFn(y2, z2, ...constValues);
6514
- const hY = patternFn(x2, z2, ...constValues);
6515
- const hZ = patternFn(x2, y2, ...constValues);
6954
+ const hX = patternFn(y2, z2);
6955
+ const hY = patternFn(x2, z2);
6956
+ const hZ = patternFn(x2, y2);
6516
6957
  return d2 + wx * hX + wy * hY + wz * hZ;
6517
6958
  };
6518
6959
  }
@@ -6582,7 +7023,7 @@ function compileSdfNode3(node) {
6582
7023
  const gx = gradFn(x2 + eps, y2, z2) - gradFn(x2 - eps, y2, z2);
6583
7024
  const gy = gradFn(x2, y2 + eps, z2) - gradFn(x2, y2 - eps, z2);
6584
7025
  const gz = gradFn(x2, y2, z2 + eps) - gradFn(x2, y2, z2 - eps);
6585
- const glen = sqrt(gx * gx + gy * gy + gz * gz);
7026
+ const glen = sqrt$1(gx * gx + gy * gy + gz * gz);
6586
7027
  let nx = 0, ny = 0, nz = 0;
6587
7028
  if (glen > 1e-10) {
6588
7029
  const invG = 1 / glen;
@@ -6644,7 +7085,7 @@ function estimateSdfBounds(node) {
6644
7085
  for (const point2 of node.points) {
6645
7086
  for (let i = 0; i < 3; i++) {
6646
7087
  minPoint[i] = min(minPoint[i], point2[i]);
6647
- maxPoint[i] = max(maxPoint[i], point2[i]);
7088
+ maxPoint[i] = max$1(maxPoint[i], point2[i]);
6648
7089
  }
6649
7090
  }
6650
7091
  return padBounds({ min: minPoint, max: maxPoint }, pad);
@@ -6675,7 +7116,7 @@ function estimateSdfBounds(node) {
6675
7116
  }
6676
7117
  case "sdf:rotate": {
6677
7118
  const b = estimateSdfBounds(node.child);
6678
- 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])));
7119
+ 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])));
6679
7120
  return { min: [-r, -r, -r], max: [r, r, r] };
6680
7121
  }
6681
7122
  case "sdf:scale": {
@@ -6689,7 +7130,7 @@ function estimateSdfBounds(node) {
6689
7130
  case "sdf:twist":
6690
7131
  case "sdf:bend": {
6691
7132
  const b = estimateSdfBounds(node.child);
6692
- 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;
7133
+ 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;
6693
7134
  return { min: [-r, -r, -r], max: [r, r, r] };
6694
7135
  }
6695
7136
  case "sdf:repeat": {
@@ -6710,16 +7151,29 @@ function estimateSdfBounds(node) {
6710
7151
  const [zMin, zMax] = expand(sz, cz, b.min[2], b.max[2]);
6711
7152
  return { min: [xMin, yMin, zMin], max: [xMax, yMax, zMax] };
6712
7153
  }
7154
+ case "sdf:circularArray": {
7155
+ const b = estimateSdfBounds(node.child);
7156
+ const x0 = b.min[0] + node.offset;
7157
+ const x1 = b.max[0] + node.offset;
7158
+ const y0 = b.min[1];
7159
+ const y1 = b.max[1];
7160
+ const r = max$1(length2$1(x0, y0), length2$1(x0, y1), length2$1(x1, y0), length2$1(x1, y1));
7161
+ return { min: [-r, -r, b.min[2]], max: [r, r, b.max[2]] };
7162
+ }
6713
7163
  case "sdf:shell": {
6714
7164
  const b = estimateSdfBounds(node.child);
6715
7165
  const t = node.thickness * 0.5;
6716
7166
  return padBounds(b, t);
6717
7167
  }
6718
- case "sdf:displace":
6719
- case "sdf:surfaceDisplace": {
7168
+ case "sdf:displace": {
6720
7169
  const b = estimateSdfBounds(node.child);
6721
7170
  return padBounds(b, 5);
6722
7171
  }
7172
+ case "sdf:surfaceDisplace": {
7173
+ const b = estimateSdfBounds(node.child);
7174
+ if (!node.pattern) return padBounds(b, 5);
7175
+ return padBounds(b, estimateSurfacePatternAmplitude(node.pattern) ?? 5);
7176
+ }
6723
7177
  case "sdf:onion": {
6724
7178
  const b = estimateSdfBounds(node.child);
6725
7179
  return padBounds(b, node.layers * node.thickness);
@@ -6756,7 +7210,7 @@ function unionBounds(bounds, pad) {
6756
7210
  for (const b of bounds) {
6757
7211
  for (let i = 0; i < 3; i++) {
6758
7212
  result.min[i] = min(result.min[i], b.min[i]);
6759
- result.max[i] = max(result.max[i], b.max[i]);
7213
+ result.max[i] = max$1(result.max[i], b.max[i]);
6760
7214
  }
6761
7215
  }
6762
7216
  if (pad > 0) return padBounds(result, pad);
@@ -6769,7 +7223,7 @@ function intersectBounds(bounds, pad) {
6769
7223
  };
6770
7224
  for (const b of bounds) {
6771
7225
  for (let i = 0; i < 3; i++) {
6772
- result.min[i] = max(result.min[i], b.min[i]);
7226
+ result.min[i] = max$1(result.min[i], b.min[i]);
6773
7227
  result.max[i] = min(result.max[i], b.max[i]);
6774
7228
  }
6775
7229
  }
@@ -6785,242 +7239,544 @@ function padBounds(b, pad) {
6785
7239
  max: [b.max[0] + pad, b.max[1] + pad, b.max[2] + pad]
6786
7240
  };
6787
7241
  }
6788
- const DEFAULT_MAX_GRID_POINTS = 8e6;
6789
- const DEFAULT_MIN_EDGE_LENGTH = 0.15;
6790
- function resolveSdfMeshingSettings(tree, bounds, options = {}) {
6791
- const quality = options.quality ?? "preview";
6792
- const minEdgeLength = positiveOrDefault(options.minEdgeLength, DEFAULT_MIN_EDGE_LENGTH);
6793
- const maxGridPoints = positiveOrDefault(options.maxGridPoints, DEFAULT_MAX_GRID_POINTS);
6794
- const tolerance = options.tolerance !== void 0 ? requirePositiveFinite$2(options.tolerance, "SDF tolerance") : void 0;
6795
- const minFeatureSize = options.minFeatureSize !== void 0 ? requirePositiveFinite$2(options.minFeatureSize, "SDF minFeatureSize") : void 0;
6796
- const maxTriangles = options.maxTriangles !== void 0 ? Math.floor(requirePositiveFinite$2(options.maxTriangles, "SDF maxTriangles")) : void 0;
6797
- const analysis = analyzeSdfTree(tree);
6798
- const warnings = [];
6799
- let edgeLength2;
6800
- if (options.edgeLength !== void 0) {
6801
- edgeLength2 = requirePositiveFinite$2(options.edgeLength, "SDF edgeLength");
6802
- if (edgeLength2 < minEdgeLength) {
6803
- warnings.push(`edgeLength ${formatMm(edgeLength2)} was clamped to minimum ${formatMm(minEdgeLength)}.`);
6804
- edgeLength2 = minEdgeLength;
6805
- }
6806
- } else {
6807
- edgeLength2 = resolveDefaultEdgeLength(bounds, quality, minEdgeLength, analysis, options);
7242
+ const { PI } = Math;
7243
+ const DEG$1 = PI / 180;
7244
+ const TAU = 2 * PI;
7245
+ const GRAD_EPS = 1e-9;
7246
+ const Op = {
7247
+ Const: 0,
7248
+ Neg: 1,
7249
+ Abs: 2,
7250
+ Sqrt: 3,
7251
+ Sin: 4,
7252
+ Cos: 5,
7253
+ Round: 6,
7254
+ Add: 7,
7255
+ Sub: 8,
7256
+ Mul: 9,
7257
+ Div: 10,
7258
+ Min: 11,
7259
+ Max: 12
7260
+ };
7261
+ class UnsupportedSdfProgramNodeError extends Error {
7262
+ constructor(message) {
7263
+ super(message);
7264
+ this.name = "UnsupportedSdfProgramNodeError";
6808
7265
  }
6809
- if (analysis.minWallThickness < Infinity && analysis.minWallThickness < edgeLength2 * 2) {
6810
- analysis.riskFlags.add("thin-shell");
6811
- warnings.push(
6812
- `shell/wall thickness ${formatMm(analysis.minWallThickness)} is below 2 x edgeLength ${formatMm(edgeLength2)}; thin features may be under-sampled.`
6813
- );
7266
+ }
7267
+ class SdfProgramBuilder {
7268
+ constructor() {
7269
+ __publicField(this, "opcodes", []);
7270
+ __publicField(this, "argA", []);
7271
+ __publicField(this, "argB", []);
7272
+ __publicField(this, "argC", []);
7273
+ __publicField(this, "constants", []);
7274
+ __publicField(this, "x", 0);
7275
+ __publicField(this, "y", 1);
7276
+ __publicField(this, "z", 2);
6814
7277
  }
6815
- if (!options.bounds && analysis.hasInfiniteRepeat) {
6816
- warnings.push("infinite repeat bounds are heuristic; pass .toShape({ bounds }) for predictable clipping.");
7278
+ constant(value) {
7279
+ const index2 = this.constants.length;
7280
+ this.constants.push(value);
7281
+ return this.push(Op.Const, 0, 0, index2);
6817
7282
  }
6818
- if (!options.bounds && analysis.riskFlags.has("noise")) {
6819
- warnings.push("noise field bounds are heuristic; pass .toShape({ bounds }) for predictable clipping.");
7283
+ neg(a2) {
7284
+ return this.push(Op.Neg, a2);
6820
7285
  }
6821
- if (!options.bounds && (analysis.riskFlags.has("tpms") || analysis.riskFlags.has("voronoi"))) {
6822
- warnings.push("TPMS/Voronoi bounds are heuristic unless clipped or passed explicitly.");
7286
+ abs(a2) {
7287
+ return this.push(Op.Abs, a2);
6823
7288
  }
6824
- if (analysis.hasLegacyTpmsThreshold) {
6825
- warnings.push("TPMS thickness is using legacy field-threshold units; use wallThickness for approximate millimeters.");
7289
+ sqrt(a2) {
7290
+ return this.push(Op.Sqrt, a2);
6826
7291
  }
7292
+ sin(a2) {
7293
+ return this.push(Op.Sin, a2);
7294
+ }
7295
+ cos(a2) {
7296
+ return this.push(Op.Cos, a2);
7297
+ }
7298
+ round(a2) {
7299
+ return this.push(Op.Round, a2);
7300
+ }
7301
+ add(a2, b) {
7302
+ return this.push(Op.Add, a2, b);
7303
+ }
7304
+ sub(a2, b) {
7305
+ return this.push(Op.Sub, a2, b);
7306
+ }
7307
+ mul(a2, b) {
7308
+ return this.push(Op.Mul, a2, b);
7309
+ }
7310
+ div(a2, b) {
7311
+ return this.push(Op.Div, a2, b);
7312
+ }
7313
+ min(a2, b) {
7314
+ return this.push(Op.Min, a2, b);
7315
+ }
7316
+ max(a2, b) {
7317
+ return this.push(Op.Max, a2, b);
7318
+ }
7319
+ finalize(output) {
7320
+ return {
7321
+ opcodes: Uint8Array.from(this.opcodes),
7322
+ argA: Int32Array.from(this.argA),
7323
+ argB: Int32Array.from(this.argB),
7324
+ argC: Int32Array.from(this.argC),
7325
+ constants: Float64Array.from(this.constants),
7326
+ output,
7327
+ slotCount: this.opcodes.length + 3
7328
+ };
7329
+ }
7330
+ push(op, a2 = 0, b = 0, c2 = 0) {
7331
+ const slot2 = this.opcodes.length + 3;
7332
+ this.opcodes.push(op);
7333
+ this.argA.push(a2);
7334
+ this.argB.push(b);
7335
+ this.argC.push(c2);
7336
+ return slot2;
7337
+ }
7338
+ }
7339
+ function compileSdfProgramEvaluator3(program) {
7340
+ const lines = ["const { abs, sqrt, sin, cos, round, min, max } = Math;", "let v0 = x;", "let v1 = y;", "let v2 = z;"];
7341
+ const { opcodes, argA, argB, argC, constants, output } = program;
7342
+ for (let i = 0; i < opcodes.length; i++) {
7343
+ const slot2 = `v${i + 3}`;
7344
+ const a2 = `v${argA[i]}`;
7345
+ const b = `v${argB[i]}`;
7346
+ switch (opcodes[i]) {
7347
+ case Op.Const:
7348
+ lines.push(`let ${slot2} = ${numberLiteral(constants[argC[i]])};`);
7349
+ break;
7350
+ case Op.Neg:
7351
+ lines.push(`let ${slot2} = -${a2};`);
7352
+ break;
7353
+ case Op.Abs:
7354
+ lines.push(`let ${slot2} = abs(${a2});`);
7355
+ break;
7356
+ case Op.Sqrt:
7357
+ lines.push(`let ${slot2} = sqrt(${a2});`);
7358
+ break;
7359
+ case Op.Sin:
7360
+ lines.push(`let ${slot2} = sin(${a2});`);
7361
+ break;
7362
+ case Op.Cos:
7363
+ lines.push(`let ${slot2} = cos(${a2});`);
7364
+ break;
7365
+ case Op.Round:
7366
+ lines.push(`let ${slot2} = round(${a2});`);
7367
+ break;
7368
+ case Op.Add:
7369
+ lines.push(`let ${slot2} = ${a2} + ${b};`);
7370
+ break;
7371
+ case Op.Sub:
7372
+ lines.push(`let ${slot2} = ${a2} - ${b};`);
7373
+ break;
7374
+ case Op.Mul:
7375
+ lines.push(`let ${slot2} = ${a2} * ${b};`);
7376
+ break;
7377
+ case Op.Div:
7378
+ lines.push(`let ${slot2} = ${a2} / ${b};`);
7379
+ break;
7380
+ case Op.Min:
7381
+ lines.push(`let ${slot2} = min(${a2}, ${b});`);
7382
+ break;
7383
+ case Op.Max:
7384
+ lines.push(`let ${slot2} = max(${a2}, ${b});`);
7385
+ break;
7386
+ default:
7387
+ throw new Error(`Unknown SdfProgram opcode ${opcodes[i]} at instruction ${i}.`);
7388
+ }
7389
+ }
7390
+ lines.push(`return v${output};`);
7391
+ return new Function("Math", `return function sdfProgramEval(x, y, z) {
7392
+ ${lines.join("\n")}
7393
+ };`)(Math);
7394
+ }
7395
+ function numberLiteral(value) {
7396
+ if (Number.isNaN(value)) return "NaN";
7397
+ if (value === Number.POSITIVE_INFINITY) return "Infinity";
7398
+ if (value === Number.NEGATIVE_INFINITY) return "-Infinity";
7399
+ return String(value);
7400
+ }
7401
+ function clampSlot(b, v, lo, hi) {
7402
+ return b.min(b.max(v, b.constant(lo)), b.constant(hi));
7403
+ }
7404
+ function length2(b, x2, y2) {
7405
+ return b.sqrt(b.add(b.mul(x2, x2), b.mul(y2, y2)));
7406
+ }
7407
+ function length3(b, x2, y2, z2) {
7408
+ return b.sqrt(b.add(b.add(b.mul(x2, x2), b.mul(y2, y2)), b.mul(z2, z2)));
7409
+ }
7410
+ function smin(b, a2, child, k2) {
7411
+ if (k2 <= 0) return b.min(a2, child);
7412
+ const h = b.div(b.max(b.sub(b.constant(k2), b.abs(b.sub(a2, child))), b.constant(0)), b.constant(k2));
7413
+ return b.sub(b.min(a2, child), b.mul(b.mul(b.mul(h, h), h), b.constant(k2 / 6)));
7414
+ }
7415
+ function smax(b, a2, child, k2) {
7416
+ return b.neg(smin(b, b.neg(a2), b.neg(child), k2));
7417
+ }
7418
+ function emitScaledTrig(b, x2, y2, z2, cellSize) {
7419
+ const s = b.constant(TAU / cellSize);
7420
+ const xs = b.mul(x2, s);
7421
+ const ys = b.mul(y2, s);
7422
+ const zs = b.mul(z2, s);
6827
7423
  return {
6828
- quality,
6829
- edgeLength: edgeLength2,
6830
- tolerance,
6831
- minFeatureSize,
6832
- minEdgeLength,
6833
- simplify: resolveSimplificationMode(options.simplify, quality, analysis.riskFlags),
6834
- maxTriangles,
6835
- maxGridPoints,
6836
- diagnostics: options.diagnostics === true,
6837
- treeRiskFlags: [...analysis.riskFlags].sort(),
6838
- warnings
7424
+ scale: s,
7425
+ sx: b.sin(xs),
7426
+ sy: b.sin(ys),
7427
+ sz: b.sin(zs),
7428
+ cx: b.cos(xs),
7429
+ cy: b.cos(ys),
7430
+ cz: b.cos(zs),
7431
+ sx2: b.sin(b.mul(b.constant(2), xs)),
7432
+ sy2: b.sin(b.mul(b.constant(2), ys)),
7433
+ sz2: b.sin(b.mul(b.constant(2), zs)),
7434
+ cx2: b.cos(b.mul(b.constant(2), xs)),
7435
+ cy2: b.cos(b.mul(b.constant(2), ys)),
7436
+ cz2: b.cos(b.mul(b.constant(2), zs))
6839
7437
  };
6840
7438
  }
6841
- function withScaledSdfEdgeLength(settings, edgeLength2) {
6842
- return { ...settings, edgeLength: Math.max(settings.minEdgeLength, edgeLength2) };
7439
+ function emitGyroidValueAndGradient(b, x2, y2, z2, cellSize) {
7440
+ const t = emitScaledTrig(b, x2, y2, z2, cellSize);
7441
+ return {
7442
+ value: b.add(b.add(b.mul(t.sx, t.cy), b.mul(t.sy, t.cz)), b.mul(t.sz, t.cx)),
7443
+ gx: b.mul(t.scale, b.sub(b.mul(t.cx, t.cy), b.mul(t.sz, t.sx))),
7444
+ gy: b.mul(t.scale, b.add(b.neg(b.mul(t.sx, t.sy)), b.mul(t.cy, t.cz))),
7445
+ gz: b.mul(t.scale, b.add(b.neg(b.mul(t.sy, t.sz)), b.mul(t.cz, t.cx)))
7446
+ };
6843
7447
  }
6844
- function createSdfMeshingDiagnostics(settings, bounds, paddedBounds) {
6845
- const grid = estimateSdfGridDimensions(paddedBounds, settings.edgeLength);
6846
- const estimatedSamples = grid[0] * grid[1] * grid[2];
7448
+ function emitSchwarzPValueAndGradient(b, x2, y2, z2, cellSize) {
7449
+ const t = emitScaledTrig(b, x2, y2, z2, cellSize);
6847
7450
  return {
6848
- bounds: cloneBounds$2(bounds),
6849
- paddedBounds: cloneBounds$2(paddedBounds),
6850
- edgeLength: settings.edgeLength,
6851
- grid,
6852
- estimatedSamples,
6853
- estimatedMemoryBytes: estimatedSamples * 8,
6854
- treeRiskFlags: [...settings.treeRiskFlags],
6855
- simplification: settings.simplify,
6856
- capMode: "box",
6857
- capInset: settings.edgeLength,
6858
- warnings: [...settings.warnings]
7451
+ value: b.add(b.add(t.cx, t.cy), t.cz),
7452
+ gx: b.neg(b.mul(t.scale, t.sx)),
7453
+ gy: b.neg(b.mul(t.scale, t.sy)),
7454
+ gz: b.neg(b.mul(t.scale, t.sz))
6859
7455
  };
6860
7456
  }
6861
- function assertSdfMeshingBudget(diagnostics, maxGridPoints) {
6862
- if (diagnostics.estimatedSamples <= maxGridPoints) return;
6863
- const suggestedEdge = suggestEdgeLengthForSampleBudget(diagnostics.paddedBounds, maxGridPoints);
6864
- throw new Error(
6865
- `SDF meshing would sample ${formatCount(diagnostics.estimatedSamples)} grid points (~${formatBytes(
6866
- diagnostics.estimatedMemoryBytes
6867
- )}). Reduce bounds or use edgeLength >= ${formatMm(suggestedEdge)}.`
7457
+ function emitDiamondValueAndGradient(b, x2, y2, z2, cellSize) {
7458
+ const t = emitScaledTrig(b, x2, y2, z2, cellSize);
7459
+ return {
7460
+ value: b.add(
7461
+ b.add(b.mul(b.mul(t.sx, t.sy), t.sz), b.mul(b.mul(t.sx, t.cy), t.cz)),
7462
+ b.add(b.mul(b.mul(t.cx, t.sy), t.cz), b.mul(b.mul(t.cx, t.cy), t.sz))
7463
+ ),
7464
+ gx: b.mul(
7465
+ t.scale,
7466
+ b.add(
7467
+ b.add(b.mul(b.mul(t.cx, t.sy), t.sz), b.mul(b.mul(t.cx, t.cy), t.cz)),
7468
+ 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)))
7469
+ )
7470
+ ),
7471
+ gy: b.mul(
7472
+ t.scale,
7473
+ b.add(
7474
+ b.add(b.mul(b.mul(t.sx, t.cy), t.sz), b.neg(b.mul(b.mul(t.sx, t.sy), t.cz))),
7475
+ b.add(b.mul(b.mul(t.cx, t.cy), t.cz), b.neg(b.mul(b.mul(t.cx, t.sy), t.sz)))
7476
+ )
7477
+ ),
7478
+ gz: b.mul(
7479
+ t.scale,
7480
+ b.add(
7481
+ b.add(b.mul(b.mul(t.sx, t.sy), t.cz), b.neg(b.mul(b.mul(t.sx, t.cy), t.sz))),
7482
+ b.add(b.neg(b.mul(b.mul(t.cx, t.sy), t.sz)), b.mul(b.mul(t.cx, t.cy), t.cz))
7483
+ )
7484
+ )
7485
+ };
7486
+ }
7487
+ function emitLidinoidValueAndGradient(b, x2, y2, z2, cellSize) {
7488
+ const t = emitScaledTrig(b, x2, y2, z2, cellSize);
7489
+ const value = b.add(
7490
+ b.sub(
7491
+ 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)),
7492
+ b.add(b.add(b.mul(t.cx2, t.cy2), b.mul(t.cy2, t.cz2)), b.mul(t.cz2, t.cx2))
7493
+ ),
7494
+ b.constant(0.3)
6868
7495
  );
7496
+ return {
7497
+ value,
7498
+ gx: b.mul(
7499
+ t.scale,
7500
+ b.add(
7501
+ b.add(
7502
+ 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)),
7503
+ b.neg(b.mul(b.mul(t.sz2, t.sx), t.sy))
7504
+ ),
7505
+ b.add(b.mul(b.mul(b.constant(2), t.sx2), t.cy2), b.mul(b.mul(b.constant(2), t.cz2), t.sx2))
7506
+ )
7507
+ ),
7508
+ gy: b.mul(
7509
+ t.scale,
7510
+ b.add(
7511
+ b.add(
7512
+ 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))),
7513
+ b.mul(b.mul(t.sz2, t.cx), t.cy)
7514
+ ),
7515
+ b.add(b.mul(b.mul(b.constant(2), t.cx2), t.sy2), b.mul(b.mul(b.constant(2), t.sy2), t.cz2))
7516
+ )
7517
+ ),
7518
+ gz: b.mul(
7519
+ t.scale,
7520
+ b.add(
7521
+ b.add(
7522
+ b.add(b.mul(b.mul(t.sx2, t.cy), t.cz), b.neg(b.mul(b.mul(t.sy2, t.sz), t.sx))),
7523
+ b.mul(b.mul(b.constant(2), t.cz2), b.mul(t.cx, t.sy))
7524
+ ),
7525
+ b.add(b.mul(b.mul(b.constant(2), t.cy2), t.sz2), b.mul(b.mul(b.constant(2), t.sz2), t.cx2))
7526
+ )
7527
+ )
7528
+ };
6869
7529
  }
6870
- function estimateSdfGridDimensions(bounds, edgeLength2) {
6871
- const dx = bounds.max[0] - bounds.min[0];
6872
- const dy = bounds.max[1] - bounds.min[1];
6873
- const dz = bounds.max[2] - bounds.min[2];
6874
- return [
6875
- Math.max(2, Math.ceil(dx / edgeLength2) + 1),
6876
- Math.max(2, Math.ceil(dy / edgeLength2) + 1),
6877
- Math.max(2, Math.ceil(dz / edgeLength2) + 1)
6878
- ];
7530
+ function emitTpmsDistance(b, { value, gx, gy, gz }, thickness, thicknessMode) {
7531
+ if (thicknessMode !== "metric-approx") return b.sub(b.abs(value), b.constant(thickness));
7532
+ const grad = length3(b, gx, gy, gz);
7533
+ return b.sub(b.div(b.abs(value), b.max(grad, b.constant(GRAD_EPS))), b.constant(thickness * 0.5));
6879
7534
  }
6880
- function logSdfMeshingDiagnostics(prefix, diagnostics) {
6881
- const warnings = diagnostics.warnings.length > 0 ? `, warnings=${diagnostics.warnings.join(" | ")}` : "";
6882
- console.info(
6883
- `${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}`
6884
- );
7535
+ const { cos, max, sin, sqrt } = Math;
7536
+ function emitSdfProgramNode(b, node, x2, y2, z2) {
7537
+ switch (node.kind) {
7538
+ case "sdf:sphere":
7539
+ return sdSphere(b, x2, y2, z2, node.radius);
7540
+ case "sdf:box": {
7541
+ const [hx, hy, hz] = node.halfExtents;
7542
+ return sdBox(b, x2, y2, z2, hx, hy, hz);
7543
+ }
7544
+ case "sdf:cylinder":
7545
+ return sdCylinder(b, x2, y2, z2, node.height, node.radius);
7546
+ case "sdf:torus":
7547
+ return sdTorus(b, x2, y2, z2, node.majorRadius, node.minorRadius);
7548
+ case "sdf:capsule":
7549
+ return sdCapsule(b, x2, y2, z2, node.height, node.radius);
7550
+ case "sdf:cone":
7551
+ return sdCone(b, x2, y2, z2, node.height, node.radius);
7552
+ case "sdf:polylineSweep":
7553
+ return sdPolylineSweep(b, node, x2, y2, z2);
7554
+ case "sdf:union":
7555
+ return foldChildren(b, node.children, x2, y2, z2, (a2, child) => b.min(a2, child));
7556
+ case "sdf:difference":
7557
+ return foldChildren(b, node.children, x2, y2, z2, (a2, child) => b.max(a2, b.neg(child)));
7558
+ case "sdf:intersection":
7559
+ return foldChildren(b, node.children, x2, y2, z2, (a2, child) => b.max(a2, child));
7560
+ case "sdf:smoothUnion":
7561
+ return foldChildren(b, node.children, x2, y2, z2, (a2, child) => smin(b, a2, child, node.radius));
7562
+ case "sdf:smoothDifference":
7563
+ return foldChildren(b, node.children, x2, y2, z2, (a2, child) => smax(b, a2, b.neg(child), node.radius));
7564
+ case "sdf:smoothIntersection":
7565
+ return foldChildren(b, node.children, x2, y2, z2, (a2, child) => smax(b, a2, child, node.radius));
7566
+ case "sdf:morph": {
7567
+ const a2 = emitSdfProgramNode(b, node.a, x2, y2, z2);
7568
+ const childB = emitSdfProgramNode(b, node.b, x2, y2, z2);
7569
+ return b.add(b.mul(a2, b.constant(1 - node.t)), b.mul(childB, b.constant(node.t)));
7570
+ }
7571
+ case "sdf:translate": {
7572
+ const [ox, oy, oz] = node.offset;
7573
+ return emitSdfProgramNode(b, node.child, b.sub(x2, b.constant(ox)), b.sub(y2, b.constant(oy)), b.sub(z2, b.constant(oz)));
7574
+ }
7575
+ case "sdf:rotate":
7576
+ return emitRotated(b, node, x2, y2, z2);
7577
+ case "sdf:scale": {
7578
+ const inv = 1 / node.factor;
7579
+ const child = emitSdfProgramNode(b, node.child, b.mul(x2, b.constant(inv)), b.mul(y2, b.constant(inv)), b.mul(z2, b.constant(inv)));
7580
+ return b.mul(child, b.constant(node.factor));
7581
+ }
7582
+ case "sdf:twist": {
7583
+ const angle = b.mul(b.constant(node.degreesPerUnit * DEG$1), z2);
7584
+ const c2 = b.cos(angle);
7585
+ const s = b.sin(angle);
7586
+ 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);
7587
+ }
7588
+ case "sdf:bend": {
7589
+ const angle = b.div(x2, b.constant(node.radius));
7590
+ const c2 = b.cos(angle);
7591
+ const s = b.sin(angle);
7592
+ const radiusPlusY = b.add(b.constant(node.radius), y2);
7593
+ return emitSdfProgramNode(b, node.child, b.mul(radiusPlusY, s), b.sub(b.mul(radiusPlusY, c2), b.constant(node.radius)), z2);
7594
+ }
7595
+ case "sdf:repeat": {
7596
+ const [sx, sy, sz] = node.spacing;
7597
+ const [cx, cy, cz] = node.count;
7598
+ return emitSdfProgramNode(b, node.child, repeatCoord(b, x2, sx, cx), repeatCoord(b, y2, sy, cy), repeatCoord(b, z2, sz, cz));
7599
+ }
7600
+ case "sdf:shell": {
7601
+ const child = emitSdfProgramNode(b, node.child, x2, y2, z2);
7602
+ return b.sub(b.abs(child), b.constant(node.thickness * 0.5));
7603
+ }
7604
+ case "sdf:onion": {
7605
+ let d2 = emitSdfProgramNode(b, node.child, x2, y2, z2);
7606
+ for (let i = 0; i < node.layers; i++) d2 = b.sub(b.abs(d2), b.constant(node.thickness));
7607
+ return d2;
7608
+ }
7609
+ case "sdf:gyroid":
7610
+ return emitTpmsDistance(b, emitGyroidValueAndGradient(b, x2, y2, z2, node.cellSize), node.thickness, node.thicknessMode);
7611
+ case "sdf:schwarzP":
7612
+ return emitTpmsDistance(b, emitSchwarzPValueAndGradient(b, x2, y2, z2, node.cellSize), node.thickness, node.thicknessMode);
7613
+ case "sdf:diamond":
7614
+ return emitTpmsDistance(b, emitDiamondValueAndGradient(b, x2, y2, z2, node.cellSize), node.thickness, node.thicknessMode);
7615
+ case "sdf:lidinoid":
7616
+ return emitTpmsDistance(b, emitLidinoidValueAndGradient(b, x2, y2, z2, node.cellSize), node.thickness, node.thicknessMode);
7617
+ default:
7618
+ throw new UnsupportedSdfProgramNodeError(`SdfProgram does not support node kind ${node.kind} yet.`);
7619
+ }
6885
7620
  }
6886
- function resolveDefaultEdgeLength(bounds, quality, minEdgeLength, analysis, options) {
6887
- const dx = bounds.max[0] - bounds.min[0];
6888
- const dy = bounds.max[1] - bounds.min[1];
6889
- const dz = bounds.max[2] - bounds.min[2];
6890
- const maxDim = Math.max(dx, dy, dz, minEdgeLength);
6891
- const divisor = quality === "draft" ? 60 : quality === "export" ? 160 : 100;
6892
- const candidates = [maxDim / divisor];
6893
- if (options.tolerance !== void 0) candidates.push(requirePositiveFinite$2(options.tolerance, "SDF tolerance") * 2);
6894
- if (options.minFeatureSize !== void 0) candidates.push(requirePositiveFinite$2(options.minFeatureSize, "SDF minFeatureSize") / 2.5);
6895
- if (analysis.minMetricTpmsThickness < Infinity) candidates.push(analysis.minMetricTpmsThickness / 2);
6896
- if (analysis.minTpmsCellSize < Infinity) candidates.push(analysis.minTpmsCellSize / 10);
6897
- if (analysis.minRepeatSpacing < Infinity) candidates.push(analysis.minRepeatSpacing / 8);
6898
- if (analysis.minWallThickness < Infinity) candidates.push(analysis.minWallThickness / 2.5);
6899
- return Math.max(minEdgeLength, Math.min(...candidates.filter((v) => Number.isFinite(v) && v > 0)));
7621
+ function foldChildren(b, children, x2, y2, z2, combine2) {
7622
+ let d2 = emitSdfProgramNode(b, children[0], x2, y2, z2);
7623
+ for (let i = 1; i < children.length; i++) d2 = combine2(d2, emitSdfProgramNode(b, children[i], x2, y2, z2));
7624
+ return d2;
6900
7625
  }
6901
- function resolveSimplificationMode(simplify, quality, riskFlags) {
6902
- if (simplify === false) return "off";
6903
- if (simplify === true || simplify === "safe") return "safe";
6904
- if (quality === "export" && riskFlags.size > 0) return "off";
6905
- return "safe";
7626
+ function emitRotated(b, node, x2, y2, z2) {
7627
+ const [rx, ry, rz] = node.degrees.map((d2) => d2 * DEG$1);
7628
+ const cx = cos(rx);
7629
+ const sx = sin(rx);
7630
+ const cy = cos(ry);
7631
+ const sy = sin(ry);
7632
+ const cz = cos(rz);
7633
+ const sz = sin(rz);
7634
+ const x1 = b.add(b.mul(b.constant(cz), x2), b.mul(b.constant(sz), y2));
7635
+ const y1 = b.sub(b.mul(b.constant(cz), y2), b.mul(b.constant(sz), x2));
7636
+ const x22 = b.sub(b.mul(b.constant(cy), x1), b.mul(b.constant(sy), z2));
7637
+ const z22 = b.add(b.mul(b.constant(sy), x1), b.mul(b.constant(cy), z2));
7638
+ const y22 = b.add(b.mul(b.constant(cx), y1), b.mul(b.constant(sx), z22));
7639
+ const z3 = b.sub(b.mul(b.constant(cx), z22), b.mul(b.constant(sx), y1));
7640
+ return emitSdfProgramNode(b, node.child, x22, y22, z3);
7641
+ }
7642
+ function sdSphere(b, x2, y2, z2, r) {
7643
+ return b.sub(length3(b, x2, y2, z2), b.constant(r));
7644
+ }
7645
+ function sdBox(b, x2, y2, z2, hx, hy, hz) {
7646
+ const dx = b.sub(b.abs(x2), b.constant(hx));
7647
+ const dy = b.sub(b.abs(y2), b.constant(hy));
7648
+ const dz = b.sub(b.abs(z2), b.constant(hz));
7649
+ const outside = length3(b, b.max(dx, b.constant(0)), b.max(dy, b.constant(0)), b.max(dz, b.constant(0)));
7650
+ const inside = b.min(b.max(b.max(dx, dy), dz), b.constant(0));
7651
+ return b.add(outside, inside);
7652
+ }
7653
+ function sdCylinder(b, x2, y2, z2, h, r) {
7654
+ const dx = b.sub(length2(b, x2, y2), b.constant(r));
7655
+ const dz = b.sub(b.abs(z2), b.constant(h * 0.5));
7656
+ 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)));
7657
+ }
7658
+ function sdTorus(b, x2, y2, z2, majorRadius, minorRadius) {
7659
+ return b.sub(length2(b, b.sub(length2(b, x2, y2), b.constant(majorRadius)), z2), b.constant(minorRadius));
7660
+ }
7661
+ function sdCapsule(b, x2, y2, z2, h, r) {
7662
+ const cz = clampSlot(b, z2, -h * 0.5, h * 0.5);
7663
+ return b.sub(length3(b, x2, y2, b.sub(z2, cz)), b.constant(r));
7664
+ }
7665
+ function sdCone(b, x2, y2, z2, h, r) {
7666
+ const q = length2(b, x2, y2);
7667
+ const cLen = sqrt(h * h + r * r);
7668
+ const side = b.add(b.mul(b.constant(h / cLen), q), b.mul(b.constant(-r / cLen), b.sub(z2, b.constant(h))));
7669
+ return b.max(b.max(side, b.neg(z2)), b.sub(z2, b.constant(h)));
7670
+ }
7671
+ function sdPolylineSweep(b, node, x2, y2, z2) {
7672
+ let d2 = sdTaperedSegment(b, x2, y2, z2, node.points[0], node.points[1], node.radii[0], node.radii[1]);
7673
+ for (let i = 1; i < node.points.length - 1; i++) {
7674
+ const segment = sdTaperedSegment(b, x2, y2, z2, node.points[i], node.points[i + 1], node.radii[i], node.radii[i + 1]);
7675
+ d2 = smin(b, d2, segment, node.blend);
7676
+ }
7677
+ return d2;
6906
7678
  }
6907
- function analyzeSdfTree(tree) {
6908
- const analysis = {
6909
- riskFlags: /* @__PURE__ */ new Set(),
6910
- minTpmsCellSize: Infinity,
6911
- minMetricTpmsThickness: Infinity,
6912
- minRepeatSpacing: Infinity,
6913
- minWallThickness: Infinity,
6914
- hasInfiniteRepeat: false,
6915
- hasLegacyTpmsThreshold: false
6916
- };
6917
- visitSdfNode(tree, analysis);
6918
- return analysis;
7679
+ function sdTaperedSegment(b, x2, y2, z2, a2, end, ra, rb) {
7680
+ const vx = end[0] - a2[0];
7681
+ const vy = end[1] - a2[1];
7682
+ const vz = end[2] - a2[2];
7683
+ const len2 = vx * vx + vy * vy + vz * vz;
7684
+ 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));
7685
+ const h = clampSlot(
7686
+ b,
7687
+ b.div(
7688
+ b.add(
7689
+ 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))),
7690
+ b.mul(b.sub(z2, b.constant(a2[2])), b.constant(vz))
7691
+ ),
7692
+ b.constant(len2)
7693
+ ),
7694
+ 0,
7695
+ 1
7696
+ );
7697
+ const sx = b.sub(x2, b.add(b.constant(a2[0]), b.mul(b.constant(vx), h)));
7698
+ const sy = b.sub(y2, b.add(b.constant(a2[1]), b.mul(b.constant(vy), h)));
7699
+ const sz = b.sub(z2, b.add(b.constant(a2[2]), b.mul(b.constant(vz), h)));
7700
+ const radius = b.add(b.constant(ra), b.mul(b.constant(rb - ra), h));
7701
+ return b.sub(length3(b, sx, sy, sz), radius);
6919
7702
  }
6920
- function visitSdfNode(node, analysis) {
7703
+ function repeatCoord(b, v, spacing, count) {
7704
+ if (spacing <= 0) return v;
7705
+ if (count > 0) {
7706
+ const center = (count - 1) * 0.5;
7707
+ const index2 = clampSlot(b, b.round(b.add(b.div(v, b.constant(spacing)), b.constant(center))), 0, count - 1);
7708
+ return b.sub(v, b.mul(b.sub(index2, b.constant(center)), b.constant(spacing)));
7709
+ }
7710
+ return b.sub(v, b.mul(b.constant(spacing), b.round(b.div(v, b.constant(spacing)))));
7711
+ }
7712
+ function getUnsupportedSdfProgramReason(node) {
6921
7713
  switch (node.kind) {
7714
+ case "sdf:displace":
7715
+ return "displace uses a dynamic JavaScript function body";
7716
+ case "sdf:surfaceDisplace":
7717
+ return "surfaceDisplace uses dynamic UV/pattern evaluation";
7718
+ case "sdf:spatialBlend":
7719
+ return "spatialBlend uses a dynamic JavaScript blend function";
7720
+ case "sdf:noise":
7721
+ return "noise depends on table-based simplex evaluation";
7722
+ case "sdf:voronoi":
7723
+ return "voronoi depends on table-based Worley evaluation";
7724
+ case "sdf:custom":
7725
+ return "custom uses a dynamic JavaScript function body";
7726
+ case "sdf:polylineSweep":
7727
+ if (node.points.length < 2) return "polylineSweep needs at least two points";
7728
+ if (node.points.length !== node.radii.length) return "polylineSweep point/radius counts differ";
7729
+ return void 0;
6922
7730
  case "sdf:union":
6923
7731
  case "sdf:difference":
6924
7732
  case "sdf:intersection":
6925
7733
  case "sdf:smoothUnion":
6926
7734
  case "sdf:smoothDifference":
6927
7735
  case "sdf:smoothIntersection":
6928
- for (const child of node.children) visitSdfNode(child, analysis);
6929
- break;
7736
+ for (const child of node.children) {
7737
+ const reason = getUnsupportedSdfProgramReason(child);
7738
+ if (reason) return reason;
7739
+ }
7740
+ return void 0;
6930
7741
  case "sdf:morph":
6931
- case "sdf:spatialBlend":
6932
- visitSdfNode(node.a, analysis);
6933
- visitSdfNode(node.b, analysis);
6934
- break;
7742
+ return getUnsupportedSdfProgramReason(node.a) ?? getUnsupportedSdfProgramReason(node.b);
6935
7743
  case "sdf:translate":
6936
7744
  case "sdf:rotate":
6937
7745
  case "sdf:scale":
6938
7746
  case "sdf:twist":
6939
7747
  case "sdf:bend":
6940
- case "sdf:onion":
6941
- visitSdfNode(node.child, analysis);
6942
- break;
6943
7748
  case "sdf:repeat":
6944
- analysis.riskFlags.add("repeat");
6945
- for (let i = 0; i < 3; i++) {
6946
- const spacing = node.spacing[i];
6947
- if (spacing > 0) {
6948
- analysis.minRepeatSpacing = Math.min(analysis.minRepeatSpacing, spacing);
6949
- if (node.count[i] <= 0) analysis.hasInfiniteRepeat = true;
6950
- }
6951
- }
6952
- visitSdfNode(node.child, analysis);
6953
- break;
6954
7749
  case "sdf:shell":
6955
- analysis.minWallThickness = Math.min(analysis.minWallThickness, node.thickness);
6956
- visitSdfNode(node.child, analysis);
6957
- break;
6958
- case "sdf:displace":
6959
- case "sdf:surfaceDisplace":
6960
- analysis.riskFlags.add("displacement");
6961
- visitSdfNode(node.child, analysis);
6962
- break;
6963
- case "sdf:gyroid":
6964
- case "sdf:schwarzP":
6965
- case "sdf:diamond":
6966
- case "sdf:lidinoid":
6967
- analysis.riskFlags.add("tpms");
6968
- analysis.minTpmsCellSize = Math.min(analysis.minTpmsCellSize, node.cellSize);
6969
- if (node.thicknessMode === "metric-approx") {
6970
- analysis.minMetricTpmsThickness = Math.min(analysis.minMetricTpmsThickness, node.thickness);
6971
- analysis.minWallThickness = Math.min(analysis.minWallThickness, node.thickness);
6972
- } else {
6973
- analysis.hasLegacyTpmsThreshold = true;
6974
- }
6975
- break;
6976
- case "sdf:noise":
6977
- analysis.riskFlags.add("noise");
6978
- break;
6979
- case "sdf:voronoi":
6980
- analysis.riskFlags.add("voronoi");
6981
- analysis.minWallThickness = Math.min(analysis.minWallThickness, node.wallThickness);
6982
- if (node.surfaceChild) visitSdfNode(node.surfaceChild, analysis);
6983
- break;
6984
- case "sdf:custom":
6985
- analysis.riskFlags.add("custom");
6986
- break;
7750
+ case "sdf:onion":
7751
+ return getUnsupportedSdfProgramReason(node.child);
7752
+ default:
7753
+ return void 0;
6987
7754
  }
6988
7755
  }
6989
- function positiveOrDefault(value, fallback) {
6990
- if (value === void 0) return fallback;
6991
- return requirePositiveFinite$2(value, "SDF meshing option");
6992
- }
6993
- function requirePositiveFinite$2(value, name) {
6994
- if (!Number.isFinite(value) || value <= 0) {
6995
- throw new Error(`${name} must be a positive finite number.`);
7756
+ function compileSdfProgram(node) {
7757
+ const unsupportedReason = getUnsupportedSdfProgramReason(node);
7758
+ if (unsupportedReason) {
7759
+ throw new UnsupportedSdfProgramNodeError(`SdfProgram does not support this tree yet: ${unsupportedReason}.`);
6996
7760
  }
6997
- return value;
6998
- }
6999
- function cloneBounds$2(bounds) {
7000
- return { min: [...bounds.min], max: [...bounds.max] };
7001
- }
7002
- function suggestEdgeLengthForSampleBudget(bounds, maxGridPoints) {
7003
- const dx = bounds.max[0] - bounds.min[0];
7004
- const dy = bounds.max[1] - bounds.min[1];
7005
- const dz = bounds.max[2] - bounds.min[2];
7006
- const volume = Math.max(dx * dy * dz, 1);
7007
- return Math.cbrt(volume / Math.max(maxGridPoints, 8));
7761
+ const builder = new SdfProgramBuilder();
7762
+ return builder.finalize(emitSdfProgramNode(builder, node, builder.x, builder.y, builder.z));
7008
7763
  }
7009
- function formatBounds(bounds) {
7010
- return `[${bounds.min.map(formatNumber$1).join(",")}]-[${bounds.max.map(formatNumber$1).join(",")}]`;
7764
+ function compileSdfProgram3(node) {
7765
+ return compileSdfProgramEvaluator3(compileSdfProgram(node));
7011
7766
  }
7012
- function formatMm(value) {
7013
- return `${formatNumber$1(value)}mm`;
7014
- }
7015
- function formatNumber$1(value) {
7016
- return Number.isInteger(value) ? String(value) : value.toFixed(3).replace(/0+$/, "").replace(/\.$/, "");
7017
- }
7018
- function formatCount(value) {
7019
- return Math.round(value).toLocaleString("en-US");
7020
- }
7021
- function formatBytes(bytes) {
7022
- if (bytes < 1024 * 1024) return `${Math.ceil(bytes / 1024)} KB`;
7023
- return `${Math.ceil(bytes / (1024 * 1024))} MB`;
7767
+ function compileSdfMaterializationEvaluator3(node) {
7768
+ const unsupportedReason = getUnsupportedSdfProgramReason(node);
7769
+ if (unsupportedReason) {
7770
+ return {
7771
+ fn: compileSdfNode3(node),
7772
+ engine: "closure",
7773
+ unsupportedReason
7774
+ };
7775
+ }
7776
+ return {
7777
+ fn: compileSdfProgram3(node),
7778
+ engine: "program"
7779
+ };
7024
7780
  }
7025
7781
  function midpoint$3(a2, b) {
7026
7782
  return [(a2[0] + b[0]) / 2, (a2[1] + b[1]) / 2, (a2[2] + b[2]) / 2];
@@ -7486,7 +8242,7 @@ function buildCircleExtrusionTopology(circ, height, center = false) {
7486
8242
  );
7487
8243
  return { faces, edges };
7488
8244
  }
7489
- function requireFinite$8(v, label) {
8245
+ function requireFinite$9(v, label) {
7490
8246
  if (!Number.isFinite(v)) throw new Error(`nurbsSurface: ${label} must be finite, got ${v}`);
7491
8247
  }
7492
8248
  function normalizeSurfaceTessellation(tessellation) {
@@ -7496,11 +8252,11 @@ function normalizeSurfaceTessellation(tessellation) {
7496
8252
  throw new Error(`nurbsSurface: tessellation.mode must be "uniform" or "adaptive", got ${mode}`);
7497
8253
  }
7498
8254
  if (tessellation.tolerance !== void 0) {
7499
- requireFinite$8(tessellation.tolerance, "tessellation.tolerance");
8255
+ requireFinite$9(tessellation.tolerance, "tessellation.tolerance");
7500
8256
  if (tessellation.tolerance <= 0) throw new Error("nurbsSurface: tessellation.tolerance must be > 0");
7501
8257
  }
7502
- if (tessellation.minResolution !== void 0) requireFinite$8(tessellation.minResolution, "tessellation.minResolution");
7503
- if (tessellation.maxResolution !== void 0) requireFinite$8(tessellation.maxResolution, "tessellation.maxResolution");
8258
+ if (tessellation.minResolution !== void 0) requireFinite$9(tessellation.minResolution, "tessellation.minResolution");
8259
+ if (tessellation.maxResolution !== void 0) requireFinite$9(tessellation.maxResolution, "tessellation.maxResolution");
7504
8260
  const minResolution = tessellation.minResolution === void 0 ? void 0 : Math.max(2, Math.round(tessellation.minResolution));
7505
8261
  const maxResolution = tessellation.maxResolution === void 0 ? void 0 : Math.max(2, Math.round(tessellation.maxResolution));
7506
8262
  if (minResolution !== void 0 && maxResolution !== void 0 && minResolution > maxResolution) {
@@ -7519,10 +8275,10 @@ function normalizeSurfaceDomain(domain) {
7519
8275
  const uMax = domain.uMax ?? 1;
7520
8276
  const vMin = domain.vMin ?? 0;
7521
8277
  const vMax = domain.vMax ?? 1;
7522
- requireFinite$8(uMin, "domain.uMin");
7523
- requireFinite$8(uMax, "domain.uMax");
7524
- requireFinite$8(vMin, "domain.vMin");
7525
- requireFinite$8(vMax, "domain.vMax");
8278
+ requireFinite$9(uMin, "domain.uMin");
8279
+ requireFinite$9(uMax, "domain.uMax");
8280
+ requireFinite$9(vMin, "domain.vMin");
8281
+ requireFinite$9(vMax, "domain.vMax");
7526
8282
  if (uMin < 0 || uMax > 1 || vMin < 0 || vMax > 1) {
7527
8283
  throw new Error("nurbsSurface: domain bounds must stay within [0, 1]");
7528
8284
  }
@@ -7534,8 +8290,8 @@ function normalizeSurfaceDomain(domain) {
7534
8290
  function normalizeTrimLoop(loop, label) {
7535
8291
  if (loop.length < 3) throw new Error(`nurbsSurface: ${label} requires at least 3 points`);
7536
8292
  const normalized = loop.map(([u2, v], idx) => {
7537
- requireFinite$8(u2, `${label}[${idx}][0]`);
7538
- requireFinite$8(v, `${label}[${idx}][1]`);
8293
+ requireFinite$9(u2, `${label}[${idx}][0]`);
8294
+ requireFinite$9(v, `${label}[${idx}][1]`);
7539
8295
  if (u2 < 0 || u2 > 1 || v < 0 || v > 1) throw new Error(`nurbsSurface: ${label}[${idx}] must stay within [0, 1]`);
7540
8296
  return [u2, v];
7541
8297
  });
@@ -7558,8 +8314,8 @@ function normalizeTrimCurve(curve, label) {
7558
8314
  throw new Error(`nurbsSurface: ${label} needs at least ${degree + 1} control points for degree=${degree}`);
7559
8315
  }
7560
8316
  const normalizedControlPoints = controlPoints.map(([u2, v], idx) => {
7561
- requireFinite$8(u2, `${label}.controlPoints[${idx}][0]`);
7562
- requireFinite$8(v, `${label}.controlPoints[${idx}][1]`);
8317
+ requireFinite$9(u2, `${label}.controlPoints[${idx}][0]`);
8318
+ requireFinite$9(v, `${label}.controlPoints[${idx}][1]`);
7563
8319
  if (u2 < 0 || u2 > 1 || v < 0 || v > 1) {
7564
8320
  throw new Error(`nurbsSurface: ${label}.controlPoints[${idx}] must stay within [0, 1]`);
7565
8321
  }
@@ -7570,7 +8326,7 @@ function normalizeTrimCurve(curve, label) {
7570
8326
  throw new Error(`nurbsSurface: ${label}.weights length must match controlPoints length`);
7571
8327
  }
7572
8328
  for (let idx = 0; idx < weights.length; idx += 1) {
7573
- requireFinite$8(weights[idx], `${label}.weights[${idx}]`);
8329
+ requireFinite$9(weights[idx], `${label}.weights[${idx}]`);
7574
8330
  if (weights[idx] <= 0) throw new Error(`nurbsSurface: ${label}.weights[${idx}] must be > 0`);
7575
8331
  }
7576
8332
  const knots = curve.knots ?? generateClampedKnots(controlPoints.length, degree);
@@ -7578,7 +8334,7 @@ function normalizeTrimCurve(curve, label) {
7578
8334
  throw new Error(`nurbsSurface: ${label}.knots.length should be ${controlPoints.length + degree + 1}, got ${knots.length}`);
7579
8335
  }
7580
8336
  for (let idx = 0; idx < knots.length; idx += 1) {
7581
- requireFinite$8(knots[idx], `${label}.knots[${idx}]`);
8337
+ requireFinite$9(knots[idx], `${label}.knots[${idx}]`);
7582
8338
  if (idx > 0 && knots[idx] < knots[idx - 1]) throw new Error(`nurbsSurface: ${label}.knots must be non-decreasing`);
7583
8339
  }
7584
8340
  if (knots[degree] >= knots[controlPoints.length]) {
@@ -7758,16 +8514,16 @@ class NurbsSurface {
7758
8514
  for (let i = 0; i < nU; i++) {
7759
8515
  if (controlGrid[i].length !== nV) throw new Error(`nurbsSurface: row ${i} has ${controlGrid[i].length} points, expected ${nV}`);
7760
8516
  for (let j = 0; j < nV; j++) {
7761
- requireFinite$8(controlGrid[i][j][0], `controlGrid[${i}][${j}][0]`);
7762
- requireFinite$8(controlGrid[i][j][1], `controlGrid[${i}][${j}][1]`);
7763
- requireFinite$8(controlGrid[i][j][2], `controlGrid[${i}][${j}][2]`);
8517
+ requireFinite$9(controlGrid[i][j][0], `controlGrid[${i}][${j}][0]`);
8518
+ requireFinite$9(controlGrid[i][j][1], `controlGrid[${i}][${j}][1]`);
8519
+ requireFinite$9(controlGrid[i][j][2], `controlGrid[${i}][${j}][2]`);
7764
8520
  }
7765
8521
  }
7766
8522
  const weightsGrid = options.weights ?? controlGrid.map((row) => row.map(() => 1));
7767
8523
  for (let i = 0; i < nU; i++) {
7768
8524
  if (weightsGrid[i].length !== nV) throw new Error(`nurbsSurface: weights row ${i} length mismatch`);
7769
8525
  for (let j = 0; j < nV; j++) {
7770
- requireFinite$8(weightsGrid[i][j], `weights[${i}][${j}]`);
8526
+ requireFinite$9(weightsGrid[i][j], `weights[${i}][${j}]`);
7771
8527
  if (weightsGrid[i][j] <= 0) throw new Error(`nurbsSurface: weights[${i}][${j}] must be > 0`);
7772
8528
  }
7773
8529
  }
@@ -9157,8 +9913,8 @@ function stitchSingleLoopLoft(loops, heights, wasm) {
9157
9913
  const v0 = baseIdx + j;
9158
9914
  const v1 = nextIdx + j;
9159
9915
  const v2 = nextIdx + j1;
9160
- const v3 = baseIdx + j1;
9161
- triangles.push(v0, v3, v2);
9916
+ const v32 = baseIdx + j1;
9917
+ triangles.push(v0, v32, v2);
9162
9918
  triangles.push(v0, v2, v1);
9163
9919
  }
9164
9920
  }
@@ -9199,7 +9955,7 @@ let _wasm$1 = null;
9199
9955
  async function initManifoldWasm() {
9200
9956
  if (_wasm$1) return _wasm$1;
9201
9957
  performance.mark("manifold:start");
9202
- const Module = (await import("./manifold-DTvmxSDf.js")).default;
9958
+ const Module = (await import("./manifold-Dp6pvFr6.js")).default;
9203
9959
  performance.mark("manifold:imported");
9204
9960
  const wasm = await Module();
9205
9961
  wasm.setup();
@@ -9481,8 +10237,8 @@ function stitchLoopAlongPath(loop, _path, frames, wasm) {
9481
10237
  const v0 = baseIdx + j;
9482
10238
  const v1 = nextIdx + j;
9483
10239
  const v2 = nextIdx + j1;
9484
- const v3 = baseIdx + j1;
9485
- triangles.push(v0, v3, v2);
10240
+ const v32 = baseIdx + j1;
10241
+ triangles.push(v0, v32, v2);
9486
10242
  triangles.push(v0, v2, v1);
9487
10243
  }
9488
10244
  }
@@ -10828,8 +11584,16 @@ function lowerShapeCompilePlanToManifold(plan, wasm) {
10828
11584
  case "importedMesh":
10829
11585
  return lowerImportedMeshToManifold(plan.fileData, plan.format, plan.filePath, wasm);
10830
11586
  case "sdf": {
10831
- const evalFn = compileSdfNode3(plan.tree);
10832
- return lowerSdfToManifold(evalFn, plan.bounds, plan.edgeLength, wasm, plan.meshing);
11587
+ const evaluator = compileSdfMaterializationEvaluator3(plan.tree);
11588
+ return lowerSdfToManifold(
11589
+ evaluator.fn,
11590
+ plan.bounds,
11591
+ plan.edgeLength,
11592
+ wasm,
11593
+ plan.meshing,
11594
+ evaluator.engine,
11595
+ evaluator.unsupportedReason
11596
+ );
10833
11597
  }
10834
11598
  case "fromSlices":
10835
11599
  return lowerFromSlicesToManifold(plan, wasm);
@@ -10847,8 +11611,12 @@ function lowerShapeCompilePlanToManifold(plan, wasm) {
10847
11611
  assertExhaustive(plan);
10848
11612
  }
10849
11613
  }
10850
- function lowerSdfToManifold(evalFn, bounds, edgeLength2, wasm, meshing) {
11614
+ function lowerSdfToManifold(evalFn, bounds, edgeLength2, wasm, meshing, evaluatorEngine, evaluatorUnsupportedReason) {
10851
11615
  const diagnostics = (meshing == null ? void 0 : meshing.diagnostics) ? { ...meshing.diagnostics } : void 0;
11616
+ if (diagnostics && evaluatorEngine) {
11617
+ diagnostics.evaluator = evaluatorEngine;
11618
+ if (evaluatorUnsupportedReason) diagnostics.evaluatorUnsupportedReason = evaluatorUnsupportedReason;
11619
+ }
10852
11620
  const inset = edgeLength2;
10853
11621
  const cappedEvalFn = (x2, y2, z2) => {
10854
11622
  const bx = Math.max(bounds.min[0] + inset - x2, x2 - bounds.max[0] + inset);
@@ -16294,9 +17062,9 @@ function requireClipper() {
16294
17062
  if (ClipperLib2.use_xyz) j.OffPt.Z = OffPt.Z;
16295
17063
  this.m_Joins.push(j);
16296
17064
  };
16297
- ClipperLib2.Clipper.prototype.AddGhostJoin = function(Op, OffPt) {
17065
+ ClipperLib2.Clipper.prototype.AddGhostJoin = function(Op2, OffPt) {
16298
17066
  var j = new ClipperLib2.Join();
16299
- j.OutPt1 = Op;
17067
+ j.OutPt1 = Op2;
16300
17068
  j.OffPt.X = OffPt.X;
16301
17069
  j.OffPt.Y = OffPt.Y;
16302
17070
  if (ClipperLib2.use_xyz) j.OffPt.Z = OffPt.Z;
@@ -19198,7 +19966,7 @@ function requireClipper() {
19198
19966
  }
19199
19967
  var clipperExports = requireClipper();
19200
19968
  var ClipperLib = /* @__PURE__ */ getDefaultExportFromCjs(clipperExports);
19201
- let f$1 = class f {
19969
+ let f$3 = class f {
19202
19970
  constructor(t, e) {
19203
19971
  this.next = null, this.key = t, this.data = e, this.left = null, this.right = null;
19204
19972
  }
@@ -19207,7 +19975,7 @@ function d(n, t) {
19207
19975
  return n > t ? 1 : n < t ? -1 : 0;
19208
19976
  }
19209
19977
  function u$1(n, t, e) {
19210
- const r = new f$1(null, null);
19978
+ const r = new f$3(null, null);
19211
19979
  let l = r, i = r;
19212
19980
  for (; ; ) {
19213
19981
  const o = e(n, t.key);
@@ -19230,7 +19998,7 @@ function u$1(n, t, e) {
19230
19998
  return l.right = t.left, i.left = t.right, t.left = r.right, t.right = r.left, t;
19231
19999
  }
19232
20000
  function c(n, t, e, r) {
19233
- const l = new f$1(n, t);
20001
+ const l = new f$3(n, t);
19234
20002
  if (e === null)
19235
20003
  return l.left = l.right = null, l;
19236
20004
  e = u$1(n, e, r);
@@ -19271,7 +20039,7 @@ class z {
19271
20039
  * Adds a key, if it is not present in the tree
19272
20040
  */
19273
20041
  add(t, e) {
19274
- const r = new f$1(t, e);
20042
+ const r = new f$3(t, e);
19275
20043
  this._root === null && (r.left = r.right = null, this._size++, this._root = r);
19276
20044
  const l = this._comparator, i = u$1(t, this._root, l), o = l(t, i.key);
19277
20045
  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;
@@ -19484,23 +20252,23 @@ class z {
19484
20252
  function a(n, t, e, r) {
19485
20253
  const l = r - e;
19486
20254
  if (l > 0) {
19487
- const i = e + Math.floor(l / 2), o = n[i], s = t[i], h = new f$1(o, s);
20255
+ const i = e + Math.floor(l / 2), o = n[i], s = t[i], h = new f$3(o, s);
19488
20256
  return h.left = a(n, t, e, i), h.right = a(n, t, i + 1, r), h;
19489
20257
  }
19490
20258
  return null;
19491
20259
  }
19492
20260
  function x(n, t) {
19493
- const e = new f$1(null, null);
20261
+ const e = new f$3(null, null);
19494
20262
  let r = e;
19495
20263
  for (let l = 0; l < n.length; l++)
19496
- r = r.next = new f$1(n[l], t[l]);
20264
+ r = r.next = new f$3(n[l], t[l]);
19497
20265
  return r.next = null, e.next;
19498
20266
  }
19499
20267
  function k(n) {
19500
20268
  let t = n;
19501
20269
  const e = [];
19502
20270
  let r = false;
19503
- const l = new f$1(null, null);
20271
+ const l = new f$3(null, null);
19504
20272
  let i = l;
19505
20273
  for (; !r; )
19506
20274
  t ? (e.push(t), t = t.left) : e.length > 0 ? (t = i = i.next = e.pop(), t = t.right) : r = true;
@@ -19515,7 +20283,7 @@ function p(n, t, e) {
19515
20283
  return null;
19516
20284
  }
19517
20285
  function y(n, t, e) {
19518
- const r = new f$1(null, null);
20286
+ const r = new f$3(null, null);
19519
20287
  let l = r, i = n, o = t;
19520
20288
  for (; i !== null && o !== null; )
19521
20289
  e(i.key, o.key) < 0 ? (l.next = i, i = i.next) : (l.next = o, o = o.next), l = l.next;
@@ -24500,7 +25268,13 @@ function normalizeTruckShapeForBooleanInput(shape) {
24500
25268
  return normalized;
24501
25269
  }
24502
25270
  function lowerSdfPlan(plan) {
24503
- const evalFn = compileSdfNode3(plan.tree);
25271
+ var _a3, _b3, _c2;
25272
+ const evaluator = compileSdfMaterializationEvaluator3(plan.tree);
25273
+ if ((_a3 = plan.meshing) == null ? void 0 : _a3.diagnostics) {
25274
+ plan.meshing.diagnostics.evaluator = evaluator.engine;
25275
+ if (evaluator.unsupportedReason) plan.meshing.diagnostics.evaluatorUnsupportedReason = evaluator.unsupportedReason;
25276
+ }
25277
+ const evalFn = evaluator.fn;
24504
25278
  const inset = plan.edgeLength;
24505
25279
  const cappedEvalFn = (x2, y2, z2) => {
24506
25280
  const bx = Math.max(plan.bounds.min[0] + inset - x2, x2 - plan.bounds.max[0] + inset);
@@ -24512,14 +25286,18 @@ function lowerSdfPlan(plan) {
24512
25286
  assertSdfMeshBudget(mesh, plan);
24513
25287
  let surfaceNetsError;
24514
25288
  try {
24515
- return lowerExtractedSdfMesh(mesh, cappedEvalFn, true);
25289
+ const shape = lowerExtractedSdfMesh(mesh, cappedEvalFn, true);
25290
+ if ((_b3 = plan.meshing) == null ? void 0 : _b3.diagnostics) logSdfMeshingDiagnostics("SDF meshing result", plan.meshing.diagnostics);
25291
+ return shape;
24516
25292
  } catch (error) {
24517
25293
  surfaceNetsError = error;
24518
25294
  }
24519
25295
  const tetraMesh = marchingTetrahedra(cappedEvalFn, plan.bounds, plan.edgeLength);
24520
25296
  assertSdfMeshBudget(tetraMesh, plan);
24521
25297
  try {
24522
- return lowerExtractedSdfMesh(tetraMesh, cappedEvalFn, false);
25298
+ const shape = lowerExtractedSdfMesh(tetraMesh, cappedEvalFn, false);
25299
+ if ((_c2 = plan.meshing) == null ? void 0 : _c2.diagnostics) logSdfMeshingDiagnostics("SDF meshing result", plan.meshing.diagnostics);
25300
+ return shape;
24523
25301
  } catch (error) {
24524
25302
  throw new Error(
24525
25303
  `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)}`
@@ -24883,7 +25661,9 @@ function lowerOffsetSolidPlan(plan) {
24883
25661
  if (base.kind === "transform") {
24884
25662
  return lowerTransformedOffsetSolidPlan(base, plan.thickness);
24885
25663
  }
24886
- return truckUnsupported(`compile plan "${plan.kind}" for non-vertical-prism/non-revolved/non-loft/non-straight-sweep/non-straight-variable-sweep solids`);
25664
+ return truckUnsupported(
25665
+ `compile plan "${plan.kind}" for non-vertical-prism/non-revolved/non-loft/non-straight-sweep/non-straight-variable-sweep solids`
25666
+ );
24887
25667
  }
24888
25668
  function lowerLoftPlan(plan) {
24889
25669
  return wrapTruckShapeBackend(
@@ -32185,6 +32965,37 @@ function mergeSketchPlacementModel(sketches) {
32185
32965
  }
32186
32966
  return first;
32187
32967
  }
32968
+ function normalizeSceneTags(value, label = "tags") {
32969
+ if (value == null) return [];
32970
+ const rawTags = typeof value === "string" ? [value] : value;
32971
+ if (!Array.isArray(rawTags)) {
32972
+ throw new Error(`${label} must be a string or array of strings`);
32973
+ }
32974
+ const out = [];
32975
+ const seen = /* @__PURE__ */ new Set();
32976
+ rawTags.forEach((tag, index2) => {
32977
+ if (typeof tag !== "string") {
32978
+ throw new Error(`${label}[${index2}] must be a string`);
32979
+ }
32980
+ const trimmed = tag.trim();
32981
+ if (!trimmed || seen.has(trimmed)) return;
32982
+ seen.add(trimmed);
32983
+ out.push(trimmed);
32984
+ });
32985
+ return out;
32986
+ }
32987
+ function mergeSceneTags(...values) {
32988
+ const out = [];
32989
+ const seen = /* @__PURE__ */ new Set();
32990
+ values.forEach((value) => {
32991
+ normalizeSceneTags(value).forEach((tag) => {
32992
+ if (seen.has(tag)) return;
32993
+ seen.add(tag);
32994
+ out.push(tag);
32995
+ });
32996
+ });
32997
+ return out;
32998
+ }
32188
32999
  const _groupPlacementRefs = /* @__PURE__ */ new WeakMap();
32189
33000
  const _groupExplodeHint = /* @__PURE__ */ new WeakMap();
32190
33001
  function getGroupRefs(g2) {
@@ -32208,7 +33019,7 @@ function transformGroupRefs(source, dest, matrix) {
32208
33019
  }
32209
33020
  return dest;
32210
33021
  }
32211
- function requireFiniteAngle(v, method) {
33022
+ function requireFiniteAngle$1(v, method) {
32212
33023
  if (typeof v !== "number" || !Number.isFinite(v))
32213
33024
  throw new Error(`${method} angleDeg must be a finite number, got ${typeof v === "number" ? v : typeof v}`);
32214
33025
  }
@@ -32269,31 +33080,46 @@ function resolveNamedGroupChild(item) {
32269
33080
  function normalizeGroupInputs(items) {
32270
33081
  const children = [];
32271
33082
  const childNames = [];
33083
+ const childTags = [];
32272
33084
  items.forEach((item) => {
32273
33085
  if (isNamedGroupChild(item)) {
32274
33086
  children.push(resolveNamedGroupChild(item));
32275
33087
  childNames.push(normalizeChildName(item.name));
33088
+ childTags.push(normalizeSceneTags(item.tags, `group(...) named item "${item.name}" tags`));
32276
33089
  return;
32277
33090
  }
32278
33091
  children.push(item);
32279
33092
  childNames.push(void 0);
33093
+ childTags.push([]);
32280
33094
  });
32281
- return { children, childNames };
33095
+ return { children, childNames, childTags };
32282
33096
  }
32283
33097
  class ShapeGroup {
32284
- constructor(children, childNames) {
33098
+ constructor(children, childNames, childTags) {
32285
33099
  __publicField(this, "children");
32286
33100
  __publicField(this, "childNames");
33101
+ __publicField(this, "childTags");
32287
33102
  if (childNames && childNames.length !== children.length) {
32288
33103
  throw new Error("ShapeGroup childNames must match children length");
32289
33104
  }
33105
+ if (childTags && childTags.length !== children.length) {
33106
+ throw new Error("ShapeGroup childTags must match children length");
33107
+ }
32290
33108
  this.children = [...children];
32291
33109
  this.childNames = this.children.map((_2, index2) => normalizeChildName(childNames == null ? void 0 : childNames[index2]));
33110
+ this.childTags = this.children.map((_2, index2) => normalizeSceneTags(childTags == null ? void 0 : childTags[index2], "ShapeGroup childTags"));
32292
33111
  }
32293
33112
  /** Return the optional name of the child at `index`. */
32294
33113
  childName(index2) {
32295
33114
  return this.childNames[index2];
32296
33115
  }
33116
+ /**
33117
+ * Return tags attached to the child at `index`.
33118
+ * @internal
33119
+ */
33120
+ tagsForChild(index2) {
33121
+ return [...this.childTags[index2] ?? []];
33122
+ }
32297
33123
  /**
32298
33124
  * Return the named child by name. Throws if not found.
32299
33125
  * Useful when importing a multipart group and working on components individually.
@@ -32308,13 +33134,13 @@ class ShapeGroup {
32308
33134
  }
32309
33135
  /** Apply fn to all children, producing a new ShapeGroup that also copies placement refs. */
32310
33136
  mapChildren(fn) {
32311
- const next = new ShapeGroup(this.children.map(fn), this.childNames);
33137
+ const next = new ShapeGroup(this.children.map(fn), this.childNames, this.childTags);
32312
33138
  copyGroupPorts(this, next);
32313
33139
  return copyGroupRefs(this, next);
32314
33140
  }
32315
33141
  /** Apply fn to all children and also transform placement refs by the given matrix. */
32316
33142
  mapChildrenTransform(fn, matrix) {
32317
- const next = new ShapeGroup(this.children.map(fn), this.childNames);
33143
+ const next = new ShapeGroup(this.children.map(fn), this.childNames, this.childTags);
32318
33144
  transformGroupPortsHelper(this, next, matrix);
32319
33145
  return transformGroupRefs(this, next, matrix);
32320
33146
  }
@@ -32441,25 +33267,25 @@ class ShapeGroup {
32441
33267
  /** Rotate the group around an arbitrary axis through the origin. */
32442
33268
  rotate(axis, angleDeg, options) {
32443
33269
  requireRotateAxis(axis, "ShapeGroup.rotate()");
32444
- requireFiniteAngle(angleDeg, "ShapeGroup.rotate()");
33270
+ requireFiniteAngle$1(angleDeg, "ShapeGroup.rotate()");
32445
33271
  if (options == null ? void 0 : options.pivot) requireVec3Pivot(options.pivot, "ShapeGroup.rotate()");
32446
33272
  return this.rotateAroundAxis(axis, angleDeg, options == null ? void 0 : options.pivot);
32447
33273
  }
32448
33274
  /** Rotate the group around the X axis. */
32449
33275
  rotateX(angleDeg, options) {
32450
- requireFiniteAngle(angleDeg, "ShapeGroup.rotateX()");
33276
+ requireFiniteAngle$1(angleDeg, "ShapeGroup.rotateX()");
32451
33277
  if (options == null ? void 0 : options.pivot) requireVec3Pivot(options.pivot, "ShapeGroup.rotateX()");
32452
33278
  return this.rotateAroundAxis([1, 0, 0], angleDeg, options == null ? void 0 : options.pivot);
32453
33279
  }
32454
33280
  /** Rotate the group around the Y axis. */
32455
33281
  rotateY(angleDeg, options) {
32456
- requireFiniteAngle(angleDeg, "ShapeGroup.rotateY()");
33282
+ requireFiniteAngle$1(angleDeg, "ShapeGroup.rotateY()");
32457
33283
  if (options == null ? void 0 : options.pivot) requireVec3Pivot(options.pivot, "ShapeGroup.rotateY()");
32458
33284
  return this.rotateAroundAxis([0, 1, 0], angleDeg, options == null ? void 0 : options.pivot);
32459
33285
  }
32460
33286
  /** Rotate the group around the Z axis. */
32461
33287
  rotateZ(angleDeg, options) {
32462
- requireFiniteAngle(angleDeg, "ShapeGroup.rotateZ()");
33288
+ requireFiniteAngle$1(angleDeg, "ShapeGroup.rotateZ()");
32463
33289
  if (options == null ? void 0 : options.pivot) requireVec3Pivot(options.pivot, "ShapeGroup.rotateZ()");
32464
33290
  return this.rotateAroundAxis([0, 0, 1], angleDeg, options == null ? void 0 : options.pivot);
32465
33291
  }
@@ -32502,7 +33328,8 @@ class ShapeGroup {
32502
33328
  "ShapeGroup.transform only supports 3D children (Shape/ShapeGroup). For Sketch children, use 2D transforms (translate/rotate/scale/mirror)."
32503
33329
  );
32504
33330
  }),
32505
- this.childNames
33331
+ this.childNames,
33332
+ this.childTags
32506
33333
  );
32507
33334
  transformGroupPortsHelper(this, next, matrix);
32508
33335
  return transformGroupRefs(this, next, matrix);
@@ -32570,7 +33397,7 @@ class ShapeGroup {
32570
33397
  * ```
32571
33398
  */
32572
33399
  withReferences(refs) {
32573
- const next = new ShapeGroup(this.children, this.childNames);
33400
+ const next = new ShapeGroup(this.children, this.childNames, this.childTags);
32574
33401
  const merged = applyPlacementReferenceInput(getGroupRefs(this), refs);
32575
33402
  return setGroupRefs(next, merged);
32576
33403
  }
@@ -32638,7 +33465,7 @@ class ShapeGroup {
32638
33465
  /** Attach named connectors — attachment points that survive transforms.
32639
33466
  * Connectors can be bare (position + orientation) or typed (with connectorType/gender for compatibility matching). */
32640
33467
  withConnectors(connectors) {
32641
- const next = new ShapeGroup(this.children, this.childNames);
33468
+ const next = new ShapeGroup(this.children, this.childNames, this.childTags);
32642
33469
  copyGroupRefs(this, next);
32643
33470
  const existing = getGroupPorts(this);
32644
33471
  const incoming = normalizeConnectorMapInput(connectors);
@@ -32688,7 +33515,7 @@ class ShapeGroup {
32688
33515
  }
32689
33516
  function group(...items) {
32690
33517
  const normalized = normalizeGroupInputs(items);
32691
- return new ShapeGroup(normalized.children, normalized.childNames);
33518
+ return new ShapeGroup(normalized.children, normalized.childNames, normalized.childTags);
32692
33519
  }
32693
33520
  function getTargetPortsForGroup(target) {
32694
33521
  if (target instanceof Shape) {
@@ -34859,7 +35686,7 @@ function buildSdfFunctionDefinition(source, options) {
34859
35686
  jsExpression: expression,
34860
35687
  ...shader.ok ? { shaderExpression: shader.expression } : { shaderUnsupportedReason: shader.reason },
34861
35688
  raymarchStepLimit: resolveRaymarchStepLimit(options.bounds, options.maxStep),
34862
- ...options.lipschitz !== void 0 ? { raymarchLipschitz: requirePositiveFinite$1(options.lipschitz, "sdf.fromFunction() lipschitz") } : {}
35689
+ ...options.lipschitz !== void 0 ? { raymarchLipschitz: requirePositiveFinite$2(options.lipschitz, "sdf.fromFunction() lipschitz") } : {}
34863
35690
  };
34864
35691
  }
34865
35692
  function extractSdfExpression(source) {
@@ -35027,7 +35854,7 @@ function formatNumericLiteralsForGlsl(source) {
35027
35854
  return result;
35028
35855
  }
35029
35856
  function resolveRaymarchStepLimit(bounds, maxStep) {
35030
- if (maxStep !== void 0) return requirePositiveFinite$1(maxStep, "sdf.fromFunction() maxStep");
35857
+ if (maxStep !== void 0) return requirePositiveFinite$2(maxStep, "sdf.fromFunction() maxStep");
35031
35858
  const dx = bounds.max[0] - bounds.min[0];
35032
35859
  const dy = bounds.max[1] - bounds.min[1];
35033
35860
  const dz = bounds.max[2] - bounds.min[2];
@@ -35035,7 +35862,7 @@ function resolveRaymarchStepLimit(bounds, maxStep) {
35035
35862
  if (!Number.isFinite(diagonal) || diagonal <= 0) return 0.1;
35036
35863
  return Math.max(0.025, Math.min(0.5, diagonal / 240));
35037
35864
  }
35038
- function requirePositiveFinite$1(value, label) {
35865
+ function requirePositiveFinite$2(value, label) {
35039
35866
  if (!Number.isFinite(value) || value <= 0) throw new Error(`${label} must be a positive finite number.`);
35040
35867
  return value;
35041
35868
  }
@@ -35062,6 +35889,199 @@ class SurfacePattern {
35062
35889
  this.constants = constants;
35063
35890
  }
35064
35891
  }
35892
+ const typedSurfacePatterns = /* @__PURE__ */ new WeakMap();
35893
+ function getTypedSurfacePattern(pattern) {
35894
+ return typedSurfacePatterns.get(pattern);
35895
+ }
35896
+ class Pattern2D extends SurfacePattern {
35897
+ constructor(body) {
35898
+ super(body);
35899
+ }
35900
+ /** Add this pattern to one or more patterns or constant height offsets. */
35901
+ add(...patterns) {
35902
+ throw new Error("Pattern2D values are created by sdf.pattern2d().");
35903
+ }
35904
+ /** Subtract another pattern or constant height offset from this pattern. */
35905
+ subtract(pattern) {
35906
+ throw new Error("Pattern2D values are created by sdf.pattern2d().");
35907
+ }
35908
+ /** Multiply this pattern by one or more patterns or numeric scale factors. */
35909
+ multiply(...patterns) {
35910
+ throw new Error("Pattern2D values are created by sdf.pattern2d().");
35911
+ }
35912
+ /** Keep the lower height between this pattern and one or more other patterns. */
35913
+ min(...patterns) {
35914
+ throw new Error("Pattern2D values are created by sdf.pattern2d().");
35915
+ }
35916
+ /** Keep the higher height between this pattern and one or more other patterns. */
35917
+ max(...patterns) {
35918
+ throw new Error("Pattern2D values are created by sdf.pattern2d().");
35919
+ }
35920
+ /** Limit pattern height to the inclusive `[min, max]` range in millimeters. */
35921
+ clamp(min2, max2) {
35922
+ throw new Error("Pattern2D values are created by sdf.pattern2d().");
35923
+ }
35924
+ /** Convert negative heights to positive heights. */
35925
+ abs() {
35926
+ throw new Error("Pattern2D values are created by sdf.pattern2d().");
35927
+ }
35928
+ /** Flip the pattern height sign. */
35929
+ negate() {
35930
+ throw new Error("Pattern2D values are created by sdf.pattern2d().");
35931
+ }
35932
+ }
35933
+ class Pattern2DImpl extends Pattern2D {
35934
+ constructor(node) {
35935
+ super(emitSurfacePatternJsExpression(node));
35936
+ __publicField(this, "node");
35937
+ this.node = node;
35938
+ typedSurfacePatterns.set(this, node);
35939
+ }
35940
+ add(...patterns) {
35941
+ return new Pattern2DImpl({ kind: "surfacePattern:add", children: [this.node, ...patterns.map(patternNodeFromInput)] });
35942
+ }
35943
+ subtract(pattern) {
35944
+ return this.add(new Pattern2DImpl({ kind: "surfacePattern:negate", child: patternNodeFromInput(pattern) }));
35945
+ }
35946
+ multiply(...patterns) {
35947
+ return new Pattern2DImpl({ kind: "surfacePattern:multiply", children: [this.node, ...patterns.map(patternNodeFromInput)] });
35948
+ }
35949
+ min(...patterns) {
35950
+ return new Pattern2DImpl({ kind: "surfacePattern:min", children: [this.node, ...patterns.map(patternNodeFromInput)] });
35951
+ }
35952
+ max(...patterns) {
35953
+ return new Pattern2DImpl({ kind: "surfacePattern:max", children: [this.node, ...patterns.map(patternNodeFromInput)] });
35954
+ }
35955
+ clamp(min2, max2) {
35956
+ const lo = requireFinite$8(min2, "Pattern2D.clamp() min");
35957
+ const hi = requireFinite$8(max2, "Pattern2D.clamp() max");
35958
+ if (lo > hi) throw new Error(`Pattern2D.clamp() min must be <= max. Received: ${lo} > ${hi}`);
35959
+ return new Pattern2DImpl({ kind: "surfacePattern:clamp", child: this.node, min: lo, max: hi });
35960
+ }
35961
+ abs() {
35962
+ return new Pattern2DImpl({ kind: "surfacePattern:abs", child: this.node });
35963
+ }
35964
+ negate() {
35965
+ return new Pattern2DImpl({ kind: "surfacePattern:negate", child: this.node });
35966
+ }
35967
+ }
35968
+ class Pattern2DBuilder {
35969
+ /** Create a constant-height pattern in millimeters. */
35970
+ constant(value = 0) {
35971
+ return new Pattern2DImpl({ kind: "surfacePattern:constant", value: requireFinite$8(value, "sdf.pattern2d().constant() value") });
35972
+ }
35973
+ /** Create a sinusoidal wave pattern in UV space. */
35974
+ sineWave(options) {
35975
+ return new Pattern2DImpl({
35976
+ kind: "surfacePattern:sineWave",
35977
+ direction: normalizeDirection$1(options.direction ?? [1, 0], "sdf.pattern2d().sineWave() direction"),
35978
+ wavelength: requirePositiveFinite$1(options.wavelength, "sdf.pattern2d().sineWave() wavelength"),
35979
+ amplitude: requireFinite$8(options.amplitude ?? 1, "sdf.pattern2d().sineWave() amplitude"),
35980
+ phase: requireFinite$8(options.phase ?? 0, "sdf.pattern2d().sineWave() phase"),
35981
+ bias: requireFinite$8(options.bias ?? 0, "sdf.pattern2d().sineWave() bias")
35982
+ });
35983
+ }
35984
+ /** Create recessed stripe bands in UV space. */
35985
+ stripes(options) {
35986
+ return new Pattern2DImpl({
35987
+ kind: "surfacePattern:stripes",
35988
+ direction: normalizeDirection$1(options.direction ?? [1, 0], "sdf.pattern2d().stripes() direction"),
35989
+ spacing: requirePositiveFinite$1(options.spacing, "sdf.pattern2d().stripes() spacing"),
35990
+ width: requirePositiveFinite$1(options.width, "sdf.pattern2d().stripes() width"),
35991
+ depth: requireNonNegativeFinite$1(options.depth ?? 1, "sdf.pattern2d().stripes() depth")
35992
+ });
35993
+ }
35994
+ /** Create an over-under woven relief pattern in UV space. */
35995
+ overUnderWeave(options) {
35996
+ return new Pattern2DImpl({
35997
+ kind: "surfacePattern:overUnderWeave",
35998
+ spacing: normalizeVec2(options.spacing, "sdf.pattern2d().overUnderWeave() spacing", requirePositiveFinite$1),
35999
+ threadWidth: normalizeVec2(options.threadWidth, "sdf.pattern2d().overUnderWeave() threadWidth", requirePositiveFinite$1),
36000
+ depth: requireNonNegativeFinite$1(options.depth ?? 0.8, "sdf.pattern2d().overUnderWeave() depth"),
36001
+ underScale: requireNonNegativeFinite$1(options.underScale ?? 0.15, "sdf.pattern2d().overUnderWeave() underScale")
36002
+ });
36003
+ }
36004
+ }
36005
+ function pattern2d() {
36006
+ return new Pattern2DBuilder();
36007
+ }
36008
+ function patternNodeFromInput(input) {
36009
+ if (input instanceof SurfacePattern) {
36010
+ const node = getTypedSurfacePattern(input);
36011
+ if (node) return node;
36012
+ }
36013
+ if (typeof input === "number") {
36014
+ return { kind: "surfacePattern:constant", value: requireFinite$8(input, "Pattern2D numeric input") };
36015
+ }
36016
+ throw new Error("Pattern2D composition expects another typed Pattern2D or a number.");
36017
+ }
36018
+ function requireFinite$8(value, label) {
36019
+ if (typeof value !== "number" || !Number.isFinite(value)) {
36020
+ throw new Error(`${label} must be a finite number. Received: ${String(value)}`);
36021
+ }
36022
+ return value;
36023
+ }
36024
+ function requirePositiveFinite$1(value, label) {
36025
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
36026
+ throw new Error(`${label} must be a positive finite number. Received: ${String(value)}`);
36027
+ }
36028
+ return value;
36029
+ }
36030
+ function requireNonNegativeFinite$1(value, label) {
36031
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
36032
+ throw new Error(`${label} must be a non-negative finite number. Received: ${String(value)}`);
36033
+ }
36034
+ return value;
36035
+ }
36036
+ function normalizeVec2(value, label, validate) {
36037
+ if (typeof value === "number") {
36038
+ const n = validate(value, label);
36039
+ return [n, n];
36040
+ }
36041
+ return [validate(value[0], `${label}[0]`), validate(value[1], `${label}[1]`)];
36042
+ }
36043
+ function normalizeDirection$1(value, label) {
36044
+ const x2 = requireFinite$8(value[0], `${label}[0]`);
36045
+ const y2 = requireFinite$8(value[1], `${label}[1]`);
36046
+ const length4 = Math.hypot(x2, y2);
36047
+ if (length4 <= 0) throw new Error(`${label} must not be the zero vector.`);
36048
+ return [x2 / length4, y2 / length4];
36049
+ }
36050
+ function f$2(value) {
36051
+ if (!Number.isFinite(value)) return "0";
36052
+ return Number(value.toPrecision(12)).toString();
36053
+ }
36054
+ function emitSurfacePatternJsExpression(node) {
36055
+ switch (node.kind) {
36056
+ case "surfacePattern:constant":
36057
+ return f$2(node.value);
36058
+ case "surfacePattern:sineWave": {
36059
+ const coord = `(u * ${f$2(node.direction[0])} + v * ${f$2(node.direction[1])})`;
36060
+ const phase = `(${coord} * ${f$2(2 * Math.PI / node.wavelength)} + ${f$2(node.phase)})`;
36061
+ return `(${f$2(node.bias)} + Math.sin(${phase}) * ${f$2(node.amplitude)})`;
36062
+ }
36063
+ case "surfacePattern:stripes": {
36064
+ const coord = `(u * ${f$2(node.direction[0])} + v * ${f$2(node.direction[1])})`;
36065
+ 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)};})()`;
36066
+ }
36067
+ case "surfacePattern:overUnderWeave":
36068
+ 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)};})()`;
36069
+ case "surfacePattern:abs":
36070
+ return `Math.abs(${emitSurfacePatternJsExpression(node.child)})`;
36071
+ case "surfacePattern:negate":
36072
+ return `(-(${emitSurfacePatternJsExpression(node.child)}))`;
36073
+ case "surfacePattern:add":
36074
+ return node.children.length === 0 ? "0" : `(${node.children.map(emitSurfacePatternJsExpression).join(" + ")})`;
36075
+ case "surfacePattern:multiply":
36076
+ return node.children.length === 0 ? "1" : `(${node.children.map(emitSurfacePatternJsExpression).join(" * ")})`;
36077
+ case "surfacePattern:min":
36078
+ return node.children.length === 0 ? "0" : `Math.min(${node.children.map(emitSurfacePatternJsExpression).join(", ")})`;
36079
+ case "surfacePattern:max":
36080
+ return node.children.length === 0 ? "0" : `Math.max(${node.children.map(emitSurfacePatternJsExpression).join(", ")})`;
36081
+ case "surfacePattern:clamp":
36082
+ return `Math.min(${f$2(node.max)}, Math.max(${f$2(node.min)}, ${emitSurfacePatternJsExpression(node.child)}))`;
36083
+ }
36084
+ }
35065
36085
  const SCULPT_MATERIAL_PRESETS = {
35066
36086
  ceramic: {
35067
36087
  color: "#f4f0e6",
@@ -35131,6 +36151,18 @@ function requirePositiveFinite(value, label) {
35131
36151
  }
35132
36152
  return value;
35133
36153
  }
36154
+ function requirePositiveInteger(value, label) {
36155
+ if (typeof value !== "number" || !Number.isFinite(value) || !Number.isInteger(value) || value < 1) {
36156
+ throw new Error(`${label} must be a positive integer. Received: ${String(value)}`);
36157
+ }
36158
+ return value;
36159
+ }
36160
+ function requireNonNegativeFinite(value, label) {
36161
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
36162
+ throw new Error(`${label} must be a non-negative finite number. Received: ${String(value)}`);
36163
+ }
36164
+ return value;
36165
+ }
35134
36166
  function resolveBlendRadius(input, label, fallback = 4) {
35135
36167
  if (typeof input === "number") return requirePositiveFinite(input, `${label} radius`);
35136
36168
  if ((input == null ? void 0 : input.radius) !== void 0) return requirePositiveFinite(input.radius, `${label} radius`);
@@ -35377,6 +36409,29 @@ class SdfShape {
35377
36409
  clipBox(x2, y2, z2) {
35378
36410
  return this.intersect(box$1(x2, y2, z2));
35379
36411
  }
36412
+ /** Keep only the material where this shape overlaps another SDF pattern. */
36413
+ fillWith(pattern) {
36414
+ if (!(pattern instanceof SdfShape)) {
36415
+ throw new Error("SdfShape.fillWith() expects an SdfShape pattern, such as sdf.gyroid({ cellSize, wallThickness }).");
36416
+ }
36417
+ return this.intersect(pattern);
36418
+ }
36419
+ /** Keep only the gyroid lattice inside this shape. */
36420
+ fillWithGyroid(options) {
36421
+ return this.fillWith(gyroid(options));
36422
+ }
36423
+ /** Keep only the Schwarz-P lattice inside this shape. */
36424
+ fillWithSchwarzP(options) {
36425
+ return this.fillWith(schwarzP(options));
36426
+ }
36427
+ /** Keep only the diamond TPMS lattice inside this shape. */
36428
+ fillWithDiamond(options) {
36429
+ return this.fillWith(diamond(options));
36430
+ }
36431
+ /** Keep only the lidinoid TPMS lattice inside this shape. */
36432
+ fillWithLidinoid(options) {
36433
+ return this.fillWith(lidinoid(options));
36434
+ }
35380
36435
  /** Smooth union — blends shapes together with a smooth radius. */
35381
36436
  smoothUnion(other, radius) {
35382
36437
  return this.withNode({ kind: "sdf:smoothUnion", children: [this._node, other._node], radius });
@@ -35437,6 +36492,21 @@ class SdfShape {
35437
36492
  repeat(spacing, count) {
35438
36493
  return this.withNode({ kind: "sdf:repeat", child: this._node, spacing, count: count ?? [0, 0, 0] });
35439
36494
  }
36495
+ /**
36496
+ * Arrange this SDF in a circular array around the Z axis.
36497
+ *
36498
+ * The source shape is translated by `offset` in +X before arraying. This uses
36499
+ * angular domain folding, so evaluation stays O(1): the source SDF is sampled
36500
+ * twice no matter how many copies are requested.
36501
+ */
36502
+ circularArray(count, offset2 = 0) {
36503
+ return this.withNode({
36504
+ kind: "sdf:circularArray",
36505
+ child: this._node,
36506
+ count: requirePositiveInteger(count, "SdfShape.circularArray() count"),
36507
+ offset: requireNonNegativeFinite(offset2, "SdfShape.circularArray() offset")
36508
+ });
36509
+ }
35440
36510
  /** Hollow out, keeping only a shell of given thickness. */
35441
36511
  shell(thickness) {
35442
36512
  return this.withNode({ kind: "sdf:shell", child: this._node, thickness });
@@ -35448,8 +36518,8 @@ class SdfShape {
35448
36518
  * // Function displacement
35449
36519
  * shape.displace((x, y, z) => Math.sin(x) * 0.5)
35450
36520
  *
35451
- * // Pattern displacement (e.g. basketWeave)
35452
- * shape.displace(sdf.basketWeave({ threads: 16, spacing: 3 }))
36521
+ * // Pattern displacement from a 3D SDF field
36522
+ * shape.displace(sdf.knurl({ pitch: 2, depth: 0.3 }))
35453
36523
  * ```
35454
36524
  */
35455
36525
  displace(fn, constants) {
@@ -35473,10 +36543,18 @@ class SdfShape {
35473
36543
  * UV coordinates are in **surface millimeters** — patterns defined with `spacing: 3`
35474
36544
  * always produce 3mm spacing, regardless of shape size.
35475
36545
  *
36546
+ * Prefer `sdf.pattern2d()` or built-in surface patterns when the relief should
36547
+ * stay on the native shader and meshing path. Callback functions are supported
36548
+ * for experimentation, but they are opaque to the typed pattern optimizer.
36549
+ *
35476
36550
  * ```js
35477
- * // Surface-following basket weave — auto-detects sphere UV
36551
+ * // Native typed pattern — auto-detects sphere UV
36552
+ * const p = sdf.pattern2d()
36553
+ * const ribs = p.stripes({ spacing: 3, width: 0.8, depth: 0.35 })
36554
+ * .add(p.sineWave({ direction: [0, 1], wavelength: 14, amplitude: 0.08 }))
36555
+ *
35478
36556
  * sdf.sphere(27).shell(3)
35479
- * .surfaceDisplace(sdf.basketWeave({ spacing: 3, depth: 0.8 }))
36557
+ * .surfaceDisplace(ribs)
35480
36558
  * .toShape()
35481
36559
  *
35482
36560
  * // Custom 2D pattern via function
@@ -35486,15 +36564,18 @@ class SdfShape {
35486
36564
  surfaceDisplace(pattern, options) {
35487
36565
  let body;
35488
36566
  let constants;
36567
+ let typedPattern;
35489
36568
  if (pattern instanceof SurfacePattern) {
35490
36569
  body = pattern.body;
35491
36570
  constants = pattern.constants;
36571
+ typedPattern = getTypedSurfacePattern(pattern);
35492
36572
  } else {
35493
36573
  body = extractFunctionBody(pattern);
35494
36574
  }
35495
36575
  return this.withNode({
35496
36576
  kind: "sdf:surfaceDisplace",
35497
36577
  child: this._node,
36578
+ ...typedPattern ? { pattern: typedPattern } : {},
35498
36579
  patternBody: body,
35499
36580
  constants,
35500
36581
  ...(options == null ? void 0 : options.uv) ? { uvMode: options.uv } : {},
@@ -35713,24 +36794,10 @@ function weave(options) {
35713
36794
  });
35714
36795
  }
35715
36796
  function basketWeave(options) {
35716
- const SP = (options == null ? void 0 : options.spacing) ?? 3;
35717
- const TW = (options == null ? void 0 : options.threadWidth) ?? 1.5;
35718
- const D2 = (options == null ? void 0 : options.depth) ?? 0.8;
35719
- const hw = TW * 0.5;
35720
- const body = `(function() {
35721
- var su = u / ${SP};
35722
- var sv = v / ${SP};
35723
- var du = Math.abs(su - Math.round(su)) * ${SP};
35724
- var dv = Math.abs(sv - Math.round(sv)) * ${SP};
35725
- var hw = ${hw};
35726
- var pU = Math.max(0, 1 - du / hw); pU *= pU;
35727
- var pV = Math.max(0, 1 - dv / hw); pV *= pV;
35728
- var checker = ((Math.round(su) & 65535) + (Math.round(sv) & 65535)) & 1;
35729
- var top = checker ? pV : pU;
35730
- var bot = checker ? pU : pV;
35731
- return -(top > bot * 0.15 ? top : bot * 0.15) * ${D2};
35732
- })()`;
35733
- return new SurfacePattern(body);
36797
+ const SP = requirePositiveFinite((options == null ? void 0 : options.spacing) ?? 3, "sdf.basketWeave() spacing");
36798
+ const TW = requirePositiveFinite((options == null ? void 0 : options.threadWidth) ?? 1.5, "sdf.basketWeave() threadWidth");
36799
+ const D2 = requireNonNegativeFinite((options == null ? void 0 : options.depth) ?? 0.8, "sdf.basketWeave() depth");
36800
+ return pattern2d().overUnderWeave({ spacing: SP, threadWidth: TW, depth: D2 });
35734
36801
  }
35735
36802
  function fromFunction(fn, options) {
35736
36803
  if (!options || typeof options !== "object") {
@@ -35763,6 +36830,9 @@ function bend(shape, radius) {
35763
36830
  function repeat(shape, spacing, count) {
35764
36831
  return shape.repeat(spacing, count);
35765
36832
  }
36833
+ function circularArray(shape, count, offset2 = 0) {
36834
+ return shape.circularArray(count, offset2);
36835
+ }
35766
36836
  function resolveTpmsOptions(options) {
35767
36837
  const wallThickness = options.wallThickness;
35768
36838
  const thickness = wallThickness ?? options.thickness;
@@ -36203,12 +37273,16 @@ const sdf = {
36203
37273
  weave,
36204
37274
  /** Basket weave surface pattern — threads with over-under crossings in UV space. Returns a SurfacePattern for use with `.surfaceDisplace()`. */
36205
37275
  basketWeave,
37276
+ /** Create typed, composable 2D surface patterns for `.surfaceDisplace()`. */
37277
+ pattern2d,
36206
37278
  /** Twist an SDF shape around the Z axis. */
36207
37279
  twist,
36208
37280
  /** Bend an SDF shape around the Z axis. */
36209
37281
  bend,
36210
37282
  /** Repeat an SDF shape in space. */
36211
37283
  repeat,
37284
+ /** Arrange an SDF shape in a circular array around the Z axis with O(1) folded-domain evaluation. */
37285
+ circularArray,
36212
37286
  /** A 2D surface pattern — a heightmap function for use with `.surfaceDisplace()`. */
36213
37287
  SurfacePattern,
36214
37288
  /** Create a custom SDF from one expression; shader-safe expressions raymarch directly. */
@@ -41128,13 +42202,16 @@ class SolvedAssembly {
41128
42202
  * @category Assembly
41129
42203
  */
41130
42204
  toGroup() {
42205
+ var _a3;
41131
42206
  const children = [];
41132
42207
  const childNames = [];
41133
- for (const [name] of this.parts) {
42208
+ const childTags = [];
42209
+ for (const [name, rec] of this.parts) {
41134
42210
  children.push(this.getPart(name));
41135
42211
  childNames.push(name);
42212
+ childTags.push(normalizeSceneTags((_a3 = rec.metadata) == null ? void 0 : _a3.tags, `Assembly part "${name}" metadata.tags`));
41136
42213
  }
41137
- return new ShapeGroup(children, childNames);
42214
+ return new ShapeGroup(children, childNames, childTags);
41138
42215
  }
41139
42216
  /**
41140
42217
  * Return an array of named scene objects for the viewport renderer.
@@ -41168,17 +42245,18 @@ class SolvedAssembly {
41168
42245
  const used = usedByPart.get(partName);
41169
42246
  if (used && used.length > 0) markShapePortsUsed(shape, used);
41170
42247
  };
41171
- const appendGroupChildren = (grp, prefix, partName, out2) => {
42248
+ const appendGroupChildren = (grp, prefix, partName, out2, inheritedTags = []) => {
41172
42249
  grp.children.forEach((child, index2) => {
41173
42250
  const childName = grp.childName(index2);
41174
42251
  const label = childName ? `${prefix}.${childName}` : `${prefix}.${index2 + 1}`;
42252
+ const tags = mergeSceneTags(inheritedTags, grp.tagsForChild(index2));
41175
42253
  if (child instanceof ShapeGroup) {
41176
- appendGroupChildren(child, label, partName, out2);
42254
+ appendGroupChildren(child, label, partName, out2, tags);
41177
42255
  return;
41178
42256
  }
41179
42257
  if (child instanceof Shape) {
41180
42258
  markUsedOnShape(child, partName);
41181
- out2.push({ name: label, shape: child });
42259
+ out2.push({ name: label, shape: child, ...tags.length > 0 ? { tags } : {} });
41182
42260
  }
41183
42261
  });
41184
42262
  };
@@ -41544,7 +42622,7 @@ class Assembly {
41544
42622
  *
41545
42623
  * @param name - Unique part name (must not already exist)
41546
42624
  * @param part - The `Shape` or `ShapeGroup` geometry
41547
- * @param options - Optional `{ transform, metadata }` (material, process, qty, etc.)
42625
+ * @param options - Optional `{ transform, metadata }` (material, process, qty, tags, etc.)
41548
42626
  * @returns `this` for chaining
41549
42627
  * @category Assembly
41550
42628
  */
@@ -42498,15 +43576,18 @@ class ImportedAssembly {
42498
43576
  * Any stored placement offset and placement references are forwarded to the group.
42499
43577
  */
42500
43578
  toGroup(state) {
43579
+ var _a3;
42501
43580
  const solved = this._assembly.solve(state);
42502
43581
  const def = this._assembly.describe();
42503
43582
  const children = [];
42504
43583
  const childNames = [];
43584
+ const childTags = [];
42505
43585
  for (const p2 of def.parts) {
42506
43586
  children.push(solved.getPart(p2.name));
42507
43587
  childNames.push(p2.name);
43588
+ childTags.push(normalizeSceneTags((_a3 = p2.metadata) == null ? void 0 : _a3.tags, `Assembly part "${p2.name}" metadata.tags`));
42508
43589
  }
42509
- let result = new ShapeGroup(children, childNames);
43590
+ let result = new ShapeGroup(children, childNames, childTags);
42510
43591
  const [dx, dy, dz] = this._offset;
42511
43592
  if (dx !== 0 || dy !== 0 || dz !== 0) {
42512
43593
  result = result.translate(dx, dy, dz);
@@ -43052,7 +44133,7 @@ class GCodeBuilder {
43052
44133
  this.lines.push("G1 E-0.8 F1800 ; retract");
43053
44134
  this.lines.push("");
43054
44135
  const safeZ = Math.min(maxZ + 5, p2.bedZ - 1);
43055
- this.lines.push(`G1 Z${f2(safeZ)} F900 ; lift nozzle above print`);
44136
+ this.lines.push(`G1 Z${f$1(safeZ)} F900 ; lift nozzle above print`);
43056
44137
  this.lines.push("");
43057
44138
  this.lines.push("M140 S0 ; bed off");
43058
44139
  this.lines.push("M104 S0 ; hotend off");
@@ -43096,7 +44177,7 @@ class GCodeBuilder {
43096
44177
  travelTo(x2, y2, z2) {
43097
44178
  this.retract();
43098
44179
  const from = [...this.pos];
43099
- this.lines.push(`G0 X${f2(x2)} Y${f2(y2)} Z${f2(z2)} F${Math.round(this.profile.travelSpeed)}`);
44180
+ this.lines.push(`G0 X${f$1(x2)} Y${f$1(y2)} Z${f$1(z2)} F${Math.round(this.profile.travelSpeed)}`);
43100
44181
  if (this.posInitialized) {
43101
44182
  this._segments.push({ from, to: [x2, y2, z2], extrude: false, speed: this.profile.travelSpeed });
43102
44183
  }
@@ -43128,7 +44209,7 @@ class GCodeBuilder {
43128
44209
  const beadArea = this.profile.layerHeight * this.profile.nozzle;
43129
44210
  const eIncrement = beadArea * dist4 / this.filamentArea;
43130
44211
  this.e += eIncrement;
43131
- this.lines.push(`G1 X${f2(x2)} Y${f2(y2)} Z${f2(z2)} E${f2(this.e)} F${Math.round(this.currentSpeed)}`);
44212
+ 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)}`);
43132
44213
  if (this.posInitialized) {
43133
44214
  this._segments.push({ from, to: [x2, y2, z2], extrude: true, speed: this.currentSpeed });
43134
44215
  }
@@ -43226,13 +44307,13 @@ class GCodeBuilder {
43226
44307
  retract() {
43227
44308
  if (this.retracted) return;
43228
44309
  this.e -= this.profile.retractionDistance;
43229
- this.lines.push(`G1 E${f2(this.e)} F${Math.round(this.profile.retractionSpeed)}`);
44310
+ this.lines.push(`G1 E${f$1(this.e)} F${Math.round(this.profile.retractionSpeed)}`);
43230
44311
  this.retracted = true;
43231
44312
  }
43232
44313
  unretract() {
43233
44314
  if (!this.retracted) return;
43234
44315
  this.e += this.profile.retractionDistance;
43235
- this.lines.push(`G1 E${f2(this.e)} F${Math.round(this.profile.retractionSpeed)}`);
44316
+ this.lines.push(`G1 E${f$1(this.e)} F${Math.round(this.profile.retractionSpeed)}`);
43236
44317
  this.retracted = false;
43237
44318
  }
43238
44319
  // ---- Bounds tracking ----
@@ -43303,7 +44384,7 @@ class GCodeBuilder {
43303
44384
  return this.lines.join("\n") + "\n";
43304
44385
  }
43305
44386
  }
43306
- function f2(n) {
44387
+ function f$1(n) {
43307
44388
  return n.toFixed(5).replace(/\.?0+$/, "");
43308
44389
  }
43309
44390
  function bambuModelName(preset) {
@@ -46814,7 +47895,8 @@ function explode(items, options = {}) {
46814
47895
  if (child instanceof ShapeGroup) return explodeGroup(child, p2, depth + 1, total, groupCenter, motion.branchDirection);
46815
47896
  return explodeLeaf(child, explodeAdd(total, leafMotion(child, p2, depth + 1, groupCenter, motion.branchDirection).offset));
46816
47897
  }),
46817
- grp.childNames
47898
+ grp.childNames,
47899
+ grp.children.map((_2, i) => grp.tagsForChild(i))
46818
47900
  );
46819
47901
  };
46820
47902
  const explodeItemNode = (item, path2, depth, inherited, parentCenter, parentDirection) => {
@@ -46850,7 +47932,8 @@ function explode(items, options = {}) {
46850
47932
  if (child instanceof ShapeGroup) return explodeGroup(child, p2, 1, [0, 0, 0], rootCenter, void 0);
46851
47933
  return explodeLeaf(child, nodeMotion(child, p2, 1, rootCenter, void 0).offset);
46852
47934
  }),
46853
- items.childNames
47935
+ items.childNames,
47936
+ items.children.map((_2, i) => items.tagsForChild(i))
46854
47937
  );
46855
47938
  }
46856
47939
  return items.map((item, i) => {
@@ -47619,6 +48702,398 @@ function spurGear(options) {
47619
48702
  });
47620
48703
  return attachGearMeta(shapeWithConnectors, meta2);
47621
48704
  }
48705
+ function requirePositive$7(scope, name, value) {
48706
+ if (!isFinitePositive(value)) throw new Error(`${scope}: "${name}" must be > 0`);
48707
+ }
48708
+ function requireOptionalBore(scope, boreDiameter, maxDiameter) {
48709
+ const bore = boreDiameter ?? 0;
48710
+ if (!Number.isFinite(bore) || bore < 0) throw new Error(`${scope}: "boreDiameter" must be >= 0`);
48711
+ if (bore > 0 && bore >= maxDiameter) throw new Error(`${scope}: bore is too large for the body`);
48712
+ return bore;
48713
+ }
48714
+ function resolveSegments(segments) {
48715
+ if (segments === void 0) return void 0;
48716
+ if (!Number.isInteger(segments) || segments < 12) throw new Error('gear body: "segments" must be an integer >= 12');
48717
+ return segments;
48718
+ }
48719
+ function cutBore$1(shape, boreDiameter) {
48720
+ if (boreDiameter <= 0) return shape;
48721
+ const bounds = shape.boundingBox();
48722
+ const height = bounds.max[2] - bounds.min[2] + 2;
48723
+ const cutter = cylinder(height, boreDiameter * 0.5, void 0, 64).translate(0, 0, bounds.min[2] - 1);
48724
+ return shape.subtract(cutter);
48725
+ }
48726
+ function gearBodyDisk(options) {
48727
+ requirePositive$7("gearBodyDisk", "outerRadius", options.outerRadius);
48728
+ requirePositive$7("gearBodyDisk", "faceWidth", options.faceWidth);
48729
+ const bore = requireOptionalBore("gearBodyDisk", options.boreDiameter, options.outerRadius * 2);
48730
+ const segments = resolveSegments(options.segments);
48731
+ const outer = circle2d(options.outerRadius, segments);
48732
+ const profile = bore > 0 ? difference2d(outer, circle2d(bore * 0.5, segments)) : outer;
48733
+ return sketchExtrude(profile, options.faceWidth);
48734
+ }
48735
+ function gearBodyDiskWithHub(options) {
48736
+ requirePositive$7("gearBodyDiskWithHub", "hubDiameter", options.hubDiameter);
48737
+ if (options.hubDiameter >= options.outerRadius * 2) {
48738
+ throw new Error('gearBodyDiskWithHub: "hubDiameter" must be smaller than the outer diameter');
48739
+ }
48740
+ const bore = requireOptionalBore("gearBodyDiskWithHub", options.boreDiameter, options.hubDiameter);
48741
+ const base = gearBodyDisk({ ...options, boreDiameter: 0 });
48742
+ const hubFaceWidth = options.hubFaceWidth ?? options.faceWidth * 1.5;
48743
+ requirePositive$7("gearBodyDiskWithHub", "hubFaceWidth", hubFaceWidth);
48744
+ const hub = cylinder(hubFaceWidth, options.hubDiameter * 0.5, void 0, options.segments).translate(
48745
+ 0,
48746
+ 0,
48747
+ (options.faceWidth - hubFaceWidth) * 0.5
48748
+ );
48749
+ return cutBore$1(base.add(hub), bore);
48750
+ }
48751
+ function gearBodySpoked(options) {
48752
+ requirePositive$7("gearBodySpoked", "outerRadius", options.outerRadius);
48753
+ requirePositive$7("gearBodySpoked", "faceWidth", options.faceWidth);
48754
+ requirePositive$7("gearBodySpoked", "rimWidth", options.rimWidth);
48755
+ requirePositive$7("gearBodySpoked", "hubDiameter", options.hubDiameter);
48756
+ requirePositive$7("gearBodySpoked", "spokeWidth", options.spokeWidth);
48757
+ if (!Number.isInteger(options.spokeCount) || options.spokeCount < 2) {
48758
+ throw new Error('gearBodySpoked: "spokeCount" must be an integer >= 2');
48759
+ }
48760
+ const hubRadius = options.hubDiameter * 0.5;
48761
+ const rimInnerRadius = options.outerRadius - options.rimWidth;
48762
+ if (rimInnerRadius <= hubRadius) throw new Error("gearBodySpoked: rim overlaps the hub");
48763
+ const bore = requireOptionalBore("gearBodySpoked", options.boreDiameter, options.hubDiameter);
48764
+ const segments = resolveSegments(options.segments);
48765
+ const rim = difference2d(circle2d(options.outerRadius, segments), circle2d(rimInnerRadius, segments));
48766
+ const hub = circle2d(hubRadius, segments);
48767
+ const spokeLength = rimInnerRadius - hubRadius + options.spokeWidth;
48768
+ const spokeCenter = hubRadius + spokeLength * 0.5 - options.spokeWidth * 0.5;
48769
+ const spoke = sketchTranslate(rect(spokeLength, options.spokeWidth), spokeCenter, 0);
48770
+ const spokes = [];
48771
+ for (let i = 0; i < options.spokeCount; i++) {
48772
+ spokes.push(sketchRotateAround(spoke, 360 / options.spokeCount * i, [0, 0]));
48773
+ }
48774
+ const profile = bore > 0 ? difference2d(union2d(rim, hub, ...spokes), circle2d(bore * 0.5, segments)) : union2d(rim, hub, ...spokes);
48775
+ return sketchExtrude(profile, options.faceWidth);
48776
+ }
48777
+ function gearBodyFromProfile(profile, options) {
48778
+ if (!(profile instanceof Sketch)) throw new Error('gearBodyFromProfile: "profile" must be a Sketch');
48779
+ requirePositive$7("gearBodyFromProfile", "faceWidth", options.faceWidth);
48780
+ const bore = options.boreDiameter ?? 0;
48781
+ if (!Number.isFinite(bore) || bore < 0) throw new Error('gearBodyFromProfile: "boreDiameter" must be >= 0');
48782
+ return cutBore$1(sketchExtrude(profile, options.faceWidth), bore);
48783
+ }
48784
+ function requirePositive$6(scope, name, value) {
48785
+ if (!isFinitePositive(value)) throw new Error(`${scope}: "${name}" must be > 0`);
48786
+ }
48787
+ function requireFiniteAngle(scope, name, value) {
48788
+ if (value !== void 0 && !Number.isFinite(value)) throw new Error(`${scope}: "${name}" must be finite`);
48789
+ }
48790
+ function cutBore(shape, boreDiameter) {
48791
+ if (boreDiameter <= 0) return shape;
48792
+ const bounds = shape.boundingBox();
48793
+ const height = bounds.max[2] - bounds.min[2] + 2;
48794
+ const cutter = cylinder(height, boreDiameter * 0.5, void 0, 64).translate(0, 0, bounds.min[2] - 1);
48795
+ return shape.subtract(cutter);
48796
+ }
48797
+ function bodyOuterRadius(shape) {
48798
+ const bounds = shape.boundingBox();
48799
+ return Math.max(Math.abs(bounds.min[0]), Math.abs(bounds.max[0]), Math.abs(bounds.min[1]), Math.abs(bounds.max[1]));
48800
+ }
48801
+ function buildSpurTeethRegion(options, name, faceWidth) {
48802
+ const scope = "driveWheel.addSpurTeethBetween";
48803
+ const teethOnFullCircle = options.teethOnFullCircle;
48804
+ if (!Number.isInteger(teethOnFullCircle) || teethOnFullCircle < 6) {
48805
+ throw new Error(`${scope}: "teethOnFullCircle" must be an integer >= 6`);
48806
+ }
48807
+ const toothCount = options.toothCount;
48808
+ if (!Number.isInteger(toothCount) || toothCount < 1 || toothCount > teethOnFullCircle) {
48809
+ throw new Error(`${scope}: "toothCount" must be an integer in [1, teethOnFullCircle]`);
48810
+ }
48811
+ const firstTooth = options.firstTooth ?? 0;
48812
+ if (!Number.isInteger(firstTooth) || firstTooth < 0 || firstTooth >= teethOnFullCircle) {
48813
+ throw new Error(`${scope}: "firstTooth" must be an integer in [0, teethOnFullCircle)`);
48814
+ }
48815
+ let normalized;
48816
+ try {
48817
+ normalized = normalizeSpurGearOptions({ ...options, teeth: teethOnFullCircle, faceWidth, boreDiameter: 0 });
48818
+ } catch (error) {
48819
+ remapErrorPrefix(error, "spurGear", scope);
48820
+ }
48821
+ const gearMeta = buildSpurGearMeta(normalized);
48822
+ const pitchStepDeg = 360 / teethOnFullCircle;
48823
+ const fromAngleDeg = firstTooth * pitchStepDeg - pitchStepDeg * 0.5;
48824
+ const toAngleDeg = (firstTooth + toothCount - 1) * pitchStepDeg + pitchStepDeg * 0.5;
48825
+ const profile = buildSpurToothRegionProfile(gearMeta, firstTooth, toothCount, normalized.segmentsPerTooth);
48826
+ return {
48827
+ shape: sketchExtrude(profile, faceWidth),
48828
+ gearMeta,
48829
+ meta: {
48830
+ name,
48831
+ kind: "spurTeeth",
48832
+ fromAngleDeg,
48833
+ toAngleDeg,
48834
+ outerRadius: gearMeta.outerRadius,
48835
+ rootRadius: gearMeta.rootRadius,
48836
+ pitchRadius: gearMeta.pitchRadius,
48837
+ module: normalized.module,
48838
+ teethOnFullCircle,
48839
+ toothCount,
48840
+ faceWidth
48841
+ }
48842
+ };
48843
+ }
48844
+ function buildSolidArcRegion(options, name, faceWidth) {
48845
+ const scope = "driveWheel.addSolidArcBetween";
48846
+ requirePositive$6(scope, "outerRadius", options.outerRadius);
48847
+ const innerRadius = options.innerRadius ?? 0;
48848
+ if (!Number.isFinite(innerRadius) || innerRadius < 0) throw new Error(`${scope}: "innerRadius" must be >= 0`);
48849
+ if (innerRadius >= options.outerRadius) throw new Error(`${scope}: "innerRadius" must be smaller than "outerRadius"`);
48850
+ const sweepDeg = normalizedSweep(scope, options.fromAngleDeg, options.toAngleDeg);
48851
+ return {
48852
+ shape: sketchExtrude(buildSolidArcProfile(options, sweepDeg), faceWidth),
48853
+ meta: {
48854
+ name,
48855
+ kind: "solidArc",
48856
+ fromAngleDeg: options.fromAngleDeg,
48857
+ toAngleDeg: options.fromAngleDeg + sweepDeg,
48858
+ innerRadius,
48859
+ outerRadius: options.outerRadius,
48860
+ faceWidth
48861
+ }
48862
+ };
48863
+ }
48864
+ function normalizedSweep(scope, fromAngleDeg, toAngleDeg) {
48865
+ if (!Number.isFinite(fromAngleDeg)) throw new Error(`${scope}: "fromAngleDeg" must be finite`);
48866
+ if (!Number.isFinite(toAngleDeg)) throw new Error(`${scope}: "toAngleDeg" must be finite`);
48867
+ let sweep2 = toAngleDeg - fromAngleDeg;
48868
+ while (sweep2 <= 0) sweep2 += 360;
48869
+ if (sweep2 > 360 + EPSILON$1) throw new Error(`${scope}: angular sweep must be <= 360 degrees`);
48870
+ return Math.min(360, sweep2);
48871
+ }
48872
+ function buildSpurToothRegionProfile(meta2, firstTooth, toothCount, segmentsPerTooth) {
48873
+ const tooth = createSpurToothSketch(meta2, segmentsPerTooth);
48874
+ const teeth = [];
48875
+ for (let i = 0; i < toothCount; i++) {
48876
+ teeth.push(sketchRotateAround(tooth, 360 / meta2.teeth * (firstTooth + i), [0, 0]));
48877
+ }
48878
+ return union2d(...teeth);
48879
+ }
48880
+ function buildSolidArcProfile(options, sweepDeg) {
48881
+ const innerRadius = options.innerRadius ?? 0;
48882
+ const segments = options.segments ?? Math.max(16, Math.ceil(sweepDeg / 6));
48883
+ if (!Number.isInteger(segments) || segments < 4) throw new Error('driveWheel.addSolidArcBetween: "segments" must be an integer >= 4');
48884
+ if (Math.abs(sweepDeg - 360) < EPSILON$1) {
48885
+ const outer = circle2d(options.outerRadius, segments);
48886
+ return innerRadius > 0 ? difference2d(outer, circle2d(innerRadius, segments)) : outer;
48887
+ }
48888
+ const start = options.fromAngleDeg * Math.PI / 180;
48889
+ const end = start + sweepDeg * Math.PI / 180;
48890
+ const pts = [];
48891
+ if (innerRadius <= 0) pts.push([0, 0]);
48892
+ addArcPoints(pts, options.outerRadius, start, end, segments, true, true);
48893
+ if (innerRadius > 0) addArcPoints(pts, innerRadius, end, start, segments, true, true);
48894
+ return polygon(pts);
48895
+ }
48896
+ const DRIVE_WHEEL_META_KEY = Symbol.for("forgecad.library.driveWheelMeta");
48897
+ function attachDriveWheelMeta(shape, meta2) {
48898
+ shape[DRIVE_WHEEL_META_KEY] = meta2;
48899
+ return shape;
48900
+ }
48901
+ function readDriveWheelMeta(shape) {
48902
+ const meta2 = shape[DRIVE_WHEEL_META_KEY];
48903
+ return meta2 ?? null;
48904
+ }
48905
+ class DriveWheelBuilder {
48906
+ constructor(options = {}) {
48907
+ __publicField(this, "body");
48908
+ __publicField(this, "faceWidth");
48909
+ __publicField(this, "boreDiameter");
48910
+ __publicField(this, "regions", []);
48911
+ if (options.body !== void 0 && !(options.body instanceof Shape)) throw new Error('driveWheel: "body" must be a Shape');
48912
+ if (options.faceWidth !== void 0) requirePositive$6("driveWheel", "faceWidth", options.faceWidth);
48913
+ const boreDiameter = options.boreDiameter ?? 0;
48914
+ if (!Number.isFinite(boreDiameter) || boreDiameter < 0) throw new Error('driveWheel: "boreDiameter" must be >= 0');
48915
+ this.body = options.body;
48916
+ this.faceWidth = options.faceWidth;
48917
+ this.boreDiameter = boreDiameter;
48918
+ }
48919
+ /**
48920
+ * Add an involute spur-tooth window on part of the pitch circle.
48921
+ */
48922
+ addSpurTeethBetween(options) {
48923
+ const faceWidth = this.resolveFaceWidth("driveWheel.addSpurTeethBetween", options.faceWidth);
48924
+ this.regions.push(buildSpurTeethRegion(options, this.resolveName("teeth", options.name), faceWidth));
48925
+ return this;
48926
+ }
48927
+ /**
48928
+ * Add a constant-radius solid arc region such as a dwell, stop, or pusher.
48929
+ */
48930
+ addSolidArcBetween(options) {
48931
+ const faceWidth = this.resolveFaceWidth("driveWheel.addSolidArcBetween", options.faceWidth);
48932
+ this.regions.push(buildSolidArcRegion(options, this.resolveName("arc", options.name), faceWidth));
48933
+ return this;
48934
+ }
48935
+ /**
48936
+ * Add a fully custom region shape while preserving region metadata.
48937
+ */
48938
+ addShapeRegion(name, shape, options = {}) {
48939
+ const scope = "driveWheel.addShapeRegion";
48940
+ if (typeof name !== "string" || name.trim().length === 0) throw new Error(`${scope}: "name" must be a non-empty string`);
48941
+ if (!(shape instanceof Shape)) throw new Error(`${scope}: "shape" must be a Shape`);
48942
+ requireFiniteAngle(scope, "fromAngleDeg", options.fromAngleDeg);
48943
+ requireFiniteAngle(scope, "toAngleDeg", options.toAngleDeg);
48944
+ if (options.innerRadius !== void 0 && (!Number.isFinite(options.innerRadius) || options.innerRadius < 0)) {
48945
+ throw new Error(`${scope}: "innerRadius" must be >= 0`);
48946
+ }
48947
+ if (options.outerRadius !== void 0) requirePositive$6(scope, "outerRadius", options.outerRadius);
48948
+ this.regions.push({
48949
+ shape: shape.clone(),
48950
+ meta: {
48951
+ name: this.resolveName("region", name),
48952
+ kind: "custom",
48953
+ ...options
48954
+ }
48955
+ });
48956
+ return this;
48957
+ }
48958
+ /**
48959
+ * Build the final wheel shape with a bore connector and region metadata.
48960
+ */
48961
+ build() {
48962
+ var _a3, _b3;
48963
+ if (this.regions.length === 0 && this.body === void 0) {
48964
+ throw new Error("driveWheel: add a body or at least one region before build()");
48965
+ }
48966
+ const faceWidth = this.resolveBuildFaceWidth();
48967
+ const firstGearRegion = (_a3 = this.regions.find((region) => region.gearMeta)) == null ? void 0 : _a3.gearMeta;
48968
+ if (firstGearRegion && this.boreDiameter * 0.5 >= firstGearRegion.rootRadius - EPSILON$1) {
48969
+ throw new Error("driveWheel: bore is too large for the first spur-tooth region");
48970
+ }
48971
+ const body = ((_b3 = this.body) == null ? void 0 : _b3.clone()) ?? gearBodyDisk({ outerRadius: (firstGearRegion == null ? void 0 : firstGearRegion.rootRadius) ?? this.defaultBodyRadius(), faceWidth });
48972
+ let combined = body;
48973
+ for (const region of this.regions) combined = combined.add(region.shape);
48974
+ combined = cutBore(combined, this.boreDiameter);
48975
+ const withConnectors = combined.withConnectors({
48976
+ bore: connectorFactory(
48977
+ "drive-wheel-bore",
48978
+ { origin: [0, 0, faceWidth / 2], axis: [0, 0, 1], kind: "revolute" },
48979
+ this.measurements(faceWidth)
48980
+ )
48981
+ });
48982
+ return attachDriveWheelMeta(withConnectors, {
48983
+ kind: "driveWheel",
48984
+ faceWidth,
48985
+ boreDiameter: this.boreDiameter,
48986
+ regions: this.regionMetadata(body, faceWidth)
48987
+ });
48988
+ }
48989
+ measurements(faceWidth) {
48990
+ var _a3;
48991
+ const firstGearRegion = (_a3 = this.regions.find((region) => region.gearMeta)) == null ? void 0 : _a3.gearMeta;
48992
+ return {
48993
+ faceWidth,
48994
+ boreDiameter: this.boreDiameter,
48995
+ regionCount: this.regions.length,
48996
+ ...firstGearRegion ? {
48997
+ module: firstGearRegion.module,
48998
+ teethOnFullCircle: firstGearRegion.teeth,
48999
+ pitchRadius: firstGearRegion.pitchRadius,
49000
+ outerRadius: firstGearRegion.outerRadius
49001
+ } : {}
49002
+ };
49003
+ }
49004
+ regionMetadata(body, faceWidth) {
49005
+ return [
49006
+ { name: "body", kind: "body", outerRadius: bodyOuterRadius(body), faceWidth },
49007
+ ...this.regions.map((region) => ({ ...region.meta }))
49008
+ ];
49009
+ }
49010
+ resolveFaceWidth(scope, localFaceWidth) {
49011
+ const faceWidth = localFaceWidth ?? this.faceWidth;
49012
+ if (faceWidth === void 0) throw new Error(`${scope}: "faceWidth" is required unless driveWheel({ faceWidth }) was set`);
49013
+ requirePositive$6(scope, "faceWidth", faceWidth);
49014
+ if (this.faceWidth !== void 0 && localFaceWidth !== void 0 && Math.abs(this.faceWidth - localFaceWidth) > EPSILON$1) {
49015
+ throw new Error(`${scope}: region faceWidth must match driveWheel faceWidth`);
49016
+ }
49017
+ return faceWidth;
49018
+ }
49019
+ resolveBuildFaceWidth() {
49020
+ var _a3;
49021
+ const faceWidth = this.faceWidth ?? ((_a3 = this.regions.find((region) => region.meta.faceWidth !== void 0)) == null ? void 0 : _a3.meta.faceWidth);
49022
+ if (faceWidth === void 0) throw new Error('driveWheel: "faceWidth" is required before build()');
49023
+ return faceWidth;
49024
+ }
49025
+ defaultBodyRadius() {
49026
+ const outerRadius = this.regions.reduce((max2, region) => Math.max(max2, region.meta.outerRadius ?? 0), 0);
49027
+ if (outerRadius <= 0) throw new Error('driveWheel: "body" is required when regions do not define an outer radius');
49028
+ return outerRadius;
49029
+ }
49030
+ resolveName(prefix, requested) {
49031
+ const base = (requested == null ? void 0 : requested.trim()) || prefix;
49032
+ if (this.regions.every((region) => region.meta.name !== base)) return base;
49033
+ for (let i = 2; ; i++) {
49034
+ const candidate = `${base}${i}`;
49035
+ if (this.regions.every((region) => region.meta.name !== candidate)) return candidate;
49036
+ }
49037
+ }
49038
+ }
49039
+ function driveWheel(options = {}) {
49040
+ return new DriveWheelBuilder(options);
49041
+ }
49042
+ function normalizeSectorGearOptions(options) {
49043
+ const teethOnFullCircle = options.teethOnFullCircle;
49044
+ if (!Number.isInteger(teethOnFullCircle) || teethOnFullCircle < 6) {
49045
+ throw new Error('sectorGear: "teethOnFullCircle" must be an integer >= 6');
49046
+ }
49047
+ const toothCount = options.toothCount;
49048
+ if (!Number.isInteger(toothCount) || toothCount < 1 || toothCount > teethOnFullCircle) {
49049
+ throw new Error('sectorGear: "toothCount" must be an integer in [1, teethOnFullCircle]');
49050
+ }
49051
+ const firstTooth = options.firstTooth ?? 0;
49052
+ if (!Number.isInteger(firstTooth) || firstTooth < 0 || firstTooth >= teethOnFullCircle) {
49053
+ throw new Error('sectorGear: "firstTooth" must be an integer in [0, teethOnFullCircle)');
49054
+ }
49055
+ return {
49056
+ ...normalizeSpurGearOptions({ ...options, teeth: teethOnFullCircle }),
49057
+ teethOnFullCircle,
49058
+ toothCount,
49059
+ firstTooth,
49060
+ boreDiameter: options.boreDiameter ?? 0
49061
+ };
49062
+ }
49063
+ function sectorGear(options) {
49064
+ const normalized = normalizeSectorGearOptions(options);
49065
+ if (options.body !== void 0 && !(options.body instanceof Shape)) {
49066
+ throw new Error('sectorGear: "body" must be a Shape');
49067
+ }
49068
+ const spurMeta = buildSpurGearMeta(normalized);
49069
+ const pitchStepDeg = 360 / normalized.teethOnFullCircle;
49070
+ const activeAngleStartDeg = normalized.firstTooth * pitchStepDeg - pitchStepDeg * 0.5;
49071
+ const activeAngleEndDeg = (normalized.firstTooth + normalized.toothCount - 1) * pitchStepDeg + pitchStepDeg * 0.5;
49072
+ const meta2 = {
49073
+ ...spurMeta,
49074
+ kind: "sector",
49075
+ teethOnFullCircle: normalized.teethOnFullCircle,
49076
+ firstTooth: normalized.firstTooth,
49077
+ toothCount: normalized.toothCount,
49078
+ activeAngleStartDeg,
49079
+ activeAngleEndDeg
49080
+ };
49081
+ const wheel = driveWheel({ body: options.body, faceWidth: normalized.faceWidth, boreDiameter: normalized.boreDiameter }).addSpurTeethBetween({
49082
+ name: "teeth",
49083
+ module: normalized.module,
49084
+ teethOnFullCircle: normalized.teethOnFullCircle,
49085
+ toothCount: normalized.toothCount,
49086
+ firstTooth: normalized.firstTooth,
49087
+ pressureAngleDeg: normalized.pressureAngleDeg,
49088
+ faceWidth: normalized.faceWidth,
49089
+ backlash: normalized.backlash,
49090
+ clearance: normalized.clearance,
49091
+ addendum: normalized.addendum,
49092
+ dedendum: normalized.dedendum,
49093
+ segmentsPerTooth: normalized.segmentsPerTooth
49094
+ }).build();
49095
+ return attachGearMeta(wheel, meta2);
49096
+ }
47622
49097
  function normalizeSideGearOptions(options) {
47623
49098
  let normalizedSpur;
47624
49099
  try {
@@ -48598,6 +50073,12 @@ function boltPattern(options) {
48598
50073
  }
48599
50074
  };
48600
50075
  }
50076
+ const gearBodies = {
50077
+ disk: gearBodyDisk,
50078
+ diskWithHub: gearBodyDiskWithHub,
50079
+ spoked: gearBodySpoked,
50080
+ fromProfile: gearBodyFromProfile
50081
+ };
48601
50082
  function thread(diameter, pitch, length4, options) {
48602
50083
  const r = diameter / 2;
48603
50084
  const depth = (options == null ? void 0 : options.depth) ?? pitch * 0.35;
@@ -48763,7 +50244,23 @@ const partLibrary = {
48763
50244
  gearRatio,
48764
50245
  rackRatio,
48765
50246
  planetaryRatio,
48766
- boltPattern
50247
+ boltPattern,
50248
+ /** Start a composable exceptional gear or drive wheel. */
50249
+ driveWheel,
50250
+ /** Read functional-region metadata from a drive wheel shape. */
50251
+ readDriveWheelMeta,
50252
+ /** Involute sector gear with teeth on only part of the pitch circle. */
50253
+ sectorGear,
50254
+ /** Gear body preset namespace: disk, diskWithHub, spoked, and fromProfile. */
50255
+ gearBodies,
50256
+ /** Solid disk/ring gear body, independent from any tooth geometry. */
50257
+ gearBodyDisk,
50258
+ /** Disk gear body with a raised center hub. */
50259
+ gearBodyDiskWithHub,
50260
+ /** Spoked gear body with an outer rim, center hub, and radial spokes. */
50261
+ gearBodySpoked,
50262
+ /** Extrude a custom 2D profile into a gear body. */
50263
+ gearBodyFromProfile
48767
50264
  };
48768
50265
  /**
48769
50266
  * @license
@@ -58234,7 +59731,7 @@ class ProductStationBuilder {
58234
59731
  this.profileValue = profileFromSketch(sketch, "custom", width, depth);
58235
59732
  return this;
58236
59733
  }
58237
- /** Stores a semantic crown amount for diagnostics and future rail solving. */
59734
+ /** Set the station crown amount for soft product-section intent. */
58238
59735
  crown(amount) {
58239
59736
  if (!Number.isFinite(amount)) throw new Error("station.crown(amount) requires a finite number");
58240
59737
  this.crownValue = amount;
@@ -59565,7 +61062,7 @@ class ProductSkinBuilder {
59565
61062
  this.stationsValue = stations.map(toStationSpec).sort((a2, b) => axisPosition(this.axisValue, a2.center) - axisPosition(this.axisValue, b.center));
59566
61063
  return this;
59567
61064
  }
59568
- /** Attach guide rails as ProductSkin IR metadata and diagnostics. */
61065
+ /** Attach named guide rails for product-skin construction and downstream surface references. */
59569
61066
  rails(rails) {
59570
61067
  this.railsValue = { ...rails };
59571
61068
  return this;
@@ -59599,7 +61096,7 @@ class ProductSkinBuilder {
59599
61096
  this.edgeLengthValue = value;
59600
61097
  return this;
59601
61098
  }
59602
- /** Records a target wall thickness; v1 keeps exterior skin lowering sampled and reports wall as a diagnostic. */
61099
+ /** Record intended wall thickness for product design metadata. Use explicit shelling when the model needs real inner-wall geometry. */
59603
61100
  wall(thickness) {
59604
61101
  if (!Number.isFinite(thickness) || thickness <= 0) throw new Error("Product.skin().wall(thickness) requires a positive finite number");
59605
61102
  this.wallValue = thickness;
@@ -62741,7 +64238,7 @@ class SurfaceMemberBuilder {
62741
64238
  this.record.features.push({ ...normalizeFeature(name, feature), type: "counterbore" });
62742
64239
  return this;
62743
64240
  }
62744
- /** Add a named anchor at a carrier surface coordinate for diagnostics, debug views, and future named-anchor joins. */
64241
+ /** Add a named anchor at a carrier surface coordinate for explicit member joins. */
62745
64242
  anchorAt(name, coordinate) {
62746
64243
  if (!name.trim()) throw new Error("SurfaceMemberBuilder.anchorAt(name, coordinate) requires a non-empty name");
62747
64244
  const explicitAnchors = this.record.spec.explicitAnchors ?? [];
@@ -74017,8 +75514,8 @@ tinf_build_bits_base(dist_bits, dist_base, 2, 1);
74017
75514
  length_bits[28] = 0;
74018
75515
  length_base[28] = 258;
74019
75516
  var tinyInflate = tinf_uncompress;
74020
- function derive(v0, v1, v2, v3, t) {
74021
- 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;
75517
+ function derive(v0, v1, v2, v32, t) {
75518
+ 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;
74022
75519
  }
74023
75520
  function BoundingBox() {
74024
75521
  this.x1 = Number.NaN;
@@ -302829,6 +304326,7 @@ function classifySdfPreviewNode(node) {
302829
304326
  case "sdf:twist":
302830
304327
  case "sdf:bend":
302831
304328
  case "sdf:repeat":
304329
+ case "sdf:circularArray":
302832
304330
  case "sdf:shell":
302833
304331
  case "sdf:onion":
302834
304332
  return classifySdfPreviewNode(node.child);
@@ -302838,10 +304336,7 @@ function classifySdfPreviewNode(node) {
302838
304336
  reason: "This SDF uses a custom JavaScript displacement function that cannot be compiled for raymarch preview."
302839
304337
  };
302840
304338
  case "sdf:surfaceDisplace":
302841
- return {
302842
- mode: "unsupported",
302843
- reason: "This SDF uses surface displacement that is not yet available in the raymarch shader."
302844
- };
304339
+ return classifySurfaceDisplacePreviewNode(node);
302845
304340
  case "sdf:spatialBlend":
302846
304341
  return {
302847
304342
  mode: "unsupported",
@@ -302867,6 +304362,79 @@ ${node.shaderUnsupportedReason}` : ""}`
302867
304362
  };
302868
304363
  }
302869
304364
  }
304365
+ function classifySurfaceDisplacePreviewNode(node) {
304366
+ if (!node.pattern) {
304367
+ return {
304368
+ mode: "unsupported",
304369
+ reason: "This SDF uses a custom JavaScript surface pattern that cannot be compiled for raymarch preview."
304370
+ };
304371
+ }
304372
+ const childResult = classifySdfPreviewNode(node.child);
304373
+ if (childResult.mode !== "raymarch") return childResult;
304374
+ const uv = analyzeShaderSurfaceUv(node.child, "p", node.uvMode);
304375
+ if (uv.mode === "triplanar") {
304376
+ return {
304377
+ mode: "unsupported",
304378
+ reason: "Typed surface displacement raymarch preview currently supports sphere, cylinder, and torus UV mappings."
304379
+ };
304380
+ }
304381
+ return { mode: "raymarch" };
304382
+ }
304383
+ function f2(value) {
304384
+ if (!Number.isFinite(value)) return "0.0";
304385
+ const text = Number(value.toPrecision(9)).toString();
304386
+ return text.includes(".") || text.includes("e") ? text : `${text}.0`;
304387
+ }
304388
+ function v3(value) {
304389
+ return `vec3(${f2(value[0])}, ${f2(value[1])}, ${f2(value[2])})`;
304390
+ }
304391
+ function analyzeShaderSurfaceUv(node, p2, override) {
304392
+ const analysis = analyzeShaderSurfaceUvNode(node, p2);
304393
+ if (!override || override === "auto") return analysis;
304394
+ if (override === "triplanar") return { mode: "triplanar" };
304395
+ if (analysis.mode === "triplanar") return analysis;
304396
+ if (override === analysis.mode) return analysis;
304397
+ if (override === "sphere" || override === "cylinder") {
304398
+ return { mode: override, localPoint: analysis.localPoint, radius: analysis.radius };
304399
+ }
304400
+ return analysis.mode === "torus" ? analysis : { mode: "triplanar" };
304401
+ }
304402
+ function analyzeShaderSurfaceUvNode(node, p2) {
304403
+ switch (node.kind) {
304404
+ case "sdf:sphere":
304405
+ return { mode: "sphere", localPoint: p2, radius: node.radius };
304406
+ case "sdf:cylinder":
304407
+ return { mode: "cylinder", localPoint: p2, radius: node.radius };
304408
+ case "sdf:torus":
304409
+ return { mode: "torus", localPoint: p2, radius: node.minorRadius, majorRadius: node.majorRadius };
304410
+ case "sdf:translate":
304411
+ return analyzeShaderSurfaceUvNode(node.child, `(${p2} - ${v3(node.offset)})`);
304412
+ case "sdf:rotate":
304413
+ return analyzeShaderSurfaceUvNode(node.child, `rotateInvEuler(${p2}, ${v3(node.degrees)})`);
304414
+ case "sdf:scale": {
304415
+ const result = analyzeShaderSurfaceUvNode(node.child, `(${p2} / ${f2(node.factor)})`);
304416
+ if (result.mode === "triplanar") return result;
304417
+ return {
304418
+ ...result,
304419
+ radius: result.radius * node.factor,
304420
+ ...result.mode === "torus" ? { majorRadius: result.majorRadius * node.factor } : {}
304421
+ };
304422
+ }
304423
+ case "sdf:shell":
304424
+ return analyzeShaderSurfaceUvNode(node.child, p2);
304425
+ case "sdf:union":
304426
+ case "sdf:smoothUnion":
304427
+ case "sdf:intersection":
304428
+ case "sdf:smoothIntersection":
304429
+ case "sdf:difference":
304430
+ case "sdf:smoothDifference":
304431
+ return node.children.length > 0 ? analyzeShaderSurfaceUvNode(node.children[0], p2) : { mode: "triplanar" };
304432
+ case "sdf:morph":
304433
+ return analyzeShaderSurfaceUvNode(node.a, p2);
304434
+ default:
304435
+ return { mode: "triplanar" };
304436
+ }
304437
+ }
302870
304438
  function describeScriptResultType(value) {
302871
304439
  var _a3, _b3;
302872
304440
  if (value == null) return String(value);
@@ -302927,7 +304495,7 @@ function mapScriptResultToScene(args) {
302927
304495
  var _a3;
302928
304496
  const objects = [];
302929
304497
  const shapeDimensions = [];
302930
- const pushShape = (shape, name, groupName, color, treePath) => {
304498
+ const pushShape = (shape, name, groupName, color, treePath, tags = []) => {
302931
304499
  const objectId = `obj-${objects.length + 1}`;
302932
304500
  objects.push({
302933
304501
  id: objectId,
@@ -302938,7 +304506,8 @@ function mapScriptResultToScene(args) {
302938
304506
  materialProps: shape.materialProps,
302939
304507
  geometryInfo: shape.geometryInfo(),
302940
304508
  groupName,
302941
- treePath: treePath && treePath.length > 0 ? [...treePath] : [name]
304509
+ treePath: treePath && treePath.length > 0 ? [...treePath] : [name],
304510
+ ...tags.length > 0 ? { tags: [...tags] } : {}
302942
304511
  });
302943
304512
  const dims = getShapeDimensions(shape);
302944
304513
  dims.forEach((dim2) => {
@@ -302962,7 +304531,7 @@ function mapScriptResultToScene(args) {
302962
304531
  });
302963
304532
  }
302964
304533
  };
302965
- const pushSketch = (sketch, name, groupName, treePath) => {
304534
+ const pushSketch = (sketch, name, groupName, treePath, tags = []) => {
302966
304535
  const meta2 = sketch instanceof ConstraintSketch ? sketch.constraintMeta : void 0;
302967
304536
  objects.push({
302968
304537
  id: `obj-${objects.length + 1}`,
@@ -302973,10 +304542,11 @@ function mapScriptResultToScene(args) {
302973
304542
  sketchMeta: meta2,
302974
304543
  color: sketch.colorHex,
302975
304544
  groupName,
302976
- treePath: treePath && treePath.length > 0 ? [...treePath] : [name]
304545
+ treePath: treePath && treePath.length > 0 ? [...treePath] : [name],
304546
+ ...tags.length > 0 ? { tags: [...tags] } : {}
302977
304547
  });
302978
304548
  };
302979
- const pushSdf = (sdfShape, name, groupName, treePath, color) => {
304549
+ const pushSdf = (sdfShape, name, groupName, treePath, color, tags = []) => {
302980
304550
  const preview = classifySdfPreviewNode(sdfShape._node);
302981
304551
  const displayColor = color || sdfShape.colorHex;
302982
304552
  const data = {
@@ -302996,7 +304566,8 @@ function mapScriptResultToScene(args) {
302996
304566
  materialProps: sdfShape.materialProps,
302997
304567
  geometryInfo: null,
302998
304568
  groupName,
302999
- treePath: treePath && treePath.length > 0 ? [...treePath] : [name]
304569
+ treePath: treePath && treePath.length > 0 ? [...treePath] : [name],
304570
+ ...tags.length > 0 ? { tags: [...tags] } : {}
303000
304571
  });
303001
304572
  };
303002
304573
  const isNamedObject = (item) => {
@@ -303013,18 +304584,24 @@ function mapScriptResultToScene(args) {
303013
304584
  const rootGroupChildLabel = (grp, index2) => {
303014
304585
  return shapeGroupChildSegment(grp, index2, true);
303015
304586
  };
303016
- const flattenGroupChild = (child, label, groupName, treePath) => {
304587
+ const flattenGroupChild = (child, label, groupName, treePath, tags = []) => {
303017
304588
  const resolvedTreePath = treePath && treePath.length > 0 ? treePath : [label];
303018
304589
  if (child instanceof ShapeGroup) {
303019
304590
  child.children.forEach((nested, i) => {
303020
- flattenGroupChild(nested, groupChildLabel(child, label, i), groupName, [...resolvedTreePath, shapeGroupChildSegment(child, i)]);
304591
+ flattenGroupChild(
304592
+ nested,
304593
+ groupChildLabel(child, label, i),
304594
+ groupName,
304595
+ [...resolvedTreePath, shapeGroupChildSegment(child, i)],
304596
+ mergeSceneTags(tags, child.tagsForChild(i))
304597
+ );
303021
304598
  });
303022
304599
  return;
303023
304600
  }
303024
304601
  if (child instanceof Shape) {
303025
- pushShape(child, label, groupName, void 0, resolvedTreePath);
304602
+ pushShape(child, label, groupName, void 0, resolvedTreePath, tags);
303026
304603
  } else if (child instanceof Sketch) {
303027
- pushSketch(child, label, groupName, resolvedTreePath);
304604
+ pushSketch(child, label, groupName, resolvedTreePath, tags);
303028
304605
  }
303029
304606
  };
303030
304607
  const isPlainObject2 = (value) => {
@@ -303033,34 +304610,40 @@ function mapScriptResultToScene(args) {
303033
304610
  return proto2 === Object.prototype || proto2 === null;
303034
304611
  };
303035
304612
  const joinName = (path2) => path2.join(".");
303036
- const processRenderableTree = (value, fallbackLabel, fallbackSegment, parentGroup, parentTreePath = [], seen = /* @__PURE__ */ new WeakSet()) => {
304613
+ const processRenderableTree = (value, fallbackLabel, fallbackSegment, parentGroup, parentTreePath = [], inheritedTags = [], seen = /* @__PURE__ */ new WeakSet()) => {
303037
304614
  const segment = fallbackSegment.trim().length > 0 ? fallbackSegment : fallbackLabel;
303038
304615
  const treePath = [...parentTreePath, segment];
303039
304616
  const name = joinName(treePath) || fallbackLabel;
303040
304617
  if (value instanceof Assembly) {
303041
- value.solve().toSceneObjects().forEach((item, index2) => processNamedItem(item, `${name}.${index2 + 1}`, `${index2 + 1}`, name, treePath));
304618
+ value.solve().toSceneObjects().forEach((item, index2) => processNamedItem(item, `${name}.${index2 + 1}`, `${index2 + 1}`, name, treePath, inheritedTags));
303042
304619
  return;
303043
304620
  }
303044
304621
  if (value instanceof SolvedAssembly) {
303045
- value.toSceneObjects().forEach((item, index2) => processNamedItem(item, `${name}.${index2 + 1}`, `${index2 + 1}`, name, treePath));
304622
+ value.toSceneObjects().forEach((item, index2) => processNamedItem(item, `${name}.${index2 + 1}`, `${index2 + 1}`, name, treePath, inheritedTags));
303046
304623
  return;
303047
304624
  }
303048
304625
  if (value instanceof ShapeGroup) {
303049
304626
  value.children.forEach((child, i) => {
303050
- flattenGroupChild(child, groupChildLabel(value, name, i), parentGroup, [...treePath, shapeGroupChildSegment(value, i)]);
304627
+ flattenGroupChild(
304628
+ child,
304629
+ groupChildLabel(value, name, i),
304630
+ parentGroup,
304631
+ [...treePath, shapeGroupChildSegment(value, i)],
304632
+ mergeSceneTags(inheritedTags, value.tagsForChild(i))
304633
+ );
303051
304634
  });
303052
304635
  return;
303053
304636
  }
303054
304637
  if (value instanceof Shape) {
303055
- pushShape(value, name, parentGroup, void 0, treePath);
304638
+ pushShape(value, name, parentGroup, void 0, treePath, inheritedTags);
303056
304639
  return;
303057
304640
  }
303058
304641
  if (value instanceof Sketch) {
303059
- pushSketch(value, name, parentGroup, treePath);
304642
+ pushSketch(value, name, parentGroup, treePath, inheritedTags);
303060
304643
  return;
303061
304644
  }
303062
304645
  if (value instanceof SdfShape) {
303063
- pushSdf(value, name, parentGroup, treePath);
304646
+ pushSdf(value, name, parentGroup, treePath, void 0, inheritedTags);
303064
304647
  return;
303065
304648
  }
303066
304649
  if (value instanceof GCodeBuilder) {
@@ -303071,7 +304654,8 @@ function mapScriptResultToScene(args) {
303071
304654
  sketch: null,
303072
304655
  toolpath: value.build(),
303073
304656
  geometryInfo: null,
303074
- treePath
304657
+ treePath,
304658
+ ...inheritedTags.length > 0 ? { tags: [...inheritedTags] } : {}
303075
304659
  });
303076
304660
  return;
303077
304661
  }
@@ -303081,30 +304665,38 @@ function mapScriptResultToScene(args) {
303081
304665
  value.forEach((item, index2) => {
303082
304666
  const childSegment = `${index2 + 1}`;
303083
304667
  const childLabel = `${name}.${childSegment}`;
303084
- processRenderableTree(item, childLabel, childSegment, parentGroup, treePath, seen);
304668
+ processRenderableTree(item, childLabel, childSegment, parentGroup, treePath, inheritedTags, seen);
303085
304669
  });
303086
304670
  return;
303087
304671
  }
303088
304672
  if (isNamedObject(value)) {
303089
- processNamedItem(value, fallbackLabel, fallbackSegment, parentGroup, parentTreePath);
304673
+ processNamedItem(value, fallbackLabel, fallbackSegment, parentGroup, parentTreePath, inheritedTags);
303090
304674
  return;
303091
304675
  }
303092
304676
  if (isPlainObject2(value)) {
303093
304677
  if (seen.has(value)) return;
303094
304678
  seen.add(value);
303095
304679
  Object.entries(value).forEach(([key, entry]) => {
303096
- processRenderableTree(entry, key, key, parentGroup, treePath, seen);
304680
+ processRenderableTree(entry, key, key, parentGroup, treePath, inheritedTags, seen);
303097
304681
  });
303098
304682
  }
303099
304683
  };
303100
- const processNamedItem = (item, fallbackLabel, fallbackSegment, parentGroup, parentTreePath = []) => {
304684
+ const processNamedItem = (item, fallbackLabel, fallbackSegment, parentGroup, parentTreePath = [], inheritedTags = []) => {
304685
+ var _a4;
303101
304686
  const name = typeof item.name === "string" && item.name.trim().length > 0 ? item.name : fallbackLabel;
303102
304687
  const localSegment = typeof item.name === "string" && item.name.trim().length > 0 ? item.name : fallbackSegment;
303103
304688
  const treePath = [...parentTreePath, localSegment];
303104
304689
  const grp = parentGroup;
304690
+ const tags = mergeSceneTags(inheritedTags, (_a4 = item.metadata) == null ? void 0 : _a4.tags, item.tags);
303105
304691
  if (item.group instanceof ShapeGroup) {
303106
304692
  item.group.children.forEach((child, i) => {
303107
- flattenGroupChild(child, groupChildLabel(item.group, name, i), name, [...treePath, shapeGroupChildSegment(item.group, i)]);
304693
+ flattenGroupChild(
304694
+ child,
304695
+ groupChildLabel(item.group, name, i),
304696
+ name,
304697
+ [...treePath, shapeGroupChildSegment(item.group, i)],
304698
+ mergeSceneTags(tags, item.group.tagsForChild(i))
304699
+ );
303108
304700
  });
303109
304701
  return;
303110
304702
  }
@@ -303114,39 +304706,48 @@ function mapScriptResultToScene(args) {
303114
304706
  const childTreePath = [...treePath, `${i + 1}`];
303115
304707
  if (child instanceof ShapeGroup) {
303116
304708
  child.children.forEach((nested, nestedIndex) => {
303117
- flattenGroupChild(nested, groupChildLabel(child, name, nestedIndex), name, [
303118
- ...treePath,
303119
- shapeGroupChildSegment(child, nestedIndex)
303120
- ]);
304709
+ flattenGroupChild(
304710
+ nested,
304711
+ groupChildLabel(child, name, nestedIndex),
304712
+ name,
304713
+ [...treePath, shapeGroupChildSegment(child, nestedIndex)],
304714
+ mergeSceneTags(tags, child.tagsForChild(nestedIndex))
304715
+ );
303121
304716
  });
303122
304717
  } else if (child instanceof Shape) {
303123
- pushShape(child, childLabel, name, void 0, childTreePath);
304718
+ pushShape(child, childLabel, name, void 0, childTreePath, tags);
303124
304719
  } else if (child instanceof Sketch) {
303125
- pushSketch(child, childLabel, name, childTreePath);
304720
+ pushSketch(child, childLabel, name, childTreePath, tags);
303126
304721
  } else if (child instanceof SdfShape) {
303127
- pushSdf(child, childLabel, name, childTreePath);
304722
+ pushSdf(child, childLabel, name, childTreePath, void 0, tags);
303128
304723
  } else if (isNamedObject(child)) {
303129
- processNamedItem(child, childLabel, `${i + 1}`, name, treePath);
304724
+ processNamedItem(child, childLabel, `${i + 1}`, name, treePath, tags);
303130
304725
  }
303131
304726
  });
303132
304727
  return;
303133
304728
  }
303134
304729
  if (item.shape instanceof ShapeGroup) {
303135
304730
  item.shape.children.forEach(
303136
- (child, i) => flattenGroupChild(child, groupChildLabel(item.shape, name, i), name, [...treePath, shapeGroupChildSegment(item.shape, i)])
304731
+ (child, i) => flattenGroupChild(
304732
+ child,
304733
+ groupChildLabel(item.shape, name, i),
304734
+ name,
304735
+ [...treePath, shapeGroupChildSegment(item.shape, i)],
304736
+ mergeSceneTags(tags, item.shape.tagsForChild(i))
304737
+ )
303137
304738
  );
303138
304739
  return;
303139
304740
  }
303140
304741
  if (item.shape instanceof Shape) {
303141
- pushShape(item.shape, name, grp, item.color, treePath);
304742
+ pushShape(item.shape, name, grp, item.color, treePath, tags);
303142
304743
  return;
303143
304744
  }
303144
304745
  if (item.shape instanceof SdfShape) {
303145
- pushSdf(item.shape, name, grp, treePath, item.color);
304746
+ pushSdf(item.shape, name, grp, treePath, item.color, tags);
303146
304747
  return;
303147
304748
  }
303148
304749
  if (item.sdf instanceof SdfShape) {
303149
- pushSdf(item.sdf, name, grp, treePath, item.color);
304750
+ pushSdf(item.sdf, name, grp, treePath, item.color, tags);
303150
304751
  return;
303151
304752
  }
303152
304753
  if (item.sketch instanceof Sketch) {
@@ -303160,7 +304761,8 @@ function mapScriptResultToScene(args) {
303160
304761
  sketchMeta: meta2,
303161
304762
  color: item.color || item.sketch.colorHex,
303162
304763
  groupName: grp,
303163
- treePath
304764
+ treePath,
304765
+ ...tags.length > 0 ? { tags: [...tags] } : {}
303164
304766
  });
303165
304767
  }
303166
304768
  };
@@ -303174,14 +304776,20 @@ function mapScriptResultToScene(args) {
303174
304776
  } else if (result instanceof ShapeGroup) {
303175
304777
  result.children.forEach((child, i) => {
303176
304778
  const label = rootGroupChildLabel(result, i);
303177
- flattenGroupChild(child, label, void 0, [label]);
304779
+ flattenGroupChild(child, label, void 0, [label], result.tagsForChild(i));
303178
304780
  });
303179
304781
  } else if (Array.isArray(result)) {
303180
304782
  result.forEach((item, index2) => {
303181
304783
  const label = `Object ${index2 + 1}`;
303182
304784
  if (item instanceof ShapeGroup) {
303183
304785
  item.children.forEach((child, i) => {
303184
- flattenGroupChild(child, groupChildLabel(item, label, i), void 0, [label, shapeGroupChildSegment(item, i)]);
304786
+ flattenGroupChild(
304787
+ child,
304788
+ groupChildLabel(item, label, i),
304789
+ void 0,
304790
+ [label, shapeGroupChildSegment(item, i)],
304791
+ item.tagsForChild(i)
304792
+ );
303185
304793
  });
303186
304794
  return;
303187
304795
  }
@@ -303214,7 +304822,7 @@ function mapScriptResultToScene(args) {
303214
304822
  } else if (defaultValue instanceof ShapeGroup) {
303215
304823
  defaultValue.children.forEach((child, i) => {
303216
304824
  const label = rootGroupChildLabel(defaultValue, i);
303217
- flattenGroupChild(child, label, void 0, [label]);
304825
+ flattenGroupChild(child, label, void 0, [label], defaultValue.tagsForChild(i));
303218
304826
  });
303219
304827
  } else if (defaultValue instanceof Shape) {
303220
304828
  pushShape(defaultValue, args.fileName, void 0, void 0, [args.fileName]);
@@ -303260,7 +304868,8 @@ function mapScriptResultToScene(args) {
303260
304868
  name: `${mock2.name} (mock)`,
303261
304869
  shape: mock2.shape,
303262
304870
  sketch: null,
303263
- mock: true
304871
+ mock: true,
304872
+ tags: ["mock"]
303264
304873
  });
303265
304874
  }
303266
304875
  const hasSdfLeaves = objects.some((obj) => obj.sdf);