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
|
@@ -799,18 +799,18 @@ function cloneSdfNode(node) {
|
|
|
799
799
|
}
|
|
800
800
|
}
|
|
801
801
|
const SHEET_METAL_EDGES = ["top", "right", "bottom", "left"];
|
|
802
|
-
const EPS$
|
|
802
|
+
const EPS$d = 1e-9;
|
|
803
803
|
function isFinitePositive$3(value) {
|
|
804
804
|
return Number.isFinite(value) && value > 0;
|
|
805
805
|
}
|
|
806
806
|
function isFiniteNonNegative(value) {
|
|
807
807
|
return Number.isFinite(value) && value >= 0;
|
|
808
808
|
}
|
|
809
|
-
function cloneVec3$
|
|
809
|
+
function cloneVec3$5(vec2) {
|
|
810
810
|
return [vec2[0], vec2[1], vec2[2]];
|
|
811
811
|
}
|
|
812
812
|
function cloneFaceAxis(vec2) {
|
|
813
|
-
return vec2 ? cloneVec3$
|
|
813
|
+
return vec2 ? cloneVec3$5(vec2) : void 0;
|
|
814
814
|
}
|
|
815
815
|
function cloneSheetMetalModel(model) {
|
|
816
816
|
if (!model) return null;
|
|
@@ -840,7 +840,7 @@ function edgeDisplayName(edge) {
|
|
|
840
840
|
return `sheetMetal().flange("${edge}", ...)`;
|
|
841
841
|
}
|
|
842
842
|
function normalizeAngle(angleDeg) {
|
|
843
|
-
return Math.abs(angleDeg) <= EPS$
|
|
843
|
+
return Math.abs(angleDeg) <= EPS$d ? 0 : angleDeg;
|
|
844
844
|
}
|
|
845
845
|
function validateSheetMetalModel(model) {
|
|
846
846
|
if (!isFinitePositive$3(model.panel.width) || !isFinitePositive$3(model.panel.height)) {
|
|
@@ -852,7 +852,7 @@ function validateSheetMetalModel(model) {
|
|
|
852
852
|
if (!isFiniteNonNegative(model.bendRadius)) {
|
|
853
853
|
return "sheetMetal() requires a finite non-negative bendRadius.";
|
|
854
854
|
}
|
|
855
|
-
if (model.bendRadius <= EPS$
|
|
855
|
+
if (model.bendRadius <= EPS$d) {
|
|
856
856
|
return "sheetMetal() v1 requires a positive bendRadius so the bend region stays explicit instead of collapsing into a sharp fold.";
|
|
857
857
|
}
|
|
858
858
|
if (model.bendAllowance.kind !== "k-factor") {
|
|
@@ -914,7 +914,7 @@ function deriveSheetMetalModel(model) {
|
|
|
914
914
|
const trimEnd = flanges.has(adjacent.end) ? model.cornerRelief.size : 0;
|
|
915
915
|
const fullLength = edge === "top" || edge === "bottom" ? model.panel.width : model.panel.height;
|
|
916
916
|
const span = fullLength - trimStart - trimEnd;
|
|
917
|
-
if (!(span > EPS$
|
|
917
|
+
if (!(span > EPS$d)) {
|
|
918
918
|
throw new Error(
|
|
919
919
|
`${edgeDisplayName(edge)} loses all usable span after applying the defended rectangular corner relief size ${model.cornerRelief.size}.`
|
|
920
920
|
);
|
|
@@ -972,7 +972,7 @@ function transformPlacement(origin, u2, v, normal) {
|
|
|
972
972
|
};
|
|
973
973
|
}
|
|
974
974
|
function translatePlan(plan, x2, y2, z2) {
|
|
975
|
-
if (Math.abs(x2) <= EPS$
|
|
975
|
+
if (Math.abs(x2) <= EPS$d && Math.abs(y2) <= EPS$d && Math.abs(z2) <= EPS$d) return cloneShapeCompilePlan(plan);
|
|
976
976
|
return appendShapeCompileTransform(cloneShapeCompilePlan(plan), {
|
|
977
977
|
kind: "translate",
|
|
978
978
|
x: x2,
|
|
@@ -1120,8 +1120,8 @@ function lowerSheetMetalBasePlan(model, output) {
|
|
|
1120
1120
|
function descriptor(name, center, normal, planar, uAxis, vAxis, semantic = "face", memberNames = [name], coplanar = planar) {
|
|
1121
1121
|
return {
|
|
1122
1122
|
name,
|
|
1123
|
-
center: cloneVec3$
|
|
1124
|
-
normal: cloneVec3$
|
|
1123
|
+
center: cloneVec3$5(center),
|
|
1124
|
+
normal: cloneVec3$5(normal),
|
|
1125
1125
|
planar,
|
|
1126
1126
|
uAxis: cloneFaceAxis(uAxis),
|
|
1127
1127
|
vAxis: cloneFaceAxis(vAxis),
|
|
@@ -1374,10 +1374,10 @@ function describeSheetMetalPlanarRegionFrames(model, output) {
|
|
|
1374
1374
|
const size = sheetMetalPlanarRegionSize(derived, face.name);
|
|
1375
1375
|
return {
|
|
1376
1376
|
name: face.name,
|
|
1377
|
-
center: cloneVec3$
|
|
1378
|
-
normal: cloneVec3$
|
|
1379
|
-
uAxis: cloneVec3$
|
|
1380
|
-
vAxis: cloneVec3$
|
|
1377
|
+
center: cloneVec3$5(face.center),
|
|
1378
|
+
normal: cloneVec3$5(face.normal),
|
|
1379
|
+
uAxis: cloneVec3$5(face.uAxis),
|
|
1380
|
+
vAxis: cloneVec3$5(face.vAxis),
|
|
1381
1381
|
width: size.width,
|
|
1382
1382
|
height: size.height,
|
|
1383
1383
|
thickness: derived.thickness
|
|
@@ -1416,7 +1416,7 @@ function cloneShapeWorkplanePlacement(placement) {
|
|
|
1416
1416
|
placement: cloneSketchPlacementModel(placement.placement)
|
|
1417
1417
|
};
|
|
1418
1418
|
}
|
|
1419
|
-
const EPS$
|
|
1419
|
+
const EPS$c = 1e-10;
|
|
1420
1420
|
function subVec3(a2, b) {
|
|
1421
1421
|
return [a2[0] - b[0], a2[1] - b[1], a2[2] - b[2]];
|
|
1422
1422
|
}
|
|
@@ -1442,7 +1442,7 @@ function projectRadial(v, axis) {
|
|
|
1442
1442
|
function signedAngleAroundAxis(from, to, axis) {
|
|
1443
1443
|
const fromLen = lengthVec3$1(from);
|
|
1444
1444
|
const toLen = lengthVec3$1(to);
|
|
1445
|
-
if (fromLen < EPS$
|
|
1445
|
+
if (fromLen < EPS$c || toLen < EPS$c) return 0;
|
|
1446
1446
|
const fn = scaleVec3(from, 1 / fromLen);
|
|
1447
1447
|
const tn = scaleVec3(to, 1 / toLen);
|
|
1448
1448
|
const sin2 = dotVec3$4(axis, crossVec3$2(fn, tn));
|
|
@@ -1463,19 +1463,19 @@ function solveRotateAroundAngle(axis, pivot, movingPoint, targetPoint, options =
|
|
|
1463
1463
|
const targetDecomp = projectRadial(target, unitAxis);
|
|
1464
1464
|
const movingRadialLen = lengthVec3$1(movingDecomp.radial);
|
|
1465
1465
|
const targetRadialLen = lengthVec3$1(targetDecomp.radial);
|
|
1466
|
-
if (movingRadialLen < EPS$
|
|
1467
|
-
if (mode === "line" && targetRadialLen >= EPS$
|
|
1466
|
+
if (movingRadialLen < EPS$c) {
|
|
1467
|
+
if (mode === "line" && targetRadialLen >= EPS$c) {
|
|
1468
1468
|
throw new Error("rotateAroundTo(...): moving point lies on the rotation axis, so line alignment is impossible");
|
|
1469
1469
|
}
|
|
1470
1470
|
return 0;
|
|
1471
1471
|
}
|
|
1472
1472
|
if (mode === "plane") {
|
|
1473
|
-
if (targetRadialLen < EPS$
|
|
1473
|
+
if (targetRadialLen < EPS$c) {
|
|
1474
1474
|
throw new Error("rotateAroundTo(...): target point lies on the rotation axis, so the target plane is undefined");
|
|
1475
1475
|
}
|
|
1476
1476
|
return signedAngleAroundAxis(movingDecomp.radial, targetDecomp.radial, unitAxis);
|
|
1477
1477
|
}
|
|
1478
|
-
if (targetRadialLen < EPS$
|
|
1478
|
+
if (targetRadialLen < EPS$c) {
|
|
1479
1479
|
throw new Error("rotateAroundTo(...): target line lies on the rotation axis, but the moving point does not");
|
|
1480
1480
|
}
|
|
1481
1481
|
const axialTol = 1e-8 * Math.max(1, Math.abs(movingDecomp.axial), Math.abs(targetDecomp.axial));
|
|
@@ -1515,10 +1515,10 @@ function multiplyMat4(a2, b) {
|
|
|
1515
1515
|
}
|
|
1516
1516
|
function normalizeVec3$5(v) {
|
|
1517
1517
|
const len2 = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
|
|
1518
|
-
if (len2 < EPS$
|
|
1518
|
+
if (len2 < EPS$c) throw new Error("Axis must be non-zero");
|
|
1519
1519
|
return [v[0] / len2, v[1] / len2, v[2] / len2];
|
|
1520
1520
|
}
|
|
1521
|
-
function transformPoint$
|
|
1521
|
+
function transformPoint$2(m2, p2, w2) {
|
|
1522
1522
|
const x2 = p2[0], y2 = p2[1], z2 = p2[2];
|
|
1523
1523
|
return [
|
|
1524
1524
|
m2[0] * x2 + m2[4] * y2 + m2[8] * z2 + m2[12] * w2,
|
|
@@ -1545,7 +1545,7 @@ function invertMat4(m2) {
|
|
|
1545
1545
|
const b10 = a21 * a33 - a23 * a31;
|
|
1546
1546
|
const b11 = a22 * a33 - a23 * a32;
|
|
1547
1547
|
const det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
|
|
1548
|
-
if (Math.abs(det) < EPS$
|
|
1548
|
+
if (Math.abs(det) < EPS$c) throw new Error("Transform matrix is not invertible");
|
|
1549
1549
|
const invDet = 1 / det;
|
|
1550
1550
|
out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * invDet;
|
|
1551
1551
|
out[1] = (a02 * b10 - a01 * b11 - a03 * b09) * invDet;
|
|
@@ -1638,11 +1638,11 @@ class Transform {
|
|
|
1638
1638
|
}
|
|
1639
1639
|
/** Transform a point using homogeneous coordinates. */
|
|
1640
1640
|
point(p2) {
|
|
1641
|
-
return transformPoint$
|
|
1641
|
+
return transformPoint$2(this.m, p2, 1);
|
|
1642
1642
|
}
|
|
1643
1643
|
/** Transform a direction vector without translation. */
|
|
1644
1644
|
vector(v) {
|
|
1645
|
-
return transformPoint$
|
|
1645
|
+
return transformPoint$2(this.m, v, 0);
|
|
1646
1646
|
}
|
|
1647
1647
|
/** Return the transform as a raw 4x4 matrix array. */
|
|
1648
1648
|
toArray() {
|
|
@@ -3302,14 +3302,14 @@ function sweepPathToPolylineAdaptive(path2, baseSamples = 48) {
|
|
|
3302
3302
|
pts.push(evalPathAt(path2, 1));
|
|
3303
3303
|
return pts;
|
|
3304
3304
|
}
|
|
3305
|
-
const EPS$
|
|
3305
|
+
const EPS$b = 1e-8;
|
|
3306
3306
|
const SUPPORTED_VERTICAL_EDGE_NAMES = ["vert-bl", "vert-br", "vert-tr", "vert-tl"];
|
|
3307
3307
|
function midpoint$4(start, end) {
|
|
3308
3308
|
return [(start[0] + end[0]) * 0.5, (start[1] + end[1]) * 0.5, (start[2] + end[2]) * 0.5];
|
|
3309
3309
|
}
|
|
3310
3310
|
function normalize$8(v) {
|
|
3311
3311
|
const len2 = Math.hypot(v[0], v[1], v[2]);
|
|
3312
|
-
if (len2 <= EPS$
|
|
3312
|
+
if (len2 <= EPS$b) throw new Error("Edge feature selection requires a non-zero direction vector");
|
|
3313
3313
|
return [v[0] / len2, v[1] / len2, v[2] / len2];
|
|
3314
3314
|
}
|
|
3315
3315
|
function subtract(a2, b) {
|
|
@@ -3391,7 +3391,7 @@ function rigidTransformForEdgeStep(step) {
|
|
|
3391
3391
|
case "mirror": {
|
|
3392
3392
|
const [nx0, ny0, nz0] = [step.normalX, step.normalY, step.normalZ];
|
|
3393
3393
|
const len2 = Math.hypot(nx0, ny0, nz0);
|
|
3394
|
-
if (len2 <= EPS$
|
|
3394
|
+
if (len2 <= EPS$b) return Transform.identity();
|
|
3395
3395
|
const nx = nx0 / len2;
|
|
3396
3396
|
const ny = ny0 / len2;
|
|
3397
3397
|
const nz = nz0 / len2;
|
|
@@ -3702,7 +3702,7 @@ function isRectangleProfile(points) {
|
|
|
3702
3702
|
return [next[0] - point2[0], next[1] - point2[1]];
|
|
3703
3703
|
});
|
|
3704
3704
|
const lengths2 = vectors.map(([x2, y2]) => Math.hypot(x2, y2));
|
|
3705
|
-
if (lengths2.some((length4) => length4 <= EPS$
|
|
3705
|
+
if (lengths2.some((length4) => length4 <= EPS$b)) return false;
|
|
3706
3706
|
const dot01 = vectors[0][0] * vectors[1][0] + vectors[0][1] * vectors[1][1];
|
|
3707
3707
|
const dot12 = vectors[1][0] * vectors[2][0] + vectors[1][1] * vectors[2][1];
|
|
3708
3708
|
const dot23 = vectors[2][0] * vectors[3][0] + vectors[2][1] * vectors[3][1];
|
|
@@ -5769,13 +5769,13 @@ function parseMeshFile(data, format) {
|
|
|
5769
5769
|
return parse3mf(data);
|
|
5770
5770
|
}
|
|
5771
5771
|
}
|
|
5772
|
-
const EPS$
|
|
5772
|
+
const EPS$a = 1e-8;
|
|
5773
5773
|
function length$3(v) {
|
|
5774
5774
|
return Math.hypot(v[0], v[1], v[2]);
|
|
5775
5775
|
}
|
|
5776
5776
|
function normalize$7(v) {
|
|
5777
5777
|
const len2 = length$3(v);
|
|
5778
|
-
if (len2 < EPS$
|
|
5778
|
+
if (len2 < EPS$a) throw new Error("Plane normal must be non-zero");
|
|
5779
5779
|
return [v[0] / len2, v[1] / len2, v[2] / len2];
|
|
5780
5780
|
}
|
|
5781
5781
|
function resolvePlaneOriginNormal(plane) {
|
|
@@ -5797,12 +5797,12 @@ function resolvePlaneOriginNormal(plane) {
|
|
|
5797
5797
|
function rotationToPlaneSpace(normal) {
|
|
5798
5798
|
const n = normalize$7(normal);
|
|
5799
5799
|
const dot2 = n[2];
|
|
5800
|
-
if (dot2 > 1 - EPS$
|
|
5800
|
+
if (dot2 > 1 - EPS$a) {
|
|
5801
5801
|
return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
|
|
5802
5802
|
}
|
|
5803
5803
|
let axis;
|
|
5804
5804
|
let angle;
|
|
5805
|
-
if (dot2 < -1 + EPS$
|
|
5805
|
+
if (dot2 < -1 + EPS$a) {
|
|
5806
5806
|
axis = [1, 0, 0];
|
|
5807
5807
|
angle = Math.PI;
|
|
5808
5808
|
} else {
|
|
@@ -8364,10 +8364,10 @@ function scale$6(v, s) {
|
|
|
8364
8364
|
function sub$7(a2, b) {
|
|
8365
8365
|
return [a2[0] - b[0], a2[1] - b[1], a2[2] - b[2]];
|
|
8366
8366
|
}
|
|
8367
|
-
function cross$
|
|
8367
|
+
function cross$8(a2, b) {
|
|
8368
8368
|
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]];
|
|
8369
8369
|
}
|
|
8370
|
-
function makeEdge(name, start, end, faceName, curve) {
|
|
8370
|
+
function makeEdge$1(name, start, end, faceName, curve) {
|
|
8371
8371
|
return {
|
|
8372
8372
|
name,
|
|
8373
8373
|
start,
|
|
@@ -8400,7 +8400,7 @@ function buildSurfaceSheetTopology(boundaries, options = {}) {
|
|
|
8400
8400
|
const center = options.center ?? average$1(corners);
|
|
8401
8401
|
const uAxis = normalizeAxis$1(sub$7(midpoint$3(u1Start, u1End), midpoint$3(u0Start, u0End)));
|
|
8402
8402
|
const vAxis = normalizeAxis$1(sub$7(midpoint$3(v1Start, v1End), midpoint$3(v0Start, v0End)));
|
|
8403
|
-
const normal = normalizeAxis$1(options.normal ?? cross$
|
|
8403
|
+
const normal = normalizeAxis$1(options.normal ?? cross$8(uAxis, vAxis));
|
|
8404
8404
|
const faces = /* @__PURE__ */ new Map();
|
|
8405
8405
|
faces.set(faceName, {
|
|
8406
8406
|
name: faceName,
|
|
@@ -8411,10 +8411,10 @@ function buildSurfaceSheetTopology(boundaries, options = {}) {
|
|
|
8411
8411
|
surface: options.surface
|
|
8412
8412
|
});
|
|
8413
8413
|
const edges = /* @__PURE__ */ new Map();
|
|
8414
|
-
edges.set("u0", makeEdge("u0", u0Start, u0End, faceName, (_a3 = options.edgeCurves) == null ? void 0 : _a3.u0));
|
|
8415
|
-
edges.set("u1", makeEdge("u1", u1Start, u1End, faceName, (_b3 = options.edgeCurves) == null ? void 0 : _b3.u1));
|
|
8416
|
-
edges.set("v0", makeEdge("v0", v0Start, v0End, faceName, (_c2 = options.edgeCurves) == null ? void 0 : _c2.v0));
|
|
8417
|
-
edges.set("v1", makeEdge("v1", v1Start, v1End, faceName, (_d2 = options.edgeCurves) == null ? void 0 : _d2.v1));
|
|
8414
|
+
edges.set("u0", makeEdge$1("u0", u0Start, u0End, faceName, (_a3 = options.edgeCurves) == null ? void 0 : _a3.u0));
|
|
8415
|
+
edges.set("u1", makeEdge$1("u1", u1Start, u1End, faceName, (_b3 = options.edgeCurves) == null ? void 0 : _b3.u1));
|
|
8416
|
+
edges.set("v0", makeEdge$1("v0", v0Start, v0End, faceName, (_c2 = options.edgeCurves) == null ? void 0 : _c2.v0));
|
|
8417
|
+
edges.set("v1", makeEdge$1("v1", v1Start, v1End, faceName, (_d2 = options.edgeCurves) == null ? void 0 : _d2.v1));
|
|
8418
8418
|
return { faces, edges };
|
|
8419
8419
|
}
|
|
8420
8420
|
function attachSurfaceSheetTopology(shape, boundaries, options = {}) {
|
|
@@ -8432,7 +8432,7 @@ function attachSurfaceSheetTopology(shape, boundaries, options = {}) {
|
|
|
8432
8432
|
});
|
|
8433
8433
|
return shape;
|
|
8434
8434
|
}
|
|
8435
|
-
function cloneVec3$
|
|
8435
|
+
function cloneVec3$4(value) {
|
|
8436
8436
|
return [value[0], value[1], value[2]];
|
|
8437
8437
|
}
|
|
8438
8438
|
function cloneNurbsFaceTrimLoop(loop) {
|
|
@@ -8454,38 +8454,38 @@ function cloneFaceSurface(surface) {
|
|
|
8454
8454
|
if (!surface) return void 0;
|
|
8455
8455
|
switch (surface.kind) {
|
|
8456
8456
|
case "plane":
|
|
8457
|
-
return { kind: "plane", normal: cloneVec3$
|
|
8457
|
+
return { kind: "plane", normal: cloneVec3$4(surface.normal) };
|
|
8458
8458
|
case "cylinder":
|
|
8459
8459
|
return {
|
|
8460
8460
|
kind: "cylinder",
|
|
8461
|
-
origin: cloneVec3$
|
|
8462
|
-
axis: cloneVec3$
|
|
8461
|
+
origin: cloneVec3$4(surface.origin),
|
|
8462
|
+
axis: cloneVec3$4(surface.axis),
|
|
8463
8463
|
radius: surface.radius,
|
|
8464
8464
|
height: surface.height
|
|
8465
8465
|
};
|
|
8466
8466
|
case "cone":
|
|
8467
8467
|
return {
|
|
8468
8468
|
kind: "cone",
|
|
8469
|
-
origin: cloneVec3$
|
|
8470
|
-
axis: cloneVec3$
|
|
8469
|
+
origin: cloneVec3$4(surface.origin),
|
|
8470
|
+
axis: cloneVec3$4(surface.axis),
|
|
8471
8471
|
radiusBottom: surface.radiusBottom,
|
|
8472
8472
|
radiusTop: surface.radiusTop,
|
|
8473
8473
|
height: surface.height
|
|
8474
8474
|
};
|
|
8475
8475
|
case "sphere":
|
|
8476
|
-
return { kind: "sphere", center: cloneVec3$
|
|
8476
|
+
return { kind: "sphere", center: cloneVec3$4(surface.center), radius: surface.radius };
|
|
8477
8477
|
case "torus":
|
|
8478
8478
|
return {
|
|
8479
8479
|
kind: "torus",
|
|
8480
|
-
center: cloneVec3$
|
|
8481
|
-
axis: cloneVec3$
|
|
8480
|
+
center: cloneVec3$4(surface.center),
|
|
8481
|
+
axis: cloneVec3$4(surface.axis),
|
|
8482
8482
|
majorRadius: surface.majorRadius,
|
|
8483
8483
|
minorRadius: surface.minorRadius
|
|
8484
8484
|
};
|
|
8485
8485
|
case "ruled":
|
|
8486
8486
|
return {
|
|
8487
8487
|
kind: "ruled",
|
|
8488
|
-
rails: surface.rails.map((rail2) => rail2.map(cloneVec3$
|
|
8488
|
+
rails: surface.rails.map((rail2) => rail2.map(cloneVec3$4))
|
|
8489
8489
|
};
|
|
8490
8490
|
case "nurbs":
|
|
8491
8491
|
return {
|
|
@@ -8498,9 +8498,9 @@ function cloneEdgeCurve(curve) {
|
|
|
8498
8498
|
if (!curve) return void 0;
|
|
8499
8499
|
switch (curve.kind) {
|
|
8500
8500
|
case "line":
|
|
8501
|
-
return { ...curve, start: cloneVec3$
|
|
8501
|
+
return { ...curve, start: cloneVec3$4(curve.start), end: cloneVec3$4(curve.end) };
|
|
8502
8502
|
case "circle":
|
|
8503
|
-
return { ...curve, center: cloneVec3$
|
|
8503
|
+
return { ...curve, center: cloneVec3$4(curve.center), axis: cloneVec3$4(curve.axis) };
|
|
8504
8504
|
case "surfaceIso":
|
|
8505
8505
|
return { ...curve, parameterRange: [curve.parameterRange[0], curve.parameterRange[1]] };
|
|
8506
8506
|
case "nurbsUv":
|
|
@@ -8518,7 +8518,7 @@ function cloneEdgeCurve(curve) {
|
|
|
8518
8518
|
}
|
|
8519
8519
|
}
|
|
8520
8520
|
function makeLineEdgeCurve(start, end, faceName) {
|
|
8521
|
-
return faceName ? { kind: "line", start: cloneVec3$
|
|
8521
|
+
return faceName ? { kind: "line", start: cloneVec3$4(start), end: cloneVec3$4(end), faceName } : { kind: "line", start: cloneVec3$4(start), end: cloneVec3$4(end) };
|
|
8522
8522
|
}
|
|
8523
8523
|
function edgeCurveFaceName(curve) {
|
|
8524
8524
|
switch (curve.kind) {
|
|
@@ -10302,6 +10302,7 @@ function buildSweepLevelSetInput(profilePolygons, pathInput, options) {
|
|
|
10302
10302
|
edgeLength: options.edgeLength
|
|
10303
10303
|
};
|
|
10304
10304
|
}
|
|
10305
|
+
const EPS$9 = 1e-9;
|
|
10305
10306
|
function resamplePolygon(poly, targetCount) {
|
|
10306
10307
|
if (poly.length < 2) return poly;
|
|
10307
10308
|
if (targetCount <= 0) return [];
|
|
@@ -10339,6 +10340,78 @@ function resamplePolygon(poly, targetCount) {
|
|
|
10339
10340
|
}
|
|
10340
10341
|
return out;
|
|
10341
10342
|
}
|
|
10343
|
+
function resamplePolygonByAngle(poly, targetCount, center = polygonCentroid$2(poly)) {
|
|
10344
|
+
if (poly.length < 3 || targetCount <= 0) return null;
|
|
10345
|
+
if (!isConvexPolygon(poly)) return null;
|
|
10346
|
+
const out = [];
|
|
10347
|
+
for (let index2 = 0; index2 < targetCount; index2 += 1) {
|
|
10348
|
+
const angle = index2 / targetCount * Math.PI * 2;
|
|
10349
|
+
const point2 = rayPolygonIntersection(center, [Math.cos(angle), Math.sin(angle)], poly);
|
|
10350
|
+
if (!point2) return null;
|
|
10351
|
+
out.push(point2);
|
|
10352
|
+
}
|
|
10353
|
+
return out;
|
|
10354
|
+
}
|
|
10355
|
+
function rayPolygonIntersection(origin, direction2, poly) {
|
|
10356
|
+
let bestT = Infinity;
|
|
10357
|
+
let best = null;
|
|
10358
|
+
for (let index2 = 0; index2 < poly.length; index2 += 1) {
|
|
10359
|
+
const a2 = poly[index2];
|
|
10360
|
+
const b = poly[(index2 + 1) % poly.length];
|
|
10361
|
+
const edge = [b[0] - a2[0], b[1] - a2[1]];
|
|
10362
|
+
const denom = cross$7(direction2, edge);
|
|
10363
|
+
if (Math.abs(denom) < EPS$9) continue;
|
|
10364
|
+
const delta = [a2[0] - origin[0], a2[1] - origin[1]];
|
|
10365
|
+
const rayT = cross$7(delta, edge) / denom;
|
|
10366
|
+
const edgeT = cross$7(delta, direction2) / denom;
|
|
10367
|
+
if (rayT >= -EPS$9 && edgeT >= -EPS$9 && edgeT <= 1 + EPS$9 && rayT < bestT) {
|
|
10368
|
+
bestT = rayT;
|
|
10369
|
+
best = [origin[0] + direction2[0] * rayT, origin[1] + direction2[1] * rayT];
|
|
10370
|
+
}
|
|
10371
|
+
}
|
|
10372
|
+
return best;
|
|
10373
|
+
}
|
|
10374
|
+
function polygonCentroid$2(poly) {
|
|
10375
|
+
let area2 = 0;
|
|
10376
|
+
let cx = 0;
|
|
10377
|
+
let cy = 0;
|
|
10378
|
+
for (let index2 = 0; index2 < poly.length; index2 += 1) {
|
|
10379
|
+
const a2 = poly[index2];
|
|
10380
|
+
const b = poly[(index2 + 1) % poly.length];
|
|
10381
|
+
const crossValue = cross$7(a2, b);
|
|
10382
|
+
area2 += crossValue;
|
|
10383
|
+
cx += (a2[0] + b[0]) * crossValue;
|
|
10384
|
+
cy += (a2[1] + b[1]) * crossValue;
|
|
10385
|
+
}
|
|
10386
|
+
if (Math.abs(area2) < EPS$9) return averagePoint(poly);
|
|
10387
|
+
return [cx / (3 * area2), cy / (3 * area2)];
|
|
10388
|
+
}
|
|
10389
|
+
function averagePoint(poly) {
|
|
10390
|
+
let x2 = 0;
|
|
10391
|
+
let y2 = 0;
|
|
10392
|
+
for (const point2 of poly) {
|
|
10393
|
+
x2 += point2[0];
|
|
10394
|
+
y2 += point2[1];
|
|
10395
|
+
}
|
|
10396
|
+
return [x2 / poly.length, y2 / poly.length];
|
|
10397
|
+
}
|
|
10398
|
+
function isConvexPolygon(poly) {
|
|
10399
|
+
let sign2 = 0;
|
|
10400
|
+
for (let index2 = 0; index2 < poly.length; index2 += 1) {
|
|
10401
|
+
const a2 = poly[index2];
|
|
10402
|
+
const b = poly[(index2 + 1) % poly.length];
|
|
10403
|
+
const c2 = poly[(index2 + 2) % poly.length];
|
|
10404
|
+
const turn = cross$7([b[0] - a2[0], b[1] - a2[1]], [c2[0] - b[0], c2[1] - b[1]]);
|
|
10405
|
+
if (Math.abs(turn) < EPS$9) continue;
|
|
10406
|
+
const currentSign = Math.sign(turn);
|
|
10407
|
+
if (sign2 !== 0 && currentSign !== sign2) return false;
|
|
10408
|
+
sign2 = currentSign;
|
|
10409
|
+
}
|
|
10410
|
+
return sign2 !== 0;
|
|
10411
|
+
}
|
|
10412
|
+
function cross$7(a2, b) {
|
|
10413
|
+
return a2[0] * b[1] - a2[1] * b[0];
|
|
10414
|
+
}
|
|
10342
10415
|
function loftStitched(profiles2, heights, wasm) {
|
|
10343
10416
|
if (profiles2.length < 2) return null;
|
|
10344
10417
|
const classified = profiles2.map((loops) => classifyLoops(loops));
|
|
@@ -10467,8 +10540,10 @@ function stitchSingleLoopLoft(loops, heights, wasm) {
|
|
|
10467
10540
|
maxPoints = Math.max(maxPoints, loop.length);
|
|
10468
10541
|
}
|
|
10469
10542
|
const N = Math.max(maxPoints, 24);
|
|
10543
|
+
const angularSamples = normalizedLoops.map((loop) => resamplePolygonByAngle(loop, N));
|
|
10544
|
+
const useAngularSamples = angularSamples.every((samples) => samples != null);
|
|
10470
10545
|
const resampled = normalizedLoops.map((loop, i) => {
|
|
10471
|
-
const pts2d = resamplePolygon(loop, N);
|
|
10546
|
+
const pts2d = useAngularSamples ? angularSamples[i] : resamplePolygon(loop, N);
|
|
10472
10547
|
const z2 = heights[i];
|
|
10473
10548
|
return pts2d.map(([x2, y2]) => [x2, y2, z2]);
|
|
10474
10549
|
});
|
|
@@ -10530,7 +10605,7 @@ async function initManifoldWasm() {
|
|
|
10530
10605
|
if (_wasm$1) return _wasm$1;
|
|
10531
10606
|
performance.mark("manifold:start");
|
|
10532
10607
|
const Module = (await __vitePreload(async () => {
|
|
10533
|
-
const { default: __vite_default__ } = await import("./manifold-
|
|
10608
|
+
const { default: __vite_default__ } = await import("./manifold-DzZ4VRPs.js");
|
|
10534
10609
|
return { default: __vite_default__ };
|
|
10535
10610
|
}, true ? [] : void 0)).default;
|
|
10536
10611
|
performance.mark("manifold:imported");
|
|
@@ -22414,7 +22489,7 @@ function marchingTetrahedra(sdfFn, bounds, edgeLength2) {
|
|
|
22414
22489
|
};
|
|
22415
22490
|
}
|
|
22416
22491
|
const EPS$8 = 1e-9;
|
|
22417
|
-
function finitePositive(value) {
|
|
22492
|
+
function finitePositive$1(value) {
|
|
22418
22493
|
return Number.isFinite(value) && value > EPS$8;
|
|
22419
22494
|
}
|
|
22420
22495
|
function clampNonNegative(value) {
|
|
@@ -22433,7 +22508,7 @@ function distancePreservingMatrixScale(matrix) {
|
|
|
22433
22508
|
const sx = length4(col0);
|
|
22434
22509
|
const sy = length4(col1);
|
|
22435
22510
|
const sz = length4(col2);
|
|
22436
|
-
if (!finitePositive(sx) || !finitePositive(sy) || !finitePositive(sz)) return null;
|
|
22511
|
+
if (!finitePositive$1(sx) || !finitePositive$1(sy) || !finitePositive$1(sz)) return null;
|
|
22437
22512
|
if (Math.abs(sx - sy) > EPS$8 || Math.abs(sx - sz) > EPS$8) return null;
|
|
22438
22513
|
if (Math.abs(dot2(col0, col1)) > EPS$8 || Math.abs(dot2(col0, col2)) > EPS$8 || Math.abs(dot2(col1, col2)) > EPS$8) return null;
|
|
22439
22514
|
return sx;
|
|
@@ -22450,7 +22525,7 @@ function transformStepDistanceScale(step) {
|
|
|
22450
22525
|
const sx = Math.abs(step.x);
|
|
22451
22526
|
const sy = Math.abs(step.y);
|
|
22452
22527
|
const sz = Math.abs(step.z);
|
|
22453
|
-
if (!finitePositive(sx) || !finitePositive(sy) || !finitePositive(sz)) return null;
|
|
22528
|
+
if (!finitePositive$1(sx) || !finitePositive$1(sy) || !finitePositive$1(sz)) return null;
|
|
22454
22529
|
return Math.abs(sx - sy) <= EPS$8 && Math.abs(sx - sz) <= EPS$8 ? sx : null;
|
|
22455
22530
|
}
|
|
22456
22531
|
}
|
|
@@ -22490,7 +22565,7 @@ function translatedPlan(base, z2) {
|
|
|
22490
22565
|
};
|
|
22491
22566
|
}
|
|
22492
22567
|
function offsetCylinderDimensions(plan, thickness) {
|
|
22493
|
-
if (!finitePositive(plan.height)) return null;
|
|
22568
|
+
if (!finitePositive$1(plan.height)) return null;
|
|
22494
22569
|
const radiusTop = plan.radiusTop ?? plan.radius;
|
|
22495
22570
|
const slope = (radiusTop - plan.radius) / plan.height;
|
|
22496
22571
|
const normalScale = Math.hypot(1, slope);
|
|
@@ -22499,7 +22574,7 @@ function offsetCylinderDimensions(plan, thickness) {
|
|
|
22499
22574
|
const height = zMax - zMin;
|
|
22500
22575
|
const radiusBottom = plan.radius + thickness * (normalScale - slope);
|
|
22501
22576
|
const offsetRadiusTop = radiusTop + thickness * (normalScale + slope);
|
|
22502
|
-
if (!finitePositive(height) || radiusBottom < -EPS$8 || offsetRadiusTop < -EPS$8) return null;
|
|
22577
|
+
if (!finitePositive$1(height) || radiusBottom < -EPS$8 || offsetRadiusTop < -EPS$8) return null;
|
|
22503
22578
|
return {
|
|
22504
22579
|
zMin,
|
|
22505
22580
|
height,
|
|
@@ -22557,7 +22632,7 @@ function rectangleCandidatePointsFromProfile(plan) {
|
|
|
22557
22632
|
case "rect": {
|
|
22558
22633
|
const width = Math.abs(plan.width);
|
|
22559
22634
|
const height = Math.abs(plan.height);
|
|
22560
|
-
if (!finitePositive(width) || !finitePositive(height)) return null;
|
|
22635
|
+
if (!finitePositive$1(width) || !finitePositive$1(height)) return null;
|
|
22561
22636
|
const halfWidth = width / 2;
|
|
22562
22637
|
const halfHeight = height / 2;
|
|
22563
22638
|
const points = [
|
|
@@ -22591,7 +22666,7 @@ function rectangleFootprintFromProfile(plan) {
|
|
|
22591
22666
|
const [xMin, xMax] = xs;
|
|
22592
22667
|
const [zMin, zMax] = zs;
|
|
22593
22668
|
if (xMin == null || xMax == null || zMin == null || zMax == null) return null;
|
|
22594
|
-
if (xMin < -EPS$8 || !finitePositive(xMax) || !finitePositive(zMax - zMin)) return null;
|
|
22669
|
+
if (xMin < -EPS$8 || !finitePositive$1(xMax) || !finitePositive$1(zMax - zMin)) return null;
|
|
22595
22670
|
const hasCorner = (x2, z2) => points.some(([px2, pz2]) => sameScalar$1(px2, x2) && sameScalar$1(pz2, z2));
|
|
22596
22671
|
if (!hasCorner(xMin, zMin) || !hasCorner(xMax, zMin) || !hasCorner(xMax, zMax) || !hasCorner(xMin, zMax)) return null;
|
|
22597
22672
|
return {
|
|
@@ -22604,7 +22679,7 @@ function rectangleFootprintFromProfile(plan) {
|
|
|
22604
22679
|
function circleFootprintFromProfile(plan) {
|
|
22605
22680
|
if (plan.kind !== "circle") return null;
|
|
22606
22681
|
const radius = Math.abs(plan.radius);
|
|
22607
|
-
if (!finitePositive(radius)) return null;
|
|
22682
|
+
if (!finitePositive$1(radius)) return null;
|
|
22608
22683
|
const center = transformProfilePointThrough$1([0, 0], plan.transforms);
|
|
22609
22684
|
const xPoint = transformProfilePointThrough$1([1, 0], plan.transforms);
|
|
22610
22685
|
const yPoint = transformProfilePointThrough$1([0, 1], plan.transforms);
|
|
@@ -22613,7 +22688,7 @@ function circleFootprintFromProfile(plan) {
|
|
|
22613
22688
|
const xScale = Math.hypot(xAxis[0], xAxis[1]);
|
|
22614
22689
|
const yScale = Math.hypot(yAxis[0], yAxis[1]);
|
|
22615
22690
|
const dot2 = xAxis[0] * yAxis[0] + xAxis[1] * yAxis[1];
|
|
22616
|
-
if (!finitePositive(xScale) || !finitePositive(yScale)) return null;
|
|
22691
|
+
if (!finitePositive$1(xScale) || !finitePositive$1(yScale)) return null;
|
|
22617
22692
|
if (Math.abs(xScale - yScale) > EPS$8 || Math.abs(dot2) > EPS$8 * xScale * yScale) return null;
|
|
22618
22693
|
return {
|
|
22619
22694
|
center,
|
|
@@ -22626,7 +22701,7 @@ function fullCircleRevolveTorusPlan(plan, minorRadiusOffset = 0) {
|
|
|
22626
22701
|
const circle2 = circleFootprintFromProfile(plan.profile);
|
|
22627
22702
|
if (!circle2 || circle2.center[0] <= EPS$8) return null;
|
|
22628
22703
|
const minorRadius = circle2.radius + minorRadiusOffset;
|
|
22629
|
-
if (!finitePositive(minorRadius) || minorRadius >= circle2.center[0] - EPS$8) return null;
|
|
22704
|
+
if (!finitePositive$1(minorRadius) || minorRadius >= circle2.center[0] - EPS$8) return null;
|
|
22630
22705
|
return translatedPlan(
|
|
22631
22706
|
{
|
|
22632
22707
|
kind: "torus",
|
|
@@ -22673,7 +22748,7 @@ function offsetFullRectRevolvePlan(plan, thickness) {
|
|
|
22673
22748
|
const innerRadius = rectangle.innerRadius - thickness;
|
|
22674
22749
|
const outerRadius = rectangle.outerRadius + thickness;
|
|
22675
22750
|
const height = rectangle.zMax - rectangle.zMin + 2 * thickness;
|
|
22676
|
-
if (innerRadius < -EPS$8 || !finitePositive(outerRadius) || !finitePositive(height) || outerRadius <= innerRadius + EPS$8) return null;
|
|
22751
|
+
if (innerRadius < -EPS$8 || !finitePositive$1(outerRadius) || !finitePositive$1(height) || outerRadius <= innerRadius + EPS$8) return null;
|
|
22677
22752
|
const zCenter = (rectangle.zMin + rectangle.zMax) / 2;
|
|
22678
22753
|
if (innerRadius <= EPS$8) {
|
|
22679
22754
|
return translatedPlan(
|
|
@@ -22714,11 +22789,11 @@ function offsetSolidAnalyticPrimitivePlan(base, thickness) {
|
|
|
22714
22789
|
};
|
|
22715
22790
|
}
|
|
22716
22791
|
case "box": {
|
|
22717
|
-
if (!finitePositive(base.z)) return null;
|
|
22792
|
+
if (!finitePositive$1(base.z)) return null;
|
|
22718
22793
|
const x2 = Math.abs(base.x) + 2 * thickness;
|
|
22719
22794
|
const y2 = Math.abs(base.y) + 2 * thickness;
|
|
22720
22795
|
const z2 = base.z + 2 * thickness;
|
|
22721
|
-
if (!finitePositive(x2) || !finitePositive(y2) || !finitePositive(z2)) return null;
|
|
22796
|
+
if (!finitePositive$1(x2) || !finitePositive$1(y2) || !finitePositive$1(z2)) return null;
|
|
22722
22797
|
return translatedPlan(
|
|
22723
22798
|
{
|
|
22724
22799
|
kind: "box",
|
|
@@ -22745,7 +22820,7 @@ function offsetSolidAnalyticPrimitivePlan(base, thickness) {
|
|
|
22745
22820
|
}
|
|
22746
22821
|
case "sphere": {
|
|
22747
22822
|
const radius = base.radius + thickness;
|
|
22748
|
-
if (!finitePositive(radius)) return null;
|
|
22823
|
+
if (!finitePositive$1(radius)) return null;
|
|
22749
22824
|
return {
|
|
22750
22825
|
kind: "sphere",
|
|
22751
22826
|
radius,
|
|
@@ -22754,7 +22829,7 @@ function offsetSolidAnalyticPrimitivePlan(base, thickness) {
|
|
|
22754
22829
|
}
|
|
22755
22830
|
case "torus": {
|
|
22756
22831
|
const minorRadius = base.minorRadius + thickness;
|
|
22757
|
-
if (!finitePositive(minorRadius) || minorRadius >= base.majorRadius - EPS$8) return null;
|
|
22832
|
+
if (!finitePositive$1(minorRadius) || minorRadius >= base.majorRadius - EPS$8) return null;
|
|
22758
22833
|
return {
|
|
22759
22834
|
kind: "torus",
|
|
22760
22835
|
majorRadius: base.majorRadius,
|
|
@@ -23494,18 +23569,18 @@ function faceAxes(face) {
|
|
|
23494
23569
|
}
|
|
23495
23570
|
return {};
|
|
23496
23571
|
}
|
|
23497
|
-
function edgeKey(start, end) {
|
|
23572
|
+
function edgeKey$1(start, end) {
|
|
23498
23573
|
const encode2 = (p2) => p2.map((value) => value.toFixed(9)).join(",");
|
|
23499
23574
|
const a2 = encode2(start);
|
|
23500
23575
|
const b = encode2(end);
|
|
23501
23576
|
return a2 < b ? `${a2}|${b}` : `${b}|${a2}`;
|
|
23502
23577
|
}
|
|
23503
23578
|
function faceEdgeIndex(face, start, end) {
|
|
23504
|
-
const target = edgeKey(start, end);
|
|
23579
|
+
const target = edgeKey$1(start, end);
|
|
23505
23580
|
for (let i = 0; i < face.vertices.length; i++) {
|
|
23506
23581
|
const faceStart = face.vertices[i];
|
|
23507
23582
|
const faceEnd = face.vertices[(i + 1) % face.vertices.length];
|
|
23508
|
-
if (faceStart && faceEnd && edgeKey(faceStart, faceEnd) === target) return i;
|
|
23583
|
+
if (faceStart && faceEnd && edgeKey$1(faceStart, faceEnd) === target) return i;
|
|
23509
23584
|
}
|
|
23510
23585
|
return null;
|
|
23511
23586
|
}
|
|
@@ -23706,7 +23781,7 @@ function topologyPayloadToTopology(payload) {
|
|
|
23706
23781
|
const start = explicitVertices[explicitEdge.vertices[0]];
|
|
23707
23782
|
const end = explicitVertices[explicitEdge.vertices[1]];
|
|
23708
23783
|
if (!isVec3$2(start) || !isVec3$2(end)) continue;
|
|
23709
|
-
const key = edgeKey(start, end);
|
|
23784
|
+
const key = edgeKey$1(start, end);
|
|
23710
23785
|
if (seenEdges.has(key)) continue;
|
|
23711
23786
|
seenEdges.set(key, edges.size);
|
|
23712
23787
|
const display = explicitEdgeDisplayName(payload, explicitEdge, explicitEdgeIndex, start, end);
|
|
@@ -23726,7 +23801,7 @@ function topologyPayloadToTopology(payload) {
|
|
|
23726
23801
|
const start = face.vertices[i];
|
|
23727
23802
|
const end = face.vertices[(i + 1) % face.vertices.length];
|
|
23728
23803
|
if (!start || !end) continue;
|
|
23729
|
-
const key = edgeKey(start, end);
|
|
23804
|
+
const key = edgeKey$1(start, end);
|
|
23730
23805
|
if (seenEdges.has(key)) continue;
|
|
23731
23806
|
seenEdges.set(key, edges.size);
|
|
23732
23807
|
edges.set(`${face.id}:edge-${i}`, {
|
|
@@ -30608,17 +30683,17 @@ function emptyFaceTable() {
|
|
|
30608
30683
|
blockedQueries: []
|
|
30609
30684
|
};
|
|
30610
30685
|
}
|
|
30611
|
-
function cloneVec3$
|
|
30686
|
+
function cloneVec3$3(vec2) {
|
|
30612
30687
|
return [vec2[0], vec2[1], vec2[2]];
|
|
30613
30688
|
}
|
|
30614
30689
|
function cloneFaceRefValue(face) {
|
|
30615
30690
|
return {
|
|
30616
30691
|
...face,
|
|
30617
|
-
normal: cloneVec3$
|
|
30618
|
-
center: cloneVec3$
|
|
30692
|
+
normal: cloneVec3$3(face.normal),
|
|
30693
|
+
center: cloneVec3$3(face.center),
|
|
30619
30694
|
query: cloneFaceQueryRef(face.query),
|
|
30620
|
-
uAxis: face.uAxis ? cloneVec3$
|
|
30621
|
-
vAxis: face.vAxis ? cloneVec3$
|
|
30695
|
+
uAxis: face.uAxis ? cloneVec3$3(face.uAxis) : void 0,
|
|
30696
|
+
vAxis: face.vAxis ? cloneVec3$3(face.vAxis) : void 0,
|
|
30622
30697
|
surface: cloneFaceSurface(face.surface),
|
|
30623
30698
|
descendant: face.descendant ? cloneFaceDescendantMetadata(face.descendant) : void 0
|
|
30624
30699
|
};
|
|
@@ -31473,11 +31548,11 @@ function resolveShapeFaceTableInternal(plan, owner) {
|
|
|
31473
31548
|
for (const descriptor2 of describeSheetMetalFaces(plan.model, plan.output)) {
|
|
31474
31549
|
registerFace(table2, {
|
|
31475
31550
|
name: descriptor2.name,
|
|
31476
|
-
normal: cloneVec3$
|
|
31477
|
-
center: cloneVec3$
|
|
31551
|
+
normal: cloneVec3$3(descriptor2.normal),
|
|
31552
|
+
center: cloneVec3$3(descriptor2.center),
|
|
31478
31553
|
planar: descriptor2.planar,
|
|
31479
|
-
uAxis: descriptor2.uAxis ? cloneVec3$
|
|
31480
|
-
vAxis: descriptor2.vAxis ? cloneVec3$
|
|
31554
|
+
uAxis: descriptor2.uAxis ? cloneVec3$3(descriptor2.uAxis) : void 0,
|
|
31555
|
+
vAxis: descriptor2.vAxis ? cloneVec3$3(descriptor2.vAxis) : void 0,
|
|
31481
31556
|
query: createTrackedFaceQuery(descriptor2.name, owner),
|
|
31482
31557
|
descendant: createFaceDescendantMetadata(descriptor2.semantic, descriptor2.memberNames, descriptor2.coplanar)
|
|
31483
31558
|
});
|
|
@@ -31504,7 +31579,7 @@ function resolveShapeFaceTableInternal(plan, owner) {
|
|
|
31504
31579
|
baseFace.center[2] - baseFace.normal[2] * plan.thickness
|
|
31505
31580
|
],
|
|
31506
31581
|
normal: [-baseFace.normal[0], -baseFace.normal[1], -baseFace.normal[2]],
|
|
31507
|
-
uAxis: baseFace.uAxis ? cloneVec3$
|
|
31582
|
+
uAxis: baseFace.uAxis ? cloneVec3$3(baseFace.uAxis) : void 0,
|
|
31508
31583
|
vAxis: baseFace.vAxis ? [-baseFace.vAxis[0], -baseFace.vAxis[1], -baseFace.vAxis[2]] : void 0,
|
|
31509
31584
|
query: createdQuery
|
|
31510
31585
|
});
|
|
@@ -31570,15 +31645,15 @@ function resolveShapeFaceTableInternal(plan, owner) {
|
|
|
31570
31645
|
if (counterboreFloorQuery && plan.hole.counterbore) {
|
|
31571
31646
|
registerFace(table2, {
|
|
31572
31647
|
name: "counterbore-floor",
|
|
31573
|
-
normal: cloneVec3$
|
|
31648
|
+
normal: cloneVec3$3(workplane.normal),
|
|
31574
31649
|
center: [
|
|
31575
31650
|
origin[0] + inward[0] * plan.hole.counterbore.depth,
|
|
31576
31651
|
origin[1] + inward[1] * plan.hole.counterbore.depth,
|
|
31577
31652
|
origin[2] + inward[2] * plan.hole.counterbore.depth
|
|
31578
31653
|
],
|
|
31579
31654
|
planar: true,
|
|
31580
|
-
uAxis: cloneVec3$
|
|
31581
|
-
vAxis: cloneVec3$
|
|
31655
|
+
uAxis: cloneVec3$3(workplane.u),
|
|
31656
|
+
vAxis: cloneVec3$3(workplane.v),
|
|
31582
31657
|
query: counterboreFloorQuery
|
|
31583
31658
|
});
|
|
31584
31659
|
}
|
|
@@ -31602,11 +31677,11 @@ function resolveShapeFaceTableInternal(plan, owner) {
|
|
|
31602
31677
|
if (floorQuery) {
|
|
31603
31678
|
registerFace(table2, {
|
|
31604
31679
|
name: "floor",
|
|
31605
|
-
normal: cloneVec3$
|
|
31680
|
+
normal: cloneVec3$3(workplane.normal),
|
|
31606
31681
|
center: [origin[0] + inward[0] * forward.depth, origin[1] + inward[1] * forward.depth, origin[2] + inward[2] * forward.depth],
|
|
31607
31682
|
planar: true,
|
|
31608
|
-
uAxis: cloneVec3$
|
|
31609
|
-
vAxis: cloneVec3$
|
|
31683
|
+
uAxis: cloneVec3$3(workplane.u),
|
|
31684
|
+
vAxis: cloneVec3$3(workplane.v),
|
|
31610
31685
|
query: floorQuery
|
|
31611
31686
|
});
|
|
31612
31687
|
}
|
|
@@ -31617,7 +31692,7 @@ function resolveShapeFaceTableInternal(plan, owner) {
|
|
|
31617
31692
|
normal: [-workplane.normal[0], -workplane.normal[1], -workplane.normal[2]],
|
|
31618
31693
|
center: [origin[0] - inward[0] * reverse.depth, origin[1] - inward[1] * reverse.depth, origin[2] - inward[2] * reverse.depth],
|
|
31619
31694
|
planar: true,
|
|
31620
|
-
uAxis: cloneVec3$
|
|
31695
|
+
uAxis: cloneVec3$3(workplane.u),
|
|
31621
31696
|
vAxis: [-workplane.v[0], -workplane.v[1], -workplane.v[2]],
|
|
31622
31697
|
query: capQuery
|
|
31623
31698
|
});
|
|
@@ -31796,11 +31871,11 @@ function resolveShapeFaceTableInternal(plan, owner) {
|
|
|
31796
31871
|
})();
|
|
31797
31872
|
registerFace(table2, {
|
|
31798
31873
|
name: "floor",
|
|
31799
|
-
normal: cloneVec3$
|
|
31874
|
+
normal: cloneVec3$3(placement.workplane.normal),
|
|
31800
31875
|
center: floorCenter,
|
|
31801
31876
|
planar: true,
|
|
31802
|
-
uAxis: cloneVec3$
|
|
31803
|
-
vAxis: cloneVec3$
|
|
31877
|
+
uAxis: cloneVec3$3(placement.workplane.u),
|
|
31878
|
+
vAxis: cloneVec3$3(placement.workplane.v),
|
|
31804
31879
|
query: floorQuery
|
|
31805
31880
|
});
|
|
31806
31881
|
}
|
|
@@ -31815,7 +31890,7 @@ function resolveShapeFaceTableInternal(plan, owner) {
|
|
|
31815
31890
|
origin[2] - depthDir[2] * reverse.depth
|
|
31816
31891
|
],
|
|
31817
31892
|
planar: true,
|
|
31818
|
-
uAxis: cloneVec3$
|
|
31893
|
+
uAxis: cloneVec3$3(placement.workplane.u),
|
|
31819
31894
|
vAxis: [-placement.workplane.v[0], -placement.workplane.v[1], -placement.workplane.v[2]],
|
|
31820
31895
|
query: capQuery
|
|
31821
31896
|
});
|
|
@@ -32001,7 +32076,7 @@ function preservedShapeFaceQueries(basePlan) {
|
|
|
32001
32076
|
return listShapeFaceQueries(basePlan);
|
|
32002
32077
|
}
|
|
32003
32078
|
const PLACEMENT_REFERENCE_KINDS = ["points", "edges", "surfaces", "objects"];
|
|
32004
|
-
function cloneVec3(value, label) {
|
|
32079
|
+
function cloneVec3$2(value, label) {
|
|
32005
32080
|
if (!Array.isArray(value) || value.length < 3) {
|
|
32006
32081
|
throw new Error(`${label} must be a [x, y, z] tuple`);
|
|
32007
32082
|
}
|
|
@@ -32027,23 +32102,23 @@ function isBoundsObject(value) {
|
|
|
32027
32102
|
function toObjectBounds(value, label) {
|
|
32028
32103
|
if (isBoundsObject(value)) {
|
|
32029
32104
|
return {
|
|
32030
|
-
min: cloneVec3(value.min, `${label}.min`),
|
|
32031
|
-
max: cloneVec3(value.max, `${label}.max`)
|
|
32105
|
+
min: cloneVec3$2(value.min, `${label}.min`),
|
|
32106
|
+
max: cloneVec3$2(value.max, `${label}.max`)
|
|
32032
32107
|
};
|
|
32033
32108
|
}
|
|
32034
32109
|
if (typeof value === "object" && value != null) {
|
|
32035
32110
|
if ("boundingBox" in value && typeof value.boundingBox === "function") {
|
|
32036
32111
|
const bounds = value.boundingBox();
|
|
32037
32112
|
return {
|
|
32038
|
-
min: cloneVec3(bounds.min, `${label}.min`),
|
|
32039
|
-
max: cloneVec3(bounds.max, `${label}.max`)
|
|
32113
|
+
min: cloneVec3$2(bounds.min, `${label}.min`),
|
|
32114
|
+
max: cloneVec3$2(bounds.max, `${label}.max`)
|
|
32040
32115
|
};
|
|
32041
32116
|
}
|
|
32042
32117
|
if ("_bbox" in value && typeof value._bbox === "function") {
|
|
32043
32118
|
const bounds = value._bbox();
|
|
32044
32119
|
return {
|
|
32045
|
-
min: cloneVec3(bounds.min, `${label}.min`),
|
|
32046
|
-
max: cloneVec3(bounds.max, `${label}.max`)
|
|
32120
|
+
min: cloneVec3$2(bounds.min, `${label}.min`),
|
|
32121
|
+
max: cloneVec3$2(bounds.max, `${label}.max`)
|
|
32047
32122
|
};
|
|
32048
32123
|
}
|
|
32049
32124
|
}
|
|
@@ -32087,24 +32162,24 @@ function createPlacementReferences() {
|
|
|
32087
32162
|
function clonePlacementReferences(refs) {
|
|
32088
32163
|
const out = createPlacementReferences();
|
|
32089
32164
|
for (const [name, point2] of Object.entries(refs.points)) {
|
|
32090
|
-
out.points[name] = cloneVec3(point2, `points.${name}`);
|
|
32165
|
+
out.points[name] = cloneVec3$2(point2, `points.${name}`);
|
|
32091
32166
|
}
|
|
32092
32167
|
for (const [name, edge] of Object.entries(refs.edges)) {
|
|
32093
32168
|
out.edges[name] = {
|
|
32094
|
-
start: cloneVec3(edge.start, `edges.${name}.start`),
|
|
32095
|
-
end: cloneVec3(edge.end, `edges.${name}.end`)
|
|
32169
|
+
start: cloneVec3$2(edge.start, `edges.${name}.start`),
|
|
32170
|
+
end: cloneVec3$2(edge.end, `edges.${name}.end`)
|
|
32096
32171
|
};
|
|
32097
32172
|
}
|
|
32098
32173
|
for (const [name, surface] of Object.entries(refs.surfaces)) {
|
|
32099
32174
|
out.surfaces[name] = {
|
|
32100
|
-
center: cloneVec3(surface.center, `surfaces.${name}.center`),
|
|
32101
|
-
normal: cloneVec3(surface.normal, `surfaces.${name}.normal`)
|
|
32175
|
+
center: cloneVec3$2(surface.center, `surfaces.${name}.center`),
|
|
32176
|
+
normal: cloneVec3$2(surface.normal, `surfaces.${name}.normal`)
|
|
32102
32177
|
};
|
|
32103
32178
|
}
|
|
32104
32179
|
for (const [name, objectRef] of Object.entries(refs.objects)) {
|
|
32105
32180
|
out.objects[name] = {
|
|
32106
|
-
min: cloneVec3(objectRef.min, `objects.${name}.min`),
|
|
32107
|
-
max: cloneVec3(objectRef.max, `objects.${name}.max`)
|
|
32181
|
+
min: cloneVec3$2(objectRef.min, `objects.${name}.min`),
|
|
32182
|
+
max: cloneVec3$2(objectRef.max, `objects.${name}.max`)
|
|
32108
32183
|
};
|
|
32109
32184
|
}
|
|
32110
32185
|
return out;
|
|
@@ -32112,18 +32187,18 @@ function clonePlacementReferences(refs) {
|
|
|
32112
32187
|
function normalizePlacementReferenceInput(input = {}) {
|
|
32113
32188
|
const out = createPlacementReferences();
|
|
32114
32189
|
for (const [name, point2] of Object.entries(input.points ?? {})) {
|
|
32115
|
-
out.points[name] = cloneVec3(point2, `points.${name}`);
|
|
32190
|
+
out.points[name] = cloneVec3$2(point2, `points.${name}`);
|
|
32116
32191
|
}
|
|
32117
32192
|
for (const [name, edge] of Object.entries(input.edges ?? {})) {
|
|
32118
32193
|
out.edges[name] = {
|
|
32119
|
-
start: cloneVec3(edge.start, `edges.${name}.start`),
|
|
32120
|
-
end: cloneVec3(edge.end, `edges.${name}.end`)
|
|
32194
|
+
start: cloneVec3$2(edge.start, `edges.${name}.start`),
|
|
32195
|
+
end: cloneVec3$2(edge.end, `edges.${name}.end`)
|
|
32121
32196
|
};
|
|
32122
32197
|
}
|
|
32123
32198
|
for (const [name, surface] of Object.entries(input.surfaces ?? {})) {
|
|
32124
32199
|
out.surfaces[name] = {
|
|
32125
|
-
center: cloneVec3(surface.center, `surfaces.${name}.center`),
|
|
32126
|
-
normal: normalizeVector$1(cloneVec3(surface.normal, `surfaces.${name}.normal`))
|
|
32200
|
+
center: cloneVec3$2(surface.center, `surfaces.${name}.center`),
|
|
32201
|
+
normal: normalizeVector$1(cloneVec3$2(surface.normal, `surfaces.${name}.normal`))
|
|
32127
32202
|
};
|
|
32128
32203
|
}
|
|
32129
32204
|
for (const [name, objectRef] of Object.entries(input.objects ?? {})) {
|
|
@@ -32134,23 +32209,23 @@ function normalizePlacementReferenceInput(input = {}) {
|
|
|
32134
32209
|
function mergePlacementReferences(...refsList) {
|
|
32135
32210
|
const out = createPlacementReferences();
|
|
32136
32211
|
for (const refs of refsList) {
|
|
32137
|
-
for (const [name, point2] of Object.entries(refs.points)) out.points[name] = cloneVec3(point2, `points.${name}`);
|
|
32212
|
+
for (const [name, point2] of Object.entries(refs.points)) out.points[name] = cloneVec3$2(point2, `points.${name}`);
|
|
32138
32213
|
for (const [name, edge] of Object.entries(refs.edges)) {
|
|
32139
32214
|
out.edges[name] = {
|
|
32140
|
-
start: cloneVec3(edge.start, `edges.${name}.start`),
|
|
32141
|
-
end: cloneVec3(edge.end, `edges.${name}.end`)
|
|
32215
|
+
start: cloneVec3$2(edge.start, `edges.${name}.start`),
|
|
32216
|
+
end: cloneVec3$2(edge.end, `edges.${name}.end`)
|
|
32142
32217
|
};
|
|
32143
32218
|
}
|
|
32144
32219
|
for (const [name, surface] of Object.entries(refs.surfaces)) {
|
|
32145
32220
|
out.surfaces[name] = {
|
|
32146
|
-
center: cloneVec3(surface.center, `surfaces.${name}.center`),
|
|
32147
|
-
normal: cloneVec3(surface.normal, `surfaces.${name}.normal`)
|
|
32221
|
+
center: cloneVec3$2(surface.center, `surfaces.${name}.center`),
|
|
32222
|
+
normal: cloneVec3$2(surface.normal, `surfaces.${name}.normal`)
|
|
32148
32223
|
};
|
|
32149
32224
|
}
|
|
32150
32225
|
for (const [name, objectRef] of Object.entries(refs.objects)) {
|
|
32151
32226
|
out.objects[name] = {
|
|
32152
|
-
min: cloneVec3(objectRef.min, `objects.${name}.min`),
|
|
32153
|
-
max: cloneVec3(objectRef.max, `objects.${name}.max`)
|
|
32227
|
+
min: cloneVec3$2(objectRef.min, `objects.${name}.min`),
|
|
32228
|
+
max: cloneVec3$2(objectRef.max, `objects.${name}.max`)
|
|
32154
32229
|
};
|
|
32155
32230
|
}
|
|
32156
32231
|
}
|
|
@@ -32194,7 +32269,7 @@ function resolvePointFromKind(refs, kind, name, selector, originalRef) {
|
|
|
32194
32269
|
const point2 = refs.points[name];
|
|
32195
32270
|
if (!point2) return null;
|
|
32196
32271
|
if (selector != null) placementRefSelectorError(originalRef, "does not support selectors");
|
|
32197
|
-
return cloneVec3(point2, `points.${name}`);
|
|
32272
|
+
return cloneVec3$2(point2, `points.${name}`);
|
|
32198
32273
|
}
|
|
32199
32274
|
case "edges": {
|
|
32200
32275
|
const edge = refs.edges[name];
|
|
@@ -32202,8 +32277,8 @@ function resolvePointFromKind(refs, kind, name, selector, originalRef) {
|
|
|
32202
32277
|
if (selector == null || selector === "midpoint" || selector === "center") {
|
|
32203
32278
|
return midpoint$2(edge.start, edge.end);
|
|
32204
32279
|
}
|
|
32205
|
-
if (selector === "start") return cloneVec3(edge.start, `edges.${name}.start`);
|
|
32206
|
-
if (selector === "end") return cloneVec3(edge.end, `edges.${name}.end`);
|
|
32280
|
+
if (selector === "start") return cloneVec3$2(edge.start, `edges.${name}.start`);
|
|
32281
|
+
if (selector === "end") return cloneVec3$2(edge.end, `edges.${name}.end`);
|
|
32207
32282
|
placementRefSelectorError(originalRef, "supports only .start, .end, or .midpoint");
|
|
32208
32283
|
}
|
|
32209
32284
|
case "surfaces": {
|
|
@@ -32212,7 +32287,7 @@ function resolvePointFromKind(refs, kind, name, selector, originalRef) {
|
|
|
32212
32287
|
if (selector != null && selector !== "center") {
|
|
32213
32288
|
placementRefSelectorError(originalRef, "supports only .center");
|
|
32214
32289
|
}
|
|
32215
|
-
return cloneVec3(surface.center, `surfaces.${name}.center`);
|
|
32290
|
+
return cloneVec3$2(surface.center, `surfaces.${name}.center`);
|
|
32216
32291
|
}
|
|
32217
32292
|
case "objects": {
|
|
32218
32293
|
const objectRef = refs.objects[name];
|
|
@@ -35822,7 +35897,7 @@ function edgesBetweenFaces(shape, faceA, faceBs) {
|
|
|
35822
35897
|
});
|
|
35823
35898
|
return coalesceEdges(result);
|
|
35824
35899
|
}
|
|
35825
|
-
const EPSILON$
|
|
35900
|
+
const EPSILON$3 = 1e-8;
|
|
35826
35901
|
function rayTriangle(ox, oy, oz, dx, dy, dz, ax, ay, az, bx, by, bz, cx, cy, cz) {
|
|
35827
35902
|
const e1x = bx - ax, e1y = by - ay, e1z = bz - az;
|
|
35828
35903
|
const e2x = cx - ax, e2y = cy - ay, e2z = cz - az;
|
|
@@ -35830,7 +35905,7 @@ function rayTriangle(ox, oy, oz, dx, dy, dz, ax, ay, az, bx, by, bz, cx, cy, cz)
|
|
|
35830
35905
|
const py2 = dz * e2x - dx * e2z;
|
|
35831
35906
|
const pz2 = dx * e2y - dy * e2x;
|
|
35832
35907
|
const det = e1x * px2 + e1y * py2 + e1z * pz2;
|
|
35833
|
-
if (det > -EPSILON$
|
|
35908
|
+
if (det > -EPSILON$3 && det < EPSILON$3) return null;
|
|
35834
35909
|
const invDet = 1 / det;
|
|
35835
35910
|
const tx = ox - ax, ty = oy - ay, tz = oz - az;
|
|
35836
35911
|
const u2 = (tx * px2 + ty * py2 + tz * pz2) * invDet;
|
|
@@ -35869,7 +35944,7 @@ function rayMeshIntersect(mesh, origin, direction2) {
|
|
|
35869
35944
|
hits.sort((a2, b) => a2.distance - b.distance);
|
|
35870
35945
|
const deduped = [];
|
|
35871
35946
|
for (const hit of hits) {
|
|
35872
|
-
if (deduped.length === 0 || Math.abs(hit.distance - deduped[deduped.length - 1].distance) > EPSILON$
|
|
35947
|
+
if (deduped.length === 0 || Math.abs(hit.distance - deduped[deduped.length - 1].distance) > EPSILON$3 * 100) {
|
|
35873
35948
|
deduped.push(hit);
|
|
35874
35949
|
}
|
|
35875
35950
|
}
|
|
@@ -41412,7 +41487,7 @@ function rodrigues(rv) {
|
|
|
41412
41487
|
function rotateVec3(R, v) {
|
|
41413
41488
|
return [R[0] * v[0] + R[1] * v[1] + R[2] * v[2], R[3] * v[0] + R[4] * v[1] + R[5] * v[2], R[6] * v[0] + R[7] * v[1] + R[8] * v[2]];
|
|
41414
41489
|
}
|
|
41415
|
-
function transformPoint(rv, translation, point2) {
|
|
41490
|
+
function transformPoint$1(rv, translation, point2) {
|
|
41416
41491
|
const R = rodrigues(rv);
|
|
41417
41492
|
const rotated = rotateVec3(R, point2);
|
|
41418
41493
|
return [rotated[0] + translation[0], rotated[1] + translation[1], rotated[2] + translation[2]];
|
|
@@ -41642,7 +41717,7 @@ function createContext(bodies) {
|
|
|
41642
41717
|
toWorld(bodyId, point2) {
|
|
41643
41718
|
const body = bodies.get(bodyId);
|
|
41644
41719
|
if (!body) throw new Error(`Unknown body: ${bodyId}`);
|
|
41645
|
-
return transformPoint(body.rotation, body.position, point2);
|
|
41720
|
+
return transformPoint$1(body.rotation, body.position, point2);
|
|
41646
41721
|
},
|
|
41647
41722
|
toWorldDir(bodyId, dir) {
|
|
41648
41723
|
const body = bodies.get(bodyId);
|
|
@@ -41656,7 +41731,7 @@ function createContext(bodies) {
|
|
|
41656
41731
|
if (!face) throw new Error(`Unknown face "${faceName}" on body "${bodyId}"`);
|
|
41657
41732
|
return {
|
|
41658
41733
|
normal: normalize3(transformDir(body.rotation, face.normal)),
|
|
41659
|
-
center: transformPoint(body.rotation, body.position, face.center)
|
|
41734
|
+
center: transformPoint$1(body.rotation, body.position, face.center)
|
|
41660
41735
|
};
|
|
41661
41736
|
},
|
|
41662
41737
|
worldAxis(bodyId, axisName) {
|
|
@@ -41665,7 +41740,7 @@ function createContext(bodies) {
|
|
|
41665
41740
|
const axis = body.axes.get(axisName);
|
|
41666
41741
|
if (!axis) throw new Error(`Unknown axis "${axisName}" on body "${bodyId}"`);
|
|
41667
41742
|
return {
|
|
41668
|
-
origin: transformPoint(body.rotation, body.position, axis.origin),
|
|
41743
|
+
origin: transformPoint$1(body.rotation, body.position, axis.origin),
|
|
41669
41744
|
direction: normalize3(transformDir(body.rotation, axis.direction))
|
|
41670
41745
|
};
|
|
41671
41746
|
},
|
|
@@ -41674,7 +41749,7 @@ function createContext(bodies) {
|
|
|
41674
41749
|
if (!body) throw new Error(`Unknown body: ${bodyId}`);
|
|
41675
41750
|
const pt = body.points.get(pointName);
|
|
41676
41751
|
if (!pt) throw new Error(`Unknown point "${pointName}" on body "${bodyId}"`);
|
|
41677
|
-
return transformPoint(body.rotation, body.position, pt.position);
|
|
41752
|
+
return transformPoint$1(body.rotation, body.position, pt.position);
|
|
41678
41753
|
}
|
|
41679
41754
|
};
|
|
41680
41755
|
}
|
|
@@ -47218,10 +47293,8 @@ class PathBuilder {
|
|
|
47218
47293
|
if (radius <= 0) throw new Error("fillet: radius must be positive");
|
|
47219
47294
|
const n = this.segs.length;
|
|
47220
47295
|
if (n < 2) throw new Error("fillet: need at least 2 segments before a fillet");
|
|
47221
|
-
const prev = this.segs[n - 2];
|
|
47222
47296
|
const curr = this.segs[n - 1];
|
|
47223
|
-
|
|
47224
|
-
const { trimA, trimB, arcSeg } = this.computeFilletGeom(radius);
|
|
47297
|
+
const { trimA, arcSeg } = this.computeFilletGeom(radius);
|
|
47225
47298
|
if (!arcSeg) throw new Error("fillet: cannot fillet these segments (parallel or degenerate)");
|
|
47226
47299
|
this.trimLastSegEnd(n - 2, trimA[0], trimA[1]);
|
|
47227
47300
|
const trimmedSeg = { ...curr };
|
|
@@ -47293,7 +47366,6 @@ class PathBuilder {
|
|
|
47293
47366
|
}
|
|
47294
47367
|
getSegDirAt(seg, which) {
|
|
47295
47368
|
if (seg.kind === "line" || seg.kind === "move") {
|
|
47296
|
-
this.segs.length;
|
|
47297
47369
|
const idx = this.segs.indexOf(seg);
|
|
47298
47370
|
if (seg.kind === "line") {
|
|
47299
47371
|
let sx, sy;
|
|
@@ -47535,6 +47607,41 @@ class PathBuilder {
|
|
|
47535
47607
|
}
|
|
47536
47608
|
return pts;
|
|
47537
47609
|
}
|
|
47610
|
+
/**
|
|
47611
|
+
* Return the open path as a sampled 2D polyline.
|
|
47612
|
+
*
|
|
47613
|
+
* This is for construction geometry such as guide rails, measured centerlines,
|
|
47614
|
+
* and curve-driven helpers where the authored path should stay open instead of
|
|
47615
|
+
* becoming a filled sketch or stroked profile.
|
|
47616
|
+
*
|
|
47617
|
+
* **Example**
|
|
47618
|
+
*
|
|
47619
|
+
* ```ts
|
|
47620
|
+
* const rail = path()
|
|
47621
|
+
* .moveTo(24, 0)
|
|
47622
|
+
* .bezierTo(32, 44, 28, 92, 18, 120)
|
|
47623
|
+
* .toPolyline();
|
|
47624
|
+
* ```
|
|
47625
|
+
*
|
|
47626
|
+
* @returns A sampled open polyline.
|
|
47627
|
+
* @category Path Builder
|
|
47628
|
+
*/
|
|
47629
|
+
toPolyline() {
|
|
47630
|
+
const moveCount = this.segs.filter((seg) => seg.kind === "move").length;
|
|
47631
|
+
if (moveCount > 1) {
|
|
47632
|
+
throw new Error("path().toPolyline() supports one continuous open path. Use separate path() builders for separate rails.");
|
|
47633
|
+
}
|
|
47634
|
+
const pts = [];
|
|
47635
|
+
for (const point2 of this.tessellate()) {
|
|
47636
|
+
if (!point2.every(Number.isFinite)) throw new Error("path().toPolyline() produced a non-finite point");
|
|
47637
|
+
const previous = pts[pts.length - 1];
|
|
47638
|
+
if (!previous || Math.hypot(point2[0] - previous[0], point2[1] - previous[1]) > 1e-9) {
|
|
47639
|
+
pts.push(point2);
|
|
47640
|
+
}
|
|
47641
|
+
}
|
|
47642
|
+
if (pts.length < 2) throw new Error("path().toPolyline() needs at least 2 points");
|
|
47643
|
+
return pts;
|
|
47644
|
+
}
|
|
47538
47645
|
// ── Output ────────────────────────────────────────────────────────────────
|
|
47539
47646
|
/**
|
|
47540
47647
|
* Close the path and return a filled `Sketch`.
|
|
@@ -49173,7 +49280,7 @@ function beltDrive(options) {
|
|
|
49173
49280
|
};
|
|
49174
49281
|
}
|
|
49175
49282
|
const GEAR_META_KEY = Symbol.for("forgecad.library.gearMeta");
|
|
49176
|
-
const EPSILON$
|
|
49283
|
+
const EPSILON$2 = 1e-9;
|
|
49177
49284
|
function clamp01$1(value) {
|
|
49178
49285
|
return Math.max(-1, Math.min(1, value));
|
|
49179
49286
|
}
|
|
@@ -49292,11 +49399,11 @@ function buildSpurGearMeta(options) {
|
|
|
49292
49399
|
const pitchRadius = options.module * options.teeth * 0.5;
|
|
49293
49400
|
const baseRadius = pitchRadius * Math.cos(options.pressureAngleRad);
|
|
49294
49401
|
const outerRadius = pitchRadius + options.addendum;
|
|
49295
|
-
const rootRadius = Math.max(EPSILON$
|
|
49296
|
-
if (!(rootRadius < outerRadius - EPSILON$
|
|
49402
|
+
const rootRadius = Math.max(EPSILON$2, pitchRadius - options.dedendum);
|
|
49403
|
+
if (!(rootRadius < outerRadius - EPSILON$2)) {
|
|
49297
49404
|
throw new Error("spurGear: invalid radii (root radius must be smaller than outer radius)");
|
|
49298
49405
|
}
|
|
49299
|
-
if (options.boreDiameter > 0 && options.boreDiameter * 0.5 >= rootRadius - EPSILON$
|
|
49406
|
+
if (options.boreDiameter > 0 && options.boreDiameter * 0.5 >= rootRadius - EPSILON$2) {
|
|
49300
49407
|
throw new Error("spurGear: bore is too large for the computed root radius");
|
|
49301
49408
|
}
|
|
49302
49409
|
return {
|
|
@@ -49319,7 +49426,7 @@ function buildSpurGearMeta(options) {
|
|
|
49319
49426
|
function createSpurToothSketch(meta2, segmentsPerTooth) {
|
|
49320
49427
|
const circularPitch = Math.PI * meta2.module;
|
|
49321
49428
|
const thicknessAtPitch = circularPitch * 0.5 - meta2.backlash;
|
|
49322
|
-
if (thicknessAtPitch <= EPSILON$
|
|
49429
|
+
if (thicknessAtPitch <= EPSILON$2) {
|
|
49323
49430
|
throw new Error("spurGear: backlash leaves no tooth thickness at pitch circle");
|
|
49324
49431
|
}
|
|
49325
49432
|
const halfThicknessAtPitch = thicknessAtPitch / (2 * meta2.pitchRadius);
|
|
@@ -49328,7 +49435,7 @@ function createSpurToothSketch(meta2, segmentsPerTooth) {
|
|
|
49328
49435
|
const arcSteps = Math.max(3, Math.ceil(segmentsPerTooth * 0.6));
|
|
49329
49436
|
const radialGap = flankStartRadius - meta2.rootRadius;
|
|
49330
49437
|
const filletRadius = Math.min(0.38 * meta2.module, radialGap * 0.9);
|
|
49331
|
-
const hasFillets = filletRadius > EPSILON$
|
|
49438
|
+
const hasFillets = filletRadius > EPSILON$2;
|
|
49332
49439
|
const leftFlank = [];
|
|
49333
49440
|
const rightFlank = [];
|
|
49334
49441
|
for (let i = 0; i <= flankSteps; i++) {
|
|
@@ -49556,7 +49663,7 @@ function normalizedSweep(scope, fromAngleDeg, toAngleDeg) {
|
|
|
49556
49663
|
if (!Number.isFinite(toAngleDeg)) throw new Error(`${scope}: "toAngleDeg" must be finite`);
|
|
49557
49664
|
let sweep2 = toAngleDeg - fromAngleDeg;
|
|
49558
49665
|
while (sweep2 <= 0) sweep2 += 360;
|
|
49559
|
-
if (sweep2 > 360 + EPSILON$
|
|
49666
|
+
if (sweep2 > 360 + EPSILON$2) throw new Error(`${scope}: angular sweep must be <= 360 degrees`);
|
|
49560
49667
|
return Math.min(360, sweep2);
|
|
49561
49668
|
}
|
|
49562
49669
|
function buildSpurToothRegionProfile(meta2, firstTooth, toothCount, segmentsPerTooth) {
|
|
@@ -49571,7 +49678,7 @@ function buildSolidArcProfile(options, sweepDeg) {
|
|
|
49571
49678
|
const innerRadius = options.innerRadius ?? 0;
|
|
49572
49679
|
const segments = options.segments ?? Math.max(16, Math.ceil(sweepDeg / 6));
|
|
49573
49680
|
if (!Number.isInteger(segments) || segments < 4) throw new Error('driveWheel.addSolidArcBetween: "segments" must be an integer >= 4');
|
|
49574
|
-
if (Math.abs(sweepDeg - 360) < EPSILON$
|
|
49681
|
+
if (Math.abs(sweepDeg - 360) < EPSILON$2) {
|
|
49575
49682
|
const outer = circle2d(options.outerRadius, segments);
|
|
49576
49683
|
return innerRadius > 0 ? difference2d(outer, circle2d(innerRadius, segments)) : outer;
|
|
49577
49684
|
}
|
|
@@ -49655,7 +49762,7 @@ class DriveWheelBuilder {
|
|
|
49655
49762
|
}
|
|
49656
49763
|
const faceWidth = this.resolveBuildFaceWidth();
|
|
49657
49764
|
const firstGearRegion = (_a3 = this.regions.find((region) => region.gearMeta)) == null ? void 0 : _a3.gearMeta;
|
|
49658
|
-
if (firstGearRegion && this.boreDiameter * 0.5 >= firstGearRegion.rootRadius - EPSILON$
|
|
49765
|
+
if (firstGearRegion && this.boreDiameter * 0.5 >= firstGearRegion.rootRadius - EPSILON$2) {
|
|
49659
49766
|
throw new Error("driveWheel: bore is too large for the first spur-tooth region");
|
|
49660
49767
|
}
|
|
49661
49768
|
const body = ((_b3 = this.body) == null ? void 0 : _b3.clone()) ?? gearBodyDisk({ outerRadius: (firstGearRegion == null ? void 0 : firstGearRegion.rootRadius) ?? this.defaultBodyRadius(), faceWidth });
|
|
@@ -49701,7 +49808,7 @@ class DriveWheelBuilder {
|
|
|
49701
49808
|
const faceWidth = localFaceWidth ?? this.faceWidth;
|
|
49702
49809
|
if (faceWidth === void 0) throw new Error(`${scope}: "faceWidth" is required unless driveWheel({ faceWidth }) was set`);
|
|
49703
49810
|
requirePositive$6(scope, "faceWidth", faceWidth);
|
|
49704
|
-
if (this.faceWidth !== void 0 && localFaceWidth !== void 0 && Math.abs(this.faceWidth - localFaceWidth) > EPSILON$
|
|
49811
|
+
if (this.faceWidth !== void 0 && localFaceWidth !== void 0 && Math.abs(this.faceWidth - localFaceWidth) > EPSILON$2) {
|
|
49705
49812
|
throw new Error(`${scope}: region faceWidth must match driveWheel faceWidth`);
|
|
49706
49813
|
}
|
|
49707
49814
|
return faceWidth;
|
|
@@ -49924,7 +50031,7 @@ function normalizeRingGearOptions(options) {
|
|
|
49924
50031
|
const rimWidth = options.rimWidth ?? module * 2;
|
|
49925
50032
|
if (!isFinitePositive(rimWidth)) throw new Error('ringGear: "rimWidth" must be > 0');
|
|
49926
50033
|
const outerRadius = options.outerDiameter != null ? options.outerDiameter * 0.5 : rootRadius + rimWidth;
|
|
49927
|
-
if (!(outerRadius > rootRadius + EPSILON$
|
|
50034
|
+
if (!(outerRadius > rootRadius + EPSILON$2)) {
|
|
49928
50035
|
throw new Error("ringGear: outer diameter/rim width leaves no ring body");
|
|
49929
50036
|
}
|
|
49930
50037
|
return {
|
|
@@ -49976,8 +50083,8 @@ function ringGear(options) {
|
|
|
49976
50083
|
const baseRadius = pitchRadius * Math.cos(normalized.pressureAngleRad);
|
|
49977
50084
|
const tipRadius = pitchRadius - normalized.addendum;
|
|
49978
50085
|
const rootRadius = pitchRadius + normalized.dedendum;
|
|
49979
|
-
if (!(tipRadius > EPSILON$
|
|
49980
|
-
if (!(tipRadius < rootRadius - EPSILON$
|
|
50086
|
+
if (!(tipRadius > EPSILON$2)) throw new Error("ringGear: addendum is too large for the tooth count/module");
|
|
50087
|
+
if (!(tipRadius < rootRadius - EPSILON$2)) throw new Error("ringGear: invalid tip/root radius relationship");
|
|
49981
50088
|
const meta2 = {
|
|
49982
50089
|
kind: "ring",
|
|
49983
50090
|
module: normalized.module,
|
|
@@ -50051,13 +50158,13 @@ function rackGear(options) {
|
|
|
50051
50158
|
const baseHeight = options.baseHeight ?? module * 1.6;
|
|
50052
50159
|
if (!isFinitePositive(baseHeight)) throw new Error('rackGear: "baseHeight" must be > 0');
|
|
50053
50160
|
const thicknessAtPitch = pitch * 0.5 - backlash;
|
|
50054
|
-
if (thicknessAtPitch <= EPSILON$
|
|
50161
|
+
if (thicknessAtPitch <= EPSILON$2) throw new Error("rackGear: backlash leaves no tooth thickness");
|
|
50055
50162
|
const halfPitchThickness = thicknessAtPitch * 0.5;
|
|
50056
50163
|
const dxTip = addendum * Math.tan(pressureAngleRad);
|
|
50057
50164
|
const dxRoot = dedendum * Math.tan(pressureAngleRad);
|
|
50058
50165
|
const halfTip = halfPitchThickness - dxTip;
|
|
50059
50166
|
const halfRoot = halfPitchThickness + dxRoot;
|
|
50060
|
-
if (halfTip <= EPSILON$
|
|
50167
|
+
if (halfTip <= EPSILON$2) {
|
|
50061
50168
|
throw new Error("rackGear: tooth tip collapsed (increase module or lower pressure angle/addendum)");
|
|
50062
50169
|
}
|
|
50063
50170
|
const toothSketch = polygon([
|
|
@@ -50123,7 +50230,7 @@ function computeBevelPitchAngleDeg(teeth, mateTeeth, shaftAngleDeg) {
|
|
|
50123
50230
|
const numerator = teeth * Math.sin(shaftAngleRad);
|
|
50124
50231
|
const denominator = mateTeeth + teeth * Math.cos(shaftAngleRad);
|
|
50125
50232
|
const angle = Math.atan2(numerator, denominator);
|
|
50126
|
-
if (!(angle > EPSILON$
|
|
50233
|
+
if (!(angle > EPSILON$2 && angle < shaftAngleRad - EPSILON$2)) {
|
|
50127
50234
|
throw new Error("bevelGear: could not derive a valid pitch angle from teeth/shaft angle");
|
|
50128
50235
|
}
|
|
50129
50236
|
return angle * 180 / Math.PI;
|
|
@@ -50158,7 +50265,7 @@ function normalizeBevelGearOptions(options) {
|
|
|
50158
50265
|
}
|
|
50159
50266
|
const pitchAngleRad = pitchAngleDeg * Math.PI / 180;
|
|
50160
50267
|
const pitchRadius = spur.module * spur.teeth * 0.5;
|
|
50161
|
-
const coneDistance = pitchRadius / Math.max(EPSILON$
|
|
50268
|
+
const coneDistance = pitchRadius / Math.max(EPSILON$2, Math.sin(pitchAngleRad));
|
|
50162
50269
|
if (!isFinitePositive(coneDistance)) {
|
|
50163
50270
|
throw new Error("bevelGear: invalid cone distance");
|
|
50164
50271
|
}
|
|
@@ -50167,7 +50274,7 @@ function normalizeBevelGearOptions(options) {
|
|
|
50167
50274
|
throw new Error("bevelGear: faceWidth is too large for the selected pitch angle");
|
|
50168
50275
|
}
|
|
50169
50276
|
const topScale = smallPitchRadius / pitchRadius;
|
|
50170
|
-
if (!(topScale > EPSILON$
|
|
50277
|
+
if (!(topScale > EPSILON$2 && topScale <= 1)) {
|
|
50171
50278
|
throw new Error("bevelGear: computed top scale is invalid");
|
|
50172
50279
|
}
|
|
50173
50280
|
return {
|
|
@@ -50283,7 +50390,7 @@ function gearPair(options) {
|
|
|
50283
50390
|
const alpha = pinion.meta.pressureAngleRad;
|
|
50284
50391
|
const nominalCenterDistance = pinion.meta.pitchRadius + gear.meta.pitchRadius;
|
|
50285
50392
|
const requestedBacklash = options.backlash ?? Math.max(pinion.meta.backlash, gear.meta.backlash, 0);
|
|
50286
|
-
const autoCenterDistance = nominalCenterDistance + requestedBacklash / (2 * Math.max(EPSILON$
|
|
50393
|
+
const autoCenterDistance = nominalCenterDistance + requestedBacklash / (2 * Math.max(EPSILON$2, Math.tan(alpha)));
|
|
50287
50394
|
const centerDistance = options.centerDistance ?? autoCenterDistance;
|
|
50288
50395
|
if (!Number.isFinite(centerDistance) || centerDistance <= 0) {
|
|
50289
50396
|
throw new Error("gearPair: centerDistance must be > 0");
|
|
@@ -50312,11 +50419,11 @@ function gearPair(options) {
|
|
|
50312
50419
|
message: `Center distance ${centerDistance.toFixed(4)} exceeds addendum reach ${addendumReach.toFixed(4)} (no mesh contact)`
|
|
50313
50420
|
});
|
|
50314
50421
|
}
|
|
50315
|
-
const cosWorking = clamp01$1(baseSum / Math.max(centerDistance, EPSILON$
|
|
50422
|
+
const cosWorking = clamp01$1(baseSum / Math.max(centerDistance, EPSILON$2));
|
|
50316
50423
|
const alphaWorking = Math.acos(cosWorking);
|
|
50317
50424
|
const basePitch = Math.PI * module * Math.cos(alpha);
|
|
50318
50425
|
const pathLength = Math.sqrt(Math.max(0, pinion.meta.outerRadius ** 2 - pinion.meta.baseRadius ** 2)) + Math.sqrt(Math.max(0, gear.meta.outerRadius ** 2 - gear.meta.baseRadius ** 2)) - centerDistance * Math.sin(alphaWorking);
|
|
50319
|
-
const contactRatio = pathLength / Math.max(EPSILON$
|
|
50426
|
+
const contactRatio = pathLength / Math.max(EPSILON$2, basePitch);
|
|
50320
50427
|
if (contactRatio < 1) {
|
|
50321
50428
|
diagnostics.push({
|
|
50322
50429
|
level: "error",
|
|
@@ -50447,8 +50554,8 @@ function bevelGearPair(options) {
|
|
|
50447
50554
|
message: `Gear pitch angle (${gearMetaPitch.toFixed(2)}deg) differs from tooth-derived ${gearPitchAngleDeg.toFixed(2)}deg`
|
|
50448
50555
|
});
|
|
50449
50556
|
}
|
|
50450
|
-
const coneDistancePinion = pinion.meta.coneDistance ?? pinion.meta.pitchRadius / Math.max(EPSILON$
|
|
50451
|
-
const coneDistanceGear = gear.meta.coneDistance ?? gear.meta.pitchRadius / Math.max(EPSILON$
|
|
50557
|
+
const coneDistancePinion = pinion.meta.coneDistance ?? pinion.meta.pitchRadius / Math.max(EPSILON$2, Math.sin(pinionPitchAngleRad));
|
|
50558
|
+
const coneDistanceGear = gear.meta.coneDistance ?? gear.meta.pitchRadius / Math.max(EPSILON$2, Math.sin(gearPitchAngleRad));
|
|
50452
50559
|
const coneDistance = (coneDistancePinion + coneDistanceGear) * 0.5;
|
|
50453
50560
|
if (Math.abs(coneDistancePinion - coneDistanceGear) > pinion.meta.module * 0.5) {
|
|
50454
50561
|
diagnostics.push({
|
|
@@ -50457,8 +50564,8 @@ function bevelGearPair(options) {
|
|
|
50457
50564
|
message: `Pitch-cone distances differ: pinion=${coneDistancePinion.toFixed(3)}, gear=${coneDistanceGear.toFixed(3)}`
|
|
50458
50565
|
});
|
|
50459
50566
|
}
|
|
50460
|
-
const pinionToApex = pinion.meta.pitchRadius / Math.max(EPSILON$
|
|
50461
|
-
const gearToApex = gear.meta.pitchRadius / Math.max(EPSILON$
|
|
50567
|
+
const pinionToApex = pinion.meta.pitchRadius / Math.max(EPSILON$2, Math.tan(pinionPitchAngleRad)) - pinion.meta.faceWidth * 0.5;
|
|
50568
|
+
const gearToApex = gear.meta.pitchRadius / Math.max(EPSILON$2, Math.tan(gearPitchAngleRad)) - gear.meta.faceWidth * 0.5;
|
|
50462
50569
|
if (pinionToApex <= 0 || gearToApex <= 0) {
|
|
50463
50570
|
diagnostics.push({
|
|
50464
50571
|
level: "warn",
|
|
@@ -51253,11 +51360,11 @@ function inverseLerp(x2, y2, value) {
|
|
|
51253
51360
|
return 0;
|
|
51254
51361
|
}
|
|
51255
51362
|
}
|
|
51256
|
-
function lerp$
|
|
51363
|
+
function lerp$6(x2, y2, t) {
|
|
51257
51364
|
return (1 - t) * x2 + t * y2;
|
|
51258
51365
|
}
|
|
51259
51366
|
function damp(x2, y2, lambda, dt) {
|
|
51260
|
-
return lerp$
|
|
51367
|
+
return lerp$6(x2, y2, 1 - Math.exp(-lambda * dt));
|
|
51261
51368
|
}
|
|
51262
51369
|
function pingpong(x2, length4 = 1) {
|
|
51263
51370
|
return length4 - Math.abs(euclideanModulo(x2, length4 * 2) - length4);
|
|
@@ -51450,7 +51557,7 @@ const MathUtils = {
|
|
|
51450
51557
|
* @param {number} t - The interpolation factor in the closed interval `[0, 1]`.
|
|
51451
51558
|
* @return {number} The interpolated value.
|
|
51452
51559
|
*/
|
|
51453
|
-
lerp: lerp$
|
|
51560
|
+
lerp: lerp$6,
|
|
51454
51561
|
/**
|
|
51455
51562
|
* Smoothly interpolate a number from `x` to `y` in a spring-like manner using a delta
|
|
51456
51563
|
* time to maintain frame rate independent movement. For details, see
|
|
@@ -60404,9 +60511,9 @@ class Color {
|
|
|
60404
60511
|
lerpHSL(color, alpha) {
|
|
60405
60512
|
this.getHSL(_hslA);
|
|
60406
60513
|
color.getHSL(_hslB);
|
|
60407
|
-
const h = lerp$
|
|
60408
|
-
const s = lerp$
|
|
60409
|
-
const l = lerp$
|
|
60514
|
+
const h = lerp$6(_hslA.h, _hslB.h, alpha);
|
|
60515
|
+
const s = lerp$6(_hslA.s, _hslB.s, alpha);
|
|
60516
|
+
const l = lerp$6(_hslA.l, _hslB.l, alpha);
|
|
60410
60517
|
this.setHSL(h, s, l);
|
|
60411
60518
|
return this;
|
|
60412
60519
|
}
|
|
@@ -92404,7 +92511,7 @@ function requireFinite$7(value, label) {
|
|
|
92404
92511
|
}
|
|
92405
92512
|
return value;
|
|
92406
92513
|
}
|
|
92407
|
-
function requireVec3$
|
|
92514
|
+
function requireVec3$3(value, label) {
|
|
92408
92515
|
if (!Array.isArray(value) || value.length !== 3) {
|
|
92409
92516
|
throw new Error(`${label} must be [x, y, z]`);
|
|
92410
92517
|
}
|
|
@@ -92448,7 +92555,7 @@ function normalizeOptions(options) {
|
|
|
92448
92555
|
out.size = requireFinite$7(options.size, "Viewport.label options.size");
|
|
92449
92556
|
if (out.size <= 0) throw new Error("Viewport.label options.size must be positive");
|
|
92450
92557
|
}
|
|
92451
|
-
if (options.offset !== void 0) out.offset = requireVec3$
|
|
92558
|
+
if (options.offset !== void 0) out.offset = requireVec3$3(options.offset, "Viewport.label options.offset");
|
|
92452
92559
|
if (options.anchor !== void 0) {
|
|
92453
92560
|
if (!VALID_ANCHORS.has(options.anchor)) {
|
|
92454
92561
|
throw new Error(`Viewport.label options.anchor must be one of: ${Array.from(VALID_ANCHORS).join(", ")}`);
|
|
@@ -92465,7 +92572,7 @@ function collectRenderLabel(text, at, options) {
|
|
|
92465
92572
|
if (typeof text !== "string" || text.trim().length === 0) {
|
|
92466
92573
|
throw new Error("Viewport.label text must be a non-empty string");
|
|
92467
92574
|
}
|
|
92468
|
-
const normalizedAt = requireVec3$
|
|
92575
|
+
const normalizedAt = requireVec3$3(at, "Viewport.label at");
|
|
92469
92576
|
const normalizedOptions = normalizeOptions(options);
|
|
92470
92577
|
_collected$4.push({
|
|
92471
92578
|
id: `render-label-${_nextId++}`,
|
|
@@ -92884,7 +92991,7 @@ function requireFinite$6(value, label) {
|
|
|
92884
92991
|
}
|
|
92885
92992
|
return value;
|
|
92886
92993
|
}
|
|
92887
|
-
function requireVec3$
|
|
92994
|
+
function requireVec3$2(value, label) {
|
|
92888
92995
|
if (!Array.isArray(value) || value.length !== 3) {
|
|
92889
92996
|
throw new Error(`${label} must be [x, y, z]`);
|
|
92890
92997
|
}
|
|
@@ -92912,9 +93019,9 @@ const VALID_ENVIRONMENT_PRESETS = /* @__PURE__ */ new Set([
|
|
|
92912
93019
|
]);
|
|
92913
93020
|
function validateCamera(cam, label) {
|
|
92914
93021
|
const out = {};
|
|
92915
|
-
if (cam.position !== void 0) out.position = requireVec3$
|
|
92916
|
-
if (cam.target !== void 0) out.target = requireVec3$
|
|
92917
|
-
if (cam.up !== void 0) out.up = requireVec3$
|
|
93022
|
+
if (cam.position !== void 0) out.position = requireVec3$2(cam.position, `${label}.position`);
|
|
93023
|
+
if (cam.target !== void 0) out.target = requireVec3$2(cam.target, `${label}.target`);
|
|
93024
|
+
if (cam.up !== void 0) out.up = requireVec3$2(cam.up, `${label}.up`);
|
|
92918
93025
|
if (cam.fov !== void 0) {
|
|
92919
93026
|
out.fov = requireFinite$6(cam.fov, `${label}.fov`);
|
|
92920
93027
|
if (out.fov <= 0 || out.fov >= 180) throw new Error(`${label}.fov must be between 0 and 180`);
|
|
@@ -93049,8 +93156,8 @@ function validateLight(light, label) {
|
|
|
93049
93156
|
const out = { type: light.type };
|
|
93050
93157
|
if (light.color !== void 0) out.color = requireColor(light.color, `${label}.color`);
|
|
93051
93158
|
if (light.intensity !== void 0) out.intensity = requireFinite$6(light.intensity, `${label}.intensity`);
|
|
93052
|
-
if (light.position !== void 0) out.position = requireVec3$
|
|
93053
|
-
if (light.target !== void 0) out.target = requireVec3$
|
|
93159
|
+
if (light.position !== void 0) out.position = requireVec3$2(light.position, `${label}.position`);
|
|
93160
|
+
if (light.target !== void 0) out.target = requireVec3$2(light.target, `${label}.target`);
|
|
93054
93161
|
if (light.groundColor !== void 0) out.groundColor = requireColor(light.groundColor, `${label}.groundColor`);
|
|
93055
93162
|
if (light.skyColor !== void 0) out.skyColor = requireColor(light.skyColor, `${label}.skyColor`);
|
|
93056
93163
|
if (light.angle !== void 0) out.angle = requireFinite$6(light.angle, `${label}.angle`);
|
|
@@ -94582,7 +94689,7 @@ function scale$1(v, s) {
|
|
|
94582
94689
|
function dot$2(a2, b) {
|
|
94583
94690
|
return a2[0] * b[0] + a2[1] * b[1] + a2[2] * b[2];
|
|
94584
94691
|
}
|
|
94585
|
-
function lerp$
|
|
94692
|
+
function lerp$5(a2, b, t) {
|
|
94586
94693
|
return a2 + (b - a2) * t;
|
|
94587
94694
|
}
|
|
94588
94695
|
function frameMatrix$1(x2, y2, z2, p2) {
|
|
@@ -94593,7 +94700,7 @@ function axisVector(axis, sign2 = 1) {
|
|
|
94593
94700
|
if (axis === "Y") return [0, sign2, 0];
|
|
94594
94701
|
return [0, 0, sign2];
|
|
94595
94702
|
}
|
|
94596
|
-
function axisPosition(axis, point2) {
|
|
94703
|
+
function axisPosition$1(axis, point2) {
|
|
94597
94704
|
return point2[AXIS_INDEX[axis]];
|
|
94598
94705
|
}
|
|
94599
94706
|
function crossPointForStation(axis, point2) {
|
|
@@ -94601,7 +94708,7 @@ function crossPointForStation(axis, point2) {
|
|
|
94601
94708
|
if (axis === "Y") return [point2[0], -point2[2]];
|
|
94602
94709
|
return [point2[1], point2[2]];
|
|
94603
94710
|
}
|
|
94604
|
-
function orientLoftToAxis(shape, axis) {
|
|
94711
|
+
function orientLoftToAxis$1(shape, axis) {
|
|
94605
94712
|
if (axis === "Z") return shape;
|
|
94606
94713
|
if (axis === "Y") return shape.rotateX(-90);
|
|
94607
94714
|
return shape.transform([0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1]);
|
|
@@ -94658,9 +94765,9 @@ function interpolateQuery(a2, b, t) {
|
|
|
94658
94765
|
}
|
|
94659
94766
|
return {
|
|
94660
94767
|
side: sideA,
|
|
94661
|
-
u: lerp$
|
|
94662
|
-
v: lerp$
|
|
94663
|
-
offset: lerp$
|
|
94768
|
+
u: lerp$5(a2.u ?? 0.5, b.u ?? 0.5, t),
|
|
94769
|
+
v: lerp$5(a2.v ?? 0.5, b.v ?? 0.5, t),
|
|
94770
|
+
offset: lerp$5(a2.offset ?? 0, b.offset ?? 0, t)
|
|
94664
94771
|
};
|
|
94665
94772
|
}
|
|
94666
94773
|
function resolvePathQueries(points) {
|
|
@@ -94727,8 +94834,8 @@ class ProductSkin {
|
|
|
94727
94834
|
this.stations = stations;
|
|
94728
94835
|
this.rails = rails;
|
|
94729
94836
|
for (const [name2, query] of Object.entries(refs)) this.refQueries.set(name2, cloneQuery(query));
|
|
94730
|
-
this.axisMin = Math.min(...stations.map((station) => axisPosition(axis, station.center)));
|
|
94731
|
-
this.axisMax = Math.max(...stations.map((station) => axisPosition(axis, station.center)));
|
|
94837
|
+
this.axisMin = Math.min(...stations.map((station) => axisPosition$1(axis, station.center)));
|
|
94838
|
+
this.axisMax = Math.max(...stations.map((station) => axisPosition$1(axis, station.center)));
|
|
94732
94839
|
this.diagnosticsValue = {
|
|
94733
94840
|
...diagnostics,
|
|
94734
94841
|
stationNames: stations.map((station) => station.name),
|
|
@@ -94785,24 +94892,24 @@ class ProductSkin {
|
|
|
94785
94892
|
}
|
|
94786
94893
|
/** Interpolate center, width, and depth at a normalized v or absolute axis value. */
|
|
94787
94894
|
stationAt(vOrAxis) {
|
|
94788
|
-
const axisValue = vOrAxis >= 0 && vOrAxis <= 1 ? lerp$
|
|
94895
|
+
const axisValue = vOrAxis >= 0 && vOrAxis <= 1 ? lerp$5(this.axisMin, this.axisMax, vOrAxis) : clamp$5(vOrAxis, this.axisMin, this.axisMax);
|
|
94789
94896
|
const sorted = this.stations;
|
|
94790
94897
|
for (let index2 = 0; index2 < sorted.length - 1; index2 += 1) {
|
|
94791
94898
|
const a2 = sorted[index2];
|
|
94792
94899
|
const b = sorted[index2 + 1];
|
|
94793
|
-
const aAxis = axisPosition(this.axis, a2.center);
|
|
94794
|
-
const bAxis = axisPosition(this.axis, b.center);
|
|
94900
|
+
const aAxis = axisPosition$1(this.axis, a2.center);
|
|
94901
|
+
const bAxis = axisPosition$1(this.axis, b.center);
|
|
94795
94902
|
if (axisValue < aAxis - EPS$5 || axisValue > bAxis + EPS$5) continue;
|
|
94796
94903
|
const span = Math.max(EPS$5, bAxis - aAxis);
|
|
94797
94904
|
const t = clamp$5((axisValue - aAxis) / span, 0, 1);
|
|
94798
94905
|
return {
|
|
94799
94906
|
axisValue,
|
|
94800
|
-
center: [lerp$
|
|
94801
|
-
width: lerp$
|
|
94802
|
-
depth: lerp$
|
|
94907
|
+
center: [lerp$5(a2.center[0], b.center[0], t), lerp$5(a2.center[1], b.center[1], t), lerp$5(a2.center[2], b.center[2], t)],
|
|
94908
|
+
width: lerp$5(a2.profile.width, b.profile.width, t),
|
|
94909
|
+
depth: lerp$5(a2.profile.depth, b.profile.depth, t),
|
|
94803
94910
|
dWidth: (b.profile.width - a2.profile.width) / span,
|
|
94804
94911
|
dDepth: (b.profile.depth - a2.profile.depth) / span,
|
|
94805
|
-
exponent: lerp$
|
|
94912
|
+
exponent: lerp$5(profileExponent(a2), profileExponent(b), t),
|
|
94806
94913
|
kind: a2.profile.kind === b.profile.kind ? a2.profile.kind : "custom"
|
|
94807
94914
|
};
|
|
94808
94915
|
}
|
|
@@ -94924,7 +95031,7 @@ class ProductSkinBuilder {
|
|
|
94924
95031
|
}
|
|
94925
95032
|
/** Set named cross-section stations for the product skin. */
|
|
94926
95033
|
stations(stations) {
|
|
94927
|
-
this.stationsValue = stations.map(toStationSpec).sort((a2, b) => axisPosition(this.axisValue, a2.center) - axisPosition(this.axisValue, b.center));
|
|
95034
|
+
this.stationsValue = stations.map(toStationSpec).sort((a2, b) => axisPosition$1(this.axisValue, a2.center) - axisPosition$1(this.axisValue, b.center));
|
|
94928
95035
|
return this;
|
|
94929
95036
|
}
|
|
94930
95037
|
/** Attach named guide rails for product-skin construction and downstream surface references. */
|
|
@@ -94974,9 +95081,9 @@ class ProductSkinBuilder {
|
|
|
94974
95081
|
const [x2, y2] = crossPointForStation(this.axisValue, station.center);
|
|
94975
95082
|
return station.profile.sketch.translate(x2, y2);
|
|
94976
95083
|
});
|
|
94977
|
-
const heights = this.stationsValue.map((station) => axisPosition(this.axisValue, station.center));
|
|
95084
|
+
const heights = this.stationsValue.map((station) => axisPosition$1(this.axisValue, station.center));
|
|
94978
95085
|
let shape = loft(localProfiles, heights, { edgeLength: this.edgeLengthValue });
|
|
94979
|
-
shape = orientLoftToAxis(shape, this.axisValue);
|
|
95086
|
+
shape = orientLoftToAxis$1(shape, this.axisValue);
|
|
94980
95087
|
if (this.colorValue) shape = shape.color(this.colorValue);
|
|
94981
95088
|
shape = applyMaterial(shape, this.materialValue).as(this.name);
|
|
94982
95089
|
const warnings = [];
|
|
@@ -95635,7 +95742,7 @@ function requirePositive$3(value, label) {
|
|
|
95635
95742
|
function clamp$4(value, min2, max2) {
|
|
95636
95743
|
return Math.max(min2, Math.min(max2, value));
|
|
95637
95744
|
}
|
|
95638
|
-
function lerp$
|
|
95745
|
+
function lerp$4(a2, b, t) {
|
|
95639
95746
|
return a2 + (b - a2) * t;
|
|
95640
95747
|
}
|
|
95641
95748
|
function add(a2, b) {
|
|
@@ -95685,19 +95792,19 @@ function transformLocal(point2, tangentAcross, normal, tangentAlong, x2, y2, z2
|
|
|
95685
95792
|
function interpolateCylinder(a2, b, t, mode) {
|
|
95686
95793
|
let delta = b.angle - a2.angle;
|
|
95687
95794
|
if (mode === "shortest" && Math.abs(delta) > 180) delta -= Math.sign(delta) * 360;
|
|
95688
|
-
return { kind: "cylinder", angle: a2.angle + delta * t, z: lerp$
|
|
95795
|
+
return { kind: "cylinder", angle: a2.angle + delta * t, z: lerp$4(a2.z, b.z, t), offset: lerp$4(a2.offset ?? 0, b.offset ?? 0, t) };
|
|
95689
95796
|
}
|
|
95690
95797
|
function interpolatePlane(a2, b, t) {
|
|
95691
|
-
return { kind: "plane", x: lerp$
|
|
95798
|
+
return { kind: "plane", x: lerp$4(a2.x, b.x, t), y: lerp$4(a2.y, b.y, t), offset: lerp$4(a2.offset ?? 0, b.offset ?? 0, t) };
|
|
95692
95799
|
}
|
|
95693
95800
|
function interpolateProductSkin(a2, b, t) {
|
|
95694
95801
|
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.");
|
|
95695
95802
|
return {
|
|
95696
95803
|
kind: "productSkin",
|
|
95697
95804
|
side: a2.side ?? b.side,
|
|
95698
|
-
u: lerp$
|
|
95699
|
-
v: lerp$
|
|
95700
|
-
offset: lerp$
|
|
95805
|
+
u: lerp$4(a2.u ?? 0.5, b.u ?? 0.5, t),
|
|
95806
|
+
v: lerp$4(a2.v ?? 0.5, b.v ?? 0.5, t),
|
|
95807
|
+
offset: lerp$4(a2.offset ?? 0, b.offset ?? 0, t)
|
|
95701
95808
|
};
|
|
95702
95809
|
}
|
|
95703
95810
|
class SurfacePath {
|
|
@@ -96020,11 +96127,11 @@ function coordinateOnSide(coordinate, side, label) {
|
|
|
96020
96127
|
return { ...coordinate, kind: "productSkin", side };
|
|
96021
96128
|
}
|
|
96022
96129
|
class ProductSkinCarrier {
|
|
96023
|
-
constructor(skin, name = skin.name,
|
|
96130
|
+
constructor(skin, name = skin.name, sideValue2, offsetValue = 0) {
|
|
96024
96131
|
__publicField(this, "kind", "productSkin");
|
|
96025
96132
|
this.skin = skin;
|
|
96026
96133
|
this.name = name;
|
|
96027
|
-
this.sideValue =
|
|
96134
|
+
this.sideValue = sideValue2;
|
|
96028
96135
|
this.offsetValue = offsetValue;
|
|
96029
96136
|
}
|
|
96030
96137
|
surface(side) {
|
|
@@ -96795,7 +96902,7 @@ function counterboresForPlate(spec2, width, height, thickness, diagnostics) {
|
|
|
96795
96902
|
function minWidthAcrossAlongRange(widthAtT, length4, minAlong, maxAlong) {
|
|
96796
96903
|
let minWidth = Number.POSITIVE_INFINITY;
|
|
96797
96904
|
for (let index2 = 0; index2 <= 8; index2 += 1) {
|
|
96798
|
-
const along = lerp$
|
|
96905
|
+
const along = lerp$4(minAlong, maxAlong, index2 / 8);
|
|
96799
96906
|
const t = Math.max(0, Math.min(1, (along + length4 / 2) / Math.max(length4, 1e-8)));
|
|
96800
96907
|
minWidth = Math.min(minWidth, widthAtT(t));
|
|
96801
96908
|
}
|
|
@@ -97095,7 +97202,7 @@ function pathParameterAtDistance(samples, distance2) {
|
|
|
97095
97202
|
const segmentLength = Math.hypot(b.point[0] - a2.point[0], b.point[1] - a2.point[1], b.point[2] - a2.point[2]);
|
|
97096
97203
|
if (traveled + segmentLength >= distance2) {
|
|
97097
97204
|
const localT = segmentLength <= 1e-8 ? 0 : (distance2 - traveled) / segmentLength;
|
|
97098
|
-
return lerp$
|
|
97205
|
+
return lerp$4(a2.t, b.t, localT);
|
|
97099
97206
|
}
|
|
97100
97207
|
traveled += segmentLength;
|
|
97101
97208
|
}
|
|
@@ -97148,7 +97255,7 @@ function compileBandFootprintMesh(path2, input) {
|
|
|
97148
97255
|
const width = input.widthAt(t);
|
|
97149
97256
|
const along = distance2 - length4 / 2;
|
|
97150
97257
|
for (let acrossIndex = 0; acrossIndex <= acrossSegments; acrossIndex += 1) {
|
|
97151
|
-
const across = lerp$
|
|
97258
|
+
const across = lerp$4(-width / 2, width / 2, acrossIndex / acrossSegments);
|
|
97152
97259
|
mesh.vertices.push(pointAtProfile([across, along], false));
|
|
97153
97260
|
}
|
|
97154
97261
|
}
|
|
@@ -97158,7 +97265,7 @@ function compileBandFootprintMesh(path2, input) {
|
|
|
97158
97265
|
const width = input.widthAt(t);
|
|
97159
97266
|
const along = distance2 - length4 / 2;
|
|
97160
97267
|
for (let acrossIndex = 0; acrossIndex <= acrossSegments; acrossIndex += 1) {
|
|
97161
|
-
const across = lerp$
|
|
97268
|
+
const across = lerp$4(-width / 2, width / 2, acrossIndex / acrossSegments);
|
|
97162
97269
|
mesh.vertices.push(pointAtProfile([across, along], true));
|
|
97163
97270
|
}
|
|
97164
97271
|
}
|
|
@@ -97170,7 +97277,7 @@ function compileBandFootprintMesh(path2, input) {
|
|
|
97170
97277
|
const width = input.widthAt(t);
|
|
97171
97278
|
const along = distance2 - length4 / 2;
|
|
97172
97279
|
for (let acrossIndex = 0; acrossIndex < acrossSegments; acrossIndex += 1) {
|
|
97173
|
-
const across = lerp$
|
|
97280
|
+
const across = lerp$4(-width / 2, width / 2, (acrossIndex + 0.5) / acrossSegments);
|
|
97174
97281
|
filled[alongIndex][acrossIndex] = !holes.some((hole2) => pointInProfileLoop([across, along], hole2));
|
|
97175
97282
|
}
|
|
97176
97283
|
}
|
|
@@ -99348,7 +99455,7 @@ const Constraint = {
|
|
|
99348
99455
|
return builder.constrain({ type: "length", line: resolveLineId(builder, line2), value });
|
|
99349
99456
|
}
|
|
99350
99457
|
};
|
|
99351
|
-
function requireVec3(v, label) {
|
|
99458
|
+
function requireVec3$1(v, label) {
|
|
99352
99459
|
if (!Array.isArray(v) || v.length !== 3 || !Number.isFinite(v[0]) || !Number.isFinite(v[1]) || !Number.isFinite(v[2])) {
|
|
99353
99460
|
throw new Error(`${label} must be a [number, number, number] with finite values, got ${JSON.stringify(v)}`);
|
|
99354
99461
|
}
|
|
@@ -99361,24 +99468,24 @@ function requireFiniteNumber(n, label) {
|
|
|
99361
99468
|
return n;
|
|
99362
99469
|
}
|
|
99363
99470
|
function distance$1(a2, b) {
|
|
99364
|
-
requireVec3(a2, "a");
|
|
99365
|
-
requireVec3(b, "b");
|
|
99471
|
+
requireVec3$1(a2, "a");
|
|
99472
|
+
requireVec3$1(b, "b");
|
|
99366
99473
|
return Math.hypot(b[0] - a2[0], b[1] - a2[1], b[2] - a2[2]);
|
|
99367
99474
|
}
|
|
99368
99475
|
function midpoint$1(a2, b) {
|
|
99369
|
-
requireVec3(a2, "a");
|
|
99370
|
-
requireVec3(b, "b");
|
|
99476
|
+
requireVec3$1(a2, "a");
|
|
99477
|
+
requireVec3$1(b, "b");
|
|
99371
99478
|
return [(a2[0] + b[0]) / 2, (a2[1] + b[1]) / 2, (a2[2] + b[2]) / 2];
|
|
99372
99479
|
}
|
|
99373
|
-
function lerp(a2, b, t) {
|
|
99374
|
-
requireVec3(a2, "a");
|
|
99375
|
-
requireVec3(b, "b");
|
|
99480
|
+
function lerp$3(a2, b, t) {
|
|
99481
|
+
requireVec3$1(a2, "a");
|
|
99482
|
+
requireVec3$1(b, "b");
|
|
99376
99483
|
requireFiniteNumber(t, "t");
|
|
99377
99484
|
return [a2[0] + (b[0] - a2[0]) * t, a2[1] + (b[1] - a2[1]) * t, a2[2] + (b[2] - a2[2]) * t];
|
|
99378
99485
|
}
|
|
99379
99486
|
function direction(a2, b) {
|
|
99380
|
-
requireVec3(a2, "a");
|
|
99381
|
-
requireVec3(b, "b");
|
|
99487
|
+
requireVec3$1(a2, "a");
|
|
99488
|
+
requireVec3$1(b, "b");
|
|
99382
99489
|
const dx = b[0] - a2[0];
|
|
99383
99490
|
const dy = b[1] - a2[1];
|
|
99384
99491
|
const dz = b[2] - a2[2];
|
|
@@ -99389,8 +99496,8 @@ function direction(a2, b) {
|
|
|
99389
99496
|
return [dx / len2, dy / len2, dz / len2];
|
|
99390
99497
|
}
|
|
99391
99498
|
function offset(point2, dir, amount) {
|
|
99392
|
-
requireVec3(point2, "point");
|
|
99393
|
-
requireVec3(dir, "dir");
|
|
99499
|
+
requireVec3$1(point2, "point");
|
|
99500
|
+
requireVec3$1(dir, "dir");
|
|
99394
99501
|
requireFiniteNumber(amount, "amount");
|
|
99395
99502
|
return [point2[0] + dir[0] * amount, point2[1] + dir[1] * amount, point2[2] + dir[2] * amount];
|
|
99396
99503
|
}
|
|
@@ -99400,7 +99507,7 @@ const Points2 = {
|
|
|
99400
99507
|
/** Center point between two 3D points. */
|
|
99401
99508
|
midpoint: midpoint$1,
|
|
99402
99509
|
/** Linearly interpolate between two 3D points. t=0 returns a, t=1 returns b. */
|
|
99403
|
-
lerp,
|
|
99510
|
+
lerp: lerp$3,
|
|
99404
99511
|
/** Unit direction vector from a to b. Throws if a and b are the same point. */
|
|
99405
99512
|
direction,
|
|
99406
99513
|
/** Move a point along a direction vector by a given amount. */
|
|
@@ -104532,9 +104639,84 @@ class ConstraintSketch extends Sketch {
|
|
|
104532
104639
|
* Select the single arrangement region that contains the given seed point.
|
|
104533
104640
|
* Throws if no region contains the seed.
|
|
104534
104641
|
*/
|
|
104535
|
-
detectArrangementRegion(
|
|
104642
|
+
detectArrangementRegion(_seed2) {
|
|
104536
104643
|
throw new Error("Not implemented");
|
|
104537
104644
|
}
|
|
104645
|
+
/**
|
|
104646
|
+
* Return the solved constrained path as a sampled 2D polyline.
|
|
104647
|
+
*
|
|
104648
|
+
* Use this when a construction rail was authored with `constrainedSketch()`
|
|
104649
|
+
* and should feed another operation such as `Loft.pathOnXz(...)`.
|
|
104650
|
+
* The sketch must contain exactly one profile path.
|
|
104651
|
+
*
|
|
104652
|
+
* @param samples - Samples per curved segment. Default 32.
|
|
104653
|
+
* @returns The solved path as an open polyline.
|
|
104654
|
+
*/
|
|
104655
|
+
toPolyline(samples = 32) {
|
|
104656
|
+
if (!Number.isFinite(samples) || samples < 2) throw new Error("ConstraintSketch.toPolyline() samples must be at least 2");
|
|
104657
|
+
const profileLoops = this.definition.loops.filter((loop) => loop.type === "profile");
|
|
104658
|
+
if (profileLoops.length !== 1) {
|
|
104659
|
+
throw new Error("ConstraintSketch.toPolyline() requires exactly one profile path");
|
|
104660
|
+
}
|
|
104661
|
+
const sampleCount = Math.max(2, Math.round(samples));
|
|
104662
|
+
const pointMap = new Map(this.definition.points.map((point2) => [point2.id, point2]));
|
|
104663
|
+
const lineMap = new Map(this.definition.lines.map((line2) => [line2.id, line2]));
|
|
104664
|
+
const arcMap = new Map(this.definition.arcs.map((arc) => [arc.id, arc]));
|
|
104665
|
+
const bezierMap = new Map(this.definition.beziers.map((bezier) => [bezier.id, bezier]));
|
|
104666
|
+
const points = [];
|
|
104667
|
+
const appendStart = (point2, label) => {
|
|
104668
|
+
const previous = points[points.length - 1];
|
|
104669
|
+
if (!previous) {
|
|
104670
|
+
points.push(point2);
|
|
104671
|
+
return;
|
|
104672
|
+
}
|
|
104673
|
+
if (Math.hypot(point2[0] - previous[0], point2[1] - previous[1]) > 1e-6) {
|
|
104674
|
+
throw new Error(`ConstraintSketch.toPolyline() profile path is not continuous at ${label}`);
|
|
104675
|
+
}
|
|
104676
|
+
};
|
|
104677
|
+
const appendPoint = (point2) => {
|
|
104678
|
+
const previous = points[points.length - 1];
|
|
104679
|
+
if (!previous || Math.hypot(point2[0] - previous[0], point2[1] - previous[1]) > 1e-9) points.push(point2);
|
|
104680
|
+
};
|
|
104681
|
+
const requirePoint = (id, label) => {
|
|
104682
|
+
const point2 = pointMap.get(id);
|
|
104683
|
+
if (!point2) throw new Error(`ConstraintSketch.toPolyline() missing ${label}`);
|
|
104684
|
+
return [point2.x, point2.y];
|
|
104685
|
+
};
|
|
104686
|
+
for (const segment of profileLoops[0].segments) {
|
|
104687
|
+
if (segment.kind === "line") {
|
|
104688
|
+
const line2 = lineMap.get(segment.line);
|
|
104689
|
+
if (!line2) throw new Error(`ConstraintSketch.toPolyline() missing line "${segment.line}"`);
|
|
104690
|
+
appendStart(requirePoint(line2.a, `line "${segment.line}" start point`), `line "${segment.line}"`);
|
|
104691
|
+
appendPoint(requirePoint(line2.b, `line "${segment.line}" end point`));
|
|
104692
|
+
} else if (segment.kind === "arc") {
|
|
104693
|
+
const arc = arcMap.get(segment.arc);
|
|
104694
|
+
if (!arc) throw new Error(`ConstraintSketch.toPolyline() missing arc "${segment.arc}"`);
|
|
104695
|
+
const center = requirePoint(arc.center, `arc "${segment.arc}" center point`);
|
|
104696
|
+
const start = requirePoint(arc.start, `arc "${segment.arc}" start point`);
|
|
104697
|
+
const end = requirePoint(arc.end, `arc "${segment.arc}" end point`);
|
|
104698
|
+
appendStart(start, `arc "${segment.arc}"`);
|
|
104699
|
+
const startAngle = Math.atan2(start[1] - center[1], start[0] - center[0]);
|
|
104700
|
+
const endAngle = Math.atan2(end[1] - center[1], end[0] - center[0]);
|
|
104701
|
+
for (const point2 of tessellateArc(center[0], center[1], arc.radius, startAngle, endAngle, arc.clockwise, sampleCount)) {
|
|
104702
|
+
appendPoint(point2);
|
|
104703
|
+
}
|
|
104704
|
+
} else {
|
|
104705
|
+
const bezier = bezierMap.get(segment.bezier);
|
|
104706
|
+
if (!bezier) throw new Error(`ConstraintSketch.toPolyline() missing bezier "${segment.bezier}"`);
|
|
104707
|
+
const p0 = requirePoint(bezier.p0, `bezier "${segment.bezier}" start point`);
|
|
104708
|
+
const p1 = requirePoint(bezier.p1, `bezier "${segment.bezier}" first control point`);
|
|
104709
|
+
const p2 = requirePoint(bezier.p2, `bezier "${segment.bezier}" second control point`);
|
|
104710
|
+
const p3 = requirePoint(bezier.p3, `bezier "${segment.bezier}" end point`);
|
|
104711
|
+
appendStart(p0, `bezier "${segment.bezier}"`);
|
|
104712
|
+
for (const point2 of tessellateBezier(p0[0], p0[1], p1[0], p1[1], p2[0], p2[1], p3[0], p3[1], sampleCount)) {
|
|
104713
|
+
appendPoint(point2);
|
|
104714
|
+
}
|
|
104715
|
+
}
|
|
104716
|
+
}
|
|
104717
|
+
if (points.length < 2) throw new Error("ConstraintSketch.toPolyline() needs at least 2 points");
|
|
104718
|
+
return points;
|
|
104719
|
+
}
|
|
104538
104720
|
/**
|
|
104539
104721
|
* Re-solve the sketch after changing the value of one existing constraint.
|
|
104540
104722
|
*
|
|
@@ -107224,7 +107406,7 @@ function chamferTrackedEdge(shape, edge, size, quadrant = [-1, -1]) {
|
|
|
107224
107406
|
);
|
|
107225
107407
|
return buildEdgeFeatureResult(target, plan, "chamfer");
|
|
107226
107408
|
}
|
|
107227
|
-
const EPSILON = 1e-8;
|
|
107409
|
+
const EPSILON$1 = 1e-8;
|
|
107228
107410
|
function toTuple(point2) {
|
|
107229
107411
|
return Array.isArray(point2) ? [point2[0], point2[1]] : [point2.x, point2.y];
|
|
107230
107412
|
}
|
|
@@ -107235,7 +107417,7 @@ function distance(a2, b) {
|
|
|
107235
107417
|
}
|
|
107236
107418
|
function normalize(vx, vy) {
|
|
107237
107419
|
const len2 = Math.hypot(vx, vy);
|
|
107238
|
-
if (len2 <= EPSILON) throw new Error("filletCorners requires non-degenerate edges");
|
|
107420
|
+
if (len2 <= EPSILON$1) throw new Error("filletCorners requires non-degenerate edges");
|
|
107239
107421
|
return [vx / len2, vy / len2];
|
|
107240
107422
|
}
|
|
107241
107423
|
function clamp$3(value, min2, max2) {
|
|
@@ -107272,19 +107454,19 @@ function buildCornerGeometry(points, spec2, winding) {
|
|
|
107272
107454
|
const [inDirX, inDirY] = normalize(current[0] - prev[0], current[1] - prev[1]);
|
|
107273
107455
|
const [outDirX, outDirY] = normalize(next[0] - current[0], next[1] - current[1]);
|
|
107274
107456
|
const turn = inDirX * outDirY - inDirY * outDirX;
|
|
107275
|
-
const isConvex = turn * winding > EPSILON;
|
|
107276
|
-
const isConcave = turn * winding < -EPSILON;
|
|
107457
|
+
const isConvex = turn * winding > EPSILON$1;
|
|
107458
|
+
const isConcave = turn * winding < -EPSILON$1;
|
|
107277
107459
|
if (!isConvex && !isConcave) {
|
|
107278
107460
|
throw new Error(`filletCorners corner ${spec2.index} is collinear; cannot fillet a straight edge`);
|
|
107279
107461
|
}
|
|
107280
107462
|
const toPrev = [-inDirX, -inDirY];
|
|
107281
107463
|
const toNext = [outDirX, outDirY];
|
|
107282
107464
|
const interiorAngle = Math.acos(clamp$3(toPrev[0] * toNext[0] + toPrev[1] * toNext[1], -1, 1));
|
|
107283
|
-
if (interiorAngle <= EPSILON || interiorAngle >= Math.PI - EPSILON) {
|
|
107465
|
+
if (interiorAngle <= EPSILON$1 || interiorAngle >= Math.PI - EPSILON$1) {
|
|
107284
107466
|
throw new Error(`filletCorners corner ${spec2.index} has an unsupported angle`);
|
|
107285
107467
|
}
|
|
107286
107468
|
const tangentDistance = spec2.radius / Math.tan(interiorAngle / 2);
|
|
107287
|
-
if (tangentDistance >= inLength - EPSILON || tangentDistance >= outLength - EPSILON) {
|
|
107469
|
+
if (tangentDistance >= inLength - EPSILON$1 || tangentDistance >= outLength - EPSILON$1) {
|
|
107288
107470
|
const maxRadius = Math.min(inLength, outLength) * Math.tan(interiorAngle / 2);
|
|
107289
107471
|
throw new Error(`filletCorners radius ${spec2.radius} is too large for corner ${spec2.index}; max is ${maxRadius.toFixed(3)}`);
|
|
107290
107472
|
}
|
|
@@ -107314,7 +107496,7 @@ function filletCorners(points, corners) {
|
|
|
107314
107496
|
if (corners.length === 0) return polygon(points);
|
|
107315
107497
|
const tuples = points.map(toTuple);
|
|
107316
107498
|
const area2 = signedArea$3(tuples);
|
|
107317
|
-
if (Math.abs(area2) <= EPSILON) throw new Error("filletCorners requires a non-degenerate polygon");
|
|
107499
|
+
if (Math.abs(area2) <= EPSILON$1) throw new Error("filletCorners requires a non-degenerate polygon");
|
|
107318
107500
|
const winding = Math.sign(area2);
|
|
107319
107501
|
const geometryByIndex = /* @__PURE__ */ new Map();
|
|
107320
107502
|
for (const spec2 of corners) {
|
|
@@ -107328,7 +107510,7 @@ function filletCorners(points, corners) {
|
|
|
107328
107510
|
const edgeLength2 = distance(tuples[i], tuples[nextIndex]);
|
|
107329
107511
|
const exitDistance = ((_a3 = geometryByIndex.get(i)) == null ? void 0 : _a3.tangentDistance) ?? 0;
|
|
107330
107512
|
const entryDistance = ((_b3 = geometryByIndex.get(nextIndex)) == null ? void 0 : _b3.tangentDistance) ?? 0;
|
|
107331
|
-
if (exitDistance + entryDistance >= edgeLength2 - EPSILON) {
|
|
107513
|
+
if (exitDistance + entryDistance >= edgeLength2 - EPSILON$1) {
|
|
107332
107514
|
throw new Error(`filletCorners adjacent fillets overlap on edge ${i} -> ${nextIndex}; reduce one of the radii`);
|
|
107333
107515
|
}
|
|
107334
107516
|
}
|
|
@@ -119832,6 +120014,295 @@ function polygonVertices(sides, radius, options) {
|
|
|
119832
120014
|
centerY: options == null ? void 0 : options.centerY
|
|
119833
120015
|
});
|
|
119834
120016
|
}
|
|
120017
|
+
const LOFT_GUIDE_EPS = 1e-8;
|
|
120018
|
+
function orientLoftToAxis(shape, axis) {
|
|
120019
|
+
if (axis === "Z") return shape;
|
|
120020
|
+
if (axis === "Y") return shape.rotateX(-90);
|
|
120021
|
+
return shape.transform([0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1]);
|
|
120022
|
+
}
|
|
120023
|
+
function buildRailEvaluators(rails, axis, start, end, railSamples) {
|
|
120024
|
+
const seen2 = /* @__PURE__ */ new Set();
|
|
120025
|
+
return rails.map((rail2) => {
|
|
120026
|
+
if (seen2.has(rail2.side)) throw new Error(`Loft.withGuideRails() received more than one ${rail2.side} rail`);
|
|
120027
|
+
seen2.add(rail2.side);
|
|
120028
|
+
const sampled = sampleRailPath(rail2.path, railSamples);
|
|
120029
|
+
if (sampled.length < 2) throw new Error("Loft guide rails require at least two points");
|
|
120030
|
+
const points = sampled.map((point2) => ({ position: axisPosition(axis, point2), cross: crossPointForAxis(axis, point2) }));
|
|
120031
|
+
const ordered = points[points.length - 1].position >= points[0].position ? points : [...points].reverse();
|
|
120032
|
+
validateRailCoverage(ordered, start, end);
|
|
120033
|
+
return { side: rail2.side, points: ordered };
|
|
120034
|
+
});
|
|
120035
|
+
}
|
|
120036
|
+
function railCrossAt(rail2, position) {
|
|
120037
|
+
const points = rail2.points;
|
|
120038
|
+
if (position <= points[0].position + LOFT_GUIDE_EPS) return points[0].cross;
|
|
120039
|
+
const last = points[points.length - 1];
|
|
120040
|
+
if (position >= last.position - LOFT_GUIDE_EPS) return last.cross;
|
|
120041
|
+
for (let index2 = 0; index2 < points.length - 1; index2 += 1) {
|
|
120042
|
+
const a2 = points[index2];
|
|
120043
|
+
const b = points[index2 + 1];
|
|
120044
|
+
if (position >= a2.position - LOFT_GUIDE_EPS && position <= b.position + LOFT_GUIDE_EPS) {
|
|
120045
|
+
const t = (position - a2.position) / (b.position - a2.position);
|
|
120046
|
+
return [lerp$2(a2.cross[0], b.cross[0], t), lerp$2(a2.cross[1], b.cross[1], t)];
|
|
120047
|
+
}
|
|
120048
|
+
}
|
|
120049
|
+
throw new Error("Loft guide rail does not cover requested station position");
|
|
120050
|
+
}
|
|
120051
|
+
function validateRailCoverage(points, start, end) {
|
|
120052
|
+
for (let index2 = 1; index2 < points.length; index2 += 1) {
|
|
120053
|
+
if (points[index2].position - points[index2 - 1].position < LOFT_GUIDE_EPS) {
|
|
120054
|
+
throw new Error("Loft guide rails must be monotone along the loft axis");
|
|
120055
|
+
}
|
|
120056
|
+
}
|
|
120057
|
+
if (points[0].position - start > LOFT_GUIDE_EPS || end - points[points.length - 1].position > LOFT_GUIDE_EPS) {
|
|
120058
|
+
throw new Error("Loft guide rails must cover the full station range");
|
|
120059
|
+
}
|
|
120060
|
+
}
|
|
120061
|
+
function sampleRailPath(path2, samples) {
|
|
120062
|
+
if (Array.isArray(path2)) return path2.map((point2, index2) => requireVec3(point2, `Loft guide rail point ${index2}`));
|
|
120063
|
+
if (path2 instanceof Curve3D || path2 instanceof HermiteCurve3D || path2 instanceof QuinticHermiteCurve3D || path2 instanceof NurbsCurve3D) {
|
|
120064
|
+
return path2.sample(Math.max(2, Math.round(samples))).map((point2, index2) => requireVec3(point2, `Loft guide rail sample ${index2}`));
|
|
120065
|
+
}
|
|
120066
|
+
throw new Error("Loft guide rail path must be a Vec3[] or ForgeCAD 3D curve");
|
|
120067
|
+
}
|
|
120068
|
+
function requireVec3(point2, label) {
|
|
120069
|
+
if (!Array.isArray(point2) || point2.length !== 3 || !point2.every(Number.isFinite)) {
|
|
120070
|
+
throw new Error(`${label} must be a finite [x, y, z] point`);
|
|
120071
|
+
}
|
|
120072
|
+
return [point2[0], point2[1], point2[2]];
|
|
120073
|
+
}
|
|
120074
|
+
function axisPosition(axis, point2) {
|
|
120075
|
+
if (axis === "X") return point2[0];
|
|
120076
|
+
if (axis === "Y") return point2[1];
|
|
120077
|
+
return point2[2];
|
|
120078
|
+
}
|
|
120079
|
+
function crossPointForAxis(axis, point2) {
|
|
120080
|
+
if (axis === "X") return [point2[1], point2[2]];
|
|
120081
|
+
if (axis === "Y") return [point2[0], -point2[2]];
|
|
120082
|
+
return [point2[0], point2[1]];
|
|
120083
|
+
}
|
|
120084
|
+
function lerp$2(a2, b, t) {
|
|
120085
|
+
return a2 + (b - a2) * t;
|
|
120086
|
+
}
|
|
120087
|
+
function loftWithGuideRails(stations, rails, options = {}) {
|
|
120088
|
+
if (stations.length < 2) throw new Error("Loft.withGuideRails() requires at least two stations");
|
|
120089
|
+
if (rails.length === 0) throw new Error("Loft.withGuideRails() requires at least one guide rail");
|
|
120090
|
+
const sortedStations = sortedValidStations(stations);
|
|
120091
|
+
const axis = options.axis ?? "Z";
|
|
120092
|
+
const start = sortedStations[0].position;
|
|
120093
|
+
const end = sortedStations[sortedStations.length - 1].position;
|
|
120094
|
+
const railEvaluators = buildRailEvaluators(rails, axis, start, end, options.railSamples ?? 64);
|
|
120095
|
+
const positions = generatedPositions(sortedStations, options.samples);
|
|
120096
|
+
const profiles2 = positions.map((position) => {
|
|
120097
|
+
const source = profileForPosition(sortedStations, position);
|
|
120098
|
+
const bounds = boundsForPosition(sortedStations, position);
|
|
120099
|
+
return fitProfileToBounds(source, applyRailsToBounds(bounds, railEvaluators, position));
|
|
120100
|
+
});
|
|
120101
|
+
const shape = loft(profiles2, positions, {
|
|
120102
|
+
edgeLength: options.edgeLength,
|
|
120103
|
+
boundsPadding: options.boundsPadding
|
|
120104
|
+
});
|
|
120105
|
+
return orientLoftToAxis(shape, axis);
|
|
120106
|
+
}
|
|
120107
|
+
function sortedValidStations(stations) {
|
|
120108
|
+
const sorted = [...stations].sort((a2, b) => a2.position - b.position);
|
|
120109
|
+
for (let index2 = 0; index2 < sorted.length; index2 += 1) {
|
|
120110
|
+
if (!Number.isFinite(sorted[index2].position)) throw new Error("Loft.withGuideRails station position must be finite");
|
|
120111
|
+
if (!(sorted[index2].profile instanceof Sketch)) throw new Error("Loft.withGuideRails() stations must use Sketch profiles");
|
|
120112
|
+
if (index2 > 0 && sorted[index2].position - sorted[index2 - 1].position < LOFT_GUIDE_EPS) {
|
|
120113
|
+
throw new Error("Loft.withGuideRails() requires unique, strictly increasing station positions");
|
|
120114
|
+
}
|
|
120115
|
+
}
|
|
120116
|
+
return sorted;
|
|
120117
|
+
}
|
|
120118
|
+
function generatedPositions(stations, samples) {
|
|
120119
|
+
const count = Math.max(2, Math.round(samples ?? Math.max(9, (stations.length - 1) * 8 + 1)));
|
|
120120
|
+
const start = stations[0].position;
|
|
120121
|
+
const end = stations[stations.length - 1].position;
|
|
120122
|
+
const values = /* @__PURE__ */ new Set();
|
|
120123
|
+
const positions = [];
|
|
120124
|
+
const addPosition = (position) => {
|
|
120125
|
+
const key = position.toFixed(9);
|
|
120126
|
+
if (!values.has(key)) {
|
|
120127
|
+
values.add(key);
|
|
120128
|
+
positions.push(position);
|
|
120129
|
+
}
|
|
120130
|
+
};
|
|
120131
|
+
for (let index2 = 0; index2 < count; index2 += 1) addPosition(start + (end - start) * index2 / (count - 1));
|
|
120132
|
+
for (const station of stations) addPosition(station.position);
|
|
120133
|
+
return positions.sort((a2, b) => a2 - b);
|
|
120134
|
+
}
|
|
120135
|
+
function profileForPosition(stations, position) {
|
|
120136
|
+
for (let index2 = 0; index2 < stations.length - 1; index2 += 1) {
|
|
120137
|
+
if (position <= stations[index2 + 1].position + LOFT_GUIDE_EPS) return stations[index2].profile;
|
|
120138
|
+
}
|
|
120139
|
+
return stations[stations.length - 1].profile;
|
|
120140
|
+
}
|
|
120141
|
+
function boundsForPosition(stations, position) {
|
|
120142
|
+
if (position <= stations[0].position + LOFT_GUIDE_EPS) return sketchBounds(stations[0].profile);
|
|
120143
|
+
const last = stations[stations.length - 1];
|
|
120144
|
+
if (position >= last.position - LOFT_GUIDE_EPS) return sketchBounds(last.profile);
|
|
120145
|
+
for (let index2 = 0; index2 < stations.length - 1; index2 += 1) {
|
|
120146
|
+
const a2 = stations[index2];
|
|
120147
|
+
const b = stations[index2 + 1];
|
|
120148
|
+
if (position >= a2.position - LOFT_GUIDE_EPS && position <= b.position + LOFT_GUIDE_EPS) {
|
|
120149
|
+
return lerpBounds(sketchBounds(a2.profile), sketchBounds(b.profile), (position - a2.position) / (b.position - a2.position));
|
|
120150
|
+
}
|
|
120151
|
+
}
|
|
120152
|
+
return sketchBounds(last.profile);
|
|
120153
|
+
}
|
|
120154
|
+
function applyRailsToBounds(bounds, rails, position) {
|
|
120155
|
+
const centerRail = rails.find((rail2) => rail2.side === "center");
|
|
120156
|
+
const center = centerRail ? railCrossAt(centerRail, position) : void 0;
|
|
120157
|
+
const next = { ...bounds };
|
|
120158
|
+
applyAxisRail(next, "X", sideValue(rails, "left", position, 0), sideValue(rails, "right", position, 0), center == null ? void 0 : center[0]);
|
|
120159
|
+
applyAxisRail(next, "Y", sideValue(rails, "back", position, 1), sideValue(rails, "front", position, 1), center == null ? void 0 : center[1]);
|
|
120160
|
+
if (next.maxX - next.minX < LOFT_GUIDE_EPS || next.maxY - next.minY < LOFT_GUIDE_EPS) {
|
|
120161
|
+
throw new Error("Loft.withGuideRails() guide rails produced a non-positive section size");
|
|
120162
|
+
}
|
|
120163
|
+
return next;
|
|
120164
|
+
}
|
|
120165
|
+
function sideValue(rails, side, position, crossIndex) {
|
|
120166
|
+
const rail2 = rails.find((entry) => entry.side === side);
|
|
120167
|
+
return rail2 ? railCrossAt(rail2, position)[crossIndex] : void 0;
|
|
120168
|
+
}
|
|
120169
|
+
function applyAxisRail(bounds, axis, minRail, maxRail, center) {
|
|
120170
|
+
const minKey = axis === "X" ? "minX" : "minY";
|
|
120171
|
+
const maxKey = axis === "X" ? "maxX" : "maxY";
|
|
120172
|
+
const width = bounds[maxKey] - bounds[minKey];
|
|
120173
|
+
if (minRail != null && maxRail != null) {
|
|
120174
|
+
if (maxRail - minRail < LOFT_GUIDE_EPS) throw new Error("Loft.withGuideRails() opposite guide rails crossed");
|
|
120175
|
+
if (center != null && Math.abs((minRail + maxRail) / 2 - center) > 1e-5) {
|
|
120176
|
+
throw new Error("Loft.withGuideRails() center rail conflicts with opposite side rails");
|
|
120177
|
+
}
|
|
120178
|
+
bounds[minKey] = minRail;
|
|
120179
|
+
bounds[maxKey] = maxRail;
|
|
120180
|
+
} else if (maxRail != null) {
|
|
120181
|
+
bounds[maxKey] = maxRail;
|
|
120182
|
+
bounds[minKey] = center != null ? 2 * center - maxRail : maxRail - width;
|
|
120183
|
+
} else if (minRail != null) {
|
|
120184
|
+
bounds[minKey] = minRail;
|
|
120185
|
+
bounds[maxKey] = center != null ? 2 * center - minRail : minRail + width;
|
|
120186
|
+
} else if (center != null) {
|
|
120187
|
+
bounds[minKey] = center - width / 2;
|
|
120188
|
+
bounds[maxKey] = center + width / 2;
|
|
120189
|
+
}
|
|
120190
|
+
}
|
|
120191
|
+
function fitProfileToBounds(profile, target) {
|
|
120192
|
+
const source = sketchBounds(profile);
|
|
120193
|
+
const sourceWidth = source.maxX - source.minX;
|
|
120194
|
+
const sourceDepth = source.maxY - source.minY;
|
|
120195
|
+
if (sourceWidth < LOFT_GUIDE_EPS || sourceDepth < LOFT_GUIDE_EPS) {
|
|
120196
|
+
throw new Error("Loft.withGuideRails() station profiles must have positive bounds");
|
|
120197
|
+
}
|
|
120198
|
+
const sourceCenter = [(source.minX + source.maxX) / 2, (source.minY + source.maxY) / 2];
|
|
120199
|
+
const targetCenter = [(target.minX + target.maxX) / 2, (target.minY + target.maxY) / 2];
|
|
120200
|
+
return profile.scaleAround(sourceCenter, [(target.maxX - target.minX) / sourceWidth, (target.maxY - target.minY) / sourceDepth]).translate(targetCenter[0] - sourceCenter[0], targetCenter[1] - sourceCenter[1]);
|
|
120201
|
+
}
|
|
120202
|
+
function sketchBounds(profile) {
|
|
120203
|
+
const bounds = profile.bounds();
|
|
120204
|
+
return { minX: bounds.min[0], maxX: bounds.max[0], minY: bounds.min[1], maxY: bounds.max[1] };
|
|
120205
|
+
}
|
|
120206
|
+
function lerpBounds(a2, b, t) {
|
|
120207
|
+
return {
|
|
120208
|
+
minX: lerp$1(a2.minX, b.minX, t),
|
|
120209
|
+
maxX: lerp$1(a2.maxX, b.maxX, t),
|
|
120210
|
+
minY: lerp$1(a2.minY, b.minY, t),
|
|
120211
|
+
maxY: lerp$1(a2.maxY, b.maxY, t)
|
|
120212
|
+
};
|
|
120213
|
+
}
|
|
120214
|
+
function lerp$1(a2, b, t) {
|
|
120215
|
+
return a2 + (b - a2) * t;
|
|
120216
|
+
}
|
|
120217
|
+
function mapLoftPath2D(path2, label, mapper) {
|
|
120218
|
+
const points = sampleLoftPath2D(path2, label);
|
|
120219
|
+
return points.map((point2, index2) => {
|
|
120220
|
+
if (!Array.isArray(point2) || point2.length !== 2 || !point2.every(Number.isFinite)) {
|
|
120221
|
+
throw new Error(`${label} point ${index2} must be a finite [x, y] point`);
|
|
120222
|
+
}
|
|
120223
|
+
return mapper([point2[0], point2[1]]);
|
|
120224
|
+
});
|
|
120225
|
+
}
|
|
120226
|
+
function sampleLoftPath2D(path2, label) {
|
|
120227
|
+
if (Array.isArray(path2)) {
|
|
120228
|
+
if (path2.length < 2) throw new Error(`${label} requires at least two [x, y] points`);
|
|
120229
|
+
return path2;
|
|
120230
|
+
}
|
|
120231
|
+
if (!path2 || typeof path2 !== "object" || typeof path2.toPolyline !== "function") {
|
|
120232
|
+
throw new Error(`${label} requires a 2D path, solved constrained path, or [x, y] point array`);
|
|
120233
|
+
}
|
|
120234
|
+
const points = path2.toPolyline();
|
|
120235
|
+
if (!Array.isArray(points) || points.length < 2) throw new Error(`${label} path must produce at least two [x, y] points`);
|
|
120236
|
+
return points;
|
|
120237
|
+
}
|
|
120238
|
+
const Loft = {
|
|
120239
|
+
/** Create a loft station from a 2D profile and an axis position. */
|
|
120240
|
+
station(profile, position) {
|
|
120241
|
+
if (!Number.isFinite(position)) throw new Error("Loft.station position must be finite");
|
|
120242
|
+
return { profile, position };
|
|
120243
|
+
},
|
|
120244
|
+
/** Create a guide rail that constrains the section-local negative-X side. */
|
|
120245
|
+
leftRail(path2) {
|
|
120246
|
+
return { side: "left", path: path2 };
|
|
120247
|
+
},
|
|
120248
|
+
/** Create a guide rail that constrains the section-local positive-X side. */
|
|
120249
|
+
rightRail(path2) {
|
|
120250
|
+
return { side: "right", path: path2 };
|
|
120251
|
+
},
|
|
120252
|
+
/** Create a guide rail that constrains the section-local positive-Y side. */
|
|
120253
|
+
frontRail(path2) {
|
|
120254
|
+
return { side: "front", path: path2 };
|
|
120255
|
+
},
|
|
120256
|
+
/** Create a guide rail that constrains the section-local negative-Y side. */
|
|
120257
|
+
backRail(path2) {
|
|
120258
|
+
return { side: "back", path: path2 };
|
|
120259
|
+
},
|
|
120260
|
+
/** Create a guide rail that moves section centers along the loft. */
|
|
120261
|
+
centerRail(path2) {
|
|
120262
|
+
return { side: "center", path: path2 };
|
|
120263
|
+
},
|
|
120264
|
+
/**
|
|
120265
|
+
* Place a 2D guide path onto the XZ plane.
|
|
120266
|
+
*
|
|
120267
|
+
* The path's first coordinate becomes X and its second coordinate becomes Z.
|
|
120268
|
+
* Use this for left/right silhouette rails authored with `path()` or `constrainedSketch()`.
|
|
120269
|
+
*/
|
|
120270
|
+
pathOnXz(path2, y2 = 0) {
|
|
120271
|
+
if (!Number.isFinite(y2)) throw new Error("Loft.pathOnXz y must be finite");
|
|
120272
|
+
return mapLoftPath2D(path2, "Loft.pathOnXz", ([x2, z2]) => [x2, y2, z2]);
|
|
120273
|
+
},
|
|
120274
|
+
/**
|
|
120275
|
+
* Place a 2D guide path onto the YZ plane.
|
|
120276
|
+
*
|
|
120277
|
+
* The path's first coordinate becomes Y and its second coordinate becomes Z.
|
|
120278
|
+
* Use this for front/back crown rails authored with `path()` or `constrainedSketch()`.
|
|
120279
|
+
*/
|
|
120280
|
+
pathOnYz(path2, x2 = 0) {
|
|
120281
|
+
if (!Number.isFinite(x2)) throw new Error("Loft.pathOnYz x must be finite");
|
|
120282
|
+
return mapLoftPath2D(path2, "Loft.pathOnYz", ([y2, z2]) => [x2, y2, z2]);
|
|
120283
|
+
},
|
|
120284
|
+
/**
|
|
120285
|
+
* Place a 2D guide path onto the XY plane.
|
|
120286
|
+
*
|
|
120287
|
+
* The path's first coordinate becomes X and its second coordinate becomes Y.
|
|
120288
|
+
* Use this when lofting along X or Y and a rail lives in a horizontal sketch plane.
|
|
120289
|
+
*/
|
|
120290
|
+
pathOnXy(path2, z2 = 0) {
|
|
120291
|
+
if (!Number.isFinite(z2)) throw new Error("Loft.pathOnXy z must be finite");
|
|
120292
|
+
return mapLoftPath2D(path2, "Loft.pathOnXy", ([x2, y2]) => [x2, y2, z2]);
|
|
120293
|
+
},
|
|
120294
|
+
/**
|
|
120295
|
+
* Loft through profile stations while forcing generated sections to follow guide rails.
|
|
120296
|
+
*
|
|
120297
|
+
* Stations define the cross-section family. Guide rails define the side or center
|
|
120298
|
+
* paths the loft must pass through. With opposite side rails, the section is scaled
|
|
120299
|
+
* to touch both rails. With one side rail, the section keeps its interpolated size
|
|
120300
|
+
* unless a center rail is also present.
|
|
120301
|
+
*/
|
|
120302
|
+
withGuideRails(stations, rails, options = {}) {
|
|
120303
|
+
return loftWithGuideRails(stations, rails, options);
|
|
120304
|
+
}
|
|
120305
|
+
};
|
|
119835
120306
|
let collectedHighlights = [];
|
|
119836
120307
|
function resetHighlights() {
|
|
119837
120308
|
collectedHighlights = [];
|
|
@@ -120504,7 +120975,7 @@ function resolveCornerYSelection(edges, options = {}) {
|
|
|
120504
120975
|
for (const candidate of candidates) {
|
|
120505
120976
|
const selectedEndpoints = [];
|
|
120506
120977
|
let totalGap = 0;
|
|
120507
|
-
let
|
|
120978
|
+
let maxGap2 = 0;
|
|
120508
120979
|
for (const edge of edges) {
|
|
120509
120980
|
const start = edgeEndpoint(edge, "start");
|
|
120510
120981
|
const end = edgeEndpoint(edge, "end");
|
|
@@ -120514,10 +120985,10 @@ function resolveCornerYSelection(edges, options = {}) {
|
|
|
120514
120985
|
const gap = Math.min(startGap, endGap);
|
|
120515
120986
|
selectedEndpoints.push(selected);
|
|
120516
120987
|
totalGap += gap;
|
|
120517
|
-
|
|
120988
|
+
maxGap2 = Math.max(maxGap2, gap);
|
|
120518
120989
|
}
|
|
120519
|
-
if (
|
|
120520
|
-
best = { totalGap, maxGap, selectedEndpoints };
|
|
120990
|
+
if (maxGap2 <= cornerTolerance && (!best || totalGap < best.totalGap)) {
|
|
120991
|
+
best = { totalGap, maxGap: maxGap2, selectedEndpoints };
|
|
120521
120992
|
}
|
|
120522
120993
|
}
|
|
120523
120994
|
if (!best) {
|
|
@@ -338491,6 +338962,7 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
|
|
|
338491
338962
|
nurbsSurface,
|
|
338492
338963
|
spline2d,
|
|
338493
338964
|
spline3d,
|
|
338965
|
+
Loft,
|
|
338494
338966
|
loft,
|
|
338495
338967
|
loftAlongSpine,
|
|
338496
338968
|
sweep,
|
|
@@ -338946,6 +339418,644 @@ function runScript(code, fileName = "main.forge.js", allFiles = {}, options = {}
|
|
|
338946
339418
|
}
|
|
338947
339419
|
}
|
|
338948
339420
|
}
|
|
339421
|
+
const DEFAULT_COLLISION_INSPECTION_OPTIONS = {
|
|
339422
|
+
minOverlapVolume: 0.1
|
|
339423
|
+
};
|
|
339424
|
+
function cloneVec3$1(value) {
|
|
339425
|
+
return [value[0], value[1], value[2]];
|
|
339426
|
+
}
|
|
339427
|
+
function isIdentityTransform(matrix) {
|
|
339428
|
+
if (!matrix) return true;
|
|
339429
|
+
const identity = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
|
|
339430
|
+
return identity.every((value, index2) => Math.abs(matrix[index2] - value) <= 1e-12);
|
|
339431
|
+
}
|
|
339432
|
+
function transformPoint(matrix, point2) {
|
|
339433
|
+
const [x2, y2, z2] = point2;
|
|
339434
|
+
return [
|
|
339435
|
+
matrix[0] * x2 + matrix[4] * y2 + matrix[8] * z2 + matrix[12],
|
|
339436
|
+
matrix[1] * x2 + matrix[5] * y2 + matrix[9] * z2 + matrix[13],
|
|
339437
|
+
matrix[2] * x2 + matrix[6] * y2 + matrix[10] * z2 + matrix[14]
|
|
339438
|
+
];
|
|
339439
|
+
}
|
|
339440
|
+
function transformBBox(min2, max2, matrix) {
|
|
339441
|
+
const corners = [
|
|
339442
|
+
[min2[0], min2[1], min2[2]],
|
|
339443
|
+
[min2[0], min2[1], max2[2]],
|
|
339444
|
+
[min2[0], max2[1], min2[2]],
|
|
339445
|
+
[min2[0], max2[1], max2[2]],
|
|
339446
|
+
[max2[0], min2[1], min2[2]],
|
|
339447
|
+
[max2[0], min2[1], max2[2]],
|
|
339448
|
+
[max2[0], max2[1], min2[2]],
|
|
339449
|
+
[max2[0], max2[1], max2[2]]
|
|
339450
|
+
];
|
|
339451
|
+
const outMin = [Infinity, Infinity, Infinity];
|
|
339452
|
+
const outMax = [-Infinity, -Infinity, -Infinity];
|
|
339453
|
+
for (const corner of corners) {
|
|
339454
|
+
const transformed = transformPoint(matrix, corner);
|
|
339455
|
+
for (let axis = 0; axis < 3; axis += 1) {
|
|
339456
|
+
outMin[axis] = Math.min(outMin[axis], transformed[axis]);
|
|
339457
|
+
outMax[axis] = Math.max(outMax[axis], transformed[axis]);
|
|
339458
|
+
}
|
|
339459
|
+
}
|
|
339460
|
+
return { min: outMin, max: outMax };
|
|
339461
|
+
}
|
|
339462
|
+
function prepareEntry(entry) {
|
|
339463
|
+
if (isIdentityTransform(entry.transform)) {
|
|
339464
|
+
return {
|
|
339465
|
+
...entry,
|
|
339466
|
+
min: cloneVec3$1(entry.min),
|
|
339467
|
+
max: cloneVec3$1(entry.max)
|
|
339468
|
+
};
|
|
339469
|
+
}
|
|
339470
|
+
const bbox = transformBBox(entry.min, entry.max, entry.transform);
|
|
339471
|
+
return {
|
|
339472
|
+
...entry,
|
|
339473
|
+
shape: entry.shape.transform(entry.transform),
|
|
339474
|
+
min: bbox.min,
|
|
339475
|
+
max: bbox.max
|
|
339476
|
+
};
|
|
339477
|
+
}
|
|
339478
|
+
function bboxOverlaps(a2, b) {
|
|
339479
|
+
return [0, 1, 2].every((axis) => a2.min[axis] < b.max[axis] && a2.max[axis] > b.min[axis]);
|
|
339480
|
+
}
|
|
339481
|
+
function collisionId(a2, b) {
|
|
339482
|
+
return `${a2.id}__${b.id}`;
|
|
339483
|
+
}
|
|
339484
|
+
function estimateSweepPairCount$1(entries, axis) {
|
|
339485
|
+
const ordered = entries.map((entry) => ({ min: entry.min[axis], max: entry.max[axis] })).sort((a2, b) => a2.min - b.min || a2.max - b.max);
|
|
339486
|
+
const endValues = ordered.map((entry) => entry.max).sort((a2, b) => a2 - b);
|
|
339487
|
+
let expired = 0;
|
|
339488
|
+
let count = 0;
|
|
339489
|
+
for (let seen2 = 0; seen2 < ordered.length; seen2 += 1) {
|
|
339490
|
+
const currentMin = ordered[seen2].min;
|
|
339491
|
+
while (expired < seen2 && endValues[expired] <= currentMin) expired += 1;
|
|
339492
|
+
count += seen2 - expired;
|
|
339493
|
+
}
|
|
339494
|
+
return count;
|
|
339495
|
+
}
|
|
339496
|
+
function chooseSweepAxis$1(entries) {
|
|
339497
|
+
let bestAxis = 0;
|
|
339498
|
+
let bestCount = estimateSweepPairCount$1(entries, bestAxis);
|
|
339499
|
+
for (const axis of [1, 2]) {
|
|
339500
|
+
const count = estimateSweepPairCount$1(entries, axis);
|
|
339501
|
+
if (count < bestCount) {
|
|
339502
|
+
bestAxis = axis;
|
|
339503
|
+
bestCount = count;
|
|
339504
|
+
}
|
|
339505
|
+
}
|
|
339506
|
+
return bestAxis;
|
|
339507
|
+
}
|
|
339508
|
+
function collectCandidatePairs$1(entries) {
|
|
339509
|
+
if (entries.length < 2) return { pairs: [], bboxPairChecks: 0 };
|
|
339510
|
+
const axis = chooseSweepAxis$1(entries);
|
|
339511
|
+
const ordered = entries.map((entry, index2) => ({ entry, index: index2 })).sort((a2, b) => a2.entry.min[axis] - b.entry.min[axis] || a2.entry.max[axis] - b.entry.max[axis] || a2.index - b.index);
|
|
339512
|
+
let active = [];
|
|
339513
|
+
const pairs = [];
|
|
339514
|
+
let bboxPairChecks = 0;
|
|
339515
|
+
for (const current of ordered) {
|
|
339516
|
+
active = active.filter((candidate) => candidate.entry.max[axis] > current.entry.min[axis]);
|
|
339517
|
+
for (const candidate of active) {
|
|
339518
|
+
bboxPairChecks += 1;
|
|
339519
|
+
if (!bboxOverlaps(candidate.entry, current.entry)) continue;
|
|
339520
|
+
const sourceIndex = Math.min(candidate.index, current.index);
|
|
339521
|
+
const targetIndex = Math.max(candidate.index, current.index);
|
|
339522
|
+
pairs.push({ sourceIndex, targetIndex });
|
|
339523
|
+
}
|
|
339524
|
+
active.push(current);
|
|
339525
|
+
}
|
|
339526
|
+
pairs.sort((a2, b) => a2.sourceIndex - b.sourceIndex || a2.targetIndex - b.targetIndex);
|
|
339527
|
+
return { pairs, bboxPairChecks };
|
|
339528
|
+
}
|
|
339529
|
+
function serializeCollisionFinding(finding) {
|
|
339530
|
+
return {
|
|
339531
|
+
index: finding.index,
|
|
339532
|
+
id: finding.id,
|
|
339533
|
+
sourceIndex: finding.sourceIndex,
|
|
339534
|
+
targetIndex: finding.targetIndex,
|
|
339535
|
+
sourceId: finding.sourceId,
|
|
339536
|
+
targetId: finding.targetId,
|
|
339537
|
+
sourceName: finding.sourceName,
|
|
339538
|
+
targetName: finding.targetName,
|
|
339539
|
+
overlapVolume: finding.overlapVolume
|
|
339540
|
+
};
|
|
339541
|
+
}
|
|
339542
|
+
function analyzeCollisionIntersections(entries, rawOptions = {}) {
|
|
339543
|
+
const options = {
|
|
339544
|
+
minOverlapVolume: rawOptions.minOverlapVolume ?? DEFAULT_COLLISION_INSPECTION_OPTIONS.minOverlapVolume
|
|
339545
|
+
};
|
|
339546
|
+
const warnings = [];
|
|
339547
|
+
const collisions = [];
|
|
339548
|
+
const preparedEntries = entries.map((entry) => prepareEntry(entry));
|
|
339549
|
+
const { pairs: candidatePairs, bboxPairChecks } = collectCandidatePairs$1(preparedEntries);
|
|
339550
|
+
for (const pair of candidatePairs) {
|
|
339551
|
+
const a2 = preparedEntries[pair.sourceIndex];
|
|
339552
|
+
const b = preparedEntries[pair.targetIndex];
|
|
339553
|
+
try {
|
|
339554
|
+
const hit = a2.shape.intersect(b.shape);
|
|
339555
|
+
if (hit.isEmpty()) continue;
|
|
339556
|
+
const overlapVolume = hit.volume();
|
|
339557
|
+
if (!Number.isFinite(overlapVolume) || overlapVolume <= options.minOverlapVolume) continue;
|
|
339558
|
+
collisions.push({
|
|
339559
|
+
index: collisions.length + 1,
|
|
339560
|
+
id: collisionId(a2, b),
|
|
339561
|
+
sourceIndex: pair.sourceIndex,
|
|
339562
|
+
targetIndex: pair.targetIndex,
|
|
339563
|
+
sourceId: a2.id,
|
|
339564
|
+
targetId: b.id,
|
|
339565
|
+
sourceName: a2.name,
|
|
339566
|
+
targetName: b.name,
|
|
339567
|
+
overlapVolume,
|
|
339568
|
+
shape: hit
|
|
339569
|
+
});
|
|
339570
|
+
} catch (err2) {
|
|
339571
|
+
const message = err2 instanceof Error ? err2.message : String(err2);
|
|
339572
|
+
warnings.push(`Could not boolean-test ${a2.name} against ${b.name}: ${message}`);
|
|
339573
|
+
}
|
|
339574
|
+
}
|
|
339575
|
+
const objects = preparedEntries.map((entry, index2) => ({
|
|
339576
|
+
index: index2,
|
|
339577
|
+
id: entry.id,
|
|
339578
|
+
name: entry.name,
|
|
339579
|
+
groupName: entry.groupName,
|
|
339580
|
+
treePath: entry.treePath,
|
|
339581
|
+
mock: entry.mock === true,
|
|
339582
|
+
bbox: {
|
|
339583
|
+
min: cloneVec3$1(entry.min),
|
|
339584
|
+
max: cloneVec3$1(entry.max)
|
|
339585
|
+
}
|
|
339586
|
+
}));
|
|
339587
|
+
return {
|
|
339588
|
+
method: "boolean-intersection",
|
|
339589
|
+
options,
|
|
339590
|
+
broadphase: {
|
|
339591
|
+
method: "sweep-and-prune",
|
|
339592
|
+
allPairCount: preparedEntries.length * (preparedEntries.length - 1) / 2,
|
|
339593
|
+
bboxPairChecks,
|
|
339594
|
+
booleanPairChecks: candidatePairs.length
|
|
339595
|
+
},
|
|
339596
|
+
objectCount: objects.length,
|
|
339597
|
+
collisionCount: collisions.length,
|
|
339598
|
+
objects,
|
|
339599
|
+
collisions,
|
|
339600
|
+
warnings
|
|
339601
|
+
};
|
|
339602
|
+
}
|
|
339603
|
+
const DEFAULT_ROUGHNESS_INSPECTION_OPTIONS = {
|
|
339604
|
+
smoothAngleDeg: 5,
|
|
339605
|
+
sharpAngleDeg: 30,
|
|
339606
|
+
harshAngleDeg: 90
|
|
339607
|
+
};
|
|
339608
|
+
const ROUGHNESS_COLORS = {
|
|
339609
|
+
smooth: [62, 72, 84],
|
|
339610
|
+
moderate: [255, 214, 0],
|
|
339611
|
+
sharp: [255, 124, 34],
|
|
339612
|
+
harsh: [255, 42, 96]
|
|
339613
|
+
};
|
|
339614
|
+
function resolveRoughnessInspectionOptions(raw = {}) {
|
|
339615
|
+
const options = {
|
|
339616
|
+
smoothAngleDeg: raw.smoothAngleDeg ?? DEFAULT_ROUGHNESS_INSPECTION_OPTIONS.smoothAngleDeg,
|
|
339617
|
+
sharpAngleDeg: raw.sharpAngleDeg ?? DEFAULT_ROUGHNESS_INSPECTION_OPTIONS.sharpAngleDeg,
|
|
339618
|
+
harshAngleDeg: raw.harshAngleDeg ?? DEFAULT_ROUGHNESS_INSPECTION_OPTIONS.harshAngleDeg
|
|
339619
|
+
};
|
|
339620
|
+
if (!Number.isFinite(options.smoothAngleDeg) || options.smoothAngleDeg < 0) {
|
|
339621
|
+
throw new Error(`smoothAngleDeg must be a finite non-negative angle (got ${options.smoothAngleDeg}).`);
|
|
339622
|
+
}
|
|
339623
|
+
if (!Number.isFinite(options.sharpAngleDeg) || options.sharpAngleDeg <= options.smoothAngleDeg) {
|
|
339624
|
+
throw new Error(`sharpAngleDeg must be greater than smoothAngleDeg (got ${options.sharpAngleDeg}).`);
|
|
339625
|
+
}
|
|
339626
|
+
if (!Number.isFinite(options.harshAngleDeg) || options.harshAngleDeg <= options.sharpAngleDeg || options.harshAngleDeg > 180) {
|
|
339627
|
+
throw new Error(`harshAngleDeg must be greater than sharpAngleDeg and <= 180 (got ${options.harshAngleDeg}).`);
|
|
339628
|
+
}
|
|
339629
|
+
return options;
|
|
339630
|
+
}
|
|
339631
|
+
function roughnessClassForAngle(angleDeg, options) {
|
|
339632
|
+
if (angleDeg >= options.harshAngleDeg) return "harsh";
|
|
339633
|
+
if (angleDeg >= options.sharpAngleDeg) return "sharp";
|
|
339634
|
+
if (angleDeg >= options.smoothAngleDeg) return "moderate";
|
|
339635
|
+
return "smooth";
|
|
339636
|
+
}
|
|
339637
|
+
function roughnessScoreForAngle(angleDeg, options) {
|
|
339638
|
+
if (angleDeg < options.sharpAngleDeg) return 0;
|
|
339639
|
+
if (angleDeg < options.harshAngleDeg) {
|
|
339640
|
+
return MathUtils.lerp(0.48, 0.82, (angleDeg - options.sharpAngleDeg) / (options.harshAngleDeg - options.sharpAngleDeg));
|
|
339641
|
+
}
|
|
339642
|
+
return 1;
|
|
339643
|
+
}
|
|
339644
|
+
function roughnessColorForAngle(angleDeg, options) {
|
|
339645
|
+
const cls = roughnessClassForAngle(angleDeg, options);
|
|
339646
|
+
if (cls === "smooth" || cls === "harsh") return ROUGHNESS_COLORS[cls];
|
|
339647
|
+
if (cls === "moderate") {
|
|
339648
|
+
return lerpRgb(
|
|
339649
|
+
ROUGHNESS_COLORS.moderate,
|
|
339650
|
+
ROUGHNESS_COLORS.sharp,
|
|
339651
|
+
(angleDeg - options.smoothAngleDeg) / (options.sharpAngleDeg - options.smoothAngleDeg)
|
|
339652
|
+
);
|
|
339653
|
+
}
|
|
339654
|
+
return lerpRgb(
|
|
339655
|
+
ROUGHNESS_COLORS.sharp,
|
|
339656
|
+
ROUGHNESS_COLORS.harsh,
|
|
339657
|
+
(angleDeg - options.sharpAngleDeg) / (options.harshAngleDeg - options.sharpAngleDeg)
|
|
339658
|
+
);
|
|
339659
|
+
}
|
|
339660
|
+
function lerpRgb(a2, b, t) {
|
|
339661
|
+
const clamped = MathUtils.clamp(t, 0, 1);
|
|
339662
|
+
return [
|
|
339663
|
+
Math.round(MathUtils.lerp(a2[0], b[0], clamped)),
|
|
339664
|
+
Math.round(MathUtils.lerp(a2[1], b[1], clamped)),
|
|
339665
|
+
Math.round(MathUtils.lerp(a2[2], b[2], clamped))
|
|
339666
|
+
];
|
|
339667
|
+
}
|
|
339668
|
+
function emptyRoughnessSummary() {
|
|
339669
|
+
return {
|
|
339670
|
+
triangleCount: 0,
|
|
339671
|
+
edgeCount: 0,
|
|
339672
|
+
boundaryEdgeCount: 0,
|
|
339673
|
+
nonManifoldEdgeCount: 0,
|
|
339674
|
+
smoothAreaPercent: 0,
|
|
339675
|
+
moderateAreaPercent: 0,
|
|
339676
|
+
sharpAreaPercent: 0,
|
|
339677
|
+
harshAreaPercent: 0,
|
|
339678
|
+
roughAreaPercent: 0,
|
|
339679
|
+
meanAngleDeg: null,
|
|
339680
|
+
p50AngleDeg: null,
|
|
339681
|
+
p90AngleDeg: null,
|
|
339682
|
+
p95AngleDeg: null,
|
|
339683
|
+
p99AngleDeg: null,
|
|
339684
|
+
maxAngleDeg: null,
|
|
339685
|
+
qualityScore: 0
|
|
339686
|
+
};
|
|
339687
|
+
}
|
|
339688
|
+
function summarizeRoughnessTriangles(triangles, edgeAngles, edgeCount, boundaryEdgeCount, nonManifoldEdgeCount, options) {
|
|
339689
|
+
const areaByClass = {
|
|
339690
|
+
smooth: 0,
|
|
339691
|
+
moderate: 0,
|
|
339692
|
+
sharp: 0,
|
|
339693
|
+
harsh: 0
|
|
339694
|
+
};
|
|
339695
|
+
let totalArea = 0;
|
|
339696
|
+
for (const tri of triangles) {
|
|
339697
|
+
const area2 = Number.isFinite(tri.area) ? tri.area : 0;
|
|
339698
|
+
totalArea += area2;
|
|
339699
|
+
areaByClass[roughnessClassForAngle(tri.maxAngleDeg, options)] += area2;
|
|
339700
|
+
}
|
|
339701
|
+
const sortedAngles = [...edgeAngles].sort((lhs, rhs) => lhs - rhs);
|
|
339702
|
+
const meanAngleDeg = sortedAngles.length > 0 ? Number((sortedAngles.reduce((sum2, angle) => sum2 + angle, 0) / sortedAngles.length).toFixed(2)) : null;
|
|
339703
|
+
const qualityScore = totalArea > 0 ? Math.round(
|
|
339704
|
+
MathUtils.clamp(
|
|
339705
|
+
100 * (areaByClass.smooth + areaByClass.moderate * 0.9) / totalArea - 50 * areaByClass.harsh / totalArea,
|
|
339706
|
+
0,
|
|
339707
|
+
100
|
|
339708
|
+
)
|
|
339709
|
+
) : 0;
|
|
339710
|
+
const safePercent = (value) => totalArea > 0 ? Number((value / totalArea * 100).toFixed(2)) : 0;
|
|
339711
|
+
return {
|
|
339712
|
+
triangleCount: triangles.length,
|
|
339713
|
+
edgeCount,
|
|
339714
|
+
boundaryEdgeCount,
|
|
339715
|
+
nonManifoldEdgeCount,
|
|
339716
|
+
smoothAreaPercent: safePercent(areaByClass.smooth),
|
|
339717
|
+
moderateAreaPercent: safePercent(areaByClass.moderate),
|
|
339718
|
+
sharpAreaPercent: safePercent(areaByClass.sharp),
|
|
339719
|
+
harshAreaPercent: safePercent(areaByClass.harsh),
|
|
339720
|
+
roughAreaPercent: safePercent(areaByClass.sharp + areaByClass.harsh),
|
|
339721
|
+
meanAngleDeg,
|
|
339722
|
+
p50AngleDeg: percentile(sortedAngles, 0.5),
|
|
339723
|
+
p90AngleDeg: percentile(sortedAngles, 0.9),
|
|
339724
|
+
p95AngleDeg: percentile(sortedAngles, 0.95),
|
|
339725
|
+
p99AngleDeg: percentile(sortedAngles, 0.99),
|
|
339726
|
+
maxAngleDeg: sortedAngles.length > 0 ? Number(sortedAngles[sortedAngles.length - 1].toFixed(2)) : null,
|
|
339727
|
+
qualityScore
|
|
339728
|
+
};
|
|
339729
|
+
}
|
|
339730
|
+
function percentile(sorted, q) {
|
|
339731
|
+
if (sorted.length === 0) return null;
|
|
339732
|
+
const index2 = MathUtils.clamp(Math.floor(sorted.length * q), 0, sorted.length - 1);
|
|
339733
|
+
return Number(sorted[index2].toFixed(2));
|
|
339734
|
+
}
|
|
339735
|
+
const DEG_PER_RAD = 180 / Math.PI;
|
|
339736
|
+
function analyzeRoughnessGeometry(sourceGeometry, rawOptions = {}) {
|
|
339737
|
+
const options = resolveRoughnessInspectionOptions(rawOptions);
|
|
339738
|
+
const geometry = sourceGeometry.index ? sourceGeometry.toNonIndexed() : sourceGeometry.clone();
|
|
339739
|
+
const position = geometry.getAttribute("position");
|
|
339740
|
+
const warnings = [];
|
|
339741
|
+
if (!position || position.count < 3) {
|
|
339742
|
+
return { geometry, summary: emptyRoughnessSummary(), warnings: ["No triangle geometry."] };
|
|
339743
|
+
}
|
|
339744
|
+
const triangleCount = Math.floor(position.count / 3);
|
|
339745
|
+
const normals = new Array(triangleCount);
|
|
339746
|
+
const triangles = new Array(triangleCount);
|
|
339747
|
+
const edges = /* @__PURE__ */ new Map();
|
|
339748
|
+
const colors = new Float32Array(position.count * 3);
|
|
339749
|
+
const scores = new Float32Array(position.count);
|
|
339750
|
+
const a2 = new Vector3();
|
|
339751
|
+
const b = new Vector3();
|
|
339752
|
+
const c2 = new Vector3();
|
|
339753
|
+
const ac = new Vector3();
|
|
339754
|
+
const normal = new Vector3();
|
|
339755
|
+
const bbox = new Box3().setFromBufferAttribute(position);
|
|
339756
|
+
const snap = Math.max(1e-6, bbox.getSize(new Vector3()).length() * 1e-8);
|
|
339757
|
+
for (let tri = 0; tri < triangleCount; tri += 1) {
|
|
339758
|
+
const offset2 = tri * 3;
|
|
339759
|
+
readVertex(position, offset2, a2);
|
|
339760
|
+
readVertex(position, offset2 + 1, b);
|
|
339761
|
+
readVertex(position, offset2 + 2, c2);
|
|
339762
|
+
normal.subVectors(b, a2).cross(ac.subVectors(c2, a2));
|
|
339763
|
+
const areaTwice = normal.length();
|
|
339764
|
+
triangles[tri] = { area: areaTwice * 0.5, maxAngleDeg: 0 };
|
|
339765
|
+
normals[tri] = areaTwice > 1e-12 ? normal.multiplyScalar(1 / areaTwice).clone() : new Vector3(0, 0, 1);
|
|
339766
|
+
const keys = [vertexKey(a2, snap), vertexKey(b, snap), vertexKey(c2, snap)];
|
|
339767
|
+
for (let edge = 0; edge < 3; edge += 1) {
|
|
339768
|
+
const key = edgeKey(keys[edge], keys[(edge + 1) % 3]);
|
|
339769
|
+
let record = edges.get(key);
|
|
339770
|
+
if (!record) {
|
|
339771
|
+
record = { triangles: [] };
|
|
339772
|
+
edges.set(key, record);
|
|
339773
|
+
}
|
|
339774
|
+
record.triangles.push(tri);
|
|
339775
|
+
}
|
|
339776
|
+
}
|
|
339777
|
+
const { boundaryEdgeCount, nonManifoldEdgeCount, edgeAngles } = markTriangleRoughness(edges, triangles, normals);
|
|
339778
|
+
if (boundaryEdgeCount > 0) warnings.push(`${boundaryEdgeCount} boundary edge(s) were treated as harsh roughness.`);
|
|
339779
|
+
if (nonManifoldEdgeCount > 0) warnings.push(`${nonManifoldEdgeCount} non-manifold edge(s) were treated as harsh roughness.`);
|
|
339780
|
+
for (let tri = 0; tri < triangleCount; tri += 1) {
|
|
339781
|
+
const { maxAngleDeg } = triangles[tri];
|
|
339782
|
+
const color = roughnessColorForAngle(maxAngleDeg, options);
|
|
339783
|
+
const score = roughnessScoreForAngle(maxAngleDeg, options);
|
|
339784
|
+
const offset2 = tri * 3;
|
|
339785
|
+
for (let vertex2 = 0; vertex2 < 3; vertex2 += 1) {
|
|
339786
|
+
const colorOffset = (offset2 + vertex2) * 3;
|
|
339787
|
+
colors[colorOffset] = color[0] / 255;
|
|
339788
|
+
colors[colorOffset + 1] = color[1] / 255;
|
|
339789
|
+
colors[colorOffset + 2] = color[2] / 255;
|
|
339790
|
+
scores[offset2 + vertex2] = score;
|
|
339791
|
+
}
|
|
339792
|
+
}
|
|
339793
|
+
geometry.setAttribute("color", new BufferAttribute(colors, 3));
|
|
339794
|
+
geometry.setAttribute("roughnessScore", new BufferAttribute(scores, 1));
|
|
339795
|
+
geometry.computeBoundingBox();
|
|
339796
|
+
return {
|
|
339797
|
+
geometry,
|
|
339798
|
+
summary: summarizeRoughnessTriangles(triangles, edgeAngles, edges.size, boundaryEdgeCount, nonManifoldEdgeCount, options),
|
|
339799
|
+
warnings
|
|
339800
|
+
};
|
|
339801
|
+
}
|
|
339802
|
+
function markTriangleRoughness(edges, triangles, normals) {
|
|
339803
|
+
const edgeAngles = [];
|
|
339804
|
+
let boundaryEdgeCount = 0;
|
|
339805
|
+
let nonManifoldEdgeCount = 0;
|
|
339806
|
+
for (const edge of edges.values()) {
|
|
339807
|
+
if (edge.triangles.length === 1) {
|
|
339808
|
+
boundaryEdgeCount += 1;
|
|
339809
|
+
markTriangles(edge.triangles, triangles, 180);
|
|
339810
|
+
edgeAngles.push(180);
|
|
339811
|
+
continue;
|
|
339812
|
+
}
|
|
339813
|
+
if (edge.triangles.length > 2) {
|
|
339814
|
+
nonManifoldEdgeCount += 1;
|
|
339815
|
+
markTriangles(edge.triangles, triangles, 180);
|
|
339816
|
+
edgeAngles.push(180);
|
|
339817
|
+
continue;
|
|
339818
|
+
}
|
|
339819
|
+
const [first, second] = edge.triangles;
|
|
339820
|
+
const dot2 = MathUtils.clamp(normals[first].dot(normals[second]), -1, 1);
|
|
339821
|
+
const angleDeg = Math.acos(dot2) * DEG_PER_RAD;
|
|
339822
|
+
markTriangles(edge.triangles, triangles, angleDeg);
|
|
339823
|
+
edgeAngles.push(angleDeg);
|
|
339824
|
+
}
|
|
339825
|
+
return { boundaryEdgeCount, nonManifoldEdgeCount, edgeAngles };
|
|
339826
|
+
}
|
|
339827
|
+
function readVertex(position, index2, target) {
|
|
339828
|
+
target.set(position.getX(index2), position.getY(index2), position.getZ(index2));
|
|
339829
|
+
}
|
|
339830
|
+
function vertexKey(point2, snap) {
|
|
339831
|
+
return `${Math.round(point2.x / snap)},${Math.round(point2.y / snap)},${Math.round(point2.z / snap)}`;
|
|
339832
|
+
}
|
|
339833
|
+
function edgeKey(a2, b) {
|
|
339834
|
+
return a2 < b ? `${a2}|${b}` : `${b}|${a2}`;
|
|
339835
|
+
}
|
|
339836
|
+
function markTriangles(indices, triangles, angleDeg) {
|
|
339837
|
+
for (const index2 of indices) {
|
|
339838
|
+
triangles[index2].maxAngleDeg = Math.max(triangles[index2].maxAngleDeg, angleDeg);
|
|
339839
|
+
}
|
|
339840
|
+
}
|
|
339841
|
+
const DEFAULT_THICKNESS_INSPECTION_OPTIONS = {
|
|
339842
|
+
minThickness: 1.2,
|
|
339843
|
+
warnThickness: 2,
|
|
339844
|
+
maxThickness: 6,
|
|
339845
|
+
maxSamplesPerObject: 5e3
|
|
339846
|
+
};
|
|
339847
|
+
const THICKNESS_COLORS = {
|
|
339848
|
+
critical: [255, 28, 28],
|
|
339849
|
+
warning: [255, 150, 0],
|
|
339850
|
+
ok: [60, 220, 90],
|
|
339851
|
+
thick: [70, 145, 255],
|
|
339852
|
+
unknown: [90, 90, 90]
|
|
339853
|
+
};
|
|
339854
|
+
function finitePositive(value, fallback, label) {
|
|
339855
|
+
if (value === void 0) return fallback;
|
|
339856
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
339857
|
+
throw new Error(`${label} must be a positive finite number.`);
|
|
339858
|
+
}
|
|
339859
|
+
return value;
|
|
339860
|
+
}
|
|
339861
|
+
function resolveThicknessInspectionOptions(raw = {}) {
|
|
339862
|
+
const minThickness = finitePositive(raw.minThickness, DEFAULT_THICKNESS_INSPECTION_OPTIONS.minThickness, "minThickness");
|
|
339863
|
+
const warnThickness = finitePositive(raw.warnThickness, DEFAULT_THICKNESS_INSPECTION_OPTIONS.warnThickness, "warnThickness");
|
|
339864
|
+
const maxThickness = finitePositive(raw.maxThickness, DEFAULT_THICKNESS_INSPECTION_OPTIONS.maxThickness, "maxThickness");
|
|
339865
|
+
const maxSamplesPerObject = finitePositive(
|
|
339866
|
+
raw.maxSamplesPerObject,
|
|
339867
|
+
DEFAULT_THICKNESS_INSPECTION_OPTIONS.maxSamplesPerObject,
|
|
339868
|
+
"maxSamplesPerObject"
|
|
339869
|
+
);
|
|
339870
|
+
if (minThickness > warnThickness) {
|
|
339871
|
+
throw new Error("minThickness must be less than or equal to warnThickness.");
|
|
339872
|
+
}
|
|
339873
|
+
if (warnThickness > maxThickness) {
|
|
339874
|
+
throw new Error("warnThickness must be less than or equal to maxThickness.");
|
|
339875
|
+
}
|
|
339876
|
+
return {
|
|
339877
|
+
minThickness,
|
|
339878
|
+
warnThickness,
|
|
339879
|
+
maxThickness,
|
|
339880
|
+
maxSamplesPerObject: Math.max(1, Math.floor(maxSamplesPerObject))
|
|
339881
|
+
};
|
|
339882
|
+
}
|
|
339883
|
+
function lerp(a2, b, t) {
|
|
339884
|
+
return a2 + (b - a2) * Math.max(0, Math.min(1, t));
|
|
339885
|
+
}
|
|
339886
|
+
function lerpColor(a2, b, t) {
|
|
339887
|
+
return [Math.round(lerp(a2[0], b[0], t)), Math.round(lerp(a2[1], b[1], t)), Math.round(lerp(a2[2], b[2], t))];
|
|
339888
|
+
}
|
|
339889
|
+
function thicknessClass(thickness, options) {
|
|
339890
|
+
if (thickness == null || !Number.isFinite(thickness) || thickness <= 0) return "unknown";
|
|
339891
|
+
if (thickness <= options.minThickness) return "critical";
|
|
339892
|
+
if (thickness <= options.warnThickness) return "warning";
|
|
339893
|
+
if (thickness <= options.maxThickness) return "ok";
|
|
339894
|
+
return "thick";
|
|
339895
|
+
}
|
|
339896
|
+
function thicknessColor(thickness, options) {
|
|
339897
|
+
const cls = thicknessClass(thickness, options);
|
|
339898
|
+
if (cls === "unknown") return THICKNESS_COLORS.unknown;
|
|
339899
|
+
if (cls === "critical") return THICKNESS_COLORS.critical;
|
|
339900
|
+
if (cls === "warning") {
|
|
339901
|
+
const span = Math.max(1e-9, options.warnThickness - options.minThickness);
|
|
339902
|
+
return lerpColor(THICKNESS_COLORS.critical, THICKNESS_COLORS.warning, ((thickness ?? 0) - options.minThickness) / span);
|
|
339903
|
+
}
|
|
339904
|
+
if (cls === "ok") {
|
|
339905
|
+
const span = Math.max(1e-9, options.maxThickness - options.warnThickness);
|
|
339906
|
+
return lerpColor(THICKNESS_COLORS.ok, THICKNESS_COLORS.thick, ((thickness ?? 0) - options.warnThickness) / span);
|
|
339907
|
+
}
|
|
339908
|
+
return THICKNESS_COLORS.thick;
|
|
339909
|
+
}
|
|
339910
|
+
function sampleArea(sample) {
|
|
339911
|
+
const area2 = sample.area ?? 1;
|
|
339912
|
+
return Number.isFinite(area2) && area2 > 0 ? area2 : 1;
|
|
339913
|
+
}
|
|
339914
|
+
function weightedQuantile(samples, q) {
|
|
339915
|
+
if (samples.length === 0) return null;
|
|
339916
|
+
const sorted = [...samples].sort((a2, b) => a2.thickness - b.thickness);
|
|
339917
|
+
const totalArea = sorted.reduce((sum2, sample) => sum2 + sample.area, 0);
|
|
339918
|
+
const target = totalArea * Math.max(0, Math.min(1, q));
|
|
339919
|
+
let cumulative = 0;
|
|
339920
|
+
for (const sample of sorted) {
|
|
339921
|
+
cumulative += sample.area;
|
|
339922
|
+
if (cumulative >= target) return sample.thickness;
|
|
339923
|
+
}
|
|
339924
|
+
return sorted[sorted.length - 1].thickness;
|
|
339925
|
+
}
|
|
339926
|
+
function percent(part, total) {
|
|
339927
|
+
if (total <= 0) return 0;
|
|
339928
|
+
return part / total * 100;
|
|
339929
|
+
}
|
|
339930
|
+
function summarizeThicknessSamples(samples, options) {
|
|
339931
|
+
const resolved = [];
|
|
339932
|
+
let totalArea = 0;
|
|
339933
|
+
let resolvedArea = 0;
|
|
339934
|
+
let unresolvedArea = 0;
|
|
339935
|
+
let criticalArea = 0;
|
|
339936
|
+
let warningArea = 0;
|
|
339937
|
+
let weightedSum = 0;
|
|
339938
|
+
for (const sample of samples) {
|
|
339939
|
+
const area2 = sampleArea(sample);
|
|
339940
|
+
totalArea += area2;
|
|
339941
|
+
const value = sample.thickness;
|
|
339942
|
+
if (value == null || !Number.isFinite(value) || value <= 0) {
|
|
339943
|
+
unresolvedArea += area2;
|
|
339944
|
+
continue;
|
|
339945
|
+
}
|
|
339946
|
+
resolved.push({ thickness: value, area: area2 });
|
|
339947
|
+
resolvedArea += area2;
|
|
339948
|
+
weightedSum += value * area2;
|
|
339949
|
+
if (value <= options.minThickness) {
|
|
339950
|
+
criticalArea += area2;
|
|
339951
|
+
} else if (value <= options.warnThickness) {
|
|
339952
|
+
warningArea += area2;
|
|
339953
|
+
}
|
|
339954
|
+
}
|
|
339955
|
+
const values = resolved.map((sample) => sample.thickness);
|
|
339956
|
+
return {
|
|
339957
|
+
sampleCount: samples.length,
|
|
339958
|
+
resolvedCount: resolved.length,
|
|
339959
|
+
unresolvedCount: samples.length - resolved.length,
|
|
339960
|
+
minThickness: values.length > 0 ? Math.min(...values) : null,
|
|
339961
|
+
p05Thickness: weightedQuantile(resolved, 0.05),
|
|
339962
|
+
medianThickness: weightedQuantile(resolved, 0.5),
|
|
339963
|
+
meanThickness: resolvedArea > 0 ? weightedSum / resolvedArea : null,
|
|
339964
|
+
maxThickness: values.length > 0 ? Math.max(...values) : null,
|
|
339965
|
+
criticalAreaPercent: percent(criticalArea, resolvedArea),
|
|
339966
|
+
warningAreaPercent: percent(warningArea, resolvedArea),
|
|
339967
|
+
belowWarnAreaPercent: percent(criticalArea + warningArea, resolvedArea),
|
|
339968
|
+
unresolvedAreaPercent: percent(unresolvedArea, totalArea)
|
|
339969
|
+
};
|
|
339970
|
+
}
|
|
339971
|
+
function cloneGeometryForFaceColors(geometry) {
|
|
339972
|
+
return geometry.index ? geometry.toNonIndexed() : geometry.clone();
|
|
339973
|
+
}
|
|
339974
|
+
function geometryMaxDimension(geometry) {
|
|
339975
|
+
geometry.computeBoundingBox();
|
|
339976
|
+
const box2 = geometry.boundingBox;
|
|
339977
|
+
if (!box2) return 1;
|
|
339978
|
+
const size = new Vector3();
|
|
339979
|
+
box2.getSize(size);
|
|
339980
|
+
return Math.max(1, size.x, size.y, size.z);
|
|
339981
|
+
}
|
|
339982
|
+
function firstOppositeSurfaceDistance(raycaster, mesh, point2, direction2, epsilon2, far) {
|
|
339983
|
+
const origin = point2.clone().addScaledVector(direction2, epsilon2);
|
|
339984
|
+
raycaster.set(origin, direction2);
|
|
339985
|
+
raycaster.near = epsilon2;
|
|
339986
|
+
raycaster.far = far;
|
|
339987
|
+
const hit = raycaster.intersectObject(mesh, false).find((entry) => entry.distance > epsilon2);
|
|
339988
|
+
return hit ? hit.distance + epsilon2 : null;
|
|
339989
|
+
}
|
|
339990
|
+
function triangleThickness(raycaster, mesh, centroid, normal, epsilon2, far) {
|
|
339991
|
+
const forward = firstOppositeSurfaceDistance(raycaster, mesh, centroid, normal, epsilon2, far);
|
|
339992
|
+
const backward = firstOppositeSurfaceDistance(raycaster, mesh, centroid, normal.clone().negate(), epsilon2, far);
|
|
339993
|
+
if (forward == null) return backward;
|
|
339994
|
+
if (backward == null) return forward;
|
|
339995
|
+
return Math.min(forward, backward);
|
|
339996
|
+
}
|
|
339997
|
+
function analyzeThicknessGeometry(sourceGeometry, rawOptions = {}) {
|
|
339998
|
+
const options = resolveThicknessInspectionOptions(rawOptions);
|
|
339999
|
+
const geometry = cloneGeometryForFaceColors(sourceGeometry);
|
|
340000
|
+
const position = geometry.getAttribute("position");
|
|
340001
|
+
if (!position || position.count < 3) {
|
|
340002
|
+
return { geometry, samples: [], triangleCount: 0, sampledTriangleCount: 0, sampleStride: 1, warnings: ["No triangle geometry."] };
|
|
340003
|
+
}
|
|
340004
|
+
const triangleCount = Math.floor(position.count / 3);
|
|
340005
|
+
const sampleStride = Math.max(1, Math.ceil(triangleCount / options.maxSamplesPerObject));
|
|
340006
|
+
const maxDim = geometryMaxDimension(geometry);
|
|
340007
|
+
const epsilon2 = Math.max(1e-4, maxDim * 1e-6);
|
|
340008
|
+
const far = Math.max(maxDim * 4, options.maxThickness * 4, 1);
|
|
340009
|
+
const colors = new Float32Array(position.count * 3);
|
|
340010
|
+
const samples = [];
|
|
340011
|
+
const warnings = [];
|
|
340012
|
+
const rayMaterial = new MeshBasicMaterial({ side: DoubleSide });
|
|
340013
|
+
const rayMesh = new Mesh(geometry, rayMaterial);
|
|
340014
|
+
const raycaster = new Raycaster();
|
|
340015
|
+
if (sampleStride > 1) {
|
|
340016
|
+
warnings.push(`Triangle sampling stride ${sampleStride}; increase --thickness-samples for denser analysis.`);
|
|
340017
|
+
}
|
|
340018
|
+
const a2 = new Vector3();
|
|
340019
|
+
const b = new Vector3();
|
|
340020
|
+
const c2 = new Vector3();
|
|
340021
|
+
const ac = new Vector3();
|
|
340022
|
+
const normal = new Vector3();
|
|
340023
|
+
const centroid = new Vector3();
|
|
340024
|
+
let sampledTriangleCount = 0;
|
|
340025
|
+
let lastThickness = null;
|
|
340026
|
+
for (let tri = 0; tri < triangleCount; tri += 1) {
|
|
340027
|
+
const offset2 = tri * 3;
|
|
340028
|
+
a2.fromBufferAttribute(position, offset2);
|
|
340029
|
+
b.fromBufferAttribute(position, offset2 + 1);
|
|
340030
|
+
c2.fromBufferAttribute(position, offset2 + 2);
|
|
340031
|
+
normal.subVectors(b, a2).cross(ac.subVectors(c2, a2));
|
|
340032
|
+
const areaTwice = normal.length();
|
|
340033
|
+
const area2 = areaTwice * 0.5;
|
|
340034
|
+
let thickness = lastThickness;
|
|
340035
|
+
if (tri % sampleStride === 0) {
|
|
340036
|
+
sampledTriangleCount += 1;
|
|
340037
|
+
if (areaTwice <= 1e-12) {
|
|
340038
|
+
thickness = null;
|
|
340039
|
+
} else {
|
|
340040
|
+
normal.multiplyScalar(1 / areaTwice);
|
|
340041
|
+
centroid.copy(a2).add(b).add(c2).multiplyScalar(1 / 3);
|
|
340042
|
+
thickness = triangleThickness(raycaster, rayMesh, centroid, normal, epsilon2, far);
|
|
340043
|
+
}
|
|
340044
|
+
lastThickness = thickness;
|
|
340045
|
+
samples.push({ thickness, area: area2 });
|
|
340046
|
+
}
|
|
340047
|
+
const color = thicknessColor(thickness, options);
|
|
340048
|
+
for (let vertex2 = 0; vertex2 < 3; vertex2 += 1) {
|
|
340049
|
+
const colorOffset = (offset2 + vertex2) * 3;
|
|
340050
|
+
colors[colorOffset] = color[0] / 255;
|
|
340051
|
+
colors[colorOffset + 1] = color[1] / 255;
|
|
340052
|
+
colors[colorOffset + 2] = color[2] / 255;
|
|
340053
|
+
}
|
|
340054
|
+
}
|
|
340055
|
+
geometry.setAttribute("color", new BufferAttribute(colors, 3));
|
|
340056
|
+
rayMaterial.dispose();
|
|
340057
|
+
return { geometry, samples, triangleCount, sampledTriangleCount, sampleStride, warnings };
|
|
340058
|
+
}
|
|
338949
340059
|
const VECTOR_KEYS = /* @__PURE__ */ new Set(["pos", "position", "target", "lookat", "aim", "up"]);
|
|
338950
340060
|
const roundNumber = (value, digits) => {
|
|
338951
340061
|
const scale2 = 10 ** digits;
|
|
@@ -339117,6 +340227,494 @@ function localAabbPlaneRelation(min2, max2, plane, eps = 1e-7) {
|
|
|
339117
340227
|
if (minDistance > -eps) return "positive";
|
|
339118
340228
|
return "crossing";
|
|
339119
340229
|
}
|
|
340230
|
+
const DEFAULT_PHYSICAL_CONNECTIVITY_OPTIONS = {
|
|
340231
|
+
contactTolerance: 0.05,
|
|
340232
|
+
minOverlapVolume: 0.1,
|
|
340233
|
+
exactGeometry: false
|
|
340234
|
+
};
|
|
340235
|
+
const AXIS_NAMES = ["x", "y", "z"];
|
|
340236
|
+
class UnionFind {
|
|
340237
|
+
constructor(size) {
|
|
340238
|
+
__publicField(this, "parent");
|
|
340239
|
+
__publicField(this, "rank");
|
|
340240
|
+
this.parent = Array.from({ length: size }, (_2, index2) => index2);
|
|
340241
|
+
this.rank = Array.from({ length: size }, () => 0);
|
|
340242
|
+
}
|
|
340243
|
+
find(value) {
|
|
340244
|
+
const parent = this.parent[value];
|
|
340245
|
+
if (parent === value) return value;
|
|
340246
|
+
const root = this.find(parent);
|
|
340247
|
+
this.parent[value] = root;
|
|
340248
|
+
return root;
|
|
340249
|
+
}
|
|
340250
|
+
union(a2, b) {
|
|
340251
|
+
const rootA = this.find(a2);
|
|
340252
|
+
const rootB = this.find(b);
|
|
340253
|
+
if (rootA === rootB) return;
|
|
340254
|
+
if (this.rank[rootA] < this.rank[rootB]) {
|
|
340255
|
+
this.parent[rootA] = rootB;
|
|
340256
|
+
return;
|
|
340257
|
+
}
|
|
340258
|
+
if (this.rank[rootA] > this.rank[rootB]) {
|
|
340259
|
+
this.parent[rootB] = rootA;
|
|
340260
|
+
return;
|
|
340261
|
+
}
|
|
340262
|
+
this.parent[rootB] = rootA;
|
|
340263
|
+
this.rank[rootA] += 1;
|
|
340264
|
+
}
|
|
340265
|
+
}
|
|
340266
|
+
function cloneVec3(value) {
|
|
340267
|
+
return [value[0], value[1], value[2]];
|
|
340268
|
+
}
|
|
340269
|
+
function emptyBBox() {
|
|
340270
|
+
return {
|
|
340271
|
+
min: [Infinity, Infinity, Infinity],
|
|
340272
|
+
max: [-Infinity, -Infinity, -Infinity]
|
|
340273
|
+
};
|
|
340274
|
+
}
|
|
340275
|
+
function expandBBox(target, min2, max2) {
|
|
340276
|
+
for (let axis = 0; axis < 3; axis += 1) {
|
|
340277
|
+
target.min[axis] = Math.min(target.min[axis], min2[axis]);
|
|
340278
|
+
target.max[axis] = Math.max(target.max[axis], max2[axis]);
|
|
340279
|
+
}
|
|
340280
|
+
}
|
|
340281
|
+
function intervalGap$1(aMin, aMax, bMin, bMax) {
|
|
340282
|
+
if (aMax < bMin) return bMin - aMax;
|
|
340283
|
+
if (bMax < aMin) return aMin - bMax;
|
|
340284
|
+
return 0;
|
|
340285
|
+
}
|
|
340286
|
+
function nearestBoundaryGap(a2, b, axis) {
|
|
340287
|
+
return Math.min(Math.abs(a2.max[axis] - b.min[axis]), Math.abs(b.max[axis] - a2.min[axis]));
|
|
340288
|
+
}
|
|
340289
|
+
function bboxGaps(a2, b) {
|
|
340290
|
+
return [
|
|
340291
|
+
intervalGap$1(a2.min[0], a2.max[0], b.min[0], b.max[0]),
|
|
340292
|
+
intervalGap$1(a2.min[1], a2.max[1], b.min[1], b.max[1]),
|
|
340293
|
+
intervalGap$1(a2.min[2], a2.max[2], b.min[2], b.max[2])
|
|
340294
|
+
];
|
|
340295
|
+
}
|
|
340296
|
+
function maxGap(gaps) {
|
|
340297
|
+
return Math.max(gaps[0], gaps[1], gaps[2]);
|
|
340298
|
+
}
|
|
340299
|
+
function hasPositiveGap(gaps) {
|
|
340300
|
+
return gaps[0] > 0 || gaps[1] > 0 || gaps[2] > 0;
|
|
340301
|
+
}
|
|
340302
|
+
function bboxInteriorOverlaps(a2, b) {
|
|
340303
|
+
for (let axis = 0; axis < 3; axis += 1) {
|
|
340304
|
+
if (Math.min(a2.max[axis], b.max[axis]) - Math.max(a2.min[axis], b.min[axis]) <= 0) return false;
|
|
340305
|
+
}
|
|
340306
|
+
return true;
|
|
340307
|
+
}
|
|
340308
|
+
function bboxOverlapVolume(a2, b) {
|
|
340309
|
+
let volume = 1;
|
|
340310
|
+
for (let axis = 0; axis < 3; axis += 1) {
|
|
340311
|
+
volume *= Math.max(0, Math.min(a2.max[axis], b.max[axis]) - Math.max(a2.min[axis], b.min[axis]));
|
|
340312
|
+
}
|
|
340313
|
+
return volume;
|
|
340314
|
+
}
|
|
340315
|
+
function estimateSweepPairCount(entries, axis, tolerance) {
|
|
340316
|
+
const ordered = entries.map((entry) => ({ min: entry.min[axis], max: entry.max[axis] })).sort((a2, b) => a2.min - b.min || a2.max - b.max);
|
|
340317
|
+
const endValues = ordered.map((entry) => entry.max + tolerance).sort((a2, b) => a2 - b);
|
|
340318
|
+
let expired = 0;
|
|
340319
|
+
let count = 0;
|
|
340320
|
+
for (let seen2 = 0; seen2 < ordered.length; seen2 += 1) {
|
|
340321
|
+
const currentMin = ordered[seen2].min;
|
|
340322
|
+
while (expired < seen2 && endValues[expired] < currentMin) expired += 1;
|
|
340323
|
+
count += seen2 - expired;
|
|
340324
|
+
}
|
|
340325
|
+
return count;
|
|
340326
|
+
}
|
|
340327
|
+
function chooseSweepAxis(entries, tolerance) {
|
|
340328
|
+
let bestAxis = 0;
|
|
340329
|
+
let bestCount = estimateSweepPairCount(entries, bestAxis, tolerance);
|
|
340330
|
+
for (const axis of [1, 2]) {
|
|
340331
|
+
const count = estimateSweepPairCount(entries, axis, tolerance);
|
|
340332
|
+
if (count < bestCount) {
|
|
340333
|
+
bestAxis = axis;
|
|
340334
|
+
bestCount = count;
|
|
340335
|
+
}
|
|
340336
|
+
}
|
|
340337
|
+
return bestAxis;
|
|
340338
|
+
}
|
|
340339
|
+
function collectCandidatePairs(entries, tolerance) {
|
|
340340
|
+
if (entries.length < 2) return [];
|
|
340341
|
+
const axis = chooseSweepAxis(entries, tolerance);
|
|
340342
|
+
const ordered = entries.map((entry, index2) => ({ entry, index: index2 })).sort((a2, b) => a2.entry.min[axis] - b.entry.min[axis] || a2.entry.max[axis] - b.entry.max[axis] || a2.index - b.index);
|
|
340343
|
+
let active = [];
|
|
340344
|
+
const pairs = [];
|
|
340345
|
+
for (const current of ordered) {
|
|
340346
|
+
active = active.filter((candidate) => candidate.entry.max[axis] + tolerance >= current.entry.min[axis]);
|
|
340347
|
+
for (const candidate of active) {
|
|
340348
|
+
const gaps = bboxGaps(candidate.entry, current.entry);
|
|
340349
|
+
if (maxGap(gaps) > tolerance) continue;
|
|
340350
|
+
const sourceIndex = Math.min(candidate.index, current.index);
|
|
340351
|
+
const targetIndex = Math.max(candidate.index, current.index);
|
|
340352
|
+
pairs.push({ sourceIndex, targetIndex, gaps });
|
|
340353
|
+
}
|
|
340354
|
+
active.push(current);
|
|
340355
|
+
}
|
|
340356
|
+
pairs.sort((a2, b) => a2.sourceIndex - b.sourceIndex || a2.targetIndex - b.targetIndex);
|
|
340357
|
+
return pairs;
|
|
340358
|
+
}
|
|
340359
|
+
function contactFromBBoxes(a2, b, tolerance) {
|
|
340360
|
+
const gaps = bboxGaps(a2, b);
|
|
340361
|
+
const largestGap = maxGap(gaps);
|
|
340362
|
+
if (largestGap > tolerance) return { touching: false, gap: largestGap };
|
|
340363
|
+
const separatedAxes = gaps.map((gap, axis) => ({ gap, axis })).filter((entry) => entry.gap > 0);
|
|
340364
|
+
if (separatedAxes.length > 0) {
|
|
340365
|
+
const nearest2 = separatedAxes.reduce((best, entry) => entry.gap > best.gap ? entry : best, separatedAxes[0]);
|
|
340366
|
+
return { touching: true, gap: nearest2.gap, axis: AXIS_NAMES[nearest2.axis] };
|
|
340367
|
+
}
|
|
340368
|
+
const boundaryAxes = AXIS_NAMES.map((axisName, axis) => ({
|
|
340369
|
+
axis,
|
|
340370
|
+
axisName,
|
|
340371
|
+
gap: nearestBoundaryGap(a2, b, axis)
|
|
340372
|
+
})).filter((entry) => entry.gap <= tolerance);
|
|
340373
|
+
if (boundaryAxes.length === 0) return { touching: false, gap: 0 };
|
|
340374
|
+
const nearest = boundaryAxes.reduce((best, entry) => entry.gap < best.gap ? entry : best, boundaryAxes[0]);
|
|
340375
|
+
return { touching: true, gap: nearest.gap, axis: nearest.axisName };
|
|
340376
|
+
}
|
|
340377
|
+
function intersectionVolume(a2, b) {
|
|
340378
|
+
try {
|
|
340379
|
+
const hit = a2.shape.intersect(b.shape);
|
|
340380
|
+
if (hit.isEmpty()) return { volume: 0 };
|
|
340381
|
+
const volume = hit.volume();
|
|
340382
|
+
return { volume: Number.isFinite(volume) ? volume : 0 };
|
|
340383
|
+
} catch (err2) {
|
|
340384
|
+
const message = err2 instanceof Error ? err2.message : String(err2);
|
|
340385
|
+
return { volume: null, warning: `Could not boolean-test ${a2.name} against ${b.name}: ${message}` };
|
|
340386
|
+
}
|
|
340387
|
+
}
|
|
340388
|
+
function bodyCountForEntry(entry) {
|
|
340389
|
+
if (typeof entry.bodyCount === "number" && Number.isFinite(entry.bodyCount)) {
|
|
340390
|
+
return Math.max(0, Math.round(entry.bodyCount));
|
|
340391
|
+
}
|
|
340392
|
+
return 1;
|
|
340393
|
+
}
|
|
340394
|
+
function makeEdge(entries, sourceIndex, targetIndex, edge) {
|
|
340395
|
+
const source = entries[sourceIndex];
|
|
340396
|
+
const target = entries[targetIndex];
|
|
340397
|
+
return {
|
|
340398
|
+
sourceIndex,
|
|
340399
|
+
targetIndex,
|
|
340400
|
+
sourceId: source.id,
|
|
340401
|
+
targetId: target.id,
|
|
340402
|
+
sourceName: source.name,
|
|
340403
|
+
targetName: target.name,
|
|
340404
|
+
...edge
|
|
340405
|
+
};
|
|
340406
|
+
}
|
|
340407
|
+
function analyzePhysicalConnectivity(entries, rawOptions = {}) {
|
|
340408
|
+
const options = {
|
|
340409
|
+
contactTolerance: rawOptions.contactTolerance ?? DEFAULT_PHYSICAL_CONNECTIVITY_OPTIONS.contactTolerance,
|
|
340410
|
+
minOverlapVolume: rawOptions.minOverlapVolume ?? DEFAULT_PHYSICAL_CONNECTIVITY_OPTIONS.minOverlapVolume,
|
|
340411
|
+
exactGeometry: rawOptions.exactGeometry ?? DEFAULT_PHYSICAL_CONNECTIVITY_OPTIONS.exactGeometry
|
|
340412
|
+
};
|
|
340413
|
+
const warnings = [];
|
|
340414
|
+
const edges = [];
|
|
340415
|
+
const unionFind = new UnionFind(entries.length);
|
|
340416
|
+
for (const pair of collectCandidatePairs(entries, options.contactTolerance)) {
|
|
340417
|
+
const i = pair.sourceIndex;
|
|
340418
|
+
const j = pair.targetIndex;
|
|
340419
|
+
const a2 = entries[i];
|
|
340420
|
+
const b = entries[j];
|
|
340421
|
+
const bboxOverlaps2 = !hasPositiveGap(pair.gaps) && bboxInteriorOverlaps(a2, b);
|
|
340422
|
+
if (options.exactGeometry && bboxOverlaps2) {
|
|
340423
|
+
const overlap = intersectionVolume(a2, b);
|
|
340424
|
+
if (overlap.warning) warnings.push(overlap.warning);
|
|
340425
|
+
if (overlap.volume != null && overlap.volume > options.minOverlapVolume) {
|
|
340426
|
+
unionFind.union(i, j);
|
|
340427
|
+
edges.push(
|
|
340428
|
+
makeEdge(entries, i, j, {
|
|
340429
|
+
kind: "overlap",
|
|
340430
|
+
method: "boolean-intersection",
|
|
340431
|
+
gap: 0,
|
|
340432
|
+
overlapVolume: overlap.volume
|
|
340433
|
+
})
|
|
340434
|
+
);
|
|
340435
|
+
continue;
|
|
340436
|
+
}
|
|
340437
|
+
if (overlap.volume != null && overlap.volume > 0) {
|
|
340438
|
+
unionFind.union(i, j);
|
|
340439
|
+
edges.push(
|
|
340440
|
+
makeEdge(entries, i, j, {
|
|
340441
|
+
kind: "touching",
|
|
340442
|
+
method: "boolean-intersection",
|
|
340443
|
+
gap: 0,
|
|
340444
|
+
overlapVolume: overlap.volume
|
|
340445
|
+
})
|
|
340446
|
+
);
|
|
340447
|
+
continue;
|
|
340448
|
+
}
|
|
340449
|
+
}
|
|
340450
|
+
if (bboxOverlaps2) {
|
|
340451
|
+
unionFind.union(i, j);
|
|
340452
|
+
edges.push(
|
|
340453
|
+
makeEdge(entries, i, j, {
|
|
340454
|
+
kind: "overlap",
|
|
340455
|
+
method: "bbox-overlap",
|
|
340456
|
+
gap: 0,
|
|
340457
|
+
overlapVolume: bboxOverlapVolume(a2, b)
|
|
340458
|
+
})
|
|
340459
|
+
);
|
|
340460
|
+
} else {
|
|
340461
|
+
const contact = contactFromBBoxes(a2, b, options.contactTolerance);
|
|
340462
|
+
if (!contact.touching) continue;
|
|
340463
|
+
unionFind.union(i, j);
|
|
340464
|
+
edges.push(
|
|
340465
|
+
makeEdge(entries, i, j, {
|
|
340466
|
+
kind: "touching",
|
|
340467
|
+
method: "bbox-contact",
|
|
340468
|
+
gap: contact.gap,
|
|
340469
|
+
axis: contact.axis
|
|
340470
|
+
})
|
|
340471
|
+
);
|
|
340472
|
+
}
|
|
340473
|
+
}
|
|
340474
|
+
const objects = entries.map((entry, index2) => ({
|
|
340475
|
+
index: index2,
|
|
340476
|
+
id: entry.id,
|
|
340477
|
+
name: entry.name,
|
|
340478
|
+
groupName: entry.groupName,
|
|
340479
|
+
treePath: entry.treePath,
|
|
340480
|
+
mock: entry.mock === true,
|
|
340481
|
+
bodyCount: bodyCountForEntry(entry),
|
|
340482
|
+
bbox: {
|
|
340483
|
+
min: cloneVec3(entry.min),
|
|
340484
|
+
max: cloneVec3(entry.max)
|
|
340485
|
+
},
|
|
340486
|
+
componentIndex: 0
|
|
340487
|
+
}));
|
|
340488
|
+
const componentByRoot = /* @__PURE__ */ new Map();
|
|
340489
|
+
const rootToComponentIndex = /* @__PURE__ */ new Map();
|
|
340490
|
+
for (let objectIndex = 0; objectIndex < objects.length; objectIndex += 1) {
|
|
340491
|
+
const root = unionFind.find(objectIndex);
|
|
340492
|
+
let component = componentByRoot.get(root);
|
|
340493
|
+
if (!component) {
|
|
340494
|
+
component = {
|
|
340495
|
+
index: componentByRoot.size + 1,
|
|
340496
|
+
objectIndexes: [],
|
|
340497
|
+
objectIds: [],
|
|
340498
|
+
objectNames: [],
|
|
340499
|
+
objectCount: 0,
|
|
340500
|
+
bodyCount: 0,
|
|
340501
|
+
bbox: emptyBBox()
|
|
340502
|
+
};
|
|
340503
|
+
componentByRoot.set(root, component);
|
|
340504
|
+
rootToComponentIndex.set(root, component.index);
|
|
340505
|
+
}
|
|
340506
|
+
const object = objects[objectIndex];
|
|
340507
|
+
object.componentIndex = rootToComponentIndex.get(root) ?? component.index;
|
|
340508
|
+
component.objectIndexes.push(object.index);
|
|
340509
|
+
component.objectIds.push(object.id);
|
|
340510
|
+
component.objectNames.push(object.name);
|
|
340511
|
+
component.objectCount += 1;
|
|
340512
|
+
component.bodyCount += object.bodyCount;
|
|
340513
|
+
expandBBox(component.bbox, object.bbox.min, object.bbox.max);
|
|
340514
|
+
}
|
|
340515
|
+
const components = [...componentByRoot.values()];
|
|
340516
|
+
return {
|
|
340517
|
+
method: options.exactGeometry ? "boolean-overlap-plus-bbox-contact" : "bbox-neighborhood",
|
|
340518
|
+
options,
|
|
340519
|
+
objectCount: objects.length,
|
|
340520
|
+
componentCount: components.length,
|
|
340521
|
+
objects,
|
|
340522
|
+
components,
|
|
340523
|
+
edges,
|
|
340524
|
+
warnings
|
|
340525
|
+
};
|
|
340526
|
+
}
|
|
340527
|
+
const EPSILON = 1e-9;
|
|
340528
|
+
function intervalGap(aMin, aMax, bMin, bMax) {
|
|
340529
|
+
if (aMax < bMin) return bMin - aMax;
|
|
340530
|
+
if (bMax < aMin) return aMin - bMax;
|
|
340531
|
+
return 0;
|
|
340532
|
+
}
|
|
340533
|
+
function bboxGap(a2, b) {
|
|
340534
|
+
const axisGaps = [
|
|
340535
|
+
intervalGap(a2.bbox.min[0], a2.bbox.max[0], b.bbox.min[0], b.bbox.max[0]),
|
|
340536
|
+
intervalGap(a2.bbox.min[1], a2.bbox.max[1], b.bbox.min[1], b.bbox.max[1]),
|
|
340537
|
+
intervalGap(a2.bbox.min[2], a2.bbox.max[2], b.bbox.min[2], b.bbox.max[2])
|
|
340538
|
+
];
|
|
340539
|
+
const gap = Math.sqrt(axisGaps[0] ** 2 + axisGaps[1] ** 2 + axisGaps[2] ** 2);
|
|
340540
|
+
return { gap, axisGaps };
|
|
340541
|
+
}
|
|
340542
|
+
function bboxVolume(component) {
|
|
340543
|
+
const dx = Math.max(0, component.bbox.max[0] - component.bbox.min[0]);
|
|
340544
|
+
const dy = Math.max(0, component.bbox.max[1] - component.bbox.min[1]);
|
|
340545
|
+
const dz = Math.max(0, component.bbox.max[2] - component.bbox.min[2]);
|
|
340546
|
+
return dx * dy * dz;
|
|
340547
|
+
}
|
|
340548
|
+
function compareDefaultRoot(a2, b) {
|
|
340549
|
+
if (a2.bodyCount !== b.bodyCount) return a2.bodyCount - b.bodyCount;
|
|
340550
|
+
if (a2.objectCount !== b.objectCount) return a2.objectCount - b.objectCount;
|
|
340551
|
+
const volumeDelta = bboxVolume(a2) - bboxVolume(b);
|
|
340552
|
+
if (Math.abs(volumeDelta) > EPSILON) return volumeDelta;
|
|
340553
|
+
return b.index - a2.index;
|
|
340554
|
+
}
|
|
340555
|
+
function defaultRootComponentIndex(components) {
|
|
340556
|
+
if (components.length === 0) return null;
|
|
340557
|
+
return components.reduce((best, component) => compareDefaultRoot(component, best) > 0 ? component : best, components[0]).index;
|
|
340558
|
+
}
|
|
340559
|
+
function componentPositionByIndex(components) {
|
|
340560
|
+
return new Map(components.map((component, position) => [component.index, position]));
|
|
340561
|
+
}
|
|
340562
|
+
function computeNearestComponents(components) {
|
|
340563
|
+
const nearest = components.map(() => ({ nearestGap: null, nearestComponentIndex: null }));
|
|
340564
|
+
for (let sourcePosition = 0; sourcePosition < components.length; sourcePosition += 1) {
|
|
340565
|
+
for (let targetPosition = sourcePosition + 1; targetPosition < components.length; targetPosition += 1) {
|
|
340566
|
+
const sourceComponent = components[sourcePosition];
|
|
340567
|
+
const targetComponent = components[targetPosition];
|
|
340568
|
+
const edge = bboxGap(sourceComponent, targetComponent);
|
|
340569
|
+
if (!Number.isFinite(edge.gap)) continue;
|
|
340570
|
+
const source = nearest[sourcePosition];
|
|
340571
|
+
if (source.nearestGap == null || edge.gap < source.nearestGap - EPSILON || Math.abs(edge.gap - source.nearestGap) <= EPSILON && targetComponent.index < (source.nearestComponentIndex ?? Infinity)) {
|
|
340572
|
+
source.nearestGap = edge.gap;
|
|
340573
|
+
source.nearestComponentIndex = targetComponent.index;
|
|
340574
|
+
}
|
|
340575
|
+
const target = nearest[targetPosition];
|
|
340576
|
+
if (target.nearestGap == null || edge.gap < target.nearestGap - EPSILON || Math.abs(edge.gap - target.nearestGap) <= EPSILON && sourceComponent.index < (target.nearestComponentIndex ?? Infinity)) {
|
|
340577
|
+
target.nearestGap = edge.gap;
|
|
340578
|
+
target.nearestComponentIndex = sourceComponent.index;
|
|
340579
|
+
}
|
|
340580
|
+
}
|
|
340581
|
+
}
|
|
340582
|
+
return nearest;
|
|
340583
|
+
}
|
|
340584
|
+
function computeRootDistances(components, rootComponentIndex) {
|
|
340585
|
+
if (rootComponentIndex == null) return [];
|
|
340586
|
+
const positions = componentPositionByIndex(components);
|
|
340587
|
+
const rootPosition = positions.get(rootComponentIndex);
|
|
340588
|
+
if (rootPosition == null) {
|
|
340589
|
+
throw new Error(`rootComponentIndex ${rootComponentIndex} does not match any physical component`);
|
|
340590
|
+
}
|
|
340591
|
+
const visited = components.map(() => false);
|
|
340592
|
+
const distances = components.map(() => Infinity);
|
|
340593
|
+
const parents = components.map(() => null);
|
|
340594
|
+
const parentGaps = components.map(() => null);
|
|
340595
|
+
distances[rootPosition] = 0;
|
|
340596
|
+
for (; ; ) {
|
|
340597
|
+
let current = -1;
|
|
340598
|
+
for (let i = 0; i < components.length; i += 1) {
|
|
340599
|
+
if (visited[i]) continue;
|
|
340600
|
+
if (current === -1 || distances[i] < distances[current] - EPSILON || Math.abs(distances[i] - distances[current]) <= EPSILON && components[i].index < components[current].index) {
|
|
340601
|
+
current = i;
|
|
340602
|
+
}
|
|
340603
|
+
}
|
|
340604
|
+
if (current === -1 || !Number.isFinite(distances[current])) break;
|
|
340605
|
+
visited[current] = true;
|
|
340606
|
+
for (let targetPosition = 0; targetPosition < components.length; targetPosition += 1) {
|
|
340607
|
+
if (visited[targetPosition] || targetPosition === current) continue;
|
|
340608
|
+
const edge = bboxGap(components[current], components[targetPosition]);
|
|
340609
|
+
if (!Number.isFinite(edge.gap)) continue;
|
|
340610
|
+
const nextDistance = distances[current] + edge.gap;
|
|
340611
|
+
if (nextDistance < distances[targetPosition] - EPSILON || Math.abs(nextDistance - distances[targetPosition]) <= EPSILON && components[current].index < (parents[targetPosition] ?? Infinity)) {
|
|
340612
|
+
distances[targetPosition] = nextDistance;
|
|
340613
|
+
parents[targetPosition] = components[current].index;
|
|
340614
|
+
parentGaps[targetPosition] = edge.gap;
|
|
340615
|
+
}
|
|
340616
|
+
}
|
|
340617
|
+
}
|
|
340618
|
+
return components.map((_2, position) => ({
|
|
340619
|
+
rootDistance: distances[position],
|
|
340620
|
+
parentComponentIndex: parents[position],
|
|
340621
|
+
parentGap: parentGaps[position]
|
|
340622
|
+
}));
|
|
340623
|
+
}
|
|
340624
|
+
function makeGapEdge(source, target) {
|
|
340625
|
+
const gap = bboxGap(source, target);
|
|
340626
|
+
return {
|
|
340627
|
+
sourceComponentIndex: source.index,
|
|
340628
|
+
targetComponentIndex: target.index,
|
|
340629
|
+
sourceObjectNames: [...source.objectNames],
|
|
340630
|
+
targetObjectNames: [...target.objectNames],
|
|
340631
|
+
gap: gap.gap,
|
|
340632
|
+
axisGaps: gap.axisGaps
|
|
340633
|
+
};
|
|
340634
|
+
}
|
|
340635
|
+
function compactGapEdges(components, nearest, rooted) {
|
|
340636
|
+
const componentByIndex = new Map(components.map((component) => [component.index, component]));
|
|
340637
|
+
const seen2 = /* @__PURE__ */ new Set();
|
|
340638
|
+
const edges = [];
|
|
340639
|
+
const add2 = (sourceIndex, targetIndex) => {
|
|
340640
|
+
if (targetIndex == null || sourceIndex === targetIndex) return;
|
|
340641
|
+
const source = componentByIndex.get(Math.min(sourceIndex, targetIndex));
|
|
340642
|
+
const target = componentByIndex.get(Math.max(sourceIndex, targetIndex));
|
|
340643
|
+
if (!source || !target) return;
|
|
340644
|
+
const key = `${source.index}:${target.index}`;
|
|
340645
|
+
if (seen2.has(key)) return;
|
|
340646
|
+
seen2.add(key);
|
|
340647
|
+
edges.push(makeGapEdge(source, target));
|
|
340648
|
+
};
|
|
340649
|
+
components.forEach((component, position) => {
|
|
340650
|
+
var _a3, _b3;
|
|
340651
|
+
add2(component.index, ((_a3 = nearest[position]) == null ? void 0 : _a3.nearestComponentIndex) ?? null);
|
|
340652
|
+
add2(component.index, ((_b3 = rooted[position]) == null ? void 0 : _b3.parentComponentIndex) ?? null);
|
|
340653
|
+
});
|
|
340654
|
+
return edges.sort((a2, b) => a2.sourceComponentIndex - b.sourceComponentIndex || a2.targetComponentIndex - b.targetComponentIndex);
|
|
340655
|
+
}
|
|
340656
|
+
function analyzeDistanceInspection(entries, rawOptions = {}) {
|
|
340657
|
+
const connectivity = analyzePhysicalConnectivity(entries, rawOptions);
|
|
340658
|
+
const rootComponentIndex = rawOptions.rootComponentIndex ?? defaultRootComponentIndex(connectivity.components);
|
|
340659
|
+
const nearest = computeNearestComponents(connectivity.components);
|
|
340660
|
+
const rooted = computeRootDistances(connectivity.components, rootComponentIndex);
|
|
340661
|
+
const gapEdges = compactGapEdges(connectivity.components, nearest, rooted);
|
|
340662
|
+
const componentByIndex = /* @__PURE__ */ new Map();
|
|
340663
|
+
const components = connectivity.components.map((component, position) => {
|
|
340664
|
+
var _a3, _b3;
|
|
340665
|
+
const rootData = rooted[position] ?? {
|
|
340666
|
+
rootDistance: rootComponentIndex === component.index ? 0 : Infinity,
|
|
340667
|
+
parentComponentIndex: null,
|
|
340668
|
+
parentGap: null
|
|
340669
|
+
};
|
|
340670
|
+
const decorated = {
|
|
340671
|
+
...component,
|
|
340672
|
+
isRoot: component.index === rootComponentIndex,
|
|
340673
|
+
rootDistance: rootData.rootDistance,
|
|
340674
|
+
nearestGap: ((_a3 = nearest[position]) == null ? void 0 : _a3.nearestGap) ?? null,
|
|
340675
|
+
nearestComponentIndex: ((_b3 = nearest[position]) == null ? void 0 : _b3.nearestComponentIndex) ?? null,
|
|
340676
|
+
parentComponentIndex: rootData.parentComponentIndex,
|
|
340677
|
+
parentGap: rootData.parentGap
|
|
340678
|
+
};
|
|
340679
|
+
componentByIndex.set(component.index, decorated);
|
|
340680
|
+
return decorated;
|
|
340681
|
+
});
|
|
340682
|
+
const objects = connectivity.objects.map((object) => {
|
|
340683
|
+
const component = componentByIndex.get(object.componentIndex);
|
|
340684
|
+
return {
|
|
340685
|
+
...object,
|
|
340686
|
+
rootDistance: (component == null ? void 0 : component.rootDistance) ?? Infinity,
|
|
340687
|
+
nearestGap: (component == null ? void 0 : component.nearestGap) ?? null,
|
|
340688
|
+
nearestComponentIndex: (component == null ? void 0 : component.nearestComponentIndex) ?? null,
|
|
340689
|
+
parentComponentIndex: (component == null ? void 0 : component.parentComponentIndex) ?? null,
|
|
340690
|
+
parentGap: (component == null ? void 0 : component.parentGap) ?? null
|
|
340691
|
+
};
|
|
340692
|
+
});
|
|
340693
|
+
const finiteDistances = components.map((component) => component.rootDistance).filter(Number.isFinite);
|
|
340694
|
+
const maxRootDistance = finiteDistances.length > 0 ? Math.max(...finiteDistances) : 0;
|
|
340695
|
+
return {
|
|
340696
|
+
method: "physical-component-bbox-gap-graph",
|
|
340697
|
+
distanceMethod: "axis-aligned-bbox-gap",
|
|
340698
|
+
options: {
|
|
340699
|
+
contactTolerance: connectivity.options.contactTolerance,
|
|
340700
|
+
minOverlapVolume: connectivity.options.minOverlapVolume,
|
|
340701
|
+
rootComponentIndex
|
|
340702
|
+
},
|
|
340703
|
+
objectCount: connectivity.objectCount,
|
|
340704
|
+
componentCount: connectivity.componentCount,
|
|
340705
|
+
rootComponentIndex,
|
|
340706
|
+
maxRootDistance,
|
|
340707
|
+
gapEdgeCount: connectivity.components.length * (connectivity.components.length - 1) / 2,
|
|
340708
|
+
objects,
|
|
340709
|
+
components,
|
|
340710
|
+
gapEdges,
|
|
340711
|
+
connectivity: {
|
|
340712
|
+
method: connectivity.method,
|
|
340713
|
+
edges: connectivity.edges
|
|
340714
|
+
},
|
|
340715
|
+
warnings: [...connectivity.warnings]
|
|
340716
|
+
};
|
|
340717
|
+
}
|
|
339120
340718
|
export {
|
|
339121
340719
|
Matrix4 as $,
|
|
339122
340720
|
ACESFilmicToneMapping as A,
|
|
@@ -339212,64 +340810,75 @@ export {
|
|
|
339212
340810
|
DEFAULT_ACTIVE_BACKEND as ay,
|
|
339213
340811
|
isConstraintSketch as az,
|
|
339214
340812
|
PCFShadowMap as b,
|
|
340813
|
+
generateCuttingLayoutPdf as b$,
|
|
339215
340814
|
PointLight as b0,
|
|
339216
340815
|
DirectionalLight as b1,
|
|
339217
|
-
|
|
340816
|
+
analyzeCollisionIntersections as b2,
|
|
339218
340817
|
shapeToGeometry as b3,
|
|
339219
|
-
|
|
339220
|
-
|
|
339221
|
-
|
|
339222
|
-
|
|
339223
|
-
|
|
339224
|
-
|
|
339225
|
-
|
|
339226
|
-
|
|
339227
|
-
|
|
339228
|
-
|
|
339229
|
-
|
|
339230
|
-
|
|
339231
|
-
|
|
339232
|
-
|
|
339233
|
-
|
|
339234
|
-
|
|
339235
|
-
|
|
339236
|
-
|
|
339237
|
-
|
|
339238
|
-
|
|
339239
|
-
|
|
339240
|
-
|
|
339241
|
-
|
|
339242
|
-
|
|
339243
|
-
|
|
339244
|
-
|
|
339245
|
-
|
|
339246
|
-
|
|
339247
|
-
|
|
339248
|
-
|
|
339249
|
-
|
|
339250
|
-
|
|
339251
|
-
|
|
339252
|
-
|
|
339253
|
-
|
|
339254
|
-
|
|
339255
|
-
|
|
339256
|
-
|
|
339257
|
-
|
|
339258
|
-
|
|
339259
|
-
|
|
339260
|
-
|
|
339261
|
-
|
|
339262
|
-
|
|
339263
|
-
|
|
339264
|
-
|
|
339265
|
-
|
|
339266
|
-
|
|
339267
|
-
|
|
339268
|
-
|
|
339269
|
-
|
|
339270
|
-
|
|
339271
|
-
|
|
340818
|
+
buildShapeFromCompilePlan as b4,
|
|
340819
|
+
sketchToSvg as b5,
|
|
340820
|
+
sketchToDxf as b6,
|
|
340821
|
+
runScript as b7,
|
|
340822
|
+
MeshPhysicalMaterial as b8,
|
|
340823
|
+
LineSegments as b9,
|
|
340824
|
+
normalizeCutPlane as bA,
|
|
340825
|
+
toClippingPlane as bB,
|
|
340826
|
+
findJointAnimationClip as bC,
|
|
340827
|
+
resolveJointAnimation as bD,
|
|
340828
|
+
resolveJointViewValues as bE,
|
|
340829
|
+
getShapePorts as bF,
|
|
340830
|
+
getShapeUsedPorts as bG,
|
|
340831
|
+
DEFAULT_VIEW_CONFIG as bH,
|
|
340832
|
+
getKernelFaceNameForTriangle as bI,
|
|
340833
|
+
analyzePhysicalConnectivity as bJ,
|
|
340834
|
+
analyzeDistanceInspection as bK,
|
|
340835
|
+
initKernel as bL,
|
|
340836
|
+
initSolverWasm as bM,
|
|
340837
|
+
BoxGeometry as bN,
|
|
340838
|
+
localAabbPlaneRelation as bO,
|
|
340839
|
+
ShapeUtils as bP,
|
|
340840
|
+
Group as bQ,
|
|
340841
|
+
resolveRoughnessInspectionOptions as bR,
|
|
340842
|
+
ROUGHNESS_COLORS as bS,
|
|
340843
|
+
resolveThicknessInspectionOptions as bT,
|
|
340844
|
+
summarizeThicknessSamples as bU,
|
|
340845
|
+
THICKNESS_COLORS as bV,
|
|
340846
|
+
intersectWithPlane as bW,
|
|
340847
|
+
parseCameraCliSpec as bX,
|
|
340848
|
+
PMREMGenerator as bY,
|
|
340849
|
+
serializeCollisionFinding as bZ,
|
|
340850
|
+
worldAuthorPlaneToLocal as b_,
|
|
340851
|
+
analyzeThicknessGeometry as ba,
|
|
340852
|
+
analyzeRoughnessGeometry as bb,
|
|
340853
|
+
getRenderStylePreset as bc,
|
|
340854
|
+
AdditiveBlending as bd,
|
|
340855
|
+
CatmullRomCurve3 as be,
|
|
340856
|
+
TubeGeometry as bf,
|
|
340857
|
+
MeshStandardMaterial as bg,
|
|
340858
|
+
compileSdfNode3 as bh,
|
|
340859
|
+
buildSdfRaymarchFragmentShader as bi,
|
|
340860
|
+
SDF_RAYMARCH_PROXY_VERTEX_SHADER as bj,
|
|
340861
|
+
Shape2 as bk,
|
|
340862
|
+
ShapeGeometry as bl,
|
|
340863
|
+
ShaderLib as bm,
|
|
340864
|
+
CylinderGeometry as bn,
|
|
340865
|
+
parseViewportCameraState as bo,
|
|
340866
|
+
createResolvedExplodeConfig as bp,
|
|
340867
|
+
explodeBoundsCenter as bq,
|
|
340868
|
+
explodeMergeBounds as br,
|
|
340869
|
+
resolveExplodeDirective as bs,
|
|
340870
|
+
computeExplodeMotion as bt,
|
|
340871
|
+
getSketchWorldMatrix as bu,
|
|
340872
|
+
explodeAdd as bv,
|
|
340873
|
+
hasExplodeOverride as bw,
|
|
340874
|
+
resolveExplodeLocalFanDirection as bx,
|
|
340875
|
+
explodeMul as by,
|
|
340876
|
+
explodeLeafFanStage as bz,
|
|
339272
340877
|
SRGBColorSpace as c,
|
|
340878
|
+
getCameraForwardVector as c0,
|
|
340879
|
+
RENDER_STYLE_OPTIONS as c1,
|
|
340880
|
+
initKernelManifoldOnly as c2,
|
|
340881
|
+
__viteBrowserExternal$1 as c3,
|
|
339273
340882
|
Layers as d,
|
|
339274
340883
|
Color as e,
|
|
339275
340884
|
RGBAFormat as f,
|