brepjs 18.125.0 → 18.127.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.cjs CHANGED
@@ -254,8 +254,8 @@ function repairMesh(mesh, opts, id) {
254
254
  if (require_errors.isErr(engine)) return engine;
255
255
  try {
256
256
  try {
257
- var _usingCtx$8 = require_shapeTypes._usingCtx();
258
- const repaired = _usingCtx$8.u(engine.value.repair_mesh(mesh.vertices, mesh.triangles, resolution, padding));
257
+ var _usingCtx$11 = require_shapeTypes._usingCtx();
258
+ const repaired = _usingCtx$11.u(engine.value.repair_mesh(mesh.vertices, mesh.triangles, resolution, padding));
259
259
  const vertexCount = repaired.positions.length / 3;
260
260
  return require_errors.ok({
261
261
  vertices: repaired.positions,
@@ -269,9 +269,9 @@ function repairMesh(mesh, opts, id) {
269
269
  }]
270
270
  });
271
271
  } catch (_) {
272
- _usingCtx$8.e = _;
272
+ _usingCtx$11.e = _;
273
273
  } finally {
274
- _usingCtx$8.d();
274
+ _usingCtx$11.d();
275
275
  }
276
276
  } catch (cause) {
277
277
  return require_errors.err(require_errors.computationError("VOXEL_REPAIR_FAILED", cause instanceof Error ? cause.message : "voxel repair failed (grid too large?).", cause));
@@ -350,12 +350,12 @@ function offsetMesh(mesh, distance, opts, id) {
350
350
  if (require_errors.isErr(engine)) return engine;
351
351
  try {
352
352
  try {
353
- var _usingCtx$7 = require_shapeTypes._usingCtx();
354
- return meshFromResult(_usingCtx$7.u(engine.value.offset_mesh(mesh.vertices, mesh.triangles, distance, params.value.resolution, params.value.padding)));
353
+ var _usingCtx$10 = require_shapeTypes._usingCtx();
354
+ return meshFromResult(_usingCtx$10.u(engine.value.offset_mesh(mesh.vertices, mesh.triangles, distance, params.value.resolution, params.value.padding)));
355
355
  } catch (_) {
356
- _usingCtx$7.e = _;
356
+ _usingCtx$10.e = _;
357
357
  } finally {
358
- _usingCtx$7.d();
358
+ _usingCtx$10.d();
359
359
  }
360
360
  } catch (cause) {
361
361
  return require_errors.err(require_errors.computationError("VOXEL_OFFSET_FAILED", cause instanceof Error ? cause.message : "voxel offset failed (grid too large?).", cause));
@@ -480,9 +480,9 @@ function resolveGridParams$1(opts) {
480
480
  */
481
481
  function meshFromField(field) {
482
482
  try {
483
- var _usingCtx$6 = require_shapeTypes._usingCtx();
483
+ var _usingCtx$9 = require_shapeTypes._usingCtx();
484
484
  const rawResult = field.contour();
485
- const { positions, normals, indices } = _usingCtx$6.u({
485
+ const { positions, normals, indices } = _usingCtx$9.u({
486
486
  value: rawResult,
487
487
  [Symbol.dispose]() {
488
488
  rawResult.free();
@@ -502,9 +502,9 @@ function meshFromField(field) {
502
502
  }]
503
503
  });
504
504
  } catch (_) {
505
- _usingCtx$6.e = _;
505
+ _usingCtx$9.e = _;
506
506
  } finally {
507
- _usingCtx$6.d();
507
+ _usingCtx$9.d();
508
508
  }
509
509
  }
510
510
  function fieldDeletable(raw) {
@@ -1072,13 +1072,13 @@ function latticeInfill(mesh, opts, id) {
1072
1072
  const { tag, period, thickness, resolution, padding } = resolved.value;
1073
1073
  try {
1074
1074
  try {
1075
- var _usingCtx$5 = require_shapeTypes._usingCtx();
1076
- const repaired = _usingCtx$5.u(engine.value.lattice_infill(mesh.vertices, mesh.triangles, resolution, padding, tag, period, thickness));
1075
+ var _usingCtx$8 = require_shapeTypes._usingCtx();
1076
+ const repaired = _usingCtx$8.u(engine.value.lattice_infill(mesh.vertices, mesh.triangles, resolution, padding, tag, period, thickness));
1077
1077
  return require_errors.ok(toMesh(repaired));
1078
1078
  } catch (_) {
1079
- _usingCtx$5.e = _;
1079
+ _usingCtx$8.e = _;
1080
1080
  } finally {
1081
- _usingCtx$5.d();
1081
+ _usingCtx$8.d();
1082
1082
  }
1083
1083
  } catch (cause) {
1084
1084
  return require_errors.err(require_errors.computationError("LATTICE_INFILL_FAILED", cause instanceof Error ? cause.message : "lattice infill failed (grid too large?).", cause));
@@ -1557,7 +1557,7 @@ function polyhedron(points, faces, options = {}) {
1557
1557
  }
1558
1558
  //#endregion
1559
1559
  //#region src/operations/straightSkeleton.ts
1560
- var EPS = 1e-10;
1560
+ var EPS$1 = 1e-10;
1561
1561
  function cross2(ax, ay, bx, by) {
1562
1562
  return ax * by - ay * bx;
1563
1563
  }
@@ -1595,7 +1595,7 @@ function bisector(poly, i) {
1595
1595
  const e2x = next.x - cur.x;
1596
1596
  const e2y = next.y - cur.y;
1597
1597
  const e2l = len2(e2x, e2y);
1598
- if (e1l < EPS || e2l < EPS) return {
1598
+ if (e1l < EPS$1 || e2l < EPS$1) return {
1599
1599
  dx: 0,
1600
1600
  dy: 0
1601
1601
  };
@@ -1606,14 +1606,14 @@ function bisector(poly, i) {
1606
1606
  let bx = n1x + n2x;
1607
1607
  let by = n1y + n2y;
1608
1608
  const bl = len2(bx, by);
1609
- if (bl < EPS) return {
1609
+ if (bl < EPS$1) return {
1610
1610
  dx: n1x,
1611
1611
  dy: n1y
1612
1612
  };
1613
1613
  bx /= bl;
1614
1614
  by /= bl;
1615
1615
  const cosHalf = dot2(bx, by, n1x, n1y);
1616
- const speed = Math.abs(cosHalf) > EPS ? 1 / cosHalf : 1;
1616
+ const speed = Math.abs(cosHalf) > EPS$1 ? 1 / cosHalf : 1;
1617
1617
  return {
1618
1618
  dx: bx * speed,
1619
1619
  dy: by * speed
@@ -1675,14 +1675,14 @@ function bisectorIntersectTime(a, b) {
1675
1675
  const ddy = a.by - b.by;
1676
1676
  const dxp = b.x - a.x;
1677
1677
  const dyp = b.y - a.y;
1678
- if (Math.abs(ddx) < EPS && Math.abs(ddy) < EPS) return null;
1678
+ if (Math.abs(ddx) < EPS$1 && Math.abs(ddy) < EPS$1) return null;
1679
1679
  let t;
1680
1680
  if (Math.abs(ddx) > Math.abs(ddy)) t = dxp / ddx;
1681
1681
  else t = dyp / ddy;
1682
- if (t < EPS) return null;
1682
+ if (t < EPS$1) return null;
1683
1683
  const otherDd = Math.abs(ddx) > Math.abs(ddy) ? ddy : ddx;
1684
1684
  const otherDp = Math.abs(ddx) > Math.abs(ddy) ? dyp : dxp;
1685
- if (Math.abs(otherDd) > EPS) {
1685
+ if (Math.abs(otherDd) > EPS$1) {
1686
1686
  const t2 = otherDp / otherDd;
1687
1687
  if (Math.abs(t - t2) > 1e-4 * Math.max(1, Math.abs(t))) return null;
1688
1688
  }
@@ -1693,16 +1693,16 @@ function raySplitTime(node, eA, eB) {
1693
1693
  const edx = eB.x - eA.x;
1694
1694
  const edy = eB.y - eA.y;
1695
1695
  const el = len2(edx, edy);
1696
- if (el < EPS) return null;
1696
+ if (el < EPS$1) return null;
1697
1697
  const enx = -edy / el;
1698
1698
  const eny = edx / el;
1699
1699
  const d0 = (node.x - eA.x) * enx + (node.y - eA.y) * eny;
1700
1700
  const relBx = node.bx - (eA.bx + eB.bx) / 2;
1701
1701
  const relBy = node.by - (eA.by + eB.by) / 2;
1702
1702
  const dRate = relBx * enx + relBy * eny;
1703
- if (Math.abs(dRate) < EPS) return null;
1703
+ if (Math.abs(dRate) < EPS$1) return null;
1704
1704
  const t = -d0 / dRate;
1705
- if (t < EPS) return null;
1705
+ if (t < EPS$1) return null;
1706
1706
  const px = node.x + t * node.bx;
1707
1707
  const py = node.y + t * node.by;
1708
1708
  const ax = eA.x + t * eA.bx;
@@ -1712,7 +1712,7 @@ function raySplitTime(node, eA, eB) {
1712
1712
  const segDx = bxx - ax;
1713
1713
  const segDy = byy - ay;
1714
1714
  const segL = len2(segDx, segDy);
1715
- if (segL < EPS) return t;
1715
+ if (segL < EPS$1) return t;
1716
1716
  const s = dot2(px - ax, py - ay, segDx, segDy) / (segL * segL);
1717
1717
  if (s < -.01 || s > 1.01) return null;
1718
1718
  return t;
@@ -1722,7 +1722,7 @@ function computeEvents(lavNodes) {
1722
1722
  for (const node of lavNodes) {
1723
1723
  if (!node.active || !node.next) continue;
1724
1724
  const t = bisectorIntersectTime(node, node.next);
1725
- if (t !== null && t > EPS) {
1725
+ if (t !== null && t > EPS$1) {
1726
1726
  const x = node.x + t * node.bx;
1727
1727
  const y = node.y + t * node.by;
1728
1728
  events.push({
@@ -1739,7 +1739,7 @@ function computeEvents(lavNodes) {
1739
1739
  let count = 0;
1740
1740
  while (cur !== node.prev && cur !== node && count < 1e3) {
1741
1741
  const st = raySplitTime(node, cur, cur.next);
1742
- if (st !== null && st > EPS) {
1742
+ if (st !== null && st > EPS$1) {
1743
1743
  const x = node.x + st * node.bx;
1744
1744
  const y = node.y + st * node.by;
1745
1745
  events.push({
@@ -1795,7 +1795,7 @@ function computeStraightSkeletonImpl(polygon) {
1795
1795
  const b = a.next;
1796
1796
  const c = b.next;
1797
1797
  const t = bisectorIntersectTime(a, b);
1798
- const time = t !== null && t > EPS ? t : 0;
1798
+ const time = t !== null && t > EPS$1 ? t : 0;
1799
1799
  const mx = (a.x + b.x + c.x) / 3 + time * (a.bx + b.bx + c.bx) / 3;
1800
1800
  const my = (a.y + b.y + c.y) / 3 + time * (a.by + b.by + c.by) / 3;
1801
1801
  const nodeIdx = skeletonNodes.length;
@@ -1934,7 +1934,7 @@ function computeStraightSkeletonImpl(polygon) {
1934
1934
  if (!sn) continue;
1935
1935
  const lastVert = faceVerts[faceVerts.length - 1];
1936
1936
  if (!lastVert) continue;
1937
- if (len2(sn.x - lastVert.x, sn.y - lastVert.y) > EPS) {
1937
+ if (len2(sn.x - lastVert.x, sn.y - lastVert.y) > EPS$1) {
1938
1938
  faceVerts.push({
1939
1939
  x: sn.x,
1940
1940
  y: sn.y
@@ -3901,7 +3901,7 @@ function drill(shape, options) {
3901
3901
  */
3902
3902
  function pocket(shape, options) {
3903
3903
  try {
3904
- var _usingCtx$4 = require_shapeTypes._usingCtx();
3904
+ var _usingCtx$7 = require_shapeTypes._usingCtx();
3905
3905
  const s = resolve(shape);
3906
3906
  const { profile, depth } = options;
3907
3907
  if (depth <= 0) return require_errors.err(require_errors.validationError("POCKET_INVALID_DEPTH", "Pocket depth must be positive"));
@@ -3910,7 +3910,7 @@ function pocket(shape, options) {
3910
3910
  const targetFace = targetResult.value;
3911
3911
  const normal = require_faceFns.normalAt(targetFace);
3912
3912
  const center = require_faceFns.faceCenter(targetFace);
3913
- const scope = _usingCtx$4.u(new require_shapeTypes.DisposalScope());
3913
+ const scope = _usingCtx$7.u(new require_shapeTypes.DisposalScope());
3914
3914
  const { wire: w, owned } = toWire(profile);
3915
3915
  if (owned) scope.register(w);
3916
3916
  const faceResult = require_surfaceBuilders.makeFace(w);
@@ -3923,9 +3923,9 @@ function pocket(shape, options) {
3923
3923
  scope.register(toolResult.value);
3924
3924
  return require_solidBuilders.cut(s, toolResult.value, { unsafe: true });
3925
3925
  } catch (_) {
3926
- _usingCtx$4.e = _;
3926
+ _usingCtx$7.e = _;
3927
3927
  } finally {
3928
- _usingCtx$4.d();
3928
+ _usingCtx$7.d();
3929
3929
  }
3930
3930
  }
3931
3931
  /**
@@ -4515,12 +4515,12 @@ function makeInternalGearProfileWire(params) {
4515
4515
  }
4516
4516
  function makeProfileWire(params, isInternal) {
4517
4517
  try {
4518
- var _usingCtx$3 = require_shapeTypes._usingCtx();
4518
+ var _usingCtx$6 = require_shapeTypes._usingCtx();
4519
4519
  const { teeth, moduleSize, pressureAngle, shift, clearance, flankThinning, samples = adaptiveSampleCount(moduleSize) } = params;
4520
4520
  if (teeth < 4) return require_errors.err(require_errors.validationError("GEAR_TEETH_TOO_FEW", `gear needs ≥ 4 teeth, got ${teeth}`));
4521
4521
  if (moduleSize <= 0) return require_errors.err(require_errors.validationError("GEAR_MODULE_NONPOSITIVE", `module must be > 0, got ${moduleSize}`));
4522
4522
  const tm = gearGeometry(teeth, moduleSize, pressureAngle, shift, clearance, flankThinning, isInternal);
4523
- const scope = _usingCtx$3.u(new require_shapeTypes.DisposalScope());
4523
+ const scope = _usingCtx$6.u(new require_shapeTypes.DisposalScope());
4524
4524
  const allEdges = [];
4525
4525
  for (let i = 0; i < teeth; i++) {
4526
4526
  const periodEdges = buildToothPeriodEdges(tm, i, teeth, samples, scope);
@@ -4531,9 +4531,9 @@ function makeProfileWire(params, isInternal) {
4531
4531
  if (require_errors.isErr(wireResult)) return wireResult;
4532
4532
  return require_errors.ok(wireResult.value);
4533
4533
  } catch (_) {
4534
- _usingCtx$3.e = _;
4534
+ _usingCtx$6.e = _;
4535
4535
  } finally {
4536
- _usingCtx$3.d();
4536
+ _usingCtx$6.d();
4537
4537
  }
4538
4538
  }
4539
4539
  //#endregion
@@ -4714,8 +4714,8 @@ function resolvePlanetaryParams(params) {
4714
4714
  }
4715
4715
  function makeOuterCircleWire(radius) {
4716
4716
  try {
4717
- var _usingCtx$2 = require_shapeTypes._usingCtx();
4718
- const circleEdge = _usingCtx$2.u(new require_shapeTypes.DisposalScope()).register(require_surfaceBuilders.makeCircle(radius, [
4717
+ var _usingCtx$5 = require_shapeTypes._usingCtx();
4718
+ const circleEdge = _usingCtx$5.u(new require_shapeTypes.DisposalScope()).register(require_surfaceBuilders.makeCircle(radius, [
4719
4719
  0,
4720
4720
  0,
4721
4721
  0
@@ -4728,9 +4728,9 @@ function makeOuterCircleWire(radius) {
4728
4728
  if (require_errors.isErr(wireResult)) return wireResult;
4729
4729
  return require_errors.ok(wireResult.value);
4730
4730
  } catch (_) {
4731
- _usingCtx$2.e = _;
4731
+ _usingCtx$5.e = _;
4732
4732
  } finally {
4733
- _usingCtx$2.d();
4733
+ _usingCtx$5.d();
4734
4734
  }
4735
4735
  }
4736
4736
  function finalizeExternalSolid(wire, thickness, bore, geom, diagnostics) {
@@ -5327,6 +5327,14 @@ function evalVec3(expr, env, where) {
5327
5327
  c
5328
5328
  ]);
5329
5329
  }
5330
+ function evalVec2(expr, env, where) {
5331
+ const r = evalExpr(expr, env);
5332
+ if (!r.ok) return r;
5333
+ const v = expectVecLen(r.value, 2, where);
5334
+ if (!v.ok) return v;
5335
+ const [a, b] = v.value;
5336
+ return require_errors.ok([a, b]);
5337
+ }
5330
5338
  function projectEnv(env, deps) {
5331
5339
  if (deps.size === 0) return Object.freeze({});
5332
5340
  const out = {};
@@ -5337,6 +5345,91 @@ function projectEnv(env, deps) {
5337
5345
  return Object.freeze(out);
5338
5346
  }
5339
5347
  //#endregion
5348
+ //#region src/csg/segments.ts
5349
+ /**
5350
+ * Serializable 2D segment vocabulary — endpoint-parametrized (SVG-style)
5351
+ * curve segments shared by the Path node (open spines) and, later, Profile
5352
+ * contours. Positions and radii are expressions, so `freeParams` propagates
5353
+ * and segment data hashes with the same FNV machinery as every node field.
5354
+ */
5355
+ function lineTo(to) {
5356
+ return {
5357
+ kind: "Line",
5358
+ to: asVec2Expr(to)
5359
+ };
5360
+ }
5361
+ function arcTo(to, radius, options) {
5362
+ return {
5363
+ kind: "Arc",
5364
+ to: asVec2Expr(to),
5365
+ radius: asScalarExpr(radius),
5366
+ largeArc: options?.largeArc ?? false,
5367
+ clockwise: options?.clockwise ?? false
5368
+ };
5369
+ }
5370
+ function bezierTo(controls, to) {
5371
+ return {
5372
+ kind: "Bezier",
5373
+ controls: controls.map(asVec2Expr),
5374
+ to: asVec2Expr(to)
5375
+ };
5376
+ }
5377
+ function ellipseArcTo(to, radii, options) {
5378
+ return {
5379
+ kind: "EllipseArc",
5380
+ to: asVec2Expr(to),
5381
+ radii: asVec2Expr(radii),
5382
+ rotation: asScalarExpr(options?.rotation ?? 0),
5383
+ largeArc: options?.largeArc ?? false,
5384
+ clockwise: options?.clockwise ?? false
5385
+ };
5386
+ }
5387
+ function hashSegments(h0, segments) {
5388
+ let h = fnvMixInt32(h0, segments.length);
5389
+ const mix = (acc, e) => fnvMixHash(acc, e.structuralHash);
5390
+ for (const s of segments) {
5391
+ h = fnvMixString(h, s.kind);
5392
+ h = mix(h, s.to);
5393
+ if (s.kind === "Arc") {
5394
+ h = mix(h, s.radius);
5395
+ h = fnvMixBool(fnvMixBool(h, s.largeArc), s.clockwise);
5396
+ } else if (s.kind === "Bezier") {
5397
+ h = fnvMixInt32(h, s.controls.length);
5398
+ for (const c of s.controls) h = mix(h, c);
5399
+ } else if (s.kind === "EllipseArc") {
5400
+ h = mix(h, s.radii);
5401
+ h = mix(h, s.rotation);
5402
+ h = fnvMixBool(fnvMixBool(h, s.largeArc), s.clockwise);
5403
+ }
5404
+ }
5405
+ return h;
5406
+ }
5407
+ function segmentFreeParams(segments) {
5408
+ const out = [];
5409
+ for (const s of segments) {
5410
+ out.push(s.to);
5411
+ if (s.kind === "Arc") out.push(s.radius);
5412
+ else if (s.kind === "Bezier") out.push(...s.controls);
5413
+ else if (s.kind === "EllipseArc") out.push(s.radii, s.rotation);
5414
+ }
5415
+ return out;
5416
+ }
5417
+ function foldSegment(s, foldExpr) {
5418
+ switch (s.kind) {
5419
+ case "Line": return lineTo(foldExpr(s.to));
5420
+ case "Arc": return arcTo(foldExpr(s.to), foldExpr(s.radius), {
5421
+ largeArc: s.largeArc,
5422
+ clockwise: s.clockwise
5423
+ });
5424
+ case "Bezier": return bezierTo(s.controls.map(foldExpr), foldExpr(s.to));
5425
+ case "EllipseArc": return ellipseArcTo(foldExpr(s.to), foldExpr(s.radii), {
5426
+ rotation: foldExpr(s.rotation),
5427
+ largeArc: s.largeArc,
5428
+ clockwise: s.clockwise
5429
+ });
5430
+ }
5431
+ }
5432
+ //#endregion
5340
5433
  //#region src/csg/builders.ts
5341
5434
  var EMPTY_DEPS = /* @__PURE__ */ new Set();
5342
5435
  function startHash(tag) {
@@ -5513,29 +5606,31 @@ function intersect$1(a, b, tolerance) {
5513
5606
  };
5514
5607
  }
5515
5608
  function fuseAll$1(shapes, tolerance) {
5516
- let h = fnvMixInt32(startHash("FuseAll"), shapes.length);
5517
- for (const s of shapes) h = mix(h, s);
5609
+ const copied = [...shapes];
5610
+ let h = fnvMixInt32(startHash("FuseAll"), copied.length);
5611
+ for (const s of copied) h = mix(h, s);
5518
5612
  h = mixOptNumber(h, tolerance);
5519
5613
  return {
5520
5614
  kind: "FuseAll",
5521
- shapes,
5615
+ shapes: copied,
5522
5616
  tolerance,
5523
5617
  structuralHash: h,
5524
- freeParams: depsOf(...shapes)
5618
+ freeParams: depsOf(...copied)
5525
5619
  };
5526
5620
  }
5527
5621
  function cutAll$1(base, tools, tolerance) {
5622
+ const copied = [...tools];
5528
5623
  let h = mix(startHash("CutAll"), base);
5529
- h = fnvMixInt32(h, tools.length);
5530
- for (const t of tools) h = mix(h, t);
5624
+ h = fnvMixInt32(h, copied.length);
5625
+ for (const t of copied) h = mix(h, t);
5531
5626
  h = mixOptNumber(h, tolerance);
5532
5627
  return {
5533
5628
  kind: "CutAll",
5534
5629
  base,
5535
- tools,
5630
+ tools: copied,
5536
5631
  tolerance,
5537
5632
  structuralHash: h,
5538
- freeParams: depsOf(base, ...tools)
5633
+ freeParams: depsOf(base, ...copied)
5539
5634
  };
5540
5635
  }
5541
5636
  function optVec3$1(v) {
@@ -5607,14 +5702,82 @@ function extrude$1(profile, vector) {
5607
5702
  freeParams: depsOf(profile, ve)
5608
5703
  };
5609
5704
  }
5705
+ /** Sweep a face-producing profile along a Wire/Edge-producing spine.
5706
+ * `frenet` is canonicalized (default false) so the default and explicit
5707
+ * forms share one content address. */
5708
+ function sweep$2(profile, spine, options) {
5709
+ const frenet = options?.frenet ?? false;
5710
+ let h = mix(mix(startHash("Sweep"), profile), spine);
5711
+ h = fnvMixBool(h, frenet);
5712
+ return {
5713
+ kind: "Sweep",
5714
+ profile,
5715
+ spine,
5716
+ frenet,
5717
+ structuralHash: h,
5718
+ freeParams: depsOf(profile, spine)
5719
+ };
5720
+ }
5721
+ /** Open (or incidentally closed) planar path in the XY plane at z = 0,
5722
+ * producing a Wire. Segments come from `lineTo`/`arcTo`/`bezierTo`/
5723
+ * `ellipseArcTo`. */
5724
+ function path(start, segments) {
5725
+ const se = asVec2Expr(start);
5726
+ const copied = [...segments];
5727
+ let h = mix(startHash("Path"), se);
5728
+ h = hashSegments(h, copied);
5729
+ return {
5730
+ kind: "Path",
5731
+ start: se,
5732
+ segments: copied,
5733
+ structuralHash: h,
5734
+ freeParams: depsOf(se, ...segmentFreeParams(copied))
5735
+ };
5736
+ }
5737
+ /** Loft through two or more face-producing sections (default ruled). */
5738
+ function loft$1(sections, options) {
5739
+ const copied = [...sections];
5740
+ const ruled = options?.ruled ?? true;
5741
+ let h = fnvMixInt32(startHash("Loft"), copied.length);
5742
+ for (const s of copied) h = mix(h, s);
5743
+ h = fnvMixBool(h, ruled);
5744
+ return {
5745
+ kind: "Loft",
5746
+ sections: copied,
5747
+ ruled,
5748
+ structuralHash: h,
5749
+ freeParams: depsOf(...copied)
5750
+ };
5751
+ }
5752
+ /** Angle is in degrees (matching `rotate`); defaults to a full revolution
5753
+ * around the Z axis at the origin. Evaluation clamps angles above 360 to one
5754
+ * revolution (kernels diverge past 2*pi) and rejects non-positive angles. */
5755
+ function revolve$1(profile, angle = 360, options) {
5756
+ const ae = asScalarExpr(angle);
5757
+ const axisE = optVec3$1(options?.axis);
5758
+ const atE = optVec3$1(options?.at);
5759
+ let h = mix(mix(startHash("Revolve"), profile), ae);
5760
+ h = mixOptExpr(h, axisE);
5761
+ h = mixOptExpr(h, atE);
5762
+ return {
5763
+ kind: "Revolve",
5764
+ profile,
5765
+ angle: ae,
5766
+ axis: axisE,
5767
+ at: atE,
5768
+ structuralHash: h,
5769
+ freeParams: depsOf(profile, ae, axisE, atE)
5770
+ };
5771
+ }
5610
5772
  function compound$1(children) {
5611
- let h = fnvMixInt32(startHash("Compound"), children.length);
5612
- for (const c of children) h = mix(h, c);
5773
+ const copied = [...children];
5774
+ let h = fnvMixInt32(startHash("Compound"), copied.length);
5775
+ for (const c of copied) h = mix(h, c);
5613
5776
  return {
5614
5777
  kind: "Compound",
5615
- children,
5778
+ children: copied,
5616
5779
  structuralHash: h,
5617
- freeParams: depsOf(...children)
5780
+ freeParams: depsOf(...copied)
5618
5781
  };
5619
5782
  }
5620
5783
  function instance$1(source, placements, fuse = false) {
@@ -5653,7 +5816,11 @@ function outputKindOf(node) {
5653
5816
  case "Rotate":
5654
5817
  case "Scale":
5655
5818
  case "Mirror": return outputKindOf(node.target);
5656
- case "Extrude": return "Solid";
5819
+ case "Extrude":
5820
+ case "Revolve":
5821
+ case "Loft":
5822
+ case "Sweep": return "Solid";
5823
+ case "Path": return "Wire";
5657
5824
  case "Compound": return "Compound";
5658
5825
  case "Instance": return node.fuse ? "Solid" : "Compound";
5659
5826
  }
@@ -5933,17 +6100,322 @@ function evalInstance(node, ctx) {
5933
6100
  return materialize(instance(r.value, node.placements), { fuse: node.fuse });
5934
6101
  }
5935
6102
  //#endregion
6103
+ //#region src/csg/evaluators/profileFace.ts
6104
+ /** Evaluate a profile child and prove it is an oriented planar face — the
6105
+ * input contract shared by every profile-consuming feature node. */
6106
+ function resolveProfileFace(ctx, profile, where) {
6107
+ const p = ctx.evalNode(profile);
6108
+ if (!p.ok) return p;
6109
+ if (!require_shapeTypes.isFace(p.value)) return require_errors.err(require_errors.typeCastError("CSG_NOT_FACE", `${where}: node did not produce a Face`));
6110
+ const oriented = require_validityTypes.orientedFace(p.value);
6111
+ if (!oriented.ok) return require_errors.err(require_errors.typeCastError("CSG_NOT_FACE", `${where}: ${oriented.error}`));
6112
+ if (!require_validityTypes.isPlanarFace(oriented.value)) return require_errors.err(require_errors.typeCastError("CSG_NOT_FACE", `${where}: face is not planar`));
6113
+ return require_errors.ok(oriented.value);
6114
+ }
6115
+ //#endregion
5936
6116
  //#region src/csg/evaluators/extrude.ts
5937
6117
  function evalExtrude(node, ctx) {
5938
6118
  const v = evalVec3(node.vector, ctx.env, "Extrude.vector");
5939
6119
  if (!v.ok) return v;
5940
- const p = ctx.evalNode(node.profile);
6120
+ const face = resolveProfileFace(ctx, node.profile, "Extrude.profile");
6121
+ if (!face.ok) return face;
6122
+ return require_textBlueprints.extrude(face.value, v.value);
6123
+ }
6124
+ //#endregion
6125
+ //#region src/csg/evaluators/revolve.ts
6126
+ function evalRevolve(node, ctx) {
6127
+ const a = evalScalar(node.angle, ctx.env, "Revolve.angle");
6128
+ if (!a.ok) return a;
6129
+ if (a.value <= 0) return require_errors.err(require_errors.validationError("CSG_REVOLVE_ANGLE", `Revolve.angle must be positive (degrees), got ${a.value}`));
6130
+ const deg = Math.min(a.value, 360);
6131
+ const axis = node.axis ? evalVec3(node.axis, ctx.env, "Revolve.axis") : require_errors.ok([
6132
+ 0,
6133
+ 0,
6134
+ 1
6135
+ ]);
6136
+ if (!axis.ok) return axis;
6137
+ const at = node.at ? evalVec3(node.at, ctx.env, "Revolve.at") : require_errors.ok([
6138
+ 0,
6139
+ 0,
6140
+ 0
6141
+ ]);
6142
+ if (!at.ok) return at;
6143
+ const face = resolveProfileFace(ctx, node.profile, "Revolve.profile");
6144
+ if (!face.ok) return face;
6145
+ return require_textBlueprints.revolve(face.value, at.value, axis.value, deg * Math.PI / 180);
6146
+ }
6147
+ //#endregion
6148
+ //#region src/csg/evaluators/loft.ts
6149
+ function evalLoft(node, ctx) {
6150
+ try {
6151
+ var _usingCtx$4 = require_shapeTypes._usingCtx();
6152
+ if (node.sections.length < 2) return require_errors.err(require_errors.validationError("CSG_LOFT_SECTIONS", `Loft requires at least 2 sections, got ${node.sections.length}`));
6153
+ const scope = _usingCtx$4.u(new require_shapeTypes.DisposalScope());
6154
+ const wires = [];
6155
+ for (const [i, section] of node.sections.entries()) {
6156
+ const face = resolveProfileFace(ctx, section, `Loft.sections[${i}]`);
6157
+ if (!face.ok) return face;
6158
+ const w = require_faceFns.outerWire(face.value);
6159
+ scope.register(w);
6160
+ wires.push(w);
6161
+ }
6162
+ return require_loftFns.loft(wires, { ruled: node.ruled });
6163
+ } catch (_) {
6164
+ _usingCtx$4.e = _;
6165
+ } finally {
6166
+ _usingCtx$4.d();
6167
+ }
6168
+ }
6169
+ //#endregion
6170
+ //#region src/csg/evaluators/path.ts
6171
+ var EPS = 1e-9;
6172
+ function v3(p) {
6173
+ return [
6174
+ p[0],
6175
+ p[1],
6176
+ 0
6177
+ ];
6178
+ }
6179
+ function fail(msg) {
6180
+ return require_errors.err(require_errors.validationError("CSG_PATH_SEGMENT", `Path: ${msg}`));
6181
+ }
6182
+ /** SVG-style endpoint circular arc: recover the on-arc midpoint so the edge
6183
+ * can be built as a three-point arc. */
6184
+ function circularArcMidpoint(from, to, r, largeArc, clockwise) {
6185
+ const [x1, y1] = from;
6186
+ const [x2, y2] = to;
6187
+ const dx = x2 - x1;
6188
+ const dy = y2 - y1;
6189
+ const d = Math.hypot(dx, dy);
6190
+ if (d < EPS) return fail("Arc: coincident endpoints");
6191
+ if (d > 2 * r + 1e-6) return fail(`Arc: radius ${r} too small for chord ${d}`);
6192
+ const h = Math.sqrt(Math.max(0, r * r - d / 2 * (d / 2)));
6193
+ const ux = dx / d;
6194
+ const uy = dy / d;
6195
+ const sign = clockwise === largeArc ? 1 : -1;
6196
+ const cx = (x1 + x2) / 2 + sign * h * -uy;
6197
+ const cy = (y1 + y2) / 2 + sign * h * ux;
6198
+ const a0 = Math.atan2(y1 - cy, x1 - cx);
6199
+ let a1 = Math.atan2(y2 - cy, x2 - cx);
6200
+ if (clockwise) while (a1 >= a0 - EPS) a1 -= 2 * Math.PI;
6201
+ else while (a1 <= a0 + EPS) a1 += 2 * Math.PI;
6202
+ const amid = (a0 + a1) / 2;
6203
+ return require_errors.ok([cx + r * Math.cos(amid), cy + r * Math.sin(amid)]);
6204
+ }
6205
+ /** SVG F.6.5 endpoint-to-center conversion (math y-up; sweep = !clockwise).
6206
+ * Angles are relative to the rotated x-axis. */
6207
+ function ellipseArcParams(from, to, radii, phi, largeArc, clockwise) {
6208
+ const sweep = !clockwise;
6209
+ let [rx, ry] = radii;
6210
+ if (rx <= 0 || ry <= 0) return fail("EllipseArc: non-positive radius");
6211
+ const cosPhi = Math.cos(phi);
6212
+ const sinPhi = Math.sin(phi);
6213
+ const hx = (from[0] - to[0]) / 2;
6214
+ const hy = (from[1] - to[1]) / 2;
6215
+ const x1p = cosPhi * hx + sinPhi * hy;
6216
+ const y1p = -sinPhi * hx + cosPhi * hy;
6217
+ const lambda = x1p * x1p / (rx * rx) + y1p * y1p / (ry * ry);
6218
+ if (lambda > 1) {
6219
+ const s = Math.sqrt(lambda);
6220
+ rx *= s;
6221
+ ry *= s;
6222
+ }
6223
+ const num = rx * rx * ry * ry - rx * rx * y1p * y1p - ry * ry * x1p * x1p;
6224
+ const den = rx * rx * y1p * y1p + ry * ry * x1p * x1p;
6225
+ if (den < EPS) return fail("EllipseArc: coincident endpoints");
6226
+ const coef = (largeArc !== sweep ? 1 : -1) * Math.sqrt(Math.max(0, num / den));
6227
+ const cxp = coef * (rx * y1p / ry);
6228
+ const cyp = coef * (-(ry * x1p) / rx);
6229
+ const cx = cosPhi * cxp - sinPhi * cyp + (from[0] + to[0]) / 2;
6230
+ const cy = sinPhi * cxp + cosPhi * cyp + (from[1] + to[1]) / 2;
6231
+ const theta1 = Math.atan2((y1p - cyp) / ry, (x1p - cxp) / rx);
6232
+ let dTheta = Math.atan2((-y1p - cyp) / ry, (-x1p - cxp) / rx) - theta1;
6233
+ if (!sweep && dTheta > 0) dTheta -= 2 * Math.PI;
6234
+ if (sweep && dTheta < 0) dTheta += 2 * Math.PI;
6235
+ const t1 = dTheta >= 0 ? theta1 : theta1 + dTheta;
6236
+ const t2 = dTheta >= 0 ? theta1 + dTheta : theta1;
6237
+ return require_errors.ok({
6238
+ center: [cx, cy],
6239
+ theta1: t1,
6240
+ theta2: t2,
6241
+ rx,
6242
+ ry
6243
+ });
6244
+ }
6245
+ function ellipseEdge(from, seg, ctx) {
6246
+ if (seg.kind !== "EllipseArc") return fail("internal: not an ellipse segment");
6247
+ const to = evalVec2(seg.to, ctx.env, "Path.EllipseArc.to");
6248
+ if (!to.ok) return to;
6249
+ const radii = evalVec2(seg.radii, ctx.env, "Path.EllipseArc.radii");
6250
+ if (!radii.ok) return radii;
6251
+ const rotDeg = evalScalar(seg.rotation, ctx.env, "Path.EllipseArc.rotation");
6252
+ if (!rotDeg.ok) return rotDeg;
6253
+ const phi = rotDeg.value * Math.PI / 180;
6254
+ const p = ellipseArcParams(from, to.value, radii.value, phi, seg.largeArc, seg.clockwise);
5941
6255
  if (!p.ok) return p;
5942
- if (!require_shapeTypes.isFace(p.value)) return require_errors.err(require_errors.typeCastError("CSG_NOT_FACE", "Extrude.profile: node did not produce a Face"));
5943
- const oriented = require_validityTypes.orientedFace(p.value);
5944
- if (!oriented.ok) return require_errors.err(require_errors.typeCastError("CSG_NOT_FACE", `Extrude.profile: ${oriented.error}`));
5945
- if (!require_validityTypes.isPlanarFace(oriented.value)) return require_errors.err(require_errors.typeCastError("CSG_NOT_FACE", "Extrude.profile: face is not planar"));
5946
- return require_textBlueprints.extrude(oriented.value, v.value);
6256
+ const { center, theta1, theta2, rx, ry } = p.value;
6257
+ let arc;
6258
+ let psi;
6259
+ if (rx >= ry) {
6260
+ arc = require_surfaceBuilders.makeEllipseArc(rx, ry, theta1, theta2, v3(center), [
6261
+ 0,
6262
+ 0,
6263
+ 1
6264
+ ]);
6265
+ psi = phi;
6266
+ } else {
6267
+ arc = require_surfaceBuilders.makeEllipseArc(ry, rx, theta1 - Math.PI / 2, theta2 - Math.PI / 2, v3(center), [
6268
+ 0,
6269
+ 0,
6270
+ 1
6271
+ ]);
6272
+ psi = phi + Math.PI / 2;
6273
+ }
6274
+ if (!arc.ok) return arc;
6275
+ let edge = arc.value;
6276
+ if (Math.abs(psi) > EPS) try {
6277
+ var _usingCtx$3 = require_shapeTypes._usingCtx();
6278
+ const rotated = rotateEdgeAbout(_usingCtx$3.u(edge), psi * 180 / Math.PI, center);
6279
+ if (!rotated.ok) return rotated;
6280
+ edge = rotated.value;
6281
+ } catch (_) {
6282
+ _usingCtx$3.e = _;
6283
+ } finally {
6284
+ _usingCtx$3.d();
6285
+ }
6286
+ if (seg.clockwise) try {
6287
+ var _usingCtx3 = require_shapeTypes._usingCtx();
6288
+ const tmp = _usingCtx3.u(edge);
6289
+ edge = require_curveFns.flipOrientation(tmp);
6290
+ } catch (_) {
6291
+ _usingCtx3.e = _;
6292
+ } finally {
6293
+ _usingCtx3.d();
6294
+ }
6295
+ return require_errors.ok(edge);
6296
+ }
6297
+ /** Rotate an edge about the Z axis at `center` (degrees) via a cheap location
6298
+ * re-tag; returns a fresh, independently-disposable handle. Kernels without
6299
+ * an edge-relocation path (brepkit) surface as a Result error, not a throw. */
6300
+ function rotateEdgeAbout(edge, angleDeg, center) {
6301
+ const kernel = require_shapeTypes.getKernel();
6302
+ const { handle, dispose } = kernel.composeTransform([{
6303
+ type: "rotate",
6304
+ angle: angleDeg,
6305
+ axis: [
6306
+ 0,
6307
+ 0,
6308
+ 1
6309
+ ],
6310
+ center: [
6311
+ center[0],
6312
+ center[1],
6313
+ 0
6314
+ ]
6315
+ }]);
6316
+ try {
6317
+ return require_errors.ok(require_shapeTypes.castShape(kernel.locate(edge.wrapped, handle)));
6318
+ } catch (e) {
6319
+ return require_errors.err(require_errors.kernelError("CSG_PATH_ELLIPSE_ROTATE", "Path: kernel cannot relocate ellipse-arc edges", e, { operation: "evalPath" }));
6320
+ } finally {
6321
+ dispose();
6322
+ }
6323
+ }
6324
+ function segmentEnd(seg, ctx) {
6325
+ return evalVec2(seg.to, ctx.env, `Path.${seg.kind}.to`);
6326
+ }
6327
+ function segmentToEdge(from, seg, ctx) {
6328
+ switch (seg.kind) {
6329
+ case "Line": {
6330
+ const to = segmentEnd(seg, ctx);
6331
+ if (!to.ok) return to;
6332
+ if (Math.hypot(to.value[0] - from[0], to.value[1] - from[1]) < EPS) return fail("Line: zero length");
6333
+ return require_errors.ok(require_surfaceBuilders.makeLine(v3(from), v3(to.value)));
6334
+ }
6335
+ case "Arc": {
6336
+ const to = segmentEnd(seg, ctx);
6337
+ if (!to.ok) return to;
6338
+ const r = evalScalar(seg.radius, ctx.env, "Path.Arc.radius");
6339
+ if (!r.ok) return r;
6340
+ const mid = circularArcMidpoint(from, to.value, r.value, seg.largeArc, seg.clockwise);
6341
+ if (!mid.ok) return mid;
6342
+ return require_errors.ok(require_surfaceBuilders.makeThreePointArc(v3(from), v3(mid.value), v3(to.value)));
6343
+ }
6344
+ case "Bezier": {
6345
+ const to = segmentEnd(seg, ctx);
6346
+ if (!to.ok) return to;
6347
+ const controls = [];
6348
+ for (const c of seg.controls) {
6349
+ const cv = evalVec2(c, ctx.env, "Path.Bezier.control");
6350
+ if (!cv.ok) return cv;
6351
+ controls.push(v3(cv.value));
6352
+ }
6353
+ return require_surfaceBuilders.makeBezierCurve([
6354
+ v3(from),
6355
+ ...controls,
6356
+ v3(to.value)
6357
+ ]);
6358
+ }
6359
+ case "EllipseArc": return ellipseEdge(from, seg, ctx);
6360
+ }
6361
+ }
6362
+ function evalPath(node, ctx) {
6363
+ try {
6364
+ var _usingCtx4 = require_shapeTypes._usingCtx();
6365
+ if (node.segments.length === 0) return fail("at least one segment required");
6366
+ const start = evalVec2(node.start, ctx.env, "Path.start");
6367
+ if (!start.ok) return start;
6368
+ const scope = _usingCtx4.u(new require_shapeTypes.DisposalScope());
6369
+ const edges = [];
6370
+ let cur = start.value;
6371
+ for (const seg of node.segments) {
6372
+ const e = segmentToEdge(cur, seg, ctx);
6373
+ if (!e.ok) return e;
6374
+ scope.register(e.value);
6375
+ edges.push(e.value);
6376
+ const end = segmentEnd(seg, ctx);
6377
+ if (!end.ok) return end;
6378
+ cur = end.value;
6379
+ }
6380
+ return require_surfaceBuilders.assembleWire(edges);
6381
+ } catch (_) {
6382
+ _usingCtx4.e = _;
6383
+ } finally {
6384
+ _usingCtx4.d();
6385
+ }
6386
+ }
6387
+ //#endregion
6388
+ //#region src/csg/evaluators/sweep.ts
6389
+ function evalSweep(node, ctx) {
6390
+ try {
6391
+ var _usingCtx$2 = require_shapeTypes._usingCtx();
6392
+ const face = resolveProfileFace(ctx, node.profile, "Sweep.profile");
6393
+ if (!face.ok) return face;
6394
+ const spineShape = ctx.evalNode(node.spine);
6395
+ if (!spineShape.ok) return spineShape;
6396
+ const scope = _usingCtx$2.u(new require_shapeTypes.DisposalScope());
6397
+ let spine;
6398
+ if (require_shapeTypes.isWire(spineShape.value)) spine = spineShape.value;
6399
+ else if (require_shapeTypes.isEdge(spineShape.value)) {
6400
+ const wrapped = require_surfaceBuilders.assembleWire([spineShape.value]);
6401
+ if (!wrapped.ok) return wrapped;
6402
+ scope.register(wrapped.value);
6403
+ spine = wrapped.value;
6404
+ } else return require_errors.err(require_errors.typeCastError("CSG_SWEEP_SPINE", "Sweep.spine: node did not produce a Wire or Edge"));
6405
+ const profileWire = require_faceFns.outerWire(face.value);
6406
+ scope.register(profileWire);
6407
+ const r = require_loftFns.sweep(profileWire, spine, { frenet: node.frenet });
6408
+ if (!r.ok) return r;
6409
+ if (Array.isArray(r.value)) {
6410
+ for (const s of r.value) s[Symbol.dispose]();
6411
+ return require_errors.err(require_errors.typeCastError("CSG_SWEEP_SPINE", "Sweep: unexpected shell-mode result"));
6412
+ }
6413
+ return require_errors.ok(r.value);
6414
+ } catch (_) {
6415
+ _usingCtx$2.e = _;
6416
+ } finally {
6417
+ _usingCtx$2.d();
6418
+ }
5947
6419
  }
5948
6420
  //#endregion
5949
6421
  //#region src/csg/evaluate.ts
@@ -5971,6 +6443,10 @@ function dispatch(node, ctx) {
5971
6443
  case "Compound": return evalCompound(node, ctx);
5972
6444
  case "Instance": return evalInstance(node, ctx);
5973
6445
  case "Extrude": return evalExtrude(node, ctx);
6446
+ case "Revolve": return evalRevolve(node, ctx);
6447
+ case "Loft": return evalLoft(node, ctx);
6448
+ case "Path": return evalPath(node, ctx);
6449
+ case "Sweep": return evalSweep(node, ctx);
5974
6450
  }
5975
6451
  }
5976
6452
  function hashExprValue(h, v) {
@@ -6377,9 +6853,10 @@ function withEvaluator(options, fn) {
6377
6853
  _usingCtx$1.d();
6378
6854
  }
6379
6855
  }
6856
+ var MIN_CSG_VERSION = 1;
6380
6857
  function toJSON(node) {
6381
6858
  return {
6382
- csgVersion: 1,
6859
+ csgVersion: 2,
6383
6860
  root: nodeToJson(node)
6384
6861
  };
6385
6862
  }
@@ -6507,6 +6984,34 @@ function booleanToJson(n) {
6507
6984
  function optExprToJson(e) {
6508
6985
  return e ? exprToJson(e) : void 0;
6509
6986
  }
6987
+ function segmentToJson(s) {
6988
+ switch (s.kind) {
6989
+ case "Line": return {
6990
+ kind: "Line",
6991
+ to: exprToJson(s.to)
6992
+ };
6993
+ case "Arc": return {
6994
+ kind: "Arc",
6995
+ to: exprToJson(s.to),
6996
+ radius: exprToJson(s.radius),
6997
+ largeArc: s.largeArc,
6998
+ clockwise: s.clockwise
6999
+ };
7000
+ case "Bezier": return {
7001
+ kind: "Bezier",
7002
+ controls: s.controls.map(exprToJson),
7003
+ to: exprToJson(s.to)
7004
+ };
7005
+ case "EllipseArc": return {
7006
+ kind: "EllipseArc",
7007
+ to: exprToJson(s.to),
7008
+ radii: exprToJson(s.radii),
7009
+ rotation: exprToJson(s.rotation),
7010
+ largeArc: s.largeArc,
7011
+ clockwise: s.clockwise
7012
+ };
7013
+ }
7014
+ }
6510
7015
  function transformToJson(n) {
6511
7016
  switch (n.kind) {
6512
7017
  case "Translate": return {
@@ -6542,6 +7047,29 @@ function nodeToJson(n) {
6542
7047
  profile: nodeToJson(n.profile),
6543
7048
  vector: exprToJson(n.vector)
6544
7049
  };
7050
+ if (n.kind === "Revolve") return {
7051
+ kind: "Revolve",
7052
+ profile: nodeToJson(n.profile),
7053
+ angle: exprToJson(n.angle),
7054
+ axis: optExprToJson(n.axis),
7055
+ at: optExprToJson(n.at)
7056
+ };
7057
+ if (n.kind === "Loft") return {
7058
+ kind: "Loft",
7059
+ sections: n.sections.map(nodeToJson),
7060
+ ruled: n.ruled
7061
+ };
7062
+ if (n.kind === "Path") return {
7063
+ kind: "Path",
7064
+ start: exprToJson(n.start),
7065
+ segments: n.segments.map(segmentToJson)
7066
+ };
7067
+ if (n.kind === "Sweep") return {
7068
+ kind: "Sweep",
7069
+ profile: nodeToJson(n.profile),
7070
+ spine: nodeToJson(n.spine),
7071
+ frenet: n.frenet
7072
+ };
6545
7073
  if (n.kind === "Compound") return {
6546
7074
  kind: "Compound",
6547
7075
  children: n.children.map(nodeToJson)
@@ -6585,7 +7113,7 @@ function readVec2(v, where) {
6585
7113
  function fromJSON(envelope) {
6586
7114
  if (!isObj(envelope)) return bad("input is not an object");
6587
7115
  const v = envelope["csgVersion"];
6588
- if (v !== 1) return bad(`unsupported csgVersion ${String(v)} (expected 1)`);
7116
+ if (typeof v !== "number" || !Number.isInteger(v) || v < MIN_CSG_VERSION || v > 2) return bad(`unsupported csgVersion ${String(v)} (expected ${MIN_CSG_VERSION}..2)`);
6589
7117
  const root = envelope["root"];
6590
7118
  return readNode(root);
6591
7119
  }
@@ -6694,6 +7222,10 @@ function readNode(j) {
6694
7222
  case "Compound": return readCompound(j);
6695
7223
  case "Instance": return readInstance(j);
6696
7224
  case "Extrude": return readExtrude(j);
7225
+ case "Revolve": return readRevolve(j);
7226
+ case "Loft": return readLoft(j);
7227
+ case "Path": return readPath(j);
7228
+ case "Sweep": return readSweep(j);
6697
7229
  default: return bad(`unknown node kind: ${String(kind)}`);
6698
7230
  }
6699
7231
  }
@@ -6855,6 +7387,102 @@ function readExtrude(j) {
6855
7387
  if (!vector.ok) return vector;
6856
7388
  return require_errors.ok(extrude$1(profile.value, vector.value));
6857
7389
  }
7390
+ function readRevolve(j) {
7391
+ const profile = readNode(j["profile"]);
7392
+ if (!profile.ok) return profile;
7393
+ const angle = readExpr(j["angle"]);
7394
+ if (!angle.ok) return angle;
7395
+ const axis = readOptExpr(j, "axis");
7396
+ if (!axis.ok) return axis;
7397
+ const at = readOptExpr(j, "at");
7398
+ if (!at.ok) return at;
7399
+ return require_errors.ok(revolve$1(profile.value, angle.value, {
7400
+ axis: axis.value,
7401
+ at: at.value
7402
+ }));
7403
+ }
7404
+ function readLoft(j) {
7405
+ const sections = readNodeArray(j["sections"], "Loft.sections");
7406
+ if (!sections.ok) return sections;
7407
+ const ruled = j["ruled"];
7408
+ if (ruled !== void 0 && typeof ruled !== "boolean") return bad("Loft.ruled: not a boolean");
7409
+ return require_errors.ok(loft$1(sections.value, { ruled }));
7410
+ }
7411
+ function readFlag(j, key) {
7412
+ const v = j[key];
7413
+ if (v === void 0) return require_errors.ok(false);
7414
+ return typeof v === "boolean" ? require_errors.ok(v) : bad(`${key}: not a boolean`);
7415
+ }
7416
+ function readSegment(j) {
7417
+ if (!isObj(j)) return bad("segment: not an object");
7418
+ const to = readExpr(j["to"]);
7419
+ if (!to.ok) return to;
7420
+ const kind = j["kind"];
7421
+ switch (kind) {
7422
+ case "Line": return require_errors.ok(lineTo(to.value));
7423
+ case "Arc": {
7424
+ const radius = readExpr(j["radius"]);
7425
+ if (!radius.ok) return radius;
7426
+ const largeArc = readFlag(j, "largeArc");
7427
+ if (!largeArc.ok) return largeArc;
7428
+ const clockwise = readFlag(j, "clockwise");
7429
+ if (!clockwise.ok) return clockwise;
7430
+ return require_errors.ok(arcTo(to.value, radius.value, {
7431
+ largeArc: largeArc.value,
7432
+ clockwise: clockwise.value
7433
+ }));
7434
+ }
7435
+ case "Bezier": {
7436
+ const raw = j["controls"];
7437
+ if (!Array.isArray(raw)) return bad("Bezier.controls: not array");
7438
+ const controls = [];
7439
+ for (const c of raw) {
7440
+ const r = readExpr(c);
7441
+ if (!r.ok) return r;
7442
+ controls.push(r.value);
7443
+ }
7444
+ return require_errors.ok(bezierTo(controls, to.value));
7445
+ }
7446
+ case "EllipseArc": {
7447
+ const radii = readExpr(j["radii"]);
7448
+ if (!radii.ok) return radii;
7449
+ const rotation = readExpr(j["rotation"]);
7450
+ if (!rotation.ok) return rotation;
7451
+ const largeArc = readFlag(j, "largeArc");
7452
+ if (!largeArc.ok) return largeArc;
7453
+ const clockwise = readFlag(j, "clockwise");
7454
+ if (!clockwise.ok) return clockwise;
7455
+ return require_errors.ok(ellipseArcTo(to.value, radii.value, {
7456
+ rotation: rotation.value,
7457
+ largeArc: largeArc.value,
7458
+ clockwise: clockwise.value
7459
+ }));
7460
+ }
7461
+ default: return bad(`unknown segment kind: ${String(kind)}`);
7462
+ }
7463
+ }
7464
+ function readSweep(j) {
7465
+ const profile = readNode(j["profile"]);
7466
+ if (!profile.ok) return profile;
7467
+ const spine = readNode(j["spine"]);
7468
+ if (!spine.ok) return spine;
7469
+ const frenet = readFlag(j, "frenet");
7470
+ if (!frenet.ok) return frenet;
7471
+ return require_errors.ok(sweep$2(profile.value, spine.value, { frenet: frenet.value }));
7472
+ }
7473
+ function readPath(j) {
7474
+ const start = readExpr(j["start"]);
7475
+ if (!start.ok) return start;
7476
+ const raw = j["segments"];
7477
+ if (!Array.isArray(raw)) return bad("Path.segments: not array");
7478
+ const segments = [];
7479
+ for (const s of raw) {
7480
+ const r = readSegment(s);
7481
+ if (!r.ok) return r;
7482
+ segments.push(r.value);
7483
+ }
7484
+ return require_errors.ok(path(start.value, segments));
7485
+ }
6858
7486
  function readCompound(j) {
6859
7487
  const children = readNodeArray(j["children"], "Compound.children");
6860
7488
  return children.ok ? require_errors.ok(compound$1(children.value)) : children;
@@ -6966,7 +7594,23 @@ function optimizeNode(n) {
6966
7594
  });
6967
7595
  case "Compound": return compound$1(n.children.map(optimizeNode).filter((c) => c.kind !== "Empty"));
6968
7596
  case "Instance": return instance$1(optimizeNode(n.source), n.placements, n.fuse);
7597
+ case "Extrude":
7598
+ case "Revolve":
7599
+ case "Loft":
7600
+ case "Sweep":
7601
+ case "Path": return optimizeFeature(n);
7602
+ }
7603
+ }
7604
+ function optimizeFeature(n) {
7605
+ switch (n.kind) {
6969
7606
  case "Extrude": return extrude$1(optimizeNode(n.profile), foldExpr(n.vector));
7607
+ case "Revolve": return revolve$1(optimizeNode(n.profile), foldExpr(n.angle), {
7608
+ axis: n.axis ? foldExpr(n.axis) : void 0,
7609
+ at: n.at ? foldExpr(n.at) : void 0
7610
+ });
7611
+ case "Loft": return loft$1(n.sections.map(optimizeNode), { ruled: n.ruled });
7612
+ case "Sweep": return sweep$2(optimizeNode(n.profile), optimizeNode(n.spine), { frenet: n.frenet });
7613
+ case "Path": return path(foldExpr(n.start), n.segments.map((s) => foldSegment(s, foldExpr)));
6970
7614
  }
6971
7615
  }
6972
7616
  function optimizeFuse(a, b, tol) {
@@ -7039,7 +7683,8 @@ function rebuildChildren(n, pred, repl) {
7039
7683
  case "Circle":
7040
7684
  case "Line":
7041
7685
  case "Vertex":
7042
- case "Empty": return n;
7686
+ case "Empty":
7687
+ case "Path": return n;
7043
7688
  case "Fuse": return fuse$1(walk(n.a, pred, repl), walk(n.b, pred, repl), n.tolerance);
7044
7689
  case "Cut": return cut$1(walk(n.a, pred, repl), walk(n.b, pred, repl), n.tolerance);
7045
7690
  case "Intersect": return intersect$1(walk(n.a, pred, repl), walk(n.b, pred, repl), n.tolerance);
@@ -7058,6 +7703,12 @@ function rebuildChildren(n, pred, repl) {
7058
7703
  case "Compound": return compound$1(n.children.map((c) => walk(c, pred, repl)));
7059
7704
  case "Instance": return instance$1(walk(n.source, pred, repl), n.placements, n.fuse);
7060
7705
  case "Extrude": return extrude$1(walk(n.profile, pred, repl), n.vector);
7706
+ case "Revolve": return revolve$1(walk(n.profile, pred, repl), n.angle, {
7707
+ axis: n.axis,
7708
+ at: n.at
7709
+ });
7710
+ case "Loft": return loft$1(n.sections.map((s) => walk(s, pred, repl)), { ruled: n.ruled });
7711
+ case "Sweep": return sweep$2(walk(n.profile, pred, repl), walk(n.spine, pred, repl), { frenet: n.frenet });
7061
7712
  }
7062
7713
  }
7063
7714
  function forEachNode(root, fn) {
@@ -7075,7 +7726,8 @@ function childrenOf(n) {
7075
7726
  case "Circle":
7076
7727
  case "Line":
7077
7728
  case "Vertex":
7078
- case "Empty": return [];
7729
+ case "Empty":
7730
+ case "Path": return [];
7079
7731
  case "Fuse":
7080
7732
  case "Cut":
7081
7733
  case "Intersect": return [n.a, n.b];
@@ -7087,7 +7739,10 @@ function childrenOf(n) {
7087
7739
  case "Mirror": return [n.target];
7088
7740
  case "Compound": return n.children;
7089
7741
  case "Instance": return [n.source];
7090
- case "Extrude": return [n.profile];
7742
+ case "Extrude":
7743
+ case "Revolve": return [n.profile];
7744
+ case "Loft": return n.sections;
7745
+ case "Sweep": return [n.profile, n.spine];
7091
7746
  }
7092
7747
  }
7093
7748
  function nodeCount(root) {
@@ -7100,12 +7755,14 @@ function nodeCount(root) {
7100
7755
  //#endregion
7101
7756
  //#region src/ns/csg.ts
7102
7757
  var csg_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
7103
- CSG_VERSION: () => 1,
7758
+ CSG_VERSION: () => 2,
7104
7759
  Evaluator: () => Evaluator,
7105
7760
  add: () => add,
7761
+ arcTo: () => arcTo,
7106
7762
  asScalarExpr: () => asScalarExpr,
7107
7763
  asVec2Expr: () => asVec2Expr,
7108
7764
  asVec3Expr: () => asVec3Expr,
7765
+ bezierTo: () => bezierTo,
7109
7766
  binOp: () => binOp,
7110
7767
  box: () => box$2,
7111
7768
  buildVec: () => buildVec,
@@ -7116,6 +7773,7 @@ var csg_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
7116
7773
  cut: () => cut$1,
7117
7774
  cutAll: () => cutAll$1,
7118
7775
  cylinder: () => cylinder$2,
7776
+ ellipseArcTo: () => ellipseArcTo,
7119
7777
  emptyFace: () => emptyFace,
7120
7778
  emptySolid: () => emptySolid,
7121
7779
  emptyWire: () => emptyWire,
@@ -7128,6 +7786,8 @@ var csg_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
7128
7786
  instance: () => instance$1,
7129
7787
  intersect: () => intersect$1,
7130
7788
  line: () => line$1,
7789
+ lineTo: () => lineTo,
7790
+ loft: () => loft$1,
7131
7791
  mirror: () => mirror$1,
7132
7792
  mul: () => mul,
7133
7793
  nodeCount: () => nodeCount,
@@ -7135,11 +7795,14 @@ var csg_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
7135
7795
  optimize: () => optimize,
7136
7796
  outputKindOf: () => outputKindOf,
7137
7797
  param: () => param,
7798
+ path: () => path,
7138
7799
  polygon: () => polygon$1,
7139
7800
  replaceNode: () => replaceNode,
7801
+ revolve: () => revolve$1,
7140
7802
  rotate: () => rotate$1,
7141
7803
  scale: () => scale$1,
7142
7804
  sphere: () => sphere$2,
7805
+ sweep: () => sweep$2,
7143
7806
  toJSON: () => toJSON,
7144
7807
  torus: () => torus$2,
7145
7808
  translate: () => translate$1,