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,184 @@ 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 findSpan(n, degree, u, knots) {
|
|
2482
|
+
if (u >= knots[n]) return n - 1;
|
|
2483
|
+
if (u <= knots[degree]) return degree;
|
|
2484
|
+
let lo = degree;
|
|
2485
|
+
let hi = n;
|
|
2486
|
+
let mid = lo + hi >>> 1;
|
|
2487
|
+
while (u < knots[mid] || u >= knots[mid + 1]) {
|
|
2488
|
+
if (u < knots[mid]) hi = mid;
|
|
2489
|
+
else lo = mid;
|
|
2490
|
+
mid = lo + hi >>> 1;
|
|
2491
|
+
}
|
|
2492
|
+
return mid;
|
|
2493
|
+
}
|
|
2494
|
+
function basisFuns(span, u, degree, knots) {
|
|
2495
|
+
const N = new Array(degree + 1);
|
|
2496
|
+
const left = new Array(degree + 1);
|
|
2497
|
+
const right = new Array(degree + 1);
|
|
2498
|
+
N[0] = 1;
|
|
2499
|
+
for (let j = 1; j <= degree; j++) {
|
|
2500
|
+
left[j] = u - knots[span + 1 - j];
|
|
2501
|
+
right[j] = knots[span + j] - u;
|
|
2502
|
+
let saved = 0;
|
|
2503
|
+
for (let r = 0; r < j; r++) {
|
|
2504
|
+
const denom = right[r + 1] + left[j - r];
|
|
2505
|
+
const temp = denom === 0 ? 0 : N[r] / denom;
|
|
2506
|
+
N[r] = saved + right[r + 1] * temp;
|
|
2507
|
+
saved = left[j - r] * temp;
|
|
2508
|
+
}
|
|
2509
|
+
N[j] = saved;
|
|
2510
|
+
}
|
|
2511
|
+
return N;
|
|
2512
|
+
}
|
|
2513
|
+
function basisFunsDeriv(span, u, degree, knots, nDeriv) {
|
|
2514
|
+
const ndu = Array.from({ length: degree + 1 }, () => new Array(degree + 1).fill(0));
|
|
2515
|
+
const a = Array.from({ length: 2 }, () => new Array(degree + 1).fill(0));
|
|
2516
|
+
const left = new Array(degree + 1);
|
|
2517
|
+
const right = new Array(degree + 1);
|
|
2518
|
+
ndu[0][0] = 1;
|
|
2519
|
+
for (let j = 1; j <= degree; j++) {
|
|
2520
|
+
left[j] = u - knots[span + 1 - j];
|
|
2521
|
+
right[j] = knots[span + j] - u;
|
|
2522
|
+
let saved = 0;
|
|
2523
|
+
for (let r2 = 0; r2 < j; r2++) {
|
|
2524
|
+
ndu[j][r2] = right[r2 + 1] + left[j - r2];
|
|
2525
|
+
const temp = ndu[j][r2] === 0 ? 0 : ndu[r2][j - 1] / ndu[j][r2];
|
|
2526
|
+
ndu[r2][j] = saved + right[r2 + 1] * temp;
|
|
2527
|
+
saved = left[j - r2] * temp;
|
|
2528
|
+
}
|
|
2529
|
+
ndu[j][j] = saved;
|
|
2530
|
+
}
|
|
2531
|
+
const ders = Array.from({ length: nDeriv + 1 }, () => new Array(degree + 1).fill(0));
|
|
2532
|
+
for (let j = 0; j <= degree; j++) {
|
|
2533
|
+
ders[0][j] = ndu[j][degree];
|
|
2534
|
+
}
|
|
2535
|
+
for (let r2 = 0; r2 <= degree; r2++) {
|
|
2536
|
+
let s1 = 0;
|
|
2537
|
+
let s2 = 1;
|
|
2538
|
+
a[0][0] = 1;
|
|
2539
|
+
for (let k = 1; k <= nDeriv; k++) {
|
|
2540
|
+
let d = 0;
|
|
2541
|
+
const rk = r2 - k;
|
|
2542
|
+
const pk = degree - k;
|
|
2543
|
+
if (r2 >= k) {
|
|
2544
|
+
a[s2][0] = ndu[pk + 1][rk] === 0 ? 0 : a[s1][0] / ndu[pk + 1][rk];
|
|
2545
|
+
d = a[s2][0] * ndu[rk][pk];
|
|
2546
|
+
}
|
|
2547
|
+
const j1 = rk >= -1 ? 1 : -rk;
|
|
2548
|
+
const j2 = r2 - 1 <= pk ? k - 1 : degree - r2;
|
|
2549
|
+
for (let j = j1; j <= j2; j++) {
|
|
2550
|
+
a[s2][j] = ndu[pk + 1][rk + j] === 0 ? 0 : (a[s1][j] - a[s1][j - 1]) / ndu[pk + 1][rk + j];
|
|
2551
|
+
d += a[s2][j] * ndu[rk + j][pk];
|
|
2552
|
+
}
|
|
2553
|
+
if (r2 <= pk) {
|
|
2554
|
+
a[s2][k] = ndu[pk + 1][r2] === 0 ? 0 : -a[s1][k - 1] / ndu[pk + 1][r2];
|
|
2555
|
+
d += a[s2][k] * ndu[r2][pk];
|
|
2556
|
+
}
|
|
2557
|
+
ders[k][r2] = d;
|
|
2558
|
+
const tmp = s1;
|
|
2559
|
+
s1 = s2;
|
|
2560
|
+
s2 = tmp;
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2563
|
+
let r = degree;
|
|
2564
|
+
for (let k = 1; k <= nDeriv; k++) {
|
|
2565
|
+
for (let j = 0; j <= degree; j++) {
|
|
2566
|
+
ders[k][j] *= r;
|
|
2567
|
+
}
|
|
2568
|
+
r *= degree - k;
|
|
2569
|
+
}
|
|
2570
|
+
return ders;
|
|
2571
|
+
}
|
|
2572
|
+
function deBoor3D(controlPoints, weights, knots, degree, u) {
|
|
2573
|
+
const n = controlPoints.length;
|
|
2574
|
+
const span = findSpan(n, degree, u, knots);
|
|
2575
|
+
const N = basisFuns(span, u, degree, knots);
|
|
2576
|
+
let wx = 0, wy = 0, wz = 0, wSum = 0;
|
|
2577
|
+
for (let j = 0; j <= degree; j++) {
|
|
2578
|
+
const idx = span - degree + j;
|
|
2579
|
+
const w = weights[idx] * N[j];
|
|
2580
|
+
wx += w * controlPoints[idx][0];
|
|
2581
|
+
wy += w * controlPoints[idx][1];
|
|
2582
|
+
wz += w * controlPoints[idx][2];
|
|
2583
|
+
wSum += w;
|
|
2584
|
+
}
|
|
2585
|
+
if (wSum === 0) return [0, 0, 0];
|
|
2586
|
+
return [wx / wSum, wy / wSum, wz / wSum];
|
|
2587
|
+
}
|
|
2588
|
+
function deBoor3DDeriv(controlPoints, weights, knots, degree, u) {
|
|
2589
|
+
const n = controlPoints.length;
|
|
2590
|
+
const span = findSpan(n, degree, u, knots);
|
|
2591
|
+
const ders = basisFunsDeriv(span, u, degree, knots, 1);
|
|
2592
|
+
const N = ders[0];
|
|
2593
|
+
const dN = ders[1];
|
|
2594
|
+
let ax = 0, ay = 0, az = 0, wVal = 0;
|
|
2595
|
+
let dax = 0, day = 0, daz = 0, dwVal = 0;
|
|
2596
|
+
for (let j = 0; j <= degree; j++) {
|
|
2597
|
+
const idx = span - degree + j;
|
|
2598
|
+
const wi = weights[idx];
|
|
2599
|
+
const px = controlPoints[idx][0], py = controlPoints[idx][1], pz = controlPoints[idx][2];
|
|
2600
|
+
ax += N[j] * wi * px;
|
|
2601
|
+
ay += N[j] * wi * py;
|
|
2602
|
+
az += N[j] * wi * pz;
|
|
2603
|
+
wVal += N[j] * wi;
|
|
2604
|
+
dax += dN[j] * wi * px;
|
|
2605
|
+
day += dN[j] * wi * py;
|
|
2606
|
+
daz += dN[j] * wi * pz;
|
|
2607
|
+
dwVal += dN[j] * wi;
|
|
2608
|
+
}
|
|
2609
|
+
if (wVal === 0) return [0, 0, 0];
|
|
2610
|
+
const invW = 1 / wVal;
|
|
2611
|
+
return [
|
|
2612
|
+
(dax - dwVal * ax * invW) * invW,
|
|
2613
|
+
(day - dwVal * ay * invW) * invW,
|
|
2614
|
+
(daz - dwVal * az * invW) * invW
|
|
2615
|
+
];
|
|
2616
|
+
}
|
|
2617
|
+
function deBoor2D(controlPoints, weights, knots, degree, u) {
|
|
2618
|
+
const n = controlPoints.length;
|
|
2619
|
+
const span = findSpan(n, degree, u, knots);
|
|
2620
|
+
const N = basisFuns(span, u, degree, knots);
|
|
2621
|
+
let wx = 0, wy = 0, wSum = 0;
|
|
2622
|
+
for (let j = 0; j <= degree; j++) {
|
|
2623
|
+
const idx = span - degree + j;
|
|
2624
|
+
const w = weights[idx] * N[j];
|
|
2625
|
+
wx += w * controlPoints[idx][0];
|
|
2626
|
+
wy += w * controlPoints[idx][1];
|
|
2627
|
+
wSum += w;
|
|
2628
|
+
}
|
|
2629
|
+
if (wSum === 0) return [0, 0];
|
|
2630
|
+
return [wx / wSum, wy / wSum];
|
|
2631
|
+
}
|
|
2632
|
+
function sampleNurbs3D(controlPoints, weights, knots, degree, count) {
|
|
2633
|
+
const n = controlPoints.length;
|
|
2634
|
+
const uMin = knots[degree];
|
|
2635
|
+
const uMax = knots[n];
|
|
2636
|
+
const result = new Array(count);
|
|
2637
|
+
for (let i = 0; i < count; i++) {
|
|
2638
|
+
const t = i / (count - 1);
|
|
2639
|
+
const u = uMin + t * (uMax - uMin);
|
|
2640
|
+
result[i] = deBoor3D(controlPoints, weights, knots, degree, u);
|
|
2641
|
+
}
|
|
2642
|
+
return result;
|
|
2643
|
+
}
|
|
2644
|
+
function remapToKnotDomain(t, n, degree, knots) {
|
|
2645
|
+
const uMin = knots[degree];
|
|
2646
|
+
const uMax = knots[n];
|
|
2647
|
+
return uMin + Math.max(0, Math.min(1, t)) * (uMax - uMin);
|
|
2648
|
+
}
|
|
2413
2649
|
function catmullRom3D$2(p0, p1, p2, p3, t, tension) {
|
|
2414
2650
|
const tt = t * t;
|
|
2415
2651
|
const ttt = tt * t;
|
|
@@ -2544,6 +2780,10 @@ function evalPathAt(path2, t) {
|
|
|
2544
2780
|
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
2781
|
];
|
|
2546
2782
|
}
|
|
2783
|
+
case "nurbs": {
|
|
2784
|
+
const u = remapToKnotDomain(Math.max(0, Math.min(1, t)), path2.controlPoints.length, path2.degree, path2.knots);
|
|
2785
|
+
return deBoor3D(path2.controlPoints, path2.weights, path2.knots, path2.degree, u);
|
|
2786
|
+
}
|
|
2547
2787
|
}
|
|
2548
2788
|
}
|
|
2549
2789
|
function estimateCurvatureAt(path2, t) {
|
|
@@ -2578,6 +2818,18 @@ function sweepPathToPolyline(path2, samples = 48) {
|
|
|
2578
2818
|
path2.c1,
|
|
2579
2819
|
samples
|
|
2580
2820
|
);
|
|
2821
|
+
case "nurbs": {
|
|
2822
|
+
const n = path2.controlPoints.length;
|
|
2823
|
+
const uMin = path2.knots[path2.degree];
|
|
2824
|
+
const uMax = path2.knots[n];
|
|
2825
|
+
const result = new Array(samples);
|
|
2826
|
+
for (let i = 0; i < samples; i++) {
|
|
2827
|
+
const t = i / (samples - 1);
|
|
2828
|
+
const u = uMin + t * (uMax - uMin);
|
|
2829
|
+
result[i] = deBoor3D(path2.controlPoints, path2.weights, path2.knots, path2.degree, u);
|
|
2830
|
+
}
|
|
2831
|
+
return result;
|
|
2832
|
+
}
|
|
2581
2833
|
}
|
|
2582
2834
|
}
|
|
2583
2835
|
function sweepPathToPolylineAdaptive(path2, baseSamples = 48) {
|
|
@@ -2811,6 +3063,8 @@ function searchOwnerMatch(plan, owner) {
|
|
|
2811
3063
|
case "importedMesh":
|
|
2812
3064
|
case "sdf":
|
|
2813
3065
|
case "fromSlices":
|
|
3066
|
+
case "nurbsSurface":
|
|
3067
|
+
case "importedStep":
|
|
2814
3068
|
return {
|
|
2815
3069
|
issue: {
|
|
2816
3070
|
code: "edge-owner-not-found",
|
|
@@ -2892,7 +3146,7 @@ function propagateCandidateAcrossRewrite(plan, candidate) {
|
|
|
2892
3146
|
return edgeSuccess(candidate.selection, preservedEntry.query);
|
|
2893
3147
|
}
|
|
2894
3148
|
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") {
|
|
3149
|
+
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
3150
|
return edgeIssue(
|
|
2897
3151
|
"edge-query-propagation-mismatch",
|
|
2898
3152
|
"The selected propagated edge query does not point at a topology-rewrite result on this target shape."
|
|
@@ -3118,6 +3372,8 @@ function resolveSelectionFromOwnerBase(plan, edgeName) {
|
|
|
3118
3372
|
case "importedMesh":
|
|
3119
3373
|
case "sdf":
|
|
3120
3374
|
case "fromSlices":
|
|
3375
|
+
case "nurbsSurface":
|
|
3376
|
+
case "importedStep":
|
|
3121
3377
|
return edgeIssue(
|
|
3122
3378
|
"unsupported-edge-base",
|
|
3123
3379
|
"Edge finishing v1 currently supports tracked vertical edges from compile-covered box() bodies and rectangle extrusions before topology-changing edits."
|
|
@@ -3152,7 +3408,7 @@ function resolveSupportedEdgeFeatureSelection(plan, ref) {
|
|
|
3152
3408
|
return edgeIssue("edge-query-unsupported-after-rewrite", descendant.reason);
|
|
3153
3409
|
}
|
|
3154
3410
|
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") {
|
|
3411
|
+
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
3412
|
return {
|
|
3157
3413
|
kind: "unsupported",
|
|
3158
3414
|
query: cloneEdgeQueryRef(ref),
|
|
@@ -3185,7 +3441,7 @@ function resolveEdgeChainAtOwnerBase(ownerBase, ref) {
|
|
|
3185
3441
|
};
|
|
3186
3442
|
}
|
|
3187
3443
|
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") {
|
|
3444
|
+
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
3445
|
return {
|
|
3190
3446
|
kind: "unsupported",
|
|
3191
3447
|
query: cloneEdgeQueryRef(ref),
|
|
@@ -4678,6 +4934,8 @@ function lowerBaseShellPlanToConcretePlan(plan, thickness, openFaces) {
|
|
|
4678
4934
|
case "importedMesh":
|
|
4679
4935
|
case "sdf":
|
|
4680
4936
|
case "fromSlices":
|
|
4937
|
+
case "nurbsSurface":
|
|
4938
|
+
case "importedStep":
|
|
4681
4939
|
return {
|
|
4682
4940
|
ok: false,
|
|
4683
4941
|
reason: `Shape.shell() supports box(), cylinder(), straight extrude(), loft(), sweep(), and variableSweep() bases. "${plan.kind}" bases are not supported.`
|
|
@@ -5016,7 +5274,7 @@ const defaultPerm = doublePerm(p);
|
|
|
5016
5274
|
const defaultPermMod12 = permMod12(defaultPerm);
|
|
5017
5275
|
const F3 = 1 / 3;
|
|
5018
5276
|
const G3 = 1 / 6;
|
|
5019
|
-
function dot3$
|
|
5277
|
+
function dot3$6(gi, x, y, z) {
|
|
5020
5278
|
const o = gi * 3;
|
|
5021
5279
|
return grad3[o] * x + grad3[o + 1] * y + grad3[o + 2] * z;
|
|
5022
5280
|
}
|
|
@@ -5126,28 +5384,28 @@ function simplex3Core(x, y, z, perm, pm12) {
|
|
|
5126
5384
|
n0 = 0;
|
|
5127
5385
|
} else {
|
|
5128
5386
|
t0 *= t0;
|
|
5129
|
-
n0 = t0 * t0 * dot3$
|
|
5387
|
+
n0 = t0 * t0 * dot3$6(gi0, x0, y0, z0);
|
|
5130
5388
|
}
|
|
5131
5389
|
let t1 = 0.6 - x1 * x1 - y1 * y1 - z1 * z1;
|
|
5132
5390
|
if (t1 < 0) {
|
|
5133
5391
|
n1 = 0;
|
|
5134
5392
|
} else {
|
|
5135
5393
|
t1 *= t1;
|
|
5136
|
-
n1 = t1 * t1 * dot3$
|
|
5394
|
+
n1 = t1 * t1 * dot3$6(gi1, x1, y1, z1);
|
|
5137
5395
|
}
|
|
5138
5396
|
let t2 = 0.6 - x2 * x2 - y2 * y2 - z2 * z2;
|
|
5139
5397
|
if (t2 < 0) {
|
|
5140
5398
|
n2 = 0;
|
|
5141
5399
|
} else {
|
|
5142
5400
|
t2 *= t2;
|
|
5143
|
-
n2 = t2 * t2 * dot3$
|
|
5401
|
+
n2 = t2 * t2 * dot3$6(gi2, x2, y2, z2);
|
|
5144
5402
|
}
|
|
5145
5403
|
let t3 = 0.6 - x3 * x3 - y3 * y3 - z3 * z3;
|
|
5146
5404
|
if (t3 < 0) {
|
|
5147
5405
|
n3 = 0;
|
|
5148
5406
|
} else {
|
|
5149
5407
|
t3 *= t3;
|
|
5150
|
-
n3 = t3 * t3 * dot3$
|
|
5408
|
+
n3 = t3 * t3 * dot3$6(gi3, x3, y3, z3);
|
|
5151
5409
|
}
|
|
5152
5410
|
return 32 * (n0 + n1 + n2 + n3);
|
|
5153
5411
|
}
|
|
@@ -5911,6 +6169,158 @@ function padBounds(b, pad) {
|
|
|
5911
6169
|
max: [b.max[0] + pad, b.max[1] + pad, b.max[2] + pad]
|
|
5912
6170
|
};
|
|
5913
6171
|
}
|
|
6172
|
+
function requireFinite$6(v, label) {
|
|
6173
|
+
if (!Number.isFinite(v)) throw new Error(`nurbsSurface: ${label} must be finite, got ${v}`);
|
|
6174
|
+
}
|
|
6175
|
+
class NurbsSurface {
|
|
6176
|
+
// columns in control grid
|
|
6177
|
+
constructor(controlGrid, options = {}) {
|
|
6178
|
+
__publicField(this, "controlGrid");
|
|
6179
|
+
__publicField(this, "weightsGrid");
|
|
6180
|
+
__publicField(this, "knotsU");
|
|
6181
|
+
__publicField(this, "knotsV");
|
|
6182
|
+
__publicField(this, "degreeU");
|
|
6183
|
+
__publicField(this, "degreeV");
|
|
6184
|
+
__publicField(this, "nU");
|
|
6185
|
+
// rows in control grid
|
|
6186
|
+
__publicField(this, "nV");
|
|
6187
|
+
const nU = controlGrid.length;
|
|
6188
|
+
if (nU < 2) throw new Error("nurbsSurface: controlGrid must have at least 2 rows");
|
|
6189
|
+
const nV = controlGrid[0].length;
|
|
6190
|
+
if (nV < 2) throw new Error("nurbsSurface: controlGrid must have at least 2 columns");
|
|
6191
|
+
const degreeU = options.degreeU ?? Math.min(nU - 1, 3);
|
|
6192
|
+
const degreeV = options.degreeV ?? Math.min(nV - 1, 3);
|
|
6193
|
+
if (nU < degreeU + 1) throw new Error(`nurbsSurface: need at least ${degreeU + 1} rows for degreeU=${degreeU}, got ${nU}`);
|
|
6194
|
+
if (nV < degreeV + 1) throw new Error(`nurbsSurface: need at least ${degreeV + 1} columns for degreeV=${degreeV}, got ${nV}`);
|
|
6195
|
+
for (let i = 0; i < nU; i++) {
|
|
6196
|
+
if (controlGrid[i].length !== nV) throw new Error(`nurbsSurface: row ${i} has ${controlGrid[i].length} points, expected ${nV}`);
|
|
6197
|
+
for (let j = 0; j < nV; j++) {
|
|
6198
|
+
requireFinite$6(controlGrid[i][j][0], `controlGrid[${i}][${j}][0]`);
|
|
6199
|
+
requireFinite$6(controlGrid[i][j][1], `controlGrid[${i}][${j}][1]`);
|
|
6200
|
+
requireFinite$6(controlGrid[i][j][2], `controlGrid[${i}][${j}][2]`);
|
|
6201
|
+
}
|
|
6202
|
+
}
|
|
6203
|
+
const weightsGrid = options.weights ?? controlGrid.map((row) => row.map(() => 1));
|
|
6204
|
+
for (let i = 0; i < nU; i++) {
|
|
6205
|
+
if (weightsGrid[i].length !== nV) throw new Error(`nurbsSurface: weights row ${i} length mismatch`);
|
|
6206
|
+
for (let j = 0; j < nV; j++) {
|
|
6207
|
+
requireFinite$6(weightsGrid[i][j], `weights[${i}][${j}]`);
|
|
6208
|
+
if (weightsGrid[i][j] <= 0) throw new Error(`nurbsSurface: weights[${i}][${j}] must be > 0`);
|
|
6209
|
+
}
|
|
6210
|
+
}
|
|
6211
|
+
const knotsU = options.knotsU ?? generateClampedKnots(nU, degreeU);
|
|
6212
|
+
const knotsV = options.knotsV ?? generateClampedKnots(nV, degreeV);
|
|
6213
|
+
if (knotsU.length !== nU + degreeU + 1) throw new Error(`nurbsSurface: knotsU.length should be ${nU + degreeU + 1}, got ${knotsU.length}`);
|
|
6214
|
+
if (knotsV.length !== nV + degreeV + 1) throw new Error(`nurbsSurface: knotsV.length should be ${nV + degreeV + 1}, got ${knotsV.length}`);
|
|
6215
|
+
this.controlGrid = controlGrid.map((row) => row.map(([x, y, z]) => [x, y, z]));
|
|
6216
|
+
this.weightsGrid = weightsGrid.map((row) => [...row]);
|
|
6217
|
+
this.knotsU = [...knotsU];
|
|
6218
|
+
this.knotsV = [...knotsV];
|
|
6219
|
+
this.degreeU = degreeU;
|
|
6220
|
+
this.degreeV = degreeV;
|
|
6221
|
+
this.nU = nU;
|
|
6222
|
+
this.nV = nV;
|
|
6223
|
+
}
|
|
6224
|
+
/**
|
|
6225
|
+
* Evaluate the surface at parameters (u, v) ∈ [0, 1]².
|
|
6226
|
+
* Uses tensor product evaluation: evaluate basis functions in U and V independently.
|
|
6227
|
+
*/
|
|
6228
|
+
pointAt(u, v) {
|
|
6229
|
+
const uu = this.remapU(Math.max(0, Math.min(1, u)));
|
|
6230
|
+
const vv = this.remapV(Math.max(0, Math.min(1, v)));
|
|
6231
|
+
const spanU = findSpan(this.nU, this.degreeU, uu, this.knotsU);
|
|
6232
|
+
const spanV = findSpan(this.nV, this.degreeV, vv, this.knotsV);
|
|
6233
|
+
const Nu = basisFuns(spanU, uu, this.degreeU, this.knotsU);
|
|
6234
|
+
const Nv = basisFuns(spanV, vv, this.degreeV, this.knotsV);
|
|
6235
|
+
let wx = 0, wy = 0, wz = 0, wSum = 0;
|
|
6236
|
+
for (let i = 0; i <= this.degreeU; i++) {
|
|
6237
|
+
const rowIdx = spanU - this.degreeU + i;
|
|
6238
|
+
for (let j = 0; j <= this.degreeV; j++) {
|
|
6239
|
+
const colIdx = spanV - this.degreeV + j;
|
|
6240
|
+
const w = Nu[i] * Nv[j] * this.weightsGrid[rowIdx][colIdx];
|
|
6241
|
+
const pt = this.controlGrid[rowIdx][colIdx];
|
|
6242
|
+
wx += w * pt[0];
|
|
6243
|
+
wy += w * pt[1];
|
|
6244
|
+
wz += w * pt[2];
|
|
6245
|
+
wSum += w;
|
|
6246
|
+
}
|
|
6247
|
+
}
|
|
6248
|
+
if (wSum === 0) return [0, 0, 0];
|
|
6249
|
+
return [wx / wSum, wy / wSum, wz / wSum];
|
|
6250
|
+
}
|
|
6251
|
+
/**
|
|
6252
|
+
* Evaluate the surface normal at (u, v) via cross product of partial derivatives.
|
|
6253
|
+
*/
|
|
6254
|
+
normalAt(u, v) {
|
|
6255
|
+
const eps = 1e-5;
|
|
6256
|
+
const u0 = Math.max(0, u - eps), u1 = Math.min(1, u + eps);
|
|
6257
|
+
const v0 = Math.max(0, v - eps), v1 = Math.min(1, v + eps);
|
|
6258
|
+
const pu = this.pointAt(u1, v), pmu = this.pointAt(u0, v);
|
|
6259
|
+
const pv = this.pointAt(u, v1), pmv = this.pointAt(u, v0);
|
|
6260
|
+
const du = [pu[0] - pmu[0], pu[1] - pmu[1], pu[2] - pmu[2]];
|
|
6261
|
+
const dv = [pv[0] - pmv[0], pv[1] - pmv[1], pv[2] - pmv[2]];
|
|
6262
|
+
const nx = du[1] * dv[2] - du[2] * dv[1];
|
|
6263
|
+
const ny = du[2] * dv[0] - du[0] * dv[2];
|
|
6264
|
+
const nz = du[0] * dv[1] - du[1] * dv[0];
|
|
6265
|
+
const len = Math.sqrt(nx * nx + ny * ny + nz * nz);
|
|
6266
|
+
if (len < 1e-12) return [0, 0, 1];
|
|
6267
|
+
return [nx / len, ny / len, nz / len];
|
|
6268
|
+
}
|
|
6269
|
+
/**
|
|
6270
|
+
* Tessellate the surface into a triangle mesh.
|
|
6271
|
+
* Returns positions, normals, and triangle indices.
|
|
6272
|
+
*/
|
|
6273
|
+
tessellate(resU = 32, resV = 32) {
|
|
6274
|
+
const positions = [];
|
|
6275
|
+
const normals = [];
|
|
6276
|
+
for (let i = 0; i <= resU; i++) {
|
|
6277
|
+
const u = i / resU;
|
|
6278
|
+
for (let j = 0; j <= resV; j++) {
|
|
6279
|
+
const v = j / resV;
|
|
6280
|
+
positions.push(this.pointAt(u, v));
|
|
6281
|
+
normals.push(this.normalAt(u, v));
|
|
6282
|
+
}
|
|
6283
|
+
}
|
|
6284
|
+
const indices = [];
|
|
6285
|
+
for (let i = 0; i < resU; i++) {
|
|
6286
|
+
for (let j = 0; j < resV; j++) {
|
|
6287
|
+
const a = i * (resV + 1) + j;
|
|
6288
|
+
const b = a + 1;
|
|
6289
|
+
const c = (i + 1) * (resV + 1) + j;
|
|
6290
|
+
const d = c + 1;
|
|
6291
|
+
indices.push(a, c, b);
|
|
6292
|
+
indices.push(b, c, d);
|
|
6293
|
+
}
|
|
6294
|
+
}
|
|
6295
|
+
return { positions, normals, indices };
|
|
6296
|
+
}
|
|
6297
|
+
remapU(t) {
|
|
6298
|
+
return this.knotsU[this.degreeU] + t * (this.knotsU[this.nU] - this.knotsU[this.degreeU]);
|
|
6299
|
+
}
|
|
6300
|
+
remapV(t) {
|
|
6301
|
+
return this.knotsV[this.degreeV] + t * (this.knotsV[this.nV] - this.knotsV[this.degreeV]);
|
|
6302
|
+
}
|
|
6303
|
+
}
|
|
6304
|
+
function nurbsSurface(controlGrid, options) {
|
|
6305
|
+
const surface = new NurbsSurface(controlGrid, options);
|
|
6306
|
+
const thickness = (options == null ? void 0 : options.thickness) ?? 1;
|
|
6307
|
+
const resolution = (options == null ? void 0 : options.resolution) ?? 32;
|
|
6308
|
+
return buildShapeFromCompilePlan(
|
|
6309
|
+
createOwnedShapeCompilePlan({
|
|
6310
|
+
kind: "nurbsSurface",
|
|
6311
|
+
controlGrid: surface.controlGrid.map(
|
|
6312
|
+
(row) => row.map(([x, y, z]) => [x, y, z])
|
|
6313
|
+
),
|
|
6314
|
+
weightsGrid: surface.weightsGrid.map((row) => [...row]),
|
|
6315
|
+
knotsU: [...surface.knotsU],
|
|
6316
|
+
knotsV: [...surface.knotsV],
|
|
6317
|
+
degreeU: surface.degreeU,
|
|
6318
|
+
degreeV: surface.degreeV,
|
|
6319
|
+
thickness,
|
|
6320
|
+
resolution
|
|
6321
|
+
}, "nurbsSurface")
|
|
6322
|
+
);
|
|
6323
|
+
}
|
|
5914
6324
|
let _simplifier = null;
|
|
5915
6325
|
async function initMeshoptimizer() {
|
|
5916
6326
|
if (_simplifier) return;
|
|
@@ -6314,6 +6724,8 @@ function sampleSweepPath(path2, edgeLengthHint) {
|
|
|
6314
6724
|
return sweepPathToPolyline(path2, resolveCurveSampleCount(edgeLengthHint, path2.chordLength));
|
|
6315
6725
|
case "quintic-hermite":
|
|
6316
6726
|
return sweepPathToPolyline(path2, resolveCurveSampleCount(edgeLengthHint, path2.chordLength));
|
|
6727
|
+
case "nurbs":
|
|
6728
|
+
return sweepPathToPolyline(path2, edgeLengthHint != null ? Math.max(16, Math.round(path2.controlPoints.length * 12 / Math.max(0.1, edgeLengthHint))) : 48);
|
|
6317
6729
|
}
|
|
6318
6730
|
}
|
|
6319
6731
|
function clamp$7(v, lo, hi) {
|
|
@@ -7173,7 +7585,7 @@ let _wasm = null;
|
|
|
7173
7585
|
async function initManifoldWasm() {
|
|
7174
7586
|
if (_wasm) return _wasm;
|
|
7175
7587
|
performance.mark("manifold:start");
|
|
7176
|
-
const Module = (await import("./manifold-
|
|
7588
|
+
const Module = (await import("./manifold-DBckbFgx.js")).default;
|
|
7177
7589
|
performance.mark("manifold:imported");
|
|
7178
7590
|
const wasm = await Module();
|
|
7179
7591
|
wasm.setup();
|
|
@@ -7195,74 +7607,105 @@ function isManifoldCapableBackend(b) {
|
|
|
7195
7607
|
}
|
|
7196
7608
|
_a2 = SHAPE_BACKEND_MARKER;
|
|
7197
7609
|
const _ManifoldShapeBackend = class _ManifoldShapeBackend {
|
|
7198
|
-
constructor(
|
|
7610
|
+
constructor(manifoldOrResource) {
|
|
7199
7611
|
__publicField(this, _a2, true);
|
|
7200
|
-
this
|
|
7612
|
+
__publicField(this, "resource");
|
|
7613
|
+
__publicField(this, "released", false);
|
|
7614
|
+
this.resource = "refCount" in manifoldOrResource ? manifoldOrResource : {
|
|
7615
|
+
manifold: manifoldOrResource,
|
|
7616
|
+
refCount: 1,
|
|
7617
|
+
disposed: false
|
|
7618
|
+
};
|
|
7619
|
+
}
|
|
7620
|
+
getLiveManifold(apiName = "ManifoldShapeBackend") {
|
|
7621
|
+
if (this.released || this.resource.disposed) {
|
|
7622
|
+
throw new Error(`${apiName}: manifold backend was already disposed`);
|
|
7623
|
+
}
|
|
7624
|
+
return this.resource.manifold;
|
|
7201
7625
|
}
|
|
7202
7626
|
clone() {
|
|
7203
|
-
|
|
7627
|
+
this.resource.refCount += 1;
|
|
7628
|
+
return new _ManifoldShapeBackend(this.resource);
|
|
7204
7629
|
}
|
|
7205
7630
|
translate(x, y, z) {
|
|
7206
|
-
return new _ManifoldShapeBackend(this.
|
|
7631
|
+
return new _ManifoldShapeBackend(this.getLiveManifold("translate()").translate(x, y, z));
|
|
7207
7632
|
}
|
|
7208
7633
|
rotate(x, y, z) {
|
|
7209
|
-
return new _ManifoldShapeBackend(this.
|
|
7634
|
+
return new _ManifoldShapeBackend(this.getLiveManifold("rotate()").rotate(x, y, z));
|
|
7210
7635
|
}
|
|
7211
7636
|
transform(m) {
|
|
7212
|
-
return new _ManifoldShapeBackend(this.
|
|
7637
|
+
return new _ManifoldShapeBackend(this.getLiveManifold("transform()").transform(m));
|
|
7213
7638
|
}
|
|
7214
7639
|
scale(v) {
|
|
7215
|
-
return new _ManifoldShapeBackend(this.
|
|
7640
|
+
return new _ManifoldShapeBackend(this.getLiveManifold("scale()").scale(v));
|
|
7216
7641
|
}
|
|
7217
7642
|
mirror(normal) {
|
|
7218
|
-
return new _ManifoldShapeBackend(this.
|
|
7643
|
+
return new _ManifoldShapeBackend(this.getLiveManifold("mirror()").mirror(normal));
|
|
7219
7644
|
}
|
|
7220
7645
|
split(other) {
|
|
7221
|
-
const [inside, outside] = this.
|
|
7646
|
+
const [inside, outside] = this.getLiveManifold("split()").split(requireManifoldShapeBackend(other, "ShapeBackend.split()"));
|
|
7222
7647
|
return [new _ManifoldShapeBackend(inside), new _ManifoldShapeBackend(outside)];
|
|
7223
7648
|
}
|
|
7224
7649
|
splitByPlane(normal, originOffset) {
|
|
7225
|
-
const [inside, outside] = this.
|
|
7650
|
+
const [inside, outside] = this.getLiveManifold("splitByPlane()").splitByPlane(normal, originOffset);
|
|
7226
7651
|
return [new _ManifoldShapeBackend(inside), new _ManifoldShapeBackend(outside)];
|
|
7227
7652
|
}
|
|
7228
7653
|
trimByPlane(normal, originOffset) {
|
|
7229
|
-
return new _ManifoldShapeBackend(this.
|
|
7654
|
+
return new _ManifoldShapeBackend(this.getLiveManifold("trimByPlane()").trimByPlane(normal, originOffset));
|
|
7230
7655
|
}
|
|
7231
7656
|
simplify(tolerance) {
|
|
7232
|
-
return new _ManifoldShapeBackend(this.
|
|
7657
|
+
return new _ManifoldShapeBackend(this.getLiveManifold("simplify()").simplify(tolerance));
|
|
7233
7658
|
}
|
|
7234
7659
|
boundingBox() {
|
|
7235
|
-
return this.
|
|
7660
|
+
return this.getLiveManifold("boundingBox()").boundingBox();
|
|
7236
7661
|
}
|
|
7237
7662
|
volume() {
|
|
7238
|
-
return this.
|
|
7663
|
+
return this.getLiveManifold("volume()").volume();
|
|
7239
7664
|
}
|
|
7240
7665
|
surfaceArea() {
|
|
7241
|
-
return this.
|
|
7666
|
+
return this.getLiveManifold("surfaceArea()").surfaceArea();
|
|
7242
7667
|
}
|
|
7243
7668
|
minGap(other, searchLength) {
|
|
7244
|
-
return this.
|
|
7669
|
+
return this.getLiveManifold("minGap()").minGap(requireManifoldShapeBackend(other, "ShapeBackend.minGap()"), searchLength);
|
|
7245
7670
|
}
|
|
7246
7671
|
isEmpty() {
|
|
7247
|
-
return this.
|
|
7672
|
+
return this.getLiveManifold("isEmpty()").isEmpty();
|
|
7248
7673
|
}
|
|
7249
7674
|
numBodies() {
|
|
7250
|
-
|
|
7675
|
+
const parts = this.getLiveManifold("numBodies()").decompose();
|
|
7676
|
+
try {
|
|
7677
|
+
return parts.length;
|
|
7678
|
+
} finally {
|
|
7679
|
+
parts.forEach((part) => {
|
|
7680
|
+
var _a3;
|
|
7681
|
+
return (_a3 = part.delete) == null ? void 0 : _a3.call(part);
|
|
7682
|
+
});
|
|
7683
|
+
}
|
|
7251
7684
|
}
|
|
7252
7685
|
numTri() {
|
|
7253
|
-
return this.
|
|
7686
|
+
return this.getLiveManifold("numTri()").numTri();
|
|
7254
7687
|
}
|
|
7255
7688
|
getMesh() {
|
|
7256
|
-
return this.
|
|
7689
|
+
return this.getLiveManifold("getMesh()").getMesh();
|
|
7257
7690
|
}
|
|
7258
7691
|
slice(offset2) {
|
|
7259
|
-
return this.
|
|
7692
|
+
return this.getLiveManifold("slice()").slice(offset2);
|
|
7260
7693
|
}
|
|
7261
7694
|
project() {
|
|
7262
|
-
return this.
|
|
7695
|
+
return this.getLiveManifold("project()").project();
|
|
7263
7696
|
}
|
|
7264
7697
|
requireManifold() {
|
|
7265
|
-
return this.
|
|
7698
|
+
return this.getLiveManifold("requireManifold()");
|
|
7699
|
+
}
|
|
7700
|
+
dispose() {
|
|
7701
|
+
var _a3, _b3;
|
|
7702
|
+
if (this.released) return;
|
|
7703
|
+
this.released = true;
|
|
7704
|
+
this.resource.refCount = Math.max(0, this.resource.refCount - 1);
|
|
7705
|
+
if (this.resource.refCount === 0 && !this.resource.disposed) {
|
|
7706
|
+
this.resource.disposed = true;
|
|
7707
|
+
(_b3 = (_a3 = this.resource.manifold).delete) == null ? void 0 : _b3.call(_a3);
|
|
7708
|
+
}
|
|
7266
7709
|
}
|
|
7267
7710
|
};
|
|
7268
7711
|
let ManifoldShapeBackend = _ManifoldShapeBackend;
|
|
@@ -7498,6 +7941,12 @@ function rotateVector(v, axis, c, s) {
|
|
|
7498
7941
|
v[2] * c + kCrossV[2] * s + axis[2] * kDotV * (1 - c)
|
|
7499
7942
|
];
|
|
7500
7943
|
}
|
|
7944
|
+
function disposeWasmObject(value) {
|
|
7945
|
+
if (value != null && typeof value.delete === "function") value.delete();
|
|
7946
|
+
}
|
|
7947
|
+
function disposeWasmObjects(values) {
|
|
7948
|
+
for (const value of values) disposeWasmObject(value);
|
|
7949
|
+
}
|
|
7501
7950
|
function applyProfileCompileTransform(crossSection, step) {
|
|
7502
7951
|
switch (step.kind) {
|
|
7503
7952
|
case "translate":
|
|
@@ -7513,7 +7962,9 @@ function applyProfileCompileTransform(crossSection, step) {
|
|
|
7513
7962
|
function applyProfileCompileTransforms(crossSection, transforms) {
|
|
7514
7963
|
let out = crossSection;
|
|
7515
7964
|
for (const step of transforms) {
|
|
7965
|
+
const prev = out;
|
|
7516
7966
|
out = applyProfileCompileTransform(out, step);
|
|
7967
|
+
if (out !== prev) disposeWasmObject(prev);
|
|
7517
7968
|
}
|
|
7518
7969
|
return out;
|
|
7519
7970
|
}
|
|
@@ -7525,17 +7976,21 @@ function lowerProfileBooleanCompilePlan(plan, wasm) {
|
|
|
7525
7976
|
if (profiles.length === 1) {
|
|
7526
7977
|
return applyProfileCompileTransforms(profiles[0], plan.transforms);
|
|
7527
7978
|
}
|
|
7528
|
-
|
|
7529
|
-
|
|
7530
|
-
|
|
7531
|
-
|
|
7532
|
-
|
|
7533
|
-
|
|
7534
|
-
|
|
7535
|
-
|
|
7536
|
-
|
|
7537
|
-
|
|
7538
|
-
|
|
7979
|
+
try {
|
|
7980
|
+
const combined = (() => {
|
|
7981
|
+
switch (plan.op) {
|
|
7982
|
+
case "union":
|
|
7983
|
+
return wasm.CrossSection.union(profiles);
|
|
7984
|
+
case "difference":
|
|
7985
|
+
return wasm.CrossSection.difference(profiles);
|
|
7986
|
+
case "intersection":
|
|
7987
|
+
return wasm.CrossSection.intersection(profiles);
|
|
7988
|
+
}
|
|
7989
|
+
})();
|
|
7990
|
+
return applyProfileCompileTransforms(combined, plan.transforms);
|
|
7991
|
+
} finally {
|
|
7992
|
+
disposeWasmObjects(profiles);
|
|
7993
|
+
}
|
|
7539
7994
|
}
|
|
7540
7995
|
function lowerProfileCompilePlanToCrossSection(plan, wasm) {
|
|
7541
7996
|
switch (plan.kind) {
|
|
@@ -7543,7 +7998,9 @@ function lowerProfileCompilePlanToCrossSection(plan, wasm) {
|
|
|
7543
7998
|
return applyProfileCompileTransforms(wasm.CrossSection.square([plan.width, plan.height], true), plan.transforms);
|
|
7544
7999
|
case "roundedRect": {
|
|
7545
8000
|
const radius = Math.min(plan.radius, plan.width / 2, plan.height / 2);
|
|
7546
|
-
const
|
|
8001
|
+
const base = wasm.CrossSection.square([plan.width - 2 * radius, plan.height - 2 * radius], true);
|
|
8002
|
+
const crossSection = base.offset(radius, "Round");
|
|
8003
|
+
if (crossSection !== base) disposeWasmObject(base);
|
|
7547
8004
|
return applyProfileCompileTransforms(crossSection, plan.transforms);
|
|
7548
8005
|
}
|
|
7549
8006
|
case "circle":
|
|
@@ -7552,15 +8009,29 @@ function lowerProfileCompilePlanToCrossSection(plan, wasm) {
|
|
|
7552
8009
|
return applyProfileCompileTransforms(new wasm.CrossSection([plan.points]), plan.transforms);
|
|
7553
8010
|
case "boolean":
|
|
7554
8011
|
return lowerProfileBooleanCompilePlan(plan, wasm);
|
|
7555
|
-
case "offset":
|
|
7556
|
-
|
|
7557
|
-
|
|
7558
|
-
plan.transforms
|
|
7559
|
-
|
|
8012
|
+
case "offset": {
|
|
8013
|
+
const base = lowerProfileCompilePlanToCrossSection(plan.base, wasm);
|
|
8014
|
+
try {
|
|
8015
|
+
return applyProfileCompileTransforms(base.offset(plan.delta, plan.join), plan.transforms);
|
|
8016
|
+
} finally {
|
|
8017
|
+
disposeWasmObject(base);
|
|
8018
|
+
}
|
|
8019
|
+
}
|
|
7560
8020
|
case "project": {
|
|
7561
|
-
const
|
|
7562
|
-
|
|
8021
|
+
const source = lowerShapeCompilePlanToManifold(plan.sourceShape, wasm);
|
|
8022
|
+
try {
|
|
8023
|
+
const transformed = source.transform(planeFrameToWorldToPlaneMatrix(plan.plane));
|
|
8024
|
+
try {
|
|
8025
|
+
return applyProfileCompileTransforms(transformed.project(), plan.transforms);
|
|
8026
|
+
} finally {
|
|
8027
|
+
if (transformed !== source) disposeWasmObject(transformed);
|
|
8028
|
+
}
|
|
8029
|
+
} finally {
|
|
8030
|
+
disposeWasmObject(source);
|
|
8031
|
+
}
|
|
7563
8032
|
}
|
|
8033
|
+
case "pathProfile":
|
|
8034
|
+
return applyProfileCompileTransforms(new wasm.CrossSection([plan.points]), plan.transforms);
|
|
7564
8035
|
default:
|
|
7565
8036
|
assertExhaustive(plan);
|
|
7566
8037
|
}
|
|
@@ -7584,7 +8055,9 @@ function applyShapeCompileTransform(manifold, step) {
|
|
|
7584
8055
|
function applyShapeCompileTransforms(manifold, steps) {
|
|
7585
8056
|
let out = manifold;
|
|
7586
8057
|
for (const step of steps) {
|
|
8058
|
+
const prev = out;
|
|
7587
8059
|
out = applyShapeCompileTransform(out, step);
|
|
8060
|
+
if (out !== prev) disposeWasmObject(prev);
|
|
7588
8061
|
}
|
|
7589
8062
|
return out;
|
|
7590
8063
|
}
|
|
@@ -7599,22 +8072,36 @@ function lowerShapeBooleanCompilePlan(plan, wasm) {
|
|
|
7599
8072
|
if (shapes.length === 1) {
|
|
7600
8073
|
return shapes[0];
|
|
7601
8074
|
}
|
|
7602
|
-
|
|
7603
|
-
|
|
7604
|
-
|
|
7605
|
-
|
|
7606
|
-
|
|
7607
|
-
|
|
7608
|
-
|
|
8075
|
+
try {
|
|
8076
|
+
switch (plan.op) {
|
|
8077
|
+
case "union":
|
|
8078
|
+
return wasm.Manifold.union(shapes);
|
|
8079
|
+
case "difference":
|
|
8080
|
+
return wasm.Manifold.difference(shapes);
|
|
8081
|
+
case "intersection":
|
|
8082
|
+
return wasm.Manifold.intersection(shapes);
|
|
8083
|
+
}
|
|
8084
|
+
} finally {
|
|
8085
|
+
disposeWasmObjects(shapes);
|
|
7609
8086
|
}
|
|
7610
8087
|
}
|
|
7611
8088
|
function lowerShapeTrimByPlaneCompilePlan(plan, wasm) {
|
|
7612
|
-
|
|
8089
|
+
const base = lowerShapeCompilePlanToManifold(plan.base, wasm);
|
|
8090
|
+
try {
|
|
8091
|
+
return base.trimByPlane([plan.normalX, plan.normalY, plan.normalZ], plan.originOffset);
|
|
8092
|
+
} finally {
|
|
8093
|
+
disposeWasmObject(base);
|
|
8094
|
+
}
|
|
7613
8095
|
}
|
|
7614
8096
|
function lowerShapeLoftCompilePlan(plan, wasm) {
|
|
7615
|
-
const inputPolygons = plan.profiles.map(
|
|
7616
|
-
|
|
7617
|
-
|
|
8097
|
+
const inputPolygons = plan.profiles.map((profile) => {
|
|
8098
|
+
const crossSection = lowerProfileCompilePlanToCrossSection(profile, wasm);
|
|
8099
|
+
try {
|
|
8100
|
+
return crossSection.toPolygons();
|
|
8101
|
+
} finally {
|
|
8102
|
+
disposeWasmObject(crossSection);
|
|
8103
|
+
}
|
|
8104
|
+
});
|
|
7618
8105
|
if (inputPolygons.length >= 2) {
|
|
7619
8106
|
const stitched = loftStitched(inputPolygons, plan.heights, wasm);
|
|
7620
8107
|
if (stitched) return stitched;
|
|
@@ -7623,7 +8110,14 @@ function lowerShapeLoftCompilePlan(plan, wasm) {
|
|
|
7623
8110
|
return lowerSdfToManifold(levelSetFieldToStandardSdf(input.sdf), input.bounds, input.edgeLength, wasm);
|
|
7624
8111
|
}
|
|
7625
8112
|
function lowerShapeSweepCompilePlan(plan, wasm) {
|
|
7626
|
-
const
|
|
8113
|
+
const crossSection = lowerProfileCompilePlanToCrossSection(plan.profile, wasm);
|
|
8114
|
+
const profilePolygons = (() => {
|
|
8115
|
+
try {
|
|
8116
|
+
return crossSection.toPolygons();
|
|
8117
|
+
} finally {
|
|
8118
|
+
disposeWasmObject(crossSection);
|
|
8119
|
+
}
|
|
8120
|
+
})();
|
|
7627
8121
|
const pathPoints = sweepPathToPolylineAdaptive(plan.path, plan.pathSamples ?? 48);
|
|
7628
8122
|
const up = [plan.up[0], plan.up[1], plan.up[2]];
|
|
7629
8123
|
const stitched = sweepStitched(profilePolygons, pathPoints, up, wasm);
|
|
@@ -7635,77 +8129,318 @@ function lowerShapeSweepCompilePlan(plan, wasm) {
|
|
|
7635
8129
|
});
|
|
7636
8130
|
return lowerSdfToManifold(levelSetFieldToStandardSdf(input.sdf), input.bounds, input.edgeLength, wasm);
|
|
7637
8131
|
}
|
|
7638
|
-
function
|
|
7639
|
-
|
|
7640
|
-
|
|
7641
|
-
|
|
8132
|
+
function buildPlaneFrame(normal) {
|
|
8133
|
+
const [nx, ny, nz] = normal;
|
|
8134
|
+
if (Math.abs(nz) > 1 - 1e-8) {
|
|
8135
|
+
const s = nz > 0 ? 1 : -1;
|
|
8136
|
+
return { u: [1, 0, 0], v: [0, s, 0] };
|
|
8137
|
+
}
|
|
8138
|
+
if (Math.abs(ny) > 1 - 1e-8) {
|
|
8139
|
+
const s = ny > 0 ? 1 : -1;
|
|
8140
|
+
return { u: [1, 0, 0], v: [0, 0, s] };
|
|
8141
|
+
}
|
|
8142
|
+
if (Math.abs(nx) > 1 - 1e-8) {
|
|
8143
|
+
const s = nx > 0 ? 1 : -1;
|
|
8144
|
+
return { u: [0, 1, 0], v: [0, 0, s] };
|
|
8145
|
+
}
|
|
8146
|
+
const ref = Math.abs(nx) < 0.9 ? [1, 0, 0] : [0, 1, 0];
|
|
8147
|
+
const uRaw = [
|
|
8148
|
+
ref[1] * nz - ref[2] * ny,
|
|
8149
|
+
ref[2] * nx - ref[0] * nz,
|
|
8150
|
+
ref[0] * ny - ref[1] * nx
|
|
8151
|
+
];
|
|
8152
|
+
const uLen = Math.sqrt(uRaw[0] * uRaw[0] + uRaw[1] * uRaw[1] + uRaw[2] * uRaw[2]);
|
|
8153
|
+
const u = [uRaw[0] / uLen, uRaw[1] / uLen, uRaw[2] / uLen];
|
|
8154
|
+
const v = [
|
|
8155
|
+
ny * u[2] - nz * u[1],
|
|
8156
|
+
nz * u[0] - nx * u[2],
|
|
8157
|
+
nx * u[1] - ny * u[0]
|
|
8158
|
+
];
|
|
8159
|
+
return { u, v };
|
|
8160
|
+
}
|
|
8161
|
+
function dot3$5(a, b) {
|
|
8162
|
+
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
|
8163
|
+
}
|
|
8164
|
+
function profileHalfWidths(loops, height) {
|
|
8165
|
+
let maxPos = 0;
|
|
8166
|
+
let maxNeg = 0;
|
|
8167
|
+
for (const loop of loops) {
|
|
8168
|
+
for (let i = 0; i < loop.length; i++) {
|
|
8169
|
+
const [x0, y0] = loop[i];
|
|
8170
|
+
const [x1, y1] = loop[(i + 1) % loop.length];
|
|
8171
|
+
if (y0 <= height && y1 > height || y1 <= height && y0 > height) {
|
|
8172
|
+
const t = (height - y0) / (y1 - y0);
|
|
8173
|
+
const x = x0 + t * (x1 - x0);
|
|
8174
|
+
if (x > maxPos) maxPos = x;
|
|
8175
|
+
if (x < 0 && -x > maxNeg) maxNeg = -x;
|
|
8176
|
+
}
|
|
8177
|
+
}
|
|
8178
|
+
}
|
|
8179
|
+
return [maxPos, maxNeg];
|
|
8180
|
+
}
|
|
8181
|
+
function interpolatedHalfWidths(slices, spineCoord) {
|
|
8182
|
+
if (slices.length === 1) return profileHalfWidths(slices[0].polygons, spineCoord);
|
|
8183
|
+
if (spineCoord <= slices[0].offset) return profileHalfWidths(slices[0].polygons, spineCoord);
|
|
8184
|
+
if (spineCoord >= slices[slices.length - 1].offset) {
|
|
8185
|
+
return profileHalfWidths(slices[slices.length - 1].polygons, spineCoord);
|
|
8186
|
+
}
|
|
8187
|
+
let seg = 0;
|
|
8188
|
+
while (seg + 1 < slices.length && spineCoord > slices[seg + 1].offset) seg++;
|
|
8189
|
+
const t = (spineCoord - slices[seg].offset) / (slices[seg + 1].offset - slices[seg].offset);
|
|
8190
|
+
const [p0, n0] = profileHalfWidths(slices[seg].polygons, spineCoord);
|
|
8191
|
+
const [p1, n1] = profileHalfWidths(slices[seg + 1].polygons, spineCoord);
|
|
8192
|
+
return [p0 * (1 - t) + p1 * t, n0 * (1 - t) + n1 * t];
|
|
8193
|
+
}
|
|
8194
|
+
function lowerMultiGroupFromSlicesToManifold(plan, wasm) {
|
|
8195
|
+
const n0 = plan.groups[0].normal;
|
|
8196
|
+
const n1 = plan.groups[1].normal;
|
|
8197
|
+
const spineRaw = [
|
|
8198
|
+
n0[1] * n1[2] - n0[2] * n1[1],
|
|
8199
|
+
n0[2] * n1[0] - n0[0] * n1[2],
|
|
8200
|
+
n0[0] * n1[1] - n0[1] * n1[0]
|
|
8201
|
+
];
|
|
8202
|
+
const spineLen = Math.sqrt(spineRaw[0] ** 2 + spineRaw[1] ** 2 + spineRaw[2] ** 2);
|
|
8203
|
+
if (spineLen < 1e-8) {
|
|
8204
|
+
throw new Error("Shape.fromSlices: multi-group slices must have non-parallel normals.");
|
|
8205
|
+
}
|
|
8206
|
+
const spine = [spineRaw[0] / spineLen, spineRaw[1] / spineLen, spineRaw[2] / spineLen];
|
|
8207
|
+
const preparedGroups = plan.groups.map((group2) => {
|
|
8208
|
+
const frame = buildPlaneFrame(group2.normal);
|
|
7642
8209
|
const sorted = [...group2.slices].sort((a, b) => a.offset - b.offset);
|
|
7643
|
-
const
|
|
7644
|
-
|
|
7645
|
-
|
|
7646
|
-
|
|
7647
|
-
|
|
7648
|
-
|
|
7649
|
-
|
|
7650
|
-
|
|
7651
|
-
|
|
7652
|
-
|
|
7653
|
-
|
|
7654
|
-
|
|
8210
|
+
const sliceData = sorted.map((s) => {
|
|
8211
|
+
const cross2 = lowerProfileCompilePlanToCrossSection(s.profile, wasm);
|
|
8212
|
+
try {
|
|
8213
|
+
const polygons = cross2.toPolygons();
|
|
8214
|
+
return { offset: s.offset, polygons, sdf: compilePolygonsSdf(polygons) };
|
|
8215
|
+
} finally {
|
|
8216
|
+
disposeWasmObject(cross2);
|
|
8217
|
+
}
|
|
8218
|
+
});
|
|
8219
|
+
const spineOnU = Math.abs(dot3$5(spine, frame.u));
|
|
8220
|
+
const spineOnV = Math.abs(dot3$5(spine, frame.v));
|
|
8221
|
+
const vIsSpine = spineOnV >= spineOnU;
|
|
8222
|
+
const measAxis = vIsSpine ? frame.u : frame.v;
|
|
8223
|
+
const spineAxis = vIsSpine ? frame.v : frame.u;
|
|
8224
|
+
const spineSign = dot3$5(spine, spineAxis) >= 0 ? 1 : -1;
|
|
8225
|
+
let minMeas = Infinity;
|
|
8226
|
+
let maxMeas = -Infinity;
|
|
8227
|
+
let minSpine = Infinity;
|
|
8228
|
+
let maxSpine = -Infinity;
|
|
8229
|
+
for (const s of sliceData) {
|
|
8230
|
+
for (const loop of s.polygons) {
|
|
8231
|
+
for (const [x, y] of loop) {
|
|
8232
|
+
const meas = vIsSpine ? x : y;
|
|
8233
|
+
const sp = vIsSpine ? y : x;
|
|
8234
|
+
if (meas < minMeas) minMeas = meas;
|
|
8235
|
+
if (meas > maxMeas) maxMeas = meas;
|
|
8236
|
+
if (sp < minSpine) minSpine = sp;
|
|
8237
|
+
if (sp > maxSpine) maxSpine = sp;
|
|
8238
|
+
}
|
|
8239
|
+
}
|
|
8240
|
+
}
|
|
8241
|
+
const sliceDataForRayCast = vIsSpine ? sliceData : sliceData.map((s) => ({
|
|
8242
|
+
offset: s.offset,
|
|
8243
|
+
polygons: s.polygons.map((loop) => loop.map(([x, y]) => [y, x]))
|
|
8244
|
+
}));
|
|
8245
|
+
const profileSdf2d = (u, v) => {
|
|
8246
|
+
if (sliceData.length === 1) return sliceData[0].sdf(u, v);
|
|
8247
|
+
const w = vIsSpine ? v : u;
|
|
8248
|
+
const offsets = sliceData.map((s) => s.offset);
|
|
8249
|
+
if (w <= offsets[0]) return sliceData[0].sdf(u, v);
|
|
8250
|
+
if (w >= offsets[offsets.length - 1]) return sliceData[sliceData.length - 1].sdf(u, v);
|
|
8251
|
+
let seg = 0;
|
|
8252
|
+
while (seg + 1 < offsets.length && w > offsets[seg + 1]) seg++;
|
|
8253
|
+
const t = (w - offsets[seg]) / (offsets[seg + 1] - offsets[seg]);
|
|
8254
|
+
return sliceData[seg].sdf(u, v) * (1 - t) + sliceData[seg + 1].sdf(u, v) * t;
|
|
8255
|
+
};
|
|
8256
|
+
return {
|
|
8257
|
+
normal: group2.normal,
|
|
8258
|
+
frame,
|
|
8259
|
+
measAxis,
|
|
8260
|
+
spineSign,
|
|
8261
|
+
vIsSpine,
|
|
8262
|
+
sliceData: sliceDataForRayCast,
|
|
8263
|
+
profileSdf2d,
|
|
8264
|
+
minMeas,
|
|
8265
|
+
maxMeas,
|
|
8266
|
+
minSpine,
|
|
8267
|
+
maxSpine
|
|
8268
|
+
};
|
|
8269
|
+
});
|
|
8270
|
+
const sdf3d = (p2) => {
|
|
8271
|
+
const spineCoord = dot3$5(p2, spine);
|
|
8272
|
+
let sumRhoSq = 0;
|
|
8273
|
+
let profileCapSdf = Infinity;
|
|
8274
|
+
for (const g of preparedGroups) {
|
|
8275
|
+
const measCoord = dot3$5(p2, g.measAxis);
|
|
8276
|
+
const localSpine = spineCoord * g.spineSign;
|
|
8277
|
+
const [wPos, wNeg] = interpolatedHalfWidths(g.sliceData, localSpine);
|
|
8278
|
+
let rho;
|
|
8279
|
+
if (wPos <= 1e-10 && wNeg <= 1e-10) {
|
|
8280
|
+
rho = 2;
|
|
8281
|
+
} else if (measCoord >= 0) {
|
|
8282
|
+
rho = wPos > 1e-10 ? measCoord / wPos : 2;
|
|
7655
8283
|
} else {
|
|
7656
|
-
|
|
7657
|
-
edgeLength: plan.edgeLength,
|
|
7658
|
-
boundsPadding: plan.boundsPadding
|
|
7659
|
-
});
|
|
7660
|
-
solid = lowerSdfToManifold(levelSetFieldToStandardSdf(input.sdf), input.bounds, input.edgeLength, wasm);
|
|
8284
|
+
rho = wNeg > 1e-10 ? -measCoord / wNeg : 2;
|
|
7661
8285
|
}
|
|
8286
|
+
sumRhoSq += rho * rho;
|
|
8287
|
+
const u = dot3$5(p2, g.frame.u);
|
|
8288
|
+
const v = dot3$5(p2, g.frame.v);
|
|
8289
|
+
const dProfile = g.profileSdf2d(u, v);
|
|
8290
|
+
profileCapSdf = Math.min(profileCapSdf, dProfile);
|
|
7662
8291
|
}
|
|
7663
|
-
|
|
7664
|
-
|
|
7665
|
-
|
|
8292
|
+
const rhoSdf = sumRhoSq - 1;
|
|
8293
|
+
const capSdf = -profileCapSdf;
|
|
8294
|
+
return Math.max(rhoSdf, capSdf);
|
|
8295
|
+
};
|
|
8296
|
+
let bMinX = -Infinity;
|
|
8297
|
+
let bMaxX = Infinity;
|
|
8298
|
+
let bMinY = -Infinity;
|
|
8299
|
+
let bMaxY = Infinity;
|
|
8300
|
+
let bMinZ = -Infinity;
|
|
8301
|
+
let bMaxZ = Infinity;
|
|
8302
|
+
for (const g of preparedGroups) {
|
|
8303
|
+
for (let axis = 0; axis < 3; axis++) {
|
|
8304
|
+
const mc = g.measAxis[axis];
|
|
8305
|
+
if (Math.abs(mc) > 0.5) {
|
|
8306
|
+
const lo = mc > 0 ? g.minMeas : -g.maxMeas;
|
|
8307
|
+
const hi = mc > 0 ? g.maxMeas : -g.minMeas;
|
|
8308
|
+
if (axis === 0) {
|
|
8309
|
+
bMinX = Math.max(bMinX, lo);
|
|
8310
|
+
bMaxX = Math.min(bMaxX, hi);
|
|
8311
|
+
} else if (axis === 1) {
|
|
8312
|
+
bMinY = Math.max(bMinY, lo);
|
|
8313
|
+
bMaxY = Math.min(bMaxY, hi);
|
|
8314
|
+
} else {
|
|
8315
|
+
bMinZ = Math.max(bMinZ, lo);
|
|
8316
|
+
bMaxZ = Math.min(bMaxZ, hi);
|
|
8317
|
+
}
|
|
8318
|
+
}
|
|
8319
|
+
}
|
|
8320
|
+
}
|
|
8321
|
+
let spineMin = -Infinity;
|
|
8322
|
+
let spineMax = Infinity;
|
|
8323
|
+
for (const g of preparedGroups) {
|
|
8324
|
+
const lo = g.spineSign > 0 ? g.minSpine : -g.maxSpine;
|
|
8325
|
+
const hi = g.spineSign > 0 ? g.maxSpine : -g.minSpine;
|
|
8326
|
+
spineMin = Math.max(spineMin, lo);
|
|
8327
|
+
spineMax = Math.min(spineMax, hi);
|
|
8328
|
+
}
|
|
8329
|
+
for (let axis = 0; axis < 3; axis++) {
|
|
8330
|
+
if (Math.abs(spine[axis]) > 0.5) {
|
|
8331
|
+
const lo = spine[axis] > 0 ? spineMin : -spineMax;
|
|
8332
|
+
const hi = spine[axis] > 0 ? spineMax : -spineMin;
|
|
8333
|
+
if (axis === 0) {
|
|
8334
|
+
bMinX = Math.max(bMinX, lo);
|
|
8335
|
+
bMaxX = Math.min(bMaxX, hi);
|
|
8336
|
+
} else if (axis === 1) {
|
|
8337
|
+
bMinY = Math.max(bMinY, lo);
|
|
8338
|
+
bMaxY = Math.min(bMaxY, hi);
|
|
7666
8339
|
} else {
|
|
7667
|
-
|
|
7668
|
-
|
|
7669
|
-
|
|
7670
|
-
|
|
7671
|
-
const s = len;
|
|
7672
|
-
const ux = ax / len;
|
|
7673
|
-
const uy = ay / len;
|
|
7674
|
-
const m00 = c + ux * ux * (1 - c);
|
|
7675
|
-
const m01 = ux * uy * (1 - c);
|
|
7676
|
-
const m02 = uy * s;
|
|
7677
|
-
const m10 = uy * ux * (1 - c);
|
|
7678
|
-
const m11 = c + uy * uy * (1 - c);
|
|
7679
|
-
const m12 = -ux * s;
|
|
7680
|
-
const m20 = -uy * s;
|
|
7681
|
-
const m21 = ux * s;
|
|
7682
|
-
const m22 = c;
|
|
7683
|
-
solid = solid.transform([m00, m01, m02, 0, m10, m11, m12, 0, m20, m21, m22, 0, 0, 0, 0, 1]);
|
|
7684
|
-
}
|
|
7685
|
-
}
|
|
7686
|
-
groupSolids.push(solid);
|
|
7687
|
-
}
|
|
7688
|
-
if (groupSolids.length === 1) return groupSolids[0];
|
|
7689
|
-
let result = groupSolids[0];
|
|
7690
|
-
for (let i = 1; i < groupSolids.length; i++) {
|
|
7691
|
-
result = result.intersect(groupSolids[i]);
|
|
8340
|
+
bMinZ = Math.max(bMinZ, lo);
|
|
8341
|
+
bMaxZ = Math.min(bMaxZ, hi);
|
|
8342
|
+
}
|
|
8343
|
+
}
|
|
7692
8344
|
}
|
|
7693
|
-
|
|
8345
|
+
const fallback = 100;
|
|
8346
|
+
if (!isFinite(bMinX)) bMinX = -fallback;
|
|
8347
|
+
if (!isFinite(bMaxX)) bMaxX = fallback;
|
|
8348
|
+
if (!isFinite(bMinY)) bMinY = -fallback;
|
|
8349
|
+
if (!isFinite(bMaxY)) bMaxY = fallback;
|
|
8350
|
+
if (!isFinite(bMinZ)) bMinZ = -fallback;
|
|
8351
|
+
if (!isFinite(bMaxZ)) bMaxZ = fallback;
|
|
8352
|
+
const pad = plan.boundsPadding;
|
|
8353
|
+
const bounds = {
|
|
8354
|
+
min: [bMinX - pad, bMinY - pad, bMinZ - pad],
|
|
8355
|
+
max: [bMaxX + pad, bMaxY + pad, bMaxZ + pad]
|
|
8356
|
+
};
|
|
8357
|
+
return lowerSdfToManifold(sdf3d, bounds, plan.edgeLength, wasm);
|
|
8358
|
+
}
|
|
8359
|
+
function lowerFromSlicesToManifold(plan, wasm) {
|
|
8360
|
+
if (plan.groups.length === 0) throw new Error("Shape.fromSlices requires at least one slice");
|
|
8361
|
+
if (plan.groups.length > 1) {
|
|
8362
|
+
return lowerMultiGroupFromSlicesToManifold(plan, wasm);
|
|
8363
|
+
}
|
|
8364
|
+
const group2 = plan.groups[0];
|
|
8365
|
+
const sorted = [...group2.slices].sort((a, b) => a.offset - b.offset);
|
|
8366
|
+
const [nx, ny, nz] = group2.normal;
|
|
8367
|
+
let solid;
|
|
8368
|
+
if (sorted.length === 1) {
|
|
8369
|
+
const cross2 = lowerProfileCompilePlanToCrossSection(sorted[0].profile, wasm);
|
|
8370
|
+
const extrudeHalf = 500;
|
|
8371
|
+
try {
|
|
8372
|
+
const extruded = cross2.extrude(2 * extrudeHalf);
|
|
8373
|
+
solid = applyShapeCompileTransforms(extruded, [{ kind: "translate", x: 0, y: 0, z: sorted[0].offset - extrudeHalf }]);
|
|
8374
|
+
} finally {
|
|
8375
|
+
disposeWasmObject(cross2);
|
|
8376
|
+
}
|
|
8377
|
+
} else {
|
|
8378
|
+
const polygons = sorted.map((s) => {
|
|
8379
|
+
const crossSection = lowerProfileCompilePlanToCrossSection(s.profile, wasm);
|
|
8380
|
+
try {
|
|
8381
|
+
return crossSection.toPolygons();
|
|
8382
|
+
} finally {
|
|
8383
|
+
disposeWasmObject(crossSection);
|
|
8384
|
+
}
|
|
8385
|
+
});
|
|
8386
|
+
const heights = sorted.map((s) => s.offset);
|
|
8387
|
+
const stitched = polygons.length >= 2 ? loftStitched(polygons, heights, wasm) : null;
|
|
8388
|
+
if (stitched) {
|
|
8389
|
+
solid = stitched;
|
|
8390
|
+
} else {
|
|
8391
|
+
const input = buildLoftLevelSetInput(polygons, heights, {
|
|
8392
|
+
edgeLength: plan.edgeLength,
|
|
8393
|
+
boundsPadding: plan.boundsPadding
|
|
8394
|
+
});
|
|
8395
|
+
solid = lowerSdfToManifold(levelSetFieldToStandardSdf(input.sdf), input.bounds, input.edgeLength, wasm);
|
|
8396
|
+
}
|
|
8397
|
+
}
|
|
8398
|
+
if (Math.abs(nx) > 1e-10 || Math.abs(ny) > 1e-10 || nz < 0) {
|
|
8399
|
+
if (Math.abs(nx) < 1e-10 && Math.abs(ny) < 1e-10 && nz < 0) {
|
|
8400
|
+
const prev = solid;
|
|
8401
|
+
solid = solid.rotate([180, 0, 0]);
|
|
8402
|
+
if (solid !== prev) disposeWasmObject(prev);
|
|
8403
|
+
} else {
|
|
8404
|
+
const ax = -ny;
|
|
8405
|
+
const ay = nx;
|
|
8406
|
+
const len = Math.sqrt(ax * ax + ay * ay);
|
|
8407
|
+
const c = nz;
|
|
8408
|
+
const s = len;
|
|
8409
|
+
const ux = ax / len;
|
|
8410
|
+
const uy = ay / len;
|
|
8411
|
+
const m00 = c + ux * ux * (1 - c);
|
|
8412
|
+
const m01 = ux * uy * (1 - c);
|
|
8413
|
+
const m02 = uy * s;
|
|
8414
|
+
const m10 = uy * ux * (1 - c);
|
|
8415
|
+
const m11 = c + uy * uy * (1 - c);
|
|
8416
|
+
const m12 = -ux * s;
|
|
8417
|
+
const m20 = -uy * s;
|
|
8418
|
+
const m21 = ux * s;
|
|
8419
|
+
const m22 = c;
|
|
8420
|
+
const prev = solid;
|
|
8421
|
+
solid = solid.transform([m00, m01, m02, 0, m10, m11, m12, 0, m20, m21, m22, 0, 0, 0, 0, 1]);
|
|
8422
|
+
if (solid !== prev) disposeWasmObject(prev);
|
|
8423
|
+
}
|
|
8424
|
+
}
|
|
8425
|
+
return solid;
|
|
7694
8426
|
}
|
|
7695
8427
|
function lowerShapeVariableSweepCompilePlan(plan, wasm) {
|
|
7696
|
-
const sectionPolygons = plan.sections.map((s) =>
|
|
7697
|
-
|
|
7698
|
-
|
|
7699
|
-
|
|
7700
|
-
|
|
7701
|
-
|
|
7702
|
-
|
|
7703
|
-
{
|
|
7704
|
-
|
|
7705
|
-
boundsPadding: plan.boundsPadding,
|
|
7706
|
-
up: [plan.up[0], plan.up[1], plan.up[2]]
|
|
8428
|
+
const sectionPolygons = plan.sections.map((s) => {
|
|
8429
|
+
const crossSection = lowerProfileCompilePlanToCrossSection(s.profile, wasm);
|
|
8430
|
+
try {
|
|
8431
|
+
return {
|
|
8432
|
+
t: s.t,
|
|
8433
|
+
polygons: crossSection.toPolygons()
|
|
8434
|
+
};
|
|
8435
|
+
} finally {
|
|
8436
|
+
disposeWasmObject(crossSection);
|
|
7707
8437
|
}
|
|
7708
|
-
);
|
|
8438
|
+
});
|
|
8439
|
+
const input = buildVariableSweepLevelSetInput(sectionPolygons, plan.path, {
|
|
8440
|
+
edgeLength: plan.edgeLength,
|
|
8441
|
+
boundsPadding: plan.boundsPadding,
|
|
8442
|
+
up: [plan.up[0], plan.up[1], plan.up[2]]
|
|
8443
|
+
});
|
|
7709
8444
|
return lowerSdfToManifold(levelSetFieldToStandardSdf(input.sdf), input.bounds, input.edgeLength, wasm);
|
|
7710
8445
|
}
|
|
7711
8446
|
function lowerShapeFilletCompilePlan(plan, wasm) {
|
|
@@ -7716,13 +8451,15 @@ function lowerShapeFilletCompilePlan(plan, wasm) {
|
|
|
7716
8451
|
`fillet2d() currently supports ${selection.selection.edgeName} only with quadrant [${selection.selection.quadrant[0]}, ${selection.selection.quadrant[1]}].`
|
|
7717
8452
|
);
|
|
7718
8453
|
}
|
|
7719
|
-
|
|
7720
|
-
|
|
7721
|
-
selection.selection,
|
|
7722
|
-
|
|
7723
|
-
|
|
7724
|
-
|
|
7725
|
-
|
|
8454
|
+
const base = lowerShapeCompilePlanToManifold(plan.base, wasm);
|
|
8455
|
+
try {
|
|
8456
|
+
const result = applyFilletSelectionToManifold(base, selection.selection, plan.radius, plan.segments, wasm);
|
|
8457
|
+
if (result !== base) disposeWasmObject(base);
|
|
8458
|
+
return result;
|
|
8459
|
+
} catch (error) {
|
|
8460
|
+
disposeWasmObject(base);
|
|
8461
|
+
throw error;
|
|
8462
|
+
}
|
|
7726
8463
|
}
|
|
7727
8464
|
function lowerShapeChamferCompilePlan(plan, wasm) {
|
|
7728
8465
|
const selection = resolveSupportedEdgeFeatureSelection(plan.base, plan.edge);
|
|
@@ -7732,7 +8469,15 @@ function lowerShapeChamferCompilePlan(plan, wasm) {
|
|
|
7732
8469
|
`chamfer2d() currently supports ${selection.selection.edgeName} only with quadrant [${selection.selection.quadrant[0]}, ${selection.selection.quadrant[1]}].`
|
|
7733
8470
|
);
|
|
7734
8471
|
}
|
|
7735
|
-
|
|
8472
|
+
const base = lowerShapeCompilePlanToManifold(plan.base, wasm);
|
|
8473
|
+
try {
|
|
8474
|
+
const result = applyChamferSelectionToManifold(base, selection.selection, plan.size, wasm);
|
|
8475
|
+
if (result !== base) disposeWasmObject(base);
|
|
8476
|
+
return result;
|
|
8477
|
+
} catch (error) {
|
|
8478
|
+
disposeWasmObject(base);
|
|
8479
|
+
throw error;
|
|
8480
|
+
}
|
|
7736
8481
|
}
|
|
7737
8482
|
function edgeSegmentToSelection(segment) {
|
|
7738
8483
|
const { start, end, direction: axis, normalA, normalB, convex } = segment;
|
|
@@ -7827,7 +8572,9 @@ function lowerFilletEdgesCompilePlan(plan, wasm) {
|
|
|
7827
8572
|
try {
|
|
7828
8573
|
const selection = edgeSegmentToSelection(seg);
|
|
7829
8574
|
const apply = seg.convex ? applyFilletSelectionToManifold : applyConcaveFilletSelectionToManifold;
|
|
7830
|
-
|
|
8575
|
+
const prev = manifold;
|
|
8576
|
+
manifold = apply(prev, selection, plan.radius, plan.segments, wasm);
|
|
8577
|
+
if (manifold !== prev) disposeWasmObject(prev);
|
|
7831
8578
|
} catch {
|
|
7832
8579
|
}
|
|
7833
8580
|
}
|
|
@@ -7852,7 +8599,9 @@ function lowerChamferEdgesCompilePlan(plan, wasm) {
|
|
|
7852
8599
|
try {
|
|
7853
8600
|
const selection = edgeSegmentToSelection(seg);
|
|
7854
8601
|
const apply = seg.convex ? applyChamferSelectionToManifold : applyConcaveChamferSelectionToManifold;
|
|
7855
|
-
|
|
8602
|
+
const prev = manifold;
|
|
8603
|
+
manifold = apply(prev, selection, plan.size, wasm);
|
|
8604
|
+
if (manifold !== prev) disposeWasmObject(prev);
|
|
7856
8605
|
} catch {
|
|
7857
8606
|
}
|
|
7858
8607
|
}
|
|
@@ -7861,24 +8610,34 @@ function lowerChamferEdgesCompilePlan(plan, wasm) {
|
|
|
7861
8610
|
function lowerShapeCompilePlanToManifold(plan, wasm) {
|
|
7862
8611
|
switch (plan.kind) {
|
|
7863
8612
|
case "box":
|
|
7864
|
-
return wasm.Manifold.cube([plan.x, plan.y, plan.z], false)
|
|
8613
|
+
return applyShapeCompileTransforms(wasm.Manifold.cube([plan.x, plan.y, plan.z], false), [
|
|
8614
|
+
{ kind: "translate", x: -plan.x / 2, y: -plan.y / 2, z: 0 }
|
|
8615
|
+
]);
|
|
7865
8616
|
case "cylinder":
|
|
7866
8617
|
return wasm.Manifold.cylinder(plan.height, plan.radius, plan.radiusTop ?? -1, plan.segments ?? 0, false);
|
|
7867
8618
|
case "sphere":
|
|
7868
8619
|
return wasm.Manifold.sphere(plan.radius, plan.segments ?? 0);
|
|
7869
8620
|
case "torus": {
|
|
7870
8621
|
const circle2 = wasm.CrossSection.circle(plan.minorRadius, plan.segments ?? 0);
|
|
7871
|
-
|
|
7872
|
-
|
|
8622
|
+
try {
|
|
8623
|
+
const translated = circle2.translate([plan.majorRadius, 0]);
|
|
8624
|
+
try {
|
|
8625
|
+
return translated.revolve(plan.segments ?? 0, 360);
|
|
8626
|
+
} finally {
|
|
8627
|
+
disposeWasmObject(translated);
|
|
8628
|
+
}
|
|
8629
|
+
} finally {
|
|
8630
|
+
disposeWasmObject(circle2);
|
|
8631
|
+
}
|
|
8632
|
+
}
|
|
8633
|
+
case "extrude": {
|
|
8634
|
+
const profile = lowerProfileCompilePlanToCrossSection(plan.profile, wasm);
|
|
8635
|
+
try {
|
|
8636
|
+
return profile.extrude(plan.height, plan.twistSegments ?? 0, plan.twist ?? 0, plan.scaleTop, false);
|
|
8637
|
+
} finally {
|
|
8638
|
+
disposeWasmObject(profile);
|
|
8639
|
+
}
|
|
7873
8640
|
}
|
|
7874
|
-
case "extrude":
|
|
7875
|
-
return lowerProfileCompilePlanToCrossSection(plan.profile, wasm).extrude(
|
|
7876
|
-
plan.height,
|
|
7877
|
-
plan.twistSegments ?? 0,
|
|
7878
|
-
plan.twist ?? 0,
|
|
7879
|
-
plan.scaleTop,
|
|
7880
|
-
false
|
|
7881
|
-
);
|
|
7882
8641
|
case "sheetMetal":
|
|
7883
8642
|
return lowerShapeCompilePlanToManifold(lowerSheetMetalBasePlan(plan.model, plan.output), wasm);
|
|
7884
8643
|
case "shell": {
|
|
@@ -7896,8 +8655,14 @@ function lowerShapeCompilePlanToManifold(plan, wasm) {
|
|
|
7896
8655
|
if (!lowered.ok) throw new Error(lowered.reason);
|
|
7897
8656
|
return lowerShapeCompilePlanToManifold(lowered.plan, wasm);
|
|
7898
8657
|
}
|
|
7899
|
-
case "revolve":
|
|
7900
|
-
|
|
8658
|
+
case "revolve": {
|
|
8659
|
+
const profile = lowerProfileCompilePlanToCrossSection(plan.profile, wasm);
|
|
8660
|
+
try {
|
|
8661
|
+
return profile.revolve(plan.segments ?? 0, plan.degrees);
|
|
8662
|
+
} finally {
|
|
8663
|
+
disposeWasmObject(profile);
|
|
8664
|
+
}
|
|
8665
|
+
}
|
|
7901
8666
|
case "loft":
|
|
7902
8667
|
return lowerShapeLoftCompilePlan(plan, wasm);
|
|
7903
8668
|
case "sweep":
|
|
@@ -7932,6 +8697,10 @@ function lowerShapeCompilePlanToManifold(plan, wasm) {
|
|
|
7932
8697
|
}
|
|
7933
8698
|
case "fromSlices":
|
|
7934
8699
|
return lowerFromSlicesToManifold(plan, wasm);
|
|
8700
|
+
case "nurbsSurface":
|
|
8701
|
+
return lowerNurbsSurfaceToManifold(plan, wasm);
|
|
8702
|
+
case "importedStep":
|
|
8703
|
+
throw new Error(`importStep("${plan.filePath}") requires the OCCT backend. Add setActiveBackend('occt') at the top of your script.`);
|
|
7935
8704
|
default:
|
|
7936
8705
|
assertExhaustive(plan);
|
|
7937
8706
|
}
|
|
@@ -7958,17 +8727,27 @@ function lowerSdfToManifold(evalFn, bounds, edgeLength2, wasm) {
|
|
|
7958
8727
|
vertProperties: vertProps6,
|
|
7959
8728
|
triVerts
|
|
7960
8729
|
});
|
|
7961
|
-
|
|
8730
|
+
try {
|
|
8731
|
+
return new wasm.Manifold(wasmMesh);
|
|
8732
|
+
} finally {
|
|
8733
|
+
disposeWasmObject(wasmMesh);
|
|
8734
|
+
}
|
|
7962
8735
|
}
|
|
7963
8736
|
function simplifySdfMesh(triVerts, vertProperties, edgeLength2, wasm) {
|
|
7964
8737
|
const maxError = edgeLength2 * 0.15;
|
|
7965
8738
|
for (const ratio of [0.5, 0.75]) {
|
|
7966
8739
|
let simplified = simplifyMesh(triVerts, vertProperties, ratio, maxError);
|
|
7967
8740
|
simplified = filterDegenerateTriangles(simplified);
|
|
8741
|
+
let mesh = null;
|
|
8742
|
+
let manifold = null;
|
|
7968
8743
|
try {
|
|
7969
|
-
|
|
8744
|
+
mesh = new wasm.Mesh({ numProp: 3, vertProperties, triVerts: simplified });
|
|
8745
|
+
manifold = new wasm.Manifold(mesh);
|
|
7970
8746
|
return simplified;
|
|
7971
8747
|
} catch {
|
|
8748
|
+
} finally {
|
|
8749
|
+
disposeWasmObject(manifold);
|
|
8750
|
+
disposeWasmObject(mesh);
|
|
7972
8751
|
}
|
|
7973
8752
|
}
|
|
7974
8753
|
return triVerts;
|
|
@@ -8028,6 +8807,58 @@ function projectVerticesToSurfaceWithNormals(vertProperties, sdfFn, out6) {
|
|
|
8028
8807
|
}
|
|
8029
8808
|
}
|
|
8030
8809
|
}
|
|
8810
|
+
function lowerNurbsSurfaceToManifold(plan, wasm) {
|
|
8811
|
+
const surface = new NurbsSurface(plan.controlGrid, {
|
|
8812
|
+
degreeU: plan.degreeU,
|
|
8813
|
+
degreeV: plan.degreeV,
|
|
8814
|
+
weights: plan.weightsGrid,
|
|
8815
|
+
knotsU: plan.knotsU,
|
|
8816
|
+
knotsV: plan.knotsV
|
|
8817
|
+
});
|
|
8818
|
+
const res = plan.resolution;
|
|
8819
|
+
const { positions, normals, indices } = surface.tessellate(res, res);
|
|
8820
|
+
const thickness = plan.thickness;
|
|
8821
|
+
const numVerts = positions.length;
|
|
8822
|
+
const allPositions = [];
|
|
8823
|
+
for (const [x, y, z] of positions) allPositions.push(x, y, z);
|
|
8824
|
+
for (let i = 0; i < numVerts; i++) {
|
|
8825
|
+
const [x, y, z] = positions[i];
|
|
8826
|
+
const [nx, ny, nz] = normals[i];
|
|
8827
|
+
allPositions.push(x - nx * thickness, y - ny * thickness, z - nz * thickness);
|
|
8828
|
+
}
|
|
8829
|
+
const allIndices = [];
|
|
8830
|
+
for (const idx of indices) allIndices.push(idx);
|
|
8831
|
+
for (let i = 0; i < indices.length; i += 3) {
|
|
8832
|
+
allIndices.push(indices[i] + numVerts, indices[i + 2] + numVerts, indices[i + 1] + numVerts);
|
|
8833
|
+
}
|
|
8834
|
+
const resU = res, resV = res;
|
|
8835
|
+
for (let i = 0; i < resU; i++) {
|
|
8836
|
+
const a = i * (resV + 1);
|
|
8837
|
+
const b = (i + 1) * (resV + 1);
|
|
8838
|
+
allIndices.push(a, a + numVerts, b, b, a + numVerts, b + numVerts);
|
|
8839
|
+
const c = a + resV;
|
|
8840
|
+
const d = b + resV;
|
|
8841
|
+
allIndices.push(c, d, c + numVerts, d, d + numVerts, c + numVerts);
|
|
8842
|
+
}
|
|
8843
|
+
for (let j = 0; j < resV; j++) {
|
|
8844
|
+
const a = j;
|
|
8845
|
+
const b = j + 1;
|
|
8846
|
+
allIndices.push(a, b, a + numVerts, b, b + numVerts, a + numVerts);
|
|
8847
|
+
const c = resU * (resV + 1) + j;
|
|
8848
|
+
const d = c + 1;
|
|
8849
|
+
allIndices.push(c, c + numVerts, d, d, c + numVerts, d + numVerts);
|
|
8850
|
+
}
|
|
8851
|
+
const mesh = new wasm.Mesh({
|
|
8852
|
+
numProp: 3,
|
|
8853
|
+
vertProperties: new Float32Array(allPositions),
|
|
8854
|
+
triVerts: new Uint32Array(allIndices)
|
|
8855
|
+
});
|
|
8856
|
+
try {
|
|
8857
|
+
return new wasm.Manifold(mesh);
|
|
8858
|
+
} finally {
|
|
8859
|
+
disposeWasmObject(mesh);
|
|
8860
|
+
}
|
|
8861
|
+
}
|
|
8031
8862
|
function lowerImportedMeshToManifold(fileData, format, filePath, wasm) {
|
|
8032
8863
|
const parsed = parseMeshFile(fileData, format);
|
|
8033
8864
|
if (parsed.triVerts.length === 0) {
|
|
@@ -8046,6 +8877,8 @@ function lowerImportedMeshToManifold(fileData, format, filePath, wasm) {
|
|
|
8046
8877
|
throw new Error(
|
|
8047
8878
|
`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)}`
|
|
8048
8879
|
);
|
|
8880
|
+
} finally {
|
|
8881
|
+
disposeWasmObject(wasmMesh);
|
|
8049
8882
|
}
|
|
8050
8883
|
}
|
|
8051
8884
|
function lowerShapeCompilePlanToShapeBackend(plan, wasm) {
|
|
@@ -8480,6 +9313,8 @@ function summarizeProfile(profile) {
|
|
|
8480
9313
|
return "offset profile";
|
|
8481
9314
|
case "project":
|
|
8482
9315
|
return "projected profile";
|
|
9316
|
+
case "pathProfile":
|
|
9317
|
+
return `path profile (${profile.edges.length} edges)`;
|
|
8483
9318
|
default:
|
|
8484
9319
|
assertExhaustive(profile);
|
|
8485
9320
|
}
|
|
@@ -10102,6 +10937,8 @@ function resolveShapeFaceTableInternal(plan, owner) {
|
|
|
10102
10937
|
case "importedMesh":
|
|
10103
10938
|
case "sdf":
|
|
10104
10939
|
case "fromSlices":
|
|
10940
|
+
case "nurbsSurface":
|
|
10941
|
+
case "importedStep":
|
|
10105
10942
|
return emptyFaceTable();
|
|
10106
10943
|
default:
|
|
10107
10944
|
assertExhaustive(plan);
|
|
@@ -10605,7 +11442,7 @@ function transformPortMap(ports, matrix) {
|
|
|
10605
11442
|
}
|
|
10606
11443
|
return out;
|
|
10607
11444
|
}
|
|
10608
|
-
function computeConnectFrame(childBase, childPort, parentPort,
|
|
11445
|
+
function computeConnectFrame(childBase, childPort, parentPort, _flip, childAlign = "middle", parentAlign = "middle") {
|
|
10609
11446
|
const childAlignPt = resolvePortAlignPoint(childPort, childAlign);
|
|
10610
11447
|
const parentAlignPt = resolvePortAlignPoint(parentPort, parentAlign);
|
|
10611
11448
|
const cI = childBase.point(childAlignPt);
|
|
@@ -10613,7 +11450,9 @@ function computeConnectFrame(childBase, childPort, parentPort, flip, childAlign
|
|
|
10613
11450
|
const cUp = normalize3$2(childBase.vector(childPort.up));
|
|
10614
11451
|
const cRight = normalize3$2(cross3$3(cAxis, cUp));
|
|
10615
11452
|
const pOrigin = parentAlignPt;
|
|
10616
|
-
const
|
|
11453
|
+
const jointKind = childPort.kind ?? parentPort.kind;
|
|
11454
|
+
const faceToFace = jointKind !== "prismatic";
|
|
11455
|
+
const pAxis = faceToFace ? negate3$1(parentPort.axis) : [...parentPort.axis];
|
|
10617
11456
|
const pUp = [...parentPort.up];
|
|
10618
11457
|
const pRight = normalize3$2(cross3$3(pAxis, pUp));
|
|
10619
11458
|
const r00 = pRight[0] * cRight[0] + pUp[0] * cUp[0] + pAxis[0] * cAxis[0];
|
|
@@ -12409,6 +13248,8 @@ function rootTopologyRewritePropagation(plan) {
|
|
|
12409
13248
|
case "importedMesh":
|
|
12410
13249
|
case "sdf":
|
|
12411
13250
|
case "fromSlices":
|
|
13251
|
+
case "nurbsSurface":
|
|
13252
|
+
case "importedStep":
|
|
12412
13253
|
return null;
|
|
12413
13254
|
default:
|
|
12414
13255
|
assertExhaustive(plan);
|
|
@@ -12832,6 +13673,8 @@ function rootPlanPropagation(plan) {
|
|
|
12832
13673
|
case "importedMesh":
|
|
12833
13674
|
case "sdf":
|
|
12834
13675
|
case "fromSlices":
|
|
13676
|
+
case "nurbsSurface":
|
|
13677
|
+
case "importedStep":
|
|
12835
13678
|
return void 0;
|
|
12836
13679
|
default:
|
|
12837
13680
|
assertExhaustive(plan);
|
|
@@ -14676,46 +15519,82 @@ function injectVoronoiSurfaceChild(children) {
|
|
|
14676
15519
|
return child;
|
|
14677
15520
|
});
|
|
14678
15521
|
}
|
|
14679
|
-
|
|
14680
|
-
|
|
14681
|
-
|
|
14682
|
-
|
|
14683
|
-
basketWeave,
|
|
14684
|
-
bend,
|
|
14685
|
-
blend,
|
|
15522
|
+
const sdf = {
|
|
15523
|
+
/** Create an SDF sphere centered at the origin. */
|
|
15524
|
+
sphere: sphere$1,
|
|
15525
|
+
/** Create an SDF box centered at the origin with given full dimensions (not half-extents). */
|
|
14686
15526
|
box: box$1,
|
|
14687
|
-
|
|
15527
|
+
/** Create an SDF cylinder centered at the origin, axis along Z. */
|
|
15528
|
+
cylinder: cylinder$1,
|
|
15529
|
+
/** Create an SDF torus centered at the origin, lying in the XY plane. */
|
|
15530
|
+
torus: torus$1,
|
|
15531
|
+
/** Create an SDF capsule centered at the origin, axis along Z. */
|
|
14688
15532
|
capsule,
|
|
15533
|
+
/** Create an SDF cone with base at z=0 and tip at z=height. */
|
|
14689
15534
|
cone,
|
|
14690
|
-
|
|
14691
|
-
|
|
14692
|
-
|
|
15535
|
+
/** Smooth union — blends shapes together with a smooth transition radius. */
|
|
15536
|
+
smoothUnion,
|
|
15537
|
+
/** Smooth difference — smoothly subtracts b from a. */
|
|
15538
|
+
smoothDifference,
|
|
15539
|
+
/** Smooth intersection — smoothly intersects a and b. */
|
|
15540
|
+
smoothIntersection,
|
|
15541
|
+
/** Morph between two SDF shapes. t=0 → a, t=1 → b. */
|
|
15542
|
+
morph,
|
|
15543
|
+
/**
|
|
15544
|
+
* Spatially blend between two SDF patterns.
|
|
15545
|
+
* The blend function receives (x, y, z) and returns 0..1:
|
|
15546
|
+
* 0 = fully pattern `a`, 1 = fully pattern `b`.
|
|
15547
|
+
*/
|
|
15548
|
+
blend,
|
|
15549
|
+
/** Gyroid TPMS lattice — the most common lattice for additive manufacturing. */
|
|
14693
15550
|
gyroid,
|
|
14694
|
-
|
|
14695
|
-
|
|
15551
|
+
/** Schwarz-P TPMS lattice — isotropic pore structure. */
|
|
15552
|
+
schwarzP,
|
|
15553
|
+
/** Diamond TPMS lattice — stiffest TPMS structure. */
|
|
15554
|
+
diamond,
|
|
15555
|
+
/** Lidinoid TPMS lattice — visually distinct from gyroid, popular in research and art. */
|
|
14696
15556
|
lidinoid,
|
|
14697
|
-
|
|
15557
|
+
/** 3D Simplex noise field — produces organic, natural-looking displacements. */
|
|
14698
15558
|
noise,
|
|
15559
|
+
/** 3D Voronoi pattern — organic cellular structures like bone, coral, or soap bubbles. */
|
|
15560
|
+
voronoi,
|
|
15561
|
+
/** Honeycomb (hexagonal) lattice pattern. Intersect with your shape to apply. */
|
|
15562
|
+
honeycomb,
|
|
15563
|
+
/** Sinusoidal wave ridges — parallel ridges along an axis. */
|
|
15564
|
+
waves,
|
|
15565
|
+
/** Knurl pattern — crossed helical grooves for grips and handles. */
|
|
15566
|
+
knurl,
|
|
15567
|
+
/** Perforated plate pattern — regular array of cylindrical holes. */
|
|
14699
15568
|
perforated,
|
|
14700
|
-
|
|
15569
|
+
/** Fish/dragon scale pattern — overlapping circular scales in hex-packed rows. */
|
|
14701
15570
|
scales,
|
|
14702
|
-
|
|
14703
|
-
|
|
14704
|
-
|
|
14705
|
-
|
|
14706
|
-
|
|
14707
|
-
|
|
15571
|
+
/** Brick/stone wall pattern — running bond with mortar grooves. */
|
|
15572
|
+
brick,
|
|
15573
|
+
/** Grid lattice pattern — two families of infinite slabs crossing at 90°. */
|
|
15574
|
+
weave,
|
|
15575
|
+
/** Basket weave surface pattern — threads with over-under crossings in UV space. Returns a SurfacePattern for use with `.surfaceDisplace()`. */
|
|
15576
|
+
basketWeave,
|
|
15577
|
+
/** Twist an SDF shape around the Z axis. */
|
|
14708
15578
|
twist,
|
|
14709
|
-
|
|
14710
|
-
|
|
14711
|
-
|
|
14712
|
-
|
|
15579
|
+
/** Bend an SDF shape around the Z axis. */
|
|
15580
|
+
bend,
|
|
15581
|
+
/** Repeat an SDF shape in space. */
|
|
15582
|
+
repeat,
|
|
15583
|
+
/** A 2D surface pattern — a heightmap function for use with `.surfaceDisplace()`. */
|
|
15584
|
+
SurfacePattern,
|
|
15585
|
+
/** Create an SDF shape from an arbitrary distance function. You must provide bounds since the function is opaque. */
|
|
15586
|
+
fromFunction
|
|
15587
|
+
};
|
|
14713
15588
|
async function initKernel() {
|
|
14714
15589
|
initOCCT().catch((e) => console.warn("[kernel] OCCT background init failed:", e));
|
|
14715
15590
|
const [manifoldModule] = await Promise.all([initManifoldWasm(), initMeshoptimizer()]);
|
|
14716
15591
|
return manifoldModule;
|
|
14717
15592
|
}
|
|
14718
15593
|
let _activeBackend = "manifold";
|
|
15594
|
+
let _runtimeWarn = (msg) => console.warn(msg);
|
|
15595
|
+
function setRuntimeWarnSink(fn) {
|
|
15596
|
+
_runtimeWarn = fn;
|
|
15597
|
+
}
|
|
14719
15598
|
function unwrapShapeLike(value) {
|
|
14720
15599
|
if (value instanceof Shape) return value;
|
|
14721
15600
|
if (value && typeof value === "object" && typeof value.toShape === "function") {
|
|
@@ -15873,6 +16752,33 @@ class Shape {
|
|
|
15873
16752
|
static _unwrap(value) {
|
|
15874
16753
|
return unwrapShapeLike(value);
|
|
15875
16754
|
}
|
|
16755
|
+
/**
|
|
16756
|
+
* Warn if a boolean operation had no geometric effect.
|
|
16757
|
+
* Compares volumes before and after; if they match within tolerance, the operation was a no-op.
|
|
16758
|
+
*/
|
|
16759
|
+
static _checkBooleanNoOp(op, base, result, others) {
|
|
16760
|
+
try {
|
|
16761
|
+
if (op === "intersection") {
|
|
16762
|
+
if (result.isEmpty()) {
|
|
16763
|
+
_runtimeWarn(
|
|
16764
|
+
`intersection() produced an empty shape — the operands may not overlap.`
|
|
16765
|
+
);
|
|
16766
|
+
}
|
|
16767
|
+
return;
|
|
16768
|
+
}
|
|
16769
|
+
if (op === "difference") {
|
|
16770
|
+
const volBefore = base.volume();
|
|
16771
|
+
const volAfter = result.volume();
|
|
16772
|
+
const tol = Math.max(volBefore * 1e-4, 1e-3);
|
|
16773
|
+
if (Math.abs(volBefore - volAfter) < tol) {
|
|
16774
|
+
_runtimeWarn(
|
|
16775
|
+
`subtract() had no effect — the tool may not overlap the base shape. Base vol=${volBefore.toFixed(1)}mm³, result vol=${volAfter.toFixed(1)}mm³`
|
|
16776
|
+
);
|
|
16777
|
+
}
|
|
16778
|
+
}
|
|
16779
|
+
} catch {
|
|
16780
|
+
}
|
|
16781
|
+
}
|
|
15876
16782
|
/** Union this shape with others (additive boolean). Method form of union(). */
|
|
15877
16783
|
add(...others) {
|
|
15878
16784
|
const shapes = [
|
|
@@ -15910,7 +16816,7 @@ class Shape {
|
|
|
15910
16816
|
"boolean:difference",
|
|
15911
16817
|
(owner) => buildBooleanTopologyRewritePropagation("difference", owner, operandPlans)
|
|
15912
16818
|
);
|
|
15913
|
-
|
|
16819
|
+
const resultShape = setShapeCompilePlanInternal(
|
|
15914
16820
|
setShapeGeometryInfoInternal(
|
|
15915
16821
|
withBaseDimensions(this, buildShapeFromCompilePlan(nextPlan, this.colorHex)),
|
|
15916
16822
|
mergeGeometryInfos(
|
|
@@ -15921,6 +16827,8 @@ class Shape {
|
|
|
15921
16827
|
),
|
|
15922
16828
|
nextPlan
|
|
15923
16829
|
);
|
|
16830
|
+
Shape._checkBooleanNoOp("difference", this, resultShape, shapes.slice(1));
|
|
16831
|
+
return resultShape;
|
|
15924
16832
|
}
|
|
15925
16833
|
/** Keep only the overlap with other shapes. Method form of intersection(). */
|
|
15926
16834
|
intersect(...others) {
|
|
@@ -15939,7 +16847,7 @@ class Shape {
|
|
|
15939
16847
|
"boolean:intersection",
|
|
15940
16848
|
(owner) => buildBooleanTopologyRewritePropagation("intersection", owner, operandPlans)
|
|
15941
16849
|
);
|
|
15942
|
-
|
|
16850
|
+
const resultShape = setShapeCompilePlanInternal(
|
|
15943
16851
|
setShapeGeometryInfoInternal(
|
|
15944
16852
|
withMergedDimensions(shapes, buildShapeFromCompilePlan(nextPlan, this.colorHex)),
|
|
15945
16853
|
mergeGeometryInfos(
|
|
@@ -15950,6 +16858,8 @@ class Shape {
|
|
|
15950
16858
|
),
|
|
15951
16859
|
nextPlan
|
|
15952
16860
|
);
|
|
16861
|
+
Shape._checkBooleanNoOp("intersection", this, resultShape, shapes.slice(1));
|
|
16862
|
+
return resultShape;
|
|
15953
16863
|
}
|
|
15954
16864
|
/** Alias for add() — matches the free-function union() naming. */
|
|
15955
16865
|
union(...others) {
|
|
@@ -16551,6 +17461,7 @@ function intersection(...inputs) {
|
|
|
16551
17461
|
var define_process_env_default = {};
|
|
16552
17462
|
let _wasm_solve = null;
|
|
16553
17463
|
let _wasm_get_profile = null;
|
|
17464
|
+
let _solverMemory = null;
|
|
16554
17465
|
let _sessionApi = null;
|
|
16555
17466
|
function getSessionApi() {
|
|
16556
17467
|
return _sessionApi;
|
|
@@ -16850,9 +17761,11 @@ async function initSolverWasm() {
|
|
|
16850
17761
|
}
|
|
16851
17762
|
if (!wasmPath) throw new Error("solver_bg.wasm not found — run: npm run build:solver");
|
|
16852
17763
|
const wasmBytes = readFileSync(wasmPath);
|
|
16853
|
-
await solverModule.default(wasmBytes);
|
|
17764
|
+
const exports$1 = await solverModule.default(wasmBytes);
|
|
17765
|
+
_solverMemory = (exports$1 == null ? void 0 : exports$1.memory) ?? null;
|
|
16854
17766
|
} else {
|
|
16855
|
-
await solverModule.default();
|
|
17767
|
+
const exports$1 = await solverModule.default();
|
|
17768
|
+
_solverMemory = (exports$1 == null ? void 0 : exports$1.memory) ?? null;
|
|
16856
17769
|
}
|
|
16857
17770
|
performance.mark("solver:ready");
|
|
16858
17771
|
performance.measure("solver:import", "solver:start", "solver:imported");
|
|
@@ -17839,7 +18752,7 @@ class MateBuilder {
|
|
|
17839
18752
|
return this.constraints.reduce((sum, c) => sum + (CONSTRAINT_EQUATIONS[c.type] ?? 0), 0);
|
|
17840
18753
|
}
|
|
17841
18754
|
}
|
|
17842
|
-
let _collected$
|
|
18755
|
+
let _collected$7 = null;
|
|
17843
18756
|
const isAxis = (value) => value === "x" || value === "y" || value === "z";
|
|
17844
18757
|
const normalizeDirection = (value, label) => {
|
|
17845
18758
|
if (value === "radial" || isAxis(value)) return value;
|
|
@@ -17898,16 +18811,16 @@ const mergeDirective = (target, patch, label) => {
|
|
|
17898
18811
|
return out;
|
|
17899
18812
|
};
|
|
17900
18813
|
function resetExplodeView() {
|
|
17901
|
-
_collected$
|
|
18814
|
+
_collected$7 = null;
|
|
17902
18815
|
}
|
|
17903
18816
|
function getCollectedExplodeView() {
|
|
17904
|
-
return _collected$
|
|
18817
|
+
return _collected$7 ? cloneOptions(_collected$7) : null;
|
|
17905
18818
|
}
|
|
17906
18819
|
function explodeView(options = {}) {
|
|
17907
18820
|
if (!options || typeof options !== "object") {
|
|
17908
18821
|
throw new Error("explodeView(options) expects an options object");
|
|
17909
18822
|
}
|
|
17910
|
-
const next = _collected$
|
|
18823
|
+
const next = _collected$7 ? cloneOptions(_collected$7) : {};
|
|
17911
18824
|
if (options.enabled !== void 0) {
|
|
17912
18825
|
if (typeof options.enabled !== "boolean") throw new Error("explodeView.enabled must be a boolean");
|
|
17913
18826
|
next.enabled = options.enabled;
|
|
@@ -17955,9 +18868,9 @@ function explodeView(options = {}) {
|
|
|
17955
18868
|
});
|
|
17956
18869
|
next.byPath = byPath;
|
|
17957
18870
|
}
|
|
17958
|
-
_collected$
|
|
18871
|
+
_collected$7 = next;
|
|
17959
18872
|
}
|
|
17960
|
-
let _collected$
|
|
18873
|
+
let _collected$6 = null;
|
|
17961
18874
|
const isFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value);
|
|
17962
18875
|
const isVec3$1 = (value) => Array.isArray(value) && value.length === 3 && isFiniteNumber(value[0]) && isFiniteNumber(value[1]) && isFiniteNumber(value[2]);
|
|
17963
18876
|
const normalizeAxis = (axis) => {
|
|
@@ -18247,22 +19160,22 @@ const cloneCollected = (value) => ({
|
|
|
18247
19160
|
defaultAnimation: value.defaultAnimation
|
|
18248
19161
|
});
|
|
18249
19162
|
function resetJointsView() {
|
|
18250
|
-
_collected$
|
|
19163
|
+
_collected$6 = null;
|
|
18251
19164
|
}
|
|
18252
19165
|
function getCollectedJointsView() {
|
|
18253
|
-
return _collected$
|
|
19166
|
+
return _collected$6 ? cloneCollected(_collected$6) : null;
|
|
18254
19167
|
}
|
|
18255
19168
|
function saveJointsView() {
|
|
18256
|
-
return _collected$
|
|
19169
|
+
return _collected$6 ? cloneCollected(_collected$6) : null;
|
|
18257
19170
|
}
|
|
18258
19171
|
function restoreJointsView(state) {
|
|
18259
|
-
_collected$
|
|
19172
|
+
_collected$6 = state;
|
|
18260
19173
|
}
|
|
18261
19174
|
function jointsView(options = {}) {
|
|
18262
19175
|
if (!options || typeof options !== "object") {
|
|
18263
19176
|
throw new Error("jointsView(options) expects an options object");
|
|
18264
19177
|
}
|
|
18265
|
-
const next = _collected$
|
|
19178
|
+
const next = _collected$6 ? cloneCollected(_collected$6) : { joints: [], couplings: [], animations: [] };
|
|
18266
19179
|
if (options.enabled !== void 0) {
|
|
18267
19180
|
if (typeof options.enabled !== "boolean") {
|
|
18268
19181
|
throw new Error("jointsView.enabled must be a boolean");
|
|
@@ -18317,7 +19230,7 @@ function jointsView(options = {}) {
|
|
|
18317
19230
|
if (next.defaultAnimation && !next.animations.some((animation) => animation.name === next.defaultAnimation)) {
|
|
18318
19231
|
throw new Error(`jointsView defaultAnimation "${next.defaultAnimation}" does not exist in animations`);
|
|
18319
19232
|
}
|
|
18320
|
-
_collected$
|
|
19233
|
+
_collected$6 = next;
|
|
18321
19234
|
}
|
|
18322
19235
|
function bomToCsv(rows) {
|
|
18323
19236
|
const header = ["part", "qty", "material", "process", "tolerance", "notes"];
|
|
@@ -19024,8 +19937,16 @@ class Assembly {
|
|
|
19024
19937
|
* origins (child connector lands exactly on parent connector) and derives the joint frame
|
|
19025
19938
|
* and axis from the connector geometry — no manual `frame` or `axis` math needed.
|
|
19026
19939
|
*
|
|
19940
|
+
* **Face-to-face convention:** Connectors always meet face-to-face, like a USB plug
|
|
19941
|
+
* meeting a socket. Each connector's axis points "outward" from its part. When two
|
|
19942
|
+
* connectors mate, the system brings them together so their axes oppose (anti-parallel).
|
|
19943
|
+
* This is the same convention used by `matchTo()`.
|
|
19944
|
+
*
|
|
19945
|
+
* For a revolute joint (hinge), both connectors' axes should point outward from their
|
|
19946
|
+
* respective parts along the hinge line. For a prismatic joint (slider), both axes
|
|
19947
|
+
* should point along the slide direction from their part's perspective.
|
|
19948
|
+
*
|
|
19027
19949
|
* The joint type is inferred from the connector's `kind` field if not specified in `options`.
|
|
19028
|
-
* Use `flip: true` for mirrored parts whose connector axis is reflected.
|
|
19029
19950
|
*
|
|
19030
19951
|
* When connectors are defined with `start`/`end`, you can control which point on each
|
|
19031
19952
|
* connector meets via `align` / `parentAlign` / `childAlign` (`'start'`, `'middle'`, `'end'`).
|
|
@@ -19037,20 +19958,22 @@ class Assembly {
|
|
|
19037
19958
|
* **Example**
|
|
19038
19959
|
*
|
|
19039
19960
|
* ```ts
|
|
19040
|
-
*
|
|
19041
|
-
*
|
|
19042
|
-
*
|
|
19043
|
-
*
|
|
19044
|
-
*
|
|
19045
|
-
*
|
|
19046
|
-
*
|
|
19047
|
-
*
|
|
19048
|
-
*
|
|
19961
|
+
* // Hinge: both axes point outward along the hinge line
|
|
19962
|
+
* const frame = box(100, 10, 80).withConnectors({
|
|
19963
|
+
* hinge: connector("hinge", { origin: [0, 0, 40], axis: [0, 0, 1] }),
|
|
19964
|
+
* });
|
|
19965
|
+
* const door = box(60, 4, 80).withConnectors({
|
|
19966
|
+
* hinge: connector("hinge", { origin: [0, 0, 40], axis: [0, 0, -1] }),
|
|
19967
|
+
* });
|
|
19968
|
+
* assembly("Door")
|
|
19969
|
+
* .addPart("Frame", frame)
|
|
19970
|
+
* .addPart("Door", door)
|
|
19971
|
+
* .connect("Frame.hinge", "Door.hinge", { as: "swing", min: 0, max: 110 });
|
|
19049
19972
|
* ```
|
|
19050
19973
|
*
|
|
19051
19974
|
* @param parentPortRef - `"PartName.connectorName"` on the parent side
|
|
19052
19975
|
* @param childPortRef - `"PartName.connectorName"` on the child side
|
|
19053
|
-
* @param options - `as` (joint name), `type`, `min`, `max`, `default`, `
|
|
19976
|
+
* @param options - `as` (joint name), `type`, `min`, `max`, `default`, `align`, effort, velocity, etc.
|
|
19054
19977
|
* @returns `this` for chaining
|
|
19055
19978
|
* @see {@link match} for typed connector matching with gender/type validation
|
|
19056
19979
|
* @category Connectors
|
|
@@ -19067,7 +19990,7 @@ class Assembly {
|
|
|
19067
19990
|
const childBase = childRecord.base;
|
|
19068
19991
|
const childAlign = options.childAlign ?? options.align ?? "middle";
|
|
19069
19992
|
const parentAlign = options.parentAlign ?? options.align ?? "middle";
|
|
19070
|
-
const { frame, axis } = computeConnectFrame(childBase, child.port, parent.port,
|
|
19993
|
+
const { frame, axis } = computeConnectFrame(childBase, child.port, parent.port, false, childAlign, parentAlign);
|
|
19071
19994
|
const min2 = options.min ?? child.port.min ?? parent.port.min;
|
|
19072
19995
|
const max2 = options.max ?? child.port.max ?? parent.port.max;
|
|
19073
19996
|
this._usedPortRefs.add(parentPortRef);
|
|
@@ -19985,12 +20908,12 @@ function bom(quantity, description, opts) {
|
|
|
19985
20908
|
metadata
|
|
19986
20909
|
});
|
|
19987
20910
|
}
|
|
19988
|
-
let _collected$
|
|
20911
|
+
let _collected$5 = [];
|
|
19989
20912
|
function resetCutPlanes() {
|
|
19990
|
-
_collected$
|
|
20913
|
+
_collected$5 = [];
|
|
19991
20914
|
}
|
|
19992
20915
|
function getCollectedCutPlanes() {
|
|
19993
|
-
return _collected$
|
|
20916
|
+
return _collected$5.slice();
|
|
19994
20917
|
}
|
|
19995
20918
|
function normalizeExcludedObjectNames(input) {
|
|
19996
20919
|
if (input === void 0) return void 0;
|
|
@@ -20006,7 +20929,29 @@ function cutPlane(name, normal, offsetOrOptions = 0, maybeOptions = {}) {
|
|
|
20006
20929
|
const offset2 = Number.isFinite(rawOffset) ? rawOffset : 0;
|
|
20007
20930
|
const options = usingOffsetArg ? maybeOptions : offsetOrOptions;
|
|
20008
20931
|
const excludeObjectNames = normalizeExcludedObjectNames(options.exclude);
|
|
20009
|
-
_collected$
|
|
20932
|
+
_collected$5.push({ name, normal, offset: offset2, excludeObjectNames });
|
|
20933
|
+
}
|
|
20934
|
+
let _collected$4 = [];
|
|
20935
|
+
let _counter$1 = 0;
|
|
20936
|
+
function resetMocks() {
|
|
20937
|
+
_collected$4 = [];
|
|
20938
|
+
_counter$1 = 0;
|
|
20939
|
+
}
|
|
20940
|
+
function getCollectedMocks() {
|
|
20941
|
+
return _collected$4.slice();
|
|
20942
|
+
}
|
|
20943
|
+
function mock(shape, name) {
|
|
20944
|
+
if (!shape || typeof shape !== "object") {
|
|
20945
|
+
throw new Error("mock(shape): shape must be a Shape");
|
|
20946
|
+
}
|
|
20947
|
+
_counter$1 += 1;
|
|
20948
|
+
const displayName = name && typeof name === "string" && name.trim().length > 0 ? name.trim() : `Mock ${_counter$1}`;
|
|
20949
|
+
_collected$4.push({
|
|
20950
|
+
id: `mock-${_counter$1}`,
|
|
20951
|
+
name: displayName,
|
|
20952
|
+
shape
|
|
20953
|
+
});
|
|
20954
|
+
return shape;
|
|
20010
20955
|
}
|
|
20011
20956
|
const DEFAULT_PROFILE = {
|
|
20012
20957
|
bedX: 220,
|
|
@@ -21374,11 +22319,13 @@ Shape.prototype.cutout = function cutout(sketch, opts = {}) {
|
|
|
21374
22319
|
return shapeCutout(this, sketch, opts);
|
|
21375
22320
|
};
|
|
21376
22321
|
let _params = [];
|
|
22322
|
+
let _stringParams = [];
|
|
21377
22323
|
let _listParams = [];
|
|
21378
22324
|
let _overrides = {};
|
|
21379
22325
|
let _scopeStack = [];
|
|
21380
22326
|
function resetParams() {
|
|
21381
22327
|
_params = [];
|
|
22328
|
+
_stringParams = [];
|
|
21382
22329
|
_listParams = [];
|
|
21383
22330
|
_scopeStack = [];
|
|
21384
22331
|
}
|
|
@@ -21388,6 +22335,9 @@ function setParamOverrides(overrides) {
|
|
|
21388
22335
|
function getCollectedParams() {
|
|
21389
22336
|
return _params;
|
|
21390
22337
|
}
|
|
22338
|
+
function getCollectedStringParams() {
|
|
22339
|
+
return _stringParams;
|
|
22340
|
+
}
|
|
21391
22341
|
function getCollectedListParams() {
|
|
21392
22342
|
return _listParams;
|
|
21393
22343
|
}
|
|
@@ -21404,6 +22354,11 @@ function hasOwn(obj, key) {
|
|
|
21404
22354
|
}
|
|
21405
22355
|
function param(name, defaultValue, opts = {}) {
|
|
21406
22356
|
var _a3;
|
|
22357
|
+
if (typeof defaultValue !== "number") {
|
|
22358
|
+
throw new Error(
|
|
22359
|
+
`param("${name}"): defaultValue must be a number, got ${typeof defaultValue}. For text parameters, use Param.string("${name}", ${JSON.stringify(defaultValue)}).`
|
|
22360
|
+
);
|
|
22361
|
+
}
|
|
21407
22362
|
const scope = _scopeStack[_scopeStack.length - 1];
|
|
21408
22363
|
const scopedName = (scope == null ? void 0 : scope.namePrefix) ? `${scope.namePrefix} / ${name}` : name;
|
|
21409
22364
|
const scopedLocal = scope == null ? void 0 : scope.localOverrides;
|
|
@@ -21473,6 +22428,32 @@ function choiceParam(name, defaultValue, choices) {
|
|
|
21473
22428
|
}
|
|
21474
22429
|
return choices[index];
|
|
21475
22430
|
}
|
|
22431
|
+
function stringParam(name, defaultValue, opts = {}) {
|
|
22432
|
+
var _a3;
|
|
22433
|
+
if (typeof defaultValue !== "string") {
|
|
22434
|
+
throw new Error(`Param.string("${name}"): defaultValue must be a string, got ${typeof defaultValue}.`);
|
|
22435
|
+
}
|
|
22436
|
+
const scope = _scopeStack[_scopeStack.length - 1];
|
|
22437
|
+
const scopedName = (scope == null ? void 0 : scope.namePrefix) ? `${scope.namePrefix} / ${name}` : name;
|
|
22438
|
+
const scopedLocal = scope == null ? void 0 : scope.localOverrides;
|
|
22439
|
+
const hasLocalOverride = !!(scopedLocal && Object.prototype.hasOwnProperty.call(scopedLocal, name));
|
|
22440
|
+
if (hasLocalOverride) (_a3 = scope.consumedKeys) == null ? void 0 : _a3.add(name);
|
|
22441
|
+
const rawOverride = (hasLocalOverride ? scopedLocal[name] : void 0) ?? _overrides[scopedName] ?? _overrides[name];
|
|
22442
|
+
const value = typeof rawOverride === "string" ? rawOverride : defaultValue;
|
|
22443
|
+
const maxLength = opts.maxLength;
|
|
22444
|
+
const clamped = maxLength !== void 0 ? value.slice(0, maxLength) : value;
|
|
22445
|
+
if (!hasLocalOverride) {
|
|
22446
|
+
_stringParams.push({ name: scopedName, value: clamped, defaultValue, maxLength });
|
|
22447
|
+
}
|
|
22448
|
+
return clamped;
|
|
22449
|
+
}
|
|
22450
|
+
const Param = {
|
|
22451
|
+
number: param,
|
|
22452
|
+
string: stringParam,
|
|
22453
|
+
bool: boolParam,
|
|
22454
|
+
choice: choiceParam,
|
|
22455
|
+
list: listParam
|
|
22456
|
+
};
|
|
21476
22457
|
function listParam(name, defaultItems, opts) {
|
|
21477
22458
|
if (!Array.isArray(defaultItems)) throw new Error(`listParam("${name}"): defaultItems must be an array`);
|
|
21478
22459
|
const scope = _scopeStack[_scopeStack.length - 1];
|
|
@@ -22051,6 +23032,24 @@ function catmullRom2D$1(p0, p1, p2, p3, t, tension) {
|
|
|
22051
23032
|
const h11 = ttt - tt;
|
|
22052
23033
|
return [h00 * p1[0] + h10 * m1x + h01 * p2[0] + h11 * m2x, h00 * p1[1] + h10 * m1y + h01 * p2[1] + h11 * m2y];
|
|
22053
23034
|
}
|
|
23035
|
+
function sampleBSpline2D(controlPoints, weights, degree, tol = DEFAULT_TOLERANCE$1) {
|
|
23036
|
+
const n = controlPoints.length;
|
|
23037
|
+
const knots = generateClampedKnots(n, degree);
|
|
23038
|
+
let polyLen = 0;
|
|
23039
|
+
for (let i = 1; i < n; i++) {
|
|
23040
|
+
polyLen += Math.hypot(controlPoints[i][0] - controlPoints[i - 1][0], controlPoints[i][1] - controlPoints[i - 1][1]);
|
|
23041
|
+
}
|
|
23042
|
+
const count = Math.max(8, Math.min(256, Math.ceil(polyLen / tol)));
|
|
23043
|
+
const uMin = knots[degree];
|
|
23044
|
+
const uMax = knots[n];
|
|
23045
|
+
const pts = new Array(count);
|
|
23046
|
+
for (let i = 0; i < count; i++) {
|
|
23047
|
+
const t = i / (count - 1);
|
|
23048
|
+
const u = uMin + t * (uMax - uMin);
|
|
23049
|
+
pts[i] = deBoor2D(controlPoints, weights, knots, degree, u);
|
|
23050
|
+
}
|
|
23051
|
+
return pts;
|
|
23052
|
+
}
|
|
22054
23053
|
function ensureCCW(pts) {
|
|
22055
23054
|
let signedArea2 = 0;
|
|
22056
23055
|
for (let i = 0; i < pts.length; i++) {
|
|
@@ -22425,6 +23424,82 @@ class PathBuilder {
|
|
|
22425
23424
|
this.y = last[1];
|
|
22426
23425
|
return this;
|
|
22427
23426
|
}
|
|
23427
|
+
// ── NURBS / exact curves ──────────────────────────────────────────────────
|
|
23428
|
+
/**
|
|
23429
|
+
* Rational B-spline edge to (x, y) with explicit control points and weights.
|
|
23430
|
+
*
|
|
23431
|
+
* The control points define the B-spline shape between the current position
|
|
23432
|
+
* and (x, y). The current position is NOT included in `controlPoints` — it is
|
|
23433
|
+
* automatically prepended. The endpoint (x, y) is the last control point.
|
|
23434
|
+
*
|
|
23435
|
+
* @param controlPoints — interior + endpoint control points (endpoint = last)
|
|
23436
|
+
* @param opts.weights — rational weights (default: all 1.0)
|
|
23437
|
+
* @param opts.degree — B-spline degree (default: control point count - 1, capped at 3)
|
|
23438
|
+
*/
|
|
23439
|
+
nurbsTo(controlPoints, opts) {
|
|
23440
|
+
if (controlPoints.length < 1) throw new Error("nurbsTo: need at least 1 control point (the endpoint)");
|
|
23441
|
+
const allPts = [[this.x, this.y], ...controlPoints];
|
|
23442
|
+
const n = allPts.length;
|
|
23443
|
+
const degree = (opts == null ? void 0 : opts.degree) ?? Math.min(n - 1, 3);
|
|
23444
|
+
const weights = (opts == null ? void 0 : opts.weights) ?? new Array(n).fill(1);
|
|
23445
|
+
if (weights.length !== n) throw new Error(`nurbsTo: weights.length (${weights.length}) must match total control points (${n})`);
|
|
23446
|
+
const last = controlPoints[controlPoints.length - 1];
|
|
23447
|
+
this.segs.push({ kind: "bspline", x: last[0], y: last[1], controlPoints: allPts, weights, degree });
|
|
23448
|
+
const prevPt = allPts[n - 2];
|
|
23449
|
+
const tdx = last[0] - prevPt[0];
|
|
23450
|
+
const tdy = last[1] - prevPt[1];
|
|
23451
|
+
const tlen = Math.hypot(tdx, tdy);
|
|
23452
|
+
if (tlen > 1e-9) {
|
|
23453
|
+
this.dirX = tdx / tlen;
|
|
23454
|
+
this.dirY = tdy / tlen;
|
|
23455
|
+
}
|
|
23456
|
+
this.x = last[0];
|
|
23457
|
+
this.y = last[1];
|
|
23458
|
+
return this;
|
|
23459
|
+
}
|
|
23460
|
+
/**
|
|
23461
|
+
* Exact circular arc to (x, y) using a rational quadratic NURBS.
|
|
23462
|
+
*
|
|
23463
|
+
* Unlike `arcTo()` which tessellates to a polyline, this preserves the
|
|
23464
|
+
* exact arc definition. When extruded through the OCCT backend, it produces
|
|
23465
|
+
* a true cylindrical face — not a faceted approximation.
|
|
23466
|
+
*
|
|
23467
|
+
* @param x — endpoint X
|
|
23468
|
+
* @param y — endpoint Y
|
|
23469
|
+
* @param opts.radius — arc radius (default: auto-computed from chord)
|
|
23470
|
+
* @param opts.clockwise — winding direction (default: false = CCW)
|
|
23471
|
+
*/
|
|
23472
|
+
exactArcTo(x, y, opts) {
|
|
23473
|
+
const clockwise = (opts == null ? void 0 : opts.clockwise) ?? false;
|
|
23474
|
+
const dx = x - this.x;
|
|
23475
|
+
const dy = y - this.y;
|
|
23476
|
+
const chordLen = Math.hypot(dx, dy);
|
|
23477
|
+
if (chordLen < 1e-9) return this;
|
|
23478
|
+
const radius = (opts == null ? void 0 : opts.radius) ?? chordLen;
|
|
23479
|
+
if (radius < chordLen / 2 - 1e-9) throw new Error("exactArcTo: radius too small for the chord");
|
|
23480
|
+
const mx = (this.x + x) / 2;
|
|
23481
|
+
const my = (this.y + y) / 2;
|
|
23482
|
+
let nx = -dy / chordLen;
|
|
23483
|
+
let ny = dx / chordLen;
|
|
23484
|
+
if (clockwise) {
|
|
23485
|
+
nx = -nx;
|
|
23486
|
+
ny = -ny;
|
|
23487
|
+
}
|
|
23488
|
+
const h = Math.sqrt(Math.max(0, radius * radius - chordLen / 2 * (chordLen / 2)));
|
|
23489
|
+
const cx = mx + nx * h;
|
|
23490
|
+
const cy = my + ny * h;
|
|
23491
|
+
const halfAngle = Math.asin(Math.min(1, chordLen / (2 * radius)));
|
|
23492
|
+
const w = Math.cos(halfAngle);
|
|
23493
|
+
const smx = (this.x + x) / 2 - cx;
|
|
23494
|
+
const smy = (this.y + y) / 2 - cy;
|
|
23495
|
+
const smLen = Math.hypot(smx, smy);
|
|
23496
|
+
const shoulderX = cx + smx / smLen * radius;
|
|
23497
|
+
const shoulderY = cy + smy / smLen * radius;
|
|
23498
|
+
return this.nurbsTo(
|
|
23499
|
+
[[shoulderX, shoulderY], [x, y]],
|
|
23500
|
+
{ weights: [1, w, 1], degree: 2 }
|
|
23501
|
+
);
|
|
23502
|
+
}
|
|
22428
23503
|
// ── Corner modifiers ──────────────────────────────────────────────────────
|
|
22429
23504
|
/**
|
|
22430
23505
|
* Round the last corner (the junction between the previous two segments)
|
|
@@ -22749,6 +23824,11 @@ class PathBuilder {
|
|
|
22749
23824
|
const last = seg.points[seg.points.length - 1];
|
|
22750
23825
|
px = last[0];
|
|
22751
23826
|
py = last[1];
|
|
23827
|
+
} else if (seg.kind === "bspline") {
|
|
23828
|
+
const sampled = sampleBSpline2D(seg.controlPoints, seg.weights, seg.degree);
|
|
23829
|
+
for (let i = 1; i < sampled.length; i++) pts.push(sampled[i]);
|
|
23830
|
+
px = seg.x;
|
|
23831
|
+
py = seg.y;
|
|
22752
23832
|
}
|
|
22753
23833
|
}
|
|
22754
23834
|
return pts;
|
|
@@ -22785,11 +23865,23 @@ class PathBuilder {
|
|
|
22785
23865
|
close() {
|
|
22786
23866
|
const subPaths = this.splitSubPaths();
|
|
22787
23867
|
if (subPaths.length === 0) throw new Error("Path needs at least 3 points");
|
|
23868
|
+
const hasBSpline = this.segs.some((s) => s.kind === "bspline");
|
|
22788
23869
|
const tessellated = subPaths.map((segs) => this.tessellateSegs(segs));
|
|
22789
23870
|
const outer = tessellated[0];
|
|
22790
23871
|
if (outer.length < 3) throw new Error("Path needs at least 3 points");
|
|
22791
23872
|
ensureCCW(outer);
|
|
22792
|
-
let result
|
|
23873
|
+
let result;
|
|
23874
|
+
if (hasBSpline && subPaths.length === 1) {
|
|
23875
|
+
const edges = this.buildProfileEdges(subPaths[0]);
|
|
23876
|
+
result = buildSketchFromCompileProfilePlan({
|
|
23877
|
+
kind: "pathProfile",
|
|
23878
|
+
points: outer,
|
|
23879
|
+
edges,
|
|
23880
|
+
transforms: []
|
|
23881
|
+
});
|
|
23882
|
+
} else {
|
|
23883
|
+
result = polygon(outer);
|
|
23884
|
+
}
|
|
22793
23885
|
for (let i = 1; i < tessellated.length; i++) {
|
|
22794
23886
|
const hole2 = tessellated[i];
|
|
22795
23887
|
if (hole2.length < 3) continue;
|
|
@@ -22916,6 +24008,48 @@ class PathBuilder {
|
|
|
22916
24008
|
if (current.length > 0) paths.push(current);
|
|
22917
24009
|
return paths;
|
|
22918
24010
|
}
|
|
24011
|
+
/** Build semantic ProfileEdge array from path segments (for pathProfile compile plan). */
|
|
24012
|
+
buildProfileEdges(segs) {
|
|
24013
|
+
const edges = [];
|
|
24014
|
+
let px = 0, py = 0;
|
|
24015
|
+
for (const seg of segs) {
|
|
24016
|
+
if (seg.kind === "move") {
|
|
24017
|
+
px = seg.x;
|
|
24018
|
+
py = seg.y;
|
|
24019
|
+
} else if (seg.kind === "line") {
|
|
24020
|
+
edges.push({ kind: "line", x1: px, y1: py, x2: seg.x, y2: seg.y });
|
|
24021
|
+
px = seg.x;
|
|
24022
|
+
py = seg.y;
|
|
24023
|
+
} else if (seg.kind === "arc") {
|
|
24024
|
+
edges.push({ kind: "arc", x1: px, y1: py, x2: seg.x, y2: seg.y, cx: seg.cx, cy: seg.cy, clockwise: seg.clockwise });
|
|
24025
|
+
px = seg.x;
|
|
24026
|
+
py = seg.y;
|
|
24027
|
+
} else if (seg.kind === "bspline") {
|
|
24028
|
+
edges.push({
|
|
24029
|
+
kind: "bspline",
|
|
24030
|
+
controlPoints: seg.controlPoints,
|
|
24031
|
+
weights: seg.weights,
|
|
24032
|
+
knots: generateClampedKnots(seg.controlPoints.length, seg.degree),
|
|
24033
|
+
degree: seg.degree
|
|
24034
|
+
});
|
|
24035
|
+
px = seg.x;
|
|
24036
|
+
py = seg.y;
|
|
24037
|
+
} else {
|
|
24038
|
+
edges.push({ kind: "line", x1: px, y1: py, x2: seg.x, y2: seg.y });
|
|
24039
|
+
px = seg.x;
|
|
24040
|
+
py = seg.y;
|
|
24041
|
+
}
|
|
24042
|
+
}
|
|
24043
|
+
if (segs.length > 0) {
|
|
24044
|
+
const first = segs[0];
|
|
24045
|
+
const firstX = first.kind === "move" ? first.x : 0;
|
|
24046
|
+
const firstY = first.kind === "move" ? first.y : 0;
|
|
24047
|
+
if (Math.hypot(px - firstX, py - firstY) > 1e-9) {
|
|
24048
|
+
edges.push({ kind: "line", x1: px, y1: py, x2: firstX, y2: firstY });
|
|
24049
|
+
}
|
|
24050
|
+
}
|
|
24051
|
+
return edges;
|
|
24052
|
+
}
|
|
22919
24053
|
/** Tessellate a sub-path (sequence of segments). */
|
|
22920
24054
|
tessellateSegs(segs) {
|
|
22921
24055
|
const pts = [];
|
|
@@ -22943,6 +24077,11 @@ class PathBuilder {
|
|
|
22943
24077
|
const last = seg.points[seg.points.length - 1];
|
|
22944
24078
|
px = last[0];
|
|
22945
24079
|
py = last[1];
|
|
24080
|
+
} else if (seg.kind === "bspline") {
|
|
24081
|
+
const sampled = sampleBSpline2D(seg.controlPoints, seg.weights, seg.degree);
|
|
24082
|
+
for (let i = 1; i < sampled.length; i++) pts.push(sampled[i]);
|
|
24083
|
+
px = seg.x;
|
|
24084
|
+
py = seg.y;
|
|
22946
24085
|
}
|
|
22947
24086
|
}
|
|
22948
24087
|
return pts;
|
|
@@ -24229,7 +25368,20 @@ function spurGear(options) {
|
|
|
24229
25368
|
profile = difference2d(profile, circle2d(normalized.boreDiameter * 0.5, Math.max(48, normalized.teeth * 2)));
|
|
24230
25369
|
}
|
|
24231
25370
|
const shape = sketchExtrude(profile, normalized.faceWidth);
|
|
24232
|
-
|
|
25371
|
+
const shapeWithConnectors = shape.withConnectors({
|
|
25372
|
+
bore: connectorFactory("gear-bore", {
|
|
25373
|
+
origin: [0, 0, normalized.faceWidth / 2],
|
|
25374
|
+
axis: [0, 0, 1],
|
|
25375
|
+
kind: "revolute"
|
|
25376
|
+
}, {
|
|
25377
|
+
module: normalized.module,
|
|
25378
|
+
teeth: normalized.teeth,
|
|
25379
|
+
pitchRadius: meta2.pitchRadius,
|
|
25380
|
+
outerRadius: meta2.outerRadius,
|
|
25381
|
+
faceWidth: normalized.faceWidth
|
|
25382
|
+
})
|
|
25383
|
+
});
|
|
25384
|
+
return attachGearMeta(shapeWithConnectors, meta2);
|
|
24233
25385
|
}
|
|
24234
25386
|
function normalizeSideGearOptions(options) {
|
|
24235
25387
|
let normalizedSpur;
|
|
@@ -24315,7 +25467,23 @@ function sideGear(options) {
|
|
|
24315
25467
|
);
|
|
24316
25468
|
shape = shape.subtract(bore);
|
|
24317
25469
|
}
|
|
24318
|
-
|
|
25470
|
+
const boreZ = normalized.side === "top" ? zBands.bodyMinZ : zBands.bodyMaxZ;
|
|
25471
|
+
const boreAxis = normalized.side === "top" ? [0, 0, -1] : [0, 0, 1];
|
|
25472
|
+
const shapeWithConnectors = shape.withConnectors({
|
|
25473
|
+
bore: connectorFactory("gear-bore", {
|
|
25474
|
+
origin: [0, 0, boreZ],
|
|
25475
|
+
axis: boreAxis,
|
|
25476
|
+
kind: "revolute"
|
|
25477
|
+
}, {
|
|
25478
|
+
module: normalized.module,
|
|
25479
|
+
teeth: normalized.teeth,
|
|
25480
|
+
pitchRadius: meta2.pitchRadius,
|
|
25481
|
+
outerRadius: meta2.outerRadius,
|
|
25482
|
+
faceWidth: normalized.faceWidth,
|
|
25483
|
+
toothSide: normalized.side
|
|
25484
|
+
})
|
|
25485
|
+
});
|
|
25486
|
+
return attachGearMeta(shapeWithConnectors, meta2);
|
|
24319
25487
|
}
|
|
24320
25488
|
function faceGear(options) {
|
|
24321
25489
|
try {
|
|
@@ -24432,7 +25600,20 @@ function ringGear(options) {
|
|
|
24432
25600
|
}
|
|
24433
25601
|
const profile = difference2d(ringBlank, union2d(...spaces));
|
|
24434
25602
|
const shape = sketchExtrude(profile, normalized.faceWidth);
|
|
24435
|
-
|
|
25603
|
+
const shapeWithConnectors = shape.withConnectors({
|
|
25604
|
+
bore: connectorFactory("ring-bore", {
|
|
25605
|
+
origin: [0, 0, normalized.faceWidth / 2],
|
|
25606
|
+
axis: [0, 0, 1]
|
|
25607
|
+
}, {
|
|
25608
|
+
module: normalized.module,
|
|
25609
|
+
teeth: normalized.teeth,
|
|
25610
|
+
pitchRadius,
|
|
25611
|
+
innerRadius: tipRadius,
|
|
25612
|
+
outerRadius: normalized.outerRadius,
|
|
25613
|
+
faceWidth: normalized.faceWidth
|
|
25614
|
+
})
|
|
25615
|
+
});
|
|
25616
|
+
return attachGearMeta(shapeWithConnectors, meta2);
|
|
24436
25617
|
}
|
|
24437
25618
|
function rackGear(options) {
|
|
24438
25619
|
if (!isFinitePositive(options.module)) throw new Error('rackGear: "module" must be > 0');
|
|
@@ -24483,7 +25664,8 @@ function rackGear(options) {
|
|
|
24483
25664
|
teethSketches.push(sketchTranslate(toothSketch, cx, 0));
|
|
24484
25665
|
}
|
|
24485
25666
|
const span = (options.teeth - 1) * pitch + halfRoot * 2;
|
|
24486
|
-
const
|
|
25667
|
+
const length4 = span + module * 2;
|
|
25668
|
+
const base = sketchTranslate(rect(length4, baseHeight), 0, -dedendum - baseHeight * 0.5);
|
|
24487
25669
|
const profile = union2d(base, ...teethSketches);
|
|
24488
25670
|
const shape = sketchExtrude(profile, options.faceWidth);
|
|
24489
25671
|
const meta2 = {
|
|
@@ -24502,7 +25684,20 @@ function rackGear(options) {
|
|
|
24502
25684
|
backlash,
|
|
24503
25685
|
centered: false
|
|
24504
25686
|
};
|
|
24505
|
-
|
|
25687
|
+
const shapeWithConnectors = shape.withConnectors({
|
|
25688
|
+
teeth: connectorFactory("rack-teeth", {
|
|
25689
|
+
origin: [0, 0, options.faceWidth / 2],
|
|
25690
|
+
axis: [1, 0, 0],
|
|
25691
|
+
up: [0, 1, 0],
|
|
25692
|
+
kind: "prismatic"
|
|
25693
|
+
}, {
|
|
25694
|
+
module,
|
|
25695
|
+
teeth: options.teeth,
|
|
25696
|
+
faceWidth: options.faceWidth,
|
|
25697
|
+
length: length4
|
|
25698
|
+
})
|
|
25699
|
+
});
|
|
25700
|
+
return attachGearMeta(shapeWithConnectors, meta2);
|
|
24506
25701
|
}
|
|
24507
25702
|
function normalizeShaftAngle(label, value) {
|
|
24508
25703
|
if (!isFinitePositive(value) || value >= 175) {
|
|
@@ -24581,7 +25776,27 @@ function bevelGear(options) {
|
|
|
24581
25776
|
const shape = sketchExtrude(profile, normalized.faceWidth, {
|
|
24582
25777
|
scaleTop: normalized.topScale
|
|
24583
25778
|
});
|
|
24584
|
-
|
|
25779
|
+
const apexZ = normalized.module * normalized.teeth * 0.5 / Math.tan(normalized.pitchAngleRad);
|
|
25780
|
+
const measurements = {
|
|
25781
|
+
module: normalized.module,
|
|
25782
|
+
teeth: normalized.teeth,
|
|
25783
|
+
pitchRadius: meta2.pitchRadius,
|
|
25784
|
+
pitchAngleDeg: normalized.pitchAngleDeg,
|
|
25785
|
+
coneDistance: normalized.coneDistance,
|
|
25786
|
+
faceWidth: normalized.faceWidth
|
|
25787
|
+
};
|
|
25788
|
+
const shapeWithConnectors = shape.withConnectors({
|
|
25789
|
+
bore: connectorFactory("gear-bore", {
|
|
25790
|
+
origin: [0, 0, 0],
|
|
25791
|
+
axis: [0, 0, -1],
|
|
25792
|
+
kind: "revolute"
|
|
25793
|
+
}, measurements),
|
|
25794
|
+
apex: connectorFactory("bevel-apex", {
|
|
25795
|
+
origin: [0, 0, apexZ],
|
|
25796
|
+
axis: [0, 0, 1]
|
|
25797
|
+
}, measurements)
|
|
25798
|
+
});
|
|
25799
|
+
return attachGearMeta(shapeWithConnectors, {
|
|
24585
25800
|
...meta2,
|
|
24586
25801
|
kind: "bevel",
|
|
24587
25802
|
centered: false,
|
|
@@ -25063,6 +26278,70 @@ function faceGearPair(options) {
|
|
|
25063
26278
|
status: pairStatusFromDiagnostics(diagnostics)
|
|
25064
26279
|
};
|
|
25065
26280
|
}
|
|
26281
|
+
function gearRatio(teethA, teethB, options) {
|
|
26282
|
+
if (!Number.isFinite(teethA) || teethA <= 0) throw new Error("gearRatio: teethA must be > 0");
|
|
26283
|
+
if (!Number.isFinite(teethB) || teethB <= 0) throw new Error("gearRatio: teethB must be > 0");
|
|
26284
|
+
const sign = (options == null ? void 0 : options.internal) ? 1 : -1;
|
|
26285
|
+
return sign * teethA / teethB;
|
|
26286
|
+
}
|
|
26287
|
+
function rackRatio(module, pinionTeeth) {
|
|
26288
|
+
if (!Number.isFinite(module) || module <= 0) throw new Error("rackRatio: module must be > 0");
|
|
26289
|
+
if (!Number.isFinite(pinionTeeth) || pinionTeeth <= 0) throw new Error("rackRatio: pinionTeeth must be > 0");
|
|
26290
|
+
const pitchRadius = module * pinionTeeth / 2;
|
|
26291
|
+
return 180 / (Math.PI * pitchRadius);
|
|
26292
|
+
}
|
|
26293
|
+
function planetaryRatio(sunTeeth, ringTeeth) {
|
|
26294
|
+
if (!Number.isFinite(sunTeeth) || sunTeeth <= 0) throw new Error("planetaryRatio: sunTeeth must be > 0");
|
|
26295
|
+
if (!Number.isFinite(ringTeeth) || ringTeeth <= 0) throw new Error("planetaryRatio: ringTeeth must be > 0");
|
|
26296
|
+
return 1 + ringTeeth / sunTeeth;
|
|
26297
|
+
}
|
|
26298
|
+
function boltPattern(options) {
|
|
26299
|
+
const sizeData = METRIC_HOLE_TABLE[options.size];
|
|
26300
|
+
if (!sizeData) throw new Error(`boltPattern: unsupported size "${options.size}"`);
|
|
26301
|
+
const fit = options.fit ?? "normal";
|
|
26302
|
+
const dia = sizeData[fit];
|
|
26303
|
+
const segments = options.segments ?? 48;
|
|
26304
|
+
if (!Array.isArray(options.positions) || options.positions.length === 0) {
|
|
26305
|
+
throw new Error('boltPattern: "positions" must be a non-empty array of [x, y] pairs');
|
|
26306
|
+
}
|
|
26307
|
+
const positions = options.positions.map((p2, i) => {
|
|
26308
|
+
if (!Array.isArray(p2) || p2.length !== 2 || !Number.isFinite(p2[0]) || !Number.isFinite(p2[1])) {
|
|
26309
|
+
throw new Error(`boltPattern: position[${i}] must be a finite [x, y] pair`);
|
|
26310
|
+
}
|
|
26311
|
+
return [p2[0], p2[1]];
|
|
26312
|
+
});
|
|
26313
|
+
const xs = positions.map((p2) => p2[0]);
|
|
26314
|
+
const ys = positions.map((p2) => p2[1]);
|
|
26315
|
+
return {
|
|
26316
|
+
size: options.size,
|
|
26317
|
+
dia,
|
|
26318
|
+
positions,
|
|
26319
|
+
minX: Math.min(...xs),
|
|
26320
|
+
maxX: Math.max(...xs),
|
|
26321
|
+
minY: Math.min(...ys),
|
|
26322
|
+
maxY: Math.max(...ys),
|
|
26323
|
+
cut(shape, depth, cutOptions = {}) {
|
|
26324
|
+
if (!Number.isFinite(depth) || depth <= 0) {
|
|
26325
|
+
throw new Error("boltPattern.cut: depth must be > 0");
|
|
26326
|
+
}
|
|
26327
|
+
const from = cutOptions.from ?? 0;
|
|
26328
|
+
const cutter = fastenerHole({
|
|
26329
|
+
size: options.size,
|
|
26330
|
+
fit,
|
|
26331
|
+
depth,
|
|
26332
|
+
center: false,
|
|
26333
|
+
segments,
|
|
26334
|
+
counterbore: cutOptions.counterbore,
|
|
26335
|
+
countersink: cutOptions.countersink
|
|
26336
|
+
});
|
|
26337
|
+
let result = shape;
|
|
26338
|
+
for (const [x, y] of positions) {
|
|
26339
|
+
result = result.subtract(cutter.translate(x, y, from));
|
|
26340
|
+
}
|
|
26341
|
+
return result;
|
|
26342
|
+
}
|
|
26343
|
+
};
|
|
26344
|
+
}
|
|
25066
26345
|
function thread(diameter, pitch, length4, options) {
|
|
25067
26346
|
const r = diameter / 2;
|
|
25068
26347
|
const depth = (options == null ? void 0 : options.depth) ?? pitch * 0.35;
|
|
@@ -25222,7 +26501,11 @@ const partLibrary = {
|
|
|
25222
26501
|
gearPair,
|
|
25223
26502
|
bevelGearPair,
|
|
25224
26503
|
faceGearPair,
|
|
25225
|
-
sideGearPair
|
|
26504
|
+
sideGearPair,
|
|
26505
|
+
gearRatio,
|
|
26506
|
+
rackRatio,
|
|
26507
|
+
planetaryRatio,
|
|
26508
|
+
boltPattern
|
|
25226
26509
|
};
|
|
25227
26510
|
/**
|
|
25228
26511
|
* @license
|
|
@@ -36725,14 +38008,14 @@ class WoodBoard {
|
|
|
36725
38008
|
return this._withShape(this.shape.clone());
|
|
36726
38009
|
}
|
|
36727
38010
|
}
|
|
36728
|
-
function requireFinite$
|
|
38011
|
+
function requireFinite$5(value, name) {
|
|
36729
38012
|
if (!Number.isFinite(value)) {
|
|
36730
38013
|
throw new Error(`${name} must be a finite number, got ${value}`);
|
|
36731
38014
|
}
|
|
36732
38015
|
return value;
|
|
36733
38016
|
}
|
|
36734
38017
|
function requirePositive$2(value, name) {
|
|
36735
|
-
requireFinite$
|
|
38018
|
+
requireFinite$5(value, name);
|
|
36736
38019
|
if (value <= 0) {
|
|
36737
38020
|
throw new Error(`${name} must be positive, got ${value}`);
|
|
36738
38021
|
}
|
|
@@ -36767,10 +38050,10 @@ function dado(host, guest, opts) {
|
|
|
36767
38050
|
}
|
|
36768
38051
|
let fromBottom;
|
|
36769
38052
|
if (opts.fromBottom != null) {
|
|
36770
|
-
fromBottom = requireFinite$
|
|
38053
|
+
fromBottom = requireFinite$5(opts.fromBottom, "fromBottom");
|
|
36771
38054
|
} else {
|
|
36772
38055
|
fromBottom = host.height - opts.fromTop - channelWidth;
|
|
36773
|
-
requireFinite$
|
|
38056
|
+
requireFinite$5(fromBottom, "computed fromBottom");
|
|
36774
38057
|
}
|
|
36775
38058
|
let dadoLength = host.width;
|
|
36776
38059
|
let xOffset = 0;
|
|
@@ -36827,7 +38110,7 @@ function mortiseAndTenon(mortiseBoard, tenonBoard, opts) {
|
|
|
36827
38110
|
const style = o.style ?? "blind";
|
|
36828
38111
|
const fit = o.fit ?? "snug";
|
|
36829
38112
|
const clearance = clearanceForFit(fit);
|
|
36830
|
-
const cornerRadius = o.cornerRadius != null ? requireFinite$
|
|
38113
|
+
const cornerRadius = o.cornerRadius != null ? requireFinite$5(o.cornerRadius, "cornerRadius") : 0;
|
|
36831
38114
|
const tenonThickness = o.tenonThickness != null ? requirePositive$2(o.tenonThickness, "tenonThickness") : tenonBoard.thickness / 3;
|
|
36832
38115
|
const tenonWidth = o.tenonWidth != null ? requirePositive$2(o.tenonWidth, "tenonWidth") : Math.min(tenonBoard.height * 0.6, mortiseBoard.height * 0.8);
|
|
36833
38116
|
const tenonLength = o.tenonLength != null ? requirePositive$2(o.tenonLength, "tenonLength") : style === "through" ? mortiseBoard.thickness : mortiseBoard.thickness * 2 / 3;
|
|
@@ -36840,10 +38123,10 @@ function mortiseAndTenon(mortiseBoard, tenonBoard, opts) {
|
|
|
36840
38123
|
throw new Error("mortiseAndTenon: specify position.fromTop or position.fromBottom, not both");
|
|
36841
38124
|
}
|
|
36842
38125
|
if (o.position.fromTop != null) {
|
|
36843
|
-
requireFinite$
|
|
38126
|
+
requireFinite$5(o.position.fromTop, "position.fromTop");
|
|
36844
38127
|
mortiseCenterY = mortiseBoard.height / 2 - o.position.fromTop - mortiseH / 2;
|
|
36845
38128
|
} else if (o.position.fromBottom != null) {
|
|
36846
|
-
requireFinite$
|
|
38129
|
+
requireFinite$5(o.position.fromBottom, "position.fromBottom");
|
|
36847
38130
|
mortiseCenterY = -mortiseBoard.height / 2 + o.position.fromBottom + mortiseH / 2;
|
|
36848
38131
|
}
|
|
36849
38132
|
}
|
|
@@ -36913,6 +38196,94 @@ const Wood = {
|
|
|
36913
38196
|
*/
|
|
36914
38197
|
mortiseAndTenon
|
|
36915
38198
|
};
|
|
38199
|
+
let _collected$3 = null;
|
|
38200
|
+
function resetCameraTrajectory() {
|
|
38201
|
+
_collected$3 = null;
|
|
38202
|
+
}
|
|
38203
|
+
function getCollectedCameraTrajectory() {
|
|
38204
|
+
return _collected$3;
|
|
38205
|
+
}
|
|
38206
|
+
function isOrbitKeyframe(kf) {
|
|
38207
|
+
return "orbit" in kf;
|
|
38208
|
+
}
|
|
38209
|
+
function isCartesianKeyframe(kf) {
|
|
38210
|
+
return "position" in kf;
|
|
38211
|
+
}
|
|
38212
|
+
function requireFinite$4(value, label) {
|
|
38213
|
+
if (!Number.isFinite(value)) {
|
|
38214
|
+
throw new Error(`cameraTrajectory(): ${label} must be a finite number, got ${value}`);
|
|
38215
|
+
}
|
|
38216
|
+
}
|
|
38217
|
+
function validateOrbitKeyframe(kf, index) {
|
|
38218
|
+
requireFinite$4(kf.at, `keyframes[${index}].at`);
|
|
38219
|
+
requireFinite$4(kf.orbit.angle, `keyframes[${index}].orbit.angle`);
|
|
38220
|
+
requireFinite$4(kf.orbit.pitch, `keyframes[${index}].orbit.pitch`);
|
|
38221
|
+
requireFinite$4(kf.orbit.distance, `keyframes[${index}].orbit.distance`);
|
|
38222
|
+
}
|
|
38223
|
+
function validateCartesianKeyframe(kf, index) {
|
|
38224
|
+
requireFinite$4(kf.at, `keyframes[${index}].at`);
|
|
38225
|
+
if (!Array.isArray(kf.position) || kf.position.length !== 3) {
|
|
38226
|
+
throw new Error(`cameraTrajectory(): keyframes[${index}].position must be a 3-element array`);
|
|
38227
|
+
}
|
|
38228
|
+
if (!Array.isArray(kf.target) || kf.target.length !== 3) {
|
|
38229
|
+
throw new Error(`cameraTrajectory(): keyframes[${index}].target must be a 3-element array`);
|
|
38230
|
+
}
|
|
38231
|
+
for (let i = 0; i < 3; i++) {
|
|
38232
|
+
requireFinite$4(kf.position[i], `keyframes[${index}].position[${i}]`);
|
|
38233
|
+
requireFinite$4(kf.target[i], `keyframes[${index}].target[${i}]`);
|
|
38234
|
+
}
|
|
38235
|
+
}
|
|
38236
|
+
function validateKeyframeOrder(keyframes) {
|
|
38237
|
+
if (keyframes.length < 2) {
|
|
38238
|
+
throw new Error("cameraTrajectory(): keyframes must contain at least 2 entries");
|
|
38239
|
+
}
|
|
38240
|
+
for (let i = 0; i < keyframes.length; i++) {
|
|
38241
|
+
const at = keyframes[i].at;
|
|
38242
|
+
if (at < 0 || at > 1) {
|
|
38243
|
+
throw new Error(`cameraTrajectory(): keyframes[${i}].at must be in [0, 1], got ${at}`);
|
|
38244
|
+
}
|
|
38245
|
+
if (i > 0 && at < keyframes[i - 1].at) {
|
|
38246
|
+
throw new Error(
|
|
38247
|
+
`cameraTrajectory(): keyframes must be sorted by 'at' ascending — keyframes[${i - 1}].at=${keyframes[i - 1].at} > keyframes[${i}].at=${at}`
|
|
38248
|
+
);
|
|
38249
|
+
}
|
|
38250
|
+
}
|
|
38251
|
+
}
|
|
38252
|
+
function cameraTrajectory(defOrFn, options) {
|
|
38253
|
+
if (_collected$3 !== null) {
|
|
38254
|
+
console.warn("cameraTrajectory() called more than once — overwriting previous trajectory.");
|
|
38255
|
+
}
|
|
38256
|
+
if (typeof defOrFn === "function") {
|
|
38257
|
+
_collected$3 = {
|
|
38258
|
+
kind: "parametric",
|
|
38259
|
+
parametricFn: defOrFn,
|
|
38260
|
+
duration: options == null ? void 0 : options.duration,
|
|
38261
|
+
fps: options == null ? void 0 : options.fps
|
|
38262
|
+
};
|
|
38263
|
+
return;
|
|
38264
|
+
}
|
|
38265
|
+
const { keyframes, duration, fps, easing } = defOrFn;
|
|
38266
|
+
if (!Array.isArray(keyframes) || keyframes.length === 0) {
|
|
38267
|
+
throw new Error("cameraTrajectory(): keyframes must be a non-empty array");
|
|
38268
|
+
}
|
|
38269
|
+
validateKeyframeOrder(keyframes);
|
|
38270
|
+
const first = keyframes[0];
|
|
38271
|
+
if (isOrbitKeyframe(first)) {
|
|
38272
|
+
const orbitKeyframes = keyframes;
|
|
38273
|
+
for (let i = 0; i < orbitKeyframes.length; i++) {
|
|
38274
|
+
validateOrbitKeyframe(orbitKeyframes[i], i);
|
|
38275
|
+
}
|
|
38276
|
+
_collected$3 = { kind: "orbit-keyframes", orbitKeyframes, duration, fps, easing };
|
|
38277
|
+
} else if (isCartesianKeyframe(first)) {
|
|
38278
|
+
const cartesianKeyframes = keyframes;
|
|
38279
|
+
for (let i = 0; i < cartesianKeyframes.length; i++) {
|
|
38280
|
+
validateCartesianKeyframe(cartesianKeyframes[i], i);
|
|
38281
|
+
}
|
|
38282
|
+
_collected$3 = { kind: "cartesian-keyframes", cartesianKeyframes, duration, fps, easing };
|
|
38283
|
+
} else {
|
|
38284
|
+
throw new Error('cameraTrajectory(): each keyframe must have either an "orbit" or "position" property');
|
|
38285
|
+
}
|
|
38286
|
+
}
|
|
36916
38287
|
function resolveEdges(shape, edges) {
|
|
36917
38288
|
if (!edges) {
|
|
36918
38289
|
return selectEdges(shape);
|
|
@@ -37020,7 +38391,7 @@ function offsetSolid(shape, thickness) {
|
|
|
37020
38391
|
sources: ["offset-solid"]
|
|
37021
38392
|
});
|
|
37022
38393
|
}
|
|
37023
|
-
function requireFinite$
|
|
38394
|
+
function requireFinite$3(value, label) {
|
|
37024
38395
|
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
37025
38396
|
throw new Error(`${label} must be a finite number`);
|
|
37026
38397
|
}
|
|
@@ -37030,7 +38401,7 @@ function requireVec3(value, label) {
|
|
|
37030
38401
|
if (!Array.isArray(value) || value.length !== 3) {
|
|
37031
38402
|
throw new Error(`${label} must be [x, y, z]`);
|
|
37032
38403
|
}
|
|
37033
|
-
return [requireFinite$
|
|
38404
|
+
return [requireFinite$3(value[0], `${label}[0]`), requireFinite$3(value[1], `${label}[1]`), requireFinite$3(value[2], `${label}[2]`)];
|
|
37034
38405
|
}
|
|
37035
38406
|
function requireColor(value, label) {
|
|
37036
38407
|
if (typeof value !== "string" || !value.trim()) {
|
|
@@ -37058,7 +38429,7 @@ function validateCamera(cam, label) {
|
|
|
37058
38429
|
if (cam.target !== void 0) out.target = requireVec3(cam.target, `${label}.target`);
|
|
37059
38430
|
if (cam.up !== void 0) out.up = requireVec3(cam.up, `${label}.up`);
|
|
37060
38431
|
if (cam.fov !== void 0) {
|
|
37061
|
-
out.fov = requireFinite$
|
|
38432
|
+
out.fov = requireFinite$3(cam.fov, `${label}.fov`);
|
|
37062
38433
|
if (out.fov <= 0 || out.fov >= 180) throw new Error(`${label}.fov must be between 0 and 180`);
|
|
37063
38434
|
}
|
|
37064
38435
|
if (cam.type !== void 0) {
|
|
@@ -37076,15 +38447,15 @@ function validateLight(light, label) {
|
|
|
37076
38447
|
}
|
|
37077
38448
|
const out = { type: light.type };
|
|
37078
38449
|
if (light.color !== void 0) out.color = requireColor(light.color, `${label}.color`);
|
|
37079
|
-
if (light.intensity !== void 0) out.intensity = requireFinite$
|
|
38450
|
+
if (light.intensity !== void 0) out.intensity = requireFinite$3(light.intensity, `${label}.intensity`);
|
|
37080
38451
|
if (light.position !== void 0) out.position = requireVec3(light.position, `${label}.position`);
|
|
37081
38452
|
if (light.target !== void 0) out.target = requireVec3(light.target, `${label}.target`);
|
|
37082
38453
|
if (light.groundColor !== void 0) out.groundColor = requireColor(light.groundColor, `${label}.groundColor`);
|
|
37083
38454
|
if (light.skyColor !== void 0) out.skyColor = requireColor(light.skyColor, `${label}.skyColor`);
|
|
37084
|
-
if (light.angle !== void 0) out.angle = requireFinite$
|
|
37085
|
-
if (light.penumbra !== void 0) out.penumbra = requireFinite$
|
|
37086
|
-
if (light.decay !== void 0) out.decay = requireFinite$
|
|
37087
|
-
if (light.distance !== void 0) out.distance = requireFinite$
|
|
38455
|
+
if (light.angle !== void 0) out.angle = requireFinite$3(light.angle, `${label}.angle`);
|
|
38456
|
+
if (light.penumbra !== void 0) out.penumbra = requireFinite$3(light.penumbra, `${label}.penumbra`);
|
|
38457
|
+
if (light.decay !== void 0) out.decay = requireFinite$3(light.decay, `${label}.decay`);
|
|
38458
|
+
if (light.distance !== void 0) out.distance = requireFinite$3(light.distance, `${label}.distance`);
|
|
37088
38459
|
if (light.castShadow !== void 0) {
|
|
37089
38460
|
if (typeof light.castShadow !== "boolean") throw new Error(`${label}.castShadow must be a boolean`);
|
|
37090
38461
|
out.castShadow = light.castShadow;
|
|
@@ -37099,7 +38470,7 @@ function validateEnvironment(env, label) {
|
|
|
37099
38470
|
}
|
|
37100
38471
|
out.preset = env.preset;
|
|
37101
38472
|
}
|
|
37102
|
-
if (env.intensity !== void 0) out.intensity = requireFinite$
|
|
38473
|
+
if (env.intensity !== void 0) out.intensity = requireFinite$3(env.intensity, `${label}.intensity`);
|
|
37103
38474
|
if (env.background !== void 0) {
|
|
37104
38475
|
if (typeof env.background !== "boolean") throw new Error(`${label}.background must be a boolean`);
|
|
37105
38476
|
out.background = env.background;
|
|
@@ -37109,9 +38480,9 @@ function validateEnvironment(env, label) {
|
|
|
37109
38480
|
function validateFog(fog, label) {
|
|
37110
38481
|
const out = {};
|
|
37111
38482
|
if (fog.color !== void 0) out.color = requireColor(fog.color, `${label}.color`);
|
|
37112
|
-
if (fog.near !== void 0) out.near = requireFinite$
|
|
37113
|
-
if (fog.far !== void 0) out.far = requireFinite$
|
|
37114
|
-
if (fog.density !== void 0) out.density = requireFinite$
|
|
38483
|
+
if (fog.near !== void 0) out.near = requireFinite$3(fog.near, `${label}.near`);
|
|
38484
|
+
if (fog.far !== void 0) out.far = requireFinite$3(fog.far, `${label}.far`);
|
|
38485
|
+
if (fog.density !== void 0) out.density = requireFinite$3(fog.density, `${label}.density`);
|
|
37115
38486
|
return out;
|
|
37116
38487
|
}
|
|
37117
38488
|
function validatePostProcessing(pp, label) {
|
|
@@ -37119,23 +38490,23 @@ function validatePostProcessing(pp, label) {
|
|
|
37119
38490
|
if (pp.bloom !== void 0) {
|
|
37120
38491
|
if (!pp.bloom || typeof pp.bloom !== "object") throw new Error(`${label}.bloom must be an object`);
|
|
37121
38492
|
out.bloom = {};
|
|
37122
|
-
if (pp.bloom.intensity !== void 0) out.bloom.intensity = requireFinite$
|
|
37123
|
-
if (pp.bloom.threshold !== void 0) out.bloom.threshold = requireFinite$
|
|
37124
|
-
if (pp.bloom.radius !== void 0) out.bloom.radius = requireFinite$
|
|
38493
|
+
if (pp.bloom.intensity !== void 0) out.bloom.intensity = requireFinite$3(pp.bloom.intensity, `${label}.bloom.intensity`);
|
|
38494
|
+
if (pp.bloom.threshold !== void 0) out.bloom.threshold = requireFinite$3(pp.bloom.threshold, `${label}.bloom.threshold`);
|
|
38495
|
+
if (pp.bloom.radius !== void 0) out.bloom.radius = requireFinite$3(pp.bloom.radius, `${label}.bloom.radius`);
|
|
37125
38496
|
}
|
|
37126
38497
|
if (pp.vignette !== void 0) {
|
|
37127
38498
|
if (!pp.vignette || typeof pp.vignette !== "object") throw new Error(`${label}.vignette must be an object`);
|
|
37128
38499
|
out.vignette = {};
|
|
37129
|
-
if (pp.vignette.darkness !== void 0) out.vignette.darkness = requireFinite$
|
|
37130
|
-
if (pp.vignette.offset !== void 0) out.vignette.offset = requireFinite$
|
|
38500
|
+
if (pp.vignette.darkness !== void 0) out.vignette.darkness = requireFinite$3(pp.vignette.darkness, `${label}.vignette.darkness`);
|
|
38501
|
+
if (pp.vignette.offset !== void 0) out.vignette.offset = requireFinite$3(pp.vignette.offset, `${label}.vignette.offset`);
|
|
37131
38502
|
}
|
|
37132
38503
|
if (pp.grain !== void 0) {
|
|
37133
38504
|
if (!pp.grain || typeof pp.grain !== "object") throw new Error(`${label}.grain must be an object`);
|
|
37134
38505
|
out.grain = {};
|
|
37135
|
-
if (pp.grain.intensity !== void 0) out.grain.intensity = requireFinite$
|
|
38506
|
+
if (pp.grain.intensity !== void 0) out.grain.intensity = requireFinite$3(pp.grain.intensity, `${label}.grain.intensity`);
|
|
37136
38507
|
}
|
|
37137
38508
|
if (pp.toneMappingExposure !== void 0) {
|
|
37138
|
-
out.toneMappingExposure = requireFinite$
|
|
38509
|
+
out.toneMappingExposure = requireFinite$3(pp.toneMappingExposure, `${label}.toneMappingExposure`);
|
|
37139
38510
|
}
|
|
37140
38511
|
return out;
|
|
37141
38512
|
}
|
|
@@ -37146,7 +38517,7 @@ function validateGround(ground, label) {
|
|
|
37146
38517
|
out.visible = ground.visible;
|
|
37147
38518
|
}
|
|
37148
38519
|
if (ground.color !== void 0) out.color = requireColor(ground.color, `${label}.color`);
|
|
37149
|
-
if (ground.offset !== void 0) out.offset = requireFinite$
|
|
38520
|
+
if (ground.offset !== void 0) out.offset = requireFinite$3(ground.offset, `${label}.offset`);
|
|
37150
38521
|
if (ground.receiveShadow !== void 0) {
|
|
37151
38522
|
if (typeof ground.receiveShadow !== "boolean") throw new Error(`${label}.receiveShadow must be a boolean`);
|
|
37152
38523
|
out.receiveShadow = ground.receiveShadow;
|
|
@@ -37156,31 +38527,31 @@ function validateGround(ground, label) {
|
|
|
37156
38527
|
function validateCapture(cap, label) {
|
|
37157
38528
|
const out = {};
|
|
37158
38529
|
if (cap.framesPerTurn !== void 0) {
|
|
37159
|
-
out.framesPerTurn = requireFinite$
|
|
38530
|
+
out.framesPerTurn = requireFinite$3(cap.framesPerTurn, `${label}.framesPerTurn`);
|
|
37160
38531
|
if (out.framesPerTurn < 12 || out.framesPerTurn > 720) {
|
|
37161
38532
|
throw new Error(`${label}.framesPerTurn must be between 12 and 720`);
|
|
37162
38533
|
}
|
|
37163
38534
|
}
|
|
37164
38535
|
if (cap.holdFrames !== void 0) {
|
|
37165
|
-
out.holdFrames = requireFinite$
|
|
38536
|
+
out.holdFrames = requireFinite$3(cap.holdFrames, `${label}.holdFrames`);
|
|
37166
38537
|
if (out.holdFrames < 0 || out.holdFrames > 300) {
|
|
37167
38538
|
throw new Error(`${label}.holdFrames must be between 0 and 300`);
|
|
37168
38539
|
}
|
|
37169
38540
|
}
|
|
37170
38541
|
if (cap.pitchDeg !== void 0) {
|
|
37171
|
-
out.pitchDeg = requireFinite$
|
|
38542
|
+
out.pitchDeg = requireFinite$3(cap.pitchDeg, `${label}.pitchDeg`);
|
|
37172
38543
|
if (out.pitchDeg < -80 || out.pitchDeg > 80) {
|
|
37173
38544
|
throw new Error(`${label}.pitchDeg must be between -80 and 80`);
|
|
37174
38545
|
}
|
|
37175
38546
|
}
|
|
37176
38547
|
if (cap.fps !== void 0) {
|
|
37177
|
-
out.fps = requireFinite$
|
|
38548
|
+
out.fps = requireFinite$3(cap.fps, `${label}.fps`);
|
|
37178
38549
|
if (out.fps < 1 || out.fps > 60) {
|
|
37179
38550
|
throw new Error(`${label}.fps must be between 1 and 60`);
|
|
37180
38551
|
}
|
|
37181
38552
|
}
|
|
37182
38553
|
if (cap.size !== void 0) {
|
|
37183
|
-
out.size = requireFinite$
|
|
38554
|
+
out.size = requireFinite$3(cap.size, `${label}.size`);
|
|
37184
38555
|
if (out.size < 1) {
|
|
37185
38556
|
throw new Error(`${label}.size must be positive`);
|
|
37186
38557
|
}
|
|
@@ -37881,6 +39252,16 @@ function buildProjectionReplayContext(plan) {
|
|
|
37881
39252
|
ok: false,
|
|
37882
39253
|
reason: "projection replay cannot derive a planar projection basis from fromSlices shapes."
|
|
37883
39254
|
};
|
|
39255
|
+
case "nurbsSurface":
|
|
39256
|
+
return {
|
|
39257
|
+
ok: false,
|
|
39258
|
+
reason: "projection replay cannot derive a planar projection basis from NURBS surface shapes."
|
|
39259
|
+
};
|
|
39260
|
+
case "importedStep":
|
|
39261
|
+
return {
|
|
39262
|
+
ok: false,
|
|
39263
|
+
reason: "projection replay cannot derive a planar projection basis from imported STEP files."
|
|
39264
|
+
};
|
|
37884
39265
|
default:
|
|
37885
39266
|
assertExhaustive(plan);
|
|
37886
39267
|
}
|
|
@@ -42249,6 +43630,138 @@ function hermiteTransitionG2(a, b) {
|
|
|
42249
43630
|
{ point: b.point, tangent: b.tangent, curvature: b.curvature, weight: b.weight }
|
|
42250
43631
|
);
|
|
42251
43632
|
}
|
|
43633
|
+
function requireFinite$2(v, label) {
|
|
43634
|
+
if (!Number.isFinite(v)) throw new Error(`nurbs3d: ${label} must be finite, got ${v}`);
|
|
43635
|
+
}
|
|
43636
|
+
class NurbsCurve3D {
|
|
43637
|
+
constructor(points, options = {}) {
|
|
43638
|
+
__publicField(this, "controlPoints");
|
|
43639
|
+
__publicField(this, "weights");
|
|
43640
|
+
__publicField(this, "knots");
|
|
43641
|
+
__publicField(this, "degree");
|
|
43642
|
+
__publicField(this, "closed");
|
|
43643
|
+
const n = points.length;
|
|
43644
|
+
const degree = options.degree ?? 3;
|
|
43645
|
+
if (degree < 1) throw new Error("nurbs3d: degree must be ≥ 1");
|
|
43646
|
+
if (n < degree + 1) throw new Error(`nurbs3d: need at least ${degree + 1} control points for degree ${degree}, got ${n}`);
|
|
43647
|
+
for (let i = 0; i < n; i++) {
|
|
43648
|
+
requireFinite$2(points[i][0], `controlPoints[${i}][0]`);
|
|
43649
|
+
requireFinite$2(points[i][1], `controlPoints[${i}][1]`);
|
|
43650
|
+
requireFinite$2(points[i][2], `controlPoints[${i}][2]`);
|
|
43651
|
+
}
|
|
43652
|
+
const weights = options.weights ?? new Array(n).fill(1);
|
|
43653
|
+
if (weights.length !== n) throw new Error(`nurbs3d: weights.length (${weights.length}) must equal controlPoints.length (${n})`);
|
|
43654
|
+
for (let i = 0; i < n; i++) {
|
|
43655
|
+
requireFinite$2(weights[i], `weights[${i}]`);
|
|
43656
|
+
if (weights[i] <= 0) throw new Error(`nurbs3d: weights[${i}] must be > 0, got ${weights[i]}`);
|
|
43657
|
+
}
|
|
43658
|
+
const expectedKnotLength = n + degree + 1;
|
|
43659
|
+
const knots = options.knots ?? generateClampedKnots(n, degree);
|
|
43660
|
+
if (knots.length !== expectedKnotLength) {
|
|
43661
|
+
throw new Error(`nurbs3d: knots.length (${knots.length}) must be controlPoints.length + degree + 1 (${expectedKnotLength})`);
|
|
43662
|
+
}
|
|
43663
|
+
for (let i = 0; i < knots.length; i++) {
|
|
43664
|
+
requireFinite$2(knots[i], `knots[${i}]`);
|
|
43665
|
+
if (i > 0 && knots[i] < knots[i - 1]) {
|
|
43666
|
+
throw new Error(`nurbs3d: knot vector must be non-decreasing, but knots[${i - 1}]=${knots[i - 1]} > knots[${i}]=${knots[i]}`);
|
|
43667
|
+
}
|
|
43668
|
+
}
|
|
43669
|
+
this.controlPoints = points.map(([x, y, z]) => [x, y, z]);
|
|
43670
|
+
this.weights = [...weights];
|
|
43671
|
+
this.knots = [...knots];
|
|
43672
|
+
this.degree = degree;
|
|
43673
|
+
this.closed = options.closed ?? false;
|
|
43674
|
+
}
|
|
43675
|
+
/**
|
|
43676
|
+
* Evaluate the curve at parameter t ∈ [0, 1].
|
|
43677
|
+
* Uses De Boor's algorithm — exact, O(degree²).
|
|
43678
|
+
*/
|
|
43679
|
+
pointAt(t) {
|
|
43680
|
+
const u = remapToKnotDomain(t, this.controlPoints.length, this.degree, this.knots);
|
|
43681
|
+
return deBoor3D(this.controlPoints, this.weights, this.knots, this.degree, u);
|
|
43682
|
+
}
|
|
43683
|
+
/**
|
|
43684
|
+
* Evaluate the unit tangent vector at parameter t ∈ [0, 1].
|
|
43685
|
+
*/
|
|
43686
|
+
tangentAt(t) {
|
|
43687
|
+
const u = remapToKnotDomain(t, this.controlPoints.length, this.degree, this.knots);
|
|
43688
|
+
const d = deBoor3DDeriv(this.controlPoints, this.weights, this.knots, this.degree, u);
|
|
43689
|
+
const len = Math.sqrt(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]);
|
|
43690
|
+
if (len < 1e-12) return [0, 0, 1];
|
|
43691
|
+
return [d[0] / len, d[1] / len, d[2] / len];
|
|
43692
|
+
}
|
|
43693
|
+
/**
|
|
43694
|
+
* Sample the curve uniformly at `count` points.
|
|
43695
|
+
*/
|
|
43696
|
+
sample(count = 48) {
|
|
43697
|
+
return sampleNurbs3D(this.controlPoints, this.weights, this.knots, this.degree, Math.max(2, count));
|
|
43698
|
+
}
|
|
43699
|
+
/**
|
|
43700
|
+
* Sample with adaptive density — more points in high-curvature regions.
|
|
43701
|
+
*/
|
|
43702
|
+
sampleAdaptive(minCount = 32, maxCount = 128) {
|
|
43703
|
+
const probeCount = Math.max(minCount, 64);
|
|
43704
|
+
const curvatures = [];
|
|
43705
|
+
for (let i = 0; i <= probeCount; i++) {
|
|
43706
|
+
curvatures.push(this.estimateCurvature(i / probeCount));
|
|
43707
|
+
}
|
|
43708
|
+
const cumulative = [0];
|
|
43709
|
+
for (let i = 1; i < curvatures.length; i++) {
|
|
43710
|
+
const avgCurv = (curvatures[i - 1] + curvatures[i]) / 2;
|
|
43711
|
+
cumulative.push(cumulative[i - 1] + 1 + avgCurv);
|
|
43712
|
+
}
|
|
43713
|
+
const total = cumulative[cumulative.length - 1];
|
|
43714
|
+
const targetCount = Math.min(maxCount, Math.max(minCount, Math.round(minCount * 1.5)));
|
|
43715
|
+
const pts = [this.pointAt(0)];
|
|
43716
|
+
let probeIdx = 0;
|
|
43717
|
+
for (let i = 1; i < targetCount; i++) {
|
|
43718
|
+
const target = i / targetCount * total;
|
|
43719
|
+
while (probeIdx < cumulative.length - 1 && cumulative[probeIdx + 1] < target) {
|
|
43720
|
+
probeIdx++;
|
|
43721
|
+
}
|
|
43722
|
+
const frac = (target - cumulative[probeIdx]) / (cumulative[probeIdx + 1] - cumulative[probeIdx]);
|
|
43723
|
+
const t = (probeIdx + frac) / probeCount;
|
|
43724
|
+
pts.push(this.pointAt(t));
|
|
43725
|
+
}
|
|
43726
|
+
pts.push(this.pointAt(1));
|
|
43727
|
+
return pts;
|
|
43728
|
+
}
|
|
43729
|
+
/**
|
|
43730
|
+
* Approximate arc length by summing polyline segment lengths.
|
|
43731
|
+
*/
|
|
43732
|
+
length(samples = 100) {
|
|
43733
|
+
const pts = this.sample(Math.max(10, samples));
|
|
43734
|
+
let len = 0;
|
|
43735
|
+
for (let i = 1; i < pts.length; i++) {
|
|
43736
|
+
const dx = pts[i][0] - pts[i - 1][0];
|
|
43737
|
+
const dy = pts[i][1] - pts[i - 1][1];
|
|
43738
|
+
const dz = pts[i][2] - pts[i - 1][2];
|
|
43739
|
+
len += Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
43740
|
+
}
|
|
43741
|
+
return len;
|
|
43742
|
+
}
|
|
43743
|
+
/** Convert to a format compatible with sweep() path input. */
|
|
43744
|
+
toPolyline(samples = 64) {
|
|
43745
|
+
return this.sampleAdaptive(Math.max(16, samples), samples * 2);
|
|
43746
|
+
}
|
|
43747
|
+
estimateCurvature(t) {
|
|
43748
|
+
const eps = 1e-4;
|
|
43749
|
+
const t0 = Math.max(0, t - eps);
|
|
43750
|
+
const t1 = Math.min(1, t + eps);
|
|
43751
|
+
const tm = (t0 + t1) / 2;
|
|
43752
|
+
const p0 = this.pointAt(t0);
|
|
43753
|
+
const pm = this.pointAt(tm);
|
|
43754
|
+
const p1 = this.pointAt(t1);
|
|
43755
|
+
const dt = (t1 - t0) / 2;
|
|
43756
|
+
const d2x = (p0[0] - 2 * pm[0] + p1[0]) / (dt * dt);
|
|
43757
|
+
const d2y = (p0[1] - 2 * pm[1] + p1[1]) / (dt * dt);
|
|
43758
|
+
const d2z = (p0[2] - 2 * pm[2] + p1[2]) / (dt * dt);
|
|
43759
|
+
return Math.sqrt(d2x * d2x + d2y * d2y + d2z * d2z);
|
|
43760
|
+
}
|
|
43761
|
+
}
|
|
43762
|
+
function nurbs3d(points, options) {
|
|
43763
|
+
return new NurbsCurve3D(points, options);
|
|
43764
|
+
}
|
|
42252
43765
|
function clamp$4(v, lo, hi) {
|
|
42253
43766
|
return Math.max(lo, Math.min(hi, v));
|
|
42254
43767
|
}
|
|
@@ -42590,6 +44103,19 @@ function buildPathPlan(path2) {
|
|
|
42590
44103
|
sampleForBounds: (samples) => path2.sample(samples)
|
|
42591
44104
|
};
|
|
42592
44105
|
}
|
|
44106
|
+
if (path2 instanceof NurbsCurve3D) {
|
|
44107
|
+
return {
|
|
44108
|
+
plan: {
|
|
44109
|
+
kind: "nurbs",
|
|
44110
|
+
controlPoints: path2.controlPoints.map(([x, y, z]) => [x, y, z]),
|
|
44111
|
+
weights: [...path2.weights],
|
|
44112
|
+
knots: [...path2.knots],
|
|
44113
|
+
degree: path2.degree,
|
|
44114
|
+
closed: path2.closed
|
|
44115
|
+
},
|
|
44116
|
+
sampleForBounds: (samples) => path2.sample(samples)
|
|
44117
|
+
};
|
|
44118
|
+
}
|
|
42593
44119
|
throw new Error("sweep: unsupported path type");
|
|
42594
44120
|
}
|
|
42595
44121
|
function sweep(profile, path2, options = {}) {
|
|
@@ -57948,6 +59474,16 @@ function sheetMetal(options) {
|
|
|
57948
59474
|
deriveSheetMetalModel(model);
|
|
57949
59475
|
return new SheetMetalPart(model);
|
|
57950
59476
|
}
|
|
59477
|
+
function importStepFromBuffer(fileData, displayName = "import.step") {
|
|
59478
|
+
return buildShapeFromCompilePlan(
|
|
59479
|
+
createOwnedShapeCompilePlan(
|
|
59480
|
+
{ kind: "importedStep", filePath: displayName, fileData },
|
|
59481
|
+
"importStep"
|
|
59482
|
+
),
|
|
59483
|
+
void 0,
|
|
59484
|
+
{ fidelity: "exact", sources: ["imported"] }
|
|
59485
|
+
);
|
|
59486
|
+
}
|
|
57951
59487
|
let collectedSheetStock = [];
|
|
57952
59488
|
let sheetStockCounter = 0;
|
|
57953
59489
|
function resetSheetStock() {
|
|
@@ -270813,6 +272349,75 @@ function resolveErrorLocation(stack, compiledFiles) {
|
|
|
270813
272349
|
column: parseInt(anonymousMatch[2], 10)
|
|
270814
272350
|
};
|
|
270815
272351
|
}
|
|
272352
|
+
function statementTargetVar(stmt) {
|
|
272353
|
+
if (typescriptExports.isVariableStatement(stmt)) {
|
|
272354
|
+
const decl = stmt.declarationList.declarations[0];
|
|
272355
|
+
if (decl && typescriptExports.isIdentifier(decl.name)) return decl.name.text;
|
|
272356
|
+
}
|
|
272357
|
+
if (typescriptExports.isExpressionStatement(stmt)) {
|
|
272358
|
+
const expr = stmt.expression;
|
|
272359
|
+
if (typescriptExports.isBinaryExpression(expr) && expr.operatorToken.kind === typescriptExports.SyntaxKind.EqualsToken && typescriptExports.isIdentifier(expr.left)) {
|
|
272360
|
+
return expr.left.text;
|
|
272361
|
+
}
|
|
272362
|
+
}
|
|
272363
|
+
return null;
|
|
272364
|
+
}
|
|
272365
|
+
function collectReferencedNames(node, exclude, names) {
|
|
272366
|
+
if (typescriptExports.isIdentifier(node) && !exclude.has(node)) {
|
|
272367
|
+
names.add(node.text);
|
|
272368
|
+
}
|
|
272369
|
+
typescriptExports.forEachChild(node, (child) => collectReferencedNames(child, exclude, names));
|
|
272370
|
+
}
|
|
272371
|
+
function extractUnusedTopLevelVarNames(code) {
|
|
272372
|
+
const sourceFile = typescriptExports.createSourceFile("__implicit.js", code, typescriptExports.ScriptTarget.ES2020, false, typescriptExports.ScriptKind.JS);
|
|
272373
|
+
const declaredNames = [];
|
|
272374
|
+
const collectBindingNames = (node) => {
|
|
272375
|
+
if (typescriptExports.isIdentifier(node)) {
|
|
272376
|
+
declaredNames.push(node.text);
|
|
272377
|
+
} else if (typescriptExports.isObjectBindingPattern(node) || typescriptExports.isArrayBindingPattern(node)) {
|
|
272378
|
+
for (const element of node.elements) {
|
|
272379
|
+
if (typescriptExports.isBindingElement(element)) {
|
|
272380
|
+
collectBindingNames(element.name);
|
|
272381
|
+
}
|
|
272382
|
+
}
|
|
272383
|
+
}
|
|
272384
|
+
};
|
|
272385
|
+
for (const statement of sourceFile.statements) {
|
|
272386
|
+
if (typescriptExports.isVariableStatement(statement)) {
|
|
272387
|
+
for (const decl of statement.declarationList.declarations) {
|
|
272388
|
+
collectBindingNames(decl.name);
|
|
272389
|
+
}
|
|
272390
|
+
} else if (typescriptExports.isFunctionDeclaration(statement) && statement.name) {
|
|
272391
|
+
declaredNames.push(statement.name.text);
|
|
272392
|
+
}
|
|
272393
|
+
}
|
|
272394
|
+
const excluded = /* @__PURE__ */ new Set(["exports", "module", "require", "__filename", "__dirname"]);
|
|
272395
|
+
const topLevelNames = new Set(declaredNames.filter((n) => !excluded.has(n)));
|
|
272396
|
+
if (topLevelNames.size === 0) return [];
|
|
272397
|
+
const usedByOthers = /* @__PURE__ */ new Set();
|
|
272398
|
+
for (const statement of sourceFile.statements) {
|
|
272399
|
+
const target = statementTargetVar(statement);
|
|
272400
|
+
const refs = /* @__PURE__ */ new Set();
|
|
272401
|
+
const lhsNodes = /* @__PURE__ */ new Set();
|
|
272402
|
+
if (typescriptExports.isVariableStatement(statement)) {
|
|
272403
|
+
for (const decl of statement.declarationList.declarations) {
|
|
272404
|
+
if (typescriptExports.isIdentifier(decl.name)) lhsNodes.add(decl.name);
|
|
272405
|
+
}
|
|
272406
|
+
} else if (typescriptExports.isExpressionStatement(statement)) {
|
|
272407
|
+
const expr = statement.expression;
|
|
272408
|
+
if (typescriptExports.isBinaryExpression(expr) && typescriptExports.isIdentifier(expr.left)) {
|
|
272409
|
+
lhsNodes.add(expr.left);
|
|
272410
|
+
}
|
|
272411
|
+
}
|
|
272412
|
+
collectReferencedNames(statement, lhsNodes, refs);
|
|
272413
|
+
for (const ref of refs) {
|
|
272414
|
+
if (topLevelNames.has(ref) && ref !== target) {
|
|
272415
|
+
usedByOthers.add(ref);
|
|
272416
|
+
}
|
|
272417
|
+
}
|
|
272418
|
+
}
|
|
272419
|
+
return declaredNames.filter((n) => topLevelNames.has(n) && !usedByOthers.has(n));
|
|
272420
|
+
}
|
|
270816
272421
|
function createForgeRuntimeModule(bindings) {
|
|
270817
272422
|
const runtime = { ...bindings };
|
|
270818
272423
|
Object.defineProperty(runtime, "__esModule", { value: true });
|
|
@@ -270867,6 +272472,34 @@ function finalizeForgeJsImport(moduleExports, importedDims) {
|
|
|
270867
272472
|
if (importedDims.length === 0) return base;
|
|
270868
272473
|
return setShapeDimensions(base, [...getShapeDimensions(base), ...importedDims]);
|
|
270869
272474
|
}
|
|
272475
|
+
function rejectPathTraversal(fnName, userPath, resolvedPath) {
|
|
272476
|
+
if (resolvedPath.startsWith("..")) {
|
|
272477
|
+
throw new Error(`${fnName}("${userPath}"): path traversal blocked — resolved path escapes the project directory`);
|
|
272478
|
+
}
|
|
272479
|
+
}
|
|
272480
|
+
let _constructorLockdownDepth = 0;
|
|
272481
|
+
let _origConstructorDescriptor;
|
|
272482
|
+
function withConstructorChainLockdown(fn) {
|
|
272483
|
+
_constructorLockdownDepth++;
|
|
272484
|
+
if (_constructorLockdownDepth === 1) {
|
|
272485
|
+
_origConstructorDescriptor = Object.getOwnPropertyDescriptor(Function.prototype, "constructor");
|
|
272486
|
+
Object.defineProperty(Function.prototype, "constructor", {
|
|
272487
|
+
get() {
|
|
272488
|
+
throw new Error("Dynamic code generation is not allowed in ForgeCAD scripts");
|
|
272489
|
+
},
|
|
272490
|
+
configurable: true
|
|
272491
|
+
});
|
|
272492
|
+
}
|
|
272493
|
+
try {
|
|
272494
|
+
return fn();
|
|
272495
|
+
} finally {
|
|
272496
|
+
_constructorLockdownDepth--;
|
|
272497
|
+
if (_constructorLockdownDepth === 0 && _origConstructorDescriptor) {
|
|
272498
|
+
Object.defineProperty(Function.prototype, "constructor", _origConstructorDescriptor);
|
|
272499
|
+
_origConstructorDescriptor = void 0;
|
|
272500
|
+
}
|
|
272501
|
+
}
|
|
272502
|
+
}
|
|
270870
272503
|
function executeFile(code, fileName, allFiles, visited, scope = {}, options, executionMode = "script", moduleCacheEntry) {
|
|
270871
272504
|
const trackCircularImports = executionMode === "script";
|
|
270872
272505
|
if (trackCircularImports) {
|
|
@@ -270914,6 +272547,7 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
|
|
|
270914
272547
|
throw new Error("importMesh() requires a non-empty file path string");
|
|
270915
272548
|
}
|
|
270916
272549
|
const resolvedPath = resolveImportPath(fileName, name.trim());
|
|
272550
|
+
rejectPathTraversal("importMesh", name, resolvedPath);
|
|
270917
272551
|
const format = detectMeshFormat(resolvedPath);
|
|
270918
272552
|
if (!format) {
|
|
270919
272553
|
const ext = resolvedPath.split(".").pop() ?? "";
|
|
@@ -270943,6 +272577,25 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
|
|
|
270943
272577
|
sources: ["imported"]
|
|
270944
272578
|
});
|
|
270945
272579
|
};
|
|
272580
|
+
const importStep = (name) => {
|
|
272581
|
+
var _a3;
|
|
272582
|
+
if (typeof name !== "string" || name.trim().length === 0) {
|
|
272583
|
+
throw new Error("importStep() requires a non-empty file path string");
|
|
272584
|
+
}
|
|
272585
|
+
const resolvedPath = resolveImportPath(fileName, name.trim());
|
|
272586
|
+
rejectPathTraversal("importStep", name, resolvedPath);
|
|
272587
|
+
const ext = ((_a3 = resolvedPath.split(".").pop()) == null ? void 0 : _a3.toLowerCase()) ?? "";
|
|
272588
|
+
if (ext !== "step" && ext !== "stp") {
|
|
272589
|
+
throw new Error(`importStep("${name}"): unsupported extension ".${ext}". Expected .step or .stp`);
|
|
272590
|
+
}
|
|
272591
|
+
if (!options.readBinaryFile) {
|
|
272592
|
+
throw new Error(
|
|
272593
|
+
`importStep("${name}"): binary file reading is not available in this environment. Provide a readBinaryFile callback in RunScriptOptions.`
|
|
272594
|
+
);
|
|
272595
|
+
}
|
|
272596
|
+
const fileData = options.readBinaryFile(resolvedPath);
|
|
272597
|
+
return importStepFromBuffer(fileData, name);
|
|
272598
|
+
};
|
|
270946
272599
|
const wrappedUnion = union;
|
|
270947
272600
|
const wrappedDifference = difference;
|
|
270948
272601
|
const wrappedIntersection = intersection;
|
|
@@ -271006,6 +272659,10 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
|
|
|
271006
272659
|
filletCorners,
|
|
271007
272660
|
chamfer2d,
|
|
271008
272661
|
Curve3D,
|
|
272662
|
+
NurbsCurve3D,
|
|
272663
|
+
nurbs3d,
|
|
272664
|
+
NurbsSurface,
|
|
272665
|
+
nurbsSurface,
|
|
271009
272666
|
spline2d,
|
|
271010
272667
|
spline3d,
|
|
271011
272668
|
loft,
|
|
@@ -271019,10 +272676,13 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
|
|
|
271019
272676
|
surfacePatch,
|
|
271020
272677
|
sheetMetal,
|
|
271021
272678
|
SheetMetalPart,
|
|
272679
|
+
Param,
|
|
271022
272680
|
param,
|
|
271023
272681
|
boolParam,
|
|
271024
272682
|
choiceParam,
|
|
271025
|
-
listParam
|
|
272683
|
+
listParam: () => {
|
|
272684
|
+
throw new Error("listParam() has been renamed to Param.list(). Update your script.");
|
|
272685
|
+
},
|
|
271026
272686
|
sdf,
|
|
271027
272687
|
Shape,
|
|
271028
272688
|
Sketch,
|
|
@@ -271052,6 +272712,7 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
|
|
|
271052
272712
|
offsetSolid,
|
|
271053
272713
|
importSvgSketch,
|
|
271054
272714
|
importMesh,
|
|
272715
|
+
importStep,
|
|
271055
272716
|
text2d,
|
|
271056
272717
|
textWidth,
|
|
271057
272718
|
loadFont,
|
|
@@ -271068,12 +272729,14 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
|
|
|
271068
272729
|
ShapeGroup,
|
|
271069
272730
|
console: sandboxConsole,
|
|
271070
272731
|
cutPlane,
|
|
272732
|
+
cameraTrajectory,
|
|
271071
272733
|
explodeView,
|
|
271072
272734
|
jointsView,
|
|
271073
272735
|
viewConfig,
|
|
271074
272736
|
scene,
|
|
271075
272737
|
verify,
|
|
271076
272738
|
spec,
|
|
272739
|
+
mock,
|
|
271077
272740
|
gcode,
|
|
271078
272741
|
GCodeBuilder,
|
|
271079
272742
|
// ── Laser Kit ──────────────────────────────────────────────────
|
|
@@ -271086,7 +272749,19 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
|
|
|
271086
272749
|
assemblyInstructions,
|
|
271087
272750
|
formatInstructions,
|
|
271088
272751
|
lookupKerf,
|
|
271089
|
-
COMMON_KERFS
|
|
272752
|
+
COMMON_KERFS,
|
|
272753
|
+
// ── Sandbox safety: shadow dangerous globals ───────────────────
|
|
272754
|
+
// These prevent user code from accessing escape vectors directly.
|
|
272755
|
+
// The constructor-chain lockdown (below) covers indirect access.
|
|
272756
|
+
Function: void 0,
|
|
272757
|
+
globalThis: void 0,
|
|
272758
|
+
global: void 0,
|
|
272759
|
+
self: void 0,
|
|
272760
|
+
window: void 0,
|
|
272761
|
+
setTimeout: void 0,
|
|
272762
|
+
setInterval: void 0,
|
|
272763
|
+
setImmediate: void 0,
|
|
272764
|
+
queueMicrotask: void 0
|
|
271090
272765
|
};
|
|
271091
272766
|
const requireModule = (requestedName, paramOverrides) => {
|
|
271092
272767
|
if (typeof requestedName !== "string" || requestedName.trim().length === 0) {
|
|
@@ -271200,6 +272875,15 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
|
|
|
271200
272875
|
const compiled = compileScript(code, fileName, options);
|
|
271201
272876
|
const bindingNames = Object.keys(runtimeBindings);
|
|
271202
272877
|
const bindingValues = bindingNames.map((name) => runtimeBindings[name]);
|
|
272878
|
+
let scriptCode = compiled.code;
|
|
272879
|
+
if (executionMode === "script") {
|
|
272880
|
+
const varNames = extractUnusedTopLevelVarNames(compiled.code).filter((n) => !bindingNames.includes(n));
|
|
272881
|
+
if (varNames.length > 0) {
|
|
272882
|
+
const collector = varNames.map((n) => `${JSON.stringify(n)}: ${n}`).join(", ");
|
|
272883
|
+
scriptCode += `
|
|
272884
|
+
; try { module.__implicitVars = {${collector}}; } catch(e) {}`;
|
|
272885
|
+
}
|
|
272886
|
+
}
|
|
271203
272887
|
const fn = new Function(
|
|
271204
272888
|
"exports",
|
|
271205
272889
|
"module",
|
|
@@ -271207,16 +272891,19 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
|
|
|
271207
272891
|
"__filename",
|
|
271208
272892
|
"__dirname",
|
|
271209
272893
|
...bindingNames,
|
|
271210
|
-
|
|
272894
|
+
`"use strict";
|
|
272895
|
+
${scriptCode}
|
|
271211
272896
|
//# sourceURL=${fileName}`
|
|
271212
272897
|
);
|
|
271213
272898
|
const moduleValue = {
|
|
271214
272899
|
exports: executionMode === "module" && moduleCacheEntry ? moduleCacheEntry.exports : {}
|
|
271215
272900
|
};
|
|
271216
272901
|
const initialExportsRef = moduleValue.exports;
|
|
271217
|
-
const returnValue =
|
|
271218
|
-
|
|
271219
|
-
|
|
272902
|
+
const returnValue = withConstructorChainLockdown(
|
|
272903
|
+
() => runWithParamScope(
|
|
272904
|
+
scope,
|
|
272905
|
+
() => fn(moduleValue.exports, moduleValue, requireModule, fileName, dirnamePath(fileName), ...bindingValues)
|
|
272906
|
+
)
|
|
271220
272907
|
);
|
|
271221
272908
|
if (executionMode === "module") {
|
|
271222
272909
|
const hasExports = hasExplicitModuleExports(moduleValue.exports, initialExportsRef);
|
|
@@ -271241,7 +272928,18 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
|
|
|
271241
272928
|
}
|
|
271242
272929
|
const exportedResult = resolveExportedEntryResult(moduleValue.exports);
|
|
271243
272930
|
if (returnValue === void 0) {
|
|
271244
|
-
|
|
272931
|
+
if (exportedResult != null) return exportedResult;
|
|
272932
|
+
const implicitVars = moduleValue.__implicitVars;
|
|
272933
|
+
if (implicitVars) {
|
|
272934
|
+
const renderables = {};
|
|
272935
|
+
for (const [key, value] of Object.entries(implicitVars)) {
|
|
272936
|
+
if (isRenderableEntryResult(value)) {
|
|
272937
|
+
renderables[key] = value;
|
|
272938
|
+
}
|
|
272939
|
+
}
|
|
272940
|
+
if (Object.keys(renderables).length > 0) return renderables;
|
|
272941
|
+
}
|
|
272942
|
+
return null;
|
|
271245
272943
|
}
|
|
271246
272944
|
return returnValue;
|
|
271247
272945
|
} finally {
|
|
@@ -271278,12 +272976,15 @@ function runScript(code, fileName = "main.forge.js", allFiles = {}, options = {}
|
|
|
271278
272976
|
resetSheetStock();
|
|
271279
272977
|
resetRobotExport();
|
|
271280
272978
|
resetCutPlanes();
|
|
272979
|
+
resetCameraTrajectory();
|
|
271281
272980
|
resetExplodeView();
|
|
271282
272981
|
resetJointsView();
|
|
271283
272982
|
resetViewConfig();
|
|
271284
272983
|
resetScene();
|
|
271285
272984
|
resetVerifications();
|
|
272985
|
+
resetMocks();
|
|
271286
272986
|
_collectedLogs = [];
|
|
272987
|
+
setRuntimeWarnSink((msg) => _collectedLogs.push({ level: "warn", args: [msg], timestamp: Date.now() }));
|
|
271287
272988
|
const t0 = performance.now();
|
|
271288
272989
|
const execOptions = {
|
|
271289
272990
|
debugImports: options.debugImports ?? envFlagEnabled("FORGECAD_DEBUG_IMPORTS"),
|
|
@@ -271523,7 +273224,19 @@ function runScript(code, fileName = "main.forge.js", allFiles = {}, options = {}
|
|
|
271523
273224
|
} else {
|
|
271524
273225
|
const entries = Object.entries(obj);
|
|
271525
273226
|
entries.forEach(([key, value]) => {
|
|
271526
|
-
if (value instanceof
|
|
273227
|
+
if (value instanceof Assembly) {
|
|
273228
|
+
const items = value.solve().toSceneObjects();
|
|
273229
|
+
items.forEach((item, index) => {
|
|
273230
|
+
const label = `${key}.${index + 1}`;
|
|
273231
|
+
processNamedItem(item, label, label);
|
|
273232
|
+
});
|
|
273233
|
+
} else if (value instanceof SolvedAssembly) {
|
|
273234
|
+
const items = value.toSceneObjects();
|
|
273235
|
+
items.forEach((item, index) => {
|
|
273236
|
+
const label = `${key}.${index + 1}`;
|
|
273237
|
+
processNamedItem(item, label, label);
|
|
273238
|
+
});
|
|
273239
|
+
} else if (value instanceof Shape) {
|
|
271527
273240
|
pushShape(value, key, void 0, void 0, void 0, [key]);
|
|
271528
273241
|
} else if (value instanceof Sketch) {
|
|
271529
273242
|
pushSketch(value, key, void 0, [key]);
|
|
@@ -271531,6 +273244,21 @@ function runScript(code, fileName = "main.forge.js", allFiles = {}, options = {}
|
|
|
271531
273244
|
value.children.forEach((child, i) => {
|
|
271532
273245
|
flattenGroupChild(child, groupChildLabel(value, key, i), void 0, [key, shapeGroupChildSegment(value, i)]);
|
|
271533
273246
|
});
|
|
273247
|
+
} else if (Array.isArray(value)) {
|
|
273248
|
+
value.forEach((item, index) => {
|
|
273249
|
+
const label = `${key}.${index + 1}`;
|
|
273250
|
+
if (item instanceof ShapeGroup) {
|
|
273251
|
+
item.children.forEach((child, i) => {
|
|
273252
|
+
flattenGroupChild(child, groupChildLabel(item, label, i), void 0, [key, label, shapeGroupChildSegment(item, i)]);
|
|
273253
|
+
});
|
|
273254
|
+
} else if (item instanceof Shape) {
|
|
273255
|
+
pushShape(item, label, void 0, void 0, void 0, [key, label]);
|
|
273256
|
+
} else if (item instanceof Sketch) {
|
|
273257
|
+
pushSketch(item, label, void 0, [key, label]);
|
|
273258
|
+
} else if (isNamedObject(item)) {
|
|
273259
|
+
processNamedItem(item, label, label, void 0, [key]);
|
|
273260
|
+
}
|
|
273261
|
+
});
|
|
271534
273262
|
}
|
|
271535
273263
|
});
|
|
271536
273264
|
}
|
|
@@ -271559,12 +273287,23 @@ function runScript(code, fileName = "main.forge.js", allFiles = {}, options = {}
|
|
|
271559
273287
|
}
|
|
271560
273288
|
const shape = objects.length === 1 ? objects[0].shape : null;
|
|
271561
273289
|
const sketch = objects.length === 1 ? objects[0].sketch : null;
|
|
273290
|
+
const collectedMocks = getCollectedMocks();
|
|
273291
|
+
for (const m of collectedMocks) {
|
|
273292
|
+
objects.push({
|
|
273293
|
+
id: m.id,
|
|
273294
|
+
name: `${m.name} (mock)`,
|
|
273295
|
+
shape: m.shape,
|
|
273296
|
+
sketch: null,
|
|
273297
|
+
mock: true
|
|
273298
|
+
});
|
|
273299
|
+
}
|
|
271562
273300
|
autoFillExplodeHints(objects);
|
|
271563
273301
|
return {
|
|
271564
273302
|
shape,
|
|
271565
273303
|
sketch,
|
|
271566
273304
|
objects,
|
|
271567
273305
|
params: getCollectedParams(),
|
|
273306
|
+
stringParams: getCollectedStringParams(),
|
|
271568
273307
|
listParams: getCollectedListParams(),
|
|
271569
273308
|
dimensions: [...getCollectedDimensions(), ...shapeDimensions],
|
|
271570
273309
|
highlights: getCollectedHighlights(),
|
|
@@ -271572,6 +273311,7 @@ function runScript(code, fileName = "main.forge.js", allFiles = {}, options = {}
|
|
|
271572
273311
|
bom: getCollectedBom(),
|
|
271573
273312
|
sheetStock: getCollectedSheetStock(),
|
|
271574
273313
|
cutPlanes: getCollectedCutPlanes(),
|
|
273314
|
+
cameraTrajectory: getCollectedCameraTrajectory(),
|
|
271575
273315
|
explodeView: getCollectedExplodeView(),
|
|
271576
273316
|
jointsView: getCollectedJointsView(),
|
|
271577
273317
|
viewConfig: getCollectedViewConfig(),
|
|
@@ -271581,7 +273321,8 @@ function runScript(code, fileName = "main.forge.js", allFiles = {}, options = {}
|
|
|
271581
273321
|
error: objects.length > 0 || options.allowEmptyResult ? null : "Script must return a Shape or Sketch",
|
|
271582
273322
|
timeMs: performance.now() - t0,
|
|
271583
273323
|
logs: _collectedLogs.slice(),
|
|
271584
|
-
verifications: getCollectedVerifications()
|
|
273324
|
+
verifications: getCollectedVerifications(),
|
|
273325
|
+
mocks: getCollectedMocks()
|
|
271585
273326
|
};
|
|
271586
273327
|
});
|
|
271587
273328
|
} catch (e) {
|
|
@@ -271598,6 +273339,7 @@ function runScript(code, fileName = "main.forge.js", allFiles = {}, options = {}
|
|
|
271598
273339
|
sketch: null,
|
|
271599
273340
|
objects: [],
|
|
271600
273341
|
params: getCollectedParams(),
|
|
273342
|
+
stringParams: getCollectedStringParams(),
|
|
271601
273343
|
listParams: getCollectedListParams(),
|
|
271602
273344
|
dimensions: getCollectedDimensions(),
|
|
271603
273345
|
highlights: getCollectedHighlights(),
|
|
@@ -271605,6 +273347,7 @@ function runScript(code, fileName = "main.forge.js", allFiles = {}, options = {}
|
|
|
271605
273347
|
bom: getCollectedBom(),
|
|
271606
273348
|
sheetStock: getCollectedSheetStock(),
|
|
271607
273349
|
cutPlanes: getCollectedCutPlanes(),
|
|
273350
|
+
cameraTrajectory: getCollectedCameraTrajectory(),
|
|
271608
273351
|
explodeView: getCollectedExplodeView(),
|
|
271609
273352
|
jointsView: getCollectedJointsView(),
|
|
271610
273353
|
viewConfig: getCollectedViewConfig(),
|
|
@@ -271614,7 +273357,8 @@ function runScript(code, fileName = "main.forge.js", allFiles = {}, options = {}
|
|
|
271614
273357
|
error: `${msg}${lineInfo}`,
|
|
271615
273358
|
timeMs: performance.now() - t0,
|
|
271616
273359
|
logs: _collectedLogs.slice(),
|
|
271617
|
-
verifications: getCollectedVerifications()
|
|
273360
|
+
verifications: getCollectedVerifications(),
|
|
273361
|
+
mocks: getCollectedMocks()
|
|
271618
273362
|
};
|
|
271619
273363
|
}
|
|
271620
273364
|
}
|