brepjs 18.151.2 → 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.
- package/README.md +24 -0
- package/bin/brepjs.mjs +37 -4
- package/dist/brepjs.cjs +47 -28
- package/dist/brepjs.js +47 -28
- package/dist/csg/edit.d.ts +2 -0
- 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
|
-
|
|
26
|
-
|
|
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:
|
|
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/brepjs.cjs
CHANGED
|
@@ -4528,7 +4528,15 @@ function foldBuildVec(dim, comps) {
|
|
|
4528
4528
|
nums[2]
|
|
4529
4529
|
]);
|
|
4530
4530
|
}
|
|
4531
|
+
var optimized = /* @__PURE__ */ new WeakMap();
|
|
4531
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) {
|
|
4532
4540
|
switch (n.kind) {
|
|
4533
4541
|
case "Box": return box$2(foldExpr(n.x), foldExpr(n.y), foldExpr(n.z));
|
|
4534
4542
|
case "Sphere": return sphere$2(foldExpr(n.radius));
|
|
@@ -4646,13 +4654,16 @@ function optimizeTranslate(target, vector) {
|
|
|
4646
4654
|
//#endregion
|
|
4647
4655
|
//#region src/csg/edit.ts
|
|
4648
4656
|
function replaceNode(root, pred, replacement) {
|
|
4649
|
-
return walk(root, pred, replacement);
|
|
4657
|
+
return walk(root, pred, replacement, /* @__PURE__ */ new Map());
|
|
4650
4658
|
}
|
|
4651
|
-
function walk(node, pred, repl) {
|
|
4652
|
-
|
|
4653
|
-
|
|
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;
|
|
4654
4665
|
}
|
|
4655
|
-
function rebuildChildren(n, pred, repl) {
|
|
4666
|
+
function rebuildChildren(n, pred, repl, memo) {
|
|
4656
4667
|
switch (n.kind) {
|
|
4657
4668
|
case "Box":
|
|
4658
4669
|
case "Sphere":
|
|
@@ -4666,23 +4677,23 @@ function rebuildChildren(n, pred, repl) {
|
|
|
4666
4677
|
case "Empty":
|
|
4667
4678
|
case "Path":
|
|
4668
4679
|
case "Profile": return n;
|
|
4669
|
-
case "Fuse": return fuse$1(walk(n.a, pred, repl), walk(n.b, pred, repl), n.tolerance);
|
|
4670
|
-
case "Cut": return cut$1(walk(n.a, pred, repl), walk(n.b, pred, repl), n.tolerance);
|
|
4671
|
-
case "Intersect": return intersect$1(walk(n.a, pred, repl), walk(n.b, pred, repl), n.tolerance);
|
|
4672
|
-
case "FuseAll": return fuseAll$1(n.shapes.map((c) => walk(c, pred, repl)), n.tolerance);
|
|
4673
|
-
case "CutAll": return cutAll$1(walk(n.base, pred, repl), n.tools.map((c) => walk(c, pred, repl)), n.tolerance);
|
|
4674
|
-
case "Translate": return translate$1(walk(n.target, pred, repl), n.vector);
|
|
4675
|
-
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, {
|
|
4676
4687
|
axis: n.axis,
|
|
4677
4688
|
at: n.at
|
|
4678
4689
|
});
|
|
4679
|
-
case "Scale": return scale$2(walk(n.target, pred, repl), n.factor, { center: n.center });
|
|
4680
|
-
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), {
|
|
4681
4692
|
normal: n.normal,
|
|
4682
4693
|
at: n.at
|
|
4683
4694
|
});
|
|
4684
|
-
case "Compound": return compound$1(n.children.map((c) => walk(c, pred, repl)));
|
|
4685
|
-
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);
|
|
4686
4697
|
case "Extrude":
|
|
4687
4698
|
case "Revolve":
|
|
4688
4699
|
case "Loft":
|
|
@@ -4690,27 +4701,35 @@ function rebuildChildren(n, pred, repl) {
|
|
|
4690
4701
|
case "Color":
|
|
4691
4702
|
case "Fillet":
|
|
4692
4703
|
case "Chamfer":
|
|
4693
|
-
case "Shell": return rebuildFeature(n, pred, repl);
|
|
4704
|
+
case "Shell": return rebuildFeature(n, pred, repl, memo);
|
|
4694
4705
|
}
|
|
4695
4706
|
}
|
|
4696
|
-
function rebuildFeature(n, pred, repl) {
|
|
4707
|
+
function rebuildFeature(n, pred, repl, memo) {
|
|
4697
4708
|
switch (n.kind) {
|
|
4698
|
-
case "Extrude": return extrude$1(walk(n.profile, pred, repl), n.vector);
|
|
4699
|
-
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, {
|
|
4700
4711
|
axis: n.axis,
|
|
4701
4712
|
at: n.at
|
|
4702
4713
|
});
|
|
4703
|
-
case "Loft": return loft$1(n.sections.map((s) => walk(s, pred, repl)), { ruled: n.ruled });
|
|
4704
|
-
case "Sweep": return sweep$2(walk(n.profile, pred, repl), walk(n.spine, pred, repl), { frenet: n.frenet });
|
|
4705
|
-
case "Color": return color(walk(n.target, pred, repl), [...n.color]);
|
|
4706
|
-
case "Fillet": return fillet$1(walk(n.target, pred, repl), n.ref, n.radius);
|
|
4707
|
-
case "Chamfer": return chamfer$1(walk(n.target, pred, repl), n.ref, n.distance);
|
|
4708
|
-
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);
|
|
4709
4720
|
}
|
|
4710
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. */
|
|
4711
4724
|
function forEachNode(root, fn) {
|
|
4712
|
-
|
|
4713
|
-
|
|
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);
|
|
4714
4733
|
}
|
|
4715
4734
|
function childrenOf(n) {
|
|
4716
4735
|
switch (n.kind) {
|
package/dist/brepjs.js
CHANGED
|
@@ -4538,7 +4538,15 @@ function foldBuildVec(dim, comps) {
|
|
|
4538
4538
|
nums[2]
|
|
4539
4539
|
]);
|
|
4540
4540
|
}
|
|
4541
|
+
var optimized = /* @__PURE__ */ new WeakMap();
|
|
4541
4542
|
function optimizeNode(n) {
|
|
4543
|
+
const cached = optimized.get(n);
|
|
4544
|
+
if (cached !== void 0) return cached;
|
|
4545
|
+
const out = rewriteNode(n);
|
|
4546
|
+
optimized.set(n, out);
|
|
4547
|
+
return out;
|
|
4548
|
+
}
|
|
4549
|
+
function rewriteNode(n) {
|
|
4542
4550
|
switch (n.kind) {
|
|
4543
4551
|
case "Box": return box$2(foldExpr(n.x), foldExpr(n.y), foldExpr(n.z));
|
|
4544
4552
|
case "Sphere": return sphere$2(foldExpr(n.radius));
|
|
@@ -4656,13 +4664,16 @@ function optimizeTranslate(target, vector) {
|
|
|
4656
4664
|
//#endregion
|
|
4657
4665
|
//#region src/csg/edit.ts
|
|
4658
4666
|
function replaceNode(root, pred, replacement) {
|
|
4659
|
-
return walk(root, pred, replacement);
|
|
4667
|
+
return walk(root, pred, replacement, /* @__PURE__ */ new Map());
|
|
4660
4668
|
}
|
|
4661
|
-
function walk(node, pred, repl) {
|
|
4662
|
-
|
|
4663
|
-
|
|
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;
|
|
4664
4675
|
}
|
|
4665
|
-
function rebuildChildren(n, pred, repl) {
|
|
4676
|
+
function rebuildChildren(n, pred, repl, memo) {
|
|
4666
4677
|
switch (n.kind) {
|
|
4667
4678
|
case "Box":
|
|
4668
4679
|
case "Sphere":
|
|
@@ -4676,23 +4687,23 @@ function rebuildChildren(n, pred, repl) {
|
|
|
4676
4687
|
case "Empty":
|
|
4677
4688
|
case "Path":
|
|
4678
4689
|
case "Profile": return n;
|
|
4679
|
-
case "Fuse": return fuse$1(walk(n.a, pred, repl), walk(n.b, pred, repl), n.tolerance);
|
|
4680
|
-
case "Cut": return cut$1(walk(n.a, pred, repl), walk(n.b, pred, repl), n.tolerance);
|
|
4681
|
-
case "Intersect": return intersect$1(walk(n.a, pred, repl), walk(n.b, pred, repl), n.tolerance);
|
|
4682
|
-
case "FuseAll": return fuseAll$1(n.shapes.map((c) => walk(c, pred, repl)), n.tolerance);
|
|
4683
|
-
case "CutAll": return cutAll$1(walk(n.base, pred, repl), n.tools.map((c) => walk(c, pred, repl)), n.tolerance);
|
|
4684
|
-
case "Translate": return translate$1(walk(n.target, pred, repl), n.vector);
|
|
4685
|
-
case "Rotate": return rotate$1(walk(n.target, pred, repl), n.angle, {
|
|
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, {
|
|
4686
4697
|
axis: n.axis,
|
|
4687
4698
|
at: n.at
|
|
4688
4699
|
});
|
|
4689
|
-
case "Scale": return scale$2(walk(n.target, pred, repl), n.factor, { center: n.center });
|
|
4690
|
-
case "Mirror": return mirror$1(walk(n.target, pred, repl), {
|
|
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), {
|
|
4691
4702
|
normal: n.normal,
|
|
4692
4703
|
at: n.at
|
|
4693
4704
|
});
|
|
4694
|
-
case "Compound": return compound$1(n.children.map((c) => walk(c, pred, repl)));
|
|
4695
|
-
case "Instance": return instance$1(walk(n.source, pred, repl), n.placements, n.fuse);
|
|
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);
|
|
4696
4707
|
case "Extrude":
|
|
4697
4708
|
case "Revolve":
|
|
4698
4709
|
case "Loft":
|
|
@@ -4700,27 +4711,35 @@ function rebuildChildren(n, pred, repl) {
|
|
|
4700
4711
|
case "Color":
|
|
4701
4712
|
case "Fillet":
|
|
4702
4713
|
case "Chamfer":
|
|
4703
|
-
case "Shell": return rebuildFeature(n, pred, repl);
|
|
4714
|
+
case "Shell": return rebuildFeature(n, pred, repl, memo);
|
|
4704
4715
|
}
|
|
4705
4716
|
}
|
|
4706
|
-
function rebuildFeature(n, pred, repl) {
|
|
4717
|
+
function rebuildFeature(n, pred, repl, memo) {
|
|
4707
4718
|
switch (n.kind) {
|
|
4708
|
-
case "Extrude": return extrude$1(walk(n.profile, pred, repl), n.vector);
|
|
4709
|
-
case "Revolve": return revolve$1(walk(n.profile, pred, repl), n.angle, {
|
|
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, {
|
|
4710
4721
|
axis: n.axis,
|
|
4711
4722
|
at: n.at
|
|
4712
4723
|
});
|
|
4713
|
-
case "Loft": return loft$1(n.sections.map((s) => walk(s, pred, repl)), { ruled: n.ruled });
|
|
4714
|
-
case "Sweep": return sweep$2(walk(n.profile, pred, repl), walk(n.spine, pred, repl), { frenet: n.frenet });
|
|
4715
|
-
case "Color": return color(walk(n.target, pred, repl), [...n.color]);
|
|
4716
|
-
case "Fillet": return fillet$1(walk(n.target, pred, repl), n.ref, n.radius);
|
|
4717
|
-
case "Chamfer": return chamfer$1(walk(n.target, pred, repl), n.ref, n.distance);
|
|
4718
|
-
case "Shell": return shell$1(walk(n.target, pred, repl), n.refs, n.thickness);
|
|
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);
|
|
4719
4730
|
}
|
|
4720
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. */
|
|
4721
4734
|
function forEachNode(root, fn) {
|
|
4722
|
-
|
|
4723
|
-
|
|
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);
|
|
4724
4743
|
}
|
|
4725
4744
|
function childrenOf(n) {
|
|
4726
4745
|
switch (n.kind) {
|
package/dist/csg/edit.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { IRNode } from './types.js';
|
|
2
2
|
export type NodePredicate = (node: IRNode) => boolean;
|
|
3
3
|
export declare function replaceNode(root: IRNode, pred: NodePredicate, replacement: IRNode): IRNode;
|
|
4
|
+
/** Visits each DISTINCT node once (identity-deduplicated), so a shared
|
|
5
|
+
* subtree is reported once and a self-referencing DAG walks in linear time. */
|
|
4
6
|
export declare function forEachNode(root: IRNode, fn: (node: IRNode) => void): void;
|
|
5
7
|
export declare function nodeCount(root: IRNode): number;
|