@jfk-solutions/demo3d-file-format 1.4.0 → 1.5.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 +14 -4
- package/dist/model.js +5 -0
- package/dist/model.js.map +1 -1
- package/dist/three/index.d.ts +2 -0
- package/dist/three/index.d.ts.map +1 -1
- package/dist/three/index.js +504 -7
- package/dist/three/index.js.map +1 -1
- package/package.json +1 -1
package/dist/three/index.js
CHANGED
|
@@ -151,6 +151,7 @@ function createState(three, project, options, buffers) {
|
|
|
151
151
|
lines: 0,
|
|
152
152
|
directVisuals: 0,
|
|
153
153
|
proceduralBelts: 0,
|
|
154
|
+
proceduralRacks: 0,
|
|
154
155
|
proceduralSupportStands: 0,
|
|
155
156
|
proceduralConveyorSides: 0,
|
|
156
157
|
proceduralPhotoEyes: 0,
|
|
@@ -380,9 +381,16 @@ function createDirectVisualObject(visual, state, parentVisual) {
|
|
|
380
381
|
if (visual.typeName === "e3d:ImportedImageVisual") {
|
|
381
382
|
return createImportedImageObject(visual, state);
|
|
382
383
|
}
|
|
383
|
-
if (visual.typeName === "e3d:StraightBeltConveyor"
|
|
384
|
+
if (visual.typeName === "e3d:StraightBeltConveyor" ||
|
|
385
|
+
visual.typeName === "e3d:CurveBeltConveyor" ||
|
|
386
|
+
visual.typeName === "e3d:InjectorBeltConveyor") {
|
|
384
387
|
return state.options.renderProceduralBelts === true
|
|
385
|
-
?
|
|
388
|
+
? createBeltConveyorObject(visual, state)
|
|
389
|
+
: undefined;
|
|
390
|
+
}
|
|
391
|
+
if (visual.typeName === "e3d:RackVisual") {
|
|
392
|
+
return state.options.renderProceduralRacks === true
|
|
393
|
+
? createRackObject(visual, state)
|
|
386
394
|
: undefined;
|
|
387
395
|
}
|
|
388
396
|
if (visual.typeName === "e3d:SupportStand") {
|
|
@@ -415,7 +423,16 @@ function createDirectVisualObject(visual, state, parentVisual) {
|
|
|
415
423
|
state.stats.directVisuals += 1;
|
|
416
424
|
return object;
|
|
417
425
|
}
|
|
418
|
-
function
|
|
426
|
+
function createBeltConveyorObject(visual, state) {
|
|
427
|
+
if (visual.typeName === "e3d:CurveBeltConveyor") {
|
|
428
|
+
return createCurveBeltConveyorObject(visual, state);
|
|
429
|
+
}
|
|
430
|
+
if (visual.typeName === "e3d:InjectorBeltConveyor") {
|
|
431
|
+
const injector = createInjectorBeltConveyorObject(visual, state);
|
|
432
|
+
if (injector) {
|
|
433
|
+
return injector;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
419
436
|
const belt = readProceduralBelt(visual, state);
|
|
420
437
|
if (!belt) {
|
|
421
438
|
return undefined;
|
|
@@ -444,6 +461,146 @@ function createStraightBeltConveyorObject(visual, state) {
|
|
|
444
461
|
state.stats.proceduralBelts += 1;
|
|
445
462
|
return object;
|
|
446
463
|
}
|
|
464
|
+
const DEFAULT_BELT_WIDTH = 0.5;
|
|
465
|
+
const DEFAULT_BELT_DIAMETER = 0.06;
|
|
466
|
+
function appendOrientedTriangle(target, a, b, c, normal) {
|
|
467
|
+
const abx = b[0] - a[0];
|
|
468
|
+
const aby = b[1] - a[1];
|
|
469
|
+
const abz = b[2] - a[2];
|
|
470
|
+
const acx = c[0] - a[0];
|
|
471
|
+
const acy = c[1] - a[1];
|
|
472
|
+
const acz = c[2] - a[2];
|
|
473
|
+
const crossX = aby * acz - abz * acy;
|
|
474
|
+
const crossY = abz * acx - abx * acz;
|
|
475
|
+
const crossZ = abx * acy - aby * acx;
|
|
476
|
+
const forward = crossX * normal[0] + crossY * normal[1] + crossZ * normal[2] >= 0;
|
|
477
|
+
target.push(...a, ...(forward ? b : c), ...(forward ? c : b));
|
|
478
|
+
}
|
|
479
|
+
function appendOrientedQuad(target, a, b, c, d, normal) {
|
|
480
|
+
appendOrientedTriangle(target, a, b, c, normal);
|
|
481
|
+
appendOrientedTriangle(target, a, c, d, normal);
|
|
482
|
+
}
|
|
483
|
+
function createInjectorBeltConveyorObject(visual, state) {
|
|
484
|
+
const belt = readProceduralInjectorBelt(visual);
|
|
485
|
+
if (!belt) {
|
|
486
|
+
return undefined;
|
|
487
|
+
}
|
|
488
|
+
const geometry = getProceduralInjectorBeltGeometry(belt, state);
|
|
489
|
+
const object = new state.three.Mesh(geometry, [
|
|
490
|
+
getMaterial(belt.surfaceMaterial, state),
|
|
491
|
+
getMaterial(belt.sideMaterial, state)
|
|
492
|
+
]);
|
|
493
|
+
object.name = visual.displayName ?? visual.id ?? visual.typeName;
|
|
494
|
+
object.userData.demo3d = {
|
|
495
|
+
kind: "procedural-belt",
|
|
496
|
+
id: visual.id,
|
|
497
|
+
name: visual.displayName,
|
|
498
|
+
typeName: visual.typeName,
|
|
499
|
+
xmlPath: visual.xml.path,
|
|
500
|
+
loopHeight: belt.thickness,
|
|
501
|
+
startEdge: belt.startEdge,
|
|
502
|
+
endEdge: belt.endEdge,
|
|
503
|
+
connectorShaped: true
|
|
504
|
+
};
|
|
505
|
+
state.stats.meshes += 1;
|
|
506
|
+
state.stats.directVisuals += 1;
|
|
507
|
+
state.stats.proceduralBelts += 1;
|
|
508
|
+
return object;
|
|
509
|
+
}
|
|
510
|
+
function readProceduralInjectorBelt(visual) {
|
|
511
|
+
const properties = visual.properties;
|
|
512
|
+
if (!properties || isFalse(properties.textOf("CenterVisible"))) {
|
|
513
|
+
return undefined;
|
|
514
|
+
}
|
|
515
|
+
const startEdge = conveyorConnectorEdge(visual, "Start");
|
|
516
|
+
const serializedEndEdge = conveyorConnectorEdge(visual, "End");
|
|
517
|
+
if (!startEdge || !serializedEndEdge) {
|
|
518
|
+
return undefined;
|
|
519
|
+
}
|
|
520
|
+
const sameDirection = pointDistanceSquared(startEdge[0], serializedEndEdge[0]) +
|
|
521
|
+
pointDistanceSquared(startEdge[1], serializedEndEdge[1]);
|
|
522
|
+
const reversedDirection = pointDistanceSquared(startEdge[0], serializedEndEdge[1]) +
|
|
523
|
+
pointDistanceSquared(startEdge[1], serializedEndEdge[0]);
|
|
524
|
+
const endEdge = sameDirection <= reversedDirection
|
|
525
|
+
? serializedEndEdge
|
|
526
|
+
: [serializedEndEdge[1], serializedEndEdge[0]];
|
|
527
|
+
const beltDiameter = numberChildOptional(properties, "CenterDiameter") ??
|
|
528
|
+
numberChildOptional(properties, "BeltDiameter") ??
|
|
529
|
+
DEFAULT_BELT_DIAMETER;
|
|
530
|
+
const thickness = isTrue(properties.textOf("UseBeltCenterHeight"))
|
|
531
|
+
? numberChild(properties, "BeltCenterHeight", beltDiameter)
|
|
532
|
+
: beltDiameter;
|
|
533
|
+
if (thickness <= 0) {
|
|
534
|
+
return undefined;
|
|
535
|
+
}
|
|
536
|
+
const surfaceMaterial = firstMaterial(properties.child("SurfaceMaterial") ?? properties);
|
|
537
|
+
const sideMaterial = firstMaterial(properties.child("SurfaceSideMaterial") ?? properties) ?? surfaceMaterial;
|
|
538
|
+
return { startEdge, endEdge, thickness, surfaceMaterial, sideMaterial };
|
|
539
|
+
}
|
|
540
|
+
function conveyorConnectorEdge(visual, name) {
|
|
541
|
+
const connector = visual.xml.child("CN")?.children.find((candidate) => candidate.xsiType === "e3d:ConveyorConnector" && candidate.textOf("Name") === name);
|
|
542
|
+
if (!connector) {
|
|
543
|
+
return undefined;
|
|
544
|
+
}
|
|
545
|
+
const start = pipePoint(connector.textOf("Start"));
|
|
546
|
+
const end = pipePoint(connector.textOf("End"));
|
|
547
|
+
return start && end ? [start, end] : undefined;
|
|
548
|
+
}
|
|
549
|
+
function pipePoint(value) {
|
|
550
|
+
const values = parsePipeNumbers(value);
|
|
551
|
+
if (values.length === 0) {
|
|
552
|
+
return undefined;
|
|
553
|
+
}
|
|
554
|
+
return [values[0] ?? 0, values[1] ?? 0, -(values[2] ?? 0)];
|
|
555
|
+
}
|
|
556
|
+
function pointDistanceSquared(left, right) {
|
|
557
|
+
return ((left[0] - right[0]) ** 2 +
|
|
558
|
+
(left[1] - right[1]) ** 2 +
|
|
559
|
+
(left[2] - right[2]) ** 2);
|
|
560
|
+
}
|
|
561
|
+
function getProceduralInjectorBeltGeometry(belt, state) {
|
|
562
|
+
const key = `injector-belt:${[...belt.startEdge, ...belt.endEdge].map((point) => point.join(",")).join(";")}:${belt.thickness}`;
|
|
563
|
+
const cached = state.primitiveGeometryCache.get(key);
|
|
564
|
+
if (cached) {
|
|
565
|
+
return cached;
|
|
566
|
+
}
|
|
567
|
+
const surfacePositions = [];
|
|
568
|
+
const sidePositions = [];
|
|
569
|
+
const top = [belt.startEdge[0], belt.startEdge[1], belt.endEdge[1], belt.endEdge[0]];
|
|
570
|
+
const bottom = top.map((point) => [point[0], point[1] - belt.thickness, point[2]]);
|
|
571
|
+
const center = [
|
|
572
|
+
top.reduce((sum, point) => sum + point[0], 0) / top.length,
|
|
573
|
+
top.reduce((sum, point) => sum + point[1], 0) / top.length,
|
|
574
|
+
top.reduce((sum, point) => sum + point[2], 0) / top.length
|
|
575
|
+
];
|
|
576
|
+
appendOrientedQuad(surfacePositions, top[0], top[1], top[2], top[3], [0, 1, 0]);
|
|
577
|
+
appendOrientedQuad(surfacePositions, bottom[0], bottom[3], bottom[2], bottom[1], [0, -1, 0]);
|
|
578
|
+
appendInjectorBeltWall(surfacePositions, top[0], top[1], bottom[1], bottom[0], center);
|
|
579
|
+
appendInjectorBeltWall(sidePositions, top[1], top[2], bottom[2], bottom[1], center);
|
|
580
|
+
appendInjectorBeltWall(surfacePositions, top[2], top[3], bottom[3], bottom[2], center);
|
|
581
|
+
appendInjectorBeltWall(sidePositions, top[3], top[0], bottom[0], bottom[3], center);
|
|
582
|
+
const geometry = new state.three.BufferGeometry();
|
|
583
|
+
geometry.setAttribute("position", new state.three.Float32BufferAttribute([...surfacePositions, ...sidePositions], 3));
|
|
584
|
+
geometry.addGroup(0, surfacePositions.length / 3, 0);
|
|
585
|
+
geometry.addGroup(surfacePositions.length / 3, sidePositions.length / 3, 1);
|
|
586
|
+
geometry.computeVertexNormals();
|
|
587
|
+
geometry.computeBoundingBox();
|
|
588
|
+
geometry.computeBoundingSphere();
|
|
589
|
+
state.primitiveGeometryCache.set(key, geometry);
|
|
590
|
+
state.stats.geometries += 1;
|
|
591
|
+
return geometry;
|
|
592
|
+
}
|
|
593
|
+
function appendInjectorBeltWall(target, topA, topB, bottomB, bottomA, center) {
|
|
594
|
+
const middleX = (topA[0] + topB[0]) / 2;
|
|
595
|
+
const middleZ = (topA[2] + topB[2]) / 2;
|
|
596
|
+
let normalX = topB[2] - topA[2];
|
|
597
|
+
let normalZ = -(topB[0] - topA[0]);
|
|
598
|
+
if ((middleX - center[0]) * normalX + (middleZ - center[2]) * normalZ < 0) {
|
|
599
|
+
normalX = -normalX;
|
|
600
|
+
normalZ = -normalZ;
|
|
601
|
+
}
|
|
602
|
+
appendOrientedQuad(target, topA, topB, bottomB, bottomA, [normalX, 0, normalZ]);
|
|
603
|
+
}
|
|
447
604
|
function readProceduralBelt(visual, state) {
|
|
448
605
|
const properties = visual.properties;
|
|
449
606
|
if (!properties || isFalse(properties.textOf("CenterVisible"))) {
|
|
@@ -468,12 +625,12 @@ function readProceduralBelt(visual, state) {
|
|
|
468
625
|
const beltLength = numberChildOptional(properties, "BeltLength") ??
|
|
469
626
|
(serializedLength === undefined ? undefined : serializedLength - startPadding - endPadding);
|
|
470
627
|
const serializedWidth = numberChildOptional(properties, "Width");
|
|
471
|
-
const beltWidth = numberChildOptional(properties, "BeltWidth") ?? serializedWidth;
|
|
628
|
+
const beltWidth = numberChildOptional(properties, "BeltWidth") ?? serializedWidth ?? DEFAULT_BELT_WIDTH;
|
|
472
629
|
const effectiveLength = beltLength ?? 0;
|
|
473
|
-
const effectiveWidth =
|
|
630
|
+
const effectiveWidth = beltWidth - leftPadding - rightPadding;
|
|
474
631
|
const centerDiameter = numberChildOptional(properties, "CenterDiameter") ??
|
|
475
632
|
numberChildOptional(properties, "BeltDiameter") ??
|
|
476
|
-
|
|
633
|
+
DEFAULT_BELT_DIAMETER;
|
|
477
634
|
const useBeltCenterHeight = isTrue(properties.textOf("UseBeltCenterHeight"));
|
|
478
635
|
const loopHeight = useBeltCenterHeight
|
|
479
636
|
? numberChild(properties, "BeltCenterHeight", centerDiameter)
|
|
@@ -538,6 +695,131 @@ function getProceduralBeltGeometry(belt, state) {
|
|
|
538
695
|
state.stats.geometries += 1;
|
|
539
696
|
return geometry;
|
|
540
697
|
}
|
|
698
|
+
function createCurveBeltConveyorObject(visual, state) {
|
|
699
|
+
const belt = readProceduralCurveBelt(visual, state);
|
|
700
|
+
if (!belt) {
|
|
701
|
+
return undefined;
|
|
702
|
+
}
|
|
703
|
+
const geometry = getProceduralCurveBeltGeometry(belt, state);
|
|
704
|
+
const object = new state.three.Mesh(geometry, [
|
|
705
|
+
getMaterial(belt.surfaceMaterial, state),
|
|
706
|
+
getMaterial(belt.sideMaterial, state)
|
|
707
|
+
]);
|
|
708
|
+
object.name = visual.displayName ?? visual.id ?? visual.typeName;
|
|
709
|
+
object.userData.demo3d = {
|
|
710
|
+
kind: "procedural-belt",
|
|
711
|
+
id: visual.id,
|
|
712
|
+
name: visual.displayName,
|
|
713
|
+
typeName: visual.typeName,
|
|
714
|
+
xmlPath: visual.xml.path,
|
|
715
|
+
innerRadius: belt.innerRadius,
|
|
716
|
+
width: belt.width,
|
|
717
|
+
loopHeight: belt.thickness,
|
|
718
|
+
angle: belt.angle
|
|
719
|
+
};
|
|
720
|
+
state.stats.meshes += 1;
|
|
721
|
+
state.stats.directVisuals += 1;
|
|
722
|
+
state.stats.proceduralBelts += 1;
|
|
723
|
+
return object;
|
|
724
|
+
}
|
|
725
|
+
function readProceduralCurveBelt(visual, state) {
|
|
726
|
+
const properties = visual.properties;
|
|
727
|
+
if (!properties || isFalse(properties.textOf("CenterVisible"))) {
|
|
728
|
+
return undefined;
|
|
729
|
+
}
|
|
730
|
+
const centerProfile = properties.child("CenterProfile");
|
|
731
|
+
if (centerProfile && (centerProfile.children.length > 0 || centerProfile.text.length > 0)) {
|
|
732
|
+
warn(state, {
|
|
733
|
+
code: "DEMO3D_THREE_UNSUPPORTED_BELT_PROFILE",
|
|
734
|
+
message: "Curve belt conveyors with a custom CenterProfile are not supported.",
|
|
735
|
+
sourceId: visual.id,
|
|
736
|
+
sourceType: visual.typeName,
|
|
737
|
+
xmlPath: visual.xml.path
|
|
738
|
+
});
|
|
739
|
+
return undefined;
|
|
740
|
+
}
|
|
741
|
+
const innerRadius = numberChild(properties, "InnerRadius", 0.5);
|
|
742
|
+
const width = numberChildOptional(properties, "BeltWidth")
|
|
743
|
+
?? numberChildOptional(properties, "Width")
|
|
744
|
+
?? DEFAULT_BELT_WIDTH;
|
|
745
|
+
const beltDiameter = numberChildOptional(properties, "CenterDiameter") ??
|
|
746
|
+
numberChildOptional(properties, "BeltDiameter") ??
|
|
747
|
+
DEFAULT_BELT_DIAMETER;
|
|
748
|
+
const thickness = isTrue(properties.textOf("UseBeltCenterHeight"))
|
|
749
|
+
? numberChild(properties, "BeltCenterHeight", beltDiameter)
|
|
750
|
+
: beltDiameter;
|
|
751
|
+
const angle = degreesToRadians(numberChild(properties, "Angle", 90));
|
|
752
|
+
const stepAngle = Math.abs(degreesToRadians(numberChild(properties, "StepAngle", 15)));
|
|
753
|
+
if (innerRadius <= 0 || width <= 0 || thickness <= 0 || Math.abs(angle) <= 1e-6) {
|
|
754
|
+
warn(state, {
|
|
755
|
+
code: "DEMO3D_THREE_INVALID_BELT_DIMENSIONS",
|
|
756
|
+
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)}).`,
|
|
757
|
+
sourceId: visual.id,
|
|
758
|
+
sourceType: visual.typeName,
|
|
759
|
+
xmlPath: visual.xml.path
|
|
760
|
+
});
|
|
761
|
+
return undefined;
|
|
762
|
+
}
|
|
763
|
+
const surfaceMaterial = firstMaterial(properties.child("SurfaceMaterial") ?? properties);
|
|
764
|
+
const sideMaterial = firstMaterial(properties.child("SurfaceSideMaterial") ?? properties) ?? surfaceMaterial;
|
|
765
|
+
return {
|
|
766
|
+
innerRadius,
|
|
767
|
+
width,
|
|
768
|
+
thickness,
|
|
769
|
+
angle,
|
|
770
|
+
stepAngle: stepAngle > 1e-6 ? stepAngle : Math.PI / 12,
|
|
771
|
+
surfaceMaterial,
|
|
772
|
+
sideMaterial
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
function getProceduralCurveBeltGeometry(belt, state) {
|
|
776
|
+
const key = `curve-belt:${belt.innerRadius}:${belt.width}:${belt.thickness}:${belt.angle}:${belt.stepAngle}`;
|
|
777
|
+
const cached = state.primitiveGeometryCache.get(key);
|
|
778
|
+
if (cached) {
|
|
779
|
+
return cached;
|
|
780
|
+
}
|
|
781
|
+
const surfacePositions = [];
|
|
782
|
+
const sidePositions = [];
|
|
783
|
+
const outerRadius = belt.innerRadius + belt.width;
|
|
784
|
+
const segments = Math.max(4, Math.ceil(Math.abs(belt.angle) / belt.stepAngle));
|
|
785
|
+
const point = (radius, y, theta) => [
|
|
786
|
+
radius * Math.cos(theta),
|
|
787
|
+
y,
|
|
788
|
+
radius * Math.sin(theta)
|
|
789
|
+
];
|
|
790
|
+
for (let segment = 0; segment < segments; segment += 1) {
|
|
791
|
+
const theta0 = belt.angle * (segment / segments);
|
|
792
|
+
const theta1 = belt.angle * ((segment + 1) / segments);
|
|
793
|
+
const middle = (theta0 + theta1) / 2;
|
|
794
|
+
const innerTop0 = point(belt.innerRadius, 0, theta0);
|
|
795
|
+
const innerTop1 = point(belt.innerRadius, 0, theta1);
|
|
796
|
+
const outerTop0 = point(outerRadius, 0, theta0);
|
|
797
|
+
const outerTop1 = point(outerRadius, 0, theta1);
|
|
798
|
+
const innerBottom0 = point(belt.innerRadius, -belt.thickness, theta0);
|
|
799
|
+
const innerBottom1 = point(belt.innerRadius, -belt.thickness, theta1);
|
|
800
|
+
const outerBottom0 = point(outerRadius, -belt.thickness, theta0);
|
|
801
|
+
const outerBottom1 = point(outerRadius, -belt.thickness, theta1);
|
|
802
|
+
appendOrientedQuad(surfacePositions, innerTop0, outerTop0, outerTop1, innerTop1, [0, 1, 0]);
|
|
803
|
+
appendOrientedQuad(surfacePositions, innerBottom0, innerBottom1, outerBottom1, outerBottom0, [0, -1, 0]);
|
|
804
|
+
appendOrientedQuad(sidePositions, outerBottom0, outerBottom1, outerTop1, outerTop0, [Math.cos(middle), 0, Math.sin(middle)]);
|
|
805
|
+
appendOrientedQuad(sidePositions, innerBottom0, innerTop0, innerTop1, innerBottom1, [-Math.cos(middle), 0, -Math.sin(middle)]);
|
|
806
|
+
}
|
|
807
|
+
const direction = Math.sign(belt.angle) || 1;
|
|
808
|
+
const start = 0;
|
|
809
|
+
const end = belt.angle;
|
|
810
|
+
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)]);
|
|
811
|
+
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)]);
|
|
812
|
+
const geometry = new state.three.BufferGeometry();
|
|
813
|
+
geometry.setAttribute("position", new state.three.Float32BufferAttribute([...surfacePositions, ...sidePositions], 3));
|
|
814
|
+
geometry.addGroup(0, surfacePositions.length / 3, 0);
|
|
815
|
+
geometry.addGroup(surfacePositions.length / 3, sidePositions.length / 3, 1);
|
|
816
|
+
geometry.computeVertexNormals();
|
|
817
|
+
geometry.computeBoundingBox();
|
|
818
|
+
geometry.computeBoundingSphere();
|
|
819
|
+
state.primitiveGeometryCache.set(key, geometry);
|
|
820
|
+
state.stats.geometries += 1;
|
|
821
|
+
return geometry;
|
|
822
|
+
}
|
|
541
823
|
function createRectangularBeltGeometry(belt, state) {
|
|
542
824
|
const geometry = new state.three.BoxGeometry(belt.length, belt.thickness, belt.width);
|
|
543
825
|
geometry.translate(belt.xOffset + belt.length / 2, -belt.thickness / 2, belt.zOffset);
|
|
@@ -582,6 +864,117 @@ function createRoundedBeltGeometry(belt, state) {
|
|
|
582
864
|
}
|
|
583
865
|
return geometry;
|
|
584
866
|
}
|
|
867
|
+
function createRackObject(visual, state) {
|
|
868
|
+
const properties = visual.properties;
|
|
869
|
+
if (!properties) {
|
|
870
|
+
return undefined;
|
|
871
|
+
}
|
|
872
|
+
const frameHeight = numberChild(properties, "FrameHeight", 0);
|
|
873
|
+
const uprightWidth = numberChild(properties, "UprightWidth", 0.05);
|
|
874
|
+
const uprightDepth = numberChild(properties, "UprightDepth", 0.05);
|
|
875
|
+
const frameDepth = numberChildOptional(properties, "FrameDepth") ?? numberChild(properties, "BayDepth", 0);
|
|
876
|
+
const strutWidth = numberChild(properties, "StrutWidth", 0.05);
|
|
877
|
+
if (frameHeight <= 0 || frameDepth <= 0 || uprightWidth <= 0 || uprightDepth <= 0 || strutWidth <= 0) {
|
|
878
|
+
warn(state, {
|
|
879
|
+
code: "DEMO3D_THREE_INVALID_RACK_DIMENSIONS",
|
|
880
|
+
message: `Rack dimensions must be positive (height ${frameHeight}, depth ${frameDepth}, upright ${uprightWidth} x ${uprightDepth}, strut ${strutWidth}).`,
|
|
881
|
+
sourceId: visual.id,
|
|
882
|
+
sourceType: visual.typeName,
|
|
883
|
+
xmlPath: visual.xml.path
|
|
884
|
+
});
|
|
885
|
+
return undefined;
|
|
886
|
+
}
|
|
887
|
+
const numBays = Math.max(1, Math.round(numberChild(properties, "NumBays", 1)));
|
|
888
|
+
const bayWidth = Math.max(0, numberChild(properties, "BayWidth", 0));
|
|
889
|
+
const framePitch = bayWidth + uprightWidth + Math.max(0, numberChild(properties, "MinFrameGap", 0));
|
|
890
|
+
const framePositions = [0];
|
|
891
|
+
if (!isFalse(properties.child("MiddleFrames")?.textOf("Visible"))) {
|
|
892
|
+
for (let bay = 1; bay < numBays; bay += 1) {
|
|
893
|
+
framePositions.push(bay * framePitch);
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
if (!isFalse(properties.child("LastFrame")?.textOf("Visible")) && framePitch > 0) {
|
|
897
|
+
framePositions.push(numBays * framePitch);
|
|
898
|
+
}
|
|
899
|
+
const initialStrutHeight = clamp(numberChild(properties, "InitialStrutHeight", strutWidth / 2), 0, frameHeight);
|
|
900
|
+
const strutSpanHeight = numberChild(properties, "StrutSpanHeight", frameHeight);
|
|
901
|
+
const strutHeights = [initialStrutHeight];
|
|
902
|
+
if (strutSpanHeight > 0) {
|
|
903
|
+
for (let height = initialStrutHeight + strutSpanHeight; height < frameHeight - 1e-6; height += strutSpanHeight) {
|
|
904
|
+
strutHeights.push(height);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
const extensionHeight = clamp(frameHeight - Math.max(0, numberChild(properties, "ExtensionStrutOffset", strutWidth / 2)), 0, frameHeight);
|
|
908
|
+
if (!strutHeights.some((height) => Math.abs(height - extensionHeight) < 1e-6)) {
|
|
909
|
+
strutHeights.push(extensionHeight);
|
|
910
|
+
}
|
|
911
|
+
strutHeights.sort((left, right) => left - right);
|
|
912
|
+
const uprightMaterial = getColorMaterial(demo3dColorValue(properties.child("UprightColor"), 0xffd3d3d3 | 0), state);
|
|
913
|
+
const strutMaterial = getColorMaterial(demo3dColorValue(properties.child("StrutColor"), 0xffd3d3d3 | 0), state);
|
|
914
|
+
const uprightGeometry = cachedBoxGeometry(uprightWidth, frameHeight, uprightDepth, state);
|
|
915
|
+
const strutGeometry = cachedBoxGeometry(strutWidth, strutWidth, frameDepth, state);
|
|
916
|
+
const group = new state.three.Group();
|
|
917
|
+
group.name = `${visual.displayName ?? visual.id ?? "Rack"} generated geometry`;
|
|
918
|
+
for (const frameX of framePositions) {
|
|
919
|
+
for (const z of [-frameDepth / 2, frameDepth / 2]) {
|
|
920
|
+
const upright = new state.three.Mesh(uprightGeometry, uprightMaterial);
|
|
921
|
+
upright.name = "Rack upright";
|
|
922
|
+
upright.position.set(frameX, frameHeight / 2, z);
|
|
923
|
+
upright.userData.demo3d = { kind: "procedural-rack-upright", frameX };
|
|
924
|
+
group.add(upright);
|
|
925
|
+
state.stats.meshes += 1;
|
|
926
|
+
}
|
|
927
|
+
for (const height of strutHeights) {
|
|
928
|
+
const strut = new state.three.Mesh(strutGeometry, strutMaterial);
|
|
929
|
+
strut.name = "Rack strut";
|
|
930
|
+
strut.position.set(frameX, height, 0);
|
|
931
|
+
strut.userData.demo3d = { kind: "procedural-rack-strut", frameX, height };
|
|
932
|
+
group.add(strut);
|
|
933
|
+
state.stats.meshes += 1;
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
group.userData.demo3d = {
|
|
937
|
+
kind: "procedural-rack",
|
|
938
|
+
id: visual.id,
|
|
939
|
+
name: visual.displayName,
|
|
940
|
+
typeName: visual.typeName,
|
|
941
|
+
xmlPath: visual.xml.path,
|
|
942
|
+
frameHeight,
|
|
943
|
+
frameDepth,
|
|
944
|
+
framePositions,
|
|
945
|
+
strutHeights
|
|
946
|
+
};
|
|
947
|
+
state.stats.groups += 1;
|
|
948
|
+
state.stats.directVisuals += 1;
|
|
949
|
+
state.stats.proceduralRacks += 1;
|
|
950
|
+
return group;
|
|
951
|
+
}
|
|
952
|
+
function demo3dColorValue(container, fallback) {
|
|
953
|
+
const numeric = container?.text.trim();
|
|
954
|
+
if (numeric) {
|
|
955
|
+
const parsed = Number(numeric);
|
|
956
|
+
if (Number.isFinite(parsed)) {
|
|
957
|
+
return parsed;
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
switch (container?.textOf("name")?.trim().toLowerCase()) {
|
|
961
|
+
case "black":
|
|
962
|
+
return 0xff000000 | 0;
|
|
963
|
+
case "darkgray":
|
|
964
|
+
case "darkgrey":
|
|
965
|
+
return 0xffa9a9a9 | 0;
|
|
966
|
+
case "gray":
|
|
967
|
+
case "grey":
|
|
968
|
+
return 0xff808080 | 0;
|
|
969
|
+
case "lightgray":
|
|
970
|
+
case "lightgrey":
|
|
971
|
+
return 0xffd3d3d3 | 0;
|
|
972
|
+
case "white":
|
|
973
|
+
return 0xffffffff | 0;
|
|
974
|
+
default:
|
|
975
|
+
return fallback;
|
|
976
|
+
}
|
|
977
|
+
}
|
|
585
978
|
function createSupportStandObject(visual, parentVisual, state) {
|
|
586
979
|
const stand = visual instanceof Demo3DSupportStand
|
|
587
980
|
? visual
|
|
@@ -591,7 +984,13 @@ function createSupportStandObject(visual, parentVisual, state) {
|
|
|
591
984
|
const footProfile = properties?.footProfile;
|
|
592
985
|
const floorPlateProfile = properties?.floorPlateProfile;
|
|
593
986
|
const crossBraceProfile = properties?.crossBraceProfile;
|
|
594
|
-
if (!properties
|
|
987
|
+
if (!properties) {
|
|
988
|
+
return undefined;
|
|
989
|
+
}
|
|
990
|
+
if (!legProfile && !crossBraceProfile) {
|
|
991
|
+
return createDefaultSupportStandObject(visual, parentVisual, properties, state);
|
|
992
|
+
}
|
|
993
|
+
if (!legProfile || !crossBraceProfile) {
|
|
595
994
|
warn(state, {
|
|
596
995
|
code: "DEMO3D_THREE_UNSUPPORTED_SUPPORT_STAND_PROFILE",
|
|
597
996
|
message: "Support stand is missing its leg or cross-brace extrusion profile.",
|
|
@@ -659,6 +1058,84 @@ function createSupportStandObject(visual, parentVisual, state) {
|
|
|
659
1058
|
state.stats.proceduralSupportStands += 1;
|
|
660
1059
|
return group;
|
|
661
1060
|
}
|
|
1061
|
+
const DEFAULT_CONVEYOR_HEIGHT = 0.83;
|
|
1062
|
+
const DEFAULT_SUPPORT_LEG_SIZE = 0.04;
|
|
1063
|
+
const DEFAULT_SUPPORT_FOOT_SIZE = 0.06;
|
|
1064
|
+
const DEFAULT_SUPPORT_FLOOR_PLATE_SIZE = 0.1;
|
|
1065
|
+
function createDefaultSupportStandObject(visual, parentVisual, properties, state) {
|
|
1066
|
+
const parentWidth = numberChildOptional(parentVisual?.properties, "RollerWidth") ??
|
|
1067
|
+
numberChildOptional(parentVisual?.properties, "BeltWidth") ??
|
|
1068
|
+
numberChildOptional(parentVisual?.properties, "Width") ??
|
|
1069
|
+
DEFAULT_BELT_WIDTH;
|
|
1070
|
+
const conveyorOffset = properties.conveyorOffset;
|
|
1071
|
+
const sideExtension = Math.abs(conveyorOffset[2] ?? 0);
|
|
1072
|
+
const span = Math.max(parentWidth + sideExtension * 2, 0.1);
|
|
1073
|
+
const top = conveyorOffset[1] ?? 0;
|
|
1074
|
+
const x = conveyorOffset[0] ?? 0;
|
|
1075
|
+
const floorY = numberChild(properties.xml, "FloorY", 0);
|
|
1076
|
+
const explicitStandHeight = numberChildOptional(properties.xml, "StandHeight") ??
|
|
1077
|
+
numberChildOptional(properties.xml, "SupportHeight") ??
|
|
1078
|
+
numberChildOptional(properties.xml, "Height");
|
|
1079
|
+
const explicitLegHeight = numberChildOptional(properties.xml, "LegHeight");
|
|
1080
|
+
const conveyorHeight = numberChildOptional(parentVisual?.properties, "ConveyorHeight") ?? DEFAULT_CONVEYOR_HEIGHT;
|
|
1081
|
+
const componentHeight = explicitLegHeight === undefined
|
|
1082
|
+
? undefined
|
|
1083
|
+
: explicitLegHeight + properties.footHeight + properties.floorPlateHeight;
|
|
1084
|
+
const supportHeight = Math.max(explicitStandHeight ?? componentHeight ?? conveyorHeight + top - floorY, properties.footHeight + properties.floorPlateHeight + 0.001);
|
|
1085
|
+
const bottom = top - supportHeight;
|
|
1086
|
+
const floorPlateHeight = clamp(properties.floorPlateHeight, 0, supportHeight);
|
|
1087
|
+
const footHeight = clamp(properties.footHeight, 0, supportHeight - floorPlateHeight);
|
|
1088
|
+
const legHeight = Math.max(supportHeight - floorPlateHeight - footHeight, 0.001);
|
|
1089
|
+
const braces = [...properties.crossBraceHeights]
|
|
1090
|
+
.filter((height) => height > 0 && height < supportHeight)
|
|
1091
|
+
.sort((left, right) => left - right);
|
|
1092
|
+
const group = new state.three.Group();
|
|
1093
|
+
group.name = `${visual.displayName ?? visual.id ?? "Support stand"} default geometry`;
|
|
1094
|
+
const legMaterial = supportComponentMaterial(visual.properties?.child("LegMaterial"), properties.legMaterial, state);
|
|
1095
|
+
const footMaterial = supportComponentMaterial(visual.properties?.child("FootMaterial"), properties.footMaterial, state);
|
|
1096
|
+
const floorPlateMaterial = supportComponentMaterial(visual.properties?.child("FloorPlateMaterial"), properties.floorPlateMaterial, state);
|
|
1097
|
+
const braceMaterial = supportComponentMaterial(visual.properties?.child("CrossBraceMaterial"), properties.crossBraceMaterial, state);
|
|
1098
|
+
for (const z of [-span / 2, span / 2]) {
|
|
1099
|
+
addDefaultSupportBox(group, "leg", DEFAULT_SUPPORT_LEG_SIZE, legHeight, DEFAULT_SUPPORT_LEG_SIZE, [x, top - legHeight / 2, z], legMaterial, state);
|
|
1100
|
+
if (footHeight > 0) {
|
|
1101
|
+
addDefaultSupportBox(group, "foot", DEFAULT_SUPPORT_FOOT_SIZE, footHeight, DEFAULT_SUPPORT_FOOT_SIZE, [x, bottom + floorPlateHeight + footHeight / 2, z], footMaterial, state);
|
|
1102
|
+
}
|
|
1103
|
+
if (floorPlateHeight > 0) {
|
|
1104
|
+
addDefaultSupportBox(group, "floor plate", DEFAULT_SUPPORT_FLOOR_PLATE_SIZE, floorPlateHeight, DEFAULT_SUPPORT_FLOOR_PLATE_SIZE, [x, bottom + floorPlateHeight / 2, z], floorPlateMaterial, state);
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
for (const height of braces) {
|
|
1108
|
+
addDefaultSupportBox(group, "cross brace", DEFAULT_SUPPORT_LEG_SIZE, DEFAULT_SUPPORT_LEG_SIZE, span, [x, bottom + height, 0], braceMaterial, state);
|
|
1109
|
+
}
|
|
1110
|
+
addDefaultSupportBox(group, "top brace", DEFAULT_SUPPORT_LEG_SIZE, DEFAULT_SUPPORT_LEG_SIZE, span, [x, top - DEFAULT_SUPPORT_LEG_SIZE / 2, 0], braceMaterial, state);
|
|
1111
|
+
group.userData.demo3d = {
|
|
1112
|
+
kind: "procedural-support-stand",
|
|
1113
|
+
id: visual.id,
|
|
1114
|
+
name: visual.displayName,
|
|
1115
|
+
typeName: visual.typeName,
|
|
1116
|
+
xmlPath: visual.xml.path,
|
|
1117
|
+
supportHeight,
|
|
1118
|
+
span,
|
|
1119
|
+
braceHeights: braces,
|
|
1120
|
+
approximate: true,
|
|
1121
|
+
defaultProfiles: true
|
|
1122
|
+
};
|
|
1123
|
+
state.stats.groups += 1;
|
|
1124
|
+
state.stats.directVisuals += 1;
|
|
1125
|
+
state.stats.proceduralSupportStands += 1;
|
|
1126
|
+
return group;
|
|
1127
|
+
}
|
|
1128
|
+
function supportComponentMaterial(container, fallbackColor, state) {
|
|
1129
|
+
return materialFromContainer(container, state) ?? getColorMaterial(fallbackColor, state);
|
|
1130
|
+
}
|
|
1131
|
+
function addDefaultSupportBox(group, component, width, height, depth, position, material, state) {
|
|
1132
|
+
const mesh = new state.three.Mesh(cachedBoxGeometry(width, height, depth, state), material);
|
|
1133
|
+
mesh.name = `Default support ${component}`;
|
|
1134
|
+
mesh.position.set(position[0], position[1], position[2]);
|
|
1135
|
+
mesh.userData.demo3d = { kind: "support-stand-component", component, approximate: true };
|
|
1136
|
+
group.add(mesh);
|
|
1137
|
+
state.stats.meshes += 1;
|
|
1138
|
+
}
|
|
662
1139
|
function addSupportProfileMeshes(group, profile, axis, length, position, fallbackColor, component, state) {
|
|
663
1140
|
profile.polygons.forEach((polygon, index) => {
|
|
664
1141
|
if (polygon.points.length < 3) {
|
|
@@ -937,6 +1414,25 @@ function addProceduralRollers(group, visual, state) {
|
|
|
937
1414
|
const material = materialFromContainer(properties.child("SurfaceMaterial") ?? properties, state)
|
|
938
1415
|
?? getColorMaterial(numberChildOptional(properties, "RollerColor"), state);
|
|
939
1416
|
const geometry = cachedRollerGeometry(radius, width, state);
|
|
1417
|
+
if (visual.typeName === "e3d:DiverterRollerConveyor") {
|
|
1418
|
+
const length = numberChild(properties, "Length", 1);
|
|
1419
|
+
const pitch = Math.max(numberChild(properties, "RollerPitch", diameter * 1.2), diameter);
|
|
1420
|
+
const count = Math.max(1, Math.round(numberChildOptional(properties, "RollerCount") ?? length / pitch));
|
|
1421
|
+
const across = Math.max(1, Math.round(numberChild(properties, "NumRollersAcrossWidth", 1)));
|
|
1422
|
+
const totalWidth = numberChild(properties, "Width", width);
|
|
1423
|
+
for (let index = 0; index < count; index += 1) {
|
|
1424
|
+
for (let acrossIndex = 0; acrossIndex < across; acrossIndex += 1) {
|
|
1425
|
+
const roller = new state.three.Mesh(geometry, material);
|
|
1426
|
+
roller.name = `Generated divert roller ${index + 1}.${acrossIndex + 1}`;
|
|
1427
|
+
roller.position.set(((index + 0.5) / count) * length, 0, ((acrossIndex + 0.5) / across - 0.5) * totalWidth);
|
|
1428
|
+
roller.userData.demo3d = { kind: "procedural-roller", index, acrossIndex };
|
|
1429
|
+
group.add(roller);
|
|
1430
|
+
state.stats.meshes += 1;
|
|
1431
|
+
state.stats.proceduralRollers += 1;
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
return;
|
|
1435
|
+
}
|
|
940
1436
|
if (visual.typeName === "e3d:CurveRollerConveyor") {
|
|
941
1437
|
const count = Math.max(1, Math.round(numberChild(properties, "RollerCount", 1)));
|
|
942
1438
|
const innerRadius = numberChild(properties, "InnerRadius", 0.5);
|
|
@@ -1050,6 +1546,7 @@ function cachedBoxGeometry(width, height, depth, state) {
|
|
|
1050
1546
|
function isRollerConveyorType(typeName) {
|
|
1051
1547
|
return (typeName === "e3d:StraightRollerConveyor" ||
|
|
1052
1548
|
typeName === "e3d:CurveRollerConveyor" ||
|
|
1549
|
+
typeName === "e3d:DiverterRollerConveyor" ||
|
|
1053
1550
|
typeName === "e3d:InjectorRollerConveyor");
|
|
1054
1551
|
}
|
|
1055
1552
|
function createImportedImageObject(visual, state) {
|