okgeometry-api 1.33.0 → 1.34.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,69 +1,69 @@
1
- {
2
- "name": "okgeometry-api",
3
- "version": "1.33.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
- "smoke:rebuild": "tsx scripts/smoke-rebuild.ts",
40
- "smoke:mesh-residency": "tsx scripts/smoke-mesh-residency.ts",
41
- "build:wasm-threaded": "node scripts/build-threaded.mjs",
42
- "smoke:browser-threaded": "node scripts/browser-smoke-threaded.mjs",
43
- "smoke:browser-heavy": "node scripts/browser-smoke-heavy-worker.mjs",
44
- "smoke:thicken": "tsx scripts/smoke-thicken.ts",
45
- "bench:thicken": "tsx scripts/bench-thicken.ts",
46
- "smoke:sweep": "tsx scripts/smoke-sweep.ts"
47
- },
48
- "keywords": [
49
- "geometry",
50
- "wasm",
51
- "nurbs",
52
- "mesh",
53
- "boolean",
54
- "cad",
55
- "aec",
56
- "3d"
57
- ],
58
- "author": "Mostafa El Ayoubi",
59
- "license": "Proprietary License",
60
- "repository": {
61
- "type": "git",
62
- "url": "https://www.orkestra.online"
63
- },
64
- "devDependencies": {
65
- "tsx": "^4.7.0",
66
- "typescript": "^5.4.0",
67
- "playwright": "^1.49.0"
68
- }
69
- }
1
+ {
2
+ "name": "okgeometry-api",
3
+ "version": "1.34.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
+ "smoke:rebuild": "tsx scripts/smoke-rebuild.ts",
40
+ "smoke:mesh-residency": "tsx scripts/smoke-mesh-residency.ts",
41
+ "build:wasm-threaded": "node scripts/build-threaded.mjs",
42
+ "smoke:browser-threaded": "node scripts/browser-smoke-threaded.mjs",
43
+ "smoke:browser-heavy": "node scripts/browser-smoke-heavy-worker.mjs",
44
+ "smoke:thicken": "tsx scripts/smoke-thicken.ts",
45
+ "bench:thicken": "tsx scripts/bench-thicken.ts",
46
+ "smoke:sweep": "tsx scripts/smoke-sweep.ts"
47
+ },
48
+ "keywords": [
49
+ "geometry",
50
+ "wasm",
51
+ "nurbs",
52
+ "mesh",
53
+ "boolean",
54
+ "cad",
55
+ "aec",
56
+ "3d"
57
+ ],
58
+ "author": "Mostafa El Ayoubi",
59
+ "license": "Proprietary License",
60
+ "repository": {
61
+ "type": "git",
62
+ "url": "https://www.orkestra.online"
63
+ },
64
+ "devDependencies": {
65
+ "tsx": "^4.7.0",
66
+ "typescript": "^5.4.0",
67
+ "playwright": "^1.49.0"
68
+ }
69
+ }
package/src/Brep.ts CHANGED
@@ -2,10 +2,12 @@ import { ensureInit } from "./engine.js";
2
2
  import { Point } from "./Point.js";
3
3
  import { Vec3 } from "./Vec3.js";
4
4
  import { Plane } from "./Plane.js";
5
- import { Mesh, type MeshBooleanOptions } from "./Mesh.js";
5
+ import { Mesh, type MeshBooleanOptions, shouldFallbackFromWorkerFailure } from "./Mesh.js";
6
6
  import { Polyline } from "./Polyline.js";
7
7
  import { NurbsCurve } from "./NurbsCurve.js";
8
8
  import { NurbsSurface } from "./NurbsSurface.js";
9
+ import { MeshBooleanExecutionError, type MeshBooleanAsyncOptions } from "./mesh-boolean.protocol.js";
10
+ import { runMeshHeavyInWorkerPool } from "./mesh-boolean.pool.js";
9
11
  import { wasm } from "./wasm-active.js";
10
12
 
11
13
  /** Options for Brep boolean operations. */
@@ -26,6 +28,13 @@ export interface BrepParametricBooleanOptions {
26
28
  tolerance?: number;
27
29
  }
28
30
 
31
+ /**
32
+ * Options for the ASYNC parametric Brep booleans: the parametric options
33
+ * plus the worker-pool controls (signal, timeout, progress, pool sizing).
34
+ */
35
+ export type BrepParametricBooleanAsyncOptions = BrepParametricBooleanOptions &
36
+ Omit<MeshBooleanAsyncOptions, "limits" | "allowUnsafe" | "debugForceFaceID">;
37
+
29
38
  /** Options for mesh → BREP conversion ({@link Brep.fromMesh}). */
30
39
  export interface MeshToBrepConversionOptions {
31
40
  /**
@@ -1125,6 +1134,62 @@ export class Brep {
1125
1134
  return this.runParametricBoolean(other, "intersect", options);
1126
1135
  }
1127
1136
 
1137
+ private async runParametricBooleanAsync(
1138
+ other: Brep | Mesh,
1139
+ op: "union" | "subtract" | "intersect",
1140
+ options?: BrepParametricBooleanAsyncOptions,
1141
+ ): Promise<Brep> {
1142
+ ensureInit();
1143
+ const rhs = Brep.resolveParametricOperand(other);
1144
+ try {
1145
+ const { meta } = await runMeshHeavyInWorkerPool("brepBoolean", [], {
1146
+ signal: options?.signal,
1147
+ timeoutMs: options?.timeoutMs,
1148
+ workerFactory: options?.workerFactory,
1149
+ poolSize: options?.poolSize,
1150
+ onProgress: options?.onProgress,
1151
+ params: {
1152
+ brepA: this.json,
1153
+ brepB: rhs.json,
1154
+ brepOp: op,
1155
+ tolerance: options?.tolerance,
1156
+ },
1157
+ });
1158
+ const json = meta?.brepJson;
1159
+ if (typeof json !== "string" || json.length === 0) {
1160
+ throw new MeshBooleanExecutionError({
1161
+ code: "worker_protocol",
1162
+ message: `Brep.${op}BrepAsync worker returned no result body.`,
1163
+ });
1164
+ }
1165
+ return new Brep(json);
1166
+ } catch (error) {
1167
+ if (!shouldFallbackFromWorkerFailure(error)) throw error;
1168
+ console.warn(`Brep.${op}BrepAsync worker failed; falling back to main-thread boolean.`, error);
1169
+ return this.runParametricBoolean(other, op, options);
1170
+ }
1171
+ }
1172
+
1173
+ /**
1174
+ * {@link unionBrep} on the worker pool (non-blocking). Worker
1175
+ * INFRASTRUCTURE failures fall back to the synchronous main-thread path;
1176
+ * kernel-level failures propagate (they would fail again on the main
1177
+ * thread).
1178
+ */
1179
+ unionBrepAsync(other: Brep | Mesh, options?: BrepParametricBooleanAsyncOptions): Promise<Brep> {
1180
+ return this.runParametricBooleanAsync(other, "union", options);
1181
+ }
1182
+
1183
+ /** {@link subtractBrep} on the worker pool (non-blocking). See {@link unionBrepAsync}. */
1184
+ subtractBrepAsync(other: Brep | Mesh, options?: BrepParametricBooleanAsyncOptions): Promise<Brep> {
1185
+ return this.runParametricBooleanAsync(other, "subtract", options);
1186
+ }
1187
+
1188
+ /** {@link intersectBrep} on the worker pool (non-blocking). See {@link unionBrepAsync}. */
1189
+ intersectBrepAsync(other: Brep | Mesh, options?: BrepParametricBooleanAsyncOptions): Promise<Brep> {
1190
+ return this.runParametricBooleanAsync(other, "intersect", options);
1191
+ }
1192
+
1128
1193
  // ── Mesh → BREP and solid ÷ sheet split ──
1129
1194
 
1130
1195
  /**
package/src/Mesh.ts CHANGED
@@ -275,7 +275,13 @@ export interface MeshCurveSplitOptions extends MeshBooleanOptions {
275
275
  curveSegments?: number;
276
276
  }
277
277
 
278
- function shouldFallbackFromWorkerFailure(error: unknown): boolean {
278
+ /**
279
+ * Whether an async worker failure should fall back to the synchronous
280
+ * main-thread path: only worker INFRASTRUCTURE failures qualify — a
281
+ * kernel-level error would fail again on the main thread and must
282
+ * propagate instead. Shared by the Mesh and Brep async wrappers.
283
+ */
284
+ export function shouldFallbackFromWorkerFailure(error: unknown): boolean {
279
285
  if (!error || typeof error !== "object") return false;
280
286
  const code = "code" in error ? error.code : undefined;
281
287
  return code === "worker_unavailable" || code === "worker_crashed" || code === "worker_protocol";
@@ -129,13 +129,15 @@ export class MeshBooleanExecutionError extends Error {
129
129
  //
130
130
  // Multi-second kernel operations beyond pairwise booleans run on the same
131
131
  // worker pool: batch booleans, watertight repair, voxel remesh, solid edge
132
- // chamfer. Buffers are op-specific:
132
+ // chamfer, parametric BREP booleans. Buffers are op-specific:
133
133
  // unionAll: [mesh0, mesh1, ...]
134
134
  // differenceAll: [host, cutter0, cutter1, ...]
135
135
  // makeWatertight: [mesh]
136
136
  // voxelRemesh: [mesh]
137
137
  // chamferEdges: [mesh, edgeSegments] (flat [ax,ay,az,bx,by,bz] per edge)
138
138
  // filletEdges: [mesh, edgeSegments] (same layout, radius + segments in params)
139
+ // brepBoolean: [] (both operands ride `params` as BREP JSON strings;
140
+ // the result BREP JSON returns in the response `meta`)
139
141
 
140
142
  export type MeshHeavyOperation =
141
143
  | "unionAll"
@@ -143,7 +145,8 @@ export type MeshHeavyOperation =
143
145
  | "makeWatertight"
144
146
  | "voxelRemesh"
145
147
  | "chamferEdges"
146
- | "filletEdges";
148
+ | "filletEdges"
149
+ | "brepBoolean";
147
150
 
148
151
  export interface MeshHeavyParams {
149
152
  resolution?: number;
@@ -151,6 +154,14 @@ export interface MeshHeavyParams {
151
154
  distance?: number;
152
155
  radius?: number;
153
156
  segments?: number;
157
+ /** brepBoolean: operand A as BREP JSON. */
158
+ brepA?: string;
159
+ /** brepBoolean: operand B as BREP JSON. */
160
+ brepB?: string;
161
+ /** brepBoolean: which parametric boolean to run. */
162
+ brepOp?: "union" | "subtract" | "intersect";
163
+ /** brepBoolean: absolute geometric tolerance (default 1e-6). */
164
+ tolerance?: number;
154
165
  }
155
166
 
156
167
  export interface MeshHeavyWorkerRequest {
@@ -1,4 +1,5 @@
1
1
  import { init, isInitialized } from "./engine.js";
2
+ import { Brep } from "./Brep.js";
2
3
  import { Mesh } from "./Mesh.js";
3
4
  import { Point } from "./Point.js";
4
5
  import type {
@@ -77,7 +78,17 @@ function runHeavyOperation(message: {
77
78
  operation: string;
78
79
  buffers: ArrayBuffer[];
79
80
  trusted?: boolean[];
80
- params?: { resolution?: number; iso?: number; distance?: number; radius?: number; segments?: number };
81
+ params?: {
82
+ resolution?: number;
83
+ iso?: number;
84
+ distance?: number;
85
+ radius?: number;
86
+ segments?: number;
87
+ brepA?: string;
88
+ brepB?: string;
89
+ brepOp?: "union" | "subtract" | "intersect";
90
+ tolerance?: number;
91
+ };
81
92
  options: { allowUnsafe?: boolean; limits?: unknown; debugForceFaceID?: boolean };
82
93
  }): { buffer: ArrayBuffer; meta?: Record<string, string | number | boolean> } {
83
94
  const meshes = () =>
@@ -134,6 +145,24 @@ function runHeavyOperation(message: {
134
145
  const result = mesh.chamferEdges(edges, params.distance ?? 0);
135
146
  return { buffer: new Float64Array(result.rawBuffer).buffer };
136
147
  }
148
+ case "brepBoolean": {
149
+ // Parametric BREP boolean: operands ride params as JSON strings; the
150
+ // result BREP JSON returns via meta (the buffer channel stays empty).
151
+ const { brepA, brepB, brepOp } = params;
152
+ if (!brepA || !brepB || !brepOp) {
153
+ throw new Error("brepBoolean requires brepA, brepB and brepOp params");
154
+ }
155
+ const a = Brep.fromJson(brepA);
156
+ const b = Brep.fromJson(brepB);
157
+ const opts = params.tolerance !== undefined ? { tolerance: params.tolerance } : undefined;
158
+ const result =
159
+ brepOp === "union"
160
+ ? a.unionBrep(b, opts)
161
+ : brepOp === "subtract"
162
+ ? a.subtractBrep(b, opts)
163
+ : a.intersectBrep(b, opts);
164
+ return { buffer: new ArrayBuffer(0), meta: { brepJson: result.toJson() } };
165
+ }
137
166
  case "filletEdges": {
138
167
  const mesh = Mesh.fromBuffer(new Float64Array(message.buffers[0]), {
139
168
  trustedBooleanInput: message.trusted?.[0],