forgecad 0.10.0 → 0.10.1

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 (58) hide show
  1. package/dist/assets/{AdminPage-DwYHz72L.js → AdminPage-DcCnj0qo.js} +1 -1
  2. package/dist/assets/{BenchmarkPage-a9_f-1US.js → BenchmarkPage-BVEpJSVk.js} +1 -1
  3. package/dist/assets/{BlogPage-DodHpvmf.js → BlogPage-DHaGP50_.js} +1 -1
  4. package/dist/assets/{DocsPage-B5LePEuj.js → DocsPage-CDoxHkz8.js} +33 -2
  5. package/dist/assets/{EditorApp-QXsAISLR.js → EditorApp-BJ0Dloyh.js} +174 -35
  6. package/dist/assets/{EmbedViewer-DdEHGUMU.js → EmbedViewer-CRKZbY0y.js} +2 -2
  7. package/dist/assets/{LandingPageProofDriven-yhhOodbf.js → LandingPageProofDriven-BxHkYRE7.js} +1 -1
  8. package/dist/assets/{LegalPage-5RbKRGYK.js → LegalPage-B-u6FrVv.js} +1 -1
  9. package/dist/assets/{PricingPage-E3Rma7aV.js → PricingPage-CzpZ6-Ce.js} +1 -1
  10. package/dist/assets/{SettingsPage-BJZcM97j.js → SettingsPage-CIZSSAd0.js} +1 -1
  11. package/dist/assets/{app-CE3sYcV7.css → app-CjsbDlb7.css} +143 -0
  12. package/dist/assets/{app-DSYrDg0V.js → app-DaTMg3nH.js} +612 -120
  13. package/dist/assets/cli/{render-ZMHR9HkV.js → render-DPf4AYJK.js} +38 -16
  14. package/dist/assets/{evalWorker-DbNs7Dkp.js → evalWorker-CjZZWRWW.js} +1428 -1038
  15. package/dist/assets/{jointPose-DO6mnXn_.js → jointPose-DzQOViQH.js} +1 -1
  16. package/dist/assets/{manifold-BGlQBBH9.js → manifold-BYlzU521.js} +1 -1
  17. package/dist/assets/{manifold-fy2MV7K1.js → manifold-DgXo0T5P.js} +2 -2
  18. package/dist/assets/{manifold-BU-tJwQh.js → manifold-K1SkarlQ.js} +1 -1
  19. package/dist/assets/{reportWorker-DO6hcQbh.js → reportWorker-B9nWwSrB.js} +1402 -1012
  20. package/dist/assets/{scalar-sampling-budget-o90NSNmF.js → scalar-sampling-budget-prBw_s8t.js} +2139 -1749
  21. package/dist/cli/render.html +1 -1
  22. package/dist/docs/index.html +2 -2
  23. package/dist/docs-raw/CLI.md +18 -3
  24. package/dist/docs-raw/generated/assembly.md +70 -5
  25. package/dist/docs-raw/generated/concepts.md +16 -2
  26. package/dist/docs-raw/generated/core.md +9 -2
  27. package/dist/docs-raw/generated/lib.md +1 -1
  28. package/dist/docs-raw/generated/output.md +14 -43
  29. package/dist/docs-raw/generated/runtime-names.md +4 -4
  30. package/dist/docs-raw/guides/simready-quickstart.md +171 -0
  31. package/dist/docs-raw/simulation-workflow.md +273 -0
  32. package/dist/index.html +2 -2
  33. package/dist/sitemap.xml +25 -13
  34. package/dist-cli/{check-compiler-JTVBITCR.js → check-compiler-II7NLPAB.js} +1 -1
  35. package/dist-cli/{check-query-propagation-3FFLSMVN.js → check-query-propagation-7462TR3R.js} +1 -1
  36. package/dist-cli/{chunk-OAN5T4XD.js → chunk-UWTJCGXF.js} +1455 -722
  37. package/dist-cli/forgecad.js +2994 -529
  38. package/dist-skill/CONTEXT.md +94 -55
  39. package/dist-skill/docs/API/core/concepts.md +1 -1
  40. package/dist-skill/docs/CLI.md +18 -3
  41. package/dist-skill/docs/generated/assembly.md +66 -5
  42. package/dist-skill/docs/generated/core.md +9 -2
  43. package/dist-skill/docs/generated/lib.md +1 -1
  44. package/dist-skill/docs/generated/output.md +14 -43
  45. package/dist-skill/docs/generated/runtime-names.md +4 -4
  46. package/examples/robotics/README.md +46 -0
  47. package/examples/robotics/scout-cam-rover-simready/README.md +119 -0
  48. package/examples/robotics/scout-cam-rover-simready/lib/dims.js +140 -0
  49. package/examples/robotics/scout-cam-rover-simready/main.forge.js +343 -0
  50. package/examples/robotics/scout-cam-rover-simready/parts/body.forge.js +304 -0
  51. package/examples/robotics/scout-cam-rover-simready/parts/chassis.forge.js +320 -0
  52. package/examples/robotics/scout-cam-rover-simready/parts/hardware.forge.js +21 -0
  53. package/examples/robotics/scout-cam-rover-simready/parts/turret.forge.js +70 -0
  54. package/examples/robotics/scout-cam-rover-simready/parts/wheel.forge.js +116 -0
  55. package/examples/robotics/simready-asset-crate.forge.js +79 -0
  56. package/examples/robotics/simready-diff-drive-rover.forge.js +141 -0
  57. package/examples/robotics/simready-parallel-gripper.forge.js +102 -0
  58. package/package.json +1 -1
@@ -361,8 +361,8 @@ function normalizeVariadicArgs({ apiName, inputs, minCount, itemName, usage, coe
361
361
  return flattened.map((value, index) => {
362
362
  try {
363
363
  return coerce(value);
364
- } catch (error) {
365
- const message = error instanceof Error ? error.message : String(error);
364
+ } catch (error2) {
365
+ const message = error2 instanceof Error ? error2.message : String(error2);
366
366
  throw new Error(`${apiName} argument ${index + 1}: ${message}`);
367
367
  }
368
368
  });
@@ -2442,7 +2442,7 @@ function cloneProfileCompilePlan(plan) {
2442
2442
  return {
2443
2443
  kind: "boolean",
2444
2444
  op: plan.op,
2445
- profiles: plan.profiles.map((profile) => cloneProfileCompilePlan(profile)),
2445
+ profiles: plan.profiles.map((profile2) => cloneProfileCompilePlan(profile2)),
2446
2446
  transforms: plan.transforms.map(cloneProfileTransform)
2447
2447
  };
2448
2448
  case "offset":
@@ -2580,7 +2580,7 @@ function cloneShapeCompilePlan(plan) {
2580
2580
  case "loft":
2581
2581
  result = {
2582
2582
  kind: "loft",
2583
- profiles: plan.profiles.map((profile) => cloneProfileCompilePlan(profile)),
2583
+ profiles: plan.profiles.map((profile2) => cloneProfileCompilePlan(profile2)),
2584
2584
  heights: plan.heights.map((height) => height),
2585
2585
  edgeLength: plan.edgeLength,
2586
2586
  boundsPadding: plan.boundsPadding,
@@ -2975,7 +2975,7 @@ function buildBooleanProfileCompilePlan(op, profiles2) {
2975
2975
  return {
2976
2976
  kind: "boolean",
2977
2977
  op,
2978
- profiles: profiles2.map((profile) => cloneProfileCompilePlan(profile)),
2978
+ profiles: profiles2.map((profile2) => cloneProfileCompilePlan(profile2)),
2979
2979
  transforms: []
2980
2980
  };
2981
2981
  }
@@ -3044,7 +3044,7 @@ function buildChamferShapeCompilePlan(base, edge, size, quadrant) {
3044
3044
  function buildLoftShapeCompilePlan(profiles2, heights, options) {
3045
3045
  return {
3046
3046
  kind: "loft",
3047
- profiles: profiles2.map((profile) => cloneProfileCompilePlan(profile)),
3047
+ profiles: profiles2.map((profile2) => cloneProfileCompilePlan(profile2)),
3048
3048
  heights: heights.map((height) => canonicalNumber(height)),
3049
3049
  edgeLength: canonicalNumber(options.edgeLength),
3050
3050
  boundsPadding: canonicalNumber(options.boundsPadding),
@@ -3053,10 +3053,10 @@ function buildLoftShapeCompilePlan(profiles2, heights, options) {
3053
3053
  edgeLabels: options.edgeLabels ? { ...options.edgeLabels } : void 0
3054
3054
  };
3055
3055
  }
3056
- function buildSweepShapeCompilePlan(profile, path2, options) {
3056
+ function buildSweepShapeCompilePlan(profile2, path2, options) {
3057
3057
  return {
3058
3058
  kind: "sweep",
3059
- profile: cloneProfileCompilePlan(profile),
3059
+ profile: cloneProfileCompilePlan(profile2),
3060
3060
  path: cloneSweepPathCompilePlan(path2),
3061
3061
  edgeLength: canonicalNumber(options.edgeLength),
3062
3062
  boundsPadding: canonicalNumber(options.boundsPadding),
@@ -4543,48 +4543,48 @@ var DRAFT_TO_LOFT_EPSILON = 1e-9;
4543
4543
  var DRAFT_TO_LOFT_ZERO_EPSILON = 1e-12;
4544
4544
  var DRAFT_TO_LOFT_EDGE_LENGTH = 1;
4545
4545
  var DRAFT_TO_LOFT_BOUNDS_PADDING = 1;
4546
- function offsetProfile(profile, delta) {
4547
- if (Math.abs(delta) <= DRAFT_TO_LOFT_ZERO_EPSILON) return cloneProfileCompilePlan(profile);
4546
+ function offsetProfile(profile2, delta) {
4547
+ if (Math.abs(delta) <= DRAFT_TO_LOFT_ZERO_EPSILON) return cloneProfileCompilePlan(profile2);
4548
4548
  return {
4549
4549
  kind: "offset",
4550
- base: cloneProfileCompilePlan(profile),
4550
+ base: cloneProfileCompilePlan(profile2),
4551
4551
  delta,
4552
4552
  join: "Miter",
4553
4553
  transforms: []
4554
4554
  };
4555
4555
  }
4556
- function scaleProfile(profile, scale11) {
4556
+ function scaleProfile(profile2, scale11) {
4557
4557
  if (!Number.isFinite(scale11[0]) || !Number.isFinite(scale11[1]) || scale11[0] <= DRAFT_TO_LOFT_EPSILON || scale11[1] <= DRAFT_TO_LOFT_EPSILON) {
4558
4558
  return null;
4559
4559
  }
4560
4560
  if (Math.abs(scale11[0] - 1) <= DRAFT_TO_LOFT_EPSILON && Math.abs(scale11[1] - 1) <= DRAFT_TO_LOFT_EPSILON) {
4561
- return cloneProfileCompilePlan(profile);
4561
+ return cloneProfileCompilePlan(profile2);
4562
4562
  }
4563
- if (profile.transforms.length > 0) return null;
4564
- switch (profile.kind) {
4563
+ if (profile2.transforms.length > 0) return null;
4564
+ switch (profile2.kind) {
4565
4565
  case "rect":
4566
- return { kind: "rect", width: profile.width * scale11[0], height: profile.height * scale11[1], transforms: [] };
4566
+ return { kind: "rect", width: profile2.width * scale11[0], height: profile2.height * scale11[1], transforms: [] };
4567
4567
  case "roundedRect":
4568
4568
  if (Math.abs(scale11[0] - scale11[1]) > DRAFT_TO_LOFT_EPSILON) return null;
4569
4569
  return {
4570
4570
  kind: "roundedRect",
4571
- width: profile.width * scale11[0],
4572
- height: profile.height * scale11[1],
4573
- radius: profile.radius * scale11[0],
4571
+ width: profile2.width * scale11[0],
4572
+ height: profile2.height * scale11[1],
4573
+ radius: profile2.radius * scale11[0],
4574
4574
  transforms: []
4575
4575
  };
4576
4576
  case "circle":
4577
4577
  if (Math.abs(scale11[0] - scale11[1]) > DRAFT_TO_LOFT_EPSILON) return null;
4578
- return { kind: "circle", radius: profile.radius * scale11[0], segments: profile.segments, transforms: [] };
4578
+ return { kind: "circle", radius: profile2.radius * scale11[0], segments: profile2.segments, transforms: [] };
4579
4579
  case "polygon":
4580
- return { kind: "polygon", points: profile.points.map(([x, y]) => [x * scale11[0], y * scale11[1]]), transforms: [] };
4580
+ return { kind: "polygon", points: profile2.points.map(([x, y]) => [x * scale11[0], y * scale11[1]]), transforms: [] };
4581
4581
  case "pathProfile":
4582
4582
  return null;
4583
4583
  case "boolean": {
4584
- if (profile.transforms.length > 0) return null;
4585
- const profiles2 = profile.profiles.map((child) => scaleProfile(child, scale11));
4584
+ if (profile2.transforms.length > 0) return null;
4585
+ const profiles2 = profile2.profiles.map((child) => scaleProfile(child, scale11));
4586
4586
  if (profiles2.some((child) => child == null)) return null;
4587
- return { kind: "boolean", op: profile.op, profiles: profiles2, transforms: [] };
4587
+ return { kind: "boolean", op: profile2.op, profiles: profiles2, transforms: [] };
4588
4588
  }
4589
4589
  case "offset":
4590
4590
  case "project":
@@ -4597,10 +4597,10 @@ function directVerticalPrismDraftBase(plan) {
4597
4597
  return directVerticalPrismDraftBase(plan.base);
4598
4598
  case "box": {
4599
4599
  if (![plan.x, plan.y, plan.z].every(Number.isFinite)) return null;
4600
- const profile = { kind: "rect", width: Math.abs(plan.x), height: Math.abs(plan.y), transforms: [] };
4600
+ const profile2 = { kind: "rect", width: Math.abs(plan.x), height: Math.abs(plan.y), transforms: [] };
4601
4601
  return {
4602
- bottomProfile: cloneProfileCompilePlan(profile),
4603
- topProfile: cloneProfileCompilePlan(profile),
4602
+ bottomProfile: cloneProfileCompilePlan(profile2),
4603
+ topProfile: cloneProfileCompilePlan(profile2),
4604
4604
  zMin: Math.min(0, plan.z),
4605
4605
  zMax: Math.max(0, plan.z)
4606
4606
  };
@@ -4951,13 +4951,13 @@ function buildHoleShapeCompilePlan(base, placement, hole2, extent) {
4951
4951
  extent: cloneFeatureCutExtent2(extent)
4952
4952
  };
4953
4953
  }
4954
- function buildCutShapeCompilePlan(base, placement, profile, extent, taper) {
4955
- if (!base || !placement || !profile || !isFinitePositive2(featureCutExtentDepth(extent))) return null;
4954
+ function buildCutShapeCompilePlan(base, placement, profile2, extent, taper) {
4955
+ if (!base || !placement || !profile2 || !isFinitePositive2(featureCutExtentDepth(extent))) return null;
4956
4956
  return {
4957
4957
  kind: "cut",
4958
4958
  base: cloneShapeCompilePlan(base),
4959
4959
  placement: cloneShapeWorkplanePlacement(placement),
4960
- profile: cloneProfileCompilePlan(profile),
4960
+ profile: cloneProfileCompilePlan(profile2),
4961
4961
  extent: cloneFeatureCutExtent2(extent),
4962
4962
  taper: taper ? { scale: [taper.scale[0], taper.scale[1]] } : void 0
4963
4963
  };
@@ -6179,12 +6179,12 @@ function parseModel(data) {
6179
6179
  const objects = [];
6180
6180
  for (const match of xml.matchAll(/<object\b([^>]*)>([\s\S]*?)<\/object>/gi)) {
6181
6181
  const attrs = parseAttrs(match[1]);
6182
- const body = match[2];
6183
- const vertices = [...body.matchAll(/<vertex\b([^>]*)\/?>/gi)].map((vertexMatch) => {
6182
+ const body2 = match[2];
6183
+ const vertices = [...body2.matchAll(/<vertex\b([^>]*)\/?>/gi)].map((vertexMatch) => {
6184
6184
  const vertex = parseAttrs(vertexMatch[1]);
6185
6185
  return [finiteNumber(vertex.x, "vertex x"), finiteNumber(vertex.y, "vertex y"), finiteNumber(vertex.z, "vertex z")];
6186
6186
  });
6187
- const triangles = [...body.matchAll(/<triangle\b([^>]*)\/?>/gi)].map((triangleMatch) => {
6187
+ const triangles = [...body2.matchAll(/<triangle\b([^>]*)\/?>/gi)].map((triangleMatch) => {
6188
6188
  const triangle = parseAttrs(triangleMatch[1]);
6189
6189
  return [
6190
6190
  finiteIndex(triangle.v1, "triangle v1"),
@@ -6204,8 +6204,8 @@ function parseModel(data) {
6204
6204
  type: cleanName(attrs.type) ?? void 0,
6205
6205
  vertices,
6206
6206
  triangles,
6207
- meshCount: [...body.matchAll(/<mesh\b/gi)].length,
6208
- components: [...body.matchAll(/<component\b([^>]*)\/?>/gi)].map((componentMatch) => {
6207
+ meshCount: [...body2.matchAll(/<mesh\b/gi)].length,
6208
+ components: [...body2.matchAll(/<component\b([^>]*)\/?>/gi)].map((componentMatch) => {
6209
6209
  const component = parseAttrs(componentMatch[1]);
6210
6210
  if (!component.objectid) throw new Error(`3MF object "${attrs.id ?? objects.length + 1}" contains a component without objectid`);
6211
6211
  return { objectId: component.objectid, transform: parseTransform(component.transform) };
@@ -8790,8 +8790,8 @@ function positiveMod(v, period) {
8790
8790
  function evalStripesPattern(u, v, directionX, directionY, spacing, width, depth) {
8791
8791
  const coord = u * directionX + v * directionY;
8792
8792
  const d = abs3(coord - Math.round(coord / spacing) * spacing);
8793
- const profile = max2(0, 1 - d / (width * 0.5));
8794
- return -(profile * profile) * depth;
8793
+ const profile2 = max2(0, 1 - d / (width * 0.5));
8794
+ return -(profile2 * profile2) * depth;
8795
8795
  }
8796
8796
  function evalOverUnderWeavePattern(u, v, spacingX, spacingY, widthX, widthY, depth, underScale) {
8797
8797
  const su = u / spacingX;
@@ -13014,7 +13014,7 @@ function applyProfileCompileTransforms(crossSection, transforms) {
13014
13014
  return out;
13015
13015
  }
13016
13016
  function lowerProfileBooleanCompilePlan(plan, wasm) {
13017
- const profiles2 = plan.profiles.map((profile) => lowerProfileCompilePlanToCrossSection(profile, wasm));
13017
+ const profiles2 = plan.profiles.map((profile2) => lowerProfileCompilePlanToCrossSection(profile2, wasm));
13018
13018
  if (profiles2.length === 0) {
13019
13019
  throw new Error(`Cannot lower empty profile boolean (${plan.op})`);
13020
13020
  }
@@ -13414,11 +13414,11 @@ function fromSlicesOffsetEquivalentPlanForManifold(plan) {
13414
13414
  };
13415
13415
  return transformZAlignedFromSlicesPlanForManifold(zAligned, group2.normal);
13416
13416
  }
13417
- function offsetProfilePolygonsForManifold(profile, thickness, wasm) {
13417
+ function offsetProfilePolygonsForManifold(profile2, thickness, wasm) {
13418
13418
  const crossSection = lowerProfileCompilePlanToCrossSection(
13419
13419
  {
13420
13420
  kind: "offset",
13421
- base: profile,
13421
+ base: profile2,
13422
13422
  delta: thickness,
13423
13423
  join: "Miter",
13424
13424
  transforms: []
@@ -13431,8 +13431,8 @@ function offsetProfilePolygonsForManifold(profile, thickness, wasm) {
13431
13431
  disposeWasmObject(crossSection);
13432
13432
  }
13433
13433
  }
13434
- function profilePolygonsForManifold(profile, wasm) {
13435
- const crossSection = lowerProfileCompilePlanToCrossSection(profile, wasm);
13434
+ function profilePolygonsForManifold(profile2, wasm) {
13435
+ const crossSection = lowerProfileCompilePlanToCrossSection(profile2, wasm);
13436
13436
  try {
13437
13437
  return crossSection.toPolygons();
13438
13438
  } finally {
@@ -13475,7 +13475,7 @@ function lowerOffsetLoftCompilePlan(plan, thickness, wasm) {
13475
13475
  if (heights.some((height) => !Number.isFinite(height)) || heights.some((height, idx) => idx > 0 && height <= heights[idx - 1] + OFFSET_SOLID_EPS)) {
13476
13476
  throw new Error("offsetSolid() collapsed the compatible-loft height span.");
13477
13477
  }
13478
- const offsetPolygons = plan.profiles.map((profile) => offsetProfilePolygonsForManifold(profile, thickness, wasm));
13478
+ const offsetPolygons = plan.profiles.map((profile2) => offsetProfilePolygonsForManifold(profile2, thickness, wasm));
13479
13479
  const stitched = loftStitched(offsetPolygons, heights, wasm, { edgeLength: plan.edgeLength });
13480
13480
  if (!stitched) {
13481
13481
  throw new Error(`Offset solid requires the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
@@ -13586,7 +13586,7 @@ function lowerOffsetSolidCompilePlan(plan, wasm) {
13586
13586
  if (zMax <= zMin + OFFSET_SOLID_EPS) {
13587
13587
  throw new Error("offsetSolid() collapsed the vertical-prism height.");
13588
13588
  }
13589
- const profile = lowerProfileCompilePlanToCrossSection(
13589
+ const profile2 = lowerProfileCompilePlanToCrossSection(
13590
13590
  {
13591
13591
  kind: "offset",
13592
13592
  base: base.profile,
@@ -13597,16 +13597,16 @@ function lowerOffsetSolidCompilePlan(plan, wasm) {
13597
13597
  wasm
13598
13598
  );
13599
13599
  try {
13600
- const solid = profile.extrude(zMax - zMin, 0, 0, void 0, false);
13600
+ const solid = profile2.extrude(zMax - zMin, 0, 0, void 0, false);
13601
13601
  if (Math.abs(zMin) <= OFFSET_SOLID_EPS) return solid;
13602
13602
  return applyShapeCompileTransforms(solid, [{ kind: "translate", x: 0, y: 0, z: zMin }]);
13603
13603
  } finally {
13604
- disposeWasmObject(profile);
13604
+ disposeWasmObject(profile2);
13605
13605
  }
13606
13606
  }
13607
13607
  function lowerShapeLoftCompilePlan(plan, wasm) {
13608
- const inputPolygons = plan.profiles.map((profile) => {
13609
- const crossSection = lowerProfileCompilePlanToCrossSection(profile, wasm);
13608
+ const inputPolygons = plan.profiles.map((profile2) => {
13609
+ const crossSection = lowerProfileCompilePlanToCrossSection(profile2, wasm);
13610
13610
  try {
13611
13611
  return crossSection.toPolygons();
13612
13612
  } finally {
@@ -14007,9 +14007,9 @@ function lowerShapeFilletCompilePlan(plan, wasm) {
14007
14007
  const result = applyFilletSelectionToManifold(base, selection.selection, plan.radius, plan.segments, wasm);
14008
14008
  if (result !== base) disposeWasmObject(base);
14009
14009
  return result;
14010
- } catch (error) {
14010
+ } catch (error2) {
14011
14011
  disposeWasmObject(base);
14012
- throw error;
14012
+ throw error2;
14013
14013
  }
14014
14014
  }
14015
14015
  function lowerShapeChamferCompilePlan(plan, wasm) {
@@ -14025,9 +14025,9 @@ function lowerShapeChamferCompilePlan(plan, wasm) {
14025
14025
  const result = applyChamferSelectionToManifold(base, selection.selection, plan.size, wasm);
14026
14026
  if (result !== base) disposeWasmObject(base);
14027
14027
  return result;
14028
- } catch (error) {
14028
+ } catch (error2) {
14029
14029
  disposeWasmObject(base);
14030
- throw error;
14030
+ throw error2;
14031
14031
  }
14032
14032
  }
14033
14033
  function edgeSegmentToSelection(segment) {
@@ -14188,10 +14188,10 @@ function lowerShapeCompilePlanToManifold(plan, wasm) {
14188
14188
  }
14189
14189
  }
14190
14190
  case "extrude": {
14191
- const profile = lowerProfileCompilePlanToCrossSection(plan.profile, wasm);
14191
+ const profile2 = lowerProfileCompilePlanToCrossSection(plan.profile, wasm);
14192
14192
  try {
14193
14193
  const height = plan.height;
14194
- const solid = profile.extrude(
14194
+ const solid = profile2.extrude(
14195
14195
  Math.abs(height),
14196
14196
  plan.twistSegments ?? 0,
14197
14197
  plan.twist ?? 0,
@@ -14205,7 +14205,7 @@ function lowerShapeCompilePlanToManifold(plan, wasm) {
14205
14205
  disposeWasmObject(solid);
14206
14206
  }
14207
14207
  } finally {
14208
- disposeWasmObject(profile);
14208
+ disposeWasmObject(profile2);
14209
14209
  }
14210
14210
  }
14211
14211
  case "sheetMetal":
@@ -14226,11 +14226,11 @@ function lowerShapeCompilePlanToManifold(plan, wasm) {
14226
14226
  return lowerShapeCompilePlanToManifold(lowered.plan, wasm);
14227
14227
  }
14228
14228
  case "revolve": {
14229
- const profile = lowerProfileCompilePlanToCrossSection(plan.profile, wasm);
14229
+ const profile2 = lowerProfileCompilePlanToCrossSection(plan.profile, wasm);
14230
14230
  try {
14231
- return profile.revolve(plan.segments ?? 0, plan.degrees);
14231
+ return profile2.revolve(plan.segments ?? 0, plan.degrees);
14232
14232
  } finally {
14233
- disposeWasmObject(profile);
14233
+ disposeWasmObject(profile2);
14234
14234
  }
14235
14235
  }
14236
14236
  case "loft":
@@ -14757,9 +14757,9 @@ var ManifoldProfileBackend = class _ManifoldProfileBackend {
14757
14757
  function wrapManifoldProfileBackend(cs) {
14758
14758
  return new ManifoldProfileBackend(cs);
14759
14759
  }
14760
- function requireManifoldCrossSection(profile) {
14761
- if (profile instanceof ManifoldProfileBackend) {
14762
- return profile.requireCrossSection();
14760
+ function requireManifoldCrossSection(profile2) {
14761
+ if (profile2 instanceof ManifoldProfileBackend) {
14762
+ return profile2.requireCrossSection();
14763
14763
  }
14764
14764
  throw new Error("requireManifoldCrossSection(): expected a ManifoldProfileBackend");
14765
14765
  }
@@ -15522,9 +15522,9 @@ var OCCTProfileBackend = class _OCCTProfileBackend {
15522
15522
  function wrapOCCTProfileBackend(face) {
15523
15523
  return new OCCTProfileBackend(face);
15524
15524
  }
15525
- function requireOCCTFace(profile) {
15526
- if (profile instanceof OCCTProfileBackend) {
15527
- return profile.requireFace();
15525
+ function requireOCCTFace(profile2) {
15526
+ if (profile2 instanceof OCCTProfileBackend) {
15527
+ return profile2.requireFace();
15528
15528
  }
15529
15529
  throw new Error("requireOCCTFace(): expected an OCCTProfileBackend");
15530
15530
  }
@@ -17804,9 +17804,9 @@ function expandBounds3(b, amount) {
17804
17804
  const pad = Math.abs(amount);
17805
17805
  return bounds3([b.min[0] - pad, b.min[1] - pad, b.min[2] - pad], [b.max[0] + pad, b.max[1] + pad, b.max[2] + pad]);
17806
17806
  }
17807
- function boundsFromProfileZ(profile, z0, z1) {
17808
- if (!profile) return null;
17809
- return bounds3([profile.min[0], profile.min[1], z0], [profile.max[0], profile.max[1], z1]);
17807
+ function boundsFromProfileZ(profile2, z0, z1) {
17808
+ if (!profile2) return null;
17809
+ return bounds3([profile2.min[0], profile2.min[1], z0], [profile2.max[0], profile2.max[1], z1]);
17810
17810
  }
17811
17811
  function transformBounds(b, transform) {
17812
17812
  if (!b) return null;
@@ -17857,14 +17857,14 @@ function transformedBySteps(b, steps) {
17857
17857
  return out;
17858
17858
  }
17859
17859
  function extrudeBounds(plan) {
17860
- const profile = boundsFromProfilePlan(plan.profile);
17861
- let base = boundsFromProfileZ(profile, 0, plan.height);
17862
- if (!base || !profile) return null;
17860
+ const profile2 = boundsFromProfilePlan(plan.profile);
17861
+ let base = boundsFromProfileZ(profile2, 0, plan.height);
17862
+ if (!base || !profile2) return null;
17863
17863
  if (plan.scaleTop) {
17864
17864
  const top = boundsFromProfileZ(
17865
17865
  {
17866
- min: [profile.min[0] * plan.scaleTop[0], profile.min[1] * plan.scaleTop[1]],
17867
- max: [profile.max[0] * plan.scaleTop[0], profile.max[1] * plan.scaleTop[1]]
17866
+ min: [profile2.min[0] * plan.scaleTop[0], profile2.min[1] * plan.scaleTop[1]],
17867
+ max: [profile2.max[0] * plan.scaleTop[0], profile2.max[1] * plan.scaleTop[1]]
17868
17868
  },
17869
17869
  plan.height,
17870
17870
  plan.height
@@ -17873,10 +17873,10 @@ function extrudeBounds(plan) {
17873
17873
  }
17874
17874
  if (plan.twist && Math.abs(plan.twist) > 1e-9 && base) {
17875
17875
  const radial = Math.max(
17876
- Math.hypot(profile.min[0], profile.min[1]),
17877
- Math.hypot(profile.min[0], profile.max[1]),
17878
- Math.hypot(profile.max[0], profile.min[1]),
17879
- Math.hypot(profile.max[0], profile.max[1])
17876
+ Math.hypot(profile2.min[0], profile2.min[1]),
17877
+ Math.hypot(profile2.min[0], profile2.max[1]),
17878
+ Math.hypot(profile2.max[0], profile2.min[1]),
17879
+ Math.hypot(profile2.max[0], profile2.max[1])
17880
17880
  );
17881
17881
  base = bounds3([-radial, -radial, base.min[2]], [radial, radial, base.max[2]]);
17882
17882
  }
@@ -17902,13 +17902,13 @@ function pathBounds(path2) {
17902
17902
  );
17903
17903
  }
17904
17904
  }
17905
- function profileRadius(profile) {
17906
- if (!profile) return 0;
17905
+ function profileRadius(profile2) {
17906
+ if (!profile2) return 0;
17907
17907
  return Math.max(
17908
- Math.hypot(profile.min[0], profile.min[1]),
17909
- Math.hypot(profile.min[0], profile.max[1]),
17910
- Math.hypot(profile.max[0], profile.min[1]),
17911
- Math.hypot(profile.max[0], profile.max[1])
17908
+ Math.hypot(profile2.min[0], profile2.min[1]),
17909
+ Math.hypot(profile2.min[0], profile2.max[1]),
17910
+ Math.hypot(profile2.max[0], profile2.min[1]),
17911
+ Math.hypot(profile2.max[0], profile2.max[1])
17912
17912
  );
17913
17913
  }
17914
17914
  function analyticSurfaceBounds(plan) {
@@ -17957,14 +17957,14 @@ function boundsFromShapeBase(plan) {
17957
17957
  case "extrude":
17958
17958
  return extrudeBounds(plan);
17959
17959
  case "revolve": {
17960
- const profile = boundsFromProfilePlan(plan.profile);
17961
- if (!profile) return null;
17962
- const radius = Math.max(Math.abs(profile.min[0]), Math.abs(profile.max[0]));
17963
- return bounds3([-radius, -radius, profile.min[1]], [radius, radius, profile.max[1]]);
17960
+ const profile2 = boundsFromProfilePlan(plan.profile);
17961
+ if (!profile2) return null;
17962
+ const radius = Math.max(Math.abs(profile2.min[0]), Math.abs(profile2.max[0]));
17963
+ return bounds3([-radius, -radius, profile2.min[1]], [radius, radius, profile2.max[1]]);
17964
17964
  }
17965
17965
  case "loft":
17966
17966
  return unionBounds3(
17967
- plan.profiles.map((profile, i) => boundsFromProfileZ(boundsFromProfilePlan(profile), plan.heights[i] ?? 0, plan.heights[i] ?? 0))
17967
+ plan.profiles.map((profile2, i) => boundsFromProfileZ(boundsFromProfilePlan(profile2), plan.heights[i] ?? 0, plan.heights[i] ?? 0))
17968
17968
  );
17969
17969
  case "sweep":
17970
17970
  return expandBounds3(pathBounds(plan.path), profileRadius(boundsFromProfilePlan(plan.profile)) + plan.boundsPadding);
@@ -19010,12 +19010,12 @@ function wrapTruckPolygonProfileBackend(polygons) {
19010
19010
  function wrapTruckProfileRegions(regions) {
19011
19011
  return new TruckPolygonProfileBackend(regions, true);
19012
19012
  }
19013
- function requireTruckPolygonProfileBackend(profile) {
19014
- if (profile instanceof TruckPolygonProfileBackend) return profile;
19013
+ function requireTruckPolygonProfileBackend(profile2) {
19014
+ if (profile2 instanceof TruckPolygonProfileBackend) return profile2;
19015
19015
  throw new Error("requireTruckPolygonProfileBackend(): expected a TruckPolygonProfileBackend");
19016
19016
  }
19017
- function requireTruckProfileRegions(profile) {
19018
- return requireTruckPolygonProfileBackend(profile).requireRegions();
19017
+ function requireTruckProfileRegions(profile2) {
19018
+ return requireTruckPolygonProfileBackend(profile2).requireRegions();
19019
19019
  }
19020
19020
 
19021
19021
  // src/forge/backends/truck/shapeBackend.ts
@@ -20330,7 +20330,7 @@ function lowerProfileBooleanToRegions(plan) {
20330
20330
  if (plan.profiles.length === 0) {
20331
20331
  throw new Error(`Truck backend cannot lower empty profile boolean "${plan.op}".`);
20332
20332
  }
20333
- const operands = plan.profiles.map((profile) => regionsToMultiPolygon2(profilePlanToRegions(profile)));
20333
+ const operands = plan.profiles.map((profile2) => regionsToMultiPolygon2(profilePlanToRegions(profile2)));
20334
20334
  const result = (() => {
20335
20335
  switch (plan.op) {
20336
20336
  case "union":
@@ -21127,8 +21127,8 @@ function lowerGenericBooleanPlan(plan) {
21127
21127
  let rawBoolean;
21128
21128
  try {
21129
21129
  rawBoolean = wrapTruckShapeBackend(getTruckGeometryWasm().geometry_boolean(JSON.stringify(booleanHandles), plan.op));
21130
- } catch (error) {
21131
- if (plan.op !== "union") throw error;
21130
+ } catch (error2) {
21131
+ if (plan.op !== "union") throw error2;
21132
21132
  returned = wrapTruckShapeBackend(getTruckGeometryWasm().geometry_union(JSON.stringify(booleanHandles)));
21133
21133
  return returned;
21134
21134
  }
@@ -21479,9 +21479,9 @@ function lowerImportedMeshPlan(plan) {
21479
21479
  })
21480
21480
  )
21481
21481
  );
21482
- } catch (error) {
21482
+ } catch (error2) {
21483
21483
  throw new Error(
21484
- `importMesh("${plan.filePath}"): Truck rejected the mesh; it must be closed, manifold, and non-degenerate. Original error: ${error instanceof Error ? error.message : String(error)}`
21484
+ `importMesh("${plan.filePath}"): Truck rejected the mesh; it must be closed, manifold, and non-degenerate. Original error: ${error2 instanceof Error ? error2.message : String(error2)}`
21485
21485
  );
21486
21486
  }
21487
21487
  }
@@ -21696,8 +21696,8 @@ function lowerSdfPlan(plan) {
21696
21696
  const shape = lowerExtractedSdfMesh(mesh, cappedEvalFn, true);
21697
21697
  if (plan.meshing?.diagnostics) logSdfMeshingDiagnostics("SDF meshing result", plan.meshing.diagnostics);
21698
21698
  return shape;
21699
- } catch (error) {
21700
- surfaceNetsError = error;
21699
+ } catch (error2) {
21700
+ surfaceNetsError = error2;
21701
21701
  }
21702
21702
  const tetraMesh = marchingTetrahedra(cappedEvalFn, plan.bounds, plan.edgeLength);
21703
21703
  assertSdfMeshBudget(tetraMesh, plan);
@@ -21705,9 +21705,9 @@ function lowerSdfPlan(plan) {
21705
21705
  const shape = lowerExtractedSdfMesh(tetraMesh, cappedEvalFn, false);
21706
21706
  if (plan.meshing?.diagnostics) logSdfMeshingDiagnostics("SDF meshing result", plan.meshing.diagnostics);
21707
21707
  return shape;
21708
- } catch (error) {
21708
+ } catch (error2) {
21709
21709
  throw new Error(
21710
- `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)}`
21710
+ `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 ${error2 instanceof Error ? error2.message : String(error2)}`
21711
21711
  );
21712
21712
  }
21713
21713
  }
@@ -21863,7 +21863,7 @@ function lowerDraftPlan2(plan) {
21863
21863
  );
21864
21864
  }
21865
21865
  function offsetLoftPlan(plan, thickness) {
21866
- const offsetStations = plan.profiles.map((profile) => offsetRegionsWithClipper2(profilePlanToRegions(profile), thickness, "Miter"));
21866
+ const offsetStations = plan.profiles.map((profile2) => offsetRegionsWithClipper2(profilePlanToRegions(profile2), thickness, "Miter"));
21867
21867
  if (!regionsHaveMatchingTopology(offsetStations)) {
21868
21868
  return truckUnsupported("offsetSolid() for lofts whose station offsets change profile topology");
21869
21869
  }
@@ -22083,8 +22083,8 @@ function lowerLoftPlan2(plan) {
22083
22083
  return wrapTruckShapeBackend(
22084
22084
  getTruckGeometryWasm().geometry_create_lofted_regions(
22085
22085
  JSON.stringify(
22086
- plan.profiles.map((profile, idx) => ({
22087
- regions: profilePlanToRegions(profile),
22086
+ plan.profiles.map((profile2, idx) => ({
22087
+ regions: profilePlanToRegions(profile2),
22088
22088
  z: plan.heights[idx]
22089
22089
  }))
22090
22090
  )
@@ -22431,9 +22431,9 @@ function lowerThickenedClosedSewMesh(shapes, thickness) {
22431
22431
  if (!mesh) return null;
22432
22432
  try {
22433
22433
  return wrapTruckShapeBackend(getTruckGeometryWasm().geometry_create_imported_triangle_mesh(JSON.stringify(mesh)));
22434
- } catch (error) {
22434
+ } catch (error2) {
22435
22435
  throw new Error(
22436
- `Truck backend could not thicken the closed sewn surface set as a manifold shell: ${error instanceof Error ? error.message : String(error)}`
22436
+ `Truck backend could not thicken the closed sewn surface set as a manifold shell: ${error2 instanceof Error ? error2.message : String(error2)}`
22437
22437
  );
22438
22438
  }
22439
22439
  }
@@ -22510,9 +22510,9 @@ function lowerSurfaceSolidPlan2(plan) {
22510
22510
  if (!mesh) truckUnsupported("Surface.Solid() for this surface source");
22511
22511
  try {
22512
22512
  return wrapTruckShapeBackend(getTruckGeometryWasm().geometry_create_imported_triangle_mesh(JSON.stringify(mesh)));
22513
- } catch (error) {
22513
+ } catch (error2) {
22514
22514
  throw new Error(
22515
- `Truck backend could not build Surface.Solid() from the closed surface set: ${error instanceof Error ? error.message : String(error)}`
22515
+ `Truck backend could not build Surface.Solid() from the closed surface set: ${error2 instanceof Error ? error2.message : String(error2)}`
22516
22516
  );
22517
22517
  }
22518
22518
  }
@@ -22917,9 +22917,9 @@ function transformedSphereProfile(basePlan, steps, sliceOffset) {
22917
22917
  const radius = Math.abs(source.radius * scale11);
22918
22918
  const sectionRadius = sliceOffset == null ? radius : Math.sqrt(Math.max(0, radius * radius - (sliceOffset - center[2]) * (sliceOffset - center[2])));
22919
22919
  if (sectionRadius <= EXACT_PROFILE_EPS) return emptyProfilePlan();
22920
- const profile = circleProfilePlan(sectionRadius, source.segments);
22921
- if (!isNearlyZero(center[0]) || !isNearlyZero(center[1])) profile.transforms.push({ kind: "translate", x: center[0], y: center[1] });
22922
- return profile;
22920
+ const profile2 = circleProfilePlan(sectionRadius, source.segments);
22921
+ if (!isNearlyZero(center[0]) || !isNearlyZero(center[1])) profile2.transforms.push({ kind: "translate", x: center[0], y: center[1] });
22922
+ return profile2;
22923
22923
  }
22924
22924
  function profileTransformsForShapeTransform(step) {
22925
22925
  switch (step.kind) {
@@ -23823,15 +23823,15 @@ function centeredRectSegmentEnvelopeProjectionProfilePlan(footprints) {
23823
23823
  for (let idx = 0; idx + 1 < footprints.length; idx += 1) {
23824
23824
  const a = footprints[idx];
23825
23825
  const b = footprints[idx + 1];
23826
- const profile = centeredRectPairProjectionProfilePlan({ outer: a.outer, holes: [] }, { outer: b.outer, holes: [] });
23827
- if (!profile) return null;
23826
+ const profile2 = centeredRectPairProjectionProfilePlan({ outer: a.outer, holes: [] }, { outer: b.outer, holes: [] });
23827
+ if (!profile2) return null;
23828
23828
  const segmentOuter = selectLargestNestedRectFootprint([a.outer, b.outer]) ?? {
23829
23829
  width: Math.max(a.outer.width, b.outer.width),
23830
23830
  height: Math.max(a.outer.height, b.outer.height)
23831
23831
  };
23832
23832
  const holes = centeredRectCommonHoleFootprints([a, b], segmentOuter);
23833
23833
  if (!holes) return null;
23834
- profiles2.push(differenceCenteredRectHoles(profile, holes));
23834
+ profiles2.push(differenceCenteredRectHoles(profile2, holes));
23835
23835
  }
23836
23836
  if (profiles2.length === 1) return profiles2[0];
23837
23837
  return { kind: "boolean", op: "union", profiles: profiles2, transforms: [] };
@@ -24373,8 +24373,8 @@ function singleRegionWithoutHoles(regions) {
24373
24373
  }
24374
24374
  function nestedSingleRegionLoftProjectionProfilePlan(profiles2) {
24375
24375
  if (profiles2.length < 2) return null;
24376
- const regions = profiles2.map((profile) => {
24377
- const lowered = regionsForProfilePlan(profile);
24376
+ const regions = profiles2.map((profile2) => {
24377
+ const lowered = regionsForProfilePlan(profile2);
24378
24378
  return lowered ? singleRegionWithoutHoles(lowered) : null;
24379
24379
  });
24380
24380
  if (regions.some((region) => !region)) return null;
@@ -24403,8 +24403,8 @@ function loftProjectedProfilePlan(plan) {
24403
24403
  function variableSweepProjectedProfilePlan(plan) {
24404
24404
  const pathInfo = verticalSweepPathInfo(plan.path, plan.up);
24405
24405
  if (!pathInfo || !hasDistinctFiniteSectionParameters(plan.sections)) return null;
24406
- const profile = compatibleLoftProjectionProfilePlan(plan.sections.map((section) => section.profile));
24407
- return profile ? appendProfileTransforms(profile, pathInfo.transforms) : null;
24406
+ const profile2 = compatibleLoftProjectionProfilePlan(plan.sections.map((section) => section.profile));
24407
+ return profile2 ? appendProfileTransforms(profile2, pathInfo.transforms) : null;
24408
24408
  }
24409
24409
  function normalizeIntervals(intervals) {
24410
24410
  const sorted = intervals.map(([a, b]) => [Math.min(a, b), Math.max(a, b)]).filter(([a, b]) => b - a > EXACT_PROFILE_EPS).sort((a, b) => a[0] - b[0]);
@@ -24451,7 +24451,7 @@ function subtractIntervals(subject, clips) {
24451
24451
  return out;
24452
24452
  }
24453
24453
  function profilePlanForRadialIntervals(intervals, segments) {
24454
- const profiles2 = normalizeIntervals(intervals).map(([inner, outer]) => annulusProfilePlan(outer, Math.max(0, inner), segments)).filter((profile) => !isEmptyProfilePlan(profile));
24454
+ const profiles2 = normalizeIntervals(intervals).map(([inner, outer]) => annulusProfilePlan(outer, Math.max(0, inner), segments)).filter((profile2) => !isEmptyProfilePlan(profile2));
24455
24455
  if (profiles2.length === 0) return emptyProfilePlan();
24456
24456
  if (profiles2.length === 1) return profiles2[0];
24457
24457
  return { kind: "boolean", op: "union", profiles: profiles2, transforms: [] };
@@ -24482,7 +24482,7 @@ function sectorProfilePlan(innerRadius, outerRadius, degrees, segments) {
24482
24482
  }
24483
24483
  function partialRevolutionProfilePlanForRadialIntervals(intervals, degrees, segments) {
24484
24484
  if (!Number.isFinite(degrees) || isNearlyZero(degrees)) return null;
24485
- const profiles2 = normalizeIntervals(intervals).map(([inner, outer]) => sectorProfilePlan(inner, outer, degrees, segments)).filter((profile) => !isEmptyProfilePlan(profile));
24485
+ const profiles2 = normalizeIntervals(intervals).map(([inner, outer]) => sectorProfilePlan(inner, outer, degrees, segments)).filter((profile2) => !isEmptyProfilePlan(profile2));
24486
24486
  if (profiles2.length === 0) return emptyProfilePlan();
24487
24487
  if (profiles2.length === 1) return profiles2[0];
24488
24488
  return { kind: "boolean", op: "union", profiles: profiles2, transforms: [] };
@@ -24715,11 +24715,11 @@ function expandSimpleFullRevolutionProfileFootprint(plan, distance5) {
24715
24715
  }
24716
24716
  }
24717
24717
  function profilePlanForSimpleFullRevolutionFootprint(footprint, segments) {
24718
- const translate = (profile, center) => {
24718
+ const translate = (profile2, center) => {
24719
24719
  if (!isNearlyZero(center[0]) || !isNearlyZero(center[1])) {
24720
- profile.transforms.push({ kind: "translate", x: center[0], y: center[1] });
24720
+ profile2.transforms.push({ kind: "translate", x: center[0], y: center[1] });
24721
24721
  }
24722
- return profile;
24722
+ return profile2;
24723
24723
  };
24724
24724
  switch (footprint.kind) {
24725
24725
  case "circle":
@@ -24737,12 +24737,12 @@ function profilePlanForSimpleFullRevolutionFootprint(footprint, segments) {
24737
24737
  case "roundedRect": {
24738
24738
  const { halfWidth, halfHeight, radius, xAxis, yAxis, center } = footprint.footprint;
24739
24739
  if (halfWidth <= EXACT_PROFILE_EPS || halfHeight <= EXACT_PROFILE_EPS || radius < -EXACT_PROFILE_EPS) return null;
24740
- const profile = roundedRectProfilePlan({
24740
+ const profile2 = roundedRectProfilePlan({
24741
24741
  width: halfWidth * 2,
24742
24742
  height: halfHeight * 2,
24743
24743
  radius
24744
24744
  });
24745
- profile.transforms.push(
24745
+ profile2.transforms.push(
24746
24746
  ...profileOrientationTransforms({
24747
24747
  m00: xAxis[0],
24748
24748
  m01: yAxis[0],
@@ -24750,7 +24750,7 @@ function profilePlanForSimpleFullRevolutionFootprint(footprint, segments) {
24750
24750
  m11: yAxis[1]
24751
24751
  })
24752
24752
  );
24753
- return translate(profile, center);
24753
+ return translate(profile2, center);
24754
24754
  }
24755
24755
  default:
24756
24756
  return assertExhaustive(footprint);
@@ -24854,9 +24854,9 @@ function insetRadialIntervalsForFullRevolutionProfile(plan, distance5) {
24854
24854
  if (plan.op === "union") {
24855
24855
  const baseIntervalGroups = [];
24856
24856
  const insetIntervals = [];
24857
- for (const profile of profiles2) {
24858
- const baseIntervals = radialProjectionIntervalsForRevolveProfile(profile);
24859
- const profileInsetIntervals = insetRadialIntervalsForFullRevolutionProfile(profile, distance5);
24857
+ for (const profile2 of profiles2) {
24858
+ const baseIntervals = radialProjectionIntervalsForRevolveProfile(profile2);
24859
+ const profileInsetIntervals = insetRadialIntervalsForFullRevolutionProfile(profile2, distance5);
24860
24860
  if (!baseIntervals || !profileInsetIntervals) return null;
24861
24861
  baseIntervalGroups.push(baseIntervals);
24862
24862
  insetIntervals.push(...profileInsetIntervals);
@@ -24905,9 +24905,9 @@ function insetSliceProfileForFullRevolutionProfile(plan, distance5, segments) {
24905
24905
  if (plan.op === "union") {
24906
24906
  const baseIntervalGroups = [];
24907
24907
  const sliceProfiles = [];
24908
- for (const profile of profiles2) {
24909
- const baseIntervals = radialProjectionIntervalsForRevolveProfile(profile);
24910
- const sliceProfile = insetSliceProfileForFullRevolutionProfile(profile, distance5, segments);
24908
+ for (const profile2 of profiles2) {
24909
+ const baseIntervals = radialProjectionIntervalsForRevolveProfile(profile2);
24910
+ const sliceProfile = insetSliceProfileForFullRevolutionProfile(profile2, distance5, segments);
24911
24911
  if (!baseIntervals || !sliceProfile) return null;
24912
24912
  baseIntervalGroups.push(baseIntervals);
24913
24913
  sliceProfiles.push(sliceProfile);
@@ -25061,7 +25061,7 @@ function fullRevolutionOffsetSourceContext(plan) {
25061
25061
  }
25062
25062
  }
25063
25063
  function booleanProfilesWithParentTransforms(plan) {
25064
- return plan.transforms.length === 0 ? plan.profiles : plan.profiles.map((profile) => appendProfileTransforms(profile, plan.transforms));
25064
+ return plan.transforms.length === 0 ? plan.profiles : plan.profiles.map((profile2) => appendProfileTransforms(profile2, plan.transforms));
25065
25065
  }
25066
25066
  function radialProjectionIntervalsForRevolveProfile(plan) {
25067
25067
  if (plan.kind === "circle") {
@@ -25078,8 +25078,8 @@ function radialProjectionIntervalsForRevolveProfile(plan) {
25078
25078
  const profiles2 = booleanProfilesWithParentTransforms(plan);
25079
25079
  if (plan.op === "union") {
25080
25080
  const intervals2 = [];
25081
- for (const profile of profiles2) {
25082
- const profileIntervals = radialProjectionIntervalsForRevolveProfile(profile);
25081
+ for (const profile2 of profiles2) {
25082
+ const profileIntervals = radialProjectionIntervalsForRevolveProfile(profile2);
25083
25083
  if (!profileIntervals) return null;
25084
25084
  intervals2.push(...profileIntervals);
25085
25085
  }
@@ -25152,8 +25152,8 @@ function radialSliceIntervalsForRevolveProfile(plan, z) {
25152
25152
  const profiles2 = booleanProfilesWithParentTransforms(plan);
25153
25153
  if (plan.op === "union") {
25154
25154
  const intervals2 = [];
25155
- for (const profile of profiles2) {
25156
- const profileIntervals = radialSliceIntervalsForRevolveProfile(profile, z);
25155
+ for (const profile2 of profiles2) {
25156
+ const profileIntervals = radialSliceIntervalsForRevolveProfile(profile2, z);
25157
25157
  if (!profileIntervals) return null;
25158
25158
  intervals2.push(...profileIntervals);
25159
25159
  }
@@ -25276,9 +25276,9 @@ function offsetSolidFullRevolutionProfilePlan(plan) {
25276
25276
  const baseProjectionIntervals = radialProjectionIntervalsForRevolveProfile(source.plan.profile);
25277
25277
  const projectionIntervals = localThickness > EXACT_PROFILE_EPS ? baseProjectionIntervals ? expandRadialIntervalsForOutwardOffset(baseProjectionIntervals, localThickness) : null : insetRadialIntervalsForFullRevolutionProfile(source.plan.profile, Math.abs(localThickness));
25278
25278
  const sliceProfile = localThickness < -EXACT_PROFILE_EPS ? insetSliceProfileForFullRevolutionProfile(source.plan.profile, Math.abs(localThickness), source.plan.segments) : null;
25279
- const profile = fullRevolutionOffsetProfilePlan(source.plan.profile, localThickness);
25280
- return profile ? {
25281
- profile,
25279
+ const profile2 = fullRevolutionOffsetProfilePlan(source.plan.profile, localThickness);
25280
+ return profile2 ? {
25281
+ profile: profile2,
25282
25282
  projectionIntervals,
25283
25283
  sliceProfile,
25284
25284
  segments: source.plan.segments,
@@ -25336,25 +25336,25 @@ function exactVerticalProjectionProfilePlan(plan) {
25336
25336
  case "transform": {
25337
25337
  const base = exactVerticalProjectionProfilePlan(plan.base);
25338
25338
  if (!base) return null;
25339
- let profile = base.profile;
25339
+ let profile2 = base.profile;
25340
25340
  let span = [base.zMin, base.zMax];
25341
25341
  for (const step of plan.steps) {
25342
25342
  const projectedTransforms = profileTransformsForShapeTransform(step);
25343
25343
  if (!projectedTransforms) return null;
25344
25344
  const nextSpan = transformZSpan(span, step);
25345
25345
  if (!nextSpan) return null;
25346
- profile = appendProfileTransforms(profile, projectedTransforms);
25346
+ profile2 = appendProfileTransforms(profile2, projectedTransforms);
25347
25347
  span = nextSpan;
25348
25348
  }
25349
- return { profile, zMin: span[0], zMax: span[1] };
25349
+ return { profile: profile2, zMin: span[0], zMax: span[1] };
25350
25350
  }
25351
25351
  default:
25352
25352
  return null;
25353
25353
  }
25354
25354
  }
25355
25355
  function zSpanOverlap(profiles2) {
25356
- const zMin = Math.max(...profiles2.map((profile) => profile.zMin));
25357
- const zMax = Math.min(...profiles2.map((profile) => profile.zMax));
25356
+ const zMin = Math.max(...profiles2.map((profile2) => profile2.zMin));
25357
+ const zMax = Math.min(...profiles2.map((profile2) => profile2.zMax));
25358
25358
  return zMax - zMin > EXACT_PROFILE_EPS;
25359
25359
  }
25360
25360
  function clippedZSpan(subject, clip) {
@@ -25427,7 +25427,7 @@ function regionsHaveMatchingTopology2(stations) {
25427
25427
  }
25428
25428
  function loftSlicedProfilePlan(plan, offset2) {
25429
25429
  if (plan.profiles.length !== plan.heights.length || plan.profiles.length < 2) return null;
25430
- const stations = plan.profiles.map((profile, idx) => ({ profile, height: plan.heights[idx] })).sort((a, b) => a.height - b.height);
25430
+ const stations = plan.profiles.map((profile2, idx) => ({ profile: profile2, height: plan.heights[idx] })).sort((a, b) => a.height - b.height);
25431
25431
  if (stations.some((station) => !Number.isFinite(station.height))) return null;
25432
25432
  if (stations.some((station, idx) => idx > 0 && station.height <= stations[idx - 1].height + EXACT_PROFILE_EPS)) return null;
25433
25433
  if (!offsetInRange(offset2, stations[0].height, stations[stations.length - 1].height)) return emptyProfilePlan();
@@ -25454,17 +25454,17 @@ function offsetSolidCompatibleLoftSlicedProfilePlan(plan, offset2) {
25454
25454
  const base = compatibleLoftProfileReplayBasePlan(plan.base);
25455
25455
  if (!base || !Number.isFinite(plan.thickness) || isNearlyZero(plan.thickness)) return null;
25456
25456
  const offsetProfiles = base.profiles.map(
25457
- (profile) => ({
25457
+ (profile2) => ({
25458
25458
  kind: "offset",
25459
- base: cloneProfileCompilePlan(profile),
25459
+ base: cloneProfileCompilePlan(profile2),
25460
25460
  delta: plan.thickness,
25461
25461
  join: "Miter",
25462
25462
  transforms: []
25463
25463
  })
25464
25464
  );
25465
25465
  const offsetStations = [];
25466
- for (const profile of offsetProfiles) {
25467
- const regions = regionsForProfilePlan(profile);
25466
+ for (const profile2 of offsetProfiles) {
25467
+ const regions = regionsForProfilePlan(profile2);
25468
25468
  if (!regions) return null;
25469
25469
  offsetStations.push(regions);
25470
25470
  }
@@ -25502,9 +25502,9 @@ function offsetSolidCompatibleLoftProjectedProfilePlan(plan) {
25502
25502
  if (!hasDistinctFiniteHeights(base.heights)) return null;
25503
25503
  return compatibleLoftProjectionProfilePlan(
25504
25504
  base.profiles.map(
25505
- (profile) => ({
25505
+ (profile2) => ({
25506
25506
  kind: "offset",
25507
- base: cloneProfileCompilePlan(profile),
25507
+ base: cloneProfileCompilePlan(profile2),
25508
25508
  delta: plan.thickness,
25509
25509
  join: "Miter",
25510
25510
  transforms: []
@@ -25518,7 +25518,7 @@ function offsetSolidVerticalVariableSweepProjectedProfilePlan(plan) {
25518
25518
  if (base.kind !== "variableSweep" || !Number.isFinite(plan.thickness) || isNearlyZero(plan.thickness)) return null;
25519
25519
  const pathInfo = verticalSweepPathInfo(base.path, base.up);
25520
25520
  if (!pathInfo || !hasDistinctFiniteSectionParameters(base.sections)) return null;
25521
- const profile = compatibleLoftProjectionProfilePlan(
25521
+ const profile2 = compatibleLoftProjectionProfilePlan(
25522
25522
  base.sections.map(
25523
25523
  (section) => ({
25524
25524
  kind: "offset",
@@ -25529,7 +25529,7 @@ function offsetSolidVerticalVariableSweepProjectedProfilePlan(plan) {
25529
25529
  })
25530
25530
  )
25531
25531
  );
25532
- return profile ? appendProfileTransforms(profile, pathInfo.transforms) : null;
25532
+ return profile2 ? appendProfileTransforms(profile2, pathInfo.transforms) : null;
25533
25533
  }
25534
25534
  function offsetSolidVerticalVariableSweepSlicedProfilePlan(plan, offset2) {
25535
25535
  let base = plan.base;
@@ -25734,27 +25734,27 @@ function exactTruckProjectedProfilePlan(plan) {
25734
25734
  case "transform": {
25735
25735
  const transformedSphere = transformedSphereProfile(plan.base, plan.steps);
25736
25736
  if (transformedSphere) return transformedSphere;
25737
- let profile = exactTruckProjectedProfilePlan(plan.base);
25738
- if (!profile) return null;
25737
+ let profile2 = exactTruckProjectedProfilePlan(plan.base);
25738
+ if (!profile2) return null;
25739
25739
  for (const step of plan.steps) {
25740
25740
  const projectedTransforms = profileTransformsForShapeTransform(step);
25741
25741
  if (!projectedTransforms) return null;
25742
- profile = appendProfileTransforms(profile, projectedTransforms);
25742
+ profile2 = appendProfileTransforms(profile2, projectedTransforms);
25743
25743
  }
25744
- return profile;
25744
+ return profile2;
25745
25745
  }
25746
25746
  case "boolean": {
25747
25747
  if (plan.op === "union") {
25748
25748
  const profiles3 = [];
25749
25749
  for (const shape of plan.shapes) {
25750
- const profile = exactTruckProjectedProfilePlan(shape);
25751
- if (!profile) return null;
25752
- profiles3.push(profile);
25750
+ const profile2 = exactTruckProjectedProfilePlan(shape);
25751
+ if (!profile2) return null;
25752
+ profiles3.push(profile2);
25753
25753
  }
25754
25754
  return { kind: "boolean", op: "union", profiles: profiles3, transforms: [] };
25755
25755
  }
25756
25756
  const verticalProfiles = plan.shapes.map(exactVerticalProjectionProfilePlan);
25757
- if (verticalProfiles.some((profile) => !profile)) return null;
25757
+ if (verticalProfiles.some((profile2) => !profile2)) return null;
25758
25758
  const profiles2 = verticalProfiles;
25759
25759
  switch (plan.op) {
25760
25760
  case "difference": {
@@ -25764,7 +25764,7 @@ function exactTruckProjectedProfilePlan(plan) {
25764
25764
  }
25765
25765
  case "intersection":
25766
25766
  if (profiles2.length === 0 || !zSpanOverlap(profiles2)) return emptyProfilePlan();
25767
- return { kind: "boolean", op: "intersection", profiles: profiles2.map((profile) => profile.profile), transforms: [] };
25767
+ return { kind: "boolean", op: "intersection", profiles: profiles2.map((profile2) => profile2.profile), transforms: [] };
25768
25768
  default:
25769
25769
  return assertExhaustive(plan.op);
25770
25770
  }
@@ -25859,15 +25859,15 @@ function exactTruckSlicedProfilePlan(plan, offset2) {
25859
25859
  case "extrude": {
25860
25860
  if (isNearlyZero(plan.height) || !offsetInRange(offset2, 0, plan.height)) return emptyProfilePlan();
25861
25861
  if (plan.twist != null && !isNearlyZero(plan.twist)) return null;
25862
- const profile = cloneProfileCompilePlan(plan.profile);
25862
+ const profile2 = cloneProfileCompilePlan(plan.profile);
25863
25863
  if (plan.scaleTop) {
25864
25864
  const t = offset2 / plan.height;
25865
25865
  const scaleX = 1 + (plan.scaleTop[0] - 1) * t;
25866
25866
  const scaleY = 1 + (plan.scaleTop[1] - 1) * t;
25867
25867
  if (isNearlyZero(scaleX) || isNearlyZero(scaleY)) return emptyProfilePlan();
25868
- profile.transforms.push({ kind: "scale", x: scaleX, y: scaleY });
25868
+ profile2.transforms.push({ kind: "scale", x: scaleX, y: scaleY });
25869
25869
  }
25870
- return profile;
25870
+ return profile2;
25871
25871
  }
25872
25872
  case "transform": {
25873
25873
  const transformedSphere = transformedSphereProfile(plan.base, plan.steps, offset2);
@@ -25878,25 +25878,25 @@ function exactTruckSlicedProfilePlan(plan, offset2) {
25878
25878
  if (nextOffset == null) return null;
25879
25879
  sourceOffset = nextOffset;
25880
25880
  }
25881
- let profile = exactTruckSlicedProfilePlan(plan.base, sourceOffset);
25882
- if (!profile) return null;
25881
+ let profile2 = exactTruckSlicedProfilePlan(plan.base, sourceOffset);
25882
+ if (!profile2) return null;
25883
25883
  for (const step of plan.steps) {
25884
25884
  const transforms = profileTransformsForShapeTransform(step);
25885
25885
  if (!transforms) return null;
25886
- profile = appendProfileTransforms(profile, transforms);
25886
+ profile2 = appendProfileTransforms(profile2, transforms);
25887
25887
  }
25888
- return profile;
25888
+ return profile2;
25889
25889
  }
25890
25890
  case "boolean": {
25891
25891
  const profiles2 = [];
25892
25892
  for (const shape of plan.shapes) {
25893
- const profile = exactTruckSlicedProfilePlan(shape, offset2);
25894
- if (!profile) return null;
25895
- profiles2.push(profile);
25893
+ const profile2 = exactTruckSlicedProfilePlan(shape, offset2);
25894
+ if (!profile2) return null;
25895
+ profiles2.push(profile2);
25896
25896
  }
25897
25897
  switch (plan.op) {
25898
25898
  case "union": {
25899
- const nonEmpty = profiles2.filter((profile) => !isEmptyProfilePlan(profile));
25899
+ const nonEmpty = profiles2.filter((profile2) => !isEmptyProfilePlan(profile2));
25900
25900
  if (nonEmpty.length === 0) return emptyProfilePlan();
25901
25901
  if (nonEmpty.length === 1) return cloneProfileCompilePlan(nonEmpty[0]);
25902
25902
  return { kind: "boolean", op: "union", profiles: nonEmpty, transforms: [] };
@@ -25904,7 +25904,7 @@ function exactTruckSlicedProfilePlan(plan, offset2) {
25904
25904
  case "difference": {
25905
25905
  const [subject, ...clips] = profiles2;
25906
25906
  if (!subject || isEmptyProfilePlan(subject)) return emptyProfilePlan();
25907
- const nonEmptyClips = clips.filter((profile) => !isEmptyProfilePlan(profile));
25907
+ const nonEmptyClips = clips.filter((profile2) => !isEmptyProfilePlan(profile2));
25908
25908
  if (nonEmptyClips.length === 0) return cloneProfileCompilePlan(subject);
25909
25909
  return { kind: "boolean", op: "difference", profiles: [subject, ...nonEmptyClips], transforms: [] };
25910
25910
  }
@@ -26527,26 +26527,26 @@ function findOriginOperation(plan) {
26527
26527
  return { operation: plan.kind };
26528
26528
  }
26529
26529
  }
26530
- function summarizeProfile(profile) {
26531
- switch (profile.kind) {
26530
+ function summarizeProfile(profile2) {
26531
+ switch (profile2.kind) {
26532
26532
  case "rect":
26533
- return `${profile.width}\xD7${profile.height} rect`;
26533
+ return `${profile2.width}\xD7${profile2.height} rect`;
26534
26534
  case "roundedRect":
26535
- return `${profile.width}\xD7${profile.height} rounded rect`;
26535
+ return `${profile2.width}\xD7${profile2.height} rounded rect`;
26536
26536
  case "circle":
26537
- return `r=${profile.radius} circle`;
26537
+ return `r=${profile2.radius} circle`;
26538
26538
  case "polygon":
26539
- return `polygon (${profile.points.length} pts)`;
26539
+ return `polygon (${profile2.points.length} pts)`;
26540
26540
  case "boolean":
26541
- return `${profile.op} profile`;
26541
+ return `${profile2.op} profile`;
26542
26542
  case "offset":
26543
26543
  return "offset profile";
26544
26544
  case "project":
26545
26545
  return "projected profile";
26546
26546
  case "pathProfile":
26547
- return `path profile (${profile.edges.length} edges)`;
26547
+ return `path profile (${profile2.edges.length} edges)`;
26548
26548
  default:
26549
- assertExhaustive(profile);
26549
+ assertExhaustive(profile2);
26550
26550
  }
26551
26551
  }
26552
26552
  function collectTimelineEntries(plan, entries) {
@@ -27200,13 +27200,13 @@ function isRectanglePolygon(points) {
27200
27200
  const cross13 = vectors[1][0] * vectors[3][1] - vectors[1][1] * vectors[3][0];
27201
27201
  return Math.abs(dot01) <= 1e-6 * Math.max(1, lengths[0] * lengths[1]) && Math.abs(dot12) <= 1e-6 * Math.max(1, lengths[1] * lengths[2]) && Math.abs(dot23) <= 1e-6 * Math.max(1, lengths[2] * lengths[3]) && Math.abs(dot30) <= 1e-6 * Math.max(1, lengths[3] * lengths[0]) && Math.abs(cross02) <= 1e-6 * Math.max(1, lengths[0] * lengths[2]) && Math.abs(cross13) <= 1e-6 * Math.max(1, lengths[1] * lengths[3]);
27202
27202
  }
27203
- function rectLikeProfileCorners(profile) {
27204
- const matrix = profileTransformMatrix(profile.transforms);
27205
- if (profile.kind === "rect" || profile.kind === "roundedRect") {
27206
- const minX = -profile.width / 2;
27207
- const minY = -profile.height / 2;
27208
- const maxX = minX + profile.width;
27209
- const maxY = minY + profile.height;
27203
+ function rectLikeProfileCorners(profile2) {
27204
+ const matrix = profileTransformMatrix(profile2.transforms);
27205
+ if (profile2.kind === "rect" || profile2.kind === "roundedRect") {
27206
+ const minX = -profile2.width / 2;
27207
+ const minY = -profile2.height / 2;
27208
+ const maxX = minX + profile2.width;
27209
+ const maxY = minY + profile2.height;
27210
27210
  return [
27211
27211
  profilePoint(matrix, minX, minY),
27212
27212
  profilePoint(matrix, maxX, minY),
@@ -27214,8 +27214,8 @@ function rectLikeProfileCorners(profile) {
27214
27214
  profilePoint(matrix, minX, maxY)
27215
27215
  ];
27216
27216
  }
27217
- if (!isRectanglePolygon(profile.points)) return null;
27218
- const transformed = profile.points.map(([x, y]) => profilePoint(matrix, x, y));
27217
+ if (!isRectanglePolygon(profile2.points)) return null;
27218
+ const transformed = profile2.points.map(([x, y]) => profilePoint(matrix, x, y));
27219
27219
  return [transformed[0], transformed[1], transformed[2], transformed[3]];
27220
27220
  }
27221
27221
  function buildBoxFaceTable(plan, owner) {
@@ -27297,8 +27297,8 @@ function buildCylinderFaceTable(plan, owner) {
27297
27297
  });
27298
27298
  return table;
27299
27299
  }
27300
- function buildRectExtrudeFaceTable(profile, height, owner) {
27301
- const corners2 = rectLikeProfileCorners(profile);
27300
+ function buildRectExtrudeFaceTable(profile2, height, owner) {
27301
+ const corners2 = rectLikeProfileCorners(profile2);
27302
27302
  if (!corners2) return emptyFaceTable();
27303
27303
  const [bl, br, tr, tl] = corners2;
27304
27304
  const center2d = midpoint2d(bl, tr);
@@ -27331,11 +27331,11 @@ function buildRectExtrudeFaceTable(profile, height, owner) {
27331
27331
  registerFace(table, faceFrom2DEdge("side-left", tl, bl, (zTop + zBot) / 2, createTrackedFaceQuery("side-left", owner)));
27332
27332
  return table;
27333
27333
  }
27334
- function buildCircleExtrudeFaceTable(profile, height, owner) {
27334
+ function buildCircleExtrudeFaceTable(profile2, height, owner) {
27335
27335
  const table = emptyFaceTable();
27336
- const matrix = profileTransformMatrix(profile.transforms);
27336
+ const matrix = profileTransformMatrix(profile2.transforms);
27337
27337
  const origin = Transform.from(matrix).point([0, 0, 0]);
27338
- const sidePoint = Transform.from(matrix).point([profile.radius, 0, 0]);
27338
+ const sidePoint = Transform.from(matrix).point([profile2.radius, 0, 0]);
27339
27339
  const sideNormal = normalizeAxis([sidePoint[0] - origin[0], sidePoint[1] - origin[1], 0]);
27340
27340
  const xAxis = normalizeAxis(Transform.from(matrix).vector([1, 0, 0]));
27341
27341
  const yAxis = normalizeAxis(Transform.from(matrix).vector([0, 1, 0]));
@@ -27490,16 +27490,16 @@ function holeCreatedFaceNames(hole2, extent) {
27490
27490
  if (featureCutExtentReverseSide(extent)?.kind === "blind") names.push("cap");
27491
27491
  return names;
27492
27492
  }
27493
- function cutCreatedFaceNames(profile, extent, _taper) {
27493
+ function cutCreatedFaceNames(profile2, extent, _taper) {
27494
27494
  const names = (() => {
27495
- switch (profile.kind) {
27495
+ switch (profile2.kind) {
27496
27496
  case "circle":
27497
27497
  return ["wall"];
27498
27498
  case "rect":
27499
27499
  case "roundedRect":
27500
27500
  return ["wall-bottom", "wall-right", "wall-top", "wall-left"];
27501
27501
  case "polygon":
27502
- return isRectanglePolygon(profile.points) ? ["wall-bottom", "wall-right", "wall-top", "wall-left"] : [];
27502
+ return isRectanglePolygon(profile2.points) ? ["wall-bottom", "wall-right", "wall-top", "wall-left"] : [];
27503
27503
  default:
27504
27504
  return [];
27505
27505
  }
@@ -27518,8 +27518,8 @@ function holeCreatedEdgeNames(hole2, extent) {
27518
27518
  }
27519
27519
  return names;
27520
27520
  }
27521
- function cutCreatedEdgeNames(profile, extent, taper) {
27522
- if (cutCreatedFaceNames(profile, extent, taper).length === 0) return [];
27521
+ function cutCreatedEdgeNames(profile2, extent, taper) {
27522
+ if (cutCreatedFaceNames(profile2, extent, taper).length === 0) return [];
27523
27523
  const names = ["entry-rim", "forward-end-rim"];
27524
27524
  if (featureCutExtentReverseSide(extent)) {
27525
27525
  names.push("reverse-end-rim");
@@ -28267,14 +28267,14 @@ function supportedShellCreatedFaceNames(basePlan, openFaces) {
28267
28267
  function supportedHoleCreatedFaceNames(hole2, extent) {
28268
28268
  return holeCreatedFaceNames(hole2, extent);
28269
28269
  }
28270
- function supportedCutCreatedFaceNames(profile, extent, taper) {
28271
- return cutCreatedFaceNames(profile, extent, taper);
28270
+ function supportedCutCreatedFaceNames(profile2, extent, taper) {
28271
+ return cutCreatedFaceNames(profile2, extent, taper);
28272
28272
  }
28273
28273
  function supportedHoleCreatedEdgeNames(hole2, extent) {
28274
28274
  return holeCreatedEdgeNames(hole2, extent);
28275
28275
  }
28276
- function supportedCutCreatedEdgeNames(profile, extent, taper) {
28277
- return cutCreatedEdgeNames(profile, extent, taper);
28276
+ function supportedCutCreatedEdgeNames(profile2, extent, taper) {
28277
+ return cutCreatedEdgeNames(profile2, extent, taper);
28278
28278
  }
28279
28279
  function preservedShapeFaceQueries(basePlan) {
28280
28280
  return listShapeFaceQueries(basePlan);
@@ -31595,7 +31595,7 @@ function buildHoleTopologyRewritePropagation(owner, base, placement, hole2, exte
31595
31595
  buildHoleCutEdgePropagation(propagation, owner, base, blocked, "hole");
31596
31596
  return propagation;
31597
31597
  }
31598
- function buildCutTopologyRewritePropagation(owner, base, placement, profile, extent, taper) {
31598
+ function buildCutTopologyRewritePropagation(owner, base, placement, profile2, extent, taper) {
31599
31599
  const propagation = createTopologyRewritePropagation("cut", owner);
31600
31600
  const blocked = new Map(blockedShapeFacesForFeature(base, placement.workplane.source, extent).map((entry) => [entry.name, entry.reason]));
31601
31601
  for (const entry of preservedShapeFaceQueries(base)) {
@@ -31630,7 +31630,7 @@ function buildCutTopologyRewritePropagation(owner, base, placement, profile, ext
31630
31630
  })
31631
31631
  );
31632
31632
  }
31633
- const createdNames = supportedCutCreatedFaceNames(profile, extent);
31633
+ const createdNames = supportedCutCreatedFaceNames(profile2, extent);
31634
31634
  for (const name of createdNames) {
31635
31635
  const query = createCreatedFaceQueryRef(owner, "cut", name);
31636
31636
  propagation.createdFaces.push({
@@ -31644,7 +31644,7 @@ function buildCutTopologyRewritePropagation(owner, base, placement, profile, ext
31644
31644
  })
31645
31645
  );
31646
31646
  }
31647
- for (const name of supportedCutCreatedEdgeNames(profile, extent, taper)) {
31647
+ for (const name of supportedCutCreatedEdgeNames(profile2, extent, taper)) {
31648
31648
  const query = createCreatedEdgeQueryRef(owner, "cut", name);
31649
31649
  propagation.createdEdges.push({
31650
31650
  query,
@@ -32103,8 +32103,8 @@ function selectTruckKernelEdges(shape, query) {
32103
32103
  return JSON.parse(
32104
32104
  getTruckGeometryWasm().geometry_select_edges(getTruckShapeBackendHandle(backend), JSON.stringify(query))
32105
32105
  );
32106
- } catch (error) {
32107
- throw new Error(`selectEdges(): native Truck edge selection failed: ${error instanceof Error ? error.message : String(error)}`);
32106
+ } catch (error2) {
32107
+ throw new Error(`selectEdges(): native Truck edge selection failed: ${error2 instanceof Error ? error2.message : String(error2)}`);
32108
32108
  }
32109
32109
  }
32110
32110
  function selectEdges(shape, query = {}) {
@@ -33230,11 +33230,11 @@ function extractSdfExpression(source) {
33230
33230
  const src = source.toString().trim();
33231
33231
  const arrowIdx = src.indexOf("=>");
33232
33232
  if (arrowIdx !== -1) {
33233
- const body = src.slice(arrowIdx + 2).trim();
33234
- if (body.startsWith("{") && body.endsWith("}")) {
33235
- return expressionFromFunctionBody(body.slice(1, -1));
33233
+ const body2 = src.slice(arrowIdx + 2).trim();
33234
+ if (body2.startsWith("{") && body2.endsWith("}")) {
33235
+ return expressionFromFunctionBody(body2.slice(1, -1));
33236
33236
  }
33237
- return stripReturnKeyword(body);
33237
+ return stripReturnKeyword(body2);
33238
33238
  }
33239
33239
  const start = src.indexOf("{");
33240
33240
  const end = src.lastIndexOf("}");
@@ -33319,8 +33319,8 @@ function validateSdfFunctionConstant(value, label) {
33319
33319
  }
33320
33320
  throw new Error(`sdf.fromFunction() ${label} must be a JSON-compatible value.`);
33321
33321
  }
33322
- function expressionFromFunctionBody(body) {
33323
- const trimmed = body.trim();
33322
+ function expressionFromFunctionBody(body2) {
33323
+ const trimmed = body2.trim();
33324
33324
  const match = trimmed.match(/^return\s+([\s\S]*?);?$/);
33325
33325
  if (match) return stripReturnKeyword(match[1].trim());
33326
33326
  return `(function() {
@@ -33422,8 +33422,8 @@ var SurfacePattern = class {
33422
33422
  body;
33423
33423
  /** Named constants injected into the function. */
33424
33424
  constants;
33425
- constructor(body, constants) {
33426
- this.body = body;
33425
+ constructor(body2, constants) {
33426
+ this.body = body2;
33427
33427
  this.constants = constants;
33428
33428
  }
33429
33429
  };
@@ -33432,8 +33432,8 @@ function getTypedSurfacePattern(pattern) {
33432
33432
  return typedSurfacePatterns.get(pattern);
33433
33433
  }
33434
33434
  var Pattern2D = class extends SurfacePattern {
33435
- constructor(body) {
33436
- super(body);
33435
+ constructor(body2) {
33436
+ super(body2);
33437
33437
  }
33438
33438
  /** Add this pattern to one or more patterns or constant height offsets. */
33439
33439
  add(...patterns) {
@@ -33686,10 +33686,10 @@ Fix: use a known preset or pass material props directly.`
33686
33686
  if (!input || typeof input !== "object") {
33687
33687
  throw new Error(`${label}() expects a material preset string or material properties object.`);
33688
33688
  }
33689
- const { color, ...material } = input;
33689
+ const { color, ...material2 } = input;
33690
33690
  return {
33691
33691
  ...typeof color === "string" ? { color } : {},
33692
- material: validateMaterialProps(material)
33692
+ material: validateMaterialProps(material2)
33693
33693
  };
33694
33694
  }
33695
33695
  function requirePositiveFinite4(value, label) {
@@ -34250,21 +34250,21 @@ var SdfShape = class _SdfShape {
34250
34250
  * ```
34251
34251
  */
34252
34252
  surfaceDisplace(pattern, options) {
34253
- let body;
34253
+ let body2;
34254
34254
  let constants;
34255
34255
  let typedPattern;
34256
34256
  if (pattern instanceof SurfacePattern) {
34257
- body = pattern.body;
34257
+ body2 = pattern.body;
34258
34258
  constants = pattern.constants;
34259
34259
  typedPattern = getTypedSurfacePattern(pattern);
34260
34260
  } else {
34261
- body = extractFunctionBody(pattern);
34261
+ body2 = extractFunctionBody(pattern);
34262
34262
  }
34263
34263
  return this.withNode({
34264
34264
  kind: "sdf:surfaceDisplace",
34265
34265
  child: this._node,
34266
34266
  ...typedPattern ? { pattern: typedPattern } : {},
34267
- patternBody: body,
34267
+ patternBody: body2,
34268
34268
  constants,
34269
34269
  ...options?.uv ? { uvMode: options.uv } : {},
34270
34270
  ...options?.triplanarSharpness !== void 0 ? { triplanarSharpness: options.triplanarSharpness } : {}
@@ -36161,8 +36161,8 @@ function formatShapeBoundsForDiagnostic(shape) {
36161
36161
  try {
36162
36162
  const bounds = shape.boundingBox();
36163
36163
  return `bounds=${formatDiagnosticVec(bounds.min)}..${formatDiagnosticVec(bounds.max)}`;
36164
- } catch (error) {
36165
- const message = error instanceof Error ? error.message : String(error);
36164
+ } catch (error2) {
36165
+ const message = error2 instanceof Error ? error2.message : String(error2);
36166
36166
  return `bounds=unavailable(${message})`;
36167
36167
  }
36168
36168
  }
@@ -36396,10 +36396,10 @@ function parseReferencePath(path2) {
36396
36396
  if (!trimmed) throw new Error("Shape.ref() requires a non-empty path.");
36397
36397
  const atIndex = trimmed.indexOf("@");
36398
36398
  if (atIndex < 0) return { body: trimmed };
36399
- const body = trimmed.slice(0, atIndex).trim();
36399
+ const body2 = trimmed.slice(0, atIndex).trim();
36400
36400
  const selector = trimmed.slice(atIndex + 1).trim();
36401
- if (!body || !selector) throw new Error(`Shape.ref("${path2}") has an incomplete @ selector.`);
36402
- return { body, selector };
36401
+ if (!body2 || !selector) throw new Error(`Shape.ref("${path2}") has an incomplete @ selector.`);
36402
+ return { body: body2, selector };
36403
36403
  }
36404
36404
  function midpoint32(a, b) {
36405
36405
  return [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2, (a[2] + b[2]) / 2];
@@ -36477,21 +36477,21 @@ function explainReferenceMissingFace(shape, path2, name) {
36477
36477
  `Shape.ref("${path2}") could not resolve face "${name}". Available face refs: ${shape.faceNames().join(", ") || "none"}`
36478
36478
  );
36479
36479
  }
36480
- function resolveReferenceBody(shape, originalPath, body) {
36481
- const exactFaces = resolveShapeRefFaces(shape, body);
36480
+ function resolveReferenceBody(shape, originalPath, body2) {
36481
+ const exactFaces = resolveShapeRefFaces(shape, body2);
36482
36482
  if (exactFaces) {
36483
36483
  return {
36484
36484
  baseKind: "face",
36485
- rule: exactFaces.length === 1 ? `face(${body})` : `face-set(${body})`,
36485
+ rule: exactFaces.length === 1 ? `face(${body2})` : `face-set(${body2})`,
36486
36486
  faces: exactFaces,
36487
36487
  edges: [],
36488
36488
  points: []
36489
36489
  };
36490
36490
  }
36491
- const slashIndex = body.indexOf("/");
36492
- if (slashIndex < 0) explainReferenceMissingFace(shape, originalPath, body);
36493
- const leftName = body.slice(0, slashIndex).trim();
36494
- const rightName = body.slice(slashIndex + 1).trim();
36491
+ const slashIndex = body2.indexOf("/");
36492
+ if (slashIndex < 0) explainReferenceMissingFace(shape, originalPath, body2);
36493
+ const leftName = body2.slice(0, slashIndex).trim();
36494
+ const rightName = body2.slice(slashIndex + 1).trim();
36495
36495
  if (!leftName || !rightName) throw new Error(`Shape.ref("${originalPath}") has an incomplete face relationship path.`);
36496
36496
  const leftFaces = resolveShapeRefFaces(shape, leftName) ?? explainReferenceMissingFace(shape, originalPath, leftName);
36497
36497
  const rightFaces = resolveShapeRefFaces(shape, rightName) ?? explainReferenceMissingFace(shape, originalPath, rightName);
@@ -36591,8 +36591,8 @@ var ShapeRef = class _ShapeRef {
36591
36591
  resolve() {
36592
36592
  try {
36593
36593
  return resolveShapeReference(this.shape, this.path);
36594
- } catch (error) {
36595
- if (!this.optional) throw error;
36594
+ } catch (error2) {
36595
+ if (!this.optional) throw error2;
36596
36596
  const sourcePath = resolveShapeRefAliases(this.shape, this.path.trim());
36597
36597
  const parsed = parseReferencePath(sourcePath);
36598
36598
  const missingKind = parsed.selector ? "point-set" : parsed.body.includes("/") ? "edge-set" : "face-set";
@@ -36603,7 +36603,7 @@ var ShapeRef = class _ShapeRef {
36603
36603
  kind: missingKind,
36604
36604
  status: "deleted",
36605
36605
  cardinality: "zero",
36606
- rule: error instanceof Error ? error.message : "optional reference failed to resolve",
36606
+ rule: error2 instanceof Error ? error2.message : "optional reference failed to resolve",
36607
36607
  faces: [],
36608
36608
  edges: [],
36609
36609
  points: []
@@ -39149,11 +39149,11 @@ var constraintDefs = /* @__PURE__ */ new Map([
39149
39149
  ]);
39150
39150
  function buildVariables(bodies) {
39151
39151
  const vars = [];
39152
- for (const [id, body] of bodies) {
39153
- if (body.grounded) continue;
39152
+ for (const [id, body2] of bodies) {
39153
+ if (body2.grounded) continue;
39154
39154
  for (let c = 0; c < 6; c++) {
39155
39155
  const isRotation = c >= 3;
39156
- const arr = isRotation ? body.rotation : body.position;
39156
+ const arr = isRotation ? body2.rotation : body2.position;
39157
39157
  const idx = isRotation ? c - 3 : c;
39158
39158
  vars.push({
39159
39159
  bodyId: id,
@@ -39162,15 +39162,15 @@ function buildVariables(bodies) {
39162
39162
  set: (v) => {
39163
39163
  arr[idx] = v;
39164
39164
  },
39165
- scale: isRotation ? 1 : computePositionScale(body)
39165
+ scale: isRotation ? 1 : computePositionScale(body2)
39166
39166
  });
39167
39167
  }
39168
39168
  }
39169
39169
  return vars;
39170
39170
  }
39171
- function computePositionScale(body) {
39171
+ function computePositionScale(body2) {
39172
39172
  let maxDist = 1;
39173
- for (const face of body.faces.values()) {
39173
+ for (const face of body2.faces.values()) {
39174
39174
  const d = Math.hypot(face.center[0], face.center[1], face.center[2]);
39175
39175
  if (d > maxDist) maxDist = d;
39176
39176
  }
@@ -39180,41 +39180,41 @@ function createContext(bodies) {
39180
39180
  return {
39181
39181
  bodies,
39182
39182
  toWorld(bodyId, point2) {
39183
- const body = bodies.get(bodyId);
39184
- if (!body) throw new Error(`Unknown body: ${bodyId}`);
39185
- return transformPoint2(body.rotation, body.position, point2);
39183
+ const body2 = bodies.get(bodyId);
39184
+ if (!body2) throw new Error(`Unknown body: ${bodyId}`);
39185
+ return transformPoint2(body2.rotation, body2.position, point2);
39186
39186
  },
39187
39187
  toWorldDir(bodyId, dir) {
39188
- const body = bodies.get(bodyId);
39189
- if (!body) throw new Error(`Unknown body: ${bodyId}`);
39190
- return normalize36(transformDir(body.rotation, dir));
39188
+ const body2 = bodies.get(bodyId);
39189
+ if (!body2) throw new Error(`Unknown body: ${bodyId}`);
39190
+ return normalize36(transformDir(body2.rotation, dir));
39191
39191
  },
39192
39192
  worldFace(bodyId, faceName) {
39193
- const body = bodies.get(bodyId);
39194
- if (!body) throw new Error(`Unknown body: ${bodyId}`);
39195
- const face = body.faces.get(faceName);
39193
+ const body2 = bodies.get(bodyId);
39194
+ if (!body2) throw new Error(`Unknown body: ${bodyId}`);
39195
+ const face = body2.faces.get(faceName);
39196
39196
  if (!face) throw new Error(`Unknown face "${faceName}" on body "${bodyId}"`);
39197
39197
  return {
39198
- normal: normalize36(transformDir(body.rotation, face.normal)),
39199
- center: transformPoint2(body.rotation, body.position, face.center)
39198
+ normal: normalize36(transformDir(body2.rotation, face.normal)),
39199
+ center: transformPoint2(body2.rotation, body2.position, face.center)
39200
39200
  };
39201
39201
  },
39202
39202
  worldAxis(bodyId, axisName) {
39203
- const body = bodies.get(bodyId);
39204
- if (!body) throw new Error(`Unknown body: ${bodyId}`);
39205
- const axis = body.axes.get(axisName);
39203
+ const body2 = bodies.get(bodyId);
39204
+ if (!body2) throw new Error(`Unknown body: ${bodyId}`);
39205
+ const axis = body2.axes.get(axisName);
39206
39206
  if (!axis) throw new Error(`Unknown axis "${axisName}" on body "${bodyId}"`);
39207
39207
  return {
39208
- origin: transformPoint2(body.rotation, body.position, axis.origin),
39209
- direction: normalize36(transformDir(body.rotation, axis.direction))
39208
+ origin: transformPoint2(body2.rotation, body2.position, axis.origin),
39209
+ direction: normalize36(transformDir(body2.rotation, axis.direction))
39210
39210
  };
39211
39211
  },
39212
39212
  worldPoint(bodyId, pointName) {
39213
- const body = bodies.get(bodyId);
39214
- if (!body) throw new Error(`Unknown body: ${bodyId}`);
39215
- const pt = body.points.get(pointName);
39213
+ const body2 = bodies.get(bodyId);
39214
+ if (!body2) throw new Error(`Unknown body: ${bodyId}`);
39215
+ const pt = body2.points.get(pointName);
39216
39216
  if (!pt) throw new Error(`Unknown point "${pointName}" on body "${bodyId}"`);
39217
- return transformPoint2(body.rotation, body.position, pt.position);
39217
+ return transformPoint2(body2.rotation, body2.position, pt.position);
39218
39218
  }
39219
39219
  };
39220
39220
  }
@@ -39346,8 +39346,8 @@ function solve3D(bodies, constraints, options = {}) {
39346
39346
  if (def) totalEquations += def.equations;
39347
39347
  }
39348
39348
  let freeBodies = 0;
39349
- for (const body of bodies.values()) {
39350
- if (!body.grounded) freeBodies++;
39349
+ for (const body2 of bodies.values()) {
39350
+ if (!body2.grounded) freeBodies++;
39351
39351
  }
39352
39352
  const totalDof = 6 * freeBodies;
39353
39353
  const netDof = totalDof - totalEquations;
@@ -39365,8 +39365,8 @@ function solve3D(bodies, constraints, options = {}) {
39365
39365
  };
39366
39366
  }
39367
39367
  let refLength = 1;
39368
- for (const body of bodies.values()) {
39369
- for (const face of body.faces.values()) {
39368
+ for (const body2 of bodies.values()) {
39369
+ for (const face of body2.faces.values()) {
39370
39370
  const d = Math.hypot(face.center[0], face.center[1], face.center[2]);
39371
39371
  if (d > refLength) refLength = d;
39372
39372
  }
@@ -39431,10 +39431,10 @@ function classifyStatus(dof, maxError, tolerance) {
39431
39431
  }
39432
39432
  function extractTransforms(bodies) {
39433
39433
  const result = /* @__PURE__ */ new Map();
39434
- for (const [id, body] of bodies) {
39434
+ for (const [id, body2] of bodies) {
39435
39435
  result.set(id, {
39436
- position: [...body.position],
39437
- rotation: [...body.rotation]
39436
+ position: [...body2.position],
39437
+ rotation: [...body2.rotation]
39438
39438
  });
39439
39439
  }
39440
39440
  return result;
@@ -40096,6 +40096,201 @@ function jointsView(options = {}) {
40096
40096
  _collected2 = collectJointsView(options, _collected2);
40097
40097
  }
40098
40098
 
40099
+ // src/forge/sim.ts
40100
+ function requireNonEmptyString(value, label) {
40101
+ if (typeof value !== "string" || value.trim().length === 0) {
40102
+ throw new Error(`${label} must be a non-empty string`);
40103
+ }
40104
+ return value.trim();
40105
+ }
40106
+ function requireFiniteNumber2(value, label) {
40107
+ if (value !== void 0 && !Number.isFinite(value)) {
40108
+ throw new Error(`${label} must be finite`);
40109
+ }
40110
+ }
40111
+ function requirePositiveNumber(value, label) {
40112
+ requireFiniteNumber2(value, label);
40113
+ if (value !== void 0 && value <= 0) {
40114
+ throw new Error(`${label} must be > 0`);
40115
+ }
40116
+ }
40117
+ function requireUnitRange(value, label) {
40118
+ requireFiniteNumber2(value, label);
40119
+ if (value !== void 0 && (value < 0 || value > 1)) {
40120
+ throw new Error(`${label} must be between 0 and 1`);
40121
+ }
40122
+ }
40123
+ function cloneMaterial(material2) {
40124
+ return material2 ? { ...material2 } : void 0;
40125
+ }
40126
+ function cloneSimCollider(collider2) {
40127
+ return collider2 ? { ...collider2 } : void 0;
40128
+ }
40129
+ function cloneSimContact(contact2) {
40130
+ return { ...contact2 };
40131
+ }
40132
+ function cloneSimContacts(contacts) {
40133
+ if (!contacts) return void 0;
40134
+ return Object.fromEntries(Object.entries(contacts).map(([name, contact2]) => [name, cloneSimContact(contact2)]));
40135
+ }
40136
+ function cloneSimBody(body2) {
40137
+ if (!body2) return void 0;
40138
+ return {
40139
+ ...body2,
40140
+ material: cloneMaterial(body2.material),
40141
+ collider: cloneSimCollider(body2.collider),
40142
+ contacts: cloneSimContacts(body2.contacts)
40143
+ };
40144
+ }
40145
+ function cloneSimDrive(drive) {
40146
+ return drive ? { ...drive } : void 0;
40147
+ }
40148
+ function cloneSimController(controller) {
40149
+ return {
40150
+ ...controller,
40151
+ leftJoints: [...controller.leftJoints],
40152
+ rightJoints: [...controller.rightJoints]
40153
+ };
40154
+ }
40155
+ function cloneSimProfile(profile2) {
40156
+ return { ...profile2 };
40157
+ }
40158
+ function cloneSimAssemblySimulation(simulation) {
40159
+ if (!simulation) return void 0;
40160
+ return {
40161
+ ...simulation,
40162
+ profile: cloneSimProfile(simulation.profile),
40163
+ controllers: simulation.controllers?.map((controller) => cloneSimController(controller))
40164
+ };
40165
+ }
40166
+ function simProfileRequiresRootPart(profile2) {
40167
+ return profile2?.name === "Robot-Body-Runnable" || profile2?.name === "Robot-Body-Isaac";
40168
+ }
40169
+ function simProfileAllowsControllers(profile2) {
40170
+ return simProfileRequiresRootPart(profile2);
40171
+ }
40172
+ function material(name, options = {}) {
40173
+ const materialName = requireNonEmptyString(name, "Sim.material(...) name");
40174
+ requirePositiveNumber(options.densityKgM3, `Sim.material("${materialName}") densityKgM3`);
40175
+ requireUnitRange(options.staticFriction, `Sim.material("${materialName}") staticFriction`);
40176
+ requireUnitRange(options.dynamicFriction, `Sim.material("${materialName}") dynamicFriction`);
40177
+ requireUnitRange(options.restitution, `Sim.material("${materialName}") restitution`);
40178
+ return { kind: "material", name: materialName, ...options };
40179
+ }
40180
+ function body(options) {
40181
+ if (!options || typeof options !== "object") {
40182
+ throw new Error("Sim.body(...) expects an options object");
40183
+ }
40184
+ requirePositiveNumber(options.massKg, "Sim.body(...) massKg");
40185
+ requirePositiveNumber(options.densityKgM3, "Sim.body(...) densityKgM3");
40186
+ if (options.massKg === void 0 && options.densityKgM3 === void 0 && options.material?.densityKgM3 === void 0) {
40187
+ throw new Error("Sim.body(...) requires massKg, densityKgM3, or material densityKgM3");
40188
+ }
40189
+ return {
40190
+ kind: "body",
40191
+ massKg: options.massKg,
40192
+ densityKgM3: options.densityKgM3,
40193
+ material: cloneMaterial(options.material),
40194
+ collider: cloneSimCollider(options.collider),
40195
+ contacts: cloneSimContacts(options.contacts)
40196
+ };
40197
+ }
40198
+ function collider(mode, reason) {
40199
+ const trimmedReason = reason?.trim();
40200
+ if (mode === "none" && !trimmedReason) {
40201
+ throw new Error("Sim.collider.none(reason) requires a non-empty reason");
40202
+ }
40203
+ return trimmedReason ? { kind: "collider", mode, reason: trimmedReason } : { kind: "collider", mode };
40204
+ }
40205
+ function contact(kind, connectorName) {
40206
+ return { kind, connectorName: requireNonEmptyString(connectorName, `Sim.contact.${kind}(...) connectorName`) };
40207
+ }
40208
+ function passiveDrive(options = {}) {
40209
+ requireFiniteNumber2(options.damping, "Sim.drive.passive(...) damping");
40210
+ requireFiniteNumber2(options.friction, "Sim.drive.passive(...) friction");
40211
+ return { kind: "passive", ...options };
40212
+ }
40213
+ function velocityDrive(options) {
40214
+ if (!options || typeof options !== "object") {
40215
+ throw new Error("Sim.drive.velocity(...) expects an options object");
40216
+ }
40217
+ requirePositiveNumber(options.maxTorqueNm, "Sim.drive.velocity(...) maxTorqueNm");
40218
+ requirePositiveNumber(options.maxSpeedRpm, "Sim.drive.velocity(...) maxSpeedRpm");
40219
+ requireFiniteNumber2(options.damping, "Sim.drive.velocity(...) damping");
40220
+ requireFiniteNumber2(options.friction, "Sim.drive.velocity(...) friction");
40221
+ return { kind: "velocity", ...options };
40222
+ }
40223
+ function diffDrive(options) {
40224
+ if (!options || typeof options !== "object") {
40225
+ throw new Error("Sim.controller.diffDrive(...) expects an options object");
40226
+ }
40227
+ if (!Array.isArray(options.leftJoints) || options.leftJoints.length === 0) {
40228
+ throw new Error("Sim.controller.diffDrive(...) requires at least one left joint");
40229
+ }
40230
+ if (!Array.isArray(options.rightJoints) || options.rightJoints.length === 0) {
40231
+ throw new Error("Sim.controller.diffDrive(...) requires at least one right joint");
40232
+ }
40233
+ const leftJoints = options.leftJoints.map(
40234
+ (joint2, index) => requireNonEmptyString(joint2, `Sim.controller.diffDrive(...) leftJoints[${index}]`)
40235
+ );
40236
+ const rightJoints = options.rightJoints.map(
40237
+ (joint2, index) => requireNonEmptyString(joint2, `Sim.controller.diffDrive(...) rightJoints[${index}]`)
40238
+ );
40239
+ requirePositiveNumber(options.wheelSeparationMm, "Sim.controller.diffDrive(...) wheelSeparationMm");
40240
+ requirePositiveNumber(options.wheelRadiusMm, "Sim.controller.diffDrive(...) wheelRadiusMm");
40241
+ requireFiniteNumber2(options.maxLinearVelocity, "Sim.controller.diffDrive(...) maxLinearVelocity");
40242
+ requireFiniteNumber2(options.maxAngularVelocity, "Sim.controller.diffDrive(...) maxAngularVelocity");
40243
+ requireFiniteNumber2(options.linearAcceleration, "Sim.controller.diffDrive(...) linearAcceleration");
40244
+ requireFiniteNumber2(options.angularAcceleration, "Sim.controller.diffDrive(...) angularAcceleration");
40245
+ return { ...options, kind: "diffDrive", leftJoints, rightJoints };
40246
+ }
40247
+ var profile = (name) => ({ kind: "profile", name });
40248
+ var Sim = {
40249
+ /** Create a named physical material with density and contact coefficients for simulation export and checks. */
40250
+ material,
40251
+ /** Describe one assembly part as a physical body with mass/density, material, collider intent, and optional contact surfaces. */
40252
+ body,
40253
+ /** Collision-geometry intent constructors for physical parts. */
40254
+ collider: {
40255
+ /** Use a generated collision mesh for the part. This is the default fast rigid-body collider for irregular parts. */
40256
+ convexHull: () => collider("convex"),
40257
+ /** Use the part bounding box as the collision geometry. This is fastest and works well for chassis and simple blocks. */
40258
+ boundingBox: () => collider("box"),
40259
+ /** Use the visual mesh as collision geometry. This is exact but usually slower in physics engines. */
40260
+ visualMesh: () => collider("visual"),
40261
+ /** Disable collision for a part with an explicit reason, such as a sensor-only or decorative object. */
40262
+ none: (reason) => collider("none", reason)
40263
+ },
40264
+ /** Joint-drive intent constructors for passive or powered assembly joints. */
40265
+ drive: {
40266
+ /** Mark a joint as passive while preserving damping and friction metadata for simulation export. */
40267
+ passive: passiveDrive,
40268
+ /** Mark a revolute joint as velocity-driven with torque and speed limits. Speed is authored in rpm and exported as deg/s or rad/s as needed. */
40269
+ velocity: velocityDrive
40270
+ },
40271
+ /** Contact-surface metadata over existing part connectors. */
40272
+ contact: {
40273
+ /** Mark a connector as the wheel tread contact surface for offline checks and downstream simulation metadata. */
40274
+ wheelSurface: (connectorName) => contact("wheelSurface", connectorName),
40275
+ /** Mark a connector as a gripper pad/contact surface for offline checks and downstream grasp-readiness metadata. */
40276
+ gripperSurface: (connectorName) => contact("gripperSurface", connectorName)
40277
+ },
40278
+ /** Named validation/export profile constructors. */
40279
+ profile: {
40280
+ /** SimReady-style profile for a robot body that should be runnable in a physics simulator. */
40281
+ robotBodyRunnable: () => profile("Robot-Body-Runnable"),
40282
+ /** SimReady-style profile for robot bodies targeting Isaac Sim readiness. */
40283
+ robotBodyIsaac: () => profile("Robot-Body-Isaac"),
40284
+ /** SimReady-style profile for robotics assets with PhysX-ready rigid bodies and colliders. */
40285
+ roboticsAssetPhysx: () => profile("Prop-Robotics-Physx")
40286
+ },
40287
+ /** Standard controller metadata constructors for simulator package generation. */
40288
+ controller: {
40289
+ /** Describe a differential-drive controller from left/right wheel joints and wheel dimensions. */
40290
+ diffDrive
40291
+ }
40292
+ };
40293
+
40099
40294
  // src/forge/assembly/frames.ts
40100
40295
  function requireFiniteVec33(input, label) {
40101
40296
  if (!Array.isArray(input) || input.length !== 3) throw new Error(`${label} must be a [x, y, z] tuple`);
@@ -40839,6 +41034,7 @@ var Assembly = class _Assembly {
40839
41034
  viewAnimations = [];
40840
41035
  _defaultViewAnimation;
40841
41036
  _refs = createPlacementReferences();
41037
+ _simulation;
40842
41038
  _portsByPart = /* @__PURE__ */ new Map();
40843
41039
  _usedPortRefs = /* @__PURE__ */ new Set();
40844
41040
  _connectCounter = 0;
@@ -40848,6 +41044,7 @@ var Assembly = class _Assembly {
40848
41044
  _fork() {
40849
41045
  const next = new _Assembly(this.name);
40850
41046
  next._refs = clonePlacementReferences(this._refs);
41047
+ next._simulation = cloneSimAssemblySimulation(this._simulation);
40851
41048
  next._connectCounter = this._connectCounter;
40852
41049
  next._frameEdgeCounter = this._frameEdgeCounter;
40853
41050
  next._linkEdgeCounter = this._linkEdgeCounter;
@@ -40859,6 +41056,7 @@ var Assembly = class _Assembly {
40859
41056
  part: record.part,
40860
41057
  base: record.base,
40861
41058
  metadata: record.metadata ? { ...record.metadata } : void 0,
41059
+ sim: cloneSimBody(record.sim),
40862
41060
  mates: record.mates.map((mate) => ({ ...mate })),
40863
41061
  bindToFrame: record.bindToFrame
40864
41062
  });
@@ -40879,6 +41077,7 @@ var Assembly = class _Assembly {
40879
41077
  velocity: joint2.velocity,
40880
41078
  damping: joint2.damping,
40881
41079
  friction: joint2.friction,
41080
+ sim: joint2.sim ? { drive: cloneSimDrive(joint2.sim.drive) } : void 0,
40882
41081
  connectorRefs: joint2.connectorRefs ? { ...joint2.connectorRefs } : void 0
40883
41082
  });
40884
41083
  }
@@ -40999,6 +41198,42 @@ var Assembly = class _Assembly {
40999
41198
  getReferences() {
41000
41199
  return clonePlacementReferences(this._refs);
41001
41200
  }
41201
+ /**
41202
+ * Attach the root simulation contract for this assembly.
41203
+ *
41204
+ * Use this after adding physical parts and joints. Robot-body profiles require
41205
+ * `rootPart`; asset profiles can describe one-part or multi-part physical assets.
41206
+ * URDF/SDF exporters and `forgecad check simready` read this contract directly,
41207
+ * so model files no longer need a separate `robotExport(...)` side effect.
41208
+ *
41209
+ * @category Assembly
41210
+ */
41211
+ withSimulation(options) {
41212
+ if (!options || typeof options !== "object") {
41213
+ throw new Error("assembly.withSimulation(...) expects an options object");
41214
+ }
41215
+ if (!options.profile || options.profile.kind !== "profile") {
41216
+ throw new Error("assembly.withSimulation(...) requires a Sim.profile.*() value");
41217
+ }
41218
+ const rootPart = typeof options.rootPart === "string" ? options.rootPart.trim() : "";
41219
+ if (simProfileRequiresRootPart(options.profile) && !rootPart) {
41220
+ throw new Error("assembly.withSimulation(...) rootPart is required for robot-body profiles");
41221
+ }
41222
+ if (rootPart && !this.parts.has(rootPart)) {
41223
+ throw new Error(`assembly.withSimulation(...) rootPart "${rootPart}" does not exist`);
41224
+ }
41225
+ if (options.controllers && options.controllers.length > 0 && !simProfileAllowsControllers(options.profile)) {
41226
+ throw new Error("assembly.withSimulation(...) controllers are only supported for robot-body profiles");
41227
+ }
41228
+ const simulation = {
41229
+ kind: "simulation",
41230
+ profile: cloneSimProfile(options.profile),
41231
+ rootPart: rootPart || void 0,
41232
+ controllers: options.controllers?.map((controller) => ({ ...controller }))
41233
+ };
41234
+ this._simulation = cloneSimAssemblySimulation(simulation);
41235
+ return this;
41236
+ }
41002
41237
  withConnectors(partNameOrConnectors, maybeConnectors) {
41003
41238
  if (typeof partNameOrConnectors === "string") {
41004
41239
  const partName = partNameOrConnectors;
@@ -41588,6 +41823,7 @@ var Assembly = class _Assembly {
41588
41823
  part,
41589
41824
  base: options.transform ? Transform.from(options.transform) : Transform.identity(),
41590
41825
  metadata: options.metadata ? { ...options.metadata } : void 0,
41826
+ sim: cloneSimBody(options.sim),
41591
41827
  mates: mates.map((mate, index) => {
41592
41828
  const connector = typeof mate.connector === "string" ? mate.connector.trim() : "";
41593
41829
  const toLink = typeof mate.toLink === "string" ? mate.toLink.trim() : "";
@@ -41676,6 +41912,7 @@ var Assembly = class _Assembly {
41676
41912
  velocity: options.velocity,
41677
41913
  damping: options.damping,
41678
41914
  friction: options.friction,
41915
+ sim: options.drive ? { drive: cloneSimDrive(options.drive) } : void 0,
41679
41916
  connectorRefs: options.connectorRefs ? { ...options.connectorRefs } : void 0
41680
41917
  });
41681
41918
  if (options.follows) {
@@ -41766,8 +42003,15 @@ var Assembly = class _Assembly {
41766
42003
  * mating makes the axes anti-parallel, like a plug meeting a socket (same
41767
42004
  * convention as `matchTo()`).
41768
42005
  *
41769
- * **Mirrored revolute axes:** revolute values follow the right-hand rule, so a
41770
- * mirrored hinge axis (`[1, 0, 0]` vs `[-1, 0, 0]`) rotates oppositely for the
42006
+ * **Revolute sign:** a positive joint value follows the right-hand rule about
42007
+ * the **child** connector's placed axis. Because face-to-face mating makes
42008
+ * the axes anti-parallel, that is the *left*-hand rule about the parent
42009
+ * connector's outward axis — if `+30` swings the opposite way you expected,
42010
+ * you predicted from the parent's axis. `forgecad debug assembly` prints each
42011
+ * joint's resolved world axis.
42012
+ *
42013
+ * **Mirrored revolute axes:** because of the right-hand rule, a mirrored
42014
+ * hinge axis (`[1, 0, 0]` vs `[-1, 0, 0]`) rotates oppositely for the
41771
42015
  * same `+theta`: negate the mirrored side's value and mirror limits as
41772
42016
  * `[min, max] -> [-max, -min]`. Prismatic joints have no handedness flip. Use
41773
42017
  * an explicit per-side sign mapping (or side-neutral link controls) for
@@ -41825,6 +42069,7 @@ var Assembly = class _Assembly {
41825
42069
  velocity: options.velocity,
41826
42070
  damping: options.damping,
41827
42071
  friction: options.friction,
42072
+ drive: options.drive,
41828
42073
  follows: options.follows,
41829
42074
  connectorRefs: {
41830
42075
  parent: parentConnectorRef,
@@ -43291,11 +43536,13 @@ var Assembly = class _Assembly {
43291
43536
  describe() {
43292
43537
  return {
43293
43538
  name: this.name,
43539
+ sim: cloneSimAssemblySimulation(this._simulation),
43294
43540
  parts: [...this.parts.values()].map((part) => ({
43295
43541
  name: part.name,
43296
43542
  part: part.part,
43297
43543
  base: part.base,
43298
43544
  metadata: part.metadata ? { ...part.metadata } : void 0,
43545
+ sim: cloneSimBody(part.sim),
43299
43546
  mates: part.mates.map((mate) => ({ ...mate })),
43300
43547
  bindToFrame: part.bindToFrame
43301
43548
  })),
@@ -43314,6 +43561,7 @@ var Assembly = class _Assembly {
43314
43561
  velocity: joint2.velocity,
43315
43562
  damping: joint2.damping,
43316
43563
  friction: joint2.friction,
43564
+ sim: joint2.sim ? { drive: cloneSimDrive(joint2.sim.drive) } : void 0,
43317
43565
  connectorRefs: joint2.connectorRefs ? { ...joint2.connectorRefs } : void 0
43318
43566
  })),
43319
43567
  jointCouplings: [...this.jointCouplings.values()].map((coupling) => ({
@@ -43644,6 +43892,7 @@ var ImportedAssembly = class _ImportedAssembly {
43644
43892
  parent.addPart(`${pfx}${p2.name}`, p2.part, {
43645
43893
  transform: p2.base,
43646
43894
  metadata: p2.metadata,
43895
+ sim: p2.sim,
43647
43896
  mate: p2.mates.map((mate) => ({ connector: mate.connector, toLink: `${pfx}${mate.toLink}` }))
43648
43897
  });
43649
43898
  }
@@ -43658,7 +43907,8 @@ var ImportedAssembly = class _ImportedAssembly {
43658
43907
  effort: j.effort,
43659
43908
  velocity: j.velocity,
43660
43909
  damping: j.damping,
43661
- friction: j.friction
43910
+ friction: j.friction,
43911
+ drive: j.sim?.drive
43662
43912
  });
43663
43913
  }
43664
43914
  const allPorts = this._assembly.getAllPorts();
@@ -44173,9 +44423,9 @@ var GCodeBuilder = class {
44173
44423
  _maxZ = -Infinity;
44174
44424
  // Filament cross-section area (precomputed)
44175
44425
  filamentArea;
44176
- constructor(profile) {
44177
- const preset = profile?.printer ?? DEFAULT_PROFILE.printer;
44178
- this.profile = { ...DEFAULT_PROFILE, ...bambuOverridesFor(preset), ...profile };
44426
+ constructor(profile2) {
44427
+ const preset = profile2?.printer ?? DEFAULT_PROFILE.printer;
44428
+ this.profile = { ...DEFAULT_PROFILE, ...bambuOverridesFor(preset), ...profile2 };
44179
44429
  this.currentSpeed = this.profile.printSpeed;
44180
44430
  this.filamentArea = Math.PI * (this.profile.filament / 2) ** 2;
44181
44431
  }
@@ -44836,8 +45086,8 @@ function bambuConfigBlock(p2, hotend, bed) {
44836
45086
  lines.push("; CONFIG_BLOCK_END");
44837
45087
  return lines;
44838
45088
  }
44839
- function gcode(profile) {
44840
- return new GCodeBuilder(profile);
45089
+ function gcode(profile2) {
45090
+ return new GCodeBuilder(profile2);
44841
45091
  }
44842
45092
 
44843
45093
  // src/forge/sketch/workplane.ts
@@ -44926,8 +45176,8 @@ function resolvePlanarFaceWorkplane(face) {
44926
45176
  source
44927
45177
  };
44928
45178
  }
44929
- function isGenericMissingFaceError(error, face) {
44930
- const message = error instanceof Error ? error.message : String(error);
45179
+ function isGenericMissingFaceError(error2, face) {
45180
+ const message = error2 instanceof Error ? error2.message : String(error2);
44931
45181
  return message === `Face "${face}" is not available. Supported faces: none` || message.startsWith(`Face "${face}" is not available. Supported faces: `);
44932
45182
  }
44933
45183
  function resolveSketchWorkplane(parentOrFace, face) {
@@ -44951,9 +45201,9 @@ function resolveSketchWorkplane(parentOrFace, face) {
44951
45201
  workplane.source = { kind: "canonical-face", face, owner: resolveTargetQueryOwner(parentOrFace) };
44952
45202
  }
44953
45203
  return workplane;
44954
- } catch (error) {
44955
- if (!isCanonicalFace(face) || !isGenericMissingFaceError(error, face)) {
44956
- throw error;
45204
+ } catch (error2) {
45205
+ if (!isCanonicalFace(face) || !isGenericMissingFaceError(error2, face)) {
45206
+ throw error2;
44957
45207
  }
44958
45208
  }
44959
45209
  }
@@ -45308,22 +45558,22 @@ function validateHoleExtentCompatibility(hole2, extent) {
45308
45558
  throw new Error("Shape.hole() thread.depth cannot exceed the primary forward hole depth.");
45309
45559
  }
45310
45560
  }
45311
- function resolveCutTaperCompilePlan(profile, opts, extent) {
45561
+ function resolveCutTaperCompilePlan(profile2, opts, extent) {
45312
45562
  if (opts.taperScale == null) return void 0;
45313
45563
  if (extent.kind === "two-sided") {
45314
45564
  throw new Error("Shape.cutout() does not yet combine taperScale with reverse two-sided extents.");
45315
45565
  }
45316
- if (!profile) {
45566
+ if (!profile2) {
45317
45567
  throw new Error("Shape.cutout() requires a compile-covered sketch profile before taperScale can be validated.");
45318
45568
  }
45319
- if (profile.kind !== "circle" && profile.kind !== "rect" && profile.kind !== "roundedRect") {
45569
+ if (profile2.kind !== "circle" && profile2.kind !== "rect" && profile2.kind !== "roundedRect") {
45320
45570
  throw new Error("Shape.cutout() taperScale currently supports circle, rect, and roundedRect sketch profiles only.");
45321
45571
  }
45322
45572
  const scale11 = Array.isArray(opts.taperScale) ? opts.taperScale : [opts.taperScale, opts.taperScale];
45323
45573
  if (scale11.length !== 2 || !isFinitePositive3(scale11[0]) || !isFinitePositive3(scale11[1])) {
45324
45574
  throw new Error("Shape.cutout() taperScale must be a positive finite number or [x, y] pair.");
45325
45575
  }
45326
- if (profile.kind === "circle" && Math.abs(scale11[0] - scale11[1]) > 1e-6) {
45576
+ if (profile2.kind === "circle" && Math.abs(scale11[0] - scale11[1]) > 1e-6) {
45327
45577
  throw new Error("Shape.cutout() circular tapered cuts currently require a uniform taperScale.");
45328
45578
  }
45329
45579
  return {
@@ -45418,8 +45668,8 @@ function shapeCutout(target, sketch, opts = {}) {
45418
45668
  if (!basePlan) {
45419
45669
  throw new Error("Shape.cutout() currently requires a compile-covered target shape.");
45420
45670
  }
45421
- const profile = getSketchCompileProfilePlan(sketch);
45422
- if (!profile) {
45671
+ const profile2 = getSketchCompileProfilePlan(sketch);
45672
+ if (!profile2) {
45423
45673
  throw new Error("Shape.cutout() requires a compile-covered sketch profile.");
45424
45674
  }
45425
45675
  const placementModel = getSketchPlacementModel(sketch);
@@ -45441,7 +45691,7 @@ function shapeCutout(target, sketch, opts = {}) {
45441
45691
  },
45442
45692
  "Shape.cutout()"
45443
45693
  );
45444
- const taper = resolveCutTaperCompilePlan(profile, opts, extent);
45694
+ const taper = resolveCutTaperCompilePlan(profile2, opts, extent);
45445
45695
  const plan = createOwnedTopologyRewritePlan2(
45446
45696
  buildCutShapeCompilePlan(
45447
45697
  basePlan,
@@ -45449,12 +45699,12 @@ function shapeCutout(target, sketch, opts = {}) {
45449
45699
  matrix: placementMatrix2,
45450
45700
  placement: cloneSketchPlacementModel(placementModel)
45451
45701
  },
45452
- profile,
45702
+ profile2,
45453
45703
  extent,
45454
45704
  taper
45455
45705
  ),
45456
45706
  "cut",
45457
- (owner) => buildCutTopologyRewritePropagation(owner, basePlan, placementModel, profile, extent, taper)
45707
+ (owner) => buildCutTopologyRewritePropagation(owner, basePlan, placementModel, profile2, extent, taper)
45458
45708
  );
45459
45709
  return buildFeatureResult(target, plan);
45460
45710
  }
@@ -47570,20 +47820,20 @@ function tSlotProfile(options = {}) {
47570
47820
  sketchRotateAround(sideSlot, 180, [0, 0]),
47571
47821
  sketchRotateAround(sideSlot, 270, [0, 0])
47572
47822
  );
47573
- let profile = union2d(shell, webX, webY);
47574
- if (boss2) profile = union2d(profile, boss2);
47575
- profile = difference2d(profile, slots);
47823
+ let profile2 = union2d(shell, webX, webY);
47824
+ if (boss2) profile2 = union2d(profile2, boss2);
47825
+ profile2 = difference2d(profile2, slots);
47576
47826
  if (centerBoreDia > 0) {
47577
- profile = difference2d(profile, circle2d(centerBoreDia / 2, segments));
47827
+ profile2 = difference2d(profile2, circle2d(centerBoreDia / 2, segments));
47578
47828
  }
47579
- return profile;
47829
+ return profile2;
47580
47830
  }
47581
47831
  function tSlotExtrusion(length7, options = {}) {
47582
47832
  if (!Number.isFinite(length7) || length7 <= 0) {
47583
47833
  throw new Error('tSlotExtrusion: "length" must be > 0');
47584
47834
  }
47585
- const profile = tSlotProfile(options);
47586
- return sketchExtrude(profile, length7);
47835
+ const profile2 = tSlotProfile(options);
47836
+ return sketchExtrude(profile2, length7);
47587
47837
  }
47588
47838
 
47589
47839
  // src/forge/lib/profiles-2020.ts
@@ -47648,26 +47898,26 @@ function profile2020BSlot6Profile(options = {}) {
47648
47898
  sketchRotateAround(sideSlot, 180, [0, 0]),
47649
47899
  sketchRotateAround(sideSlot, 270, [0, 0])
47650
47900
  );
47651
- let profile = difference2d(outer, slots);
47901
+ let profile2 = difference2d(outer, slots);
47652
47902
  const centerBoss = opts.centerBossDia > 0 ? circle2d(opts.centerBossDia / 2, opts.segments) : null;
47653
47903
  const webLen = size * 1.15;
47654
47904
  const xWeb = rect(webLen, opts.diagonalWebWidth);
47655
47905
  const webs = union2d(sketchRotateAround(xWeb, 45, [0, 0]), sketchRotateAround(xWeb, -45, [0, 0]));
47656
- profile = union2d(profile, webs);
47906
+ profile2 = union2d(profile2, webs);
47657
47907
  if (centerBoss) {
47658
- profile = union2d(profile, centerBoss);
47908
+ profile2 = union2d(profile2, centerBoss);
47659
47909
  }
47660
47910
  if (opts.centerBoreDia > 0) {
47661
- profile = difference2d(profile, circle2d(opts.centerBoreDia / 2, opts.segments));
47911
+ profile2 = difference2d(profile2, circle2d(opts.centerBoreDia / 2, opts.segments));
47662
47912
  }
47663
- return profile;
47913
+ return profile2;
47664
47914
  }
47665
47915
  function profile2020BSlot6(length7, options = {}) {
47666
47916
  if (!Number.isFinite(length7) || length7 <= 0) {
47667
47917
  throw new Error('profile2020BSlot6: "length" must be > 0');
47668
47918
  }
47669
- const profile = profile2020BSlot6Profile(options);
47670
- return sketchExtrude(profile, length7);
47919
+ const profile2 = profile2020BSlot6Profile(options);
47920
+ return sketchExtrude(profile2, length7);
47671
47921
  }
47672
47922
 
47673
47923
  // src/forge/assembly/explodeCore.ts
@@ -48930,7 +49180,7 @@ function loftInternal(profiles2, heights, options = {}) {
48930
49180
  if (profiles2.length !== heights.length) {
48931
49181
  throw new Error("loft requires heights.length === profiles.length");
48932
49182
  }
48933
- const pairs = profiles2.map((profile, i) => ({ profile, z: heights[i] })).sort((a, b) => a.z - b.z);
49183
+ const pairs = profiles2.map((profile2, i) => ({ profile: profile2, z: heights[i] })).sort((a, b) => a.z - b.z);
48934
49184
  for (let i = 1; i < pairs.length; i++) {
48935
49185
  if (Math.abs(pairs[i].z - pairs[i - 1].z) < 1e-8) {
48936
49186
  throw new Error("loft requires strictly increasing, unique heights");
@@ -48981,8 +49231,8 @@ function loftInternal(profiles2, heights, options = {}) {
48981
49231
  sources: isKernelLoft ? ["loft"] : ["loft", "level-set"]
48982
49232
  });
48983
49233
  }
48984
- function extractSweepEdgeLabels(profile) {
48985
- const labels = getSketchEdgeLabels(profile);
49234
+ function extractSweepEdgeLabels(profile2) {
49235
+ const labels = getSketchEdgeLabels(profile2);
48986
49236
  if (labels.size === 0) return void 0;
48987
49237
  const result = {};
48988
49238
  for (const [name, label] of labels) {
@@ -49088,14 +49338,14 @@ function buildPathPlan(path2) {
49088
49338
  }
49089
49339
  throw new Error("sweep: unsupported path type");
49090
49340
  }
49091
- function sweep(profile, path2, options = {}) {
49341
+ function sweep(profile2, path2, options = {}) {
49092
49342
  const requestedPathSamples = Math.max(4, options.samples ?? 48);
49093
49343
  const effectivePathSamples = scaleSweepPathSamples(requestedPathSamples);
49094
49344
  const { plan: pathPlan, sampleForBounds } = buildPathPlan(path2);
49095
49345
  const pathPts = sampleForBounds(effectivePathSamples);
49096
49346
  if (pathPts.length < 2) throw new Error("sweep requires a path with at least two points");
49097
49347
  const up = options.up ?? [0, 0, 1];
49098
- const pb = profile.bounds();
49348
+ const pb = profile2.bounds();
49099
49349
  const pr = Math.max(Math.abs(pb.min[0]), Math.abs(pb.max[0]), Math.abs(pb.min[1]), Math.abs(pb.max[1]));
49100
49350
  const roughStats = measurePathPoints(pathPts);
49101
49351
  const span = Math.max(
@@ -49109,8 +49359,8 @@ function sweep(profile, path2, options = {}) {
49109
49359
  const edgeLength2 = scaleLevelSetEdgeLength(requestedEdgeLength);
49110
49360
  const requestedPad = options.boundsPadding ?? Math.max(pr + edgeLength2 * 2, span * 0.04, 2);
49111
49361
  const pad = scaleLevelSetBoundsPadding(requestedPad);
49112
- const edgeLabels = extractSweepEdgeLabels(profile);
49113
- const plan = buildSweepShapeCompilePlan(getSketchCompileProfilePlan(profile), pathPlan, {
49362
+ const edgeLabels = extractSweepEdgeLabels(profile2);
49363
+ const plan = buildSweepShapeCompilePlan(getSketchCompileProfilePlan(profile2), pathPlan, {
49114
49364
  edgeLength: edgeLength2,
49115
49365
  boundsPadding: pad,
49116
49366
  up,
@@ -49123,7 +49373,7 @@ function sweep(profile, path2, options = {}) {
49123
49373
  const ownedPlan = createOwnedShapeCompilePlan(plan, "sweep");
49124
49374
  const activeBackend = getActiveBackend();
49125
49375
  const isKernelSweep = activeBackend === "occt" || activeBackend === "truck";
49126
- return buildShapeFromCompilePlan(ownedPlan, profile.colorHex, {
49376
+ return buildShapeFromCompilePlan(ownedPlan, profile2.colorHex, {
49127
49377
  fidelity: isKernelSweep ? "kernel-native" : "sampled",
49128
49378
  sources: isKernelSweep ? ["sweep"] : ["sweep", "level-set"]
49129
49379
  });
@@ -49277,8 +49527,8 @@ var HelixCurve = class extends Curve3D {
49277
49527
  return Math.hypot(this.radius * Math.PI * 2 * this.turns, this.height);
49278
49528
  }
49279
49529
  };
49280
- function profileHalfExtent(profile) {
49281
- const bounds = profile.bounds();
49530
+ function profileHalfExtent(profile2) {
49531
+ const bounds = profile2.bounds();
49282
49532
  return Math.max(Math.abs(bounds.min[0]), Math.abs(bounds.max[0]), Math.abs(bounds.min[1]), Math.abs(bounds.max[1]));
49283
49533
  }
49284
49534
  function buildHelixCoil(profileOrOptions, maybeOptions) {
@@ -49286,12 +49536,12 @@ function buildHelixCoil(profileOrOptions, maybeOptions) {
49286
49536
  const options = hasCustomProfile ? maybeOptions : profileOrOptions;
49287
49537
  if (!options) throw new Error("Helix.coil: options are required.");
49288
49538
  const spec2 = normalizeHelixOptions(options, "Helix.coil");
49289
- const profile = hasCustomProfile ? profileOrOptions : circle2d(
49539
+ const profile2 = hasCustomProfile ? profileOrOptions : circle2d(
49290
49540
  requirePositive(options.wireRadius, "Helix.coil.wireRadius"),
49291
49541
  requireIntegerAtLeast2(options.profileSegments ?? 24, "Helix.coil.profileSegments", 8)
49292
49542
  );
49293
- if (profile.isEmpty()) throw new Error("Helix.coil: profile must not be empty.");
49294
- const halfExtent = profileHalfExtent(profile);
49543
+ if (profile2.isEmpty()) throw new Error("Helix.coil: profile must not be empty.");
49544
+ const halfExtent = profileHalfExtent(profile2);
49295
49545
  if (spec2.radius <= halfExtent) {
49296
49546
  throw new Error(`Helix.coil: radius must be greater than the profile half-extent (${halfExtent}).`);
49297
49547
  }
@@ -49305,7 +49555,7 @@ function buildHelixCoil(profileOrOptions, maybeOptions) {
49305
49555
  ...options,
49306
49556
  samplesPerTurn: divisionsPerTurn
49307
49557
  });
49308
- return sweep(profile, guide, { samples, up: [0, 0, -spec2.direction] });
49558
+ return sweep(profile2, guide, { samples, up: [0, 0, -spec2.direction] });
49309
49559
  }
49310
49560
  var Helix = {
49311
49561
  path(options) {
@@ -50027,8 +50277,8 @@ function pipeRoute(points, radius, options) {
50027
50277
  }
50028
50278
  const route = Curve.Route.fromPolyline(simplifyStraightPipeRoutePoints(points), { cornerRadius: bendR });
50029
50279
  const outer = circle2d(radius, segs);
50030
- const profile = wall != null && wall > 0 ? difference2d(outer, circle2d(radius - wall, segs)) : outer;
50031
- return sweep(profile, route, { samples: segs });
50280
+ const profile2 = wall != null && wall > 0 ? difference2d(outer, circle2d(radius - wall, segs)) : outer;
50281
+ return sweep(profile2, route, { samples: segs });
50032
50282
  }
50033
50283
  function simplifyStraightPipeRoutePoints(points) {
50034
50284
  const simplified = [points[0]];
@@ -50076,9 +50326,9 @@ function elbow(pipeRadius, bendRadius, angle, options) {
50076
50326
  const center = scale7(inward, bendRadius);
50077
50327
  const endRadial = rotateDirection(scale7(inward, -bendRadius), turnAxis, angleRad);
50078
50328
  const end = addVec(center, endRadial);
50079
- const profile = profileForPipeRadius(pipeRadius, wall, Math.floor(segments));
50329
+ const profile2 = profileForPipeRadius(pipeRadius, wall, Math.floor(segments));
50080
50330
  const pathSamples = Math.max(6, Math.ceil(segments * (angleRad * 180 / Math.PI) / 360) + 1);
50081
- return sweep(profile, Curve.Arc({ start: [0, 0, 0], end, tangent: from }), { samples: pathSamples, up: turnAxis });
50331
+ return sweep(profile2, Curve.Arc({ start: [0, 0, 0], end, tangent: from }), { samples: pathSamples, up: turnAxis });
50082
50332
  }
50083
50333
 
50084
50334
  // src/forge/lib/belts.ts
@@ -50427,14 +50677,14 @@ function readGearMeta(shape) {
50427
50677
  const meta = shape[GEAR_META_KEY];
50428
50678
  return meta ?? null;
50429
50679
  }
50430
- function remapErrorPrefix(error, sourcePrefix, targetPrefix) {
50431
- if (error instanceof Error) {
50432
- if (error.message.startsWith(`${sourcePrefix}:`)) {
50433
- throw new Error(`${targetPrefix}:${error.message.slice(sourcePrefix.length + 1)}`);
50680
+ function remapErrorPrefix(error2, sourcePrefix, targetPrefix) {
50681
+ if (error2 instanceof Error) {
50682
+ if (error2.message.startsWith(`${sourcePrefix}:`)) {
50683
+ throw new Error(`${targetPrefix}:${error2.message.slice(sourcePrefix.length + 1)}`);
50434
50684
  }
50435
- throw error;
50685
+ throw error2;
50436
50686
  }
50437
- throw error;
50687
+ throw error2;
50438
50688
  }
50439
50689
 
50440
50690
  // src/forge/lib/gears/spur.ts
@@ -50561,11 +50811,11 @@ function buildSpurGearProfile(meta, segmentsPerTooth) {
50561
50811
  function spurGear(options) {
50562
50812
  const normalized = normalizeSpurGearOptions(options);
50563
50813
  const meta = buildSpurGearMeta(normalized);
50564
- let profile = buildSpurGearProfile(meta, normalized.segmentsPerTooth);
50814
+ let profile2 = buildSpurGearProfile(meta, normalized.segmentsPerTooth);
50565
50815
  if (normalized.boreDiameter > 0) {
50566
- profile = difference2d(profile, circle2d(normalized.boreDiameter * 0.5, Math.max(48, normalized.teeth * 2)));
50816
+ profile2 = difference2d(profile2, circle2d(normalized.boreDiameter * 0.5, Math.max(48, normalized.teeth * 2)));
50567
50817
  }
50568
- const shape = sketchExtrude(profile, normalized.faceWidth);
50818
+ const shape = sketchExtrude(profile2, normalized.faceWidth);
50569
50819
  const shapeWithConnectors = shape.withConnectors({
50570
50820
  bore: connectorFactory(
50571
50821
  "gear-bore",
@@ -50614,8 +50864,8 @@ function gearBodyDisk(options) {
50614
50864
  const bore = requireOptionalBore("gearBodyDisk", options.boreDiameter, options.outerRadius * 2);
50615
50865
  const segments = resolveSegments(options.segments);
50616
50866
  const outer = circle2d(options.outerRadius, segments);
50617
- const profile = bore > 0 ? difference2d(outer, circle2d(bore * 0.5, segments)) : outer;
50618
- return sketchExtrude(profile, options.faceWidth);
50867
+ const profile2 = bore > 0 ? difference2d(outer, circle2d(bore * 0.5, segments)) : outer;
50868
+ return sketchExtrude(profile2, options.faceWidth);
50619
50869
  }
50620
50870
  function gearBodyDiskWithHub(options) {
50621
50871
  requirePositive2("gearBodyDiskWithHub", "hubDiameter", options.hubDiameter);
@@ -50656,15 +50906,15 @@ function gearBodySpoked(options) {
50656
50906
  for (let i = 0; i < options.spokeCount; i++) {
50657
50907
  spokes.push(sketchRotateAround(spoke, 360 / options.spokeCount * i, [0, 0]));
50658
50908
  }
50659
- const profile = bore > 0 ? difference2d(union2d(rim, hub, ...spokes), circle2d(bore * 0.5, segments)) : union2d(rim, hub, ...spokes);
50660
- return sketchExtrude(profile, options.faceWidth);
50909
+ const profile2 = bore > 0 ? difference2d(union2d(rim, hub, ...spokes), circle2d(bore * 0.5, segments)) : union2d(rim, hub, ...spokes);
50910
+ return sketchExtrude(profile2, options.faceWidth);
50661
50911
  }
50662
- function gearBodyFromProfile(profile, options) {
50663
- if (!(profile instanceof Sketch)) throw new Error('gearBodyFromProfile: "profile" must be a Sketch');
50912
+ function gearBodyFromProfile(profile2, options) {
50913
+ if (!(profile2 instanceof Sketch)) throw new Error('gearBodyFromProfile: "profile" must be a Sketch');
50664
50914
  requirePositive2("gearBodyFromProfile", "faceWidth", options.faceWidth);
50665
50915
  const bore = options.boreDiameter ?? 0;
50666
50916
  if (!Number.isFinite(bore) || bore < 0) throw new Error('gearBodyFromProfile: "boreDiameter" must be >= 0');
50667
- return cutBore(sketchExtrude(profile, options.faceWidth), bore);
50917
+ return cutBore(sketchExtrude(profile2, options.faceWidth), bore);
50668
50918
  }
50669
50919
 
50670
50920
  // src/forge/lib/gears/drive-wheel-geometry.ts
@@ -50702,16 +50952,16 @@ function buildSpurTeethRegion(options, name, faceWidth) {
50702
50952
  let normalized;
50703
50953
  try {
50704
50954
  normalized = normalizeSpurGearOptions({ ...options, teeth: teethOnFullCircle, faceWidth, boreDiameter: 0 });
50705
- } catch (error) {
50706
- remapErrorPrefix(error, "spurGear", scope);
50955
+ } catch (error2) {
50956
+ remapErrorPrefix(error2, "spurGear", scope);
50707
50957
  }
50708
50958
  const gearMeta = buildSpurGearMeta(normalized);
50709
50959
  const pitchStepDeg = 360 / teethOnFullCircle;
50710
50960
  const fromAngleDeg = firstTooth * pitchStepDeg - pitchStepDeg * 0.5;
50711
50961
  const toAngleDeg = (firstTooth + toothCount - 1) * pitchStepDeg + pitchStepDeg * 0.5;
50712
- const profile = buildSpurToothRegionProfile(gearMeta, firstTooth, toothCount, normalized.segmentsPerTooth);
50962
+ const profile2 = buildSpurToothRegionProfile(gearMeta, firstTooth, toothCount, normalized.segmentsPerTooth);
50713
50963
  return {
50714
- shape: sketchExtrude(profile, faceWidth),
50964
+ shape: sketchExtrude(profile2, faceWidth),
50715
50965
  gearMeta,
50716
50966
  meta: {
50717
50967
  name,
@@ -50858,8 +51108,8 @@ var DriveWheelBuilder = class {
50858
51108
  if (firstGearRegion && this.boreDiameter * 0.5 >= firstGearRegion.rootRadius - EPSILON4) {
50859
51109
  throw new Error("driveWheel: bore is too large for the first spur-tooth region");
50860
51110
  }
50861
- const body = this.body?.clone() ?? gearBodyDisk({ outerRadius: firstGearRegion?.rootRadius ?? this.defaultBodyRadius(), faceWidth });
50862
- let combined = body;
51111
+ const body2 = this.body?.clone() ?? gearBodyDisk({ outerRadius: firstGearRegion?.rootRadius ?? this.defaultBodyRadius(), faceWidth });
51112
+ let combined = body2;
50863
51113
  for (const region of this.regions) combined = combined.add(region.shape);
50864
51114
  combined = cutBore2(combined, this.boreDiameter);
50865
51115
  const withConnectors = combined.withConnectors({
@@ -50873,7 +51123,7 @@ var DriveWheelBuilder = class {
50873
51123
  kind: "driveWheel",
50874
51124
  faceWidth,
50875
51125
  boreDiameter: this.boreDiameter,
50876
- regions: this.regionMetadata(body, faceWidth)
51126
+ regions: this.regionMetadata(body2, faceWidth)
50877
51127
  });
50878
51128
  }
50879
51129
  measurements(faceWidth) {
@@ -50890,9 +51140,9 @@ var DriveWheelBuilder = class {
50890
51140
  } : {}
50891
51141
  };
50892
51142
  }
50893
- regionMetadata(body, faceWidth) {
51143
+ regionMetadata(body2, faceWidth) {
50894
51144
  return [
50895
- { name: "body", kind: "body", outerRadius: bodyOuterRadius(body), faceWidth },
51145
+ { name: "body", kind: "body", outerRadius: bodyOuterRadius(body2), faceWidth },
50896
51146
  ...this.regions.map((region) => ({ ...region.meta }))
50897
51147
  ];
50898
51148
  }
@@ -50990,8 +51240,8 @@ function normalizeFaceGearOptions(options) {
50990
51240
  let normalizedSpur;
50991
51241
  try {
50992
51242
  normalizedSpur = normalizeSpurGearOptions(options);
50993
- } catch (error) {
50994
- remapErrorPrefix(error, "spurGear", "faceGear");
51243
+ } catch (error2) {
51244
+ remapErrorPrefix(error2, "spurGear", "faceGear");
50995
51245
  }
50996
51246
  const side = options.side ?? "top";
50997
51247
  if (side !== "top" && side !== "bottom") {
@@ -51036,8 +51286,8 @@ function faceGear(options) {
51036
51286
  let spurMeta;
51037
51287
  try {
51038
51288
  spurMeta = buildSpurGearMeta(normalized);
51039
- } catch (error) {
51040
- remapErrorPrefix(error, "spurGear", "faceGear");
51289
+ } catch (error2) {
51290
+ remapErrorPrefix(error2, "spurGear", "faceGear");
51041
51291
  }
51042
51292
  const zBands = resolveFaceGearZBands(normalized);
51043
51293
  const meta = {
@@ -51051,8 +51301,8 @@ function faceGear(options) {
51051
51301
  };
51052
51302
  const segments = Math.max(48, normalized.teeth * normalized.segmentsPerTooth);
51053
51303
  const rootDisk = cylinder2(normalized.faceWidth, meta.rootRadius, void 0, segments);
51054
- const profile = buildSpurGearProfile(spurMeta, normalized.segmentsPerTooth);
51055
- const toothBandRaw = difference2d(profile, circle2d(meta.rootRadius, Math.max(48, normalized.teeth * 2)));
51304
+ const profile2 = buildSpurGearProfile(spurMeta, normalized.segmentsPerTooth);
51305
+ const toothBandRaw = difference2d(profile2, circle2d(meta.rootRadius, Math.max(48, normalized.teeth * 2)));
51056
51306
  const simplifiedCross = toothBandRaw.cross.simplify(1e-6);
51057
51307
  const toothBandProfile = setSketchCompileProfilePlan(
51058
51308
  new Sketch(simplifiedCross, toothBandRaw.colorHex),
@@ -51095,8 +51345,8 @@ function faceGear(options) {
51095
51345
  function sideGear(options) {
51096
51346
  try {
51097
51347
  return faceGear(options);
51098
- } catch (error) {
51099
- remapErrorPrefix(error, "faceGear", "sideGear");
51348
+ } catch (error2) {
51349
+ remapErrorPrefix(error2, "faceGear", "sideGear");
51100
51350
  }
51101
51351
  }
51102
51352
 
@@ -51207,8 +51457,8 @@ function ringGear(options) {
51207
51457
  for (let i = 0; i < normalized.teeth; i++) {
51208
51458
  spaces.push(sketchRotateAround(toothSpace, 360 / normalized.teeth * i, [0, 0]));
51209
51459
  }
51210
- const profile = difference2d(ringBlank, union2d(...spaces));
51211
- const shape = sketchExtrude(profile, normalized.faceWidth);
51460
+ const profile2 = difference2d(ringBlank, union2d(...spaces));
51461
+ const shape = sketchExtrude(profile2, normalized.faceWidth);
51212
51462
  const shapeWithConnectors = shape.withConnectors({
51213
51463
  bore: connectorFactory(
51214
51464
  "ring-bore",
@@ -51281,8 +51531,8 @@ function rackGear(options) {
51281
51531
  const span = (options.teeth - 1) * pitch + halfRoot * 2;
51282
51532
  const length7 = span + module * 2;
51283
51533
  const base = sketchTranslate(rect(length7, baseHeight), 0, -dedendum - baseHeight * 0.5);
51284
- const profile = union2d(base, ...teethSketches);
51285
- const shape = sketchExtrude(profile, options.faceWidth);
51534
+ const profile2 = union2d(base, ...teethSketches);
51535
+ const shape = sketchExtrude(profile2, options.faceWidth);
51286
51536
  const meta = {
51287
51537
  kind: "rack",
51288
51538
  module,
@@ -51390,11 +51640,11 @@ function normalizeBevelGearOptions(options) {
51390
51640
  function bevelGear(options) {
51391
51641
  const normalized = normalizeBevelGearOptions(options);
51392
51642
  const meta = buildSpurGearMeta(normalized);
51393
- let profile = buildSpurGearProfile(meta, normalized.segmentsPerTooth);
51643
+ let profile2 = buildSpurGearProfile(meta, normalized.segmentsPerTooth);
51394
51644
  if (normalized.boreDiameter > 0) {
51395
- profile = difference2d(profile, circle2d(normalized.boreDiameter * 0.5, Math.max(48, normalized.teeth * 2)));
51645
+ profile2 = difference2d(profile2, circle2d(normalized.boreDiameter * 0.5, Math.max(48, normalized.teeth * 2)));
51396
51646
  }
51397
- const shape = sketchExtrude(profile, normalized.faceWidth, {
51647
+ const shape = sketchExtrude(profile2, normalized.faceWidth, {
51398
51648
  scaleTop: normalized.topScale
51399
51649
  });
51400
51650
  const apexZ = normalized.module * normalized.teeth * 0.5 / Math.tan(normalized.pitchAngleRad);
@@ -51886,8 +52136,8 @@ function sideGearPair(options) {
51886
52136
  place: options.place,
51887
52137
  phaseDeg: options.phaseDeg
51888
52138
  });
51889
- } catch (error) {
51890
- remapErrorPrefix(error, "faceGearPair", "sideGearPair");
52139
+ } catch (error2) {
52140
+ remapErrorPrefix(error2, "faceGearPair", "sideGearPair");
51891
52141
  }
51892
52142
  const diagnostics = pair.diagnostics.map((d) => ({
51893
52143
  ...d,
@@ -52235,7 +52485,7 @@ var partLibrary = {
52235
52485
  gearBodyFromProfile: softDeprecated(
52236
52486
  "lib.gearBodyFromProfile",
52237
52487
  "lib.gearBodies.fromProfile(profile, options)",
52238
- (profile, options) => gearBodyFromProfile(profile, options)
52488
+ (profile2, options) => gearBodyFromProfile(profile2, options)
52239
52489
  )
52240
52490
  };
52241
52491
 
@@ -65834,23 +66084,23 @@ function lowerShapeCompilePlanToCadQueryResultAtPath(plan, path2) {
65834
66084
  }
65835
66085
  return compilerSuccess({ kind: "torus", majorRadius: plan.majorRadius, minorRadius: plan.minorRadius });
65836
66086
  case "extrude": {
65837
- const profile = lowerProfileCompilePlanToCadQueryResultAtPath(plan.profile, `${path2}.profile`);
65838
- if (!profile.ok) return compilerFailure(...profile.diagnostics);
66087
+ const profile2 = lowerProfileCompilePlanToCadQueryResultAtPath(plan.profile, `${path2}.profile`);
66088
+ if (!profile2.ok) return compilerFailure(...profile2.diagnostics);
65839
66089
  return compilerSuccess(
65840
66090
  {
65841
66091
  kind: "extrude",
65842
- profile: profile.value,
66092
+ profile: profile2.value,
65843
66093
  height: plan.height,
65844
66094
  scaleTop: plan.scaleTop ? [plan.scaleTop[0], plan.scaleTop[1]] : void 0
65845
66095
  },
65846
- profile.diagnostics
66096
+ profile2.diagnostics
65847
66097
  );
65848
66098
  }
65849
66099
  case "sheetMetal": {
65850
66100
  try {
65851
66101
  return lowerShapeCompilePlanToCadQueryResultAtPath(lowerSheetMetalBasePlan(plan.model, plan.output), path2);
65852
- } catch (error) {
65853
- return compilerFailure(unsupportedSheetMetalDiagnostic(path2, error instanceof Error ? error.message : String(error)));
66102
+ } catch (error2) {
66103
+ return compilerFailure(unsupportedSheetMetalDiagnostic(path2, error2 instanceof Error ? error2.message : String(error2)));
65854
66104
  }
65855
66105
  }
65856
66106
  case "shell": {
@@ -65872,15 +66122,15 @@ function lowerShapeCompilePlanToCadQueryResultAtPath(plan, path2) {
65872
66122
  if (plan.segments != null && plan.segments > 0) {
65873
66123
  return compilerFailure(segmentedShapeDiagnostic("revolve", path2));
65874
66124
  }
65875
- const profile = lowerProfileCompilePlanToCadQueryResultAtPath(plan.profile, `${path2}.profile`);
65876
- if (!profile.ok) return compilerFailure(...profile.diagnostics);
66125
+ const profile2 = lowerProfileCompilePlanToCadQueryResultAtPath(plan.profile, `${path2}.profile`);
66126
+ if (!profile2.ok) return compilerFailure(...profile2.diagnostics);
65877
66127
  return compilerSuccess(
65878
66128
  {
65879
66129
  kind: "revolve",
65880
- profile: profile.value,
66130
+ profile: profile2.value,
65881
66131
  degrees: plan.degrees
65882
66132
  },
65883
- profile.diagnostics
66133
+ profile2.diagnostics
65884
66134
  );
65885
66135
  }
65886
66136
  case "loft": {
@@ -65910,18 +66160,18 @@ function lowerShapeCompilePlanToCadQueryResultAtPath(plan, path2) {
65910
66160
  );
65911
66161
  }
65912
66162
  case "sweep": {
65913
- const profile = lowerProfileCompilePlanToCadQueryResultAtPath(plan.profile, `${path2}.profile`);
65914
- if (!profile.ok) return compilerFailure(...profile.diagnostics);
66163
+ const profile2 = lowerProfileCompilePlanToCadQueryResultAtPath(plan.profile, `${path2}.profile`);
66164
+ if (!profile2.ok) return compilerFailure(...profile2.diagnostics);
65915
66165
  return compilerSuccess(
65916
66166
  {
65917
66167
  kind: "sweep",
65918
- profile: profile.value,
66168
+ profile: profile2.value,
65919
66169
  path: lowerSweepPathToCadQuery(plan.path),
65920
66170
  edgeLength: plan.edgeLength,
65921
66171
  boundsPadding: plan.boundsPadding,
65922
66172
  up: [plan.up[0], plan.up[1], plan.up[2]]
65923
66173
  },
65924
- profile.diagnostics
66174
+ profile2.diagnostics
65925
66175
  );
65926
66176
  }
65927
66177
  case "variableSweep": {
@@ -66856,8 +67106,8 @@ var ProductSkinBuilder = class {
66856
67106
  return { side, u, v };
66857
67107
  }
66858
67108
  /** Apply a product material preset to the lowered skin. */
66859
- material(material) {
66860
- this.materialValue = material;
67109
+ material(material2) {
67110
+ this.materialValue = material2;
66861
67111
  return this;
66862
67112
  }
66863
67113
  /** Apply a simple color override to the lowered skin. */
@@ -66926,8 +67176,8 @@ var ProductPanelBuilder = class {
66926
67176
  return this;
66927
67177
  }
66928
67178
  /** Use a custom 2D panel profile. */
66929
- profile(profile) {
66930
- this.profileValue = profile;
67179
+ profile(profile2) {
67180
+ this.profileValue = profile2;
66931
67181
  return this;
66932
67182
  }
66933
67183
  /** Set panel extrusion thickness. */
@@ -66936,8 +67186,8 @@ var ProductPanelBuilder = class {
66936
67186
  return this;
66937
67187
  }
66938
67188
  /** Apply a product material preset to the panel. */
66939
- material(material) {
66940
- this.materialValue = material;
67189
+ material(material2) {
67190
+ this.materialValue = material2;
66941
67191
  return this;
66942
67192
  }
66943
67193
  /** Apply a simple color override to the panel. */
@@ -66991,8 +67241,8 @@ var ProductSpoutBuilder = class {
66991
67241
  return this;
66992
67242
  }
66993
67243
  /** Apply a product material preset to the spout. */
66994
- material(material) {
66995
- this.materialValue = material;
67244
+ material(material2) {
67245
+ this.materialValue = material2;
66996
67246
  return this;
66997
67247
  }
66998
67248
  /** Apply a simple color override to the spout. */
@@ -67062,18 +67312,18 @@ var ProductHandleBuilder = class {
67062
67312
  return this;
67063
67313
  }
67064
67314
  /** Set the grip cross-section profile. */
67065
- grip(profile) {
67066
- this.gripProfile = profile;
67315
+ grip(profile2) {
67316
+ this.gripProfile = profile2;
67067
67317
  return this;
67068
67318
  }
67069
67319
  /** Apply a product material preset to the grip. */
67070
- material(material) {
67071
- this.materialValue = material;
67320
+ material(material2) {
67321
+ this.materialValue = material2;
67072
67322
  return this;
67073
67323
  }
67074
67324
  /** Apply a product material preset to handle landing pads. */
67075
- padMaterial(material) {
67076
- this.padMaterialValue = material;
67325
+ padMaterial(material2) {
67326
+ this.padMaterialValue = material2;
67077
67327
  return this;
67078
67328
  }
67079
67329
  /** Set the sampled loft target edge length for the grip. */
@@ -67248,8 +67498,8 @@ var ProductRibbonBuilder = class {
67248
67498
  return this;
67249
67499
  }
67250
67500
  /** Apply a product material preset. */
67251
- material(material) {
67252
- this.materialValue = material;
67501
+ material(material2) {
67502
+ this.materialValue = material2;
67253
67503
  return this;
67254
67504
  }
67255
67505
  /** Apply a simple color override. */
@@ -67563,8 +67813,8 @@ var Product = {
67563
67813
  return detail.transform(frame.matrix);
67564
67814
  },
67565
67815
  /** Small blended landing volume for manual structural bridges and connection proofs. */
67566
- landing(name, radius = 10, material) {
67567
- return applyMaterial(sphere2(radius, 48).scale([1.25, 0.75, 0.38]).as(name), material);
67816
+ landing(name, radius = 10, material2) {
67817
+ return applyMaterial(sphere2(radius, 48).scale([1.25, 0.75, 0.38]).as(name), material2);
67568
67818
  }
67569
67819
  };
67570
67820
 
@@ -68260,19 +68510,19 @@ function ease(t, easing) {
68260
68510
  function validateWidth(width, label) {
68261
68511
  return requirePositive6(width, label);
68262
68512
  }
68263
- function widthAt(profile, t) {
68264
- if (typeof profile === "number") return profile;
68265
- if (typeof profile === "function") return validateWidth(profile(t), "SurfaceBand width function result");
68266
- if ("from" in profile)
68267
- return validateWidth(profile.from + (profile.to - profile.from) * ease(t, profile.easing), "SurfaceBand eased width");
68268
- const stations = [...profile.stations].sort((a, b) => a.t - b.t);
68513
+ function widthAt(profile2, t) {
68514
+ if (typeof profile2 === "number") return profile2;
68515
+ if (typeof profile2 === "function") return validateWidth(profile2(t), "SurfaceBand width function result");
68516
+ if ("from" in profile2)
68517
+ return validateWidth(profile2.from + (profile2.to - profile2.from) * ease(t, profile2.easing), "SurfaceBand eased width");
68518
+ const stations = [...profile2.stations].sort((a, b) => a.t - b.t);
68269
68519
  if (stations.length === 0) throw new Error("SurfaceBand width profile requires at least one station");
68270
68520
  if (t <= stations[0].t) return stations[0].width;
68271
68521
  for (let index = 0; index < stations.length - 1; index += 1) {
68272
68522
  const a = stations[index];
68273
68523
  const b = stations[index + 1];
68274
68524
  if (t > b.t) continue;
68275
- const localT = ease((t - a.t) / Math.max(1e-8, b.t - a.t), profile.easing);
68525
+ const localT = ease((t - a.t) / Math.max(1e-8, b.t - a.t), profile2.easing);
68276
68526
  return a.width + (b.width - a.width) * localT;
68277
68527
  }
68278
68528
  return stations[stations.length - 1].width;
@@ -70287,8 +70537,8 @@ function debugCarrierFootprintPoint(path2, frame, across) {
70287
70537
  return add9(frame.point, scale10(frame.tangentAcross, across));
70288
70538
  }
70289
70539
  var SurfaceJoinBuilder = class {
70290
- constructor(body, join2) {
70291
- this.body = body;
70540
+ constructor(body2, join2) {
70541
+ this.body = body2;
70292
70542
  this.join = join2;
70293
70543
  }
70294
70544
  /** Select named anchors on the source and target members before lowering this join. */
@@ -70308,8 +70558,8 @@ var SurfaceJoinBuilder = class {
70308
70558
  }
70309
70559
  };
70310
70560
  var SurfaceMemberBuilder = class {
70311
- constructor(body, record) {
70312
- this.body = body;
70561
+ constructor(body2, record) {
70562
+ this.body = body2;
70313
70563
  this.record = record;
70314
70564
  }
70315
70565
  plate() {
@@ -72885,54 +73135,123 @@ function assertFinite(value, label) {
72885
73135
  throw new Error(`${label} must be finite`);
72886
73136
  }
72887
73137
  }
73138
+ function metadataNumber(value) {
73139
+ return typeof value === "number" ? value : void 0;
73140
+ }
73141
+ function metadataCollision(value) {
73142
+ return value === "none" || value === "visual" || value === "box" || value === "convex" ? value : void 0;
73143
+ }
73144
+ function colliderCollision(collider2) {
73145
+ if (!collider2) return void 0;
73146
+ if (collider2.mode === "box") return "box";
73147
+ if (collider2.mode === "visual") return "visual";
73148
+ return collider2.mode;
73149
+ }
73150
+ function velocityDriveEffort(drive) {
73151
+ return drive?.kind === "velocity" ? drive.maxTorqueNm : void 0;
73152
+ }
73153
+ function velocityDriveVelocityDegS(drive) {
73154
+ return drive?.kind === "velocity" ? drive.maxSpeedRpm * 6 : void 0;
73155
+ }
73156
+ function driveDamping(drive) {
73157
+ return drive?.damping;
73158
+ }
73159
+ function driveFriction(drive) {
73160
+ return drive?.friction;
73161
+ }
72888
73162
  function jointByName(assembly2) {
72889
73163
  return new Map(assembly2.joints.map((joint2) => [joint2.name, joint2]));
72890
73164
  }
73165
+ function diffDriveFromControllers(controllers) {
73166
+ if (!controllers) return void 0;
73167
+ const diffDrives = controllers.filter((controller) => controller.kind === "diffDrive");
73168
+ if (diffDrives.length > 1) {
73169
+ throw new Error("assembly.withSimulation(...) currently supports one Sim.controller.diffDrive(...) controller");
73170
+ }
73171
+ const diffDrive2 = diffDrives[0];
73172
+ return diffDrive2 ? {
73173
+ leftJoints: [...diffDrive2.leftJoints],
73174
+ rightJoints: [...diffDrive2.rightJoints],
73175
+ wheelSeparationMm: diffDrive2.wheelSeparationMm,
73176
+ wheelRadiusMm: diffDrive2.wheelRadiusMm,
73177
+ topic: diffDrive2.topic,
73178
+ odomTopic: diffDrive2.odomTopic,
73179
+ tfTopic: diffDrive2.tfTopic,
73180
+ frameId: diffDrive2.frameId,
73181
+ odomFrameId: diffDrive2.odomFrameId,
73182
+ maxLinearVelocity: diffDrive2.maxLinearVelocity,
73183
+ maxAngularVelocity: diffDrive2.maxAngularVelocity,
73184
+ linearAcceleration: diffDrive2.linearAcceleration,
73185
+ angularAcceleration: diffDrive2.angularAcceleration
73186
+ } : void 0;
73187
+ }
72891
73188
  function resetRobotExport() {
72892
73189
  _collectedRobotExport = null;
72893
73190
  }
72894
73191
  function getCollectedRobotExport() {
72895
73192
  return _collectedRobotExport;
72896
73193
  }
72897
- function robotExport(options) {
72898
- if (!options || typeof options !== "object") {
72899
- throw new Error("robotExport(...) expects an options object");
72900
- }
72901
- if (!options.assembly || typeof options.assembly.describe !== "function") {
72902
- throw new Error("robotExport(...) requires an assembly");
72903
- }
72904
- const assembly2 = options.assembly.describe();
73194
+ function collectSimulationModel(assemblyInput, options = {}) {
73195
+ const assembly2 = typeof assemblyInput.describe === "function" ? assemblyInput.describe() : assemblyInput;
72905
73196
  const partNames = new Set(assembly2.parts.map((part) => part.name));
72906
73197
  const joints = jointByName(assembly2);
72907
73198
  const links = cloneLinkOptions(options.links);
73199
+ for (const part of assembly2.parts) {
73200
+ const fromSim = part.sim;
73201
+ const fromMaterialDensity = fromSim?.material?.densityKgM3;
73202
+ const current = links[part.name] ?? {};
73203
+ const next = {
73204
+ massKg: current.massKg ?? fromSim?.massKg ?? metadataNumber(part.metadata?.massKg),
73205
+ densityKgM3: current.densityKgM3 ?? fromSim?.densityKgM3 ?? fromMaterialDensity ?? metadataNumber(part.metadata?.densityKgM3),
73206
+ collision: current.collision ?? colliderCollision(fromSim?.collider) ?? metadataCollision(part.metadata?.collision)
73207
+ };
73208
+ if (next.massKg !== void 0 || next.densityKgM3 !== void 0 || next.collision !== void 0) {
73209
+ links[part.name] = next;
73210
+ }
73211
+ }
72908
73212
  for (const [partName, link] of Object.entries(links)) {
72909
- if (!partNames.has(partName)) throw new Error(`robotExport(...) unknown link "${partName}"`);
72910
- assertFinite(link.massKg, `robotExport link "${partName}" massKg`);
72911
- assertFinite(link.densityKgM3, `robotExport link "${partName}" densityKgM3`);
73213
+ if (!partNames.has(partName)) throw new Error(`simulation model references unknown link "${partName}"`);
73214
+ assertFinite(link.massKg, `simulation model link "${partName}" massKg`);
73215
+ assertFinite(link.densityKgM3, `simulation model link "${partName}" densityKgM3`);
72912
73216
  }
72913
73217
  const jointOpts = cloneJointOptions(options.joints);
73218
+ for (const joint2 of assembly2.joints) {
73219
+ const drive = joint2.sim?.drive;
73220
+ const current = jointOpts[joint2.name] ?? {};
73221
+ const next = {
73222
+ effort: current.effort ?? velocityDriveEffort(drive) ?? joint2.effort,
73223
+ velocity: current.velocity ?? velocityDriveVelocityDegS(drive) ?? joint2.velocity,
73224
+ damping: current.damping ?? driveDamping(drive) ?? joint2.damping,
73225
+ friction: current.friction ?? driveFriction(drive) ?? joint2.friction
73226
+ };
73227
+ if (next.effort !== void 0 || next.velocity !== void 0 || next.damping !== void 0 || next.friction !== void 0) {
73228
+ jointOpts[joint2.name] = next;
73229
+ }
73230
+ }
72914
73231
  for (const [jointName, joint2] of Object.entries(jointOpts)) {
72915
- if (!joints.has(jointName)) throw new Error(`robotExport(...) unknown joint "${jointName}"`);
72916
- assertFinite(joint2.effort, `robotExport joint "${jointName}" effort`);
72917
- assertFinite(joint2.velocity, `robotExport joint "${jointName}" velocity`);
72918
- assertFinite(joint2.damping, `robotExport joint "${jointName}" damping`);
72919
- assertFinite(joint2.friction, `robotExport joint "${jointName}" friction`);
72920
- }
72921
- const diffDrive = cloneDiffDrive(options.plugins?.diffDrive);
72922
- if (diffDrive) {
72923
- if (diffDrive.leftJoints.length === 0 || diffDrive.rightJoints.length === 0) {
72924
- throw new Error("robotExport(...) diffDrive requires at least one left joint and one right joint");
72925
- }
72926
- assertFinite(diffDrive.wheelSeparationMm, "robotExport(...) diffDrive wheelSeparationMm");
72927
- assertFinite(diffDrive.wheelRadiusMm, "robotExport(...) diffDrive wheelRadiusMm");
72928
- if (diffDrive.wheelSeparationMm <= 0 || diffDrive.wheelRadiusMm <= 0) {
72929
- throw new Error("robotExport(...) diffDrive wheel separation and radius must be > 0");
72930
- }
72931
- [...diffDrive.leftJoints, ...diffDrive.rightJoints].forEach((jointName) => {
73232
+ if (!joints.has(jointName)) throw new Error(`simulation model references unknown joint "${jointName}"`);
73233
+ assertFinite(joint2.effort, `simulation model joint "${jointName}" effort`);
73234
+ assertFinite(joint2.velocity, `simulation model joint "${jointName}" velocity`);
73235
+ assertFinite(joint2.damping, `simulation model joint "${jointName}" damping`);
73236
+ assertFinite(joint2.friction, `simulation model joint "${jointName}" friction`);
73237
+ }
73238
+ const simulation = cloneSimAssemblySimulation(assembly2.sim) ?? null;
73239
+ const simulationDiffDrive = diffDriveFromControllers(simulation?.controllers);
73240
+ const diffDrive2 = cloneDiffDrive(options.plugins?.diffDrive) ?? simulationDiffDrive;
73241
+ if (diffDrive2) {
73242
+ if (diffDrive2.leftJoints.length === 0 || diffDrive2.rightJoints.length === 0) {
73243
+ throw new Error("simulation model diffDrive requires at least one left joint and one right joint");
73244
+ }
73245
+ assertFinite(diffDrive2.wheelSeparationMm, "simulation model diffDrive wheelSeparationMm");
73246
+ assertFinite(diffDrive2.wheelRadiusMm, "simulation model diffDrive wheelRadiusMm");
73247
+ if (diffDrive2.wheelSeparationMm <= 0 || diffDrive2.wheelRadiusMm <= 0) {
73248
+ throw new Error("simulation model diffDrive wheel separation and radius must be > 0");
73249
+ }
73250
+ [...diffDrive2.leftJoints, ...diffDrive2.rightJoints].forEach((jointName) => {
72932
73251
  const joint2 = joints.get(jointName);
72933
- if (!joint2) throw new Error(`robotExport(...) diffDrive references unknown joint "${jointName}"`);
73252
+ if (!joint2) throw new Error(`simulation model diffDrive references unknown joint "${jointName}"`);
72934
73253
  if (joint2.type !== "revolute") {
72935
- throw new Error(`robotExport(...) diffDrive joint "${jointName}" must be revolute`);
73254
+ throw new Error(`simulation model diffDrive joint "${jointName}" must be revolute`);
72936
73255
  }
72937
73256
  });
72938
73257
  }
@@ -72940,19 +73259,21 @@ function robotExport(options) {
72940
73259
  if (jointStatePublisher?.joints) {
72941
73260
  jointStatePublisher.joints.forEach((jointName) => {
72942
73261
  if (!joints.has(jointName)) {
72943
- throw new Error(`robotExport(...) jointStatePublisher references unknown joint "${jointName}"`);
73262
+ throw new Error(`simulation model jointStatePublisher references unknown joint "${jointName}"`);
72944
73263
  }
72945
73264
  });
72946
73265
  }
72947
73266
  const world = cloneWorld(options.world);
72948
73267
  if (world?.spawnPose) {
72949
- world.spawnPose.forEach((value, index) => assertFinite(value, `robotExport(...) world spawnPose[${index}]`));
73268
+ world.spawnPose.forEach((value, index) => assertFinite(value, `simulation model world spawnPose[${index}]`));
72950
73269
  }
72951
- assertFinite(world?.keyboardTeleop?.linearStep, "robotExport(...) world keyboardTeleop.linearStep");
72952
- assertFinite(world?.keyboardTeleop?.angularStep, "robotExport(...) world keyboardTeleop.angularStep");
72953
- _collectedRobotExport = {
72954
- modelName: (options.modelName ?? assembly2.name ?? "ForgeCAD Robot").trim() || "ForgeCAD Robot",
73270
+ assertFinite(world?.keyboardTeleop?.linearStep, "simulation model world keyboardTeleop.linearStep");
73271
+ assertFinite(world?.keyboardTeleop?.angularStep, "simulation model world keyboardTeleop.angularStep");
73272
+ return {
73273
+ modelName: (options.modelName ?? assembly2.name ?? "ForgeCAD Simulation").trim() || "ForgeCAD Simulation",
72955
73274
  assembly: assembly2,
73275
+ simulation,
73276
+ source: options.source ?? "assembly",
72956
73277
  state: { ...options.state ?? {} },
72957
73278
  static: options.static ?? false,
72958
73279
  selfCollide: options.selfCollide ?? false,
@@ -72960,14 +73281,358 @@ function robotExport(options) {
72960
73281
  links,
72961
73282
  joints: jointOpts,
72962
73283
  plugins: {
72963
- diffDrive,
73284
+ diffDrive: diffDrive2,
72964
73285
  jointStatePublisher
72965
73286
  },
72966
73287
  world
72967
73288
  };
73289
+ }
73290
+ function robotExport(options) {
73291
+ if (!options || typeof options !== "object") {
73292
+ throw new Error("robotExport(...) expects an options object");
73293
+ }
73294
+ if (!options.assembly || typeof options.assembly.describe !== "function") {
73295
+ throw new Error("robotExport(...) requires an assembly");
73296
+ }
73297
+ _collectedRobotExport = collectSimulationModel(options.assembly, { ...options, source: "robotExport" });
72968
73298
  return _collectedRobotExport;
72969
73299
  }
72970
73300
 
73301
+ // src/forge/export/simreadyValidation.ts
73302
+ function error(findings, code, message, path2) {
73303
+ findings.push({ level: "error", code, message, path: path2 });
73304
+ }
73305
+ function warn(findings, code, message, path2) {
73306
+ findings.push({ level: "warning", code, message, path: path2 });
73307
+ }
73308
+ function isFiniteNumber3(value) {
73309
+ return value === void 0 || Number.isFinite(value);
73310
+ }
73311
+ function requireFinite9(findings, value, label, path2) {
73312
+ if (!isFiniteNumber3(value)) error(findings, "SIM.NUMBER.FINITE", `${label} must be finite.`, path2);
73313
+ }
73314
+ function requirePositive7(findings, value, label, path2) {
73315
+ requireFinite9(findings, value, label, path2);
73316
+ if (value !== void 0 && value <= 0) error(findings, "SIM.NUMBER.POSITIVE", `${label} must be > 0.`, path2);
73317
+ }
73318
+ function requireNonNegative(findings, value, label, path2) {
73319
+ requireFinite9(findings, value, label, path2);
73320
+ if (value !== void 0 && value < 0) error(findings, "SIM.NUMBER.NON_NEGATIVE", `${label} must be >= 0.`, path2);
73321
+ }
73322
+ function partConnectorNames(part) {
73323
+ if (part.part instanceof Shape) return new Set(Object.keys(getShapePorts(part.part)));
73324
+ if (part.part instanceof ShapeGroup) return new Set(Object.keys(getShapeGroupPorts(part.part)));
73325
+ return /* @__PURE__ */ new Set();
73326
+ }
73327
+ function profileRequiresExplicitCollider(model) {
73328
+ return model.simulation !== null;
73329
+ }
73330
+ function validatePart(findings, model, part) {
73331
+ const link = model.links[part.name];
73332
+ const hasLegacyPhysicalData = link?.massKg !== void 0 || link?.densityKgM3 !== void 0;
73333
+ const path2 = `parts.${part.name}`;
73334
+ if (!part.sim && !hasLegacyPhysicalData) {
73335
+ error(
73336
+ findings,
73337
+ "SIM.BODY.MISSING",
73338
+ `Part "${part.name}" needs Sim.body(...) metadata or legacy massKg/densityKgM3 export metadata.`,
73339
+ path2
73340
+ );
73341
+ return;
73342
+ }
73343
+ requirePositive7(findings, link?.massKg, `Part "${part.name}" massKg`, `${path2}.massKg`);
73344
+ requirePositive7(findings, link?.densityKgM3, `Part "${part.name}" densityKgM3`, `${path2}.densityKgM3`);
73345
+ if (link?.massKg === void 0 && link?.densityKgM3 === void 0) {
73346
+ error(findings, "SIM.BODY.MASS_MISSING", `Part "${part.name}" needs massKg or densityKgM3.`, path2);
73347
+ }
73348
+ if (profileRequiresExplicitCollider(model) && !part.sim?.collider && !link?.collision) {
73349
+ error(findings, "SIM.COLLIDER.MISSING", `Part "${part.name}" needs an explicit Sim.collider.*(...) selection.`, `${path2}.collider`);
73350
+ }
73351
+ if (part.sim?.collider?.mode === "none" && !part.sim.collider.reason?.trim()) {
73352
+ error(findings, "SIM.COLLIDER.NONE_REASON", `Part "${part.name}" uses Sim.collider.none(...) without a reason.`, `${path2}.collider`);
73353
+ }
73354
+ const material2 = part.sim?.material;
73355
+ if (material2) {
73356
+ requirePositive7(findings, material2.densityKgM3, `Material "${material2.name}" densityKgM3`, `${path2}.material.densityKgM3`);
73357
+ requireNonNegative(findings, material2.staticFriction, `Material "${material2.name}" staticFriction`, `${path2}.material.staticFriction`);
73358
+ requireNonNegative(
73359
+ findings,
73360
+ material2.dynamicFriction,
73361
+ `Material "${material2.name}" dynamicFriction`,
73362
+ `${path2}.material.dynamicFriction`
73363
+ );
73364
+ requireNonNegative(findings, material2.restitution, `Material "${material2.name}" restitution`, `${path2}.material.restitution`);
73365
+ }
73366
+ const connectorNames = partConnectorNames(part);
73367
+ for (const [name, contact2] of Object.entries(part.sim?.contacts ?? {})) {
73368
+ if (!connectorNames.has(contact2.connectorName)) {
73369
+ error(
73370
+ findings,
73371
+ "SIM.CONTACT.CONNECTOR_MISSING",
73372
+ `Contact "${name}" on part "${part.name}" references missing connector "${contact2.connectorName}".`,
73373
+ `${path2}.contacts.${name}`
73374
+ );
73375
+ }
73376
+ }
73377
+ }
73378
+ function validateJoint(findings, model, joint2) {
73379
+ const opts = model.joints[joint2.name];
73380
+ const path2 = `joints.${joint2.name}`;
73381
+ requirePositive7(findings, opts?.effort, `Joint "${joint2.name}" effort`, `${path2}.effort`);
73382
+ requirePositive7(findings, opts?.velocity, `Joint "${joint2.name}" velocity`, `${path2}.velocity`);
73383
+ requireNonNegative(findings, opts?.damping, `Joint "${joint2.name}" damping`, `${path2}.damping`);
73384
+ requireNonNegative(findings, opts?.friction, `Joint "${joint2.name}" friction`, `${path2}.friction`);
73385
+ const drive = joint2.sim?.drive;
73386
+ if (drive?.kind === "velocity") {
73387
+ requirePositive7(findings, drive.maxTorqueNm, `Joint "${joint2.name}" velocity drive maxTorqueNm`, `${path2}.drive.maxTorqueNm`);
73388
+ requirePositive7(findings, drive.maxSpeedRpm, `Joint "${joint2.name}" velocity drive maxSpeedRpm`, `${path2}.drive.maxSpeedRpm`);
73389
+ }
73390
+ if (drive) {
73391
+ requireNonNegative(findings, drive.damping, `Joint "${joint2.name}" drive damping`, `${path2}.drive.damping`);
73392
+ requireNonNegative(findings, drive.friction, `Joint "${joint2.name}" drive friction`, `${path2}.drive.friction`);
73393
+ }
73394
+ }
73395
+ function validateDiffDriveController(findings, model) {
73396
+ const diffDrive2 = model.plugins.diffDrive;
73397
+ if (!diffDrive2) return;
73398
+ const jointsByName = new Map(model.assembly.joints.map((joint2) => [joint2.name, joint2]));
73399
+ if (diffDrive2.leftJoints.length === 0 || diffDrive2.rightJoints.length === 0) {
73400
+ error(
73401
+ findings,
73402
+ "SIM.CONTROLLER.DIFF_DRIVE_EMPTY",
73403
+ "Diff-drive controller needs at least one left and one right joint.",
73404
+ "controllers.diffDrive"
73405
+ );
73406
+ }
73407
+ requirePositive7(findings, diffDrive2.wheelRadiusMm, "Diff-drive wheelRadiusMm", "controllers.diffDrive.wheelRadiusMm");
73408
+ requirePositive7(findings, diffDrive2.wheelSeparationMm, "Diff-drive wheelSeparationMm", "controllers.diffDrive.wheelSeparationMm");
73409
+ for (const jointName of [...diffDrive2.leftJoints, ...diffDrive2.rightJoints]) {
73410
+ const joint2 = jointsByName.get(jointName);
73411
+ if (!joint2) {
73412
+ error(
73413
+ findings,
73414
+ "SIM.CONTROLLER.UNKNOWN_JOINT",
73415
+ `Diff-drive controller references unknown joint "${jointName}".`,
73416
+ "controllers.diffDrive"
73417
+ );
73418
+ } else if (joint2.type !== "revolute") {
73419
+ error(
73420
+ findings,
73421
+ "SIM.CONTROLLER.JOINT_TYPE",
73422
+ `Diff-drive joint "${jointName}" must be revolute, got ${joint2.type}.`,
73423
+ `joints.${jointName}`
73424
+ );
73425
+ }
73426
+ }
73427
+ }
73428
+ function validateRobotGraph(findings, model) {
73429
+ if (!simProfileRequiresRootPart(model.simulation?.profile)) return;
73430
+ const rootPart = model.simulation?.rootPart;
73431
+ if (!rootPart) return;
73432
+ const partNames = new Set(model.assembly.parts.map((part) => part.name));
73433
+ if (!partNames.has(rootPart)) {
73434
+ error(findings, "SIM.ROBOT.ROOT_PART", `Robot rootPart "${rootPart}" does not exist.`, "sim.rootPart");
73435
+ return;
73436
+ }
73437
+ const graph = /* @__PURE__ */ new Map();
73438
+ for (const part of partNames) graph.set(part, /* @__PURE__ */ new Set());
73439
+ for (const joint2 of model.assembly.joints) {
73440
+ graph.get(joint2.parent)?.add(joint2.child);
73441
+ graph.get(joint2.child)?.add(joint2.parent);
73442
+ }
73443
+ const visited = /* @__PURE__ */ new Set();
73444
+ const queue = [rootPart];
73445
+ while (queue.length > 0) {
73446
+ const current = queue.shift();
73447
+ if (visited.has(current)) continue;
73448
+ visited.add(current);
73449
+ for (const next of graph.get(current) ?? []) {
73450
+ if (!visited.has(next)) queue.push(next);
73451
+ }
73452
+ }
73453
+ const disconnected = [...partNames].filter((partName) => !visited.has(partName));
73454
+ if (disconnected.length > 0) {
73455
+ error(
73456
+ findings,
73457
+ "SIM.ROBOT.DISCONNECTED_GRAPH",
73458
+ `Robot joint graph is disconnected from rootPart "${rootPart}": ${disconnected.join(", ")}.`,
73459
+ "sim.graph"
73460
+ );
73461
+ }
73462
+ }
73463
+ function validateWorld(findings, model) {
73464
+ const world = model.world;
73465
+ if (!world) return;
73466
+ world.spawnPose?.forEach((value, index) => requireFinite9(findings, value, `world spawnPose[${index}]`, `world.spawnPose.${index}`));
73467
+ requirePositive7(findings, world.keyboardTeleop?.linearStep, "world keyboardTeleop.linearStep", "world.keyboardTeleop.linearStep");
73468
+ requirePositive7(findings, world.keyboardTeleop?.angularStep, "world keyboardTeleop.angularStep", "world.keyboardTeleop.angularStep");
73469
+ }
73470
+ function validateSimulationModel(model) {
73471
+ const findings = [];
73472
+ const simulation = model.simulation;
73473
+ if (!simulation && model.source !== "robotExport") {
73474
+ error(findings, "SIM.SIMULATION.MISSING", "Model must return assembly(...).withSimulation(...).", "sim");
73475
+ }
73476
+ if (!simulation && model.source === "robotExport") {
73477
+ warn(
73478
+ findings,
73479
+ "SIM.SIMULATION.LEGACY_ROBOT_EXPORT",
73480
+ "Legacy robotExport(...) metadata is accepted for compatibility; prefer assembly.withSimulation(...).",
73481
+ "sim"
73482
+ );
73483
+ }
73484
+ if (simulation && !simulation.profile) {
73485
+ error(findings, "SIM.PROFILE.MISSING", "Simulation contract needs a Sim.profile.*() value.", "sim.profile");
73486
+ }
73487
+ if (simProfileRequiresRootPart(simulation?.profile) && !simulation?.rootPart) {
73488
+ error(findings, "SIM.ROBOT.ROOT_PART_MISSING", "Robot-body simulation profiles require rootPart.", "sim.rootPart");
73489
+ }
73490
+ if (simulation?.controllers && simulation.controllers.length > 0 && !simProfileAllowsControllers(simulation.profile)) {
73491
+ error(
73492
+ findings,
73493
+ "SIM.CONTROLLER.PROFILE_UNSUPPORTED",
73494
+ "Controllers are only supported for robot-body simulation profiles.",
73495
+ "sim.controllers"
73496
+ );
73497
+ }
73498
+ model.assembly.parts.forEach((part) => validatePart(findings, model, part));
73499
+ model.assembly.joints.forEach((joint2) => validateJoint(findings, model, joint2));
73500
+ validateDiffDriveController(findings, model);
73501
+ validateRobotGraph(findings, model);
73502
+ validateWorld(findings, model);
73503
+ const errors = findings.filter((finding) => finding.level === "error");
73504
+ const warnings = findings.filter((finding) => finding.level === "warning");
73505
+ return {
73506
+ ok: errors.length === 0,
73507
+ modelName: model.modelName,
73508
+ profile: simulation?.profile.name ?? null,
73509
+ contract: simulation?.kind ?? null,
73510
+ errors,
73511
+ warnings
73512
+ };
73513
+ }
73514
+
73515
+ // src/forge/export/simreadyManifest.ts
73516
+ function materialManifest(material2) {
73517
+ if (!material2) return void 0;
73518
+ return {
73519
+ name: material2.name,
73520
+ densityKgM3: material2.densityKgM3,
73521
+ staticFriction: material2.staticFriction,
73522
+ dynamicFriction: material2.dynamicFriction,
73523
+ restitution: material2.restitution
73524
+ };
73525
+ }
73526
+ function bodyPhysicalSource(sim, link) {
73527
+ if (sim) return "sim";
73528
+ if (link?.massKg !== void 0 || link?.densityKgM3 !== void 0 || link?.collision !== void 0) return "legacy-export";
73529
+ return "missing";
73530
+ }
73531
+ function bodyManifest(model) {
73532
+ return model.assembly.parts.map((part) => {
73533
+ const link = model.links[part.name];
73534
+ const contacts = Object.entries(part.sim?.contacts ?? {}).map(([name, contact2]) => ({
73535
+ name,
73536
+ kind: contact2.kind,
73537
+ connectorName: contact2.connectorName
73538
+ }));
73539
+ return {
73540
+ name: part.name,
73541
+ physicalSource: bodyPhysicalSource(part.sim, link),
73542
+ massKg: link?.massKg,
73543
+ densityKgM3: link?.densityKgM3,
73544
+ material: materialManifest(part.sim?.material),
73545
+ collider: part.sim?.collider ? { mode: part.sim.collider.mode, reason: part.sim.collider.reason } : void 0,
73546
+ contacts
73547
+ };
73548
+ });
73549
+ }
73550
+ function jointManifest(model) {
73551
+ return model.assembly.joints.map((joint2) => {
73552
+ const exportDynamics = model.joints[joint2.name];
73553
+ return {
73554
+ name: joint2.name,
73555
+ type: joint2.type,
73556
+ parent: joint2.parent,
73557
+ child: joint2.child,
73558
+ limits: joint2.min !== void 0 || joint2.max !== void 0 ? {
73559
+ min: joint2.min,
73560
+ max: joint2.max,
73561
+ unit: joint2.unit
73562
+ } : void 0,
73563
+ drive: joint2.sim?.drive,
73564
+ exportDynamics
73565
+ };
73566
+ });
73567
+ }
73568
+ function diffDriveManifest(diffDrive2) {
73569
+ if (!diffDrive2) return [];
73570
+ return [
73571
+ {
73572
+ kind: "diffDrive",
73573
+ leftJoints: [...diffDrive2.leftJoints],
73574
+ rightJoints: [...diffDrive2.rightJoints],
73575
+ wheelSeparationMm: diffDrive2.wheelSeparationMm,
73576
+ wheelRadiusMm: diffDrive2.wheelRadiusMm
73577
+ }
73578
+ ];
73579
+ }
73580
+ function externalStages(model) {
73581
+ const stages = ["convert-to-usd", "validate-usd-minimum"];
73582
+ if (model.simulation) stages.push("simready-conform-profile");
73583
+ stages.push("omni-asset-validate", "omni-asset-validate-geometry", "omni-asset-validate-physics", "simready-validate");
73584
+ return stages;
73585
+ }
73586
+ function buildSimReadyManifest(model) {
73587
+ const validation = validateSimulationModel(model);
73588
+ const bodies = bodyManifest(model);
73589
+ const joints = jointManifest(model);
73590
+ const controllers = diffDriveManifest(model.plugins.diffDrive);
73591
+ const contactSurfaceCount = bodies.reduce((sum, body2) => sum + body2.contacts.length, 0);
73592
+ const profile2 = model.simulation?.profile;
73593
+ return {
73594
+ format: "forgecad-simready-manifest",
73595
+ version: 1,
73596
+ modelName: model.modelName,
73597
+ sourceModelName: model.assembly.name,
73598
+ source: model.source,
73599
+ profile: profile2?.name ?? null,
73600
+ profileVersion: "1.0.0",
73601
+ units: {
73602
+ sourceLength: "millimeter",
73603
+ exportLength: "meter",
73604
+ sourceToMeters: 1e-3
73605
+ },
73606
+ contract: model.simulation ? { kind: model.simulation.kind, rootPart: model.simulation.rootPart } : null,
73607
+ capabilities: {
73608
+ bodyCount: bodies.length,
73609
+ jointCount: joints.length,
73610
+ contactSurfaceCount,
73611
+ drivenJointCount: joints.filter((joint2) => joint2.drive || joint2.exportDynamics).length,
73612
+ controllerCount: controllers.length,
73613
+ hasRobotProfile: simProfileRequiresRootPart(profile2),
73614
+ hasAssetProfile: profile2?.name === "Prop-Robotics-Physx"
73615
+ },
73616
+ bodies,
73617
+ joints,
73618
+ controllers,
73619
+ validation: {
73620
+ ok: validation.ok,
73621
+ errors: validation.errors,
73622
+ warnings: validation.warnings
73623
+ },
73624
+ externalHandoff: {
73625
+ usdAuthoring: "not-included",
73626
+ nvidiaValidation: "optional-external",
73627
+ suggestedStages: externalStages(model)
73628
+ }
73629
+ };
73630
+ }
73631
+ function simReadyManifestJson(manifest) {
73632
+ return `${JSON.stringify(manifest, null, 2)}
73633
+ `;
73634
+ }
73635
+
72971
73636
  // src/forge/sketch/entities.ts
72972
73637
  var Point2D = class _Point2D {
72973
73638
  constructor(x, y) {
@@ -73516,7 +74181,7 @@ function requireVec34(v, label) {
73516
74181
  }
73517
74182
  return v;
73518
74183
  }
73519
- function requireFiniteNumber2(n, label) {
74184
+ function requireFiniteNumber3(n, label) {
73520
74185
  if (typeof n !== "number" || !Number.isFinite(n)) {
73521
74186
  throw new Error(`${label} must be a finite number, got ${JSON.stringify(n)}`);
73522
74187
  }
@@ -73535,7 +74200,7 @@ function midpoint4(a, b) {
73535
74200
  function lerp4(a, b, t) {
73536
74201
  requireVec34(a, "a");
73537
74202
  requireVec34(b, "b");
73538
- requireFiniteNumber2(t, "t");
74203
+ requireFiniteNumber3(t, "t");
73539
74204
  return [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t, a[2] + (b[2] - a[2]) * t];
73540
74205
  }
73541
74206
  function direction(a, b) {
@@ -73553,7 +74218,7 @@ function direction(a, b) {
73553
74218
  function offset(point2, dir, amount) {
73554
74219
  requireVec34(point2, "point");
73555
74220
  requireVec34(dir, "dir");
73556
- requireFiniteNumber2(amount, "amount");
74221
+ requireFiniteNumber3(amount, "amount");
73557
74222
  return [point2[0] + dir[0] * amount, point2[1] + dir[1] * amount, point2[2] + dir[2] * amount];
73558
74223
  }
73559
74224
  var Points2 = {
@@ -73661,14 +74326,14 @@ var WoodBoard = class _WoodBoard {
73661
74326
  };
73662
74327
 
73663
74328
  // src/forge/wood/joints.ts
73664
- function requireFinite9(value, name) {
74329
+ function requireFinite10(value, name) {
73665
74330
  if (!Number.isFinite(value)) {
73666
74331
  throw new Error(`${name} must be a finite number, got ${value}`);
73667
74332
  }
73668
74333
  return value;
73669
74334
  }
73670
- function requirePositive7(value, name) {
73671
- requireFinite9(value, name);
74335
+ function requirePositive8(value, name) {
74336
+ requireFinite10(value, name);
73672
74337
  if (value <= 0) {
73673
74338
  throw new Error(`${name} must be positive, got ${value}`);
73674
74339
  }
@@ -73694,7 +74359,7 @@ function dado(host, guest, opts) {
73694
74359
  const fit = opts.fit ?? "snug";
73695
74360
  const clearance = clearanceForFit(fit);
73696
74361
  const channelWidth = guest.thickness + clearance;
73697
- const channelDepth = opts.depth != null ? requirePositive7(opts.depth, "depth") : host.thickness / 3;
74362
+ const channelDepth = opts.depth != null ? requirePositive8(opts.depth, "depth") : host.thickness / 3;
73698
74363
  if (opts.fromBottom != null && opts.fromTop != null) {
73699
74364
  throw new Error("dado: specify fromBottom or fromTop, not both");
73700
74365
  }
@@ -73703,15 +74368,15 @@ function dado(host, guest, opts) {
73703
74368
  }
73704
74369
  let fromBottom;
73705
74370
  if (opts.fromBottom != null) {
73706
- fromBottom = requireFinite9(opts.fromBottom, "fromBottom");
74371
+ fromBottom = requireFinite10(opts.fromBottom, "fromBottom");
73707
74372
  } else {
73708
74373
  fromBottom = host.height - opts.fromTop - channelWidth;
73709
- requireFinite9(fromBottom, "computed fromBottom");
74374
+ requireFinite10(fromBottom, "computed fromBottom");
73710
74375
  }
73711
74376
  let dadoLength = host.width;
73712
74377
  let xOffset = 0;
73713
74378
  if (opts.stopped != null) {
73714
- const stoppedDist = requirePositive7(opts.stopped, "stopped");
74379
+ const stoppedDist = requirePositive8(opts.stopped, "stopped");
73715
74380
  dadoLength = host.width - stoppedDist;
73716
74381
  xOffset = -stoppedDist / 2;
73717
74382
  }
@@ -73723,8 +74388,8 @@ function dado(host, guest, opts) {
73723
74388
  function rabbet(board, opts) {
73724
74389
  if (!board) throw new Error("rabbet: board is required");
73725
74390
  if (!opts) throw new Error("rabbet: options are required");
73726
- const width = requirePositive7(opts.width, "width");
73727
- const depth = requirePositive7(opts.depth, "depth");
74391
+ const width = requirePositive8(opts.width, "width");
74392
+ const depth = requirePositive8(opts.depth, "depth");
73728
74393
  const edge = opts.edge;
73729
74394
  const bw = board.width;
73730
74395
  const bh = board.height;
@@ -73764,10 +74429,10 @@ function mortiseAndTenon(mortiseBoard, tenonBoard, opts) {
73764
74429
  const style = o.style ?? "blind";
73765
74430
  const fit = o.fit ?? "snug";
73766
74431
  const clearance = clearanceForFit(fit);
73767
- const cornerRadius = o.cornerRadius != null ? requireFinite9(o.cornerRadius, "cornerRadius") : 0;
73768
- const tenonThickness = o.tenonThickness != null ? requirePositive7(o.tenonThickness, "tenonThickness") : tenonBoard.thickness / 3;
73769
- const tenonWidth = o.tenonWidth != null ? requirePositive7(o.tenonWidth, "tenonWidth") : Math.min(tenonBoard.height * 0.6, mortiseBoard.height * 0.8);
73770
- const tenonLength = o.tenonLength != null ? requirePositive7(o.tenonLength, "tenonLength") : style === "through" ? mortiseBoard.thickness : mortiseBoard.thickness * 2 / 3;
74432
+ const cornerRadius = o.cornerRadius != null ? requireFinite10(o.cornerRadius, "cornerRadius") : 0;
74433
+ const tenonThickness = o.tenonThickness != null ? requirePositive8(o.tenonThickness, "tenonThickness") : tenonBoard.thickness / 3;
74434
+ const tenonWidth = o.tenonWidth != null ? requirePositive8(o.tenonWidth, "tenonWidth") : Math.min(tenonBoard.height * 0.6, mortiseBoard.height * 0.8);
74435
+ const tenonLength = o.tenonLength != null ? requirePositive8(o.tenonLength, "tenonLength") : style === "through" ? mortiseBoard.thickness : mortiseBoard.thickness * 2 / 3;
73771
74436
  const mortiseW = tenonThickness + clearance;
73772
74437
  const mortiseH = tenonWidth + clearance;
73773
74438
  const mortiseDepth = style === "through" ? mortiseBoard.thickness + 1 : tenonLength;
@@ -73777,10 +74442,10 @@ function mortiseAndTenon(mortiseBoard, tenonBoard, opts) {
73777
74442
  throw new Error("mortiseAndTenon: specify position.fromTop or position.fromBottom, not both");
73778
74443
  }
73779
74444
  if (o.position.fromTop != null) {
73780
- requireFinite9(o.position.fromTop, "position.fromTop");
74445
+ requireFinite10(o.position.fromTop, "position.fromTop");
73781
74446
  mortiseCenterY = mortiseBoard.height / 2 - o.position.fromTop - mortiseH / 2;
73782
74447
  } else if (o.position.fromBottom != null) {
73783
- requireFinite9(o.position.fromBottom, "position.fromBottom");
74448
+ requireFinite10(o.position.fromBottom, "position.fromBottom");
73784
74449
  mortiseCenterY = -mortiseBoard.height / 2 + o.position.fromBottom + mortiseH / 2;
73785
74450
  }
73786
74451
  }
@@ -73788,8 +74453,8 @@ function mortiseAndTenon(mortiseBoard, tenonBoard, opts) {
73788
74453
  const mortiseZBase = style === "through" ? -0.5 : mortiseBoard.thickness - mortiseDepth;
73789
74454
  if (cornerRadius > 0) {
73790
74455
  const r = Math.min(cornerRadius, mortiseW / 2, mortiseH / 2);
73791
- const profile = roundedRect(mortiseW, mortiseH, r);
73792
- mortiseCutter = profile.extrude(mortiseDepth).translate(0, mortiseCenterY, mortiseZBase);
74456
+ const profile2 = roundedRect(mortiseW, mortiseH, r);
74457
+ mortiseCutter = profile2.extrude(mortiseDepth).translate(0, mortiseCenterY, mortiseZBase);
73793
74458
  } else {
73794
74459
  mortiseCutter = box2(mortiseW, mortiseH, mortiseDepth).translate(0, mortiseCenterY, mortiseZBase);
73795
74460
  }
@@ -73881,19 +74546,19 @@ function isOrbitKeyframe(kf) {
73881
74546
  function isCartesianKeyframe(kf) {
73882
74547
  return "position" in kf;
73883
74548
  }
73884
- function requireFinite10(value, label) {
74549
+ function requireFinite11(value, label) {
73885
74550
  if (!Number.isFinite(value)) {
73886
74551
  throw new Error(`cameraTrajectory(): ${label} must be a finite number, got ${value}`);
73887
74552
  }
73888
74553
  }
73889
74554
  function validateOrbitKeyframe(kf, index) {
73890
- requireFinite10(kf.at, `keyframes[${index}].at`);
73891
- requireFinite10(kf.orbit.angle, `keyframes[${index}].orbit.angle`);
73892
- requireFinite10(kf.orbit.pitch, `keyframes[${index}].orbit.pitch`);
73893
- requireFinite10(kf.orbit.distance, `keyframes[${index}].orbit.distance`);
74555
+ requireFinite11(kf.at, `keyframes[${index}].at`);
74556
+ requireFinite11(kf.orbit.angle, `keyframes[${index}].orbit.angle`);
74557
+ requireFinite11(kf.orbit.pitch, `keyframes[${index}].orbit.pitch`);
74558
+ requireFinite11(kf.orbit.distance, `keyframes[${index}].orbit.distance`);
73894
74559
  }
73895
74560
  function validateCartesianKeyframe(kf, index) {
73896
- requireFinite10(kf.at, `keyframes[${index}].at`);
74561
+ requireFinite11(kf.at, `keyframes[${index}].at`);
73897
74562
  if (!Array.isArray(kf.position) || kf.position.length !== 3) {
73898
74563
  throw new Error(`cameraTrajectory(): keyframes[${index}].position must be a 3-element array`);
73899
74564
  }
@@ -73901,8 +74566,8 @@ function validateCartesianKeyframe(kf, index) {
73901
74566
  throw new Error(`cameraTrajectory(): keyframes[${index}].target must be a 3-element array`);
73902
74567
  }
73903
74568
  for (let i = 0; i < 3; i++) {
73904
- requireFinite10(kf.position[i], `keyframes[${index}].position[${i}]`);
73905
- requireFinite10(kf.target[i], `keyframes[${index}].target[${i}]`);
74569
+ requireFinite11(kf.position[i], `keyframes[${index}].position[${i}]`);
74570
+ requireFinite11(kf.target[i], `keyframes[${index}].target[${i}]`);
73906
74571
  }
73907
74572
  }
73908
74573
  function validateKeyframeOrder(keyframes) {
@@ -74157,21 +74822,28 @@ function consumeBroadEdgeFeatureBudget(edgeCount) {
74157
74822
  broadEdgeFeatureBudget = Math.max(0, remaining - edgeCount);
74158
74823
  return true;
74159
74824
  }
74825
+ var BROAD_EDGE_FEATURE_REMEDY = "Pass an explicit edge selector (e.g. selectEdges(shape) or { convex: true }), use high quality/export, or raise the budget with FORGECAD_BROAD_EDGE_FEATURE_BUDGET / FORGECAD_ALLOW_BROAD_EDGE_FEATURES=1.";
74826
+ function skipOrRejectBroadEdgeFeature(operation, reason) {
74827
+ if (getForgeQualityPreset() === "live") {
74828
+ emitRuntimeWarning(
74829
+ `${operation}() without an edge selector ${reason} Skipped this edge finish for live-preview responsiveness. ${BROAD_EDGE_FEATURE_REMEDY}`
74830
+ );
74831
+ return true;
74832
+ }
74833
+ throw new Error(`${operation}() without an edge selector ${reason} ${BROAD_EDGE_FEATURE_REMEDY}`);
74834
+ }
74160
74835
  function shouldSkipBroadEdgeFeature(operation, edgeCount) {
74161
74836
  if (consumeBroadEdgeFeatureBudget(edgeCount)) return false;
74162
74837
  const remaining = Math.max(0, remainingBroadEdgeFeatureBudget());
74163
- emitRuntimeWarning(
74164
- `${operation}() without an edge selector matched ${edgeCount} edge(s), exceeding the remaining broad edge-feature budget (${remaining}). Skipped this cosmetic edge finish for responsiveness. Pass an explicit edge selector, use high quality/export, or set FORGECAD_BROAD_EDGE_FEATURE_BUDGET to opt into more broad edge finishing.`
74838
+ return skipOrRejectBroadEdgeFeature(
74839
+ operation,
74840
+ `matched ${edgeCount} edge(s), exceeding the remaining broad edge-feature budget (${remaining}).`
74165
74841
  );
74166
- return true;
74167
74842
  }
74168
74843
  function shouldSkipExhaustedBroadEdgeFeature(operation) {
74169
74844
  const remaining = remainingBroadEdgeFeatureBudget();
74170
74845
  if (!Number.isFinite(remaining) || remaining > 0) return false;
74171
- emitRuntimeWarning(
74172
- `${operation}() without an edge selector was skipped because the broad edge-feature budget is exhausted. Skipped this cosmetic edge finish for responsiveness. Pass an explicit edge selector, use high quality/export, or set FORGECAD_BROAD_EDGE_FEATURE_BUDGET to opt into more broad edge finishing.`
74173
- );
74174
- return true;
74846
+ return skipOrRejectBroadEdgeFeature(operation, "cannot run because the broad edge-feature budget is exhausted.");
74175
74847
  }
74176
74848
  function estimateSelectorlessEdgeCount(plan) {
74177
74849
  if (!plan) return null;
@@ -74190,10 +74862,10 @@ function shouldSkipUnestimatedBroadEdgeFeature(operation, target) {
74190
74862
  if (!Number.isFinite(remaining)) return false;
74191
74863
  const estimatedEdges = estimateSelectorlessEdgeCount(getShapeCompilePlan(target));
74192
74864
  if (estimatedEdges !== null && estimatedEdges <= remaining) return false;
74193
- emitRuntimeWarning(
74194
- `${operation}() without an edge selector was skipped before broad edge enumeration because this shape is too complex for the remaining broad edge-feature budget (${Math.max(0, remaining)}). Skipped this cosmetic edge finish for responsiveness. Pass an explicit edge selector, use high quality/export, or set FORGECAD_BROAD_EDGE_FEATURE_BUDGET to opt into more broad edge finishing.`
74865
+ return skipOrRejectBroadEdgeFeature(
74866
+ operation,
74867
+ `targets a shape too complex for the remaining broad edge-feature budget (${Math.max(0, remaining)}).`
74195
74868
  );
74196
- return true;
74197
74869
  }
74198
74870
  function edgesToTargets(edges) {
74199
74871
  return edges.map((e) => {
@@ -74439,7 +75111,7 @@ function applyMatrixToPlacement(placement, matrix) {
74439
75111
  function applyShapeTransformToPlacement(placement, step) {
74440
75112
  return applyMatrixToPlacement(placement, shapeTransformStepMatrix2(step));
74441
75113
  }
74442
- function mapProfileToPlane(profile, sourcePlacement, targetPlane) {
75114
+ function mapProfileToPlane(profile2, sourcePlacement, targetPlane) {
74443
75115
  const sourceTransform = Transform.from(sourcePlacement.matrix);
74444
75116
  const sourceOrigin = sourceTransform.point([0, 0, 0]);
74445
75117
  const sourceU = sourceTransform.vector([1, 0, 0]);
@@ -74470,7 +75142,7 @@ function mapProfileToPlane(profile, sourcePlacement, targetPlane) {
74470
75142
  if (!nearlyEqual(Math.abs(det), 1, 1e-5)) {
74471
75143
  return { reason: "projection replay requires a rigid or mirrored in-plane basis." };
74472
75144
  }
74473
- let next = cloneProfileCompilePlan(profile);
75145
+ let next = cloneProfileCompilePlan(profile2);
74474
75146
  if (!nearlyEqual(sx, 1) || !nearlyEqual(sy, 1)) {
74475
75147
  next = appendProfileCompileTransform(next, { kind: "scale", x: sx, y: sy });
74476
75148
  }
@@ -74494,8 +75166,8 @@ function mapProfileToPlane(profile, sourcePlacement, targetPlane) {
74494
75166
  }
74495
75167
  return { profile: next };
74496
75168
  }
74497
- function buildReplayProfile(profile, sourcePlacement, targetPlane) {
74498
- return mapProfileToPlane(profile, sourcePlacement, targetPlane);
75169
+ function buildReplayProfile(profile2, sourcePlacement, targetPlane) {
75170
+ return mapProfileToPlane(profile2, sourcePlacement, targetPlane);
74499
75171
  }
74500
75172
  function circleProjectionProfile(radius, segments) {
74501
75173
  return {
@@ -74550,14 +75222,14 @@ function targetPlanePlacement(targetPlane) {
74550
75222
  };
74551
75223
  }
74552
75224
  function sphereProjectionProfileAtCenter(radius, segments, center, targetPlane) {
74553
- const profile = circleProjectionProfile(Math.abs(radius), segments);
75225
+ const profile2 = circleProjectionProfile(Math.abs(radius), segments);
74554
75226
  const delta = sub11(center, targetPlane.origin);
74555
75227
  const tx = dot11(delta, targetPlane.u);
74556
75228
  const ty = dot11(delta, targetPlane.v);
74557
75229
  if (!nearlyEqual(tx, 0) || !nearlyEqual(ty, 0)) {
74558
- profile.transforms.push({ kind: "translate", x: tx, y: ty });
75230
+ profile2.transforms.push({ kind: "translate", x: tx, y: ty });
74559
75231
  }
74560
- return profile;
75232
+ return profile2;
74561
75233
  }
74562
75234
  function pointDistance2d(a, b) {
74563
75235
  return Math.hypot(a[0] - b[0], a[1] - b[1]);
@@ -74705,7 +75377,7 @@ function transformedTorusSideProjectionReplayProfile(sourceShape, targetPlane) {
74705
75377
  const radiusDirection = normalize9(cross11(targetPlane.normal, axis));
74706
75378
  const radiusDirectionU = dot11(radiusDirection, targetPlane.u);
74707
75379
  const radiusDirectionV = dot11(radiusDirection, targetPlane.v);
74708
- const profile = {
75380
+ const profile2 = {
74709
75381
  kind: "roundedRect",
74710
75382
  width: 2 * (majorRadius + minorRadius),
74711
75383
  height: 2 * minorRadius,
@@ -74713,15 +75385,15 @@ function transformedTorusSideProjectionReplayProfile(sourceShape, targetPlane) {
74713
75385
  transforms: []
74714
75386
  };
74715
75387
  const angle = Math.atan2(radiusDirectionV, radiusDirectionU) * 180 / Math.PI;
74716
- if (!nearlyEqual(angle, 0)) profile.transforms.push({ kind: "rotate", degrees: angle });
75388
+ if (!nearlyEqual(angle, 0)) profile2.transforms.push({ kind: "rotate", degrees: angle });
74717
75389
  const center = input.transform.point([0, 0, 0]);
74718
75390
  const delta = sub11(center, targetPlane.origin);
74719
75391
  const tx = dot11(delta, targetPlane.u);
74720
75392
  const ty = dot11(delta, targetPlane.v);
74721
75393
  if (!nearlyEqual(tx, 0) || !nearlyEqual(ty, 0)) {
74722
- profile.transforms.push({ kind: "translate", x: tx, y: ty });
75394
+ profile2.transforms.push({ kind: "translate", x: tx, y: ty });
74723
75395
  }
74724
- return { profile, placement: targetPlanePlacement(targetPlane) };
75396
+ return { profile: profile2, placement: targetPlanePlacement(targetPlane) };
74725
75397
  }
74726
75398
  function transformedExtrudeProjectionInput(sourceShape) {
74727
75399
  const source = unwrapQueryOwnerPlan3(sourceShape);
@@ -74844,7 +75516,7 @@ function intervalGroupsAreStrictlySeparated2(groups) {
74844
75516
  return true;
74845
75517
  }
74846
75518
  function radialIntervalsProjectionProfile(intervals, segments) {
74847
- const profiles2 = normalizeIntervals2(intervals).map(([inner, outer]) => annulusProjectionProfile(outer, inner, segments)).filter((profile) => profile != null);
75519
+ const profiles2 = normalizeIntervals2(intervals).map(([inner, outer]) => annulusProjectionProfile(outer, inner, segments)).filter((profile2) => profile2 != null);
74848
75520
  if (profiles2.length === 0) return null;
74849
75521
  if (profiles2.length === 1) return profiles2[0];
74850
75522
  return buildBooleanProfileCompilePlan("union", profiles2);
@@ -74875,7 +75547,7 @@ function radialSectorProjectionProfile(innerRadius, outerRadius, degrees, segmen
74875
75547
  }
74876
75548
  function partialRevolveProjectionProfile(intervals, degrees, segments) {
74877
75549
  if (!Number.isFinite(degrees) || nearlyEqual(degrees, 0)) return null;
74878
- const profiles2 = normalizeIntervals2(intervals).map(([inner, outer]) => radialSectorProjectionProfile(inner, outer, degrees, segments)).filter((profile) => profile != null);
75550
+ const profiles2 = normalizeIntervals2(intervals).map(([inner, outer]) => radialSectorProjectionProfile(inner, outer, degrees, segments)).filter((profile2) => profile2 != null);
74879
75551
  if (profiles2.length === 0) return null;
74880
75552
  if (profiles2.length === 1) return profiles2[0];
74881
75553
  return buildBooleanProfileCompilePlan("union", profiles2);
@@ -75039,8 +75711,8 @@ function profileOuterProjectionLoops(plan) {
75039
75711
  if (plan.profiles.length === 0) return null;
75040
75712
  if (plan.op === "union") {
75041
75713
  loops = [];
75042
- for (const profile of plan.profiles) {
75043
- const profileLoops = profileOuterProjectionLoops(profile);
75714
+ for (const profile2 of plan.profiles) {
75715
+ const profileLoops = profileOuterProjectionLoops(profile2);
75044
75716
  if (!profileLoops) return null;
75045
75717
  loops.push(...profileLoops);
75046
75718
  }
@@ -75070,10 +75742,10 @@ function profileOuterProjectionLoops(plan) {
75070
75742
  }
75071
75743
  return transformProfileLoops(loops, plan.transforms);
75072
75744
  }
75073
- function profileStrictlyContainsCircle(profile, circle2) {
75074
- switch (profile.kind) {
75745
+ function profileStrictlyContainsCircle(profile2, circle2) {
75746
+ switch (profile2.kind) {
75075
75747
  case "circle": {
75076
- const container = circleProfileFootprint(profile);
75748
+ const container = circleProfileFootprint(profile2);
75077
75749
  if (!container) return false;
75078
75750
  const distance5 = Math.hypot(circle2.center[0] - container.center[0], circle2.center[1] - container.center[1]);
75079
75751
  return distance5 + circle2.radius < container.radius - EPS10;
@@ -75081,11 +75753,11 @@ function profileStrictlyContainsCircle(profile, circle2) {
75081
75753
  case "rect":
75082
75754
  case "polygon":
75083
75755
  case "boolean": {
75084
- const loops = profileOuterProjectionLoops(profile);
75756
+ const loops = profileOuterProjectionLoops(profile2);
75085
75757
  return loops ? loops.some((loop) => loopStrictlyContainsCircle2d(loop, circle2)) : false;
75086
75758
  }
75087
75759
  case "roundedRect": {
75088
- const container = roundedRectProfileFootprint(profile);
75760
+ const container = roundedRectProfileFootprint(profile2);
75089
75761
  return container ? roundedRectStrictlyContainsCircle2d(container, circle2) : false;
75090
75762
  }
75091
75763
  case "offset":
@@ -75093,25 +75765,25 @@ function profileStrictlyContainsCircle(profile, circle2) {
75093
75765
  case "pathProfile":
75094
75766
  return false;
75095
75767
  default:
75096
- return assertExhaustive(profile);
75768
+ return assertExhaustive(profile2);
75097
75769
  }
75098
75770
  }
75099
- function profileStrictlyContainsRoundedRect(profile, roundedRect2) {
75100
- switch (profile.kind) {
75771
+ function profileStrictlyContainsRoundedRect(profile2, roundedRect2) {
75772
+ switch (profile2.kind) {
75101
75773
  case "circle": {
75102
- const container = circleProfileFootprint(profile);
75774
+ const container = circleProfileFootprint(profile2);
75103
75775
  return container ? circleStrictlyContainsRoundedRect2d(container, roundedRect2) : false;
75104
75776
  }
75105
75777
  case "rect": {
75106
- const container = rectProfileFootprint(profile);
75778
+ const container = rectProfileFootprint(profile2);
75107
75779
  return container ? rectStrictlyContainsRoundedRect2d(container, roundedRect2) : false;
75108
75780
  }
75109
75781
  case "roundedRect": {
75110
- const container = roundedRectProfileFootprint(profile);
75782
+ const container = roundedRectProfileFootprint(profile2);
75111
75783
  return container ? roundedRectStrictlyContainsRoundedRect2d(container, roundedRect2) : false;
75112
75784
  }
75113
75785
  case "polygon": {
75114
- const loops = profileOuterProjectionLoops(profile);
75786
+ const loops = profileOuterProjectionLoops(profile2);
75115
75787
  return loops ? loops.some((loop) => loopStrictlyContainsRoundedRect2d(loop, roundedRect2)) : false;
75116
75788
  }
75117
75789
  case "boolean":
@@ -75120,23 +75792,23 @@ function profileStrictlyContainsRoundedRect(profile, roundedRect2) {
75120
75792
  case "pathProfile":
75121
75793
  return false;
75122
75794
  default:
75123
- return assertExhaustive(profile);
75795
+ return assertExhaustive(profile2);
75124
75796
  }
75125
75797
  }
75126
- function profileStrictlyContainsLoop(profile, loop) {
75127
- switch (profile.kind) {
75798
+ function profileStrictlyContainsLoop(profile2, loop) {
75799
+ switch (profile2.kind) {
75128
75800
  case "circle": {
75129
- const circle2 = circleProfileFootprint(profile);
75801
+ const circle2 = circleProfileFootprint(profile2);
75130
75802
  return circle2 ? circleStrictlyContainsLoop2d(circle2, loop) : false;
75131
75803
  }
75132
75804
  case "rect":
75133
75805
  case "polygon":
75134
75806
  case "boolean": {
75135
- const loops = profileOuterProjectionLoops(profile);
75807
+ const loops = profileOuterProjectionLoops(profile2);
75136
75808
  return loops ? loops.some((subjectLoop) => loopStrictlyContainsLoop2d(subjectLoop, loop)) : false;
75137
75809
  }
75138
75810
  case "roundedRect": {
75139
- const container = roundedRectProfileFootprint(profile);
75811
+ const container = roundedRectProfileFootprint(profile2);
75140
75812
  return container ? roundedRectStrictlyContainsLoop2d(container, loop) : false;
75141
75813
  }
75142
75814
  case "offset":
@@ -75144,7 +75816,7 @@ function profileStrictlyContainsLoop(profile, loop) {
75144
75816
  case "pathProfile":
75145
75817
  return false;
75146
75818
  default:
75147
- return assertExhaustive(profile);
75819
+ return assertExhaustive(profile2);
75148
75820
  }
75149
75821
  }
75150
75822
  function profileStrictlyContainsProfile(container, subject) {
@@ -75152,7 +75824,7 @@ function profileStrictlyContainsProfile(container, subject) {
75152
75824
  const profiles2 = booleanProfilesWithParentTransforms2(subject);
75153
75825
  if (!profiles2 || profiles2.length === 0) return false;
75154
75826
  if (subject.op === "union") {
75155
- return profiles2.every((profile) => profileStrictlyContainsProfile(container, profile));
75827
+ return profiles2.every((profile2) => profileStrictlyContainsProfile(container, profile2));
75156
75828
  }
75157
75829
  if (subject.op === "intersection") {
75158
75830
  const contained = containedIntersectionProfile2(profiles2);
@@ -75272,7 +75944,7 @@ function profileProjectionIntervalAlongDirection(plan, direction2) {
75272
75944
  const profiles2 = booleanProfilesWithParentTransforms2(plan);
75273
75945
  if (!profiles2 || profiles2.length === 0) return null;
75274
75946
  if (plan.op === "union") {
75275
- const intervals = profiles2.map((profile) => profileProjectionIntervalAlongDirection(profile, direction2));
75947
+ const intervals = profiles2.map((profile2) => profileProjectionIntervalAlongDirection(profile2, direction2));
75276
75948
  if (intervals.some((interval) => !interval)) return null;
75277
75949
  const merged = normalizeIntervals2(intervals);
75278
75950
  return merged.length === 1 ? merged[0] : null;
@@ -75313,7 +75985,7 @@ function profileSupportsDefendedMiterOffsetSideInterval(plan, delta) {
75313
75985
  const profiles2 = booleanProfilesWithParentTransforms2(plan);
75314
75986
  if (!profiles2 || profiles2.length === 0) return false;
75315
75987
  if (plan.op === "union") {
75316
- return delta >= -EPS10 && profiles2.every((profile) => profileSupportsDefendedMiterOffsetSideInterval(profile, delta));
75988
+ return delta >= -EPS10 && profiles2.every((profile2) => profileSupportsDefendedMiterOffsetSideInterval(profile2, delta));
75317
75989
  }
75318
75990
  if (plan.op === "intersection") {
75319
75991
  const contained = containedIntersectionProfile2(profiles2);
@@ -75422,7 +76094,7 @@ function miterOffsetConvexPolygonProfileProjectionIntervalAlongDirection(plan, d
75422
76094
  return miterOffsetConvexPolygonProfileProjectionIntervalAlongDirection(subject, direction2, delta);
75423
76095
  }
75424
76096
  if (plan.op !== "union" || delta < -EPS10) return null;
75425
- const intervals = profiles2.map((profile) => miterOffsetConvexPolygonProfileProjectionIntervalAlongDirection(profile, direction2, delta));
76097
+ const intervals = profiles2.map((profile2) => miterOffsetConvexPolygonProfileProjectionIntervalAlongDirection(profile2, direction2, delta));
75426
76098
  if (intervals.some((interval) => !interval)) return null;
75427
76099
  const merged = normalizeIntervals2(intervals);
75428
76100
  return merged.length === 1 ? merged[0] : null;
@@ -75462,11 +76134,11 @@ function miterOffsetConvexPolygonProfileForLocalDelta(plan, localDelta) {
75462
76134
  if (strictlyConvexPolygonOrientation(offsetPoints) !== orientation3) return null;
75463
76135
  return { kind: "polygon", points: offsetPoints, transforms: [] };
75464
76136
  }
75465
- function translatedProfileIfNeeded(profile, center) {
76137
+ function translatedProfileIfNeeded(profile2, center) {
75466
76138
  if (!nearlyEqual(center[0], 0) || !nearlyEqual(center[1], 0)) {
75467
- profile.transforms.push({ kind: "translate", x: center[0], y: center[1] });
76139
+ profile2.transforms.push({ kind: "translate", x: center[0], y: center[1] });
75468
76140
  }
75469
- return profile;
76141
+ return profile2;
75470
76142
  }
75471
76143
  function insetProfileForContainment(plan, distance5) {
75472
76144
  if (!Number.isFinite(distance5) || distance5 < -EPS10) return null;
@@ -75498,7 +76170,7 @@ function insetProfileForContainment(plan, distance5) {
75498
76170
  if (!roundedRect2 || roundedRect2.halfWidth <= distance5 + EPS10 || roundedRect2.halfHeight <= distance5 + EPS10 || roundedRect2.radius <= distance5 + EPS10) {
75499
76171
  return null;
75500
76172
  }
75501
- const profile = {
76173
+ const profile2 = {
75502
76174
  kind: "roundedRect",
75503
76175
  width: 2 * (roundedRect2.halfWidth - distance5),
75504
76176
  height: 2 * (roundedRect2.halfHeight - distance5),
@@ -75506,8 +76178,8 @@ function insetProfileForContainment(plan, distance5) {
75506
76178
  transforms: []
75507
76179
  };
75508
76180
  const angle = Math.atan2(roundedRect2.xAxis[1], roundedRect2.xAxis[0]) * 180 / Math.PI;
75509
- if (!nearlyEqual(angle, 0)) profile.transforms.push({ kind: "rotate", degrees: angle });
75510
- return translatedProfileIfNeeded(profile, roundedRect2.center);
76181
+ if (!nearlyEqual(angle, 0)) profile2.transforms.push({ kind: "rotate", degrees: angle });
76182
+ return translatedProfileIfNeeded(profile2, roundedRect2.center);
75511
76183
  }
75512
76184
  if (plan.kind === "polygon") {
75513
76185
  return miterOffsetConvexPolygonProfileForLocalDelta(plan, -distance5);
@@ -75520,8 +76192,8 @@ function expandedProfileForContainment(plan, distance5) {
75520
76192
  if (plan.kind === "boolean" && plan.op === "union") {
75521
76193
  const profiles2 = booleanProfilesWithParentTransforms2(plan);
75522
76194
  if (!profiles2 || profiles2.length === 0) return null;
75523
- const expandedProfiles = profiles2.map((profile) => expandedProfileForContainment(profile, distance5));
75524
- return expandedProfiles.some((profile) => !profile) ? null : buildBooleanProfileCompilePlan("union", expandedProfiles);
76195
+ const expandedProfiles = profiles2.map((profile2) => expandedProfileForContainment(profile2, distance5));
76196
+ return expandedProfiles.some((profile2) => !profile2) ? null : buildBooleanProfileCompilePlan("union", expandedProfiles);
75525
76197
  }
75526
76198
  if (plan.kind === "boolean" && plan.op === "intersection") {
75527
76199
  const profiles2 = booleanProfilesWithParentTransforms2(plan);
@@ -75555,7 +76227,7 @@ function expandedProfileForContainment(plan, distance5) {
75555
76227
  if (plan.kind === "roundedRect") {
75556
76228
  const roundedRect2 = roundedRectProfileFootprint(plan);
75557
76229
  if (!roundedRect2) return null;
75558
- const profile = {
76230
+ const profile2 = {
75559
76231
  kind: "roundedRect",
75560
76232
  width: 2 * (roundedRect2.halfWidth + distance5),
75561
76233
  height: 2 * (roundedRect2.halfHeight + distance5),
@@ -75563,8 +76235,8 @@ function expandedProfileForContainment(plan, distance5) {
75563
76235
  transforms: []
75564
76236
  };
75565
76237
  const angle = Math.atan2(roundedRect2.xAxis[1], roundedRect2.xAxis[0]) * 180 / Math.PI;
75566
- if (!nearlyEqual(angle, 0)) profile.transforms.push({ kind: "rotate", degrees: angle });
75567
- return translatedProfileIfNeeded(profile, roundedRect2.center);
76238
+ if (!nearlyEqual(angle, 0)) profile2.transforms.push({ kind: "rotate", degrees: angle });
76239
+ return translatedProfileIfNeeded(profile2, roundedRect2.center);
75568
76240
  }
75569
76241
  if (plan.kind === "polygon") {
75570
76242
  return miterOffsetConvexPolygonProfileForLocalDelta(plan, distance5);
@@ -75577,7 +76249,7 @@ function booleanOffsetProfileProjectionIntervalAlongDirection(plan, direction2,
75577
76249
  if (!profiles2 || profiles2.length === 0) return null;
75578
76250
  if (plan.op === "union") {
75579
76251
  if (delta < -EPS10) return null;
75580
- const intervals = profiles2.map((profile) => childInterval(profile, direction2, delta));
76252
+ const intervals = profiles2.map((profile2) => childInterval(profile2, direction2, delta));
75581
76253
  if (intervals.some((interval) => !interval)) return null;
75582
76254
  const merged = normalizeIntervals2(intervals);
75583
76255
  return merged.length === 1 ? merged[0] : null;
@@ -75655,7 +76327,7 @@ function circularOffsetProfileProjectionIntervalAlongDirection(plan, direction2,
75655
76327
  if (plan.kind === "boolean" && plan.op === "union" && delta >= -EPS10) {
75656
76328
  const profiles2 = booleanProfilesWithParentTransforms2(plan);
75657
76329
  if (!profiles2 || profiles2.length === 0) return null;
75658
- const intervals = profiles2.map((profile) => circularOffsetProfileProjectionIntervalAlongDirection(profile, direction2, delta));
76330
+ const intervals = profiles2.map((profile2) => circularOffsetProfileProjectionIntervalAlongDirection(profile2, direction2, delta));
75659
76331
  if (intervals.some((interval2) => !interval2)) return null;
75660
76332
  const merged = normalizeIntervals2(intervals);
75661
76333
  return merged.length === 1 ? merged[0] : null;
@@ -75672,7 +76344,7 @@ function miterOffsetProfileProjectionIntervalAlongCardinalDirection(plan, direct
75672
76344
  if (plan.kind === "boolean" && plan.op === "union" && delta >= -EPS10) {
75673
76345
  const profiles2 = booleanProfilesWithParentTransforms2(plan);
75674
76346
  if (!profiles2 || profiles2.length === 0) return null;
75675
- const intervals = profiles2.map((profile) => miterOffsetProfileProjectionIntervalAlongCardinalDirection(profile, direction2, delta));
76347
+ const intervals = profiles2.map((profile2) => miterOffsetProfileProjectionIntervalAlongCardinalDirection(profile2, direction2, delta));
75676
76348
  if (intervals.some((interval2) => !interval2)) return null;
75677
76349
  const merged = normalizeIntervals2(intervals);
75678
76350
  return merged.length === 1 ? merged[0] : null;
@@ -75708,8 +76380,8 @@ function radialProjectionIntervalsForLoops(loops) {
75708
76380
  function booleanProfilesWithParentTransforms2(plan) {
75709
76381
  if (plan.transforms.length === 0) return plan.profiles;
75710
76382
  const profiles2 = [];
75711
- for (const profile of plan.profiles) {
75712
- const transformed = appendProfileCompileTransforms(profile, plan.transforms);
76383
+ for (const profile2 of plan.profiles) {
76384
+ const transformed = appendProfileCompileTransforms(profile2, plan.transforms);
75713
76385
  if (!transformed) return null;
75714
76386
  profiles2.push(transformed);
75715
76387
  }
@@ -75732,8 +76404,8 @@ function radialProjectionIntervalsForRevolveProfile2(plan) {
75732
76404
  if (!profiles2) return null;
75733
76405
  if (plan.op === "union") {
75734
76406
  const intervals = [];
75735
- for (const profile of profiles2) {
75736
- const profileIntervals = radialProjectionIntervalsForRevolveProfile2(profile);
76407
+ for (const profile2 of profiles2) {
76408
+ const profileIntervals = radialProjectionIntervalsForRevolveProfile2(profile2);
75737
76409
  if (!profileIntervals) return null;
75738
76410
  intervals.push(...profileIntervals);
75739
76411
  }
@@ -75963,9 +76635,9 @@ function insetRadialIntervalsForFullRevolutionProfile2(plan, distance5) {
75963
76635
  if (plan.op === "union") {
75964
76636
  const baseIntervalGroups = [];
75965
76637
  const insetIntervals = [];
75966
- for (const profile of profiles2) {
75967
- const baseIntervals = radialProjectionIntervalsForRevolveProfile2(profile);
75968
- const profileInsetIntervals = insetRadialIntervalsForFullRevolutionProfile2(profile, distance5);
76638
+ for (const profile2 of profiles2) {
76639
+ const baseIntervals = radialProjectionIntervalsForRevolveProfile2(profile2);
76640
+ const profileInsetIntervals = insetRadialIntervalsForFullRevolutionProfile2(profile2, distance5);
75969
76641
  if (!baseIntervals || !profileInsetIntervals) return null;
75970
76642
  baseIntervalGroups.push(baseIntervals);
75971
76643
  insetIntervals.push(...profileInsetIntervals);
@@ -76010,11 +76682,11 @@ function revolveProjectionReplayContext(plan) {
76010
76682
  reason: "projection replay currently supports revolve sources only when the radial profile has finite non-negative rect/polygon/circle/rounded-rectangle outer footprints and contained difference holes."
76011
76683
  };
76012
76684
  }
76013
- const profile = isFullRevolution2(plan.degrees) ? radialIntervalsProjectionProfile(intervals, plan.segments) : partialRevolveProjectionProfile(intervals, plan.degrees, plan.segments);
76014
- if (!profile) {
76685
+ const profile2 = isFullRevolution2(plan.degrees) ? radialIntervalsProjectionProfile(intervals, plan.segments) : partialRevolveProjectionProfile(intervals, plan.degrees, plan.segments);
76686
+ if (!profile2) {
76015
76687
  return { ok: false, reason: "projection replay could not derive a non-empty revolve projection profile." };
76016
76688
  }
76017
- return { ok: true, context: defaultProjectionContext(profile) };
76689
+ return { ok: true, context: defaultProjectionContext(profile2) };
76018
76690
  }
76019
76691
  function pointOnSegment2d2(point2, a, b) {
76020
76692
  const cross12 = (point2[0] - a[0]) * (b[1] - a[1]) - (point2[1] - a[1]) * (b[0] - a[0]);
@@ -76282,8 +76954,8 @@ function nestedPolygonProjectionProfile(profiles2) {
76282
76954
  const container = checked.find((candidate) => checked.every((subject) => loopContainsLoop2d(candidate.points, subject.points)));
76283
76955
  return container ? cloneProfileCompilePlan(container) : null;
76284
76956
  }
76285
- function appendProfileCompileTransforms(profile, transforms) {
76286
- let next = cloneProfileCompilePlan(profile);
76957
+ function appendProfileCompileTransforms(profile2, transforms) {
76958
+ let next = cloneProfileCompilePlan(profile2);
76287
76959
  for (const transform of transforms) {
76288
76960
  next = appendProfileCompileTransform(next, transform);
76289
76961
  }
@@ -76346,23 +77018,23 @@ function loftProjectionReplayContext(plan) {
76346
77018
  if (plan.heights.some((height) => !Number.isFinite(height))) {
76347
77019
  return { ok: false, reason: "projection replay requires finite loft heights." };
76348
77020
  }
76349
- const profile = nestedPolygonProjectionProfile(plan.profiles);
76350
- if (!profile) {
77021
+ const profile2 = nestedPolygonProjectionProfile(plan.profiles);
77022
+ if (!profile2) {
76351
77023
  return {
76352
77024
  ok: false,
76353
77025
  reason: "projection replay currently supports loft sources only when every section has a no-transform polygon footprint and one section contains all others."
76354
77026
  };
76355
77027
  }
76356
- return { ok: true, context: defaultProjectionContext(profile) };
77028
+ return { ok: true, context: defaultProjectionContext(profile2) };
76357
77029
  }
76358
77030
  function sweepProjectionReplayContext(plan) {
76359
77031
  const transforms = verticalSweepFrameProfileTransforms(plan.path, plan.up);
76360
77032
  if (!transforms) {
76361
77033
  return { ok: false, reason: "projection replay currently supports sweep sources only for monotone vertical polyline paths." };
76362
77034
  }
76363
- const profile = appendProfileCompileTransforms(plan.profile, transforms);
76364
- if (!profile) return { ok: false, reason: "projection replay could not map the sweep profile into its vertical path frame." };
76365
- return { ok: true, context: defaultProjectionContext(profile) };
77035
+ const profile2 = appendProfileCompileTransforms(plan.profile, transforms);
77036
+ if (!profile2) return { ok: false, reason: "projection replay could not map the sweep profile into its vertical path frame." };
77037
+ return { ok: true, context: defaultProjectionContext(profile2) };
76366
77038
  }
76367
77039
  function variableSweepProjectionReplayContext(plan) {
76368
77040
  const transforms = verticalSweepFrameProfileTransforms(plan.path, plan.up);
@@ -76376,14 +77048,14 @@ function variableSweepProjectionReplayContext(plan) {
76376
77048
  if (sortedT.some((t, idx) => idx > 0 && t <= sortedT[idx - 1] + EPS10)) {
76377
77049
  return { ok: false, reason: "projection replay requires distinct variable-sweep section parameters." };
76378
77050
  }
76379
- const profile = nestedPolygonProjectionProfile(plan.sections.map((section) => section.profile));
76380
- if (!profile) {
77051
+ const profile2 = nestedPolygonProjectionProfile(plan.sections.map((section) => section.profile));
77052
+ if (!profile2) {
76381
77053
  return {
76382
77054
  ok: false,
76383
77055
  reason: "projection replay currently supports variable sweep sources only when every section has a no-transform polygon footprint and one section contains all others."
76384
77056
  };
76385
77057
  }
76386
- const transformed = appendProfileCompileTransforms(profile, transforms);
77058
+ const transformed = appendProfileCompileTransforms(profile2, transforms);
76387
77059
  if (!transformed)
76388
77060
  return { ok: false, reason: "projection replay could not map the variable-sweep profile into its vertical path frame." };
76389
77061
  return { ok: true, context: defaultProjectionContext(transformed) };
@@ -76520,8 +77192,8 @@ function transformedSphereProjectionReplayProfile(sourceShape, targetPlane) {
76520
77192
  return analyticPrimitive ? transformedSphereProjectionReplayProfile(analyticPrimitive, targetPlane) : null;
76521
77193
  }
76522
77194
  if (source.kind === "sphere") {
76523
- const profile2 = sphereProjectionProfileAtCenter(source.radius, source.segments, [0, 0, 0], targetPlane);
76524
- return { profile: profile2, placement: targetPlanePlacement(targetPlane) };
77195
+ const profile3 = sphereProjectionProfileAtCenter(source.radius, source.segments, [0, 0, 0], targetPlane);
77196
+ return { profile: profile3, placement: targetPlanePlacement(targetPlane) };
76525
77197
  }
76526
77198
  if (source.kind !== "transform") return null;
76527
77199
  const base = unwrapQueryOwnerPlan3(source.base);
@@ -76535,8 +77207,8 @@ function transformedSphereProjectionReplayProfile(sourceShape, targetPlane) {
76535
77207
  distanceScale *= next.scale;
76536
77208
  }
76537
77209
  const center = transform.point([0, 0, 0]);
76538
- const profile = sphereProjectionProfileAtCenter(base.radius * distanceScale, base.segments, center, targetPlane);
76539
- return { profile, placement: targetPlanePlacement(targetPlane) };
77210
+ const profile2 = sphereProjectionProfileAtCenter(base.radius * distanceScale, base.segments, center, targetPlane);
77211
+ return { profile: profile2, placement: targetPlanePlacement(targetPlane) };
76540
77212
  }
76541
77213
  function targetPlaneSpecificProjectionReplayContext(sourceShape, targetPlane) {
76542
77214
  const boxReplay = transformedBoxProjectionReplayProfile(sourceShape, targetPlane);
@@ -76599,9 +77271,9 @@ function targetPlaneSpecificProjectionReplayContext(sourceShape, targetPlane) {
76599
77271
  profiles2.push(replay.profile);
76600
77272
  }
76601
77273
  if (!usedTargetPlaneSpecificReplay) return null;
76602
- const profile = buildBooleanProfileCompilePlan("union", profiles2);
76603
- return profile ? {
76604
- profile,
77274
+ const profile2 = buildBooleanProfileCompilePlan("union", profiles2);
77275
+ return profile2 ? {
77276
+ profile: profile2,
76605
77277
  placement: targetPlanePlacement(targetPlane),
76606
77278
  usedTargetPlaneSpecificReplay
76607
77279
  } : null;
@@ -76719,8 +77391,8 @@ function offsetSolidFromSlicesLoftProjectionReplayContext(plan) {
76719
77391
  if (zMax <= zMin + EPS10) {
76720
77392
  return { ok: false, reason: "projection replay cannot derive a collapsed offsetSolid(Shape.fromSlices(...)) vertical span." };
76721
77393
  }
76722
- const profile = nestedPolygonProjectionProfile(sorted.map((slice) => slice.profile));
76723
- if (!profile) {
77394
+ const profile2 = nestedPolygonProjectionProfile(sorted.map((slice) => slice.profile));
77395
+ if (!profile2) {
76724
77396
  return {
76725
77397
  ok: false,
76726
77398
  reason: "projection replay currently supports offsetSolid(Shape.fromSlices(...)) lofts only when every slice has a no-transform polygon footprint and one contains all others."
@@ -76729,7 +77401,7 @@ function offsetSolidFromSlicesLoftProjectionReplayContext(plan) {
76729
77401
  return {
76730
77402
  ok: true,
76731
77403
  context: {
76732
- profile: buildOffsetProfileCompilePlan(profile, plan.thickness, "Miter"),
77404
+ profile: buildOffsetProfileCompilePlan(profile2, plan.thickness, "Miter"),
76733
77405
  placement
76734
77406
  }
76735
77407
  };
@@ -76809,8 +77481,8 @@ function offsetSolidFullRevolutionProjectionReplayContext(plan) {
76809
77481
  reason: "projection replay could not derive non-empty radial intervals for the offset revolved solid."
76810
77482
  };
76811
77483
  }
76812
- const profile = radialIntervalsProjectionProfile(offsetIntervals, source.context.plan.segments);
76813
- return profile ? { ok: true, context: { profile, placement: cloneShapeWorkplanePlacement(source.context.placement) } } : { ok: false, reason: "projection replay could not build a profile for the offset revolved solid." };
77484
+ const profile2 = radialIntervalsProjectionProfile(offsetIntervals, source.context.plan.segments);
77485
+ return profile2 ? { ok: true, context: { profile: profile2, placement: cloneShapeWorkplanePlacement(source.context.placement) } } : { ok: false, reason: "projection replay could not build a profile for the offset revolved solid." };
76814
77486
  }
76815
77487
  function offsetSolidProjectionReplayContext(plan) {
76816
77488
  if (!Number.isFinite(plan.thickness) || nearlyEqual(plan.thickness, 0)) {
@@ -76845,9 +77517,9 @@ function offsetSolidProjectionReplayContext(plan) {
76845
77517
  function holeProjectionRadius(plan) {
76846
77518
  return Math.max(plan.hole.radius, plan.hole.counterbore?.radius ?? 0, plan.hole.countersink?.radius ?? 0);
76847
77519
  }
76848
- function defaultProjectionContext(profile) {
77520
+ function defaultProjectionContext(profile2) {
76849
77521
  return {
76850
- profile: cloneProfileCompilePlan(profile),
77522
+ profile: cloneProfileCompilePlan(profile2),
76851
77523
  placement: cloneShapeWorkplanePlacement(DEFAULT_PROJECTION_PLACEMENT)
76852
77524
  };
76853
77525
  }
@@ -76874,14 +77546,14 @@ function projectThroughHoleIntoContext(base, plan) {
76874
77546
  reason: `projection replay can only absorb hole rewrites when the hole axis stays parallel to the projected source basis. ${mapped.reason ?? ""}`.trim()
76875
77547
  };
76876
77548
  }
76877
- const profile = buildBooleanProfileCompilePlan("difference", [base.profile, mapped.profile]);
76878
- if (!profile) {
77549
+ const profile2 = buildBooleanProfileCompilePlan("difference", [base.profile, mapped.profile]);
77550
+ if (!profile2) {
76879
77551
  return { ok: false, reason: "projection replay could not subtract the hole silhouette from the projected source profile." };
76880
77552
  }
76881
77553
  return {
76882
77554
  ok: true,
76883
77555
  context: {
76884
- profile,
77556
+ profile: profile2,
76885
77557
  placement: cloneShapeWorkplanePlacement(base.placement)
76886
77558
  }
76887
77559
  };
@@ -76894,14 +77566,14 @@ function projectThroughCutIntoContext(base, plan) {
76894
77566
  reason: `projection replay can only absorb cut rewrites when the cut workplane stays parallel to the projected source basis. ${mapped.reason ?? ""}`.trim()
76895
77567
  };
76896
77568
  }
76897
- const profile = buildBooleanProfileCompilePlan("difference", [base.profile, mapped.profile]);
76898
- if (!profile) {
77569
+ const profile2 = buildBooleanProfileCompilePlan("difference", [base.profile, mapped.profile]);
77570
+ if (!profile2) {
76899
77571
  return { ok: false, reason: "projection replay could not subtract the cut silhouette from the projected source profile." };
76900
77572
  }
76901
77573
  return {
76902
77574
  ok: true,
76903
77575
  context: {
76904
- profile,
77576
+ profile: profile2,
76905
77577
  placement: cloneShapeWorkplanePlacement(base.placement)
76906
77578
  }
76907
77579
  };
@@ -77028,14 +77700,14 @@ function buildProjectionReplayContext(plan) {
77028
77700
  }
77029
77701
  unionProfiles.push(reoriented.context.profile);
77030
77702
  }
77031
- const profile = buildBooleanProfileCompilePlan("union", unionProfiles);
77032
- if (!profile) {
77703
+ const profile2 = buildBooleanProfileCompilePlan("union", unionProfiles);
77704
+ if (!profile2) {
77033
77705
  return { ok: false, reason: "projection replay could not combine the projected union operands into one 2D profile." };
77034
77706
  }
77035
77707
  return {
77036
77708
  ok: true,
77037
77709
  context: {
77038
- profile,
77710
+ profile: profile2,
77039
77711
  placement: targetPlacement
77040
77712
  }
77041
77713
  };
@@ -77046,8 +77718,8 @@ function buildProjectionReplayContext(plan) {
77046
77718
  context: defaultProjectionContext(circleProjectionProfile(plan.radius, plan.segments))
77047
77719
  };
77048
77720
  case "torus": {
77049
- const profile = annulusProjectionProfile(plan.majorRadius + plan.minorRadius, plan.majorRadius - plan.minorRadius, plan.segments);
77050
- return profile ? { ok: true, context: defaultProjectionContext(profile) } : { ok: false, reason: "projection replay could not derive a non-empty torus annulus profile." };
77721
+ const profile2 = annulusProjectionProfile(plan.majorRadius + plan.minorRadius, plan.majorRadius - plan.minorRadius, plan.segments);
77722
+ return profile2 ? { ok: true, context: defaultProjectionContext(profile2) } : { ok: false, reason: "projection replay could not derive a non-empty torus annulus profile." };
77051
77723
  }
77052
77724
  case "sheetMetal":
77053
77725
  return {
@@ -79514,8 +80186,8 @@ var ConstrainedSketchBuilder = class _ConstrainedSketchBuilder {
79514
80186
  if (err2 >= 0 && err2 <= DEFAULT_TOLERANCE3 * 100) {
79515
80187
  this.syncPointsFromSession();
79516
80188
  }
79517
- } catch (error) {
79518
- if (this.strict) throw error;
80189
+ } catch (error2) {
80190
+ if (this.strict) throw error2;
79519
80191
  }
79520
80192
  return;
79521
80193
  }
@@ -79541,8 +80213,8 @@ var ConstrainedSketchBuilder = class _ConstrainedSketchBuilder {
79541
80213
  if (Number.isFinite(maxError) && maxError <= DEFAULT_TOLERANCE3 * 100) {
79542
80214
  this.syncFromDefinition(working);
79543
80215
  }
79544
- } catch (error) {
79545
- if (this.strict) throw error;
80216
+ } catch (error2) {
80217
+ if (this.strict) throw error2;
79546
80218
  }
79547
80219
  }
79548
80220
  /** Serialize a constraint for the session API (matches Rust serde format). */
@@ -82962,8 +83634,8 @@ function applyAxisRail(bounds, axis, minRail, maxRail, center) {
82962
83634
  bounds[maxKey] = center + width / 2;
82963
83635
  }
82964
83636
  }
82965
- function fitProfileToBounds(profile, target) {
82966
- const source = sketchBounds(profile);
83637
+ function fitProfileToBounds(profile2, target) {
83638
+ const source = sketchBounds(profile2);
82967
83639
  const sourceWidth = source.maxX - source.minX;
82968
83640
  const sourceDepth = source.maxY - source.minY;
82969
83641
  if (sourceWidth < LOFT_GUIDE_EPS || sourceDepth < LOFT_GUIDE_EPS) {
@@ -82971,10 +83643,10 @@ function fitProfileToBounds(profile, target) {
82971
83643
  }
82972
83644
  const sourceCenter = [(source.minX + source.maxX) / 2, (source.minY + source.maxY) / 2];
82973
83645
  const targetCenter = [(target.minX + target.maxX) / 2, (target.minY + target.maxY) / 2];
82974
- return profile.scaleAround(sourceCenter, [(target.maxX - target.minX) / sourceWidth, (target.maxY - target.minY) / sourceDepth]).translate(targetCenter[0] - sourceCenter[0], targetCenter[1] - sourceCenter[1]);
83646
+ return profile2.scaleAround(sourceCenter, [(target.maxX - target.minX) / sourceWidth, (target.maxY - target.minY) / sourceDepth]).translate(targetCenter[0] - sourceCenter[0], targetCenter[1] - sourceCenter[1]);
82975
83647
  }
82976
- function sketchBounds(profile) {
82977
- const bounds = profile.bounds();
83648
+ function sketchBounds(profile2) {
83649
+ const bounds = profile2.bounds();
82978
83650
  return { minX: bounds.min[0], maxX: bounds.max[0], minY: bounds.min[1], maxY: bounds.max[1] };
82979
83651
  }
82980
83652
  function lerpBounds(a, b, t) {
@@ -83029,9 +83701,9 @@ function fieldLoftMeshingOptions(options) {
83029
83701
  }
83030
83702
  var Loft = {
83031
83703
  /** Create a loft station from a 2D profile and an axis position. */
83032
- station(profile, position) {
83704
+ station(profile2, position) {
83033
83705
  if (!Number.isFinite(position)) throw new Error("Loft.station position must be finite");
83034
- return { profile, position };
83706
+ return { profile: profile2, position };
83035
83707
  },
83036
83708
  /**
83037
83709
  * Loft by interpolating signed-distance fields instead of matching vertices.
@@ -85670,11 +86342,11 @@ function parseSvgGeometry(svgText, options) {
85670
86342
  continue;
85671
86343
  }
85672
86344
  const selfClosing = rawTag.endsWith("/>");
85673
- const body = rawTag.slice(1, rawTag.length - (selfClosing ? 2 : 1)).trim();
85674
- if (!body) continue;
85675
- const space = body.search(/\s/);
85676
- const tag = (space < 0 ? body : body.slice(0, space)).toLowerCase();
85677
- const attrText = space < 0 ? "" : body.slice(space + 1);
86345
+ const body2 = rawTag.slice(1, rawTag.length - (selfClosing ? 2 : 1)).trim();
86346
+ if (!body2) continue;
86347
+ const space = body2.search(/\s/);
86348
+ const tag = (space < 0 ? body2 : body2.slice(0, space)).toLowerCase();
86349
+ const attrText = space < 0 ? "" : body2.slice(space + 1);
85678
86350
  const attrs = parseAttributes(attrText);
85679
86351
  const parent = stack[stack.length - 1].ctx;
85680
86352
  const mergedStyle = mergeStyle(parent.style, attrs);
@@ -85913,31 +86585,31 @@ function extractFaceProfile(rawShape, face) {
85913
86585
  function shapePocket(rawShape, face, depth, opts) {
85914
86586
  const faceRef = rawShape.face(face);
85915
86587
  const frame = resolvePlaneFrame({ face: faceRef });
85916
- let profile = extractFaceProfile(rawShape, faceRef);
86588
+ let profile2 = extractFaceProfile(rawShape, faceRef);
85917
86589
  if (opts?.inset) {
85918
- profile = sketchOffset(profile, -opts.inset, opts.join ?? "Round");
86590
+ profile2 = sketchOffset(profile2, -opts.inset, opts.join ?? "Round");
85919
86591
  } else if (opts?.scale != null && opts.scale !== 1) {
85920
- profile = profile.scale(opts.scale);
86592
+ profile2 = profile2.scale(opts.scale);
85921
86593
  }
85922
86594
  const worldToPlane = planeFrameToWorldToPlaneMatrix(frame);
85923
86595
  const planeToWorld = Transform.from(worldToPlane).inverse();
85924
86596
  const inwardShift = Transform.translation(0, 0, -depth);
85925
86597
  const toolTransform = Transform.compose(inwardShift, planeToWorld).toArray();
85926
- const tool = profile.extrude(depth).transform(toolTransform);
86598
+ const tool = profile2.extrude(depth).transform(toolTransform);
85927
86599
  return rawShape.subtract(tool);
85928
86600
  }
85929
86601
  function shapeBoss(rawShape, face, height, opts) {
85930
86602
  const faceRef = rawShape.face(face);
85931
86603
  const frame = resolvePlaneFrame({ face: faceRef });
85932
- let profile = extractFaceProfile(rawShape, faceRef);
86604
+ let profile2 = extractFaceProfile(rawShape, faceRef);
85933
86605
  if (opts?.inset) {
85934
- profile = sketchOffset(profile, -opts.inset, opts.join ?? "Round");
86606
+ profile2 = sketchOffset(profile2, -opts.inset, opts.join ?? "Round");
85935
86607
  } else if (opts?.scale != null && opts.scale !== 1) {
85936
- profile = profile.scale(opts.scale);
86608
+ profile2 = profile2.scale(opts.scale);
85937
86609
  }
85938
86610
  const worldToPlane = planeFrameToWorldToPlaneMatrix(frame);
85939
86611
  const planeToWorld = Transform.from(worldToPlane).inverse();
85940
- const tool = profile.extrude(height).transform(planeToWorld.toArray());
86612
+ const tool = profile2.extrude(height).transform(planeToWorld.toArray());
85941
86613
  return rawShape.add(tool);
85942
86614
  }
85943
86615
  Shape.prototype.pocket = function pocket(face, depth, opts) {
@@ -86318,13 +86990,62 @@ function sheetMetal(options) {
86318
86990
  return new SheetMetalPart(model);
86319
86991
  }
86320
86992
 
86993
+ // src/forge/import/sourceFrame.ts
86994
+ var SOURCE_FRAME_UP_AXES = ["+X", "-X", "+Y", "-Y", "+Z", "-Z"];
86995
+ function isRecord(value) {
86996
+ return typeof value === "object" && value !== null && !Array.isArray(value);
86997
+ }
86998
+ function sourceFrameErrorContext(importKind, fileName) {
86999
+ return `${importKind}("${fileName}")`;
87000
+ }
87001
+ function requireSourceFrameUp(importKind, fileName, sourceFrame) {
87002
+ if (sourceFrame === void 0) return null;
87003
+ const context = sourceFrameErrorContext(importKind, fileName);
87004
+ if (!isRecord(sourceFrame)) throw new Error(`${context}: sourceFrame must be an object with an "up" axis`);
87005
+ const keys = Object.keys(sourceFrame);
87006
+ if (keys.some((key) => key !== "up")) {
87007
+ throw new Error(
87008
+ `${context}: sourceFrame only supports "up"; remove unsupported keys: ${keys.filter((key) => key !== "up").join(", ")}`
87009
+ );
87010
+ }
87011
+ if (!Object.prototype.hasOwnProperty.call(sourceFrame, "up")) throw new Error(`${context}: sourceFrame.up is required`);
87012
+ const up = sourceFrame.up;
87013
+ if (typeof up !== "string" || !SOURCE_FRAME_UP_AXES.includes(up)) {
87014
+ throw new Error(`${context}: sourceFrame.up must be one of ${SOURCE_FRAME_UP_AXES.join(", ")}, got ${JSON.stringify(up)}`);
87015
+ }
87016
+ return up;
87017
+ }
87018
+ function sourceFrameTransformStep(importKind, fileName, sourceFrame) {
87019
+ const up = requireSourceFrameUp(importKind, fileName, sourceFrame);
87020
+ switch (up) {
87021
+ case null:
87022
+ case "+Z":
87023
+ return null;
87024
+ case "-Z":
87025
+ return { kind: "rotateAround", axisX: 1, axisY: 0, axisZ: 0, degrees: 180, pivotX: 0, pivotY: 0, pivotZ: 0 };
87026
+ case "+Y":
87027
+ return { kind: "rotateAround", axisX: 1, axisY: 0, axisZ: 0, degrees: 90, pivotX: 0, pivotY: 0, pivotZ: 0 };
87028
+ case "-Y":
87029
+ return { kind: "rotateAround", axisX: 1, axisY: 0, axisZ: 0, degrees: -90, pivotX: 0, pivotY: 0, pivotZ: 0 };
87030
+ case "+X":
87031
+ return { kind: "rotateAround", axisX: 0, axisY: 1, axisZ: 0, degrees: -90, pivotX: 0, pivotY: 0, pivotZ: 0 };
87032
+ case "-X":
87033
+ return { kind: "rotateAround", axisX: 0, axisY: 1, axisZ: 0, degrees: 90, pivotX: 0, pivotY: 0, pivotZ: 0 };
87034
+ }
87035
+ }
87036
+ function applySourceFrameToShapePlan(plan, importKind, fileName, sourceFrame) {
87037
+ const step = sourceFrameTransformStep(importKind, fileName, sourceFrame);
87038
+ return step ? appendShapeCompileTransform(plan, step) : plan;
87039
+ }
87040
+
86321
87041
  // src/forge/import/importStep.ts
86322
- function importStepFromBuffer(fileData, displayName = "import.step") {
86323
- return buildShapeFromCompilePlan(
86324
- createOwnedShapeCompilePlan({ kind: "importedStep", filePath: displayName, fileData }, "importStep"),
86325
- void 0,
86326
- { fidelity: "exact", sources: ["imported"] }
86327
- );
87042
+ function importStepFromBuffer(fileData, displayName = "import.step", options = {}) {
87043
+ let plan = { kind: "importedStep", filePath: displayName, fileData };
87044
+ plan = applySourceFrameToShapePlan(plan, "Import.step", displayName, options.sourceFrame);
87045
+ return buildShapeFromCompilePlan(createOwnedShapeCompilePlan(plan, "importStep"), void 0, {
87046
+ fidelity: "exact",
87047
+ sources: ["imported"]
87048
+ });
86328
87049
  }
86329
87050
 
86330
87051
  // src/forge/export/sheetStock.ts
@@ -86337,9 +87058,9 @@ function resetSheetStock() {
86337
87058
  function getCollectedSheetStock() {
86338
87059
  return collectedSheetStock;
86339
87060
  }
86340
- function normalizeMaterial(material) {
86341
- if (typeof material !== "string") return "sheet stock";
86342
- const value = material.trim();
87061
+ function normalizeMaterial(material2) {
87062
+ if (typeof material2 !== "string") return "sheet stock";
87063
+ const value = material2.trim();
86343
87064
  return value.length > 0 ? value : "sheet stock";
86344
87065
  }
86345
87066
  function sheetStock(width, height, description, opts) {
@@ -86368,14 +87089,14 @@ function sheetStock(width, height, description, opts) {
86368
87089
  }
86369
87090
 
86370
87091
  // src/forge/laserKit/joints.ts
86371
- function requireFinite11(value, name) {
87092
+ function requireFinite12(value, name) {
86372
87093
  if (!Number.isFinite(value)) {
86373
87094
  throw new Error(`${name} must be a finite number, got ${value}`);
86374
87095
  }
86375
87096
  return value;
86376
87097
  }
86377
- function requirePositive8(value, name) {
86378
- requireFinite11(value, name);
87098
+ function requirePositive9(value, name) {
87099
+ requireFinite12(value, name);
86379
87100
  if (value <= 0) {
86380
87101
  throw new Error(`${name} must be positive, got ${value}`);
86381
87102
  }
@@ -86388,10 +87109,10 @@ function requireOdd(value, name) {
86388
87109
  return value;
86389
87110
  }
86390
87111
  function fingerJointProfile(length7, thickness, options = {}) {
86391
- requirePositive8(length7, "length");
86392
- requirePositive8(thickness, "thickness");
86393
- const clearance = requireFinite11(options.clearance ?? 0, "clearance");
86394
- const kerf = requireFinite11(options.kerf ?? 0, "kerf");
87112
+ requirePositive9(length7, "length");
87113
+ requirePositive9(thickness, "thickness");
87114
+ const clearance = requireFinite12(options.clearance ?? 0, "clearance");
87115
+ const kerf = requireFinite12(options.kerf ?? 0, "kerf");
86395
87116
  const endStyle = options.endStyle ?? "full";
86396
87117
  let fingers;
86397
87118
  if (options.fingers != null) {
@@ -86400,7 +87121,7 @@ function fingerJointProfile(length7, thickness, options = {}) {
86400
87121
  fingers = Math.max(3, Math.round(length7 / (2 * thickness)) | 1);
86401
87122
  if (fingers % 2 === 0) fingers += 1;
86402
87123
  }
86403
- const fingerWidth = options.fingerWidth != null ? requirePositive8(options.fingerWidth, "fingerWidth") : length7 / (2 * fingers - 1);
87124
+ const fingerWidth = options.fingerWidth != null ? requirePositive9(options.fingerWidth, "fingerWidth") : length7 / (2 * fingers - 1);
86404
87125
  const halfKerf = kerf / 2;
86405
87126
  const evenFingers = [];
86406
87127
  const oddFingers = [];
@@ -86452,13 +87173,13 @@ function fingerJointProfile(length7, thickness, options = {}) {
86452
87173
  return { tabProfile, matingProfile, slotProfile };
86453
87174
  }
86454
87175
  function tabSlotProfile(length7, thickness, options = {}) {
86455
- requirePositive8(length7, "length");
86456
- requirePositive8(thickness, "thickness");
86457
- const clearance = requireFinite11(options.clearance ?? 0, "clearance");
86458
- const kerf = requireFinite11(options.kerf ?? 0, "kerf");
86459
- const inset = requirePositive8(options.inset ?? thickness, "inset");
86460
- const tabWidth = requirePositive8(options.tabWidth ?? 2 * thickness, "tabWidth");
86461
- const tabCountRaw = options.tabCount != null ? requireFinite11(options.tabCount, "tabCount") : length7 / (4 * thickness);
87176
+ requirePositive9(length7, "length");
87177
+ requirePositive9(thickness, "thickness");
87178
+ const clearance = requireFinite12(options.clearance ?? 0, "clearance");
87179
+ const kerf = requireFinite12(options.kerf ?? 0, "kerf");
87180
+ const inset = requirePositive9(options.inset ?? thickness, "inset");
87181
+ const tabWidth = requirePositive9(options.tabWidth ?? 2 * thickness, "tabWidth");
87182
+ const tabCountRaw = options.tabCount != null ? requireFinite12(options.tabCount, "tabCount") : length7 / (4 * thickness);
86462
87183
  const tabCount = Math.max(1, Math.round(tabCountRaw));
86463
87184
  const halfKerf = kerf / 2;
86464
87185
  const usableLength = length7 - 2 * inset;
@@ -86508,20 +87229,20 @@ function kerfCompensateSlots(sketch, kerf) {
86508
87229
  }
86509
87230
  function kerfCompensatePart(baseProfile, joints, kerf) {
86510
87231
  requireFiniteKerf(kerf);
86511
- let profile = baseProfile;
87232
+ let profile2 = baseProfile;
86512
87233
  if (joints.additions) {
86513
87234
  for (const tab of joints.additions) {
86514
87235
  const compensated = kerfCompensateTabs(tab, kerf);
86515
- profile = profile.add(compensated);
87236
+ profile2 = profile2.add(compensated);
86516
87237
  }
86517
87238
  }
86518
87239
  if (joints.subtractions) {
86519
87240
  for (const slot2 of joints.subtractions) {
86520
87241
  const compensated = kerfCompensateSlots(slot2, kerf);
86521
- profile = profile.subtract(compensated);
87242
+ profile2 = profile2.subtract(compensated);
86522
87243
  }
86523
87244
  }
86524
- return kerfCompensateOutline(profile, kerf);
87245
+ return kerfCompensateOutline(profile2, kerf);
86525
87246
  }
86526
87247
  var COMMON_KERFS_DATA = [
86527
87248
  // Plywood
@@ -86541,22 +87262,22 @@ var COMMON_KERFS_DATA = [
86541
87262
  { material: "cardboard", thickness: 3, kerf: 0.15, laserType: "CO2-40W" }
86542
87263
  ];
86543
87264
  var COMMON_KERFS = COMMON_KERFS_DATA;
86544
- function lookupKerf(material, thickness, laserType) {
87265
+ function lookupKerf(material2, thickness, laserType) {
86545
87266
  const entry = COMMON_KERFS_DATA.find(
86546
- (e) => e.material === material && e.thickness === thickness && (laserType === void 0 || e.laserType === laserType)
87267
+ (e) => e.material === material2 && e.thickness === thickness && (laserType === void 0 || e.laserType === laserType)
86547
87268
  );
86548
87269
  return entry?.kerf;
86549
87270
  }
86550
87271
 
86551
87272
  // src/forge/laserKit/flatPart.ts
86552
- function requireFinite12(value, name) {
87273
+ function requireFinite13(value, name) {
86553
87274
  if (!Number.isFinite(value)) {
86554
87275
  throw new Error(`${name} must be a finite number, got ${value}`);
86555
87276
  }
86556
87277
  return value;
86557
87278
  }
86558
- function requirePositive9(value, name) {
86559
- requireFinite12(value, name);
87279
+ function requirePositive10(value, name) {
87280
+ requireFinite13(value, name);
86560
87281
  if (value <= 0) {
86561
87282
  throw new Error(`${name} must be positive, got ${value}`);
86562
87283
  }
@@ -86580,7 +87301,7 @@ var FlatPart = class {
86580
87301
  _partNumber = 0;
86581
87302
  constructor(name, baseProfile, thickness, edges, options) {
86582
87303
  requireNonEmpty(name, "name");
86583
- requirePositive9(thickness, "thickness");
87304
+ requirePositive10(thickness, "thickness");
86584
87305
  if (baseProfile.area() <= 0) {
86585
87306
  throw new Error(`FlatPart "${name}" baseProfile must have positive area`);
86586
87307
  }
@@ -86650,14 +87371,14 @@ var FlatPart = class {
86650
87371
  return this.profile(kerf).extrude(this.thickness);
86651
87372
  }
86652
87373
  };
86653
- function positionAlongEdge(profile, edge, inward = false) {
87374
+ function positionAlongEdge(profile2, edge, inward = false) {
86654
87375
  const nx = inward ? -edge.normal[0] : edge.normal[0];
86655
87376
  const ny = inward ? -edge.normal[1] : edge.normal[1];
86656
87377
  const angle = Math.atan2(-nx, ny) * (180 / Math.PI);
86657
87378
  const edx = (edge.end[0] - edge.start[0]) / edge.length;
86658
87379
  const edy = (edge.end[1] - edge.start[1]) / edge.length;
86659
87380
  const dot12 = ny * edx + -nx * edy;
86660
- const rotated = profile.rotateAround(angle, [0, 0]);
87381
+ const rotated = profile2.rotateAround(angle, [0, 0]);
86661
87382
  if (dot12 > 0) {
86662
87383
  return rotated.translate(edge.start[0], edge.start[1]);
86663
87384
  } else {
@@ -86665,20 +87386,20 @@ function positionAlongEdge(profile, edge, inward = false) {
86665
87386
  }
86666
87387
  }
86667
87388
  function flatPanel(name, width, height, thickness, options) {
86668
- requirePositive9(width, "width");
86669
- requirePositive9(height, "height");
86670
- requirePositive9(thickness, "thickness");
86671
- const profile = rect(width, height);
87389
+ requirePositive10(width, "width");
87390
+ requirePositive10(height, "height");
87391
+ requirePositive10(thickness, "thickness");
87392
+ const profile2 = rect(width, height);
86672
87393
  const edges = /* @__PURE__ */ new Map([
86673
87394
  ["bottom", { name: "bottom", start: [0, 0], end: [width, 0], length: width, normal: [0, -1] }],
86674
87395
  ["right", { name: "right", start: [width, 0], end: [width, height], length: height, normal: [1, 0] }],
86675
87396
  ["top", { name: "top", start: [width, height], end: [0, height], length: width, normal: [0, 1] }],
86676
87397
  ["left", { name: "left", start: [0, height], end: [0, 0], length: height, normal: [-1, 0] }]
86677
87398
  ]);
86678
- return new FlatPart(name, profile, thickness, edges, options);
87399
+ return new FlatPart(name, profile2, thickness, edges, options);
86679
87400
  }
86680
- function flatPart(name, profile, thickness, edges, options) {
86681
- requirePositive9(thickness, "thickness");
87401
+ function flatPart(name, profile2, thickness, edges, options) {
87402
+ requirePositive10(thickness, "thickness");
86682
87403
  const edgeMap = /* @__PURE__ */ new Map();
86683
87404
  if (edges) {
86684
87405
  for (const [eName, e] of Object.entries(edges)) {
@@ -86693,7 +87414,7 @@ function flatPart(name, profile, thickness, edges, options) {
86693
87414
  edgeMap.set(eName, { name: eName, start: e.start, end: e.end, length: len2, normal: [nx, ny] });
86694
87415
  }
86695
87416
  }
86696
- return new FlatPart(name, profile, thickness, edgeMap, options);
87417
+ return new FlatPart(name, profile2, thickness, edgeMap, options);
86697
87418
  }
86698
87419
  function fingerJoint(partA, edgeNameA, partB, edgeNameB, options) {
86699
87420
  const edgeA = partA.edge(edgeNameA);
@@ -86793,10 +87514,10 @@ function groupByMaterial(pieces) {
86793
87514
  }
86794
87515
  return groups;
86795
87516
  }
86796
- function createEmptySheet(sheetIndex, material, sheetWidth, sheetHeight) {
87517
+ function createEmptySheet(sheetIndex, material2, sheetWidth, sheetHeight) {
86797
87518
  return {
86798
87519
  sheetIndex,
86799
- material,
87520
+ material: material2,
86800
87521
  pieces: [],
86801
87522
  sheetWidth,
86802
87523
  sheetHeight,
@@ -87160,7 +87881,7 @@ function packSingleSheetExact(pieces, sheetW, sheetH, kerf) {
87160
87881
  usedArea: bestUsedArea
87161
87882
  };
87162
87883
  }
87163
- function packMaterialGroupExact(pieces, sheetW, sheetH, material, kerf) {
87884
+ function packMaterialGroupExact(pieces, sheetW, sheetH, material2, kerf) {
87164
87885
  const sheets = [];
87165
87886
  let remainingPieces = [...pieces];
87166
87887
  while (remainingPieces.length > 0) {
@@ -87176,7 +87897,7 @@ function packMaterialGroupExact(pieces, sheetW, sheetH, material, kerf) {
87176
87897
  continue;
87177
87898
  }
87178
87899
  sheets.push({
87179
- ...createEmptySheet(sheets.length, material, sheetW, sheetH),
87900
+ ...createEmptySheet(sheets.length, material2, sheetW, sheetH),
87180
87901
  pieces: searchResult.placedPieces.map((placedPiece) => ({ ...placedPiece.packed })),
87181
87902
  usedArea: searchResult.usedArea
87182
87903
  });
@@ -87185,10 +87906,10 @@ function packMaterialGroupExact(pieces, sheetW, sheetH, material, kerf) {
87185
87906
  }
87186
87907
  return sheets;
87187
87908
  }
87188
- function packMaterialGroup(pieces, sheetW, sheetH, material, kerf) {
87909
+ function packMaterialGroup(pieces, sheetW, sheetH, material2, kerf) {
87189
87910
  const sorted = [...pieces].sort((a, b) => b.width * b.height - a.width * a.height);
87190
87911
  if (sorted.length <= EXACT_SHEET_SEARCH_MAX_PIECES) {
87191
- const sheets = packMaterialGroupExact(sorted, sheetW, sheetH, material, kerf);
87912
+ const sheets = packMaterialGroupExact(sorted, sheetW, sheetH, material2, kerf);
87192
87913
  for (const sheet of sheets) {
87193
87914
  sheet.cuts = computeSheetCutSequence(sheet);
87194
87915
  }
@@ -87304,8 +88025,8 @@ function computeCuttingLayout(entries, sheetWidth, sheetHeight, kerf = 0) {
87304
88025
  const expanded = expandPieces(entries);
87305
88026
  const groups = groupByMaterial(expanded);
87306
88027
  const allSheets = [];
87307
- for (const [material, pieces] of groups) {
87308
- const sheets = packMaterialGroup(pieces, sheetWidth, sheetHeight, material, kerf);
88028
+ for (const [material2, pieces] of groups) {
88029
+ const sheets = packMaterialGroup(pieces, sheetWidth, sheetHeight, material2, kerf);
87309
88030
  allSheets.push(...sheets);
87310
88031
  }
87311
88032
  allSheets.forEach((s, i) => {
@@ -87639,13 +88360,13 @@ function renderSummaryPage(layout, sheetWidth, sheetHeight) {
87639
88360
  arr.push(s);
87640
88361
  }
87641
88362
  cmd.push(commandSetFill([0.14, 0.14, 0.16]));
87642
- for (const [material, sheets] of byMaterial) {
88363
+ for (const [material2, sheets] of byMaterial) {
87643
88364
  const usedArea = sheets.reduce((s, sh) => s + sh.usedArea, 0);
87644
88365
  const totalArea = sheets.length * sheetWidth * sheetHeight;
87645
88366
  const waste = totalArea > 0 ? (totalArea - usedArea) / totalArea * 100 : 0;
87646
88367
  const matCuts = sheets.reduce((s, sh) => s + sh.cuts.length, 0);
87647
88368
  const matCutLen = sheets.reduce((s, sh) => s + sh.cuts.reduce((cs, c) => cs + c.lengthMm, 0), 0);
87648
- cmd.push(commandText(material, colMaterial + 4, tableY, 9));
88369
+ cmd.push(commandText(material2, colMaterial + 4, tableY, 9));
87649
88370
  cmd.push(commandText(String(sheets.length), colSheets + 4, tableY, 9));
87650
88371
  cmd.push(commandText(`${matCuts} (${formatNumber2(matCutLen)} mm)`, colCuts + 4, tableY, 9));
87651
88372
  cmd.push(commandText(formatNumber2(usedArea), colArea + 4, tableY, 9));
@@ -88225,8 +88946,8 @@ var LaserKit = class {
88225
88946
  const pieces = [];
88226
88947
  let idCounter = 0;
88227
88948
  for (const { part, qty } of this._parts) {
88228
- const profile = part.profile(this.kerf);
88229
- const bounds = getSketchBounds(profile);
88949
+ const profile2 = part.profile(this.kerf);
88950
+ const bounds = getSketchBounds(profile2);
88230
88951
  const w = bounds.maxX - bounds.minX;
88231
88952
  const h = bounds.maxY - bounds.minY;
88232
88953
  idCounter++;
@@ -88244,8 +88965,8 @@ var LaserKit = class {
88244
88965
  /** Bill of materials listing every part with dimensions. */
88245
88966
  bom() {
88246
88967
  return this._parts.map(({ part, qty }) => {
88247
- const profile = part.profile(this.kerf);
88248
- const bounds = getSketchBounds(profile);
88968
+ const profile2 = part.profile(this.kerf);
88969
+ const bounds = getSketchBounds(profile2);
88249
88970
  return {
88250
88971
  partNumber: part.partNumber,
88251
88972
  name: part.name,
@@ -88260,8 +88981,8 @@ var LaserKit = class {
88260
88981
  partSvgs() {
88261
88982
  const result = /* @__PURE__ */ new Map();
88262
88983
  for (const { part } of this._parts) {
88263
- const profile = part.profile(this.kerf);
88264
- const svg = sketchToSvg(profile, { strokeWidth: 0.1, padding: 1 });
88984
+ const profile2 = part.profile(this.kerf);
88985
+ const svg = sketchToSvg(profile2, { strokeWidth: 0.1, padding: 1 });
88265
88986
  result.set(part.name, svg);
88266
88987
  }
88267
88988
  return result;
@@ -88275,13 +88996,13 @@ var LaserKit = class {
88275
88996
  let maxCellH = 0;
88276
88997
  const partData = [];
88277
88998
  for (const { part, qty } of this._parts) {
88278
- const profile = part.profile(this.kerf);
88279
- const bounds = getSketchBounds(profile);
88999
+ const profile2 = part.profile(this.kerf);
89000
+ const bounds = getSketchBounds(profile2);
88280
89001
  const w = bounds.maxX - bounds.minX;
88281
89002
  const h = bounds.maxY - bounds.minY;
88282
89003
  maxCellW = Math.max(maxCellW, w);
88283
89004
  maxCellH = Math.max(maxCellH, h);
88284
- const polys = profile.toPolygons();
89005
+ const polys = profile2.toPolygons();
88285
89006
  const pathParts = [];
88286
89007
  for (const loop of polys) {
88287
89008
  if (loop.length < 2) continue;
@@ -88407,7 +89128,7 @@ var Laser = {
88407
89128
  * @returns A new FlatPart with the given named edges.
88408
89129
  * @category Laser Cutting
88409
89130
  */
88410
- part: (name, profile, thickness, edges, options) => flatPart(name, profile, thickness, edges, options),
89131
+ part: (name, profile2, thickness, edges, options) => flatPart(name, profile2, thickness, edges, options),
88411
89132
  /**
88412
89133
  * Connect two parts with finger joints along the named edges.
88413
89134
  *
@@ -88503,7 +89224,7 @@ var Laser = {
88503
89224
  * @returns Full kerf width in mm, or `undefined`.
88504
89225
  * @category Laser Cutting
88505
89226
  */
88506
- lookupKerf: (material, thickness, laserType) => lookupKerf(material, thickness, laserType),
89227
+ lookupKerf: (material2, thickness, laserType) => lookupKerf(material2, thickness, laserType),
88507
89228
  /**
88508
89229
  * Common full-kerf values by material, thickness, and laser type.
88509
89230
  *
@@ -89980,9 +90701,9 @@ var LOG_MAX_DEPTH = 4;
89980
90701
  var LOG_MAX_ARRAY_ITEMS = 24;
89981
90702
  var LOG_MAX_OBJECT_KEYS = 32;
89982
90703
  var LOG_NUMBER_PRECISION = 4;
89983
- function formatLogError(error) {
89984
- if (error instanceof Error) return `${error.name}: ${error.message}`;
89985
- return String(error);
90704
+ function formatLogError(error2) {
90705
+ if (error2 instanceof Error) return `${error2.name}: ${error2.message}`;
90706
+ return String(error2);
89986
90707
  }
89987
90708
  function roundLogNumber(value) {
89988
90709
  return Number(value.toFixed(LOG_NUMBER_PRECISION));
@@ -90008,28 +90729,28 @@ function summarizeShapeForLog(shape) {
90008
90729
  if (min2.length === max4.length && min2.length > 0) {
90009
90730
  summary.size = min2.map((value, index) => roundLogNumber(max4[index] - value));
90010
90731
  }
90011
- } catch (error) {
90012
- summary.bboxError = formatLogError(error);
90732
+ } catch (error2) {
90733
+ summary.bboxError = formatLogError(error2);
90013
90734
  }
90014
90735
  try {
90015
90736
  summary.volume = roundLogNumber(shape.volume());
90016
- } catch (error) {
90017
- summary.volumeError = formatLogError(error);
90737
+ } catch (error2) {
90738
+ summary.volumeError = formatLogError(error2);
90018
90739
  }
90019
90740
  try {
90020
90741
  summary.surfaceArea = roundLogNumber(shape.surfaceArea());
90021
- } catch (error) {
90022
- summary.surfaceAreaError = formatLogError(error);
90742
+ } catch (error2) {
90743
+ summary.surfaceAreaError = formatLogError(error2);
90023
90744
  }
90024
90745
  try {
90025
90746
  summary.triangles = shape.numTri();
90026
- } catch (error) {
90027
- summary.triangleError = formatLogError(error);
90747
+ } catch (error2) {
90748
+ summary.triangleError = formatLogError(error2);
90028
90749
  }
90029
90750
  try {
90030
90751
  summary.isEmpty = shape.isEmpty();
90031
- } catch (error) {
90032
- summary.isEmptyError = formatLogError(error);
90752
+ } catch (error2) {
90753
+ summary.isEmptyError = formatLogError(error2);
90033
90754
  }
90034
90755
  return summary;
90035
90756
  }
@@ -90104,8 +90825,8 @@ function formatLogArg(value) {
90104
90825
  const inspected = inspectForLog(value);
90105
90826
  if (typeof inspected === "string") return inspected;
90106
90827
  return JSON.stringify(inspected, null, 2);
90107
- } catch (error) {
90108
- return `[Log serialization failed: ${formatLogError(error)}]`;
90828
+ } catch (error2) {
90829
+ return `[Log serialization failed: ${formatLogError(error2)}]`;
90109
90830
  }
90110
90831
  }
90111
90832
  function makeSandboxConsole(collectedLogs, mirror) {
@@ -91401,6 +92122,9 @@ function attachAssemblySource(runResult, result) {
91401
92122
  }
91402
92123
  return runResult;
91403
92124
  }
92125
+ function getRunResultAssemblySource(runResult) {
92126
+ return runResult[RUN_RESULT_ASSEMBLY_SOURCE] ?? null;
92127
+ }
91404
92128
  var FORGE_RUNTIME_MODULE_SPECIFIERS = /* @__PURE__ */ new Set(["forgecad", "@forge/runtime", "@forgecad/runtime"]);
91405
92129
  var MAX_PERSISTENT_COMPILED_FILES = 256;
91406
92130
  var persistentCompiledFiles = /* @__PURE__ */ new Map();
@@ -91523,12 +92247,12 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
91523
92247
  verts: result.numVert()
91524
92248
  });
91525
92249
  return result;
91526
- } catch (error) {
92250
+ } catch (error2) {
91527
92251
  logImportTrace(_collectedLogs, fileName, scope, options, "Import.svgSketch", resolvedPath, "error", {
91528
92252
  requested: name,
91529
- error: formatLogError(error)
92253
+ error: formatLogError(error2)
91530
92254
  });
91531
- throw error;
92255
+ throw error2;
91532
92256
  }
91533
92257
  };
91534
92258
  const importDxfSketch = (name, optionsArg) => {
@@ -91550,12 +92274,12 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
91550
92274
  verts: result.numVert()
91551
92275
  });
91552
92276
  return result;
91553
- } catch (error) {
92277
+ } catch (error2) {
91554
92278
  logImportTrace(_collectedLogs, fileName, scope, options, "Import.dxfSketch", resolvedPath, "error", {
91555
92279
  requested: name,
91556
- error: formatLogError(error)
92280
+ error: formatLogError(error2)
91557
92281
  });
91558
- throw error;
92282
+ throw error2;
91559
92283
  }
91560
92284
  };
91561
92285
  const importMesh = (name, meshOptions) => {
@@ -91569,6 +92293,7 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
91569
92293
  const ext = resolvedPath.split(".").pop() ?? "";
91570
92294
  throw new Error(`Import.mesh("${name}"): unsupported format ".${ext}". Supported: .stl, .obj, .3mf`);
91571
92295
  }
92296
+ const sourceFrameStep = sourceFrameTransformStep("Import.mesh", name, meshOptions?.sourceFrame);
91572
92297
  if (!options.readBinaryFile) {
91573
92298
  throw new Error(
91574
92299
  `Import.mesh("${name}"): binary file reading is not available in this environment. Provide a readBinaryFile callback in RunScriptOptions.`
@@ -91603,6 +92328,7 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
91603
92328
  ...object ? { object } : {},
91604
92329
  fileData
91605
92330
  };
92331
+ if (sourceFrameStep) plan = appendShapeCompileTransform(plan, sourceFrameStep);
91606
92332
  if (meshOptions?.scale != null && meshOptions.scale !== 1) {
91607
92333
  const s = meshOptions.scale;
91608
92334
  if (!Number.isFinite(s) || s <= 0) {
@@ -91626,7 +92352,7 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
91626
92352
  }
91627
92353
  return centerImportedMesh(buildImportedMeshShape());
91628
92354
  };
91629
- const importStep = (name) => {
92355
+ const importStep = (name, stepOptions) => {
91630
92356
  if (typeof name !== "string" || name.trim().length === 0) {
91631
92357
  throw new Error("Import.step() requires a non-empty file path string");
91632
92358
  }
@@ -91636,13 +92362,14 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
91636
92362
  if (ext !== "step" && ext !== "stp") {
91637
92363
  throw new Error(`Import.step("${name}"): unsupported extension ".${ext}". Expected .step or .stp`);
91638
92364
  }
92365
+ sourceFrameTransformStep("Import.step", name, stepOptions?.sourceFrame);
91639
92366
  if (!options.readBinaryFile) {
91640
92367
  throw new Error(
91641
92368
  `Import.step("${name}"): binary file reading is not available in this environment. Provide a readBinaryFile callback in RunScriptOptions.`
91642
92369
  );
91643
92370
  }
91644
92371
  const fileData = options.readBinaryFile(resolvedPath);
91645
- return importStepFromBuffer(fileData, name);
92372
+ return importStepFromBuffer(fileData, name, stepOptions);
91646
92373
  };
91647
92374
  const Import = {
91648
92375
  dxfSketch: importDxfSketch,
@@ -91867,6 +92594,7 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
91867
92594
  bom,
91868
92595
  sheetStock,
91869
92596
  robotExport,
92597
+ Sim,
91870
92598
  group,
91871
92599
  ShapeGroup,
91872
92600
  console: sandboxConsole,
@@ -91983,13 +92711,13 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
91983
92711
  cached: false
91984
92712
  });
91985
92713
  return finalExports;
91986
- } catch (error) {
92714
+ } catch (error2) {
91987
92715
  options.moduleCache.delete(cacheKey3);
91988
92716
  logImportTrace(_collectedLogs, fileName, scope, options, "require", resolvedPath, "error", {
91989
92717
  requested: normalizedRequested,
91990
- error: formatLogError(error)
92718
+ error: formatLogError(error2)
91991
92719
  });
91992
- throw error;
92720
+ throw error2;
91993
92721
  } finally {
91994
92722
  restoreJointsView(savedJointsView);
91995
92723
  }
@@ -92022,13 +92750,13 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
92022
92750
  cached: false
92023
92751
  });
92024
92752
  return finalExports;
92025
- } catch (error) {
92753
+ } catch (error2) {
92026
92754
  options.moduleCache.delete(cacheKey2);
92027
92755
  logImportTrace(_collectedLogs, fileName, scope, options, "require", resolvedPath, "error", {
92028
92756
  requested: normalizedRequested,
92029
- error: formatLogError(error)
92757
+ error: formatLogError(error2)
92030
92758
  });
92031
- throw error;
92759
+ throw error2;
92032
92760
  }
92033
92761
  };
92034
92762
  const bindingNames = Object.keys(runtimeBindings).filter((name) => name !== "showLabels");
@@ -92552,8 +93280,8 @@ function runDirectCliMain(metaUrl, sourceEntryPath, run) {
92552
93280
  Promise.resolve().then(() => {
92553
93281
  setActiveBackend(CLI_DEFAULT_BACKEND);
92554
93282
  return run();
92555
- }).catch((error) => {
92556
- const message = error instanceof Error ? error.message : String(error);
93283
+ }).catch((error2) => {
93284
+ const message = error2 instanceof Error ? error2.message : String(error2);
92557
93285
  console.error(message);
92558
93286
  process.exit(1);
92559
93287
  });
@@ -92766,23 +93494,23 @@ function collectFilesRecursive(dir, root) {
92766
93494
  return result;
92767
93495
  }
92768
93496
  var MANIFEST_FILE = "forgecad.json";
92769
- function errorMessage(error) {
92770
- return error instanceof Error ? error.message : String(error);
93497
+ function errorMessage(error2) {
93498
+ return error2 instanceof Error ? error2.message : String(error2);
92771
93499
  }
92772
- function errorPath(error) {
92773
- if (!error || typeof error !== "object" || !("path" in error)) return null;
92774
- const path2 = error.path;
93500
+ function errorPath(error2) {
93501
+ if (!error2 || typeof error2 !== "object" || !("path" in error2)) return null;
93502
+ const path2 = error2.path;
92775
93503
  return typeof path2 === "string" && path2.length > 0 ? path2 : null;
92776
93504
  }
92777
- function formatProjectCollectionError(scriptPath, projectRoot, error) {
92778
- const failedPath = errorPath(error);
93505
+ function formatProjectCollectionError(scriptPath, projectRoot, error2) {
93506
+ const failedPath = errorPath(error2);
92779
93507
  const rootNote = resolve2(projectRoot) === resolve2(homedir()) ? "The inferred project root is your home directory. Move the model into a dedicated folder or put forgecad.json in the intended project folder." : "ForgeCAD scans the project root for .forge.js, .js, .svg, and .dxf files so local require/import paths work.";
92780
93508
  return [
92781
93509
  "ForgeCAD could not collect local project files.",
92782
93510
  ` Input file: ${resolve2(scriptPath)}`,
92783
93511
  ` Project root used for import discovery: ${projectRoot}`,
92784
93512
  failedPath ? ` Failed path: ${failedPath}` : null,
92785
- ` Filesystem error: ${errorMessage(error)}`,
93513
+ ` Filesystem error: ${errorMessage(error2)}`,
92786
93514
  "",
92787
93515
  rootNote,
92788
93516
  "If this root is too broad, run the command from a dedicated ForgeCAD folder or initialize the folder with `forgecad project init`."
@@ -92807,8 +93535,8 @@ function collectProjectFiles(scriptPath) {
92807
93535
  let allFiles;
92808
93536
  try {
92809
93537
  allFiles = collectFilesRecursive(root, root);
92810
- } catch (error) {
92811
- throw Object.assign(new Error(formatProjectCollectionError(absScript, root, error)), { cause: error });
93538
+ } catch (error2) {
93539
+ throw Object.assign(new Error(formatProjectCollectionError(absScript, root, error2)), { cause: error2 });
92812
93540
  }
92813
93541
  const fileName = relative(root, absScript);
92814
93542
  const readBinaryFile = (relativePath) => {
@@ -92924,10 +93652,10 @@ function inspectShapeObject(object, compiled) {
92924
93652
  summary: summarizeShapeRuntime(lowerShapeCompilePlanToShapeBackend(report.compilePlan, getManifoldWasm())),
92925
93653
  error: null
92926
93654
  };
92927
- } catch (error) {
93655
+ } catch (error2) {
92928
93656
  return {
92929
93657
  summary: null,
92930
- error: error instanceof Error ? error.message : String(error)
93658
+ error: error2 instanceof Error ? error2.message : String(error2)
92931
93659
  };
92932
93660
  }
92933
93661
  })();
@@ -92965,10 +93693,10 @@ function inspectSketchObject(object, compiled) {
92965
93693
  summary: summarizeCrossSectionRuntime(lowerProfileCompilePlanToCrossSection(compilePlan, getManifoldWasm())),
92966
93694
  error: null
92967
93695
  };
92968
- } catch (error) {
93696
+ } catch (error2) {
92969
93697
  return {
92970
93698
  summary: null,
92971
- error: error instanceof Error ? error.message : String(error)
93699
+ error: error2 instanceof Error ? error2.message : String(error2)
92972
93700
  };
92973
93701
  }
92974
93702
  })();
@@ -93102,6 +93830,10 @@ export {
93102
93830
  mapDimensionsToOwnerIds,
93103
93831
  generateReportPdf,
93104
93832
  getCollectedRobotExport,
93833
+ collectSimulationModel,
93834
+ validateSimulationModel,
93835
+ buildSimReadyManifest,
93836
+ simReadyManifestJson,
93105
93837
  Rectangle2D,
93106
93838
  filletTrackedEdge,
93107
93839
  chamferTrackedEdge,
@@ -93137,6 +93869,7 @@ export {
93137
93869
  getDesignTraceInputs,
93138
93870
  buildDesignTraceNeighborhood,
93139
93871
  formatDesignTraceAnchor,
93872
+ getRunResultAssemblySource,
93140
93873
  runScript,
93141
93874
  generateCuttingLayoutDxf,
93142
93875
  init,