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$7(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];
|
|
@@ -5691,13 +5691,13 @@ function parseMeshFile(data, format) {
|
|
|
5691
5691
|
return parse3mf(data);
|
|
5692
5692
|
}
|
|
5693
5693
|
}
|
|
5694
|
-
const EPS$
|
|
5694
|
+
const EPS$a = 1e-8;
|
|
5695
5695
|
function length$3(v) {
|
|
5696
5696
|
return Math.hypot(v[0], v[1], v[2]);
|
|
5697
5697
|
}
|
|
5698
5698
|
function normalize$6(v) {
|
|
5699
5699
|
const len2 = length$3(v);
|
|
5700
|
-
if (len2 < EPS$
|
|
5700
|
+
if (len2 < EPS$a) throw new Error("Plane normal must be non-zero");
|
|
5701
5701
|
return [v[0] / len2, v[1] / len2, v[2] / len2];
|
|
5702
5702
|
}
|
|
5703
5703
|
function resolvePlaneOriginNormal(plane) {
|
|
@@ -5719,12 +5719,12 @@ function resolvePlaneOriginNormal(plane) {
|
|
|
5719
5719
|
function rotationToPlaneSpace(normal) {
|
|
5720
5720
|
const n = normalize$6(normal);
|
|
5721
5721
|
const dot2 = n[2];
|
|
5722
|
-
if (dot2 > 1 - EPS$
|
|
5722
|
+
if (dot2 > 1 - EPS$a) {
|
|
5723
5723
|
return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
|
|
5724
5724
|
}
|
|
5725
5725
|
let axis;
|
|
5726
5726
|
let angle;
|
|
5727
|
-
if (dot2 < -1 + EPS$
|
|
5727
|
+
if (dot2 < -1 + EPS$a) {
|
|
5728
5728
|
axis = [1, 0, 0];
|
|
5729
5729
|
angle = Math.PI;
|
|
5730
5730
|
} else {
|
|
@@ -8286,7 +8286,7 @@ function scale$6(v, s) {
|
|
|
8286
8286
|
function sub$7(a2, b) {
|
|
8287
8287
|
return [a2[0] - b[0], a2[1] - b[1], a2[2] - b[2]];
|
|
8288
8288
|
}
|
|
8289
|
-
function cross$
|
|
8289
|
+
function cross$8(a2, b) {
|
|
8290
8290
|
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]];
|
|
8291
8291
|
}
|
|
8292
8292
|
function makeEdge(name, start, end, faceName, curve) {
|
|
@@ -8322,7 +8322,7 @@ function buildSurfaceSheetTopology(boundaries, options = {}) {
|
|
|
8322
8322
|
const center = options.center ?? average$1(corners);
|
|
8323
8323
|
const uAxis = normalizeAxis$1(sub$7(midpoint$3(u1Start, u1End), midpoint$3(u0Start, u0End)));
|
|
8324
8324
|
const vAxis = normalizeAxis$1(sub$7(midpoint$3(v1Start, v1End), midpoint$3(v0Start, v0End)));
|
|
8325
|
-
const normal = normalizeAxis$1(options.normal ?? cross$
|
|
8325
|
+
const normal = normalizeAxis$1(options.normal ?? cross$8(uAxis, vAxis));
|
|
8326
8326
|
const faces = /* @__PURE__ */ new Map();
|
|
8327
8327
|
faces.set(faceName, {
|
|
8328
8328
|
name: faceName,
|
|
@@ -10224,6 +10224,7 @@ function buildSweepLevelSetInput(profilePolygons, pathInput, options) {
|
|
|
10224
10224
|
edgeLength: options.edgeLength
|
|
10225
10225
|
};
|
|
10226
10226
|
}
|
|
10227
|
+
const EPS$9 = 1e-9;
|
|
10227
10228
|
function resamplePolygon(poly, targetCount) {
|
|
10228
10229
|
if (poly.length < 2) return poly;
|
|
10229
10230
|
if (targetCount <= 0) return [];
|
|
@@ -10261,6 +10262,78 @@ function resamplePolygon(poly, targetCount) {
|
|
|
10261
10262
|
}
|
|
10262
10263
|
return out;
|
|
10263
10264
|
}
|
|
10265
|
+
function resamplePolygonByAngle(poly, targetCount, center = polygonCentroid$2(poly)) {
|
|
10266
|
+
if (poly.length < 3 || targetCount <= 0) return null;
|
|
10267
|
+
if (!isConvexPolygon(poly)) return null;
|
|
10268
|
+
const out = [];
|
|
10269
|
+
for (let index2 = 0; index2 < targetCount; index2 += 1) {
|
|
10270
|
+
const angle = index2 / targetCount * Math.PI * 2;
|
|
10271
|
+
const point2 = rayPolygonIntersection(center, [Math.cos(angle), Math.sin(angle)], poly);
|
|
10272
|
+
if (!point2) return null;
|
|
10273
|
+
out.push(point2);
|
|
10274
|
+
}
|
|
10275
|
+
return out;
|
|
10276
|
+
}
|
|
10277
|
+
function rayPolygonIntersection(origin, direction2, poly) {
|
|
10278
|
+
let bestT = Infinity;
|
|
10279
|
+
let best = null;
|
|
10280
|
+
for (let index2 = 0; index2 < poly.length; index2 += 1) {
|
|
10281
|
+
const a2 = poly[index2];
|
|
10282
|
+
const b = poly[(index2 + 1) % poly.length];
|
|
10283
|
+
const edge = [b[0] - a2[0], b[1] - a2[1]];
|
|
10284
|
+
const denom = cross$7(direction2, edge);
|
|
10285
|
+
if (Math.abs(denom) < EPS$9) continue;
|
|
10286
|
+
const delta = [a2[0] - origin[0], a2[1] - origin[1]];
|
|
10287
|
+
const rayT = cross$7(delta, edge) / denom;
|
|
10288
|
+
const edgeT = cross$7(delta, direction2) / denom;
|
|
10289
|
+
if (rayT >= -EPS$9 && edgeT >= -EPS$9 && edgeT <= 1 + EPS$9 && rayT < bestT) {
|
|
10290
|
+
bestT = rayT;
|
|
10291
|
+
best = [origin[0] + direction2[0] * rayT, origin[1] + direction2[1] * rayT];
|
|
10292
|
+
}
|
|
10293
|
+
}
|
|
10294
|
+
return best;
|
|
10295
|
+
}
|
|
10296
|
+
function polygonCentroid$2(poly) {
|
|
10297
|
+
let area2 = 0;
|
|
10298
|
+
let cx = 0;
|
|
10299
|
+
let cy = 0;
|
|
10300
|
+
for (let index2 = 0; index2 < poly.length; index2 += 1) {
|
|
10301
|
+
const a2 = poly[index2];
|
|
10302
|
+
const b = poly[(index2 + 1) % poly.length];
|
|
10303
|
+
const crossValue = cross$7(a2, b);
|
|
10304
|
+
area2 += crossValue;
|
|
10305
|
+
cx += (a2[0] + b[0]) * crossValue;
|
|
10306
|
+
cy += (a2[1] + b[1]) * crossValue;
|
|
10307
|
+
}
|
|
10308
|
+
if (Math.abs(area2) < EPS$9) return averagePoint(poly);
|
|
10309
|
+
return [cx / (3 * area2), cy / (3 * area2)];
|
|
10310
|
+
}
|
|
10311
|
+
function averagePoint(poly) {
|
|
10312
|
+
let x2 = 0;
|
|
10313
|
+
let y2 = 0;
|
|
10314
|
+
for (const point2 of poly) {
|
|
10315
|
+
x2 += point2[0];
|
|
10316
|
+
y2 += point2[1];
|
|
10317
|
+
}
|
|
10318
|
+
return [x2 / poly.length, y2 / poly.length];
|
|
10319
|
+
}
|
|
10320
|
+
function isConvexPolygon(poly) {
|
|
10321
|
+
let sign2 = 0;
|
|
10322
|
+
for (let index2 = 0; index2 < poly.length; index2 += 1) {
|
|
10323
|
+
const a2 = poly[index2];
|
|
10324
|
+
const b = poly[(index2 + 1) % poly.length];
|
|
10325
|
+
const c2 = poly[(index2 + 2) % poly.length];
|
|
10326
|
+
const turn = cross$7([b[0] - a2[0], b[1] - a2[1]], [c2[0] - b[0], c2[1] - b[1]]);
|
|
10327
|
+
if (Math.abs(turn) < EPS$9) continue;
|
|
10328
|
+
const currentSign = Math.sign(turn);
|
|
10329
|
+
if (sign2 !== 0 && currentSign !== sign2) return false;
|
|
10330
|
+
sign2 = currentSign;
|
|
10331
|
+
}
|
|
10332
|
+
return sign2 !== 0;
|
|
10333
|
+
}
|
|
10334
|
+
function cross$7(a2, b) {
|
|
10335
|
+
return a2[0] * b[1] - a2[1] * b[0];
|
|
10336
|
+
}
|
|
10264
10337
|
function loftStitched(profiles2, heights, wasm) {
|
|
10265
10338
|
if (profiles2.length < 2) return null;
|
|
10266
10339
|
const classified = profiles2.map((loops) => classifyLoops(loops));
|
|
@@ -10389,8 +10462,10 @@ function stitchSingleLoopLoft(loops, heights, wasm) {
|
|
|
10389
10462
|
maxPoints = Math.max(maxPoints, loop.length);
|
|
10390
10463
|
}
|
|
10391
10464
|
const N = Math.max(maxPoints, 24);
|
|
10465
|
+
const angularSamples = normalizedLoops.map((loop) => resamplePolygonByAngle(loop, N));
|
|
10466
|
+
const useAngularSamples = angularSamples.every((samples) => samples != null);
|
|
10392
10467
|
const resampled = normalizedLoops.map((loop, i) => {
|
|
10393
|
-
const pts2d = resamplePolygon(loop, N);
|
|
10468
|
+
const pts2d = useAngularSamples ? angularSamples[i] : resamplePolygon(loop, N);
|
|
10394
10469
|
const z2 = heights[i];
|
|
10395
10470
|
return pts2d.map(([x2, y2]) => [x2, y2, z2]);
|
|
10396
10471
|
});
|
|
@@ -10451,7 +10526,7 @@ let _wasm$1 = null;
|
|
|
10451
10526
|
async function initManifoldWasm() {
|
|
10452
10527
|
if (_wasm$1) return _wasm$1;
|
|
10453
10528
|
performance.mark("manifold:start");
|
|
10454
|
-
const Module = (await import("./manifold-
|
|
10529
|
+
const Module = (await import("./manifold-B9QSr-qP.js")).default;
|
|
10455
10530
|
performance.mark("manifold:imported");
|
|
10456
10531
|
const wasm = await Module();
|
|
10457
10532
|
wasm.setup();
|
|
@@ -47289,10 +47364,8 @@ class PathBuilder {
|
|
|
47289
47364
|
if (radius <= 0) throw new Error("fillet: radius must be positive");
|
|
47290
47365
|
const n = this.segs.length;
|
|
47291
47366
|
if (n < 2) throw new Error("fillet: need at least 2 segments before a fillet");
|
|
47292
|
-
const prev = this.segs[n - 2];
|
|
47293
47367
|
const curr = this.segs[n - 1];
|
|
47294
|
-
|
|
47295
|
-
const { trimA, trimB, arcSeg } = this.computeFilletGeom(radius);
|
|
47368
|
+
const { trimA, arcSeg } = this.computeFilletGeom(radius);
|
|
47296
47369
|
if (!arcSeg) throw new Error("fillet: cannot fillet these segments (parallel or degenerate)");
|
|
47297
47370
|
this.trimLastSegEnd(n - 2, trimA[0], trimA[1]);
|
|
47298
47371
|
const trimmedSeg = { ...curr };
|
|
@@ -47364,7 +47437,6 @@ class PathBuilder {
|
|
|
47364
47437
|
}
|
|
47365
47438
|
getSegDirAt(seg, which) {
|
|
47366
47439
|
if (seg.kind === "line" || seg.kind === "move") {
|
|
47367
|
-
this.segs.length;
|
|
47368
47440
|
const idx = this.segs.indexOf(seg);
|
|
47369
47441
|
if (seg.kind === "line") {
|
|
47370
47442
|
let sx, sy;
|
|
@@ -47606,6 +47678,41 @@ class PathBuilder {
|
|
|
47606
47678
|
}
|
|
47607
47679
|
return pts;
|
|
47608
47680
|
}
|
|
47681
|
+
/**
|
|
47682
|
+
* Return the open path as a sampled 2D polyline.
|
|
47683
|
+
*
|
|
47684
|
+
* This is for construction geometry such as guide rails, measured centerlines,
|
|
47685
|
+
* and curve-driven helpers where the authored path should stay open instead of
|
|
47686
|
+
* becoming a filled sketch or stroked profile.
|
|
47687
|
+
*
|
|
47688
|
+
* **Example**
|
|
47689
|
+
*
|
|
47690
|
+
* ```ts
|
|
47691
|
+
* const rail = path()
|
|
47692
|
+
* .moveTo(24, 0)
|
|
47693
|
+
* .bezierTo(32, 44, 28, 92, 18, 120)
|
|
47694
|
+
* .toPolyline();
|
|
47695
|
+
* ```
|
|
47696
|
+
*
|
|
47697
|
+
* @returns A sampled open polyline.
|
|
47698
|
+
* @category Path Builder
|
|
47699
|
+
*/
|
|
47700
|
+
toPolyline() {
|
|
47701
|
+
const moveCount = this.segs.filter((seg) => seg.kind === "move").length;
|
|
47702
|
+
if (moveCount > 1) {
|
|
47703
|
+
throw new Error("path().toPolyline() supports one continuous open path. Use separate path() builders for separate rails.");
|
|
47704
|
+
}
|
|
47705
|
+
const pts = [];
|
|
47706
|
+
for (const point2 of this.tessellate()) {
|
|
47707
|
+
if (!point2.every(Number.isFinite)) throw new Error("path().toPolyline() produced a non-finite point");
|
|
47708
|
+
const previous = pts[pts.length - 1];
|
|
47709
|
+
if (!previous || Math.hypot(point2[0] - previous[0], point2[1] - previous[1]) > 1e-9) {
|
|
47710
|
+
pts.push(point2);
|
|
47711
|
+
}
|
|
47712
|
+
}
|
|
47713
|
+
if (pts.length < 2) throw new Error("path().toPolyline() needs at least 2 points");
|
|
47714
|
+
return pts;
|
|
47715
|
+
}
|
|
47609
47716
|
// ── Output ────────────────────────────────────────────────────────────────
|
|
47610
47717
|
/**
|
|
47611
47718
|
* Close the path and return a filled `Sketch`.
|
|
@@ -52378,7 +52485,7 @@ function requireFinite$7(value, label) {
|
|
|
52378
52485
|
}
|
|
52379
52486
|
return value;
|
|
52380
52487
|
}
|
|
52381
|
-
function requireVec3$
|
|
52488
|
+
function requireVec3$3(value, label) {
|
|
52382
52489
|
if (!Array.isArray(value) || value.length !== 3) {
|
|
52383
52490
|
throw new Error(`${label} must be [x, y, z]`);
|
|
52384
52491
|
}
|
|
@@ -52422,7 +52529,7 @@ function normalizeOptions(options) {
|
|
|
52422
52529
|
out.size = requireFinite$7(options.size, "Viewport.label options.size");
|
|
52423
52530
|
if (out.size <= 0) throw new Error("Viewport.label options.size must be positive");
|
|
52424
52531
|
}
|
|
52425
|
-
if (options.offset !== void 0) out.offset = requireVec3$
|
|
52532
|
+
if (options.offset !== void 0) out.offset = requireVec3$3(options.offset, "Viewport.label options.offset");
|
|
52426
52533
|
if (options.anchor !== void 0) {
|
|
52427
52534
|
if (!VALID_ANCHORS.has(options.anchor)) {
|
|
52428
52535
|
throw new Error(`Viewport.label options.anchor must be one of: ${Array.from(VALID_ANCHORS).join(", ")}`);
|
|
@@ -52439,7 +52546,7 @@ function collectRenderLabel(text, at, options) {
|
|
|
52439
52546
|
if (typeof text !== "string" || text.trim().length === 0) {
|
|
52440
52547
|
throw new Error("Viewport.label text must be a non-empty string");
|
|
52441
52548
|
}
|
|
52442
|
-
const normalizedAt = requireVec3$
|
|
52549
|
+
const normalizedAt = requireVec3$3(at, "Viewport.label at");
|
|
52443
52550
|
const normalizedOptions = normalizeOptions(options);
|
|
52444
52551
|
_collected$4.push({
|
|
52445
52552
|
id: `render-label-${_nextId++}`,
|
|
@@ -52634,7 +52741,7 @@ function requireFinite$6(value, label) {
|
|
|
52634
52741
|
}
|
|
52635
52742
|
return value;
|
|
52636
52743
|
}
|
|
52637
|
-
function requireVec3$
|
|
52744
|
+
function requireVec3$2(value, label) {
|
|
52638
52745
|
if (!Array.isArray(value) || value.length !== 3) {
|
|
52639
52746
|
throw new Error(`${label} must be [x, y, z]`);
|
|
52640
52747
|
}
|
|
@@ -52662,9 +52769,9 @@ const VALID_ENVIRONMENT_PRESETS = /* @__PURE__ */ new Set([
|
|
|
52662
52769
|
]);
|
|
52663
52770
|
function validateCamera(cam, label) {
|
|
52664
52771
|
const out = {};
|
|
52665
|
-
if (cam.position !== void 0) out.position = requireVec3$
|
|
52666
|
-
if (cam.target !== void 0) out.target = requireVec3$
|
|
52667
|
-
if (cam.up !== void 0) out.up = requireVec3$
|
|
52772
|
+
if (cam.position !== void 0) out.position = requireVec3$2(cam.position, `${label}.position`);
|
|
52773
|
+
if (cam.target !== void 0) out.target = requireVec3$2(cam.target, `${label}.target`);
|
|
52774
|
+
if (cam.up !== void 0) out.up = requireVec3$2(cam.up, `${label}.up`);
|
|
52668
52775
|
if (cam.fov !== void 0) {
|
|
52669
52776
|
out.fov = requireFinite$6(cam.fov, `${label}.fov`);
|
|
52670
52777
|
if (out.fov <= 0 || out.fov >= 180) throw new Error(`${label}.fov must be between 0 and 180`);
|
|
@@ -52799,8 +52906,8 @@ function validateLight(light, label) {
|
|
|
52799
52906
|
const out = { type: light.type };
|
|
52800
52907
|
if (light.color !== void 0) out.color = requireColor(light.color, `${label}.color`);
|
|
52801
52908
|
if (light.intensity !== void 0) out.intensity = requireFinite$6(light.intensity, `${label}.intensity`);
|
|
52802
|
-
if (light.position !== void 0) out.position = requireVec3$
|
|
52803
|
-
if (light.target !== void 0) out.target = requireVec3$
|
|
52909
|
+
if (light.position !== void 0) out.position = requireVec3$2(light.position, `${label}.position`);
|
|
52910
|
+
if (light.target !== void 0) out.target = requireVec3$2(light.target, `${label}.target`);
|
|
52804
52911
|
if (light.groundColor !== void 0) out.groundColor = requireColor(light.groundColor, `${label}.groundColor`);
|
|
52805
52912
|
if (light.skyColor !== void 0) out.skyColor = requireColor(light.skyColor, `${label}.skyColor`);
|
|
52806
52913
|
if (light.angle !== void 0) out.angle = requireFinite$6(light.angle, `${label}.angle`);
|
|
@@ -54332,7 +54439,7 @@ function scale$1(v, s) {
|
|
|
54332
54439
|
function dot$2(a2, b) {
|
|
54333
54440
|
return a2[0] * b[0] + a2[1] * b[1] + a2[2] * b[2];
|
|
54334
54441
|
}
|
|
54335
|
-
function lerp$
|
|
54442
|
+
function lerp$4(a2, b, t) {
|
|
54336
54443
|
return a2 + (b - a2) * t;
|
|
54337
54444
|
}
|
|
54338
54445
|
function frameMatrix$1(x2, y2, z2, p2) {
|
|
@@ -54343,7 +54450,7 @@ function axisVector(axis, sign2 = 1) {
|
|
|
54343
54450
|
if (axis === "Y") return [0, sign2, 0];
|
|
54344
54451
|
return [0, 0, sign2];
|
|
54345
54452
|
}
|
|
54346
|
-
function axisPosition(axis, point2) {
|
|
54453
|
+
function axisPosition$1(axis, point2) {
|
|
54347
54454
|
return point2[AXIS_INDEX[axis]];
|
|
54348
54455
|
}
|
|
54349
54456
|
function crossPointForStation(axis, point2) {
|
|
@@ -54351,7 +54458,7 @@ function crossPointForStation(axis, point2) {
|
|
|
54351
54458
|
if (axis === "Y") return [point2[0], -point2[2]];
|
|
54352
54459
|
return [point2[1], point2[2]];
|
|
54353
54460
|
}
|
|
54354
|
-
function orientLoftToAxis(shape, axis) {
|
|
54461
|
+
function orientLoftToAxis$1(shape, axis) {
|
|
54355
54462
|
if (axis === "Z") return shape;
|
|
54356
54463
|
if (axis === "Y") return shape.rotateX(-90);
|
|
54357
54464
|
return shape.transform([0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1]);
|
|
@@ -54408,9 +54515,9 @@ function interpolateQuery(a2, b, t) {
|
|
|
54408
54515
|
}
|
|
54409
54516
|
return {
|
|
54410
54517
|
side: sideA,
|
|
54411
|
-
u: lerp$
|
|
54412
|
-
v: lerp$
|
|
54413
|
-
offset: lerp$
|
|
54518
|
+
u: lerp$4(a2.u ?? 0.5, b.u ?? 0.5, t),
|
|
54519
|
+
v: lerp$4(a2.v ?? 0.5, b.v ?? 0.5, t),
|
|
54520
|
+
offset: lerp$4(a2.offset ?? 0, b.offset ?? 0, t)
|
|
54414
54521
|
};
|
|
54415
54522
|
}
|
|
54416
54523
|
function resolvePathQueries(points) {
|
|
@@ -54477,8 +54584,8 @@ class ProductSkin {
|
|
|
54477
54584
|
this.stations = stations;
|
|
54478
54585
|
this.rails = rails;
|
|
54479
54586
|
for (const [name2, query] of Object.entries(refs)) this.refQueries.set(name2, cloneQuery(query));
|
|
54480
|
-
this.axisMin = Math.min(...stations.map((station) => axisPosition(axis, station.center)));
|
|
54481
|
-
this.axisMax = Math.max(...stations.map((station) => axisPosition(axis, station.center)));
|
|
54587
|
+
this.axisMin = Math.min(...stations.map((station) => axisPosition$1(axis, station.center)));
|
|
54588
|
+
this.axisMax = Math.max(...stations.map((station) => axisPosition$1(axis, station.center)));
|
|
54482
54589
|
this.diagnosticsValue = {
|
|
54483
54590
|
...diagnostics,
|
|
54484
54591
|
stationNames: stations.map((station) => station.name),
|
|
@@ -54535,24 +54642,24 @@ class ProductSkin {
|
|
|
54535
54642
|
}
|
|
54536
54643
|
/** Interpolate center, width, and depth at a normalized v or absolute axis value. */
|
|
54537
54644
|
stationAt(vOrAxis) {
|
|
54538
|
-
const axisValue = vOrAxis >= 0 && vOrAxis <= 1 ? lerp$
|
|
54645
|
+
const axisValue = vOrAxis >= 0 && vOrAxis <= 1 ? lerp$4(this.axisMin, this.axisMax, vOrAxis) : clamp$5(vOrAxis, this.axisMin, this.axisMax);
|
|
54539
54646
|
const sorted = this.stations;
|
|
54540
54647
|
for (let index2 = 0; index2 < sorted.length - 1; index2 += 1) {
|
|
54541
54648
|
const a2 = sorted[index2];
|
|
54542
54649
|
const b = sorted[index2 + 1];
|
|
54543
|
-
const aAxis = axisPosition(this.axis, a2.center);
|
|
54544
|
-
const bAxis = axisPosition(this.axis, b.center);
|
|
54650
|
+
const aAxis = axisPosition$1(this.axis, a2.center);
|
|
54651
|
+
const bAxis = axisPosition$1(this.axis, b.center);
|
|
54545
54652
|
if (axisValue < aAxis - EPS$5 || axisValue > bAxis + EPS$5) continue;
|
|
54546
54653
|
const span = Math.max(EPS$5, bAxis - aAxis);
|
|
54547
54654
|
const t = clamp$5((axisValue - aAxis) / span, 0, 1);
|
|
54548
54655
|
return {
|
|
54549
54656
|
axisValue,
|
|
54550
|
-
center: [lerp$
|
|
54551
|
-
width: lerp$
|
|
54552
|
-
depth: lerp$
|
|
54657
|
+
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)],
|
|
54658
|
+
width: lerp$4(a2.profile.width, b.profile.width, t),
|
|
54659
|
+
depth: lerp$4(a2.profile.depth, b.profile.depth, t),
|
|
54553
54660
|
dWidth: (b.profile.width - a2.profile.width) / span,
|
|
54554
54661
|
dDepth: (b.profile.depth - a2.profile.depth) / span,
|
|
54555
|
-
exponent: lerp$
|
|
54662
|
+
exponent: lerp$4(profileExponent(a2), profileExponent(b), t),
|
|
54556
54663
|
kind: a2.profile.kind === b.profile.kind ? a2.profile.kind : "custom"
|
|
54557
54664
|
};
|
|
54558
54665
|
}
|
|
@@ -54674,7 +54781,7 @@ class ProductSkinBuilder {
|
|
|
54674
54781
|
}
|
|
54675
54782
|
/** Set named cross-section stations for the product skin. */
|
|
54676
54783
|
stations(stations) {
|
|
54677
|
-
this.stationsValue = stations.map(toStationSpec).sort((a2, b) => axisPosition(this.axisValue, a2.center) - axisPosition(this.axisValue, b.center));
|
|
54784
|
+
this.stationsValue = stations.map(toStationSpec).sort((a2, b) => axisPosition$1(this.axisValue, a2.center) - axisPosition$1(this.axisValue, b.center));
|
|
54678
54785
|
return this;
|
|
54679
54786
|
}
|
|
54680
54787
|
/** Attach named guide rails for product-skin construction and downstream surface references. */
|
|
@@ -54724,9 +54831,9 @@ class ProductSkinBuilder {
|
|
|
54724
54831
|
const [x2, y2] = crossPointForStation(this.axisValue, station.center);
|
|
54725
54832
|
return station.profile.sketch.translate(x2, y2);
|
|
54726
54833
|
});
|
|
54727
|
-
const heights = this.stationsValue.map((station) => axisPosition(this.axisValue, station.center));
|
|
54834
|
+
const heights = this.stationsValue.map((station) => axisPosition$1(this.axisValue, station.center));
|
|
54728
54835
|
let shape = loft(localProfiles, heights, { edgeLength: this.edgeLengthValue });
|
|
54729
|
-
shape = orientLoftToAxis(shape, this.axisValue);
|
|
54836
|
+
shape = orientLoftToAxis$1(shape, this.axisValue);
|
|
54730
54837
|
if (this.colorValue) shape = shape.color(this.colorValue);
|
|
54731
54838
|
shape = applyMaterial(shape, this.materialValue).as(this.name);
|
|
54732
54839
|
const warnings = [];
|
|
@@ -55385,7 +55492,7 @@ function requirePositive$3(value, label) {
|
|
|
55385
55492
|
function clamp$4(value, min2, max2) {
|
|
55386
55493
|
return Math.max(min2, Math.min(max2, value));
|
|
55387
55494
|
}
|
|
55388
|
-
function lerp$
|
|
55495
|
+
function lerp$3(a2, b, t) {
|
|
55389
55496
|
return a2 + (b - a2) * t;
|
|
55390
55497
|
}
|
|
55391
55498
|
function add(a2, b) {
|
|
@@ -55435,19 +55542,19 @@ function transformLocal(point2, tangentAcross, normal, tangentAlong, x2, y2, z2
|
|
|
55435
55542
|
function interpolateCylinder(a2, b, t, mode) {
|
|
55436
55543
|
let delta = b.angle - a2.angle;
|
|
55437
55544
|
if (mode === "shortest" && Math.abs(delta) > 180) delta -= Math.sign(delta) * 360;
|
|
55438
|
-
return { kind: "cylinder", angle: a2.angle + delta * t, z: lerp$
|
|
55545
|
+
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) };
|
|
55439
55546
|
}
|
|
55440
55547
|
function interpolatePlane(a2, b, t) {
|
|
55441
|
-
return { kind: "plane", x: lerp$
|
|
55548
|
+
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) };
|
|
55442
55549
|
}
|
|
55443
55550
|
function interpolateProductSkin(a2, b, t) {
|
|
55444
55551
|
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.");
|
|
55445
55552
|
return {
|
|
55446
55553
|
kind: "productSkin",
|
|
55447
55554
|
side: a2.side ?? b.side,
|
|
55448
|
-
u: lerp$
|
|
55449
|
-
v: lerp$
|
|
55450
|
-
offset: lerp$
|
|
55555
|
+
u: lerp$3(a2.u ?? 0.5, b.u ?? 0.5, t),
|
|
55556
|
+
v: lerp$3(a2.v ?? 0.5, b.v ?? 0.5, t),
|
|
55557
|
+
offset: lerp$3(a2.offset ?? 0, b.offset ?? 0, t)
|
|
55451
55558
|
};
|
|
55452
55559
|
}
|
|
55453
55560
|
class SurfacePath {
|
|
@@ -55770,11 +55877,11 @@ function coordinateOnSide(coordinate, side, label) {
|
|
|
55770
55877
|
return { ...coordinate, kind: "productSkin", side };
|
|
55771
55878
|
}
|
|
55772
55879
|
class ProductSkinCarrier {
|
|
55773
|
-
constructor(skin, name = skin.name,
|
|
55880
|
+
constructor(skin, name = skin.name, sideValue2, offsetValue = 0) {
|
|
55774
55881
|
__publicField(this, "kind", "productSkin");
|
|
55775
55882
|
this.skin = skin;
|
|
55776
55883
|
this.name = name;
|
|
55777
|
-
this.sideValue =
|
|
55884
|
+
this.sideValue = sideValue2;
|
|
55778
55885
|
this.offsetValue = offsetValue;
|
|
55779
55886
|
}
|
|
55780
55887
|
surface(side) {
|
|
@@ -56545,7 +56652,7 @@ function counterboresForPlate(spec2, width, height, thickness, diagnostics) {
|
|
|
56545
56652
|
function minWidthAcrossAlongRange(widthAtT, length4, minAlong, maxAlong) {
|
|
56546
56653
|
let minWidth = Number.POSITIVE_INFINITY;
|
|
56547
56654
|
for (let index2 = 0; index2 <= 8; index2 += 1) {
|
|
56548
|
-
const along = lerp$
|
|
56655
|
+
const along = lerp$3(minAlong, maxAlong, index2 / 8);
|
|
56549
56656
|
const t = Math.max(0, Math.min(1, (along + length4 / 2) / Math.max(length4, 1e-8)));
|
|
56550
56657
|
minWidth = Math.min(minWidth, widthAtT(t));
|
|
56551
56658
|
}
|
|
@@ -56845,7 +56952,7 @@ function pathParameterAtDistance(samples, distance2) {
|
|
|
56845
56952
|
const segmentLength = Math.hypot(b.point[0] - a2.point[0], b.point[1] - a2.point[1], b.point[2] - a2.point[2]);
|
|
56846
56953
|
if (traveled + segmentLength >= distance2) {
|
|
56847
56954
|
const localT = segmentLength <= 1e-8 ? 0 : (distance2 - traveled) / segmentLength;
|
|
56848
|
-
return lerp$
|
|
56955
|
+
return lerp$3(a2.t, b.t, localT);
|
|
56849
56956
|
}
|
|
56850
56957
|
traveled += segmentLength;
|
|
56851
56958
|
}
|
|
@@ -56898,7 +57005,7 @@ function compileBandFootprintMesh(path2, input) {
|
|
|
56898
57005
|
const width = input.widthAt(t);
|
|
56899
57006
|
const along = distance2 - length4 / 2;
|
|
56900
57007
|
for (let acrossIndex = 0; acrossIndex <= acrossSegments; acrossIndex += 1) {
|
|
56901
|
-
const across = lerp$
|
|
57008
|
+
const across = lerp$3(-width / 2, width / 2, acrossIndex / acrossSegments);
|
|
56902
57009
|
mesh.vertices.push(pointAtProfile([across, along], false));
|
|
56903
57010
|
}
|
|
56904
57011
|
}
|
|
@@ -56908,7 +57015,7 @@ function compileBandFootprintMesh(path2, input) {
|
|
|
56908
57015
|
const width = input.widthAt(t);
|
|
56909
57016
|
const along = distance2 - length4 / 2;
|
|
56910
57017
|
for (let acrossIndex = 0; acrossIndex <= acrossSegments; acrossIndex += 1) {
|
|
56911
|
-
const across = lerp$
|
|
57018
|
+
const across = lerp$3(-width / 2, width / 2, acrossIndex / acrossSegments);
|
|
56912
57019
|
mesh.vertices.push(pointAtProfile([across, along], true));
|
|
56913
57020
|
}
|
|
56914
57021
|
}
|
|
@@ -56920,7 +57027,7 @@ function compileBandFootprintMesh(path2, input) {
|
|
|
56920
57027
|
const width = input.widthAt(t);
|
|
56921
57028
|
const along = distance2 - length4 / 2;
|
|
56922
57029
|
for (let acrossIndex = 0; acrossIndex < acrossSegments; acrossIndex += 1) {
|
|
56923
|
-
const across = lerp$
|
|
57030
|
+
const across = lerp$3(-width / 2, width / 2, (acrossIndex + 0.5) / acrossSegments);
|
|
56924
57031
|
filled[alongIndex][acrossIndex] = !holes.some((hole2) => pointInProfileLoop([across, along], hole2));
|
|
56925
57032
|
}
|
|
56926
57033
|
}
|
|
@@ -59002,7 +59109,7 @@ const Constraint = {
|
|
|
59002
59109
|
return builder.constrain({ type: "length", line: resolveLineId(builder, line2), value });
|
|
59003
59110
|
}
|
|
59004
59111
|
};
|
|
59005
|
-
function requireVec3(v, label) {
|
|
59112
|
+
function requireVec3$1(v, label) {
|
|
59006
59113
|
if (!Array.isArray(v) || v.length !== 3 || !Number.isFinite(v[0]) || !Number.isFinite(v[1]) || !Number.isFinite(v[2])) {
|
|
59007
59114
|
throw new Error(`${label} must be a [number, number, number] with finite values, got ${JSON.stringify(v)}`);
|
|
59008
59115
|
}
|
|
@@ -59015,24 +59122,24 @@ function requireFiniteNumber(n, label) {
|
|
|
59015
59122
|
return n;
|
|
59016
59123
|
}
|
|
59017
59124
|
function distance$1(a2, b) {
|
|
59018
|
-
requireVec3(a2, "a");
|
|
59019
|
-
requireVec3(b, "b");
|
|
59125
|
+
requireVec3$1(a2, "a");
|
|
59126
|
+
requireVec3$1(b, "b");
|
|
59020
59127
|
return Math.hypot(b[0] - a2[0], b[1] - a2[1], b[2] - a2[2]);
|
|
59021
59128
|
}
|
|
59022
59129
|
function midpoint$1(a2, b) {
|
|
59023
|
-
requireVec3(a2, "a");
|
|
59024
|
-
requireVec3(b, "b");
|
|
59130
|
+
requireVec3$1(a2, "a");
|
|
59131
|
+
requireVec3$1(b, "b");
|
|
59025
59132
|
return [(a2[0] + b[0]) / 2, (a2[1] + b[1]) / 2, (a2[2] + b[2]) / 2];
|
|
59026
59133
|
}
|
|
59027
|
-
function lerp(a2, b, t) {
|
|
59028
|
-
requireVec3(a2, "a");
|
|
59029
|
-
requireVec3(b, "b");
|
|
59134
|
+
function lerp$2(a2, b, t) {
|
|
59135
|
+
requireVec3$1(a2, "a");
|
|
59136
|
+
requireVec3$1(b, "b");
|
|
59030
59137
|
requireFiniteNumber(t, "t");
|
|
59031
59138
|
return [a2[0] + (b[0] - a2[0]) * t, a2[1] + (b[1] - a2[1]) * t, a2[2] + (b[2] - a2[2]) * t];
|
|
59032
59139
|
}
|
|
59033
59140
|
function direction(a2, b) {
|
|
59034
|
-
requireVec3(a2, "a");
|
|
59035
|
-
requireVec3(b, "b");
|
|
59141
|
+
requireVec3$1(a2, "a");
|
|
59142
|
+
requireVec3$1(b, "b");
|
|
59036
59143
|
const dx = b[0] - a2[0];
|
|
59037
59144
|
const dy = b[1] - a2[1];
|
|
59038
59145
|
const dz = b[2] - a2[2];
|
|
@@ -59043,8 +59150,8 @@ function direction(a2, b) {
|
|
|
59043
59150
|
return [dx / len2, dy / len2, dz / len2];
|
|
59044
59151
|
}
|
|
59045
59152
|
function offset(point2, dir, amount) {
|
|
59046
|
-
requireVec3(point2, "point");
|
|
59047
|
-
requireVec3(dir, "dir");
|
|
59153
|
+
requireVec3$1(point2, "point");
|
|
59154
|
+
requireVec3$1(dir, "dir");
|
|
59048
59155
|
requireFiniteNumber(amount, "amount");
|
|
59049
59156
|
return [point2[0] + dir[0] * amount, point2[1] + dir[1] * amount, point2[2] + dir[2] * amount];
|
|
59050
59157
|
}
|
|
@@ -59054,7 +59161,7 @@ const Points = {
|
|
|
59054
59161
|
/** Center point between two 3D points. */
|
|
59055
59162
|
midpoint: midpoint$1,
|
|
59056
59163
|
/** Linearly interpolate between two 3D points. t=0 returns a, t=1 returns b. */
|
|
59057
|
-
lerp,
|
|
59164
|
+
lerp: lerp$2,
|
|
59058
59165
|
/** Unit direction vector from a to b. Throws if a and b are the same point. */
|
|
59059
59166
|
direction,
|
|
59060
59167
|
/** Move a point along a direction vector by a given amount. */
|
|
@@ -64186,9 +64293,84 @@ class ConstraintSketch extends Sketch {
|
|
|
64186
64293
|
* Select the single arrangement region that contains the given seed point.
|
|
64187
64294
|
* Throws if no region contains the seed.
|
|
64188
64295
|
*/
|
|
64189
|
-
detectArrangementRegion(
|
|
64296
|
+
detectArrangementRegion(_seed) {
|
|
64190
64297
|
throw new Error("Not implemented");
|
|
64191
64298
|
}
|
|
64299
|
+
/**
|
|
64300
|
+
* Return the solved constrained path as a sampled 2D polyline.
|
|
64301
|
+
*
|
|
64302
|
+
* Use this when a construction rail was authored with `constrainedSketch()`
|
|
64303
|
+
* and should feed another operation such as `Loft.pathOnXz(...)`.
|
|
64304
|
+
* The sketch must contain exactly one profile path.
|
|
64305
|
+
*
|
|
64306
|
+
* @param samples - Samples per curved segment. Default 32.
|
|
64307
|
+
* @returns The solved path as an open polyline.
|
|
64308
|
+
*/
|
|
64309
|
+
toPolyline(samples = 32) {
|
|
64310
|
+
if (!Number.isFinite(samples) || samples < 2) throw new Error("ConstraintSketch.toPolyline() samples must be at least 2");
|
|
64311
|
+
const profileLoops = this.definition.loops.filter((loop) => loop.type === "profile");
|
|
64312
|
+
if (profileLoops.length !== 1) {
|
|
64313
|
+
throw new Error("ConstraintSketch.toPolyline() requires exactly one profile path");
|
|
64314
|
+
}
|
|
64315
|
+
const sampleCount = Math.max(2, Math.round(samples));
|
|
64316
|
+
const pointMap = new Map(this.definition.points.map((point2) => [point2.id, point2]));
|
|
64317
|
+
const lineMap = new Map(this.definition.lines.map((line2) => [line2.id, line2]));
|
|
64318
|
+
const arcMap = new Map(this.definition.arcs.map((arc) => [arc.id, arc]));
|
|
64319
|
+
const bezierMap = new Map(this.definition.beziers.map((bezier) => [bezier.id, bezier]));
|
|
64320
|
+
const points = [];
|
|
64321
|
+
const appendStart = (point2, label) => {
|
|
64322
|
+
const previous = points[points.length - 1];
|
|
64323
|
+
if (!previous) {
|
|
64324
|
+
points.push(point2);
|
|
64325
|
+
return;
|
|
64326
|
+
}
|
|
64327
|
+
if (Math.hypot(point2[0] - previous[0], point2[1] - previous[1]) > 1e-6) {
|
|
64328
|
+
throw new Error(`ConstraintSketch.toPolyline() profile path is not continuous at ${label}`);
|
|
64329
|
+
}
|
|
64330
|
+
};
|
|
64331
|
+
const appendPoint = (point2) => {
|
|
64332
|
+
const previous = points[points.length - 1];
|
|
64333
|
+
if (!previous || Math.hypot(point2[0] - previous[0], point2[1] - previous[1]) > 1e-9) points.push(point2);
|
|
64334
|
+
};
|
|
64335
|
+
const requirePoint = (id, label) => {
|
|
64336
|
+
const point2 = pointMap.get(id);
|
|
64337
|
+
if (!point2) throw new Error(`ConstraintSketch.toPolyline() missing ${label}`);
|
|
64338
|
+
return [point2.x, point2.y];
|
|
64339
|
+
};
|
|
64340
|
+
for (const segment of profileLoops[0].segments) {
|
|
64341
|
+
if (segment.kind === "line") {
|
|
64342
|
+
const line2 = lineMap.get(segment.line);
|
|
64343
|
+
if (!line2) throw new Error(`ConstraintSketch.toPolyline() missing line "${segment.line}"`);
|
|
64344
|
+
appendStart(requirePoint(line2.a, `line "${segment.line}" start point`), `line "${segment.line}"`);
|
|
64345
|
+
appendPoint(requirePoint(line2.b, `line "${segment.line}" end point`));
|
|
64346
|
+
} else if (segment.kind === "arc") {
|
|
64347
|
+
const arc = arcMap.get(segment.arc);
|
|
64348
|
+
if (!arc) throw new Error(`ConstraintSketch.toPolyline() missing arc "${segment.arc}"`);
|
|
64349
|
+
const center = requirePoint(arc.center, `arc "${segment.arc}" center point`);
|
|
64350
|
+
const start = requirePoint(arc.start, `arc "${segment.arc}" start point`);
|
|
64351
|
+
const end = requirePoint(arc.end, `arc "${segment.arc}" end point`);
|
|
64352
|
+
appendStart(start, `arc "${segment.arc}"`);
|
|
64353
|
+
const startAngle = Math.atan2(start[1] - center[1], start[0] - center[0]);
|
|
64354
|
+
const endAngle = Math.atan2(end[1] - center[1], end[0] - center[0]);
|
|
64355
|
+
for (const point2 of tessellateArc(center[0], center[1], arc.radius, startAngle, endAngle, arc.clockwise, sampleCount)) {
|
|
64356
|
+
appendPoint(point2);
|
|
64357
|
+
}
|
|
64358
|
+
} else {
|
|
64359
|
+
const bezier = bezierMap.get(segment.bezier);
|
|
64360
|
+
if (!bezier) throw new Error(`ConstraintSketch.toPolyline() missing bezier "${segment.bezier}"`);
|
|
64361
|
+
const p0 = requirePoint(bezier.p0, `bezier "${segment.bezier}" start point`);
|
|
64362
|
+
const p1 = requirePoint(bezier.p1, `bezier "${segment.bezier}" first control point`);
|
|
64363
|
+
const p2 = requirePoint(bezier.p2, `bezier "${segment.bezier}" second control point`);
|
|
64364
|
+
const p3 = requirePoint(bezier.p3, `bezier "${segment.bezier}" end point`);
|
|
64365
|
+
appendStart(p0, `bezier "${segment.bezier}"`);
|
|
64366
|
+
for (const point2 of tessellateBezier(p0[0], p0[1], p1[0], p1[1], p2[0], p2[1], p3[0], p3[1], sampleCount)) {
|
|
64367
|
+
appendPoint(point2);
|
|
64368
|
+
}
|
|
64369
|
+
}
|
|
64370
|
+
}
|
|
64371
|
+
if (points.length < 2) throw new Error("ConstraintSketch.toPolyline() needs at least 2 points");
|
|
64372
|
+
return points;
|
|
64373
|
+
}
|
|
64192
64374
|
/**
|
|
64193
64375
|
* Re-solve the sketch after changing the value of one existing constraint.
|
|
64194
64376
|
*
|
|
@@ -79473,6 +79655,295 @@ function polygonVertices(sides, radius, options) {
|
|
|
79473
79655
|
centerY: options == null ? void 0 : options.centerY
|
|
79474
79656
|
});
|
|
79475
79657
|
}
|
|
79658
|
+
const LOFT_GUIDE_EPS = 1e-8;
|
|
79659
|
+
function orientLoftToAxis(shape, axis) {
|
|
79660
|
+
if (axis === "Z") return shape;
|
|
79661
|
+
if (axis === "Y") return shape.rotateX(-90);
|
|
79662
|
+
return shape.transform([0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1]);
|
|
79663
|
+
}
|
|
79664
|
+
function buildRailEvaluators(rails, axis, start, end, railSamples) {
|
|
79665
|
+
const seen = /* @__PURE__ */ new Set();
|
|
79666
|
+
return rails.map((rail2) => {
|
|
79667
|
+
if (seen.has(rail2.side)) throw new Error(`Loft.withGuideRails() received more than one ${rail2.side} rail`);
|
|
79668
|
+
seen.add(rail2.side);
|
|
79669
|
+
const sampled = sampleRailPath(rail2.path, railSamples);
|
|
79670
|
+
if (sampled.length < 2) throw new Error("Loft guide rails require at least two points");
|
|
79671
|
+
const points = sampled.map((point2) => ({ position: axisPosition(axis, point2), cross: crossPointForAxis(axis, point2) }));
|
|
79672
|
+
const ordered = points[points.length - 1].position >= points[0].position ? points : [...points].reverse();
|
|
79673
|
+
validateRailCoverage(ordered, start, end);
|
|
79674
|
+
return { side: rail2.side, points: ordered };
|
|
79675
|
+
});
|
|
79676
|
+
}
|
|
79677
|
+
function railCrossAt(rail2, position) {
|
|
79678
|
+
const points = rail2.points;
|
|
79679
|
+
if (position <= points[0].position + LOFT_GUIDE_EPS) return points[0].cross;
|
|
79680
|
+
const last = points[points.length - 1];
|
|
79681
|
+
if (position >= last.position - LOFT_GUIDE_EPS) return last.cross;
|
|
79682
|
+
for (let index2 = 0; index2 < points.length - 1; index2 += 1) {
|
|
79683
|
+
const a2 = points[index2];
|
|
79684
|
+
const b = points[index2 + 1];
|
|
79685
|
+
if (position >= a2.position - LOFT_GUIDE_EPS && position <= b.position + LOFT_GUIDE_EPS) {
|
|
79686
|
+
const t = (position - a2.position) / (b.position - a2.position);
|
|
79687
|
+
return [lerp$1(a2.cross[0], b.cross[0], t), lerp$1(a2.cross[1], b.cross[1], t)];
|
|
79688
|
+
}
|
|
79689
|
+
}
|
|
79690
|
+
throw new Error("Loft guide rail does not cover requested station position");
|
|
79691
|
+
}
|
|
79692
|
+
function validateRailCoverage(points, start, end) {
|
|
79693
|
+
for (let index2 = 1; index2 < points.length; index2 += 1) {
|
|
79694
|
+
if (points[index2].position - points[index2 - 1].position < LOFT_GUIDE_EPS) {
|
|
79695
|
+
throw new Error("Loft guide rails must be monotone along the loft axis");
|
|
79696
|
+
}
|
|
79697
|
+
}
|
|
79698
|
+
if (points[0].position - start > LOFT_GUIDE_EPS || end - points[points.length - 1].position > LOFT_GUIDE_EPS) {
|
|
79699
|
+
throw new Error("Loft guide rails must cover the full station range");
|
|
79700
|
+
}
|
|
79701
|
+
}
|
|
79702
|
+
function sampleRailPath(path2, samples) {
|
|
79703
|
+
if (Array.isArray(path2)) return path2.map((point2, index2) => requireVec3(point2, `Loft guide rail point ${index2}`));
|
|
79704
|
+
if (path2 instanceof Curve3D || path2 instanceof HermiteCurve3D || path2 instanceof QuinticHermiteCurve3D || path2 instanceof NurbsCurve3D) {
|
|
79705
|
+
return path2.sample(Math.max(2, Math.round(samples))).map((point2, index2) => requireVec3(point2, `Loft guide rail sample ${index2}`));
|
|
79706
|
+
}
|
|
79707
|
+
throw new Error("Loft guide rail path must be a Vec3[] or ForgeCAD 3D curve");
|
|
79708
|
+
}
|
|
79709
|
+
function requireVec3(point2, label) {
|
|
79710
|
+
if (!Array.isArray(point2) || point2.length !== 3 || !point2.every(Number.isFinite)) {
|
|
79711
|
+
throw new Error(`${label} must be a finite [x, y, z] point`);
|
|
79712
|
+
}
|
|
79713
|
+
return [point2[0], point2[1], point2[2]];
|
|
79714
|
+
}
|
|
79715
|
+
function axisPosition(axis, point2) {
|
|
79716
|
+
if (axis === "X") return point2[0];
|
|
79717
|
+
if (axis === "Y") return point2[1];
|
|
79718
|
+
return point2[2];
|
|
79719
|
+
}
|
|
79720
|
+
function crossPointForAxis(axis, point2) {
|
|
79721
|
+
if (axis === "X") return [point2[1], point2[2]];
|
|
79722
|
+
if (axis === "Y") return [point2[0], -point2[2]];
|
|
79723
|
+
return [point2[0], point2[1]];
|
|
79724
|
+
}
|
|
79725
|
+
function lerp$1(a2, b, t) {
|
|
79726
|
+
return a2 + (b - a2) * t;
|
|
79727
|
+
}
|
|
79728
|
+
function loftWithGuideRails(stations, rails, options = {}) {
|
|
79729
|
+
if (stations.length < 2) throw new Error("Loft.withGuideRails() requires at least two stations");
|
|
79730
|
+
if (rails.length === 0) throw new Error("Loft.withGuideRails() requires at least one guide rail");
|
|
79731
|
+
const sortedStations = sortedValidStations(stations);
|
|
79732
|
+
const axis = options.axis ?? "Z";
|
|
79733
|
+
const start = sortedStations[0].position;
|
|
79734
|
+
const end = sortedStations[sortedStations.length - 1].position;
|
|
79735
|
+
const railEvaluators = buildRailEvaluators(rails, axis, start, end, options.railSamples ?? 64);
|
|
79736
|
+
const positions = generatedPositions(sortedStations, options.samples);
|
|
79737
|
+
const profiles2 = positions.map((position) => {
|
|
79738
|
+
const source = profileForPosition(sortedStations, position);
|
|
79739
|
+
const bounds = boundsForPosition(sortedStations, position);
|
|
79740
|
+
return fitProfileToBounds(source, applyRailsToBounds(bounds, railEvaluators, position));
|
|
79741
|
+
});
|
|
79742
|
+
const shape = loft(profiles2, positions, {
|
|
79743
|
+
edgeLength: options.edgeLength,
|
|
79744
|
+
boundsPadding: options.boundsPadding
|
|
79745
|
+
});
|
|
79746
|
+
return orientLoftToAxis(shape, axis);
|
|
79747
|
+
}
|
|
79748
|
+
function sortedValidStations(stations) {
|
|
79749
|
+
const sorted = [...stations].sort((a2, b) => a2.position - b.position);
|
|
79750
|
+
for (let index2 = 0; index2 < sorted.length; index2 += 1) {
|
|
79751
|
+
if (!Number.isFinite(sorted[index2].position)) throw new Error("Loft.withGuideRails station position must be finite");
|
|
79752
|
+
if (!(sorted[index2].profile instanceof Sketch)) throw new Error("Loft.withGuideRails() stations must use Sketch profiles");
|
|
79753
|
+
if (index2 > 0 && sorted[index2].position - sorted[index2 - 1].position < LOFT_GUIDE_EPS) {
|
|
79754
|
+
throw new Error("Loft.withGuideRails() requires unique, strictly increasing station positions");
|
|
79755
|
+
}
|
|
79756
|
+
}
|
|
79757
|
+
return sorted;
|
|
79758
|
+
}
|
|
79759
|
+
function generatedPositions(stations, samples) {
|
|
79760
|
+
const count = Math.max(2, Math.round(samples ?? Math.max(9, (stations.length - 1) * 8 + 1)));
|
|
79761
|
+
const start = stations[0].position;
|
|
79762
|
+
const end = stations[stations.length - 1].position;
|
|
79763
|
+
const values = /* @__PURE__ */ new Set();
|
|
79764
|
+
const positions = [];
|
|
79765
|
+
const addPosition = (position) => {
|
|
79766
|
+
const key = position.toFixed(9);
|
|
79767
|
+
if (!values.has(key)) {
|
|
79768
|
+
values.add(key);
|
|
79769
|
+
positions.push(position);
|
|
79770
|
+
}
|
|
79771
|
+
};
|
|
79772
|
+
for (let index2 = 0; index2 < count; index2 += 1) addPosition(start + (end - start) * index2 / (count - 1));
|
|
79773
|
+
for (const station of stations) addPosition(station.position);
|
|
79774
|
+
return positions.sort((a2, b) => a2 - b);
|
|
79775
|
+
}
|
|
79776
|
+
function profileForPosition(stations, position) {
|
|
79777
|
+
for (let index2 = 0; index2 < stations.length - 1; index2 += 1) {
|
|
79778
|
+
if (position <= stations[index2 + 1].position + LOFT_GUIDE_EPS) return stations[index2].profile;
|
|
79779
|
+
}
|
|
79780
|
+
return stations[stations.length - 1].profile;
|
|
79781
|
+
}
|
|
79782
|
+
function boundsForPosition(stations, position) {
|
|
79783
|
+
if (position <= stations[0].position + LOFT_GUIDE_EPS) return sketchBounds(stations[0].profile);
|
|
79784
|
+
const last = stations[stations.length - 1];
|
|
79785
|
+
if (position >= last.position - LOFT_GUIDE_EPS) return sketchBounds(last.profile);
|
|
79786
|
+
for (let index2 = 0; index2 < stations.length - 1; index2 += 1) {
|
|
79787
|
+
const a2 = stations[index2];
|
|
79788
|
+
const b = stations[index2 + 1];
|
|
79789
|
+
if (position >= a2.position - LOFT_GUIDE_EPS && position <= b.position + LOFT_GUIDE_EPS) {
|
|
79790
|
+
return lerpBounds(sketchBounds(a2.profile), sketchBounds(b.profile), (position - a2.position) / (b.position - a2.position));
|
|
79791
|
+
}
|
|
79792
|
+
}
|
|
79793
|
+
return sketchBounds(last.profile);
|
|
79794
|
+
}
|
|
79795
|
+
function applyRailsToBounds(bounds, rails, position) {
|
|
79796
|
+
const centerRail = rails.find((rail2) => rail2.side === "center");
|
|
79797
|
+
const center = centerRail ? railCrossAt(centerRail, position) : void 0;
|
|
79798
|
+
const next = { ...bounds };
|
|
79799
|
+
applyAxisRail(next, "X", sideValue(rails, "left", position, 0), sideValue(rails, "right", position, 0), center == null ? void 0 : center[0]);
|
|
79800
|
+
applyAxisRail(next, "Y", sideValue(rails, "back", position, 1), sideValue(rails, "front", position, 1), center == null ? void 0 : center[1]);
|
|
79801
|
+
if (next.maxX - next.minX < LOFT_GUIDE_EPS || next.maxY - next.minY < LOFT_GUIDE_EPS) {
|
|
79802
|
+
throw new Error("Loft.withGuideRails() guide rails produced a non-positive section size");
|
|
79803
|
+
}
|
|
79804
|
+
return next;
|
|
79805
|
+
}
|
|
79806
|
+
function sideValue(rails, side, position, crossIndex) {
|
|
79807
|
+
const rail2 = rails.find((entry) => entry.side === side);
|
|
79808
|
+
return rail2 ? railCrossAt(rail2, position)[crossIndex] : void 0;
|
|
79809
|
+
}
|
|
79810
|
+
function applyAxisRail(bounds, axis, minRail, maxRail, center) {
|
|
79811
|
+
const minKey = axis === "X" ? "minX" : "minY";
|
|
79812
|
+
const maxKey = axis === "X" ? "maxX" : "maxY";
|
|
79813
|
+
const width = bounds[maxKey] - bounds[minKey];
|
|
79814
|
+
if (minRail != null && maxRail != null) {
|
|
79815
|
+
if (maxRail - minRail < LOFT_GUIDE_EPS) throw new Error("Loft.withGuideRails() opposite guide rails crossed");
|
|
79816
|
+
if (center != null && Math.abs((minRail + maxRail) / 2 - center) > 1e-5) {
|
|
79817
|
+
throw new Error("Loft.withGuideRails() center rail conflicts with opposite side rails");
|
|
79818
|
+
}
|
|
79819
|
+
bounds[minKey] = minRail;
|
|
79820
|
+
bounds[maxKey] = maxRail;
|
|
79821
|
+
} else if (maxRail != null) {
|
|
79822
|
+
bounds[maxKey] = maxRail;
|
|
79823
|
+
bounds[minKey] = center != null ? 2 * center - maxRail : maxRail - width;
|
|
79824
|
+
} else if (minRail != null) {
|
|
79825
|
+
bounds[minKey] = minRail;
|
|
79826
|
+
bounds[maxKey] = center != null ? 2 * center - minRail : minRail + width;
|
|
79827
|
+
} else if (center != null) {
|
|
79828
|
+
bounds[minKey] = center - width / 2;
|
|
79829
|
+
bounds[maxKey] = center + width / 2;
|
|
79830
|
+
}
|
|
79831
|
+
}
|
|
79832
|
+
function fitProfileToBounds(profile, target) {
|
|
79833
|
+
const source = sketchBounds(profile);
|
|
79834
|
+
const sourceWidth = source.maxX - source.minX;
|
|
79835
|
+
const sourceDepth = source.maxY - source.minY;
|
|
79836
|
+
if (sourceWidth < LOFT_GUIDE_EPS || sourceDepth < LOFT_GUIDE_EPS) {
|
|
79837
|
+
throw new Error("Loft.withGuideRails() station profiles must have positive bounds");
|
|
79838
|
+
}
|
|
79839
|
+
const sourceCenter = [(source.minX + source.maxX) / 2, (source.minY + source.maxY) / 2];
|
|
79840
|
+
const targetCenter = [(target.minX + target.maxX) / 2, (target.minY + target.maxY) / 2];
|
|
79841
|
+
return profile.scaleAround(sourceCenter, [(target.maxX - target.minX) / sourceWidth, (target.maxY - target.minY) / sourceDepth]).translate(targetCenter[0] - sourceCenter[0], targetCenter[1] - sourceCenter[1]);
|
|
79842
|
+
}
|
|
79843
|
+
function sketchBounds(profile) {
|
|
79844
|
+
const bounds = profile.bounds();
|
|
79845
|
+
return { minX: bounds.min[0], maxX: bounds.max[0], minY: bounds.min[1], maxY: bounds.max[1] };
|
|
79846
|
+
}
|
|
79847
|
+
function lerpBounds(a2, b, t) {
|
|
79848
|
+
return {
|
|
79849
|
+
minX: lerp(a2.minX, b.minX, t),
|
|
79850
|
+
maxX: lerp(a2.maxX, b.maxX, t),
|
|
79851
|
+
minY: lerp(a2.minY, b.minY, t),
|
|
79852
|
+
maxY: lerp(a2.maxY, b.maxY, t)
|
|
79853
|
+
};
|
|
79854
|
+
}
|
|
79855
|
+
function lerp(a2, b, t) {
|
|
79856
|
+
return a2 + (b - a2) * t;
|
|
79857
|
+
}
|
|
79858
|
+
function mapLoftPath2D(path2, label, mapper) {
|
|
79859
|
+
const points = sampleLoftPath2D(path2, label);
|
|
79860
|
+
return points.map((point2, index2) => {
|
|
79861
|
+
if (!Array.isArray(point2) || point2.length !== 2 || !point2.every(Number.isFinite)) {
|
|
79862
|
+
throw new Error(`${label} point ${index2} must be a finite [x, y] point`);
|
|
79863
|
+
}
|
|
79864
|
+
return mapper([point2[0], point2[1]]);
|
|
79865
|
+
});
|
|
79866
|
+
}
|
|
79867
|
+
function sampleLoftPath2D(path2, label) {
|
|
79868
|
+
if (Array.isArray(path2)) {
|
|
79869
|
+
if (path2.length < 2) throw new Error(`${label} requires at least two [x, y] points`);
|
|
79870
|
+
return path2;
|
|
79871
|
+
}
|
|
79872
|
+
if (!path2 || typeof path2 !== "object" || typeof path2.toPolyline !== "function") {
|
|
79873
|
+
throw new Error(`${label} requires a 2D path, solved constrained path, or [x, y] point array`);
|
|
79874
|
+
}
|
|
79875
|
+
const points = path2.toPolyline();
|
|
79876
|
+
if (!Array.isArray(points) || points.length < 2) throw new Error(`${label} path must produce at least two [x, y] points`);
|
|
79877
|
+
return points;
|
|
79878
|
+
}
|
|
79879
|
+
const Loft = {
|
|
79880
|
+
/** Create a loft station from a 2D profile and an axis position. */
|
|
79881
|
+
station(profile, position) {
|
|
79882
|
+
if (!Number.isFinite(position)) throw new Error("Loft.station position must be finite");
|
|
79883
|
+
return { profile, position };
|
|
79884
|
+
},
|
|
79885
|
+
/** Create a guide rail that constrains the section-local negative-X side. */
|
|
79886
|
+
leftRail(path2) {
|
|
79887
|
+
return { side: "left", path: path2 };
|
|
79888
|
+
},
|
|
79889
|
+
/** Create a guide rail that constrains the section-local positive-X side. */
|
|
79890
|
+
rightRail(path2) {
|
|
79891
|
+
return { side: "right", path: path2 };
|
|
79892
|
+
},
|
|
79893
|
+
/** Create a guide rail that constrains the section-local positive-Y side. */
|
|
79894
|
+
frontRail(path2) {
|
|
79895
|
+
return { side: "front", path: path2 };
|
|
79896
|
+
},
|
|
79897
|
+
/** Create a guide rail that constrains the section-local negative-Y side. */
|
|
79898
|
+
backRail(path2) {
|
|
79899
|
+
return { side: "back", path: path2 };
|
|
79900
|
+
},
|
|
79901
|
+
/** Create a guide rail that moves section centers along the loft. */
|
|
79902
|
+
centerRail(path2) {
|
|
79903
|
+
return { side: "center", path: path2 };
|
|
79904
|
+
},
|
|
79905
|
+
/**
|
|
79906
|
+
* Place a 2D guide path onto the XZ plane.
|
|
79907
|
+
*
|
|
79908
|
+
* The path's first coordinate becomes X and its second coordinate becomes Z.
|
|
79909
|
+
* Use this for left/right silhouette rails authored with `path()` or `constrainedSketch()`.
|
|
79910
|
+
*/
|
|
79911
|
+
pathOnXz(path2, y2 = 0) {
|
|
79912
|
+
if (!Number.isFinite(y2)) throw new Error("Loft.pathOnXz y must be finite");
|
|
79913
|
+
return mapLoftPath2D(path2, "Loft.pathOnXz", ([x2, z2]) => [x2, y2, z2]);
|
|
79914
|
+
},
|
|
79915
|
+
/**
|
|
79916
|
+
* Place a 2D guide path onto the YZ plane.
|
|
79917
|
+
*
|
|
79918
|
+
* The path's first coordinate becomes Y and its second coordinate becomes Z.
|
|
79919
|
+
* Use this for front/back crown rails authored with `path()` or `constrainedSketch()`.
|
|
79920
|
+
*/
|
|
79921
|
+
pathOnYz(path2, x2 = 0) {
|
|
79922
|
+
if (!Number.isFinite(x2)) throw new Error("Loft.pathOnYz x must be finite");
|
|
79923
|
+
return mapLoftPath2D(path2, "Loft.pathOnYz", ([y2, z2]) => [x2, y2, z2]);
|
|
79924
|
+
},
|
|
79925
|
+
/**
|
|
79926
|
+
* Place a 2D guide path onto the XY plane.
|
|
79927
|
+
*
|
|
79928
|
+
* The path's first coordinate becomes X and its second coordinate becomes Y.
|
|
79929
|
+
* Use this when lofting along X or Y and a rail lives in a horizontal sketch plane.
|
|
79930
|
+
*/
|
|
79931
|
+
pathOnXy(path2, z2 = 0) {
|
|
79932
|
+
if (!Number.isFinite(z2)) throw new Error("Loft.pathOnXy z must be finite");
|
|
79933
|
+
return mapLoftPath2D(path2, "Loft.pathOnXy", ([x2, y2]) => [x2, y2, z2]);
|
|
79934
|
+
},
|
|
79935
|
+
/**
|
|
79936
|
+
* Loft through profile stations while forcing generated sections to follow guide rails.
|
|
79937
|
+
*
|
|
79938
|
+
* Stations define the cross-section family. Guide rails define the side or center
|
|
79939
|
+
* paths the loft must pass through. With opposite side rails, the section is scaled
|
|
79940
|
+
* to touch both rails. With one side rail, the section keeps its interpolated size
|
|
79941
|
+
* unless a center rail is also present.
|
|
79942
|
+
*/
|
|
79943
|
+
withGuideRails(stations, rails, options = {}) {
|
|
79944
|
+
return loftWithGuideRails(stations, rails, options);
|
|
79945
|
+
}
|
|
79946
|
+
};
|
|
79476
79947
|
let collectedHighlights = [];
|
|
79477
79948
|
function resetHighlights() {
|
|
79478
79949
|
collectedHighlights = [];
|
|
@@ -296941,6 +297412,7 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
|
|
|
296941
297412
|
nurbsSurface,
|
|
296942
297413
|
spline2d,
|
|
296943
297414
|
spline3d,
|
|
297415
|
+
Loft,
|
|
296944
297416
|
loft,
|
|
296945
297417
|
loftAlongSpine,
|
|
296946
297418
|
sweep,
|