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
package/dist-cli/forgecad.js
CHANGED
|
@@ -4172,11 +4172,11 @@ function extractEdgeSegments(mesh) {
|
|
|
4172
4172
|
const revKey = useNumeric ? vb * MAX_NUMERIC + va : `${vb},${va}`;
|
|
4173
4173
|
const adjTri = halfEdges.get(revKey);
|
|
4174
4174
|
if (adjTri !== void 0) {
|
|
4175
|
-
const
|
|
4176
|
-
if (
|
|
4175
|
+
const dot9 = faceNx[t] * faceNx[adjTri] + faceNy[t] * faceNy[adjTri] + faceNz[t] * faceNz[adjTri];
|
|
4176
|
+
if (dot9 <= EDGE_THRESHOLD_DOT) {
|
|
4177
4177
|
const origVa = triVerts[t * 3 + e];
|
|
4178
4178
|
const origVb = triVerts[t * 3 + (e + 1) % 3];
|
|
4179
|
-
const seg = buildEdgeSegment(edgeIndex++, origVa, origVb, vertProperties, numProp, t, adjTri, faceNx, faceNy, faceNz,
|
|
4179
|
+
const seg = buildEdgeSegment(edgeIndex++, origVa, origVb, vertProperties, numProp, t, adjTri, faceNx, faceNy, faceNz, dot9, false);
|
|
4180
4180
|
edges.push(seg);
|
|
4181
4181
|
}
|
|
4182
4182
|
halfEdges.delete(revKey);
|
|
@@ -4206,7 +4206,7 @@ function extractEdgeSegments(mesh) {
|
|
|
4206
4206
|
}
|
|
4207
4207
|
return edges;
|
|
4208
4208
|
}
|
|
4209
|
-
function buildEdgeSegment(index, origVa, origVb, vertProperties, numProp, triA, triB, faceNx, faceNy, faceNz,
|
|
4209
|
+
function buildEdgeSegment(index, origVa, origVb, vertProperties, numProp, triA, triB, faceNx, faceNy, faceNz, dot9, boundary) {
|
|
4210
4210
|
const sx = vertProperties[origVa * numProp];
|
|
4211
4211
|
const sy = vertProperties[origVa * numProp + 1];
|
|
4212
4212
|
const sz = vertProperties[origVa * numProp + 2];
|
|
@@ -4218,7 +4218,7 @@ function buildEdgeSegment(index, origVa, origVb, vertProperties, numProp, triA,
|
|
|
4218
4218
|
const invLen = length5 > 1e-12 ? 1 / length5 : 0;
|
|
4219
4219
|
const nA = [faceNx[triA], faceNy[triA], faceNz[triA]];
|
|
4220
4220
|
const nB = [faceNx[triB], faceNy[triB], faceNz[triB]];
|
|
4221
|
-
const clampedDot = Math.max(-1, Math.min(1,
|
|
4221
|
+
const clampedDot = Math.max(-1, Math.min(1, dot9));
|
|
4222
4222
|
const dihedralAngle = boundary ? 0 : 180 - Math.acos(clampedDot) * (180 / Math.PI);
|
|
4223
4223
|
const crossX = nA[1] * nB[2] - nA[2] * nB[1];
|
|
4224
4224
|
const crossY = nA[2] * nB[0] - nA[0] * nB[2];
|
|
@@ -5467,20 +5467,20 @@ function resolvePlaneOriginNormal(plane) {
|
|
|
5467
5467
|
}
|
|
5468
5468
|
function rotationToPlaneSpace(normal) {
|
|
5469
5469
|
const n = normalize2(normal);
|
|
5470
|
-
const
|
|
5471
|
-
if (
|
|
5470
|
+
const dot9 = n[2];
|
|
5471
|
+
if (dot9 > 1 - EPS4) {
|
|
5472
5472
|
return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
|
|
5473
5473
|
}
|
|
5474
5474
|
let axis;
|
|
5475
5475
|
let angle;
|
|
5476
|
-
if (
|
|
5476
|
+
if (dot9 < -1 + EPS4) {
|
|
5477
5477
|
axis = [1, 0, 0];
|
|
5478
5478
|
angle = Math.PI;
|
|
5479
5479
|
} else {
|
|
5480
5480
|
axis = [n[1], -n[0], 0];
|
|
5481
5481
|
const axisLen = length(axis);
|
|
5482
5482
|
axis = [axis[0] / axisLen, axis[1] / axisLen, axis[2] / axisLen];
|
|
5483
|
-
angle = Math.acos(
|
|
5483
|
+
angle = Math.acos(dot9);
|
|
5484
5484
|
}
|
|
5485
5485
|
const [x, y, z] = axis;
|
|
5486
5486
|
const c = Math.cos(angle);
|
|
@@ -10560,8 +10560,8 @@ function isFlat(p0, pMid, p1, cosThreshold) {
|
|
|
10560
10560
|
const len1sq = d1x * d1x + d1y * d1y + d1z * d1z;
|
|
10561
10561
|
const len2sq = d2x * d2x + d2y * d2y + d2z * d2z;
|
|
10562
10562
|
if (len1sq < 1e-20 || len2sq < 1e-20) return true;
|
|
10563
|
-
const
|
|
10564
|
-
const cosAngle =
|
|
10563
|
+
const dot9 = d1x * d2x + d1y * d2y + d1z * d2z;
|
|
10564
|
+
const cosAngle = dot9 / Math.sqrt(len1sq * len2sq);
|
|
10565
10565
|
return cosAngle >= cosThreshold;
|
|
10566
10566
|
}
|
|
10567
10567
|
function extractBoundingBox(oc, shape) {
|
|
@@ -12535,8 +12535,8 @@ Faces on this shape (${allFaces.length}):`);
|
|
|
12535
12535
|
lines.push(` ... and ${allFaces.length - 10} more`);
|
|
12536
12536
|
}
|
|
12537
12537
|
if (query.normal) {
|
|
12538
|
-
const
|
|
12539
|
-
const sorted = [...allFaces].map((f3) => ({ face: f3, sim:
|
|
12538
|
+
const dot9 = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
|
12539
|
+
const sorted = [...allFaces].map((f3) => ({ face: f3, sim: dot9(f3.normal, query.normal) })).sort((a, b) => b.sim - a.sim).slice(0, 3);
|
|
12540
12540
|
if (sorted.length > 0 && sorted[0].sim < 0.9998) {
|
|
12541
12541
|
lines.push(`
|
|
12542
12542
|
Nearest normals to query:`);
|
|
@@ -18313,7 +18313,7 @@ function extractEdgesWithFaces(mesh, triCluster) {
|
|
|
18313
18313
|
}
|
|
18314
18314
|
return edges;
|
|
18315
18315
|
}
|
|
18316
|
-
function buildEdgeWithFaces(index, origVa, origVb, vertProperties, numProp, triA, triB, faceNx, faceNy, faceNz,
|
|
18316
|
+
function buildEdgeWithFaces(index, origVa, origVb, vertProperties, numProp, triA, triB, faceNx, faceNy, faceNz, dot9, boundary, clusterA, clusterB) {
|
|
18317
18317
|
const sx = vertProperties[origVa * numProp];
|
|
18318
18318
|
const sy = vertProperties[origVa * numProp + 1];
|
|
18319
18319
|
const sz = vertProperties[origVa * numProp + 2];
|
|
@@ -18325,7 +18325,7 @@ function buildEdgeWithFaces(index, origVa, origVb, vertProperties, numProp, triA
|
|
|
18325
18325
|
const invLen = length5 > 1e-12 ? 1 / length5 : 0;
|
|
18326
18326
|
const nA = [faceNx[triA], faceNy[triA], faceNz[triA]];
|
|
18327
18327
|
const nB = [faceNx[triB], faceNy[triB], faceNz[triB]];
|
|
18328
|
-
const clampedDot = Math.max(-1, Math.min(1,
|
|
18328
|
+
const clampedDot = Math.max(-1, Math.min(1, dot9));
|
|
18329
18329
|
const dihedralAngle = boundary ? 0 : 180 - Math.acos(clampedDot) * (180 / Math.PI);
|
|
18330
18330
|
const crossX = nA[1] * nB[2] - nA[2] * nB[1];
|
|
18331
18331
|
const crossY = nA[2] * nB[0] - nA[0] * nB[2];
|
|
@@ -20162,6 +20162,7 @@ Fix: pass a path like [[0,0,0], [20,0,8,4], [40,0,0,2]].`
|
|
|
20162
20162
|
const curveMode = shouldSmoothCurve(options, defaultCurve) && sourcePoints.length >= 3;
|
|
20163
20163
|
const normalized = compactPathPoints(resampleCurve(sourcePoints, options, defaultCurve));
|
|
20164
20164
|
const minRadius = Math.min(...normalized.map((point2) => point2.radius));
|
|
20165
|
+
const hasExplicitBlend = options.blend !== void 0;
|
|
20165
20166
|
const blendRadius = positiveFinite(options.blend, "Sculpt.tube() blend", Math.max(0.5, minRadius));
|
|
20166
20167
|
let segmentCount = 0;
|
|
20167
20168
|
for (let i = 0; i < normalized.length - 1; i += 1) {
|
|
@@ -20170,8 +20171,8 @@ Fix: pass a path like [[0,0,0], [20,0,8,4], [40,0,0,2]].`
|
|
|
20170
20171
|
if (segmentCount === 0) {
|
|
20171
20172
|
throw new Error("Sculpt.tube() points must include at least one non-zero-length segment.");
|
|
20172
20173
|
}
|
|
20173
|
-
const
|
|
20174
|
-
let out = polylineSweep(normalized,
|
|
20174
|
+
const effectiveBlendRadius = curveMode && !hasExplicitBlend ? Math.max(blendRadius, minRadius * 1.5) : blendRadius;
|
|
20175
|
+
let out = polylineSweep(normalized, effectiveBlendRadius);
|
|
20175
20176
|
if (options.polish !== void 0) out = out.polish(options.polish);
|
|
20176
20177
|
return out;
|
|
20177
20178
|
}
|
|
@@ -24422,6 +24423,11 @@ function jointsView(options = {}) {
|
|
|
24422
24423
|
}
|
|
24423
24424
|
|
|
24424
24425
|
// src/forge/assembly/assembly.ts
|
|
24426
|
+
var SWEEP_JOINT_DEFAULT_STEP_LIMIT = {
|
|
24427
|
+
live: 1,
|
|
24428
|
+
default: 4,
|
|
24429
|
+
high: Number.POSITIVE_INFINITY
|
|
24430
|
+
};
|
|
24425
24431
|
var collectedAssemblies = [];
|
|
24426
24432
|
function resetCollectedAssemblies() {
|
|
24427
24433
|
collectedAssemblies = [];
|
|
@@ -24465,6 +24471,33 @@ function collisionShape(part) {
|
|
|
24465
24471
|
if (shapes.length === 1) return shapes[0];
|
|
24466
24472
|
return union(...shapes);
|
|
24467
24473
|
}
|
|
24474
|
+
function boundsOverlap(a, b) {
|
|
24475
|
+
for (let axis = 0; axis < 3; axis++) {
|
|
24476
|
+
if (a.max[axis] <= b.min[axis] || b.max[axis] <= a.min[axis]) return false;
|
|
24477
|
+
}
|
|
24478
|
+
return true;
|
|
24479
|
+
}
|
|
24480
|
+
function readAssemblyPerfEnv(name) {
|
|
24481
|
+
return typeof process !== "undefined" ? process.env?.[name] : void 0;
|
|
24482
|
+
}
|
|
24483
|
+
function resolveSweepJointStepLimit() {
|
|
24484
|
+
if (readAssemblyPerfEnv("FORGECAD_ALLOW_FULL_SWEEP_JOINT") === "1") return Number.POSITIVE_INFINITY;
|
|
24485
|
+
const override = readAssemblyPerfEnv("FORGECAD_SWEEP_JOINT_STEP_LIMIT");
|
|
24486
|
+
if (override != null && override.trim() !== "") {
|
|
24487
|
+
const parsed = Number(override);
|
|
24488
|
+
if (Number.isFinite(parsed) && parsed >= 1) return Math.floor(parsed);
|
|
24489
|
+
}
|
|
24490
|
+
return SWEEP_JOINT_DEFAULT_STEP_LIMIT[getForgeQualityPreset()];
|
|
24491
|
+
}
|
|
24492
|
+
function boundSweepJointSteps(jointName, requestedSteps) {
|
|
24493
|
+
const limit = resolveSweepJointStepLimit();
|
|
24494
|
+
if (!Number.isFinite(limit) || requestedSteps <= limit) return requestedSteps;
|
|
24495
|
+
const bounded = Math.max(1, Math.floor(limit));
|
|
24496
|
+
emitRuntimeWarning(
|
|
24497
|
+
`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.`
|
|
24498
|
+
);
|
|
24499
|
+
return bounded;
|
|
24500
|
+
}
|
|
24468
24501
|
var FASTENER_PATTERN = /\b(bolt|screw|nut|washer|pin|rivet|fastener|standoff|insert)\b/i;
|
|
24469
24502
|
function isFastenerName(name) {
|
|
24470
24503
|
return FASTENER_PATTERN.test(name);
|
|
@@ -24793,16 +24826,23 @@ var SolvedAssembly = class {
|
|
|
24793
24826
|
const minOverlap = options.minOverlapVolume ?? 0.1;
|
|
24794
24827
|
const ignore = new Set((options.ignorePairs ?? []).map(([a, b]) => [a, b].sort().join("|")));
|
|
24795
24828
|
const findings = [];
|
|
24796
|
-
|
|
24797
|
-
|
|
24798
|
-
|
|
24799
|
-
|
|
24829
|
+
const entries = [];
|
|
24830
|
+
for (const name of names) {
|
|
24831
|
+
const shape = collisionShape(this.getPart(name));
|
|
24832
|
+
if (!shape) continue;
|
|
24833
|
+
try {
|
|
24834
|
+
entries.push({ name, shape, bounds: shape.boundingBox() });
|
|
24835
|
+
} catch {
|
|
24836
|
+
}
|
|
24837
|
+
}
|
|
24838
|
+
for (let i = 0; i < entries.length; i++) {
|
|
24839
|
+
for (let j = i + 1; j < entries.length; j++) {
|
|
24840
|
+
const aName = entries[i].name;
|
|
24841
|
+
const bName = entries[j].name;
|
|
24800
24842
|
if (ignore.has([aName, bName].sort().join("|"))) continue;
|
|
24801
|
-
|
|
24802
|
-
const b = collisionShape(this.getPart(bName));
|
|
24803
|
-
if (!a || !b) continue;
|
|
24843
|
+
if (!boundsOverlap(entries[i].bounds, entries[j].bounds)) continue;
|
|
24804
24844
|
try {
|
|
24805
|
-
const hit =
|
|
24845
|
+
const hit = entries[i].shape.intersect(entries[j].shape);
|
|
24806
24846
|
if (hit.isEmpty()) continue;
|
|
24807
24847
|
const vol = hit.volume();
|
|
24808
24848
|
if (vol > minOverlap) {
|
|
@@ -25779,7 +25819,7 @@ var Assembly = class _Assembly {
|
|
|
25779
25819
|
if (this.jointCouplings.has(jointName)) {
|
|
25780
25820
|
throw new Error(`Cannot sweep coupled joint "${jointName}". Sweep one of its source joints instead.`);
|
|
25781
25821
|
}
|
|
25782
|
-
const n = Math.max(1, Math.floor(steps));
|
|
25822
|
+
const n = boundSweepJointSteps(jointName, Math.max(1, Math.floor(steps)));
|
|
25783
25823
|
const frames = [];
|
|
25784
25824
|
for (let i = 0; i <= n; i++) {
|
|
25785
25825
|
const t = n === 0 ? 0 : i / n;
|
|
@@ -29280,9 +29320,9 @@ var PathBuilder = class {
|
|
|
29280
29320
|
const [d1x, d1y] = this.getSegDirAt(prev, "end");
|
|
29281
29321
|
const [d2x, d2y] = this.getSegDirAt(curr, "start");
|
|
29282
29322
|
const cross7 = d1x * d2y - d1y * d2x;
|
|
29283
|
-
const
|
|
29323
|
+
const dot9 = d1x * d2x + d1y * d2y;
|
|
29284
29324
|
if (Math.abs(cross7) < 1e-9) return { trimA: [cx, cy], trimB: [cx, cy], arcSeg: null };
|
|
29285
|
-
const halfAngle = Math.atan2(Math.abs(cross7),
|
|
29325
|
+
const halfAngle = Math.atan2(Math.abs(cross7), dot9) / 2;
|
|
29286
29326
|
const trimDist = radius / Math.tan(halfAngle);
|
|
29287
29327
|
const trimA = [cx - d1x * trimDist, cy - d1y * trimDist];
|
|
29288
29328
|
const trimB = [cx + d2x * trimDist, cy + d2y * trimDist];
|
|
@@ -29419,8 +29459,8 @@ var PathBuilder = class {
|
|
|
29419
29459
|
const reflect = (px, py) => {
|
|
29420
29460
|
const dx = px - pivotX;
|
|
29421
29461
|
const dy = py - pivotY;
|
|
29422
|
-
const
|
|
29423
|
-
return [px - 2 *
|
|
29462
|
+
const dot9 = dx * nx + dy * ny;
|
|
29463
|
+
return [px - 2 * dot9 * nx, py - 2 * dot9 * ny];
|
|
29424
29464
|
};
|
|
29425
29465
|
const toMirror = this.segs.slice(1).reverse();
|
|
29426
29466
|
for (const seg of toMirror) {
|
|
@@ -30533,8 +30573,8 @@ function resolveExplodeDirection(mode, center, originCenter, seed) {
|
|
|
30533
30573
|
return [0, 0, 1];
|
|
30534
30574
|
}
|
|
30535
30575
|
function explodeProjectPerpendicular(vec, axis) {
|
|
30536
|
-
const
|
|
30537
|
-
return [vec[0] - axis[0] *
|
|
30576
|
+
const dot9 = explodeDot(vec, axis);
|
|
30577
|
+
return [vec[0] - axis[0] * dot9, vec[1] - axis[1] * dot9, vec[2] - axis[2] * dot9];
|
|
30538
30578
|
}
|
|
30539
30579
|
function applyExplodeTreeBias(direction2, mode, inheritedDirection, seed) {
|
|
30540
30580
|
if (!inheritedDirection || mode !== "radial") return direction2;
|
|
@@ -44520,6 +44560,132 @@ function shapeToGeometryFallback(shape) {
|
|
|
44520
44560
|
return { solid, edges, hasSmoothNormals: false };
|
|
44521
44561
|
}
|
|
44522
44562
|
|
|
44563
|
+
// src/forge/renderLabel.ts
|
|
44564
|
+
var _collected5 = [];
|
|
44565
|
+
var _nextId = 1;
|
|
44566
|
+
function resetRenderLabels() {
|
|
44567
|
+
_collected5 = [];
|
|
44568
|
+
_nextId = 1;
|
|
44569
|
+
}
|
|
44570
|
+
function getCollectedRenderLabels() {
|
|
44571
|
+
return _collected5.map((label) => ({ ...label, at: [...label.at], offset: [...label.offset] }));
|
|
44572
|
+
}
|
|
44573
|
+
function requireFinite2(value, label) {
|
|
44574
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
44575
|
+
throw new Error(`${label} must be a finite number`);
|
|
44576
|
+
}
|
|
44577
|
+
return value;
|
|
44578
|
+
}
|
|
44579
|
+
function requireVec3(value, label) {
|
|
44580
|
+
if (!Array.isArray(value) || value.length !== 3) {
|
|
44581
|
+
throw new Error(`${label} must be [x, y, z]`);
|
|
44582
|
+
}
|
|
44583
|
+
return [requireFinite2(value[0], `${label}[0]`), requireFinite2(value[1], `${label}[1]`), requireFinite2(value[2], `${label}[2]`)];
|
|
44584
|
+
}
|
|
44585
|
+
function optionalColor(value, label) {
|
|
44586
|
+
if (value === void 0) return void 0;
|
|
44587
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
44588
|
+
throw new Error(`${label} must be a non-empty CSS color string`);
|
|
44589
|
+
}
|
|
44590
|
+
return value.trim();
|
|
44591
|
+
}
|
|
44592
|
+
var VALID_ANCHORS = /* @__PURE__ */ new Set([
|
|
44593
|
+
"center",
|
|
44594
|
+
"top",
|
|
44595
|
+
"bottom",
|
|
44596
|
+
"left",
|
|
44597
|
+
"right",
|
|
44598
|
+
"top-left",
|
|
44599
|
+
"top-right",
|
|
44600
|
+
"bottom-left",
|
|
44601
|
+
"bottom-right"
|
|
44602
|
+
]);
|
|
44603
|
+
function normalizeOptions(options) {
|
|
44604
|
+
if (options === void 0) {
|
|
44605
|
+
return { offset: [0, 0, 0], anchor: "center", alwaysOnTop: true };
|
|
44606
|
+
}
|
|
44607
|
+
if (!options || typeof options !== "object" || Array.isArray(options)) {
|
|
44608
|
+
throw new Error("Viewport.label options must be an object");
|
|
44609
|
+
}
|
|
44610
|
+
const out = {
|
|
44611
|
+
offset: [0, 0, 0],
|
|
44612
|
+
anchor: "center",
|
|
44613
|
+
alwaysOnTop: true
|
|
44614
|
+
};
|
|
44615
|
+
const color = optionalColor(options.color, "Viewport.label options.color");
|
|
44616
|
+
if (color !== void 0) out.color = color;
|
|
44617
|
+
const background = optionalColor(options.background, "Viewport.label options.background");
|
|
44618
|
+
if (background !== void 0) out.background = background;
|
|
44619
|
+
if (options.size !== void 0) {
|
|
44620
|
+
out.size = requireFinite2(options.size, "Viewport.label options.size");
|
|
44621
|
+
if (out.size <= 0) throw new Error("Viewport.label options.size must be positive");
|
|
44622
|
+
}
|
|
44623
|
+
if (options.offset !== void 0) out.offset = requireVec3(options.offset, "Viewport.label options.offset");
|
|
44624
|
+
if (options.anchor !== void 0) {
|
|
44625
|
+
if (!VALID_ANCHORS.has(options.anchor)) {
|
|
44626
|
+
throw new Error(`Viewport.label options.anchor must be one of: ${Array.from(VALID_ANCHORS).join(", ")}`);
|
|
44627
|
+
}
|
|
44628
|
+
out.anchor = options.anchor;
|
|
44629
|
+
}
|
|
44630
|
+
if (options.alwaysOnTop !== void 0) {
|
|
44631
|
+
if (typeof options.alwaysOnTop !== "boolean") throw new Error("Viewport.label options.alwaysOnTop must be a boolean");
|
|
44632
|
+
out.alwaysOnTop = options.alwaysOnTop;
|
|
44633
|
+
}
|
|
44634
|
+
return out;
|
|
44635
|
+
}
|
|
44636
|
+
function collectRenderLabel(text, at, options) {
|
|
44637
|
+
if (typeof text !== "string" || text.trim().length === 0) {
|
|
44638
|
+
throw new Error("Viewport.label text must be a non-empty string");
|
|
44639
|
+
}
|
|
44640
|
+
const normalizedAt = requireVec3(at, "Viewport.label at");
|
|
44641
|
+
const normalizedOptions = normalizeOptions(options);
|
|
44642
|
+
_collected5.push({
|
|
44643
|
+
id: `render-label-${_nextId++}`,
|
|
44644
|
+
text: text.trim(),
|
|
44645
|
+
at: normalizedAt,
|
|
44646
|
+
...normalizedOptions
|
|
44647
|
+
});
|
|
44648
|
+
}
|
|
44649
|
+
var Viewport = {
|
|
44650
|
+
/**
|
|
44651
|
+
* Add a render-only viewport label at a world-space point.
|
|
44652
|
+
*
|
|
44653
|
+
* **Details**
|
|
44654
|
+
*
|
|
44655
|
+
* `Viewport.label()` is for explanatory text that helps a viewer understand
|
|
44656
|
+
* the model. It does not create sketches, meshes, B-rep topology, exported
|
|
44657
|
+
* text, or face labels, so it stays off the OCCT path. Use `text2d()` only
|
|
44658
|
+
* when the letters should become manufactured geometry, such as raised
|
|
44659
|
+
* lettering, engraved serial numbers, or exported nameplates.
|
|
44660
|
+
*
|
|
44661
|
+
* Labels are collected during script execution and rendered by the viewport
|
|
44662
|
+
* as lightweight overlay annotations. They are ignored by exports and do not
|
|
44663
|
+
* appear in `objects`.
|
|
44664
|
+
*
|
|
44665
|
+
* **Example**
|
|
44666
|
+
*
|
|
44667
|
+
* ```js
|
|
44668
|
+
* Viewport.label('Bearing bore', [0, 0, 18], {
|
|
44669
|
+
* color: '#f8fafc',
|
|
44670
|
+
* background: '#0f172acc',
|
|
44671
|
+
* offset: [0, 0, 8],
|
|
44672
|
+
* anchor: 'bottom',
|
|
44673
|
+
* });
|
|
44674
|
+
*
|
|
44675
|
+
* return box(40, 30, 12);
|
|
44676
|
+
* ```
|
|
44677
|
+
*
|
|
44678
|
+
* @param text - Label text to display in the viewport
|
|
44679
|
+
* @param at - World-space anchor point `[x, y, z]`
|
|
44680
|
+
* @param options - Visual label options
|
|
44681
|
+
* @returns void
|
|
44682
|
+
* @category Viewport Labels
|
|
44683
|
+
*/
|
|
44684
|
+
label(text, at, options) {
|
|
44685
|
+
collectRenderLabel(text, at, options);
|
|
44686
|
+
}
|
|
44687
|
+
};
|
|
44688
|
+
|
|
44523
44689
|
// src/forge/renderStyle.ts
|
|
44524
44690
|
var RENDER_STYLE_OPTIONS = [
|
|
44525
44691
|
{
|
|
@@ -44666,17 +44832,17 @@ var rail = {
|
|
|
44666
44832
|
};
|
|
44667
44833
|
|
|
44668
44834
|
// src/forge/scene/scene.ts
|
|
44669
|
-
function
|
|
44835
|
+
function requireFinite3(value, label) {
|
|
44670
44836
|
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
44671
44837
|
throw new Error(`${label} must be a finite number`);
|
|
44672
44838
|
}
|
|
44673
44839
|
return value;
|
|
44674
44840
|
}
|
|
44675
|
-
function
|
|
44841
|
+
function requireVec32(value, label) {
|
|
44676
44842
|
if (!Array.isArray(value) || value.length !== 3) {
|
|
44677
44843
|
throw new Error(`${label} must be [x, y, z]`);
|
|
44678
44844
|
}
|
|
44679
|
-
return [
|
|
44845
|
+
return [requireFinite3(value[0], `${label}[0]`), requireFinite3(value[1], `${label}[1]`), requireFinite3(value[2], `${label}[2]`)];
|
|
44680
44846
|
}
|
|
44681
44847
|
function requireColor(value, label) {
|
|
44682
44848
|
if (typeof value !== "string" || !value.trim()) {
|
|
@@ -44700,11 +44866,11 @@ var VALID_ENVIRONMENT_PRESETS = /* @__PURE__ */ new Set([
|
|
|
44700
44866
|
]);
|
|
44701
44867
|
function validateCamera(cam, label) {
|
|
44702
44868
|
const out = {};
|
|
44703
|
-
if (cam.position !== void 0) out.position =
|
|
44704
|
-
if (cam.target !== void 0) out.target =
|
|
44705
|
-
if (cam.up !== void 0) out.up =
|
|
44869
|
+
if (cam.position !== void 0) out.position = requireVec32(cam.position, `${label}.position`);
|
|
44870
|
+
if (cam.target !== void 0) out.target = requireVec32(cam.target, `${label}.target`);
|
|
44871
|
+
if (cam.up !== void 0) out.up = requireVec32(cam.up, `${label}.up`);
|
|
44706
44872
|
if (cam.fov !== void 0) {
|
|
44707
|
-
out.fov =
|
|
44873
|
+
out.fov = requireFinite3(cam.fov, `${label}.fov`);
|
|
44708
44874
|
if (out.fov <= 0 || out.fov >= 180) throw new Error(`${label}.fov must be between 0 and 180`);
|
|
44709
44875
|
}
|
|
44710
44876
|
if (cam.type !== void 0) {
|
|
@@ -44715,6 +44881,120 @@ function validateCamera(cam, label) {
|
|
|
44715
44881
|
}
|
|
44716
44882
|
return out;
|
|
44717
44883
|
}
|
|
44884
|
+
function validateViewCamera(cam, label) {
|
|
44885
|
+
const validated = validateCamera(cam, label);
|
|
44886
|
+
if (!validated.position) {
|
|
44887
|
+
throw new Error(`${label}.position is required for named render views`);
|
|
44888
|
+
}
|
|
44889
|
+
if (!validated.target) {
|
|
44890
|
+
throw new Error(`${label}.target is required for named render views`);
|
|
44891
|
+
}
|
|
44892
|
+
return {
|
|
44893
|
+
...validated,
|
|
44894
|
+
position: validated.position,
|
|
44895
|
+
target: validated.target
|
|
44896
|
+
};
|
|
44897
|
+
}
|
|
44898
|
+
function validateViews(views, label) {
|
|
44899
|
+
if (!views || typeof views !== "object" || Array.isArray(views)) {
|
|
44900
|
+
throw new Error(`${label} must be an object mapping view names to cameras`);
|
|
44901
|
+
}
|
|
44902
|
+
const out = {};
|
|
44903
|
+
for (const [name, view] of Object.entries(views)) {
|
|
44904
|
+
if (!name.trim()) {
|
|
44905
|
+
throw new Error(`${label} names must be non-empty strings`);
|
|
44906
|
+
}
|
|
44907
|
+
const viewLabel = `${label}.${name}`;
|
|
44908
|
+
if (!view || typeof view !== "object" || Array.isArray(view)) {
|
|
44909
|
+
throw new Error(`${viewLabel} must be a camera object or an object with a camera property`);
|
|
44910
|
+
}
|
|
44911
|
+
const hasExplicitCamera = Object.prototype.hasOwnProperty.call(view, "camera");
|
|
44912
|
+
if (hasExplicitCamera) {
|
|
44913
|
+
const camera = view.camera;
|
|
44914
|
+
if (!camera || typeof camera !== "object" || Array.isArray(camera)) {
|
|
44915
|
+
throw new Error(`${viewLabel}.camera must be an object`);
|
|
44916
|
+
}
|
|
44917
|
+
out[name] = { camera: validateViewCamera(camera, `${viewLabel}.camera`) };
|
|
44918
|
+
continue;
|
|
44919
|
+
}
|
|
44920
|
+
out[name] = { camera: validateViewCamera(view, viewLabel) };
|
|
44921
|
+
}
|
|
44922
|
+
return out;
|
|
44923
|
+
}
|
|
44924
|
+
function requireString(value, label) {
|
|
44925
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
44926
|
+
throw new Error(`${label} must be a non-empty string`);
|
|
44927
|
+
}
|
|
44928
|
+
return value.trim();
|
|
44929
|
+
}
|
|
44930
|
+
function optionalString(value, label) {
|
|
44931
|
+
if (value === void 0) return void 0;
|
|
44932
|
+
return requireString(value, label);
|
|
44933
|
+
}
|
|
44934
|
+
function validateJourneyStep(step, label) {
|
|
44935
|
+
if (!step || typeof step !== "object" || Array.isArray(step)) {
|
|
44936
|
+
throw new Error(`${label} must be an object`);
|
|
44937
|
+
}
|
|
44938
|
+
const out = {
|
|
44939
|
+
id: requireString(step.id, `${label}.id`)
|
|
44940
|
+
};
|
|
44941
|
+
const title = optionalString(step.title, `${label}.title`);
|
|
44942
|
+
if (title !== void 0) out.title = title;
|
|
44943
|
+
const focus = optionalString(step.focus, `${label}.focus`);
|
|
44944
|
+
if (focus !== void 0) out.focus = focus;
|
|
44945
|
+
const caption = optionalString(step.caption, `${label}.caption`);
|
|
44946
|
+
if (caption !== void 0) out.caption = caption;
|
|
44947
|
+
if (step.camera !== void 0) {
|
|
44948
|
+
if (!step.camera || typeof step.camera !== "object" || Array.isArray(step.camera)) {
|
|
44949
|
+
throw new Error(`${label}.camera must be an object`);
|
|
44950
|
+
}
|
|
44951
|
+
out.camera = validateViewCamera(step.camera, `${label}.camera`);
|
|
44952
|
+
}
|
|
44953
|
+
return out;
|
|
44954
|
+
}
|
|
44955
|
+
function validateJourney(journey, label) {
|
|
44956
|
+
if (!journey || typeof journey !== "object" || Array.isArray(journey)) {
|
|
44957
|
+
throw new Error(`${label} must be an object`);
|
|
44958
|
+
}
|
|
44959
|
+
if (!Array.isArray(journey.steps) || journey.steps.length === 0) {
|
|
44960
|
+
throw new Error(`${label}.steps must be a non-empty array`);
|
|
44961
|
+
}
|
|
44962
|
+
const out = {
|
|
44963
|
+
steps: journey.steps.map((step, index) => validateJourneyStep(step, `${label}.steps[${index}]`))
|
|
44964
|
+
};
|
|
44965
|
+
const title = optionalString(journey.title, `${label}.title`);
|
|
44966
|
+
if (title !== void 0) out.title = title;
|
|
44967
|
+
const startsAt = optionalString(journey.startsAt, `${label}.startsAt`);
|
|
44968
|
+
if (startsAt !== void 0) out.startsAt = startsAt;
|
|
44969
|
+
if (journey.behavior !== void 0) {
|
|
44970
|
+
if (journey.behavior !== "opt-in" && journey.behavior !== "auto") {
|
|
44971
|
+
throw new Error(`${label}.behavior must be "opt-in" or "auto"`);
|
|
44972
|
+
}
|
|
44973
|
+
out.behavior = journey.behavior;
|
|
44974
|
+
}
|
|
44975
|
+
const seen = /* @__PURE__ */ new Set();
|
|
44976
|
+
for (const step of out.steps) {
|
|
44977
|
+
if (seen.has(step.id)) {
|
|
44978
|
+
throw new Error(`${label}.steps contains duplicate step id "${step.id}"`);
|
|
44979
|
+
}
|
|
44980
|
+
seen.add(step.id);
|
|
44981
|
+
}
|
|
44982
|
+
if (out.startsAt && !seen.has(out.startsAt)) {
|
|
44983
|
+
throw new Error(`${label}.startsAt "${out.startsAt}" does not match any step id`);
|
|
44984
|
+
}
|
|
44985
|
+
return out;
|
|
44986
|
+
}
|
|
44987
|
+
function validateJourneys(journeys, label) {
|
|
44988
|
+
if (!journeys || typeof journeys !== "object" || Array.isArray(journeys)) {
|
|
44989
|
+
throw new Error(`${label} must be an object mapping journey ids to journey configs`);
|
|
44990
|
+
}
|
|
44991
|
+
const out = {};
|
|
44992
|
+
for (const [id, journey] of Object.entries(journeys)) {
|
|
44993
|
+
const normalizedId = requireString(id, `${label} journey id`);
|
|
44994
|
+
out[normalizedId] = validateJourney(journey, `${label}.${normalizedId}`);
|
|
44995
|
+
}
|
|
44996
|
+
return out;
|
|
44997
|
+
}
|
|
44718
44998
|
function validateLight(light, label) {
|
|
44719
44999
|
if (!light || typeof light !== "object") throw new Error(`${label} must be an object`);
|
|
44720
45000
|
if (!VALID_LIGHT_TYPES.has(light.type)) {
|
|
@@ -44722,15 +45002,15 @@ function validateLight(light, label) {
|
|
|
44722
45002
|
}
|
|
44723
45003
|
const out = { type: light.type };
|
|
44724
45004
|
if (light.color !== void 0) out.color = requireColor(light.color, `${label}.color`);
|
|
44725
|
-
if (light.intensity !== void 0) out.intensity =
|
|
44726
|
-
if (light.position !== void 0) out.position =
|
|
44727
|
-
if (light.target !== void 0) out.target =
|
|
45005
|
+
if (light.intensity !== void 0) out.intensity = requireFinite3(light.intensity, `${label}.intensity`);
|
|
45006
|
+
if (light.position !== void 0) out.position = requireVec32(light.position, `${label}.position`);
|
|
45007
|
+
if (light.target !== void 0) out.target = requireVec32(light.target, `${label}.target`);
|
|
44728
45008
|
if (light.groundColor !== void 0) out.groundColor = requireColor(light.groundColor, `${label}.groundColor`);
|
|
44729
45009
|
if (light.skyColor !== void 0) out.skyColor = requireColor(light.skyColor, `${label}.skyColor`);
|
|
44730
|
-
if (light.angle !== void 0) out.angle =
|
|
44731
|
-
if (light.penumbra !== void 0) out.penumbra =
|
|
44732
|
-
if (light.decay !== void 0) out.decay =
|
|
44733
|
-
if (light.distance !== void 0) out.distance =
|
|
45010
|
+
if (light.angle !== void 0) out.angle = requireFinite3(light.angle, `${label}.angle`);
|
|
45011
|
+
if (light.penumbra !== void 0) out.penumbra = requireFinite3(light.penumbra, `${label}.penumbra`);
|
|
45012
|
+
if (light.decay !== void 0) out.decay = requireFinite3(light.decay, `${label}.decay`);
|
|
45013
|
+
if (light.distance !== void 0) out.distance = requireFinite3(light.distance, `${label}.distance`);
|
|
44734
45014
|
if (light.castShadow !== void 0) {
|
|
44735
45015
|
if (typeof light.castShadow !== "boolean") throw new Error(`${label}.castShadow must be a boolean`);
|
|
44736
45016
|
out.castShadow = light.castShadow;
|
|
@@ -44745,7 +45025,7 @@ function validateEnvironment(env, label) {
|
|
|
44745
45025
|
}
|
|
44746
45026
|
out.preset = env.preset;
|
|
44747
45027
|
}
|
|
44748
|
-
if (env.intensity !== void 0) out.intensity =
|
|
45028
|
+
if (env.intensity !== void 0) out.intensity = requireFinite3(env.intensity, `${label}.intensity`);
|
|
44749
45029
|
if (env.background !== void 0) {
|
|
44750
45030
|
if (typeof env.background !== "boolean") throw new Error(`${label}.background must be a boolean`);
|
|
44751
45031
|
out.background = env.background;
|
|
@@ -44755,9 +45035,9 @@ function validateEnvironment(env, label) {
|
|
|
44755
45035
|
function validateFog(fog, label) {
|
|
44756
45036
|
const out = {};
|
|
44757
45037
|
if (fog.color !== void 0) out.color = requireColor(fog.color, `${label}.color`);
|
|
44758
|
-
if (fog.near !== void 0) out.near =
|
|
44759
|
-
if (fog.far !== void 0) out.far =
|
|
44760
|
-
if (fog.density !== void 0) out.density =
|
|
45038
|
+
if (fog.near !== void 0) out.near = requireFinite3(fog.near, `${label}.near`);
|
|
45039
|
+
if (fog.far !== void 0) out.far = requireFinite3(fog.far, `${label}.far`);
|
|
45040
|
+
if (fog.density !== void 0) out.density = requireFinite3(fog.density, `${label}.density`);
|
|
44761
45041
|
return out;
|
|
44762
45042
|
}
|
|
44763
45043
|
function validatePostProcessing(pp, label) {
|
|
@@ -44765,23 +45045,23 @@ function validatePostProcessing(pp, label) {
|
|
|
44765
45045
|
if (pp.bloom !== void 0) {
|
|
44766
45046
|
if (!pp.bloom || typeof pp.bloom !== "object") throw new Error(`${label}.bloom must be an object`);
|
|
44767
45047
|
out.bloom = {};
|
|
44768
|
-
if (pp.bloom.intensity !== void 0) out.bloom.intensity =
|
|
44769
|
-
if (pp.bloom.threshold !== void 0) out.bloom.threshold =
|
|
44770
|
-
if (pp.bloom.radius !== void 0) out.bloom.radius =
|
|
45048
|
+
if (pp.bloom.intensity !== void 0) out.bloom.intensity = requireFinite3(pp.bloom.intensity, `${label}.bloom.intensity`);
|
|
45049
|
+
if (pp.bloom.threshold !== void 0) out.bloom.threshold = requireFinite3(pp.bloom.threshold, `${label}.bloom.threshold`);
|
|
45050
|
+
if (pp.bloom.radius !== void 0) out.bloom.radius = requireFinite3(pp.bloom.radius, `${label}.bloom.radius`);
|
|
44771
45051
|
}
|
|
44772
45052
|
if (pp.vignette !== void 0) {
|
|
44773
45053
|
if (!pp.vignette || typeof pp.vignette !== "object") throw new Error(`${label}.vignette must be an object`);
|
|
44774
45054
|
out.vignette = {};
|
|
44775
|
-
if (pp.vignette.darkness !== void 0) out.vignette.darkness =
|
|
44776
|
-
if (pp.vignette.offset !== void 0) out.vignette.offset =
|
|
45055
|
+
if (pp.vignette.darkness !== void 0) out.vignette.darkness = requireFinite3(pp.vignette.darkness, `${label}.vignette.darkness`);
|
|
45056
|
+
if (pp.vignette.offset !== void 0) out.vignette.offset = requireFinite3(pp.vignette.offset, `${label}.vignette.offset`);
|
|
44777
45057
|
}
|
|
44778
45058
|
if (pp.grain !== void 0) {
|
|
44779
45059
|
if (!pp.grain || typeof pp.grain !== "object") throw new Error(`${label}.grain must be an object`);
|
|
44780
45060
|
out.grain = {};
|
|
44781
|
-
if (pp.grain.intensity !== void 0) out.grain.intensity =
|
|
45061
|
+
if (pp.grain.intensity !== void 0) out.grain.intensity = requireFinite3(pp.grain.intensity, `${label}.grain.intensity`);
|
|
44782
45062
|
}
|
|
44783
45063
|
if (pp.toneMappingExposure !== void 0) {
|
|
44784
|
-
out.toneMappingExposure =
|
|
45064
|
+
out.toneMappingExposure = requireFinite3(pp.toneMappingExposure, `${label}.toneMappingExposure`);
|
|
44785
45065
|
}
|
|
44786
45066
|
return out;
|
|
44787
45067
|
}
|
|
@@ -44792,7 +45072,7 @@ function validateGround(ground, label) {
|
|
|
44792
45072
|
out.visible = ground.visible;
|
|
44793
45073
|
}
|
|
44794
45074
|
if (ground.color !== void 0) out.color = requireColor(ground.color, `${label}.color`);
|
|
44795
|
-
if (ground.offset !== void 0) out.offset =
|
|
45075
|
+
if (ground.offset !== void 0) out.offset = requireFinite3(ground.offset, `${label}.offset`);
|
|
44796
45076
|
if (ground.receiveShadow !== void 0) {
|
|
44797
45077
|
if (typeof ground.receiveShadow !== "boolean") throw new Error(`${label}.receiveShadow must be a boolean`);
|
|
44798
45078
|
out.receiveShadow = ground.receiveShadow;
|
|
@@ -44802,31 +45082,31 @@ function validateGround(ground, label) {
|
|
|
44802
45082
|
function validateCapture(cap, label) {
|
|
44803
45083
|
const out = {};
|
|
44804
45084
|
if (cap.framesPerTurn !== void 0) {
|
|
44805
|
-
out.framesPerTurn =
|
|
45085
|
+
out.framesPerTurn = requireFinite3(cap.framesPerTurn, `${label}.framesPerTurn`);
|
|
44806
45086
|
if (out.framesPerTurn < 12 || out.framesPerTurn > 720) {
|
|
44807
45087
|
throw new Error(`${label}.framesPerTurn must be between 12 and 720`);
|
|
44808
45088
|
}
|
|
44809
45089
|
}
|
|
44810
45090
|
if (cap.holdFrames !== void 0) {
|
|
44811
|
-
out.holdFrames =
|
|
45091
|
+
out.holdFrames = requireFinite3(cap.holdFrames, `${label}.holdFrames`);
|
|
44812
45092
|
if (out.holdFrames < 0 || out.holdFrames > 300) {
|
|
44813
45093
|
throw new Error(`${label}.holdFrames must be between 0 and 300`);
|
|
44814
45094
|
}
|
|
44815
45095
|
}
|
|
44816
45096
|
if (cap.pitchDeg !== void 0) {
|
|
44817
|
-
out.pitchDeg =
|
|
45097
|
+
out.pitchDeg = requireFinite3(cap.pitchDeg, `${label}.pitchDeg`);
|
|
44818
45098
|
if (out.pitchDeg < -80 || out.pitchDeg > 80) {
|
|
44819
45099
|
throw new Error(`${label}.pitchDeg must be between -80 and 80`);
|
|
44820
45100
|
}
|
|
44821
45101
|
}
|
|
44822
45102
|
if (cap.fps !== void 0) {
|
|
44823
|
-
out.fps =
|
|
45103
|
+
out.fps = requireFinite3(cap.fps, `${label}.fps`);
|
|
44824
45104
|
if (out.fps < 1 || out.fps > 60) {
|
|
44825
45105
|
throw new Error(`${label}.fps must be between 1 and 60`);
|
|
44826
45106
|
}
|
|
44827
45107
|
}
|
|
44828
45108
|
if (cap.size !== void 0) {
|
|
44829
|
-
out.size =
|
|
45109
|
+
out.size = requireFinite3(cap.size, `${label}.size`);
|
|
44830
45110
|
if (out.size < 1) {
|
|
44831
45111
|
throw new Error(`${label}.size must be positive`);
|
|
44832
45112
|
}
|
|
@@ -44847,18 +45127,29 @@ function validateBackground(bg, label) {
|
|
|
44847
45127
|
}
|
|
44848
45128
|
throw new Error(`${label} must be a color string or { top, bottom } gradient`);
|
|
44849
45129
|
}
|
|
44850
|
-
var
|
|
45130
|
+
var _collected6 = null;
|
|
44851
45131
|
function resetScene() {
|
|
44852
|
-
|
|
45132
|
+
_collected6 = null;
|
|
44853
45133
|
}
|
|
44854
45134
|
function getCollectedScene() {
|
|
44855
|
-
return
|
|
45135
|
+
return _collected6 ? { ..._collected6 } : null;
|
|
44856
45136
|
}
|
|
44857
45137
|
function scene(options) {
|
|
44858
45138
|
if (!options || typeof options !== "object") {
|
|
44859
45139
|
throw new Error("scene(options) expects an options object");
|
|
44860
45140
|
}
|
|
44861
|
-
const current =
|
|
45141
|
+
const current = _collected6 ? { ..._collected6 } : {
|
|
45142
|
+
background: null,
|
|
45143
|
+
camera: null,
|
|
45144
|
+
views: null,
|
|
45145
|
+
journeys: null,
|
|
45146
|
+
lights: null,
|
|
45147
|
+
environment: null,
|
|
45148
|
+
fog: null,
|
|
45149
|
+
postProcessing: null,
|
|
45150
|
+
ground: null,
|
|
45151
|
+
capture: null
|
|
45152
|
+
};
|
|
44862
45153
|
if (options.background !== void 0) {
|
|
44863
45154
|
current.background = validateBackground(options.background, "scene.background");
|
|
44864
45155
|
}
|
|
@@ -44869,6 +45160,14 @@ function scene(options) {
|
|
|
44869
45160
|
const validated = validateCamera(options.camera, "scene.camera");
|
|
44870
45161
|
current.camera = current.camera ? { ...current.camera, ...validated } : validated;
|
|
44871
45162
|
}
|
|
45163
|
+
if (options.views !== void 0) {
|
|
45164
|
+
const validated = validateViews(options.views, "scene.views");
|
|
45165
|
+
current.views = current.views ? { ...current.views, ...validated } : validated;
|
|
45166
|
+
}
|
|
45167
|
+
if (options.journeys !== void 0) {
|
|
45168
|
+
const validated = validateJourneys(options.journeys, "scene.journeys");
|
|
45169
|
+
current.journeys = current.journeys ? { ...current.journeys, ...validated } : validated;
|
|
45170
|
+
}
|
|
44872
45171
|
if (options.lights !== void 0) {
|
|
44873
45172
|
if (!Array.isArray(options.lights)) {
|
|
44874
45173
|
throw new Error("scene.lights must be an array");
|
|
@@ -44907,7 +45206,74 @@ function scene(options) {
|
|
|
44907
45206
|
const validated = validateCapture(options.capture, "scene.capture");
|
|
44908
45207
|
current.capture = current.capture ? { ...current.capture, ...validated } : validated;
|
|
44909
45208
|
}
|
|
44910
|
-
|
|
45209
|
+
_collected6 = current;
|
|
45210
|
+
}
|
|
45211
|
+
var targetPath = (target) => {
|
|
45212
|
+
const path4 = target.treePath?.filter((entry) => entry.trim());
|
|
45213
|
+
return path4 && path4.length > 0 ? path4.join("/") : target.name;
|
|
45214
|
+
};
|
|
45215
|
+
var hasErrorDiagnostic = (diagnostics) => diagnostics.some((diagnostic) => diagnostic.level === "error");
|
|
45216
|
+
function resolveJourneyFocus(focus, targets) {
|
|
45217
|
+
return targets.filter((target) => target.name === focus || targetPath(target) === focus);
|
|
45218
|
+
}
|
|
45219
|
+
function formatAvailableTargets(targets) {
|
|
45220
|
+
return targets.map((target) => targetPath(target)).filter((value, index, values) => values.indexOf(value) === index).sort().slice(0, 8);
|
|
45221
|
+
}
|
|
45222
|
+
function resolveSceneJourneyTargets(config, targets) {
|
|
45223
|
+
if (!config?.journeys) return config;
|
|
45224
|
+
const journeys = {};
|
|
45225
|
+
for (const [journeyId, journey] of Object.entries(config.journeys)) {
|
|
45226
|
+
const journeyDiagnostics = [...journey.diagnostics ?? []];
|
|
45227
|
+
const steps = journey.steps.map((step) => {
|
|
45228
|
+
const stepDiagnostics = [...step.diagnostics ?? []];
|
|
45229
|
+
let resolvedFocusId = null;
|
|
45230
|
+
let resolvedFocusPath = null;
|
|
45231
|
+
if (step.focus) {
|
|
45232
|
+
const matches = resolveJourneyFocus(step.focus, targets);
|
|
45233
|
+
if (matches.length === 1) {
|
|
45234
|
+
resolvedFocusId = matches[0].id;
|
|
45235
|
+
resolvedFocusPath = targetPath(matches[0]);
|
|
45236
|
+
} else if (matches.length === 0) {
|
|
45237
|
+
stepDiagnostics.push({
|
|
45238
|
+
level: "error",
|
|
45239
|
+
stepId: step.id,
|
|
45240
|
+
message: `focus "${step.focus}" did not match any returned object by name or tree path.`,
|
|
45241
|
+
suggestions: formatAvailableTargets(targets)
|
|
45242
|
+
});
|
|
45243
|
+
} else {
|
|
45244
|
+
stepDiagnostics.push({
|
|
45245
|
+
level: "error",
|
|
45246
|
+
stepId: step.id,
|
|
45247
|
+
message: `focus "${step.focus}" matched ${matches.length} objects. Use a slash-separated tree path.`,
|
|
45248
|
+
suggestions: matches.map((match) => targetPath(match))
|
|
45249
|
+
});
|
|
45250
|
+
}
|
|
45251
|
+
} else if (!step.camera) {
|
|
45252
|
+
stepDiagnostics.push({
|
|
45253
|
+
level: "warning",
|
|
45254
|
+
stepId: step.id,
|
|
45255
|
+
message: "step has no focus or explicit camera, so the viewer can show the caption but cannot move the camera."
|
|
45256
|
+
});
|
|
45257
|
+
}
|
|
45258
|
+
journeyDiagnostics.push(...stepDiagnostics);
|
|
45259
|
+
return {
|
|
45260
|
+
...step,
|
|
45261
|
+
resolvedFocusId,
|
|
45262
|
+
resolvedFocusPath,
|
|
45263
|
+
diagnostics: stepDiagnostics.length > 0 ? stepDiagnostics : void 0
|
|
45264
|
+
};
|
|
45265
|
+
});
|
|
45266
|
+
journeys[journeyId] = {
|
|
45267
|
+
...journey,
|
|
45268
|
+
steps,
|
|
45269
|
+
valid: !hasErrorDiagnostic(journeyDiagnostics),
|
|
45270
|
+
diagnostics: journeyDiagnostics.length > 0 ? journeyDiagnostics : void 0
|
|
45271
|
+
};
|
|
45272
|
+
}
|
|
45273
|
+
return {
|
|
45274
|
+
...config,
|
|
45275
|
+
journeys
|
|
45276
|
+
};
|
|
44911
45277
|
}
|
|
44912
45278
|
|
|
44913
45279
|
// src/forge/scene/viewConfig.ts
|
|
@@ -45065,25 +45431,25 @@ var DEFAULT_JOINT_OVERLAY_VIEW_CONFIG = {
|
|
|
45065
45431
|
var DEFAULT_VIEW_CONFIG = {
|
|
45066
45432
|
jointOverlay: cloneJointOverlay(DEFAULT_JOINT_OVERLAY_VIEW_CONFIG)
|
|
45067
45433
|
};
|
|
45068
|
-
var
|
|
45434
|
+
var _collected7 = null;
|
|
45069
45435
|
function resetViewConfig() {
|
|
45070
|
-
|
|
45436
|
+
_collected7 = null;
|
|
45071
45437
|
}
|
|
45072
45438
|
function getCollectedViewConfig() {
|
|
45073
|
-
return
|
|
45439
|
+
return _collected7 ? cloneViewConfig(_collected7) : null;
|
|
45074
45440
|
}
|
|
45075
45441
|
function viewConfig(options = {}) {
|
|
45076
45442
|
if (!options || typeof options !== "object") {
|
|
45077
45443
|
throw new Error("viewConfig(options) expects an options object");
|
|
45078
45444
|
}
|
|
45079
|
-
const next =
|
|
45445
|
+
const next = _collected7 ? cloneViewConfig(_collected7) : cloneViewConfig(DEFAULT_VIEW_CONFIG);
|
|
45080
45446
|
if (options.jointOverlay !== void 0) {
|
|
45081
45447
|
if (!options.jointOverlay || typeof options.jointOverlay !== "object") {
|
|
45082
45448
|
throw new Error("viewConfig.jointOverlay must be an object");
|
|
45083
45449
|
}
|
|
45084
45450
|
next.jointOverlay = patchJointOverlay(next.jointOverlay, options.jointOverlay, "viewConfig.jointOverlay");
|
|
45085
45451
|
}
|
|
45086
|
-
|
|
45452
|
+
_collected7 = next;
|
|
45087
45453
|
}
|
|
45088
45454
|
|
|
45089
45455
|
// src/forge/intent/compilerDiagnostics.ts
|
|
@@ -46239,7 +46605,7 @@ function hermiteTransitionG2(a, b) {
|
|
|
46239
46605
|
}
|
|
46240
46606
|
|
|
46241
46607
|
// src/forge/sketch/nurbsCurve.ts
|
|
46242
|
-
function
|
|
46608
|
+
function requireFinite4(v, label) {
|
|
46243
46609
|
if (!Number.isFinite(v)) throw new Error(`nurbs3d: ${label} must be finite, got ${v}`);
|
|
46244
46610
|
}
|
|
46245
46611
|
var NurbsCurve3D = class {
|
|
@@ -46254,14 +46620,14 @@ var NurbsCurve3D = class {
|
|
|
46254
46620
|
if (degree < 1) throw new Error("nurbs3d: degree must be \u2265 1");
|
|
46255
46621
|
if (n < degree + 1) throw new Error(`nurbs3d: need at least ${degree + 1} control points for degree ${degree}, got ${n}`);
|
|
46256
46622
|
for (let i = 0; i < n; i++) {
|
|
46257
|
-
|
|
46258
|
-
|
|
46259
|
-
|
|
46623
|
+
requireFinite4(points[i][0], `controlPoints[${i}][0]`);
|
|
46624
|
+
requireFinite4(points[i][1], `controlPoints[${i}][1]`);
|
|
46625
|
+
requireFinite4(points[i][2], `controlPoints[${i}][2]`);
|
|
46260
46626
|
}
|
|
46261
46627
|
const weights = options.weights ?? new Array(n).fill(1);
|
|
46262
46628
|
if (weights.length !== n) throw new Error(`nurbs3d: weights.length (${weights.length}) must equal controlPoints.length (${n})`);
|
|
46263
46629
|
for (let i = 0; i < n; i++) {
|
|
46264
|
-
|
|
46630
|
+
requireFinite4(weights[i], `weights[${i}]`);
|
|
46265
46631
|
if (weights[i] <= 0) throw new Error(`nurbs3d: weights[${i}] must be > 0, got ${weights[i]}`);
|
|
46266
46632
|
}
|
|
46267
46633
|
const expectedKnotLength = n + degree + 1;
|
|
@@ -46270,7 +46636,7 @@ var NurbsCurve3D = class {
|
|
|
46270
46636
|
throw new Error(`nurbs3d: knots.length (${knots.length}) must be controlPoints.length + degree + 1 (${expectedKnotLength})`);
|
|
46271
46637
|
}
|
|
46272
46638
|
for (let i = 0; i < knots.length; i++) {
|
|
46273
|
-
|
|
46639
|
+
requireFinite4(knots[i], `knots[${i}]`);
|
|
46274
46640
|
if (i > 0 && knots[i] < knots[i - 1]) {
|
|
46275
46641
|
throw new Error(`nurbs3d: knot vector must be non-decreasing, but knots[${i - 1}]=${knots[i - 1]} > knots[${i}]=${knots[i]}`);
|
|
46276
46642
|
}
|
|
@@ -46831,9 +47197,15 @@ function cross5(a, b) {
|
|
|
46831
47197
|
function add4(a, b) {
|
|
46832
47198
|
return [a[0] + b[0], a[1] + b[1], a[2] + b[2]];
|
|
46833
47199
|
}
|
|
47200
|
+
function sub6(a, b) {
|
|
47201
|
+
return [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
|
|
47202
|
+
}
|
|
46834
47203
|
function scale5(v, s) {
|
|
46835
47204
|
return [v[0] * s, v[1] * s, v[2] * s];
|
|
46836
47205
|
}
|
|
47206
|
+
function dot6(a, b) {
|
|
47207
|
+
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
|
47208
|
+
}
|
|
46837
47209
|
function lerp2(a, b, t) {
|
|
46838
47210
|
return a + (b - a) * t;
|
|
46839
47211
|
}
|
|
@@ -46866,6 +47238,65 @@ function sideVectors(axis) {
|
|
|
46866
47238
|
function cloneQuery(query) {
|
|
46867
47239
|
return { side: query.side, u: query.u, v: query.v, offset: query.offset };
|
|
46868
47240
|
}
|
|
47241
|
+
function normalizedSide(side) {
|
|
47242
|
+
return side === "back" ? "rear" : side;
|
|
47243
|
+
}
|
|
47244
|
+
function profileExponent(station) {
|
|
47245
|
+
if (station.profile.kind === "superEllipse") return station.profile.exponent ?? 3.2;
|
|
47246
|
+
if (station.profile.kind === "roundedRect") return 4.5;
|
|
47247
|
+
return 2;
|
|
47248
|
+
}
|
|
47249
|
+
function superEllipsePoint(rx, ry, exponent, angle) {
|
|
47250
|
+
const cos4 = Math.cos(angle);
|
|
47251
|
+
const sin4 = Math.sin(angle);
|
|
47252
|
+
const x = rx * Math.sign(cos4) * Math.abs(cos4) ** (2 / exponent);
|
|
47253
|
+
const y = ry * Math.sign(sin4) * Math.abs(sin4) ** (2 / exponent);
|
|
47254
|
+
const nx = Math.sign(x) * Math.abs(x / Math.max(rx, EPS7)) ** Math.max(exponent - 1, 1e-3);
|
|
47255
|
+
const ny = Math.sign(y) * Math.abs(y / Math.max(ry, EPS7)) ** Math.max(exponent - 1, 1e-3);
|
|
47256
|
+
const nLen = Math.hypot(nx, ny);
|
|
47257
|
+
return {
|
|
47258
|
+
point: [x, y],
|
|
47259
|
+
normal: nLen < EPS7 ? [Math.sign(cos4), Math.sign(sin4)] : [nx / nLen, ny / nLen]
|
|
47260
|
+
};
|
|
47261
|
+
}
|
|
47262
|
+
function angleForSide(side, u) {
|
|
47263
|
+
const t = clamp6(u, 0, 1);
|
|
47264
|
+
if (side === "right") return -Math.PI / 2 + t * Math.PI;
|
|
47265
|
+
if (side === "left") return Math.PI / 2 + t * Math.PI;
|
|
47266
|
+
if (side === "top") return Math.PI - t * Math.PI;
|
|
47267
|
+
if (side === "bottom") return Math.PI + t * Math.PI;
|
|
47268
|
+
return null;
|
|
47269
|
+
}
|
|
47270
|
+
function sideSpan(side, width, depth) {
|
|
47271
|
+
if (side === "left" || side === "right") return Math.max(depth, EPS7);
|
|
47272
|
+
if (side === "top" || side === "bottom") return Math.max(width, EPS7);
|
|
47273
|
+
return Math.max(width, depth, EPS7);
|
|
47274
|
+
}
|
|
47275
|
+
function interpolateQuery(a, b, t) {
|
|
47276
|
+
const sideA = normalizedSide(a.side);
|
|
47277
|
+
const sideB = normalizedSide(b.side);
|
|
47278
|
+
if (sideA !== sideB) {
|
|
47279
|
+
throw new Error(
|
|
47280
|
+
`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.`
|
|
47281
|
+
);
|
|
47282
|
+
}
|
|
47283
|
+
return {
|
|
47284
|
+
side: sideA,
|
|
47285
|
+
u: lerp2(a.u ?? 0.5, b.u ?? 0.5, t),
|
|
47286
|
+
v: lerp2(a.v ?? 0.5, b.v ?? 0.5, t),
|
|
47287
|
+
offset: lerp2(a.offset ?? 0, b.offset ?? 0, t)
|
|
47288
|
+
};
|
|
47289
|
+
}
|
|
47290
|
+
function resolvePathQueries(points) {
|
|
47291
|
+
return points.map((point2) => point2 instanceof ProductSurfaceRef ? point2.querySpec() : cloneQuery(point2));
|
|
47292
|
+
}
|
|
47293
|
+
function orientGridToNormal(grid, desiredNormal) {
|
|
47294
|
+
if (grid.length < 2 || grid[0].length < 2) return grid;
|
|
47295
|
+
const widthEdge = sub6(grid[grid.length - 1][0], grid[0][0]);
|
|
47296
|
+
const lengthEdge = sub6(grid[0][grid[0].length - 1], grid[0][0]);
|
|
47297
|
+
const actual = norm2(cross5(widthEdge, lengthEdge));
|
|
47298
|
+
return dot6(actual, desiredNormal) < 0 ? [...grid].reverse() : grid;
|
|
47299
|
+
}
|
|
46869
47300
|
function isStationBuilder(input) {
|
|
46870
47301
|
return typeof input.toSpec === "function";
|
|
46871
47302
|
}
|
|
@@ -46967,6 +47398,15 @@ var ProductSkin = class {
|
|
|
46967
47398
|
curveOnSurface(name, points) {
|
|
46968
47399
|
return points.map((point2, index) => new ProductSurfaceRef(this, { u: 0.5, v: 0.5, ...point2 }, `${name}/${index}`));
|
|
46969
47400
|
}
|
|
47401
|
+
/**
|
|
47402
|
+
* Create a fluent surface helper for refs and conformal features on one side of this skin.
|
|
47403
|
+
*
|
|
47404
|
+
* Use this when several refs or ribbons share the same skin side; side-local helpers keep
|
|
47405
|
+
* path points concise and make it harder to mix sides accidentally.
|
|
47406
|
+
*/
|
|
47407
|
+
surface(side) {
|
|
47408
|
+
return new ProductSurfaceBuilder(this, side);
|
|
47409
|
+
}
|
|
46970
47410
|
/** Interpolate center, width, and depth at a normalized v or absolute axis value. */
|
|
46971
47411
|
stationAt(vOrAxis) {
|
|
46972
47412
|
const axisValue = vOrAxis >= 0 && vOrAxis <= 1 ? lerp2(this.axisMin, this.axisMax, vOrAxis) : clamp6(vOrAxis, this.axisMin, this.axisMax);
|
|
@@ -46985,7 +47425,9 @@ var ProductSkin = class {
|
|
|
46985
47425
|
width: lerp2(a.profile.width, b.profile.width, t),
|
|
46986
47426
|
depth: lerp2(a.profile.depth, b.profile.depth, t),
|
|
46987
47427
|
dWidth: (b.profile.width - a.profile.width) / span,
|
|
46988
|
-
dDepth: (b.profile.depth - a.profile.depth) / span
|
|
47428
|
+
dDepth: (b.profile.depth - a.profile.depth) / span,
|
|
47429
|
+
exponent: lerp2(profileExponent(a), profileExponent(b), t),
|
|
47430
|
+
kind: a.profile.kind === b.profile.kind ? a.profile.kind : "custom"
|
|
46989
47431
|
};
|
|
46990
47432
|
}
|
|
46991
47433
|
const last = sorted[sorted.length - 1];
|
|
@@ -46995,12 +47437,14 @@ var ProductSkin = class {
|
|
|
46995
47437
|
width: last.profile.width,
|
|
46996
47438
|
depth: last.profile.depth,
|
|
46997
47439
|
dWidth: 0,
|
|
46998
|
-
dDepth: 0
|
|
47440
|
+
dDepth: 0,
|
|
47441
|
+
exponent: profileExponent(last),
|
|
47442
|
+
kind: last.profile.kind
|
|
46999
47443
|
};
|
|
47000
47444
|
}
|
|
47001
47445
|
/** Build a local surface frame from a side/u/v query. */
|
|
47002
47446
|
frame(query) {
|
|
47003
|
-
const side = query.side
|
|
47447
|
+
const side = normalizedSide(query.side);
|
|
47004
47448
|
const offset2 = query.offset ?? 0;
|
|
47005
47449
|
const basis = sideVectors(this.axis);
|
|
47006
47450
|
const isFrontCap = side === "front";
|
|
@@ -47024,11 +47468,31 @@ var ProductSkin = class {
|
|
|
47024
47468
|
}
|
|
47025
47469
|
const station = this.stationAt(query.v ?? 0.5);
|
|
47026
47470
|
const u = clamp6(query.u ?? 0.5, 0, 1) - 0.5;
|
|
47471
|
+
const sideAngle = angleForSide(side, query.u ?? 0.5);
|
|
47027
47472
|
let crossA = 0;
|
|
47028
47473
|
let crossB = 0;
|
|
47029
47474
|
let normal = [0, 0, 1];
|
|
47030
47475
|
let tangentU = basis.crossA;
|
|
47031
|
-
if (
|
|
47476
|
+
if (sideAngle != null) {
|
|
47477
|
+
const section = superEllipsePoint(station.width / 2, station.depth / 2, station.exponent, sideAngle);
|
|
47478
|
+
crossA = section.point[0];
|
|
47479
|
+
crossB = section.point[1];
|
|
47480
|
+
normal = norm2(add4(scale5(basis.crossA, section.normal[0]), scale5(basis.crossB, section.normal[1])));
|
|
47481
|
+
const delta = 2e-3;
|
|
47482
|
+
const prev = superEllipsePoint(
|
|
47483
|
+
station.width / 2,
|
|
47484
|
+
station.depth / 2,
|
|
47485
|
+
station.exponent,
|
|
47486
|
+
angleForSide(side, clamp6((query.u ?? 0.5) - delta, 0, 1)) ?? sideAngle
|
|
47487
|
+
).point;
|
|
47488
|
+
const next = superEllipsePoint(
|
|
47489
|
+
station.width / 2,
|
|
47490
|
+
station.depth / 2,
|
|
47491
|
+
station.exponent,
|
|
47492
|
+
angleForSide(side, clamp6((query.u ?? 0.5) + delta, 0, 1)) ?? sideAngle
|
|
47493
|
+
).point;
|
|
47494
|
+
tangentU = norm2(add4(scale5(basis.crossA, next[0] - prev[0]), scale5(basis.crossB, next[1] - prev[1])));
|
|
47495
|
+
} else if (side === "right") {
|
|
47032
47496
|
crossA = station.width / 2;
|
|
47033
47497
|
crossB = u * station.depth;
|
|
47034
47498
|
normal = basis.crossA;
|
|
@@ -47050,7 +47514,7 @@ var ProductSkin = class {
|
|
|
47050
47514
|
tangentU = basis.crossA;
|
|
47051
47515
|
}
|
|
47052
47516
|
normal = norm2(normal);
|
|
47053
|
-
tangentU = norm2(tangentU);
|
|
47517
|
+
tangentU = norm2(sub6(tangentU, scale5(normal, dot6(tangentU, normal))));
|
|
47054
47518
|
const tangentV = norm2(cross5(normal, tangentU));
|
|
47055
47519
|
const point2 = add4(add4(station.center, add4(scale5(basis.crossA, crossA), scale5(basis.crossB, crossB))), scale5(normal, offset2));
|
|
47056
47520
|
return {
|
|
@@ -47370,6 +47834,303 @@ var ProductHandleBuilder = class {
|
|
|
47370
47834
|
return new ProductHandleFeature(grip, upperPad, lowerPad);
|
|
47371
47835
|
}
|
|
47372
47836
|
};
|
|
47837
|
+
var ProductSurfaceBuilder = class {
|
|
47838
|
+
constructor(skin, side) {
|
|
47839
|
+
this.skin = skin;
|
|
47840
|
+
this.side = side;
|
|
47841
|
+
}
|
|
47842
|
+
/** Create a ref on this skin side. */
|
|
47843
|
+
ref(u = 0.5, v = 0.5, offset2) {
|
|
47844
|
+
return Product.ref(this.skin, {
|
|
47845
|
+
side: this.side,
|
|
47846
|
+
u,
|
|
47847
|
+
v,
|
|
47848
|
+
...offset2 != null ? { offset: offset2 } : {}
|
|
47849
|
+
});
|
|
47850
|
+
}
|
|
47851
|
+
/** Create a side/u/v query on this skin side. */
|
|
47852
|
+
uv(u = 0.5, v = 0.5, offset2) {
|
|
47853
|
+
return {
|
|
47854
|
+
side: this.side,
|
|
47855
|
+
u,
|
|
47856
|
+
v,
|
|
47857
|
+
...offset2 != null ? { offset: offset2 } : {}
|
|
47858
|
+
};
|
|
47859
|
+
}
|
|
47860
|
+
/**
|
|
47861
|
+
* Start a conformal ribbon on this skin side.
|
|
47862
|
+
*
|
|
47863
|
+
* Path points use side-local `u`/`v` coordinates; this builder supplies the side.
|
|
47864
|
+
* The returned ProductRibbonBuilder is already bound to the source skin and can be further
|
|
47865
|
+
* configured before build(). Use `widthSamples` >= 3 when the ribbon must visibly wrap over
|
|
47866
|
+
* curved product sections instead of behaving like a flat strip.
|
|
47867
|
+
*/
|
|
47868
|
+
ribbon(name, points, options = {}) {
|
|
47869
|
+
if (points.length < 2) throw new Error("Product.surface(...).ribbon(name, points) requires at least two path points");
|
|
47870
|
+
const path4 = points.map((point2) => ({
|
|
47871
|
+
side: this.side,
|
|
47872
|
+
u: point2.u ?? 0.5,
|
|
47873
|
+
v: point2.v ?? 0.5,
|
|
47874
|
+
...point2.offset != null ? { offset: point2.offset } : {}
|
|
47875
|
+
}));
|
|
47876
|
+
return new ProductRibbonBuilder(name).on(this.skin, path4, options);
|
|
47877
|
+
}
|
|
47878
|
+
};
|
|
47879
|
+
var ProductRibbonBuilder = class {
|
|
47880
|
+
constructor(name) {
|
|
47881
|
+
this.name = name;
|
|
47882
|
+
if (!name || !name.trim()) throw new Error("Product.ribbon(name) requires a non-empty name");
|
|
47883
|
+
}
|
|
47884
|
+
skinValue;
|
|
47885
|
+
queryPath = [];
|
|
47886
|
+
refPath = [];
|
|
47887
|
+
widthValue = 6;
|
|
47888
|
+
thicknessValue = 0.8;
|
|
47889
|
+
offsetValue = 0.25;
|
|
47890
|
+
samplesValue = 24;
|
|
47891
|
+
widthSamplesValue = 5;
|
|
47892
|
+
resolutionValue;
|
|
47893
|
+
materialValue;
|
|
47894
|
+
colorValue;
|
|
47895
|
+
lastDiagnosticsValue;
|
|
47896
|
+
/**
|
|
47897
|
+
* Follow a ProductSkin with side/u/v path queries or refs.
|
|
47898
|
+
*
|
|
47899
|
+
* This is the highest-fidelity mode because every interpolated sample is resolved through
|
|
47900
|
+
* ProductSkin.frame(), so the ribbon bends along the selected side as station width/depth changes.
|
|
47901
|
+
* All query path points must stay on one side; split side transitions into separate ribbons.
|
|
47902
|
+
*/
|
|
47903
|
+
on(skin, points, options = {}) {
|
|
47904
|
+
if (points.length < 2) throw new Error("Product.ribbon().on(skin, points) requires at least two path points");
|
|
47905
|
+
this.skinValue = skin;
|
|
47906
|
+
this.queryPath = resolvePathQueries(points);
|
|
47907
|
+
this.refPath = [];
|
|
47908
|
+
return this.applyOptions(options);
|
|
47909
|
+
}
|
|
47910
|
+
/**
|
|
47911
|
+
* Follow explicit surface refs.
|
|
47912
|
+
*
|
|
47913
|
+
* Useful for named refs or paths assembled elsewhere. The builder resolves each ref frame and
|
|
47914
|
+
* interpolates between those frames; use on(skin, points) when you need full skin-side sampling
|
|
47915
|
+
* between sparse control points.
|
|
47916
|
+
*/
|
|
47917
|
+
fromRefs(points, options = {}) {
|
|
47918
|
+
if (points.length < 2) throw new Error("Product.ribbon().fromRefs(points) requires at least two refs");
|
|
47919
|
+
this.skinValue = void 0;
|
|
47920
|
+
this.queryPath = [];
|
|
47921
|
+
this.refPath = [...points];
|
|
47922
|
+
return this.applyOptions(options);
|
|
47923
|
+
}
|
|
47924
|
+
/** Set ribbon width in millimeters. */
|
|
47925
|
+
width(width) {
|
|
47926
|
+
if (!Number.isFinite(width) || width <= 0) throw new Error("Product.ribbon().width(width) requires a positive finite number");
|
|
47927
|
+
this.widthValue = width;
|
|
47928
|
+
return this;
|
|
47929
|
+
}
|
|
47930
|
+
/** Set solid thickness outward from the source surface in millimeters. */
|
|
47931
|
+
thickness(thickness) {
|
|
47932
|
+
if (!Number.isFinite(thickness) || thickness <= 0)
|
|
47933
|
+
throw new Error("Product.ribbon().thickness(thickness) requires a positive finite number");
|
|
47934
|
+
this.thicknessValue = thickness;
|
|
47935
|
+
return this;
|
|
47936
|
+
}
|
|
47937
|
+
/** Set positive clearance between the source surface and the ribbon's inner face. */
|
|
47938
|
+
offset(offset2) {
|
|
47939
|
+
if (!Number.isFinite(offset2)) throw new Error("Product.ribbon().offset(offset) requires a finite number");
|
|
47940
|
+
this.offsetValue = offset2;
|
|
47941
|
+
return this;
|
|
47942
|
+
}
|
|
47943
|
+
/** Set samples along the path. */
|
|
47944
|
+
samples(samples) {
|
|
47945
|
+
if (!Number.isFinite(samples) || samples < 2) throw new Error("Product.ribbon().samples(samples) requires a value >= 2");
|
|
47946
|
+
this.samplesValue = Math.round(samples);
|
|
47947
|
+
return this;
|
|
47948
|
+
}
|
|
47949
|
+
/** Set samples across the width. Use 3+ to bend over curved cross-sections. */
|
|
47950
|
+
widthSamples(samples) {
|
|
47951
|
+
if (!Number.isFinite(samples) || samples < 2) throw new Error("Product.ribbon().widthSamples(samples) requires a value >= 2");
|
|
47952
|
+
this.widthSamplesValue = Math.round(samples);
|
|
47953
|
+
return this;
|
|
47954
|
+
}
|
|
47955
|
+
/** Set NURBS tessellation resolution. */
|
|
47956
|
+
resolution(resolution) {
|
|
47957
|
+
if (!Number.isFinite(resolution) || resolution < 2) throw new Error("Product.ribbon().resolution(resolution) requires a value >= 2");
|
|
47958
|
+
this.resolutionValue = Math.round(resolution);
|
|
47959
|
+
return this;
|
|
47960
|
+
}
|
|
47961
|
+
/** Apply a product material preset. */
|
|
47962
|
+
material(material) {
|
|
47963
|
+
this.materialValue = material;
|
|
47964
|
+
return this;
|
|
47965
|
+
}
|
|
47966
|
+
/** Apply a simple color override. */
|
|
47967
|
+
color(color) {
|
|
47968
|
+
this.colorValue = color;
|
|
47969
|
+
return this;
|
|
47970
|
+
}
|
|
47971
|
+
/** Build a conformal ribbon as a thin NURBS surface solid. */
|
|
47972
|
+
build(options = {}) {
|
|
47973
|
+
return this.buildWithDiagnostics(options).shape;
|
|
47974
|
+
}
|
|
47975
|
+
/**
|
|
47976
|
+
* Build a conformal ribbon and return surface-feature diagnostics.
|
|
47977
|
+
*
|
|
47978
|
+
* Use this while validating API usage or model fidelity; diagnostics report sampling counts,
|
|
47979
|
+
* side-span clamping, lowering mode, and warnings that should be visible in reviews.
|
|
47980
|
+
*/
|
|
47981
|
+
buildWithDiagnostics(options = {}) {
|
|
47982
|
+
this.applyOptions(options);
|
|
47983
|
+
const gridResult = this.skinValue ? this.buildSkinGrid(this.skinValue, this.queryPath) : this.buildRefGrid(this.refPath);
|
|
47984
|
+
const desiredNormal = this.centerDesiredNormal();
|
|
47985
|
+
let ribbon = nurbsSurface(orientGridToNormal(gridResult.grid, desiredNormal), {
|
|
47986
|
+
degreeU: Math.min(3, this.widthSamplesValue - 1),
|
|
47987
|
+
degreeV: Math.min(3, this.samplesValue - 1),
|
|
47988
|
+
thickness: this.thicknessValue,
|
|
47989
|
+
resolution: this.resolutionValue ?? Math.max(this.samplesValue, this.widthSamplesValue, 12),
|
|
47990
|
+
approximate: true
|
|
47991
|
+
}).as(this.name);
|
|
47992
|
+
if (this.colorValue) ribbon = ribbon.color(this.colorValue);
|
|
47993
|
+
const shape = applyMaterial(ribbon, this.materialValue);
|
|
47994
|
+
this.lastDiagnosticsValue = gridResult.diagnostics;
|
|
47995
|
+
return { shape, diagnostics: this.cloneDiagnostics(gridResult.diagnostics) };
|
|
47996
|
+
}
|
|
47997
|
+
/** Return diagnostics from the most recent build, if this builder has been built. */
|
|
47998
|
+
diagnostics() {
|
|
47999
|
+
return this.lastDiagnosticsValue ? this.cloneDiagnostics(this.lastDiagnosticsValue) : void 0;
|
|
48000
|
+
}
|
|
48001
|
+
applyOptions(options) {
|
|
48002
|
+
if (options.width != null) this.width(options.width);
|
|
48003
|
+
if (options.thickness != null) this.thickness(options.thickness);
|
|
48004
|
+
if (options.offset != null) this.offset(options.offset);
|
|
48005
|
+
if (options.samples != null) this.samples(options.samples);
|
|
48006
|
+
if (options.widthSamples != null) this.widthSamples(options.widthSamples);
|
|
48007
|
+
if (options.resolution != null) this.resolution(options.resolution);
|
|
48008
|
+
if (options.material) this.material(options.material);
|
|
48009
|
+
if (options.color) this.color(options.color);
|
|
48010
|
+
return this;
|
|
48011
|
+
}
|
|
48012
|
+
centerDesiredNormal() {
|
|
48013
|
+
if (this.skinValue && this.queryPath.length > 0) {
|
|
48014
|
+
const mid = this.samplePathQuery(0.5);
|
|
48015
|
+
return this.skinValue.frame({ ...mid, offset: (mid.offset ?? 0) + this.offsetValue }).normal;
|
|
48016
|
+
}
|
|
48017
|
+
if (this.refPath.length > 0) return this.refPath[Math.floor(this.refPath.length / 2)].frame({ offset: this.offsetValue }).normal;
|
|
48018
|
+
return [0, 0, 1];
|
|
48019
|
+
}
|
|
48020
|
+
samplePathQuery(t) {
|
|
48021
|
+
if (this.queryPath.length < 2) throw new Error("Product.ribbon().on(...) must be called before .build()");
|
|
48022
|
+
const segmentCount = this.queryPath.length - 1;
|
|
48023
|
+
const scaled = clamp6(t, 0, 1) * segmentCount;
|
|
48024
|
+
const segment = Math.min(segmentCount - 1, Math.floor(scaled));
|
|
48025
|
+
const localT = scaled - segment;
|
|
48026
|
+
return interpolateQuery(this.queryPath[segment], this.queryPath[segment + 1], localT);
|
|
48027
|
+
}
|
|
48028
|
+
buildSkinGrid(skin, path4) {
|
|
48029
|
+
if (path4.length < 2) throw new Error("Product.ribbon().on(skin, points) must be called before .build()");
|
|
48030
|
+
const side = normalizedSide(path4[0].side);
|
|
48031
|
+
if (side === "front" || side === "rear") {
|
|
48032
|
+
throw new Error(
|
|
48033
|
+
"Product.ribbon().on(...) supports side ribbons on left/right/top/bottom surfaces. Use Product.panel() for front/rear caps."
|
|
48034
|
+
);
|
|
48035
|
+
}
|
|
48036
|
+
for (const point2 of path4) {
|
|
48037
|
+
if (normalizedSide(point2.side) !== side) {
|
|
48038
|
+
throw new Error("Product.ribbon().on(...) currently supports one side per ribbon. Split ribbons at side transitions.");
|
|
48039
|
+
}
|
|
48040
|
+
}
|
|
48041
|
+
const rows = Array.from({ length: this.widthSamplesValue }, () => []);
|
|
48042
|
+
let clampedUCount = 0;
|
|
48043
|
+
let maxUClampDistance = 0;
|
|
48044
|
+
for (let i = 0; i < this.samplesValue; i += 1) {
|
|
48045
|
+
const along = this.samplesValue === 1 ? 0 : i / (this.samplesValue - 1);
|
|
48046
|
+
const center = this.samplePathQuery(along);
|
|
48047
|
+
const station = skin.stationAt(center.v ?? 0.5);
|
|
48048
|
+
const span = sideSpan(side, station.width, station.depth);
|
|
48049
|
+
for (let j = 0; j < this.widthSamplesValue; j += 1) {
|
|
48050
|
+
const across = this.widthSamplesValue === 1 ? 0 : j / (this.widthSamplesValue - 1) - 0.5;
|
|
48051
|
+
const rawU = (center.u ?? 0.5) + across * this.widthValue / span;
|
|
48052
|
+
const u = clamp6(rawU, 0, 1);
|
|
48053
|
+
const clampDistance = Math.abs(rawU - u) * span;
|
|
48054
|
+
if (clampDistance > EPS7) {
|
|
48055
|
+
clampedUCount += 1;
|
|
48056
|
+
maxUClampDistance = Math.max(maxUClampDistance, clampDistance);
|
|
48057
|
+
}
|
|
48058
|
+
const query = {
|
|
48059
|
+
...center,
|
|
48060
|
+
side,
|
|
48061
|
+
u,
|
|
48062
|
+
offset: (center.offset ?? 0) + this.offsetValue + this.thicknessValue
|
|
48063
|
+
};
|
|
48064
|
+
rows[j].push(skin.frame(query).point);
|
|
48065
|
+
}
|
|
48066
|
+
}
|
|
48067
|
+
return {
|
|
48068
|
+
grid: rows,
|
|
48069
|
+
diagnostics: this.makeDiagnostics({
|
|
48070
|
+
skin: skin.name,
|
|
48071
|
+
side,
|
|
48072
|
+
pathPointCount: path4.length,
|
|
48073
|
+
clampedUCount,
|
|
48074
|
+
maxUClampDistance
|
|
48075
|
+
})
|
|
48076
|
+
};
|
|
48077
|
+
}
|
|
48078
|
+
buildRefGrid(refs) {
|
|
48079
|
+
if (refs.length < 2) throw new Error("Product.ribbon().fromRefs(points) must be called before .build()");
|
|
48080
|
+
const frames = refs.map((ref) => ref.frame({ offset: this.offsetValue + this.thicknessValue }));
|
|
48081
|
+
const rows = Array.from({ length: this.widthSamplesValue }, () => []);
|
|
48082
|
+
for (const frame of frames) {
|
|
48083
|
+
for (let j = 0; j < this.widthSamplesValue; j += 1) {
|
|
48084
|
+
const across = this.widthSamplesValue === 1 ? 0 : j / (this.widthSamplesValue - 1) - 0.5;
|
|
48085
|
+
rows[j].push(add4(frame.point, scale5(frame.tangentU, across * this.widthValue)));
|
|
48086
|
+
}
|
|
48087
|
+
}
|
|
48088
|
+
this.samplesValue = refs.length;
|
|
48089
|
+
return {
|
|
48090
|
+
grid: rows,
|
|
48091
|
+
diagnostics: this.makeDiagnostics({
|
|
48092
|
+
pathPointCount: refs.length,
|
|
48093
|
+
clampedUCount: 0,
|
|
48094
|
+
maxUClampDistance: 0
|
|
48095
|
+
})
|
|
48096
|
+
};
|
|
48097
|
+
}
|
|
48098
|
+
makeDiagnostics(input) {
|
|
48099
|
+
const resolution = this.resolutionValue ?? Math.max(this.samplesValue, this.widthSamplesValue, 12);
|
|
48100
|
+
const warnings = [];
|
|
48101
|
+
if (input.clampedUCount > 0) {
|
|
48102
|
+
warnings.push(
|
|
48103
|
+
`Ribbon '${this.name}' was clipped to the ${input.side ?? "surface"} UV bounds at ${input.clampedUCount} sampled point(s).`
|
|
48104
|
+
);
|
|
48105
|
+
}
|
|
48106
|
+
if (this.samplesValue < 8) warnings.push(`Ribbon '${this.name}' uses low along-path sampling; increase samples() for smoother bends.`);
|
|
48107
|
+
if (this.widthSamplesValue < 3)
|
|
48108
|
+
warnings.push(`Ribbon '${this.name}' uses low width sampling; use widthSamples(3+) to show cross-surface curvature.`);
|
|
48109
|
+
return {
|
|
48110
|
+
name: this.name,
|
|
48111
|
+
...input.skin ? { skin: input.skin } : {},
|
|
48112
|
+
...input.side ? { side: input.side } : {},
|
|
48113
|
+
pathPointCount: input.pathPointCount,
|
|
48114
|
+
width: this.widthValue,
|
|
48115
|
+
thickness: this.thicknessValue,
|
|
48116
|
+
offset: this.offsetValue,
|
|
48117
|
+
samples: this.samplesValue,
|
|
48118
|
+
widthSamples: this.widthSamplesValue,
|
|
48119
|
+
resolution,
|
|
48120
|
+
lowering: "nurbsSurface",
|
|
48121
|
+
expectedFidelity: "mixed",
|
|
48122
|
+
clampedUCount: input.clampedUCount,
|
|
48123
|
+
maxUClampDistance: input.maxUClampDistance,
|
|
48124
|
+
warnings
|
|
48125
|
+
};
|
|
48126
|
+
}
|
|
48127
|
+
cloneDiagnostics(diagnostics) {
|
|
48128
|
+
return {
|
|
48129
|
+
...diagnostics,
|
|
48130
|
+
warnings: [...diagnostics.warnings]
|
|
48131
|
+
};
|
|
48132
|
+
}
|
|
48133
|
+
};
|
|
47373
48134
|
var Product = {
|
|
47374
48135
|
/** Start a named product skin builder. */
|
|
47375
48136
|
skin(name) {
|
|
@@ -47442,10 +48203,27 @@ var Product = {
|
|
|
47442
48203
|
ref(skin, query) {
|
|
47443
48204
|
return new ProductSurfaceRef(skin, query);
|
|
47444
48205
|
},
|
|
48206
|
+
/**
|
|
48207
|
+
* Create a fluent surface helper for refs and conformal features on one side of a skin.
|
|
48208
|
+
*
|
|
48209
|
+
* Equivalent to skin.surface(side), useful when writing in Product.* namespace style.
|
|
48210
|
+
*/
|
|
48211
|
+
surface(skin, side) {
|
|
48212
|
+
return skin.surface(side);
|
|
48213
|
+
},
|
|
47445
48214
|
/** Start a panel feature builder. */
|
|
47446
48215
|
panel(name) {
|
|
47447
48216
|
return new ProductPanelBuilder(name);
|
|
47448
48217
|
},
|
|
48218
|
+
/**
|
|
48219
|
+
* Start a conformal ribbon/trim builder for details that should bend with a ProductSkin.
|
|
48220
|
+
*
|
|
48221
|
+
* Call .on(skin, points) for side/u/v sampling or .fromRefs(points) for explicit surface refs,
|
|
48222
|
+
* then configure width, thickness, offset, sampling, material, and color before build().
|
|
48223
|
+
*/
|
|
48224
|
+
ribbon(name) {
|
|
48225
|
+
return new ProductRibbonBuilder(name);
|
|
48226
|
+
},
|
|
47449
48227
|
/** Start a spout/nozzle feature builder. */
|
|
47450
48228
|
spout(name) {
|
|
47451
48229
|
return new ProductSpoutBuilder(name);
|
|
@@ -47615,8 +48393,8 @@ function buildNameLookup(objects) {
|
|
|
47615
48393
|
const bySuffix = /* @__PURE__ */ new Map();
|
|
47616
48394
|
objects.forEach((obj) => {
|
|
47617
48395
|
pushLookup(byExact, obj.name, obj.id);
|
|
47618
|
-
for (let
|
|
47619
|
-
pushLookup(bySuffix, obj.name.slice(
|
|
48396
|
+
for (let dot9 = obj.name.indexOf("."); dot9 >= 0; dot9 = obj.name.indexOf(".", dot9 + 1)) {
|
|
48397
|
+
pushLookup(bySuffix, obj.name.slice(dot9 + 1), obj.id);
|
|
47620
48398
|
}
|
|
47621
48399
|
});
|
|
47622
48400
|
return { byExact, bySuffix };
|
|
@@ -49904,7 +50682,7 @@ var Constraint = {
|
|
|
49904
50682
|
};
|
|
49905
50683
|
|
|
49906
50684
|
// src/forge/points.ts
|
|
49907
|
-
function
|
|
50685
|
+
function requireVec33(v, label) {
|
|
49908
50686
|
if (!Array.isArray(v) || v.length !== 3 || !Number.isFinite(v[0]) || !Number.isFinite(v[1]) || !Number.isFinite(v[2])) {
|
|
49909
50687
|
throw new Error(`${label} must be a [number, number, number] with finite values, got ${JSON.stringify(v)}`);
|
|
49910
50688
|
}
|
|
@@ -49917,24 +50695,24 @@ function requireFiniteNumber(n, label) {
|
|
|
49917
50695
|
return n;
|
|
49918
50696
|
}
|
|
49919
50697
|
function distance(a, b) {
|
|
49920
|
-
|
|
49921
|
-
|
|
50698
|
+
requireVec33(a, "a");
|
|
50699
|
+
requireVec33(b, "b");
|
|
49922
50700
|
return Math.hypot(b[0] - a[0], b[1] - a[1], b[2] - a[2]);
|
|
49923
50701
|
}
|
|
49924
50702
|
function midpoint4(a, b) {
|
|
49925
|
-
|
|
49926
|
-
|
|
50703
|
+
requireVec33(a, "a");
|
|
50704
|
+
requireVec33(b, "b");
|
|
49927
50705
|
return [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2, (a[2] + b[2]) / 2];
|
|
49928
50706
|
}
|
|
49929
50707
|
function lerp3(a, b, t) {
|
|
49930
|
-
|
|
49931
|
-
|
|
50708
|
+
requireVec33(a, "a");
|
|
50709
|
+
requireVec33(b, "b");
|
|
49932
50710
|
requireFiniteNumber(t, "t");
|
|
49933
50711
|
return [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t, a[2] + (b[2] - a[2]) * t];
|
|
49934
50712
|
}
|
|
49935
50713
|
function direction(a, b) {
|
|
49936
|
-
|
|
49937
|
-
|
|
50714
|
+
requireVec33(a, "a");
|
|
50715
|
+
requireVec33(b, "b");
|
|
49938
50716
|
const dx = b[0] - a[0];
|
|
49939
50717
|
const dy = b[1] - a[1];
|
|
49940
50718
|
const dz = b[2] - a[2];
|
|
@@ -49945,8 +50723,8 @@ function direction(a, b) {
|
|
|
49945
50723
|
return [dx / len2, dy / len2, dz / len2];
|
|
49946
50724
|
}
|
|
49947
50725
|
function offset(point2, dir, amount) {
|
|
49948
|
-
|
|
49949
|
-
|
|
50726
|
+
requireVec33(point2, "point");
|
|
50727
|
+
requireVec33(dir, "dir");
|
|
49950
50728
|
requireFiniteNumber(amount, "amount");
|
|
49951
50729
|
return [point2[0] + dir[0] * amount, point2[1] + dir[1] * amount, point2[2] + dir[2] * amount];
|
|
49952
50730
|
}
|
|
@@ -50055,14 +50833,14 @@ var WoodBoard = class _WoodBoard {
|
|
|
50055
50833
|
};
|
|
50056
50834
|
|
|
50057
50835
|
// src/forge/wood/joints.ts
|
|
50058
|
-
function
|
|
50836
|
+
function requireFinite5(value, name) {
|
|
50059
50837
|
if (!Number.isFinite(value)) {
|
|
50060
50838
|
throw new Error(`${name} must be a finite number, got ${value}`);
|
|
50061
50839
|
}
|
|
50062
50840
|
return value;
|
|
50063
50841
|
}
|
|
50064
50842
|
function requirePositive3(value, name) {
|
|
50065
|
-
|
|
50843
|
+
requireFinite5(value, name);
|
|
50066
50844
|
if (value <= 0) {
|
|
50067
50845
|
throw new Error(`${name} must be positive, got ${value}`);
|
|
50068
50846
|
}
|
|
@@ -50097,10 +50875,10 @@ function dado(host, guest, opts) {
|
|
|
50097
50875
|
}
|
|
50098
50876
|
let fromBottom;
|
|
50099
50877
|
if (opts.fromBottom != null) {
|
|
50100
|
-
fromBottom =
|
|
50878
|
+
fromBottom = requireFinite5(opts.fromBottom, "fromBottom");
|
|
50101
50879
|
} else {
|
|
50102
50880
|
fromBottom = host.height - opts.fromTop - channelWidth;
|
|
50103
|
-
|
|
50881
|
+
requireFinite5(fromBottom, "computed fromBottom");
|
|
50104
50882
|
}
|
|
50105
50883
|
let dadoLength = host.width;
|
|
50106
50884
|
let xOffset = 0;
|
|
@@ -50157,7 +50935,7 @@ function mortiseAndTenon(mortiseBoard, tenonBoard, opts) {
|
|
|
50157
50935
|
const style = o.style ?? "blind";
|
|
50158
50936
|
const fit = o.fit ?? "snug";
|
|
50159
50937
|
const clearance = clearanceForFit(fit);
|
|
50160
|
-
const cornerRadius = o.cornerRadius != null ?
|
|
50938
|
+
const cornerRadius = o.cornerRadius != null ? requireFinite5(o.cornerRadius, "cornerRadius") : 0;
|
|
50161
50939
|
const tenonThickness = o.tenonThickness != null ? requirePositive3(o.tenonThickness, "tenonThickness") : tenonBoard.thickness / 3;
|
|
50162
50940
|
const tenonWidth = o.tenonWidth != null ? requirePositive3(o.tenonWidth, "tenonWidth") : Math.min(tenonBoard.height * 0.6, mortiseBoard.height * 0.8);
|
|
50163
50941
|
const tenonLength = o.tenonLength != null ? requirePositive3(o.tenonLength, "tenonLength") : style === "through" ? mortiseBoard.thickness : mortiseBoard.thickness * 2 / 3;
|
|
@@ -50170,10 +50948,10 @@ function mortiseAndTenon(mortiseBoard, tenonBoard, opts) {
|
|
|
50170
50948
|
throw new Error("mortiseAndTenon: specify position.fromTop or position.fromBottom, not both");
|
|
50171
50949
|
}
|
|
50172
50950
|
if (o.position.fromTop != null) {
|
|
50173
|
-
|
|
50951
|
+
requireFinite5(o.position.fromTop, "position.fromTop");
|
|
50174
50952
|
mortiseCenterY = mortiseBoard.height / 2 - o.position.fromTop - mortiseH / 2;
|
|
50175
50953
|
} else if (o.position.fromBottom != null) {
|
|
50176
|
-
|
|
50954
|
+
requireFinite5(o.position.fromBottom, "position.fromBottom");
|
|
50177
50955
|
mortiseCenterY = -mortiseBoard.height / 2 + o.position.fromBottom + mortiseH / 2;
|
|
50178
50956
|
}
|
|
50179
50957
|
}
|
|
@@ -50249,12 +51027,12 @@ var Wood = {
|
|
|
50249
51027
|
};
|
|
50250
51028
|
|
|
50251
51029
|
// src/forge/cameraTrajectory.ts
|
|
50252
|
-
var
|
|
51030
|
+
var _collected8 = null;
|
|
50253
51031
|
function resetCameraTrajectory() {
|
|
50254
|
-
|
|
51032
|
+
_collected8 = null;
|
|
50255
51033
|
}
|
|
50256
51034
|
function getCollectedCameraTrajectory() {
|
|
50257
|
-
return
|
|
51035
|
+
return _collected8;
|
|
50258
51036
|
}
|
|
50259
51037
|
function isOrbitKeyframe(kf) {
|
|
50260
51038
|
return "orbit" in kf;
|
|
@@ -50262,19 +51040,19 @@ function isOrbitKeyframe(kf) {
|
|
|
50262
51040
|
function isCartesianKeyframe(kf) {
|
|
50263
51041
|
return "position" in kf;
|
|
50264
51042
|
}
|
|
50265
|
-
function
|
|
51043
|
+
function requireFinite6(value, label) {
|
|
50266
51044
|
if (!Number.isFinite(value)) {
|
|
50267
51045
|
throw new Error(`cameraTrajectory(): ${label} must be a finite number, got ${value}`);
|
|
50268
51046
|
}
|
|
50269
51047
|
}
|
|
50270
51048
|
function validateOrbitKeyframe(kf, index) {
|
|
50271
|
-
|
|
50272
|
-
|
|
50273
|
-
|
|
50274
|
-
|
|
51049
|
+
requireFinite6(kf.at, `keyframes[${index}].at`);
|
|
51050
|
+
requireFinite6(kf.orbit.angle, `keyframes[${index}].orbit.angle`);
|
|
51051
|
+
requireFinite6(kf.orbit.pitch, `keyframes[${index}].orbit.pitch`);
|
|
51052
|
+
requireFinite6(kf.orbit.distance, `keyframes[${index}].orbit.distance`);
|
|
50275
51053
|
}
|
|
50276
51054
|
function validateCartesianKeyframe(kf, index) {
|
|
50277
|
-
|
|
51055
|
+
requireFinite6(kf.at, `keyframes[${index}].at`);
|
|
50278
51056
|
if (!Array.isArray(kf.position) || kf.position.length !== 3) {
|
|
50279
51057
|
throw new Error(`cameraTrajectory(): keyframes[${index}].position must be a 3-element array`);
|
|
50280
51058
|
}
|
|
@@ -50282,8 +51060,8 @@ function validateCartesianKeyframe(kf, index) {
|
|
|
50282
51060
|
throw new Error(`cameraTrajectory(): keyframes[${index}].target must be a 3-element array`);
|
|
50283
51061
|
}
|
|
50284
51062
|
for (let i = 0; i < 3; i++) {
|
|
50285
|
-
|
|
50286
|
-
|
|
51063
|
+
requireFinite6(kf.position[i], `keyframes[${index}].position[${i}]`);
|
|
51064
|
+
requireFinite6(kf.target[i], `keyframes[${index}].target[${i}]`);
|
|
50287
51065
|
}
|
|
50288
51066
|
}
|
|
50289
51067
|
function validateKeyframeOrder(keyframes) {
|
|
@@ -50303,11 +51081,11 @@ function validateKeyframeOrder(keyframes) {
|
|
|
50303
51081
|
}
|
|
50304
51082
|
}
|
|
50305
51083
|
function cameraTrajectory(defOrFn, options) {
|
|
50306
|
-
if (
|
|
51084
|
+
if (_collected8 !== null) {
|
|
50307
51085
|
console.warn("cameraTrajectory() called more than once \u2014 overwriting previous trajectory.");
|
|
50308
51086
|
}
|
|
50309
51087
|
if (typeof defOrFn === "function") {
|
|
50310
|
-
|
|
51088
|
+
_collected8 = {
|
|
50311
51089
|
kind: "parametric",
|
|
50312
51090
|
parametricFn: defOrFn,
|
|
50313
51091
|
duration: options?.duration,
|
|
@@ -50326,13 +51104,13 @@ function cameraTrajectory(defOrFn, options) {
|
|
|
50326
51104
|
for (let i = 0; i < orbitKeyframes.length; i++) {
|
|
50327
51105
|
validateOrbitKeyframe(orbitKeyframes[i], i);
|
|
50328
51106
|
}
|
|
50329
|
-
|
|
51107
|
+
_collected8 = { kind: "orbit-keyframes", orbitKeyframes, duration, fps, easing };
|
|
50330
51108
|
} else if (isCartesianKeyframe(first)) {
|
|
50331
51109
|
const cartesianKeyframes = keyframes;
|
|
50332
51110
|
for (let i = 0; i < cartesianKeyframes.length; i++) {
|
|
50333
51111
|
validateCartesianKeyframe(cartesianKeyframes[i], i);
|
|
50334
51112
|
}
|
|
50335
|
-
|
|
51113
|
+
_collected8 = { kind: "cartesian-keyframes", cartesianKeyframes, duration, fps, easing };
|
|
50336
51114
|
} else {
|
|
50337
51115
|
throw new Error('cameraTrajectory(): each keyframe must have either an "orbit" or "position" property');
|
|
50338
51116
|
}
|
|
@@ -50360,6 +51138,76 @@ function isEdgeSegment(value) {
|
|
|
50360
51138
|
function isEdgeReferenceLike(value) {
|
|
50361
51139
|
return typeof value === "object" && value !== null && "edges" in value && typeof value.edges === "function";
|
|
50362
51140
|
}
|
|
51141
|
+
var BROAD_EDGE_FEATURE_DEFAULT_BUDGET = {
|
|
51142
|
+
live: 0,
|
|
51143
|
+
default: 12,
|
|
51144
|
+
high: Number.POSITIVE_INFINITY
|
|
51145
|
+
};
|
|
51146
|
+
var broadEdgeFeatureBudget = null;
|
|
51147
|
+
function readBroadEdgeFeatureEnv(name) {
|
|
51148
|
+
return typeof process !== "undefined" ? process.env?.[name] : void 0;
|
|
51149
|
+
}
|
|
51150
|
+
function resolveBroadEdgeFeatureBudget() {
|
|
51151
|
+
if (readBroadEdgeFeatureEnv("FORGECAD_ALLOW_BROAD_EDGE_FEATURES") === "1") return Number.POSITIVE_INFINITY;
|
|
51152
|
+
const override = readBroadEdgeFeatureEnv("FORGECAD_BROAD_EDGE_FEATURE_BUDGET");
|
|
51153
|
+
if (override != null && override.trim() !== "") {
|
|
51154
|
+
const parsed = Number(override);
|
|
51155
|
+
if (Number.isFinite(parsed) && parsed >= 0) return parsed;
|
|
51156
|
+
}
|
|
51157
|
+
return BROAD_EDGE_FEATURE_DEFAULT_BUDGET[getForgeQualityPreset()];
|
|
51158
|
+
}
|
|
51159
|
+
function resetBroadEdgeFeatureBudget() {
|
|
51160
|
+
broadEdgeFeatureBudget = null;
|
|
51161
|
+
}
|
|
51162
|
+
function remainingBroadEdgeFeatureBudget() {
|
|
51163
|
+
if (broadEdgeFeatureBudget === null) broadEdgeFeatureBudget = resolveBroadEdgeFeatureBudget();
|
|
51164
|
+
return broadEdgeFeatureBudget;
|
|
51165
|
+
}
|
|
51166
|
+
function consumeBroadEdgeFeatureBudget(edgeCount) {
|
|
51167
|
+
const remaining = remainingBroadEdgeFeatureBudget();
|
|
51168
|
+
if (!Number.isFinite(remaining)) return true;
|
|
51169
|
+
if (edgeCount > remaining) return false;
|
|
51170
|
+
broadEdgeFeatureBudget = Math.max(0, remaining - edgeCount);
|
|
51171
|
+
return true;
|
|
51172
|
+
}
|
|
51173
|
+
function shouldSkipBroadEdgeFeature(operation, edgeCount) {
|
|
51174
|
+
if (consumeBroadEdgeFeatureBudget(edgeCount)) return false;
|
|
51175
|
+
const remaining = Math.max(0, remainingBroadEdgeFeatureBudget());
|
|
51176
|
+
emitRuntimeWarning(
|
|
51177
|
+
`${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.`
|
|
51178
|
+
);
|
|
51179
|
+
return true;
|
|
51180
|
+
}
|
|
51181
|
+
function shouldSkipExhaustedBroadEdgeFeature(operation) {
|
|
51182
|
+
const remaining = remainingBroadEdgeFeatureBudget();
|
|
51183
|
+
if (!Number.isFinite(remaining) || remaining > 0) return false;
|
|
51184
|
+
emitRuntimeWarning(
|
|
51185
|
+
`${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.`
|
|
51186
|
+
);
|
|
51187
|
+
return true;
|
|
51188
|
+
}
|
|
51189
|
+
function estimateSelectorlessEdgeCount(plan) {
|
|
51190
|
+
if (!plan) return null;
|
|
51191
|
+
switch (plan.kind) {
|
|
51192
|
+
case "box":
|
|
51193
|
+
return 12;
|
|
51194
|
+
case "queryOwner":
|
|
51195
|
+
case "transform":
|
|
51196
|
+
return estimateSelectorlessEdgeCount(plan.base);
|
|
51197
|
+
default:
|
|
51198
|
+
return null;
|
|
51199
|
+
}
|
|
51200
|
+
}
|
|
51201
|
+
function shouldSkipUnestimatedBroadEdgeFeature(operation, target) {
|
|
51202
|
+
const remaining = remainingBroadEdgeFeatureBudget();
|
|
51203
|
+
if (!Number.isFinite(remaining)) return false;
|
|
51204
|
+
const estimatedEdges = estimateSelectorlessEdgeCount(getShapeCompilePlan(target));
|
|
51205
|
+
if (estimatedEdges !== null && estimatedEdges <= remaining) return false;
|
|
51206
|
+
emitRuntimeWarning(
|
|
51207
|
+
`${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.`
|
|
51208
|
+
);
|
|
51209
|
+
return true;
|
|
51210
|
+
}
|
|
50363
51211
|
function edgesToTargets(edges) {
|
|
50364
51212
|
return edges.map((e) => ({
|
|
50365
51213
|
midpoint: [e.midpoint[0], e.midpoint[1], e.midpoint[2]],
|
|
@@ -50373,10 +51221,13 @@ function fillet(shape, radius, edges, segments = 16) {
|
|
|
50373
51221
|
throw new Error("fillet() requires a positive finite radius.");
|
|
50374
51222
|
}
|
|
50375
51223
|
const target = shape;
|
|
51224
|
+
if (edges === void 0 && shouldSkipExhaustedBroadEdgeFeature("fillet")) return target;
|
|
51225
|
+
if (edges === void 0 && shouldSkipUnestimatedBroadEdgeFeature("fillet", target)) return target;
|
|
50376
51226
|
const resolvedEdges = resolveEdges(target, edges);
|
|
50377
51227
|
if (resolvedEdges.length === 0) {
|
|
50378
51228
|
throw new Error("fillet(): no edges match the given selection.");
|
|
50379
51229
|
}
|
|
51230
|
+
if (edges === void 0 && shouldSkipBroadEdgeFeature("fillet", resolvedEdges.length)) return target;
|
|
50380
51231
|
const basePlan = getShapeCompilePlan(target);
|
|
50381
51232
|
const plan = {
|
|
50382
51233
|
kind: "filletEdges",
|
|
@@ -50396,10 +51247,13 @@ function chamfer(shape, size, edges) {
|
|
|
50396
51247
|
throw new Error("chamfer() requires a positive finite size.");
|
|
50397
51248
|
}
|
|
50398
51249
|
const target = shape;
|
|
51250
|
+
if (edges === void 0 && shouldSkipExhaustedBroadEdgeFeature("chamfer")) return target;
|
|
51251
|
+
if (edges === void 0 && shouldSkipUnestimatedBroadEdgeFeature("chamfer", target)) return target;
|
|
50399
51252
|
const resolvedEdges = resolveEdges(target, edges);
|
|
50400
51253
|
if (resolvedEdges.length === 0) {
|
|
50401
51254
|
throw new Error("chamfer(): no edges match the given selection.");
|
|
50402
51255
|
}
|
|
51256
|
+
if (edges === void 0 && shouldSkipBroadEdgeFeature("chamfer", resolvedEdges.length)) return target;
|
|
50403
51257
|
const basePlan = getShapeCompilePlan(target);
|
|
50404
51258
|
const plan = {
|
|
50405
51259
|
kind: "chamferEdges",
|
|
@@ -50455,10 +51309,10 @@ function offsetSolid(shape, thickness) {
|
|
|
50455
51309
|
|
|
50456
51310
|
// src/forge/projectionCompile.ts
|
|
50457
51311
|
var EPS8 = 1e-6;
|
|
50458
|
-
function
|
|
51312
|
+
function dot7(a, b) {
|
|
50459
51313
|
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
|
50460
51314
|
}
|
|
50461
|
-
function
|
|
51315
|
+
function sub7(a, b) {
|
|
50462
51316
|
return [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
|
|
50463
51317
|
}
|
|
50464
51318
|
function length2d(x, y) {
|
|
@@ -50583,14 +51437,14 @@ function mapProfileToPlane(profile, sourcePlacement, targetPlane) {
|
|
|
50583
51437
|
const sourceU = sourceTransform.vector([1, 0, 0]);
|
|
50584
51438
|
const sourceV = sourceTransform.vector([0, 1, 0]);
|
|
50585
51439
|
const sourceNormal = sourceTransform.vector([0, 0, 1]);
|
|
50586
|
-
const normalAlignment = Math.abs(
|
|
51440
|
+
const normalAlignment = Math.abs(dot7(normalize8(sourceNormal), targetPlane.normal));
|
|
50587
51441
|
if (!nearlyEqual(normalAlignment, 1, 1e-5)) {
|
|
50588
51442
|
return { reason: "projection replay currently requires the target plane to stay parallel to the source workplane." };
|
|
50589
51443
|
}
|
|
50590
|
-
const a =
|
|
50591
|
-
const b =
|
|
50592
|
-
const c =
|
|
50593
|
-
const d =
|
|
51444
|
+
const a = dot7(sourceU, targetPlane.u);
|
|
51445
|
+
const b = dot7(sourceV, targetPlane.u);
|
|
51446
|
+
const c = dot7(sourceU, targetPlane.v);
|
|
51447
|
+
const d = dot7(sourceV, targetPlane.v);
|
|
50594
51448
|
const sx = length2d(a, c);
|
|
50595
51449
|
const sy = length2d(b, d);
|
|
50596
51450
|
if (sx < EPS8 || sy < EPS8) {
|
|
@@ -50624,9 +51478,9 @@ function mapProfileToPlane(profile, sourcePlacement, targetPlane) {
|
|
|
50624
51478
|
next = appendProfileCompileTransform(next, { kind: "rotate", degrees: angle });
|
|
50625
51479
|
}
|
|
50626
51480
|
}
|
|
50627
|
-
const delta =
|
|
50628
|
-
const tx =
|
|
50629
|
-
const ty =
|
|
51481
|
+
const delta = sub7(sourceOrigin, targetPlane.origin);
|
|
51482
|
+
const tx = dot7(delta, targetPlane.u);
|
|
51483
|
+
const ty = dot7(delta, targetPlane.v);
|
|
50630
51484
|
if (!nearlyEqual(tx, 0) || !nearlyEqual(ty, 0)) {
|
|
50631
51485
|
next = appendProfileCompileTransform(next, { kind: "translate", x: tx, y: ty });
|
|
50632
51486
|
}
|
|
@@ -56547,8 +57401,8 @@ function normalizeVector2(v) {
|
|
|
56547
57401
|
function angleDegBetween(a, b) {
|
|
56548
57402
|
const na = normalizeVector2(a);
|
|
56549
57403
|
const nb = normalizeVector2(b);
|
|
56550
|
-
const
|
|
56551
|
-
return Math.acos(
|
|
57404
|
+
const dot9 = Math.max(-1, Math.min(1, na[0] * nb[0] + na[1] * nb[1] + na[2] * nb[2]));
|
|
57405
|
+
return Math.acos(dot9) * 180 / Math.PI;
|
|
56552
57406
|
}
|
|
56553
57407
|
function edgeEndpoint(edge, endpoint) {
|
|
56554
57408
|
const source = endpoint === "start" ? edge.start : edge.end;
|
|
@@ -57466,9 +58320,9 @@ function sampleQuadratic2(p0, p1, p2, tolerance) {
|
|
|
57466
58320
|
return out;
|
|
57467
58321
|
}
|
|
57468
58322
|
function vectorAngle(ux, uy, vx, vy) {
|
|
57469
|
-
const
|
|
58323
|
+
const dot9 = ux * vx + uy * vy;
|
|
57470
58324
|
const det = ux * vy - uy * vx;
|
|
57471
|
-
return Math.atan2(det,
|
|
58325
|
+
return Math.atan2(det, dot9);
|
|
57472
58326
|
}
|
|
57473
58327
|
function sampleArc2(x1, y1, rxInput, ryInput, xAxisRotation, largeArcFlag, sweepFlag, x2, y2, tolerance, arcSegmentsMin) {
|
|
57474
58328
|
let rx = Math.abs(rxInput);
|
|
@@ -58567,8 +59421,8 @@ function fromSlicesImpl(slices, options = {}) {
|
|
|
58567
59421
|
group2 = { normal: canonicalNormal(s.normal), slices: [] };
|
|
58568
59422
|
groupMap.set(key, group2);
|
|
58569
59423
|
}
|
|
58570
|
-
const
|
|
58571
|
-
const sign =
|
|
59424
|
+
const dot9 = s.normal[0] * group2.normal[0] + s.normal[1] * group2.normal[1] + s.normal[2] * group2.normal[2];
|
|
59425
|
+
const sign = dot9 < 0 ? -1 : 1;
|
|
58572
59426
|
group2.slices.push({ offset: s.offset * sign, profile: s.profile });
|
|
58573
59427
|
}
|
|
58574
59428
|
const groups = [...groupMap.values()];
|
|
@@ -58933,14 +59787,14 @@ function sheetStock(width, height, description, opts) {
|
|
|
58933
59787
|
}
|
|
58934
59788
|
|
|
58935
59789
|
// src/forge/laserKit/joints.ts
|
|
58936
|
-
function
|
|
59790
|
+
function requireFinite7(value, name) {
|
|
58937
59791
|
if (!Number.isFinite(value)) {
|
|
58938
59792
|
throw new Error(`${name} must be a finite number, got ${value}`);
|
|
58939
59793
|
}
|
|
58940
59794
|
return value;
|
|
58941
59795
|
}
|
|
58942
59796
|
function requirePositive4(value, name) {
|
|
58943
|
-
|
|
59797
|
+
requireFinite7(value, name);
|
|
58944
59798
|
if (value <= 0) {
|
|
58945
59799
|
throw new Error(`${name} must be positive, got ${value}`);
|
|
58946
59800
|
}
|
|
@@ -58955,8 +59809,8 @@ function requireOdd(value, name) {
|
|
|
58955
59809
|
function fingerJointProfile(length5, thickness, options = {}) {
|
|
58956
59810
|
requirePositive4(length5, "length");
|
|
58957
59811
|
requirePositive4(thickness, "thickness");
|
|
58958
|
-
const clearance =
|
|
58959
|
-
const kerf =
|
|
59812
|
+
const clearance = requireFinite7(options.clearance ?? 0, "clearance");
|
|
59813
|
+
const kerf = requireFinite7(options.kerf ?? 0, "kerf");
|
|
58960
59814
|
const endStyle = options.endStyle ?? "full";
|
|
58961
59815
|
let fingers;
|
|
58962
59816
|
if (options.fingers != null) {
|
|
@@ -59019,11 +59873,11 @@ function fingerJointProfile(length5, thickness, options = {}) {
|
|
|
59019
59873
|
function tabSlotProfile(length5, thickness, options = {}) {
|
|
59020
59874
|
requirePositive4(length5, "length");
|
|
59021
59875
|
requirePositive4(thickness, "thickness");
|
|
59022
|
-
const clearance =
|
|
59023
|
-
const kerf =
|
|
59876
|
+
const clearance = requireFinite7(options.clearance ?? 0, "clearance");
|
|
59877
|
+
const kerf = requireFinite7(options.kerf ?? 0, "kerf");
|
|
59024
59878
|
const inset = requirePositive4(options.inset ?? thickness, "inset");
|
|
59025
59879
|
const tabWidth = requirePositive4(options.tabWidth ?? 2 * thickness, "tabWidth");
|
|
59026
|
-
const tabCount = options.tabCount != null ? (
|
|
59880
|
+
const tabCount = options.tabCount != null ? (requireFinite7(options.tabCount, "tabCount"), Math.max(1, Math.round(options.tabCount))) : Math.max(1, Math.round(length5 / (4 * thickness)));
|
|
59027
59881
|
const halfKerf = kerf / 2;
|
|
59028
59882
|
const usableLength = length5 - 2 * inset;
|
|
59029
59883
|
const spacing = tabCount > 1 ? usableLength / (tabCount - 1) : 0;
|
|
@@ -59112,14 +59966,14 @@ function lookupKerf(material, thickness, laserType) {
|
|
|
59112
59966
|
}
|
|
59113
59967
|
|
|
59114
59968
|
// src/forge/laserKit/flatPart.ts
|
|
59115
|
-
function
|
|
59969
|
+
function requireFinite8(value, name) {
|
|
59116
59970
|
if (!Number.isFinite(value)) {
|
|
59117
59971
|
throw new Error(`${name} must be a finite number, got ${value}`);
|
|
59118
59972
|
}
|
|
59119
59973
|
return value;
|
|
59120
59974
|
}
|
|
59121
59975
|
function requirePositive5(value, name) {
|
|
59122
|
-
|
|
59976
|
+
requireFinite8(value, name);
|
|
59123
59977
|
if (value <= 0) {
|
|
59124
59978
|
throw new Error(`${name} must be positive, got ${value}`);
|
|
59125
59979
|
}
|
|
@@ -59219,9 +60073,9 @@ function positionAlongEdge(profile, edge, inward = false) {
|
|
|
59219
60073
|
const angle = Math.atan2(-nx, ny) * (180 / Math.PI);
|
|
59220
60074
|
const edx = (edge.end[0] - edge.start[0]) / edge.length;
|
|
59221
60075
|
const edy = (edge.end[1] - edge.start[1]) / edge.length;
|
|
59222
|
-
const
|
|
60076
|
+
const dot9 = ny * edx + -nx * edy;
|
|
59223
60077
|
const rotated = profile.rotateAround(angle, [0, 0]);
|
|
59224
|
-
if (
|
|
60078
|
+
if (dot9 > 0) {
|
|
59225
60079
|
return rotated.translate(edge.start[0], edge.start[1]);
|
|
59226
60080
|
} else {
|
|
59227
60081
|
return rotated.translate(edge.end[0], edge.end[1]);
|
|
@@ -60363,12 +61217,12 @@ function rotatePoint(p2, axis, angleDeg2, pivot) {
|
|
|
60363
61217
|
const s = Math.sin(rad);
|
|
60364
61218
|
const k = v3normalize(axis);
|
|
60365
61219
|
const v = v3sub(p2, pivot);
|
|
60366
|
-
const
|
|
61220
|
+
const dot9 = v3dot(k, v);
|
|
60367
61221
|
const cross7 = v3cross(k, v);
|
|
60368
61222
|
const result = [
|
|
60369
|
-
v[0] * c + cross7[0] * s + k[0] *
|
|
60370
|
-
v[1] * c + cross7[1] * s + k[1] *
|
|
60371
|
-
v[2] * c + cross7[2] * s + k[2] *
|
|
61223
|
+
v[0] * c + cross7[0] * s + k[0] * dot9 * (1 - c),
|
|
61224
|
+
v[1] * c + cross7[1] * s + k[1] * dot9 * (1 - c),
|
|
61225
|
+
v[2] * c + cross7[2] * s + k[2] * dot9 * (1 - c)
|
|
60372
61226
|
];
|
|
60373
61227
|
return v3add(result, pivot);
|
|
60374
61228
|
}
|
|
@@ -60943,14 +61797,14 @@ function spec(name, checkFn) {
|
|
|
60943
61797
|
return {
|
|
60944
61798
|
name,
|
|
60945
61799
|
check(...args) {
|
|
60946
|
-
const before =
|
|
61800
|
+
const before = _collected9.length;
|
|
60947
61801
|
_activeGroup = name;
|
|
60948
61802
|
try {
|
|
60949
61803
|
checkFn(...args);
|
|
60950
61804
|
} finally {
|
|
60951
61805
|
_activeGroup = null;
|
|
60952
61806
|
}
|
|
60953
|
-
const added =
|
|
61807
|
+
const added = _collected9.slice(before);
|
|
60954
61808
|
return {
|
|
60955
61809
|
name,
|
|
60956
61810
|
passed: added.filter((r) => r.status === "pass").length,
|
|
@@ -60960,15 +61814,15 @@ function spec(name, checkFn) {
|
|
|
60960
61814
|
}
|
|
60961
61815
|
};
|
|
60962
61816
|
}
|
|
60963
|
-
var
|
|
61817
|
+
var _collected9 = [];
|
|
60964
61818
|
var _counter2 = 0;
|
|
60965
61819
|
var _activeGroup = null;
|
|
60966
61820
|
function resetVerifications() {
|
|
60967
|
-
|
|
61821
|
+
_collected9 = [];
|
|
60968
61822
|
_counter2 = 0;
|
|
60969
61823
|
}
|
|
60970
61824
|
function getCollectedVerifications() {
|
|
60971
|
-
return
|
|
61825
|
+
return _collected9.slice();
|
|
60972
61826
|
}
|
|
60973
61827
|
function nextId() {
|
|
60974
61828
|
_counter2 += 1;
|
|
@@ -60993,7 +61847,7 @@ function captureSourceLine() {
|
|
|
60993
61847
|
}
|
|
60994
61848
|
function push(result) {
|
|
60995
61849
|
if (_activeGroup) result.group = _activeGroup;
|
|
60996
|
-
|
|
61850
|
+
_collected9.push(result);
|
|
60997
61851
|
}
|
|
60998
61852
|
function roundNum(n, digits = 4) {
|
|
60999
61853
|
return Number.isFinite(n) ? n.toFixed(digits).replace(/\.?0+$/, "") : String(n);
|
|
@@ -61196,14 +62050,14 @@ var verify = {
|
|
|
61196
62050
|
try {
|
|
61197
62051
|
const na = faceA.normal;
|
|
61198
62052
|
const nb = faceB.normal;
|
|
61199
|
-
const
|
|
62053
|
+
const dot9 = vec3Dot3(na, nb);
|
|
61200
62054
|
const lenA = vec3Len3(na);
|
|
61201
62055
|
const lenB = vec3Len3(nb);
|
|
61202
62056
|
if (lenA < 1e-9 || lenB < 1e-9) {
|
|
61203
62057
|
push({ id: nextId(), label, status: "fail", message: "One or both faces have zero-length normals", line: line2 });
|
|
61204
62058
|
return;
|
|
61205
62059
|
}
|
|
61206
|
-
const cosAngle = Math.abs(
|
|
62060
|
+
const cosAngle = Math.abs(dot9) / (lenA * lenB);
|
|
61207
62061
|
const angleDeg2 = Math.acos(Math.min(1, cosAngle)) * 180 / Math.PI;
|
|
61208
62062
|
const passed = angleDeg2 <= toleranceDeg;
|
|
61209
62063
|
push({
|
|
@@ -61227,14 +62081,14 @@ var verify = {
|
|
|
61227
62081
|
try {
|
|
61228
62082
|
const na = faceA.normal;
|
|
61229
62083
|
const nb = faceB.normal;
|
|
61230
|
-
const
|
|
62084
|
+
const dot9 = vec3Dot3(na, nb);
|
|
61231
62085
|
const lenA = vec3Len3(na);
|
|
61232
62086
|
const lenB = vec3Len3(nb);
|
|
61233
62087
|
if (lenA < 1e-9 || lenB < 1e-9) {
|
|
61234
62088
|
push({ id: nextId(), label, status: "fail", message: "One or both faces have zero-length normals", line: line2 });
|
|
61235
62089
|
return;
|
|
61236
62090
|
}
|
|
61237
|
-
const cosAngle = Math.abs(
|
|
62091
|
+
const cosAngle = Math.abs(dot9) / (lenA * lenB);
|
|
61238
62092
|
const angleDeg2 = 90 - Math.acos(Math.min(1, cosAngle)) * 180 / Math.PI;
|
|
61239
62093
|
const passed = Math.abs(angleDeg2) <= toleranceDeg;
|
|
61240
62094
|
push({
|
|
@@ -61328,18 +62182,18 @@ var verify = {
|
|
|
61328
62182
|
try {
|
|
61329
62183
|
const na = faceA.normal;
|
|
61330
62184
|
const nb = faceB.normal;
|
|
61331
|
-
const
|
|
62185
|
+
const dot9 = vec3Dot3(na, nb);
|
|
61332
62186
|
const lenA = vec3Len3(na);
|
|
61333
62187
|
const lenB = vec3Len3(nb);
|
|
61334
62188
|
if (lenA < 1e-9 || lenB < 1e-9) {
|
|
61335
62189
|
push({ id: nextId(), label, status: "fail", message: "One or both faces have zero-length normals", line: line2 });
|
|
61336
62190
|
return;
|
|
61337
62191
|
}
|
|
61338
|
-
if (
|
|
62192
|
+
if (dot9 <= 0) {
|
|
61339
62193
|
push({ id: nextId(), label, status: "fail", message: "Face normals point in opposite directions", line: line2 });
|
|
61340
62194
|
return;
|
|
61341
62195
|
}
|
|
61342
|
-
const cosAngle =
|
|
62196
|
+
const cosAngle = dot9 / (lenA * lenB);
|
|
61343
62197
|
const angleDeg2 = Math.acos(Math.min(1, cosAngle)) * 180 / Math.PI;
|
|
61344
62198
|
const passed = angleDeg2 <= toleranceDeg;
|
|
61345
62199
|
push({
|
|
@@ -61556,6 +62410,7 @@ var verify = {
|
|
|
61556
62410
|
// src/forge/script-runtime/executionSession.ts
|
|
61557
62411
|
function resetExecutionSession(logs) {
|
|
61558
62412
|
resetCollectedAssemblies();
|
|
62413
|
+
resetBroadEdgeFeatureBudget();
|
|
61559
62414
|
resetParams();
|
|
61560
62415
|
resetShapeQueryOwnerIds();
|
|
61561
62416
|
resetDimensions();
|
|
@@ -61564,6 +62419,7 @@ function resetExecutionSession(logs) {
|
|
|
61564
62419
|
resetSheetStock();
|
|
61565
62420
|
resetRobotExport();
|
|
61566
62421
|
resetCutPlanes();
|
|
62422
|
+
resetRenderLabels();
|
|
61567
62423
|
resetCameraTrajectory();
|
|
61568
62424
|
resetExplodeView();
|
|
61569
62425
|
resetJointsView();
|
|
@@ -61627,6 +62483,7 @@ function collectSuccessfulExecutionSnapshot(args) {
|
|
|
61627
62483
|
bom: getCollectedBom(),
|
|
61628
62484
|
sheetStock: getCollectedSheetStock(),
|
|
61629
62485
|
cutPlanes: getCollectedCutPlanes(),
|
|
62486
|
+
renderLabels: getCollectedRenderLabels(),
|
|
61630
62487
|
cameraTrajectory: getCollectedCameraTrajectory(),
|
|
61631
62488
|
explodeView: getCollectedExplodeView(),
|
|
61632
62489
|
jointsView: getCollectedJointsView(),
|
|
@@ -61650,6 +62507,7 @@ function collectFailedExecutionSnapshot(args) {
|
|
|
61650
62507
|
bom: getCollectedBom(),
|
|
61651
62508
|
sheetStock: getCollectedSheetStock(),
|
|
61652
62509
|
cutPlanes: getCollectedCutPlanes(),
|
|
62510
|
+
renderLabels: getCollectedRenderLabels(),
|
|
61653
62511
|
cameraTrajectory: getCollectedCameraTrajectory(),
|
|
61654
62512
|
explodeView: getCollectedExplodeView(),
|
|
61655
62513
|
jointsView: getCollectedJointsView(),
|
|
@@ -61796,13 +62654,15 @@ function formatLogArg(value) {
|
|
|
61796
62654
|
return `[Log serialization failed: ${formatLogError(error)}]`;
|
|
61797
62655
|
}
|
|
61798
62656
|
}
|
|
61799
|
-
function makeSandboxConsole(collectedLogs) {
|
|
62657
|
+
function makeSandboxConsole(collectedLogs, mirror) {
|
|
61800
62658
|
const capture = (level) => (...args) => {
|
|
62659
|
+
const formattedArgs = args.map(formatLogArg);
|
|
61801
62660
|
collectedLogs.push({
|
|
61802
62661
|
level,
|
|
61803
|
-
args:
|
|
62662
|
+
args: formattedArgs,
|
|
61804
62663
|
timestamp: Date.now()
|
|
61805
62664
|
});
|
|
62665
|
+
mirror?.(level, formattedArgs);
|
|
61806
62666
|
};
|
|
61807
62667
|
return { log: capture("log"), warn: capture("warn"), error: capture("error"), info: capture("info") };
|
|
61808
62668
|
}
|
|
@@ -61966,6 +62826,18 @@ function buildFileIndex(allFiles) {
|
|
|
61966
62826
|
}
|
|
61967
62827
|
return fileIndex;
|
|
61968
62828
|
}
|
|
62829
|
+
function hasPathExtension(path4) {
|
|
62830
|
+
const fileName = path4.split("/").pop() ?? path4;
|
|
62831
|
+
return /\.[^/.]+$/.test(fileName);
|
|
62832
|
+
}
|
|
62833
|
+
function explicitExtensionHint(requestedName, resolvedPath, fileIndex) {
|
|
62834
|
+
if (!resolvedPath || hasPathExtension(resolvedPath)) return "";
|
|
62835
|
+
const requestedBase = requestedName.trim();
|
|
62836
|
+
const suggestions = [".forge.js", ".js"].map((ext) => ({ ext, resolved: `${resolvedPath}${ext}` })).filter(({ resolved }) => fileIndex.has(resolved)).map(({ ext }) => `"${requestedBase}${ext}"`);
|
|
62837
|
+
if (suggestions.length === 0) return "";
|
|
62838
|
+
const joined = suggestions.length === 1 ? suggestions[0] : suggestions.join(" or ");
|
|
62839
|
+
return ` Did you mean ${joined}? ForgeCAD requires explicit file extensions in project imports.`;
|
|
62840
|
+
}
|
|
61969
62841
|
function resolveImportSource(fromFile, requestedName, allFiles, options) {
|
|
61970
62842
|
if (typeof requestedName !== "string" || requestedName.trim().length === 0) {
|
|
61971
62843
|
throw new Error("Import path must be a non-empty string");
|
|
@@ -61974,7 +62846,8 @@ function resolveImportSource(fromFile, requestedName, allFiles, options) {
|
|
|
61974
62846
|
const lookupKey = options.fileIndex.get(resolvedPath);
|
|
61975
62847
|
if (!lookupKey) {
|
|
61976
62848
|
const suffix = resolvedPath && resolvedPath !== requestedName ? ` (resolved to "${resolvedPath}" from "${fromFile}")` : ` (from "${fromFile}")`;
|
|
61977
|
-
|
|
62849
|
+
const hint = explicitExtensionHint(requestedName, resolvedPath, options.fileIndex);
|
|
62850
|
+
throw new Error(`File not found: "${requestedName}"${suffix}.${hint}`);
|
|
61978
62851
|
}
|
|
61979
62852
|
const source = allFiles[lookupKey];
|
|
61980
62853
|
if (typeof source !== "string") {
|
|
@@ -62227,6 +63100,36 @@ function extractUnusedTopLevelVarNames(code) {
|
|
|
62227
63100
|
}
|
|
62228
63101
|
return declaredNames.filter((n) => topLevelNames.has(n) && (!usedByOthers.has(n) || explicitImplicitResultNames.has(n)));
|
|
62229
63102
|
}
|
|
63103
|
+
function collectBindingNameLocations(node, sourceFile, names) {
|
|
63104
|
+
if (ts.isIdentifier(node)) {
|
|
63105
|
+
const { line: line2, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
63106
|
+
names.push({ name: node.text, line: line2 + 1, column: character + 1 });
|
|
63107
|
+
return;
|
|
63108
|
+
}
|
|
63109
|
+
for (const element of node.elements) {
|
|
63110
|
+
if (ts.isBindingElement(element)) {
|
|
63111
|
+
collectBindingNameLocations(element.name, sourceFile, names);
|
|
63112
|
+
}
|
|
63113
|
+
}
|
|
63114
|
+
}
|
|
63115
|
+
function findTopLevelRuntimeGlobalCollision(code, runtimeGlobalNames) {
|
|
63116
|
+
const runtimeGlobals = new Set(runtimeGlobalNames);
|
|
63117
|
+
const sourceFile = ts.createSourceFile("__runtime-globals.js", code, ts.ScriptTarget.ES2020, false, ts.ScriptKind.JS);
|
|
63118
|
+
const declarations = [];
|
|
63119
|
+
for (const statement of sourceFile.statements) {
|
|
63120
|
+
if (ts.isVariableStatement(statement)) {
|
|
63121
|
+
const isLexical = (statement.declarationList.flags & (ts.NodeFlags.Let | ts.NodeFlags.Const)) !== 0;
|
|
63122
|
+
if (!isLexical) continue;
|
|
63123
|
+
for (const decl of statement.declarationList.declarations) {
|
|
63124
|
+
collectBindingNameLocations(decl.name, sourceFile, declarations);
|
|
63125
|
+
}
|
|
63126
|
+
} else if (ts.isClassDeclaration(statement) && statement.name) {
|
|
63127
|
+
const { line: line2, character } = sourceFile.getLineAndCharacterOfPosition(statement.name.getStart(sourceFile));
|
|
63128
|
+
declarations.push({ name: statement.name.text, line: line2 + 1, column: character + 1 });
|
|
63129
|
+
}
|
|
63130
|
+
}
|
|
63131
|
+
return declarations.find((declaration) => runtimeGlobals.has(declaration.name)) ?? null;
|
|
63132
|
+
}
|
|
62230
63133
|
function createForgeRuntimeModule(bindings) {
|
|
62231
63134
|
const runtime = { ...bindings };
|
|
62232
63135
|
Object.defineProperty(runtime, "__esModule", { value: true });
|
|
@@ -62768,6 +63671,11 @@ function withConstructorChainLockdown(fn) {
|
|
|
62768
63671
|
}
|
|
62769
63672
|
}
|
|
62770
63673
|
function executeFile(code, fileName, allFiles, visited, scope = {}, options, executionMode = "script", moduleCacheEntry) {
|
|
63674
|
+
options.debug?.("executeFile:start", {
|
|
63675
|
+
fileName,
|
|
63676
|
+
executionMode,
|
|
63677
|
+
scope: scope.namePrefix ?? fileName
|
|
63678
|
+
});
|
|
62771
63679
|
const trackCircularImports = executionMode === "script";
|
|
62772
63680
|
if (trackCircularImports) {
|
|
62773
63681
|
if (visited.has(fileName)) {
|
|
@@ -62879,7 +63787,34 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
|
|
|
62879
63787
|
setShapeTopology(shape, topo);
|
|
62880
63788
|
return shape;
|
|
62881
63789
|
};
|
|
62882
|
-
const sandboxConsole = makeSandboxConsole(_collectedLogs);
|
|
63790
|
+
const sandboxConsole = makeSandboxConsole(_collectedLogs, options.debug ? (level, args) => options.debug?.("console", { level, args }) : void 0);
|
|
63791
|
+
const runtimeVerify = options.debug ? {
|
|
63792
|
+
...verify,
|
|
63793
|
+
that(label, check, message) {
|
|
63794
|
+
options.debug?.("verify:that:start", { label });
|
|
63795
|
+
const verifyStart = performance.now();
|
|
63796
|
+
try {
|
|
63797
|
+
return verify.that(label, check, message);
|
|
63798
|
+
} finally {
|
|
63799
|
+
options.debug?.("verify:that:end", {
|
|
63800
|
+
label,
|
|
63801
|
+
ms: Number((performance.now() - verifyStart).toFixed(1))
|
|
63802
|
+
});
|
|
63803
|
+
}
|
|
63804
|
+
},
|
|
63805
|
+
equal(label, actual, expected, tolerance = 0, message) {
|
|
63806
|
+
options.debug?.("verify:equal:start", { label, actual, expected, tolerance });
|
|
63807
|
+
const verifyStart = performance.now();
|
|
63808
|
+
try {
|
|
63809
|
+
return verify.equal(label, actual, expected, tolerance, message);
|
|
63810
|
+
} finally {
|
|
63811
|
+
options.debug?.("verify:equal:end", {
|
|
63812
|
+
label,
|
|
63813
|
+
ms: Number((performance.now() - verifyStart).toFixed(1))
|
|
63814
|
+
});
|
|
63815
|
+
}
|
|
63816
|
+
}
|
|
63817
|
+
} : verify;
|
|
62883
63818
|
setShowLabelsHighlight(highlight);
|
|
62884
63819
|
const runtimeBindings = {
|
|
62885
63820
|
box: trackedBox,
|
|
@@ -63020,7 +63955,8 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
|
|
|
63020
63955
|
jointsView,
|
|
63021
63956
|
viewConfig,
|
|
63022
63957
|
scene,
|
|
63023
|
-
|
|
63958
|
+
Viewport,
|
|
63959
|
+
verify: runtimeVerify,
|
|
63024
63960
|
spec,
|
|
63025
63961
|
mock,
|
|
63026
63962
|
gcode,
|
|
@@ -63158,8 +64094,21 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
|
|
|
63158
64094
|
throw error;
|
|
63159
64095
|
}
|
|
63160
64096
|
};
|
|
63161
|
-
const compiled = compileScript(code, fileName, options);
|
|
63162
64097
|
const bindingNames = Object.keys(runtimeBindings);
|
|
64098
|
+
const collision = findTopLevelRuntimeGlobalCollision(code, bindingNames);
|
|
64099
|
+
if (collision) {
|
|
64100
|
+
throw new Error(
|
|
64101
|
+
`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}").`
|
|
64102
|
+
);
|
|
64103
|
+
}
|
|
64104
|
+
options.debug?.("executeFile:compile:start", { fileName, executionMode });
|
|
64105
|
+
const compileStart = performance.now();
|
|
64106
|
+
const compiled = compileScript(code, fileName, options);
|
|
64107
|
+
options.debug?.("executeFile:compile:end", {
|
|
64108
|
+
fileName,
|
|
64109
|
+
executionMode,
|
|
64110
|
+
ms: Number((performance.now() - compileStart).toFixed(1))
|
|
64111
|
+
});
|
|
63163
64112
|
const bindingValues = bindingNames.map((name) => runtimeBindings[name]);
|
|
63164
64113
|
let scriptCode = compiled.code;
|
|
63165
64114
|
if (executionMode === "script") {
|
|
@@ -63185,12 +64134,20 @@ ${scriptCode}
|
|
|
63185
64134
|
exports: executionMode === "module" && moduleCacheEntry ? moduleCacheEntry.exports : {}
|
|
63186
64135
|
};
|
|
63187
64136
|
const initialExportsRef = moduleValue.exports;
|
|
64137
|
+
options.debug?.("executeFile:invoke:start", { fileName, executionMode });
|
|
64138
|
+
const invokeStart = performance.now();
|
|
63188
64139
|
const returnValue = withConstructorChainLockdown(
|
|
63189
64140
|
() => runWithParamScope(
|
|
63190
64141
|
scope,
|
|
63191
64142
|
() => fn(moduleValue.exports, moduleValue, requireModule, fileName, dirnamePath(fileName), ...bindingValues)
|
|
63192
64143
|
)
|
|
63193
64144
|
);
|
|
64145
|
+
options.debug?.("executeFile:invoke:end", {
|
|
64146
|
+
fileName,
|
|
64147
|
+
executionMode,
|
|
64148
|
+
ms: Number((performance.now() - invokeStart).toFixed(1)),
|
|
64149
|
+
returned: returnValue === void 0 ? "undefined" : returnValue === null ? "null" : typeof returnValue
|
|
64150
|
+
});
|
|
63194
64151
|
if (executionMode === "module") {
|
|
63195
64152
|
const hasExports = hasExplicitModuleExports(moduleValue.exports, initialExportsRef);
|
|
63196
64153
|
if (returnValue !== void 0 && hasExports) {
|
|
@@ -63229,6 +64186,7 @@ ${scriptCode}
|
|
|
63229
64186
|
}
|
|
63230
64187
|
return returnValue;
|
|
63231
64188
|
} finally {
|
|
64189
|
+
options.debug?.("executeFile:end", { fileName, executionMode });
|
|
63232
64190
|
if (trackCircularImports) {
|
|
63233
64191
|
visited.delete(fileName);
|
|
63234
64192
|
}
|
|
@@ -63243,14 +64201,24 @@ function runScript(code, fileName = "main.forge.js", allFiles = {}, options = {}
|
|
|
63243
64201
|
fileIndex: buildFileIndex(allFiles),
|
|
63244
64202
|
compiledFiles: persistentCompiledFiles,
|
|
63245
64203
|
moduleCache: /* @__PURE__ */ new Map(),
|
|
63246
|
-
readBinaryFile: options.readBinaryFile
|
|
64204
|
+
readBinaryFile: options.readBinaryFile,
|
|
64205
|
+
debug: options.debug
|
|
63247
64206
|
};
|
|
63248
64207
|
const quality = resolveForgeQualityPreset(options.quality);
|
|
64208
|
+
options.debug?.("runScript:start", { fileName, quality, fileCount: Object.keys(allFiles).length });
|
|
63249
64209
|
try {
|
|
63250
64210
|
return runWithForgeQuality(quality, () => {
|
|
64211
|
+
options.debug?.("runScript:execute:start", { fileName });
|
|
64212
|
+
const executeStart = performance.now();
|
|
63251
64213
|
const result = executeFile(code, fileName, allFiles, /* @__PURE__ */ new Set(), {}, execOptions);
|
|
64214
|
+
options.debug?.("runScript:execute:end", {
|
|
64215
|
+
fileName,
|
|
64216
|
+
ms: Number((performance.now() - executeStart).toFixed(1)),
|
|
64217
|
+
resultType: result === void 0 ? "undefined" : result === null ? "null" : typeof result
|
|
64218
|
+
});
|
|
63252
64219
|
const highlights = getCollectedHighlights();
|
|
63253
64220
|
const mocks = getCollectedMocks();
|
|
64221
|
+
options.debug?.("runScript:map:start", { highlights: highlights.length, mocks: mocks.length });
|
|
63254
64222
|
const mapped = mapScriptResultToScene({
|
|
63255
64223
|
result,
|
|
63256
64224
|
fileName,
|
|
@@ -63259,24 +64227,43 @@ function runScript(code, fileName = "main.forge.js", allFiles = {}, options = {}
|
|
|
63259
64227
|
mocks,
|
|
63260
64228
|
logs: _collectedLogs
|
|
63261
64229
|
});
|
|
64230
|
+
options.debug?.("runScript:map:end", {
|
|
64231
|
+
objects: mapped.objects.length,
|
|
64232
|
+
hasShape: Boolean(mapped.shape),
|
|
64233
|
+
hasSketch: Boolean(mapped.sketch),
|
|
64234
|
+
hasError: Boolean(mapped.error)
|
|
64235
|
+
});
|
|
64236
|
+
options.debug?.("runScript:explodeHints:start", { objects: mapped.objects.length });
|
|
63262
64237
|
autoFillExplodeHints(mapped.objects);
|
|
64238
|
+
options.debug?.("runScript:explodeHints:end");
|
|
64239
|
+
options.debug?.("runScript:snapshot:start", { objects: mapped.objects.length });
|
|
64240
|
+
const snapshot = collectSuccessfulExecutionSnapshot({
|
|
64241
|
+
quality,
|
|
64242
|
+
objects: mapped.objects,
|
|
64243
|
+
logs: _collectedLogs,
|
|
64244
|
+
extraDimensions: mapped.extraDimensions,
|
|
64245
|
+
highlights,
|
|
64246
|
+
mocks
|
|
64247
|
+
});
|
|
64248
|
+
options.debug?.("runScript:snapshot:end", {
|
|
64249
|
+
params: snapshot.params.length,
|
|
64250
|
+
cutPlanes: snapshot.cutPlanes.length,
|
|
64251
|
+
verifications: snapshot.verifications.length
|
|
64252
|
+
});
|
|
64253
|
+
options.debug?.("runScript:sceneTargets:start");
|
|
64254
|
+
snapshot.sceneConfig = resolveSceneJourneyTargets(snapshot.sceneConfig, mapped.objects);
|
|
64255
|
+
options.debug?.("runScript:sceneTargets:end");
|
|
63263
64256
|
return {
|
|
63264
64257
|
shape: mapped.shape,
|
|
63265
64258
|
sketch: mapped.sketch,
|
|
63266
64259
|
objects: mapped.objects,
|
|
63267
|
-
...
|
|
63268
|
-
quality,
|
|
63269
|
-
objects: mapped.objects,
|
|
63270
|
-
logs: _collectedLogs,
|
|
63271
|
-
extraDimensions: mapped.extraDimensions,
|
|
63272
|
-
highlights,
|
|
63273
|
-
mocks
|
|
63274
|
-
}),
|
|
64260
|
+
...snapshot,
|
|
63275
64261
|
error: mapped.error,
|
|
63276
64262
|
timeMs: performance.now() - t0
|
|
63277
64263
|
};
|
|
63278
64264
|
});
|
|
63279
64265
|
} catch (e) {
|
|
64266
|
+
options.debug?.("runScript:error", { error: e?.message || String(e) });
|
|
63280
64267
|
const msg = e.message || String(e);
|
|
63281
64268
|
const stack = e.stack || "";
|
|
63282
64269
|
let lineInfo = "";
|
|
@@ -69319,6 +70306,7 @@ Options:
|
|
|
69319
70306
|
With distance: azimuth:elevation:distance (e.g. 45:30:200)
|
|
69320
70307
|
Full spec: proj=perspective;pos=x,y,z;target=x,y,z;up=x,y,z;fov=45
|
|
69321
70308
|
Default: iso
|
|
70309
|
+
--view <name> Use a named camera view declared with scene({ views })
|
|
69322
70310
|
--scene <json> Scene state JSON copied from the viewport
|
|
69323
70311
|
--size <px> Image size in pixels (default: ${DEFAULT_SIZE})
|
|
69324
70312
|
--focus [names] Focus: no arg hides mocks; comma-separated names shows only those
|
|
@@ -69343,6 +70331,7 @@ Examples:
|
|
|
69343
70331
|
forgecad render model.forge.js --camera 45:30 # 45\xB0 azimuth, 30\xB0 elevation
|
|
69344
70332
|
forgecad render model.forge.js --camera 45:30:200 # same, at 200mm distance
|
|
69345
70333
|
forgecad render model.forge.js --camera "proj=perspective;pos=200,-160,120;target=0,0,20;up=0,0,1;fov=38"
|
|
70334
|
+
forgecad render model.forge.js --view hero # camera from scene({ views: { hero: ... } })
|
|
69346
70335
|
forgecad render model.forge.js --camera front --camera side # two views`;
|
|
69347
70336
|
}
|
|
69348
70337
|
function readValue(argv, idx, flag) {
|
|
@@ -69359,6 +70348,15 @@ function parseRenderStyle(value) {
|
|
|
69359
70348
|
if (RENDER_STYLES.has(value)) return value;
|
|
69360
70349
|
throw new Error(`--render-style must be 'classic', 'studio', 'fast', or 'glass' (got '${value}')`);
|
|
69361
70350
|
}
|
|
70351
|
+
function sceneSpecHasCamera(sceneSpec) {
|
|
70352
|
+
let parsed;
|
|
70353
|
+
try {
|
|
70354
|
+
parsed = JSON.parse(sceneSpec);
|
|
70355
|
+
} catch (err2) {
|
|
70356
|
+
throw new Error(`--scene must be valid JSON when used with --view: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
70357
|
+
}
|
|
70358
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) && Object.prototype.hasOwnProperty.call(parsed, "camera");
|
|
70359
|
+
}
|
|
69362
70360
|
function parseFocusArg(argv, idx) {
|
|
69363
70361
|
const next = argv[idx + 1];
|
|
69364
70362
|
if (!next || next.startsWith("--")) {
|
|
@@ -69394,6 +70392,7 @@ function parseRenderCliOptions(argv) {
|
|
|
69394
70392
|
let port = DEFAULT_PORT;
|
|
69395
70393
|
let chromePath = process.env.CHROME_PATH;
|
|
69396
70394
|
let cameraSpec;
|
|
70395
|
+
let viewName;
|
|
69397
70396
|
let sceneSpec;
|
|
69398
70397
|
let background;
|
|
69399
70398
|
let focus = null;
|
|
@@ -69425,6 +70424,18 @@ function parseRenderCliOptions(argv) {
|
|
|
69425
70424
|
i += 1;
|
|
69426
70425
|
continue;
|
|
69427
70426
|
}
|
|
70427
|
+
if (arg === "--view") {
|
|
70428
|
+
if (viewName) {
|
|
70429
|
+
throw new Error("Pass --view only once.");
|
|
70430
|
+
}
|
|
70431
|
+
const val = readValue(argv, i, arg).trim();
|
|
70432
|
+
if (!val) {
|
|
70433
|
+
throw new Error("--view requires a non-empty view name.");
|
|
70434
|
+
}
|
|
70435
|
+
viewName = val;
|
|
70436
|
+
i += 1;
|
|
70437
|
+
continue;
|
|
70438
|
+
}
|
|
69428
70439
|
if (arg === "--angles") {
|
|
69429
70440
|
const vals = readValue(argv, i, arg).split(",").map((s) => s.trim()).filter(Boolean);
|
|
69430
70441
|
cameras.push(...vals);
|
|
@@ -69504,7 +70515,13 @@ function parseRenderCliOptions(argv) {
|
|
|
69504
70515
|
if (!Number.isFinite(port) || port < 1 || port > 65535) {
|
|
69505
70516
|
throw new Error(`--port must be between 1 and 65535 (got ${port})`);
|
|
69506
70517
|
}
|
|
69507
|
-
if (cameras.length
|
|
70518
|
+
if (viewName && (cameras.length > 0 || cameraSpec)) {
|
|
70519
|
+
throw new Error("Cannot use --view with --camera or --angles. Choose either a model-declared view or an explicit camera.");
|
|
70520
|
+
}
|
|
70521
|
+
if (viewName && sceneSpec && sceneSpecHasCamera(sceneSpec)) {
|
|
70522
|
+
throw new Error("Cannot use --view with --scene JSON that includes camera. Remove the camera field from --scene or use --camera.");
|
|
70523
|
+
}
|
|
70524
|
+
if (cameras.length === 0 && !cameraSpec && !sceneSpec && !viewName) {
|
|
69508
70525
|
cameras.push("iso");
|
|
69509
70526
|
}
|
|
69510
70527
|
if (focus && hide) {
|
|
@@ -69518,6 +70535,7 @@ function parseRenderCliOptions(argv) {
|
|
|
69518
70535
|
port,
|
|
69519
70536
|
chromePath: resolveChromePath(chromePath),
|
|
69520
70537
|
cameraSpec,
|
|
70538
|
+
viewName,
|
|
69521
70539
|
sceneSpec,
|
|
69522
70540
|
background,
|
|
69523
70541
|
focus,
|
|
@@ -69668,7 +70686,9 @@ function parseInspectCli(argv) {
|
|
|
69668
70686
|
throw new Error("Missing input .forge.js path");
|
|
69669
70687
|
}
|
|
69670
70688
|
if (!channels) {
|
|
69671
|
-
throw new Error(
|
|
70689
|
+
throw new Error(
|
|
70690
|
+
"Missing required --channels. Choose explicit channels, for example --channels rgb,mask or --channels rgb,mask,collisions."
|
|
70691
|
+
);
|
|
69672
70692
|
}
|
|
69673
70693
|
if (flagOutputDir && positionalOutputDir) {
|
|
69674
70694
|
throw new Error("Pass either positional output-dir or --output, not both.");
|
|
@@ -70279,6 +71299,7 @@ async function runRenderCli(argv = process.argv.slice(2)) {
|
|
|
70279
71299
|
allFiles: files,
|
|
70280
71300
|
fileName: scriptName,
|
|
70281
71301
|
cameraSpec: renderOptions.cameraSpec,
|
|
71302
|
+
viewName: renderOptions.viewName,
|
|
70282
71303
|
sceneSpec: renderOptions.sceneSpec,
|
|
70283
71304
|
background: renderOptions.background,
|
|
70284
71305
|
focus: renderOptions.focus,
|
|
@@ -70295,6 +71316,7 @@ async function runRenderCli(argv = process.argv.slice(2)) {
|
|
|
70295
71316
|
cameras: options.cameras,
|
|
70296
71317
|
size: options.size,
|
|
70297
71318
|
cameraSpec: options.cameraSpec || null,
|
|
71319
|
+
viewName: options.viewName || null,
|
|
70298
71320
|
sceneSpec: options.sceneSpec || null,
|
|
70299
71321
|
background: options.background || null,
|
|
70300
71322
|
focus: options.focus || null,
|
|
@@ -74816,7 +75838,7 @@ function inBounds(p2, min2, max3, pad = 1e-4) {
|
|
|
74816
75838
|
function clamp13(v, lo, hi) {
|
|
74817
75839
|
return Math.max(lo, Math.min(hi, v));
|
|
74818
75840
|
}
|
|
74819
|
-
function
|
|
75841
|
+
function dot8(a, b) {
|
|
74820
75842
|
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
|
74821
75843
|
}
|
|
74822
75844
|
function cross6(a, b) {
|
|
@@ -74830,7 +75852,7 @@ function norm4(v) {
|
|
|
74830
75852
|
function mul(v, s) {
|
|
74831
75853
|
return [v[0] * s, v[1] * s, v[2] * s];
|
|
74832
75854
|
}
|
|
74833
|
-
function
|
|
75855
|
+
function sub8(a, b) {
|
|
74834
75856
|
return [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
|
|
74835
75857
|
}
|
|
74836
75858
|
function makeViewFrame2(view) {
|
|
@@ -74847,12 +75869,12 @@ function makeViewFrame2(view) {
|
|
|
74847
75869
|
return { id: view, right, up, forward };
|
|
74848
75870
|
}
|
|
74849
75871
|
function isDimensionVisibleInView2(dim2, frame, toleranceDeg) {
|
|
74850
|
-
const dir =
|
|
75872
|
+
const dir = sub8(dim2.to, dim2.from);
|
|
74851
75873
|
const len2 = Math.hypot(dir[0], dir[1], dir[2]);
|
|
74852
75874
|
if (len2 < 1e-9) return false;
|
|
74853
75875
|
const d = [dir[0] / len2, dir[1] / len2, dir[2] / len2];
|
|
74854
|
-
const alignRight = clamp13(Math.abs(
|
|
74855
|
-
const alignUp = clamp13(Math.abs(
|
|
75876
|
+
const alignRight = clamp13(Math.abs(dot8(d, frame.right)), 0, 1);
|
|
75877
|
+
const alignUp = clamp13(Math.abs(dot8(d, frame.up)), 0, 1);
|
|
74856
75878
|
const angleRight = Math.acos(alignRight) * 180 / Math.PI;
|
|
74857
75879
|
const angleUp = Math.acos(alignUp) * 180 / Math.PI;
|
|
74858
75880
|
const minAngle = Math.min(angleRight, angleUp);
|
|
@@ -75367,6 +76389,9 @@ var DEFAULTS = {
|
|
|
75367
76389
|
crf: parseIntEnv(["FORGE_CAPTURE_CRF"], 18),
|
|
75368
76390
|
port: parseIntEnv(["FORGE_PORT"], 5173)
|
|
75369
76391
|
};
|
|
76392
|
+
var PUPPETEER_PROTOCOL_TIMEOUT_MS2 = parseIntEnv(["FORGE_CAPTURE_PROTOCOL_TIMEOUT_MS"], 10 * 60 * 1e3);
|
|
76393
|
+
var CAPTURE_DEBUG = parseBooleanEnv(["FORGE_CAPTURE_DEBUG", "FORGE_CAPTURE_TRACE"], false);
|
|
76394
|
+
var CAPTURE_DEBUG_ALL_BROWSER = parseBooleanEnv(["FORGE_CAPTURE_DEBUG_ALL_BROWSER"], false);
|
|
75370
76395
|
var repoRoot = packageRootFrom(import.meta.url);
|
|
75371
76396
|
function parseIntEnv(names, fallback) {
|
|
75372
76397
|
for (const name of names) {
|
|
@@ -75395,10 +76420,24 @@ function parseOptionalFloatEnv(names) {
|
|
|
75395
76420
|
}
|
|
75396
76421
|
return void 0;
|
|
75397
76422
|
}
|
|
76423
|
+
function parseBooleanEnv(names, fallback) {
|
|
76424
|
+
for (const name of names) {
|
|
76425
|
+
const raw = process.env[name];
|
|
76426
|
+
if (raw == null) continue;
|
|
76427
|
+
const normalized = raw.trim().toLowerCase();
|
|
76428
|
+
if (["1", "true", "yes", "on"].includes(normalized)) return true;
|
|
76429
|
+
if (["0", "false", "no", "off"].includes(normalized)) return false;
|
|
76430
|
+
}
|
|
76431
|
+
return fallback;
|
|
76432
|
+
}
|
|
75398
76433
|
function parseQualityEnv(raw) {
|
|
75399
76434
|
if (raw === "default" || raw === "live" || raw === "high") return raw;
|
|
75400
76435
|
return null;
|
|
75401
76436
|
}
|
|
76437
|
+
function debugLog(message) {
|
|
76438
|
+
if (!CAPTURE_DEBUG) return;
|
|
76439
|
+
console.log(`[forge-capture:debug] ${message}`);
|
|
76440
|
+
}
|
|
75402
76441
|
function parseSweepPlaneEnv(raw) {
|
|
75403
76442
|
if (!raw) return null;
|
|
75404
76443
|
const normalized = raw.toUpperCase();
|
|
@@ -76355,7 +77394,8 @@ async function captureAndEncode(page, options, framePlan, encoderMode, ffmpegPat
|
|
|
76355
77394
|
imageFormat: ffmpegInputMode === "jpeg" ? "jpeg" : "png",
|
|
76356
77395
|
jpegQuality: 0.9,
|
|
76357
77396
|
showEdges: process.env.FORGE_CAPTURE_SHOW_EDGES !== "0",
|
|
76358
|
-
profile
|
|
77397
|
+
profile,
|
|
77398
|
+
debug: CAPTURE_DEBUG
|
|
76359
77399
|
}
|
|
76360
77400
|
);
|
|
76361
77401
|
timings.browserFrameMs += Date.now() - browserStart;
|
|
@@ -76580,10 +77620,26 @@ async function runCaptureCli(config, argv = process.argv.slice(2)) {
|
|
|
76580
77620
|
}
|
|
76581
77621
|
browser = await puppeteer2.launch({
|
|
76582
77622
|
headless: true,
|
|
77623
|
+
protocolTimeout: PUPPETEER_PROTOCOL_TIMEOUT_MS2,
|
|
76583
77624
|
executablePath: chromePath,
|
|
76584
77625
|
args: chromeArgs
|
|
76585
77626
|
});
|
|
76586
77627
|
const page = await browser.newPage();
|
|
77628
|
+
if (CAPTURE_DEBUG) {
|
|
77629
|
+
page.on("console", (message) => {
|
|
77630
|
+
const text = message.text();
|
|
77631
|
+
if (text.includes("[forge-capture:debug]") || CAPTURE_DEBUG_ALL_BROWSER) {
|
|
77632
|
+
console.log(text.includes("[forge-capture:debug]") ? text : `[browser:${message.type()}] ${text}`);
|
|
77633
|
+
}
|
|
77634
|
+
});
|
|
77635
|
+
page.on("pageerror", (error) => {
|
|
77636
|
+
console.log(`[forge-capture:debug] browser page error: ${error.message}`);
|
|
77637
|
+
});
|
|
77638
|
+
page.on("requestfailed", (request) => {
|
|
77639
|
+
console.log(`[forge-capture:debug] browser request failed: ${request.url()} ${request.failure()?.errorText ?? ""}`.trim());
|
|
77640
|
+
});
|
|
77641
|
+
}
|
|
77642
|
+
debugLog(`loading capture runtime on :${activePort}`);
|
|
76587
77643
|
let captureRuntimeReady = await loadCaptureRuntime(page, activePort, options.capture);
|
|
76588
77644
|
if (!captureRuntimeReady && !viteProc) {
|
|
76589
77645
|
const fallbackPort = await findFreePort(activePort + 1);
|
|
@@ -76597,6 +77653,8 @@ async function runCaptureCli(config, argv = process.argv.slice(2)) {
|
|
|
76597
77653
|
if (!captureRuntimeReady) {
|
|
76598
77654
|
throw new Error("Capture runtime did not initialize. Restart the Forge dev server and try again.");
|
|
76599
77655
|
}
|
|
77656
|
+
debugLog(`capture runtime ready; starting browser init for ${basename4(input)}`);
|
|
77657
|
+
const initStart = Date.now();
|
|
76600
77658
|
const init2 = await page.evaluate(
|
|
76601
77659
|
(payload) => {
|
|
76602
77660
|
return window.__forgeCaptureInit(payload.code, payload.options);
|
|
@@ -76617,6 +77675,7 @@ async function runCaptureCli(config, argv = process.argv.slice(2)) {
|
|
|
76617
77675
|
paramOverrides: options.paramOverrides,
|
|
76618
77676
|
animationName: options.animationName ?? null,
|
|
76619
77677
|
capture: options.capture,
|
|
77678
|
+
debug: CAPTURE_DEBUG,
|
|
76620
77679
|
sweep: options.capture === "section-sweep" ? {
|
|
76621
77680
|
plane: options.sweepPlane,
|
|
76622
77681
|
normal: options.sweepNormal ?? null,
|
|
@@ -76629,6 +77688,7 @@ async function runCaptureCli(config, argv = process.argv.slice(2)) {
|
|
|
76629
77688
|
}
|
|
76630
77689
|
}
|
|
76631
77690
|
);
|
|
77691
|
+
debugLog(`browser init returned in ${Date.now() - initStart}ms`);
|
|
76632
77692
|
if (!init2?.ok) {
|
|
76633
77693
|
throw new Error(init2?.error || "Script failed to initialize in renderer");
|
|
76634
77694
|
}
|
|
@@ -82355,29 +83415,39 @@ import { homedir as homedir7 } from "os";
|
|
|
82355
83415
|
import { join as join12 } from "path";
|
|
82356
83416
|
var VALID_TIERS = /* @__PURE__ */ new Set(["free", "pro", "team"]);
|
|
82357
83417
|
var PRO_COMMANDS = /* @__PURE__ */ new Set([
|
|
82358
|
-
// Exact CAD interchange formats
|
|
82359
|
-
"export step",
|
|
82360
|
-
"export brep",
|
|
82361
83418
|
// Advanced rendering
|
|
82362
83419
|
"render hq",
|
|
82363
83420
|
// Animations
|
|
82364
83421
|
"capture gif",
|
|
82365
|
-
"capture mp4"
|
|
82366
|
-
// Manufacturing outputs
|
|
82367
|
-
"cut-list",
|
|
82368
|
-
"export gcode",
|
|
82369
|
-
"export report",
|
|
82370
|
-
"export cutting-layout",
|
|
82371
|
-
// Robotics exports
|
|
82372
|
-
"export sdf",
|
|
82373
|
-
"export urdf",
|
|
82374
|
-
// Sketch PDF export
|
|
82375
|
-
"export sketch-pdf"
|
|
83422
|
+
"capture mp4"
|
|
82376
83423
|
]);
|
|
82377
83424
|
function requiredTierForCommand(commandPath) {
|
|
82378
83425
|
const key = commandPath.join(" ");
|
|
82379
83426
|
return PRO_COMMANDS.has(key) ? "pro" : "free";
|
|
82380
83427
|
}
|
|
83428
|
+
var PRODUCTION_OUTPUT_COMMANDS = /* @__PURE__ */ new Map([
|
|
83429
|
+
["cut-list", "sheet cut list"],
|
|
83430
|
+
["export sketch-pdf", "dimensioned sketch PDF"],
|
|
83431
|
+
["export step", "STEP exact CAD exchange"],
|
|
83432
|
+
["export brep", "BREP exact geometry"],
|
|
83433
|
+
["export gcode", "G-code toolpath"],
|
|
83434
|
+
["export sdf", "SDF robotics package"],
|
|
83435
|
+
["export urdf", "URDF robotics package"],
|
|
83436
|
+
["export report", "model report PDF"],
|
|
83437
|
+
["export cutting-layout", "sheet cutting layout PDF"]
|
|
83438
|
+
]);
|
|
83439
|
+
function productionOutputNoticeForCommand(commandPath) {
|
|
83440
|
+
const key = commandPath.join(" ");
|
|
83441
|
+
const label = PRODUCTION_OUTPUT_COMMANDS.get(key);
|
|
83442
|
+
if (!label) return null;
|
|
83443
|
+
return {
|
|
83444
|
+
label,
|
|
83445
|
+
message: "Free to run. For client, employer, sale, funded product, or closed commercial IP work, ForgeCAD Pro covers commercial use and support."
|
|
83446
|
+
};
|
|
83447
|
+
}
|
|
83448
|
+
function productionOutputBadgeForCommand(commandPath) {
|
|
83449
|
+
return productionOutputNoticeForCommand(commandPath) ? "Production" : null;
|
|
83450
|
+
}
|
|
82381
83451
|
function tierSatisfies(userTier, required) {
|
|
82382
83452
|
if (required === "free") return true;
|
|
82383
83453
|
return userTier === "pro" || userTier === "team";
|
|
@@ -82645,6 +83715,17 @@ function enforceFeatureGate(commandPath) {
|
|
|
82645
83715
|
}
|
|
82646
83716
|
process.exit(1);
|
|
82647
83717
|
}
|
|
83718
|
+
function printProductionOutputNotice(commandPath) {
|
|
83719
|
+
const notice = productionOutputNoticeForCommand(commandPath);
|
|
83720
|
+
if (!notice) return;
|
|
83721
|
+
const license = getCurrentLicense();
|
|
83722
|
+
if (license.valid && tierSatisfies(license.tier, "pro")) return;
|
|
83723
|
+
const commandName = `forgecad ${commandPath.join(" ")}`;
|
|
83724
|
+
console.error(`\x1B[36mProduction output:\x1B[0m ${commandName} creates a ${notice.label}.`);
|
|
83725
|
+
console.error(` ${notice.message}`);
|
|
83726
|
+
console.error(" Upgrade: https://forgecad.io/pricing");
|
|
83727
|
+
console.error("");
|
|
83728
|
+
}
|
|
82648
83729
|
|
|
82649
83730
|
// cli/forge-project.ts
|
|
82650
83731
|
function slugify(name) {
|
|
@@ -85292,6 +86373,64 @@ async function writeSolverDebugIndex(root, scriptPath, bundles) {
|
|
|
85292
86373
|
}
|
|
85293
86374
|
|
|
85294
86375
|
// cli/test-run.ts
|
|
86376
|
+
var DEFAULT_EXACT_SPATIAL_OBJECT_LIMIT = 250;
|
|
86377
|
+
function parseSpatialMode(argv) {
|
|
86378
|
+
const consumed = /* @__PURE__ */ new Set();
|
|
86379
|
+
const idx = argv.indexOf("--spatial");
|
|
86380
|
+
if (idx === -1) return { mode: "bounded", consumed };
|
|
86381
|
+
consumed.add(idx);
|
|
86382
|
+
const raw = argv[idx + 1];
|
|
86383
|
+
if (!raw || raw.startsWith("-")) {
|
|
86384
|
+
console.error("Missing value for --spatial. Expected bounded, exact, or off.");
|
|
86385
|
+
process.exit(1);
|
|
86386
|
+
}
|
|
86387
|
+
consumed.add(idx + 1);
|
|
86388
|
+
if (raw === "bounded" || raw === "exact" || raw === "off") return { mode: raw, consumed };
|
|
86389
|
+
console.error(`Invalid --spatial value: ${raw}. Expected bounded, exact, or off.`);
|
|
86390
|
+
process.exit(1);
|
|
86391
|
+
}
|
|
86392
|
+
function buildJourneyInspection(journeys) {
|
|
86393
|
+
return {
|
|
86394
|
+
journeys: Object.entries(journeys ?? {}).map(([id, journey]) => ({
|
|
86395
|
+
id,
|
|
86396
|
+
title: journey.title ?? id,
|
|
86397
|
+
startsAt: journey.startsAt ?? journey.steps[0]?.id ?? null,
|
|
86398
|
+
behavior: journey.behavior ?? "opt-in",
|
|
86399
|
+
valid: journey.valid !== false,
|
|
86400
|
+
diagnostics: journey.diagnostics ?? [],
|
|
86401
|
+
steps: journey.steps.map((step) => ({
|
|
86402
|
+
id: step.id,
|
|
86403
|
+
title: step.title ?? step.id,
|
|
86404
|
+
focus: step.focus ?? null,
|
|
86405
|
+
resolvedFocusId: step.resolvedFocusId ?? null,
|
|
86406
|
+
resolvedFocusPath: step.resolvedFocusPath ?? null,
|
|
86407
|
+
caption: step.caption ?? null,
|
|
86408
|
+
camera: step.camera ?? null,
|
|
86409
|
+
diagnostics: step.diagnostics ?? []
|
|
86410
|
+
}))
|
|
86411
|
+
}))
|
|
86412
|
+
};
|
|
86413
|
+
}
|
|
86414
|
+
function printJourneySummary(journeys) {
|
|
86415
|
+
const inspected = buildJourneyInspection(journeys).journeys;
|
|
86416
|
+
console.log(`\u2713 Journeys: ${inspected.length}`);
|
|
86417
|
+
for (const journey of inspected) {
|
|
86418
|
+
const status = journey.valid ? "ok" : "has errors";
|
|
86419
|
+
console.log(` ${journey.id}: ${journey.title} (${journey.steps.length} step${journey.steps.length === 1 ? "" : "s"}, ${status})`);
|
|
86420
|
+
for (const diagnostic of journey.diagnostics) {
|
|
86421
|
+
const icon = diagnostic.level === "error" ? "\u2717" : "!";
|
|
86422
|
+
const stepTag = diagnostic.stepId ? `${diagnostic.stepId}: ` : "";
|
|
86423
|
+
console.log(` ${icon} ${stepTag}${diagnostic.message}`);
|
|
86424
|
+
if (diagnostic.suggestions && diagnostic.suggestions.length > 0) {
|
|
86425
|
+
console.log(` suggestions: ${diagnostic.suggestions.join(", ")}`);
|
|
86426
|
+
}
|
|
86427
|
+
}
|
|
86428
|
+
for (const [index, step] of journey.steps.entries()) {
|
|
86429
|
+
const target = step.resolvedFocusPath ?? step.focus ?? (step.camera ? "camera" : "no target");
|
|
86430
|
+
console.log(` ${index + 1}. ${step.id} -> ${target}`);
|
|
86431
|
+
}
|
|
86432
|
+
}
|
|
86433
|
+
}
|
|
85295
86434
|
function tallyFeatures(plan) {
|
|
85296
86435
|
const counts = /* @__PURE__ */ new Map();
|
|
85297
86436
|
function inc(key) {
|
|
@@ -85390,7 +86529,7 @@ function formatFeatureSummary(counts) {
|
|
|
85390
86529
|
function bboxOverlap2(a, b) {
|
|
85391
86530
|
return [0, 1, 2].every((k) => a.min[k] < b.max[k] + 0.1 && a.max[k] > b.min[k] - 0.1);
|
|
85392
86531
|
}
|
|
85393
|
-
function analyzeSpatial(entries) {
|
|
86532
|
+
function analyzeSpatial(entries, mode) {
|
|
85394
86533
|
const lines = [];
|
|
85395
86534
|
const _axisLabel = ["X", "Y", "Z"];
|
|
85396
86535
|
const dirLabels = {
|
|
@@ -85408,12 +86547,20 @@ function analyzeSpatial(entries) {
|
|
|
85408
86547
|
}
|
|
85409
86548
|
const sceneSize = Math.max(...allMax.map((v, i) => v - allMin[i]));
|
|
85410
86549
|
const proximityThreshold = sceneSize * 0.15;
|
|
86550
|
+
const exactCollisionChecksEnabled = mode === "exact" || entries.length <= DEFAULT_EXACT_SPATIAL_OBJECT_LIMIT;
|
|
86551
|
+
let bboxOverlapPairs = 0;
|
|
86552
|
+
let skippedExactCollisionPairs = 0;
|
|
85411
86553
|
for (let i = 0; i < entries.length; i++) {
|
|
85412
86554
|
for (let j = i + 1; j < entries.length; j++) {
|
|
85413
86555
|
const a = entries[i], b = entries[j];
|
|
85414
86556
|
if (a.groupName && a.groupName === b.groupName) continue;
|
|
85415
86557
|
if (a.mock && b.mock) continue;
|
|
85416
86558
|
if (!bboxOverlap2(a, b)) continue;
|
|
86559
|
+
bboxOverlapPairs++;
|
|
86560
|
+
if (!exactCollisionChecksEnabled) {
|
|
86561
|
+
skippedExactCollisionPairs++;
|
|
86562
|
+
continue;
|
|
86563
|
+
}
|
|
85417
86564
|
try {
|
|
85418
86565
|
const hit = a.shape.intersect(b.shape);
|
|
85419
86566
|
if (!hit.isEmpty()) {
|
|
@@ -85426,6 +86573,13 @@ function analyzeSpatial(entries) {
|
|
|
85426
86573
|
}
|
|
85427
86574
|
}
|
|
85428
86575
|
}
|
|
86576
|
+
if (skippedExactCollisionPairs > 0) {
|
|
86577
|
+
lines.push(
|
|
86578
|
+
` Exact collision checks skipped for ${skippedExactCollisionPairs} bbox-overlapping pair(s) in this ${entries.length}-object scene. Re-run with --spatial exact for exhaustive pairwise intersections.`
|
|
86579
|
+
);
|
|
86580
|
+
} else if (bboxOverlapPairs > 0 && exactCollisionChecksEnabled) {
|
|
86581
|
+
lines.push(` Exact collision checks: ${bboxOverlapPairs} bbox-overlapping pair(s) tested.`);
|
|
86582
|
+
}
|
|
85429
86583
|
for (let i = 0; i < entries.length; i++) {
|
|
85430
86584
|
const a = entries[i];
|
|
85431
86585
|
const nearest = Array.from({ length: 6 }, () => ({ idx: -1, gap: Infinity }));
|
|
@@ -85538,7 +86692,7 @@ function parseParamFlags(argv) {
|
|
|
85538
86692
|
}
|
|
85539
86693
|
function usage15() {
|
|
85540
86694
|
console.error(
|
|
85541
|
-
"Usage: forgecad run <script.forge.js> [--connectivity] [--connectivity-tolerance <mm>] [--param Key=Value] [--debug-imports] [--verbose] [--backend manifold|occt] [--solver-debug-out <dir>]"
|
|
86695
|
+
"Usage: forgecad run <script.forge.js> [--connectivity] [--connectivity-tolerance <mm>] [--spatial bounded|exact|off] [--param Key=Value] [--debug-imports] [--verbose] [--backend manifold|occt] [--solver-debug-out <dir>]"
|
|
85542
86696
|
);
|
|
85543
86697
|
process.exit(1);
|
|
85544
86698
|
}
|
|
@@ -85648,11 +86802,14 @@ async function runScriptCli(argv = process.argv.slice(2)) {
|
|
|
85648
86802
|
consumed: connectivityConsumed
|
|
85649
86803
|
} = parsePhysicalConnectivityFlags(argv);
|
|
85650
86804
|
const debugImports = argv.includes("--debug-imports");
|
|
86805
|
+
const printJourneys = argv.includes("--journeys");
|
|
86806
|
+
const printJourneysJson = argv.includes("--journeys-json");
|
|
85651
86807
|
const verbose = argv.includes("--verbose") || argv.includes("-v");
|
|
85652
86808
|
const backend = parseBackendArg(argv);
|
|
85653
86809
|
const solverDebugOut = parseRequiredArg(argv, "--solver-debug-out");
|
|
86810
|
+
const { mode: spatialMode, consumed: spatialConsumed } = parseSpatialMode(argv);
|
|
85654
86811
|
const positional = argv.filter(
|
|
85655
|
-
(arg, i) => !paramConsumed.has(i) && !focusConsumed.has(i) && !connectivityConsumed.has(i) && arg !== "--debug-imports" && arg !== "--backend" && argv[i - 1] !== "--backend" && arg !== "--solver-debug-out" && argv[i - 1] !== "--solver-debug-out"
|
|
86812
|
+
(arg, i) => !paramConsumed.has(i) && !focusConsumed.has(i) && !connectivityConsumed.has(i) && !spatialConsumed.has(i) && arg !== "--debug-imports" && arg !== "--journeys" && arg !== "--journeys-json" && arg !== "--backend" && argv[i - 1] !== "--backend" && arg !== "--solver-debug-out" && argv[i - 1] !== "--solver-debug-out"
|
|
85656
86813
|
);
|
|
85657
86814
|
const scriptPath = positional[0];
|
|
85658
86815
|
if (!scriptPath) usage15();
|
|
@@ -85681,6 +86838,10 @@ async function runScriptCli(argv = process.argv.slice(2)) {
|
|
|
85681
86838
|
}
|
|
85682
86839
|
process.exit(1);
|
|
85683
86840
|
}
|
|
86841
|
+
if (printJourneysJson) {
|
|
86842
|
+
console.log(JSON.stringify(buildJourneyInspection(result.sceneConfig?.journeys), null, 2));
|
|
86843
|
+
return;
|
|
86844
|
+
}
|
|
85684
86845
|
const { visible: visibleObjects, summary: focusSummary } = applyFocusFilter(result.objects, focusFilter);
|
|
85685
86846
|
let totalBodies = 0;
|
|
85686
86847
|
for (const obj of visibleObjects) {
|
|
@@ -85741,6 +86902,10 @@ async function runScriptCli(argv = process.argv.slice(2)) {
|
|
|
85741
86902
|
}
|
|
85742
86903
|
}
|
|
85743
86904
|
}
|
|
86905
|
+
if (printJourneys) {
|
|
86906
|
+
console.log();
|
|
86907
|
+
printJourneySummary(result.sceneConfig?.journeys);
|
|
86908
|
+
}
|
|
85744
86909
|
for (const obj of result.objects) {
|
|
85745
86910
|
if (!obj.shape) continue;
|
|
85746
86911
|
const plan = getShapeCompilePlan(obj.shape);
|
|
@@ -85826,14 +86991,17 @@ async function runScriptCli(argv = process.argv.slice(2)) {
|
|
|
85826
86991
|
const connectivity = analyzePhysicalConnectivity2(entries, connectivityOptions);
|
|
85827
86992
|
for (const line2 of formatPhysicalConnectivity(connectivity)) console.log(line2);
|
|
85828
86993
|
}
|
|
85829
|
-
if (entries.length > 1) {
|
|
86994
|
+
if (entries.length > 1 && spatialMode !== "off") {
|
|
85830
86995
|
console.log(`
|
|
85831
86996
|
\u2713 Spatial analysis:`);
|
|
85832
|
-
const spatialLines = analyzeSpatial(entries);
|
|
86997
|
+
const spatialLines = analyzeSpatial(entries, spatialMode);
|
|
85833
86998
|
for (const line2 of spatialLines) console.log(line2);
|
|
85834
86999
|
if (spatialLines.length === 0) {
|
|
85835
87000
|
console.log(` (no collisions, all objects well-separated)`);
|
|
85836
87001
|
}
|
|
87002
|
+
} else if (entries.length > 1 && spatialMode === "off") {
|
|
87003
|
+
console.log(`
|
|
87004
|
+
\u2713 Spatial analysis: skipped (--spatial off)`);
|
|
85837
87005
|
}
|
|
85838
87006
|
console.log(
|
|
85839
87007
|
`\u2713 Params: ${result.params.map((p2) => {
|
|
@@ -86300,6 +87468,12 @@ var RENDER_OPTIONS = [
|
|
|
86300
87468
|
valueLabel: "<front|back|side|right|top|iso|az:el|az:el:dist|spec>",
|
|
86301
87469
|
values: RENDER_ANGLE_VALUES
|
|
86302
87470
|
},
|
|
87471
|
+
{
|
|
87472
|
+
name: "--view",
|
|
87473
|
+
description: "Named camera view declared by the model with scene({ views })",
|
|
87474
|
+
argument: "required",
|
|
87475
|
+
valueLabel: "<name>"
|
|
87476
|
+
},
|
|
86303
87477
|
{ name: "--size", description: "Image size in pixels", argument: "required", valueLabel: "<px>" },
|
|
86304
87478
|
{ name: "--scene", description: "Viewport scene state JSON", argument: "required", valueLabel: "<json>" },
|
|
86305
87479
|
{ name: "--background", description: "Canvas background override", argument: "required", valueLabel: "<color>" },
|
|
@@ -86472,27 +87646,27 @@ var CAPTURE_COMMON_OPTIONS = [
|
|
|
86472
87646
|
var commands = [
|
|
86473
87647
|
{
|
|
86474
87648
|
group: "Studio",
|
|
86475
|
-
path: ["
|
|
86476
|
-
summary: "
|
|
86477
|
-
usage: ["forgecad
|
|
86478
|
-
examples: ["forgecad
|
|
87649
|
+
path: ["studio"],
|
|
87650
|
+
summary: "Open the installed local editor around one or more project folders.",
|
|
87651
|
+
usage: ["forgecad studio <project-path> [project-path ...]"],
|
|
87652
|
+
examples: ["forgecad studio ~/cad/gearbox", "forgecad studio ~/cad/gearbox ~/cad/fixture", "forgecad studio ~/cad/gearbox --port 4173"],
|
|
86479
87653
|
completion: {
|
|
86480
87654
|
options: STUDIO_OPTIONS,
|
|
86481
87655
|
positionals: [{ description: "project path", valueKind: "directory", repeatable: true }]
|
|
86482
87656
|
},
|
|
86483
|
-
run:
|
|
87657
|
+
run: runStudioCli
|
|
86484
87658
|
},
|
|
86485
87659
|
{
|
|
86486
87660
|
group: "Studio",
|
|
86487
|
-
path: ["
|
|
86488
|
-
summary: "
|
|
86489
|
-
usage: ["forgecad
|
|
86490
|
-
examples: ["forgecad
|
|
87661
|
+
path: ["dev"],
|
|
87662
|
+
summary: "Start the Vite dev server for ForgeCAD source development.",
|
|
87663
|
+
usage: ["forgecad dev <project-path> [project-path ...]"],
|
|
87664
|
+
examples: ["forgecad dev ~/cad/gearbox", "forgecad dev ~/cad/gearbox ~/cad/fixture", "forgecad dev ~/cad/gearbox --port 4173"],
|
|
86491
87665
|
completion: {
|
|
86492
87666
|
options: STUDIO_OPTIONS,
|
|
86493
87667
|
positionals: [{ description: "project path", valueKind: "directory", repeatable: true }]
|
|
86494
87668
|
},
|
|
86495
|
-
run:
|
|
87669
|
+
run: runDevCli
|
|
86496
87670
|
},
|
|
86497
87671
|
{
|
|
86498
87672
|
group: "Studio",
|
|
@@ -86606,9 +87780,9 @@ var commands = [
|
|
|
86606
87780
|
group: "Modeling",
|
|
86607
87781
|
path: ["run"],
|
|
86608
87782
|
summary: "Execute a Forge script and print full geometry diagnostics \u2014 object summary, collision detection, spatial analysis, verification results, and solver profiling.",
|
|
86609
|
-
description: "The primary validation command. Runs your script with the real geometry kernel (no browser needed) and prints a comprehensive report:\n\n**Object summary** \u2014 lists every named shape with its volume, bounding box, and body count. For constrained sketches, shows solver status (FULLY / UNDER / OVER constrained), DOF, and error residuals. Problematic constraints (conflicting, redundant, or high-residual) are flagged individually.\n\n**Construction history** \u2014 shows the build sequence for each shape (primitives, operations, modifications) so you can verify the modeling intent.\n\n**Feature summary** \u2014 tallies geometry features across all objects (e.g. `3 extrude, 2 fillet, 1 chamfer`).\n\n**Verification results** \u2014 runs any `verify.*` checks in the script and reports pass/fail with expected vs actual values.\n\n**Automatic collision detection** \u2014 performs an all-pairs collision check on every named shape. For each pair whose bounding boxes overlap, computes the boolean intersection and reports overlap above 0.1 mm\xB3:\n\n```\n\u26A0 COLLISION: bolt \u2229 base (shared vol: 42.3mm\xB3)\n```\n\nIntra-group pairs (same assembly group) and mock-to-mock pairs are skipped. If a part passes through a boolean-subtracted hole, no collision is reported \u2014 the material is gone.\n\n**Spatial analysis** \u2014 reports directional relationships and gap distances between nearby objects (e.g. `bracket is ABOVE base (gap: 5mm)`).
|
|
87783
|
+
description: "The primary validation command. Runs your script with the real geometry kernel (no browser needed) and prints a comprehensive report:\n\n**Object summary** \u2014 lists every named shape with its volume, bounding box, and body count. For constrained sketches, shows solver status (FULLY / UNDER / OVER constrained), DOF, and error residuals. Problematic constraints (conflicting, redundant, or high-residual) are flagged individually.\n\n**Construction history** \u2014 shows the build sequence for each shape (primitives, operations, modifications) so you can verify the modeling intent.\n\n**Feature summary** \u2014 tallies geometry features across all objects (e.g. `3 extrude, 2 fillet, 1 chamfer`).\n\n**Verification results** \u2014 runs any `verify.*` checks in the script and reports pass/fail with expected vs actual values.\n\n**Automatic collision detection** \u2014 performs an all-pairs collision check on every named shape. For each pair whose bounding boxes overlap, computes the boolean intersection and reports overlap above 0.1 mm\xB3:\n\n```\n\u26A0 COLLISION: bolt \u2229 base (shared vol: 42.3mm\xB3)\n```\n\nIntra-group pairs (same assembly group) and mock-to-mock pairs are skipped. If a part passes through a boolean-subtracted hole, no collision is reported \u2014 the material is gone.\n\n**Spatial analysis** \u2014 reports directional relationships and gap distances between nearby objects (e.g. `bracket is ABOVE base (gap: 5mm)`). Exact pairwise collision intersections run by default only for bounded scenes; use `--spatial exact` for exhaustive collision checks or `--spatial off` to skip this section.\n\n**Physical connectivity** \u2014 pass `--connectivity` to list physically connected components across visible objects. Overlapping or touching bboxes are joined within `--connectivity-tolerance` (default `0.05` model units); use collision inspection for exact positive-volume overlaps. This helps answer whether the model is one continuous assembly or several separate islands.\n\n**Parameters** \u2014 lists all declared parameters with their current values. Overridden values are marked with `*`.\n\n**Solver profiling** \u2014 when constraint solving occurs, shows timing breakdown (clone, solve, redundancy detection, surface building) and solver internals.",
|
|
86610
87784
|
usage: [
|
|
86611
|
-
"forgecad run <script.forge.js> [--connectivity] [--connectivity-tolerance <mm>] [--focus [names]] [--hide names] [--param Key=Value] [--debug-imports] [--backend manifold|occt] [--solver-debug-out <dir>]"
|
|
87785
|
+
"forgecad run <script.forge.js> [--connectivity] [--connectivity-tolerance <mm>] [--spatial bounded|exact|off] [--focus [names]] [--hide names] [--journeys] [--journeys-json] [--param Key=Value] [--debug-imports] [--backend manifold|occt] [--solver-debug-out <dir>]"
|
|
86612
87786
|
],
|
|
86613
87787
|
examples: [
|
|
86614
87788
|
"forgecad run examples/cup.forge.js",
|
|
@@ -86616,6 +87790,7 @@ var commands = [
|
|
|
86616
87790
|
"forgecad run examples/cup.forge.js --focus bracket,hinge",
|
|
86617
87791
|
'forgecad run examples/cup.forge.js --hide "wall,bolt"',
|
|
86618
87792
|
"forgecad run examples/cup.forge.js --connectivity",
|
|
87793
|
+
"forgecad run examples/cup.forge.js --journeys",
|
|
86619
87794
|
"forgecad run examples/cup.forge.js --backend occt",
|
|
86620
87795
|
"forgecad run examples/cup.forge.js --debug-imports",
|
|
86621
87796
|
'forgecad run examples/cup.forge.js -p "Wall Thickness=3" -p "Body Height=200"',
|
|
@@ -86637,7 +87812,20 @@ var commands = [
|
|
|
86637
87812
|
argument: "required",
|
|
86638
87813
|
valueLabel: "<mm>"
|
|
86639
87814
|
},
|
|
87815
|
+
{
|
|
87816
|
+
name: "--spatial",
|
|
87817
|
+
description: "Spatial diagnostics mode",
|
|
87818
|
+
argument: "required",
|
|
87819
|
+
valueLabel: "<bounded|exact|off>",
|
|
87820
|
+
values: [
|
|
87821
|
+
{ value: "bounded", description: "Default: exact checks only for bounded scene sizes" },
|
|
87822
|
+
{ value: "exact", description: "Exhaustive pairwise exact collision intersections" },
|
|
87823
|
+
{ value: "off", description: "Skip spatial diagnostics" }
|
|
87824
|
+
]
|
|
87825
|
+
},
|
|
86640
87826
|
{ name: "--debug-imports", description: "Print the import trace" },
|
|
87827
|
+
{ name: "--journeys", description: "Print model journey summaries declared with scene({ journeys })" },
|
|
87828
|
+
{ name: "--journeys-json", description: "Emit machine-readable journey metadata and diagnostics only" },
|
|
86641
87829
|
{
|
|
86642
87830
|
name: "--solver-debug-out",
|
|
86643
87831
|
description: "Write constructive transcripts and SVG snapshots to a directory",
|
|
@@ -86704,13 +87892,14 @@ var commands = [
|
|
|
86704
87892
|
group: "Modeling",
|
|
86705
87893
|
path: ["render", "3d"],
|
|
86706
87894
|
summary: "Render a Forge scene to PNG using the real viewport renderer.",
|
|
86707
|
-
description: "Launches a headless Chrome instance, renders the scene with the same WebGL viewport as the editor, and saves a PNG. The output path defaults to `<script-name>.png` next to the input file.\n\nUse `--focus` to isolate specific parts (hides everything else) or `--hide` to remove clutter like mock objects. The `--camera` flag accepts
|
|
87895
|
+
description: "Launches a headless Chrome instance, renders the scene with the same WebGL viewport as the editor, and saves a PNG. The output path defaults to `<script-name>.png` next to the input file.\n\nUse `--focus` to isolate specific parts (hides everything else) or `--hide` to remove clutter like mock objects. The `--view` flag selects a named camera declared in `scene({ views })`. The `--camera` flag accepts built-in views (`front`, `top`, `iso`), `azimuth:elevation` angles, or an exact `proj/pos/target/up/fov` camera spec \u2014 pass `--camera` multiple times to render several viewpoints in one run.\n\nUse `--edges=<off|thin|bold>` to control the edge overlay. For a pure wireframe look, use `render wireframe` instead.\n\nThis is the standard way to visually verify geometry from the CLI or in agent workflows. For higher quality (path-traced, materials, HDRI lighting), use `render hq` instead.",
|
|
86708
87896
|
usage: ["forgecad render 3d <script.forge.js> [output.png] [--focus [names]] [--hide names] [--edges off|thin|bold] [options]"],
|
|
86709
87897
|
examples: [
|
|
86710
87898
|
"forgecad render 3d examples/cup.forge.js",
|
|
86711
87899
|
"forgecad render 3d examples/cup.forge.js --focus",
|
|
86712
87900
|
"forgecad render 3d examples/cup.forge.js --focus bracket",
|
|
86713
87901
|
'forgecad render 3d examples/cup.forge.js --hide "wall,bolt"',
|
|
87902
|
+
"forgecad render 3d model.forge.js --view hero",
|
|
86714
87903
|
"forgecad render 3d model.forge.js --camera 45:30",
|
|
86715
87904
|
'forgecad render 3d model.forge.js --camera "proj=perspective;pos=200,-160,120;target=0,0,20;up=0,0,1;fov=38"',
|
|
86716
87905
|
"forgecad render 3d model.forge.js --camera front --camera side",
|
|
@@ -88124,7 +89313,8 @@ Commands:`);
|
|
|
88124
89313
|
${group2}`);
|
|
88125
89314
|
visibleCommands.filter((command) => command.group === group2).forEach((command) => {
|
|
88126
89315
|
const tier = requiredTierForCommand(command.path);
|
|
88127
|
-
const
|
|
89316
|
+
const productionBadge = productionOutputBadgeForCommand(command.path);
|
|
89317
|
+
const badge = tier === "pro" ? " \x1B[33m[Pro]\x1B[0m" : productionBadge ? ` \x1B[36m[${productionBadge}]\x1B[0m` : "";
|
|
88128
89318
|
console.log(` ${padRight2(commandLabel(command), width + 2)}${command.summary}${badge}`);
|
|
88129
89319
|
});
|
|
88130
89320
|
}
|
|
@@ -88306,6 +89496,7 @@ async function runForgeCadCli(argv = process.argv.slice(2)) {
|
|
|
88306
89496
|
}
|
|
88307
89497
|
if (!shouldRequireAuth) {
|
|
88308
89498
|
enforceFeatureGate(match.command.path);
|
|
89499
|
+
if (match.args.length > 0) printProductionOutputNotice(match.command.path);
|
|
88309
89500
|
await match.command.run(match.args);
|
|
88310
89501
|
return;
|
|
88311
89502
|
}
|
|
@@ -88313,6 +89504,7 @@ async function runForgeCadCli(argv = process.argv.slice(2)) {
|
|
|
88313
89504
|
const requiredTier = requiredTierForCommand(match.command.path);
|
|
88314
89505
|
if (isLongRunningCommand(match.command.path)) {
|
|
88315
89506
|
enforceFeatureGate(match.command.path);
|
|
89507
|
+
if (match.args.length > 0) printProductionOutputNotice(match.command.path);
|
|
88316
89508
|
await recordCliCommandEvent({
|
|
88317
89509
|
commandPath: match.command.path,
|
|
88318
89510
|
args: match.args,
|
|
@@ -88328,6 +89520,7 @@ async function runForgeCadCli(argv = process.argv.slice(2)) {
|
|
|
88328
89520
|
try {
|
|
88329
89521
|
capturedExitCode = await runWithExitCapture(async () => {
|
|
88330
89522
|
enforceFeatureGate(match.command.path);
|
|
89523
|
+
if (match.args.length > 0) printProductionOutputNotice(match.command.path);
|
|
88331
89524
|
await match.command.run(match.args);
|
|
88332
89525
|
});
|
|
88333
89526
|
await recordCliCommandEvent({
|
|
@@ -88364,6 +89557,7 @@ if (isDirectCliRun(import.meta.url)) {
|
|
|
88364
89557
|
export {
|
|
88365
89558
|
commandRequiresCliAuth,
|
|
88366
89559
|
commands,
|
|
89560
|
+
productionOutputBadgeForCommand,
|
|
88367
89561
|
requiredTierForCommand,
|
|
88368
89562
|
runForgeCadCli
|
|
88369
89563
|
};
|