@jfk-solutions/demo3d-file-format 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +107 -0
- package/dist/binary.d.ts +10 -0
- package/dist/binary.d.ts.map +1 -0
- package/dist/binary.js +78 -0
- package/dist/binary.js.map +1 -0
- package/dist/errors.d.ts +14 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +27 -0
- package/dist/errors.js.map +1 -0
- package/dist/fast-xml.d.ts +3 -0
- package/dist/fast-xml.d.ts.map +1 -0
- package/dist/fast-xml.js +256 -0
- package/dist/fast-xml.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/model.d.ts +96 -0
- package/dist/model.d.ts.map +1 -0
- package/dist/model.js +336 -0
- package/dist/model.js.map +1 -0
- package/dist/parser.d.ts +9 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +74 -0
- package/dist/parser.js.map +1 -0
- package/dist/three/index.d.ts +51 -0
- package/dist/three/index.d.ts.map +1 -0
- package/dist/three/index.js +1317 -0
- package/dist/three/index.js.map +1 -0
- package/dist/three/renderer.d.ts +31 -0
- package/dist/three/renderer.d.ts.map +1 -0
- package/dist/three/renderer.js +74 -0
- package/dist/three/renderer.js.map +1 -0
- package/dist/xml.d.ts +36 -0
- package/dist/xml.d.ts.map +1 -0
- package/dist/xml.js +139 -0
- package/dist/xml.js.map +1 -0
- package/dist/zip.d.ts +30 -0
- package/dist/zip.d.ts.map +1 -0
- package/dist/zip.js +129 -0
- package/dist/zip.js.map +1 -0
- package/package.json +55 -0
|
@@ -0,0 +1,1317 @@
|
|
|
1
|
+
import { Demo3DMaterial, Demo3DMesh, Demo3DPackage, Demo3DProject, Demo3DResource, Demo3DVisual } from "../model.js";
|
|
2
|
+
import { Demo3DBinaryBlock, Demo3DXmlElement } from "../xml.js";
|
|
3
|
+
export { createDemo3DThreeRenderer } from "./renderer.js";
|
|
4
|
+
export async function createDemo3DThreeScene(parsed, options = {}) {
|
|
5
|
+
const three = await resolveThree(options);
|
|
6
|
+
const group = await createDemo3DThreeGroup(parsed, { ...options, three });
|
|
7
|
+
const scene = new three.Scene();
|
|
8
|
+
scene.name = "Demo3D Scene";
|
|
9
|
+
scene.add(group);
|
|
10
|
+
return {
|
|
11
|
+
three,
|
|
12
|
+
scene,
|
|
13
|
+
group,
|
|
14
|
+
stats: group.userData.demo3d?.stats,
|
|
15
|
+
warnings: group.userData.demo3d?.warnings
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export async function createDemo3DThreeGroup(parsed, options = {}) {
|
|
19
|
+
const three = await resolveThree(options);
|
|
20
|
+
const project = parsed instanceof Demo3DPackage ? parsed.model : parsed;
|
|
21
|
+
const state = createState(three, project, options, parsed instanceof Demo3DPackage ? parsed.buffers : []);
|
|
22
|
+
const root = new three.Group();
|
|
23
|
+
root.name = "Demo3D";
|
|
24
|
+
for (const visual of project.visuals) {
|
|
25
|
+
root.add(createVisualObject(visual, state));
|
|
26
|
+
}
|
|
27
|
+
if (options.includeSerializedRenderables === true) {
|
|
28
|
+
const serialized = createSerializedRenderableGroup(state);
|
|
29
|
+
if (serialized.children.length > 0) {
|
|
30
|
+
root.add(serialized);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
root.userData.demo3d = {
|
|
34
|
+
kind: "package",
|
|
35
|
+
stats: state.stats,
|
|
36
|
+
warnings: state.warnings
|
|
37
|
+
};
|
|
38
|
+
return root;
|
|
39
|
+
}
|
|
40
|
+
export function decodeDemo3DThreeGeometry(mesh, three) {
|
|
41
|
+
if (mesh.meshFormat !== "TriangleList") {
|
|
42
|
+
throw new Error(`Unsupported Demo3D mesh format: ${mesh.meshFormat ?? "unknown"}`);
|
|
43
|
+
}
|
|
44
|
+
if (!mesh.vertices) {
|
|
45
|
+
throw new Error(`Demo3D mesh ${mesh.id ?? "(unknown)"} has no vertex buffer.`);
|
|
46
|
+
}
|
|
47
|
+
const vertexFormat = mesh.vertexFormat ?? "unknown";
|
|
48
|
+
const layout = vertexLayout(vertexFormat);
|
|
49
|
+
const vertexBytes = mesh.vertices.toUint8Array();
|
|
50
|
+
if (vertexBytes.length % layout.stride !== 0) {
|
|
51
|
+
throw new Error(`Demo3D mesh ${mesh.id ?? "(unknown)"} vertex buffer size ${vertexBytes.length} is not divisible by ${layout.stride}.`);
|
|
52
|
+
}
|
|
53
|
+
const vertexCount = vertexBytes.length / layout.stride;
|
|
54
|
+
const view = new DataView(vertexBytes.buffer, vertexBytes.byteOffset, vertexBytes.byteLength);
|
|
55
|
+
const positions = new Float32Array(vertexCount * 3);
|
|
56
|
+
const normals = layout.normalOffset === undefined ? undefined : new Float32Array(vertexCount * 3);
|
|
57
|
+
const uvs = layout.uvOffset === undefined ? undefined : new Float32Array(vertexCount * 2);
|
|
58
|
+
for (let vertexIndex = 0; vertexIndex < vertexCount; vertexIndex += 1) {
|
|
59
|
+
const base = vertexIndex * layout.stride;
|
|
60
|
+
positions[vertexIndex * 3] = view.getFloat32(base, true);
|
|
61
|
+
positions[vertexIndex * 3 + 1] = view.getFloat32(base + 4, true);
|
|
62
|
+
positions[vertexIndex * 3 + 2] = view.getFloat32(base + 8, true);
|
|
63
|
+
if (normals && layout.normalOffset !== undefined) {
|
|
64
|
+
normals[vertexIndex * 3] = view.getFloat32(base + layout.normalOffset, true);
|
|
65
|
+
normals[vertexIndex * 3 + 1] = view.getFloat32(base + layout.normalOffset + 4, true);
|
|
66
|
+
normals[vertexIndex * 3 + 2] = view.getFloat32(base + layout.normalOffset + 8, true);
|
|
67
|
+
}
|
|
68
|
+
if (uvs && layout.uvOffset !== undefined) {
|
|
69
|
+
uvs[vertexIndex * 2] = view.getFloat32(base + layout.uvOffset, true);
|
|
70
|
+
uvs[vertexIndex * 2 + 1] = view.getFloat32(base + layout.uvOffset + 4, true);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const geometry = new three.BufferGeometry();
|
|
74
|
+
geometry.setAttribute("position", new three.BufferAttribute(positions, 3));
|
|
75
|
+
if (normals) {
|
|
76
|
+
geometry.setAttribute("normal", new three.BufferAttribute(normals, 3));
|
|
77
|
+
}
|
|
78
|
+
if (uvs) {
|
|
79
|
+
geometry.setAttribute("uv", new three.BufferAttribute(uvs, 2));
|
|
80
|
+
}
|
|
81
|
+
if (mesh.indices) {
|
|
82
|
+
geometry.setIndex(new three.BufferAttribute(readIndexBuffer(mesh.indices, mesh.indexFormat), 1));
|
|
83
|
+
}
|
|
84
|
+
convertTriangleGeometryToThreeCoordinates(geometry);
|
|
85
|
+
if (!normals) {
|
|
86
|
+
geometry.computeVertexNormals();
|
|
87
|
+
}
|
|
88
|
+
geometry.computeBoundingBox();
|
|
89
|
+
geometry.computeBoundingSphere();
|
|
90
|
+
return geometry;
|
|
91
|
+
}
|
|
92
|
+
export function createDemo3DThreeMaterial(material, three) {
|
|
93
|
+
const diffuse = material?.diffuse;
|
|
94
|
+
const color = diffuse === undefined ? 0x9aa0a6 : demo3dColorToHex(diffuse);
|
|
95
|
+
const opacity = diffuse === undefined ? 1 : demo3dColorToOpacity(diffuse);
|
|
96
|
+
return new three.MeshStandardMaterial({
|
|
97
|
+
color,
|
|
98
|
+
opacity,
|
|
99
|
+
transparent: opacity < 1,
|
|
100
|
+
roughness: 0.72,
|
|
101
|
+
metalness: 0.05
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
async function resolveThree(options) {
|
|
105
|
+
if (options.three) {
|
|
106
|
+
return options.three;
|
|
107
|
+
}
|
|
108
|
+
if (options.loadThree) {
|
|
109
|
+
return options.loadThree();
|
|
110
|
+
}
|
|
111
|
+
return import("three");
|
|
112
|
+
}
|
|
113
|
+
function createState(three, project, options, buffers) {
|
|
114
|
+
return {
|
|
115
|
+
three,
|
|
116
|
+
project,
|
|
117
|
+
options,
|
|
118
|
+
warnings: [],
|
|
119
|
+
meshById: new Map(project.meshes.flatMap((mesh) => (mesh.id ? [[mesh.id, mesh]] : []))),
|
|
120
|
+
serializedObjectById: indexSerializedObjects(project),
|
|
121
|
+
textureImageById: indexTextureImages(project),
|
|
122
|
+
drawingBlockById: indexDrawingBlocks(project),
|
|
123
|
+
bufferByName: indexBuffers(buffers),
|
|
124
|
+
geometryCache: new Map(),
|
|
125
|
+
primitiveGeometryCache: new Map(),
|
|
126
|
+
lineGeometryCache: new Map(),
|
|
127
|
+
materialCache: new Map(),
|
|
128
|
+
textureCache: new Map(),
|
|
129
|
+
defaultMaterial: createDemo3DThreeMaterial(undefined, three),
|
|
130
|
+
stats: {
|
|
131
|
+
groups: 0,
|
|
132
|
+
meshes: 0,
|
|
133
|
+
geometries: 0,
|
|
134
|
+
materials: 1,
|
|
135
|
+
textures: 0,
|
|
136
|
+
textVisuals: 0,
|
|
137
|
+
drawingBlocks: 0,
|
|
138
|
+
lines: 0,
|
|
139
|
+
directVisuals: 0,
|
|
140
|
+
proceduralBelts: 0,
|
|
141
|
+
imageVisuals: 0,
|
|
142
|
+
lights: 0,
|
|
143
|
+
missingGeometryPlaceholders: 0,
|
|
144
|
+
serializedRenderables: 0,
|
|
145
|
+
unsupported: 0
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function createVisualObject(visual, state, inheritedLayer) {
|
|
150
|
+
const refs = findMeshReferenceIds(visual.xml);
|
|
151
|
+
const meshes = [];
|
|
152
|
+
const textObjects = [];
|
|
153
|
+
const drawingBlockObjects = [];
|
|
154
|
+
const directVisualObjects = [];
|
|
155
|
+
const placeholderObjects = [];
|
|
156
|
+
const textObject = createTextVisualObject(visual, state);
|
|
157
|
+
if (textObject) {
|
|
158
|
+
textObjects.push(textObject);
|
|
159
|
+
meshes.push(textObject);
|
|
160
|
+
}
|
|
161
|
+
const drawingBlockObject = createDrawingBlockVisualObject(visual, state);
|
|
162
|
+
if (drawingBlockObject) {
|
|
163
|
+
drawingBlockObjects.push(drawingBlockObject);
|
|
164
|
+
meshes.push(drawingBlockObject);
|
|
165
|
+
}
|
|
166
|
+
const directVisualObject = createDirectVisualObject(visual, state);
|
|
167
|
+
if (directVisualObject) {
|
|
168
|
+
directVisualObjects.push(directVisualObject);
|
|
169
|
+
meshes.push(directVisualObject);
|
|
170
|
+
}
|
|
171
|
+
for (const ref of refs) {
|
|
172
|
+
const mesh = createMeshObject(ref, visual, visual.materials[0], state);
|
|
173
|
+
if (mesh) {
|
|
174
|
+
if (mesh.userData.demo3d?.kind === "missing-geometry-placeholder") {
|
|
175
|
+
placeholderObjects.push(mesh);
|
|
176
|
+
}
|
|
177
|
+
meshes.push(mesh);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const aspectRenderableObjects = createAspectRenderableObjects(visual, state);
|
|
181
|
+
meshes.push(...aspectRenderableObjects);
|
|
182
|
+
let object;
|
|
183
|
+
if (meshes.length === 1 &&
|
|
184
|
+
visual.children.length === 0 &&
|
|
185
|
+
aspectRenderableObjects.length === 0 &&
|
|
186
|
+
textObjects.length === 0 &&
|
|
187
|
+
drawingBlockObjects.length === 0 &&
|
|
188
|
+
directVisualObjects.length === 0 &&
|
|
189
|
+
placeholderObjects.length === 0) {
|
|
190
|
+
object = meshes[0];
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
const group = new state.three.Group();
|
|
194
|
+
for (const mesh of meshes) {
|
|
195
|
+
group.add(mesh);
|
|
196
|
+
}
|
|
197
|
+
object = group;
|
|
198
|
+
state.stats.groups += 1;
|
|
199
|
+
}
|
|
200
|
+
object.name = visual.displayName ?? visual.id ?? visual.typeName;
|
|
201
|
+
applyDemo3DTransform(object, visual.xml.textOf("LR"));
|
|
202
|
+
applyDemo3DScale(object, visual.properties?.textOf("Scale"));
|
|
203
|
+
const layer = visual.layer ?? inheritedLayer;
|
|
204
|
+
object.userData.demo3d = {
|
|
205
|
+
kind: "visual",
|
|
206
|
+
id: visual.id,
|
|
207
|
+
name: visual.displayName,
|
|
208
|
+
typeName: visual.typeName,
|
|
209
|
+
xmlPath: visual.xml.path,
|
|
210
|
+
localTransform: visual.localTransform,
|
|
211
|
+
localTransformText: visual.xml.textOf("LR"),
|
|
212
|
+
layer
|
|
213
|
+
};
|
|
214
|
+
for (const child of visual.children) {
|
|
215
|
+
object.add(createVisualObject(child, state, layer));
|
|
216
|
+
}
|
|
217
|
+
return object;
|
|
218
|
+
}
|
|
219
|
+
function createAspectRenderableObjects(visual, state) {
|
|
220
|
+
const objects = [];
|
|
221
|
+
for (const aspect of findVisualAspects(visual, state)) {
|
|
222
|
+
const renderables = aspect.child("Renderables");
|
|
223
|
+
if (!renderables) {
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
for (const renderable of renderables.children) {
|
|
227
|
+
const meshIds = findMeshReferenceIds(renderable);
|
|
228
|
+
for (const meshId of meshIds) {
|
|
229
|
+
const material = firstMaterial(renderable) ?? visual.materials[0];
|
|
230
|
+
const mesh = state.meshById.has(meshId)
|
|
231
|
+
? createMeshObject(meshId, renderable, material, state)
|
|
232
|
+
: createPrimitiveRenderableObject(aspect, renderable, meshId, material, state) ??
|
|
233
|
+
createMeshObject(meshId, renderable, material, state);
|
|
234
|
+
if (!mesh) {
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
applyRenderableTransform(mesh, renderable);
|
|
238
|
+
mesh.name = visual.displayName ? `${visual.displayName} ${mesh.name}` : mesh.name;
|
|
239
|
+
const existingDemo3D = mesh.userData.demo3d ?? {};
|
|
240
|
+
mesh.userData.demo3d = {
|
|
241
|
+
...existingDemo3D,
|
|
242
|
+
kind: existingDemo3D.kind === "missing-geometry-placeholder" ? existingDemo3D.kind : "renderable",
|
|
243
|
+
renderableKind: "renderable",
|
|
244
|
+
visualId: visual.id,
|
|
245
|
+
visualName: visual.displayName,
|
|
246
|
+
aspectId: aspect.textOf("Id"),
|
|
247
|
+
aspectType: aspect.xsiType,
|
|
248
|
+
renderableId: renderable.textOf("Id"),
|
|
249
|
+
renderablePath: renderable.path
|
|
250
|
+
};
|
|
251
|
+
objects.push(mesh);
|
|
252
|
+
state.stats.serializedRenderables += 1;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return objects;
|
|
257
|
+
}
|
|
258
|
+
function createTextVisualObject(visual, state) {
|
|
259
|
+
if (state.options.renderText === false || visual.typeName !== "e3d:TextVisual") {
|
|
260
|
+
return undefined;
|
|
261
|
+
}
|
|
262
|
+
const properties = visual.properties;
|
|
263
|
+
if (!properties) {
|
|
264
|
+
return undefined;
|
|
265
|
+
}
|
|
266
|
+
const text = properties?.textOf("Text");
|
|
267
|
+
if (!text) {
|
|
268
|
+
return undefined;
|
|
269
|
+
}
|
|
270
|
+
const material = firstMaterial(visual.xml);
|
|
271
|
+
const color = demo3dColorToHex(material?.diffuse ?? -1);
|
|
272
|
+
const lineHeight = numberChild(properties, "LineHeight", 0.05);
|
|
273
|
+
const bold = properties?.textOf("Bold") === "1" || properties?.textOf("Bold")?.toLowerCase() === "true";
|
|
274
|
+
const textMesh = canUseCanvas()
|
|
275
|
+
? createCanvasTextMesh(text, lineHeight, color, bold, state)
|
|
276
|
+
: createFallbackTextMesh(text, lineHeight, material, state);
|
|
277
|
+
textMesh.name = visual.displayName ?? text;
|
|
278
|
+
textMesh.userData.demo3d = {
|
|
279
|
+
kind: "text",
|
|
280
|
+
id: visual.id,
|
|
281
|
+
text,
|
|
282
|
+
typeName: visual.typeName,
|
|
283
|
+
xmlPath: visual.xml.path,
|
|
284
|
+
lineHeight,
|
|
285
|
+
fontFamily: properties?.textOf("FontFamily"),
|
|
286
|
+
horizontalAlign: properties?.textOf("HorizontalAlign"),
|
|
287
|
+
verticalAlign: properties?.textOf("VerticalAlign")
|
|
288
|
+
};
|
|
289
|
+
state.stats.meshes += 1;
|
|
290
|
+
state.stats.textVisuals += 1;
|
|
291
|
+
return textMesh;
|
|
292
|
+
}
|
|
293
|
+
function createDrawingBlockVisualObject(visual, state) {
|
|
294
|
+
if (visual.typeName !== "e3d:PrimitivesVisual") {
|
|
295
|
+
return undefined;
|
|
296
|
+
}
|
|
297
|
+
const blockId = visual.properties?.textOf("DrawingBlockRef");
|
|
298
|
+
if (!blockId) {
|
|
299
|
+
return undefined;
|
|
300
|
+
}
|
|
301
|
+
const geometry = getDrawingBlockGeometry(blockId, state);
|
|
302
|
+
if (!geometry) {
|
|
303
|
+
return undefined;
|
|
304
|
+
}
|
|
305
|
+
const material = getLineMaterial(firstMaterial(visual.xml) ?? visual.materials[0], state);
|
|
306
|
+
const object = new state.three.LineSegments(geometry, material);
|
|
307
|
+
object.name = visual.displayName ?? blockId;
|
|
308
|
+
object.userData.demo3d = {
|
|
309
|
+
kind: "drawing-block",
|
|
310
|
+
id: visual.id,
|
|
311
|
+
blockId,
|
|
312
|
+
typeName: visual.typeName,
|
|
313
|
+
xmlPath: visual.xml.path
|
|
314
|
+
};
|
|
315
|
+
state.stats.drawingBlocks += 1;
|
|
316
|
+
state.stats.lines += geometry.getAttribute("position").count / 2;
|
|
317
|
+
return object;
|
|
318
|
+
}
|
|
319
|
+
function createDirectVisualObject(visual, state) {
|
|
320
|
+
if (!isRenderableVisualVisible(visual)) {
|
|
321
|
+
return undefined;
|
|
322
|
+
}
|
|
323
|
+
if (visual.typeName === "e3d:LightVisual") {
|
|
324
|
+
return createLightObject(visual, state);
|
|
325
|
+
}
|
|
326
|
+
if (visual.typeName === "e3d:ImportedImageVisual") {
|
|
327
|
+
return createImportedImageObject(visual, state);
|
|
328
|
+
}
|
|
329
|
+
if (visual.typeName === "e3d:StraightBeltConveyor") {
|
|
330
|
+
return state.options.renderProceduralBelts === true
|
|
331
|
+
? createStraightBeltConveyorObject(visual, state)
|
|
332
|
+
: undefined;
|
|
333
|
+
}
|
|
334
|
+
const geometry = getDirectVisualGeometry(visual, state);
|
|
335
|
+
if (!geometry) {
|
|
336
|
+
return undefined;
|
|
337
|
+
}
|
|
338
|
+
const object = new state.three.Mesh(geometry, getMaterial(primaryMaterial(visual), state));
|
|
339
|
+
object.name = visual.displayName ?? visual.id ?? visual.typeName;
|
|
340
|
+
object.userData.demo3d = {
|
|
341
|
+
kind: "direct-visual",
|
|
342
|
+
id: visual.id,
|
|
343
|
+
name: visual.displayName,
|
|
344
|
+
typeName: visual.typeName,
|
|
345
|
+
xmlPath: visual.xml.path
|
|
346
|
+
};
|
|
347
|
+
state.stats.meshes += 1;
|
|
348
|
+
state.stats.directVisuals += 1;
|
|
349
|
+
return object;
|
|
350
|
+
}
|
|
351
|
+
function createStraightBeltConveyorObject(visual, state) {
|
|
352
|
+
const belt = readProceduralBelt(visual, state);
|
|
353
|
+
if (!belt) {
|
|
354
|
+
return undefined;
|
|
355
|
+
}
|
|
356
|
+
const geometry = getProceduralBeltGeometry(belt, state);
|
|
357
|
+
const object = new state.three.Mesh(geometry, [
|
|
358
|
+
getMaterial(belt.surfaceMaterial, state),
|
|
359
|
+
getMaterial(belt.sideMaterial, state)
|
|
360
|
+
]);
|
|
361
|
+
object.name = visual.displayName ?? visual.id ?? visual.typeName;
|
|
362
|
+
object.userData.demo3d = {
|
|
363
|
+
kind: "procedural-belt",
|
|
364
|
+
id: visual.id,
|
|
365
|
+
name: visual.displayName,
|
|
366
|
+
typeName: visual.typeName,
|
|
367
|
+
xmlPath: visual.xml.path,
|
|
368
|
+
length: belt.length,
|
|
369
|
+
width: belt.width,
|
|
370
|
+
loopHeight: belt.thickness,
|
|
371
|
+
radius: belt.endRadius,
|
|
372
|
+
startCap: belt.startCap,
|
|
373
|
+
endCap: belt.endCap
|
|
374
|
+
};
|
|
375
|
+
state.stats.meshes += 1;
|
|
376
|
+
state.stats.directVisuals += 1;
|
|
377
|
+
state.stats.proceduralBelts += 1;
|
|
378
|
+
return object;
|
|
379
|
+
}
|
|
380
|
+
function readProceduralBelt(visual, state) {
|
|
381
|
+
const properties = visual.properties;
|
|
382
|
+
if (!properties || isFalse(properties.textOf("CenterVisible"))) {
|
|
383
|
+
return undefined;
|
|
384
|
+
}
|
|
385
|
+
const centerProfile = properties.child("CenterProfile");
|
|
386
|
+
if (centerProfile && (centerProfile.children.length > 0 || centerProfile.text.length > 0)) {
|
|
387
|
+
warn(state, {
|
|
388
|
+
code: "DEMO3D_THREE_UNSUPPORTED_BELT_PROFILE",
|
|
389
|
+
message: "Straight belt conveyors with a custom CenterProfile are not supported.",
|
|
390
|
+
sourceId: visual.id,
|
|
391
|
+
sourceType: visual.typeName,
|
|
392
|
+
xmlPath: visual.xml.path
|
|
393
|
+
});
|
|
394
|
+
return undefined;
|
|
395
|
+
}
|
|
396
|
+
const startPadding = numberChild(properties, "StartPadding", 0);
|
|
397
|
+
const endPadding = numberChild(properties, "EndPadding", 0);
|
|
398
|
+
const leftPadding = numberChild(properties, "LeftPadding", 0);
|
|
399
|
+
const rightPadding = numberChild(properties, "RightPadding", 0);
|
|
400
|
+
const serializedLength = numberChildOptional(properties, "Length");
|
|
401
|
+
const beltLength = numberChildOptional(properties, "BeltLength") ??
|
|
402
|
+
(serializedLength === undefined ? undefined : serializedLength - startPadding - endPadding);
|
|
403
|
+
const serializedWidth = numberChildOptional(properties, "Width");
|
|
404
|
+
const beltWidth = numberChildOptional(properties, "BeltWidth") ?? serializedWidth;
|
|
405
|
+
const effectiveLength = beltLength ?? 0;
|
|
406
|
+
const effectiveWidth = (beltWidth ?? 0) - leftPadding - rightPadding;
|
|
407
|
+
const centerDiameter = numberChildOptional(properties, "CenterDiameter") ??
|
|
408
|
+
numberChildOptional(properties, "BeltDiameter") ??
|
|
409
|
+
0;
|
|
410
|
+
const useBeltCenterHeight = isTrue(properties.textOf("UseBeltCenterHeight"));
|
|
411
|
+
const loopHeight = useBeltCenterHeight
|
|
412
|
+
? numberChild(properties, "BeltCenterHeight", centerDiameter)
|
|
413
|
+
: centerDiameter;
|
|
414
|
+
const endRadius = centerDiameter / 2;
|
|
415
|
+
if (effectiveLength <= 0 || effectiveWidth <= 0 || loopHeight <= 0 || endRadius <= 0) {
|
|
416
|
+
warn(state, {
|
|
417
|
+
code: "DEMO3D_THREE_INVALID_BELT_DIMENSIONS",
|
|
418
|
+
message: `Straight belt conveyor dimensions must be positive (length ${effectiveLength}, width ${effectiveWidth}, height ${loopHeight}).`,
|
|
419
|
+
sourceId: visual.id,
|
|
420
|
+
sourceType: visual.typeName,
|
|
421
|
+
xmlPath: visual.xml.path
|
|
422
|
+
});
|
|
423
|
+
return undefined;
|
|
424
|
+
}
|
|
425
|
+
const startCap = beltCap(properties.textOf("BeltCapStart"), visual, state);
|
|
426
|
+
const endCap = beltCap(properties.textOf("BeltCapEnd"), visual, state);
|
|
427
|
+
const surfaceMaterial = firstMaterial(properties.child("SurfaceMaterial") ?? properties);
|
|
428
|
+
const sideMaterial = firstMaterial(properties.child("SurfaceSideMaterial") ?? properties) ?? surfaceMaterial;
|
|
429
|
+
return {
|
|
430
|
+
length: effectiveLength,
|
|
431
|
+
width: effectiveWidth,
|
|
432
|
+
thickness: loopHeight,
|
|
433
|
+
endRadius,
|
|
434
|
+
xOffset: startPadding,
|
|
435
|
+
zOffset: (rightPadding - leftPadding) / 2,
|
|
436
|
+
startCap,
|
|
437
|
+
endCap,
|
|
438
|
+
surfaceMaterial,
|
|
439
|
+
sideMaterial
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
function beltCap(value, visual, state) {
|
|
443
|
+
if (!value || value === "Box") {
|
|
444
|
+
return "Box";
|
|
445
|
+
}
|
|
446
|
+
if (value === "Cylinder") {
|
|
447
|
+
return "Cylinder";
|
|
448
|
+
}
|
|
449
|
+
warn(state, {
|
|
450
|
+
code: "DEMO3D_THREE_UNSUPPORTED_BELT_CAP",
|
|
451
|
+
message: `Belt cap ${value} is not supported; using Box geometry.`,
|
|
452
|
+
sourceId: visual.id,
|
|
453
|
+
sourceType: visual.typeName,
|
|
454
|
+
xmlPath: visual.xml.path
|
|
455
|
+
});
|
|
456
|
+
return "Box";
|
|
457
|
+
}
|
|
458
|
+
function getProceduralBeltGeometry(belt, state) {
|
|
459
|
+
const key = `belt:${belt.length}:${belt.width}:${belt.thickness}:${belt.endRadius}:${belt.xOffset}:${belt.zOffset}:${belt.startCap}:${belt.endCap}`;
|
|
460
|
+
const cached = state.primitiveGeometryCache.get(key);
|
|
461
|
+
if (cached) {
|
|
462
|
+
return cached;
|
|
463
|
+
}
|
|
464
|
+
const geometry = belt.startCap === "Box" && belt.endCap === "Box"
|
|
465
|
+
? createRectangularBeltGeometry(belt, state)
|
|
466
|
+
: createRoundedBeltGeometry(belt, state);
|
|
467
|
+
convertTriangleGeometryToThreeCoordinates(geometry);
|
|
468
|
+
geometry.computeBoundingBox();
|
|
469
|
+
geometry.computeBoundingSphere();
|
|
470
|
+
state.primitiveGeometryCache.set(key, geometry);
|
|
471
|
+
state.stats.geometries += 1;
|
|
472
|
+
return geometry;
|
|
473
|
+
}
|
|
474
|
+
function createRectangularBeltGeometry(belt, state) {
|
|
475
|
+
const geometry = new state.three.BoxGeometry(belt.length, belt.thickness, belt.width);
|
|
476
|
+
geometry.translate(belt.xOffset + belt.length / 2, -belt.thickness / 2, belt.zOffset);
|
|
477
|
+
// BoxGeometry emits +/-Z last; those two width-facing ends use the side material.
|
|
478
|
+
geometry.groups.forEach((group, index) => {
|
|
479
|
+
group.materialIndex = index >= 4 ? 1 : 0;
|
|
480
|
+
});
|
|
481
|
+
return geometry;
|
|
482
|
+
}
|
|
483
|
+
function createRoundedBeltGeometry(belt, state) {
|
|
484
|
+
const capRadius = Math.min(belt.endRadius, belt.thickness / 2, belt.length / 2);
|
|
485
|
+
const startIsCylinder = belt.startCap === "Cylinder";
|
|
486
|
+
const endIsCylinder = belt.endCap === "Cylinder";
|
|
487
|
+
const shape = new state.three.Shape();
|
|
488
|
+
shape.moveTo(startIsCylinder ? capRadius : 0, 0);
|
|
489
|
+
shape.lineTo(endIsCylinder ? belt.length - capRadius : belt.length, 0);
|
|
490
|
+
if (endIsCylinder) {
|
|
491
|
+
shape.absarc(belt.length - capRadius, -capRadius, capRadius, Math.PI / 2, -Math.PI / 2, true);
|
|
492
|
+
shape.lineTo(belt.length - capRadius, -belt.thickness);
|
|
493
|
+
}
|
|
494
|
+
else {
|
|
495
|
+
shape.lineTo(belt.length, -belt.thickness);
|
|
496
|
+
}
|
|
497
|
+
shape.lineTo(startIsCylinder ? capRadius : 0, -belt.thickness);
|
|
498
|
+
if (startIsCylinder) {
|
|
499
|
+
shape.lineTo(capRadius, -2 * capRadius);
|
|
500
|
+
shape.absarc(capRadius, -capRadius, capRadius, -Math.PI / 2, Math.PI / 2, true);
|
|
501
|
+
}
|
|
502
|
+
else {
|
|
503
|
+
shape.lineTo(0, 0);
|
|
504
|
+
}
|
|
505
|
+
const geometry = new state.three.ExtrudeGeometry(shape, {
|
|
506
|
+
bevelEnabled: false,
|
|
507
|
+
curveSegments: 8,
|
|
508
|
+
depth: belt.width,
|
|
509
|
+
steps: 1
|
|
510
|
+
});
|
|
511
|
+
geometry.translate(belt.xOffset, 0, -belt.width / 2 + belt.zOffset);
|
|
512
|
+
// Extrusion caps are width-facing and use the second material.
|
|
513
|
+
for (const group of geometry.groups) {
|
|
514
|
+
group.materialIndex = group.materialIndex === 0 ? 1 : 0;
|
|
515
|
+
}
|
|
516
|
+
return geometry;
|
|
517
|
+
}
|
|
518
|
+
function createImportedImageObject(visual, state) {
|
|
519
|
+
const properties = visual.properties;
|
|
520
|
+
if (!properties) {
|
|
521
|
+
return undefined;
|
|
522
|
+
}
|
|
523
|
+
const width = numberChild(properties, "WidthScale", 1);
|
|
524
|
+
const height = numberChild(properties, "HeightScale", 1);
|
|
525
|
+
const geometry = new state.three.PlaneGeometry(width, height);
|
|
526
|
+
convertTriangleGeometryToThreeCoordinates(geometry);
|
|
527
|
+
const material = getImageMaterial(primaryMaterial(visual), state);
|
|
528
|
+
const object = new state.three.Mesh(geometry, material);
|
|
529
|
+
object.name = visual.displayName ?? visual.id ?? visual.typeName;
|
|
530
|
+
object.userData.demo3d = {
|
|
531
|
+
kind: "image",
|
|
532
|
+
id: visual.id,
|
|
533
|
+
name: visual.displayName,
|
|
534
|
+
typeName: visual.typeName,
|
|
535
|
+
xmlPath: visual.xml.path
|
|
536
|
+
};
|
|
537
|
+
state.stats.geometries += 1;
|
|
538
|
+
state.stats.meshes += 1;
|
|
539
|
+
state.stats.imageVisuals += 1;
|
|
540
|
+
return object;
|
|
541
|
+
}
|
|
542
|
+
function createLightObject(visual, state) {
|
|
543
|
+
const properties = visual.properties;
|
|
544
|
+
const color = demo3dColorToHex(numberChildOptional(properties, "Diffuse") ?? -1);
|
|
545
|
+
const lightType = properties?.textOf("LightType") ?? "Directional";
|
|
546
|
+
const light = lightType === "Point"
|
|
547
|
+
? new state.three.PointLight(color, 1.4)
|
|
548
|
+
: new state.three.DirectionalLight(color, 1.4);
|
|
549
|
+
light.name = visual.displayName ?? visual.id ?? visual.typeName;
|
|
550
|
+
light.userData.demo3d = {
|
|
551
|
+
kind: "light",
|
|
552
|
+
id: visual.id,
|
|
553
|
+
name: visual.displayName,
|
|
554
|
+
typeName: visual.typeName,
|
|
555
|
+
lightType,
|
|
556
|
+
xmlPath: visual.xml.path
|
|
557
|
+
};
|
|
558
|
+
state.stats.lights += 1;
|
|
559
|
+
return light;
|
|
560
|
+
}
|
|
561
|
+
function getDirectVisualGeometry(visual, state) {
|
|
562
|
+
const properties = visual.properties;
|
|
563
|
+
if (!properties) {
|
|
564
|
+
return undefined;
|
|
565
|
+
}
|
|
566
|
+
const key = `direct:${visual.typeName}:${[
|
|
567
|
+
properties.textOf("Angle") ?? "",
|
|
568
|
+
properties.textOf("Depth") ?? "",
|
|
569
|
+
properties.textOf("Height") ?? "",
|
|
570
|
+
properties.textOf("Length") ?? "",
|
|
571
|
+
properties.textOf("Radius") ?? "",
|
|
572
|
+
properties.textOf("StartAngle") ?? "",
|
|
573
|
+
properties.textOf("Width") ?? ""
|
|
574
|
+
].join("|")}`;
|
|
575
|
+
const cached = state.primitiveGeometryCache.get(key);
|
|
576
|
+
if (cached) {
|
|
577
|
+
return cached;
|
|
578
|
+
}
|
|
579
|
+
let geometry;
|
|
580
|
+
if (visual.typeName === "e3d:BoxVisual" || visual.typeName === "e3d:BoxTubeVisual") {
|
|
581
|
+
geometry = new state.three.BoxGeometry(numberChild(properties, "Width", 1), numberChild(properties, "Height", 1), numberChild(properties, "Depth", 1));
|
|
582
|
+
}
|
|
583
|
+
else if (visual.typeName === "e3d:CylinderVisual") {
|
|
584
|
+
geometry = new state.three.CylinderGeometry(numberChild(properties, "Radius", 0.5), numberChild(properties, "Radius", 0.5), numberChild(properties, "Length", 1), 32, 1, false, degreesToRadians(numberChild(properties, "StartAngle", 0)), degreesToRadians(numberChild(properties, "Angle", 360)));
|
|
585
|
+
}
|
|
586
|
+
else if (visual.typeName === "e3d:WedgeVisual") {
|
|
587
|
+
geometry = createWedgeGeometry(numberChild(properties, "Width", 1), numberChild(properties, "Height", 1), numberChild(properties, "Length", 1), state);
|
|
588
|
+
}
|
|
589
|
+
if (!geometry) {
|
|
590
|
+
return undefined;
|
|
591
|
+
}
|
|
592
|
+
convertTriangleGeometryToThreeCoordinates(geometry);
|
|
593
|
+
geometry.computeBoundingBox();
|
|
594
|
+
geometry.computeBoundingSphere();
|
|
595
|
+
state.primitiveGeometryCache.set(key, geometry);
|
|
596
|
+
state.stats.geometries += 1;
|
|
597
|
+
return geometry;
|
|
598
|
+
}
|
|
599
|
+
function createWedgeGeometry(width, height, length, state) {
|
|
600
|
+
const x = width / 2;
|
|
601
|
+
const y = height / 2;
|
|
602
|
+
const z = length / 2;
|
|
603
|
+
const vertices = new Float32Array([
|
|
604
|
+
-x, -y, -z,
|
|
605
|
+
x, -y, -z,
|
|
606
|
+
-x, -y, z,
|
|
607
|
+
x, -y, z,
|
|
608
|
+
-x, y, -z,
|
|
609
|
+
x, y, -z
|
|
610
|
+
]);
|
|
611
|
+
const indices = [0, 1, 2, 1, 3, 2, 0, 4, 1, 1, 4, 5, 0, 2, 4, 1, 5, 3, 2, 3, 4, 3, 5, 4];
|
|
612
|
+
const geometry = new state.three.BufferGeometry();
|
|
613
|
+
geometry.setAttribute("position", new state.three.BufferAttribute(vertices, 3));
|
|
614
|
+
geometry.setIndex(indices);
|
|
615
|
+
geometry.computeVertexNormals();
|
|
616
|
+
return geometry;
|
|
617
|
+
}
|
|
618
|
+
function getDrawingBlockGeometry(blockId, state) {
|
|
619
|
+
const cached = state.lineGeometryCache.get(blockId);
|
|
620
|
+
if (cached) {
|
|
621
|
+
return cached;
|
|
622
|
+
}
|
|
623
|
+
const block = state.drawingBlockById.get(blockId);
|
|
624
|
+
const brep = block?.child("BREP");
|
|
625
|
+
if (!block) {
|
|
626
|
+
return undefined;
|
|
627
|
+
}
|
|
628
|
+
const positions = [];
|
|
629
|
+
if (brep) {
|
|
630
|
+
for (const entity of brep.children) {
|
|
631
|
+
const points = parseBrepPoints(entity.textOf("C"));
|
|
632
|
+
if (points.length < 2) {
|
|
633
|
+
continue;
|
|
634
|
+
}
|
|
635
|
+
if (entity.xsiType === "Demo3D.BREP.Line") {
|
|
636
|
+
pushLineSegment(positions, points[0], points[1]);
|
|
637
|
+
}
|
|
638
|
+
else if (entity.xsiType === "Demo3D.BREP.Curve") {
|
|
639
|
+
for (let index = 1; index < points.length; index += 1) {
|
|
640
|
+
pushLineSegment(positions, points[index - 1], points[index]);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
if (positions.length === 0) {
|
|
646
|
+
for (const name of block.childrenNamed("Name")) {
|
|
647
|
+
const buffer = state.bufferByName.get(name.text.toLowerCase());
|
|
648
|
+
if (buffer) {
|
|
649
|
+
pushFloat32LineSegments(positions, buffer);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
if (positions.length === 0) {
|
|
654
|
+
return undefined;
|
|
655
|
+
}
|
|
656
|
+
const geometry = new state.three.BufferGeometry();
|
|
657
|
+
geometry.setAttribute("position", new state.three.BufferAttribute(new Float32Array(positions), 3));
|
|
658
|
+
geometry.scale(1, 1, -1);
|
|
659
|
+
geometry.computeBoundingBox();
|
|
660
|
+
geometry.computeBoundingSphere();
|
|
661
|
+
state.lineGeometryCache.set(blockId, geometry);
|
|
662
|
+
state.stats.geometries += 1;
|
|
663
|
+
return geometry;
|
|
664
|
+
}
|
|
665
|
+
function createMeshObject(meshId, source, material, state) {
|
|
666
|
+
const mesh = state.meshById.get(meshId);
|
|
667
|
+
const sourceId = source instanceof Demo3DVisual ? source.id : source.textOf("Id");
|
|
668
|
+
const sourceType = source instanceof Demo3DVisual ? source.typeName : source.xsiType ?? source.localName;
|
|
669
|
+
const xmlPath = source instanceof Demo3DVisual ? source.xml.path : source.path;
|
|
670
|
+
if (!mesh) {
|
|
671
|
+
warn(state, {
|
|
672
|
+
code: "DEMO3D_THREE_MISSING_MESH",
|
|
673
|
+
message: `Mesh reference ${meshId} does not exist in the mesh library.`,
|
|
674
|
+
sourceId,
|
|
675
|
+
sourceType,
|
|
676
|
+
xmlPath
|
|
677
|
+
});
|
|
678
|
+
return state.options.showPlaceholders === false
|
|
679
|
+
? undefined
|
|
680
|
+
: createPlaceholderMesh(sourceId ?? meshId, meshId, material, state);
|
|
681
|
+
}
|
|
682
|
+
const geometry = getGeometry(mesh, state);
|
|
683
|
+
if (!geometry) {
|
|
684
|
+
return state.options.showPlaceholders === false
|
|
685
|
+
? undefined
|
|
686
|
+
: createPlaceholderMesh(sourceId ?? meshId, meshId, material, state);
|
|
687
|
+
}
|
|
688
|
+
const object = new state.three.Mesh(geometry, getMaterial(material, state));
|
|
689
|
+
object.name = source instanceof Demo3DVisual ? source.displayName ?? meshId : meshId;
|
|
690
|
+
object.userData.demo3d = {
|
|
691
|
+
kind: "mesh",
|
|
692
|
+
meshId,
|
|
693
|
+
sourceId,
|
|
694
|
+
sourceType,
|
|
695
|
+
xmlPath
|
|
696
|
+
};
|
|
697
|
+
state.stats.meshes += 1;
|
|
698
|
+
return object;
|
|
699
|
+
}
|
|
700
|
+
function createPrimitiveRenderableObject(aspect, renderable, meshReferenceId, material, state) {
|
|
701
|
+
const geometry = getPrimitiveGeometry(aspect, renderable, state);
|
|
702
|
+
if (!geometry) {
|
|
703
|
+
return undefined;
|
|
704
|
+
}
|
|
705
|
+
const object = new state.three.Mesh(geometry, getMaterial(material, state));
|
|
706
|
+
object.name = renderable.textOf("Id") ?? aspect.xsiType ?? meshReferenceId;
|
|
707
|
+
object.userData.demo3d = {
|
|
708
|
+
kind: "primitive-renderable",
|
|
709
|
+
meshReferenceId,
|
|
710
|
+
sourceId: renderable.textOf("Id"),
|
|
711
|
+
sourceType: aspect.xsiType,
|
|
712
|
+
xmlPath: renderable.path
|
|
713
|
+
};
|
|
714
|
+
state.stats.meshes += 1;
|
|
715
|
+
return object;
|
|
716
|
+
}
|
|
717
|
+
function getGeometry(mesh, state) {
|
|
718
|
+
const key = mesh.id ?? mesh.xml.path;
|
|
719
|
+
const cached = state.geometryCache.get(key);
|
|
720
|
+
if (cached) {
|
|
721
|
+
return cached;
|
|
722
|
+
}
|
|
723
|
+
try {
|
|
724
|
+
const geometry = decodeDemo3DThreeGeometry(mesh, state.three);
|
|
725
|
+
state.geometryCache.set(key, geometry);
|
|
726
|
+
state.stats.geometries += 1;
|
|
727
|
+
return geometry;
|
|
728
|
+
}
|
|
729
|
+
catch (error) {
|
|
730
|
+
warn(state, {
|
|
731
|
+
code: "DEMO3D_THREE_UNSUPPORTED_GEOMETRY",
|
|
732
|
+
message: error instanceof Error ? error.message : String(error),
|
|
733
|
+
sourceId: mesh.id,
|
|
734
|
+
sourceType: mesh.typeName,
|
|
735
|
+
xmlPath: mesh.xml.path
|
|
736
|
+
});
|
|
737
|
+
return undefined;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
function getPrimitiveGeometry(aspect, renderable, state) {
|
|
741
|
+
const type = aspect.xsiType;
|
|
742
|
+
const key = primitiveGeometryKey(type, renderable);
|
|
743
|
+
const cached = state.primitiveGeometryCache.get(key);
|
|
744
|
+
if (cached) {
|
|
745
|
+
return cached;
|
|
746
|
+
}
|
|
747
|
+
let geometry;
|
|
748
|
+
if (type === "e3d:CylinderRendererAspect") {
|
|
749
|
+
const radius = numberChild(renderable, "Radius", 0.5);
|
|
750
|
+
const radiusRatio = numberChild(renderable, "RadiusRatio", 1);
|
|
751
|
+
const coneRatio = numberChild(renderable, "ConeRatio", 1);
|
|
752
|
+
const length = numberChild(renderable, "Length", 1);
|
|
753
|
+
const slices = Math.max(3, Math.round(numberChild(renderable, "Slices", 16)));
|
|
754
|
+
const startAngle = degreesToRadians(numberChild(renderable, "StartAngle", 0));
|
|
755
|
+
const angle = degreesToRadians(numberChild(renderable, "Angle", 360));
|
|
756
|
+
geometry = new state.three.CylinderGeometry(radius * radiusRatio, radius * coneRatio, length, slices, 1, false, startAngle, angle);
|
|
757
|
+
}
|
|
758
|
+
else if (type === "e3d:BoxRendererAspect") {
|
|
759
|
+
geometry = new state.three.BoxGeometry(numberChild(renderable, "Width", 1), numberChild(renderable, "Height", 1), numberChild(renderable, "Depth", 1));
|
|
760
|
+
}
|
|
761
|
+
else if (type === "e3d:ContainerRendererAspect") {
|
|
762
|
+
geometry = new state.three.BoxGeometry(numberChild(renderable, "Width", 1), numberChild(renderable, "Height", 1), numberChild(renderable, "Depth", 1));
|
|
763
|
+
}
|
|
764
|
+
if (!geometry) {
|
|
765
|
+
return undefined;
|
|
766
|
+
}
|
|
767
|
+
convertTriangleGeometryToThreeCoordinates(geometry);
|
|
768
|
+
geometry.computeBoundingBox();
|
|
769
|
+
geometry.computeBoundingSphere();
|
|
770
|
+
state.primitiveGeometryCache.set(key, geometry);
|
|
771
|
+
state.stats.geometries += 1;
|
|
772
|
+
return geometry;
|
|
773
|
+
}
|
|
774
|
+
function primitiveGeometryKey(type, renderable) {
|
|
775
|
+
const fields = [
|
|
776
|
+
"Angle",
|
|
777
|
+
"ConeRatio",
|
|
778
|
+
"Depth",
|
|
779
|
+
"Height",
|
|
780
|
+
"Length",
|
|
781
|
+
"Radius",
|
|
782
|
+
"RadiusRatio",
|
|
783
|
+
"Slices",
|
|
784
|
+
"StartAngle",
|
|
785
|
+
"Width"
|
|
786
|
+
];
|
|
787
|
+
return `primitive:${type ?? "unknown"}:${fields.map((field) => renderable.textOf(field) ?? "").join("|")}`;
|
|
788
|
+
}
|
|
789
|
+
function getMaterial(material, state) {
|
|
790
|
+
if (!material) {
|
|
791
|
+
return state.defaultMaterial;
|
|
792
|
+
}
|
|
793
|
+
const textureReference = material.textureReference;
|
|
794
|
+
const key = `${String(material.diffuse ?? "default")}:${textureReference ?? ""}`;
|
|
795
|
+
const cached = state.materialCache.get(key);
|
|
796
|
+
if (cached) {
|
|
797
|
+
return cached;
|
|
798
|
+
}
|
|
799
|
+
const created = createDemo3DThreeMaterial(material, state.three);
|
|
800
|
+
const texture = textureReference ? getTexture(textureReference, state) : undefined;
|
|
801
|
+
if (texture && "map" in created) {
|
|
802
|
+
created.map = texture;
|
|
803
|
+
created.needsUpdate = true;
|
|
804
|
+
}
|
|
805
|
+
state.materialCache.set(key, created);
|
|
806
|
+
state.stats.materials += 1;
|
|
807
|
+
return created;
|
|
808
|
+
}
|
|
809
|
+
function getImageMaterial(material, state) {
|
|
810
|
+
const diffuse = material?.diffuse;
|
|
811
|
+
const textureReference = material?.textureReference;
|
|
812
|
+
const texture = textureReference ? getTexture(textureReference, state) : undefined;
|
|
813
|
+
const key = `image:${String(diffuse ?? "default")}:${textureReference ?? ""}`;
|
|
814
|
+
const cached = state.materialCache.get(key);
|
|
815
|
+
if (cached) {
|
|
816
|
+
return cached;
|
|
817
|
+
}
|
|
818
|
+
const parameters = {
|
|
819
|
+
color: diffuse === undefined ? 0xffffff : demo3dColorToHex(diffuse),
|
|
820
|
+
transparent: texture !== undefined || demo3dColorToOpacity(diffuse ?? 0xffffffff) < 1,
|
|
821
|
+
opacity: diffuse === undefined ? 1 : demo3dColorToOpacity(diffuse),
|
|
822
|
+
side: state.three.DoubleSide
|
|
823
|
+
};
|
|
824
|
+
if (texture) {
|
|
825
|
+
parameters.map = texture;
|
|
826
|
+
}
|
|
827
|
+
const created = new state.three.MeshBasicMaterial(parameters);
|
|
828
|
+
state.materialCache.set(key, created);
|
|
829
|
+
state.stats.materials += 1;
|
|
830
|
+
return created;
|
|
831
|
+
}
|
|
832
|
+
function primaryMaterial(visual) {
|
|
833
|
+
const materialNode = visual.properties?.child("Material") ??
|
|
834
|
+
visual.properties?.child("OuterMaterial") ??
|
|
835
|
+
visual.properties?.child("SurfaceMaterial") ??
|
|
836
|
+
visual.properties ??
|
|
837
|
+
visual.xml;
|
|
838
|
+
return firstMaterial(materialNode) ?? visual.materials[0];
|
|
839
|
+
}
|
|
840
|
+
function getTexture(textureReference, state) {
|
|
841
|
+
const cached = state.textureCache.get(textureReference);
|
|
842
|
+
if (cached) {
|
|
843
|
+
return cached;
|
|
844
|
+
}
|
|
845
|
+
const image = state.textureImageById.get(textureReference);
|
|
846
|
+
if (!image || typeof state.three.TextureLoader !== "function" || typeof document !== "object") {
|
|
847
|
+
return undefined;
|
|
848
|
+
}
|
|
849
|
+
const texture = new state.three.TextureLoader().load(`data:${image.mimeType};base64,${image.base64}`);
|
|
850
|
+
if ("SRGBColorSpace" in state.three) {
|
|
851
|
+
texture.colorSpace = state.three.SRGBColorSpace;
|
|
852
|
+
}
|
|
853
|
+
texture.name = image.name ?? image.id;
|
|
854
|
+
texture.flipY = false;
|
|
855
|
+
state.textureCache.set(textureReference, texture);
|
|
856
|
+
state.stats.textures += 1;
|
|
857
|
+
return texture;
|
|
858
|
+
}
|
|
859
|
+
function createCanvasTextMesh(text, lineHeight, color, bold, state) {
|
|
860
|
+
const canvas = document.createElement("canvas");
|
|
861
|
+
const context = canvas.getContext("2d");
|
|
862
|
+
if (!context) {
|
|
863
|
+
return createFallbackTextMesh(text, lineHeight, undefined, state);
|
|
864
|
+
}
|
|
865
|
+
const fontSize = 64;
|
|
866
|
+
const padding = 12;
|
|
867
|
+
context.font = `${bold ? "700 " : ""}${fontSize}px sans-serif`;
|
|
868
|
+
const metrics = context.measureText(text);
|
|
869
|
+
canvas.width = Math.max(16, Math.ceil(metrics.width + padding * 2));
|
|
870
|
+
canvas.height = fontSize + padding * 2;
|
|
871
|
+
context.clearRect(0, 0, canvas.width, canvas.height);
|
|
872
|
+
context.font = `${bold ? "700 " : ""}${fontSize}px sans-serif`;
|
|
873
|
+
context.textBaseline = "middle";
|
|
874
|
+
context.fillStyle = `#${color.toString(16).padStart(6, "0")}`;
|
|
875
|
+
context.fillText(text, padding, canvas.height / 2);
|
|
876
|
+
const texture = new state.three.CanvasTexture(canvas);
|
|
877
|
+
if ("SRGBColorSpace" in state.three) {
|
|
878
|
+
texture.colorSpace = state.three.SRGBColorSpace;
|
|
879
|
+
}
|
|
880
|
+
texture.needsUpdate = true;
|
|
881
|
+
state.stats.textures += 1;
|
|
882
|
+
const aspect = canvas.width / canvas.height;
|
|
883
|
+
const geometry = new state.three.PlaneGeometry(Math.max(lineHeight * aspect, lineHeight), lineHeight);
|
|
884
|
+
convertTriangleGeometryToThreeCoordinates(geometry);
|
|
885
|
+
const material = new state.three.MeshBasicMaterial({
|
|
886
|
+
map: texture,
|
|
887
|
+
transparent: true,
|
|
888
|
+
side: state.three.DoubleSide,
|
|
889
|
+
depthWrite: false
|
|
890
|
+
});
|
|
891
|
+
state.stats.geometries += 1;
|
|
892
|
+
state.stats.materials += 1;
|
|
893
|
+
return new state.three.Mesh(geometry, material);
|
|
894
|
+
}
|
|
895
|
+
function createFallbackTextMesh(text, lineHeight, material, state) {
|
|
896
|
+
const geometry = new state.three.PlaneGeometry(Math.max(text.length * lineHeight * 0.6, lineHeight), lineHeight);
|
|
897
|
+
convertTriangleGeometryToThreeCoordinates(geometry);
|
|
898
|
+
const mesh = new state.three.Mesh(geometry, getMaterial(material, state));
|
|
899
|
+
state.stats.geometries += 1;
|
|
900
|
+
return mesh;
|
|
901
|
+
}
|
|
902
|
+
function getLineMaterial(material, state) {
|
|
903
|
+
const color = material?.diffuse === undefined ? 0x202124 : demo3dColorToHex(material.diffuse);
|
|
904
|
+
const key = `line:${color}`;
|
|
905
|
+
const cached = state.materialCache.get(key);
|
|
906
|
+
if (cached) {
|
|
907
|
+
return cached;
|
|
908
|
+
}
|
|
909
|
+
const created = new state.three.LineBasicMaterial({ color });
|
|
910
|
+
state.materialCache.set(key, created);
|
|
911
|
+
state.stats.materials += 1;
|
|
912
|
+
return created;
|
|
913
|
+
}
|
|
914
|
+
function parseBrepPoints(value) {
|
|
915
|
+
if (!value) {
|
|
916
|
+
return [];
|
|
917
|
+
}
|
|
918
|
+
return value
|
|
919
|
+
.split("|")
|
|
920
|
+
.map((point) => point.trim())
|
|
921
|
+
.filter(Boolean)
|
|
922
|
+
.map((point) => {
|
|
923
|
+
const values = point
|
|
924
|
+
.split(/\s+/)
|
|
925
|
+
.map((part) => Number.parseFloat(part))
|
|
926
|
+
.filter((part) => Number.isFinite(part));
|
|
927
|
+
return values.length >= 3 ? [values[0], values[1], values[2]] : undefined;
|
|
928
|
+
})
|
|
929
|
+
.filter((point) => point !== undefined);
|
|
930
|
+
}
|
|
931
|
+
function pushLineSegment(positions, start, end) {
|
|
932
|
+
positions.push(start[0], start[1], start[2], end[0], end[1], end[2]);
|
|
933
|
+
}
|
|
934
|
+
function pushFloat32LineSegments(positions, buffer) {
|
|
935
|
+
if (buffer.byteLength < 24) {
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
const lineByteLength = Math.floor(buffer.byteLength / 24) * 24;
|
|
939
|
+
const view = new DataView(buffer.buffer, buffer.byteOffset, lineByteLength);
|
|
940
|
+
for (let offset = 0; offset < lineByteLength; offset += 24) {
|
|
941
|
+
const values = [
|
|
942
|
+
view.getFloat32(offset, true),
|
|
943
|
+
view.getFloat32(offset + 4, true),
|
|
944
|
+
view.getFloat32(offset + 8, true),
|
|
945
|
+
view.getFloat32(offset + 12, true),
|
|
946
|
+
view.getFloat32(offset + 16, true),
|
|
947
|
+
view.getFloat32(offset + 20, true)
|
|
948
|
+
];
|
|
949
|
+
if (values.every((value) => Number.isFinite(value))) {
|
|
950
|
+
positions.push(...values);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
function canUseCanvas() {
|
|
955
|
+
return typeof document === "object" && typeof document.createElement === "function";
|
|
956
|
+
}
|
|
957
|
+
function indexSerializedObjects(project) {
|
|
958
|
+
const indexed = new Map();
|
|
959
|
+
const serializedObjects = project.root.child("SerializedObjects");
|
|
960
|
+
if (!serializedObjects) {
|
|
961
|
+
return indexed;
|
|
962
|
+
}
|
|
963
|
+
for (const item of serializedObjects.children) {
|
|
964
|
+
const id = item.textOf("Id");
|
|
965
|
+
if (id) {
|
|
966
|
+
indexed.set(id, item);
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
return indexed;
|
|
970
|
+
}
|
|
971
|
+
function indexTextureImages(project) {
|
|
972
|
+
const indexed = new Map();
|
|
973
|
+
const textures = project.root.child("TextureLibrary")?.child("Textures");
|
|
974
|
+
if (!textures) {
|
|
975
|
+
return indexed;
|
|
976
|
+
}
|
|
977
|
+
for (const entry of textures.children) {
|
|
978
|
+
const id = entry.child("key")?.textOf("Id") ?? entry.child("key")?.text;
|
|
979
|
+
const value = entry.child("val");
|
|
980
|
+
const bytes = value?.child("Image")?.child("bytes")?.text;
|
|
981
|
+
if (!id || !bytes) {
|
|
982
|
+
continue;
|
|
983
|
+
}
|
|
984
|
+
indexed.set(id, {
|
|
985
|
+
id,
|
|
986
|
+
name: value?.textOf("Name"),
|
|
987
|
+
base64: bytes,
|
|
988
|
+
mimeType: imageMimeType(bytes),
|
|
989
|
+
width: numberChildOptional(value, "Width"),
|
|
990
|
+
height: numberChildOptional(value, "Height")
|
|
991
|
+
});
|
|
992
|
+
}
|
|
993
|
+
return indexed;
|
|
994
|
+
}
|
|
995
|
+
function indexDrawingBlocks(project) {
|
|
996
|
+
const indexed = new Map();
|
|
997
|
+
const blocks = project.root.child("DrawingBlockLibrary")?.child("Blocks");
|
|
998
|
+
if (!blocks) {
|
|
999
|
+
return indexed;
|
|
1000
|
+
}
|
|
1001
|
+
for (const entry of blocks.children) {
|
|
1002
|
+
const id = entry.child("key")?.text;
|
|
1003
|
+
const value = entry.child("val");
|
|
1004
|
+
if (id && value) {
|
|
1005
|
+
indexed.set(id, value);
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
return indexed;
|
|
1009
|
+
}
|
|
1010
|
+
function indexBuffers(buffers) {
|
|
1011
|
+
const indexed = new Map();
|
|
1012
|
+
for (const buffer of buffers) {
|
|
1013
|
+
if (!buffer.data) {
|
|
1014
|
+
continue;
|
|
1015
|
+
}
|
|
1016
|
+
const name = buffer.path.split("/").pop();
|
|
1017
|
+
if (name) {
|
|
1018
|
+
indexed.set(name.toLowerCase(), buffer.data);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
return indexed;
|
|
1022
|
+
}
|
|
1023
|
+
function findVisualAspects(visual, state) {
|
|
1024
|
+
const aspects = visual.xml.child("AS");
|
|
1025
|
+
if (!aspects) {
|
|
1026
|
+
return [];
|
|
1027
|
+
}
|
|
1028
|
+
const found = [];
|
|
1029
|
+
for (const entry of aspects.children) {
|
|
1030
|
+
const id = entry.text;
|
|
1031
|
+
if (!id) {
|
|
1032
|
+
continue;
|
|
1033
|
+
}
|
|
1034
|
+
const aspect = state.serializedObjectById.get(id);
|
|
1035
|
+
if (aspect) {
|
|
1036
|
+
found.push(aspect);
|
|
1037
|
+
}
|
|
1038
|
+
else if (state.options.includeUnsupported) {
|
|
1039
|
+
warn(state, {
|
|
1040
|
+
code: "DEMO3D_THREE_MISSING_ASPECT",
|
|
1041
|
+
message: `Visual aspect ${id} does not exist in SerializedObjects.`,
|
|
1042
|
+
sourceId: visual.id,
|
|
1043
|
+
sourceType: visual.typeName,
|
|
1044
|
+
xmlPath: visual.xml.path
|
|
1045
|
+
});
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
return found;
|
|
1049
|
+
}
|
|
1050
|
+
function createSerializedRenderableGroup(state) {
|
|
1051
|
+
const group = new state.three.Group();
|
|
1052
|
+
group.name = "Demo3D Unlinked Serialized Renderables";
|
|
1053
|
+
group.userData.demo3d = {
|
|
1054
|
+
kind: "unlinked-serialized-renderables",
|
|
1055
|
+
note: "Opt-in fallback for raw renderables that were not placed through visual aspect links."
|
|
1056
|
+
};
|
|
1057
|
+
const serializedObjects = state.project.root.child("SerializedObjects");
|
|
1058
|
+
if (!serializedObjects) {
|
|
1059
|
+
return group;
|
|
1060
|
+
}
|
|
1061
|
+
let index = 0;
|
|
1062
|
+
const max = state.options.maxSerializedRenderables ?? Number.POSITIVE_INFINITY;
|
|
1063
|
+
for (const item of serializedObjects.children) {
|
|
1064
|
+
const renderables = item.child("Renderables");
|
|
1065
|
+
if (!renderables) {
|
|
1066
|
+
continue;
|
|
1067
|
+
}
|
|
1068
|
+
for (const renderable of renderables.children) {
|
|
1069
|
+
if (index >= max) {
|
|
1070
|
+
warn(state, {
|
|
1071
|
+
code: "DEMO3D_THREE_RENDERABLE_LIMIT",
|
|
1072
|
+
message: `Stopped serialized renderable preview at ${max} objects.`,
|
|
1073
|
+
xmlPath: serializedObjects.path
|
|
1074
|
+
});
|
|
1075
|
+
return group;
|
|
1076
|
+
}
|
|
1077
|
+
const meshIds = findMeshReferenceIds(renderable);
|
|
1078
|
+
for (const meshId of meshIds) {
|
|
1079
|
+
const material = firstMaterial(renderable);
|
|
1080
|
+
const mesh = createMeshObject(meshId, renderable, material, state);
|
|
1081
|
+
if (!mesh) {
|
|
1082
|
+
continue;
|
|
1083
|
+
}
|
|
1084
|
+
applyRenderableTransform(mesh, renderable);
|
|
1085
|
+
positionSerializedPreviewMesh(mesh, index);
|
|
1086
|
+
group.add(mesh);
|
|
1087
|
+
state.stats.serializedRenderables += 1;
|
|
1088
|
+
index += 1;
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
return group;
|
|
1093
|
+
}
|
|
1094
|
+
function positionSerializedPreviewMesh(mesh, index) {
|
|
1095
|
+
const columns = 30;
|
|
1096
|
+
const cellSize = 2.4;
|
|
1097
|
+
const targetSize = 1.65;
|
|
1098
|
+
mesh.geometry.computeBoundingBox();
|
|
1099
|
+
const bounds = mesh.geometry.boundingBox;
|
|
1100
|
+
if (!bounds) {
|
|
1101
|
+
mesh.position.set((index % columns) * cellSize, 0, Math.floor(index / columns) * cellSize);
|
|
1102
|
+
return;
|
|
1103
|
+
}
|
|
1104
|
+
const size = mesh.position.clone();
|
|
1105
|
+
const center = mesh.position.clone();
|
|
1106
|
+
bounds.getSize(size);
|
|
1107
|
+
bounds.getCenter(center);
|
|
1108
|
+
const maxSize = Math.max(size.x, size.y, size.z, 0.001);
|
|
1109
|
+
const scale = targetSize / maxSize;
|
|
1110
|
+
const gridX = (index % columns) * cellSize;
|
|
1111
|
+
const gridZ = Math.floor(index / columns) * cellSize;
|
|
1112
|
+
mesh.scale.setScalar(scale);
|
|
1113
|
+
mesh.position.set(gridX - center.x * scale, -center.y * scale, gridZ - center.z * scale);
|
|
1114
|
+
}
|
|
1115
|
+
function createPlaceholderMesh(name, meshReferenceId, material, state) {
|
|
1116
|
+
const geometry = new state.three.BoxGeometry(0.25, 0.25, 0.25);
|
|
1117
|
+
convertTriangleGeometryToThreeCoordinates(geometry);
|
|
1118
|
+
const mesh = new state.three.Mesh(geometry, getMaterial(material, state));
|
|
1119
|
+
mesh.name = `${name} missing geometry`;
|
|
1120
|
+
mesh.userData.demo3d = {
|
|
1121
|
+
kind: "missing-geometry-placeholder",
|
|
1122
|
+
sourceId: name,
|
|
1123
|
+
meshReferenceId
|
|
1124
|
+
};
|
|
1125
|
+
state.stats.meshes += 1;
|
|
1126
|
+
state.stats.geometries += 1;
|
|
1127
|
+
state.stats.missingGeometryPlaceholders += 1;
|
|
1128
|
+
return mesh;
|
|
1129
|
+
}
|
|
1130
|
+
function applyRenderableTransform(object, renderable) {
|
|
1131
|
+
applyDemo3DTransform(object, renderable.textOf("LR"));
|
|
1132
|
+
const scale = parsePipeNumbers(renderable.textOf("Scale"));
|
|
1133
|
+
if (scale.length > 0) {
|
|
1134
|
+
object.scale.set(scale[0] ?? 1, scale[1] ?? scale[0] ?? 1, scale[2] ?? scale[0] ?? 1);
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
function findMeshReferenceIds(root) {
|
|
1138
|
+
const ids = new Set();
|
|
1139
|
+
visit(root, (node) => {
|
|
1140
|
+
if (node.localName === "MeshReference" || node.localName === "Mesh" || node.xsiType === "e3d:MeshReference") {
|
|
1141
|
+
const id = node.textOf("Id");
|
|
1142
|
+
if (id) {
|
|
1143
|
+
ids.add(id);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
});
|
|
1147
|
+
return [...ids];
|
|
1148
|
+
}
|
|
1149
|
+
function firstMaterial(root) {
|
|
1150
|
+
let found;
|
|
1151
|
+
visit(root, (node) => {
|
|
1152
|
+
if (!found && node.xsiType === "e3d:MeshMaterial") {
|
|
1153
|
+
found = new Demo3DMaterial(node.xsiType, node);
|
|
1154
|
+
}
|
|
1155
|
+
});
|
|
1156
|
+
return found;
|
|
1157
|
+
}
|
|
1158
|
+
function visit(root, visitor) {
|
|
1159
|
+
const stack = [root];
|
|
1160
|
+
while (stack.length > 0) {
|
|
1161
|
+
const node = stack.pop();
|
|
1162
|
+
if (!node) {
|
|
1163
|
+
continue;
|
|
1164
|
+
}
|
|
1165
|
+
visitor(node);
|
|
1166
|
+
for (let index = node.children.length - 1; index >= 0; index -= 1) {
|
|
1167
|
+
stack.push(node.children[index]);
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
function vertexLayout(vertexFormat) {
|
|
1172
|
+
switch (vertexFormat) {
|
|
1173
|
+
case "Position":
|
|
1174
|
+
return { stride: 12 };
|
|
1175
|
+
case "PositionNormal":
|
|
1176
|
+
return { stride: 24, normalOffset: 12 };
|
|
1177
|
+
case "PositionNormalTexture":
|
|
1178
|
+
return { stride: 32, normalOffset: 12, uvOffset: 24 };
|
|
1179
|
+
default:
|
|
1180
|
+
throw new Error(`Unsupported Demo3D vertex format: ${vertexFormat}`);
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
function readIndexBuffer(indices, indexFormat) {
|
|
1184
|
+
const bytes = indices.toUint8Array();
|
|
1185
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
1186
|
+
if (indexFormat === "UInt16") {
|
|
1187
|
+
const out = new Uint16Array(bytes.length / 2);
|
|
1188
|
+
for (let index = 0; index < out.length; index += 1) {
|
|
1189
|
+
out[index] = view.getUint16(index * 2, true);
|
|
1190
|
+
}
|
|
1191
|
+
return out;
|
|
1192
|
+
}
|
|
1193
|
+
if (indexFormat === "UInt32") {
|
|
1194
|
+
const out = new Uint32Array(bytes.length / 4);
|
|
1195
|
+
for (let index = 0; index < out.length; index += 1) {
|
|
1196
|
+
out[index] = view.getUint32(index * 4, true);
|
|
1197
|
+
}
|
|
1198
|
+
return out;
|
|
1199
|
+
}
|
|
1200
|
+
throw new Error(`Unsupported Demo3D index format: ${indexFormat ?? "unknown"}`);
|
|
1201
|
+
}
|
|
1202
|
+
function demo3dColorToHex(color) {
|
|
1203
|
+
const unsigned = color >>> 0;
|
|
1204
|
+
const red = (unsigned >>> 16) & 0xff;
|
|
1205
|
+
const green = (unsigned >>> 8) & 0xff;
|
|
1206
|
+
const blue = unsigned & 0xff;
|
|
1207
|
+
return (red << 16) | (green << 8) | blue;
|
|
1208
|
+
}
|
|
1209
|
+
function demo3dColorToOpacity(color) {
|
|
1210
|
+
const alpha = (color >>> 24) & 0xff;
|
|
1211
|
+
return alpha === 0 ? 1 : alpha / 255;
|
|
1212
|
+
}
|
|
1213
|
+
function applyDemo3DTransform(object, transformText) {
|
|
1214
|
+
const values = parsePipeNumbers(transformText);
|
|
1215
|
+
if (values.length === 0) {
|
|
1216
|
+
return;
|
|
1217
|
+
}
|
|
1218
|
+
object.position.set(values[0] ?? 0, values[1] ?? 0, -(values[2] ?? 0));
|
|
1219
|
+
object.rotation.set(-(values[3] ?? 0), -(values[4] ?? 0), values[5] ?? 0);
|
|
1220
|
+
}
|
|
1221
|
+
function convertTriangleGeometryToThreeCoordinates(geometry) {
|
|
1222
|
+
geometry.scale(1, 1, -1);
|
|
1223
|
+
const index = geometry.getIndex();
|
|
1224
|
+
if (index) {
|
|
1225
|
+
const values = index.array;
|
|
1226
|
+
for (let offset = 0; offset + 2 < values.length; offset += 3) {
|
|
1227
|
+
const second = values[offset + 1];
|
|
1228
|
+
values[offset + 1] = values[offset + 2];
|
|
1229
|
+
values[offset + 2] = second;
|
|
1230
|
+
}
|
|
1231
|
+
index.needsUpdate = true;
|
|
1232
|
+
return;
|
|
1233
|
+
}
|
|
1234
|
+
for (const attribute of Object.values(geometry.attributes)) {
|
|
1235
|
+
const values = attribute.array;
|
|
1236
|
+
for (let vertex = 0; vertex + 2 < attribute.count; vertex += 3) {
|
|
1237
|
+
for (let component = 0; component < attribute.itemSize; component += 1) {
|
|
1238
|
+
const second = (vertex + 1) * attribute.itemSize + component;
|
|
1239
|
+
const third = (vertex + 2) * attribute.itemSize + component;
|
|
1240
|
+
const value = values[second];
|
|
1241
|
+
values[second] = values[third];
|
|
1242
|
+
values[third] = value;
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
attribute.needsUpdate = true;
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
function applyDemo3DScale(object, scaleText) {
|
|
1249
|
+
const values = parsePipeNumbers(scaleText);
|
|
1250
|
+
if (values.length === 0) {
|
|
1251
|
+
return;
|
|
1252
|
+
}
|
|
1253
|
+
object.scale.set(object.scale.x * (values[0] ?? 1), object.scale.y * (values[1] ?? 1), object.scale.z * (values[2] ?? 1));
|
|
1254
|
+
}
|
|
1255
|
+
function parsePipeNumbers(value) {
|
|
1256
|
+
if (!value) {
|
|
1257
|
+
return [];
|
|
1258
|
+
}
|
|
1259
|
+
return value.split("|").map((part) => {
|
|
1260
|
+
const trimmed = part.trim();
|
|
1261
|
+
if (!trimmed) {
|
|
1262
|
+
return undefined;
|
|
1263
|
+
}
|
|
1264
|
+
const parsed = Number.parseFloat(trimmed);
|
|
1265
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
1266
|
+
});
|
|
1267
|
+
}
|
|
1268
|
+
function numberChild(element, localName, fallback) {
|
|
1269
|
+
const value = element.textOf(localName);
|
|
1270
|
+
if (!value) {
|
|
1271
|
+
return fallback;
|
|
1272
|
+
}
|
|
1273
|
+
const parsed = Number.parseFloat(value);
|
|
1274
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
1275
|
+
}
|
|
1276
|
+
function numberChildOptional(element, localName) {
|
|
1277
|
+
const value = element?.textOf(localName);
|
|
1278
|
+
if (!value) {
|
|
1279
|
+
return undefined;
|
|
1280
|
+
}
|
|
1281
|
+
const parsed = Number.parseFloat(value);
|
|
1282
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
1283
|
+
}
|
|
1284
|
+
function isRenderableVisualVisible(visual) {
|
|
1285
|
+
const visible = visual.properties?.textOf("Visible");
|
|
1286
|
+
return visible === undefined || !(visible === "0" || visible.toLowerCase() === "false");
|
|
1287
|
+
}
|
|
1288
|
+
function isTrue(value) {
|
|
1289
|
+
return value === "1" || value?.toLowerCase() === "true";
|
|
1290
|
+
}
|
|
1291
|
+
function isFalse(value) {
|
|
1292
|
+
return value === "0" || value?.toLowerCase() === "false";
|
|
1293
|
+
}
|
|
1294
|
+
function imageMimeType(base64) {
|
|
1295
|
+
if (base64.startsWith("iVBORw0KGgo")) {
|
|
1296
|
+
return "image/png";
|
|
1297
|
+
}
|
|
1298
|
+
if (base64.startsWith("/9j/")) {
|
|
1299
|
+
return "image/jpeg";
|
|
1300
|
+
}
|
|
1301
|
+
if (base64.startsWith("R0lGOD")) {
|
|
1302
|
+
return "image/gif";
|
|
1303
|
+
}
|
|
1304
|
+
return "application/octet-stream";
|
|
1305
|
+
}
|
|
1306
|
+
function degreesToRadians(value) {
|
|
1307
|
+
return (value * Math.PI) / 180;
|
|
1308
|
+
}
|
|
1309
|
+
function warn(state, warning) {
|
|
1310
|
+
state.stats.unsupported += 1;
|
|
1311
|
+
state.warnings.push(warning);
|
|
1312
|
+
state.options.onWarning?.(warning);
|
|
1313
|
+
}
|
|
1314
|
+
function isObject3D(value) {
|
|
1315
|
+
return value !== undefined;
|
|
1316
|
+
}
|
|
1317
|
+
//# sourceMappingURL=index.js.map
|