brepjs 18.151.1 → 18.152.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.
Files changed (92) hide show
  1. package/README.md +24 -0
  2. package/bin/brepjs.mjs +37 -4
  3. package/dist/2d.cjs +6 -6
  4. package/dist/2d.js +6 -6
  5. package/dist/{adjacencyFns-B1FEq-7P.cjs → adjacencyFns-DmzCVsNA.cjs} +1 -1
  6. package/dist/{adjacencyFns-ChpKOdJX.js → adjacencyFns-oMRMitRE.js} +1 -1
  7. package/dist/{blueprint-SQlROOFK.cjs → blueprint-DT7TQoxE.cjs} +6 -6
  8. package/dist/{blueprint-Be2V1tdb.js → blueprint-Dq3o62cF.js} +6 -6
  9. package/dist/{blueprintFns-DpZo8T6u.js → blueprintFns-B74C4tbO.js} +3 -3
  10. package/dist/{blueprintFns-BcWGUCk3.cjs → blueprintFns-CS4t2ukh.cjs} +3 -3
  11. package/dist/{blueprintSketcher-C9lVrRBr.cjs → blueprintSketcher-AtKzjQzf.cjs} +3 -3
  12. package/dist/{blueprintSketcher-HsSFy10k.js → blueprintSketcher-CEw91ij9.js} +3 -3
  13. package/dist/{boolean2D-Bh07EPIS.cjs → boolean2D-C1TN_Tx_.cjs} +5 -5
  14. package/dist/{boolean2D-CwFJsT_T.js → boolean2D-CTN9BHhX.js} +5 -5
  15. package/dist/brepjs.cjs +116 -60
  16. package/dist/brepjs.js +116 -60
  17. package/dist/{cameraFns-CWS3e1p_.js → cameraFns-By4a7rrk.js} +3 -3
  18. package/dist/{cameraFns-BYBavyGz.cjs → cameraFns-DvwFx7Zy.cjs} +3 -3
  19. package/dist/core/errors.d.ts +1 -0
  20. package/dist/core.cjs +3 -3
  21. package/dist/core.js +3 -3
  22. package/dist/{cornerFinder-D5doC-g3.js → cornerFinder-Bt1Ijr7a.js} +1 -1
  23. package/dist/{cornerFinder-B_6OVtcc.cjs → cornerFinder-wCndsp-s.cjs} +1 -1
  24. package/dist/csg/edit.d.ts +2 -0
  25. package/dist/csg/evaluate.d.ts +6 -0
  26. package/dist/{curveFns-dXPHWXhw.cjs → curveFns-DTbY4aYG.cjs} +1 -1
  27. package/dist/{curveFns-CO0dlocY.js → curveFns-U6vegvin.js} +1 -1
  28. package/dist/{drawFns-C_dvmPvb.cjs → drawFns-BxM81HkL.cjs} +13 -13
  29. package/dist/{drawFns-CVvt5Hmy.js → drawFns-qbcjiKq-.js} +13 -13
  30. package/dist/{errors-MHLiNE-C.js → errors-CTn8i3lY.js} +1 -0
  31. package/dist/{errors-RHkKO5bZ.cjs → errors-DWezb9DI.cjs} +1 -0
  32. package/dist/{faceFns-9lfp_6de.cjs → faceFns-ClDKjey7.cjs} +2 -2
  33. package/dist/{faceFns-C-SjeGpG.js → faceFns-D4z1xEh3.js} +2 -2
  34. package/dist/{healingFns-BtrFRoK5.js → healingFns-CN9ra8Kh.js} +4 -4
  35. package/dist/{healingFns-D4HAilgL.cjs → healingFns-CnRyuMWf.cjs} +4 -4
  36. package/dist/{helpers-BipNXMsi.js → helpers-DiOcrJ0A.js} +6 -6
  37. package/dist/{helpers-uUXFl5Pn.cjs → helpers-HM_a__7P.cjs} +6 -6
  38. package/dist/{importFns-lWFKWnCe.js → importFns-DjS57JAv.js} +2 -2
  39. package/dist/{importFns-CCnECEQt.cjs → importFns-cs7Y4qFg.cjs} +2 -2
  40. package/dist/io.cjs +2 -2
  41. package/dist/io.js +2 -2
  42. package/dist/{loftFns-BNziMKOW.js → loftFns-CvPetZyA.js} +1 -1
  43. package/dist/{loftFns-DUyKz3r7.cjs → loftFns-CzPUAFgL.cjs} +1 -1
  44. package/dist/{measureFns-D9lwC7kw.js → measureFns-DfgTyVBz.js} +3 -3
  45. package/dist/{measureFns-K2jBIt9p.cjs → measureFns-Dr0dCH-w.cjs} +3 -3
  46. package/dist/measurement.cjs +1 -1
  47. package/dist/measurement.js +1 -1
  48. package/dist/{meshFns-D5_15Njg.js → meshFns-FSW58GBR.js} +3 -3
  49. package/dist/{meshFns-D0JF9XF8.cjs → meshFns-cMtop3IN.cjs} +3 -3
  50. package/dist/operations.cjs +2 -2
  51. package/dist/operations.js +2 -2
  52. package/dist/{originTrackingFns-BgWmLwox.cjs → originTrackingFns-CSsyNoUc.cjs} +1 -1
  53. package/dist/{originTrackingFns-BZwAfjbF.js → originTrackingFns-Cd7Rrp9x.js} +1 -1
  54. package/dist/{planeOps-CQahLrCx.js → planeOps-CkpMeheg.js} +1 -1
  55. package/dist/{planeOps-VlFxtvzY.cjs → planeOps-CpupTCRM.cjs} +1 -1
  56. package/dist/{primitiveFns-p0NdUf-t.js → primitiveFns-B0iLpCEN.js} +4 -4
  57. package/dist/{primitiveFns-B_09AGpd.cjs → primitiveFns-BjwF7CHF.cjs} +4 -4
  58. package/dist/projection.cjs +1 -1
  59. package/dist/projection.js +1 -1
  60. package/dist/query.cjs +2 -2
  61. package/dist/query.js +2 -2
  62. package/dist/{refResolveFns-Dnc4_49q.js → refResolveFns-Bkm7htwc.js} +5 -5
  63. package/dist/{refResolveFns-Cin3djM3.cjs → refResolveFns-CTQaQ8Q8.cjs} +5 -5
  64. package/dist/result.cjs +1 -1
  65. package/dist/result.js +1 -1
  66. package/dist/{shapeFns-D65fk--y.cjs → shapeFns-C3AFMiyv.cjs} +2 -2
  67. package/dist/{shapeFns-BZnM8fRy.js → shapeFns-D0hK1y4m.js} +2 -2
  68. package/dist/shapeRef.cjs +1 -1
  69. package/dist/shapeRef.js +1 -1
  70. package/dist/sketching.cjs +3 -3
  71. package/dist/sketching.js +3 -3
  72. package/dist/{solidBuilders-Bm4P-jGM.cjs → solidBuilders-BKSCyfzL.cjs} +6 -6
  73. package/dist/{solidBuilders-CIDWhMT6.js → solidBuilders-BiRBDuvG.js} +6 -6
  74. package/dist/{surfaceBuilders-BC3swenJ.js → surfaceBuilders-C_oBS19q.js} +3 -3
  75. package/dist/{surfaceBuilders-CxbTUaok.cjs → surfaceBuilders-aSFtcDr6.cjs} +3 -3
  76. package/dist/text.cjs +2 -2
  77. package/dist/text.js +2 -2
  78. package/dist/{textBlueprints-75GTk1vk.js → textBlueprints-BM_HzZ_Z.js} +8 -8
  79. package/dist/{textBlueprints-D1OMdDIP.cjs → textBlueprints-rWPsnXS2.cjs} +8 -8
  80. package/dist/{textMetrics-DCBkXf-H.js → textMetrics-BS2MIEXH.js} +2 -2
  81. package/dist/{textMetrics-J2bKijAB.cjs → textMetrics-CQi7r4K0.cjs} +2 -2
  82. package/dist/{threadFns-D55B4LJk.js → threadFns-B86t62TB.js} +7 -7
  83. package/dist/{threadFns-9Xvow5fk.cjs → threadFns-BhTaoFLm.cjs} +7 -7
  84. package/dist/topology.cjs +9 -9
  85. package/dist/topology.js +9 -9
  86. package/dist/{topologyQueryFns-G6xu8ExU.js → topologyQueryFns-Dxwm2jN-.js} +1 -1
  87. package/dist/{topologyQueryFns-nT3Ir1y-.cjs → topologyQueryFns-K7_UgEws.cjs} +1 -1
  88. package/dist/{validityTypes-OvTu-S-y.cjs → validityTypes-Det5YWSW.cjs} +1 -1
  89. package/dist/{validityTypes-seS7aZI9.js → validityTypes-T6dHSAOb.js} +1 -1
  90. package/dist/vectors.cjs +1 -1
  91. package/dist/vectors.js +1 -1
  92. package/package.json +1 -1
package/README.md CHANGED
@@ -60,6 +60,8 @@ To set expectations, this project deliberately does not:
60
60
  npm install brepjs occt-wasm
61
61
  ```
62
62
 
63
+ Starting fresh? `npm create brepjs my-project` scaffolds a TypeScript app with the kernel and the declarative family layer preconfigured.
64
+
63
65
  `brepjs/quick` handles WASM init automatically via top-level await (ESM only). Other options:
64
66
 
65
67
  ```typescript
@@ -84,6 +86,7 @@ The chapter-based guide is the recommended starting point:
84
86
  - **[Cheat Sheet](https://brepjs.dev/getting-started/cheat-sheet)**: single-page reference
85
87
  - **[Core Concepts](https://brepjs.dev/concepts/brep-vs-mesh)**: B-Rep, topology, types, kernels, tolerance
86
88
  - **[Common Tasks](https://brepjs.dev/tasks/booleans)**: booleans, fillets, sketching, lofts, sweeps, finders, measurement, IO
89
+ - **[Declarative Models](https://brepjs.dev/families/overview)**: React-like parametric components (brepjs-families), key-path identity, IFC export
87
90
  - **[Three.js Integration](https://brepjs.dev/integration/threejs)**: meshing and rendering
88
91
  - **[Migration](https://brepjs.dev/migration/replicad)**: coming from Replicad, OpenSCAD, or Three.js
89
92
  - **[Extending brepjs](https://brepjs.dev/extending/architecture)**: custom kernels, custom operations, architecture
@@ -139,6 +142,27 @@ npx -y -p brepjs-cad brep bracket.brep.ts --check --step bracket.step --json rep
139
142
 
140
143
  The command exits non-zero unless the report is `ok` (valid **and** every declared dimension within tolerance), so it drops straight into CI or an agent loop. For MCP-capable agents the package also ships a stdio MCP server (`brep-mcp`) exposing the same build-and-verify step as a `run_program` tool. See the [**Authoring with AI**](https://brepjs.dev/agent/overview) guide for the full loop, CLI reference, the MCP server, examples, and the measurement eval.
141
144
 
145
+ ## Declarative models and BIM (brepjs-families + brepjs-bim)
146
+
147
+ [`brepjs-families`](https://www.npmjs.com/package/brepjs-families) is a React-like component layer over the CSG IR: parametric parts are prop-driven, composable, zod-validated components, and identical subtrees materialize once because the IR is content-addressed. [`brepjs-bim`](https://www.npmjs.com/package/brepjs-bim) projects the same tree to IFC4 with stable GlobalIds derived from component key paths — walls, slabs, columns, beams, roofs, stairs, and real openings (`IfcOpeningElement` + `IfcRelFillsElement`).
148
+
149
+ ```typescript
150
+ const Wall = family('Wall', (p: WallProps) =>
151
+ el('Box', { size: [p.length, p.thickness, p.height], voids: p.voids ?? [] })
152
+ );
153
+ const wall = Wall({ key: 'south', length: 4000, thickness: 200, height: 2700 });
154
+ const tree = resolve(Storey({ key: 'ground', items: [wall] }));
155
+ evaluateModel(tree, evaluator); // viewport meshes, one per key path
156
+ familiesToBim(tree, { project }); // IFC via brepjs-bim
157
+ ```
158
+
159
+ ```bash
160
+ npm create brepjs my-building # scaffold a project
161
+ npx brepjs add room storey slab # copy in starter families (code you own)
162
+ ```
163
+
164
+ See the [**Declarative Models**](https://brepjs.dev/families/overview) guide.
165
+
142
166
  ## Projects Using brepjs
143
167
 
144
168
  - [Gridfinity Layout Tool](https://github.com/andymai/gridfinity-layout-tool): Web-based layout generator for Gridfinity storage systems
package/bin/brepjs.mjs CHANGED
@@ -19,14 +19,46 @@
19
19
  import { mkdir, open, readFile, writeFile, access, lstat, realpath, rename, rm } from 'node:fs/promises';
20
20
  import { constants } from 'node:fs';
21
21
  import { spawnSync } from 'node:child_process';
22
- import { join, resolve as resolvePath, sep } from 'node:path';
22
+ import { dirname, join, resolve as resolvePath, sep } from 'node:path';
23
23
  import process from 'node:process';
24
24
 
25
- const DEFAULT_REGISTRY =
26
- 'https://raw.githubusercontent.com/andymai/brepjs/main/packages/brepjs-families/registry';
25
+ // The default registry pins to the release tag of the INSTALLED
26
+ // brepjs-families, so copied sources match the package the project compiles
27
+ // against instead of tracking main (which can drift ahead). Without a local
28
+ // install, fall back to main.
29
+ const REGISTRY_PATH = 'packages/brepjs-families/registry';
30
+
31
+ async function installedFamiliesVersion() {
32
+ // Walk ancestor node_modules like Node's resolver, so a hoisted install
33
+ // (workspaces, monorepos) still pins the registry.
34
+ let dir = process.cwd();
35
+ for (;;) {
36
+ try {
37
+ const pkg = JSON.parse(
38
+ await readFile(join(dir, 'node_modules', 'brepjs-families', 'package.json'), 'utf8')
39
+ );
40
+ if (typeof pkg.version === 'string' && /^\d+\.\d+\.\d+$/.test(pkg.version)) {
41
+ return pkg.version;
42
+ }
43
+ } catch {
44
+ // keep walking
45
+ }
46
+ const parent = dirname(dir);
47
+ if (parent === dir) return null;
48
+ dir = parent;
49
+ }
50
+ }
51
+
52
+ async function defaultRegistry() {
53
+ const version = await installedFamiliesVersion();
54
+ if (version !== null) {
55
+ return `https://raw.githubusercontent.com/andymai/brepjs/brepjs-families-v${version}/${REGISTRY_PATH}`;
56
+ }
57
+ return `https://raw.githubusercontent.com/andymai/brepjs/main/${REGISTRY_PATH}`;
58
+ }
27
59
 
28
60
  function parseArgs(argv) {
29
- const args = { _: [], registry: DEFAULT_REGISTRY, dir: 'src/families', force: false, install: false };
61
+ const args = { _: [], registry: null, dir: 'src/families', force: false, install: false };
30
62
  for (let i = 0; i < argv.length; i++) {
31
63
  const a = argv[i];
32
64
  if (a === '--registry') args.registry = argv[++i];
@@ -295,6 +327,7 @@ async function diff(args) {
295
327
  async function main() {
296
328
  const [cmd, ...rest] = process.argv.slice(2);
297
329
  const args = parseArgs(rest);
330
+ args.registry = args.registry ?? (await defaultRegistry());
298
331
  if (cmd === 'add' && args._.length > 0) return add(args);
299
332
  if (cmd === 'diff' && args._.length === 1) return diff(args);
300
333
  console.error(
package/dist/2d.cjs CHANGED
@@ -1,11 +1,11 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_shapeTypes = require("./shapeTypes-Dq5ylbmP.cjs");
3
- const require_errors = require("./errors-RHkKO5bZ.cjs");
4
- const require_topologyQueryFns = require("./topologyQueryFns-nT3Ir1y-.cjs");
5
- const require_blueprintSketcher = require("./blueprintSketcher-C9lVrRBr.cjs");
6
- const require_blueprint = require("./blueprint-SQlROOFK.cjs");
7
- const require_boolean2D = require("./boolean2D-Bh07EPIS.cjs");
8
- const require_blueprintFns = require("./blueprintFns-BcWGUCk3.cjs");
3
+ const require_errors = require("./errors-DWezb9DI.cjs");
4
+ const require_topologyQueryFns = require("./topologyQueryFns-K7_UgEws.cjs");
5
+ const require_blueprintSketcher = require("./blueprintSketcher-AtKzjQzf.cjs");
6
+ const require_blueprint = require("./blueprint-DT7TQoxE.cjs");
7
+ const require_boolean2D = require("./boolean2D-C1TN_Tx_.cjs");
8
+ const require_blueprintFns = require("./blueprintFns-CS4t2ukh.cjs");
9
9
  //#region src/core/curve2dHandle.ts
10
10
  /**
11
11
  * Wrap a raw kernel 2D curve handle with brand + disposal tracking.
package/dist/2d.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import { P as createKernelHandle, c as createEdge, q as getKernel2D } from "./shapeTypes-JD6SSVwZ.js";
2
- import { E as ok, d as validationError, g as err, t as BrepErrorCode } from "./errors-MHLiNE-C.js";
3
- import { T as kernelCallRaw } from "./topologyQueryFns-G6xu8ExU.js";
4
- import { a as Blueprints, i as CompoundBlueprint, r as organiseBlueprints } from "./blueprintSketcher-HsSFy10k.js";
5
- import { t as Blueprint, v as Curve2D, x as BoundingBox2d } from "./blueprint-Be2V1tdb.js";
6
- import { a as fuseBlueprints, c as roundedRectangleBlueprint, i as cutBlueprints, n as fuse2D, o as intersectBlueprints, r as intersect2D, s as polysidesBlueprint, t as cut2D } from "./boolean2D-CwFJsT_T.js";
7
- import { S as reverseCurve, _ as curve2dIsOnCurve, a as isInside2D, b as curve2dSplitAt, c as scale2D, d as stretch2D, f as toSVGPathD, g as curve2dFirstPoint, h as curve2dDistanceFrom, i as getOrientation2D, l as sketchOnFace2D, m as curve2dBoundingBox, o as mirror2D, p as translate2D, r as getBounds2D, s as rotate2D, t as createBlueprint, u as sketchOnPlane2D, v as curve2dLastPoint, x as curve2dTangentAt, y as curve2dParameter } from "./blueprintFns-DpZo8T6u.js";
2
+ import { E as ok, d as validationError, g as err, t as BrepErrorCode } from "./errors-CTn8i3lY.js";
3
+ import { T as kernelCallRaw } from "./topologyQueryFns-Dxwm2jN-.js";
4
+ import { a as Blueprints, i as CompoundBlueprint, r as organiseBlueprints } from "./blueprintSketcher-CEw91ij9.js";
5
+ import { t as Blueprint, v as Curve2D, x as BoundingBox2d } from "./blueprint-Dq3o62cF.js";
6
+ import { a as fuseBlueprints, c as roundedRectangleBlueprint, i as cutBlueprints, n as fuse2D, o as intersectBlueprints, r as intersect2D, s as polysidesBlueprint, t as cut2D } from "./boolean2D-CTN9BHhX.js";
7
+ import { S as reverseCurve, _ as curve2dIsOnCurve, a as isInside2D, b as curve2dSplitAt, c as scale2D, d as stretch2D, f as toSVGPathD, g as curve2dFirstPoint, h as curve2dDistanceFrom, i as getOrientation2D, l as sketchOnFace2D, m as curve2dBoundingBox, o as mirror2D, p as translate2D, r as getBounds2D, s as rotate2D, t as createBlueprint, u as sketchOnPlane2D, v as curve2dLastPoint, x as curve2dTangentAt, y as curve2dParameter } from "./blueprintFns-B74C4tbO.js";
8
8
  //#region src/core/curve2dHandle.ts
9
9
  /**
10
10
  * Wrap a raw kernel 2D curve handle with brand + disposal tracking.
@@ -1,6 +1,6 @@
1
1
  const require_shapeTypes = require("./shapeTypes-Dq5ylbmP.cjs");
2
2
  const require_occtWasmAdapter = require("./occtWasmAdapter-BnMFWwNp.cjs");
3
- const require_topologyQueryFns = require("./topologyQueryFns-nT3Ir1y-.cjs");
3
+ const require_topologyQueryFns = require("./topologyQueryFns-K7_UgEws.cjs");
4
4
  const require_constants = require("./constants-BOVyEYGH.cjs");
5
5
  //#region src/topology/adjacencyFns.ts
6
6
  /**
@@ -1,6 +1,6 @@
1
1
  import { G as getActiveKernelId, K as getKernel, r as castResultShapeWithKnownType, t as borrowShapeWithKnownType } from "./shapeTypes-JD6SSVwZ.js";
2
2
  import { O as isUnsupportedKernelOperationError } from "./occtWasmAdapter-Cu1SW10s.js";
3
- import { c as getFaces, f as getVertices, l as getOrCreateCache, s as getEdges } from "./topologyQueryFns-G6xu8ExU.js";
3
+ import { c as getFaces, f as getVertices, l as getOrCreateCache, s as getEdges } from "./topologyQueryFns-Dxwm2jN-.js";
4
4
  import { n as HASH_CODE_MAX } from "./constants-ITRzCnCp.js";
5
5
  //#region src/topology/adjacencyFns.ts
6
6
  /**
@@ -1,14 +1,14 @@
1
1
  const require_shapeTypes = require("./shapeTypes-Dq5ylbmP.cjs");
2
2
  const require_bug = require("./bug-O02CsAmt.cjs");
3
- const require_errors = require("./errors-RHkKO5bZ.cjs");
3
+ const require_errors = require("./errors-DWezb9DI.cjs");
4
4
  const require_constants = require("./constants-BOVyEYGH.cjs");
5
5
  const require_types = require("./types-KjA8tY4Y.cjs");
6
- const require_planeOps = require("./planeOps-VlFxtvzY.cjs");
7
- const require_faceFns = require("./faceFns-9lfp_6de.cjs");
8
- const require_curveFns = require("./curveFns-dXPHWXhw.cjs");
6
+ const require_planeOps = require("./planeOps-CpupTCRM.cjs");
7
+ const require_faceFns = require("./faceFns-ClDKjey7.cjs");
8
+ const require_curveFns = require("./curveFns-DTbY4aYG.cjs");
9
9
  const require_arrayAccess = require("./arrayAccess-e4H9cBfh.cjs");
10
- const require_surfaceBuilders = require("./surfaceBuilders-CxbTUaok.cjs");
11
- const require_helpers = require("./helpers-uUXFl5Pn.cjs");
10
+ const require_surfaceBuilders = require("./surfaceBuilders-aSFtcDr6.cjs");
11
+ const require_helpers = require("./helpers-HM_a__7P.cjs");
12
12
  //#region src/2d/lib/definitions.ts
13
13
  /** Type guard that checks whether a value is a `Point2D`. */
14
14
  function isPoint2D(point) {
@@ -1,14 +1,14 @@
1
1
  import { K as getKernel, L as registerForCleanup, c as createEdge, p as createWire, q as getKernel2D, z as unregisterFromCleanup } from "./shapeTypes-JD6SSVwZ.js";
2
2
  import { i as wasmIndex, n as bug } from "./bug-BdOHwn_e.js";
3
- import { E as ok, P as unwrap, d as validationError, g as err, n as computationError } from "./errors-MHLiNE-C.js";
3
+ import { E as ok, P as unwrap, d as validationError, g as err, n as computationError } from "./errors-CTn8i3lY.js";
4
4
  import { r as RAD2DEG, t as DEG2RAD } from "./constants-ITRzCnCp.js";
5
5
  import { r as toVec3 } from "./types-D24Y27N0.js";
6
- import { r as makePlane } from "./planeOps-CQahLrCx.js";
7
- import { _ as cast, h as uvCoordinates, i as faceGeomType, m as uvBounds, r as faceCenter } from "./faceFns-C-SjeGpG.js";
8
- import { p as getOrientation } from "./curveFns-CO0dlocY.js";
6
+ import { r as makePlane } from "./planeOps-CkpMeheg.js";
7
+ import { _ as cast, h as uvCoordinates, i as faceGeomType, m as uvBounds, r as faceCenter } from "./faceFns-D4z1xEh3.js";
8
+ import { p as getOrientation } from "./curveFns-U6vegvin.js";
9
9
  import { n as getAtOrThrow, r as lastOrThrow, t as firstOrThrow } from "./arrayAccess-DrUGPADn.js";
10
- import { r as makeFace } from "./surfaceBuilders-BC3swenJ.js";
11
- import { l as add2d, m as normalize2d$1, p as distance2d, t as getSingleFace, v as samePoint, x as subtract2d, y as scalarMultiply2d } from "./helpers-BipNXMsi.js";
10
+ import { r as makeFace } from "./surfaceBuilders-C_oBS19q.js";
11
+ import { l as add2d, m as normalize2d$1, p as distance2d, t as getSingleFace, v as samePoint, x as subtract2d, y as scalarMultiply2d } from "./helpers-DiOcrJ0A.js";
12
12
  //#region src/2d/lib/definitions.ts
13
13
  /** Type guard that checks whether a value is a `Point2D`. */
14
14
  function isPoint2D(point) {
@@ -1,6 +1,6 @@
1
- import { E as ok, d as validationError, g as err, t as BrepErrorCode } from "./errors-MHLiNE-C.js";
2
- import { i as CompoundBlueprint } from "./blueprintSketcher-HsSFy10k.js";
3
- import { t as Blueprint } from "./blueprint-Be2V1tdb.js";
1
+ import { E as ok, d as validationError, g as err, t as BrepErrorCode } from "./errors-CTn8i3lY.js";
2
+ import { i as CompoundBlueprint } from "./blueprintSketcher-CEw91ij9.js";
3
+ import { t as Blueprint } from "./blueprint-Dq3o62cF.js";
4
4
  //#region src/2d/lib/curve2dFns.ts
5
5
  /**
6
6
  * Return a reversed copy of the curve (non-mutating).
@@ -1,6 +1,6 @@
1
- const require_errors = require("./errors-RHkKO5bZ.cjs");
2
- const require_blueprintSketcher = require("./blueprintSketcher-C9lVrRBr.cjs");
3
- const require_blueprint = require("./blueprint-SQlROOFK.cjs");
1
+ const require_errors = require("./errors-DWezb9DI.cjs");
2
+ const require_blueprintSketcher = require("./blueprintSketcher-AtKzjQzf.cjs");
3
+ const require_blueprint = require("./blueprint-DT7TQoxE.cjs");
4
4
  //#region src/2d/lib/curve2dFns.ts
5
5
  /**
6
6
  * Return a reversed copy of the curve (non-mutating).
@@ -1,10 +1,10 @@
1
1
  const require_shapeTypes = require("./shapeTypes-Dq5ylbmP.cjs");
2
2
  const require_bug = require("./bug-O02CsAmt.cjs");
3
- const require_errors = require("./errors-RHkKO5bZ.cjs");
3
+ const require_errors = require("./errors-DWezb9DI.cjs");
4
4
  const require_constants = require("./constants-BOVyEYGH.cjs");
5
5
  const require_arrayAccess = require("./arrayAccess-e4H9cBfh.cjs");
6
- const require_helpers = require("./helpers-uUXFl5Pn.cjs");
7
- const require_blueprint = require("./blueprint-SQlROOFK.cjs");
6
+ const require_helpers = require("./helpers-HM_a__7P.cjs");
7
+ const require_blueprint = require("./blueprint-DT7TQoxE.cjs");
8
8
  //#region node_modules/flatqueue/index.js
9
9
  /**
10
10
  * @typedef {Float64ArrayConstructor | Float32ArrayConstructor |
@@ -1,10 +1,10 @@
1
1
  import { K as getKernel, q as getKernel2D } from "./shapeTypes-JD6SSVwZ.js";
2
2
  import { i as wasmIndex, n as bug } from "./bug-BdOHwn_e.js";
3
- import { E as ok, P as unwrap, g as err, n as computationError, s as safeIndex, x as isOk } from "./errors-MHLiNE-C.js";
3
+ import { E as ok, P as unwrap, g as err, n as computationError, s as safeIndex, x as isOk } from "./errors-CTn8i3lY.js";
4
4
  import { r as RAD2DEG, t as DEG2RAD } from "./constants-ITRzCnCp.js";
5
5
  import { t as firstOrThrow } from "./arrayAccess-DrUGPADn.js";
6
- import { _ as rotate2d, d as cartesianToPolar, f as crossProduct2d, g as polarToCartesian, h as polarAngle2d, l as add2d, p as distance2d, v as samePoint, x as subtract2d, y as scalarMultiply2d } from "./helpers-BipNXMsi.js";
7
- import { C as isPoint2D, b as normalize2d, c as make2dBezierCurve, d as make2dEllipseArc, g as approximateAsBSpline, h as make2dThreePointArc, l as make2dCircle, m as make2dTangentArc, n as asSVG, p as make2dSegmentCurve, r as viewbox, s as make2dArcFromCenter, t as Blueprint, v as Curve2D, x as BoundingBox2d } from "./blueprint-Be2V1tdb.js";
6
+ import { _ as rotate2d, d as cartesianToPolar, f as crossProduct2d, g as polarToCartesian, h as polarAngle2d, l as add2d, p as distance2d, v as samePoint, x as subtract2d, y as scalarMultiply2d } from "./helpers-DiOcrJ0A.js";
7
+ import { C as isPoint2D, b as normalize2d, c as make2dBezierCurve, d as make2dEllipseArc, g as approximateAsBSpline, h as make2dThreePointArc, l as make2dCircle, m as make2dTangentArc, n as asSVG, p as make2dSegmentCurve, r as viewbox, s as make2dArcFromCenter, t as Blueprint, v as Curve2D, x as BoundingBox2d } from "./blueprint-Dq3o62cF.js";
8
8
  //#region node_modules/flatqueue/index.js
9
9
  /**
10
10
  * @typedef {Float64ArrayConstructor | Float32ArrayConstructor |
@@ -1,10 +1,10 @@
1
1
  const require_bug = require("./bug-O02CsAmt.cjs");
2
- const require_errors = require("./errors-RHkKO5bZ.cjs");
2
+ const require_errors = require("./errors-DWezb9DI.cjs");
3
3
  const require_arrayAccess = require("./arrayAccess-e4H9cBfh.cjs");
4
- const require_surfaceBuilders = require("./surfaceBuilders-CxbTUaok.cjs");
5
- const require_blueprintSketcher = require("./blueprintSketcher-C9lVrRBr.cjs");
6
- const require_helpers = require("./helpers-uUXFl5Pn.cjs");
7
- const require_blueprint = require("./blueprint-SQlROOFK.cjs");
4
+ const require_surfaceBuilders = require("./surfaceBuilders-aSFtcDr6.cjs");
5
+ const require_blueprintSketcher = require("./blueprintSketcher-AtKzjQzf.cjs");
6
+ const require_helpers = require("./helpers-HM_a__7P.cjs");
7
+ const require_blueprint = require("./blueprint-DT7TQoxE.cjs");
8
8
  //#region src/2d/blueprints/cannedBlueprints.ts
9
9
  /**
10
10
  * Create a regular polygon blueprint inscribed in a circle of the given radius.
@@ -1,10 +1,10 @@
1
1
  import { i as wasmIndex, n as bug } from "./bug-BdOHwn_e.js";
2
- import { P as unwrap, s as safeIndex } from "./errors-MHLiNE-C.js";
2
+ import { P as unwrap, s as safeIndex } from "./errors-CTn8i3lY.js";
3
3
  import { r as lastOrThrow } from "./arrayAccess-DrUGPADn.js";
4
- import { v as zip } from "./surfaceBuilders-BC3swenJ.js";
5
- import { a as Blueprints, i as CompoundBlueprint, l as intersectCurves, r as organiseBlueprints, t as BlueprintSketcher, u as Flatbush } from "./blueprintSketcher-HsSFy10k.js";
6
- import { f as crossProduct2d, o as PRECISION_INTERSECTION, v as samePoint$1, x as subtract2d } from "./helpers-BipNXMsi.js";
7
- import { S as removeDuplicatePoints, p as make2dSegmentCurve, t as Blueprint } from "./blueprint-Be2V1tdb.js";
4
+ import { v as zip } from "./surfaceBuilders-C_oBS19q.js";
5
+ import { a as Blueprints, i as CompoundBlueprint, l as intersectCurves, r as organiseBlueprints, t as BlueprintSketcher, u as Flatbush } from "./blueprintSketcher-CEw91ij9.js";
6
+ import { f as crossProduct2d, o as PRECISION_INTERSECTION, v as samePoint$1, x as subtract2d } from "./helpers-DiOcrJ0A.js";
7
+ import { S as removeDuplicatePoints, p as make2dSegmentCurve, t as Blueprint } from "./blueprint-Dq3o62cF.js";
8
8
  //#region src/2d/blueprints/cannedBlueprints.ts
9
9
  /**
10
10
  * Create a regular polygon blueprint inscribed in a circle of the given radius.
package/dist/brepjs.cjs CHANGED
@@ -3,39 +3,39 @@ const require_rolldown_runtime = require("./rolldown-runtime-C0BPl7ul.cjs");
3
3
  const require_shapeTypes = require("./shapeTypes-Dq5ylbmP.cjs");
4
4
  const require_occtWasmAdapter = require("./occtWasmAdapter-BnMFWwNp.cjs");
5
5
  const require_bug = require("./bug-O02CsAmt.cjs");
6
- const require_errors = require("./errors-RHkKO5bZ.cjs");
7
- const require_topologyQueryFns = require("./topologyQueryFns-nT3Ir1y-.cjs");
8
- const require_validityTypes = require("./validityTypes-OvTu-S-y.cjs");
6
+ const require_errors = require("./errors-DWezb9DI.cjs");
7
+ const require_topologyQueryFns = require("./topologyQueryFns-K7_UgEws.cjs");
8
+ const require_validityTypes = require("./validityTypes-Det5YWSW.cjs");
9
9
  const require_constants = require("./constants-BOVyEYGH.cjs");
10
10
  const require_types = require("./types-KjA8tY4Y.cjs");
11
11
  const require_vecOps = require("./vecOps-CCnJt-yH.cjs");
12
- const require_planeOps = require("./planeOps-VlFxtvzY.cjs");
13
- const require_faceFns = require("./faceFns-9lfp_6de.cjs");
14
- const require_originTrackingFns = require("./originTrackingFns-BgWmLwox.cjs");
15
- const require_solidBuilders = require("./solidBuilders-Bm4P-jGM.cjs");
16
- const require_primitiveFns = require("./primitiveFns-B_09AGpd.cjs");
17
- const require_shapeFns = require("./shapeFns-D65fk--y.cjs");
18
- const require_curveFns = require("./curveFns-dXPHWXhw.cjs");
19
- const require_meshFns = require("./meshFns-D0JF9XF8.cjs");
12
+ const require_planeOps = require("./planeOps-CpupTCRM.cjs");
13
+ const require_faceFns = require("./faceFns-ClDKjey7.cjs");
14
+ const require_originTrackingFns = require("./originTrackingFns-CSsyNoUc.cjs");
15
+ const require_solidBuilders = require("./solidBuilders-BKSCyfzL.cjs");
16
+ const require_primitiveFns = require("./primitiveFns-BjwF7CHF.cjs");
17
+ const require_shapeFns = require("./shapeFns-C3AFMiyv.cjs");
18
+ const require_curveFns = require("./curveFns-DTbY4aYG.cjs");
19
+ const require_meshFns = require("./meshFns-cMtop3IN.cjs");
20
20
  const require_arrayAccess = require("./arrayAccess-e4H9cBfh.cjs");
21
- const require_surfaceBuilders = require("./surfaceBuilders-CxbTUaok.cjs");
22
- const require_healingFns = require("./healingFns-D4HAilgL.cjs");
23
- const require_threadFns = require("./threadFns-9Xvow5fk.cjs");
24
- const require_blueprintSketcher = require("./blueprintSketcher-C9lVrRBr.cjs");
25
- const require_helpers = require("./helpers-uUXFl5Pn.cjs");
26
- const require_blueprint = require("./blueprint-SQlROOFK.cjs");
27
- const require_drawFns = require("./drawFns-C_dvmPvb.cjs");
28
- const require_measureFns = require("./measureFns-K2jBIt9p.cjs");
29
- const require_cornerFinder = require("./cornerFinder-B_6OVtcc.cjs");
30
- const require_boolean2D = require("./boolean2D-Bh07EPIS.cjs");
31
- const require_blueprintFns = require("./blueprintFns-BcWGUCk3.cjs");
32
- const require_importFns = require("./importFns-CCnECEQt.cjs");
33
- const require_loftFns = require("./loftFns-DUyKz3r7.cjs");
34
- const require_textBlueprints = require("./textBlueprints-D1OMdDIP.cjs");
35
- const require_refResolveFns = require("./refResolveFns-Cin3djM3.cjs");
36
- const require_adjacencyFns = require("./adjacencyFns-B1FEq-7P.cjs");
37
- const require_cameraFns = require("./cameraFns-BYBavyGz.cjs");
38
- const require_textMetrics = require("./textMetrics-J2bKijAB.cjs");
21
+ const require_surfaceBuilders = require("./surfaceBuilders-aSFtcDr6.cjs");
22
+ const require_healingFns = require("./healingFns-CnRyuMWf.cjs");
23
+ const require_threadFns = require("./threadFns-BhTaoFLm.cjs");
24
+ const require_blueprintSketcher = require("./blueprintSketcher-AtKzjQzf.cjs");
25
+ const require_helpers = require("./helpers-HM_a__7P.cjs");
26
+ const require_blueprint = require("./blueprint-DT7TQoxE.cjs");
27
+ const require_drawFns = require("./drawFns-BxM81HkL.cjs");
28
+ const require_measureFns = require("./measureFns-Dr0dCH-w.cjs");
29
+ const require_cornerFinder = require("./cornerFinder-wCndsp-s.cjs");
30
+ const require_boolean2D = require("./boolean2D-C1TN_Tx_.cjs");
31
+ const require_blueprintFns = require("./blueprintFns-CS4t2ukh.cjs");
32
+ const require_importFns = require("./importFns-cs7Y4qFg.cjs");
33
+ const require_loftFns = require("./loftFns-CzPUAFgL.cjs");
34
+ const require_textBlueprints = require("./textBlueprints-rWPsnXS2.cjs");
35
+ const require_refResolveFns = require("./refResolveFns-CTQaQ8Q8.cjs");
36
+ const require_adjacencyFns = require("./adjacencyFns-DmzCVsNA.cjs");
37
+ const require_cameraFns = require("./cameraFns-DvwFx7Zy.cjs");
38
+ const require_textMetrics = require("./textMetrics-CQi7r4K0.cjs");
39
39
  const require_workerPool = require("./workerPool-D30NsiIW.cjs");
40
40
  //#region src/kernel/types.ts
41
41
  /** Check if the kernel supports hidden-line-removal projection. */
@@ -3355,6 +3355,20 @@ function relocateFaceGroups(faceGroups, innerHashes, placedHashes) {
3355
3355
  faceId: remap.get(g.faceId) ?? g.faceId
3356
3356
  }));
3357
3357
  }
3358
+ var SCALE_INVARIANT_DIAGONAL = 10;
3359
+ function scaleDefaultTolerance(base, shape) {
3360
+ let diagonal;
3361
+ try {
3362
+ const b = require_topologyQueryFns.getBounds(shape);
3363
+ const dx = b.xMax - b.xMin;
3364
+ const dy = b.yMax - b.yMin;
3365
+ const dz = b.zMax - b.zMin;
3366
+ diagonal = Math.sqrt(dx * dx + dy * dy + dz * dz);
3367
+ } catch {
3368
+ return base;
3369
+ }
3370
+ return base * Math.max(1, diagonal / SCALE_INVARIANT_DIAGONAL);
3371
+ }
3358
3372
  var Evaluator = class {
3359
3373
  cache = /* @__PURE__ */ new Map();
3360
3374
  refCounts = /* @__PURE__ */ new Map();
@@ -3419,9 +3433,9 @@ var Evaluator = class {
3419
3433
  meshOpts.signal?.throwIfAborted();
3420
3434
  const useCache = meshOpts.cache ?? true;
3421
3435
  const quality = require_shapeTypes.qualityDeflection();
3422
- const tolerance = meshOpts.tolerance ?? quality.tolerance;
3436
+ const explicitTolerance = meshOpts.tolerance;
3423
3437
  const angularTolerance = meshOpts.angularTolerance ?? quality.angularTolerance;
3424
- const meshKey = `${cacheKey(node, env, this.kernelId, this.defaultTolerance)}|${require_meshFns.buildMeshCacheKey(tolerance, angularTolerance, meshOpts.skipNormals ?? false, meshOpts.includeUVs ?? false)}`;
3438
+ const meshKey = `${cacheKey(node, env, this.kernelId, this.defaultTolerance)}|${explicitTolerance === void 0 ? "rel|" : ""}${require_meshFns.buildMeshCacheKey(explicitTolerance ?? quality.tolerance, angularTolerance, meshOpts.skipNormals ?? false, meshOpts.includeUVs ?? false)}`;
3425
3439
  if (useCache) {
3426
3440
  const cached = this.meshCache.get(meshKey);
3427
3441
  if (cached !== void 0) {
@@ -3455,15 +3469,38 @@ var Evaluator = class {
3455
3469
  }
3456
3470
  }
3457
3471
  }
3472
+ const tolerance = explicitTolerance ?? this.defaultToleranceFor(node, env, quality.tolerance);
3458
3473
  const shape = this.evaluate(node, env);
3459
3474
  if (!shape.ok) return shape;
3460
- const built = require_shapeTypes.withKernel(this.kernelId, () => require_meshFns.mesh(shape.value, meshOpts));
3475
+ const resolvedOpts = {
3476
+ ...meshOpts,
3477
+ tolerance,
3478
+ angularTolerance
3479
+ };
3480
+ let built;
3481
+ try {
3482
+ built = require_shapeTypes.withKernel(this.kernelId, () => require_meshFns.mesh(shape.value, resolvedOpts));
3483
+ } catch (e) {
3484
+ if (meshOpts.signal?.aborted) throw e;
3485
+ return require_errors.err(require_errors.kernelError(require_errors.BrepErrorCode.MESH_FAILED, `evaluateMesh: ${e instanceof Error ? e.message : String(e)}`, e));
3486
+ }
3461
3487
  if (useCache) {
3462
3488
  this.meshCache.set(meshKey, built);
3463
3489
  if (this.maxMeshCacheEntries !== void 0) this.trimMeshCache(this.maxMeshCacheEntries);
3464
3490
  }
3465
3491
  return require_errors.ok(built);
3466
3492
  }
3493
+ /** Scale-relative default deflection, derived from the placement-stripped
3494
+ * geometry so the resolved density is identical whether or not the
3495
+ * rigid-reuse path served the call: a rotated placement's axis-aligned
3496
+ * bounds inflate the diagonal by up to sqrt(3). The bounds are read while
3497
+ * the just-evaluated shape is live; only the number outlives it. */
3498
+ defaultToleranceFor(node, env, base) {
3499
+ const { inner } = peelRigid(node, env);
3500
+ const source = this.evaluate(inner, env);
3501
+ if (!source.ok) return base;
3502
+ return scaleDefaultTolerance(base, source.value);
3503
+ }
3467
3504
  evaluateInner(node, env) {
3468
3505
  const key = cacheKey(node, env, this.kernelId, this.defaultTolerance);
3469
3506
  const cached = this.cache.get(key);
@@ -4491,7 +4528,15 @@ function foldBuildVec(dim, comps) {
4491
4528
  nums[2]
4492
4529
  ]);
4493
4530
  }
4531
+ var optimized = /* @__PURE__ */ new WeakMap();
4494
4532
  function optimizeNode(n) {
4533
+ const cached = optimized.get(n);
4534
+ if (cached !== void 0) return cached;
4535
+ const out = rewriteNode(n);
4536
+ optimized.set(n, out);
4537
+ return out;
4538
+ }
4539
+ function rewriteNode(n) {
4495
4540
  switch (n.kind) {
4496
4541
  case "Box": return box$2(foldExpr(n.x), foldExpr(n.y), foldExpr(n.z));
4497
4542
  case "Sphere": return sphere$2(foldExpr(n.radius));
@@ -4609,13 +4654,16 @@ function optimizeTranslate(target, vector) {
4609
4654
  //#endregion
4610
4655
  //#region src/csg/edit.ts
4611
4656
  function replaceNode(root, pred, replacement) {
4612
- return walk(root, pred, replacement);
4657
+ return walk(root, pred, replacement, /* @__PURE__ */ new Map());
4613
4658
  }
4614
- function walk(node, pred, repl) {
4615
- if (pred(node)) return repl;
4616
- return rebuildChildren(node, pred, repl);
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;
4617
4665
  }
4618
- function rebuildChildren(n, pred, repl) {
4666
+ function rebuildChildren(n, pred, repl, memo) {
4619
4667
  switch (n.kind) {
4620
4668
  case "Box":
4621
4669
  case "Sphere":
@@ -4629,23 +4677,23 @@ function rebuildChildren(n, pred, repl) {
4629
4677
  case "Empty":
4630
4678
  case "Path":
4631
4679
  case "Profile": return n;
4632
- case "Fuse": return fuse$1(walk(n.a, pred, repl), walk(n.b, pred, repl), n.tolerance);
4633
- case "Cut": return cut$1(walk(n.a, pred, repl), walk(n.b, pred, repl), n.tolerance);
4634
- case "Intersect": return intersect$1(walk(n.a, pred, repl), walk(n.b, pred, repl), n.tolerance);
4635
- case "FuseAll": return fuseAll$1(n.shapes.map((c) => walk(c, pred, repl)), n.tolerance);
4636
- case "CutAll": return cutAll$1(walk(n.base, pred, repl), n.tools.map((c) => walk(c, pred, repl)), n.tolerance);
4637
- case "Translate": return translate$1(walk(n.target, pred, repl), n.vector);
4638
- case "Rotate": return rotate$1(walk(n.target, pred, repl), n.angle, {
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, {
4639
4687
  axis: n.axis,
4640
4688
  at: n.at
4641
4689
  });
4642
- case "Scale": return scale$2(walk(n.target, pred, repl), n.factor, { center: n.center });
4643
- case "Mirror": return mirror$1(walk(n.target, pred, repl), {
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), {
4644
4692
  normal: n.normal,
4645
4693
  at: n.at
4646
4694
  });
4647
- case "Compound": return compound$1(n.children.map((c) => walk(c, pred, repl)));
4648
- case "Instance": return instance$1(walk(n.source, pred, repl), n.placements, n.fuse);
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);
4649
4697
  case "Extrude":
4650
4698
  case "Revolve":
4651
4699
  case "Loft":
@@ -4653,27 +4701,35 @@ function rebuildChildren(n, pred, repl) {
4653
4701
  case "Color":
4654
4702
  case "Fillet":
4655
4703
  case "Chamfer":
4656
- case "Shell": return rebuildFeature(n, pred, repl);
4704
+ case "Shell": return rebuildFeature(n, pred, repl, memo);
4657
4705
  }
4658
4706
  }
4659
- function rebuildFeature(n, pred, repl) {
4707
+ function rebuildFeature(n, pred, repl, memo) {
4660
4708
  switch (n.kind) {
4661
- case "Extrude": return extrude$1(walk(n.profile, pred, repl), n.vector);
4662
- case "Revolve": return revolve$1(walk(n.profile, pred, repl), n.angle, {
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, {
4663
4711
  axis: n.axis,
4664
4712
  at: n.at
4665
4713
  });
4666
- case "Loft": return loft$1(n.sections.map((s) => walk(s, pred, repl)), { ruled: n.ruled });
4667
- case "Sweep": return sweep$2(walk(n.profile, pred, repl), walk(n.spine, pred, repl), { frenet: n.frenet });
4668
- case "Color": return color(walk(n.target, pred, repl), [...n.color]);
4669
- case "Fillet": return fillet$1(walk(n.target, pred, repl), n.ref, n.radius);
4670
- case "Chamfer": return chamfer$1(walk(n.target, pred, repl), n.ref, n.distance);
4671
- case "Shell": return shell$1(walk(n.target, pred, repl), n.refs, n.thickness);
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);
4672
4720
  }
4673
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. */
4674
4724
  function forEachNode(root, fn) {
4675
- fn(root);
4676
- for (const child of childrenOf(root)) forEachNode(child, 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);
4677
4733
  }
4678
4734
  function childrenOf(n) {
4679
4735
  switch (n.kind) {