forgecad 0.9.2 → 0.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +7 -5
- package/README.md +1 -1
- package/README.public.md +24 -2
- package/dist/assets/{AdminPage-Bs4PiK00.js → AdminPage-4jihcEk_.js} +1 -1
- package/dist/assets/{BlogPage-DVmgN0ma.js → BlogPage-BvzruKtw.js} +1 -1
- package/dist/assets/{DocsPage-BP6wlsBN.js → DocsPage-DHbd-WS-.js} +13 -13
- package/dist/assets/{EditorApp-Arw2NnGJ.js → EditorApp-C5P2rBfh.js} +433 -84
- package/dist/assets/{EditorApp-VY9lXx0N.css → EditorApp-DS0AIUrZ.css} +25 -0
- package/dist/assets/{EmbedViewer-qgQiOahL.js → EmbedViewer-B70wQwlE.js} +2 -2
- package/dist/assets/{LandingPageProofDriven-DvhtmWOz.js → LandingPageProofDriven-DIsYTnep.js} +1 -1
- package/dist/assets/{PricingPage-Ck3CP2ti.css → PricingPage-BMedqFef.css} +48 -0
- package/dist/assets/{PricingPage-657oLvWh.js → PricingPage-YPOr12pP.js} +34 -6
- package/dist/assets/{SettingsPage-wNy3_2yn.js → SettingsPage-rntoyJ3b.js} +10 -13
- package/dist/assets/{app-BdBoMQeO.js → app-CWucmnLZ.js} +801 -1208
- package/dist/assets/cli/{render-Ci3jjyT1.js → render-DZHmUySW.js} +214 -23
- package/dist/assets/copy-CQKQppF-.js +8 -0
- package/dist/assets/{evalWorker-CMCAbK8r.js → evalWorker-C3dKxi9Y.js} +1117 -95
- package/dist/assets/{manifold-BMn-8Vf8.js → manifold-CQ3FhfWB.js} +1 -1
- package/dist/assets/{manifold-jlYQ6E5R.js → manifold-CU0G1yYL.js} +1 -1
- package/dist/assets/{manifold-DbyILno4.js → manifold-CYWZMfjB.js} +2 -2
- package/dist/assets/{renderSceneState-DAnqvxSt.js → renderSceneState-BBUrnsUN.js} +1 -1
- package/dist/assets/{reportWorker-BcRVMHK-.js → reportWorker-BhZ7DjxQ.js} +1091 -95
- package/dist/assets/{sectionPlaneMath-DXJ_TdIW.js → sectionPlaneMath-BxfokaJE.js} +1091 -95
- package/dist/cli/render.html +1 -1
- package/dist/docs/index.html +2 -2
- package/dist/docs-raw/AI/usage.md +182 -89
- package/dist/docs-raw/API/core/concepts.md +26 -0
- package/dist/docs-raw/CLI.md +58 -37
- package/dist/docs-raw/INDEX.md +81 -64
- package/dist/docs-raw/cli-monetization.md +9 -8
- package/dist/docs-raw/generated/concepts.md +111 -4
- package/dist/docs-raw/generated/core.md +2 -0
- package/dist/docs-raw/generated/curves.md +480 -1
- package/dist/docs-raw/generated/output.md +1 -0
- package/dist/docs-raw/generated/sketch.md +2 -0
- package/dist/docs-raw/generated/viewport.md +81 -3
- package/dist/docs-raw/product/user-outreach-email-templates.md +159 -0
- package/dist/docs-raw/skills/forgecad-image-replicator.md +1 -1
- package/dist/docs-raw/skills/forgecad-make-a-model.md +33 -4
- package/dist/docs-raw/skills/forgecad-prepare-prompt.md +1 -1
- package/dist/docs-raw/skills/forgecad-project.md +1 -1
- package/dist/docs-raw/skills/forgecad-render-inspect.md +1 -1
- package/dist/docs-raw/skills/forgecad.md +2 -1
- package/dist/docs-raw/welcome.md +85 -137
- package/dist/index.html +1 -1
- package/dist/llms.txt +4 -3
- package/dist/sitemap.xml +6 -6
- package/dist-cli/forgecad.js +1413 -219
- package/dist-cli/forgecad.js.map +1 -1
- package/dist-skill/CONTEXT.md +594 -5
- package/dist-skill/SKILL-dev.md +2 -1
- package/dist-skill/SKILL.md +2 -1
- package/dist-skill/docs/API/core/concepts.md +26 -0
- package/dist-skill/docs/CLI.md +58 -37
- package/dist-skill/docs/generated/core.md +2 -0
- package/dist-skill/docs/generated/curves.md +480 -1
- package/dist-skill/docs/generated/output.md +1 -0
- package/dist-skill/docs/generated/sketch.md +2 -0
- package/dist-skill/docs/generated/viewport.md +81 -3
- package/dist-skill/docs-dev/API/core/concepts.md +26 -0
- package/dist-skill/docs-dev/CLI.md +58 -37
- package/dist-skill/docs-dev/generated/core.md +2 -0
- package/dist-skill/docs-dev/generated/curves.md +480 -1
- package/dist-skill/docs-dev/generated/output.md +1 -0
- package/dist-skill/docs-dev/generated/sketch.md +2 -0
- package/dist-skill/docs-dev/generated/viewport.md +81 -3
- package/dist-skill/library/README.md +0 -1
- package/dist-skill/library/forgecad-image-replicator/SKILL.md +1 -1
- package/dist-skill/library/forgecad-make-a-model/SKILL.md +33 -4
- package/dist-skill/library/forgecad-prepare-prompt/SKILL.md +1 -1
- package/dist-skill/library/forgecad-project/SKILL.md +1 -1
- package/dist-skill/library/forgecad-render-inspect/SKILL.md +1 -1
- package/examples/api/conformal-product-ribbon.forge.js +77 -0
- package/examples/api/render-labels.forge.js +33 -0
- package/examples/api/text2d-basics.forge.js +6 -3
- package/package.json +1 -1
- package/dist-skill/library/forgecad-deep-dive/SKILL.md +0 -120
- package/dist-skill/library/forgecad-deep-dive/agents/openai.yaml +0 -4
- package/dist-skill/library/forgecad-deep-dive/references/output-shape.md +0 -64
|
@@ -7232,7 +7232,7 @@ function add$3(a, b) {
|
|
|
7232
7232
|
function scale$4(v, s) {
|
|
7233
7233
|
return [v[0] * s, v[1] * s, v[2] * s];
|
|
7234
7234
|
}
|
|
7235
|
-
function sub$
|
|
7235
|
+
function sub$5(a, b) {
|
|
7236
7236
|
return [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
|
|
7237
7237
|
}
|
|
7238
7238
|
function cross$4(a, b) {
|
|
@@ -7271,8 +7271,8 @@ function buildSurfaceSheetTopology(boundaries, options = {}) {
|
|
|
7271
7271
|
const [v1Start, v1End] = boundaries.v1;
|
|
7272
7272
|
const corners = [u0Start, u0End, u1Start, u1End];
|
|
7273
7273
|
const center = options.center ?? average$1(corners);
|
|
7274
|
-
const uAxis = normalizeAxis$1(sub$
|
|
7275
|
-
const vAxis = normalizeAxis$1(sub$
|
|
7274
|
+
const uAxis = normalizeAxis$1(sub$5(midpoint$3(u1Start, u1End), midpoint$3(u0Start, u0End)));
|
|
7275
|
+
const vAxis = normalizeAxis$1(sub$5(midpoint$3(v1Start, v1End), midpoint$3(v0Start, v0End)));
|
|
7276
7276
|
const normal = normalizeAxis$1(options.normal ?? cross$4(uAxis, vAxis));
|
|
7277
7277
|
const faces = /* @__PURE__ */ new Map();
|
|
7278
7278
|
faces.set(faceName, {
|
|
@@ -7304,7 +7304,7 @@ function attachSurfaceSheetTopology(shape, boundaries, options = {}) {
|
|
|
7304
7304
|
});
|
|
7305
7305
|
return shape;
|
|
7306
7306
|
}
|
|
7307
|
-
function requireFinite$
|
|
7307
|
+
function requireFinite$7(v, label) {
|
|
7308
7308
|
if (!Number.isFinite(v)) throw new Error(`nurbsSurface: ${label} must be finite, got ${v}`);
|
|
7309
7309
|
}
|
|
7310
7310
|
class NurbsSurface {
|
|
@@ -7330,16 +7330,16 @@ class NurbsSurface {
|
|
|
7330
7330
|
for (let i = 0; i < nU; i++) {
|
|
7331
7331
|
if (controlGrid[i].length !== nV) throw new Error(`nurbsSurface: row ${i} has ${controlGrid[i].length} points, expected ${nV}`);
|
|
7332
7332
|
for (let j = 0; j < nV; j++) {
|
|
7333
|
-
requireFinite$
|
|
7334
|
-
requireFinite$
|
|
7335
|
-
requireFinite$
|
|
7333
|
+
requireFinite$7(controlGrid[i][j][0], `controlGrid[${i}][${j}][0]`);
|
|
7334
|
+
requireFinite$7(controlGrid[i][j][1], `controlGrid[${i}][${j}][1]`);
|
|
7335
|
+
requireFinite$7(controlGrid[i][j][2], `controlGrid[${i}][${j}][2]`);
|
|
7336
7336
|
}
|
|
7337
7337
|
}
|
|
7338
7338
|
const weightsGrid = options.weights ?? controlGrid.map((row) => row.map(() => 1));
|
|
7339
7339
|
for (let i = 0; i < nU; i++) {
|
|
7340
7340
|
if (weightsGrid[i].length !== nV) throw new Error(`nurbsSurface: weights row ${i} length mismatch`);
|
|
7341
7341
|
for (let j = 0; j < nV; j++) {
|
|
7342
|
-
requireFinite$
|
|
7342
|
+
requireFinite$7(weightsGrid[i][j], `weights[${i}][${j}]`);
|
|
7343
7343
|
if (weightsGrid[i][j] <= 0) throw new Error(`nurbsSurface: weights[${i}][${j}] must be > 0`);
|
|
7344
7344
|
}
|
|
7345
7345
|
}
|
|
@@ -8750,7 +8750,7 @@ let _wasm = null;
|
|
|
8750
8750
|
async function initManifoldWasm() {
|
|
8751
8751
|
if (_wasm) return _wasm;
|
|
8752
8752
|
performance.mark("manifold:start");
|
|
8753
|
-
const Module = (await import("./manifold-
|
|
8753
|
+
const Module = (await import("./manifold-CQ3FhfWB.js")).default;
|
|
8754
8754
|
performance.mark("manifold:imported");
|
|
8755
8755
|
const wasm = await Module();
|
|
8756
8756
|
wasm.setup();
|
|
@@ -8959,7 +8959,7 @@ function sweepStitched(profilePolygons, pathPoints, up, wasm) {
|
|
|
8959
8959
|
function computeParallelTransportFrames(path2, preferredUp) {
|
|
8960
8960
|
const n = path2.length;
|
|
8961
8961
|
const frames = [];
|
|
8962
|
-
const firstTangent = normalize$5(sub$
|
|
8962
|
+
const firstTangent = normalize$5(sub$4(path2[1], path2[0]));
|
|
8963
8963
|
if (!firstTangent) return null;
|
|
8964
8964
|
let x = normalize$5(cross$3(preferredUp, firstTangent));
|
|
8965
8965
|
if (!x || length(x) < 1e-8) {
|
|
@@ -8973,18 +8973,18 @@ function computeParallelTransportFrames(path2, preferredUp) {
|
|
|
8973
8973
|
const prevT = frames[i - 1].t;
|
|
8974
8974
|
let nextT;
|
|
8975
8975
|
if (i < n - 1) {
|
|
8976
|
-
const t1 = normalize$5(sub$
|
|
8977
|
-
const t2 = normalize$5(sub$
|
|
8976
|
+
const t1 = normalize$5(sub$4(path2[i], path2[i - 1]));
|
|
8977
|
+
const t2 = normalize$5(sub$4(path2[i + 1], path2[i]));
|
|
8978
8978
|
if (!t1 || !t2) return null;
|
|
8979
8979
|
nextT = normalize$5(add$2(t1, t2)) || t1;
|
|
8980
8980
|
} else {
|
|
8981
|
-
const nt = normalize$5(sub$
|
|
8981
|
+
const nt = normalize$5(sub$4(path2[i], path2[i - 1]));
|
|
8982
8982
|
if (!nt) return null;
|
|
8983
8983
|
nextT = nt;
|
|
8984
8984
|
}
|
|
8985
8985
|
const v = cross$3(prevT, nextT);
|
|
8986
8986
|
const vLen = length(v);
|
|
8987
|
-
const c = dot$
|
|
8987
|
+
const c = dot$5(prevT, nextT);
|
|
8988
8988
|
if (vLen > 1e-10) {
|
|
8989
8989
|
const axis = scale$3(v, 1 / vLen);
|
|
8990
8990
|
x = rotateVector(frames[i - 1].x, axis, c, vLen);
|
|
@@ -9058,7 +9058,7 @@ function signedArea$4(loop) {
|
|
|
9058
9058
|
}
|
|
9059
9059
|
return area * 0.5;
|
|
9060
9060
|
}
|
|
9061
|
-
function sub$
|
|
9061
|
+
function sub$4(a, b) {
|
|
9062
9062
|
return [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
|
|
9063
9063
|
}
|
|
9064
9064
|
function add$2(a, b) {
|
|
@@ -9067,7 +9067,7 @@ function add$2(a, b) {
|
|
|
9067
9067
|
function scale$3(v, s) {
|
|
9068
9068
|
return [v[0] * s, v[1] * s, v[2] * s];
|
|
9069
9069
|
}
|
|
9070
|
-
function dot$
|
|
9070
|
+
function dot$5(a, b) {
|
|
9071
9071
|
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
|
9072
9072
|
}
|
|
9073
9073
|
function cross$3(a, b) {
|
|
@@ -9086,7 +9086,7 @@ function normalize$5(v) {
|
|
|
9086
9086
|
return [v[0] / len2, v[1] / len2, v[2] / len2];
|
|
9087
9087
|
}
|
|
9088
9088
|
function rotateVector(v, axis, c, s) {
|
|
9089
|
-
const kDotV = dot$
|
|
9089
|
+
const kDotV = dot$5(axis, v);
|
|
9090
9090
|
const kCrossV = cross$3(axis, v);
|
|
9091
9091
|
return [
|
|
9092
9092
|
v[0] * c + kCrossV[0] * s + axis[0] * kDotV * (1 - c),
|
|
@@ -12792,7 +12792,7 @@ function normalizeFaceSelector(selector) {
|
|
|
12792
12792
|
function cross$2(a, b) {
|
|
12793
12793
|
return [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]];
|
|
12794
12794
|
}
|
|
12795
|
-
function dot$
|
|
12795
|
+
function dot$4(a, b) {
|
|
12796
12796
|
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
|
12797
12797
|
}
|
|
12798
12798
|
function normVec3(v) {
|
|
@@ -12827,11 +12827,11 @@ function clusterMeshFaces(shape) {
|
|
|
12827
12827
|
if (!normal) continue;
|
|
12828
12828
|
const crossLen = Math.sqrt(rawCross[0] * rawCross[0] + rawCross[1] * rawCross[1] + rawCross[2] * rawCross[2]);
|
|
12829
12829
|
const triArea = crossLen / 2;
|
|
12830
|
-
const planeOffset = dot$
|
|
12830
|
+
const planeOffset = dot$4(normal, v0);
|
|
12831
12831
|
const triCentroid = [(v0[0] + v1[0] + v2[0]) / 3, (v0[1] + v1[1] + v2[1]) / 3, (v0[2] + v1[2] + v2[2]) / 3];
|
|
12832
12832
|
let merged = false;
|
|
12833
12833
|
for (const c of clusters) {
|
|
12834
|
-
if (dot$
|
|
12834
|
+
if (dot$4(c.normal, normal) > NORMAL_COS_EPS$1 && Math.abs(c.planeOffset - planeOffset) < PLANE_OFFSET_EPS$1) {
|
|
12835
12835
|
c.centroidSum[0] += triCentroid[0];
|
|
12836
12836
|
c.centroidSum[1] += triCentroid[1];
|
|
12837
12837
|
c.centroidSum[2] += triCentroid[2];
|
|
@@ -12873,7 +12873,7 @@ function queryMeshFaces(shape, query) {
|
|
|
12873
12873
|
let clusters = clusterMeshFaces(shape);
|
|
12874
12874
|
if (query.normal) {
|
|
12875
12875
|
const qn = query.normal;
|
|
12876
|
-
clusters = clusters.filter((c) => dot$
|
|
12876
|
+
clusters = clusters.filter((c) => dot$4(c.normal, qn) > NORMAL_COS_EPS$1);
|
|
12877
12877
|
}
|
|
12878
12878
|
if (query.planar !== false) {
|
|
12879
12879
|
clusters = clusters.filter((c) => c.normal !== null);
|
|
@@ -12889,7 +12889,7 @@ function queryMeshFace(shape, query) {
|
|
|
12889
12889
|
let clusters = clusterMeshFaces(shape);
|
|
12890
12890
|
if (query.normal) {
|
|
12891
12891
|
const qn = query.normal;
|
|
12892
|
-
clusters = clusters.filter((c) => dot$
|
|
12892
|
+
clusters = clusters.filter((c) => dot$4(c.normal, qn) > NORMAL_COS_EPS$1);
|
|
12893
12893
|
}
|
|
12894
12894
|
if (query.planar !== false) {
|
|
12895
12895
|
clusters = clusters.filter((c) => c.normal !== null);
|
|
@@ -19841,6 +19841,7 @@ Fix: pass a path like [[0,0,0], [20,0,8,4], [40,0,0,2]].`
|
|
|
19841
19841
|
const curveMode = shouldSmoothCurve(options, defaultCurve) && sourcePoints.length >= 3;
|
|
19842
19842
|
const normalized = compactPathPoints(resampleCurve(sourcePoints, options, defaultCurve));
|
|
19843
19843
|
const minRadius = Math.min(...normalized.map((point2) => point2.radius));
|
|
19844
|
+
const hasExplicitBlend = options.blend !== void 0;
|
|
19844
19845
|
const blendRadius = positiveFinite(options.blend, "Sculpt.tube() blend", Math.max(0.5, minRadius));
|
|
19845
19846
|
let segmentCount = 0;
|
|
19846
19847
|
for (let i = 0; i < normalized.length - 1; i += 1) {
|
|
@@ -19849,8 +19850,8 @@ Fix: pass a path like [[0,0,0], [20,0,8,4], [40,0,0,2]].`
|
|
|
19849
19850
|
if (segmentCount === 0) {
|
|
19850
19851
|
throw new Error("Sculpt.tube() points must include at least one non-zero-length segment.");
|
|
19851
19852
|
}
|
|
19852
|
-
const
|
|
19853
|
-
let out = polylineSweep(normalized,
|
|
19853
|
+
const effectiveBlendRadius = curveMode && !hasExplicitBlend ? Math.max(blendRadius, minRadius * 1.5) : blendRadius;
|
|
19854
|
+
let out = polylineSweep(normalized, effectiveBlendRadius);
|
|
19854
19855
|
if (options.polish !== void 0) out = out.polish(options.polish);
|
|
19855
19856
|
return out;
|
|
19856
19857
|
}
|
|
@@ -22238,7 +22239,7 @@ function intersection(...inputs) {
|
|
|
22238
22239
|
nextPlan
|
|
22239
22240
|
);
|
|
22240
22241
|
}
|
|
22241
|
-
var define_process_env_default = {};
|
|
22242
|
+
var define_process_env_default$2 = {};
|
|
22242
22243
|
let _wasm_solve = null;
|
|
22243
22244
|
let _wasm_get_profile = null;
|
|
22244
22245
|
let _solverMemory = null;
|
|
@@ -22276,7 +22277,7 @@ function readInitialConsoleDebug() {
|
|
|
22276
22277
|
return false;
|
|
22277
22278
|
}
|
|
22278
22279
|
function readEnvFlag(name) {
|
|
22279
|
-
const value = typeof process !== "undefined" ? define_process_env_default == null ? void 0 : define_process_env_default[name] : void 0;
|
|
22280
|
+
const value = typeof process !== "undefined" ? define_process_env_default$2 == null ? void 0 : define_process_env_default$2[name] : void 0;
|
|
22280
22281
|
if (typeof value !== "string") return false;
|
|
22281
22282
|
return ["1", "true", "yes", "on"].includes(value.toLowerCase());
|
|
22282
22283
|
}
|
|
@@ -23552,7 +23553,7 @@ class MateBuilder {
|
|
|
23552
23553
|
return this.constraints.reduce((sum, c) => sum + (CONSTRAINT_EQUATIONS[c.type] ?? 0), 0);
|
|
23553
23554
|
}
|
|
23554
23555
|
}
|
|
23555
|
-
let _collected$
|
|
23556
|
+
let _collected$8 = null;
|
|
23556
23557
|
const isAxis = (value) => value === "x" || value === "y" || value === "z";
|
|
23557
23558
|
const normalizeDirection = (value, label) => {
|
|
23558
23559
|
if (value === "radial" || isAxis(value)) return value;
|
|
@@ -23611,16 +23612,16 @@ const mergeDirective = (target, patch, label) => {
|
|
|
23611
23612
|
return out;
|
|
23612
23613
|
};
|
|
23613
23614
|
function resetExplodeView() {
|
|
23614
|
-
_collected$
|
|
23615
|
+
_collected$8 = null;
|
|
23615
23616
|
}
|
|
23616
23617
|
function getCollectedExplodeView() {
|
|
23617
|
-
return _collected$
|
|
23618
|
+
return _collected$8 ? cloneOptions(_collected$8) : null;
|
|
23618
23619
|
}
|
|
23619
23620
|
function explodeView(options = {}) {
|
|
23620
23621
|
if (!options || typeof options !== "object") {
|
|
23621
23622
|
throw new Error("explodeView(options) expects an options object");
|
|
23622
23623
|
}
|
|
23623
|
-
const next = _collected$
|
|
23624
|
+
const next = _collected$8 ? cloneOptions(_collected$8) : {};
|
|
23624
23625
|
if (options.enabled !== void 0) {
|
|
23625
23626
|
if (typeof options.enabled !== "boolean") throw new Error("explodeView.enabled must be a boolean");
|
|
23626
23627
|
next.enabled = options.enabled;
|
|
@@ -23668,9 +23669,9 @@ function explodeView(options = {}) {
|
|
|
23668
23669
|
});
|
|
23669
23670
|
next.byPath = byPath;
|
|
23670
23671
|
}
|
|
23671
|
-
_collected$
|
|
23672
|
+
_collected$8 = next;
|
|
23672
23673
|
}
|
|
23673
|
-
let _collected$
|
|
23674
|
+
let _collected$7 = null;
|
|
23674
23675
|
const isFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value);
|
|
23675
23676
|
const isVec3$1 = (value) => Array.isArray(value) && value.length === 3 && isFiniteNumber(value[0]) && isFiniteNumber(value[1]) && isFiniteNumber(value[2]);
|
|
23676
23677
|
const normalizeAxis = (axis) => {
|
|
@@ -23960,22 +23961,22 @@ const cloneCollected = (value) => ({
|
|
|
23960
23961
|
defaultAnimation: value.defaultAnimation
|
|
23961
23962
|
});
|
|
23962
23963
|
function resetJointsView() {
|
|
23963
|
-
_collected$
|
|
23964
|
+
_collected$7 = null;
|
|
23964
23965
|
}
|
|
23965
23966
|
function getCollectedJointsView() {
|
|
23966
|
-
return _collected$
|
|
23967
|
+
return _collected$7 ? cloneCollected(_collected$7) : null;
|
|
23967
23968
|
}
|
|
23968
23969
|
function saveJointsView() {
|
|
23969
|
-
return _collected$
|
|
23970
|
+
return _collected$7 ? cloneCollected(_collected$7) : null;
|
|
23970
23971
|
}
|
|
23971
23972
|
function restoreJointsView(state) {
|
|
23972
|
-
_collected$
|
|
23973
|
+
_collected$7 = state;
|
|
23973
23974
|
}
|
|
23974
23975
|
function jointsView(options = {}) {
|
|
23975
23976
|
if (!options || typeof options !== "object") {
|
|
23976
23977
|
throw new Error("jointsView(options) expects an options object");
|
|
23977
23978
|
}
|
|
23978
|
-
const next = _collected$
|
|
23979
|
+
const next = _collected$7 ? cloneCollected(_collected$7) : { joints: [], couplings: [], animations: [] };
|
|
23979
23980
|
if (options.enabled !== void 0) {
|
|
23980
23981
|
if (typeof options.enabled !== "boolean") {
|
|
23981
23982
|
throw new Error("jointsView.enabled must be a boolean");
|
|
@@ -24030,8 +24031,14 @@ function jointsView(options = {}) {
|
|
|
24030
24031
|
if (next.defaultAnimation && !next.animations.some((animation) => animation.name === next.defaultAnimation)) {
|
|
24031
24032
|
throw new Error(`jointsView defaultAnimation "${next.defaultAnimation}" does not exist in animations`);
|
|
24032
24033
|
}
|
|
24033
|
-
_collected$
|
|
24034
|
+
_collected$7 = next;
|
|
24034
24035
|
}
|
|
24036
|
+
var define_process_env_default$1 = {};
|
|
24037
|
+
const SWEEP_JOINT_DEFAULT_STEP_LIMIT = {
|
|
24038
|
+
live: 1,
|
|
24039
|
+
default: 4,
|
|
24040
|
+
high: Number.POSITIVE_INFINITY
|
|
24041
|
+
};
|
|
24035
24042
|
let collectedAssemblies = [];
|
|
24036
24043
|
function resetCollectedAssemblies() {
|
|
24037
24044
|
collectedAssemblies = [];
|
|
@@ -24072,6 +24079,33 @@ function collisionShape(part) {
|
|
|
24072
24079
|
if (shapes.length === 1) return shapes[0];
|
|
24073
24080
|
return union(...shapes);
|
|
24074
24081
|
}
|
|
24082
|
+
function boundsOverlap(a, b) {
|
|
24083
|
+
for (let axis = 0; axis < 3; axis++) {
|
|
24084
|
+
if (a.max[axis] <= b.min[axis] || b.max[axis] <= a.min[axis]) return false;
|
|
24085
|
+
}
|
|
24086
|
+
return true;
|
|
24087
|
+
}
|
|
24088
|
+
function readAssemblyPerfEnv(name) {
|
|
24089
|
+
return typeof process !== "undefined" ? define_process_env_default$1 == null ? void 0 : define_process_env_default$1[name] : void 0;
|
|
24090
|
+
}
|
|
24091
|
+
function resolveSweepJointStepLimit() {
|
|
24092
|
+
if (readAssemblyPerfEnv("FORGECAD_ALLOW_FULL_SWEEP_JOINT") === "1") return Number.POSITIVE_INFINITY;
|
|
24093
|
+
const override = readAssemblyPerfEnv("FORGECAD_SWEEP_JOINT_STEP_LIMIT");
|
|
24094
|
+
if (override != null && override.trim() !== "") {
|
|
24095
|
+
const parsed = Number(override);
|
|
24096
|
+
if (Number.isFinite(parsed) && parsed >= 1) return Math.floor(parsed);
|
|
24097
|
+
}
|
|
24098
|
+
return SWEEP_JOINT_DEFAULT_STEP_LIMIT[getForgeQualityPreset()];
|
|
24099
|
+
}
|
|
24100
|
+
function boundSweepJointSteps(jointName, requestedSteps) {
|
|
24101
|
+
const limit = resolveSweepJointStepLimit();
|
|
24102
|
+
if (!Number.isFinite(limit) || requestedSteps <= limit) return requestedSteps;
|
|
24103
|
+
const bounded = Math.max(1, Math.floor(limit));
|
|
24104
|
+
emitRuntimeWarning(
|
|
24105
|
+
`sweepJoint("${jointName}") requested ${requestedSteps} step(s), exceeding the ${getForgeQualityPreset()} quality sweep limit (${bounded}). Using ${bounded} step(s) for responsiveness. Use high quality/export, set FORGECAD_SWEEP_JOINT_STEP_LIMIT, or set FORGECAD_ALLOW_FULL_SWEEP_JOINT=1 for the full sweep.`
|
|
24106
|
+
);
|
|
24107
|
+
return bounded;
|
|
24108
|
+
}
|
|
24075
24109
|
const FASTENER_PATTERN = /\b(bolt|screw|nut|washer|pin|rivet|fastener|standoff|insert)\b/i;
|
|
24076
24110
|
function isFastenerName(name) {
|
|
24077
24111
|
return FASTENER_PATTERN.test(name);
|
|
@@ -24404,16 +24438,23 @@ class SolvedAssembly {
|
|
|
24404
24438
|
const minOverlap = options.minOverlapVolume ?? 0.1;
|
|
24405
24439
|
const ignore = new Set((options.ignorePairs ?? []).map(([a, b]) => [a, b].sort().join("|")));
|
|
24406
24440
|
const findings = [];
|
|
24407
|
-
|
|
24408
|
-
|
|
24409
|
-
|
|
24410
|
-
|
|
24441
|
+
const entries = [];
|
|
24442
|
+
for (const name of names) {
|
|
24443
|
+
const shape = collisionShape(this.getPart(name));
|
|
24444
|
+
if (!shape) continue;
|
|
24445
|
+
try {
|
|
24446
|
+
entries.push({ name, shape, bounds: shape.boundingBox() });
|
|
24447
|
+
} catch {
|
|
24448
|
+
}
|
|
24449
|
+
}
|
|
24450
|
+
for (let i = 0; i < entries.length; i++) {
|
|
24451
|
+
for (let j = i + 1; j < entries.length; j++) {
|
|
24452
|
+
const aName = entries[i].name;
|
|
24453
|
+
const bName = entries[j].name;
|
|
24411
24454
|
if (ignore.has([aName, bName].sort().join("|"))) continue;
|
|
24412
|
-
|
|
24413
|
-
const b = collisionShape(this.getPart(bName));
|
|
24414
|
-
if (!a || !b) continue;
|
|
24455
|
+
if (!boundsOverlap(entries[i].bounds, entries[j].bounds)) continue;
|
|
24415
24456
|
try {
|
|
24416
|
-
const hit =
|
|
24457
|
+
const hit = entries[i].shape.intersect(entries[j].shape);
|
|
24417
24458
|
if (hit.isEmpty()) continue;
|
|
24418
24459
|
const vol = hit.volume();
|
|
24419
24460
|
if (vol > minOverlap) {
|
|
@@ -25390,7 +25431,7 @@ class Assembly {
|
|
|
25390
25431
|
if (this.jointCouplings.has(jointName)) {
|
|
25391
25432
|
throw new Error(`Cannot sweep coupled joint "${jointName}". Sweep one of its source joints instead.`);
|
|
25392
25433
|
}
|
|
25393
|
-
const n = Math.max(1, Math.floor(steps));
|
|
25434
|
+
const n = boundSweepJointSteps(jointName, Math.max(1, Math.floor(steps)));
|
|
25394
25435
|
const frames = [];
|
|
25395
25436
|
for (let i = 0; i <= n; i++) {
|
|
25396
25437
|
const t = n === 0 ? 0 : i / n;
|
|
@@ -25806,12 +25847,12 @@ function bom(quantity, description, opts) {
|
|
|
25806
25847
|
metadata
|
|
25807
25848
|
});
|
|
25808
25849
|
}
|
|
25809
|
-
let _collected$
|
|
25850
|
+
let _collected$6 = [];
|
|
25810
25851
|
function resetCutPlanes() {
|
|
25811
|
-
_collected$
|
|
25852
|
+
_collected$6 = [];
|
|
25812
25853
|
}
|
|
25813
25854
|
function getCollectedCutPlanes() {
|
|
25814
|
-
return _collected$
|
|
25855
|
+
return _collected$6.slice();
|
|
25815
25856
|
}
|
|
25816
25857
|
function normalizeExcludedObjectNames(input) {
|
|
25817
25858
|
if (input === void 0) return void 0;
|
|
@@ -25827,16 +25868,16 @@ function cutPlane(name, normal, offsetOrOptions = 0, maybeOptions = {}) {
|
|
|
25827
25868
|
const offset2 = Number.isFinite(rawOffset) ? rawOffset : 0;
|
|
25828
25869
|
const options = usingOffsetArg ? maybeOptions : offsetOrOptions;
|
|
25829
25870
|
const excludeObjectNames = normalizeExcludedObjectNames(options.exclude);
|
|
25830
|
-
_collected$
|
|
25871
|
+
_collected$6.push({ name, normal, offset: offset2, excludeObjectNames });
|
|
25831
25872
|
}
|
|
25832
|
-
let _collected$
|
|
25873
|
+
let _collected$5 = [];
|
|
25833
25874
|
let _counter$1 = 0;
|
|
25834
25875
|
function resetMocks() {
|
|
25835
|
-
_collected$
|
|
25876
|
+
_collected$5 = [];
|
|
25836
25877
|
_counter$1 = 0;
|
|
25837
25878
|
}
|
|
25838
25879
|
function getCollectedMocks() {
|
|
25839
|
-
return _collected$
|
|
25880
|
+
return _collected$5.slice();
|
|
25840
25881
|
}
|
|
25841
25882
|
function mock(shape, name) {
|
|
25842
25883
|
if (!shape || typeof shape !== "object") {
|
|
@@ -25844,7 +25885,7 @@ function mock(shape, name) {
|
|
|
25844
25885
|
}
|
|
25845
25886
|
_counter$1 += 1;
|
|
25846
25887
|
const displayName = name && typeof name === "string" && name.trim().length > 0 ? name.trim() : `Mock ${_counter$1}`;
|
|
25847
|
-
_collected$
|
|
25888
|
+
_collected$5.push({
|
|
25848
25889
|
id: `mock-${_counter$1}`,
|
|
25849
25890
|
name: displayName,
|
|
25850
25891
|
shape
|
|
@@ -30236,7 +30277,7 @@ function shapeToBounds(shape) {
|
|
|
30236
30277
|
max: [bb.max[0], bb.max[1], bb.max[2]]
|
|
30237
30278
|
};
|
|
30238
30279
|
}
|
|
30239
|
-
function sub$
|
|
30280
|
+
function sub$3(a, b) {
|
|
30240
30281
|
return [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
|
|
30241
30282
|
}
|
|
30242
30283
|
function addVec(a, b) {
|
|
@@ -30245,7 +30286,7 @@ function addVec(a, b) {
|
|
|
30245
30286
|
function scale$2(v, s) {
|
|
30246
30287
|
return [v[0] * s, v[1] * s, v[2] * s];
|
|
30247
30288
|
}
|
|
30248
|
-
function dot$
|
|
30289
|
+
function dot$3(a, b) {
|
|
30249
30290
|
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
|
30250
30291
|
}
|
|
30251
30292
|
function cross$1(a, b) {
|
|
@@ -30269,9 +30310,9 @@ function pipeRoute(points, radius, options) {
|
|
|
30269
30310
|
const bends = new Array(points.length).fill(null);
|
|
30270
30311
|
for (let i = 1; i < points.length - 1; i++) {
|
|
30271
30312
|
const prev = points[i - 1], cur = points[i], next = points[i + 1];
|
|
30272
|
-
const dIn = normalize$2(sub$
|
|
30273
|
-
const dOut = normalize$2(sub$
|
|
30274
|
-
const dotVal = clampDot(dot$
|
|
30313
|
+
const dIn = normalize$2(sub$3(cur, prev));
|
|
30314
|
+
const dOut = normalize$2(sub$3(next, cur));
|
|
30315
|
+
const dotVal = clampDot(dot$3(dIn, dOut));
|
|
30275
30316
|
const bendAngle = Math.acos(dotVal);
|
|
30276
30317
|
if (bendAngle < 1e-6) {
|
|
30277
30318
|
continue;
|
|
@@ -30290,7 +30331,7 @@ function pipeRoute(points, radius, options) {
|
|
|
30290
30331
|
}
|
|
30291
30332
|
const parts = [];
|
|
30292
30333
|
const makeSeg = (a, b) => {
|
|
30293
|
-
const d = sub$
|
|
30334
|
+
const d = sub$3(b, a);
|
|
30294
30335
|
const len2 = vecLen(d);
|
|
30295
30336
|
if (len2 < 0.01) return null;
|
|
30296
30337
|
const dir = normalize$2(d);
|
|
@@ -30335,7 +30376,7 @@ function pipeRoute(points, radius, options) {
|
|
|
30335
30376
|
const innerBend = buildShapeFromCompilePlan(innerPlan);
|
|
30336
30377
|
bendShape = bendShape.subtract(innerBend);
|
|
30337
30378
|
}
|
|
30338
|
-
const radialDir = normalize$2(sub$
|
|
30379
|
+
const radialDir = normalize$2(sub$3(info.startPt, info.center));
|
|
30339
30380
|
const tangentDir = cross$1(info.axis, radialDir);
|
|
30340
30381
|
const c = info.center;
|
|
30341
30382
|
bendShape = bendShape.transform([
|
|
@@ -30396,7 +30437,7 @@ function elbow(pipeRadius, bendRadius, angle, options) {
|
|
|
30396
30437
|
if (fromDir && toDir) {
|
|
30397
30438
|
const nFrom = normalize$2(fromDir);
|
|
30398
30439
|
const nTo = normalize$2(toDir);
|
|
30399
|
-
const d = clampDot(dot$
|
|
30440
|
+
const d = clampDot(dot$3(nFrom, nTo));
|
|
30400
30441
|
angleDeg = Math.acos(d) * 180 / Math.PI;
|
|
30401
30442
|
}
|
|
30402
30443
|
if (angleDeg < 0.01) throw new Error("elbow: angle too small");
|
|
@@ -30467,20 +30508,20 @@ function assertFinitePositive(apiName, name, value) {
|
|
|
30467
30508
|
function add$1(a, b) {
|
|
30468
30509
|
return [a[0] + b[0], a[1] + b[1]];
|
|
30469
30510
|
}
|
|
30470
|
-
function sub$
|
|
30511
|
+
function sub$2(a, b) {
|
|
30471
30512
|
return [a[0] - b[0], a[1] - b[1]];
|
|
30472
30513
|
}
|
|
30473
30514
|
function scale$1(v, s) {
|
|
30474
30515
|
return [v[0] * s, v[1] * s];
|
|
30475
30516
|
}
|
|
30476
|
-
function dot$
|
|
30517
|
+
function dot$2(a, b) {
|
|
30477
30518
|
return a[0] * b[0] + a[1] * b[1];
|
|
30478
30519
|
}
|
|
30479
30520
|
function len(v) {
|
|
30480
30521
|
return Math.hypot(v[0], v[1]);
|
|
30481
30522
|
}
|
|
30482
30523
|
function dist$2(a, b) {
|
|
30483
|
-
return len(sub$
|
|
30524
|
+
return len(sub$2(b, a));
|
|
30484
30525
|
}
|
|
30485
30526
|
function norm$1(v) {
|
|
30486
30527
|
const l = len(v);
|
|
@@ -30505,7 +30546,7 @@ function chooseSweepDeg(center, start, end, incomingDir) {
|
|
|
30505
30546
|
const endAngle = angleOf(center, end);
|
|
30506
30547
|
const cwTangent = tangentAt(startAngle, true);
|
|
30507
30548
|
const ccwTangent = tangentAt(startAngle, false);
|
|
30508
|
-
const clockwise = dot$
|
|
30549
|
+
const clockwise = dot$2(cwTangent, incomingDir) >= dot$2(ccwTangent, incomingDir);
|
|
30509
30550
|
const sweep2 = clockwise ? -normalizePositiveRadians(startAngle - endAngle) : normalizePositiveRadians(endAngle - startAngle);
|
|
30510
30551
|
return sweep2 * 180 / Math.PI;
|
|
30511
30552
|
}
|
|
@@ -30548,8 +30589,8 @@ function normalizePulleyAsCircle(pulley, index, radiusOverride) {
|
|
|
30548
30589
|
);
|
|
30549
30590
|
}
|
|
30550
30591
|
function commonTangents(a, b, mode) {
|
|
30551
|
-
const delta = sub$
|
|
30552
|
-
const z = dot$
|
|
30592
|
+
const delta = sub$2(b.center, a.center);
|
|
30593
|
+
const z = dot$2(delta, delta);
|
|
30553
30594
|
if (z < EPS$5) {
|
|
30554
30595
|
throw new Error(`beltDrive: pulleys "${a.name}" and "${b.name}" have the same center.`);
|
|
30555
30596
|
}
|
|
@@ -30579,8 +30620,8 @@ function commonTangents(a, b, mode) {
|
|
|
30579
30620
|
});
|
|
30580
30621
|
}
|
|
30581
30622
|
function buildSegmentsForTangentOrder(a, b, t0, t1) {
|
|
30582
|
-
const span0Dir = norm$1(sub$
|
|
30583
|
-
const span1Dir = norm$1(sub$
|
|
30623
|
+
const span0Dir = norm$1(sub$2(t0.b, t0.a));
|
|
30624
|
+
const span1Dir = norm$1(sub$2(t1.a, t1.b));
|
|
30584
30625
|
const bSweepDeg = chooseSweepDeg(b.center, t0.b, t1.b, span0Dir);
|
|
30585
30626
|
const aSweepDeg = chooseSweepDeg(a.center, t1.a, t0.a, span1Dir);
|
|
30586
30627
|
const span0 = {
|
|
@@ -32081,6 +32122,130 @@ const partLibrary = {
|
|
|
32081
32122
|
planetaryRatio,
|
|
32082
32123
|
boltPattern
|
|
32083
32124
|
};
|
|
32125
|
+
let _collected$4 = [];
|
|
32126
|
+
let _nextId = 1;
|
|
32127
|
+
function resetRenderLabels() {
|
|
32128
|
+
_collected$4 = [];
|
|
32129
|
+
_nextId = 1;
|
|
32130
|
+
}
|
|
32131
|
+
function getCollectedRenderLabels() {
|
|
32132
|
+
return _collected$4.map((label) => ({ ...label, at: [...label.at], offset: [...label.offset] }));
|
|
32133
|
+
}
|
|
32134
|
+
function requireFinite$6(value, label) {
|
|
32135
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
32136
|
+
throw new Error(`${label} must be a finite number`);
|
|
32137
|
+
}
|
|
32138
|
+
return value;
|
|
32139
|
+
}
|
|
32140
|
+
function requireVec3$2(value, label) {
|
|
32141
|
+
if (!Array.isArray(value) || value.length !== 3) {
|
|
32142
|
+
throw new Error(`${label} must be [x, y, z]`);
|
|
32143
|
+
}
|
|
32144
|
+
return [requireFinite$6(value[0], `${label}[0]`), requireFinite$6(value[1], `${label}[1]`), requireFinite$6(value[2], `${label}[2]`)];
|
|
32145
|
+
}
|
|
32146
|
+
function optionalColor(value, label) {
|
|
32147
|
+
if (value === void 0) return void 0;
|
|
32148
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
32149
|
+
throw new Error(`${label} must be a non-empty CSS color string`);
|
|
32150
|
+
}
|
|
32151
|
+
return value.trim();
|
|
32152
|
+
}
|
|
32153
|
+
const VALID_ANCHORS = /* @__PURE__ */ new Set([
|
|
32154
|
+
"center",
|
|
32155
|
+
"top",
|
|
32156
|
+
"bottom",
|
|
32157
|
+
"left",
|
|
32158
|
+
"right",
|
|
32159
|
+
"top-left",
|
|
32160
|
+
"top-right",
|
|
32161
|
+
"bottom-left",
|
|
32162
|
+
"bottom-right"
|
|
32163
|
+
]);
|
|
32164
|
+
function normalizeOptions(options) {
|
|
32165
|
+
if (options === void 0) {
|
|
32166
|
+
return { offset: [0, 0, 0], anchor: "center", alwaysOnTop: true };
|
|
32167
|
+
}
|
|
32168
|
+
if (!options || typeof options !== "object" || Array.isArray(options)) {
|
|
32169
|
+
throw new Error("Viewport.label options must be an object");
|
|
32170
|
+
}
|
|
32171
|
+
const out = {
|
|
32172
|
+
offset: [0, 0, 0],
|
|
32173
|
+
anchor: "center",
|
|
32174
|
+
alwaysOnTop: true
|
|
32175
|
+
};
|
|
32176
|
+
const color = optionalColor(options.color, "Viewport.label options.color");
|
|
32177
|
+
if (color !== void 0) out.color = color;
|
|
32178
|
+
const background = optionalColor(options.background, "Viewport.label options.background");
|
|
32179
|
+
if (background !== void 0) out.background = background;
|
|
32180
|
+
if (options.size !== void 0) {
|
|
32181
|
+
out.size = requireFinite$6(options.size, "Viewport.label options.size");
|
|
32182
|
+
if (out.size <= 0) throw new Error("Viewport.label options.size must be positive");
|
|
32183
|
+
}
|
|
32184
|
+
if (options.offset !== void 0) out.offset = requireVec3$2(options.offset, "Viewport.label options.offset");
|
|
32185
|
+
if (options.anchor !== void 0) {
|
|
32186
|
+
if (!VALID_ANCHORS.has(options.anchor)) {
|
|
32187
|
+
throw new Error(`Viewport.label options.anchor must be one of: ${Array.from(VALID_ANCHORS).join(", ")}`);
|
|
32188
|
+
}
|
|
32189
|
+
out.anchor = options.anchor;
|
|
32190
|
+
}
|
|
32191
|
+
if (options.alwaysOnTop !== void 0) {
|
|
32192
|
+
if (typeof options.alwaysOnTop !== "boolean") throw new Error("Viewport.label options.alwaysOnTop must be a boolean");
|
|
32193
|
+
out.alwaysOnTop = options.alwaysOnTop;
|
|
32194
|
+
}
|
|
32195
|
+
return out;
|
|
32196
|
+
}
|
|
32197
|
+
function collectRenderLabel(text, at, options) {
|
|
32198
|
+
if (typeof text !== "string" || text.trim().length === 0) {
|
|
32199
|
+
throw new Error("Viewport.label text must be a non-empty string");
|
|
32200
|
+
}
|
|
32201
|
+
const normalizedAt = requireVec3$2(at, "Viewport.label at");
|
|
32202
|
+
const normalizedOptions = normalizeOptions(options);
|
|
32203
|
+
_collected$4.push({
|
|
32204
|
+
id: `render-label-${_nextId++}`,
|
|
32205
|
+
text: text.trim(),
|
|
32206
|
+
at: normalizedAt,
|
|
32207
|
+
...normalizedOptions
|
|
32208
|
+
});
|
|
32209
|
+
}
|
|
32210
|
+
const Viewport = {
|
|
32211
|
+
/**
|
|
32212
|
+
* Add a render-only viewport label at a world-space point.
|
|
32213
|
+
*
|
|
32214
|
+
* **Details**
|
|
32215
|
+
*
|
|
32216
|
+
* `Viewport.label()` is for explanatory text that helps a viewer understand
|
|
32217
|
+
* the model. It does not create sketches, meshes, B-rep topology, exported
|
|
32218
|
+
* text, or face labels, so it stays off the OCCT path. Use `text2d()` only
|
|
32219
|
+
* when the letters should become manufactured geometry, such as raised
|
|
32220
|
+
* lettering, engraved serial numbers, or exported nameplates.
|
|
32221
|
+
*
|
|
32222
|
+
* Labels are collected during script execution and rendered by the viewport
|
|
32223
|
+
* as lightweight overlay annotations. They are ignored by exports and do not
|
|
32224
|
+
* appear in `objects`.
|
|
32225
|
+
*
|
|
32226
|
+
* **Example**
|
|
32227
|
+
*
|
|
32228
|
+
* ```js
|
|
32229
|
+
* Viewport.label('Bearing bore', [0, 0, 18], {
|
|
32230
|
+
* color: '#f8fafc',
|
|
32231
|
+
* background: '#0f172acc',
|
|
32232
|
+
* offset: [0, 0, 8],
|
|
32233
|
+
* anchor: 'bottom',
|
|
32234
|
+
* });
|
|
32235
|
+
*
|
|
32236
|
+
* return box(40, 30, 12);
|
|
32237
|
+
* ```
|
|
32238
|
+
*
|
|
32239
|
+
* @param text - Label text to display in the viewport
|
|
32240
|
+
* @param at - World-space anchor point `[x, y, z]`
|
|
32241
|
+
* @param options - Visual label options
|
|
32242
|
+
* @returns void
|
|
32243
|
+
* @category Viewport Labels
|
|
32244
|
+
*/
|
|
32245
|
+
label(text, at, options) {
|
|
32246
|
+
collectRenderLabel(text, at, options);
|
|
32247
|
+
}
|
|
32248
|
+
};
|
|
32084
32249
|
const RENDER_STYLE_OPTIONS = [
|
|
32085
32250
|
{
|
|
32086
32251
|
id: "classic",
|
|
@@ -32267,6 +32432,120 @@ function validateCamera(cam, label) {
|
|
|
32267
32432
|
}
|
|
32268
32433
|
return out;
|
|
32269
32434
|
}
|
|
32435
|
+
function validateViewCamera(cam, label) {
|
|
32436
|
+
const validated = validateCamera(cam, label);
|
|
32437
|
+
if (!validated.position) {
|
|
32438
|
+
throw new Error(`${label}.position is required for named render views`);
|
|
32439
|
+
}
|
|
32440
|
+
if (!validated.target) {
|
|
32441
|
+
throw new Error(`${label}.target is required for named render views`);
|
|
32442
|
+
}
|
|
32443
|
+
return {
|
|
32444
|
+
...validated,
|
|
32445
|
+
position: validated.position,
|
|
32446
|
+
target: validated.target
|
|
32447
|
+
};
|
|
32448
|
+
}
|
|
32449
|
+
function validateViews(views, label) {
|
|
32450
|
+
if (!views || typeof views !== "object" || Array.isArray(views)) {
|
|
32451
|
+
throw new Error(`${label} must be an object mapping view names to cameras`);
|
|
32452
|
+
}
|
|
32453
|
+
const out = {};
|
|
32454
|
+
for (const [name, view] of Object.entries(views)) {
|
|
32455
|
+
if (!name.trim()) {
|
|
32456
|
+
throw new Error(`${label} names must be non-empty strings`);
|
|
32457
|
+
}
|
|
32458
|
+
const viewLabel = `${label}.${name}`;
|
|
32459
|
+
if (!view || typeof view !== "object" || Array.isArray(view)) {
|
|
32460
|
+
throw new Error(`${viewLabel} must be a camera object or an object with a camera property`);
|
|
32461
|
+
}
|
|
32462
|
+
const hasExplicitCamera = Object.prototype.hasOwnProperty.call(view, "camera");
|
|
32463
|
+
if (hasExplicitCamera) {
|
|
32464
|
+
const camera = view.camera;
|
|
32465
|
+
if (!camera || typeof camera !== "object" || Array.isArray(camera)) {
|
|
32466
|
+
throw new Error(`${viewLabel}.camera must be an object`);
|
|
32467
|
+
}
|
|
32468
|
+
out[name] = { camera: validateViewCamera(camera, `${viewLabel}.camera`) };
|
|
32469
|
+
continue;
|
|
32470
|
+
}
|
|
32471
|
+
out[name] = { camera: validateViewCamera(view, viewLabel) };
|
|
32472
|
+
}
|
|
32473
|
+
return out;
|
|
32474
|
+
}
|
|
32475
|
+
function requireString(value, label) {
|
|
32476
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
32477
|
+
throw new Error(`${label} must be a non-empty string`);
|
|
32478
|
+
}
|
|
32479
|
+
return value.trim();
|
|
32480
|
+
}
|
|
32481
|
+
function optionalString(value, label) {
|
|
32482
|
+
if (value === void 0) return void 0;
|
|
32483
|
+
return requireString(value, label);
|
|
32484
|
+
}
|
|
32485
|
+
function validateJourneyStep(step, label) {
|
|
32486
|
+
if (!step || typeof step !== "object" || Array.isArray(step)) {
|
|
32487
|
+
throw new Error(`${label} must be an object`);
|
|
32488
|
+
}
|
|
32489
|
+
const out = {
|
|
32490
|
+
id: requireString(step.id, `${label}.id`)
|
|
32491
|
+
};
|
|
32492
|
+
const title = optionalString(step.title, `${label}.title`);
|
|
32493
|
+
if (title !== void 0) out.title = title;
|
|
32494
|
+
const focus = optionalString(step.focus, `${label}.focus`);
|
|
32495
|
+
if (focus !== void 0) out.focus = focus;
|
|
32496
|
+
const caption = optionalString(step.caption, `${label}.caption`);
|
|
32497
|
+
if (caption !== void 0) out.caption = caption;
|
|
32498
|
+
if (step.camera !== void 0) {
|
|
32499
|
+
if (!step.camera || typeof step.camera !== "object" || Array.isArray(step.camera)) {
|
|
32500
|
+
throw new Error(`${label}.camera must be an object`);
|
|
32501
|
+
}
|
|
32502
|
+
out.camera = validateViewCamera(step.camera, `${label}.camera`);
|
|
32503
|
+
}
|
|
32504
|
+
return out;
|
|
32505
|
+
}
|
|
32506
|
+
function validateJourney(journey, label) {
|
|
32507
|
+
if (!journey || typeof journey !== "object" || Array.isArray(journey)) {
|
|
32508
|
+
throw new Error(`${label} must be an object`);
|
|
32509
|
+
}
|
|
32510
|
+
if (!Array.isArray(journey.steps) || journey.steps.length === 0) {
|
|
32511
|
+
throw new Error(`${label}.steps must be a non-empty array`);
|
|
32512
|
+
}
|
|
32513
|
+
const out = {
|
|
32514
|
+
steps: journey.steps.map((step, index) => validateJourneyStep(step, `${label}.steps[${index}]`))
|
|
32515
|
+
};
|
|
32516
|
+
const title = optionalString(journey.title, `${label}.title`);
|
|
32517
|
+
if (title !== void 0) out.title = title;
|
|
32518
|
+
const startsAt = optionalString(journey.startsAt, `${label}.startsAt`);
|
|
32519
|
+
if (startsAt !== void 0) out.startsAt = startsAt;
|
|
32520
|
+
if (journey.behavior !== void 0) {
|
|
32521
|
+
if (journey.behavior !== "opt-in" && journey.behavior !== "auto") {
|
|
32522
|
+
throw new Error(`${label}.behavior must be "opt-in" or "auto"`);
|
|
32523
|
+
}
|
|
32524
|
+
out.behavior = journey.behavior;
|
|
32525
|
+
}
|
|
32526
|
+
const seen = /* @__PURE__ */ new Set();
|
|
32527
|
+
for (const step of out.steps) {
|
|
32528
|
+
if (seen.has(step.id)) {
|
|
32529
|
+
throw new Error(`${label}.steps contains duplicate step id "${step.id}"`);
|
|
32530
|
+
}
|
|
32531
|
+
seen.add(step.id);
|
|
32532
|
+
}
|
|
32533
|
+
if (out.startsAt && !seen.has(out.startsAt)) {
|
|
32534
|
+
throw new Error(`${label}.startsAt "${out.startsAt}" does not match any step id`);
|
|
32535
|
+
}
|
|
32536
|
+
return out;
|
|
32537
|
+
}
|
|
32538
|
+
function validateJourneys(journeys, label) {
|
|
32539
|
+
if (!journeys || typeof journeys !== "object" || Array.isArray(journeys)) {
|
|
32540
|
+
throw new Error(`${label} must be an object mapping journey ids to journey configs`);
|
|
32541
|
+
}
|
|
32542
|
+
const out = {};
|
|
32543
|
+
for (const [id, journey] of Object.entries(journeys)) {
|
|
32544
|
+
const normalizedId = requireString(id, `${label} journey id`);
|
|
32545
|
+
out[normalizedId] = validateJourney(journey, `${label}.${normalizedId}`);
|
|
32546
|
+
}
|
|
32547
|
+
return out;
|
|
32548
|
+
}
|
|
32270
32549
|
function validateLight(light, label) {
|
|
32271
32550
|
if (!light || typeof light !== "object") throw new Error(`${label} must be an object`);
|
|
32272
32551
|
if (!VALID_LIGHT_TYPES.has(light.type)) {
|
|
@@ -32410,7 +32689,18 @@ function scene(options) {
|
|
|
32410
32689
|
if (!options || typeof options !== "object") {
|
|
32411
32690
|
throw new Error("scene(options) expects an options object");
|
|
32412
32691
|
}
|
|
32413
|
-
const current = _collected$3 ? { ..._collected$3 } : {
|
|
32692
|
+
const current = _collected$3 ? { ..._collected$3 } : {
|
|
32693
|
+
background: null,
|
|
32694
|
+
camera: null,
|
|
32695
|
+
views: null,
|
|
32696
|
+
journeys: null,
|
|
32697
|
+
lights: null,
|
|
32698
|
+
environment: null,
|
|
32699
|
+
fog: null,
|
|
32700
|
+
postProcessing: null,
|
|
32701
|
+
ground: null,
|
|
32702
|
+
capture: null
|
|
32703
|
+
};
|
|
32414
32704
|
if (options.background !== void 0) {
|
|
32415
32705
|
current.background = validateBackground(options.background, "scene.background");
|
|
32416
32706
|
}
|
|
@@ -32421,6 +32711,14 @@ function scene(options) {
|
|
|
32421
32711
|
const validated = validateCamera(options.camera, "scene.camera");
|
|
32422
32712
|
current.camera = current.camera ? { ...current.camera, ...validated } : validated;
|
|
32423
32713
|
}
|
|
32714
|
+
if (options.views !== void 0) {
|
|
32715
|
+
const validated = validateViews(options.views, "scene.views");
|
|
32716
|
+
current.views = current.views ? { ...current.views, ...validated } : validated;
|
|
32717
|
+
}
|
|
32718
|
+
if (options.journeys !== void 0) {
|
|
32719
|
+
const validated = validateJourneys(options.journeys, "scene.journeys");
|
|
32720
|
+
current.journeys = current.journeys ? { ...current.journeys, ...validated } : validated;
|
|
32721
|
+
}
|
|
32424
32722
|
if (options.lights !== void 0) {
|
|
32425
32723
|
if (!Array.isArray(options.lights)) {
|
|
32426
32724
|
throw new Error("scene.lights must be an array");
|
|
@@ -32461,6 +32759,74 @@ function scene(options) {
|
|
|
32461
32759
|
}
|
|
32462
32760
|
_collected$3 = current;
|
|
32463
32761
|
}
|
|
32762
|
+
const targetPath = (target) => {
|
|
32763
|
+
var _a3;
|
|
32764
|
+
const path2 = (_a3 = target.treePath) == null ? void 0 : _a3.filter((entry) => entry.trim());
|
|
32765
|
+
return path2 && path2.length > 0 ? path2.join("/") : target.name;
|
|
32766
|
+
};
|
|
32767
|
+
const hasErrorDiagnostic = (diagnostics) => diagnostics.some((diagnostic) => diagnostic.level === "error");
|
|
32768
|
+
function resolveJourneyFocus(focus, targets) {
|
|
32769
|
+
return targets.filter((target) => target.name === focus || targetPath(target) === focus);
|
|
32770
|
+
}
|
|
32771
|
+
function formatAvailableTargets(targets) {
|
|
32772
|
+
return targets.map((target) => targetPath(target)).filter((value, index, values) => values.indexOf(value) === index).sort().slice(0, 8);
|
|
32773
|
+
}
|
|
32774
|
+
function resolveSceneJourneyTargets(config, targets) {
|
|
32775
|
+
if (!(config == null ? void 0 : config.journeys)) return config;
|
|
32776
|
+
const journeys = {};
|
|
32777
|
+
for (const [journeyId, journey] of Object.entries(config.journeys)) {
|
|
32778
|
+
const journeyDiagnostics = [...journey.diagnostics ?? []];
|
|
32779
|
+
const steps = journey.steps.map((step) => {
|
|
32780
|
+
const stepDiagnostics = [...step.diagnostics ?? []];
|
|
32781
|
+
let resolvedFocusId = null;
|
|
32782
|
+
let resolvedFocusPath = null;
|
|
32783
|
+
if (step.focus) {
|
|
32784
|
+
const matches = resolveJourneyFocus(step.focus, targets);
|
|
32785
|
+
if (matches.length === 1) {
|
|
32786
|
+
resolvedFocusId = matches[0].id;
|
|
32787
|
+
resolvedFocusPath = targetPath(matches[0]);
|
|
32788
|
+
} else if (matches.length === 0) {
|
|
32789
|
+
stepDiagnostics.push({
|
|
32790
|
+
level: "error",
|
|
32791
|
+
stepId: step.id,
|
|
32792
|
+
message: `focus "${step.focus}" did not match any returned object by name or tree path.`,
|
|
32793
|
+
suggestions: formatAvailableTargets(targets)
|
|
32794
|
+
});
|
|
32795
|
+
} else {
|
|
32796
|
+
stepDiagnostics.push({
|
|
32797
|
+
level: "error",
|
|
32798
|
+
stepId: step.id,
|
|
32799
|
+
message: `focus "${step.focus}" matched ${matches.length} objects. Use a slash-separated tree path.`,
|
|
32800
|
+
suggestions: matches.map((match) => targetPath(match))
|
|
32801
|
+
});
|
|
32802
|
+
}
|
|
32803
|
+
} else if (!step.camera) {
|
|
32804
|
+
stepDiagnostics.push({
|
|
32805
|
+
level: "warning",
|
|
32806
|
+
stepId: step.id,
|
|
32807
|
+
message: "step has no focus or explicit camera, so the viewer can show the caption but cannot move the camera."
|
|
32808
|
+
});
|
|
32809
|
+
}
|
|
32810
|
+
journeyDiagnostics.push(...stepDiagnostics);
|
|
32811
|
+
return {
|
|
32812
|
+
...step,
|
|
32813
|
+
resolvedFocusId,
|
|
32814
|
+
resolvedFocusPath,
|
|
32815
|
+
diagnostics: stepDiagnostics.length > 0 ? stepDiagnostics : void 0
|
|
32816
|
+
};
|
|
32817
|
+
});
|
|
32818
|
+
journeys[journeyId] = {
|
|
32819
|
+
...journey,
|
|
32820
|
+
steps,
|
|
32821
|
+
valid: !hasErrorDiagnostic(journeyDiagnostics),
|
|
32822
|
+
diagnostics: journeyDiagnostics.length > 0 ? journeyDiagnostics : void 0
|
|
32823
|
+
};
|
|
32824
|
+
}
|
|
32825
|
+
return {
|
|
32826
|
+
...config,
|
|
32827
|
+
journeys
|
|
32828
|
+
};
|
|
32829
|
+
}
|
|
32464
32830
|
const validateColor = (value, label) => {
|
|
32465
32831
|
if (typeof value !== "string") throw new Error(`${label} must be a string`);
|
|
32466
32832
|
const trimmed = value.trim();
|
|
@@ -33706,9 +34072,15 @@ function cross(a, b) {
|
|
|
33706
34072
|
function add(a, b) {
|
|
33707
34073
|
return [a[0] + b[0], a[1] + b[1], a[2] + b[2]];
|
|
33708
34074
|
}
|
|
34075
|
+
function sub$1(a, b) {
|
|
34076
|
+
return [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
|
|
34077
|
+
}
|
|
33709
34078
|
function scale(v, s) {
|
|
33710
34079
|
return [v[0] * s, v[1] * s, v[2] * s];
|
|
33711
34080
|
}
|
|
34081
|
+
function dot$1(a, b) {
|
|
34082
|
+
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
|
34083
|
+
}
|
|
33712
34084
|
function lerp$1(a, b, t) {
|
|
33713
34085
|
return a + (b - a) * t;
|
|
33714
34086
|
}
|
|
@@ -33741,6 +34113,65 @@ function sideVectors(axis) {
|
|
|
33741
34113
|
function cloneQuery(query) {
|
|
33742
34114
|
return { side: query.side, u: query.u, v: query.v, offset: query.offset };
|
|
33743
34115
|
}
|
|
34116
|
+
function normalizedSide(side) {
|
|
34117
|
+
return side === "back" ? "rear" : side;
|
|
34118
|
+
}
|
|
34119
|
+
function profileExponent(station) {
|
|
34120
|
+
if (station.profile.kind === "superEllipse") return station.profile.exponent ?? 3.2;
|
|
34121
|
+
if (station.profile.kind === "roundedRect") return 4.5;
|
|
34122
|
+
return 2;
|
|
34123
|
+
}
|
|
34124
|
+
function superEllipsePoint(rx, ry, exponent, angle) {
|
|
34125
|
+
const cos2 = Math.cos(angle);
|
|
34126
|
+
const sin2 = Math.sin(angle);
|
|
34127
|
+
const x = rx * Math.sign(cos2) * Math.abs(cos2) ** (2 / exponent);
|
|
34128
|
+
const y = ry * Math.sign(sin2) * Math.abs(sin2) ** (2 / exponent);
|
|
34129
|
+
const nx = Math.sign(x) * Math.abs(x / Math.max(rx, EPS$4)) ** Math.max(exponent - 1, 1e-3);
|
|
34130
|
+
const ny = Math.sign(y) * Math.abs(y / Math.max(ry, EPS$4)) ** Math.max(exponent - 1, 1e-3);
|
|
34131
|
+
const nLen = Math.hypot(nx, ny);
|
|
34132
|
+
return {
|
|
34133
|
+
point: [x, y],
|
|
34134
|
+
normal: nLen < EPS$4 ? [Math.sign(cos2), Math.sign(sin2)] : [nx / nLen, ny / nLen]
|
|
34135
|
+
};
|
|
34136
|
+
}
|
|
34137
|
+
function angleForSide(side, u) {
|
|
34138
|
+
const t = clamp$4(u, 0, 1);
|
|
34139
|
+
if (side === "right") return -Math.PI / 2 + t * Math.PI;
|
|
34140
|
+
if (side === "left") return Math.PI / 2 + t * Math.PI;
|
|
34141
|
+
if (side === "top") return Math.PI - t * Math.PI;
|
|
34142
|
+
if (side === "bottom") return Math.PI + t * Math.PI;
|
|
34143
|
+
return null;
|
|
34144
|
+
}
|
|
34145
|
+
function sideSpan(side, width, depth) {
|
|
34146
|
+
if (side === "left" || side === "right") return Math.max(depth, EPS$4);
|
|
34147
|
+
if (side === "top" || side === "bottom") return Math.max(width, EPS$4);
|
|
34148
|
+
return Math.max(width, depth, EPS$4);
|
|
34149
|
+
}
|
|
34150
|
+
function interpolateQuery(a, b, t) {
|
|
34151
|
+
const sideA = normalizedSide(a.side);
|
|
34152
|
+
const sideB = normalizedSide(b.side);
|
|
34153
|
+
if (sideA !== sideB) {
|
|
34154
|
+
throw new Error(
|
|
34155
|
+
`Product.ribbon().on(...) currently samples one skin side per ribbon; got '${a.side}' then '${b.side}'. Split this into separate ribbons at the side transition.`
|
|
34156
|
+
);
|
|
34157
|
+
}
|
|
34158
|
+
return {
|
|
34159
|
+
side: sideA,
|
|
34160
|
+
u: lerp$1(a.u ?? 0.5, b.u ?? 0.5, t),
|
|
34161
|
+
v: lerp$1(a.v ?? 0.5, b.v ?? 0.5, t),
|
|
34162
|
+
offset: lerp$1(a.offset ?? 0, b.offset ?? 0, t)
|
|
34163
|
+
};
|
|
34164
|
+
}
|
|
34165
|
+
function resolvePathQueries(points) {
|
|
34166
|
+
return points.map((point2) => point2 instanceof ProductSurfaceRef ? point2.querySpec() : cloneQuery(point2));
|
|
34167
|
+
}
|
|
34168
|
+
function orientGridToNormal(grid, desiredNormal) {
|
|
34169
|
+
if (grid.length < 2 || grid[0].length < 2) return grid;
|
|
34170
|
+
const widthEdge = sub$1(grid[grid.length - 1][0], grid[0][0]);
|
|
34171
|
+
const lengthEdge = sub$1(grid[0][grid[0].length - 1], grid[0][0]);
|
|
34172
|
+
const actual = norm(cross(widthEdge, lengthEdge));
|
|
34173
|
+
return dot$1(actual, desiredNormal) < 0 ? [...grid].reverse() : grid;
|
|
34174
|
+
}
|
|
33744
34175
|
function isStationBuilder(input) {
|
|
33745
34176
|
return typeof input.toSpec === "function";
|
|
33746
34177
|
}
|
|
@@ -33842,6 +34273,15 @@ class ProductSkin {
|
|
|
33842
34273
|
curveOnSurface(name, points) {
|
|
33843
34274
|
return points.map((point2, index) => new ProductSurfaceRef(this, { u: 0.5, v: 0.5, ...point2 }, `${name}/${index}`));
|
|
33844
34275
|
}
|
|
34276
|
+
/**
|
|
34277
|
+
* Create a fluent surface helper for refs and conformal features on one side of this skin.
|
|
34278
|
+
*
|
|
34279
|
+
* Use this when several refs or ribbons share the same skin side; side-local helpers keep
|
|
34280
|
+
* path points concise and make it harder to mix sides accidentally.
|
|
34281
|
+
*/
|
|
34282
|
+
surface(side) {
|
|
34283
|
+
return new ProductSurfaceBuilder(this, side);
|
|
34284
|
+
}
|
|
33845
34285
|
/** Interpolate center, width, and depth at a normalized v or absolute axis value. */
|
|
33846
34286
|
stationAt(vOrAxis) {
|
|
33847
34287
|
const axisValue = vOrAxis >= 0 && vOrAxis <= 1 ? lerp$1(this.axisMin, this.axisMax, vOrAxis) : clamp$4(vOrAxis, this.axisMin, this.axisMax);
|
|
@@ -33860,7 +34300,9 @@ class ProductSkin {
|
|
|
33860
34300
|
width: lerp$1(a.profile.width, b.profile.width, t),
|
|
33861
34301
|
depth: lerp$1(a.profile.depth, b.profile.depth, t),
|
|
33862
34302
|
dWidth: (b.profile.width - a.profile.width) / span,
|
|
33863
|
-
dDepth: (b.profile.depth - a.profile.depth) / span
|
|
34303
|
+
dDepth: (b.profile.depth - a.profile.depth) / span,
|
|
34304
|
+
exponent: lerp$1(profileExponent(a), profileExponent(b), t),
|
|
34305
|
+
kind: a.profile.kind === b.profile.kind ? a.profile.kind : "custom"
|
|
33864
34306
|
};
|
|
33865
34307
|
}
|
|
33866
34308
|
const last = sorted[sorted.length - 1];
|
|
@@ -33870,12 +34312,14 @@ class ProductSkin {
|
|
|
33870
34312
|
width: last.profile.width,
|
|
33871
34313
|
depth: last.profile.depth,
|
|
33872
34314
|
dWidth: 0,
|
|
33873
|
-
dDepth: 0
|
|
34315
|
+
dDepth: 0,
|
|
34316
|
+
exponent: profileExponent(last),
|
|
34317
|
+
kind: last.profile.kind
|
|
33874
34318
|
};
|
|
33875
34319
|
}
|
|
33876
34320
|
/** Build a local surface frame from a side/u/v query. */
|
|
33877
34321
|
frame(query) {
|
|
33878
|
-
const side = query.side
|
|
34322
|
+
const side = normalizedSide(query.side);
|
|
33879
34323
|
const offset2 = query.offset ?? 0;
|
|
33880
34324
|
const basis = sideVectors(this.axis);
|
|
33881
34325
|
const isFrontCap = side === "front";
|
|
@@ -33899,11 +34343,31 @@ class ProductSkin {
|
|
|
33899
34343
|
}
|
|
33900
34344
|
const station = this.stationAt(query.v ?? 0.5);
|
|
33901
34345
|
const u = clamp$4(query.u ?? 0.5, 0, 1) - 0.5;
|
|
34346
|
+
const sideAngle = angleForSide(side, query.u ?? 0.5);
|
|
33902
34347
|
let crossA = 0;
|
|
33903
34348
|
let crossB = 0;
|
|
33904
34349
|
let normal = [0, 0, 1];
|
|
33905
34350
|
let tangentU = basis.crossA;
|
|
33906
|
-
if (
|
|
34351
|
+
if (sideAngle != null) {
|
|
34352
|
+
const section = superEllipsePoint(station.width / 2, station.depth / 2, station.exponent, sideAngle);
|
|
34353
|
+
crossA = section.point[0];
|
|
34354
|
+
crossB = section.point[1];
|
|
34355
|
+
normal = norm(add(scale(basis.crossA, section.normal[0]), scale(basis.crossB, section.normal[1])));
|
|
34356
|
+
const delta = 2e-3;
|
|
34357
|
+
const prev = superEllipsePoint(
|
|
34358
|
+
station.width / 2,
|
|
34359
|
+
station.depth / 2,
|
|
34360
|
+
station.exponent,
|
|
34361
|
+
angleForSide(side, clamp$4((query.u ?? 0.5) - delta, 0, 1)) ?? sideAngle
|
|
34362
|
+
).point;
|
|
34363
|
+
const next = superEllipsePoint(
|
|
34364
|
+
station.width / 2,
|
|
34365
|
+
station.depth / 2,
|
|
34366
|
+
station.exponent,
|
|
34367
|
+
angleForSide(side, clamp$4((query.u ?? 0.5) + delta, 0, 1)) ?? sideAngle
|
|
34368
|
+
).point;
|
|
34369
|
+
tangentU = norm(add(scale(basis.crossA, next[0] - prev[0]), scale(basis.crossB, next[1] - prev[1])));
|
|
34370
|
+
} else if (side === "right") {
|
|
33907
34371
|
crossA = station.width / 2;
|
|
33908
34372
|
crossB = u * station.depth;
|
|
33909
34373
|
normal = basis.crossA;
|
|
@@ -33925,7 +34389,7 @@ class ProductSkin {
|
|
|
33925
34389
|
tangentU = basis.crossA;
|
|
33926
34390
|
}
|
|
33927
34391
|
normal = norm(normal);
|
|
33928
|
-
tangentU = norm(tangentU);
|
|
34392
|
+
tangentU = norm(sub$1(tangentU, scale(normal, dot$1(tangentU, normal))));
|
|
33929
34393
|
const tangentV = norm(cross(normal, tangentU));
|
|
33930
34394
|
const point2 = add(add(station.center, add(scale(basis.crossA, crossA), scale(basis.crossB, crossB))), scale(normal, offset2));
|
|
33931
34395
|
return {
|
|
@@ -34245,6 +34709,303 @@ class ProductHandleBuilder {
|
|
|
34245
34709
|
return new ProductHandleFeature(grip, upperPad, lowerPad);
|
|
34246
34710
|
}
|
|
34247
34711
|
}
|
|
34712
|
+
class ProductSurfaceBuilder {
|
|
34713
|
+
constructor(skin, side) {
|
|
34714
|
+
this.skin = skin;
|
|
34715
|
+
this.side = side;
|
|
34716
|
+
}
|
|
34717
|
+
/** Create a ref on this skin side. */
|
|
34718
|
+
ref(u = 0.5, v = 0.5, offset2) {
|
|
34719
|
+
return Product.ref(this.skin, {
|
|
34720
|
+
side: this.side,
|
|
34721
|
+
u,
|
|
34722
|
+
v,
|
|
34723
|
+
...offset2 != null ? { offset: offset2 } : {}
|
|
34724
|
+
});
|
|
34725
|
+
}
|
|
34726
|
+
/** Create a side/u/v query on this skin side. */
|
|
34727
|
+
uv(u = 0.5, v = 0.5, offset2) {
|
|
34728
|
+
return {
|
|
34729
|
+
side: this.side,
|
|
34730
|
+
u,
|
|
34731
|
+
v,
|
|
34732
|
+
...offset2 != null ? { offset: offset2 } : {}
|
|
34733
|
+
};
|
|
34734
|
+
}
|
|
34735
|
+
/**
|
|
34736
|
+
* Start a conformal ribbon on this skin side.
|
|
34737
|
+
*
|
|
34738
|
+
* Path points use side-local `u`/`v` coordinates; this builder supplies the side.
|
|
34739
|
+
* The returned ProductRibbonBuilder is already bound to the source skin and can be further
|
|
34740
|
+
* configured before build(). Use `widthSamples` >= 3 when the ribbon must visibly wrap over
|
|
34741
|
+
* curved product sections instead of behaving like a flat strip.
|
|
34742
|
+
*/
|
|
34743
|
+
ribbon(name, points, options = {}) {
|
|
34744
|
+
if (points.length < 2) throw new Error("Product.surface(...).ribbon(name, points) requires at least two path points");
|
|
34745
|
+
const path2 = points.map((point2) => ({
|
|
34746
|
+
side: this.side,
|
|
34747
|
+
u: point2.u ?? 0.5,
|
|
34748
|
+
v: point2.v ?? 0.5,
|
|
34749
|
+
...point2.offset != null ? { offset: point2.offset } : {}
|
|
34750
|
+
}));
|
|
34751
|
+
return new ProductRibbonBuilder(name).on(this.skin, path2, options);
|
|
34752
|
+
}
|
|
34753
|
+
}
|
|
34754
|
+
class ProductRibbonBuilder {
|
|
34755
|
+
constructor(name) {
|
|
34756
|
+
__publicField(this, "skinValue");
|
|
34757
|
+
__publicField(this, "queryPath", []);
|
|
34758
|
+
__publicField(this, "refPath", []);
|
|
34759
|
+
__publicField(this, "widthValue", 6);
|
|
34760
|
+
__publicField(this, "thicknessValue", 0.8);
|
|
34761
|
+
__publicField(this, "offsetValue", 0.25);
|
|
34762
|
+
__publicField(this, "samplesValue", 24);
|
|
34763
|
+
__publicField(this, "widthSamplesValue", 5);
|
|
34764
|
+
__publicField(this, "resolutionValue");
|
|
34765
|
+
__publicField(this, "materialValue");
|
|
34766
|
+
__publicField(this, "colorValue");
|
|
34767
|
+
__publicField(this, "lastDiagnosticsValue");
|
|
34768
|
+
this.name = name;
|
|
34769
|
+
if (!name || !name.trim()) throw new Error("Product.ribbon(name) requires a non-empty name");
|
|
34770
|
+
}
|
|
34771
|
+
/**
|
|
34772
|
+
* Follow a ProductSkin with side/u/v path queries or refs.
|
|
34773
|
+
*
|
|
34774
|
+
* This is the highest-fidelity mode because every interpolated sample is resolved through
|
|
34775
|
+
* ProductSkin.frame(), so the ribbon bends along the selected side as station width/depth changes.
|
|
34776
|
+
* All query path points must stay on one side; split side transitions into separate ribbons.
|
|
34777
|
+
*/
|
|
34778
|
+
on(skin, points, options = {}) {
|
|
34779
|
+
if (points.length < 2) throw new Error("Product.ribbon().on(skin, points) requires at least two path points");
|
|
34780
|
+
this.skinValue = skin;
|
|
34781
|
+
this.queryPath = resolvePathQueries(points);
|
|
34782
|
+
this.refPath = [];
|
|
34783
|
+
return this.applyOptions(options);
|
|
34784
|
+
}
|
|
34785
|
+
/**
|
|
34786
|
+
* Follow explicit surface refs.
|
|
34787
|
+
*
|
|
34788
|
+
* Useful for named refs or paths assembled elsewhere. The builder resolves each ref frame and
|
|
34789
|
+
* interpolates between those frames; use on(skin, points) when you need full skin-side sampling
|
|
34790
|
+
* between sparse control points.
|
|
34791
|
+
*/
|
|
34792
|
+
fromRefs(points, options = {}) {
|
|
34793
|
+
if (points.length < 2) throw new Error("Product.ribbon().fromRefs(points) requires at least two refs");
|
|
34794
|
+
this.skinValue = void 0;
|
|
34795
|
+
this.queryPath = [];
|
|
34796
|
+
this.refPath = [...points];
|
|
34797
|
+
return this.applyOptions(options);
|
|
34798
|
+
}
|
|
34799
|
+
/** Set ribbon width in millimeters. */
|
|
34800
|
+
width(width) {
|
|
34801
|
+
if (!Number.isFinite(width) || width <= 0) throw new Error("Product.ribbon().width(width) requires a positive finite number");
|
|
34802
|
+
this.widthValue = width;
|
|
34803
|
+
return this;
|
|
34804
|
+
}
|
|
34805
|
+
/** Set solid thickness outward from the source surface in millimeters. */
|
|
34806
|
+
thickness(thickness) {
|
|
34807
|
+
if (!Number.isFinite(thickness) || thickness <= 0)
|
|
34808
|
+
throw new Error("Product.ribbon().thickness(thickness) requires a positive finite number");
|
|
34809
|
+
this.thicknessValue = thickness;
|
|
34810
|
+
return this;
|
|
34811
|
+
}
|
|
34812
|
+
/** Set positive clearance between the source surface and the ribbon's inner face. */
|
|
34813
|
+
offset(offset2) {
|
|
34814
|
+
if (!Number.isFinite(offset2)) throw new Error("Product.ribbon().offset(offset) requires a finite number");
|
|
34815
|
+
this.offsetValue = offset2;
|
|
34816
|
+
return this;
|
|
34817
|
+
}
|
|
34818
|
+
/** Set samples along the path. */
|
|
34819
|
+
samples(samples) {
|
|
34820
|
+
if (!Number.isFinite(samples) || samples < 2) throw new Error("Product.ribbon().samples(samples) requires a value >= 2");
|
|
34821
|
+
this.samplesValue = Math.round(samples);
|
|
34822
|
+
return this;
|
|
34823
|
+
}
|
|
34824
|
+
/** Set samples across the width. Use 3+ to bend over curved cross-sections. */
|
|
34825
|
+
widthSamples(samples) {
|
|
34826
|
+
if (!Number.isFinite(samples) || samples < 2) throw new Error("Product.ribbon().widthSamples(samples) requires a value >= 2");
|
|
34827
|
+
this.widthSamplesValue = Math.round(samples);
|
|
34828
|
+
return this;
|
|
34829
|
+
}
|
|
34830
|
+
/** Set NURBS tessellation resolution. */
|
|
34831
|
+
resolution(resolution) {
|
|
34832
|
+
if (!Number.isFinite(resolution) || resolution < 2) throw new Error("Product.ribbon().resolution(resolution) requires a value >= 2");
|
|
34833
|
+
this.resolutionValue = Math.round(resolution);
|
|
34834
|
+
return this;
|
|
34835
|
+
}
|
|
34836
|
+
/** Apply a product material preset. */
|
|
34837
|
+
material(material) {
|
|
34838
|
+
this.materialValue = material;
|
|
34839
|
+
return this;
|
|
34840
|
+
}
|
|
34841
|
+
/** Apply a simple color override. */
|
|
34842
|
+
color(color) {
|
|
34843
|
+
this.colorValue = color;
|
|
34844
|
+
return this;
|
|
34845
|
+
}
|
|
34846
|
+
/** Build a conformal ribbon as a thin NURBS surface solid. */
|
|
34847
|
+
build(options = {}) {
|
|
34848
|
+
return this.buildWithDiagnostics(options).shape;
|
|
34849
|
+
}
|
|
34850
|
+
/**
|
|
34851
|
+
* Build a conformal ribbon and return surface-feature diagnostics.
|
|
34852
|
+
*
|
|
34853
|
+
* Use this while validating API usage or model fidelity; diagnostics report sampling counts,
|
|
34854
|
+
* side-span clamping, lowering mode, and warnings that should be visible in reviews.
|
|
34855
|
+
*/
|
|
34856
|
+
buildWithDiagnostics(options = {}) {
|
|
34857
|
+
this.applyOptions(options);
|
|
34858
|
+
const gridResult = this.skinValue ? this.buildSkinGrid(this.skinValue, this.queryPath) : this.buildRefGrid(this.refPath);
|
|
34859
|
+
const desiredNormal = this.centerDesiredNormal();
|
|
34860
|
+
let ribbon = nurbsSurface(orientGridToNormal(gridResult.grid, desiredNormal), {
|
|
34861
|
+
degreeU: Math.min(3, this.widthSamplesValue - 1),
|
|
34862
|
+
degreeV: Math.min(3, this.samplesValue - 1),
|
|
34863
|
+
thickness: this.thicknessValue,
|
|
34864
|
+
resolution: this.resolutionValue ?? Math.max(this.samplesValue, this.widthSamplesValue, 12),
|
|
34865
|
+
approximate: true
|
|
34866
|
+
}).as(this.name);
|
|
34867
|
+
if (this.colorValue) ribbon = ribbon.color(this.colorValue);
|
|
34868
|
+
const shape = applyMaterial(ribbon, this.materialValue);
|
|
34869
|
+
this.lastDiagnosticsValue = gridResult.diagnostics;
|
|
34870
|
+
return { shape, diagnostics: this.cloneDiagnostics(gridResult.diagnostics) };
|
|
34871
|
+
}
|
|
34872
|
+
/** Return diagnostics from the most recent build, if this builder has been built. */
|
|
34873
|
+
diagnostics() {
|
|
34874
|
+
return this.lastDiagnosticsValue ? this.cloneDiagnostics(this.lastDiagnosticsValue) : void 0;
|
|
34875
|
+
}
|
|
34876
|
+
applyOptions(options) {
|
|
34877
|
+
if (options.width != null) this.width(options.width);
|
|
34878
|
+
if (options.thickness != null) this.thickness(options.thickness);
|
|
34879
|
+
if (options.offset != null) this.offset(options.offset);
|
|
34880
|
+
if (options.samples != null) this.samples(options.samples);
|
|
34881
|
+
if (options.widthSamples != null) this.widthSamples(options.widthSamples);
|
|
34882
|
+
if (options.resolution != null) this.resolution(options.resolution);
|
|
34883
|
+
if (options.material) this.material(options.material);
|
|
34884
|
+
if (options.color) this.color(options.color);
|
|
34885
|
+
return this;
|
|
34886
|
+
}
|
|
34887
|
+
centerDesiredNormal() {
|
|
34888
|
+
if (this.skinValue && this.queryPath.length > 0) {
|
|
34889
|
+
const mid = this.samplePathQuery(0.5);
|
|
34890
|
+
return this.skinValue.frame({ ...mid, offset: (mid.offset ?? 0) + this.offsetValue }).normal;
|
|
34891
|
+
}
|
|
34892
|
+
if (this.refPath.length > 0) return this.refPath[Math.floor(this.refPath.length / 2)].frame({ offset: this.offsetValue }).normal;
|
|
34893
|
+
return [0, 0, 1];
|
|
34894
|
+
}
|
|
34895
|
+
samplePathQuery(t) {
|
|
34896
|
+
if (this.queryPath.length < 2) throw new Error("Product.ribbon().on(...) must be called before .build()");
|
|
34897
|
+
const segmentCount = this.queryPath.length - 1;
|
|
34898
|
+
const scaled = clamp$4(t, 0, 1) * segmentCount;
|
|
34899
|
+
const segment = Math.min(segmentCount - 1, Math.floor(scaled));
|
|
34900
|
+
const localT = scaled - segment;
|
|
34901
|
+
return interpolateQuery(this.queryPath[segment], this.queryPath[segment + 1], localT);
|
|
34902
|
+
}
|
|
34903
|
+
buildSkinGrid(skin, path2) {
|
|
34904
|
+
if (path2.length < 2) throw new Error("Product.ribbon().on(skin, points) must be called before .build()");
|
|
34905
|
+
const side = normalizedSide(path2[0].side);
|
|
34906
|
+
if (side === "front" || side === "rear") {
|
|
34907
|
+
throw new Error(
|
|
34908
|
+
"Product.ribbon().on(...) supports side ribbons on left/right/top/bottom surfaces. Use Product.panel() for front/rear caps."
|
|
34909
|
+
);
|
|
34910
|
+
}
|
|
34911
|
+
for (const point2 of path2) {
|
|
34912
|
+
if (normalizedSide(point2.side) !== side) {
|
|
34913
|
+
throw new Error("Product.ribbon().on(...) currently supports one side per ribbon. Split ribbons at side transitions.");
|
|
34914
|
+
}
|
|
34915
|
+
}
|
|
34916
|
+
const rows = Array.from({ length: this.widthSamplesValue }, () => []);
|
|
34917
|
+
let clampedUCount = 0;
|
|
34918
|
+
let maxUClampDistance = 0;
|
|
34919
|
+
for (let i = 0; i < this.samplesValue; i += 1) {
|
|
34920
|
+
const along = this.samplesValue === 1 ? 0 : i / (this.samplesValue - 1);
|
|
34921
|
+
const center = this.samplePathQuery(along);
|
|
34922
|
+
const station = skin.stationAt(center.v ?? 0.5);
|
|
34923
|
+
const span = sideSpan(side, station.width, station.depth);
|
|
34924
|
+
for (let j = 0; j < this.widthSamplesValue; j += 1) {
|
|
34925
|
+
const across = this.widthSamplesValue === 1 ? 0 : j / (this.widthSamplesValue - 1) - 0.5;
|
|
34926
|
+
const rawU = (center.u ?? 0.5) + across * this.widthValue / span;
|
|
34927
|
+
const u = clamp$4(rawU, 0, 1);
|
|
34928
|
+
const clampDistance = Math.abs(rawU - u) * span;
|
|
34929
|
+
if (clampDistance > EPS$4) {
|
|
34930
|
+
clampedUCount += 1;
|
|
34931
|
+
maxUClampDistance = Math.max(maxUClampDistance, clampDistance);
|
|
34932
|
+
}
|
|
34933
|
+
const query = {
|
|
34934
|
+
...center,
|
|
34935
|
+
side,
|
|
34936
|
+
u,
|
|
34937
|
+
offset: (center.offset ?? 0) + this.offsetValue + this.thicknessValue
|
|
34938
|
+
};
|
|
34939
|
+
rows[j].push(skin.frame(query).point);
|
|
34940
|
+
}
|
|
34941
|
+
}
|
|
34942
|
+
return {
|
|
34943
|
+
grid: rows,
|
|
34944
|
+
diagnostics: this.makeDiagnostics({
|
|
34945
|
+
skin: skin.name,
|
|
34946
|
+
side,
|
|
34947
|
+
pathPointCount: path2.length,
|
|
34948
|
+
clampedUCount,
|
|
34949
|
+
maxUClampDistance
|
|
34950
|
+
})
|
|
34951
|
+
};
|
|
34952
|
+
}
|
|
34953
|
+
buildRefGrid(refs) {
|
|
34954
|
+
if (refs.length < 2) throw new Error("Product.ribbon().fromRefs(points) must be called before .build()");
|
|
34955
|
+
const frames = refs.map((ref) => ref.frame({ offset: this.offsetValue + this.thicknessValue }));
|
|
34956
|
+
const rows = Array.from({ length: this.widthSamplesValue }, () => []);
|
|
34957
|
+
for (const frame of frames) {
|
|
34958
|
+
for (let j = 0; j < this.widthSamplesValue; j += 1) {
|
|
34959
|
+
const across = this.widthSamplesValue === 1 ? 0 : j / (this.widthSamplesValue - 1) - 0.5;
|
|
34960
|
+
rows[j].push(add(frame.point, scale(frame.tangentU, across * this.widthValue)));
|
|
34961
|
+
}
|
|
34962
|
+
}
|
|
34963
|
+
this.samplesValue = refs.length;
|
|
34964
|
+
return {
|
|
34965
|
+
grid: rows,
|
|
34966
|
+
diagnostics: this.makeDiagnostics({
|
|
34967
|
+
pathPointCount: refs.length,
|
|
34968
|
+
clampedUCount: 0,
|
|
34969
|
+
maxUClampDistance: 0
|
|
34970
|
+
})
|
|
34971
|
+
};
|
|
34972
|
+
}
|
|
34973
|
+
makeDiagnostics(input) {
|
|
34974
|
+
const resolution = this.resolutionValue ?? Math.max(this.samplesValue, this.widthSamplesValue, 12);
|
|
34975
|
+
const warnings = [];
|
|
34976
|
+
if (input.clampedUCount > 0) {
|
|
34977
|
+
warnings.push(
|
|
34978
|
+
`Ribbon '${this.name}' was clipped to the ${input.side ?? "surface"} UV bounds at ${input.clampedUCount} sampled point(s).`
|
|
34979
|
+
);
|
|
34980
|
+
}
|
|
34981
|
+
if (this.samplesValue < 8) warnings.push(`Ribbon '${this.name}' uses low along-path sampling; increase samples() for smoother bends.`);
|
|
34982
|
+
if (this.widthSamplesValue < 3)
|
|
34983
|
+
warnings.push(`Ribbon '${this.name}' uses low width sampling; use widthSamples(3+) to show cross-surface curvature.`);
|
|
34984
|
+
return {
|
|
34985
|
+
name: this.name,
|
|
34986
|
+
...input.skin ? { skin: input.skin } : {},
|
|
34987
|
+
...input.side ? { side: input.side } : {},
|
|
34988
|
+
pathPointCount: input.pathPointCount,
|
|
34989
|
+
width: this.widthValue,
|
|
34990
|
+
thickness: this.thicknessValue,
|
|
34991
|
+
offset: this.offsetValue,
|
|
34992
|
+
samples: this.samplesValue,
|
|
34993
|
+
widthSamples: this.widthSamplesValue,
|
|
34994
|
+
resolution,
|
|
34995
|
+
lowering: "nurbsSurface",
|
|
34996
|
+
expectedFidelity: "mixed",
|
|
34997
|
+
clampedUCount: input.clampedUCount,
|
|
34998
|
+
maxUClampDistance: input.maxUClampDistance,
|
|
34999
|
+
warnings
|
|
35000
|
+
};
|
|
35001
|
+
}
|
|
35002
|
+
cloneDiagnostics(diagnostics) {
|
|
35003
|
+
return {
|
|
35004
|
+
...diagnostics,
|
|
35005
|
+
warnings: [...diagnostics.warnings]
|
|
35006
|
+
};
|
|
35007
|
+
}
|
|
35008
|
+
}
|
|
34248
35009
|
const Product = {
|
|
34249
35010
|
/** Start a named product skin builder. */
|
|
34250
35011
|
skin(name) {
|
|
@@ -34317,10 +35078,27 @@ const Product = {
|
|
|
34317
35078
|
ref(skin, query) {
|
|
34318
35079
|
return new ProductSurfaceRef(skin, query);
|
|
34319
35080
|
},
|
|
35081
|
+
/**
|
|
35082
|
+
* Create a fluent surface helper for refs and conformal features on one side of a skin.
|
|
35083
|
+
*
|
|
35084
|
+
* Equivalent to skin.surface(side), useful when writing in Product.* namespace style.
|
|
35085
|
+
*/
|
|
35086
|
+
surface(skin, side) {
|
|
35087
|
+
return skin.surface(side);
|
|
35088
|
+
},
|
|
34320
35089
|
/** Start a panel feature builder. */
|
|
34321
35090
|
panel(name) {
|
|
34322
35091
|
return new ProductPanelBuilder(name);
|
|
34323
35092
|
},
|
|
35093
|
+
/**
|
|
35094
|
+
* Start a conformal ribbon/trim builder for details that should bend with a ProductSkin.
|
|
35095
|
+
*
|
|
35096
|
+
* Call .on(skin, points) for side/u/v sampling or .fromRefs(points) for explicit surface refs,
|
|
35097
|
+
* then configure width, thickness, offset, sampling, material, and color before build().
|
|
35098
|
+
*/
|
|
35099
|
+
ribbon(name) {
|
|
35100
|
+
return new ProductRibbonBuilder(name);
|
|
35101
|
+
},
|
|
34324
35102
|
/** Start a spout/nozzle feature builder. */
|
|
34325
35103
|
spout(name) {
|
|
34326
35104
|
return new ProductSpoutBuilder(name);
|
|
@@ -35367,6 +36145,7 @@ function cameraTrajectory(defOrFn, options) {
|
|
|
35367
36145
|
throw new Error('cameraTrajectory(): each keyframe must have either an "orbit" or "position" property');
|
|
35368
36146
|
}
|
|
35369
36147
|
}
|
|
36148
|
+
var define_process_env_default = {};
|
|
35370
36149
|
function resolveEdges(shape, edges) {
|
|
35371
36150
|
if (!edges) {
|
|
35372
36151
|
return selectEdges(shape);
|
|
@@ -35388,6 +36167,76 @@ function isEdgeSegment(value) {
|
|
|
35388
36167
|
function isEdgeReferenceLike(value) {
|
|
35389
36168
|
return typeof value === "object" && value !== null && "edges" in value && typeof value.edges === "function";
|
|
35390
36169
|
}
|
|
36170
|
+
const BROAD_EDGE_FEATURE_DEFAULT_BUDGET = {
|
|
36171
|
+
live: 0,
|
|
36172
|
+
default: 12,
|
|
36173
|
+
high: Number.POSITIVE_INFINITY
|
|
36174
|
+
};
|
|
36175
|
+
let broadEdgeFeatureBudget = null;
|
|
36176
|
+
function readBroadEdgeFeatureEnv(name) {
|
|
36177
|
+
return typeof process !== "undefined" ? define_process_env_default == null ? void 0 : define_process_env_default[name] : void 0;
|
|
36178
|
+
}
|
|
36179
|
+
function resolveBroadEdgeFeatureBudget() {
|
|
36180
|
+
if (readBroadEdgeFeatureEnv("FORGECAD_ALLOW_BROAD_EDGE_FEATURES") === "1") return Number.POSITIVE_INFINITY;
|
|
36181
|
+
const override = readBroadEdgeFeatureEnv("FORGECAD_BROAD_EDGE_FEATURE_BUDGET");
|
|
36182
|
+
if (override != null && override.trim() !== "") {
|
|
36183
|
+
const parsed = Number(override);
|
|
36184
|
+
if (Number.isFinite(parsed) && parsed >= 0) return parsed;
|
|
36185
|
+
}
|
|
36186
|
+
return BROAD_EDGE_FEATURE_DEFAULT_BUDGET[getForgeQualityPreset()];
|
|
36187
|
+
}
|
|
36188
|
+
function resetBroadEdgeFeatureBudget() {
|
|
36189
|
+
broadEdgeFeatureBudget = null;
|
|
36190
|
+
}
|
|
36191
|
+
function remainingBroadEdgeFeatureBudget() {
|
|
36192
|
+
if (broadEdgeFeatureBudget === null) broadEdgeFeatureBudget = resolveBroadEdgeFeatureBudget();
|
|
36193
|
+
return broadEdgeFeatureBudget;
|
|
36194
|
+
}
|
|
36195
|
+
function consumeBroadEdgeFeatureBudget(edgeCount) {
|
|
36196
|
+
const remaining = remainingBroadEdgeFeatureBudget();
|
|
36197
|
+
if (!Number.isFinite(remaining)) return true;
|
|
36198
|
+
if (edgeCount > remaining) return false;
|
|
36199
|
+
broadEdgeFeatureBudget = Math.max(0, remaining - edgeCount);
|
|
36200
|
+
return true;
|
|
36201
|
+
}
|
|
36202
|
+
function shouldSkipBroadEdgeFeature(operation, edgeCount) {
|
|
36203
|
+
if (consumeBroadEdgeFeatureBudget(edgeCount)) return false;
|
|
36204
|
+
const remaining = Math.max(0, remainingBroadEdgeFeatureBudget());
|
|
36205
|
+
emitRuntimeWarning(
|
|
36206
|
+
`${operation}() without an edge selector matched ${edgeCount} edge(s), exceeding the remaining broad edge-feature budget (${remaining}). Skipped this cosmetic edge finish for responsiveness. Pass an explicit edge selector, use high quality/export, or set FORGECAD_BROAD_EDGE_FEATURE_BUDGET to opt into more broad edge finishing.`
|
|
36207
|
+
);
|
|
36208
|
+
return true;
|
|
36209
|
+
}
|
|
36210
|
+
function shouldSkipExhaustedBroadEdgeFeature(operation) {
|
|
36211
|
+
const remaining = remainingBroadEdgeFeatureBudget();
|
|
36212
|
+
if (!Number.isFinite(remaining) || remaining > 0) return false;
|
|
36213
|
+
emitRuntimeWarning(
|
|
36214
|
+
`${operation}() without an edge selector was skipped because the broad edge-feature budget is exhausted. Skipped this cosmetic edge finish for responsiveness. Pass an explicit edge selector, use high quality/export, or set FORGECAD_BROAD_EDGE_FEATURE_BUDGET to opt into more broad edge finishing.`
|
|
36215
|
+
);
|
|
36216
|
+
return true;
|
|
36217
|
+
}
|
|
36218
|
+
function estimateSelectorlessEdgeCount(plan) {
|
|
36219
|
+
if (!plan) return null;
|
|
36220
|
+
switch (plan.kind) {
|
|
36221
|
+
case "box":
|
|
36222
|
+
return 12;
|
|
36223
|
+
case "queryOwner":
|
|
36224
|
+
case "transform":
|
|
36225
|
+
return estimateSelectorlessEdgeCount(plan.base);
|
|
36226
|
+
default:
|
|
36227
|
+
return null;
|
|
36228
|
+
}
|
|
36229
|
+
}
|
|
36230
|
+
function shouldSkipUnestimatedBroadEdgeFeature(operation, target) {
|
|
36231
|
+
const remaining = remainingBroadEdgeFeatureBudget();
|
|
36232
|
+
if (!Number.isFinite(remaining)) return false;
|
|
36233
|
+
const estimatedEdges = estimateSelectorlessEdgeCount(getShapeCompilePlan(target));
|
|
36234
|
+
if (estimatedEdges !== null && estimatedEdges <= remaining) return false;
|
|
36235
|
+
emitRuntimeWarning(
|
|
36236
|
+
`${operation}() without an edge selector was skipped before broad edge enumeration because this shape is too complex for the remaining broad edge-feature budget (${Math.max(0, remaining)}). Skipped this cosmetic edge finish for responsiveness. Pass an explicit edge selector, use high quality/export, or set FORGECAD_BROAD_EDGE_FEATURE_BUDGET to opt into more broad edge finishing.`
|
|
36237
|
+
);
|
|
36238
|
+
return true;
|
|
36239
|
+
}
|
|
35391
36240
|
function edgesToTargets(edges) {
|
|
35392
36241
|
return edges.map((e) => ({
|
|
35393
36242
|
midpoint: [e.midpoint[0], e.midpoint[1], e.midpoint[2]],
|
|
@@ -35401,10 +36250,13 @@ function fillet(shape, radius, edges, segments = 16) {
|
|
|
35401
36250
|
throw new Error("fillet() requires a positive finite radius.");
|
|
35402
36251
|
}
|
|
35403
36252
|
const target = shape;
|
|
36253
|
+
if (edges === void 0 && shouldSkipExhaustedBroadEdgeFeature("fillet")) return target;
|
|
36254
|
+
if (edges === void 0 && shouldSkipUnestimatedBroadEdgeFeature("fillet", target)) return target;
|
|
35404
36255
|
const resolvedEdges = resolveEdges(target, edges);
|
|
35405
36256
|
if (resolvedEdges.length === 0) {
|
|
35406
36257
|
throw new Error("fillet(): no edges match the given selection.");
|
|
35407
36258
|
}
|
|
36259
|
+
if (edges === void 0 && shouldSkipBroadEdgeFeature("fillet", resolvedEdges.length)) return target;
|
|
35408
36260
|
const basePlan = getShapeCompilePlan(target);
|
|
35409
36261
|
const plan = {
|
|
35410
36262
|
kind: "filletEdges",
|
|
@@ -35424,10 +36276,13 @@ function chamfer(shape, size, edges) {
|
|
|
35424
36276
|
throw new Error("chamfer() requires a positive finite size.");
|
|
35425
36277
|
}
|
|
35426
36278
|
const target = shape;
|
|
36279
|
+
if (edges === void 0 && shouldSkipExhaustedBroadEdgeFeature("chamfer")) return target;
|
|
36280
|
+
if (edges === void 0 && shouldSkipUnestimatedBroadEdgeFeature("chamfer", target)) return target;
|
|
35427
36281
|
const resolvedEdges = resolveEdges(target, edges);
|
|
35428
36282
|
if (resolvedEdges.length === 0) {
|
|
35429
36283
|
throw new Error("chamfer(): no edges match the given selection.");
|
|
35430
36284
|
}
|
|
36285
|
+
if (edges === void 0 && shouldSkipBroadEdgeFeature("chamfer", resolvedEdges.length)) return target;
|
|
35431
36286
|
const basePlan = getShapeCompilePlan(target);
|
|
35432
36287
|
const plan = {
|
|
35433
36288
|
kind: "chamferEdges",
|
|
@@ -58115,6 +58970,7 @@ const verify = {
|
|
|
58115
58970
|
};
|
|
58116
58971
|
function resetExecutionSession(logs) {
|
|
58117
58972
|
resetCollectedAssemblies();
|
|
58973
|
+
resetBroadEdgeFeatureBudget();
|
|
58118
58974
|
resetParams();
|
|
58119
58975
|
resetShapeQueryOwnerIds();
|
|
58120
58976
|
resetDimensions();
|
|
@@ -58123,6 +58979,7 @@ function resetExecutionSession(logs) {
|
|
|
58123
58979
|
resetSheetStock();
|
|
58124
58980
|
resetRobotExport();
|
|
58125
58981
|
resetCutPlanes();
|
|
58982
|
+
resetRenderLabels();
|
|
58126
58983
|
resetCameraTrajectory();
|
|
58127
58984
|
resetExplodeView();
|
|
58128
58985
|
resetJointsView();
|
|
@@ -58186,6 +59043,7 @@ function collectSuccessfulExecutionSnapshot(args) {
|
|
|
58186
59043
|
bom: getCollectedBom(),
|
|
58187
59044
|
sheetStock: getCollectedSheetStock(),
|
|
58188
59045
|
cutPlanes: getCollectedCutPlanes(),
|
|
59046
|
+
renderLabels: getCollectedRenderLabels(),
|
|
58189
59047
|
cameraTrajectory: getCollectedCameraTrajectory(),
|
|
58190
59048
|
explodeView: getCollectedExplodeView(),
|
|
58191
59049
|
jointsView: getCollectedJointsView(),
|
|
@@ -58209,6 +59067,7 @@ function collectFailedExecutionSnapshot(args) {
|
|
|
58209
59067
|
bom: getCollectedBom(),
|
|
58210
59068
|
sheetStock: getCollectedSheetStock(),
|
|
58211
59069
|
cutPlanes: getCollectedCutPlanes(),
|
|
59070
|
+
renderLabels: getCollectedRenderLabels(),
|
|
58212
59071
|
cameraTrajectory: getCollectedCameraTrajectory(),
|
|
58213
59072
|
explodeView: getCollectedExplodeView(),
|
|
58214
59073
|
jointsView: getCollectedJointsView(),
|
|
@@ -58354,13 +59213,15 @@ function formatLogArg(value) {
|
|
|
58354
59213
|
return `[Log serialization failed: ${formatLogError(error)}]`;
|
|
58355
59214
|
}
|
|
58356
59215
|
}
|
|
58357
|
-
function makeSandboxConsole(collectedLogs) {
|
|
59216
|
+
function makeSandboxConsole(collectedLogs, mirror) {
|
|
58358
59217
|
const capture = (level) => (...args) => {
|
|
59218
|
+
const formattedArgs = args.map(formatLogArg);
|
|
58359
59219
|
collectedLogs.push({
|
|
58360
59220
|
level,
|
|
58361
|
-
args:
|
|
59221
|
+
args: formattedArgs,
|
|
58362
59222
|
timestamp: Date.now()
|
|
58363
59223
|
});
|
|
59224
|
+
mirror == null ? void 0 : mirror(level, formattedArgs);
|
|
58364
59225
|
};
|
|
58365
59226
|
return { log: capture("log"), warn: capture("warn"), error: capture("error"), info: capture("info") };
|
|
58366
59227
|
}
|
|
@@ -58522,6 +59383,18 @@ function buildFileIndex(allFiles) {
|
|
|
58522
59383
|
}
|
|
58523
59384
|
return fileIndex;
|
|
58524
59385
|
}
|
|
59386
|
+
function hasPathExtension(path2) {
|
|
59387
|
+
const fileName = path2.split("/").pop() ?? path2;
|
|
59388
|
+
return /\.[^/.]+$/.test(fileName);
|
|
59389
|
+
}
|
|
59390
|
+
function explicitExtensionHint(requestedName, resolvedPath, fileIndex) {
|
|
59391
|
+
if (!resolvedPath || hasPathExtension(resolvedPath)) return "";
|
|
59392
|
+
const requestedBase = requestedName.trim();
|
|
59393
|
+
const suggestions = [".forge.js", ".js"].map((ext) => ({ ext, resolved: `${resolvedPath}${ext}` })).filter(({ resolved }) => fileIndex.has(resolved)).map(({ ext }) => `"${requestedBase}${ext}"`);
|
|
59394
|
+
if (suggestions.length === 0) return "";
|
|
59395
|
+
const joined = suggestions.length === 1 ? suggestions[0] : suggestions.join(" or ");
|
|
59396
|
+
return ` Did you mean ${joined}? ForgeCAD requires explicit file extensions in project imports.`;
|
|
59397
|
+
}
|
|
58525
59398
|
function resolveImportSource(fromFile, requestedName, allFiles, options) {
|
|
58526
59399
|
if (typeof requestedName !== "string" || requestedName.trim().length === 0) {
|
|
58527
59400
|
throw new Error("Import path must be a non-empty string");
|
|
@@ -58530,7 +59403,8 @@ function resolveImportSource(fromFile, requestedName, allFiles, options) {
|
|
|
58530
59403
|
const lookupKey = options.fileIndex.get(resolvedPath);
|
|
58531
59404
|
if (!lookupKey) {
|
|
58532
59405
|
const suffix = resolvedPath && resolvedPath !== requestedName ? ` (resolved to "${resolvedPath}" from "${fromFile}")` : ` (from "${fromFile}")`;
|
|
58533
|
-
|
|
59406
|
+
const hint = explicitExtensionHint(requestedName, resolvedPath, options.fileIndex);
|
|
59407
|
+
throw new Error(`File not found: "${requestedName}"${suffix}.${hint}`);
|
|
58534
59408
|
}
|
|
58535
59409
|
const source = allFiles[lookupKey];
|
|
58536
59410
|
if (typeof source !== "string") {
|
|
@@ -112836,7 +113710,7 @@ ${lanes.join("\n")}
|
|
|
112836
113710
|
}
|
|
112837
113711
|
const normalizedSourcePaths = getPathsRelativeToRootDirs(sourceDirectory, rootDirs, getCanonicalFileName);
|
|
112838
113712
|
const relativePaths = flatMap(normalizedSourcePaths, (sourcePath) => {
|
|
112839
|
-
return map(normalizedTargetPaths, (
|
|
113713
|
+
return map(normalizedTargetPaths, (targetPath2) => ensurePathIsNonModuleName(getRelativePathFromDirectory(sourcePath, targetPath2, getCanonicalFileName)));
|
|
112840
113714
|
});
|
|
112841
113715
|
const shortest = min2(relativePaths, compareNumberOfDirectorySeparators);
|
|
112842
113716
|
if (!shortest) {
|
|
@@ -269406,6 +270280,36 @@ function extractUnusedTopLevelVarNames(code) {
|
|
|
269406
270280
|
}
|
|
269407
270281
|
return declaredNames.filter((n) => topLevelNames.has(n) && (!usedByOthers.has(n) || explicitImplicitResultNames.has(n)));
|
|
269408
270282
|
}
|
|
270283
|
+
function collectBindingNameLocations(node, sourceFile, names) {
|
|
270284
|
+
if (typescriptExports.isIdentifier(node)) {
|
|
270285
|
+
const { line: line2, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
270286
|
+
names.push({ name: node.text, line: line2 + 1, column: character + 1 });
|
|
270287
|
+
return;
|
|
270288
|
+
}
|
|
270289
|
+
for (const element of node.elements) {
|
|
270290
|
+
if (typescriptExports.isBindingElement(element)) {
|
|
270291
|
+
collectBindingNameLocations(element.name, sourceFile, names);
|
|
270292
|
+
}
|
|
270293
|
+
}
|
|
270294
|
+
}
|
|
270295
|
+
function findTopLevelRuntimeGlobalCollision(code, runtimeGlobalNames) {
|
|
270296
|
+
const runtimeGlobals = new Set(runtimeGlobalNames);
|
|
270297
|
+
const sourceFile = typescriptExports.createSourceFile("__runtime-globals.js", code, typescriptExports.ScriptTarget.ES2020, false, typescriptExports.ScriptKind.JS);
|
|
270298
|
+
const declarations = [];
|
|
270299
|
+
for (const statement of sourceFile.statements) {
|
|
270300
|
+
if (typescriptExports.isVariableStatement(statement)) {
|
|
270301
|
+
const isLexical = (statement.declarationList.flags & (typescriptExports.NodeFlags.Let | typescriptExports.NodeFlags.Const)) !== 0;
|
|
270302
|
+
if (!isLexical) continue;
|
|
270303
|
+
for (const decl of statement.declarationList.declarations) {
|
|
270304
|
+
collectBindingNameLocations(decl.name, sourceFile, declarations);
|
|
270305
|
+
}
|
|
270306
|
+
} else if (typescriptExports.isClassDeclaration(statement) && statement.name) {
|
|
270307
|
+
const { line: line2, character } = sourceFile.getLineAndCharacterOfPosition(statement.name.getStart(sourceFile));
|
|
270308
|
+
declarations.push({ name: statement.name.text, line: line2 + 1, column: character + 1 });
|
|
270309
|
+
}
|
|
270310
|
+
}
|
|
270311
|
+
return declarations.find((declaration) => runtimeGlobals.has(declaration.name)) ?? null;
|
|
270312
|
+
}
|
|
269409
270313
|
function createForgeRuntimeModule(bindings) {
|
|
269410
270314
|
const runtime = { ...bindings };
|
|
269411
270315
|
Object.defineProperty(runtime, "__esModule", { value: true });
|
|
@@ -269946,6 +270850,12 @@ function withConstructorChainLockdown(fn) {
|
|
|
269946
270850
|
}
|
|
269947
270851
|
}
|
|
269948
270852
|
function executeFile(code, fileName, allFiles, visited, scope = {}, options, executionMode = "script", moduleCacheEntry) {
|
|
270853
|
+
var _a3, _b3, _c2, _d2, _e, _f;
|
|
270854
|
+
(_a3 = options.debug) == null ? void 0 : _a3.call(options, "executeFile:start", {
|
|
270855
|
+
fileName,
|
|
270856
|
+
executionMode,
|
|
270857
|
+
scope: scope.namePrefix ?? fileName
|
|
270858
|
+
});
|
|
269949
270859
|
const trackCircularImports = executionMode === "script";
|
|
269950
270860
|
if (trackCircularImports) {
|
|
269951
270861
|
if (visited.has(fileName)) {
|
|
@@ -270023,13 +270933,13 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
|
|
|
270023
270933
|
});
|
|
270024
270934
|
};
|
|
270025
270935
|
const importStep = (name) => {
|
|
270026
|
-
var
|
|
270936
|
+
var _a4;
|
|
270027
270937
|
if (typeof name !== "string" || name.trim().length === 0) {
|
|
270028
270938
|
throw new Error("importStep() requires a non-empty file path string");
|
|
270029
270939
|
}
|
|
270030
270940
|
const resolvedPath = resolveImportPath(fileName, name.trim());
|
|
270031
270941
|
rejectPathTraversal("importStep", name, resolvedPath);
|
|
270032
|
-
const ext = ((
|
|
270942
|
+
const ext = ((_a4 = resolvedPath.split(".").pop()) == null ? void 0 : _a4.toLowerCase()) ?? "";
|
|
270033
270943
|
if (ext !== "step" && ext !== "stp") {
|
|
270034
270944
|
throw new Error(`importStep("${name}"): unsupported extension ".${ext}". Expected .step or .stp`);
|
|
270035
270945
|
}
|
|
@@ -270058,7 +270968,39 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
|
|
|
270058
270968
|
setShapeTopology(shape, topo);
|
|
270059
270969
|
return shape;
|
|
270060
270970
|
};
|
|
270061
|
-
const sandboxConsole = makeSandboxConsole(_collectedLogs)
|
|
270971
|
+
const sandboxConsole = makeSandboxConsole(_collectedLogs, options.debug ? (level, args) => {
|
|
270972
|
+
var _a4;
|
|
270973
|
+
return (_a4 = options.debug) == null ? void 0 : _a4.call(options, "console", { level, args });
|
|
270974
|
+
} : void 0);
|
|
270975
|
+
const runtimeVerify = options.debug ? {
|
|
270976
|
+
...verify,
|
|
270977
|
+
that(label, check2, message) {
|
|
270978
|
+
var _a4, _b4;
|
|
270979
|
+
(_a4 = options.debug) == null ? void 0 : _a4.call(options, "verify:that:start", { label });
|
|
270980
|
+
const verifyStart = performance.now();
|
|
270981
|
+
try {
|
|
270982
|
+
return verify.that(label, check2, message);
|
|
270983
|
+
} finally {
|
|
270984
|
+
(_b4 = options.debug) == null ? void 0 : _b4.call(options, "verify:that:end", {
|
|
270985
|
+
label,
|
|
270986
|
+
ms: Number((performance.now() - verifyStart).toFixed(1))
|
|
270987
|
+
});
|
|
270988
|
+
}
|
|
270989
|
+
},
|
|
270990
|
+
equal(label, actual, expected, tolerance = 0, message) {
|
|
270991
|
+
var _a4, _b4;
|
|
270992
|
+
(_a4 = options.debug) == null ? void 0 : _a4.call(options, "verify:equal:start", { label, actual, expected, tolerance });
|
|
270993
|
+
const verifyStart = performance.now();
|
|
270994
|
+
try {
|
|
270995
|
+
return verify.equal(label, actual, expected, tolerance, message);
|
|
270996
|
+
} finally {
|
|
270997
|
+
(_b4 = options.debug) == null ? void 0 : _b4.call(options, "verify:equal:end", {
|
|
270998
|
+
label,
|
|
270999
|
+
ms: Number((performance.now() - verifyStart).toFixed(1))
|
|
271000
|
+
});
|
|
271001
|
+
}
|
|
271002
|
+
}
|
|
271003
|
+
} : verify;
|
|
270062
271004
|
setShowLabelsHighlight(highlight);
|
|
270063
271005
|
const runtimeBindings = {
|
|
270064
271006
|
box: trackedBox,
|
|
@@ -270199,7 +271141,8 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
|
|
|
270199
271141
|
jointsView,
|
|
270200
271142
|
viewConfig,
|
|
270201
271143
|
scene,
|
|
270202
|
-
|
|
271144
|
+
Viewport,
|
|
271145
|
+
verify: runtimeVerify,
|
|
270203
271146
|
spec,
|
|
270204
271147
|
mock,
|
|
270205
271148
|
gcode,
|
|
@@ -270337,8 +271280,21 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
|
|
|
270337
271280
|
throw error;
|
|
270338
271281
|
}
|
|
270339
271282
|
};
|
|
270340
|
-
const compiled = compileScript(code, fileName, options);
|
|
270341
271283
|
const bindingNames = Object.keys(runtimeBindings);
|
|
271284
|
+
const collision = findTopLevelRuntimeGlobalCollision(code, bindingNames);
|
|
271285
|
+
if (collision) {
|
|
271286
|
+
throw new Error(
|
|
271287
|
+
`Local declaration "${collision.name}" at ${fileName}:${collision.line}:${collision.column} collides with the built-in ForgeCAD runtime name "${collision.name}". Use the ForgeCAD API directly, or rename your local variable/import (for example, import the helper module object instead of destructuring "${collision.name}").`
|
|
271288
|
+
);
|
|
271289
|
+
}
|
|
271290
|
+
(_b3 = options.debug) == null ? void 0 : _b3.call(options, "executeFile:compile:start", { fileName, executionMode });
|
|
271291
|
+
const compileStart = performance.now();
|
|
271292
|
+
const compiled = compileScript(code, fileName, options);
|
|
271293
|
+
(_c2 = options.debug) == null ? void 0 : _c2.call(options, "executeFile:compile:end", {
|
|
271294
|
+
fileName,
|
|
271295
|
+
executionMode,
|
|
271296
|
+
ms: Number((performance.now() - compileStart).toFixed(1))
|
|
271297
|
+
});
|
|
270342
271298
|
const bindingValues = bindingNames.map((name) => runtimeBindings[name]);
|
|
270343
271299
|
let scriptCode = compiled.code;
|
|
270344
271300
|
if (executionMode === "script") {
|
|
@@ -270364,12 +271320,20 @@ ${scriptCode}
|
|
|
270364
271320
|
exports: executionMode === "module" && moduleCacheEntry ? moduleCacheEntry.exports : {}
|
|
270365
271321
|
};
|
|
270366
271322
|
const initialExportsRef = moduleValue.exports;
|
|
271323
|
+
(_d2 = options.debug) == null ? void 0 : _d2.call(options, "executeFile:invoke:start", { fileName, executionMode });
|
|
271324
|
+
const invokeStart = performance.now();
|
|
270367
271325
|
const returnValue = withConstructorChainLockdown(
|
|
270368
271326
|
() => runWithParamScope(
|
|
270369
271327
|
scope,
|
|
270370
271328
|
() => fn(moduleValue.exports, moduleValue, requireModule, fileName, dirnamePath(fileName), ...bindingValues)
|
|
270371
271329
|
)
|
|
270372
271330
|
);
|
|
271331
|
+
(_e = options.debug) == null ? void 0 : _e.call(options, "executeFile:invoke:end", {
|
|
271332
|
+
fileName,
|
|
271333
|
+
executionMode,
|
|
271334
|
+
ms: Number((performance.now() - invokeStart).toFixed(1)),
|
|
271335
|
+
returned: returnValue === void 0 ? "undefined" : returnValue === null ? "null" : typeof returnValue
|
|
271336
|
+
});
|
|
270373
271337
|
if (executionMode === "module") {
|
|
270374
271338
|
const hasExports = hasExplicitModuleExports(moduleValue.exports, initialExportsRef);
|
|
270375
271339
|
if (returnValue !== void 0 && hasExports) {
|
|
@@ -270408,12 +271372,14 @@ ${scriptCode}
|
|
|
270408
271372
|
}
|
|
270409
271373
|
return returnValue;
|
|
270410
271374
|
} finally {
|
|
271375
|
+
(_f = options.debug) == null ? void 0 : _f.call(options, "executeFile:end", { fileName, executionMode });
|
|
270411
271376
|
if (trackCircularImports) {
|
|
270412
271377
|
visited.delete(fileName);
|
|
270413
271378
|
}
|
|
270414
271379
|
}
|
|
270415
271380
|
}
|
|
270416
271381
|
function runScript(code, fileName = "main.forge.js", allFiles = {}, options = {}) {
|
|
271382
|
+
var _a3, _b3;
|
|
270417
271383
|
_collectedLogs = [];
|
|
270418
271384
|
resetExecutionSession(_collectedLogs);
|
|
270419
271385
|
const t0 = performance.now();
|
|
@@ -270422,14 +271388,25 @@ function runScript(code, fileName = "main.forge.js", allFiles = {}, options = {}
|
|
|
270422
271388
|
fileIndex: buildFileIndex(allFiles),
|
|
270423
271389
|
compiledFiles: persistentCompiledFiles,
|
|
270424
271390
|
moduleCache: /* @__PURE__ */ new Map(),
|
|
270425
|
-
readBinaryFile: options.readBinaryFile
|
|
271391
|
+
readBinaryFile: options.readBinaryFile,
|
|
271392
|
+
debug: options.debug
|
|
270426
271393
|
};
|
|
270427
271394
|
const quality = resolveForgeQualityPreset(options.quality);
|
|
271395
|
+
(_a3 = options.debug) == null ? void 0 : _a3.call(options, "runScript:start", { fileName, quality, fileCount: Object.keys(allFiles).length });
|
|
270428
271396
|
try {
|
|
270429
271397
|
return runWithForgeQuality(quality, () => {
|
|
271398
|
+
var _a4, _b4, _c2, _d2, _e, _f, _g, _h, _i, _j;
|
|
271399
|
+
(_a4 = options.debug) == null ? void 0 : _a4.call(options, "runScript:execute:start", { fileName });
|
|
271400
|
+
const executeStart = performance.now();
|
|
270430
271401
|
const result = executeFile(code, fileName, allFiles, /* @__PURE__ */ new Set(), {}, execOptions);
|
|
271402
|
+
(_b4 = options.debug) == null ? void 0 : _b4.call(options, "runScript:execute:end", {
|
|
271403
|
+
fileName,
|
|
271404
|
+
ms: Number((performance.now() - executeStart).toFixed(1)),
|
|
271405
|
+
resultType: result === void 0 ? "undefined" : result === null ? "null" : typeof result
|
|
271406
|
+
});
|
|
270431
271407
|
const highlights = getCollectedHighlights();
|
|
270432
271408
|
const mocks = getCollectedMocks();
|
|
271409
|
+
(_c2 = options.debug) == null ? void 0 : _c2.call(options, "runScript:map:start", { highlights: highlights.length, mocks: mocks.length });
|
|
270433
271410
|
const mapped = mapScriptResultToScene({
|
|
270434
271411
|
result,
|
|
270435
271412
|
fileName,
|
|
@@ -270438,24 +271415,43 @@ function runScript(code, fileName = "main.forge.js", allFiles = {}, options = {}
|
|
|
270438
271415
|
mocks,
|
|
270439
271416
|
logs: _collectedLogs
|
|
270440
271417
|
});
|
|
271418
|
+
(_d2 = options.debug) == null ? void 0 : _d2.call(options, "runScript:map:end", {
|
|
271419
|
+
objects: mapped.objects.length,
|
|
271420
|
+
hasShape: Boolean(mapped.shape),
|
|
271421
|
+
hasSketch: Boolean(mapped.sketch),
|
|
271422
|
+
hasError: Boolean(mapped.error)
|
|
271423
|
+
});
|
|
271424
|
+
(_e = options.debug) == null ? void 0 : _e.call(options, "runScript:explodeHints:start", { objects: mapped.objects.length });
|
|
270441
271425
|
autoFillExplodeHints(mapped.objects);
|
|
271426
|
+
(_f = options.debug) == null ? void 0 : _f.call(options, "runScript:explodeHints:end");
|
|
271427
|
+
(_g = options.debug) == null ? void 0 : _g.call(options, "runScript:snapshot:start", { objects: mapped.objects.length });
|
|
271428
|
+
const snapshot = collectSuccessfulExecutionSnapshot({
|
|
271429
|
+
quality,
|
|
271430
|
+
objects: mapped.objects,
|
|
271431
|
+
logs: _collectedLogs,
|
|
271432
|
+
extraDimensions: mapped.extraDimensions,
|
|
271433
|
+
highlights,
|
|
271434
|
+
mocks
|
|
271435
|
+
});
|
|
271436
|
+
(_h = options.debug) == null ? void 0 : _h.call(options, "runScript:snapshot:end", {
|
|
271437
|
+
params: snapshot.params.length,
|
|
271438
|
+
cutPlanes: snapshot.cutPlanes.length,
|
|
271439
|
+
verifications: snapshot.verifications.length
|
|
271440
|
+
});
|
|
271441
|
+
(_i = options.debug) == null ? void 0 : _i.call(options, "runScript:sceneTargets:start");
|
|
271442
|
+
snapshot.sceneConfig = resolveSceneJourneyTargets(snapshot.sceneConfig, mapped.objects);
|
|
271443
|
+
(_j = options.debug) == null ? void 0 : _j.call(options, "runScript:sceneTargets:end");
|
|
270442
271444
|
return {
|
|
270443
271445
|
shape: mapped.shape,
|
|
270444
271446
|
sketch: mapped.sketch,
|
|
270445
271447
|
objects: mapped.objects,
|
|
270446
|
-
...
|
|
270447
|
-
quality,
|
|
270448
|
-
objects: mapped.objects,
|
|
270449
|
-
logs: _collectedLogs,
|
|
270450
|
-
extraDimensions: mapped.extraDimensions,
|
|
270451
|
-
highlights,
|
|
270452
|
-
mocks
|
|
270453
|
-
}),
|
|
271448
|
+
...snapshot,
|
|
270454
271449
|
error: mapped.error,
|
|
270455
271450
|
timeMs: performance.now() - t0
|
|
270456
271451
|
};
|
|
270457
271452
|
});
|
|
270458
271453
|
} catch (e) {
|
|
271454
|
+
(_b3 = options.debug) == null ? void 0 : _b3.call(options, "runScript:error", { error: (e == null ? void 0 : e.message) || String(e) });
|
|
270459
271455
|
const msg = e.message || String(e);
|
|
270460
271456
|
const stack = e.stack || "";
|
|
270461
271457
|
let lineInfo = "";
|
|
@@ -271051,6 +272047,31 @@ function serializeRunResult(result, solverDebug = null) {
|
|
|
271051
272047
|
};
|
|
271052
272048
|
return { serialized, transferables };
|
|
271053
272049
|
}
|
|
272050
|
+
function serializeRunMetadata(result) {
|
|
272051
|
+
const { shape: _shape, sketch: _sketch, objects, debugHighlights3D: _debugHighlights3D, mocks: _mocks, ...passthrough } = result;
|
|
272052
|
+
return {
|
|
272053
|
+
...passthrough,
|
|
272054
|
+
objects: objects.map((obj) => ({
|
|
272055
|
+
id: obj.id,
|
|
272056
|
+
name: obj.name,
|
|
272057
|
+
hasShape: Boolean(obj.shape),
|
|
272058
|
+
hasSketch: Boolean(obj.sketch),
|
|
272059
|
+
sdfData: obj.sdf ?? null,
|
|
272060
|
+
toolpathData: obj.toolpath ?? null,
|
|
272061
|
+
color: obj.color,
|
|
272062
|
+
materialProps: obj.materialProps,
|
|
272063
|
+
geometryInfo: obj.geometryInfo,
|
|
272064
|
+
sketchMeta: obj.sketchMeta,
|
|
272065
|
+
groupName: obj.groupName,
|
|
272066
|
+
treePath: obj.treePath,
|
|
272067
|
+
mock: obj.mock,
|
|
272068
|
+
serverShapeRef: obj.serverShapeRef,
|
|
272069
|
+
exactState: obj.exactState
|
|
272070
|
+
})),
|
|
272071
|
+
debugHighlights3D: [],
|
|
272072
|
+
mocks: []
|
|
272073
|
+
};
|
|
272074
|
+
}
|
|
271054
272075
|
let currentProjectId = null;
|
|
271055
272076
|
function readBinaryFile(resolvedPath) {
|
|
271056
272077
|
const xhr = new XMLHttpRequest();
|
|
@@ -271124,6 +272145,7 @@ async function runOnce(payload) {
|
|
|
271124
272145
|
console.log(`[worker] seq=${seq} stale (newer queued) — skipping serialize. run=${(tRun - tKernel).toFixed(0)}ms`);
|
|
271125
272146
|
return;
|
|
271126
272147
|
}
|
|
272148
|
+
worker.postMessage({ type: "run-metadata", payload: { seq, metadata: serializeRunMetadata(runResult) } });
|
|
271127
272149
|
disposeRunResult(lastRunResult);
|
|
271128
272150
|
lastRunResult = runResult;
|
|
271129
272151
|
worker.postMessage({ type: "progress", payload: { seq, phase: "serializing" } });
|