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.js CHANGED
@@ -3616,12 +3616,164 @@ function withEvaluator(options, fn) {
3616
3616
  _usingCtx$4.d();
3617
3617
  }
3618
3618
  }
3619
+ //#endregion
3620
+ //#region src/csg/edit.ts
3621
+ function replaceNode(root, pred, replacement) {
3622
+ return walk(root, pred, replacement, /* @__PURE__ */ new Map());
3623
+ }
3624
+ function walk(node, pred, repl, memo) {
3625
+ const cached = memo.get(node);
3626
+ if (cached !== void 0) return cached;
3627
+ const out = pred(node) ? repl : rebuildChildren(node, pred, repl, memo);
3628
+ memo.set(node, out);
3629
+ return out;
3630
+ }
3631
+ function rebuildChildren(n, pred, repl, memo) {
3632
+ switch (n.kind) {
3633
+ case "Box":
3634
+ case "Sphere":
3635
+ case "Cylinder":
3636
+ case "Cone":
3637
+ case "Torus":
3638
+ case "Polygon":
3639
+ case "Circle":
3640
+ case "Line":
3641
+ case "Vertex":
3642
+ case "Empty":
3643
+ case "Path":
3644
+ case "Profile": return n;
3645
+ case "Fuse": return fuse$1(walk(n.a, pred, repl, memo), walk(n.b, pred, repl, memo), n.tolerance);
3646
+ case "Cut": return cut$1(walk(n.a, pred, repl, memo), walk(n.b, pred, repl, memo), n.tolerance);
3647
+ case "Intersect": return intersect$1(walk(n.a, pred, repl, memo), walk(n.b, pred, repl, memo), n.tolerance);
3648
+ case "FuseAll": return fuseAll$1(n.shapes.map((c) => walk(c, pred, repl, memo)), n.tolerance);
3649
+ case "CutAll": return cutAll$1(walk(n.base, pred, repl, memo), n.tools.map((c) => walk(c, pred, repl, memo)), n.tolerance);
3650
+ case "Translate": return translate$1(walk(n.target, pred, repl, memo), n.vector);
3651
+ case "Rotate": return rotate$1(walk(n.target, pred, repl, memo), n.angle, {
3652
+ axis: n.axis,
3653
+ at: n.at
3654
+ });
3655
+ case "Scale": return scale$2(walk(n.target, pred, repl, memo), n.factor, { center: n.center });
3656
+ case "Mirror": return mirror$1(walk(n.target, pred, repl, memo), {
3657
+ normal: n.normal,
3658
+ at: n.at
3659
+ });
3660
+ case "Compound": return compound$1(n.children.map((c) => walk(c, pred, repl, memo)));
3661
+ case "Instance": return instance$1(walk(n.source, pred, repl, memo), n.placements, n.fuse);
3662
+ case "Extrude":
3663
+ case "Revolve":
3664
+ case "Loft":
3665
+ case "Sweep":
3666
+ case "Color":
3667
+ case "Fillet":
3668
+ case "Chamfer":
3669
+ case "Shell": return rebuildFeature(n, pred, repl, memo);
3670
+ }
3671
+ }
3672
+ function rebuildFeature(n, pred, repl, memo) {
3673
+ switch (n.kind) {
3674
+ case "Extrude": return extrude$1(walk(n.profile, pred, repl, memo), n.vector);
3675
+ case "Revolve": return revolve$1(walk(n.profile, pred, repl, memo), n.angle, {
3676
+ axis: n.axis,
3677
+ at: n.at
3678
+ });
3679
+ case "Loft": return loft$1(n.sections.map((s) => walk(s, pred, repl, memo)), { ruled: n.ruled });
3680
+ case "Sweep": return sweep$2(walk(n.profile, pred, repl, memo), walk(n.spine, pred, repl, memo), { frenet: n.frenet });
3681
+ case "Color": return color(walk(n.target, pred, repl, memo), [...n.color]);
3682
+ case "Fillet": return fillet$1(walk(n.target, pred, repl, memo), n.ref, n.radius);
3683
+ case "Chamfer": return chamfer$1(walk(n.target, pred, repl, memo), n.ref, n.distance);
3684
+ case "Shell": return shell$1(walk(n.target, pred, repl, memo), n.refs, n.thickness);
3685
+ }
3686
+ }
3687
+ /** Visits each DISTINCT node once (identity-deduplicated), so a shared
3688
+ * subtree is reported once and a self-referencing DAG walks in linear time. */
3689
+ function forEachNode(root, fn) {
3690
+ const seen = /* @__PURE__ */ new Set();
3691
+ const visit = (node) => {
3692
+ if (seen.has(node)) return;
3693
+ seen.add(node);
3694
+ fn(node);
3695
+ for (const child of childrenOf(node)) visit(child);
3696
+ };
3697
+ visit(root);
3698
+ }
3699
+ function childrenOf(n) {
3700
+ switch (n.kind) {
3701
+ case "Box":
3702
+ case "Sphere":
3703
+ case "Cylinder":
3704
+ case "Cone":
3705
+ case "Torus":
3706
+ case "Polygon":
3707
+ case "Circle":
3708
+ case "Line":
3709
+ case "Vertex":
3710
+ case "Empty":
3711
+ case "Path":
3712
+ case "Profile": return [];
3713
+ case "Fuse":
3714
+ case "Cut":
3715
+ case "Intersect": return [n.a, n.b];
3716
+ case "FuseAll": return n.shapes;
3717
+ case "CutAll": return [n.base, ...n.tools];
3718
+ case "Translate":
3719
+ case "Rotate":
3720
+ case "Scale":
3721
+ case "Mirror":
3722
+ case "Color":
3723
+ case "Fillet":
3724
+ case "Chamfer":
3725
+ case "Shell": return [n.target];
3726
+ case "Compound": return n.children;
3727
+ case "Instance": return [n.source];
3728
+ case "Extrude":
3729
+ case "Revolve": return [n.profile];
3730
+ case "Loft": return n.sections;
3731
+ case "Sweep": return [n.profile, n.spine];
3732
+ }
3733
+ }
3734
+ function nodeCount(root) {
3735
+ let n = 0;
3736
+ forEachNode(root, () => {
3737
+ n++;
3738
+ });
3739
+ return n;
3740
+ }
3619
3741
  var MIN_CSG_VERSION = 1;
3620
3742
  function toJSON(node) {
3621
- return {
3622
- csgVersion: 7,
3623
- root: nodeToJson(node)
3743
+ const ctx = {
3744
+ counts: countRefs(node),
3745
+ ids: /* @__PURE__ */ new Map(),
3746
+ defs: []
3624
3747
  };
3748
+ const root = emitNode(node, ctx);
3749
+ return ctx.defs.length > 0 ? {
3750
+ csgVersion: 8,
3751
+ defs: ctx.defs,
3752
+ root
3753
+ } : {
3754
+ csgVersion: 8,
3755
+ root
3756
+ };
3757
+ }
3758
+ function countRefs(root) {
3759
+ const counts = /* @__PURE__ */ new Map();
3760
+ const visit = (n) => {
3761
+ const seen = counts.get(n) ?? 0;
3762
+ counts.set(n, seen + 1);
3763
+ if (seen === 0) for (const child of childrenOf(n)) visit(child);
3764
+ };
3765
+ visit(root);
3766
+ return counts;
3767
+ }
3768
+ function emitNode(n, ctx) {
3769
+ if ((ctx.counts.get(n) ?? 0) < 2) return nodeToJson(n, ctx);
3770
+ const existing = ctx.ids.get(n);
3771
+ if (existing !== void 0) return { $ref: existing };
3772
+ const body = nodeToJson(n, ctx);
3773
+ const id = ctx.defs.length;
3774
+ ctx.defs.push(body);
3775
+ ctx.ids.set(n, id);
3776
+ return { $ref: id };
3625
3777
  }
3626
3778
  function exprToJson(e) {
3627
3779
  switch (e.kind) {
@@ -3720,25 +3872,25 @@ function primitiveToJson(n) {
3720
3872
  default: return;
3721
3873
  }
3722
3874
  }
3723
- function booleanToJson(n) {
3875
+ function booleanToJson(n, ctx) {
3724
3876
  switch (n.kind) {
3725
3877
  case "Fuse":
3726
3878
  case "Cut":
3727
3879
  case "Intersect": return {
3728
3880
  kind: n.kind,
3729
- a: nodeToJson(n.a),
3730
- b: nodeToJson(n.b),
3881
+ a: emitNode(n.a, ctx),
3882
+ b: emitNode(n.b, ctx),
3731
3883
  tolerance: n.tolerance
3732
3884
  };
3733
3885
  case "FuseAll": return {
3734
3886
  kind: "FuseAll",
3735
- shapes: n.shapes.map(nodeToJson),
3887
+ shapes: n.shapes.map((s) => emitNode(s, ctx)),
3736
3888
  tolerance: n.tolerance
3737
3889
  };
3738
3890
  case "CutAll": return {
3739
3891
  kind: "CutAll",
3740
- base: nodeToJson(n.base),
3741
- tools: n.tools.map(nodeToJson),
3892
+ base: emitNode(n.base, ctx),
3893
+ tools: n.tools.map((t) => emitNode(t, ctx)),
3742
3894
  tolerance: n.tolerance
3743
3895
  };
3744
3896
  default: return;
@@ -3805,103 +3957,108 @@ function contourToJson(c) {
3805
3957
  segments: c.segments.map(segmentToJson)
3806
3958
  };
3807
3959
  }
3808
- function transformToJson(n) {
3960
+ function transformToJson(n, ctx) {
3809
3961
  switch (n.kind) {
3810
3962
  case "Translate": return {
3811
3963
  kind: "Translate",
3812
- target: nodeToJson(n.target),
3964
+ target: emitNode(n.target, ctx),
3813
3965
  vector: exprToJson(n.vector)
3814
3966
  };
3815
3967
  case "Rotate": return {
3816
3968
  kind: "Rotate",
3817
- target: nodeToJson(n.target),
3969
+ target: emitNode(n.target, ctx),
3818
3970
  angle: exprToJson(n.angle),
3819
3971
  axis: optExprToJson(n.axis),
3820
3972
  at: optExprToJson(n.at)
3821
3973
  };
3822
3974
  case "Scale": return {
3823
3975
  kind: "Scale",
3824
- target: nodeToJson(n.target),
3976
+ target: emitNode(n.target, ctx),
3825
3977
  factor: exprToJson(n.factor),
3826
3978
  center: optExprToJson(n.center)
3827
3979
  };
3828
3980
  case "Mirror": return {
3829
3981
  kind: "Mirror",
3830
- target: nodeToJson(n.target),
3982
+ target: emitNode(n.target, ctx),
3831
3983
  normal: optExprToJson(n.normal),
3832
3984
  at: optExprToJson(n.at)
3833
3985
  };
3834
3986
  default: return;
3835
3987
  }
3836
3988
  }
3837
- function nodeToJson(n) {
3838
- if (n.kind === "Extrude") return {
3839
- kind: "Extrude",
3840
- profile: nodeToJson(n.profile),
3841
- vector: exprToJson(n.vector)
3842
- };
3843
- if (n.kind === "Revolve") return {
3844
- kind: "Revolve",
3845
- profile: nodeToJson(n.profile),
3846
- angle: exprToJson(n.angle),
3847
- axis: optExprToJson(n.axis),
3848
- at: optExprToJson(n.at)
3849
- };
3850
- if (n.kind === "Loft") return {
3851
- kind: "Loft",
3852
- sections: n.sections.map(nodeToJson),
3853
- ruled: n.ruled
3854
- };
3855
- if (n.kind === "Path") return {
3856
- kind: "Path",
3857
- start: exprToJson(n.start),
3858
- segments: n.segments.map(segmentToJson)
3859
- };
3860
- if (n.kind === "Sweep") return {
3861
- kind: "Sweep",
3862
- profile: nodeToJson(n.profile),
3863
- spine: nodeToJson(n.spine),
3864
- frenet: n.frenet
3865
- };
3866
- if (n.kind === "Profile") return {
3867
- kind: "Profile",
3868
- outline: contourToJson(n.outline),
3869
- holes: n.holes.map(contourToJson)
3870
- };
3989
+ function featureToJson(n, ctx) {
3990
+ switch (n.kind) {
3991
+ case "Extrude": return {
3992
+ kind: "Extrude",
3993
+ profile: emitNode(n.profile, ctx),
3994
+ vector: exprToJson(n.vector)
3995
+ };
3996
+ case "Revolve": return {
3997
+ kind: "Revolve",
3998
+ profile: emitNode(n.profile, ctx),
3999
+ angle: exprToJson(n.angle),
4000
+ axis: optExprToJson(n.axis),
4001
+ at: optExprToJson(n.at)
4002
+ };
4003
+ case "Loft": return {
4004
+ kind: "Loft",
4005
+ sections: n.sections.map((s) => emitNode(s, ctx)),
4006
+ ruled: n.ruled
4007
+ };
4008
+ case "Path": return {
4009
+ kind: "Path",
4010
+ start: exprToJson(n.start),
4011
+ segments: n.segments.map(segmentToJson)
4012
+ };
4013
+ case "Sweep": return {
4014
+ kind: "Sweep",
4015
+ profile: emitNode(n.profile, ctx),
4016
+ spine: emitNode(n.spine, ctx),
4017
+ frenet: n.frenet
4018
+ };
4019
+ case "Profile": return {
4020
+ kind: "Profile",
4021
+ outline: contourToJson(n.outline),
4022
+ holes: n.holes.map(contourToJson)
4023
+ };
4024
+ default: return;
4025
+ }
4026
+ }
4027
+ function nodeToJson(n, ctx) {
3871
4028
  if (n.kind === "Color") return {
3872
4029
  kind: "Color",
3873
- target: nodeToJson(n.target),
4030
+ target: emitNode(n.target, ctx),
3874
4031
  color: [...n.color]
3875
4032
  };
3876
4033
  if (n.kind === "Fillet") return {
3877
4034
  kind: "Fillet",
3878
- target: nodeToJson(n.target),
4035
+ target: emitNode(n.target, ctx),
3879
4036
  ref: edgeRefToJson(n.ref),
3880
4037
  radius: exprToJson(n.radius)
3881
4038
  };
3882
4039
  if (n.kind === "Chamfer") return {
3883
4040
  kind: "Chamfer",
3884
- target: nodeToJson(n.target),
4041
+ target: emitNode(n.target, ctx),
3885
4042
  ref: edgeRefToJson(n.ref),
3886
4043
  distance: exprToJson(n.distance)
3887
4044
  };
3888
4045
  if (n.kind === "Shell") return {
3889
4046
  kind: "Shell",
3890
- target: nodeToJson(n.target),
4047
+ target: emitNode(n.target, ctx),
3891
4048
  refs: n.refs.map(shapeRefToJson),
3892
4049
  thickness: exprToJson(n.thickness)
3893
4050
  };
3894
4051
  if (n.kind === "Compound") return {
3895
4052
  kind: "Compound",
3896
- children: n.children.map(nodeToJson)
4053
+ children: n.children.map((c) => emitNode(c, ctx))
3897
4054
  };
3898
4055
  if (n.kind === "Instance") return {
3899
4056
  kind: "Instance",
3900
- source: nodeToJson(n.source),
4057
+ source: emitNode(n.source, ctx),
3901
4058
  placements: n.placements,
3902
4059
  fuse: n.fuse
3903
4060
  };
3904
- return primitiveToJson(n) ?? booleanToJson(n) ?? transformToJson(n);
4061
+ return featureToJson(n, ctx) ?? primitiveToJson(n) ?? booleanToJson(n, ctx) ?? transformToJson(n, ctx);
3905
4062
  }
3906
4063
  function bad(msg) {
3907
4064
  return err(validationError(BrepErrorCode.NULL_SHAPE_INPUT, `csg.fromJSON: ${msg}`));
@@ -3934,9 +4091,18 @@ function readVec2(v, where) {
3934
4091
  function fromJSON(envelope) {
3935
4092
  if (!isObj(envelope)) return bad("input is not an object");
3936
4093
  const v = envelope["csgVersion"];
3937
- if (typeof v !== "number" || !Number.isInteger(v) || v < MIN_CSG_VERSION || v > 7) return bad(`unsupported csgVersion ${String(v)} (expected ${MIN_CSG_VERSION}..7)`);
3938
- const root = envelope["root"];
3939
- return readNode(root);
4094
+ if (typeof v !== "number" || !Number.isInteger(v) || v < MIN_CSG_VERSION || v > 8) return bad(`unsupported csgVersion ${String(v)} (expected ${MIN_CSG_VERSION}..8)`);
4095
+ const rawDefs = envelope["defs"];
4096
+ const defs = [];
4097
+ if (rawDefs !== void 0) {
4098
+ if (!Array.isArray(rawDefs)) return bad("defs: not an array");
4099
+ for (const raw of rawDefs) {
4100
+ const r = readNode(raw, defs);
4101
+ if (!r.ok) return r;
4102
+ defs.push(r.value);
4103
+ }
4104
+ }
4105
+ return readNode(envelope["root"], defs);
3940
4106
  }
3941
4107
  function readExpr(j) {
3942
4108
  if (!isObj(j)) return bad("expression: not an object");
@@ -4002,11 +4168,11 @@ function readBuildVec(j) {
4002
4168
  }
4003
4169
  return ok(buildVec(dim, out));
4004
4170
  }
4005
- function readNodeArray(j, where) {
4171
+ function readNodeArray(j, where, defs) {
4006
4172
  if (!Array.isArray(j)) return bad(`${where}: not array`);
4007
4173
  const out = [];
4008
4174
  for (const c of j) {
4009
- const r = readNode(c);
4175
+ const r = readNode(c, defs);
4010
4176
  if (!r.ok) return r;
4011
4177
  out.push(r.value);
4012
4178
  }
@@ -4017,8 +4183,15 @@ function readOptTolerance(j) {
4017
4183
  if (t === void 0 || t === null) return ok(void 0);
4018
4184
  return isNumber$1(t) ? ok(t) : bad("tolerance: not a finite number");
4019
4185
  }
4020
- function readNode(j) {
4186
+ function readNode(j, defs) {
4021
4187
  if (!isObj(j)) return bad("node: not an object");
4188
+ const ref = j["$ref"];
4189
+ if (ref !== void 0) {
4190
+ if (!isNumber$1(ref) || !Number.isInteger(ref) || ref < 0) return bad("$ref: expected a non-negative integer def index");
4191
+ const node = defs[ref];
4192
+ if (node === void 0) return bad(`$ref: ${ref} is out of range (forward refs are rejected)`);
4193
+ return ok(node);
4194
+ }
4022
4195
  const kind = j["kind"];
4023
4196
  switch (kind) {
4024
4197
  case "Box":
@@ -4033,25 +4206,25 @@ function readNode(j) {
4033
4206
  case "Empty": return readPrimitive(kind, j);
4034
4207
  case "Fuse":
4035
4208
  case "Cut":
4036
- case "Intersect": return readBinaryBool(kind, j);
4209
+ case "Intersect": return readBinaryBool(kind, j, defs);
4037
4210
  case "FuseAll":
4038
- case "CutAll": return readNaryBool(kind, j);
4211
+ case "CutAll": return readNaryBool(kind, j, defs);
4039
4212
  case "Translate":
4040
4213
  case "Rotate":
4041
4214
  case "Scale":
4042
- case "Mirror": return readTransform(kind, j);
4043
- case "Compound": return readCompound(j);
4044
- case "Instance": return readInstance(j);
4045
- case "Extrude": return readExtrude(j);
4046
- case "Revolve": return readRevolve(j);
4047
- case "Loft": return readLoft(j);
4215
+ case "Mirror": return readTransform(kind, j, defs);
4216
+ case "Compound": return readCompound(j, defs);
4217
+ case "Instance": return readInstance(j, defs);
4218
+ case "Extrude": return readExtrude(j, defs);
4219
+ case "Revolve": return readRevolve(j, defs);
4220
+ case "Loft": return readLoft(j, defs);
4048
4221
  case "Path": return readPath(j);
4049
- case "Sweep": return readSweep(j);
4222
+ case "Sweep": return readSweep(j, defs);
4050
4223
  case "Profile": return readProfile(j);
4051
- case "Color": return readColor(j);
4052
- case "Fillet": return readFillet(j);
4053
- case "Chamfer": return readChamfer(j);
4054
- case "Shell": return readShell(j);
4224
+ case "Color": return readColor(j, defs);
4225
+ case "Fillet": return readFillet(j, defs);
4226
+ case "Chamfer": return readChamfer(j, defs);
4227
+ case "Shell": return readShell(j, defs);
4055
4228
  default: return bad(`unknown node kind: ${String(kind)}`);
4056
4229
  }
4057
4230
  }
@@ -4133,10 +4306,10 @@ function readEmpty(j) {
4133
4306
  default: return bad(`Empty.output: ${String(out)}`);
4134
4307
  }
4135
4308
  }
4136
- function readBinaryBool(kind, j) {
4137
- const a = readNode(j["a"]);
4309
+ function readBinaryBool(kind, j, defs) {
4310
+ const a = readNode(j["a"], defs);
4138
4311
  if (!a.ok) return a;
4139
- const b = readNode(j["b"]);
4312
+ const b = readNode(j["b"], defs);
4140
4313
  if (!b.ok) return b;
4141
4314
  const t = readOptTolerance(j);
4142
4315
  if (!t.ok) return t;
@@ -4146,20 +4319,20 @@ function readBinaryBool(kind, j) {
4146
4319
  case "Intersect": return ok(intersect$1(a.value, b.value, t.value));
4147
4320
  }
4148
4321
  }
4149
- function readNaryBool(kind, j) {
4322
+ function readNaryBool(kind, j, defs) {
4150
4323
  const t = readOptTolerance(j);
4151
4324
  if (!t.ok) return t;
4152
4325
  if (kind === "FuseAll") {
4153
- const shapes = readNodeArray(j["shapes"], "FuseAll.shapes");
4326
+ const shapes = readNodeArray(j["shapes"], "FuseAll.shapes", defs);
4154
4327
  return shapes.ok ? ok(fuseAll$1(shapes.value, t.value)) : shapes;
4155
4328
  }
4156
- const base = readNode(j["base"]);
4329
+ const base = readNode(j["base"], defs);
4157
4330
  if (!base.ok) return base;
4158
- const tools = readNodeArray(j["tools"], "CutAll.tools");
4331
+ const tools = readNodeArray(j["tools"], "CutAll.tools", defs);
4159
4332
  return tools.ok ? ok(cutAll$1(base.value, tools.value, t.value)) : tools;
4160
4333
  }
4161
- function readTransform(kind, j) {
4162
- const tgt = readNode(j["target"]);
4334
+ function readTransform(kind, j, defs) {
4335
+ const tgt = readNode(j["target"], defs);
4163
4336
  if (!tgt.ok) return tgt;
4164
4337
  switch (kind) {
4165
4338
  case "Translate": return readTranslate(j, tgt.value);
@@ -4206,15 +4379,15 @@ function readMirror(j, target) {
4206
4379
  at: at.value
4207
4380
  }));
4208
4381
  }
4209
- function readExtrude(j) {
4210
- const profile = readNode(j["profile"]);
4382
+ function readExtrude(j, defs) {
4383
+ const profile = readNode(j["profile"], defs);
4211
4384
  if (!profile.ok) return profile;
4212
4385
  const vector = readExpr(j["vector"]);
4213
4386
  if (!vector.ok) return vector;
4214
4387
  return ok(extrude$1(profile.value, vector.value));
4215
4388
  }
4216
- function readRevolve(j) {
4217
- const profile = readNode(j["profile"]);
4389
+ function readRevolve(j, defs) {
4390
+ const profile = readNode(j["profile"], defs);
4218
4391
  if (!profile.ok) return profile;
4219
4392
  const angle = readExpr(j["angle"]);
4220
4393
  if (!angle.ok) return angle;
@@ -4227,8 +4400,8 @@ function readRevolve(j) {
4227
4400
  at: at.value
4228
4401
  }));
4229
4402
  }
4230
- function readLoft(j) {
4231
- const sections = readNodeArray(j["sections"], "Loft.sections");
4403
+ function readLoft(j, defs) {
4404
+ const sections = readNodeArray(j["sections"], "Loft.sections", defs);
4232
4405
  if (!sections.ok) return sections;
4233
4406
  const ruled = j["ruled"];
4234
4407
  if (ruled !== void 0 && typeof ruled !== "boolean") return bad("Loft.ruled: not a boolean");
@@ -4376,8 +4549,8 @@ function readShapeRef(j, where) {
4376
4549
  }
4377
4550
  });
4378
4551
  }
4379
- function readShell(j) {
4380
- const target = readNode(j["target"]);
4552
+ function readShell(j, defs) {
4553
+ const target = readNode(j["target"], defs);
4381
4554
  if (!target.ok) return target;
4382
4555
  const thickness = readExpr(j["thickness"]);
4383
4556
  if (!thickness.ok) return thickness;
@@ -4391,8 +4564,8 @@ function readShell(j) {
4391
4564
  }
4392
4565
  return ok(shell$1(target.value, refs, thickness.value));
4393
4566
  }
4394
- function readFillet(j) {
4395
- const target = readNode(j["target"]);
4567
+ function readFillet(j, defs) {
4568
+ const target = readNode(j["target"], defs);
4396
4569
  if (!target.ok) return target;
4397
4570
  const radius = readExpr(j["radius"]);
4398
4571
  if (!radius.ok) return radius;
@@ -4400,8 +4573,8 @@ function readFillet(j) {
4400
4573
  if (!ref.ok) return ref;
4401
4574
  return ok(fillet$1(target.value, ref.value, radius.value));
4402
4575
  }
4403
- function readChamfer(j) {
4404
- const target = readNode(j["target"]);
4576
+ function readChamfer(j, defs) {
4577
+ const target = readNode(j["target"], defs);
4405
4578
  if (!target.ok) return target;
4406
4579
  const distance = readExpr(j["distance"]);
4407
4580
  if (!distance.ok) return distance;
@@ -4409,8 +4582,8 @@ function readChamfer(j) {
4409
4582
  if (!ref.ok) return ref;
4410
4583
  return ok(chamfer$1(target.value, ref.value, distance.value));
4411
4584
  }
4412
- function readColor(j) {
4413
- const target = readNode(j["target"]);
4585
+ function readColor(j, defs) {
4586
+ const target = readNode(j["target"], defs);
4414
4587
  if (!target.ok) return target;
4415
4588
  const c = j["color"];
4416
4589
  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]");
@@ -4434,10 +4607,10 @@ function readProfile(j) {
4434
4607
  }
4435
4608
  return ok(profile(outline.value, holes));
4436
4609
  }
4437
- function readSweep(j) {
4438
- const profile = readNode(j["profile"]);
4610
+ function readSweep(j, defs) {
4611
+ const profile = readNode(j["profile"], defs);
4439
4612
  if (!profile.ok) return profile;
4440
- const spine = readNode(j["spine"]);
4613
+ const spine = readNode(j["spine"], defs);
4441
4614
  if (!spine.ok) return spine;
4442
4615
  const frenet = readFlag(j, "frenet");
4443
4616
  if (!frenet.ok) return frenet;
@@ -4456,15 +4629,15 @@ function readPath(j) {
4456
4629
  }
4457
4630
  return ok(path(start.value, segments));
4458
4631
  }
4459
- function readCompound(j) {
4460
- const children = readNodeArray(j["children"], "Compound.children");
4632
+ function readCompound(j, defs) {
4633
+ const children = readNodeArray(j["children"], "Compound.children", defs);
4461
4634
  return children.ok ? ok(compound$1(children.value)) : children;
4462
4635
  }
4463
4636
  function isMatrix4x4(v) {
4464
4637
  return Array.isArray(v) && v.length === 4 && v.every((row) => Array.isArray(row) && row.length === 4 && row.every(isNumber$1));
4465
4638
  }
4466
- function readInstance(j) {
4467
- const source = readNode(j["source"]);
4639
+ function readInstance(j, defs) {
4640
+ const source = readNode(j["source"], defs);
4468
4641
  if (!source.ok) return source;
4469
4642
  const placements = j["placements"];
4470
4643
  if (!Array.isArray(placements) || !placements.every(isMatrix4x4)) return bad("Instance.placements must be an array of 4x4 number matrices");
@@ -4662,128 +4835,6 @@ function optimizeTranslate(target, vector) {
4662
4835
  return translate$1(ot, ov);
4663
4836
  }
4664
4837
  //#endregion
4665
- //#region src/csg/edit.ts
4666
- function replaceNode(root, pred, replacement) {
4667
- return walk(root, pred, replacement, /* @__PURE__ */ new Map());
4668
- }
4669
- function walk(node, pred, repl, memo) {
4670
- const cached = memo.get(node);
4671
- if (cached !== void 0) return cached;
4672
- const out = pred(node) ? repl : rebuildChildren(node, pred, repl, memo);
4673
- memo.set(node, out);
4674
- return out;
4675
- }
4676
- function rebuildChildren(n, pred, repl, memo) {
4677
- switch (n.kind) {
4678
- case "Box":
4679
- case "Sphere":
4680
- case "Cylinder":
4681
- case "Cone":
4682
- case "Torus":
4683
- case "Polygon":
4684
- case "Circle":
4685
- case "Line":
4686
- case "Vertex":
4687
- case "Empty":
4688
- case "Path":
4689
- case "Profile": return n;
4690
- case "Fuse": return fuse$1(walk(n.a, pred, repl, memo), walk(n.b, pred, repl, memo), n.tolerance);
4691
- case "Cut": return cut$1(walk(n.a, pred, repl, memo), walk(n.b, pred, repl, memo), n.tolerance);
4692
- case "Intersect": return intersect$1(walk(n.a, pred, repl, memo), walk(n.b, pred, repl, memo), n.tolerance);
4693
- case "FuseAll": return fuseAll$1(n.shapes.map((c) => walk(c, pred, repl, memo)), n.tolerance);
4694
- case "CutAll": return cutAll$1(walk(n.base, pred, repl, memo), n.tools.map((c) => walk(c, pred, repl, memo)), n.tolerance);
4695
- case "Translate": return translate$1(walk(n.target, pred, repl, memo), n.vector);
4696
- case "Rotate": return rotate$1(walk(n.target, pred, repl, memo), n.angle, {
4697
- axis: n.axis,
4698
- at: n.at
4699
- });
4700
- case "Scale": return scale$2(walk(n.target, pred, repl, memo), n.factor, { center: n.center });
4701
- case "Mirror": return mirror$1(walk(n.target, pred, repl, memo), {
4702
- normal: n.normal,
4703
- at: n.at
4704
- });
4705
- case "Compound": return compound$1(n.children.map((c) => walk(c, pred, repl, memo)));
4706
- case "Instance": return instance$1(walk(n.source, pred, repl, memo), n.placements, n.fuse);
4707
- case "Extrude":
4708
- case "Revolve":
4709
- case "Loft":
4710
- case "Sweep":
4711
- case "Color":
4712
- case "Fillet":
4713
- case "Chamfer":
4714
- case "Shell": return rebuildFeature(n, pred, repl, memo);
4715
- }
4716
- }
4717
- function rebuildFeature(n, pred, repl, memo) {
4718
- switch (n.kind) {
4719
- case "Extrude": return extrude$1(walk(n.profile, pred, repl, memo), n.vector);
4720
- case "Revolve": return revolve$1(walk(n.profile, pred, repl, memo), n.angle, {
4721
- axis: n.axis,
4722
- at: n.at
4723
- });
4724
- case "Loft": return loft$1(n.sections.map((s) => walk(s, pred, repl, memo)), { ruled: n.ruled });
4725
- case "Sweep": return sweep$2(walk(n.profile, pred, repl, memo), walk(n.spine, pred, repl, memo), { frenet: n.frenet });
4726
- case "Color": return color(walk(n.target, pred, repl, memo), [...n.color]);
4727
- case "Fillet": return fillet$1(walk(n.target, pred, repl, memo), n.ref, n.radius);
4728
- case "Chamfer": return chamfer$1(walk(n.target, pred, repl, memo), n.ref, n.distance);
4729
- case "Shell": return shell$1(walk(n.target, pred, repl, memo), n.refs, n.thickness);
4730
- }
4731
- }
4732
- /** Visits each DISTINCT node once (identity-deduplicated), so a shared
4733
- * subtree is reported once and a self-referencing DAG walks in linear time. */
4734
- function forEachNode(root, fn) {
4735
- const seen = /* @__PURE__ */ new Set();
4736
- const visit = (node) => {
4737
- if (seen.has(node)) return;
4738
- seen.add(node);
4739
- fn(node);
4740
- for (const child of childrenOf(node)) visit(child);
4741
- };
4742
- visit(root);
4743
- }
4744
- function childrenOf(n) {
4745
- switch (n.kind) {
4746
- case "Box":
4747
- case "Sphere":
4748
- case "Cylinder":
4749
- case "Cone":
4750
- case "Torus":
4751
- case "Polygon":
4752
- case "Circle":
4753
- case "Line":
4754
- case "Vertex":
4755
- case "Empty":
4756
- case "Path":
4757
- case "Profile": return [];
4758
- case "Fuse":
4759
- case "Cut":
4760
- case "Intersect": return [n.a, n.b];
4761
- case "FuseAll": return n.shapes;
4762
- case "CutAll": return [n.base, ...n.tools];
4763
- case "Translate":
4764
- case "Rotate":
4765
- case "Scale":
4766
- case "Mirror":
4767
- case "Color":
4768
- case "Fillet":
4769
- case "Chamfer":
4770
- case "Shell": return [n.target];
4771
- case "Compound": return n.children;
4772
- case "Instance": return [n.source];
4773
- case "Extrude":
4774
- case "Revolve": return [n.profile];
4775
- case "Loft": return n.sections;
4776
- case "Sweep": return [n.profile, n.spine];
4777
- }
4778
- }
4779
- function nodeCount(root) {
4780
- let n = 0;
4781
- forEachNode(root, () => {
4782
- n++;
4783
- });
4784
- return n;
4785
- }
4786
- //#endregion
4787
4838
  //#region src/sketching/blueprintContourFns.ts
4788
4839
  var EPS$1 = 1e-6;
4789
4840
  function p2(p) {
@@ -8741,7 +8792,7 @@ var patterns_exports = /* @__PURE__ */ __exportAll({
8741
8792
  //#endregion
8742
8793
  //#region src/ns/csg.ts
8743
8794
  var csg_exports = /* @__PURE__ */ __exportAll({
8744
- CSG_VERSION: () => 7,
8795
+ CSG_VERSION: () => 8,
8745
8796
  Evaluator: () => Evaluator,
8746
8797
  add: () => add$1,
8747
8798
  arbitraryClosedProfile: () => arbitraryClosedProfile,