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 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$3 = 48;
222
- var DEFAULT_PADDING$3 = 2;
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$3;
236
- const padding = opts?.padding ?? DEFAULT_PADDING$3;
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$2 = 48;
290
- var DEFAULT_PADDING$2 = 2;
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$1(opts) {
297
- const resolution = opts?.resolution ?? DEFAULT_RESOLUTION$2;
298
- const padding = opts?.padding ?? DEFAULT_PADDING$2;
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$1(opts);
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$1(opts);
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$1(opts);
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$1 = 48;
446
- var DEFAULT_PADDING$1 = 2;
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$1;
454
- const padding = opts?.padding ?? DEFAULT_PADDING$1;
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$1(x, y, z) {
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$1(radius) {
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$1(radius, height) {
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$1(radius1, radius2, height) {
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$1(majorRadius, minorRadius) {
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$1);
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$1(x.value, y.value, z.value));
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$1(r.value, h.value));
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$1(r1.value, r2.value, h.value));
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$1(ma.value, mi.value));
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$1(foldExpr(n.x), foldExpr(n.y), foldExpr(n.z));
5638
- case "Sphere": return sphere$1(foldExpr(n.radius));
5639
- case "Cylinder": return cylinder$1(foldExpr(n.radius), foldExpr(n.height));
5640
- case "Cone": return cone$1(foldExpr(n.radius1), foldExpr(n.radius2), foldExpr(n.height));
5641
- case "Torus": return torus$1(foldExpr(n.majorRadius), foldExpr(n.minorRadius));
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$1,
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$1,
6103
+ cone: () => cone$2,
5805
6104
  cut: () => cut$1,
5806
6105
  cutAll: () => cutAll$1,
5807
- cylinder: () => cylinder$1,
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$1,
6128
+ sphere: () => sphere$2,
5830
6129
  toJSON: () => toJSON,
5831
- torus: () => torus$1,
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;