@jfk-solutions/demo3d-file-format 1.4.0 → 1.6.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 +39 -6
- package/dist/binary.js.map +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/fast-xml.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +5 -0
- package/dist/model.js.map +1 -1
- package/dist/parser.js.map +1 -1
- package/dist/raw3d.d.ts +107 -0
- package/dist/raw3d.d.ts.map +1 -0
- package/dist/raw3d.js +281 -0
- package/dist/raw3d.js.map +1 -0
- package/dist/three/index.d.ts +3 -0
- package/dist/three/index.d.ts.map +1 -1
- package/dist/three/index.js +601 -20
- package/dist/three/index.js.map +1 -1
- package/dist/three/raw3d.d.ts +34 -0
- package/dist/three/raw3d.d.ts.map +1 -0
- package/dist/three/raw3d.js +319 -0
- package/dist/three/raw3d.js.map +1 -0
- package/dist/three/renderer.js.map +1 -1
- package/dist/xml.d.ts.map +1 -1
- package/dist/xml.js.map +1 -1
- package/dist/zip.d.ts.map +1 -1
- package/dist/zip.js.map +1 -1
- package/package.json +7 -6
package/dist/three/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Demo3DConveyorSideProperties, Demo3DDimensionAspect, Demo3DDimensionPoint, Demo3DExtrusionPolygon, Demo3DExtrusionProfile, Demo3DMaterial, Demo3DMesh, Demo3DPackage, Demo3DProject, Demo3DPhotoEye, Demo3DResource, Demo3DSupportStand, Demo3DVisual } from "../model.js";
|
|
2
2
|
import { Demo3DBinaryBlock, Demo3DXmlElement } from "../xml.js";
|
|
3
|
+
export { createRaw3DThreeGroup, createRaw3DThreeScene, decodeRaw3DThreeGeometry } from "./raw3d.js";
|
|
3
4
|
export { createDemo3DThreeRenderer } from "./renderer.js";
|
|
4
5
|
export async function createDemo3DThreeScene(parsed, options = {}) {
|
|
5
6
|
const three = await resolveThree(options);
|
|
@@ -88,6 +89,7 @@ export function decodeDemo3DThreeGeometry(mesh, three) {
|
|
|
88
89
|
if (mesh.indices) {
|
|
89
90
|
geometry.setIndex(new three.BufferAttribute(readIndexBuffer(mesh.indices, mesh.indexFormat), 1));
|
|
90
91
|
}
|
|
92
|
+
addMeshSubsetGroups(geometry, mesh);
|
|
91
93
|
convertTriangleGeometryToThreeCoordinates(geometry);
|
|
92
94
|
if (!normals) {
|
|
93
95
|
geometry.computeVertexNormals();
|
|
@@ -96,6 +98,25 @@ export function decodeDemo3DThreeGeometry(mesh, three) {
|
|
|
96
98
|
geometry.computeBoundingSphere();
|
|
97
99
|
return geometry;
|
|
98
100
|
}
|
|
101
|
+
function addMeshSubsetGroups(geometry, mesh) {
|
|
102
|
+
const subsets = mesh.auxiliary?.toUint8Array();
|
|
103
|
+
const drawCount = geometry.getIndex()?.count ?? geometry.getAttribute("position").count;
|
|
104
|
+
const faceCount = Math.floor(drawCount / 3);
|
|
105
|
+
if (!subsets || subsets.length !== faceCount || faceCount === 0) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
let runStart = 0;
|
|
109
|
+
let materialIndex = subsets[0] ?? 0;
|
|
110
|
+
for (let face = 1; face <= faceCount; face += 1) {
|
|
111
|
+
const nextMaterialIndex = face < faceCount ? subsets[face] : undefined;
|
|
112
|
+
if (face < faceCount && nextMaterialIndex === materialIndex) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
geometry.addGroup(runStart * 3, (face - runStart) * 3, materialIndex);
|
|
116
|
+
runStart = face;
|
|
117
|
+
materialIndex = nextMaterialIndex ?? 0;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
99
120
|
export function createDemo3DThreeMaterial(material, three) {
|
|
100
121
|
const diffuse = material?.diffuse;
|
|
101
122
|
const color = diffuse === undefined ? 0x9aa0a6 : demo3dColorToHex(diffuse);
|
|
@@ -151,6 +172,7 @@ function createState(three, project, options, buffers) {
|
|
|
151
172
|
lines: 0,
|
|
152
173
|
directVisuals: 0,
|
|
153
174
|
proceduralBelts: 0,
|
|
175
|
+
proceduralRacks: 0,
|
|
154
176
|
proceduralSupportStands: 0,
|
|
155
177
|
proceduralConveyorSides: 0,
|
|
156
178
|
proceduralPhotoEyes: 0,
|
|
@@ -191,7 +213,7 @@ function createVisualObject(visual, state, inheritedLayer, parentVisual) {
|
|
|
191
213
|
meshes.push(directVisualObject);
|
|
192
214
|
}
|
|
193
215
|
for (const ref of refs) {
|
|
194
|
-
const mesh = createMeshObject(ref, visual, visual.materials
|
|
216
|
+
const mesh = createMeshObject(ref, visual, visual.materials, state);
|
|
195
217
|
if (mesh) {
|
|
196
218
|
if (mesh.userData.demo3d?.kind === "missing-geometry-placeholder") {
|
|
197
219
|
placeholderObjects.push(mesh);
|
|
@@ -280,11 +302,13 @@ function createAspectRenderableObjects(visual, state) {
|
|
|
280
302
|
for (const renderable of renderables.children) {
|
|
281
303
|
const meshIds = findMeshReferenceIds(renderable);
|
|
282
304
|
for (const meshId of meshIds) {
|
|
283
|
-
const
|
|
305
|
+
const serializedMaterials = renderableMaterials(renderable);
|
|
306
|
+
const materials = serializedMaterials.length > 0 ? serializedMaterials : visual.materials;
|
|
307
|
+
const primaryMaterial = materials[0];
|
|
284
308
|
const mesh = state.meshById.has(meshId)
|
|
285
|
-
? createMeshObject(meshId, renderable,
|
|
286
|
-
: createPrimitiveRenderableObject(aspect, renderable, meshId,
|
|
287
|
-
createMeshObject(meshId, renderable,
|
|
309
|
+
? createMeshObject(meshId, renderable, materials, state)
|
|
310
|
+
: createPrimitiveRenderableObject(aspect, renderable, meshId, primaryMaterial, state) ??
|
|
311
|
+
createMeshObject(meshId, renderable, materials, state);
|
|
288
312
|
if (!mesh) {
|
|
289
313
|
continue;
|
|
290
314
|
}
|
|
@@ -380,9 +404,16 @@ function createDirectVisualObject(visual, state, parentVisual) {
|
|
|
380
404
|
if (visual.typeName === "e3d:ImportedImageVisual") {
|
|
381
405
|
return createImportedImageObject(visual, state);
|
|
382
406
|
}
|
|
383
|
-
if (visual.typeName === "e3d:StraightBeltConveyor"
|
|
407
|
+
if (visual.typeName === "e3d:StraightBeltConveyor" ||
|
|
408
|
+
visual.typeName === "e3d:CurveBeltConveyor" ||
|
|
409
|
+
visual.typeName === "e3d:InjectorBeltConveyor") {
|
|
384
410
|
return state.options.renderProceduralBelts === true
|
|
385
|
-
?
|
|
411
|
+
? createBeltConveyorObject(visual, state)
|
|
412
|
+
: undefined;
|
|
413
|
+
}
|
|
414
|
+
if (visual.typeName === "e3d:RackVisual") {
|
|
415
|
+
return state.options.renderProceduralRacks === true
|
|
416
|
+
? createRackObject(visual, state)
|
|
386
417
|
: undefined;
|
|
387
418
|
}
|
|
388
419
|
if (visual.typeName === "e3d:SupportStand") {
|
|
@@ -415,7 +446,16 @@ function createDirectVisualObject(visual, state, parentVisual) {
|
|
|
415
446
|
state.stats.directVisuals += 1;
|
|
416
447
|
return object;
|
|
417
448
|
}
|
|
418
|
-
function
|
|
449
|
+
function createBeltConveyorObject(visual, state) {
|
|
450
|
+
if (visual.typeName === "e3d:CurveBeltConveyor") {
|
|
451
|
+
return createCurveBeltConveyorObject(visual, state);
|
|
452
|
+
}
|
|
453
|
+
if (visual.typeName === "e3d:InjectorBeltConveyor") {
|
|
454
|
+
const injector = createInjectorBeltConveyorObject(visual, state);
|
|
455
|
+
if (injector) {
|
|
456
|
+
return injector;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
419
459
|
const belt = readProceduralBelt(visual, state);
|
|
420
460
|
if (!belt) {
|
|
421
461
|
return undefined;
|
|
@@ -444,6 +484,146 @@ function createStraightBeltConveyorObject(visual, state) {
|
|
|
444
484
|
state.stats.proceduralBelts += 1;
|
|
445
485
|
return object;
|
|
446
486
|
}
|
|
487
|
+
const DEFAULT_BELT_WIDTH = 0.5;
|
|
488
|
+
const DEFAULT_BELT_DIAMETER = 0.06;
|
|
489
|
+
function appendOrientedTriangle(target, a, b, c, normal) {
|
|
490
|
+
const abx = b[0] - a[0];
|
|
491
|
+
const aby = b[1] - a[1];
|
|
492
|
+
const abz = b[2] - a[2];
|
|
493
|
+
const acx = c[0] - a[0];
|
|
494
|
+
const acy = c[1] - a[1];
|
|
495
|
+
const acz = c[2] - a[2];
|
|
496
|
+
const crossX = aby * acz - abz * acy;
|
|
497
|
+
const crossY = abz * acx - abx * acz;
|
|
498
|
+
const crossZ = abx * acy - aby * acx;
|
|
499
|
+
const forward = crossX * normal[0] + crossY * normal[1] + crossZ * normal[2] >= 0;
|
|
500
|
+
target.push(...a, ...(forward ? b : c), ...(forward ? c : b));
|
|
501
|
+
}
|
|
502
|
+
function appendOrientedQuad(target, a, b, c, d, normal) {
|
|
503
|
+
appendOrientedTriangle(target, a, b, c, normal);
|
|
504
|
+
appendOrientedTriangle(target, a, c, d, normal);
|
|
505
|
+
}
|
|
506
|
+
function createInjectorBeltConveyorObject(visual, state) {
|
|
507
|
+
const belt = readProceduralInjectorBelt(visual);
|
|
508
|
+
if (!belt) {
|
|
509
|
+
return undefined;
|
|
510
|
+
}
|
|
511
|
+
const geometry = getProceduralInjectorBeltGeometry(belt, state);
|
|
512
|
+
const object = new state.three.Mesh(geometry, [
|
|
513
|
+
getMaterial(belt.surfaceMaterial, state),
|
|
514
|
+
getMaterial(belt.sideMaterial, state)
|
|
515
|
+
]);
|
|
516
|
+
object.name = visual.displayName ?? visual.id ?? visual.typeName;
|
|
517
|
+
object.userData.demo3d = {
|
|
518
|
+
kind: "procedural-belt",
|
|
519
|
+
id: visual.id,
|
|
520
|
+
name: visual.displayName,
|
|
521
|
+
typeName: visual.typeName,
|
|
522
|
+
xmlPath: visual.xml.path,
|
|
523
|
+
loopHeight: belt.thickness,
|
|
524
|
+
startEdge: belt.startEdge,
|
|
525
|
+
endEdge: belt.endEdge,
|
|
526
|
+
connectorShaped: true
|
|
527
|
+
};
|
|
528
|
+
state.stats.meshes += 1;
|
|
529
|
+
state.stats.directVisuals += 1;
|
|
530
|
+
state.stats.proceduralBelts += 1;
|
|
531
|
+
return object;
|
|
532
|
+
}
|
|
533
|
+
function readProceduralInjectorBelt(visual) {
|
|
534
|
+
const properties = visual.properties;
|
|
535
|
+
if (!properties || isFalse(properties.textOf("CenterVisible"))) {
|
|
536
|
+
return undefined;
|
|
537
|
+
}
|
|
538
|
+
const startEdge = conveyorConnectorEdge(visual, "Start");
|
|
539
|
+
const serializedEndEdge = conveyorConnectorEdge(visual, "End");
|
|
540
|
+
if (!startEdge || !serializedEndEdge) {
|
|
541
|
+
return undefined;
|
|
542
|
+
}
|
|
543
|
+
const sameDirection = pointDistanceSquared(startEdge[0], serializedEndEdge[0]) +
|
|
544
|
+
pointDistanceSquared(startEdge[1], serializedEndEdge[1]);
|
|
545
|
+
const reversedDirection = pointDistanceSquared(startEdge[0], serializedEndEdge[1]) +
|
|
546
|
+
pointDistanceSquared(startEdge[1], serializedEndEdge[0]);
|
|
547
|
+
const endEdge = sameDirection <= reversedDirection
|
|
548
|
+
? serializedEndEdge
|
|
549
|
+
: [serializedEndEdge[1], serializedEndEdge[0]];
|
|
550
|
+
const beltDiameter = numberChildOptional(properties, "CenterDiameter") ??
|
|
551
|
+
numberChildOptional(properties, "BeltDiameter") ??
|
|
552
|
+
DEFAULT_BELT_DIAMETER;
|
|
553
|
+
const thickness = isTrue(properties.textOf("UseBeltCenterHeight"))
|
|
554
|
+
? numberChild(properties, "BeltCenterHeight", beltDiameter)
|
|
555
|
+
: beltDiameter;
|
|
556
|
+
if (thickness <= 0) {
|
|
557
|
+
return undefined;
|
|
558
|
+
}
|
|
559
|
+
const surfaceMaterial = firstMaterial(properties.child("SurfaceMaterial") ?? properties);
|
|
560
|
+
const sideMaterial = firstMaterial(properties.child("SurfaceSideMaterial") ?? properties) ?? surfaceMaterial;
|
|
561
|
+
return { startEdge, endEdge, thickness, surfaceMaterial, sideMaterial };
|
|
562
|
+
}
|
|
563
|
+
function conveyorConnectorEdge(visual, name) {
|
|
564
|
+
const connector = visual.xml.child("CN")?.children.find((candidate) => candidate.xsiType === "e3d:ConveyorConnector" && candidate.textOf("Name") === name);
|
|
565
|
+
if (!connector) {
|
|
566
|
+
return undefined;
|
|
567
|
+
}
|
|
568
|
+
const start = pipePoint(connector.textOf("Start"));
|
|
569
|
+
const end = pipePoint(connector.textOf("End"));
|
|
570
|
+
return start && end ? [start, end] : undefined;
|
|
571
|
+
}
|
|
572
|
+
function pipePoint(value) {
|
|
573
|
+
const values = parsePipeNumbers(value);
|
|
574
|
+
if (values.length === 0) {
|
|
575
|
+
return undefined;
|
|
576
|
+
}
|
|
577
|
+
return [values[0] ?? 0, values[1] ?? 0, -(values[2] ?? 0)];
|
|
578
|
+
}
|
|
579
|
+
function pointDistanceSquared(left, right) {
|
|
580
|
+
return ((left[0] - right[0]) ** 2 +
|
|
581
|
+
(left[1] - right[1]) ** 2 +
|
|
582
|
+
(left[2] - right[2]) ** 2);
|
|
583
|
+
}
|
|
584
|
+
function getProceduralInjectorBeltGeometry(belt, state) {
|
|
585
|
+
const key = `injector-belt:${[...belt.startEdge, ...belt.endEdge].map((point) => point.join(",")).join(";")}:${belt.thickness}`;
|
|
586
|
+
const cached = state.primitiveGeometryCache.get(key);
|
|
587
|
+
if (cached) {
|
|
588
|
+
return cached;
|
|
589
|
+
}
|
|
590
|
+
const surfacePositions = [];
|
|
591
|
+
const sidePositions = [];
|
|
592
|
+
const top = [belt.startEdge[0], belt.startEdge[1], belt.endEdge[1], belt.endEdge[0]];
|
|
593
|
+
const bottom = top.map((point) => [point[0], point[1] - belt.thickness, point[2]]);
|
|
594
|
+
const center = [
|
|
595
|
+
top.reduce((sum, point) => sum + point[0], 0) / top.length,
|
|
596
|
+
top.reduce((sum, point) => sum + point[1], 0) / top.length,
|
|
597
|
+
top.reduce((sum, point) => sum + point[2], 0) / top.length
|
|
598
|
+
];
|
|
599
|
+
appendOrientedQuad(surfacePositions, top[0], top[1], top[2], top[3], [0, 1, 0]);
|
|
600
|
+
appendOrientedQuad(surfacePositions, bottom[0], bottom[3], bottom[2], bottom[1], [0, -1, 0]);
|
|
601
|
+
appendInjectorBeltWall(surfacePositions, top[0], top[1], bottom[1], bottom[0], center);
|
|
602
|
+
appendInjectorBeltWall(sidePositions, top[1], top[2], bottom[2], bottom[1], center);
|
|
603
|
+
appendInjectorBeltWall(surfacePositions, top[2], top[3], bottom[3], bottom[2], center);
|
|
604
|
+
appendInjectorBeltWall(sidePositions, top[3], top[0], bottom[0], bottom[3], center);
|
|
605
|
+
const geometry = new state.three.BufferGeometry();
|
|
606
|
+
geometry.setAttribute("position", new state.three.Float32BufferAttribute([...surfacePositions, ...sidePositions], 3));
|
|
607
|
+
geometry.addGroup(0, surfacePositions.length / 3, 0);
|
|
608
|
+
geometry.addGroup(surfacePositions.length / 3, sidePositions.length / 3, 1);
|
|
609
|
+
geometry.computeVertexNormals();
|
|
610
|
+
geometry.computeBoundingBox();
|
|
611
|
+
geometry.computeBoundingSphere();
|
|
612
|
+
state.primitiveGeometryCache.set(key, geometry);
|
|
613
|
+
state.stats.geometries += 1;
|
|
614
|
+
return geometry;
|
|
615
|
+
}
|
|
616
|
+
function appendInjectorBeltWall(target, topA, topB, bottomB, bottomA, center) {
|
|
617
|
+
const middleX = (topA[0] + topB[0]) / 2;
|
|
618
|
+
const middleZ = (topA[2] + topB[2]) / 2;
|
|
619
|
+
let normalX = topB[2] - topA[2];
|
|
620
|
+
let normalZ = -(topB[0] - topA[0]);
|
|
621
|
+
if ((middleX - center[0]) * normalX + (middleZ - center[2]) * normalZ < 0) {
|
|
622
|
+
normalX = -normalX;
|
|
623
|
+
normalZ = -normalZ;
|
|
624
|
+
}
|
|
625
|
+
appendOrientedQuad(target, topA, topB, bottomB, bottomA, [normalX, 0, normalZ]);
|
|
626
|
+
}
|
|
447
627
|
function readProceduralBelt(visual, state) {
|
|
448
628
|
const properties = visual.properties;
|
|
449
629
|
if (!properties || isFalse(properties.textOf("CenterVisible"))) {
|
|
@@ -468,12 +648,12 @@ function readProceduralBelt(visual, state) {
|
|
|
468
648
|
const beltLength = numberChildOptional(properties, "BeltLength") ??
|
|
469
649
|
(serializedLength === undefined ? undefined : serializedLength - startPadding - endPadding);
|
|
470
650
|
const serializedWidth = numberChildOptional(properties, "Width");
|
|
471
|
-
const beltWidth = numberChildOptional(properties, "BeltWidth") ?? serializedWidth;
|
|
651
|
+
const beltWidth = numberChildOptional(properties, "BeltWidth") ?? serializedWidth ?? DEFAULT_BELT_WIDTH;
|
|
472
652
|
const effectiveLength = beltLength ?? 0;
|
|
473
|
-
const effectiveWidth =
|
|
653
|
+
const effectiveWidth = beltWidth - leftPadding - rightPadding;
|
|
474
654
|
const centerDiameter = numberChildOptional(properties, "CenterDiameter") ??
|
|
475
655
|
numberChildOptional(properties, "BeltDiameter") ??
|
|
476
|
-
|
|
656
|
+
DEFAULT_BELT_DIAMETER;
|
|
477
657
|
const useBeltCenterHeight = isTrue(properties.textOf("UseBeltCenterHeight"));
|
|
478
658
|
const loopHeight = useBeltCenterHeight
|
|
479
659
|
? numberChild(properties, "BeltCenterHeight", centerDiameter)
|
|
@@ -538,6 +718,131 @@ function getProceduralBeltGeometry(belt, state) {
|
|
|
538
718
|
state.stats.geometries += 1;
|
|
539
719
|
return geometry;
|
|
540
720
|
}
|
|
721
|
+
function createCurveBeltConveyorObject(visual, state) {
|
|
722
|
+
const belt = readProceduralCurveBelt(visual, state);
|
|
723
|
+
if (!belt) {
|
|
724
|
+
return undefined;
|
|
725
|
+
}
|
|
726
|
+
const geometry = getProceduralCurveBeltGeometry(belt, state);
|
|
727
|
+
const object = new state.three.Mesh(geometry, [
|
|
728
|
+
getMaterial(belt.surfaceMaterial, state),
|
|
729
|
+
getMaterial(belt.sideMaterial, state)
|
|
730
|
+
]);
|
|
731
|
+
object.name = visual.displayName ?? visual.id ?? visual.typeName;
|
|
732
|
+
object.userData.demo3d = {
|
|
733
|
+
kind: "procedural-belt",
|
|
734
|
+
id: visual.id,
|
|
735
|
+
name: visual.displayName,
|
|
736
|
+
typeName: visual.typeName,
|
|
737
|
+
xmlPath: visual.xml.path,
|
|
738
|
+
innerRadius: belt.innerRadius,
|
|
739
|
+
width: belt.width,
|
|
740
|
+
loopHeight: belt.thickness,
|
|
741
|
+
angle: belt.angle
|
|
742
|
+
};
|
|
743
|
+
state.stats.meshes += 1;
|
|
744
|
+
state.stats.directVisuals += 1;
|
|
745
|
+
state.stats.proceduralBelts += 1;
|
|
746
|
+
return object;
|
|
747
|
+
}
|
|
748
|
+
function readProceduralCurveBelt(visual, state) {
|
|
749
|
+
const properties = visual.properties;
|
|
750
|
+
if (!properties || isFalse(properties.textOf("CenterVisible"))) {
|
|
751
|
+
return undefined;
|
|
752
|
+
}
|
|
753
|
+
const centerProfile = properties.child("CenterProfile");
|
|
754
|
+
if (centerProfile && (centerProfile.children.length > 0 || centerProfile.text.length > 0)) {
|
|
755
|
+
warn(state, {
|
|
756
|
+
code: "DEMO3D_THREE_UNSUPPORTED_BELT_PROFILE",
|
|
757
|
+
message: "Curve belt conveyors with a custom CenterProfile are not supported.",
|
|
758
|
+
sourceId: visual.id,
|
|
759
|
+
sourceType: visual.typeName,
|
|
760
|
+
xmlPath: visual.xml.path
|
|
761
|
+
});
|
|
762
|
+
return undefined;
|
|
763
|
+
}
|
|
764
|
+
const innerRadius = numberChild(properties, "InnerRadius", 0.5);
|
|
765
|
+
const width = numberChildOptional(properties, "BeltWidth")
|
|
766
|
+
?? numberChildOptional(properties, "Width")
|
|
767
|
+
?? DEFAULT_BELT_WIDTH;
|
|
768
|
+
const beltDiameter = numberChildOptional(properties, "CenterDiameter") ??
|
|
769
|
+
numberChildOptional(properties, "BeltDiameter") ??
|
|
770
|
+
DEFAULT_BELT_DIAMETER;
|
|
771
|
+
const thickness = isTrue(properties.textOf("UseBeltCenterHeight"))
|
|
772
|
+
? numberChild(properties, "BeltCenterHeight", beltDiameter)
|
|
773
|
+
: beltDiameter;
|
|
774
|
+
const angle = degreesToRadians(numberChild(properties, "Angle", 90));
|
|
775
|
+
const stepAngle = Math.abs(degreesToRadians(numberChild(properties, "StepAngle", 15)));
|
|
776
|
+
if (innerRadius <= 0 || width <= 0 || thickness <= 0 || Math.abs(angle) <= 1e-6) {
|
|
777
|
+
warn(state, {
|
|
778
|
+
code: "DEMO3D_THREE_INVALID_BELT_DIMENSIONS",
|
|
779
|
+
message: `Curve belt conveyor dimensions must be positive and its angle non-zero (inner radius ${innerRadius}, width ${width}, height ${thickness}, angle ${numberChild(properties, "Angle", 90)}).`,
|
|
780
|
+
sourceId: visual.id,
|
|
781
|
+
sourceType: visual.typeName,
|
|
782
|
+
xmlPath: visual.xml.path
|
|
783
|
+
});
|
|
784
|
+
return undefined;
|
|
785
|
+
}
|
|
786
|
+
const surfaceMaterial = firstMaterial(properties.child("SurfaceMaterial") ?? properties);
|
|
787
|
+
const sideMaterial = firstMaterial(properties.child("SurfaceSideMaterial") ?? properties) ?? surfaceMaterial;
|
|
788
|
+
return {
|
|
789
|
+
innerRadius,
|
|
790
|
+
width,
|
|
791
|
+
thickness,
|
|
792
|
+
angle,
|
|
793
|
+
stepAngle: stepAngle > 1e-6 ? stepAngle : Math.PI / 12,
|
|
794
|
+
surfaceMaterial,
|
|
795
|
+
sideMaterial
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
function getProceduralCurveBeltGeometry(belt, state) {
|
|
799
|
+
const key = `curve-belt:${belt.innerRadius}:${belt.width}:${belt.thickness}:${belt.angle}:${belt.stepAngle}`;
|
|
800
|
+
const cached = state.primitiveGeometryCache.get(key);
|
|
801
|
+
if (cached) {
|
|
802
|
+
return cached;
|
|
803
|
+
}
|
|
804
|
+
const surfacePositions = [];
|
|
805
|
+
const sidePositions = [];
|
|
806
|
+
const outerRadius = belt.innerRadius + belt.width;
|
|
807
|
+
const segments = Math.max(4, Math.ceil(Math.abs(belt.angle) / belt.stepAngle));
|
|
808
|
+
const point = (radius, y, theta) => [
|
|
809
|
+
radius * Math.cos(theta),
|
|
810
|
+
y,
|
|
811
|
+
radius * Math.sin(theta)
|
|
812
|
+
];
|
|
813
|
+
for (let segment = 0; segment < segments; segment += 1) {
|
|
814
|
+
const theta0 = belt.angle * (segment / segments);
|
|
815
|
+
const theta1 = belt.angle * ((segment + 1) / segments);
|
|
816
|
+
const middle = (theta0 + theta1) / 2;
|
|
817
|
+
const innerTop0 = point(belt.innerRadius, 0, theta0);
|
|
818
|
+
const innerTop1 = point(belt.innerRadius, 0, theta1);
|
|
819
|
+
const outerTop0 = point(outerRadius, 0, theta0);
|
|
820
|
+
const outerTop1 = point(outerRadius, 0, theta1);
|
|
821
|
+
const innerBottom0 = point(belt.innerRadius, -belt.thickness, theta0);
|
|
822
|
+
const innerBottom1 = point(belt.innerRadius, -belt.thickness, theta1);
|
|
823
|
+
const outerBottom0 = point(outerRadius, -belt.thickness, theta0);
|
|
824
|
+
const outerBottom1 = point(outerRadius, -belt.thickness, theta1);
|
|
825
|
+
appendOrientedQuad(surfacePositions, innerTop0, outerTop0, outerTop1, innerTop1, [0, 1, 0]);
|
|
826
|
+
appendOrientedQuad(surfacePositions, innerBottom0, innerBottom1, outerBottom1, outerBottom0, [0, -1, 0]);
|
|
827
|
+
appendOrientedQuad(sidePositions, outerBottom0, outerBottom1, outerTop1, outerTop0, [Math.cos(middle), 0, Math.sin(middle)]);
|
|
828
|
+
appendOrientedQuad(sidePositions, innerBottom0, innerTop0, innerTop1, innerBottom1, [-Math.cos(middle), 0, -Math.sin(middle)]);
|
|
829
|
+
}
|
|
830
|
+
const direction = Math.sign(belt.angle) || 1;
|
|
831
|
+
const start = 0;
|
|
832
|
+
const end = belt.angle;
|
|
833
|
+
appendOrientedQuad(surfacePositions, point(belt.innerRadius, -belt.thickness, start), point(outerRadius, -belt.thickness, start), point(outerRadius, 0, start), point(belt.innerRadius, 0, start), [direction * Math.sin(start), 0, -direction * Math.cos(start)]);
|
|
834
|
+
appendOrientedQuad(surfacePositions, point(belt.innerRadius, -belt.thickness, end), point(belt.innerRadius, 0, end), point(outerRadius, 0, end), point(outerRadius, -belt.thickness, end), [-direction * Math.sin(end), 0, direction * Math.cos(end)]);
|
|
835
|
+
const geometry = new state.three.BufferGeometry();
|
|
836
|
+
geometry.setAttribute("position", new state.three.Float32BufferAttribute([...surfacePositions, ...sidePositions], 3));
|
|
837
|
+
geometry.addGroup(0, surfacePositions.length / 3, 0);
|
|
838
|
+
geometry.addGroup(surfacePositions.length / 3, sidePositions.length / 3, 1);
|
|
839
|
+
geometry.computeVertexNormals();
|
|
840
|
+
geometry.computeBoundingBox();
|
|
841
|
+
geometry.computeBoundingSphere();
|
|
842
|
+
state.primitiveGeometryCache.set(key, geometry);
|
|
843
|
+
state.stats.geometries += 1;
|
|
844
|
+
return geometry;
|
|
845
|
+
}
|
|
541
846
|
function createRectangularBeltGeometry(belt, state) {
|
|
542
847
|
const geometry = new state.three.BoxGeometry(belt.length, belt.thickness, belt.width);
|
|
543
848
|
geometry.translate(belt.xOffset + belt.length / 2, -belt.thickness / 2, belt.zOffset);
|
|
@@ -582,6 +887,117 @@ function createRoundedBeltGeometry(belt, state) {
|
|
|
582
887
|
}
|
|
583
888
|
return geometry;
|
|
584
889
|
}
|
|
890
|
+
function createRackObject(visual, state) {
|
|
891
|
+
const properties = visual.properties;
|
|
892
|
+
if (!properties) {
|
|
893
|
+
return undefined;
|
|
894
|
+
}
|
|
895
|
+
const frameHeight = numberChild(properties, "FrameHeight", 0);
|
|
896
|
+
const uprightWidth = numberChild(properties, "UprightWidth", 0.05);
|
|
897
|
+
const uprightDepth = numberChild(properties, "UprightDepth", 0.05);
|
|
898
|
+
const frameDepth = numberChildOptional(properties, "FrameDepth") ?? numberChild(properties, "BayDepth", 0);
|
|
899
|
+
const strutWidth = numberChild(properties, "StrutWidth", 0.05);
|
|
900
|
+
if (frameHeight <= 0 || frameDepth <= 0 || uprightWidth <= 0 || uprightDepth <= 0 || strutWidth <= 0) {
|
|
901
|
+
warn(state, {
|
|
902
|
+
code: "DEMO3D_THREE_INVALID_RACK_DIMENSIONS",
|
|
903
|
+
message: `Rack dimensions must be positive (height ${frameHeight}, depth ${frameDepth}, upright ${uprightWidth} x ${uprightDepth}, strut ${strutWidth}).`,
|
|
904
|
+
sourceId: visual.id,
|
|
905
|
+
sourceType: visual.typeName,
|
|
906
|
+
xmlPath: visual.xml.path
|
|
907
|
+
});
|
|
908
|
+
return undefined;
|
|
909
|
+
}
|
|
910
|
+
const numBays = Math.max(1, Math.round(numberChild(properties, "NumBays", 1)));
|
|
911
|
+
const bayWidth = Math.max(0, numberChild(properties, "BayWidth", 0));
|
|
912
|
+
const framePitch = bayWidth + uprightWidth + Math.max(0, numberChild(properties, "MinFrameGap", 0));
|
|
913
|
+
const framePositions = [0];
|
|
914
|
+
if (!isFalse(properties.child("MiddleFrames")?.textOf("Visible"))) {
|
|
915
|
+
for (let bay = 1; bay < numBays; bay += 1) {
|
|
916
|
+
framePositions.push(bay * framePitch);
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
if (!isFalse(properties.child("LastFrame")?.textOf("Visible")) && framePitch > 0) {
|
|
920
|
+
framePositions.push(numBays * framePitch);
|
|
921
|
+
}
|
|
922
|
+
const initialStrutHeight = clamp(numberChild(properties, "InitialStrutHeight", strutWidth / 2), 0, frameHeight);
|
|
923
|
+
const strutSpanHeight = numberChild(properties, "StrutSpanHeight", frameHeight);
|
|
924
|
+
const strutHeights = [initialStrutHeight];
|
|
925
|
+
if (strutSpanHeight > 0) {
|
|
926
|
+
for (let height = initialStrutHeight + strutSpanHeight; height < frameHeight - 1e-6; height += strutSpanHeight) {
|
|
927
|
+
strutHeights.push(height);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
const extensionHeight = clamp(frameHeight - Math.max(0, numberChild(properties, "ExtensionStrutOffset", strutWidth / 2)), 0, frameHeight);
|
|
931
|
+
if (!strutHeights.some((height) => Math.abs(height - extensionHeight) < 1e-6)) {
|
|
932
|
+
strutHeights.push(extensionHeight);
|
|
933
|
+
}
|
|
934
|
+
strutHeights.sort((left, right) => left - right);
|
|
935
|
+
const uprightMaterial = getColorMaterial(demo3dColorValue(properties.child("UprightColor"), 0xffd3d3d3 | 0), state);
|
|
936
|
+
const strutMaterial = getColorMaterial(demo3dColorValue(properties.child("StrutColor"), 0xffd3d3d3 | 0), state);
|
|
937
|
+
const uprightGeometry = cachedBoxGeometry(uprightWidth, frameHeight, uprightDepth, state);
|
|
938
|
+
const strutGeometry = cachedBoxGeometry(strutWidth, strutWidth, frameDepth, state);
|
|
939
|
+
const group = new state.three.Group();
|
|
940
|
+
group.name = `${visual.displayName ?? visual.id ?? "Rack"} generated geometry`;
|
|
941
|
+
for (const frameX of framePositions) {
|
|
942
|
+
for (const z of [-frameDepth / 2, frameDepth / 2]) {
|
|
943
|
+
const upright = new state.three.Mesh(uprightGeometry, uprightMaterial);
|
|
944
|
+
upright.name = "Rack upright";
|
|
945
|
+
upright.position.set(frameX, frameHeight / 2, z);
|
|
946
|
+
upright.userData.demo3d = { kind: "procedural-rack-upright", frameX };
|
|
947
|
+
group.add(upright);
|
|
948
|
+
state.stats.meshes += 1;
|
|
949
|
+
}
|
|
950
|
+
for (const height of strutHeights) {
|
|
951
|
+
const strut = new state.three.Mesh(strutGeometry, strutMaterial);
|
|
952
|
+
strut.name = "Rack strut";
|
|
953
|
+
strut.position.set(frameX, height, 0);
|
|
954
|
+
strut.userData.demo3d = { kind: "procedural-rack-strut", frameX, height };
|
|
955
|
+
group.add(strut);
|
|
956
|
+
state.stats.meshes += 1;
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
group.userData.demo3d = {
|
|
960
|
+
kind: "procedural-rack",
|
|
961
|
+
id: visual.id,
|
|
962
|
+
name: visual.displayName,
|
|
963
|
+
typeName: visual.typeName,
|
|
964
|
+
xmlPath: visual.xml.path,
|
|
965
|
+
frameHeight,
|
|
966
|
+
frameDepth,
|
|
967
|
+
framePositions,
|
|
968
|
+
strutHeights
|
|
969
|
+
};
|
|
970
|
+
state.stats.groups += 1;
|
|
971
|
+
state.stats.directVisuals += 1;
|
|
972
|
+
state.stats.proceduralRacks += 1;
|
|
973
|
+
return group;
|
|
974
|
+
}
|
|
975
|
+
function demo3dColorValue(container, fallback) {
|
|
976
|
+
const numeric = container?.text.trim();
|
|
977
|
+
if (numeric) {
|
|
978
|
+
const parsed = Number(numeric);
|
|
979
|
+
if (Number.isFinite(parsed)) {
|
|
980
|
+
return parsed;
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
switch (container?.textOf("name")?.trim().toLowerCase()) {
|
|
984
|
+
case "black":
|
|
985
|
+
return 0xff000000 | 0;
|
|
986
|
+
case "darkgray":
|
|
987
|
+
case "darkgrey":
|
|
988
|
+
return 0xffa9a9a9 | 0;
|
|
989
|
+
case "gray":
|
|
990
|
+
case "grey":
|
|
991
|
+
return 0xff808080 | 0;
|
|
992
|
+
case "lightgray":
|
|
993
|
+
case "lightgrey":
|
|
994
|
+
return 0xffd3d3d3 | 0;
|
|
995
|
+
case "white":
|
|
996
|
+
return 0xffffffff | 0;
|
|
997
|
+
default:
|
|
998
|
+
return fallback;
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
585
1001
|
function createSupportStandObject(visual, parentVisual, state) {
|
|
586
1002
|
const stand = visual instanceof Demo3DSupportStand
|
|
587
1003
|
? visual
|
|
@@ -591,7 +1007,13 @@ function createSupportStandObject(visual, parentVisual, state) {
|
|
|
591
1007
|
const footProfile = properties?.footProfile;
|
|
592
1008
|
const floorPlateProfile = properties?.floorPlateProfile;
|
|
593
1009
|
const crossBraceProfile = properties?.crossBraceProfile;
|
|
594
|
-
if (!properties
|
|
1010
|
+
if (!properties) {
|
|
1011
|
+
return undefined;
|
|
1012
|
+
}
|
|
1013
|
+
if (!legProfile && !crossBraceProfile) {
|
|
1014
|
+
return createDefaultSupportStandObject(visual, parentVisual, properties, state);
|
|
1015
|
+
}
|
|
1016
|
+
if (!legProfile || !crossBraceProfile) {
|
|
595
1017
|
warn(state, {
|
|
596
1018
|
code: "DEMO3D_THREE_UNSUPPORTED_SUPPORT_STAND_PROFILE",
|
|
597
1019
|
message: "Support stand is missing its leg or cross-brace extrusion profile.",
|
|
@@ -659,6 +1081,84 @@ function createSupportStandObject(visual, parentVisual, state) {
|
|
|
659
1081
|
state.stats.proceduralSupportStands += 1;
|
|
660
1082
|
return group;
|
|
661
1083
|
}
|
|
1084
|
+
const DEFAULT_CONVEYOR_HEIGHT = 0.83;
|
|
1085
|
+
const DEFAULT_SUPPORT_LEG_SIZE = 0.04;
|
|
1086
|
+
const DEFAULT_SUPPORT_FOOT_SIZE = 0.06;
|
|
1087
|
+
const DEFAULT_SUPPORT_FLOOR_PLATE_SIZE = 0.1;
|
|
1088
|
+
function createDefaultSupportStandObject(visual, parentVisual, properties, state) {
|
|
1089
|
+
const parentWidth = numberChildOptional(parentVisual?.properties, "RollerWidth") ??
|
|
1090
|
+
numberChildOptional(parentVisual?.properties, "BeltWidth") ??
|
|
1091
|
+
numberChildOptional(parentVisual?.properties, "Width") ??
|
|
1092
|
+
DEFAULT_BELT_WIDTH;
|
|
1093
|
+
const conveyorOffset = properties.conveyorOffset;
|
|
1094
|
+
const sideExtension = Math.abs(conveyorOffset[2] ?? 0);
|
|
1095
|
+
const span = Math.max(parentWidth + sideExtension * 2, 0.1);
|
|
1096
|
+
const top = conveyorOffset[1] ?? 0;
|
|
1097
|
+
const x = conveyorOffset[0] ?? 0;
|
|
1098
|
+
const floorY = numberChild(properties.xml, "FloorY", 0);
|
|
1099
|
+
const explicitStandHeight = numberChildOptional(properties.xml, "StandHeight") ??
|
|
1100
|
+
numberChildOptional(properties.xml, "SupportHeight") ??
|
|
1101
|
+
numberChildOptional(properties.xml, "Height");
|
|
1102
|
+
const explicitLegHeight = numberChildOptional(properties.xml, "LegHeight");
|
|
1103
|
+
const conveyorHeight = numberChildOptional(parentVisual?.properties, "ConveyorHeight") ?? DEFAULT_CONVEYOR_HEIGHT;
|
|
1104
|
+
const componentHeight = explicitLegHeight === undefined
|
|
1105
|
+
? undefined
|
|
1106
|
+
: explicitLegHeight + properties.footHeight + properties.floorPlateHeight;
|
|
1107
|
+
const supportHeight = Math.max(explicitStandHeight ?? componentHeight ?? conveyorHeight + top - floorY, properties.footHeight + properties.floorPlateHeight + 0.001);
|
|
1108
|
+
const bottom = top - supportHeight;
|
|
1109
|
+
const floorPlateHeight = clamp(properties.floorPlateHeight, 0, supportHeight);
|
|
1110
|
+
const footHeight = clamp(properties.footHeight, 0, supportHeight - floorPlateHeight);
|
|
1111
|
+
const legHeight = Math.max(supportHeight - floorPlateHeight - footHeight, 0.001);
|
|
1112
|
+
const braces = [...properties.crossBraceHeights]
|
|
1113
|
+
.filter((height) => height > 0 && height < supportHeight)
|
|
1114
|
+
.sort((left, right) => left - right);
|
|
1115
|
+
const group = new state.three.Group();
|
|
1116
|
+
group.name = `${visual.displayName ?? visual.id ?? "Support stand"} default geometry`;
|
|
1117
|
+
const legMaterial = supportComponentMaterial(visual.properties?.child("LegMaterial"), properties.legMaterial, state);
|
|
1118
|
+
const footMaterial = supportComponentMaterial(visual.properties?.child("FootMaterial"), properties.footMaterial, state);
|
|
1119
|
+
const floorPlateMaterial = supportComponentMaterial(visual.properties?.child("FloorPlateMaterial"), properties.floorPlateMaterial, state);
|
|
1120
|
+
const braceMaterial = supportComponentMaterial(visual.properties?.child("CrossBraceMaterial"), properties.crossBraceMaterial, state);
|
|
1121
|
+
for (const z of [-span / 2, span / 2]) {
|
|
1122
|
+
addDefaultSupportBox(group, "leg", DEFAULT_SUPPORT_LEG_SIZE, legHeight, DEFAULT_SUPPORT_LEG_SIZE, [x, top - legHeight / 2, z], legMaterial, state);
|
|
1123
|
+
if (footHeight > 0) {
|
|
1124
|
+
addDefaultSupportBox(group, "foot", DEFAULT_SUPPORT_FOOT_SIZE, footHeight, DEFAULT_SUPPORT_FOOT_SIZE, [x, bottom + floorPlateHeight + footHeight / 2, z], footMaterial, state);
|
|
1125
|
+
}
|
|
1126
|
+
if (floorPlateHeight > 0) {
|
|
1127
|
+
addDefaultSupportBox(group, "floor plate", DEFAULT_SUPPORT_FLOOR_PLATE_SIZE, floorPlateHeight, DEFAULT_SUPPORT_FLOOR_PLATE_SIZE, [x, bottom + floorPlateHeight / 2, z], floorPlateMaterial, state);
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
for (const height of braces) {
|
|
1131
|
+
addDefaultSupportBox(group, "cross brace", DEFAULT_SUPPORT_LEG_SIZE, DEFAULT_SUPPORT_LEG_SIZE, span, [x, bottom + height, 0], braceMaterial, state);
|
|
1132
|
+
}
|
|
1133
|
+
addDefaultSupportBox(group, "top brace", DEFAULT_SUPPORT_LEG_SIZE, DEFAULT_SUPPORT_LEG_SIZE, span, [x, top - DEFAULT_SUPPORT_LEG_SIZE / 2, 0], braceMaterial, state);
|
|
1134
|
+
group.userData.demo3d = {
|
|
1135
|
+
kind: "procedural-support-stand",
|
|
1136
|
+
id: visual.id,
|
|
1137
|
+
name: visual.displayName,
|
|
1138
|
+
typeName: visual.typeName,
|
|
1139
|
+
xmlPath: visual.xml.path,
|
|
1140
|
+
supportHeight,
|
|
1141
|
+
span,
|
|
1142
|
+
braceHeights: braces,
|
|
1143
|
+
approximate: true,
|
|
1144
|
+
defaultProfiles: true
|
|
1145
|
+
};
|
|
1146
|
+
state.stats.groups += 1;
|
|
1147
|
+
state.stats.directVisuals += 1;
|
|
1148
|
+
state.stats.proceduralSupportStands += 1;
|
|
1149
|
+
return group;
|
|
1150
|
+
}
|
|
1151
|
+
function supportComponentMaterial(container, fallbackColor, state) {
|
|
1152
|
+
return materialFromContainer(container, state) ?? getColorMaterial(fallbackColor, state);
|
|
1153
|
+
}
|
|
1154
|
+
function addDefaultSupportBox(group, component, width, height, depth, position, material, state) {
|
|
1155
|
+
const mesh = new state.three.Mesh(cachedBoxGeometry(width, height, depth, state), material);
|
|
1156
|
+
mesh.name = `Default support ${component}`;
|
|
1157
|
+
mesh.position.set(position[0], position[1], position[2]);
|
|
1158
|
+
mesh.userData.demo3d = { kind: "support-stand-component", component, approximate: true };
|
|
1159
|
+
group.add(mesh);
|
|
1160
|
+
state.stats.meshes += 1;
|
|
1161
|
+
}
|
|
662
1162
|
function addSupportProfileMeshes(group, profile, axis, length, position, fallbackColor, component, state) {
|
|
663
1163
|
profile.polygons.forEach((polygon, index) => {
|
|
664
1164
|
if (polygon.points.length < 3) {
|
|
@@ -937,6 +1437,25 @@ function addProceduralRollers(group, visual, state) {
|
|
|
937
1437
|
const material = materialFromContainer(properties.child("SurfaceMaterial") ?? properties, state)
|
|
938
1438
|
?? getColorMaterial(numberChildOptional(properties, "RollerColor"), state);
|
|
939
1439
|
const geometry = cachedRollerGeometry(radius, width, state);
|
|
1440
|
+
if (visual.typeName === "e3d:DiverterRollerConveyor") {
|
|
1441
|
+
const length = numberChild(properties, "Length", 1);
|
|
1442
|
+
const pitch = Math.max(numberChild(properties, "RollerPitch", diameter * 1.2), diameter);
|
|
1443
|
+
const count = Math.max(1, Math.round(numberChildOptional(properties, "RollerCount") ?? length / pitch));
|
|
1444
|
+
const across = Math.max(1, Math.round(numberChild(properties, "NumRollersAcrossWidth", 1)));
|
|
1445
|
+
const totalWidth = numberChild(properties, "Width", width);
|
|
1446
|
+
for (let index = 0; index < count; index += 1) {
|
|
1447
|
+
for (let acrossIndex = 0; acrossIndex < across; acrossIndex += 1) {
|
|
1448
|
+
const roller = new state.three.Mesh(geometry, material);
|
|
1449
|
+
roller.name = `Generated divert roller ${index + 1}.${acrossIndex + 1}`;
|
|
1450
|
+
roller.position.set(((index + 0.5) / count) * length, 0, ((acrossIndex + 0.5) / across - 0.5) * totalWidth);
|
|
1451
|
+
roller.userData.demo3d = { kind: "procedural-roller", index, acrossIndex };
|
|
1452
|
+
group.add(roller);
|
|
1453
|
+
state.stats.meshes += 1;
|
|
1454
|
+
state.stats.proceduralRollers += 1;
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
return;
|
|
1458
|
+
}
|
|
940
1459
|
if (visual.typeName === "e3d:CurveRollerConveyor") {
|
|
941
1460
|
const count = Math.max(1, Math.round(numberChild(properties, "RollerCount", 1)));
|
|
942
1461
|
const innerRadius = numberChild(properties, "InnerRadius", 0.5);
|
|
@@ -1050,6 +1569,7 @@ function cachedBoxGeometry(width, height, depth, state) {
|
|
|
1050
1569
|
function isRollerConveyorType(typeName) {
|
|
1051
1570
|
return (typeName === "e3d:StraightRollerConveyor" ||
|
|
1052
1571
|
typeName === "e3d:CurveRollerConveyor" ||
|
|
1572
|
+
typeName === "e3d:DiverterRollerConveyor" ||
|
|
1053
1573
|
typeName === "e3d:InjectorRollerConveyor");
|
|
1054
1574
|
}
|
|
1055
1575
|
function createImportedImageObject(visual, state) {
|
|
@@ -1107,6 +1627,7 @@ function getDirectVisualGeometry(visual, state) {
|
|
|
1107
1627
|
properties.textOf("Length") ?? "",
|
|
1108
1628
|
properties.textOf("Radius") ?? "",
|
|
1109
1629
|
properties.textOf("StartAngle") ?? "",
|
|
1630
|
+
properties.textOf("Thickness") ?? "",
|
|
1110
1631
|
properties.textOf("Width") ?? ""
|
|
1111
1632
|
].join("|")}`;
|
|
1112
1633
|
const cached = state.primitiveGeometryCache.get(key);
|
|
@@ -1114,9 +1635,12 @@ function getDirectVisualGeometry(visual, state) {
|
|
|
1114
1635
|
return cached;
|
|
1115
1636
|
}
|
|
1116
1637
|
let geometry;
|
|
1117
|
-
if (visual.typeName === "e3d:BoxVisual"
|
|
1638
|
+
if (visual.typeName === "e3d:BoxVisual") {
|
|
1118
1639
|
geometry = new state.three.BoxGeometry(numberChild(properties, "Width", 1), numberChild(properties, "Height", 1), numberChild(properties, "Depth", 1));
|
|
1119
1640
|
}
|
|
1641
|
+
else if (visual.typeName === "e3d:BoxTubeVisual") {
|
|
1642
|
+
geometry = createBoxTubeGeometry(numberChild(properties, "Width", 0.25), numberChild(properties, "Height", 1), numberChild(properties, "Depth", 0.25), numberChild(properties, "Thickness", 0.015), state);
|
|
1643
|
+
}
|
|
1120
1644
|
else if (visual.typeName === "e3d:CylinderVisual") {
|
|
1121
1645
|
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)));
|
|
1122
1646
|
}
|
|
@@ -1133,6 +1657,37 @@ function getDirectVisualGeometry(visual, state) {
|
|
|
1133
1657
|
state.stats.geometries += 1;
|
|
1134
1658
|
return geometry;
|
|
1135
1659
|
}
|
|
1660
|
+
function createBoxTubeGeometry(width, height, depth, thickness, state) {
|
|
1661
|
+
const halfWidth = Math.max(width, 0) / 2;
|
|
1662
|
+
const halfDepth = Math.max(depth, 0) / 2;
|
|
1663
|
+
const edge = clamp(thickness, 0, Math.min(halfWidth, halfDepth));
|
|
1664
|
+
const shape = new state.three.Shape();
|
|
1665
|
+
shape.moveTo(-halfWidth, -halfDepth);
|
|
1666
|
+
shape.lineTo(-halfWidth, halfDepth);
|
|
1667
|
+
shape.lineTo(halfWidth, halfDepth);
|
|
1668
|
+
shape.lineTo(halfWidth, -halfDepth);
|
|
1669
|
+
shape.closePath();
|
|
1670
|
+
const innerHalfWidth = halfWidth - edge;
|
|
1671
|
+
const innerHalfDepth = halfDepth - edge;
|
|
1672
|
+
if (innerHalfWidth > 0 && innerHalfDepth > 0) {
|
|
1673
|
+
const hole = new state.three.Path();
|
|
1674
|
+
hole.moveTo(-innerHalfWidth, -innerHalfDepth);
|
|
1675
|
+
hole.lineTo(innerHalfWidth, -innerHalfDepth);
|
|
1676
|
+
hole.lineTo(innerHalfWidth, innerHalfDepth);
|
|
1677
|
+
hole.lineTo(-innerHalfWidth, innerHalfDepth);
|
|
1678
|
+
hole.closePath();
|
|
1679
|
+
shape.holes.push(hole);
|
|
1680
|
+
}
|
|
1681
|
+
const geometry = new state.three.ExtrudeGeometry(shape, {
|
|
1682
|
+
depth: Math.max(height, 0),
|
|
1683
|
+
steps: 1,
|
|
1684
|
+
bevelEnabled: false
|
|
1685
|
+
});
|
|
1686
|
+
// ExtrudeGeometry uses Z for its extrusion. Demo3D box tubes extrude along Y.
|
|
1687
|
+
geometry.rotateX(-Math.PI / 2);
|
|
1688
|
+
geometry.translate(0, -Math.max(height, 0) / 2, 0);
|
|
1689
|
+
return geometry;
|
|
1690
|
+
}
|
|
1136
1691
|
function createWedgeGeometry(width, height, length, state) {
|
|
1137
1692
|
const x = width / 2;
|
|
1138
1693
|
const y = height / 2;
|
|
@@ -1199,8 +1754,12 @@ function getDrawingBlockGeometry(blockId, state) {
|
|
|
1199
1754
|
state.stats.geometries += 1;
|
|
1200
1755
|
return geometry;
|
|
1201
1756
|
}
|
|
1202
|
-
function createMeshObject(meshId, source,
|
|
1757
|
+
function createMeshObject(meshId, source, materialSelection, state) {
|
|
1203
1758
|
const mesh = state.meshById.get(meshId);
|
|
1759
|
+
const materials = Array.isArray(materialSelection)
|
|
1760
|
+
? materialSelection
|
|
1761
|
+
: materialSelection ? [materialSelection] : [];
|
|
1762
|
+
const primaryMaterial = materials[0];
|
|
1204
1763
|
const sourceId = source instanceof Demo3DVisual ? source.id : source.textOf("Id");
|
|
1205
1764
|
const sourceType = source instanceof Demo3DVisual ? source.typeName : source.xsiType ?? source.localName;
|
|
1206
1765
|
const xmlPath = source instanceof Demo3DVisual ? source.xml.path : source.path;
|
|
@@ -1213,20 +1772,24 @@ function createMeshObject(meshId, source, material, state) {
|
|
|
1213
1772
|
xmlPath
|
|
1214
1773
|
});
|
|
1215
1774
|
return state.options.showPlaceholders === true
|
|
1216
|
-
? createPlaceholderMesh(sourceId ?? meshId, meshId,
|
|
1775
|
+
? createPlaceholderMesh(sourceId ?? meshId, meshId, primaryMaterial, state)
|
|
1217
1776
|
: undefined;
|
|
1218
1777
|
}
|
|
1219
1778
|
const geometry = getGeometry(mesh, state);
|
|
1220
1779
|
if (!geometry) {
|
|
1221
1780
|
return state.options.showPlaceholders === true
|
|
1222
|
-
? createPlaceholderMesh(sourceId ?? meshId, meshId,
|
|
1781
|
+
? createPlaceholderMesh(sourceId ?? meshId, meshId, primaryMaterial, state)
|
|
1223
1782
|
: undefined;
|
|
1224
1783
|
}
|
|
1225
|
-
const
|
|
1784
|
+
const threeMaterial = materials.length > 1
|
|
1785
|
+
? materials.map((material) => getMaterial(material, state))
|
|
1786
|
+
: getMaterial(primaryMaterial, state);
|
|
1787
|
+
const object = new state.three.Mesh(geometry, threeMaterial);
|
|
1226
1788
|
object.name = source instanceof Demo3DVisual ? source.displayName ?? meshId : meshId;
|
|
1227
1789
|
object.userData.demo3d = {
|
|
1228
1790
|
kind: "mesh",
|
|
1229
1791
|
meshId,
|
|
1792
|
+
materialSlots: materials.length,
|
|
1230
1793
|
sourceId,
|
|
1231
1794
|
sourceType,
|
|
1232
1795
|
xmlPath
|
|
@@ -1720,8 +2283,7 @@ function createSerializedRenderableGroup(state) {
|
|
|
1720
2283
|
}
|
|
1721
2284
|
const meshIds = findMeshReferenceIds(renderable);
|
|
1722
2285
|
for (const meshId of meshIds) {
|
|
1723
|
-
const
|
|
1724
|
-
const mesh = createMeshObject(meshId, renderable, material, state);
|
|
2286
|
+
const mesh = createMeshObject(meshId, renderable, renderableMaterials(renderable), state);
|
|
1725
2287
|
if (!mesh) {
|
|
1726
2288
|
continue;
|
|
1727
2289
|
}
|
|
@@ -1921,6 +2483,23 @@ function findVisualMeshReferenceIds(root) {
|
|
|
1921
2483
|
}
|
|
1922
2484
|
return [...ids];
|
|
1923
2485
|
}
|
|
2486
|
+
function renderableMaterials(renderable) {
|
|
2487
|
+
const materials = [];
|
|
2488
|
+
const materialProperties = renderable.child("MaterialProperties");
|
|
2489
|
+
for (const entry of materialProperties?.children ?? []) {
|
|
2490
|
+
const material = firstMaterial(entry);
|
|
2491
|
+
if (material) {
|
|
2492
|
+
materials.push(material);
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
2495
|
+
if (materials.length === 0) {
|
|
2496
|
+
const material = firstMaterial(renderable);
|
|
2497
|
+
if (material) {
|
|
2498
|
+
materials.push(material);
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
return materials;
|
|
2502
|
+
}
|
|
1924
2503
|
function firstMaterial(root) {
|
|
1925
2504
|
let found;
|
|
1926
2505
|
visit(root, (node) => {
|
|
@@ -2004,7 +2583,9 @@ function applyDemo3DTransform(object, transformText) {
|
|
|
2004
2583
|
return;
|
|
2005
2584
|
}
|
|
2006
2585
|
object.position.set(values[0] ?? 0, values[1] ?? 0, -(values[2] ?? 0));
|
|
2007
|
-
|
|
2586
|
+
// Demo3D rotation triples are pitch (X), yaw (Y), and roll (Z). Its
|
|
2587
|
+
// RotationYawPitchRoll matrix corresponds to Three's intrinsic YXZ order.
|
|
2588
|
+
object.rotation.set(-(values[3] ?? 0), -(values[4] ?? 0), values[5] ?? 0, "YXZ");
|
|
2008
2589
|
}
|
|
2009
2590
|
function convertTriangleGeometryToThreeCoordinates(geometry) {
|
|
2010
2591
|
geometry.scale(1, 1, -1);
|