forgecad 0.9.7 → 0.9.8
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/README.md +1 -0
- package/dist/assets/{AdminPage-DX0mpSZT.js → AdminPage-CXaVLMiV.js} +1 -1
- package/dist/assets/{BlogPage-CI_P0_Pf.js → BlogPage-Crpr3JjH.js} +1 -1
- package/dist/assets/{DocsPage-DLhIIZyJ.js → DocsPage-CNBKuitP.js} +2 -2
- package/dist/assets/{EditorApp-DfFT2Dn8.css → EditorApp-D11wL4Qn.css} +51 -0
- package/dist/assets/{EditorApp-BujZvuwX.js → EditorApp-DVMnXOmO.js} +151 -9
- package/dist/assets/{EmbedViewer-0S0qXKog.js → EmbedViewer-KXFLSnpo.js} +2 -2
- package/dist/assets/{LandingPageProofDriven-O_yMtAri.js → LandingPageProofDriven-2q2sn7aW.js} +1 -1
- package/dist/assets/{PricingPage-DGkX3Ahr.js → PricingPage-CVvgdv0i.js} +1 -1
- package/dist/assets/{SettingsPage-DBsqTB_y.js → SettingsPage-BVj1FtEv.js} +1 -1
- package/dist/assets/__vite-browser-external-Dhvy_jtL.js +4 -0
- package/dist/assets/{app-BE2nD6Yz.js → app-Dn4EwHhN.js} +707 -458
- package/dist/assets/cli/{render-iP9qh475.js → render-BI3gLMXz.js} +1011 -145
- package/dist/assets/constructionHistoryWorker-z9_LGiRd.js +42984 -0
- package/dist/assets/{evalWorker-Ds5U4xtN.js → evalWorker-CtO7GsJR.js} +42 -9
- package/dist/assets/{inspectWorker-Dll4eVyD.js → inspectWorker-BZ2CkQZr.js} +785 -111
- package/dist/assets/{manifold-sJ-axdXM.js → manifold-BVi4_OeB.js} +1 -1
- package/dist/assets/{manifold-DjYsd7A_.js → manifold-C6-sZYQN.js} +2 -2
- package/dist/assets/manifold-Cp_dCC7i.js +3018 -0
- package/dist/assets/{manifold-Bk26ViCr.js → manifold-DAzn2Fsa.js} +1 -1
- package/dist/assets/{renderSceneState-Bngp5MrQ.js → renderSceneState-BIvOkPK3.js} +1 -1
- package/dist/assets/{reportWorker-CU8RZ4O0.js → reportWorker-Bz9tGiHb.js} +42 -9
- package/dist/assets/{sectionPlaneMath-BdTjyVfs.js → scalar-sampling-budget-iBAeF8RM.js} +483 -71
- package/dist/cli/render.html +1 -1
- package/dist/docs/index.html +1 -1
- package/dist/docs-raw/CLI.md +10 -10
- package/dist/docs-raw/coding-best-practices.md +1 -1
- package/dist/docs-raw/guides/inspection-bundles.md +77 -19
- package/dist/docs-raw/guides/skill-maintenance.md +1 -1
- package/dist/docs-raw/runbook.md +2 -2
- package/dist/docs-raw/skills/forgecad-make-a-model.md +11 -0
- package/dist/docs-raw/skills/forgecad-render-inspect.md +12 -6
- package/dist/docs-raw/skills/index.md +1 -1
- package/dist/index.html +1 -1
- package/dist/sitemap.xml +6 -6
- package/dist-cli/forgecad.js +596 -354
- package/dist-cli/forgecad.js.map +1 -1
- package/dist-skill/CONTEXT.md +77 -19
- package/dist-skill/docs/CLI.md +10 -10
- package/dist-skill/docs/guides/inspection-bundles.md +77 -19
- package/dist-skill/docs-dev/CLI.md +10 -10
- package/dist-skill/docs-dev/coding-best-practices.md +1 -1
- package/dist-skill/docs-dev/guides/inspection-bundles.md +77 -19
- package/dist-skill/docs-dev/guides/skill-maintenance.md +1 -1
- package/dist-skill/library/forgecad-make-a-model/SKILL.md +11 -0
- package/dist-skill/library/forgecad-render-inspect/SKILL.md +12 -6
- package/package.json +6 -3
package/dist-cli/forgecad.js
CHANGED
|
@@ -58033,6 +58033,16 @@ var RENDER_STYLE_OPTIONS = [
|
|
|
58033
58033
|
id: "glass",
|
|
58034
58034
|
label: "Glass",
|
|
58035
58035
|
description: "Showcase lighting for authored transparent materials."
|
|
58036
|
+
},
|
|
58037
|
+
{
|
|
58038
|
+
id: "precision",
|
|
58039
|
+
label: "Precision",
|
|
58040
|
+
description: "Dense, thin surface-field contours for technical inspection."
|
|
58041
|
+
},
|
|
58042
|
+
{
|
|
58043
|
+
id: "hybrid",
|
|
58044
|
+
label: "Hybrid",
|
|
58045
|
+
description: "Topo-led surface contours with a quiet orthogonal field."
|
|
58036
58046
|
}
|
|
58037
58047
|
];
|
|
58038
58048
|
var RENDER_STYLE_IDS = new Set(RENDER_STYLE_OPTIONS.map((option) => option.id));
|
|
@@ -82694,6 +82704,16 @@ function createForgeRuntimeModule(bindings) {
|
|
|
82694
82704
|
return runtime;
|
|
82695
82705
|
}
|
|
82696
82706
|
|
|
82707
|
+
// src/forge/script-runtime/importFinalizer.ts
|
|
82708
|
+
function finalizeForgeJsImport(moduleExports, importedDims) {
|
|
82709
|
+
if (moduleExports instanceof Assembly) {
|
|
82710
|
+
return new ImportedAssembly(moduleExports, moduleExports.getReferences());
|
|
82711
|
+
}
|
|
82712
|
+
if (!(moduleExports instanceof Shape)) return moduleExports;
|
|
82713
|
+
if (importedDims.length === 0) return moduleExports;
|
|
82714
|
+
return setShapeDimensions(moduleExports, [...getShapeDimensions(moduleExports), ...importedDims]);
|
|
82715
|
+
}
|
|
82716
|
+
|
|
82697
82717
|
// src/forge/sdf/sdfShader.ts
|
|
82698
82718
|
function classifySdfPreviewNode(node) {
|
|
82699
82719
|
switch (node.kind) {
|
|
@@ -82871,7 +82891,7 @@ function isRenderableEntryResult(value) {
|
|
|
82871
82891
|
return containsRenderableLeaf(value, /* @__PURE__ */ new WeakSet());
|
|
82872
82892
|
}
|
|
82873
82893
|
function containsRenderableLeaf(value, seen) {
|
|
82874
|
-
if (value instanceof Shape || value instanceof Sketch || value instanceof SdfShape || value instanceof ShapeGroup || value instanceof GCodeBuilder || value instanceof Assembly || value instanceof SolvedAssembly) {
|
|
82894
|
+
if (value instanceof Shape || value instanceof Sketch || value instanceof SdfShape || value instanceof ShapeGroup || value instanceof GCodeBuilder || value instanceof Assembly || value instanceof ImportedAssembly || value instanceof SolvedAssembly) {
|
|
82875
82895
|
return true;
|
|
82876
82896
|
}
|
|
82877
82897
|
if (!value || typeof value !== "object") return false;
|
|
@@ -82893,11 +82913,6 @@ function hasExplicitModuleExports(exportsValue, initialExportsRef) {
|
|
|
82893
82913
|
const keys = Object.keys(exportsValue);
|
|
82894
82914
|
return keys.some((key) => key !== "__esModule");
|
|
82895
82915
|
}
|
|
82896
|
-
function finalizeForgeJsImport(moduleExports, importedDims) {
|
|
82897
|
-
if (!(moduleExports instanceof Shape)) return moduleExports;
|
|
82898
|
-
if (importedDims.length === 0) return moduleExports;
|
|
82899
|
-
return setShapeDimensions(moduleExports, [...getShapeDimensions(moduleExports), ...importedDims]);
|
|
82900
|
-
}
|
|
82901
82916
|
function mapScriptResultToScene(args) {
|
|
82902
82917
|
const objects = [];
|
|
82903
82918
|
const shapeDimensions = [];
|
|
@@ -83024,6 +83039,19 @@ function mapScriptResultToScene(args) {
|
|
|
83024
83039
|
value.solve().toSceneObjects().forEach((item, index) => processNamedItem(item, `${name}.${index + 1}`, `${index + 1}`, name, treePath, inheritedTags));
|
|
83025
83040
|
return;
|
|
83026
83041
|
}
|
|
83042
|
+
if (value instanceof ImportedAssembly) {
|
|
83043
|
+
const group2 = value.toGroup();
|
|
83044
|
+
group2.children.forEach((child, index) => {
|
|
83045
|
+
flattenGroupChild(
|
|
83046
|
+
child,
|
|
83047
|
+
groupChildLabel(group2, name, index),
|
|
83048
|
+
name,
|
|
83049
|
+
[...treePath, shapeGroupChildSegment(group2, index)],
|
|
83050
|
+
mergeSceneTags(inheritedTags, group2.tagsForChild(index))
|
|
83051
|
+
);
|
|
83052
|
+
});
|
|
83053
|
+
return;
|
|
83054
|
+
}
|
|
83027
83055
|
if (value instanceof SolvedAssembly) {
|
|
83028
83056
|
value.toSceneObjects().forEach((item, index) => processNamedItem(item, `${name}.${index + 1}`, `${index + 1}`, name, treePath, inheritedTags));
|
|
83029
83057
|
return;
|
|
@@ -83178,6 +83206,12 @@ function mapScriptResultToScene(args) {
|
|
|
83178
83206
|
const label = `Object ${index + 1}`;
|
|
83179
83207
|
processNamedItem(item, label, label);
|
|
83180
83208
|
});
|
|
83209
|
+
} else if (result instanceof ImportedAssembly) {
|
|
83210
|
+
const group2 = result.toGroup();
|
|
83211
|
+
group2.children.forEach((child, index) => {
|
|
83212
|
+
const label = rootGroupChildLabel(group2, index);
|
|
83213
|
+
flattenGroupChild(child, label, void 0, [label], group2.tagsForChild(index));
|
|
83214
|
+
});
|
|
83181
83215
|
} else if (result instanceof ShapeGroup) {
|
|
83182
83216
|
result.children.forEach((child, i) => {
|
|
83183
83217
|
const label = rootGroupChildLabel(result, i);
|
|
@@ -83216,7 +83250,7 @@ function mapScriptResultToScene(args) {
|
|
|
83216
83250
|
}
|
|
83217
83251
|
processRenderableTree(item, label, label);
|
|
83218
83252
|
});
|
|
83219
|
-
} else if (result !== null && typeof result === "object" && !Array.isArray(result) && !(result instanceof Shape) && !(result instanceof Sketch) && !(result instanceof SdfShape) && !(result instanceof ShapeGroup) && !(result instanceof GCodeBuilder) && !(result instanceof Assembly) && !(result instanceof SolvedAssembly)) {
|
|
83253
|
+
} else if (result !== null && typeof result === "object" && !Array.isArray(result) && !(result instanceof Shape) && !(result instanceof Sketch) && !(result instanceof SdfShape) && !(result instanceof ShapeGroup) && !(result instanceof GCodeBuilder) && !(result instanceof Assembly) && !(result instanceof ImportedAssembly) && !(result instanceof SolvedAssembly)) {
|
|
83220
83254
|
const obj = result;
|
|
83221
83255
|
const defaultValue = obj.default;
|
|
83222
83256
|
if (defaultValue && isRenderableEntryResult(defaultValue)) {
|
|
@@ -83301,7 +83335,7 @@ Fix one:
|
|
|
83301
83335
|
sketch: objects.length === 1 ? objects[0].sketch : null,
|
|
83302
83336
|
objects,
|
|
83303
83337
|
extraDimensions: shapeDimensions,
|
|
83304
|
-
error: objects.length > 0 || args.allowEmptyResult ? null : "No renderable objects found. Return a Shape, Sketch, SdfShape, array, or object tree containing renderable values."
|
|
83338
|
+
error: objects.length > 0 || args.allowEmptyResult ? null : "No renderable objects found. Return a Shape, Sketch, SdfShape, Assembly, ImportedAssembly, array, or object tree containing renderable values."
|
|
83305
83339
|
};
|
|
83306
83340
|
}
|
|
83307
83341
|
|
|
@@ -83593,6 +83627,7 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
|
|
|
83593
83627
|
composeChain,
|
|
83594
83628
|
assembly,
|
|
83595
83629
|
Assembly,
|
|
83630
|
+
ImportedAssembly,
|
|
83596
83631
|
port: () => {
|
|
83597
83632
|
throw new Error("port() has been renamed to connector(). Update your script.");
|
|
83598
83633
|
},
|
|
@@ -84003,6 +84038,11 @@ import { execSync, spawn } from "child_process";
|
|
|
84003
84038
|
import { existsSync, realpathSync } from "fs";
|
|
84004
84039
|
import { dirname, resolve } from "path";
|
|
84005
84040
|
import { fileURLToPath } from "url";
|
|
84041
|
+
|
|
84042
|
+
// cli/default-backend.ts
|
|
84043
|
+
var CLI_DEFAULT_BACKEND = "manifold";
|
|
84044
|
+
|
|
84045
|
+
// cli/package-runtime.ts
|
|
84006
84046
|
function isCompiledBinary() {
|
|
84007
84047
|
return import.meta.url.includes("$bunfs");
|
|
84008
84048
|
}
|
|
@@ -84040,7 +84080,10 @@ function isDirectSourceCliRun(metaUrl, sourceEntryPath) {
|
|
|
84040
84080
|
}
|
|
84041
84081
|
function runDirectCliMain(metaUrl, sourceEntryPath, run) {
|
|
84042
84082
|
if (!isDirectSourceCliRun(metaUrl, sourceEntryPath)) return;
|
|
84043
|
-
Promise.resolve().then(
|
|
84083
|
+
Promise.resolve().then(() => {
|
|
84084
|
+
setActiveBackend(CLI_DEFAULT_BACKEND);
|
|
84085
|
+
return run();
|
|
84086
|
+
}).catch((error) => {
|
|
84044
84087
|
const message = error instanceof Error ? error.message : String(error);
|
|
84045
84088
|
console.error(message);
|
|
84046
84089
|
process.exit(1);
|
|
@@ -86273,7 +86316,15 @@ return [
|
|
|
86273
86316
|
];
|
|
86274
86317
|
`
|
|
86275
86318
|
};
|
|
86276
|
-
const
|
|
86319
|
+
const previousBackend = getActiveBackend();
|
|
86320
|
+
setActiveBackend("truck");
|
|
86321
|
+
let result = null;
|
|
86322
|
+
try {
|
|
86323
|
+
result = runScript(files["main.forge.js"], "main.forge.js", files);
|
|
86324
|
+
} finally {
|
|
86325
|
+
setActiveBackend(previousBackend);
|
|
86326
|
+
}
|
|
86327
|
+
assert2(result, "runScript did not return a result");
|
|
86277
86328
|
assert2.equal(result.error, null, `runScript failed: ${result.error ?? "unknown error"}`);
|
|
86278
86329
|
const exactManifest = buildBrepExportManifest(result.objects);
|
|
86279
86330
|
assert2.equal(exactManifest.objects.length, 0, "Broad native edge finishes must not export as their unfinished base shape");
|
|
@@ -89390,14 +89441,36 @@ function listExampleArtifacts() {
|
|
|
89390
89441
|
}
|
|
89391
89442
|
|
|
89392
89443
|
// cli/sketch-svg.ts
|
|
89393
|
-
function
|
|
89444
|
+
function paletteForTheme(theme) {
|
|
89445
|
+
if (theme === "cad-section") {
|
|
89446
|
+
return {
|
|
89447
|
+
background: "#f7f7f4",
|
|
89448
|
+
fill: "#e6e8eb",
|
|
89449
|
+
stroke: "#20262e",
|
|
89450
|
+
hatchStroke: "#a8afb7",
|
|
89451
|
+
strokeThin: 0.22,
|
|
89452
|
+
strokeBold: 0.55,
|
|
89453
|
+
useHatch: true
|
|
89454
|
+
};
|
|
89455
|
+
}
|
|
89456
|
+
return {
|
|
89457
|
+
background: "#2a2a2a",
|
|
89458
|
+
fill: "#4488cc",
|
|
89459
|
+
stroke: "#224466",
|
|
89460
|
+
hatchStroke: "#224466",
|
|
89461
|
+
strokeThin: 0.3,
|
|
89462
|
+
strokeBold: 0.8,
|
|
89463
|
+
useHatch: false
|
|
89464
|
+
};
|
|
89465
|
+
}
|
|
89466
|
+
function edgesStrokeAttrs(preset, palette) {
|
|
89394
89467
|
switch (preset) {
|
|
89395
89468
|
case "off":
|
|
89396
89469
|
return 'stroke="none"';
|
|
89397
89470
|
case "bold":
|
|
89398
|
-
return
|
|
89471
|
+
return `stroke="${palette.stroke}" stroke-width="${palette.strokeBold}"`;
|
|
89399
89472
|
default:
|
|
89400
|
-
return
|
|
89473
|
+
return `stroke="${palette.stroke}" stroke-width="${palette.strokeThin}"`;
|
|
89401
89474
|
}
|
|
89402
89475
|
}
|
|
89403
89476
|
function combineBounds(left, right) {
|
|
@@ -89412,6 +89485,19 @@ function escapeAttribute(value) {
|
|
|
89412
89485
|
function polygonPath(poly) {
|
|
89413
89486
|
return `${poly.map((point2, index) => `${index === 0 ? "M" : "L"}${point2[0].toFixed(3)},${(-point2[1]).toFixed(3)}`).join(" ")} Z`;
|
|
89414
89487
|
}
|
|
89488
|
+
function compoundPolygonPath(polygons) {
|
|
89489
|
+
return polygons.map(polygonPath).join(" ");
|
|
89490
|
+
}
|
|
89491
|
+
function hatchDefs(palette) {
|
|
89492
|
+
if (!palette.useHatch) return "";
|
|
89493
|
+
return ` <defs>
|
|
89494
|
+
<pattern id="forge-section-hatch" patternUnits="userSpaceOnUse" width="4" height="4" patternTransform="rotate(45)">
|
|
89495
|
+
<rect width="4" height="4" fill="${palette.fill}"/>
|
|
89496
|
+
<line x1="0" y1="0" x2="0" y2="4" stroke="${palette.hatchStroke}" stroke-width="0.28"/>
|
|
89497
|
+
</pattern>
|
|
89498
|
+
</defs>
|
|
89499
|
+
`;
|
|
89500
|
+
}
|
|
89415
89501
|
function prepareEntry(entry) {
|
|
89416
89502
|
const polygons = entry.sketch.toPolygons();
|
|
89417
89503
|
if (polygons.length === 0) {
|
|
@@ -89433,7 +89519,9 @@ function buildSketchSvgDocument(entries, options = {}) {
|
|
|
89433
89519
|
throw new Error("Sketch SVG export requires at least one sketch payload.");
|
|
89434
89520
|
}
|
|
89435
89521
|
const edges = options.edges ?? "thin";
|
|
89436
|
-
const
|
|
89522
|
+
const palette = paletteForTheme(options.theme ?? "debug");
|
|
89523
|
+
const strokeAttrs = edgesStrokeAttrs(edges, palette);
|
|
89524
|
+
const fill = palette.useHatch ? "url(#forge-section-hatch)" : palette.fill;
|
|
89437
89525
|
const prepared = entries.map(prepareEntry);
|
|
89438
89526
|
const combinedBounds = prepared.reduce((acc, entry) => combineBounds(acc, entry.bounds), prepared[0].bounds);
|
|
89439
89527
|
const margin = 2;
|
|
@@ -89446,17 +89534,15 @@ function buildSketchSvgDocument(entries, options = {}) {
|
|
|
89446
89534
|
let pathCount = 0;
|
|
89447
89535
|
const body = prepared.map((entry) => {
|
|
89448
89536
|
const label = entry.name ? ` data-name="${escapeAttribute(entry.name)}"` : "";
|
|
89449
|
-
|
|
89450
|
-
pathCount += 1;
|
|
89451
|
-
return ` <path d="${polygonPath(poly)}" fill="#4488cc" ${strokeAttrs}/>`;
|
|
89452
|
-
}).join("\n");
|
|
89537
|
+
pathCount += entry.polygons.length;
|
|
89453
89538
|
return ` <g${label}>
|
|
89454
|
-
${
|
|
89539
|
+
<path d="${compoundPolygonPath(entry.polygons)}" fill="${fill}" fill-rule="evenodd" ${strokeAttrs}/>
|
|
89455
89540
|
</g>`;
|
|
89456
89541
|
}).join("\n");
|
|
89457
89542
|
return {
|
|
89458
89543
|
svg: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="${minX.toFixed(1)} ${(-maxY).toFixed(1)} ${width.toFixed(1)} ${height.toFixed(1)}" width="${Math.max(width * 4, 400)}" height="${Math.max(height * 4, 400)}">
|
|
89459
|
-
<rect x="${minX.toFixed(1)}" y="${(-maxY).toFixed(1)}" width="${width.toFixed(1)}" height="${height.toFixed(1)}" fill="
|
|
89544
|
+
<rect x="${minX.toFixed(1)}" y="${(-maxY).toFixed(1)}" width="${width.toFixed(1)}" height="${height.toFixed(1)}" fill="${palette.background}"/>
|
|
89545
|
+
${hatchDefs(palette)}
|
|
89460
89546
|
${body}
|
|
89461
89547
|
</svg>`,
|
|
89462
89548
|
width,
|
|
@@ -90257,9 +90343,11 @@ var INSPECT_CHANNEL_DESCRIPTIONS = {
|
|
|
90257
90343
|
rgb: "Canonical viewport RGB renders.",
|
|
90258
90344
|
depth: "Packed linear view-depth maps.",
|
|
90259
90345
|
normals: "View-space normal maps.",
|
|
90346
|
+
zebra: "Reflective zebra stripes for surface-continuity inspection.",
|
|
90260
90347
|
roughness: "Surface roughness and sharp-feature heatmap.",
|
|
90261
90348
|
mask: "Object identity masks.",
|
|
90262
90349
|
connectivity: "Physical connected-component masks.",
|
|
90350
|
+
floating: "Disconnected floating body highlights.",
|
|
90263
90351
|
distance: "Rooted physical-component distance heatmap.",
|
|
90264
90352
|
collisions: "Ghosted source objects with highlighted intersection volumes.",
|
|
90265
90353
|
thickness: "Local wall-thickness heatmap.",
|
|
@@ -90269,16 +90357,18 @@ var ALL_INSPECT_CHANNELS = [
|
|
|
90269
90357
|
"rgb",
|
|
90270
90358
|
"depth",
|
|
90271
90359
|
"normals",
|
|
90360
|
+
"zebra",
|
|
90272
90361
|
"roughness",
|
|
90273
90362
|
"mask",
|
|
90274
90363
|
"connectivity",
|
|
90364
|
+
"floating",
|
|
90275
90365
|
"distance",
|
|
90276
90366
|
"collisions",
|
|
90277
90367
|
"thickness",
|
|
90278
90368
|
"section"
|
|
90279
90369
|
];
|
|
90280
90370
|
var SUPPORTED_INSPECT_CHANNELS = new Set(ALL_INSPECT_CHANNELS);
|
|
90281
|
-
var RENDER_STYLES = /* @__PURE__ */ new Set(["classic", "studio", "fast", "glass"]);
|
|
90371
|
+
var RENDER_STYLES = /* @__PURE__ */ new Set(["classic", "studio", "fast", "glass", "precision", "hybrid"]);
|
|
90282
90372
|
function resolveChromePath(explicitPath) {
|
|
90283
90373
|
if (explicitPath && existsSync5(explicitPath)) return explicitPath;
|
|
90284
90374
|
return CHROME_PATHS.find((p2) => existsSync5(p2)) || null;
|
|
@@ -90303,7 +90393,7 @@ Options:
|
|
|
90303
90393
|
--focus [names] Focus: no arg hides mocks; comma-separated names/globs show only those
|
|
90304
90394
|
--hide <names> Hide comma-separated object names/globs, show everything else
|
|
90305
90395
|
--background <color> Canvas background override
|
|
90306
|
-
--render-style <classic|studio|fast|glass>
|
|
90396
|
+
--render-style <classic|studio|fast|glass|precision|hybrid>
|
|
90307
90397
|
Visual render style (default: classic)
|
|
90308
90398
|
--render-mode <solid|wireframe> Render as shaded solid (default) or wireframe only
|
|
90309
90399
|
--edges <off|thin|bold> Edge overlay preset for solid mode (default: off)
|
|
@@ -90340,14 +90430,16 @@ function splitCsv(value) {
|
|
|
90340
90430
|
}
|
|
90341
90431
|
function parseRenderStyle(value) {
|
|
90342
90432
|
if (RENDER_STYLES.has(value)) return value;
|
|
90343
|
-
throw new Error(`--render-style must be 'classic', 'studio', 'fast', or '
|
|
90433
|
+
throw new Error(`--render-style must be 'classic', 'studio', 'fast', 'glass', 'precision', or 'hybrid' (got '${value}')`);
|
|
90344
90434
|
}
|
|
90345
90435
|
function sceneSpecHasCamera(sceneSpec) {
|
|
90346
90436
|
let parsed;
|
|
90347
90437
|
try {
|
|
90348
90438
|
parsed = JSON.parse(sceneSpec);
|
|
90349
90439
|
} catch (err2) {
|
|
90350
|
-
throw new Error(
|
|
90440
|
+
throw new Error(
|
|
90441
|
+
`--scene must be valid JSON when combined with --camera or --view: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
90442
|
+
);
|
|
90351
90443
|
}
|
|
90352
90444
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed) && Object.prototype.hasOwnProperty.call(parsed, "camera");
|
|
90353
90445
|
}
|
|
@@ -90530,7 +90622,9 @@ function parseRenderCliOptions(argv) {
|
|
|
90530
90622
|
throw new Error("Cannot use --view with --scene JSON that includes camera. Remove the camera field from --scene or use --camera.");
|
|
90531
90623
|
}
|
|
90532
90624
|
if ((cameraSpec || cameras.length > 0) && sceneHasCamera) {
|
|
90533
|
-
throw new Error(
|
|
90625
|
+
throw new Error(
|
|
90626
|
+
"Cannot use --camera with --scene JSON that includes camera. Remove the camera field from --scene or use --scene by itself."
|
|
90627
|
+
);
|
|
90534
90628
|
}
|
|
90535
90629
|
if (cameras.length === 0 && !cameraSpec && !sceneSpec && !viewName) {
|
|
90536
90630
|
cameras.push("iso");
|
|
@@ -90572,8 +90666,8 @@ Options:
|
|
|
90572
90666
|
--min-thickness <mm> Critical thickness threshold (default: 1.2)
|
|
90573
90667
|
--warn-thickness <mm> Warning thickness threshold (default: 2.0)
|
|
90574
90668
|
--max-thickness <mm> Thick/blue heatmap threshold (default: 6.0)
|
|
90575
|
-
--thickness-samples <n> Max thickness point samples per object (default
|
|
90576
|
-
--roughness-samples <n> Max roughness point samples per object (default
|
|
90669
|
+
--thickness-samples <n> Max thickness point samples per object (overrides the default scene budget)
|
|
90670
|
+
--roughness-samples <n> Max roughness point samples per object (overrides the default scene budget)
|
|
90577
90671
|
--size <px> Image size in pixels (default: ${DEFAULT_SIZE})
|
|
90578
90672
|
--quality <default|live|high> Mesh/render quality (default: default)
|
|
90579
90673
|
--force Replace an existing bundle directory
|
|
@@ -91014,6 +91108,16 @@ function buildInspectManifest({ options, result, scriptPath, projectRoot, emitte
|
|
|
91014
91108
|
views: buildViewPathEntries(emittedPaths.normals)
|
|
91015
91109
|
};
|
|
91016
91110
|
}
|
|
91111
|
+
if (emittedPaths.zebra) {
|
|
91112
|
+
channels.zebra = {
|
|
91113
|
+
format: "png",
|
|
91114
|
+
encoding: "rgb8-reflected-zebra-stripes",
|
|
91115
|
+
coordinateSpace: "camera-view",
|
|
91116
|
+
background: { rgb: [0, 0, 0], stripe: null },
|
|
91117
|
+
decode: "procedural reflected stripe visualization from camera-view normals; use visually to spot surface-continuity breaks",
|
|
91118
|
+
views: buildViewPathEntries(emittedPaths.zebra)
|
|
91119
|
+
};
|
|
91120
|
+
}
|
|
91017
91121
|
if (emittedPaths.roughness) {
|
|
91018
91122
|
channels.roughness = {
|
|
91019
91123
|
format: "png",
|
|
@@ -91023,6 +91127,7 @@ function buildInspectManifest({ options, result, scriptPath, projectRoot, emitte
|
|
|
91023
91127
|
decode: "smooth and moderate triangles render as a translucent shadow; colored regions mark triangles adjacent to sharp, harsh, boundary, or non-manifold mesh edges",
|
|
91024
91128
|
method: result.roughness?.method,
|
|
91025
91129
|
options: result.roughness?.options,
|
|
91130
|
+
sampleBudget: result.roughness?.sampleBudget,
|
|
91026
91131
|
style: result.roughness?.style,
|
|
91027
91132
|
objectCount: result.roughness?.objectCount ?? 0,
|
|
91028
91133
|
objects: result.roughness?.objects ?? [],
|
|
@@ -91066,6 +91171,28 @@ function buildInspectManifest({ options, result, scriptPath, projectRoot, emitte
|
|
|
91066
91171
|
views: buildViewPathEntries(emittedPaths.connectivity)
|
|
91067
91172
|
};
|
|
91068
91173
|
}
|
|
91174
|
+
if (emittedPaths.floating) {
|
|
91175
|
+
channels.floating = {
|
|
91176
|
+
format: "png",
|
|
91177
|
+
encoding: "rgb8-floating-body-highlight",
|
|
91178
|
+
backgroundColor: [0, 0, 0],
|
|
91179
|
+
decode: "only bodies without a mesh-contact path to the ground plane render in the highlight color; black means background or ground-reachable geometry",
|
|
91180
|
+
method: result.floating?.method,
|
|
91181
|
+
options: result.floating?.options,
|
|
91182
|
+
rootComponentIndex: result.floating?.rootComponentIndex ?? null,
|
|
91183
|
+
style: result.floating?.style,
|
|
91184
|
+
objectCount: result.floating?.objectCount ?? 0,
|
|
91185
|
+
componentCount: result.floating?.componentCount ?? 0,
|
|
91186
|
+
floatingComponentCount: result.floating?.floatingComponentCount ?? 0,
|
|
91187
|
+
floatingObjectCount: result.floating?.floatingObjectCount ?? 0,
|
|
91188
|
+
floatingBodyCount: result.floating?.floatingBodyCount ?? 0,
|
|
91189
|
+
components: result.floating?.components ?? [],
|
|
91190
|
+
objects: result.floating?.objects ?? [],
|
|
91191
|
+
contacts: result.floating?.contacts ?? [],
|
|
91192
|
+
warnings: result.floating?.warnings ?? [],
|
|
91193
|
+
views: buildViewPathEntries(emittedPaths.floating)
|
|
91194
|
+
};
|
|
91195
|
+
}
|
|
91069
91196
|
if (emittedPaths.distance) {
|
|
91070
91197
|
channels.distance = {
|
|
91071
91198
|
format: "png",
|
|
@@ -91099,6 +91226,13 @@ function buildInspectManifest({ options, result, scriptPath, projectRoot, emitte
|
|
|
91099
91226
|
method: result.collisions?.method,
|
|
91100
91227
|
options: result.collisions?.options,
|
|
91101
91228
|
broadphase: result.collisions?.broadphase,
|
|
91229
|
+
candidatePairCount: result.collisions?.candidatePairCount ?? 0,
|
|
91230
|
+
bboxVolumePrunedPairCount: result.collisions?.bboxVolumePrunedPairCount ?? 0,
|
|
91231
|
+
testedPairCount: result.collisions?.testedPairCount ?? 0,
|
|
91232
|
+
skippedPairCount: result.collisions?.skippedPairCount ?? 0,
|
|
91233
|
+
pairLimitSkippedPairCount: result.collisions?.pairLimitSkippedPairCount ?? 0,
|
|
91234
|
+
timeBudgetSkippedPairCount: result.collisions?.timeBudgetSkippedPairCount ?? 0,
|
|
91235
|
+
exactCheckMs: result.collisions?.exactCheckMs ?? 0,
|
|
91102
91236
|
style: result.collisions?.style,
|
|
91103
91237
|
objectCount: result.collisions?.objectCount ?? 0,
|
|
91104
91238
|
collisionCount: result.collisions?.collisionCount ?? 0,
|
|
@@ -91114,9 +91248,10 @@ function buildInspectManifest({ options, result, scriptPath, projectRoot, emitte
|
|
|
91114
91248
|
encoding: "rgb8-thickness-heatmap",
|
|
91115
91249
|
units: "model",
|
|
91116
91250
|
backgroundColor: [0, 0, 0],
|
|
91117
|
-
decode: "surface heatmap from mesh-normal raycast thickness; red <= minThickness, orange <= warnThickness, green-to-blue through maxThickness, gray unresolved",
|
|
91251
|
+
decode: "surface heatmap from contact-aware mesh-normal raycast thickness; red <= minThickness, orange <= warnThickness, green-to-blue through maxThickness, gray unresolved",
|
|
91118
91252
|
method: result.thickness?.method,
|
|
91119
91253
|
options: result.thickness?.options,
|
|
91254
|
+
sampleBudget: result.thickness?.sampleBudget,
|
|
91120
91255
|
style: result.thickness?.style,
|
|
91121
91256
|
objectCount: result.thickness?.objectCount ?? 0,
|
|
91122
91257
|
objects: result.thickness?.objects ?? [],
|
|
@@ -91536,10 +91671,10 @@ async function runRenderInspectCli(argv = process.argv.slice(2)) {
|
|
|
91536
91671
|
}
|
|
91537
91672
|
console.log(`[inspect] Wrote ${channel} channel files.`);
|
|
91538
91673
|
};
|
|
91539
|
-
const writeChannelJson = async (channel, filename, source) => {
|
|
91674
|
+
const writeChannelJson = async (channel, filename, source, { pretty = true } = {}) => {
|
|
91540
91675
|
if (!source) return null;
|
|
91541
91676
|
const relPath = toPortablePath(join5("channels", channel, filename));
|
|
91542
|
-
await writeFile(join5(tempDir, relPath), `${JSON.stringify(source, null, 2)}
|
|
91677
|
+
await writeFile(join5(tempDir, relPath), `${pretty ? JSON.stringify(source, null, 2) : JSON.stringify(source)}
|
|
91543
91678
|
`, "utf-8");
|
|
91544
91679
|
return relPath;
|
|
91545
91680
|
};
|
|
@@ -91552,9 +91687,14 @@ async function runRenderInspectCli(argv = process.argv.slice(2)) {
|
|
|
91552
91687
|
if (requested.has("normals")) {
|
|
91553
91688
|
await writeViewChannel("normals", result.normals);
|
|
91554
91689
|
}
|
|
91690
|
+
if (requested.has("zebra")) {
|
|
91691
|
+
await writeViewChannel("zebra", result.zebra);
|
|
91692
|
+
}
|
|
91555
91693
|
if (requested.has("roughness")) {
|
|
91556
91694
|
await writeViewChannel("roughness", result.roughness?.views);
|
|
91557
|
-
emittedPaths.roughnessPointCloud = await writeChannelJson("roughness", "point-cloud.json", result.roughness?.pointCloud
|
|
91695
|
+
emittedPaths.roughnessPointCloud = await writeChannelJson("roughness", "point-cloud.json", result.roughness?.pointCloud, {
|
|
91696
|
+
pretty: false
|
|
91697
|
+
});
|
|
91558
91698
|
}
|
|
91559
91699
|
if (requested.has("mask")) {
|
|
91560
91700
|
await writeViewChannel("mask", result.mask?.views);
|
|
@@ -91562,6 +91702,9 @@ async function runRenderInspectCli(argv = process.argv.slice(2)) {
|
|
|
91562
91702
|
if (requested.has("connectivity")) {
|
|
91563
91703
|
await writeViewChannel("connectivity", result.connectivity?.views);
|
|
91564
91704
|
}
|
|
91705
|
+
if (requested.has("floating")) {
|
|
91706
|
+
await writeViewChannel("floating", result.floating?.views);
|
|
91707
|
+
}
|
|
91565
91708
|
if (requested.has("distance")) {
|
|
91566
91709
|
await writeViewChannel("distance", result.distance?.views);
|
|
91567
91710
|
}
|
|
@@ -91570,7 +91713,9 @@ async function runRenderInspectCli(argv = process.argv.slice(2)) {
|
|
|
91570
91713
|
}
|
|
91571
91714
|
if (requested.has("thickness")) {
|
|
91572
91715
|
await writeViewChannel("thickness", result.thickness?.views);
|
|
91573
|
-
emittedPaths.thicknessPointCloud = await writeChannelJson("thickness", "point-cloud.json", result.thickness?.pointCloud
|
|
91716
|
+
emittedPaths.thicknessPointCloud = await writeChannelJson("thickness", "point-cloud.json", result.thickness?.pointCloud, {
|
|
91717
|
+
pretty: false
|
|
91718
|
+
});
|
|
91574
91719
|
}
|
|
91575
91720
|
if (requested.has("section")) {
|
|
91576
91721
|
console.log("[inspect] Writing section channel files...");
|
|
@@ -92783,10 +92928,16 @@ function analyzeCollisionIntersections(entries, rawOptions = {}) {
|
|
|
92783
92928
|
const collisions = [];
|
|
92784
92929
|
const preparedEntries = entries.map((entry) => prepareEntry2(entry));
|
|
92785
92930
|
const { pairs: candidatePairs, bboxPairChecks } = collectCandidatePairs(preparedEntries, options);
|
|
92786
|
-
const
|
|
92787
|
-
const
|
|
92931
|
+
const exactCandidatePairs = candidatePairs.filter((pair) => pair.bboxOverlapVolume > options.minOverlapVolume);
|
|
92932
|
+
const bboxVolumePrunedPairCount = candidatePairs.length - exactCandidatePairs.length;
|
|
92933
|
+
const limitedCandidatePairs = options.maxCandidatePairs === null ? exactCandidatePairs : exactCandidatePairs.slice(0, options.maxCandidatePairs);
|
|
92934
|
+
const pairLimitSkippedPairCount = exactCandidatePairs.length - limitedCandidatePairs.length;
|
|
92788
92935
|
let testedPairCount = 0;
|
|
92789
92936
|
let timeBudgetStopped = false;
|
|
92937
|
+
const exactCheckedKeys = /* @__PURE__ */ new Set();
|
|
92938
|
+
const pairKey2 = (pair) => `${pair.sourceIndex}:${pair.targetIndex}`;
|
|
92939
|
+
const exactCandidateKeys = new Set(exactCandidatePairs.map(pairKey2));
|
|
92940
|
+
const limitedCandidateKeys = new Set(limitedCandidatePairs.map(pairKey2));
|
|
92790
92941
|
const exactStarted = performance.now();
|
|
92791
92942
|
for (const pair of limitedCandidatePairs) {
|
|
92792
92943
|
if (options.maxElapsedMs !== null && (options.maxElapsedMs <= 0 || performance.now() - exactStarted >= options.maxElapsedMs)) {
|
|
@@ -92794,6 +92945,7 @@ function analyzeCollisionIntersections(entries, rawOptions = {}) {
|
|
|
92794
92945
|
break;
|
|
92795
92946
|
}
|
|
92796
92947
|
testedPairCount += 1;
|
|
92948
|
+
exactCheckedKeys.add(pairKey2(pair));
|
|
92797
92949
|
const a = preparedEntries[pair.sourceIndex];
|
|
92798
92950
|
const b = preparedEntries[pair.targetIndex];
|
|
92799
92951
|
try {
|
|
@@ -92824,10 +92976,11 @@ function analyzeCollisionIntersections(entries, rawOptions = {}) {
|
|
|
92824
92976
|
const bboxCandidates = options.includeBBoxCandidates ? candidatePairs.map((pair, index) => {
|
|
92825
92977
|
const source = preparedEntries[pair.sourceIndex];
|
|
92826
92978
|
const target = preparedEntries[pair.targetIndex];
|
|
92827
|
-
const exactChecked =
|
|
92979
|
+
const exactChecked = exactCheckedKeys.has(pairKey2(pair));
|
|
92828
92980
|
let skippedBy;
|
|
92829
92981
|
if (!exactChecked) {
|
|
92830
|
-
|
|
92982
|
+
const key = pairKey2(pair);
|
|
92983
|
+
skippedBy = !exactCandidateKeys.has(key) ? "bbox-volume-threshold" : limitedCandidateKeys.has(key) ? "time-budget" : "pair-limit";
|
|
92831
92984
|
}
|
|
92832
92985
|
return {
|
|
92833
92986
|
index: index + 1,
|
|
@@ -92871,10 +93024,12 @@ function analyzeCollisionIntersections(entries, rawOptions = {}) {
|
|
|
92871
93024
|
method: "aabb-bvh",
|
|
92872
93025
|
allPairCount: preparedEntries.length * (preparedEntries.length - 1) / 2,
|
|
92873
93026
|
bboxPairChecks,
|
|
93027
|
+
bboxVolumePrunedPairCount,
|
|
92874
93028
|
booleanPairChecks: testedPairCount
|
|
92875
93029
|
},
|
|
92876
93030
|
objectCount: objects.length,
|
|
92877
93031
|
candidatePairCount: candidatePairs.length,
|
|
93032
|
+
bboxVolumePrunedPairCount,
|
|
92878
93033
|
testedPairCount,
|
|
92879
93034
|
skippedPairCount,
|
|
92880
93035
|
pairLimitSkippedPairCount,
|
|
@@ -92888,13 +93043,335 @@ function analyzeCollisionIntersections(entries, rawOptions = {}) {
|
|
|
92888
93043
|
};
|
|
92889
93044
|
}
|
|
92890
93045
|
|
|
93046
|
+
// src/forge/inspection/contact.ts
|
|
93047
|
+
var AXIS_NAMES = ["x", "y", "z"];
|
|
93048
|
+
function nearestBoundaryGap(a, b, axis) {
|
|
93049
|
+
return Math.min(Math.abs(a.max[axis] - b.min[axis]), Math.abs(b.max[axis] - a.min[axis]));
|
|
93050
|
+
}
|
|
93051
|
+
function hasPositiveGap(gaps) {
|
|
93052
|
+
return gaps[0] > 0 || gaps[1] > 0 || gaps[2] > 0;
|
|
93053
|
+
}
|
|
93054
|
+
function contactFromBBoxes(a, b, tolerance) {
|
|
93055
|
+
const gaps = aabbGaps(a, b);
|
|
93056
|
+
const largestGap = Math.max(gaps[0], gaps[1], gaps[2]);
|
|
93057
|
+
if (largestGap > tolerance) return { touching: false, gap: largestGap };
|
|
93058
|
+
const separatedAxes = gaps.map((gap, axis) => ({ gap, axis })).filter((entry) => entry.gap > 0);
|
|
93059
|
+
if (separatedAxes.length > 0) {
|
|
93060
|
+
const nearest2 = separatedAxes.reduce((best, entry) => entry.gap > best.gap ? entry : best, separatedAxes[0]);
|
|
93061
|
+
return { touching: true, gap: nearest2.gap, axis: AXIS_NAMES[nearest2.axis] };
|
|
93062
|
+
}
|
|
93063
|
+
const boundaryAxes = AXIS_NAMES.map((axisName, axis) => ({
|
|
93064
|
+
axis,
|
|
93065
|
+
axisName,
|
|
93066
|
+
gap: nearestBoundaryGap(a, b, axis)
|
|
93067
|
+
})).filter((entry) => entry.gap <= tolerance);
|
|
93068
|
+
if (boundaryAxes.length === 0) return { touching: false, gap: 0 };
|
|
93069
|
+
const nearest = boundaryAxes.reduce((best, entry) => entry.gap < best.gap ? entry : best, boundaryAxes[0]);
|
|
93070
|
+
return { touching: true, gap: nearest.gap, axis: nearest.axisName };
|
|
93071
|
+
}
|
|
93072
|
+
function isFiniteTriangle(positions, offset2) {
|
|
93073
|
+
for (let index = 0; index < 9; index += 1) {
|
|
93074
|
+
if (!Number.isFinite(positions[offset2 + index])) return false;
|
|
93075
|
+
}
|
|
93076
|
+
return true;
|
|
93077
|
+
}
|
|
93078
|
+
function triangleRef(positions, offset2) {
|
|
93079
|
+
if (!isFiniteTriangle(positions, offset2)) return null;
|
|
93080
|
+
const ax = positions[offset2];
|
|
93081
|
+
const ay = positions[offset2 + 1];
|
|
93082
|
+
const az = positions[offset2 + 2];
|
|
93083
|
+
const bx = positions[offset2 + 3];
|
|
93084
|
+
const by = positions[offset2 + 4];
|
|
93085
|
+
const bz = positions[offset2 + 5];
|
|
93086
|
+
const cx = positions[offset2 + 6];
|
|
93087
|
+
const cy = positions[offset2 + 7];
|
|
93088
|
+
const cz = positions[offset2 + 8];
|
|
93089
|
+
return {
|
|
93090
|
+
offset: offset2,
|
|
93091
|
+
min: [Math.min(ax, bx, cx), Math.min(ay, by, cy), Math.min(az, bz, cz)],
|
|
93092
|
+
max: [Math.max(ax, bx, cx), Math.max(ay, by, cy), Math.max(az, bz, cz)]
|
|
93093
|
+
};
|
|
93094
|
+
}
|
|
93095
|
+
function meshContactDataFor(entry) {
|
|
93096
|
+
const positions = entry.positions;
|
|
93097
|
+
if (!positions || positions.length < 9) return null;
|
|
93098
|
+
const triangleCount = Math.floor(positions.length / 9);
|
|
93099
|
+
const triangles = [];
|
|
93100
|
+
for (let triangleIndex = 0; triangleIndex < triangleCount; triangleIndex += 1) {
|
|
93101
|
+
const triangle = triangleRef(positions, triangleIndex * 9);
|
|
93102
|
+
if (triangle) triangles.push(triangle);
|
|
93103
|
+
}
|
|
93104
|
+
if (triangles.length === 0) return null;
|
|
93105
|
+
return {
|
|
93106
|
+
positions,
|
|
93107
|
+
vertexCount: triangleCount * 3,
|
|
93108
|
+
triangles
|
|
93109
|
+
};
|
|
93110
|
+
}
|
|
93111
|
+
function distSq2(px, py, pz, qx, qy, qz) {
|
|
93112
|
+
const dx = px - qx;
|
|
93113
|
+
const dy = py - qy;
|
|
93114
|
+
const dz = pz - qz;
|
|
93115
|
+
return dx * dx + dy * dy + dz * dz;
|
|
93116
|
+
}
|
|
93117
|
+
function pointSegmentDistanceSq(point2, start, end) {
|
|
93118
|
+
const dx = end[0] - start[0];
|
|
93119
|
+
const dy = end[1] - start[1];
|
|
93120
|
+
const dz = end[2] - start[2];
|
|
93121
|
+
const lengthSq = dx * dx + dy * dy + dz * dz;
|
|
93122
|
+
if (lengthSq <= 1e-20) return distSq2(point2[0], point2[1], point2[2], start[0], start[1], start[2]);
|
|
93123
|
+
const t = Math.max(0, Math.min(1, ((point2[0] - start[0]) * dx + (point2[1] - start[1]) * dy + (point2[2] - start[2]) * dz) / lengthSq));
|
|
93124
|
+
return distSq2(point2[0], point2[1], point2[2], start[0] + t * dx, start[1] + t * dy, start[2] + t * dz);
|
|
93125
|
+
}
|
|
93126
|
+
function pointTriangleDistanceSq(px, py, pz, ax, ay, az, bx, by, bz, cx, cy, cz) {
|
|
93127
|
+
const abx = bx - ax;
|
|
93128
|
+
const aby = by - ay;
|
|
93129
|
+
const abz = bz - az;
|
|
93130
|
+
const acx = cx - ax;
|
|
93131
|
+
const acy = cy - ay;
|
|
93132
|
+
const acz = cz - az;
|
|
93133
|
+
const apx = px - ax;
|
|
93134
|
+
const apy = py - ay;
|
|
93135
|
+
const apz = pz - az;
|
|
93136
|
+
const d1 = abx * apx + aby * apy + abz * apz;
|
|
93137
|
+
const d2 = acx * apx + acy * apy + acz * apz;
|
|
93138
|
+
if (d1 <= 0 && d2 <= 0) return distSq2(px, py, pz, ax, ay, az);
|
|
93139
|
+
const bpx = px - bx;
|
|
93140
|
+
const bpy = py - by;
|
|
93141
|
+
const bpz = pz - bz;
|
|
93142
|
+
const d3 = abx * bpx + aby * bpy + abz * bpz;
|
|
93143
|
+
const d4 = acx * bpx + acy * bpy + acz * bpz;
|
|
93144
|
+
if (d3 >= 0 && d4 <= d3) return distSq2(px, py, pz, bx, by, bz);
|
|
93145
|
+
const vc = d1 * d4 - d3 * d2;
|
|
93146
|
+
if (vc <= 0 && d1 >= 0 && d3 <= 0) {
|
|
93147
|
+
const v2 = d1 / (d1 - d3);
|
|
93148
|
+
return distSq2(px, py, pz, ax + v2 * abx, ay + v2 * aby, az + v2 * abz);
|
|
93149
|
+
}
|
|
93150
|
+
const cpx = px - cx;
|
|
93151
|
+
const cpy = py - cy;
|
|
93152
|
+
const cpz = pz - cz;
|
|
93153
|
+
const d5 = abx * cpx + aby * cpy + abz * cpz;
|
|
93154
|
+
const d6 = acx * cpx + acy * cpy + acz * cpz;
|
|
93155
|
+
if (d6 >= 0 && d5 <= d6) return distSq2(px, py, pz, cx, cy, cz);
|
|
93156
|
+
const vb = d5 * d2 - d1 * d6;
|
|
93157
|
+
if (vb <= 0 && d2 >= 0 && d6 <= 0) {
|
|
93158
|
+
const w2 = d2 / (d2 - d6);
|
|
93159
|
+
return distSq2(px, py, pz, ax + w2 * acx, ay + w2 * acy, az + w2 * acz);
|
|
93160
|
+
}
|
|
93161
|
+
const va = d3 * d6 - d5 * d4;
|
|
93162
|
+
if (va <= 0 && d4 - d3 >= 0 && d5 - d6 >= 0) {
|
|
93163
|
+
const bcx = cx - bx;
|
|
93164
|
+
const bcy = cy - by;
|
|
93165
|
+
const bcz = cz - bz;
|
|
93166
|
+
const w2 = (d4 - d3) / (d4 - d3 + d5 - d6);
|
|
93167
|
+
return distSq2(px, py, pz, bx + w2 * bcx, by + w2 * bcy, bz + w2 * bcz);
|
|
93168
|
+
}
|
|
93169
|
+
const barycentricTotal = va + vb + vc;
|
|
93170
|
+
if (!Number.isFinite(barycentricTotal) || Math.abs(barycentricTotal) < 1e-20) {
|
|
93171
|
+
return Math.min(distSq2(px, py, pz, ax, ay, az), distSq2(px, py, pz, bx, by, bz), distSq2(px, py, pz, cx, cy, cz));
|
|
93172
|
+
}
|
|
93173
|
+
const denom = 1 / barycentricTotal;
|
|
93174
|
+
const v = vb * denom;
|
|
93175
|
+
const w = vc * denom;
|
|
93176
|
+
return distSq2(px, py, pz, ax + abx * v + acx * w, ay + aby * v + acy * w, az + abz * v + acz * w);
|
|
93177
|
+
}
|
|
93178
|
+
function segmentSegmentDistanceSq(a0, a1, b0, b1) {
|
|
93179
|
+
const ux = a1[0] - a0[0];
|
|
93180
|
+
const uy = a1[1] - a0[1];
|
|
93181
|
+
const uz = a1[2] - a0[2];
|
|
93182
|
+
const vx = b1[0] - b0[0];
|
|
93183
|
+
const vy = b1[1] - b0[1];
|
|
93184
|
+
const vz = b1[2] - b0[2];
|
|
93185
|
+
const wx = a0[0] - b0[0];
|
|
93186
|
+
const wy = a0[1] - b0[1];
|
|
93187
|
+
const wz = a0[2] - b0[2];
|
|
93188
|
+
const a = ux * ux + uy * uy + uz * uz;
|
|
93189
|
+
const b = ux * vx + uy * vy + uz * vz;
|
|
93190
|
+
const c = vx * vx + vy * vy + vz * vz;
|
|
93191
|
+
const d = ux * wx + uy * wy + uz * wz;
|
|
93192
|
+
const e = vx * wx + vy * wy + vz * wz;
|
|
93193
|
+
const denom = a * c - b * b;
|
|
93194
|
+
let sNumerator = denom;
|
|
93195
|
+
let sDenominator = denom;
|
|
93196
|
+
let tNumerator = denom;
|
|
93197
|
+
let tDenominator = denom;
|
|
93198
|
+
if (a <= 1e-20) return pointSegmentDistanceSq(a0, b0, b1);
|
|
93199
|
+
if (c <= 1e-20) return pointSegmentDistanceSq(b0, a0, a1);
|
|
93200
|
+
if (denom < 1e-20) {
|
|
93201
|
+
sNumerator = 0;
|
|
93202
|
+
sDenominator = 1;
|
|
93203
|
+
tNumerator = e;
|
|
93204
|
+
tDenominator = c;
|
|
93205
|
+
} else {
|
|
93206
|
+
sNumerator = b * e - c * d;
|
|
93207
|
+
tNumerator = a * e - b * d;
|
|
93208
|
+
if (sNumerator < 0) {
|
|
93209
|
+
sNumerator = 0;
|
|
93210
|
+
tNumerator = e;
|
|
93211
|
+
tDenominator = c;
|
|
93212
|
+
} else if (sNumerator > sDenominator) {
|
|
93213
|
+
sNumerator = sDenominator;
|
|
93214
|
+
tNumerator = e + b;
|
|
93215
|
+
tDenominator = c;
|
|
93216
|
+
}
|
|
93217
|
+
}
|
|
93218
|
+
if (tNumerator < 0) {
|
|
93219
|
+
tNumerator = 0;
|
|
93220
|
+
if (-d < 0) {
|
|
93221
|
+
sNumerator = 0;
|
|
93222
|
+
} else if (-d > a) {
|
|
93223
|
+
sNumerator = sDenominator;
|
|
93224
|
+
} else {
|
|
93225
|
+
sNumerator = -d;
|
|
93226
|
+
sDenominator = a;
|
|
93227
|
+
}
|
|
93228
|
+
} else if (tNumerator > tDenominator) {
|
|
93229
|
+
tNumerator = tDenominator;
|
|
93230
|
+
if (-d + b < 0) {
|
|
93231
|
+
sNumerator = 0;
|
|
93232
|
+
} else if (-d + b > a) {
|
|
93233
|
+
sNumerator = sDenominator;
|
|
93234
|
+
} else {
|
|
93235
|
+
sNumerator = -d + b;
|
|
93236
|
+
sDenominator = a;
|
|
93237
|
+
}
|
|
93238
|
+
}
|
|
93239
|
+
const s = Math.abs(sNumerator) < 1e-20 ? 0 : sNumerator / sDenominator;
|
|
93240
|
+
const t = Math.abs(tNumerator) < 1e-20 ? 0 : tNumerator / tDenominator;
|
|
93241
|
+
const dx = wx + s * ux - t * vx;
|
|
93242
|
+
const dy = wy + s * uy - t * vy;
|
|
93243
|
+
const dz = wz + s * uz - t * vz;
|
|
93244
|
+
return dx * dx + dy * dy + dz * dz;
|
|
93245
|
+
}
|
|
93246
|
+
function trianglePoint(positions, triangle, corner) {
|
|
93247
|
+
const offset2 = triangle.offset + corner * 3;
|
|
93248
|
+
return [positions[offset2], positions[offset2 + 1], positions[offset2 + 2]];
|
|
93249
|
+
}
|
|
93250
|
+
function bboxDistanceSq(a, b) {
|
|
93251
|
+
let total = 0;
|
|
93252
|
+
for (let axis = 0; axis < 3; axis += 1) {
|
|
93253
|
+
const gap = Math.max(0, a.min[axis] - b.max[axis], b.min[axis] - a.max[axis]);
|
|
93254
|
+
total += gap * gap;
|
|
93255
|
+
}
|
|
93256
|
+
return total;
|
|
93257
|
+
}
|
|
93258
|
+
function triangleDistanceSq(aPositions, a, bPositions, b) {
|
|
93259
|
+
const a0 = trianglePoint(aPositions, a, 0);
|
|
93260
|
+
const a1 = trianglePoint(aPositions, a, 1);
|
|
93261
|
+
const a2 = trianglePoint(aPositions, a, 2);
|
|
93262
|
+
const b0 = trianglePoint(bPositions, b, 0);
|
|
93263
|
+
const b1 = trianglePoint(bPositions, b, 1);
|
|
93264
|
+
const b22 = trianglePoint(bPositions, b, 2);
|
|
93265
|
+
return Math.min(
|
|
93266
|
+
pointTriangleDistanceSq(a0[0], a0[1], a0[2], b0[0], b0[1], b0[2], b1[0], b1[1], b1[2], b22[0], b22[1], b22[2]),
|
|
93267
|
+
pointTriangleDistanceSq(a1[0], a1[1], a1[2], b0[0], b0[1], b0[2], b1[0], b1[1], b1[2], b22[0], b22[1], b22[2]),
|
|
93268
|
+
pointTriangleDistanceSq(a2[0], a2[1], a2[2], b0[0], b0[1], b0[2], b1[0], b1[1], b1[2], b22[0], b22[1], b22[2]),
|
|
93269
|
+
pointTriangleDistanceSq(b0[0], b0[1], b0[2], a0[0], a0[1], a0[2], a1[0], a1[1], a1[2], a2[0], a2[1], a2[2]),
|
|
93270
|
+
pointTriangleDistanceSq(b1[0], b1[1], b1[2], a0[0], a0[1], a0[2], a1[0], a1[1], a1[2], a2[0], a2[1], a2[2]),
|
|
93271
|
+
pointTriangleDistanceSq(b22[0], b22[1], b22[2], a0[0], a0[1], a0[2], a1[0], a1[1], a1[2], a2[0], a2[1], a2[2]),
|
|
93272
|
+
segmentSegmentDistanceSq(a0, a1, b0, b1),
|
|
93273
|
+
segmentSegmentDistanceSq(a0, a1, b1, b22),
|
|
93274
|
+
segmentSegmentDistanceSq(a0, a1, b22, b0),
|
|
93275
|
+
segmentSegmentDistanceSq(a1, a2, b0, b1),
|
|
93276
|
+
segmentSegmentDistanceSq(a1, a2, b1, b22),
|
|
93277
|
+
segmentSegmentDistanceSq(a1, a2, b22, b0),
|
|
93278
|
+
segmentSegmentDistanceSq(a2, a0, b0, b1),
|
|
93279
|
+
segmentSegmentDistanceSq(a2, a0, b1, b22),
|
|
93280
|
+
segmentSegmentDistanceSq(a2, a0, b22, b0)
|
|
93281
|
+
);
|
|
93282
|
+
}
|
|
93283
|
+
function meshEntriesContactDistance(a, b, tolerance) {
|
|
93284
|
+
const toleranceSq = tolerance * tolerance;
|
|
93285
|
+
let bestDistanceSq = Infinity;
|
|
93286
|
+
for (const aTriangle of a.triangles) {
|
|
93287
|
+
for (const bTriangle of b.triangles) {
|
|
93288
|
+
if (bboxDistanceSq(aTriangle, bTriangle) > toleranceSq) continue;
|
|
93289
|
+
const distanceSq = triangleDistanceSq(a.positions, aTriangle, b.positions, bTriangle);
|
|
93290
|
+
if (distanceSq > toleranceSq) continue;
|
|
93291
|
+
if (distanceSq <= 1e-20) return 0;
|
|
93292
|
+
bestDistanceSq = Math.min(bestDistanceSq, distanceSq);
|
|
93293
|
+
}
|
|
93294
|
+
}
|
|
93295
|
+
return Number.isFinite(bestDistanceSq) ? Math.sqrt(bestDistanceSq) : null;
|
|
93296
|
+
}
|
|
93297
|
+
function intersectionVolume(a, b) {
|
|
93298
|
+
try {
|
|
93299
|
+
const hit = a.shape.intersect(b.shape);
|
|
93300
|
+
if (hit.isEmpty()) return { volume: 0 };
|
|
93301
|
+
const volume = hit.volume();
|
|
93302
|
+
return { volume: Number.isFinite(volume) ? volume : 0 };
|
|
93303
|
+
} catch (err2) {
|
|
93304
|
+
const message = err2 instanceof Error ? err2.message : String(err2);
|
|
93305
|
+
return { volume: null, warning: `Could not boolean-test ${a.name} against ${b.name}: ${message}` };
|
|
93306
|
+
}
|
|
93307
|
+
}
|
|
93308
|
+
function detectPhysicalContact(a, b, options, meshCache = {}) {
|
|
93309
|
+
const gaps = aabbGaps(a, b);
|
|
93310
|
+
const largestGap = Math.max(gaps[0], gaps[1], gaps[2]);
|
|
93311
|
+
if (largestGap > options.contactTolerance) return { contact: null };
|
|
93312
|
+
const sourceMesh = meshCache.sourceMesh !== void 0 ? meshCache.sourceMesh : meshContactDataFor(a);
|
|
93313
|
+
const targetMesh = meshCache.targetMesh !== void 0 ? meshCache.targetMesh : meshContactDataFor(b);
|
|
93314
|
+
if (sourceMesh && targetMesh) {
|
|
93315
|
+
const distance5 = meshEntriesContactDistance(sourceMesh, targetMesh, options.contactTolerance);
|
|
93316
|
+
if (distance5 != null) {
|
|
93317
|
+
return {
|
|
93318
|
+
contact: {
|
|
93319
|
+
kind: "touching",
|
|
93320
|
+
method: "mesh-surface-distance",
|
|
93321
|
+
gap: distance5
|
|
93322
|
+
}
|
|
93323
|
+
};
|
|
93324
|
+
}
|
|
93325
|
+
}
|
|
93326
|
+
const bboxOverlaps = !hasPositiveGap(gaps) && aabbInteriorOverlaps(a, b);
|
|
93327
|
+
if (options.exactGeometry && bboxOverlaps) {
|
|
93328
|
+
if (aabbOverlapVolume(a, b) <= options.minOverlapVolume) return { contact: null };
|
|
93329
|
+
const overlap = intersectionVolume(a, b);
|
|
93330
|
+
if (overlap.volume != null && overlap.volume > options.minOverlapVolume) {
|
|
93331
|
+
return {
|
|
93332
|
+
contact: {
|
|
93333
|
+
kind: "overlap",
|
|
93334
|
+
method: "boolean-intersection",
|
|
93335
|
+
gap: 0,
|
|
93336
|
+
overlapVolume: overlap.volume
|
|
93337
|
+
},
|
|
93338
|
+
warning: overlap.warning
|
|
93339
|
+
};
|
|
93340
|
+
}
|
|
93341
|
+
return { contact: null, warning: overlap.warning };
|
|
93342
|
+
}
|
|
93343
|
+
if (bboxOverlaps && options.mergeOverlappingBBoxes) {
|
|
93344
|
+
return {
|
|
93345
|
+
contact: {
|
|
93346
|
+
kind: "overlap",
|
|
93347
|
+
method: "bbox-overlap",
|
|
93348
|
+
gap: 0,
|
|
93349
|
+
overlapVolume: aabbOverlapVolume(a, b)
|
|
93350
|
+
}
|
|
93351
|
+
};
|
|
93352
|
+
}
|
|
93353
|
+
if (options.mergeTouchingBBoxes) {
|
|
93354
|
+
const contact = contactFromBBoxes(a, b, options.contactTolerance);
|
|
93355
|
+
if (contact.touching) {
|
|
93356
|
+
return {
|
|
93357
|
+
contact: {
|
|
93358
|
+
kind: "touching",
|
|
93359
|
+
method: "bbox-contact",
|
|
93360
|
+
gap: contact.gap,
|
|
93361
|
+
axis: contact.axis
|
|
93362
|
+
}
|
|
93363
|
+
};
|
|
93364
|
+
}
|
|
93365
|
+
}
|
|
93366
|
+
return { contact: null };
|
|
93367
|
+
}
|
|
93368
|
+
|
|
92891
93369
|
// src/forge/inspection/physical-connectivity.ts
|
|
92892
93370
|
var DEFAULT_PHYSICAL_CONNECTIVITY_OPTIONS = {
|
|
92893
93371
|
contactTolerance: 0.05,
|
|
92894
93372
|
minOverlapVolume: 0.1,
|
|
92895
93373
|
exactGeometry: true
|
|
92896
93374
|
};
|
|
92897
|
-
var AXIS_NAMES = ["x", "y", "z"];
|
|
92898
93375
|
var UnionFind2 = class {
|
|
92899
93376
|
parent;
|
|
92900
93377
|
rank;
|
|
@@ -92940,12 +93417,6 @@ function expandBBox(target, min2, max4) {
|
|
|
92940
93417
|
target.max[axis] = Math.max(target.max[axis], max4[axis]);
|
|
92941
93418
|
}
|
|
92942
93419
|
}
|
|
92943
|
-
function nearestBoundaryGap(a, b, axis) {
|
|
92944
|
-
return Math.min(Math.abs(a.max[axis] - b.min[axis]), Math.abs(b.max[axis] - a.min[axis]));
|
|
92945
|
-
}
|
|
92946
|
-
function hasPositiveGap(gaps) {
|
|
92947
|
-
return gaps[0] > 0 || gaps[1] > 0 || gaps[2] > 0;
|
|
92948
|
-
}
|
|
92949
93420
|
function collectCandidatePairs2(entries, tolerance) {
|
|
92950
93421
|
if (entries.length < 2) return [];
|
|
92951
93422
|
const index = new AabbSpatialIndex(entries);
|
|
@@ -92957,35 +93428,6 @@ function collectCandidatePairs2(entries, tolerance) {
|
|
|
92957
93428
|
pairs.sort((a, b) => a.sourceIndex - b.sourceIndex || a.targetIndex - b.targetIndex);
|
|
92958
93429
|
return pairs;
|
|
92959
93430
|
}
|
|
92960
|
-
function contactFromBBoxes(a, b, tolerance) {
|
|
92961
|
-
const gaps = aabbGaps(a, b);
|
|
92962
|
-
const largestGap = Math.max(gaps[0], gaps[1], gaps[2]);
|
|
92963
|
-
if (largestGap > tolerance) return { touching: false, gap: largestGap };
|
|
92964
|
-
const separatedAxes = gaps.map((gap, axis) => ({ gap, axis })).filter((entry) => entry.gap > 0);
|
|
92965
|
-
if (separatedAxes.length > 0) {
|
|
92966
|
-
const nearest2 = separatedAxes.reduce((best, entry) => entry.gap > best.gap ? entry : best, separatedAxes[0]);
|
|
92967
|
-
return { touching: true, gap: nearest2.gap, axis: AXIS_NAMES[nearest2.axis] };
|
|
92968
|
-
}
|
|
92969
|
-
const boundaryAxes = AXIS_NAMES.map((axisName, axis) => ({
|
|
92970
|
-
axis,
|
|
92971
|
-
axisName,
|
|
92972
|
-
gap: nearestBoundaryGap(a, b, axis)
|
|
92973
|
-
})).filter((entry) => entry.gap <= tolerance);
|
|
92974
|
-
if (boundaryAxes.length === 0) return { touching: false, gap: 0 };
|
|
92975
|
-
const nearest = boundaryAxes.reduce((best, entry) => entry.gap < best.gap ? entry : best, boundaryAxes[0]);
|
|
92976
|
-
return { touching: true, gap: nearest.gap, axis: nearest.axisName };
|
|
92977
|
-
}
|
|
92978
|
-
function intersectionVolume(a, b) {
|
|
92979
|
-
try {
|
|
92980
|
-
const hit = a.shape.intersect(b.shape);
|
|
92981
|
-
if (hit.isEmpty()) return { volume: 0 };
|
|
92982
|
-
const volume = hit.volume();
|
|
92983
|
-
return { volume: Number.isFinite(volume) ? volume : 0 };
|
|
92984
|
-
} catch (err2) {
|
|
92985
|
-
const message = err2 instanceof Error ? err2.message : String(err2);
|
|
92986
|
-
return { volume: null, warning: `Could not boolean-test ${a.name} against ${b.name}: ${message}` };
|
|
92987
|
-
}
|
|
92988
|
-
}
|
|
92989
93431
|
function bodyCountForEntry(entry) {
|
|
92990
93432
|
if (typeof entry.bodyCount === "number" && Number.isFinite(entry.bodyCount)) {
|
|
92991
93433
|
return Math.max(0, Math.round(entry.bodyCount));
|
|
@@ -93017,52 +93459,22 @@ function analyzePhysicalConnectivity2(entries, rawOptions = {}) {
|
|
|
93017
93459
|
const warnings = [];
|
|
93018
93460
|
const edges = [];
|
|
93019
93461
|
const unionFind = new UnionFind2(entries.length);
|
|
93462
|
+
const meshDataByIndex = /* @__PURE__ */ new Map();
|
|
93463
|
+
entries.forEach((entry, index) => {
|
|
93464
|
+
const meshData = meshContactDataFor(entry);
|
|
93465
|
+
if (meshData) meshDataByIndex.set(index, meshData);
|
|
93466
|
+
});
|
|
93020
93467
|
for (const pair of collectCandidatePairs2(entries, options.contactTolerance)) {
|
|
93021
93468
|
const i = pair.sourceIndex;
|
|
93022
93469
|
const j = pair.targetIndex;
|
|
93023
|
-
const
|
|
93024
|
-
|
|
93025
|
-
|
|
93026
|
-
|
|
93027
|
-
|
|
93028
|
-
|
|
93029
|
-
|
|
93030
|
-
|
|
93031
|
-
edges.push(
|
|
93032
|
-
makeEdge2(entries, i, j, {
|
|
93033
|
-
kind: "overlap",
|
|
93034
|
-
method: "boolean-intersection",
|
|
93035
|
-
gap: 0,
|
|
93036
|
-
overlapVolume: overlap.volume
|
|
93037
|
-
})
|
|
93038
|
-
);
|
|
93039
|
-
continue;
|
|
93040
|
-
}
|
|
93041
|
-
continue;
|
|
93042
|
-
}
|
|
93043
|
-
if (bboxOverlaps && options.mergeOverlappingBBoxes) {
|
|
93044
|
-
unionFind.union(i, j);
|
|
93045
|
-
edges.push(
|
|
93046
|
-
makeEdge2(entries, i, j, {
|
|
93047
|
-
kind: "overlap",
|
|
93048
|
-
method: "bbox-overlap",
|
|
93049
|
-
gap: 0,
|
|
93050
|
-
overlapVolume: aabbOverlapVolume(a, b)
|
|
93051
|
-
})
|
|
93052
|
-
);
|
|
93053
|
-
} else {
|
|
93054
|
-
const contact = contactFromBBoxes(a, b, options.contactTolerance);
|
|
93055
|
-
if (!contact.touching || !options.mergeTouchingBBoxes) continue;
|
|
93056
|
-
unionFind.union(i, j);
|
|
93057
|
-
edges.push(
|
|
93058
|
-
makeEdge2(entries, i, j, {
|
|
93059
|
-
kind: "touching",
|
|
93060
|
-
method: "bbox-contact",
|
|
93061
|
-
gap: contact.gap,
|
|
93062
|
-
axis: contact.axis
|
|
93063
|
-
})
|
|
93064
|
-
);
|
|
93065
|
-
}
|
|
93470
|
+
const detection = detectPhysicalContact(entries[i], entries[j], options, {
|
|
93471
|
+
sourceMesh: meshDataByIndex.get(i) ?? null,
|
|
93472
|
+
targetMesh: meshDataByIndex.get(j) ?? null
|
|
93473
|
+
});
|
|
93474
|
+
if (detection.warning) warnings.push(detection.warning);
|
|
93475
|
+
if (!detection.contact) continue;
|
|
93476
|
+
unionFind.union(i, j);
|
|
93477
|
+
edges.push(makeEdge2(entries, i, j, detection.contact));
|
|
93066
93478
|
}
|
|
93067
93479
|
const objects = entries.map((entry, index) => ({
|
|
93068
93480
|
index,
|
|
@@ -93107,7 +93519,7 @@ function analyzePhysicalConnectivity2(entries, rawOptions = {}) {
|
|
|
93107
93519
|
}
|
|
93108
93520
|
const components = [...componentByRoot.values()];
|
|
93109
93521
|
return {
|
|
93110
|
-
method: options.exactGeometry ? "
|
|
93522
|
+
method: options.exactGeometry ? "mesh-contact-plus-boolean-overlap" : "bbox-neighborhood",
|
|
93111
93523
|
options,
|
|
93112
93524
|
objectCount: objects.length,
|
|
93113
93525
|
componentCount: components.length,
|
|
@@ -96661,9 +97073,6 @@ ${errors.join("\n\n")}`);
|
|
|
96661
97073
|
}
|
|
96662
97074
|
runDirectCliMain(import.meta.url, "cli/check-runtime-globals.ts", () => runCheckRuntimeGlobalsCli());
|
|
96663
97075
|
|
|
96664
|
-
// cli/check-suite.ts
|
|
96665
|
-
import { execSync as execSync2 } from "child_process";
|
|
96666
|
-
|
|
96667
97076
|
// cli/check-text.ts
|
|
96668
97077
|
import assert8 from "assert/strict";
|
|
96669
97078
|
var EPS17 = 1e-3;
|
|
@@ -97238,181 +97647,6 @@ async function runCheckTransformsCli() {
|
|
|
97238
97647
|
}
|
|
97239
97648
|
runDirectCliMain(import.meta.url, "cli/check-transforms.ts", () => runCheckTransformsCli());
|
|
97240
97649
|
|
|
97241
|
-
// cli/check-suite.ts
|
|
97242
|
-
var BRAILLE_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
97243
|
-
var ANSI = {
|
|
97244
|
-
dim: "\x1B[2m",
|
|
97245
|
-
bold: "\x1B[1m",
|
|
97246
|
-
green: "\x1B[32m",
|
|
97247
|
-
cyan: "\x1B[36m",
|
|
97248
|
-
yellow: "\x1B[33m",
|
|
97249
|
-
red: "\x1B[31m",
|
|
97250
|
-
reset: "\x1B[0m",
|
|
97251
|
-
clearLine: "\x1B[2K",
|
|
97252
|
-
cursorUp: "\x1B[1A",
|
|
97253
|
-
hideCursor: "\x1B[?25l",
|
|
97254
|
-
showCursor: "\x1B[?25h"
|
|
97255
|
-
};
|
|
97256
|
-
function createSmokeStages() {
|
|
97257
|
-
return [
|
|
97258
|
-
{ label: "Constraints", insight: "solver convergence & snapshot fidelity", run: () => runCheckConstraintsCli([]) },
|
|
97259
|
-
{ label: "Transforms", insight: "matrix composition & assembly trees", run: () => runCheckTransformsCli() },
|
|
97260
|
-
{ label: "Dimensions", insight: "parametric dimension propagation", run: () => runCheckDimensionsCli() },
|
|
97261
|
-
{ label: "JS modules", insight: "import graph & bundle hygiene", run: () => runCheckJsModulesCli() },
|
|
97262
|
-
{ label: "Runtime globals", insight: "sandbox namespace allowlist", run: () => runCheckRuntimeGlobalsCli() },
|
|
97263
|
-
{ label: "Text", insight: "text2d rendering contracts", run: () => runCheckTextCli() },
|
|
97264
|
-
{
|
|
97265
|
-
label: "Examples",
|
|
97266
|
-
insight: "maintained example smoke surface",
|
|
97267
|
-
run: () => runCheckExamplesCli(["--profile", "smoke"])
|
|
97268
|
-
}
|
|
97269
|
-
];
|
|
97270
|
-
}
|
|
97271
|
-
function createStages(profile) {
|
|
97272
|
-
const smokeStages = createSmokeStages();
|
|
97273
|
-
if (profile === "smoke") return smokeStages;
|
|
97274
|
-
return [
|
|
97275
|
-
...smokeStages,
|
|
97276
|
-
{
|
|
97277
|
-
label: "Unit tests",
|
|
97278
|
-
insight: "vitest \u2014 pure geometry and integration contracts",
|
|
97279
|
-
run: async () => {
|
|
97280
|
-
execSync2("npx vitest run", { stdio: "pipe" });
|
|
97281
|
-
}
|
|
97282
|
-
},
|
|
97283
|
-
{ label: "Placement refs", insight: "reference stability across edits", run: () => runCheckPlacementReferencesCli() },
|
|
97284
|
-
{ label: "BREP export", insight: "solid topology & face-history integrity", run: () => runCheckBrepExportCli() },
|
|
97285
|
-
{ label: "Compiler", insight: "AST snapshots & code-gen correctness", run: () => runCheckCompilerCli([]) },
|
|
97286
|
-
{ label: "Query propagation", insight: "reactive data-flow graph consistency", run: () => runCheckQueryPropagationCli([]) },
|
|
97287
|
-
{ label: "API contracts", insight: "public script API surface stability", run: () => runCheckApiContractsCli() },
|
|
97288
|
-
{ label: "OCCT lowerer", insight: "compile-plan \u2192 OCCT geometry invariants", run: () => runCheckOcctLowerCli() },
|
|
97289
|
-
{
|
|
97290
|
-
label: "Lint & format",
|
|
97291
|
-
insight: "Biome lint + formatting consistency",
|
|
97292
|
-
run: async () => {
|
|
97293
|
-
execSync2("npx biome check .", { stdio: "pipe" });
|
|
97294
|
-
}
|
|
97295
|
-
}
|
|
97296
|
-
];
|
|
97297
|
-
}
|
|
97298
|
-
function parseProfile(argv) {
|
|
97299
|
-
let profile = "full";
|
|
97300
|
-
for (let i = 0; i < argv.length; i += 1) {
|
|
97301
|
-
const arg = argv[i];
|
|
97302
|
-
if (arg === "--profile") {
|
|
97303
|
-
const value = argv[i + 1];
|
|
97304
|
-
if (value !== "full" && value !== "smoke" && value !== "local") {
|
|
97305
|
-
throw new Error(`Unknown suite profile "${value ?? ""}". Expected "smoke" or "full" (legacy alias: "local").`);
|
|
97306
|
-
}
|
|
97307
|
-
profile = value === "local" ? "smoke" : value;
|
|
97308
|
-
i += 1;
|
|
97309
|
-
continue;
|
|
97310
|
-
}
|
|
97311
|
-
throw new Error(`Unknown flag: ${arg}`);
|
|
97312
|
-
}
|
|
97313
|
-
return profile;
|
|
97314
|
-
}
|
|
97315
|
-
function progressBar(done, total, width) {
|
|
97316
|
-
const filled = Math.round(done / total * width);
|
|
97317
|
-
const empty = width - filled;
|
|
97318
|
-
const bar = "\x1B[32m" + "\u2501".repeat(filled) + "\x1B[2m" + "\u2500".repeat(empty) + "\x1B[0m";
|
|
97319
|
-
return bar;
|
|
97320
|
-
}
|
|
97321
|
-
function formatDuration2(ms) {
|
|
97322
|
-
if (ms < 1e3) return `${ms}ms`;
|
|
97323
|
-
return `${(ms / 1e3).toFixed(1)}s`;
|
|
97324
|
-
}
|
|
97325
|
-
function startSpinner(stage, index, total) {
|
|
97326
|
-
const isTTY = process.stdout.isTTY;
|
|
97327
|
-
if (!isTTY) {
|
|
97328
|
-
process.stdout.write(`[${index + 1}/${total}] ${stage.label} ...
|
|
97329
|
-
`);
|
|
97330
|
-
return () => {
|
|
97331
|
-
};
|
|
97332
|
-
}
|
|
97333
|
-
let frame = 0;
|
|
97334
|
-
const startMs = Date.now();
|
|
97335
|
-
const render = () => {
|
|
97336
|
-
const spinner = `${ANSI.cyan}${BRAILLE_FRAMES[frame % BRAILLE_FRAMES.length]}${ANSI.reset}`;
|
|
97337
|
-
const bar = progressBar(index, total, 20);
|
|
97338
|
-
const pct = `${Math.round(index / total * 100)}%`;
|
|
97339
|
-
const elapsed = formatDuration2(Date.now() - startMs);
|
|
97340
|
-
const line2 = `${ANSI.clearLine}\r ${spinner} ${bar} ${ANSI.dim}${pct}${ANSI.reset} ${ANSI.bold}${stage.label}${ANSI.reset} ${ANSI.dim}\u2014 ${stage.insight}${ANSI.reset} ${ANSI.dim}(${elapsed})${ANSI.reset}`;
|
|
97341
|
-
process.stdout.write(line2);
|
|
97342
|
-
frame++;
|
|
97343
|
-
};
|
|
97344
|
-
render();
|
|
97345
|
-
const interval = setInterval(render, 80);
|
|
97346
|
-
return () => {
|
|
97347
|
-
clearInterval(interval);
|
|
97348
|
-
process.stdout.write(`${ANSI.clearLine}\r`);
|
|
97349
|
-
};
|
|
97350
|
-
}
|
|
97351
|
-
function printStageResult(result, index, total) {
|
|
97352
|
-
const icon = result.ok ? `${ANSI.green}\u2713${ANSI.reset}` : `${ANSI.red}\u2717${ANSI.reset}`;
|
|
97353
|
-
const dur = `${ANSI.dim}${formatDuration2(result.durationMs)}${ANSI.reset}`;
|
|
97354
|
-
const counter = `${ANSI.dim}[${index + 1}/${total}]${ANSI.reset}`;
|
|
97355
|
-
console.log(` ${icon} ${counter} ${result.label} ${dur}`);
|
|
97356
|
-
}
|
|
97357
|
-
function printSummary(results, totalMs) {
|
|
97358
|
-
const passed = results.filter((r) => r.ok).length;
|
|
97359
|
-
const failed = results.filter((r) => !r.ok).length;
|
|
97360
|
-
const slowest = results.reduce((a, b) => a.durationMs > b.durationMs ? a : b);
|
|
97361
|
-
console.log("");
|
|
97362
|
-
console.log(` ${progressBar(results.length, results.length, 20)} ${ANSI.bold}100%${ANSI.reset}`);
|
|
97363
|
-
console.log("");
|
|
97364
|
-
if (failed === 0) {
|
|
97365
|
-
console.log(
|
|
97366
|
-
` ${ANSI.green}${ANSI.bold}\u2713 Invariant suite passed${ANSI.reset} ${ANSI.dim}\u2014 ${passed} checks in ${formatDuration2(totalMs)}${ANSI.reset}`
|
|
97367
|
-
);
|
|
97368
|
-
} else {
|
|
97369
|
-
console.log(
|
|
97370
|
-
` ${ANSI.red}${ANSI.bold}\u2717 Suite failed${ANSI.reset} ${ANSI.dim}\u2014 ${passed} passed, ${failed} failed in ${formatDuration2(totalMs)}${ANSI.reset}`
|
|
97371
|
-
);
|
|
97372
|
-
}
|
|
97373
|
-
console.log(` ${ANSI.dim}slowest: ${slowest.label} (${formatDuration2(slowest.durationMs)})${ANSI.reset}`);
|
|
97374
|
-
console.log("");
|
|
97375
|
-
}
|
|
97376
|
-
async function runCheckSuiteCli(argv = process.argv.slice(2)) {
|
|
97377
|
-
const profile = parseProfile(argv);
|
|
97378
|
-
const stages = createStages(profile);
|
|
97379
|
-
const isTTY = process.stdout.isTTY;
|
|
97380
|
-
if (isTTY) process.stdout.write(ANSI.hideCursor);
|
|
97381
|
-
console.log("");
|
|
97382
|
-
console.log(` ${ANSI.bold}ForgeCAD Invariant Suite${ANSI.reset} ${ANSI.dim}(${profile} profile, ${stages.length} checks)${ANSI.reset}`);
|
|
97383
|
-
console.log("");
|
|
97384
|
-
const results = [];
|
|
97385
|
-
const suiteStart = Date.now();
|
|
97386
|
-
for (let i = 0; i < stages.length; i++) {
|
|
97387
|
-
const stage = stages[i];
|
|
97388
|
-
const stopSpinner = startSpinner(stage, i, stages.length);
|
|
97389
|
-
const t0 = Date.now();
|
|
97390
|
-
let ok = true;
|
|
97391
|
-
let error;
|
|
97392
|
-
try {
|
|
97393
|
-
await stage.run();
|
|
97394
|
-
} catch (err2) {
|
|
97395
|
-
ok = false;
|
|
97396
|
-
error = err2.message;
|
|
97397
|
-
}
|
|
97398
|
-
stopSpinner();
|
|
97399
|
-
const result = { label: stage.label, durationMs: Date.now() - t0, ok, error };
|
|
97400
|
-
results.push(result);
|
|
97401
|
-
printStageResult(result, i, stages.length);
|
|
97402
|
-
if (!ok && error) {
|
|
97403
|
-
console.error(` ${ANSI.red}${error}${ANSI.reset}`);
|
|
97404
|
-
}
|
|
97405
|
-
}
|
|
97406
|
-
const totalMs = Date.now() - suiteStart;
|
|
97407
|
-
printSummary(results, totalMs);
|
|
97408
|
-
if (isTTY) process.stdout.write(ANSI.showCursor);
|
|
97409
|
-
const failed = results.filter((r) => !r.ok);
|
|
97410
|
-
if (failed.length > 0) {
|
|
97411
|
-
process.exit(1);
|
|
97412
|
-
}
|
|
97413
|
-
}
|
|
97414
|
-
runDirectCliMain(import.meta.url, "cli/check-suite.ts", () => runCheckSuiteCli(process.argv.slice(2)));
|
|
97415
|
-
|
|
97416
97650
|
// cli/debug-compiler.ts
|
|
97417
97651
|
function usage3() {
|
|
97418
97652
|
console.error("Usage: forgecad debug compiler <script.forge.js> [--compact]");
|
|
@@ -97511,7 +97745,7 @@ async function runDebugAssemblyCli(argv = process.argv.slice(2)) {
|
|
|
97511
97745
|
const code = readFileSync12(resolve12(scriptPath), "utf-8");
|
|
97512
97746
|
const { allFiles, fileName, readBinaryFile } = collectProjectFiles(scriptPath);
|
|
97513
97747
|
await init();
|
|
97514
|
-
|
|
97748
|
+
await activateBackend(backend ?? CLI_DEFAULT_BACKEND);
|
|
97515
97749
|
const result = runScript(code, fileName, allFiles, { readBinaryFile, allowEmptyResult: true });
|
|
97516
97750
|
if (result.error) {
|
|
97517
97751
|
console.error("ERROR:", result.error);
|
|
@@ -97971,7 +98205,7 @@ async function runBrepCli(argv = process.argv.slice(2)) {
|
|
|
97971
98205
|
}
|
|
97972
98206
|
|
|
97973
98207
|
// cli/forge-capture.ts
|
|
97974
|
-
import { execSync as
|
|
98208
|
+
import { execSync as execSync2, spawn as spawn3 } from "child_process";
|
|
97975
98209
|
import { once } from "events";
|
|
97976
98210
|
import { existsSync as existsSync7 } from "fs";
|
|
97977
98211
|
import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
@@ -98823,7 +99057,7 @@ function findExecutablePath(explicitPath, staticCandidates, binCandidates) {
|
|
|
98823
99057
|
for (const bin of binCandidates) {
|
|
98824
99058
|
try {
|
|
98825
99059
|
const cmd = process.platform === "win32" ? `where ${bin}` : `command -v ${bin}`;
|
|
98826
|
-
const found =
|
|
99060
|
+
const found = execSync2(cmd, { stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
|
|
98827
99061
|
if (found && existsSync7(found)) return found;
|
|
98828
99062
|
} catch {
|
|
98829
99063
|
}
|
|
@@ -99389,7 +99623,7 @@ function printList(scriptPath, init2) {
|
|
|
99389
99623
|
);
|
|
99390
99624
|
console.log(` cut planes: ${cutPlanes.length === 0 ? "(none)" : cutPlanes.join(", ")}`);
|
|
99391
99625
|
}
|
|
99392
|
-
function
|
|
99626
|
+
function printSummary(options, init2, encoderMode) {
|
|
99393
99627
|
const lines = [
|
|
99394
99628
|
`${options.format.toUpperCase()} capture: ${basename4(options.outputPath)}`,
|
|
99395
99629
|
` capture=${options.capture} render=${options.renderMode}${options.includeWireframePass && options.renderMode !== "wireframe" ? "+wireframe" : ""}`,
|
|
@@ -99581,7 +99815,7 @@ async function runCaptureCli(config, argv = process.argv.slice(2)) {
|
|
|
99581
99815
|
return;
|
|
99582
99816
|
}
|
|
99583
99817
|
const framePlan = buildFramePlan(options, init2);
|
|
99584
|
-
|
|
99818
|
+
printSummary(options, init2, encoderMode);
|
|
99585
99819
|
if (options.format === "gif" && encoderMode === "js" && options.encoder !== "js") {
|
|
99586
99820
|
console.log(" ffmpeg not found; using the fallback 256-color JS GIF encoder.");
|
|
99587
99821
|
}
|
|
@@ -100293,7 +100527,7 @@ async function runMeshExportCli(format, argv) {
|
|
|
100293
100527
|
const code = (await import("fs")).readFileSync(resolve20(scriptPath), "utf-8");
|
|
100294
100528
|
const { allFiles, fileName, readBinaryFile } = collectProjectFiles(scriptPath);
|
|
100295
100529
|
await initKernel();
|
|
100296
|
-
|
|
100530
|
+
await activateBackend(backend ?? CLI_DEFAULT_BACKEND);
|
|
100297
100531
|
const qualityPreset = quality && quality !== "default" ? quality : void 0;
|
|
100298
100532
|
const result = runScript(code, fileName, allFiles, { ...qualityPreset ? { quality: qualityPreset } : {}, readBinaryFile });
|
|
100299
100533
|
if (result.error) {
|
|
@@ -100429,7 +100663,7 @@ async function runRenderViewsCli(argv) {
|
|
|
100429
100663
|
const code = readFileSync16(scriptPath, "utf-8");
|
|
100430
100664
|
const { allFiles, fileName, readBinaryFile } = collectProjectFiles(scriptPath);
|
|
100431
100665
|
await init();
|
|
100432
|
-
|
|
100666
|
+
await activateBackend(args.backend ?? CLI_DEFAULT_BACKEND);
|
|
100433
100667
|
const result = runScript(code, fileName, allFiles, {
|
|
100434
100668
|
...args.quality && args.quality !== "default" ? { quality: args.quality } : {},
|
|
100435
100669
|
allowEmptyResult: true,
|
|
@@ -100487,7 +100721,7 @@ async function runRenderInspectChannelsCli(argv) {
|
|
|
100487
100721
|
// cli/forge-render-hq.ts
|
|
100488
100722
|
import { writeFileSync as writeFileSync9, readFileSync as readFileSync17, mkdtempSync as mkdtempSync2, rmSync as rmSync2, existsSync as existsSync9 } from "fs";
|
|
100489
100723
|
import { resolve as resolve22, dirname as dirname8, join as join6, extname as extname6 } from "path";
|
|
100490
|
-
import { execSync as
|
|
100724
|
+
import { execSync as execSync3, spawnSync } from "child_process";
|
|
100491
100725
|
import { tmpdir as tmpdir2 } from "os";
|
|
100492
100726
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
100493
100727
|
|
|
@@ -100692,7 +100926,7 @@ function parseArgs8(argv) {
|
|
|
100692
100926
|
}
|
|
100693
100927
|
if (!scriptPath) {
|
|
100694
100928
|
throw new Error(
|
|
100695
|
-
"Usage: forgecad render hq <script.forge.js> [output.png] [options]\n --preset <studio|outdoor|dramatic|clay|wireframe|glass|metallic>\n --edges <off|thin|bold> Edge overlay preset (default: off)\n --size <px> Square output (sets both width and height)\n --width <px> Output width (default: 1920)\n --height <px> Output height (default: 1080)\n --samples <n> Render samples (default: 256)\n --engine <cycles|eevee> Render engine (default: cycles)\n --camera <front|back|side|right|top|iso|az:el|spec> Camera preset, angle, or exact spec\n --camera-json <file> Exact viewport camera JSON file\n --view <name> Named camera view from scene({ views })\n --scene <json|file> Viewport scene JSON or JSON file with camera/object overrides\n --transparent Transparent background\n --hdri <path.hdr> Custom HDRI environment map\n --video Render orbit turntable video (MP4)\n --frames <n> Video frames (default: 72)\n --fps <n> Video FPS (default: 24)\n --pitch <deg> Camera pitch angle (default: 25)\n --quality <default|live|high> Mesh tessellation quality\n --backend <manifold|occt|truck> Geometry backend"
|
|
100929
|
+
"Usage: forgecad render hq <script.forge.js> [output.png] [options]\n --preset <studio|outdoor|dramatic|clay|wireframe|glass|metallic>\n --edges <off|thin|bold> Edge overlay preset (default: off)\n --size <px> Square output (sets both width and height)\n --width <px> Output width (default: 1920)\n --height <px> Output height (default: 1080)\n --samples <n> Render samples (default: 256)\n --engine <cycles|eevee> Render engine (default: cycles)\n --camera <front|back|side|right|top|iso|az:el|spec> Camera preset, angle, or exact spec\n --camera-json <file> Exact viewport camera JSON file\n --view <name> Named camera view from scene({ views })\n --scene <json|file> Viewport scene JSON or JSON file with camera/object overrides\n --transparent Transparent background\n --hdri <path.hdr> Custom HDRI environment map\n --video Render orbit turntable video (MP4)\n --frames <n> Video frames (default: 72)\n --fps <n> Video FPS (default: 24)\n --pitch <deg> Camera pitch angle (default: 25)\n --quality <default|live|high> Mesh tessellation quality\n --backend <manifold|occt|truck> Geometry backend (default: manifold)"
|
|
100696
100930
|
);
|
|
100697
100931
|
}
|
|
100698
100932
|
if (viewName && (cameraToken || scene2?.camera)) {
|
|
@@ -100729,7 +100963,7 @@ function parseArgs8(argv) {
|
|
|
100729
100963
|
}
|
|
100730
100964
|
function findBlender() {
|
|
100731
100965
|
try {
|
|
100732
|
-
const which =
|
|
100966
|
+
const which = execSync3("which blender 2>/dev/null", { encoding: "utf-8" }).trim();
|
|
100733
100967
|
if (which) return which;
|
|
100734
100968
|
} catch {
|
|
100735
100969
|
}
|
|
@@ -100836,7 +101070,7 @@ async function runRenderHqCli(argv) {
|
|
|
100836
101070
|
const { allFiles, fileName, readBinaryFile } = collectProjectFiles(args.scriptPath);
|
|
100837
101071
|
console.log("Evaluating ForgeCAD script...");
|
|
100838
101072
|
await initKernel();
|
|
100839
|
-
|
|
101073
|
+
await activateBackend(args.backend ?? CLI_DEFAULT_BACKEND);
|
|
100840
101074
|
const qualityPreset = args.quality && args.quality !== "default" ? args.quality : void 0;
|
|
100841
101075
|
const result = runScript(code, fileName, allFiles, {
|
|
100842
101076
|
...qualityPreset ? { quality: qualityPreset } : {},
|
|
@@ -105347,7 +105581,7 @@ async function runWebCli(argv = process.argv.slice(2)) {
|
|
|
105347
105581
|
}
|
|
105348
105582
|
|
|
105349
105583
|
// cli/forge-link.ts
|
|
105350
|
-
import { execSync as
|
|
105584
|
+
import { execSync as execSync4 } from "child_process";
|
|
105351
105585
|
var PROD_BASE = "https://forgecad.io/app";
|
|
105352
105586
|
function extractGistId(input) {
|
|
105353
105587
|
const match = input.match(/gist\.github\.com\/(?:[^/]+\/)?([a-f0-9]+)/i);
|
|
@@ -105363,7 +105597,7 @@ async function runLinkCli(args) {
|
|
|
105363
105597
|
const gistId = extractGistId(input);
|
|
105364
105598
|
const url = `${PROD_BASE}?gist=${encodeURIComponent(gistId)}`;
|
|
105365
105599
|
try {
|
|
105366
|
-
|
|
105600
|
+
execSync4('printf %s "$URL" | pbcopy', { env: { ...process.env, URL: url }, stdio: "ignore" });
|
|
105367
105601
|
console.log(`Copied to clipboard:
|
|
105368
105602
|
${url}`);
|
|
105369
105603
|
} catch {
|
|
@@ -105372,7 +105606,7 @@ ${url}`);
|
|
|
105372
105606
|
}
|
|
105373
105607
|
|
|
105374
105608
|
// cli/forge-publish.ts
|
|
105375
|
-
import { execSync as
|
|
105609
|
+
import { execSync as execSync5 } from "child_process";
|
|
105376
105610
|
import { existsSync as existsSync15 } from "fs";
|
|
105377
105611
|
import { dirname as dirname11, relative as relative7, resolve as resolve33 } from "path";
|
|
105378
105612
|
function findProjectRoot3(dir) {
|
|
@@ -105389,14 +105623,14 @@ function findProjectRoot3(dir) {
|
|
|
105389
105623
|
}
|
|
105390
105624
|
function copyToClipboard(text) {
|
|
105391
105625
|
try {
|
|
105392
|
-
|
|
105626
|
+
execSync5('printf %s "$TEXT" | pbcopy', {
|
|
105393
105627
|
env: { ...process.env, TEXT: text },
|
|
105394
105628
|
stdio: "ignore"
|
|
105395
105629
|
});
|
|
105396
105630
|
return true;
|
|
105397
105631
|
} catch {
|
|
105398
105632
|
try {
|
|
105399
|
-
|
|
105633
|
+
execSync5('printf %s "$TEXT" | xclip -selection clipboard', {
|
|
105400
105634
|
env: { ...process.env, TEXT: text },
|
|
105401
105635
|
stdio: "ignore"
|
|
105402
105636
|
});
|
|
@@ -105500,7 +105734,7 @@ async function runPublishCli(args) {
|
|
|
105500
105734
|
}
|
|
105501
105735
|
|
|
105502
105736
|
// cli/forge-doctor.ts
|
|
105503
|
-
import { execSync as
|
|
105737
|
+
import { execSync as execSync6 } from "child_process";
|
|
105504
105738
|
import { existsSync as existsSync16 } from "fs";
|
|
105505
105739
|
function findExecutablePath2(staticCandidates, binCandidates) {
|
|
105506
105740
|
for (const candidate of staticCandidates) {
|
|
@@ -105509,7 +105743,7 @@ function findExecutablePath2(staticCandidates, binCandidates) {
|
|
|
105509
105743
|
for (const bin of binCandidates) {
|
|
105510
105744
|
try {
|
|
105511
105745
|
const cmd = process.platform === "win32" ? `where ${bin}` : `command -v ${bin}`;
|
|
105512
|
-
const found =
|
|
105746
|
+
const found = execSync6(cmd, { stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
|
|
105513
105747
|
if (found && existsSync16(found)) return found;
|
|
105514
105748
|
} catch {
|
|
105515
105749
|
}
|
|
@@ -105565,7 +105799,7 @@ function findFfmpeg() {
|
|
|
105565
105799
|
}
|
|
105566
105800
|
function getVersion(cmd) {
|
|
105567
105801
|
try {
|
|
105568
|
-
return
|
|
105802
|
+
return execSync6(cmd, { stdio: ["ignore", "pipe", "ignore"] }).toString().trim().split("\n")[0];
|
|
105569
105803
|
} catch {
|
|
105570
105804
|
return null;
|
|
105571
105805
|
}
|
|
@@ -107991,7 +108225,7 @@ async function runPrintCheckCli(argv = process.argv.slice(2)) {
|
|
|
107991
108225
|
const code = readFileSync26(resolve36(args.scriptPath), "utf-8");
|
|
107992
108226
|
const { allFiles, fileName, readBinaryFile } = collectProjectFiles(args.scriptPath);
|
|
107993
108227
|
await init();
|
|
107994
|
-
|
|
108228
|
+
await activateBackend(args.backend ?? CLI_DEFAULT_BACKEND);
|
|
107995
108229
|
if (Object.keys(args.paramOverrides).length > 0) setParamOverrides(args.paramOverrides);
|
|
107996
108230
|
const result = runScript(code, fileName, allFiles, {
|
|
107997
108231
|
debugImports: args.debugImports,
|
|
@@ -108947,7 +109181,6 @@ var DEFAULT_EXACT_SPATIAL_OBJECT_LIMIT = 250;
|
|
|
108947
109181
|
var SPATIAL_BBOX_PADDING = 0.1;
|
|
108948
109182
|
var MAX_SPATIAL_COLLISION_LINES = 50;
|
|
108949
109183
|
var MAX_COMPACT_OBJECT_NAMES = 24;
|
|
108950
|
-
var RUN_DEFAULT_BACKEND = "manifold";
|
|
108951
109184
|
function parseSpatialMode(argv, defaultMode) {
|
|
108952
109185
|
const consumed = /* @__PURE__ */ new Set();
|
|
108953
109186
|
const idx = argv.indexOf("--spatial");
|
|
@@ -109404,7 +109637,7 @@ async function runScriptCli(argv = process.argv.slice(2)) {
|
|
|
109404
109637
|
const printConstructionHistory = fullReport || verbose || argv.includes("--history");
|
|
109405
109638
|
const printFeatureSummary = fullReport || verbose || argv.includes("--features");
|
|
109406
109639
|
const printSolverProfile = fullReport || verbose || argv.includes("--solver-profile");
|
|
109407
|
-
const backend = parseBackendArg(argv) ??
|
|
109640
|
+
const backend = parseBackendArg(argv) ?? CLI_DEFAULT_BACKEND;
|
|
109408
109641
|
const quality = parseQualityArg(argv);
|
|
109409
109642
|
const solverDebugOut = parseRequiredArg(argv, "--solver-debug-out");
|
|
109410
109643
|
const {
|
|
@@ -109767,7 +110000,7 @@ async function runScriptCli(argv = process.argv.slice(2)) {
|
|
|
109767
110000
|
if (solverDebugOut) {
|
|
109768
110001
|
setSolveOptionOverrides(null);
|
|
109769
110002
|
}
|
|
109770
|
-
if (backend
|
|
110003
|
+
if (backend !== CLI_DEFAULT_BACKEND) setActiveBackend(CLI_DEFAULT_BACKEND);
|
|
109771
110004
|
}
|
|
109772
110005
|
}
|
|
109773
110006
|
|
|
@@ -109796,7 +110029,7 @@ async function runRenderSectionCli(argv = process.argv.slice(2)) {
|
|
|
109796
110029
|
let offset2 = 0;
|
|
109797
110030
|
let size = 1024;
|
|
109798
110031
|
let chromePath = process.env.CHROME_PATH;
|
|
109799
|
-
let background = "#
|
|
110032
|
+
let background = "#f7f7f4";
|
|
109800
110033
|
let edges = "thin";
|
|
109801
110034
|
const args = [];
|
|
109802
110035
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -109875,7 +110108,7 @@ async function runRenderSectionCli(argv = process.argv.slice(2)) {
|
|
|
109875
110108
|
console.error("No intersection found.");
|
|
109876
110109
|
process.exit(1);
|
|
109877
110110
|
}
|
|
109878
|
-
const svgDocument = buildSketchSvgDocument(sectionSketches, { edges });
|
|
110111
|
+
const svgDocument = buildSketchSvgDocument(sectionSketches, { edges, theme: "cad-section" });
|
|
109879
110112
|
if (wantsPng) {
|
|
109880
110113
|
const png = await svgToPng(svgDocument.svg, { size, background, chromePath: resolvedChromePath });
|
|
109881
110114
|
await writeFile9(resolve39(outputPath), png);
|
|
@@ -110038,9 +110271,11 @@ var INSPECT_CHANNEL_VALUES = [
|
|
|
110038
110271
|
{ value: "rgb", description: "Canonical RGB viewport renders" },
|
|
110039
110272
|
{ value: "depth", description: "Packed linear view-depth maps" },
|
|
110040
110273
|
{ value: "normals", description: "View-space normal maps" },
|
|
110274
|
+
{ value: "zebra", description: "Reflective zebra stripes for surface-continuity inspection" },
|
|
110041
110275
|
{ value: "roughness", description: "Surface roughness and sharp-feature heatmap" },
|
|
110042
110276
|
{ value: "mask", description: "Object identity masks" },
|
|
110043
110277
|
{ value: "connectivity", description: "Physical connected-component masks" },
|
|
110278
|
+
{ value: "floating", description: "Disconnected floating body highlights" },
|
|
110044
110279
|
{ value: "distance", description: "Rooted physical-component distance heatmap" },
|
|
110045
110280
|
{ value: "collisions", description: "Ghosted source objects with highlighted intersection volumes" },
|
|
110046
110281
|
{ value: "thickness", description: "Local wall-thickness heatmap" },
|
|
@@ -110079,7 +110314,9 @@ var RENDER_STYLE_VALUES = [
|
|
|
110079
110314
|
{ value: "classic", description: "Technical CAD shading with crisp overlay edges" },
|
|
110080
110315
|
{ value: "studio", description: "Polished product-preview lighting and reflections" },
|
|
110081
110316
|
{ value: "fast", description: "Low-cost attractive live preview shading" },
|
|
110082
|
-
{ value: "glass", description: "Transparent inspection view with rim shell and readable edges" }
|
|
110317
|
+
{ value: "glass", description: "Transparent inspection view with rim shell and readable edges" },
|
|
110318
|
+
{ value: "precision", description: "Dense technical surface-field contours" },
|
|
110319
|
+
{ value: "hybrid", description: "Topo-led surface contours with a quiet orthogonal field" }
|
|
110083
110320
|
];
|
|
110084
110321
|
var RENDER_OPTIONS = [
|
|
110085
110322
|
{
|
|
@@ -110124,7 +110361,7 @@ var RENDER_OPTIONS = [
|
|
|
110124
110361
|
name: "--render-style",
|
|
110125
110362
|
description: "Visual render style (default: classic)",
|
|
110126
110363
|
argument: "required",
|
|
110127
|
-
valueLabel: "<classic|studio|fast|glass>",
|
|
110364
|
+
valueLabel: "<classic|studio|fast|glass|precision|hybrid>",
|
|
110128
110365
|
values: RENDER_STYLE_VALUES
|
|
110129
110366
|
},
|
|
110130
110367
|
{ name: "--port", description: "Vite dev server port", argument: "required", valueLabel: "<n>" },
|
|
@@ -110144,7 +110381,7 @@ var RENDER_INSPECT_OPTIONS = [
|
|
|
110144
110381
|
name: "--channels",
|
|
110145
110382
|
description: "Required inspection channels to emit; no default",
|
|
110146
110383
|
argument: "required",
|
|
110147
|
-
valueLabel: "<rgb,depth,normals,roughness,mask,connectivity,distance,collisions,thickness,section>",
|
|
110384
|
+
valueLabel: "<rgb,depth,normals,zebra,roughness,mask,connectivity,floating,distance,collisions,thickness,section>",
|
|
110148
110385
|
values: INSPECT_CHANNEL_VALUES
|
|
110149
110386
|
},
|
|
110150
110387
|
{
|
|
@@ -110166,8 +110403,18 @@ var RENDER_INSPECT_OPTIONS = [
|
|
|
110166
110403
|
{ name: "--min-thickness", description: "Critical thickness threshold in model units", argument: "required", valueLabel: "<mm>" },
|
|
110167
110404
|
{ name: "--warn-thickness", description: "Warning thickness threshold in model units", argument: "required", valueLabel: "<mm>" },
|
|
110168
110405
|
{ name: "--max-thickness", description: "Thick/blue heatmap threshold in model units", argument: "required", valueLabel: "<mm>" },
|
|
110169
|
-
{
|
|
110170
|
-
|
|
110406
|
+
{
|
|
110407
|
+
name: "--thickness-samples",
|
|
110408
|
+
description: "Override default scene budget with max thickness point samples per object",
|
|
110409
|
+
argument: "required",
|
|
110410
|
+
valueLabel: "<n>"
|
|
110411
|
+
},
|
|
110412
|
+
{
|
|
110413
|
+
name: "--roughness-samples",
|
|
110414
|
+
description: "Override default scene budget with max roughness point samples per object",
|
|
110415
|
+
argument: "required",
|
|
110416
|
+
valueLabel: "<n>"
|
|
110417
|
+
},
|
|
110171
110418
|
{ name: "--port", description: "Vite dev server port", argument: "required", valueLabel: "<n>" },
|
|
110172
110419
|
{ name: "--fresh-server", description: "Start a fresh renderer instead of reusing an existing one" },
|
|
110173
110420
|
{
|
|
@@ -110497,13 +110744,13 @@ var commands = [
|
|
|
110497
110744
|
},
|
|
110498
110745
|
{
|
|
110499
110746
|
name: "--backend",
|
|
110500
|
-
description: "Geometry backend",
|
|
110747
|
+
description: "Geometry backend (default: manifold)",
|
|
110501
110748
|
argument: "required",
|
|
110502
110749
|
valueLabel: "<manifold|occt|truck>",
|
|
110503
110750
|
values: [
|
|
110504
|
-
{ value: "manifold", description: "Manifold backend" },
|
|
110751
|
+
{ value: "manifold", description: "Manifold backend (default)" },
|
|
110505
110752
|
{ value: "occt", description: "OCCT backend" },
|
|
110506
|
-
{ value: "truck", description: "Rust topology backend
|
|
110753
|
+
{ value: "truck", description: "Rust topology backend" }
|
|
110507
110754
|
]
|
|
110508
110755
|
},
|
|
110509
110756
|
{
|
|
@@ -110636,7 +110883,7 @@ var commands = [
|
|
|
110636
110883
|
},
|
|
110637
110884
|
{
|
|
110638
110885
|
name: "--backend",
|
|
110639
|
-
description: "Geometry backend",
|
|
110886
|
+
description: "Geometry backend (default: manifold)",
|
|
110640
110887
|
argument: "required",
|
|
110641
110888
|
valueLabel: "<manifold|occt|truck>",
|
|
110642
110889
|
values: ["manifold", "occt", "truck"]
|
|
@@ -110661,12 +110908,13 @@ var commands = [
|
|
|
110661
110908
|
group: "Modeling",
|
|
110662
110909
|
path: ["render", "inspect"],
|
|
110663
110910
|
summary: "Render a machine-readable inspection bundle with geometry channels and a manifest.",
|
|
110664
|
-
description: "Launches the headless viewport renderer and writes a directory bundle for agent and automation workflows. Every channel is opt-in with `--channels`; there is no default bundle. Selected channels emit canonical `front`, `right`, `top`, and `iso` views for RGB, depth, normals, surface roughness, object masks, physical connectivity, rooted component distance, collisions, and wall thickness, or a principal-plane section atlas, plus a root `manifest.json` with scene metadata, filters, object visibility, and relative file paths.\n\nUse `--focus` to isolate specific parts or hide mocks, and `--hide` to remove named clutter. Output defaults to `<script-name>-inspect/` next to the input file.\n\nFor bundle layout, channel encodings, and manifest semantics, see [Inspection Bundles](guides/inspection-bundles.md).",
|
|
110911
|
+
description: "Launches the headless viewport renderer and writes a directory bundle for agent and automation workflows. Every channel is opt-in with `--channels`; there is no default bundle. Selected channels emit canonical `front`, `right`, `top`, and `iso` views for RGB, depth, normals, Zebra surface-continuity stripes, surface roughness, object masks, physical connectivity, floating body highlights, rooted component distance, collisions, and wall thickness, or a principal-plane section atlas, plus a root `manifest.json` with scene metadata, filters, object visibility, and relative file paths.\n\nUse `--focus` to isolate specific parts or hide mocks, and `--hide` to remove named clutter. Output defaults to `<script-name>-inspect/` next to the input file.\n\nFor bundle layout, channel encodings, and manifest semantics, see [Inspection Bundles](guides/inspection-bundles.md).",
|
|
110665
110912
|
usage: [
|
|
110666
|
-
"forgecad render inspect <script.forge.js> [output-dir] --channels rgb,depth,normals,roughness,mask,connectivity,distance,collisions,thickness,section [--focus [names]] [--hide names] [--min-thickness <mm>] [--warn-thickness <mm>] [options]"
|
|
110913
|
+
"forgecad render inspect <script.forge.js> [output-dir] --channels rgb,depth,normals,zebra,roughness,mask,connectivity,floating,distance,collisions,thickness,section [--focus [names]] [--hide names] [--min-thickness <mm>] [--warn-thickness <mm>] [options]"
|
|
110667
110914
|
],
|
|
110668
110915
|
examples: [
|
|
110669
110916
|
"forgecad render inspect examples/api/static-assembly-connectors.forge.js --channels rgb,mask",
|
|
110917
|
+
"forgecad render inspect examples/api/static-assembly-connectors.forge.js --channels rgb,zebra,normals",
|
|
110670
110918
|
'forgecad render inspect examples/api/static-assembly-connectors.forge.js out/bench-inspect --channels collisions --focus "Bench.*"',
|
|
110671
110919
|
'forgecad render inspect examples/api/static-assembly-connectors.forge.js --channels rgb,mask,collisions --hide "Bench.Slat0" --force'
|
|
110672
110920
|
],
|
|
@@ -110751,7 +110999,7 @@ var commands = [
|
|
|
110751
110999
|
},
|
|
110752
111000
|
{
|
|
110753
111001
|
name: "--backend",
|
|
110754
|
-
description: "Geometry backend",
|
|
111002
|
+
description: "Geometry backend (default: manifold)",
|
|
110755
111003
|
argument: "required",
|
|
110756
111004
|
valueLabel: "<manifold|occt|truck>",
|
|
110757
111005
|
values: ["manifold", "occt", "truck"]
|
|
@@ -110989,13 +111237,13 @@ var commands = [
|
|
|
110989
111237
|
},
|
|
110990
111238
|
{
|
|
110991
111239
|
name: "--backend",
|
|
110992
|
-
description: "Geometry backend",
|
|
111240
|
+
description: "Geometry backend (default: manifold)",
|
|
110993
111241
|
argument: "required",
|
|
110994
111242
|
valueLabel: "<manifold|occt|truck>",
|
|
110995
111243
|
values: [
|
|
110996
|
-
{ value: "manifold", description: "Manifold backend" },
|
|
111244
|
+
{ value: "manifold", description: "Manifold backend (default)" },
|
|
110997
111245
|
{ value: "occt", description: "OCCT backend" },
|
|
110998
|
-
{ value: "truck", description: "Rust topology backend
|
|
111246
|
+
{ value: "truck", description: "Rust topology backend" }
|
|
110999
111247
|
]
|
|
111000
111248
|
}
|
|
111001
111249
|
],
|
|
@@ -111026,13 +111274,13 @@ var commands = [
|
|
|
111026
111274
|
},
|
|
111027
111275
|
{
|
|
111028
111276
|
name: "--backend",
|
|
111029
|
-
description: "Geometry backend",
|
|
111277
|
+
description: "Geometry backend (default: manifold)",
|
|
111030
111278
|
argument: "required",
|
|
111031
111279
|
valueLabel: "<manifold|occt|truck>",
|
|
111032
111280
|
values: [
|
|
111033
|
-
{ value: "manifold", description: "Manifold backend" },
|
|
111281
|
+
{ value: "manifold", description: "Manifold backend (default)" },
|
|
111034
111282
|
{ value: "occt", description: "OCCT backend" },
|
|
111035
|
-
{ value: "truck", description: "Rust topology backend
|
|
111283
|
+
{ value: "truck", description: "Rust topology backend" }
|
|
111036
111284
|
]
|
|
111037
111285
|
}
|
|
111038
111286
|
],
|
|
@@ -111663,11 +111911,11 @@ var commands = [
|
|
|
111663
111911
|
{ name: "--debug-imports", description: "Include import trace logs during script execution" },
|
|
111664
111912
|
{
|
|
111665
111913
|
name: "--backend",
|
|
111666
|
-
description: "Geometry backend",
|
|
111914
|
+
description: "Geometry backend (default: manifold)",
|
|
111667
111915
|
argument: "required",
|
|
111668
111916
|
valueLabel: "<manifold|occt|truck>",
|
|
111669
111917
|
values: [
|
|
111670
|
-
{ value: "manifold", description: "Manifold backend" },
|
|
111918
|
+
{ value: "manifold", description: "Manifold backend (default)" },
|
|
111671
111919
|
{ value: "occt", description: "OCCT backend" },
|
|
111672
111920
|
{ value: "truck", description: "Rust topology backend" }
|
|
111673
111921
|
]
|
|
@@ -111773,11 +112021,11 @@ var commands = [
|
|
|
111773
112021
|
{ name: "--all-forge", description: "Scan every *.forge.js file instead of only main.forge.js files" },
|
|
111774
112022
|
{
|
|
111775
112023
|
name: "--backend",
|
|
111776
|
-
description: "Geometry backend",
|
|
112024
|
+
description: "Geometry backend (default: manifold)",
|
|
111777
112025
|
argument: "required",
|
|
111778
112026
|
valueLabel: "<manifold|occt|truck>",
|
|
111779
112027
|
values: [
|
|
111780
|
-
{ value: "manifold", description: "Manifold backend" },
|
|
112028
|
+
{ value: "manifold", description: "Manifold backend (default)" },
|
|
111781
112029
|
{ value: "occt", description: "OCCT backend" },
|
|
111782
112030
|
{ value: "truck", description: "Rust topology backend" }
|
|
111783
112031
|
]
|
|
@@ -111787,17 +112035,6 @@ var commands = [
|
|
|
111787
112035
|
},
|
|
111788
112036
|
run: runInspectMechanicalIntegrityCli
|
|
111789
112037
|
},
|
|
111790
|
-
{
|
|
111791
|
-
group: "Checks",
|
|
111792
|
-
path: ["check", "suite"],
|
|
111793
|
-
summary: "Run the repo invariant suite, with smoke and full profiles for the fast merge lane vs the broader regression sweep.",
|
|
111794
|
-
usage: ["forgecad check suite", "forgecad check suite --profile smoke"],
|
|
111795
|
-
examples: ["forgecad check suite", "forgecad check suite --profile smoke"],
|
|
111796
|
-
completion: {
|
|
111797
|
-
options: [{ name: "--profile", description: "Suite profile: smoke or full", argument: "required", valueLabel: "<profile>" }]
|
|
111798
|
-
},
|
|
111799
|
-
run: (args) => runCheckSuiteCli(args)
|
|
111800
|
-
},
|
|
111801
112038
|
{
|
|
111802
112039
|
group: "Checks",
|
|
111803
112040
|
path: ["check", "runtime-globals"],
|
|
@@ -111989,7 +112226,12 @@ var commands = [
|
|
|
111989
112226
|
argument: "required",
|
|
111990
112227
|
valueLabel: "<error|warning>"
|
|
111991
112228
|
},
|
|
111992
|
-
{
|
|
112229
|
+
{
|
|
112230
|
+
name: "--backend",
|
|
112231
|
+
description: "Geometry backend to use (default: manifold)",
|
|
112232
|
+
argument: "required",
|
|
112233
|
+
valueLabel: "<manifold|occt|truck>"
|
|
112234
|
+
},
|
|
111993
112235
|
{
|
|
111994
112236
|
name: "--sweep-steps",
|
|
111995
112237
|
description: "Samples per joint range sweep; enables exact geometry checks",
|
|
@@ -112154,7 +112396,6 @@ var AUTH_EXEMPT_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
112154
112396
|
"debug compiler",
|
|
112155
112397
|
"debug dimensions",
|
|
112156
112398
|
"debug faces",
|
|
112157
|
-
"check suite",
|
|
112158
112399
|
"check print",
|
|
112159
112400
|
"inspect mechanical-integrity"
|
|
112160
112401
|
]);
|
|
@@ -112426,6 +112667,7 @@ async function runForgeCadCli(argv = process.argv.slice(2)) {
|
|
|
112426
112667
|
return;
|
|
112427
112668
|
}
|
|
112428
112669
|
validateCommandArgs(match.command, match.args);
|
|
112670
|
+
setActiveBackend(CLI_DEFAULT_BACKEND);
|
|
112429
112671
|
const shouldRequireAuth = commandRequiresCliAuth(match.command.path);
|
|
112430
112672
|
if (shouldRequireAuth) {
|
|
112431
112673
|
requireCliAuth();
|