@2112-lab/central-plant 0.3.49 → 0.3.50

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.
@@ -25488,6 +25488,405 @@ function interceptControlUp( event ) {
25488
25488
 
25489
25489
  }
25490
25490
 
25491
+ /** Default horizontal orbit bearing (target → camera, XY plane). */
25492
+ var DEFAULT_HOME_VIEW_DIRECTION = {
25493
+ x: -8,
25494
+ y: -9,
25495
+ z: 0
25496
+ };
25497
+
25498
+ /** Downward pitch from camera to the bottom-center look-at target (degrees). */
25499
+ var HOME_DOWNWARD_PITCH_DEG = 13;
25500
+
25501
+ /** Minimum camera height above the bbox floor (world units). */
25502
+ var HOME_CAMERA_MIN_HEIGHT = 0.8;
25503
+
25504
+ /** Ground plane height in world space (Z-up). */
25505
+ var WORLD_GROUND_Z = 0;
25506
+
25507
+ /** Camera height above the ground when the scene has no components. */
25508
+ var HOME_EMPTY_CAMERA_HEIGHT_ABOVE_GROUND = 2.5;
25509
+
25510
+ /** Default horizontal distance from world origin when the scene is empty. */
25511
+ var HOME_EMPTY_SCENE_DISTANCE = Math.hypot(DEFAULT_HOME_VIEW_DIRECTION.x, DEFAULT_HOME_VIEW_DIRECTION.y);
25512
+ var WORLD_ORIGIN = new THREE__namespace.Vector3(0, 0, 0);
25513
+ var CameraControlsManager = /*#__PURE__*/function () {
25514
+ function CameraControlsManager(component) {
25515
+ _classCallCheck(this, CameraControlsManager);
25516
+ this.sceneViewer = component;
25517
+ this.autoRotateSpeed = 1.0; // Default rotation speed
25518
+ this._isAutoRotating = false; // Internal state tracking
25519
+ }
25520
+
25521
+ /**
25522
+ * Frame the camera so that every component in the scene fits perfectly in view.
25523
+ *
25524
+ * Computes the combined bounding box of all scene components, then moves the
25525
+ * (perspective) camera along its current viewing direction to the distance
25526
+ * required to fit that bounding sphere within the frustum. The orbit target,
25527
+ * clipping planes and distance limits are updated to match.
25528
+ *
25529
+ * @param {Object} [options]
25530
+ * @param {number} [options.padding=1.04] - Multiplier on the fit distance (>1 leaves margin).
25531
+ * @param {THREE.Vector3|{x:number,y:number,z:number}} [options.direction] - Override viewing
25532
+ * direction (target -> camera). When omitted, the current orbit angle is preserved.
25533
+ * @param {boolean} [options.groundLevel] - Pin camera low with a downward pitch toward
25534
+ * the bottom-center of the assembly. Defaults to true when a home direction is used.
25535
+ * @returns {boolean} True if the camera was reframed, false if there was nothing to frame.
25536
+ */
25537
+ return _createClass(CameraControlsManager, [{
25538
+ key: "fitCameraToScene",
25539
+ value: function fitCameraToScene() {
25540
+ var _options$padding, _options$groundLevel;
25541
+ var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
25542
+ var component = this.sceneViewer;
25543
+ var camera = component === null || component === void 0 ? void 0 : component.camera;
25544
+ var scene = component === null || component === void 0 ? void 0 : component.scene;
25545
+ var controls = component === null || component === void 0 ? void 0 : component.controls;
25546
+ if (!camera || !scene || !camera.isPerspectiveCamera) {
25547
+ console.warn('⚠️ fitCameraToScene: missing perspective camera or scene');
25548
+ return false;
25549
+ }
25550
+ var padding = (_options$padding = options.padding) !== null && _options$padding !== void 0 ? _options$padding : 1.04;
25551
+
25552
+ // Make sure world matrices are current before measuring bounds
25553
+ scene.updateMatrixWorld(true);
25554
+ var box = this._collectSceneBounds(scene);
25555
+ if (!box) {
25556
+ return this._aimCameraAtWorldCenter(camera, controls, options);
25557
+ }
25558
+ var useGroundLevel = (_options$groundLevel = options.groundLevel) !== null && _options$groundLevel !== void 0 ? _options$groundLevel : this._isHomeDirection(options.direction);
25559
+ if (useGroundLevel) {
25560
+ return this._fitCameraGroundLevel(box, camera, controls, options, padding);
25561
+ }
25562
+ return this._fitCameraFromCenter(box, camera, controls, options, padding);
25563
+ }
25564
+
25565
+ /**
25566
+ * Aim the camera at world origin when there are no components to frame.
25567
+ * @private
25568
+ */
25569
+ }, {
25570
+ key: "_aimCameraAtWorldCenter",
25571
+ value: function _aimCameraAtWorldCenter(camera, controls) {
25572
+ var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
25573
+ var frameTarget = WORLD_ORIGIN.clone();
25574
+ var distance = HOME_EMPTY_SCENE_DISTANCE;
25575
+ var horizDir = new THREE__namespace.Vector3();
25576
+ if (options.direction) {
25577
+ horizDir.set(options.direction.x, options.direction.y, 0);
25578
+ } else {
25579
+ horizDir.set(DEFAULT_HOME_VIEW_DIRECTION.x, DEFAULT_HOME_VIEW_DIRECTION.y, 0);
25580
+ }
25581
+ if (horizDir.lengthSq() < 1e-6) {
25582
+ horizDir.set(DEFAULT_HOME_VIEW_DIRECTION.x, DEFAULT_HOME_VIEW_DIRECTION.y, 0);
25583
+ }
25584
+ horizDir.normalize();
25585
+ var cameraHeight = WORLD_GROUND_Z + HOME_EMPTY_CAMERA_HEIGHT_ABOVE_GROUND;
25586
+ var camPos = new THREE__namespace.Vector3(frameTarget.x + horizDir.x * distance, frameTarget.y + horizDir.y * distance, cameraHeight);
25587
+ camera.position.copy(camPos);
25588
+ camera.near = 0.01;
25589
+ camera.far = 1000;
25590
+ camera.updateProjectionMatrix();
25591
+ camera.lookAt(frameTarget);
25592
+ if (controls) {
25593
+ controls.target.copy(frameTarget);
25594
+ controls.minDistance = 1;
25595
+ controls.maxDistance = 20;
25596
+ controls.update();
25597
+ }
25598
+ console.log('🎥 Camera aimed at world origin (empty scene)');
25599
+ return true;
25600
+ }
25601
+
25602
+ /**
25603
+ * @private
25604
+ */
25605
+ }, {
25606
+ key: "_collectSceneBounds",
25607
+ value: function _collectSceneBounds(scene) {
25608
+ var includeTypes = ['component', 'gateway', 'segment'];
25609
+ var box = new THREE__namespace.Box3();
25610
+ var found = false;
25611
+ scene.traverse(function (obj) {
25612
+ var _obj$userData;
25613
+ if (includeTypes.includes((_obj$userData = obj.userData) === null || _obj$userData === void 0 ? void 0 : _obj$userData.objectType)) {
25614
+ var objBox = new THREE__namespace.Box3().setFromObject(obj);
25615
+ if (!objBox.isEmpty()) {
25616
+ box.union(objBox);
25617
+ found = true;
25618
+ }
25619
+ }
25620
+ });
25621
+ return found && !box.isEmpty() ? box : null;
25622
+ }
25623
+
25624
+ /**
25625
+ * @private
25626
+ */
25627
+ }, {
25628
+ key: "_isHomeDirection",
25629
+ value: function _isHomeDirection(direction) {
25630
+ if (!direction) return false;
25631
+ var horiz = Math.hypot(direction.x, direction.y);
25632
+ if (horiz < 1e-6) return false;
25633
+ var dx = direction.x / horiz - DEFAULT_HOME_VIEW_DIRECTION.x / Math.hypot(DEFAULT_HOME_VIEW_DIRECTION.x, DEFAULT_HOME_VIEW_DIRECTION.y);
25634
+ var dy = direction.y / horiz - DEFAULT_HOME_VIEW_DIRECTION.y / Math.hypot(DEFAULT_HOME_VIEW_DIRECTION.x, DEFAULT_HOME_VIEW_DIRECTION.y);
25635
+ return Math.hypot(dx, dy) < 0.05 && Math.abs(direction.z) < 0.5;
25636
+ }
25637
+
25638
+ /**
25639
+ * Ground-hugging camera: horizontal offset with a fixed downward pitch toward
25640
+ * the bottom-center of the component group.
25641
+ * @private
25642
+ */
25643
+ }, {
25644
+ key: "_fitCameraGroundLevel",
25645
+ value: function _fitCameraGroundLevel(box, camera, controls, options, padding) {
25646
+ var size = box.getSize(new THREE__namespace.Vector3());
25647
+ var frameTarget = this._getBoxBottomCenter(box);
25648
+ var downwardPitch = THREE__namespace.MathUtils.degToRad(HOME_DOWNWARD_PITCH_DEG);
25649
+ var horizDir = new THREE__namespace.Vector3();
25650
+ if (options.direction) {
25651
+ horizDir.set(options.direction.x, options.direction.y, 0);
25652
+ } else {
25653
+ horizDir.set(DEFAULT_HOME_VIEW_DIRECTION.x, DEFAULT_HOME_VIEW_DIRECTION.y, 0);
25654
+ }
25655
+ if (horizDir.lengthSq() < 1e-6) {
25656
+ horizDir.set(DEFAULT_HOME_VIEW_DIRECTION.x, DEFAULT_HOME_VIEW_DIRECTION.y, 0);
25657
+ }
25658
+ horizDir.normalize();
25659
+ var corners = this._getBoxCorners(box);
25660
+ var vFov = THREE__namespace.MathUtils.degToRad(camera.fov);
25661
+ var aspect = camera.aspect || 1;
25662
+ var tanHalfV = Math.tan(vFov / 2);
25663
+ var tanHalfH = tanHalfV * aspect;
25664
+ var fitsAtDistance = function fitsAtDistance(distance) {
25665
+ var cameraHeight = Math.max(frameTarget.z + distance * Math.tan(downwardPitch), box.min.z + HOME_CAMERA_MIN_HEIGHT);
25666
+ var camPos = new THREE__namespace.Vector3(frameTarget.x + horizDir.x * distance, frameTarget.y + horizDir.y * distance, cameraHeight);
25667
+ var forward = new THREE__namespace.Vector3().subVectors(frameTarget, camPos).normalize();
25668
+ var worldUp = camera.up.clone().normalize();
25669
+ var right = new THREE__namespace.Vector3().crossVectors(forward, worldUp);
25670
+ if (right.lengthSq() < 1e-6) {
25671
+ right = new THREE__namespace.Vector3().crossVectors(forward, new THREE__namespace.Vector3(1, 0, 0));
25672
+ }
25673
+ right.normalize();
25674
+ var up = new THREE__namespace.Vector3().crossVectors(right, forward).normalize();
25675
+ var _iterator = _createForOfIteratorHelper(corners),
25676
+ _step;
25677
+ try {
25678
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
25679
+ var corner = _step.value;
25680
+ var rel = new THREE__namespace.Vector3().subVectors(corner, camPos);
25681
+ var depth = rel.dot(forward);
25682
+ if (depth <= 0.01) return false;
25683
+ if (Math.abs(rel.dot(right)) / depth > tanHalfH) return false;
25684
+ if (Math.abs(rel.dot(up)) / depth > tanHalfV) return false;
25685
+ }
25686
+ } catch (err) {
25687
+ _iterator.e(err);
25688
+ } finally {
25689
+ _iterator.f();
25690
+ }
25691
+ return true;
25692
+ };
25693
+ var fitDistance = 0.5;
25694
+ while (!fitsAtDistance(fitDistance) && fitDistance < 500) {
25695
+ fitDistance *= 1.15;
25696
+ }
25697
+ if (fitDistance >= 500) {
25698
+ console.warn('⚠️ fitCameraToScene: could not frame scene from ground level');
25699
+ return false;
25700
+ }
25701
+
25702
+ // Tighten to the closest distance that still fits, then apply padding.
25703
+ var lo = 0.01;
25704
+ var hi = fitDistance;
25705
+ while (hi - lo > 0.1) {
25706
+ var mid = (lo + hi) / 2;
25707
+ if (fitsAtDistance(mid)) hi = mid;else lo = mid;
25708
+ }
25709
+ fitDistance = hi * padding;
25710
+ var cameraHeight = Math.max(frameTarget.z + fitDistance * Math.tan(downwardPitch), box.min.z + HOME_CAMERA_MIN_HEIGHT);
25711
+ var camPos = new THREE__namespace.Vector3(frameTarget.x + horizDir.x * fitDistance, frameTarget.y + horizDir.y * fitDistance, cameraHeight);
25712
+ camera.position.copy(camPos);
25713
+ var span = size.length();
25714
+ camera.near = Math.max(fitDistance / 1000, 0.01);
25715
+ camera.far = Math.max(fitDistance + span * 4, 1000);
25716
+ camera.updateProjectionMatrix();
25717
+ camera.lookAt(frameTarget);
25718
+ if (controls) {
25719
+ controls.target.copy(frameTarget);
25720
+ controls.minDistance = Math.max(span * 0.1, 0.1);
25721
+ controls.maxDistance = fitDistance * 4;
25722
+ controls.update();
25723
+ }
25724
+ console.log("\uD83C\uDFA5 Camera framed to scene at ground level (distance: ".concat(fitDistance.toFixed(2), ", height: ").concat(cameraHeight.toFixed(2), ")"));
25725
+ return true;
25726
+ }
25727
+
25728
+ /**
25729
+ * Bottom-center of a bounding box (centered in X/Y, at the floor in Z).
25730
+ * @private
25731
+ */
25732
+ }, {
25733
+ key: "_getBoxBottomCenter",
25734
+ value: function _getBoxBottomCenter(box) {
25735
+ var center = box.getCenter(new THREE__namespace.Vector3());
25736
+ return new THREE__namespace.Vector3(center.x, center.y, box.min.z);
25737
+ }
25738
+
25739
+ /**
25740
+ * @private
25741
+ */
25742
+ }, {
25743
+ key: "_getBoxCorners",
25744
+ value: function _getBoxCorners(box) {
25745
+ var min = box.min,
25746
+ max = box.max;
25747
+ var corners = [];
25748
+ for (var i = 0; i < 8; i++) {
25749
+ corners.push(new THREE__namespace.Vector3(i & 1 ? max.x : min.x, i & 2 ? max.y : min.y, i & 4 ? max.z : min.z));
25750
+ }
25751
+ return corners;
25752
+ }
25753
+
25754
+ /**
25755
+ * @private
25756
+ */
25757
+ }, {
25758
+ key: "_fitCameraFromCenter",
25759
+ value: function _fitCameraFromCenter(box, camera, controls, options, padding) {
25760
+ var center = box.getCenter(new THREE__namespace.Vector3());
25761
+ var target = controls !== null && controls !== void 0 && controls.target ? controls.target.clone() : new THREE__namespace.Vector3();
25762
+ var direction = new THREE__namespace.Vector3();
25763
+ if (options.direction) {
25764
+ direction.set(options.direction.x, options.direction.y, options.direction.z);
25765
+ } else {
25766
+ direction.subVectors(camera.position, target);
25767
+ }
25768
+ if (direction.lengthSq() < 1e-6) {
25769
+ direction.set(DEFAULT_HOME_VIEW_DIRECTION.x, DEFAULT_HOME_VIEW_DIRECTION.y, DEFAULT_HOME_VIEW_DIRECTION.z);
25770
+ }
25771
+ direction.normalize();
25772
+ var forward = direction.clone().negate();
25773
+ var worldUp = camera.up.clone().normalize();
25774
+ var right = new THREE__namespace.Vector3().crossVectors(forward, worldUp);
25775
+ if (right.lengthSq() < 1e-6) {
25776
+ right = new THREE__namespace.Vector3().crossVectors(forward, new THREE__namespace.Vector3(1, 0, 0));
25777
+ }
25778
+ right.normalize();
25779
+ var up = new THREE__namespace.Vector3().crossVectors(right, forward).normalize();
25780
+ var vFov = THREE__namespace.MathUtils.degToRad(camera.fov);
25781
+ var aspect = camera.aspect || 1;
25782
+ var tanHalfV = Math.tan(vFov / 2);
25783
+ var tanHalfH = tanHalfV * aspect;
25784
+ var min = box.min;
25785
+ var max = box.max;
25786
+ var fitDistance = 0.01;
25787
+ for (var i = 0; i < 8; i++) {
25788
+ var v = new THREE__namespace.Vector3(i & 1 ? max.x : min.x, i & 2 ? max.y : min.y, i & 4 ? max.z : min.z).sub(center);
25789
+ var vForward = v.dot(forward);
25790
+ var vRight = Math.abs(v.dot(right));
25791
+ var vUp = Math.abs(v.dot(up));
25792
+ var cornerDistance = Math.max(vRight / tanHalfH - vForward, vUp / tanHalfV - vForward, -vForward + 0.01);
25793
+ fitDistance = Math.max(fitDistance, cornerDistance);
25794
+ }
25795
+ fitDistance *= padding;
25796
+ camera.position.copy(center).addScaledVector(direction, fitDistance);
25797
+ var span = box.getSize(new THREE__namespace.Vector3()).length();
25798
+ camera.near = Math.max(fitDistance / 1000, 0.01);
25799
+ camera.far = Math.max(fitDistance + span * 4, 1000);
25800
+ camera.updateProjectionMatrix();
25801
+ camera.lookAt(center);
25802
+ if (controls) {
25803
+ controls.target.copy(center);
25804
+ controls.minDistance = Math.max(span * 0.1, 0.1);
25805
+ controls.maxDistance = fitDistance * 4;
25806
+ controls.update();
25807
+ }
25808
+ console.log("\uD83C\uDFA5 Camera framed to scene (distance: ".concat(fitDistance.toFixed(2), ")"));
25809
+ return true;
25810
+ }
25811
+
25812
+ /**
25813
+ * Frame the scene using the default low, ground-level home viewing angle.
25814
+ * @param {Object} [options] - Passed to fitCameraToScene (padding, etc.)
25815
+ * @returns {boolean}
25816
+ */
25817
+ }, {
25818
+ key: "fitCameraToSceneHome",
25819
+ value: function fitCameraToSceneHome() {
25820
+ var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
25821
+ return this.fitCameraToScene(_objectSpread2(_objectSpread2({}, options), {}, {
25822
+ direction: DEFAULT_HOME_VIEW_DIRECTION,
25823
+ groundLevel: true
25824
+ }));
25825
+ }
25826
+
25827
+ /**
25828
+ * Toggle camera auto-rotation on/off
25829
+ *
25830
+ * @returns {boolean} The new auto-rotation state
25831
+ */
25832
+ }, {
25833
+ key: "toggleCameraRotation",
25834
+ value: function toggleCameraRotation() {
25835
+ var component = this.sceneViewer;
25836
+ if (!component.controls || !component.sceneInitializationManager) {
25837
+ console.warn('⚠️ Cannot toggle camera rotation: missing controls or sceneInitializationManager');
25838
+ return false;
25839
+ }
25840
+
25841
+ // Use the sceneInitializationManager to toggle auto-rotation
25842
+ var isAutoRotating = component.sceneInitializationManager.toggleAutoRotation();
25843
+
25844
+ // Update our internal state
25845
+ this._isAutoRotating = isAutoRotating;
25846
+
25847
+ // Set autoRotate speed if needed (using the stored preference)
25848
+ if (isAutoRotating && component.controls.autoRotateSpeed !== this.autoRotateSpeed) {
25849
+ component.controls.autoRotateSpeed = this.autoRotateSpeed;
25850
+ }
25851
+ console.log("\uD83D\uDD04 Camera auto-rotation ".concat(isAutoRotating ? 'enabled' : 'disabled', " via CameraControlsManager"));
25852
+ return isAutoRotating;
25853
+ }
25854
+
25855
+ /**
25856
+ * Get the current auto-rotation state
25857
+ *
25858
+ * @returns {boolean} Whether auto-rotation is enabled
25859
+ */
25860
+ }, {
25861
+ key: "isAutoRotating",
25862
+ value: function isAutoRotating() {
25863
+ // If controls exist, sync our state with actual control state
25864
+ if (this.sceneViewer.controls) {
25865
+ this._isAutoRotating = this.sceneViewer.controls.autoRotate;
25866
+ }
25867
+ return this._isAutoRotating;
25868
+ }
25869
+
25870
+ /**
25871
+ * Directly enable or disable camera auto-rotation
25872
+ *
25873
+ * @param {boolean} enable - Whether to enable auto-rotation
25874
+ * @returns {boolean} The new auto-rotation state
25875
+ */
25876
+ }, {
25877
+ key: "setAutoRotation",
25878
+ value: function setAutoRotation(enable) {
25879
+ var component = this.sceneViewer;
25880
+ if (component.sceneInitializationManager) {
25881
+ var result = component.sceneInitializationManager.toggleAutoRotation(enable);
25882
+ this._isAutoRotating = result;
25883
+ return result;
25884
+ }
25885
+ return false;
25886
+ }
25887
+ }]);
25888
+ }();
25889
+
25491
25890
  var SceneInitializationManager = /*#__PURE__*/function () {
25492
25891
  function SceneInitializationManager(sceneViewer) {
25493
25892
  _classCallCheck(this, SceneInitializationManager);
@@ -25515,7 +25914,7 @@ var SceneInitializationManager = /*#__PURE__*/function () {
25515
25914
  containerWidth = containerRect.width;
25516
25915
  containerHeight = containerRect.height; // Create camera (Z-up coordinate system with flipped Y)
25517
25916
  component.camera = new THREE__namespace.PerspectiveCamera(50, containerWidth / containerHeight, 0.01, 1000);
25518
- component.camera.position.set(-8, -9, 2); // Flipped Y direction
25917
+ component.camera.position.set(DEFAULT_HOME_VIEW_DIRECTION.x, DEFAULT_HOME_VIEW_DIRECTION.y, HOME_EMPTY_CAMERA_HEIGHT_ABOVE_GROUND);
25519
25918
  component.camera.up.set(0, 0, 1); // Set Z as up vector
25520
25919
 
25521
25920
  // Create renderer
@@ -29209,204 +29608,344 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
29209
29608
  }
29210
29609
 
29211
29610
  /**
29212
- * Enrich scene data with world-space bounding boxes and positions for collision avoidance
29213
- * @param {Object|Array} sceneData - Original scene data or array of children
29214
- * @returns {Object|Array} Enriched scene data (shallow copy with modifications)
29611
+ * Enrich sceneData with worldBoundingBox for segments and components
29612
+ * Connectors remain with just position information.
29613
+ *
29614
+ * Uses _bboxCache to avoid recomputing filtered / io-device bboxes
29615
+ * when the underlying object's world matrix has not changed.
29616
+ *
29617
+ * @param {Object} sceneData - Original scene data
29618
+ * @returns {Object} Enriched scene data (shallow copy with modifications)
29215
29619
  * @private
29216
29620
  */
29217
29621
  }, {
29218
29622
  key: "_enrichSceneDataWithBoundingBoxes",
29219
29623
  value: function _enrichSceneDataWithBoundingBoxes(sceneData) {
29220
29624
  var _this4 = this;
29221
- if (!sceneData) return sceneData;
29222
-
29223
- // Recursive helper to enrich a node and its children
29224
- var _enrichNode = function enrichNode(node) {
29225
- if (!node) return node;
29226
- var enrichedNode = _objectSpread2({}, node);
29625
+ // Create a shallow copy of sceneData structure
29626
+ var enriched = _objectSpread2({}, sceneData);
29627
+ if (!sceneData.children || !Array.isArray(sceneData.children)) {
29628
+ console.warn('⚠️ sceneData has no children array');
29629
+ return enriched;
29630
+ }
29227
29631
 
29632
+ // Process children to add worldBoundingBox to segments and components
29633
+ enriched.children = sceneData.children.map(function (child) {
29228
29634
  // Skip computed objects ( elbows etc )
29229
- if (node.userData && (node.userData.isComputed === true || node.userData.objectType === 'elbow')) {
29230
- return node;
29635
+ if (child.userData && (child.userData.isComputed === true || child.userData.objectType === 'elbow')) {
29636
+ return child;
29231
29637
  }
29232
29638
 
29233
- // ── Enrich Segments ──
29234
- if (node.userData && node.userData.objectType === 'segment') {
29235
- var segmentObject = _this4.sceneViewer.scene.getObjectByProperty('uuid', node.uuid);
29639
+ // Enrich segments (check if objectType is 'segment' in userData)
29640
+ if (child.userData && child.userData.objectType === 'segment') {
29641
+ // Find the actual segment object in the scene
29642
+ var segmentObject = _this4.sceneViewer.scene.getObjectByProperty('uuid', child.uuid);
29236
29643
  if (segmentObject) {
29644
+ // ── Cache check ──
29237
29645
  var hash = _this4._matrixHash(segmentObject);
29238
- var cached = _this4._bboxCache.get(node.uuid);
29646
+ var cached = _this4._bboxCache.get(child.uuid);
29239
29647
  if (cached && cached.matrixHash === hash && cached.segmentBBox) {
29240
- enrichedNode.userData = _objectSpread2(_objectSpread2({}, node.userData), {}, {
29241
- worldBoundingBox: cached.segmentBBox
29242
- });
29243
- } else {
29244
- var worldBBox = new THREE__namespace.Box3().setFromObject(segmentObject);
29245
- var bboxData = {
29246
- min: [worldBBox.min.x, worldBBox.min.y, worldBBox.min.z],
29247
- max: [worldBBox.max.x, worldBBox.max.y, worldBBox.max.z]
29248
- };
29249
- _this4._bboxCache.set(node.uuid, {
29250
- matrixHash: hash,
29251
- segmentBBox: bboxData
29252
- });
29253
- enrichedNode.userData = _objectSpread2(_objectSpread2({}, node.userData), {}, {
29254
- worldBoundingBox: bboxData
29648
+ return _objectSpread2(_objectSpread2({}, child), {}, {
29649
+ userData: _objectSpread2(_objectSpread2({}, child.userData), {}, {
29650
+ worldBoundingBox: cached.segmentBBox
29651
+ })
29255
29652
  });
29256
29653
  }
29654
+
29655
+ // Compute world bounding box
29656
+ var worldBBox = new THREE__namespace.Box3().setFromObject(segmentObject);
29657
+ var bboxData = {
29658
+ min: [worldBBox.min.x, worldBBox.min.y, worldBBox.min.z],
29659
+ max: [worldBBox.max.x, worldBBox.max.y, worldBBox.max.z]
29660
+ };
29661
+
29662
+ // Store in cache
29663
+ _this4._bboxCache.set(child.uuid, {
29664
+ matrixHash: hash,
29665
+ segmentBBox: bboxData
29666
+ });
29667
+ return _objectSpread2(_objectSpread2({}, child), {}, {
29668
+ userData: _objectSpread2(_objectSpread2({}, child.userData), {}, {
29669
+ worldBoundingBox: bboxData
29670
+ })
29671
+ });
29672
+ } else {
29673
+ console.warn("\u26A0\uFE0F Could not find segment object in scene: ".concat(child.uuid));
29257
29674
  }
29258
29675
  }
29259
29676
 
29260
- // ── Enrich Components ──
29261
- else if (node.userData && node.userData.objectType === 'component') {
29262
- var componentObject = _this4.sceneViewer.scene.getObjectByProperty('uuid', node.uuid);
29677
+ // Enrich components (check if objectType is 'component' in userData)
29678
+ if (child.userData && child.userData.objectType === 'component') {
29679
+ // Find the actual component object in the scene
29680
+ var componentObject = _this4.sceneViewer.scene.getObjectByProperty('uuid', child.uuid);
29263
29681
  if (componentObject) {
29682
+ // Explicitly update matrices for this component subtree before computing bounding boxes
29264
29683
  componentObject.updateMatrixWorld(true);
29684
+
29685
+ // ── Cache check ──
29265
29686
  var _hash = _this4._matrixHash(componentObject);
29266
- var _cached = _this4._bboxCache.get(node.uuid);
29687
+ var _cached = _this4._bboxCache.get(child.uuid);
29267
29688
  if (_cached && _cached.matrixHash === _hash && _cached.filteredBBox) {
29268
- enrichedNode.position = {
29269
- x: componentObject.position.x,
29270
- y: componentObject.position.y,
29271
- z: componentObject.position.z
29272
- };
29273
- enrichedNode.rotation = {
29274
- x: THREE__namespace.MathUtils.radToDeg(componentObject.rotation.x),
29275
- y: THREE__namespace.MathUtils.radToDeg(componentObject.rotation.y),
29276
- z: THREE__namespace.MathUtils.radToDeg(componentObject.rotation.z)
29277
- };
29278
- enrichedNode.userData = _objectSpread2(_objectSpread2({}, node.userData), {}, {
29279
- worldBoundingBox: _cached.filteredBBox,
29280
- position: [componentObject.position.x, componentObject.position.y, componentObject.position.z]
29689
+ // Rebuild enriched child from cached data
29690
+ var _enrichedChild = _objectSpread2(_objectSpread2({}, child), {}, {
29691
+ // Sync live transform even on cache hit just in case the manifest (child) is stale
29692
+ position: {
29693
+ x: componentObject.position.x,
29694
+ y: componentObject.position.y,
29695
+ z: componentObject.position.z
29696
+ },
29697
+ rotation: {
29698
+ x: THREE__namespace.MathUtils.radToDeg(componentObject.rotation.x),
29699
+ y: THREE__namespace.MathUtils.radToDeg(componentObject.rotation.y),
29700
+ z: THREE__namespace.MathUtils.radToDeg(componentObject.rotation.z)
29701
+ },
29702
+ userData: _objectSpread2(_objectSpread2({}, child.userData), {}, {
29703
+ worldBoundingBox: _cached.filteredBBox,
29704
+ position: [componentObject.position.x, componentObject.position.y, componentObject.position.z]
29705
+ })
29281
29706
  });
29282
-
29283
- // Re-merge cached connectors and devices if they exist
29284
- if (!enrichedNode.children) enrichedNode.children = [];
29285
- if (_cached.connectorBBoxes) {
29286
- _cached.connectorBBoxes.forEach(function (conn) {
29287
- return _this4._mergeEnrichedChild(enrichedNode.children, conn);
29707
+ if (_cached.ioDeviceBBoxes && _cached.ioDeviceBBoxes.length > 0) {
29708
+ if (!_enrichedChild.children) _enrichedChild.children = [];
29709
+ _cached.ioDeviceBBoxes.forEach(function (deviceBBox) {
29710
+ var existingIndex = _enrichedChild.children.findIndex(function (c) {
29711
+ return c.uuid === deviceBBox.uuid;
29712
+ });
29713
+ if (existingIndex >= 0) {
29714
+ _enrichedChild.children[existingIndex] = _objectSpread2(_objectSpread2({}, _enrichedChild.children[existingIndex]), {}, {
29715
+ position: {
29716
+ x: deviceBBox.userData.position[0],
29717
+ y: deviceBBox.userData.position[1],
29718
+ z: deviceBBox.userData.position[2]
29719
+ },
29720
+ userData: _objectSpread2(_objectSpread2({}, _enrichedChild.children[existingIndex].userData), {}, {
29721
+ objectType: 'io-device',
29722
+ worldBoundingBox: deviceBBox.worldBoundingBox,
29723
+ position: deviceBBox.userData.position
29724
+ })
29725
+ });
29726
+ } else {
29727
+ _enrichedChild.children.push({
29728
+ uuid: deviceBBox.uuid,
29729
+ position: {
29730
+ x: deviceBBox.userData.position[0],
29731
+ y: deviceBBox.userData.position[1],
29732
+ z: deviceBBox.userData.position[2]
29733
+ },
29734
+ userData: _objectSpread2(_objectSpread2({}, deviceBBox.userData), {}, {
29735
+ worldBoundingBox: deviceBBox.worldBoundingBox,
29736
+ position: deviceBBox.userData.position
29737
+ }),
29738
+ children: []
29739
+ });
29740
+ }
29288
29741
  });
29289
29742
  }
29290
- if (_cached.ioDeviceBBoxes) {
29291
- _cached.ioDeviceBBoxes.forEach(function (dev) {
29292
- return _this4._mergeEnrichedChild(enrichedNode.children, dev);
29743
+
29744
+ // Also reinject connectors from cache
29745
+ if (_cached.connectorBBoxes && _cached.connectorBBoxes.length > 0) {
29746
+ if (!_enrichedChild.children) _enrichedChild.children = [];
29747
+ _cached.connectorBBoxes.forEach(function (connBBox) {
29748
+ var existingIndex = _enrichedChild.children.findIndex(function (c) {
29749
+ return c.uuid === connBBox.uuid;
29750
+ });
29751
+ if (existingIndex >= 0) {
29752
+ _enrichedChild.children[existingIndex] = _objectSpread2(_objectSpread2({}, _enrichedChild.children[existingIndex]), {}, {
29753
+ position: {
29754
+ x: connBBox.userData.position[0],
29755
+ y: connBBox.userData.position[1],
29756
+ z: connBBox.userData.position[2]
29757
+ },
29758
+ userData: _objectSpread2(_objectSpread2({}, _enrichedChild.children[existingIndex].userData), {}, {
29759
+ worldBoundingBox: connBBox.worldBoundingBox,
29760
+ position: connBBox.userData.position
29761
+ })
29762
+ });
29763
+ } else {
29764
+ _enrichedChild.children.push({
29765
+ uuid: connBBox.uuid,
29766
+ position: {
29767
+ x: connBBox.userData.position[0],
29768
+ y: connBBox.userData.position[1],
29769
+ z: connBBox.userData.position[2]
29770
+ },
29771
+ userData: _objectSpread2(_objectSpread2({}, connBBox.userData), {}, {
29772
+ worldBoundingBox: connBBox.worldBoundingBox,
29773
+ position: connBBox.userData.position
29774
+ }),
29775
+ children: []
29776
+ });
29777
+ }
29293
29778
  });
29294
29779
  }
29295
- } else {
29296
- var filteredBBox = computeFilteredBoundingBox(componentObject, ['io-device', 'connector']);
29297
- var _bboxData = {
29298
- min: [filteredBBox.min.x, filteredBBox.min.y, filteredBBox.min.z],
29299
- max: [filteredBBox.max.x, filteredBBox.max.y, filteredBBox.max.z]
29300
- };
29301
- enrichedNode.position = {
29780
+ return _enrichedChild;
29781
+ }
29782
+
29783
+ // Compute FILTERED bounding box — excludes io-device and connector subtrees
29784
+ // so the component body bbox is tight-fitting and doesn't envelop attached devices
29785
+ var filteredBBox = computeFilteredBoundingBox(componentObject, ['io-device', 'connector']);
29786
+ console.log("\uD83D\uDD04 Updated worldBoundingBox for component ".concat(child.uuid, " (filtered): min=[").concat(filteredBBox.min.x.toFixed(2), ", ").concat(filteredBBox.min.y.toFixed(2), ", ").concat(filteredBBox.min.z.toFixed(2), "], max=[").concat(filteredBBox.max.x.toFixed(2), ", ").concat(filteredBBox.max.y.toFixed(2), ", ").concat(filteredBBox.max.z.toFixed(2), "]"));
29787
+ var _bboxData = {
29788
+ min: [filteredBBox.min.x, filteredBBox.min.y, filteredBBox.min.z],
29789
+ max: [filteredBBox.max.x, filteredBBox.max.y, filteredBBox.max.z]
29790
+ };
29791
+
29792
+ // Build the enriched component entry
29793
+ var enrichedChild = _objectSpread2(_objectSpread2({}, child), {}, {
29794
+ // Sync live transform to ensure pathfinder has latest coordinates
29795
+ position: {
29302
29796
  x: componentObject.position.x,
29303
29797
  y: componentObject.position.y,
29304
29798
  z: componentObject.position.z
29305
- };
29306
- enrichedNode.rotation = {
29799
+ },
29800
+ rotation: {
29307
29801
  x: THREE__namespace.MathUtils.radToDeg(componentObject.rotation.x),
29308
29802
  y: THREE__namespace.MathUtils.radToDeg(componentObject.rotation.y),
29309
29803
  z: THREE__namespace.MathUtils.radToDeg(componentObject.rotation.z)
29310
- };
29311
- enrichedNode.userData = _objectSpread2(_objectSpread2({}, node.userData), {}, {
29804
+ },
29805
+ userData: _objectSpread2(_objectSpread2({}, child.userData), {}, {
29312
29806
  worldBoundingBox: _bboxData,
29313
29807
  position: [componentObject.position.x, componentObject.position.y, componentObject.position.z]
29808
+ })
29809
+ });
29810
+
29811
+ // Compute separate bounding boxes for each attached io-device
29812
+ var ioDeviceBBoxes = computeIODeviceBoundingBoxes(componentObject);
29813
+ if (ioDeviceBBoxes.length > 0) {
29814
+ // Ensure children array exists (may already contain connectors)
29815
+ if (!enrichedChild.children) {
29816
+ enrichedChild.children = [];
29817
+ }
29818
+
29819
+ // Inject io-device entries with their own worldBoundingBox
29820
+ ioDeviceBBoxes.forEach(function (deviceBBox) {
29821
+ var existingIndex = enrichedChild.children.findIndex(function (c) {
29822
+ return c.uuid === deviceBBox.uuid;
29823
+ });
29824
+ if (existingIndex >= 0) {
29825
+ enrichedChild.children[existingIndex] = _objectSpread2(_objectSpread2({}, enrichedChild.children[existingIndex]), {}, {
29826
+ position: {
29827
+ x: deviceBBox.userData.position[0],
29828
+ y: deviceBBox.userData.position[1],
29829
+ z: deviceBBox.userData.position[2]
29830
+ },
29831
+ userData: _objectSpread2(_objectSpread2({}, enrichedChild.children[existingIndex].userData), {}, {
29832
+ objectType: 'io-device',
29833
+ worldBoundingBox: deviceBBox.worldBoundingBox,
29834
+ position: deviceBBox.userData.position
29835
+ })
29836
+ });
29837
+ } else {
29838
+ enrichedChild.children.push({
29839
+ uuid: deviceBBox.uuid,
29840
+ position: {
29841
+ x: deviceBBox.userData.position[0],
29842
+ y: deviceBBox.userData.position[1],
29843
+ z: deviceBBox.userData.position[2]
29844
+ },
29845
+ userData: _objectSpread2(_objectSpread2({}, deviceBBox.userData), {}, {
29846
+ worldBoundingBox: deviceBBox.worldBoundingBox,
29847
+ position: deviceBBox.userData.position
29848
+ }),
29849
+ children: []
29850
+ });
29851
+ }
29852
+ console.log("\uD83D\uDCE6 Injected io-device bbox for ".concat(deviceBBox.uuid, ": min=[").concat(deviceBBox.worldBoundingBox.min.map(function (v) {
29853
+ return v.toFixed(2);
29854
+ }).join(', '), "], max=[").concat(deviceBBox.worldBoundingBox.max.map(function (v) {
29855
+ return v.toFixed(2);
29856
+ }).join(', '), "]"));
29314
29857
  });
29315
- var connectorBBoxes = computeConnectorBoundingBoxes(componentObject);
29316
- var ioDeviceBBoxes = computeIODeviceBoundingBoxes(componentObject);
29317
- if (!enrichedNode.children) enrichedNode.children = [];
29318
- connectorBBoxes.forEach(function (conn) {
29319
- return _this4._mergeEnrichedChild(enrichedNode.children, conn);
29320
- });
29321
- ioDeviceBBoxes.forEach(function (dev) {
29322
- return _this4._mergeEnrichedChild(enrichedNode.children, dev);
29323
- });
29324
- _this4._bboxCache.set(node.uuid, {
29325
- matrixHash: _hash,
29326
- filteredBBox: _bboxData,
29327
- ioDeviceBBoxes: ioDeviceBBoxes,
29328
- connectorBBoxes: connectorBBoxes
29858
+ console.log("\uD83D\uDCE6 Injected ".concat(ioDeviceBBoxes.length, " io-device bounding box(es) for component ").concat(child.uuid));
29859
+ }
29860
+
29861
+ // PHASE 2: Enrich Connectors (CRITICAL for pathfinding API)
29862
+ // Also compute world bounding boxes for connectors and inject into children.
29863
+ // This ensures endpoints have world coordinates in sceneDataCopy.
29864
+ var connectorBBoxes = computeConnectorBoundingBoxes(componentObject);
29865
+ if (connectorBBoxes.length > 0) {
29866
+ if (!enrichedChild.children) enrichedChild.children = [];
29867
+ connectorBBoxes.forEach(function (connBBox) {
29868
+ var existingIndex = enrichedChild.children.findIndex(function (c) {
29869
+ return c.uuid === connBBox.uuid;
29870
+ });
29871
+ if (existingIndex >= 0) {
29872
+ enrichedChild.children[existingIndex] = _objectSpread2(_objectSpread2({}, enrichedChild.children[existingIndex]), {}, {
29873
+ position: {
29874
+ x: connBBox.userData.position[0],
29875
+ y: connBBox.userData.position[1],
29876
+ z: connBBox.userData.position[2]
29877
+ },
29878
+ userData: _objectSpread2(_objectSpread2({}, enrichedChild.children[existingIndex].userData), {}, {
29879
+ worldBoundingBox: connBBox.worldBoundingBox,
29880
+ position: connBBox.userData.position
29881
+ })
29882
+ });
29883
+ } else {
29884
+ enrichedChild.children.push({
29885
+ uuid: connBBox.uuid,
29886
+ position: {
29887
+ x: connBBox.userData.position[0],
29888
+ y: connBBox.userData.position[1],
29889
+ z: connBBox.userData.position[2]
29890
+ },
29891
+ userData: _objectSpread2(_objectSpread2({}, connBBox.userData), {}, {
29892
+ worldBoundingBox: connBBox.worldBoundingBox,
29893
+ position: connBBox.userData.position
29894
+ }),
29895
+ children: []
29896
+ });
29897
+ }
29329
29898
  });
29330
29899
  }
29900
+
29901
+ // Store in cache
29902
+ _this4._bboxCache.set(child.uuid, {
29903
+ matrixHash: _hash,
29904
+ filteredBBox: _bboxData,
29905
+ ioDeviceBBoxes: ioDeviceBBoxes,
29906
+ connectorBBoxes: connectorBBoxes
29907
+ });
29908
+ return enrichedChild;
29909
+ } else {
29910
+ console.warn("\u26A0\uFE0F Could not find component object in scene: ".concat(child.uuid));
29331
29911
  }
29332
29912
  }
29333
29913
 
29334
- // ── Enrich Standalone Objects (Gateways/Connectors) ──
29335
- else if (node.userData && (node.userData.objectType === 'gateway' || node.userData.objectType === 'connector')) {
29336
- var _node$userData;
29337
- var object = _this4.sceneViewer.scene.getObjectByProperty('uuid', node.uuid) || _this4.sceneViewer.scene.getObjectByProperty('uuid', (_node$userData = node.userData) === null || _node$userData === void 0 ? void 0 : _node$userData.originalUuid);
29914
+ // ────────────────────────────────────────────────────────────────────────
29915
+ // PHASE 3: Handle top-level Gateways and Connectors
29916
+ // ────────────────────────────────────────────────────────────────────────
29917
+ if (child.userData && (child.userData.objectType === 'gateway' || child.userData.objectType === 'connector')) {
29918
+ var _child$userData;
29919
+ var object = _this4.sceneViewer.scene.getObjectByProperty('uuid', child.uuid) || _this4.sceneViewer.scene.getObjectByProperty('uuid', (_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.originalUuid);
29338
29920
  if (object) {
29339
29921
  var worldPos = new THREE__namespace.Vector3();
29340
29922
  object.getWorldPosition(worldPos);
29341
- enrichedNode.position = {
29342
- x: worldPos.x,
29343
- y: worldPos.y,
29344
- z: worldPos.z
29345
- };
29346
- enrichedNode.userData = _objectSpread2(_objectSpread2({}, node.userData), {}, {
29347
- position: [worldPos.x, worldPos.y, worldPos.z]
29923
+ return _objectSpread2(_objectSpread2({}, child), {}, {
29924
+ // Sync live transform to ensure pathfinder has latest coordinates
29925
+ position: {
29926
+ x: worldPos.x,
29927
+ y: worldPos.y,
29928
+ z: worldPos.z
29929
+ },
29930
+ userData: _objectSpread2(_objectSpread2({}, child.userData), {}, {
29931
+ position: [worldPos.x, worldPos.y, worldPos.z]
29932
+ })
29348
29933
  });
29349
-
29350
- // If it's a connector, also sync direction
29351
- if (node.userData.objectType === 'connector') {
29352
- var worldDir = new THREE__namespace.Vector3(0, 0, 1);
29353
- if (node.userData.direction) worldDir.set(node.userData.direction[0], node.userData.direction[1], node.userData.direction[2]);
29354
- worldDir.applyQuaternion(object.getWorldQuaternion(new THREE__namespace.Quaternion())).normalize();
29355
- enrichedNode.userData.direction = [worldDir.x, worldDir.y, worldDir.z];
29356
- }
29357
29934
  }
29358
29935
  }
29359
29936
 
29360
- // Recurse into children
29361
- if (node.children && Array.isArray(node.children)) {
29362
- enrichedNode.children = node.children.map(_enrichNode);
29363
- }
29364
- return enrichedNode;
29365
- };
29366
-
29367
- // Handle root being an array or object
29368
- if (Array.isArray(sceneData)) {
29369
- return sceneData.map(_enrichNode);
29370
- } else if (sceneData.children && Array.isArray(sceneData.children)) {
29371
- var enrichedRoot = _objectSpread2({}, sceneData);
29372
- enrichedRoot.children = sceneData.children.map(_enrichNode);
29373
- return enrichedRoot;
29374
- } else {
29375
- return _enrichNode(sceneData);
29376
- }
29937
+ // For non-segments and non-components (and if object search failed), return as-is
29938
+ return child;
29939
+ });
29940
+ return enriched;
29377
29941
  }
29378
29942
 
29379
29943
  /**
29380
- * Helper to merge an enriched child (connector/device) into a children array
29381
- * @private
29944
+ * Core pathfinding logic shared across initialization and updates
29382
29945
  */
29383
- }, {
29384
- key: "_mergeEnrichedChild",
29385
- value: function _mergeEnrichedChild(childrenArray, enrichedData) {
29386
- var existingIndex = childrenArray.findIndex(function (c) {
29387
- return c.uuid === enrichedData.uuid;
29388
- });
29389
- var nodeToMerge = {
29390
- uuid: enrichedData.uuid,
29391
- position: {
29392
- x: enrichedData.userData.position[0],
29393
- y: enrichedData.userData.position[1],
29394
- z: enrichedData.userData.position[2]
29395
- },
29396
- userData: _objectSpread2(_objectSpread2({}, enrichedData.userData), {}, {
29397
- worldBoundingBox: enrichedData.worldBoundingBox
29398
- }),
29399
- children: []
29400
- };
29401
- if (existingIndex >= 0) {
29402
- childrenArray[existingIndex] = _objectSpread2(_objectSpread2({}, childrenArray[existingIndex]), nodeToMerge);
29403
- } else {
29404
- childrenArray.push(nodeToMerge);
29405
- }
29406
- }
29407
29946
  }, {
29408
29947
  key: "_executePathfinding",
29409
- value: function () {
29948
+ value: (function () {
29410
29949
  var _executePathfinding2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(sceneData, connections) {
29411
29950
  var _this5 = this,
29412
29951
  _sceneDataCopy$childr,
@@ -29581,7 +30120,7 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
29581
30120
  return _executePathfinding2.apply(this, arguments);
29582
30121
  }
29583
30122
  return _executePathfinding;
29584
- }()
30123
+ }())
29585
30124
  }, {
29586
30125
  key: "getSimplifiedSceneData",
29587
30126
  value: function getSimplifiedSceneData() {
@@ -34091,6 +34630,7 @@ var SceneOperationsManager = /*#__PURE__*/function () {
34091
34630
  }, {
34092
34631
  key: "_finalizeScene",
34093
34632
  value: function _finalizeScene(data, crosscubeTextureSet, isImported) {
34633
+ var _component$cameraCont;
34094
34634
  var component = this.sceneViewer;
34095
34635
  component.currentSceneData = data;
34096
34636
  component.crosscubeTextureSet = crosscubeTextureSet;
@@ -34102,6 +34642,14 @@ var SceneOperationsManager = /*#__PURE__*/function () {
34102
34642
  }
34103
34643
  this._setTransformControlsState(true, false); // Enable but keep hidden initially
34104
34644
  }
34645
+
34646
+ // Frame the camera so all components fit perfectly in view (low, ground-level angle)
34647
+ if ((_component$cameraCont = component.cameraControlsManager) !== null && _component$cameraCont !== void 0 && _component$cameraCont.fitCameraToScene) {
34648
+ component.cameraControlsManager.fitCameraToScene({
34649
+ direction: DEFAULT_HOME_VIEW_DIRECTION,
34650
+ groundLevel: true
34651
+ });
34652
+ }
34105
34653
  }
34106
34654
 
34107
34655
  /**
@@ -34984,82 +35532,6 @@ var AnimationManager = /*#__PURE__*/function (_BaseDisposable) {
34984
35532
  }]);
34985
35533
  }(BaseDisposable);
34986
35534
 
34987
- /**
34988
- * CameraControlsManager
34989
- * Handles camera movement, rotation, and other camera-specific controls
34990
- */
34991
-
34992
- var CameraControlsManager = /*#__PURE__*/function () {
34993
- function CameraControlsManager(component) {
34994
- _classCallCheck(this, CameraControlsManager);
34995
- this.sceneViewer = component;
34996
- this.autoRotateSpeed = 1.0; // Default rotation speed
34997
- this._isAutoRotating = false; // Internal state tracking
34998
- }
34999
-
35000
- /**
35001
- * Toggle camera auto-rotation on/off
35002
- *
35003
- * @returns {boolean} The new auto-rotation state
35004
- */
35005
- return _createClass(CameraControlsManager, [{
35006
- key: "toggleCameraRotation",
35007
- value: function toggleCameraRotation() {
35008
- var component = this.sceneViewer;
35009
- if (!component.controls || !component.sceneInitializationManager) {
35010
- console.warn('⚠️ Cannot toggle camera rotation: missing controls or sceneInitializationManager');
35011
- return false;
35012
- }
35013
-
35014
- // Use the sceneInitializationManager to toggle auto-rotation
35015
- var isAutoRotating = component.sceneInitializationManager.toggleAutoRotation();
35016
-
35017
- // Update our internal state
35018
- this._isAutoRotating = isAutoRotating;
35019
-
35020
- // Set autoRotate speed if needed (using the stored preference)
35021
- if (isAutoRotating && component.controls.autoRotateSpeed !== this.autoRotateSpeed) {
35022
- component.controls.autoRotateSpeed = this.autoRotateSpeed;
35023
- }
35024
- console.log("\uD83D\uDD04 Camera auto-rotation ".concat(isAutoRotating ? 'enabled' : 'disabled', " via CameraControlsManager"));
35025
- return isAutoRotating;
35026
- }
35027
-
35028
- /**
35029
- * Get the current auto-rotation state
35030
- *
35031
- * @returns {boolean} Whether auto-rotation is enabled
35032
- */
35033
- }, {
35034
- key: "isAutoRotating",
35035
- value: function isAutoRotating() {
35036
- // If controls exist, sync our state with actual control state
35037
- if (this.sceneViewer.controls) {
35038
- this._isAutoRotating = this.sceneViewer.controls.autoRotate;
35039
- }
35040
- return this._isAutoRotating;
35041
- }
35042
-
35043
- /**
35044
- * Directly enable or disable camera auto-rotation
35045
- *
35046
- * @param {boolean} enable - Whether to enable auto-rotation
35047
- * @returns {boolean} The new auto-rotation state
35048
- */
35049
- }, {
35050
- key: "setAutoRotation",
35051
- value: function setAutoRotation(enable) {
35052
- var component = this.sceneViewer;
35053
- if (component.sceneInitializationManager) {
35054
- var result = component.sceneInitializationManager.toggleAutoRotation(enable);
35055
- this._isAutoRotating = result;
35056
- return result;
35057
- }
35058
- return false;
35059
- }
35060
- }]);
35061
- }();
35062
-
35063
35535
  /**
35064
35536
  * ComponentDragManager handles drag and drop from external UI elements to the 3D scene
35065
35537
  */
@@ -41125,7 +41597,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
41125
41597
  * Initialize the CentralPlant manager
41126
41598
  *
41127
41599
  * @constructor
41128
- * @version 0.3.49
41600
+ * @version 0.3.50
41129
41601
  * @updated 2025-10-22
41130
41602
  *
41131
41603
  * @description Creates a new CentralPlant instance and initializes internal managers and utilities.
@@ -43001,6 +43473,98 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
43001
43473
  }
43002
43474
  }
43003
43475
 
43476
+ /**
43477
+ * Get the world-space axis-aligned bounding box for a scene object.
43478
+ * @param {string|THREE.Object3D} objectOrId - The object's UUID/name, or the Three.js object itself
43479
+ * @param {Object} [options={}] - Bounding box options
43480
+ * @param {boolean} [options.filtered=true] - When true, exclude connector and io-device
43481
+ * subtrees so the box reflects the component body footprint. When false, the box
43482
+ * envelops all descendants (equivalent to THREE.Box3.setFromObject).
43483
+ * @param {string[]} [options.excludeTypes] - Override the userData.objectType values to
43484
+ * exclude (defaults to ['io-device', 'connector'] when filtered is true).
43485
+ * @returns {{min: {x,y,z}, max: {x,y,z}, size: {x,y,z}, center: {x,y,z}}|null}
43486
+ * The bounding box descriptor in world space, or null if the object can't be found
43487
+ * or has no geometry.
43488
+ * @description Computes a tight world-space bounding box for a component (or any scene
43489
+ * object). By default the component body is measured independently of its attached
43490
+ * connectors and io-devices, which is useful for layout and spacing operations.
43491
+ * @example
43492
+ * // Component body bounding box (excludes connectors / io-devices)
43493
+ * const bbox = centralPlant.getBoundingBox('PUMP-1');
43494
+ * console.log(bbox.size.x, bbox.size.y, bbox.size.z);
43495
+ *
43496
+ * @example
43497
+ * // Full bounding box including connectors and io-devices
43498
+ * const fullBox = centralPlant.getBoundingBox('PUMP-1', { filtered: false });
43499
+ *
43500
+ * @since 0.3.50
43501
+ */
43502
+ }, {
43503
+ key: "getBoundingBox",
43504
+ value: function getBoundingBox(objectOrId) {
43505
+ var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
43506
+ if (!this.sceneViewer || !this.sceneViewer.scene) {
43507
+ console.warn('⚠️ getBoundingBox(): Scene viewer or scene not available');
43508
+ return null;
43509
+ }
43510
+ var _options$filtered = options.filtered,
43511
+ filtered = _options$filtered === void 0 ? true : _options$filtered,
43512
+ excludeTypes = options.excludeTypes;
43513
+
43514
+ // Resolve the target object
43515
+ var object = objectOrId;
43516
+ if (typeof objectOrId === 'string') {
43517
+ object = this.sceneViewer.scene.getObjectByProperty('uuid', objectOrId) || this.sceneViewer.scene.getObjectByProperty('name', objectOrId);
43518
+ if (!object) {
43519
+ // Fall back to a full traverse (matches translate's lookup by originalUuid)
43520
+ this.sceneViewer.scene.traverse(function (child) {
43521
+ var _child$userData2;
43522
+ if (!object && ((_child$userData2 = child.userData) === null || _child$userData2 === void 0 ? void 0 : _child$userData2.originalUuid) === objectOrId) {
43523
+ object = child;
43524
+ }
43525
+ });
43526
+ }
43527
+ }
43528
+ if (!object || typeof object.traverse !== 'function') {
43529
+ console.warn("\u26A0\uFE0F getBoundingBox(): Object '".concat(objectOrId, "' not found in scene"));
43530
+ return null;
43531
+ }
43532
+ try {
43533
+ var typesToExclude = filtered ? excludeTypes || ['io-device', 'connector'] : [];
43534
+ var box = computeFilteredBoundingBox(object, typesToExclude);
43535
+ if (box.isEmpty()) {
43536
+ return null;
43537
+ }
43538
+ var size = box.getSize(new THREE__namespace.Vector3());
43539
+ var center = box.getCenter(new THREE__namespace.Vector3());
43540
+ return {
43541
+ min: {
43542
+ x: box.min.x,
43543
+ y: box.min.y,
43544
+ z: box.min.z
43545
+ },
43546
+ max: {
43547
+ x: box.max.x,
43548
+ y: box.max.y,
43549
+ z: box.max.z
43550
+ },
43551
+ size: {
43552
+ x: size.x,
43553
+ y: size.y,
43554
+ z: size.z
43555
+ },
43556
+ center: {
43557
+ x: center.x,
43558
+ y: center.y,
43559
+ z: center.z
43560
+ }
43561
+ };
43562
+ } catch (error) {
43563
+ console.error('❌ getBoundingBox(): Error computing bounding box:', error);
43564
+ return null;
43565
+ }
43566
+ }
43567
+
43004
43568
  /**
43005
43569
  * Get available component categories
43006
43570
  * @returns {Array<Object>} Array of category objects with id, label, icon, and description
@@ -43235,8 +43799,8 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
43235
43799
  var componentDictionary = ((_this$managers$compon = this.managers.componentDataManager) === null || _this$managers$compon === void 0 ? void 0 : _this$managers$compon.componentDictionary) || {};
43236
43800
  var missingIds = [];
43237
43801
  sceneData.scene.children.forEach(function (child) {
43238
- var _child$userData2;
43239
- var libraryId = (_child$userData2 = child.userData) === null || _child$userData2 === void 0 ? void 0 : _child$userData2.libraryId;
43802
+ var _child$userData3;
43803
+ var libraryId = (_child$userData3 = child.userData) === null || _child$userData3 === void 0 ? void 0 : _child$userData3.libraryId;
43240
43804
  if (libraryId && !componentDictionary[libraryId]) {
43241
43805
  // Only add unique IDs
43242
43806
  if (!missingIds.includes(libraryId)) {
@@ -43314,8 +43878,8 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
43314
43878
  // If still not found, try finding by originalUuid in userData
43315
43879
  if (!targetObject) {
43316
43880
  this.sceneViewer.scene.traverse(function (child) {
43317
- var _child$userData3;
43318
- if (((_child$userData3 = child.userData) === null || _child$userData3 === void 0 ? void 0 : _child$userData3.originalUuid) === objectId) {
43881
+ var _child$userData4;
43882
+ if (((_child$userData4 = child.userData) === null || _child$userData4 === void 0 ? void 0 : _child$userData4.originalUuid) === objectId) {
43319
43883
  targetObject = child;
43320
43884
  return;
43321
43885
  }
@@ -49940,9 +50504,11 @@ exports.ComponentDataManager = ComponentDataManager;
49940
50504
  exports.ComponentDragManager = ComponentDragManager;
49941
50505
  exports.ComponentManager = ComponentManager;
49942
50506
  exports.ComponentTooltipManager = ComponentTooltipManager;
50507
+ exports.DEFAULT_HOME_VIEW_DIRECTION = DEFAULT_HOME_VIEW_DIRECTION;
49943
50508
  exports.EnvironmentManager = EnvironmentManager;
49944
50509
  exports.FLOW_ATTRIBUTE_KEYS = FLOW_ATTRIBUTE_KEYS;
49945
50510
  exports.GLOBAL_CACHE_NAME = GLOBAL_CACHE_NAME;
50511
+ exports.HOME_EMPTY_CAMERA_HEIGHT_ABOVE_GROUND = HOME_EMPTY_CAMERA_HEIGHT_ABOVE_GROUND;
49946
50512
  exports.IoBehaviorManager = IoBehaviorManager;
49947
50513
  exports.KeyboardControlsManager = KeyboardControlsManager;
49948
50514
  exports.ModelManager = ModelManager;