@treasuryspatial/rhino-bridge 0.1.4 → 0.1.5

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/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as THREE from "three";
2
- export type RhinoBridgeOptions = {
2
+ export type GeometryBridgeOptions = {
3
3
  wasmPath?: string;
4
4
  wasmUrl?: string;
5
5
  rotateToYUp?: boolean;
@@ -12,6 +12,7 @@ export type RhinoBridgeOptions = {
12
12
  packetKey?: string;
13
13
  layerKey?: string;
14
14
  materialKey?: string;
15
+ curveMode?: "auto" | "always" | "never";
15
16
  };
16
17
  export type LayeredGeometryOptions = {
17
18
  mapLayerToId?: (layerName: string, attributes: any) => string | null;
@@ -23,18 +24,35 @@ export type LayeredGeometryOptions = {
23
24
  layerKey?: string;
24
25
  materialKey?: string;
25
26
  };
26
- export declare function threeGroupFrom3dm(base64: string, options?: RhinoBridgeOptions): Promise<THREE.Group>;
27
- export type Rhino3dmInspection = {
27
+ export declare function threeGroupFrom3dm(base64: string, options?: GeometryBridgeOptions): Promise<THREE.Group>;
28
+ export type Geometry3dmInspection = {
28
29
  ok: boolean;
29
30
  meshCount: number;
30
31
  brepCount: number;
31
32
  extrusionCount: number;
32
33
  otherCount: number;
33
34
  };
34
- export declare function inspect3dmArrayBuffer(buffer: ArrayBuffer | Uint8Array): Promise<Rhino3dmInspection>;
35
- export declare function inspect3dmBase64(base64: string): Promise<Rhino3dmInspection>;
35
+ export type GrasshopperTo3dmStats = {
36
+ decodedObjects: number;
37
+ meshesAdded: number;
38
+ brepsMeshed: number;
39
+ brepsSkipped: number;
40
+ unsupported: Record<string, number>;
41
+ errors: string[];
42
+ };
43
+ export declare function grasshopperResultTo3dmBase64(result: {
44
+ values?: any[];
45
+ } | null | undefined): Promise<{
46
+ geometry3dm: string;
47
+ stats: GrasshopperTo3dmStats;
48
+ }>;
49
+ export declare function inspect3dmArrayBuffer(buffer: ArrayBuffer | Uint8Array): Promise<Geometry3dmInspection>;
50
+ export declare function inspect3dmBase64(base64: string): Promise<Geometry3dmInspection>;
36
51
  export declare function loadLayeredGeometry(geometry3dm: string, options?: LayeredGeometryOptions): Promise<Record<string, THREE.Group>>;
37
- export declare function extractBrepsByRole(base64: string, options?: RhinoBridgeOptions): Promise<Record<string, string>>;
38
- declare function convertGeometryToThree(geometry: any): THREE.Object3D | null;
52
+ export declare function extractBrepsByRole(base64: string, options?: GeometryBridgeOptions): Promise<Record<string, string>>;
53
+ type ConvertGeometryOptions = {
54
+ allowCurves?: boolean;
55
+ };
56
+ declare function convertGeometryToThree(geometry: any, options?: ConvertGeometryOptions): THREE.Object3D | null;
39
57
  export { convertGeometryToThree };
40
58
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AA0B/B,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,QAAQ,KAAK,IAAI,CAAC;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AA2FF,MAAM,MAAM,sBAAsB,GAAG;IACnC,YAAY,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,KAAK,MAAM,GAAG,IAAI,CAAC;IACrE,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,QAAQ,KAAK,IAAI,CAAC;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAUF,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAqD1G;AAED,MAAM,MAAM,kBAAkB,GAAG;IAC/B,EAAE,EAAE,OAAO,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAsCF,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,WAAW,GAAG,UAAU,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAQzG;AAED,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAGlF;AAED,wBAAsB,mBAAmB,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,sBAAsB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAyErI;AAED,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAgDtH;AAED,iBAAS,sBAAsB,CAAC,QAAQ,EAAE,GAAG,GAAG,KAAK,CAAC,QAAQ,GAAG,IAAI,CAkBpE;AAyGD,OAAO,EAAE,sBAAsB,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AA0B/B,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,QAAQ,KAAK,IAAI,CAAC;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;CACzC,CAAC;AA2FF,MAAM,MAAM,sBAAsB,GAAG;IACnC,YAAY,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,KAAK,MAAM,GAAG,IAAI,CAAC;IACrE,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,QAAQ,KAAK,IAAI,CAAC;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAUF,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAqE7G;AAED,MAAM,MAAM,qBAAqB,GAAG;IAClC,EAAE,EAAE,OAAO,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC;AAEF,wBAAsB,4BAA4B,CAChD,MAAM,EAAE;IAAE,MAAM,CAAC,EAAE,GAAG,EAAE,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,GAC5C,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,qBAAqB,CAAA;CAAE,CAAC,CAuHhE;AAsCD,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,WAAW,GAAG,UAAU,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAQ5G;AAED,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAGrF;AAED,wBAAsB,mBAAmB,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,sBAAsB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAyErI;AAED,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAgDzH;AAED,KAAK,sBAAsB,GAAG;IAC5B,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAgIF,iBAAS,sBAAsB,CAAC,QAAQ,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,sBAAsB,GAAG,KAAK,CAAC,QAAQ,GAAG,IAAI,CAuCtG;AAyGD,OAAO,EAAE,sBAAsB,EAAE,CAAC"}
package/dist/index.js CHANGED
@@ -1,53 +1,53 @@
1
1
  import * as THREE from "three";
2
2
  import rhino3dm from "rhino3dm";
3
- const DEFAULT_BROWSER_WASM_PATH = "/rhino3dm.wasm";
3
+ const DEFAULT_BROWSER_WASM_PATH = "/geometry.wasm";
4
+ const DEFAULT_BROWSER_WASM_FILENAME = DEFAULT_BROWSER_WASM_PATH.split("/").pop() ?? "geometry.wasm";
4
5
  const DEFAULT_ROLE_KEY = "b2:role";
5
6
  const DEFAULT_GEOMETRY_ROLE = "geometry";
6
7
  const DEFAULT_ID_KEY = "b2:id";
7
8
  const trimTrailingSlashes = (value) => value.replace(/\/+$/, "");
8
9
  const trimLeadingSlashes = (value) => value.replace(/^\/+/, "");
9
10
  const resolveBrowserWasmUrl = (path, override) => {
10
- const envBase = override?.trim() || process.env.NEXT_PUBLIC_RHINO3DM_WASM_URL?.trim() || process.env.RHINO3DM_WASM_URL?.trim();
11
- const normalizedPath = trimLeadingSlashes(path);
11
+ const envBase = override?.trim() || process.env.NEXT_PUBLIC_GEOMETRY_WASM_URL?.trim() || process.env.GEOMETRY_WASM_URL?.trim();
12
12
  if (envBase && envBase.length > 0) {
13
13
  if (envBase.endsWith(".wasm"))
14
14
  return envBase;
15
15
  const base = trimTrailingSlashes(envBase);
16
- return `${base}/${normalizedPath}`;
16
+ return `${base}/${DEFAULT_BROWSER_WASM_FILENAME}`;
17
17
  }
18
- return `${DEFAULT_BROWSER_WASM_PATH.replace(/[^/]+$/, "")}${normalizedPath}`;
18
+ return DEFAULT_BROWSER_WASM_PATH;
19
19
  };
20
- let rhinoModulePromise = null;
21
- async function getRhinoModule(options) {
22
- if (!rhinoModulePromise) {
20
+ let geometryModulePromise = null;
21
+ async function getGeometryModule(options) {
22
+ if (!geometryModulePromise) {
23
23
  if (typeof window === "undefined") {
24
24
  const pathModule = await import("path");
25
25
  const fs = await import("fs");
26
26
  const wasmPath = options?.wasmPath
27
27
  ? pathModule.resolve(process.cwd(), options.wasmPath)
28
- : process.env.RHINO3DM_WASM_PATH
29
- ? pathModule.resolve(process.cwd(), process.env.RHINO3DM_WASM_PATH)
30
- : pathModule.resolve(process.cwd(), "public", "rhino3dm.wasm");
28
+ : process.env.GEOMETRY_WASM_PATH
29
+ ? pathModule.resolve(process.cwd(), process.env.GEOMETRY_WASM_PATH)
30
+ : pathModule.resolve(process.cwd(), "public", "geometry.wasm");
31
31
  let wasmBinary = null;
32
32
  try {
33
33
  const buffer = fs.readFileSync(wasmPath);
34
34
  wasmBinary = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
35
35
  }
36
36
  catch (error) {
37
- console.error("[rhino3dm] Failed to read WASM binary from", wasmPath, error);
37
+ console.error("[geometry] Failed to read WASM binary from", wasmPath, error);
38
38
  }
39
- rhinoModulePromise = rhino3dm({
39
+ geometryModulePromise = rhino3dm({
40
40
  wasmBinary: wasmBinary ?? undefined,
41
41
  locateFile: (path) => (wasmBinary ? wasmPath : resolveBrowserWasmUrl(path, options?.wasmUrl)),
42
42
  });
43
43
  }
44
44
  else {
45
- rhinoModulePromise = rhino3dm({
45
+ geometryModulePromise = rhino3dm({
46
46
  locateFile: (path) => resolveBrowserWasmUrl(path, options?.wasmUrl),
47
47
  });
48
48
  }
49
49
  }
50
- return rhinoModulePromise;
50
+ return geometryModulePromise;
51
51
  }
52
52
  const base64ToUint8Array = (base64) => {
53
53
  const normalized = base64.replace(/\s/g, "");
@@ -96,7 +96,7 @@ const getUserString = (attributes, key) => {
96
96
  }
97
97
  }
98
98
  catch (error) {
99
- console.warn("[rhino3dm] Unable to read user string", key, error);
99
+ console.warn("[geometry] Unable to read user string", key, error);
100
100
  }
101
101
  return null;
102
102
  };
@@ -109,12 +109,12 @@ const resolveUserString = (attributes, keys) => {
109
109
  return null;
110
110
  };
111
111
  export async function threeGroupFrom3dm(base64, options) {
112
- const rhino = await getRhinoModule(options);
112
+ const rhino = await getGeometryModule(options);
113
113
  const bytes = base64ToUint8Array(base64);
114
114
  const doc = rhino.File3dm.fromByteArray(bytes);
115
115
  const group = new THREE.Group();
116
116
  if (!doc) {
117
- console.error("[rhino3dm] Failed to parse 3DM file from bytes");
117
+ console.error("[geometry] Failed to parse 3DM file from bytes");
118
118
  return group;
119
119
  }
120
120
  const rotation = options?.rotateToYUp === false ? null : new THREE.Matrix4().makeRotationX(-Math.PI / 2);
@@ -127,6 +127,23 @@ export async function threeGroupFrom3dm(base64, options) {
127
127
  const materialKey = options?.materialKey ?? "b2:material";
128
128
  const objects = doc.objects();
129
129
  const objectCount = objects.count ?? 0;
130
+ const curveMode = options?.curveMode ?? "auto";
131
+ let allowCurves = curveMode === "always";
132
+ if (curveMode === "auto") {
133
+ allowCurves = true;
134
+ for (let i = 0; i < objectCount; i += 1) {
135
+ const obj = objects.get(i);
136
+ const geom = obj?.geometry?.();
137
+ const typeName = geom?.constructor?.name;
138
+ if (typeName === "Mesh") {
139
+ allowCurves = false;
140
+ break;
141
+ }
142
+ }
143
+ }
144
+ else if (curveMode === "never") {
145
+ allowCurves = false;
146
+ }
130
147
  for (let i = 0; i < objectCount; i += 1) {
131
148
  const obj = objects.get(i);
132
149
  const attributes = typeof obj?.attributes === "function" ? obj.attributes() : null;
@@ -135,7 +152,7 @@ export async function threeGroupFrom3dm(base64, options) {
135
152
  continue;
136
153
  }
137
154
  const geom = obj?.geometry?.();
138
- const threeObject = convertGeometryToThree(geom);
155
+ const threeObject = convertGeometryToThree(geom, { allowCurves });
139
156
  if (threeObject) {
140
157
  if (rotation) {
141
158
  threeObject.applyMatrix4(rotation);
@@ -156,7 +173,124 @@ export async function threeGroupFrom3dm(base64, options) {
156
173
  }
157
174
  return group;
158
175
  }
159
- const countRhinoObjects = (doc) => {
176
+ export async function grasshopperResultTo3dmBase64(result) {
177
+ const rhino = await getGeometryModule();
178
+ const stats = {
179
+ decodedObjects: 0,
180
+ meshesAdded: 0,
181
+ brepsMeshed: 0,
182
+ brepsSkipped: 0,
183
+ unsupported: {},
184
+ errors: [],
185
+ };
186
+ const doc = new rhino.File3dm();
187
+ const values = Array.isArray(result?.values) ? result?.values ?? [] : [];
188
+ if (!values.length) {
189
+ return { geometry3dm: "", stats };
190
+ }
191
+ const addObject = (geometry, name) => {
192
+ try {
193
+ if (name && rhino.ObjectAttributes) {
194
+ const attributes = new rhino.ObjectAttributes();
195
+ attributes.name = name;
196
+ doc.objects().add(geometry, attributes);
197
+ }
198
+ else {
199
+ doc.objects().add(geometry, null);
200
+ }
201
+ }
202
+ catch (e) {
203
+ stats.errors.push(`Failed to add object to 3dm: ${e?.message ?? String(e)}`);
204
+ }
205
+ };
206
+ const tryMeshBrep = (brep) => {
207
+ try {
208
+ const createFromBrep = rhino.Mesh?.createFromBrep;
209
+ if (typeof createFromBrep !== "function")
210
+ return null;
211
+ const mp = rhino.MeshingParameters?.default ?? null;
212
+ try {
213
+ const meshes = mp ? createFromBrep(brep, mp) : createFromBrep(brep);
214
+ return Array.isArray(meshes) ? meshes.filter(Boolean) : null;
215
+ }
216
+ catch {
217
+ const meshes = createFromBrep(brep);
218
+ return Array.isArray(meshes) ? meshes.filter(Boolean) : null;
219
+ }
220
+ }
221
+ catch (e) {
222
+ stats.errors.push(`Failed to mesh Brep: ${e?.message ?? String(e)}`);
223
+ return null;
224
+ }
225
+ };
226
+ for (const output of values) {
227
+ const paramName = String(output?.ParamName ?? "output");
228
+ const tree = output?.InnerTree ?? {};
229
+ for (const path of Object.keys(tree)) {
230
+ const items = Array.isArray(tree[path]) ? tree[path] : [];
231
+ for (let idx = 0; idx < items.length; idx += 1) {
232
+ const item = items[idx];
233
+ const itemType = String(item?.type ?? "");
234
+ const rawData = item?.data;
235
+ if (!itemType.startsWith("Rhino.Geometry"))
236
+ continue;
237
+ let json = null;
238
+ try {
239
+ if (typeof rawData === "string") {
240
+ json = JSON.parse(rawData);
241
+ }
242
+ else if (typeof rawData === "object") {
243
+ json = rawData;
244
+ }
245
+ }
246
+ catch (e) {
247
+ stats.errors.push(`Failed to parse geometry JSON (${paramName} ${path} ${idx}): ${e?.message ?? String(e)}`);
248
+ continue;
249
+ }
250
+ if (!json)
251
+ continue;
252
+ let decoded = null;
253
+ try {
254
+ decoded = rhino.CommonObject.decode(json);
255
+ }
256
+ catch (e) {
257
+ stats.errors.push(`Failed to decode geometry (${paramName} ${path} ${idx}): ${e?.message ?? String(e)}`);
258
+ continue;
259
+ }
260
+ if (!decoded)
261
+ continue;
262
+ stats.decodedObjects += 1;
263
+ const typeName = decoded?.constructor?.name ?? "unknown";
264
+ if (typeName === "Mesh") {
265
+ addObject(decoded, `${paramName}[${path}][${idx}]`);
266
+ stats.meshesAdded += 1;
267
+ continue;
268
+ }
269
+ if (typeName === "Brep") {
270
+ const meshes = tryMeshBrep(decoded);
271
+ if (meshes?.length) {
272
+ stats.brepsMeshed += 1;
273
+ for (let mi = 0; mi < meshes.length; mi += 1) {
274
+ addObject(meshes[mi], `${paramName}[${path}][${idx}]::mesh${mi}`);
275
+ stats.meshesAdded += 1;
276
+ }
277
+ }
278
+ else {
279
+ addObject(decoded, `${paramName}[${path}][${idx}]::brep`);
280
+ stats.brepsSkipped += 1;
281
+ }
282
+ continue;
283
+ }
284
+ stats.unsupported[typeName] = (stats.unsupported[typeName] ?? 0) + 1;
285
+ addObject(decoded, `${paramName}[${path}][${idx}]::${typeName}`);
286
+ }
287
+ }
288
+ }
289
+ const bytes = doc.toByteArray();
290
+ const geometry3dm = bytes?.length ? uint8ArrayToBase64(new Uint8Array(bytes)) : "";
291
+ return { geometry3dm, stats };
292
+ }
293
+ const countGeometryObjects = (doc) => {
160
294
  const objects = doc?.objects?.();
161
295
  const total = objects?.count ?? 0;
162
296
  let meshCount = 0;
@@ -190,25 +324,25 @@ const countRhinoObjects = (doc) => {
190
324
  };
191
325
  };
192
326
  export async function inspect3dmArrayBuffer(buffer) {
193
- const rhino = await getRhinoModule();
327
+ const rhino = await getGeometryModule();
194
328
  const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
195
329
  const doc = rhino.File3dm.fromByteArray(bytes);
196
330
  if (!doc) {
197
331
  return { ok: false, meshCount: 0, brepCount: 0, extrusionCount: 0, otherCount: 0 };
198
332
  }
199
- return countRhinoObjects(doc);
333
+ return countGeometryObjects(doc);
200
334
  }
201
335
  export async function inspect3dmBase64(base64) {
202
336
  const bytes = base64ToUint8Array(base64);
203
337
  return inspect3dmArrayBuffer(bytes);
204
338
  }
205
339
  export async function loadLayeredGeometry(geometry3dm, options) {
206
- const rhino = await getRhinoModule();
340
+ const rhino = await getGeometryModule();
207
341
  const bytes = base64ToUint8Array(geometry3dm);
208
342
  const doc = rhino.File3dm.fromByteArray(bytes);
209
343
  const groups = {};
210
344
  if (!doc) {
211
- console.error("[rhino3dm] Failed to parse 3DM file");
345
+ console.error("[geometry] Failed to parse 3DM file");
212
346
  return groups;
213
347
  }
214
348
  const layerTable = doc.layers?.();
@@ -268,12 +402,12 @@ export async function loadLayeredGeometry(geometry3dm, options) {
268
402
  return groups;
269
403
  }
270
404
  export async function extractBrepsByRole(base64, options) {
271
- const rhino = await getRhinoModule(options);
405
+ const rhino = await getGeometryModule(options);
272
406
  const bytes = base64ToUint8Array(base64);
273
407
  const doc = rhino.File3dm.fromByteArray(bytes);
274
408
  const breps = {};
275
409
  if (!doc) {
276
- console.error("[rhino3dm] Failed to parse 3DM file for Brep extraction");
410
+ console.error("[geometry] Failed to parse 3DM file for Brep extraction");
277
411
  return breps;
278
412
  }
279
413
  const roleKeys = options?.roleKeys ?? [];
@@ -311,7 +445,134 @@ export async function extractBrepsByRole(base64, options) {
311
445
  }
312
446
  return breps;
313
447
  }
314
- function convertGeometryToThree(geometry) {
448
+ const extractPolylinePoints = (polyline) => {
449
+ if (!polyline)
450
+ return null;
451
+ const points = [];
452
+ const count = typeof polyline.count === "number" ? polyline.count : typeof polyline.pointCount === "number" ? polyline.pointCount : 0;
453
+ const getter = typeof polyline.get === "function" ? polyline.get.bind(polyline) : null;
454
+ const pointGetter = typeof polyline.point === "function" ? polyline.point.bind(polyline) : null;
455
+ for (let i = 0; i < count; i += 1) {
456
+ const raw = getter ? getter(i) : pointGetter ? pointGetter(i) : null;
457
+ if (Array.isArray(raw) && raw.length >= 3) {
458
+ const x = Number(raw[0]);
459
+ const y = Number(raw[1]);
460
+ const z = Number(raw[2]);
461
+ if (Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z)) {
462
+ points.push([x, y, z]);
463
+ }
464
+ }
465
+ }
466
+ return points.length > 1 ? points : null;
467
+ };
468
+ const extractCurvePoints = (curve) => {
469
+ if (!curve)
470
+ return null;
471
+ try {
472
+ if (typeof curve.ToPolyline === "function") {
473
+ const polyline = curve.ToPolyline();
474
+ const points = extractPolylinePoints(polyline);
475
+ if (points)
476
+ return points;
477
+ }
478
+ if (typeof curve.toPolyline === "function") {
479
+ const polyline = curve.toPolyline();
480
+ const points = extractPolylinePoints(polyline);
481
+ if (points)
482
+ return points;
483
+ }
484
+ if (typeof curve.tryGetPolyline === "function") {
485
+ const result = curve.tryGetPolyline();
486
+ const success = Array.isArray(result) ? result[0] : result?.[0];
487
+ const polyline = Array.isArray(result) ? result[1] : result?.[1];
488
+ if (success && polyline) {
489
+ const points = extractPolylinePoints(polyline);
490
+ if (points)
491
+ return points;
492
+ }
493
+ }
494
+ }
495
+ catch {
496
+ // fall through to sampling
497
+ }
498
+ if (typeof curve.pointAt === "function" && Array.isArray(curve.domain) && curve.domain.length >= 2) {
499
+ const t0 = Number(curve.domain[0]);
500
+ const t1 = Number(curve.domain[1]);
501
+ if (Number.isFinite(t0) && Number.isFinite(t1) && t0 !== t1) {
502
+ const segments = 64;
503
+ const points = [];
504
+ for (let i = 0; i <= segments; i += 1) {
505
+ const t = t0 + ((t1 - t0) * i) / segments;
506
+ const raw = curve.pointAt(t);
507
+ if (Array.isArray(raw) && raw.length >= 3) {
508
+ const x = Number(raw[0]);
509
+ const y = Number(raw[1]);
510
+ const z = Number(raw[2]);
511
+ if (Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z)) {
512
+ points.push([x, y, z]);
513
+ }
514
+ }
515
+ }
516
+ if (points.length > 1) {
517
+ if (curve.isClosed && points.length > 2) {
518
+ const first = points[0];
519
+ const last = points[points.length - 1];
520
+ if (first[0] !== last[0] || first[1] !== last[1] || first[2] !== last[2]) {
521
+ points.push([...first]);
522
+ }
523
+ }
524
+ return points;
525
+ }
526
+ }
527
+ }
528
+ return null;
529
+ };
530
+ const pointsToLine = (points) => {
531
+ if (!points || points.length < 2)
532
+ return null;
533
+ const positions = new Float32Array(points.length * 3);
534
+ points.forEach((point, index) => {
535
+ positions[index * 3 + 0] = point[0];
536
+ positions[index * 3 + 1] = point[1];
537
+ positions[index * 3 + 2] = point[2];
538
+ });
539
+ const geometry = new THREE.BufferGeometry();
540
+ geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));
541
+ const material = new THREE.LineBasicMaterial({
542
+ color: 0x1f2937,
543
+ transparent: true,
544
+ opacity: 1,
545
+ toneMapped: false,
546
+ depthTest: false,
547
+ depthWrite: false,
548
+ });
549
+ const line = new THREE.Line(geometry, material);
550
+ line.renderOrder = 10;
551
+ return line;
552
+ };
553
+ const pointsToPoints = (points) => {
554
+ if (!points || points.length === 0)
555
+ return null;
556
+ const positions = new Float32Array(points.length * 3);
557
+ points.forEach((point, index) => {
558
+ positions[index * 3 + 0] = point[0];
559
+ positions[index * 3 + 1] = point[1];
560
+ positions[index * 3 + 2] = point[2];
561
+ });
562
+ const geometry = new THREE.BufferGeometry();
563
+ geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));
564
+ const material = new THREE.PointsMaterial({
565
+ color: 0x1f2937,
566
+ size: 3,
567
+ sizeAttenuation: false,
568
+ depthTest: false,
569
+ depthWrite: false,
570
+ });
571
+ const pointsObject = new THREE.Points(geometry, material);
572
+ pointsObject.renderOrder = 10;
573
+ return pointsObject;
574
+ };
575
+ function convertGeometryToThree(geometry, options) {
315
576
  if (!geometry) {
316
577
  return null;
317
578
  }
@@ -319,24 +580,44 @@ function convertGeometryToThree(geometry) {
319
580
  if (typeName === "Mesh") {
320
581
  return meshToThree(geometry);
321
582
  }
583
+ if (options?.allowCurves) {
584
+ if (typeName === "Point") {
585
+ const location = geometry.location;
586
+ if (Array.isArray(location) && location.length >= 3) {
587
+ return pointsToPoints([[Number(location[0]), Number(location[1]), Number(location[2])]]);
588
+ }
589
+ }
590
+ if (typeName === "PointCloud" && typeof geometry.getPoints === "function") {
591
+ const points = geometry.getPoints();
592
+ if (Array.isArray(points) && points.length > 0) {
593
+ return pointsToPoints(points);
594
+ }
595
+ }
596
+ if (typeName === "Curve" || typeName === "PolylineCurve" || typeName === "LineCurve" || typeName === "ArcCurve" || typeName === "PolyCurve" || typeName === "NurbsCurve") {
597
+ const points = extractCurvePoints(geometry);
598
+ if (points) {
599
+ return pointsToLine(points);
600
+ }
601
+ }
602
+ }
322
603
  if (typeName === "Brep" || typeName === "Extrusion") {
323
- console.warn("[rhino3dm] Non-meshed geometry received. Ensure server-side meshing before serialization.");
604
+ console.warn("[geometry] Non-meshed geometry received. Ensure server-side meshing before serialization.");
324
605
  return null;
325
606
  }
326
- console.warn(`[rhino3dm] Unsupported geometry type: ${typeName}`);
607
+ console.warn(`[geometry] Unsupported geometry type: ${typeName}`);
327
608
  return null;
328
609
  }
329
610
  function meshToThree(mesh) {
330
611
  const vertices = mesh.vertices?.();
331
612
  const faces = mesh.faces?.();
332
613
  if (!vertices || !faces) {
333
- console.warn("[rhino3dm] Mesh missing vertices or faces");
614
+ console.warn("[geometry] Mesh missing vertices or faces");
334
615
  return null;
335
616
  }
336
617
  const vertexCount = vertices.count ?? 0;
337
618
  const faceCount = faces.count ?? 0;
338
619
  if (vertexCount === 0 || faceCount === 0) {
339
- console.warn("[rhino3dm] Mesh received with no vertices or faces; skipping");
620
+ console.warn("[geometry] Mesh received with no vertices or faces; skipping");
340
621
  return null;
341
622
  }
342
623
  const geometry = new THREE.BufferGeometry();
@@ -372,7 +653,7 @@ function meshToThree(mesh) {
372
653
  };
373
654
  for (let i = 0; i < vertexCount; i += 1) {
374
655
  if (!writeVertex(i, vertices.get(i))) {
375
- console.warn("[rhino3dm] Invalid vertex encountered; skipping mesh");
656
+ console.warn("[geometry] Invalid vertex encountered; skipping mesh");
376
657
  return null;
377
658
  }
378
659
  }
@@ -380,7 +661,7 @@ function meshToThree(mesh) {
380
661
  else {
381
662
  for (let i = 0; i < positionArray.length; i += 1) {
382
663
  if (!Number.isFinite(positionArray[i])) {
383
- console.warn("[rhino3dm] Mesh float array contained non-finite values; skipping mesh");
664
+ console.warn("[geometry] Mesh float array contained non-finite values; skipping mesh");
384
665
  return null;
385
666
  }
386
667
  }
@@ -396,7 +677,7 @@ function meshToThree(mesh) {
396
677
  const c = face[2];
397
678
  const d = face.length > 3 ? face[3] : undefined;
398
679
  if (![a, b, c].every((idx) => Number.isInteger(idx) && idx >= 0 && idx < vertexCount)) {
399
- console.warn("[rhino3dm] Face reference out of bounds; skipping mesh");
680
+ console.warn("[geometry] Face reference out of bounds; skipping mesh");
400
681
  return null;
401
682
  }
402
683
  indexArray.push(a, b, c);
@@ -405,7 +686,7 @@ function meshToThree(mesh) {
405
686
  }
406
687
  }
407
688
  if (indexArray.length === 0) {
408
- console.warn("[rhino3dm] Mesh produced no triangle indices; skipping");
689
+ console.warn("[geometry] Mesh produced no triangle indices; skipping");
409
690
  return null;
410
691
  }
411
692
  geometry.setIndex(indexArray);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@treasuryspatial/rhino-bridge",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "type": "module",
5
5
  "license": "UNLICENSED",
6
6
  "main": "./dist/index.js",