forgecad 0.9.14 → 0.9.15
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.
- package/LICENSE +6 -4
- package/README.md +8 -4
- package/dist/assets/{AdminPage-eWGs2K6H.js → AdminPage-CDyGUinA.js} +2 -2
- package/dist/assets/{BenchmarkPage-CTrLKfpo.js → BenchmarkPage-DfPMY_-d.js} +4 -15
- package/dist/assets/{BlogPage-5nPesyds.js → BlogPage-kF0fkdJT.js} +2 -2
- package/dist/assets/{DocsPage-C4Y3nbYc.js → DocsPage-B954L3YN.js} +9 -3
- package/dist/assets/EditorApp-Beb-IZ0y.js +14014 -0
- package/dist/assets/{EditorApp-BAnckbsk.css → EditorApp-CuDLxKqL.css} +698 -0
- package/dist/assets/{EmbedViewer-C8fB4n5U.js → EmbedViewer-C77B-TrF.js} +3 -3
- package/dist/assets/{LandingPageProofDriven-jSz0LaMM.js → LandingPageProofDriven-Cr6fXMDj.js} +35 -37
- package/dist/assets/LegalPage-BRlScr9A.css +91 -0
- package/dist/assets/LegalPage-Dzklqmmg.js +39 -0
- package/dist/assets/{PricingPage-BMedqFef.css → PricingPage-BPF6HKyO.css} +25 -0
- package/dist/assets/{PricingPage-B83B90zh.js → PricingPage-zWXkvlwl.js} +19 -19
- package/dist/assets/{SettingsPage-DY889pcu.js → SettingsPage-Bz0of4KQ.js} +2 -2
- package/dist/assets/app-CE3sYcV7.css +3890 -0
- package/dist/assets/{app-bEww1ic4.js → app-D3kDkggg.js} +2293 -946
- package/dist/assets/cli/{render-Cho2uKG_.js → render-DSY3mMQa.js} +337 -7
- package/dist/assets/{constructionHistoryWorker-HYwzJY4m.js → constructionHistoryWorker-gpDo-uH2.js} +927 -243
- package/dist/assets/{evalWorker-CjQwJSE-.js → evalWorker-CU0Ke6DP.js} +7800 -4164
- package/dist/assets/{forgecad_geometry-CH2nvuLA.js → forgecad_geometry-Dgceylq9.js} +43 -1
- package/dist/assets/forgecad_geometry_bg-dD4RNQF1.wasm +0 -0
- package/dist/assets/{inspectWorker-DeRnMVv1.js → inspectWorker-COyp8XXA.js} +927 -243
- package/dist/assets/{javascript-70-4uGcz.js → javascript-1kQXfVaz.js} +1 -1
- package/dist/assets/landing-proof-driven-DiGqdtWa.js +18 -0
- package/dist/assets/{landing-proof-driven-oFYW6mjz.css → landing-proof-driven-ORyigZ6p.css} +13 -7
- package/dist/assets/legalContent-ZfFGMmi4.js +251 -0
- package/dist/assets/{manifold-CG9Fokx-.js → manifold-BRI5prcH.js} +1 -1
- package/dist/assets/{manifold-uRzgk5O8.js → manifold-C-3h2M7p.js} +2 -2
- package/dist/assets/{manifold-rmfAcdwF.js → manifold-DNkrUWpA.js} +1 -1
- package/dist/assets/{reportWorker-4cW_ZpoS.js → reportWorker-CdBz5bNg.js} +7538 -10857
- package/dist/assets/{scalar-sampling-budget-CfDiFvh7.js → scalar-sampling-budget-wJF98aY9.js} +6935 -4331
- package/dist/assets/{scanProxyWorker-Bs2TDgLw.js → scanProxyWorker-B-9VbLIs.js} +32 -1
- package/dist/assets/{solver-DuJAO8S6.js → solver-BZ9LPTHs.js} +1 -1
- package/dist/assets/solver_bg-DAHZJ_rw.wasm +0 -0
- package/dist/assets/{targets-D6PWsv6X.js → targets-B9sGB5nB.js} +1 -1
- package/dist/assets/{vendor-react-Da3A2QmU.js → vendor-react-6j1Kke-Y.js} +6 -5
- package/dist/cli/render.html +1 -1
- package/dist/docs/index.html +2 -2
- package/dist/docs-raw/AI/ai-native-cad.md +50 -0
- package/dist/docs-raw/AI/usage.md +3 -12
- package/dist/docs-raw/CLI.md +30 -10
- package/dist/docs-raw/component-model.md +27 -11
- package/dist/docs-raw/generated/assembly.md +301 -212
- package/dist/docs-raw/generated/concepts.md +235 -237
- package/dist/docs-raw/generated/core.md +283 -6
- package/dist/docs-raw/generated/curves.md +274 -361
- package/dist/docs-raw/generated/lib.md +7 -1
- package/dist/docs-raw/generated/output.md +19 -4
- package/dist/docs-raw/generated/runtime-names.md +41 -0
- package/dist/docs-raw/generated/sdf.md +31 -0
- package/dist/docs-raw/generated/sheet-metal.md +9 -0
- package/dist/docs-raw/generated/sketch.md +44 -1
- package/dist/docs-raw/generated/viewport.md +11 -3
- package/dist/docs-raw/guides/coordinate-system.md +20 -16
- package/dist/docs-raw/guides/geometry-conventions.md +2 -2
- package/dist/docs-raw/guides/inspection-bundles.md +2 -1
- package/dist/docs-raw/guides/joint-design.md +24 -0
- package/dist/docs-raw/guides/positioning.md +13 -3
- package/dist/docs-raw/legal/privacy.md +63 -0
- package/dist/docs-raw/legal/software-license.md +55 -0
- package/dist/docs-raw/legal/terms.md +87 -0
- package/dist/docs-raw/skills/forgecad-3d-reconstruction.md +1 -1
- package/dist/docs-raw/skills/forgecad-blockout-model.md +1 -1
- package/dist/docs-raw/skills/forgecad-component-model.md +11 -2
- package/dist/docs-raw/skills/forgecad-high-level-spec.md +1 -1
- package/dist/docs-raw/skills/forgecad-image-replicator.md +8 -8
- package/dist/docs-raw/skills/forgecad-lld.md +1 -1
- package/dist/docs-raw/skills/forgecad-make-a-model.md +1 -1
- package/dist/docs-raw/skills/forgecad-model-grader.md +2 -2
- package/dist/docs-raw/skills/forgecad-prepare-prompt.md +2 -2
- package/dist/docs-raw/skills/forgecad-project.md +1 -1
- package/dist/docs-raw/skills/forgecad-reconstruction-benchmark.md +1 -1
- package/dist/docs-raw/skills/forgecad-render-inspect.md +4 -2
- package/dist/docs-raw/skills/forgecad-visual-spec.md +1 -1
- package/dist/docs-raw/skills/forgecad.md +4 -3
- package/dist/index.html +40 -12
- package/dist/llms.txt +8 -0
- package/dist/site.webmanifest +1 -1
- package/dist/sitemap.xml +49 -13
- package/dist-cli/{check-compiler-U5SOPN7X.js → check-compiler-SDX5QIXI.js} +1 -2
- package/dist-cli/{check-query-propagation-XOKNSSYU.js → check-query-propagation-EAYEFT77.js} +1 -2
- package/dist-cli/{chunk-EXWGNL6K.js → chunk-N4O47JLF.js} +12540 -9046
- package/dist-cli/forgecad.js +1786 -679
- package/dist-cli/{forgecad_geometry-GYVNKPIE.js → forgecad_geometry-QOQIIP53.js} +42 -1
- package/dist-cli/forgecad_geometry_bg.wasm +0 -0
- package/dist-cli/{solver-46FFSK2U.js → solver-OK4HECRH.js} +0 -1
- package/dist-cli/solver_bg.wasm +0 -0
- package/dist-skill/CONTEXT.md +1117 -721
- package/dist-skill/SKILL.md +3 -2
- package/dist-skill/docs/API/core/concepts.md +64 -1
- package/dist-skill/docs/CLI.md +30 -10
- package/dist-skill/docs/generated/assembly.md +277 -229
- package/dist-skill/docs/generated/core.md +283 -6
- package/dist-skill/docs/generated/curves.md +272 -362
- package/dist-skill/docs/generated/lib.md +7 -1
- package/dist-skill/docs/generated/output.md +19 -4
- package/dist-skill/docs/generated/runtime-names.md +41 -0
- package/dist-skill/docs/generated/sdf.md +31 -0
- package/dist-skill/docs/generated/sheet-metal.md +9 -0
- package/dist-skill/docs/generated/sketch.md +44 -2
- package/dist-skill/docs/generated/viewport.md +2 -87
- package/dist-skill/docs/guides/coordinate-system.md +20 -16
- package/dist-skill/docs/guides/geometry-conventions.md +2 -2
- package/dist-skill/docs/guides/inspection-bundles.md +2 -1
- package/dist-skill/docs/guides/joint-design.md +24 -0
- package/dist-skill/docs/guides/positioning.md +13 -3
- package/dist-skill/library/forgecad-component-model/SKILL.md +10 -1
- package/dist-skill/library/forgecad-image-replicator/SKILL.md +6 -6
- package/dist-skill/library/forgecad-image-replicator/scripts/compare_images.py +166 -0
- package/dist-skill/library/forgecad-model-grader/SKILL.md +1 -1
- package/dist-skill/library/forgecad-prepare-prompt/SKILL.md +1 -1
- package/dist-skill/library/forgecad-render-inspect/SKILL.md +3 -1
- package/examples/api/assembly-kinematics-foundation.forge.js +65 -0
- package/examples/api/assembly-kinematics-four-bar.forge.js +115 -0
- package/examples/api/assembly-kinematics-limb.forge.js +116 -0
- package/examples/api/connector-frame-rig-chain.forge.js +102 -0
- package/examples/api/exact-sheet-shell-assembly.forge.js +0 -2
- package/examples/api/exact-surface-studio.forge.js +6 -8
- package/examples/api/helix-basics.forge.js +6 -6
- package/examples/api/lean-foundations/README.md +12 -0
- package/examples/api/lean-foundations/curve-blend-exact.forge.js +22 -0
- package/examples/api/lean-foundations/curve-fit-interpolation.forge.js +18 -0
- package/examples/api/lean-foundations/curve-helix-canonicalization.forge.js +27 -0
- package/examples/api/lean-foundations/curve-route-canonicalization.forge.js +16 -0
- package/examples/api/lean-foundations/curve-trim-reverse.forge.js +24 -0
- package/examples/api/lean-foundations/exact-curve-arc.forge.js +36 -0
- package/examples/api/mixed-edge-finishes-proof.forge.js +8 -11
- package/examples/api/route3d-elbow.forge.js +68 -0
- package/examples/api/transition-curves.forge.js +44 -15
- package/examples/api/y-blend-corner-showcase.forge.js +0 -2
- package/examples/generative/coral-vase.forge.js +1 -1
- package/examples/nurbs-tube.forge.js +1 -1
- package/package.json +14 -13
- package/dist/assets/EditorApp-lXv53A1m.js +0 -13610
- package/dist/assets/app-CsHnaBWt.css +0 -1789
- package/dist/assets/forgecad_geometry_bg-C5_E9Oa9.wasm +0 -0
- package/dist/assets/solver_bg-CWvv4lnN.wasm +0 -0
- package/dist/docs-raw/API/README.md +0 -16
- package/dist/docs-raw/API/core/concepts.md +0 -118
- package/dist/docs-raw/INDEX.md +0 -138
- package/dist/docs-raw/RELEASING.md +0 -87
- package/dist/docs-raw/agent-native-api.md +0 -27
- package/dist/docs-raw/beta-deployment.md +0 -304
- package/dist/docs-raw/beta-operations.md +0 -325
- package/dist/docs-raw/blueprint-first.md +0 -145
- package/dist/docs-raw/cli-monetization.md +0 -112
- package/dist/docs-raw/coding-best-practices.md +0 -120
- package/dist/docs-raw/coding.md +0 -340
- package/dist/docs-raw/deployment.md +0 -374
- package/dist/docs-raw/guides/skill-maintenance.md +0 -161
- package/dist/docs-raw/guides/surface-members.md +0 -82
- package/dist/docs-raw/harbor-cli.md +0 -854
- package/dist/docs-raw/internals/backend-vocabulary.md +0 -35
- package/dist/docs-raw/internals/compiler.md +0 -307
- package/dist/docs-raw/internals/constraint-solver-quality.md +0 -161
- package/dist/docs-raw/internals/constraint-solver.md +0 -176
- package/dist/docs-raw/internals/shape-from-slices.md +0 -152
- package/dist/docs-raw/internals/sketch-2d-pipeline.md +0 -108
- package/dist/docs-raw/platform/admin.md +0 -45
- package/dist/docs-raw/platform/architecture.md +0 -82
- package/dist/docs-raw/platform/auth.md +0 -139
- package/dist/docs-raw/platform/email.md +0 -67
- package/dist/docs-raw/platform/google-oauth-setup.md +0 -88
- package/dist/docs-raw/platform/observability.md +0 -197
- package/dist/docs-raw/platform/projects.md +0 -111
- package/dist/docs-raw/platform/sharing.md +0 -90
- package/dist/docs-raw/product/README.md +0 -39
- package/dist/docs-raw/product/api-as-product-language.md +0 -13
- package/dist/docs-raw/product/business-model.md +0 -15
- package/dist/docs-raw/product/competitive-positioning.md +0 -17
- package/dist/docs-raw/product/creative-manufacturing.md +0 -15
- package/dist/docs-raw/product/founder-story.md +0 -11
- package/dist/docs-raw/product/manufacturing-workflows.md +0 -15
- package/dist/docs-raw/product/onboarding-first-experience.md +0 -256
- package/dist/docs-raw/product/product-loop.md +0 -17
- package/dist/docs-raw/product/strategic-decisions.md +0 -22
- package/dist/docs-raw/product/user-outreach-email-templates.md +0 -161
- package/dist/docs-raw/product/user-segments.md +0 -15
- package/dist/docs-raw/product/vision.md +0 -26
- package/dist/docs-raw/rl-environments.md +0 -350
- package/dist/docs-raw/runbook.md +0 -611
- package/dist-cli/check-compiler-U5SOPN7X.js.map +0 -1
- package/dist-cli/check-query-propagation-XOKNSSYU.js.map +0 -1
- package/dist-cli/chunk-EXWGNL6K.js.map +0 -1
- package/dist-cli/forgecad.js.map +0 -1
- package/dist-cli/forgecad_geometry-GYVNKPIE.js.map +0 -1
- package/dist-cli/solver-46FFSK2U.js.map +0 -1
- package/dist-skill/SKILL-dev.md +0 -145
- package/dist-skill/docs-dev/API/core/concepts.md +0 -118
- package/dist-skill/docs-dev/CLI.md +0 -677
- package/dist-skill/docs-dev/agent-native-api.md +0 -27
- package/dist-skill/docs-dev/blueprint-first.md +0 -145
- package/dist-skill/docs-dev/coding-best-practices.md +0 -120
- package/dist-skill/docs-dev/coding.md +0 -340
- package/dist-skill/docs-dev/component-model.md +0 -164
- package/dist-skill/docs-dev/generated/assembly.md +0 -794
- package/dist-skill/docs-dev/generated/core.md +0 -2117
- package/dist-skill/docs-dev/generated/curves.md +0 -2583
- package/dist-skill/docs-dev/generated/lib.md +0 -169
- package/dist-skill/docs-dev/generated/output.md +0 -247
- package/dist-skill/docs-dev/generated/sdf.md +0 -446
- package/dist-skill/docs-dev/generated/sheet-metal.md +0 -504
- package/dist-skill/docs-dev/generated/sketch.md +0 -1811
- package/dist-skill/docs-dev/generated/viewport.md +0 -585
- package/dist-skill/docs-dev/generated/wood.md +0 -108
- package/dist-skill/docs-dev/guides/coordinate-system.md +0 -46
- package/dist-skill/docs-dev/guides/geometry-conventions.md +0 -52
- package/dist-skill/docs-dev/guides/inspection-bundles.md +0 -485
- package/dist-skill/docs-dev/guides/joint-design.md +0 -78
- package/dist-skill/docs-dev/guides/modeling-recipes.md +0 -78
- package/dist-skill/docs-dev/guides/positioning.md +0 -161
- package/dist-skill/docs-dev/guides/skill-maintenance.md +0 -161
- package/dist-skill/docs-dev/internals/backend-vocabulary.md +0 -35
- package/dist-skill/docs-dev/internals/compiler.md +0 -307
- package/dist-skill/docs-dev/internals/constraint-solver-quality.md +0 -161
- package/dist-skill/docs-dev/internals/constraint-solver.md +0 -176
- package/dist-skill/docs-dev/internals/sketch-2d-pipeline.md +0 -108
- package/dist-skill/library/forgecad-image-replicator/scripts/compare_images.mjs +0 -289
package/dist-cli/forgecad.js
CHANGED
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
buildDesignTraceNeighborhood,
|
|
30
30
|
buildEdgeSvg,
|
|
31
31
|
buildObjString,
|
|
32
|
+
buildSketchFromCompileProfilePlan,
|
|
32
33
|
chamferTrackedEdge,
|
|
33
34
|
circle2d,
|
|
34
35
|
collectProjectFiles,
|
|
@@ -72,6 +73,8 @@ import {
|
|
|
72
73
|
getSketchPlacementModel,
|
|
73
74
|
getSketchWorkplane,
|
|
74
75
|
getSolverStats,
|
|
76
|
+
getTruckGeometryWasm,
|
|
77
|
+
getTruckShapeBackendHandle,
|
|
75
78
|
group,
|
|
76
79
|
init,
|
|
77
80
|
initKernel,
|
|
@@ -82,6 +85,7 @@ import {
|
|
|
82
85
|
intersection,
|
|
83
86
|
intersection2d,
|
|
84
87
|
isCompiledBinary,
|
|
88
|
+
isConstraintSketch,
|
|
85
89
|
isDirectCliRun,
|
|
86
90
|
isOCCTShapeBackend,
|
|
87
91
|
jointsView,
|
|
@@ -121,7 +125,7 @@ import {
|
|
|
121
125
|
union2d,
|
|
122
126
|
updateConstraintValue,
|
|
123
127
|
wrapOCCTShapeBackend
|
|
124
|
-
} from "./chunk-
|
|
128
|
+
} from "./chunk-N4O47JLF.js";
|
|
125
129
|
|
|
126
130
|
// cli/forgecad.ts
|
|
127
131
|
import { Command, Flags, Help, flush as flushOclif, handle as handleOclif, run as runOclif } from "@oclif/core";
|
|
@@ -3469,7 +3473,7 @@ function testCaseLayoutWindingOrder() {
|
|
|
3469
3473
|
assertConverged(result, "caseLayoutWinding");
|
|
3470
3474
|
const def = result.definition;
|
|
3471
3475
|
const ptMap = new Map(def.points.map((p) => [p.id, p]));
|
|
3472
|
-
function
|
|
3476
|
+
function signedArea2(vertices) {
|
|
3473
3477
|
const pts = vertices.map((id) => ptMap.get(id)).filter(Boolean);
|
|
3474
3478
|
let area = 0;
|
|
3475
3479
|
for (let i = 0; i < pts.length; i++) {
|
|
@@ -3492,7 +3496,7 @@ function testCaseLayoutWindingOrder() {
|
|
|
3492
3496
|
{ name: "wrapper", v: wrapper.vertices }
|
|
3493
3497
|
];
|
|
3494
3498
|
for (const { name, v } of rects) {
|
|
3495
|
-
const area =
|
|
3499
|
+
const area = signedArea2(v);
|
|
3496
3500
|
assert3(area > 0, `caseLayoutWinding: ${name} has CW winding (signed area=${area.toFixed(2)})`);
|
|
3497
3501
|
}
|
|
3498
3502
|
}
|
|
@@ -4112,6 +4116,11 @@ var API_EXACT_PART_PATHS = [
|
|
|
4112
4116
|
"examples/api/drive-wheel-regions.forge.js"
|
|
4113
4117
|
];
|
|
4114
4118
|
var API_FACETED_PARTS = [
|
|
4119
|
+
{
|
|
4120
|
+
path: "examples/api/route3d-elbow.forge.js",
|
|
4121
|
+
blocker: "The Route3D elbow still uses segmented circle profiles and segmented cylinders in its swept tube and flange cuts, so exact CadQuery/OCCT export remains blocked while runtime backends validate the route primitive.",
|
|
4122
|
+
note: "Route3D itself preserves line/arc route intent; this contract fences the surrounding faceted profile and flange geometry."
|
|
4123
|
+
},
|
|
4115
4124
|
{
|
|
4116
4125
|
path: "examples/api/profile-2020-b-slot6.forge.js",
|
|
4117
4126
|
blocker: "The direct 3D profile helper still lowers through segmented profile geometry, so the extrusion must stay on the faceted route for now.",
|
|
@@ -5029,8 +5038,8 @@ function applyNonPartExpectations(entryPath, result, expectations) {
|
|
|
5029
5038
|
assertMinimum(entryPath, uniqueGroups, expectations.minUniqueGroups, "named group(s)");
|
|
5030
5039
|
assertMinimum(entryPath, result.bom.length, expectations.minBomEntries, "BOM entrie(s)");
|
|
5031
5040
|
assertMinimum(entryPath, result.cutPlanes.length, expectations.minCutPlanes, "cut plane(s)");
|
|
5032
|
-
assertMinimum(entryPath, jointCount, expectations.minJoints, "
|
|
5033
|
-
assertMinimum(entryPath, animationCount, expectations.minAnimations, "
|
|
5041
|
+
assertMinimum(entryPath, jointCount, expectations.minJoints, "joint control(s)");
|
|
5042
|
+
assertMinimum(entryPath, animationCount, expectations.minAnimations, "joint animation(s)");
|
|
5034
5043
|
if (expectations.requireRobotExport || expectations.minRobotParts != null || expectations.minRobotJoints != null) {
|
|
5035
5044
|
assert4(result.robotExport, `${entryPath}: expected robotExport(...) data to stay available to the example gate`);
|
|
5036
5045
|
if (!result.robotExport) return;
|
|
@@ -5510,9 +5519,10 @@ function replaceCliInputExtension(path3, newExt) {
|
|
|
5510
5519
|
}
|
|
5511
5520
|
function buildDirectCadImportScript(fileName, kind) {
|
|
5512
5521
|
const importFn = kind === "step" ? "importStep" : "importMesh";
|
|
5522
|
+
const importOptions = kind === "mesh" && extname(fileName).toLowerCase() === ".3mf" ? ", { separateObjects: true }" : "";
|
|
5513
5523
|
const objectName = basename3(fileName);
|
|
5514
5524
|
return [
|
|
5515
|
-
`const imported = ${importFn}(${JSON.stringify(fileName)});`,
|
|
5525
|
+
`const imported = ${importFn}(${JSON.stringify(fileName)}${importOptions});`,
|
|
5516
5526
|
`return [{ name: ${JSON.stringify(objectName)}, shape: imported }];`
|
|
5517
5527
|
].join("\n");
|
|
5518
5528
|
}
|
|
@@ -7042,8 +7052,9 @@ var CHROME_PATHS = [
|
|
|
7042
7052
|
"/Applications/Chromium.app/Contents/MacOS/Chromium",
|
|
7043
7053
|
"/Applications/Brave Browser.app/Contents/MacOS/Brave Browser"
|
|
7044
7054
|
];
|
|
7045
|
-
var DEFAULT_PORT = parseInt(process.env.FORGE_PORT
|
|
7055
|
+
var DEFAULT_PORT = process.env.FORGE_PORT ? parseInt(process.env.FORGE_PORT, 10) : null;
|
|
7046
7056
|
var DEFAULT_SIZE = parseInt(process.env.FORGE_SIZE || "1024", 10);
|
|
7057
|
+
var RENDERER_PORT_HELP = "Renderer port (default: private ephemeral; FORGE_PORT sets a reusable port)";
|
|
7047
7058
|
var INSPECT_VIEWS = ["front", "right", "top", "iso"];
|
|
7048
7059
|
var INSPECT_EVIDENCE_DEFINITIONS = [
|
|
7049
7060
|
{
|
|
@@ -7070,6 +7081,12 @@ var INSPECT_EVIDENCE_DEFINITIONS = [
|
|
|
7070
7081
|
summary: "Capture surface normal evidence.",
|
|
7071
7082
|
description: "Writes view-space normal maps for spotting orientation changes, faceting, and surface direction problems."
|
|
7072
7083
|
},
|
|
7084
|
+
{
|
|
7085
|
+
name: "rig",
|
|
7086
|
+
channel: "rig",
|
|
7087
|
+
summary: "Capture kinematic rig skeleton evidence.",
|
|
7088
|
+
description: "Writes bright joint, axis, ring, and link overlays with source geometry reduced to a translucent shadow."
|
|
7089
|
+
},
|
|
7073
7090
|
{
|
|
7074
7091
|
name: "zebra",
|
|
7075
7092
|
channel: "zebra",
|
|
@@ -7138,7 +7155,8 @@ var INSPECT_EVIDENCE_BY_NAME = new Map(
|
|
|
7138
7155
|
INSPECT_EVIDENCE_DEFINITIONS.flatMap((entry) => [[entry.name, entry], ...(entry.aliases ?? []).map((alias) => [alias, entry])])
|
|
7139
7156
|
);
|
|
7140
7157
|
var INSPECT_EVIDENCE_BY_CHANNEL = new Map(INSPECT_EVIDENCE_DEFINITIONS.map((entry) => [entry.channel, entry]));
|
|
7141
|
-
var RENDER_STYLE_NAMES = ["classic", "studio", "fast", "glass", "inspection", "
|
|
7158
|
+
var RENDER_STYLE_NAMES = ["classic", "studio", "fast", "glass", "inspection", "contour", "scan"];
|
|
7159
|
+
var LEGACY_RENDER_STYLE_ALIASES = /* @__PURE__ */ new Map([["precision", "contour"]]);
|
|
7142
7160
|
var RENDER_STYLES = new Set(RENDER_STYLE_NAMES);
|
|
7143
7161
|
var RENDER_STYLE_LABEL = RENDER_STYLE_NAMES.join("|");
|
|
7144
7162
|
var SCAN_GRANULARITY_MIN = 12;
|
|
@@ -7180,14 +7198,14 @@ Options:
|
|
|
7180
7198
|
--render-mode <solid|wireframe> Render as shaded solid (default) or wireframe only
|
|
7181
7199
|
--edges <off|thin|bold> Edge overlay preset for solid mode (default: off)
|
|
7182
7200
|
--backend <manifold|occt|truck> Geometry backend (auto-selects OCCT for direct STEP inputs)
|
|
7183
|
-
--port <n>
|
|
7184
|
-
--fresh-server Start
|
|
7201
|
+
--port <n> ${RENDERER_PORT_HELP}
|
|
7202
|
+
--fresh-server Start an isolated renderer instead of reusing the requested port
|
|
7185
7203
|
--chrome-path <path> Chrome executable path
|
|
7186
7204
|
-h, --help Show this help
|
|
7187
7205
|
|
|
7188
7206
|
Environment variables:
|
|
7189
7207
|
FORGE_SIZE=1024
|
|
7190
|
-
FORGE_PORT
|
|
7208
|
+
FORGE_PORT=<n>
|
|
7191
7209
|
CHROME_PATH=/path/to/chrome
|
|
7192
7210
|
|
|
7193
7211
|
Examples:
|
|
@@ -7213,6 +7231,7 @@ function splitCsv(value) {
|
|
|
7213
7231
|
}
|
|
7214
7232
|
function parseRenderStyle(value) {
|
|
7215
7233
|
if (RENDER_STYLES.has(value)) return value;
|
|
7234
|
+
if (LEGACY_RENDER_STYLE_ALIASES.has(value)) return LEGACY_RENDER_STYLE_ALIASES.get(value);
|
|
7216
7235
|
throw new Error(`--render-style must be one of ${RENDER_STYLE_NAMES.map((entry) => `'${entry}'`).join(", ")} (got '${value}')`);
|
|
7217
7236
|
}
|
|
7218
7237
|
function parseEdgesPreset(value) {
|
|
@@ -7332,8 +7351,8 @@ function inspectSectionModeUsage(mode) {
|
|
|
7332
7351
|
--quality <default|live|high> Mesh/render quality (default: default)
|
|
7333
7352
|
--backend <manifold|occt|truck> Geometry backend (auto-selects OCCT for direct STEP inputs)
|
|
7334
7353
|
--force Replace the requested output directory instead of allocating a sibling
|
|
7335
|
-
--port <n>
|
|
7336
|
-
--fresh-server Start
|
|
7354
|
+
--port <n> ${RENDERER_PORT_HELP}
|
|
7355
|
+
--fresh-server Start an isolated renderer instead of reusing the requested port
|
|
7337
7356
|
--chrome-path <path> Chrome executable path
|
|
7338
7357
|
-h, --help Show this help`;
|
|
7339
7358
|
const output = `Output:
|
|
@@ -7452,6 +7471,7 @@ function parseRenderCliOptions(argv) {
|
|
|
7452
7471
|
const cameras = [];
|
|
7453
7472
|
let size = DEFAULT_SIZE;
|
|
7454
7473
|
let port = DEFAULT_PORT;
|
|
7474
|
+
let portRequested = DEFAULT_PORT != null;
|
|
7455
7475
|
let chromePath = process.env.CHROME_PATH;
|
|
7456
7476
|
let cameraSpec;
|
|
7457
7477
|
let viewName;
|
|
@@ -7525,6 +7545,7 @@ function parseRenderCliOptions(argv) {
|
|
|
7525
7545
|
}
|
|
7526
7546
|
if (arg === "--port") {
|
|
7527
7547
|
port = parseInt(readValue2(argv, i, arg), 10);
|
|
7548
|
+
portRequested = true;
|
|
7528
7549
|
i += 1;
|
|
7529
7550
|
continue;
|
|
7530
7551
|
}
|
|
@@ -7598,7 +7619,7 @@ function parseRenderCliOptions(argv) {
|
|
|
7598
7619
|
if (!Number.isFinite(size) || size < 128 || size > 4096) {
|
|
7599
7620
|
throw new Error(`--size must be between 128 and 4096 (got ${size})`);
|
|
7600
7621
|
}
|
|
7601
|
-
if (!Number.isFinite(port) || port < 1 || port > 65535) {
|
|
7622
|
+
if (port != null && (!Number.isFinite(port) || port < 1 || port > 65535)) {
|
|
7602
7623
|
throw new Error(`--port must be between 1 and 65535 (got ${port})`);
|
|
7603
7624
|
}
|
|
7604
7625
|
if (cameraSpec && cameras.length > 0) {
|
|
@@ -7628,6 +7649,7 @@ function parseRenderCliOptions(argv) {
|
|
|
7628
7649
|
cameras,
|
|
7629
7650
|
size,
|
|
7630
7651
|
port,
|
|
7652
|
+
portRequested,
|
|
7631
7653
|
chromePath: resolveChromePath(chromePath),
|
|
7632
7654
|
cameraSpec,
|
|
7633
7655
|
viewName,
|
|
@@ -7677,8 +7699,8 @@ Common options:
|
|
|
7677
7699
|
--edges <off|thin|bold> Edge overlay preset for solid visual evidence (default: thin)
|
|
7678
7700
|
--backend <manifold|occt|truck> Geometry backend (auto-selects OCCT for direct STEP inputs)
|
|
7679
7701
|
--force Replace the requested output directory instead of allocating a sibling
|
|
7680
|
-
--port <n>
|
|
7681
|
-
--fresh-server Start
|
|
7702
|
+
--port <n> ${RENDERER_PORT_HELP}
|
|
7703
|
+
--fresh-server Start an isolated renderer instead of reusing the requested port
|
|
7682
7704
|
--chrome-path <path> Chrome executable path
|
|
7683
7705
|
-h, --help Show this help
|
|
7684
7706
|
|
|
@@ -7692,6 +7714,7 @@ Examples:
|
|
|
7692
7714
|
cutaway: "visual cutaway",
|
|
7693
7715
|
depth: "visual depth",
|
|
7694
7716
|
normals: "visual normals",
|
|
7717
|
+
rig: "visual rig",
|
|
7695
7718
|
zebra: "surface zebra",
|
|
7696
7719
|
roughness: "surface roughness",
|
|
7697
7720
|
objects: "visual objects",
|
|
@@ -7734,8 +7757,8 @@ Options:
|
|
|
7734
7757
|
--edges <off|thin|bold> Edge overlay preset for solid visual evidence (default: thin)
|
|
7735
7758
|
--backend <manifold|occt|truck> Geometry backend (auto-selects OCCT for direct STEP inputs)
|
|
7736
7759
|
--force Replace the requested output directory instead of allocating a sibling
|
|
7737
|
-
--port <n>
|
|
7738
|
-
--fresh-server Start
|
|
7760
|
+
--port <n> ${RENDERER_PORT_HELP}
|
|
7761
|
+
--fresh-server Start an isolated renderer instead of reusing the requested port
|
|
7739
7762
|
--chrome-path <path> Chrome executable path
|
|
7740
7763
|
-h, --help Show this help
|
|
7741
7764
|
|
|
@@ -7776,6 +7799,7 @@ function parseInspectCli(argv, config = {}) {
|
|
|
7776
7799
|
const cameras = [];
|
|
7777
7800
|
let size = DEFAULT_SIZE;
|
|
7778
7801
|
let port = DEFAULT_PORT;
|
|
7802
|
+
let portRequested = DEFAULT_PORT != null;
|
|
7779
7803
|
let chromePath = process.env.CHROME_PATH;
|
|
7780
7804
|
let quality = "default";
|
|
7781
7805
|
let backend;
|
|
@@ -8042,6 +8066,7 @@ function parseInspectCli(argv, config = {}) {
|
|
|
8042
8066
|
}
|
|
8043
8067
|
if (arg === "--port") {
|
|
8044
8068
|
port = parseInt(readValue2(argv, i, arg), 10);
|
|
8069
|
+
portRequested = true;
|
|
8045
8070
|
i += 1;
|
|
8046
8071
|
continue;
|
|
8047
8072
|
}
|
|
@@ -8077,7 +8102,7 @@ function parseInspectCli(argv, config = {}) {
|
|
|
8077
8102
|
if (!Number.isFinite(size) || size < 128 || size > 4096) {
|
|
8078
8103
|
throw new Error(`--size must be between 128 and 4096 (got ${size})`);
|
|
8079
8104
|
}
|
|
8080
|
-
if (!Number.isFinite(port) || port < 1 || port > 65535) {
|
|
8105
|
+
if (port != null && (!Number.isFinite(port) || port < 1 || port > 65535)) {
|
|
8081
8106
|
throw new Error(`--port must be between 1 and 65535 (got ${port})`);
|
|
8082
8107
|
}
|
|
8083
8108
|
if (cameraSpec && cameras.length > 0) {
|
|
@@ -8159,6 +8184,7 @@ function parseInspectCli(argv, config = {}) {
|
|
|
8159
8184
|
edges,
|
|
8160
8185
|
size,
|
|
8161
8186
|
port,
|
|
8187
|
+
portRequested,
|
|
8162
8188
|
chromePath: resolveChromePath(chromePath),
|
|
8163
8189
|
quality,
|
|
8164
8190
|
backend,
|
|
@@ -8436,6 +8462,7 @@ function collectInspectViewNames(emittedPaths) {
|
|
|
8436
8462
|
add2(emittedPaths.cutaway);
|
|
8437
8463
|
add2(emittedPaths.depth);
|
|
8438
8464
|
add2(emittedPaths.normals);
|
|
8465
|
+
add2(emittedPaths.rig);
|
|
8439
8466
|
add2(emittedPaths.zebra);
|
|
8440
8467
|
add2(emittedPaths.roughness);
|
|
8441
8468
|
add2(emittedPaths.mask);
|
|
@@ -8576,6 +8603,23 @@ function buildInspectManifest({ options, result, scriptPath, projectRoot, emitte
|
|
|
8576
8603
|
views: buildViewPathEntries(emittedPaths.zebra)
|
|
8577
8604
|
};
|
|
8578
8605
|
}
|
|
8606
|
+
if (emittedPaths.rig) {
|
|
8607
|
+
evidence.rig = {
|
|
8608
|
+
format: "png",
|
|
8609
|
+
encoding: "rgba-over-dark-shadow-rig-skeleton",
|
|
8610
|
+
coordinateSpace: "world",
|
|
8611
|
+
decode: "source geometry is rendered as a translucent shadow; bright cylinders show hierarchy and part links; oriented rings and axes show joint pivots and motion directions",
|
|
8612
|
+
method: result.rig?.method ?? "rig-skeleton-shadow-v1",
|
|
8613
|
+
objectCount: result.rig?.objectCount ?? 0,
|
|
8614
|
+
jointCount: result.rig?.jointCount ?? 0,
|
|
8615
|
+
linkCount: result.rig?.linkCount ?? 0,
|
|
8616
|
+
hiddenJointCount: result.rig?.hiddenJointCount ?? 0,
|
|
8617
|
+
palette: result.rig?.palette,
|
|
8618
|
+
joints: result.rig?.joints ?? [],
|
|
8619
|
+
links: result.rig?.links ?? [],
|
|
8620
|
+
views: buildViewPathEntries(emittedPaths.rig)
|
|
8621
|
+
};
|
|
8622
|
+
}
|
|
8579
8623
|
if (emittedPaths.roughness) {
|
|
8580
8624
|
evidence.roughness = {
|
|
8581
8625
|
format: "png",
|
|
@@ -8854,23 +8898,6 @@ function buildInspectManifest({ options, result, scriptPath, projectRoot, emitte
|
|
|
8854
8898
|
evidence
|
|
8855
8899
|
};
|
|
8856
8900
|
}
|
|
8857
|
-
async function isPortFree(port) {
|
|
8858
|
-
return new Promise((resolve40) => {
|
|
8859
|
-
const server = createNetServer();
|
|
8860
|
-
server.once("error", () => resolve40(false));
|
|
8861
|
-
server.once("listening", () => {
|
|
8862
|
-
server.close();
|
|
8863
|
-
resolve40(true);
|
|
8864
|
-
});
|
|
8865
|
-
server.listen(port, "127.0.0.1");
|
|
8866
|
-
});
|
|
8867
|
-
}
|
|
8868
|
-
async function findFreePort(startPort) {
|
|
8869
|
-
for (let port = startPort; port <= 65535; port += 1) {
|
|
8870
|
-
if (await isPortFree(port)) return port;
|
|
8871
|
-
}
|
|
8872
|
-
return null;
|
|
8873
|
-
}
|
|
8874
8901
|
var viteProcess = null;
|
|
8875
8902
|
var staticServer = null;
|
|
8876
8903
|
var STATIC_MIME = {
|
|
@@ -8887,6 +8914,29 @@ var STATIC_MIME = {
|
|
|
8887
8914
|
};
|
|
8888
8915
|
var PUPPETEER_PROTOCOL_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
8889
8916
|
var RENDER_READY_TIMEOUT_MS = 30 * 1e3;
|
|
8917
|
+
var VITE_STARTUP_TIMEOUT_MS = 30 * 1e3;
|
|
8918
|
+
var PRIVATE_RENDER_SERVER_ATTEMPTS = 5;
|
|
8919
|
+
var STARTUP_OUTPUT_LIMIT = 4e3;
|
|
8920
|
+
function sleep(ms) {
|
|
8921
|
+
return new Promise((resolveSleep) => setTimeout(resolveSleep, ms));
|
|
8922
|
+
}
|
|
8923
|
+
async function reserveEphemeralPort() {
|
|
8924
|
+
return new Promise((resolvePort, reject) => {
|
|
8925
|
+
const server = createNetServer();
|
|
8926
|
+
server.once("error", reject);
|
|
8927
|
+
server.once("listening", () => {
|
|
8928
|
+
const address = server.address();
|
|
8929
|
+
server.close(() => {
|
|
8930
|
+
if (address && typeof address === "object") {
|
|
8931
|
+
resolvePort(address.port);
|
|
8932
|
+
} else {
|
|
8933
|
+
reject(new Error("Failed to reserve a private renderer port."));
|
|
8934
|
+
}
|
|
8935
|
+
});
|
|
8936
|
+
});
|
|
8937
|
+
server.listen(0, "127.0.0.1");
|
|
8938
|
+
});
|
|
8939
|
+
}
|
|
8890
8940
|
async function fetchRenderHtml(port) {
|
|
8891
8941
|
const ctrl = new AbortController();
|
|
8892
8942
|
const timer = setTimeout(() => ctrl.abort(), 1200);
|
|
@@ -8901,6 +8951,18 @@ async function fetchRenderHtml(port) {
|
|
|
8901
8951
|
clearTimeout(timer);
|
|
8902
8952
|
}
|
|
8903
8953
|
}
|
|
8954
|
+
async function waitForRenderHtml(port, timeoutMs) {
|
|
8955
|
+
const deadline = Date.now() + timeoutMs;
|
|
8956
|
+
while (Date.now() < deadline) {
|
|
8957
|
+
if (await fetchRenderHtml(port)) return true;
|
|
8958
|
+
await sleep(250);
|
|
8959
|
+
}
|
|
8960
|
+
return false;
|
|
8961
|
+
}
|
|
8962
|
+
function isAddressInUse(value) {
|
|
8963
|
+
const message = value instanceof Error ? `${value.message} ${value.code ?? ""}` : String(value ?? "");
|
|
8964
|
+
return /EADDRINUSE|address already in use|port \d+ is already in use/i.test(message);
|
|
8965
|
+
}
|
|
8904
8966
|
function serveStaticFile(distDir, req, res) {
|
|
8905
8967
|
const root = resolve8(distDir);
|
|
8906
8968
|
const requestUrl = new URL(req.url || "/", "http://localhost");
|
|
@@ -8930,58 +8992,147 @@ async function startStaticRenderServer(port) {
|
|
|
8930
8992
|
const server = createHttpServer((req, res) => serveStaticFile(distDir, req, res));
|
|
8931
8993
|
await new Promise((resolveListen, reject) => {
|
|
8932
8994
|
server.once("error", reject);
|
|
8933
|
-
server.listen(port, "127.0.0.1", resolveListen);
|
|
8995
|
+
server.listen(port ?? 0, "127.0.0.1", resolveListen);
|
|
8934
8996
|
});
|
|
8997
|
+
const address = server.address();
|
|
8998
|
+
if (!address || typeof address === "string") {
|
|
8999
|
+
server.close();
|
|
9000
|
+
throw new Error("Packaged render server started without a TCP port.");
|
|
9001
|
+
}
|
|
8935
9002
|
staticServer = server;
|
|
9003
|
+
return address.port;
|
|
8936
9004
|
}
|
|
8937
|
-
async function
|
|
8938
|
-
|
|
8939
|
-
|
|
8940
|
-
|
|
8941
|
-
|
|
9005
|
+
async function startViteRenderServer(port, { allowReuseAfterConflict = false } = {}) {
|
|
9006
|
+
const packageRoot = packageRootFrom(import.meta.url);
|
|
9007
|
+
const proc = spawnPackageVite(import.meta.url, ["--host", "127.0.0.1", "--port", String(port), "--strictPort"], {
|
|
9008
|
+
cwd: packageRoot,
|
|
9009
|
+
stdio: "pipe",
|
|
9010
|
+
detached: false
|
|
9011
|
+
});
|
|
9012
|
+
let startupOutput = "";
|
|
9013
|
+
let exitedEarly = false;
|
|
9014
|
+
let exitCode = null;
|
|
9015
|
+
let exitSignal = null;
|
|
9016
|
+
let spawnError = null;
|
|
9017
|
+
const captureOutput = (chunk) => {
|
|
9018
|
+
startupOutput += String(chunk);
|
|
9019
|
+
if (startupOutput.length > STARTUP_OUTPUT_LIMIT) {
|
|
9020
|
+
startupOutput = startupOutput.slice(-STARTUP_OUTPUT_LIMIT);
|
|
9021
|
+
}
|
|
9022
|
+
};
|
|
9023
|
+
proc.stdout?.on("data", captureOutput);
|
|
9024
|
+
proc.stderr?.on("data", captureOutput);
|
|
9025
|
+
proc.once("error", (error) => {
|
|
9026
|
+
spawnError = error;
|
|
9027
|
+
captureOutput(`
|
|
9028
|
+
${error.message}`);
|
|
9029
|
+
});
|
|
9030
|
+
proc.once("exit", (code, signal) => {
|
|
9031
|
+
exitedEarly = true;
|
|
9032
|
+
exitCode = code;
|
|
9033
|
+
exitSignal = signal;
|
|
9034
|
+
});
|
|
9035
|
+
const deadline = Date.now() + VITE_STARTUP_TIMEOUT_MS;
|
|
9036
|
+
while (Date.now() < deadline) {
|
|
9037
|
+
if (await fetchRenderHtml(port)) {
|
|
9038
|
+
return { ok: true, proc, startupOutput };
|
|
9039
|
+
}
|
|
9040
|
+
if (spawnError || exitedEarly) {
|
|
9041
|
+
if (allowReuseAfterConflict && isAddressInUse(startupOutput) && await waitForRenderHtml(port, 5e3)) {
|
|
9042
|
+
return { ok: true, proc: null, startupOutput, reusedAfterConflict: true };
|
|
9043
|
+
}
|
|
9044
|
+
return { ok: false, proc: null, startupOutput, spawnError, exitedEarly: true, exitCode, exitSignal };
|
|
9045
|
+
}
|
|
9046
|
+
await sleep(250);
|
|
9047
|
+
}
|
|
9048
|
+
proc.kill();
|
|
9049
|
+
return { ok: false, proc: null, startupOutput, timedOut: true };
|
|
9050
|
+
}
|
|
9051
|
+
function viteStartupError(port, result) {
|
|
9052
|
+
const detail = result.startupOutput.trim();
|
|
9053
|
+
const suffix = detail ? `
|
|
9054
|
+
${detail}` : "";
|
|
9055
|
+
if (result.spawnError) {
|
|
9056
|
+
return new Error(`Failed to start Vite render server on :${port}: ${result.spawnError.message}${suffix}`);
|
|
9057
|
+
}
|
|
9058
|
+
if (result.timedOut) {
|
|
9059
|
+
return new Error(`Timed out waiting for ForgeCAD render server on :${port}.${suffix}`);
|
|
9060
|
+
}
|
|
9061
|
+
if (result.exitedEarly) {
|
|
9062
|
+
const code = result.exitCode != null ? `code ${result.exitCode}` : result.exitSignal ? `signal ${result.exitSignal}` : "unknown exit";
|
|
9063
|
+
return new Error(`Vite render server on :${port} exited before becoming ready (${code}).${suffix}`);
|
|
9064
|
+
}
|
|
9065
|
+
return new Error(`Failed to start ForgeCAD render server on :${port}.${suffix}`);
|
|
9066
|
+
}
|
|
9067
|
+
async function startPrivateViteRenderServer() {
|
|
9068
|
+
let lastError = null;
|
|
9069
|
+
for (let attempt = 1; attempt <= PRIVATE_RENDER_SERVER_ATTEMPTS; attempt += 1) {
|
|
9070
|
+
const port = await reserveEphemeralPort();
|
|
9071
|
+
console.log(`Starting private ForgeCAD render server on :${port} ...`);
|
|
9072
|
+
const result = await startViteRenderServer(port);
|
|
9073
|
+
if (result.ok) {
|
|
9074
|
+
viteProcess = result.proc;
|
|
9075
|
+
return port;
|
|
9076
|
+
}
|
|
9077
|
+
const error = viteStartupError(port, result);
|
|
9078
|
+
if (isAddressInUse(error)) {
|
|
9079
|
+
lastError = error;
|
|
9080
|
+
continue;
|
|
9081
|
+
}
|
|
9082
|
+
throw error;
|
|
9083
|
+
}
|
|
9084
|
+
throw lastError ?? new Error("Failed to start a private ForgeCAD render server.");
|
|
9085
|
+
}
|
|
9086
|
+
async function startConfiguredViteRenderServer(port, { fresh = false, allowReuse = false } = {}) {
|
|
9087
|
+
if (allowReuse && !fresh && await fetchRenderHtml(port)) {
|
|
9088
|
+
console.log(`Reusing existing ForgeCAD render server on :${port}.`);
|
|
9089
|
+
return port;
|
|
8942
9090
|
}
|
|
8943
|
-
|
|
8944
|
-
|
|
8945
|
-
|
|
8946
|
-
|
|
9091
|
+
console.log(`${fresh ? "Starting fresh" : "Starting"} ForgeCAD render server on :${port} ...`);
|
|
9092
|
+
const result = await startViteRenderServer(port, { allowReuseAfterConflict: allowReuse && !fresh });
|
|
9093
|
+
if (result.ok) {
|
|
9094
|
+
if (result.reusedAfterConflict) {
|
|
9095
|
+
console.log(`Reusing ForgeCAD render server that became ready on :${port}.`);
|
|
8947
9096
|
}
|
|
8948
|
-
|
|
8949
|
-
|
|
8950
|
-
|
|
9097
|
+
viteProcess = result.proc;
|
|
9098
|
+
return port;
|
|
9099
|
+
}
|
|
9100
|
+
if (fresh && isAddressInUse(result.startupOutput)) {
|
|
9101
|
+
console.log(`Port ${port} is occupied; starting a private ForgeCAD render server instead.`);
|
|
9102
|
+
return startPrivateViteRenderServer();
|
|
9103
|
+
}
|
|
9104
|
+
throw viteStartupError(port, result);
|
|
9105
|
+
}
|
|
9106
|
+
async function startPackagedRenderServer(port, { fresh = false, allowReuse = false } = {}) {
|
|
9107
|
+
if (port != null && allowReuse && !fresh && await fetchRenderHtml(port)) {
|
|
9108
|
+
console.log(`Reusing existing ForgeCAD render server on :${port}.`);
|
|
9109
|
+
return port;
|
|
9110
|
+
}
|
|
9111
|
+
try {
|
|
9112
|
+
const activePort = await startStaticRenderServer(port);
|
|
9113
|
+
console.log(`Starting packaged ForgeCAD render server on :${activePort}.`);
|
|
9114
|
+
return activePort;
|
|
9115
|
+
} catch (error) {
|
|
9116
|
+
if (port != null && fresh && isAddressInUse(error)) {
|
|
9117
|
+
console.log(`Port ${port} is occupied; starting a private packaged ForgeCAD render server instead.`);
|
|
9118
|
+
const activePort = await startStaticRenderServer(null);
|
|
9119
|
+
console.log(`Starting packaged ForgeCAD render server on :${activePort}.`);
|
|
9120
|
+
return activePort;
|
|
8951
9121
|
}
|
|
8952
|
-
|
|
8953
|
-
activePort = fallbackPort;
|
|
8954
|
-
} else if (fresh) {
|
|
8955
|
-
console.log(`Starting fresh ForgeCAD render server on :${activePort}.`);
|
|
9122
|
+
throw error;
|
|
8956
9123
|
}
|
|
9124
|
+
}
|
|
9125
|
+
async function ensureDevServer(port, { fresh = false, allowReuse = false } = {}) {
|
|
8957
9126
|
const packageRoot = packageRootFrom(import.meta.url);
|
|
8958
9127
|
const sourceRenderHtml = join4(packageRoot, "cli", "render.html");
|
|
8959
9128
|
const packagedRenderHtml = join4(packageRoot, "dist", "cli", "render.html");
|
|
8960
9129
|
if (!existsSync3(sourceRenderHtml) && existsSync3(packagedRenderHtml)) {
|
|
8961
|
-
|
|
8962
|
-
await startStaticRenderServer(activePort);
|
|
8963
|
-
return activePort;
|
|
9130
|
+
return startPackagedRenderServer(port, { fresh, allowReuse });
|
|
8964
9131
|
}
|
|
8965
|
-
|
|
8966
|
-
|
|
8967
|
-
|
|
8968
|
-
|
|
8969
|
-
detached: false
|
|
8970
|
-
});
|
|
8971
|
-
await new Promise((resolve40, reject) => {
|
|
8972
|
-
const timeout = setTimeout(() => reject(new Error("Vite startup timeout")), 15e3);
|
|
8973
|
-
viteProcess.stdout.on("data", (data) => {
|
|
8974
|
-
if (data.toString().includes("ready")) {
|
|
8975
|
-
clearTimeout(timeout);
|
|
8976
|
-
resolve40();
|
|
8977
|
-
}
|
|
8978
|
-
});
|
|
8979
|
-
viteProcess.on("error", (e) => {
|
|
8980
|
-
clearTimeout(timeout);
|
|
8981
|
-
reject(e);
|
|
8982
|
-
});
|
|
8983
|
-
});
|
|
8984
|
-
return activePort;
|
|
9132
|
+
if (port == null) {
|
|
9133
|
+
return startPrivateViteRenderServer();
|
|
9134
|
+
}
|
|
9135
|
+
return startConfiguredViteRenderServer(port, { fresh, allowReuse });
|
|
8985
9136
|
}
|
|
8986
9137
|
function stopDevServer() {
|
|
8987
9138
|
if (viteProcess) {
|
|
@@ -9014,7 +9165,10 @@ async function runRenderCli(argv = process.argv.slice(2)) {
|
|
|
9014
9165
|
const binaryFiles = collectBrowserBinaryFiles(input);
|
|
9015
9166
|
const needsBinaryFileReader = Object.keys(binaryFiles).length > 0;
|
|
9016
9167
|
const activeBackend = resolveCliBackend(options.backend, input) ?? CLI_DEFAULT_BACKEND;
|
|
9017
|
-
const activePort = await ensureDevServer(options.port, {
|
|
9168
|
+
const activePort = await ensureDevServer(options.port, {
|
|
9169
|
+
fresh: options.freshServer || needsBinaryFileReader,
|
|
9170
|
+
allowReuse: options.portRequested && !needsBinaryFileReader
|
|
9171
|
+
});
|
|
9018
9172
|
const browser = await puppeteer.launch({
|
|
9019
9173
|
headless: true,
|
|
9020
9174
|
protocolTimeout: PUPPETEER_PROTOCOL_TIMEOUT_MS,
|
|
@@ -9149,7 +9303,10 @@ async function runInspectBundleCli(argv, config) {
|
|
|
9149
9303
|
console.log(`[inspect] Preparing bundle directory: ${resolve8(options.outputDir)}`);
|
|
9150
9304
|
const bundleDirs = await prepareInspectBundleDir(options.outputDir, options.force, options.channels);
|
|
9151
9305
|
tempDir = bundleDirs.tempDir;
|
|
9152
|
-
const activePort = await ensureDevServer(options.port, {
|
|
9306
|
+
const activePort = await ensureDevServer(options.port, {
|
|
9307
|
+
fresh: options.freshServer || needsBinaryFileReader,
|
|
9308
|
+
allowReuse: options.portRequested && !needsBinaryFileReader
|
|
9309
|
+
});
|
|
9153
9310
|
const browser = await puppeteer.launch({
|
|
9154
9311
|
headless: true,
|
|
9155
9312
|
protocolTimeout: PUPPETEER_PROTOCOL_TIMEOUT_MS,
|
|
@@ -9316,6 +9473,9 @@ async function runInspectBundleCli(argv, config) {
|
|
|
9316
9473
|
if (requested.has("zebra")) {
|
|
9317
9474
|
await writeViewChannel("zebra", result.zebra);
|
|
9318
9475
|
}
|
|
9476
|
+
if (requested.has("rig")) {
|
|
9477
|
+
await writeViewChannel("rig", result.rig?.views);
|
|
9478
|
+
}
|
|
9319
9479
|
if (requested.has("roughness")) {
|
|
9320
9480
|
await writeViewChannel("roughness", result.roughness?.views);
|
|
9321
9481
|
emittedPaths.roughnessPointCloud = await writeChannelJson("roughness", "point-cloud.json", result.roughness?.pointCloud, {
|
|
@@ -9898,11 +10058,15 @@ function parseTraceQueryFlags(argv) {
|
|
|
9898
10058
|
consumed.add(i);
|
|
9899
10059
|
const raw = argv[i + 1];
|
|
9900
10060
|
if (!raw || raw.startsWith("-")) {
|
|
9901
|
-
console.error(
|
|
10061
|
+
console.error(
|
|
10062
|
+
"Missing value for --query. Expected a trace lens such as feature:hole, source:main.forge.js:42, trace:<id>, or cache:hits."
|
|
10063
|
+
);
|
|
9902
10064
|
process.exit(1);
|
|
9903
10065
|
}
|
|
9904
10066
|
consumed.add(i + 1);
|
|
9905
|
-
queries.push(
|
|
10067
|
+
queries.push(
|
|
10068
|
+
...raw.split(",").map((query) => query.trim()).filter(Boolean)
|
|
10069
|
+
);
|
|
9906
10070
|
i += 1;
|
|
9907
10071
|
}
|
|
9908
10072
|
return { queries, consumed };
|
|
@@ -9918,7 +10082,7 @@ async function runInspectDesignTraceCli(argv = process.argv.slice(2)) {
|
|
|
9918
10082
|
argv.forEach((arg, index) => {
|
|
9919
10083
|
if (arg === "--anchor" || arg === "--full") consumed.add(index);
|
|
9920
10084
|
});
|
|
9921
|
-
const positional = argv.filter((
|
|
10085
|
+
const positional = argv.filter((_arg, index) => !consumed.has(index));
|
|
9922
10086
|
const scriptPath = positional[0];
|
|
9923
10087
|
if (!scriptPath) usage3();
|
|
9924
10088
|
if (positional.length > 1) {
|
|
@@ -9965,7 +10129,8 @@ function cacheNote(node) {
|
|
|
9965
10129
|
if (node.liveCacheStatus === "rebuilt") return `cache rebuilt in ${node.liveCacheBuildMs ?? 0}ms`;
|
|
9966
10130
|
if (node.liveCacheStatus === "miss") return "cache miss";
|
|
9967
10131
|
if (node.cacheRole === "reuseCandidate") return `structural reuse candidate, saved estimate ${node.cacheOpportunitySavedMs}ms`;
|
|
9968
|
-
if (node.cacheRole === "seed" && node.duplicateGeometryCount > 1)
|
|
10132
|
+
if (node.cacheRole === "seed" && node.duplicateGeometryCount > 1)
|
|
10133
|
+
return `repeated geometry seed, ${node.duplicateGeometryCount} equivalent nodes`;
|
|
9969
10134
|
return void 0;
|
|
9970
10135
|
}
|
|
9971
10136
|
function toHistoryStep(trace, node) {
|
|
@@ -10002,7 +10167,9 @@ function repeatedGeometryGroups(nodes) {
|
|
|
10002
10167
|
function projectDesignHistory(trace, options = {}) {
|
|
10003
10168
|
const objectQueries = (options.objectQueries ?? []).map(normalize).filter(Boolean);
|
|
10004
10169
|
const nodeQueries = (options.nodeQueries ?? []).map(normalize).filter(Boolean);
|
|
10005
|
-
const matchingNodeIds = new Set(
|
|
10170
|
+
const matchingNodeIds = new Set(
|
|
10171
|
+
(nodeQueries.length > 0 ? filterDesignTraceNodes(trace, nodeQueries) : trace.nodes).map((node) => node.id)
|
|
10172
|
+
);
|
|
10006
10173
|
const nodesByObjectId = /* @__PURE__ */ new Map();
|
|
10007
10174
|
for (const node of trace.nodes) {
|
|
10008
10175
|
const objectNodes = nodesByObjectId.get(node.objectId) ?? [];
|
|
@@ -10075,7 +10242,9 @@ function printStep(step, detailed, options) {
|
|
|
10075
10242
|
if (summary) console.log(` uses: ${summary}`);
|
|
10076
10243
|
return;
|
|
10077
10244
|
}
|
|
10078
|
-
console.log(
|
|
10245
|
+
console.log(
|
|
10246
|
+
` inputs: ${step.inputRefs.length > 0 ? step.inputRefs.map((ref) => formatStepRef(ref, Boolean(options.printTraceIds))).join("; ") : "none"}`
|
|
10247
|
+
);
|
|
10079
10248
|
console.log(
|
|
10080
10249
|
` used by: ${step.usedByRefs.length > 0 ? step.usedByRefs.map((ref) => formatStepRef(ref, Boolean(options.printTraceIds))).join("; ") : "none"}`
|
|
10081
10250
|
);
|
|
@@ -10131,8 +10300,10 @@ Object recipe: ${pathLabel(object.path, object.name)}`);
|
|
|
10131
10300
|
} else if (selectedTraceIds.length === 0) {
|
|
10132
10301
|
console.log("\nHistory feedback anchor: no matching feature step.");
|
|
10133
10302
|
} else {
|
|
10134
|
-
console.log(
|
|
10135
|
-
|
|
10303
|
+
console.log(
|
|
10304
|
+
`
|
|
10305
|
+
History feedback anchor: ${selectedTraceIds.length} feature steps matched; narrow --query to one feature/source/trace node.`
|
|
10306
|
+
);
|
|
10136
10307
|
}
|
|
10137
10308
|
}
|
|
10138
10309
|
|
|
@@ -10155,7 +10326,9 @@ function parseListFlag(argv, flag, missingHelp) {
|
|
|
10155
10326
|
process.exit(1);
|
|
10156
10327
|
}
|
|
10157
10328
|
consumed.add(i + 1);
|
|
10158
|
-
values.push(
|
|
10329
|
+
values.push(
|
|
10330
|
+
...raw.split(",").map((value) => value.trim()).filter(Boolean)
|
|
10331
|
+
);
|
|
10159
10332
|
i += 1;
|
|
10160
10333
|
}
|
|
10161
10334
|
return { values, consumed };
|
|
@@ -10184,7 +10357,14 @@ async function runInspectHistoryCli(argv = process.argv.slice(2)) {
|
|
|
10184
10357
|
"--query",
|
|
10185
10358
|
"Missing value for --query. Expected feature:hole, source:main.forge.js:42, trace:<id>, cache:hits, or text."
|
|
10186
10359
|
);
|
|
10187
|
-
const consumed = /* @__PURE__ */ new Set([
|
|
10360
|
+
const consumed = /* @__PURE__ */ new Set([
|
|
10361
|
+
...paramConsumed,
|
|
10362
|
+
...backendConsumed,
|
|
10363
|
+
...qualityConsumed,
|
|
10364
|
+
...levelConsumed,
|
|
10365
|
+
...objectConsumed,
|
|
10366
|
+
...queryConsumed
|
|
10367
|
+
]);
|
|
10188
10368
|
const printAnchor = argv.includes("--anchor");
|
|
10189
10369
|
const printSource = argv.includes("--source");
|
|
10190
10370
|
const printTraceIds = argv.includes("--trace-ids");
|
|
@@ -10192,7 +10372,7 @@ async function runInspectHistoryCli(argv = process.argv.slice(2)) {
|
|
|
10192
10372
|
argv.forEach((arg, index) => {
|
|
10193
10373
|
if (arg === "--anchor" || arg === "--source" || arg === "--trace-ids" || arg === "--params") consumed.add(index);
|
|
10194
10374
|
});
|
|
10195
|
-
const positional = argv.filter((
|
|
10375
|
+
const positional = argv.filter((_arg, index) => !consumed.has(index));
|
|
10196
10376
|
const scriptPath = positional[0];
|
|
10197
10377
|
if (!scriptPath) usage4();
|
|
10198
10378
|
if (positional.length > 1) {
|
|
@@ -10772,7 +10952,7 @@ function auditAssembly(assembly2, rawOptions = {}) {
|
|
|
10772
10952
|
continue;
|
|
10773
10953
|
}
|
|
10774
10954
|
try {
|
|
10775
|
-
const frames = assembly2.
|
|
10955
|
+
const frames = assembly2._sweepJointForDiagnostics(joint2.name, joint2.min, joint2.max, options.sweepSteps, options.state, {
|
|
10776
10956
|
minOverlapVolume: options.minOverlapVolume
|
|
10777
10957
|
});
|
|
10778
10958
|
for (const frame of frames) {
|
|
@@ -13452,6 +13632,733 @@ async function runInspectReplayCli(argv = process.argv.slice(2)) {
|
|
|
13452
13632
|
printSectionSummary(result);
|
|
13453
13633
|
}
|
|
13454
13634
|
|
|
13635
|
+
// src/forge/inspection/sketch.ts
|
|
13636
|
+
var EPS5 = 1e-9;
|
|
13637
|
+
var BOUNDARY_EPS = 1e-7;
|
|
13638
|
+
function inspectSketches(objects, options = {}) {
|
|
13639
|
+
const targets = [];
|
|
13640
|
+
const diagnostics = [];
|
|
13641
|
+
for (const object of objects) {
|
|
13642
|
+
if (options.objectIds && !options.objectIds.has(object.id)) continue;
|
|
13643
|
+
const objectPath = object.treePath && object.treePath.length > 0 ? object.treePath.join("/") : object.name;
|
|
13644
|
+
if (object.sketch) {
|
|
13645
|
+
targets.push(
|
|
13646
|
+
inspectSketchTarget(object.sketch, {
|
|
13647
|
+
id: `${object.id}:sketch`,
|
|
13648
|
+
object,
|
|
13649
|
+
objectPath,
|
|
13650
|
+
sourceKind: "returned-sketch",
|
|
13651
|
+
options
|
|
13652
|
+
})
|
|
13653
|
+
);
|
|
13654
|
+
}
|
|
13655
|
+
if (object.shape) {
|
|
13656
|
+
const shapePlan = getShapeCompilePlan(object.shape);
|
|
13657
|
+
const profileUses = collectProfileUses(shapePlan);
|
|
13658
|
+
profileUses.forEach((use, index) => {
|
|
13659
|
+
targets.push(
|
|
13660
|
+
inspectProfileTarget(use.profile, {
|
|
13661
|
+
id: `${object.id}:profile:${index}`,
|
|
13662
|
+
object,
|
|
13663
|
+
objectPath,
|
|
13664
|
+
operation: use.operation,
|
|
13665
|
+
profilePath: use.path,
|
|
13666
|
+
options
|
|
13667
|
+
})
|
|
13668
|
+
);
|
|
13669
|
+
});
|
|
13670
|
+
}
|
|
13671
|
+
}
|
|
13672
|
+
if (targets.length === 0) {
|
|
13673
|
+
diagnostics.push({
|
|
13674
|
+
level: "info",
|
|
13675
|
+
code: "no-sketch-targets",
|
|
13676
|
+
message: "No returned sketches or supported profile-bearing shape operations were found."
|
|
13677
|
+
});
|
|
13678
|
+
}
|
|
13679
|
+
return { targets, diagnostics };
|
|
13680
|
+
}
|
|
13681
|
+
function inspectSketchTarget(sketch, args) {
|
|
13682
|
+
const diagnostics = [];
|
|
13683
|
+
const regions = extractRegionsForSketch(sketch, diagnostics);
|
|
13684
|
+
const bounds2 = readSketchBounds(sketch, diagnostics);
|
|
13685
|
+
const area = readSketchArea(sketch, diagnostics);
|
|
13686
|
+
addSketchHealthDiagnostics(sketch, regions, diagnostics);
|
|
13687
|
+
const target = {
|
|
13688
|
+
id: args.id,
|
|
13689
|
+
objectId: args.object.id,
|
|
13690
|
+
objectName: args.object.name,
|
|
13691
|
+
objectPath: args.objectPath,
|
|
13692
|
+
sourceKind: args.sourceKind,
|
|
13693
|
+
bounds: bounds2,
|
|
13694
|
+
area,
|
|
13695
|
+
isEmpty: area === null ? regions.length === 0 : Math.abs(area) <= EPS5,
|
|
13696
|
+
regions,
|
|
13697
|
+
diagnostics
|
|
13698
|
+
};
|
|
13699
|
+
if (isConstraintSketch(sketch)) {
|
|
13700
|
+
target.constraintStatus = sketch.constraintMeta.status;
|
|
13701
|
+
target.constraintDof = sketch.constraintMeta.dof;
|
|
13702
|
+
target.constraintMaxError = sketch.constraintMeta.maxError;
|
|
13703
|
+
}
|
|
13704
|
+
if (args.options.seed) {
|
|
13705
|
+
target.selection = selectRegion(regions, args.options.seed, args.options.operation);
|
|
13706
|
+
}
|
|
13707
|
+
return target;
|
|
13708
|
+
}
|
|
13709
|
+
function inspectProfileTarget(plan, args) {
|
|
13710
|
+
const diagnostics = [];
|
|
13711
|
+
const targetBase = {
|
|
13712
|
+
id: args.id,
|
|
13713
|
+
objectId: args.object.id,
|
|
13714
|
+
objectName: args.object.name,
|
|
13715
|
+
objectPath: args.objectPath,
|
|
13716
|
+
sourceKind: "shape-profile",
|
|
13717
|
+
operation: args.operation,
|
|
13718
|
+
profilePath: args.profilePath,
|
|
13719
|
+
profileTree: summarizeProfilePlan(plan, "$")
|
|
13720
|
+
};
|
|
13721
|
+
try {
|
|
13722
|
+
const sketch = buildSketchFromCompileProfilePlan(plan);
|
|
13723
|
+
const target = inspectSketchTarget(sketch, {
|
|
13724
|
+
id: args.id,
|
|
13725
|
+
object: args.object,
|
|
13726
|
+
objectPath: args.objectPath,
|
|
13727
|
+
sourceKind: "shape-profile",
|
|
13728
|
+
options: args.options
|
|
13729
|
+
});
|
|
13730
|
+
return {
|
|
13731
|
+
...target,
|
|
13732
|
+
...targetBase,
|
|
13733
|
+
diagnostics: [...target.diagnostics, ...diagnostics]
|
|
13734
|
+
};
|
|
13735
|
+
} catch (error) {
|
|
13736
|
+
diagnostics.push({
|
|
13737
|
+
level: "error",
|
|
13738
|
+
code: "profile-lowering-failed",
|
|
13739
|
+
message: error instanceof Error ? error.message : String(error)
|
|
13740
|
+
});
|
|
13741
|
+
return {
|
|
13742
|
+
...targetBase,
|
|
13743
|
+
bounds: null,
|
|
13744
|
+
area: null,
|
|
13745
|
+
isEmpty: true,
|
|
13746
|
+
regions: [],
|
|
13747
|
+
diagnostics,
|
|
13748
|
+
...args.options.seed ? { selection: selectRegion([], args.options.seed, args.options.operation) } : {}
|
|
13749
|
+
};
|
|
13750
|
+
}
|
|
13751
|
+
}
|
|
13752
|
+
function addSketchHealthDiagnostics(sketch, regions, diagnostics) {
|
|
13753
|
+
if (regions.length === 0) {
|
|
13754
|
+
diagnostics.push({
|
|
13755
|
+
level: "warning",
|
|
13756
|
+
code: "no-regions",
|
|
13757
|
+
message: "No filled sketch regions were detected."
|
|
13758
|
+
});
|
|
13759
|
+
}
|
|
13760
|
+
if (!isConstraintSketch(sketch)) return;
|
|
13761
|
+
const meta = sketch.constraintMeta;
|
|
13762
|
+
if (meta.status === "under") {
|
|
13763
|
+
diagnostics.push({
|
|
13764
|
+
level: "warning",
|
|
13765
|
+
code: "under-constrained",
|
|
13766
|
+
message: `Constraint sketch is under-constrained with ${meta.dof} degree(s) of freedom.`
|
|
13767
|
+
});
|
|
13768
|
+
} else if (meta.status === "over" || meta.status === "over-redundant") {
|
|
13769
|
+
diagnostics.push({
|
|
13770
|
+
level: "warning",
|
|
13771
|
+
code: meta.status === "over" ? "over-constrained" : "over-redundant",
|
|
13772
|
+
message: `Constraint sketch status is ${meta.status}.`
|
|
13773
|
+
});
|
|
13774
|
+
}
|
|
13775
|
+
if (meta.maxError > 1e-6) {
|
|
13776
|
+
diagnostics.push({
|
|
13777
|
+
level: "warning",
|
|
13778
|
+
code: "constraint-residual",
|
|
13779
|
+
message: `Constraint max error is ${meta.maxError}.`
|
|
13780
|
+
});
|
|
13781
|
+
}
|
|
13782
|
+
}
|
|
13783
|
+
function extractRegionsForSketch(sketch, diagnostics) {
|
|
13784
|
+
if (isConstraintSketch(sketch) && sketch.constraintMeta.surfaces.length > 0) {
|
|
13785
|
+
return sketch.constraintMeta.surfaces.map((surface, index) => ({
|
|
13786
|
+
id: `R${index}`,
|
|
13787
|
+
area: surface.area,
|
|
13788
|
+
bounds: { min: [...surface.bounds.min], max: [...surface.bounds.max] },
|
|
13789
|
+
centroid: [...surface.centroid],
|
|
13790
|
+
seed: [...surface.seed],
|
|
13791
|
+
outer: surface.polygon.map(cloneVec2),
|
|
13792
|
+
holes: [],
|
|
13793
|
+
compatibleOperations: { extrude: surface.area > EPS5 }
|
|
13794
|
+
}));
|
|
13795
|
+
}
|
|
13796
|
+
try {
|
|
13797
|
+
return extractFilledRegions(sketch.toPolygons()).map((region, index) => ({
|
|
13798
|
+
id: `R${index}`,
|
|
13799
|
+
...region,
|
|
13800
|
+
compatibleOperations: { extrude: region.area > EPS5 }
|
|
13801
|
+
}));
|
|
13802
|
+
} catch (error) {
|
|
13803
|
+
diagnostics.push({
|
|
13804
|
+
level: "error",
|
|
13805
|
+
code: "polygon-extraction-failed",
|
|
13806
|
+
message: error instanceof Error ? error.message : String(error)
|
|
13807
|
+
});
|
|
13808
|
+
return [];
|
|
13809
|
+
}
|
|
13810
|
+
}
|
|
13811
|
+
function extractFilledRegions(rawLoops) {
|
|
13812
|
+
const loops = rawLoops.map((loop) => loop.map(([x, y]) => [x, y])).map(removeDuplicateClose).filter((points) => points.length >= 3).map((points) => {
|
|
13813
|
+
const area = signedArea(points);
|
|
13814
|
+
return { points, area, absArea: Math.abs(area), sample: polygonCentroid(points) };
|
|
13815
|
+
}).filter((loop) => loop.absArea > EPS5);
|
|
13816
|
+
if (loops.length === 0) return [];
|
|
13817
|
+
const outers = loops.filter((loop) => loop.area > 0);
|
|
13818
|
+
const holes = loops.filter((loop) => loop.area < 0);
|
|
13819
|
+
const effectiveOuters = outers.length > 0 ? outers : [loops.slice().sort((a, b) => b.absArea - a.absArea)[0]];
|
|
13820
|
+
const regions = effectiveOuters.map((outer) => ({ outer, holes: [] }));
|
|
13821
|
+
for (const hole of holes) {
|
|
13822
|
+
const containers = regions.filter((region) => pointInPolygon(hole.sample, region.outer.points)).sort((a, b) => a.outer.absArea - b.outer.absArea);
|
|
13823
|
+
if (containers[0]) containers[0].holes.push(hole);
|
|
13824
|
+
}
|
|
13825
|
+
return regions.map((region) => {
|
|
13826
|
+
const holePoints = region.holes.map((hole) => hole.points);
|
|
13827
|
+
const area = Math.max(0, region.outer.absArea - region.holes.reduce((sum2, hole) => sum2 + hole.absArea, 0));
|
|
13828
|
+
return {
|
|
13829
|
+
area,
|
|
13830
|
+
bounds: boundsForLoops([region.outer.points, ...holePoints]),
|
|
13831
|
+
centroid: polygonCentroid(region.outer.points),
|
|
13832
|
+
seed: findRegionSeed(region.outer.points, holePoints),
|
|
13833
|
+
outer: region.outer.points.map(cloneVec2),
|
|
13834
|
+
holes: holePoints.map((hole) => hole.map(cloneVec2))
|
|
13835
|
+
};
|
|
13836
|
+
}).filter((region) => region.area > EPS5).sort((a, b) => b.area - a.area);
|
|
13837
|
+
}
|
|
13838
|
+
function selectRegion(regions, seed, operation) {
|
|
13839
|
+
for (const region of regions) {
|
|
13840
|
+
if (pointOnLoopBoundary(seed, region.outer) || region.holes.some((hole) => pointOnLoopBoundary(seed, hole))) {
|
|
13841
|
+
return {
|
|
13842
|
+
seed,
|
|
13843
|
+
ok: false,
|
|
13844
|
+
regionId: null,
|
|
13845
|
+
code: "seed-on-boundary",
|
|
13846
|
+
message: `Seed [${seed[0]}, ${seed[1]}] lies on a region boundary.`,
|
|
13847
|
+
operation,
|
|
13848
|
+
operationCompatible: false
|
|
13849
|
+
};
|
|
13850
|
+
}
|
|
13851
|
+
}
|
|
13852
|
+
const outerMatches = regions.filter((region) => pointInPolygon(seed, region.outer));
|
|
13853
|
+
const holeMatches = outerMatches.filter((region) => region.holes.some((hole) => pointInPolygon(seed, hole)));
|
|
13854
|
+
const matches = outerMatches.filter((region) => !region.holes.some((hole) => pointInPolygon(seed, hole)));
|
|
13855
|
+
if (matches.length === 1) {
|
|
13856
|
+
const compatible = operation ? matches[0].compatibleOperations[operation] === true : void 0;
|
|
13857
|
+
return {
|
|
13858
|
+
seed,
|
|
13859
|
+
ok: operation ? compatible === true : true,
|
|
13860
|
+
regionId: matches[0].id,
|
|
13861
|
+
code: operation && !compatible ? "operation-incompatible" : "matched",
|
|
13862
|
+
message: operation && !compatible ? `Region ${matches[0].id} is not compatible with ${operation}.` : `Seed matched region ${matches[0].id}.`,
|
|
13863
|
+
operation,
|
|
13864
|
+
operationCompatible: compatible
|
|
13865
|
+
};
|
|
13866
|
+
}
|
|
13867
|
+
if (matches.length > 1) {
|
|
13868
|
+
return {
|
|
13869
|
+
seed,
|
|
13870
|
+
ok: false,
|
|
13871
|
+
regionId: null,
|
|
13872
|
+
code: "seed-ambiguous",
|
|
13873
|
+
message: `Seed [${seed[0]}, ${seed[1]}] matched ${matches.length} regions.`,
|
|
13874
|
+
operation,
|
|
13875
|
+
operationCompatible: false
|
|
13876
|
+
};
|
|
13877
|
+
}
|
|
13878
|
+
if (holeMatches.length > 0) {
|
|
13879
|
+
return {
|
|
13880
|
+
seed,
|
|
13881
|
+
ok: false,
|
|
13882
|
+
regionId: null,
|
|
13883
|
+
code: "seed-inside-hole",
|
|
13884
|
+
message: `Seed [${seed[0]}, ${seed[1]}] is inside a region hole.`,
|
|
13885
|
+
operation,
|
|
13886
|
+
operationCompatible: false
|
|
13887
|
+
};
|
|
13888
|
+
}
|
|
13889
|
+
return {
|
|
13890
|
+
seed,
|
|
13891
|
+
ok: false,
|
|
13892
|
+
regionId: null,
|
|
13893
|
+
code: regions.length === 0 ? "no-regions" : "seed-outside-regions",
|
|
13894
|
+
message: regions.length === 0 ? "No regions are available for seed selection." : `Seed [${seed[0]}, ${seed[1]}] is outside all detected regions.`,
|
|
13895
|
+
operation,
|
|
13896
|
+
operationCompatible: false
|
|
13897
|
+
};
|
|
13898
|
+
}
|
|
13899
|
+
function collectProfileUses(plan, path3 = "$") {
|
|
13900
|
+
switch (plan.kind) {
|
|
13901
|
+
case "extrude":
|
|
13902
|
+
return [{ operation: "extrude", profile: plan.profile, path: `${path3}.profile` }];
|
|
13903
|
+
case "cut":
|
|
13904
|
+
return [...collectProfileUses(plan.base, `${path3}.base`), { operation: "cut", profile: plan.profile, path: `${path3}.profile` }];
|
|
13905
|
+
case "revolve":
|
|
13906
|
+
return [{ operation: "revolve", profile: plan.profile, path: `${path3}.profile` }];
|
|
13907
|
+
case "queryOwner":
|
|
13908
|
+
return collectProfileUses(plan.base, `${path3}.base`);
|
|
13909
|
+
case "transform":
|
|
13910
|
+
return collectProfileUses(plan.base, `${path3}.base`);
|
|
13911
|
+
case "boolean":
|
|
13912
|
+
return plan.shapes.flatMap((shape, index) => collectProfileUses(shape, `${path3}.shapes[${index}]`));
|
|
13913
|
+
case "shell":
|
|
13914
|
+
case "hole":
|
|
13915
|
+
case "trimByPlane":
|
|
13916
|
+
case "fillet":
|
|
13917
|
+
case "chamfer":
|
|
13918
|
+
case "filletEdges":
|
|
13919
|
+
case "cornerYBlend":
|
|
13920
|
+
case "surfaceExtend":
|
|
13921
|
+
case "surfaceThicken":
|
|
13922
|
+
case "surfaceSolid":
|
|
13923
|
+
return collectProfileUses(plan.base, `${path3}.base`);
|
|
13924
|
+
case "surfaceSew":
|
|
13925
|
+
return plan.shapes.flatMap((shape, index) => collectProfileUses(shape, `${path3}.shapes[${index}]`));
|
|
13926
|
+
default:
|
|
13927
|
+
return [];
|
|
13928
|
+
}
|
|
13929
|
+
}
|
|
13930
|
+
function summarizeProfilePlan(plan, path3) {
|
|
13931
|
+
switch (plan.kind) {
|
|
13932
|
+
case "boolean":
|
|
13933
|
+
return {
|
|
13934
|
+
path: path3,
|
|
13935
|
+
kind: plan.kind,
|
|
13936
|
+
op: plan.op,
|
|
13937
|
+
children: plan.profiles.map((profile, index) => summarizeProfilePlan(profile, `${path3}.profiles[${index}]`))
|
|
13938
|
+
};
|
|
13939
|
+
case "offset":
|
|
13940
|
+
return { path: path3, kind: plan.kind, children: [summarizeProfilePlan(plan.base, `${path3}.base`)] };
|
|
13941
|
+
case "project":
|
|
13942
|
+
return {
|
|
13943
|
+
path: path3,
|
|
13944
|
+
kind: plan.kind,
|
|
13945
|
+
children: plan.replayProfile ? [summarizeProfilePlan(plan.replayProfile, `${path3}.replayProfile`)] : []
|
|
13946
|
+
};
|
|
13947
|
+
default:
|
|
13948
|
+
return { path: path3, kind: plan.kind, children: [] };
|
|
13949
|
+
}
|
|
13950
|
+
}
|
|
13951
|
+
function readSketchBounds(sketch, diagnostics) {
|
|
13952
|
+
try {
|
|
13953
|
+
const bounds2 = sketch.bounds();
|
|
13954
|
+
return { min: [bounds2.min[0], bounds2.min[1]], max: [bounds2.max[0], bounds2.max[1]] };
|
|
13955
|
+
} catch (error) {
|
|
13956
|
+
diagnostics.push({
|
|
13957
|
+
level: "error",
|
|
13958
|
+
code: "bounds-failed",
|
|
13959
|
+
message: error instanceof Error ? error.message : String(error)
|
|
13960
|
+
});
|
|
13961
|
+
return null;
|
|
13962
|
+
}
|
|
13963
|
+
}
|
|
13964
|
+
function readSketchArea(sketch, diagnostics) {
|
|
13965
|
+
try {
|
|
13966
|
+
return sketch.area();
|
|
13967
|
+
} catch (error) {
|
|
13968
|
+
diagnostics.push({
|
|
13969
|
+
level: "error",
|
|
13970
|
+
code: "area-failed",
|
|
13971
|
+
message: error instanceof Error ? error.message : String(error)
|
|
13972
|
+
});
|
|
13973
|
+
return null;
|
|
13974
|
+
}
|
|
13975
|
+
}
|
|
13976
|
+
function findRegionSeed(outer, holes) {
|
|
13977
|
+
const candidates = [polygonCentroid(outer)];
|
|
13978
|
+
for (let i = 0; i < outer.length; i += 1) {
|
|
13979
|
+
const a = outer[i];
|
|
13980
|
+
const b = outer[(i + 1) % outer.length];
|
|
13981
|
+
const dx = b[0] - a[0];
|
|
13982
|
+
const dy = b[1] - a[1];
|
|
13983
|
+
const length3 = Math.hypot(dx, dy);
|
|
13984
|
+
if (length3 <= EPS5) continue;
|
|
13985
|
+
const nudge = Math.min(1, length3 * 0.01);
|
|
13986
|
+
candidates.push([(a[0] + b[0]) / 2 - dy / length3 * nudge, (a[1] + b[1]) / 2 + dx / length3 * nudge]);
|
|
13987
|
+
}
|
|
13988
|
+
return candidates.find((candidate) => pointInFilledRegion(candidate, outer, holes)) ?? candidates[0] ?? [0, 0];
|
|
13989
|
+
}
|
|
13990
|
+
function pointInFilledRegion(point, outer, holes) {
|
|
13991
|
+
return pointInPolygon(point, outer) && !holes.some((hole) => pointInPolygon(point, hole));
|
|
13992
|
+
}
|
|
13993
|
+
function boundsForLoops(loops) {
|
|
13994
|
+
let minX = Infinity;
|
|
13995
|
+
let minY = Infinity;
|
|
13996
|
+
let maxX = -Infinity;
|
|
13997
|
+
let maxY = -Infinity;
|
|
13998
|
+
for (const loop of loops) {
|
|
13999
|
+
for (const [x, y] of loop) {
|
|
14000
|
+
if (x < minX) minX = x;
|
|
14001
|
+
if (y < minY) minY = y;
|
|
14002
|
+
if (x > maxX) maxX = x;
|
|
14003
|
+
if (y > maxY) maxY = y;
|
|
14004
|
+
}
|
|
14005
|
+
}
|
|
14006
|
+
return { min: [minX, minY], max: [maxX, maxY] };
|
|
14007
|
+
}
|
|
14008
|
+
function removeDuplicateClose(points) {
|
|
14009
|
+
if (points.length <= 1) return points;
|
|
14010
|
+
return Math.hypot(points[0][0] - points[points.length - 1][0], points[0][1] - points[points.length - 1][1]) <= BOUNDARY_EPS ? points.slice(0, -1) : points;
|
|
14011
|
+
}
|
|
14012
|
+
function signedArea(points) {
|
|
14013
|
+
let area = 0;
|
|
14014
|
+
for (let i = 0; i < points.length; i += 1) {
|
|
14015
|
+
const [x1, y1] = points[i];
|
|
14016
|
+
const [x2, y2] = points[(i + 1) % points.length];
|
|
14017
|
+
area += x1 * y2 - x2 * y1;
|
|
14018
|
+
}
|
|
14019
|
+
return area * 0.5;
|
|
14020
|
+
}
|
|
14021
|
+
function polygonCentroid(points) {
|
|
14022
|
+
let cx = 0;
|
|
14023
|
+
let cy = 0;
|
|
14024
|
+
let a2 = 0;
|
|
14025
|
+
for (let i = 0; i < points.length; i += 1) {
|
|
14026
|
+
const [x1, y1] = points[i];
|
|
14027
|
+
const [x2, y2] = points[(i + 1) % points.length];
|
|
14028
|
+
const cross6 = x1 * y2 - x2 * y1;
|
|
14029
|
+
a2 += cross6;
|
|
14030
|
+
cx += (x1 + x2) * cross6;
|
|
14031
|
+
cy += (y1 + y2) * cross6;
|
|
14032
|
+
}
|
|
14033
|
+
if (Math.abs(a2) < EPS5) {
|
|
14034
|
+
return points.length > 0 ? [points.reduce((sum2, p) => sum2 + p[0], 0) / points.length, points.reduce((sum2, p) => sum2 + p[1], 0) / points.length] : [0, 0];
|
|
14035
|
+
}
|
|
14036
|
+
const f2 = 1 / (3 * a2);
|
|
14037
|
+
return [cx * f2, cy * f2];
|
|
14038
|
+
}
|
|
14039
|
+
function pointInPolygon(point, polygon) {
|
|
14040
|
+
const [px, py] = point;
|
|
14041
|
+
let inside = false;
|
|
14042
|
+
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
|
|
14043
|
+
const [xi, yi] = polygon[i];
|
|
14044
|
+
const [xj, yj] = polygon[j];
|
|
14045
|
+
if (yi > py !== yj > py && px < (xj - xi) * (py - yi) / (yj - yi || 1e-20) + xi) inside = !inside;
|
|
14046
|
+
}
|
|
14047
|
+
return inside;
|
|
14048
|
+
}
|
|
14049
|
+
function pointOnLoopBoundary(point, loop) {
|
|
14050
|
+
for (let i = 0; i < loop.length; i += 1) {
|
|
14051
|
+
if (distToSegment(point, loop[i], loop[(i + 1) % loop.length]) <= BOUNDARY_EPS) return true;
|
|
14052
|
+
}
|
|
14053
|
+
return false;
|
|
14054
|
+
}
|
|
14055
|
+
function distToSegment(point, a, b) {
|
|
14056
|
+
const dx = b[0] - a[0];
|
|
14057
|
+
const dy = b[1] - a[1];
|
|
14058
|
+
const len2 = dx * dx + dy * dy;
|
|
14059
|
+
if (len2 <= EPS5) return Math.hypot(point[0] - a[0], point[1] - a[1]);
|
|
14060
|
+
const t = Math.max(0, Math.min(1, ((point[0] - a[0]) * dx + (point[1] - a[1]) * dy) / len2));
|
|
14061
|
+
return Math.hypot(point[0] - (a[0] + t * dx), point[1] - (a[1] + t * dy));
|
|
14062
|
+
}
|
|
14063
|
+
function cloneVec2(point) {
|
|
14064
|
+
return [point[0], point[1]];
|
|
14065
|
+
}
|
|
14066
|
+
|
|
14067
|
+
// src/forge/targets.ts
|
|
14068
|
+
var cleanSceneTargetPathSegments = (segments) => (segments ?? []).map((segment) => segment.trim()).filter((segment) => segment.length > 0);
|
|
14069
|
+
function getSceneObjectTreePath(object) {
|
|
14070
|
+
const explicitTreePath = cleanSceneTargetPathSegments(object.treePath);
|
|
14071
|
+
if (explicitTreePath.length > 0) return explicitTreePath;
|
|
14072
|
+
const name = object.name.trim() || object.id;
|
|
14073
|
+
const groupName = object.groupName?.trim();
|
|
14074
|
+
if (!groupName) return [name];
|
|
14075
|
+
const groupPath = groupName.split(".").map((segment) => segment.trim()).filter((segment) => segment.length > 0);
|
|
14076
|
+
const prefixedLeaf = `${groupName}.`;
|
|
14077
|
+
if (name.startsWith(prefixedLeaf)) {
|
|
14078
|
+
const leafName = name.slice(prefixedLeaf.length).trim();
|
|
14079
|
+
return [...groupPath, leafName || name];
|
|
14080
|
+
}
|
|
14081
|
+
return [...groupPath, name];
|
|
14082
|
+
}
|
|
14083
|
+
function getSceneObjectKind(object) {
|
|
14084
|
+
if (object.mock) return "mock";
|
|
14085
|
+
if (object.sketch) return "sketch";
|
|
14086
|
+
if (object.toolpath) return "toolpath";
|
|
14087
|
+
if (object.sdf) return "sdf";
|
|
14088
|
+
if (object.shape) return "shape";
|
|
14089
|
+
return "object";
|
|
14090
|
+
}
|
|
14091
|
+
function buildSceneTargetEntries(objects) {
|
|
14092
|
+
return objects.map((object) => {
|
|
14093
|
+
const pathSegments = getSceneObjectTreePath(object);
|
|
14094
|
+
return {
|
|
14095
|
+
id: object.id,
|
|
14096
|
+
name: object.name,
|
|
14097
|
+
kind: getSceneObjectKind(object),
|
|
14098
|
+
pathSegments,
|
|
14099
|
+
path: pathSegments.join("/"),
|
|
14100
|
+
dottedPath: pathSegments.join("."),
|
|
14101
|
+
group: object.groupName?.trim() || void 0,
|
|
14102
|
+
tags: object.tags ?? [],
|
|
14103
|
+
mock: object.mock === true,
|
|
14104
|
+
object
|
|
14105
|
+
};
|
|
14106
|
+
});
|
|
14107
|
+
}
|
|
14108
|
+
function formatSceneTargetPath(entry) {
|
|
14109
|
+
return entry.path || entry.name;
|
|
14110
|
+
}
|
|
14111
|
+
function resolveSceneTargets(entries, selector) {
|
|
14112
|
+
const needle = normalizeTargetText(selector);
|
|
14113
|
+
if (!needle) throw new Error("Target selector must not be empty.");
|
|
14114
|
+
const hasGlob = /[*?]/.test(selector);
|
|
14115
|
+
if (hasGlob) {
|
|
14116
|
+
const pattern = globToRegExp(needle);
|
|
14117
|
+
const matches = entries.filter((entry) => targetCandidateTexts(entry).some((candidate) => pattern.test(candidate)));
|
|
14118
|
+
if (matches.length > 0) return matches;
|
|
14119
|
+
throw targetNotFound(selector, entries);
|
|
14120
|
+
}
|
|
14121
|
+
const idMatches = entries.filter((entry) => normalizeTargetText(entry.id) === needle);
|
|
14122
|
+
if (idMatches.length > 0) return idMatches;
|
|
14123
|
+
const exactPathMatches = entries.filter(
|
|
14124
|
+
(entry) => normalizeTargetText(entry.path) === needle || normalizeTargetText(entry.dottedPath) === needle
|
|
14125
|
+
);
|
|
14126
|
+
if (exactPathMatches.length > 0) return exactPathMatches;
|
|
14127
|
+
const groupMatches = entries.filter((entry) => {
|
|
14128
|
+
const groupPath = entry.pathSegments.slice(0, -1);
|
|
14129
|
+
return normalizeTargetText(groupPath.join("/")) === needle || normalizeTargetText(groupPath.join(".")) === needle;
|
|
14130
|
+
});
|
|
14131
|
+
if (groupMatches.length > 0) return groupMatches;
|
|
14132
|
+
const nameMatches2 = entries.filter(
|
|
14133
|
+
(entry) => normalizeTargetText(entry.name) === needle || normalizeTargetText(entry.pathSegments[entry.pathSegments.length - 1] ?? "") === needle
|
|
14134
|
+
);
|
|
14135
|
+
if (nameMatches2.length === 1) return nameMatches2;
|
|
14136
|
+
if (nameMatches2.length > 1) throw ambiguousTarget(selector, nameMatches2);
|
|
14137
|
+
throw targetNotFound(selector, entries);
|
|
14138
|
+
}
|
|
14139
|
+
function suggestSceneTargets(selector, entries, limit = 8) {
|
|
14140
|
+
const needle = normalizeTargetText(selector);
|
|
14141
|
+
if (!needle) return entries.slice(0, limit);
|
|
14142
|
+
return entries.map((entry) => ({ entry, score: scoreTarget(entry, needle) })).filter((ranked) => ranked.score > 0).sort((a, b) => b.score - a.score || a.entry.path.localeCompare(b.entry.path)).slice(0, limit).map((ranked) => ranked.entry);
|
|
14143
|
+
}
|
|
14144
|
+
function targetCandidateTexts(entry) {
|
|
14145
|
+
return [
|
|
14146
|
+
entry.id,
|
|
14147
|
+
entry.name,
|
|
14148
|
+
entry.path,
|
|
14149
|
+
entry.dottedPath,
|
|
14150
|
+
entry.group ?? "",
|
|
14151
|
+
entry.pathSegments[entry.pathSegments.length - 1] ?? ""
|
|
14152
|
+
].map(normalizeTargetText);
|
|
14153
|
+
}
|
|
14154
|
+
function normalizeTargetText(value) {
|
|
14155
|
+
return value.trim().replace(/\\/g, "/").replace(/\s*\/\s*/g, "/").replace(/\s*\.\s*/g, ".").toLowerCase();
|
|
14156
|
+
}
|
|
14157
|
+
function globToRegExp(value) {
|
|
14158
|
+
const escaped = value.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
14159
|
+
return new RegExp(`^${escaped}$`, "i");
|
|
14160
|
+
}
|
|
14161
|
+
function scoreTarget(entry, needle) {
|
|
14162
|
+
const candidates = targetCandidateTexts(entry);
|
|
14163
|
+
if (candidates.some((candidate) => candidate === needle)) return 100;
|
|
14164
|
+
if (candidates.some((candidate) => candidate.startsWith(needle))) return 60;
|
|
14165
|
+
if (candidates.some((candidate) => candidate.includes(needle))) return 30;
|
|
14166
|
+
const tokens = needle.split(/[\s/._-]+/).filter(Boolean);
|
|
14167
|
+
if (tokens.length > 0 && tokens.every((token) => candidates.some((candidate) => candidate.includes(token)))) return 15;
|
|
14168
|
+
return 0;
|
|
14169
|
+
}
|
|
14170
|
+
function targetSummary(entry) {
|
|
14171
|
+
return `${formatSceneTargetPath(entry)} (${entry.kind}, ${entry.id})`;
|
|
14172
|
+
}
|
|
14173
|
+
function targetNotFound(selector, entries) {
|
|
14174
|
+
const suggestions = suggestSceneTargets(selector, entries);
|
|
14175
|
+
const detail = suggestions.length > 0 ? `
|
|
14176
|
+
Did you mean:
|
|
14177
|
+
${suggestions.map((entry) => ` ${targetSummary(entry)}`).join("\n")}` : "";
|
|
14178
|
+
return new Error(`Target "${selector}" matched no scene objects.${detail}`);
|
|
14179
|
+
}
|
|
14180
|
+
function ambiguousTarget(selector, matches) {
|
|
14181
|
+
return new Error(
|
|
14182
|
+
`Target "${selector}" is ambiguous.
|
|
14183
|
+
Use a full path:
|
|
14184
|
+
${matches.map((entry) => ` ${targetSummary(entry)}`).join("\n")}`
|
|
14185
|
+
);
|
|
14186
|
+
}
|
|
14187
|
+
|
|
14188
|
+
// cli/inspect-sketch.ts
|
|
14189
|
+
function usage7() {
|
|
14190
|
+
return [
|
|
14191
|
+
"Usage: forgecad inspect sketch <model.forge.js> [--json] [--object <name-or-path>] [--seed <x,y>] [--operation extrude] [--param Key=Value] [--backend manifold|occt|truck] [--quality live|default|high]"
|
|
14192
|
+
].join("\n");
|
|
14193
|
+
}
|
|
14194
|
+
function parseSeed(raw) {
|
|
14195
|
+
const parts = raw.split(",").map((part) => Number(part.trim()));
|
|
14196
|
+
if (parts.length !== 2 || !Number.isFinite(parts[0]) || !Number.isFinite(parts[1])) {
|
|
14197
|
+
throw new Error(`--seed must be two finite numbers as x,y (got "${raw}")`);
|
|
14198
|
+
}
|
|
14199
|
+
return [parts[0], parts[1]];
|
|
14200
|
+
}
|
|
14201
|
+
function parseOperation(raw) {
|
|
14202
|
+
if (raw === "extrude") return raw;
|
|
14203
|
+
throw new Error(`--operation must be extrude (got "${raw}")`);
|
|
14204
|
+
}
|
|
14205
|
+
function readFlagValue(argv, index, flag) {
|
|
14206
|
+
const value = argv[index + 1];
|
|
14207
|
+
if (!value || value.startsWith("--")) throw new Error(`${flag} requires a value.`);
|
|
14208
|
+
return value;
|
|
14209
|
+
}
|
|
14210
|
+
function parseOptions(argv) {
|
|
14211
|
+
if (argv.length === 0 || argv.includes("-h") || argv.includes("--help")) {
|
|
14212
|
+
console.log(usage7());
|
|
14213
|
+
process.exit(0);
|
|
14214
|
+
}
|
|
14215
|
+
const { consumed: paramConsumed } = parseParamFlags(argv);
|
|
14216
|
+
const { backend, consumed: backendConsumed } = parseBackendArg(argv);
|
|
14217
|
+
const { quality, consumed: qualityConsumed } = parseQualityArg(argv);
|
|
14218
|
+
const consumed = /* @__PURE__ */ new Set([...paramConsumed, ...backendConsumed, ...qualityConsumed]);
|
|
14219
|
+
let json = false;
|
|
14220
|
+
let compact = false;
|
|
14221
|
+
let objectSelector;
|
|
14222
|
+
let seed;
|
|
14223
|
+
let operation;
|
|
14224
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
14225
|
+
if (consumed.has(i)) continue;
|
|
14226
|
+
const arg = argv[i];
|
|
14227
|
+
if (arg === "--json") {
|
|
14228
|
+
json = true;
|
|
14229
|
+
consumed.add(i);
|
|
14230
|
+
} else if (arg === "--compact") {
|
|
14231
|
+
compact = true;
|
|
14232
|
+
consumed.add(i);
|
|
14233
|
+
} else if (arg === "--object") {
|
|
14234
|
+
objectSelector = readFlagValue(argv, i, arg);
|
|
14235
|
+
consumed.add(i);
|
|
14236
|
+
consumed.add(i + 1);
|
|
14237
|
+
i += 1;
|
|
14238
|
+
} else if (arg === "--seed") {
|
|
14239
|
+
seed = parseSeed(readFlagValue(argv, i, arg));
|
|
14240
|
+
consumed.add(i);
|
|
14241
|
+
consumed.add(i + 1);
|
|
14242
|
+
i += 1;
|
|
14243
|
+
} else if (arg === "--operation") {
|
|
14244
|
+
operation = parseOperation(readFlagValue(argv, i, arg));
|
|
14245
|
+
consumed.add(i);
|
|
14246
|
+
consumed.add(i + 1);
|
|
14247
|
+
i += 1;
|
|
14248
|
+
} else if (arg.startsWith("--")) {
|
|
14249
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
14250
|
+
}
|
|
14251
|
+
}
|
|
14252
|
+
const positionals = argv.filter((_, index) => !consumed.has(index));
|
|
14253
|
+
if (!positionals[0]) throw new Error("Missing input path.");
|
|
14254
|
+
if (positionals.length > 1) throw new Error(`Unexpected argument: ${positionals[1]}`);
|
|
14255
|
+
return { scriptPath: positionals[0], json, compact, objectSelector, seed, operation, backend, quality };
|
|
14256
|
+
}
|
|
14257
|
+
function formatNumber(value, digits = 3) {
|
|
14258
|
+
if (value == null || !Number.isFinite(value)) return String(value);
|
|
14259
|
+
return value.toFixed(digits).replace(/\.?0+$/, "");
|
|
14260
|
+
}
|
|
14261
|
+
function formatPoint(point) {
|
|
14262
|
+
return `(${formatNumber(point[0])}, ${formatNumber(point[1])})`;
|
|
14263
|
+
}
|
|
14264
|
+
function formatBounds(target) {
|
|
14265
|
+
if (!target.bounds) return "unknown";
|
|
14266
|
+
return `${formatPoint(target.bounds.min)}..${formatPoint(target.bounds.max)}`;
|
|
14267
|
+
}
|
|
14268
|
+
function printHuman2(result, scriptPath) {
|
|
14269
|
+
console.log(`Sketch inspection for ${scriptPath}`);
|
|
14270
|
+
console.log(`${result.targets.length} target(s)`);
|
|
14271
|
+
for (const diagnostic of result.diagnostics) {
|
|
14272
|
+
console.log(`${diagnostic.level}: ${diagnostic.code}: ${diagnostic.message}`);
|
|
14273
|
+
}
|
|
14274
|
+
for (const target of result.targets) {
|
|
14275
|
+
const source = target.sourceKind === "shape-profile" ? `${target.sourceKind}:${target.operation} ${target.profilePath}` : target.sourceKind;
|
|
14276
|
+
console.log("");
|
|
14277
|
+
console.log(`${target.id} ${target.objectPath} ${source}`);
|
|
14278
|
+
console.log(
|
|
14279
|
+
` area=${formatNumber(target.area)} bounds=${formatBounds(target)} empty=${target.isEmpty} regions=${target.regions.length}`
|
|
14280
|
+
);
|
|
14281
|
+
if (target.constraintStatus) {
|
|
14282
|
+
console.log(
|
|
14283
|
+
` constraints=${target.constraintStatus} dof=${target.constraintDof ?? "?"} maxError=${formatNumber(target.constraintMaxError, 6)}`
|
|
14284
|
+
);
|
|
14285
|
+
}
|
|
14286
|
+
for (const region of target.regions) {
|
|
14287
|
+
console.log(
|
|
14288
|
+
` ${region.id} area=${formatNumber(region.area)} seed=${formatPoint(region.seed)} bounds=${formatPoint(region.bounds.min)}..${formatPoint(region.bounds.max)} holes=${region.holes.length}`
|
|
14289
|
+
);
|
|
14290
|
+
}
|
|
14291
|
+
if (target.selection) {
|
|
14292
|
+
const status = target.selection.ok ? "ok" : "failed";
|
|
14293
|
+
const region = target.selection.regionId ? ` ${target.selection.regionId}` : "";
|
|
14294
|
+
const op = target.selection.operation && target.selection.operationCompatible != null ? ` ${target.selection.operation}=${target.selection.operationCompatible ? "yes" : "no"}` : "";
|
|
14295
|
+
console.log(` selection ${status}${region}${op}: ${target.selection.message}`);
|
|
14296
|
+
}
|
|
14297
|
+
for (const diagnostic of target.diagnostics) {
|
|
14298
|
+
console.log(` ${diagnostic.level}: ${diagnostic.code}: ${diagnostic.message}`);
|
|
14299
|
+
}
|
|
14300
|
+
}
|
|
14301
|
+
}
|
|
14302
|
+
async function captureConsoleWarnings(fn) {
|
|
14303
|
+
const originalWarn = console.warn;
|
|
14304
|
+
const warnings = [];
|
|
14305
|
+
console.warn = (...args) => {
|
|
14306
|
+
warnings.push(args.map((arg) => typeof arg === "string" ? arg : JSON.stringify(arg)).join(" "));
|
|
14307
|
+
};
|
|
14308
|
+
try {
|
|
14309
|
+
return { result: await fn(), warnings };
|
|
14310
|
+
} finally {
|
|
14311
|
+
console.warn = originalWarn;
|
|
14312
|
+
}
|
|
14313
|
+
}
|
|
14314
|
+
async function runInspectSketchCli(argv = process.argv.slice(2)) {
|
|
14315
|
+
let options;
|
|
14316
|
+
try {
|
|
14317
|
+
options = parseOptions(argv);
|
|
14318
|
+
} catch (error) {
|
|
14319
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
14320
|
+
console.error(usage7());
|
|
14321
|
+
process.exit(1);
|
|
14322
|
+
}
|
|
14323
|
+
const { overrides } = parseParamFlags(argv);
|
|
14324
|
+
const run = () => runModelForInspect(options.scriptPath, options.backend, options.quality, overrides);
|
|
14325
|
+
const { result: runResult, warnings: engineWarnings } = options.json ? await captureConsoleWarnings(run) : { result: await run(), warnings: [] };
|
|
14326
|
+
if (runResult.error) {
|
|
14327
|
+
console.error("ERROR:", runResult.error);
|
|
14328
|
+
process.exit(1);
|
|
14329
|
+
}
|
|
14330
|
+
let objectIds;
|
|
14331
|
+
if (options.objectSelector) {
|
|
14332
|
+
try {
|
|
14333
|
+
const entries = resolveSceneTargets(buildSceneTargetEntries(runResult.objects), options.objectSelector);
|
|
14334
|
+
objectIds = new Set(entries.map((entry) => entry.id));
|
|
14335
|
+
} catch (error) {
|
|
14336
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
14337
|
+
process.exit(1);
|
|
14338
|
+
}
|
|
14339
|
+
}
|
|
14340
|
+
const inspection = inspectSketches(runResult.objects, {
|
|
14341
|
+
objectIds,
|
|
14342
|
+
seed: options.seed,
|
|
14343
|
+
operation: options.operation
|
|
14344
|
+
});
|
|
14345
|
+
for (const warning of engineWarnings) {
|
|
14346
|
+
inspection.diagnostics.push({
|
|
14347
|
+
level: "warning",
|
|
14348
|
+
code: "engine-warning",
|
|
14349
|
+
message: warning
|
|
14350
|
+
});
|
|
14351
|
+
}
|
|
14352
|
+
if (options.json) {
|
|
14353
|
+
console.log(JSON.stringify({ script: options.scriptPath, ...inspection }, null, options.compact ? 0 : 2));
|
|
14354
|
+
} else {
|
|
14355
|
+
printHuman2(inspection, options.scriptPath);
|
|
14356
|
+
}
|
|
14357
|
+
if (options.seed && inspection.targets.length > 0 && !inspection.targets.some((target) => target.selection?.ok)) {
|
|
14358
|
+
process.exit(1);
|
|
14359
|
+
}
|
|
14360
|
+
}
|
|
14361
|
+
|
|
13455
14362
|
// cli/check-occt-lower.ts
|
|
13456
14363
|
import assert5 from "assert/strict";
|
|
13457
14364
|
function approx3(a, b, eps = 0.01) {
|
|
@@ -15485,9 +16392,13 @@ var ESTABLISHED_LOWERCASE_PUBLIC_API_GLOBALS = /* @__PURE__ */ new Set([
|
|
|
15485
16392
|
"kerfCompensateTabs",
|
|
15486
16393
|
"require"
|
|
15487
16394
|
]);
|
|
16395
|
+
function readText(relativePath) {
|
|
16396
|
+
const path3 = resolvePackagePath(import.meta.url, relativePath);
|
|
16397
|
+
return readFileSync9(path3, "utf8");
|
|
16398
|
+
}
|
|
15488
16399
|
function readSource(relativePath) {
|
|
15489
16400
|
const path3 = resolvePackagePath(import.meta.url, relativePath);
|
|
15490
|
-
return ts.createSourceFile(path3,
|
|
16401
|
+
return ts.createSourceFile(path3, readText(relativePath), ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
15491
16402
|
}
|
|
15492
16403
|
function propertyNameText(name) {
|
|
15493
16404
|
if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)) return name.text;
|
|
@@ -15570,6 +16481,29 @@ function uniqueSorted2(values) {
|
|
|
15570
16481
|
function isLowercaseGlobal(name) {
|
|
15571
16482
|
return /^[a-z]/u.test(name);
|
|
15572
16483
|
}
|
|
16484
|
+
function renderRuntimeNameList(names) {
|
|
16485
|
+
const lines = [];
|
|
16486
|
+
for (let i = 0; i < names.length; i += 8) {
|
|
16487
|
+
lines.push(names.slice(i, i + 8).join(", "));
|
|
16488
|
+
}
|
|
16489
|
+
return lines.join("\n");
|
|
16490
|
+
}
|
|
16491
|
+
function checkRuntimeNamesDoc(runtimeNames) {
|
|
16492
|
+
const collisionReservedNames = runtimeNames.filter((name) => name !== "showLabels");
|
|
16493
|
+
const expectedList = renderRuntimeNameList(collisionReservedNames);
|
|
16494
|
+
let doc;
|
|
16495
|
+
try {
|
|
16496
|
+
doc = readText("docs/permanent/generated/runtime-names.md");
|
|
16497
|
+
} catch {
|
|
16498
|
+
return "Generated runtime names doc is missing. Run npm run gen:docs.";
|
|
16499
|
+
}
|
|
16500
|
+
if (!doc.includes(`\`\`\`text
|
|
16501
|
+
${expectedList}
|
|
16502
|
+
\`\`\``)) {
|
|
16503
|
+
return "Generated runtime names doc is stale. Run npm run gen:docs.";
|
|
16504
|
+
}
|
|
16505
|
+
return null;
|
|
16506
|
+
}
|
|
15573
16507
|
async function runCheckRuntimeGlobalsCli() {
|
|
15574
16508
|
const errors = [];
|
|
15575
16509
|
const runtime = collectRuntimeBindingNames(readSource("src/forge/script-runtime/runScript.ts"));
|
|
@@ -15577,6 +16511,8 @@ async function runCheckRuntimeGlobalsCli() {
|
|
|
15577
16511
|
if (runtime.unsupported.length > 0) {
|
|
15578
16512
|
errors.push(`runtimeBindings must use explicit property names; unsupported object entries at lines ${runtime.unsupported.join(", ")}.`);
|
|
15579
16513
|
}
|
|
16514
|
+
const runtimeNamesDocError = checkRuntimeNamesDoc(runtimeNames);
|
|
16515
|
+
if (runtimeNamesDocError) errors.push(runtimeNamesDocError);
|
|
15580
16516
|
const newLowercaseGlobals = runtimeNames.filter((name) => isLowercaseGlobal(name) && !ESTABLISHED_LOWERCASE_RUNTIME_GLOBALS.has(name));
|
|
15581
16517
|
if (newLowercaseGlobals.length > 0) {
|
|
15582
16518
|
errors.push(
|
|
@@ -15631,11 +16567,11 @@ runDirectCliMain(import.meta.url, "cli/check-runtime-globals.ts", () => runCheck
|
|
|
15631
16567
|
|
|
15632
16568
|
// cli/check-text.ts
|
|
15633
16569
|
import assert6 from "assert/strict";
|
|
15634
|
-
var
|
|
15635
|
-
function approx4(a, b, eps =
|
|
16570
|
+
var EPS6 = 1e-3;
|
|
16571
|
+
function approx4(a, b, eps = EPS6) {
|
|
15636
16572
|
return Math.abs(a - b) <= eps;
|
|
15637
16573
|
}
|
|
15638
|
-
function expectClose3(actual, expected, label, eps =
|
|
16574
|
+
function expectClose3(actual, expected, label, eps = EPS6) {
|
|
15639
16575
|
assert6(approx4(actual, expected, eps), `${label}: expected ${expected}, got ${actual}`);
|
|
15640
16576
|
}
|
|
15641
16577
|
function bounds(sk) {
|
|
@@ -15690,7 +16626,7 @@ function checkTextWidth() {
|
|
|
15690
16626
|
Math.abs(reported - w) < tolerance,
|
|
15691
16627
|
`textWidth ${reported.toFixed(3)} should be close to sketch width ${w.toFixed(3)} within ${tolerance.toFixed(3)}`
|
|
15692
16628
|
);
|
|
15693
|
-
assert6(reported >= w -
|
|
16629
|
+
assert6(reported >= w - EPS6, `textWidth ${reported.toFixed(3)} should be >= sketch width ${w.toFixed(3)}`);
|
|
15694
16630
|
}
|
|
15695
16631
|
function checkHorizontalAlignment() {
|
|
15696
16632
|
const left = text2d("CAD", { size: 10, align: "left" });
|
|
@@ -15753,8 +16689,8 @@ runDirectCliMain(import.meta.url, "cli/check-text.ts", () => runCheckTextCli());
|
|
|
15753
16689
|
|
|
15754
16690
|
// cli/check-transforms.ts
|
|
15755
16691
|
import assert7 from "assert/strict";
|
|
15756
|
-
var
|
|
15757
|
-
function approx5(a, b, eps =
|
|
16692
|
+
var EPS7 = 1e-6;
|
|
16693
|
+
function approx5(a, b, eps = EPS7) {
|
|
15758
16694
|
return Math.abs(a - b) <= eps;
|
|
15759
16695
|
}
|
|
15760
16696
|
function assertVec(actual, expected, label) {
|
|
@@ -15893,53 +16829,16 @@ function testAssemblyNamedGroupLabels() {
|
|
|
15893
16829
|
`Expected flattened objects to retain groupName Base Assembly, got ${JSON.stringify(result.objects.map((obj) => obj.groupName))}`
|
|
15894
16830
|
);
|
|
15895
16831
|
}
|
|
15896
|
-
function
|
|
15897
|
-
const mech = assembly("
|
|
15898
|
-
|
|
15899
|
-
|
|
15900
|
-
|
|
15901
|
-
|
|
15902
|
-
|
|
15903
|
-
|
|
15904
|
-
|
|
15905
|
-
const state = solved.getJointState();
|
|
15906
|
-
assert7(approx5(state.A ?? Number.NaN, 20), `Expected A=20, got ${state.A}`);
|
|
15907
|
-
assert7(approx5(state.B ?? Number.NaN, 30), `Expected B=30 after clamp, got ${state.B}`);
|
|
15908
|
-
assert7(approx5(state.C ?? Number.NaN, 0), `Expected C=0 from coupled joints, got ${state.C}`);
|
|
15909
|
-
const warnings = solved.warnings().join("\n");
|
|
15910
|
-
assert7(
|
|
15911
|
-
warnings.includes('Joint "B" state override ignored because it is coupled'),
|
|
15912
|
-
`Expected ignored-override warning for B, got:
|
|
15913
|
-
${warnings}`
|
|
15914
|
-
);
|
|
15915
|
-
}
|
|
15916
|
-
function testAssemblyGearCouplings() {
|
|
15917
|
-
const mech = assembly("GearCouplingsInvariant").addFrame("Base").addFrame("DriverPart").addFrame("DrivenPart").addFrame("FollowerPart").addRevolute("Driver", "Base", "DriverPart", { axis: [0, 0, 1] }).addRevolute("Driven", "Base", "DrivenPart", { axis: [0, 0, 1], min: -20, max: 20 }).addRevolute("Follower", "Base", "FollowerPart", { axis: [0, 0, 1] }).addGearCoupling("Driven", "Driver", { driverTeeth: 14, drivenTeeth: 28 }).addGearCoupling("Follower", "Driven", { pair: { jointRatio: -2 }, offset: 5 });
|
|
15918
|
-
const solved = mech.solve({ Driver: 30, Driven: 999, Follower: 999 });
|
|
15919
|
-
const state = solved.getJointState();
|
|
15920
|
-
assert7(approx5(state.Driver ?? Number.NaN, 30), `Expected Driver=30, got ${state.Driver}`);
|
|
15921
|
-
assert7(approx5(state.Driven ?? Number.NaN, -15), `Expected Driven=-15 from teeth ratio, got ${state.Driven}`);
|
|
15922
|
-
assert7(approx5(state.Follower ?? Number.NaN, 35), `Expected Follower=35 from pair ratio, got ${state.Follower}`);
|
|
15923
|
-
const warnings = solved.warnings().join("\n");
|
|
15924
|
-
assert7(
|
|
15925
|
-
warnings.includes('Joint "Driven" state override ignored because it is coupled'),
|
|
15926
|
-
`Expected ignored-override warning for Driven, got:
|
|
15927
|
-
${warnings}`
|
|
16832
|
+
function testRetiredAssemblyCouplingStubs() {
|
|
16833
|
+
const mech = assembly("RetiredCouplingInvariant").addFrame("Base").addFrame("DriverPart").addFrame("DrivenPart").addRevolute("Driver", "Base", "DriverPart", { axis: [0, 0, 1] }).addRevolute("Driven", "Base", "DrivenPart", { axis: [0, 0, 1] });
|
|
16834
|
+
assert7.throws(
|
|
16835
|
+
() => mech.addJointCoupling("Driven", { terms: [{ joint: "Driver", ratio: 1 }] }),
|
|
16836
|
+
/addJointCoupling\(\) has been removed from the modeling API/
|
|
16837
|
+
);
|
|
16838
|
+
assert7.throws(
|
|
16839
|
+
() => mech.addGearCoupling("Driven", "Driver", { ratio: -1 }),
|
|
16840
|
+
/addGearCoupling\(\) has been removed from the modeling API/
|
|
15928
16841
|
);
|
|
15929
|
-
assert7(
|
|
15930
|
-
warnings.includes('Joint "Follower" state override ignored because it is coupled'),
|
|
15931
|
-
`Expected ignored-override warning for Follower, got:
|
|
15932
|
-
${warnings}`
|
|
15933
|
-
);
|
|
15934
|
-
const internal = assembly("InternalMeshSignInvariant").addFrame("Base").addFrame("A").addFrame("B").addRevolute("A", "Base", "A", { axis: [0, 0, 1] }).addRevolute("B", "Base", "B", { axis: [0, 0, 1] }).addGearCoupling("B", "A", { driverTeeth: 20, drivenTeeth: 40, mesh: "internal" });
|
|
15935
|
-
const internalState = internal.solve({ A: 12 }).getJointState();
|
|
15936
|
-
assert7(approx5(internalState.B ?? Number.NaN, 6), `Expected internal mesh B=6, got ${internalState.B}`);
|
|
15937
|
-
const bevel = assembly("BevelMeshSignInvariant").addFrame("Base").addFrame("A").addFrame("B").addRevolute("A", "Base", "A", { axis: [0, 0, 1] }).addRevolute("B", "Base", "B", { axis: [1, 0, 0] }).addGearCoupling("B", "A", { driverTeeth: 24, drivenTeeth: 48, mesh: "bevel" });
|
|
15938
|
-
const bevelState = bevel.solve({ A: 12 }).getJointState();
|
|
15939
|
-
assert7(approx5(bevelState.B ?? Number.NaN, -6), `Expected bevel mesh B=-6, got ${bevelState.B}`);
|
|
15940
|
-
const face = assembly("FaceMeshSignInvariant").addFrame("Base").addFrame("A").addFrame("B").addRevolute("A", "Base", "A", { axis: [0, 0, 1] }).addRevolute("B", "Base", "B", { axis: [1, 0, 0] }).addGearCoupling("B", "A", { driverTeeth: 24, drivenTeeth: 48, mesh: "face" });
|
|
15941
|
-
const faceState = face.solve({ A: 12 }).getJointState();
|
|
15942
|
-
assert7(approx5(faceState.B ?? Number.NaN, -6), `Expected face mesh B=-6, got ${faceState.B}`);
|
|
15943
16842
|
}
|
|
15944
16843
|
function testRuntimeJointCouplingResolution() {
|
|
15945
16844
|
const joints = [
|
|
@@ -16180,7 +17079,7 @@ function testBevelGearTopSectionCircularity() {
|
|
|
16180
17079
|
const topSlice = gear.slice(bb.max[2] - 1e-4).bounds();
|
|
16181
17080
|
const spanX = topSlice.max[0] - topSlice.min[0];
|
|
16182
17081
|
const spanY = topSlice.max[1] - topSlice.min[1];
|
|
16183
|
-
assert7(spanX >
|
|
17082
|
+
assert7(spanX > EPS7 && spanY > EPS7, `Expected non-degenerate top slice, got spanX=${spanX}, spanY=${spanY}`);
|
|
16184
17083
|
const aspect = spanY / spanX;
|
|
16185
17084
|
assert7(aspect > 0.85 && aspect < 1.15, `Expected near-circular top slice, got spanX=${spanX}, spanY=${spanY}, aspect=${aspect}`);
|
|
16186
17085
|
}
|
|
@@ -16193,8 +17092,7 @@ async function runCheckTransformsCli() {
|
|
|
16193
17092
|
testShapeRotateAroundTo();
|
|
16194
17093
|
testShapeGroupRotateAroundToSugar();
|
|
16195
17094
|
testAssemblyNamedGroupLabels();
|
|
16196
|
-
|
|
16197
|
-
testAssemblyGearCouplings();
|
|
17095
|
+
testRetiredAssemblyCouplingStubs();
|
|
16198
17096
|
testRuntimeJointCouplingResolution();
|
|
16199
17097
|
testContinuousRuntimeJointAnimation();
|
|
16200
17098
|
testTickBasedKeyframes();
|
|
@@ -16258,13 +17156,13 @@ function enforceCommandConfig(commandPath) {
|
|
|
16258
17156
|
}
|
|
16259
17157
|
|
|
16260
17158
|
// cli/debug-compiler.ts
|
|
16261
|
-
function
|
|
17159
|
+
function usage8() {
|
|
16262
17160
|
console.error("Usage: forgecad debug compiler <script.forge.js> [--compact]");
|
|
16263
17161
|
process.exit(1);
|
|
16264
17162
|
}
|
|
16265
17163
|
async function runDebugCompilerCli(argv = process.argv.slice(2)) {
|
|
16266
17164
|
const scriptPath = argv.find((arg) => !arg.startsWith("--"));
|
|
16267
|
-
if (!scriptPath)
|
|
17165
|
+
if (!scriptPath) usage8();
|
|
16268
17166
|
const compact = argv.includes("--compact");
|
|
16269
17167
|
await init();
|
|
16270
17168
|
const inspection = inspectCompilerScene(loadCompilerInspectionInput(scriptPath));
|
|
@@ -16274,7 +17172,7 @@ async function runDebugCompilerCli(argv = process.argv.slice(2)) {
|
|
|
16274
17172
|
// cli/debug-assembly.ts
|
|
16275
17173
|
import { readFileSync as readFileSync11 } from "fs";
|
|
16276
17174
|
import { resolve as resolve12 } from "path";
|
|
16277
|
-
function
|
|
17175
|
+
function usage9() {
|
|
16278
17176
|
console.error(
|
|
16279
17177
|
"Usage: forgecad debug assembly <script.forge.js> [--json] [--compact] [--all] [--exact-geometry] [--fail-on error|warning] [--backend manifold|occt|truck] [--sweep-steps <n>]"
|
|
16280
17178
|
);
|
|
@@ -16334,7 +17232,7 @@ async function runDebugAssemblyCli(argv = process.argv.slice(2)) {
|
|
|
16334
17232
|
const consumed = consumedIndexes(argv);
|
|
16335
17233
|
const positional = argv.filter((arg, index) => !consumed.has(index) && !arg.startsWith("--"));
|
|
16336
17234
|
const scriptPath = positional[0];
|
|
16337
|
-
if (!scriptPath)
|
|
17235
|
+
if (!scriptPath) usage9();
|
|
16338
17236
|
const asJson = argv.includes("--json");
|
|
16339
17237
|
const compact = argv.includes("--compact");
|
|
16340
17238
|
const all = argv.includes("--all");
|
|
@@ -16456,13 +17354,13 @@ function mapDimensionsToOwners(dimensions, objects) {
|
|
|
16456
17354
|
});
|
|
16457
17355
|
return { combinedCount, byDimensionId };
|
|
16458
17356
|
}
|
|
16459
|
-
function
|
|
17357
|
+
function usage10() {
|
|
16460
17358
|
console.error("Usage: forgecad debug dimensions <script.forge.js> [--all] [--dim-angle-tol 12]");
|
|
16461
17359
|
process.exit(1);
|
|
16462
17360
|
}
|
|
16463
17361
|
async function runDebugDimensionsCli(argv = process.argv.slice(2)) {
|
|
16464
17362
|
const scriptPath = argv[0];
|
|
16465
|
-
if (!scriptPath)
|
|
17363
|
+
if (!scriptPath) usage10();
|
|
16466
17364
|
const showAll = argv.includes("--all");
|
|
16467
17365
|
const tolFlagIndex = argv.indexOf("--dim-angle-tol");
|
|
16468
17366
|
const tolValue = tolFlagIndex >= 0 ? Number(argv[tolFlagIndex + 1]) : NaN;
|
|
@@ -16712,6 +17610,7 @@ async function buildStepBlob(objects) {
|
|
|
16712
17610
|
// cli/forge-brep.ts
|
|
16713
17611
|
function parseArgs4(argv) {
|
|
16714
17612
|
let format = "step";
|
|
17613
|
+
let backend = "occt";
|
|
16715
17614
|
let outputPath;
|
|
16716
17615
|
let scriptPath;
|
|
16717
17616
|
for (let i = 0; i < argv.length; i += 1) {
|
|
@@ -16725,6 +17624,15 @@ function parseArgs4(argv) {
|
|
|
16725
17624
|
i += 1;
|
|
16726
17625
|
continue;
|
|
16727
17626
|
}
|
|
17627
|
+
if (arg === "--backend") {
|
|
17628
|
+
const value = argv[i + 1];
|
|
17629
|
+
if (value !== "occt" && value !== "truck") {
|
|
17630
|
+
throw new Error(`--backend must be "occt" or "truck" (got ${value ?? "missing"})`);
|
|
17631
|
+
}
|
|
17632
|
+
backend = value;
|
|
17633
|
+
i += 1;
|
|
17634
|
+
continue;
|
|
17635
|
+
}
|
|
16728
17636
|
if (arg === "--output") {
|
|
16729
17637
|
outputPath = argv[i + 1];
|
|
16730
17638
|
if (!outputPath) throw new Error("--output requires a path");
|
|
@@ -16749,9 +17657,14 @@ function parseArgs4(argv) {
|
|
|
16749
17657
|
scriptPath = arg;
|
|
16750
17658
|
}
|
|
16751
17659
|
if (!scriptPath) {
|
|
16752
|
-
throw new Error(
|
|
17660
|
+
throw new Error(
|
|
17661
|
+
"Usage: npx tsx cli/forge-brep.ts [--format step|brep] [--backend occt|truck] [--output path] <model.forge.js|asset.step|asset.stp>"
|
|
17662
|
+
);
|
|
17663
|
+
}
|
|
17664
|
+
if (backend === "truck" && format !== "step") {
|
|
17665
|
+
throw new Error("The native Truck BREP exporter currently supports STEP only (use --format step).");
|
|
16753
17666
|
}
|
|
16754
|
-
return { format, outputPath, scriptPath };
|
|
17667
|
+
return { format, backend, outputPath, scriptPath };
|
|
16755
17668
|
}
|
|
16756
17669
|
function defaultOutputPath(scriptPath, sourcePath, format) {
|
|
16757
17670
|
const abs = resolve15(scriptPath);
|
|
@@ -16790,12 +17703,25 @@ async function buildRuntimeExactExportBlob(format, code, fileName, allFiles, rea
|
|
|
16790
17703
|
objectCount: exactObjects.length
|
|
16791
17704
|
};
|
|
16792
17705
|
}
|
|
17706
|
+
async function buildTruckStepBlob(code, fileName, allFiles, readBinaryFile) {
|
|
17707
|
+
await activateBackend("truck");
|
|
17708
|
+
const result = runScript(code, fileName, allFiles, { readBinaryFile });
|
|
17709
|
+
if (result.error) {
|
|
17710
|
+
throw new Error(result.error);
|
|
17711
|
+
}
|
|
17712
|
+
const handles = result.objects.filter((obj) => obj.shape).map((obj) => getTruckShapeBackendHandle(getShapeRuntimeBackend(obj.shape)));
|
|
17713
|
+
if (handles.length === 0) {
|
|
17714
|
+
throw new Error("No 3D shapes found in the script output.");
|
|
17715
|
+
}
|
|
17716
|
+
const step = getTruckGeometryWasm().geometry_export_step_multi(JSON.stringify(handles));
|
|
17717
|
+
return { blob: new Blob([step], { type: "application/step" }), objectCount: handles.length };
|
|
17718
|
+
}
|
|
16793
17719
|
async function runBrepCli(argv = process.argv.slice(2)) {
|
|
16794
|
-
const { format, outputPath, scriptPath } = parseArgs4(argv);
|
|
17720
|
+
const { format, backend, outputPath, scriptPath } = parseArgs4(argv);
|
|
16795
17721
|
const input = loadCliScriptInput(scriptPath);
|
|
16796
17722
|
await init();
|
|
16797
17723
|
try {
|
|
16798
|
-
const exactExport = await buildRuntimeExactExportBlob(format, input.code, input.fileName, input.allFiles, input.readBinaryFile);
|
|
17724
|
+
const exactExport = backend === "truck" ? await buildTruckStepBlob(input.code, input.fileName, input.allFiles, input.readBinaryFile) : await buildRuntimeExactExportBlob(format, input.code, input.fileName, input.allFiles, input.readBinaryFile);
|
|
16799
17725
|
if (outputPath && resolve15(outputPath) === input.sourcePath) {
|
|
16800
17726
|
console.error("Output path would overwrite the input file. Pass a different --output path.");
|
|
16801
17727
|
process.exit(1);
|
|
@@ -17123,7 +18049,7 @@ function parseSweepEaseEnv(raw) {
|
|
|
17123
18049
|
if (raw === "linear" || raw === "smoothstep") return raw;
|
|
17124
18050
|
return null;
|
|
17125
18051
|
}
|
|
17126
|
-
function
|
|
18052
|
+
function usage11(config) {
|
|
17127
18053
|
const defaultPitch = DEFAULTS.pitchDeg == null ? "copied camera pitch or 18" : String(DEFAULTS.pitchDeg);
|
|
17128
18054
|
return `ForgeCAD Capture Renderer
|
|
17129
18055
|
|
|
@@ -17134,7 +18060,7 @@ Options:
|
|
|
17134
18060
|
--format <gif|mp4> Output format (default: ${config.defaultFormat})
|
|
17135
18061
|
--capture <orbit|animation|section-sweep>
|
|
17136
18062
|
Capture preset (default: orbit)
|
|
17137
|
-
--animation <name> Select a
|
|
18063
|
+
--animation <name> Select a named joint animation clip
|
|
17138
18064
|
--animation-loops <n> Repeat the selected clip this many times (default: ${DEFAULTS.animationLoops})
|
|
17139
18065
|
--cut-plane <name> Enable a named cut plane (repeatable)
|
|
17140
18066
|
--sweep-plane <XY|XZ|YZ> Plane to move for --capture section-sweep (default: ${DEFAULTS.sweepPlane})
|
|
@@ -17179,7 +18105,7 @@ Options:
|
|
|
17179
18105
|
|
|
17180
18106
|
Examples:
|
|
17181
18107
|
forgecad capture gif examples/products/cup.forge.js
|
|
17182
|
-
forgecad capture mp4 examples/api/
|
|
18108
|
+
forgecad capture mp4 examples/api/assembly-kinematics-four-bar.forge.js out/four-bar.mp4 --view iso
|
|
17183
18109
|
forgecad capture mp4 model.forge.js out/raw.mp4 --param "Output=raw-sdf"
|
|
17184
18110
|
forgecad capture gif examples/3d-printer.forge.js out/section.gif --cut-plane "Front Section"
|
|
17185
18111
|
forgecad capture mp4 examples/3d-printer.forge.js out/sweep.mp4 --capture section-sweep --sweep-plane YZ --sweep-frames 180
|
|
@@ -17255,7 +18181,7 @@ function defaultOutputPath2(scriptPath, format, capture) {
|
|
|
17255
18181
|
}
|
|
17256
18182
|
function parseCli(argv, config) {
|
|
17257
18183
|
if (argv.length === 0 || argv.includes("-h") || argv.includes("--help")) {
|
|
17258
|
-
console.log(
|
|
18184
|
+
console.log(usage11(config));
|
|
17259
18185
|
process.exit(0);
|
|
17260
18186
|
}
|
|
17261
18187
|
let scriptPath;
|
|
@@ -17744,7 +18670,7 @@ async function fetchRenderHtml2(port) {
|
|
|
17744
18670
|
clearTimeout(timer);
|
|
17745
18671
|
}
|
|
17746
18672
|
}
|
|
17747
|
-
async function
|
|
18673
|
+
async function waitForRenderHtml2(port, timeoutMs) {
|
|
17748
18674
|
const deadline = Date.now() + timeoutMs;
|
|
17749
18675
|
while (Date.now() < deadline) {
|
|
17750
18676
|
if (await fetchRenderHtml2(port)) return true;
|
|
@@ -17784,7 +18710,7 @@ async function ensureDevServer2(port) {
|
|
|
17784
18710
|
proc.once("exit", () => {
|
|
17785
18711
|
exitedEarly = true;
|
|
17786
18712
|
});
|
|
17787
|
-
const ready = await
|
|
18713
|
+
const ready = await waitForRenderHtml2(port, 3e4);
|
|
17788
18714
|
if (!ready) {
|
|
17789
18715
|
proc.kill();
|
|
17790
18716
|
const detail = startupOutput.trim();
|
|
@@ -17807,7 +18733,7 @@ async function stopDevServer2(proc) {
|
|
|
17807
18733
|
proc.kill("SIGKILL");
|
|
17808
18734
|
}
|
|
17809
18735
|
}
|
|
17810
|
-
async function
|
|
18736
|
+
async function isPortFree(port) {
|
|
17811
18737
|
const probe = (host) => new Promise((resolvePort) => {
|
|
17812
18738
|
const server = createServer();
|
|
17813
18739
|
server.once("error", (err) => {
|
|
@@ -17826,10 +18752,10 @@ async function isPortFree2(port) {
|
|
|
17826
18752
|
const ipv6Free = await probe("::1");
|
|
17827
18753
|
return ipv4Free && ipv6Free;
|
|
17828
18754
|
}
|
|
17829
|
-
async function
|
|
18755
|
+
async function findFreePort(startPort, maxAttempts = 30) {
|
|
17830
18756
|
let candidate = Math.max(1024, startPort);
|
|
17831
18757
|
for (let i = 0; i < maxAttempts; i += 1) {
|
|
17832
|
-
if (await
|
|
18758
|
+
if (await isPortFree(candidate)) return candidate;
|
|
17833
18759
|
candidate += 1;
|
|
17834
18760
|
}
|
|
17835
18761
|
return null;
|
|
@@ -18280,7 +19206,7 @@ async function runCaptureCli(config, argv = process.argv.slice(2)) {
|
|
|
18280
19206
|
} catch (err) {
|
|
18281
19207
|
console.error(String(err));
|
|
18282
19208
|
console.error("");
|
|
18283
|
-
console.error(
|
|
19209
|
+
console.error(usage11(config));
|
|
18284
19210
|
process.exit(1);
|
|
18285
19211
|
}
|
|
18286
19212
|
const chromePath = findChromePath2(options.chromePath);
|
|
@@ -18301,8 +19227,8 @@ async function runCaptureCli(config, argv = process.argv.slice(2)) {
|
|
|
18301
19227
|
if (forgeAlreadyRunning) {
|
|
18302
19228
|
console.log(`Reusing existing ForgeCAD render server on :${activePort}.`);
|
|
18303
19229
|
}
|
|
18304
|
-
if (!forgeAlreadyRunning && !await
|
|
18305
|
-
const fallbackPort = await
|
|
19230
|
+
if (!forgeAlreadyRunning && !await isPortFree(activePort)) {
|
|
19231
|
+
const fallbackPort = await findFreePort(activePort + 1);
|
|
18306
19232
|
if (fallbackPort == null) {
|
|
18307
19233
|
throw new Error(`Port ${activePort} is occupied and no free fallback port was found.`);
|
|
18308
19234
|
}
|
|
@@ -18322,7 +19248,7 @@ async function runCaptureCli(config, argv = process.argv.slice(2)) {
|
|
|
18322
19248
|
const message = String(err);
|
|
18323
19249
|
const isPortConflict = message.includes("already in use") || message.includes("EADDRINUSE");
|
|
18324
19250
|
if (!isPortConflict) throw err;
|
|
18325
|
-
const fallbackPort = await
|
|
19251
|
+
const fallbackPort = await findFreePort(activePort + 1);
|
|
18326
19252
|
if (fallbackPort == null) throw err;
|
|
18327
19253
|
console.log(`Port ${activePort} failed to start due to a port conflict. Retrying on ${fallbackPort} ...`);
|
|
18328
19254
|
activePort = fallbackPort;
|
|
@@ -18357,7 +19283,7 @@ async function runCaptureCli(config, argv = process.argv.slice(2)) {
|
|
|
18357
19283
|
debugLog(`loading capture runtime on :${activePort}`);
|
|
18358
19284
|
let captureRuntimeReady = await loadCaptureRuntime(page, activePort, options.capture);
|
|
18359
19285
|
if (!captureRuntimeReady && !viteProc) {
|
|
18360
|
-
const fallbackPort = await
|
|
19286
|
+
const fallbackPort = await findFreePort(activePort + 1);
|
|
18361
19287
|
if (fallbackPort != null) {
|
|
18362
19288
|
console.log(`Existing server on :${activePort} is missing required capture APIs. Starting a fresh server on :${fallbackPort} ...`);
|
|
18363
19289
|
activePort = fallbackPort;
|
|
@@ -18906,11 +19832,10 @@ function runHiddenCompletionCli(argv, commands2) {
|
|
|
18906
19832
|
}
|
|
18907
19833
|
|
|
18908
19834
|
// cli/forge-dev.ts
|
|
18909
|
-
import { fork } from "child_process";
|
|
18910
19835
|
import path from "path";
|
|
18911
19836
|
import { resolve as resolve18, dirname as dirname4, basename as basename7 } from "path";
|
|
18912
|
-
import {
|
|
18913
|
-
function
|
|
19837
|
+
import { statSync as statSync7 } from "fs";
|
|
19838
|
+
function usage12() {
|
|
18914
19839
|
console.error(`ForgeCAD Dev Server
|
|
18915
19840
|
|
|
18916
19841
|
Usage:
|
|
@@ -18938,7 +19863,7 @@ function parseDevArgs(argv) {
|
|
|
18938
19863
|
if (argv.length === 0) {
|
|
18939
19864
|
throw new Error("Missing project path. Use `forgecad dev <project-path> [project-path ...]`.");
|
|
18940
19865
|
}
|
|
18941
|
-
if (argv.includes("-h") || argv.includes("--help"))
|
|
19866
|
+
if (argv.includes("-h") || argv.includes("--help")) usage12();
|
|
18942
19867
|
const options = { open: false, strictPort: false, projectPaths: [] };
|
|
18943
19868
|
for (let i = 0; i < argv.length; i += 1) {
|
|
18944
19869
|
const arg = argv[i];
|
|
@@ -18998,35 +19923,10 @@ function waitForExit(child) {
|
|
|
18998
19923
|
child.once("exit", (code) => resolve40(code ?? 0));
|
|
18999
19924
|
});
|
|
19000
19925
|
}
|
|
19001
|
-
function spawnBackendServer(packageRoot) {
|
|
19002
|
-
const backendEntry = resolve18(packageRoot, "dist-backend", "server.js");
|
|
19003
|
-
if (!existsSync9(backendEntry)) return null;
|
|
19004
|
-
const child = fork(backendEntry, {
|
|
19005
|
-
stdio: ["ignore", "pipe", "pipe", "ipc"],
|
|
19006
|
-
env: { ...process.env, FORGE_BACKEND_PORT: "4510" }
|
|
19007
|
-
});
|
|
19008
|
-
child.stdout?.on("data", (data) => {
|
|
19009
|
-
for (const line of data.toString().trim().split("\n")) {
|
|
19010
|
-
console.log(` [backend] ${line}`);
|
|
19011
|
-
}
|
|
19012
|
-
});
|
|
19013
|
-
child.stderr?.on("data", (data) => {
|
|
19014
|
-
for (const line of data.toString().trim().split("\n")) {
|
|
19015
|
-
console.error(` [backend] ${line}`);
|
|
19016
|
-
}
|
|
19017
|
-
});
|
|
19018
|
-
child.on("exit", (code) => {
|
|
19019
|
-
if (code !== null && code !== 0) {
|
|
19020
|
-
console.error(` [backend] Compute server exited with code ${code}`);
|
|
19021
|
-
}
|
|
19022
|
-
});
|
|
19023
|
-
return child;
|
|
19024
|
-
}
|
|
19025
19926
|
async function runDevCli(argv = process.argv.slice(2)) {
|
|
19026
19927
|
const options = parseDevArgs(argv);
|
|
19027
19928
|
const projectDirs = options.projectPaths;
|
|
19028
19929
|
const packageRoot = packageRootFrom(import.meta.url);
|
|
19029
|
-
const backendChild = spawnBackendServer(packageRoot);
|
|
19030
19930
|
const child = spawnPackageVite(import.meta.url, toViteArgs(options), {
|
|
19031
19931
|
cwd: packageRoot,
|
|
19032
19932
|
stdio: "inherit",
|
|
@@ -19040,7 +19940,6 @@ async function runDevCli(argv = process.argv.slice(2)) {
|
|
|
19040
19940
|
}
|
|
19041
19941
|
});
|
|
19042
19942
|
const code = await waitForExit(child);
|
|
19043
|
-
if (backendChild && !backendChild.killed) backendChild.kill();
|
|
19044
19943
|
if (code !== 0) process.exit(code);
|
|
19045
19944
|
}
|
|
19046
19945
|
|
|
@@ -19108,130 +20007,9 @@ async function runGcodeExportCli(argv) {
|
|
|
19108
20007
|
console.log(` Bounds: [${tp.bounds.min.map((v) => v.toFixed(1)).join(", ")}] \u2192 [${tp.bounds.max.map((v) => v.toFixed(1)).join(", ")}]`);
|
|
19109
20008
|
}
|
|
19110
20009
|
|
|
19111
|
-
// src/forge/targets.ts
|
|
19112
|
-
var cleanSceneTargetPathSegments = (segments) => (segments ?? []).map((segment) => segment.trim()).filter((segment) => segment.length > 0);
|
|
19113
|
-
function getSceneObjectTreePath(object) {
|
|
19114
|
-
const explicitTreePath = cleanSceneTargetPathSegments(object.treePath);
|
|
19115
|
-
if (explicitTreePath.length > 0) return explicitTreePath;
|
|
19116
|
-
const name = object.name.trim() || object.id;
|
|
19117
|
-
const groupName = object.groupName?.trim();
|
|
19118
|
-
if (!groupName) return [name];
|
|
19119
|
-
const groupPath = groupName.split(".").map((segment) => segment.trim()).filter((segment) => segment.length > 0);
|
|
19120
|
-
const prefixedLeaf = `${groupName}.`;
|
|
19121
|
-
if (name.startsWith(prefixedLeaf)) {
|
|
19122
|
-
const leafName = name.slice(prefixedLeaf.length).trim();
|
|
19123
|
-
return [...groupPath, leafName || name];
|
|
19124
|
-
}
|
|
19125
|
-
return [...groupPath, name];
|
|
19126
|
-
}
|
|
19127
|
-
function getSceneObjectKind(object) {
|
|
19128
|
-
if (object.mock) return "mock";
|
|
19129
|
-
if (object.sketch) return "sketch";
|
|
19130
|
-
if (object.toolpath) return "toolpath";
|
|
19131
|
-
if (object.sdf) return "sdf";
|
|
19132
|
-
if (object.shape) return "shape";
|
|
19133
|
-
return "object";
|
|
19134
|
-
}
|
|
19135
|
-
function buildSceneTargetEntries(objects) {
|
|
19136
|
-
return objects.map((object) => {
|
|
19137
|
-
const pathSegments = getSceneObjectTreePath(object);
|
|
19138
|
-
return {
|
|
19139
|
-
id: object.id,
|
|
19140
|
-
name: object.name,
|
|
19141
|
-
kind: getSceneObjectKind(object),
|
|
19142
|
-
pathSegments,
|
|
19143
|
-
path: pathSegments.join("/"),
|
|
19144
|
-
dottedPath: pathSegments.join("."),
|
|
19145
|
-
group: object.groupName?.trim() || void 0,
|
|
19146
|
-
tags: object.tags ?? [],
|
|
19147
|
-
mock: object.mock === true,
|
|
19148
|
-
object
|
|
19149
|
-
};
|
|
19150
|
-
});
|
|
19151
|
-
}
|
|
19152
|
-
function formatSceneTargetPath(entry) {
|
|
19153
|
-
return entry.path || entry.name;
|
|
19154
|
-
}
|
|
19155
|
-
function resolveSceneTargets(entries, selector) {
|
|
19156
|
-
const needle = normalizeTargetText(selector);
|
|
19157
|
-
if (!needle) throw new Error("Target selector must not be empty.");
|
|
19158
|
-
const hasGlob = /[*?]/.test(selector);
|
|
19159
|
-
if (hasGlob) {
|
|
19160
|
-
const pattern = globToRegExp(needle);
|
|
19161
|
-
const matches = entries.filter((entry) => targetCandidateTexts(entry).some((candidate) => pattern.test(candidate)));
|
|
19162
|
-
if (matches.length > 0) return matches;
|
|
19163
|
-
throw targetNotFound(selector, entries);
|
|
19164
|
-
}
|
|
19165
|
-
const idMatches = entries.filter((entry) => normalizeTargetText(entry.id) === needle);
|
|
19166
|
-
if (idMatches.length > 0) return idMatches;
|
|
19167
|
-
const exactPathMatches = entries.filter(
|
|
19168
|
-
(entry) => normalizeTargetText(entry.path) === needle || normalizeTargetText(entry.dottedPath) === needle
|
|
19169
|
-
);
|
|
19170
|
-
if (exactPathMatches.length > 0) return exactPathMatches;
|
|
19171
|
-
const groupMatches = entries.filter((entry) => {
|
|
19172
|
-
const groupPath = entry.pathSegments.slice(0, -1);
|
|
19173
|
-
return normalizeTargetText(groupPath.join("/")) === needle || normalizeTargetText(groupPath.join(".")) === needle;
|
|
19174
|
-
});
|
|
19175
|
-
if (groupMatches.length > 0) return groupMatches;
|
|
19176
|
-
const nameMatches2 = entries.filter(
|
|
19177
|
-
(entry) => normalizeTargetText(entry.name) === needle || normalizeTargetText(entry.pathSegments[entry.pathSegments.length - 1] ?? "") === needle
|
|
19178
|
-
);
|
|
19179
|
-
if (nameMatches2.length === 1) return nameMatches2;
|
|
19180
|
-
if (nameMatches2.length > 1) throw ambiguousTarget(selector, nameMatches2);
|
|
19181
|
-
throw targetNotFound(selector, entries);
|
|
19182
|
-
}
|
|
19183
|
-
function suggestSceneTargets(selector, entries, limit = 8) {
|
|
19184
|
-
const needle = normalizeTargetText(selector);
|
|
19185
|
-
if (!needle) return entries.slice(0, limit);
|
|
19186
|
-
return entries.map((entry) => ({ entry, score: scoreTarget(entry, needle) })).filter((ranked) => ranked.score > 0).sort((a, b) => b.score - a.score || a.entry.path.localeCompare(b.entry.path)).slice(0, limit).map((ranked) => ranked.entry);
|
|
19187
|
-
}
|
|
19188
|
-
function targetCandidateTexts(entry) {
|
|
19189
|
-
return [
|
|
19190
|
-
entry.id,
|
|
19191
|
-
entry.name,
|
|
19192
|
-
entry.path,
|
|
19193
|
-
entry.dottedPath,
|
|
19194
|
-
entry.group ?? "",
|
|
19195
|
-
entry.pathSegments[entry.pathSegments.length - 1] ?? ""
|
|
19196
|
-
].map(normalizeTargetText);
|
|
19197
|
-
}
|
|
19198
|
-
function normalizeTargetText(value) {
|
|
19199
|
-
return value.trim().replace(/\\/g, "/").replace(/\s*\/\s*/g, "/").replace(/\s*\.\s*/g, ".").toLowerCase();
|
|
19200
|
-
}
|
|
19201
|
-
function globToRegExp(value) {
|
|
19202
|
-
const escaped = value.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
19203
|
-
return new RegExp(`^${escaped}$`, "i");
|
|
19204
|
-
}
|
|
19205
|
-
function scoreTarget(entry, needle) {
|
|
19206
|
-
const candidates = targetCandidateTexts(entry);
|
|
19207
|
-
if (candidates.some((candidate) => candidate === needle)) return 100;
|
|
19208
|
-
if (candidates.some((candidate) => candidate.startsWith(needle))) return 60;
|
|
19209
|
-
if (candidates.some((candidate) => candidate.includes(needle))) return 30;
|
|
19210
|
-
const tokens = needle.split(/[\s/._-]+/).filter(Boolean);
|
|
19211
|
-
if (tokens.length > 0 && tokens.every((token) => candidates.some((candidate) => candidate.includes(token)))) return 15;
|
|
19212
|
-
return 0;
|
|
19213
|
-
}
|
|
19214
|
-
function targetSummary(entry) {
|
|
19215
|
-
return `${formatSceneTargetPath(entry)} (${entry.kind}, ${entry.id})`;
|
|
19216
|
-
}
|
|
19217
|
-
function targetNotFound(selector, entries) {
|
|
19218
|
-
const suggestions = suggestSceneTargets(selector, entries);
|
|
19219
|
-
const detail = suggestions.length > 0 ? `
|
|
19220
|
-
Did you mean:
|
|
19221
|
-
${suggestions.map((entry) => ` ${targetSummary(entry)}`).join("\n")}` : "";
|
|
19222
|
-
return new Error(`Target "${selector}" matched no scene objects.${detail}`);
|
|
19223
|
-
}
|
|
19224
|
-
function ambiguousTarget(selector, matches) {
|
|
19225
|
-
return new Error(
|
|
19226
|
-
`Target "${selector}" is ambiguous.
|
|
19227
|
-
Use a full path:
|
|
19228
|
-
${matches.map((entry) => ` ${targetSummary(entry)}`).join("\n")}`
|
|
19229
|
-
);
|
|
19230
|
-
}
|
|
19231
|
-
|
|
19232
20010
|
// cli/forge-ls.ts
|
|
19233
20011
|
var TARGET_KINDS = ["shape", "sketch", "sdf", "toolpath", "mock", "object"];
|
|
19234
|
-
function
|
|
20012
|
+
function usage13() {
|
|
19235
20013
|
return [
|
|
19236
20014
|
"Usage: forgecad ls <model.forge.js|asset.stl|asset.obj|asset.3mf|asset.step|asset.stp> [target] [--tree] [--long] [--json] [--kind shape,sketch,mock] [--param Key=Value] [--backend manifold|occt|truck] [--quality live|default|high]"
|
|
19237
20015
|
].join("\n");
|
|
@@ -19250,7 +20028,7 @@ function parseKindFilter(raw) {
|
|
|
19250
20028
|
}
|
|
19251
20029
|
function parseLsOptions(argv) {
|
|
19252
20030
|
if (argv.length === 0 || argv.includes("-h") || argv.includes("--help")) {
|
|
19253
|
-
console.log(
|
|
20031
|
+
console.log(usage13());
|
|
19254
20032
|
process.exit(0);
|
|
19255
20033
|
}
|
|
19256
20034
|
const { overrides: _paramOverrides, consumed: paramConsumed } = parseParamFlags(argv);
|
|
@@ -19298,12 +20076,12 @@ function filteredTargets(entries, options) {
|
|
|
19298
20076
|
if (options.kinds) targets = targets.filter((entry) => options.kinds?.has(entry.kind));
|
|
19299
20077
|
return targets;
|
|
19300
20078
|
}
|
|
19301
|
-
function
|
|
20079
|
+
function formatNumber2(value, digits = 1) {
|
|
19302
20080
|
if (!Number.isFinite(value)) return String(value);
|
|
19303
20081
|
return value.toFixed(digits).replace(/\.0$/, "");
|
|
19304
20082
|
}
|
|
19305
|
-
function
|
|
19306
|
-
return `[${min.map((v) =>
|
|
20083
|
+
function formatBounds2(min, max) {
|
|
20084
|
+
return `[${min.map((v) => formatNumber2(v)).join(",")}]..[${max.map((v) => formatNumber2(v)).join(",")}]`;
|
|
19307
20085
|
}
|
|
19308
20086
|
function targetMetrics(entry) {
|
|
19309
20087
|
const object = entry.object;
|
|
@@ -19340,7 +20118,7 @@ function printLineList(entries, long) {
|
|
|
19340
20118
|
continue;
|
|
19341
20119
|
}
|
|
19342
20120
|
const metrics = targetMetrics(entry);
|
|
19343
|
-
const detail = metrics && "volume" in metrics ? ` id=${entry.id} vol=${
|
|
20121
|
+
const detail = metrics && "volume" in metrics ? ` id=${entry.id} vol=${formatNumber2(metrics.volume)} tris=${metrics.triangles.toLocaleString()} bodies=${metrics.bodies} bbox=${formatBounds2(metrics.bounds.min, metrics.bounds.max)}` : metrics && "area" in metrics ? ` id=${entry.id} area=${formatNumber2(metrics.area)} regions=${metrics.regions}` : ` id=${entry.id}`;
|
|
19344
20122
|
console.log(`${base}${detail}`);
|
|
19345
20123
|
}
|
|
19346
20124
|
}
|
|
@@ -19364,7 +20142,7 @@ async function runLsCli(argv = process.argv.slice(2)) {
|
|
|
19364
20142
|
options = parseLsOptions(argv);
|
|
19365
20143
|
} catch (error) {
|
|
19366
20144
|
console.error(error instanceof Error ? error.message : String(error));
|
|
19367
|
-
console.error(
|
|
20145
|
+
console.error(usage13());
|
|
19368
20146
|
process.exit(1);
|
|
19369
20147
|
}
|
|
19370
20148
|
const { overrides } = parseParamFlags(argv);
|
|
@@ -19635,6 +20413,11 @@ async function runInspectEvidenceListCli(argv) {
|
|
|
19635
20413
|
process.exit(1);
|
|
19636
20414
|
}
|
|
19637
20415
|
const commands2 = [
|
|
20416
|
+
{
|
|
20417
|
+
command: "inspect sketch",
|
|
20418
|
+
evidence: "sketch",
|
|
20419
|
+
description: "Inspect returned sketch/profile regions and seed selector dry-runs."
|
|
20420
|
+
},
|
|
19638
20421
|
{
|
|
19639
20422
|
command: "inspect visual image",
|
|
19640
20423
|
evidence: "image",
|
|
@@ -19643,6 +20426,7 @@ async function runInspectEvidenceListCli(argv) {
|
|
|
19643
20426
|
{ command: "inspect visual cutaway", evidence: "cutaway", description: "Capture a clipped 3D viewport from the cut side." },
|
|
19644
20427
|
{ command: "inspect visual depth", evidence: "depth", description: "Capture visible surface depth evidence." },
|
|
19645
20428
|
{ command: "inspect visual normals", evidence: "normals", description: "Capture camera-view surface normal evidence." },
|
|
20429
|
+
{ command: "inspect visual rig", evidence: "rig", description: "Capture kinematic rig skeleton evidence." },
|
|
19646
20430
|
{ command: "inspect visual objects", evidence: "objects", description: "Capture object identity evidence." },
|
|
19647
20431
|
{ command: "inspect surface zebra", evidence: "zebra", description: "Capture Zebra stripe surface-continuity evidence." },
|
|
19648
20432
|
{ command: "inspect surface roughness", evidence: "roughness", description: "Capture mesh roughness and sharp-feature evidence." },
|
|
@@ -19680,7 +20464,7 @@ async function runInspectEvidenceListCli(argv) {
|
|
|
19680
20464
|
}
|
|
19681
20465
|
|
|
19682
20466
|
// cli/forge-render-hq.ts
|
|
19683
|
-
import { writeFileSync as writeFileSync8, mkdtempSync as mkdtempSync2, rmSync as rmSync2, existsSync as
|
|
20467
|
+
import { writeFileSync as writeFileSync8, mkdtempSync as mkdtempSync2, rmSync as rmSync2, existsSync as existsSync9 } from "fs";
|
|
19684
20468
|
import { resolve as resolve22, dirname as dirname5, join as join7, extname as extname7 } from "path";
|
|
19685
20469
|
import { execSync as execSync2, spawnSync } from "child_process";
|
|
19686
20470
|
import { tmpdir as tmpdir2 } from "os";
|
|
@@ -19897,7 +20681,7 @@ function parseArgs7(argv) {
|
|
|
19897
20681
|
i += 1;
|
|
19898
20682
|
} else if (arg === "--hdri") {
|
|
19899
20683
|
hdriPath = resolve22(next);
|
|
19900
|
-
if (!
|
|
20684
|
+
if (!existsSync9(hdriPath)) throw new Error(`HDRI file not found: ${hdriPath}`);
|
|
19901
20685
|
i += 1;
|
|
19902
20686
|
} else if (arg === "--camera") {
|
|
19903
20687
|
const value = readArgValue(argv, i, arg);
|
|
@@ -20002,11 +20786,11 @@ function findBlender() {
|
|
|
20002
20786
|
`${process.env.HOME}/Applications/Blender.app/Contents/MacOS/Blender`
|
|
20003
20787
|
];
|
|
20004
20788
|
for (const p of macPaths) {
|
|
20005
|
-
if (
|
|
20789
|
+
if (existsSync9(p)) return p;
|
|
20006
20790
|
}
|
|
20007
20791
|
const linuxPaths = ["/usr/bin/blender", "/snap/bin/blender", "/usr/local/bin/blender"];
|
|
20008
20792
|
for (const p of linuxPaths) {
|
|
20009
|
-
if (
|
|
20793
|
+
if (existsSync9(p)) return p;
|
|
20010
20794
|
}
|
|
20011
20795
|
throw new Error(
|
|
20012
20796
|
"Blender not found. Install it:\n macOS: brew install --cask blender\n Linux: sudo apt install blender (or snap install blender)\n All: https://www.blender.org/download/"
|
|
@@ -20021,7 +20805,7 @@ function defaultOutputPath5(scriptPath, video) {
|
|
|
20021
20805
|
function resolveBlenderRenderScript() {
|
|
20022
20806
|
const cliDir = dirname5(fileURLToPath2(import.meta.url));
|
|
20023
20807
|
const renderScript = join7(cliDir, "blender", "render.py");
|
|
20024
|
-
if (!
|
|
20808
|
+
if (!existsSync9(renderScript)) {
|
|
20025
20809
|
throw new Error(
|
|
20026
20810
|
`Blender render script missing at ${renderScript}. Rebuild the CLI with \`npm run build:cli\`; packaged installs should include dist-cli/blender/render.py.`
|
|
20027
20811
|
);
|
|
@@ -20199,7 +20983,7 @@ async function runRenderHqCli(argv) {
|
|
|
20199
20983
|
rmSync2(tmpDir, { recursive: true, force: true });
|
|
20200
20984
|
} catch {
|
|
20201
20985
|
}
|
|
20202
|
-
if (
|
|
20986
|
+
if (existsSync9(outputPath)) {
|
|
20203
20987
|
const stats = (await import("fs")).statSync(outputPath);
|
|
20204
20988
|
const sizeMb = (stats.size / 1024 / 1024).toFixed(1);
|
|
20205
20989
|
console.log(`
|
|
@@ -20225,12 +21009,12 @@ import { basename as basename9, resolve as resolve23 } from "path";
|
|
|
20225
21009
|
function defaultPngOutput(scriptPath) {
|
|
20226
21010
|
return scriptPath.replace(/\.(forge|sketch)\.js$/, ".png").replace(/\.js$/, ".png");
|
|
20227
21011
|
}
|
|
20228
|
-
function
|
|
21012
|
+
function usage14() {
|
|
20229
21013
|
console.error("Usage: forgecad render sketch <script.forge.js> [output.png] [--size <px>] [--chrome-path <path>]");
|
|
20230
21014
|
process.exit(1);
|
|
20231
21015
|
}
|
|
20232
21016
|
function parseCli2(argv) {
|
|
20233
|
-
if (argv.length === 0 || argv.includes("-h") || argv.includes("--help"))
|
|
21017
|
+
if (argv.length === 0 || argv.includes("-h") || argv.includes("--help")) usage14();
|
|
20234
21018
|
let scriptPath;
|
|
20235
21019
|
let outputPath;
|
|
20236
21020
|
let size = 1024;
|
|
@@ -20255,7 +21039,7 @@ function parseCli2(argv) {
|
|
|
20255
21039
|
else if (!outputPath) outputPath = arg;
|
|
20256
21040
|
else throw new Error(`Unexpected argument: ${arg}`);
|
|
20257
21041
|
}
|
|
20258
|
-
if (!scriptPath)
|
|
21042
|
+
if (!scriptPath) usage14();
|
|
20259
21043
|
if (!Number.isFinite(size) || size < 128 || size > 4096) throw new Error(`--size must be between 128 and 4096 (got ${size})`);
|
|
20260
21044
|
return {
|
|
20261
21045
|
scriptPath,
|
|
@@ -20271,7 +21055,7 @@ async function runRender2dCli(argv = process.argv.slice(2)) {
|
|
|
20271
21055
|
options = parseCli2(argv);
|
|
20272
21056
|
} catch (err) {
|
|
20273
21057
|
console.error(String(err));
|
|
20274
|
-
|
|
21058
|
+
usage14();
|
|
20275
21059
|
}
|
|
20276
21060
|
const chromePath = findChromePath(options.chromePath);
|
|
20277
21061
|
if (!chromePath) {
|
|
@@ -20390,13 +21174,13 @@ function formatSheetCutList(input) {
|
|
|
20390
21174
|
}
|
|
20391
21175
|
|
|
20392
21176
|
// cli/forge-cut-list.ts
|
|
20393
|
-
function
|
|
21177
|
+
function usage15() {
|
|
20394
21178
|
console.error("Usage: forgecad cut-list <script.forge.js>");
|
|
20395
21179
|
process.exit(1);
|
|
20396
21180
|
}
|
|
20397
21181
|
async function runCutListCli(argv = process.argv.slice(2)) {
|
|
20398
21182
|
const scriptPath = argv[0];
|
|
20399
|
-
if (!scriptPath)
|
|
21183
|
+
if (!scriptPath) usage15();
|
|
20400
21184
|
const source = await readFile2(resolve24(scriptPath), "utf-8");
|
|
20401
21185
|
const { allFiles, fileName } = collectProjectFiles(scriptPath);
|
|
20402
21186
|
await init();
|
|
@@ -20429,7 +21213,7 @@ function hasFlag(argv, name) {
|
|
|
20429
21213
|
const prefix = `${name}=`;
|
|
20430
21214
|
return argv.some((arg) => arg === name || arg.startsWith(prefix));
|
|
20431
21215
|
}
|
|
20432
|
-
function
|
|
21216
|
+
function usage16() {
|
|
20433
21217
|
console.error(
|
|
20434
21218
|
"Usage: forgecad export cutting-layout <script.forge.js> [output.pdf|output.dxf]\n [--format pdf|dxf] [--sheet-width <mm>] [--sheet-height <mm>] [--kerf <mm>]"
|
|
20435
21219
|
);
|
|
@@ -20493,7 +21277,7 @@ function smartDefaults(entries) {
|
|
|
20493
21277
|
}
|
|
20494
21278
|
async function runCuttingLayoutCli(argv = process.argv.slice(2)) {
|
|
20495
21279
|
const scriptPath = argv[0];
|
|
20496
|
-
if (!scriptPath)
|
|
21280
|
+
if (!scriptPath) usage16();
|
|
20497
21281
|
const explicitOutputPath = outputPathArg(argv);
|
|
20498
21282
|
const formatArg = argValue(argv, "--format");
|
|
20499
21283
|
if (hasFlag(argv, "--format") && formatArg == null) {
|
|
@@ -20556,7 +21340,7 @@ function argValue2(argv, name) {
|
|
|
20556
21340
|
if (idx === -1) return void 0;
|
|
20557
21341
|
return argv[idx + 1];
|
|
20558
21342
|
}
|
|
20559
|
-
function
|
|
21343
|
+
function usage17() {
|
|
20560
21344
|
console.error(
|
|
20561
21345
|
"Usage: forgecad export report <model.forge.js|asset.stl|asset.obj|asset.3mf|asset.step|asset.stp> [output.pdf] [--dim-angle-tol <deg>]"
|
|
20562
21346
|
);
|
|
@@ -20564,7 +21348,7 @@ function usage16() {
|
|
|
20564
21348
|
}
|
|
20565
21349
|
async function runReportCli(argv = process.argv.slice(2)) {
|
|
20566
21350
|
const scriptPath = argv[0];
|
|
20567
|
-
if (!scriptPath)
|
|
21351
|
+
if (!scriptPath) usage17();
|
|
20568
21352
|
const defaultOut = replaceCliInputExtension(scriptPath, ".report.pdf");
|
|
20569
21353
|
const outputPath = argv[1] && !argv[1].startsWith("--") ? argv[1] : defaultOut;
|
|
20570
21354
|
const toleranceArg = argValue2(argv, "--dim-angle-tol");
|
|
@@ -20628,16 +21412,16 @@ function mmToM(valueMm) {
|
|
|
20628
21412
|
function degToRad(valueDeg) {
|
|
20629
21413
|
return valueDeg * Math.PI / 180;
|
|
20630
21414
|
}
|
|
20631
|
-
function
|
|
21415
|
+
function formatNumber3(value, digits = 6) {
|
|
20632
21416
|
if (!Number.isFinite(value)) return "0";
|
|
20633
21417
|
const normalized = Math.abs(value) < 1e-12 ? 0 : value;
|
|
20634
21418
|
return normalized.toFixed(digits).replace(/\.?0+$/, "");
|
|
20635
21419
|
}
|
|
20636
21420
|
function formatPose(parts) {
|
|
20637
|
-
return [...parts.xyzM.map((value) =>
|
|
21421
|
+
return [...parts.xyzM.map((value) => formatNumber3(value, 6)), ...parts.rpyRad.map((value) => formatNumber3(value, 6))].join(" ");
|
|
20638
21422
|
}
|
|
20639
21423
|
function axisToText(axis) {
|
|
20640
|
-
return axis.map((value) =>
|
|
21424
|
+
return axis.map((value) => formatNumber3(value, 6)).join(" ");
|
|
20641
21425
|
}
|
|
20642
21426
|
function transformToPose(transform) {
|
|
20643
21427
|
const m = transform.toArray();
|
|
@@ -20783,15 +21567,15 @@ function inertiaFromBounds(geometry, massKg) {
|
|
|
20783
21567
|
}
|
|
20784
21568
|
function jointTypeLimitUnits(joint2, value) {
|
|
20785
21569
|
if (value === void 0) return null;
|
|
20786
|
-
if (joint2.type === "revolute") return
|
|
20787
|
-
if (joint2.type === "prismatic") return
|
|
21570
|
+
if (joint2.type === "revolute") return formatNumber3(degToRad(value), 6);
|
|
21571
|
+
if (joint2.type === "prismatic") return formatNumber3(mmToM(value), 6);
|
|
20788
21572
|
return null;
|
|
20789
21573
|
}
|
|
20790
21574
|
function jointVelocityUnits(joint2, value) {
|
|
20791
21575
|
if (value === void 0) return null;
|
|
20792
|
-
if (joint2.type === "revolute") return
|
|
20793
|
-
if (joint2.type === "prismatic") return
|
|
20794
|
-
return
|
|
21576
|
+
if (joint2.type === "revolute") return formatNumber3(degToRad(value), 6);
|
|
21577
|
+
if (joint2.type === "prismatic") return formatNumber3(mmToM(value), 6);
|
|
21578
|
+
return formatNumber3(value, 6);
|
|
20795
21579
|
}
|
|
20796
21580
|
function sRgbFloat(hex) {
|
|
20797
21581
|
if (!hex || !/^#([0-9a-f]{6})$/i.test(hex)) return null;
|
|
@@ -20820,14 +21604,14 @@ function demoWorldName(world, modelName) {
|
|
|
20820
21604
|
}
|
|
20821
21605
|
function keyboardPluginXml(cmdVelTopic, linearStep, angularStep) {
|
|
20822
21606
|
const bindings = [
|
|
20823
|
-
{ key: 87, twist: `linear: {x: ${
|
|
20824
|
-
{ key: 88, twist: `linear: {x: ${
|
|
20825
|
-
{ key: 65, twist: `linear: {x: 0.0}, angular: {z: ${
|
|
20826
|
-
{ key: 68, twist: `linear: {x: 0.0}, angular: {z: ${
|
|
20827
|
-
{ key: 81, twist: `linear: {x: ${
|
|
20828
|
-
{ key: 69, twist: `linear: {x: ${
|
|
20829
|
-
{ key: 90, twist: `linear: {x: ${
|
|
20830
|
-
{ key: 67, twist: `linear: {x: ${
|
|
21607
|
+
{ key: 87, twist: `linear: {x: ${formatNumber3(linearStep, 3)}}, angular: {z: 0.0}` },
|
|
21608
|
+
{ key: 88, twist: `linear: {x: ${formatNumber3(-linearStep, 3)}}, angular: {z: 0.0}` },
|
|
21609
|
+
{ key: 65, twist: `linear: {x: 0.0}, angular: {z: ${formatNumber3(angularStep, 3)}}` },
|
|
21610
|
+
{ key: 68, twist: `linear: {x: 0.0}, angular: {z: ${formatNumber3(-angularStep, 3)}}` },
|
|
21611
|
+
{ key: 81, twist: `linear: {x: ${formatNumber3(linearStep, 3)}}, angular: {z: ${formatNumber3(angularStep, 3)}}` },
|
|
21612
|
+
{ key: 69, twist: `linear: {x: ${formatNumber3(linearStep, 3)}}, angular: {z: ${formatNumber3(-angularStep, 3)}}` },
|
|
21613
|
+
{ key: 90, twist: `linear: {x: ${formatNumber3(-linearStep, 3)}}, angular: {z: ${formatNumber3(angularStep, 3)}}` },
|
|
21614
|
+
{ key: 67, twist: `linear: {x: ${formatNumber3(-linearStep, 3)}}, angular: {z: ${formatNumber3(-angularStep, 3)}}` },
|
|
20831
21615
|
{ key: 83, twist: "linear: {x: 0.0}, angular: {z: 0.0}" },
|
|
20832
21616
|
{ key: 32, twist: "linear: {x: 0.0}, angular: {z: 0.0}" }
|
|
20833
21617
|
];
|
|
@@ -20987,12 +21771,12 @@ ${keyboardGuiPluginXml()}` : "";
|
|
|
20987
21771
|
function demoWorldXml(worldName, modelName, cmdVelTopic, world) {
|
|
20988
21772
|
const spawnPose = world?.spawnPose ?? [0, 0, 120, 0, 0, 0];
|
|
20989
21773
|
const spawnPoseText = [
|
|
20990
|
-
|
|
20991
|
-
|
|
20992
|
-
|
|
20993
|
-
|
|
20994
|
-
|
|
20995
|
-
|
|
21774
|
+
formatNumber3(mmToM(spawnPose[0]), 6),
|
|
21775
|
+
formatNumber3(mmToM(spawnPose[1]), 6),
|
|
21776
|
+
formatNumber3(mmToM(spawnPose[2]), 6),
|
|
21777
|
+
formatNumber3(degToRad(spawnPose[3]), 6),
|
|
21778
|
+
formatNumber3(degToRad(spawnPose[4]), 6),
|
|
21779
|
+
formatNumber3(degToRad(spawnPose[5]), 6)
|
|
20996
21780
|
].join(" ");
|
|
20997
21781
|
const keyboardEnabled = world?.keyboardTeleop?.enabled ?? true;
|
|
20998
21782
|
const linearStep = world?.keyboardTeleop?.linearStep ?? 0.9;
|
|
@@ -21162,28 +21946,28 @@ function modelXml(spec, modelName, linkNameMap, jointNameMap, geometries, linkWo
|
|
|
21162
21946
|
const color = sRgbFloat(geometry.shapes[0]?.colorHex);
|
|
21163
21947
|
const materialXml = color ? `
|
|
21164
21948
|
<material>
|
|
21165
|
-
<ambient>${
|
|
21166
|
-
<diffuse>${
|
|
21949
|
+
<ambient>${formatNumber3(color[0], 3)} ${formatNumber3(color[1], 3)} ${formatNumber3(color[2], 3)} 1</ambient>
|
|
21950
|
+
<diffuse>${formatNumber3(color[0], 3)} ${formatNumber3(color[1], 3)} ${formatNumber3(color[2], 3)} 1</diffuse>
|
|
21167
21951
|
</material>` : "";
|
|
21168
21952
|
return ` <link name="${escapeXml(sdfLinkName)}">
|
|
21169
21953
|
<pose relative_to="__model__">${formatPose(worldPose)}</pose>
|
|
21170
21954
|
<inertial>
|
|
21171
21955
|
<pose>${formatPose(inertia.pose)}</pose>
|
|
21172
|
-
<mass>${
|
|
21956
|
+
<mass>${formatNumber3(massKg, 6)}</mass>
|
|
21173
21957
|
<inertia>
|
|
21174
|
-
<ixx>${
|
|
21175
|
-
<ixy>${
|
|
21176
|
-
<ixz>${
|
|
21177
|
-
<iyy>${
|
|
21178
|
-
<iyz>${
|
|
21179
|
-
<izz>${
|
|
21958
|
+
<ixx>${formatNumber3(inertia.ixx, 8)}</ixx>
|
|
21959
|
+
<ixy>${formatNumber3(inertia.ixy, 8)}</ixy>
|
|
21960
|
+
<ixz>${formatNumber3(inertia.ixz, 8)}</ixz>
|
|
21961
|
+
<iyy>${formatNumber3(inertia.iyy, 8)}</iyy>
|
|
21962
|
+
<iyz>${formatNumber3(inertia.iyz, 8)}</iyz>
|
|
21963
|
+
<izz>${formatNumber3(inertia.izz, 8)}</izz>
|
|
21180
21964
|
</inertia>
|
|
21181
21965
|
</inertial>
|
|
21182
21966
|
<visual name="${escapeXml(sdfLinkName)}_visual">
|
|
21183
21967
|
<geometry>
|
|
21184
21968
|
<mesh>
|
|
21185
21969
|
<uri>${escapeXml(meshPath)}</uri>
|
|
21186
|
-
<scale>${
|
|
21970
|
+
<scale>${formatNumber3(STL_SCALE_METERS, 6)} ${formatNumber3(STL_SCALE_METERS, 6)} ${formatNumber3(STL_SCALE_METERS, 6)}</scale>
|
|
21187
21971
|
</mesh>
|
|
21188
21972
|
</geometry>${materialXml}
|
|
21189
21973
|
</visual>${(() => {
|
|
@@ -21193,7 +21977,7 @@ function modelXml(spec, modelName, linkNameMap, jointNameMap, geometries, linkWo
|
|
|
21193
21977
|
<geometry>
|
|
21194
21978
|
<mesh>
|
|
21195
21979
|
<uri>${escapeXml(meshPath)}</uri>
|
|
21196
|
-
<scale>${
|
|
21980
|
+
<scale>${formatNumber3(STL_SCALE_METERS, 6)} ${formatNumber3(STL_SCALE_METERS, 6)} ${formatNumber3(STL_SCALE_METERS, 6)}</scale>
|
|
21197
21981
|
</mesh>
|
|
21198
21982
|
</geometry>
|
|
21199
21983
|
</collision>`;
|
|
@@ -21205,7 +21989,7 @@ function modelXml(spec, modelName, linkNameMap, jointNameMap, geometries, linkWo
|
|
|
21205
21989
|
<geometry>
|
|
21206
21990
|
<mesh>
|
|
21207
21991
|
<uri>${escapeXml(collisionMeshPath)}</uri>
|
|
21208
|
-
<scale>${
|
|
21992
|
+
<scale>${formatNumber3(STL_SCALE_METERS, 6)} ${formatNumber3(STL_SCALE_METERS, 6)} ${formatNumber3(STL_SCALE_METERS, 6)}</scale>
|
|
21209
21993
|
</mesh>
|
|
21210
21994
|
</geometry>
|
|
21211
21995
|
</collision>`;
|
|
@@ -21219,9 +22003,9 @@ function modelXml(spec, modelName, linkNameMap, jointNameMap, geometries, linkWo
|
|
|
21219
22003
|
const bCz = mmToM((geometry.bboxMin[2] + geometry.bboxMax[2]) * 0.5);
|
|
21220
22004
|
return `
|
|
21221
22005
|
<collision name="${escapeXml(sdfLinkName)}_collision">
|
|
21222
|
-
<pose>${
|
|
22006
|
+
<pose>${formatNumber3(bCx, 6)} ${formatNumber3(bCy, 6)} ${formatNumber3(bCz, 6)} 0 0 0</pose>
|
|
21223
22007
|
<geometry>
|
|
21224
|
-
<box><size>${
|
|
22008
|
+
<box><size>${formatNumber3(bDx, 6)} ${formatNumber3(bDy, 6)} ${formatNumber3(bDz, 6)}</size></box>
|
|
21225
22009
|
</geometry>
|
|
21226
22010
|
</collision>`;
|
|
21227
22011
|
}
|
|
@@ -21248,8 +22032,8 @@ function modelXml(spec, modelName, linkNameMap, jointNameMap, geometries, linkWo
|
|
|
21248
22032
|
const leaderSdfName = jointNameMap.get(`${primary.joint}_joint`);
|
|
21249
22033
|
mimicXml = `
|
|
21250
22034
|
<mimic joint="${escapeXml(leaderSdfName)}">
|
|
21251
|
-
<multiplier>${
|
|
21252
|
-
<offset>${
|
|
22035
|
+
<multiplier>${formatNumber3(primary.ratio, 6)}</multiplier>
|
|
22036
|
+
<offset>${formatNumber3(coupling.offset, 6)}</offset>
|
|
21253
22037
|
</mimic>`;
|
|
21254
22038
|
if (coupling.terms.length > 1) {
|
|
21255
22039
|
warnings.push(
|
|
@@ -21266,12 +22050,12 @@ function modelXml(spec, modelName, linkNameMap, jointNameMap, geometries, linkWo
|
|
|
21266
22050
|
<limit>${limitLower !== null ? `
|
|
21267
22051
|
<lower>${limitLower}</lower>` : ""}${limitUpper !== null ? `
|
|
21268
22052
|
<upper>${limitUpper}</upper>` : ""}${effort !== void 0 ? `
|
|
21269
|
-
<effort>${
|
|
22053
|
+
<effort>${formatNumber3(effort, 6)}</effort>` : ""}${velocity !== null ? `
|
|
21270
22054
|
<velocity>${velocity}</velocity>` : ""}
|
|
21271
22055
|
</limit>` : ""}${damping !== void 0 || friction !== void 0 ? `
|
|
21272
22056
|
<dynamics>${damping !== void 0 ? `
|
|
21273
|
-
<damping>${
|
|
21274
|
-
<friction>${
|
|
22057
|
+
<damping>${formatNumber3(damping, 6)}</damping>` : ""}${friction !== void 0 ? `
|
|
22058
|
+
<friction>${formatNumber3(friction, 6)}</friction>` : ""}
|
|
21275
22059
|
</dynamics>` : ""}
|
|
21276
22060
|
</axis>` : ""}
|
|
21277
22061
|
</joint>`;
|
|
@@ -21281,17 +22065,17 @@ function modelXml(spec, modelName, linkNameMap, jointNameMap, geometries, linkWo
|
|
|
21281
22065
|
plugins.push(` <plugin filename="gz-sim-diff-drive-system" name="gz::sim::systems::DiffDrive">
|
|
21282
22066
|
${spec.plugins.diffDrive.leftJoints.map((jointName) => `<left_joint>${escapeXml(jointNameMap.get(`${jointName}_joint`))}</left_joint>`).join("\n ")}
|
|
21283
22067
|
${spec.plugins.diffDrive.rightJoints.map((jointName) => `<right_joint>${escapeXml(jointNameMap.get(`${jointName}_joint`))}</right_joint>`).join("\n ")}
|
|
21284
|
-
<wheel_separation>${
|
|
21285
|
-
<wheel_radius>${
|
|
22068
|
+
<wheel_separation>${formatNumber3(mmToM(spec.plugins.diffDrive.wheelSeparationMm), 6)}</wheel_separation>
|
|
22069
|
+
<wheel_radius>${formatNumber3(mmToM(spec.plugins.diffDrive.wheelRadiusMm), 6)}</wheel_radius>
|
|
21286
22070
|
<topic>${escapeXml(cmdVelTopic)}</topic>${spec.plugins.diffDrive.odomTopic ? `
|
|
21287
22071
|
<odom_topic>${escapeXml(spec.plugins.diffDrive.odomTopic)}</odom_topic>` : ""}${spec.plugins.diffDrive.tfTopic ? `
|
|
21288
22072
|
<tf_topic>${escapeXml(spec.plugins.diffDrive.tfTopic)}</tf_topic>` : ""}${spec.plugins.diffDrive.frameId ? `
|
|
21289
22073
|
<frame_id>${escapeXml(spec.plugins.diffDrive.frameId)}</frame_id>` : ""}${spec.plugins.diffDrive.odomFrameId ? `
|
|
21290
22074
|
<odom_frame>${escapeXml(spec.plugins.diffDrive.odomFrameId)}</odom_frame>` : ""}${spec.plugins.diffDrive.maxLinearVelocity !== void 0 ? `
|
|
21291
|
-
<max_linear_velocity>${
|
|
21292
|
-
<max_angular_velocity>${
|
|
21293
|
-
<linear_acceleration>${
|
|
21294
|
-
<angular_acceleration>${
|
|
22075
|
+
<max_linear_velocity>${formatNumber3(spec.plugins.diffDrive.maxLinearVelocity, 6)}</max_linear_velocity>` : ""}${spec.plugins.diffDrive.maxAngularVelocity !== void 0 ? `
|
|
22076
|
+
<max_angular_velocity>${formatNumber3(spec.plugins.diffDrive.maxAngularVelocity, 6)}</max_angular_velocity>` : ""}${spec.plugins.diffDrive.linearAcceleration !== void 0 ? `
|
|
22077
|
+
<linear_acceleration>${formatNumber3(spec.plugins.diffDrive.linearAcceleration, 6)}</linear_acceleration>` : ""}${spec.plugins.diffDrive.angularAcceleration !== void 0 ? `
|
|
22078
|
+
<angular_acceleration>${formatNumber3(spec.plugins.diffDrive.angularAcceleration, 6)}</angular_acceleration>` : ""}
|
|
21295
22079
|
</plugin>`);
|
|
21296
22080
|
}
|
|
21297
22081
|
const jointState = spec.plugins.jointStatePublisher;
|
|
@@ -21301,7 +22085,7 @@ function modelXml(spec, modelName, linkNameMap, jointNameMap, geometries, linkWo
|
|
|
21301
22085
|
(jointName) => `
|
|
21302
22086
|
<joint_name>${escapeXml(jointNameMap.get(`${jointName}_joint`))}</joint_name>`
|
|
21303
22087
|
).join("")}${jointState?.updateRate !== void 0 ? `
|
|
21304
|
-
<update_rate>${
|
|
22088
|
+
<update_rate>${formatNumber3(jointState.updateRate, 6)}</update_rate>` : ""}
|
|
21305
22089
|
</plugin>`);
|
|
21306
22090
|
}
|
|
21307
22091
|
const rootNames = spec.assembly.parts.filter((part) => !spec.assembly.joints.some((joint2) => joint2.child === part.name)).map((part) => linkNameMap.get(part.name));
|
|
@@ -21513,7 +22297,7 @@ var FORWARDED_VALUE_OPTIONS = /* @__PURE__ */ new Set([
|
|
|
21513
22297
|
]);
|
|
21514
22298
|
var FORWARDED_FLAG_OPTIONS = /* @__PURE__ */ new Set(["--fresh-server"]);
|
|
21515
22299
|
var CAMERA_SOURCE_OPTIONS = /* @__PURE__ */ new Set(["--camera", "--camera-json", "--view", "--scene"]);
|
|
21516
|
-
function
|
|
22300
|
+
function usage18() {
|
|
21517
22301
|
return [
|
|
21518
22302
|
"Usage: forgecad show <model.forge.js|asset.stl|asset.obj|asset.3mf|asset.step|asset.stp> [target] [output.png] [--from front|back|side|right|top|iso|az:el|az:el:dist] [--out output.png] [--param Key=Value] [--backend manifold|occt|truck] [render options]"
|
|
21519
22303
|
].join("\n");
|
|
@@ -21528,7 +22312,7 @@ function readValue6(argv, index, flag) {
|
|
|
21528
22312
|
}
|
|
21529
22313
|
function parseShowOptions(argv) {
|
|
21530
22314
|
if (argv.length === 0 || argv.includes("-h") || argv.includes("--help")) {
|
|
21531
|
-
console.log(
|
|
22315
|
+
console.log(usage18());
|
|
21532
22316
|
process.exit(0);
|
|
21533
22317
|
}
|
|
21534
22318
|
const consumed = /* @__PURE__ */ new Set();
|
|
@@ -21594,7 +22378,7 @@ async function runShowCli(argv = process.argv.slice(2)) {
|
|
|
21594
22378
|
options = parseShowOptions(argv);
|
|
21595
22379
|
} catch (error) {
|
|
21596
22380
|
console.error(error instanceof Error ? error.message : String(error));
|
|
21597
|
-
console.error(
|
|
22381
|
+
console.error(usage18());
|
|
21598
22382
|
process.exit(1);
|
|
21599
22383
|
}
|
|
21600
22384
|
const renderArgs = [options.scriptPath];
|
|
@@ -22175,7 +22959,7 @@ function generateSketchPdf(meta, options) {
|
|
|
22175
22959
|
}
|
|
22176
22960
|
|
|
22177
22961
|
// cli/forge-sketch-pdf.ts
|
|
22178
|
-
function
|
|
22962
|
+
function usage19() {
|
|
22179
22963
|
console.error("Usage: forgecad export sketch-pdf <script.forge.js> [output.pdf]");
|
|
22180
22964
|
process.exit(1);
|
|
22181
22965
|
}
|
|
@@ -22184,7 +22968,7 @@ function defaultPdfOutput(scriptPath) {
|
|
|
22184
22968
|
}
|
|
22185
22969
|
async function runSketchPdfCli(argv = process.argv.slice(2)) {
|
|
22186
22970
|
const scriptPath = argv[0];
|
|
22187
|
-
if (!scriptPath)
|
|
22971
|
+
if (!scriptPath) usage19();
|
|
22188
22972
|
const outputPath = argv[1] || defaultPdfOutput(scriptPath);
|
|
22189
22973
|
if (resolve28(outputPath) === resolve28(scriptPath)) {
|
|
22190
22974
|
console.error(`ERROR: output path would overwrite the input script. Specify an explicit output path.`);
|
|
@@ -22217,7 +23001,7 @@ async function runSketchPdfCli(argv = process.argv.slice(2)) {
|
|
|
22217
23001
|
}
|
|
22218
23002
|
|
|
22219
23003
|
// cli/forge-skill.ts
|
|
22220
|
-
import { cpSync, existsSync as
|
|
23004
|
+
import { cpSync, existsSync as existsSync10, mkdirSync as mkdirSync4, readdirSync as readdirSync6, readFileSync as readFileSync15, rmSync as rmSync3, writeFileSync as writeFileSync10 } from "fs";
|
|
22221
23005
|
import { homedir as homedir6 } from "os";
|
|
22222
23006
|
import { extname as extname9, join as join8, relative as relative5, resolve as resolve29 } from "path";
|
|
22223
23007
|
var INSTALL_TARGETS = {
|
|
@@ -22230,7 +23014,7 @@ var ALL_INSTALL_TARGETS = Object.keys(INSTALL_TARGETS);
|
|
|
22230
23014
|
var DEFAULT_INSTALL_TARGET = "agents";
|
|
22231
23015
|
function installUsage() {
|
|
22232
23016
|
return [
|
|
22233
|
-
"Usage: forgecad skill install [--target agents|claude|codex|opencode|all] [--core-only]
|
|
23017
|
+
"Usage: forgecad skill install [--target agents|claude|codex|opencode|all] [--core-only]",
|
|
22234
23018
|
"Examples:",
|
|
22235
23019
|
" forgecad skill install",
|
|
22236
23020
|
" forgecad skill install --target claude",
|
|
@@ -22261,15 +23045,12 @@ function uniqueTargets(targets) {
|
|
|
22261
23045
|
return [...new Set(targets)];
|
|
22262
23046
|
}
|
|
22263
23047
|
function parseInstallArgs(argv) {
|
|
22264
|
-
let isDev = false;
|
|
22265
23048
|
let coreOnly = false;
|
|
22266
23049
|
let sawLibraryAlias = false;
|
|
22267
23050
|
let targets = [DEFAULT_INSTALL_TARGET];
|
|
22268
23051
|
for (let i = 0; i < argv.length; i++) {
|
|
22269
23052
|
const arg = argv[i];
|
|
22270
|
-
if (arg === "--
|
|
22271
|
-
isDev = true;
|
|
22272
|
-
} else if (arg === "--library" || arg === "--all") {
|
|
23053
|
+
if (arg === "--library" || arg === "--all") {
|
|
22273
23054
|
sawLibraryAlias = true;
|
|
22274
23055
|
} else if (arg === "--core-only") {
|
|
22275
23056
|
coreOnly = true;
|
|
@@ -22297,13 +23078,12 @@ ${installUsage()}`);
|
|
|
22297
23078
|
if (coreOnly && sawLibraryAlias) {
|
|
22298
23079
|
throw new Error("`--core-only` cannot be combined with `--library` or `--all`.");
|
|
22299
23080
|
}
|
|
22300
|
-
return {
|
|
23081
|
+
return { includeLibrary: !coreOnly, targets: uniqueTargets(targets) };
|
|
22301
23082
|
}
|
|
22302
|
-
function installCoreSkill(
|
|
22303
|
-
const
|
|
22304
|
-
const
|
|
22305
|
-
|
|
22306
|
-
if (!existsSync11(srcSkill)) {
|
|
23083
|
+
function installCoreSkill(targetRoot) {
|
|
23084
|
+
const srcSkill = resolvePackagePath(import.meta.url, "dist-skill", "SKILL.md");
|
|
23085
|
+
const srcDocs = resolvePackagePath(import.meta.url, "dist-skill", "docs");
|
|
23086
|
+
if (!existsSync10(srcSkill)) {
|
|
22307
23087
|
throw new Error(
|
|
22308
23088
|
`Built skill file not found at ${srcSkill}.
|
|
22309
23089
|
If you are running from a source checkout, run: npm run build:skill:forgecad`
|
|
@@ -22314,18 +23094,17 @@ If you are running from a source checkout, run: npm run build:skill:forgecad`
|
|
|
22314
23094
|
mkdirSync4(destDir, { recursive: true });
|
|
22315
23095
|
const skillContent = readFileSync15(srcSkill, "utf-8").replaceAll("{{SKILL_DIR}}", destDir);
|
|
22316
23096
|
writeFileSync10(dest, skillContent);
|
|
22317
|
-
if (
|
|
23097
|
+
if (existsSync10(srcDocs)) {
|
|
22318
23098
|
const destDocs = join8(destDir, "docs");
|
|
22319
|
-
if (
|
|
23099
|
+
if (existsSync10(destDocs)) rmSync3(destDocs, { recursive: true });
|
|
22320
23100
|
cpSync(srcDocs, destDocs, { recursive: true });
|
|
22321
23101
|
}
|
|
22322
|
-
|
|
22323
|
-
console.log(`ForgeCAD skill installed to ${dest} [${mode}]`);
|
|
23102
|
+
console.log(`ForgeCAD skill installed to ${dest} [standard model authoring]`);
|
|
22324
23103
|
return dest;
|
|
22325
23104
|
}
|
|
22326
23105
|
function installCompanionLibrary(targetRoot) {
|
|
22327
23106
|
const srcLibrary = resolvePackagePath(import.meta.url, "dist-skill", "library");
|
|
22328
|
-
if (!
|
|
23107
|
+
if (!existsSync10(srcLibrary)) {
|
|
22329
23108
|
throw new Error(
|
|
22330
23109
|
`Built companion skill library not found at ${srcLibrary}.
|
|
22331
23110
|
If you are running from a source checkout, run: npm run build:skill:forgecad`
|
|
@@ -22353,7 +23132,7 @@ async function runSkillInstallCli(argv = []) {
|
|
|
22353
23132
|
for (const target of options.targets) {
|
|
22354
23133
|
const config = INSTALL_TARGETS[target];
|
|
22355
23134
|
console.log(`Installing ForgeCAD skills for ${config.label} at ${config.root}`);
|
|
22356
|
-
installCoreSkill(
|
|
23135
|
+
installCoreSkill(config.root);
|
|
22357
23136
|
if (options.includeLibrary) installCompanionLibrary(config.root);
|
|
22358
23137
|
}
|
|
22359
23138
|
console.log(`Reload your agent (Claude Code, Codex, OpenCode, \u2026) to activate.`);
|
|
@@ -22365,7 +23144,7 @@ async function runSkillOneFileCli(argv = []) {
|
|
|
22365
23144
|
Example: forgecad skill one-file ~/Desktop/forgecad-context.md`);
|
|
22366
23145
|
}
|
|
22367
23146
|
const src = resolvePackagePath(import.meta.url, "dist-skill", "CONTEXT.md");
|
|
22368
|
-
if (!
|
|
23147
|
+
if (!existsSync10(src)) {
|
|
22369
23148
|
throw new Error(
|
|
22370
23149
|
`Built context file not found at ${src}.
|
|
22371
23150
|
If you are running from a source checkout, run: npm run build:skill:forgecad`
|
|
@@ -22429,7 +23208,7 @@ function discoverFlattenedSkillSources() {
|
|
|
22429
23208
|
const coreSkill = resolvePackagePath(import.meta.url, "dist-skill", "SKILL.md");
|
|
22430
23209
|
const coreDocs = resolvePackagePath(import.meta.url, "dist-skill", "docs");
|
|
22431
23210
|
const libraryRoot = resolvePackagePath(import.meta.url, "dist-skill", "library");
|
|
22432
|
-
if (!
|
|
23211
|
+
if (!existsSync10(coreSkill) || !existsSync10(coreDocs)) {
|
|
22433
23212
|
throw new Error(
|
|
22434
23213
|
`Built skill artifacts not found in ${resolvePackagePath(import.meta.url, "dist-skill")}.
|
|
22435
23214
|
If you are running from a source checkout, run: npm run build:skill:forgecad`
|
|
@@ -22444,12 +23223,12 @@ If you are running from a source checkout, run: npm run build:skill:forgecad`
|
|
|
22444
23223
|
]
|
|
22445
23224
|
}
|
|
22446
23225
|
];
|
|
22447
|
-
if (
|
|
23226
|
+
if (existsSync10(libraryRoot)) {
|
|
22448
23227
|
for (const entry of readdirSync6(libraryRoot, { withFileTypes: true })) {
|
|
22449
23228
|
if (!entry.isDirectory()) continue;
|
|
22450
23229
|
const root = join8(libraryRoot, entry.name);
|
|
22451
23230
|
const skillFile = join8(root, "SKILL.md");
|
|
22452
|
-
if (!
|
|
23231
|
+
if (!existsSync10(skillFile)) continue;
|
|
22453
23232
|
sources.push({ name: entry.name, files: findSkillFiles(root) });
|
|
22454
23233
|
}
|
|
22455
23234
|
}
|
|
@@ -22485,19 +23264,113 @@ import { existsSync as existsSync14 } from "fs";
|
|
|
22485
23264
|
import { resolve as resolve30 } from "path";
|
|
22486
23265
|
|
|
22487
23266
|
// cli/forge-studio-server.ts
|
|
22488
|
-
import { fork as fork2 } from "child_process";
|
|
22489
23267
|
import chokidar from "chokidar";
|
|
22490
23268
|
import fs from "fs";
|
|
22491
23269
|
import http from "http";
|
|
22492
23270
|
import path2 from "path";
|
|
22493
23271
|
|
|
23272
|
+
// server/importAnalysis.ts
|
|
23273
|
+
var FORGE_IMPORT_RE2 = /\b(?:importMesh|importStep|importSvgSketch|Import\.dxfSketch)\s*\(\s*(?:"([^"]+)"|'([^']+)')/g;
|
|
23274
|
+
var REQUIRE_RE2 = /\brequire\s*\(\s*(?:"([^"]+)"|'([^']+)')/g;
|
|
23275
|
+
var ES_IMPORT_RE2 = /\bfrom\s+(?:"([^"]+)"|'([^']+)')/g;
|
|
23276
|
+
var VIRTUAL_MODULES2 = /* @__PURE__ */ new Set(["forgecad", "@forge/runtime", "@forgecad/runtime"]);
|
|
23277
|
+
function extractImports2(code) {
|
|
23278
|
+
const refs = [];
|
|
23279
|
+
const seen = /* @__PURE__ */ new Set();
|
|
23280
|
+
const add2 = (raw, kind) => {
|
|
23281
|
+
if (!seen.has(raw) && !VIRTUAL_MODULES2.has(raw)) {
|
|
23282
|
+
seen.add(raw);
|
|
23283
|
+
refs.push({ raw, kind });
|
|
23284
|
+
}
|
|
23285
|
+
};
|
|
23286
|
+
const kindMap = {
|
|
23287
|
+
importMesh: "forgeMesh",
|
|
23288
|
+
importStep: "forgeMesh",
|
|
23289
|
+
importSvgSketch: "forgeSvg",
|
|
23290
|
+
"Import.dxfSketch": "forgeDxf"
|
|
23291
|
+
};
|
|
23292
|
+
let m;
|
|
23293
|
+
const forgeRe = new RegExp(FORGE_IMPORT_RE2.source, FORGE_IMPORT_RE2.flags);
|
|
23294
|
+
while ((m = forgeRe.exec(code)) !== null) {
|
|
23295
|
+
const path3 = m[1] ?? m[2];
|
|
23296
|
+
const fn = m[0].match(/\b(importMesh|importStep|importSvgSketch|Import\.dxfSketch)/)?.[1];
|
|
23297
|
+
add2(path3, kindMap[fn] ?? "forgeMesh");
|
|
23298
|
+
}
|
|
23299
|
+
const reqRe = new RegExp(REQUIRE_RE2.source, REQUIRE_RE2.flags);
|
|
23300
|
+
while ((m = reqRe.exec(code)) !== null) {
|
|
23301
|
+
const path3 = m[1] ?? m[2];
|
|
23302
|
+
add2(path3, "require");
|
|
23303
|
+
}
|
|
23304
|
+
const esRe = new RegExp(ES_IMPORT_RE2.source, ES_IMPORT_RE2.flags);
|
|
23305
|
+
while ((m = esRe.exec(code)) !== null) {
|
|
23306
|
+
const path3 = m[1] ?? m[2];
|
|
23307
|
+
add2(path3, "esImport");
|
|
23308
|
+
}
|
|
23309
|
+
return refs;
|
|
23310
|
+
}
|
|
23311
|
+
function resolveRelative2(fromFile, importPath) {
|
|
23312
|
+
if (!importPath.startsWith("./") && !importPath.startsWith("../")) {
|
|
23313
|
+
return importPath;
|
|
23314
|
+
}
|
|
23315
|
+
const fromDir = fromFile.includes("/") ? fromFile.slice(0, fromFile.lastIndexOf("/")) : "";
|
|
23316
|
+
const combined = fromDir ? `${fromDir}/${importPath}` : importPath;
|
|
23317
|
+
const parts = combined.split("/");
|
|
23318
|
+
const resolved = [];
|
|
23319
|
+
for (const part of parts) {
|
|
23320
|
+
if (part === "." || part === "") continue;
|
|
23321
|
+
if (part === "..") {
|
|
23322
|
+
if (resolved.length > 0 && resolved[resolved.length - 1] !== "..") {
|
|
23323
|
+
resolved.pop();
|
|
23324
|
+
} else {
|
|
23325
|
+
resolved.push("..");
|
|
23326
|
+
}
|
|
23327
|
+
} else {
|
|
23328
|
+
resolved.push(part);
|
|
23329
|
+
}
|
|
23330
|
+
}
|
|
23331
|
+
return resolved.join("/");
|
|
23332
|
+
}
|
|
23333
|
+
function collectDependencyFiles(entryFile, allFiles) {
|
|
23334
|
+
const result = {};
|
|
23335
|
+
const visited = /* @__PURE__ */ new Set();
|
|
23336
|
+
function visit(fileName) {
|
|
23337
|
+
if (visited.has(fileName)) return;
|
|
23338
|
+
visited.add(fileName);
|
|
23339
|
+
const code = allFiles[fileName];
|
|
23340
|
+
if (code == null) return;
|
|
23341
|
+
result[fileName] = code;
|
|
23342
|
+
const imports = extractImports2(code);
|
|
23343
|
+
for (const imp of imports) {
|
|
23344
|
+
const resolved = resolveRelative2(fileName, imp.raw);
|
|
23345
|
+
if (imp.kind === "forgeMesh") continue;
|
|
23346
|
+
if (imp.kind === "forgeSvg") {
|
|
23347
|
+
if (allFiles[resolved] != null) {
|
|
23348
|
+
result[resolved] = allFiles[resolved];
|
|
23349
|
+
}
|
|
23350
|
+
continue;
|
|
23351
|
+
}
|
|
23352
|
+
if (imp.kind === "forgeDxf") {
|
|
23353
|
+
if (allFiles[resolved] != null) {
|
|
23354
|
+
result[resolved] = allFiles[resolved];
|
|
23355
|
+
}
|
|
23356
|
+
continue;
|
|
23357
|
+
}
|
|
23358
|
+
if (allFiles[resolved] != null) {
|
|
23359
|
+
visit(resolved);
|
|
23360
|
+
}
|
|
23361
|
+
}
|
|
23362
|
+
}
|
|
23363
|
+
visit(entryFile);
|
|
23364
|
+
return result;
|
|
23365
|
+
}
|
|
23366
|
+
|
|
22494
23367
|
// cli/project.ts
|
|
22495
23368
|
import { createHash as createHash2 } from "crypto";
|
|
22496
|
-
import { existsSync as
|
|
23369
|
+
import { existsSync as existsSync12, readFileSync as readFileSync17, readdirSync as readdirSync7, writeFileSync as writeFileSync12 } from "fs";
|
|
22497
23370
|
import { join as join10 } from "path";
|
|
22498
23371
|
|
|
22499
23372
|
// cli/auth.ts
|
|
22500
|
-
import { chmodSync, existsSync as
|
|
23373
|
+
import { chmodSync, existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync16, unlinkSync, writeFileSync as writeFileSync11 } from "fs";
|
|
22501
23374
|
import { homedir as homedir7 } from "os";
|
|
22502
23375
|
import { join as join9 } from "path";
|
|
22503
23376
|
import { createInterface } from "readline";
|
|
@@ -22584,7 +23457,7 @@ function forgecadDir() {
|
|
|
22584
23457
|
}
|
|
22585
23458
|
function ensureDir() {
|
|
22586
23459
|
const dir = forgecadDir();
|
|
22587
|
-
if (!
|
|
23460
|
+
if (!existsSync11(dir)) mkdirSync5(dir, { recursive: true, mode: 448 });
|
|
22588
23461
|
try {
|
|
22589
23462
|
chmodSync(dir, 448);
|
|
22590
23463
|
} catch {
|
|
@@ -22595,7 +23468,7 @@ function authFilePath() {
|
|
|
22595
23468
|
}
|
|
22596
23469
|
function readStoredAuth() {
|
|
22597
23470
|
const p = authFilePath();
|
|
22598
|
-
if (!
|
|
23471
|
+
if (!existsSync11(p)) return null;
|
|
22599
23472
|
try {
|
|
22600
23473
|
const auth = JSON.parse(readFileSync16(p, "utf-8"));
|
|
22601
23474
|
return { ...auth, server: normalizeServerUrl(auth.server, "Stored ForgeCAD server URL") };
|
|
@@ -22614,7 +23487,7 @@ function writeStoredAuth(auth) {
|
|
|
22614
23487
|
}
|
|
22615
23488
|
function clearStoredAuth() {
|
|
22616
23489
|
const p = authFilePath();
|
|
22617
|
-
if (
|
|
23490
|
+
if (existsSync11(p)) unlinkSync(p);
|
|
22618
23491
|
}
|
|
22619
23492
|
function getServerUrl() {
|
|
22620
23493
|
if (process.env.FORGECAD_TOKEN) {
|
|
@@ -22918,7 +23791,7 @@ function manifestPath(projectDir) {
|
|
|
22918
23791
|
}
|
|
22919
23792
|
function readManifest(projectDir) {
|
|
22920
23793
|
const p = manifestPath(projectDir);
|
|
22921
|
-
if (!
|
|
23794
|
+
if (!existsSync12(p)) return null;
|
|
22922
23795
|
try {
|
|
22923
23796
|
return JSON.parse(readFileSync17(p, "utf-8"));
|
|
22924
23797
|
} catch {
|
|
@@ -23164,99 +24037,164 @@ async function deleteShare(shareId) {
|
|
|
23164
24037
|
}
|
|
23165
24038
|
}
|
|
23166
24039
|
|
|
23167
|
-
//
|
|
23168
|
-
|
|
23169
|
-
|
|
23170
|
-
|
|
23171
|
-
|
|
23172
|
-
|
|
23173
|
-
|
|
23174
|
-
|
|
23175
|
-
|
|
23176
|
-
|
|
23177
|
-
|
|
23178
|
-
|
|
23179
|
-
|
|
23180
|
-
|
|
23181
|
-
|
|
23182
|
-
|
|
23183
|
-
|
|
23184
|
-
|
|
23185
|
-
|
|
23186
|
-
|
|
23187
|
-
|
|
23188
|
-
|
|
23189
|
-
|
|
23190
|
-
|
|
23191
|
-
|
|
23192
|
-
|
|
23193
|
-
|
|
23194
|
-
|
|
23195
|
-
|
|
23196
|
-
|
|
23197
|
-
|
|
23198
|
-
|
|
23199
|
-
|
|
23200
|
-
|
|
23201
|
-
|
|
23202
|
-
|
|
24040
|
+
// cli/local-native-compute.ts
|
|
24041
|
+
import { spawn as spawn3, execFileSync as execFileSync2 } from "child_process";
|
|
24042
|
+
import { existsSync as existsSync13, readdirSync as readdirSync8, statSync as statSync8 } from "fs";
|
|
24043
|
+
import { createServer as createServer2 } from "net";
|
|
24044
|
+
import { join as join11 } from "path";
|
|
24045
|
+
var startedComputeServer = null;
|
|
24046
|
+
var REQUIRED_ENGINE_VERSION = "native-occt-node-api-0.2.0";
|
|
24047
|
+
var REQUIRED_PLAN_KINDS = [
|
|
24048
|
+
"box",
|
|
24049
|
+
"cylinder",
|
|
24050
|
+
"sphere",
|
|
24051
|
+
"torus",
|
|
24052
|
+
"extrude",
|
|
24053
|
+
"boolean",
|
|
24054
|
+
"transform",
|
|
24055
|
+
"queryOwner",
|
|
24056
|
+
"revolve",
|
|
24057
|
+
"loft",
|
|
24058
|
+
"sweep",
|
|
24059
|
+
"nurbsSurface",
|
|
24060
|
+
"filletEdges",
|
|
24061
|
+
"chamferEdges"
|
|
24062
|
+
];
|
|
24063
|
+
function npmCommand() {
|
|
24064
|
+
return process.platform === "win32" ? "npm.cmd" : "npm";
|
|
24065
|
+
}
|
|
24066
|
+
function isLocalComputeUrl(url) {
|
|
24067
|
+
return url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1";
|
|
24068
|
+
}
|
|
24069
|
+
async function isHealthy(computeUrl) {
|
|
24070
|
+
try {
|
|
24071
|
+
const response = await fetch(`${computeUrl}/health`, { signal: AbortSignal.timeout(1e3) });
|
|
24072
|
+
if (!response.ok) return false;
|
|
24073
|
+
const body = await response.json().catch(() => null);
|
|
24074
|
+
if (body?.status !== "ok" || body?.nativeOcctAvailable !== true || body?.engineVersion !== REQUIRED_ENGINE_VERSION) return false;
|
|
24075
|
+
const capabilitiesResponse = await fetch(`${computeUrl}/capabilities`, { signal: AbortSignal.timeout(1e3) });
|
|
24076
|
+
if (!capabilitiesResponse.ok) return false;
|
|
24077
|
+
const capabilities = await capabilitiesResponse.json().catch(() => null);
|
|
24078
|
+
const supportedKinds = capabilities?.nativeOcct?.supportedPlanKinds;
|
|
24079
|
+
return Array.isArray(supportedKinds) && REQUIRED_PLAN_KINDS.every((kind) => supportedKinds.includes(kind));
|
|
24080
|
+
} catch {
|
|
24081
|
+
return false;
|
|
23203
24082
|
}
|
|
23204
|
-
return refs;
|
|
23205
24083
|
}
|
|
23206
|
-
function
|
|
23207
|
-
|
|
23208
|
-
return
|
|
24084
|
+
async function waitForHealthy(computeUrl, child) {
|
|
24085
|
+
for (let attempt = 0; attempt < 120; attempt += 1) {
|
|
24086
|
+
if (await isHealthy(computeUrl)) return;
|
|
24087
|
+
if (child.exitCode != null) break;
|
|
24088
|
+
await new Promise((resolve40) => setTimeout(resolve40, 50));
|
|
23209
24089
|
}
|
|
23210
|
-
|
|
23211
|
-
|
|
23212
|
-
|
|
23213
|
-
|
|
23214
|
-
|
|
23215
|
-
|
|
23216
|
-
|
|
23217
|
-
if (resolved.length > 0 && resolved[resolved.length - 1] !== "..") {
|
|
23218
|
-
resolved.pop();
|
|
23219
|
-
} else {
|
|
23220
|
-
resolved.push("..");
|
|
23221
|
-
}
|
|
23222
|
-
} else {
|
|
23223
|
-
resolved.push(part);
|
|
24090
|
+
throw new Error(`Native OCCT compute backend did not become healthy at ${computeUrl}.`);
|
|
24091
|
+
}
|
|
24092
|
+
function closeChild(child) {
|
|
24093
|
+
return new Promise((resolve40) => {
|
|
24094
|
+
if (child.exitCode != null || child.killed) {
|
|
24095
|
+
resolve40();
|
|
24096
|
+
return;
|
|
23224
24097
|
}
|
|
23225
|
-
|
|
23226
|
-
|
|
24098
|
+
child.once("exit", () => resolve40());
|
|
24099
|
+
child.kill();
|
|
24100
|
+
});
|
|
23227
24101
|
}
|
|
23228
|
-
function
|
|
23229
|
-
|
|
23230
|
-
|
|
23231
|
-
|
|
23232
|
-
|
|
23233
|
-
|
|
23234
|
-
|
|
23235
|
-
|
|
23236
|
-
|
|
23237
|
-
|
|
23238
|
-
|
|
23239
|
-
|
|
23240
|
-
|
|
23241
|
-
|
|
23242
|
-
|
|
23243
|
-
|
|
23244
|
-
|
|
23245
|
-
|
|
23246
|
-
|
|
23247
|
-
|
|
23248
|
-
|
|
23249
|
-
|
|
23250
|
-
|
|
23251
|
-
|
|
23252
|
-
|
|
23253
|
-
|
|
23254
|
-
|
|
23255
|
-
|
|
24102
|
+
function hasLocalNativeComputeSource(packageRoot) {
|
|
24103
|
+
return existsSync13(join11(packageRoot, "apps/backend/src/server.ts")) && existsSync13(join11(packageRoot, "scripts/build-native-occt.mjs"));
|
|
24104
|
+
}
|
|
24105
|
+
function newestMtimeMs(path3) {
|
|
24106
|
+
if (!existsSync13(path3)) return 0;
|
|
24107
|
+
const stat = statSync8(path3);
|
|
24108
|
+
if (!stat.isDirectory()) return stat.mtimeMs;
|
|
24109
|
+
let newest = stat.mtimeMs;
|
|
24110
|
+
for (const entry of readdirSync8(path3, { withFileTypes: true })) {
|
|
24111
|
+
newest = Math.max(newest, newestMtimeMs(join11(path3, entry.name)));
|
|
24112
|
+
}
|
|
24113
|
+
return newest;
|
|
24114
|
+
}
|
|
24115
|
+
function isNativeAddonStale(packageRoot, addonPath) {
|
|
24116
|
+
if (!existsSync13(addonPath)) return true;
|
|
24117
|
+
const addonMtime = statSync8(addonPath).mtimeMs;
|
|
24118
|
+
const sourceMtime = Math.max(
|
|
24119
|
+
newestMtimeMs(join11(packageRoot, "native/occt-ir/src")),
|
|
24120
|
+
newestMtimeMs(join11(packageRoot, "native/occt-ir/CMakeLists.txt")),
|
|
24121
|
+
newestMtimeMs(join11(packageRoot, "scripts/build-native-occt.mjs"))
|
|
24122
|
+
);
|
|
24123
|
+
return sourceMtime > addonMtime;
|
|
24124
|
+
}
|
|
24125
|
+
function computeUrlWithPort(url, port) {
|
|
24126
|
+
const next = new URL(url.toString());
|
|
24127
|
+
next.port = String(port);
|
|
24128
|
+
return next.toString().replace(/\/$/, "");
|
|
24129
|
+
}
|
|
24130
|
+
async function pickComputeUrl(preferred) {
|
|
24131
|
+
const preferredPort = Number.parseInt(preferred.port || (preferred.protocol === "https:" ? "443" : "80"), 10);
|
|
24132
|
+
for (let port = preferredPort; port < preferredPort + 10; port += 1) {
|
|
24133
|
+
const candidate = computeUrlWithPort(preferred, port);
|
|
24134
|
+
if (await isHealthy(candidate)) return candidate;
|
|
24135
|
+
try {
|
|
24136
|
+
const server = await new Promise((resolve40, reject) => {
|
|
24137
|
+
const probe = createServer2();
|
|
24138
|
+
probe.once("error", reject);
|
|
24139
|
+
probe.listen(port, preferred.hostname, () => resolve40(probe));
|
|
24140
|
+
});
|
|
24141
|
+
await new Promise((resolve40) => server.close(() => resolve40()));
|
|
24142
|
+
return candidate;
|
|
24143
|
+
} catch {
|
|
23256
24144
|
}
|
|
23257
24145
|
}
|
|
23258
|
-
|
|
23259
|
-
|
|
24146
|
+
throw new Error(`No local port available for native OCCT compute near ${preferred.href}.`);
|
|
24147
|
+
}
|
|
24148
|
+
function startLocalNativeComputeServer(options) {
|
|
24149
|
+
if (startedComputeServer) return startedComputeServer;
|
|
24150
|
+
const startPromise = (async () => {
|
|
24151
|
+
const requestedComputeUrl = options.computeUrl ?? "http://127.0.0.1:4510";
|
|
24152
|
+
if (options.enabled === false) {
|
|
24153
|
+
return { url: requestedComputeUrl, spawned: false, close: async () => {
|
|
24154
|
+
} };
|
|
24155
|
+
}
|
|
24156
|
+
const parsed = new URL(requestedComputeUrl);
|
|
24157
|
+
if (!isLocalComputeUrl(parsed)) {
|
|
24158
|
+
return { url: requestedComputeUrl, spawned: false, close: async () => {
|
|
24159
|
+
} };
|
|
24160
|
+
}
|
|
24161
|
+
if (!hasLocalNativeComputeSource(options.packageRoot)) {
|
|
24162
|
+
return { url: requestedComputeUrl, spawned: false, close: async () => {
|
|
24163
|
+
} };
|
|
24164
|
+
}
|
|
24165
|
+
if (await isHealthy(requestedComputeUrl)) {
|
|
24166
|
+
return { url: requestedComputeUrl, spawned: false, close: async () => {
|
|
24167
|
+
} };
|
|
24168
|
+
}
|
|
24169
|
+
const addonPath = join11(options.packageRoot, "native/occt-ir/build/forgecad_occt.node");
|
|
24170
|
+
if (isNativeAddonStale(options.packageRoot, addonPath)) {
|
|
24171
|
+
console.log("[compute] Native OCCT addon missing or stale; building...");
|
|
24172
|
+
execFileSync2(npmCommand(), ["run", "build:native-occt"], {
|
|
24173
|
+
cwd: options.packageRoot,
|
|
24174
|
+
stdio: "inherit"
|
|
24175
|
+
});
|
|
24176
|
+
}
|
|
24177
|
+
const computeUrl = await pickComputeUrl(parsed);
|
|
24178
|
+
const selected = new URL(computeUrl);
|
|
24179
|
+
const port = selected.port || (selected.protocol === "https:" ? "443" : "80");
|
|
24180
|
+
console.log(`[compute] Starting native OCCT backend at ${computeUrl}`);
|
|
24181
|
+
const child = spawn3("npx", ["tsx", "apps/backend/src/server.ts"], {
|
|
24182
|
+
cwd: options.packageRoot,
|
|
24183
|
+
env: { ...process.env, FORGE_BACKEND_PORT: port },
|
|
24184
|
+
stdio: ["ignore", "inherit", "inherit"]
|
|
24185
|
+
});
|
|
24186
|
+
await waitForHealthy(computeUrl, child);
|
|
24187
|
+
return {
|
|
24188
|
+
url: computeUrl,
|
|
24189
|
+
spawned: true,
|
|
24190
|
+
close: () => closeChild(child)
|
|
24191
|
+
};
|
|
24192
|
+
})();
|
|
24193
|
+
startedComputeServer = startPromise.catch((error) => {
|
|
24194
|
+
startedComputeServer = null;
|
|
24195
|
+
throw error;
|
|
24196
|
+
});
|
|
24197
|
+
return startedComputeServer;
|
|
23260
24198
|
}
|
|
23261
24199
|
|
|
23262
24200
|
// cli/forge-studio-server.ts
|
|
@@ -23396,6 +24334,28 @@ function resolveProjectFile(projectDir, filename, opts = {}) {
|
|
|
23396
24334
|
}
|
|
23397
24335
|
return { filePath, filename: rel.replace(/\\/g, "/") };
|
|
23398
24336
|
}
|
|
24337
|
+
function resolveProjectDirectory(projectDir, dirPath) {
|
|
24338
|
+
if (!projectDir) throw new Error("No project directory");
|
|
24339
|
+
if (!dirPath) throw new Error("Invalid directory path");
|
|
24340
|
+
const abs = path2.resolve(projectDir);
|
|
24341
|
+
const resolved = path2.resolve(abs, dirPath);
|
|
24342
|
+
const rel = path2.relative(abs, resolved);
|
|
24343
|
+
if (rel.startsWith("..") || path2.isAbsolute(rel)) {
|
|
24344
|
+
throw new Error(`Path "${dirPath}" is outside the project root`);
|
|
24345
|
+
}
|
|
24346
|
+
return { dirPath: resolved, dirname: rel.replace(/\\/g, "/") };
|
|
24347
|
+
}
|
|
24348
|
+
function directoryTreeContainsFile(dirPath) {
|
|
24349
|
+
for (const item of fs.readdirSync(dirPath, { withFileTypes: true })) {
|
|
24350
|
+
const childPath2 = path2.join(dirPath, item.name);
|
|
24351
|
+
if (item.isDirectory()) {
|
|
24352
|
+
if (directoryTreeContainsFile(childPath2)) return true;
|
|
24353
|
+
} else {
|
|
24354
|
+
return true;
|
|
24355
|
+
}
|
|
24356
|
+
}
|
|
24357
|
+
return false;
|
|
24358
|
+
}
|
|
23399
24359
|
function serveStatic(distDir, req, res) {
|
|
23400
24360
|
const absDistDir = path2.resolve(distDir);
|
|
23401
24361
|
const urlPath = decodeURIComponent((req.url ?? "/").split("?")[0]);
|
|
@@ -23419,67 +24379,6 @@ function serveStatic(distDir, req, res) {
|
|
|
23419
24379
|
res.end(content);
|
|
23420
24380
|
return true;
|
|
23421
24381
|
}
|
|
23422
|
-
var BACKEND_PORT = 4510;
|
|
23423
|
-
var BACKEND_URL = `http://127.0.0.1:${BACKEND_PORT}`;
|
|
23424
|
-
function spawnBackendServer2(packageRoot) {
|
|
23425
|
-
const backendEntry = path2.join(packageRoot, "dist-backend", "server.js");
|
|
23426
|
-
if (!fs.existsSync(backendEntry)) {
|
|
23427
|
-
console.log(" Backend compute server not found (dist-backend/server.js) \u2014 server rendering disabled");
|
|
23428
|
-
return null;
|
|
23429
|
-
}
|
|
23430
|
-
const child = fork2(backendEntry, {
|
|
23431
|
-
stdio: ["ignore", "pipe", "pipe", "ipc"],
|
|
23432
|
-
env: { ...process.env, FORGE_BACKEND_PORT: String(BACKEND_PORT) }
|
|
23433
|
-
});
|
|
23434
|
-
child.stdout?.on("data", (data) => {
|
|
23435
|
-
for (const line of data.toString().trim().split("\n")) {
|
|
23436
|
-
console.log(` [backend] ${line}`);
|
|
23437
|
-
}
|
|
23438
|
-
});
|
|
23439
|
-
child.stderr?.on("data", (data) => {
|
|
23440
|
-
for (const line of data.toString().trim().split("\n")) {
|
|
23441
|
-
console.error(` [backend] ${line}`);
|
|
23442
|
-
}
|
|
23443
|
-
});
|
|
23444
|
-
child.on("exit", (code) => {
|
|
23445
|
-
if (code !== null && code !== 0) {
|
|
23446
|
-
console.error(` [backend] Compute server exited with code ${code}`);
|
|
23447
|
-
}
|
|
23448
|
-
});
|
|
23449
|
-
return child;
|
|
23450
|
-
}
|
|
23451
|
-
async function proxyToBackend(req, res, backendPath, method) {
|
|
23452
|
-
const abortController = new AbortController();
|
|
23453
|
-
res.on("close", () => abortController.abort());
|
|
23454
|
-
try {
|
|
23455
|
-
const body = method === "POST" ? await readRawBody(req) : void 0;
|
|
23456
|
-
const backendRes = await fetch(`${BACKEND_URL}${backendPath}`, {
|
|
23457
|
-
method,
|
|
23458
|
-
headers: body ? { "Content-Type": "application/json" } : void 0,
|
|
23459
|
-
body,
|
|
23460
|
-
signal: abortController.signal
|
|
23461
|
-
});
|
|
23462
|
-
if (abortController.signal.aborted) return false;
|
|
23463
|
-
res.writeHead(backendRes.status, {
|
|
23464
|
-
"Content-Type": backendRes.headers.get("Content-Type") ?? "application/octet-stream"
|
|
23465
|
-
});
|
|
23466
|
-
const buffer = Buffer.from(await backendRes.arrayBuffer());
|
|
23467
|
-
res.end(buffer);
|
|
23468
|
-
return true;
|
|
23469
|
-
} catch {
|
|
23470
|
-
if (abortController.signal.aborted) return false;
|
|
23471
|
-
sendJson(res, 503, { status: "unavailable" });
|
|
23472
|
-
return false;
|
|
23473
|
-
}
|
|
23474
|
-
}
|
|
23475
|
-
function readRawBody(req) {
|
|
23476
|
-
return new Promise((resolve40, reject) => {
|
|
23477
|
-
const chunks = [];
|
|
23478
|
-
req.on("data", (chunk) => chunks.push(chunk));
|
|
23479
|
-
req.on("end", () => resolve40(Buffer.concat(chunks).toString("utf-8")));
|
|
23480
|
-
req.on("error", reject);
|
|
23481
|
-
});
|
|
23482
|
-
}
|
|
23483
24382
|
function isPortAvailable(port, host) {
|
|
23484
24383
|
return new Promise((resolve40) => {
|
|
23485
24384
|
const s = http.createServer();
|
|
@@ -23528,9 +24427,19 @@ function matchSSERoute(url, suffix) {
|
|
|
23528
24427
|
}
|
|
23529
24428
|
async function startStudioServer(options) {
|
|
23530
24429
|
const { projectDirs, distDir, packageRoot, open, strictPort } = options;
|
|
23531
|
-
const backendProcess = spawnBackendServer2(packageRoot);
|
|
23532
24430
|
const host = options.host || "127.0.0.1";
|
|
23533
24431
|
const port = await pickPort(options.port || 5173, host, strictPort);
|
|
24432
|
+
const computeUrl = process.env.FORGE_COMPUTE_URL ?? "http://127.0.0.1:4510";
|
|
24433
|
+
const computeServer = await startLocalNativeComputeServer({
|
|
24434
|
+
packageRoot,
|
|
24435
|
+
computeUrl,
|
|
24436
|
+
enabled: process.env.FORGE_AUTO_START_COMPUTE !== "false"
|
|
24437
|
+
}).catch((error) => {
|
|
24438
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
24439
|
+
console.warn(`[compute] Native OCCT backend unavailable: ${message}`);
|
|
24440
|
+
return null;
|
|
24441
|
+
});
|
|
24442
|
+
const activeComputeUrl = computeServer?.url ?? computeUrl;
|
|
23534
24443
|
const projects = buildProjectRegistry(projectDirs);
|
|
23535
24444
|
const projectById = new Map(projects.map((p) => [p.id, p]));
|
|
23536
24445
|
function lookupProject(projectId) {
|
|
@@ -23553,6 +24462,18 @@ data: ${JSON.stringify(data)}
|
|
|
23553
24462
|
}
|
|
23554
24463
|
}
|
|
23555
24464
|
const watchers = [];
|
|
24465
|
+
const snapshotTimers = /* @__PURE__ */ new Map();
|
|
24466
|
+
function broadcastProjectSnapshot(project) {
|
|
24467
|
+
const existing = snapshotTimers.get(project.id);
|
|
24468
|
+
if (existing) clearTimeout(existing);
|
|
24469
|
+
snapshotTimers.set(
|
|
24470
|
+
project.id,
|
|
24471
|
+
setTimeout(() => {
|
|
24472
|
+
snapshotTimers.delete(project.id);
|
|
24473
|
+
broadcastToProject(project.id, "snapshot", {});
|
|
24474
|
+
}, 50)
|
|
24475
|
+
);
|
|
24476
|
+
}
|
|
23556
24477
|
for (const proj of projects) {
|
|
23557
24478
|
const abs = path2.resolve(proj.dir);
|
|
23558
24479
|
const watcher = chokidar.watch(abs, { ignoreInitial: true, ignored: /(^|[/\\])\../ });
|
|
@@ -23581,7 +24502,10 @@ data: ${JSON.stringify(data)}
|
|
|
23581
24502
|
watcher.on("unlink", (f2) => {
|
|
23582
24503
|
if (!isProjectFile(f2) && !isBinaryProjectFile(f2)) return;
|
|
23583
24504
|
broadcastToProject(proj.id, "delete", { filename: path2.relative(abs, f2).replace(/\\/g, "/") });
|
|
24505
|
+
broadcastProjectSnapshot(proj);
|
|
23584
24506
|
});
|
|
24507
|
+
watcher.on("addDir", () => broadcastProjectSnapshot(proj));
|
|
24508
|
+
watcher.on("unlinkDir", () => broadcastProjectSnapshot(proj));
|
|
23585
24509
|
watchers.push(watcher);
|
|
23586
24510
|
}
|
|
23587
24511
|
const server = http.createServer(async (req, res) => {
|
|
@@ -23591,6 +24515,79 @@ data: ${JSON.stringify(data)}
|
|
|
23591
24515
|
sendJson(res, 200, { status: "ok", mode: "local", uptime: process.uptime() });
|
|
23592
24516
|
return;
|
|
23593
24517
|
}
|
|
24518
|
+
if (method === "GET" && url2 === "/api/compute/health") {
|
|
24519
|
+
try {
|
|
24520
|
+
const response = await fetch(`${activeComputeUrl}/health`, { signal: AbortSignal.timeout(2e3) });
|
|
24521
|
+
if (!response.ok) {
|
|
24522
|
+
sendJson(res, 200, { status: "unavailable" });
|
|
24523
|
+
return;
|
|
24524
|
+
}
|
|
24525
|
+
const body = await response.json();
|
|
24526
|
+
sendJson(res, 200, { status: body?.status === "ok" ? "ok" : "unavailable" });
|
|
24527
|
+
} catch {
|
|
24528
|
+
sendJson(res, 200, { status: "unavailable" });
|
|
24529
|
+
}
|
|
24530
|
+
return;
|
|
24531
|
+
}
|
|
24532
|
+
if (method === "GET" && url2 === "/api/compute/capabilities") {
|
|
24533
|
+
try {
|
|
24534
|
+
const response = await fetch(`${activeComputeUrl}/capabilities`, { signal: AbortSignal.timeout(5e3) });
|
|
24535
|
+
const body = await response.json().catch(() => ({ error: `Compute server returned ${response.status}` }));
|
|
24536
|
+
sendJson(res, response.status, body);
|
|
24537
|
+
} catch {
|
|
24538
|
+
sendJson(res, 502, { error: "Backend compute server unreachable" });
|
|
24539
|
+
}
|
|
24540
|
+
return;
|
|
24541
|
+
}
|
|
24542
|
+
if (method === "GET" && url2.startsWith("/api/compute/jobs")) {
|
|
24543
|
+
try {
|
|
24544
|
+
const response = await fetch(`${activeComputeUrl}${url2.replace("/api", "")}`, { signal: AbortSignal.timeout(5e3) });
|
|
24545
|
+
const body = await response.json().catch(() => ({ error: `Compute server returned ${response.status}` }));
|
|
24546
|
+
sendJson(res, response.status, body);
|
|
24547
|
+
} catch {
|
|
24548
|
+
sendJson(res, 502, { error: "Backend compute server unreachable" });
|
|
24549
|
+
}
|
|
24550
|
+
return;
|
|
24551
|
+
}
|
|
24552
|
+
if (method === "POST" && /^\/api\/compute\/jobs\/[^/]+\/cancel$/.test(url2)) {
|
|
24553
|
+
try {
|
|
24554
|
+
const response = await fetch(`${activeComputeUrl}${url2.replace("/api", "")}`, {
|
|
24555
|
+
method: "POST",
|
|
24556
|
+
signal: AbortSignal.timeout(5e3)
|
|
24557
|
+
});
|
|
24558
|
+
const body = await response.json().catch(() => ({ error: `Compute server returned ${response.status}` }));
|
|
24559
|
+
sendJson(res, response.status, body);
|
|
24560
|
+
} catch {
|
|
24561
|
+
sendJson(res, 502, { error: "Backend compute server unreachable" });
|
|
24562
|
+
}
|
|
24563
|
+
return;
|
|
24564
|
+
}
|
|
24565
|
+
if (method === "POST" && url2 === "/api/compute/ir") {
|
|
24566
|
+
const abortController = new AbortController();
|
|
24567
|
+
res.on("close", () => abortController.abort());
|
|
24568
|
+
try {
|
|
24569
|
+
const body = await readJsonBody(req);
|
|
24570
|
+
const response = await fetch(`${activeComputeUrl}/compute/ir`, {
|
|
24571
|
+
method: "POST",
|
|
24572
|
+
headers: { "Content-Type": "application/json" },
|
|
24573
|
+
body: JSON.stringify(body),
|
|
24574
|
+
signal: abortController.signal
|
|
24575
|
+
});
|
|
24576
|
+
if (abortController.signal.aborted) return;
|
|
24577
|
+
if (!response.ok) {
|
|
24578
|
+
const errBody = await response.json().catch(() => ({ error: `Compute server returned ${response.status}` }));
|
|
24579
|
+
sendJson(res, response.status, errBody);
|
|
24580
|
+
return;
|
|
24581
|
+
}
|
|
24582
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
24583
|
+
res.writeHead(200, { "Content-Type": "application/octet-stream", "Content-Length": buffer.byteLength });
|
|
24584
|
+
res.end(buffer);
|
|
24585
|
+
} catch {
|
|
24586
|
+
if (abortController.signal.aborted) return;
|
|
24587
|
+
sendJson(res, 502, { error: "Backend compute server unreachable" });
|
|
24588
|
+
}
|
|
24589
|
+
return;
|
|
24590
|
+
}
|
|
23594
24591
|
if (method === "GET" && url2.split("?")[0] === "/api/projects") {
|
|
23595
24592
|
sendJson(res, 200, {
|
|
23596
24593
|
projects: projects.map((p) => ({
|
|
@@ -23823,6 +24820,49 @@ data: ${JSON.stringify(data)}
|
|
|
23823
24820
|
return;
|
|
23824
24821
|
}
|
|
23825
24822
|
}
|
|
24823
|
+
{
|
|
24824
|
+
const m = matchProjectRoute(url2, method, "POST", "/rmdir");
|
|
24825
|
+
if (m) {
|
|
24826
|
+
const proj = lookupProject(m.projectId);
|
|
24827
|
+
if (!proj) {
|
|
24828
|
+
sendJson(res, 404, { error: "Project not found" });
|
|
24829
|
+
return;
|
|
24830
|
+
}
|
|
24831
|
+
readJsonBody(req).then((body) => {
|
|
24832
|
+
const { dirPath } = body;
|
|
24833
|
+
if (!dirPath || typeof dirPath !== "string") {
|
|
24834
|
+
sendJson(res, 400, { error: "Invalid request" });
|
|
24835
|
+
return;
|
|
24836
|
+
}
|
|
24837
|
+
let resolved;
|
|
24838
|
+
try {
|
|
24839
|
+
resolved = resolveProjectDirectory(proj.dir, dirPath);
|
|
24840
|
+
} catch (e) {
|
|
24841
|
+
sendJson(res, 400, { error: e.message });
|
|
24842
|
+
return;
|
|
24843
|
+
}
|
|
24844
|
+
if (!resolved.dirname) {
|
|
24845
|
+
sendJson(res, 400, { error: "Cannot delete project root" });
|
|
24846
|
+
return;
|
|
24847
|
+
}
|
|
24848
|
+
if (!fs.existsSync(resolved.dirPath)) {
|
|
24849
|
+
sendJson(res, 200, { success: true });
|
|
24850
|
+
return;
|
|
24851
|
+
}
|
|
24852
|
+
if (!fs.statSync(resolved.dirPath).isDirectory()) {
|
|
24853
|
+
sendJson(res, 400, { error: "Path is not a directory" });
|
|
24854
|
+
return;
|
|
24855
|
+
}
|
|
24856
|
+
if (directoryTreeContainsFile(resolved.dirPath)) {
|
|
24857
|
+
sendJson(res, 409, { error: "Directory is not empty" });
|
|
24858
|
+
return;
|
|
24859
|
+
}
|
|
24860
|
+
fs.rmSync(resolved.dirPath, { recursive: true, force: true });
|
|
24861
|
+
sendJson(res, 200, { success: true });
|
|
24862
|
+
}).catch((e) => sendJson(res, 500, { error: e.message }));
|
|
24863
|
+
return;
|
|
24864
|
+
}
|
|
24865
|
+
}
|
|
23826
24866
|
{
|
|
23827
24867
|
const m = matchProjectRoute(url2, method, "POST", "/move");
|
|
23828
24868
|
if (m) {
|
|
@@ -23975,14 +25015,6 @@ data: ${JSON.stringify(data)}
|
|
|
23975
25015
|
}
|
|
23976
25016
|
return;
|
|
23977
25017
|
}
|
|
23978
|
-
if (method === "GET" && url2 === "/api/compute/health") {
|
|
23979
|
-
await proxyToBackend(req, res, "/health", "GET");
|
|
23980
|
-
return;
|
|
23981
|
-
}
|
|
23982
|
-
if (method === "POST" && url2 === "/api/compute") {
|
|
23983
|
-
await proxyToBackend(req, res, "/compute", "POST");
|
|
23984
|
-
return;
|
|
23985
|
-
}
|
|
23986
25018
|
if (!serveStatic(distDir, req, res)) {
|
|
23987
25019
|
res.statusCode = 404;
|
|
23988
25020
|
res.end("Not found");
|
|
@@ -23992,27 +25024,29 @@ data: ${JSON.stringify(data)}
|
|
|
23992
25024
|
const displayHost = host === "0.0.0.0" ? "localhost" : host;
|
|
23993
25025
|
const url = `http://${displayHost}:${port}`;
|
|
23994
25026
|
if (open) openBrowser(url);
|
|
23995
|
-
const close4 =
|
|
23996
|
-
|
|
23997
|
-
|
|
23998
|
-
|
|
23999
|
-
|
|
24000
|
-
|
|
24001
|
-
|
|
24002
|
-
|
|
24003
|
-
|
|
24004
|
-
|
|
25027
|
+
const close4 = async () => {
|
|
25028
|
+
await new Promise((resolve40) => {
|
|
25029
|
+
for (const timer of snapshotTimers.values()) clearTimeout(timer);
|
|
25030
|
+
snapshotTimers.clear();
|
|
25031
|
+
for (const w of watchers) w.close();
|
|
25032
|
+
for (const clients of sseClients.values()) {
|
|
25033
|
+
for (const c of clients) {
|
|
25034
|
+
try {
|
|
25035
|
+
c.end();
|
|
25036
|
+
} catch {
|
|
25037
|
+
}
|
|
24005
25038
|
}
|
|
24006
25039
|
}
|
|
24007
|
-
|
|
24008
|
-
|
|
24009
|
-
|
|
24010
|
-
|
|
25040
|
+
sseClients.clear();
|
|
25041
|
+
server.close(() => resolve40());
|
|
25042
|
+
});
|
|
25043
|
+
await computeServer?.close();
|
|
25044
|
+
};
|
|
24011
25045
|
return { url, close: close4 };
|
|
24012
25046
|
}
|
|
24013
25047
|
|
|
24014
25048
|
// cli/forge-studio.ts
|
|
24015
|
-
function
|
|
25049
|
+
function usage20() {
|
|
24016
25050
|
console.error(`ForgeCAD Studio
|
|
24017
25051
|
|
|
24018
25052
|
Usage:
|
|
@@ -24042,7 +25076,7 @@ function parseStudioArgs(argv) {
|
|
|
24042
25076
|
if (argv.length === 0) {
|
|
24043
25077
|
throw new Error("Missing project path. Use `forgecad studio <project-path> [project-path ...]`.");
|
|
24044
25078
|
}
|
|
24045
|
-
if (argv.includes("-h") || argv.includes("--help"))
|
|
25079
|
+
if (argv.includes("-h") || argv.includes("--help")) usage20();
|
|
24046
25080
|
const options = { open: false, strictPort: false, projectPaths: [] };
|
|
24047
25081
|
for (let i = 0; i < argv.length; i += 1) {
|
|
24048
25082
|
const arg = argv[i];
|
|
@@ -24117,7 +25151,7 @@ async function runStudioCli(argv = process.argv.slice(2)) {
|
|
|
24117
25151
|
// cli/forge-svg.ts
|
|
24118
25152
|
import { readFile as readFile5, writeFile as writeFile8 } from "fs/promises";
|
|
24119
25153
|
import { basename as basename13, resolve as resolve31 } from "path";
|
|
24120
|
-
function
|
|
25154
|
+
function usage21() {
|
|
24121
25155
|
console.error("Usage: forgecad export svg <script.forge.js> [output.svg]");
|
|
24122
25156
|
process.exit(1);
|
|
24123
25157
|
}
|
|
@@ -24126,7 +25160,7 @@ function defaultSvgOutput(scriptPath) {
|
|
|
24126
25160
|
}
|
|
24127
25161
|
async function runSvgCli(argv = process.argv.slice(2)) {
|
|
24128
25162
|
const scriptPath = argv[0];
|
|
24129
|
-
if (!scriptPath)
|
|
25163
|
+
if (!scriptPath) usage21();
|
|
24130
25164
|
const outputPath = argv[1] || defaultSvgOutput(scriptPath);
|
|
24131
25165
|
if (resolve31(outputPath) === resolve31(scriptPath)) {
|
|
24132
25166
|
console.error(`ERROR: output path would overwrite the input script. Specify an explicit output path.`);
|
|
@@ -24194,7 +25228,7 @@ function mmToM2(valueMm) {
|
|
|
24194
25228
|
function degToRad2(valueDeg) {
|
|
24195
25229
|
return valueDeg * Math.PI / 180;
|
|
24196
25230
|
}
|
|
24197
|
-
function
|
|
25231
|
+
function formatNumber4(value, digits = 6) {
|
|
24198
25232
|
if (!Number.isFinite(value)) return "0";
|
|
24199
25233
|
const normalized = Math.abs(value) < 1e-12 ? 0 : value;
|
|
24200
25234
|
return normalized.toFixed(digits).replace(/\.?0+$/, "");
|
|
@@ -24317,7 +25351,7 @@ function transformToOrigin(transform) {
|
|
|
24317
25351
|
yaw = Math.atan2(r10, r00);
|
|
24318
25352
|
}
|
|
24319
25353
|
const x = mmToM2(m[12]), y = mmToM2(m[13]), z = mmToM2(m[14]);
|
|
24320
|
-
return `xyz="${
|
|
25354
|
+
return `xyz="${formatNumber4(x)} ${formatNumber4(y)} ${formatNumber4(z)}" rpy="${formatNumber4(roll)} ${formatNumber4(pitch)} ${formatNumber4(yaw)}"`;
|
|
24321
25355
|
}
|
|
24322
25356
|
function sRgbFloat2(hex) {
|
|
24323
25357
|
if (!hex || !/^#([0-9a-f]{6})$/i.test(hex)) return null;
|
|
@@ -24378,7 +25412,7 @@ function urdfXml(spec, modelName, linkNameMap, jointNameMap, geometries, _linkWo
|
|
|
24378
25412
|
const color = sRgbFloat2(geometry.shapes[0]?.colorHex);
|
|
24379
25413
|
const materialXml = color ? `
|
|
24380
25414
|
<material name="${escapeXml2(urdfLinkName)}_material">
|
|
24381
|
-
<color rgba="${
|
|
25415
|
+
<color rgba="${formatNumber4(color[0], 3)} ${formatNumber4(color[1], 3)} ${formatNumber4(color[2], 3)} 1"/>
|
|
24382
25416
|
</material>` : "";
|
|
24383
25417
|
let collisionXml = "";
|
|
24384
25418
|
if (collisionMode === "visual") {
|
|
@@ -24407,17 +25441,17 @@ function urdfXml(spec, modelName, linkNameMap, jointNameMap, geometries, _linkWo
|
|
|
24407
25441
|
const bCz = mmToM2((geometry.bboxMin[2] + geometry.bboxMax[2]) * 0.5);
|
|
24408
25442
|
collisionXml = `
|
|
24409
25443
|
<collision>
|
|
24410
|
-
<origin xyz="${
|
|
25444
|
+
<origin xyz="${formatNumber4(bCx)} ${formatNumber4(bCy)} ${formatNumber4(bCz)}" rpy="0 0 0"/>
|
|
24411
25445
|
<geometry>
|
|
24412
|
-
<box size="${
|
|
25446
|
+
<box size="${formatNumber4(bDx)} ${formatNumber4(bDy)} ${formatNumber4(bDz)}"/>
|
|
24413
25447
|
</geometry>
|
|
24414
25448
|
</collision>`;
|
|
24415
25449
|
}
|
|
24416
25450
|
return ` <link name="${escapeXml2(urdfLinkName)}">
|
|
24417
25451
|
<inertial>
|
|
24418
|
-
<origin xyz="${
|
|
24419
|
-
<mass value="${
|
|
24420
|
-
<inertia ixx="${
|
|
25452
|
+
<origin xyz="${formatNumber4(comX)} ${formatNumber4(comY)} ${formatNumber4(comZ)}" rpy="0 0 0"/>
|
|
25453
|
+
<mass value="${formatNumber4(massKg, 6)}"/>
|
|
25454
|
+
<inertia ixx="${formatNumber4(ixx, 8)}" ixy="${formatNumber4(ixy, 8)}" ixz="${formatNumber4(ixz, 8)}" iyy="${formatNumber4(iyy, 8)}" iyz="${formatNumber4(iyz, 8)}" izz="${formatNumber4(izz, 8)}"/>
|
|
24421
25455
|
</inertial>
|
|
24422
25456
|
<visual>
|
|
24423
25457
|
<geometry>
|
|
@@ -24434,7 +25468,7 @@ function urdfXml(spec, modelName, linkNameMap, jointNameMap, geometries, _linkWo
|
|
|
24434
25468
|
const jType = urdfJointHasContinuous(joint2) ? "continuous" : urdfJointType(joint2.type);
|
|
24435
25469
|
const originAttr = transformToOrigin(joint2.frame);
|
|
24436
25470
|
const axisXml = joint2.type !== "fixed" ? `
|
|
24437
|
-
<axis xyz="${joint2.axis.map((v) =>
|
|
25471
|
+
<axis xyz="${joint2.axis.map((v) => formatNumber4(v)).join(" ")}"/>` : "";
|
|
24438
25472
|
let limitXml = "";
|
|
24439
25473
|
if (joint2.type !== "fixed" && jType !== "continuous") {
|
|
24440
25474
|
const lower2 = joint2.min !== void 0 ? joint2.type === "revolute" ? degToRad2(joint2.min) : mmToM2(joint2.min) : void 0;
|
|
@@ -24443,20 +25477,20 @@ function urdfXml(spec, modelName, linkNameMap, jointNameMap, geometries, _linkWo
|
|
|
24443
25477
|
const velocity = sourceOverrides?.velocity ?? joint2.velocity;
|
|
24444
25478
|
const vel = velocity !== void 0 ? joint2.type === "revolute" ? degToRad2(velocity) : mmToM2(velocity) : 10;
|
|
24445
25479
|
limitXml = `
|
|
24446
|
-
<limit${lower2 !== void 0 ? ` lower="${
|
|
25480
|
+
<limit${lower2 !== void 0 ? ` lower="${formatNumber4(lower2)}"` : ""}${upper !== void 0 ? ` upper="${formatNumber4(upper)}"` : ""} effort="${formatNumber4(effort)}" velocity="${formatNumber4(vel)}"/>`;
|
|
24447
25481
|
} else if (jType === "continuous") {
|
|
24448
25482
|
const effort = sourceOverrides?.effort ?? joint2.effort ?? 100;
|
|
24449
25483
|
const velocity = sourceOverrides?.velocity ?? joint2.velocity;
|
|
24450
25484
|
const vel = velocity !== void 0 ? degToRad2(velocity) : 10;
|
|
24451
25485
|
limitXml = `
|
|
24452
|
-
<limit effort="${
|
|
25486
|
+
<limit effort="${formatNumber4(effort)}" velocity="${formatNumber4(vel)}"/>`;
|
|
24453
25487
|
}
|
|
24454
25488
|
let dynamicsXml = "";
|
|
24455
25489
|
const damping = sourceOverrides?.damping ?? joint2.damping;
|
|
24456
25490
|
const friction = sourceOverrides?.friction ?? joint2.friction;
|
|
24457
25491
|
if (damping !== void 0 || friction !== void 0) {
|
|
24458
25492
|
dynamicsXml = `
|
|
24459
|
-
<dynamics${damping !== void 0 ? ` damping="${
|
|
25493
|
+
<dynamics${damping !== void 0 ? ` damping="${formatNumber4(damping)}"` : ""}${friction !== void 0 ? ` friction="${formatNumber4(friction)}"` : ""}/>`;
|
|
24460
25494
|
}
|
|
24461
25495
|
let mimicXml = "";
|
|
24462
25496
|
const coupling = couplingByJoint.get(joint2.name);
|
|
@@ -24464,7 +25498,7 @@ function urdfXml(spec, modelName, linkNameMap, jointNameMap, geometries, _linkWo
|
|
|
24464
25498
|
const primary = coupling.terms.reduce((a, b) => Math.abs(a.ratio) >= Math.abs(b.ratio) ? a : b);
|
|
24465
25499
|
const leaderUrdfName = jointNameMap.get(`${primary.joint}_joint`);
|
|
24466
25500
|
mimicXml = `
|
|
24467
|
-
<mimic joint="${escapeXml2(leaderUrdfName)}" multiplier="${
|
|
25501
|
+
<mimic joint="${escapeXml2(leaderUrdfName)}" multiplier="${formatNumber4(primary.ratio)}" offset="${formatNumber4(coupling.offset)}"/>`;
|
|
24468
25502
|
if (coupling.terms.length > 1) {
|
|
24469
25503
|
warnings.push(
|
|
24470
25504
|
`Joint "${joint2.name}" coupling has ${coupling.terms.length} terms but URDF mimic only supports 1. Using primary term (ratio=${primary.ratio} from "${primary.joint}").`
|
|
@@ -25017,12 +26051,12 @@ async function recordCliCommandEvent(input) {
|
|
|
25017
26051
|
// cli/forge-project.ts
|
|
25018
26052
|
import { existsSync as existsSync18, mkdirSync as mkdirSync8, readFileSync as readFileSync20, writeFileSync as writeFileSync15 } from "fs";
|
|
25019
26053
|
import { createInterface as createInterface2 } from "readline";
|
|
25020
|
-
import { basename as basename14, join as
|
|
26054
|
+
import { basename as basename14, join as join13, resolve as resolve34 } from "path";
|
|
25021
26055
|
|
|
25022
26056
|
// cli/license.ts
|
|
25023
26057
|
import { existsSync as existsSync17, mkdirSync as mkdirSync7, readFileSync as readFileSync19, unlinkSync as unlinkSync2, writeFileSync as writeFileSync14 } from "fs";
|
|
25024
26058
|
import { homedir as homedir8 } from "os";
|
|
25025
|
-
import { join as
|
|
26059
|
+
import { join as join12 } from "path";
|
|
25026
26060
|
var VALID_TIERS = /* @__PURE__ */ new Set(["free", "pro", "team"]);
|
|
25027
26061
|
var PRO_COMMANDS = /* @__PURE__ */ new Set([
|
|
25028
26062
|
// Advanced rendering
|
|
@@ -25063,14 +26097,14 @@ function tierSatisfies(userTier, required) {
|
|
|
25063
26097
|
return userTier === "pro" || userTier === "team";
|
|
25064
26098
|
}
|
|
25065
26099
|
function licenseDir() {
|
|
25066
|
-
return
|
|
26100
|
+
return join12(homedir8(), ".forgecad");
|
|
25067
26101
|
}
|
|
25068
26102
|
function ensureLicenseDir() {
|
|
25069
26103
|
const dir = licenseDir();
|
|
25070
26104
|
if (!existsSync17(dir)) mkdirSync7(dir, { recursive: true });
|
|
25071
26105
|
}
|
|
25072
26106
|
function licenseFilePath() {
|
|
25073
|
-
return
|
|
26107
|
+
return join12(licenseDir(), "license.json");
|
|
25074
26108
|
}
|
|
25075
26109
|
function readStoredLicense() {
|
|
25076
26110
|
const path3 = licenseFilePath();
|
|
@@ -25430,8 +26464,8 @@ async function runProjectCloneCli(args) {
|
|
|
25430
26464
|
const remoteFiles = await fetchRemoteFiles(project.id);
|
|
25431
26465
|
for (const file of remoteFiles) {
|
|
25432
26466
|
if (!file.content) continue;
|
|
25433
|
-
const filePath =
|
|
25434
|
-
mkdirSync8(
|
|
26467
|
+
const filePath = join13(targetDir, file.path);
|
|
26468
|
+
mkdirSync8(join13(filePath, ".."), { recursive: true });
|
|
25435
26469
|
writeFileSync15(filePath, file.content, "utf-8");
|
|
25436
26470
|
console.log(` ${file.path}`);
|
|
25437
26471
|
}
|
|
@@ -25499,8 +26533,8 @@ async function runProjectPullCli(args) {
|
|
|
25499
26533
|
for (const diff of incoming) {
|
|
25500
26534
|
const remote = remoteFiles.find((f2) => f2.path === diff.path);
|
|
25501
26535
|
if (remote?.content) {
|
|
25502
|
-
const filePath =
|
|
25503
|
-
mkdirSync8(
|
|
26536
|
+
const filePath = join13(cwd, remote.path);
|
|
26537
|
+
mkdirSync8(join13(filePath, ".."), { recursive: true });
|
|
25504
26538
|
writeFileSync15(filePath, remote.content, "utf-8");
|
|
25505
26539
|
written++;
|
|
25506
26540
|
}
|
|
@@ -25975,7 +27009,7 @@ async function runFileSaveCli(args) {
|
|
|
25975
27009
|
}
|
|
25976
27010
|
content = Buffer.concat(chunks).toString("utf-8");
|
|
25977
27011
|
} else if (!content) {
|
|
25978
|
-
const localPath =
|
|
27012
|
+
const localPath = join13(cwd, filePath);
|
|
25979
27013
|
if (!existsSync18(localPath)) {
|
|
25980
27014
|
console.error(`Local file not found: ${filePath}`);
|
|
25981
27015
|
console.error("Use --content or --stdin to provide content directly.");
|
|
@@ -26399,13 +27433,13 @@ function findCollisions(entries) {
|
|
|
26399
27433
|
}
|
|
26400
27434
|
return collisions;
|
|
26401
27435
|
}
|
|
26402
|
-
function
|
|
27436
|
+
function usage22() {
|
|
26403
27437
|
console.error("Usage: forgecad check params <script.forge.js> [--samples N]");
|
|
26404
27438
|
process.exit(1);
|
|
26405
27439
|
}
|
|
26406
27440
|
async function runParamCheckCli(argv = process.argv.slice(2)) {
|
|
26407
27441
|
const scriptPath = argv[0];
|
|
26408
|
-
if (!scriptPath)
|
|
27442
|
+
if (!scriptPath) usage22();
|
|
26409
27443
|
const samplesArg = argv.indexOf("--samples");
|
|
26410
27444
|
const numSamples = samplesArg >= 0 ? parseInt(argv[samplesArg + 1], 10) : 8;
|
|
26411
27445
|
const code = readFileSync21(resolve35(scriptPath), "utf-8");
|
|
@@ -26965,7 +27999,9 @@ function buildPrintCheckReport(objects, verifications, options) {
|
|
|
26965
27999
|
if (entries.length === 0) {
|
|
26966
28000
|
checks.push(check("print.scene.objects", "Printable objects", "fail", "error", "No visible shape objects were found."));
|
|
26967
28001
|
} else {
|
|
26968
|
-
checks.push(
|
|
28002
|
+
checks.push(
|
|
28003
|
+
check("print.scene.objects", "Printable objects", "pass", "info", `${entries.length} shape object(s) ready for print checks.`)
|
|
28004
|
+
);
|
|
26969
28005
|
}
|
|
26970
28006
|
const sceneBox = entries.length > 0 ? { min: sceneBBox.min, max: sceneBBox.max, size: bboxSize(sceneBBox.min, sceneBBox.max) } : null;
|
|
26971
28007
|
const failedVerifications = verifications.filter((verification) => verification.status === "fail");
|
|
@@ -27017,7 +28053,9 @@ function buildPrintCheckReport(objects, verifications, options) {
|
|
|
27017
28053
|
)
|
|
27018
28054
|
);
|
|
27019
28055
|
const thinObjects = objectReports.filter((object) => (object.thickness?.criticalAreaPercent ?? 0) > options.profile.maxThinAreaPercent);
|
|
27020
|
-
const warningThinObjects = objectReports.filter(
|
|
28056
|
+
const warningThinObjects = objectReports.filter(
|
|
28057
|
+
(object) => (object.thickness?.warningAreaPercent ?? 0) > options.profile.maxThinAreaPercent
|
|
28058
|
+
);
|
|
27021
28059
|
checks.push(
|
|
27022
28060
|
check(
|
|
27023
28061
|
"print.fdm.wall-thickness",
|
|
@@ -27085,7 +28123,7 @@ function buildPrintCheckReport(objects, verifications, options) {
|
|
|
27085
28123
|
}
|
|
27086
28124
|
|
|
27087
28125
|
// cli/print-check.ts
|
|
27088
|
-
function
|
|
28126
|
+
function usage23() {
|
|
27089
28127
|
console.error(
|
|
27090
28128
|
"Usage: forgecad check print <model.forge.js|asset.stl|asset.obj|asset.3mf|asset.step|asset.stp> [--json] [--output report.json] [--profile fdm-pla-0.4mm] [--param Key=Value]"
|
|
27091
28129
|
);
|
|
@@ -27212,7 +28250,7 @@ function parseArgs10(argv) {
|
|
|
27212
28250
|
}
|
|
27213
28251
|
const positional = argv.filter((arg, index) => !consumed.has(index) && !arg.startsWith("-"));
|
|
27214
28252
|
const scriptPath = positional[0];
|
|
27215
|
-
if (!scriptPath)
|
|
28253
|
+
if (!scriptPath) usage23();
|
|
27216
28254
|
return {
|
|
27217
28255
|
scriptPath,
|
|
27218
28256
|
json,
|
|
@@ -27348,7 +28386,7 @@ import { resolve as resolve38 } from "path";
|
|
|
27348
28386
|
|
|
27349
28387
|
// cli/solver-debug-artifacts.ts
|
|
27350
28388
|
import { mkdir as mkdir4, writeFile as writeFile9 } from "fs/promises";
|
|
27351
|
-
import { basename as basename15, join as
|
|
28389
|
+
import { basename as basename15, join as join14, resolve as resolve37 } from "path";
|
|
27352
28390
|
|
|
27353
28391
|
// cli/solver-debug-html.ts
|
|
27354
28392
|
function escapeHtml(s) {
|
|
@@ -28063,14 +29101,14 @@ async function writeSolverDebugArtifacts(outputRoot, scriptPath, objects) {
|
|
|
28063
29101
|
const seen = usedNames.get(baseName) ?? 0;
|
|
28064
29102
|
usedNames.set(baseName, seen + 1);
|
|
28065
29103
|
const dirName = seen === 0 ? baseName : `${baseName}-${seen + 1}`;
|
|
28066
|
-
const dir =
|
|
28067
|
-
const svgDir =
|
|
29104
|
+
const dir = join14(root, dirName);
|
|
29105
|
+
const svgDir = join14(dir, "svg");
|
|
28068
29106
|
await mkdir4(svgDir, { recursive: true });
|
|
28069
|
-
const transcriptPath =
|
|
29107
|
+
const transcriptPath = join14(dir, "constructive-transcript.md");
|
|
28070
29108
|
await writeFile9(transcriptPath, buildConstructiveTranscriptMarkdown(object.name || dirName, meta), "utf8");
|
|
28071
29109
|
const snapshotPaths = [];
|
|
28072
29110
|
for (const snapshot of debug.svgSnapshots) {
|
|
28073
|
-
const snapshotPath =
|
|
29111
|
+
const snapshotPath = join14(svgDir, `${snapshot.label}.svg`);
|
|
28074
29112
|
await writeFile9(snapshotPath, snapshot.svg, "utf8");
|
|
28075
29113
|
snapshotPaths.push(snapshotPath);
|
|
28076
29114
|
}
|
|
@@ -28095,7 +29133,7 @@ async function writeSolverDebugArtifacts(outputRoot, scriptPath, objects) {
|
|
|
28095
29133
|
for (const [index, html] of htmlMap) {
|
|
28096
29134
|
const bundle = bundles[index] ?? bundles[bundles.length - 1];
|
|
28097
29135
|
if (bundle) {
|
|
28098
|
-
const htmlPath =
|
|
29136
|
+
const htmlPath = join14(bundle.dir, "dashboard.html");
|
|
28099
29137
|
await writeFile9(htmlPath, html, "utf8");
|
|
28100
29138
|
}
|
|
28101
29139
|
}
|
|
@@ -28114,7 +29152,7 @@ async function writeSolverDebugIndex(root, scriptPath, bundles) {
|
|
|
28114
29152
|
lines.push(` svg_snapshots: ${bundle.snapshotPaths.length}`);
|
|
28115
29153
|
}
|
|
28116
29154
|
}
|
|
28117
|
-
await writeFile9(
|
|
29155
|
+
await writeFile9(join14(root, "README.md"), lines.join("\n") + "\n", "utf8");
|
|
28118
29156
|
}
|
|
28119
29157
|
|
|
28120
29158
|
// cli/test-run.ts
|
|
@@ -28274,7 +29312,7 @@ function parseParamFlags2(argv) {
|
|
|
28274
29312
|
}
|
|
28275
29313
|
return { overrides, consumed };
|
|
28276
29314
|
}
|
|
28277
|
-
function
|
|
29315
|
+
function usage24() {
|
|
28278
29316
|
console.error(
|
|
28279
29317
|
"Usage: forgecad run <model.forge.js|asset.stl|asset.obj|asset.3mf|asset.step|asset.stp> [--details] [--history] [--features] [--connectivity] [--connectivity-tolerance <mm>] [--param Key=Value] [--debug-imports] [--verbose|-v] [--backend manifold|occt|truck] [--quality live|default|high] [--solver-profile] [--solver-debug-out <dir>]"
|
|
28280
29318
|
);
|
|
@@ -28418,9 +29456,14 @@ function formatThreeMfStructure(analysis) {
|
|
|
28418
29456
|
function objectDisplayName(obj) {
|
|
28419
29457
|
return obj.groupName ? `${obj.name} [${obj.groupName}]` : obj.name;
|
|
28420
29458
|
}
|
|
28421
|
-
function printCompactObjectSummary(objects, focusSummary) {
|
|
29459
|
+
function printCompactObjectSummary(objects, focusSummary, assemblyKinematics) {
|
|
28422
29460
|
const focusTag = focusSummary ? ` (focused, ${focusSummary})` : "";
|
|
28423
29461
|
console.log(`\u2713 Objects: ${objects.length}${focusTag}`);
|
|
29462
|
+
if (objects.length === 0 && assemblyKinematics) {
|
|
29463
|
+
console.log(
|
|
29464
|
+
` Rig only: ${assemblyKinematics.links.length} link(s), ${assemblyKinematics.edges.length} edge(s), ${assemblyKinematics.angles.length} angle constraint(s)`
|
|
29465
|
+
);
|
|
29466
|
+
}
|
|
28424
29467
|
const names = objects.map(objectDisplayName);
|
|
28425
29468
|
const shown = names.slice(0, MAX_COMPACT_OBJECT_NAMES);
|
|
28426
29469
|
for (let i = 0; i < shown.length; i += 4) {
|
|
@@ -28439,7 +29482,9 @@ async function runScriptCli(argv = process.argv.slice(2)) {
|
|
|
28439
29482
|
const removedSpatialFlag = argv.find((arg) => arg === "--spatial" || arg.startsWith("--spatial="));
|
|
28440
29483
|
if (removedSpatialFlag) {
|
|
28441
29484
|
console.error("`forgecad run --spatial` has been removed.");
|
|
28442
|
-
console.error(
|
|
29485
|
+
console.error(
|
|
29486
|
+
"Use `forgecad inspect fit interference <model>` for collision evidence or `forgecad inspect physical gaps <model>` for spatial gap evidence."
|
|
29487
|
+
);
|
|
28443
29488
|
process.exit(1);
|
|
28444
29489
|
}
|
|
28445
29490
|
const { overrides: paramCliOverrides, consumed: paramConsumed } = parseParamFlags2(argv);
|
|
@@ -28470,7 +29515,7 @@ async function runScriptCli(argv = process.argv.slice(2)) {
|
|
|
28470
29515
|
(arg, i) => !paramConsumed.has(i) && !focusConsumed.has(i) && !connectivityConsumed.has(i) && arg !== "--details" && arg !== "--history" && arg !== "--features" && arg !== "--solver-profile" && arg !== "--debug-imports" && arg !== "--journeys" && arg !== "--journeys-json" && arg !== "--verbose" && arg !== "-v" && arg !== "--backend" && argv[i - 1] !== "--backend" && arg !== "--quality" && argv[i - 1] !== "--quality" && arg !== "-q" && argv[i - 1] !== "-q" && arg !== "--solver-debug-out" && argv[i - 1] !== "--solver-debug-out"
|
|
28471
29516
|
);
|
|
28472
29517
|
const scriptPath = positional[0];
|
|
28473
|
-
if (!scriptPath)
|
|
29518
|
+
if (!scriptPath) usage24();
|
|
28474
29519
|
let activeBackend = CLI_DEFAULT_BACKEND;
|
|
28475
29520
|
try {
|
|
28476
29521
|
if (solverDebugOut) {
|
|
@@ -28564,7 +29609,7 @@ async function runScriptCli(argv = process.argv.slice(2)) {
|
|
|
28564
29609
|
}
|
|
28565
29610
|
}
|
|
28566
29611
|
} else {
|
|
28567
|
-
printCompactObjectSummary(visibleObjects, focusSummary);
|
|
29612
|
+
printCompactObjectSummary(visibleObjects, focusSummary, result.assemblyKinematics);
|
|
28568
29613
|
}
|
|
28569
29614
|
if (detailedObjects && input.directThreeMfAnalysis) {
|
|
28570
29615
|
console.log(`
|
|
@@ -28822,7 +29867,7 @@ async function runScriptCli(argv = process.argv.slice(2)) {
|
|
|
28822
29867
|
// cli/forge-render-section.ts
|
|
28823
29868
|
import { writeFile as writeFile10 } from "fs/promises";
|
|
28824
29869
|
import { basename as basename16, extname as extname11, resolve as resolve39 } from "path";
|
|
28825
|
-
function
|
|
29870
|
+
function usage25() {
|
|
28826
29871
|
console.error(
|
|
28827
29872
|
"Usage: forgecad render section <model.forge.js|asset.stl|asset.obj|asset.3mf|asset.step|asset.stp> [output.svg|.png] [--param Key=Value] [--plane XY|XZ|YZ] [--offset <number>] [--size <px>] [--edges <off|thin|bold>] [--chrome-path <path>] [--background <color>]"
|
|
28828
29873
|
);
|
|
@@ -28883,12 +29928,12 @@ async function runRenderSectionCli(argv = process.argv.slice(2)) {
|
|
|
28883
29928
|
background = argv[++i];
|
|
28884
29929
|
} else if (argv[i].startsWith("-")) {
|
|
28885
29930
|
console.error(`Unknown option: ${argv[i]}`);
|
|
28886
|
-
|
|
29931
|
+
usage25();
|
|
28887
29932
|
} else {
|
|
28888
29933
|
args.push(argv[i]);
|
|
28889
29934
|
}
|
|
28890
29935
|
}
|
|
28891
|
-
if (args.length === 0)
|
|
29936
|
+
if (args.length === 0) usage25();
|
|
28892
29937
|
scriptPath = args[0];
|
|
28893
29938
|
const wantsPng = args[1] ? extname11(args[1]).toLowerCase() === ".png" : false;
|
|
28894
29939
|
outputPath = args[1] || defaultOutput(scriptPath, wantsPng ? "png" : "svg");
|
|
@@ -28946,17 +29991,17 @@ async function runRenderSectionCli(argv = process.argv.slice(2)) {
|
|
|
28946
29991
|
}
|
|
28947
29992
|
|
|
28948
29993
|
// cli/update-check.ts
|
|
28949
|
-
import { spawn as
|
|
29994
|
+
import { spawn as spawn4 } from "child_process";
|
|
28950
29995
|
import { existsSync as existsSync19, mkdirSync as mkdirSync10, readFileSync as readFileSync22, writeFileSync as writeFileSync17 } from "fs";
|
|
28951
29996
|
import { homedir as homedir9 } from "os";
|
|
28952
|
-
import { dirname as dirname10, join as
|
|
29997
|
+
import { dirname as dirname10, join as join15 } from "path";
|
|
28953
29998
|
var PACKAGE_NAME = "forgecad";
|
|
28954
29999
|
var REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
|
|
28955
30000
|
var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
28956
30001
|
var FETCH_TIMEOUT_MS = 3e3;
|
|
28957
30002
|
var WORKER_FLAG = "--__update-check-worker";
|
|
28958
30003
|
function cachePath() {
|
|
28959
|
-
return
|
|
30004
|
+
return join15(homedir9(), ".forgecad", "update-check.json");
|
|
28960
30005
|
}
|
|
28961
30006
|
function readCache() {
|
|
28962
30007
|
try {
|
|
@@ -29025,7 +30070,7 @@ function scheduleUpdateCheck(currentVersion) {
|
|
|
29025
30070
|
const scriptPath = process.argv[1];
|
|
29026
30071
|
if (!scriptPath) return;
|
|
29027
30072
|
try {
|
|
29028
|
-
const child =
|
|
30073
|
+
const child = spawn4(process.execPath, [scriptPath, WORKER_FLAG], {
|
|
29029
30074
|
detached: true,
|
|
29030
30075
|
stdio: "ignore",
|
|
29031
30076
|
windowsHide: true,
|
|
@@ -29073,7 +30118,7 @@ var RENDER_ANGLE_VALUES = [
|
|
|
29073
30118
|
];
|
|
29074
30119
|
var CAPTURE_TYPE_VALUES = [
|
|
29075
30120
|
{ value: "orbit", description: "Orbit the camera around the model" },
|
|
29076
|
-
{ value: "animation", description: "Play a
|
|
30121
|
+
{ value: "animation", description: "Play a named joint clip with a fixed camera" },
|
|
29077
30122
|
{ value: "section-sweep", description: "Move a clipping plane through the model" }
|
|
29078
30123
|
];
|
|
29079
30124
|
var OUTPUT_FORMAT_VALUES = [
|
|
@@ -29162,8 +30207,7 @@ var RENDER_STYLE_VALUES = [
|
|
|
29162
30207
|
{ value: "fast", description: "Low-cost attractive live preview shading" },
|
|
29163
30208
|
{ value: "glass", description: "Transparent inspection view with rim shell and readable edges" },
|
|
29164
30209
|
{ value: "inspection", description: "Light technical evidence style with matte shading and dark edges" },
|
|
29165
|
-
{ value: "
|
|
29166
|
-
{ value: "hybrid", description: "Topo-led surface contours with a quiet orthogonal field" },
|
|
30210
|
+
{ value: "contour", description: "Dense technical surface-field contours" },
|
|
29167
30211
|
{ value: "scan", description: "Dark spatial-report style with object-colored scene-grid cells" }
|
|
29168
30212
|
];
|
|
29169
30213
|
var PARAM_OPTIONS = [
|
|
@@ -29226,11 +30270,11 @@ var RENDER_OPTIONS = [
|
|
|
29226
30270
|
name: "--render-style",
|
|
29227
30271
|
description: "Visual render style (render default: classic; inspect default: inspection)",
|
|
29228
30272
|
argument: "required",
|
|
29229
|
-
valueLabel: "<classic|studio|fast|glass|inspection|
|
|
30273
|
+
valueLabel: "<classic|studio|fast|glass|inspection|contour|scan>",
|
|
29230
30274
|
values: RENDER_STYLE_VALUES
|
|
29231
30275
|
},
|
|
29232
30276
|
{ name: "--scan-granularity", description: "Scan cells across the scene longest axis", argument: "required", valueLabel: "<12-144>" },
|
|
29233
|
-
{ name: "--port", description: "
|
|
30277
|
+
{ name: "--port", description: "Renderer server port", argument: "required", valueLabel: "<n>" },
|
|
29234
30278
|
{ name: "--fresh-server", description: "Start a fresh renderer instead of reusing an existing one" },
|
|
29235
30279
|
{
|
|
29236
30280
|
name: "--chrome-path",
|
|
@@ -29323,7 +30367,7 @@ var INSPECT_EVIDENCE_OPTIONS = [
|
|
|
29323
30367
|
name: "--render-style",
|
|
29324
30368
|
description: "Visual render style (default: inspection)",
|
|
29325
30369
|
argument: "required",
|
|
29326
|
-
valueLabel: "<classic|studio|fast|glass|inspection|
|
|
30370
|
+
valueLabel: "<classic|studio|fast|glass|inspection|contour|scan>",
|
|
29327
30371
|
values: RENDER_STYLE_VALUES
|
|
29328
30372
|
},
|
|
29329
30373
|
{
|
|
@@ -29437,7 +30481,7 @@ var INSPECT_EVIDENCE_OPTIONS = [
|
|
|
29437
30481
|
{ value: "clean", description: "Solid cap faces" }
|
|
29438
30482
|
]
|
|
29439
30483
|
},
|
|
29440
|
-
{ name: "--port", description: "
|
|
30484
|
+
{ name: "--port", description: "Renderer server port", argument: "required", valueLabel: "<n>" },
|
|
29441
30485
|
{ name: "--fresh-server", description: "Start a fresh renderer instead of reusing an existing one" },
|
|
29442
30486
|
{
|
|
29443
30487
|
name: "--chrome-path",
|
|
@@ -29523,7 +30567,7 @@ var INSPECT_SECTIONS_COMMON_OPTIONS = [
|
|
|
29523
30567
|
values: BACKEND_VALUES
|
|
29524
30568
|
},
|
|
29525
30569
|
{ name: "--force", description: "Replace the requested output directory instead of allocating a sibling" },
|
|
29526
|
-
{ name: "--port", description: "
|
|
30570
|
+
{ name: "--port", description: "Renderer server port", argument: "required", valueLabel: "<n>" },
|
|
29527
30571
|
{ name: "--fresh-server", description: "Start a fresh renderer instead of reusing an existing one" },
|
|
29528
30572
|
{
|
|
29529
30573
|
name: "--chrome-path",
|
|
@@ -29703,6 +30747,14 @@ var INSPECT_CANONICAL_SPECS = [
|
|
|
29703
30747
|
description: "Use this to inspect orientation changes, flipped surfaces, and faceting next to image or Zebra evidence.",
|
|
29704
30748
|
examples: ["forgecad inspect visual normals examples/api/static-assembly-connectors.forge.js --camera iso"]
|
|
29705
30749
|
},
|
|
30750
|
+
{
|
|
30751
|
+
path: ["inspect", "visual", "rig"],
|
|
30752
|
+
evidenceName: "rig",
|
|
30753
|
+
label: "visual rig",
|
|
30754
|
+
summary: "Capture kinematic rig skeleton evidence.",
|
|
30755
|
+
description: "Use this to inspect joint pivots, axes, angle arcs, hierarchy links, and child-part links with geometry reduced to a shadow.",
|
|
30756
|
+
examples: ["forgecad inspect visual rig model.forge.js --camera iso"]
|
|
30757
|
+
},
|
|
29706
30758
|
{
|
|
29707
30759
|
path: ["inspect", "visual", "objects"],
|
|
29708
30760
|
evidenceName: "objects",
|
|
@@ -29820,7 +30872,7 @@ Cutaway options:
|
|
|
29820
30872
|
|
|
29821
30873
|
Visual options:
|
|
29822
30874
|
--background <color> Canvas background override
|
|
29823
|
-
--render-style <classic|studio|fast|glass|inspection|
|
|
30875
|
+
--render-style <classic|studio|fast|glass|inspection|contour|scan>
|
|
29824
30876
|
Visual render style (default: inspection)
|
|
29825
30877
|
--edges <off|thin|bold> Edge overlay preset for solid visual evidence (default: thin)
|
|
29826
30878
|
|
|
@@ -29873,18 +30925,19 @@ var INSPECT_TOPIC_COMMANDS = [
|
|
|
29873
30925
|
{
|
|
29874
30926
|
group: "Inspect",
|
|
29875
30927
|
path: ["inspect", "visual"],
|
|
29876
|
-
summary: "Inspect visual context, cutaways, depth, normals, or object identity.",
|
|
29877
|
-
description: "Choose `image`, `cutaway`, `depth`, `normals`, or `objects` visual evidence.",
|
|
30928
|
+
summary: "Inspect visual context, cutaways, depth, normals, rig skeletons, or object identity.",
|
|
30929
|
+
description: "Choose `image`, `cutaway`, `depth`, `normals`, `rig`, or `objects` visual evidence.",
|
|
29878
30930
|
usage: [
|
|
29879
|
-
"forgecad inspect visual <image|cutaway|depth|normals|objects> <model.forge.js|asset.stl|asset.obj|asset.3mf|asset.step|asset.stp> [options]"
|
|
30931
|
+
"forgecad inspect visual <image|cutaway|depth|normals|rig|objects> <model.forge.js|asset.stl|asset.obj|asset.3mf|asset.step|asset.stp> [options]"
|
|
29880
30932
|
],
|
|
29881
30933
|
examples: [
|
|
29882
30934
|
"forgecad inspect visual image model.forge.js --camera iso",
|
|
29883
30935
|
"forgecad inspect visual cutaway model.forge.js --plane yz",
|
|
30936
|
+
"forgecad inspect visual rig model.forge.js --camera iso",
|
|
29884
30937
|
"forgecad inspect visual objects model.forge.js --camera front --camera right"
|
|
29885
30938
|
],
|
|
29886
30939
|
run: async () => {
|
|
29887
|
-
console.error("`forgecad inspect visual` needs a mode: image, cutaway, depth, normals, or objects.");
|
|
30940
|
+
console.error("`forgecad inspect visual` needs a mode: image, cutaway, depth, normals, rig, or objects.");
|
|
29888
30941
|
process.exit(1);
|
|
29889
30942
|
}
|
|
29890
30943
|
},
|
|
@@ -29958,11 +31011,12 @@ var INSPECT_TOPIC_COMMANDS = [
|
|
|
29958
31011
|
}
|
|
29959
31012
|
];
|
|
29960
31013
|
var INSPECT_COMMAND_OVERVIEW = [
|
|
31014
|
+
"- `inspect sketch` \u2014 returned sketch/profile regions, selector dry-runs, and extrusion compatibility",
|
|
29961
31015
|
"- `inspect history` \u2014 final-object feature recipes and feedback anchors",
|
|
29962
31016
|
"- `inspect design-trace` \u2014 raw construction DAG, source spans, and cache diagnostics",
|
|
29963
31017
|
"- `inspect section` \u2014 agent-native one-off section probe with optional ray rulers and replay JSON",
|
|
29964
31018
|
"- `inspect replay` \u2014 rerun a saved section probe on the same or another source",
|
|
29965
|
-
"- `inspect visual image|cutaway|depth|normals|objects` \u2014 visual context, clipped 3D cutaways, depth, normals, and identity evidence",
|
|
31019
|
+
"- `inspect visual image|cutaway|depth|normals|rig|objects` \u2014 visual context, clipped 3D cutaways, depth, normals, rig skeletons, and identity evidence",
|
|
29966
31020
|
"- `inspect surface zebra|roughness` \u2014 surface continuity and roughness evidence",
|
|
29967
31021
|
"- `inspect physical components|floating|gaps` \u2014 physical component graph evidence",
|
|
29968
31022
|
"- `inspect fit interference` \u2014 positive-volume overlap evidence",
|
|
@@ -30102,7 +31156,7 @@ var CAPTURE_COMMON_OPTIONS = [
|
|
|
30102
31156
|
valueLabel: "<orbit|animation|section-sweep>",
|
|
30103
31157
|
values: CAPTURE_TYPE_VALUES
|
|
30104
31158
|
},
|
|
30105
|
-
{ name: "--animation", description: "Named
|
|
31159
|
+
{ name: "--animation", description: "Named joint animation clip", argument: "required", valueLabel: "<name>" },
|
|
30106
31160
|
{ name: "--animation-loops", description: "Repeat the selected animation clip", argument: "required", valueLabel: "<n>" },
|
|
30107
31161
|
{ name: "--cut-plane", description: "Enable a named cut plane", argument: "required", valueLabel: "<name>", repeatable: true },
|
|
30108
31162
|
{
|
|
@@ -30262,15 +31316,14 @@ var commands = [
|
|
|
30262
31316
|
group: "Shell",
|
|
30263
31317
|
path: ["skill", "install"],
|
|
30264
31318
|
summary: "Install ForgeCAD agent skills into the default global agent skill directory.",
|
|
30265
|
-
usage: ["forgecad skill install [--target agents|claude|codex|opencode|all] [--core-only]
|
|
31319
|
+
usage: ["forgecad skill install [--target agents|claude|codex|opencode|all] [--core-only]"],
|
|
30266
31320
|
examples: [
|
|
30267
31321
|
"forgecad skill install",
|
|
30268
31322
|
"forgecad skill install --target claude",
|
|
30269
31323
|
"forgecad skill install --target codex",
|
|
30270
31324
|
"forgecad skill install --target opencode",
|
|
30271
31325
|
"forgecad skill install --target all",
|
|
30272
|
-
"forgecad skill install --core-only"
|
|
30273
|
-
"forgecad skill install --dev"
|
|
31326
|
+
"forgecad skill install --core-only"
|
|
30274
31327
|
],
|
|
30275
31328
|
completion: {
|
|
30276
31329
|
options: [
|
|
@@ -30289,7 +31342,6 @@ var commands = [
|
|
|
30289
31342
|
},
|
|
30290
31343
|
{ name: "--all-agents", description: "Alias for `--target all`" },
|
|
30291
31344
|
{ name: "--core-only", description: "Install only the core forgecad skill, without companion workflow skills" },
|
|
30292
|
-
{ name: "--dev", description: "Install the internal ForgeCAD developer variant of the core skill" },
|
|
30293
31345
|
{ name: "--library", description: "Deprecated no-op; the public skill library is installed by default" },
|
|
30294
31346
|
{ name: "--all", description: "Deprecated no-op; the public skill library is installed by default" }
|
|
30295
31347
|
]
|
|
@@ -30504,7 +31556,7 @@ var commands = [
|
|
|
30504
31556
|
group: "Modeling",
|
|
30505
31557
|
path: ["render", "3d"],
|
|
30506
31558
|
summary: "Render a Forge scene to PNG using the real viewport renderer.",
|
|
30507
|
-
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.\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.",
|
|
31559
|
+
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.",
|
|
30508
31560
|
usage: [
|
|
30509
31561
|
"forgecad render 3d <model.forge.js|asset.stl|asset.obj|asset.3mf|asset.step|asset.stp> [output.png] [--param Key=Value] [--focus [names]] [--hide names] [--edges off|thin|bold] [options]"
|
|
30510
31562
|
],
|
|
@@ -30706,8 +31758,8 @@ var commands = [
|
|
|
30706
31758
|
{
|
|
30707
31759
|
group: "Modeling",
|
|
30708
31760
|
path: ["capture", "gif"],
|
|
30709
|
-
summary: "Capture an animated GIF from a script via orbit,
|
|
30710
|
-
description: "Renders an animated sequence by either orbiting the camera around the model or playing back a
|
|
31761
|
+
summary: "Capture an animated GIF from a script via orbit, named joint playback, or section sweep.",
|
|
31762
|
+
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.",
|
|
30711
31763
|
usage: [
|
|
30712
31764
|
"forgecad capture gif <model.forge.js|asset.stl|asset.obj|asset.3mf|asset.step|asset.stp> [output.gif] [options] [--param Key=Value]"
|
|
30713
31765
|
],
|
|
@@ -30731,14 +31783,14 @@ var commands = [
|
|
|
30731
31783
|
{
|
|
30732
31784
|
group: "Modeling",
|
|
30733
31785
|
path: ["capture", "mp4"],
|
|
30734
|
-
summary: "Capture an MP4 from a script via orbit,
|
|
31786
|
+
summary: "Capture an MP4 from a script via orbit, named joint playback, or section sweep.",
|
|
30735
31787
|
description: "Same as `capture gif` but outputs H.264 MP4. Prefers ffmpeg when available for better compression; falls back to a pure-JS encoder. MP4 is better for longer animations, section sweeps, or higher resolutions where GIF file sizes become unwieldy. The camera flags match `capture gif`.",
|
|
30736
31788
|
usage: [
|
|
30737
31789
|
"forgecad capture mp4 <model.forge.js|asset.stl|asset.obj|asset.3mf|asset.step|asset.stp> [output.mp4] [options] [--param Key=Value]"
|
|
30738
31790
|
],
|
|
30739
31791
|
examples: [
|
|
30740
31792
|
"forgecad capture mp4 examples/products/cup.forge.js",
|
|
30741
|
-
"forgecad capture mp4 examples/api/
|
|
31793
|
+
"forgecad capture mp4 examples/api/assembly-kinematics-four-bar.forge.js out/four-bar.mp4 --view iso",
|
|
30742
31794
|
'forgecad capture mp4 model.forge.js out/raw.mp4 --param "Output=raw-sdf"',
|
|
30743
31795
|
"forgecad capture mp4 model.forge.js out/front.mp4 --camera front",
|
|
30744
31796
|
"forgecad capture mp4 model.forge.js out/hero.mp4 --view hero",
|
|
@@ -30866,14 +31918,23 @@ var commands = [
|
|
|
30866
31918
|
{
|
|
30867
31919
|
group: "Export",
|
|
30868
31920
|
path: ["export", "step"],
|
|
30869
|
-
summary: "Export exact STEP through the native OCCT runtime exporter.",
|
|
30870
|
-
usage: ["forgecad export step <model.forge.js|asset.step|asset.stp> [--output path]"],
|
|
31921
|
+
summary: "Export exact STEP through the native OCCT or Truck runtime exporter.",
|
|
31922
|
+
usage: ["forgecad export step <model.forge.js|asset.step|asset.stp> [--output path] [--backend occt|truck]"],
|
|
30871
31923
|
examples: [
|
|
30872
31924
|
"forgecad export step examples/api/boolean-operations.forge.js",
|
|
30873
|
-
"forgecad export step housing.step --output housing-copy.step"
|
|
31925
|
+
"forgecad export step housing.step --output housing-copy.step",
|
|
31926
|
+
"forgecad export step examples/api/mixed-edge-finishes-proof.forge.js --backend truck"
|
|
30874
31927
|
],
|
|
30875
31928
|
completion: {
|
|
30876
|
-
options: [
|
|
31929
|
+
options: [
|
|
31930
|
+
{ name: "--output", description: "Output STEP path", argument: "required", valueLabel: "<path>", valueKind: "path" },
|
|
31931
|
+
{
|
|
31932
|
+
name: "--backend",
|
|
31933
|
+
description: "Exact BREP exporter: occt (default) or truck (native analytic kernel)",
|
|
31934
|
+
argument: "required",
|
|
31935
|
+
valueLabel: "<occt|truck>"
|
|
31936
|
+
}
|
|
31937
|
+
],
|
|
30877
31938
|
positionals: [{ description: "Forge script or STEP file", valueKind: "exact-cad-input" }]
|
|
30878
31939
|
},
|
|
30879
31940
|
run: (args) => runBrepCli(["--format", "step", ...args])
|
|
@@ -31805,6 +32866,51 @@ var commands = [
|
|
|
31805
32866
|
examples: ["forgecad inspect evidence", "forgecad inspect evidence --json"],
|
|
31806
32867
|
run: runInspectEvidenceListCli
|
|
31807
32868
|
},
|
|
32869
|
+
{
|
|
32870
|
+
group: "Inspect",
|
|
32871
|
+
path: ["inspect", "sketch"],
|
|
32872
|
+
summary: "Inspect returned sketches and profile regions used by returned shapes.",
|
|
32873
|
+
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.",
|
|
32874
|
+
usage: [
|
|
32875
|
+
"forgecad inspect sketch <model.forge.js> [--json] [--object <name-or-path>] [--seed <x,y>] [--operation extrude] [--param Key=Value] [--backend manifold|occt|truck] [--quality live|default|high]"
|
|
32876
|
+
],
|
|
32877
|
+
examples: [
|
|
32878
|
+
"forgecad inspect sketch examples/api/sketch-regions.forge.js",
|
|
32879
|
+
'forgecad inspect sketch model.forge.js --object "Profile" --seed 45,15',
|
|
32880
|
+
'forgecad inspect sketch model.forge.js --json --object "Body" --seed 45,15 --operation extrude'
|
|
32881
|
+
],
|
|
32882
|
+
completion: {
|
|
32883
|
+
options: [
|
|
32884
|
+
{ name: "--json", description: "Emit machine-readable JSON" },
|
|
32885
|
+
{ name: "--compact", description: "Minify JSON output" },
|
|
32886
|
+
{ name: "--object", description: "Inspect one scene object by name, id, or tree path", argument: "required", valueLabel: "<name>" },
|
|
32887
|
+
{
|
|
32888
|
+
name: "--seed",
|
|
32889
|
+
description: "Dry-run a region selector point in sketch-local coordinates",
|
|
32890
|
+
argument: "required",
|
|
32891
|
+
valueLabel: "<x,y>"
|
|
32892
|
+
},
|
|
32893
|
+
{ name: "--operation", description: "Operation compatibility to preview", argument: "required", valueLabel: "<extrude>" },
|
|
32894
|
+
...PARAM_OPTIONS,
|
|
32895
|
+
{
|
|
32896
|
+
name: "--quality",
|
|
32897
|
+
description: "Geometry quality",
|
|
32898
|
+
argument: "required",
|
|
32899
|
+
valueLabel: "<default|live|high>",
|
|
32900
|
+
values: QUALITY_VALUES
|
|
32901
|
+
},
|
|
32902
|
+
{
|
|
32903
|
+
name: "--backend",
|
|
32904
|
+
description: "Geometry backend",
|
|
32905
|
+
argument: "required",
|
|
32906
|
+
valueLabel: "<manifold|occt|truck>",
|
|
32907
|
+
values: BACKEND_VALUES
|
|
32908
|
+
}
|
|
32909
|
+
],
|
|
32910
|
+
positionals: [{ description: "Forge script or CAD asset", valueKind: "renderable" }]
|
|
32911
|
+
},
|
|
32912
|
+
run: runInspectSketchCli
|
|
32913
|
+
},
|
|
31808
32914
|
{
|
|
31809
32915
|
group: "Inspect",
|
|
31810
32916
|
path: ["inspect", "section"],
|
|
@@ -32068,7 +33174,7 @@ var commands = [
|
|
|
32068
33174
|
{ name: "--update", description: "Regenerate compiler snapshots" }
|
|
32069
33175
|
]
|
|
32070
33176
|
},
|
|
32071
|
-
run: async (args) => (await import("./check-compiler-
|
|
33177
|
+
run: async (args) => (await import("./check-compiler-SDX5QIXI.js")).runCheckCompilerCli(args)
|
|
32072
33178
|
},
|
|
32073
33179
|
{
|
|
32074
33180
|
group: "Checks",
|
|
@@ -32091,7 +33197,7 @@ var commands = [
|
|
|
32091
33197
|
{ name: "--update", description: "Regenerate query-propagation snapshots" }
|
|
32092
33198
|
]
|
|
32093
33199
|
},
|
|
32094
|
-
run: async (args) => (await import("./check-query-propagation-
|
|
33200
|
+
run: async (args) => (await import("./check-query-propagation-EAYEFT77.js")).runCheckQueryPropagationCli(args)
|
|
32095
33201
|
},
|
|
32096
33202
|
{
|
|
32097
33203
|
group: "Checks",
|
|
@@ -32881,7 +33987,9 @@ function printRemovedRunFullFlagMessage() {
|
|
|
32881
33987
|
}
|
|
32882
33988
|
function printRemovedRunSpatialFlagMessage() {
|
|
32883
33989
|
console.error("`forgecad run --spatial` has been removed.");
|
|
32884
|
-
console.error(
|
|
33990
|
+
console.error(
|
|
33991
|
+
"Use `forgecad inspect fit interference <model>` for collision evidence or `forgecad inspect physical gaps <model>` for spatial gap evidence."
|
|
33992
|
+
);
|
|
32885
33993
|
}
|
|
32886
33994
|
function printInternalCheckCommandMessage(commandName) {
|
|
32887
33995
|
if (commandName === "params") {
|
|
@@ -33034,4 +34142,3 @@ export {
|
|
|
33034
34142
|
runForgeCadCli,
|
|
33035
34143
|
runForgeCommandDefinition
|
|
33036
34144
|
};
|
|
33037
|
-
//# sourceMappingURL=forgecad.js.map
|