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
package/dist/brepjs.cjs
CHANGED
|
@@ -182,7 +182,7 @@ function validateInputs(mesh, queries) {
|
|
|
182
182
|
return null;
|
|
183
183
|
}
|
|
184
184
|
/** Resolve a registered voxel engine, mapping an unregistered id to an error. */
|
|
185
|
-
function resolveEngine(id) {
|
|
185
|
+
function resolveEngine$1(id) {
|
|
186
186
|
try {
|
|
187
187
|
return require_errors.ok(getVoxel(id));
|
|
188
188
|
} catch (cause) {
|
|
@@ -199,7 +199,7 @@ function resolveEngine(id) {
|
|
|
199
199
|
function windingNumbers(mesh, queries, id) {
|
|
200
200
|
const invalid = validateInputs(mesh, queries);
|
|
201
201
|
if (invalid) return require_errors.err(invalid);
|
|
202
|
-
const engine = resolveEngine(id);
|
|
202
|
+
const engine = resolveEngine$1(id);
|
|
203
203
|
if (require_errors.isErr(engine)) return engine;
|
|
204
204
|
return require_errors.ok(engine.value.winding_numbers(mesh.vertices, mesh.triangles, queries));
|
|
205
205
|
}
|
|
@@ -211,15 +211,15 @@ function windingNumbers(mesh, queries, id) {
|
|
|
211
211
|
function pointsInside(mesh, queries, id) {
|
|
212
212
|
const invalid = validateInputs(mesh, queries);
|
|
213
213
|
if (invalid) return require_errors.err(invalid);
|
|
214
|
-
const engine = resolveEngine(id);
|
|
214
|
+
const engine = resolveEngine$1(id);
|
|
215
215
|
if (require_errors.isErr(engine)) return engine;
|
|
216
216
|
const flags = engine.value.points_inside(mesh.vertices, mesh.triangles, queries);
|
|
217
217
|
return require_errors.ok(Array.from(flags, (flag) => flag === 1));
|
|
218
218
|
}
|
|
219
219
|
//#endregion
|
|
220
220
|
//#region src/voxel/repairFns.ts
|
|
221
|
-
var DEFAULT_RESOLUTION$
|
|
222
|
-
var DEFAULT_PADDING$
|
|
221
|
+
var DEFAULT_RESOLUTION$4 = 48;
|
|
222
|
+
var DEFAULT_PADDING$4 = 2;
|
|
223
223
|
/**
|
|
224
224
|
* Repair a (possibly non-watertight) triangle-soup mesh into a closed surface:
|
|
225
225
|
* voxelize an FWN-signed SDF, then Surface-Nets contour it back to triangles.
|
|
@@ -232,11 +232,11 @@ function repairMesh(mesh, opts, id) {
|
|
|
232
232
|
const invalid = validateMesh(mesh);
|
|
233
233
|
if (invalid) return require_errors.err(invalid);
|
|
234
234
|
if (mesh.vertices.length === 0 || mesh.triangles.length === 0) return require_errors.err(require_errors.validationError("VOXEL_EMPTY_MESH", "repairMesh requires a non-empty triangle mesh."));
|
|
235
|
-
const resolution = opts?.resolution ?? DEFAULT_RESOLUTION$
|
|
236
|
-
const padding = opts?.padding ?? DEFAULT_PADDING$
|
|
235
|
+
const resolution = opts?.resolution ?? DEFAULT_RESOLUTION$4;
|
|
236
|
+
const padding = opts?.padding ?? DEFAULT_PADDING$4;
|
|
237
237
|
if (!Number.isInteger(resolution) || resolution < 1) return require_errors.err(require_errors.validationError("VOXEL_INVALID_RESOLUTION", "resolution must be an integer >= 1."));
|
|
238
238
|
if (!Number.isInteger(padding) || padding < 1) return require_errors.err(require_errors.validationError("VOXEL_INVALID_PADDING", "padding must be an integer >= 1."));
|
|
239
|
-
const engine = resolveEngine(id);
|
|
239
|
+
const engine = resolveEngine$1(id);
|
|
240
240
|
if (require_errors.isErr(engine)) return engine;
|
|
241
241
|
try {
|
|
242
242
|
try {
|
|
@@ -286,16 +286,16 @@ function shapeToMeshInput(shape, deflection = DEFAULT_DEFLECTION) {
|
|
|
286
286
|
}
|
|
287
287
|
//#endregion
|
|
288
288
|
//#region src/voxel/meshOpsFns.ts
|
|
289
|
-
var DEFAULT_RESOLUTION$
|
|
290
|
-
var DEFAULT_PADDING$
|
|
289
|
+
var DEFAULT_RESOLUTION$3 = 48;
|
|
290
|
+
var DEFAULT_PADDING$3 = 2;
|
|
291
291
|
var BOOLEAN_OP_CODES$1 = {
|
|
292
292
|
union: 0,
|
|
293
293
|
intersection: 1,
|
|
294
294
|
difference: 2
|
|
295
295
|
};
|
|
296
|
-
function resolveGridParams$
|
|
297
|
-
const resolution = opts?.resolution ?? DEFAULT_RESOLUTION$
|
|
298
|
-
const padding = opts?.padding ?? DEFAULT_PADDING$
|
|
296
|
+
function resolveGridParams$2(opts) {
|
|
297
|
+
const resolution = opts?.resolution ?? DEFAULT_RESOLUTION$3;
|
|
298
|
+
const padding = opts?.padding ?? DEFAULT_PADDING$3;
|
|
299
299
|
if (!Number.isInteger(resolution) || resolution < 1) return require_errors.err(require_errors.validationError("VOXEL_INVALID_RESOLUTION", "resolution must be an integer >= 1."));
|
|
300
300
|
if (!Number.isInteger(padding) || padding < 1) return require_errors.err(require_errors.validationError("VOXEL_INVALID_PADDING", "padding must be an integer >= 1."));
|
|
301
301
|
return require_errors.ok({
|
|
@@ -330,9 +330,9 @@ function offsetMesh(mesh, distance, opts, id) {
|
|
|
330
330
|
if (invalid) return require_errors.err(invalid);
|
|
331
331
|
if (mesh.vertices.length === 0 || mesh.triangles.length === 0) return require_errors.err(require_errors.validationError("VOXEL_EMPTY_MESH", "offsetMesh requires a non-empty triangle mesh."));
|
|
332
332
|
if (!Number.isFinite(distance)) return require_errors.err(require_errors.validationError("VOXEL_INVALID_DISTANCE", "distance must be a finite number."));
|
|
333
|
-
const params = resolveGridParams$
|
|
333
|
+
const params = resolveGridParams$2(opts);
|
|
334
334
|
if (require_errors.isErr(params)) return params;
|
|
335
|
-
const engine = resolveEngine(id);
|
|
335
|
+
const engine = resolveEngine$1(id);
|
|
336
336
|
if (require_errors.isErr(engine)) return engine;
|
|
337
337
|
try {
|
|
338
338
|
try {
|
|
@@ -359,9 +359,9 @@ function shellMesh(mesh, thickness, opts, id) {
|
|
|
359
359
|
if (invalid) return require_errors.err(invalid);
|
|
360
360
|
if (mesh.vertices.length === 0 || mesh.triangles.length === 0) return require_errors.err(require_errors.validationError("VOXEL_EMPTY_MESH", "shellMesh requires a non-empty triangle mesh."));
|
|
361
361
|
if (!Number.isFinite(thickness) || thickness <= 0) return require_errors.err(require_errors.validationError("VOXEL_INVALID_THICKNESS", "thickness must be a finite number > 0."));
|
|
362
|
-
const params = resolveGridParams$
|
|
362
|
+
const params = resolveGridParams$2(opts);
|
|
363
363
|
if (require_errors.isErr(params)) return params;
|
|
364
|
-
const engine = resolveEngine(id);
|
|
364
|
+
const engine = resolveEngine$1(id);
|
|
365
365
|
if (require_errors.isErr(engine)) return engine;
|
|
366
366
|
try {
|
|
367
367
|
try {
|
|
@@ -391,9 +391,9 @@ function voxelBoolean(a, b, op, opts, id) {
|
|
|
391
391
|
if (a.vertices.length === 0 || a.triangles.length === 0) return require_errors.err(require_errors.validationError("VOXEL_EMPTY_MESH", "voxelBoolean requires a non-empty mesh for operand A."));
|
|
392
392
|
if (b.vertices.length === 0 || b.triangles.length === 0) return require_errors.err(require_errors.validationError("VOXEL_EMPTY_MESH", "voxelBoolean requires a non-empty mesh for operand B."));
|
|
393
393
|
const opCode = BOOLEAN_OP_CODES$1[op];
|
|
394
|
-
const params = resolveGridParams$
|
|
394
|
+
const params = resolveGridParams$2(opts);
|
|
395
395
|
if (require_errors.isErr(params)) return params;
|
|
396
|
-
const engine = resolveEngine(id);
|
|
396
|
+
const engine = resolveEngine$1(id);
|
|
397
397
|
if (require_errors.isErr(engine)) return engine;
|
|
398
398
|
try {
|
|
399
399
|
try {
|
|
@@ -442,16 +442,16 @@ function voxelBooleanShapes(a, b, op, opts, id) {
|
|
|
442
442
|
}
|
|
443
443
|
//#endregion
|
|
444
444
|
//#region src/voxel/fieldFns.ts
|
|
445
|
-
var DEFAULT_RESOLUTION$
|
|
446
|
-
var DEFAULT_PADDING$
|
|
445
|
+
var DEFAULT_RESOLUTION$2 = 48;
|
|
446
|
+
var DEFAULT_PADDING$2 = 2;
|
|
447
447
|
var BOOLEAN_OP_CODES = {
|
|
448
448
|
union: 0,
|
|
449
449
|
intersection: 1,
|
|
450
450
|
difference: 2
|
|
451
451
|
};
|
|
452
|
-
function resolveGridParams(opts) {
|
|
453
|
-
const resolution = opts?.resolution ?? DEFAULT_RESOLUTION$
|
|
454
|
-
const padding = opts?.padding ?? DEFAULT_PADDING$
|
|
452
|
+
function resolveGridParams$1(opts) {
|
|
453
|
+
const resolution = opts?.resolution ?? DEFAULT_RESOLUTION$2;
|
|
454
|
+
const padding = opts?.padding ?? DEFAULT_PADDING$2;
|
|
455
455
|
if (!Number.isInteger(resolution) || resolution < 1) return require_errors.err(require_errors.validationError("VOXEL_INVALID_RESOLUTION", "resolution must be an integer >= 1."));
|
|
456
456
|
if (!Number.isInteger(padding) || padding < 1) return require_errors.err(require_errors.validationError("VOXEL_INVALID_PADDING", "padding must be an integer >= 1."));
|
|
457
457
|
return require_errors.ok({
|
|
@@ -501,6 +501,9 @@ function fieldDeletable(raw) {
|
|
|
501
501
|
}
|
|
502
502
|
};
|
|
503
503
|
}
|
|
504
|
+
function makeFieldHandle(raw) {
|
|
505
|
+
return makeHandle(raw);
|
|
506
|
+
}
|
|
504
507
|
function makeHandle(raw) {
|
|
505
508
|
const inner = require_shapeTypes.createKernelHandle(fieldDeletable(raw));
|
|
506
509
|
const handle = {
|
|
@@ -557,9 +560,9 @@ function voxelField(mesh, opts, id) {
|
|
|
557
560
|
const invalid = validateMesh(mesh);
|
|
558
561
|
if (invalid) return require_errors.err(invalid);
|
|
559
562
|
if (mesh.vertices.length === 0 || mesh.triangles.length === 0) return require_errors.err(require_errors.validationError("VOXEL_EMPTY_MESH", "voxelField requires a non-empty triangle mesh."));
|
|
560
|
-
const params = resolveGridParams(opts);
|
|
563
|
+
const params = resolveGridParams$1(opts);
|
|
561
564
|
if (require_errors.isErr(params)) return params;
|
|
562
|
-
const engine = resolveEngine(id);
|
|
565
|
+
const engine = resolveEngine$1(id);
|
|
563
566
|
if (require_errors.isErr(engine)) return engine;
|
|
564
567
|
try {
|
|
565
568
|
return require_errors.ok(makeHandle(new engine.value.VoxelField(mesh.vertices, mesh.triangles, params.value.resolution, params.value.padding)));
|
|
@@ -586,9 +589,9 @@ function voxelBooleanField(a, b, op, opts, id) {
|
|
|
586
589
|
if (invalidB) return require_errors.err(invalidB);
|
|
587
590
|
if (a.vertices.length === 0 || a.triangles.length === 0) return require_errors.err(require_errors.validationError("VOXEL_EMPTY_MESH", "voxelBooleanField requires a non-empty mesh for operand A."));
|
|
588
591
|
if (b.vertices.length === 0 || b.triangles.length === 0) return require_errors.err(require_errors.validationError("VOXEL_EMPTY_MESH", "voxelBooleanField requires a non-empty mesh for operand B."));
|
|
589
|
-
const params = resolveGridParams(opts);
|
|
592
|
+
const params = resolveGridParams$1(opts);
|
|
590
593
|
if (require_errors.isErr(params)) return params;
|
|
591
|
-
const engine = resolveEngine(id);
|
|
594
|
+
const engine = resolveEngine$1(id);
|
|
592
595
|
if (require_errors.isErr(engine)) return engine;
|
|
593
596
|
try {
|
|
594
597
|
return require_errors.ok(makeHandle(engine.value.VoxelField.boolean_of(a.vertices, a.triangles, b.vertices, b.triangles, BOOLEAN_OP_CODES[op], params.value.resolution, params.value.padding)));
|
|
@@ -698,6 +701,302 @@ function voxelBooleanFieldShapes(a, b, op, opts, id) {
|
|
|
698
701
|
return voxelBooleanField(meshA.value, meshB.value, op, opts, id);
|
|
699
702
|
}
|
|
700
703
|
//#endregion
|
|
704
|
+
//#region src/implicit/sdfFns.ts
|
|
705
|
+
var DEFAULT_RESOLUTION$1 = 48;
|
|
706
|
+
var DEFAULT_PADDING$1 = 2;
|
|
707
|
+
function resolveEngine(id) {
|
|
708
|
+
try {
|
|
709
|
+
return require_errors.ok(getVoxel(id));
|
|
710
|
+
} catch (cause) {
|
|
711
|
+
return require_errors.err(require_errors.moduleInitError("VOXEL_NOT_INITIALIZED", cause instanceof Error ? cause.message : "voxel engine not initialized", cause));
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
function resolveGridParams(opts) {
|
|
715
|
+
const resolution = opts?.resolution ?? DEFAULT_RESOLUTION$1;
|
|
716
|
+
const padding = opts?.padding ?? DEFAULT_PADDING$1;
|
|
717
|
+
if (!Number.isInteger(resolution) || resolution < 1) return require_errors.err(require_errors.validationError("SDF_INVALID_RESOLUTION", "resolution must be an integer >= 1."));
|
|
718
|
+
if (!Number.isInteger(padding) || padding < 1) return require_errors.err(require_errors.validationError("SDF_INVALID_PADDING", "padding must be an integer >= 1."));
|
|
719
|
+
return require_errors.ok({
|
|
720
|
+
resolution,
|
|
721
|
+
padding
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
function sdfDeletable(raw) {
|
|
725
|
+
return {
|
|
726
|
+
raw,
|
|
727
|
+
delete() {
|
|
728
|
+
raw.free();
|
|
729
|
+
}
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
/** The position-modulated operator methods, split out to keep {@link makeSdfHandle}
|
|
733
|
+
* under the per-function line cap. `this` binds to the owning {@link SdfHandle} when
|
|
734
|
+
* spread into its object literal. */
|
|
735
|
+
var MODULATED_FIELD_METHODS = {
|
|
736
|
+
offsetField(field) {
|
|
737
|
+
return makeSdfHandle(this.value.offset_field(field.value));
|
|
738
|
+
},
|
|
739
|
+
roundField(field) {
|
|
740
|
+
return makeSdfHandle(this.value.round_field(field.value));
|
|
741
|
+
},
|
|
742
|
+
shellField(field) {
|
|
743
|
+
return makeSdfHandle(this.value.shell_field(field.value));
|
|
744
|
+
},
|
|
745
|
+
smoothUnionField(other, field) {
|
|
746
|
+
return makeSdfHandle(this.value.smooth_union_field(other.value, field.value));
|
|
747
|
+
}
|
|
748
|
+
};
|
|
749
|
+
function makeSdfHandle(raw) {
|
|
750
|
+
const inner = require_shapeTypes.createKernelHandle(sdfDeletable(raw));
|
|
751
|
+
return {
|
|
752
|
+
get value() {
|
|
753
|
+
return inner.value.raw;
|
|
754
|
+
},
|
|
755
|
+
get disposed() {
|
|
756
|
+
return inner.disposed;
|
|
757
|
+
},
|
|
758
|
+
[Symbol.dispose]() {
|
|
759
|
+
inner[Symbol.dispose]();
|
|
760
|
+
},
|
|
761
|
+
union(other) {
|
|
762
|
+
return makeSdfHandle(this.value.union(other.value));
|
|
763
|
+
},
|
|
764
|
+
intersection(other) {
|
|
765
|
+
return makeSdfHandle(this.value.intersection(other.value));
|
|
766
|
+
},
|
|
767
|
+
difference(other) {
|
|
768
|
+
return makeSdfHandle(this.value.difference(other.value));
|
|
769
|
+
},
|
|
770
|
+
smoothUnion(other, k) {
|
|
771
|
+
return makeSdfHandle(this.value.smooth_union(other.value, k));
|
|
772
|
+
},
|
|
773
|
+
smoothIntersection(other, k) {
|
|
774
|
+
return makeSdfHandle(this.value.smooth_intersection(other.value, k));
|
|
775
|
+
},
|
|
776
|
+
smoothDifference(other, k) {
|
|
777
|
+
return makeSdfHandle(this.value.smooth_difference(other.value, k));
|
|
778
|
+
},
|
|
779
|
+
offset(distance) {
|
|
780
|
+
return makeSdfHandle(this.value.offset(distance));
|
|
781
|
+
},
|
|
782
|
+
round(radius) {
|
|
783
|
+
return makeSdfHandle(this.value.round(radius));
|
|
784
|
+
},
|
|
785
|
+
shell(thickness) {
|
|
786
|
+
return makeSdfHandle(this.value.shell(thickness));
|
|
787
|
+
},
|
|
788
|
+
onion(thickness) {
|
|
789
|
+
return makeSdfHandle(this.value.onion(thickness));
|
|
790
|
+
},
|
|
791
|
+
...MODULATED_FIELD_METHODS,
|
|
792
|
+
translate(x, y, z) {
|
|
793
|
+
return makeSdfHandle(this.value.translate(x, y, z));
|
|
794
|
+
},
|
|
795
|
+
rotate(ax, ay, az, angle) {
|
|
796
|
+
return makeSdfHandle(this.value.rotate(ax, ay, az, angle));
|
|
797
|
+
},
|
|
798
|
+
scale(s) {
|
|
799
|
+
return makeSdfHandle(this.value.scale(s));
|
|
800
|
+
},
|
|
801
|
+
rasterize(opts) {
|
|
802
|
+
return rasterizeField(this.value, opts);
|
|
803
|
+
},
|
|
804
|
+
rasterizeIn(bounds, opts) {
|
|
805
|
+
return rasterizeFieldIn(this.value, bounds, opts);
|
|
806
|
+
}
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
function rasterizeField(sdf, opts) {
|
|
810
|
+
const params = resolveGridParams(opts);
|
|
811
|
+
if (require_errors.isErr(params)) return params;
|
|
812
|
+
try {
|
|
813
|
+
return require_errors.ok(makeFieldHandle(sdf.rasterize(params.value.resolution, params.value.padding)));
|
|
814
|
+
} catch (cause) {
|
|
815
|
+
return rasterizeError(cause);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
function rasterizeFieldIn(sdf, bounds, opts) {
|
|
819
|
+
const params = resolveGridParams(opts);
|
|
820
|
+
if (require_errors.isErr(params)) return params;
|
|
821
|
+
const invalid = validateBounds(bounds);
|
|
822
|
+
if (invalid) return require_errors.err(invalid);
|
|
823
|
+
try {
|
|
824
|
+
return require_errors.ok(makeFieldHandle(sdf.rasterize_in(bounds.min[0], bounds.min[1], bounds.min[2], bounds.max[0], bounds.max[1], bounds.max[2], params.value.resolution, params.value.padding)));
|
|
825
|
+
} catch (cause) {
|
|
826
|
+
return rasterizeError(cause);
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
function rasterizeError(cause) {
|
|
830
|
+
return require_errors.err(require_errors.computationError("SDF_RASTERIZE_FAILED", cause instanceof Error ? cause.message : "SDF rasterization failed (grid too large?).", cause));
|
|
831
|
+
}
|
|
832
|
+
function validateBounds(bounds) {
|
|
833
|
+
if (![...bounds.min, ...bounds.max].every((v) => Number.isFinite(v))) return require_errors.validationError("SDF_INVALID_BOUNDS", "bounds must be finite numbers.");
|
|
834
|
+
for (let axis = 0; axis < 3; axis++) if (bounds.max[axis] <= bounds.min[axis]) return require_errors.validationError("SDF_INVALID_BOUNDS", "bounds max must exceed min on every axis.");
|
|
835
|
+
return null;
|
|
836
|
+
}
|
|
837
|
+
function build(make, id) {
|
|
838
|
+
const engine = resolveEngine(id);
|
|
839
|
+
if (require_errors.isErr(engine)) return engine;
|
|
840
|
+
try {
|
|
841
|
+
return require_errors.ok(makeSdfHandle(make(engine.value)));
|
|
842
|
+
} catch (cause) {
|
|
843
|
+
return require_errors.err(require_errors.computationError("SDF_BUILD_FAILED", cause instanceof Error ? cause.message : "SDF primitive construction failed.", cause));
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
/** A sphere of radius `r`, centered at the origin. */
|
|
847
|
+
function sphere(r, id) {
|
|
848
|
+
return build((e) => e.Sdf.sphere(r), id);
|
|
849
|
+
}
|
|
850
|
+
/** An axis-aligned box of half-extents `(hx, hy, hz)`, centered at the origin. */
|
|
851
|
+
function box$1(hx, hy, hz, id) {
|
|
852
|
+
return build((e) => e.Sdf.box_(hx, hy, hz), id);
|
|
853
|
+
}
|
|
854
|
+
/** A box with rounded edges of radius `r`. */
|
|
855
|
+
function roundedBox(hx, hy, hz, r, id) {
|
|
856
|
+
return build((e) => e.Sdf.rounded_box(hx, hy, hz, r), id);
|
|
857
|
+
}
|
|
858
|
+
/** A capped cylinder, axis +Z, radius `r`, total height `h`, centered at origin. */
|
|
859
|
+
function cylinder$1(r, h, id) {
|
|
860
|
+
return build((e) => e.Sdf.cylinder(r, h), id);
|
|
861
|
+
}
|
|
862
|
+
/** A capped cone centered at the origin: base radius `r` at z = −h/2 tapering to an apex at z = +h/2. */
|
|
863
|
+
function cone$1(r, h, id) {
|
|
864
|
+
return build((e) => e.Sdf.cone(r, h), id);
|
|
865
|
+
}
|
|
866
|
+
/** A capsule: a line segment `a`→`b` of radius `r`. */
|
|
867
|
+
function capsule(a, b, r, id) {
|
|
868
|
+
return build((e) => e.Sdf.capsule(a[0], a[1], a[2], b[0], b[1], b[2], r), id);
|
|
869
|
+
}
|
|
870
|
+
/** A torus in the XY plane (axis +Z): a `minor`-radius tube on a `major` circle. */
|
|
871
|
+
function torus(major, minor, id) {
|
|
872
|
+
return build((e) => e.Sdf.torus(major, minor), id);
|
|
873
|
+
}
|
|
874
|
+
/** A half-space: the plane through `h·n` with outward normal `n` (normalized). */
|
|
875
|
+
function plane(n, h, id) {
|
|
876
|
+
return build((e) => e.Sdf.plane(n[0], n[1], n[2], h), id);
|
|
877
|
+
}
|
|
878
|
+
function flattenSpine(spine) {
|
|
879
|
+
if (spine.length < 2) return require_errors.err(require_errors.validationError("SDF_INVALID_SPINE", "sweep spine needs at least 2 points."));
|
|
880
|
+
const flat = new Float64Array(spine.length * 3);
|
|
881
|
+
for (let i = 0; i < spine.length; i++) {
|
|
882
|
+
const pt = spine[i];
|
|
883
|
+
if (!pt.every((c) => Number.isFinite(c))) return require_errors.err(require_errors.validationError("SDF_INVALID_SPINE", "sweep spine coordinates must be finite."));
|
|
884
|
+
flat[i * 3] = pt[0];
|
|
885
|
+
flat[i * 3 + 1] = pt[1];
|
|
886
|
+
flat[i * 3 + 2] = pt[2];
|
|
887
|
+
}
|
|
888
|
+
return require_errors.ok(flat);
|
|
889
|
+
}
|
|
890
|
+
/**
|
|
891
|
+
* Sweep an in-plane `profile` along a `spine` polyline using rotation-minimizing
|
|
892
|
+
* frames (no 180° flip at inflections). The profile is sampled in its own
|
|
893
|
+
* `(normal, binormal)` plane at every station; `opts.closed` loops the spine and
|
|
894
|
+
* skips the open-end caps. The result is a pseudo-SDF (exact distance only near
|
|
895
|
+
* the swept wall), which contours cleanly. `spine` needs at least 2 finite points.
|
|
896
|
+
*/
|
|
897
|
+
function sweep(spine, profile, opts, id) {
|
|
898
|
+
const flat = flattenSpine(spine);
|
|
899
|
+
if (require_errors.isErr(flat)) return flat;
|
|
900
|
+
const closed = opts?.closed ?? false;
|
|
901
|
+
return build((e) => e.Sdf.sweep(flat.value, profile.value, closed), id);
|
|
902
|
+
}
|
|
903
|
+
var LATTICE_TAGS$1 = {
|
|
904
|
+
gyroid: 0,
|
|
905
|
+
schwarzP: 1,
|
|
906
|
+
diamond: 2
|
|
907
|
+
};
|
|
908
|
+
/**
|
|
909
|
+
* A graded/conformal TPMS lattice: `|f(p)| − ½·thickness(p)` for the chosen `kind`
|
|
910
|
+
* (negative = strut material), with `period` and `thickness` GRADED per-position via
|
|
911
|
+
* {@link ScalarFieldHandle}. A {@link fieldConst} period/thickness reproduces a
|
|
912
|
+
* uniform lattice. The field is Lipschitz and APPROXIMATE (not a true SDF).
|
|
913
|
+
*
|
|
914
|
+
* The lattice is INFINITE/periodic, so it must be clipped to a bounded region
|
|
915
|
+
* (`lattice.intersection(region)`) before rasterizing — that conformal clip is what
|
|
916
|
+
* frames a finite grid. NOTE: grading the PERIOD is an approximation (a
|
|
917
|
+
* spatially-varying period isn't strictly periodic); grading THICKNESS is the
|
|
918
|
+
* well-behaved primary knob.
|
|
919
|
+
*/
|
|
920
|
+
function lattice(kind, period, thickness, id) {
|
|
921
|
+
const tag = LATTICE_TAGS$1[kind];
|
|
922
|
+
if (tag === void 0) return require_errors.err(require_errors.validationError("SDF_INVALID_LATTICE_KIND", `unknown lattice kind: ${kind}`));
|
|
923
|
+
return build((e) => e.Sdf.lattice(tag, period.value, thickness.value), id);
|
|
924
|
+
}
|
|
925
|
+
/**
|
|
926
|
+
* A cubic beam/strut lattice: axis-aligned cylindrical struts on a `period`-spaced
|
|
927
|
+
* cubic grid, with the strut `radius` GRADED per-position via
|
|
928
|
+
* {@link ScalarFieldHandle}. Periodic/infinite — clip to a bounded region
|
|
929
|
+
* (`strut.intersection(region)`) before rasterizing.
|
|
930
|
+
*/
|
|
931
|
+
function strutLattice(period, radius, id) {
|
|
932
|
+
return build((e) => e.Sdf.strut_lattice(period, radius.value), id);
|
|
933
|
+
}
|
|
934
|
+
function scalarFieldDeletable(raw) {
|
|
935
|
+
return {
|
|
936
|
+
raw,
|
|
937
|
+
delete() {
|
|
938
|
+
raw.free();
|
|
939
|
+
}
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
function makeScalarFieldHandle(raw) {
|
|
943
|
+
const inner = require_shapeTypes.createKernelHandle(scalarFieldDeletable(raw));
|
|
944
|
+
return {
|
|
945
|
+
get value() {
|
|
946
|
+
return inner.value.raw;
|
|
947
|
+
},
|
|
948
|
+
get disposed() {
|
|
949
|
+
return inner.disposed;
|
|
950
|
+
},
|
|
951
|
+
[Symbol.dispose]() {
|
|
952
|
+
inner[Symbol.dispose]();
|
|
953
|
+
}
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
function buildField(make, id) {
|
|
957
|
+
const engine = resolveEngine(id);
|
|
958
|
+
if (require_errors.isErr(engine)) return engine;
|
|
959
|
+
try {
|
|
960
|
+
return require_errors.ok(makeScalarFieldHandle(make(engine.value)));
|
|
961
|
+
} catch (cause) {
|
|
962
|
+
return require_errors.err(require_errors.computationError("SDF_FIELD_BUILD_FAILED", cause instanceof Error ? cause.message : "scalar field construction failed.", cause));
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
/** A spatially constant field — reproduces a constant operator parameter exactly. */
|
|
966
|
+
function fieldConst(c, id) {
|
|
967
|
+
return buildField((e) => e.ScalarField.constant(c), id);
|
|
968
|
+
}
|
|
969
|
+
/**
|
|
970
|
+
* A field that ramps `lo → hi` as `coord[axis]` goes `a → b`, clamped to the
|
|
971
|
+
* endpoint band outside `[a, b]`. `axis` is 0 (x), 1 (y), or 2 (z).
|
|
972
|
+
*/
|
|
973
|
+
function fieldAxialRamp(axis, a, b, lo, hi, id) {
|
|
974
|
+
return buildField((e) => e.ScalarField.axial_ramp(axis, a, b, lo, hi), id);
|
|
975
|
+
}
|
|
976
|
+
/**
|
|
977
|
+
* A field by radial distance from the line through `center` along `axis`: `lo → hi`
|
|
978
|
+
* as that distance goes `r0 → r1`, clamped. `axis` is 0 (x), 1 (y), or 2 (z).
|
|
979
|
+
*/
|
|
980
|
+
function fieldRadialRamp(center, axis, r0, r1, lo, hi, id) {
|
|
981
|
+
return buildField((e) => e.ScalarField.radial_ramp(center[0], center[1], center[2], axis, r0, r1, lo, hi), id);
|
|
982
|
+
}
|
|
983
|
+
/**
|
|
984
|
+
* A field from an {@link SdfHandle}'s signed distance, affinely remapped to
|
|
985
|
+
* `sdf.eval(p) * scale + offset`. UNBOUNDED — drive a bounds-affecting op
|
|
986
|
+
* (`offsetField`/`shellField`) with it only via `rasterizeIn` or wrapped in
|
|
987
|
+
* {@link fieldClamp}.
|
|
988
|
+
*/
|
|
989
|
+
function fieldFromSdf(sdf, scale, offset, id) {
|
|
990
|
+
return buildField((e) => e.ScalarField.from_sdf(sdf.value, scale, offset), id);
|
|
991
|
+
}
|
|
992
|
+
/**
|
|
993
|
+
* Clamp another field's value to `[min, max]` — bounds an otherwise unbounded
|
|
994
|
+
* {@link fieldFromSdf} so it can safely drive offset/shell.
|
|
995
|
+
*/
|
|
996
|
+
function fieldClamp(field, min, max, id) {
|
|
997
|
+
return buildField((e) => e.ScalarField.clamp(field.value, min, max), id);
|
|
998
|
+
}
|
|
999
|
+
//#endregion
|
|
701
1000
|
//#region src/lattice/latticeFns.ts
|
|
702
1001
|
var LATTICE_TAGS = {
|
|
703
1002
|
gyroid: 0,
|
|
@@ -751,7 +1050,7 @@ function latticeInfill(mesh, opts, id) {
|
|
|
751
1050
|
if (mesh.vertices.length === 0 || mesh.triangles.length === 0) return require_errors.err(require_errors.validationError("LATTICE_EMPTY_MESH", "latticeInfill requires a non-empty triangle mesh."));
|
|
752
1051
|
const resolved = validateOptions(opts);
|
|
753
1052
|
if (require_errors.isErr(resolved)) return resolved;
|
|
754
|
-
const engine = resolveEngine(id);
|
|
1053
|
+
const engine = resolveEngine$1(id);
|
|
755
1054
|
if (require_errors.isErr(engine)) return engine;
|
|
756
1055
|
const { tag, period, thickness, resolution, padding } = resolved.value;
|
|
757
1056
|
try {
|
|
@@ -792,7 +1091,7 @@ function tpmsLattice(bounds, opts, id) {
|
|
|
792
1091
|
}
|
|
793
1092
|
const resolved = validateOptions(opts);
|
|
794
1093
|
if (require_errors.isErr(resolved)) return resolved;
|
|
795
|
-
const engine = resolveEngine(id);
|
|
1094
|
+
const engine = resolveEngine$1(id);
|
|
796
1095
|
if (require_errors.isErr(engine)) return engine;
|
|
797
1096
|
const { tag, period, thickness, resolution, padding } = resolved.value;
|
|
798
1097
|
const [minX, minY, minZ] = bounds.min;
|
|
@@ -4461,7 +4760,7 @@ function depsOf(...sources) {
|
|
|
4461
4760
|
}
|
|
4462
4761
|
return acc.size === 0 ? EMPTY_DEPS : acc;
|
|
4463
4762
|
}
|
|
4464
|
-
function box$
|
|
4763
|
+
function box$2(x, y, z) {
|
|
4465
4764
|
const xe = asScalarExpr(x);
|
|
4466
4765
|
const ye = asScalarExpr(y);
|
|
4467
4766
|
const ze = asScalarExpr(z);
|
|
@@ -4476,7 +4775,7 @@ function box$1(x, y, z) {
|
|
|
4476
4775
|
freeParams: depsOf(xe, ye, ze)
|
|
4477
4776
|
};
|
|
4478
4777
|
}
|
|
4479
|
-
function sphere$
|
|
4778
|
+
function sphere$2(radius) {
|
|
4480
4779
|
const re = asScalarExpr(radius);
|
|
4481
4780
|
return {
|
|
4482
4781
|
kind: "Sphere",
|
|
@@ -4485,7 +4784,7 @@ function sphere$1(radius) {
|
|
|
4485
4784
|
freeParams: re.freeParams
|
|
4486
4785
|
};
|
|
4487
4786
|
}
|
|
4488
|
-
function cylinder$
|
|
4787
|
+
function cylinder$2(radius, height) {
|
|
4489
4788
|
const re = asScalarExpr(radius);
|
|
4490
4789
|
const he = asScalarExpr(height);
|
|
4491
4790
|
return {
|
|
@@ -4496,7 +4795,7 @@ function cylinder$1(radius, height) {
|
|
|
4496
4795
|
freeParams: depsOf(re, he)
|
|
4497
4796
|
};
|
|
4498
4797
|
}
|
|
4499
|
-
function cone$
|
|
4798
|
+
function cone$2(radius1, radius2, height) {
|
|
4500
4799
|
const r1 = asScalarExpr(radius1);
|
|
4501
4800
|
const r2 = asScalarExpr(radius2);
|
|
4502
4801
|
const he = asScalarExpr(height);
|
|
@@ -4511,7 +4810,7 @@ function cone$1(radius1, radius2, height) {
|
|
|
4511
4810
|
freeParams: depsOf(r1, r2, he)
|
|
4512
4811
|
};
|
|
4513
4812
|
}
|
|
4514
|
-
function torus$
|
|
4813
|
+
function torus$2(majorRadius, minorRadius) {
|
|
4515
4814
|
const ma = asScalarExpr(majorRadius);
|
|
4516
4815
|
const mi = asScalarExpr(minorRadius);
|
|
4517
4816
|
return {
|
|
@@ -5416,7 +5715,7 @@ function readSingleExpr(j, key, build) {
|
|
|
5416
5715
|
function readPrimitive(kind, j) {
|
|
5417
5716
|
switch (kind) {
|
|
5418
5717
|
case "Box": return readBox(j);
|
|
5419
|
-
case "Sphere": return readSingleExpr(j, "radius", sphere$
|
|
5718
|
+
case "Sphere": return readSingleExpr(j, "radius", sphere$2);
|
|
5420
5719
|
case "Cylinder": return readCylinder(j);
|
|
5421
5720
|
case "Cone": return readCone(j);
|
|
5422
5721
|
case "Torus": return readTorus(j);
|
|
@@ -5435,14 +5734,14 @@ function readBox(j) {
|
|
|
5435
5734
|
if (!y.ok) return y;
|
|
5436
5735
|
const z = readExpr(j["z"]);
|
|
5437
5736
|
if (!z.ok) return z;
|
|
5438
|
-
return require_errors.ok(box$
|
|
5737
|
+
return require_errors.ok(box$2(x.value, y.value, z.value));
|
|
5439
5738
|
}
|
|
5440
5739
|
function readCylinder(j) {
|
|
5441
5740
|
const r = readExpr(j["radius"]);
|
|
5442
5741
|
if (!r.ok) return r;
|
|
5443
5742
|
const h = readExpr(j["height"]);
|
|
5444
5743
|
if (!h.ok) return h;
|
|
5445
|
-
return require_errors.ok(cylinder$
|
|
5744
|
+
return require_errors.ok(cylinder$2(r.value, h.value));
|
|
5446
5745
|
}
|
|
5447
5746
|
function readCone(j) {
|
|
5448
5747
|
const r1 = readExpr(j["radius1"]);
|
|
@@ -5451,14 +5750,14 @@ function readCone(j) {
|
|
|
5451
5750
|
if (!r2.ok) return r2;
|
|
5452
5751
|
const h = readExpr(j["height"]);
|
|
5453
5752
|
if (!h.ok) return h;
|
|
5454
|
-
return require_errors.ok(cone$
|
|
5753
|
+
return require_errors.ok(cone$2(r1.value, r2.value, h.value));
|
|
5455
5754
|
}
|
|
5456
5755
|
function readTorus(j) {
|
|
5457
5756
|
const ma = readExpr(j["majorRadius"]);
|
|
5458
5757
|
if (!ma.ok) return ma;
|
|
5459
5758
|
const mi = readExpr(j["minorRadius"]);
|
|
5460
5759
|
if (!mi.ok) return mi;
|
|
5461
|
-
return require_errors.ok(torus$
|
|
5760
|
+
return require_errors.ok(torus$2(ma.value, mi.value));
|
|
5462
5761
|
}
|
|
5463
5762
|
function readPolygon(j) {
|
|
5464
5763
|
const pts = j["points"];
|
|
@@ -5634,11 +5933,11 @@ function foldBuildVec(dim, comps) {
|
|
|
5634
5933
|
}
|
|
5635
5934
|
function optimizeNode(n) {
|
|
5636
5935
|
switch (n.kind) {
|
|
5637
|
-
case "Box": return box$
|
|
5638
|
-
case "Sphere": return sphere$
|
|
5639
|
-
case "Cylinder": return cylinder$
|
|
5640
|
-
case "Cone": return cone$
|
|
5641
|
-
case "Torus": return torus$
|
|
5936
|
+
case "Box": return box$2(foldExpr(n.x), foldExpr(n.y), foldExpr(n.z));
|
|
5937
|
+
case "Sphere": return sphere$2(foldExpr(n.radius));
|
|
5938
|
+
case "Cylinder": return cylinder$2(foldExpr(n.radius), foldExpr(n.height));
|
|
5939
|
+
case "Cone": return cone$2(foldExpr(n.radius1), foldExpr(n.radius2), foldExpr(n.height));
|
|
5940
|
+
case "Torus": return torus$2(foldExpr(n.majorRadius), foldExpr(n.minorRadius));
|
|
5642
5941
|
case "Polygon": return polygon$1(n.points.map(foldExpr));
|
|
5643
5942
|
case "Circle": return circle$1(foldExpr(n.radius));
|
|
5644
5943
|
case "Line": return line$1(foldExpr(n.from), foldExpr(n.to));
|
|
@@ -5796,15 +6095,15 @@ var csg_exports = /* @__PURE__ */ require_textBlueprints.__exportAll({
|
|
|
5796
6095
|
asVec2Expr: () => asVec2Expr,
|
|
5797
6096
|
asVec3Expr: () => asVec3Expr,
|
|
5798
6097
|
binOp: () => binOp,
|
|
5799
|
-
box: () => box$
|
|
6098
|
+
box: () => box$2,
|
|
5800
6099
|
buildVec: () => buildVec,
|
|
5801
6100
|
circle: () => circle$1,
|
|
5802
6101
|
component: () => component,
|
|
5803
6102
|
compound: () => compound$1,
|
|
5804
|
-
cone: () => cone$
|
|
6103
|
+
cone: () => cone$2,
|
|
5805
6104
|
cut: () => cut$1,
|
|
5806
6105
|
cutAll: () => cutAll$1,
|
|
5807
|
-
cylinder: () => cylinder$
|
|
6106
|
+
cylinder: () => cylinder$2,
|
|
5808
6107
|
emptyFace: () => emptyFace,
|
|
5809
6108
|
emptySolid: () => emptySolid,
|
|
5810
6109
|
emptyWire: () => emptyWire,
|
|
@@ -5826,9 +6125,9 @@ var csg_exports = /* @__PURE__ */ require_textBlueprints.__exportAll({
|
|
|
5826
6125
|
replaceNode: () => replaceNode,
|
|
5827
6126
|
rotate: () => rotate$1,
|
|
5828
6127
|
scale: () => scale$1,
|
|
5829
|
-
sphere: () => sphere$
|
|
6128
|
+
sphere: () => sphere$2,
|
|
5830
6129
|
toJSON: () => toJSON,
|
|
5831
|
-
torus: () => torus$
|
|
6130
|
+
torus: () => torus$2,
|
|
5832
6131
|
translate: () => translate$1,
|
|
5833
6132
|
unaryOp: () => unaryOp,
|
|
5834
6133
|
vec2Lit: () => vec2Lit,
|
|
@@ -6308,6 +6607,22 @@ exports.roundedRectangleBlueprint = require_boolean2D.roundedRectangleBlueprint;
|
|
|
6308
6607
|
exports.scale = scale;
|
|
6309
6608
|
exports.scale2D = require_blueprintFns.scale2D;
|
|
6310
6609
|
exports.scaleDrawing = require_drawFns.scaleDrawing;
|
|
6610
|
+
exports.sdfBox = box$1;
|
|
6611
|
+
exports.sdfCapsule = capsule;
|
|
6612
|
+
exports.sdfCone = cone$1;
|
|
6613
|
+
exports.sdfCylinder = cylinder$1;
|
|
6614
|
+
exports.sdfFieldAxialRamp = fieldAxialRamp;
|
|
6615
|
+
exports.sdfFieldClamp = fieldClamp;
|
|
6616
|
+
exports.sdfFieldConst = fieldConst;
|
|
6617
|
+
exports.sdfFieldFromSdf = fieldFromSdf;
|
|
6618
|
+
exports.sdfFieldRadialRamp = fieldRadialRamp;
|
|
6619
|
+
exports.sdfLattice = lattice;
|
|
6620
|
+
exports.sdfPlane = plane;
|
|
6621
|
+
exports.sdfRoundedBox = roundedBox;
|
|
6622
|
+
exports.sdfSphere = sphere;
|
|
6623
|
+
exports.sdfStrutLattice = strutLattice;
|
|
6624
|
+
exports.sdfSweep = sweep;
|
|
6625
|
+
exports.sdfTorus = torus;
|
|
6311
6626
|
exports.section = section;
|
|
6312
6627
|
exports.sectionToFace = sectionToFace;
|
|
6313
6628
|
exports.serializeHistory = require_historyFns.serializeHistory;
|