forgecad 0.9.16 → 0.10.0

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 (147) hide show
  1. package/dist/assets/{AdminPage-CXvls4-J.js → AdminPage-DwYHz72L.js} +1 -1
  2. package/dist/assets/{BenchmarkPage-B27zk8xL.js → BenchmarkPage-a9_f-1US.js} +1 -1
  3. package/dist/assets/{BlogPage-CMAVvgQL.js → BlogPage-DodHpvmf.js} +1 -1
  4. package/dist/assets/{DocsPage-knf4I4h7.js → DocsPage-B5LePEuj.js} +8 -858
  5. package/dist/assets/EditorApp-QXsAISLR.js +16307 -0
  6. package/dist/assets/{EmbedViewer-D7ZGlFjx.js → EmbedViewer-DdEHGUMU.js} +2 -2
  7. package/dist/assets/{LandingPageProofDriven-CnevhTE8.js → LandingPageProofDriven-yhhOodbf.js} +1 -1
  8. package/dist/assets/{LegalPage-BPTUmqeg.js → LegalPage-5RbKRGYK.js} +1 -1
  9. package/dist/assets/{PricingPage-B0D4goG_.js → PricingPage-E3Rma7aV.js} +1 -1
  10. package/dist/assets/{SettingsPage-CFF-UgjI.js → SettingsPage-BJZcM97j.js} +1 -1
  11. package/dist/assets/{app-T0pDcSX4.js → app-DSYrDg0V.js} +733 -205
  12. package/dist/assets/cli/{render-C5pcIISc.js → render-ZMHR9HkV.js} +19 -46
  13. package/dist/assets/{constructionHistoryWorker-Ba2Hm58b.js → constructionHistoryWorker-AwMMWSxg.js} +1103 -349
  14. package/dist/assets/{evalWorker-vkx310U2.js → evalWorker-DbNs7Dkp.js} +3798 -1622
  15. package/dist/assets/{inspectWorker-BuTJDVX6.js → inspectWorker-CZsCFtQT.js} +1163 -409
  16. package/dist/assets/{jointPose-B_Cgedn9.js → jointPose-DO6mnXn_.js} +1 -1
  17. package/dist/assets/{manifold-BWgsjmAM.js → manifold-BGlQBBH9.js} +1 -1
  18. package/dist/assets/{manifold-rZexZI0G.js → manifold-BU-tJwQh.js} +1 -1
  19. package/dist/assets/{manifold-D6IFSkhH.js → manifold-fy2MV7K1.js} +2 -2
  20. package/dist/assets/{reportWorker-0AGij1Ru.js → reportWorker-DO6hcQbh.js} +7155 -2437
  21. package/dist/assets/{scalar-sampling-budget-J5cuzxT1.js → scalar-sampling-budget-o90NSNmF.js} +3940 -1742
  22. package/dist/assets/{scanProxyWorker-Vl4Wxa1y.js → scanProxyWorker-2GtDLk-R.js} +1 -1
  23. package/dist/assets/{javascript-1kQXfVaz.js → typescript-DBQ6RN5l.js} +874 -22
  24. package/dist/cli/render.html +1 -1
  25. package/dist/docs/index.html +3 -3
  26. package/dist/docs-raw/AI/usage.md +1 -1
  27. package/dist/docs-raw/CLI.md +63 -241
  28. package/dist/docs-raw/README.md +6 -0
  29. package/dist/docs-raw/component-model.md +17 -150
  30. package/dist/docs-raw/generated/assembly.md +139 -598
  31. package/dist/docs-raw/generated/concepts.md +245 -3501
  32. package/dist/docs-raw/generated/core.md +277 -1251
  33. package/dist/docs-raw/generated/curves.md +387 -1608
  34. package/dist/docs-raw/generated/legacy.md +162 -0
  35. package/dist/docs-raw/generated/lib.md +227 -85
  36. package/dist/docs-raw/generated/output.md +38 -73
  37. package/dist/docs-raw/generated/runtime-names.md +23 -23
  38. package/dist/docs-raw/generated/sdf.md +68 -284
  39. package/dist/docs-raw/generated/sheet-metal.md +68 -335
  40. package/dist/docs-raw/generated/sketch.md +240 -1161
  41. package/dist/docs-raw/generated/viewport.md +75 -316
  42. package/dist/docs-raw/generated/wood.md +21 -49
  43. package/dist/docs-raw/guides/coordinate-system.md +4 -42
  44. package/dist/docs-raw/guides/inspection-bundles.md +44 -442
  45. package/dist/docs-raw/guides/joint-design.md +18 -79
  46. package/dist/docs-raw/guides/positioning.md +21 -143
  47. package/dist/docs-raw/guides/scene-presentation.md +89 -0
  48. package/dist/docs-raw/skills/forgecad-3d-reconstruction.md +25 -111
  49. package/dist/docs-raw/skills/forgecad-blockout-model.md +20 -117
  50. package/dist/docs-raw/skills/forgecad-component-model.md +23 -107
  51. package/dist/docs-raw/skills/forgecad-high-level-spec.md +47 -155
  52. package/dist/docs-raw/skills/forgecad-image-replicator.md +26 -143
  53. package/dist/docs-raw/skills/forgecad-lld.md +19 -113
  54. package/dist/docs-raw/skills/forgecad-make-a-model.md +112 -532
  55. package/dist/docs-raw/skills/forgecad-model-grader.md +38 -108
  56. package/dist/docs-raw/skills/forgecad-prepare-prompt.md +24 -211
  57. package/dist/docs-raw/skills/forgecad-project.md +13 -131
  58. package/dist/docs-raw/skills/forgecad-reconstruction-benchmark.md +42 -134
  59. package/dist/docs-raw/skills/forgecad-render-inspect.md +27 -174
  60. package/dist/docs-raw/skills/forgecad-visual-spec.md +32 -112
  61. package/dist/docs-raw/skills/forgecad.md +19 -18
  62. package/dist/docs-raw/skills/index.md +2 -0
  63. package/dist/docs-raw/welcome.md +2 -2
  64. package/dist/index.html +1 -1
  65. package/dist/llms.txt +1 -2
  66. package/dist/sitemap.xml +13 -13
  67. package/dist-cli/{check-compiler-SYQ2PWOB.js → check-compiler-JTVBITCR.js} +1 -1
  68. package/dist-cli/{check-query-propagation-HIAGV62W.js → check-query-propagation-3FFLSMVN.js} +1 -1
  69. package/dist-cli/{chunk-SPZE3DUY.js → chunk-OAN5T4XD.js} +4412 -2212
  70. package/dist-cli/forgecad.js +507 -179
  71. package/dist-skill/CONTEXT.md +2172 -8377
  72. package/dist-skill/SKILL.md +15 -15
  73. package/dist-skill/docs/API/core/concepts.md +27 -157
  74. package/dist-skill/docs/CLI.md +63 -241
  75. package/dist-skill/docs/generated/assembly.md +138 -549
  76. package/dist-skill/docs/generated/core.md +277 -1251
  77. package/dist-skill/docs/generated/curves.md +387 -1609
  78. package/dist-skill/docs/generated/lib.md +227 -85
  79. package/dist-skill/docs/generated/output.md +38 -73
  80. package/dist-skill/docs/generated/runtime-names.md +16 -21
  81. package/dist-skill/docs/generated/sdf.md +68 -284
  82. package/dist-skill/docs/generated/sheet-metal.md +68 -335
  83. package/dist-skill/docs/generated/sketch.md +240 -1160
  84. package/dist-skill/docs/generated/viewport.md +75 -223
  85. package/dist-skill/docs/generated/wood.md +21 -49
  86. package/dist-skill/docs/guides/coordinate-system.md +4 -42
  87. package/dist-skill/docs/guides/inspection-bundles.md +44 -442
  88. package/dist-skill/docs/guides/joint-design.md +18 -79
  89. package/dist-skill/docs/guides/positioning.md +21 -143
  90. package/dist-skill/docs/guides/scene-presentation.md +89 -0
  91. package/dist-skill/docs/guides/surface-members.md +26 -0
  92. package/dist-skill/library/forgecad-3d-reconstruction/SKILL.md +23 -111
  93. package/dist-skill/library/forgecad-blockout-model/SKILL.md +18 -117
  94. package/dist-skill/library/forgecad-component-model/SKILL.md +21 -107
  95. package/dist-skill/library/forgecad-high-level-spec/SKILL.md +45 -155
  96. package/dist-skill/library/forgecad-image-replicator/SKILL.md +24 -143
  97. package/dist-skill/library/forgecad-lld/SKILL.md +17 -113
  98. package/dist-skill/library/forgecad-make-a-model/SKILL.md +110 -532
  99. package/dist-skill/library/forgecad-model-grader/SKILL.md +36 -108
  100. package/dist-skill/library/forgecad-prepare-prompt/SKILL.md +35 -224
  101. package/dist-skill/library/forgecad-prepare-prompt/references/default-profiles.md +43 -271
  102. package/dist-skill/library/forgecad-prepare-prompt/references/master-prompt.md +30 -99
  103. package/dist-skill/library/forgecad-project/SKILL.md +13 -133
  104. package/dist-skill/library/forgecad-reconstruction-benchmark/SKILL.md +29 -123
  105. package/dist-skill/library/forgecad-render-inspect/SKILL.md +25 -174
  106. package/dist-skill/library/forgecad-visual-spec/SKILL.md +30 -111
  107. package/dist-skill/website/skills/forgecad-3d-reconstruction.md +58 -0
  108. package/dist-skill/website/skills/forgecad-blockout-model.md +49 -0
  109. package/dist-skill/website/skills/forgecad-component-model.md +53 -0
  110. package/dist-skill/website/skills/forgecad-high-level-spec.md +101 -0
  111. package/dist-skill/website/skills/forgecad-image-replicator.md +63 -0
  112. package/dist-skill/website/skills/forgecad-lld.md +41 -0
  113. package/dist-skill/website/skills/forgecad-make-a-model.md +186 -0
  114. package/dist-skill/website/skills/forgecad-model-grader.md +82 -0
  115. package/dist-skill/website/skills/forgecad-prepare-prompt.md +63 -0
  116. package/dist-skill/website/skills/forgecad-project.md +26 -0
  117. package/dist-skill/website/skills/forgecad-reconstruction-benchmark.md +60 -0
  118. package/dist-skill/website/skills/forgecad-render-inspect.md +80 -0
  119. package/dist-skill/website/skills/forgecad-visual-spec.md +71 -0
  120. package/dist-skill/website/skills/forgecad.md +122 -0
  121. package/dist-skill/website/skills/index.md +26 -0
  122. package/examples/api/comparison-imported-sphere-candidate.forge.js +1 -1
  123. package/examples/api/conformal-product-ribbon.forge.js +1 -1
  124. package/examples/api/exact-sheet-shell-assembly.forge.js +1 -1
  125. package/examples/api/extrude-options.forge.js +4 -2
  126. package/examples/api/field-loft-drive-tip.forge.js +40 -0
  127. package/examples/api/guided-loft-olive-oil-bottle.forge.js +1 -1
  128. package/examples/api/highlight-debug.forge.js +10 -10
  129. package/examples/api/mesh-import-slats.forge.js +1 -1
  130. package/examples/api/real-product-curves.forge.js +1 -1
  131. package/examples/api/sculpt-box-circle-booleans.forge.js +1 -1
  132. package/examples/api/sdf-shapes.forge.js +2 -5
  133. package/examples/api/sketch-rounding-strategies.forge.js +6 -6
  134. package/examples/api/surface-member-bottle-cage.forge.js +3 -3
  135. package/examples/api/surface-member-conformal-product-ribbon.forge.js +3 -3
  136. package/examples/api/surface-member-razor-inlay.forge.js +1 -1
  137. package/examples/api/variable-sweep-test.forge.js +3 -3
  138. package/examples/mechanical/airplane-propeller.forge.js +74 -39
  139. package/examples/nurbs-surface.forge.js +1 -1
  140. package/examples/products/iphone.forge.js +1 -1
  141. package/package.json +1 -1
  142. package/dist/assets/EditorApp-BHMQlJ-D.js +0 -14686
  143. package/dist/docs-raw/guides/geometry-conventions.md +0 -52
  144. package/dist/docs-raw/guides/modeling-recipes.md +0 -78
  145. package/dist-skill/docs/guides/geometry-conventions.md +0 -52
  146. package/dist-skill/docs/guides/modeling-recipes.md +0 -78
  147. package/dist-skill/library/forgecad-visual-spec/references/prompt-template.md +0 -79
@@ -4,6 +4,7 @@ import {
4
4
  CLI_DEFAULT_BACKEND,
5
5
  COMPILER_REGRESSION_CORPUS,
6
6
  FILLET_EDGE_WORKFLOW_CODE,
7
+ Loft,
7
8
  MathUtils,
8
9
  OCCTUnsupportedError,
9
10
  Rectangle2D,
@@ -33,7 +34,6 @@ import {
33
34
  chamferTrackedEdge,
34
35
  circle2d,
35
36
  collectProjectFiles,
36
- composeChain,
37
37
  constrainedSketch,
38
38
  cylinder,
39
39
  describeFaceQueryRef,
@@ -63,6 +63,7 @@ import {
63
63
  getLastSolveTrail,
64
64
  getShapeCompilePlan,
65
65
  getShapeDimensions,
66
+ getShapeLineageToken,
66
67
  getShapePrimaryQueryOwner,
67
68
  getShapeQueryOwners,
68
69
  getShapeRuntimeBackend,
@@ -126,7 +127,7 @@ import {
126
127
  union2d,
127
128
  updateConstraintValue,
128
129
  wrapOCCTShapeBackend
129
- } from "./chunk-SPZE3DUY.js";
130
+ } from "./chunk-OAN5T4XD.js";
130
131
 
131
132
  // cli/forgecad.ts
132
133
  import { Command, Flags, Help, flush as flushOclif, handle as handleOclif, run as runOclif } from "@oclif/core";
@@ -234,6 +235,18 @@ function checkBooleanErrors() {
234
235
  /union2d\(\) argument 2: expected a Sketch, got undefined/
235
236
  );
236
237
  }
238
+ function checkLoftFieldApiContract() {
239
+ const fieldLoft = Loft.field([circle2d(4, 32), roundedRect(8, 2, 0.5)], [0, 8], { edgeLength: 0.8, maxTriangles: 5e3 });
240
+ assert(fieldLoft.volume() > 0, "Loft.field() should build a positive-volume field loft");
241
+ assert.throws(
242
+ () => Loft.field([circle2d(4, 16), roundedRect(8, 2, 0.5)], [0, 8], { maxTriangles: 0.5 }),
243
+ /Loft\.field\(\) maxTriangles must be a positive integer/
244
+ );
245
+ assert.throws(
246
+ () => Loft.field([circle2d(4, 16), roundedRect(8, 2, 0.5)], [0, 8], { edgeLength: 1.2, maxTriangles: 1 }).volume(),
247
+ /above maxTriangles=1/
248
+ );
249
+ }
237
250
  function checkEdgeFinishSubsetErrors() {
238
251
  const base = Rectangle2D.fromDimensions(-24, -16, 48, 32).extrude(18);
239
252
  const once2 = filletTrackedEdge(base, base.edge("vert-br"), 4, [-1, -1]);
@@ -340,7 +353,7 @@ function checkSculptApiContracts() {
340
353
  "Sculpt.tube() should return an SdfShape"
341
354
  );
342
355
  assert(
343
- Sculpt.path(
356
+ Sculpt.tube(
344
357
  [
345
358
  [0, 0, 0, 2],
346
359
  [12, 0, 0, 5],
@@ -348,7 +361,7 @@ function checkSculptApiContracts() {
348
361
  ],
349
362
  { blend: 2 }
350
363
  ) instanceof SdfShape,
351
- "Sculpt.path() should accept [x, y, z, radius] control points"
364
+ "Sculpt.tube() should accept [x, y, z, radius] control points"
352
365
  );
353
366
  assert(
354
367
  Sculpt.curve(
@@ -361,7 +374,7 @@ function checkSculptApiContracts() {
361
374
  ) instanceof SdfShape,
362
375
  "Sculpt.curve() should accept object control points and produce a smoothed variable sweep"
363
376
  );
364
- assert(Sculpt.circle(8, 1.5) instanceof SdfShape, "Sculpt.circle() should return a thin circular SDF shape");
377
+ assert(Sculpt.disk(8, 1.5) instanceof SdfShape, "Sculpt.disk() should return a thin circular SDF shape");
365
378
  assert(
366
379
  Sculpt.blend([Sculpt.sphere(8), Sculpt.box(12, 8, 6, { radius: 2 })], { radius: 3 }).polish("ceramic") instanceof SdfShape,
367
380
  "Sculpt.blend([...], options).polish() should stay in SDF space"
@@ -379,7 +392,7 @@ scene(Sculpt.look('gallery'));
379
392
  return Sculpt
380
393
  .blend([
381
394
  Sculpt.curve([[0, 0, 0, 2], [8, 0, 6, 4], [16, 4, 0, 2]], { segments: 5 }),
382
- Sculpt.box(10, 8, 6, { radius: 2 }).carve(Sculpt.circle(3, 8).tilt(90, 'x'), { radius: 1.2 }).at(8, 0, 0),
395
+ Sculpt.box(10, 8, 6, { radius: 2 }).carve(Sculpt.disk(3, 8).tilt(90, 'x'), { radius: 1.2 }).at(8, 0, 0),
383
396
  ], { radius: 3 })
384
397
  .polish('glass');
385
398
  `;
@@ -567,6 +580,7 @@ async function runCheckApiContractsCli() {
567
580
  checkTrackedShapeInterop();
568
581
  checkSketchBooleanForms();
569
582
  checkBooleanErrors();
583
+ checkLoftFieldApiContract();
570
584
  checkEdgeFinishSubsetErrors();
571
585
  checkSheetMetalApiContracts();
572
586
  checkSandboxBindings();
@@ -2085,7 +2099,7 @@ return [
2085
2099
  assert2.equal(facetedManifest.objects.length, 4, "Expected all segmented shapes to export as faceted objects");
2086
2100
  assert2.equal(facetedManifest.fallbacks.length, 4, "Expected faceted fallbacks for all segmented runtime-hint shapes");
2087
2101
  }
2088
- function checkBroadNativeEdgeFinishesStayOutOfCadQueryExactSubset() {
2102
+ async function checkBroadNativeEdgeFinishesStayOutOfCadQueryExactSubset() {
2089
2103
  const files = {
2090
2104
  "main.forge.js": `
2091
2105
  const base = box(30, 20, 15);
@@ -2098,7 +2112,7 @@ return [
2098
2112
  `
2099
2113
  };
2100
2114
  const previousBackend = getActiveBackend();
2101
- setActiveBackend("truck");
2115
+ await activateBackend("truck");
2102
2116
  let result = null;
2103
2117
  try {
2104
2118
  result = runScript(files["main.forge.js"], "main.forge.js", files);
@@ -2188,7 +2202,7 @@ async function runCheckBrepExportCli() {
2188
2202
  checkPlaneTrimAndSplitStayExactExportable();
2189
2203
  checkLoftAndSweepExportEndToEnd();
2190
2204
  checkSegmentedRuntimeHintsStayOutOfExactSubset();
2191
- checkBroadNativeEdgeFinishesStayOutOfCadQueryExactSubset();
2205
+ await checkBroadNativeEdgeFinishesStayOutOfCadQueryExactSubset();
2192
2206
  console.log("\u2713 BREP export invariants passed");
2193
2207
  }
2194
2208
  runDirectCliMain(import.meta.url, "cli/check-brep-export.ts", () => runCheckBrepExportCli());
@@ -5387,6 +5401,10 @@ function applyViewportJointOverridesToResult(result, overrides) {
5387
5401
  objects: result.objects.map(transformObject)
5388
5402
  };
5389
5403
  }
5404
+ function formatJointNumber(value) {
5405
+ const rounded = Number(value.toFixed(6));
5406
+ return Object.is(rounded, -0) ? "0" : String(rounded);
5407
+ }
5390
5408
 
5391
5409
  // cli/joint-overrides.ts
5392
5410
  function parseJointFlags(argv) {
@@ -5414,6 +5432,57 @@ function applyCliJointOverrides(result, overrides) {
5414
5432
  return applyViewportJointOverridesToResult(result, overrides);
5415
5433
  }
5416
5434
 
5435
+ // cli/joint-sweep.ts
5436
+ var MAX_CLI_JOINT_SWEEP_SAMPLES = 101;
5437
+ var NUMBER_PATTERN = String.raw`[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?`;
5438
+ var SWEEP_PATTERN = new RegExp(String.raw`^\s*([^=]+?)\s*=\s*(${NUMBER_PATTERN})\s*\.\.\s*(${NUMBER_PATTERN})\s*:\s*(\d+)\s*$`);
5439
+ function normalizeSweepNumber(value) {
5440
+ const rounded = Number(value.toPrecision(12));
5441
+ return Object.is(rounded, -0) ? 0 : rounded;
5442
+ }
5443
+ function sampleCliJointSweepValues(start, end, samples) {
5444
+ if (samples < 2) return [normalizeSweepNumber(start)];
5445
+ return Array.from({ length: samples }, (_, index) => normalizeSweepNumber(start + (end - start) * index / (samples - 1)));
5446
+ }
5447
+ function parseCliJointSweep(raw, flag = "--joint-sweep") {
5448
+ const match = SWEEP_PATTERN.exec(raw);
5449
+ if (!match) {
5450
+ throw new Error(`Invalid ${flag} format: "${raw}". Expected JointName=start..end:samples.`);
5451
+ }
5452
+ const joint2 = match[1].trim();
5453
+ const start = Number(match[2]);
5454
+ const end = Number(match[3]);
5455
+ const samples = Number(match[4]);
5456
+ if (!joint2) throw new Error(`${flag} joint name cannot be blank.`);
5457
+ if (!Number.isFinite(start) || !Number.isFinite(end)) throw new Error(`${flag} range values must be finite.`);
5458
+ if (start === end) throw new Error(`${flag} range must span two different values.`);
5459
+ if (!Number.isSafeInteger(samples) || samples < 2 || samples > MAX_CLI_JOINT_SWEEP_SAMPLES) {
5460
+ throw new Error(`${flag} samples must be an integer between 2 and ${MAX_CLI_JOINT_SWEEP_SAMPLES}.`);
5461
+ }
5462
+ return {
5463
+ raw,
5464
+ joint: joint2,
5465
+ start,
5466
+ end,
5467
+ samples,
5468
+ values: sampleCliJointSweepValues(start, end, samples)
5469
+ };
5470
+ }
5471
+ function describeCliJointSweep(sweep) {
5472
+ return `${sweep.joint}=${formatJointNumber(sweep.start)}..${formatJointNumber(sweep.end)}:${sweep.samples}`;
5473
+ }
5474
+ function buildCliJointSweepPoses(sweep, baseOverrides = {}) {
5475
+ return sweep.values.map((value, index) => ({
5476
+ index: index + 1,
5477
+ joint: sweep.joint,
5478
+ value,
5479
+ overrides: {
5480
+ ...baseOverrides,
5481
+ [sweep.joint]: value
5482
+ }
5483
+ }));
5484
+ }
5485
+
5417
5486
  // cli/render-cli-inputs.ts
5418
5487
  import { existsSync as existsSync2, readFileSync as readFileSync4, statSync as statSync3 } from "fs";
5419
5488
  import { homedir } from "os";
@@ -5539,7 +5608,7 @@ function computeMeshInertia(mesh, massKg) {
5539
5608
  }
5540
5609
 
5541
5610
  // src/studio/project-files/importGraph.ts
5542
- var FORGE_IMPORT_RE = /\b(?:importMesh|importStep|importSvgSketch|Import\.dxfSketch|compareWith)\s*\(\s*(?:"([^"]+)"|'([^']+)')/g;
5611
+ var FORGE_IMPORT_RE = /\b(?:importMesh|importStep|importSvgSketch|Import\.mesh|Import\.step|Import\.svgSketch|Import\.dxfSketch|compareWith)\s*\(\s*(?:"([^"]+)"|'([^']+)')/g;
5543
5612
  var REQUIRE_RE = /\brequire\s*\(\s*(?:"([^"]+)"|'([^']+)')/g;
5544
5613
  var ES_IMPORT_RE = /\bfrom\s+(?:"([^"]+)"|'([^']+)')/g;
5545
5614
  var VIRTUAL_MODULES = /* @__PURE__ */ new Set(["forgecad", "@forge/runtime", "@forgecad/runtime"]);
@@ -5556,6 +5625,9 @@ function extractImports(code) {
5556
5625
  importMesh: "forgeMesh",
5557
5626
  importStep: "forgeMesh",
5558
5627
  importSvgSketch: "forgeSvg",
5628
+ "Import.mesh": "forgeMesh",
5629
+ "Import.step": "forgeMesh",
5630
+ "Import.svgSketch": "forgeSvg",
5559
5631
  "Import.dxfSketch": "forgeDxf",
5560
5632
  compareWith: "compareWith"
5561
5633
  };
@@ -5563,7 +5635,9 @@ function extractImports(code) {
5563
5635
  const forgeRe = new RegExp(FORGE_IMPORT_RE.source, FORGE_IMPORT_RE.flags);
5564
5636
  while ((m = forgeRe.exec(code)) !== null) {
5565
5637
  const path5 = m[1] ?? m[2];
5566
- const fn = m[0].match(/\b(importMesh|importStep|importSvgSketch|Import\.dxfSketch|compareWith)/)?.[1];
5638
+ const fn = m[0].match(
5639
+ /\b(importMesh|importStep|importSvgSketch|Import\.mesh|Import\.step|Import\.svgSketch|Import\.dxfSketch|compareWith)/
5640
+ )?.[1];
5567
5641
  add2(path5, kindMap[fn] ?? "forgeMesh");
5568
5642
  }
5569
5643
  const reqRe = new RegExp(REQUIRE_RE.source, REQUIRE_RE.flags);
@@ -5673,7 +5747,7 @@ function replaceCliInputExtension(path5, newExt) {
5673
5747
  return path5 + newExt;
5674
5748
  }
5675
5749
  function buildDirectCadImportScript(fileName, kind) {
5676
- const importFn = kind === "step" ? "importStep" : "importMesh";
5750
+ const importFn = kind === "step" ? "Import.step" : "Import.mesh";
5677
5751
  const importOptions = kind === "mesh" && extname(fileName).toLowerCase() === ".3mf" ? ", { separateObjects: true }" : "";
5678
5752
  const objectName = basename3(fileName);
5679
5753
  return [
@@ -7915,6 +7989,7 @@ Options:
7915
7989
  --param <Key=Value> Override a script parameter value. Repeatable.
7916
7990
  -p <Key=Value> Shorthand for --param
7917
7991
  --joint <JointName=Value> Override a Motion tab joint value. Repeatable.
7992
+ --joint-sweep <Joint=-10..10:5> Sweep one Motion tab joint for fit interference evidence.
7918
7993
  --size <px> Image size in pixels (default: ${DEFAULT_SIZE})
7919
7994
  --quality <default|live|high> Mesh/render quality (default: default)
7920
7995
  --render-style <${RENDER_STYLE_LABEL}>
@@ -7992,6 +8067,7 @@ function parseInspectCli(argv, config = {}) {
7992
8067
  let cutawayPlaneSource = null;
7993
8068
  const paramOverrides = {};
7994
8069
  const jointOverrides = {};
8070
+ let jointSweep = null;
7995
8071
  const setCutawayNormal = (normal, source) => {
7996
8072
  if (!cutaway) throw new Error(`${source} only works with cutaway inspection.`);
7997
8073
  if (cutawayPlaneSource) {
@@ -8012,6 +8088,12 @@ function parseInspectCli(argv, config = {}) {
8012
8088
  i += 1;
8013
8089
  continue;
8014
8090
  }
8091
+ if (arg === "--joint-sweep") {
8092
+ if (jointSweep) throw new Error("Pass --joint-sweep only once.");
8093
+ jointSweep = parseCliJointSweep(readValue2(argv, i, arg), arg);
8094
+ i += 1;
8095
+ continue;
8096
+ }
8015
8097
  if (arg === "--output" || arg === "--out") {
8016
8098
  flagOutputDir = readValue2(argv, i, arg);
8017
8099
  i += 1;
@@ -8301,6 +8383,12 @@ function parseInspectCli(argv, config = {}) {
8301
8383
  if (hasCompareOptions && !channels.includes("comparison")) {
8302
8384
  throw new Error("Comparison options only work with `forgecad inspect compare overlay`.");
8303
8385
  }
8386
+ if (jointSweep && !channels.includes("collisions")) {
8387
+ throw new Error("--joint-sweep only works with `forgecad inspect fit interference`.");
8388
+ }
8389
+ if (jointSweep && Object.prototype.hasOwnProperty.call(jointOverrides, jointSweep.joint)) {
8390
+ throw new Error(`Pass either --joint ${jointSweep.joint}=... or --joint-sweep ${jointSweep.raw}, not both.`);
8391
+ }
8304
8392
  if (sectionOptionsUsed && !channels.includes("section")) {
8305
8393
  throw new Error("Section options only work with `forgecad inspect sections`.");
8306
8394
  }
@@ -8375,6 +8463,7 @@ function parseInspectCli(argv, config = {}) {
8375
8463
  cutaway,
8376
8464
  paramOverrides,
8377
8465
  jointOverrides,
8466
+ jointSweep,
8378
8467
  comparison: hasCompareOptions ? {
8379
8468
  compareWith,
8380
8469
  align: compareAlign,
@@ -8623,6 +8712,70 @@ function buildViewPathEntries(pathsByView) {
8623
8712
  ])
8624
8713
  );
8625
8714
  }
8715
+ function collisionSweepPoseKey(pose) {
8716
+ return `pose-${String(pose.index).padStart(3, "0")}`;
8717
+ }
8718
+ function buildCollisionSweepSummary(options, poseResults) {
8719
+ const sweep = options.jointSweep;
8720
+ const poses = poseResults.map(({ pose, result }) => {
8721
+ const collisions = result.collisions ?? {};
8722
+ return {
8723
+ index: pose.index,
8724
+ joint: pose.joint,
8725
+ value: pose.value,
8726
+ overrides: pose.overrides,
8727
+ collisionCount: collisions.collisionCount ?? 0,
8728
+ candidatePairCount: collisions.candidatePairCount ?? 0,
8729
+ testedPairCount: collisions.testedPairCount ?? 0,
8730
+ skippedPairCount: collisions.skippedPairCount ?? 0,
8731
+ warnings: collisions.warnings ?? [],
8732
+ collisions: collisions.collisions ?? [],
8733
+ views: collisions.views ?? {}
8734
+ };
8735
+ });
8736
+ const worstCollisions = poses.flatMap(
8737
+ (pose) => pose.collisions.map((collision) => ({
8738
+ poseIndex: pose.index,
8739
+ joint: pose.joint,
8740
+ value: pose.value,
8741
+ collision
8742
+ }))
8743
+ ).sort((a, b) => (b.collision.overlapVolume ?? 0) - (a.collision.overlapVolume ?? 0)).slice(0, 10);
8744
+ const maxCollisionCount = poses.reduce((max, pose) => Math.max(max, pose.collisionCount), 0);
8745
+ const maxOverlapVolume = worstCollisions[0]?.collision?.overlapVolume ?? 0;
8746
+ return {
8747
+ method: "cli-joint-sweep-v1",
8748
+ status: maxCollisionCount > 0 ? "fail" : "pass",
8749
+ expression: describeCliJointSweep(sweep),
8750
+ rawExpression: sweep.raw,
8751
+ joint: sweep.joint,
8752
+ start: sweep.start,
8753
+ end: sweep.end,
8754
+ sampleCount: sweep.samples,
8755
+ values: sweep.values,
8756
+ baseOverrides: options.jointOverrides ?? {},
8757
+ poseCount: poses.length,
8758
+ collidingPoseCount: poses.filter((pose) => pose.collisionCount > 0).length,
8759
+ maxCollisionCount,
8760
+ maxOverlapVolume,
8761
+ worstCollisions,
8762
+ warnings: Array.from(new Set(poses.flatMap((pose) => pose.warnings))),
8763
+ poses
8764
+ };
8765
+ }
8766
+ function buildCollisionSweepManifest(sweep, emittedSweepPaths) {
8767
+ if (!sweep) return null;
8768
+ return {
8769
+ ...sweep,
8770
+ poses: sweep.poses.map((pose) => {
8771
+ const { views: _rawViews, ...rest } = pose;
8772
+ return {
8773
+ ...rest,
8774
+ views: buildViewPathEntries(emittedSweepPaths?.[collisionSweepPoseKey(pose)])
8775
+ };
8776
+ })
8777
+ };
8778
+ }
8626
8779
  function collectInspectViewNames(emittedPaths) {
8627
8780
  const names = [];
8628
8781
  const add2 = (value) => {
@@ -8939,11 +9092,14 @@ function buildInspectManifest({ options, result, scriptPath, projectRoot, emitte
8939
9092
  };
8940
9093
  }
8941
9094
  if (emittedPaths.collisions) {
9095
+ const jointSweep = buildCollisionSweepManifest(result.collisions?.jointSweep, emittedPaths.collisionSweep);
9096
+ const collisionStatus = jointSweep ? (result.collisions?.collisionCount ?? 0) > 0 || jointSweep.status === "fail" ? "fail" : "pass" : null;
8942
9097
  evidence.collisions = {
8943
9098
  format: "png",
8944
9099
  encoding: "rgb8-collision-highlight",
8945
9100
  backgroundColor: [0, 0, 0],
8946
9101
  decode: "source objects are translucent ghost geometry; boolean intersection volumes use per-finding collisions[].color palette entries",
9102
+ ...collisionStatus ? { status: collisionStatus } : {},
8947
9103
  method: result.collisions?.method,
8948
9104
  options: result.collisions?.options,
8949
9105
  broadphase: result.collisions?.broadphase,
@@ -8960,6 +9116,7 @@ function buildInspectManifest({ options, result, scriptPath, projectRoot, emitte
8960
9116
  objects: result.collisions?.objects ?? [],
8961
9117
  collisions: result.collisions?.collisions ?? [],
8962
9118
  warnings: result.collisions?.warnings ?? [],
9119
+ ...jointSweep ? { jointSweep } : {},
8963
9120
  views: buildViewPathEntries(emittedPaths.collisions)
8964
9121
  };
8965
9122
  }
@@ -9493,81 +9650,85 @@ async function runInspectBundleCli(argv, config) {
9493
9650
  await page.goto(url, { waitUntil: "networkidle0", timeout: 15e3 });
9494
9651
  await page.waitForFunction("window.__forgeReady === true", { timeout: RENDER_READY_TIMEOUT_MS });
9495
9652
  await page.exposeFunction("__forgeInspectProgress", logInspectProgress);
9496
- const result = await page.evaluate(
9497
- (scriptCode, files, scriptName, renderOptions) => {
9653
+ const baseRenderOptions = {
9654
+ cameras: options.cameras,
9655
+ size: options.size,
9656
+ quality: options.quality,
9657
+ binaryFiles,
9658
+ activeBackend,
9659
+ cameraSpec: options.cameraSpec || null,
9660
+ viewName: options.viewName || null,
9661
+ sceneSpec: options.sceneSpec || null,
9662
+ background: options.background || null,
9663
+ focus: options.focus || null,
9664
+ hide: options.hide || null,
9665
+ renderStyle: options.renderStyle,
9666
+ edges: options.edges,
9667
+ paramOverrides: options.paramOverrides
9668
+ };
9669
+ const renderInspectInBrowser = (renderOptions) => page.evaluate(
9670
+ (scriptCode, files, scriptName, browserRenderOptions) => {
9498
9671
  return window.__forgeRender(scriptCode, {
9499
- cameras: renderOptions.cameras,
9500
- size: renderOptions.size,
9501
- quality: renderOptions.quality,
9502
- channels: renderOptions.channels,
9672
+ cameras: browserRenderOptions.cameras,
9673
+ size: browserRenderOptions.size,
9674
+ quality: browserRenderOptions.quality,
9675
+ channels: browserRenderOptions.channels,
9503
9676
  allFiles: files,
9504
9677
  fileName: scriptName,
9505
- binaryFiles: renderOptions.binaryFiles,
9506
- activeBackend: renderOptions.activeBackend,
9507
- cameraSpec: renderOptions.cameraSpec,
9508
- viewName: renderOptions.viewName,
9509
- sceneSpec: renderOptions.sceneSpec,
9510
- background: renderOptions.background,
9511
- focus: renderOptions.focus,
9512
- hide: renderOptions.hide,
9513
- thickness: renderOptions.thickness,
9514
- roughness: renderOptions.roughness,
9515
- sectionPlan: renderOptions.sectionPlan,
9516
- cutaway: renderOptions.cutaway,
9517
- comparison: renderOptions.comparison,
9678
+ binaryFiles: browserRenderOptions.binaryFiles,
9679
+ activeBackend: browserRenderOptions.activeBackend,
9680
+ cameraSpec: browserRenderOptions.cameraSpec,
9681
+ viewName: browserRenderOptions.viewName,
9682
+ sceneSpec: browserRenderOptions.sceneSpec,
9683
+ background: browserRenderOptions.background,
9684
+ focus: browserRenderOptions.focus,
9685
+ hide: browserRenderOptions.hide,
9686
+ thickness: browserRenderOptions.thickness,
9687
+ roughness: browserRenderOptions.roughness,
9688
+ sectionPlan: browserRenderOptions.sectionPlan,
9689
+ cutaway: browserRenderOptions.cutaway,
9690
+ comparison: browserRenderOptions.comparison,
9518
9691
  progress: "inspect",
9519
9692
  renderMode: "solid",
9520
- edges: renderOptions.edges,
9521
- renderStyle: renderOptions.renderStyle,
9693
+ edges: browserRenderOptions.edges,
9694
+ renderStyle: browserRenderOptions.renderStyle,
9522
9695
  respectAuthoredSceneStyle: false,
9523
- paramOverrides: renderOptions.paramOverrides,
9524
- jointOverrides: renderOptions.jointOverrides
9696
+ paramOverrides: browserRenderOptions.paramOverrides,
9697
+ jointOverrides: browserRenderOptions.jointOverrides
9525
9698
  });
9526
9699
  },
9527
9700
  input.code,
9528
9701
  input.allFiles,
9529
9702
  input.fileName,
9530
- {
9531
- cameras: options.cameras,
9532
- size: options.size,
9533
- quality: options.quality,
9534
- channels: options.channels,
9535
- binaryFiles,
9536
- activeBackend,
9537
- cameraSpec: options.cameraSpec || null,
9538
- viewName: options.viewName || null,
9539
- sceneSpec: options.sceneSpec || null,
9540
- background: options.background || null,
9541
- focus: options.focus || null,
9542
- hide: options.hide || null,
9543
- thickness: options.thickness,
9544
- roughness: options.roughness,
9545
- sectionPlan: options.sectionPlan,
9546
- cutaway: options.cutaway,
9547
- renderStyle: options.renderStyle,
9548
- edges: options.edges,
9549
- paramOverrides: options.paramOverrides,
9550
- jointOverrides: options.jointOverrides,
9551
- comparison: comparisonInput ? {
9552
- code: comparisonInput.code,
9553
- allFiles: comparisonInput.allFiles,
9554
- fileName: comparisonInput.fileName,
9555
- binaryFiles: comparisonBinaryFiles,
9556
- activeBackend: comparisonActiveBackend,
9557
- displayPath: options.comparison.compareWith,
9558
- sourcePath: comparisonInput.sourcePath,
9559
- align: options.comparison.align,
9560
- view: options.comparison.view,
9561
- samples: options.comparison.samples,
9562
- toleranceMm: options.comparison.toleranceMm
9563
- } : options.comparison ? {
9564
- align: options.comparison.align,
9565
- view: options.comparison.view,
9566
- samples: options.comparison.samples,
9567
- toleranceMm: options.comparison.toleranceMm
9568
- } : null
9569
- }
9703
+ renderOptions
9570
9704
  );
9705
+ const result = await renderInspectInBrowser({
9706
+ ...baseRenderOptions,
9707
+ channels: options.channels,
9708
+ thickness: options.thickness,
9709
+ roughness: options.roughness,
9710
+ sectionPlan: options.sectionPlan,
9711
+ cutaway: options.cutaway,
9712
+ jointOverrides: options.jointOverrides,
9713
+ comparison: comparisonInput ? {
9714
+ code: comparisonInput.code,
9715
+ allFiles: comparisonInput.allFiles,
9716
+ fileName: comparisonInput.fileName,
9717
+ binaryFiles: comparisonBinaryFiles,
9718
+ activeBackend: comparisonActiveBackend,
9719
+ displayPath: options.comparison.compareWith,
9720
+ sourcePath: comparisonInput.sourcePath,
9721
+ align: options.comparison.align,
9722
+ view: options.comparison.view,
9723
+ samples: options.comparison.samples,
9724
+ toleranceMm: options.comparison.toleranceMm
9725
+ } : options.comparison ? {
9726
+ align: options.comparison.align,
9727
+ view: options.comparison.view,
9728
+ samples: options.comparison.samples,
9729
+ toleranceMm: options.comparison.toleranceMm
9730
+ } : null
9731
+ });
9571
9732
  if (!result.ok) {
9572
9733
  console.error("Script error:", result.error);
9573
9734
  process.exit(1);
@@ -9605,6 +9766,33 @@ async function runInspectBundleCli(argv, config) {
9605
9766
  }
9606
9767
  }
9607
9768
  }
9769
+ if (options.jointSweep && requested.has("collisions")) {
9770
+ const sweepPoses = buildCliJointSweepPoses(options.jointSweep, options.jointOverrides);
9771
+ const poseResults = [];
9772
+ console.log(`[inspect] Sweeping joint: ${describeCliJointSweep(options.jointSweep)}`);
9773
+ for (const pose of sweepPoses) {
9774
+ console.log(`[inspect] Sweep pose ${pose.index}/${sweepPoses.length}: ${pose.joint}=${pose.value}`);
9775
+ const sweepResult = await renderInspectInBrowser({
9776
+ ...baseRenderOptions,
9777
+ channels: ["collisions"],
9778
+ thickness: {},
9779
+ roughness: {},
9780
+ sectionPlan: null,
9781
+ cutaway: null,
9782
+ comparison: null,
9783
+ jointOverrides: pose.overrides
9784
+ });
9785
+ if (!sweepResult.ok) {
9786
+ console.error("Script error:", sweepResult.error);
9787
+ process.exit(1);
9788
+ }
9789
+ poseResults.push({ pose, result: sweepResult });
9790
+ }
9791
+ result.collisions = {
9792
+ ...result.collisions ?? {},
9793
+ jointSweep: buildCollisionSweepSummary(options, poseResults)
9794
+ };
9795
+ }
9608
9796
  const emittedPaths = {};
9609
9797
  const writeViewChannel = async (channel, source, getPng = (entry) => entry) => {
9610
9798
  const label = displayInspectChannel(channel, options);
@@ -9678,6 +9866,25 @@ async function runInspectBundleCli(argv, config) {
9678
9866
  }
9679
9867
  if (requested.has("collisions")) {
9680
9868
  await writeViewChannel("collisions", result.collisions?.views);
9869
+ if (result.collisions?.jointSweep) {
9870
+ console.log("[inspect] Writing fit interference joint sweep files...");
9871
+ emittedPaths.collisionSweep = {};
9872
+ for (const pose of result.collisions.jointSweep.poses) {
9873
+ const poseKey = collisionSweepPoseKey(pose);
9874
+ emittedPaths.collisionSweep[poseKey] = {};
9875
+ const usedFileStems = /* @__PURE__ */ new Set();
9876
+ for (const [view, png] of Object.entries(pose.views ?? {})) {
9877
+ if (!png) throw new Error(`Renderer did not emit required joint sweep collision view: ${poseKey}/${view}`);
9878
+ const relPath = toPortablePath(
9879
+ join4("evidence", "collisions", "sweep", poseKey, `${sanitizeViewFileStem(view, usedFileStems)}.png`)
9880
+ );
9881
+ await mkdir(dirname(join4(tempDir, relPath)), { recursive: true });
9882
+ await writeFile(join4(tempDir, relPath), pngBufferFromDataUrl(png));
9883
+ emittedPaths.collisionSweep[poseKey][view] = relPath;
9884
+ }
9885
+ }
9886
+ console.log("[inspect] Wrote fit interference joint sweep files.");
9887
+ }
9681
9888
  }
9682
9889
  if (requested.has("thickness")) {
9683
9890
  await writeViewChannel("thickness", result.thickness?.views);
@@ -9734,6 +9941,15 @@ async function runInspectBundleCli(argv, config) {
9734
9941
  printFramingWarnings(result.framing);
9735
9942
  console.log(` Size: ${sz[0]} \xD7 ${sz[1]} \xD7 ${sz[2]} mm`);
9736
9943
  console.log(` Volume: ${result.volume.toFixed(1)} mm\xB3`);
9944
+ if (manifest.evidence?.collisions?.jointSweep) {
9945
+ const sweep = manifest.evidence.collisions.jointSweep;
9946
+ console.log(
9947
+ ` Joint sweep: ${sweep.status} (${sweep.collidingPoseCount}/${sweep.poseCount} colliding pose(s), max ${sweep.maxCollisionCount} collision(s))`
9948
+ );
9949
+ }
9950
+ if (options.jointSweep && manifest.evidence?.collisions?.status === "fail") {
9951
+ process.exitCode = 1;
9952
+ }
9737
9953
  } finally {
9738
9954
  await browser.close();
9739
9955
  }
@@ -9813,7 +10029,7 @@ function writeParentImportFixture(root, withManifest) {
9813
10029
  resolve9(root, "part/finger.forge.js"),
9814
10030
  `
9815
10031
  const pose = Param.choice("Static Pose", "open-display", ["open-display", "curl"]);
9816
- const showStaticTendons = boolParam("Show Static Tendons", true);
10032
+ const showStaticTendons = Param.bool("Show Static Tendons", true);
9817
10033
 
9818
10034
  return box(pose === "curl" ? 8 : 10, showStaticTendons ? 6 : 4, 3);
9819
10035
  `,
@@ -12261,15 +12477,29 @@ function pairKey(a, b) {
12261
12477
  }
12262
12478
  function resolveIntentionalOverlaps(allowances, entries, findings) {
12263
12479
  const entryByShape = /* @__PURE__ */ new Map();
12264
- for (const entry of entries) entryByShape.set(entry.shape, entry);
12480
+ const entriesByLineage = /* @__PURE__ */ new Map();
12481
+ for (const entry of entries) {
12482
+ entryByShape.set(entry.shape, entry);
12483
+ const token = getShapeLineageToken(entry.shape);
12484
+ const lineageEntries = entriesByLineage.get(token) ?? [];
12485
+ lineageEntries.push(entry);
12486
+ entriesByLineage.set(token, lineageEntries);
12487
+ }
12265
12488
  const findingByPair = /* @__PURE__ */ new Map();
12266
12489
  for (const finding2 of findings) {
12267
12490
  findingByPair.set(pairKey(finding2.sourceId, finding2.targetId), finding2);
12268
12491
  }
12492
+ const resolveVisibleEntry = (shape) => {
12493
+ const exact = entryByShape.get(shape);
12494
+ if (exact) return exact;
12495
+ if (!shape || typeof shape !== "object") return void 0;
12496
+ const lineageEntries = entriesByLineage.get(getShapeLineageToken(shape)) ?? [];
12497
+ return lineageEntries.length === 1 ? lineageEntries[0] : void 0;
12498
+ };
12269
12499
  const allowedCollisionKeys = /* @__PURE__ */ new Set();
12270
12500
  const reports = allowances.map((allowance) => {
12271
- const source = entryByShape.get(allowance.a);
12272
- const target = entryByShape.get(allowance.b);
12501
+ const source = resolveVisibleEntry(allowance.a);
12502
+ const target = resolveVisibleEntry(allowance.b);
12273
12503
  const base = {
12274
12504
  label: allowance.label,
12275
12505
  reason: allowance.reason,
@@ -16737,7 +16967,51 @@ var SKILL_SUPPRESSED_RUNTIME_NAMES = /* @__PURE__ */ new Set([
16737
16967
  "nurbs3d",
16738
16968
  "spline3d",
16739
16969
  "transitionCurve",
16740
- "transitionSurface"
16970
+ "transitionSurface",
16971
+ // Lean-API sweep (2026-06): soft-deprecated globals (legacy.md) and
16972
+ // hard-removed throwing stubs. Still reserved in human-authored scripts,
16973
+ // never taught to agents.
16974
+ "assemblyInstructions",
16975
+ "assemblyPreview",
16976
+ "bomToCsv",
16977
+ "boolParam",
16978
+ "chamferTrackedEdge",
16979
+ "choiceParam",
16980
+ "circle",
16981
+ "combine",
16982
+ "COMMON_KERFS",
16983
+ "composeChain",
16984
+ "Constraint",
16985
+ "Counterbore",
16986
+ "degrees",
16987
+ "dimLine",
16988
+ "filletCorners",
16989
+ "filletTrackedEdge",
16990
+ "fingerJoint",
16991
+ "flatPanel",
16992
+ "flatPart",
16993
+ "formatInstructions",
16994
+ "highlight",
16995
+ "importMesh",
16996
+ "importStep",
16997
+ "importSvgSketch",
16998
+ "jointsView",
16999
+ "laserKit",
17000
+ "line",
17001
+ "listParam",
17002
+ "lookupKerf",
17003
+ "nurbsSurface",
17004
+ "point",
17005
+ "ProductHandleBuilder",
17006
+ "ProductHandleFeature",
17007
+ "ProductSpoutBuilder",
17008
+ "radians",
17009
+ "Ribs",
17010
+ "Slot",
17011
+ "star",
17012
+ "surfacePatch",
17013
+ "tabSlot",
17014
+ "viewConfig"
16741
17015
  ]);
16742
17016
  function checkRuntimeNamesDoc(runtimeNames) {
16743
17017
  const collisionReservedNames = runtimeNames.filter((name) => name !== "showLabels");
@@ -21912,7 +22186,7 @@ function computeLinkFrameWorlds(assembly2, jointValues) {
21912
22186
  const visit = (partName, world) => {
21913
22187
  worlds.set(partName, world);
21914
22188
  (jointsByParent.get(partName) ?? []).forEach((joint2) => {
21915
- const childWorld = composeChain(motionTransform(joint2, jointValues[joint2.name] ?? joint2.defaultValue), joint2.frame, world);
22189
+ const childWorld = Transform.compose(motionTransform(joint2, jointValues[joint2.name] ?? joint2.defaultValue), joint2.frame, world);
21916
22190
  visit(joint2.child, childWorld);
21917
22191
  });
21918
22192
  };
@@ -23667,7 +23941,7 @@ import http from "http";
23667
23941
  import path3 from "path";
23668
23942
 
23669
23943
  // server/importAnalysis.ts
23670
- var FORGE_IMPORT_RE2 = /\b(?:importMesh|importStep|importSvgSketch|Import\.dxfSketch)\s*\(\s*(?:"([^"]+)"|'([^']+)')/g;
23944
+ var FORGE_IMPORT_RE2 = /\b(?:importMesh|importStep|importSvgSketch|Import\.mesh|Import\.step|Import\.svgSketch|Import\.dxfSketch)\s*\(\s*(?:"([^"]+)"|'([^']+)')/g;
23671
23945
  var REQUIRE_RE2 = /\brequire\s*\(\s*(?:"([^"]+)"|'([^']+)')/g;
23672
23946
  var ES_IMPORT_RE2 = /\bfrom\s+(?:"([^"]+)"|'([^']+)')/g;
23673
23947
  var VIRTUAL_MODULES2 = /* @__PURE__ */ new Set(["forgecad", "@forge/runtime", "@forgecad/runtime"]);
@@ -23684,13 +23958,16 @@ function extractImports2(code) {
23684
23958
  importMesh: "forgeMesh",
23685
23959
  importStep: "forgeMesh",
23686
23960
  importSvgSketch: "forgeSvg",
23961
+ "Import.mesh": "forgeMesh",
23962
+ "Import.step": "forgeMesh",
23963
+ "Import.svgSketch": "forgeSvg",
23687
23964
  "Import.dxfSketch": "forgeDxf"
23688
23965
  };
23689
23966
  let m;
23690
23967
  const forgeRe = new RegExp(FORGE_IMPORT_RE2.source, FORGE_IMPORT_RE2.flags);
23691
23968
  while ((m = forgeRe.exec(code)) !== null) {
23692
23969
  const path5 = m[1] ?? m[2];
23693
- const fn = m[0].match(/\b(importMesh|importStep|importSvgSketch|Import\.dxfSketch)/)?.[1];
23970
+ const fn = m[0].match(/\b(importMesh|importStep|importSvgSketch|Import\.mesh|Import\.step|Import\.svgSketch|Import\.dxfSketch)/)?.[1];
23694
23971
  add2(path5, kindMap[fn] ?? "forgeMesh");
23695
23972
  }
23696
23973
  const reqRe = new RegExp(REQUIRE_RE2.source, REQUIRE_RE2.flags);
@@ -24176,11 +24453,39 @@ function requireCliAuth() {
24176
24453
  }
24177
24454
 
24178
24455
  // cli/project.ts
24179
- var SOURCE_EXTS = [".forge.js", ".js", ".svg", ".dxf"];
24456
+ var SOURCE_EXTS = [
24457
+ ".forge.js",
24458
+ ".sketch.js",
24459
+ ".js",
24460
+ ".mjs",
24461
+ ".cjs",
24462
+ ".jsx",
24463
+ ".ts",
24464
+ ".tsx",
24465
+ ".json",
24466
+ ".md",
24467
+ ".txt",
24468
+ ".svg",
24469
+ ".dxf",
24470
+ ".html",
24471
+ ".css",
24472
+ ".scss",
24473
+ ".yml",
24474
+ ".yaml",
24475
+ ".toml",
24476
+ ".xml",
24477
+ ".csv",
24478
+ ".ini"
24479
+ ];
24180
24480
  var MESH_EXTS = [".stl", ".obj", ".3mf"];
24181
24481
  var ALL_EXTS = [...SOURCE_EXTS, ...MESH_EXTS];
24482
+ var SKIPPED_PROJECT_DIRECTORY_NAMES = /* @__PURE__ */ new Set([".next", ".turbo", "build", "coverage", "dist", "node_modules"]);
24182
24483
  function isSourceFile(name) {
24183
- return SOURCE_EXTS.some((ext) => name.endsWith(ext));
24484
+ const lower2 = name.toLowerCase();
24485
+ return SOURCE_EXTS.some((ext) => lower2.endsWith(ext));
24486
+ }
24487
+ function shouldSkipProjectDirectory(name) {
24488
+ return name.startsWith(".") || SKIPPED_PROJECT_DIRECTORY_NAMES.has(name);
24184
24489
  }
24185
24490
  var MANIFEST_FILE2 = "forgecad.json";
24186
24491
  function manifestPath(projectDir) {
@@ -24219,8 +24524,9 @@ function scanLocalFiles(projectDir) {
24219
24524
  const rel = prefix ? `${prefix}/${item.name}` : item.name;
24220
24525
  const full = join10(dir, item.name);
24221
24526
  if (item.isDirectory()) {
24527
+ if (shouldSkipProjectDirectory(item.name)) continue;
24222
24528
  walk(full, rel);
24223
- } else if (item.isFile() && isSourceFile(item.name)) {
24529
+ } else if (item.isFile() && rel !== MANIFEST_FILE2 && isSourceFile(item.name)) {
24224
24530
  const content = readFileSync17(full, "utf-8");
24225
24531
  files.push({ path: rel, content, hash: contentHash(content) });
24226
24532
  }
@@ -24440,7 +24746,7 @@ import { existsSync as existsSync13, readdirSync as readdirSync8, statSync as st
24440
24746
  import { createServer as createServer2 } from "net";
24441
24747
  import { join as join11 } from "path";
24442
24748
  var startedComputeServer = null;
24443
- var REQUIRED_ENGINE_VERSION = "native-occt-node-api-0.2.0";
24749
+ var REQUIRED_ENGINE_VERSION = "native-occt-node-api-0.3.0";
24444
24750
  var REQUIRED_PLAN_KINDS = [
24445
24751
  "box",
24446
24752
  "cylinder",
@@ -24461,7 +24767,11 @@ function npmCommand() {
24461
24767
  return process.platform === "win32" ? "npm.cmd" : "npm";
24462
24768
  }
24463
24769
  function isLocalComputeUrl(url) {
24464
- return url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1";
24770
+ const hostname = listenHostForUrl(url);
24771
+ return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1";
24772
+ }
24773
+ function listenHostForUrl(url) {
24774
+ return url.hostname.replace(/^\[|\]$/g, "");
24465
24775
  }
24466
24776
  async function isHealthy(computeUrl) {
24467
24777
  try {
@@ -24525,6 +24835,7 @@ function computeUrlWithPort(url, port) {
24525
24835
  return next.toString().replace(/\/$/, "");
24526
24836
  }
24527
24837
  async function pickComputeUrl(preferred) {
24838
+ const listenHost = listenHostForUrl(preferred);
24528
24839
  const preferredPort = Number.parseInt(preferred.port || (preferred.protocol === "https:" ? "443" : "80"), 10);
24529
24840
  for (let port = preferredPort; port < preferredPort + 10; port += 1) {
24530
24841
  const candidate = computeUrlWithPort(preferred, port);
@@ -24533,7 +24844,7 @@ async function pickComputeUrl(preferred) {
24533
24844
  const server = await new Promise((resolve40, reject) => {
24534
24845
  const probe = createServer2();
24535
24846
  probe.once("error", reject);
24536
- probe.listen(port, preferred.hostname, () => resolve40(probe));
24847
+ probe.listen(port, listenHost, () => resolve40(probe));
24537
24848
  });
24538
24849
  await new Promise((resolve40) => server.close(() => resolve40()));
24539
24850
  return candidate;
@@ -24574,10 +24885,11 @@ function startLocalNativeComputeServer(options) {
24574
24885
  const computeUrl = await pickComputeUrl(parsed);
24575
24886
  const selected = new URL(computeUrl);
24576
24887
  const port = selected.port || (selected.protocol === "https:" ? "443" : "80");
24888
+ const host = listenHostForUrl(selected);
24577
24889
  console.log(`[compute] Starting native OCCT backend at ${computeUrl}`);
24578
24890
  const child = spawn3("npx", ["tsx", "apps/backend/src/server.ts"], {
24579
24891
  cwd: options.packageRoot,
24580
- env: { ...process.env, FORGE_BACKEND_PORT: port },
24892
+ env: { ...process.env, FORGE_BACKEND_PORT: port, FORGE_BACKEND_HOST: host },
24581
24893
  stdio: ["ignore", "inherit", "inherit"]
24582
24894
  });
24583
24895
  await waitForHealthy(computeUrl, child);
@@ -24876,12 +25188,42 @@ function updateLocalAgentContextLatestBundle(projectRoot, filePath, latestBundle
24876
25188
  }
24877
25189
 
24878
25190
  // cli/forge-studio-server.ts
24879
- var PROJECT_FILE_EXTS = [".forge.js", ".js", ".svg", ".dxf"];
24880
- var isProjectFile = (name) => PROJECT_FILE_EXTS.some((ext) => name.endsWith(ext));
25191
+ var PROJECT_FILE_EXTS = [
25192
+ ".forge.js",
25193
+ ".sketch.js",
25194
+ ".js",
25195
+ ".mjs",
25196
+ ".cjs",
25197
+ ".jsx",
25198
+ ".ts",
25199
+ ".tsx",
25200
+ ".json",
25201
+ ".md",
25202
+ ".txt",
25203
+ ".svg",
25204
+ ".dxf",
25205
+ ".html",
25206
+ ".css",
25207
+ ".scss",
25208
+ ".yml",
25209
+ ".yaml",
25210
+ ".toml",
25211
+ ".xml",
25212
+ ".csv",
25213
+ ".ini"
25214
+ ];
25215
+ var isProjectFile = (name) => {
25216
+ const lower2 = name.toLowerCase();
25217
+ return PROJECT_FILE_EXTS.some((ext) => lower2.endsWith(ext));
25218
+ };
24881
25219
  var MESH_FILE_EXTS = [".stl", ".obj", ".3mf"];
24882
25220
  var EXACT_IMPORT_FILE_EXTS = [".step", ".stp"];
24883
25221
  var BINARY_PROJECT_FILE_EXTS = [...MESH_FILE_EXTS, ...EXACT_IMPORT_FILE_EXTS];
24884
25222
  var isBinaryProjectFile = (name) => BINARY_PROJECT_FILE_EXTS.some((ext) => name.toLowerCase().endsWith(ext));
25223
+ var SKIPPED_PROJECT_DIRECTORY_NAMES2 = /* @__PURE__ */ new Set([".next", ".turbo", "build", "coverage", "dist", "node_modules"]);
25224
+ var shouldSkipProjectDirectory2 = (name) => name.startsWith(".") || SKIPPED_PROJECT_DIRECTORY_NAMES2.has(name);
25225
+ var LOCAL_PROJECT_MANIFEST_FILE = "forgecad.json";
25226
+ var isLocalProjectManifestPath = (rel) => rel.toLowerCase() === LOCAL_PROJECT_MANIFEST_FILE;
24885
25227
  var MIME = {
24886
25228
  ".html": "text/html; charset=utf-8",
24887
25229
  ".js": "application/javascript; charset=utf-8",
@@ -24965,9 +25307,10 @@ function scanProjectFiles(projectDir) {
24965
25307
  if (item.name.startsWith(".")) continue;
24966
25308
  const rel = prefix ? `${prefix}/${item.name}` : item.name;
24967
25309
  if (item.isDirectory()) {
25310
+ if (shouldSkipProjectDirectory2(item.name)) continue;
24968
25311
  folderSet.add(rel);
24969
25312
  scan(path3.join(dir, item.name), rel);
24970
- } else if (item.isFile() && isProjectFile(item.name)) {
25313
+ } else if (item.isFile() && !isLocalProjectManifestPath(rel) && isProjectFile(item.name)) {
24971
25314
  files[rel] = fs2.readFileSync(path3.join(dir, item.name), "utf-8");
24972
25315
  } else if (item.isFile() && isBinaryProjectFile(item.name)) {
24973
25316
  files[rel] = "";
@@ -24989,9 +25332,10 @@ function scanProjectFileListing(projectDir) {
24989
25332
  if (item.name.startsWith(".")) continue;
24990
25333
  const rel = prefix ? `${prefix}/${item.name}` : item.name;
24991
25334
  if (item.isDirectory()) {
25335
+ if (shouldSkipProjectDirectory2(item.name)) continue;
24992
25336
  folders.push(rel);
24993
25337
  scan(path3.join(dir, item.name), rel);
24994
- } else if (item.isFile() && (isProjectFile(item.name) || isBinaryProjectFile(item.name))) {
25338
+ } else if (item.isFile() && !isLocalProjectManifestPath(rel) && (isProjectFile(item.name) || isBinaryProjectFile(item.name))) {
24995
25339
  files.push(rel);
24996
25340
  }
24997
25341
  }
@@ -25012,6 +25356,7 @@ function resolveProjectFile(projectDir, filename, opts = {}) {
25012
25356
  if (rel.startsWith("..") || path3.isAbsolute(rel)) {
25013
25357
  throw new Error(`Path "${filename}" is outside the project root`);
25014
25358
  }
25359
+ if (isLocalProjectManifestPath(rel.replace(/\\/g, "/"))) throw new Error("Project manifest is not editable");
25015
25360
  return { filePath, filename: rel.replace(/\\/g, "/") };
25016
25361
  }
25017
25362
  function resolveProjectDirectory(projectDir, dirPath) {
@@ -25025,17 +25370,6 @@ function resolveProjectDirectory(projectDir, dirPath) {
25025
25370
  }
25026
25371
  return { dirPath: resolved, dirname: rel.replace(/\\/g, "/") };
25027
25372
  }
25028
- function directoryTreeContainsFile(dirPath) {
25029
- for (const item of fs2.readdirSync(dirPath, { withFileTypes: true })) {
25030
- const childPath2 = path3.join(dirPath, item.name);
25031
- if (item.isDirectory()) {
25032
- if (directoryTreeContainsFile(childPath2)) return true;
25033
- } else {
25034
- return true;
25035
- }
25036
- }
25037
- return false;
25038
- }
25039
25373
  function serveStatic(distDir, req, res) {
25040
25374
  const absDistDir = path3.resolve(distDir);
25041
25375
  const urlPath = decodeURIComponent((req.url ?? "/").split("?")[0]);
@@ -25171,10 +25505,14 @@ data: ${JSON.stringify(data)}
25171
25505
  }
25172
25506
  for (const proj of projects) {
25173
25507
  const abs = path3.resolve(proj.dir);
25174
- const watcher = chokidar.watch(abs, { ignoreInitial: true, ignored: /(^|[/\\])\../ });
25508
+ const watcher = chokidar.watch(abs, {
25509
+ ignoreInitial: true,
25510
+ ignored: (candidate) => path3.relative(abs, candidate).split(/[\\/]/).some(shouldSkipProjectDirectory2)
25511
+ });
25175
25512
  watcherReadyPromises.push(waitForWatcherReady(watcher));
25176
25513
  watcher.on("add", (f2) => {
25177
25514
  const rel = path3.relative(abs, f2).replace(/\\/g, "/");
25515
+ if (isLocalProjectManifestPath(rel)) return;
25178
25516
  if (isProjectFile(f2)) {
25179
25517
  try {
25180
25518
  broadcastToProject(proj.id, "change", { filename: rel, content: fs2.readFileSync(f2, "utf-8") });
@@ -25186,6 +25524,7 @@ data: ${JSON.stringify(data)}
25186
25524
  });
25187
25525
  watcher.on("change", (f2) => {
25188
25526
  const rel = path3.relative(abs, f2).replace(/\\/g, "/");
25527
+ if (isLocalProjectManifestPath(rel)) return;
25189
25528
  if (isProjectFile(f2)) {
25190
25529
  try {
25191
25530
  broadcastToProject(proj.id, "change", { filename: rel, content: fs2.readFileSync(f2, "utf-8") });
@@ -25196,8 +25535,10 @@ data: ${JSON.stringify(data)}
25196
25535
  }
25197
25536
  });
25198
25537
  watcher.on("unlink", (f2) => {
25538
+ const rel = path3.relative(abs, f2).replace(/\\/g, "/");
25539
+ if (isLocalProjectManifestPath(rel)) return;
25199
25540
  if (!isProjectFile(f2) && !isBinaryProjectFile(f2)) return;
25200
- broadcastToProject(proj.id, "delete", { filename: path3.relative(abs, f2).replace(/\\/g, "/") });
25541
+ broadcastToProject(proj.id, "delete", { filename: rel });
25201
25542
  broadcastProjectSnapshot(proj);
25202
25543
  });
25203
25544
  watcher.on("addDir", () => broadcastProjectSnapshot(proj));
@@ -25604,10 +25945,6 @@ data: ${JSON.stringify(data)}
25604
25945
  sendJson(res, 400, { error: "Path is not a directory" });
25605
25946
  return;
25606
25947
  }
25607
- if (directoryTreeContainsFile(resolved.dirPath)) {
25608
- sendJson(res, 409, { error: "Directory is not empty" });
25609
- return;
25610
- }
25611
25948
  fs2.rmSync(resolved.dirPath, { recursive: true, force: true });
25612
25949
  sendJson(res, 200, { success: true });
25613
25950
  }).catch((e) => sendJson(res, 500, { error: e.message }));
@@ -26428,7 +26765,7 @@ function computeLinkFrameWorlds2(assembly2, jointValues) {
26428
26765
  const visit = (partName, world) => {
26429
26766
  worlds.set(partName, world);
26430
26767
  (jointsByParent.get(partName) ?? []).forEach((joint2) => {
26431
- const childWorld = composeChain(motionTransform2(joint2, jointValues[joint2.name] ?? joint2.defaultValue), joint2.frame, world);
26768
+ const childWorld = Transform.compose(motionTransform2(joint2, jointValues[joint2.name] ?? joint2.defaultValue), joint2.frame, world);
26432
26769
  visit(joint2.child, childWorld);
26433
26770
  });
26434
26771
  };
@@ -31469,6 +31806,23 @@ var RENDER_OPTIONS = [
31469
31806
  },
31470
31807
  { name: "--output", description: "Output file path", argument: "required", valueLabel: "<path>", valueKind: "png" }
31471
31808
  ];
31809
+ var RENDER_MODEL_OPTIONS = [
31810
+ ...RENDER_OPTIONS,
31811
+ {
31812
+ name: "--quality",
31813
+ description: "Mesh quality preset",
31814
+ argument: "required",
31815
+ valueLabel: "<default|live|high>",
31816
+ values: QUALITY_VALUES
31817
+ },
31818
+ {
31819
+ name: "--backend",
31820
+ description: "Geometry backend (default: manifold; STEP inputs default to OCCT)",
31821
+ argument: "required",
31822
+ valueLabel: "<manifold|occt|truck>",
31823
+ values: BACKEND_VALUES
31824
+ }
31825
+ ];
31472
31826
  var LS_OPTIONS = [
31473
31827
  { name: "--tree", description: "Print targets as a compact object tree" },
31474
31828
  { name: "--long", description: "Include geometry metrics such as bounds, volume, area, bodies, and triangle counts" },
@@ -31516,6 +31870,12 @@ var SHOW_OPTIONS = [
31516
31870
  ];
31517
31871
  var INSPECT_EVIDENCE_OPTIONS = [
31518
31872
  ...PARAM_OPTIONS,
31873
+ {
31874
+ name: "--joint-sweep",
31875
+ description: "Sweep one Motion tab joint for fit interference evidence",
31876
+ argument: "required",
31877
+ valueLabel: "<JointName=start..end:samples>"
31878
+ },
31519
31879
  {
31520
31880
  name: "--output",
31521
31881
  description: "Preferred output bundle directory; a non-colliding sibling is created if needed",
@@ -31707,6 +32067,9 @@ function inspectEvidenceOptions(definition) {
31707
32067
  if (definition.channel === "roughness") {
31708
32068
  names.add("--roughness-samples");
31709
32069
  }
32070
+ if (definition.channel === "collisions") {
32071
+ names.add("--joint-sweep");
32072
+ }
31710
32073
  if (definition.channel === "comparison") {
31711
32074
  for (const name of ["--with", "--compare-with", "--compare-align", "--compare-view", "--compare-samples", "--compare-tolerance-mm"]) {
31712
32075
  names.add(name);
@@ -32591,23 +32954,16 @@ var commands = [
32591
32954
  group: "Modeling",
32592
32955
  path: ["run"],
32593
32956
  summary: "Execute a Forge script quickly and print the inner-loop build summary: build count, verification results, parameters, and timing.",
32594
- description: "The fast validation command. Runs your script with the real geometry kernel (no browser needed) and reports whether it built, how many objects came back, any `verify.*` results, parameter values, script logs, and elapsed script time. This is the command agents should run frequently while editing a model.\n\n**Fast by default** \u2014 a bare `forgecad run model.forge.js` does not compute per-object volumes, bounding boxes, construction history, feature tallies, or solver profiles. Those run diagnostics are useful, but they are no longer part of the hot path.\n\n**Opt-in diagnostics** \u2014 use `--details` for volume/bounding-box/object geometry summaries, `--history` for the construction tree, `--features` for feature tallies, or `--solver-profile` for constraint solver timing. Use `inspect fit interference`, `inspect physical gaps`, or `inspect mechanical-integrity --collisions` for spatial and collision evidence.\n\n**Verification results** \u2014 runs any `verify.*` checks in the script and reports pass/fail with expected vs actual values. Verification failures remain non-fatal so the model can still render and be inspected.\n\n**Physical connectivity** \u2014 pass `--connectivity` to list physically connected components across visible objects. Overlapping bbox candidates are checked with exact geometry by default, while bbox-only contact is treated as evidence rather than proof of one connected component. This helps answer whether the model is one continuous assembly or several separate islands.\n\n**Quality preset** \u2014 pass `--quality live|default|high` to select the same geometry quality profile used by the editor and export tools. `live` is the fastest preset for large audit models.\n\n**Direct CAD inputs** \u2014 pass `.stl`, `.obj`, `.3mf`, `.step`, or `.stp` directly when you just want to inspect an external asset. Mesh files are imported with `importMesh(...)`; STEP/STP files are imported with `importStep(...)` and auto-select OCCT unless you pass `--backend`.\n\nFor deeper confidence gates, prefer `forgecad inspect mechanical-integrity`, `forgecad check print`, or targeted evidence commands such as `forgecad inspect fit interference` instead of turning `run` back into a catch-all audit command.",
32957
+ description: "The fast validation command: runs the script with the real geometry kernel (no browser) and reports build status, object count, `verify.*` pass/fail with expected vs actual values (non-fatal \u2014 the model still renders), parameter values, script logs, and timing. Run it frequently while editing.\n\nA bare `forgecad run` skips expensive diagnostics. Opt in with `--details` (volumes/bounds), `--history` (construction tree), `--features` (feature tallies), `--solver-profile` (constraint solver timing), or `--connectivity` (physical connected components \u2014 bbox contact is evidence, exact geometry is checked by default). `--quality live|default|high` selects the same geometry quality profile as the editor and export tools; `live` is fastest for large models.\n\nDirect `.stl`/`.obj`/`.3mf`/`.step`/`.stp` inputs are imported automatically; STEP/STP auto-selects OCCT unless you pass `--backend`. For deeper confidence gates, prefer `inspect mechanical-integrity`, `check print`, or `inspect fit interference` instead of turning `run` into a catch-all audit command.",
32595
32958
  usage: [
32596
32959
  "forgecad run <model.forge.js|asset.stl|asset.obj|asset.3mf|asset.step|asset.stp> [--details] [--history] [--features] [--connectivity] [--connectivity-tolerance <mm>] [--focus [names]] [--hide names] [--journeys] [--journeys-json] [--param Key=Value] [--joint JointName=Value] [--debug-imports] [--verbose] [--backend manifold|occt|truck] [--quality live|default|high] [--solver-profile] [--solver-debug-out <dir>]"
32597
32960
  ],
32598
32961
  examples: [
32599
- "forgecad run examples/api/static-assembly-connectors.forge.js",
32600
- "forgecad run examples/api/static-assembly-connectors.forge.js --focus",
32601
- 'forgecad run examples/api/static-assembly-connectors.forge.js --focus "Bench.Slat*"',
32602
- 'forgecad run examples/api/static-assembly-connectors.forge.js --hide "Bench.Slat0,Bench.Slat1"',
32603
- "forgecad run examples/api/static-assembly-connectors.forge.js --details --history",
32604
- "forgecad run examples/products/cup.forge.js --connectivity",
32605
- "forgecad run examples/products/cup.forge.js --journeys",
32606
- "forgecad run examples/products/cup.forge.js --backend occt",
32607
- "forgecad run examples/products/cup.forge.js --backend truck --quality live",
32608
- "forgecad run examples/products/cup.forge.js --debug-imports",
32609
- 'forgecad run examples/products/cup.forge.js -p "Wall Thickness=3" -p "Body Height=200"',
32610
- "forgecad run examples/constraints/06-complex-spectrogram.forge.js --solver-debug-out tmp/spectrogram-debug"
32962
+ "forgecad run model.forge.js",
32963
+ "forgecad run model.forge.js --details --history",
32964
+ 'forgecad run model.forge.js --focus "Bench.Slat*"',
32965
+ "forgecad run model.forge.js --connectivity --quality live",
32966
+ 'forgecad run model.forge.js -p "Wall Thickness=3" -p "Body Height=200"'
32611
32967
  ],
32612
32968
  completion: {
32613
32969
  options: [
@@ -32678,14 +33034,7 @@ var commands = [
32678
33034
  summary: "Render a Forge scene. Use a subcommand \u2014 `3d`, `views`, `section`, `wireframe`, `sketch`, or `hq`.",
32679
33035
  description: "`forgecad render` is a group of rendering subcommands. Pick one based on what you want:\n\n- `render 3d` \u2014 standard viewport PNG, the usual way to visually verify geometry\n- `render views` \u2014 list named cameras declared with `scene({ views })`\n- `render wireframe` \u2014 edges only, no shading\n- `render section` \u2014 2D cross-section cut by a plane (SVG or PNG)\n- `render sketch` \u2014 2D sketch script to PNG\n- `render hq` \u2014 path-traced via Blender Cycles, for documentation and marketing shots",
32680
33036
  usage: ["forgecad render <subcommand> <model.forge.js|asset.stl|asset.obj|asset.3mf|asset.step|asset.stp> [options]"],
32681
- examples: [
32682
- "forgecad render 3d examples/products/cup.forge.js",
32683
- "forgecad inspect fit interference examples/api/static-assembly-connectors.forge.js --camera iso",
32684
- "forgecad render views examples/products/cup.forge.js",
32685
- "forgecad render wireframe examples/products/cup.forge.js",
32686
- "forgecad render section examples/furniture/01-table.forge.js --plane XZ",
32687
- "forgecad render hq examples/products/cup.forge.js --preset dramatic"
32688
- ],
33037
+ examples: ["forgecad render 3d model.forge.js", "forgecad render section model.forge.js --plane XZ"],
32689
33038
  run: async (_args) => {
32690
33039
  console.error("`forgecad render` requires a subcommand.");
32691
33040
  console.error("");
@@ -32705,28 +33054,19 @@ var commands = [
32705
33054
  group: "Modeling",
32706
33055
  path: ["render", "3d"],
32707
33056
  summary: "Render a Forge scene to PNG using the real viewport renderer.",
32708
- description: "Launches a headless Chrome instance, renders the scene with the same WebGL viewport as the editor, and saves a PNG. The output path defaults to `<script-name>.png` next to the input file. Each render uses a private renderer server by default, so parallel renders do not compete for the same Vite port.\n\nThe input can be a `.forge.js` script or a direct `.stl`, `.obj`, `.3mf`, `.step`, or `.stp` asset. Direct STEP/STP rendering auto-selects OCCT unless you pass `--backend`.\n\nUse `--focus` to isolate specific parts (hides everything else) or `--hide` to remove clutter like mock objects. The `--view` flag selects a named camera declared in `scene({ views })`. The `--camera` flag accepts built-in views (`front`, `top`, `iso`), `azimuth:elevation` angles, or an exact `proj/pos/target/up/fov` camera spec \u2014 pass `--camera` multiple times to render several viewpoints in one run. Use `--camera-json <file>` or `--scene <file>` for exact reproducible viewport cameras without shell escaping. Filtered renders report any visible objects whose bounding boxes are partially or fully outside the camera frame; exact cameras fail when they frame none of the visible objects.\n\nUse `--edges=<off|thin|bold>` to control the edge overlay. For a pure wireframe look, use `render wireframe` instead.\n\nThis is the standard way to visually verify geometry from the CLI or in agent workflows. For higher quality (path-traced, materials, HDRI lighting), use `render hq` instead.",
33057
+ description: "Launches headless Chrome, renders the scene with the same WebGL viewport as the editor, and saves a PNG. The output path defaults to `<script-name>.png` next to the input; the input can be a `.forge.js` script or a direct `.stl`/`.obj`/`.3mf`/`.step`/`.stp` asset.\n\n`--camera` accepts built-in views (`front`, `top`, `iso`), `azimuth:elevation` angles, or an exact `proj/pos/target/up/fov` camera spec \u2014 pass it multiple times to render several viewpoints in one run. `--view` selects a named camera declared in `scene({ views })`; `--camera-json <file>` or `--scene <file>` give exact reproducible cameras without shell escaping. `--focus`/`--hide` filter visible objects; `--edges=<off|thin|bold>` controls the edge overlay.\n\nThis is the standard way to visually verify geometry from the CLI or in agent workflows. For path-traced quality use `render hq`; for edges only use `render wireframe`.",
32709
33058
  usage: [
32710
33059
  "forgecad render 3d <model.forge.js|asset.stl|asset.obj|asset.3mf|asset.step|asset.stp> [output.png] [--param Key=Value] [--joint JointName=Value] [--focus [names]] [--hide names] [--edges off|thin|bold] [options]"
32711
33060
  ],
32712
33061
  examples: [
32713
- "forgecad render 3d examples/products/cup.forge.js",
32714
- "forgecad render 3d examples/api/static-assembly-connectors.forge.js --focus",
32715
- 'forgecad render 3d examples/api/static-assembly-connectors.forge.js --focus "Bench.Slat*"',
32716
- 'forgecad render 3d examples/api/static-assembly-connectors.forge.js --hide "Bench.Slat0,Bench.Slat1"',
32717
- "forgecad render 3d model.forge.js --view hero",
32718
- "forgecad render 3d model.forge.js --camera 45:30",
33062
+ "forgecad render 3d model.forge.js",
33063
+ "forgecad render 3d model.forge.js --camera front --camera side --edges bold",
33064
+ 'forgecad render 3d model.forge.js --focus "Bench.Slat*" -p "Wall Thickness=3"',
32719
33065
  'forgecad render 3d model.forge.js --camera "proj=perspective;pos=200,-160,120;target=0,0,20;up=0,0,1;fov=38"',
32720
- "forgecad render 3d model.forge.js --camera-json camera.json",
32721
- "forgecad render 3d model.forge.js --scene scene.json",
32722
- "forgecad render 3d model.forge.js --camera front --camera side",
32723
- 'forgecad render 3d model.forge.js -p "Wall Thickness=3"',
32724
- "forgecad render 3d model.forge.js --edges bold",
32725
- "forgecad render 3d model.forge.js --edges off",
32726
33066
  "forgecad render 3d bracket.3mf bracket.png"
32727
33067
  ],
32728
33068
  completion: {
32729
- options: RENDER_OPTIONS,
33069
+ options: RENDER_MODEL_OPTIONS,
32730
33070
  positionals: [
32731
33071
  { description: "Forge script or CAD asset", valueKind: "renderable" },
32732
33072
  { description: "output PNG path", valueKind: "png" }
@@ -32747,7 +33087,7 @@ var commands = [
32747
33087
  "forgecad render wireframe examples/products/cup.forge.js --camera iso"
32748
33088
  ],
32749
33089
  completion: {
32750
- options: RENDER_OPTIONS,
33090
+ options: RENDER_MODEL_OPTIONS,
32751
33091
  positionals: [
32752
33092
  { description: "Forge script or CAD asset", valueKind: "renderable" },
32753
33093
  { description: "output PNG path", valueKind: "png" }
@@ -32791,17 +33131,14 @@ var commands = [
32791
33131
  group: "Modeling",
32792
33132
  path: ["render", "hq"],
32793
33133
  summary: "High-quality render via Blender Cycles \u2014 path-traced, HDRI, material presets.",
32794
- description: "Exports the scene to Blender and renders with Cycles (path tracer). Requires Blender installed and on PATH.\n\nChoose a `--preset` for the look: `studio` (neutral product shot), `dramatic` (high-contrast), `clay` (matte, no color), `glass`, `metallic`, `toon`, `xray`, `normals`, `silhouette`, and more. Control quality vs speed with `--samples` (default 256). Use `--view`, `--camera`, `--camera-json`, or `--scene <file>` for still camera control, matching `render 3d`. Use `--transparent` for a transparent background (compositing-ready).\n\nOutput defaults to `<script-name>-hq.png`. Great for documentation, marketing renders, and social media.",
33134
+ description: "Exports the scene to Blender and renders with Cycles (path tracer); requires Blender on PATH. Output defaults to `<script-name>-hq.png`.\n\n`--preset` picks the look (`studio`, `dramatic`, `clay`, `glass`, `metallic`, `toon`, `xray`, `normals`, `silhouette`, and more); `--samples` (default 256) trades quality vs speed; `--transparent` gives a compositing-ready background. Camera control (`--view`, `--camera`, `--camera-json`, `--scene <file>`) matches `render 3d`.",
32795
33135
  usage: [
32796
33136
  "forgecad render hq <model.forge.js|asset.stl|asset.obj|asset.3mf|asset.step|asset.stp> [output.png] [--param Key=Value] [--joint JointName=Value] [options]"
32797
33137
  ],
32798
33138
  examples: [
32799
- "forgecad render hq examples/products/cup.forge.js",
32800
- "forgecad render hq examples/products/cup.forge.js hero.png --preset dramatic --samples 1024",
32801
- "forgecad render hq examples/products/cup.forge.js hero.png --view hero",
32802
- "forgecad render hq examples/products/cup.forge.js hero.png --camera-json camera.json",
32803
- "forgecad render hq examples/products/cup.forge.js --preset clay --size 2048",
32804
- "forgecad render hq examples/products/cup.forge.js --transparent --preset glass"
33139
+ "forgecad render hq model.forge.js",
33140
+ "forgecad render hq model.forge.js hero.png --preset dramatic --samples 1024",
33141
+ "forgecad render hq model.forge.js --transparent --preset glass --size 2048"
32805
33142
  ],
32806
33143
  completion: {
32807
33144
  options: [
@@ -32908,17 +33245,14 @@ var commands = [
32908
33245
  group: "Modeling",
32909
33246
  path: ["capture", "gif"],
32910
33247
  summary: "Capture an animated GIF from a script via orbit, named joint playback, or section sweep.",
32911
- description: "Renders an animated sequence by either orbiting the camera around the model or playing back a named joint animation. Use `--capture orbit` (default) for a turntable rotation, `--capture animation --animation <name>` to play a named joint clip, or `--capture section-sweep` to move a clipping plane through the model. Supports `--cut-plane` to animate with a static cross-section visible. Use `--view`, `--camera`, `--camera-json`, or `--scene <file>` to choose the orbit base camera or the fixed camera for animations and section sweeps.",
33248
+ description: "Renders an animated sequence: `--capture orbit` (default) for a turntable, `--capture animation --animation <name>` for a named joint clip, or `--capture section-sweep` to move a clipping plane through the model. `--cut-plane` keeps a static cross-section visible while animating; the orbit base or fixed camera comes from `--view`, `--camera`, `--camera-json`, or `--scene <file>`.",
32912
33249
  usage: [
32913
33250
  "forgecad capture gif <model.forge.js|asset.stl|asset.obj|asset.3mf|asset.step|asset.stp> [output.gif] [options] [--param Key=Value] [--joint JointName=Value]"
32914
33251
  ],
32915
33252
  examples: [
32916
- "forgecad capture gif examples/products/cup.forge.js",
32917
- 'forgecad capture gif examples/3d-printer.forge.js out/section.gif --cut-plane "Front Section"',
32918
- 'forgecad capture gif model.forge.js out/raw.gif --param "Output=raw-sdf"',
32919
- "forgecad capture gif model.forge.js out/front.gif --camera front",
32920
- "forgecad capture gif model.forge.js out/hero.gif --view hero",
32921
- "forgecad capture gif examples/3d-printer.forge.js out/sweep.gif --capture section-sweep --sweep-plane YZ"
33253
+ "forgecad capture gif model.forge.js",
33254
+ "forgecad capture gif model.forge.js out/sweep.gif --capture section-sweep --sweep-plane YZ",
33255
+ 'forgecad capture gif model.forge.js out/hero.gif --view hero --cut-plane "Front Section"'
32922
33256
  ],
32923
33257
  completion: {
32924
33258
  options: CAPTURE_COMMON_OPTIONS,
@@ -32938,12 +33272,8 @@ var commands = [
32938
33272
  "forgecad capture mp4 <model.forge.js|asset.stl|asset.obj|asset.3mf|asset.step|asset.stp> [output.mp4] [options] [--param Key=Value] [--joint JointName=Value]"
32939
33273
  ],
32940
33274
  examples: [
32941
- "forgecad capture mp4 examples/products/cup.forge.js",
32942
- "forgecad capture mp4 examples/api/assembly-kinematics-four-bar.forge.js out/four-bar.mp4 --view iso",
32943
- 'forgecad capture mp4 model.forge.js out/raw.mp4 --param "Output=raw-sdf"',
32944
- "forgecad capture mp4 model.forge.js out/front.mp4 --camera front",
32945
- "forgecad capture mp4 model.forge.js out/hero.mp4 --view hero",
32946
- "forgecad capture mp4 examples/3d-printer.forge.js out/sweep.mp4 --capture section-sweep --sweep-plane YZ --sweep-frames 180"
33275
+ "forgecad capture mp4 model.forge.js out/four-bar.mp4 --view iso",
33276
+ "forgecad capture mp4 model.forge.js out/sweep.mp4 --capture section-sweep --sweep-plane YZ --sweep-frames 180"
32947
33277
  ],
32948
33278
  completion: {
32949
33279
  options: CAPTURE_COMMON_OPTIONS,
@@ -32958,15 +33288,14 @@ var commands = [
32958
33288
  group: "Modeling",
32959
33289
  path: ["render", "section"],
32960
33290
  summary: "Render a 2D cross-section of a 3D model (cut by a plane) to SVG or PNG.",
32961
- description: "Cuts all shapes in the scene with an axis-aligned plane and produces a 2D cross-section drawing. The default plane is XY at Z=0. Use `--plane XZ` or `--plane YZ` for other orientations, and `--offset` to shift the cut position.\n\nOutput format is determined by the file extension: `.svg` (default, vector) or `.png` (rasterized at `--size` pixels). Use `--edges=<off|thin|bold>` to control the outline stroke on cut shapes.\n\nUseful for verifying internal geometry, wall thicknesses, and fit checks that aren't visible in 3D renders.",
33291
+ description: "Cuts all shapes with an axis-aligned plane (default XY at Z=0; `--plane XZ|YZ` reorients, `--offset` shifts the cut) and writes a 2D cross-section drawing. The file extension picks the format: `.svg` (default, vector) or `.png` (rasterized at `--size` pixels); `--edges=<off|thin|bold>` sets the outline stroke. Useful for verifying internal geometry, wall thicknesses, and fits that aren't visible in 3D renders.",
32962
33292
  usage: [
32963
33293
  "forgecad render section <model.forge.js|asset.stl|asset.obj|asset.3mf|asset.step|asset.stp> [output.svg|.png] [--param Key=Value] [--joint JointName=Value] [--plane XY|XZ|YZ] [--offset <number>] [--size <px>] [--edges off|thin|bold] [--background <color>]"
32964
33294
  ],
32965
33295
  examples: [
32966
- "forgecad render section examples/furniture/01-table.forge.js",
32967
- "forgecad render section examples/furniture/01-table.forge.js out/section.svg --plane XZ --offset 10",
32968
- "forgecad render section examples/furniture/01-table.forge.js out/section.png --size 2048",
32969
- "forgecad render section examples/furniture/01-table.forge.js out/bold.svg --edges bold"
33296
+ "forgecad render section model.forge.js",
33297
+ "forgecad render section model.forge.js out/section.svg --plane XZ --offset 10",
33298
+ "forgecad render section model.forge.js out/section.png --size 2048"
32970
33299
  ],
32971
33300
  completion: {
32972
33301
  options: [
@@ -34081,13 +34410,12 @@ var commands = [
34081
34410
  group: "Inspect",
34082
34411
  path: ["inspect", "sketch"],
34083
34412
  summary: "Inspect returned sketches and profile regions used by returned shapes.",
34084
- description: "Runs a model and reports inspectable 2D sketch/profile regions without requiring model code to register inspection hooks. The command inspects returned Sketch objects and profile-bearing shape compile plans such as extrude, cut, and revolve. Use `--seed x,y` to dry-run which region a point selector would consume, and `--operation extrude` to check extrusion compatibility.",
34413
+ description: "External inspection: runs the model, then reads returned `Sketch`/`ConstraintSketch` objects and profile-bearing shape compile plans (extrude, cut, revolve) \u2014 model code never calls an inspection API. Reports filled selectable regions (sorted largest-first, run-local ids like `R0`) and excluded hole interiors. `--seed x,y` is the stable selection mechanism (not region ids): it dry-runs which region a point selector would consume, and seed failures exit nonzero (outside every region, on a boundary, inside a hole, ambiguous, or incompatible operation). `--operation extrude` checks only whether the selected filled region can be consumed by extrusion.",
34085
34414
  usage: [
34086
34415
  "forgecad inspect sketch <model.forge.js> [--json] [--object <name-or-path>] [--seed <x,y>] [--operation extrude] [--param Key=Value] [--joint JointName=Value] [--backend manifold|occt|truck] [--quality live|default|high]"
34087
34416
  ],
34088
34417
  examples: [
34089
- "forgecad inspect sketch examples/api/sketch-regions.forge.js",
34090
- 'forgecad inspect sketch model.forge.js --object "Profile" --seed 45,15',
34418
+ "forgecad inspect sketch model.forge.js",
34091
34419
  'forgecad inspect sketch model.forge.js --json --object "Body" --seed 45,15 --operation extrude'
34092
34420
  ],
34093
34421
  completion: {
@@ -34218,7 +34546,7 @@ var commands = [
34218
34546
  group: "Inspect",
34219
34547
  path: ["inspect", "mechanical-integrity"],
34220
34548
  summary: "Inspect generated ForgeCAD models for mechanical integrity failures.",
34221
- description: "Scans a Forge script or a folder of generated projects and runs a mechanical integrity inspection. The inspection flags timeouts, runtime errors, missing `verify.*` checks, missing executed mechanical-interface checks, fragmented named groups, uncontracted manual assemblies, optional positive-volume object collisions, and excessive physical component counts when requested. Markdown details include suggested repair patterns such as connector-authored mates, bolted service covers, pinned levers, captured slides, hinges, clevis joints, retained shafts, and seated bearings. When `--collisions` is enabled, the Markdown details list the largest overlapping object pairs by volume so agents can repair the highest-risk interfaces first. Exact collision checks use a default 40-pair bbox-overlap budget and 30s exact-check time budget; exhausting either budget fails the file instead of silently passing a partial check. Script-authored `console.warn()` diagnostics are reported as warnings; use `verify.*` checks or `console.error()` for gate-failing contracts. Script-side joint sweeps use a separate validation step budget so the gate can keep default geometry quality while failing truncated validation explicitly.",
34549
+ description: "Scans a Forge script or a folder of generated projects and flags timeouts, runtime errors, missing `verify.*` checks, missing executed mechanical-interface checks, fragmented named groups, uncontracted manual assemblies, and (when requested) positive-volume collisions and excessive physical component counts. Details suggest concrete repair patterns. With `--collisions`, the largest overlapping object pairs are listed by volume so the highest-risk interfaces get repaired first; exhausting the exact-check pair or time budget fails the file instead of silently passing a partial check, and truncated script-side joint-sweep validation fails explicitly. Script `console.warn()` diagnostics are reported as warnings; use `verify.*` checks or `console.error()` for gate-failing contracts.",
34222
34550
  usage: [
34223
34551
  "forgecad inspect mechanical-integrity <script-or-dir> [--json] [--param Key=Value] [--joint JointName=Value] [--timeout-ms <ms>] [--jobs <n>] [--min-verifications <n>] [--max-components <n>] [--collisions] [--min-overlap-volume <mm3>] [--collision-pair-limit <n>] [--collision-time-budget-ms <ms>] [--sweep-joint-step-limit <n>] [--all-forge] [--backend manifold|occt|truck]"
34224
34552
  ],
@@ -34385,7 +34713,7 @@ var commands = [
34385
34713
  { name: "--update", description: "Regenerate compiler snapshots" }
34386
34714
  ]
34387
34715
  },
34388
- run: async (args) => (await import("./check-compiler-SYQ2PWOB.js")).runCheckCompilerCli(args)
34716
+ run: async (args) => (await import("./check-compiler-JTVBITCR.js")).runCheckCompilerCli(args)
34389
34717
  },
34390
34718
  {
34391
34719
  group: "Checks",
@@ -34408,7 +34736,7 @@ var commands = [
34408
34736
  { name: "--update", description: "Regenerate query-propagation snapshots" }
34409
34737
  ]
34410
34738
  },
34411
- run: async (args) => (await import("./check-query-propagation-HIAGV62W.js")).runCheckQueryPropagationCli(args)
34739
+ run: async (args) => (await import("./check-query-propagation-3FFLSMVN.js")).runCheckQueryPropagationCli(args)
34412
34740
  },
34413
34741
  {
34414
34742
  group: "Checks",