forgecad 0.9.5 → 0.9.6
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/dist/assets/{AdminPage-uTtcSXtn.js → AdminPage-Da6hhpJx.js} +1 -1
- package/dist/assets/{BlogPage-DYJMjWx3.js → BlogPage-Bl_sKeWb.js} +1 -1
- package/dist/assets/{DocsPage-C58f0K5v.js → DocsPage-Blz3Tp4j.js} +1 -1
- package/dist/assets/{EditorApp-DNH1TEz1.js → EditorApp-CuiPbtn5.js} +32 -7
- package/dist/assets/{EmbedViewer-CMXWA2LX.js → EmbedViewer-BFG6-Ufm.js} +2 -2
- package/dist/assets/{LandingPageProofDriven-CAu2OZFn.js → LandingPageProofDriven-DB9fQd5P.js} +1 -1
- package/dist/assets/{PricingPage-BIgW7m3X.js → PricingPage-BMxYT_F0.js} +1 -1
- package/dist/assets/{SettingsPage-N1l1tMXO.js → SettingsPage-VVQNrCAg.js} +1 -1
- package/dist/assets/{app-CFy7g5WP.js → app-Dl9ymBWC.js} +293 -36
- package/dist/assets/cli/{render-BrVVdj_T.js → render-CFtwKCCY.js} +10 -1081
- package/dist/assets/{sectionPlaneMath-CykEnkvQ.js → distance-BEC2RjJi.js} +1897 -288
- package/dist/assets/{evalWorker-c_SB9gg3.js → evalWorker-CRvbzTXm.js} +555 -83
- package/dist/assets/{manifold-Cjk7WhRs.js → manifold-B9QSr-qP.js} +1 -1
- package/dist/assets/{manifold-Dp6pvFr6.js → manifold-DpBXFS2K.js} +1 -1
- package/dist/assets/{manifold-CRoBhJKH.js → manifold-DzZ4VRPs.js} +2 -2
- package/dist/assets/{renderSceneState-3DfsSASX.js → renderSceneState-BuAXF2jh.js} +1 -1
- package/dist/assets/{reportWorker-BLkuIoS8.js → reportWorker-BNWEnRg1.js} +555 -83
- package/dist/cli/render.html +1 -1
- package/dist/docs/index.html +1 -1
- package/dist/docs-raw/beta-operations.md +4 -0
- package/dist/docs-raw/deployment.md +38 -23
- package/dist/docs-raw/generated/concepts.md +82 -5
- package/dist/docs-raw/generated/curves.md +97 -5
- package/dist/docs-raw/generated/sketch.md +9 -1
- package/dist/docs-raw/guides/inspection-bundles.md +9 -3
- package/dist/docs-raw/runbook.md +3 -3
- package/dist/index.html +1 -1
- package/dist/sitemap.xml +6 -6
- package/dist-cli/forgecad.js +828 -297
- package/dist-cli/forgecad.js.map +1 -1
- package/dist-skill/CONTEXT.md +115 -9
- package/dist-skill/docs/generated/curves.md +97 -5
- package/dist-skill/docs/generated/sketch.md +9 -1
- package/dist-skill/docs/guides/inspection-bundles.md +9 -3
- package/dist-skill/docs-dev/generated/curves.md +97 -5
- package/dist-skill/docs-dev/generated/sketch.md +9 -1
- package/dist-skill/docs-dev/guides/inspection-bundles.md +9 -3
- package/examples/api/guided-loft-olive-oil-bottle.forge.js +135 -0
- package/package.json +20 -2
|
@@ -721,7 +721,7 @@ function cloneSdfNode(node) {
|
|
|
721
721
|
}
|
|
722
722
|
}
|
|
723
723
|
const SHEET_METAL_EDGES = ["top", "right", "bottom", "left"];
|
|
724
|
-
const EPS$
|
|
724
|
+
const EPS$d = 1e-9;
|
|
725
725
|
function isFinitePositive$3(value) {
|
|
726
726
|
return Number.isFinite(value) && value > 0;
|
|
727
727
|
}
|
|
@@ -762,7 +762,7 @@ function edgeDisplayName(edge) {
|
|
|
762
762
|
return `sheetMetal().flange("${edge}", ...)`;
|
|
763
763
|
}
|
|
764
764
|
function normalizeAngle(angleDeg) {
|
|
765
|
-
return Math.abs(angleDeg) <= EPS$
|
|
765
|
+
return Math.abs(angleDeg) <= EPS$d ? 0 : angleDeg;
|
|
766
766
|
}
|
|
767
767
|
function validateSheetMetalModel(model) {
|
|
768
768
|
if (!isFinitePositive$3(model.panel.width) || !isFinitePositive$3(model.panel.height)) {
|
|
@@ -774,7 +774,7 @@ function validateSheetMetalModel(model) {
|
|
|
774
774
|
if (!isFiniteNonNegative(model.bendRadius)) {
|
|
775
775
|
return "sheetMetal() requires a finite non-negative bendRadius.";
|
|
776
776
|
}
|
|
777
|
-
if (model.bendRadius <= EPS$
|
|
777
|
+
if (model.bendRadius <= EPS$d) {
|
|
778
778
|
return "sheetMetal() v1 requires a positive bendRadius so the bend region stays explicit instead of collapsing into a sharp fold.";
|
|
779
779
|
}
|
|
780
780
|
if (model.bendAllowance.kind !== "k-factor") {
|
|
@@ -836,7 +836,7 @@ function deriveSheetMetalModel(model) {
|
|
|
836
836
|
const trimEnd = flanges.has(adjacent.end) ? model.cornerRelief.size : 0;
|
|
837
837
|
const fullLength = edge === "top" || edge === "bottom" ? model.panel.width : model.panel.height;
|
|
838
838
|
const span = fullLength - trimStart - trimEnd;
|
|
839
|
-
if (!(span > EPS$
|
|
839
|
+
if (!(span > EPS$d)) {
|
|
840
840
|
throw new Error(
|
|
841
841
|
`${edgeDisplayName(edge)} loses all usable span after applying the defended rectangular corner relief size ${model.cornerRelief.size}.`
|
|
842
842
|
);
|
|
@@ -894,7 +894,7 @@ function transformPlacement(origin, u2, v, normal) {
|
|
|
894
894
|
};
|
|
895
895
|
}
|
|
896
896
|
function translatePlan(plan, x2, y2, z2) {
|
|
897
|
-
if (Math.abs(x2) <= EPS$
|
|
897
|
+
if (Math.abs(x2) <= EPS$d && Math.abs(y2) <= EPS$d && Math.abs(z2) <= EPS$d) return cloneShapeCompilePlan(plan);
|
|
898
898
|
return appendShapeCompileTransform(cloneShapeCompilePlan(plan), {
|
|
899
899
|
kind: "translate",
|
|
900
900
|
x: x2,
|
|
@@ -1338,7 +1338,7 @@ function cloneShapeWorkplanePlacement(placement) {
|
|
|
1338
1338
|
placement: cloneSketchPlacementModel(placement.placement)
|
|
1339
1339
|
};
|
|
1340
1340
|
}
|
|
1341
|
-
const EPS$
|
|
1341
|
+
const EPS$c = 1e-10;
|
|
1342
1342
|
function subVec3(a2, b) {
|
|
1343
1343
|
return [a2[0] - b[0], a2[1] - b[1], a2[2] - b[2]];
|
|
1344
1344
|
}
|
|
@@ -1364,7 +1364,7 @@ function projectRadial(v, axis) {
|
|
|
1364
1364
|
function signedAngleAroundAxis(from, to, axis) {
|
|
1365
1365
|
const fromLen = lengthVec3$1(from);
|
|
1366
1366
|
const toLen = lengthVec3$1(to);
|
|
1367
|
-
if (fromLen < EPS$
|
|
1367
|
+
if (fromLen < EPS$c || toLen < EPS$c) return 0;
|
|
1368
1368
|
const fn = scaleVec3(from, 1 / fromLen);
|
|
1369
1369
|
const tn = scaleVec3(to, 1 / toLen);
|
|
1370
1370
|
const sin2 = dotVec3$4(axis, crossVec3$2(fn, tn));
|
|
@@ -1385,19 +1385,19 @@ function solveRotateAroundAngle(axis, pivot, movingPoint, targetPoint, options =
|
|
|
1385
1385
|
const targetDecomp = projectRadial(target, unitAxis);
|
|
1386
1386
|
const movingRadialLen = lengthVec3$1(movingDecomp.radial);
|
|
1387
1387
|
const targetRadialLen = lengthVec3$1(targetDecomp.radial);
|
|
1388
|
-
if (movingRadialLen < EPS$
|
|
1389
|
-
if (mode === "line" && targetRadialLen >= EPS$
|
|
1388
|
+
if (movingRadialLen < EPS$c) {
|
|
1389
|
+
if (mode === "line" && targetRadialLen >= EPS$c) {
|
|
1390
1390
|
throw new Error("rotateAroundTo(...): moving point lies on the rotation axis, so line alignment is impossible");
|
|
1391
1391
|
}
|
|
1392
1392
|
return 0;
|
|
1393
1393
|
}
|
|
1394
1394
|
if (mode === "plane") {
|
|
1395
|
-
if (targetRadialLen < EPS$
|
|
1395
|
+
if (targetRadialLen < EPS$c) {
|
|
1396
1396
|
throw new Error("rotateAroundTo(...): target point lies on the rotation axis, so the target plane is undefined");
|
|
1397
1397
|
}
|
|
1398
1398
|
return signedAngleAroundAxis(movingDecomp.radial, targetDecomp.radial, unitAxis);
|
|
1399
1399
|
}
|
|
1400
|
-
if (targetRadialLen < EPS$
|
|
1400
|
+
if (targetRadialLen < EPS$c) {
|
|
1401
1401
|
throw new Error("rotateAroundTo(...): target line lies on the rotation axis, but the moving point does not");
|
|
1402
1402
|
}
|
|
1403
1403
|
const axialTol = 1e-8 * Math.max(1, Math.abs(movingDecomp.axial), Math.abs(targetDecomp.axial));
|
|
@@ -1437,7 +1437,7 @@ function multiplyMat4(a2, b) {
|
|
|
1437
1437
|
}
|
|
1438
1438
|
function normalizeVec3$5(v) {
|
|
1439
1439
|
const len2 = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
|
|
1440
|
-
if (len2 < EPS$
|
|
1440
|
+
if (len2 < EPS$c) throw new Error("Axis must be non-zero");
|
|
1441
1441
|
return [v[0] / len2, v[1] / len2, v[2] / len2];
|
|
1442
1442
|
}
|
|
1443
1443
|
function transformPoint$1(m2, p2, w2) {
|
|
@@ -1467,7 +1467,7 @@ function invertMat4(m2) {
|
|
|
1467
1467
|
const b10 = a21 * a33 - a23 * a31;
|
|
1468
1468
|
const b11 = a22 * a33 - a23 * a32;
|
|
1469
1469
|
const det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
|
|
1470
|
-
if (Math.abs(det) < EPS$
|
|
1470
|
+
if (Math.abs(det) < EPS$c) throw new Error("Transform matrix is not invertible");
|
|
1471
1471
|
const invDet = 1 / det;
|
|
1472
1472
|
out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * invDet;
|
|
1473
1473
|
out[1] = (a02 * b10 - a01 * b11 - a03 * b09) * invDet;
|
|
@@ -3224,14 +3224,14 @@ function sweepPathToPolylineAdaptive(path2, baseSamples = 48) {
|
|
|
3224
3224
|
pts.push(evalPathAt(path2, 1));
|
|
3225
3225
|
return pts;
|
|
3226
3226
|
}
|
|
3227
|
-
const EPS$
|
|
3227
|
+
const EPS$b = 1e-8;
|
|
3228
3228
|
const SUPPORTED_VERTICAL_EDGE_NAMES = ["vert-bl", "vert-br", "vert-tr", "vert-tl"];
|
|
3229
3229
|
function midpoint$4(start, end) {
|
|
3230
3230
|
return [(start[0] + end[0]) * 0.5, (start[1] + end[1]) * 0.5, (start[2] + end[2]) * 0.5];
|
|
3231
3231
|
}
|
|
3232
3232
|
function normalize$8(v) {
|
|
3233
3233
|
const len2 = Math.hypot(v[0], v[1], v[2]);
|
|
3234
|
-
if (len2 <= EPS$
|
|
3234
|
+
if (len2 <= EPS$b) throw new Error("Edge feature selection requires a non-zero direction vector");
|
|
3235
3235
|
return [v[0] / len2, v[1] / len2, v[2] / len2];
|
|
3236
3236
|
}
|
|
3237
3237
|
function subtract(a2, b) {
|
|
@@ -3313,7 +3313,7 @@ function rigidTransformForEdgeStep(step) {
|
|
|
3313
3313
|
case "mirror": {
|
|
3314
3314
|
const [nx0, ny0, nz0] = [step.normalX, step.normalY, step.normalZ];
|
|
3315
3315
|
const len2 = Math.hypot(nx0, ny0, nz0);
|
|
3316
|
-
if (len2 <= EPS$
|
|
3316
|
+
if (len2 <= EPS$b) return Transform.identity();
|
|
3317
3317
|
const nx = nx0 / len2;
|
|
3318
3318
|
const ny = ny0 / len2;
|
|
3319
3319
|
const nz = nz0 / len2;
|
|
@@ -3624,7 +3624,7 @@ function isRectangleProfile(points) {
|
|
|
3624
3624
|
return [next[0] - point2[0], next[1] - point2[1]];
|
|
3625
3625
|
});
|
|
3626
3626
|
const lengths2 = vectors.map(([x2, y2]) => Math.hypot(x2, y2));
|
|
3627
|
-
if (lengths2.some((length4) => length4 <= EPS$
|
|
3627
|
+
if (lengths2.some((length4) => length4 <= EPS$b)) return false;
|
|
3628
3628
|
const dot01 = vectors[0][0] * vectors[1][0] + vectors[0][1] * vectors[1][1];
|
|
3629
3629
|
const dot12 = vectors[1][0] * vectors[2][0] + vectors[1][1] * vectors[2][1];
|
|
3630
3630
|
const dot23 = vectors[2][0] * vectors[3][0] + vectors[2][1] * vectors[3][1];
|
|
@@ -5195,13 +5195,13 @@ function parseMeshFile(data, format) {
|
|
|
5195
5195
|
return parse3mf(data);
|
|
5196
5196
|
}
|
|
5197
5197
|
}
|
|
5198
|
-
const EPS$
|
|
5198
|
+
const EPS$a = 1e-8;
|
|
5199
5199
|
function length$3(v) {
|
|
5200
5200
|
return Math.hypot(v[0], v[1], v[2]);
|
|
5201
5201
|
}
|
|
5202
5202
|
function normalize$7(v) {
|
|
5203
5203
|
const len2 = length$3(v);
|
|
5204
|
-
if (len2 < EPS$
|
|
5204
|
+
if (len2 < EPS$a) throw new Error("Plane normal must be non-zero");
|
|
5205
5205
|
return [v[0] / len2, v[1] / len2, v[2] / len2];
|
|
5206
5206
|
}
|
|
5207
5207
|
function resolvePlaneOriginNormal(plane) {
|
|
@@ -5223,12 +5223,12 @@ function resolvePlaneOriginNormal(plane) {
|
|
|
5223
5223
|
function rotationToPlaneSpace(normal) {
|
|
5224
5224
|
const n = normalize$7(normal);
|
|
5225
5225
|
const dot2 = n[2];
|
|
5226
|
-
if (dot2 > 1 - EPS$
|
|
5226
|
+
if (dot2 > 1 - EPS$a) {
|
|
5227
5227
|
return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
|
|
5228
5228
|
}
|
|
5229
5229
|
let axis;
|
|
5230
5230
|
let angle;
|
|
5231
|
-
if (dot2 < -1 + EPS$
|
|
5231
|
+
if (dot2 < -1 + EPS$a) {
|
|
5232
5232
|
axis = [1, 0, 0];
|
|
5233
5233
|
angle = Math.PI;
|
|
5234
5234
|
} else {
|
|
@@ -7790,7 +7790,7 @@ function scale$6(v, s) {
|
|
|
7790
7790
|
function sub$7(a2, b) {
|
|
7791
7791
|
return [a2[0] - b[0], a2[1] - b[1], a2[2] - b[2]];
|
|
7792
7792
|
}
|
|
7793
|
-
function cross$
|
|
7793
|
+
function cross$8(a2, b) {
|
|
7794
7794
|
return [a2[1] * b[2] - a2[2] * b[1], a2[2] * b[0] - a2[0] * b[2], a2[0] * b[1] - a2[1] * b[0]];
|
|
7795
7795
|
}
|
|
7796
7796
|
function makeEdge(name, start, end, faceName, curve) {
|
|
@@ -7826,7 +7826,7 @@ function buildSurfaceSheetTopology(boundaries, options = {}) {
|
|
|
7826
7826
|
const center = options.center ?? average$1(corners);
|
|
7827
7827
|
const uAxis = normalizeAxis$1(sub$7(midpoint$3(u1Start, u1End), midpoint$3(u0Start, u0End)));
|
|
7828
7828
|
const vAxis = normalizeAxis$1(sub$7(midpoint$3(v1Start, v1End), midpoint$3(v0Start, v0End)));
|
|
7829
|
-
const normal = normalizeAxis$1(options.normal ?? cross$
|
|
7829
|
+
const normal = normalizeAxis$1(options.normal ?? cross$8(uAxis, vAxis));
|
|
7830
7830
|
const faces = /* @__PURE__ */ new Map();
|
|
7831
7831
|
faces.set(faceName, {
|
|
7832
7832
|
name: faceName,
|
|
@@ -9728,6 +9728,7 @@ function buildSweepLevelSetInput(profilePolygons, pathInput, options) {
|
|
|
9728
9728
|
edgeLength: options.edgeLength
|
|
9729
9729
|
};
|
|
9730
9730
|
}
|
|
9731
|
+
const EPS$9 = 1e-9;
|
|
9731
9732
|
function resamplePolygon(poly, targetCount) {
|
|
9732
9733
|
if (poly.length < 2) return poly;
|
|
9733
9734
|
if (targetCount <= 0) return [];
|
|
@@ -9765,6 +9766,78 @@ function resamplePolygon(poly, targetCount) {
|
|
|
9765
9766
|
}
|
|
9766
9767
|
return out;
|
|
9767
9768
|
}
|
|
9769
|
+
function resamplePolygonByAngle(poly, targetCount, center = polygonCentroid$2(poly)) {
|
|
9770
|
+
if (poly.length < 3 || targetCount <= 0) return null;
|
|
9771
|
+
if (!isConvexPolygon(poly)) return null;
|
|
9772
|
+
const out = [];
|
|
9773
|
+
for (let index2 = 0; index2 < targetCount; index2 += 1) {
|
|
9774
|
+
const angle = index2 / targetCount * Math.PI * 2;
|
|
9775
|
+
const point2 = rayPolygonIntersection(center, [Math.cos(angle), Math.sin(angle)], poly);
|
|
9776
|
+
if (!point2) return null;
|
|
9777
|
+
out.push(point2);
|
|
9778
|
+
}
|
|
9779
|
+
return out;
|
|
9780
|
+
}
|
|
9781
|
+
function rayPolygonIntersection(origin, direction2, poly) {
|
|
9782
|
+
let bestT = Infinity;
|
|
9783
|
+
let best = null;
|
|
9784
|
+
for (let index2 = 0; index2 < poly.length; index2 += 1) {
|
|
9785
|
+
const a2 = poly[index2];
|
|
9786
|
+
const b = poly[(index2 + 1) % poly.length];
|
|
9787
|
+
const edge = [b[0] - a2[0], b[1] - a2[1]];
|
|
9788
|
+
const denom = cross$7(direction2, edge);
|
|
9789
|
+
if (Math.abs(denom) < EPS$9) continue;
|
|
9790
|
+
const delta = [a2[0] - origin[0], a2[1] - origin[1]];
|
|
9791
|
+
const rayT = cross$7(delta, edge) / denom;
|
|
9792
|
+
const edgeT = cross$7(delta, direction2) / denom;
|
|
9793
|
+
if (rayT >= -EPS$9 && edgeT >= -EPS$9 && edgeT <= 1 + EPS$9 && rayT < bestT) {
|
|
9794
|
+
bestT = rayT;
|
|
9795
|
+
best = [origin[0] + direction2[0] * rayT, origin[1] + direction2[1] * rayT];
|
|
9796
|
+
}
|
|
9797
|
+
}
|
|
9798
|
+
return best;
|
|
9799
|
+
}
|
|
9800
|
+
function polygonCentroid$2(poly) {
|
|
9801
|
+
let area2 = 0;
|
|
9802
|
+
let cx = 0;
|
|
9803
|
+
let cy = 0;
|
|
9804
|
+
for (let index2 = 0; index2 < poly.length; index2 += 1) {
|
|
9805
|
+
const a2 = poly[index2];
|
|
9806
|
+
const b = poly[(index2 + 1) % poly.length];
|
|
9807
|
+
const crossValue = cross$7(a2, b);
|
|
9808
|
+
area2 += crossValue;
|
|
9809
|
+
cx += (a2[0] + b[0]) * crossValue;
|
|
9810
|
+
cy += (a2[1] + b[1]) * crossValue;
|
|
9811
|
+
}
|
|
9812
|
+
if (Math.abs(area2) < EPS$9) return averagePoint(poly);
|
|
9813
|
+
return [cx / (3 * area2), cy / (3 * area2)];
|
|
9814
|
+
}
|
|
9815
|
+
function averagePoint(poly) {
|
|
9816
|
+
let x2 = 0;
|
|
9817
|
+
let y2 = 0;
|
|
9818
|
+
for (const point2 of poly) {
|
|
9819
|
+
x2 += point2[0];
|
|
9820
|
+
y2 += point2[1];
|
|
9821
|
+
}
|
|
9822
|
+
return [x2 / poly.length, y2 / poly.length];
|
|
9823
|
+
}
|
|
9824
|
+
function isConvexPolygon(poly) {
|
|
9825
|
+
let sign2 = 0;
|
|
9826
|
+
for (let index2 = 0; index2 < poly.length; index2 += 1) {
|
|
9827
|
+
const a2 = poly[index2];
|
|
9828
|
+
const b = poly[(index2 + 1) % poly.length];
|
|
9829
|
+
const c2 = poly[(index2 + 2) % poly.length];
|
|
9830
|
+
const turn = cross$7([b[0] - a2[0], b[1] - a2[1]], [c2[0] - b[0], c2[1] - b[1]]);
|
|
9831
|
+
if (Math.abs(turn) < EPS$9) continue;
|
|
9832
|
+
const currentSign = Math.sign(turn);
|
|
9833
|
+
if (sign2 !== 0 && currentSign !== sign2) return false;
|
|
9834
|
+
sign2 = currentSign;
|
|
9835
|
+
}
|
|
9836
|
+
return sign2 !== 0;
|
|
9837
|
+
}
|
|
9838
|
+
function cross$7(a2, b) {
|
|
9839
|
+
return a2[0] * b[1] - a2[1] * b[0];
|
|
9840
|
+
}
|
|
9768
9841
|
function loftStitched(profiles2, heights, wasm) {
|
|
9769
9842
|
if (profiles2.length < 2) return null;
|
|
9770
9843
|
const classified = profiles2.map((loops) => classifyLoops(loops));
|
|
@@ -9893,8 +9966,10 @@ function stitchSingleLoopLoft(loops, heights, wasm) {
|
|
|
9893
9966
|
maxPoints = Math.max(maxPoints, loop.length);
|
|
9894
9967
|
}
|
|
9895
9968
|
const N = Math.max(maxPoints, 24);
|
|
9969
|
+
const angularSamples = normalizedLoops.map((loop) => resamplePolygonByAngle(loop, N));
|
|
9970
|
+
const useAngularSamples = angularSamples.every((samples) => samples != null);
|
|
9896
9971
|
const resampled = normalizedLoops.map((loop, i) => {
|
|
9897
|
-
const pts2d = resamplePolygon(loop, N);
|
|
9972
|
+
const pts2d = useAngularSamples ? angularSamples[i] : resamplePolygon(loop, N);
|
|
9898
9973
|
const z2 = heights[i];
|
|
9899
9974
|
return pts2d.map(([x2, y2]) => [x2, y2, z2]);
|
|
9900
9975
|
});
|
|
@@ -9955,7 +10030,7 @@ let _wasm$1 = null;
|
|
|
9955
10030
|
async function initManifoldWasm() {
|
|
9956
10031
|
if (_wasm$1) return _wasm$1;
|
|
9957
10032
|
performance.mark("manifold:start");
|
|
9958
|
-
const Module = (await import("./manifold-
|
|
10033
|
+
const Module = (await import("./manifold-DpBXFS2K.js")).default;
|
|
9959
10034
|
performance.mark("manifold:imported");
|
|
9960
10035
|
const wasm = await Module();
|
|
9961
10036
|
wasm.setup();
|
|
@@ -46528,10 +46603,8 @@ class PathBuilder {
|
|
|
46528
46603
|
if (radius <= 0) throw new Error("fillet: radius must be positive");
|
|
46529
46604
|
const n = this.segs.length;
|
|
46530
46605
|
if (n < 2) throw new Error("fillet: need at least 2 segments before a fillet");
|
|
46531
|
-
const prev = this.segs[n - 2];
|
|
46532
46606
|
const curr = this.segs[n - 1];
|
|
46533
|
-
|
|
46534
|
-
const { trimA, trimB, arcSeg } = this.computeFilletGeom(radius);
|
|
46607
|
+
const { trimA, arcSeg } = this.computeFilletGeom(radius);
|
|
46535
46608
|
if (!arcSeg) throw new Error("fillet: cannot fillet these segments (parallel or degenerate)");
|
|
46536
46609
|
this.trimLastSegEnd(n - 2, trimA[0], trimA[1]);
|
|
46537
46610
|
const trimmedSeg = { ...curr };
|
|
@@ -46603,7 +46676,6 @@ class PathBuilder {
|
|
|
46603
46676
|
}
|
|
46604
46677
|
getSegDirAt(seg, which) {
|
|
46605
46678
|
if (seg.kind === "line" || seg.kind === "move") {
|
|
46606
|
-
this.segs.length;
|
|
46607
46679
|
const idx = this.segs.indexOf(seg);
|
|
46608
46680
|
if (seg.kind === "line") {
|
|
46609
46681
|
let sx, sy;
|
|
@@ -46845,6 +46917,41 @@ class PathBuilder {
|
|
|
46845
46917
|
}
|
|
46846
46918
|
return pts;
|
|
46847
46919
|
}
|
|
46920
|
+
/**
|
|
46921
|
+
* Return the open path as a sampled 2D polyline.
|
|
46922
|
+
*
|
|
46923
|
+
* This is for construction geometry such as guide rails, measured centerlines,
|
|
46924
|
+
* and curve-driven helpers where the authored path should stay open instead of
|
|
46925
|
+
* becoming a filled sketch or stroked profile.
|
|
46926
|
+
*
|
|
46927
|
+
* **Example**
|
|
46928
|
+
*
|
|
46929
|
+
* ```ts
|
|
46930
|
+
* const rail = path()
|
|
46931
|
+
* .moveTo(24, 0)
|
|
46932
|
+
* .bezierTo(32, 44, 28, 92, 18, 120)
|
|
46933
|
+
* .toPolyline();
|
|
46934
|
+
* ```
|
|
46935
|
+
*
|
|
46936
|
+
* @returns A sampled open polyline.
|
|
46937
|
+
* @category Path Builder
|
|
46938
|
+
*/
|
|
46939
|
+
toPolyline() {
|
|
46940
|
+
const moveCount = this.segs.filter((seg) => seg.kind === "move").length;
|
|
46941
|
+
if (moveCount > 1) {
|
|
46942
|
+
throw new Error("path().toPolyline() supports one continuous open path. Use separate path() builders for separate rails.");
|
|
46943
|
+
}
|
|
46944
|
+
const pts = [];
|
|
46945
|
+
for (const point2 of this.tessellate()) {
|
|
46946
|
+
if (!point2.every(Number.isFinite)) throw new Error("path().toPolyline() produced a non-finite point");
|
|
46947
|
+
const previous = pts[pts.length - 1];
|
|
46948
|
+
if (!previous || Math.hypot(point2[0] - previous[0], point2[1] - previous[1]) > 1e-9) {
|
|
46949
|
+
pts.push(point2);
|
|
46950
|
+
}
|
|
46951
|
+
}
|
|
46952
|
+
if (pts.length < 2) throw new Error("path().toPolyline() needs at least 2 points");
|
|
46953
|
+
return pts;
|
|
46954
|
+
}
|
|
46848
46955
|
// ── Output ────────────────────────────────────────────────────────────────
|
|
46849
46956
|
/**
|
|
46850
46957
|
* Close the path and return a filled `Sketch`.
|
|
@@ -58763,7 +58870,7 @@ function requireFinite$7(value, label) {
|
|
|
58763
58870
|
}
|
|
58764
58871
|
return value;
|
|
58765
58872
|
}
|
|
58766
|
-
function requireVec3$
|
|
58873
|
+
function requireVec3$3(value, label) {
|
|
58767
58874
|
if (!Array.isArray(value) || value.length !== 3) {
|
|
58768
58875
|
throw new Error(`${label} must be [x, y, z]`);
|
|
58769
58876
|
}
|
|
@@ -58807,7 +58914,7 @@ function normalizeOptions(options) {
|
|
|
58807
58914
|
out.size = requireFinite$7(options.size, "Viewport.label options.size");
|
|
58808
58915
|
if (out.size <= 0) throw new Error("Viewport.label options.size must be positive");
|
|
58809
58916
|
}
|
|
58810
|
-
if (options.offset !== void 0) out.offset = requireVec3$
|
|
58917
|
+
if (options.offset !== void 0) out.offset = requireVec3$3(options.offset, "Viewport.label options.offset");
|
|
58811
58918
|
if (options.anchor !== void 0) {
|
|
58812
58919
|
if (!VALID_ANCHORS.has(options.anchor)) {
|
|
58813
58920
|
throw new Error(`Viewport.label options.anchor must be one of: ${Array.from(VALID_ANCHORS).join(", ")}`);
|
|
@@ -58824,7 +58931,7 @@ function collectRenderLabel(text, at, options) {
|
|
|
58824
58931
|
if (typeof text !== "string" || text.trim().length === 0) {
|
|
58825
58932
|
throw new Error("Viewport.label text must be a non-empty string");
|
|
58826
58933
|
}
|
|
58827
|
-
const normalizedAt = requireVec3$
|
|
58934
|
+
const normalizedAt = requireVec3$3(at, "Viewport.label at");
|
|
58828
58935
|
const normalizedOptions = normalizeOptions(options);
|
|
58829
58936
|
_collected$4.push({
|
|
58830
58937
|
id: `render-label-${_nextId++}`,
|
|
@@ -59019,7 +59126,7 @@ function requireFinite$6(value, label) {
|
|
|
59019
59126
|
}
|
|
59020
59127
|
return value;
|
|
59021
59128
|
}
|
|
59022
|
-
function requireVec3$
|
|
59129
|
+
function requireVec3$2(value, label) {
|
|
59023
59130
|
if (!Array.isArray(value) || value.length !== 3) {
|
|
59024
59131
|
throw new Error(`${label} must be [x, y, z]`);
|
|
59025
59132
|
}
|
|
@@ -59047,9 +59154,9 @@ const VALID_ENVIRONMENT_PRESETS = /* @__PURE__ */ new Set([
|
|
|
59047
59154
|
]);
|
|
59048
59155
|
function validateCamera(cam, label) {
|
|
59049
59156
|
const out = {};
|
|
59050
|
-
if (cam.position !== void 0) out.position = requireVec3$
|
|
59051
|
-
if (cam.target !== void 0) out.target = requireVec3$
|
|
59052
|
-
if (cam.up !== void 0) out.up = requireVec3$
|
|
59157
|
+
if (cam.position !== void 0) out.position = requireVec3$2(cam.position, `${label}.position`);
|
|
59158
|
+
if (cam.target !== void 0) out.target = requireVec3$2(cam.target, `${label}.target`);
|
|
59159
|
+
if (cam.up !== void 0) out.up = requireVec3$2(cam.up, `${label}.up`);
|
|
59053
59160
|
if (cam.fov !== void 0) {
|
|
59054
59161
|
out.fov = requireFinite$6(cam.fov, `${label}.fov`);
|
|
59055
59162
|
if (out.fov <= 0 || out.fov >= 180) throw new Error(`${label}.fov must be between 0 and 180`);
|
|
@@ -59184,8 +59291,8 @@ function validateLight(light, label) {
|
|
|
59184
59291
|
const out = { type: light.type };
|
|
59185
59292
|
if (light.color !== void 0) out.color = requireColor(light.color, `${label}.color`);
|
|
59186
59293
|
if (light.intensity !== void 0) out.intensity = requireFinite$6(light.intensity, `${label}.intensity`);
|
|
59187
|
-
if (light.position !== void 0) out.position = requireVec3$
|
|
59188
|
-
if (light.target !== void 0) out.target = requireVec3$
|
|
59294
|
+
if (light.position !== void 0) out.position = requireVec3$2(light.position, `${label}.position`);
|
|
59295
|
+
if (light.target !== void 0) out.target = requireVec3$2(light.target, `${label}.target`);
|
|
59189
59296
|
if (light.groundColor !== void 0) out.groundColor = requireColor(light.groundColor, `${label}.groundColor`);
|
|
59190
59297
|
if (light.skyColor !== void 0) out.skyColor = requireColor(light.skyColor, `${label}.skyColor`);
|
|
59191
59298
|
if (light.angle !== void 0) out.angle = requireFinite$6(light.angle, `${label}.angle`);
|
|
@@ -60717,7 +60824,7 @@ function scale$1(v, s) {
|
|
|
60717
60824
|
function dot$2(a2, b) {
|
|
60718
60825
|
return a2[0] * b[0] + a2[1] * b[1] + a2[2] * b[2];
|
|
60719
60826
|
}
|
|
60720
|
-
function lerp$
|
|
60827
|
+
function lerp$4(a2, b, t) {
|
|
60721
60828
|
return a2 + (b - a2) * t;
|
|
60722
60829
|
}
|
|
60723
60830
|
function frameMatrix$1(x2, y2, z2, p2) {
|
|
@@ -60728,7 +60835,7 @@ function axisVector(axis, sign2 = 1) {
|
|
|
60728
60835
|
if (axis === "Y") return [0, sign2, 0];
|
|
60729
60836
|
return [0, 0, sign2];
|
|
60730
60837
|
}
|
|
60731
|
-
function axisPosition(axis, point2) {
|
|
60838
|
+
function axisPosition$1(axis, point2) {
|
|
60732
60839
|
return point2[AXIS_INDEX[axis]];
|
|
60733
60840
|
}
|
|
60734
60841
|
function crossPointForStation(axis, point2) {
|
|
@@ -60736,7 +60843,7 @@ function crossPointForStation(axis, point2) {
|
|
|
60736
60843
|
if (axis === "Y") return [point2[0], -point2[2]];
|
|
60737
60844
|
return [point2[1], point2[2]];
|
|
60738
60845
|
}
|
|
60739
|
-
function orientLoftToAxis(shape, axis) {
|
|
60846
|
+
function orientLoftToAxis$1(shape, axis) {
|
|
60740
60847
|
if (axis === "Z") return shape;
|
|
60741
60848
|
if (axis === "Y") return shape.rotateX(-90);
|
|
60742
60849
|
return shape.transform([0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1]);
|
|
@@ -60793,9 +60900,9 @@ function interpolateQuery(a2, b, t) {
|
|
|
60793
60900
|
}
|
|
60794
60901
|
return {
|
|
60795
60902
|
side: sideA,
|
|
60796
|
-
u: lerp$
|
|
60797
|
-
v: lerp$
|
|
60798
|
-
offset: lerp$
|
|
60903
|
+
u: lerp$4(a2.u ?? 0.5, b.u ?? 0.5, t),
|
|
60904
|
+
v: lerp$4(a2.v ?? 0.5, b.v ?? 0.5, t),
|
|
60905
|
+
offset: lerp$4(a2.offset ?? 0, b.offset ?? 0, t)
|
|
60799
60906
|
};
|
|
60800
60907
|
}
|
|
60801
60908
|
function resolvePathQueries(points) {
|
|
@@ -60862,8 +60969,8 @@ class ProductSkin {
|
|
|
60862
60969
|
this.stations = stations;
|
|
60863
60970
|
this.rails = rails;
|
|
60864
60971
|
for (const [name2, query] of Object.entries(refs)) this.refQueries.set(name2, cloneQuery(query));
|
|
60865
|
-
this.axisMin = Math.min(...stations.map((station) => axisPosition(axis, station.center)));
|
|
60866
|
-
this.axisMax = Math.max(...stations.map((station) => axisPosition(axis, station.center)));
|
|
60972
|
+
this.axisMin = Math.min(...stations.map((station) => axisPosition$1(axis, station.center)));
|
|
60973
|
+
this.axisMax = Math.max(...stations.map((station) => axisPosition$1(axis, station.center)));
|
|
60867
60974
|
this.diagnosticsValue = {
|
|
60868
60975
|
...diagnostics,
|
|
60869
60976
|
stationNames: stations.map((station) => station.name),
|
|
@@ -60920,24 +61027,24 @@ class ProductSkin {
|
|
|
60920
61027
|
}
|
|
60921
61028
|
/** Interpolate center, width, and depth at a normalized v or absolute axis value. */
|
|
60922
61029
|
stationAt(vOrAxis) {
|
|
60923
|
-
const axisValue = vOrAxis >= 0 && vOrAxis <= 1 ? lerp$
|
|
61030
|
+
const axisValue = vOrAxis >= 0 && vOrAxis <= 1 ? lerp$4(this.axisMin, this.axisMax, vOrAxis) : clamp$6(vOrAxis, this.axisMin, this.axisMax);
|
|
60924
61031
|
const sorted = this.stations;
|
|
60925
61032
|
for (let index2 = 0; index2 < sorted.length - 1; index2 += 1) {
|
|
60926
61033
|
const a2 = sorted[index2];
|
|
60927
61034
|
const b = sorted[index2 + 1];
|
|
60928
|
-
const aAxis = axisPosition(this.axis, a2.center);
|
|
60929
|
-
const bAxis = axisPosition(this.axis, b.center);
|
|
61035
|
+
const aAxis = axisPosition$1(this.axis, a2.center);
|
|
61036
|
+
const bAxis = axisPosition$1(this.axis, b.center);
|
|
60930
61037
|
if (axisValue < aAxis - EPS$5 || axisValue > bAxis + EPS$5) continue;
|
|
60931
61038
|
const span = Math.max(EPS$5, bAxis - aAxis);
|
|
60932
61039
|
const t = clamp$6((axisValue - aAxis) / span, 0, 1);
|
|
60933
61040
|
return {
|
|
60934
61041
|
axisValue,
|
|
60935
|
-
center: [lerp$
|
|
60936
|
-
width: lerp$
|
|
60937
|
-
depth: lerp$
|
|
61042
|
+
center: [lerp$4(a2.center[0], b.center[0], t), lerp$4(a2.center[1], b.center[1], t), lerp$4(a2.center[2], b.center[2], t)],
|
|
61043
|
+
width: lerp$4(a2.profile.width, b.profile.width, t),
|
|
61044
|
+
depth: lerp$4(a2.profile.depth, b.profile.depth, t),
|
|
60938
61045
|
dWidth: (b.profile.width - a2.profile.width) / span,
|
|
60939
61046
|
dDepth: (b.profile.depth - a2.profile.depth) / span,
|
|
60940
|
-
exponent: lerp$
|
|
61047
|
+
exponent: lerp$4(profileExponent(a2), profileExponent(b), t),
|
|
60941
61048
|
kind: a2.profile.kind === b.profile.kind ? a2.profile.kind : "custom"
|
|
60942
61049
|
};
|
|
60943
61050
|
}
|
|
@@ -61059,7 +61166,7 @@ class ProductSkinBuilder {
|
|
|
61059
61166
|
}
|
|
61060
61167
|
/** Set named cross-section stations for the product skin. */
|
|
61061
61168
|
stations(stations) {
|
|
61062
|
-
this.stationsValue = stations.map(toStationSpec).sort((a2, b) => axisPosition(this.axisValue, a2.center) - axisPosition(this.axisValue, b.center));
|
|
61169
|
+
this.stationsValue = stations.map(toStationSpec).sort((a2, b) => axisPosition$1(this.axisValue, a2.center) - axisPosition$1(this.axisValue, b.center));
|
|
61063
61170
|
return this;
|
|
61064
61171
|
}
|
|
61065
61172
|
/** Attach named guide rails for product-skin construction and downstream surface references. */
|
|
@@ -61109,9 +61216,9 @@ class ProductSkinBuilder {
|
|
|
61109
61216
|
const [x2, y2] = crossPointForStation(this.axisValue, station.center);
|
|
61110
61217
|
return station.profile.sketch.translate(x2, y2);
|
|
61111
61218
|
});
|
|
61112
|
-
const heights = this.stationsValue.map((station) => axisPosition(this.axisValue, station.center));
|
|
61219
|
+
const heights = this.stationsValue.map((station) => axisPosition$1(this.axisValue, station.center));
|
|
61113
61220
|
let shape = loft(localProfiles, heights, { edgeLength: this.edgeLengthValue });
|
|
61114
|
-
shape = orientLoftToAxis(shape, this.axisValue);
|
|
61221
|
+
shape = orientLoftToAxis$1(shape, this.axisValue);
|
|
61115
61222
|
if (this.colorValue) shape = shape.color(this.colorValue);
|
|
61116
61223
|
shape = applyMaterial(shape, this.materialValue).as(this.name);
|
|
61117
61224
|
const warnings = [];
|
|
@@ -61770,7 +61877,7 @@ function requirePositive$3(value, label) {
|
|
|
61770
61877
|
function clamp$5(value, min2, max2) {
|
|
61771
61878
|
return Math.max(min2, Math.min(max2, value));
|
|
61772
61879
|
}
|
|
61773
|
-
function lerp$
|
|
61880
|
+
function lerp$3(a2, b, t) {
|
|
61774
61881
|
return a2 + (b - a2) * t;
|
|
61775
61882
|
}
|
|
61776
61883
|
function add(a2, b) {
|
|
@@ -61820,19 +61927,19 @@ function transformLocal(point2, tangentAcross, normal, tangentAlong, x2, y2, z2
|
|
|
61820
61927
|
function interpolateCylinder(a2, b, t, mode) {
|
|
61821
61928
|
let delta = b.angle - a2.angle;
|
|
61822
61929
|
if (mode === "shortest" && Math.abs(delta) > 180) delta -= Math.sign(delta) * 360;
|
|
61823
|
-
return { kind: "cylinder", angle: a2.angle + delta * t, z: lerp$
|
|
61930
|
+
return { kind: "cylinder", angle: a2.angle + delta * t, z: lerp$3(a2.z, b.z, t), offset: lerp$3(a2.offset ?? 0, b.offset ?? 0, t) };
|
|
61824
61931
|
}
|
|
61825
61932
|
function interpolatePlane(a2, b, t) {
|
|
61826
|
-
return { kind: "plane", x: lerp$
|
|
61933
|
+
return { kind: "plane", x: lerp$3(a2.x, b.x, t), y: lerp$3(a2.y, b.y, t), offset: lerp$3(a2.offset ?? 0, b.offset ?? 0, t) };
|
|
61827
61934
|
}
|
|
61828
61935
|
function interpolateProductSkin(a2, b, t) {
|
|
61829
61936
|
if ((a2.side ?? b.side) !== (b.side ?? a2.side)) throw new Error("SurfacePath on ProductSkin currently supports one side per path; split side transitions into separate members.");
|
|
61830
61937
|
return {
|
|
61831
61938
|
kind: "productSkin",
|
|
61832
61939
|
side: a2.side ?? b.side,
|
|
61833
|
-
u: lerp$
|
|
61834
|
-
v: lerp$
|
|
61835
|
-
offset: lerp$
|
|
61940
|
+
u: lerp$3(a2.u ?? 0.5, b.u ?? 0.5, t),
|
|
61941
|
+
v: lerp$3(a2.v ?? 0.5, b.v ?? 0.5, t),
|
|
61942
|
+
offset: lerp$3(a2.offset ?? 0, b.offset ?? 0, t)
|
|
61836
61943
|
};
|
|
61837
61944
|
}
|
|
61838
61945
|
class SurfacePath {
|
|
@@ -62155,11 +62262,11 @@ function coordinateOnSide(coordinate, side, label) {
|
|
|
62155
62262
|
return { ...coordinate, kind: "productSkin", side };
|
|
62156
62263
|
}
|
|
62157
62264
|
class ProductSkinCarrier {
|
|
62158
|
-
constructor(skin, name = skin.name,
|
|
62265
|
+
constructor(skin, name = skin.name, sideValue2, offsetValue = 0) {
|
|
62159
62266
|
__publicField(this, "kind", "productSkin");
|
|
62160
62267
|
this.skin = skin;
|
|
62161
62268
|
this.name = name;
|
|
62162
|
-
this.sideValue =
|
|
62269
|
+
this.sideValue = sideValue2;
|
|
62163
62270
|
this.offsetValue = offsetValue;
|
|
62164
62271
|
}
|
|
62165
62272
|
surface(side) {
|
|
@@ -62930,7 +63037,7 @@ function counterboresForPlate(spec2, width, height, thickness, diagnostics) {
|
|
|
62930
63037
|
function minWidthAcrossAlongRange(widthAtT, length4, minAlong, maxAlong) {
|
|
62931
63038
|
let minWidth = Number.POSITIVE_INFINITY;
|
|
62932
63039
|
for (let index2 = 0; index2 <= 8; index2 += 1) {
|
|
62933
|
-
const along = lerp$
|
|
63040
|
+
const along = lerp$3(minAlong, maxAlong, index2 / 8);
|
|
62934
63041
|
const t = Math.max(0, Math.min(1, (along + length4 / 2) / Math.max(length4, 1e-8)));
|
|
62935
63042
|
minWidth = Math.min(minWidth, widthAtT(t));
|
|
62936
63043
|
}
|
|
@@ -63230,7 +63337,7 @@ function pathParameterAtDistance(samples, distance2) {
|
|
|
63230
63337
|
const segmentLength = Math.hypot(b.point[0] - a2.point[0], b.point[1] - a2.point[1], b.point[2] - a2.point[2]);
|
|
63231
63338
|
if (traveled + segmentLength >= distance2) {
|
|
63232
63339
|
const localT = segmentLength <= 1e-8 ? 0 : (distance2 - traveled) / segmentLength;
|
|
63233
|
-
return lerp$
|
|
63340
|
+
return lerp$3(a2.t, b.t, localT);
|
|
63234
63341
|
}
|
|
63235
63342
|
traveled += segmentLength;
|
|
63236
63343
|
}
|
|
@@ -63283,7 +63390,7 @@ function compileBandFootprintMesh(path2, input) {
|
|
|
63283
63390
|
const width = input.widthAt(t);
|
|
63284
63391
|
const along = distance2 - length4 / 2;
|
|
63285
63392
|
for (let acrossIndex = 0; acrossIndex <= acrossSegments; acrossIndex += 1) {
|
|
63286
|
-
const across = lerp$
|
|
63393
|
+
const across = lerp$3(-width / 2, width / 2, acrossIndex / acrossSegments);
|
|
63287
63394
|
mesh.vertices.push(pointAtProfile([across, along], false));
|
|
63288
63395
|
}
|
|
63289
63396
|
}
|
|
@@ -63293,7 +63400,7 @@ function compileBandFootprintMesh(path2, input) {
|
|
|
63293
63400
|
const width = input.widthAt(t);
|
|
63294
63401
|
const along = distance2 - length4 / 2;
|
|
63295
63402
|
for (let acrossIndex = 0; acrossIndex <= acrossSegments; acrossIndex += 1) {
|
|
63296
|
-
const across = lerp$
|
|
63403
|
+
const across = lerp$3(-width / 2, width / 2, acrossIndex / acrossSegments);
|
|
63297
63404
|
mesh.vertices.push(pointAtProfile([across, along], true));
|
|
63298
63405
|
}
|
|
63299
63406
|
}
|
|
@@ -63305,7 +63412,7 @@ function compileBandFootprintMesh(path2, input) {
|
|
|
63305
63412
|
const width = input.widthAt(t);
|
|
63306
63413
|
const along = distance2 - length4 / 2;
|
|
63307
63414
|
for (let acrossIndex = 0; acrossIndex < acrossSegments; acrossIndex += 1) {
|
|
63308
|
-
const across = lerp$
|
|
63415
|
+
const across = lerp$3(-width / 2, width / 2, (acrossIndex + 0.5) / acrossSegments);
|
|
63309
63416
|
filled[alongIndex][acrossIndex] = !holes.some((hole2) => pointInProfileLoop([across, along], hole2));
|
|
63310
63417
|
}
|
|
63311
63418
|
}
|
|
@@ -67201,7 +67308,7 @@ const Constraint = {
|
|
|
67201
67308
|
return builder.constrain({ type: "length", line: resolveLineId(builder, line2), value });
|
|
67202
67309
|
}
|
|
67203
67310
|
};
|
|
67204
|
-
function requireVec3(v, label) {
|
|
67311
|
+
function requireVec3$1(v, label) {
|
|
67205
67312
|
if (!Array.isArray(v) || v.length !== 3 || !Number.isFinite(v[0]) || !Number.isFinite(v[1]) || !Number.isFinite(v[2])) {
|
|
67206
67313
|
throw new Error(`${label} must be a [number, number, number] with finite values, got ${JSON.stringify(v)}`);
|
|
67207
67314
|
}
|
|
@@ -67214,24 +67321,24 @@ function requireFiniteNumber(n, label) {
|
|
|
67214
67321
|
return n;
|
|
67215
67322
|
}
|
|
67216
67323
|
function distance$1(a2, b) {
|
|
67217
|
-
requireVec3(a2, "a");
|
|
67218
|
-
requireVec3(b, "b");
|
|
67324
|
+
requireVec3$1(a2, "a");
|
|
67325
|
+
requireVec3$1(b, "b");
|
|
67219
67326
|
return Math.hypot(b[0] - a2[0], b[1] - a2[1], b[2] - a2[2]);
|
|
67220
67327
|
}
|
|
67221
67328
|
function midpoint$1(a2, b) {
|
|
67222
|
-
requireVec3(a2, "a");
|
|
67223
|
-
requireVec3(b, "b");
|
|
67329
|
+
requireVec3$1(a2, "a");
|
|
67330
|
+
requireVec3$1(b, "b");
|
|
67224
67331
|
return [(a2[0] + b[0]) / 2, (a2[1] + b[1]) / 2, (a2[2] + b[2]) / 2];
|
|
67225
67332
|
}
|
|
67226
|
-
function lerp(a2, b, t) {
|
|
67227
|
-
requireVec3(a2, "a");
|
|
67228
|
-
requireVec3(b, "b");
|
|
67333
|
+
function lerp$2(a2, b, t) {
|
|
67334
|
+
requireVec3$1(a2, "a");
|
|
67335
|
+
requireVec3$1(b, "b");
|
|
67229
67336
|
requireFiniteNumber(t, "t");
|
|
67230
67337
|
return [a2[0] + (b[0] - a2[0]) * t, a2[1] + (b[1] - a2[1]) * t, a2[2] + (b[2] - a2[2]) * t];
|
|
67231
67338
|
}
|
|
67232
67339
|
function direction(a2, b) {
|
|
67233
|
-
requireVec3(a2, "a");
|
|
67234
|
-
requireVec3(b, "b");
|
|
67340
|
+
requireVec3$1(a2, "a");
|
|
67341
|
+
requireVec3$1(b, "b");
|
|
67235
67342
|
const dx = b[0] - a2[0];
|
|
67236
67343
|
const dy = b[1] - a2[1];
|
|
67237
67344
|
const dz = b[2] - a2[2];
|
|
@@ -67242,8 +67349,8 @@ function direction(a2, b) {
|
|
|
67242
67349
|
return [dx / len2, dy / len2, dz / len2];
|
|
67243
67350
|
}
|
|
67244
67351
|
function offset(point2, dir, amount) {
|
|
67245
|
-
requireVec3(point2, "point");
|
|
67246
|
-
requireVec3(dir, "dir");
|
|
67352
|
+
requireVec3$1(point2, "point");
|
|
67353
|
+
requireVec3$1(dir, "dir");
|
|
67247
67354
|
requireFiniteNumber(amount, "amount");
|
|
67248
67355
|
return [point2[0] + dir[0] * amount, point2[1] + dir[1] * amount, point2[2] + dir[2] * amount];
|
|
67249
67356
|
}
|
|
@@ -67253,7 +67360,7 @@ const Points = {
|
|
|
67253
67360
|
/** Center point between two 3D points. */
|
|
67254
67361
|
midpoint: midpoint$1,
|
|
67255
67362
|
/** Linearly interpolate between two 3D points. t=0 returns a, t=1 returns b. */
|
|
67256
|
-
lerp,
|
|
67363
|
+
lerp: lerp$2,
|
|
67257
67364
|
/** Unit direction vector from a to b. Throws if a and b are the same point. */
|
|
67258
67365
|
direction,
|
|
67259
67366
|
/** Move a point along a direction vector by a given amount. */
|
|
@@ -72385,9 +72492,84 @@ class ConstraintSketch extends Sketch {
|
|
|
72385
72492
|
* Select the single arrangement region that contains the given seed point.
|
|
72386
72493
|
* Throws if no region contains the seed.
|
|
72387
72494
|
*/
|
|
72388
|
-
detectArrangementRegion(
|
|
72495
|
+
detectArrangementRegion(_seed) {
|
|
72389
72496
|
throw new Error("Not implemented");
|
|
72390
72497
|
}
|
|
72498
|
+
/**
|
|
72499
|
+
* Return the solved constrained path as a sampled 2D polyline.
|
|
72500
|
+
*
|
|
72501
|
+
* Use this when a construction rail was authored with `constrainedSketch()`
|
|
72502
|
+
* and should feed another operation such as `Loft.pathOnXz(...)`.
|
|
72503
|
+
* The sketch must contain exactly one profile path.
|
|
72504
|
+
*
|
|
72505
|
+
* @param samples - Samples per curved segment. Default 32.
|
|
72506
|
+
* @returns The solved path as an open polyline.
|
|
72507
|
+
*/
|
|
72508
|
+
toPolyline(samples = 32) {
|
|
72509
|
+
if (!Number.isFinite(samples) || samples < 2) throw new Error("ConstraintSketch.toPolyline() samples must be at least 2");
|
|
72510
|
+
const profileLoops = this.definition.loops.filter((loop) => loop.type === "profile");
|
|
72511
|
+
if (profileLoops.length !== 1) {
|
|
72512
|
+
throw new Error("ConstraintSketch.toPolyline() requires exactly one profile path");
|
|
72513
|
+
}
|
|
72514
|
+
const sampleCount = Math.max(2, Math.round(samples));
|
|
72515
|
+
const pointMap = new Map(this.definition.points.map((point2) => [point2.id, point2]));
|
|
72516
|
+
const lineMap = new Map(this.definition.lines.map((line2) => [line2.id, line2]));
|
|
72517
|
+
const arcMap = new Map(this.definition.arcs.map((arc) => [arc.id, arc]));
|
|
72518
|
+
const bezierMap = new Map(this.definition.beziers.map((bezier) => [bezier.id, bezier]));
|
|
72519
|
+
const points = [];
|
|
72520
|
+
const appendStart = (point2, label) => {
|
|
72521
|
+
const previous = points[points.length - 1];
|
|
72522
|
+
if (!previous) {
|
|
72523
|
+
points.push(point2);
|
|
72524
|
+
return;
|
|
72525
|
+
}
|
|
72526
|
+
if (Math.hypot(point2[0] - previous[0], point2[1] - previous[1]) > 1e-6) {
|
|
72527
|
+
throw new Error(`ConstraintSketch.toPolyline() profile path is not continuous at ${label}`);
|
|
72528
|
+
}
|
|
72529
|
+
};
|
|
72530
|
+
const appendPoint = (point2) => {
|
|
72531
|
+
const previous = points[points.length - 1];
|
|
72532
|
+
if (!previous || Math.hypot(point2[0] - previous[0], point2[1] - previous[1]) > 1e-9) points.push(point2);
|
|
72533
|
+
};
|
|
72534
|
+
const requirePoint = (id, label) => {
|
|
72535
|
+
const point2 = pointMap.get(id);
|
|
72536
|
+
if (!point2) throw new Error(`ConstraintSketch.toPolyline() missing ${label}`);
|
|
72537
|
+
return [point2.x, point2.y];
|
|
72538
|
+
};
|
|
72539
|
+
for (const segment of profileLoops[0].segments) {
|
|
72540
|
+
if (segment.kind === "line") {
|
|
72541
|
+
const line2 = lineMap.get(segment.line);
|
|
72542
|
+
if (!line2) throw new Error(`ConstraintSketch.toPolyline() missing line "${segment.line}"`);
|
|
72543
|
+
appendStart(requirePoint(line2.a, `line "${segment.line}" start point`), `line "${segment.line}"`);
|
|
72544
|
+
appendPoint(requirePoint(line2.b, `line "${segment.line}" end point`));
|
|
72545
|
+
} else if (segment.kind === "arc") {
|
|
72546
|
+
const arc = arcMap.get(segment.arc);
|
|
72547
|
+
if (!arc) throw new Error(`ConstraintSketch.toPolyline() missing arc "${segment.arc}"`);
|
|
72548
|
+
const center = requirePoint(arc.center, `arc "${segment.arc}" center point`);
|
|
72549
|
+
const start = requirePoint(arc.start, `arc "${segment.arc}" start point`);
|
|
72550
|
+
const end = requirePoint(arc.end, `arc "${segment.arc}" end point`);
|
|
72551
|
+
appendStart(start, `arc "${segment.arc}"`);
|
|
72552
|
+
const startAngle = Math.atan2(start[1] - center[1], start[0] - center[0]);
|
|
72553
|
+
const endAngle = Math.atan2(end[1] - center[1], end[0] - center[0]);
|
|
72554
|
+
for (const point2 of tessellateArc(center[0], center[1], arc.radius, startAngle, endAngle, arc.clockwise, sampleCount)) {
|
|
72555
|
+
appendPoint(point2);
|
|
72556
|
+
}
|
|
72557
|
+
} else {
|
|
72558
|
+
const bezier = bezierMap.get(segment.bezier);
|
|
72559
|
+
if (!bezier) throw new Error(`ConstraintSketch.toPolyline() missing bezier "${segment.bezier}"`);
|
|
72560
|
+
const p0 = requirePoint(bezier.p0, `bezier "${segment.bezier}" start point`);
|
|
72561
|
+
const p1 = requirePoint(bezier.p1, `bezier "${segment.bezier}" first control point`);
|
|
72562
|
+
const p2 = requirePoint(bezier.p2, `bezier "${segment.bezier}" second control point`);
|
|
72563
|
+
const p3 = requirePoint(bezier.p3, `bezier "${segment.bezier}" end point`);
|
|
72564
|
+
appendStart(p0, `bezier "${segment.bezier}"`);
|
|
72565
|
+
for (const point2 of tessellateBezier(p0[0], p0[1], p1[0], p1[1], p2[0], p2[1], p3[0], p3[1], sampleCount)) {
|
|
72566
|
+
appendPoint(point2);
|
|
72567
|
+
}
|
|
72568
|
+
}
|
|
72569
|
+
}
|
|
72570
|
+
if (points.length < 2) throw new Error("ConstraintSketch.toPolyline() needs at least 2 points");
|
|
72571
|
+
return points;
|
|
72572
|
+
}
|
|
72391
72573
|
/**
|
|
72392
72574
|
* Re-solve the sketch after changing the value of one existing constraint.
|
|
72393
72575
|
*
|
|
@@ -87672,6 +87854,295 @@ function polygonVertices(sides, radius, options) {
|
|
|
87672
87854
|
centerY: options == null ? void 0 : options.centerY
|
|
87673
87855
|
});
|
|
87674
87856
|
}
|
|
87857
|
+
const LOFT_GUIDE_EPS = 1e-8;
|
|
87858
|
+
function orientLoftToAxis(shape, axis) {
|
|
87859
|
+
if (axis === "Z") return shape;
|
|
87860
|
+
if (axis === "Y") return shape.rotateX(-90);
|
|
87861
|
+
return shape.transform([0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1]);
|
|
87862
|
+
}
|
|
87863
|
+
function buildRailEvaluators(rails, axis, start, end, railSamples) {
|
|
87864
|
+
const seen = /* @__PURE__ */ new Set();
|
|
87865
|
+
return rails.map((rail2) => {
|
|
87866
|
+
if (seen.has(rail2.side)) throw new Error(`Loft.withGuideRails() received more than one ${rail2.side} rail`);
|
|
87867
|
+
seen.add(rail2.side);
|
|
87868
|
+
const sampled = sampleRailPath(rail2.path, railSamples);
|
|
87869
|
+
if (sampled.length < 2) throw new Error("Loft guide rails require at least two points");
|
|
87870
|
+
const points = sampled.map((point2) => ({ position: axisPosition(axis, point2), cross: crossPointForAxis(axis, point2) }));
|
|
87871
|
+
const ordered = points[points.length - 1].position >= points[0].position ? points : [...points].reverse();
|
|
87872
|
+
validateRailCoverage(ordered, start, end);
|
|
87873
|
+
return { side: rail2.side, points: ordered };
|
|
87874
|
+
});
|
|
87875
|
+
}
|
|
87876
|
+
function railCrossAt(rail2, position) {
|
|
87877
|
+
const points = rail2.points;
|
|
87878
|
+
if (position <= points[0].position + LOFT_GUIDE_EPS) return points[0].cross;
|
|
87879
|
+
const last = points[points.length - 1];
|
|
87880
|
+
if (position >= last.position - LOFT_GUIDE_EPS) return last.cross;
|
|
87881
|
+
for (let index2 = 0; index2 < points.length - 1; index2 += 1) {
|
|
87882
|
+
const a2 = points[index2];
|
|
87883
|
+
const b = points[index2 + 1];
|
|
87884
|
+
if (position >= a2.position - LOFT_GUIDE_EPS && position <= b.position + LOFT_GUIDE_EPS) {
|
|
87885
|
+
const t = (position - a2.position) / (b.position - a2.position);
|
|
87886
|
+
return [lerp$1(a2.cross[0], b.cross[0], t), lerp$1(a2.cross[1], b.cross[1], t)];
|
|
87887
|
+
}
|
|
87888
|
+
}
|
|
87889
|
+
throw new Error("Loft guide rail does not cover requested station position");
|
|
87890
|
+
}
|
|
87891
|
+
function validateRailCoverage(points, start, end) {
|
|
87892
|
+
for (let index2 = 1; index2 < points.length; index2 += 1) {
|
|
87893
|
+
if (points[index2].position - points[index2 - 1].position < LOFT_GUIDE_EPS) {
|
|
87894
|
+
throw new Error("Loft guide rails must be monotone along the loft axis");
|
|
87895
|
+
}
|
|
87896
|
+
}
|
|
87897
|
+
if (points[0].position - start > LOFT_GUIDE_EPS || end - points[points.length - 1].position > LOFT_GUIDE_EPS) {
|
|
87898
|
+
throw new Error("Loft guide rails must cover the full station range");
|
|
87899
|
+
}
|
|
87900
|
+
}
|
|
87901
|
+
function sampleRailPath(path2, samples) {
|
|
87902
|
+
if (Array.isArray(path2)) return path2.map((point2, index2) => requireVec3(point2, `Loft guide rail point ${index2}`));
|
|
87903
|
+
if (path2 instanceof Curve3D || path2 instanceof HermiteCurve3D || path2 instanceof QuinticHermiteCurve3D || path2 instanceof NurbsCurve3D) {
|
|
87904
|
+
return path2.sample(Math.max(2, Math.round(samples))).map((point2, index2) => requireVec3(point2, `Loft guide rail sample ${index2}`));
|
|
87905
|
+
}
|
|
87906
|
+
throw new Error("Loft guide rail path must be a Vec3[] or ForgeCAD 3D curve");
|
|
87907
|
+
}
|
|
87908
|
+
function requireVec3(point2, label) {
|
|
87909
|
+
if (!Array.isArray(point2) || point2.length !== 3 || !point2.every(Number.isFinite)) {
|
|
87910
|
+
throw new Error(`${label} must be a finite [x, y, z] point`);
|
|
87911
|
+
}
|
|
87912
|
+
return [point2[0], point2[1], point2[2]];
|
|
87913
|
+
}
|
|
87914
|
+
function axisPosition(axis, point2) {
|
|
87915
|
+
if (axis === "X") return point2[0];
|
|
87916
|
+
if (axis === "Y") return point2[1];
|
|
87917
|
+
return point2[2];
|
|
87918
|
+
}
|
|
87919
|
+
function crossPointForAxis(axis, point2) {
|
|
87920
|
+
if (axis === "X") return [point2[1], point2[2]];
|
|
87921
|
+
if (axis === "Y") return [point2[0], -point2[2]];
|
|
87922
|
+
return [point2[0], point2[1]];
|
|
87923
|
+
}
|
|
87924
|
+
function lerp$1(a2, b, t) {
|
|
87925
|
+
return a2 + (b - a2) * t;
|
|
87926
|
+
}
|
|
87927
|
+
function loftWithGuideRails(stations, rails, options = {}) {
|
|
87928
|
+
if (stations.length < 2) throw new Error("Loft.withGuideRails() requires at least two stations");
|
|
87929
|
+
if (rails.length === 0) throw new Error("Loft.withGuideRails() requires at least one guide rail");
|
|
87930
|
+
const sortedStations = sortedValidStations(stations);
|
|
87931
|
+
const axis = options.axis ?? "Z";
|
|
87932
|
+
const start = sortedStations[0].position;
|
|
87933
|
+
const end = sortedStations[sortedStations.length - 1].position;
|
|
87934
|
+
const railEvaluators = buildRailEvaluators(rails, axis, start, end, options.railSamples ?? 64);
|
|
87935
|
+
const positions = generatedPositions(sortedStations, options.samples);
|
|
87936
|
+
const profiles2 = positions.map((position) => {
|
|
87937
|
+
const source = profileForPosition(sortedStations, position);
|
|
87938
|
+
const bounds = boundsForPosition(sortedStations, position);
|
|
87939
|
+
return fitProfileToBounds(source, applyRailsToBounds(bounds, railEvaluators, position));
|
|
87940
|
+
});
|
|
87941
|
+
const shape = loft(profiles2, positions, {
|
|
87942
|
+
edgeLength: options.edgeLength,
|
|
87943
|
+
boundsPadding: options.boundsPadding
|
|
87944
|
+
});
|
|
87945
|
+
return orientLoftToAxis(shape, axis);
|
|
87946
|
+
}
|
|
87947
|
+
function sortedValidStations(stations) {
|
|
87948
|
+
const sorted = [...stations].sort((a2, b) => a2.position - b.position);
|
|
87949
|
+
for (let index2 = 0; index2 < sorted.length; index2 += 1) {
|
|
87950
|
+
if (!Number.isFinite(sorted[index2].position)) throw new Error("Loft.withGuideRails station position must be finite");
|
|
87951
|
+
if (!(sorted[index2].profile instanceof Sketch)) throw new Error("Loft.withGuideRails() stations must use Sketch profiles");
|
|
87952
|
+
if (index2 > 0 && sorted[index2].position - sorted[index2 - 1].position < LOFT_GUIDE_EPS) {
|
|
87953
|
+
throw new Error("Loft.withGuideRails() requires unique, strictly increasing station positions");
|
|
87954
|
+
}
|
|
87955
|
+
}
|
|
87956
|
+
return sorted;
|
|
87957
|
+
}
|
|
87958
|
+
function generatedPositions(stations, samples) {
|
|
87959
|
+
const count = Math.max(2, Math.round(samples ?? Math.max(9, (stations.length - 1) * 8 + 1)));
|
|
87960
|
+
const start = stations[0].position;
|
|
87961
|
+
const end = stations[stations.length - 1].position;
|
|
87962
|
+
const values = /* @__PURE__ */ new Set();
|
|
87963
|
+
const positions = [];
|
|
87964
|
+
const addPosition = (position) => {
|
|
87965
|
+
const key = position.toFixed(9);
|
|
87966
|
+
if (!values.has(key)) {
|
|
87967
|
+
values.add(key);
|
|
87968
|
+
positions.push(position);
|
|
87969
|
+
}
|
|
87970
|
+
};
|
|
87971
|
+
for (let index2 = 0; index2 < count; index2 += 1) addPosition(start + (end - start) * index2 / (count - 1));
|
|
87972
|
+
for (const station of stations) addPosition(station.position);
|
|
87973
|
+
return positions.sort((a2, b) => a2 - b);
|
|
87974
|
+
}
|
|
87975
|
+
function profileForPosition(stations, position) {
|
|
87976
|
+
for (let index2 = 0; index2 < stations.length - 1; index2 += 1) {
|
|
87977
|
+
if (position <= stations[index2 + 1].position + LOFT_GUIDE_EPS) return stations[index2].profile;
|
|
87978
|
+
}
|
|
87979
|
+
return stations[stations.length - 1].profile;
|
|
87980
|
+
}
|
|
87981
|
+
function boundsForPosition(stations, position) {
|
|
87982
|
+
if (position <= stations[0].position + LOFT_GUIDE_EPS) return sketchBounds(stations[0].profile);
|
|
87983
|
+
const last = stations[stations.length - 1];
|
|
87984
|
+
if (position >= last.position - LOFT_GUIDE_EPS) return sketchBounds(last.profile);
|
|
87985
|
+
for (let index2 = 0; index2 < stations.length - 1; index2 += 1) {
|
|
87986
|
+
const a2 = stations[index2];
|
|
87987
|
+
const b = stations[index2 + 1];
|
|
87988
|
+
if (position >= a2.position - LOFT_GUIDE_EPS && position <= b.position + LOFT_GUIDE_EPS) {
|
|
87989
|
+
return lerpBounds(sketchBounds(a2.profile), sketchBounds(b.profile), (position - a2.position) / (b.position - a2.position));
|
|
87990
|
+
}
|
|
87991
|
+
}
|
|
87992
|
+
return sketchBounds(last.profile);
|
|
87993
|
+
}
|
|
87994
|
+
function applyRailsToBounds(bounds, rails, position) {
|
|
87995
|
+
const centerRail = rails.find((rail2) => rail2.side === "center");
|
|
87996
|
+
const center = centerRail ? railCrossAt(centerRail, position) : void 0;
|
|
87997
|
+
const next = { ...bounds };
|
|
87998
|
+
applyAxisRail(next, "X", sideValue(rails, "left", position, 0), sideValue(rails, "right", position, 0), center == null ? void 0 : center[0]);
|
|
87999
|
+
applyAxisRail(next, "Y", sideValue(rails, "back", position, 1), sideValue(rails, "front", position, 1), center == null ? void 0 : center[1]);
|
|
88000
|
+
if (next.maxX - next.minX < LOFT_GUIDE_EPS || next.maxY - next.minY < LOFT_GUIDE_EPS) {
|
|
88001
|
+
throw new Error("Loft.withGuideRails() guide rails produced a non-positive section size");
|
|
88002
|
+
}
|
|
88003
|
+
return next;
|
|
88004
|
+
}
|
|
88005
|
+
function sideValue(rails, side, position, crossIndex) {
|
|
88006
|
+
const rail2 = rails.find((entry) => entry.side === side);
|
|
88007
|
+
return rail2 ? railCrossAt(rail2, position)[crossIndex] : void 0;
|
|
88008
|
+
}
|
|
88009
|
+
function applyAxisRail(bounds, axis, minRail, maxRail, center) {
|
|
88010
|
+
const minKey = axis === "X" ? "minX" : "minY";
|
|
88011
|
+
const maxKey = axis === "X" ? "maxX" : "maxY";
|
|
88012
|
+
const width = bounds[maxKey] - bounds[minKey];
|
|
88013
|
+
if (minRail != null && maxRail != null) {
|
|
88014
|
+
if (maxRail - minRail < LOFT_GUIDE_EPS) throw new Error("Loft.withGuideRails() opposite guide rails crossed");
|
|
88015
|
+
if (center != null && Math.abs((minRail + maxRail) / 2 - center) > 1e-5) {
|
|
88016
|
+
throw new Error("Loft.withGuideRails() center rail conflicts with opposite side rails");
|
|
88017
|
+
}
|
|
88018
|
+
bounds[minKey] = minRail;
|
|
88019
|
+
bounds[maxKey] = maxRail;
|
|
88020
|
+
} else if (maxRail != null) {
|
|
88021
|
+
bounds[maxKey] = maxRail;
|
|
88022
|
+
bounds[minKey] = center != null ? 2 * center - maxRail : maxRail - width;
|
|
88023
|
+
} else if (minRail != null) {
|
|
88024
|
+
bounds[minKey] = minRail;
|
|
88025
|
+
bounds[maxKey] = center != null ? 2 * center - minRail : minRail + width;
|
|
88026
|
+
} else if (center != null) {
|
|
88027
|
+
bounds[minKey] = center - width / 2;
|
|
88028
|
+
bounds[maxKey] = center + width / 2;
|
|
88029
|
+
}
|
|
88030
|
+
}
|
|
88031
|
+
function fitProfileToBounds(profile, target) {
|
|
88032
|
+
const source = sketchBounds(profile);
|
|
88033
|
+
const sourceWidth = source.maxX - source.minX;
|
|
88034
|
+
const sourceDepth = source.maxY - source.minY;
|
|
88035
|
+
if (sourceWidth < LOFT_GUIDE_EPS || sourceDepth < LOFT_GUIDE_EPS) {
|
|
88036
|
+
throw new Error("Loft.withGuideRails() station profiles must have positive bounds");
|
|
88037
|
+
}
|
|
88038
|
+
const sourceCenter = [(source.minX + source.maxX) / 2, (source.minY + source.maxY) / 2];
|
|
88039
|
+
const targetCenter = [(target.minX + target.maxX) / 2, (target.minY + target.maxY) / 2];
|
|
88040
|
+
return profile.scaleAround(sourceCenter, [(target.maxX - target.minX) / sourceWidth, (target.maxY - target.minY) / sourceDepth]).translate(targetCenter[0] - sourceCenter[0], targetCenter[1] - sourceCenter[1]);
|
|
88041
|
+
}
|
|
88042
|
+
function sketchBounds(profile) {
|
|
88043
|
+
const bounds = profile.bounds();
|
|
88044
|
+
return { minX: bounds.min[0], maxX: bounds.max[0], minY: bounds.min[1], maxY: bounds.max[1] };
|
|
88045
|
+
}
|
|
88046
|
+
function lerpBounds(a2, b, t) {
|
|
88047
|
+
return {
|
|
88048
|
+
minX: lerp(a2.minX, b.minX, t),
|
|
88049
|
+
maxX: lerp(a2.maxX, b.maxX, t),
|
|
88050
|
+
minY: lerp(a2.minY, b.minY, t),
|
|
88051
|
+
maxY: lerp(a2.maxY, b.maxY, t)
|
|
88052
|
+
};
|
|
88053
|
+
}
|
|
88054
|
+
function lerp(a2, b, t) {
|
|
88055
|
+
return a2 + (b - a2) * t;
|
|
88056
|
+
}
|
|
88057
|
+
function mapLoftPath2D(path2, label, mapper) {
|
|
88058
|
+
const points = sampleLoftPath2D(path2, label);
|
|
88059
|
+
return points.map((point2, index2) => {
|
|
88060
|
+
if (!Array.isArray(point2) || point2.length !== 2 || !point2.every(Number.isFinite)) {
|
|
88061
|
+
throw new Error(`${label} point ${index2} must be a finite [x, y] point`);
|
|
88062
|
+
}
|
|
88063
|
+
return mapper([point2[0], point2[1]]);
|
|
88064
|
+
});
|
|
88065
|
+
}
|
|
88066
|
+
function sampleLoftPath2D(path2, label) {
|
|
88067
|
+
if (Array.isArray(path2)) {
|
|
88068
|
+
if (path2.length < 2) throw new Error(`${label} requires at least two [x, y] points`);
|
|
88069
|
+
return path2;
|
|
88070
|
+
}
|
|
88071
|
+
if (!path2 || typeof path2 !== "object" || typeof path2.toPolyline !== "function") {
|
|
88072
|
+
throw new Error(`${label} requires a 2D path, solved constrained path, or [x, y] point array`);
|
|
88073
|
+
}
|
|
88074
|
+
const points = path2.toPolyline();
|
|
88075
|
+
if (!Array.isArray(points) || points.length < 2) throw new Error(`${label} path must produce at least two [x, y] points`);
|
|
88076
|
+
return points;
|
|
88077
|
+
}
|
|
88078
|
+
const Loft = {
|
|
88079
|
+
/** Create a loft station from a 2D profile and an axis position. */
|
|
88080
|
+
station(profile, position) {
|
|
88081
|
+
if (!Number.isFinite(position)) throw new Error("Loft.station position must be finite");
|
|
88082
|
+
return { profile, position };
|
|
88083
|
+
},
|
|
88084
|
+
/** Create a guide rail that constrains the section-local negative-X side. */
|
|
88085
|
+
leftRail(path2) {
|
|
88086
|
+
return { side: "left", path: path2 };
|
|
88087
|
+
},
|
|
88088
|
+
/** Create a guide rail that constrains the section-local positive-X side. */
|
|
88089
|
+
rightRail(path2) {
|
|
88090
|
+
return { side: "right", path: path2 };
|
|
88091
|
+
},
|
|
88092
|
+
/** Create a guide rail that constrains the section-local positive-Y side. */
|
|
88093
|
+
frontRail(path2) {
|
|
88094
|
+
return { side: "front", path: path2 };
|
|
88095
|
+
},
|
|
88096
|
+
/** Create a guide rail that constrains the section-local negative-Y side. */
|
|
88097
|
+
backRail(path2) {
|
|
88098
|
+
return { side: "back", path: path2 };
|
|
88099
|
+
},
|
|
88100
|
+
/** Create a guide rail that moves section centers along the loft. */
|
|
88101
|
+
centerRail(path2) {
|
|
88102
|
+
return { side: "center", path: path2 };
|
|
88103
|
+
},
|
|
88104
|
+
/**
|
|
88105
|
+
* Place a 2D guide path onto the XZ plane.
|
|
88106
|
+
*
|
|
88107
|
+
* The path's first coordinate becomes X and its second coordinate becomes Z.
|
|
88108
|
+
* Use this for left/right silhouette rails authored with `path()` or `constrainedSketch()`.
|
|
88109
|
+
*/
|
|
88110
|
+
pathOnXz(path2, y2 = 0) {
|
|
88111
|
+
if (!Number.isFinite(y2)) throw new Error("Loft.pathOnXz y must be finite");
|
|
88112
|
+
return mapLoftPath2D(path2, "Loft.pathOnXz", ([x2, z2]) => [x2, y2, z2]);
|
|
88113
|
+
},
|
|
88114
|
+
/**
|
|
88115
|
+
* Place a 2D guide path onto the YZ plane.
|
|
88116
|
+
*
|
|
88117
|
+
* The path's first coordinate becomes Y and its second coordinate becomes Z.
|
|
88118
|
+
* Use this for front/back crown rails authored with `path()` or `constrainedSketch()`.
|
|
88119
|
+
*/
|
|
88120
|
+
pathOnYz(path2, x2 = 0) {
|
|
88121
|
+
if (!Number.isFinite(x2)) throw new Error("Loft.pathOnYz x must be finite");
|
|
88122
|
+
return mapLoftPath2D(path2, "Loft.pathOnYz", ([y2, z2]) => [x2, y2, z2]);
|
|
88123
|
+
},
|
|
88124
|
+
/**
|
|
88125
|
+
* Place a 2D guide path onto the XY plane.
|
|
88126
|
+
*
|
|
88127
|
+
* The path's first coordinate becomes X and its second coordinate becomes Y.
|
|
88128
|
+
* Use this when lofting along X or Y and a rail lives in a horizontal sketch plane.
|
|
88129
|
+
*/
|
|
88130
|
+
pathOnXy(path2, z2 = 0) {
|
|
88131
|
+
if (!Number.isFinite(z2)) throw new Error("Loft.pathOnXy z must be finite");
|
|
88132
|
+
return mapLoftPath2D(path2, "Loft.pathOnXy", ([x2, y2]) => [x2, y2, z2]);
|
|
88133
|
+
},
|
|
88134
|
+
/**
|
|
88135
|
+
* Loft through profile stations while forcing generated sections to follow guide rails.
|
|
88136
|
+
*
|
|
88137
|
+
* Stations define the cross-section family. Guide rails define the side or center
|
|
88138
|
+
* paths the loft must pass through. With opposite side rails, the section is scaled
|
|
88139
|
+
* to touch both rails. With one side rail, the section keeps its interpolated size
|
|
88140
|
+
* unless a center rail is also present.
|
|
88141
|
+
*/
|
|
88142
|
+
withGuideRails(stations, rails, options = {}) {
|
|
88143
|
+
return loftWithGuideRails(stations, rails, options);
|
|
88144
|
+
}
|
|
88145
|
+
};
|
|
87675
88146
|
let collectedHighlights = [];
|
|
87676
88147
|
function resetHighlights() {
|
|
87677
88148
|
collectedHighlights = [];
|
|
@@ -305140,6 +305611,7 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
|
|
|
305140
305611
|
nurbsSurface,
|
|
305141
305612
|
spline2d,
|
|
305142
305613
|
spline3d,
|
|
305614
|
+
Loft,
|
|
305143
305615
|
loft,
|
|
305144
305616
|
loftAlongSpine,
|
|
305145
305617
|
sweep,
|