brepjs 18.67.0 → 18.69.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/brepjs.cjs +365 -50
- package/dist/brepjs.js +358 -59
- package/dist/implicit/index.d.ts +13 -0
- package/dist/implicit/sdfFns.d.ts +136 -0
- package/dist/index.d.ts +2 -0
- package/dist/voxel/engine.d.ts +104 -0
- package/dist/voxel/fieldFns.d.ts +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Field-first implicit CAD domain (ADR-0013, brepjs-implicit Phase 1).
|
|
3
|
+
*
|
|
4
|
+
* An analytic SDF expression tree that rasterizes DIRECTLY into the voxel
|
|
5
|
+
* substrate's dense grid with no input mesh — the field-first twin of the
|
|
6
|
+
* mesh-first voxel path. Primitives compose through CSG / smooth / domain
|
|
7
|
+
* combinators into an {@link SdfHandle}, which rasterizes to a banded-SDF
|
|
8
|
+
* {@link VoxelFieldHandle} for contour / offset / shell.
|
|
9
|
+
*/
|
|
10
|
+
export type { SdfHandle, SdfBounds, SdfSweepOptions, ScalarFieldHandle, LatticeKind, } from './sdfFns.js';
|
|
11
|
+
export { sphere, box, roundedBox, cylinder, cone, capsule, torus, plane, sweep } from './sdfFns.js';
|
|
12
|
+
export { fieldConst, fieldAxialRamp, fieldRadialRamp, fieldFromSdf, fieldClamp } from './sdfFns.js';
|
|
13
|
+
export { lattice, strutLattice } from './sdfFns.js';
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { Result } from '../core/result.js';
|
|
2
|
+
import { WasmSdf, WasmScalarField } from '../voxel/engine.js';
|
|
3
|
+
import { VoxelFieldHandle, VoxelFieldOptions } from '../voxel/fieldFns.js';
|
|
4
|
+
/** Explicit world bounds `[min..max]` for {@link SdfHandle.rasterizeIn}. */
|
|
5
|
+
export interface SdfBounds {
|
|
6
|
+
min: [number, number, number];
|
|
7
|
+
max: [number, number, number];
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* A disposable handle around an analytic SDF expression. Every combinator clones
|
|
11
|
+
* into a NEW wasm node and returns a NEW handle (the wasm `Sdf` is a value, not a
|
|
12
|
+
* mutable builder), so an `SdfHandle` is immutable — the receiver stays valid and
|
|
13
|
+
* must be disposed independently. `rasterize` produces a banded-SDF
|
|
14
|
+
* {@link VoxelFieldHandle} you can boolean / offset / shell / contour.
|
|
15
|
+
*
|
|
16
|
+
* Dispose is mandatory: use `using`, or call `[Symbol.dispose]()`, to free the
|
|
17
|
+
* WASM expression tree. Intermediate handles in a chain (`a.union(b).shell(t)`)
|
|
18
|
+
* each own a wasm allocation; bind them to `using` or dispose them explicitly.
|
|
19
|
+
*/
|
|
20
|
+
export interface SdfHandle {
|
|
21
|
+
/** The wrapped WASM expression. Throws if the handle has been disposed. */
|
|
22
|
+
readonly value: WasmSdf;
|
|
23
|
+
/** Whether the backing WASM expression has been freed. */
|
|
24
|
+
readonly disposed: boolean;
|
|
25
|
+
[Symbol.dispose](): void;
|
|
26
|
+
union(other: SdfHandle): SdfHandle;
|
|
27
|
+
intersection(other: SdfHandle): SdfHandle;
|
|
28
|
+
difference(other: SdfHandle): SdfHandle;
|
|
29
|
+
smoothUnion(other: SdfHandle, k: number): SdfHandle;
|
|
30
|
+
smoothIntersection(other: SdfHandle, k: number): SdfHandle;
|
|
31
|
+
smoothDifference(other: SdfHandle, k: number): SdfHandle;
|
|
32
|
+
offset(distance: number): SdfHandle;
|
|
33
|
+
round(radius: number): SdfHandle;
|
|
34
|
+
shell(thickness: number): SdfHandle;
|
|
35
|
+
onion(thickness: number): SdfHandle;
|
|
36
|
+
offsetField(field: ScalarFieldHandle): SdfHandle;
|
|
37
|
+
roundField(field: ScalarFieldHandle): SdfHandle;
|
|
38
|
+
shellField(field: ScalarFieldHandle): SdfHandle;
|
|
39
|
+
smoothUnionField(other: SdfHandle, field: ScalarFieldHandle): SdfHandle;
|
|
40
|
+
translate(x: number, y: number, z: number): SdfHandle;
|
|
41
|
+
rotate(ax: number, ay: number, az: number, angle: number): SdfHandle;
|
|
42
|
+
scale(s: number): SdfHandle;
|
|
43
|
+
/** Rasterize into a banded-SDF field over the expression's analytic bounds. */
|
|
44
|
+
rasterize(opts?: VoxelFieldOptions): Result<VoxelFieldHandle>;
|
|
45
|
+
/** Rasterize over explicit world bounds (clips unbounded primitives). */
|
|
46
|
+
rasterizeIn(bounds: SdfBounds, opts?: VoxelFieldOptions): Result<VoxelFieldHandle>;
|
|
47
|
+
}
|
|
48
|
+
/** A sphere of radius `r`, centered at the origin. */
|
|
49
|
+
export declare function sphere(r: number, id?: string): Result<SdfHandle>;
|
|
50
|
+
/** An axis-aligned box of half-extents `(hx, hy, hz)`, centered at the origin. */
|
|
51
|
+
export declare function box(hx: number, hy: number, hz: number, id?: string): Result<SdfHandle>;
|
|
52
|
+
/** A box with rounded edges of radius `r`. */
|
|
53
|
+
export declare function roundedBox(hx: number, hy: number, hz: number, r: number, id?: string): Result<SdfHandle>;
|
|
54
|
+
/** A capped cylinder, axis +Z, radius `r`, total height `h`, centered at origin. */
|
|
55
|
+
export declare function cylinder(r: number, h: number, id?: string): Result<SdfHandle>;
|
|
56
|
+
/** A capped cone centered at the origin: base radius `r` at z = −h/2 tapering to an apex at z = +h/2. */
|
|
57
|
+
export declare function cone(r: number, h: number, id?: string): Result<SdfHandle>;
|
|
58
|
+
/** A capsule: a line segment `a`→`b` of radius `r`. */
|
|
59
|
+
export declare function capsule(a: [number, number, number], b: [number, number, number], r: number, id?: string): Result<SdfHandle>;
|
|
60
|
+
/** A torus in the XY plane (axis +Z): a `minor`-radius tube on a `major` circle. */
|
|
61
|
+
export declare function torus(major: number, minor: number, id?: string): Result<SdfHandle>;
|
|
62
|
+
/** A half-space: the plane through `h·n` with outward normal `n` (normalized). */
|
|
63
|
+
export declare function plane(n: [number, number, number], h: number, id?: string): Result<SdfHandle>;
|
|
64
|
+
/** Options for {@link sweep}. */
|
|
65
|
+
export interface SdfSweepOptions {
|
|
66
|
+
/** Close the spine into a loop, skipping the open-end caps. Default `false`. */
|
|
67
|
+
closed?: boolean;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Sweep an in-plane `profile` along a `spine` polyline using rotation-minimizing
|
|
71
|
+
* frames (no 180° flip at inflections). The profile is sampled in its own
|
|
72
|
+
* `(normal, binormal)` plane at every station; `opts.closed` loops the spine and
|
|
73
|
+
* skips the open-end caps. The result is a pseudo-SDF (exact distance only near
|
|
74
|
+
* the swept wall), which contours cleanly. `spine` needs at least 2 finite points.
|
|
75
|
+
*/
|
|
76
|
+
export declare function sweep(spine: [number, number, number][], profile: SdfHandle, opts?: SdfSweepOptions, id?: string): Result<SdfHandle>;
|
|
77
|
+
/** TPMS family selector for {@link lattice}. */
|
|
78
|
+
export type LatticeKind = 'gyroid' | 'schwarzP' | 'diamond';
|
|
79
|
+
/**
|
|
80
|
+
* A graded/conformal TPMS lattice: `|f(p)| − ½·thickness(p)` for the chosen `kind`
|
|
81
|
+
* (negative = strut material), with `period` and `thickness` GRADED per-position via
|
|
82
|
+
* {@link ScalarFieldHandle}. A {@link fieldConst} period/thickness reproduces a
|
|
83
|
+
* uniform lattice. The field is Lipschitz and APPROXIMATE (not a true SDF).
|
|
84
|
+
*
|
|
85
|
+
* The lattice is INFINITE/periodic, so it must be clipped to a bounded region
|
|
86
|
+
* (`lattice.intersection(region)`) before rasterizing — that conformal clip is what
|
|
87
|
+
* frames a finite grid. NOTE: grading the PERIOD is an approximation (a
|
|
88
|
+
* spatially-varying period isn't strictly periodic); grading THICKNESS is the
|
|
89
|
+
* well-behaved primary knob.
|
|
90
|
+
*/
|
|
91
|
+
export declare function lattice(kind: LatticeKind, period: ScalarFieldHandle, thickness: ScalarFieldHandle, id?: string): Result<SdfHandle>;
|
|
92
|
+
/**
|
|
93
|
+
* A cubic beam/strut lattice: axis-aligned cylindrical struts on a `period`-spaced
|
|
94
|
+
* cubic grid, with the strut `radius` GRADED per-position via
|
|
95
|
+
* {@link ScalarFieldHandle}. Periodic/infinite — clip to a bounded region
|
|
96
|
+
* (`strut.intersection(region)`) before rasterizing.
|
|
97
|
+
*/
|
|
98
|
+
export declare function strutLattice(period: number, radius: ScalarFieldHandle, id?: string): Result<SdfHandle>;
|
|
99
|
+
/**
|
|
100
|
+
* A disposable handle around a position-varying scalar field (brepjs-implicit Phase
|
|
101
|
+
* 2b). Fed to the {@link SdfHandle} modulated operators (`offsetField`, `shellField`,
|
|
102
|
+
* …) to vary an operator parameter per voxel. Like {@link SdfHandle} it is a value —
|
|
103
|
+
* the constructors return fresh fields — and dispose is mandatory (`using`, or
|
|
104
|
+
* `[Symbol.dispose]()`) to free the WASM allocation.
|
|
105
|
+
*/
|
|
106
|
+
export interface ScalarFieldHandle {
|
|
107
|
+
/** The wrapped WASM field. Throws if the handle has been disposed. */
|
|
108
|
+
readonly value: WasmScalarField;
|
|
109
|
+
/** Whether the backing WASM field has been freed. */
|
|
110
|
+
readonly disposed: boolean;
|
|
111
|
+
[Symbol.dispose](): void;
|
|
112
|
+
}
|
|
113
|
+
/** A spatially constant field — reproduces a constant operator parameter exactly. */
|
|
114
|
+
export declare function fieldConst(c: number, id?: string): Result<ScalarFieldHandle>;
|
|
115
|
+
/**
|
|
116
|
+
* A field that ramps `lo → hi` as `coord[axis]` goes `a → b`, clamped to the
|
|
117
|
+
* endpoint band outside `[a, b]`. `axis` is 0 (x), 1 (y), or 2 (z).
|
|
118
|
+
*/
|
|
119
|
+
export declare function fieldAxialRamp(axis: number, a: number, b: number, lo: number, hi: number, id?: string): Result<ScalarFieldHandle>;
|
|
120
|
+
/**
|
|
121
|
+
* A field by radial distance from the line through `center` along `axis`: `lo → hi`
|
|
122
|
+
* as that distance goes `r0 → r1`, clamped. `axis` is 0 (x), 1 (y), or 2 (z).
|
|
123
|
+
*/
|
|
124
|
+
export declare function fieldRadialRamp(center: [number, number, number], axis: number, r0: number, r1: number, lo: number, hi: number, id?: string): Result<ScalarFieldHandle>;
|
|
125
|
+
/**
|
|
126
|
+
* A field from an {@link SdfHandle}'s signed distance, affinely remapped to
|
|
127
|
+
* `sdf.eval(p) * scale + offset`. UNBOUNDED — drive a bounds-affecting op
|
|
128
|
+
* (`offsetField`/`shellField`) with it only via `rasterizeIn` or wrapped in
|
|
129
|
+
* {@link fieldClamp}.
|
|
130
|
+
*/
|
|
131
|
+
export declare function fieldFromSdf(sdf: SdfHandle, scale: number, offset: number, id?: string): Result<ScalarFieldHandle>;
|
|
132
|
+
/**
|
|
133
|
+
* Clamp another field's value to `[min, max]` — bounds an otherwise unbounded
|
|
134
|
+
* {@link fieldFromSdf} so it can safely drive offset/shell.
|
|
135
|
+
*/
|
|
136
|
+
export declare function fieldClamp(field: ScalarFieldHandle, min: number, max: number, id?: string): Result<ScalarFieldHandle>;
|
package/dist/index.d.ts
CHANGED
|
@@ -40,6 +40,8 @@ export { importSVGPathD, importSVG, type SVGImportOptions } from './io/svgImport
|
|
|
40
40
|
export { exportSTEPConfigured, type StepExportOptions, type StepExportPart, } from './io/stepConfigFns.js';
|
|
41
41
|
export { initVoxel, registerVoxel, getVoxel, getActiveVoxelId, windingNumbers, pointsInside, repairMesh, offsetMesh, shellMesh, voxelBoolean, offsetShape, shellShape, voxelBooleanShapes, voxelField, voxelBooleanField, fieldBoolean, fieldOffset, fieldShell, fieldReinit, fieldContour, voxelFieldFromShape, voxelBooleanFieldShapes, shapeToMeshInput, } from './voxel/index.js';
|
|
42
42
|
export type { VoxelEngine, VoxelMeshInput, VoxelRepairResult, RepairOptions, VoxelOpOptions, VoxelFieldHandle, VoxelFieldOptions, VoxelBooleanOp, } from './voxel/index.js';
|
|
43
|
+
export { sphere as sdfSphere, box as sdfBox, roundedBox as sdfRoundedBox, cylinder as sdfCylinder, cone as sdfCone, capsule as sdfCapsule, torus as sdfTorus, plane as sdfPlane, sweep as sdfSweep, fieldConst as sdfFieldConst, fieldAxialRamp as sdfFieldAxialRamp, fieldRadialRamp as sdfFieldRadialRamp, fieldFromSdf as sdfFieldFromSdf, fieldClamp as sdfFieldClamp, lattice as sdfLattice, strutLattice as sdfStrutLattice, } from './implicit/index.js';
|
|
44
|
+
export type { SdfHandle, SdfBounds, SdfSweepOptions, ScalarFieldHandle, LatticeKind as SdfLatticeKind, } from './implicit/index.js';
|
|
43
45
|
export { latticeInfill, latticeInfillShape, tpmsLattice } from './lattice/index.js';
|
|
44
46
|
export type { LatticeType, LatticeOptions, LatticeBounds } from './lattice/index.js';
|
|
45
47
|
export { default as Sketcher } from './sketching/sketcher.js';
|
package/dist/voxel/engine.d.ts
CHANGED
|
@@ -29,9 +29,113 @@ export interface VoxelEngine {
|
|
|
29
29
|
* satisfied by the generated `VoxelField` wasm-bindgen class.
|
|
30
30
|
*/
|
|
31
31
|
VoxelField: WasmVoxelFieldConstructor;
|
|
32
|
+
/**
|
|
33
|
+
* Field-first analytic SDF builder (ADR-0013). Static primitive constructors
|
|
34
|
+
* and combinator methods compose an opaque expression tree that rasterizes
|
|
35
|
+
* directly into a {@link WasmVoxelField} with no input mesh. Structurally
|
|
36
|
+
* satisfied by the generated `Sdf` wasm-bindgen class.
|
|
37
|
+
*/
|
|
38
|
+
Sdf: WasmSdfConstructor;
|
|
39
|
+
/**
|
|
40
|
+
* Position-varying scalar fields (brepjs-implicit Phase 2b). Static constructors
|
|
41
|
+
* compose an opaque field that the `Sdf` modulated operators sample per voxel.
|
|
42
|
+
* Structurally satisfied by the generated `ScalarField` wasm-bindgen class.
|
|
43
|
+
*/
|
|
44
|
+
ScalarField: WasmScalarFieldConstructor;
|
|
32
45
|
/** Engine artifact version, for loader/artifact compatibility checks. */
|
|
33
46
|
version(): string;
|
|
34
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Static surface of the wasm `ScalarField` class: the constructors that seed a
|
|
50
|
+
* position-varying field. Each returns a fresh {@link WasmScalarField}; the ramp
|
|
51
|
+
* constructors throw (as a JS exception) on an out-of-range axis. Structurally
|
|
52
|
+
* satisfied by the generated `ScalarField` class.
|
|
53
|
+
*/
|
|
54
|
+
export interface WasmScalarFieldConstructor {
|
|
55
|
+
constant(c: number): WasmScalarField;
|
|
56
|
+
axial_ramp(axis: number, a: number, b: number, lo: number, hi: number): WasmScalarField;
|
|
57
|
+
radial_ramp(cx: number, cy: number, cz: number, axis: number, r0: number, r1: number, lo: number, hi: number): WasmScalarField;
|
|
58
|
+
from_sdf(sdf: WasmSdf, scale: number, offset: number): WasmScalarField;
|
|
59
|
+
clamp(field: WasmScalarField, min: number, max: number): WasmScalarField;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* An opaque position-varying scalar field. A value, not a builder (each constructor
|
|
63
|
+
* returns a fresh field). Fed to the `Sdf` modulated operators
|
|
64
|
+
* ({@link WasmSdf.offset_field} et al.) to vary an operator parameter per voxel.
|
|
65
|
+
* `free()` releases the backing WASM allocation. Structurally satisfied by the
|
|
66
|
+
* generated `ScalarField` wasm-bindgen class.
|
|
67
|
+
*/
|
|
68
|
+
export interface WasmScalarField {
|
|
69
|
+
/** Release the backing WASM allocation (wasm-bindgen lifecycle). */
|
|
70
|
+
free(): void;
|
|
71
|
+
[Symbol.dispose](): void;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Static surface of the wasm `Sdf` class: the primitive constructors that seed an
|
|
75
|
+
* expression tree (centered at the origin unless noted). Each returns a fresh
|
|
76
|
+
* {@link WasmSdf}. Structurally satisfied by the generated `Sdf` class.
|
|
77
|
+
*/
|
|
78
|
+
export interface WasmSdfConstructor {
|
|
79
|
+
sphere(r: number): WasmSdf;
|
|
80
|
+
box_(hx: number, hy: number, hz: number): WasmSdf;
|
|
81
|
+
rounded_box(hx: number, hy: number, hz: number, r: number): WasmSdf;
|
|
82
|
+
cylinder(r: number, h: number): WasmSdf;
|
|
83
|
+
cone(r: number, h: number): WasmSdf;
|
|
84
|
+
capsule(ax: number, ay: number, az: number, bx: number, by: number, bz: number, r: number): WasmSdf;
|
|
85
|
+
torus(major: number, minor: number): WasmSdf;
|
|
86
|
+
plane(nx: number, ny: number, nz: number, h: number): WasmSdf;
|
|
87
|
+
/**
|
|
88
|
+
* Sweep an in-plane `profile` along `spine` (flat xyz, length 3·N, N >= 2)
|
|
89
|
+
* using rotation-minimizing frames. `closed` skips the end caps. Throws (as a
|
|
90
|
+
* JS exception) on fewer than two stations or a degenerate spine.
|
|
91
|
+
*/
|
|
92
|
+
sweep(spine: Float64Array, profile: WasmSdf, closed: boolean): WasmSdf;
|
|
93
|
+
/**
|
|
94
|
+
* A graded/conformal TPMS lattice node (`kind_tag`: 0=Gyroid, 1=SchwarzP,
|
|
95
|
+
* 2=Diamond) with per-position `period`/`thickness`. Periodic/infinite — clip via
|
|
96
|
+
* `intersection` before rasterize. Throws (as a JS exception) on a bad `kind_tag`.
|
|
97
|
+
*/
|
|
98
|
+
lattice(kind_tag: number, period: WasmScalarField, thickness: WasmScalarField): WasmSdf;
|
|
99
|
+
/**
|
|
100
|
+
* A cubic beam/strut lattice node with per-position `radius`. Periodic/infinite —
|
|
101
|
+
* clip via `intersection` before rasterize. Throws (as a JS exception) on a
|
|
102
|
+
* non-positive `period`.
|
|
103
|
+
*/
|
|
104
|
+
strut_lattice(period: number, radius: WasmScalarField): WasmSdf;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* An opaque analytic SDF expression. Every combinator CLONES into a new node and
|
|
108
|
+
* returns a fresh `WasmSdf` (wasm-bindgen has no shared borrow across calls), so an
|
|
109
|
+
* `Sdf` is a value, not a mutable builder. `rasterize` builds a banded-SDF
|
|
110
|
+
* {@link WasmVoxelField}; `free()` releases the backing WASM expression tree.
|
|
111
|
+
* Structurally satisfied by the generated `Sdf` wasm-bindgen class.
|
|
112
|
+
*/
|
|
113
|
+
export interface WasmSdf {
|
|
114
|
+
union(other: WasmSdf): WasmSdf;
|
|
115
|
+
intersection(other: WasmSdf): WasmSdf;
|
|
116
|
+
difference(other: WasmSdf): WasmSdf;
|
|
117
|
+
smooth_union(other: WasmSdf, k: number): WasmSdf;
|
|
118
|
+
smooth_intersection(other: WasmSdf, k: number): WasmSdf;
|
|
119
|
+
smooth_difference(other: WasmSdf, k: number): WasmSdf;
|
|
120
|
+
offset(d: number): WasmSdf;
|
|
121
|
+
round(r: number): WasmSdf;
|
|
122
|
+
shell(t: number): WasmSdf;
|
|
123
|
+
onion(t: number): WasmSdf;
|
|
124
|
+
offset_field(f: WasmScalarField): WasmSdf;
|
|
125
|
+
round_field(f: WasmScalarField): WasmSdf;
|
|
126
|
+
shell_field(f: WasmScalarField): WasmSdf;
|
|
127
|
+
smooth_union_field(other: WasmSdf, k: WasmScalarField): WasmSdf;
|
|
128
|
+
translate(x: number, y: number, z: number): WasmSdf;
|
|
129
|
+
rotate(ax: number, ay: number, az: number, angle: number): WasmSdf;
|
|
130
|
+
scale(s: number): WasmSdf;
|
|
131
|
+
/** Rasterize into a banded-SDF dense field over the expression's analytic bounds. */
|
|
132
|
+
rasterize(resolution: number, padding: number): WasmVoxelField;
|
|
133
|
+
/** Rasterize over explicit `[min..max]` bounds (clips unbounded primitives). */
|
|
134
|
+
rasterize_in(min_x: number, min_y: number, min_z: number, max_x: number, max_y: number, max_z: number, resolution: number, padding: number): WasmVoxelField;
|
|
135
|
+
/** Release the backing WASM expression-tree allocation (wasm-bindgen lifecycle). */
|
|
136
|
+
free(): void;
|
|
137
|
+
[Symbol.dispose](): void;
|
|
138
|
+
}
|
|
35
139
|
/**
|
|
36
140
|
* Constructor of the wasm `VoxelField` class. `new VoxelField(verts, tris, res,
|
|
37
141
|
* padding)` voxelizes a mesh into a persistent dense field. Throws (as a JS
|
package/dist/voxel/fieldFns.d.ts
CHANGED
|
@@ -45,6 +45,7 @@ export interface VoxelFieldHandle {
|
|
|
45
45
|
/** Surface-Nets contour the current field to a mesh (the field stays alive). */
|
|
46
46
|
contour(): KernelMeshResult;
|
|
47
47
|
}
|
|
48
|
+
export declare function makeFieldHandle(raw: WasmVoxelField): VoxelFieldHandle;
|
|
48
49
|
/**
|
|
49
50
|
* Voxelize a mesh into a persistent dense {@link VoxelFieldHandle}: one grid you
|
|
50
51
|
* can boolean / offset / shell / reinit in place, then contour once. The handle
|