forgecad 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/assets/{AdminPage-DAu1C1ST.js → AdminPage-D4bocK4E.js} +1 -1
- package/dist/assets/{DocsPage-Gc_BCdqC.js → DocsPage-D3A_g8V3.js} +85 -45
- package/dist/assets/{EditorApp-DG1-oUSV.css → EditorApp-BWYUSpUN.css} +133 -51
- package/dist/assets/EditorApp-Cihhqcsq.js +11692 -0
- package/dist/assets/{EmbedViewer-CEO8XbV8.js → EmbedViewer-kWjKaC_t.js} +1 -1
- package/dist/assets/LandingPageProofDriven-Bg2IUc3l.css +856 -0
- package/dist/assets/LandingPageProofDriven-DXkKlyhI.js +601 -0
- package/dist/assets/{PricingPage-BSrxu6d7.js → PricingPage-BsU5vzEx.js} +1 -1
- package/dist/assets/{SettingsPage-FUCSIRq6.js → SettingsPage-PqvpAKIs.js} +1 -1
- package/dist/assets/{evalWorker-KoR0SNKq.js → evalWorker-C-hzNUMy.js} +2218 -286
- package/dist/assets/{index-wTEK39at.js → index-Pz321YAt.js} +7416 -1481
- package/dist/assets/{index-CyVd1D4D.css → index-ay13WNfa.css} +501 -2
- package/dist/assets/{manifold-B1sGWdYk.js → manifold-BcbjWLIo.js} +3 -3
- package/dist/assets/{manifold-D7o0N50J.js → manifold-DBckbFgx.js} +1 -1
- package/dist/assets/{manifold-G5sBaXzi.js → manifold-O2AAGXyj.js} +1 -1
- package/dist/assets/{reportWorker-DYcRHhv9.js → reportWorker-Dxr-5A7w.js} +2003 -259
- package/dist/docs/index.html +2 -2
- package/dist/docs-raw/CLI.md +488 -0
- package/dist/docs-raw/generated/assembly.md +19 -11
- package/dist/docs-raw/generated/concepts.md +1023 -360
- package/dist/docs-raw/generated/core.md +1165 -264
- package/dist/docs-raw/generated/curves.md +168 -1
- package/dist/docs-raw/generated/lib.md +10 -5
- package/dist/docs-raw/generated/output.md +1 -1
- package/dist/docs-raw/generated/sdf.md +208 -0
- package/dist/docs-raw/generated/sketch.md +1281 -329
- package/dist/docs-raw/generated/viewport.md +29 -2
- package/dist/index.html +2 -2
- package/dist/landing/proof-ams-adapter.png +0 -0
- package/dist/landing/proof-bolt-and-nut.png +0 -0
- package/dist/landing/proof-fillet-enclosure.png +0 -0
- package/dist/landing/proof-glasses.png +0 -0
- package/dist/landing/proof-gyroid.png +0 -0
- package/dist/sitemap.xml +6 -6
- package/dist-cli/forgecad.js +3148 -555
- package/dist-cli/forgecad.js.map +1 -1
- package/dist-cli/{solver-FV7TJZGI.js → solver-46FFSK2U.js} +1 -3
- package/dist-cli/{solver-FV7TJZGI.js.map → solver-46FFSK2U.js.map} +1 -1
- package/dist-skill/CONTEXT.md +3700 -1153
- package/dist-skill/SKILL-dev.md +15 -17
- package/dist-skill/SKILL.md +14 -9
- package/dist-skill/docs/API/core/concepts.md +28 -1
- package/dist-skill/docs/CLI.md +488 -0
- package/dist-skill/docs/generated/assembly.md +19 -11
- package/dist-skill/docs/generated/core.md +1165 -264
- package/dist-skill/docs/generated/curves.md +168 -1
- package/dist-skill/docs/generated/lib.md +10 -5
- package/dist-skill/docs/generated/output.md +1 -1
- package/dist-skill/docs/generated/sdf.md +208 -0
- package/dist-skill/docs/generated/sketch.md +1281 -329
- package/dist-skill/docs/generated/viewport.md +29 -2
- package/dist-skill/docs/guides/joint-design.md +78 -0
- package/dist-skill/docs-dev/API/core/concepts.md +28 -1
- package/dist-skill/docs-dev/CLI.md +488 -0
- package/dist-skill/docs-dev/coding.md +1 -1
- package/dist-skill/docs-dev/component-model.md +164 -0
- package/dist-skill/docs-dev/generated/assembly.md +19 -11
- package/dist-skill/docs-dev/generated/core.md +1165 -264
- package/dist-skill/docs-dev/generated/curves.md +168 -1
- package/dist-skill/docs-dev/generated/lib.md +10 -5
- package/dist-skill/docs-dev/generated/output.md +1 -1
- package/dist-skill/docs-dev/generated/sdf.md +208 -0
- package/dist-skill/docs-dev/generated/sketch.md +1281 -329
- package/dist-skill/docs-dev/generated/viewport.md +29 -2
- package/dist-skill/docs-dev/guides/joint-design.md +78 -0
- package/examples/api/attachTo-basics.forge.js +3 -3
- package/examples/api/bill-of-materials.forge.js +9 -9
- package/examples/api/bolt-pattern.forge.js +5 -5
- package/examples/api/boolean-operations.forge.js +2 -2
- package/examples/api/bounding-box-visualizer.forge.js +1 -1
- package/examples/api/clone-duplicate.forge.js +1 -1
- package/examples/api/connector-assembly.forge.js +4 -2
- package/examples/api/connector-basics.forge.js +5 -5
- package/examples/api/constrained-sketch-mechanical.forge.js +4 -4
- package/examples/api/elbow-test.forge.js +3 -3
- package/examples/api/extrude-options.forge.js +4 -4
- package/examples/api/fillet-showcase.forge.js +1 -1
- package/examples/api/gears-tier1.forge.js +5 -5
- package/examples/api/group-test.forge.js +2 -2
- package/examples/api/mesh-import-slats.forge.js +3 -3
- package/examples/api/patterns.forge.js +3 -3
- package/examples/api/pointAlong-orientation.forge.js +2 -2
- package/examples/api/profile-2020-b-slot6.forge.js +4 -4
- package/examples/api/sketch-rounding-strategies.forge.js +1 -1
- package/examples/api/smooth-curve-connections.forge.js +1 -1
- package/examples/api/transition-curves.forge.js +3 -3
- package/examples/constraints/01-fully-constrained-rect.forge.js +2 -2
- package/examples/constraints/02-underconstrained-triangle.forge.js +1 -1
- package/examples/constraints/03-redundant-constraints.forge.js +2 -2
- package/examples/constraints/05-parallel-with-linedistance.forge.js +2 -2
- package/examples/constraints/06-complex-spectrogram.forge.js +1 -1
- package/examples/constraints/07-perpendicular-chain.forge.js +4 -4
- package/examples/constraints/08-symmetric-bracket.forge.js +4 -4
- package/examples/constraints/09-stress-spiral.forge.js +1 -1
- package/examples/constraints/10-stress-honeycomb.forge.js +1 -1
- package/examples/constraints/11-surface-grid.forge.js +2 -2
- package/examples/constraints/12-surface-nested.forge.js +4 -4
- package/examples/constraints/13-surface-complex.forge.js +1 -1
- package/examples/exact-arc-housing.forge.js +12 -0
- package/examples/furniture/adjustable-table.forge.js +13 -13
- package/examples/furniture/bathroom.forge.js +15 -15
- package/examples/furniture/chair.forge.js +12 -12
- package/examples/furniture/picture-frame.forge.js +6 -6
- package/examples/furniture/shoe-rack-doors.forge.js +8 -8
- package/examples/furniture/shoe-rack.forge.js +7 -7
- package/examples/furniture/table-lamp.forge.js +8 -8
- package/examples/gcode/lissajous-vase.forge.js +4 -4
- package/examples/gcode/math-surface.forge.js +3 -3
- package/examples/gcode/parametric-vase.forge.js +4 -4
- package/examples/gcode/spiral-tower.forge.js +4 -4
- package/examples/generative/crystal-growth.forge.js +7 -7
- package/examples/generative/frost-spires.forge.js +6 -6
- package/examples/generative/golden-spiral-tower.forge.js +8 -8
- package/examples/generative/molten-forge.forge.js +6 -6
- package/examples/generative/neon-coral.forge.js +7 -7
- package/examples/mechanical/3d-printer.forge.js +9 -9
- package/examples/mechanical/5-finger-robot-hand.forge.js +4 -4
- package/examples/mechanical/airplane-propeller.forge.js +7 -7
- package/examples/mechanical/bolt-and-nut.forge.js +10 -10
- package/examples/mechanical/door-with-hinges.forge.js +7 -7
- package/examples/mechanical/fillet-enclosure.forge.js +14 -10
- package/examples/mechanical/headphone-hanger-v2.forge.js +9 -9
- package/examples/mechanical/robot_hand.forge.js +10 -10
- package/examples/mechanical/robot_hand_2.forge.js +17 -17
- package/examples/nurbs-surface.forge.js +8 -0
- package/examples/nurbs-tube.forge.js +7 -0
- package/examples/products/bottle.forge.js +7 -7
- package/examples/products/chess-set.forge.js +6 -6
- package/examples/products/classical-piano.forge.js +9 -9
- package/examples/products/clock.forge.js +21 -21
- package/examples/products/cup.forge.js +5 -5
- package/examples/products/iphone.forge.js +12 -12
- package/examples/products/laptop.forge.js +9 -9
- package/examples/products/laser-cut-box.forge.js +6 -6
- package/examples/products/laser-cut-tray.forge.js +6 -6
- package/examples/products/liquid-soap-dispenser.forge.js +5 -5
- package/examples/products/origami-fish.forge.js +6 -6
- package/examples/products/spiderman-cake.forge.js +2 -2
- package/examples/shelf/container.forge.js +5 -5
- package/examples/shelf/shelf-unit.forge.js +6 -6
- package/examples/toolbox/bolted-joint.forge.js +5 -5
- package/package.json +3 -1
- package/dist/assets/EditorApp-D9bJvtf7.js +0 -11338
- package/dist/assets/LandingPage-CdCuEOdC.js +0 -451
- package/dist-cli/chunk-PZ5AY32C.js +0 -10
- package/dist-cli/chunk-PZ5AY32C.js.map +0 -1
- package/dist-skill/docs/CLI/export.md +0 -91
- package/dist-skill/docs/CLI/projects.md +0 -107
- package/dist-skill/docs/CLI/studio_publishing.md +0 -52
- package/dist-skill/docs/CLI/validation.md +0 -66
- package/dist-skill/docs-dev/API/core/sdf-advanced.md +0 -92
- package/dist-skill/docs-dev/API/core/sdf-primitives.md +0 -58
- package/dist-skill/docs-dev/API/core/sdf-workflow.md +0 -42
- package/dist-skill/docs-dev/CLI/export.md +0 -91
- package/dist-skill/docs-dev/CLI/projects.md +0 -107
- package/dist-skill/docs-dev/CLI/studio_publishing.md +0 -52
- package/dist-skill/docs-dev/CLI/validation.md +0 -66
|
@@ -1688,6 +1688,17 @@ function cloneSweepPathCompilePlan(path2) {
|
|
|
1688
1688
|
c1: canonicalVec3(path2.c1),
|
|
1689
1689
|
chordLength: canonicalNumber(path2.chordLength)
|
|
1690
1690
|
};
|
|
1691
|
+
case "nurbs":
|
|
1692
|
+
return {
|
|
1693
|
+
kind: "nurbs",
|
|
1694
|
+
controlPoints: path2.controlPoints.map(
|
|
1695
|
+
([x, y, z]) => [canonicalNumber(x), canonicalNumber(y), canonicalNumber(z)]
|
|
1696
|
+
),
|
|
1697
|
+
weights: path2.weights.map(canonicalNumber),
|
|
1698
|
+
knots: path2.knots.map(canonicalNumber),
|
|
1699
|
+
degree: path2.degree,
|
|
1700
|
+
closed: path2.closed
|
|
1701
|
+
};
|
|
1691
1702
|
default:
|
|
1692
1703
|
assertExhaustive(path2);
|
|
1693
1704
|
}
|
|
@@ -1754,10 +1765,33 @@ function cloneProfileCompilePlan(plan) {
|
|
|
1754
1765
|
replayReason: plan.replayReason,
|
|
1755
1766
|
transforms: plan.transforms.map(cloneProfileTransform)
|
|
1756
1767
|
};
|
|
1768
|
+
case "pathProfile":
|
|
1769
|
+
return {
|
|
1770
|
+
kind: "pathProfile",
|
|
1771
|
+
points: plan.points.map(([x, y]) => [x, y]),
|
|
1772
|
+
edges: plan.edges.map(cloneProfileEdge),
|
|
1773
|
+
transforms: plan.transforms.map(cloneProfileTransform)
|
|
1774
|
+
};
|
|
1757
1775
|
default:
|
|
1758
1776
|
assertExhaustive(plan);
|
|
1759
1777
|
}
|
|
1760
1778
|
}
|
|
1779
|
+
function cloneProfileEdge(edge) {
|
|
1780
|
+
switch (edge.kind) {
|
|
1781
|
+
case "line":
|
|
1782
|
+
return { kind: "line", x1: edge.x1, y1: edge.y1, x2: edge.x2, y2: edge.y2 };
|
|
1783
|
+
case "arc":
|
|
1784
|
+
return { kind: "arc", x1: edge.x1, y1: edge.y1, x2: edge.x2, y2: edge.y2, cx: edge.cx, cy: edge.cy, clockwise: edge.clockwise };
|
|
1785
|
+
case "bspline":
|
|
1786
|
+
return {
|
|
1787
|
+
kind: "bspline",
|
|
1788
|
+
controlPoints: edge.controlPoints.map(([x, y]) => [x, y]),
|
|
1789
|
+
weights: [...edge.weights],
|
|
1790
|
+
knots: [...edge.knots],
|
|
1791
|
+
degree: edge.degree
|
|
1792
|
+
};
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1761
1795
|
function cloneShapeCompilePlan(plan) {
|
|
1762
1796
|
if (!plan) return null;
|
|
1763
1797
|
let result;
|
|
@@ -1998,6 +2032,24 @@ function cloneShapeCompilePlan(plan) {
|
|
|
1998
2032
|
boundsPadding: canonicalNumber(plan.boundsPadding)
|
|
1999
2033
|
};
|
|
2000
2034
|
break;
|
|
2035
|
+
case "importedStep":
|
|
2036
|
+
result = { kind: "importedStep", filePath: plan.filePath, fileData: plan.fileData };
|
|
2037
|
+
break;
|
|
2038
|
+
case "nurbsSurface":
|
|
2039
|
+
result = {
|
|
2040
|
+
kind: "nurbsSurface",
|
|
2041
|
+
controlGrid: plan.controlGrid.map(
|
|
2042
|
+
(row) => row.map(([x, y, z]) => [canonicalNumber(x), canonicalNumber(y), canonicalNumber(z)])
|
|
2043
|
+
),
|
|
2044
|
+
weightsGrid: plan.weightsGrid.map((row) => row.map(canonicalNumber)),
|
|
2045
|
+
knotsU: plan.knotsU.map(canonicalNumber),
|
|
2046
|
+
knotsV: plan.knotsV.map(canonicalNumber),
|
|
2047
|
+
degreeU: plan.degreeU,
|
|
2048
|
+
degreeV: plan.degreeV,
|
|
2049
|
+
thickness: canonicalNumber(plan.thickness),
|
|
2050
|
+
resolution: plan.resolution
|
|
2051
|
+
};
|
|
2052
|
+
break;
|
|
2001
2053
|
default:
|
|
2002
2054
|
assertExhaustive(plan);
|
|
2003
2055
|
}
|
|
@@ -2188,6 +2240,8 @@ function findShapePrimaryQueryOwner(plan) {
|
|
|
2188
2240
|
case "importedMesh":
|
|
2189
2241
|
case "sdf":
|
|
2190
2242
|
case "fromSlices":
|
|
2243
|
+
case "nurbsSurface":
|
|
2244
|
+
case "importedStep":
|
|
2191
2245
|
return null;
|
|
2192
2246
|
default:
|
|
2193
2247
|
assertExhaustive(plan);
|
|
@@ -2234,6 +2288,8 @@ function collectShapeQueryOwners(plan) {
|
|
|
2234
2288
|
case "importedMesh":
|
|
2235
2289
|
case "sdf":
|
|
2236
2290
|
case "fromSlices":
|
|
2291
|
+
case "nurbsSurface":
|
|
2292
|
+
case "importedStep":
|
|
2237
2293
|
return;
|
|
2238
2294
|
default:
|
|
2239
2295
|
assertExhaustive(current);
|
|
@@ -2288,6 +2344,8 @@ function findShapeWorkplanePlacement(plan) {
|
|
|
2288
2344
|
case "importedMesh":
|
|
2289
2345
|
case "sdf":
|
|
2290
2346
|
case "fromSlices":
|
|
2347
|
+
case "nurbsSurface":
|
|
2348
|
+
case "importedStep":
|
|
2291
2349
|
return null;
|
|
2292
2350
|
default:
|
|
2293
2351
|
assertExhaustive(plan);
|
|
@@ -2410,6 +2468,199 @@ function buildFromSlicesShapeCompilePlan(groups, options) {
|
|
|
2410
2468
|
boundsPadding: canonicalNumber(options.boundsPadding)
|
|
2411
2469
|
};
|
|
2412
2470
|
}
|
|
2471
|
+
function generateClampedKnots(n, degree) {
|
|
2472
|
+
const m = n + degree + 1;
|
|
2473
|
+
const knots = new Array(m);
|
|
2474
|
+
for (let i = 0; i < m; i++) {
|
|
2475
|
+
if (i <= degree) knots[i] = 0;
|
|
2476
|
+
else if (i >= m - degree - 1) knots[i] = 1;
|
|
2477
|
+
else knots[i] = (i - degree) / (n - degree);
|
|
2478
|
+
}
|
|
2479
|
+
return knots;
|
|
2480
|
+
}
|
|
2481
|
+
function flatKnotsToKnotsMults(flatKnots) {
|
|
2482
|
+
const knots = [];
|
|
2483
|
+
const mults = [];
|
|
2484
|
+
let prev = -Infinity;
|
|
2485
|
+
for (const k of flatKnots) {
|
|
2486
|
+
if (Math.abs(k - prev) < 1e-12) {
|
|
2487
|
+
mults[mults.length - 1]++;
|
|
2488
|
+
} else {
|
|
2489
|
+
knots.push(k);
|
|
2490
|
+
mults.push(1);
|
|
2491
|
+
}
|
|
2492
|
+
prev = k;
|
|
2493
|
+
}
|
|
2494
|
+
return { knots, mults };
|
|
2495
|
+
}
|
|
2496
|
+
function findSpan(n, degree, u, knots) {
|
|
2497
|
+
if (u >= knots[n]) return n - 1;
|
|
2498
|
+
if (u <= knots[degree]) return degree;
|
|
2499
|
+
let lo = degree;
|
|
2500
|
+
let hi = n;
|
|
2501
|
+
let mid = lo + hi >>> 1;
|
|
2502
|
+
while (u < knots[mid] || u >= knots[mid + 1]) {
|
|
2503
|
+
if (u < knots[mid]) hi = mid;
|
|
2504
|
+
else lo = mid;
|
|
2505
|
+
mid = lo + hi >>> 1;
|
|
2506
|
+
}
|
|
2507
|
+
return mid;
|
|
2508
|
+
}
|
|
2509
|
+
function basisFuns(span, u, degree, knots) {
|
|
2510
|
+
const N = new Array(degree + 1);
|
|
2511
|
+
const left = new Array(degree + 1);
|
|
2512
|
+
const right = new Array(degree + 1);
|
|
2513
|
+
N[0] = 1;
|
|
2514
|
+
for (let j = 1; j <= degree; j++) {
|
|
2515
|
+
left[j] = u - knots[span + 1 - j];
|
|
2516
|
+
right[j] = knots[span + j] - u;
|
|
2517
|
+
let saved = 0;
|
|
2518
|
+
for (let r = 0; r < j; r++) {
|
|
2519
|
+
const denom = right[r + 1] + left[j - r];
|
|
2520
|
+
const temp = denom === 0 ? 0 : N[r] / denom;
|
|
2521
|
+
N[r] = saved + right[r + 1] * temp;
|
|
2522
|
+
saved = left[j - r] * temp;
|
|
2523
|
+
}
|
|
2524
|
+
N[j] = saved;
|
|
2525
|
+
}
|
|
2526
|
+
return N;
|
|
2527
|
+
}
|
|
2528
|
+
function basisFunsDeriv(span, u, degree, knots, nDeriv) {
|
|
2529
|
+
const ndu = Array.from({ length: degree + 1 }, () => new Array(degree + 1).fill(0));
|
|
2530
|
+
const a = Array.from({ length: 2 }, () => new Array(degree + 1).fill(0));
|
|
2531
|
+
const left = new Array(degree + 1);
|
|
2532
|
+
const right = new Array(degree + 1);
|
|
2533
|
+
ndu[0][0] = 1;
|
|
2534
|
+
for (let j = 1; j <= degree; j++) {
|
|
2535
|
+
left[j] = u - knots[span + 1 - j];
|
|
2536
|
+
right[j] = knots[span + j] - u;
|
|
2537
|
+
let saved = 0;
|
|
2538
|
+
for (let r2 = 0; r2 < j; r2++) {
|
|
2539
|
+
ndu[j][r2] = right[r2 + 1] + left[j - r2];
|
|
2540
|
+
const temp = ndu[j][r2] === 0 ? 0 : ndu[r2][j - 1] / ndu[j][r2];
|
|
2541
|
+
ndu[r2][j] = saved + right[r2 + 1] * temp;
|
|
2542
|
+
saved = left[j - r2] * temp;
|
|
2543
|
+
}
|
|
2544
|
+
ndu[j][j] = saved;
|
|
2545
|
+
}
|
|
2546
|
+
const ders = Array.from({ length: nDeriv + 1 }, () => new Array(degree + 1).fill(0));
|
|
2547
|
+
for (let j = 0; j <= degree; j++) {
|
|
2548
|
+
ders[0][j] = ndu[j][degree];
|
|
2549
|
+
}
|
|
2550
|
+
for (let r2 = 0; r2 <= degree; r2++) {
|
|
2551
|
+
let s1 = 0;
|
|
2552
|
+
let s2 = 1;
|
|
2553
|
+
a[0][0] = 1;
|
|
2554
|
+
for (let k = 1; k <= nDeriv; k++) {
|
|
2555
|
+
let d = 0;
|
|
2556
|
+
const rk = r2 - k;
|
|
2557
|
+
const pk = degree - k;
|
|
2558
|
+
if (r2 >= k) {
|
|
2559
|
+
a[s2][0] = ndu[pk + 1][rk] === 0 ? 0 : a[s1][0] / ndu[pk + 1][rk];
|
|
2560
|
+
d = a[s2][0] * ndu[rk][pk];
|
|
2561
|
+
}
|
|
2562
|
+
const j1 = rk >= -1 ? 1 : -rk;
|
|
2563
|
+
const j2 = r2 - 1 <= pk ? k - 1 : degree - r2;
|
|
2564
|
+
for (let j = j1; j <= j2; j++) {
|
|
2565
|
+
a[s2][j] = ndu[pk + 1][rk + j] === 0 ? 0 : (a[s1][j] - a[s1][j - 1]) / ndu[pk + 1][rk + j];
|
|
2566
|
+
d += a[s2][j] * ndu[rk + j][pk];
|
|
2567
|
+
}
|
|
2568
|
+
if (r2 <= pk) {
|
|
2569
|
+
a[s2][k] = ndu[pk + 1][r2] === 0 ? 0 : -a[s1][k - 1] / ndu[pk + 1][r2];
|
|
2570
|
+
d += a[s2][k] * ndu[r2][pk];
|
|
2571
|
+
}
|
|
2572
|
+
ders[k][r2] = d;
|
|
2573
|
+
const tmp = s1;
|
|
2574
|
+
s1 = s2;
|
|
2575
|
+
s2 = tmp;
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2578
|
+
let r = degree;
|
|
2579
|
+
for (let k = 1; k <= nDeriv; k++) {
|
|
2580
|
+
for (let j = 0; j <= degree; j++) {
|
|
2581
|
+
ders[k][j] *= r;
|
|
2582
|
+
}
|
|
2583
|
+
r *= degree - k;
|
|
2584
|
+
}
|
|
2585
|
+
return ders;
|
|
2586
|
+
}
|
|
2587
|
+
function deBoor3D(controlPoints, weights, knots, degree, u) {
|
|
2588
|
+
const n = controlPoints.length;
|
|
2589
|
+
const span = findSpan(n, degree, u, knots);
|
|
2590
|
+
const N = basisFuns(span, u, degree, knots);
|
|
2591
|
+
let wx = 0, wy = 0, wz = 0, wSum = 0;
|
|
2592
|
+
for (let j = 0; j <= degree; j++) {
|
|
2593
|
+
const idx = span - degree + j;
|
|
2594
|
+
const w = weights[idx] * N[j];
|
|
2595
|
+
wx += w * controlPoints[idx][0];
|
|
2596
|
+
wy += w * controlPoints[idx][1];
|
|
2597
|
+
wz += w * controlPoints[idx][2];
|
|
2598
|
+
wSum += w;
|
|
2599
|
+
}
|
|
2600
|
+
if (wSum === 0) return [0, 0, 0];
|
|
2601
|
+
return [wx / wSum, wy / wSum, wz / wSum];
|
|
2602
|
+
}
|
|
2603
|
+
function deBoor3DDeriv(controlPoints, weights, knots, degree, u) {
|
|
2604
|
+
const n = controlPoints.length;
|
|
2605
|
+
const span = findSpan(n, degree, u, knots);
|
|
2606
|
+
const ders = basisFunsDeriv(span, u, degree, knots, 1);
|
|
2607
|
+
const N = ders[0];
|
|
2608
|
+
const dN = ders[1];
|
|
2609
|
+
let ax = 0, ay = 0, az = 0, wVal = 0;
|
|
2610
|
+
let dax = 0, day = 0, daz = 0, dwVal = 0;
|
|
2611
|
+
for (let j = 0; j <= degree; j++) {
|
|
2612
|
+
const idx = span - degree + j;
|
|
2613
|
+
const wi = weights[idx];
|
|
2614
|
+
const px = controlPoints[idx][0], py = controlPoints[idx][1], pz = controlPoints[idx][2];
|
|
2615
|
+
ax += N[j] * wi * px;
|
|
2616
|
+
ay += N[j] * wi * py;
|
|
2617
|
+
az += N[j] * wi * pz;
|
|
2618
|
+
wVal += N[j] * wi;
|
|
2619
|
+
dax += dN[j] * wi * px;
|
|
2620
|
+
day += dN[j] * wi * py;
|
|
2621
|
+
daz += dN[j] * wi * pz;
|
|
2622
|
+
dwVal += dN[j] * wi;
|
|
2623
|
+
}
|
|
2624
|
+
if (wVal === 0) return [0, 0, 0];
|
|
2625
|
+
const invW = 1 / wVal;
|
|
2626
|
+
return [
|
|
2627
|
+
(dax - dwVal * ax * invW) * invW,
|
|
2628
|
+
(day - dwVal * ay * invW) * invW,
|
|
2629
|
+
(daz - dwVal * az * invW) * invW
|
|
2630
|
+
];
|
|
2631
|
+
}
|
|
2632
|
+
function deBoor2D(controlPoints, weights, knots, degree, u) {
|
|
2633
|
+
const n = controlPoints.length;
|
|
2634
|
+
const span = findSpan(n, degree, u, knots);
|
|
2635
|
+
const N = basisFuns(span, u, degree, knots);
|
|
2636
|
+
let wx = 0, wy = 0, wSum = 0;
|
|
2637
|
+
for (let j = 0; j <= degree; j++) {
|
|
2638
|
+
const idx = span - degree + j;
|
|
2639
|
+
const w = weights[idx] * N[j];
|
|
2640
|
+
wx += w * controlPoints[idx][0];
|
|
2641
|
+
wy += w * controlPoints[idx][1];
|
|
2642
|
+
wSum += w;
|
|
2643
|
+
}
|
|
2644
|
+
if (wSum === 0) return [0, 0];
|
|
2645
|
+
return [wx / wSum, wy / wSum];
|
|
2646
|
+
}
|
|
2647
|
+
function sampleNurbs3D(controlPoints, weights, knots, degree, count) {
|
|
2648
|
+
const n = controlPoints.length;
|
|
2649
|
+
const uMin = knots[degree];
|
|
2650
|
+
const uMax = knots[n];
|
|
2651
|
+
const result = new Array(count);
|
|
2652
|
+
for (let i = 0; i < count; i++) {
|
|
2653
|
+
const t = i / (count - 1);
|
|
2654
|
+
const u = uMin + t * (uMax - uMin);
|
|
2655
|
+
result[i] = deBoor3D(controlPoints, weights, knots, degree, u);
|
|
2656
|
+
}
|
|
2657
|
+
return result;
|
|
2658
|
+
}
|
|
2659
|
+
function remapToKnotDomain(t, n, degree, knots) {
|
|
2660
|
+
const uMin = knots[degree];
|
|
2661
|
+
const uMax = knots[n];
|
|
2662
|
+
return uMin + Math.max(0, Math.min(1, t)) * (uMax - uMin);
|
|
2663
|
+
}
|
|
2413
2664
|
function catmullRom3D$2(p0, p1, p2, p3, t, tension) {
|
|
2414
2665
|
const tt = t * t;
|
|
2415
2666
|
const ttt = tt * t;
|
|
@@ -2544,6 +2795,10 @@ function evalPathAt(path2, t) {
|
|
|
2544
2795
|
h00 * path2.p0[2] + h10 * path2.t0[2] + h20 * path2.c0[2] + h01 * path2.p1[2] + h11 * path2.t1[2] + h21 * path2.c1[2]
|
|
2545
2796
|
];
|
|
2546
2797
|
}
|
|
2798
|
+
case "nurbs": {
|
|
2799
|
+
const u = remapToKnotDomain(Math.max(0, Math.min(1, t)), path2.controlPoints.length, path2.degree, path2.knots);
|
|
2800
|
+
return deBoor3D(path2.controlPoints, path2.weights, path2.knots, path2.degree, u);
|
|
2801
|
+
}
|
|
2547
2802
|
}
|
|
2548
2803
|
}
|
|
2549
2804
|
function estimateCurvatureAt(path2, t) {
|
|
@@ -2578,6 +2833,18 @@ function sweepPathToPolyline(path2, samples = 48) {
|
|
|
2578
2833
|
path2.c1,
|
|
2579
2834
|
samples
|
|
2580
2835
|
);
|
|
2836
|
+
case "nurbs": {
|
|
2837
|
+
const n = path2.controlPoints.length;
|
|
2838
|
+
const uMin = path2.knots[path2.degree];
|
|
2839
|
+
const uMax = path2.knots[n];
|
|
2840
|
+
const result = new Array(samples);
|
|
2841
|
+
for (let i = 0; i < samples; i++) {
|
|
2842
|
+
const t = i / (samples - 1);
|
|
2843
|
+
const u = uMin + t * (uMax - uMin);
|
|
2844
|
+
result[i] = deBoor3D(path2.controlPoints, path2.weights, path2.knots, path2.degree, u);
|
|
2845
|
+
}
|
|
2846
|
+
return result;
|
|
2847
|
+
}
|
|
2581
2848
|
}
|
|
2582
2849
|
}
|
|
2583
2850
|
function sweepPathToPolylineAdaptive(path2, baseSamples = 48) {
|
|
@@ -2811,6 +3078,8 @@ function searchOwnerMatch(plan, owner) {
|
|
|
2811
3078
|
case "importedMesh":
|
|
2812
3079
|
case "sdf":
|
|
2813
3080
|
case "fromSlices":
|
|
3081
|
+
case "nurbsSurface":
|
|
3082
|
+
case "importedStep":
|
|
2814
3083
|
return {
|
|
2815
3084
|
issue: {
|
|
2816
3085
|
code: "edge-owner-not-found",
|
|
@@ -2892,7 +3161,7 @@ function propagateCandidateAcrossRewrite(plan, candidate) {
|
|
|
2892
3161
|
return edgeSuccess(candidate.selection, preservedEntry.query);
|
|
2893
3162
|
}
|
|
2894
3163
|
function resolvePropagatedEdgeQueryAtOwnerBase(ownerBase, ref) {
|
|
2895
|
-
if (ownerBase.kind === "box" || ownerBase.kind === "cylinder" || ownerBase.kind === "sphere" || ownerBase.kind === "extrude" || ownerBase.kind === "sheetMetal" || ownerBase.kind === "revolve" || ownerBase.kind === "loft" || ownerBase.kind === "sweep" || ownerBase.kind === "variableSweep" || ownerBase.kind === "transform" || ownerBase.kind === "queryOwner" || ownerBase.kind === "filletEdges" || ownerBase.kind === "chamferEdges" || ownerBase.kind === "importedMesh" || ownerBase.kind === "sdf" || ownerBase.kind === "fromSlices" || ownerBase.kind === "torus" || ownerBase.kind === "draft" || ownerBase.kind === "offsetSolid") {
|
|
3164
|
+
if (ownerBase.kind === "box" || ownerBase.kind === "cylinder" || ownerBase.kind === "sphere" || ownerBase.kind === "extrude" || ownerBase.kind === "sheetMetal" || ownerBase.kind === "revolve" || ownerBase.kind === "loft" || ownerBase.kind === "sweep" || ownerBase.kind === "variableSweep" || ownerBase.kind === "transform" || ownerBase.kind === "queryOwner" || ownerBase.kind === "filletEdges" || ownerBase.kind === "chamferEdges" || ownerBase.kind === "importedMesh" || ownerBase.kind === "sdf" || ownerBase.kind === "fromSlices" || ownerBase.kind === "nurbsSurface" || ownerBase.kind === "importedStep" || ownerBase.kind === "torus" || ownerBase.kind === "draft" || ownerBase.kind === "offsetSolid") {
|
|
2896
3165
|
return edgeIssue(
|
|
2897
3166
|
"edge-query-propagation-mismatch",
|
|
2898
3167
|
"The selected propagated edge query does not point at a topology-rewrite result on this target shape."
|
|
@@ -3118,6 +3387,8 @@ function resolveSelectionFromOwnerBase(plan, edgeName) {
|
|
|
3118
3387
|
case "importedMesh":
|
|
3119
3388
|
case "sdf":
|
|
3120
3389
|
case "fromSlices":
|
|
3390
|
+
case "nurbsSurface":
|
|
3391
|
+
case "importedStep":
|
|
3121
3392
|
return edgeIssue(
|
|
3122
3393
|
"unsupported-edge-base",
|
|
3123
3394
|
"Edge finishing v1 currently supports tracked vertical edges from compile-covered box() bodies and rectangle extrusions before topology-changing edits."
|
|
@@ -3152,7 +3423,7 @@ function resolveSupportedEdgeFeatureSelection(plan, ref) {
|
|
|
3152
3423
|
return edgeIssue("edge-query-unsupported-after-rewrite", descendant.reason);
|
|
3153
3424
|
}
|
|
3154
3425
|
function resolveEdgeChainAtOwnerBase(ownerBase, ref) {
|
|
3155
|
-
if (ownerBase.kind === "box" || ownerBase.kind === "cylinder" || ownerBase.kind === "sphere" || ownerBase.kind === "extrude" || ownerBase.kind === "sheetMetal" || ownerBase.kind === "revolve" || ownerBase.kind === "loft" || ownerBase.kind === "sweep" || ownerBase.kind === "variableSweep" || ownerBase.kind === "transform" || ownerBase.kind === "queryOwner" || ownerBase.kind === "filletEdges" || ownerBase.kind === "chamferEdges" || ownerBase.kind === "importedMesh" || ownerBase.kind === "sdf" || ownerBase.kind === "fromSlices" || ownerBase.kind === "torus" || ownerBase.kind === "draft" || ownerBase.kind === "offsetSolid") {
|
|
3426
|
+
if (ownerBase.kind === "box" || ownerBase.kind === "cylinder" || ownerBase.kind === "sphere" || ownerBase.kind === "extrude" || ownerBase.kind === "sheetMetal" || ownerBase.kind === "revolve" || ownerBase.kind === "loft" || ownerBase.kind === "sweep" || ownerBase.kind === "variableSweep" || ownerBase.kind === "transform" || ownerBase.kind === "queryOwner" || ownerBase.kind === "filletEdges" || ownerBase.kind === "chamferEdges" || ownerBase.kind === "importedMesh" || ownerBase.kind === "sdf" || ownerBase.kind === "fromSlices" || ownerBase.kind === "nurbsSurface" || ownerBase.kind === "importedStep" || ownerBase.kind === "torus" || ownerBase.kind === "draft" || ownerBase.kind === "offsetSolid") {
|
|
3156
3427
|
return {
|
|
3157
3428
|
kind: "unsupported",
|
|
3158
3429
|
query: cloneEdgeQueryRef(ref),
|
|
@@ -3185,7 +3456,7 @@ function resolveEdgeChainAtOwnerBase(ownerBase, ref) {
|
|
|
3185
3456
|
};
|
|
3186
3457
|
}
|
|
3187
3458
|
function resolveCreatedEdgeChainAtOwnerBase(ownerBase, ref) {
|
|
3188
|
-
if (ownerBase.kind === "box" || ownerBase.kind === "cylinder" || ownerBase.kind === "sphere" || ownerBase.kind === "extrude" || ownerBase.kind === "sheetMetal" || ownerBase.kind === "revolve" || ownerBase.kind === "loft" || ownerBase.kind === "sweep" || ownerBase.kind === "variableSweep" || ownerBase.kind === "transform" || ownerBase.kind === "queryOwner" || ownerBase.kind === "filletEdges" || ownerBase.kind === "chamferEdges" || ownerBase.kind === "importedMesh" || ownerBase.kind === "sdf" || ownerBase.kind === "fromSlices" || ownerBase.kind === "torus" || ownerBase.kind === "draft" || ownerBase.kind === "offsetSolid") {
|
|
3459
|
+
if (ownerBase.kind === "box" || ownerBase.kind === "cylinder" || ownerBase.kind === "sphere" || ownerBase.kind === "extrude" || ownerBase.kind === "sheetMetal" || ownerBase.kind === "revolve" || ownerBase.kind === "loft" || ownerBase.kind === "sweep" || ownerBase.kind === "variableSweep" || ownerBase.kind === "transform" || ownerBase.kind === "queryOwner" || ownerBase.kind === "filletEdges" || ownerBase.kind === "chamferEdges" || ownerBase.kind === "importedMesh" || ownerBase.kind === "sdf" || ownerBase.kind === "fromSlices" || ownerBase.kind === "nurbsSurface" || ownerBase.kind === "importedStep" || ownerBase.kind === "torus" || ownerBase.kind === "draft" || ownerBase.kind === "offsetSolid") {
|
|
3189
3460
|
return {
|
|
3190
3461
|
kind: "unsupported",
|
|
3191
3462
|
query: cloneEdgeQueryRef(ref),
|
|
@@ -4678,6 +4949,8 @@ function lowerBaseShellPlanToConcretePlan(plan, thickness, openFaces) {
|
|
|
4678
4949
|
case "importedMesh":
|
|
4679
4950
|
case "sdf":
|
|
4680
4951
|
case "fromSlices":
|
|
4952
|
+
case "nurbsSurface":
|
|
4953
|
+
case "importedStep":
|
|
4681
4954
|
return {
|
|
4682
4955
|
ok: false,
|
|
4683
4956
|
reason: `Shape.shell() supports box(), cylinder(), straight extrude(), loft(), sweep(), and variableSweep() bases. "${plan.kind}" bases are not supported.`
|
|
@@ -5016,7 +5289,7 @@ const defaultPerm = doublePerm(p);
|
|
|
5016
5289
|
const defaultPermMod12 = permMod12(defaultPerm);
|
|
5017
5290
|
const F3 = 1 / 3;
|
|
5018
5291
|
const G3 = 1 / 6;
|
|
5019
|
-
function dot3$
|
|
5292
|
+
function dot3$5(gi, x, y, z) {
|
|
5020
5293
|
const o = gi * 3;
|
|
5021
5294
|
return grad3[o] * x + grad3[o + 1] * y + grad3[o + 2] * z;
|
|
5022
5295
|
}
|
|
@@ -5126,28 +5399,28 @@ function simplex3Core(x, y, z, perm, pm12) {
|
|
|
5126
5399
|
n0 = 0;
|
|
5127
5400
|
} else {
|
|
5128
5401
|
t0 *= t0;
|
|
5129
|
-
n0 = t0 * t0 * dot3$
|
|
5402
|
+
n0 = t0 * t0 * dot3$5(gi0, x0, y0, z0);
|
|
5130
5403
|
}
|
|
5131
5404
|
let t1 = 0.6 - x1 * x1 - y1 * y1 - z1 * z1;
|
|
5132
5405
|
if (t1 < 0) {
|
|
5133
5406
|
n1 = 0;
|
|
5134
5407
|
} else {
|
|
5135
5408
|
t1 *= t1;
|
|
5136
|
-
n1 = t1 * t1 * dot3$
|
|
5409
|
+
n1 = t1 * t1 * dot3$5(gi1, x1, y1, z1);
|
|
5137
5410
|
}
|
|
5138
5411
|
let t2 = 0.6 - x2 * x2 - y2 * y2 - z2 * z2;
|
|
5139
5412
|
if (t2 < 0) {
|
|
5140
5413
|
n2 = 0;
|
|
5141
5414
|
} else {
|
|
5142
5415
|
t2 *= t2;
|
|
5143
|
-
n2 = t2 * t2 * dot3$
|
|
5416
|
+
n2 = t2 * t2 * dot3$5(gi2, x2, y2, z2);
|
|
5144
5417
|
}
|
|
5145
5418
|
let t3 = 0.6 - x3 * x3 - y3 * y3 - z3 * z3;
|
|
5146
5419
|
if (t3 < 0) {
|
|
5147
5420
|
n3 = 0;
|
|
5148
5421
|
} else {
|
|
5149
5422
|
t3 *= t3;
|
|
5150
|
-
n3 = t3 * t3 * dot3$
|
|
5423
|
+
n3 = t3 * t3 * dot3$5(gi3, x3, y3, z3);
|
|
5151
5424
|
}
|
|
5152
5425
|
return 32 * (n0 + n1 + n2 + n3);
|
|
5153
5426
|
}
|
|
@@ -5911,6 +6184,158 @@ function padBounds(b, pad) {
|
|
|
5911
6184
|
max: [b.max[0] + pad, b.max[1] + pad, b.max[2] + pad]
|
|
5912
6185
|
};
|
|
5913
6186
|
}
|
|
6187
|
+
function requireFinite$6(v, label) {
|
|
6188
|
+
if (!Number.isFinite(v)) throw new Error(`nurbsSurface: ${label} must be finite, got ${v}`);
|
|
6189
|
+
}
|
|
6190
|
+
class NurbsSurface {
|
|
6191
|
+
// columns in control grid
|
|
6192
|
+
constructor(controlGrid, options = {}) {
|
|
6193
|
+
__publicField(this, "controlGrid");
|
|
6194
|
+
__publicField(this, "weightsGrid");
|
|
6195
|
+
__publicField(this, "knotsU");
|
|
6196
|
+
__publicField(this, "knotsV");
|
|
6197
|
+
__publicField(this, "degreeU");
|
|
6198
|
+
__publicField(this, "degreeV");
|
|
6199
|
+
__publicField(this, "nU");
|
|
6200
|
+
// rows in control grid
|
|
6201
|
+
__publicField(this, "nV");
|
|
6202
|
+
const nU = controlGrid.length;
|
|
6203
|
+
if (nU < 2) throw new Error("nurbsSurface: controlGrid must have at least 2 rows");
|
|
6204
|
+
const nV = controlGrid[0].length;
|
|
6205
|
+
if (nV < 2) throw new Error("nurbsSurface: controlGrid must have at least 2 columns");
|
|
6206
|
+
const degreeU = options.degreeU ?? Math.min(nU - 1, 3);
|
|
6207
|
+
const degreeV = options.degreeV ?? Math.min(nV - 1, 3);
|
|
6208
|
+
if (nU < degreeU + 1) throw new Error(`nurbsSurface: need at least ${degreeU + 1} rows for degreeU=${degreeU}, got ${nU}`);
|
|
6209
|
+
if (nV < degreeV + 1) throw new Error(`nurbsSurface: need at least ${degreeV + 1} columns for degreeV=${degreeV}, got ${nV}`);
|
|
6210
|
+
for (let i = 0; i < nU; i++) {
|
|
6211
|
+
if (controlGrid[i].length !== nV) throw new Error(`nurbsSurface: row ${i} has ${controlGrid[i].length} points, expected ${nV}`);
|
|
6212
|
+
for (let j = 0; j < nV; j++) {
|
|
6213
|
+
requireFinite$6(controlGrid[i][j][0], `controlGrid[${i}][${j}][0]`);
|
|
6214
|
+
requireFinite$6(controlGrid[i][j][1], `controlGrid[${i}][${j}][1]`);
|
|
6215
|
+
requireFinite$6(controlGrid[i][j][2], `controlGrid[${i}][${j}][2]`);
|
|
6216
|
+
}
|
|
6217
|
+
}
|
|
6218
|
+
const weightsGrid = options.weights ?? controlGrid.map((row) => row.map(() => 1));
|
|
6219
|
+
for (let i = 0; i < nU; i++) {
|
|
6220
|
+
if (weightsGrid[i].length !== nV) throw new Error(`nurbsSurface: weights row ${i} length mismatch`);
|
|
6221
|
+
for (let j = 0; j < nV; j++) {
|
|
6222
|
+
requireFinite$6(weightsGrid[i][j], `weights[${i}][${j}]`);
|
|
6223
|
+
if (weightsGrid[i][j] <= 0) throw new Error(`nurbsSurface: weights[${i}][${j}] must be > 0`);
|
|
6224
|
+
}
|
|
6225
|
+
}
|
|
6226
|
+
const knotsU = options.knotsU ?? generateClampedKnots(nU, degreeU);
|
|
6227
|
+
const knotsV = options.knotsV ?? generateClampedKnots(nV, degreeV);
|
|
6228
|
+
if (knotsU.length !== nU + degreeU + 1) throw new Error(`nurbsSurface: knotsU.length should be ${nU + degreeU + 1}, got ${knotsU.length}`);
|
|
6229
|
+
if (knotsV.length !== nV + degreeV + 1) throw new Error(`nurbsSurface: knotsV.length should be ${nV + degreeV + 1}, got ${knotsV.length}`);
|
|
6230
|
+
this.controlGrid = controlGrid.map((row) => row.map(([x, y, z]) => [x, y, z]));
|
|
6231
|
+
this.weightsGrid = weightsGrid.map((row) => [...row]);
|
|
6232
|
+
this.knotsU = [...knotsU];
|
|
6233
|
+
this.knotsV = [...knotsV];
|
|
6234
|
+
this.degreeU = degreeU;
|
|
6235
|
+
this.degreeV = degreeV;
|
|
6236
|
+
this.nU = nU;
|
|
6237
|
+
this.nV = nV;
|
|
6238
|
+
}
|
|
6239
|
+
/**
|
|
6240
|
+
* Evaluate the surface at parameters (u, v) ∈ [0, 1]².
|
|
6241
|
+
* Uses tensor product evaluation: evaluate basis functions in U and V independently.
|
|
6242
|
+
*/
|
|
6243
|
+
pointAt(u, v) {
|
|
6244
|
+
const uu = this.remapU(Math.max(0, Math.min(1, u)));
|
|
6245
|
+
const vv = this.remapV(Math.max(0, Math.min(1, v)));
|
|
6246
|
+
const spanU = findSpan(this.nU, this.degreeU, uu, this.knotsU);
|
|
6247
|
+
const spanV = findSpan(this.nV, this.degreeV, vv, this.knotsV);
|
|
6248
|
+
const Nu = basisFuns(spanU, uu, this.degreeU, this.knotsU);
|
|
6249
|
+
const Nv = basisFuns(spanV, vv, this.degreeV, this.knotsV);
|
|
6250
|
+
let wx = 0, wy = 0, wz = 0, wSum = 0;
|
|
6251
|
+
for (let i = 0; i <= this.degreeU; i++) {
|
|
6252
|
+
const rowIdx = spanU - this.degreeU + i;
|
|
6253
|
+
for (let j = 0; j <= this.degreeV; j++) {
|
|
6254
|
+
const colIdx = spanV - this.degreeV + j;
|
|
6255
|
+
const w = Nu[i] * Nv[j] * this.weightsGrid[rowIdx][colIdx];
|
|
6256
|
+
const pt = this.controlGrid[rowIdx][colIdx];
|
|
6257
|
+
wx += w * pt[0];
|
|
6258
|
+
wy += w * pt[1];
|
|
6259
|
+
wz += w * pt[2];
|
|
6260
|
+
wSum += w;
|
|
6261
|
+
}
|
|
6262
|
+
}
|
|
6263
|
+
if (wSum === 0) return [0, 0, 0];
|
|
6264
|
+
return [wx / wSum, wy / wSum, wz / wSum];
|
|
6265
|
+
}
|
|
6266
|
+
/**
|
|
6267
|
+
* Evaluate the surface normal at (u, v) via cross product of partial derivatives.
|
|
6268
|
+
*/
|
|
6269
|
+
normalAt(u, v) {
|
|
6270
|
+
const eps = 1e-5;
|
|
6271
|
+
const u0 = Math.max(0, u - eps), u1 = Math.min(1, u + eps);
|
|
6272
|
+
const v0 = Math.max(0, v - eps), v1 = Math.min(1, v + eps);
|
|
6273
|
+
const pu = this.pointAt(u1, v), pmu = this.pointAt(u0, v);
|
|
6274
|
+
const pv = this.pointAt(u, v1), pmv = this.pointAt(u, v0);
|
|
6275
|
+
const du = [pu[0] - pmu[0], pu[1] - pmu[1], pu[2] - pmu[2]];
|
|
6276
|
+
const dv = [pv[0] - pmv[0], pv[1] - pmv[1], pv[2] - pmv[2]];
|
|
6277
|
+
const nx = du[1] * dv[2] - du[2] * dv[1];
|
|
6278
|
+
const ny = du[2] * dv[0] - du[0] * dv[2];
|
|
6279
|
+
const nz = du[0] * dv[1] - du[1] * dv[0];
|
|
6280
|
+
const len = Math.sqrt(nx * nx + ny * ny + nz * nz);
|
|
6281
|
+
if (len < 1e-12) return [0, 0, 1];
|
|
6282
|
+
return [nx / len, ny / len, nz / len];
|
|
6283
|
+
}
|
|
6284
|
+
/**
|
|
6285
|
+
* Tessellate the surface into a triangle mesh.
|
|
6286
|
+
* Returns positions, normals, and triangle indices.
|
|
6287
|
+
*/
|
|
6288
|
+
tessellate(resU = 32, resV = 32) {
|
|
6289
|
+
const positions = [];
|
|
6290
|
+
const normals = [];
|
|
6291
|
+
for (let i = 0; i <= resU; i++) {
|
|
6292
|
+
const u = i / resU;
|
|
6293
|
+
for (let j = 0; j <= resV; j++) {
|
|
6294
|
+
const v = j / resV;
|
|
6295
|
+
positions.push(this.pointAt(u, v));
|
|
6296
|
+
normals.push(this.normalAt(u, v));
|
|
6297
|
+
}
|
|
6298
|
+
}
|
|
6299
|
+
const indices = [];
|
|
6300
|
+
for (let i = 0; i < resU; i++) {
|
|
6301
|
+
for (let j = 0; j < resV; j++) {
|
|
6302
|
+
const a = i * (resV + 1) + j;
|
|
6303
|
+
const b = a + 1;
|
|
6304
|
+
const c = (i + 1) * (resV + 1) + j;
|
|
6305
|
+
const d = c + 1;
|
|
6306
|
+
indices.push(a, c, b);
|
|
6307
|
+
indices.push(b, c, d);
|
|
6308
|
+
}
|
|
6309
|
+
}
|
|
6310
|
+
return { positions, normals, indices };
|
|
6311
|
+
}
|
|
6312
|
+
remapU(t) {
|
|
6313
|
+
return this.knotsU[this.degreeU] + t * (this.knotsU[this.nU] - this.knotsU[this.degreeU]);
|
|
6314
|
+
}
|
|
6315
|
+
remapV(t) {
|
|
6316
|
+
return this.knotsV[this.degreeV] + t * (this.knotsV[this.nV] - this.knotsV[this.degreeV]);
|
|
6317
|
+
}
|
|
6318
|
+
}
|
|
6319
|
+
function nurbsSurface(controlGrid, options) {
|
|
6320
|
+
const surface = new NurbsSurface(controlGrid, options);
|
|
6321
|
+
const thickness = (options == null ? void 0 : options.thickness) ?? 1;
|
|
6322
|
+
const resolution = (options == null ? void 0 : options.resolution) ?? 32;
|
|
6323
|
+
return buildShapeFromCompilePlan(
|
|
6324
|
+
createOwnedShapeCompilePlan({
|
|
6325
|
+
kind: "nurbsSurface",
|
|
6326
|
+
controlGrid: surface.controlGrid.map(
|
|
6327
|
+
(row) => row.map(([x, y, z]) => [x, y, z])
|
|
6328
|
+
),
|
|
6329
|
+
weightsGrid: surface.weightsGrid.map((row) => [...row]),
|
|
6330
|
+
knotsU: [...surface.knotsU],
|
|
6331
|
+
knotsV: [...surface.knotsV],
|
|
6332
|
+
degreeU: surface.degreeU,
|
|
6333
|
+
degreeV: surface.degreeV,
|
|
6334
|
+
thickness,
|
|
6335
|
+
resolution
|
|
6336
|
+
}, "nurbsSurface")
|
|
6337
|
+
);
|
|
6338
|
+
}
|
|
5914
6339
|
let _simplifier = null;
|
|
5915
6340
|
async function initMeshoptimizer() {
|
|
5916
6341
|
if (_simplifier) return;
|
|
@@ -6314,6 +6739,8 @@ function sampleSweepPath(path2, edgeLengthHint) {
|
|
|
6314
6739
|
return sweepPathToPolyline(path2, resolveCurveSampleCount(edgeLengthHint, path2.chordLength));
|
|
6315
6740
|
case "quintic-hermite":
|
|
6316
6741
|
return sweepPathToPolyline(path2, resolveCurveSampleCount(edgeLengthHint, path2.chordLength));
|
|
6742
|
+
case "nurbs":
|
|
6743
|
+
return sweepPathToPolyline(path2, edgeLengthHint != null ? Math.max(16, Math.round(path2.controlPoints.length * 12 / Math.max(0.1, edgeLengthHint))) : 48);
|
|
6317
6744
|
}
|
|
6318
6745
|
}
|
|
6319
6746
|
function clamp$5(v, lo, hi) {
|
|
@@ -7169,11 +7596,15 @@ const SHAPE_BACKEND_MARKER = Symbol.for("forgecad.shapeBackend");
|
|
|
7169
7596
|
function isShapeBackend(value) {
|
|
7170
7597
|
return Boolean(value && typeof value === "object" && value[SHAPE_BACKEND_MARKER] === true);
|
|
7171
7598
|
}
|
|
7599
|
+
function disposeShapeBackend(backend) {
|
|
7600
|
+
const dispose = backend.dispose;
|
|
7601
|
+
dispose == null ? void 0 : dispose.call(backend);
|
|
7602
|
+
}
|
|
7172
7603
|
let _wasm = null;
|
|
7173
7604
|
async function initManifoldWasm() {
|
|
7174
7605
|
if (_wasm) return _wasm;
|
|
7175
7606
|
performance.mark("manifold:start");
|
|
7176
|
-
const Module = (await import("./manifold-
|
|
7607
|
+
const Module = (await import("./manifold-O2AAGXyj.js")).default;
|
|
7177
7608
|
performance.mark("manifold:imported");
|
|
7178
7609
|
const wasm = await Module();
|
|
7179
7610
|
wasm.setup();
|
|
@@ -7190,79 +7621,115 @@ function getManifoldWasm() {
|
|
|
7190
7621
|
if (!_wasm) throw new Error("Manifold WASM not initialized — call initKernel() first");
|
|
7191
7622
|
return _wasm;
|
|
7192
7623
|
}
|
|
7624
|
+
function getWasmHeapBytes() {
|
|
7625
|
+
if (!_wasm) return null;
|
|
7626
|
+
const heap = _wasm.HEAPU8;
|
|
7627
|
+
return heap ? heap.buffer.byteLength : null;
|
|
7628
|
+
}
|
|
7193
7629
|
function isManifoldCapableBackend(b) {
|
|
7194
7630
|
return typeof b.requireManifold === "function";
|
|
7195
7631
|
}
|
|
7196
7632
|
_a2 = SHAPE_BACKEND_MARKER;
|
|
7197
7633
|
const _ManifoldShapeBackend = class _ManifoldShapeBackend {
|
|
7198
|
-
constructor(
|
|
7634
|
+
constructor(manifoldOrResource) {
|
|
7199
7635
|
__publicField(this, _a2, true);
|
|
7200
|
-
this
|
|
7636
|
+
__publicField(this, "resource");
|
|
7637
|
+
__publicField(this, "released", false);
|
|
7638
|
+
this.resource = "refCount" in manifoldOrResource ? manifoldOrResource : {
|
|
7639
|
+
manifold: manifoldOrResource,
|
|
7640
|
+
refCount: 1,
|
|
7641
|
+
disposed: false
|
|
7642
|
+
};
|
|
7643
|
+
}
|
|
7644
|
+
getLiveManifold(apiName = "ManifoldShapeBackend") {
|
|
7645
|
+
if (this.released || this.resource.disposed) {
|
|
7646
|
+
throw new Error(`${apiName}: manifold backend was already disposed`);
|
|
7647
|
+
}
|
|
7648
|
+
return this.resource.manifold;
|
|
7201
7649
|
}
|
|
7202
7650
|
clone() {
|
|
7203
|
-
|
|
7651
|
+
this.resource.refCount += 1;
|
|
7652
|
+
return new _ManifoldShapeBackend(this.resource);
|
|
7204
7653
|
}
|
|
7205
7654
|
translate(x, y, z) {
|
|
7206
|
-
return new _ManifoldShapeBackend(this.
|
|
7655
|
+
return new _ManifoldShapeBackend(this.getLiveManifold("translate()").translate(x, y, z));
|
|
7207
7656
|
}
|
|
7208
7657
|
rotate(x, y, z) {
|
|
7209
|
-
return new _ManifoldShapeBackend(this.
|
|
7658
|
+
return new _ManifoldShapeBackend(this.getLiveManifold("rotate()").rotate(x, y, z));
|
|
7210
7659
|
}
|
|
7211
7660
|
transform(m) {
|
|
7212
|
-
return new _ManifoldShapeBackend(this.
|
|
7661
|
+
return new _ManifoldShapeBackend(this.getLiveManifold("transform()").transform(m));
|
|
7213
7662
|
}
|
|
7214
7663
|
scale(v) {
|
|
7215
|
-
return new _ManifoldShapeBackend(this.
|
|
7664
|
+
return new _ManifoldShapeBackend(this.getLiveManifold("scale()").scale(v));
|
|
7216
7665
|
}
|
|
7217
7666
|
mirror(normal) {
|
|
7218
|
-
return new _ManifoldShapeBackend(this.
|
|
7667
|
+
return new _ManifoldShapeBackend(this.getLiveManifold("mirror()").mirror(normal));
|
|
7219
7668
|
}
|
|
7220
7669
|
split(other) {
|
|
7221
|
-
const [inside, outside] = this.
|
|
7670
|
+
const [inside, outside] = this.getLiveManifold("split()").split(requireManifoldShapeBackend(other, "ShapeBackend.split()"));
|
|
7222
7671
|
return [new _ManifoldShapeBackend(inside), new _ManifoldShapeBackend(outside)];
|
|
7223
7672
|
}
|
|
7224
7673
|
splitByPlane(normal, originOffset) {
|
|
7225
|
-
const [inside, outside] = this.
|
|
7674
|
+
const [inside, outside] = this.getLiveManifold("splitByPlane()").splitByPlane(normal, originOffset);
|
|
7226
7675
|
return [new _ManifoldShapeBackend(inside), new _ManifoldShapeBackend(outside)];
|
|
7227
7676
|
}
|
|
7228
7677
|
trimByPlane(normal, originOffset) {
|
|
7229
|
-
return new _ManifoldShapeBackend(this.
|
|
7678
|
+
return new _ManifoldShapeBackend(this.getLiveManifold("trimByPlane()").trimByPlane(normal, originOffset));
|
|
7230
7679
|
}
|
|
7231
7680
|
simplify(tolerance) {
|
|
7232
|
-
return new _ManifoldShapeBackend(this.
|
|
7681
|
+
return new _ManifoldShapeBackend(this.getLiveManifold("simplify()").simplify(tolerance));
|
|
7233
7682
|
}
|
|
7234
7683
|
boundingBox() {
|
|
7235
|
-
return this.
|
|
7684
|
+
return this.getLiveManifold("boundingBox()").boundingBox();
|
|
7236
7685
|
}
|
|
7237
7686
|
volume() {
|
|
7238
|
-
return this.
|
|
7687
|
+
return this.getLiveManifold("volume()").volume();
|
|
7239
7688
|
}
|
|
7240
7689
|
surfaceArea() {
|
|
7241
|
-
return this.
|
|
7690
|
+
return this.getLiveManifold("surfaceArea()").surfaceArea();
|
|
7242
7691
|
}
|
|
7243
7692
|
minGap(other, searchLength) {
|
|
7244
|
-
return this.
|
|
7693
|
+
return this.getLiveManifold("minGap()").minGap(requireManifoldShapeBackend(other, "ShapeBackend.minGap()"), searchLength);
|
|
7245
7694
|
}
|
|
7246
7695
|
isEmpty() {
|
|
7247
|
-
return this.
|
|
7696
|
+
return this.getLiveManifold("isEmpty()").isEmpty();
|
|
7248
7697
|
}
|
|
7249
7698
|
numBodies() {
|
|
7250
|
-
|
|
7699
|
+
const parts = this.getLiveManifold("numBodies()").decompose();
|
|
7700
|
+
try {
|
|
7701
|
+
return parts.length;
|
|
7702
|
+
} finally {
|
|
7703
|
+
parts.forEach((part) => {
|
|
7704
|
+
var _a3;
|
|
7705
|
+
return (_a3 = part.delete) == null ? void 0 : _a3.call(part);
|
|
7706
|
+
});
|
|
7707
|
+
}
|
|
7251
7708
|
}
|
|
7252
7709
|
numTri() {
|
|
7253
|
-
return this.
|
|
7710
|
+
return this.getLiveManifold("numTri()").numTri();
|
|
7254
7711
|
}
|
|
7255
7712
|
getMesh() {
|
|
7256
|
-
return this.
|
|
7713
|
+
return this.getLiveManifold("getMesh()").getMesh();
|
|
7257
7714
|
}
|
|
7258
7715
|
slice(offset2) {
|
|
7259
|
-
return this.
|
|
7716
|
+
return this.getLiveManifold("slice()").slice(offset2);
|
|
7260
7717
|
}
|
|
7261
7718
|
project() {
|
|
7262
|
-
return this.
|
|
7719
|
+
return this.getLiveManifold("project()").project();
|
|
7263
7720
|
}
|
|
7264
7721
|
requireManifold() {
|
|
7265
|
-
return this.
|
|
7722
|
+
return this.getLiveManifold("requireManifold()");
|
|
7723
|
+
}
|
|
7724
|
+
dispose() {
|
|
7725
|
+
var _a3, _b3;
|
|
7726
|
+
if (this.released) return;
|
|
7727
|
+
this.released = true;
|
|
7728
|
+
this.resource.refCount = Math.max(0, this.resource.refCount - 1);
|
|
7729
|
+
if (this.resource.refCount === 0 && !this.resource.disposed) {
|
|
7730
|
+
this.resource.disposed = true;
|
|
7731
|
+
(_b3 = (_a3 = this.resource.manifold).delete) == null ? void 0 : _b3.call(_a3);
|
|
7732
|
+
}
|
|
7266
7733
|
}
|
|
7267
7734
|
};
|
|
7268
7735
|
let ManifoldShapeBackend = _ManifoldShapeBackend;
|
|
@@ -7481,6 +7948,12 @@ function rotateVector(v, axis, c, s) {
|
|
|
7481
7948
|
v[2] * c + kCrossV[2] * s + axis[2] * kDotV * (1 - c)
|
|
7482
7949
|
];
|
|
7483
7950
|
}
|
|
7951
|
+
function disposeWasmObject(value) {
|
|
7952
|
+
if (value != null && typeof value.delete === "function") value.delete();
|
|
7953
|
+
}
|
|
7954
|
+
function disposeWasmObjects(values) {
|
|
7955
|
+
for (const value of values) disposeWasmObject(value);
|
|
7956
|
+
}
|
|
7484
7957
|
function applyProfileCompileTransform(crossSection, step) {
|
|
7485
7958
|
switch (step.kind) {
|
|
7486
7959
|
case "translate":
|
|
@@ -7496,7 +7969,9 @@ function applyProfileCompileTransform(crossSection, step) {
|
|
|
7496
7969
|
function applyProfileCompileTransforms(crossSection, transforms) {
|
|
7497
7970
|
let out = crossSection;
|
|
7498
7971
|
for (const step of transforms) {
|
|
7972
|
+
const prev = out;
|
|
7499
7973
|
out = applyProfileCompileTransform(out, step);
|
|
7974
|
+
if (out !== prev) disposeWasmObject(prev);
|
|
7500
7975
|
}
|
|
7501
7976
|
return out;
|
|
7502
7977
|
}
|
|
@@ -7508,17 +7983,21 @@ function lowerProfileBooleanCompilePlan(plan, wasm) {
|
|
|
7508
7983
|
if (profiles.length === 1) {
|
|
7509
7984
|
return applyProfileCompileTransforms(profiles[0], plan.transforms);
|
|
7510
7985
|
}
|
|
7511
|
-
|
|
7512
|
-
|
|
7513
|
-
|
|
7514
|
-
|
|
7515
|
-
|
|
7516
|
-
|
|
7517
|
-
|
|
7518
|
-
|
|
7519
|
-
|
|
7520
|
-
|
|
7521
|
-
|
|
7986
|
+
try {
|
|
7987
|
+
const combined = (() => {
|
|
7988
|
+
switch (plan.op) {
|
|
7989
|
+
case "union":
|
|
7990
|
+
return wasm.CrossSection.union(profiles);
|
|
7991
|
+
case "difference":
|
|
7992
|
+
return wasm.CrossSection.difference(profiles);
|
|
7993
|
+
case "intersection":
|
|
7994
|
+
return wasm.CrossSection.intersection(profiles);
|
|
7995
|
+
}
|
|
7996
|
+
})();
|
|
7997
|
+
return applyProfileCompileTransforms(combined, plan.transforms);
|
|
7998
|
+
} finally {
|
|
7999
|
+
disposeWasmObjects(profiles);
|
|
8000
|
+
}
|
|
7522
8001
|
}
|
|
7523
8002
|
function lowerProfileCompilePlanToCrossSection(plan, wasm) {
|
|
7524
8003
|
switch (plan.kind) {
|
|
@@ -7526,7 +8005,9 @@ function lowerProfileCompilePlanToCrossSection(plan, wasm) {
|
|
|
7526
8005
|
return applyProfileCompileTransforms(wasm.CrossSection.square([plan.width, plan.height], true), plan.transforms);
|
|
7527
8006
|
case "roundedRect": {
|
|
7528
8007
|
const radius = Math.min(plan.radius, plan.width / 2, plan.height / 2);
|
|
7529
|
-
const
|
|
8008
|
+
const base = wasm.CrossSection.square([plan.width - 2 * radius, plan.height - 2 * radius], true);
|
|
8009
|
+
const crossSection = base.offset(radius, "Round");
|
|
8010
|
+
if (crossSection !== base) disposeWasmObject(base);
|
|
7530
8011
|
return applyProfileCompileTransforms(crossSection, plan.transforms);
|
|
7531
8012
|
}
|
|
7532
8013
|
case "circle":
|
|
@@ -7535,15 +8016,29 @@ function lowerProfileCompilePlanToCrossSection(plan, wasm) {
|
|
|
7535
8016
|
return applyProfileCompileTransforms(new wasm.CrossSection([plan.points]), plan.transforms);
|
|
7536
8017
|
case "boolean":
|
|
7537
8018
|
return lowerProfileBooleanCompilePlan(plan, wasm);
|
|
7538
|
-
case "offset":
|
|
7539
|
-
|
|
7540
|
-
|
|
7541
|
-
plan.transforms
|
|
7542
|
-
|
|
8019
|
+
case "offset": {
|
|
8020
|
+
const base = lowerProfileCompilePlanToCrossSection(plan.base, wasm);
|
|
8021
|
+
try {
|
|
8022
|
+
return applyProfileCompileTransforms(base.offset(plan.delta, plan.join), plan.transforms);
|
|
8023
|
+
} finally {
|
|
8024
|
+
disposeWasmObject(base);
|
|
8025
|
+
}
|
|
8026
|
+
}
|
|
7543
8027
|
case "project": {
|
|
7544
|
-
const
|
|
7545
|
-
|
|
8028
|
+
const source = lowerShapeCompilePlanToManifold(plan.sourceShape, wasm);
|
|
8029
|
+
try {
|
|
8030
|
+
const transformed = source.transform(planeFrameToWorldToPlaneMatrix(plan.plane));
|
|
8031
|
+
try {
|
|
8032
|
+
return applyProfileCompileTransforms(transformed.project(), plan.transforms);
|
|
8033
|
+
} finally {
|
|
8034
|
+
if (transformed !== source) disposeWasmObject(transformed);
|
|
8035
|
+
}
|
|
8036
|
+
} finally {
|
|
8037
|
+
disposeWasmObject(source);
|
|
8038
|
+
}
|
|
7546
8039
|
}
|
|
8040
|
+
case "pathProfile":
|
|
8041
|
+
return applyProfileCompileTransforms(new wasm.CrossSection([plan.points]), plan.transforms);
|
|
7547
8042
|
default:
|
|
7548
8043
|
assertExhaustive(plan);
|
|
7549
8044
|
}
|
|
@@ -7567,7 +8062,9 @@ function applyShapeCompileTransform(manifold, step) {
|
|
|
7567
8062
|
function applyShapeCompileTransforms(manifold, steps) {
|
|
7568
8063
|
let out = manifold;
|
|
7569
8064
|
for (const step of steps) {
|
|
8065
|
+
const prev = out;
|
|
7570
8066
|
out = applyShapeCompileTransform(out, step);
|
|
8067
|
+
if (out !== prev) disposeWasmObject(prev);
|
|
7571
8068
|
}
|
|
7572
8069
|
return out;
|
|
7573
8070
|
}
|
|
@@ -7582,22 +8079,36 @@ function lowerShapeBooleanCompilePlan(plan, wasm) {
|
|
|
7582
8079
|
if (shapes.length === 1) {
|
|
7583
8080
|
return shapes[0];
|
|
7584
8081
|
}
|
|
7585
|
-
|
|
7586
|
-
|
|
7587
|
-
|
|
7588
|
-
|
|
7589
|
-
|
|
7590
|
-
|
|
7591
|
-
|
|
8082
|
+
try {
|
|
8083
|
+
switch (plan.op) {
|
|
8084
|
+
case "union":
|
|
8085
|
+
return wasm.Manifold.union(shapes);
|
|
8086
|
+
case "difference":
|
|
8087
|
+
return wasm.Manifold.difference(shapes);
|
|
8088
|
+
case "intersection":
|
|
8089
|
+
return wasm.Manifold.intersection(shapes);
|
|
8090
|
+
}
|
|
8091
|
+
} finally {
|
|
8092
|
+
disposeWasmObjects(shapes);
|
|
7592
8093
|
}
|
|
7593
8094
|
}
|
|
7594
8095
|
function lowerShapeTrimByPlaneCompilePlan(plan, wasm) {
|
|
7595
|
-
|
|
8096
|
+
const base = lowerShapeCompilePlanToManifold(plan.base, wasm);
|
|
8097
|
+
try {
|
|
8098
|
+
return base.trimByPlane([plan.normalX, plan.normalY, plan.normalZ], plan.originOffset);
|
|
8099
|
+
} finally {
|
|
8100
|
+
disposeWasmObject(base);
|
|
8101
|
+
}
|
|
7596
8102
|
}
|
|
7597
8103
|
function lowerShapeLoftCompilePlan(plan, wasm) {
|
|
7598
|
-
const inputPolygons = plan.profiles.map(
|
|
7599
|
-
|
|
7600
|
-
|
|
8104
|
+
const inputPolygons = plan.profiles.map((profile) => {
|
|
8105
|
+
const crossSection = lowerProfileCompilePlanToCrossSection(profile, wasm);
|
|
8106
|
+
try {
|
|
8107
|
+
return crossSection.toPolygons();
|
|
8108
|
+
} finally {
|
|
8109
|
+
disposeWasmObject(crossSection);
|
|
8110
|
+
}
|
|
8111
|
+
});
|
|
7601
8112
|
if (inputPolygons.length >= 2) {
|
|
7602
8113
|
const stitched = loftStitched(inputPolygons, plan.heights, wasm);
|
|
7603
8114
|
if (stitched) return stitched;
|
|
@@ -7606,7 +8117,14 @@ function lowerShapeLoftCompilePlan(plan, wasm) {
|
|
|
7606
8117
|
return lowerSdfToManifold(levelSetFieldToStandardSdf(input.sdf), input.bounds, input.edgeLength, wasm);
|
|
7607
8118
|
}
|
|
7608
8119
|
function lowerShapeSweepCompilePlan(plan, wasm) {
|
|
7609
|
-
const
|
|
8120
|
+
const crossSection = lowerProfileCompilePlanToCrossSection(plan.profile, wasm);
|
|
8121
|
+
const profilePolygons = (() => {
|
|
8122
|
+
try {
|
|
8123
|
+
return crossSection.toPolygons();
|
|
8124
|
+
} finally {
|
|
8125
|
+
disposeWasmObject(crossSection);
|
|
8126
|
+
}
|
|
8127
|
+
})();
|
|
7610
8128
|
const pathPoints = sweepPathToPolylineAdaptive(plan.path, plan.pathSamples ?? 48);
|
|
7611
8129
|
const up = [plan.up[0], plan.up[1], plan.up[2]];
|
|
7612
8130
|
const stitched = sweepStitched(profilePolygons, pathPoints, up, wasm);
|
|
@@ -7618,77 +8136,318 @@ function lowerShapeSweepCompilePlan(plan, wasm) {
|
|
|
7618
8136
|
});
|
|
7619
8137
|
return lowerSdfToManifold(levelSetFieldToStandardSdf(input.sdf), input.bounds, input.edgeLength, wasm);
|
|
7620
8138
|
}
|
|
7621
|
-
function
|
|
7622
|
-
|
|
7623
|
-
|
|
7624
|
-
|
|
8139
|
+
function buildPlaneFrame(normal) {
|
|
8140
|
+
const [nx, ny, nz] = normal;
|
|
8141
|
+
if (Math.abs(nz) > 1 - 1e-8) {
|
|
8142
|
+
const s = nz > 0 ? 1 : -1;
|
|
8143
|
+
return { u: [1, 0, 0], v: [0, s, 0] };
|
|
8144
|
+
}
|
|
8145
|
+
if (Math.abs(ny) > 1 - 1e-8) {
|
|
8146
|
+
const s = ny > 0 ? 1 : -1;
|
|
8147
|
+
return { u: [1, 0, 0], v: [0, 0, s] };
|
|
8148
|
+
}
|
|
8149
|
+
if (Math.abs(nx) > 1 - 1e-8) {
|
|
8150
|
+
const s = nx > 0 ? 1 : -1;
|
|
8151
|
+
return { u: [0, 1, 0], v: [0, 0, s] };
|
|
8152
|
+
}
|
|
8153
|
+
const ref = Math.abs(nx) < 0.9 ? [1, 0, 0] : [0, 1, 0];
|
|
8154
|
+
const uRaw = [
|
|
8155
|
+
ref[1] * nz - ref[2] * ny,
|
|
8156
|
+
ref[2] * nx - ref[0] * nz,
|
|
8157
|
+
ref[0] * ny - ref[1] * nx
|
|
8158
|
+
];
|
|
8159
|
+
const uLen = Math.sqrt(uRaw[0] * uRaw[0] + uRaw[1] * uRaw[1] + uRaw[2] * uRaw[2]);
|
|
8160
|
+
const u = [uRaw[0] / uLen, uRaw[1] / uLen, uRaw[2] / uLen];
|
|
8161
|
+
const v = [
|
|
8162
|
+
ny * u[2] - nz * u[1],
|
|
8163
|
+
nz * u[0] - nx * u[2],
|
|
8164
|
+
nx * u[1] - ny * u[0]
|
|
8165
|
+
];
|
|
8166
|
+
return { u, v };
|
|
8167
|
+
}
|
|
8168
|
+
function dot3$4(a, b) {
|
|
8169
|
+
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
|
8170
|
+
}
|
|
8171
|
+
function profileHalfWidths(loops, height) {
|
|
8172
|
+
let maxPos = 0;
|
|
8173
|
+
let maxNeg = 0;
|
|
8174
|
+
for (const loop of loops) {
|
|
8175
|
+
for (let i = 0; i < loop.length; i++) {
|
|
8176
|
+
const [x0, y0] = loop[i];
|
|
8177
|
+
const [x1, y1] = loop[(i + 1) % loop.length];
|
|
8178
|
+
if (y0 <= height && y1 > height || y1 <= height && y0 > height) {
|
|
8179
|
+
const t = (height - y0) / (y1 - y0);
|
|
8180
|
+
const x = x0 + t * (x1 - x0);
|
|
8181
|
+
if (x > maxPos) maxPos = x;
|
|
8182
|
+
if (x < 0 && -x > maxNeg) maxNeg = -x;
|
|
8183
|
+
}
|
|
8184
|
+
}
|
|
8185
|
+
}
|
|
8186
|
+
return [maxPos, maxNeg];
|
|
8187
|
+
}
|
|
8188
|
+
function interpolatedHalfWidths(slices, spineCoord) {
|
|
8189
|
+
if (slices.length === 1) return profileHalfWidths(slices[0].polygons, spineCoord);
|
|
8190
|
+
if (spineCoord <= slices[0].offset) return profileHalfWidths(slices[0].polygons, spineCoord);
|
|
8191
|
+
if (spineCoord >= slices[slices.length - 1].offset) {
|
|
8192
|
+
return profileHalfWidths(slices[slices.length - 1].polygons, spineCoord);
|
|
8193
|
+
}
|
|
8194
|
+
let seg = 0;
|
|
8195
|
+
while (seg + 1 < slices.length && spineCoord > slices[seg + 1].offset) seg++;
|
|
8196
|
+
const t = (spineCoord - slices[seg].offset) / (slices[seg + 1].offset - slices[seg].offset);
|
|
8197
|
+
const [p0, n0] = profileHalfWidths(slices[seg].polygons, spineCoord);
|
|
8198
|
+
const [p1, n1] = profileHalfWidths(slices[seg + 1].polygons, spineCoord);
|
|
8199
|
+
return [p0 * (1 - t) + p1 * t, n0 * (1 - t) + n1 * t];
|
|
8200
|
+
}
|
|
8201
|
+
function lowerMultiGroupFromSlicesToManifold(plan, wasm) {
|
|
8202
|
+
const n0 = plan.groups[0].normal;
|
|
8203
|
+
const n1 = plan.groups[1].normal;
|
|
8204
|
+
const spineRaw = [
|
|
8205
|
+
n0[1] * n1[2] - n0[2] * n1[1],
|
|
8206
|
+
n0[2] * n1[0] - n0[0] * n1[2],
|
|
8207
|
+
n0[0] * n1[1] - n0[1] * n1[0]
|
|
8208
|
+
];
|
|
8209
|
+
const spineLen = Math.sqrt(spineRaw[0] ** 2 + spineRaw[1] ** 2 + spineRaw[2] ** 2);
|
|
8210
|
+
if (spineLen < 1e-8) {
|
|
8211
|
+
throw new Error("Shape.fromSlices: multi-group slices must have non-parallel normals.");
|
|
8212
|
+
}
|
|
8213
|
+
const spine = [spineRaw[0] / spineLen, spineRaw[1] / spineLen, spineRaw[2] / spineLen];
|
|
8214
|
+
const preparedGroups = plan.groups.map((group2) => {
|
|
8215
|
+
const frame = buildPlaneFrame(group2.normal);
|
|
7625
8216
|
const sorted = [...group2.slices].sort((a, b) => a.offset - b.offset);
|
|
7626
|
-
const
|
|
7627
|
-
|
|
7628
|
-
|
|
7629
|
-
|
|
7630
|
-
|
|
7631
|
-
|
|
7632
|
-
|
|
7633
|
-
|
|
7634
|
-
|
|
7635
|
-
|
|
7636
|
-
|
|
7637
|
-
|
|
8217
|
+
const sliceData = sorted.map((s) => {
|
|
8218
|
+
const cross2 = lowerProfileCompilePlanToCrossSection(s.profile, wasm);
|
|
8219
|
+
try {
|
|
8220
|
+
const polygons = cross2.toPolygons();
|
|
8221
|
+
return { offset: s.offset, polygons, sdf: compilePolygonsSdf(polygons) };
|
|
8222
|
+
} finally {
|
|
8223
|
+
disposeWasmObject(cross2);
|
|
8224
|
+
}
|
|
8225
|
+
});
|
|
8226
|
+
const spineOnU = Math.abs(dot3$4(spine, frame.u));
|
|
8227
|
+
const spineOnV = Math.abs(dot3$4(spine, frame.v));
|
|
8228
|
+
const vIsSpine = spineOnV >= spineOnU;
|
|
8229
|
+
const measAxis = vIsSpine ? frame.u : frame.v;
|
|
8230
|
+
const spineAxis = vIsSpine ? frame.v : frame.u;
|
|
8231
|
+
const spineSign = dot3$4(spine, spineAxis) >= 0 ? 1 : -1;
|
|
8232
|
+
let minMeas = Infinity;
|
|
8233
|
+
let maxMeas = -Infinity;
|
|
8234
|
+
let minSpine = Infinity;
|
|
8235
|
+
let maxSpine = -Infinity;
|
|
8236
|
+
for (const s of sliceData) {
|
|
8237
|
+
for (const loop of s.polygons) {
|
|
8238
|
+
for (const [x, y] of loop) {
|
|
8239
|
+
const meas = vIsSpine ? x : y;
|
|
8240
|
+
const sp = vIsSpine ? y : x;
|
|
8241
|
+
if (meas < minMeas) minMeas = meas;
|
|
8242
|
+
if (meas > maxMeas) maxMeas = meas;
|
|
8243
|
+
if (sp < minSpine) minSpine = sp;
|
|
8244
|
+
if (sp > maxSpine) maxSpine = sp;
|
|
8245
|
+
}
|
|
8246
|
+
}
|
|
8247
|
+
}
|
|
8248
|
+
const sliceDataForRayCast = vIsSpine ? sliceData : sliceData.map((s) => ({
|
|
8249
|
+
offset: s.offset,
|
|
8250
|
+
polygons: s.polygons.map((loop) => loop.map(([x, y]) => [y, x]))
|
|
8251
|
+
}));
|
|
8252
|
+
const profileSdf2d = (u, v) => {
|
|
8253
|
+
if (sliceData.length === 1) return sliceData[0].sdf(u, v);
|
|
8254
|
+
const w = vIsSpine ? v : u;
|
|
8255
|
+
const offsets = sliceData.map((s) => s.offset);
|
|
8256
|
+
if (w <= offsets[0]) return sliceData[0].sdf(u, v);
|
|
8257
|
+
if (w >= offsets[offsets.length - 1]) return sliceData[sliceData.length - 1].sdf(u, v);
|
|
8258
|
+
let seg = 0;
|
|
8259
|
+
while (seg + 1 < offsets.length && w > offsets[seg + 1]) seg++;
|
|
8260
|
+
const t = (w - offsets[seg]) / (offsets[seg + 1] - offsets[seg]);
|
|
8261
|
+
return sliceData[seg].sdf(u, v) * (1 - t) + sliceData[seg + 1].sdf(u, v) * t;
|
|
8262
|
+
};
|
|
8263
|
+
return {
|
|
8264
|
+
normal: group2.normal,
|
|
8265
|
+
frame,
|
|
8266
|
+
measAxis,
|
|
8267
|
+
spineSign,
|
|
8268
|
+
vIsSpine,
|
|
8269
|
+
sliceData: sliceDataForRayCast,
|
|
8270
|
+
profileSdf2d,
|
|
8271
|
+
minMeas,
|
|
8272
|
+
maxMeas,
|
|
8273
|
+
minSpine,
|
|
8274
|
+
maxSpine
|
|
8275
|
+
};
|
|
8276
|
+
});
|
|
8277
|
+
const sdf3d = (p2) => {
|
|
8278
|
+
const spineCoord = dot3$4(p2, spine);
|
|
8279
|
+
let sumRhoSq = 0;
|
|
8280
|
+
let profileCapSdf = Infinity;
|
|
8281
|
+
for (const g of preparedGroups) {
|
|
8282
|
+
const measCoord = dot3$4(p2, g.measAxis);
|
|
8283
|
+
const localSpine = spineCoord * g.spineSign;
|
|
8284
|
+
const [wPos, wNeg] = interpolatedHalfWidths(g.sliceData, localSpine);
|
|
8285
|
+
let rho;
|
|
8286
|
+
if (wPos <= 1e-10 && wNeg <= 1e-10) {
|
|
8287
|
+
rho = 2;
|
|
8288
|
+
} else if (measCoord >= 0) {
|
|
8289
|
+
rho = wPos > 1e-10 ? measCoord / wPos : 2;
|
|
7638
8290
|
} else {
|
|
7639
|
-
|
|
7640
|
-
edgeLength: plan.edgeLength,
|
|
7641
|
-
boundsPadding: plan.boundsPadding
|
|
7642
|
-
});
|
|
7643
|
-
solid = lowerSdfToManifold(levelSetFieldToStandardSdf(input.sdf), input.bounds, input.edgeLength, wasm);
|
|
8291
|
+
rho = wNeg > 1e-10 ? -measCoord / wNeg : 2;
|
|
7644
8292
|
}
|
|
8293
|
+
sumRhoSq += rho * rho;
|
|
8294
|
+
const u = dot3$4(p2, g.frame.u);
|
|
8295
|
+
const v = dot3$4(p2, g.frame.v);
|
|
8296
|
+
const dProfile = g.profileSdf2d(u, v);
|
|
8297
|
+
profileCapSdf = Math.min(profileCapSdf, dProfile);
|
|
7645
8298
|
}
|
|
7646
|
-
|
|
7647
|
-
|
|
7648
|
-
|
|
8299
|
+
const rhoSdf = sumRhoSq - 1;
|
|
8300
|
+
const capSdf = -profileCapSdf;
|
|
8301
|
+
return Math.max(rhoSdf, capSdf);
|
|
8302
|
+
};
|
|
8303
|
+
let bMinX = -Infinity;
|
|
8304
|
+
let bMaxX = Infinity;
|
|
8305
|
+
let bMinY = -Infinity;
|
|
8306
|
+
let bMaxY = Infinity;
|
|
8307
|
+
let bMinZ = -Infinity;
|
|
8308
|
+
let bMaxZ = Infinity;
|
|
8309
|
+
for (const g of preparedGroups) {
|
|
8310
|
+
for (let axis = 0; axis < 3; axis++) {
|
|
8311
|
+
const mc = g.measAxis[axis];
|
|
8312
|
+
if (Math.abs(mc) > 0.5) {
|
|
8313
|
+
const lo = mc > 0 ? g.minMeas : -g.maxMeas;
|
|
8314
|
+
const hi = mc > 0 ? g.maxMeas : -g.minMeas;
|
|
8315
|
+
if (axis === 0) {
|
|
8316
|
+
bMinX = Math.max(bMinX, lo);
|
|
8317
|
+
bMaxX = Math.min(bMaxX, hi);
|
|
8318
|
+
} else if (axis === 1) {
|
|
8319
|
+
bMinY = Math.max(bMinY, lo);
|
|
8320
|
+
bMaxY = Math.min(bMaxY, hi);
|
|
8321
|
+
} else {
|
|
8322
|
+
bMinZ = Math.max(bMinZ, lo);
|
|
8323
|
+
bMaxZ = Math.min(bMaxZ, hi);
|
|
8324
|
+
}
|
|
8325
|
+
}
|
|
8326
|
+
}
|
|
8327
|
+
}
|
|
8328
|
+
let spineMin = -Infinity;
|
|
8329
|
+
let spineMax = Infinity;
|
|
8330
|
+
for (const g of preparedGroups) {
|
|
8331
|
+
const lo = g.spineSign > 0 ? g.minSpine : -g.maxSpine;
|
|
8332
|
+
const hi = g.spineSign > 0 ? g.maxSpine : -g.minSpine;
|
|
8333
|
+
spineMin = Math.max(spineMin, lo);
|
|
8334
|
+
spineMax = Math.min(spineMax, hi);
|
|
8335
|
+
}
|
|
8336
|
+
for (let axis = 0; axis < 3; axis++) {
|
|
8337
|
+
if (Math.abs(spine[axis]) > 0.5) {
|
|
8338
|
+
const lo = spine[axis] > 0 ? spineMin : -spineMax;
|
|
8339
|
+
const hi = spine[axis] > 0 ? spineMax : -spineMin;
|
|
8340
|
+
if (axis === 0) {
|
|
8341
|
+
bMinX = Math.max(bMinX, lo);
|
|
8342
|
+
bMaxX = Math.min(bMaxX, hi);
|
|
8343
|
+
} else if (axis === 1) {
|
|
8344
|
+
bMinY = Math.max(bMinY, lo);
|
|
8345
|
+
bMaxY = Math.min(bMaxY, hi);
|
|
7649
8346
|
} else {
|
|
7650
|
-
|
|
7651
|
-
|
|
7652
|
-
|
|
7653
|
-
|
|
7654
|
-
|
|
7655
|
-
|
|
7656
|
-
|
|
7657
|
-
|
|
7658
|
-
|
|
7659
|
-
|
|
7660
|
-
|
|
7661
|
-
|
|
7662
|
-
|
|
7663
|
-
|
|
7664
|
-
|
|
7665
|
-
|
|
7666
|
-
|
|
7667
|
-
|
|
7668
|
-
|
|
7669
|
-
|
|
8347
|
+
bMinZ = Math.max(bMinZ, lo);
|
|
8348
|
+
bMaxZ = Math.min(bMaxZ, hi);
|
|
8349
|
+
}
|
|
8350
|
+
}
|
|
8351
|
+
}
|
|
8352
|
+
const fallback = 100;
|
|
8353
|
+
if (!isFinite(bMinX)) bMinX = -fallback;
|
|
8354
|
+
if (!isFinite(bMaxX)) bMaxX = fallback;
|
|
8355
|
+
if (!isFinite(bMinY)) bMinY = -fallback;
|
|
8356
|
+
if (!isFinite(bMaxY)) bMaxY = fallback;
|
|
8357
|
+
if (!isFinite(bMinZ)) bMinZ = -fallback;
|
|
8358
|
+
if (!isFinite(bMaxZ)) bMaxZ = fallback;
|
|
8359
|
+
const pad = plan.boundsPadding;
|
|
8360
|
+
const bounds = {
|
|
8361
|
+
min: [bMinX - pad, bMinY - pad, bMinZ - pad],
|
|
8362
|
+
max: [bMaxX + pad, bMaxY + pad, bMaxZ + pad]
|
|
8363
|
+
};
|
|
8364
|
+
return lowerSdfToManifold(sdf3d, bounds, plan.edgeLength, wasm);
|
|
8365
|
+
}
|
|
8366
|
+
function lowerFromSlicesToManifold(plan, wasm) {
|
|
8367
|
+
if (plan.groups.length === 0) throw new Error("Shape.fromSlices requires at least one slice");
|
|
8368
|
+
if (plan.groups.length > 1) {
|
|
8369
|
+
return lowerMultiGroupFromSlicesToManifold(plan, wasm);
|
|
7670
8370
|
}
|
|
7671
|
-
|
|
7672
|
-
|
|
7673
|
-
|
|
7674
|
-
|
|
8371
|
+
const group2 = plan.groups[0];
|
|
8372
|
+
const sorted = [...group2.slices].sort((a, b) => a.offset - b.offset);
|
|
8373
|
+
const [nx, ny, nz] = group2.normal;
|
|
8374
|
+
let solid;
|
|
8375
|
+
if (sorted.length === 1) {
|
|
8376
|
+
const cross2 = lowerProfileCompilePlanToCrossSection(sorted[0].profile, wasm);
|
|
8377
|
+
const extrudeHalf = 500;
|
|
8378
|
+
try {
|
|
8379
|
+
const extruded = cross2.extrude(2 * extrudeHalf);
|
|
8380
|
+
solid = applyShapeCompileTransforms(extruded, [{ kind: "translate", x: 0, y: 0, z: sorted[0].offset - extrudeHalf }]);
|
|
8381
|
+
} finally {
|
|
8382
|
+
disposeWasmObject(cross2);
|
|
8383
|
+
}
|
|
8384
|
+
} else {
|
|
8385
|
+
const polygons = sorted.map((s) => {
|
|
8386
|
+
const crossSection = lowerProfileCompilePlanToCrossSection(s.profile, wasm);
|
|
8387
|
+
try {
|
|
8388
|
+
return crossSection.toPolygons();
|
|
8389
|
+
} finally {
|
|
8390
|
+
disposeWasmObject(crossSection);
|
|
8391
|
+
}
|
|
8392
|
+
});
|
|
8393
|
+
const heights = sorted.map((s) => s.offset);
|
|
8394
|
+
const stitched = polygons.length >= 2 ? loftStitched(polygons, heights, wasm) : null;
|
|
8395
|
+
if (stitched) {
|
|
8396
|
+
solid = stitched;
|
|
8397
|
+
} else {
|
|
8398
|
+
const input = buildLoftLevelSetInput(polygons, heights, {
|
|
8399
|
+
edgeLength: plan.edgeLength,
|
|
8400
|
+
boundsPadding: plan.boundsPadding
|
|
8401
|
+
});
|
|
8402
|
+
solid = lowerSdfToManifold(levelSetFieldToStandardSdf(input.sdf), input.bounds, input.edgeLength, wasm);
|
|
8403
|
+
}
|
|
7675
8404
|
}
|
|
7676
|
-
|
|
8405
|
+
if (Math.abs(nx) > 1e-10 || Math.abs(ny) > 1e-10 || nz < 0) {
|
|
8406
|
+
if (Math.abs(nx) < 1e-10 && Math.abs(ny) < 1e-10 && nz < 0) {
|
|
8407
|
+
const prev = solid;
|
|
8408
|
+
solid = solid.rotate([180, 0, 0]);
|
|
8409
|
+
if (solid !== prev) disposeWasmObject(prev);
|
|
8410
|
+
} else {
|
|
8411
|
+
const ax = -ny;
|
|
8412
|
+
const ay = nx;
|
|
8413
|
+
const len = Math.sqrt(ax * ax + ay * ay);
|
|
8414
|
+
const c = nz;
|
|
8415
|
+
const s = len;
|
|
8416
|
+
const ux = ax / len;
|
|
8417
|
+
const uy = ay / len;
|
|
8418
|
+
const m00 = c + ux * ux * (1 - c);
|
|
8419
|
+
const m01 = ux * uy * (1 - c);
|
|
8420
|
+
const m02 = uy * s;
|
|
8421
|
+
const m10 = uy * ux * (1 - c);
|
|
8422
|
+
const m11 = c + uy * uy * (1 - c);
|
|
8423
|
+
const m12 = -ux * s;
|
|
8424
|
+
const m20 = -uy * s;
|
|
8425
|
+
const m21 = ux * s;
|
|
8426
|
+
const m22 = c;
|
|
8427
|
+
const prev = solid;
|
|
8428
|
+
solid = solid.transform([m00, m01, m02, 0, m10, m11, m12, 0, m20, m21, m22, 0, 0, 0, 0, 1]);
|
|
8429
|
+
if (solid !== prev) disposeWasmObject(prev);
|
|
8430
|
+
}
|
|
8431
|
+
}
|
|
8432
|
+
return solid;
|
|
7677
8433
|
}
|
|
7678
8434
|
function lowerShapeVariableSweepCompilePlan(plan, wasm) {
|
|
7679
|
-
const sectionPolygons = plan.sections.map((s) =>
|
|
7680
|
-
|
|
7681
|
-
|
|
7682
|
-
|
|
7683
|
-
|
|
7684
|
-
|
|
7685
|
-
|
|
7686
|
-
{
|
|
7687
|
-
|
|
7688
|
-
boundsPadding: plan.boundsPadding,
|
|
7689
|
-
up: [plan.up[0], plan.up[1], plan.up[2]]
|
|
8435
|
+
const sectionPolygons = plan.sections.map((s) => {
|
|
8436
|
+
const crossSection = lowerProfileCompilePlanToCrossSection(s.profile, wasm);
|
|
8437
|
+
try {
|
|
8438
|
+
return {
|
|
8439
|
+
t: s.t,
|
|
8440
|
+
polygons: crossSection.toPolygons()
|
|
8441
|
+
};
|
|
8442
|
+
} finally {
|
|
8443
|
+
disposeWasmObject(crossSection);
|
|
7690
8444
|
}
|
|
7691
|
-
);
|
|
8445
|
+
});
|
|
8446
|
+
const input = buildVariableSweepLevelSetInput(sectionPolygons, plan.path, {
|
|
8447
|
+
edgeLength: plan.edgeLength,
|
|
8448
|
+
boundsPadding: plan.boundsPadding,
|
|
8449
|
+
up: [plan.up[0], plan.up[1], plan.up[2]]
|
|
8450
|
+
});
|
|
7692
8451
|
return lowerSdfToManifold(levelSetFieldToStandardSdf(input.sdf), input.bounds, input.edgeLength, wasm);
|
|
7693
8452
|
}
|
|
7694
8453
|
function lowerShapeFilletCompilePlan(plan, wasm) {
|
|
@@ -7699,13 +8458,15 @@ function lowerShapeFilletCompilePlan(plan, wasm) {
|
|
|
7699
8458
|
`fillet2d() currently supports ${selection.selection.edgeName} only with quadrant [${selection.selection.quadrant[0]}, ${selection.selection.quadrant[1]}].`
|
|
7700
8459
|
);
|
|
7701
8460
|
}
|
|
7702
|
-
|
|
7703
|
-
|
|
7704
|
-
selection.selection,
|
|
7705
|
-
|
|
7706
|
-
|
|
7707
|
-
|
|
7708
|
-
|
|
8461
|
+
const base = lowerShapeCompilePlanToManifold(plan.base, wasm);
|
|
8462
|
+
try {
|
|
8463
|
+
const result = applyFilletSelectionToManifold(base, selection.selection, plan.radius, plan.segments, wasm);
|
|
8464
|
+
if (result !== base) disposeWasmObject(base);
|
|
8465
|
+
return result;
|
|
8466
|
+
} catch (error) {
|
|
8467
|
+
disposeWasmObject(base);
|
|
8468
|
+
throw error;
|
|
8469
|
+
}
|
|
7709
8470
|
}
|
|
7710
8471
|
function lowerShapeChamferCompilePlan(plan, wasm) {
|
|
7711
8472
|
const selection = resolveSupportedEdgeFeatureSelection(plan.base, plan.edge);
|
|
@@ -7715,7 +8476,15 @@ function lowerShapeChamferCompilePlan(plan, wasm) {
|
|
|
7715
8476
|
`chamfer2d() currently supports ${selection.selection.edgeName} only with quadrant [${selection.selection.quadrant[0]}, ${selection.selection.quadrant[1]}].`
|
|
7716
8477
|
);
|
|
7717
8478
|
}
|
|
7718
|
-
|
|
8479
|
+
const base = lowerShapeCompilePlanToManifold(plan.base, wasm);
|
|
8480
|
+
try {
|
|
8481
|
+
const result = applyChamferSelectionToManifold(base, selection.selection, plan.size, wasm);
|
|
8482
|
+
if (result !== base) disposeWasmObject(base);
|
|
8483
|
+
return result;
|
|
8484
|
+
} catch (error) {
|
|
8485
|
+
disposeWasmObject(base);
|
|
8486
|
+
throw error;
|
|
8487
|
+
}
|
|
7719
8488
|
}
|
|
7720
8489
|
function edgeSegmentToSelection(segment) {
|
|
7721
8490
|
const { start, end, direction: axis, normalA, normalB, convex } = segment;
|
|
@@ -7810,7 +8579,9 @@ function lowerFilletEdgesCompilePlan(plan, wasm) {
|
|
|
7810
8579
|
try {
|
|
7811
8580
|
const selection = edgeSegmentToSelection(seg);
|
|
7812
8581
|
const apply = seg.convex ? applyFilletSelectionToManifold : applyConcaveFilletSelectionToManifold;
|
|
7813
|
-
|
|
8582
|
+
const prev = manifold;
|
|
8583
|
+
manifold = apply(prev, selection, plan.radius, plan.segments, wasm);
|
|
8584
|
+
if (manifold !== prev) disposeWasmObject(prev);
|
|
7814
8585
|
} catch {
|
|
7815
8586
|
}
|
|
7816
8587
|
}
|
|
@@ -7835,7 +8606,9 @@ function lowerChamferEdgesCompilePlan(plan, wasm) {
|
|
|
7835
8606
|
try {
|
|
7836
8607
|
const selection = edgeSegmentToSelection(seg);
|
|
7837
8608
|
const apply = seg.convex ? applyChamferSelectionToManifold : applyConcaveChamferSelectionToManifold;
|
|
7838
|
-
|
|
8609
|
+
const prev = manifold;
|
|
8610
|
+
manifold = apply(prev, selection, plan.size, wasm);
|
|
8611
|
+
if (manifold !== prev) disposeWasmObject(prev);
|
|
7839
8612
|
} catch {
|
|
7840
8613
|
}
|
|
7841
8614
|
}
|
|
@@ -7844,24 +8617,34 @@ function lowerChamferEdgesCompilePlan(plan, wasm) {
|
|
|
7844
8617
|
function lowerShapeCompilePlanToManifold(plan, wasm) {
|
|
7845
8618
|
switch (plan.kind) {
|
|
7846
8619
|
case "box":
|
|
7847
|
-
return wasm.Manifold.cube([plan.x, plan.y, plan.z], false)
|
|
8620
|
+
return applyShapeCompileTransforms(wasm.Manifold.cube([plan.x, plan.y, plan.z], false), [
|
|
8621
|
+
{ kind: "translate", x: -plan.x / 2, y: -plan.y / 2, z: 0 }
|
|
8622
|
+
]);
|
|
7848
8623
|
case "cylinder":
|
|
7849
8624
|
return wasm.Manifold.cylinder(plan.height, plan.radius, plan.radiusTop ?? -1, plan.segments ?? 0, false);
|
|
7850
8625
|
case "sphere":
|
|
7851
8626
|
return wasm.Manifold.sphere(plan.radius, plan.segments ?? 0);
|
|
7852
8627
|
case "torus": {
|
|
7853
8628
|
const circle2 = wasm.CrossSection.circle(plan.minorRadius, plan.segments ?? 0);
|
|
7854
|
-
|
|
7855
|
-
|
|
8629
|
+
try {
|
|
8630
|
+
const translated = circle2.translate([plan.majorRadius, 0]);
|
|
8631
|
+
try {
|
|
8632
|
+
return translated.revolve(plan.segments ?? 0, 360);
|
|
8633
|
+
} finally {
|
|
8634
|
+
disposeWasmObject(translated);
|
|
8635
|
+
}
|
|
8636
|
+
} finally {
|
|
8637
|
+
disposeWasmObject(circle2);
|
|
8638
|
+
}
|
|
8639
|
+
}
|
|
8640
|
+
case "extrude": {
|
|
8641
|
+
const profile = lowerProfileCompilePlanToCrossSection(plan.profile, wasm);
|
|
8642
|
+
try {
|
|
8643
|
+
return profile.extrude(plan.height, plan.twistSegments ?? 0, plan.twist ?? 0, plan.scaleTop, false);
|
|
8644
|
+
} finally {
|
|
8645
|
+
disposeWasmObject(profile);
|
|
8646
|
+
}
|
|
7856
8647
|
}
|
|
7857
|
-
case "extrude":
|
|
7858
|
-
return lowerProfileCompilePlanToCrossSection(plan.profile, wasm).extrude(
|
|
7859
|
-
plan.height,
|
|
7860
|
-
plan.twistSegments ?? 0,
|
|
7861
|
-
plan.twist ?? 0,
|
|
7862
|
-
plan.scaleTop,
|
|
7863
|
-
false
|
|
7864
|
-
);
|
|
7865
8648
|
case "sheetMetal":
|
|
7866
8649
|
return lowerShapeCompilePlanToManifold(lowerSheetMetalBasePlan(plan.model, plan.output), wasm);
|
|
7867
8650
|
case "shell": {
|
|
@@ -7879,8 +8662,14 @@ function lowerShapeCompilePlanToManifold(plan, wasm) {
|
|
|
7879
8662
|
if (!lowered.ok) throw new Error(lowered.reason);
|
|
7880
8663
|
return lowerShapeCompilePlanToManifold(lowered.plan, wasm);
|
|
7881
8664
|
}
|
|
7882
|
-
case "revolve":
|
|
7883
|
-
|
|
8665
|
+
case "revolve": {
|
|
8666
|
+
const profile = lowerProfileCompilePlanToCrossSection(plan.profile, wasm);
|
|
8667
|
+
try {
|
|
8668
|
+
return profile.revolve(plan.segments ?? 0, plan.degrees);
|
|
8669
|
+
} finally {
|
|
8670
|
+
disposeWasmObject(profile);
|
|
8671
|
+
}
|
|
8672
|
+
}
|
|
7884
8673
|
case "loft":
|
|
7885
8674
|
return lowerShapeLoftCompilePlan(plan, wasm);
|
|
7886
8675
|
case "sweep":
|
|
@@ -7915,6 +8704,10 @@ function lowerShapeCompilePlanToManifold(plan, wasm) {
|
|
|
7915
8704
|
}
|
|
7916
8705
|
case "fromSlices":
|
|
7917
8706
|
return lowerFromSlicesToManifold(plan, wasm);
|
|
8707
|
+
case "nurbsSurface":
|
|
8708
|
+
return lowerNurbsSurfaceToManifold(plan, wasm);
|
|
8709
|
+
case "importedStep":
|
|
8710
|
+
throw new Error(`importStep("${plan.filePath}") requires the OCCT backend. Add setActiveBackend('occt') at the top of your script.`);
|
|
7918
8711
|
default:
|
|
7919
8712
|
assertExhaustive(plan);
|
|
7920
8713
|
}
|
|
@@ -7941,17 +8734,27 @@ function lowerSdfToManifold(evalFn, bounds, edgeLength2, wasm) {
|
|
|
7941
8734
|
vertProperties: vertProps6,
|
|
7942
8735
|
triVerts
|
|
7943
8736
|
});
|
|
7944
|
-
|
|
8737
|
+
try {
|
|
8738
|
+
return new wasm.Manifold(wasmMesh);
|
|
8739
|
+
} finally {
|
|
8740
|
+
disposeWasmObject(wasmMesh);
|
|
8741
|
+
}
|
|
7945
8742
|
}
|
|
7946
8743
|
function simplifySdfMesh(triVerts, vertProperties, edgeLength2, wasm) {
|
|
7947
8744
|
const maxError = edgeLength2 * 0.15;
|
|
7948
8745
|
for (const ratio of [0.5, 0.75]) {
|
|
7949
8746
|
let simplified = simplifyMesh(triVerts, vertProperties, ratio, maxError);
|
|
7950
8747
|
simplified = filterDegenerateTriangles(simplified);
|
|
8748
|
+
let mesh = null;
|
|
8749
|
+
let manifold = null;
|
|
7951
8750
|
try {
|
|
7952
|
-
|
|
8751
|
+
mesh = new wasm.Mesh({ numProp: 3, vertProperties, triVerts: simplified });
|
|
8752
|
+
manifold = new wasm.Manifold(mesh);
|
|
7953
8753
|
return simplified;
|
|
7954
8754
|
} catch {
|
|
8755
|
+
} finally {
|
|
8756
|
+
disposeWasmObject(manifold);
|
|
8757
|
+
disposeWasmObject(mesh);
|
|
7955
8758
|
}
|
|
7956
8759
|
}
|
|
7957
8760
|
return triVerts;
|
|
@@ -8011,6 +8814,58 @@ function projectVerticesToSurfaceWithNormals(vertProperties, sdfFn, out6) {
|
|
|
8011
8814
|
}
|
|
8012
8815
|
}
|
|
8013
8816
|
}
|
|
8817
|
+
function lowerNurbsSurfaceToManifold(plan, wasm) {
|
|
8818
|
+
const surface = new NurbsSurface(plan.controlGrid, {
|
|
8819
|
+
degreeU: plan.degreeU,
|
|
8820
|
+
degreeV: plan.degreeV,
|
|
8821
|
+
weights: plan.weightsGrid,
|
|
8822
|
+
knotsU: plan.knotsU,
|
|
8823
|
+
knotsV: plan.knotsV
|
|
8824
|
+
});
|
|
8825
|
+
const res = plan.resolution;
|
|
8826
|
+
const { positions, normals, indices } = surface.tessellate(res, res);
|
|
8827
|
+
const thickness = plan.thickness;
|
|
8828
|
+
const numVerts = positions.length;
|
|
8829
|
+
const allPositions = [];
|
|
8830
|
+
for (const [x, y, z] of positions) allPositions.push(x, y, z);
|
|
8831
|
+
for (let i = 0; i < numVerts; i++) {
|
|
8832
|
+
const [x, y, z] = positions[i];
|
|
8833
|
+
const [nx, ny, nz] = normals[i];
|
|
8834
|
+
allPositions.push(x - nx * thickness, y - ny * thickness, z - nz * thickness);
|
|
8835
|
+
}
|
|
8836
|
+
const allIndices = [];
|
|
8837
|
+
for (const idx of indices) allIndices.push(idx);
|
|
8838
|
+
for (let i = 0; i < indices.length; i += 3) {
|
|
8839
|
+
allIndices.push(indices[i] + numVerts, indices[i + 2] + numVerts, indices[i + 1] + numVerts);
|
|
8840
|
+
}
|
|
8841
|
+
const resU = res, resV = res;
|
|
8842
|
+
for (let i = 0; i < resU; i++) {
|
|
8843
|
+
const a = i * (resV + 1);
|
|
8844
|
+
const b = (i + 1) * (resV + 1);
|
|
8845
|
+
allIndices.push(a, a + numVerts, b, b, a + numVerts, b + numVerts);
|
|
8846
|
+
const c = a + resV;
|
|
8847
|
+
const d = b + resV;
|
|
8848
|
+
allIndices.push(c, d, c + numVerts, d, d + numVerts, c + numVerts);
|
|
8849
|
+
}
|
|
8850
|
+
for (let j = 0; j < resV; j++) {
|
|
8851
|
+
const a = j;
|
|
8852
|
+
const b = j + 1;
|
|
8853
|
+
allIndices.push(a, b, a + numVerts, b, b + numVerts, a + numVerts);
|
|
8854
|
+
const c = resU * (resV + 1) + j;
|
|
8855
|
+
const d = c + 1;
|
|
8856
|
+
allIndices.push(c, c + numVerts, d, d, c + numVerts, d + numVerts);
|
|
8857
|
+
}
|
|
8858
|
+
const mesh = new wasm.Mesh({
|
|
8859
|
+
numProp: 3,
|
|
8860
|
+
vertProperties: new Float32Array(allPositions),
|
|
8861
|
+
triVerts: new Uint32Array(allIndices)
|
|
8862
|
+
});
|
|
8863
|
+
try {
|
|
8864
|
+
return new wasm.Manifold(mesh);
|
|
8865
|
+
} finally {
|
|
8866
|
+
disposeWasmObject(mesh);
|
|
8867
|
+
}
|
|
8868
|
+
}
|
|
8014
8869
|
function lowerImportedMeshToManifold(fileData, format, filePath, wasm) {
|
|
8015
8870
|
const parsed = parseMeshFile(fileData, format);
|
|
8016
8871
|
if (parsed.triVerts.length === 0) {
|
|
@@ -8029,6 +8884,8 @@ function lowerImportedMeshToManifold(fileData, format, filePath, wasm) {
|
|
|
8029
8884
|
throw new Error(
|
|
8030
8885
|
`importMesh("${filePath}"): Manifold rejected the mesh — it may be non-manifold (non-watertight, self-intersecting, or degenerate). Original error: ${e instanceof Error ? e.message : String(e)}`
|
|
8031
8886
|
);
|
|
8887
|
+
} finally {
|
|
8888
|
+
disposeWasmObject(wasmMesh);
|
|
8032
8889
|
}
|
|
8033
8890
|
}
|
|
8034
8891
|
function lowerShapeCompilePlanToShapeBackend(plan, wasm) {
|
|
@@ -8196,6 +9053,11 @@ function getOCCT() {
|
|
|
8196
9053
|
if (!_occt) throw new Error("OCCT not initialized — call initOCCT() first");
|
|
8197
9054
|
return _occt;
|
|
8198
9055
|
}
|
|
9056
|
+
function getOcctHeapBytes() {
|
|
9057
|
+
if (!_occt) return null;
|
|
9058
|
+
const heap = _occt.HEAPU8;
|
|
9059
|
+
return heap ? heap.buffer.byteLength : null;
|
|
9060
|
+
}
|
|
8199
9061
|
const DEFAULT_LINEAR_DEFLECTION = 0.1;
|
|
8200
9062
|
const DEFAULT_ANGULAR_DEFLECTION = 0.5;
|
|
8201
9063
|
const EDGE_CURVE_ANGULAR_DEFLECTION = 5 * Math.PI / 180;
|
|
@@ -8409,22 +9271,35 @@ function findEdgeByMidpoint(oc, shape, midpoint2) {
|
|
|
8409
9271
|
}
|
|
8410
9272
|
_c = SHAPE_BACKEND_MARKER;
|
|
8411
9273
|
const _OCCTShapeBackend = class _OCCTShapeBackend {
|
|
8412
|
-
constructor(
|
|
9274
|
+
constructor(shapeOrResource) {
|
|
8413
9275
|
__publicField(this, _c, true);
|
|
8414
|
-
this
|
|
9276
|
+
__publicField(this, "resource");
|
|
9277
|
+
__publicField(this, "released", false);
|
|
9278
|
+
this.resource = shapeOrResource && typeof shapeOrResource === "object" && "refCount" in shapeOrResource && "shape" in shapeOrResource ? shapeOrResource : {
|
|
9279
|
+
shape: shapeOrResource,
|
|
9280
|
+
refCount: 1,
|
|
9281
|
+
disposed: false
|
|
9282
|
+
};
|
|
9283
|
+
}
|
|
9284
|
+
getLiveShape(apiName = "OCCTShapeBackend") {
|
|
9285
|
+
if (this.released || this.resource.disposed) {
|
|
9286
|
+
throw new Error(`${apiName}: OCCT backend was already disposed`);
|
|
9287
|
+
}
|
|
9288
|
+
return this.resource.shape;
|
|
8415
9289
|
}
|
|
8416
9290
|
/** Access the underlying TopoDS_Shape. */
|
|
8417
9291
|
get shape() {
|
|
8418
|
-
return this.
|
|
9292
|
+
return this.getLiveShape("shape");
|
|
8419
9293
|
}
|
|
8420
9294
|
clone() {
|
|
8421
|
-
|
|
9295
|
+
this.resource.refCount += 1;
|
|
9296
|
+
return new _OCCTShapeBackend(this.resource);
|
|
8422
9297
|
}
|
|
8423
9298
|
translate(x, y, z) {
|
|
8424
9299
|
const oc = getOCCT();
|
|
8425
9300
|
const trsf = new oc.gp_Trsf_1();
|
|
8426
9301
|
trsf.SetTranslation_1(new oc.gp_Vec_4(x, y, z));
|
|
8427
|
-
const transformed = new oc.BRepBuilderAPI_Transform_2(this.
|
|
9302
|
+
const transformed = new oc.BRepBuilderAPI_Transform_2(this.getLiveShape("translate()"), trsf, true);
|
|
8428
9303
|
return new _OCCTShapeBackend(transformed.Shape());
|
|
8429
9304
|
}
|
|
8430
9305
|
rotate(x, y, z) {
|
|
@@ -8432,21 +9307,21 @@ const _OCCTShapeBackend = class _OCCTShapeBackend {
|
|
|
8432
9307
|
}
|
|
8433
9308
|
transform(m) {
|
|
8434
9309
|
const oc = getOCCT();
|
|
8435
|
-
return new _OCCTShapeBackend(applyTransform(oc, this.
|
|
9310
|
+
return new _OCCTShapeBackend(applyTransform(oc, this.getLiveShape("transform()"), m));
|
|
8436
9311
|
}
|
|
8437
9312
|
scale(v) {
|
|
8438
9313
|
const oc = getOCCT();
|
|
8439
9314
|
if (typeof v === "number") {
|
|
8440
9315
|
const trsf = new oc.gp_Trsf_1();
|
|
8441
9316
|
trsf.SetScaleFactor(v);
|
|
8442
|
-
const transformed2 = new oc.BRepBuilderAPI_Transform_2(this.
|
|
9317
|
+
const transformed2 = new oc.BRepBuilderAPI_Transform_2(this.getLiveShape("scale()"), trsf, true);
|
|
8443
9318
|
return new _OCCTShapeBackend(transformed2.Shape());
|
|
8444
9319
|
}
|
|
8445
9320
|
const gtrsf = new oc.gp_GTrsf_1();
|
|
8446
9321
|
gtrsf.SetValue(1, 1, v[0]);
|
|
8447
9322
|
gtrsf.SetValue(2, 2, v[1]);
|
|
8448
9323
|
gtrsf.SetValue(3, 3, v[2]);
|
|
8449
|
-
const transformed = new oc.BRepBuilderAPI_GTransform_2(this.
|
|
9324
|
+
const transformed = new oc.BRepBuilderAPI_GTransform_2(this.getLiveShape("scale()"), gtrsf, true);
|
|
8450
9325
|
return new _OCCTShapeBackend(transformed.Shape());
|
|
8451
9326
|
}
|
|
8452
9327
|
mirror(normal) {
|
|
@@ -8454,15 +9329,15 @@ const _OCCTShapeBackend = class _OCCTShapeBackend {
|
|
|
8454
9329
|
const ax2 = new oc.gp_Ax2_3(new oc.gp_Pnt_3(0, 0, 0), new oc.gp_Dir_4(normal[0], normal[1], normal[2]));
|
|
8455
9330
|
const trsf = new oc.gp_Trsf_1();
|
|
8456
9331
|
trsf.SetMirror_3(ax2);
|
|
8457
|
-
const transformed = new oc.BRepBuilderAPI_Transform_2(this.
|
|
9332
|
+
const transformed = new oc.BRepBuilderAPI_Transform_2(this.getLiveShape("mirror()"), trsf, true);
|
|
8458
9333
|
return new _OCCTShapeBackend(transformed.Shape());
|
|
8459
9334
|
}
|
|
8460
9335
|
split(other) {
|
|
8461
9336
|
const oc = getOCCT();
|
|
8462
9337
|
const otherShape = requireOCCTShape(other, "split()");
|
|
8463
|
-
const inside = new oc.BRepAlgoAPI_Common_3(this.
|
|
9338
|
+
const inside = new oc.BRepAlgoAPI_Common_3(this.getLiveShape("split()"), otherShape, new oc.Message_ProgressRange_1());
|
|
8464
9339
|
inside.Build(new oc.Message_ProgressRange_1());
|
|
8465
|
-
const outside = new oc.BRepAlgoAPI_Cut_3(this.
|
|
9340
|
+
const outside = new oc.BRepAlgoAPI_Cut_3(this.getLiveShape("split()"), otherShape, new oc.Message_ProgressRange_1());
|
|
8466
9341
|
outside.Build(new oc.Message_ProgressRange_1());
|
|
8467
9342
|
return [new _OCCTShapeBackend(inside.Shape()), new _OCCTShapeBackend(outside.Shape())];
|
|
8468
9343
|
}
|
|
@@ -8474,9 +9349,9 @@ const _OCCTShapeBackend = class _OCCTShapeBackend {
|
|
|
8474
9349
|
new oc.BRepBuilderAPI_MakeFace_9(pln, -1e6, 1e6, -1e6, 1e6).Face(),
|
|
8475
9350
|
new oc.gp_Pnt_3(normal[0] * (originOffset + 1), normal[1] * (originOffset + 1), normal[2] * (originOffset + 1))
|
|
8476
9351
|
);
|
|
8477
|
-
const inside = new oc.BRepAlgoAPI_Common_3(this.
|
|
9352
|
+
const inside = new oc.BRepAlgoAPI_Common_3(this.getLiveShape("splitByPlane()"), halfSpace.Solid(), new oc.Message_ProgressRange_1());
|
|
8478
9353
|
inside.Build(new oc.Message_ProgressRange_1());
|
|
8479
|
-
const outside = new oc.BRepAlgoAPI_Cut_3(this.
|
|
9354
|
+
const outside = new oc.BRepAlgoAPI_Cut_3(this.getLiveShape("splitByPlane()"), halfSpace.Solid(), new oc.Message_ProgressRange_1());
|
|
8480
9355
|
outside.Build(new oc.Message_ProgressRange_1());
|
|
8481
9356
|
return [new _OCCTShapeBackend(inside.Shape()), new _OCCTShapeBackend(outside.Shape())];
|
|
8482
9357
|
}
|
|
@@ -8485,28 +9360,32 @@ const _OCCTShapeBackend = class _OCCTShapeBackend {
|
|
|
8485
9360
|
return inside;
|
|
8486
9361
|
}
|
|
8487
9362
|
boundingBox() {
|
|
8488
|
-
return extractBoundingBox(getOCCT(), this.
|
|
9363
|
+
return extractBoundingBox(getOCCT(), this.getLiveShape("boundingBox()"));
|
|
8489
9364
|
}
|
|
8490
9365
|
volume() {
|
|
8491
9366
|
const oc = getOCCT();
|
|
8492
9367
|
const props = new oc.GProp_GProps_1();
|
|
8493
|
-
oc.BRepGProp.VolumeProperties_1(this.
|
|
9368
|
+
oc.BRepGProp.VolumeProperties_1(this.getLiveShape("volume()"), props, false, false, false);
|
|
8494
9369
|
return props.Mass();
|
|
8495
9370
|
}
|
|
8496
9371
|
surfaceArea() {
|
|
8497
9372
|
const oc = getOCCT();
|
|
8498
9373
|
const props = new oc.GProp_GProps_1();
|
|
8499
|
-
oc.BRepGProp.SurfaceProperties_1(this.
|
|
9374
|
+
oc.BRepGProp.SurfaceProperties_1(this.getLiveShape("surfaceArea()"), props, false, false);
|
|
8500
9375
|
return props.Mass();
|
|
8501
9376
|
}
|
|
8502
9377
|
isEmpty() {
|
|
8503
|
-
|
|
8504
|
-
return
|
|
9378
|
+
const shape = this.getLiveShape("isEmpty()");
|
|
9379
|
+
return shape.IsNull() || shape.NbChildren() === 0;
|
|
8505
9380
|
}
|
|
8506
9381
|
numBodies() {
|
|
8507
9382
|
const oc = getOCCT();
|
|
8508
9383
|
let count = 0;
|
|
8509
|
-
const expl = new oc.TopExp_Explorer_2(
|
|
9384
|
+
const expl = new oc.TopExp_Explorer_2(
|
|
9385
|
+
this.getLiveShape("numBodies()"),
|
|
9386
|
+
oc.TopAbs_ShapeEnum.TopAbs_SOLID,
|
|
9387
|
+
oc.TopAbs_ShapeEnum.TopAbs_SHAPE
|
|
9388
|
+
);
|
|
8510
9389
|
while (expl.More()) {
|
|
8511
9390
|
count++;
|
|
8512
9391
|
expl.Next();
|
|
@@ -8517,14 +9396,14 @@ const _OCCTShapeBackend = class _OCCTShapeBackend {
|
|
|
8517
9396
|
return this.getMesh().numTri;
|
|
8518
9397
|
}
|
|
8519
9398
|
getMesh() {
|
|
8520
|
-
return extractMeshFromShape(getOCCT(), this.
|
|
9399
|
+
return extractMeshFromShape(getOCCT(), this.getLiveShape("getMesh()")).mesh;
|
|
8521
9400
|
}
|
|
8522
9401
|
/**
|
|
8523
9402
|
* Extract mesh with per-vertex normals from the B-rep surface.
|
|
8524
9403
|
* Returns both the Manifold-compatible mesh and smooth normals.
|
|
8525
9404
|
*/
|
|
8526
9405
|
getMeshWithNormals() {
|
|
8527
|
-
return extractMeshFromShape(getOCCT(), this.
|
|
9406
|
+
return extractMeshFromShape(getOCCT(), this.getLiveShape("getMeshWithNormals()"));
|
|
8528
9407
|
}
|
|
8529
9408
|
/**
|
|
8530
9409
|
* Extract smooth edge curves from the B-rep topology.
|
|
@@ -8532,7 +9411,7 @@ const _OCCTShapeBackend = class _OCCTShapeBackend {
|
|
|
8532
9411
|
* as geometryEdgePositions (pairs of xyz points, 6 floats per segment).
|
|
8533
9412
|
*/
|
|
8534
9413
|
getEdgeCurves() {
|
|
8535
|
-
return extractEdgeCurvesFromShape(getOCCT(), this.
|
|
9414
|
+
return extractEdgeCurvesFromShape(getOCCT(), this.getLiveShape("getEdgeCurves()"));
|
|
8536
9415
|
}
|
|
8537
9416
|
slice(_offset) {
|
|
8538
9417
|
throw new Error("slice() not yet implemented for OCCT backend");
|
|
@@ -8542,9 +9421,9 @@ const _OCCTShapeBackend = class _OCCTShapeBackend {
|
|
|
8542
9421
|
}
|
|
8543
9422
|
filletEdgeByMidpoint(edge, radius) {
|
|
8544
9423
|
const oc = getOCCT();
|
|
8545
|
-
const matchedEdge = findEdgeByMidpoint(oc, this.
|
|
9424
|
+
const matchedEdge = findEdgeByMidpoint(oc, this.getLiveShape("filletEdgeByMidpoint()"), edge.midpoint);
|
|
8546
9425
|
if (!matchedEdge) throw new Error("OCCT filletEdgeByMidpoint: could not find matching edge");
|
|
8547
|
-
const mkFillet = new oc.BRepFilletAPI_MakeFillet(this.
|
|
9426
|
+
const mkFillet = new oc.BRepFilletAPI_MakeFillet(this.getLiveShape("filletEdgeByMidpoint()"), oc.ChFi3d_FilletShape.ChFi3d_Rational);
|
|
8548
9427
|
mkFillet.Add_2(radius, matchedEdge);
|
|
8549
9428
|
mkFillet.Build(new oc.Message_ProgressRange_1());
|
|
8550
9429
|
if (!mkFillet.IsDone()) {
|
|
@@ -8554,14 +9433,24 @@ const _OCCTShapeBackend = class _OCCTShapeBackend {
|
|
|
8554
9433
|
}
|
|
8555
9434
|
chamferEdgeByMidpoint(edge, size) {
|
|
8556
9435
|
const oc = getOCCT();
|
|
8557
|
-
const matchedEdge = findEdgeByMidpoint(oc, this.
|
|
9436
|
+
const matchedEdge = findEdgeByMidpoint(oc, this.getLiveShape("chamferEdgeByMidpoint()"), edge.midpoint);
|
|
8558
9437
|
if (!matchedEdge) throw new Error("OCCT chamferEdgeByMidpoint: could not find matching edge");
|
|
8559
|
-
const mkChamfer = new oc.BRepFilletAPI_MakeChamfer(this.
|
|
9438
|
+
const mkChamfer = new oc.BRepFilletAPI_MakeChamfer(this.getLiveShape("chamferEdgeByMidpoint()"));
|
|
8560
9439
|
mkChamfer.Add_2(size, matchedEdge);
|
|
8561
9440
|
mkChamfer.Build(new oc.Message_ProgressRange_1());
|
|
8562
9441
|
if (!mkChamfer.IsDone()) throw new Error("OCCT chamfer operation failed");
|
|
8563
9442
|
return new _OCCTShapeBackend(mkChamfer.Shape());
|
|
8564
9443
|
}
|
|
9444
|
+
dispose() {
|
|
9445
|
+
var _a3, _b3;
|
|
9446
|
+
if (this.released) return;
|
|
9447
|
+
this.released = true;
|
|
9448
|
+
this.resource.refCount = Math.max(0, this.resource.refCount - 1);
|
|
9449
|
+
if (this.resource.refCount === 0 && !this.resource.disposed) {
|
|
9450
|
+
this.resource.disposed = true;
|
|
9451
|
+
(_b3 = (_a3 = this.resource.shape).delete) == null ? void 0 : _b3.call(_a3);
|
|
9452
|
+
}
|
|
9453
|
+
}
|
|
8565
9454
|
};
|
|
8566
9455
|
let OCCTShapeBackend = _OCCTShapeBackend;
|
|
8567
9456
|
function wrapOCCTShapeBackend(shape) {
|
|
@@ -8893,6 +9782,70 @@ function buildWireFromPoints(oc, points) {
|
|
|
8893
9782
|
}
|
|
8894
9783
|
return mkWire.Wire();
|
|
8895
9784
|
}
|
|
9785
|
+
function buildFaceFromProfileEdges(oc, plan) {
|
|
9786
|
+
try {
|
|
9787
|
+
const mkWire = new oc.BRepBuilderAPI_MakeWire_1();
|
|
9788
|
+
for (const edge of plan.edges) {
|
|
9789
|
+
switch (edge.kind) {
|
|
9790
|
+
case "line": {
|
|
9791
|
+
if (Math.abs(edge.x1 - edge.x2) < 1e-10 && Math.abs(edge.y1 - edge.y2) < 1e-10) continue;
|
|
9792
|
+
const e = new oc.BRepBuilderAPI_MakeEdge_3(
|
|
9793
|
+
new oc.gp_Pnt_3(edge.x1, edge.y1, 0),
|
|
9794
|
+
new oc.gp_Pnt_3(edge.x2, edge.y2, 0)
|
|
9795
|
+
).Edge();
|
|
9796
|
+
mkWire.Add_1(e);
|
|
9797
|
+
break;
|
|
9798
|
+
}
|
|
9799
|
+
case "arc": {
|
|
9800
|
+
const dx1 = edge.x1 - edge.cx, dy1 = edge.y1 - edge.cy;
|
|
9801
|
+
const r = Math.hypot(dx1, dy1);
|
|
9802
|
+
const startAngle = Math.atan2(dy1, dx1);
|
|
9803
|
+
const dx2 = edge.x2 - edge.cx, dy2 = edge.y2 - edge.cy;
|
|
9804
|
+
let endAngle = Math.atan2(dy2, dx2);
|
|
9805
|
+
if (edge.clockwise) {
|
|
9806
|
+
if (endAngle >= startAngle) endAngle -= 2 * Math.PI;
|
|
9807
|
+
} else {
|
|
9808
|
+
if (endAngle <= startAngle) endAngle += 2 * Math.PI;
|
|
9809
|
+
}
|
|
9810
|
+
const axis = new oc.gp_Ax2_3(new oc.gp_Pnt_3(edge.cx, edge.cy, 0), new oc.gp_Dir_4(0, 0, 1));
|
|
9811
|
+
const circ = new oc.gp_Circ_2(axis, r);
|
|
9812
|
+
const e = new oc.BRepBuilderAPI_MakeEdge_9(circ, startAngle, endAngle).Edge();
|
|
9813
|
+
mkWire.Add_1(e);
|
|
9814
|
+
break;
|
|
9815
|
+
}
|
|
9816
|
+
case "bspline": {
|
|
9817
|
+
const n = edge.controlPoints.length;
|
|
9818
|
+
const poles = new oc.TColgp_Array1OfPnt_2(1, n);
|
|
9819
|
+
for (let i = 0; i < n; i++) {
|
|
9820
|
+
poles.SetValue(i + 1, new oc.gp_Pnt_3(edge.controlPoints[i][0], edge.controlPoints[i][1], 0));
|
|
9821
|
+
}
|
|
9822
|
+
const wts = new oc.TColStd_Array1OfReal_2(1, n);
|
|
9823
|
+
for (let i = 0; i < n; i++) {
|
|
9824
|
+
wts.SetValue(i + 1, edge.weights[i]);
|
|
9825
|
+
}
|
|
9826
|
+
const { knots, mults } = flatKnotsToKnotsMults(edge.knots);
|
|
9827
|
+
const knotsArr = new oc.TColStd_Array1OfReal_2(1, knots.length);
|
|
9828
|
+
for (let i = 0; i < knots.length; i++) {
|
|
9829
|
+
knotsArr.SetValue(i + 1, knots[i]);
|
|
9830
|
+
}
|
|
9831
|
+
const multsArr = new oc.TColStd_Array1OfInteger_2(1, mults.length);
|
|
9832
|
+
for (let i = 0; i < mults.length; i++) {
|
|
9833
|
+
multsArr.SetValue(i + 1, mults[i]);
|
|
9834
|
+
}
|
|
9835
|
+
const bspline = new oc.Geom_BSplineCurve_1(poles, wts, knotsArr, multsArr, edge.degree, false);
|
|
9836
|
+
const curveHandle = new oc.Handle_Geom_Curve_2(bspline);
|
|
9837
|
+
const e = new oc.BRepBuilderAPI_MakeEdge_24(curveHandle).Edge();
|
|
9838
|
+
mkWire.Add_1(e);
|
|
9839
|
+
break;
|
|
9840
|
+
}
|
|
9841
|
+
}
|
|
9842
|
+
}
|
|
9843
|
+
return buildFaceFromWire(oc, mkWire.Wire());
|
|
9844
|
+
} catch {
|
|
9845
|
+
const wire = buildWireFromPoints(oc, plan.points);
|
|
9846
|
+
return buildFaceFromWire(oc, wire);
|
|
9847
|
+
}
|
|
9848
|
+
}
|
|
8896
9849
|
function buildFaceFromWire(oc, wire) {
|
|
8897
9850
|
const face = new oc.BRepBuilderAPI_MakeFace_15(wire, true);
|
|
8898
9851
|
return face.Face();
|
|
@@ -9056,6 +10009,10 @@ function lowerProfileToFace(oc, plan) {
|
|
|
9056
10009
|
}
|
|
9057
10010
|
case "project":
|
|
9058
10011
|
throw new OCCTUnsupportedError("profile project");
|
|
10012
|
+
case "pathProfile": {
|
|
10013
|
+
face = buildFaceFromProfileEdges(oc, plan);
|
|
10014
|
+
break;
|
|
10015
|
+
}
|
|
9059
10016
|
default:
|
|
9060
10017
|
assertExhaustive(plan);
|
|
9061
10018
|
}
|
|
@@ -9524,6 +10481,36 @@ function _lowerShapeCompilePlanToOCCTInner(plan, oc) {
|
|
|
9524
10481
|
throw new Error("SDF shapes require the Manifold backend. Add setActiveBackend('manifold') at the top of your script.");
|
|
9525
10482
|
case "fromSlices":
|
|
9526
10483
|
return lowerFromSlicesPlan(oc, plan);
|
|
10484
|
+
case "nurbsSurface":
|
|
10485
|
+
return lowerNurbsSurfacePlan();
|
|
10486
|
+
case "importedStep":
|
|
10487
|
+
return lowerImportedStepPlan(oc, plan);
|
|
10488
|
+
}
|
|
10489
|
+
}
|
|
10490
|
+
function lowerNurbsSurfacePlan(_oc, _plan) {
|
|
10491
|
+
throw new Error("NURBS surfaces are not yet supported by the OCCT backend. Use setActiveBackend('manifold').");
|
|
10492
|
+
}
|
|
10493
|
+
function lowerImportedStepPlan(oc, plan) {
|
|
10494
|
+
const virtualPath = `/tmp/forgecad-step-import-${Date.now()}.step`;
|
|
10495
|
+
const bytes = new Uint8Array(plan.fileData);
|
|
10496
|
+
oc.FS.writeFile(virtualPath, bytes);
|
|
10497
|
+
try {
|
|
10498
|
+
const reader = new oc.STEPControl_Reader_1();
|
|
10499
|
+
const readStatus = reader.ReadFile(virtualPath);
|
|
10500
|
+
if (readStatus !== oc.IFSelect_ReturnStatus.IFSelect_RetDone) {
|
|
10501
|
+
throw new Error(`importStep("${plan.filePath}"): Failed to read STEP file (status ${readStatus}).`);
|
|
10502
|
+
}
|
|
10503
|
+
reader.TransferRoots(new oc.Message_ProgressRange_1());
|
|
10504
|
+
const shape = reader.OneShape();
|
|
10505
|
+
if (!shape || shape.IsNull()) {
|
|
10506
|
+
throw new Error(`importStep("${plan.filePath}"): STEP file produced no geometry.`);
|
|
10507
|
+
}
|
|
10508
|
+
return shape;
|
|
10509
|
+
} finally {
|
|
10510
|
+
try {
|
|
10511
|
+
oc.FS.unlink(virtualPath);
|
|
10512
|
+
} catch {
|
|
10513
|
+
}
|
|
9527
10514
|
}
|
|
9528
10515
|
}
|
|
9529
10516
|
function extractOuterWire(oc, face) {
|
|
@@ -9647,6 +10634,7 @@ function buildSpineWireFromPlan(oc, path2, pathSamples) {
|
|
|
9647
10634
|
case "catmull-rom":
|
|
9648
10635
|
case "hermite":
|
|
9649
10636
|
case "quintic-hermite":
|
|
10637
|
+
case "nurbs":
|
|
9650
10638
|
return buildPolylineSpineWire(oc, sweepPathToPolyline(path2, pathSamples ?? 48));
|
|
9651
10639
|
}
|
|
9652
10640
|
}
|
|
@@ -10028,6 +11016,8 @@ function summarizeProfile(profile) {
|
|
|
10028
11016
|
return "offset profile";
|
|
10029
11017
|
case "project":
|
|
10030
11018
|
return "projected profile";
|
|
11019
|
+
case "pathProfile":
|
|
11020
|
+
return `path profile (${profile.edges.length} edges)`;
|
|
10031
11021
|
default:
|
|
10032
11022
|
assertExhaustive(profile);
|
|
10033
11023
|
}
|
|
@@ -11650,6 +12640,8 @@ function resolveShapeFaceTableInternal(plan, owner) {
|
|
|
11650
12640
|
case "importedMesh":
|
|
11651
12641
|
case "sdf":
|
|
11652
12642
|
case "fromSlices":
|
|
12643
|
+
case "nurbsSurface":
|
|
12644
|
+
case "importedStep":
|
|
11653
12645
|
return emptyFaceTable();
|
|
11654
12646
|
default:
|
|
11655
12647
|
assertExhaustive(plan);
|
|
@@ -12153,7 +13145,7 @@ function transformPortMap(ports, matrix) {
|
|
|
12153
13145
|
}
|
|
12154
13146
|
return out;
|
|
12155
13147
|
}
|
|
12156
|
-
function computeConnectFrame(childBase, childPort, parentPort,
|
|
13148
|
+
function computeConnectFrame(childBase, childPort, parentPort, _flip, childAlign = "middle", parentAlign = "middle") {
|
|
12157
13149
|
const childAlignPt = resolvePortAlignPoint(childPort, childAlign);
|
|
12158
13150
|
const parentAlignPt = resolvePortAlignPoint(parentPort, parentAlign);
|
|
12159
13151
|
const cI = childBase.point(childAlignPt);
|
|
@@ -12161,7 +13153,9 @@ function computeConnectFrame(childBase, childPort, parentPort, flip, childAlign
|
|
|
12161
13153
|
const cUp = normalize3$2(childBase.vector(childPort.up));
|
|
12162
13154
|
const cRight = normalize3$2(cross3$2(cAxis, cUp));
|
|
12163
13155
|
const pOrigin = parentAlignPt;
|
|
12164
|
-
const
|
|
13156
|
+
const jointKind = childPort.kind ?? parentPort.kind;
|
|
13157
|
+
const faceToFace = jointKind !== "prismatic";
|
|
13158
|
+
const pAxis = faceToFace ? negate3$1(parentPort.axis) : [...parentPort.axis];
|
|
12165
13159
|
const pUp = [...parentPort.up];
|
|
12166
13160
|
const pRight = normalize3$2(cross3$2(pAxis, pUp));
|
|
12167
13161
|
const r00 = pRight[0] * cRight[0] + pUp[0] * cUp[0] + pAxis[0] * cAxis[0];
|
|
@@ -14001,6 +14995,8 @@ function rootTopologyRewritePropagation(plan) {
|
|
|
14001
14995
|
case "importedMesh":
|
|
14002
14996
|
case "sdf":
|
|
14003
14997
|
case "fromSlices":
|
|
14998
|
+
case "nurbsSurface":
|
|
14999
|
+
case "importedStep":
|
|
14004
15000
|
return null;
|
|
14005
15001
|
default:
|
|
14006
15002
|
assertExhaustive(plan);
|
|
@@ -14424,6 +15420,8 @@ function rootPlanPropagation(plan) {
|
|
|
14424
15420
|
case "importedMesh":
|
|
14425
15421
|
case "sdf":
|
|
14426
15422
|
case "fromSlices":
|
|
15423
|
+
case "nurbsSurface":
|
|
15424
|
+
case "importedStep":
|
|
14427
15425
|
return void 0;
|
|
14428
15426
|
default:
|
|
14429
15427
|
assertExhaustive(plan);
|
|
@@ -16268,46 +17266,82 @@ function injectVoronoiSurfaceChild(children) {
|
|
|
16268
17266
|
return child;
|
|
16269
17267
|
});
|
|
16270
17268
|
}
|
|
16271
|
-
|
|
16272
|
-
|
|
16273
|
-
|
|
16274
|
-
|
|
16275
|
-
basketWeave,
|
|
16276
|
-
bend,
|
|
16277
|
-
blend,
|
|
17269
|
+
const sdf = {
|
|
17270
|
+
/** Create an SDF sphere centered at the origin. */
|
|
17271
|
+
sphere: sphere$1,
|
|
17272
|
+
/** Create an SDF box centered at the origin with given full dimensions (not half-extents). */
|
|
16278
17273
|
box: box$1,
|
|
16279
|
-
|
|
17274
|
+
/** Create an SDF cylinder centered at the origin, axis along Z. */
|
|
17275
|
+
cylinder: cylinder$1,
|
|
17276
|
+
/** Create an SDF torus centered at the origin, lying in the XY plane. */
|
|
17277
|
+
torus: torus$1,
|
|
17278
|
+
/** Create an SDF capsule centered at the origin, axis along Z. */
|
|
16280
17279
|
capsule,
|
|
17280
|
+
/** Create an SDF cone with base at z=0 and tip at z=height. */
|
|
16281
17281
|
cone,
|
|
16282
|
-
|
|
16283
|
-
|
|
16284
|
-
|
|
17282
|
+
/** Smooth union — blends shapes together with a smooth transition radius. */
|
|
17283
|
+
smoothUnion,
|
|
17284
|
+
/** Smooth difference — smoothly subtracts b from a. */
|
|
17285
|
+
smoothDifference,
|
|
17286
|
+
/** Smooth intersection — smoothly intersects a and b. */
|
|
17287
|
+
smoothIntersection,
|
|
17288
|
+
/** Morph between two SDF shapes. t=0 → a, t=1 → b. */
|
|
17289
|
+
morph,
|
|
17290
|
+
/**
|
|
17291
|
+
* Spatially blend between two SDF patterns.
|
|
17292
|
+
* The blend function receives (x, y, z) and returns 0..1:
|
|
17293
|
+
* 0 = fully pattern `a`, 1 = fully pattern `b`.
|
|
17294
|
+
*/
|
|
17295
|
+
blend,
|
|
17296
|
+
/** Gyroid TPMS lattice — the most common lattice for additive manufacturing. */
|
|
16285
17297
|
gyroid,
|
|
16286
|
-
|
|
16287
|
-
|
|
17298
|
+
/** Schwarz-P TPMS lattice — isotropic pore structure. */
|
|
17299
|
+
schwarzP,
|
|
17300
|
+
/** Diamond TPMS lattice — stiffest TPMS structure. */
|
|
17301
|
+
diamond,
|
|
17302
|
+
/** Lidinoid TPMS lattice — visually distinct from gyroid, popular in research and art. */
|
|
16288
17303
|
lidinoid,
|
|
16289
|
-
|
|
17304
|
+
/** 3D Simplex noise field — produces organic, natural-looking displacements. */
|
|
16290
17305
|
noise,
|
|
17306
|
+
/** 3D Voronoi pattern — organic cellular structures like bone, coral, or soap bubbles. */
|
|
17307
|
+
voronoi,
|
|
17308
|
+
/** Honeycomb (hexagonal) lattice pattern. Intersect with your shape to apply. */
|
|
17309
|
+
honeycomb,
|
|
17310
|
+
/** Sinusoidal wave ridges — parallel ridges along an axis. */
|
|
17311
|
+
waves,
|
|
17312
|
+
/** Knurl pattern — crossed helical grooves for grips and handles. */
|
|
17313
|
+
knurl,
|
|
17314
|
+
/** Perforated plate pattern — regular array of cylindrical holes. */
|
|
16291
17315
|
perforated,
|
|
16292
|
-
|
|
17316
|
+
/** Fish/dragon scale pattern — overlapping circular scales in hex-packed rows. */
|
|
16293
17317
|
scales,
|
|
16294
|
-
|
|
16295
|
-
|
|
16296
|
-
|
|
16297
|
-
|
|
16298
|
-
|
|
16299
|
-
|
|
17318
|
+
/** Brick/stone wall pattern — running bond with mortar grooves. */
|
|
17319
|
+
brick,
|
|
17320
|
+
/** Grid lattice pattern — two families of infinite slabs crossing at 90°. */
|
|
17321
|
+
weave,
|
|
17322
|
+
/** Basket weave surface pattern — threads with over-under crossings in UV space. Returns a SurfacePattern for use with `.surfaceDisplace()`. */
|
|
17323
|
+
basketWeave,
|
|
17324
|
+
/** Twist an SDF shape around the Z axis. */
|
|
16300
17325
|
twist,
|
|
16301
|
-
|
|
16302
|
-
|
|
16303
|
-
|
|
16304
|
-
|
|
17326
|
+
/** Bend an SDF shape around the Z axis. */
|
|
17327
|
+
bend,
|
|
17328
|
+
/** Repeat an SDF shape in space. */
|
|
17329
|
+
repeat,
|
|
17330
|
+
/** A 2D surface pattern — a heightmap function for use with `.surfaceDisplace()`. */
|
|
17331
|
+
SurfacePattern,
|
|
17332
|
+
/** Create an SDF shape from an arbitrary distance function. You must provide bounds since the function is opaque. */
|
|
17333
|
+
fromFunction
|
|
17334
|
+
};
|
|
16305
17335
|
async function initKernel() {
|
|
16306
17336
|
initOCCT().catch((e) => console.warn("[kernel] OCCT background init failed:", e));
|
|
16307
17337
|
const [manifoldModule] = await Promise.all([initManifoldWasm(), initMeshoptimizer()]);
|
|
16308
17338
|
return manifoldModule;
|
|
16309
17339
|
}
|
|
16310
17340
|
let _activeBackend = "manifold";
|
|
17341
|
+
let _runtimeWarn = (msg) => console.warn(msg);
|
|
17342
|
+
function setRuntimeWarnSink(fn) {
|
|
17343
|
+
_runtimeWarn = fn;
|
|
17344
|
+
}
|
|
16311
17345
|
async function activateBackend(backend) {
|
|
16312
17346
|
_activeBackend = backend;
|
|
16313
17347
|
if (backend === "occt") {
|
|
@@ -17476,6 +18510,33 @@ class Shape {
|
|
|
17476
18510
|
static _unwrap(value) {
|
|
17477
18511
|
return unwrapShapeLike(value);
|
|
17478
18512
|
}
|
|
18513
|
+
/**
|
|
18514
|
+
* Warn if a boolean operation had no geometric effect.
|
|
18515
|
+
* Compares volumes before and after; if they match within tolerance, the operation was a no-op.
|
|
18516
|
+
*/
|
|
18517
|
+
static _checkBooleanNoOp(op, base, result, others) {
|
|
18518
|
+
try {
|
|
18519
|
+
if (op === "intersection") {
|
|
18520
|
+
if (result.isEmpty()) {
|
|
18521
|
+
_runtimeWarn(
|
|
18522
|
+
`intersection() produced an empty shape — the operands may not overlap.`
|
|
18523
|
+
);
|
|
18524
|
+
}
|
|
18525
|
+
return;
|
|
18526
|
+
}
|
|
18527
|
+
if (op === "difference") {
|
|
18528
|
+
const volBefore = base.volume();
|
|
18529
|
+
const volAfter = result.volume();
|
|
18530
|
+
const tol = Math.max(volBefore * 1e-4, 1e-3);
|
|
18531
|
+
if (Math.abs(volBefore - volAfter) < tol) {
|
|
18532
|
+
_runtimeWarn(
|
|
18533
|
+
`subtract() had no effect — the tool may not overlap the base shape. Base vol=${volBefore.toFixed(1)}mm³, result vol=${volAfter.toFixed(1)}mm³`
|
|
18534
|
+
);
|
|
18535
|
+
}
|
|
18536
|
+
}
|
|
18537
|
+
} catch {
|
|
18538
|
+
}
|
|
18539
|
+
}
|
|
17479
18540
|
/** Union this shape with others (additive boolean). Method form of union(). */
|
|
17480
18541
|
add(...others) {
|
|
17481
18542
|
const shapes = [
|
|
@@ -17513,7 +18574,7 @@ class Shape {
|
|
|
17513
18574
|
"boolean:difference",
|
|
17514
18575
|
(owner) => buildBooleanTopologyRewritePropagation("difference", owner, operandPlans)
|
|
17515
18576
|
);
|
|
17516
|
-
|
|
18577
|
+
const resultShape = setShapeCompilePlanInternal(
|
|
17517
18578
|
setShapeGeometryInfoInternal(
|
|
17518
18579
|
withBaseDimensions(this, buildShapeFromCompilePlan(nextPlan, this.colorHex)),
|
|
17519
18580
|
mergeGeometryInfos(
|
|
@@ -17524,6 +18585,8 @@ class Shape {
|
|
|
17524
18585
|
),
|
|
17525
18586
|
nextPlan
|
|
17526
18587
|
);
|
|
18588
|
+
Shape._checkBooleanNoOp("difference", this, resultShape, shapes.slice(1));
|
|
18589
|
+
return resultShape;
|
|
17527
18590
|
}
|
|
17528
18591
|
/** Keep only the overlap with other shapes. Method form of intersection(). */
|
|
17529
18592
|
intersect(...others) {
|
|
@@ -17542,7 +18605,7 @@ class Shape {
|
|
|
17542
18605
|
"boolean:intersection",
|
|
17543
18606
|
(owner) => buildBooleanTopologyRewritePropagation("intersection", owner, operandPlans)
|
|
17544
18607
|
);
|
|
17545
|
-
|
|
18608
|
+
const resultShape = setShapeCompilePlanInternal(
|
|
17546
18609
|
setShapeGeometryInfoInternal(
|
|
17547
18610
|
withMergedDimensions(shapes, buildShapeFromCompilePlan(nextPlan, this.colorHex)),
|
|
17548
18611
|
mergeGeometryInfos(
|
|
@@ -17553,6 +18616,8 @@ class Shape {
|
|
|
17553
18616
|
),
|
|
17554
18617
|
nextPlan
|
|
17555
18618
|
);
|
|
18619
|
+
Shape._checkBooleanNoOp("intersection", this, resultShape, shapes.slice(1));
|
|
18620
|
+
return resultShape;
|
|
17556
18621
|
}
|
|
17557
18622
|
/** Alias for add() — matches the free-function union() naming. */
|
|
17558
18623
|
union(...others) {
|
|
@@ -18154,10 +19219,14 @@ function intersection(...inputs) {
|
|
|
18154
19219
|
var define_process_env_default = {};
|
|
18155
19220
|
let _wasm_solve = null;
|
|
18156
19221
|
let _wasm_get_profile = null;
|
|
19222
|
+
let _solverMemory = null;
|
|
18157
19223
|
let _sessionApi = null;
|
|
18158
19224
|
function getSessionApi() {
|
|
18159
19225
|
return _sessionApi;
|
|
18160
19226
|
}
|
|
19227
|
+
function getSolverWasmHeapBytes() {
|
|
19228
|
+
return _solverMemory ? _solverMemory.buffer.byteLength : null;
|
|
19229
|
+
}
|
|
18161
19230
|
let _initPromise = null;
|
|
18162
19231
|
const MAX_EXCHANGE_HISTORY = 64;
|
|
18163
19232
|
const DEBUG_STORAGE_KEY = "fc:solver-debug";
|
|
@@ -18464,9 +19533,11 @@ async function initSolverWasm() {
|
|
|
18464
19533
|
}
|
|
18465
19534
|
if (!wasmPath) throw new Error("solver_bg.wasm not found — run: npm run build:solver");
|
|
18466
19535
|
const wasmBytes = readFileSync(wasmPath);
|
|
18467
|
-
await solverModule.default(wasmBytes);
|
|
19536
|
+
const exports$1 = await solverModule.default(wasmBytes);
|
|
19537
|
+
_solverMemory = (exports$1 == null ? void 0 : exports$1.memory) ?? null;
|
|
18468
19538
|
} else {
|
|
18469
|
-
await solverModule.default();
|
|
19539
|
+
const exports$1 = await solverModule.default();
|
|
19540
|
+
_solverMemory = (exports$1 == null ? void 0 : exports$1.memory) ?? null;
|
|
18470
19541
|
}
|
|
18471
19542
|
performance.mark("solver:ready");
|
|
18472
19543
|
performance.measure("solver:import", "solver:start", "solver:imported");
|
|
@@ -19453,7 +20524,7 @@ class MateBuilder {
|
|
|
19453
20524
|
return this.constraints.reduce((sum, c) => sum + (CONSTRAINT_EQUATIONS[c.type] ?? 0), 0);
|
|
19454
20525
|
}
|
|
19455
20526
|
}
|
|
19456
|
-
let _collected$
|
|
20527
|
+
let _collected$7 = null;
|
|
19457
20528
|
const isAxis = (value) => value === "x" || value === "y" || value === "z";
|
|
19458
20529
|
const normalizeDirection = (value, label) => {
|
|
19459
20530
|
if (value === "radial" || isAxis(value)) return value;
|
|
@@ -19512,16 +20583,16 @@ const mergeDirective = (target, patch, label) => {
|
|
|
19512
20583
|
return out;
|
|
19513
20584
|
};
|
|
19514
20585
|
function resetExplodeView() {
|
|
19515
|
-
_collected$
|
|
20586
|
+
_collected$7 = null;
|
|
19516
20587
|
}
|
|
19517
20588
|
function getCollectedExplodeView() {
|
|
19518
|
-
return _collected$
|
|
20589
|
+
return _collected$7 ? cloneOptions(_collected$7) : null;
|
|
19519
20590
|
}
|
|
19520
20591
|
function explodeView(options = {}) {
|
|
19521
20592
|
if (!options || typeof options !== "object") {
|
|
19522
20593
|
throw new Error("explodeView(options) expects an options object");
|
|
19523
20594
|
}
|
|
19524
|
-
const next = _collected$
|
|
20595
|
+
const next = _collected$7 ? cloneOptions(_collected$7) : {};
|
|
19525
20596
|
if (options.enabled !== void 0) {
|
|
19526
20597
|
if (typeof options.enabled !== "boolean") throw new Error("explodeView.enabled must be a boolean");
|
|
19527
20598
|
next.enabled = options.enabled;
|
|
@@ -19569,9 +20640,9 @@ function explodeView(options = {}) {
|
|
|
19569
20640
|
});
|
|
19570
20641
|
next.byPath = byPath;
|
|
19571
20642
|
}
|
|
19572
|
-
_collected$
|
|
20643
|
+
_collected$7 = next;
|
|
19573
20644
|
}
|
|
19574
|
-
let _collected$
|
|
20645
|
+
let _collected$6 = null;
|
|
19575
20646
|
const isFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value);
|
|
19576
20647
|
const isVec3$1 = (value) => Array.isArray(value) && value.length === 3 && isFiniteNumber(value[0]) && isFiniteNumber(value[1]) && isFiniteNumber(value[2]);
|
|
19577
20648
|
const normalizeAxis = (axis) => {
|
|
@@ -19861,22 +20932,22 @@ const cloneCollected = (value) => ({
|
|
|
19861
20932
|
defaultAnimation: value.defaultAnimation
|
|
19862
20933
|
});
|
|
19863
20934
|
function resetJointsView() {
|
|
19864
|
-
_collected$
|
|
20935
|
+
_collected$6 = null;
|
|
19865
20936
|
}
|
|
19866
20937
|
function getCollectedJointsView() {
|
|
19867
|
-
return _collected$
|
|
20938
|
+
return _collected$6 ? cloneCollected(_collected$6) : null;
|
|
19868
20939
|
}
|
|
19869
20940
|
function saveJointsView() {
|
|
19870
|
-
return _collected$
|
|
20941
|
+
return _collected$6 ? cloneCollected(_collected$6) : null;
|
|
19871
20942
|
}
|
|
19872
20943
|
function restoreJointsView(state) {
|
|
19873
|
-
_collected$
|
|
20944
|
+
_collected$6 = state;
|
|
19874
20945
|
}
|
|
19875
20946
|
function jointsView(options = {}) {
|
|
19876
20947
|
if (!options || typeof options !== "object") {
|
|
19877
20948
|
throw new Error("jointsView(options) expects an options object");
|
|
19878
20949
|
}
|
|
19879
|
-
const next = _collected$
|
|
20950
|
+
const next = _collected$6 ? cloneCollected(_collected$6) : { joints: [], couplings: [], animations: [] };
|
|
19880
20951
|
if (options.enabled !== void 0) {
|
|
19881
20952
|
if (typeof options.enabled !== "boolean") {
|
|
19882
20953
|
throw new Error("jointsView.enabled must be a boolean");
|
|
@@ -19931,7 +21002,7 @@ function jointsView(options = {}) {
|
|
|
19931
21002
|
if (next.defaultAnimation && !next.animations.some((animation) => animation.name === next.defaultAnimation)) {
|
|
19932
21003
|
throw new Error(`jointsView defaultAnimation "${next.defaultAnimation}" does not exist in animations`);
|
|
19933
21004
|
}
|
|
19934
|
-
_collected$
|
|
21005
|
+
_collected$6 = next;
|
|
19935
21006
|
}
|
|
19936
21007
|
function bomToCsv(rows) {
|
|
19937
21008
|
const header = ["part", "qty", "material", "process", "tolerance", "notes"];
|
|
@@ -20638,8 +21709,16 @@ class Assembly {
|
|
|
20638
21709
|
* origins (child connector lands exactly on parent connector) and derives the joint frame
|
|
20639
21710
|
* and axis from the connector geometry — no manual `frame` or `axis` math needed.
|
|
20640
21711
|
*
|
|
21712
|
+
* **Face-to-face convention:** Connectors always meet face-to-face, like a USB plug
|
|
21713
|
+
* meeting a socket. Each connector's axis points "outward" from its part. When two
|
|
21714
|
+
* connectors mate, the system brings them together so their axes oppose (anti-parallel).
|
|
21715
|
+
* This is the same convention used by `matchTo()`.
|
|
21716
|
+
*
|
|
21717
|
+
* For a revolute joint (hinge), both connectors' axes should point outward from their
|
|
21718
|
+
* respective parts along the hinge line. For a prismatic joint (slider), both axes
|
|
21719
|
+
* should point along the slide direction from their part's perspective.
|
|
21720
|
+
*
|
|
20641
21721
|
* The joint type is inferred from the connector's `kind` field if not specified in `options`.
|
|
20642
|
-
* Use `flip: true` for mirrored parts whose connector axis is reflected.
|
|
20643
21722
|
*
|
|
20644
21723
|
* When connectors are defined with `start`/`end`, you can control which point on each
|
|
20645
21724
|
* connector meets via `align` / `parentAlign` / `childAlign` (`'start'`, `'middle'`, `'end'`).
|
|
@@ -20651,20 +21730,22 @@ class Assembly {
|
|
|
20651
21730
|
* **Example**
|
|
20652
21731
|
*
|
|
20653
21732
|
* ```ts
|
|
20654
|
-
*
|
|
20655
|
-
*
|
|
20656
|
-
*
|
|
20657
|
-
*
|
|
20658
|
-
*
|
|
20659
|
-
*
|
|
20660
|
-
*
|
|
20661
|
-
*
|
|
20662
|
-
*
|
|
21733
|
+
* // Hinge: both axes point outward along the hinge line
|
|
21734
|
+
* const frame = box(100, 10, 80).withConnectors({
|
|
21735
|
+
* hinge: connector("hinge", { origin: [0, 0, 40], axis: [0, 0, 1] }),
|
|
21736
|
+
* });
|
|
21737
|
+
* const door = box(60, 4, 80).withConnectors({
|
|
21738
|
+
* hinge: connector("hinge", { origin: [0, 0, 40], axis: [0, 0, -1] }),
|
|
21739
|
+
* });
|
|
21740
|
+
* assembly("Door")
|
|
21741
|
+
* .addPart("Frame", frame)
|
|
21742
|
+
* .addPart("Door", door)
|
|
21743
|
+
* .connect("Frame.hinge", "Door.hinge", { as: "swing", min: 0, max: 110 });
|
|
20663
21744
|
* ```
|
|
20664
21745
|
*
|
|
20665
21746
|
* @param parentPortRef - `"PartName.connectorName"` on the parent side
|
|
20666
21747
|
* @param childPortRef - `"PartName.connectorName"` on the child side
|
|
20667
|
-
* @param options - `as` (joint name), `type`, `min`, `max`, `default`, `
|
|
21748
|
+
* @param options - `as` (joint name), `type`, `min`, `max`, `default`, `align`, effort, velocity, etc.
|
|
20668
21749
|
* @returns `this` for chaining
|
|
20669
21750
|
* @see {@link match} for typed connector matching with gender/type validation
|
|
20670
21751
|
* @category Connectors
|
|
@@ -20681,7 +21762,7 @@ class Assembly {
|
|
|
20681
21762
|
const childBase = childRecord.base;
|
|
20682
21763
|
const childAlign = options.childAlign ?? options.align ?? "middle";
|
|
20683
21764
|
const parentAlign = options.parentAlign ?? options.align ?? "middle";
|
|
20684
|
-
const { frame, axis } = computeConnectFrame(childBase, child.port, parent.port,
|
|
21765
|
+
const { frame, axis } = computeConnectFrame(childBase, child.port, parent.port, false, childAlign, parentAlign);
|
|
20685
21766
|
const min2 = options.min ?? child.port.min ?? parent.port.min;
|
|
20686
21767
|
const max2 = options.max ?? child.port.max ?? parent.port.max;
|
|
20687
21768
|
this._usedPortRefs.add(parentPortRef);
|
|
@@ -21599,12 +22680,12 @@ function bom(quantity, description, opts) {
|
|
|
21599
22680
|
metadata
|
|
21600
22681
|
});
|
|
21601
22682
|
}
|
|
21602
|
-
let _collected$
|
|
22683
|
+
let _collected$5 = [];
|
|
21603
22684
|
function resetCutPlanes() {
|
|
21604
|
-
_collected$
|
|
22685
|
+
_collected$5 = [];
|
|
21605
22686
|
}
|
|
21606
22687
|
function getCollectedCutPlanes() {
|
|
21607
|
-
return _collected$
|
|
22688
|
+
return _collected$5.slice();
|
|
21608
22689
|
}
|
|
21609
22690
|
function normalizeExcludedObjectNames(input) {
|
|
21610
22691
|
if (input === void 0) return void 0;
|
|
@@ -21620,7 +22701,29 @@ function cutPlane(name, normal, offsetOrOptions = 0, maybeOptions = {}) {
|
|
|
21620
22701
|
const offset2 = Number.isFinite(rawOffset) ? rawOffset : 0;
|
|
21621
22702
|
const options = usingOffsetArg ? maybeOptions : offsetOrOptions;
|
|
21622
22703
|
const excludeObjectNames = normalizeExcludedObjectNames(options.exclude);
|
|
21623
|
-
_collected$
|
|
22704
|
+
_collected$5.push({ name, normal, offset: offset2, excludeObjectNames });
|
|
22705
|
+
}
|
|
22706
|
+
let _collected$4 = [];
|
|
22707
|
+
let _counter$1 = 0;
|
|
22708
|
+
function resetMocks() {
|
|
22709
|
+
_collected$4 = [];
|
|
22710
|
+
_counter$1 = 0;
|
|
22711
|
+
}
|
|
22712
|
+
function getCollectedMocks() {
|
|
22713
|
+
return _collected$4.slice();
|
|
22714
|
+
}
|
|
22715
|
+
function mock(shape, name) {
|
|
22716
|
+
if (!shape || typeof shape !== "object") {
|
|
22717
|
+
throw new Error("mock(shape): shape must be a Shape");
|
|
22718
|
+
}
|
|
22719
|
+
_counter$1 += 1;
|
|
22720
|
+
const displayName = name && typeof name === "string" && name.trim().length > 0 ? name.trim() : `Mock ${_counter$1}`;
|
|
22721
|
+
_collected$4.push({
|
|
22722
|
+
id: `mock-${_counter$1}`,
|
|
22723
|
+
name: displayName,
|
|
22724
|
+
shape
|
|
22725
|
+
});
|
|
22726
|
+
return shape;
|
|
21624
22727
|
}
|
|
21625
22728
|
const DEFAULT_PROFILE = {
|
|
21626
22729
|
bedX: 220,
|
|
@@ -22988,11 +24091,13 @@ Shape.prototype.cutout = function cutout(sketch, opts = {}) {
|
|
|
22988
24091
|
return shapeCutout(this, sketch, opts);
|
|
22989
24092
|
};
|
|
22990
24093
|
let _params = [];
|
|
24094
|
+
let _stringParams = [];
|
|
22991
24095
|
let _listParams = [];
|
|
22992
24096
|
let _overrides = {};
|
|
22993
24097
|
let _scopeStack = [];
|
|
22994
24098
|
function resetParams() {
|
|
22995
24099
|
_params = [];
|
|
24100
|
+
_stringParams = [];
|
|
22996
24101
|
_listParams = [];
|
|
22997
24102
|
_scopeStack = [];
|
|
22998
24103
|
}
|
|
@@ -23002,6 +24107,9 @@ function setParamOverrides(overrides) {
|
|
|
23002
24107
|
function getCollectedParams() {
|
|
23003
24108
|
return _params;
|
|
23004
24109
|
}
|
|
24110
|
+
function getCollectedStringParams() {
|
|
24111
|
+
return _stringParams;
|
|
24112
|
+
}
|
|
23005
24113
|
function getCollectedListParams() {
|
|
23006
24114
|
return _listParams;
|
|
23007
24115
|
}
|
|
@@ -23018,6 +24126,11 @@ function hasOwn(obj, key) {
|
|
|
23018
24126
|
}
|
|
23019
24127
|
function param(name, defaultValue, opts = {}) {
|
|
23020
24128
|
var _a3;
|
|
24129
|
+
if (typeof defaultValue !== "number") {
|
|
24130
|
+
throw new Error(
|
|
24131
|
+
`param("${name}"): defaultValue must be a number, got ${typeof defaultValue}. For text parameters, use Param.string("${name}", ${JSON.stringify(defaultValue)}).`
|
|
24132
|
+
);
|
|
24133
|
+
}
|
|
23021
24134
|
const scope = _scopeStack[_scopeStack.length - 1];
|
|
23022
24135
|
const scopedName = (scope == null ? void 0 : scope.namePrefix) ? `${scope.namePrefix} / ${name}` : name;
|
|
23023
24136
|
const scopedLocal = scope == null ? void 0 : scope.localOverrides;
|
|
@@ -23087,6 +24200,32 @@ function choiceParam(name, defaultValue, choices) {
|
|
|
23087
24200
|
}
|
|
23088
24201
|
return choices[index];
|
|
23089
24202
|
}
|
|
24203
|
+
function stringParam(name, defaultValue, opts = {}) {
|
|
24204
|
+
var _a3;
|
|
24205
|
+
if (typeof defaultValue !== "string") {
|
|
24206
|
+
throw new Error(`Param.string("${name}"): defaultValue must be a string, got ${typeof defaultValue}.`);
|
|
24207
|
+
}
|
|
24208
|
+
const scope = _scopeStack[_scopeStack.length - 1];
|
|
24209
|
+
const scopedName = (scope == null ? void 0 : scope.namePrefix) ? `${scope.namePrefix} / ${name}` : name;
|
|
24210
|
+
const scopedLocal = scope == null ? void 0 : scope.localOverrides;
|
|
24211
|
+
const hasLocalOverride = !!(scopedLocal && Object.prototype.hasOwnProperty.call(scopedLocal, name));
|
|
24212
|
+
if (hasLocalOverride) (_a3 = scope.consumedKeys) == null ? void 0 : _a3.add(name);
|
|
24213
|
+
const rawOverride = (hasLocalOverride ? scopedLocal[name] : void 0) ?? _overrides[scopedName] ?? _overrides[name];
|
|
24214
|
+
const value = typeof rawOverride === "string" ? rawOverride : defaultValue;
|
|
24215
|
+
const maxLength = opts.maxLength;
|
|
24216
|
+
const clamped = maxLength !== void 0 ? value.slice(0, maxLength) : value;
|
|
24217
|
+
if (!hasLocalOverride) {
|
|
24218
|
+
_stringParams.push({ name: scopedName, value: clamped, defaultValue, maxLength });
|
|
24219
|
+
}
|
|
24220
|
+
return clamped;
|
|
24221
|
+
}
|
|
24222
|
+
const Param = {
|
|
24223
|
+
number: param,
|
|
24224
|
+
string: stringParam,
|
|
24225
|
+
bool: boolParam,
|
|
24226
|
+
choice: choiceParam,
|
|
24227
|
+
list: listParam
|
|
24228
|
+
};
|
|
23090
24229
|
function listParam(name, defaultItems, opts) {
|
|
23091
24230
|
if (!Array.isArray(defaultItems)) throw new Error(`listParam("${name}"): defaultItems must be an array`);
|
|
23092
24231
|
const scope = _scopeStack[_scopeStack.length - 1];
|
|
@@ -23665,6 +24804,24 @@ function catmullRom2D$1(p0, p1, p2, p3, t, tension) {
|
|
|
23665
24804
|
const h11 = ttt - tt;
|
|
23666
24805
|
return [h00 * p1[0] + h10 * m1x + h01 * p2[0] + h11 * m2x, h00 * p1[1] + h10 * m1y + h01 * p2[1] + h11 * m2y];
|
|
23667
24806
|
}
|
|
24807
|
+
function sampleBSpline2D(controlPoints, weights, degree, tol = DEFAULT_TOLERANCE$1) {
|
|
24808
|
+
const n = controlPoints.length;
|
|
24809
|
+
const knots = generateClampedKnots(n, degree);
|
|
24810
|
+
let polyLen = 0;
|
|
24811
|
+
for (let i = 1; i < n; i++) {
|
|
24812
|
+
polyLen += Math.hypot(controlPoints[i][0] - controlPoints[i - 1][0], controlPoints[i][1] - controlPoints[i - 1][1]);
|
|
24813
|
+
}
|
|
24814
|
+
const count = Math.max(8, Math.min(256, Math.ceil(polyLen / tol)));
|
|
24815
|
+
const uMin = knots[degree];
|
|
24816
|
+
const uMax = knots[n];
|
|
24817
|
+
const pts = new Array(count);
|
|
24818
|
+
for (let i = 0; i < count; i++) {
|
|
24819
|
+
const t = i / (count - 1);
|
|
24820
|
+
const u = uMin + t * (uMax - uMin);
|
|
24821
|
+
pts[i] = deBoor2D(controlPoints, weights, knots, degree, u);
|
|
24822
|
+
}
|
|
24823
|
+
return pts;
|
|
24824
|
+
}
|
|
23668
24825
|
function ensureCCW(pts) {
|
|
23669
24826
|
let signedArea2 = 0;
|
|
23670
24827
|
for (let i = 0; i < pts.length; i++) {
|
|
@@ -24039,6 +25196,82 @@ class PathBuilder {
|
|
|
24039
25196
|
this.y = last[1];
|
|
24040
25197
|
return this;
|
|
24041
25198
|
}
|
|
25199
|
+
// ── NURBS / exact curves ──────────────────────────────────────────────────
|
|
25200
|
+
/**
|
|
25201
|
+
* Rational B-spline edge to (x, y) with explicit control points and weights.
|
|
25202
|
+
*
|
|
25203
|
+
* The control points define the B-spline shape between the current position
|
|
25204
|
+
* and (x, y). The current position is NOT included in `controlPoints` — it is
|
|
25205
|
+
* automatically prepended. The endpoint (x, y) is the last control point.
|
|
25206
|
+
*
|
|
25207
|
+
* @param controlPoints — interior + endpoint control points (endpoint = last)
|
|
25208
|
+
* @param opts.weights — rational weights (default: all 1.0)
|
|
25209
|
+
* @param opts.degree — B-spline degree (default: control point count - 1, capped at 3)
|
|
25210
|
+
*/
|
|
25211
|
+
nurbsTo(controlPoints, opts) {
|
|
25212
|
+
if (controlPoints.length < 1) throw new Error("nurbsTo: need at least 1 control point (the endpoint)");
|
|
25213
|
+
const allPts = [[this.x, this.y], ...controlPoints];
|
|
25214
|
+
const n = allPts.length;
|
|
25215
|
+
const degree = (opts == null ? void 0 : opts.degree) ?? Math.min(n - 1, 3);
|
|
25216
|
+
const weights = (opts == null ? void 0 : opts.weights) ?? new Array(n).fill(1);
|
|
25217
|
+
if (weights.length !== n) throw new Error(`nurbsTo: weights.length (${weights.length}) must match total control points (${n})`);
|
|
25218
|
+
const last = controlPoints[controlPoints.length - 1];
|
|
25219
|
+
this.segs.push({ kind: "bspline", x: last[0], y: last[1], controlPoints: allPts, weights, degree });
|
|
25220
|
+
const prevPt = allPts[n - 2];
|
|
25221
|
+
const tdx = last[0] - prevPt[0];
|
|
25222
|
+
const tdy = last[1] - prevPt[1];
|
|
25223
|
+
const tlen = Math.hypot(tdx, tdy);
|
|
25224
|
+
if (tlen > 1e-9) {
|
|
25225
|
+
this.dirX = tdx / tlen;
|
|
25226
|
+
this.dirY = tdy / tlen;
|
|
25227
|
+
}
|
|
25228
|
+
this.x = last[0];
|
|
25229
|
+
this.y = last[1];
|
|
25230
|
+
return this;
|
|
25231
|
+
}
|
|
25232
|
+
/**
|
|
25233
|
+
* Exact circular arc to (x, y) using a rational quadratic NURBS.
|
|
25234
|
+
*
|
|
25235
|
+
* Unlike `arcTo()` which tessellates to a polyline, this preserves the
|
|
25236
|
+
* exact arc definition. When extruded through the OCCT backend, it produces
|
|
25237
|
+
* a true cylindrical face — not a faceted approximation.
|
|
25238
|
+
*
|
|
25239
|
+
* @param x — endpoint X
|
|
25240
|
+
* @param y — endpoint Y
|
|
25241
|
+
* @param opts.radius — arc radius (default: auto-computed from chord)
|
|
25242
|
+
* @param opts.clockwise — winding direction (default: false = CCW)
|
|
25243
|
+
*/
|
|
25244
|
+
exactArcTo(x, y, opts) {
|
|
25245
|
+
const clockwise = (opts == null ? void 0 : opts.clockwise) ?? false;
|
|
25246
|
+
const dx = x - this.x;
|
|
25247
|
+
const dy = y - this.y;
|
|
25248
|
+
const chordLen = Math.hypot(dx, dy);
|
|
25249
|
+
if (chordLen < 1e-9) return this;
|
|
25250
|
+
const radius = (opts == null ? void 0 : opts.radius) ?? chordLen;
|
|
25251
|
+
if (radius < chordLen / 2 - 1e-9) throw new Error("exactArcTo: radius too small for the chord");
|
|
25252
|
+
const mx = (this.x + x) / 2;
|
|
25253
|
+
const my = (this.y + y) / 2;
|
|
25254
|
+
let nx = -dy / chordLen;
|
|
25255
|
+
let ny = dx / chordLen;
|
|
25256
|
+
if (clockwise) {
|
|
25257
|
+
nx = -nx;
|
|
25258
|
+
ny = -ny;
|
|
25259
|
+
}
|
|
25260
|
+
const h = Math.sqrt(Math.max(0, radius * radius - chordLen / 2 * (chordLen / 2)));
|
|
25261
|
+
const cx = mx + nx * h;
|
|
25262
|
+
const cy = my + ny * h;
|
|
25263
|
+
const halfAngle = Math.asin(Math.min(1, chordLen / (2 * radius)));
|
|
25264
|
+
const w = Math.cos(halfAngle);
|
|
25265
|
+
const smx = (this.x + x) / 2 - cx;
|
|
25266
|
+
const smy = (this.y + y) / 2 - cy;
|
|
25267
|
+
const smLen = Math.hypot(smx, smy);
|
|
25268
|
+
const shoulderX = cx + smx / smLen * radius;
|
|
25269
|
+
const shoulderY = cy + smy / smLen * radius;
|
|
25270
|
+
return this.nurbsTo(
|
|
25271
|
+
[[shoulderX, shoulderY], [x, y]],
|
|
25272
|
+
{ weights: [1, w, 1], degree: 2 }
|
|
25273
|
+
);
|
|
25274
|
+
}
|
|
24042
25275
|
// ── Corner modifiers ──────────────────────────────────────────────────────
|
|
24043
25276
|
/**
|
|
24044
25277
|
* Round the last corner (the junction between the previous two segments)
|
|
@@ -24363,6 +25596,11 @@ class PathBuilder {
|
|
|
24363
25596
|
const last = seg.points[seg.points.length - 1];
|
|
24364
25597
|
px = last[0];
|
|
24365
25598
|
py = last[1];
|
|
25599
|
+
} else if (seg.kind === "bspline") {
|
|
25600
|
+
const sampled = sampleBSpline2D(seg.controlPoints, seg.weights, seg.degree);
|
|
25601
|
+
for (let i = 1; i < sampled.length; i++) pts.push(sampled[i]);
|
|
25602
|
+
px = seg.x;
|
|
25603
|
+
py = seg.y;
|
|
24366
25604
|
}
|
|
24367
25605
|
}
|
|
24368
25606
|
return pts;
|
|
@@ -24399,11 +25637,23 @@ class PathBuilder {
|
|
|
24399
25637
|
close() {
|
|
24400
25638
|
const subPaths = this.splitSubPaths();
|
|
24401
25639
|
if (subPaths.length === 0) throw new Error("Path needs at least 3 points");
|
|
25640
|
+
const hasBSpline = this.segs.some((s) => s.kind === "bspline");
|
|
24402
25641
|
const tessellated = subPaths.map((segs) => this.tessellateSegs(segs));
|
|
24403
25642
|
const outer = tessellated[0];
|
|
24404
25643
|
if (outer.length < 3) throw new Error("Path needs at least 3 points");
|
|
24405
25644
|
ensureCCW(outer);
|
|
24406
|
-
let result
|
|
25645
|
+
let result;
|
|
25646
|
+
if (hasBSpline && subPaths.length === 1) {
|
|
25647
|
+
const edges = this.buildProfileEdges(subPaths[0]);
|
|
25648
|
+
result = buildSketchFromCompileProfilePlan({
|
|
25649
|
+
kind: "pathProfile",
|
|
25650
|
+
points: outer,
|
|
25651
|
+
edges,
|
|
25652
|
+
transforms: []
|
|
25653
|
+
});
|
|
25654
|
+
} else {
|
|
25655
|
+
result = polygon(outer);
|
|
25656
|
+
}
|
|
24407
25657
|
for (let i = 1; i < tessellated.length; i++) {
|
|
24408
25658
|
const hole2 = tessellated[i];
|
|
24409
25659
|
if (hole2.length < 3) continue;
|
|
@@ -24530,6 +25780,48 @@ class PathBuilder {
|
|
|
24530
25780
|
if (current.length > 0) paths.push(current);
|
|
24531
25781
|
return paths;
|
|
24532
25782
|
}
|
|
25783
|
+
/** Build semantic ProfileEdge array from path segments (for pathProfile compile plan). */
|
|
25784
|
+
buildProfileEdges(segs) {
|
|
25785
|
+
const edges = [];
|
|
25786
|
+
let px = 0, py = 0;
|
|
25787
|
+
for (const seg of segs) {
|
|
25788
|
+
if (seg.kind === "move") {
|
|
25789
|
+
px = seg.x;
|
|
25790
|
+
py = seg.y;
|
|
25791
|
+
} else if (seg.kind === "line") {
|
|
25792
|
+
edges.push({ kind: "line", x1: px, y1: py, x2: seg.x, y2: seg.y });
|
|
25793
|
+
px = seg.x;
|
|
25794
|
+
py = seg.y;
|
|
25795
|
+
} else if (seg.kind === "arc") {
|
|
25796
|
+
edges.push({ kind: "arc", x1: px, y1: py, x2: seg.x, y2: seg.y, cx: seg.cx, cy: seg.cy, clockwise: seg.clockwise });
|
|
25797
|
+
px = seg.x;
|
|
25798
|
+
py = seg.y;
|
|
25799
|
+
} else if (seg.kind === "bspline") {
|
|
25800
|
+
edges.push({
|
|
25801
|
+
kind: "bspline",
|
|
25802
|
+
controlPoints: seg.controlPoints,
|
|
25803
|
+
weights: seg.weights,
|
|
25804
|
+
knots: generateClampedKnots(seg.controlPoints.length, seg.degree),
|
|
25805
|
+
degree: seg.degree
|
|
25806
|
+
});
|
|
25807
|
+
px = seg.x;
|
|
25808
|
+
py = seg.y;
|
|
25809
|
+
} else {
|
|
25810
|
+
edges.push({ kind: "line", x1: px, y1: py, x2: seg.x, y2: seg.y });
|
|
25811
|
+
px = seg.x;
|
|
25812
|
+
py = seg.y;
|
|
25813
|
+
}
|
|
25814
|
+
}
|
|
25815
|
+
if (segs.length > 0) {
|
|
25816
|
+
const first = segs[0];
|
|
25817
|
+
const firstX = first.kind === "move" ? first.x : 0;
|
|
25818
|
+
const firstY = first.kind === "move" ? first.y : 0;
|
|
25819
|
+
if (Math.hypot(px - firstX, py - firstY) > 1e-9) {
|
|
25820
|
+
edges.push({ kind: "line", x1: px, y1: py, x2: firstX, y2: firstY });
|
|
25821
|
+
}
|
|
25822
|
+
}
|
|
25823
|
+
return edges;
|
|
25824
|
+
}
|
|
24533
25825
|
/** Tessellate a sub-path (sequence of segments). */
|
|
24534
25826
|
tessellateSegs(segs) {
|
|
24535
25827
|
const pts = [];
|
|
@@ -24557,6 +25849,11 @@ class PathBuilder {
|
|
|
24557
25849
|
const last = seg.points[seg.points.length - 1];
|
|
24558
25850
|
px = last[0];
|
|
24559
25851
|
py = last[1];
|
|
25852
|
+
} else if (seg.kind === "bspline") {
|
|
25853
|
+
const sampled = sampleBSpline2D(seg.controlPoints, seg.weights, seg.degree);
|
|
25854
|
+
for (let i = 1; i < sampled.length; i++) pts.push(sampled[i]);
|
|
25855
|
+
px = seg.x;
|
|
25856
|
+
py = seg.y;
|
|
24560
25857
|
}
|
|
24561
25858
|
}
|
|
24562
25859
|
return pts;
|
|
@@ -25843,7 +27140,20 @@ function spurGear(options) {
|
|
|
25843
27140
|
profile = difference2d(profile, circle2d(normalized.boreDiameter * 0.5, Math.max(48, normalized.teeth * 2)));
|
|
25844
27141
|
}
|
|
25845
27142
|
const shape = sketchExtrude(profile, normalized.faceWidth);
|
|
25846
|
-
|
|
27143
|
+
const shapeWithConnectors = shape.withConnectors({
|
|
27144
|
+
bore: connectorFactory("gear-bore", {
|
|
27145
|
+
origin: [0, 0, normalized.faceWidth / 2],
|
|
27146
|
+
axis: [0, 0, 1],
|
|
27147
|
+
kind: "revolute"
|
|
27148
|
+
}, {
|
|
27149
|
+
module: normalized.module,
|
|
27150
|
+
teeth: normalized.teeth,
|
|
27151
|
+
pitchRadius: meta2.pitchRadius,
|
|
27152
|
+
outerRadius: meta2.outerRadius,
|
|
27153
|
+
faceWidth: normalized.faceWidth
|
|
27154
|
+
})
|
|
27155
|
+
});
|
|
27156
|
+
return attachGearMeta(shapeWithConnectors, meta2);
|
|
25847
27157
|
}
|
|
25848
27158
|
function normalizeSideGearOptions(options) {
|
|
25849
27159
|
let normalizedSpur;
|
|
@@ -25929,7 +27239,23 @@ function sideGear(options) {
|
|
|
25929
27239
|
);
|
|
25930
27240
|
shape = shape.subtract(bore);
|
|
25931
27241
|
}
|
|
25932
|
-
|
|
27242
|
+
const boreZ = normalized.side === "top" ? zBands.bodyMinZ : zBands.bodyMaxZ;
|
|
27243
|
+
const boreAxis = normalized.side === "top" ? [0, 0, -1] : [0, 0, 1];
|
|
27244
|
+
const shapeWithConnectors = shape.withConnectors({
|
|
27245
|
+
bore: connectorFactory("gear-bore", {
|
|
27246
|
+
origin: [0, 0, boreZ],
|
|
27247
|
+
axis: boreAxis,
|
|
27248
|
+
kind: "revolute"
|
|
27249
|
+
}, {
|
|
27250
|
+
module: normalized.module,
|
|
27251
|
+
teeth: normalized.teeth,
|
|
27252
|
+
pitchRadius: meta2.pitchRadius,
|
|
27253
|
+
outerRadius: meta2.outerRadius,
|
|
27254
|
+
faceWidth: normalized.faceWidth,
|
|
27255
|
+
toothSide: normalized.side
|
|
27256
|
+
})
|
|
27257
|
+
});
|
|
27258
|
+
return attachGearMeta(shapeWithConnectors, meta2);
|
|
25933
27259
|
}
|
|
25934
27260
|
function faceGear(options) {
|
|
25935
27261
|
try {
|
|
@@ -26046,7 +27372,20 @@ function ringGear(options) {
|
|
|
26046
27372
|
}
|
|
26047
27373
|
const profile = difference2d(ringBlank, union2d(...spaces));
|
|
26048
27374
|
const shape = sketchExtrude(profile, normalized.faceWidth);
|
|
26049
|
-
|
|
27375
|
+
const shapeWithConnectors = shape.withConnectors({
|
|
27376
|
+
bore: connectorFactory("ring-bore", {
|
|
27377
|
+
origin: [0, 0, normalized.faceWidth / 2],
|
|
27378
|
+
axis: [0, 0, 1]
|
|
27379
|
+
}, {
|
|
27380
|
+
module: normalized.module,
|
|
27381
|
+
teeth: normalized.teeth,
|
|
27382
|
+
pitchRadius,
|
|
27383
|
+
innerRadius: tipRadius,
|
|
27384
|
+
outerRadius: normalized.outerRadius,
|
|
27385
|
+
faceWidth: normalized.faceWidth
|
|
27386
|
+
})
|
|
27387
|
+
});
|
|
27388
|
+
return attachGearMeta(shapeWithConnectors, meta2);
|
|
26050
27389
|
}
|
|
26051
27390
|
function rackGear(options) {
|
|
26052
27391
|
if (!isFinitePositive(options.module)) throw new Error('rackGear: "module" must be > 0');
|
|
@@ -26097,7 +27436,8 @@ function rackGear(options) {
|
|
|
26097
27436
|
teethSketches.push(sketchTranslate(toothSketch, cx, 0));
|
|
26098
27437
|
}
|
|
26099
27438
|
const span = (options.teeth - 1) * pitch + halfRoot * 2;
|
|
26100
|
-
const
|
|
27439
|
+
const length4 = span + module * 2;
|
|
27440
|
+
const base = sketchTranslate(rect(length4, baseHeight), 0, -dedendum - baseHeight * 0.5);
|
|
26101
27441
|
const profile = union2d(base, ...teethSketches);
|
|
26102
27442
|
const shape = sketchExtrude(profile, options.faceWidth);
|
|
26103
27443
|
const meta2 = {
|
|
@@ -26116,7 +27456,20 @@ function rackGear(options) {
|
|
|
26116
27456
|
backlash,
|
|
26117
27457
|
centered: false
|
|
26118
27458
|
};
|
|
26119
|
-
|
|
27459
|
+
const shapeWithConnectors = shape.withConnectors({
|
|
27460
|
+
teeth: connectorFactory("rack-teeth", {
|
|
27461
|
+
origin: [0, 0, options.faceWidth / 2],
|
|
27462
|
+
axis: [1, 0, 0],
|
|
27463
|
+
up: [0, 1, 0],
|
|
27464
|
+
kind: "prismatic"
|
|
27465
|
+
}, {
|
|
27466
|
+
module,
|
|
27467
|
+
teeth: options.teeth,
|
|
27468
|
+
faceWidth: options.faceWidth,
|
|
27469
|
+
length: length4
|
|
27470
|
+
})
|
|
27471
|
+
});
|
|
27472
|
+
return attachGearMeta(shapeWithConnectors, meta2);
|
|
26120
27473
|
}
|
|
26121
27474
|
function normalizeShaftAngle(label, value) {
|
|
26122
27475
|
if (!isFinitePositive(value) || value >= 175) {
|
|
@@ -26195,7 +27548,27 @@ function bevelGear(options) {
|
|
|
26195
27548
|
const shape = sketchExtrude(profile, normalized.faceWidth, {
|
|
26196
27549
|
scaleTop: normalized.topScale
|
|
26197
27550
|
});
|
|
26198
|
-
|
|
27551
|
+
const apexZ = normalized.module * normalized.teeth * 0.5 / Math.tan(normalized.pitchAngleRad);
|
|
27552
|
+
const measurements = {
|
|
27553
|
+
module: normalized.module,
|
|
27554
|
+
teeth: normalized.teeth,
|
|
27555
|
+
pitchRadius: meta2.pitchRadius,
|
|
27556
|
+
pitchAngleDeg: normalized.pitchAngleDeg,
|
|
27557
|
+
coneDistance: normalized.coneDistance,
|
|
27558
|
+
faceWidth: normalized.faceWidth
|
|
27559
|
+
};
|
|
27560
|
+
const shapeWithConnectors = shape.withConnectors({
|
|
27561
|
+
bore: connectorFactory("gear-bore", {
|
|
27562
|
+
origin: [0, 0, 0],
|
|
27563
|
+
axis: [0, 0, -1],
|
|
27564
|
+
kind: "revolute"
|
|
27565
|
+
}, measurements),
|
|
27566
|
+
apex: connectorFactory("bevel-apex", {
|
|
27567
|
+
origin: [0, 0, apexZ],
|
|
27568
|
+
axis: [0, 0, 1]
|
|
27569
|
+
}, measurements)
|
|
27570
|
+
});
|
|
27571
|
+
return attachGearMeta(shapeWithConnectors, {
|
|
26199
27572
|
...meta2,
|
|
26200
27573
|
kind: "bevel",
|
|
26201
27574
|
centered: false,
|
|
@@ -26677,6 +28050,70 @@ function faceGearPair(options) {
|
|
|
26677
28050
|
status: pairStatusFromDiagnostics(diagnostics)
|
|
26678
28051
|
};
|
|
26679
28052
|
}
|
|
28053
|
+
function gearRatio(teethA, teethB, options) {
|
|
28054
|
+
if (!Number.isFinite(teethA) || teethA <= 0) throw new Error("gearRatio: teethA must be > 0");
|
|
28055
|
+
if (!Number.isFinite(teethB) || teethB <= 0) throw new Error("gearRatio: teethB must be > 0");
|
|
28056
|
+
const sign = (options == null ? void 0 : options.internal) ? 1 : -1;
|
|
28057
|
+
return sign * teethA / teethB;
|
|
28058
|
+
}
|
|
28059
|
+
function rackRatio(module, pinionTeeth) {
|
|
28060
|
+
if (!Number.isFinite(module) || module <= 0) throw new Error("rackRatio: module must be > 0");
|
|
28061
|
+
if (!Number.isFinite(pinionTeeth) || pinionTeeth <= 0) throw new Error("rackRatio: pinionTeeth must be > 0");
|
|
28062
|
+
const pitchRadius = module * pinionTeeth / 2;
|
|
28063
|
+
return 180 / (Math.PI * pitchRadius);
|
|
28064
|
+
}
|
|
28065
|
+
function planetaryRatio(sunTeeth, ringTeeth) {
|
|
28066
|
+
if (!Number.isFinite(sunTeeth) || sunTeeth <= 0) throw new Error("planetaryRatio: sunTeeth must be > 0");
|
|
28067
|
+
if (!Number.isFinite(ringTeeth) || ringTeeth <= 0) throw new Error("planetaryRatio: ringTeeth must be > 0");
|
|
28068
|
+
return 1 + ringTeeth / sunTeeth;
|
|
28069
|
+
}
|
|
28070
|
+
function boltPattern(options) {
|
|
28071
|
+
const sizeData = METRIC_HOLE_TABLE[options.size];
|
|
28072
|
+
if (!sizeData) throw new Error(`boltPattern: unsupported size "${options.size}"`);
|
|
28073
|
+
const fit = options.fit ?? "normal";
|
|
28074
|
+
const dia = sizeData[fit];
|
|
28075
|
+
const segments = options.segments ?? 48;
|
|
28076
|
+
if (!Array.isArray(options.positions) || options.positions.length === 0) {
|
|
28077
|
+
throw new Error('boltPattern: "positions" must be a non-empty array of [x, y] pairs');
|
|
28078
|
+
}
|
|
28079
|
+
const positions = options.positions.map((p2, i) => {
|
|
28080
|
+
if (!Array.isArray(p2) || p2.length !== 2 || !Number.isFinite(p2[0]) || !Number.isFinite(p2[1])) {
|
|
28081
|
+
throw new Error(`boltPattern: position[${i}] must be a finite [x, y] pair`);
|
|
28082
|
+
}
|
|
28083
|
+
return [p2[0], p2[1]];
|
|
28084
|
+
});
|
|
28085
|
+
const xs = positions.map((p2) => p2[0]);
|
|
28086
|
+
const ys = positions.map((p2) => p2[1]);
|
|
28087
|
+
return {
|
|
28088
|
+
size: options.size,
|
|
28089
|
+
dia,
|
|
28090
|
+
positions,
|
|
28091
|
+
minX: Math.min(...xs),
|
|
28092
|
+
maxX: Math.max(...xs),
|
|
28093
|
+
minY: Math.min(...ys),
|
|
28094
|
+
maxY: Math.max(...ys),
|
|
28095
|
+
cut(shape, depth, cutOptions = {}) {
|
|
28096
|
+
if (!Number.isFinite(depth) || depth <= 0) {
|
|
28097
|
+
throw new Error("boltPattern.cut: depth must be > 0");
|
|
28098
|
+
}
|
|
28099
|
+
const from = cutOptions.from ?? 0;
|
|
28100
|
+
const cutter = fastenerHole({
|
|
28101
|
+
size: options.size,
|
|
28102
|
+
fit,
|
|
28103
|
+
depth,
|
|
28104
|
+
center: false,
|
|
28105
|
+
segments,
|
|
28106
|
+
counterbore: cutOptions.counterbore,
|
|
28107
|
+
countersink: cutOptions.countersink
|
|
28108
|
+
});
|
|
28109
|
+
let result = shape;
|
|
28110
|
+
for (const [x, y] of positions) {
|
|
28111
|
+
result = result.subtract(cutter.translate(x, y, from));
|
|
28112
|
+
}
|
|
28113
|
+
return result;
|
|
28114
|
+
}
|
|
28115
|
+
};
|
|
28116
|
+
}
|
|
26680
28117
|
function thread(diameter, pitch, length4, options) {
|
|
26681
28118
|
const r = diameter / 2;
|
|
26682
28119
|
const depth = (options == null ? void 0 : options.depth) ?? pitch * 0.35;
|
|
@@ -26836,7 +28273,11 @@ const partLibrary = {
|
|
|
26836
28273
|
gearPair,
|
|
26837
28274
|
bevelGearPair,
|
|
26838
28275
|
faceGearPair,
|
|
26839
|
-
sideGearPair
|
|
28276
|
+
sideGearPair,
|
|
28277
|
+
gearRatio,
|
|
28278
|
+
rackRatio,
|
|
28279
|
+
planetaryRatio,
|
|
28280
|
+
boltPattern
|
|
26840
28281
|
};
|
|
26841
28282
|
new TextEncoder();
|
|
26842
28283
|
let _collectedRobotExport = null;
|
|
@@ -27589,14 +29030,14 @@ class WoodBoard {
|
|
|
27589
29030
|
return this._withShape(this.shape.clone());
|
|
27590
29031
|
}
|
|
27591
29032
|
}
|
|
27592
|
-
function requireFinite$
|
|
29033
|
+
function requireFinite$5(value, name) {
|
|
27593
29034
|
if (!Number.isFinite(value)) {
|
|
27594
29035
|
throw new Error(`${name} must be a finite number, got ${value}`);
|
|
27595
29036
|
}
|
|
27596
29037
|
return value;
|
|
27597
29038
|
}
|
|
27598
29039
|
function requirePositive$2(value, name) {
|
|
27599
|
-
requireFinite$
|
|
29040
|
+
requireFinite$5(value, name);
|
|
27600
29041
|
if (value <= 0) {
|
|
27601
29042
|
throw new Error(`${name} must be positive, got ${value}`);
|
|
27602
29043
|
}
|
|
@@ -27631,10 +29072,10 @@ function dado(host, guest, opts) {
|
|
|
27631
29072
|
}
|
|
27632
29073
|
let fromBottom;
|
|
27633
29074
|
if (opts.fromBottom != null) {
|
|
27634
|
-
fromBottom = requireFinite$
|
|
29075
|
+
fromBottom = requireFinite$5(opts.fromBottom, "fromBottom");
|
|
27635
29076
|
} else {
|
|
27636
29077
|
fromBottom = host.height - opts.fromTop - channelWidth;
|
|
27637
|
-
requireFinite$
|
|
29078
|
+
requireFinite$5(fromBottom, "computed fromBottom");
|
|
27638
29079
|
}
|
|
27639
29080
|
let dadoLength = host.width;
|
|
27640
29081
|
let xOffset = 0;
|
|
@@ -27691,7 +29132,7 @@ function mortiseAndTenon(mortiseBoard, tenonBoard, opts) {
|
|
|
27691
29132
|
const style = o.style ?? "blind";
|
|
27692
29133
|
const fit = o.fit ?? "snug";
|
|
27693
29134
|
const clearance = clearanceForFit(fit);
|
|
27694
|
-
const cornerRadius = o.cornerRadius != null ? requireFinite$
|
|
29135
|
+
const cornerRadius = o.cornerRadius != null ? requireFinite$5(o.cornerRadius, "cornerRadius") : 0;
|
|
27695
29136
|
const tenonThickness = o.tenonThickness != null ? requirePositive$2(o.tenonThickness, "tenonThickness") : tenonBoard.thickness / 3;
|
|
27696
29137
|
const tenonWidth = o.tenonWidth != null ? requirePositive$2(o.tenonWidth, "tenonWidth") : Math.min(tenonBoard.height * 0.6, mortiseBoard.height * 0.8);
|
|
27697
29138
|
const tenonLength = o.tenonLength != null ? requirePositive$2(o.tenonLength, "tenonLength") : style === "through" ? mortiseBoard.thickness : mortiseBoard.thickness * 2 / 3;
|
|
@@ -27704,10 +29145,10 @@ function mortiseAndTenon(mortiseBoard, tenonBoard, opts) {
|
|
|
27704
29145
|
throw new Error("mortiseAndTenon: specify position.fromTop or position.fromBottom, not both");
|
|
27705
29146
|
}
|
|
27706
29147
|
if (o.position.fromTop != null) {
|
|
27707
|
-
requireFinite$
|
|
29148
|
+
requireFinite$5(o.position.fromTop, "position.fromTop");
|
|
27708
29149
|
mortiseCenterY = mortiseBoard.height / 2 - o.position.fromTop - mortiseH / 2;
|
|
27709
29150
|
} else if (o.position.fromBottom != null) {
|
|
27710
|
-
requireFinite$
|
|
29151
|
+
requireFinite$5(o.position.fromBottom, "position.fromBottom");
|
|
27711
29152
|
mortiseCenterY = -mortiseBoard.height / 2 + o.position.fromBottom + mortiseH / 2;
|
|
27712
29153
|
}
|
|
27713
29154
|
}
|
|
@@ -27777,6 +29218,94 @@ const Wood = {
|
|
|
27777
29218
|
*/
|
|
27778
29219
|
mortiseAndTenon
|
|
27779
29220
|
};
|
|
29221
|
+
let _collected$3 = null;
|
|
29222
|
+
function resetCameraTrajectory() {
|
|
29223
|
+
_collected$3 = null;
|
|
29224
|
+
}
|
|
29225
|
+
function getCollectedCameraTrajectory() {
|
|
29226
|
+
return _collected$3;
|
|
29227
|
+
}
|
|
29228
|
+
function isOrbitKeyframe(kf) {
|
|
29229
|
+
return "orbit" in kf;
|
|
29230
|
+
}
|
|
29231
|
+
function isCartesianKeyframe(kf) {
|
|
29232
|
+
return "position" in kf;
|
|
29233
|
+
}
|
|
29234
|
+
function requireFinite$4(value, label) {
|
|
29235
|
+
if (!Number.isFinite(value)) {
|
|
29236
|
+
throw new Error(`cameraTrajectory(): ${label} must be a finite number, got ${value}`);
|
|
29237
|
+
}
|
|
29238
|
+
}
|
|
29239
|
+
function validateOrbitKeyframe(kf, index) {
|
|
29240
|
+
requireFinite$4(kf.at, `keyframes[${index}].at`);
|
|
29241
|
+
requireFinite$4(kf.orbit.angle, `keyframes[${index}].orbit.angle`);
|
|
29242
|
+
requireFinite$4(kf.orbit.pitch, `keyframes[${index}].orbit.pitch`);
|
|
29243
|
+
requireFinite$4(kf.orbit.distance, `keyframes[${index}].orbit.distance`);
|
|
29244
|
+
}
|
|
29245
|
+
function validateCartesianKeyframe(kf, index) {
|
|
29246
|
+
requireFinite$4(kf.at, `keyframes[${index}].at`);
|
|
29247
|
+
if (!Array.isArray(kf.position) || kf.position.length !== 3) {
|
|
29248
|
+
throw new Error(`cameraTrajectory(): keyframes[${index}].position must be a 3-element array`);
|
|
29249
|
+
}
|
|
29250
|
+
if (!Array.isArray(kf.target) || kf.target.length !== 3) {
|
|
29251
|
+
throw new Error(`cameraTrajectory(): keyframes[${index}].target must be a 3-element array`);
|
|
29252
|
+
}
|
|
29253
|
+
for (let i = 0; i < 3; i++) {
|
|
29254
|
+
requireFinite$4(kf.position[i], `keyframes[${index}].position[${i}]`);
|
|
29255
|
+
requireFinite$4(kf.target[i], `keyframes[${index}].target[${i}]`);
|
|
29256
|
+
}
|
|
29257
|
+
}
|
|
29258
|
+
function validateKeyframeOrder(keyframes) {
|
|
29259
|
+
if (keyframes.length < 2) {
|
|
29260
|
+
throw new Error("cameraTrajectory(): keyframes must contain at least 2 entries");
|
|
29261
|
+
}
|
|
29262
|
+
for (let i = 0; i < keyframes.length; i++) {
|
|
29263
|
+
const at = keyframes[i].at;
|
|
29264
|
+
if (at < 0 || at > 1) {
|
|
29265
|
+
throw new Error(`cameraTrajectory(): keyframes[${i}].at must be in [0, 1], got ${at}`);
|
|
29266
|
+
}
|
|
29267
|
+
if (i > 0 && at < keyframes[i - 1].at) {
|
|
29268
|
+
throw new Error(
|
|
29269
|
+
`cameraTrajectory(): keyframes must be sorted by 'at' ascending — keyframes[${i - 1}].at=${keyframes[i - 1].at} > keyframes[${i}].at=${at}`
|
|
29270
|
+
);
|
|
29271
|
+
}
|
|
29272
|
+
}
|
|
29273
|
+
}
|
|
29274
|
+
function cameraTrajectory(defOrFn, options) {
|
|
29275
|
+
if (_collected$3 !== null) {
|
|
29276
|
+
console.warn("cameraTrajectory() called more than once — overwriting previous trajectory.");
|
|
29277
|
+
}
|
|
29278
|
+
if (typeof defOrFn === "function") {
|
|
29279
|
+
_collected$3 = {
|
|
29280
|
+
kind: "parametric",
|
|
29281
|
+
parametricFn: defOrFn,
|
|
29282
|
+
duration: options == null ? void 0 : options.duration,
|
|
29283
|
+
fps: options == null ? void 0 : options.fps
|
|
29284
|
+
};
|
|
29285
|
+
return;
|
|
29286
|
+
}
|
|
29287
|
+
const { keyframes, duration, fps, easing } = defOrFn;
|
|
29288
|
+
if (!Array.isArray(keyframes) || keyframes.length === 0) {
|
|
29289
|
+
throw new Error("cameraTrajectory(): keyframes must be a non-empty array");
|
|
29290
|
+
}
|
|
29291
|
+
validateKeyframeOrder(keyframes);
|
|
29292
|
+
const first = keyframes[0];
|
|
29293
|
+
if (isOrbitKeyframe(first)) {
|
|
29294
|
+
const orbitKeyframes = keyframes;
|
|
29295
|
+
for (let i = 0; i < orbitKeyframes.length; i++) {
|
|
29296
|
+
validateOrbitKeyframe(orbitKeyframes[i], i);
|
|
29297
|
+
}
|
|
29298
|
+
_collected$3 = { kind: "orbit-keyframes", orbitKeyframes, duration, fps, easing };
|
|
29299
|
+
} else if (isCartesianKeyframe(first)) {
|
|
29300
|
+
const cartesianKeyframes = keyframes;
|
|
29301
|
+
for (let i = 0; i < cartesianKeyframes.length; i++) {
|
|
29302
|
+
validateCartesianKeyframe(cartesianKeyframes[i], i);
|
|
29303
|
+
}
|
|
29304
|
+
_collected$3 = { kind: "cartesian-keyframes", cartesianKeyframes, duration, fps, easing };
|
|
29305
|
+
} else {
|
|
29306
|
+
throw new Error('cameraTrajectory(): each keyframe must have either an "orbit" or "position" property');
|
|
29307
|
+
}
|
|
29308
|
+
}
|
|
27780
29309
|
function resolveEdges(shape, edges) {
|
|
27781
29310
|
if (!edges) {
|
|
27782
29311
|
return selectEdges(shape);
|
|
@@ -27884,7 +29413,7 @@ function offsetSolid(shape, thickness) {
|
|
|
27884
29413
|
sources: ["offset-solid"]
|
|
27885
29414
|
});
|
|
27886
29415
|
}
|
|
27887
|
-
function requireFinite$
|
|
29416
|
+
function requireFinite$3(value, label) {
|
|
27888
29417
|
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
27889
29418
|
throw new Error(`${label} must be a finite number`);
|
|
27890
29419
|
}
|
|
@@ -27894,7 +29423,7 @@ function requireVec3(value, label) {
|
|
|
27894
29423
|
if (!Array.isArray(value) || value.length !== 3) {
|
|
27895
29424
|
throw new Error(`${label} must be [x, y, z]`);
|
|
27896
29425
|
}
|
|
27897
|
-
return [requireFinite$
|
|
29426
|
+
return [requireFinite$3(value[0], `${label}[0]`), requireFinite$3(value[1], `${label}[1]`), requireFinite$3(value[2], `${label}[2]`)];
|
|
27898
29427
|
}
|
|
27899
29428
|
function requireColor(value, label) {
|
|
27900
29429
|
if (typeof value !== "string" || !value.trim()) {
|
|
@@ -27922,7 +29451,7 @@ function validateCamera(cam, label) {
|
|
|
27922
29451
|
if (cam.target !== void 0) out.target = requireVec3(cam.target, `${label}.target`);
|
|
27923
29452
|
if (cam.up !== void 0) out.up = requireVec3(cam.up, `${label}.up`);
|
|
27924
29453
|
if (cam.fov !== void 0) {
|
|
27925
|
-
out.fov = requireFinite$
|
|
29454
|
+
out.fov = requireFinite$3(cam.fov, `${label}.fov`);
|
|
27926
29455
|
if (out.fov <= 0 || out.fov >= 180) throw new Error(`${label}.fov must be between 0 and 180`);
|
|
27927
29456
|
}
|
|
27928
29457
|
if (cam.type !== void 0) {
|
|
@@ -27940,15 +29469,15 @@ function validateLight(light, label) {
|
|
|
27940
29469
|
}
|
|
27941
29470
|
const out = { type: light.type };
|
|
27942
29471
|
if (light.color !== void 0) out.color = requireColor(light.color, `${label}.color`);
|
|
27943
|
-
if (light.intensity !== void 0) out.intensity = requireFinite$
|
|
29472
|
+
if (light.intensity !== void 0) out.intensity = requireFinite$3(light.intensity, `${label}.intensity`);
|
|
27944
29473
|
if (light.position !== void 0) out.position = requireVec3(light.position, `${label}.position`);
|
|
27945
29474
|
if (light.target !== void 0) out.target = requireVec3(light.target, `${label}.target`);
|
|
27946
29475
|
if (light.groundColor !== void 0) out.groundColor = requireColor(light.groundColor, `${label}.groundColor`);
|
|
27947
29476
|
if (light.skyColor !== void 0) out.skyColor = requireColor(light.skyColor, `${label}.skyColor`);
|
|
27948
|
-
if (light.angle !== void 0) out.angle = requireFinite$
|
|
27949
|
-
if (light.penumbra !== void 0) out.penumbra = requireFinite$
|
|
27950
|
-
if (light.decay !== void 0) out.decay = requireFinite$
|
|
27951
|
-
if (light.distance !== void 0) out.distance = requireFinite$
|
|
29477
|
+
if (light.angle !== void 0) out.angle = requireFinite$3(light.angle, `${label}.angle`);
|
|
29478
|
+
if (light.penumbra !== void 0) out.penumbra = requireFinite$3(light.penumbra, `${label}.penumbra`);
|
|
29479
|
+
if (light.decay !== void 0) out.decay = requireFinite$3(light.decay, `${label}.decay`);
|
|
29480
|
+
if (light.distance !== void 0) out.distance = requireFinite$3(light.distance, `${label}.distance`);
|
|
27952
29481
|
if (light.castShadow !== void 0) {
|
|
27953
29482
|
if (typeof light.castShadow !== "boolean") throw new Error(`${label}.castShadow must be a boolean`);
|
|
27954
29483
|
out.castShadow = light.castShadow;
|
|
@@ -27963,7 +29492,7 @@ function validateEnvironment(env, label) {
|
|
|
27963
29492
|
}
|
|
27964
29493
|
out.preset = env.preset;
|
|
27965
29494
|
}
|
|
27966
|
-
if (env.intensity !== void 0) out.intensity = requireFinite$
|
|
29495
|
+
if (env.intensity !== void 0) out.intensity = requireFinite$3(env.intensity, `${label}.intensity`);
|
|
27967
29496
|
if (env.background !== void 0) {
|
|
27968
29497
|
if (typeof env.background !== "boolean") throw new Error(`${label}.background must be a boolean`);
|
|
27969
29498
|
out.background = env.background;
|
|
@@ -27973,9 +29502,9 @@ function validateEnvironment(env, label) {
|
|
|
27973
29502
|
function validateFog(fog, label) {
|
|
27974
29503
|
const out = {};
|
|
27975
29504
|
if (fog.color !== void 0) out.color = requireColor(fog.color, `${label}.color`);
|
|
27976
|
-
if (fog.near !== void 0) out.near = requireFinite$
|
|
27977
|
-
if (fog.far !== void 0) out.far = requireFinite$
|
|
27978
|
-
if (fog.density !== void 0) out.density = requireFinite$
|
|
29505
|
+
if (fog.near !== void 0) out.near = requireFinite$3(fog.near, `${label}.near`);
|
|
29506
|
+
if (fog.far !== void 0) out.far = requireFinite$3(fog.far, `${label}.far`);
|
|
29507
|
+
if (fog.density !== void 0) out.density = requireFinite$3(fog.density, `${label}.density`);
|
|
27979
29508
|
return out;
|
|
27980
29509
|
}
|
|
27981
29510
|
function validatePostProcessing(pp, label) {
|
|
@@ -27983,23 +29512,23 @@ function validatePostProcessing(pp, label) {
|
|
|
27983
29512
|
if (pp.bloom !== void 0) {
|
|
27984
29513
|
if (!pp.bloom || typeof pp.bloom !== "object") throw new Error(`${label}.bloom must be an object`);
|
|
27985
29514
|
out.bloom = {};
|
|
27986
|
-
if (pp.bloom.intensity !== void 0) out.bloom.intensity = requireFinite$
|
|
27987
|
-
if (pp.bloom.threshold !== void 0) out.bloom.threshold = requireFinite$
|
|
27988
|
-
if (pp.bloom.radius !== void 0) out.bloom.radius = requireFinite$
|
|
29515
|
+
if (pp.bloom.intensity !== void 0) out.bloom.intensity = requireFinite$3(pp.bloom.intensity, `${label}.bloom.intensity`);
|
|
29516
|
+
if (pp.bloom.threshold !== void 0) out.bloom.threshold = requireFinite$3(pp.bloom.threshold, `${label}.bloom.threshold`);
|
|
29517
|
+
if (pp.bloom.radius !== void 0) out.bloom.radius = requireFinite$3(pp.bloom.radius, `${label}.bloom.radius`);
|
|
27989
29518
|
}
|
|
27990
29519
|
if (pp.vignette !== void 0) {
|
|
27991
29520
|
if (!pp.vignette || typeof pp.vignette !== "object") throw new Error(`${label}.vignette must be an object`);
|
|
27992
29521
|
out.vignette = {};
|
|
27993
|
-
if (pp.vignette.darkness !== void 0) out.vignette.darkness = requireFinite$
|
|
27994
|
-
if (pp.vignette.offset !== void 0) out.vignette.offset = requireFinite$
|
|
29522
|
+
if (pp.vignette.darkness !== void 0) out.vignette.darkness = requireFinite$3(pp.vignette.darkness, `${label}.vignette.darkness`);
|
|
29523
|
+
if (pp.vignette.offset !== void 0) out.vignette.offset = requireFinite$3(pp.vignette.offset, `${label}.vignette.offset`);
|
|
27995
29524
|
}
|
|
27996
29525
|
if (pp.grain !== void 0) {
|
|
27997
29526
|
if (!pp.grain || typeof pp.grain !== "object") throw new Error(`${label}.grain must be an object`);
|
|
27998
29527
|
out.grain = {};
|
|
27999
|
-
if (pp.grain.intensity !== void 0) out.grain.intensity = requireFinite$
|
|
29528
|
+
if (pp.grain.intensity !== void 0) out.grain.intensity = requireFinite$3(pp.grain.intensity, `${label}.grain.intensity`);
|
|
28000
29529
|
}
|
|
28001
29530
|
if (pp.toneMappingExposure !== void 0) {
|
|
28002
|
-
out.toneMappingExposure = requireFinite$
|
|
29531
|
+
out.toneMappingExposure = requireFinite$3(pp.toneMappingExposure, `${label}.toneMappingExposure`);
|
|
28003
29532
|
}
|
|
28004
29533
|
return out;
|
|
28005
29534
|
}
|
|
@@ -28010,7 +29539,7 @@ function validateGround(ground, label) {
|
|
|
28010
29539
|
out.visible = ground.visible;
|
|
28011
29540
|
}
|
|
28012
29541
|
if (ground.color !== void 0) out.color = requireColor(ground.color, `${label}.color`);
|
|
28013
|
-
if (ground.offset !== void 0) out.offset = requireFinite$
|
|
29542
|
+
if (ground.offset !== void 0) out.offset = requireFinite$3(ground.offset, `${label}.offset`);
|
|
28014
29543
|
if (ground.receiveShadow !== void 0) {
|
|
28015
29544
|
if (typeof ground.receiveShadow !== "boolean") throw new Error(`${label}.receiveShadow must be a boolean`);
|
|
28016
29545
|
out.receiveShadow = ground.receiveShadow;
|
|
@@ -28020,31 +29549,31 @@ function validateGround(ground, label) {
|
|
|
28020
29549
|
function validateCapture(cap, label) {
|
|
28021
29550
|
const out = {};
|
|
28022
29551
|
if (cap.framesPerTurn !== void 0) {
|
|
28023
|
-
out.framesPerTurn = requireFinite$
|
|
29552
|
+
out.framesPerTurn = requireFinite$3(cap.framesPerTurn, `${label}.framesPerTurn`);
|
|
28024
29553
|
if (out.framesPerTurn < 12 || out.framesPerTurn > 720) {
|
|
28025
29554
|
throw new Error(`${label}.framesPerTurn must be between 12 and 720`);
|
|
28026
29555
|
}
|
|
28027
29556
|
}
|
|
28028
29557
|
if (cap.holdFrames !== void 0) {
|
|
28029
|
-
out.holdFrames = requireFinite$
|
|
29558
|
+
out.holdFrames = requireFinite$3(cap.holdFrames, `${label}.holdFrames`);
|
|
28030
29559
|
if (out.holdFrames < 0 || out.holdFrames > 300) {
|
|
28031
29560
|
throw new Error(`${label}.holdFrames must be between 0 and 300`);
|
|
28032
29561
|
}
|
|
28033
29562
|
}
|
|
28034
29563
|
if (cap.pitchDeg !== void 0) {
|
|
28035
|
-
out.pitchDeg = requireFinite$
|
|
29564
|
+
out.pitchDeg = requireFinite$3(cap.pitchDeg, `${label}.pitchDeg`);
|
|
28036
29565
|
if (out.pitchDeg < -80 || out.pitchDeg > 80) {
|
|
28037
29566
|
throw new Error(`${label}.pitchDeg must be between -80 and 80`);
|
|
28038
29567
|
}
|
|
28039
29568
|
}
|
|
28040
29569
|
if (cap.fps !== void 0) {
|
|
28041
|
-
out.fps = requireFinite$
|
|
29570
|
+
out.fps = requireFinite$3(cap.fps, `${label}.fps`);
|
|
28042
29571
|
if (out.fps < 1 || out.fps > 60) {
|
|
28043
29572
|
throw new Error(`${label}.fps must be between 1 and 60`);
|
|
28044
29573
|
}
|
|
28045
29574
|
}
|
|
28046
29575
|
if (cap.size !== void 0) {
|
|
28047
|
-
out.size = requireFinite$
|
|
29576
|
+
out.size = requireFinite$3(cap.size, `${label}.size`);
|
|
28048
29577
|
if (out.size < 1) {
|
|
28049
29578
|
throw new Error(`${label}.size must be positive`);
|
|
28050
29579
|
}
|
|
@@ -28745,6 +30274,16 @@ function buildProjectionReplayContext(plan) {
|
|
|
28745
30274
|
ok: false,
|
|
28746
30275
|
reason: "projection replay cannot derive a planar projection basis from fromSlices shapes."
|
|
28747
30276
|
};
|
|
30277
|
+
case "nurbsSurface":
|
|
30278
|
+
return {
|
|
30279
|
+
ok: false,
|
|
30280
|
+
reason: "projection replay cannot derive a planar projection basis from NURBS surface shapes."
|
|
30281
|
+
};
|
|
30282
|
+
case "importedStep":
|
|
30283
|
+
return {
|
|
30284
|
+
ok: false,
|
|
30285
|
+
reason: "projection replay cannot derive a planar projection basis from imported STEP files."
|
|
30286
|
+
};
|
|
28748
30287
|
default:
|
|
28749
30288
|
assertExhaustive(plan);
|
|
28750
30289
|
}
|
|
@@ -33113,6 +34652,138 @@ function hermiteTransitionG2(a, b) {
|
|
|
33113
34652
|
{ point: b.point, tangent: b.tangent, curvature: b.curvature, weight: b.weight }
|
|
33114
34653
|
);
|
|
33115
34654
|
}
|
|
34655
|
+
function requireFinite$2(v, label) {
|
|
34656
|
+
if (!Number.isFinite(v)) throw new Error(`nurbs3d: ${label} must be finite, got ${v}`);
|
|
34657
|
+
}
|
|
34658
|
+
class NurbsCurve3D {
|
|
34659
|
+
constructor(points, options = {}) {
|
|
34660
|
+
__publicField(this, "controlPoints");
|
|
34661
|
+
__publicField(this, "weights");
|
|
34662
|
+
__publicField(this, "knots");
|
|
34663
|
+
__publicField(this, "degree");
|
|
34664
|
+
__publicField(this, "closed");
|
|
34665
|
+
const n = points.length;
|
|
34666
|
+
const degree = options.degree ?? 3;
|
|
34667
|
+
if (degree < 1) throw new Error("nurbs3d: degree must be ≥ 1");
|
|
34668
|
+
if (n < degree + 1) throw new Error(`nurbs3d: need at least ${degree + 1} control points for degree ${degree}, got ${n}`);
|
|
34669
|
+
for (let i = 0; i < n; i++) {
|
|
34670
|
+
requireFinite$2(points[i][0], `controlPoints[${i}][0]`);
|
|
34671
|
+
requireFinite$2(points[i][1], `controlPoints[${i}][1]`);
|
|
34672
|
+
requireFinite$2(points[i][2], `controlPoints[${i}][2]`);
|
|
34673
|
+
}
|
|
34674
|
+
const weights = options.weights ?? new Array(n).fill(1);
|
|
34675
|
+
if (weights.length !== n) throw new Error(`nurbs3d: weights.length (${weights.length}) must equal controlPoints.length (${n})`);
|
|
34676
|
+
for (let i = 0; i < n; i++) {
|
|
34677
|
+
requireFinite$2(weights[i], `weights[${i}]`);
|
|
34678
|
+
if (weights[i] <= 0) throw new Error(`nurbs3d: weights[${i}] must be > 0, got ${weights[i]}`);
|
|
34679
|
+
}
|
|
34680
|
+
const expectedKnotLength = n + degree + 1;
|
|
34681
|
+
const knots = options.knots ?? generateClampedKnots(n, degree);
|
|
34682
|
+
if (knots.length !== expectedKnotLength) {
|
|
34683
|
+
throw new Error(`nurbs3d: knots.length (${knots.length}) must be controlPoints.length + degree + 1 (${expectedKnotLength})`);
|
|
34684
|
+
}
|
|
34685
|
+
for (let i = 0; i < knots.length; i++) {
|
|
34686
|
+
requireFinite$2(knots[i], `knots[${i}]`);
|
|
34687
|
+
if (i > 0 && knots[i] < knots[i - 1]) {
|
|
34688
|
+
throw new Error(`nurbs3d: knot vector must be non-decreasing, but knots[${i - 1}]=${knots[i - 1]} > knots[${i}]=${knots[i]}`);
|
|
34689
|
+
}
|
|
34690
|
+
}
|
|
34691
|
+
this.controlPoints = points.map(([x, y, z]) => [x, y, z]);
|
|
34692
|
+
this.weights = [...weights];
|
|
34693
|
+
this.knots = [...knots];
|
|
34694
|
+
this.degree = degree;
|
|
34695
|
+
this.closed = options.closed ?? false;
|
|
34696
|
+
}
|
|
34697
|
+
/**
|
|
34698
|
+
* Evaluate the curve at parameter t ∈ [0, 1].
|
|
34699
|
+
* Uses De Boor's algorithm — exact, O(degree²).
|
|
34700
|
+
*/
|
|
34701
|
+
pointAt(t) {
|
|
34702
|
+
const u = remapToKnotDomain(t, this.controlPoints.length, this.degree, this.knots);
|
|
34703
|
+
return deBoor3D(this.controlPoints, this.weights, this.knots, this.degree, u);
|
|
34704
|
+
}
|
|
34705
|
+
/**
|
|
34706
|
+
* Evaluate the unit tangent vector at parameter t ∈ [0, 1].
|
|
34707
|
+
*/
|
|
34708
|
+
tangentAt(t) {
|
|
34709
|
+
const u = remapToKnotDomain(t, this.controlPoints.length, this.degree, this.knots);
|
|
34710
|
+
const d = deBoor3DDeriv(this.controlPoints, this.weights, this.knots, this.degree, u);
|
|
34711
|
+
const len = Math.sqrt(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]);
|
|
34712
|
+
if (len < 1e-12) return [0, 0, 1];
|
|
34713
|
+
return [d[0] / len, d[1] / len, d[2] / len];
|
|
34714
|
+
}
|
|
34715
|
+
/**
|
|
34716
|
+
* Sample the curve uniformly at `count` points.
|
|
34717
|
+
*/
|
|
34718
|
+
sample(count = 48) {
|
|
34719
|
+
return sampleNurbs3D(this.controlPoints, this.weights, this.knots, this.degree, Math.max(2, count));
|
|
34720
|
+
}
|
|
34721
|
+
/**
|
|
34722
|
+
* Sample with adaptive density — more points in high-curvature regions.
|
|
34723
|
+
*/
|
|
34724
|
+
sampleAdaptive(minCount = 32, maxCount = 128) {
|
|
34725
|
+
const probeCount = Math.max(minCount, 64);
|
|
34726
|
+
const curvatures = [];
|
|
34727
|
+
for (let i = 0; i <= probeCount; i++) {
|
|
34728
|
+
curvatures.push(this.estimateCurvature(i / probeCount));
|
|
34729
|
+
}
|
|
34730
|
+
const cumulative = [0];
|
|
34731
|
+
for (let i = 1; i < curvatures.length; i++) {
|
|
34732
|
+
const avgCurv = (curvatures[i - 1] + curvatures[i]) / 2;
|
|
34733
|
+
cumulative.push(cumulative[i - 1] + 1 + avgCurv);
|
|
34734
|
+
}
|
|
34735
|
+
const total = cumulative[cumulative.length - 1];
|
|
34736
|
+
const targetCount = Math.min(maxCount, Math.max(minCount, Math.round(minCount * 1.5)));
|
|
34737
|
+
const pts = [this.pointAt(0)];
|
|
34738
|
+
let probeIdx = 0;
|
|
34739
|
+
for (let i = 1; i < targetCount; i++) {
|
|
34740
|
+
const target = i / targetCount * total;
|
|
34741
|
+
while (probeIdx < cumulative.length - 1 && cumulative[probeIdx + 1] < target) {
|
|
34742
|
+
probeIdx++;
|
|
34743
|
+
}
|
|
34744
|
+
const frac = (target - cumulative[probeIdx]) / (cumulative[probeIdx + 1] - cumulative[probeIdx]);
|
|
34745
|
+
const t = (probeIdx + frac) / probeCount;
|
|
34746
|
+
pts.push(this.pointAt(t));
|
|
34747
|
+
}
|
|
34748
|
+
pts.push(this.pointAt(1));
|
|
34749
|
+
return pts;
|
|
34750
|
+
}
|
|
34751
|
+
/**
|
|
34752
|
+
* Approximate arc length by summing polyline segment lengths.
|
|
34753
|
+
*/
|
|
34754
|
+
length(samples = 100) {
|
|
34755
|
+
const pts = this.sample(Math.max(10, samples));
|
|
34756
|
+
let len = 0;
|
|
34757
|
+
for (let i = 1; i < pts.length; i++) {
|
|
34758
|
+
const dx = pts[i][0] - pts[i - 1][0];
|
|
34759
|
+
const dy = pts[i][1] - pts[i - 1][1];
|
|
34760
|
+
const dz = pts[i][2] - pts[i - 1][2];
|
|
34761
|
+
len += Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
34762
|
+
}
|
|
34763
|
+
return len;
|
|
34764
|
+
}
|
|
34765
|
+
/** Convert to a format compatible with sweep() path input. */
|
|
34766
|
+
toPolyline(samples = 64) {
|
|
34767
|
+
return this.sampleAdaptive(Math.max(16, samples), samples * 2);
|
|
34768
|
+
}
|
|
34769
|
+
estimateCurvature(t) {
|
|
34770
|
+
const eps = 1e-4;
|
|
34771
|
+
const t0 = Math.max(0, t - eps);
|
|
34772
|
+
const t1 = Math.min(1, t + eps);
|
|
34773
|
+
const tm = (t0 + t1) / 2;
|
|
34774
|
+
const p0 = this.pointAt(t0);
|
|
34775
|
+
const pm = this.pointAt(tm);
|
|
34776
|
+
const p1 = this.pointAt(t1);
|
|
34777
|
+
const dt = (t1 - t0) / 2;
|
|
34778
|
+
const d2x = (p0[0] - 2 * pm[0] + p1[0]) / (dt * dt);
|
|
34779
|
+
const d2y = (p0[1] - 2 * pm[1] + p1[1]) / (dt * dt);
|
|
34780
|
+
const d2z = (p0[2] - 2 * pm[2] + p1[2]) / (dt * dt);
|
|
34781
|
+
return Math.sqrt(d2x * d2x + d2y * d2y + d2z * d2z);
|
|
34782
|
+
}
|
|
34783
|
+
}
|
|
34784
|
+
function nurbs3d(points, options) {
|
|
34785
|
+
return new NurbsCurve3D(points, options);
|
|
34786
|
+
}
|
|
33116
34787
|
function clamp$4(v, lo, hi) {
|
|
33117
34788
|
return Math.max(lo, Math.min(hi, v));
|
|
33118
34789
|
}
|
|
@@ -33455,6 +35126,19 @@ function buildPathPlan(path2) {
|
|
|
33455
35126
|
sampleForBounds: (samples) => path2.sample(samples)
|
|
33456
35127
|
};
|
|
33457
35128
|
}
|
|
35129
|
+
if (path2 instanceof NurbsCurve3D) {
|
|
35130
|
+
return {
|
|
35131
|
+
plan: {
|
|
35132
|
+
kind: "nurbs",
|
|
35133
|
+
controlPoints: path2.controlPoints.map(([x, y, z]) => [x, y, z]),
|
|
35134
|
+
weights: [...path2.weights],
|
|
35135
|
+
knots: [...path2.knots],
|
|
35136
|
+
degree: path2.degree,
|
|
35137
|
+
closed: path2.closed
|
|
35138
|
+
},
|
|
35139
|
+
sampleForBounds: (samples) => path2.sample(samples)
|
|
35140
|
+
};
|
|
35141
|
+
}
|
|
33458
35142
|
throw new Error("sweep: unsupported path type");
|
|
33459
35143
|
}
|
|
33460
35144
|
function sweep(profile, path2, options = {}) {
|
|
@@ -48824,6 +50508,16 @@ function sheetMetal(options) {
|
|
|
48824
50508
|
deriveSheetMetalModel(model);
|
|
48825
50509
|
return new SheetMetalPart(model);
|
|
48826
50510
|
}
|
|
50511
|
+
function importStepFromBuffer(fileData, displayName = "import.step") {
|
|
50512
|
+
return buildShapeFromCompilePlan(
|
|
50513
|
+
createOwnedShapeCompilePlan(
|
|
50514
|
+
{ kind: "importedStep", filePath: displayName, fileData },
|
|
50515
|
+
"importStep"
|
|
50516
|
+
),
|
|
50517
|
+
void 0,
|
|
50518
|
+
{ fidelity: "exact", sources: ["imported"] }
|
|
50519
|
+
);
|
|
50520
|
+
}
|
|
48827
50521
|
let collectedSheetStock = [];
|
|
48828
50522
|
let sheetStockCounter = 0;
|
|
48829
50523
|
function resetSheetStock() {
|
|
@@ -261689,6 +263383,75 @@ function resolveErrorLocation(stack, compiledFiles) {
|
|
|
261689
263383
|
column: parseInt(anonymousMatch[2], 10)
|
|
261690
263384
|
};
|
|
261691
263385
|
}
|
|
263386
|
+
function statementTargetVar(stmt) {
|
|
263387
|
+
if (typescriptExports.isVariableStatement(stmt)) {
|
|
263388
|
+
const decl = stmt.declarationList.declarations[0];
|
|
263389
|
+
if (decl && typescriptExports.isIdentifier(decl.name)) return decl.name.text;
|
|
263390
|
+
}
|
|
263391
|
+
if (typescriptExports.isExpressionStatement(stmt)) {
|
|
263392
|
+
const expr = stmt.expression;
|
|
263393
|
+
if (typescriptExports.isBinaryExpression(expr) && expr.operatorToken.kind === typescriptExports.SyntaxKind.EqualsToken && typescriptExports.isIdentifier(expr.left)) {
|
|
263394
|
+
return expr.left.text;
|
|
263395
|
+
}
|
|
263396
|
+
}
|
|
263397
|
+
return null;
|
|
263398
|
+
}
|
|
263399
|
+
function collectReferencedNames(node, exclude, names) {
|
|
263400
|
+
if (typescriptExports.isIdentifier(node) && !exclude.has(node)) {
|
|
263401
|
+
names.add(node.text);
|
|
263402
|
+
}
|
|
263403
|
+
typescriptExports.forEachChild(node, (child) => collectReferencedNames(child, exclude, names));
|
|
263404
|
+
}
|
|
263405
|
+
function extractUnusedTopLevelVarNames(code) {
|
|
263406
|
+
const sourceFile = typescriptExports.createSourceFile("__implicit.js", code, typescriptExports.ScriptTarget.ES2020, false, typescriptExports.ScriptKind.JS);
|
|
263407
|
+
const declaredNames = [];
|
|
263408
|
+
const collectBindingNames = (node) => {
|
|
263409
|
+
if (typescriptExports.isIdentifier(node)) {
|
|
263410
|
+
declaredNames.push(node.text);
|
|
263411
|
+
} else if (typescriptExports.isObjectBindingPattern(node) || typescriptExports.isArrayBindingPattern(node)) {
|
|
263412
|
+
for (const element of node.elements) {
|
|
263413
|
+
if (typescriptExports.isBindingElement(element)) {
|
|
263414
|
+
collectBindingNames(element.name);
|
|
263415
|
+
}
|
|
263416
|
+
}
|
|
263417
|
+
}
|
|
263418
|
+
};
|
|
263419
|
+
for (const statement of sourceFile.statements) {
|
|
263420
|
+
if (typescriptExports.isVariableStatement(statement)) {
|
|
263421
|
+
for (const decl of statement.declarationList.declarations) {
|
|
263422
|
+
collectBindingNames(decl.name);
|
|
263423
|
+
}
|
|
263424
|
+
} else if (typescriptExports.isFunctionDeclaration(statement) && statement.name) {
|
|
263425
|
+
declaredNames.push(statement.name.text);
|
|
263426
|
+
}
|
|
263427
|
+
}
|
|
263428
|
+
const excluded = /* @__PURE__ */ new Set(["exports", "module", "require", "__filename", "__dirname"]);
|
|
263429
|
+
const topLevelNames = new Set(declaredNames.filter((n) => !excluded.has(n)));
|
|
263430
|
+
if (topLevelNames.size === 0) return [];
|
|
263431
|
+
const usedByOthers = /* @__PURE__ */ new Set();
|
|
263432
|
+
for (const statement of sourceFile.statements) {
|
|
263433
|
+
const target = statementTargetVar(statement);
|
|
263434
|
+
const refs = /* @__PURE__ */ new Set();
|
|
263435
|
+
const lhsNodes = /* @__PURE__ */ new Set();
|
|
263436
|
+
if (typescriptExports.isVariableStatement(statement)) {
|
|
263437
|
+
for (const decl of statement.declarationList.declarations) {
|
|
263438
|
+
if (typescriptExports.isIdentifier(decl.name)) lhsNodes.add(decl.name);
|
|
263439
|
+
}
|
|
263440
|
+
} else if (typescriptExports.isExpressionStatement(statement)) {
|
|
263441
|
+
const expr = statement.expression;
|
|
263442
|
+
if (typescriptExports.isBinaryExpression(expr) && typescriptExports.isIdentifier(expr.left)) {
|
|
263443
|
+
lhsNodes.add(expr.left);
|
|
263444
|
+
}
|
|
263445
|
+
}
|
|
263446
|
+
collectReferencedNames(statement, lhsNodes, refs);
|
|
263447
|
+
for (const ref of refs) {
|
|
263448
|
+
if (topLevelNames.has(ref) && ref !== target) {
|
|
263449
|
+
usedByOthers.add(ref);
|
|
263450
|
+
}
|
|
263451
|
+
}
|
|
263452
|
+
}
|
|
263453
|
+
return declaredNames.filter((n) => topLevelNames.has(n) && !usedByOthers.has(n));
|
|
263454
|
+
}
|
|
261692
263455
|
function createForgeRuntimeModule(bindings) {
|
|
261693
263456
|
const runtime = { ...bindings };
|
|
261694
263457
|
Object.defineProperty(runtime, "__esModule", { value: true });
|
|
@@ -261743,6 +263506,34 @@ function finalizeForgeJsImport(moduleExports, importedDims) {
|
|
|
261743
263506
|
if (importedDims.length === 0) return base;
|
|
261744
263507
|
return setShapeDimensions(base, [...getShapeDimensions(base), ...importedDims]);
|
|
261745
263508
|
}
|
|
263509
|
+
function rejectPathTraversal(fnName, userPath, resolvedPath) {
|
|
263510
|
+
if (resolvedPath.startsWith("..")) {
|
|
263511
|
+
throw new Error(`${fnName}("${userPath}"): path traversal blocked — resolved path escapes the project directory`);
|
|
263512
|
+
}
|
|
263513
|
+
}
|
|
263514
|
+
let _constructorLockdownDepth = 0;
|
|
263515
|
+
let _origConstructorDescriptor;
|
|
263516
|
+
function withConstructorChainLockdown(fn) {
|
|
263517
|
+
_constructorLockdownDepth++;
|
|
263518
|
+
if (_constructorLockdownDepth === 1) {
|
|
263519
|
+
_origConstructorDescriptor = Object.getOwnPropertyDescriptor(Function.prototype, "constructor");
|
|
263520
|
+
Object.defineProperty(Function.prototype, "constructor", {
|
|
263521
|
+
get() {
|
|
263522
|
+
throw new Error("Dynamic code generation is not allowed in ForgeCAD scripts");
|
|
263523
|
+
},
|
|
263524
|
+
configurable: true
|
|
263525
|
+
});
|
|
263526
|
+
}
|
|
263527
|
+
try {
|
|
263528
|
+
return fn();
|
|
263529
|
+
} finally {
|
|
263530
|
+
_constructorLockdownDepth--;
|
|
263531
|
+
if (_constructorLockdownDepth === 0 && _origConstructorDescriptor) {
|
|
263532
|
+
Object.defineProperty(Function.prototype, "constructor", _origConstructorDescriptor);
|
|
263533
|
+
_origConstructorDescriptor = void 0;
|
|
263534
|
+
}
|
|
263535
|
+
}
|
|
263536
|
+
}
|
|
261746
263537
|
function executeFile(code, fileName, allFiles, visited, scope = {}, options, executionMode = "script", moduleCacheEntry) {
|
|
261747
263538
|
const trackCircularImports = executionMode === "script";
|
|
261748
263539
|
if (trackCircularImports) {
|
|
@@ -261790,6 +263581,7 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
|
|
|
261790
263581
|
throw new Error("importMesh() requires a non-empty file path string");
|
|
261791
263582
|
}
|
|
261792
263583
|
const resolvedPath = resolveImportPath(fileName, name.trim());
|
|
263584
|
+
rejectPathTraversal("importMesh", name, resolvedPath);
|
|
261793
263585
|
const format = detectMeshFormat(resolvedPath);
|
|
261794
263586
|
if (!format) {
|
|
261795
263587
|
const ext = resolvedPath.split(".").pop() ?? "";
|
|
@@ -261819,6 +263611,25 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
|
|
|
261819
263611
|
sources: ["imported"]
|
|
261820
263612
|
});
|
|
261821
263613
|
};
|
|
263614
|
+
const importStep = (name) => {
|
|
263615
|
+
var _a3;
|
|
263616
|
+
if (typeof name !== "string" || name.trim().length === 0) {
|
|
263617
|
+
throw new Error("importStep() requires a non-empty file path string");
|
|
263618
|
+
}
|
|
263619
|
+
const resolvedPath = resolveImportPath(fileName, name.trim());
|
|
263620
|
+
rejectPathTraversal("importStep", name, resolvedPath);
|
|
263621
|
+
const ext = ((_a3 = resolvedPath.split(".").pop()) == null ? void 0 : _a3.toLowerCase()) ?? "";
|
|
263622
|
+
if (ext !== "step" && ext !== "stp") {
|
|
263623
|
+
throw new Error(`importStep("${name}"): unsupported extension ".${ext}". Expected .step or .stp`);
|
|
263624
|
+
}
|
|
263625
|
+
if (!options.readBinaryFile) {
|
|
263626
|
+
throw new Error(
|
|
263627
|
+
`importStep("${name}"): binary file reading is not available in this environment. Provide a readBinaryFile callback in RunScriptOptions.`
|
|
263628
|
+
);
|
|
263629
|
+
}
|
|
263630
|
+
const fileData = options.readBinaryFile(resolvedPath);
|
|
263631
|
+
return importStepFromBuffer(fileData, name);
|
|
263632
|
+
};
|
|
261822
263633
|
const wrappedUnion = union;
|
|
261823
263634
|
const wrappedDifference = difference;
|
|
261824
263635
|
const wrappedIntersection = intersection;
|
|
@@ -261882,6 +263693,10 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
|
|
|
261882
263693
|
filletCorners,
|
|
261883
263694
|
chamfer2d,
|
|
261884
263695
|
Curve3D,
|
|
263696
|
+
NurbsCurve3D,
|
|
263697
|
+
nurbs3d,
|
|
263698
|
+
NurbsSurface,
|
|
263699
|
+
nurbsSurface,
|
|
261885
263700
|
spline2d,
|
|
261886
263701
|
spline3d,
|
|
261887
263702
|
loft,
|
|
@@ -261895,10 +263710,13 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
|
|
|
261895
263710
|
surfacePatch,
|
|
261896
263711
|
sheetMetal,
|
|
261897
263712
|
SheetMetalPart,
|
|
263713
|
+
Param,
|
|
261898
263714
|
param,
|
|
261899
263715
|
boolParam,
|
|
261900
263716
|
choiceParam,
|
|
261901
|
-
listParam
|
|
263717
|
+
listParam: () => {
|
|
263718
|
+
throw new Error("listParam() has been renamed to Param.list(). Update your script.");
|
|
263719
|
+
},
|
|
261902
263720
|
sdf,
|
|
261903
263721
|
Shape,
|
|
261904
263722
|
Sketch,
|
|
@@ -261928,6 +263746,7 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
|
|
|
261928
263746
|
offsetSolid,
|
|
261929
263747
|
importSvgSketch,
|
|
261930
263748
|
importMesh,
|
|
263749
|
+
importStep,
|
|
261931
263750
|
text2d,
|
|
261932
263751
|
textWidth,
|
|
261933
263752
|
loadFont,
|
|
@@ -261944,12 +263763,14 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
|
|
|
261944
263763
|
ShapeGroup,
|
|
261945
263764
|
console: sandboxConsole,
|
|
261946
263765
|
cutPlane,
|
|
263766
|
+
cameraTrajectory,
|
|
261947
263767
|
explodeView,
|
|
261948
263768
|
jointsView,
|
|
261949
263769
|
viewConfig,
|
|
261950
263770
|
scene,
|
|
261951
263771
|
verify,
|
|
261952
263772
|
spec,
|
|
263773
|
+
mock,
|
|
261953
263774
|
gcode,
|
|
261954
263775
|
GCodeBuilder,
|
|
261955
263776
|
// ── Laser Kit ──────────────────────────────────────────────────
|
|
@@ -261962,7 +263783,19 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
|
|
|
261962
263783
|
assemblyInstructions,
|
|
261963
263784
|
formatInstructions,
|
|
261964
263785
|
lookupKerf,
|
|
261965
|
-
COMMON_KERFS
|
|
263786
|
+
COMMON_KERFS,
|
|
263787
|
+
// ── Sandbox safety: shadow dangerous globals ───────────────────
|
|
263788
|
+
// These prevent user code from accessing escape vectors directly.
|
|
263789
|
+
// The constructor-chain lockdown (below) covers indirect access.
|
|
263790
|
+
Function: void 0,
|
|
263791
|
+
globalThis: void 0,
|
|
263792
|
+
global: void 0,
|
|
263793
|
+
self: void 0,
|
|
263794
|
+
window: void 0,
|
|
263795
|
+
setTimeout: void 0,
|
|
263796
|
+
setInterval: void 0,
|
|
263797
|
+
setImmediate: void 0,
|
|
263798
|
+
queueMicrotask: void 0
|
|
261966
263799
|
};
|
|
261967
263800
|
const requireModule = (requestedName, paramOverrides) => {
|
|
261968
263801
|
if (typeof requestedName !== "string" || requestedName.trim().length === 0) {
|
|
@@ -262076,6 +263909,15 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
|
|
|
262076
263909
|
const compiled = compileScript(code, fileName, options);
|
|
262077
263910
|
const bindingNames = Object.keys(runtimeBindings);
|
|
262078
263911
|
const bindingValues = bindingNames.map((name) => runtimeBindings[name]);
|
|
263912
|
+
let scriptCode = compiled.code;
|
|
263913
|
+
if (executionMode === "script") {
|
|
263914
|
+
const varNames = extractUnusedTopLevelVarNames(compiled.code).filter((n) => !bindingNames.includes(n));
|
|
263915
|
+
if (varNames.length > 0) {
|
|
263916
|
+
const collector = varNames.map((n) => `${JSON.stringify(n)}: ${n}`).join(", ");
|
|
263917
|
+
scriptCode += `
|
|
263918
|
+
; try { module.__implicitVars = {${collector}}; } catch(e) {}`;
|
|
263919
|
+
}
|
|
263920
|
+
}
|
|
262079
263921
|
const fn = new Function(
|
|
262080
263922
|
"exports",
|
|
262081
263923
|
"module",
|
|
@@ -262083,16 +263925,19 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
|
|
|
262083
263925
|
"__filename",
|
|
262084
263926
|
"__dirname",
|
|
262085
263927
|
...bindingNames,
|
|
262086
|
-
|
|
263928
|
+
`"use strict";
|
|
263929
|
+
${scriptCode}
|
|
262087
263930
|
//# sourceURL=${fileName}`
|
|
262088
263931
|
);
|
|
262089
263932
|
const moduleValue = {
|
|
262090
263933
|
exports: executionMode === "module" && moduleCacheEntry ? moduleCacheEntry.exports : {}
|
|
262091
263934
|
};
|
|
262092
263935
|
const initialExportsRef = moduleValue.exports;
|
|
262093
|
-
const returnValue =
|
|
262094
|
-
|
|
262095
|
-
|
|
263936
|
+
const returnValue = withConstructorChainLockdown(
|
|
263937
|
+
() => runWithParamScope(
|
|
263938
|
+
scope,
|
|
263939
|
+
() => fn(moduleValue.exports, moduleValue, requireModule, fileName, dirnamePath(fileName), ...bindingValues)
|
|
263940
|
+
)
|
|
262096
263941
|
);
|
|
262097
263942
|
if (executionMode === "module") {
|
|
262098
263943
|
const hasExports = hasExplicitModuleExports(moduleValue.exports, initialExportsRef);
|
|
@@ -262117,7 +263962,18 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
|
|
|
262117
263962
|
}
|
|
262118
263963
|
const exportedResult = resolveExportedEntryResult(moduleValue.exports);
|
|
262119
263964
|
if (returnValue === void 0) {
|
|
262120
|
-
|
|
263965
|
+
if (exportedResult != null) return exportedResult;
|
|
263966
|
+
const implicitVars = moduleValue.__implicitVars;
|
|
263967
|
+
if (implicitVars) {
|
|
263968
|
+
const renderables = {};
|
|
263969
|
+
for (const [key, value] of Object.entries(implicitVars)) {
|
|
263970
|
+
if (isRenderableEntryResult(value)) {
|
|
263971
|
+
renderables[key] = value;
|
|
263972
|
+
}
|
|
263973
|
+
}
|
|
263974
|
+
if (Object.keys(renderables).length > 0) return renderables;
|
|
263975
|
+
}
|
|
263976
|
+
return null;
|
|
262121
263977
|
}
|
|
262122
263978
|
return returnValue;
|
|
262123
263979
|
} finally {
|
|
@@ -262154,12 +264010,15 @@ function runScript(code, fileName = "main.forge.js", allFiles = {}, options = {}
|
|
|
262154
264010
|
resetSheetStock();
|
|
262155
264011
|
resetRobotExport();
|
|
262156
264012
|
resetCutPlanes();
|
|
264013
|
+
resetCameraTrajectory();
|
|
262157
264014
|
resetExplodeView();
|
|
262158
264015
|
resetJointsView();
|
|
262159
264016
|
resetViewConfig();
|
|
262160
264017
|
resetScene();
|
|
262161
264018
|
resetVerifications();
|
|
264019
|
+
resetMocks();
|
|
262162
264020
|
_collectedLogs = [];
|
|
264021
|
+
setRuntimeWarnSink((msg) => _collectedLogs.push({ level: "warn", args: [msg], timestamp: Date.now() }));
|
|
262163
264022
|
const t0 = performance.now();
|
|
262164
264023
|
const execOptions = {
|
|
262165
264024
|
debugImports: options.debugImports ?? envFlagEnabled("FORGECAD_DEBUG_IMPORTS"),
|
|
@@ -262399,7 +264258,19 @@ function runScript(code, fileName = "main.forge.js", allFiles = {}, options = {}
|
|
|
262399
264258
|
} else {
|
|
262400
264259
|
const entries = Object.entries(obj);
|
|
262401
264260
|
entries.forEach(([key, value]) => {
|
|
262402
|
-
if (value instanceof
|
|
264261
|
+
if (value instanceof Assembly) {
|
|
264262
|
+
const items = value.solve().toSceneObjects();
|
|
264263
|
+
items.forEach((item, index) => {
|
|
264264
|
+
const label = `${key}.${index + 1}`;
|
|
264265
|
+
processNamedItem(item, label, label);
|
|
264266
|
+
});
|
|
264267
|
+
} else if (value instanceof SolvedAssembly) {
|
|
264268
|
+
const items = value.toSceneObjects();
|
|
264269
|
+
items.forEach((item, index) => {
|
|
264270
|
+
const label = `${key}.${index + 1}`;
|
|
264271
|
+
processNamedItem(item, label, label);
|
|
264272
|
+
});
|
|
264273
|
+
} else if (value instanceof Shape) {
|
|
262403
264274
|
pushShape(value, key, void 0, void 0, void 0, [key]);
|
|
262404
264275
|
} else if (value instanceof Sketch) {
|
|
262405
264276
|
pushSketch(value, key, void 0, [key]);
|
|
@@ -262407,6 +264278,21 @@ function runScript(code, fileName = "main.forge.js", allFiles = {}, options = {}
|
|
|
262407
264278
|
value.children.forEach((child, i) => {
|
|
262408
264279
|
flattenGroupChild(child, groupChildLabel(value, key, i), void 0, [key, shapeGroupChildSegment(value, i)]);
|
|
262409
264280
|
});
|
|
264281
|
+
} else if (Array.isArray(value)) {
|
|
264282
|
+
value.forEach((item, index) => {
|
|
264283
|
+
const label = `${key}.${index + 1}`;
|
|
264284
|
+
if (item instanceof ShapeGroup) {
|
|
264285
|
+
item.children.forEach((child, i) => {
|
|
264286
|
+
flattenGroupChild(child, groupChildLabel(item, label, i), void 0, [key, label, shapeGroupChildSegment(item, i)]);
|
|
264287
|
+
});
|
|
264288
|
+
} else if (item instanceof Shape) {
|
|
264289
|
+
pushShape(item, label, void 0, void 0, void 0, [key, label]);
|
|
264290
|
+
} else if (item instanceof Sketch) {
|
|
264291
|
+
pushSketch(item, label, void 0, [key, label]);
|
|
264292
|
+
} else if (isNamedObject(item)) {
|
|
264293
|
+
processNamedItem(item, label, label, void 0, [key]);
|
|
264294
|
+
}
|
|
264295
|
+
});
|
|
262410
264296
|
}
|
|
262411
264297
|
});
|
|
262412
264298
|
}
|
|
@@ -262435,12 +264321,23 @@ function runScript(code, fileName = "main.forge.js", allFiles = {}, options = {}
|
|
|
262435
264321
|
}
|
|
262436
264322
|
const shape = objects.length === 1 ? objects[0].shape : null;
|
|
262437
264323
|
const sketch = objects.length === 1 ? objects[0].sketch : null;
|
|
264324
|
+
const collectedMocks = getCollectedMocks();
|
|
264325
|
+
for (const m of collectedMocks) {
|
|
264326
|
+
objects.push({
|
|
264327
|
+
id: m.id,
|
|
264328
|
+
name: `${m.name} (mock)`,
|
|
264329
|
+
shape: m.shape,
|
|
264330
|
+
sketch: null,
|
|
264331
|
+
mock: true
|
|
264332
|
+
});
|
|
264333
|
+
}
|
|
262438
264334
|
autoFillExplodeHints(objects);
|
|
262439
264335
|
return {
|
|
262440
264336
|
shape,
|
|
262441
264337
|
sketch,
|
|
262442
264338
|
objects,
|
|
262443
264339
|
params: getCollectedParams(),
|
|
264340
|
+
stringParams: getCollectedStringParams(),
|
|
262444
264341
|
listParams: getCollectedListParams(),
|
|
262445
264342
|
dimensions: [...getCollectedDimensions(), ...shapeDimensions],
|
|
262446
264343
|
highlights: getCollectedHighlights(),
|
|
@@ -262448,6 +264345,7 @@ function runScript(code, fileName = "main.forge.js", allFiles = {}, options = {}
|
|
|
262448
264345
|
bom: getCollectedBom(),
|
|
262449
264346
|
sheetStock: getCollectedSheetStock(),
|
|
262450
264347
|
cutPlanes: getCollectedCutPlanes(),
|
|
264348
|
+
cameraTrajectory: getCollectedCameraTrajectory(),
|
|
262451
264349
|
explodeView: getCollectedExplodeView(),
|
|
262452
264350
|
jointsView: getCollectedJointsView(),
|
|
262453
264351
|
viewConfig: getCollectedViewConfig(),
|
|
@@ -262457,7 +264355,8 @@ function runScript(code, fileName = "main.forge.js", allFiles = {}, options = {}
|
|
|
262457
264355
|
error: objects.length > 0 || options.allowEmptyResult ? null : "Script must return a Shape or Sketch",
|
|
262458
264356
|
timeMs: performance.now() - t0,
|
|
262459
264357
|
logs: _collectedLogs.slice(),
|
|
262460
|
-
verifications: getCollectedVerifications()
|
|
264358
|
+
verifications: getCollectedVerifications(),
|
|
264359
|
+
mocks: getCollectedMocks()
|
|
262461
264360
|
};
|
|
262462
264361
|
});
|
|
262463
264362
|
} catch (e) {
|
|
@@ -262474,6 +264373,7 @@ function runScript(code, fileName = "main.forge.js", allFiles = {}, options = {}
|
|
|
262474
264373
|
sketch: null,
|
|
262475
264374
|
objects: [],
|
|
262476
264375
|
params: getCollectedParams(),
|
|
264376
|
+
stringParams: getCollectedStringParams(),
|
|
262477
264377
|
listParams: getCollectedListParams(),
|
|
262478
264378
|
dimensions: getCollectedDimensions(),
|
|
262479
264379
|
highlights: getCollectedHighlights(),
|
|
@@ -262481,6 +264381,7 @@ function runScript(code, fileName = "main.forge.js", allFiles = {}, options = {}
|
|
|
262481
264381
|
bom: getCollectedBom(),
|
|
262482
264382
|
sheetStock: getCollectedSheetStock(),
|
|
262483
264383
|
cutPlanes: getCollectedCutPlanes(),
|
|
264384
|
+
cameraTrajectory: getCollectedCameraTrajectory(),
|
|
262484
264385
|
explodeView: getCollectedExplodeView(),
|
|
262485
264386
|
jointsView: getCollectedJointsView(),
|
|
262486
264387
|
viewConfig: getCollectedViewConfig(),
|
|
@@ -262490,7 +264391,8 @@ function runScript(code, fileName = "main.forge.js", allFiles = {}, options = {}
|
|
|
262490
264391
|
error: `${msg}${lineInfo}`,
|
|
262491
264392
|
timeMs: performance.now() - t0,
|
|
262492
264393
|
logs: _collectedLogs.slice(),
|
|
262493
|
-
verifications: getCollectedVerifications()
|
|
264394
|
+
verifications: getCollectedVerifications(),
|
|
264395
|
+
mocks: getCollectedMocks()
|
|
262494
264396
|
};
|
|
262495
264397
|
}
|
|
262496
264398
|
}
|
|
@@ -263061,6 +264963,19 @@ function isWasmCrash(error) {
|
|
|
263061
264963
|
const msg = error instanceof Error ? error.message : String(error);
|
|
263062
264964
|
return /abort|unreachable|out of bounds|out of memory|OOM/i.test(msg);
|
|
263063
264965
|
}
|
|
264966
|
+
function disposeRunResult(runResult) {
|
|
264967
|
+
if (!runResult) return;
|
|
264968
|
+
const seen = /* @__PURE__ */ new Set();
|
|
264969
|
+
const disposeShape = (shape) => {
|
|
264970
|
+
if (!shape) return;
|
|
264971
|
+
const backend = getShapeRuntimeBackend(shape);
|
|
264972
|
+
if (seen.has(backend)) return;
|
|
264973
|
+
seen.add(backend);
|
|
264974
|
+
disposeShapeBackend(backend);
|
|
264975
|
+
};
|
|
264976
|
+
disposeShape(runResult.shape);
|
|
264977
|
+
runResult.objects.forEach((obj) => disposeShape(obj.shape));
|
|
264978
|
+
}
|
|
263064
264979
|
async function runOnce(payload) {
|
|
263065
264980
|
const { seq, code, file, files, quality, paramOverrides, activeBackend } = payload;
|
|
263066
264981
|
try {
|
|
@@ -263078,6 +264993,7 @@ async function runOnce(payload) {
|
|
|
263078
264993
|
console.log(`[worker] seq=${seq} stale (newer queued) — skipping serialize. run=${(tRun - tKernel).toFixed(0)}ms`);
|
|
263079
264994
|
return;
|
|
263080
264995
|
}
|
|
264996
|
+
disposeRunResult(lastRunResult);
|
|
263081
264997
|
lastRunResult = runResult;
|
|
263082
264998
|
worker.postMessage({ type: "progress", payload: { seq, phase: "serializing" } });
|
|
263083
264999
|
const { serialized, transferables } = serializeRunResult(runResult, getSolverWasmRunDebugSnapshot());
|
|
@@ -263085,7 +265001,21 @@ async function runOnce(payload) {
|
|
|
263085
265001
|
console.log(
|
|
263086
265002
|
`[worker] seq=${seq} kernelInit=${(tKernel - t0).toFixed(0)}ms run=${(tRun - tKernel).toFixed(0)}ms serialize=${(tSerialize - tRun).toFixed(0)}ms total=${(tSerialize - t0).toFixed(0)}ms`
|
|
263087
265003
|
);
|
|
263088
|
-
worker.postMessage(
|
|
265004
|
+
worker.postMessage(
|
|
265005
|
+
{
|
|
265006
|
+
type: "run-success",
|
|
265007
|
+
payload: {
|
|
265008
|
+
seq,
|
|
265009
|
+
result: serialized,
|
|
265010
|
+
wasmHeap: {
|
|
265011
|
+
manifoldBytes: getWasmHeapBytes(),
|
|
265012
|
+
solverBytes: getSolverWasmHeapBytes(),
|
|
265013
|
+
occtBytes: getOcctHeapBytes()
|
|
265014
|
+
}
|
|
265015
|
+
}
|
|
265016
|
+
},
|
|
265017
|
+
transferables
|
|
265018
|
+
);
|
|
263089
265019
|
} catch (error) {
|
|
263090
265020
|
const fatal = isWasmCrash(error);
|
|
263091
265021
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -263108,7 +265038,9 @@ async function handleExportExact(data) {
|
|
|
263108
265038
|
resetSolverWasmStats();
|
|
263109
265039
|
setParamOverrides(paramOverrides);
|
|
263110
265040
|
await activateBackend("occt");
|
|
263111
|
-
|
|
265041
|
+
const nextRunResult = runScript(code, file, files, { quality, readBinaryFile });
|
|
265042
|
+
disposeRunResult(lastRunResult);
|
|
265043
|
+
lastRunResult = nextRunResult;
|
|
263112
265044
|
if (lastRunResult.error) {
|
|
263113
265045
|
throw new Error(`Script has errors: ${lastRunResult.error}`);
|
|
263114
265046
|
}
|