okgeometry-api 1.1.13 → 1.1.15

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.
@@ -1688,7 +1688,7 @@ export function version() {
1688
1688
  function __wbg_get_imports() {
1689
1689
  const import0 = {
1690
1690
  __proto__: null,
1691
- __wbg___okgeometry_boolean_should_cancel_9b2919fd8513410f: function () {
1691
+ __wbg___okgeometry_boolean_should_cancel_18de25279e7a7d72: function () {
1692
1692
  const ret = globalThis.__okgeometry_boolean_should_cancel();
1693
1693
  return ret;
1694
1694
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "okgeometry-api",
3
- "version": "1.1.13",
3
+ "version": "1.1.15",
4
4
  "description": "Geometry engine API for AEC applications — NURBS, meshes, booleans, intersections. Powered by Rust/WASM.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/src/Circle.ts CHANGED
@@ -139,15 +139,25 @@ export class Circle {
139
139
  * @param caps - Whether to cap the ends (default true)
140
140
  * @returns Mesh representing the extruded cylinder
141
141
  */
142
- extrude(direction: Vec3, segments = 16, caps = true): Mesh {
143
- ensureInit();
144
- const buf = wasm.extrude_circle(
145
- this.center.x, this.center.y, this.center.z,
146
- this.normal.x, this.normal.y, this.normal.z,
142
+ extrude(direction: Vec3, segments = 16, caps = true): Mesh {
143
+ ensureInit();
144
+ const buf = wasm.extrude_circle(
145
+ this.center.x, this.center.y, this.center.z,
146
+ this.normal.x, this.normal.y, this.normal.z,
147
147
  this.radius,
148
148
  direction.x, direction.y, direction.z,
149
149
  segments, caps
150
- );
151
- return Mesh.fromBuffer(buf);
152
- }
153
- }
150
+ );
151
+ return Mesh.fromBuffer(buf);
152
+ }
153
+
154
+ /**
155
+ * Extrude this closed circle into a trusted solid suitable for booleans.
156
+ * @param direction - Extrusion direction and magnitude
157
+ * @param segments - Number of boundary samples used for tessellation (default 32)
158
+ * @returns Closed mesh solid ready for boolean operations
159
+ */
160
+ extrudeAsSolid(direction: Vec3, segments = 32): Mesh {
161
+ return Mesh.extrudeCurveAsSolid(this, direction, segments);
162
+ }
163
+ }
package/src/Mesh.ts CHANGED
@@ -1192,21 +1192,91 @@ export class Mesh {
1192
1192
  * Extrude a planar curve along a direction.
1193
1193
  * Open curves return an uncapped polysurface; closed curves are capped solids.
1194
1194
  */
1195
- static extrudePlanarCurve(points: Point[], normal: Vec3, height: number, closed: boolean): Mesh {
1196
- ensureInit();
1197
- const buf = wasm.mesh_extrude_planar_curve(
1198
- pointsToCoords(points),
1199
- normal.x, normal.y, normal.z,
1195
+ static extrudePlanarCurve(points: Point[], normal: Vec3, height: number, closed: boolean): Mesh {
1196
+ ensureInit();
1197
+ const buf = wasm.mesh_extrude_planar_curve(
1198
+ pointsToCoords(points),
1199
+ normal.x, normal.y, normal.z,
1200
1200
  height,
1201
1201
  closed,
1202
- );
1203
- return Mesh.fromTrustedBuffer(buf);
1204
- }
1205
-
1206
- /**
1207
- * Shift a closed cutter profile slightly opposite to travel direction and
1208
- * compensate height so the distal end remains at the user-intended depth.
1209
- */
1202
+ );
1203
+ return Mesh.fromTrustedBuffer(buf);
1204
+ }
1205
+
1206
+ private static normalizeClosedCurvePoints(points: Point[], eps = 1e-10): Point[] {
1207
+ const normalized: Point[] = [];
1208
+ for (const point of points) {
1209
+ if (normalized.length > 0 && normalized[normalized.length - 1].equals(point, eps)) continue;
1210
+ normalized.push(point);
1211
+ }
1212
+ if (normalized.length > 1 && normalized[0].equals(normalized[normalized.length - 1], eps)) {
1213
+ normalized.pop();
1214
+ }
1215
+ return normalized;
1216
+ }
1217
+
1218
+ private static getClosedCurveLoopPoints(curve: SweepableCurve, segments: number): Point[] {
1219
+ const closureEps = 1e-6;
1220
+ const sampleCount = Number.isFinite(segments) ? Math.max(3, Math.floor(segments)) : 32;
1221
+
1222
+ if (curve instanceof Circle) {
1223
+ return Mesh.normalizeClosedCurvePoints(curve.sample(sampleCount), closureEps);
1224
+ }
1225
+ if (curve instanceof Polygon || curve instanceof Polyline) {
1226
+ if (!curve.isClosed(closureEps)) {
1227
+ throw new Error("Mesh.extrudeCurveAsSolid() requires a closed polyline or polygon.");
1228
+ }
1229
+ return Mesh.normalizeClosedCurvePoints(curve.points, closureEps);
1230
+ }
1231
+ if (curve instanceof PolyCurve) {
1232
+ if (!curve.isClosed(closureEps)) {
1233
+ throw new Error("Mesh.extrudeCurveAsSolid() requires a closed polycurve.");
1234
+ }
1235
+ return Mesh.normalizeClosedCurvePoints(curve.sample(Math.max(2, sampleCount)), closureEps);
1236
+ }
1237
+ if (curve instanceof NurbsCurve) {
1238
+ if (!curve.isClosed(closureEps)) {
1239
+ throw new Error("Mesh.extrudeCurveAsSolid() requires a closed NURBS curve.");
1240
+ }
1241
+ return Mesh.normalizeClosedCurvePoints(curve.sample(sampleCount), closureEps);
1242
+ }
1243
+
1244
+ throw new Error(
1245
+ "Mesh.extrudeCurveAsSolid() requires a closed curve "
1246
+ + "(Circle, closed Polyline/Polygon, closed PolyCurve, or closed NurbsCurve).",
1247
+ );
1248
+ }
1249
+
1250
+ /**
1251
+ * Extrude a closed planar curve into a trusted closed solid suitable for booleans.
1252
+ * Curved inputs are sampled first, so `segments` controls tessellation density
1253
+ * for circles, polycurves, and NURBS curves.
1254
+ */
1255
+ static extrudeCurveAsSolid(curve: SweepableCurve, direction: Vec3, segments = 32): Mesh {
1256
+ ensureInit();
1257
+
1258
+ const height = direction.length();
1259
+ if (!Number.isFinite(height) || height < 1e-10) {
1260
+ throw new Error("Mesh.extrudeCurveAsSolid() requires a non-zero extrusion direction.");
1261
+ }
1262
+
1263
+ const points = Mesh.getClosedCurveLoopPoints(curve, segments);
1264
+ if (points.length < 3) {
1265
+ throw new Error("Mesh.extrudeCurveAsSolid() requires at least 3 unique boundary points.");
1266
+ }
1267
+
1268
+ const normal = direction.scale(1 / height);
1269
+ const mesh = Mesh.extrudePlanarCurve(points, normal, height, true);
1270
+ if (mesh.vertexCount === 0) {
1271
+ throw new Error("Mesh.extrudeCurveAsSolid() failed. Ensure the curve is planar and closed.");
1272
+ }
1273
+ return mesh;
1274
+ }
1275
+
1276
+ /**
1277
+ * Shift a closed cutter profile slightly opposite to travel direction and
1278
+ * compensate height so the distal end remains at the user-intended depth.
1279
+ */
1210
1280
  static prepareBooleanCutterCurve(
1211
1281
  points: Point[],
1212
1282
  closed: boolean,
package/src/NurbsCurve.ts CHANGED
@@ -1,10 +1,11 @@
1
1
  import { ensureInit } from "./engine.js";
2
2
  import { Point } from "./Point.js";
3
- import { Vec3 } from "./Vec3.js";
4
- import { Plane } from "./Plane.js";
5
- import { Polyline } from "./Polyline.js";
6
- import type { Line } from "./Line.js";
7
- import type { Arc } from "./Arc.js";
3
+ import { Vec3 } from "./Vec3.js";
4
+ import { Plane } from "./Plane.js";
5
+ import { Mesh } from "./Mesh.js";
6
+ import { Polyline } from "./Polyline.js";
7
+ import type { Line } from "./Line.js";
8
+ import type { Arc } from "./Arc.js";
8
9
  import type { RotationAxis } from "./types.js";
9
10
  import { parseIntersectionPoints } from "./BufferCodec.js";
10
11
  import * as wasm from "./wasm-bindings.js";
@@ -50,15 +51,24 @@ export class NurbsCurve {
50
51
  * @param n - Number of points to generate
51
52
  * @returns Array of n points along the curve
52
53
  */
53
- sample(n: number): Point[] {
54
- ensureInit();
55
- const buf = wasm.sample_nurbs_curve(this._data, n);
56
- const pts: Point[] = [];
57
- for (let i = 0; i < buf.length; i += 3) {
58
- pts.push(new Point(buf[i], buf[i + 1], buf[i + 2]));
59
- }
60
- return pts;
61
- }
54
+ sample(n: number): Point[] {
55
+ ensureInit();
56
+ const buf = wasm.sample_nurbs_curve(this._data, n);
57
+ const pts: Point[] = [];
58
+ for (let i = 0; i < buf.length; i += 3) {
59
+ pts.push(new Point(buf[i], buf[i + 1], buf[i + 2]));
60
+ }
61
+ return pts;
62
+ }
63
+
64
+ /**
65
+ * Check if this NURBS curve is closed.
66
+ * @param eps - Tolerance for comparing start and end points
67
+ * @returns True if the curve closes on itself
68
+ */
69
+ isClosed(eps = 1e-10): boolean {
70
+ return this.pointAt(0).equals(this.pointAt(1), eps);
71
+ }
62
72
 
63
73
  /**
64
74
  * Find intersection points with a plane.
@@ -153,15 +163,25 @@ export class NurbsCurve {
153
163
  * @param samples - Number of sample points (default 64)
154
164
  * @returns Offset polyline approximation
155
165
  */
156
- offset(distance: number, normal?: Vec3, samples = 64): Polyline {
157
- const pts = this.sample(samples);
158
- const pl = new Polyline(pts);
159
- return pl.offset(distance, normal);
160
- }
161
-
162
- /**
163
- * Decode a NurbsCurve from WASM flat buffer.
164
- * @param data - Buffer in format [degree, num_cp, xyz..., w..., k...]
166
+ offset(distance: number, normal?: Vec3, samples = 64): Polyline {
167
+ const pts = this.sample(samples);
168
+ const pl = new Polyline(pts);
169
+ return pl.offset(distance, normal);
170
+ }
171
+
172
+ /**
173
+ * Extrude this closed NURBS curve into a trusted solid suitable for booleans.
174
+ * @param direction - Extrusion direction and magnitude
175
+ * @param segments - Number of curve samples used for tessellation (default 64)
176
+ * @returns Closed mesh solid ready for boolean operations
177
+ */
178
+ extrudeAsSolid(direction: Vec3, segments = 64): Mesh {
179
+ return Mesh.extrudeCurveAsSolid(this, direction, segments);
180
+ }
181
+
182
+ /**
183
+ * Decode a NurbsCurve from WASM flat buffer.
184
+ * @param data - Buffer in format [degree, num_cp, xyz..., w..., k...]
165
185
  * @returns New NurbsCurve instance
166
186
  */
167
187
  static fromData(data: Float64Array | number[]): NurbsCurve {
package/src/PolyCurve.ts CHANGED
@@ -1,14 +1,15 @@
1
1
  import { ensureInit } from "./engine.js";
2
2
  import { Point } from "./Point.js";
3
3
  import { Line } from "./Line.js";
4
- import { Arc } from "./Arc.js";
5
- import { Vec3 } from "./Vec3.js";
6
- import { NurbsCurve } from "./NurbsCurve.js";
7
- import type { Plane } from "./Plane.js";
8
- import type { CurveSegment, RotationAxis } from "./types.js";
9
- import { SegmentTypeCode } from "./types.js";
10
- import { pointsToCoords, coordsToPoints } from "./BufferCodec.js";
11
- import * as wasm from "./wasm-bindings.js";
4
+ import { Arc } from "./Arc.js";
5
+ import { Vec3 } from "./Vec3.js";
6
+ import { Mesh } from "./Mesh.js";
7
+ import { NurbsCurve } from "./NurbsCurve.js";
8
+ import type { Plane } from "./Plane.js";
9
+ import type { CurveSegment, RotationAxis } from "./types.js";
10
+ import { SegmentTypeCode } from "./types.js";
11
+ import { pointsToCoords } from "./BufferCodec.js";
12
+ import * as wasm from "./wasm-bindings.js";
12
13
 
13
14
  /**
14
15
  * Composite curve made of sequential Line and Arc segments.
@@ -108,10 +109,10 @@ export class PolyCurve {
108
109
  return new PolyCurve(this.segments.map(s => s.translate(offset)));
109
110
  }
110
111
 
111
- projectOntoPlane(plane: Plane, direction?: Vec3, arcSamples = 32): PolyCurve {
112
- const proj = (p: Point) => direction ? plane.projectPointAlongDirection(p, direction) : plane.projectPoint(p);
113
- const segs: CurveSegment[] = [];
114
- for (const s of this.segments) {
112
+ projectOntoPlane(plane: Plane, direction?: Vec3, arcSamples = 32): PolyCurve {
113
+ const proj = (p: Point) => direction ? plane.projectPointAlongDirection(p, direction) : plane.projectPoint(p);
114
+ const segs: CurveSegment[] = [];
115
+ for (const s of this.segments) {
115
116
  if (s instanceof Line) {
116
117
  segs.push(new Line(proj(s.start), proj(s.end)));
117
118
  } else {
@@ -121,14 +122,45 @@ export class PolyCurve {
121
122
  segs.push(new Line(proj(pts[i]), proj(pts[i + 1])));
122
123
  }
123
124
  }
124
- }
125
- return new PolyCurve(segs);
126
- }
127
-
128
- /**
129
- * Fillet corners of this PolyCurve with arcs of the given radius.
130
- * Extracts the vertex points, calls WASM fillet_corners, returns a new PolyCurve.
131
- */
125
+ }
126
+ return new PolyCurve(segs);
127
+ }
128
+
129
+ /**
130
+ * Extrude this polycurve along a direction vector.
131
+ * Curved segments are sampled to a polyline first, so `segments` controls
132
+ * the tessellation density used for arc segments before extrusion.
133
+ * @param direction - Extrusion direction and magnitude
134
+ * @param segments - Number of samples per arc segment (default 32)
135
+ * @param caps - Whether to cap closed profiles (default false)
136
+ * @returns Mesh representing the extruded surface
137
+ */
138
+ extrude(direction: Vec3, segments = 32, caps = false): Mesh {
139
+ ensureInit();
140
+ const arcSamples = Number.isFinite(segments) ? Math.max(2, Math.floor(segments)) : 32;
141
+ const buf = wasm.extrude_polyline(
142
+ pointsToCoords(this.sample(arcSamples)),
143
+ direction.x, direction.y, direction.z,
144
+ arcSamples, caps,
145
+ );
146
+ return Mesh.fromBuffer(buf);
147
+ }
148
+
149
+ /**
150
+ * Extrude this closed polycurve into a trusted solid suitable for booleans.
151
+ * Curved segments are sampled first, so `segments` controls tessellation density.
152
+ * @param direction - Extrusion direction and magnitude
153
+ * @param segments - Number of samples per curved segment (default 32)
154
+ * @returns Closed mesh solid ready for boolean operations
155
+ */
156
+ extrudeAsSolid(direction: Vec3, segments = 32): Mesh {
157
+ return Mesh.extrudeCurveAsSolid(this, direction, segments);
158
+ }
159
+
160
+ /**
161
+ * Fillet corners of this PolyCurve with arcs of the given radius.
162
+ * Extracts the vertex points, calls WASM fillet_corners, returns a new PolyCurve.
163
+ */
132
164
  fillet(radius: number, normal?: Vec3): PolyCurve {
133
165
  ensureInit();
134
166
  if (this.segments.length < 2) {
package/src/Polyline.ts CHANGED
@@ -57,19 +57,28 @@ export class Polyline {
57
57
  * @param caps - Whether to cap the ends (default false)
58
58
  * @returns Mesh representing the extruded surface
59
59
  */
60
- extrude(direction: Vec3, segments = 1, caps = false): Mesh {
61
- ensureInit();
62
- const buf = wasm.extrude_polyline(
63
- this.toCoords(),
64
- direction.x, direction.y, direction.z,
60
+ extrude(direction: Vec3, segments = 1, caps = false): Mesh {
61
+ ensureInit();
62
+ const buf = wasm.extrude_polyline(
63
+ this.toCoords(),
64
+ direction.x, direction.y, direction.z,
65
65
  segments, caps
66
- );
67
- return Mesh.fromBuffer(buf);
68
- }
69
-
70
- /**
71
- * Translate this polyline by an offset vector.
72
- * @param offset - Translation vector
66
+ );
67
+ return Mesh.fromBuffer(buf);
68
+ }
69
+
70
+ /**
71
+ * Extrude this closed polyline into a trusted solid suitable for booleans.
72
+ * @param direction - Extrusion direction and magnitude
73
+ * @returns Closed mesh solid ready for boolean operations
74
+ */
75
+ extrudeAsSolid(direction: Vec3): Mesh {
76
+ return Mesh.extrudeCurveAsSolid(this, direction);
77
+ }
78
+
79
+ /**
80
+ * Translate this polyline by an offset vector.
81
+ * @param offset - Translation vector
73
82
  * @returns New translated polyline
74
83
  */
75
84
  translate(offset: Vec3): Polyline {