@inweb/viewer-three 26.7.6 → 26.8.1

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