okgeometry-api 1.21.0 → 1.22.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/package.json CHANGED
@@ -1,59 +1,60 @@
1
- {
2
- "name": "okgeometry-api",
3
- "version": "1.21.0",
4
- "description": "Geometry engine API for AEC applications - NURBS, meshes, booleans, intersections. Powered by Rust/WASM.",
5
- "type": "module",
6
- "main": "dist/index.js",
7
- "types": "dist/index.d.ts",
8
- "exports": {
9
- ".": {
10
- "types": "./dist/index.d.ts",
11
- "import": "./dist/index.js"
12
- }
13
- },
14
- "files": [
15
- "dist/",
16
- "src/"
17
- ],
18
- "scripts": {
19
- "build:wasm": "node -e \"require('fs').rmSync('wasm',{recursive:true,force:true})\" && cd .. && wasm-pack build --target web --out-dir okgeometry-api/wasm",
20
- "sync:wasm-bindings": "node scripts/sync-wasm-bindings.mjs",
21
- "inline-wasm": "tsx scripts/inline-wasm.ts",
22
- "build": "npm run build:wasm && npm run sync:wasm-bindings && npm run inline-wasm && tsc",
23
- "bench:boolean-heavy": "npm run build && tsx scripts/bench-boolean-heavy.ts",
24
- "bench:boolean-ab": "npm run build && tsx scripts/bench-boolean-ab.ts",
25
- "bench:boolean-batch": "npm run build && tsx scripts/bench-boolean-batch.ts",
26
- "bench:boolean-matrix": "tsx scripts/bench-boolean-matrix.ts",
27
- "smoke:brep-boolean": "tsx scripts/smoke-brep-boolean.ts",
28
- "prepublishOnly": "npm run build",
29
- "smoke:brep-parametric": "tsx scripts/smoke-brep-parametric.ts",
30
- "smoke:brep-from-mesh": "tsx scripts/smoke-brep-from-mesh.ts",
31
- "smoke:brep-faces": "tsx scripts/smoke-brep-faces.ts",
32
- "smoke:curve-intersections": "tsx scripts/smoke-curve-intersections.ts",
33
- "smoke:brep-intersections": "tsx scripts/smoke-brep-intersections.ts",
34
- "smoke:brep-sheet-boolean": "tsx scripts/smoke-brep-sheet-boolean.ts",
35
- "smoke:geometry-boolean": "tsx scripts/smoke-geometry-boolean.ts",
36
- "smoke:curve-region": "tsx scripts/smoke-curve-region.ts",
37
- "smoke:sculpt": "tsx scripts/smoke-sculpt.ts"
38
- },
39
- "keywords": [
40
- "geometry",
41
- "wasm",
42
- "nurbs",
43
- "mesh",
44
- "boolean",
45
- "cad",
46
- "aec",
47
- "3d"
48
- ],
49
- "author": "Mostafa El Ayoubi",
50
- "license": "Proprietary License",
51
- "repository": {
52
- "type": "git",
53
- "url": "https://www.orkestra.online"
54
- },
55
- "devDependencies": {
56
- "tsx": "^4.7.0",
57
- "typescript": "^5.4.0"
58
- }
59
- }
1
+ {
2
+ "name": "okgeometry-api",
3
+ "version": "1.22.0",
4
+ "description": "Geometry engine API for AEC applications - NURBS, meshes, booleans, intersections. Powered by Rust/WASM.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist/",
16
+ "src/"
17
+ ],
18
+ "scripts": {
19
+ "build:wasm": "node -e \"require('fs').rmSync('wasm',{recursive:true,force:true})\" && cd .. && wasm-pack build --target web --out-dir okgeometry-api/wasm",
20
+ "sync:wasm-bindings": "node scripts/sync-wasm-bindings.mjs",
21
+ "inline-wasm": "tsx scripts/inline-wasm.ts",
22
+ "build": "npm run build:wasm && npm run sync:wasm-bindings && npm run inline-wasm && tsc",
23
+ "bench:boolean-heavy": "npm run build && tsx scripts/bench-boolean-heavy.ts",
24
+ "bench:boolean-ab": "npm run build && tsx scripts/bench-boolean-ab.ts",
25
+ "bench:boolean-batch": "npm run build && tsx scripts/bench-boolean-batch.ts",
26
+ "bench:boolean-matrix": "tsx scripts/bench-boolean-matrix.ts",
27
+ "smoke:brep-boolean": "tsx scripts/smoke-brep-boolean.ts",
28
+ "prepublishOnly": "npm run build",
29
+ "smoke:brep-parametric": "tsx scripts/smoke-brep-parametric.ts",
30
+ "smoke:brep-from-mesh": "tsx scripts/smoke-brep-from-mesh.ts",
31
+ "smoke:brep-faces": "tsx scripts/smoke-brep-faces.ts",
32
+ "smoke:curve-intersections": "tsx scripts/smoke-curve-intersections.ts",
33
+ "smoke:brep-intersections": "tsx scripts/smoke-brep-intersections.ts",
34
+ "smoke:brep-sheet-boolean": "tsx scripts/smoke-brep-sheet-boolean.ts",
35
+ "smoke:geometry-boolean": "tsx scripts/smoke-geometry-boolean.ts",
36
+ "smoke:curve-region": "tsx scripts/smoke-curve-region.ts",
37
+ "smoke:sculpt": "tsx scripts/smoke-sculpt.ts",
38
+ "smoke:offset-chamfer": "tsx scripts/smoke-offset-chamfer.ts"
39
+ },
40
+ "keywords": [
41
+ "geometry",
42
+ "wasm",
43
+ "nurbs",
44
+ "mesh",
45
+ "boolean",
46
+ "cad",
47
+ "aec",
48
+ "3d"
49
+ ],
50
+ "author": "Mostafa El Ayoubi",
51
+ "license": "Proprietary License",
52
+ "repository": {
53
+ "type": "git",
54
+ "url": "https://www.orkestra.online"
55
+ },
56
+ "devDependencies": {
57
+ "tsx": "^4.7.0",
58
+ "typescript": "^5.4.0"
59
+ }
60
+ }
package/src/Mesh.ts CHANGED
@@ -3809,6 +3809,43 @@ export class Mesh {
3809
3809
  return wasm.mesh_boundary_edge_segments(this._vertexCount, this._buffer);
3810
3810
  }
3811
3811
 
3812
+ /**
3813
+ * Chamfer feature edges of this closed solid. Each entry in `edges` is one
3814
+ * picked segment `{ a, b }` on a feature edge; the kernel expands it to the
3815
+ * full chain separating the same two smooth face groups (a straight box
3816
+ * edge, or a whole tessellated rim loop from a single pick). `distance` is
3817
+ * the setback along each adjacent face, Rhino-style. Convex edges cut a
3818
+ * flat; concave edges fill the notch. The result is watertight.
3819
+ *
3820
+ * Throws for open meshes, smooth (non-feature) picks, or when the distance
3821
+ * exceeds what the adjacent faces allow.
3822
+ */
3823
+ chamferEdges(
3824
+ edges: Array<{ a: Point | Vec3; b: Point | Vec3 }>,
3825
+ distance: number,
3826
+ ): Mesh {
3827
+ ensureInit();
3828
+ if (edges.length === 0) {
3829
+ throw new Error("Mesh.chamferEdges() requires at least one edge");
3830
+ }
3831
+ const edgeBuf = new Float64Array(edges.length * 6);
3832
+ edges.forEach((edge, i) => {
3833
+ edgeBuf[i * 6] = edge.a.x;
3834
+ edgeBuf[i * 6 + 1] = edge.a.y;
3835
+ edgeBuf[i * 6 + 2] = edge.a.z;
3836
+ edgeBuf[i * 6 + 3] = edge.b.x;
3837
+ edgeBuf[i * 6 + 4] = edge.b.y;
3838
+ edgeBuf[i * 6 + 5] = edge.b.z;
3839
+ });
3840
+ const r = wasm.mesh_chamfer_edges(this._vertexCount, this._buffer, edgeBuf, distance);
3841
+ if (!r.length) {
3842
+ throw new Error(
3843
+ `Mesh.chamferEdges() failed: ${wasm.geometry_last_error() || "unknown error"}`,
3844
+ );
3845
+ }
3846
+ return Mesh.fromTrustedBuffer(r as Float64Array);
3847
+ }
3848
+
3812
3849
  /**
3813
3850
  * Geometry-preserving repair: weld coincident vertices, drop degenerate /
3814
3851
  * duplicate triangles, remove orphan vertices, make winding consistent +
package/src/NurbsCurve.ts CHANGED
@@ -394,32 +394,9 @@ export class NurbsCurve {
394
394
  );
395
395
  }
396
396
 
397
- /**
398
- * Offset this curve by a distance.
399
- * Note: NURBS offset is approximated by sampling and offsetting as polyline.
400
- * @param distance - Offset distance
401
- * @param normal - Reference normal for determining offset direction
402
- * @param samples - Number of sample points (default 64)
403
- * @param arcSamples - Number of samples per exact arc segment in the returned polyline(s)
404
- * @param options - Offset options such as join style
405
- * @returns Offset polyline approximation
406
- */
407
- offset(
408
- distance: number,
409
- normal?: Vec3,
410
- samples = 64,
411
- arcSamples = 32,
412
- options: CurveOffsetOptions = {},
413
- ): Polyline {
414
- const results = this.offsetAll(distance, normal, samples, arcSamples, options);
415
- if (results.length === 1) return results[0];
416
- if (results.length === 0) {
417
- throw new Error("NurbsCurve.offset() collapsed and produced no valid result polylines");
418
- }
419
- throw new Error(
420
- `NurbsCurve.offset() produced ${results.length} result polylines. Use NurbsCurve.offsetAll() instead.`,
421
- );
422
- }
397
+ // NOTE: `offset()` (exact NURBS offset, kernel-refit to tolerance) lives
398
+ // further down next to `data`. The former sampled-polyline `offset()` was
399
+ // replaced by it; the sampled multi-result path remains as `offsetAll()`.
423
400
 
424
401
  /**
425
402
  * Offset this curve by a distance and return every sampled result polyline.
@@ -566,6 +543,38 @@ export class NurbsCurve {
566
543
  return NurbsCurve.fromData(buf);
567
544
  }
568
545
 
546
+ /**
547
+ * Offset this planar curve by `distance` within the plane with the given
548
+ * `normal`. Positive distance offsets toward `normal × tangent` (the same
549
+ * convention as Polyline/PolyCurve offsets). The result is refit to
550
+ * `tolerance` (default 1e-6) against the exact offset locus; rational
551
+ * curves (exact arcs) offset exactly. Closed curves stay closed.
552
+ *
553
+ * Throws for non-planar or kinked curves, or when the offset cannot reach
554
+ * tolerance (for example offsetting inward past the curvature radius).
555
+ */
556
+ offset(
557
+ distance: number,
558
+ normal: Vec3 = Vec3.Z,
559
+ options: { tolerance?: number } = {},
560
+ ): NurbsCurve {
561
+ ensureInit();
562
+ const buf = wasm.nurbs_curve_offset(
563
+ this.data,
564
+ distance,
565
+ normal.x,
566
+ normal.y,
567
+ normal.z,
568
+ options.tolerance ?? 1e-6,
569
+ );
570
+ if (buf.length < 2) {
571
+ throw new Error(
572
+ `NurbsCurve.offset() failed: ${wasm.geometry_last_error() || "unknown error"}`,
573
+ );
574
+ }
575
+ return NurbsCurve.fromData(buf);
576
+ }
577
+
569
578
  /**
570
579
  * Get internal WASM data buffer.
571
580
  * For advanced use cases requiring direct WASM interop.
@@ -312,6 +312,26 @@ export class NurbsSurface implements Surface {
312
312
  return this._data;
313
313
  }
314
314
 
315
+ /**
316
+ * Offset this surface by `distance` along its oriented normal field,
317
+ * refit to `tolerance` (default 1e-4) against the exact offset. Rational
318
+ * surfaces (cylinders, spheres, revolves) offset exactly; closed seams
319
+ * stay closed and degenerate poles stay degenerate.
320
+ *
321
+ * Throws for creased surfaces or when the offset cannot reach tolerance
322
+ * (for example offsetting inward past the curvature radius).
323
+ */
324
+ offset(distance: number, options: { tolerance?: number } = {}): NurbsSurface {
325
+ ensureInit();
326
+ const buf = wasm.nurbs_surface_offset(this.data, distance, options.tolerance ?? 1e-4);
327
+ if (buf.length < 4) {
328
+ throw new Error(
329
+ `NurbsSurface.offset() failed: ${wasm.geometry_last_error() || "unknown error"}`,
330
+ );
331
+ }
332
+ return NurbsSurface.fromData(buf);
333
+ }
334
+
315
335
  // ── Analytic queries ──
316
336
 
317
337
  /**
package/src/Polyline.ts CHANGED
@@ -240,6 +240,51 @@ export class Polyline {
240
240
  return PolyCurve.fromSegmentData(buf);
241
241
  }
242
242
 
243
+ /**
244
+ * Chamfer corners of this polyline with straight cuts of the given
245
+ * setback distance (clamped per corner to half the shorter adjacent
246
+ * segment). Closed polylines (duplicated end point) chamfer every corner
247
+ * including the seam and stay closed; open polylines never touch their
248
+ * endpoints. `options.corners` selects vertex indices to chamfer
249
+ * (indices into the unique point list, seam duplicate excluded).
250
+ */
251
+ chamfer(distance: number, options: { corners?: number[] } = {}): Polyline {
252
+ ensureInit();
253
+ const pts = this.points;
254
+ if (pts.length < 3) {
255
+ throw new Error("Polyline.chamfer() requires at least 3 points");
256
+ }
257
+ const first = pts[0];
258
+ const last = pts[pts.length - 1];
259
+ const closed =
260
+ pts.length >= 4 &&
261
+ Math.abs(first.x - last.x) < 1e-10 &&
262
+ Math.abs(first.y - last.y) < 1e-10 &&
263
+ Math.abs(first.z - last.z) < 1e-10;
264
+ const unique = closed ? pts.slice(0, -1) : pts;
265
+ const coords = new Float64Array(unique.length * 3);
266
+ unique.forEach((p, i) => {
267
+ coords[i * 3] = p.x;
268
+ coords[i * 3 + 1] = p.y;
269
+ coords[i * 3 + 2] = p.z;
270
+ });
271
+ const cornerBuf = new Float64Array(options.corners ?? []);
272
+ const buf = wasm.chamfer_polyline_corners(coords, closed, distance, cornerBuf);
273
+ if (buf.length < 6) {
274
+ throw new Error(
275
+ `Polyline.chamfer() failed: ${wasm.geometry_last_error() || "unknown error"}`,
276
+ );
277
+ }
278
+ const out: Point[] = [];
279
+ for (let i = 0; i + 2 < buf.length; i += 3) {
280
+ out.push(new Point(buf[i], buf[i + 1], buf[i + 2]));
281
+ }
282
+ if (closed && out.length > 0) {
283
+ out.push(out[0]);
284
+ }
285
+ return new Polyline(out);
286
+ }
287
+
243
288
  /**
244
289
  * Convert this polyline to a PolyCurve (line segments only).
245
290
  * @returns PolyCurve with Line segments connecting consecutive points