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.
Files changed (39) hide show
  1. package/dist/assets/{AdminPage-uTtcSXtn.js → AdminPage-Da6hhpJx.js} +1 -1
  2. package/dist/assets/{BlogPage-DYJMjWx3.js → BlogPage-Bl_sKeWb.js} +1 -1
  3. package/dist/assets/{DocsPage-C58f0K5v.js → DocsPage-Blz3Tp4j.js} +1 -1
  4. package/dist/assets/{EditorApp-DNH1TEz1.js → EditorApp-CuiPbtn5.js} +32 -7
  5. package/dist/assets/{EmbedViewer-CMXWA2LX.js → EmbedViewer-BFG6-Ufm.js} +2 -2
  6. package/dist/assets/{LandingPageProofDriven-CAu2OZFn.js → LandingPageProofDriven-DB9fQd5P.js} +1 -1
  7. package/dist/assets/{PricingPage-BIgW7m3X.js → PricingPage-BMxYT_F0.js} +1 -1
  8. package/dist/assets/{SettingsPage-N1l1tMXO.js → SettingsPage-VVQNrCAg.js} +1 -1
  9. package/dist/assets/{app-CFy7g5WP.js → app-Dl9ymBWC.js} +293 -36
  10. package/dist/assets/cli/{render-BrVVdj_T.js → render-CFtwKCCY.js} +10 -1081
  11. package/dist/assets/{sectionPlaneMath-CykEnkvQ.js → distance-BEC2RjJi.js} +1897 -288
  12. package/dist/assets/{evalWorker-c_SB9gg3.js → evalWorker-CRvbzTXm.js} +555 -83
  13. package/dist/assets/{manifold-Cjk7WhRs.js → manifold-B9QSr-qP.js} +1 -1
  14. package/dist/assets/{manifold-Dp6pvFr6.js → manifold-DpBXFS2K.js} +1 -1
  15. package/dist/assets/{manifold-CRoBhJKH.js → manifold-DzZ4VRPs.js} +2 -2
  16. package/dist/assets/{renderSceneState-3DfsSASX.js → renderSceneState-BuAXF2jh.js} +1 -1
  17. package/dist/assets/{reportWorker-BLkuIoS8.js → reportWorker-BNWEnRg1.js} +555 -83
  18. package/dist/cli/render.html +1 -1
  19. package/dist/docs/index.html +1 -1
  20. package/dist/docs-raw/beta-operations.md +4 -0
  21. package/dist/docs-raw/deployment.md +38 -23
  22. package/dist/docs-raw/generated/concepts.md +82 -5
  23. package/dist/docs-raw/generated/curves.md +97 -5
  24. package/dist/docs-raw/generated/sketch.md +9 -1
  25. package/dist/docs-raw/guides/inspection-bundles.md +9 -3
  26. package/dist/docs-raw/runbook.md +3 -3
  27. package/dist/index.html +1 -1
  28. package/dist/sitemap.xml +6 -6
  29. package/dist-cli/forgecad.js +828 -297
  30. package/dist-cli/forgecad.js.map +1 -1
  31. package/dist-skill/CONTEXT.md +115 -9
  32. package/dist-skill/docs/generated/curves.md +97 -5
  33. package/dist-skill/docs/generated/sketch.md +9 -1
  34. package/dist-skill/docs/guides/inspection-bundles.md +9 -3
  35. package/dist-skill/docs-dev/generated/curves.md +97 -5
  36. package/dist-skill/docs-dev/generated/sketch.md +9 -1
  37. package/dist-skill/docs-dev/guides/inspection-bundles.md +9 -3
  38. package/examples/api/guided-loft-olive-oil-bottle.forge.js +135 -0
  39. package/package.json +20 -2
@@ -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(cross2(preferredUp, firstTangent));
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(cross2(fallback, firstTangent));
10924
+ x = normalize3(cross3(fallback, firstTangent));
10850
10925
  if (!x) return null;
10851
10926
  }
10852
- let y = normalize3(cross2(firstTangent, x));
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 = cross2(prevT, nextT);
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 cross2(a, b) {
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 = cross2(axis, v);
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 cross3(a, b) {
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(cross3(reference, normal), "Shape.fromSlices profile u axis");
11277
- return { u, v: cross3(normal, u), normal };
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, cross3(col1, col2));
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(cross3(offset2, axis)) / axisLength;
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(cross3(up, tangent), "variableSweep profile x axis");
12045
+ frameX = normalizeVec32(cross32(up, tangent), "variableSweep profile x axis");
11971
12046
  } catch {
11972
12047
  return null;
11973
12048
  }
11974
- const frameY = cross3(tangent, frameX);
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 EPS18 = 1e-8;
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 / EPS18)}:${Math.round(y / EPS18)}:${Math.round(z / EPS18)}`;
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 EPS5 = 1e-9;
15450
+ var EPS6 = 1e-9;
15376
15451
  function finitePositive(value) {
15377
- return Number.isFinite(value) && value > EPS5;
15452
+ return Number.isFinite(value) && value > EPS6;
15378
15453
  }
15379
15454
  function clampNonNegative(value) {
15380
- return Math.abs(value) <= EPS5 ? 0 : 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]) > EPS5 || Math.abs(matrix[7]) > EPS5 || Math.abs(matrix[11]) > EPS5 || Math.abs(matrix[15] - 1) > EPS5) {
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) > EPS5 || Math.abs(sx - sz) > EPS5) return null;
15397
- if (Math.abs(dot12(col0, col1)) > EPS5 || Math.abs(dot12(col0, col2)) > EPS5 || Math.abs(dot12(col1, col2)) > EPS5) return null;
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) <= EPS5 && Math.abs(sx - sz) <= EPS5 ? sx : null;
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) <= EPS5) return base;
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 < -EPS5 || offsetRadiusTop < -EPS5) return null;
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 <= EPS5) return point2;
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) <= EPS5;
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 < -EPS5 || !finitePositive(xMax) || !finitePositive(zMax - zMin)) return null;
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) > EPS5 || Math.abs(dot12) > EPS5 * xScale * yScale) return null;
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) > EPS5) return null;
15658
+ if (Math.abs(plan.degrees - 360) > EPS6) return null;
15584
15659
  const circle2 = circleFootprintFromProfile(plan.profile);
15585
- if (!circle2 || circle2.center[0] <= EPS5) return null;
15660
+ if (!circle2 || circle2.center[0] <= EPS6) return null;
15586
15661
  const minorRadius = circle2.radius + minorRadiusOffset;
15587
- if (!finitePositive(minorRadius) || minorRadius >= circle2.center[0] - EPS5) return null;
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 > EPS5) return null;
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) > EPS5) return null;
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 <= EPS5) return null;
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 < -EPS5 || !finitePositive(outerRadius) || !finitePositive(height) || outerRadius <= innerRadius + EPS5) return null;
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 <= EPS5) {
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 - EPS5) return null;
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(cross32(offset2, axis)) / axisLength;
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, cross32(col1, col2));
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 = cross32(ab, ac);
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 cross32(a, b) {
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(cross32(reference, normal), "Shape.fromSlices profile u axis");
19531
- return { u, v: cross32(normal, u), normal };
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 cross33(a, b) {
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(cross33(seed, n));
23845
- const v = normalizeAxis(cross33(n, u));
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(cross33(edgeVec, depthVec));
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 cross34(a, b) {
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(cross34(axis, seed));
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(cross34(cAxis, cUp));
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(cross34(pAxis, pUp));
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 cross35(a, b) {
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(cross35(childAxis, childUp));
25537
- const pRight = normalize33(cross35(parentAxis, parentUp));
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 EPS6 = 1e-10;
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 < EPS6) {
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 cross36(a, b) {
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 < EPS6) return [0, 0, 0];
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 = cross36(axisA.direction, axisB.direction);
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 = cross36(axisA.direction, axisB.direction);
34690
+ const dirCross = cross37(axisA.direction, axisB.direction);
34616
34691
  const delta = sub34(axisB.origin, axisA.origin);
34617
- const offsetCross = cross36(delta, axisA.direction);
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(cross36(n1, n2));
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 = cross36(faceA.normal, faceB.normal);
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 = cross36(delta, axis.direction);
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 cornerX = curr.kind === "line" || curr.kind === "move" ? prev.kind === "line" || prev.kind === "move" ? 0 : 0 : 0;
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 EPS7 = 1e-9;
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 < EPS7) throw new Error("beltDrive: coincident tangent points produced a zero-length direction.");
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 < EPS7) {
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 <= EPS7) {
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) < EPS7) return candidateA;
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 EPS8 = 1e-8;
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 < EPS8) return [0, 0, 1];
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, EPS8)) ** Math.max(exponent - 1, 1e-3);
58632
- const ny = Math.sign(y) * Math.abs(y / Math.max(ry, EPS8)) ** Math.max(exponent - 1, 1e-3);
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 < EPS8 ? [Math.sign(cos5), Math.sign(sin5)] : [nx / nLen, ny / 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, EPS8);
58649
- if (side === "top" || side === "bottom") return Math.max(width, EPS8);
58650
- return Math.max(width, depth, EPS8);
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 - EPS8 || axisValue > bAxis + EPS8) continue;
58797
- const span = Math.max(EPS8, bAxis - aAxis);
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 > EPS8) {
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 EPS9 = 1e-8;
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 < EPS9) return [...fallback];
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, sideValue, offsetValue = 0) {
60143
+ constructor(skin, name = skin.name, sideValue2, offsetValue = 0) {
60037
60144
  this.skin = skin;
60038
60145
  this.name = name;
60039
- this.sideValue = 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 cross37(a, b) {
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(cross37(sub35(b, a), sub35(c, a)));
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 = cross37(sub35(triangle.b, triangle.a), sub35(triangle.c, triangle.a));
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(cross37(forward, c.up));
63294
- const up = norm4(cross37(right, forward));
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(cross37(dirModel, frame.forward));
63339
- pushCandidate(cross37(dirModel, frame.up));
63340
- pushCandidate(cross37(dirModel, frame.right));
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 EPS10 = 1e-6;
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 = EPS10) {
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 < EPS10 || sy < EPS10) {
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 <= EPS10 || inner >= outer - EPS10) return null;
65955
- if (inner <= EPS10) {
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) <= EPS10)) {
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) <= EPS10) {
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) <= EPS10) {
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 <= EPS10 || halfY <= EPS10 || zMax - zMin <= EPS10) return null;
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]) <= EPS10) return null;
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 <= EPS10 || Math.max(bottomRadius, topRadius) <= EPS10) return null;
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 <= EPS10 || minorRadius <= EPS10 || minorRadius >= majorRadius - EPS10) return null;
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 <= EPS10 || height + 2 * input.thickness <= EPS10) return null;
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 + EPS10).sort((a, b) => a[0] - b[0]);
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] + EPS10) {
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 + EPS10) return false;
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 <= EPS10 || outer - inner <= EPS10) return null;
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 <= EPS10 ? [[0, 0], ...outerArc] : [
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 <= EPS10) return point2;
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 <= EPS10) return null;
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 <= EPS10) return null;
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 <= EPS10) return null;
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 <= EPS10 || yScale <= EPS10 || !nearlyEqual(xScale, yScale, 1e-8) || Math.abs(dotAxes) > 1e-8 * xScale * yScale) {
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 <= EPS10 || height <= EPS10) return null;
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]) <= EPS10 || Math.abs(basis.xAxis[1]) <= EPS10;
66420
- const yAxisAligned = Math.abs(basis.yAxis[0]) <= EPS10 || Math.abs(basis.yAxis[1]) <= EPS10;
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 > EPS10 && Math.abs(dotAxes) <= EPS10 * Math.max(1, 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 <= EPS10 || height <= EPS10) return null;
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 <= EPS10 || yScale <= EPS10 || !nearlyEqual(xScale, yScale, 1e-8) || Math.abs(dotAxes) > 1e-8 * xScale * yScale) {
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 <= EPS10 || height <= EPS10) return null;
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]) <= EPS10 || Math.hypot(basis.yAxis[0], basis.yAxis[1]) <= EPS10) return null;
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 <= EPS10) return null;
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 <= EPS10 || halfHeight <= EPS10) return null;
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 - EPS10;
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 - EPS10 ? [min2, max4] : null;
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) <= EPS10) return null;
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 <= EPS10) return null;
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 <= EPS10) return null;
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) <= EPS10) return null;
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]) <= EPS10) return null;
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 <= EPS10) return null;
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 > EPS10 ? [center - deviation, center + deviation] : null;
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 > EPS10 ? [center - deviation, center + deviation] : null;
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 <= EPS10 || halfHeight <= EPS10) return null;
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 > EPS10 && (ax <= 1e-6 * max4 || ay <= 1e-6 * 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 >= -EPS10;
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 >= -EPS10 && profiles2.every((profile) => profileSupportsDefendedMiterOffsetSideInterval(profile, 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 < -EPS10) {
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 >= -EPS10) {
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 + EPS10) return false;
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 <= EPS10) return null;
66949
+ if (directionLength <= EPS11) return null;
66843
66950
  const localDelta = delta / directionLength;
66844
- if (Math.abs(localDelta) <= EPS10) return null;
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 < -EPS10) {
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 < -EPS10) return null;
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) <= EPS10) return null;
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) + EPS10) return null;
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 < -EPS10) return null;
66916
- if (distance5 <= EPS10) return cloneProfileCompilePlan(plan);
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 + EPS10 ? translatedProfileIfNeeded(circleProjectionProfile(circle2.radius - distance5, plan.segments), circle2.center) : null;
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 + EPS10 && rect2.halfHeight > distance5 + EPS10 ? translatedProfileIfNeeded(
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 + EPS10 || roundedRect2.halfHeight <= distance5 + EPS10 || roundedRect2.radius <= distance5 + EPS10) {
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 < -EPS10) return null;
66962
- if (distance5 <= EPS10) return cloneProfileCompilePlan(plan);
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 < -EPS10) return null;
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 < -EPS10) {
67141
+ if (delta < -EPS11) {
67035
67142
  const directionLength = Math.hypot(direction2[0], direction2[1]);
67036
- if (directionLength <= EPS10) return null;
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 <= EPS10) return null;
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 <= EPS10 || halfHeight <= EPS10 || radius <= EPS10 || radius > Math.min(halfWidth, halfHeight) + EPS10) return null;
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 > EPS10 ? [center - deviation, center + deviation] : null;
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 <= EPS10) return null;
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 <= EPS10 || halfHeight <= EPS10) return null;
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 > EPS10 ? [center - deviation, center + deviation] : null;
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 >= -EPS10) {
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 + EPS10 ? [min2, max4] : null;
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 >= -EPS10) {
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 + EPS10 ? [min2, max4] : null;
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 < -EPS10) return null;
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] < -EPS10) return null;
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] < -EPS10) return null;
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 <= EPS10) return null;
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 < -EPS10 || outer <= EPS10) return null;
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 + EPS10) return null;
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 + EPS10 || rect2.halfHeight <= distance5 + EPS10) return null;
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 + EPS10 || roundedRect2.halfHeight <= distance5 + EPS10 || roundedRect2.radius <= distance5 + EPS10) {
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 - EPS10;
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 + EPS10 ? { kind: "circle", footprint: { center: footprint.footprint.center, radius: footprint.footprint.radius - distance5 } } : null;
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 + EPS10 && footprint.footprint.halfHeight > distance5 + EPS10 ? {
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 + EPS10 && footprint.footprint.halfHeight > distance5 + EPS10 && footprint.footprint.radius > distance5 + EPS10 ? {
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 <= EPS10) return null;
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 <= EPS10 * EPS10) return Math.hypot(point2[0] - a[0], point2[1] - a[1]);
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 + EPS10;
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 - EPS10) return false;
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 - EPS10 || Math.hypot(midpoint7[0] - circle2.center[0], midpoint7[1] - circle2.center[1]) >= circle2.radius - EPS10) {
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 + EPS10 && roundedRect2.center[0] + halfX < rect2.center[0] + rect2.halfWidth - EPS10 && roundedRect2.center[1] - halfY > rect2.center[1] - rect2.halfHeight + EPS10 && roundedRect2.center[1] + halfY < rect2.center[1] + rect2.halfHeight - EPS10;
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 <= EPS10 || sxOnCy <= EPS10) && (syOnCx <= EPS10 || syOnCy <= EPS10) && nearlyEqual(sxOnCx + sxOnCy, 1, 1e-6) && nearlyEqual(syOnCx + syOnCy, 1, 1e-6);
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 <= EPS10) {
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 > EPS10 && away[0] * sx >= -EPS10 && away[1] * sy >= -EPS10) {
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 - EPS10;
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 < -EPS10;
67768
+ return roundedRectSignedDistance2d(footprint, circle2.center) + circle2.radius < -EPS11;
67662
67769
  }
67663
67770
  function roundedRectStrictlyContainsRoundedRect2d(container, subject) {
67664
- if (subject.radius > container.radius + EPS10) return false;
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 <= EPS10 || eroded.halfHeight <= EPS10 || eroded.radius < -EPS10) return false;
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) < -EPS10);
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) >= -EPS10 || roundedRectSignedDistance2d(footprint, midpoint7) >= -EPS10) {
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 <= EPS10 || halfHeight <= EPS10) return null;
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]) > EPS10) {
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] + EPS10)) {
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 <= EPS10) return null;
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 + EPS10)) {
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 <= EPS10 || !Number.isFinite(sx)) return null;
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 <= EPS10 || !Number.isFinite(scale8) || !nearlyEqual(sx, scale8, 1e-9) || !nearlyEqual(sy, scale8, 1e-9) || !nearlyEqual(sz, scale8, 1e-9)) {
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 + EPS10)) {
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 + EPS10) {
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 > EPS10 ? expandRadialIntervalsForOutwardOffset2(baseIntervals, localThickness) : insetRadialIntervalsForFullRevolutionProfile2(source.context.plan.profile, Math.abs(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 + EPS10) {
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(seed) {
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 EPS11 = 1e-10;
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]) < EPS11 && Math.abs(first[1] - last[1]) < EPS11) {
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)) > EPS11).map(loopInfo);
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 EPS12 = 1e-9;
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 polygonCentroid(pts) {
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) < EPS12) {
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: polygonCentroid(pts) };
73884
- }).filter((loop) => loop.absArea > EPS12);
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 EPS13 = 1e-9;
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 polygonCentroid2(points) {
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) < EPS13) return centroidFallback(points);
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 < EPS13 || ry < EPS13) return [[x2, y2]];
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 < EPS13) factorDen = EPS13;
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 > EPS13) || !(ry > EPS13)) return [];
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 > EPS13) || !(h > EPS13)) return [];
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 < EPS13 || ry < EPS13) {
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 > EPS13) || !(height > EPS13)) return [];
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 > EPS13)) return [];
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 > EPS13) || !(ry > EPS13)) return [];
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)) > EPS13) {
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 > EPS13 && style.fillOpacity > EPS13;
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 > EPS13 && style.opacity > EPS13 && style.strokeOpacity > EPS13;
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 <= EPS13) continue;
76171
+ if (absArea <= EPS14) continue;
75693
76172
  loops.push({
75694
76173
  points,
75695
76174
  area: area2,
75696
76175
  absArea,
75697
- sample: polygonCentroid2(points)
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 + EPS13) continue;
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 > EPS13)) return null;
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: polygonCentroid2(points) };
75774
- }).filter((loop) => loop.absArea > EPS13);
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 + EPS13 >= minArea);
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 > EPS13) {
76315
+ if (constrainWidth && width > EPS14) {
75837
76316
  fitScale = Math.min(fitScale, options.maxWidth / width);
75838
76317
  }
75839
- if (constrainHeight && height > EPS13) {
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 - EPS13) {
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) <= EPS13 && Math.abs(cy) <= EPS13) return sketch;
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) > EPS13) {
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 <= EPS13;
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 EPS18 = 0.01;
77548
- const bestCut = findBestCut(rx, ry, rw, rh, pieces, EPS18);
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 EPS14 = 1e-6;
81466
- function approx(a, b, eps = EPS14) {
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 EPS15 = 0.01;
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 = EPS15) {
85247
+ function approx2(a, b, eps = EPS16) {
84768
85248
  return Math.abs(a - b) <= eps;
84769
85249
  }
84770
- function assertApprox(actual, expected, label, eps = EPS15) {
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 >= -EPS15 && pt.x <= 10 + EPS15, `pointOnLine: x=${pt.x} should be in [0, 10]`);
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 EPS16 = 1e-3;
91823
- function approx4(a, b, eps = EPS16) {
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 = EPS16) {
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 - EPS16, `textWidth ${reported.toFixed(3)} should be >= sketch width ${w.toFixed(3)}`);
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 EPS17 = 1e-6;
92520
- function approx5(a, b, eps = EPS17) {
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 > EPS17 && spanY > EPS17, `Expected non-degenerate top slice, got spanX=${spanX}, spanY=${spanY}`);
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
- // cli/collision-inspection.ts
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
- for (let i = 0; i < preparedEntries.length; i += 1) {
103108
- for (let j = i + 1; j < preparedEntries.length; j += 1) {
103109
- const a = preparedEntries[i];
103110
- const b = preparedEntries[j];
103111
- if (!bboxOverlaps(a, b)) continue;
103112
- try {
103113
- const hit = a.shape.intersect(b.shape);
103114
- if (hit.isEmpty()) continue;
103115
- const overlapVolume = hit.volume();
103116
- if (!Number.isFinite(overlapVolume) || overlapVolume <= options.minOverlapVolume) continue;
103117
- collisions.push({
103118
- index: collisions.length + 1,
103119
- id: collisionId(a, b),
103120
- sourceIndex: i,
103121
- targetIndex: j,
103122
- sourceId: a.id,
103123
- targetId: b.id,
103124
- sourceName: a.name,
103125
- targetName: b.name,
103126
- overlapVolume,
103127
- shape: hit
103128
- });
103129
- } catch (err2) {
103130
- const message = err2 instanceof Error ? err2.message : String(err2);
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
- // cli/physical-connectivity.ts
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 estimateSweepPairCount(entries, axis, tolerance) {
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 chooseSweepAxis(entries, tolerance) {
103989
+ function chooseSweepAxis2(entries, tolerance) {
103459
103990
  let bestAxis = 0;
103460
- let bestCount = estimateSweepPairCount(entries, bestAxis, tolerance);
103991
+ let bestCount = estimateSweepPairCount2(entries, bestAxis, tolerance);
103461
103992
  for (const axis of [1, 2]) {
103462
- const count = estimateSweepPairCount(entries, axis, tolerance);
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 collectCandidatePairs(entries, tolerance) {
104001
+ function collectCandidatePairs2(entries, tolerance) {
103471
104002
  if (entries.length < 2) return [];
103472
- const axis = chooseSweepAxis(entries, tolerance);
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 collectCandidatePairs(entries, options.contactTolerance)) {
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];