@inweb/viewer-three 26.5.0 → 26.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/plugins/components/AxesHelperComponent.js +65 -0
- package/dist/plugins/components/AxesHelperComponent.js.map +1 -0
- package/dist/plugins/components/AxesHelperComponent.min.js +1 -0
- package/dist/plugins/components/AxesHelperComponent.module.js +39 -0
- package/dist/plugins/components/AxesHelperComponent.module.js.map +1 -0
- package/dist/plugins/components/ExtentsHelperComponent.js +55 -0
- package/dist/plugins/components/ExtentsHelperComponent.js.map +1 -0
- package/dist/plugins/components/ExtentsHelperComponent.min.js +1 -0
- package/dist/plugins/components/ExtentsHelperComponent.module.js +29 -0
- package/dist/plugins/components/ExtentsHelperComponent.module.js.map +1 -0
- package/dist/plugins/components/LightHelperComponent.js +65 -0
- package/dist/plugins/components/LightHelperComponent.js.map +1 -0
- package/dist/plugins/components/LightHelperComponent.min.js +1 -0
- package/dist/plugins/components/LightHelperComponent.module.js +40 -0
- package/dist/plugins/components/LightHelperComponent.module.js.map +1 -0
- package/dist/plugins/loaders/IFCXLoader.js +887 -0
- package/dist/plugins/loaders/IFCXLoader.js.map +1 -0
- package/dist/plugins/loaders/IFCXLoader.min.js +1 -0
- package/dist/plugins/loaders/IFCXLoader.module.js +726 -0
- package/dist/plugins/loaders/IFCXLoader.module.js.map +1 -0
- package/dist/viewer-three.js +50355 -33683
- package/dist/viewer-three.js.map +1 -1
- package/dist/viewer-three.min.js +2 -7
- package/dist/viewer-three.module.js +192 -86
- package/dist/viewer-three.module.js.map +1 -1
- package/lib/Viewer/Viewer.d.ts +51 -68
- package/lib/Viewer/loaders/GLTFFileLoader.d.ts +9 -0
- package/lib/Viewer/loaders/GLTFLoadingManager.d.ts +9 -3
- package/lib/Viewer/loaders/GLTFModelLoader.d.ts +8 -0
- package/lib/Viewer/loaders/index.d.ts +67 -0
- package/lib/index-umd.d.ts +1 -0
- package/lib/index.d.ts +6 -4
- package/package.json +10 -7
- package/{src/Viewer → plugins}/components/AxesHelperComponent.ts +4 -4
- package/{src/Viewer → plugins}/components/ExtentsHelperComponent.ts +4 -4
- package/{src/Viewer → plugins}/components/LightHelperComponent.ts +4 -4
- package/plugins/loaders/IFCX/IFCXLoader.ts +71 -0
- package/plugins/loaders/IFCX/render.js +701 -0
- package/plugins/loaders/IFCXFileLoader.ts +76 -0
- package/plugins/loaders/IFCXLoader.ts +30 -0
- package/plugins/loaders/IFCXModelLoader.ts +75 -0
- package/src/Viewer/Viewer.ts +101 -148
- package/src/Viewer/commands/Explode.ts +2 -2
- package/src/Viewer/components/index.ts +2 -8
- package/src/Viewer/loaders/GLTFFileLoader.ts +73 -0
- package/src/Viewer/loaders/GLTFLoadingManager.ts +16 -8
- package/src/Viewer/loaders/GLTFModelLoader.ts +74 -0
- package/src/Viewer/loaders/index.ts +99 -0
- package/src/index-umd.ts +30 -0
- package/src/index.ts +9 -5
- package/lib/Viewer/components/AxesHelperComponent.d.ts +0 -10
- package/lib/Viewer/components/ExtentsHelperComponent.d.ts +0 -9
- package/lib/Viewer/components/LightHelperComponent.d.ts +0 -9
|
@@ -0,0 +1,726 @@
|
|
|
1
|
+
import { Loader, GLTFLoadingManager, loaders } from "@inweb/viewer-three";
|
|
2
|
+
|
|
3
|
+
import { Box3, Vector3, Group, Matrix4, Scene, PerspectiveCamera, BufferGeometry, BufferAttribute, MeshBasicMaterial, Mesh, LineBasicMaterial, Line, Color, Loader as Loader$1, FileLoader } from "three";
|
|
4
|
+
|
|
5
|
+
const THREE = {
|
|
6
|
+
Box3: Box3,
|
|
7
|
+
BufferAttribute: BufferAttribute,
|
|
8
|
+
BufferGeometry: BufferGeometry,
|
|
9
|
+
Color: Color,
|
|
10
|
+
Group: Group,
|
|
11
|
+
Line: Line,
|
|
12
|
+
LineBasicMaterial: LineBasicMaterial,
|
|
13
|
+
Matrix4: Matrix4,
|
|
14
|
+
Mesh: Mesh,
|
|
15
|
+
MeshBasicMaterial: MeshBasicMaterial,
|
|
16
|
+
PerspectiveCamera: PerspectiveCamera,
|
|
17
|
+
Scene: Scene,
|
|
18
|
+
Vector3: Vector3
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
function getChildByName(root, childName, skip = 0) {
|
|
22
|
+
let fragments = childName.replace(/^<\/|^\/|>$/g, "").split("/");
|
|
23
|
+
for (let i = 0; i < skip; ++i) {
|
|
24
|
+
fragments.shift();
|
|
25
|
+
}
|
|
26
|
+
let start = root;
|
|
27
|
+
while (fragments.length && start && start.children) {
|
|
28
|
+
let f = fragments.shift();
|
|
29
|
+
start = start.children.find((i => i.name.split("/").reverse()[0] === f));
|
|
30
|
+
}
|
|
31
|
+
if (fragments.length == 0) {
|
|
32
|
+
return start;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function GetNode(node, path) {
|
|
37
|
+
if (path === "") return node;
|
|
38
|
+
let parts = path.split("/");
|
|
39
|
+
let child = node.children.get(parts[0]);
|
|
40
|
+
if (child) {
|
|
41
|
+
if (parts.length === 1) {
|
|
42
|
+
return child;
|
|
43
|
+
}
|
|
44
|
+
return GetNode(child, GetTail(path));
|
|
45
|
+
} else {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function GetHead(path) {
|
|
51
|
+
return path.split("/")[0];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function GetTail(path) {
|
|
55
|
+
let parts = path.split("/");
|
|
56
|
+
parts.shift();
|
|
57
|
+
return parts.join("/");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function MakeNode(node) {
|
|
61
|
+
return {
|
|
62
|
+
node: node,
|
|
63
|
+
children: new Map,
|
|
64
|
+
attributes: new Map
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function ConvertToCompositionNode(path, inputNodes) {
|
|
69
|
+
let compositionNode = {
|
|
70
|
+
path: path,
|
|
71
|
+
children: {},
|
|
72
|
+
inherits: {},
|
|
73
|
+
attributes: {}
|
|
74
|
+
};
|
|
75
|
+
inputNodes.forEach((node => {
|
|
76
|
+
Object.keys(node.children).forEach((childName => {
|
|
77
|
+
compositionNode.children[childName] = node.children[childName];
|
|
78
|
+
}));
|
|
79
|
+
Object.keys(node.inherits).forEach((inheritName => {
|
|
80
|
+
let ih = node.inherits[inheritName];
|
|
81
|
+
if (ih === null) {
|
|
82
|
+
delete compositionNode.inherits[inheritName];
|
|
83
|
+
} else {
|
|
84
|
+
compositionNode.inherits[inheritName] = ih;
|
|
85
|
+
}
|
|
86
|
+
}));
|
|
87
|
+
Object.keys(node.attributes).forEach((attrName => {
|
|
88
|
+
compositionNode.attributes[attrName] = node.attributes[attrName];
|
|
89
|
+
}));
|
|
90
|
+
}));
|
|
91
|
+
return compositionNode;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function MMSet(map, key, value) {
|
|
95
|
+
if (map.has(key)) {
|
|
96
|
+
map.get(key)?.push(value);
|
|
97
|
+
} else {
|
|
98
|
+
map.set(key, [ value ]);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function FindRootsOrCycles(nodes) {
|
|
103
|
+
let dependencies = new Map;
|
|
104
|
+
let dependents = new Map;
|
|
105
|
+
nodes.forEach(((node, path) => {
|
|
106
|
+
Object.keys(node.inherits).forEach((inheritName => {
|
|
107
|
+
MMSet(dependencies, path, node.inherits[inheritName]);
|
|
108
|
+
MMSet(dependents, node.inherits[inheritName], path);
|
|
109
|
+
}));
|
|
110
|
+
Object.keys(node.children).forEach((childName => {
|
|
111
|
+
MMSet(dependencies, path, node.children[childName]);
|
|
112
|
+
MMSet(dependents, node.children[childName], path);
|
|
113
|
+
}));
|
|
114
|
+
}));
|
|
115
|
+
let paths = [ ...nodes.keys() ];
|
|
116
|
+
let perm = {};
|
|
117
|
+
let temp = {};
|
|
118
|
+
function visit(path) {
|
|
119
|
+
if (perm[path]) return;
|
|
120
|
+
if (temp[path]) throw new Error(`CYCLE!`);
|
|
121
|
+
temp[path] = true;
|
|
122
|
+
let deps = dependencies.get(path);
|
|
123
|
+
if (deps) {
|
|
124
|
+
deps.forEach((dep => visit(dep)));
|
|
125
|
+
}
|
|
126
|
+
perm[path] = true;
|
|
127
|
+
}
|
|
128
|
+
let roots = new Set;
|
|
129
|
+
try {
|
|
130
|
+
paths.forEach((path => {
|
|
131
|
+
if (!dependents.has(path) && path.indexOf("/") === -1) {
|
|
132
|
+
roots.add(path);
|
|
133
|
+
}
|
|
134
|
+
visit(path);
|
|
135
|
+
}));
|
|
136
|
+
} catch {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
return roots;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function ConvertNodes(input) {
|
|
143
|
+
let compositionNodes = new Map;
|
|
144
|
+
for (let [path, inputNodes] of input) {
|
|
145
|
+
compositionNodes.set(path, ConvertToCompositionNode(path, inputNodes));
|
|
146
|
+
}
|
|
147
|
+
return compositionNodes;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
var CycleError = class extends Error {};
|
|
151
|
+
|
|
152
|
+
function ExpandFirstRootInInput(nodes) {
|
|
153
|
+
let roots = FindRootsOrCycles(nodes);
|
|
154
|
+
if (!roots) {
|
|
155
|
+
throw new CycleError;
|
|
156
|
+
}
|
|
157
|
+
return ExpandNewNode([ ...roots.values() ][0], nodes);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function CreateArtificialRoot(nodes) {
|
|
161
|
+
let roots = FindRootsOrCycles(nodes);
|
|
162
|
+
if (!roots) {
|
|
163
|
+
throw new CycleError;
|
|
164
|
+
}
|
|
165
|
+
let pseudoRoot = {
|
|
166
|
+
node: "",
|
|
167
|
+
attributes: new Map,
|
|
168
|
+
children: new Map
|
|
169
|
+
};
|
|
170
|
+
roots.forEach((root => {
|
|
171
|
+
pseudoRoot.children.set(root, ExpandNewNode(root, nodes));
|
|
172
|
+
}));
|
|
173
|
+
return pseudoRoot;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function ExpandNewNode(node, nodes) {
|
|
177
|
+
return ExpandNode(node, MakeNode(node), nodes);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function ExpandNode(path, node, nodes) {
|
|
181
|
+
let input = nodes.get(path);
|
|
182
|
+
if (input) {
|
|
183
|
+
AddDataFromInput(input, node, nodes);
|
|
184
|
+
}
|
|
185
|
+
node.children.forEach(((child, name) => {
|
|
186
|
+
ExpandNode(`${path}/${name}`, child, nodes);
|
|
187
|
+
}));
|
|
188
|
+
return node;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function AddDataFromInput(input, node, nodes) {
|
|
192
|
+
Object.values(input.inherits).forEach((inherit => {
|
|
193
|
+
let classNode = ExpandNewNode(GetHead(inherit), nodes);
|
|
194
|
+
let subnode = GetNode(classNode, GetTail(inherit));
|
|
195
|
+
if (!subnode) throw new Error(`Unknown node ${inherit}`);
|
|
196
|
+
subnode.children.forEach(((child, childName) => {
|
|
197
|
+
node.children.set(childName, child);
|
|
198
|
+
}));
|
|
199
|
+
for (let [attrID, attr] of subnode.attributes) {
|
|
200
|
+
node.attributes.set(attrID, attr);
|
|
201
|
+
}
|
|
202
|
+
}));
|
|
203
|
+
Object.entries(input.children).forEach((([childName, child]) => {
|
|
204
|
+
if (child !== null) {
|
|
205
|
+
let classNode = ExpandNewNode(GetHead(child), nodes);
|
|
206
|
+
let subnode = GetNode(classNode, GetTail(child));
|
|
207
|
+
if (!subnode) throw new Error(`Unknown node ${child}`);
|
|
208
|
+
node.children.set(childName, subnode);
|
|
209
|
+
} else {
|
|
210
|
+
node.children.delete(childName);
|
|
211
|
+
}
|
|
212
|
+
}));
|
|
213
|
+
Object.entries(input.attributes).forEach((([attrID, attr]) => {
|
|
214
|
+
node.attributes.set(attrID, attr);
|
|
215
|
+
}));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function MMSet2(map, key, value) {
|
|
219
|
+
if (map.has(key)) {
|
|
220
|
+
map.get(key)?.push(value);
|
|
221
|
+
} else {
|
|
222
|
+
map.set(key, [ value ]);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function ToInputNodes(data) {
|
|
227
|
+
let inputNodes = new Map;
|
|
228
|
+
data.forEach((ifcxNode => {
|
|
229
|
+
let node = {
|
|
230
|
+
path: ifcxNode.identifier,
|
|
231
|
+
children: ifcxNode.children ? ifcxNode.children : {},
|
|
232
|
+
inherits: ifcxNode.inherits ? ifcxNode.inherits : {},
|
|
233
|
+
attributes: ifcxNode.attributes ? ifcxNode.attributes : {}
|
|
234
|
+
};
|
|
235
|
+
MMSet2(inputNodes, node.path, node);
|
|
236
|
+
}));
|
|
237
|
+
return inputNodes;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
var SchemaValidationError = class extends Error {};
|
|
241
|
+
|
|
242
|
+
function ValidateAttributeValue(desc, value, path, schemas) {
|
|
243
|
+
if (desc.inherits) {
|
|
244
|
+
desc.inherits.forEach((inheritedSchemaID => {
|
|
245
|
+
let inheritedSchema = schemas[inheritedSchemaID];
|
|
246
|
+
if (!inheritedSchema) {
|
|
247
|
+
throw new SchemaValidationError(`Unknown inherited schema id "${desc.inherits}"`);
|
|
248
|
+
}
|
|
249
|
+
ValidateAttributeValue(inheritedSchema.value, value, path, schemas);
|
|
250
|
+
}));
|
|
251
|
+
}
|
|
252
|
+
if (desc.dataType === "Boolean") {
|
|
253
|
+
if (typeof value !== "boolean") {
|
|
254
|
+
throw new SchemaValidationError(`Expected "${value}" to be of type boolean`);
|
|
255
|
+
}
|
|
256
|
+
} else if (desc.dataType === "String") {
|
|
257
|
+
if (typeof value !== "string") {
|
|
258
|
+
throw new SchemaValidationError(`Expected "${value}" to be of type string`);
|
|
259
|
+
}
|
|
260
|
+
} else if (desc.dataType === "DateTime") {
|
|
261
|
+
if (typeof value !== "string") {
|
|
262
|
+
throw new SchemaValidationError(`Expected "${value}" to be of type date`);
|
|
263
|
+
}
|
|
264
|
+
} else if (desc.dataType === "Enum") {
|
|
265
|
+
if (typeof value !== "string") {
|
|
266
|
+
throw new SchemaValidationError(`Expected "${value}" to be of type string`);
|
|
267
|
+
}
|
|
268
|
+
let found = desc.enumRestrictions.options.filter((option => option === value)).length === 1;
|
|
269
|
+
if (!found) {
|
|
270
|
+
throw new SchemaValidationError(`Expected "${value}" to be one of [${desc.enumRestrictions.options.join(",")}]`);
|
|
271
|
+
}
|
|
272
|
+
} else if (desc.dataType === "Integer") {
|
|
273
|
+
if (typeof value !== "number") {
|
|
274
|
+
throw new SchemaValidationError(`Expected "${value}" to be of type int`);
|
|
275
|
+
}
|
|
276
|
+
} else if (desc.dataType === "Real") {
|
|
277
|
+
if (typeof value !== "number") {
|
|
278
|
+
throw new SchemaValidationError(`Expected "${value}" to be of type real`);
|
|
279
|
+
}
|
|
280
|
+
} else if (desc.dataType === "Relation") {
|
|
281
|
+
if (typeof value !== "string") {
|
|
282
|
+
throw new SchemaValidationError(`Expected "${value}" to be of type string`);
|
|
283
|
+
}
|
|
284
|
+
} else if (desc.dataType === "Object") {
|
|
285
|
+
if (typeof value !== "object") {
|
|
286
|
+
throw new SchemaValidationError(`Expected "${value}" to be of type object`);
|
|
287
|
+
}
|
|
288
|
+
if (desc.objectRestrictions) {
|
|
289
|
+
Object.keys(desc.objectRestrictions.values).forEach((key => {
|
|
290
|
+
if (!Object.hasOwn(value, key)) {
|
|
291
|
+
throw new SchemaValidationError(`Expected "${value}" to have key ${key}`);
|
|
292
|
+
}
|
|
293
|
+
ValidateAttributeValue(desc.objectRestrictions.values[key], value[key], path + "." + key, schemas);
|
|
294
|
+
}));
|
|
295
|
+
}
|
|
296
|
+
} else if (desc.dataType === "Array") {
|
|
297
|
+
if (!Array.isArray(value)) {
|
|
298
|
+
throw new SchemaValidationError(`Expected "${value}" to be of type array`);
|
|
299
|
+
}
|
|
300
|
+
value.forEach((entry => {
|
|
301
|
+
ValidateAttributeValue(desc.arrayRestrictions.value, entry, path + ".<array>.", schemas);
|
|
302
|
+
}));
|
|
303
|
+
} else {
|
|
304
|
+
throw new SchemaValidationError(`Unexpected datatype ${desc.dataType}`);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function Validate(schemas, inputNodes) {
|
|
309
|
+
inputNodes.forEach((node => {
|
|
310
|
+
Object.keys(node.attributes).forEach((schemaID => {
|
|
311
|
+
if (!schemas[schemaID]) {
|
|
312
|
+
throw new SchemaValidationError(`Missing schema "${schemaID}" referenced by ["${node.path}"].attributes`);
|
|
313
|
+
}
|
|
314
|
+
let schema = schemas[schemaID];
|
|
315
|
+
let value = node.attributes[schemaID];
|
|
316
|
+
try {
|
|
317
|
+
ValidateAttributeValue(schema.value, value, "", schemas);
|
|
318
|
+
} catch (e) {
|
|
319
|
+
if (e instanceof SchemaValidationError) {
|
|
320
|
+
throw new SchemaValidationError(`Error validating ["${node.path}"].attributes["${schemaID}"]: ${e.message}`);
|
|
321
|
+
} else {
|
|
322
|
+
throw e;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}));
|
|
326
|
+
}));
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function LoadIfcxFile(file, checkSchemas = true, createArtificialRoot = false) {
|
|
330
|
+
let inputNodes = ToInputNodes(file.data);
|
|
331
|
+
let compositionNodes = ConvertNodes(inputNodes);
|
|
332
|
+
try {
|
|
333
|
+
if (checkSchemas) {
|
|
334
|
+
Validate(file.schemas, compositionNodes);
|
|
335
|
+
}
|
|
336
|
+
} catch (e) {
|
|
337
|
+
throw e;
|
|
338
|
+
}
|
|
339
|
+
if (createArtificialRoot) {
|
|
340
|
+
return CreateArtificialRoot(compositionNodes);
|
|
341
|
+
} else {
|
|
342
|
+
return ExpandFirstRootInInput(compositionNodes);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function Federate(files) {
|
|
347
|
+
let result = {
|
|
348
|
+
header: files[0].header,
|
|
349
|
+
schemas: {},
|
|
350
|
+
data: []
|
|
351
|
+
};
|
|
352
|
+
files.forEach((file => {
|
|
353
|
+
Object.keys(file.schemas).forEach((schemaID => result.schemas[schemaID] = file.schemas[schemaID]));
|
|
354
|
+
}));
|
|
355
|
+
files.forEach((file => {
|
|
356
|
+
file.data.forEach((node => result.data.push(node)));
|
|
357
|
+
}));
|
|
358
|
+
return Prune(result);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function Collapse(nodes, deleteEmpty = false) {
|
|
362
|
+
let result = {
|
|
363
|
+
path: nodes[0].path,
|
|
364
|
+
children: {},
|
|
365
|
+
inherits: {},
|
|
366
|
+
attributes: {}
|
|
367
|
+
};
|
|
368
|
+
nodes.forEach((node => {
|
|
369
|
+
Object.keys(node.children).forEach((name => {
|
|
370
|
+
result.children[name] = node.children[name];
|
|
371
|
+
}));
|
|
372
|
+
Object.keys(node.inherits).forEach((name => {
|
|
373
|
+
result.inherits[name] = node.inherits[name];
|
|
374
|
+
}));
|
|
375
|
+
Object.keys(node.attributes).forEach((name => {
|
|
376
|
+
result.attributes[name] = node.attributes[name];
|
|
377
|
+
}));
|
|
378
|
+
}));
|
|
379
|
+
if (deleteEmpty) {
|
|
380
|
+
let empty = true;
|
|
381
|
+
Object.keys(result.children).forEach((name => {
|
|
382
|
+
if (result.children[name] !== null) empty = false;
|
|
383
|
+
}));
|
|
384
|
+
Object.keys(result.inherits).forEach((name => {
|
|
385
|
+
if (result.inherits[name] !== null) empty = false;
|
|
386
|
+
}));
|
|
387
|
+
Object.keys(result.attributes).forEach((name => {
|
|
388
|
+
if (result.attributes[name] !== null) empty = false;
|
|
389
|
+
}));
|
|
390
|
+
if (empty) return null;
|
|
391
|
+
}
|
|
392
|
+
return result;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function Prune(file, deleteEmpty = false) {
|
|
396
|
+
let result = {
|
|
397
|
+
header: file.header,
|
|
398
|
+
schemas: file.schemas,
|
|
399
|
+
data: []
|
|
400
|
+
};
|
|
401
|
+
let inputNodes = ToInputNodes(file.data);
|
|
402
|
+
inputNodes.forEach((nodes => {
|
|
403
|
+
let collapsed = Collapse(nodes, deleteEmpty);
|
|
404
|
+
if (collapsed) result.data.push({
|
|
405
|
+
identifier: collapsed.path,
|
|
406
|
+
children: collapsed.children,
|
|
407
|
+
inherits: collapsed.inherits,
|
|
408
|
+
attributes: collapsed.attributes
|
|
409
|
+
});
|
|
410
|
+
}));
|
|
411
|
+
return result;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function TreeNodeToComposedObject(path, node, schemas) {
|
|
415
|
+
let co = {
|
|
416
|
+
name: path,
|
|
417
|
+
attributes: {},
|
|
418
|
+
children: []
|
|
419
|
+
};
|
|
420
|
+
node.children.forEach(((childNode, childName) => {
|
|
421
|
+
co.children?.push(TreeNodeToComposedObject(`${path}/${childName}`, childNode, schemas));
|
|
422
|
+
}));
|
|
423
|
+
node.attributes.forEach(((attr, attrName) => {
|
|
424
|
+
if (attr && typeof attr === "object" && !Array.isArray(attr)) {
|
|
425
|
+
Object.keys(attr).forEach((compname => {
|
|
426
|
+
co.attributes[`${attrName}::${compname}`] = attr[compname];
|
|
427
|
+
}));
|
|
428
|
+
} else {
|
|
429
|
+
let schema = schemas[attrName];
|
|
430
|
+
if (schema && schema.value.quantityKind) {
|
|
431
|
+
let postfix = "";
|
|
432
|
+
let quantityKind = schema.value.quantityKind;
|
|
433
|
+
if (quantityKind === "Length") {
|
|
434
|
+
postfix = "m";
|
|
435
|
+
} else if (quantityKind === "Volume") {
|
|
436
|
+
postfix = "m" + String.fromCodePoint(179);
|
|
437
|
+
}
|
|
438
|
+
co.attributes[attrName] = `${attr} ${postfix}`;
|
|
439
|
+
} else {
|
|
440
|
+
co.attributes[attrName] = attr;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}));
|
|
444
|
+
if (Object.keys(co.attributes).length === 0) delete co.attributes;
|
|
445
|
+
return co;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function compose3(files) {
|
|
449
|
+
let federated = Federate(files);
|
|
450
|
+
let tree = LoadIfcxFile(federated, true, true);
|
|
451
|
+
return TreeNodeToComposedObject("", tree, federated.schemas);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
var scene;
|
|
455
|
+
|
|
456
|
+
var camera;
|
|
457
|
+
|
|
458
|
+
var datas = [];
|
|
459
|
+
|
|
460
|
+
var autoCamera = true;
|
|
461
|
+
|
|
462
|
+
function init() {
|
|
463
|
+
scene = new THREE.Scene;
|
|
464
|
+
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, .1, 100);
|
|
465
|
+
camera.up.set(0, 0, 1);
|
|
466
|
+
camera.position.set(50, 50, 50);
|
|
467
|
+
camera.lookAt(0, 0, 0);
|
|
468
|
+
scene.add(camera);
|
|
469
|
+
return scene;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function HasAttr(node, attrName) {
|
|
473
|
+
if (!node || !node.attributes) return false;
|
|
474
|
+
return !!node.attributes[attrName];
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function FindChildWithAttr(node, attrName) {
|
|
478
|
+
if (!node || !node.children) return undefined;
|
|
479
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
480
|
+
if (HasAttr(node.children[i], attrName)) {
|
|
481
|
+
return node.children[i];
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
return undefined;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function createMaterialFromParent(parent, root) {
|
|
488
|
+
let reference = parent.attributes["usd::usdshade::materialbindingapi::material::binding"];
|
|
489
|
+
let material = {
|
|
490
|
+
color: new THREE.Color(.6, .6, .6),
|
|
491
|
+
transparent: false,
|
|
492
|
+
opacity: 1
|
|
493
|
+
};
|
|
494
|
+
if (reference) {
|
|
495
|
+
const materialNode = getChildByName(root, reference.ref);
|
|
496
|
+
let shader = FindChildWithAttr(materialNode, "usd::materials::inputs::diffuseColor");
|
|
497
|
+
if (shader) {
|
|
498
|
+
let color = shader?.attributes["usd::materials::inputs::diffuseColor"];
|
|
499
|
+
material.color = new THREE.Color(...color);
|
|
500
|
+
if (shader?.attributes["usd::materials::inputs::opacity"]) {
|
|
501
|
+
material.transparent = true;
|
|
502
|
+
material.opacity = shader.attributes["usd::materials::inputs::opacity"];
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
return material;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function createCurveFromJson(node, parent, root) {
|
|
510
|
+
let points = new Float32Array(node.attributes["usd::usdgeom::basiscurves::points"].flat());
|
|
511
|
+
const geometry = new THREE.BufferGeometry;
|
|
512
|
+
geometry.setAttribute("position", new THREE.BufferAttribute(points, 3));
|
|
513
|
+
const material = createMaterialFromParent(parent, root);
|
|
514
|
+
let lineMaterial = new THREE.LineBasicMaterial({
|
|
515
|
+
...material
|
|
516
|
+
});
|
|
517
|
+
lineMaterial.color.multiplyScalar(.8);
|
|
518
|
+
return new THREE.Line(geometry, lineMaterial);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function createMeshFromJson(node, parent, root) {
|
|
522
|
+
let points = new Float32Array(node.attributes["usd::usdgeom::mesh::points"].flat());
|
|
523
|
+
let indices = new Uint16Array(node.attributes["usd::usdgeom::mesh::faceVertexIndices"]);
|
|
524
|
+
const geometry = new THREE.BufferGeometry;
|
|
525
|
+
geometry.setAttribute("position", new THREE.BufferAttribute(points, 3));
|
|
526
|
+
geometry.setIndex(new THREE.BufferAttribute(indices, 1));
|
|
527
|
+
geometry.computeVertexNormals();
|
|
528
|
+
const material = createMaterialFromParent(parent, root);
|
|
529
|
+
let meshMaterial = new THREE.MeshBasicMaterial({
|
|
530
|
+
...material
|
|
531
|
+
});
|
|
532
|
+
return new THREE.Mesh(geometry, meshMaterial);
|
|
533
|
+
}
|
|
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 = node.attributes && node.attributes["usd::xformop::transform"] ? node.attributes["usd::xformop::transform"].flat() : null;
|
|
550
|
+
if (matrixNode) {
|
|
551
|
+
let matrix = new THREE.Matrix4;
|
|
552
|
+
matrix.set(...matrixNode);
|
|
553
|
+
matrix.transpose();
|
|
554
|
+
elem.matrix = matrix;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
(node.children || []).forEach((child => traverseTree(child, elem || parent, root, node)));
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
function composeAndRender() {
|
|
561
|
+
if (scene) {
|
|
562
|
+
scene.children = [];
|
|
563
|
+
}
|
|
564
|
+
if (datas.length === 0) {
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
let tree = null;
|
|
568
|
+
let dataArray = datas.map((arr => arr[1]));
|
|
569
|
+
tree = compose3(dataArray);
|
|
570
|
+
if (!tree) {
|
|
571
|
+
console.error("No result from composition");
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
traverseTree(tree, scene || init(), tree);
|
|
575
|
+
if (autoCamera) {
|
|
576
|
+
const boundingBox = new THREE.Box3;
|
|
577
|
+
boundingBox.setFromObject(scene);
|
|
578
|
+
if (!boundingBox.isEmpty()) {
|
|
579
|
+
let avg = boundingBox.min.clone().add(boundingBox.max).multiplyScalar(.5);
|
|
580
|
+
let ext = boundingBox.max.clone().sub(boundingBox.min).length();
|
|
581
|
+
camera.position.copy(avg.clone().add(new THREE.Vector3(1, 1, 1).normalize().multiplyScalar(ext)));
|
|
582
|
+
camera.far = ext * 3;
|
|
583
|
+
camera.updateProjectionMatrix();
|
|
584
|
+
autoCamera = false;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function parse(m, name) {
|
|
590
|
+
datas.push([ name, m ]);
|
|
591
|
+
composeAndRender();
|
|
592
|
+
return scene;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function clear() {
|
|
596
|
+
scene = undefined;
|
|
597
|
+
datas.length = 0;
|
|
598
|
+
autoCamera = true;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
class IFCXModelLoader extends Loader {
|
|
602
|
+
constructor(viewer) {
|
|
603
|
+
super();
|
|
604
|
+
this.viewer = viewer;
|
|
605
|
+
}
|
|
606
|
+
isSupport(file) {
|
|
607
|
+
return typeof file === "object" && typeof file.type === "string" && typeof file.download === "function" && /.ifcx$/i.test(file.type);
|
|
608
|
+
}
|
|
609
|
+
async load(model) {
|
|
610
|
+
const progress = progress => {
|
|
611
|
+
this.viewer.emitEvent({
|
|
612
|
+
type: "geometryprogress",
|
|
613
|
+
data: progress,
|
|
614
|
+
file: model
|
|
615
|
+
});
|
|
616
|
+
};
|
|
617
|
+
const arrayBuffer = await model.download(progress, this.abortController.signal);
|
|
618
|
+
if (!this.viewer.scene) return this;
|
|
619
|
+
const textDecoder = new TextDecoder;
|
|
620
|
+
const json = JSON.parse(textDecoder.decode(arrayBuffer));
|
|
621
|
+
const scene = parse(json);
|
|
622
|
+
clear();
|
|
623
|
+
let handle = 0;
|
|
624
|
+
scene.traverse((object => {
|
|
625
|
+
object.userData = {
|
|
626
|
+
handle: handle,
|
|
627
|
+
...object.userData
|
|
628
|
+
};
|
|
629
|
+
handle++;
|
|
630
|
+
}));
|
|
631
|
+
this.viewer.scene.add(scene);
|
|
632
|
+
this.viewer.models.push(scene);
|
|
633
|
+
this.viewer.syncOptions();
|
|
634
|
+
this.viewer.syncOverlay();
|
|
635
|
+
this.viewer.update();
|
|
636
|
+
this.viewer.emitEvent({
|
|
637
|
+
type: "databasechunk",
|
|
638
|
+
data: scene,
|
|
639
|
+
file: model
|
|
640
|
+
});
|
|
641
|
+
return this;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
class IFCXLoader extends Loader$1 {
|
|
646
|
+
load(url, onLoad, onProgress, onError) {
|
|
647
|
+
const manager = this.manager;
|
|
648
|
+
manager.itemStart(url);
|
|
649
|
+
const _onLoad = scene => {
|
|
650
|
+
onLoad(scene);
|
|
651
|
+
manager.itemEnd(url);
|
|
652
|
+
};
|
|
653
|
+
const _onError = e => {
|
|
654
|
+
if (onError) onError(e); else console.error(e);
|
|
655
|
+
manager.itemError(url);
|
|
656
|
+
manager.itemEnd(url);
|
|
657
|
+
};
|
|
658
|
+
const loader = new FileLoader(this.manager);
|
|
659
|
+
loader.setPath(this.path);
|
|
660
|
+
loader.setResponseType("json");
|
|
661
|
+
loader.setRequestHeader(this.requestHeader);
|
|
662
|
+
loader.setWithCredentials(this.withCredentials);
|
|
663
|
+
loader.load(url, (json => this.parse(json, _onLoad, _onError)), onProgress, onError);
|
|
664
|
+
}
|
|
665
|
+
parse(json, onLoad, onError) {
|
|
666
|
+
try {
|
|
667
|
+
onLoad(parse(json));
|
|
668
|
+
} catch (e) {
|
|
669
|
+
onError(e);
|
|
670
|
+
} finally {
|
|
671
|
+
clear();
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
class IFCXFileLoader extends Loader {
|
|
677
|
+
constructor(viewer) {
|
|
678
|
+
super();
|
|
679
|
+
this.viewer = viewer;
|
|
680
|
+
}
|
|
681
|
+
isSupport(file, format) {
|
|
682
|
+
return (typeof file === "string" || file instanceof globalThis.File || file instanceof ArrayBuffer) && /(ifcx)$/i.test(format);
|
|
683
|
+
}
|
|
684
|
+
async load(file, format, params) {
|
|
685
|
+
const manager = new GLTFLoadingManager(file, params);
|
|
686
|
+
const loader = new IFCXLoader(manager);
|
|
687
|
+
loader.setPath(manager.path);
|
|
688
|
+
loader.setCrossOrigin(params.crossOrigin || loader.crossOrigin);
|
|
689
|
+
loader.setWithCredentials(params.withCredentials || loader.withCredentials);
|
|
690
|
+
const progress = event => {
|
|
691
|
+
const {lengthComputable: lengthComputable, loaded: loaded, total: total} = event;
|
|
692
|
+
const progress = lengthComputable ? loaded / total : 1;
|
|
693
|
+
this.viewer.emitEvent({
|
|
694
|
+
type: "geometryprogress",
|
|
695
|
+
data: progress,
|
|
696
|
+
file: file
|
|
697
|
+
});
|
|
698
|
+
};
|
|
699
|
+
const scene = await loader.loadAsync(manager.fileURL, progress);
|
|
700
|
+
if (!this.viewer.scene) return this;
|
|
701
|
+
let handle = 0;
|
|
702
|
+
scene.traverse((object => {
|
|
703
|
+
object.userData = {
|
|
704
|
+
handle: handle,
|
|
705
|
+
...object.userData
|
|
706
|
+
};
|
|
707
|
+
handle++;
|
|
708
|
+
}));
|
|
709
|
+
this.viewer.scene.add(scene);
|
|
710
|
+
this.viewer.models.push(scene);
|
|
711
|
+
this.viewer.syncOptions();
|
|
712
|
+
this.viewer.syncOverlay();
|
|
713
|
+
this.viewer.update();
|
|
714
|
+
this.viewer.emitEvent({
|
|
715
|
+
type: "databasechunk",
|
|
716
|
+
data: scene,
|
|
717
|
+
file: file
|
|
718
|
+
});
|
|
719
|
+
return this;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
loaders.registerLoader("ifcx", (viewer => new IFCXModelLoader(viewer)));
|
|
724
|
+
|
|
725
|
+
loaders.registerLoader("ifcx-file", (viewer => new IFCXFileLoader(viewer)));
|
|
726
|
+
//# sourceMappingURL=IFCXLoader.module.js.map
|