brepjs 18.154.0 → 18.155.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
@@ -3606,12 +3606,164 @@ function withEvaluator(options, fn) {
3606
3606
  _usingCtx$4.d();
3607
3607
  }
3608
3608
  }
3609
+ //#endregion
3610
+ //#region src/csg/edit.ts
3611
+ function replaceNode(root, pred, replacement) {
3612
+ return walk(root, pred, replacement, /* @__PURE__ */ new Map());
3613
+ }
3614
+ function walk(node, pred, repl, memo) {
3615
+ const cached = memo.get(node);
3616
+ if (cached !== void 0) return cached;
3617
+ const out = pred(node) ? repl : rebuildChildren(node, pred, repl, memo);
3618
+ memo.set(node, out);
3619
+ return out;
3620
+ }
3621
+ function rebuildChildren(n, pred, repl, memo) {
3622
+ switch (n.kind) {
3623
+ case "Box":
3624
+ case "Sphere":
3625
+ case "Cylinder":
3626
+ case "Cone":
3627
+ case "Torus":
3628
+ case "Polygon":
3629
+ case "Circle":
3630
+ case "Line":
3631
+ case "Vertex":
3632
+ case "Empty":
3633
+ case "Path":
3634
+ case "Profile": return n;
3635
+ case "Fuse": return fuse$1(walk(n.a, pred, repl, memo), walk(n.b, pred, repl, memo), n.tolerance);
3636
+ case "Cut": return cut$1(walk(n.a, pred, repl, memo), walk(n.b, pred, repl, memo), n.tolerance);
3637
+ case "Intersect": return intersect$1(walk(n.a, pred, repl, memo), walk(n.b, pred, repl, memo), n.tolerance);
3638
+ case "FuseAll": return fuseAll$1(n.shapes.map((c) => walk(c, pred, repl, memo)), n.tolerance);
3639
+ case "CutAll": return cutAll$1(walk(n.base, pred, repl, memo), n.tools.map((c) => walk(c, pred, repl, memo)), n.tolerance);
3640
+ case "Translate": return translate$1(walk(n.target, pred, repl, memo), n.vector);
3641
+ case "Rotate": return rotate$1(walk(n.target, pred, repl, memo), n.angle, {
3642
+ axis: n.axis,
3643
+ at: n.at
3644
+ });
3645
+ case "Scale": return scale$2(walk(n.target, pred, repl, memo), n.factor, { center: n.center });
3646
+ case "Mirror": return mirror$1(walk(n.target, pred, repl, memo), {
3647
+ normal: n.normal,
3648
+ at: n.at
3649
+ });
3650
+ case "Compound": return compound$1(n.children.map((c) => walk(c, pred, repl, memo)));
3651
+ case "Instance": return instance$1(walk(n.source, pred, repl, memo), n.placements, n.fuse);
3652
+ case "Extrude":
3653
+ case "Revolve":
3654
+ case "Loft":
3655
+ case "Sweep":
3656
+ case "Color":
3657
+ case "Fillet":
3658
+ case "Chamfer":
3659
+ case "Shell": return rebuildFeature(n, pred, repl, memo);
3660
+ }
3661
+ }
3662
+ function rebuildFeature(n, pred, repl, memo) {
3663
+ switch (n.kind) {
3664
+ case "Extrude": return extrude$1(walk(n.profile, pred, repl, memo), n.vector);
3665
+ case "Revolve": return revolve$1(walk(n.profile, pred, repl, memo), n.angle, {
3666
+ axis: n.axis,
3667
+ at: n.at
3668
+ });
3669
+ case "Loft": return loft$1(n.sections.map((s) => walk(s, pred, repl, memo)), { ruled: n.ruled });
3670
+ case "Sweep": return sweep$2(walk(n.profile, pred, repl, memo), walk(n.spine, pred, repl, memo), { frenet: n.frenet });
3671
+ case "Color": return color(walk(n.target, pred, repl, memo), [...n.color]);
3672
+ case "Fillet": return fillet$1(walk(n.target, pred, repl, memo), n.ref, n.radius);
3673
+ case "Chamfer": return chamfer$1(walk(n.target, pred, repl, memo), n.ref, n.distance);
3674
+ case "Shell": return shell$1(walk(n.target, pred, repl, memo), n.refs, n.thickness);
3675
+ }
3676
+ }
3677
+ /** Visits each DISTINCT node once (identity-deduplicated), so a shared
3678
+ * subtree is reported once and a self-referencing DAG walks in linear time. */
3679
+ function forEachNode(root, fn) {
3680
+ const seen = /* @__PURE__ */ new Set();
3681
+ const visit = (node) => {
3682
+ if (seen.has(node)) return;
3683
+ seen.add(node);
3684
+ fn(node);
3685
+ for (const child of childrenOf(node)) visit(child);
3686
+ };
3687
+ visit(root);
3688
+ }
3689
+ function childrenOf(n) {
3690
+ switch (n.kind) {
3691
+ case "Box":
3692
+ case "Sphere":
3693
+ case "Cylinder":
3694
+ case "Cone":
3695
+ case "Torus":
3696
+ case "Polygon":
3697
+ case "Circle":
3698
+ case "Line":
3699
+ case "Vertex":
3700
+ case "Empty":
3701
+ case "Path":
3702
+ case "Profile": return [];
3703
+ case "Fuse":
3704
+ case "Cut":
3705
+ case "Intersect": return [n.a, n.b];
3706
+ case "FuseAll": return n.shapes;
3707
+ case "CutAll": return [n.base, ...n.tools];
3708
+ case "Translate":
3709
+ case "Rotate":
3710
+ case "Scale":
3711
+ case "Mirror":
3712
+ case "Color":
3713
+ case "Fillet":
3714
+ case "Chamfer":
3715
+ case "Shell": return [n.target];
3716
+ case "Compound": return n.children;
3717
+ case "Instance": return [n.source];
3718
+ case "Extrude":
3719
+ case "Revolve": return [n.profile];
3720
+ case "Loft": return n.sections;
3721
+ case "Sweep": return [n.profile, n.spine];
3722
+ }
3723
+ }
3724
+ function nodeCount(root) {
3725
+ let n = 0;
3726
+ forEachNode(root, () => {
3727
+ n++;
3728
+ });
3729
+ return n;
3730
+ }
3609
3731
  var MIN_CSG_VERSION = 1;
3610
3732
  function toJSON(node) {
3611
- return {
3612
- csgVersion: 7,
3613
- root: nodeToJson(node)
3733
+ const ctx = {
3734
+ counts: countRefs(node),
3735
+ ids: /* @__PURE__ */ new Map(),
3736
+ defs: []
3614
3737
  };
3738
+ const root = emitNode(node, ctx);
3739
+ return ctx.defs.length > 0 ? {
3740
+ csgVersion: 8,
3741
+ defs: ctx.defs,
3742
+ root
3743
+ } : {
3744
+ csgVersion: 8,
3745
+ root
3746
+ };
3747
+ }
3748
+ function countRefs(root) {
3749
+ const counts = /* @__PURE__ */ new Map();
3750
+ const visit = (n) => {
3751
+ const seen = counts.get(n) ?? 0;
3752
+ counts.set(n, seen + 1);
3753
+ if (seen === 0) for (const child of childrenOf(n)) visit(child);
3754
+ };
3755
+ visit(root);
3756
+ return counts;
3757
+ }
3758
+ function emitNode(n, ctx) {
3759
+ if ((ctx.counts.get(n) ?? 0) < 2) return nodeToJson(n, ctx);
3760
+ const existing = ctx.ids.get(n);
3761
+ if (existing !== void 0) return { $ref: existing };
3762
+ const body = nodeToJson(n, ctx);
3763
+ const id = ctx.defs.length;
3764
+ ctx.defs.push(body);
3765
+ ctx.ids.set(n, id);
3766
+ return { $ref: id };
3615
3767
  }
3616
3768
  function exprToJson(e) {
3617
3769
  switch (e.kind) {
@@ -3710,25 +3862,25 @@ function primitiveToJson(n) {
3710
3862
  default: return;
3711
3863
  }
3712
3864
  }
3713
- function booleanToJson(n) {
3865
+ function booleanToJson(n, ctx) {
3714
3866
  switch (n.kind) {
3715
3867
  case "Fuse":
3716
3868
  case "Cut":
3717
3869
  case "Intersect": return {
3718
3870
  kind: n.kind,
3719
- a: nodeToJson(n.a),
3720
- b: nodeToJson(n.b),
3871
+ a: emitNode(n.a, ctx),
3872
+ b: emitNode(n.b, ctx),
3721
3873
  tolerance: n.tolerance
3722
3874
  };
3723
3875
  case "FuseAll": return {
3724
3876
  kind: "FuseAll",
3725
- shapes: n.shapes.map(nodeToJson),
3877
+ shapes: n.shapes.map((s) => emitNode(s, ctx)),
3726
3878
  tolerance: n.tolerance
3727
3879
  };
3728
3880
  case "CutAll": return {
3729
3881
  kind: "CutAll",
3730
- base: nodeToJson(n.base),
3731
- tools: n.tools.map(nodeToJson),
3882
+ base: emitNode(n.base, ctx),
3883
+ tools: n.tools.map((t) => emitNode(t, ctx)),
3732
3884
  tolerance: n.tolerance
3733
3885
  };
3734
3886
  default: return;
@@ -3795,103 +3947,108 @@ function contourToJson(c) {
3795
3947
  segments: c.segments.map(segmentToJson)
3796
3948
  };
3797
3949
  }
3798
- function transformToJson(n) {
3950
+ function transformToJson(n, ctx) {
3799
3951
  switch (n.kind) {
3800
3952
  case "Translate": return {
3801
3953
  kind: "Translate",
3802
- target: nodeToJson(n.target),
3954
+ target: emitNode(n.target, ctx),
3803
3955
  vector: exprToJson(n.vector)
3804
3956
  };
3805
3957
  case "Rotate": return {
3806
3958
  kind: "Rotate",
3807
- target: nodeToJson(n.target),
3959
+ target: emitNode(n.target, ctx),
3808
3960
  angle: exprToJson(n.angle),
3809
3961
  axis: optExprToJson(n.axis),
3810
3962
  at: optExprToJson(n.at)
3811
3963
  };
3812
3964
  case "Scale": return {
3813
3965
  kind: "Scale",
3814
- target: nodeToJson(n.target),
3966
+ target: emitNode(n.target, ctx),
3815
3967
  factor: exprToJson(n.factor),
3816
3968
  center: optExprToJson(n.center)
3817
3969
  };
3818
3970
  case "Mirror": return {
3819
3971
  kind: "Mirror",
3820
- target: nodeToJson(n.target),
3972
+ target: emitNode(n.target, ctx),
3821
3973
  normal: optExprToJson(n.normal),
3822
3974
  at: optExprToJson(n.at)
3823
3975
  };
3824
3976
  default: return;
3825
3977
  }
3826
3978
  }
3827
- function nodeToJson(n) {
3828
- if (n.kind === "Extrude") return {
3829
- kind: "Extrude",
3830
- profile: nodeToJson(n.profile),
3831
- vector: exprToJson(n.vector)
3832
- };
3833
- if (n.kind === "Revolve") return {
3834
- kind: "Revolve",
3835
- profile: nodeToJson(n.profile),
3836
- angle: exprToJson(n.angle),
3837
- axis: optExprToJson(n.axis),
3838
- at: optExprToJson(n.at)
3839
- };
3840
- if (n.kind === "Loft") return {
3841
- kind: "Loft",
3842
- sections: n.sections.map(nodeToJson),
3843
- ruled: n.ruled
3844
- };
3845
- if (n.kind === "Path") return {
3846
- kind: "Path",
3847
- start: exprToJson(n.start),
3848
- segments: n.segments.map(segmentToJson)
3849
- };
3850
- if (n.kind === "Sweep") return {
3851
- kind: "Sweep",
3852
- profile: nodeToJson(n.profile),
3853
- spine: nodeToJson(n.spine),
3854
- frenet: n.frenet
3855
- };
3856
- if (n.kind === "Profile") return {
3857
- kind: "Profile",
3858
- outline: contourToJson(n.outline),
3859
- holes: n.holes.map(contourToJson)
3860
- };
3979
+ function featureToJson(n, ctx) {
3980
+ switch (n.kind) {
3981
+ case "Extrude": return {
3982
+ kind: "Extrude",
3983
+ profile: emitNode(n.profile, ctx),
3984
+ vector: exprToJson(n.vector)
3985
+ };
3986
+ case "Revolve": return {
3987
+ kind: "Revolve",
3988
+ profile: emitNode(n.profile, ctx),
3989
+ angle: exprToJson(n.angle),
3990
+ axis: optExprToJson(n.axis),
3991
+ at: optExprToJson(n.at)
3992
+ };
3993
+ case "Loft": return {
3994
+ kind: "Loft",
3995
+ sections: n.sections.map((s) => emitNode(s, ctx)),
3996
+ ruled: n.ruled
3997
+ };
3998
+ case "Path": return {
3999
+ kind: "Path",
4000
+ start: exprToJson(n.start),
4001
+ segments: n.segments.map(segmentToJson)
4002
+ };
4003
+ case "Sweep": return {
4004
+ kind: "Sweep",
4005
+ profile: emitNode(n.profile, ctx),
4006
+ spine: emitNode(n.spine, ctx),
4007
+ frenet: n.frenet
4008
+ };
4009
+ case "Profile": return {
4010
+ kind: "Profile",
4011
+ outline: contourToJson(n.outline),
4012
+ holes: n.holes.map(contourToJson)
4013
+ };
4014
+ default: return;
4015
+ }
4016
+ }
4017
+ function nodeToJson(n, ctx) {
3861
4018
  if (n.kind === "Color") return {
3862
4019
  kind: "Color",
3863
- target: nodeToJson(n.target),
4020
+ target: emitNode(n.target, ctx),
3864
4021
  color: [...n.color]
3865
4022
  };
3866
4023
  if (n.kind === "Fillet") return {
3867
4024
  kind: "Fillet",
3868
- target: nodeToJson(n.target),
4025
+ target: emitNode(n.target, ctx),
3869
4026
  ref: edgeRefToJson(n.ref),
3870
4027
  radius: exprToJson(n.radius)
3871
4028
  };
3872
4029
  if (n.kind === "Chamfer") return {
3873
4030
  kind: "Chamfer",
3874
- target: nodeToJson(n.target),
4031
+ target: emitNode(n.target, ctx),
3875
4032
  ref: edgeRefToJson(n.ref),
3876
4033
  distance: exprToJson(n.distance)
3877
4034
  };
3878
4035
  if (n.kind === "Shell") return {
3879
4036
  kind: "Shell",
3880
- target: nodeToJson(n.target),
4037
+ target: emitNode(n.target, ctx),
3881
4038
  refs: n.refs.map(shapeRefToJson),
3882
4039
  thickness: exprToJson(n.thickness)
3883
4040
  };
3884
4041
  if (n.kind === "Compound") return {
3885
4042
  kind: "Compound",
3886
- children: n.children.map(nodeToJson)
4043
+ children: n.children.map((c) => emitNode(c, ctx))
3887
4044
  };
3888
4045
  if (n.kind === "Instance") return {
3889
4046
  kind: "Instance",
3890
- source: nodeToJson(n.source),
4047
+ source: emitNode(n.source, ctx),
3891
4048
  placements: n.placements,
3892
4049
  fuse: n.fuse
3893
4050
  };
3894
- return primitiveToJson(n) ?? booleanToJson(n) ?? transformToJson(n);
4051
+ return featureToJson(n, ctx) ?? primitiveToJson(n) ?? booleanToJson(n, ctx) ?? transformToJson(n, ctx);
3895
4052
  }
3896
4053
  function bad(msg) {
3897
4054
  return require_errors.err(require_errors.validationError(require_errors.BrepErrorCode.NULL_SHAPE_INPUT, `csg.fromJSON: ${msg}`));
@@ -3924,9 +4081,18 @@ function readVec2(v, where) {
3924
4081
  function fromJSON(envelope) {
3925
4082
  if (!isObj(envelope)) return bad("input is not an object");
3926
4083
  const v = envelope["csgVersion"];
3927
- if (typeof v !== "number" || !Number.isInteger(v) || v < MIN_CSG_VERSION || v > 7) return bad(`unsupported csgVersion ${String(v)} (expected ${MIN_CSG_VERSION}..7)`);
3928
- const root = envelope["root"];
3929
- return readNode(root);
4084
+ if (typeof v !== "number" || !Number.isInteger(v) || v < MIN_CSG_VERSION || v > 8) return bad(`unsupported csgVersion ${String(v)} (expected ${MIN_CSG_VERSION}..8)`);
4085
+ const rawDefs = envelope["defs"];
4086
+ const defs = [];
4087
+ if (rawDefs !== void 0) {
4088
+ if (!Array.isArray(rawDefs)) return bad("defs: not an array");
4089
+ for (const raw of rawDefs) {
4090
+ const r = readNode(raw, defs);
4091
+ if (!r.ok) return r;
4092
+ defs.push(r.value);
4093
+ }
4094
+ }
4095
+ return readNode(envelope["root"], defs);
3930
4096
  }
3931
4097
  function readExpr(j) {
3932
4098
  if (!isObj(j)) return bad("expression: not an object");
@@ -3992,11 +4158,11 @@ function readBuildVec(j) {
3992
4158
  }
3993
4159
  return require_errors.ok(buildVec(dim, out));
3994
4160
  }
3995
- function readNodeArray(j, where) {
4161
+ function readNodeArray(j, where, defs) {
3996
4162
  if (!Array.isArray(j)) return bad(`${where}: not array`);
3997
4163
  const out = [];
3998
4164
  for (const c of j) {
3999
- const r = readNode(c);
4165
+ const r = readNode(c, defs);
4000
4166
  if (!r.ok) return r;
4001
4167
  out.push(r.value);
4002
4168
  }
@@ -4007,8 +4173,15 @@ function readOptTolerance(j) {
4007
4173
  if (t === void 0 || t === null) return require_errors.ok(void 0);
4008
4174
  return isNumber$1(t) ? require_errors.ok(t) : bad("tolerance: not a finite number");
4009
4175
  }
4010
- function readNode(j) {
4176
+ function readNode(j, defs) {
4011
4177
  if (!isObj(j)) return bad("node: not an object");
4178
+ const ref = j["$ref"];
4179
+ if (ref !== void 0) {
4180
+ if (!isNumber$1(ref) || !Number.isInteger(ref) || ref < 0) return bad("$ref: expected a non-negative integer def index");
4181
+ const node = defs[ref];
4182
+ if (node === void 0) return bad(`$ref: ${ref} is out of range (forward refs are rejected)`);
4183
+ return require_errors.ok(node);
4184
+ }
4012
4185
  const kind = j["kind"];
4013
4186
  switch (kind) {
4014
4187
  case "Box":
@@ -4023,25 +4196,25 @@ function readNode(j) {
4023
4196
  case "Empty": return readPrimitive(kind, j);
4024
4197
  case "Fuse":
4025
4198
  case "Cut":
4026
- case "Intersect": return readBinaryBool(kind, j);
4199
+ case "Intersect": return readBinaryBool(kind, j, defs);
4027
4200
  case "FuseAll":
4028
- case "CutAll": return readNaryBool(kind, j);
4201
+ case "CutAll": return readNaryBool(kind, j, defs);
4029
4202
  case "Translate":
4030
4203
  case "Rotate":
4031
4204
  case "Scale":
4032
- case "Mirror": return readTransform(kind, j);
4033
- case "Compound": return readCompound(j);
4034
- case "Instance": return readInstance(j);
4035
- case "Extrude": return readExtrude(j);
4036
- case "Revolve": return readRevolve(j);
4037
- case "Loft": return readLoft(j);
4205
+ case "Mirror": return readTransform(kind, j, defs);
4206
+ case "Compound": return readCompound(j, defs);
4207
+ case "Instance": return readInstance(j, defs);
4208
+ case "Extrude": return readExtrude(j, defs);
4209
+ case "Revolve": return readRevolve(j, defs);
4210
+ case "Loft": return readLoft(j, defs);
4038
4211
  case "Path": return readPath(j);
4039
- case "Sweep": return readSweep(j);
4212
+ case "Sweep": return readSweep(j, defs);
4040
4213
  case "Profile": return readProfile(j);
4041
- case "Color": return readColor(j);
4042
- case "Fillet": return readFillet(j);
4043
- case "Chamfer": return readChamfer(j);
4044
- case "Shell": return readShell(j);
4214
+ case "Color": return readColor(j, defs);
4215
+ case "Fillet": return readFillet(j, defs);
4216
+ case "Chamfer": return readChamfer(j, defs);
4217
+ case "Shell": return readShell(j, defs);
4045
4218
  default: return bad(`unknown node kind: ${String(kind)}`);
4046
4219
  }
4047
4220
  }
@@ -4123,10 +4296,10 @@ function readEmpty(j) {
4123
4296
  default: return bad(`Empty.output: ${String(out)}`);
4124
4297
  }
4125
4298
  }
4126
- function readBinaryBool(kind, j) {
4127
- const a = readNode(j["a"]);
4299
+ function readBinaryBool(kind, j, defs) {
4300
+ const a = readNode(j["a"], defs);
4128
4301
  if (!a.ok) return a;
4129
- const b = readNode(j["b"]);
4302
+ const b = readNode(j["b"], defs);
4130
4303
  if (!b.ok) return b;
4131
4304
  const t = readOptTolerance(j);
4132
4305
  if (!t.ok) return t;
@@ -4136,20 +4309,20 @@ function readBinaryBool(kind, j) {
4136
4309
  case "Intersect": return require_errors.ok(intersect$1(a.value, b.value, t.value));
4137
4310
  }
4138
4311
  }
4139
- function readNaryBool(kind, j) {
4312
+ function readNaryBool(kind, j, defs) {
4140
4313
  const t = readOptTolerance(j);
4141
4314
  if (!t.ok) return t;
4142
4315
  if (kind === "FuseAll") {
4143
- const shapes = readNodeArray(j["shapes"], "FuseAll.shapes");
4316
+ const shapes = readNodeArray(j["shapes"], "FuseAll.shapes", defs);
4144
4317
  return shapes.ok ? require_errors.ok(fuseAll$1(shapes.value, t.value)) : shapes;
4145
4318
  }
4146
- const base = readNode(j["base"]);
4319
+ const base = readNode(j["base"], defs);
4147
4320
  if (!base.ok) return base;
4148
- const tools = readNodeArray(j["tools"], "CutAll.tools");
4321
+ const tools = readNodeArray(j["tools"], "CutAll.tools", defs);
4149
4322
  return tools.ok ? require_errors.ok(cutAll$1(base.value, tools.value, t.value)) : tools;
4150
4323
  }
4151
- function readTransform(kind, j) {
4152
- const tgt = readNode(j["target"]);
4324
+ function readTransform(kind, j, defs) {
4325
+ const tgt = readNode(j["target"], defs);
4153
4326
  if (!tgt.ok) return tgt;
4154
4327
  switch (kind) {
4155
4328
  case "Translate": return readTranslate(j, tgt.value);
@@ -4196,15 +4369,15 @@ function readMirror(j, target) {
4196
4369
  at: at.value
4197
4370
  }));
4198
4371
  }
4199
- function readExtrude(j) {
4200
- const profile = readNode(j["profile"]);
4372
+ function readExtrude(j, defs) {
4373
+ const profile = readNode(j["profile"], defs);
4201
4374
  if (!profile.ok) return profile;
4202
4375
  const vector = readExpr(j["vector"]);
4203
4376
  if (!vector.ok) return vector;
4204
4377
  return require_errors.ok(extrude$1(profile.value, vector.value));
4205
4378
  }
4206
- function readRevolve(j) {
4207
- const profile = readNode(j["profile"]);
4379
+ function readRevolve(j, defs) {
4380
+ const profile = readNode(j["profile"], defs);
4208
4381
  if (!profile.ok) return profile;
4209
4382
  const angle = readExpr(j["angle"]);
4210
4383
  if (!angle.ok) return angle;
@@ -4217,8 +4390,8 @@ function readRevolve(j) {
4217
4390
  at: at.value
4218
4391
  }));
4219
4392
  }
4220
- function readLoft(j) {
4221
- const sections = readNodeArray(j["sections"], "Loft.sections");
4393
+ function readLoft(j, defs) {
4394
+ const sections = readNodeArray(j["sections"], "Loft.sections", defs);
4222
4395
  if (!sections.ok) return sections;
4223
4396
  const ruled = j["ruled"];
4224
4397
  if (ruled !== void 0 && typeof ruled !== "boolean") return bad("Loft.ruled: not a boolean");
@@ -4366,8 +4539,8 @@ function readShapeRef(j, where) {
4366
4539
  }
4367
4540
  });
4368
4541
  }
4369
- function readShell(j) {
4370
- const target = readNode(j["target"]);
4542
+ function readShell(j, defs) {
4543
+ const target = readNode(j["target"], defs);
4371
4544
  if (!target.ok) return target;
4372
4545
  const thickness = readExpr(j["thickness"]);
4373
4546
  if (!thickness.ok) return thickness;
@@ -4381,8 +4554,8 @@ function readShell(j) {
4381
4554
  }
4382
4555
  return require_errors.ok(shell$1(target.value, refs, thickness.value));
4383
4556
  }
4384
- function readFillet(j) {
4385
- const target = readNode(j["target"]);
4557
+ function readFillet(j, defs) {
4558
+ const target = readNode(j["target"], defs);
4386
4559
  if (!target.ok) return target;
4387
4560
  const radius = readExpr(j["radius"]);
4388
4561
  if (!radius.ok) return radius;
@@ -4390,8 +4563,8 @@ function readFillet(j) {
4390
4563
  if (!ref.ok) return ref;
4391
4564
  return require_errors.ok(fillet$1(target.value, ref.value, radius.value));
4392
4565
  }
4393
- function readChamfer(j) {
4394
- const target = readNode(j["target"]);
4566
+ function readChamfer(j, defs) {
4567
+ const target = readNode(j["target"], defs);
4395
4568
  if (!target.ok) return target;
4396
4569
  const distance = readExpr(j["distance"]);
4397
4570
  if (!distance.ok) return distance;
@@ -4399,8 +4572,8 @@ function readChamfer(j) {
4399
4572
  if (!ref.ok) return ref;
4400
4573
  return require_errors.ok(chamfer$1(target.value, ref.value, distance.value));
4401
4574
  }
4402
- function readColor(j) {
4403
- const target = readNode(j["target"]);
4575
+ function readColor(j, defs) {
4576
+ const target = readNode(j["target"], defs);
4404
4577
  if (!target.ok) return target;
4405
4578
  const c = j["color"];
4406
4579
  if (!Array.isArray(c) || c.length !== 4 || !c.every((v) => isNumber$1(v) && v >= 0 && v <= 1)) return bad("Color.color: expected an RGBA array of 4 numbers in [0, 1]");
@@ -4424,10 +4597,10 @@ function readProfile(j) {
4424
4597
  }
4425
4598
  return require_errors.ok(profile(outline.value, holes));
4426
4599
  }
4427
- function readSweep(j) {
4428
- const profile = readNode(j["profile"]);
4600
+ function readSweep(j, defs) {
4601
+ const profile = readNode(j["profile"], defs);
4429
4602
  if (!profile.ok) return profile;
4430
- const spine = readNode(j["spine"]);
4603
+ const spine = readNode(j["spine"], defs);
4431
4604
  if (!spine.ok) return spine;
4432
4605
  const frenet = readFlag(j, "frenet");
4433
4606
  if (!frenet.ok) return frenet;
@@ -4446,15 +4619,15 @@ function readPath(j) {
4446
4619
  }
4447
4620
  return require_errors.ok(path(start.value, segments));
4448
4621
  }
4449
- function readCompound(j) {
4450
- const children = readNodeArray(j["children"], "Compound.children");
4622
+ function readCompound(j, defs) {
4623
+ const children = readNodeArray(j["children"], "Compound.children", defs);
4451
4624
  return children.ok ? require_errors.ok(compound$1(children.value)) : children;
4452
4625
  }
4453
4626
  function isMatrix4x4(v) {
4454
4627
  return Array.isArray(v) && v.length === 4 && v.every((row) => Array.isArray(row) && row.length === 4 && row.every(isNumber$1));
4455
4628
  }
4456
- function readInstance(j) {
4457
- const source = readNode(j["source"]);
4629
+ function readInstance(j, defs) {
4630
+ const source = readNode(j["source"], defs);
4458
4631
  if (!source.ok) return source;
4459
4632
  const placements = j["placements"];
4460
4633
  if (!Array.isArray(placements) || !placements.every(isMatrix4x4)) return bad("Instance.placements must be an array of 4x4 number matrices");
@@ -4652,128 +4825,6 @@ function optimizeTranslate(target, vector) {
4652
4825
  return translate$1(ot, ov);
4653
4826
  }
4654
4827
  //#endregion
4655
- //#region src/csg/edit.ts
4656
- function replaceNode(root, pred, replacement) {
4657
- return walk(root, pred, replacement, /* @__PURE__ */ new Map());
4658
- }
4659
- function walk(node, pred, repl, memo) {
4660
- const cached = memo.get(node);
4661
- if (cached !== void 0) return cached;
4662
- const out = pred(node) ? repl : rebuildChildren(node, pred, repl, memo);
4663
- memo.set(node, out);
4664
- return out;
4665
- }
4666
- function rebuildChildren(n, pred, repl, memo) {
4667
- switch (n.kind) {
4668
- case "Box":
4669
- case "Sphere":
4670
- case "Cylinder":
4671
- case "Cone":
4672
- case "Torus":
4673
- case "Polygon":
4674
- case "Circle":
4675
- case "Line":
4676
- case "Vertex":
4677
- case "Empty":
4678
- case "Path":
4679
- case "Profile": return n;
4680
- case "Fuse": return fuse$1(walk(n.a, pred, repl, memo), walk(n.b, pred, repl, memo), n.tolerance);
4681
- case "Cut": return cut$1(walk(n.a, pred, repl, memo), walk(n.b, pred, repl, memo), n.tolerance);
4682
- case "Intersect": return intersect$1(walk(n.a, pred, repl, memo), walk(n.b, pred, repl, memo), n.tolerance);
4683
- case "FuseAll": return fuseAll$1(n.shapes.map((c) => walk(c, pred, repl, memo)), n.tolerance);
4684
- case "CutAll": return cutAll$1(walk(n.base, pred, repl, memo), n.tools.map((c) => walk(c, pred, repl, memo)), n.tolerance);
4685
- case "Translate": return translate$1(walk(n.target, pred, repl, memo), n.vector);
4686
- case "Rotate": return rotate$1(walk(n.target, pred, repl, memo), n.angle, {
4687
- axis: n.axis,
4688
- at: n.at
4689
- });
4690
- case "Scale": return scale$2(walk(n.target, pred, repl, memo), n.factor, { center: n.center });
4691
- case "Mirror": return mirror$1(walk(n.target, pred, repl, memo), {
4692
- normal: n.normal,
4693
- at: n.at
4694
- });
4695
- case "Compound": return compound$1(n.children.map((c) => walk(c, pred, repl, memo)));
4696
- case "Instance": return instance$1(walk(n.source, pred, repl, memo), n.placements, n.fuse);
4697
- case "Extrude":
4698
- case "Revolve":
4699
- case "Loft":
4700
- case "Sweep":
4701
- case "Color":
4702
- case "Fillet":
4703
- case "Chamfer":
4704
- case "Shell": return rebuildFeature(n, pred, repl, memo);
4705
- }
4706
- }
4707
- function rebuildFeature(n, pred, repl, memo) {
4708
- switch (n.kind) {
4709
- case "Extrude": return extrude$1(walk(n.profile, pred, repl, memo), n.vector);
4710
- case "Revolve": return revolve$1(walk(n.profile, pred, repl, memo), n.angle, {
4711
- axis: n.axis,
4712
- at: n.at
4713
- });
4714
- case "Loft": return loft$1(n.sections.map((s) => walk(s, pred, repl, memo)), { ruled: n.ruled });
4715
- case "Sweep": return sweep$2(walk(n.profile, pred, repl, memo), walk(n.spine, pred, repl, memo), { frenet: n.frenet });
4716
- case "Color": return color(walk(n.target, pred, repl, memo), [...n.color]);
4717
- case "Fillet": return fillet$1(walk(n.target, pred, repl, memo), n.ref, n.radius);
4718
- case "Chamfer": return chamfer$1(walk(n.target, pred, repl, memo), n.ref, n.distance);
4719
- case "Shell": return shell$1(walk(n.target, pred, repl, memo), n.refs, n.thickness);
4720
- }
4721
- }
4722
- /** Visits each DISTINCT node once (identity-deduplicated), so a shared
4723
- * subtree is reported once and a self-referencing DAG walks in linear time. */
4724
- function forEachNode(root, fn) {
4725
- const seen = /* @__PURE__ */ new Set();
4726
- const visit = (node) => {
4727
- if (seen.has(node)) return;
4728
- seen.add(node);
4729
- fn(node);
4730
- for (const child of childrenOf(node)) visit(child);
4731
- };
4732
- visit(root);
4733
- }
4734
- function childrenOf(n) {
4735
- switch (n.kind) {
4736
- case "Box":
4737
- case "Sphere":
4738
- case "Cylinder":
4739
- case "Cone":
4740
- case "Torus":
4741
- case "Polygon":
4742
- case "Circle":
4743
- case "Line":
4744
- case "Vertex":
4745
- case "Empty":
4746
- case "Path":
4747
- case "Profile": return [];
4748
- case "Fuse":
4749
- case "Cut":
4750
- case "Intersect": return [n.a, n.b];
4751
- case "FuseAll": return n.shapes;
4752
- case "CutAll": return [n.base, ...n.tools];
4753
- case "Translate":
4754
- case "Rotate":
4755
- case "Scale":
4756
- case "Mirror":
4757
- case "Color":
4758
- case "Fillet":
4759
- case "Chamfer":
4760
- case "Shell": return [n.target];
4761
- case "Compound": return n.children;
4762
- case "Instance": return [n.source];
4763
- case "Extrude":
4764
- case "Revolve": return [n.profile];
4765
- case "Loft": return n.sections;
4766
- case "Sweep": return [n.profile, n.spine];
4767
- }
4768
- }
4769
- function nodeCount(root) {
4770
- let n = 0;
4771
- forEachNode(root, () => {
4772
- n++;
4773
- });
4774
- return n;
4775
- }
4776
- //#endregion
4777
4828
  //#region src/sketching/blueprintContourFns.ts
4778
4829
  var EPS$1 = 1e-6;
4779
4830
  function p2(p) {
@@ -8732,7 +8783,7 @@ var patterns_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
8732
8783
  //#endregion
8733
8784
  //#region src/ns/csg.ts
8734
8785
  var csg_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
8735
- CSG_VERSION: () => 7,
8786
+ CSG_VERSION: () => 8,
8736
8787
  Evaluator: () => Evaluator,
8737
8788
  add: () => add$1,
8738
8789
  arbitraryClosedProfile: () => arbitraryClosedProfile,