@inweb/viewer-three 26.8.0 → 26.8.2

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.
Files changed (54) hide show
  1. package/dist/plugins/components/RoomEnvironmentComponent.js +75 -40
  2. package/dist/plugins/components/RoomEnvironmentComponent.js.map +1 -1
  3. package/dist/plugins/components/RoomEnvironmentComponent.min.js +1 -1
  4. package/dist/plugins/components/StatsPanelComponent.js +1 -1
  5. package/dist/plugins/components/StatsPanelComponent.js.map +1 -1
  6. package/dist/plugins/components/StatsPanelComponent.min.js +1 -1
  7. package/dist/plugins/components/StatsPanelComponent.module.js +1 -1
  8. package/dist/plugins/components/StatsPanelComponent.module.js.map +1 -1
  9. package/dist/plugins/loaders/GLTFCloudLoader.js +225 -94
  10. package/dist/plugins/loaders/GLTFCloudLoader.js.map +1 -1
  11. package/dist/plugins/loaders/GLTFCloudLoader.min.js +1 -1
  12. package/dist/plugins/loaders/IFCXLoader.js +1977 -881
  13. package/dist/plugins/loaders/IFCXLoader.js.map +1 -1
  14. package/dist/plugins/loaders/IFCXLoader.min.js +1 -1
  15. package/dist/plugins/loaders/IFCXLoader.module.js +477 -154
  16. package/dist/plugins/loaders/IFCXLoader.module.js.map +1 -1
  17. package/dist/viewer-three.js +31149 -5503
  18. package/dist/viewer-three.js.map +1 -1
  19. package/dist/viewer-three.min.js +3 -3
  20. package/dist/viewer-three.module.js +406 -298
  21. package/dist/viewer-three.module.js.map +1 -1
  22. package/lib/Viewer/Viewer.d.ts +17 -3
  23. package/lib/Viewer/commands/SetDefaultViewPosition.d.ts +6 -6
  24. package/lib/Viewer/components/HighlighterComponent.d.ts +5 -4
  25. package/lib/Viewer/components/SelectionComponent.d.ts +1 -1
  26. package/lib/Viewer/loaders/DynamicGltfLoader/DynamicModelImpl.d.ts +3 -1
  27. package/lib/Viewer/models/IModelImpl.d.ts +27 -0
  28. package/lib/Viewer/models/ModelImpl.d.ts +27 -0
  29. package/lib/Viewer/scenes/Helpers.d.ts +7 -0
  30. package/lib/index.d.ts +2 -1
  31. package/package.json +9 -9
  32. package/plugins/components/StatsPanelComponent.ts +1 -1
  33. package/plugins/loaders/IFCX/IFCXLoader.ts +4 -7
  34. package/plugins/loaders/IFCX/render.js +686 -181
  35. package/plugins/loaders/IFCXCloudLoader.ts +1 -1
  36. package/src/Viewer/Viewer.ts +124 -48
  37. package/src/Viewer/commands/SetDefaultViewPosition.ts +8 -8
  38. package/src/Viewer/components/CameraComponent.ts +20 -16
  39. package/src/Viewer/components/ExtentsComponent.ts +1 -0
  40. package/src/Viewer/components/HighlighterComponent.ts +78 -80
  41. package/src/Viewer/components/LightComponent.ts +10 -4
  42. package/src/Viewer/components/ResizeCanvasComponent.ts +1 -0
  43. package/src/Viewer/components/SelectionComponent.ts +1 -1
  44. package/src/Viewer/helpers/WCSHelper.ts +8 -5
  45. package/src/Viewer/loaders/DynamicGltfLoader/DynamicGltfLoader.js +33 -16
  46. package/src/Viewer/loaders/DynamicGltfLoader/DynamicModelImpl.ts +12 -5
  47. package/src/Viewer/loaders/DynamicGltfLoader/GltfStructure.js +100 -20
  48. package/src/Viewer/loaders/GLTFCloudDynamicLoader.ts +4 -2
  49. package/src/Viewer/loaders/GLTFFileLoader.ts +1 -1
  50. package/src/Viewer/models/IModelImpl.ts +67 -0
  51. package/src/Viewer/models/ModelImpl.ts +214 -0
  52. package/src/Viewer/postprocessing/SSAARenderPass.js +245 -0
  53. package/src/Viewer/scenes/Helpers.ts +42 -0
  54. package/src/index.ts +2 -1
@@ -1,887 +1,1983 @@
1
1
  (function (global, factory) {
2
- typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('@inweb/viewer-three'), require('three')) :
3
- typeof define === 'function' && define.amd ? define(['@inweb/viewer-three', 'three'], factory) :
4
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.ODA.Three, global.THREE));
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('@inweb/viewer-three'), require('three')) :
3
+ typeof define === 'function' && define.amd ? define(['@inweb/viewer-three', 'three'], factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.ODA.Three, global.THREE));
5
5
  })(this, (function (viewerThree, three) { 'use strict';
6
6
 
7
- ///////////////////////////////////////////////////////////////////////////////
8
- // Copyright (C) 2002-2025, Open Design Alliance (the "Alliance").
9
- // All rights reserved.
10
- //
11
- // This software and its documentation and related materials are owned by
12
- // the Alliance. The software may only be incorporated into application
13
- // programs owned by members of the Alliance, subject to a signed
14
- // Membership Agreement and Supplemental Software License Agreement with the
15
- // Alliance. The structure and organization of this software are the valuable
16
- // trade secrets of the Alliance and its suppliers. The software is also
17
- // protected by copyright law and international treaty provisions. Application
18
- // programs incorporating this software must include the following statement
19
- // with their copyright notices:
20
- //
21
- // This application incorporates Open Design Alliance software pursuant to a
22
- // license agreement with Open Design Alliance.
23
- // Open Design Alliance Copyright (C) 2002-2025 by Open Design Alliance.
24
- // All rights reserved.
25
- //
26
- // By use of this software, its documentation or related materials, you
27
- // acknowledge and accept the above terms.
28
- ///////////////////////////////////////////////////////////////////////////////
29
-
30
-
31
- const THREE = {
32
- Box3: three.Box3,
33
- BufferAttribute: three.BufferAttribute,
34
- BufferGeometry: three.BufferGeometry,
35
- Color: three.Color,
36
- Group: three.Group,
37
- Line: three.Line,
38
- LineBasicMaterial: three.LineBasicMaterial,
39
- Matrix4: three.Matrix4,
40
- Mesh: three.Mesh,
41
- MeshBasicMaterial: three.MeshBasicMaterial,
42
- PerspectiveCamera: three.PerspectiveCamera,
43
- Scene: three.Scene,
44
- Vector3: three.Vector3,
45
- };
46
-
47
- // composed-object.ts
48
- function getChildByName(root, childName, skip = 0) {
49
- let fragments = childName.replace(/^<\/|^\/|>$/g, "").split("/");
50
- for (let i = 0; i < skip; ++i) {
51
- fragments.shift();
52
- }
53
- let start = root;
54
- while (fragments.length && start && start.children) {
55
- // console.log(start, fragments[0]);
56
- let f = fragments.shift();
57
- start = start.children.find((i) => i.name.split("/").reverse()[0] === f);
58
- }
59
- if (fragments.length == 0) {
60
- return start;
61
- }
62
- }
63
-
64
- // compose-alpha.ts
65
- function GetNode(node, path) {
66
- if (path === "") return node;
67
- let parts = path.split("/");
68
- let child = node.children.get(parts[0]);
69
- if (child) {
70
- if (parts.length === 1) {
71
- return child;
72
- }
73
- return GetNode(child, GetTail(path));
74
- } else {
75
- return null;
76
- }
77
- }
78
- function GetHead(path) {
79
- return path.split("/")[0];
80
- }
81
- function GetTail(path) {
82
- let parts = path.split("/");
83
- parts.shift();
84
- return parts.join("/");
85
- }
86
- function MakeNode(node) {
87
- return {
88
- node,
89
- children: /* @__PURE__ */ new Map(),
90
- attributes: /* @__PURE__ */ new Map(),
91
- };
92
- }
93
- function ConvertToCompositionNode(path, inputNodes) {
94
- let compositionNode = {
95
- path,
96
- children: {},
97
- inherits: {},
98
- attributes: {},
99
- };
100
- inputNodes.forEach((node) => {
101
- Object.keys(node.children).forEach((childName) => {
102
- compositionNode.children[childName] = node.children[childName];
103
- });
104
- Object.keys(node.inherits).forEach((inheritName) => {
105
- let ih = node.inherits[inheritName];
106
- if (ih === null) {
107
- delete compositionNode.inherits[inheritName];
108
- } else {
109
- compositionNode.inherits[inheritName] = ih;
110
- }
111
- });
112
- Object.keys(node.attributes).forEach((attrName) => {
113
- compositionNode.attributes[attrName] = node.attributes[attrName];
114
- });
115
- });
116
- return compositionNode;
117
- }
118
- function MMSet(map, key, value) {
119
- if (map.has(key)) {
120
- map.get(key)?.push(value);
121
- } else {
122
- map.set(key, [value]);
123
- }
124
- }
125
- function FindRootsOrCycles(nodes) {
126
- let dependencies = /* @__PURE__ */ new Map();
127
- let dependents = /* @__PURE__ */ new Map();
128
- nodes.forEach((node, path) => {
129
- Object.keys(node.inherits).forEach((inheritName) => {
130
- MMSet(dependencies, path, node.inherits[inheritName]);
131
- MMSet(dependents, node.inherits[inheritName], path);
132
- });
133
- Object.keys(node.children).forEach((childName) => {
134
- MMSet(dependencies, path, node.children[childName]);
135
- MMSet(dependents, node.children[childName], path);
136
- });
137
- });
138
- let paths = [...nodes.keys()];
139
- let perm = {};
140
- let temp = {};
141
- function visit(path) {
142
- if (perm[path]) return;
143
- if (temp[path]) throw new Error(`CYCLE!`);
144
- temp[path] = true;
145
- let deps = dependencies.get(path);
146
- if (deps) {
147
- deps.forEach((dep) => visit(dep));
148
- }
149
- perm[path] = true;
150
- }
151
- let roots = /* @__PURE__ */ new Set();
152
- try {
153
- paths.forEach((path) => {
154
- if (!dependents.has(path) && path.indexOf("/") === -1) {
155
- roots.add(path);
156
- }
157
- visit(path);
158
- });
159
- } catch (e) {
160
- return null;
161
- }
162
- return roots;
163
- }
164
- function ConvertNodes(input) {
165
- let compositionNodes = /* @__PURE__ */ new Map();
166
- for (let [path, inputNodes] of input) {
167
- compositionNodes.set(path, ConvertToCompositionNode(path, inputNodes));
168
- }
169
- return compositionNodes;
170
- }
171
- var CycleError = class extends Error {};
172
- function ExpandFirstRootInInput(nodes) {
173
- let roots = FindRootsOrCycles(nodes);
174
- if (!roots) {
175
- throw new CycleError();
176
- }
177
- return ExpandNewNode([...roots.values()][0], nodes);
178
- }
179
- function CreateArtificialRoot(nodes) {
180
- let roots = FindRootsOrCycles(nodes);
181
- if (!roots) {
182
- throw new CycleError();
183
- }
184
- let pseudoRoot = {
185
- node: "",
186
- attributes: /* @__PURE__ */ new Map(),
187
- children: /* @__PURE__ */ new Map(),
188
- };
189
- roots.forEach((root) => {
190
- pseudoRoot.children.set(root, ExpandNewNode(root, nodes));
191
- });
192
- return pseudoRoot;
193
- }
194
- function ExpandNewNode(node, nodes) {
195
- return ExpandNode(node, MakeNode(node), nodes);
196
- }
197
- function ExpandNode(path, node, nodes) {
198
- let input = nodes.get(path);
199
- if (input) {
200
- AddDataFromInput(input, node, nodes);
201
- }
202
- node.children.forEach((child, name) => {
203
- ExpandNode(`${path}/${name}`, child, nodes);
204
- });
205
- return node;
206
- }
207
- function AddDataFromInput(input, node, nodes) {
208
- Object.values(input.inherits).forEach((inherit) => {
209
- let classNode = ExpandNewNode(GetHead(inherit), nodes);
210
- let subnode = GetNode(classNode, GetTail(inherit));
211
- if (!subnode) throw new Error(`Unknown node ${inherit}`);
212
- subnode.children.forEach((child, childName) => {
213
- node.children.set(childName, child);
214
- });
215
- for (let [attrID, attr] of subnode.attributes) {
216
- node.attributes.set(attrID, attr);
217
- }
218
- });
219
- Object.entries(input.children).forEach(([childName, child]) => {
220
- if (child !== null) {
221
- let classNode = ExpandNewNode(GetHead(child), nodes);
222
- let subnode = GetNode(classNode, GetTail(child));
223
- if (!subnode) throw new Error(`Unknown node ${child}`);
224
- node.children.set(childName, subnode);
225
- } else {
226
- node.children.delete(childName);
227
- }
228
- });
229
- Object.entries(input.attributes).forEach(([attrID, attr]) => {
230
- node.attributes.set(attrID, attr);
231
- });
232
- }
233
-
234
- // workflow-alpha.ts
235
- function MMSet2(map, key, value) {
236
- if (map.has(key)) {
237
- map.get(key)?.push(value);
238
- } else {
239
- map.set(key, [value]);
240
- }
241
- }
242
- function ToInputNodes(data) {
243
- let inputNodes = /* @__PURE__ */ new Map();
244
- data.forEach((ifcxNode) => {
245
- let node = {
246
- path: ifcxNode.path,
247
- children: ifcxNode.children ? ifcxNode.children : {},
248
- inherits: ifcxNode.inherits ? ifcxNode.inherits : {},
249
- attributes: ifcxNode.attributes ? ifcxNode.attributes : {},
250
- };
251
- MMSet2(inputNodes, node.path, node);
252
- });
253
- return inputNodes;
254
- }
255
- var SchemaValidationError = class extends Error {};
256
- function ValidateAttributeValue(desc, value, path, schemas) {
257
- if (desc.inherits) {
258
- desc.inherits.forEach((inheritedSchemaID) => {
259
- let inheritedSchema = schemas[inheritedSchemaID];
260
- if (!inheritedSchema) {
261
- throw new SchemaValidationError(`Unknown inherited schema id "${desc.inherits}"`);
262
- }
263
- ValidateAttributeValue(inheritedSchema.value, value, path, schemas);
264
- });
265
- }
266
- if (desc.dataType === "Boolean") {
267
- if (typeof value !== "boolean") {
268
- throw new SchemaValidationError(`Expected "${value}" to be of type boolean`);
269
- }
270
- } else if (desc.dataType === "String") {
271
- if (typeof value !== "string") {
272
- throw new SchemaValidationError(`Expected "${value}" to be of type string`);
273
- }
274
- } else if (desc.dataType === "DateTime") {
275
- if (typeof value !== "string") {
276
- throw new SchemaValidationError(`Expected "${value}" to be of type date`);
277
- }
278
- } else if (desc.dataType === "Enum") {
279
- if (typeof value !== "string") {
280
- throw new SchemaValidationError(`Expected "${value}" to be of type string`);
281
- }
282
- let found = desc.enumRestrictions.options.filter((option) => option === value).length === 1;
283
- if (!found) {
284
- throw new SchemaValidationError(`Expected "${value}" to be one of [${desc.enumRestrictions.options.join(",")}]`);
285
- }
286
- } else if (desc.dataType === "Integer") {
287
- if (typeof value !== "number") {
288
- throw new SchemaValidationError(`Expected "${value}" to be of type int`);
289
- }
290
- } else if (desc.dataType === "Real") {
291
- if (typeof value !== "number") {
292
- throw new SchemaValidationError(`Expected "${value}" to be of type real`);
293
- }
294
- } else if (desc.dataType === "Relation") {
295
- if (typeof value !== "string") {
296
- throw new SchemaValidationError(`Expected "${value}" to be of type string`);
297
- }
298
- } else if (desc.dataType === "Object") {
299
- if (typeof value !== "object") {
300
- throw new SchemaValidationError(`Expected "${value}" to be of type object`);
301
- }
302
- if (desc.objectRestrictions) {
303
- Object.keys(desc.objectRestrictions.values).forEach((key) => {
304
- if (!Object.hasOwn(value, key)) {
305
- throw new SchemaValidationError(`Expected "${value}" to have key ${key}`);
306
- }
307
- ValidateAttributeValue(desc.objectRestrictions.values[key], value[key], path + "." + key, schemas);
308
- });
309
- }
310
- } else if (desc.dataType === "Array") {
311
- if (!Array.isArray(value)) {
312
- throw new SchemaValidationError(`Expected "${value}" to be of type array`);
313
- }
314
- value.forEach((entry) => {
315
- ValidateAttributeValue(desc.arrayRestrictions.value, entry, path + ".<array>.", schemas);
316
- });
317
- } else {
318
- throw new SchemaValidationError(`Unexpected datatype ${desc.dataType}`);
319
- }
320
- }
321
- function Validate(schemas, inputNodes) {
322
- inputNodes.forEach((node) => {
323
- Object.keys(node.attributes).forEach((schemaID) => {
324
- if (!schemas[schemaID]) {
325
- throw new SchemaValidationError(`Missing schema "${schemaID}" referenced by ["${node.path}"].attributes`);
326
- }
327
- let schema = schemas[schemaID];
328
- let value = node.attributes[schemaID];
329
- try {
330
- ValidateAttributeValue(schema.value, value, "", schemas);
331
- } catch (e) {
332
- if (e instanceof SchemaValidationError) {
333
- throw new SchemaValidationError(`Error validating ["${node.path}"].attributes["${schemaID}"]: ${e.message}`);
334
- } else {
335
- throw e;
336
- }
337
- }
338
- });
339
- });
340
- }
341
- function LoadIfcxFile(file, checkSchemas = true, createArtificialRoot = false) {
342
- let inputNodes = ToInputNodes(file.data);
343
- let compositionNodes = ConvertNodes(inputNodes);
344
- try {
345
- if (checkSchemas) {
346
- Validate(file.schemas, compositionNodes);
347
- }
348
- } catch (e) {
349
- throw e;
350
- }
351
- if (createArtificialRoot) {
352
- return CreateArtificialRoot(compositionNodes);
353
- } else {
354
- return ExpandFirstRootInInput(compositionNodes);
355
- }
356
- }
357
- function Federate(files) {
358
- let result = {
359
- header: files[0].header,
360
- schemas: {},
361
- data: [],
362
- };
363
- files.forEach((file) => {
364
- Object.keys(file.schemas).forEach((schemaID) => (result.schemas[schemaID] = file.schemas[schemaID]));
365
- });
366
- files.forEach((file) => {
367
- file.data.forEach((node) => result.data.push(node));
368
- });
369
- return Prune(result);
370
- }
371
- function Collapse(nodes, deleteEmpty = false) {
372
- let result = {
373
- path: nodes[0].path,
374
- children: {},
375
- inherits: {},
376
- attributes: {},
377
- };
378
- nodes.forEach((node) => {
379
- Object.keys(node.children).forEach((name) => {
380
- result.children[name] = node.children[name];
381
- });
382
- Object.keys(node.inherits).forEach((name) => {
383
- result.inherits[name] = node.inherits[name];
384
- });
385
- Object.keys(node.attributes).forEach((name) => {
386
- result.attributes[name] = node.attributes[name];
387
- });
388
- });
389
- if (deleteEmpty) {
390
- let empty = true;
391
- Object.keys(result.children).forEach((name) => {
392
- if (result.children[name] !== null) empty = false;
393
- });
394
- Object.keys(result.inherits).forEach((name) => {
395
- if (result.inherits[name] !== null) empty = false;
396
- });
397
- Object.keys(result.attributes).forEach((name) => {
398
- if (result.attributes[name] !== null) empty = false;
399
- });
400
- if (empty) return null;
401
- }
402
- return result;
403
- }
404
- function Prune(file, deleteEmpty = false) {
405
- let result = {
406
- header: file.header,
407
- schemas: file.schemas,
408
- data: [],
409
- };
410
- let inputNodes = ToInputNodes(file.data);
411
- inputNodes.forEach((nodes) => {
412
- let collapsed = Collapse(nodes, deleteEmpty);
413
- if (collapsed)
414
- result.data.push({
415
- path: collapsed.path,
416
- children: collapsed.children,
417
- inherits: collapsed.inherits,
418
- attributes: collapsed.attributes,
419
- });
420
- });
421
- return result;
422
- }
423
-
424
- // compose-flattened.ts
425
- function TreeNodeToComposedObject(path, node, schemas) {
426
- let co = {
427
- name: path,
428
- attributes: {},
429
- children: [],
430
- };
431
- node.children.forEach((childNode, childName) => {
432
- co.children?.push(TreeNodeToComposedObject(`${path}/${childName}`, childNode, schemas));
433
- });
434
- node.attributes.forEach((attr, attrName) => {
435
- if (attr && typeof attr === "object" && !Array.isArray(attr)) {
436
- Object.keys(attr).forEach((compname) => {
437
- co.attributes[`${attrName}::${compname}`] = attr[compname];
438
- });
439
- } else {
440
- let schema = schemas[attrName];
441
- if (schema && schema.value.quantityKind) {
442
- let postfix = "";
443
- let quantityKind = schema.value.quantityKind;
444
- if (quantityKind === "Length") {
445
- postfix = "m";
446
- } else if (quantityKind === "Volume") {
447
- postfix = "m" + String.fromCodePoint(179);
448
- }
449
- co.attributes[attrName] = `${attr} ${postfix}`;
450
- } else {
451
- co.attributes[attrName] = attr;
452
- }
453
- }
454
- });
455
- if (Object.keys(co.attributes).length === 0) delete co.attributes;
456
- return co;
457
- }
458
- function compose3(files) {
459
- let federated = Federate(files);
460
- let tree = LoadIfcxFile(federated, true, true);
461
- return TreeNodeToComposedObject("", tree, federated.schemas);
462
- }
463
-
464
- // render.ts
465
- // var controls;
466
- // var renderer;
467
- var scene;
468
- var camera;
469
- var datas = [];
470
- var autoCamera = true;
471
- // var THREE = window["THREE"];
472
- function init() {
473
- scene = new THREE.Scene();
474
- camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 100);
475
- camera.up.set(0, 0, 1);
476
- camera.position.set(50, 50, 50);
477
- camera.lookAt(0, 0, 0);
478
- // const nd = document.querySelector(".viewport");
479
- // renderer = new THREE.WebGLRenderer({
480
- // alpha: true,
481
- // logarithmicDepthBuffer: true,
482
- // });
483
- // renderer.setSize(nd.offsetWidth, nd.offsetHeight);
484
- // controls = new THREE.OrbitControls(camera, renderer.domElement);
485
- // controls.enableDamping = true;
486
- // controls.dampingFactor = 0.25;
487
- // nd.appendChild(renderer.domElement);
488
- scene.add(camera);
489
- return scene;
490
- }
491
- function HasAttr(node, attrName) {
492
- if (!node || !node.attributes) return false;
493
- return !!node.attributes[attrName];
494
- }
495
- function createMaterialFromParent(parent, root) {
496
- let reference = parent.attributes["usd::usdshade::materialbindingapi::material::binding"];
497
- let material = {
498
- color: new THREE.Color(0.6, 0.6, 0.6),
499
- transparent: false,
500
- opacity: 1,
501
- };
502
- if (reference) {
503
- const materialNode = getChildByName(root, reference.ref);
504
- if (materialNode) {
505
- let color = materialNode?.attributes["bsi::presentation::diffuseColor"];
506
- material.color = new THREE.Color(...color);
507
- if (materialNode?.attributes["bsi::presentation::opacity"]) {
508
- material.transparent = true;
509
- material.opacity = materialNode.attributes["bsi::presentation::opacity"];
510
- }
511
- }
512
- }
513
- return material;
514
- }
515
- function createCurveFromJson(node, parent, root) {
516
- let points = new Float32Array(node.attributes["usd::usdgeom::basiscurves::points"].flat());
517
- const geometry = new THREE.BufferGeometry();
518
- geometry.setAttribute("position", new THREE.BufferAttribute(points, 3));
519
- const material = createMaterialFromParent(parent, root);
520
- let lineMaterial = new THREE.LineBasicMaterial({ ...material });
521
- lineMaterial.color.multiplyScalar(0.8);
522
- return new THREE.Line(geometry, lineMaterial);
523
- }
524
- function createMeshFromJson(node, parent, root) {
525
- let points = new Float32Array(node.attributes["usd::usdgeom::mesh::points"].flat());
526
- let indices = new Uint16Array(node.attributes["usd::usdgeom::mesh::faceVertexIndices"]);
527
- const geometry = new THREE.BufferGeometry();
528
- geometry.setAttribute("position", new THREE.BufferAttribute(points, 3));
529
- geometry.setIndex(new THREE.BufferAttribute(indices, 1));
530
- geometry.computeVertexNormals();
531
- const material = createMaterialFromParent(parent, root);
532
- let meshMaterial = new THREE.MeshBasicMaterial({ ...material });
533
- return new THREE.Mesh(geometry, meshMaterial);
534
- }
535
- function traverseTree(node, parent, root, parentNode = undefined) {
536
- let elem = new THREE.Group();
537
- if (HasAttr(node, "usd::usdgeom::visibility::visibility")) {
538
- if (node.attributes["usd::usdgeom::visibility::visibility"] === "invisible") {
539
- return;
540
- }
541
- } else if (HasAttr(node, "usd::usdgeom::mesh::points")) {
542
- elem = createMeshFromJson(node, parentNode, root);
543
- } else if (HasAttr(node, "usd::usdgeom::basiscurves::points")) {
544
- elem = createCurveFromJson(node, parentNode, root);
545
- }
546
- parent.add(elem);
547
- if (node !== root) {
548
- elem.matrixAutoUpdate = false;
549
- let matrixNode =
550
- node.attributes && node.attributes["usd::xformop::transform"]
551
- ? node.attributes["usd::xformop::transform"].flat()
552
- : null;
553
- if (matrixNode) {
554
- let matrix = new THREE.Matrix4();
555
- matrix.set(...matrixNode);
556
- matrix.transpose();
557
- elem.matrix = matrix;
558
- }
559
- }
560
- (node.children || []).forEach((child) => traverseTree(child, elem || parent, root, node));
561
- }
562
- // function encodeHtmlEntities(str) {
563
- // const div = document.createElement("div");
564
- // div.textContent = str;
565
- // return div.innerHTML;
566
- // }
567
- // var icons = {
568
- // "usd::usdgeom::mesh::points": "deployed_code",
569
- // "usd::usdgeom::basiscurves::points": "line_curve",
570
- // "usd::usdshade::material::outputs::surface.connect": "line_style",
571
- // };
572
- // function buildDomTree(prim, node) {
573
- // const elem = document.createElement("div");
574
- // let span;
575
- // elem.appendChild(document.createTextNode(prim.name ? prim.name.split("/").reverse()[0] : "root"));
576
- // elem.appendChild((span = document.createElement("span")));
577
- // Object.entries(icons).forEach(([k, v]) => (span.innerText += (prim.attributes || {})[k] ? v : " "));
578
- // span.className = "material-symbols-outlined";
579
- // elem.onclick = (evt) => {
580
- // let rows = [["name", prim.name]]
581
- // .concat(Object.entries(prim.attributes))
582
- // .map(
583
- // ([k, v]) =>
584
- // `<tr><td>${encodeHtmlEntities(k)}</td><td>${encodeHtmlEntities(typeof v === "object" ? JSON.stringify(v) : v)}</td>`
585
- // )
586
- // .join("");
587
- // document.querySelector(".attributes .table").innerHTML = `<table border="0">${rows}</table>`;
588
- // evt.stopPropagation();
589
- // };
590
- // node.appendChild(elem);
591
- // (prim.children || []).forEach((p) => buildDomTree(p, elem));
592
- // }
593
- function composeAndRender() {
594
- if (scene) {
595
- scene.children = [];
596
- }
597
- // document.querySelector(".tree").innerHTML = "";
598
- if (datas.length === 0) {
599
- return;
600
- }
601
- let tree = null;
602
- let dataArray = datas.map((arr) => arr[1]);
603
- tree = compose3(dataArray);
604
- if (!tree) {
605
- console.error("No result from composition");
606
- return;
607
- }
608
- traverseTree(tree, scene || init(), tree);
609
- if (autoCamera) {
610
- const boundingBox = new THREE.Box3();
611
- boundingBox.setFromObject(scene);
612
- if (!boundingBox.isEmpty()) {
613
- let avg = boundingBox.min.clone().add(boundingBox.max).multiplyScalar(0.5);
614
- let ext = boundingBox.max.clone().sub(boundingBox.min).length();
615
- camera.position.copy(avg.clone().add(new THREE.Vector3(1, 1, 1).normalize().multiplyScalar(ext)));
616
- camera.far = ext * 3;
617
- camera.updateProjectionMatrix();
618
- // controls.target.copy(avg);
619
- // controls.update();
620
- autoCamera = false;
621
- }
622
- }
623
- // buildDomTree(tree, document.querySelector(".tree"));
624
- // animate();
625
- }
626
- // function createLayerDom() {
627
- // document.querySelector(".layers div").innerHTML = "";
628
- // datas.forEach(([name, _], index) => {
629
- // const elem = document.createElement("div");
630
- // elem.appendChild(document.createTextNode(name));
631
- // ["\u25B3", "\u25BD", "\xD7"].reverse().forEach((lbl, cmd) => {
632
- // const btn = document.createElement("span");
633
- // btn.onclick = (evt) => {
634
- // evt.stopPropagation();
635
- // if (cmd === 2) {
636
- // if (index > 0) {
637
- // [datas[index], datas[index - 1]] = [datas[index - 1], datas[index]];
638
- // }
639
- // } else if (cmd === 1) {
640
- // if (index < datas.length - 1) {
641
- // [datas[index], datas[index + 1]] = [datas[index + 1], datas[index]];
642
- // }
643
- // } else if (cmd === 0) {
644
- // datas.splice(index, 1);
645
- // }
646
- // composeAndRender();
647
- // createLayerDom();
648
- // };
649
- // btn.appendChild(document.createTextNode(lbl));
650
- // elem.appendChild(btn);
651
- // });
652
- // document.querySelector(".layers div").appendChild(elem);
653
- // });
654
- // }
655
- // function addModel(name, m) {
656
- // datas.push([name, m]);
657
- // createLayerDom();
658
- // composeAndRender();
659
- // }
660
- // function animate() {
661
- // requestAnimationFrame(animate);
662
- // controls.update();
663
- // renderer.render(scene, camera);
664
- // }
665
- // export { composeAndRender, addModel as default };
666
-
667
- function parse(m, name) {
668
- datas.push([name, m]);
669
- composeAndRender();
670
- return scene;
671
- }
672
- function clear() {
673
- scene = undefined;
674
- datas.length = 0;
675
- autoCamera = true;
676
- }
677
-
678
- ///////////////////////////////////////////////////////////////////////////////
679
- // Copyright (C) 2002-2024, Open Design Alliance (the "Alliance").
680
- // All rights reserved.
681
- //
682
- // This software and its documentation and related materials are owned by
683
- // the Alliance. The software may only be incorporated into application
684
- // programs owned by members of the Alliance, subject to a signed
685
- // Membership Agreement and Supplemental Software License Agreement with the
686
- // Alliance. The structure and organization of this software are the valuable
687
- // trade secrets of the Alliance and its suppliers. The software is also
688
- // protected by copyright law and international treaty provisions. Application
689
- // programs incorporating this software must include the following statement
690
- // with their copyright notices:
691
- //
692
- // This application incorporates Open Design Alliance software pursuant to a
693
- // license agreement with Open Design Alliance.
694
- // Open Design Alliance Copyright (C) 2002-2024 by Open Design Alliance.
695
- // All rights reserved.
696
- //
697
- // By use of this software, its documentation or related materials, you
698
- // acknowledge and accept the above terms.
699
- ///////////////////////////////////////////////////////////////////////////////
700
- class IFCXLoader extends three.Loader {
701
- load(url, onLoad, onProgress, onError) {
702
- const manager = this.manager;
703
- manager.itemStart(url);
704
- const _onLoad = (scene) => {
705
- onLoad(scene);
706
- manager.itemEnd(url);
707
- };
708
- const _onError = (e) => {
709
- if (onError)
710
- onError(e);
711
- else
712
- console.error(e);
713
- manager.itemError(url);
714
- manager.itemEnd(url);
715
- };
716
- const loader = new three.FileLoader(this.manager);
717
- loader.setPath(this.path);
718
- loader.setResponseType("json");
719
- loader.setRequestHeader(this.requestHeader);
720
- loader.setWithCredentials(this.withCredentials);
721
- loader.load(url, (json) => this.parse(json, _onLoad, _onError), onProgress, onError);
722
- }
723
- parse(json, onLoad, onError) {
724
- try {
725
- onLoad(parse(json));
726
- }
727
- catch (e) {
728
- onError(e);
729
- }
730
- finally {
731
- clear();
732
- }
733
- }
734
- }
735
-
736
- ///////////////////////////////////////////////////////////////////////////////
737
- // Copyright (C) 2002-2025, Open Design Alliance (the "Alliance").
738
- // All rights reserved.
739
- //
740
- // This software and its documentation and related materials are owned by
741
- // the Alliance. The software may only be incorporated into application
742
- // programs owned by members of the Alliance, subject to a signed
743
- // Membership Agreement and Supplemental Software License Agreement with the
744
- // Alliance. The structure and organization of this software are the valuable
745
- // trade secrets of the Alliance and its suppliers. The software is also
746
- // protected by copyright law and international treaty provisions. Application
747
- // programs incorporating this software must include the following statement
748
- // with their copyright notices:
749
- //
750
- // This application incorporates Open Design Alliance software pursuant to a
751
- // license agreement with Open Design Alliance.
752
- // Open Design Alliance Copyright (C) 2002-2025 by Open Design Alliance.
753
- // All rights reserved.
754
- //
755
- // By use of this software, its documentation or related materials, you
756
- // acknowledge and accept the above terms.
757
- ///////////////////////////////////////////////////////////////////////////////
758
- class IFCXFileLoader extends viewerThree.Loader {
759
- constructor(viewer) {
760
- super();
761
- this.viewer = viewer;
762
- }
763
- isSupport(file, format) {
764
- return ((typeof file === "string" || file instanceof globalThis.File || file instanceof ArrayBuffer) &&
765
- /(ifcx)$/i.test(format));
766
- }
767
- async load(file, format, params) {
768
- const manager = new viewerThree.GLTFLoadingManager(file, params);
769
- const loader = new IFCXLoader(manager);
770
- loader.setPath(manager.path);
771
- loader.setCrossOrigin(params.crossOrigin || loader.crossOrigin);
772
- loader.setWithCredentials(params.withCredentials || loader.withCredentials);
773
- const progress = (event) => {
774
- const { lengthComputable, loaded, total } = event;
775
- const progress = lengthComputable ? loaded / total : 1;
776
- this.viewer.emitEvent({ type: "geometryprogress", data: progress, file });
777
- };
778
- const scene = await loader.loadAsync(manager.fileURL, progress);
779
- if (!this.viewer.scene)
780
- return this;
781
- let handle = 0;
782
- scene.traverse((object) => {
783
- object.userData = { handle, ...object.userData };
784
- handle++;
785
- });
786
- const modelImpl = new viewerThree.ModelImpl(scene);
787
- modelImpl.loader = this;
788
- modelImpl.viewer = this.viewer;
789
- this.viewer.scene.add(scene);
790
- this.viewer.models.push(modelImpl);
791
- this.viewer.syncOptions();
792
- this.viewer.syncOverlay();
793
- this.viewer.update();
794
- this.viewer.emitEvent({ type: "databasechunk", data: scene, file });
795
- return this;
796
- }
797
- }
798
-
799
- ///////////////////////////////////////////////////////////////////////////////
800
- // Copyright (C) 2002-2025, Open Design Alliance (the "Alliance").
801
- // All rights reserved.
802
- //
803
- // This software and its documentation and related materials are owned by
804
- // the Alliance. The software may only be incorporated into application
805
- // programs owned by members of the Alliance, subject to a signed
806
- // Membership Agreement and Supplemental Software License Agreement with the
807
- // Alliance. The structure and organization of this software are the valuable
808
- // trade secrets of the Alliance and its suppliers. The software is also
809
- // protected by copyright law and international treaty provisions. Application
810
- // programs incorporating this software must include the following statement
811
- // with their copyright notices:
812
- //
813
- // This application incorporates Open Design Alliance software pursuant to a
814
- // license agreement with Open Design Alliance.
815
- // Open Design Alliance Copyright (C) 2002-2025 by Open Design Alliance.
816
- // All rights reserved.
817
- //
818
- // By use of this software, its documentation or related materials, you
819
- // acknowledge and accept the above terms.
820
- ///////////////////////////////////////////////////////////////////////////////
821
- class IFCXCloudLoader extends viewerThree.Loader {
822
- constructor(viewer) {
823
- super();
824
- this.viewer = viewer;
825
- }
826
- isSupport(file) {
827
- return (typeof file === "object" &&
828
- typeof file.type === "string" &&
829
- typeof file.download === "function" &&
830
- /.ifcx$/i.test(file.type));
831
- }
832
- async load(file) {
833
- const progress = (progress) => {
834
- this.viewer.emitEvent({ type: "geometryprogress", data: progress, file });
835
- };
836
- const arrayBuffer = await file.download(progress, this.abortController.signal);
837
- if (!this.viewer.scene)
838
- return this;
839
- const textDecoder = new TextDecoder();
840
- const json = JSON.parse(textDecoder.decode(arrayBuffer));
841
- const scene = parse(json);
842
- clear();
843
- let handle = 0;
844
- scene.traverse((object) => {
845
- object.userData = { handle, ...object.userData };
846
- handle++;
847
- });
848
- const modelImpl = new viewerThree.ModelImpl(scene);
849
- modelImpl.loader = this;
850
- modelImpl.viewer = this.viewer;
851
- this.viewer.scene.add(scene);
852
- this.viewer.models.push(modelImpl);
853
- this.viewer.syncOptions();
854
- this.viewer.syncOverlay();
855
- this.viewer.update();
856
- this.viewer.emitEvent({ type: "databasechunk", data: scene, file });
857
- return this;
858
- }
859
- }
860
-
861
- ///////////////////////////////////////////////////////////////////////////////
862
- // Copyright (C) 2002-2025, Open Design Alliance (the "Alliance").
863
- // All rights reserved.
864
- //
865
- // This software and its documentation and related materials are owned by
866
- // the Alliance. The software may only be incorporated into application
867
- // programs owned by members of the Alliance, subject to a signed
868
- // Membership Agreement and Supplemental Software License Agreement with the
869
- // Alliance. The structure and organization of this software are the valuable
870
- // trade secrets of the Alliance and its suppliers. The software is also
871
- // protected by copyright law and international treaty provisions. Application
872
- // programs incorporating this software must include the following statement
873
- // with their copyright notices:
874
- //
875
- // This application incorporates Open Design Alliance software pursuant to a
876
- // license agreement with Open Design Alliance.
877
- // Open Design Alliance Copyright (C) 2002-2025 by Open Design Alliance.
878
- // All rights reserved.
879
- //
880
- // By use of this software, its documentation or related materials, you
881
- // acknowledge and accept the above terms.
882
- ///////////////////////////////////////////////////////////////////////////////
883
- viewerThree.loaders.registerLoader("ifcx-file", (viewer) => new IFCXFileLoader(viewer));
884
- viewerThree.loaders.registerLoader("ifcx-cloud", (viewer) => new IFCXCloudLoader(viewer));
7
+ /**
8
+ * A loader for the Point Cloud Data (PCD) format.
9
+ *
10
+ * PCDLoader supports ASCII and (compressed) binary files as well as the following PCD fields:
11
+ * - x y z
12
+ * - rgb
13
+ * - normal_x normal_y normal_z
14
+ * - intensity
15
+ * - label
16
+ *
17
+ * ```js
18
+ * const loader = new PCDLoader();
19
+ *
20
+ * const points = await loader.loadAsync( './models/pcd/binary/Zaghetto.pcd' );
21
+ * points.geometry.center(); // optional
22
+ * points.geometry.rotateX( Math.PI ); // optional
23
+ * scene.add( points );
24
+ * ```
25
+ *
26
+ * @augments Loader
27
+ * @three_import import { PCDLoader } from 'three/addons/loaders/PCDLoader.js';
28
+ */
29
+ class PCDLoader extends three.Loader {
30
+
31
+ /**
32
+ * Constructs a new PCD loader.
33
+ *
34
+ * @param {LoadingManager} [manager] - The loading manager.
35
+ */
36
+ constructor( manager ) {
37
+
38
+ super( manager );
39
+
40
+ /**
41
+ * Whether to use little Endian or not.
42
+ *
43
+ * @type {boolean}
44
+ * @default true
45
+ */
46
+ this.littleEndian = true;
47
+
48
+ }
49
+
50
+ /**
51
+ * Starts loading from the given URL and passes the loaded PCD asset
52
+ * to the `onLoad()` callback.
53
+ *
54
+ * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI.
55
+ * @param {function(Points)} onLoad - Executed when the loading process has been finished.
56
+ * @param {onProgressCallback} onProgress - Executed while the loading is in progress.
57
+ * @param {onErrorCallback} onError - Executed when errors occur.
58
+ */
59
+ load( url, onLoad, onProgress, onError ) {
60
+
61
+ const scope = this;
62
+
63
+ const loader = new three.FileLoader( scope.manager );
64
+ loader.setPath( scope.path );
65
+ loader.setResponseType( 'arraybuffer' );
66
+ loader.setRequestHeader( scope.requestHeader );
67
+ loader.setWithCredentials( scope.withCredentials );
68
+ loader.load( url, function ( data ) {
69
+
70
+ try {
71
+
72
+ onLoad( scope.parse( data ) );
73
+
74
+ } catch ( e ) {
75
+
76
+ if ( onError ) {
77
+
78
+ onError( e );
79
+
80
+ } else {
81
+
82
+ console.error( e );
83
+
84
+ }
85
+
86
+ scope.manager.itemError( url );
87
+
88
+ }
89
+
90
+ }, onProgress, onError );
91
+
92
+ }
93
+
94
+ /**
95
+ * Get dataview value by field type and size.
96
+ *
97
+ * @param {DataView} dataview - The DataView to read from.
98
+ * @param {number} offset - The offset to start reading from.
99
+ * @param {'F' | 'U' | 'I'} type - Field type.
100
+ * @param {number} size - Field size.
101
+ * @returns {number} Field value.
102
+ */
103
+ _getDataView( dataview, offset, type, size ) {
104
+
105
+ switch ( type ) {
106
+
107
+ case 'F': {
108
+
109
+ if ( size === 8 ) {
110
+
111
+ return dataview.getFloat64( offset, this.littleEndian );
112
+
113
+ }
114
+
115
+ return dataview.getFloat32( offset, this.littleEndian );
116
+
117
+ }
118
+
119
+ case 'I': {
120
+
121
+ if ( size === 1 ) {
122
+
123
+ return dataview.getInt8( offset );
124
+
125
+ }
126
+
127
+ if ( size === 2 ) {
128
+
129
+ return dataview.getInt16( offset, this.littleEndian );
130
+
131
+ }
132
+
133
+ return dataview.getInt32( offset, this.littleEndian );
134
+
135
+ }
136
+
137
+ case 'U': {
138
+
139
+ if ( size === 1 ) {
140
+
141
+ return dataview.getUint8( offset );
142
+
143
+ }
144
+
145
+ if ( size === 2 ) {
146
+
147
+ return dataview.getUint16( offset, this.littleEndian );
148
+
149
+ }
150
+
151
+ return dataview.getUint32( offset, this.littleEndian );
152
+
153
+ }
154
+
155
+ }
156
+
157
+ }
158
+
159
+ /**
160
+ * Parses the given PCD data and returns a point cloud.
161
+ *
162
+ * @param {ArrayBuffer} data - The raw PCD data as an array buffer.
163
+ * @return {Points} The parsed point cloud.
164
+ */
165
+ parse( data ) {
166
+
167
+ // from https://gitlab.com/taketwo/three-pcd-loader/blob/master/decompress-lzf.js
168
+
169
+ function decompressLZF( inData, outLength ) {
170
+
171
+ const inLength = inData.length;
172
+ const outData = new Uint8Array( outLength );
173
+ let inPtr = 0;
174
+ let outPtr = 0;
175
+ let ctrl;
176
+ let len;
177
+ let ref;
178
+ do {
179
+
180
+ ctrl = inData[ inPtr ++ ];
181
+ if ( ctrl < ( 1 << 5 ) ) {
182
+
183
+ ctrl ++;
184
+ if ( outPtr + ctrl > outLength ) throw new Error( 'Output buffer is not large enough' );
185
+ if ( inPtr + ctrl > inLength ) throw new Error( 'Invalid compressed data' );
186
+ do {
187
+
188
+ outData[ outPtr ++ ] = inData[ inPtr ++ ];
189
+
190
+ } while ( -- ctrl );
191
+
192
+ } else {
193
+
194
+ len = ctrl >> 5;
195
+ ref = outPtr - ( ( ctrl & 0x1f ) << 8 ) - 1;
196
+ if ( inPtr >= inLength ) throw new Error( 'Invalid compressed data' );
197
+ if ( len === 7 ) {
198
+
199
+ len += inData[ inPtr ++ ];
200
+ if ( inPtr >= inLength ) throw new Error( 'Invalid compressed data' );
201
+
202
+ }
203
+
204
+ ref -= inData[ inPtr ++ ];
205
+ if ( outPtr + len + 2 > outLength ) throw new Error( 'Output buffer is not large enough' );
206
+ if ( ref < 0 ) throw new Error( 'Invalid compressed data' );
207
+ if ( ref >= outPtr ) throw new Error( 'Invalid compressed data' );
208
+ do {
209
+
210
+ outData[ outPtr ++ ] = outData[ ref ++ ];
211
+
212
+ } while ( -- len + 2 );
213
+
214
+ }
215
+
216
+ } while ( inPtr < inLength );
217
+
218
+ return outData;
219
+
220
+ }
221
+
222
+ function parseHeader( binaryData ) {
223
+
224
+ const PCDheader = {};
225
+
226
+ const buffer = new Uint8Array( binaryData );
227
+
228
+ let data = '', line = '', i = 0, end = false;
229
+
230
+ const max = buffer.length;
231
+
232
+ while ( i < max && end === false ) {
233
+
234
+ const char = String.fromCharCode( buffer[ i ++ ] );
235
+
236
+ if ( char === '\n' || char === '\r' ) {
237
+
238
+ if ( line.trim().toLowerCase().startsWith( 'data' ) ) {
239
+
240
+ end = true;
241
+
242
+ }
243
+
244
+ line = '';
245
+
246
+ } else {
247
+
248
+ line += char;
249
+
250
+ }
251
+
252
+ data += char;
253
+
254
+ }
255
+
256
+ const result1 = data.search( /[\r\n]DATA\s(\S*)\s/i );
257
+ const result2 = /[\r\n]DATA\s(\S*)\s/i.exec( data.slice( result1 - 1 ) );
258
+
259
+ PCDheader.data = result2[ 1 ];
260
+ PCDheader.headerLen = result2[ 0 ].length + result1;
261
+ PCDheader.str = data.slice( 0, PCDheader.headerLen );
262
+
263
+ // remove comments
264
+
265
+ PCDheader.str = PCDheader.str.replace( /#.*/gi, '' );
266
+
267
+ // parse
268
+
269
+ PCDheader.version = /^VERSION (.*)/im.exec( PCDheader.str );
270
+ PCDheader.fields = /^FIELDS (.*)/im.exec( PCDheader.str );
271
+ PCDheader.size = /^SIZE (.*)/im.exec( PCDheader.str );
272
+ PCDheader.type = /^TYPE (.*)/im.exec( PCDheader.str );
273
+ PCDheader.count = /^COUNT (.*)/im.exec( PCDheader.str );
274
+ PCDheader.width = /^WIDTH (.*)/im.exec( PCDheader.str );
275
+ PCDheader.height = /^HEIGHT (.*)/im.exec( PCDheader.str );
276
+ PCDheader.viewpoint = /^VIEWPOINT (.*)/im.exec( PCDheader.str );
277
+ PCDheader.points = /^POINTS (.*)/im.exec( PCDheader.str );
278
+
279
+ // evaluate
280
+
281
+ if ( PCDheader.version !== null )
282
+ PCDheader.version = parseFloat( PCDheader.version[ 1 ] );
283
+
284
+ PCDheader.fields = ( PCDheader.fields !== null ) ? PCDheader.fields[ 1 ].split( ' ' ) : [];
285
+
286
+ if ( PCDheader.type !== null )
287
+ PCDheader.type = PCDheader.type[ 1 ].split( ' ' );
288
+
289
+ if ( PCDheader.width !== null )
290
+ PCDheader.width = parseInt( PCDheader.width[ 1 ] );
291
+
292
+ if ( PCDheader.height !== null )
293
+ PCDheader.height = parseInt( PCDheader.height[ 1 ] );
294
+
295
+ if ( PCDheader.viewpoint !== null )
296
+ PCDheader.viewpoint = PCDheader.viewpoint[ 1 ];
297
+
298
+ if ( PCDheader.points !== null )
299
+ PCDheader.points = parseInt( PCDheader.points[ 1 ], 10 );
300
+
301
+ if ( PCDheader.points === null )
302
+ PCDheader.points = PCDheader.width * PCDheader.height;
303
+
304
+ if ( PCDheader.size !== null ) {
305
+
306
+ PCDheader.size = PCDheader.size[ 1 ].split( ' ' ).map( function ( x ) {
307
+
308
+ return parseInt( x, 10 );
309
+
310
+ } );
311
+
312
+ }
313
+
314
+ if ( PCDheader.count !== null ) {
315
+
316
+ PCDheader.count = PCDheader.count[ 1 ].split( ' ' ).map( function ( x ) {
317
+
318
+ return parseInt( x, 10 );
319
+
320
+ } );
321
+
322
+ } else {
323
+
324
+ PCDheader.count = [];
325
+
326
+ for ( let i = 0, l = PCDheader.fields.length; i < l; i ++ ) {
327
+
328
+ PCDheader.count.push( 1 );
329
+
330
+ }
331
+
332
+ }
333
+
334
+ PCDheader.offset = {};
335
+
336
+ let sizeSum = 0;
337
+
338
+ for ( let i = 0, l = PCDheader.fields.length; i < l; i ++ ) {
339
+
340
+ if ( PCDheader.data === 'ascii' ) {
341
+
342
+ PCDheader.offset[ PCDheader.fields[ i ] ] = i;
343
+
344
+ } else {
345
+
346
+ PCDheader.offset[ PCDheader.fields[ i ] ] = sizeSum;
347
+ sizeSum += PCDheader.size[ i ] * PCDheader.count[ i ];
348
+
349
+ }
350
+
351
+ }
352
+
353
+ // for binary only
354
+
355
+ PCDheader.rowSize = sizeSum;
356
+
357
+ return PCDheader;
358
+
359
+ }
360
+
361
+ // parse header
362
+
363
+ const PCDheader = parseHeader( data );
364
+
365
+ // parse data
366
+
367
+ const position = [];
368
+ const normal = [];
369
+ const color = [];
370
+ const intensity = [];
371
+ const label = [];
372
+
373
+ const c = new three.Color();
374
+
375
+ // ascii
376
+
377
+ if ( PCDheader.data === 'ascii' ) {
378
+
379
+ const offset = PCDheader.offset;
380
+ const textData = new TextDecoder().decode( data );
381
+ const pcdData = textData.slice( PCDheader.headerLen );
382
+ const lines = pcdData.split( '\n' );
383
+
384
+ for ( let i = 0, l = lines.length; i < l; i ++ ) {
385
+
386
+ if ( lines[ i ] === '' ) continue;
387
+
388
+ const line = lines[ i ].split( ' ' );
389
+
390
+ if ( offset.x !== undefined ) {
391
+
392
+ position.push( parseFloat( line[ offset.x ] ) );
393
+ position.push( parseFloat( line[ offset.y ] ) );
394
+ position.push( parseFloat( line[ offset.z ] ) );
395
+
396
+ }
397
+
398
+ if ( offset.rgb !== undefined ) {
399
+
400
+ const rgb_field_index = PCDheader.fields.findIndex( ( field ) => field === 'rgb' );
401
+ const rgb_type = PCDheader.type[ rgb_field_index ];
402
+
403
+ const float = parseFloat( line[ offset.rgb ] );
404
+ let rgb = float;
405
+
406
+ if ( rgb_type === 'F' ) {
407
+
408
+ // treat float values as int
409
+ // https://github.com/daavoo/pyntcloud/pull/204/commits/7b4205e64d5ed09abe708b2e91b615690c24d518
410
+ const farr = new Float32Array( 1 );
411
+ farr[ 0 ] = float;
412
+ rgb = new Int32Array( farr.buffer )[ 0 ];
413
+
414
+ }
415
+
416
+ const r = ( ( rgb >> 16 ) & 0x0000ff ) / 255;
417
+ const g = ( ( rgb >> 8 ) & 0x0000ff ) / 255;
418
+ const b = ( ( rgb >> 0 ) & 0x0000ff ) / 255;
419
+
420
+ c.setRGB( r, g, b, three.SRGBColorSpace );
421
+
422
+ color.push( c.r, c.g, c.b );
423
+
424
+ }
425
+
426
+ if ( offset.normal_x !== undefined ) {
427
+
428
+ normal.push( parseFloat( line[ offset.normal_x ] ) );
429
+ normal.push( parseFloat( line[ offset.normal_y ] ) );
430
+ normal.push( parseFloat( line[ offset.normal_z ] ) );
431
+
432
+ }
433
+
434
+ if ( offset.intensity !== undefined ) {
435
+
436
+ intensity.push( parseFloat( line[ offset.intensity ] ) );
437
+
438
+ }
439
+
440
+ if ( offset.label !== undefined ) {
441
+
442
+ label.push( parseInt( line[ offset.label ] ) );
443
+
444
+ }
445
+
446
+ }
447
+
448
+ }
449
+
450
+ // binary-compressed
451
+
452
+ // normally data in PCD files are organized as array of structures: XYZRGBXYZRGB
453
+ // binary compressed PCD files organize their data as structure of arrays: XXYYZZRGBRGB
454
+ // that requires a totally different parsing approach compared to non-compressed data
455
+
456
+ if ( PCDheader.data === 'binary_compressed' ) {
457
+
458
+ const sizes = new Uint32Array( data.slice( PCDheader.headerLen, PCDheader.headerLen + 8 ) );
459
+ const compressedSize = sizes[ 0 ];
460
+ const decompressedSize = sizes[ 1 ];
461
+ const decompressed = decompressLZF( new Uint8Array( data, PCDheader.headerLen + 8, compressedSize ), decompressedSize );
462
+ const dataview = new DataView( decompressed.buffer );
463
+
464
+ const offset = PCDheader.offset;
465
+
466
+ for ( let i = 0; i < PCDheader.points; i ++ ) {
467
+
468
+ if ( offset.x !== undefined ) {
469
+
470
+ const xIndex = PCDheader.fields.indexOf( 'x' );
471
+ const yIndex = PCDheader.fields.indexOf( 'y' );
472
+ const zIndex = PCDheader.fields.indexOf( 'z' );
473
+ position.push( this._getDataView( dataview, ( PCDheader.points * offset.x ) + PCDheader.size[ xIndex ] * i, PCDheader.type[ xIndex ], PCDheader.size[ xIndex ] ) );
474
+ position.push( this._getDataView( dataview, ( PCDheader.points * offset.y ) + PCDheader.size[ yIndex ] * i, PCDheader.type[ yIndex ], PCDheader.size[ yIndex ] ) );
475
+ position.push( this._getDataView( dataview, ( PCDheader.points * offset.z ) + PCDheader.size[ zIndex ] * i, PCDheader.type[ zIndex ], PCDheader.size[ zIndex ] ) );
476
+
477
+ }
478
+
479
+ if ( offset.rgb !== undefined ) {
480
+
481
+ const rgbIndex = PCDheader.fields.indexOf( 'rgb' );
482
+
483
+ const r = dataview.getUint8( ( PCDheader.points * offset.rgb ) + PCDheader.size[ rgbIndex ] * i + 2 ) / 255.0;
484
+ const g = dataview.getUint8( ( PCDheader.points * offset.rgb ) + PCDheader.size[ rgbIndex ] * i + 1 ) / 255.0;
485
+ const b = dataview.getUint8( ( PCDheader.points * offset.rgb ) + PCDheader.size[ rgbIndex ] * i + 0 ) / 255.0;
486
+
487
+ c.setRGB( r, g, b, three.SRGBColorSpace );
488
+
489
+ color.push( c.r, c.g, c.b );
490
+
491
+ }
492
+
493
+ if ( offset.normal_x !== undefined ) {
494
+
495
+ const xIndex = PCDheader.fields.indexOf( 'normal_x' );
496
+ const yIndex = PCDheader.fields.indexOf( 'normal_y' );
497
+ const zIndex = PCDheader.fields.indexOf( 'normal_z' );
498
+ normal.push( this._getDataView( dataview, ( PCDheader.points * offset.normal_x ) + PCDheader.size[ xIndex ] * i, PCDheader.type[ xIndex ], PCDheader.size[ xIndex ] ) );
499
+ normal.push( this._getDataView( dataview, ( PCDheader.points * offset.normal_y ) + PCDheader.size[ yIndex ] * i, PCDheader.type[ yIndex ], PCDheader.size[ yIndex ] ) );
500
+ normal.push( this._getDataView( dataview, ( PCDheader.points * offset.normal_z ) + PCDheader.size[ zIndex ] * i, PCDheader.type[ zIndex ], PCDheader.size[ zIndex ] ) );
501
+
502
+ }
503
+
504
+ if ( offset.intensity !== undefined ) {
505
+
506
+ const intensityIndex = PCDheader.fields.indexOf( 'intensity' );
507
+ intensity.push( this._getDataView( dataview, ( PCDheader.points * offset.intensity ) + PCDheader.size[ intensityIndex ] * i, PCDheader.type[ intensityIndex ], PCDheader.size[ intensityIndex ] ) );
508
+
509
+ }
510
+
511
+ if ( offset.label !== undefined ) {
512
+
513
+ const labelIndex = PCDheader.fields.indexOf( 'label' );
514
+ label.push( dataview.getInt32( ( PCDheader.points * offset.label ) + PCDheader.size[ labelIndex ] * i, this.littleEndian ) );
515
+
516
+ }
517
+
518
+ }
519
+
520
+ }
521
+
522
+ // binary
523
+
524
+ if ( PCDheader.data === 'binary' ) {
525
+
526
+ const dataview = new DataView( data, PCDheader.headerLen );
527
+ const offset = PCDheader.offset;
528
+
529
+ for ( let i = 0, row = 0; i < PCDheader.points; i ++, row += PCDheader.rowSize ) {
530
+
531
+ if ( offset.x !== undefined ) {
532
+
533
+ const xIndex = PCDheader.fields.indexOf( 'x' );
534
+ const yIndex = PCDheader.fields.indexOf( 'y' );
535
+ const zIndex = PCDheader.fields.indexOf( 'z' );
536
+ position.push( this._getDataView( dataview, row + offset.x, PCDheader.type[ xIndex ], PCDheader.size[ xIndex ] ) );
537
+ position.push( this._getDataView( dataview, row + offset.y, PCDheader.type[ yIndex ], PCDheader.size[ yIndex ] ) );
538
+ position.push( this._getDataView( dataview, row + offset.z, PCDheader.type[ zIndex ], PCDheader.size[ zIndex ] ) );
539
+
540
+ }
541
+
542
+ if ( offset.rgb !== undefined ) {
543
+
544
+ const r = dataview.getUint8( row + offset.rgb + 2 ) / 255.0;
545
+ const g = dataview.getUint8( row + offset.rgb + 1 ) / 255.0;
546
+ const b = dataview.getUint8( row + offset.rgb + 0 ) / 255.0;
547
+
548
+ c.setRGB( r, g, b, three.SRGBColorSpace );
549
+
550
+ color.push( c.r, c.g, c.b );
551
+
552
+ }
553
+
554
+ if ( offset.normal_x !== undefined ) {
555
+
556
+ const xIndex = PCDheader.fields.indexOf( 'normal_x' );
557
+ const yIndex = PCDheader.fields.indexOf( 'normal_y' );
558
+ const zIndex = PCDheader.fields.indexOf( 'normal_z' );
559
+ normal.push( this._getDataView( dataview, row + offset.normal_x, PCDheader.type[ xIndex ], PCDheader.size[ xIndex ] ) );
560
+ normal.push( this._getDataView( dataview, row + offset.normal_y, PCDheader.type[ yIndex ], PCDheader.size[ yIndex ] ) );
561
+ normal.push( this._getDataView( dataview, row + offset.normal_z, PCDheader.type[ zIndex ], PCDheader.size[ zIndex ] ) );
562
+
563
+ }
564
+
565
+ if ( offset.intensity !== undefined ) {
566
+
567
+ const intensityIndex = PCDheader.fields.indexOf( 'intensity' );
568
+ intensity.push( this._getDataView( dataview, row + offset.intensity, PCDheader.type[ intensityIndex ], PCDheader.size[ intensityIndex ] ) );
569
+
570
+ }
571
+
572
+ if ( offset.label !== undefined ) {
573
+
574
+ label.push( dataview.getInt32( row + offset.label, this.littleEndian ) );
575
+
576
+ }
577
+
578
+ }
579
+
580
+ }
581
+
582
+ // build geometry
583
+
584
+ const geometry = new three.BufferGeometry();
585
+
586
+ if ( position.length > 0 ) geometry.setAttribute( 'position', new three.Float32BufferAttribute( position, 3 ) );
587
+ if ( normal.length > 0 ) geometry.setAttribute( 'normal', new three.Float32BufferAttribute( normal, 3 ) );
588
+ if ( color.length > 0 ) geometry.setAttribute( 'color', new three.Float32BufferAttribute( color, 3 ) );
589
+ if ( intensity.length > 0 ) geometry.setAttribute( 'intensity', new three.Float32BufferAttribute( intensity, 1 ) );
590
+ if ( label.length > 0 ) geometry.setAttribute( 'label', new three.Int32BufferAttribute( label, 1 ) );
591
+
592
+ geometry.computeBoundingSphere();
593
+
594
+ // build material
595
+
596
+ const material = new three.PointsMaterial( { size: 0.005 } );
597
+
598
+ if ( color.length > 0 ) {
599
+
600
+ material.vertexColors = true;
601
+
602
+ }
603
+
604
+ // build point cloud
605
+
606
+ return new three.Points( geometry, material );
607
+
608
+ }
609
+
610
+ }
611
+
612
+ ///////////////////////////////////////////////////////////////////////////////
613
+ // Copyright (C) 2002-2025, Open Design Alliance (the "Alliance").
614
+ // All rights reserved.
615
+ //
616
+ // This software and its documentation and related materials are owned by
617
+ // the Alliance. The software may only be incorporated into application
618
+ // programs owned by members of the Alliance, subject to a signed
619
+ // Membership Agreement and Supplemental Software License Agreement with the
620
+ // Alliance. The structure and organization of this software are the valuable
621
+ // trade secrets of the Alliance and its suppliers. The software is also
622
+ // protected by copyright law and international treaty provisions. Application
623
+ // programs incorporating this software must include the following statement
624
+ // with their copyright notices:
625
+ //
626
+ // This application incorporates Open Design Alliance software pursuant to a
627
+ // license agreement with Open Design Alliance.
628
+ // Open Design Alliance Copyright (C) 2002-2025 by Open Design Alliance.
629
+ // All rights reserved.
630
+ //
631
+ // By use of this software, its documentation or related materials, you
632
+ // acknowledge and accept the above terms.
633
+ ///////////////////////////////////////////////////////////////////////////////
634
+
635
+
636
+ const THREE = {
637
+ Box3: three.Box3,
638
+ BufferAttribute: three.BufferAttribute,
639
+ BufferGeometry: three.BufferGeometry,
640
+ Color: three.Color,
641
+ Euler: three.Euler,
642
+ Float32BufferAttribute: three.Float32BufferAttribute,
643
+ Group: three.Group,
644
+ Line: three.Line,
645
+ LineBasicMaterial: three.LineBasicMaterial,
646
+ Matrix4: three.Matrix4,
647
+ Mesh: three.Mesh,
648
+ MeshLambertMaterial: three.MeshLambertMaterial,
649
+ MeshStandardMaterial: three.MeshStandardMaterial,
650
+ Points: three.Points,
651
+ PointsMaterial: three.PointsMaterial,
652
+ PerspectiveCamera: three.PerspectiveCamera,
653
+ Scene: three.Scene,
654
+ Vector3: three.Vector3,
655
+ };
656
+
657
+ // ifcx-core/layers/layer-providers.ts
658
+ var StackedLayerProvider = class {
659
+ providers;
660
+ constructor(providers) {
661
+ this.providers = providers;
662
+ }
663
+ async GetLayerByURI(uri) {
664
+ let errorStack = [];
665
+ for (let provider of this.providers) {
666
+ let layer = await provider.GetLayerByURI(uri);
667
+ if (!(layer instanceof Error)) {
668
+ return layer;
669
+ } else {
670
+ errorStack.push(layer);
671
+ }
672
+ }
673
+ return new Error(JSON.stringify(errorStack));
674
+ }
675
+ };
676
+ var InMemoryLayerProvider = class {
677
+ layers;
678
+ constructor() {
679
+ this.layers = /* @__PURE__ */ new Map();
680
+ }
681
+ GetLayerByURI(uri) {
682
+ if (!this.layers.has(uri)) {
683
+ return new Error(`File with uri "${uri}" not found`);
684
+ }
685
+ return Promise.resolve(this.layers.get(uri));
686
+ }
687
+ add(file) {
688
+ if (this.layers.has(file.header.id)) {
689
+ throw new Error(`Inserting file with duplicate ID "${file.header.id}"`);
690
+ }
691
+ this.layers.set(file.header.id, file);
692
+ return this;
693
+ }
694
+ AddAll(files) {
695
+ files.forEach((f) => this.add(f));
696
+ return this;
697
+ }
698
+ };
699
+ function log(bla) {
700
+ {
701
+ console.log(`${JSON.stringify(arguments)}`);
702
+ }
703
+ }
704
+
705
+ // ifcx-core/layers/fetch-layer-provider.ts
706
+ var FetchLayerProvider = class {
707
+ layers;
708
+ constructor() {
709
+ this.layers = /* @__PURE__ */ new Map();
710
+ }
711
+ async FetchJson(url) {
712
+ let result = await fetch(url);
713
+ if (!result.ok) {
714
+ return new Error(`Failed to fetch ${url}: ${result.status}`);
715
+ }
716
+ try {
717
+ return await result.json();
718
+ } catch (e) {
719
+ log(url);
720
+ return new Error(`Failed to parse json at ${url}: ${e}`);
721
+ }
722
+ }
723
+ async GetLayerByURI(uri) {
724
+ if (!this.layers.has(uri)) {
725
+ let fetched = await this.FetchJson(uri);
726
+ if (fetched instanceof Error) {
727
+ return new Error(`File with id "${uri}" not found`);
728
+ }
729
+ let file = fetched;
730
+ this.layers.set(uri, file);
731
+ return file;
732
+ }
733
+ return this.layers.get(uri);
734
+ }
735
+ };
736
+
737
+ // ifcx-core/util/mm.ts
738
+ function MMSet(map, key, value) {
739
+ if (map.has(key)) {
740
+ map.get(key)?.push(value);
741
+ } else {
742
+ map.set(key, [value]);
743
+ }
744
+ }
745
+
746
+ // ifcx-core/composition/cycles.ts
747
+ var CycleError = class extends Error {};
748
+ function FindRootsOrCycles(nodes) {
749
+ let dependencies = /* @__PURE__ */ new Map();
750
+ let dependents = /* @__PURE__ */ new Map();
751
+ nodes.forEach((node, path) => {
752
+ Object.keys(node.inherits).forEach((inheritName) => {
753
+ MMSet(dependencies, path, node.inherits[inheritName]);
754
+ MMSet(dependents, node.inherits[inheritName], path);
755
+ });
756
+ Object.keys(node.children).forEach((childName) => {
757
+ MMSet(dependencies, path, node.children[childName]);
758
+ MMSet(dependents, node.children[childName], path);
759
+ });
760
+ });
761
+ let paths = [...nodes.keys()];
762
+ let perm = {};
763
+ let temp = {};
764
+ function visit(path) {
765
+ if (perm[path]) return;
766
+ if (temp[path]) throw new Error(`CYCLE!`);
767
+ temp[path] = true;
768
+ let deps = dependencies.get(path);
769
+ if (deps) {
770
+ deps.forEach((dep) => visit(dep));
771
+ }
772
+ perm[path] = true;
773
+ }
774
+ let roots = /* @__PURE__ */ new Set();
775
+ try {
776
+ paths.forEach((path) => {
777
+ if (!dependents.has(path) && path.indexOf("/") === -1) {
778
+ roots.add(path);
779
+ }
780
+ visit(path);
781
+ });
782
+ } catch (e) {
783
+ return null;
784
+ }
785
+ return roots;
786
+ }
787
+
788
+ // ifcx-core/composition/path.ts
789
+ function GetHead(path) {
790
+ return path.split("/")[0];
791
+ }
792
+ function GetTail(path) {
793
+ let parts = path.split("/");
794
+ parts.shift();
795
+ return parts.join("/");
796
+ }
797
+
798
+ // ifcx-core/composition/node.ts
799
+ function MakePostCompositionNode(node) {
800
+ return {
801
+ node,
802
+ children: /* @__PURE__ */ new Map(),
803
+ attributes: /* @__PURE__ */ new Map(),
804
+ };
805
+ }
806
+ function GetChildNodeWithPath(node, path) {
807
+ if (path === "") return node;
808
+ let parts = path.split("/");
809
+ let child = node.children.get(parts[0]);
810
+ if (child) {
811
+ if (parts.length === 1) {
812
+ return child;
813
+ }
814
+ return GetChildNodeWithPath(child, GetTail(path));
815
+ } else {
816
+ return null;
817
+ }
818
+ }
819
+
820
+ // ifcx-core/composition/compose.ts
821
+ function FlattenPathToPreCompositionNode(path, inputNodes) {
822
+ let compositionNode = {
823
+ path,
824
+ children: {},
825
+ inherits: {},
826
+ attributes: {},
827
+ };
828
+ inputNodes.forEach((node) => {
829
+ Object.keys(node.children).forEach((childName) => {
830
+ compositionNode.children[childName] = node.children[childName];
831
+ });
832
+ Object.keys(node.inherits).forEach((inheritName) => {
833
+ let ih = node.inherits[inheritName];
834
+ if (ih === null) {
835
+ delete compositionNode.inherits[inheritName];
836
+ } else {
837
+ compositionNode.inherits[inheritName] = ih;
838
+ }
839
+ });
840
+ Object.keys(node.attributes).forEach((attrName) => {
841
+ compositionNode.attributes[attrName] = node.attributes[attrName];
842
+ });
843
+ });
844
+ return compositionNode;
845
+ }
846
+ function FlattenCompositionInput(input) {
847
+ let compositionNodes = /* @__PURE__ */ new Map();
848
+ for (let [path, inputNodes] of input) {
849
+ compositionNodes.set(path, FlattenPathToPreCompositionNode(path, inputNodes));
850
+ }
851
+ return compositionNodes;
852
+ }
853
+ function ExpandFirstRootInInput(nodes) {
854
+ let roots = FindRootsOrCycles(nodes);
855
+ if (!roots) {
856
+ throw new CycleError();
857
+ }
858
+ return ComposeNodeFromPath([...roots.values()][0], nodes);
859
+ }
860
+ function CreateArtificialRoot(nodes) {
861
+ let roots = FindRootsOrCycles(nodes);
862
+ if (!roots) {
863
+ throw new CycleError();
864
+ }
865
+ let pseudoRoot = {
866
+ node: "",
867
+ attributes: /* @__PURE__ */ new Map(),
868
+ children: /* @__PURE__ */ new Map(),
869
+ };
870
+ roots.forEach((root) => {
871
+ pseudoRoot.children.set(root, ComposeNodeFromPath(root, nodes));
872
+ });
873
+ return pseudoRoot;
874
+ }
875
+ function ComposeNodeFromPath(path, preCompositionNodes) {
876
+ return ComposeNode(path, MakePostCompositionNode(path), preCompositionNodes);
877
+ }
878
+ function ComposeNode(path, postCompositionNode, preCompositionNodes) {
879
+ let preCompositionNode = preCompositionNodes.get(path);
880
+ if (preCompositionNode) {
881
+ AddDataFromPreComposition(preCompositionNode, postCompositionNode, preCompositionNodes);
882
+ }
883
+ postCompositionNode.children.forEach((child, name) => {
884
+ ComposeNode(`${path}/${name}`, child, preCompositionNodes);
885
+ });
886
+ return postCompositionNode;
887
+ }
888
+ function AddDataFromPreComposition(input, node, nodes) {
889
+ Object.values(input.inherits).forEach((inheritPath) => {
890
+ let classNode = ComposeNodeFromPath(GetHead(inheritPath), nodes);
891
+ let subnode = GetChildNodeWithPath(classNode, GetTail(inheritPath));
892
+ if (!subnode) throw new Error(`Unknown node ${inheritPath}`);
893
+ subnode.children.forEach((child, childName) => {
894
+ node.children.set(childName, child);
895
+ });
896
+ for (let [attrID, attr] of subnode.attributes) {
897
+ node.attributes.set(attrID, attr);
898
+ }
899
+ });
900
+ Object.entries(input.children).forEach(([childName, child]) => {
901
+ if (child !== null) {
902
+ let classNode = ComposeNodeFromPath(GetHead(child), nodes);
903
+ let subnode = GetChildNodeWithPath(classNode, GetTail(child));
904
+ if (!subnode) throw new Error(`Unknown node ${child}`);
905
+ node.children.set(childName, subnode);
906
+ } else {
907
+ node.children.delete(childName);
908
+ }
909
+ });
910
+ Object.entries(input.attributes).forEach(([attrID, attr]) => {
911
+ node.attributes.set(attrID, attr);
912
+ });
913
+ }
914
+
915
+ // ifcx-core/schema/schema-validation.ts
916
+ var SchemaValidationError = class extends Error {};
917
+ function ValidateAttributeValue(desc, value, path, schemas) {
918
+ if (desc.optional && value === undefined) {
919
+ return;
920
+ }
921
+ if (desc.inherits) {
922
+ desc.inherits.forEach((inheritedSchemaID) => {
923
+ let inheritedSchema = schemas[inheritedSchemaID];
924
+ if (!inheritedSchema) {
925
+ throw new SchemaValidationError(`Unknown inherited schema id "${desc.inherits}"`);
926
+ }
927
+ ValidateAttributeValue(inheritedSchema.value, value, path, schemas);
928
+ });
929
+ }
930
+ if (desc.dataType === "Boolean") {
931
+ if (typeof value !== "boolean") {
932
+ throw new SchemaValidationError(`Expected "${value}" to be of type boolean`);
933
+ }
934
+ } else if (desc.dataType === "String") {
935
+ if (typeof value !== "string") {
936
+ throw new SchemaValidationError(`Expected "${value}" to be of type string`);
937
+ }
938
+ } else if (desc.dataType === "DateTime") {
939
+ if (typeof value !== "string") {
940
+ throw new SchemaValidationError(`Expected "${value}" to be of type date`);
941
+ }
942
+ } else if (desc.dataType === "Enum") {
943
+ if (typeof value !== "string") {
944
+ throw new SchemaValidationError(`Expected "${value}" to be of type string`);
945
+ }
946
+ let found = desc.enumRestrictions.options.filter((option) => option === value).length === 1;
947
+ if (!found) {
948
+ throw new SchemaValidationError(`Expected "${value}" to be one of [${desc.enumRestrictions.options.join(",")}]`);
949
+ }
950
+ } else if (desc.dataType === "Integer") {
951
+ if (typeof value !== "number") {
952
+ throw new SchemaValidationError(`Expected "${value}" to be of type int`);
953
+ }
954
+ } else if (desc.dataType === "Real") {
955
+ if (typeof value !== "number") {
956
+ throw new SchemaValidationError(`Expected "${value}" to be of type real`);
957
+ }
958
+ } else if (desc.dataType === "Reference") {
959
+ if (typeof value !== "string") {
960
+ throw new SchemaValidationError(`Expected "${value}" to be of type string`);
961
+ }
962
+ } else if (desc.dataType === "Object") {
963
+ if (typeof value !== "object") {
964
+ throw new SchemaValidationError(`Expected "${value}" to be of type object`);
965
+ }
966
+ if (desc.objectRestrictions) {
967
+ Object.keys(desc.objectRestrictions.values).forEach((key) => {
968
+ let optional = desc.objectRestrictions.values[key].optional;
969
+ let hasOwn = Object.hasOwn(value, key);
970
+ if (optional && !hasOwn) return;
971
+ if (!hasOwn) {
972
+ throw new SchemaValidationError(`Expected "${value}" to have key ${key}`);
973
+ }
974
+ ValidateAttributeValue(desc.objectRestrictions.values[key], value[key], path + "." + key, schemas);
975
+ });
976
+ }
977
+ } else if (desc.dataType === "Array") {
978
+ if (!Array.isArray(value)) {
979
+ throw new SchemaValidationError(`Expected "${value}" to be of type array`);
980
+ }
981
+ value.forEach((entry) => {
982
+ ValidateAttributeValue(desc.arrayRestrictions.value, entry, path + ".<array>.", schemas);
983
+ });
984
+ } else {
985
+ throw new SchemaValidationError(`Unexpected datatype ${desc.dataType}`);
986
+ }
987
+ }
988
+ function Validate(schemas, inputNodes) {
989
+ inputNodes.forEach((node) => {
990
+ Object.keys(node.attributes)
991
+ .filter((v) => !v.startsWith("__internal"))
992
+ .forEach((schemaID) => {
993
+ if (!schemas[schemaID]) {
994
+ throw new SchemaValidationError(`Missing schema "${schemaID}" referenced by ["${node.path}"].attributes`);
995
+ }
996
+ let schema = schemas[schemaID];
997
+ let value = node.attributes[schemaID];
998
+ try {
999
+ ValidateAttributeValue(schema.value, value, "", schemas);
1000
+ } catch (e) {
1001
+ if (e instanceof SchemaValidationError) {
1002
+ throw new SchemaValidationError(
1003
+ `Error validating ["${node.path}"].attributes["${schemaID}"]: ${e.message}`
1004
+ );
1005
+ } else {
1006
+ throw e;
1007
+ }
1008
+ }
1009
+ });
1010
+ });
1011
+ }
1012
+
1013
+ // ifcx-core/workflows.ts
1014
+ function ToInputNodes(data) {
1015
+ let inputNodes = /* @__PURE__ */ new Map();
1016
+ data.forEach((ifcxNode) => {
1017
+ let node = {
1018
+ path: ifcxNode.path,
1019
+ children: ifcxNode.children ? ifcxNode.children : {},
1020
+ inherits: ifcxNode.inherits ? ifcxNode.inherits : {},
1021
+ attributes: ifcxNode.attributes ? ifcxNode.attributes : {},
1022
+ };
1023
+ MMSet(inputNodes, node.path, node);
1024
+ });
1025
+ return inputNodes;
1026
+ }
1027
+ function LoadIfcxFile(file, checkSchemas = true, createArtificialRoot = true) {
1028
+ let inputNodes = ToInputNodes(file.data);
1029
+ let compositionNodes = FlattenCompositionInput(inputNodes);
1030
+ try {
1031
+ if (checkSchemas) {
1032
+ Validate(file.schemas, compositionNodes);
1033
+ }
1034
+ } catch (e) {
1035
+ throw e;
1036
+ }
1037
+ if (createArtificialRoot) {
1038
+ return CreateArtificialRoot(compositionNodes);
1039
+ } else {
1040
+ return ExpandFirstRootInInput(compositionNodes);
1041
+ }
1042
+ }
1043
+ function Federate(files) {
1044
+ if (files.length === 0) {
1045
+ throw new Error(`Trying to federate empty set of files`);
1046
+ }
1047
+ let result = {
1048
+ header: files[0].header,
1049
+ schemas: {},
1050
+ data: [],
1051
+ };
1052
+ files.forEach((file) => {
1053
+ Object.keys(file.schemas).forEach((schemaID) => (result.schemas[schemaID] = file.schemas[schemaID]));
1054
+ });
1055
+ files.forEach((file) => {
1056
+ file.data.forEach((node) => result.data.push(node));
1057
+ });
1058
+ return Prune(result);
1059
+ }
1060
+ function Collapse(nodes, deleteEmpty = false) {
1061
+ let result = {
1062
+ path: nodes[0].path,
1063
+ children: {},
1064
+ inherits: {},
1065
+ attributes: {},
1066
+ };
1067
+ nodes.forEach((node) => {
1068
+ Object.keys(node.children).forEach((name) => {
1069
+ result.children[name] = node.children[name];
1070
+ });
1071
+ Object.keys(node.inherits).forEach((name) => {
1072
+ result.inherits[name] = node.inherits[name];
1073
+ });
1074
+ Object.keys(node.attributes).forEach((name) => {
1075
+ result.attributes[name] = node.attributes[name];
1076
+ });
1077
+ });
1078
+ if (deleteEmpty) {
1079
+ let empty = true;
1080
+ Object.keys(result.children).forEach((name) => {
1081
+ if (result.children[name] !== null) empty = false;
1082
+ });
1083
+ Object.keys(result.inherits).forEach((name) => {
1084
+ if (result.inherits[name] !== null) empty = false;
1085
+ });
1086
+ Object.keys(result.attributes).forEach((name) => {
1087
+ if (result.attributes[name] !== null) empty = false;
1088
+ });
1089
+ if (empty) return null;
1090
+ }
1091
+ return result;
1092
+ }
1093
+ function Prune(file, deleteEmpty = false) {
1094
+ let result = {
1095
+ header: file.header,
1096
+ imports: [],
1097
+ schemas: file.schemas,
1098
+ data: [],
1099
+ };
1100
+ let inputNodes = ToInputNodes(file.data);
1101
+ inputNodes.forEach((nodes) => {
1102
+ let collapsed = Collapse(nodes, deleteEmpty);
1103
+ if (collapsed)
1104
+ result.data.push({
1105
+ path: collapsed.path,
1106
+ children: collapsed.children,
1107
+ inherits: collapsed.inherits,
1108
+ attributes: collapsed.attributes,
1109
+ });
1110
+ });
1111
+ return result;
1112
+ }
1113
+
1114
+ // ifcx-core/layers/layer-stack.ts
1115
+ var IfcxLayerStack = class {
1116
+ // main layer at 0
1117
+ layers;
1118
+ tree;
1119
+ schemas;
1120
+ federated;
1121
+ constructor(layers) {
1122
+ this.layers = layers;
1123
+ this.Compose();
1124
+ }
1125
+ GetLayerIds() {
1126
+ return this.layers.map((l) => l.header.id);
1127
+ }
1128
+ Compose() {
1129
+ this.federated = Federate(this.layers);
1130
+ this.schemas = this.federated.schemas;
1131
+ this.tree = LoadIfcxFile(this.federated);
1132
+ }
1133
+ GetFullTree() {
1134
+ this.Compose();
1135
+ return this.tree;
1136
+ }
1137
+ GetFederatedLayer() {
1138
+ return this.federated;
1139
+ }
1140
+ GetSchemas() {
1141
+ return this.schemas;
1142
+ }
1143
+ };
1144
+ var IfcxLayerStackBuilder = class {
1145
+ provider;
1146
+ mainLayerId = null;
1147
+ constructor(provider) {
1148
+ this.provider = provider;
1149
+ }
1150
+ FromId(id) {
1151
+ this.mainLayerId = id;
1152
+ return this;
1153
+ }
1154
+ async Build() {
1155
+ if (!this.mainLayerId) throw new Error(`no main layer ID specified`);
1156
+ let layers = await this.BuildLayerSet(this.mainLayerId);
1157
+ if (layers instanceof Error) {
1158
+ return layers;
1159
+ }
1160
+ try {
1161
+ return new IfcxLayerStack(layers);
1162
+ } catch (e) {
1163
+ return e;
1164
+ }
1165
+ }
1166
+ async SatisfyDependencies(activeLayer, placed, orderedLayers) {
1167
+ let pending = [];
1168
+ for (const impt of activeLayer.imports) {
1169
+ if (!placed.has(impt.uri)) {
1170
+ let layer = await this.provider.GetLayerByURI(impt.uri);
1171
+ if (layer instanceof Error) {
1172
+ return layer;
1173
+ }
1174
+ pending.push(layer);
1175
+ placed.set(impt.uri, true);
1176
+ }
1177
+ }
1178
+ let temp = [];
1179
+ for (const layer of pending) {
1180
+ temp.push(layer);
1181
+ let layers = await this.SatisfyDependencies(layer, placed, orderedLayers);
1182
+ if (layers instanceof Error) {
1183
+ return layers;
1184
+ }
1185
+ temp.push(...layers);
1186
+ }
1187
+ temp.forEach((t) => orderedLayers.push(t));
1188
+ return temp;
1189
+ }
1190
+ async BuildLayerSet(activeLayerID) {
1191
+ let activeLayer = await this.provider.GetLayerByURI(activeLayerID);
1192
+ if (activeLayer instanceof Error) {
1193
+ return activeLayer;
1194
+ }
1195
+ let layerSet = [activeLayer];
1196
+ let placed = /* @__PURE__ */ new Map();
1197
+ placed.set(activeLayer.header.id, true);
1198
+ let result = await this.SatisfyDependencies(activeLayer, placed, layerSet);
1199
+ if (result instanceof Error) {
1200
+ return result;
1201
+ }
1202
+ return layerSet;
1203
+ }
1204
+ };
1205
+
1206
+ // viewer/compose-flattened.ts
1207
+ function TreeNodeToComposedObject(path, node, schemas) {
1208
+ let co = {
1209
+ name: path,
1210
+ attributes: {},
1211
+ children: [],
1212
+ };
1213
+ node.children.forEach((childNode, childName) => {
1214
+ co.children?.push(TreeNodeToComposedObject(`${path}/${childName}`, childNode, schemas));
1215
+ });
1216
+ node.attributes.forEach((attr, attrName) => {
1217
+ if (attr && typeof attr === "object" && !Array.isArray(attr)) {
1218
+ Object.keys(attr).forEach((compname) => {
1219
+ co.attributes[`${attrName}::${compname}`] = attr[compname];
1220
+ });
1221
+ } else {
1222
+ let schema = schemas[attrName];
1223
+ if (schema && schema.value.quantityKind) {
1224
+ let postfix = "";
1225
+ let quantityKind = schema.value.quantityKind;
1226
+ if (quantityKind === "Length") {
1227
+ postfix = "m";
1228
+ } else if (quantityKind === "Volume") {
1229
+ postfix = "m" + String.fromCodePoint(179);
1230
+ }
1231
+ co.attributes[attrName] = `${attr} ${postfix}`;
1232
+ } else {
1233
+ co.attributes[attrName] = attr;
1234
+ }
1235
+ }
1236
+ });
1237
+ if (Object.keys(co.attributes).length === 0) delete co.attributes;
1238
+ return co;
1239
+ }
1240
+ async function compose3(files) {
1241
+ let userDefinedOrder = {
1242
+ header: { ...files[0].header },
1243
+ imports: files.map((f) => {
1244
+ return { uri: f.header.id };
1245
+ }),
1246
+ schemas: {},
1247
+ data: [],
1248
+ };
1249
+ userDefinedOrder.header.id = "USER_DEF";
1250
+ let provider = new StackedLayerProvider([
1251
+ new InMemoryLayerProvider().AddAll([userDefinedOrder, ...files]),
1252
+ new FetchLayerProvider(),
1253
+ ]);
1254
+ let layerStack = await new IfcxLayerStackBuilder(provider).FromId(userDefinedOrder.header.id).Build();
1255
+ if (layerStack instanceof Error) {
1256
+ throw layerStack;
1257
+ }
1258
+ layerStack.GetFederatedLayer().data.forEach((n, i) => {
1259
+ n.attributes = n.attributes || {};
1260
+ n.attributes[`__internal_${i}`] = n.path;
1261
+ });
1262
+ return TreeNodeToComposedObject("", layerStack.GetFullTree(), layerStack.GetSchemas());
1263
+ }
1264
+ // var controls;
1265
+ // var renderer;
1266
+ var scene;
1267
+ var camera;
1268
+ var datas = [];
1269
+ var autoCamera = true;
1270
+ var objectMap = {};
1271
+ // var domMap = {};
1272
+ var primMap = {};
1273
+ // var currentPathMapping = null;
1274
+ // var rootPrim = null;
1275
+ // var selectedObject = null;
1276
+ // var selectedDom = null;
1277
+ // var raycaster = new THREE.Raycaster();
1278
+ // var mouse = new THREE.Vector2();
1279
+ var envMap;
1280
+ function init() {
1281
+ scene = new THREE.Scene();
1282
+ // const ambient = new THREE.AmbientLight(14544639, 0.4);
1283
+ // scene.add(ambient);
1284
+ // const keyLight = new THREE.DirectionalLight(16777215, 1);
1285
+ // keyLight.position.set(5, -10, 7.5);
1286
+ // scene.add(keyLight);
1287
+ // const fillLight = new THREE.DirectionalLight(16777215, 0.5);
1288
+ // fillLight.position.set(-5, 5, 5);
1289
+ // scene.add(fillLight);
1290
+ // const rimLight = new THREE.DirectionalLight(16777215, 0.3);
1291
+ // rimLight.position.set(0, 8, -10);
1292
+ // scene.add(rimLight);
1293
+ camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 100);
1294
+ camera.up.set(0, 0, 1);
1295
+ camera.position.set(50, 50, 50);
1296
+ camera.lookAt(0, 0, 0);
1297
+ scene.add(camera);
1298
+ // const nd = document.querySelector(".viewport");
1299
+ // renderer = new THREE.WebGLRenderer({
1300
+ // alpha: true,
1301
+ // logarithmicDepthBuffer: true,
1302
+ // });
1303
+ // const pmremGenerator = new THREE.PMREMGenerator(renderer);
1304
+ // pmremGenerator.compileEquirectangularShader();
1305
+ // new RGBELoader().load("images/wildflower_field_1k.hdr", function (texture) {
1306
+ // envMap = pmremGenerator.fromEquirectangular(texture).texture;
1307
+ // scene.environment = envMap;
1308
+ // texture.dispose();
1309
+ // pmremGenerator.dispose();
1310
+ // });
1311
+ // renderer.setSize(nd.offsetWidth, nd.offsetHeight);
1312
+ // controls = new OrbitControls(camera, renderer.domElement);
1313
+ // controls.enableDamping = true;
1314
+ // controls.dampingFactor = 0.25;
1315
+ // nd.appendChild(renderer.domElement);
1316
+ // renderer.domElement.addEventListener("click", onCanvasClick);
1317
+ return scene;
1318
+ }
1319
+ function HasAttr(node, attrName) {
1320
+ if (!node || !node.attributes) return false;
1321
+ return !!node.attributes[attrName];
1322
+ }
1323
+ // function setHighlight(obj, highlight) {
1324
+ // if (!obj) return;
1325
+ // obj.traverse((o) => {
1326
+ // const mat = o.material;
1327
+ // if (mat && mat.color) {
1328
+ // if (highlight) {
1329
+ // if (!o.userData._origColor) {
1330
+ // o.userData._origColor = mat.color.clone();
1331
+ // }
1332
+ // o.material = mat.clone();
1333
+ // o.material.color.set(16711680);
1334
+ // } else if (o.userData._origColor) {
1335
+ // mat.color.copy(o.userData._origColor);
1336
+ // delete o.userData._origColor;
1337
+ // }
1338
+ // }
1339
+ // });
1340
+ // }
1341
+ // function selectPath(path) {
1342
+ // if (!path) {
1343
+ // if (selectedObject) setHighlight(selectedObject, false);
1344
+ // if (selectedDom) selectedDom.classList.remove("selected");
1345
+ // selectedObject = null;
1346
+ // selectedDom = null;
1347
+ // return;
1348
+ // }
1349
+ // if (selectedObject) {
1350
+ // setHighlight(selectedObject, false);
1351
+ // }
1352
+ // if (selectedDom) {
1353
+ // selectedDom.classList.remove("selected");
1354
+ // }
1355
+ // selectedObject = objectMap[path] || null;
1356
+ // selectedDom = domMap[path] || null;
1357
+ // if (selectedObject) setHighlight(selectedObject, true);
1358
+ // if (selectedDom) selectedDom.classList.add("selected");
1359
+ // }
1360
+ // function onCanvasClick(event) {
1361
+ // const rect = renderer.domElement.getBoundingClientRect();
1362
+ // mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
1363
+ // mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
1364
+ // raycaster.setFromCamera(mouse, camera);
1365
+ // const intersects = raycaster.intersectObjects(Object.values(objectMap), true);
1366
+ // if (intersects.length > 0) {
1367
+ // let obj = intersects[0].object;
1368
+ // while (obj && !obj.userData.path) obj = obj.parent;
1369
+ // if (obj && obj.userData.path) {
1370
+ // const path = obj.userData.path;
1371
+ // const prim = primMap[path];
1372
+ // if (prim) {
1373
+ // handleClick(prim, currentPathMapping, rootPrim || prim);
1374
+ // }
1375
+ // selectPath(path);
1376
+ // }
1377
+ // } else {
1378
+ // selectPath(null);
1379
+ // }
1380
+ // }
1381
+ function tryCreateMeshGltfMaterial(path) {
1382
+ for (let p of path) {
1383
+ if (!p.attributes) {
1384
+ continue;
1385
+ }
1386
+ const pbrMetallicRoughness = p.attributes["gltf::material::pbrMetallicRoughness"];
1387
+ const normalTexture = p.attributes["gltf::material::normalTexture"];
1388
+ const occlusionTexture = p.attributes["gltf::material::occlusionTexture"];
1389
+ const emissiveTexture = p.attributes["gltf::material::emissiveTexture"];
1390
+ const emissiveFactor = p.attributes["gltf::material::emissiveFactor"];
1391
+ const alphaMode = p.attributes["gltf::material::alphaMode"];
1392
+ const alphaCutoff = p.attributes["gltf::material::alphaCutoff"];
1393
+ const doubleSided = p.attributes["gltf::material::doubleSided"];
1394
+ if (
1395
+ !pbrMetallicRoughness &&
1396
+ !normalTexture &&
1397
+ !occlusionTexture &&
1398
+ !emissiveTexture &&
1399
+ !emissiveFactor &&
1400
+ !alphaMode &&
1401
+ !alphaCutoff &&
1402
+ !doubleSided
1403
+ ) {
1404
+ continue;
1405
+ }
1406
+ let material = new THREE.MeshStandardMaterial();
1407
+ material.color = new THREE.Color(1, 1, 1);
1408
+ material.metalness = 1;
1409
+ material.roughness = 1;
1410
+ if (pbrMetallicRoughness) {
1411
+ let baseColorFactor = pbrMetallicRoughness["baseColorFactor"];
1412
+ if (baseColorFactor) {
1413
+ material.color = new THREE.Color(baseColorFactor[0], baseColorFactor[1], baseColorFactor[2]);
1414
+ }
1415
+ let metallicFactor = pbrMetallicRoughness["metallicFactor"];
1416
+ if (metallicFactor !== undefined) {
1417
+ material.metalness = metallicFactor;
1418
+ }
1419
+ let roughnessFactor = pbrMetallicRoughness["roughnessFactor"];
1420
+ if (roughnessFactor !== undefined) {
1421
+ material.roughness = roughnessFactor;
1422
+ }
1423
+ }
1424
+ material.envMap = envMap;
1425
+ material.needsUpdate = true;
1426
+ material.envMapRotation = new THREE.Euler(0.5 * Math.PI, 0, 0);
1427
+ return material;
1428
+ }
1429
+ return undefined;
1430
+ }
1431
+ function createMaterialFromParent(path) {
1432
+ let material = {
1433
+ color: new THREE.Color(0.6, 0.6, 0.6),
1434
+ transparent: false,
1435
+ opacity: 1,
1436
+ };
1437
+ for (let p of path) {
1438
+ const color = p.attributes ? p.attributes["bsi::ifc::presentation::diffuseColor"] : null;
1439
+ if (color) {
1440
+ material.color = new THREE.Color(...color);
1441
+ const opacity = p.attributes["bsi::ifc::presentation::opacity"];
1442
+ if (opacity) {
1443
+ material.transparent = true;
1444
+ material.opacity = opacity;
1445
+ }
1446
+ break;
1447
+ }
1448
+ }
1449
+ return material;
1450
+ }
1451
+ function createCurveFromJson(path) {
1452
+ let points = new Float32Array(path[0].attributes["usd::usdgeom::basiscurves::points"].flat());
1453
+ const geometry = new THREE.BufferGeometry();
1454
+ geometry.setAttribute("position", new THREE.BufferAttribute(points, 3));
1455
+ const material = createMaterialFromParent(path);
1456
+ let lineMaterial = new THREE.LineBasicMaterial({ ...material });
1457
+ lineMaterial.color.multiplyScalar(0.8);
1458
+ return new THREE.Line(geometry, lineMaterial);
1459
+ }
1460
+ function createMeshFromJson(path) {
1461
+ let points = new Float32Array(path[0].attributes["usd::usdgeom::mesh::points"].flat());
1462
+ let indices = new Uint16Array(path[0].attributes["usd::usdgeom::mesh::faceVertexIndices"]);
1463
+ const geometry = new THREE.BufferGeometry();
1464
+ geometry.setAttribute("position", new THREE.BufferAttribute(points, 3));
1465
+ geometry.setIndex(new THREE.BufferAttribute(indices, 1));
1466
+ geometry.computeVertexNormals();
1467
+ var meshMaterial;
1468
+ let gltfPbrMaterial = tryCreateMeshGltfMaterial(path);
1469
+ if (gltfPbrMaterial) {
1470
+ meshMaterial = gltfPbrMaterial;
1471
+ } else {
1472
+ const m = createMaterialFromParent(path);
1473
+ meshMaterial = new THREE.MeshLambertMaterial({ ...m });
1474
+ }
1475
+ return new THREE.Mesh(geometry, meshMaterial);
1476
+ }
1477
+ function createPointsFromJsonPcdBase64(path) {
1478
+ const base64_string = path[0].attributes["pcd::base64"];
1479
+ const decoded = atob(base64_string);
1480
+ const len = decoded.length;
1481
+ const bytes = new Uint8Array(len);
1482
+ for (let i = 0; i < len; i++) {
1483
+ bytes[i] = decoded.charCodeAt(i);
1484
+ }
1485
+ const loader = new PCDLoader();
1486
+ const points = loader.parse(bytes.buffer);
1487
+ points.material.sizeAttenuation = false;
1488
+ points.material.size = 2;
1489
+ return points;
1490
+ }
1491
+ function createPoints(geometry, withColors) {
1492
+ const material = new THREE.PointsMaterial();
1493
+ material.sizeAttenuation = false;
1494
+ material.fog = true;
1495
+ material.size = 5;
1496
+ material.color = new THREE.Color(withColors ? 16777215 : 0);
1497
+ if (withColors) {
1498
+ material.vertexColors = true;
1499
+ }
1500
+ return new THREE.Points(geometry, material);
1501
+ }
1502
+ function createPointsFromJsonArray(path) {
1503
+ const geometry = new THREE.BufferGeometry();
1504
+ const positions = new Float32Array(path[0].attributes["points::array::positions"].flat());
1505
+ geometry.setAttribute("position", new THREE.Float32BufferAttribute(positions, 3));
1506
+ const colors = path[0].attributes["points::array::colors"];
1507
+ if (colors) {
1508
+ const colors_ = new Float32Array(colors.flat());
1509
+ geometry.setAttribute("color", new THREE.Float32BufferAttribute(colors_, 3));
1510
+ }
1511
+ return createPoints(geometry, colors);
1512
+ }
1513
+ function base64ToArrayBuffer(str) {
1514
+ let binary;
1515
+ try {
1516
+ binary = atob(str);
1517
+ } catch (e) {
1518
+ throw new Error("base64 encoded string is invalid");
1519
+ }
1520
+ const bytes = new Uint8Array(binary.length);
1521
+ for (let i = 0; i < binary.length; ++i) {
1522
+ bytes[i] = binary.charCodeAt(i);
1523
+ }
1524
+ return bytes.buffer;
1525
+ }
1526
+ function createPointsFromJsonPositionBase64(path) {
1527
+ const geometry = new THREE.BufferGeometry();
1528
+ const positions_base64 = path[0].attributes["points::base64::positions"];
1529
+ const positions_bytes = base64ToArrayBuffer(positions_base64);
1530
+ if (!positions_bytes) {
1531
+ return null;
1532
+ }
1533
+ const positions = new Float32Array(positions_bytes);
1534
+ geometry.setAttribute("position", new THREE.Float32BufferAttribute(positions, 3));
1535
+ const colors_base64 = path[0].attributes["points::base64::colors"];
1536
+ if (colors_base64) {
1537
+ const colors_bytes = base64ToArrayBuffer(colors_base64);
1538
+ if (colors_bytes) {
1539
+ const colors = new Float32Array(colors_bytes);
1540
+ geometry.setAttribute("color", new THREE.Float32BufferAttribute(colors, 3));
1541
+ }
1542
+ }
1543
+ return createPoints(geometry, colors_base64);
1544
+ }
1545
+ function traverseTree(path, parent, pathMapping) {
1546
+ const node = path[0];
1547
+ let elem = new THREE.Group();
1548
+ if (HasAttr(node, "usd::usdgeom::visibility::visibility")) {
1549
+ if (node.attributes["usd::usdgeom::visibility::visibility"] === "invisible") {
1550
+ return;
1551
+ }
1552
+ } else if (HasAttr(node, "usd::usdgeom::mesh::points")) {
1553
+ elem = createMeshFromJson(path);
1554
+ } else if (HasAttr(node, "usd::usdgeom::basiscurves::points")) {
1555
+ elem = createCurveFromJson(path);
1556
+ } else if (HasAttr(node, "pcd::base64")) {
1557
+ elem = createPointsFromJsonPcdBase64(path);
1558
+ } else if (HasAttr(node, "points::array::positions")) {
1559
+ elem = createPointsFromJsonArray(path);
1560
+ } else if (HasAttr(node, "points::base64::positions")) {
1561
+ elem = createPointsFromJsonPositionBase64(path);
1562
+ }
1563
+ objectMap[node.name] = elem;
1564
+ primMap[node.name] = node;
1565
+ elem.userData.path = node.name;
1566
+ for (let path2 of Object.entries(node.attributes || {})
1567
+ .filter(([k, _]) => k.startsWith("__internal_"))
1568
+ .map(([_, v]) => v)) {
1569
+ (pathMapping[String(path2)] = pathMapping[String(path2)] || []).push(node.name);
1570
+ }
1571
+ parent.add(elem);
1572
+ if (path.length > 1) {
1573
+ elem.matrixAutoUpdate = false;
1574
+ let matrixNode =
1575
+ node.attributes && node.attributes["usd::xformop::transform"]
1576
+ ? node.attributes["usd::xformop::transform"].flat()
1577
+ : null;
1578
+ if (matrixNode) {
1579
+ let matrix = new THREE.Matrix4();
1580
+ matrix.set(...matrixNode);
1581
+ matrix.transpose();
1582
+ elem.matrix = matrix;
1583
+ }
1584
+ }
1585
+ (node.children || []).forEach((child) => traverseTree([child, ...path], elem || parent, pathMapping));
1586
+ }
1587
+ // function encodeHtmlEntities(str) {
1588
+ // const div = document.createElement("div");
1589
+ // div.textContent = str;
1590
+ // return div.innerHTML;
1591
+ // }
1592
+ // var icons = {
1593
+ // "usd::usdgeom::mesh::points": "deployed_code",
1594
+ // "usd::usdgeom::basiscurves::points": "line_curve",
1595
+ // "usd::usdshade::material::outputs::surface.connect": "line_style",
1596
+ // "pcd::base64": "grain",
1597
+ // "points::array::positions": "grain",
1598
+ // "points::base64::positions": "grain",
1599
+ // };
1600
+ // function handleClick(prim, pathMapping, root) {
1601
+ // const container = document.querySelector(".attributes .table");
1602
+ // if (container !== null) {
1603
+ // container.innerHTML = "";
1604
+ // const table = document.createElement("table");
1605
+ // table.setAttribute("border", "0");
1606
+ // const entries = [
1607
+ // ["name", prim.name],
1608
+ // ...Object.entries(prim.attributes).filter(([k, _]) => !k.startsWith("__internal_")),
1609
+ // ];
1610
+ // const format = (value) => {
1611
+ // if (Array.isArray(value)) {
1612
+ // let N = document.createElement("span");
1613
+ // N.appendChild(document.createTextNode("("));
1614
+ // let first = true;
1615
+ // for (let n of value.map(format)) {
1616
+ // if (!first) {
1617
+ // N.appendChild(document.createTextNode(","));
1618
+ // }
1619
+ // N.appendChild(n);
1620
+ // first = false;
1621
+ // }
1622
+ // N.appendChild(document.createTextNode(")"));
1623
+ // return N;
1624
+ // } else if (typeof value === "object") {
1625
+ // const ks = Object.keys(value);
1626
+ // if (ks.length == 1 && ks[0] === "ref" && pathMapping[value.ref] && pathMapping[value.ref].length == 1) {
1627
+ // let a = document.createElement("a");
1628
+ // let resolvedRefAsPath = pathMapping[value.ref][0];
1629
+ // a.setAttribute("href", "#");
1630
+ // a.textContent = resolvedRefAsPath;
1631
+ // a.onclick = () => {
1632
+ // let prim2 = null;
1633
+ // const recurse = (n) => {
1634
+ // if (n.name === resolvedRefAsPath) {
1635
+ // prim2 = n;
1636
+ // } else {
1637
+ // (n.children || []).forEach(recurse);
1638
+ // }
1639
+ // };
1640
+ // recurse(root);
1641
+ // if (prim2) {
1642
+ // handleClick(prim2, pathMapping, root);
1643
+ // }
1644
+ // };
1645
+ // return a;
1646
+ // } else {
1647
+ // return document.createTextNode(JSON.stringify(value));
1648
+ // }
1649
+ // } else {
1650
+ // return document.createTextNode(value);
1651
+ // }
1652
+ // };
1653
+ // entries.forEach(([key, value]) => {
1654
+ // const tr = document.createElement("tr");
1655
+ // const tdKey = document.createElement("td");
1656
+ // tdKey.textContent = encodeHtmlEntities(key);
1657
+ // const tdValue = document.createElement("td");
1658
+ // tdValue.appendChild(format(value));
1659
+ // tr.appendChild(tdKey);
1660
+ // tr.appendChild(tdValue);
1661
+ // table.appendChild(tr);
1662
+ // });
1663
+ // container.appendChild(table);
1664
+ // }
1665
+ // }
1666
+ // function buildDomTree(prim, node, pathMapping, root = null) {
1667
+ // const elem = document.createElement("div");
1668
+ // let span;
1669
+ // elem.appendChild(document.createTextNode(prim.name ? prim.name.split("/").reverse()[0] : "root"));
1670
+ // elem.appendChild((span = document.createElement("span")));
1671
+ // Object.entries(icons).forEach(([k, v]) => (span.innerText += (prim.attributes || {})[k] ? v : " "));
1672
+ // span.className = "material-symbols-outlined";
1673
+ // domMap[prim.name] = elem;
1674
+ // elem.dataset.path = prim.name;
1675
+ // elem.onclick = (evt) => {
1676
+ // handleClick(prim, pathMapping, root || prim);
1677
+ // selectPath(prim.name);
1678
+ // evt.stopPropagation();
1679
+ // };
1680
+ // node.appendChild(elem);
1681
+ // (prim.children || []).forEach((p) => buildDomTree(p, elem, pathMapping, root || prim));
1682
+ // }
1683
+ async function composeAndRender() {
1684
+ if (scene) {
1685
+ scene.children = [];
1686
+ }
1687
+ objectMap = {};
1688
+ // domMap = {};
1689
+ primMap = {};
1690
+ // currentPathMapping = null;
1691
+ // rootPrim = null;
1692
+ // document.querySelector(".tree").innerHTML = "";
1693
+ if (datas.length === 0) {
1694
+ return;
1695
+ }
1696
+ let tree = null;
1697
+ let dataArray = datas.map((arr) => arr[1]);
1698
+ tree = await compose3(dataArray);
1699
+ if (!tree) {
1700
+ console.error("No result from composition");
1701
+ return;
1702
+ }
1703
+ if (!scene) {
1704
+ init();
1705
+ }
1706
+ let pathMapping = {};
1707
+ traverseTree([tree], scene, pathMapping);
1708
+ // currentPathMapping = pathMapping;
1709
+ // rootPrim = tree;
1710
+ if (autoCamera) {
1711
+ const boundingBox = new THREE.Box3();
1712
+ boundingBox.setFromObject(scene);
1713
+ if (!boundingBox.isEmpty()) {
1714
+ let avg = boundingBox.min.clone().add(boundingBox.max).multiplyScalar(0.5);
1715
+ let ext = boundingBox.max.clone().sub(boundingBox.min).length();
1716
+ camera.position.copy(avg.clone().add(new THREE.Vector3(1, 1, 1).normalize().multiplyScalar(ext)));
1717
+ camera.far = ext * 3;
1718
+ camera.updateProjectionMatrix();
1719
+ // controls.target.copy(avg);
1720
+ // controls.update();
1721
+ autoCamera = false;
1722
+ }
1723
+ }
1724
+ // buildDomTree(tree, document.querySelector(".tree"), pathMapping);
1725
+ // animate();
1726
+ }
1727
+ // function createLayerDom() {
1728
+ // document.querySelector(".layers div").innerHTML = "";
1729
+ // datas.forEach(([name, _], index) => {
1730
+ // const elem = document.createElement("div");
1731
+ // elem.appendChild(document.createTextNode(name));
1732
+ // ["\u25B3", "\u25BD", "\xD7"].reverse().forEach((lbl, cmd) => {
1733
+ // const btn = document.createElement("span");
1734
+ // btn.onclick = (evt) => {
1735
+ // evt.stopPropagation();
1736
+ // if (cmd === 2) {
1737
+ // if (index > 0) {
1738
+ // [datas[index], datas[index - 1]] = [datas[index - 1], datas[index]];
1739
+ // }
1740
+ // } else if (cmd === 1) {
1741
+ // if (index < datas.length - 1) {
1742
+ // [datas[index], datas[index + 1]] = [datas[index + 1], datas[index]];
1743
+ // }
1744
+ // } else if (cmd === 0) {
1745
+ // datas.splice(index, 1);
1746
+ // }
1747
+ // composeAndRender();
1748
+ // createLayerDom();
1749
+ // };
1750
+ // btn.appendChild(document.createTextNode(lbl));
1751
+ // elem.appendChild(btn);
1752
+ // });
1753
+ // document.querySelector(".layers div").appendChild(elem);
1754
+ // });
1755
+ // }
1756
+ // async function addModel(name, m) {
1757
+ // datas.push([name, m]);
1758
+ // createLayerDom();
1759
+ // await composeAndRender();
1760
+ // }
1761
+ // function animate() {
1762
+ // requestAnimationFrame(animate);
1763
+ // controls.update();
1764
+ // renderer.render(scene, camera);
1765
+ // }
1766
+ // export { composeAndRender, addModel as default };
1767
+
1768
+ async function parse(m, name) {
1769
+ datas.push([name, m]);
1770
+ await composeAndRender();
1771
+ return scene;
1772
+ }
1773
+ function clear() {
1774
+ scene = undefined;
1775
+ datas.length = 0;
1776
+ autoCamera = true;
1777
+ }
1778
+
1779
+ ///////////////////////////////////////////////////////////////////////////////
1780
+ // Copyright (C) 2002-2024, Open Design Alliance (the "Alliance").
1781
+ // All rights reserved.
1782
+ //
1783
+ // This software and its documentation and related materials are owned by
1784
+ // the Alliance. The software may only be incorporated into application
1785
+ // programs owned by members of the Alliance, subject to a signed
1786
+ // Membership Agreement and Supplemental Software License Agreement with the
1787
+ // Alliance. The structure and organization of this software are the valuable
1788
+ // trade secrets of the Alliance and its suppliers. The software is also
1789
+ // protected by copyright law and international treaty provisions. Application
1790
+ // programs incorporating this software must include the following statement
1791
+ // with their copyright notices:
1792
+ //
1793
+ // This application incorporates Open Design Alliance software pursuant to a
1794
+ // license agreement with Open Design Alliance.
1795
+ // Open Design Alliance Copyright (C) 2002-2024 by Open Design Alliance.
1796
+ // All rights reserved.
1797
+ //
1798
+ // By use of this software, its documentation or related materials, you
1799
+ // acknowledge and accept the above terms.
1800
+ ///////////////////////////////////////////////////////////////////////////////
1801
+ class IFCXLoader extends three.Loader {
1802
+ load(url, onLoad, onProgress, onError) {
1803
+ const manager = this.manager;
1804
+ manager.itemStart(url);
1805
+ const _onLoad = (scene) => {
1806
+ onLoad(scene);
1807
+ manager.itemEnd(url);
1808
+ };
1809
+ const _onError = (e) => {
1810
+ if (onError)
1811
+ onError(e);
1812
+ else
1813
+ console.error(e);
1814
+ manager.itemError(url);
1815
+ manager.itemEnd(url);
1816
+ };
1817
+ const loader = new three.FileLoader(this.manager);
1818
+ loader.setPath(this.path);
1819
+ loader.setResponseType("json");
1820
+ loader.setRequestHeader(this.requestHeader);
1821
+ loader.setWithCredentials(this.withCredentials);
1822
+ loader.load(url, (json) => this.parse(json, _onLoad, _onError), onProgress, onError);
1823
+ }
1824
+ parse(json, onLoad, onError) {
1825
+ parse(json)
1826
+ .then((scene) => onLoad(scene))
1827
+ .catch((err) => onError(err))
1828
+ .finally(() => clear());
1829
+ }
1830
+ }
1831
+
1832
+ ///////////////////////////////////////////////////////////////////////////////
1833
+ // Copyright (C) 2002-2025, Open Design Alliance (the "Alliance").
1834
+ // All rights reserved.
1835
+ //
1836
+ // This software and its documentation and related materials are owned by
1837
+ // the Alliance. The software may only be incorporated into application
1838
+ // programs owned by members of the Alliance, subject to a signed
1839
+ // Membership Agreement and Supplemental Software License Agreement with the
1840
+ // Alliance. The structure and organization of this software are the valuable
1841
+ // trade secrets of the Alliance and its suppliers. The software is also
1842
+ // protected by copyright law and international treaty provisions. Application
1843
+ // programs incorporating this software must include the following statement
1844
+ // with their copyright notices:
1845
+ //
1846
+ // This application incorporates Open Design Alliance software pursuant to a
1847
+ // license agreement with Open Design Alliance.
1848
+ // Open Design Alliance Copyright (C) 2002-2025 by Open Design Alliance.
1849
+ // All rights reserved.
1850
+ //
1851
+ // By use of this software, its documentation or related materials, you
1852
+ // acknowledge and accept the above terms.
1853
+ ///////////////////////////////////////////////////////////////////////////////
1854
+ class IFCXFileLoader extends viewerThree.Loader {
1855
+ constructor(viewer) {
1856
+ super();
1857
+ this.viewer = viewer;
1858
+ }
1859
+ isSupport(file, format) {
1860
+ return ((typeof file === "string" || file instanceof globalThis.File || file instanceof ArrayBuffer) &&
1861
+ /(ifcx)$/i.test(format));
1862
+ }
1863
+ async load(file, format, params) {
1864
+ const manager = new viewerThree.GLTFLoadingManager(file, params);
1865
+ const loader = new IFCXLoader(manager);
1866
+ loader.setPath(manager.path);
1867
+ loader.setCrossOrigin(params.crossOrigin || loader.crossOrigin);
1868
+ loader.setWithCredentials(params.withCredentials || loader.withCredentials);
1869
+ const progress = (event) => {
1870
+ const { lengthComputable, loaded, total } = event;
1871
+ const progress = lengthComputable ? loaded / total : 1;
1872
+ this.viewer.emitEvent({ type: "geometryprogress", data: progress, file });
1873
+ };
1874
+ const scene = await loader.loadAsync(manager.fileURL, progress);
1875
+ if (!this.viewer.scene)
1876
+ return this;
1877
+ let handle = 0;
1878
+ scene.traverse((object) => {
1879
+ object.userData = { handle, ...object.userData };
1880
+ handle++;
1881
+ });
1882
+ const modelImpl = new viewerThree.ModelImpl(scene);
1883
+ modelImpl.loader = this;
1884
+ modelImpl.viewer = this.viewer;
1885
+ this.viewer.scene.add(scene);
1886
+ this.viewer.models.push(modelImpl);
1887
+ this.viewer.syncOptions();
1888
+ this.viewer.syncOverlay();
1889
+ this.viewer.update();
1890
+ this.viewer.emitEvent({ type: "databasechunk", data: scene, file });
1891
+ return this;
1892
+ }
1893
+ }
1894
+
1895
+ ///////////////////////////////////////////////////////////////////////////////
1896
+ // Copyright (C) 2002-2025, Open Design Alliance (the "Alliance").
1897
+ // All rights reserved.
1898
+ //
1899
+ // This software and its documentation and related materials are owned by
1900
+ // the Alliance. The software may only be incorporated into application
1901
+ // programs owned by members of the Alliance, subject to a signed
1902
+ // Membership Agreement and Supplemental Software License Agreement with the
1903
+ // Alliance. The structure and organization of this software are the valuable
1904
+ // trade secrets of the Alliance and its suppliers. The software is also
1905
+ // protected by copyright law and international treaty provisions. Application
1906
+ // programs incorporating this software must include the following statement
1907
+ // with their copyright notices:
1908
+ //
1909
+ // This application incorporates Open Design Alliance software pursuant to a
1910
+ // license agreement with Open Design Alliance.
1911
+ // Open Design Alliance Copyright (C) 2002-2025 by Open Design Alliance.
1912
+ // All rights reserved.
1913
+ //
1914
+ // By use of this software, its documentation or related materials, you
1915
+ // acknowledge and accept the above terms.
1916
+ ///////////////////////////////////////////////////////////////////////////////
1917
+ class IFCXCloudLoader extends viewerThree.Loader {
1918
+ constructor(viewer) {
1919
+ super();
1920
+ this.viewer = viewer;
1921
+ }
1922
+ isSupport(file) {
1923
+ return (typeof file === "object" &&
1924
+ typeof file.type === "string" &&
1925
+ typeof file.download === "function" &&
1926
+ /.ifcx$/i.test(file.type));
1927
+ }
1928
+ async load(file) {
1929
+ const progress = (progress) => {
1930
+ this.viewer.emitEvent({ type: "geometryprogress", data: progress, file });
1931
+ };
1932
+ const arrayBuffer = await file.download(progress, this.abortController.signal);
1933
+ if (!this.viewer.scene)
1934
+ return this;
1935
+ const textDecoder = new TextDecoder();
1936
+ const json = JSON.parse(textDecoder.decode(arrayBuffer));
1937
+ const scene = await parse(json);
1938
+ clear();
1939
+ let handle = 0;
1940
+ scene.traverse((object) => {
1941
+ object.userData = { handle, ...object.userData };
1942
+ handle++;
1943
+ });
1944
+ const modelImpl = new viewerThree.ModelImpl(scene);
1945
+ modelImpl.loader = this;
1946
+ modelImpl.viewer = this.viewer;
1947
+ this.viewer.scene.add(scene);
1948
+ this.viewer.models.push(modelImpl);
1949
+ this.viewer.syncOptions();
1950
+ this.viewer.syncOverlay();
1951
+ this.viewer.update();
1952
+ this.viewer.emitEvent({ type: "databasechunk", data: scene, file });
1953
+ return this;
1954
+ }
1955
+ }
1956
+
1957
+ ///////////////////////////////////////////////////////////////////////////////
1958
+ // Copyright (C) 2002-2025, Open Design Alliance (the "Alliance").
1959
+ // All rights reserved.
1960
+ //
1961
+ // This software and its documentation and related materials are owned by
1962
+ // the Alliance. The software may only be incorporated into application
1963
+ // programs owned by members of the Alliance, subject to a signed
1964
+ // Membership Agreement and Supplemental Software License Agreement with the
1965
+ // Alliance. The structure and organization of this software are the valuable
1966
+ // trade secrets of the Alliance and its suppliers. The software is also
1967
+ // protected by copyright law and international treaty provisions. Application
1968
+ // programs incorporating this software must include the following statement
1969
+ // with their copyright notices:
1970
+ //
1971
+ // This application incorporates Open Design Alliance software pursuant to a
1972
+ // license agreement with Open Design Alliance.
1973
+ // Open Design Alliance Copyright (C) 2002-2025 by Open Design Alliance.
1974
+ // All rights reserved.
1975
+ //
1976
+ // By use of this software, its documentation or related materials, you
1977
+ // acknowledge and accept the above terms.
1978
+ ///////////////////////////////////////////////////////////////////////////////
1979
+ viewerThree.loaders.registerLoader("ifcx-file", (viewer) => new IFCXFileLoader(viewer));
1980
+ viewerThree.loaders.registerLoader("ifcx-cloud", (viewer) => new IFCXCloudLoader(viewer));
885
1981
 
886
1982
  }));
887
1983
  //# sourceMappingURL=IFCXLoader.js.map