brepjs 18.124.8 → 18.126.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/brepjs.js CHANGED
@@ -16,7 +16,7 @@ import { a as isSameShape, i as isEqualShape, n as getHashCode, o as simplify$1,
16
16
  import { a as curveIsPeriodic, c as curvePointAt, d as flipOrientation, f as getCurveType, h as offsetWire2D, i as curveIsClosed, l as curveStartPoint, m as interpolateCurve, n as curveAxis, o as curveLength, p as getOrientation, r as curveEndPoint, s as curvePeriod, t as approximateCurve, u as curveTangentAt } from "./curveFns-Bh_PHl9N.js";
17
17
  import { a as meshEdges$1, c as meshMultiLOD, d as createMeshCache, i as mesh$1, l as buildMeshCacheKey, n as exportSTEP, o as meshLODs, r as exportSTL, s as meshLODsProgressive, t as exportIGES, u as clearMeshCache } from "./meshFns-CpfeEN1b.js";
18
18
  import { n as getAtOrThrow, r as lastOrThrow, t as firstOrThrow } from "./arrayAccess-DrUGPADn.js";
19
- import { _ as makeThreePointArc, d as makeCircle, h as makeLine, l as makeBSplineInterpolation, n as fill, r as makeFace, s as assembleWire } from "./surfaceBuilders-B7WlmoVr.js";
19
+ import { _ as makeThreePointArc, d as makeCircle, h as makeLine, l as makeBSplineInterpolation, n as fill, p as makeEllipseArc, r as makeFace, s as assembleWire, u as makeBezierCurve } from "./surfaceBuilders-B7WlmoVr.js";
20
20
  import { A as toBufferGeometryData, C as shellWithEvolution, D as getNurbsCurveData, E as fuseAllBisect, M as toLODGeometryData, N as toLODGeometryLevels, O as getNurbsSurfaceData, P as toLineGeometryData, S as intersectWithEvolution, T as cutAllBisect, _ as positionOnCurve, a as healFace, b as filletWithEvolution, c as isValid$1, d as draft$1, f as fillet$1, g as variableFillet, h as thicken$1, i as heal$1, j as toGroupedBufferGeometryData, k as chamferDistAngle, l as solidFromShell, m as shell$1, n as fixSelfIntersection, o as healSolid, p as offset$1, r as fixShape, s as healWire, t as autoHeal, u as chamfer$1, v as chamferWithEvolution, w as checkBoolean, x as fuseWithEvolution, y as cutWithEvolution } from "./healingFns-Digw1d6D.js";
21
21
  import { A as setJointValue, B as createAssemblyNode, C as cylindricalJoint, D as planarJoint, E as mechanismDOF, F as quatMultiply, G as circularPattern, H as removeChild, I as quatRotate, J as exportAssemblySTEP, K as gridPattern, L as addChild, M as sphericalJoint, N as quatFromAxisAngle, O as prismaticJoint, P as quatFromTo, R as collectShapes, S as addJoint, T as jointTransform, U as updateNode, V as findNode, W as walkAssembly, Y as createAssembly, _ as exportURDF, a as deserializeHistory, b as inverseKinematics, c as modifyStep, d as replayFrom, f as replayHistory, g as undoLast, h as stepsFrom, i as createRegistry, j as setJointValues, k as revoluteJoint, l as registerOperation, m as stepCount, n as addStep, o as findStep, p as serializeHistory, q as linearPattern, r as createHistory, s as getShape, t as thread, u as registerShape, v as importURDF, w as forwardKinematics, x as jointTrajectory, y as jointsFromDH, z as countNodes } from "./threadFns-BWbibv4L.js";
22
22
  import { n as BaseSketcher2d, r as organiseBlueprints, t as BlueprintSketcher } from "./blueprintSketcher-yZiAeqft.js";
@@ -27,8 +27,8 @@ import { t as cornerFinder } from "./cornerFinder-CiHpo7kU.js";
27
27
  import { a as fuseBlueprints, c as roundedRectangleBlueprint, i as cutBlueprints, n as fuse2D, o as intersectBlueprints, r as intersect2D, s as polysidesBlueprint, t as cut2D } from "./boolean2D-BuM5xJSa.js";
28
28
  import { S as reverseCurve, _ as curve2dIsOnCurve, a as isInside2D, b as curve2dSplitAt, c as scale2D, d as stretch2D, f as toSVGPathD, g as curve2dFirstPoint, h as curve2dDistanceFrom, i as getOrientation2D, l as sketchOnFace2D, m as curve2dBoundingBox, n as createCompoundBlueprint, o as mirror2D, p as translate2D, r as getBounds2D, s as rotate2D, t as createBlueprint, u as sketchOnPlane2D, v as curve2dLastPoint, x as curve2dTangentAt, y as curve2dParameter } from "./blueprintFns-COSD5yZH.js";
29
29
  import { a as importSVG, c as blueprintToDXF, d as exportGltf, f as exportOBJ, i as exportSTEPConfigured, l as exportDXF, n as importSTEP, o as importSVGPathD, r as importSTL, s as exportThreeMF, t as importIGES, u as exportGlb } from "./importFns-BId6d2pt.js";
30
- import { a as multiSectionSweep, c as twistExtrude, i as guidedSweep, n as loftAll, o as supportExtrude, r as complexExtrude, s as sweep$1, t as loft$1 } from "./loftFns-m7LoJWE2.js";
31
- import { a as Sketch, b as extrudeAll, c as compoundSketchLoft, d as sketchFace, f as sketchLoft, h as sketchWires, i as Sketches, l as compoundSketchRevolve, m as sketchSweep, n as getFont, o as compoundSketchExtrude, p as sketchRevolve, r as loadFont, s as compoundSketchFace, t as textBlueprints, u as sketchExtrude, v as CompoundSketch, x as revolve$1, y as extrude$1 } from "./textBlueprints-C1aMaWLc.js";
30
+ import { a as multiSectionSweep, c as twistExtrude, i as guidedSweep, n as loftAll, o as supportExtrude, r as complexExtrude, s as sweep$1, t as loft$2 } from "./loftFns-m7LoJWE2.js";
31
+ import { a as Sketch, b as extrudeAll, c as compoundSketchLoft, d as sketchFace, f as sketchLoft, h as sketchWires, i as Sketches, l as compoundSketchRevolve, m as sketchSweep, n as getFont, o as compoundSketchExtrude, p as sketchRevolve, r as loadFont, s as compoundSketchFace, t as textBlueprints, u as sketchExtrude, v as CompoundSketch, x as revolve$2, y as extrude$2 } from "./textBlueprints-C1aMaWLc.js";
32
32
  import { a as makeProjectedEdges, i as projectEdges, n as cameraLookAt, r as createCamera, s as isProjectionPlane, t as cameraFromPlane } from "./cameraFns-VVI0cWLa.js";
33
33
  import { n as textMetrics, r as sketchText, t as fontMetrics } from "./textMetrics-DH3YPwVC.js";
34
34
  import { a as facesOfVertex, c as verticesOfFace, i as facesOfEdge, l as wiresOfFace, n as adjacentFaces, o as sharedEdges, r as edgesOfFace, s as verticesOfEdge } from "./adjacencyFns-CwcdjOsT.js";
@@ -264,8 +264,8 @@ function repairMesh(mesh, opts, id) {
264
264
  if (isErr(engine)) return engine;
265
265
  try {
266
266
  try {
267
- var _usingCtx$8 = _usingCtx();
268
- const repaired = _usingCtx$8.u(engine.value.repair_mesh(mesh.vertices, mesh.triangles, resolution, padding));
267
+ var _usingCtx$10 = _usingCtx();
268
+ const repaired = _usingCtx$10.u(engine.value.repair_mesh(mesh.vertices, mesh.triangles, resolution, padding));
269
269
  const vertexCount = repaired.positions.length / 3;
270
270
  return ok({
271
271
  vertices: repaired.positions,
@@ -279,9 +279,9 @@ function repairMesh(mesh, opts, id) {
279
279
  }]
280
280
  });
281
281
  } catch (_) {
282
- _usingCtx$8.e = _;
282
+ _usingCtx$10.e = _;
283
283
  } finally {
284
- _usingCtx$8.d();
284
+ _usingCtx$10.d();
285
285
  }
286
286
  } catch (cause) {
287
287
  return err(computationError("VOXEL_REPAIR_FAILED", cause instanceof Error ? cause.message : "voxel repair failed (grid too large?).", cause));
@@ -360,12 +360,12 @@ function offsetMesh(mesh, distance, opts, id) {
360
360
  if (isErr(engine)) return engine;
361
361
  try {
362
362
  try {
363
- var _usingCtx$7 = _usingCtx();
364
- return meshFromResult(_usingCtx$7.u(engine.value.offset_mesh(mesh.vertices, mesh.triangles, distance, params.value.resolution, params.value.padding)));
363
+ var _usingCtx$9 = _usingCtx();
364
+ return meshFromResult(_usingCtx$9.u(engine.value.offset_mesh(mesh.vertices, mesh.triangles, distance, params.value.resolution, params.value.padding)));
365
365
  } catch (_) {
366
- _usingCtx$7.e = _;
366
+ _usingCtx$9.e = _;
367
367
  } finally {
368
- _usingCtx$7.d();
368
+ _usingCtx$9.d();
369
369
  }
370
370
  } catch (cause) {
371
371
  return err(computationError("VOXEL_OFFSET_FAILED", cause instanceof Error ? cause.message : "voxel offset failed (grid too large?).", cause));
@@ -490,9 +490,9 @@ function resolveGridParams$1(opts) {
490
490
  */
491
491
  function meshFromField(field) {
492
492
  try {
493
- var _usingCtx$6 = _usingCtx();
493
+ var _usingCtx$8 = _usingCtx();
494
494
  const rawResult = field.contour();
495
- const { positions, normals, indices } = _usingCtx$6.u({
495
+ const { positions, normals, indices } = _usingCtx$8.u({
496
496
  value: rawResult,
497
497
  [Symbol.dispose]() {
498
498
  rawResult.free();
@@ -512,9 +512,9 @@ function meshFromField(field) {
512
512
  }]
513
513
  });
514
514
  } catch (_) {
515
- _usingCtx$6.e = _;
515
+ _usingCtx$8.e = _;
516
516
  } finally {
517
- _usingCtx$6.d();
517
+ _usingCtx$8.d();
518
518
  }
519
519
  }
520
520
  function fieldDeletable(raw) {
@@ -1082,13 +1082,13 @@ function latticeInfill(mesh, opts, id) {
1082
1082
  const { tag, period, thickness, resolution, padding } = resolved.value;
1083
1083
  try {
1084
1084
  try {
1085
- var _usingCtx$5 = _usingCtx();
1086
- const repaired = _usingCtx$5.u(engine.value.lattice_infill(mesh.vertices, mesh.triangles, resolution, padding, tag, period, thickness));
1085
+ var _usingCtx$7 = _usingCtx();
1086
+ const repaired = _usingCtx$7.u(engine.value.lattice_infill(mesh.vertices, mesh.triangles, resolution, padding, tag, period, thickness));
1087
1087
  return ok(toMesh(repaired));
1088
1088
  } catch (_) {
1089
- _usingCtx$5.e = _;
1089
+ _usingCtx$7.e = _;
1090
1090
  } finally {
1091
- _usingCtx$5.d();
1091
+ _usingCtx$7.d();
1092
1092
  }
1093
1093
  } catch (cause) {
1094
1094
  return err(computationError("LATTICE_INFILL_FAILED", cause instanceof Error ? cause.message : "lattice infill failed (grid too large?).", cause));
@@ -1567,7 +1567,7 @@ function polyhedron(points, faces, options = {}) {
1567
1567
  }
1568
1568
  //#endregion
1569
1569
  //#region src/operations/straightSkeleton.ts
1570
- var EPS = 1e-10;
1570
+ var EPS$1 = 1e-10;
1571
1571
  function cross2(ax, ay, bx, by) {
1572
1572
  return ax * by - ay * bx;
1573
1573
  }
@@ -1605,7 +1605,7 @@ function bisector(poly, i) {
1605
1605
  const e2x = next.x - cur.x;
1606
1606
  const e2y = next.y - cur.y;
1607
1607
  const e2l = len2(e2x, e2y);
1608
- if (e1l < EPS || e2l < EPS) return {
1608
+ if (e1l < EPS$1 || e2l < EPS$1) return {
1609
1609
  dx: 0,
1610
1610
  dy: 0
1611
1611
  };
@@ -1616,14 +1616,14 @@ function bisector(poly, i) {
1616
1616
  let bx = n1x + n2x;
1617
1617
  let by = n1y + n2y;
1618
1618
  const bl = len2(bx, by);
1619
- if (bl < EPS) return {
1619
+ if (bl < EPS$1) return {
1620
1620
  dx: n1x,
1621
1621
  dy: n1y
1622
1622
  };
1623
1623
  bx /= bl;
1624
1624
  by /= bl;
1625
1625
  const cosHalf = dot2(bx, by, n1x, n1y);
1626
- const speed = Math.abs(cosHalf) > EPS ? 1 / cosHalf : 1;
1626
+ const speed = Math.abs(cosHalf) > EPS$1 ? 1 / cosHalf : 1;
1627
1627
  return {
1628
1628
  dx: bx * speed,
1629
1629
  dy: by * speed
@@ -1685,14 +1685,14 @@ function bisectorIntersectTime(a, b) {
1685
1685
  const ddy = a.by - b.by;
1686
1686
  const dxp = b.x - a.x;
1687
1687
  const dyp = b.y - a.y;
1688
- if (Math.abs(ddx) < EPS && Math.abs(ddy) < EPS) return null;
1688
+ if (Math.abs(ddx) < EPS$1 && Math.abs(ddy) < EPS$1) return null;
1689
1689
  let t;
1690
1690
  if (Math.abs(ddx) > Math.abs(ddy)) t = dxp / ddx;
1691
1691
  else t = dyp / ddy;
1692
- if (t < EPS) return null;
1692
+ if (t < EPS$1) return null;
1693
1693
  const otherDd = Math.abs(ddx) > Math.abs(ddy) ? ddy : ddx;
1694
1694
  const otherDp = Math.abs(ddx) > Math.abs(ddy) ? dyp : dxp;
1695
- if (Math.abs(otherDd) > EPS) {
1695
+ if (Math.abs(otherDd) > EPS$1) {
1696
1696
  const t2 = otherDp / otherDd;
1697
1697
  if (Math.abs(t - t2) > 1e-4 * Math.max(1, Math.abs(t))) return null;
1698
1698
  }
@@ -1703,16 +1703,16 @@ function raySplitTime(node, eA, eB) {
1703
1703
  const edx = eB.x - eA.x;
1704
1704
  const edy = eB.y - eA.y;
1705
1705
  const el = len2(edx, edy);
1706
- if (el < EPS) return null;
1706
+ if (el < EPS$1) return null;
1707
1707
  const enx = -edy / el;
1708
1708
  const eny = edx / el;
1709
1709
  const d0 = (node.x - eA.x) * enx + (node.y - eA.y) * eny;
1710
1710
  const relBx = node.bx - (eA.bx + eB.bx) / 2;
1711
1711
  const relBy = node.by - (eA.by + eB.by) / 2;
1712
1712
  const dRate = relBx * enx + relBy * eny;
1713
- if (Math.abs(dRate) < EPS) return null;
1713
+ if (Math.abs(dRate) < EPS$1) return null;
1714
1714
  const t = -d0 / dRate;
1715
- if (t < EPS) return null;
1715
+ if (t < EPS$1) return null;
1716
1716
  const px = node.x + t * node.bx;
1717
1717
  const py = node.y + t * node.by;
1718
1718
  const ax = eA.x + t * eA.bx;
@@ -1722,7 +1722,7 @@ function raySplitTime(node, eA, eB) {
1722
1722
  const segDx = bxx - ax;
1723
1723
  const segDy = byy - ay;
1724
1724
  const segL = len2(segDx, segDy);
1725
- if (segL < EPS) return t;
1725
+ if (segL < EPS$1) return t;
1726
1726
  const s = dot2(px - ax, py - ay, segDx, segDy) / (segL * segL);
1727
1727
  if (s < -.01 || s > 1.01) return null;
1728
1728
  return t;
@@ -1732,7 +1732,7 @@ function computeEvents(lavNodes) {
1732
1732
  for (const node of lavNodes) {
1733
1733
  if (!node.active || !node.next) continue;
1734
1734
  const t = bisectorIntersectTime(node, node.next);
1735
- if (t !== null && t > EPS) {
1735
+ if (t !== null && t > EPS$1) {
1736
1736
  const x = node.x + t * node.bx;
1737
1737
  const y = node.y + t * node.by;
1738
1738
  events.push({
@@ -1749,7 +1749,7 @@ function computeEvents(lavNodes) {
1749
1749
  let count = 0;
1750
1750
  while (cur !== node.prev && cur !== node && count < 1e3) {
1751
1751
  const st = raySplitTime(node, cur, cur.next);
1752
- if (st !== null && st > EPS) {
1752
+ if (st !== null && st > EPS$1) {
1753
1753
  const x = node.x + st * node.bx;
1754
1754
  const y = node.y + st * node.by;
1755
1755
  events.push({
@@ -1805,7 +1805,7 @@ function computeStraightSkeletonImpl(polygon) {
1805
1805
  const b = a.next;
1806
1806
  const c = b.next;
1807
1807
  const t = bisectorIntersectTime(a, b);
1808
- const time = t !== null && t > EPS ? t : 0;
1808
+ const time = t !== null && t > EPS$1 ? t : 0;
1809
1809
  const mx = (a.x + b.x + c.x) / 3 + time * (a.bx + b.bx + c.bx) / 3;
1810
1810
  const my = (a.y + b.y + c.y) / 3 + time * (a.by + b.by + c.by) / 3;
1811
1811
  const nodeIdx = skeletonNodes.length;
@@ -1944,7 +1944,7 @@ function computeStraightSkeletonImpl(polygon) {
1944
1944
  if (!sn) continue;
1945
1945
  const lastVert = faceVerts[faceVerts.length - 1];
1946
1946
  if (!lastVert) continue;
1947
- if (len2(sn.x - lastVert.x, sn.y - lastVert.y) > EPS) {
1947
+ if (len2(sn.x - lastVert.x, sn.y - lastVert.y) > EPS$1) {
1948
1948
  faceVerts.push({
1949
1949
  x: sn.x,
1950
1950
  y: sn.y
@@ -3737,7 +3737,7 @@ function isEmpty(shape) {
3737
3737
  */
3738
3738
  function extrude(face, height) {
3739
3739
  const f = resolve(face);
3740
- return extrude$1(f, typeof height === "number" ? [
3740
+ return extrude$2(f, typeof height === "number" ? [
3741
3741
  0,
3742
3742
  0,
3743
3743
  height
@@ -3752,7 +3752,7 @@ function revolve(face, options) {
3752
3752
  0,
3753
3753
  0
3754
3754
  ];
3755
- return revolve$1(resolve(face), pivotPoint, options?.axis ?? [
3755
+ return revolve$2(resolve(face), pivotPoint, options?.axis ?? [
3756
3756
  0,
3757
3757
  0,
3758
3758
  1
@@ -3763,7 +3763,7 @@ function revolve(face, options) {
3763
3763
  */
3764
3764
  function loft(wires, options) {
3765
3765
  const resolvedWires = wires.map((w) => resolve(w));
3766
- return loft$1(resolvedWires, options);
3766
+ return loft$2(resolvedWires, options);
3767
3767
  }
3768
3768
  //#endregion
3769
3769
  //#region src/operations/compoundOpsFns.ts
@@ -3910,7 +3910,7 @@ function drill(shape, options) {
3910
3910
  */
3911
3911
  function pocket(shape, options) {
3912
3912
  try {
3913
- var _usingCtx$4 = _usingCtx();
3913
+ var _usingCtx$6 = _usingCtx();
3914
3914
  const s = resolve(shape);
3915
3915
  const { profile, depth } = options;
3916
3916
  if (depth <= 0) return err(validationError("POCKET_INVALID_DEPTH", "Pocket depth must be positive"));
@@ -3919,7 +3919,7 @@ function pocket(shape, options) {
3919
3919
  const targetFace = targetResult.value;
3920
3920
  const normal = normalAt(targetFace);
3921
3921
  const center = faceCenter(targetFace);
3922
- const scope = _usingCtx$4.u(new DisposalScope());
3922
+ const scope = _usingCtx$6.u(new DisposalScope());
3923
3923
  const { wire: w, owned } = toWire(profile);
3924
3924
  if (owned) scope.register(w);
3925
3925
  const faceResult = makeFace(w);
@@ -3927,14 +3927,14 @@ function pocket(shape, options) {
3927
3927
  scope.register(faceResult.value);
3928
3928
  const positioned = scope.register(translate$2(faceResult.value, center));
3929
3929
  const extDir = vecScale(vecNormalize(normal), -depth);
3930
- const toolResult = extrude$1(positioned, extDir);
3930
+ const toolResult = extrude$2(positioned, extDir);
3931
3931
  if (isErr(toolResult)) return toolResult;
3932
3932
  scope.register(toolResult.value);
3933
3933
  return cut$2(s, toolResult.value, { unsafe: true });
3934
3934
  } catch (_) {
3935
- _usingCtx$4.e = _;
3935
+ _usingCtx$6.e = _;
3936
3936
  } finally {
3937
- _usingCtx$4.d();
3937
+ _usingCtx$6.d();
3938
3938
  }
3939
3939
  }
3940
3940
  /**
@@ -3962,7 +3962,7 @@ function boss(shape, options) {
3962
3962
  scope.register(faceResult.value);
3963
3963
  const positioned = scope.register(translate$2(faceResult.value, center));
3964
3964
  const extDir = vecScale(vecNormalize(normal), height);
3965
- const toolResult = extrude$1(positioned, extDir);
3965
+ const toolResult = extrude$2(positioned, extDir);
3966
3966
  if (isErr(toolResult)) return toolResult;
3967
3967
  scope.register(toolResult.value);
3968
3968
  return fuse$2(s, toolResult.value, { unsafe: true });
@@ -4524,12 +4524,12 @@ function makeInternalGearProfileWire(params) {
4524
4524
  }
4525
4525
  function makeProfileWire(params, isInternal) {
4526
4526
  try {
4527
- var _usingCtx$3 = _usingCtx();
4527
+ var _usingCtx$5 = _usingCtx();
4528
4528
  const { teeth, moduleSize, pressureAngle, shift, clearance, flankThinning, samples = adaptiveSampleCount(moduleSize) } = params;
4529
4529
  if (teeth < 4) return err(validationError("GEAR_TEETH_TOO_FEW", `gear needs ≥ 4 teeth, got ${teeth}`));
4530
4530
  if (moduleSize <= 0) return err(validationError("GEAR_MODULE_NONPOSITIVE", `module must be > 0, got ${moduleSize}`));
4531
4531
  const tm = gearGeometry(teeth, moduleSize, pressureAngle, shift, clearance, flankThinning, isInternal);
4532
- const scope = _usingCtx$3.u(new DisposalScope());
4532
+ const scope = _usingCtx$5.u(new DisposalScope());
4533
4533
  const allEdges = [];
4534
4534
  for (let i = 0; i < teeth; i++) {
4535
4535
  const periodEdges = buildToothPeriodEdges(tm, i, teeth, samples, scope);
@@ -4540,9 +4540,9 @@ function makeProfileWire(params, isInternal) {
4540
4540
  if (isErr(wireResult)) return wireResult;
4541
4541
  return ok(wireResult.value);
4542
4542
  } catch (_) {
4543
- _usingCtx$3.e = _;
4543
+ _usingCtx$5.e = _;
4544
4544
  } finally {
4545
- _usingCtx$3.d();
4545
+ _usingCtx$5.d();
4546
4546
  }
4547
4547
  }
4548
4548
  //#endregion
@@ -4723,8 +4723,8 @@ function resolvePlanetaryParams(params) {
4723
4723
  }
4724
4724
  function makeOuterCircleWire(radius) {
4725
4725
  try {
4726
- var _usingCtx$2 = _usingCtx();
4727
- const circleEdge = _usingCtx$2.u(new DisposalScope()).register(makeCircle(radius, [
4726
+ var _usingCtx$4 = _usingCtx();
4727
+ const circleEdge = _usingCtx$4.u(new DisposalScope()).register(makeCircle(radius, [
4728
4728
  0,
4729
4729
  0,
4730
4730
  0
@@ -4737,9 +4737,9 @@ function makeOuterCircleWire(radius) {
4737
4737
  if (isErr(wireResult)) return wireResult;
4738
4738
  return ok(wireResult.value);
4739
4739
  } catch (_) {
4740
- _usingCtx$2.e = _;
4740
+ _usingCtx$4.e = _;
4741
4741
  } finally {
4742
- _usingCtx$2.d();
4742
+ _usingCtx$4.d();
4743
4743
  }
4744
4744
  }
4745
4745
  function finalizeExternalSolid(wire, thickness, bore, geom, diagnostics) {
@@ -4750,7 +4750,7 @@ function finalizeExternalSolid(wire, thickness, bore, geom, diagnostics) {
4750
4750
  const faceResult = makeFace(wire);
4751
4751
  if (isErr(faceResult)) return faceResult;
4752
4752
  profileScope.register(faceResult.value);
4753
- const solidResult = extrude$1(faceResult.value, [
4753
+ const solidResult = extrude$2(faceResult.value, [
4754
4754
  0,
4755
4755
  0,
4756
4756
  thickness
@@ -4762,7 +4762,7 @@ function finalizeExternalSolid(wire, thickness, bore, geom, diagnostics) {
4762
4762
  const boreFaceResult = makeBoreFace(bore / 2);
4763
4763
  if (isErr(boreFaceResult)) return boreFaceResult;
4764
4764
  boreScope.register(boreFaceResult.value);
4765
- const boreRaw = extrude$1(boreFaceResult.value, [
4765
+ const boreRaw = extrude$2(boreFaceResult.value, [
4766
4766
  0,
4767
4767
  0,
4768
4768
  thickness + 1
@@ -4791,7 +4791,7 @@ function makeBoreFace(radius) {
4791
4791
  function finalizeInternalSolid(outerWire, innerToothedWire, thickness, geom, diagnostics) {
4792
4792
  const faceResult = makeFace(outerWire, [innerToothedWire]);
4793
4793
  if (isErr(faceResult)) return faceResult;
4794
- const solidResult = extrude$1(faceResult.value, [
4794
+ const solidResult = extrude$2(faceResult.value, [
4795
4795
  0,
4796
4796
  0,
4797
4797
  thickness
@@ -5336,6 +5336,14 @@ function evalVec3(expr, env, where) {
5336
5336
  c
5337
5337
  ]);
5338
5338
  }
5339
+ function evalVec2(expr, env, where) {
5340
+ const r = evalExpr(expr, env);
5341
+ if (!r.ok) return r;
5342
+ const v = expectVecLen(r.value, 2, where);
5343
+ if (!v.ok) return v;
5344
+ const [a, b] = v.value;
5345
+ return ok([a, b]);
5346
+ }
5339
5347
  function projectEnv(env, deps) {
5340
5348
  if (deps.size === 0) return Object.freeze({});
5341
5349
  const out = {};
@@ -5346,6 +5354,91 @@ function projectEnv(env, deps) {
5346
5354
  return Object.freeze(out);
5347
5355
  }
5348
5356
  //#endregion
5357
+ //#region src/csg/segments.ts
5358
+ /**
5359
+ * Serializable 2D segment vocabulary — endpoint-parametrized (SVG-style)
5360
+ * curve segments shared by the Path node (open spines) and, later, Profile
5361
+ * contours. Positions and radii are expressions, so `freeParams` propagates
5362
+ * and segment data hashes with the same FNV machinery as every node field.
5363
+ */
5364
+ function lineTo(to) {
5365
+ return {
5366
+ kind: "Line",
5367
+ to: asVec2Expr(to)
5368
+ };
5369
+ }
5370
+ function arcTo(to, radius, options) {
5371
+ return {
5372
+ kind: "Arc",
5373
+ to: asVec2Expr(to),
5374
+ radius: asScalarExpr(radius),
5375
+ largeArc: options?.largeArc ?? false,
5376
+ clockwise: options?.clockwise ?? false
5377
+ };
5378
+ }
5379
+ function bezierTo(controls, to) {
5380
+ return {
5381
+ kind: "Bezier",
5382
+ controls: controls.map(asVec2Expr),
5383
+ to: asVec2Expr(to)
5384
+ };
5385
+ }
5386
+ function ellipseArcTo(to, radii, options) {
5387
+ return {
5388
+ kind: "EllipseArc",
5389
+ to: asVec2Expr(to),
5390
+ radii: asVec2Expr(radii),
5391
+ rotation: asScalarExpr(options?.rotation ?? 0),
5392
+ largeArc: options?.largeArc ?? false,
5393
+ clockwise: options?.clockwise ?? false
5394
+ };
5395
+ }
5396
+ function hashSegments(h0, segments) {
5397
+ let h = fnvMixInt32(h0, segments.length);
5398
+ const mix = (acc, e) => fnvMixHash(acc, e.structuralHash);
5399
+ for (const s of segments) {
5400
+ h = fnvMixString(h, s.kind);
5401
+ h = mix(h, s.to);
5402
+ if (s.kind === "Arc") {
5403
+ h = mix(h, s.radius);
5404
+ h = fnvMixBool(fnvMixBool(h, s.largeArc), s.clockwise);
5405
+ } else if (s.kind === "Bezier") {
5406
+ h = fnvMixInt32(h, s.controls.length);
5407
+ for (const c of s.controls) h = mix(h, c);
5408
+ } else if (s.kind === "EllipseArc") {
5409
+ h = mix(h, s.radii);
5410
+ h = mix(h, s.rotation);
5411
+ h = fnvMixBool(fnvMixBool(h, s.largeArc), s.clockwise);
5412
+ }
5413
+ }
5414
+ return h;
5415
+ }
5416
+ function segmentFreeParams(segments) {
5417
+ const out = [];
5418
+ for (const s of segments) {
5419
+ out.push(s.to);
5420
+ if (s.kind === "Arc") out.push(s.radius);
5421
+ else if (s.kind === "Bezier") out.push(...s.controls);
5422
+ else if (s.kind === "EllipseArc") out.push(s.radii, s.rotation);
5423
+ }
5424
+ return out;
5425
+ }
5426
+ function foldSegment(s, foldExpr) {
5427
+ switch (s.kind) {
5428
+ case "Line": return lineTo(foldExpr(s.to));
5429
+ case "Arc": return arcTo(foldExpr(s.to), foldExpr(s.radius), {
5430
+ largeArc: s.largeArc,
5431
+ clockwise: s.clockwise
5432
+ });
5433
+ case "Bezier": return bezierTo(s.controls.map(foldExpr), foldExpr(s.to));
5434
+ case "EllipseArc": return ellipseArcTo(foldExpr(s.to), foldExpr(s.radii), {
5435
+ rotation: foldExpr(s.rotation),
5436
+ largeArc: s.largeArc,
5437
+ clockwise: s.clockwise
5438
+ });
5439
+ }
5440
+ }
5441
+ //#endregion
5349
5442
  //#region src/csg/builders.ts
5350
5443
  var EMPTY_DEPS = /* @__PURE__ */ new Set();
5351
5444
  function startHash(tag) {
@@ -5522,29 +5615,31 @@ function intersect$1(a, b, tolerance) {
5522
5615
  };
5523
5616
  }
5524
5617
  function fuseAll$1(shapes, tolerance) {
5525
- let h = fnvMixInt32(startHash("FuseAll"), shapes.length);
5526
- for (const s of shapes) h = mix(h, s);
5618
+ const copied = [...shapes];
5619
+ let h = fnvMixInt32(startHash("FuseAll"), copied.length);
5620
+ for (const s of copied) h = mix(h, s);
5527
5621
  h = mixOptNumber(h, tolerance);
5528
5622
  return {
5529
5623
  kind: "FuseAll",
5530
- shapes,
5624
+ shapes: copied,
5531
5625
  tolerance,
5532
5626
  structuralHash: h,
5533
- freeParams: depsOf(...shapes)
5627
+ freeParams: depsOf(...copied)
5534
5628
  };
5535
5629
  }
5536
5630
  function cutAll$1(base, tools, tolerance) {
5631
+ const copied = [...tools];
5537
5632
  let h = mix(startHash("CutAll"), base);
5538
- h = fnvMixInt32(h, tools.length);
5539
- for (const t of tools) h = mix(h, t);
5633
+ h = fnvMixInt32(h, copied.length);
5634
+ for (const t of copied) h = mix(h, t);
5540
5635
  h = mixOptNumber(h, tolerance);
5541
5636
  return {
5542
5637
  kind: "CutAll",
5543
5638
  base,
5544
- tools,
5639
+ tools: copied,
5545
5640
  tolerance,
5546
5641
  structuralHash: h,
5547
- freeParams: depsOf(base, ...tools)
5642
+ freeParams: depsOf(base, ...copied)
5548
5643
  };
5549
5644
  }
5550
5645
  function optVec3$1(v) {
@@ -5606,14 +5701,76 @@ function mirror$1(target, options) {
5606
5701
  freeParams: depsOf(target, nE, atE)
5607
5702
  };
5608
5703
  }
5704
+ function extrude$1(profile, vector) {
5705
+ const ve = asVec3Expr(vector);
5706
+ return {
5707
+ kind: "Extrude",
5708
+ profile,
5709
+ vector: ve,
5710
+ structuralHash: mix(mix(startHash("Extrude"), profile), ve),
5711
+ freeParams: depsOf(profile, ve)
5712
+ };
5713
+ }
5714
+ /** Open (or incidentally closed) planar path in the XY plane at z = 0,
5715
+ * producing a Wire. Segments come from `lineTo`/`arcTo`/`bezierTo`/
5716
+ * `ellipseArcTo`. */
5717
+ function path(start, segments) {
5718
+ const se = asVec2Expr(start);
5719
+ const copied = [...segments];
5720
+ let h = mix(startHash("Path"), se);
5721
+ h = hashSegments(h, copied);
5722
+ return {
5723
+ kind: "Path",
5724
+ start: se,
5725
+ segments: copied,
5726
+ structuralHash: h,
5727
+ freeParams: depsOf(se, ...segmentFreeParams(copied))
5728
+ };
5729
+ }
5730
+ /** Loft through two or more face-producing sections (default ruled). */
5731
+ function loft$1(sections, options) {
5732
+ const copied = [...sections];
5733
+ const ruled = options?.ruled ?? true;
5734
+ let h = fnvMixInt32(startHash("Loft"), copied.length);
5735
+ for (const s of copied) h = mix(h, s);
5736
+ h = fnvMixBool(h, ruled);
5737
+ return {
5738
+ kind: "Loft",
5739
+ sections: copied,
5740
+ ruled,
5741
+ structuralHash: h,
5742
+ freeParams: depsOf(...copied)
5743
+ };
5744
+ }
5745
+ /** Angle is in degrees (matching `rotate`); defaults to a full revolution
5746
+ * around the Z axis at the origin. Evaluation clamps angles above 360 to one
5747
+ * revolution (kernels diverge past 2*pi) and rejects non-positive angles. */
5748
+ function revolve$1(profile, angle = 360, options) {
5749
+ const ae = asScalarExpr(angle);
5750
+ const axisE = optVec3$1(options?.axis);
5751
+ const atE = optVec3$1(options?.at);
5752
+ let h = mix(mix(startHash("Revolve"), profile), ae);
5753
+ h = mixOptExpr(h, axisE);
5754
+ h = mixOptExpr(h, atE);
5755
+ return {
5756
+ kind: "Revolve",
5757
+ profile,
5758
+ angle: ae,
5759
+ axis: axisE,
5760
+ at: atE,
5761
+ structuralHash: h,
5762
+ freeParams: depsOf(profile, ae, axisE, atE)
5763
+ };
5764
+ }
5609
5765
  function compound$1(children) {
5610
- let h = fnvMixInt32(startHash("Compound"), children.length);
5611
- for (const c of children) h = mix(h, c);
5766
+ const copied = [...children];
5767
+ let h = fnvMixInt32(startHash("Compound"), copied.length);
5768
+ for (const c of copied) h = mix(h, c);
5612
5769
  return {
5613
5770
  kind: "Compound",
5614
- children,
5771
+ children: copied,
5615
5772
  structuralHash: h,
5616
- freeParams: depsOf(...children)
5773
+ freeParams: depsOf(...copied)
5617
5774
  };
5618
5775
  }
5619
5776
  function instance$1(source, placements, fuse = false) {
@@ -5652,6 +5809,10 @@ function outputKindOf(node) {
5652
5809
  case "Rotate":
5653
5810
  case "Scale":
5654
5811
  case "Mirror": return outputKindOf(node.target);
5812
+ case "Extrude":
5813
+ case "Revolve":
5814
+ case "Loft": return "Solid";
5815
+ case "Path": return "Wire";
5655
5816
  case "Compound": return "Compound";
5656
5817
  case "Instance": return node.fuse ? "Solid" : "Compound";
5657
5818
  }
@@ -5931,6 +6092,291 @@ function evalInstance(node, ctx) {
5931
6092
  return materialize(instance(r.value, node.placements), { fuse: node.fuse });
5932
6093
  }
5933
6094
  //#endregion
6095
+ //#region src/csg/evaluators/profileFace.ts
6096
+ /** Evaluate a profile child and prove it is an oriented planar face — the
6097
+ * input contract shared by every profile-consuming feature node. */
6098
+ function resolveProfileFace(ctx, profile, where) {
6099
+ const p = ctx.evalNode(profile);
6100
+ if (!p.ok) return p;
6101
+ if (!isFace(p.value)) return err(typeCastError("CSG_NOT_FACE", `${where}: node did not produce a Face`));
6102
+ const oriented = orientedFace(p.value);
6103
+ if (!oriented.ok) return err(typeCastError("CSG_NOT_FACE", `${where}: ${oriented.error}`));
6104
+ if (!isPlanarFace(oriented.value)) return err(typeCastError("CSG_NOT_FACE", `${where}: face is not planar`));
6105
+ return ok(oriented.value);
6106
+ }
6107
+ //#endregion
6108
+ //#region src/csg/evaluators/extrude.ts
6109
+ function evalExtrude(node, ctx) {
6110
+ const v = evalVec3(node.vector, ctx.env, "Extrude.vector");
6111
+ if (!v.ok) return v;
6112
+ const face = resolveProfileFace(ctx, node.profile, "Extrude.profile");
6113
+ if (!face.ok) return face;
6114
+ return extrude$2(face.value, v.value);
6115
+ }
6116
+ //#endregion
6117
+ //#region src/csg/evaluators/revolve.ts
6118
+ function evalRevolve(node, ctx) {
6119
+ const a = evalScalar(node.angle, ctx.env, "Revolve.angle");
6120
+ if (!a.ok) return a;
6121
+ if (a.value <= 0) return err(validationError("CSG_REVOLVE_ANGLE", `Revolve.angle must be positive (degrees), got ${a.value}`));
6122
+ const deg = Math.min(a.value, 360);
6123
+ const axis = node.axis ? evalVec3(node.axis, ctx.env, "Revolve.axis") : ok([
6124
+ 0,
6125
+ 0,
6126
+ 1
6127
+ ]);
6128
+ if (!axis.ok) return axis;
6129
+ const at = node.at ? evalVec3(node.at, ctx.env, "Revolve.at") : ok([
6130
+ 0,
6131
+ 0,
6132
+ 0
6133
+ ]);
6134
+ if (!at.ok) return at;
6135
+ const face = resolveProfileFace(ctx, node.profile, "Revolve.profile");
6136
+ if (!face.ok) return face;
6137
+ return revolve$2(face.value, at.value, axis.value, deg * Math.PI / 180);
6138
+ }
6139
+ //#endregion
6140
+ //#region src/csg/evaluators/loft.ts
6141
+ function evalLoft(node, ctx) {
6142
+ try {
6143
+ var _usingCtx$3 = _usingCtx();
6144
+ if (node.sections.length < 2) return err(validationError("CSG_LOFT_SECTIONS", `Loft requires at least 2 sections, got ${node.sections.length}`));
6145
+ const scope = _usingCtx$3.u(new DisposalScope());
6146
+ const wires = [];
6147
+ for (const [i, section] of node.sections.entries()) {
6148
+ const face = resolveProfileFace(ctx, section, `Loft.sections[${i}]`);
6149
+ if (!face.ok) return face;
6150
+ const w = outerWire(face.value);
6151
+ scope.register(w);
6152
+ wires.push(w);
6153
+ }
6154
+ return loft$2(wires, { ruled: node.ruled });
6155
+ } catch (_) {
6156
+ _usingCtx$3.e = _;
6157
+ } finally {
6158
+ _usingCtx$3.d();
6159
+ }
6160
+ }
6161
+ //#endregion
6162
+ //#region src/csg/evaluators/path.ts
6163
+ var EPS = 1e-9;
6164
+ function v3(p) {
6165
+ return [
6166
+ p[0],
6167
+ p[1],
6168
+ 0
6169
+ ];
6170
+ }
6171
+ function fail(msg) {
6172
+ return err(validationError("CSG_PATH_SEGMENT", `Path: ${msg}`));
6173
+ }
6174
+ /** SVG-style endpoint circular arc: recover the on-arc midpoint so the edge
6175
+ * can be built as a three-point arc. */
6176
+ function circularArcMidpoint(from, to, r, largeArc, clockwise) {
6177
+ const [x1, y1] = from;
6178
+ const [x2, y2] = to;
6179
+ const dx = x2 - x1;
6180
+ const dy = y2 - y1;
6181
+ const d = Math.hypot(dx, dy);
6182
+ if (d < EPS) return fail("Arc: coincident endpoints");
6183
+ if (d > 2 * r + 1e-6) return fail(`Arc: radius ${r} too small for chord ${d}`);
6184
+ const h = Math.sqrt(Math.max(0, r * r - d / 2 * (d / 2)));
6185
+ const ux = dx / d;
6186
+ const uy = dy / d;
6187
+ const sign = clockwise === largeArc ? 1 : -1;
6188
+ const cx = (x1 + x2) / 2 + sign * h * -uy;
6189
+ const cy = (y1 + y2) / 2 + sign * h * ux;
6190
+ const a0 = Math.atan2(y1 - cy, x1 - cx);
6191
+ let a1 = Math.atan2(y2 - cy, x2 - cx);
6192
+ if (clockwise) while (a1 >= a0 - EPS) a1 -= 2 * Math.PI;
6193
+ else while (a1 <= a0 + EPS) a1 += 2 * Math.PI;
6194
+ const amid = (a0 + a1) / 2;
6195
+ return ok([cx + r * Math.cos(amid), cy + r * Math.sin(amid)]);
6196
+ }
6197
+ /** SVG F.6.5 endpoint-to-center conversion (math y-up; sweep = !clockwise).
6198
+ * Angles are relative to the rotated x-axis. */
6199
+ function ellipseArcParams(from, to, radii, phi, largeArc, clockwise) {
6200
+ const sweep = !clockwise;
6201
+ let [rx, ry] = radii;
6202
+ if (rx <= 0 || ry <= 0) return fail("EllipseArc: non-positive radius");
6203
+ const cosPhi = Math.cos(phi);
6204
+ const sinPhi = Math.sin(phi);
6205
+ const hx = (from[0] - to[0]) / 2;
6206
+ const hy = (from[1] - to[1]) / 2;
6207
+ const x1p = cosPhi * hx + sinPhi * hy;
6208
+ const y1p = -sinPhi * hx + cosPhi * hy;
6209
+ const lambda = x1p * x1p / (rx * rx) + y1p * y1p / (ry * ry);
6210
+ if (lambda > 1) {
6211
+ const s = Math.sqrt(lambda);
6212
+ rx *= s;
6213
+ ry *= s;
6214
+ }
6215
+ const num = rx * rx * ry * ry - rx * rx * y1p * y1p - ry * ry * x1p * x1p;
6216
+ const den = rx * rx * y1p * y1p + ry * ry * x1p * x1p;
6217
+ if (den < EPS) return fail("EllipseArc: coincident endpoints");
6218
+ const coef = (largeArc !== sweep ? 1 : -1) * Math.sqrt(Math.max(0, num / den));
6219
+ const cxp = coef * (rx * y1p / ry);
6220
+ const cyp = coef * (-(ry * x1p) / rx);
6221
+ const cx = cosPhi * cxp - sinPhi * cyp + (from[0] + to[0]) / 2;
6222
+ const cy = sinPhi * cxp + cosPhi * cyp + (from[1] + to[1]) / 2;
6223
+ const theta1 = Math.atan2((y1p - cyp) / ry, (x1p - cxp) / rx);
6224
+ let dTheta = Math.atan2((-y1p - cyp) / ry, (-x1p - cxp) / rx) - theta1;
6225
+ if (!sweep && dTheta > 0) dTheta -= 2 * Math.PI;
6226
+ if (sweep && dTheta < 0) dTheta += 2 * Math.PI;
6227
+ const t1 = dTheta >= 0 ? theta1 : theta1 + dTheta;
6228
+ const t2 = dTheta >= 0 ? theta1 + dTheta : theta1;
6229
+ return ok({
6230
+ center: [cx, cy],
6231
+ theta1: t1,
6232
+ theta2: t2,
6233
+ rx,
6234
+ ry
6235
+ });
6236
+ }
6237
+ function ellipseEdge(from, seg, ctx) {
6238
+ if (seg.kind !== "EllipseArc") return fail("internal: not an ellipse segment");
6239
+ const to = evalVec2(seg.to, ctx.env, "Path.EllipseArc.to");
6240
+ if (!to.ok) return to;
6241
+ const radii = evalVec2(seg.radii, ctx.env, "Path.EllipseArc.radii");
6242
+ if (!radii.ok) return radii;
6243
+ const rotDeg = evalScalar(seg.rotation, ctx.env, "Path.EllipseArc.rotation");
6244
+ if (!rotDeg.ok) return rotDeg;
6245
+ const phi = rotDeg.value * Math.PI / 180;
6246
+ const p = ellipseArcParams(from, to.value, radii.value, phi, seg.largeArc, seg.clockwise);
6247
+ if (!p.ok) return p;
6248
+ const { center, theta1, theta2, rx, ry } = p.value;
6249
+ let arc;
6250
+ let psi;
6251
+ if (rx >= ry) {
6252
+ arc = makeEllipseArc(rx, ry, theta1, theta2, v3(center), [
6253
+ 0,
6254
+ 0,
6255
+ 1
6256
+ ]);
6257
+ psi = phi;
6258
+ } else {
6259
+ arc = makeEllipseArc(ry, rx, theta1 - Math.PI / 2, theta2 - Math.PI / 2, v3(center), [
6260
+ 0,
6261
+ 0,
6262
+ 1
6263
+ ]);
6264
+ psi = phi + Math.PI / 2;
6265
+ }
6266
+ if (!arc.ok) return arc;
6267
+ let edge = arc.value;
6268
+ if (Math.abs(psi) > EPS) try {
6269
+ var _usingCtx$2 = _usingCtx();
6270
+ const rotated = rotateEdgeAbout(_usingCtx$2.u(edge), psi * 180 / Math.PI, center);
6271
+ if (!rotated.ok) return rotated;
6272
+ edge = rotated.value;
6273
+ } catch (_) {
6274
+ _usingCtx$2.e = _;
6275
+ } finally {
6276
+ _usingCtx$2.d();
6277
+ }
6278
+ if (seg.clockwise) try {
6279
+ var _usingCtx3 = _usingCtx();
6280
+ const tmp = _usingCtx3.u(edge);
6281
+ edge = flipOrientation(tmp);
6282
+ } catch (_) {
6283
+ _usingCtx3.e = _;
6284
+ } finally {
6285
+ _usingCtx3.d();
6286
+ }
6287
+ return ok(edge);
6288
+ }
6289
+ /** Rotate an edge about the Z axis at `center` (degrees) via a cheap location
6290
+ * re-tag; returns a fresh, independently-disposable handle. Kernels without
6291
+ * an edge-relocation path (brepkit) surface as a Result error, not a throw. */
6292
+ function rotateEdgeAbout(edge, angleDeg, center) {
6293
+ const kernel = getKernel();
6294
+ const { handle, dispose } = kernel.composeTransform([{
6295
+ type: "rotate",
6296
+ angle: angleDeg,
6297
+ axis: [
6298
+ 0,
6299
+ 0,
6300
+ 1
6301
+ ],
6302
+ center: [
6303
+ center[0],
6304
+ center[1],
6305
+ 0
6306
+ ]
6307
+ }]);
6308
+ try {
6309
+ return ok(castShape(kernel.locate(edge.wrapped, handle)));
6310
+ } catch (e) {
6311
+ return err(kernelError("CSG_PATH_ELLIPSE_ROTATE", "Path: kernel cannot relocate ellipse-arc edges", e, { operation: "evalPath" }));
6312
+ } finally {
6313
+ dispose();
6314
+ }
6315
+ }
6316
+ function segmentEnd(seg, ctx) {
6317
+ return evalVec2(seg.to, ctx.env, `Path.${seg.kind}.to`);
6318
+ }
6319
+ function segmentToEdge(from, seg, ctx) {
6320
+ switch (seg.kind) {
6321
+ case "Line": {
6322
+ const to = segmentEnd(seg, ctx);
6323
+ if (!to.ok) return to;
6324
+ if (Math.hypot(to.value[0] - from[0], to.value[1] - from[1]) < EPS) return fail("Line: zero length");
6325
+ return ok(makeLine(v3(from), v3(to.value)));
6326
+ }
6327
+ case "Arc": {
6328
+ const to = segmentEnd(seg, ctx);
6329
+ if (!to.ok) return to;
6330
+ const r = evalScalar(seg.radius, ctx.env, "Path.Arc.radius");
6331
+ if (!r.ok) return r;
6332
+ const mid = circularArcMidpoint(from, to.value, r.value, seg.largeArc, seg.clockwise);
6333
+ if (!mid.ok) return mid;
6334
+ return ok(makeThreePointArc(v3(from), v3(mid.value), v3(to.value)));
6335
+ }
6336
+ case "Bezier": {
6337
+ const to = segmentEnd(seg, ctx);
6338
+ if (!to.ok) return to;
6339
+ const controls = [];
6340
+ for (const c of seg.controls) {
6341
+ const cv = evalVec2(c, ctx.env, "Path.Bezier.control");
6342
+ if (!cv.ok) return cv;
6343
+ controls.push(v3(cv.value));
6344
+ }
6345
+ return makeBezierCurve([
6346
+ v3(from),
6347
+ ...controls,
6348
+ v3(to.value)
6349
+ ]);
6350
+ }
6351
+ case "EllipseArc": return ellipseEdge(from, seg, ctx);
6352
+ }
6353
+ }
6354
+ function evalPath(node, ctx) {
6355
+ try {
6356
+ var _usingCtx4 = _usingCtx();
6357
+ if (node.segments.length === 0) return fail("at least one segment required");
6358
+ const start = evalVec2(node.start, ctx.env, "Path.start");
6359
+ if (!start.ok) return start;
6360
+ const scope = _usingCtx4.u(new DisposalScope());
6361
+ const edges = [];
6362
+ let cur = start.value;
6363
+ for (const seg of node.segments) {
6364
+ const e = segmentToEdge(cur, seg, ctx);
6365
+ if (!e.ok) return e;
6366
+ scope.register(e.value);
6367
+ edges.push(e.value);
6368
+ const end = segmentEnd(seg, ctx);
6369
+ if (!end.ok) return end;
6370
+ cur = end.value;
6371
+ }
6372
+ return assembleWire(edges);
6373
+ } catch (_) {
6374
+ _usingCtx4.e = _;
6375
+ } finally {
6376
+ _usingCtx4.d();
6377
+ }
6378
+ }
6379
+ //#endregion
5934
6380
  //#region src/csg/evaluate.ts
5935
6381
  function dispatch(node, ctx) {
5936
6382
  switch (node.kind) {
@@ -5955,6 +6401,10 @@ function dispatch(node, ctx) {
5955
6401
  case "Mirror": return evalMirror(node, ctx);
5956
6402
  case "Compound": return evalCompound(node, ctx);
5957
6403
  case "Instance": return evalInstance(node, ctx);
6404
+ case "Extrude": return evalExtrude(node, ctx);
6405
+ case "Revolve": return evalRevolve(node, ctx);
6406
+ case "Loft": return evalLoft(node, ctx);
6407
+ case "Path": return evalPath(node, ctx);
5958
6408
  }
5959
6409
  }
5960
6410
  function hashExprValue(h, v) {
@@ -6491,6 +6941,34 @@ function booleanToJson(n) {
6491
6941
  function optExprToJson(e) {
6492
6942
  return e ? exprToJson(e) : void 0;
6493
6943
  }
6944
+ function segmentToJson(s) {
6945
+ switch (s.kind) {
6946
+ case "Line": return {
6947
+ kind: "Line",
6948
+ to: exprToJson(s.to)
6949
+ };
6950
+ case "Arc": return {
6951
+ kind: "Arc",
6952
+ to: exprToJson(s.to),
6953
+ radius: exprToJson(s.radius),
6954
+ largeArc: s.largeArc,
6955
+ clockwise: s.clockwise
6956
+ };
6957
+ case "Bezier": return {
6958
+ kind: "Bezier",
6959
+ controls: s.controls.map(exprToJson),
6960
+ to: exprToJson(s.to)
6961
+ };
6962
+ case "EllipseArc": return {
6963
+ kind: "EllipseArc",
6964
+ to: exprToJson(s.to),
6965
+ radii: exprToJson(s.radii),
6966
+ rotation: exprToJson(s.rotation),
6967
+ largeArc: s.largeArc,
6968
+ clockwise: s.clockwise
6969
+ };
6970
+ }
6971
+ }
6494
6972
  function transformToJson(n) {
6495
6973
  switch (n.kind) {
6496
6974
  case "Translate": return {
@@ -6521,6 +6999,28 @@ function transformToJson(n) {
6521
6999
  }
6522
7000
  }
6523
7001
  function nodeToJson(n) {
7002
+ if (n.kind === "Extrude") return {
7003
+ kind: "Extrude",
7004
+ profile: nodeToJson(n.profile),
7005
+ vector: exprToJson(n.vector)
7006
+ };
7007
+ if (n.kind === "Revolve") return {
7008
+ kind: "Revolve",
7009
+ profile: nodeToJson(n.profile),
7010
+ angle: exprToJson(n.angle),
7011
+ axis: optExprToJson(n.axis),
7012
+ at: optExprToJson(n.at)
7013
+ };
7014
+ if (n.kind === "Loft") return {
7015
+ kind: "Loft",
7016
+ sections: n.sections.map(nodeToJson),
7017
+ ruled: n.ruled
7018
+ };
7019
+ if (n.kind === "Path") return {
7020
+ kind: "Path",
7021
+ start: exprToJson(n.start),
7022
+ segments: n.segments.map(segmentToJson)
7023
+ };
6524
7024
  if (n.kind === "Compound") return {
6525
7025
  kind: "Compound",
6526
7026
  children: n.children.map(nodeToJson)
@@ -6672,6 +7172,10 @@ function readNode(j) {
6672
7172
  case "Mirror": return readTransform(kind, j);
6673
7173
  case "Compound": return readCompound(j);
6674
7174
  case "Instance": return readInstance(j);
7175
+ case "Extrude": return readExtrude(j);
7176
+ case "Revolve": return readRevolve(j);
7177
+ case "Loft": return readLoft(j);
7178
+ case "Path": return readPath(j);
6675
7179
  default: return bad(`unknown node kind: ${String(kind)}`);
6676
7180
  }
6677
7181
  }
@@ -6826,6 +7330,100 @@ function readMirror(j, target) {
6826
7330
  at: at.value
6827
7331
  }));
6828
7332
  }
7333
+ function readExtrude(j) {
7334
+ const profile = readNode(j["profile"]);
7335
+ if (!profile.ok) return profile;
7336
+ const vector = readExpr(j["vector"]);
7337
+ if (!vector.ok) return vector;
7338
+ return ok(extrude$1(profile.value, vector.value));
7339
+ }
7340
+ function readRevolve(j) {
7341
+ const profile = readNode(j["profile"]);
7342
+ if (!profile.ok) return profile;
7343
+ const angle = readExpr(j["angle"]);
7344
+ if (!angle.ok) return angle;
7345
+ const axis = readOptExpr(j, "axis");
7346
+ if (!axis.ok) return axis;
7347
+ const at = readOptExpr(j, "at");
7348
+ if (!at.ok) return at;
7349
+ return ok(revolve$1(profile.value, angle.value, {
7350
+ axis: axis.value,
7351
+ at: at.value
7352
+ }));
7353
+ }
7354
+ function readLoft(j) {
7355
+ const sections = readNodeArray(j["sections"], "Loft.sections");
7356
+ if (!sections.ok) return sections;
7357
+ const ruled = j["ruled"];
7358
+ if (ruled !== void 0 && typeof ruled !== "boolean") return bad("Loft.ruled: not a boolean");
7359
+ return ok(loft$1(sections.value, { ruled }));
7360
+ }
7361
+ function readFlag(j, key) {
7362
+ const v = j[key];
7363
+ if (v === void 0) return ok(false);
7364
+ return typeof v === "boolean" ? ok(v) : bad(`${key}: not a boolean`);
7365
+ }
7366
+ function readSegment(j) {
7367
+ if (!isObj(j)) return bad("segment: not an object");
7368
+ const to = readExpr(j["to"]);
7369
+ if (!to.ok) return to;
7370
+ const kind = j["kind"];
7371
+ switch (kind) {
7372
+ case "Line": return ok(lineTo(to.value));
7373
+ case "Arc": {
7374
+ const radius = readExpr(j["radius"]);
7375
+ if (!radius.ok) return radius;
7376
+ const largeArc = readFlag(j, "largeArc");
7377
+ if (!largeArc.ok) return largeArc;
7378
+ const clockwise = readFlag(j, "clockwise");
7379
+ if (!clockwise.ok) return clockwise;
7380
+ return ok(arcTo(to.value, radius.value, {
7381
+ largeArc: largeArc.value,
7382
+ clockwise: clockwise.value
7383
+ }));
7384
+ }
7385
+ case "Bezier": {
7386
+ const raw = j["controls"];
7387
+ if (!Array.isArray(raw)) return bad("Bezier.controls: not array");
7388
+ const controls = [];
7389
+ for (const c of raw) {
7390
+ const r = readExpr(c);
7391
+ if (!r.ok) return r;
7392
+ controls.push(r.value);
7393
+ }
7394
+ return ok(bezierTo(controls, to.value));
7395
+ }
7396
+ case "EllipseArc": {
7397
+ const radii = readExpr(j["radii"]);
7398
+ if (!radii.ok) return radii;
7399
+ const rotation = readExpr(j["rotation"]);
7400
+ if (!rotation.ok) return rotation;
7401
+ const largeArc = readFlag(j, "largeArc");
7402
+ if (!largeArc.ok) return largeArc;
7403
+ const clockwise = readFlag(j, "clockwise");
7404
+ if (!clockwise.ok) return clockwise;
7405
+ return ok(ellipseArcTo(to.value, radii.value, {
7406
+ rotation: rotation.value,
7407
+ largeArc: largeArc.value,
7408
+ clockwise: clockwise.value
7409
+ }));
7410
+ }
7411
+ default: return bad(`unknown segment kind: ${String(kind)}`);
7412
+ }
7413
+ }
7414
+ function readPath(j) {
7415
+ const start = readExpr(j["start"]);
7416
+ if (!start.ok) return start;
7417
+ const raw = j["segments"];
7418
+ if (!Array.isArray(raw)) return bad("Path.segments: not array");
7419
+ const segments = [];
7420
+ for (const s of raw) {
7421
+ const r = readSegment(s);
7422
+ if (!r.ok) return r;
7423
+ segments.push(r.value);
7424
+ }
7425
+ return ok(path(start.value, segments));
7426
+ }
6829
7427
  function readCompound(j) {
6830
7428
  const children = readNodeArray(j["children"], "Compound.children");
6831
7429
  return children.ok ? ok(compound$1(children.value)) : children;
@@ -6937,6 +7535,21 @@ function optimizeNode(n) {
6937
7535
  });
6938
7536
  case "Compound": return compound$1(n.children.map(optimizeNode).filter((c) => c.kind !== "Empty"));
6939
7537
  case "Instance": return instance$1(optimizeNode(n.source), n.placements, n.fuse);
7538
+ case "Extrude":
7539
+ case "Revolve":
7540
+ case "Loft":
7541
+ case "Path": return optimizeFeature(n);
7542
+ }
7543
+ }
7544
+ function optimizeFeature(n) {
7545
+ switch (n.kind) {
7546
+ case "Extrude": return extrude$1(optimizeNode(n.profile), foldExpr(n.vector));
7547
+ case "Revolve": return revolve$1(optimizeNode(n.profile), foldExpr(n.angle), {
7548
+ axis: n.axis ? foldExpr(n.axis) : void 0,
7549
+ at: n.at ? foldExpr(n.at) : void 0
7550
+ });
7551
+ case "Loft": return loft$1(n.sections.map(optimizeNode), { ruled: n.ruled });
7552
+ case "Path": return path(foldExpr(n.start), n.segments.map((s) => foldSegment(s, foldExpr)));
6940
7553
  }
6941
7554
  }
6942
7555
  function optimizeFuse(a, b, tol) {
@@ -7009,7 +7622,8 @@ function rebuildChildren(n, pred, repl) {
7009
7622
  case "Circle":
7010
7623
  case "Line":
7011
7624
  case "Vertex":
7012
- case "Empty": return n;
7625
+ case "Empty":
7626
+ case "Path": return n;
7013
7627
  case "Fuse": return fuse$1(walk(n.a, pred, repl), walk(n.b, pred, repl), n.tolerance);
7014
7628
  case "Cut": return cut$1(walk(n.a, pred, repl), walk(n.b, pred, repl), n.tolerance);
7015
7629
  case "Intersect": return intersect$1(walk(n.a, pred, repl), walk(n.b, pred, repl), n.tolerance);
@@ -7027,6 +7641,12 @@ function rebuildChildren(n, pred, repl) {
7027
7641
  });
7028
7642
  case "Compound": return compound$1(n.children.map((c) => walk(c, pred, repl)));
7029
7643
  case "Instance": return instance$1(walk(n.source, pred, repl), n.placements, n.fuse);
7644
+ case "Extrude": return extrude$1(walk(n.profile, pred, repl), n.vector);
7645
+ case "Revolve": return revolve$1(walk(n.profile, pred, repl), n.angle, {
7646
+ axis: n.axis,
7647
+ at: n.at
7648
+ });
7649
+ case "Loft": return loft$1(n.sections.map((s) => walk(s, pred, repl)), { ruled: n.ruled });
7030
7650
  }
7031
7651
  }
7032
7652
  function forEachNode(root, fn) {
@@ -7044,7 +7664,8 @@ function childrenOf(n) {
7044
7664
  case "Circle":
7045
7665
  case "Line":
7046
7666
  case "Vertex":
7047
- case "Empty": return [];
7667
+ case "Empty":
7668
+ case "Path": return [];
7048
7669
  case "Fuse":
7049
7670
  case "Cut":
7050
7671
  case "Intersect": return [n.a, n.b];
@@ -7056,6 +7677,9 @@ function childrenOf(n) {
7056
7677
  case "Mirror": return [n.target];
7057
7678
  case "Compound": return n.children;
7058
7679
  case "Instance": return [n.source];
7680
+ case "Extrude":
7681
+ case "Revolve": return [n.profile];
7682
+ case "Loft": return n.sections;
7059
7683
  }
7060
7684
  }
7061
7685
  function nodeCount(root) {
@@ -7071,9 +7695,11 @@ var csg_exports = /* @__PURE__ */ __exportAll({
7071
7695
  CSG_VERSION: () => 1,
7072
7696
  Evaluator: () => Evaluator,
7073
7697
  add: () => add,
7698
+ arcTo: () => arcTo,
7074
7699
  asScalarExpr: () => asScalarExpr,
7075
7700
  asVec2Expr: () => asVec2Expr,
7076
7701
  asVec3Expr: () => asVec3Expr,
7702
+ bezierTo: () => bezierTo,
7077
7703
  binOp: () => binOp,
7078
7704
  box: () => box$2,
7079
7705
  buildVec: () => buildVec,
@@ -7084,9 +7710,11 @@ var csg_exports = /* @__PURE__ */ __exportAll({
7084
7710
  cut: () => cut$1,
7085
7711
  cutAll: () => cutAll$1,
7086
7712
  cylinder: () => cylinder$2,
7713
+ ellipseArcTo: () => ellipseArcTo,
7087
7714
  emptyFace: () => emptyFace,
7088
7715
  emptySolid: () => emptySolid,
7089
7716
  emptyWire: () => emptyWire,
7717
+ extrude: () => extrude$1,
7090
7718
  foldExpr: () => foldExpr,
7091
7719
  forEachNode: () => forEachNode,
7092
7720
  fromJSON: () => fromJSON,
@@ -7095,6 +7723,8 @@ var csg_exports = /* @__PURE__ */ __exportAll({
7095
7723
  instance: () => instance$1,
7096
7724
  intersect: () => intersect$1,
7097
7725
  line: () => line$1,
7726
+ lineTo: () => lineTo,
7727
+ loft: () => loft$1,
7098
7728
  mirror: () => mirror$1,
7099
7729
  mul: () => mul,
7100
7730
  nodeCount: () => nodeCount,
@@ -7102,8 +7732,10 @@ var csg_exports = /* @__PURE__ */ __exportAll({
7102
7732
  optimize: () => optimize,
7103
7733
  outputKindOf: () => outputKindOf,
7104
7734
  param: () => param,
7735
+ path: () => path,
7105
7736
  polygon: () => polygon$1,
7106
7737
  replaceNode: () => replaceNode,
7738
+ revolve: () => revolve$1,
7107
7739
  rotate: () => rotate$1,
7108
7740
  scale: () => scale$1,
7109
7741
  sphere: () => sphere$2,