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
package/dist-cli/forgecad.js
CHANGED
|
@@ -10389,6 +10389,7 @@ function buildSweepLevelSetInput(profilePolygons, pathInput, options) {
|
|
|
10389
10389
|
}
|
|
10390
10390
|
|
|
10391
10391
|
// src/forge/sketch/polygonSampling.ts
|
|
10392
|
+
var EPS5 = 1e-9;
|
|
10392
10393
|
function resamplePolygon(poly, targetCount) {
|
|
10393
10394
|
if (poly.length < 2) return poly;
|
|
10394
10395
|
if (targetCount <= 0) return [];
|
|
@@ -10426,6 +10427,78 @@ function resamplePolygon(poly, targetCount) {
|
|
|
10426
10427
|
}
|
|
10427
10428
|
return out;
|
|
10428
10429
|
}
|
|
10430
|
+
function resamplePolygonByAngle(poly, targetCount, center = polygonCentroid(poly)) {
|
|
10431
|
+
if (poly.length < 3 || targetCount <= 0) return null;
|
|
10432
|
+
if (!isConvexPolygon(poly)) return null;
|
|
10433
|
+
const out = [];
|
|
10434
|
+
for (let index = 0; index < targetCount; index += 1) {
|
|
10435
|
+
const angle = index / targetCount * Math.PI * 2;
|
|
10436
|
+
const point2 = rayPolygonIntersection(center, [Math.cos(angle), Math.sin(angle)], poly);
|
|
10437
|
+
if (!point2) return null;
|
|
10438
|
+
out.push(point2);
|
|
10439
|
+
}
|
|
10440
|
+
return out;
|
|
10441
|
+
}
|
|
10442
|
+
function rayPolygonIntersection(origin, direction2, poly) {
|
|
10443
|
+
let bestT = Infinity;
|
|
10444
|
+
let best = null;
|
|
10445
|
+
for (let index = 0; index < poly.length; index += 1) {
|
|
10446
|
+
const a = poly[index];
|
|
10447
|
+
const b = poly[(index + 1) % poly.length];
|
|
10448
|
+
const edge = [b[0] - a[0], b[1] - a[1]];
|
|
10449
|
+
const denom = cross2(direction2, edge);
|
|
10450
|
+
if (Math.abs(denom) < EPS5) continue;
|
|
10451
|
+
const delta = [a[0] - origin[0], a[1] - origin[1]];
|
|
10452
|
+
const rayT = cross2(delta, edge) / denom;
|
|
10453
|
+
const edgeT = cross2(delta, direction2) / denom;
|
|
10454
|
+
if (rayT >= -EPS5 && edgeT >= -EPS5 && edgeT <= 1 + EPS5 && rayT < bestT) {
|
|
10455
|
+
bestT = rayT;
|
|
10456
|
+
best = [origin[0] + direction2[0] * rayT, origin[1] + direction2[1] * rayT];
|
|
10457
|
+
}
|
|
10458
|
+
}
|
|
10459
|
+
return best;
|
|
10460
|
+
}
|
|
10461
|
+
function polygonCentroid(poly) {
|
|
10462
|
+
let area2 = 0;
|
|
10463
|
+
let cx = 0;
|
|
10464
|
+
let cy = 0;
|
|
10465
|
+
for (let index = 0; index < poly.length; index += 1) {
|
|
10466
|
+
const a = poly[index];
|
|
10467
|
+
const b = poly[(index + 1) % poly.length];
|
|
10468
|
+
const crossValue = cross2(a, b);
|
|
10469
|
+
area2 += crossValue;
|
|
10470
|
+
cx += (a[0] + b[0]) * crossValue;
|
|
10471
|
+
cy += (a[1] + b[1]) * crossValue;
|
|
10472
|
+
}
|
|
10473
|
+
if (Math.abs(area2) < EPS5) return averagePoint(poly);
|
|
10474
|
+
return [cx / (3 * area2), cy / (3 * area2)];
|
|
10475
|
+
}
|
|
10476
|
+
function averagePoint(poly) {
|
|
10477
|
+
let x = 0;
|
|
10478
|
+
let y = 0;
|
|
10479
|
+
for (const point2 of poly) {
|
|
10480
|
+
x += point2[0];
|
|
10481
|
+
y += point2[1];
|
|
10482
|
+
}
|
|
10483
|
+
return [x / poly.length, y / poly.length];
|
|
10484
|
+
}
|
|
10485
|
+
function isConvexPolygon(poly) {
|
|
10486
|
+
let sign2 = 0;
|
|
10487
|
+
for (let index = 0; index < poly.length; index += 1) {
|
|
10488
|
+
const a = poly[index];
|
|
10489
|
+
const b = poly[(index + 1) % poly.length];
|
|
10490
|
+
const c = poly[(index + 2) % poly.length];
|
|
10491
|
+
const turn = cross2([b[0] - a[0], b[1] - a[1]], [c[0] - b[0], c[1] - b[1]]);
|
|
10492
|
+
if (Math.abs(turn) < EPS5) continue;
|
|
10493
|
+
const currentSign = Math.sign(turn);
|
|
10494
|
+
if (sign2 !== 0 && currentSign !== sign2) return false;
|
|
10495
|
+
sign2 = currentSign;
|
|
10496
|
+
}
|
|
10497
|
+
return sign2 !== 0;
|
|
10498
|
+
}
|
|
10499
|
+
function cross2(a, b) {
|
|
10500
|
+
return a[0] * b[1] - a[1] * b[0];
|
|
10501
|
+
}
|
|
10429
10502
|
|
|
10430
10503
|
// src/forge/backends/manifold/loftStitched.ts
|
|
10431
10504
|
function loftStitched(profiles2, heights, wasm) {
|
|
@@ -10556,8 +10629,10 @@ function stitchSingleLoopLoft(loops, heights, wasm) {
|
|
|
10556
10629
|
maxPoints = Math.max(maxPoints, loop.length);
|
|
10557
10630
|
}
|
|
10558
10631
|
const N = Math.max(maxPoints, 24);
|
|
10632
|
+
const angularSamples = normalizedLoops.map((loop) => resamplePolygonByAngle(loop, N));
|
|
10633
|
+
const useAngularSamples = angularSamples.every((samples) => samples != null);
|
|
10559
10634
|
const resampled = normalizedLoops.map((loop, i) => {
|
|
10560
|
-
const pts2d = resamplePolygon(loop, N);
|
|
10635
|
+
const pts2d = useAngularSamples ? angularSamples[i] : resamplePolygon(loop, N);
|
|
10561
10636
|
const z = heights[i];
|
|
10562
10637
|
return pts2d.map(([x, y]) => [x, y, z]);
|
|
10563
10638
|
});
|
|
@@ -10843,13 +10918,13 @@ function computeParallelTransportFrames2(path4, preferredUp) {
|
|
|
10843
10918
|
const frames = [];
|
|
10844
10919
|
const firstTangent = normalize3(sub2(path4[1], path4[0]));
|
|
10845
10920
|
if (!firstTangent) return null;
|
|
10846
|
-
let x = normalize3(
|
|
10921
|
+
let x = normalize3(cross3(preferredUp, firstTangent));
|
|
10847
10922
|
if (!x || length4(x) < 1e-8) {
|
|
10848
10923
|
const fallback = Math.abs(firstTangent[0]) < 0.9 ? [1, 0, 0] : [0, 1, 0];
|
|
10849
|
-
x = normalize3(
|
|
10924
|
+
x = normalize3(cross3(fallback, firstTangent));
|
|
10850
10925
|
if (!x) return null;
|
|
10851
10926
|
}
|
|
10852
|
-
let y = normalize3(
|
|
10927
|
+
let y = normalize3(cross3(firstTangent, x));
|
|
10853
10928
|
frames.push({ origin: path4[0], x, y, t: firstTangent });
|
|
10854
10929
|
for (let i = 1; i < n; i++) {
|
|
10855
10930
|
const prevT = frames[i - 1].t;
|
|
@@ -10864,7 +10939,7 @@ function computeParallelTransportFrames2(path4, preferredUp) {
|
|
|
10864
10939
|
if (!nt) return null;
|
|
10865
10940
|
nextT = nt;
|
|
10866
10941
|
}
|
|
10867
|
-
const v =
|
|
10942
|
+
const v = cross3(prevT, nextT);
|
|
10868
10943
|
const vLen = length4(v);
|
|
10869
10944
|
const c = dot(prevT, nextT);
|
|
10870
10945
|
if (vLen > 1e-10) {
|
|
@@ -10952,7 +11027,7 @@ function scale2(v, s) {
|
|
|
10952
11027
|
function dot(a, b) {
|
|
10953
11028
|
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
|
10954
11029
|
}
|
|
10955
|
-
function
|
|
11030
|
+
function cross3(a, b) {
|
|
10956
11031
|
return [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]];
|
|
10957
11032
|
}
|
|
10958
11033
|
function length4(v) {
|
|
@@ -10965,7 +11040,7 @@ function normalize3(v) {
|
|
|
10965
11040
|
}
|
|
10966
11041
|
function rotateVector2(v, axis, c, s) {
|
|
10967
11042
|
const kDotV = dot(axis, v);
|
|
10968
|
-
const kCrossV =
|
|
11043
|
+
const kCrossV = cross3(axis, v);
|
|
10969
11044
|
return [
|
|
10970
11045
|
v[0] * c + kCrossV[0] * s + axis[0] * kDotV * (1 - c),
|
|
10971
11046
|
v[1] * c + kCrossV[1] * s + axis[1] * kDotV * (1 - c),
|
|
@@ -11257,7 +11332,7 @@ function subtract3(a, b) {
|
|
|
11257
11332
|
function addScaled3(point2, direction2, scale8) {
|
|
11258
11333
|
return [point2[0] + direction2[0] * scale8, point2[1] + direction2[1] * scale8, point2[2] + direction2[2] * scale8];
|
|
11259
11334
|
}
|
|
11260
|
-
function
|
|
11335
|
+
function cross32(a, b) {
|
|
11261
11336
|
return [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]];
|
|
11262
11337
|
}
|
|
11263
11338
|
function fromSlicesPlaneFrameForManifold(normalInput) {
|
|
@@ -11273,8 +11348,8 @@ function fromSlicesPlaneFrameForManifold(normalInput) {
|
|
|
11273
11348
|
return { u: [0, 1, 0], v: [0, 0, nx > 0 ? 1 : -1], normal };
|
|
11274
11349
|
}
|
|
11275
11350
|
const reference = Math.abs(nx) < 0.9 ? [1, 0, 0] : [0, 1, 0];
|
|
11276
|
-
const u = normalizeVec32(
|
|
11277
|
-
return { u, v:
|
|
11351
|
+
const u = normalizeVec32(cross32(reference, normal), "Shape.fromSlices profile u axis");
|
|
11352
|
+
return { u, v: cross32(normal, u), normal };
|
|
11278
11353
|
}
|
|
11279
11354
|
function fromSlicesLocalToWorldMatrixForManifold(normal) {
|
|
11280
11355
|
const frame = fromSlicesPlaneFrameForManifold(normal);
|
|
@@ -11314,7 +11389,7 @@ function isDistancePreservingMatrix(matrix) {
|
|
|
11314
11389
|
const col0 = [matrix[0], matrix[1], matrix[2]];
|
|
11315
11390
|
const col1 = [matrix[4], matrix[5], matrix[6]];
|
|
11316
11391
|
const col2 = [matrix[8], matrix[9], matrix[10]];
|
|
11317
|
-
const det = dot32(col0,
|
|
11392
|
+
const det = dot32(col0, cross32(col1, col2));
|
|
11318
11393
|
return Math.abs(vectorLength3(col0) - 1) <= 1e-6 && Math.abs(vectorLength3(col1) - 1) <= 1e-6 && Math.abs(vectorLength3(col2) - 1) <= 1e-6 && Math.abs(dot32(col0, col1)) <= 1e-6 && Math.abs(dot32(col0, col2)) <= 1e-6 && Math.abs(dot32(col1, col2)) <= 1e-6 && Math.abs(Math.abs(det) - 1) <= 1e-6;
|
|
11319
11394
|
}
|
|
11320
11395
|
function offsetSolidTransformDistanceScaleForManifold(step) {
|
|
@@ -11486,7 +11561,7 @@ function isStraightMonotonePolylineForManifold(points) {
|
|
|
11486
11561
|
for (const point2 of points) {
|
|
11487
11562
|
const offset2 = subtract3(point2, start);
|
|
11488
11563
|
const projection = dot32(offset2, axis) / axisLengthSq;
|
|
11489
|
-
const lineDistance = vectorLength3(
|
|
11564
|
+
const lineDistance = vectorLength3(cross32(offset2, axis)) / axisLength;
|
|
11490
11565
|
if (lineDistance > 1e-5 || projection < -1e-6 || projection > 1 + 1e-6 || projection + 1e-6 < previousProjection) {
|
|
11491
11566
|
return false;
|
|
11492
11567
|
}
|
|
@@ -11967,11 +12042,11 @@ function lowerVerticalVariableSweepStitchedForManifold(plan, sectionPolygons, wa
|
|
|
11967
12042
|
}
|
|
11968
12043
|
let frameX;
|
|
11969
12044
|
try {
|
|
11970
|
-
frameX = normalizeVec32(
|
|
12045
|
+
frameX = normalizeVec32(cross32(up, tangent), "variableSweep profile x axis");
|
|
11971
12046
|
} catch {
|
|
11972
12047
|
return null;
|
|
11973
12048
|
}
|
|
11974
|
-
const frameY =
|
|
12049
|
+
const frameY = cross32(tangent, frameX);
|
|
11975
12050
|
const span = end[2] - start[2];
|
|
11976
12051
|
const sections = sectionPolygons.map((section) => ({
|
|
11977
12052
|
height: start[2] + span * section.t,
|
|
@@ -12835,7 +12910,7 @@ function extractMeshFromShape(oc, shape, linearDeflection = DEFAULT_LINEAR_DEFLE
|
|
|
12835
12910
|
}
|
|
12836
12911
|
expl.Next();
|
|
12837
12912
|
}
|
|
12838
|
-
const
|
|
12913
|
+
const EPS19 = 1e-8;
|
|
12839
12914
|
const vertProperties = new Float32Array(allPositions);
|
|
12840
12915
|
const triVerts = new Uint32Array(allIndices);
|
|
12841
12916
|
const mergeFrom = [];
|
|
@@ -12845,7 +12920,7 @@ function extractMeshFromShape(oc, shape, linearDeflection = DEFAULT_LINEAR_DEFLE
|
|
|
12845
12920
|
const x = allPositions[i * 3];
|
|
12846
12921
|
const y = allPositions[i * 3 + 1];
|
|
12847
12922
|
const z = allPositions[i * 3 + 2];
|
|
12848
|
-
const key = `${Math.round(x /
|
|
12923
|
+
const key = `${Math.round(x / EPS19)}:${Math.round(y / EPS19)}:${Math.round(z / EPS19)}`;
|
|
12849
12924
|
const existing = vertMap.get(key);
|
|
12850
12925
|
if (existing !== void 0 && existing !== i) {
|
|
12851
12926
|
mergeFrom.push(i);
|
|
@@ -15372,16 +15447,16 @@ function marchingTetrahedra(sdfFn, bounds2, edgeLength2) {
|
|
|
15372
15447
|
}
|
|
15373
15448
|
|
|
15374
15449
|
// src/forge/offsetSolidAnalytic.ts
|
|
15375
|
-
var
|
|
15450
|
+
var EPS6 = 1e-9;
|
|
15376
15451
|
function finitePositive(value) {
|
|
15377
|
-
return Number.isFinite(value) && value >
|
|
15452
|
+
return Number.isFinite(value) && value > EPS6;
|
|
15378
15453
|
}
|
|
15379
15454
|
function clampNonNegative(value) {
|
|
15380
|
-
return Math.abs(value) <=
|
|
15455
|
+
return Math.abs(value) <= EPS6 ? 0 : value;
|
|
15381
15456
|
}
|
|
15382
15457
|
function distancePreservingMatrixScale(matrix) {
|
|
15383
15458
|
if (matrix.length !== 16 || matrix.some((value) => !Number.isFinite(value))) return null;
|
|
15384
|
-
if (Math.abs(matrix[3]) >
|
|
15459
|
+
if (Math.abs(matrix[3]) > EPS6 || Math.abs(matrix[7]) > EPS6 || Math.abs(matrix[11]) > EPS6 || Math.abs(matrix[15] - 1) > EPS6) {
|
|
15385
15460
|
return null;
|
|
15386
15461
|
}
|
|
15387
15462
|
const col0 = [matrix[0], matrix[1], matrix[2]];
|
|
@@ -15393,8 +15468,8 @@ function distancePreservingMatrixScale(matrix) {
|
|
|
15393
15468
|
const sy = length7(col1);
|
|
15394
15469
|
const sz = length7(col2);
|
|
15395
15470
|
if (!finitePositive(sx) || !finitePositive(sy) || !finitePositive(sz)) return null;
|
|
15396
|
-
if (Math.abs(sx - sy) >
|
|
15397
|
-
if (Math.abs(dot12(col0, col1)) >
|
|
15471
|
+
if (Math.abs(sx - sy) > EPS6 || Math.abs(sx - sz) > EPS6) return null;
|
|
15472
|
+
if (Math.abs(dot12(col0, col1)) > EPS6 || Math.abs(dot12(col0, col2)) > EPS6 || Math.abs(dot12(col1, col2)) > EPS6) return null;
|
|
15398
15473
|
return sx;
|
|
15399
15474
|
}
|
|
15400
15475
|
function transformStepDistanceScale(step) {
|
|
@@ -15410,7 +15485,7 @@ function transformStepDistanceScale(step) {
|
|
|
15410
15485
|
const sy = Math.abs(step.y);
|
|
15411
15486
|
const sz = Math.abs(step.z);
|
|
15412
15487
|
if (!finitePositive(sx) || !finitePositive(sy) || !finitePositive(sz)) return null;
|
|
15413
|
-
return Math.abs(sx - sy) <=
|
|
15488
|
+
return Math.abs(sx - sy) <= EPS6 && Math.abs(sx - sz) <= EPS6 ? sx : null;
|
|
15414
15489
|
}
|
|
15415
15490
|
}
|
|
15416
15491
|
}
|
|
@@ -15441,7 +15516,7 @@ function cloneTransformStep(step) {
|
|
|
15441
15516
|
}
|
|
15442
15517
|
}
|
|
15443
15518
|
function translatedPlan(base, z) {
|
|
15444
|
-
if (Math.abs(z) <=
|
|
15519
|
+
if (Math.abs(z) <= EPS6) return base;
|
|
15445
15520
|
return {
|
|
15446
15521
|
kind: "transform",
|
|
15447
15522
|
base,
|
|
@@ -15458,7 +15533,7 @@ function offsetCylinderDimensions(plan, thickness) {
|
|
|
15458
15533
|
const height = zMax - zMin;
|
|
15459
15534
|
const radiusBottom = plan.radius + thickness * (normalScale - slope);
|
|
15460
15535
|
const offsetRadiusTop = radiusTop + thickness * (normalScale + slope);
|
|
15461
|
-
if (!finitePositive(height) || radiusBottom < -
|
|
15536
|
+
if (!finitePositive(height) || radiusBottom < -EPS6 || offsetRadiusTop < -EPS6) return null;
|
|
15462
15537
|
return {
|
|
15463
15538
|
zMin,
|
|
15464
15539
|
height,
|
|
@@ -15482,7 +15557,7 @@ function transformProfilePoint(point2, transform) {
|
|
|
15482
15557
|
const nx = transform.normalX;
|
|
15483
15558
|
const ny = transform.normalY;
|
|
15484
15559
|
const len2 = Math.hypot(nx, ny);
|
|
15485
|
-
if (len2 <=
|
|
15560
|
+
if (len2 <= EPS6) return point2;
|
|
15486
15561
|
const ux = nx / len2;
|
|
15487
15562
|
const uy = ny / len2;
|
|
15488
15563
|
const d = point2[0] * ux + point2[1] * uy;
|
|
@@ -15496,7 +15571,7 @@ function transformProfilePointThrough(point2, transforms) {
|
|
|
15496
15571
|
return out;
|
|
15497
15572
|
}
|
|
15498
15573
|
function sameScalar2(a, b) {
|
|
15499
|
-
return Math.abs(a - b) <=
|
|
15574
|
+
return Math.abs(a - b) <= EPS6;
|
|
15500
15575
|
}
|
|
15501
15576
|
function sameProfilePoint(a, b) {
|
|
15502
15577
|
return sameScalar2(a[0], b[0]) && sameScalar2(a[1], b[1]);
|
|
@@ -15549,7 +15624,7 @@ function rectangleFootprintFromProfile(plan) {
|
|
|
15549
15624
|
const [xMin, xMax] = xs;
|
|
15550
15625
|
const [zMin, zMax] = zs;
|
|
15551
15626
|
if (xMin == null || xMax == null || zMin == null || zMax == null) return null;
|
|
15552
|
-
if (xMin < -
|
|
15627
|
+
if (xMin < -EPS6 || !finitePositive(xMax) || !finitePositive(zMax - zMin)) return null;
|
|
15553
15628
|
const hasCorner = (x, z) => points.some(([px, pz]) => sameScalar2(px, x) && sameScalar2(pz, z));
|
|
15554
15629
|
if (!hasCorner(xMin, zMin) || !hasCorner(xMax, zMin) || !hasCorner(xMax, zMax) || !hasCorner(xMin, zMax)) return null;
|
|
15555
15630
|
return {
|
|
@@ -15572,7 +15647,7 @@ function circleFootprintFromProfile(plan) {
|
|
|
15572
15647
|
const yScale = Math.hypot(yAxis[0], yAxis[1]);
|
|
15573
15648
|
const dot12 = xAxis[0] * yAxis[0] + xAxis[1] * yAxis[1];
|
|
15574
15649
|
if (!finitePositive(xScale) || !finitePositive(yScale)) return null;
|
|
15575
|
-
if (Math.abs(xScale - yScale) >
|
|
15650
|
+
if (Math.abs(xScale - yScale) > EPS6 || Math.abs(dot12) > EPS6 * xScale * yScale) return null;
|
|
15576
15651
|
return {
|
|
15577
15652
|
center,
|
|
15578
15653
|
radius: radius * xScale,
|
|
@@ -15580,11 +15655,11 @@ function circleFootprintFromProfile(plan) {
|
|
|
15580
15655
|
};
|
|
15581
15656
|
}
|
|
15582
15657
|
function fullCircleRevolveTorusPlan(plan, minorRadiusOffset = 0) {
|
|
15583
|
-
if (Math.abs(plan.degrees - 360) >
|
|
15658
|
+
if (Math.abs(plan.degrees - 360) > EPS6) return null;
|
|
15584
15659
|
const circle2 = circleFootprintFromProfile(plan.profile);
|
|
15585
|
-
if (!circle2 || circle2.center[0] <=
|
|
15660
|
+
if (!circle2 || circle2.center[0] <= EPS6) return null;
|
|
15586
15661
|
const minorRadius = circle2.radius + minorRadiusOffset;
|
|
15587
|
-
if (!finitePositive(minorRadius) || minorRadius >= circle2.center[0] -
|
|
15662
|
+
if (!finitePositive(minorRadius) || minorRadius >= circle2.center[0] - EPS6) return null;
|
|
15588
15663
|
return translatedPlan(
|
|
15589
15664
|
{
|
|
15590
15665
|
kind: "torus",
|
|
@@ -15597,7 +15672,7 @@ function fullCircleRevolveTorusPlan(plan, minorRadiusOffset = 0) {
|
|
|
15597
15672
|
}
|
|
15598
15673
|
function fullAxisRectRevolveCylinderPlan(plan) {
|
|
15599
15674
|
const rectangle = fullRectRevolveSurfacePlan(plan);
|
|
15600
|
-
if (!rectangle || rectangle.innerRadius >
|
|
15675
|
+
if (!rectangle || rectangle.innerRadius > EPS6) return null;
|
|
15601
15676
|
return translatedPlan(
|
|
15602
15677
|
{
|
|
15603
15678
|
kind: "cylinder",
|
|
@@ -15620,20 +15695,20 @@ function rectRevolveSurfacePlan(plan) {
|
|
|
15620
15695
|
};
|
|
15621
15696
|
}
|
|
15622
15697
|
function fullRectRevolveSurfacePlan(plan) {
|
|
15623
|
-
if (Math.abs(plan.degrees - 360) >
|
|
15698
|
+
if (Math.abs(plan.degrees - 360) > EPS6) return null;
|
|
15624
15699
|
return rectRevolveSurfacePlan(plan);
|
|
15625
15700
|
}
|
|
15626
15701
|
function offsetFullRectRevolvePlan(plan, thickness) {
|
|
15627
15702
|
const cylinderPlan2 = fullAxisRectRevolveCylinderPlan(plan);
|
|
15628
15703
|
if (cylinderPlan2) return offsetSolidAnalyticPrimitivePlan(cylinderPlan2, thickness);
|
|
15629
15704
|
const rectangle = fullRectRevolveSurfacePlan(plan);
|
|
15630
|
-
if (!rectangle || rectangle.innerRadius <=
|
|
15705
|
+
if (!rectangle || rectangle.innerRadius <= EPS6) return null;
|
|
15631
15706
|
const innerRadius = rectangle.innerRadius - thickness;
|
|
15632
15707
|
const outerRadius = rectangle.outerRadius + thickness;
|
|
15633
15708
|
const height = rectangle.zMax - rectangle.zMin + 2 * thickness;
|
|
15634
|
-
if (innerRadius < -
|
|
15709
|
+
if (innerRadius < -EPS6 || !finitePositive(outerRadius) || !finitePositive(height) || outerRadius <= innerRadius + EPS6) return null;
|
|
15635
15710
|
const zCenter = (rectangle.zMin + rectangle.zMax) / 2;
|
|
15636
|
-
if (innerRadius <=
|
|
15711
|
+
if (innerRadius <= EPS6) {
|
|
15637
15712
|
return translatedPlan(
|
|
15638
15713
|
{
|
|
15639
15714
|
kind: "cylinder",
|
|
@@ -15712,7 +15787,7 @@ function offsetSolidAnalyticPrimitivePlan(base, thickness) {
|
|
|
15712
15787
|
}
|
|
15713
15788
|
case "torus": {
|
|
15714
15789
|
const minorRadius = base.minorRadius + thickness;
|
|
15715
|
-
if (!finitePositive(minorRadius) || minorRadius >= base.majorRadius -
|
|
15790
|
+
if (!finitePositive(minorRadius) || minorRadius >= base.majorRadius - EPS6) return null;
|
|
15716
15791
|
return {
|
|
15717
15792
|
kind: "torus",
|
|
15718
15793
|
majorRadius: base.majorRadius,
|
|
@@ -18985,7 +19060,7 @@ function isStraightMonotonePolyline(points) {
|
|
|
18985
19060
|
for (const point2 of points) {
|
|
18986
19061
|
const offset2 = subtract32(point2, start);
|
|
18987
19062
|
const projection = dot33(offset2, axis) / axisLengthSq;
|
|
18988
|
-
const lineDistance = vectorLength32(
|
|
19063
|
+
const lineDistance = vectorLength32(cross33(offset2, axis)) / axisLength;
|
|
18989
19064
|
if (lineDistance > 1e-5 || projection < -1e-6 || projection > 1 + 1e-6 || projection + 1e-6 < previousProjection) {
|
|
18990
19065
|
return false;
|
|
18991
19066
|
}
|
|
@@ -19061,7 +19136,7 @@ function isDistancePreservingMatrix2(matrix) {
|
|
|
19061
19136
|
const col0 = [matrix[0], matrix[1], matrix[2]];
|
|
19062
19137
|
const col1 = [matrix[4], matrix[5], matrix[6]];
|
|
19063
19138
|
const col2 = [matrix[8], matrix[9], matrix[10]];
|
|
19064
|
-
const det = dot33(col0,
|
|
19139
|
+
const det = dot33(col0, cross33(col1, col2));
|
|
19065
19140
|
return Math.abs(vectorLength32(col0) - 1) <= eps && Math.abs(vectorLength32(col1) - 1) <= eps && Math.abs(vectorLength32(col2) - 1) <= eps && Math.abs(dot33(col0, col1)) <= eps && Math.abs(dot33(col0, col2)) <= eps && Math.abs(dot33(col1, col2)) <= eps && Math.abs(Math.abs(det) - 1) <= eps;
|
|
19066
19141
|
}
|
|
19067
19142
|
function offsetSolidTransformDistanceScale(step) {
|
|
@@ -19397,7 +19472,7 @@ function triangleNormal(vertices, triangle) {
|
|
|
19397
19472
|
const c = vertices[triangle[2]];
|
|
19398
19473
|
const ab = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
|
|
19399
19474
|
const ac = [c[0] - a[0], c[1] - a[1], c[2] - a[2]];
|
|
19400
|
-
const normal =
|
|
19475
|
+
const normal = cross33(ab, ac);
|
|
19401
19476
|
const len2 = Math.hypot(normal[0], normal[1], normal[2]);
|
|
19402
19477
|
if (len2 <= 1e-12) return null;
|
|
19403
19478
|
return [normal[0] / len2, normal[1] / len2, normal[2] / len2];
|
|
@@ -19511,7 +19586,7 @@ function lowerSurfaceThickenPlan2(plan) {
|
|
|
19511
19586
|
if (!grid) truckUnsupported("surface thickening for this surface source");
|
|
19512
19587
|
return lowerThickenedSurfaceGrid(grid, plan.thickness);
|
|
19513
19588
|
}
|
|
19514
|
-
function
|
|
19589
|
+
function cross33(a, b) {
|
|
19515
19590
|
return [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]];
|
|
19516
19591
|
}
|
|
19517
19592
|
function fromSlicesPlaneFrame(normalInput) {
|
|
@@ -19527,8 +19602,8 @@ function fromSlicesPlaneFrame(normalInput) {
|
|
|
19527
19602
|
return { u: [0, 1, 0], v: [0, 0, nx > 0 ? 1 : -1], normal };
|
|
19528
19603
|
}
|
|
19529
19604
|
const reference = Math.abs(nx) < 0.9 ? [1, 0, 0] : [0, 1, 0];
|
|
19530
|
-
const u = normalizedVector3(
|
|
19531
|
-
return { u, v:
|
|
19605
|
+
const u = normalizedVector3(cross33(reference, normal), "Shape.fromSlices profile u axis");
|
|
19606
|
+
return { u, v: cross33(normal, u), normal };
|
|
19532
19607
|
}
|
|
19533
19608
|
function fromSlicesLocalToWorldMatrix(normal) {
|
|
19534
19609
|
const frame = fromSlicesPlaneFrame(normal);
|
|
@@ -23835,14 +23910,14 @@ function normalize2d(vec) {
|
|
|
23835
23910
|
if (len2 < 1e-12) return [1, 0];
|
|
23836
23911
|
return [vec[0] / len2, vec[1] / len2];
|
|
23837
23912
|
}
|
|
23838
|
-
function
|
|
23913
|
+
function cross34(a, b) {
|
|
23839
23914
|
return [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]];
|
|
23840
23915
|
}
|
|
23841
23916
|
function orthonormalBasisFromNormal(normal) {
|
|
23842
23917
|
const n = normalizeAxis(normal);
|
|
23843
23918
|
const seed = Math.abs(n[2]) < 0.9 ? [0, 0, 1] : [0, 1, 0];
|
|
23844
|
-
const u = normalizeAxis(
|
|
23845
|
-
const v = normalizeAxis(
|
|
23919
|
+
const u = normalizeAxis(cross34(seed, n));
|
|
23920
|
+
const v = normalizeAxis(cross34(n, u));
|
|
23846
23921
|
return { u, v };
|
|
23847
23922
|
}
|
|
23848
23923
|
function faceFrom2DEdge(name, start, end, zMid, ownerQuery) {
|
|
@@ -24718,7 +24793,7 @@ function resolveShapeFaceTableInternal(plan, owner) {
|
|
|
24718
24793
|
forwardStart3[1] - reverseStart3[1],
|
|
24719
24794
|
forwardStart3[2] - reverseStart3[2]
|
|
24720
24795
|
]);
|
|
24721
|
-
const normal = normalizeAxis(
|
|
24796
|
+
const normal = normalizeAxis(cross34(edgeVec, depthVec));
|
|
24722
24797
|
registerFace(table, {
|
|
24723
24798
|
name: wall.name,
|
|
24724
24799
|
normal,
|
|
@@ -25241,7 +25316,7 @@ function normalize32(v) {
|
|
|
25241
25316
|
function dot34(a, b) {
|
|
25242
25317
|
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
|
25243
25318
|
}
|
|
25244
|
-
function
|
|
25319
|
+
function cross35(a, b) {
|
|
25245
25320
|
return [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]];
|
|
25246
25321
|
}
|
|
25247
25322
|
function sub32(a, b) {
|
|
@@ -25254,7 +25329,7 @@ function perpendicularTo(axis) {
|
|
|
25254
25329
|
const absX = Math.abs(axis[0]);
|
|
25255
25330
|
const absZ = Math.abs(axis[2]);
|
|
25256
25331
|
const seed = absX < absZ ? [1, 0, 0] : [0, 0, 1];
|
|
25257
|
-
return normalize32(
|
|
25332
|
+
return normalize32(cross35(axis, seed));
|
|
25258
25333
|
}
|
|
25259
25334
|
function normalizePortInput(input) {
|
|
25260
25335
|
let origin;
|
|
@@ -25404,7 +25479,7 @@ function computeConnectFrame(childBase, childPort, parentPort, _flip, childAlign
|
|
|
25404
25479
|
const cI = childBase.point(childAlignPt);
|
|
25405
25480
|
const cAxis = normalize32(childBase.vector(childPort.axis));
|
|
25406
25481
|
const cUp = normalize32(childBase.vector(childPort.up));
|
|
25407
|
-
const cRight = normalize32(
|
|
25482
|
+
const cRight = normalize32(cross35(cAxis, cUp));
|
|
25408
25483
|
const pOrigin = parentAlignPt;
|
|
25409
25484
|
const jointKind = childPort.kind ?? parentPort.kind;
|
|
25410
25485
|
const faceToFace = jointKind !== "prismatic";
|
|
@@ -25413,7 +25488,7 @@ function computeConnectFrame(childBase, childPort, parentPort, _flip, childAlign
|
|
|
25413
25488
|
if (faceToFace && dot34(cUp, pUp) < 0) {
|
|
25414
25489
|
pUp = negate3(pUp);
|
|
25415
25490
|
}
|
|
25416
|
-
const pRight = normalize32(
|
|
25491
|
+
const pRight = normalize32(cross35(pAxis, pUp));
|
|
25417
25492
|
const r00 = pRight[0] * cRight[0] + pUp[0] * cUp[0] + pAxis[0] * cAxis[0];
|
|
25418
25493
|
const r01 = pRight[0] * cRight[1] + pUp[0] * cUp[1] + pAxis[0] * cAxis[1];
|
|
25419
25494
|
const r02 = pRight[0] * cRight[2] + pUp[0] * cUp[2] + pAxis[0] * cAxis[2];
|
|
@@ -25523,7 +25598,7 @@ function normalize33(v) {
|
|
|
25523
25598
|
if (l < 1e-10) throw new Error("Cannot normalize zero-length vector");
|
|
25524
25599
|
return [v[0] / l, v[1] / l, v[2] / l];
|
|
25525
25600
|
}
|
|
25526
|
-
function
|
|
25601
|
+
function cross36(a, b) {
|
|
25527
25602
|
return [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]];
|
|
25528
25603
|
}
|
|
25529
25604
|
function sub33(a, b) {
|
|
@@ -25533,8 +25608,8 @@ function negate32(v) {
|
|
|
25533
25608
|
return [-v[0], -v[1], -v[2]];
|
|
25534
25609
|
}
|
|
25535
25610
|
function alignmentMatrix(childOrigin, childAxis, childUp, parentOrigin, parentAxis, parentUp) {
|
|
25536
|
-
const cRight = normalize33(
|
|
25537
|
-
const pRight = normalize33(
|
|
25611
|
+
const cRight = normalize33(cross36(childAxis, childUp));
|
|
25612
|
+
const pRight = normalize33(cross36(parentAxis, parentUp));
|
|
25538
25613
|
const r00 = pRight[0] * cRight[0] + parentUp[0] * childUp[0] + parentAxis[0] * childAxis[0];
|
|
25539
25614
|
const r01 = pRight[0] * cRight[1] + parentUp[0] * childUp[1] + parentAxis[0] * childAxis[1];
|
|
25540
25615
|
const r02 = pRight[0] * cRight[2] + parentUp[0] * childUp[2] + parentAxis[0] * childAxis[2];
|
|
@@ -34501,11 +34576,11 @@ function getLastRustProfile() {
|
|
|
34501
34576
|
}
|
|
34502
34577
|
|
|
34503
34578
|
// src/forge/constraints3d/rodrigues.ts
|
|
34504
|
-
var
|
|
34579
|
+
var EPS7 = 1e-10;
|
|
34505
34580
|
function rodrigues(rv) {
|
|
34506
34581
|
const [rx, ry, rz] = rv;
|
|
34507
34582
|
const theta = Math.sqrt(rx * rx + ry * ry + rz * rz);
|
|
34508
|
-
if (theta <
|
|
34583
|
+
if (theta < EPS7) {
|
|
34509
34584
|
return [1, -rz, ry, rz, 1, -rx, -ry, rx, 1];
|
|
34510
34585
|
}
|
|
34511
34586
|
const c = Math.cos(theta);
|
|
@@ -34540,7 +34615,7 @@ function transformDir(rv, dir) {
|
|
|
34540
34615
|
function dot36(a, b) {
|
|
34541
34616
|
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
|
34542
34617
|
}
|
|
34543
|
-
function
|
|
34618
|
+
function cross37(a, b) {
|
|
34544
34619
|
return [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]];
|
|
34545
34620
|
}
|
|
34546
34621
|
function sub34(a, b) {
|
|
@@ -34551,7 +34626,7 @@ function len33(v) {
|
|
|
34551
34626
|
}
|
|
34552
34627
|
function normalize34(v) {
|
|
34553
34628
|
const l = len33(v);
|
|
34554
|
-
if (l <
|
|
34629
|
+
if (l < EPS7) return [0, 0, 0];
|
|
34555
34630
|
return [v[0] / l, v[1] / l, v[2] / l];
|
|
34556
34631
|
}
|
|
34557
34632
|
|
|
@@ -34595,7 +34670,7 @@ var axisParallelDef = {
|
|
|
34595
34670
|
residual(constraint, ctx) {
|
|
34596
34671
|
const axisA = ctx.worldAxis(constraint.refA.bodyId, constraint.refA.featureName);
|
|
34597
34672
|
const axisB = ctx.worldAxis(constraint.refB.bodyId, constraint.refB.featureName);
|
|
34598
|
-
const c =
|
|
34673
|
+
const c = cross37(axisA.direction, axisB.direction);
|
|
34599
34674
|
const ax = Math.abs(c[0]);
|
|
34600
34675
|
const ay = Math.abs(c[1]);
|
|
34601
34676
|
const az = Math.abs(c[2]);
|
|
@@ -34612,9 +34687,9 @@ var concentricDef = {
|
|
|
34612
34687
|
residual(constraint, ctx) {
|
|
34613
34688
|
const axisA = ctx.worldAxis(constraint.refA.bodyId, constraint.refA.featureName);
|
|
34614
34689
|
const axisB = ctx.worldAxis(constraint.refB.bodyId, constraint.refB.featureName);
|
|
34615
|
-
const dirCross =
|
|
34690
|
+
const dirCross = cross37(axisA.direction, axisB.direction);
|
|
34616
34691
|
const delta = sub34(axisB.origin, axisA.origin);
|
|
34617
|
-
const offsetCross =
|
|
34692
|
+
const offsetCross = cross37(delta, axisA.direction);
|
|
34618
34693
|
const pickTwo = (v) => {
|
|
34619
34694
|
const ax = Math.abs(v[0]);
|
|
34620
34695
|
const ay = Math.abs(v[1]);
|
|
@@ -34641,7 +34716,7 @@ var faceDistanceDef = {
|
|
|
34641
34716
|
const n2 = faceB.normal;
|
|
34642
34717
|
const delta = sub34(faceB.center, faceA.center);
|
|
34643
34718
|
const antiParallel = dot36(n1, n2) + 1;
|
|
34644
|
-
const crossMag = len33(
|
|
34719
|
+
const crossMag = len33(cross37(n1, n2));
|
|
34645
34720
|
const signedDist = dot36(delta, n1) - distance5;
|
|
34646
34721
|
return [antiParallel, crossMag, signedDist];
|
|
34647
34722
|
}
|
|
@@ -34683,7 +34758,7 @@ var parallelDef = {
|
|
|
34683
34758
|
residual(constraint, ctx) {
|
|
34684
34759
|
const faceA = ctx.worldFace(constraint.refA.bodyId, constraint.refA.featureName);
|
|
34685
34760
|
const faceB = ctx.worldFace(constraint.refB.bodyId, constraint.refB.featureName);
|
|
34686
|
-
const c =
|
|
34761
|
+
const c = cross37(faceA.normal, faceB.normal);
|
|
34687
34762
|
const ax = Math.abs(c[0]);
|
|
34688
34763
|
const ay = Math.abs(c[1]);
|
|
34689
34764
|
const az = Math.abs(c[2]);
|
|
@@ -34712,7 +34787,7 @@ var pointOnAxisDef = {
|
|
|
34712
34787
|
const point2 = ctx.worldPoint(constraint.refA.bodyId, constraint.refA.featureName);
|
|
34713
34788
|
const axis = ctx.worldAxis(constraint.refB.bodyId, constraint.refB.featureName);
|
|
34714
34789
|
const delta = sub34(point2, axis.origin);
|
|
34715
|
-
const c =
|
|
34790
|
+
const c = cross37(delta, axis.direction);
|
|
34716
34791
|
const ax = Math.abs(c[0]);
|
|
34717
34792
|
const ay = Math.abs(c[1]);
|
|
34718
34793
|
const az = Math.abs(c[2]);
|
|
@@ -40631,10 +40706,8 @@ var PathBuilder = class {
|
|
|
40631
40706
|
if (radius <= 0) throw new Error("fillet: radius must be positive");
|
|
40632
40707
|
const n = this.segs.length;
|
|
40633
40708
|
if (n < 2) throw new Error("fillet: need at least 2 segments before a fillet");
|
|
40634
|
-
const prev = this.segs[n - 2];
|
|
40635
40709
|
const curr = this.segs[n - 1];
|
|
40636
|
-
const
|
|
40637
|
-
const { trimA, trimB, arcSeg } = this.computeFilletGeom(radius);
|
|
40710
|
+
const { trimA, arcSeg } = this.computeFilletGeom(radius);
|
|
40638
40711
|
if (!arcSeg) throw new Error("fillet: cannot fillet these segments (parallel or degenerate)");
|
|
40639
40712
|
this.trimLastSegEnd(n - 2, trimA[0], trimA[1]);
|
|
40640
40713
|
const trimmedSeg = { ...curr };
|
|
@@ -40707,7 +40780,6 @@ var PathBuilder = class {
|
|
|
40707
40780
|
}
|
|
40708
40781
|
getSegDirAt(seg, which) {
|
|
40709
40782
|
if (seg.kind === "line" || seg.kind === "move") {
|
|
40710
|
-
const n = this.segs.length;
|
|
40711
40783
|
const idx = this.segs.indexOf(seg);
|
|
40712
40784
|
if (seg.kind === "line") {
|
|
40713
40785
|
let sx, sy;
|
|
@@ -40951,6 +41023,41 @@ var PathBuilder = class {
|
|
|
40951
41023
|
}
|
|
40952
41024
|
return pts;
|
|
40953
41025
|
}
|
|
41026
|
+
/**
|
|
41027
|
+
* Return the open path as a sampled 2D polyline.
|
|
41028
|
+
*
|
|
41029
|
+
* This is for construction geometry such as guide rails, measured centerlines,
|
|
41030
|
+
* and curve-driven helpers where the authored path should stay open instead of
|
|
41031
|
+
* becoming a filled sketch or stroked profile.
|
|
41032
|
+
*
|
|
41033
|
+
* **Example**
|
|
41034
|
+
*
|
|
41035
|
+
* ```ts
|
|
41036
|
+
* const rail = path()
|
|
41037
|
+
* .moveTo(24, 0)
|
|
41038
|
+
* .bezierTo(32, 44, 28, 92, 18, 120)
|
|
41039
|
+
* .toPolyline();
|
|
41040
|
+
* ```
|
|
41041
|
+
*
|
|
41042
|
+
* @returns A sampled open polyline.
|
|
41043
|
+
* @category Path Builder
|
|
41044
|
+
*/
|
|
41045
|
+
toPolyline() {
|
|
41046
|
+
const moveCount = this.segs.filter((seg) => seg.kind === "move").length;
|
|
41047
|
+
if (moveCount > 1) {
|
|
41048
|
+
throw new Error("path().toPolyline() supports one continuous open path. Use separate path() builders for separate rails.");
|
|
41049
|
+
}
|
|
41050
|
+
const pts = [];
|
|
41051
|
+
for (const point2 of this.tessellate()) {
|
|
41052
|
+
if (!point2.every(Number.isFinite)) throw new Error("path().toPolyline() produced a non-finite point");
|
|
41053
|
+
const previous = pts[pts.length - 1];
|
|
41054
|
+
if (!previous || Math.hypot(point2[0] - previous[0], point2[1] - previous[1]) > 1e-9) {
|
|
41055
|
+
pts.push(point2);
|
|
41056
|
+
}
|
|
41057
|
+
}
|
|
41058
|
+
if (pts.length < 2) throw new Error("path().toPolyline() needs at least 2 points");
|
|
41059
|
+
return pts;
|
|
41060
|
+
}
|
|
40954
41061
|
// ── Output ────────────────────────────────────────────────────────────────
|
|
40955
41062
|
/**
|
|
40956
41063
|
* Close the path and return a filled `Sketch`.
|
|
@@ -42332,7 +42439,7 @@ function elbow(pipeRadius, bendRadius, angle, options) {
|
|
|
42332
42439
|
}
|
|
42333
42440
|
|
|
42334
42441
|
// src/forge/lib/belts.ts
|
|
42335
|
-
var
|
|
42442
|
+
var EPS8 = 1e-9;
|
|
42336
42443
|
function assertFinitePositive(apiName, name, value) {
|
|
42337
42444
|
if (!Number.isFinite(value) || value <= 0) {
|
|
42338
42445
|
throw new Error(`${apiName}: "${name}" must be a positive finite number.`);
|
|
@@ -42358,7 +42465,7 @@ function dist(a, b) {
|
|
|
42358
42465
|
}
|
|
42359
42466
|
function norm(v) {
|
|
42360
42467
|
const l = len(v);
|
|
42361
|
-
if (l <
|
|
42468
|
+
if (l < EPS8) throw new Error("beltDrive: coincident tangent points produced a zero-length direction.");
|
|
42362
42469
|
return [v[0] / l, v[1] / l];
|
|
42363
42470
|
}
|
|
42364
42471
|
function angleOf(center, point2) {
|
|
@@ -42424,13 +42531,13 @@ function normalizePulleyAsCircle(pulley, index, radiusOverride) {
|
|
|
42424
42531
|
function commonTangents(a, b, mode) {
|
|
42425
42532
|
const delta = sub5(b.center, a.center);
|
|
42426
42533
|
const z = dot6(delta, delta);
|
|
42427
|
-
if (z <
|
|
42534
|
+
if (z < EPS8) {
|
|
42428
42535
|
throw new Error(`beltDrive: pulleys "${a.name}" and "${b.name}" have the same center.`);
|
|
42429
42536
|
}
|
|
42430
42537
|
const signedA = mode === "open" ? 1 : -1;
|
|
42431
42538
|
const r = signedA * a.radius - b.radius;
|
|
42432
42539
|
const h2 = z - r * r;
|
|
42433
|
-
if (h2 <=
|
|
42540
|
+
if (h2 <= EPS8) {
|
|
42434
42541
|
const centerDistance = Math.sqrt(z);
|
|
42435
42542
|
if (mode === "open") {
|
|
42436
42543
|
throw new Error(
|
|
@@ -42506,7 +42613,7 @@ function buildTwoPulleySegments(circles, mode) {
|
|
|
42506
42613
|
const [t0, t1] = commonTangents(a, b, mode);
|
|
42507
42614
|
const candidateA = buildSegmentsForTangentOrder(a, b, t0, t1);
|
|
42508
42615
|
const candidateB = buildSegmentsForTangentOrder(a, b, t1, t0);
|
|
42509
|
-
if (mode !== "open" || Math.abs(a.radius - b.radius) <
|
|
42616
|
+
if (mode !== "open" || Math.abs(a.radius - b.radius) < EPS8) return candidateA;
|
|
42510
42617
|
const larger = a.radius > b.radius ? a.name : b.name;
|
|
42511
42618
|
const smaller = a.radius > b.radius ? b.name : a.name;
|
|
42512
42619
|
const score = (segments) => {
|
|
@@ -58559,13 +58666,13 @@ function variableSweep(spine, sections, options = {}) {
|
|
|
58559
58666
|
|
|
58560
58667
|
// src/forge/productSkin.ts
|
|
58561
58668
|
var AXIS_INDEX = { X: 0, Y: 1, Z: 2 };
|
|
58562
|
-
var
|
|
58669
|
+
var EPS9 = 1e-8;
|
|
58563
58670
|
function clamp6(value, min2, max4) {
|
|
58564
58671
|
return Math.max(min2, Math.min(max4, value));
|
|
58565
58672
|
}
|
|
58566
58673
|
function norm2(v) {
|
|
58567
58674
|
const length7 = Math.hypot(v[0], v[1], v[2]);
|
|
58568
|
-
if (length7 <
|
|
58675
|
+
if (length7 < EPS9) return [0, 0, 1];
|
|
58569
58676
|
return [v[0] / length7, v[1] / length7, v[2] / length7];
|
|
58570
58677
|
}
|
|
58571
58678
|
function cross7(a, b) {
|
|
@@ -58628,12 +58735,12 @@ function superEllipsePoint(rx, ry, exponent, angle) {
|
|
|
58628
58735
|
const sin5 = Math.sin(angle);
|
|
58629
58736
|
const x = rx * Math.sign(cos5) * Math.abs(cos5) ** (2 / exponent);
|
|
58630
58737
|
const y = ry * Math.sign(sin5) * Math.abs(sin5) ** (2 / exponent);
|
|
58631
|
-
const nx = Math.sign(x) * Math.abs(x / Math.max(rx,
|
|
58632
|
-
const ny = Math.sign(y) * Math.abs(y / Math.max(ry,
|
|
58738
|
+
const nx = Math.sign(x) * Math.abs(x / Math.max(rx, EPS9)) ** Math.max(exponent - 1, 1e-3);
|
|
58739
|
+
const ny = Math.sign(y) * Math.abs(y / Math.max(ry, EPS9)) ** Math.max(exponent - 1, 1e-3);
|
|
58633
58740
|
const nLen = Math.hypot(nx, ny);
|
|
58634
58741
|
return {
|
|
58635
58742
|
point: [x, y],
|
|
58636
|
-
normal: nLen <
|
|
58743
|
+
normal: nLen < EPS9 ? [Math.sign(cos5), Math.sign(sin5)] : [nx / nLen, ny / nLen]
|
|
58637
58744
|
};
|
|
58638
58745
|
}
|
|
58639
58746
|
function angleForSide(side, u) {
|
|
@@ -58645,9 +58752,9 @@ function angleForSide(side, u) {
|
|
|
58645
58752
|
return null;
|
|
58646
58753
|
}
|
|
58647
58754
|
function sideSpan(side, width, depth) {
|
|
58648
|
-
if (side === "left" || side === "right") return Math.max(depth,
|
|
58649
|
-
if (side === "top" || side === "bottom") return Math.max(width,
|
|
58650
|
-
return Math.max(width, depth,
|
|
58755
|
+
if (side === "left" || side === "right") return Math.max(depth, EPS9);
|
|
58756
|
+
if (side === "top" || side === "bottom") return Math.max(width, EPS9);
|
|
58757
|
+
return Math.max(width, depth, EPS9);
|
|
58651
58758
|
}
|
|
58652
58759
|
function interpolateQuery(a, b, t) {
|
|
58653
58760
|
const sideA = normalizedSide(a.side);
|
|
@@ -58793,8 +58900,8 @@ var ProductSkin = class {
|
|
|
58793
58900
|
const b = sorted[index + 1];
|
|
58794
58901
|
const aAxis = axisPosition(this.axis, a.center);
|
|
58795
58902
|
const bAxis = axisPosition(this.axis, b.center);
|
|
58796
|
-
if (axisValue < aAxis -
|
|
58797
|
-
const span = Math.max(
|
|
58903
|
+
if (axisValue < aAxis - EPS9 || axisValue > bAxis + EPS9) continue;
|
|
58904
|
+
const span = Math.max(EPS9, bAxis - aAxis);
|
|
58798
58905
|
const t = clamp6((axisValue - aAxis) / span, 0, 1);
|
|
58799
58906
|
return {
|
|
58800
58907
|
axisValue,
|
|
@@ -59432,7 +59539,7 @@ var ProductRibbonBuilder = class {
|
|
|
59432
59539
|
const rawU = (center.u ?? 0.5) + across * this.widthValue / span;
|
|
59433
59540
|
const u = clamp6(rawU, 0, 1);
|
|
59434
59541
|
const clampDistance = Math.abs(rawU - u) * span;
|
|
59435
|
-
if (clampDistance >
|
|
59542
|
+
if (clampDistance > EPS9) {
|
|
59436
59543
|
clampedUCount += 1;
|
|
59437
59544
|
maxUClampDistance = Math.max(maxUClampDistance, clampDistance);
|
|
59438
59545
|
}
|
|
@@ -59625,7 +59732,7 @@ var Product = {
|
|
|
59625
59732
|
};
|
|
59626
59733
|
|
|
59627
59734
|
// src/forge/surface-members/math.ts
|
|
59628
|
-
var
|
|
59735
|
+
var EPS10 = 1e-8;
|
|
59629
59736
|
function requireFinite6(value, label) {
|
|
59630
59737
|
if (!Number.isFinite(value)) throw new Error(`${label} must be a finite number`);
|
|
59631
59738
|
return value;
|
|
@@ -59661,7 +59768,7 @@ function length5(v) {
|
|
|
59661
59768
|
}
|
|
59662
59769
|
function norm3(v, fallback = [0, 0, 1]) {
|
|
59663
59770
|
const len2 = length5(v);
|
|
59664
|
-
if (len2 <
|
|
59771
|
+
if (len2 < EPS10) return [...fallback];
|
|
59665
59772
|
return [v[0] / len2, v[1] / len2, v[2] / len2];
|
|
59666
59773
|
}
|
|
59667
59774
|
function distance(a, b) {
|
|
@@ -60033,10 +60140,10 @@ function coordinateOnSide(coordinate, side, label) {
|
|
|
60033
60140
|
return { ...coordinate, kind: "productSkin", side };
|
|
60034
60141
|
}
|
|
60035
60142
|
var ProductSkinCarrier = class _ProductSkinCarrier {
|
|
60036
|
-
constructor(skin, name = skin.name,
|
|
60143
|
+
constructor(skin, name = skin.name, sideValue2, offsetValue = 0) {
|
|
60037
60144
|
this.skin = skin;
|
|
60038
60145
|
this.name = name;
|
|
60039
|
-
this.sideValue =
|
|
60146
|
+
this.sideValue = sideValue2;
|
|
60040
60147
|
this.offsetValue = offsetValue;
|
|
60041
60148
|
}
|
|
60042
60149
|
kind = "productSkin";
|
|
@@ -62876,7 +62983,7 @@ function mul3(a, s) {
|
|
|
62876
62983
|
function dot38(a, b) {
|
|
62877
62984
|
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
|
62878
62985
|
}
|
|
62879
|
-
function
|
|
62986
|
+
function cross38(a, b) {
|
|
62880
62987
|
return [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]];
|
|
62881
62988
|
}
|
|
62882
62989
|
function distance3(a, b) {
|
|
@@ -63105,7 +63212,7 @@ function collectShapeTriangles(shape) {
|
|
|
63105
63212
|
mesh.vertProperties[i2 * numProp + 1],
|
|
63106
63213
|
mesh.vertProperties[i2 * numProp + 2]
|
|
63107
63214
|
];
|
|
63108
|
-
const n = norm4(
|
|
63215
|
+
const n = norm4(cross38(sub35(b, a), sub35(c, a)));
|
|
63109
63216
|
tris.push({ a, b, c, normal: n });
|
|
63110
63217
|
}
|
|
63111
63218
|
return tris;
|
|
@@ -63175,7 +63282,7 @@ function summarizeMetricSeries(values) {
|
|
|
63175
63282
|
return [signatureNumber(sum2), signatureNumber(sumSquares), signatureNumber(min2), signatureNumber(max4)].join(":");
|
|
63176
63283
|
}
|
|
63177
63284
|
function triangleArea(triangle) {
|
|
63178
|
-
const c =
|
|
63285
|
+
const c = cross38(sub35(triangle.b, triangle.a), sub35(triangle.c, triangle.a));
|
|
63179
63286
|
return Math.hypot(c[0], c[1], c[2]) * 0.5;
|
|
63180
63287
|
}
|
|
63181
63288
|
function makeComponentPageSignature(object) {
|
|
@@ -63290,8 +63397,8 @@ function makeViewFrame(view) {
|
|
|
63290
63397
|
};
|
|
63291
63398
|
const c = cfg[view];
|
|
63292
63399
|
const forward = norm4(mul3(c.camDir, -1));
|
|
63293
|
-
const right = norm4(
|
|
63294
|
-
const up = norm4(
|
|
63400
|
+
const right = norm4(cross38(forward, c.up));
|
|
63401
|
+
const up = norm4(cross38(right, forward));
|
|
63295
63402
|
return { id: view, label: c.label, right, up, forward };
|
|
63296
63403
|
}
|
|
63297
63404
|
function isDimensionVisibleInView(dim2, frame, toleranceDeg) {
|
|
@@ -63335,9 +63442,9 @@ function pickDimensionOffsetBasis(dirModel, frame) {
|
|
|
63335
63442
|
pushCandidate(axisPerp);
|
|
63336
63443
|
});
|
|
63337
63444
|
if (candidates.length === 0) {
|
|
63338
|
-
pushCandidate(
|
|
63339
|
-
pushCandidate(
|
|
63340
|
-
pushCandidate(
|
|
63445
|
+
pushCandidate(cross38(dirModel, frame.forward));
|
|
63446
|
+
pushCandidate(cross38(dirModel, frame.up));
|
|
63447
|
+
pushCandidate(cross38(dirModel, frame.right));
|
|
63341
63448
|
}
|
|
63342
63449
|
if (candidates.length === 0) {
|
|
63343
63450
|
return { dir3: [0, 0, 1], proj: [0, 1], projDir: [0, 1], projLen: 1 };
|
|
@@ -65750,7 +65857,7 @@ function offsetSolid(shape, thickness) {
|
|
|
65750
65857
|
}
|
|
65751
65858
|
|
|
65752
65859
|
// src/forge/projectionCompile.ts
|
|
65753
|
-
var
|
|
65860
|
+
var EPS11 = 1e-6;
|
|
65754
65861
|
var FROM_SLICES_SINGLE_SLICE_HALF_EXTENT = 500;
|
|
65755
65862
|
function dot9(a, b) {
|
|
65756
65863
|
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
|
@@ -65772,7 +65879,7 @@ function normalize8(vec) {
|
|
|
65772
65879
|
if (len2 < 1e-12) return [0, 0, 1];
|
|
65773
65880
|
return [vec[0] / len2, vec[1] / len2, vec[2] / len2];
|
|
65774
65881
|
}
|
|
65775
|
-
function nearlyEqual(a, b, eps =
|
|
65882
|
+
function nearlyEqual(a, b, eps = EPS11) {
|
|
65776
65883
|
return Math.abs(a - b) <= eps;
|
|
65777
65884
|
}
|
|
65778
65885
|
function projectionReplayFailure(sourceShape, plane, sourcePlacement, reason, targetFaceQuery) {
|
|
@@ -65898,7 +66005,7 @@ function mapProfileToPlane(profile, sourcePlacement, targetPlane) {
|
|
|
65898
66005
|
const d = dot9(sourceV, targetPlane.v);
|
|
65899
66006
|
const sx = length2d(a, c);
|
|
65900
66007
|
const sy = length2d(b, d);
|
|
65901
|
-
if (sx <
|
|
66008
|
+
if (sx < EPS11 || sy < EPS11) {
|
|
65902
66009
|
return { reason: "projection replay requires a non-degenerate in-plane basis." };
|
|
65903
66010
|
}
|
|
65904
66011
|
const shear = a * b + c * d;
|
|
@@ -65951,8 +66058,8 @@ function circleProjectionProfile(radius, segments) {
|
|
|
65951
66058
|
function annulusProjectionProfile(outerRadius, innerRadius, segments) {
|
|
65952
66059
|
const outer = Math.abs(outerRadius);
|
|
65953
66060
|
const inner = Math.max(0, Math.abs(innerRadius));
|
|
65954
|
-
if (outer <=
|
|
65955
|
-
if (inner <=
|
|
66061
|
+
if (outer <= EPS11 || inner >= outer - EPS11) return null;
|
|
66062
|
+
if (inner <= EPS11) {
|
|
65956
66063
|
return circleProjectionProfile(outer, segments);
|
|
65957
66064
|
}
|
|
65958
66065
|
return buildBooleanProfileCompilePlan("difference", [circleProjectionProfile(outer, segments), circleProjectionProfile(inner, segments)]);
|
|
@@ -66009,7 +66116,7 @@ function convexHull2d(points) {
|
|
|
66009
66116
|
const unique = [];
|
|
66010
66117
|
for (const point2 of points) {
|
|
66011
66118
|
if (!Number.isFinite(point2[0]) || !Number.isFinite(point2[1])) return null;
|
|
66012
|
-
if (!unique.some((existing) => pointDistance2d(existing, point2) <=
|
|
66119
|
+
if (!unique.some((existing) => pointDistance2d(existing, point2) <= EPS11)) {
|
|
66013
66120
|
unique.push(point2);
|
|
66014
66121
|
}
|
|
66015
66122
|
}
|
|
@@ -66018,7 +66125,7 @@ function convexHull2d(points) {
|
|
|
66018
66125
|
const cross13 = (origin, a, b) => (a[0] - origin[0]) * (b[1] - origin[1]) - (a[1] - origin[1]) * (b[0] - origin[0]);
|
|
66019
66126
|
const lower2 = [];
|
|
66020
66127
|
for (const point2 of unique) {
|
|
66021
|
-
while (lower2.length >= 2 && cross13(lower2[lower2.length - 2], lower2[lower2.length - 1], point2) <=
|
|
66128
|
+
while (lower2.length >= 2 && cross13(lower2[lower2.length - 2], lower2[lower2.length - 1], point2) <= EPS11) {
|
|
66022
66129
|
lower2.pop();
|
|
66023
66130
|
}
|
|
66024
66131
|
lower2.push(point2);
|
|
@@ -66026,7 +66133,7 @@ function convexHull2d(points) {
|
|
|
66026
66133
|
const upper = [];
|
|
66027
66134
|
for (let idx = unique.length - 1; idx >= 0; idx -= 1) {
|
|
66028
66135
|
const point2 = unique[idx];
|
|
66029
|
-
while (upper.length >= 2 && cross13(upper[upper.length - 2], upper[upper.length - 1], point2) <=
|
|
66136
|
+
while (upper.length >= 2 && cross13(upper[upper.length - 2], upper[upper.length - 1], point2) <= EPS11) {
|
|
66030
66137
|
upper.pop();
|
|
66031
66138
|
}
|
|
66032
66139
|
upper.push(point2);
|
|
@@ -66060,7 +66167,7 @@ function transformedBoxProjectionReplayProfile(sourceShape, targetPlane) {
|
|
|
66060
66167
|
const halfY = Math.abs(input.box.y) / 2;
|
|
66061
66168
|
const zMin = Math.min(0, input.box.z);
|
|
66062
66169
|
const zMax = Math.max(0, input.box.z);
|
|
66063
|
-
if (halfX <=
|
|
66170
|
+
if (halfX <= EPS11 || halfY <= EPS11 || zMax - zMin <= EPS11) return null;
|
|
66064
66171
|
const projected = [-halfX, halfX].flatMap(
|
|
66065
66172
|
(x) => [-halfY, halfY].flatMap(
|
|
66066
66173
|
(y) => [zMin, zMax].map((z) => {
|
|
@@ -66097,10 +66204,10 @@ function transformedCylinderSideProjectionReplayProfile(sourceShape, targetPlane
|
|
|
66097
66204
|
const axis = normalize8(input.transform.vector([0, 0, 1]));
|
|
66098
66205
|
if (!nearlyEqual(dot9(axis, targetPlane.normal), 0, 1e-5)) return null;
|
|
66099
66206
|
const radiusDirection = normalize8(cross9(targetPlane.normal, axis));
|
|
66100
|
-
if (Math.hypot(radiusDirection[0], radiusDirection[1], radiusDirection[2]) <=
|
|
66207
|
+
if (Math.hypot(radiusDirection[0], radiusDirection[1], radiusDirection[2]) <= EPS11) return null;
|
|
66101
66208
|
const bottomRadius = Math.abs(input.cylinder.radius * input.distanceScale);
|
|
66102
66209
|
const topRadius = Math.abs((input.cylinder.radiusTop ?? input.cylinder.radius) * input.distanceScale);
|
|
66103
|
-
if (!Number.isFinite(input.cylinder.height) || input.cylinder.height <=
|
|
66210
|
+
if (!Number.isFinite(input.cylinder.height) || input.cylinder.height <= EPS11 || Math.max(bottomRadius, topRadius) <= EPS11) return null;
|
|
66104
66211
|
const bottomCenter = input.transform.point([0, 0, 0]);
|
|
66105
66212
|
const topCenter = input.transform.point([0, 0, input.cylinder.height]);
|
|
66106
66213
|
const projected = [
|
|
@@ -66144,7 +66251,7 @@ function transformedTorusSideProjectionReplayProfile(sourceShape, targetPlane) {
|
|
|
66144
66251
|
if (!nearlyEqual(dot9(axis, targetPlane.normal), 0, 1e-5)) return null;
|
|
66145
66252
|
const majorRadius = Math.abs(input.torus.majorRadius * input.distanceScale);
|
|
66146
66253
|
const minorRadius = Math.abs(input.torus.minorRadius * input.distanceScale);
|
|
66147
|
-
if (majorRadius <=
|
|
66254
|
+
if (majorRadius <= EPS11 || minorRadius <= EPS11 || minorRadius >= majorRadius - EPS11) return null;
|
|
66148
66255
|
const radiusDirection = normalize8(cross9(targetPlane.normal, axis));
|
|
66149
66256
|
const radiusDirectionU = dot9(radiusDirection, targetPlane.u);
|
|
66150
66257
|
const radiusDirectionV = dot9(radiusDirection, targetPlane.v);
|
|
@@ -66235,7 +66342,7 @@ function transformedOffsetExtrudeSideProjectionReplayProfile(sourceShape, target
|
|
|
66235
66342
|
const topOrigin = input.transform.point([0, 0, input.extrude.height]);
|
|
66236
66343
|
const span = sub8(topOrigin, bottomOrigin);
|
|
66237
66344
|
const height = Math.hypot(span[0], span[1], span[2]);
|
|
66238
|
-
if (!Number.isFinite(height) || height <=
|
|
66345
|
+
if (!Number.isFinite(height) || height <= EPS11 || height + 2 * input.thickness <= EPS11) return null;
|
|
66239
66346
|
const axis = [span[0] / height, span[1] / height, span[2] / height];
|
|
66240
66347
|
if (!nearlyEqual(dot9(axis, targetPlane.normal), 0, 1e-5)) return null;
|
|
66241
66348
|
const radiusDirection = normalize8(cross9(targetPlane.normal, axis));
|
|
@@ -66259,11 +66366,11 @@ function transformedOffsetExtrudeSideProjectionReplayProfile(sourceShape, target
|
|
|
66259
66366
|
return hull ? { profile: { kind: "polygon", points: hull, transforms: [] }, placement: targetPlanePlacement(targetPlane) } : null;
|
|
66260
66367
|
}
|
|
66261
66368
|
function normalizeIntervals2(intervals) {
|
|
66262
|
-
const sorted = intervals.map(([a, b]) => [Math.min(a, b), Math.max(a, b)]).filter(([a, b]) => Number.isFinite(a) && Number.isFinite(b) && b > a +
|
|
66369
|
+
const sorted = intervals.map(([a, b]) => [Math.min(a, b), Math.max(a, b)]).filter(([a, b]) => Number.isFinite(a) && Number.isFinite(b) && b > a + EPS11).sort((a, b) => a[0] - b[0]);
|
|
66263
66370
|
const out = [];
|
|
66264
66371
|
for (const [a, b] of sorted) {
|
|
66265
66372
|
const last = out[out.length - 1];
|
|
66266
|
-
if (!last || a > last[1] +
|
|
66373
|
+
if (!last || a > last[1] + EPS11) {
|
|
66267
66374
|
out.push([a, b]);
|
|
66268
66375
|
} else {
|
|
66269
66376
|
last[1] = Math.max(last[1], b);
|
|
@@ -66282,7 +66389,7 @@ function intervalGroupsAreStrictlySeparated2(groups) {
|
|
|
66282
66389
|
for (let idx = 1; idx < spans.length; idx += 1) {
|
|
66283
66390
|
const previous = spans[idx - 1];
|
|
66284
66391
|
const current = spans[idx];
|
|
66285
|
-
if (previous.groupIdx !== current.groupIdx && current.min <= previous.max +
|
|
66392
|
+
if (previous.groupIdx !== current.groupIdx && current.min <= previous.max + EPS11) return false;
|
|
66286
66393
|
}
|
|
66287
66394
|
return true;
|
|
66288
66395
|
}
|
|
@@ -66299,7 +66406,7 @@ function partialRevolveSegmentCount2(segments) {
|
|
|
66299
66406
|
function radialSectorProjectionProfile(innerRadius, outerRadius, degrees2, segments) {
|
|
66300
66407
|
const inner = Math.max(0, Math.abs(innerRadius));
|
|
66301
66408
|
const outer = Math.max(0, Math.abs(outerRadius));
|
|
66302
|
-
if (outer <=
|
|
66409
|
+
if (outer <= EPS11 || outer - inner <= EPS11) return null;
|
|
66303
66410
|
const sweep2 = degrees2 * Math.PI / 180;
|
|
66304
66411
|
const segmentCount = partialRevolveSegmentCount2(segments);
|
|
66305
66412
|
const outerArc = [];
|
|
@@ -66307,7 +66414,7 @@ function radialSectorProjectionProfile(innerRadius, outerRadius, degrees2, segme
|
|
|
66307
66414
|
const theta = sweep2 * i / segmentCount;
|
|
66308
66415
|
outerArc.push([outer * Math.cos(theta), outer * Math.sin(theta)]);
|
|
66309
66416
|
}
|
|
66310
|
-
const points = inner <=
|
|
66417
|
+
const points = inner <= EPS11 ? [[0, 0], ...outerArc] : [
|
|
66311
66418
|
...outerArc,
|
|
66312
66419
|
...Array.from({ length: segmentCount + 1 }, (_, idx) => {
|
|
66313
66420
|
const theta = sweep2 * (segmentCount - idx) / segmentCount;
|
|
@@ -66337,7 +66444,7 @@ function transformProfilePoint2(point2, transform) {
|
|
|
66337
66444
|
}
|
|
66338
66445
|
case "mirror": {
|
|
66339
66446
|
const len2 = Math.hypot(transform.normalX, transform.normalY);
|
|
66340
|
-
if (len2 <=
|
|
66447
|
+
if (len2 <= EPS11) return point2;
|
|
66341
66448
|
const nx = transform.normalX / len2;
|
|
66342
66449
|
const ny = transform.normalY / len2;
|
|
66343
66450
|
const d = point2[0] * nx + point2[1] * ny;
|
|
@@ -66375,22 +66482,22 @@ function profileTransformBasis(transforms) {
|
|
|
66375
66482
|
}
|
|
66376
66483
|
function circleRadialProjectionInterval2(plan) {
|
|
66377
66484
|
const radius = Math.abs(plan.radius);
|
|
66378
|
-
if (!Number.isFinite(radius) || radius <=
|
|
66485
|
+
if (!Number.isFinite(radius) || radius <= EPS11) return null;
|
|
66379
66486
|
const basis = profileTransformBasis(plan.transforms);
|
|
66380
66487
|
if (!basis) return null;
|
|
66381
66488
|
const xDeviation = radius * Math.hypot(basis.xAxis[0], basis.yAxis[0]);
|
|
66382
|
-
if (!Number.isFinite(xDeviation) || xDeviation <=
|
|
66489
|
+
if (!Number.isFinite(xDeviation) || xDeviation <= EPS11) return null;
|
|
66383
66490
|
return [basis.center[0] - xDeviation, basis.center[0] + xDeviation];
|
|
66384
66491
|
}
|
|
66385
66492
|
function circleProfileFootprint(plan) {
|
|
66386
66493
|
const radius = Math.abs(plan.radius);
|
|
66387
|
-
if (!Number.isFinite(radius) || radius <=
|
|
66494
|
+
if (!Number.isFinite(radius) || radius <= EPS11) return null;
|
|
66388
66495
|
const basis = profileTransformBasis(plan.transforms);
|
|
66389
66496
|
if (!basis) return null;
|
|
66390
66497
|
const xScale = Math.hypot(basis.xAxis[0], basis.xAxis[1]);
|
|
66391
66498
|
const yScale = Math.hypot(basis.yAxis[0], basis.yAxis[1]);
|
|
66392
66499
|
const dotAxes = basis.xAxis[0] * basis.yAxis[0] + basis.xAxis[1] * basis.yAxis[1];
|
|
66393
|
-
if (xScale <=
|
|
66500
|
+
if (xScale <= EPS11 || yScale <= EPS11 || !nearlyEqual(xScale, yScale, 1e-8) || Math.abs(dotAxes) > 1e-8 * xScale * yScale) {
|
|
66394
66501
|
return null;
|
|
66395
66502
|
}
|
|
66396
66503
|
return { center: basis.center, radius: radius * xScale };
|
|
@@ -66398,7 +66505,7 @@ function circleProfileFootprint(plan) {
|
|
|
66398
66505
|
function rectProfileFootprint(plan) {
|
|
66399
66506
|
const width = Math.abs(plan.width);
|
|
66400
66507
|
const height = Math.abs(plan.height);
|
|
66401
|
-
if (!Number.isFinite(width) || !Number.isFinite(height) || width <=
|
|
66508
|
+
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= EPS11 || height <= EPS11) return null;
|
|
66402
66509
|
const basis = profileTransformBasis(plan.transforms);
|
|
66403
66510
|
if (!basis || !transformBasisIsAxisAligned(basis)) return null;
|
|
66404
66511
|
return {
|
|
@@ -66416,23 +66523,23 @@ function rectFootprintLoop(rect2) {
|
|
|
66416
66523
|
];
|
|
66417
66524
|
}
|
|
66418
66525
|
function transformBasisIsAxisAligned(basis) {
|
|
66419
|
-
const xAxisAligned = Math.abs(basis.xAxis[0]) <=
|
|
66420
|
-
const yAxisAligned = Math.abs(basis.yAxis[0]) <=
|
|
66526
|
+
const xAxisAligned = Math.abs(basis.xAxis[0]) <= EPS11 || Math.abs(basis.xAxis[1]) <= EPS11;
|
|
66527
|
+
const yAxisAligned = Math.abs(basis.yAxis[0]) <= EPS11 || Math.abs(basis.yAxis[1]) <= EPS11;
|
|
66421
66528
|
const dotAxes = basis.xAxis[0] * basis.yAxis[0] + basis.xAxis[1] * basis.yAxis[1];
|
|
66422
66529
|
const scaleProduct = Math.hypot(basis.xAxis[0], basis.xAxis[1]) * Math.hypot(basis.yAxis[0], basis.yAxis[1]);
|
|
66423
|
-
return xAxisAligned && yAxisAligned && scaleProduct >
|
|
66530
|
+
return xAxisAligned && yAxisAligned && scaleProduct > EPS11 && Math.abs(dotAxes) <= EPS11 * Math.max(1, scaleProduct);
|
|
66424
66531
|
}
|
|
66425
66532
|
function roundedRectProfileFootprint(plan) {
|
|
66426
66533
|
const width = Math.abs(plan.width);
|
|
66427
66534
|
const height = Math.abs(plan.height);
|
|
66428
|
-
if (!Number.isFinite(width) || !Number.isFinite(height) || width <=
|
|
66535
|
+
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= EPS11 || height <= EPS11) return null;
|
|
66429
66536
|
const radius = Math.max(0, Math.min(Math.abs(plan.radius), width / 2, height / 2));
|
|
66430
66537
|
const basis = profileTransformBasis(plan.transforms);
|
|
66431
66538
|
if (!basis) return null;
|
|
66432
66539
|
const xScale = Math.hypot(basis.xAxis[0], basis.xAxis[1]);
|
|
66433
66540
|
const yScale = Math.hypot(basis.yAxis[0], basis.yAxis[1]);
|
|
66434
66541
|
const dotAxes = basis.xAxis[0] * basis.yAxis[0] + basis.xAxis[1] * basis.yAxis[1];
|
|
66435
|
-
if (xScale <=
|
|
66542
|
+
if (xScale <= EPS11 || yScale <= EPS11 || !nearlyEqual(xScale, yScale, 1e-8) || Math.abs(dotAxes) > 1e-8 * xScale * yScale) {
|
|
66436
66543
|
return null;
|
|
66437
66544
|
}
|
|
66438
66545
|
return {
|
|
@@ -66447,14 +66554,14 @@ function roundedRectProfileFootprint(plan) {
|
|
|
66447
66554
|
function roundedRectRadialProjectionInterval2(plan) {
|
|
66448
66555
|
const width = Math.abs(plan.width);
|
|
66449
66556
|
const height = Math.abs(plan.height);
|
|
66450
|
-
if (!Number.isFinite(width) || !Number.isFinite(height) || width <=
|
|
66557
|
+
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= EPS11 || height <= EPS11) return null;
|
|
66451
66558
|
const radius = Math.max(0, Math.min(Math.abs(plan.radius), width / 2, height / 2));
|
|
66452
66559
|
const basis = profileTransformBasis(plan.transforms);
|
|
66453
|
-
if (!basis || Math.hypot(basis.xAxis[0], basis.xAxis[1]) <=
|
|
66560
|
+
if (!basis || Math.hypot(basis.xAxis[0], basis.xAxis[1]) <= EPS11 || Math.hypot(basis.yAxis[0], basis.yAxis[1]) <= EPS11) return null;
|
|
66454
66561
|
const coreHalfWidth = Math.max(0, width / 2 - radius);
|
|
66455
66562
|
const coreHalfHeight = Math.max(0, height / 2 - radius);
|
|
66456
66563
|
const xDeviation = Math.abs(basis.xAxis[0]) * coreHalfWidth + Math.abs(basis.yAxis[0]) * coreHalfHeight + radius * Math.hypot(basis.xAxis[0], basis.yAxis[0]);
|
|
66457
|
-
if (!Number.isFinite(xDeviation) || xDeviation <=
|
|
66564
|
+
if (!Number.isFinite(xDeviation) || xDeviation <= EPS11) return null;
|
|
66458
66565
|
return [basis.center[0] - xDeviation, basis.center[0] + xDeviation];
|
|
66459
66566
|
}
|
|
66460
66567
|
function profileOuterProjectionLoops(plan) {
|
|
@@ -66463,7 +66570,7 @@ function profileOuterProjectionLoops(plan) {
|
|
|
66463
66570
|
case "rect": {
|
|
66464
66571
|
const halfWidth = Math.abs(plan.width) / 2;
|
|
66465
66572
|
const halfHeight = Math.abs(plan.height) / 2;
|
|
66466
|
-
if (halfWidth <=
|
|
66573
|
+
if (halfWidth <= EPS11 || halfHeight <= EPS11) return null;
|
|
66467
66574
|
loops = [
|
|
66468
66575
|
[
|
|
66469
66576
|
[-halfWidth, -halfHeight],
|
|
@@ -66519,7 +66626,7 @@ function profileStrictlyContainsCircle(profile, circle2) {
|
|
|
66519
66626
|
const container = circleProfileFootprint(profile);
|
|
66520
66627
|
if (!container) return false;
|
|
66521
66628
|
const distance5 = Math.hypot(circle2.center[0] - container.center[0], circle2.center[1] - container.center[1]);
|
|
66522
|
-
return distance5 + circle2.radius < container.radius -
|
|
66629
|
+
return distance5 + circle2.radius < container.radius - EPS11;
|
|
66523
66630
|
}
|
|
66524
66631
|
case "rect":
|
|
66525
66632
|
case "polygon":
|
|
@@ -66627,7 +66734,7 @@ function pointsProjectionIntervalAlongDirection(points, direction2) {
|
|
|
66627
66734
|
min2 = Math.min(min2, projected);
|
|
66628
66735
|
max4 = Math.max(max4, projected);
|
|
66629
66736
|
}
|
|
66630
|
-
return min2 <= max4 -
|
|
66737
|
+
return min2 <= max4 - EPS11 ? [min2, max4] : null;
|
|
66631
66738
|
}
|
|
66632
66739
|
function signedArea2d2(points) {
|
|
66633
66740
|
let area2 = 0;
|
|
@@ -66641,7 +66748,7 @@ function signedArea2d2(points) {
|
|
|
66641
66748
|
function strictlyConvexPolygonOrientation(points) {
|
|
66642
66749
|
if (points.length < 3) return null;
|
|
66643
66750
|
const area2 = signedArea2d2(points);
|
|
66644
|
-
if (Math.abs(area2) <=
|
|
66751
|
+
if (Math.abs(area2) <= EPS11) return null;
|
|
66645
66752
|
const orientation3 = area2 > 0 ? 1 : -1;
|
|
66646
66753
|
for (let idx = 0; idx < points.length; idx += 1) {
|
|
66647
66754
|
const prev = points[(idx + points.length - 1) % points.length];
|
|
@@ -66652,7 +66759,7 @@ function strictlyConvexPolygonOrientation(points) {
|
|
|
66652
66759
|
const bx = next[0] - current[0];
|
|
66653
66760
|
const by = next[1] - current[1];
|
|
66654
66761
|
const cross13 = ax * by - ay * bx;
|
|
66655
|
-
if (cross13 * orientation3 <=
|
|
66762
|
+
if (cross13 * orientation3 <= EPS11) return null;
|
|
66656
66763
|
}
|
|
66657
66764
|
return orientation3;
|
|
66658
66765
|
}
|
|
@@ -66660,20 +66767,20 @@ function outwardEdgeNormal2d(a, b, orientation3) {
|
|
|
66660
66767
|
const dx = b[0] - a[0];
|
|
66661
66768
|
const dy = b[1] - a[1];
|
|
66662
66769
|
const length7 = Math.hypot(dx, dy);
|
|
66663
|
-
if (length7 <=
|
|
66770
|
+
if (length7 <= EPS11) return null;
|
|
66664
66771
|
return orientation3 > 0 ? [dy / length7, -dx / length7] : [-dy / length7, dx / length7];
|
|
66665
66772
|
}
|
|
66666
66773
|
function lineIntersectionByNormals2d(normalA, offsetA, normalB, offsetB) {
|
|
66667
66774
|
const det = normalA[0] * normalB[1] - normalA[1] * normalB[0];
|
|
66668
|
-
if (Math.abs(det) <=
|
|
66775
|
+
if (Math.abs(det) <= EPS11) return null;
|
|
66669
66776
|
return [(offsetA * normalB[1] - normalA[1] * offsetB) / det, (normalA[0] * offsetB - offsetA * normalB[0]) / det];
|
|
66670
66777
|
}
|
|
66671
66778
|
function profileProjectionIntervalAlongDirection(plan, direction2) {
|
|
66672
|
-
if (Math.hypot(direction2[0], direction2[1]) <=
|
|
66779
|
+
if (Math.hypot(direction2[0], direction2[1]) <= EPS11) return null;
|
|
66673
66780
|
switch (plan.kind) {
|
|
66674
66781
|
case "circle": {
|
|
66675
66782
|
const radius = Math.abs(plan.radius);
|
|
66676
|
-
if (!Number.isFinite(radius) || radius <=
|
|
66783
|
+
if (!Number.isFinite(radius) || radius <= EPS11) return null;
|
|
66677
66784
|
const basis = profileTransformBasis(plan.transforms);
|
|
66678
66785
|
if (!basis) return null;
|
|
66679
66786
|
const center = basis.center[0] * direction2[0] + basis.center[1] * direction2[1];
|
|
@@ -66681,19 +66788,19 @@ function profileProjectionIntervalAlongDirection(plan, direction2) {
|
|
|
66681
66788
|
basis.xAxis[0] * direction2[0] + basis.xAxis[1] * direction2[1],
|
|
66682
66789
|
basis.yAxis[0] * direction2[0] + basis.yAxis[1] * direction2[1]
|
|
66683
66790
|
);
|
|
66684
|
-
return deviation >
|
|
66791
|
+
return deviation > EPS11 ? [center - deviation, center + deviation] : null;
|
|
66685
66792
|
}
|
|
66686
66793
|
case "roundedRect": {
|
|
66687
66794
|
const footprint = roundedRectProfileFootprint(plan);
|
|
66688
66795
|
if (!footprint) return null;
|
|
66689
66796
|
const center = footprint.center[0] * direction2[0] + footprint.center[1] * direction2[1];
|
|
66690
66797
|
const deviation = roundedRectHalfExtentAlong2d(footprint, direction2);
|
|
66691
|
-
return deviation >
|
|
66798
|
+
return deviation > EPS11 ? [center - deviation, center + deviation] : null;
|
|
66692
66799
|
}
|
|
66693
66800
|
case "rect": {
|
|
66694
66801
|
const halfWidth = Math.abs(plan.width) / 2;
|
|
66695
66802
|
const halfHeight = Math.abs(plan.height) / 2;
|
|
66696
|
-
if (halfWidth <=
|
|
66803
|
+
if (halfWidth <= EPS11 || halfHeight <= EPS11) return null;
|
|
66697
66804
|
const points = transformProfileLoops(
|
|
66698
66805
|
[
|
|
66699
66806
|
[
|
|
@@ -66740,7 +66847,7 @@ function isCardinalProjectionDirection(direction2) {
|
|
|
66740
66847
|
const ax = Math.abs(direction2[0]);
|
|
66741
66848
|
const ay = Math.abs(direction2[1]);
|
|
66742
66849
|
const max4 = Math.max(ax, ay);
|
|
66743
|
-
return max4 >
|
|
66850
|
+
return max4 > EPS11 && (ax <= 1e-6 * max4 || ay <= 1e-6 * max4);
|
|
66744
66851
|
}
|
|
66745
66852
|
function profileSupportsDefendedMiterOffsetSideInterval(plan, delta) {
|
|
66746
66853
|
switch (plan.kind) {
|
|
@@ -66751,12 +66858,12 @@ function profileSupportsDefendedMiterOffsetSideInterval(plan, delta) {
|
|
|
66751
66858
|
case "roundedRect":
|
|
66752
66859
|
return roundedRectProfileFootprint(plan) != null;
|
|
66753
66860
|
case "polygon":
|
|
66754
|
-
return delta >= -
|
|
66861
|
+
return delta >= -EPS11;
|
|
66755
66862
|
case "boolean": {
|
|
66756
66863
|
const profiles2 = booleanProfilesWithParentTransforms2(plan);
|
|
66757
66864
|
if (!profiles2 || profiles2.length === 0) return false;
|
|
66758
66865
|
if (plan.op === "union") {
|
|
66759
|
-
return delta >= -
|
|
66866
|
+
return delta >= -EPS11 && profiles2.every((profile) => profileSupportsDefendedMiterOffsetSideInterval(profile, delta));
|
|
66760
66867
|
}
|
|
66761
66868
|
if (plan.op === "intersection") {
|
|
66762
66869
|
const contained = containedIntersectionProfile2(profiles2);
|
|
@@ -66764,7 +66871,7 @@ function profileSupportsDefendedMiterOffsetSideInterval(plan, delta) {
|
|
|
66764
66871
|
}
|
|
66765
66872
|
const [subject, ...clips] = profiles2;
|
|
66766
66873
|
if (!subject || clips.some((clip) => !profileStrictlyContainsProfile(subject, clip))) return false;
|
|
66767
|
-
if (delta < -
|
|
66874
|
+
if (delta < -EPS11) {
|
|
66768
66875
|
const insetSubject = insetProfileForContainment(subject, Math.abs(delta));
|
|
66769
66876
|
if (!insetSubject) return false;
|
|
66770
66877
|
return clips.every((clip) => {
|
|
@@ -66796,13 +66903,13 @@ function profileSupportsExactCircularOffsetSideInterval(plan, delta) {
|
|
|
66796
66903
|
if (plan.op !== "difference") return false;
|
|
66797
66904
|
const [subject, ...clips] = profiles2;
|
|
66798
66905
|
if (!subject) return false;
|
|
66799
|
-
if (delta >= -
|
|
66906
|
+
if (delta >= -EPS11) {
|
|
66800
66907
|
return profileSupportsExactCircularOffsetSideInterval(subject, delta) && clips.every((clip) => profileStrictlyContainsProfile(subject, clip));
|
|
66801
66908
|
}
|
|
66802
66909
|
const subjectSegments = subject.kind === "circle" ? subject.segments : void 0;
|
|
66803
66910
|
const subjectCircle = subject.kind === "circle" ? circleProfileFootprint(subject) : null;
|
|
66804
66911
|
const inset = Math.abs(delta);
|
|
66805
|
-
if (!subjectCircle || subjectCircle.radius <= inset +
|
|
66912
|
+
if (!subjectCircle || subjectCircle.radius <= inset + EPS11) return false;
|
|
66806
66913
|
const subjectInset = translatedProfileIfNeeded(
|
|
66807
66914
|
circleProjectionProfile(subjectCircle.radius - inset, subjectSegments),
|
|
66808
66915
|
subjectCircle.center
|
|
@@ -66839,9 +66946,9 @@ function offsetProfileProjectionIntervalAlongDirection(plan, direction2, delta)
|
|
|
66839
66946
|
}
|
|
66840
66947
|
function miterOffsetConvexPolygonProfileProjectionIntervalAlongDirection(plan, direction2, delta) {
|
|
66841
66948
|
const directionLength = Math.hypot(direction2[0], direction2[1]);
|
|
66842
|
-
if (directionLength <=
|
|
66949
|
+
if (directionLength <= EPS11) return null;
|
|
66843
66950
|
const localDelta = delta / directionLength;
|
|
66844
|
-
if (Math.abs(localDelta) <=
|
|
66951
|
+
if (Math.abs(localDelta) <= EPS11) return null;
|
|
66845
66952
|
if (plan.kind === "boolean") {
|
|
66846
66953
|
const profiles2 = booleanProfilesWithParentTransforms2(plan);
|
|
66847
66954
|
if (!profiles2 || profiles2.length === 0) return null;
|
|
@@ -66852,7 +66959,7 @@ function miterOffsetConvexPolygonProfileProjectionIntervalAlongDirection(plan, d
|
|
|
66852
66959
|
if (plan.op === "difference") {
|
|
66853
66960
|
const [subject, ...clips] = profiles2;
|
|
66854
66961
|
if (!subject || clips.some((clip) => !profileStrictlyContainsProfile(subject, clip))) return null;
|
|
66855
|
-
if (delta < -
|
|
66962
|
+
if (delta < -EPS11) {
|
|
66856
66963
|
const subjectInset = miterOffsetConvexPolygonProfileForLocalDelta(subject, localDelta);
|
|
66857
66964
|
if (!subjectInset) return null;
|
|
66858
66965
|
const clearance = Math.abs(localDelta);
|
|
@@ -66864,7 +66971,7 @@ function miterOffsetConvexPolygonProfileProjectionIntervalAlongDirection(plan, d
|
|
|
66864
66971
|
}
|
|
66865
66972
|
return miterOffsetConvexPolygonProfileProjectionIntervalAlongDirection(subject, direction2, delta);
|
|
66866
66973
|
}
|
|
66867
|
-
if (plan.op !== "union" || delta < -
|
|
66974
|
+
if (plan.op !== "union" || delta < -EPS11) return null;
|
|
66868
66975
|
const intervals = profiles2.map((profile) => miterOffsetConvexPolygonProfileProjectionIntervalAlongDirection(profile, direction2, delta));
|
|
66869
66976
|
if (intervals.some((interval) => !interval)) return null;
|
|
66870
66977
|
const merged = normalizeIntervals2(intervals);
|
|
@@ -66874,7 +66981,7 @@ function miterOffsetConvexPolygonProfileProjectionIntervalAlongDirection(plan, d
|
|
|
66874
66981
|
return offsetProfile2 ? profileProjectionIntervalAlongDirection(offsetProfile2, direction2) : null;
|
|
66875
66982
|
}
|
|
66876
66983
|
function miterOffsetConvexPolygonProfileForLocalDelta(plan, localDelta) {
|
|
66877
|
-
if (Math.abs(localDelta) <=
|
|
66984
|
+
if (Math.abs(localDelta) <= EPS11) return null;
|
|
66878
66985
|
if (plan.kind === "boolean" && plan.op === "intersection") {
|
|
66879
66986
|
const profiles2 = booleanProfilesWithParentTransforms2(plan);
|
|
66880
66987
|
if (!profiles2 || profiles2.length === 0) return null;
|
|
@@ -66899,7 +67006,7 @@ function miterOffsetConvexPolygonProfileForLocalDelta(plan, localDelta) {
|
|
|
66899
67006
|
const offsetPoint = lineIntersectionByNormals2d(prevNormal, prevOffset, nextNormal, nextOffset);
|
|
66900
67007
|
if (!offsetPoint) return null;
|
|
66901
67008
|
const miterLength = Math.hypot(offsetPoint[0] - current[0], offsetPoint[1] - current[1]);
|
|
66902
|
-
if (miterLength > 2 * Math.abs(localDelta) +
|
|
67009
|
+
if (miterLength > 2 * Math.abs(localDelta) + EPS11) return null;
|
|
66903
67010
|
offsetPoints.push(offsetPoint);
|
|
66904
67011
|
}
|
|
66905
67012
|
if (strictlyConvexPolygonOrientation(offsetPoints) !== orientation3) return null;
|
|
@@ -66912,8 +67019,8 @@ function translatedProfileIfNeeded(profile, center) {
|
|
|
66912
67019
|
return profile;
|
|
66913
67020
|
}
|
|
66914
67021
|
function insetProfileForContainment(plan, distance5) {
|
|
66915
|
-
if (!Number.isFinite(distance5) || distance5 < -
|
|
66916
|
-
if (distance5 <=
|
|
67022
|
+
if (!Number.isFinite(distance5) || distance5 < -EPS11) return null;
|
|
67023
|
+
if (distance5 <= EPS11) return cloneProfileCompilePlan(plan);
|
|
66917
67024
|
if (plan.kind === "boolean" && plan.op === "intersection") {
|
|
66918
67025
|
const profiles2 = booleanProfilesWithParentTransforms2(plan);
|
|
66919
67026
|
if (!profiles2 || profiles2.length === 0) return null;
|
|
@@ -66922,11 +67029,11 @@ function insetProfileForContainment(plan, distance5) {
|
|
|
66922
67029
|
}
|
|
66923
67030
|
if (plan.kind === "circle") {
|
|
66924
67031
|
const circle2 = circleProfileFootprint(plan);
|
|
66925
|
-
return circle2 && circle2.radius > distance5 +
|
|
67032
|
+
return circle2 && circle2.radius > distance5 + EPS11 ? translatedProfileIfNeeded(circleProjectionProfile(circle2.radius - distance5, plan.segments), circle2.center) : null;
|
|
66926
67033
|
}
|
|
66927
67034
|
if (plan.kind === "rect") {
|
|
66928
67035
|
const rect2 = rectProfileFootprint(plan);
|
|
66929
|
-
return rect2 && rect2.halfWidth > distance5 +
|
|
67036
|
+
return rect2 && rect2.halfWidth > distance5 + EPS11 && rect2.halfHeight > distance5 + EPS11 ? translatedProfileIfNeeded(
|
|
66930
67037
|
{
|
|
66931
67038
|
kind: "rect",
|
|
66932
67039
|
width: 2 * (rect2.halfWidth - distance5),
|
|
@@ -66938,7 +67045,7 @@ function insetProfileForContainment(plan, distance5) {
|
|
|
66938
67045
|
}
|
|
66939
67046
|
if (plan.kind === "roundedRect") {
|
|
66940
67047
|
const roundedRect2 = roundedRectProfileFootprint(plan);
|
|
66941
|
-
if (!roundedRect2 || roundedRect2.halfWidth <= distance5 +
|
|
67048
|
+
if (!roundedRect2 || roundedRect2.halfWidth <= distance5 + EPS11 || roundedRect2.halfHeight <= distance5 + EPS11 || roundedRect2.radius <= distance5 + EPS11) {
|
|
66942
67049
|
return null;
|
|
66943
67050
|
}
|
|
66944
67051
|
const profile = {
|
|
@@ -66958,8 +67065,8 @@ function insetProfileForContainment(plan, distance5) {
|
|
|
66958
67065
|
return null;
|
|
66959
67066
|
}
|
|
66960
67067
|
function expandedProfileForContainment(plan, distance5) {
|
|
66961
|
-
if (!Number.isFinite(distance5) || distance5 < -
|
|
66962
|
-
if (distance5 <=
|
|
67068
|
+
if (!Number.isFinite(distance5) || distance5 < -EPS11) return null;
|
|
67069
|
+
if (distance5 <= EPS11) return cloneProfileCompilePlan(plan);
|
|
66963
67070
|
if (plan.kind === "boolean" && plan.op === "union") {
|
|
66964
67071
|
const profiles2 = booleanProfilesWithParentTransforms2(plan);
|
|
66965
67072
|
if (!profiles2 || profiles2.length === 0) return null;
|
|
@@ -67019,7 +67126,7 @@ function booleanOffsetProfileProjectionIntervalAlongDirection(plan, direction2,
|
|
|
67019
67126
|
const profiles2 = booleanProfilesWithParentTransforms2(plan);
|
|
67020
67127
|
if (!profiles2 || profiles2.length === 0) return null;
|
|
67021
67128
|
if (plan.op === "union") {
|
|
67022
|
-
if (delta < -
|
|
67129
|
+
if (delta < -EPS11) return null;
|
|
67023
67130
|
const intervals = profiles2.map((profile) => childInterval(profile, direction2, delta));
|
|
67024
67131
|
if (intervals.some((interval) => !interval)) return null;
|
|
67025
67132
|
const merged = normalizeIntervals2(intervals);
|
|
@@ -67031,9 +67138,9 @@ function booleanOffsetProfileProjectionIntervalAlongDirection(plan, direction2,
|
|
|
67031
67138
|
}
|
|
67032
67139
|
const [subject, ...clips] = profiles2;
|
|
67033
67140
|
if (!subject || clips.some((clip) => !profileStrictlyContainsProfile(subject, clip))) return null;
|
|
67034
|
-
if (delta < -
|
|
67141
|
+
if (delta < -EPS11) {
|
|
67035
67142
|
const directionLength = Math.hypot(direction2[0], direction2[1]);
|
|
67036
|
-
if (directionLength <=
|
|
67143
|
+
if (directionLength <= EPS11) return null;
|
|
67037
67144
|
const clearance = Math.abs(delta) / directionLength;
|
|
67038
67145
|
const subjectInset = insetProfileForContainment(subject, clearance);
|
|
67039
67146
|
if (!subjectInset) return null;
|
|
@@ -67057,12 +67164,12 @@ function roundedRectOffsetProfileProjectionIntervalAlongDirection(plan, directio
|
|
|
67057
67164
|
const footprint = roundedRectProfileFootprint(plan);
|
|
67058
67165
|
if (!footprint) return null;
|
|
67059
67166
|
const directionLength = Math.hypot(direction2[0], direction2[1]);
|
|
67060
|
-
if (directionLength <=
|
|
67167
|
+
if (directionLength <= EPS11) return null;
|
|
67061
67168
|
const localDelta = delta / directionLength;
|
|
67062
67169
|
const halfWidth = footprint.halfWidth + localDelta;
|
|
67063
67170
|
const halfHeight = footprint.halfHeight + localDelta;
|
|
67064
67171
|
const radius = footprint.radius + localDelta;
|
|
67065
|
-
if (halfWidth <=
|
|
67172
|
+
if (halfWidth <= EPS11 || halfHeight <= EPS11 || radius <= EPS11 || radius > Math.min(halfWidth, halfHeight) + EPS11) return null;
|
|
67066
67173
|
const adjusted = {
|
|
67067
67174
|
...footprint,
|
|
67068
67175
|
halfWidth,
|
|
@@ -67071,7 +67178,7 @@ function roundedRectOffsetProfileProjectionIntervalAlongDirection(plan, directio
|
|
|
67071
67178
|
};
|
|
67072
67179
|
const center = footprint.center[0] * direction2[0] + footprint.center[1] * direction2[1];
|
|
67073
67180
|
const deviation = roundedRectHalfExtentAlong2d(adjusted, direction2);
|
|
67074
|
-
return deviation >
|
|
67181
|
+
return deviation > EPS11 ? [center - deviation, center + deviation] : null;
|
|
67075
67182
|
}
|
|
67076
67183
|
function miterOffsetRectProfileProjectionIntervalAlongDirection(plan, direction2, delta) {
|
|
67077
67184
|
const booleanInterval = booleanOffsetProfileProjectionIntervalAlongDirection(
|
|
@@ -67085,17 +67192,17 @@ function miterOffsetRectProfileProjectionIntervalAlongDirection(plan, direction2
|
|
|
67085
67192
|
const rect2 = rectProfileFootprint(plan);
|
|
67086
67193
|
if (!rect2) return null;
|
|
67087
67194
|
const directionLength = Math.hypot(direction2[0], direction2[1]);
|
|
67088
|
-
if (directionLength <=
|
|
67195
|
+
if (directionLength <= EPS11) return null;
|
|
67089
67196
|
const localDelta = delta / directionLength;
|
|
67090
67197
|
const halfWidth = rect2.halfWidth + localDelta;
|
|
67091
67198
|
const halfHeight = rect2.halfHeight + localDelta;
|
|
67092
|
-
if (halfWidth <=
|
|
67199
|
+
if (halfWidth <= EPS11 || halfHeight <= EPS11) return null;
|
|
67093
67200
|
const center = rect2.center[0] * direction2[0] + rect2.center[1] * direction2[1];
|
|
67094
67201
|
const deviation = halfWidth * Math.abs(direction2[0]) + halfHeight * Math.abs(direction2[1]);
|
|
67095
|
-
return deviation >
|
|
67202
|
+
return deviation > EPS11 ? [center - deviation, center + deviation] : null;
|
|
67096
67203
|
}
|
|
67097
67204
|
function circularOffsetProfileProjectionIntervalAlongDirection(plan, direction2, delta) {
|
|
67098
|
-
if (plan.kind === "boolean" && plan.op === "union" && delta >= -
|
|
67205
|
+
if (plan.kind === "boolean" && plan.op === "union" && delta >= -EPS11) {
|
|
67099
67206
|
const profiles2 = booleanProfilesWithParentTransforms2(plan);
|
|
67100
67207
|
if (!profiles2 || profiles2.length === 0) return null;
|
|
67101
67208
|
const intervals = profiles2.map((profile) => circularOffsetProfileProjectionIntervalAlongDirection(profile, direction2, delta));
|
|
@@ -67108,11 +67215,11 @@ function circularOffsetProfileProjectionIntervalAlongDirection(plan, direction2,
|
|
|
67108
67215
|
if (!profileSupportsExactCircularOffsetSideInterval(plan, delta)) return null;
|
|
67109
67216
|
const min2 = interval[0] - delta;
|
|
67110
67217
|
const max4 = interval[1] + delta;
|
|
67111
|
-
return max4 > min2 +
|
|
67218
|
+
return max4 > min2 + EPS11 ? [min2, max4] : null;
|
|
67112
67219
|
}
|
|
67113
67220
|
function miterOffsetProfileProjectionIntervalAlongCardinalDirection(plan, direction2, delta) {
|
|
67114
67221
|
if (!profileSupportsDefendedMiterOffsetSideInterval(plan, delta)) return null;
|
|
67115
|
-
if (plan.kind === "boolean" && plan.op === "union" && delta >= -
|
|
67222
|
+
if (plan.kind === "boolean" && plan.op === "union" && delta >= -EPS11) {
|
|
67116
67223
|
const profiles2 = booleanProfilesWithParentTransforms2(plan);
|
|
67117
67224
|
if (!profiles2 || profiles2.length === 0) return null;
|
|
67118
67225
|
const intervals = profiles2.map((profile) => miterOffsetProfileProjectionIntervalAlongCardinalDirection(profile, direction2, delta));
|
|
@@ -67124,7 +67231,7 @@ function miterOffsetProfileProjectionIntervalAlongCardinalDirection(plan, direct
|
|
|
67124
67231
|
if (!interval) return null;
|
|
67125
67232
|
const min2 = interval[0] - delta;
|
|
67126
67233
|
const max4 = interval[1] + delta;
|
|
67127
|
-
return max4 > min2 +
|
|
67234
|
+
return max4 > min2 + EPS11 ? [min2, max4] : null;
|
|
67128
67235
|
}
|
|
67129
67236
|
function containedIntersectionProfile2(profiles2) {
|
|
67130
67237
|
for (const candidate of profiles2) {
|
|
@@ -67140,7 +67247,7 @@ function radialProjectionIntervalsForLoops(loops) {
|
|
|
67140
67247
|
let min2 = Infinity;
|
|
67141
67248
|
let max4 = -Infinity;
|
|
67142
67249
|
for (const [x] of loop) {
|
|
67143
|
-
if (x < -
|
|
67250
|
+
if (x < -EPS11) return null;
|
|
67144
67251
|
min2 = Math.min(min2, Math.max(0, x));
|
|
67145
67252
|
max4 = Math.max(max4, Math.max(0, x));
|
|
67146
67253
|
}
|
|
@@ -67162,12 +67269,12 @@ function radialProjectionIntervalsForRevolveProfile2(plan) {
|
|
|
67162
67269
|
switch (plan.kind) {
|
|
67163
67270
|
case "circle": {
|
|
67164
67271
|
const interval = circleRadialProjectionInterval2(plan);
|
|
67165
|
-
if (!interval || interval[0] < -
|
|
67272
|
+
if (!interval || interval[0] < -EPS11) return null;
|
|
67166
67273
|
return normalizeIntervals2([[Math.max(0, interval[0]), Math.max(0, interval[1])]]);
|
|
67167
67274
|
}
|
|
67168
67275
|
case "roundedRect": {
|
|
67169
67276
|
const interval = roundedRectRadialProjectionInterval2(plan);
|
|
67170
|
-
if (!interval || interval[0] < -
|
|
67277
|
+
if (!interval || interval[0] < -EPS11) return null;
|
|
67171
67278
|
return normalizeIntervals2([[Math.max(0, interval[0]), Math.max(0, interval[1])]]);
|
|
67172
67279
|
}
|
|
67173
67280
|
case "boolean": {
|
|
@@ -67220,7 +67327,7 @@ function radialProjectionIntervalsForRevolveProfile2(plan) {
|
|
|
67220
67327
|
}
|
|
67221
67328
|
}
|
|
67222
67329
|
function expandRadialIntervalsForOutwardOffset2(intervals, thickness) {
|
|
67223
|
-
if (!Number.isFinite(thickness) || thickness <=
|
|
67330
|
+
if (!Number.isFinite(thickness) || thickness <= EPS11) return null;
|
|
67224
67331
|
const expanded = intervals.map(([inner, outer]) => [Math.max(0, inner - thickness), outer + thickness]);
|
|
67225
67332
|
return normalizeIntervals2(expanded);
|
|
67226
67333
|
}
|
|
@@ -67228,19 +67335,19 @@ function radialIntervalsForSimpleFullRevolutionFootprint2(footprint) {
|
|
|
67228
67335
|
const halfX = footprint.kind === "circle" ? footprint.footprint.radius : footprint.kind === "rect" ? footprint.footprint.halfWidth : roundedRectHalfExtentAlong2d(footprint.footprint, [1, 0]);
|
|
67229
67336
|
const inner = footprint.footprint.center[0] - halfX;
|
|
67230
67337
|
const outer = footprint.footprint.center[0] + halfX;
|
|
67231
|
-
if (inner < -
|
|
67338
|
+
if (inner < -EPS11 || outer <= EPS11) return null;
|
|
67232
67339
|
return normalizeIntervals2([[Math.max(0, inner), outer]]);
|
|
67233
67340
|
}
|
|
67234
67341
|
function insetSimpleFullRevolutionProfileFootprint2(plan, distance5) {
|
|
67235
67342
|
switch (plan.kind) {
|
|
67236
67343
|
case "circle": {
|
|
67237
67344
|
const circle2 = circleProfileFootprint(plan);
|
|
67238
|
-
if (!circle2 || circle2.radius <= distance5 +
|
|
67345
|
+
if (!circle2 || circle2.radius <= distance5 + EPS11) return null;
|
|
67239
67346
|
return { kind: "circle", footprint: { center: circle2.center, radius: circle2.radius - distance5 } };
|
|
67240
67347
|
}
|
|
67241
67348
|
case "rect": {
|
|
67242
67349
|
const rect2 = rectProfileFootprint(plan);
|
|
67243
|
-
if (!rect2 || rect2.halfWidth <= distance5 +
|
|
67350
|
+
if (!rect2 || rect2.halfWidth <= distance5 + EPS11 || rect2.halfHeight <= distance5 + EPS11) return null;
|
|
67244
67351
|
return {
|
|
67245
67352
|
kind: "rect",
|
|
67246
67353
|
footprint: {
|
|
@@ -67252,7 +67359,7 @@ function insetSimpleFullRevolutionProfileFootprint2(plan, distance5) {
|
|
|
67252
67359
|
}
|
|
67253
67360
|
case "roundedRect": {
|
|
67254
67361
|
const roundedRect2 = roundedRectProfileFootprint(plan);
|
|
67255
|
-
if (!roundedRect2 || roundedRect2.halfWidth <= distance5 +
|
|
67362
|
+
if (!roundedRect2 || roundedRect2.halfWidth <= distance5 + EPS11 || roundedRect2.halfHeight <= distance5 + EPS11 || roundedRect2.radius <= distance5 + EPS11) {
|
|
67256
67363
|
return null;
|
|
67257
67364
|
}
|
|
67258
67365
|
return {
|
|
@@ -67315,7 +67422,7 @@ function simpleFootprintStrictlyContains2(container, subject) {
|
|
|
67315
67422
|
subject.footprint.center[0] - container.footprint.center[0],
|
|
67316
67423
|
subject.footprint.center[1] - container.footprint.center[1]
|
|
67317
67424
|
);
|
|
67318
|
-
return distance5 + subject.footprint.radius < container.footprint.radius -
|
|
67425
|
+
return distance5 + subject.footprint.radius < container.footprint.radius - EPS11;
|
|
67319
67426
|
}
|
|
67320
67427
|
case "rect":
|
|
67321
67428
|
return circleStrictlyContainsLoop2d(container.footprint, rectFootprintLoop(subject.footprint));
|
|
@@ -67353,9 +67460,9 @@ function simpleFootprintStrictlyContains2(container, subject) {
|
|
|
67353
67460
|
function erodeSimpleFullRevolutionProfileFootprint2(footprint, distance5) {
|
|
67354
67461
|
switch (footprint.kind) {
|
|
67355
67462
|
case "circle":
|
|
67356
|
-
return footprint.footprint.radius > distance5 +
|
|
67463
|
+
return footprint.footprint.radius > distance5 + EPS11 ? { kind: "circle", footprint: { center: footprint.footprint.center, radius: footprint.footprint.radius - distance5 } } : null;
|
|
67357
67464
|
case "rect":
|
|
67358
|
-
return footprint.footprint.halfWidth > distance5 +
|
|
67465
|
+
return footprint.footprint.halfWidth > distance5 + EPS11 && footprint.footprint.halfHeight > distance5 + EPS11 ? {
|
|
67359
67466
|
kind: "rect",
|
|
67360
67467
|
footprint: {
|
|
67361
67468
|
center: footprint.footprint.center,
|
|
@@ -67364,7 +67471,7 @@ function erodeSimpleFullRevolutionProfileFootprint2(footprint, distance5) {
|
|
|
67364
67471
|
}
|
|
67365
67472
|
} : null;
|
|
67366
67473
|
case "roundedRect":
|
|
67367
|
-
return footprint.footprint.halfWidth > distance5 +
|
|
67474
|
+
return footprint.footprint.halfWidth > distance5 + EPS11 && footprint.footprint.halfHeight > distance5 + EPS11 && footprint.footprint.radius > distance5 + EPS11 ? {
|
|
67368
67475
|
kind: "roundedRect",
|
|
67369
67476
|
footprint: {
|
|
67370
67477
|
center: footprint.footprint.center,
|
|
@@ -67392,7 +67499,7 @@ function simpleFootprintStrictlyContainsLoop2(container, loop) {
|
|
|
67392
67499
|
}
|
|
67393
67500
|
}
|
|
67394
67501
|
function insetRadialIntervalsForFullRevolutionProfile2(plan, distance5) {
|
|
67395
|
-
if (!Number.isFinite(distance5) || distance5 <=
|
|
67502
|
+
if (!Number.isFinite(distance5) || distance5 <= EPS11) return null;
|
|
67396
67503
|
switch (plan.kind) {
|
|
67397
67504
|
case "circle":
|
|
67398
67505
|
case "rect":
|
|
@@ -67533,7 +67640,7 @@ function pointSegmentDistance2d(point2, a, b) {
|
|
|
67533
67640
|
const dx = b[0] - a[0];
|
|
67534
67641
|
const dy = b[1] - a[1];
|
|
67535
67642
|
const lenSq = dx * dx + dy * dy;
|
|
67536
|
-
if (lenSq <=
|
|
67643
|
+
if (lenSq <= EPS11 * EPS11) return Math.hypot(point2[0] - a[0], point2[1] - a[1]);
|
|
67537
67644
|
const t = Math.max(0, Math.min(1, ((point2[0] - a[0]) * dx + (point2[1] - a[1]) * dy) / lenSq));
|
|
67538
67645
|
return Math.hypot(point2[0] - (a[0] + t * dx), point2[1] - (a[1] + t * dy));
|
|
67539
67646
|
}
|
|
@@ -67546,7 +67653,7 @@ function loopStrictlyContainsCircle2d(container, circle2) {
|
|
|
67546
67653
|
pointSegmentDistance2d(circle2.center, container[idx], container[(idx + 1) % container.length])
|
|
67547
67654
|
);
|
|
67548
67655
|
}
|
|
67549
|
-
return minBoundaryDistance > circle2.radius +
|
|
67656
|
+
return minBoundaryDistance > circle2.radius + EPS11;
|
|
67550
67657
|
}
|
|
67551
67658
|
function loopStrictlyContainsRoundedRect2d(container, roundedRect2) {
|
|
67552
67659
|
const orientation3 = strictlyConvexPolygonOrientation(container);
|
|
@@ -67558,7 +67665,7 @@ function loopStrictlyContainsRoundedRect2d(container, roundedRect2) {
|
|
|
67558
67665
|
if (!normal) return false;
|
|
67559
67666
|
const edgeOffset = normal[0] * current[0] + normal[1] * current[1];
|
|
67560
67667
|
const support = roundedRect2.center[0] * normal[0] + roundedRect2.center[1] * normal[1] + roundedRectHalfExtentAlong2d(roundedRect2, normal);
|
|
67561
|
-
if (support >= edgeOffset -
|
|
67668
|
+
if (support >= edgeOffset - EPS11) return false;
|
|
67562
67669
|
}
|
|
67563
67670
|
return true;
|
|
67564
67671
|
}
|
|
@@ -67568,7 +67675,7 @@ function circleStrictlyContainsLoop2d(circle2, loop) {
|
|
|
67568
67675
|
const a = loop[idx];
|
|
67569
67676
|
const b = loop[(idx + 1) % loop.length];
|
|
67570
67677
|
const midpoint7 = [(a[0] + b[0]) * 0.5, (a[1] + b[1]) * 0.5];
|
|
67571
|
-
if (Math.hypot(a[0] - circle2.center[0], a[1] - circle2.center[1]) >= circle2.radius -
|
|
67678
|
+
if (Math.hypot(a[0] - circle2.center[0], a[1] - circle2.center[1]) >= circle2.radius - EPS11 || Math.hypot(midpoint7[0] - circle2.center[0], midpoint7[1] - circle2.center[1]) >= circle2.radius - EPS11) {
|
|
67572
67679
|
return false;
|
|
67573
67680
|
}
|
|
67574
67681
|
}
|
|
@@ -67577,7 +67684,7 @@ function circleStrictlyContainsLoop2d(circle2, loop) {
|
|
|
67577
67684
|
function rectStrictlyContainsRoundedRect2d(rect2, roundedRect2) {
|
|
67578
67685
|
const halfX = roundedRectHalfExtentAlong2d(roundedRect2, [1, 0]);
|
|
67579
67686
|
const halfY = roundedRectHalfExtentAlong2d(roundedRect2, [0, 1]);
|
|
67580
|
-
return roundedRect2.center[0] - halfX > rect2.center[0] - rect2.halfWidth +
|
|
67687
|
+
return roundedRect2.center[0] - halfX > rect2.center[0] - rect2.halfWidth + EPS11 && roundedRect2.center[0] + halfX < rect2.center[0] + rect2.halfWidth - EPS11 && roundedRect2.center[1] - halfY > rect2.center[1] - rect2.halfHeight + EPS11 && roundedRect2.center[1] + halfY < rect2.center[1] + rect2.halfHeight - EPS11;
|
|
67581
67688
|
}
|
|
67582
67689
|
function roundedRectHalfExtentAlong2d(footprint, direction2) {
|
|
67583
67690
|
const coreHalfWidth = Math.max(0, footprint.halfWidth - footprint.radius);
|
|
@@ -67595,7 +67702,7 @@ function roundedRectAxesAreAligned2d(container, subject) {
|
|
|
67595
67702
|
const sxOnCy = Math.abs(subject.xAxis[0] * container.yAxis[0] + subject.xAxis[1] * container.yAxis[1]);
|
|
67596
67703
|
const syOnCx = Math.abs(subject.yAxis[0] * container.xAxis[0] + subject.yAxis[1] * container.xAxis[1]);
|
|
67597
67704
|
const syOnCy = Math.abs(subject.yAxis[0] * container.yAxis[0] + subject.yAxis[1] * container.yAxis[1]);
|
|
67598
|
-
return (sxOnCx <=
|
|
67705
|
+
return (sxOnCx <= EPS11 || sxOnCy <= EPS11) && (syOnCx <= EPS11 || syOnCy <= EPS11) && nearlyEqual(sxOnCx + sxOnCy, 1, 1e-6) && nearlyEqual(syOnCx + syOnCy, 1, 1e-6);
|
|
67599
67706
|
}
|
|
67600
67707
|
function roundedRectToContainerLocal2d(container, subject) {
|
|
67601
67708
|
if (!roundedRectAxesAreAligned2d(container, subject)) return null;
|
|
@@ -67621,7 +67728,7 @@ function roundedRectBoundaryMaxDistanceFromPoint2d(footprint, point2) {
|
|
|
67621
67728
|
const includeLocal = (candidate) => {
|
|
67622
67729
|
maxDistance = Math.max(maxDistance, Math.hypot(candidate[0] - localPoint[0], candidate[1] - localPoint[1]));
|
|
67623
67730
|
};
|
|
67624
|
-
if (footprint.radius <=
|
|
67731
|
+
if (footprint.radius <= EPS11) {
|
|
67625
67732
|
includeLocal([-footprint.halfWidth, -footprint.halfHeight]);
|
|
67626
67733
|
includeLocal([footprint.halfWidth, -footprint.halfHeight]);
|
|
67627
67734
|
includeLocal([footprint.halfWidth, footprint.halfHeight]);
|
|
@@ -67640,14 +67747,14 @@ function roundedRectBoundaryMaxDistanceFromPoint2d(footprint, point2) {
|
|
|
67640
67747
|
includeLocal([center[0], center[1] + sy * footprint.radius]);
|
|
67641
67748
|
const away = [center[0] - localPoint[0], center[1] - localPoint[1]];
|
|
67642
67749
|
const awayLength = Math.hypot(away[0], away[1]);
|
|
67643
|
-
if (awayLength >
|
|
67750
|
+
if (awayLength > EPS11 && away[0] * sx >= -EPS11 && away[1] * sy >= -EPS11) {
|
|
67644
67751
|
includeLocal([center[0] + away[0] / awayLength * footprint.radius, center[1] + away[1] / awayLength * footprint.radius]);
|
|
67645
67752
|
}
|
|
67646
67753
|
}
|
|
67647
67754
|
return maxDistance;
|
|
67648
67755
|
}
|
|
67649
67756
|
function circleStrictlyContainsRoundedRect2d(circle2, roundedRect2) {
|
|
67650
|
-
return roundedRectBoundaryMaxDistanceFromPoint2d(roundedRect2, circle2.center) < circle2.radius -
|
|
67757
|
+
return roundedRectBoundaryMaxDistanceFromPoint2d(roundedRect2, circle2.center) < circle2.radius - EPS11;
|
|
67651
67758
|
}
|
|
67652
67759
|
function roundedRectSignedDistance2d(footprint, point2) {
|
|
67653
67760
|
const localPoint = roundedRectLocalPoint2d(footprint, point2);
|
|
@@ -67658,10 +67765,10 @@ function roundedRectSignedDistance2d(footprint, point2) {
|
|
|
67658
67765
|
return Math.hypot(Math.max(qx, 0), Math.max(qy, 0)) + Math.min(Math.max(qx, qy), 0) - footprint.radius;
|
|
67659
67766
|
}
|
|
67660
67767
|
function roundedRectStrictlyContainsCircle2d(footprint, circle2) {
|
|
67661
|
-
return roundedRectSignedDistance2d(footprint, circle2.center) + circle2.radius < -
|
|
67768
|
+
return roundedRectSignedDistance2d(footprint, circle2.center) + circle2.radius < -EPS11;
|
|
67662
67769
|
}
|
|
67663
67770
|
function roundedRectStrictlyContainsRoundedRect2d(container, subject) {
|
|
67664
|
-
if (subject.radius > container.radius +
|
|
67771
|
+
if (subject.radius > container.radius + EPS11) return false;
|
|
67665
67772
|
const localSubject = roundedRectToContainerLocal2d(container, subject);
|
|
67666
67773
|
if (!localSubject) return false;
|
|
67667
67774
|
const eroded = {
|
|
@@ -67672,7 +67779,7 @@ function roundedRectStrictlyContainsRoundedRect2d(container, subject) {
|
|
|
67672
67779
|
xAxis: [1, 0],
|
|
67673
67780
|
yAxis: [0, 1]
|
|
67674
67781
|
};
|
|
67675
|
-
if (eroded.halfWidth <=
|
|
67782
|
+
if (eroded.halfWidth <= EPS11 || eroded.halfHeight <= EPS11 || eroded.radius < -EPS11) return false;
|
|
67676
67783
|
const coreHalfWidth = Math.max(0, localSubject.halfWidth - localSubject.radius);
|
|
67677
67784
|
const coreHalfHeight = Math.max(0, localSubject.halfHeight - localSubject.radius);
|
|
67678
67785
|
const corners = [
|
|
@@ -67681,7 +67788,7 @@ function roundedRectStrictlyContainsRoundedRect2d(container, subject) {
|
|
|
67681
67788
|
[localSubject.center[0] + coreHalfWidth, localSubject.center[1] + coreHalfHeight],
|
|
67682
67789
|
[localSubject.center[0] - coreHalfWidth, localSubject.center[1] + coreHalfHeight]
|
|
67683
67790
|
];
|
|
67684
|
-
return corners.every((corner) => roundedRectSignedDistance2d(eroded, corner) < -
|
|
67791
|
+
return corners.every((corner) => roundedRectSignedDistance2d(eroded, corner) < -EPS11);
|
|
67685
67792
|
}
|
|
67686
67793
|
function roundedRectStrictlyContainsLoop2d(footprint, loop) {
|
|
67687
67794
|
if (loop.length < 3) return false;
|
|
@@ -67689,7 +67796,7 @@ function roundedRectStrictlyContainsLoop2d(footprint, loop) {
|
|
|
67689
67796
|
const a = loop[idx];
|
|
67690
67797
|
const b = loop[(idx + 1) % loop.length];
|
|
67691
67798
|
const midpoint7 = [(a[0] + b[0]) * 0.5, (a[1] + b[1]) * 0.5];
|
|
67692
|
-
if (roundedRectSignedDistance2d(footprint, a) >= -
|
|
67799
|
+
if (roundedRectSignedDistance2d(footprint, a) >= -EPS11 || roundedRectSignedDistance2d(footprint, midpoint7) >= -EPS11) {
|
|
67693
67800
|
return false;
|
|
67694
67801
|
}
|
|
67695
67802
|
}
|
|
@@ -67701,7 +67808,7 @@ function simplePolygonReplayProfile(plan) {
|
|
|
67701
67808
|
if (![plan.width, plan.height].every(Number.isFinite)) return null;
|
|
67702
67809
|
const halfWidth = Math.abs(plan.width) * 0.5;
|
|
67703
67810
|
const halfHeight = Math.abs(plan.height) * 0.5;
|
|
67704
|
-
if (halfWidth <=
|
|
67811
|
+
if (halfWidth <= EPS11 || halfHeight <= EPS11) return null;
|
|
67705
67812
|
return {
|
|
67706
67813
|
kind: "polygon",
|
|
67707
67814
|
points: [
|
|
@@ -67737,7 +67844,7 @@ function cleanPolyline(points) {
|
|
|
67737
67844
|
for (const point2 of points) {
|
|
67738
67845
|
if (!point2.every(Number.isFinite)) return null;
|
|
67739
67846
|
const previous = clean[clean.length - 1];
|
|
67740
|
-
if (!previous || Math.hypot(point2[0] - previous[0], point2[1] - previous[1], point2[2] - previous[2]) >
|
|
67847
|
+
if (!previous || Math.hypot(point2[0] - previous[0], point2[1] - previous[1], point2[2] - previous[2]) > EPS11) {
|
|
67741
67848
|
clean.push([point2[0], point2[1], point2[2]]);
|
|
67742
67849
|
}
|
|
67743
67850
|
}
|
|
@@ -67816,7 +67923,7 @@ function variableSweepProjectionReplayContext(plan) {
|
|
|
67816
67923
|
return { ok: false, reason: "projection replay requires finite compatible variable-sweep sections." };
|
|
67817
67924
|
}
|
|
67818
67925
|
const sortedT = plan.sections.map((section) => section.t).sort((a, b) => a - b);
|
|
67819
|
-
if (sortedT.some((t, idx) => idx > 0 && t <= sortedT[idx - 1] +
|
|
67926
|
+
if (sortedT.some((t, idx) => idx > 0 && t <= sortedT[idx - 1] + EPS11)) {
|
|
67820
67927
|
return { ok: false, reason: "projection replay requires distinct variable-sweep section parameters." };
|
|
67821
67928
|
}
|
|
67822
67929
|
const profile = nestedPolygonProjectionProfile(plan.sections.map((section) => section.profile));
|
|
@@ -67833,7 +67940,7 @@ function variableSweepProjectionReplayContext(plan) {
|
|
|
67833
67940
|
}
|
|
67834
67941
|
function fromSlicesGroupPlaneFrame(normalInput) {
|
|
67835
67942
|
const length7 = Math.hypot(normalInput[0], normalInput[1], normalInput[2]);
|
|
67836
|
-
if (length7 <=
|
|
67943
|
+
if (length7 <= EPS11) return null;
|
|
67837
67944
|
const normal = normalize8(normalInput);
|
|
67838
67945
|
const [nx, ny, nz] = normal;
|
|
67839
67946
|
if (Math.abs(nz) > 1 - 1e-8) {
|
|
@@ -67866,7 +67973,7 @@ function fromSlicesProjectionReplayContext(plan) {
|
|
|
67866
67973
|
if (sorted.some((slice) => !Number.isFinite(slice.offset))) {
|
|
67867
67974
|
return { ok: false, reason: "projection replay requires finite Shape.fromSlices() offsets." };
|
|
67868
67975
|
}
|
|
67869
|
-
if (sorted.some((slice, idx) => idx > 0 && slice.offset <= sorted[idx - 1].offset +
|
|
67976
|
+
if (sorted.some((slice, idx) => idx > 0 && slice.offset <= sorted[idx - 1].offset + EPS11)) {
|
|
67870
67977
|
return { ok: false, reason: "projection replay requires distinct Shape.fromSlices() offsets." };
|
|
67871
67978
|
}
|
|
67872
67979
|
if (sorted.length === 1) {
|
|
@@ -67894,7 +68001,7 @@ function matrixDistanceScale(matrix) {
|
|
|
67894
68001
|
const sx = matrixColumnLength(matrix, 0);
|
|
67895
68002
|
const sy = matrixColumnLength(matrix, 1);
|
|
67896
68003
|
const sz = matrixColumnLength(matrix, 2);
|
|
67897
|
-
if (sx <=
|
|
68004
|
+
if (sx <= EPS11 || !Number.isFinite(sx)) return null;
|
|
67898
68005
|
if (!nearlyEqual(sx, sy, 1e-9) || !nearlyEqual(sx, sz, 1e-9)) return null;
|
|
67899
68006
|
if (Math.abs(matrixColumnDot(matrix, 0, 1)) > 1e-9 || Math.abs(matrixColumnDot(matrix, 0, 2)) > 1e-9 || Math.abs(matrixColumnDot(matrix, 1, 2)) > 1e-9) {
|
|
67900
68007
|
return null;
|
|
@@ -67912,7 +68019,7 @@ function offsetSolidTransformDistanceScale2(step) {
|
|
|
67912
68019
|
const sy = Math.abs(step.y);
|
|
67913
68020
|
const sz = Math.abs(step.z);
|
|
67914
68021
|
const scale8 = Math.max(sx, sy, sz);
|
|
67915
|
-
if (scale8 <=
|
|
68022
|
+
if (scale8 <= EPS11 || !Number.isFinite(scale8) || !nearlyEqual(sx, scale8, 1e-9) || !nearlyEqual(sy, scale8, 1e-9) || !nearlyEqual(sz, scale8, 1e-9)) {
|
|
67916
68023
|
return null;
|
|
67917
68024
|
}
|
|
67918
68025
|
return scale8;
|
|
@@ -68154,12 +68261,12 @@ function offsetSolidFromSlicesLoftProjectionReplayContext(plan) {
|
|
|
68154
68261
|
if (sorted.some((slice) => !Number.isFinite(slice.offset))) {
|
|
68155
68262
|
return { ok: false, reason: "projection replay requires finite Shape.fromSlices() offsets before offsetSolid()." };
|
|
68156
68263
|
}
|
|
68157
|
-
if (sorted.some((slice, idx) => idx > 0 && slice.offset <= sorted[idx - 1].offset +
|
|
68264
|
+
if (sorted.some((slice, idx) => idx > 0 && slice.offset <= sorted[idx - 1].offset + EPS11)) {
|
|
68158
68265
|
return { ok: false, reason: "projection replay requires distinct Shape.fromSlices() offsets before offsetSolid()." };
|
|
68159
68266
|
}
|
|
68160
68267
|
const zMin = sorted[0].offset - plan.thickness;
|
|
68161
68268
|
const zMax = sorted[sorted.length - 1].offset + plan.thickness;
|
|
68162
|
-
if (zMax <= zMin +
|
|
68269
|
+
if (zMax <= zMin + EPS11) {
|
|
68163
68270
|
return { ok: false, reason: "projection replay cannot derive a collapsed offsetSolid(Shape.fromSlices(...)) vertical span." };
|
|
68164
68271
|
}
|
|
68165
68272
|
const profile = nestedPolygonProjectionProfile(sorted.map((slice) => slice.profile));
|
|
@@ -68245,7 +68352,7 @@ function offsetSolidFullRevolutionProjectionReplayContext(plan) {
|
|
|
68245
68352
|
};
|
|
68246
68353
|
}
|
|
68247
68354
|
const localThickness = plan.thickness / source.context.distanceScale;
|
|
68248
|
-
const offsetIntervals = localThickness >
|
|
68355
|
+
const offsetIntervals = localThickness > EPS11 ? expandRadialIntervalsForOutwardOffset2(baseIntervals, localThickness) : insetRadialIntervalsForFullRevolutionProfile2(source.context.plan.profile, Math.abs(localThickness));
|
|
68249
68356
|
if (!offsetIntervals || offsetIntervals.length === 0) {
|
|
68250
68357
|
return {
|
|
68251
68358
|
ok: false,
|
|
@@ -68267,7 +68374,7 @@ function offsetSolidProjectionReplayContext(plan) {
|
|
|
68267
68374
|
}
|
|
68268
68375
|
const zMin = base.zMin - plan.thickness;
|
|
68269
68376
|
const zMax = base.zMax + plan.thickness;
|
|
68270
|
-
if (zMax <= zMin +
|
|
68377
|
+
if (zMax <= zMin + EPS11) {
|
|
68271
68378
|
return { ok: false, reason: "projection replay cannot derive a collapsed offsetSolid() vertical span." };
|
|
68272
68379
|
}
|
|
68273
68380
|
const distanceScale = offsetSolidBaseDistanceScale(plan.base);
|
|
@@ -70412,9 +70519,84 @@ var ConstraintSketch = class extends Sketch {
|
|
|
70412
70519
|
* Select the single arrangement region that contains the given seed point.
|
|
70413
70520
|
* Throws if no region contains the seed.
|
|
70414
70521
|
*/
|
|
70415
|
-
detectArrangementRegion(
|
|
70522
|
+
detectArrangementRegion(_seed) {
|
|
70416
70523
|
throw new Error("Not implemented");
|
|
70417
70524
|
}
|
|
70525
|
+
/**
|
|
70526
|
+
* Return the solved constrained path as a sampled 2D polyline.
|
|
70527
|
+
*
|
|
70528
|
+
* Use this when a construction rail was authored with `constrainedSketch()`
|
|
70529
|
+
* and should feed another operation such as `Loft.pathOnXz(...)`.
|
|
70530
|
+
* The sketch must contain exactly one profile path.
|
|
70531
|
+
*
|
|
70532
|
+
* @param samples - Samples per curved segment. Default 32.
|
|
70533
|
+
* @returns The solved path as an open polyline.
|
|
70534
|
+
*/
|
|
70535
|
+
toPolyline(samples = 32) {
|
|
70536
|
+
if (!Number.isFinite(samples) || samples < 2) throw new Error("ConstraintSketch.toPolyline() samples must be at least 2");
|
|
70537
|
+
const profileLoops = this.definition.loops.filter((loop) => loop.type === "profile");
|
|
70538
|
+
if (profileLoops.length !== 1) {
|
|
70539
|
+
throw new Error("ConstraintSketch.toPolyline() requires exactly one profile path");
|
|
70540
|
+
}
|
|
70541
|
+
const sampleCount = Math.max(2, Math.round(samples));
|
|
70542
|
+
const pointMap = new Map(this.definition.points.map((point2) => [point2.id, point2]));
|
|
70543
|
+
const lineMap = new Map(this.definition.lines.map((line2) => [line2.id, line2]));
|
|
70544
|
+
const arcMap = new Map(this.definition.arcs.map((arc) => [arc.id, arc]));
|
|
70545
|
+
const bezierMap = new Map(this.definition.beziers.map((bezier) => [bezier.id, bezier]));
|
|
70546
|
+
const points = [];
|
|
70547
|
+
const appendStart = (point2, label) => {
|
|
70548
|
+
const previous = points[points.length - 1];
|
|
70549
|
+
if (!previous) {
|
|
70550
|
+
points.push(point2);
|
|
70551
|
+
return;
|
|
70552
|
+
}
|
|
70553
|
+
if (Math.hypot(point2[0] - previous[0], point2[1] - previous[1]) > 1e-6) {
|
|
70554
|
+
throw new Error(`ConstraintSketch.toPolyline() profile path is not continuous at ${label}`);
|
|
70555
|
+
}
|
|
70556
|
+
};
|
|
70557
|
+
const appendPoint = (point2) => {
|
|
70558
|
+
const previous = points[points.length - 1];
|
|
70559
|
+
if (!previous || Math.hypot(point2[0] - previous[0], point2[1] - previous[1]) > 1e-9) points.push(point2);
|
|
70560
|
+
};
|
|
70561
|
+
const requirePoint = (id, label) => {
|
|
70562
|
+
const point2 = pointMap.get(id);
|
|
70563
|
+
if (!point2) throw new Error(`ConstraintSketch.toPolyline() missing ${label}`);
|
|
70564
|
+
return [point2.x, point2.y];
|
|
70565
|
+
};
|
|
70566
|
+
for (const segment of profileLoops[0].segments) {
|
|
70567
|
+
if (segment.kind === "line") {
|
|
70568
|
+
const line2 = lineMap.get(segment.line);
|
|
70569
|
+
if (!line2) throw new Error(`ConstraintSketch.toPolyline() missing line "${segment.line}"`);
|
|
70570
|
+
appendStart(requirePoint(line2.a, `line "${segment.line}" start point`), `line "${segment.line}"`);
|
|
70571
|
+
appendPoint(requirePoint(line2.b, `line "${segment.line}" end point`));
|
|
70572
|
+
} else if (segment.kind === "arc") {
|
|
70573
|
+
const arc = arcMap.get(segment.arc);
|
|
70574
|
+
if (!arc) throw new Error(`ConstraintSketch.toPolyline() missing arc "${segment.arc}"`);
|
|
70575
|
+
const center = requirePoint(arc.center, `arc "${segment.arc}" center point`);
|
|
70576
|
+
const start = requirePoint(arc.start, `arc "${segment.arc}" start point`);
|
|
70577
|
+
const end = requirePoint(arc.end, `arc "${segment.arc}" end point`);
|
|
70578
|
+
appendStart(start, `arc "${segment.arc}"`);
|
|
70579
|
+
const startAngle = Math.atan2(start[1] - center[1], start[0] - center[0]);
|
|
70580
|
+
const endAngle = Math.atan2(end[1] - center[1], end[0] - center[0]);
|
|
70581
|
+
for (const point2 of tessellateArc(center[0], center[1], arc.radius, startAngle, endAngle, arc.clockwise, sampleCount)) {
|
|
70582
|
+
appendPoint(point2);
|
|
70583
|
+
}
|
|
70584
|
+
} else {
|
|
70585
|
+
const bezier = bezierMap.get(segment.bezier);
|
|
70586
|
+
if (!bezier) throw new Error(`ConstraintSketch.toPolyline() missing bezier "${segment.bezier}"`);
|
|
70587
|
+
const p0 = requirePoint(bezier.p0, `bezier "${segment.bezier}" start point`);
|
|
70588
|
+
const p1 = requirePoint(bezier.p1, `bezier "${segment.bezier}" first control point`);
|
|
70589
|
+
const p2 = requirePoint(bezier.p2, `bezier "${segment.bezier}" second control point`);
|
|
70590
|
+
const p3 = requirePoint(bezier.p3, `bezier "${segment.bezier}" end point`);
|
|
70591
|
+
appendStart(p0, `bezier "${segment.bezier}"`);
|
|
70592
|
+
for (const point2 of tessellateBezier(p0[0], p0[1], p1[0], p1[1], p2[0], p2[1], p3[0], p3[1], sampleCount)) {
|
|
70593
|
+
appendPoint(point2);
|
|
70594
|
+
}
|
|
70595
|
+
}
|
|
70596
|
+
}
|
|
70597
|
+
if (points.length < 2) throw new Error("ConstraintSketch.toPolyline() needs at least 2 points");
|
|
70598
|
+
return points;
|
|
70599
|
+
}
|
|
70418
70600
|
/**
|
|
70419
70601
|
* Re-solve the sketch after changing the value of one existing constraint.
|
|
70420
70602
|
*
|
|
@@ -73368,7 +73550,7 @@ function sampleCubic(p0, p1, p2, p3, tolerance) {
|
|
|
73368
73550
|
}
|
|
73369
73551
|
return out;
|
|
73370
73552
|
}
|
|
73371
|
-
var
|
|
73553
|
+
var EPS12 = 1e-10;
|
|
73372
73554
|
function signedArea7(points) {
|
|
73373
73555
|
let area2 = 0;
|
|
73374
73556
|
for (let i = 0; i < points.length; i++) {
|
|
@@ -73382,7 +73564,7 @@ function removeDuplicateClosing(points) {
|
|
|
73382
73564
|
if (points.length < 2) return points;
|
|
73383
73565
|
const first = points[0];
|
|
73384
73566
|
const last = points[points.length - 1];
|
|
73385
|
-
if (Math.abs(first[0] - last[0]) <
|
|
73567
|
+
if (Math.abs(first[0] - last[0]) < EPS12 && Math.abs(first[1] - last[1]) < EPS12) {
|
|
73386
73568
|
return points.slice(0, -1);
|
|
73387
73569
|
}
|
|
73388
73570
|
return points;
|
|
@@ -73442,7 +73624,7 @@ function loopInfo(points) {
|
|
|
73442
73624
|
return { points, area: area2, absArea: Math.abs(area2), sample };
|
|
73443
73625
|
}
|
|
73444
73626
|
function loopsToSketch(loops) {
|
|
73445
|
-
const infos = loops.filter((l) => l.length >= 3 && Math.abs(signedArea7(l)) >
|
|
73627
|
+
const infos = loops.filter((l) => l.length >= 3 && Math.abs(signedArea7(l)) > EPS12).map(loopInfo);
|
|
73446
73628
|
if (infos.length === 0) return null;
|
|
73447
73629
|
const dominant = [...infos].sort((a, b) => b.absArea - a.absArea)[0];
|
|
73448
73630
|
const dominantSign = dominant.area >= 0 ? 1 : -1;
|
|
@@ -73542,6 +73724,303 @@ function polygonVertices(sides, radius, options) {
|
|
|
73542
73724
|
});
|
|
73543
73725
|
}
|
|
73544
73726
|
|
|
73727
|
+
// src/forge/sketch/loftGuideTypes.ts
|
|
73728
|
+
var LOFT_GUIDE_EPS = 1e-8;
|
|
73729
|
+
|
|
73730
|
+
// src/forge/sketch/loftGuideRailSampling.ts
|
|
73731
|
+
function orientLoftToAxis2(shape, axis) {
|
|
73732
|
+
if (axis === "Z") return shape;
|
|
73733
|
+
if (axis === "Y") return shape.rotateX(-90);
|
|
73734
|
+
return shape.transform([0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1]);
|
|
73735
|
+
}
|
|
73736
|
+
function buildRailEvaluators(rails, axis, start, end, railSamples) {
|
|
73737
|
+
const seen = /* @__PURE__ */ new Set();
|
|
73738
|
+
return rails.map((rail2) => {
|
|
73739
|
+
if (seen.has(rail2.side)) throw new Error(`Loft.withGuideRails() received more than one ${rail2.side} rail`);
|
|
73740
|
+
seen.add(rail2.side);
|
|
73741
|
+
const sampled = sampleRailPath(rail2.path, railSamples);
|
|
73742
|
+
if (sampled.length < 2) throw new Error("Loft guide rails require at least two points");
|
|
73743
|
+
const points = sampled.map((point2) => ({ position: axisPosition2(axis, point2), cross: crossPointForAxis(axis, point2) }));
|
|
73744
|
+
const ordered = points[points.length - 1].position >= points[0].position ? points : [...points].reverse();
|
|
73745
|
+
validateRailCoverage(ordered, start, end);
|
|
73746
|
+
return { side: rail2.side, points: ordered };
|
|
73747
|
+
});
|
|
73748
|
+
}
|
|
73749
|
+
function railCrossAt(rail2, position) {
|
|
73750
|
+
const points = rail2.points;
|
|
73751
|
+
if (position <= points[0].position + LOFT_GUIDE_EPS) return points[0].cross;
|
|
73752
|
+
const last = points[points.length - 1];
|
|
73753
|
+
if (position >= last.position - LOFT_GUIDE_EPS) return last.cross;
|
|
73754
|
+
for (let index = 0; index < points.length - 1; index += 1) {
|
|
73755
|
+
const a = points[index];
|
|
73756
|
+
const b = points[index + 1];
|
|
73757
|
+
if (position >= a.position - LOFT_GUIDE_EPS && position <= b.position + LOFT_GUIDE_EPS) {
|
|
73758
|
+
const t = (position - a.position) / (b.position - a.position);
|
|
73759
|
+
return [lerp5(a.cross[0], b.cross[0], t), lerp5(a.cross[1], b.cross[1], t)];
|
|
73760
|
+
}
|
|
73761
|
+
}
|
|
73762
|
+
throw new Error("Loft guide rail does not cover requested station position");
|
|
73763
|
+
}
|
|
73764
|
+
function validateRailCoverage(points, start, end) {
|
|
73765
|
+
for (let index = 1; index < points.length; index += 1) {
|
|
73766
|
+
if (points[index].position - points[index - 1].position < LOFT_GUIDE_EPS) {
|
|
73767
|
+
throw new Error("Loft guide rails must be monotone along the loft axis");
|
|
73768
|
+
}
|
|
73769
|
+
}
|
|
73770
|
+
if (points[0].position - start > LOFT_GUIDE_EPS || end - points[points.length - 1].position > LOFT_GUIDE_EPS) {
|
|
73771
|
+
throw new Error("Loft guide rails must cover the full station range");
|
|
73772
|
+
}
|
|
73773
|
+
}
|
|
73774
|
+
function sampleRailPath(path4, samples) {
|
|
73775
|
+
if (Array.isArray(path4)) return path4.map((point2, index) => requireVec34(point2, `Loft guide rail point ${index}`));
|
|
73776
|
+
if (path4 instanceof Curve3D || path4 instanceof HermiteCurve3D || path4 instanceof QuinticHermiteCurve3D || path4 instanceof NurbsCurve3D) {
|
|
73777
|
+
return path4.sample(Math.max(2, Math.round(samples))).map((point2, index) => requireVec34(point2, `Loft guide rail sample ${index}`));
|
|
73778
|
+
}
|
|
73779
|
+
throw new Error("Loft guide rail path must be a Vec3[] or ForgeCAD 3D curve");
|
|
73780
|
+
}
|
|
73781
|
+
function requireVec34(point2, label) {
|
|
73782
|
+
if (!Array.isArray(point2) || point2.length !== 3 || !point2.every(Number.isFinite)) {
|
|
73783
|
+
throw new Error(`${label} must be a finite [x, y, z] point`);
|
|
73784
|
+
}
|
|
73785
|
+
return [point2[0], point2[1], point2[2]];
|
|
73786
|
+
}
|
|
73787
|
+
function axisPosition2(axis, point2) {
|
|
73788
|
+
if (axis === "X") return point2[0];
|
|
73789
|
+
if (axis === "Y") return point2[1];
|
|
73790
|
+
return point2[2];
|
|
73791
|
+
}
|
|
73792
|
+
function crossPointForAxis(axis, point2) {
|
|
73793
|
+
if (axis === "X") return [point2[1], point2[2]];
|
|
73794
|
+
if (axis === "Y") return [point2[0], -point2[2]];
|
|
73795
|
+
return [point2[0], point2[1]];
|
|
73796
|
+
}
|
|
73797
|
+
function lerp5(a, b, t) {
|
|
73798
|
+
return a + (b - a) * t;
|
|
73799
|
+
}
|
|
73800
|
+
|
|
73801
|
+
// src/forge/sketch/loftGuided.ts
|
|
73802
|
+
function loftWithGuideRails(stations, rails, options = {}) {
|
|
73803
|
+
if (stations.length < 2) throw new Error("Loft.withGuideRails() requires at least two stations");
|
|
73804
|
+
if (rails.length === 0) throw new Error("Loft.withGuideRails() requires at least one guide rail");
|
|
73805
|
+
const sortedStations = sortedValidStations(stations);
|
|
73806
|
+
const axis = options.axis ?? "Z";
|
|
73807
|
+
const start = sortedStations[0].position;
|
|
73808
|
+
const end = sortedStations[sortedStations.length - 1].position;
|
|
73809
|
+
const railEvaluators = buildRailEvaluators(rails, axis, start, end, options.railSamples ?? 64);
|
|
73810
|
+
const positions = generatedPositions(sortedStations, options.samples);
|
|
73811
|
+
const profiles2 = positions.map((position) => {
|
|
73812
|
+
const source = profileForPosition(sortedStations, position);
|
|
73813
|
+
const bounds2 = boundsForPosition(sortedStations, position);
|
|
73814
|
+
return fitProfileToBounds(source, applyRailsToBounds(bounds2, railEvaluators, position));
|
|
73815
|
+
});
|
|
73816
|
+
const shape = loft(profiles2, positions, {
|
|
73817
|
+
edgeLength: options.edgeLength,
|
|
73818
|
+
boundsPadding: options.boundsPadding
|
|
73819
|
+
});
|
|
73820
|
+
return orientLoftToAxis2(shape, axis);
|
|
73821
|
+
}
|
|
73822
|
+
function sortedValidStations(stations) {
|
|
73823
|
+
const sorted = [...stations].sort((a, b) => a.position - b.position);
|
|
73824
|
+
for (let index = 0; index < sorted.length; index += 1) {
|
|
73825
|
+
if (!Number.isFinite(sorted[index].position)) throw new Error("Loft.withGuideRails station position must be finite");
|
|
73826
|
+
if (!(sorted[index].profile instanceof Sketch)) throw new Error("Loft.withGuideRails() stations must use Sketch profiles");
|
|
73827
|
+
if (index > 0 && sorted[index].position - sorted[index - 1].position < LOFT_GUIDE_EPS) {
|
|
73828
|
+
throw new Error("Loft.withGuideRails() requires unique, strictly increasing station positions");
|
|
73829
|
+
}
|
|
73830
|
+
}
|
|
73831
|
+
return sorted;
|
|
73832
|
+
}
|
|
73833
|
+
function generatedPositions(stations, samples) {
|
|
73834
|
+
const count = Math.max(2, Math.round(samples ?? Math.max(9, (stations.length - 1) * 8 + 1)));
|
|
73835
|
+
const start = stations[0].position;
|
|
73836
|
+
const end = stations[stations.length - 1].position;
|
|
73837
|
+
const values = /* @__PURE__ */ new Set();
|
|
73838
|
+
const positions = [];
|
|
73839
|
+
const addPosition = (position) => {
|
|
73840
|
+
const key = position.toFixed(9);
|
|
73841
|
+
if (!values.has(key)) {
|
|
73842
|
+
values.add(key);
|
|
73843
|
+
positions.push(position);
|
|
73844
|
+
}
|
|
73845
|
+
};
|
|
73846
|
+
for (let index = 0; index < count; index += 1) addPosition(start + (end - start) * index / (count - 1));
|
|
73847
|
+
for (const station of stations) addPosition(station.position);
|
|
73848
|
+
return positions.sort((a, b) => a - b);
|
|
73849
|
+
}
|
|
73850
|
+
function profileForPosition(stations, position) {
|
|
73851
|
+
for (let index = 0; index < stations.length - 1; index += 1) {
|
|
73852
|
+
if (position <= stations[index + 1].position + LOFT_GUIDE_EPS) return stations[index].profile;
|
|
73853
|
+
}
|
|
73854
|
+
return stations[stations.length - 1].profile;
|
|
73855
|
+
}
|
|
73856
|
+
function boundsForPosition(stations, position) {
|
|
73857
|
+
if (position <= stations[0].position + LOFT_GUIDE_EPS) return sketchBounds(stations[0].profile);
|
|
73858
|
+
const last = stations[stations.length - 1];
|
|
73859
|
+
if (position >= last.position - LOFT_GUIDE_EPS) return sketchBounds(last.profile);
|
|
73860
|
+
for (let index = 0; index < stations.length - 1; index += 1) {
|
|
73861
|
+
const a = stations[index];
|
|
73862
|
+
const b = stations[index + 1];
|
|
73863
|
+
if (position >= a.position - LOFT_GUIDE_EPS && position <= b.position + LOFT_GUIDE_EPS) {
|
|
73864
|
+
return lerpBounds(sketchBounds(a.profile), sketchBounds(b.profile), (position - a.position) / (b.position - a.position));
|
|
73865
|
+
}
|
|
73866
|
+
}
|
|
73867
|
+
return sketchBounds(last.profile);
|
|
73868
|
+
}
|
|
73869
|
+
function applyRailsToBounds(bounds2, rails, position) {
|
|
73870
|
+
const centerRail = rails.find((rail2) => rail2.side === "center");
|
|
73871
|
+
const center = centerRail ? railCrossAt(centerRail, position) : void 0;
|
|
73872
|
+
const next = { ...bounds2 };
|
|
73873
|
+
applyAxisRail(next, "X", sideValue(rails, "left", position, 0), sideValue(rails, "right", position, 0), center?.[0]);
|
|
73874
|
+
applyAxisRail(next, "Y", sideValue(rails, "back", position, 1), sideValue(rails, "front", position, 1), center?.[1]);
|
|
73875
|
+
if (next.maxX - next.minX < LOFT_GUIDE_EPS || next.maxY - next.minY < LOFT_GUIDE_EPS) {
|
|
73876
|
+
throw new Error("Loft.withGuideRails() guide rails produced a non-positive section size");
|
|
73877
|
+
}
|
|
73878
|
+
return next;
|
|
73879
|
+
}
|
|
73880
|
+
function sideValue(rails, side, position, crossIndex) {
|
|
73881
|
+
const rail2 = rails.find((entry) => entry.side === side);
|
|
73882
|
+
return rail2 ? railCrossAt(rail2, position)[crossIndex] : void 0;
|
|
73883
|
+
}
|
|
73884
|
+
function applyAxisRail(bounds2, axis, minRail, maxRail, center) {
|
|
73885
|
+
const minKey = axis === "X" ? "minX" : "minY";
|
|
73886
|
+
const maxKey = axis === "X" ? "maxX" : "maxY";
|
|
73887
|
+
const width = bounds2[maxKey] - bounds2[minKey];
|
|
73888
|
+
if (minRail != null && maxRail != null) {
|
|
73889
|
+
if (maxRail - minRail < LOFT_GUIDE_EPS) throw new Error("Loft.withGuideRails() opposite guide rails crossed");
|
|
73890
|
+
if (center != null && Math.abs((minRail + maxRail) / 2 - center) > 1e-5) {
|
|
73891
|
+
throw new Error("Loft.withGuideRails() center rail conflicts with opposite side rails");
|
|
73892
|
+
}
|
|
73893
|
+
bounds2[minKey] = minRail;
|
|
73894
|
+
bounds2[maxKey] = maxRail;
|
|
73895
|
+
} else if (maxRail != null) {
|
|
73896
|
+
bounds2[maxKey] = maxRail;
|
|
73897
|
+
bounds2[minKey] = center != null ? 2 * center - maxRail : maxRail - width;
|
|
73898
|
+
} else if (minRail != null) {
|
|
73899
|
+
bounds2[minKey] = minRail;
|
|
73900
|
+
bounds2[maxKey] = center != null ? 2 * center - minRail : minRail + width;
|
|
73901
|
+
} else if (center != null) {
|
|
73902
|
+
bounds2[minKey] = center - width / 2;
|
|
73903
|
+
bounds2[maxKey] = center + width / 2;
|
|
73904
|
+
}
|
|
73905
|
+
}
|
|
73906
|
+
function fitProfileToBounds(profile, target) {
|
|
73907
|
+
const source = sketchBounds(profile);
|
|
73908
|
+
const sourceWidth = source.maxX - source.minX;
|
|
73909
|
+
const sourceDepth = source.maxY - source.minY;
|
|
73910
|
+
if (sourceWidth < LOFT_GUIDE_EPS || sourceDepth < LOFT_GUIDE_EPS) {
|
|
73911
|
+
throw new Error("Loft.withGuideRails() station profiles must have positive bounds");
|
|
73912
|
+
}
|
|
73913
|
+
const sourceCenter = [(source.minX + source.maxX) / 2, (source.minY + source.maxY) / 2];
|
|
73914
|
+
const targetCenter = [(target.minX + target.maxX) / 2, (target.minY + target.maxY) / 2];
|
|
73915
|
+
return profile.scaleAround(sourceCenter, [(target.maxX - target.minX) / sourceWidth, (target.maxY - target.minY) / sourceDepth]).translate(targetCenter[0] - sourceCenter[0], targetCenter[1] - sourceCenter[1]);
|
|
73916
|
+
}
|
|
73917
|
+
function sketchBounds(profile) {
|
|
73918
|
+
const bounds2 = profile.bounds();
|
|
73919
|
+
return { minX: bounds2.min[0], maxX: bounds2.max[0], minY: bounds2.min[1], maxY: bounds2.max[1] };
|
|
73920
|
+
}
|
|
73921
|
+
function lerpBounds(a, b, t) {
|
|
73922
|
+
return {
|
|
73923
|
+
minX: lerp6(a.minX, b.minX, t),
|
|
73924
|
+
maxX: lerp6(a.maxX, b.maxX, t),
|
|
73925
|
+
minY: lerp6(a.minY, b.minY, t),
|
|
73926
|
+
maxY: lerp6(a.maxY, b.maxY, t)
|
|
73927
|
+
};
|
|
73928
|
+
}
|
|
73929
|
+
function lerp6(a, b, t) {
|
|
73930
|
+
return a + (b - a) * t;
|
|
73931
|
+
}
|
|
73932
|
+
|
|
73933
|
+
// src/forge/sketch/loftNamespace.ts
|
|
73934
|
+
function mapLoftPath2D(path4, label, mapper) {
|
|
73935
|
+
const points = sampleLoftPath2D(path4, label);
|
|
73936
|
+
return points.map((point2, index) => {
|
|
73937
|
+
if (!Array.isArray(point2) || point2.length !== 2 || !point2.every(Number.isFinite)) {
|
|
73938
|
+
throw new Error(`${label} point ${index} must be a finite [x, y] point`);
|
|
73939
|
+
}
|
|
73940
|
+
return mapper([point2[0], point2[1]]);
|
|
73941
|
+
});
|
|
73942
|
+
}
|
|
73943
|
+
function sampleLoftPath2D(path4, label) {
|
|
73944
|
+
if (Array.isArray(path4)) {
|
|
73945
|
+
if (path4.length < 2) throw new Error(`${label} requires at least two [x, y] points`);
|
|
73946
|
+
return path4;
|
|
73947
|
+
}
|
|
73948
|
+
if (!path4 || typeof path4 !== "object" || typeof path4.toPolyline !== "function") {
|
|
73949
|
+
throw new Error(`${label} requires a 2D path, solved constrained path, or [x, y] point array`);
|
|
73950
|
+
}
|
|
73951
|
+
const points = path4.toPolyline();
|
|
73952
|
+
if (!Array.isArray(points) || points.length < 2) throw new Error(`${label} path must produce at least two [x, y] points`);
|
|
73953
|
+
return points;
|
|
73954
|
+
}
|
|
73955
|
+
var Loft = {
|
|
73956
|
+
/** Create a loft station from a 2D profile and an axis position. */
|
|
73957
|
+
station(profile, position) {
|
|
73958
|
+
if (!Number.isFinite(position)) throw new Error("Loft.station position must be finite");
|
|
73959
|
+
return { profile, position };
|
|
73960
|
+
},
|
|
73961
|
+
/** Create a guide rail that constrains the section-local negative-X side. */
|
|
73962
|
+
leftRail(path4) {
|
|
73963
|
+
return { side: "left", path: path4 };
|
|
73964
|
+
},
|
|
73965
|
+
/** Create a guide rail that constrains the section-local positive-X side. */
|
|
73966
|
+
rightRail(path4) {
|
|
73967
|
+
return { side: "right", path: path4 };
|
|
73968
|
+
},
|
|
73969
|
+
/** Create a guide rail that constrains the section-local positive-Y side. */
|
|
73970
|
+
frontRail(path4) {
|
|
73971
|
+
return { side: "front", path: path4 };
|
|
73972
|
+
},
|
|
73973
|
+
/** Create a guide rail that constrains the section-local negative-Y side. */
|
|
73974
|
+
backRail(path4) {
|
|
73975
|
+
return { side: "back", path: path4 };
|
|
73976
|
+
},
|
|
73977
|
+
/** Create a guide rail that moves section centers along the loft. */
|
|
73978
|
+
centerRail(path4) {
|
|
73979
|
+
return { side: "center", path: path4 };
|
|
73980
|
+
},
|
|
73981
|
+
/**
|
|
73982
|
+
* Place a 2D guide path onto the XZ plane.
|
|
73983
|
+
*
|
|
73984
|
+
* The path's first coordinate becomes X and its second coordinate becomes Z.
|
|
73985
|
+
* Use this for left/right silhouette rails authored with `path()` or `constrainedSketch()`.
|
|
73986
|
+
*/
|
|
73987
|
+
pathOnXz(path4, y = 0) {
|
|
73988
|
+
if (!Number.isFinite(y)) throw new Error("Loft.pathOnXz y must be finite");
|
|
73989
|
+
return mapLoftPath2D(path4, "Loft.pathOnXz", ([x, z]) => [x, y, z]);
|
|
73990
|
+
},
|
|
73991
|
+
/**
|
|
73992
|
+
* Place a 2D guide path onto the YZ plane.
|
|
73993
|
+
*
|
|
73994
|
+
* The path's first coordinate becomes Y and its second coordinate becomes Z.
|
|
73995
|
+
* Use this for front/back crown rails authored with `path()` or `constrainedSketch()`.
|
|
73996
|
+
*/
|
|
73997
|
+
pathOnYz(path4, x = 0) {
|
|
73998
|
+
if (!Number.isFinite(x)) throw new Error("Loft.pathOnYz x must be finite");
|
|
73999
|
+
return mapLoftPath2D(path4, "Loft.pathOnYz", ([y, z]) => [x, y, z]);
|
|
74000
|
+
},
|
|
74001
|
+
/**
|
|
74002
|
+
* Place a 2D guide path onto the XY plane.
|
|
74003
|
+
*
|
|
74004
|
+
* The path's first coordinate becomes X and its second coordinate becomes Y.
|
|
74005
|
+
* Use this when lofting along X or Y and a rail lives in a horizontal sketch plane.
|
|
74006
|
+
*/
|
|
74007
|
+
pathOnXy(path4, z = 0) {
|
|
74008
|
+
if (!Number.isFinite(z)) throw new Error("Loft.pathOnXy z must be finite");
|
|
74009
|
+
return mapLoftPath2D(path4, "Loft.pathOnXy", ([x, y]) => [x, y, z]);
|
|
74010
|
+
},
|
|
74011
|
+
/**
|
|
74012
|
+
* Loft through profile stations while forcing generated sections to follow guide rails.
|
|
74013
|
+
*
|
|
74014
|
+
* Stations define the cross-section family. Guide rails define the side or center
|
|
74015
|
+
* paths the loft must pass through. With opposite side rails, the section is scaled
|
|
74016
|
+
* to touch both rails. With one side rail, the section keeps its interpolated size
|
|
74017
|
+
* unless a center rail is also present.
|
|
74018
|
+
*/
|
|
74019
|
+
withGuideRails(stations, rails, options = {}) {
|
|
74020
|
+
return loftWithGuideRails(stations, rails, options);
|
|
74021
|
+
}
|
|
74022
|
+
};
|
|
74023
|
+
|
|
73545
74024
|
// src/forge/sketch/highlights.ts
|
|
73546
74025
|
var collectedHighlights = [];
|
|
73547
74026
|
function resetHighlights() {
|
|
@@ -73819,7 +74298,7 @@ Sketch.prototype.onFace = function(parentOrFace, faceOrOpts, maybeOpts = {}) {
|
|
|
73819
74298
|
};
|
|
73820
74299
|
|
|
73821
74300
|
// src/forge/sketch/regions.ts
|
|
73822
|
-
var
|
|
74301
|
+
var EPS13 = 1e-9;
|
|
73823
74302
|
var MIN_POINT_DIST = 1e-7;
|
|
73824
74303
|
function dist22(a, b) {
|
|
73825
74304
|
return Math.hypot(a[0] - b[0], a[1] - b[1]);
|
|
@@ -73833,7 +74312,7 @@ function signedArea8(pts) {
|
|
|
73833
74312
|
}
|
|
73834
74313
|
return area2 * 0.5;
|
|
73835
74314
|
}
|
|
73836
|
-
function
|
|
74315
|
+
function polygonCentroid2(pts) {
|
|
73837
74316
|
let cx = 0;
|
|
73838
74317
|
let cy = 0;
|
|
73839
74318
|
let a2 = 0;
|
|
@@ -73845,7 +74324,7 @@ function polygonCentroid(pts) {
|
|
|
73845
74324
|
cx += (x1 + x2) * cross13;
|
|
73846
74325
|
cy += (y1 + y2) * cross13;
|
|
73847
74326
|
}
|
|
73848
|
-
if (Math.abs(a2) <
|
|
74327
|
+
if (Math.abs(a2) < EPS13) {
|
|
73849
74328
|
let sx = 0;
|
|
73850
74329
|
let sy = 0;
|
|
73851
74330
|
for (const [x, y] of pts) {
|
|
@@ -73880,8 +74359,8 @@ function extractRegionCandidates(sketch) {
|
|
|
73880
74359
|
const rawLoops = sketch.toPolygons();
|
|
73881
74360
|
const loops = rawLoops.map((loop) => loop.map(([x, y]) => [x, y])).map((pts) => removeDuplicateClose(pts)).filter((pts) => pts.length >= 3).map((pts) => {
|
|
73882
74361
|
const area2 = signedArea8(pts);
|
|
73883
|
-
return { points: pts, area: area2, absArea: Math.abs(area2), sample:
|
|
73884
|
-
}).filter((loop) => loop.absArea >
|
|
74362
|
+
return { points: pts, area: area2, absArea: Math.abs(area2), sample: polygonCentroid2(pts) };
|
|
74363
|
+
}).filter((loop) => loop.absArea > EPS13);
|
|
73885
74364
|
if (loops.length === 0) return [];
|
|
73886
74365
|
const outers = loops.filter((l) => l.area > 0);
|
|
73887
74366
|
const holes = loops.filter((l) => l.area < 0);
|
|
@@ -74935,7 +75414,7 @@ function surfacePatch(curves, options = {}) {
|
|
|
74935
75414
|
|
|
74936
75415
|
// src/forge/sketch/svgImport.ts
|
|
74937
75416
|
var MIN_POINT_DIST2 = 1e-7;
|
|
74938
|
-
var
|
|
75417
|
+
var EPS14 = 1e-9;
|
|
74939
75418
|
var DEFAULT_STYLE = {
|
|
74940
75419
|
fill: "black",
|
|
74941
75420
|
stroke: "none",
|
|
@@ -74985,7 +75464,7 @@ function centroidFallback(points) {
|
|
|
74985
75464
|
}
|
|
74986
75465
|
return [sx / points.length, sy / points.length];
|
|
74987
75466
|
}
|
|
74988
|
-
function
|
|
75467
|
+
function polygonCentroid3(points) {
|
|
74989
75468
|
let cx = 0;
|
|
74990
75469
|
let cy = 0;
|
|
74991
75470
|
let a2 = 0;
|
|
@@ -74997,7 +75476,7 @@ function polygonCentroid2(points) {
|
|
|
74997
75476
|
cx += (x1 + x2) * cross13;
|
|
74998
75477
|
cy += (y1 + y2) * cross13;
|
|
74999
75478
|
}
|
|
75000
|
-
if (Math.abs(a2) <
|
|
75479
|
+
if (Math.abs(a2) < EPS14) return centroidFallback(points);
|
|
75001
75480
|
const factor = 1 / (3 * a2);
|
|
75002
75481
|
return [cx * factor, cy * factor];
|
|
75003
75482
|
}
|
|
@@ -75240,7 +75719,7 @@ function vectorAngle(ux, uy, vx, vy) {
|
|
|
75240
75719
|
function sampleArc2(x1, y1, rxInput, ryInput, xAxisRotation, largeArcFlag, sweepFlag, x2, y2, tolerance, arcSegmentsMin) {
|
|
75241
75720
|
let rx = Math.abs(rxInput);
|
|
75242
75721
|
let ry = Math.abs(ryInput);
|
|
75243
|
-
if (rx <
|
|
75722
|
+
if (rx < EPS14 || ry < EPS14) return [[x2, y2]];
|
|
75244
75723
|
const phi = xAxisRotation * Math.PI / 180;
|
|
75245
75724
|
const cosPhi = Math.cos(phi);
|
|
75246
75725
|
const sinPhi = Math.sin(phi);
|
|
@@ -75260,7 +75739,7 @@ function sampleArc2(x1, y1, rxInput, ryInput, xAxisRotation, largeArcFlag, sweep
|
|
|
75260
75739
|
const y1p2 = sqr(y1p);
|
|
75261
75740
|
let factorNum = rx2 * ry2 - rx2 * y1p2 - ry2 * x1p2;
|
|
75262
75741
|
let factorDen = rx2 * y1p2 + ry2 * x1p2;
|
|
75263
|
-
if (factorDen <
|
|
75742
|
+
if (factorDen < EPS14) factorDen = EPS14;
|
|
75264
75743
|
factorNum = Math.max(0, factorNum);
|
|
75265
75744
|
const factor = (largeArcFlag === sweepFlag ? -1 : 1) * Math.sqrt(factorNum / factorDen);
|
|
75266
75745
|
const cxp = factor * (rx * y1p / ry);
|
|
@@ -75541,7 +76020,7 @@ function parsePathData(d, tolerance, arcSegments) {
|
|
|
75541
76020
|
return out;
|
|
75542
76021
|
}
|
|
75543
76022
|
function buildCircleSubpath(cx, cy, rx, ry, tolerance) {
|
|
75544
|
-
if (!(rx >
|
|
76023
|
+
if (!(rx > EPS14) || !(ry > EPS14)) return [];
|
|
75545
76024
|
const circumference = 2 * Math.PI * Math.max(rx, ry);
|
|
75546
76025
|
const segments = clamp11(Math.ceil(circumference / Math.max(tolerance, 1e-4)), 16, 720);
|
|
75547
76026
|
const points = [];
|
|
@@ -75562,10 +76041,10 @@ function appendArcSegment(points, cx, cy, rx, ry, startAngle, endAngle, toleranc
|
|
|
75562
76041
|
function buildRoundedRectSubpath(x, y, width, height, rxInput, ryInput, tolerance) {
|
|
75563
76042
|
const w = width;
|
|
75564
76043
|
const h = height;
|
|
75565
|
-
if (!(w >
|
|
76044
|
+
if (!(w > EPS14) || !(h > EPS14)) return [];
|
|
75566
76045
|
const rx = clamp11(rxInput, 0, w / 2);
|
|
75567
76046
|
const ry = clamp11(ryInput, 0, h / 2);
|
|
75568
|
-
if (rx <
|
|
76047
|
+
if (rx < EPS14 || ry < EPS14) {
|
|
75569
76048
|
return [
|
|
75570
76049
|
{
|
|
75571
76050
|
points: [
|
|
@@ -75610,7 +76089,7 @@ function geometryFromElement(tag, attrs, tolerance, arcSegments) {
|
|
|
75610
76089
|
const y = parseLength(attrs.y, 0);
|
|
75611
76090
|
const width = parseLength(attrs.width, NaN);
|
|
75612
76091
|
const height = parseLength(attrs.height, NaN);
|
|
75613
|
-
if (!(width >
|
|
76092
|
+
if (!(width > EPS14) || !(height > EPS14)) return [];
|
|
75614
76093
|
let rx = parseLength(attrs.rx, NaN);
|
|
75615
76094
|
let ry = parseLength(attrs.ry, NaN);
|
|
75616
76095
|
if (!Number.isFinite(rx) && Number.isFinite(ry)) rx = ry;
|
|
@@ -75623,7 +76102,7 @@ function geometryFromElement(tag, attrs, tolerance, arcSegments) {
|
|
|
75623
76102
|
const cx = parseLength(attrs.cx, 0);
|
|
75624
76103
|
const cy = parseLength(attrs.cy, 0);
|
|
75625
76104
|
const r = parseLength(attrs.r, NaN);
|
|
75626
|
-
if (!(r >
|
|
76105
|
+
if (!(r > EPS14)) return [];
|
|
75627
76106
|
return buildCircleSubpath(cx, cy, r, r, tolerance);
|
|
75628
76107
|
}
|
|
75629
76108
|
if (t === "ellipse") {
|
|
@@ -75631,7 +76110,7 @@ function geometryFromElement(tag, attrs, tolerance, arcSegments) {
|
|
|
75631
76110
|
const cy = parseLength(attrs.cy, 0);
|
|
75632
76111
|
const rx = parseLength(attrs.rx, NaN);
|
|
75633
76112
|
const ry = parseLength(attrs.ry, NaN);
|
|
75634
|
-
if (!(rx >
|
|
76113
|
+
if (!(rx > EPS14) || !(ry > EPS14)) return [];
|
|
75635
76114
|
return buildCircleSubpath(cx, cy, rx, ry, tolerance);
|
|
75636
76115
|
}
|
|
75637
76116
|
if (t === "line") {
|
|
@@ -75666,7 +76145,7 @@ function sanitizeSubpaths(subpaths) {
|
|
|
75666
76145
|
for (const subpath of subpaths) {
|
|
75667
76146
|
const points = removeDuplicateClosingPoint(subpath.points);
|
|
75668
76147
|
if (subpath.closed) {
|
|
75669
|
-
if (points.length >= 3 && Math.abs(signedArea9(points)) >
|
|
76148
|
+
if (points.length >= 3 && Math.abs(signedArea9(points)) > EPS14) {
|
|
75670
76149
|
out.push({ points, closed: true });
|
|
75671
76150
|
}
|
|
75672
76151
|
} else if (points.length >= 2) {
|
|
@@ -75676,10 +76155,10 @@ function sanitizeSubpaths(subpaths) {
|
|
|
75676
76155
|
return out;
|
|
75677
76156
|
}
|
|
75678
76157
|
function styleHasFill(style) {
|
|
75679
|
-
return style.fill !== "none" && style.fill !== "transparent" && style.opacity >
|
|
76158
|
+
return style.fill !== "none" && style.fill !== "transparent" && style.opacity > EPS14 && style.fillOpacity > EPS14;
|
|
75680
76159
|
}
|
|
75681
76160
|
function styleHasStroke(style) {
|
|
75682
|
-
return style.stroke !== "none" && style.stroke !== "transparent" && style.strokeWidth >
|
|
76161
|
+
return style.stroke !== "none" && style.stroke !== "transparent" && style.strokeWidth > EPS14 && style.opacity > EPS14 && style.strokeOpacity > EPS14;
|
|
75683
76162
|
}
|
|
75684
76163
|
function loopInfosFromClosedSubpaths(subpaths) {
|
|
75685
76164
|
const loops = [];
|
|
@@ -75689,12 +76168,12 @@ function loopInfosFromClosedSubpaths(subpaths) {
|
|
|
75689
76168
|
if (points.length < 3) continue;
|
|
75690
76169
|
const area2 = signedArea9(points);
|
|
75691
76170
|
const absArea = Math.abs(area2);
|
|
75692
|
-
if (absArea <=
|
|
76171
|
+
if (absArea <= EPS14) continue;
|
|
75693
76172
|
loops.push({
|
|
75694
76173
|
points,
|
|
75695
76174
|
area: area2,
|
|
75696
76175
|
absArea,
|
|
75697
|
-
sample:
|
|
76176
|
+
sample: polygonCentroid3(points)
|
|
75698
76177
|
});
|
|
75699
76178
|
}
|
|
75700
76179
|
return loops;
|
|
@@ -75710,7 +76189,7 @@ function buildFilledSketch(subpaths, fillRule) {
|
|
|
75710
76189
|
let depth = 0;
|
|
75711
76190
|
for (const other of loops) {
|
|
75712
76191
|
if (other === loop) continue;
|
|
75713
|
-
if (other.absArea <= loop.absArea +
|
|
76192
|
+
if (other.absArea <= loop.absArea + EPS14) continue;
|
|
75714
76193
|
if (pointInPolygon3(loop.sample, other.points)) depth += 1;
|
|
75715
76194
|
}
|
|
75716
76195
|
const loopSketch = polygon(loop.points);
|
|
@@ -75748,7 +76227,7 @@ function buildClosedStroke(loopPoints, width, join14) {
|
|
|
75748
76227
|
return ring.isEmpty() ? null : ring;
|
|
75749
76228
|
}
|
|
75750
76229
|
function buildStrokeSketch(subpaths, width, join14) {
|
|
75751
|
-
if (!(width >
|
|
76230
|
+
if (!(width > EPS14)) return null;
|
|
75752
76231
|
const joinMode = strokeJoinToSketchJoin(join14);
|
|
75753
76232
|
const parts = [];
|
|
75754
76233
|
for (const subpath of subpaths) {
|
|
@@ -75770,8 +76249,8 @@ function buildRegionSketches(sketch) {
|
|
|
75770
76249
|
const rawLoops = sketch.toPolygons();
|
|
75771
76250
|
const loops = rawLoops.map((loop) => loop.map(([x, y]) => [x, y])).map((points) => removeDuplicateClosingPoint(points)).filter((points) => points.length >= 3).map((points) => {
|
|
75772
76251
|
const area2 = signedArea9(points);
|
|
75773
|
-
return { points, area: area2, absArea: Math.abs(area2), sample:
|
|
75774
|
-
}).filter((loop) => loop.absArea >
|
|
76252
|
+
return { points, area: area2, absArea: Math.abs(area2), sample: polygonCentroid3(points) };
|
|
76253
|
+
}).filter((loop) => loop.absArea > EPS14);
|
|
75775
76254
|
if (loops.length === 0) return [];
|
|
75776
76255
|
const outers = loops.filter((loop) => loop.area > 0);
|
|
75777
76256
|
const holes = loops.filter((loop) => loop.area < 0);
|
|
@@ -75811,7 +76290,7 @@ function filterRegions(sketch, options) {
|
|
|
75811
76290
|
if (regions.length === 0) return sketch;
|
|
75812
76291
|
const largestArea = regions[0].area;
|
|
75813
76292
|
const minArea = Math.max(options.minRegionArea, largestArea * options.minRegionAreaRatio);
|
|
75814
|
-
let kept = regions.filter((region) => region.area +
|
|
76293
|
+
let kept = regions.filter((region) => region.area + EPS14 >= minArea);
|
|
75815
76294
|
if (kept.length === 0) kept = [regions[0]];
|
|
75816
76295
|
if (options.regionSelection === "largest") {
|
|
75817
76296
|
kept = [kept[0]];
|
|
@@ -75833,13 +76312,13 @@ function fitSketchToMaxDimensions(sketch, options) {
|
|
|
75833
76312
|
const width = Math.max(0, (max4[0] ?? 0) - (min2[0] ?? 0));
|
|
75834
76313
|
const height = Math.max(0, (max4[1] ?? 0) - (min2[1] ?? 0));
|
|
75835
76314
|
let fitScale = Number.POSITIVE_INFINITY;
|
|
75836
|
-
if (constrainWidth && width >
|
|
76315
|
+
if (constrainWidth && width > EPS14) {
|
|
75837
76316
|
fitScale = Math.min(fitScale, options.maxWidth / width);
|
|
75838
76317
|
}
|
|
75839
|
-
if (constrainHeight && height >
|
|
76318
|
+
if (constrainHeight && height > EPS14) {
|
|
75840
76319
|
fitScale = Math.min(fitScale, options.maxHeight / height);
|
|
75841
76320
|
}
|
|
75842
|
-
if (!Number.isFinite(fitScale) || fitScale <= 0 || fitScale >= 1 -
|
|
76321
|
+
if (!Number.isFinite(fitScale) || fitScale <= 0 || fitScale >= 1 - EPS14) {
|
|
75843
76322
|
return sketch;
|
|
75844
76323
|
}
|
|
75845
76324
|
return sketch.scale(fitScale);
|
|
@@ -75851,7 +76330,7 @@ function centerSketchOnOrigin(sketch, options) {
|
|
|
75851
76330
|
const max4 = bounds2.max;
|
|
75852
76331
|
const cx = ((min2[0] ?? 0) + (max4[0] ?? 0)) * 0.5;
|
|
75853
76332
|
const cy = ((min2[1] ?? 0) + (max4[1] ?? 0)) * 0.5;
|
|
75854
|
-
if (Math.abs(cx) <=
|
|
76333
|
+
if (Math.abs(cx) <= EPS14 && Math.abs(cy) <= EPS14) return sketch;
|
|
75855
76334
|
return sketch.translate(-cx, -cy);
|
|
75856
76335
|
}
|
|
75857
76336
|
function computeRootNormalizeMatrix(attrs, options) {
|
|
@@ -75862,7 +76341,7 @@ function computeRootNormalizeMatrix(attrs, options) {
|
|
|
75862
76341
|
const flipY = viewBox.length >= 4 ? viewBox[1] + viewBox[3] : Number.isFinite(fallbackHeight) ? fallbackHeight : 0;
|
|
75863
76342
|
matrix = multiplyMatrix(matrixTranslate(0, flipY), matrixScale(1, -1));
|
|
75864
76343
|
}
|
|
75865
|
-
if (Math.abs(options.scale - 1) >
|
|
76344
|
+
if (Math.abs(options.scale - 1) > EPS14) {
|
|
75866
76345
|
matrix = multiplyMatrix(matrixScale(options.scale, options.scale), matrix);
|
|
75867
76346
|
}
|
|
75868
76347
|
return matrix;
|
|
@@ -75913,7 +76392,7 @@ function parseSvgGeometry(svgText, options) {
|
|
|
75913
76392
|
topLevelSvgSeen = true;
|
|
75914
76393
|
combinedTransform = multiplyMatrix(computeRootNormalizeMatrix(attrs, options), combinedTransform);
|
|
75915
76394
|
}
|
|
75916
|
-
const hidden = parent.hidden || mergedStyle.display === "none" || mergedStyle.visibility === "hidden" || mergedStyle.opacity <=
|
|
76395
|
+
const hidden = parent.hidden || mergedStyle.display === "none" || mergedStyle.visibility === "hidden" || mergedStyle.opacity <= EPS14;
|
|
75917
76396
|
const inDefs = parent.inDefs || tag === "defs" || tag === "symbol" || tag === "clipPath" || tag === "mask" || tag === "pattern";
|
|
75918
76397
|
const ctx = {
|
|
75919
76398
|
transform: combinedTransform,
|
|
@@ -77544,8 +78023,8 @@ function computeSheetCutSequence(sheet) {
|
|
|
77544
78023
|
const result = [];
|
|
77545
78024
|
function decompose(rx, ry, rw, rh, pieces) {
|
|
77546
78025
|
if (pieces.length <= 1) return;
|
|
77547
|
-
const
|
|
77548
|
-
const bestCut = findBestCut(rx, ry, rw, rh, pieces,
|
|
78026
|
+
const EPS19 = 0.01;
|
|
78027
|
+
const bestCut = findBestCut(rx, ry, rw, rh, pieces, EPS19);
|
|
77549
78028
|
if (!bestCut) return;
|
|
77550
78029
|
const { axis, pos, groupA, groupB } = bestCut;
|
|
77551
78030
|
if (axis === "V") {
|
|
@@ -80921,6 +81400,7 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
|
|
|
80921
81400
|
nurbsSurface,
|
|
80922
81401
|
spline2d,
|
|
80923
81402
|
spline3d,
|
|
81403
|
+
Loft,
|
|
80924
81404
|
loft,
|
|
80925
81405
|
loftAlongSpine,
|
|
80926
81406
|
sweep,
|
|
@@ -81462,8 +81942,8 @@ function findRuntime() {
|
|
|
81462
81942
|
}
|
|
81463
81943
|
|
|
81464
81944
|
// cli/check-api-contracts.ts
|
|
81465
|
-
var
|
|
81466
|
-
function approx(a, b, eps =
|
|
81945
|
+
var EPS15 = 1e-6;
|
|
81946
|
+
function approx(a, b, eps = EPS15) {
|
|
81467
81947
|
return Math.abs(a - b) <= eps;
|
|
81468
81948
|
}
|
|
81469
81949
|
function expectClose(actual, expected, label) {
|
|
@@ -84760,14 +85240,14 @@ function formatMetrics(metrics) {
|
|
|
84760
85240
|
}
|
|
84761
85241
|
|
|
84762
85242
|
// cli/check-constraints.ts
|
|
84763
|
-
var
|
|
85243
|
+
var EPS16 = 0.01;
|
|
84764
85244
|
var ANGLE_EPS = 0.1;
|
|
84765
85245
|
var _verbose = false;
|
|
84766
85246
|
var _update = false;
|
|
84767
|
-
function approx2(a, b, eps =
|
|
85247
|
+
function approx2(a, b, eps = EPS16) {
|
|
84768
85248
|
return Math.abs(a - b) <= eps;
|
|
84769
85249
|
}
|
|
84770
|
-
function assertApprox(actual, expected, label, eps =
|
|
85250
|
+
function assertApprox(actual, expected, label, eps = EPS16) {
|
|
84771
85251
|
assert4(
|
|
84772
85252
|
approx2(actual, expected, eps),
|
|
84773
85253
|
`${label}: expected ${expected.toFixed(4)}, got ${actual.toFixed(4)} (diff=${Math.abs(actual - expected).toFixed(6)})`
|
|
@@ -85030,7 +85510,7 @@ function testPointOnLine() {
|
|
|
85030
85510
|
assertConverged(result, "pointOnLine");
|
|
85031
85511
|
const pt = getPoint(result.definition, p2);
|
|
85032
85512
|
assertApprox(pt.y, 0, "pointOnLine: y should be 0");
|
|
85033
|
-
assert4(pt.x >= -
|
|
85513
|
+
assert4(pt.x >= -EPS16 && pt.x <= 10 + EPS16, `pointOnLine: x=${pt.x} should be in [0, 10]`);
|
|
85034
85514
|
}
|
|
85035
85515
|
function testRightTriangle() {
|
|
85036
85516
|
const s = constrainedSketch();
|
|
@@ -88451,6 +88931,7 @@ function buildInspectManifest({ options, result, scriptPath, projectRoot, emitte
|
|
|
88451
88931
|
maxRootDistance: result.distance?.maxRootDistance ?? 0,
|
|
88452
88932
|
components: result.distance?.components ?? [],
|
|
88453
88933
|
objects: result.distance?.objects ?? [],
|
|
88934
|
+
gapEdgeCount: result.distance?.gapEdgeCount ?? 0,
|
|
88454
88935
|
gapEdges: result.distance?.gapEdges ?? [],
|
|
88455
88936
|
connectivity: result.distance?.connectivity,
|
|
88456
88937
|
warnings: result.distance?.warnings ?? [],
|
|
@@ -88465,6 +88946,7 @@ function buildInspectManifest({ options, result, scriptPath, projectRoot, emitte
|
|
|
88465
88946
|
decode: "source objects are translucent ghost geometry; boolean intersection volumes use per-finding collisions[].color palette entries",
|
|
88466
88947
|
method: result.collisions?.method,
|
|
88467
88948
|
options: result.collisions?.options,
|
|
88949
|
+
broadphase: result.collisions?.broadphase,
|
|
88468
88950
|
style: result.collisions?.style,
|
|
88469
88951
|
objectCount: result.collisions?.objectCount ?? 0,
|
|
88470
88952
|
collisionCount: result.collisions?.collisionCount ?? 0,
|
|
@@ -91819,11 +92301,11 @@ import { execSync as execSync2 } from "child_process";
|
|
|
91819
92301
|
|
|
91820
92302
|
// cli/check-text.ts
|
|
91821
92303
|
import assert8 from "assert/strict";
|
|
91822
|
-
var
|
|
91823
|
-
function approx4(a, b, eps =
|
|
92304
|
+
var EPS17 = 1e-3;
|
|
92305
|
+
function approx4(a, b, eps = EPS17) {
|
|
91824
92306
|
return Math.abs(a - b) <= eps;
|
|
91825
92307
|
}
|
|
91826
|
-
function expectClose3(actual, expected, label, eps =
|
|
92308
|
+
function expectClose3(actual, expected, label, eps = EPS17) {
|
|
91827
92309
|
assert8(approx4(actual, expected, eps), `${label}: expected ${expected}, got ${actual}`);
|
|
91828
92310
|
}
|
|
91829
92311
|
function bounds(sk) {
|
|
@@ -91878,7 +92360,7 @@ function checkTextWidth() {
|
|
|
91878
92360
|
Math.abs(reported - w) < tolerance,
|
|
91879
92361
|
`textWidth ${reported.toFixed(3)} should be close to sketch width ${w.toFixed(3)} within ${tolerance.toFixed(3)}`
|
|
91880
92362
|
);
|
|
91881
|
-
assert8(reported >= w -
|
|
92363
|
+
assert8(reported >= w - EPS17, `textWidth ${reported.toFixed(3)} should be >= sketch width ${w.toFixed(3)}`);
|
|
91882
92364
|
}
|
|
91883
92365
|
function checkHorizontalAlignment() {
|
|
91884
92366
|
const left = text2d("CAD", { size: 10, align: "left" });
|
|
@@ -92516,8 +92998,8 @@ function formatAssemblyAuditMarkdown(report) {
|
|
|
92516
92998
|
}
|
|
92517
92999
|
|
|
92518
93000
|
// cli/check-transforms.ts
|
|
92519
|
-
var
|
|
92520
|
-
function approx5(a, b, eps =
|
|
93001
|
+
var EPS18 = 1e-6;
|
|
93002
|
+
function approx5(a, b, eps = EPS18) {
|
|
92521
93003
|
return Math.abs(a - b) <= eps;
|
|
92522
93004
|
}
|
|
92523
93005
|
function assertVec(actual, expected, label) {
|
|
@@ -92943,7 +93425,7 @@ function testBevelGearTopSectionCircularity() {
|
|
|
92943
93425
|
const topSlice = gear.slice(bb.max[2] - 1e-4).bounds();
|
|
92944
93426
|
const spanX = topSlice.max[0] - topSlice.min[0];
|
|
92945
93427
|
const spanY = topSlice.max[1] - topSlice.min[1];
|
|
92946
|
-
assert9(spanX >
|
|
93428
|
+
assert9(spanX > EPS18 && spanY > EPS18, `Expected non-degenerate top slice, got spanX=${spanX}, spanY=${spanY}`);
|
|
92947
93429
|
const aspect = spanY / spanX;
|
|
92948
93430
|
assert9(aspect > 0.85 && aspect < 1.15, `Expected near-circular top slice, got spanX=${spanX}, spanY=${spanY}, aspect=${aspect}`);
|
|
92949
93431
|
}
|
|
@@ -103020,7 +103502,7 @@ Available: ${available}`);
|
|
|
103020
103502
|
return { visible, hidden, summary };
|
|
103021
103503
|
}
|
|
103022
103504
|
|
|
103023
|
-
//
|
|
103505
|
+
// src/forge/inspection/collision.ts
|
|
103024
103506
|
var DEFAULT_COLLISION_INSPECTION_OPTIONS = {
|
|
103025
103507
|
minOverlapVolume: 0.1
|
|
103026
103508
|
};
|
|
@@ -103084,6 +103566,51 @@ function bboxOverlaps(a, b) {
|
|
|
103084
103566
|
function collisionId(a, b) {
|
|
103085
103567
|
return `${a.id}__${b.id}`;
|
|
103086
103568
|
}
|
|
103569
|
+
function estimateSweepPairCount(entries, axis) {
|
|
103570
|
+
const ordered = entries.map((entry) => ({ min: entry.min[axis], max: entry.max[axis] })).sort((a, b) => a.min - b.min || a.max - b.max);
|
|
103571
|
+
const endValues = ordered.map((entry) => entry.max).sort((a, b) => a - b);
|
|
103572
|
+
let expired = 0;
|
|
103573
|
+
let count = 0;
|
|
103574
|
+
for (let seen = 0; seen < ordered.length; seen += 1) {
|
|
103575
|
+
const currentMin = ordered[seen].min;
|
|
103576
|
+
while (expired < seen && endValues[expired] <= currentMin) expired += 1;
|
|
103577
|
+
count += seen - expired;
|
|
103578
|
+
}
|
|
103579
|
+
return count;
|
|
103580
|
+
}
|
|
103581
|
+
function chooseSweepAxis(entries) {
|
|
103582
|
+
let bestAxis = 0;
|
|
103583
|
+
let bestCount = estimateSweepPairCount(entries, bestAxis);
|
|
103584
|
+
for (const axis of [1, 2]) {
|
|
103585
|
+
const count = estimateSweepPairCount(entries, axis);
|
|
103586
|
+
if (count < bestCount) {
|
|
103587
|
+
bestAxis = axis;
|
|
103588
|
+
bestCount = count;
|
|
103589
|
+
}
|
|
103590
|
+
}
|
|
103591
|
+
return bestAxis;
|
|
103592
|
+
}
|
|
103593
|
+
function collectCandidatePairs(entries) {
|
|
103594
|
+
if (entries.length < 2) return { pairs: [], bboxPairChecks: 0 };
|
|
103595
|
+
const axis = chooseSweepAxis(entries);
|
|
103596
|
+
const ordered = entries.map((entry, index) => ({ entry, index })).sort((a, b) => a.entry.min[axis] - b.entry.min[axis] || a.entry.max[axis] - b.entry.max[axis] || a.index - b.index);
|
|
103597
|
+
let active = [];
|
|
103598
|
+
const pairs = [];
|
|
103599
|
+
let bboxPairChecks = 0;
|
|
103600
|
+
for (const current of ordered) {
|
|
103601
|
+
active = active.filter((candidate) => candidate.entry.max[axis] > current.entry.min[axis]);
|
|
103602
|
+
for (const candidate of active) {
|
|
103603
|
+
bboxPairChecks += 1;
|
|
103604
|
+
if (!bboxOverlaps(candidate.entry, current.entry)) continue;
|
|
103605
|
+
const sourceIndex = Math.min(candidate.index, current.index);
|
|
103606
|
+
const targetIndex = Math.max(candidate.index, current.index);
|
|
103607
|
+
pairs.push({ sourceIndex, targetIndex });
|
|
103608
|
+
}
|
|
103609
|
+
active.push(current);
|
|
103610
|
+
}
|
|
103611
|
+
pairs.sort((a, b) => a.sourceIndex - b.sourceIndex || a.targetIndex - b.targetIndex);
|
|
103612
|
+
return { pairs, bboxPairChecks };
|
|
103613
|
+
}
|
|
103087
103614
|
function serializeCollisionFinding(finding2) {
|
|
103088
103615
|
return {
|
|
103089
103616
|
index: finding2.index,
|
|
@@ -103104,32 +103631,30 @@ function analyzeCollisionIntersections(entries, rawOptions = {}) {
|
|
|
103104
103631
|
const warnings = [];
|
|
103105
103632
|
const collisions = [];
|
|
103106
103633
|
const preparedEntries = entries.map((entry) => prepareEntry2(entry));
|
|
103107
|
-
|
|
103108
|
-
|
|
103109
|
-
|
|
103110
|
-
|
|
103111
|
-
|
|
103112
|
-
|
|
103113
|
-
|
|
103114
|
-
|
|
103115
|
-
|
|
103116
|
-
|
|
103117
|
-
collisions.
|
|
103118
|
-
|
|
103119
|
-
|
|
103120
|
-
|
|
103121
|
-
|
|
103122
|
-
|
|
103123
|
-
|
|
103124
|
-
|
|
103125
|
-
|
|
103126
|
-
|
|
103127
|
-
|
|
103128
|
-
|
|
103129
|
-
|
|
103130
|
-
|
|
103131
|
-
warnings.push(`Could not boolean-test ${a.name} against ${b.name}: ${message}`);
|
|
103132
|
-
}
|
|
103634
|
+
const { pairs: candidatePairs, bboxPairChecks } = collectCandidatePairs(preparedEntries);
|
|
103635
|
+
for (const pair of candidatePairs) {
|
|
103636
|
+
const a = preparedEntries[pair.sourceIndex];
|
|
103637
|
+
const b = preparedEntries[pair.targetIndex];
|
|
103638
|
+
try {
|
|
103639
|
+
const hit = a.shape.intersect(b.shape);
|
|
103640
|
+
if (hit.isEmpty()) continue;
|
|
103641
|
+
const overlapVolume = hit.volume();
|
|
103642
|
+
if (!Number.isFinite(overlapVolume) || overlapVolume <= options.minOverlapVolume) continue;
|
|
103643
|
+
collisions.push({
|
|
103644
|
+
index: collisions.length + 1,
|
|
103645
|
+
id: collisionId(a, b),
|
|
103646
|
+
sourceIndex: pair.sourceIndex,
|
|
103647
|
+
targetIndex: pair.targetIndex,
|
|
103648
|
+
sourceId: a.id,
|
|
103649
|
+
targetId: b.id,
|
|
103650
|
+
sourceName: a.name,
|
|
103651
|
+
targetName: b.name,
|
|
103652
|
+
overlapVolume,
|
|
103653
|
+
shape: hit
|
|
103654
|
+
});
|
|
103655
|
+
} catch (err2) {
|
|
103656
|
+
const message = err2 instanceof Error ? err2.message : String(err2);
|
|
103657
|
+
warnings.push(`Could not boolean-test ${a.name} against ${b.name}: ${message}`);
|
|
103133
103658
|
}
|
|
103134
103659
|
}
|
|
103135
103660
|
const objects = preparedEntries.map((entry, index) => ({
|
|
@@ -103147,6 +103672,12 @@ function analyzeCollisionIntersections(entries, rawOptions = {}) {
|
|
|
103147
103672
|
return {
|
|
103148
103673
|
method: "boolean-intersection",
|
|
103149
103674
|
options,
|
|
103675
|
+
broadphase: {
|
|
103676
|
+
method: "sweep-and-prune",
|
|
103677
|
+
allPairCount: preparedEntries.length * (preparedEntries.length - 1) / 2,
|
|
103678
|
+
bboxPairChecks,
|
|
103679
|
+
booleanPairChecks: candidatePairs.length
|
|
103680
|
+
},
|
|
103150
103681
|
objectCount: objects.length,
|
|
103151
103682
|
collisionCount: collisions.length,
|
|
103152
103683
|
objects,
|
|
@@ -103357,7 +103888,7 @@ function sampleWallThickness(mesh, triangleSamples, rawOptions) {
|
|
|
103357
103888
|
};
|
|
103358
103889
|
}
|
|
103359
103890
|
|
|
103360
|
-
//
|
|
103891
|
+
// src/forge/inspection/physical-connectivity.ts
|
|
103361
103892
|
var DEFAULT_PHYSICAL_CONNECTIVITY_OPTIONS = {
|
|
103362
103893
|
contactTolerance: 0.05,
|
|
103363
103894
|
minOverlapVolume: 0.1,
|
|
@@ -103443,7 +103974,7 @@ function bboxOverlapVolume(a, b) {
|
|
|
103443
103974
|
}
|
|
103444
103975
|
return volume;
|
|
103445
103976
|
}
|
|
103446
|
-
function
|
|
103977
|
+
function estimateSweepPairCount2(entries, axis, tolerance) {
|
|
103447
103978
|
const ordered = entries.map((entry) => ({ min: entry.min[axis], max: entry.max[axis] })).sort((a, b) => a.min - b.min || a.max - b.max);
|
|
103448
103979
|
const endValues = ordered.map((entry) => entry.max + tolerance).sort((a, b) => a - b);
|
|
103449
103980
|
let expired = 0;
|
|
@@ -103455,11 +103986,11 @@ function estimateSweepPairCount(entries, axis, tolerance) {
|
|
|
103455
103986
|
}
|
|
103456
103987
|
return count;
|
|
103457
103988
|
}
|
|
103458
|
-
function
|
|
103989
|
+
function chooseSweepAxis2(entries, tolerance) {
|
|
103459
103990
|
let bestAxis = 0;
|
|
103460
|
-
let bestCount =
|
|
103991
|
+
let bestCount = estimateSweepPairCount2(entries, bestAxis, tolerance);
|
|
103461
103992
|
for (const axis of [1, 2]) {
|
|
103462
|
-
const count =
|
|
103993
|
+
const count = estimateSweepPairCount2(entries, axis, tolerance);
|
|
103463
103994
|
if (count < bestCount) {
|
|
103464
103995
|
bestAxis = axis;
|
|
103465
103996
|
bestCount = count;
|
|
@@ -103467,9 +103998,9 @@ function chooseSweepAxis(entries, tolerance) {
|
|
|
103467
103998
|
}
|
|
103468
103999
|
return bestAxis;
|
|
103469
104000
|
}
|
|
103470
|
-
function
|
|
104001
|
+
function collectCandidatePairs2(entries, tolerance) {
|
|
103471
104002
|
if (entries.length < 2) return [];
|
|
103472
|
-
const axis =
|
|
104003
|
+
const axis = chooseSweepAxis2(entries, tolerance);
|
|
103473
104004
|
const ordered = entries.map((entry, index) => ({ entry, index })).sort((a, b) => a.entry.min[axis] - b.entry.min[axis] || a.entry.max[axis] - b.entry.max[axis] || a.index - b.index);
|
|
103474
104005
|
let active = [];
|
|
103475
104006
|
const pairs = [];
|
|
@@ -103544,7 +104075,7 @@ function analyzePhysicalConnectivity2(entries, rawOptions = {}) {
|
|
|
103544
104075
|
const warnings = [];
|
|
103545
104076
|
const edges = [];
|
|
103546
104077
|
const unionFind = new UnionFind2(entries.length);
|
|
103547
|
-
for (const pair of
|
|
104078
|
+
for (const pair of collectCandidatePairs2(entries, options.contactTolerance)) {
|
|
103548
104079
|
const i = pair.sourceIndex;
|
|
103549
104080
|
const j = pair.targetIndex;
|
|
103550
104081
|
const a = entries[i];
|