@2112-lab/central-plant 0.3.49 → 0.3.51

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.
@@ -2513,6 +2513,11 @@ var transformControls = /*#__PURE__*/function (_THREE$Object3D) {
2513
2513
  _this.showY = true;
2514
2514
  _this.showZ = true;
2515
2515
 
2516
+ // Axis interactivity (pickers). Visual handles can remain visible when false.
2517
+ _this.pickX = true;
2518
+ _this.pickY = true;
2519
+ _this.pickZ = true;
2520
+
2516
2521
  // Click timing for interaction delays
2517
2522
  _this.clickDelay = null;
2518
2523
  _this.lastInteractionTime = 0;
@@ -2666,6 +2671,9 @@ var transformControls = /*#__PURE__*/function (_THREE$Object3D) {
2666
2671
  this.lastInteractionTime = currentTime;
2667
2672
  }
2668
2673
  if (this.axis !== null) {
2674
+ if (!this._isAxisPickable(this.axis)) {
2675
+ return;
2676
+ }
2669
2677
  _raycaster.setFromCamera(pointer, this.camera);
2670
2678
  var planeIntersect = this._intersectObjectWithRay(this._plane, _raycaster, true);
2671
2679
  if (planeIntersect) {
@@ -2936,10 +2944,19 @@ var transformControls = /*#__PURE__*/function (_THREE$Object3D) {
2936
2944
  value: function getMode() {
2937
2945
  return this.mode;
2938
2946
  }
2947
+ }, {
2948
+ key: "_isAxisPickable",
2949
+ value: function _isAxisPickable(axisName) {
2950
+ if (!axisName) return false;
2951
+ if (axisName.indexOf('X') !== -1) return this.pickX !== false;
2952
+ if (axisName.indexOf('Y') !== -1) return this.pickY !== false;
2953
+ if (axisName.indexOf('Z') !== -1) return this.pickZ !== false;
2954
+ return true;
2955
+ }
2939
2956
  }, {
2940
2957
  key: "setMode",
2941
2958
  value: function setMode(mode) {
2942
- if (mode !== 'translate') {
2959
+ if (mode !== 'translate' && mode !== 'rotate') {
2943
2960
  console.warn("transformControls: ".concat(mode, " mode is disabled. Locking to translate."));
2944
2961
  mode = 'translate';
2945
2962
  }
@@ -3180,6 +3197,7 @@ var SimpleTransformGizmo = /*#__PURE__*/function (_THREE$Object3D2) {
3180
3197
  }, {
3181
3198
  key: "updateGizmoState",
3182
3199
  value: function updateGizmoState(controls) {
3200
+ var _this$gizmo$controls$, _this$gizmo$controls$2, _this$picker$controls, _this$picker$controls2;
3183
3201
  // Show only the gizmo for current mode
3184
3202
  this.gizmo['translate'].visible = controls.mode === 'translate';
3185
3203
  this.gizmo['rotate'].visible = controls.mode === 'rotate';
@@ -3197,43 +3215,62 @@ var SimpleTransformGizmo = /*#__PURE__*/function (_THREE$Object3D2) {
3197
3215
  // Force maximum render priority for all gizmo objects
3198
3216
  this._forceMaxRenderOrder();
3199
3217
 
3200
- // Update all handles for the current mode
3201
- var handles = [].concat(_toConsumableArray(this.picker[controls.mode].children), _toConsumableArray(this.gizmo[controls.mode].children));
3218
+ // Scale based on camera distance
3219
+ var factor;
3220
+ if (controls.camera.isOrthographicCamera) {
3221
+ factor = (controls.camera.top - controls.camera.bottom) / controls.camera.zoom;
3222
+ } else {
3223
+ factor = controls.worldPosition.distanceTo(controls.cameraPosition) * Math.min(1.9 * Math.tan(Math.PI * controls.camera.fov / 360) / controls.camera.zoom, 7);
3224
+ }
3225
+ this.scale.setScalar(factor * controls.size / 4);
3226
+ this._updateModeHandles(controls, (_this$gizmo$controls$ = (_this$gizmo$controls$2 = this.gizmo[controls.mode]) === null || _this$gizmo$controls$2 === void 0 ? void 0 : _this$gizmo$controls$2.children) !== null && _this$gizmo$controls$ !== void 0 ? _this$gizmo$controls$ : [], false);
3227
+ this._updateModeHandles(controls, (_this$picker$controls = (_this$picker$controls2 = this.picker[controls.mode]) === null || _this$picker$controls2 === void 0 ? void 0 : _this$picker$controls2.children) !== null && _this$picker$controls !== void 0 ? _this$picker$controls : [], true);
3228
+ }
3229
+ }, {
3230
+ key: "_isAxisShown",
3231
+ value: function _isAxisShown(controls, axisName) {
3232
+ if (axisName.indexOf('X') !== -1 && !controls.showX) return false;
3233
+ if (axisName.indexOf('Y') !== -1 && !controls.showY) return false;
3234
+ if (axisName.indexOf('Z') !== -1 && !controls.showZ) return false;
3235
+ return true;
3236
+ }
3237
+ }, {
3238
+ key: "_isAxisPickable",
3239
+ value: function _isAxisPickable(controls, axisName) {
3240
+ if (axisName.indexOf('X') !== -1) return controls.pickX !== false;
3241
+ if (axisName.indexOf('Y') !== -1) return controls.pickY !== false;
3242
+ if (axisName.indexOf('Z') !== -1) return controls.pickZ !== false;
3243
+ return true;
3244
+ }
3245
+ }, {
3246
+ key: "_updateModeHandles",
3247
+ value: function _updateModeHandles(controls, handles, isPicker) {
3202
3248
  var _iterator = _createForOfIteratorHelper(handles),
3203
3249
  _step;
3204
3250
  try {
3205
3251
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
3252
+ var _handle$material$_ori;
3206
3253
  var handle = _step.value;
3207
- handle.visible = true;
3208
-
3209
- // Scale based on camera distance
3210
- var factor = void 0;
3211
- if (controls.camera.isOrthographicCamera) {
3212
- factor = (controls.camera.top - controls.camera.bottom) / controls.camera.zoom;
3213
- } else {
3214
- factor = controls.worldPosition.distanceTo(controls.cameraPosition) * Math.min(1.9 * Math.tan(Math.PI * controls.camera.fov / 360) / controls.camera.zoom, 7);
3254
+ var axisName = handle.name;
3255
+ var shown = this._isAxisShown(controls, axisName);
3256
+ var pickable = this._isAxisPickable(controls, axisName);
3257
+ if (isPicker) {
3258
+ handle.visible = shown && pickable;
3259
+ continue;
3215
3260
  }
3216
-
3217
- // Apply scale to the entire gizmo, not individual handles
3218
- this.scale.setScalar(factor * controls.size / 4);
3219
-
3220
- // Apply axis visibility constraints
3221
- if (handle.name.indexOf('X') !== -1 && !controls.showX) handle.visible = false;
3222
- if (handle.name.indexOf('Y') !== -1 && !controls.showY) handle.visible = false;
3223
- if (handle.name.indexOf('Z') !== -1 && !controls.showZ) handle.visible = false;
3224
-
3225
- // Highlight selected axis
3226
- if (handle.material) {
3227
- handle.material._originalColor = handle.material._originalColor || handle.material.color.clone();
3228
- handle.material._originalOpacity = handle.material._originalOpacity || handle.material.opacity;
3261
+ handle.visible = shown;
3262
+ if (!handle.material) continue;
3263
+ handle.material._originalColor = handle.material._originalColor || handle.material.color.clone();
3264
+ handle.material._originalOpacity = (_handle$material$_ori = handle.material._originalOpacity) !== null && _handle$material$_ori !== void 0 ? _handle$material$_ori : handle.material.opacity;
3265
+ if (shown && pickable && controls.enabled && controls.axis === axisName) {
3266
+ handle.material.color.setHex(0xffff00);
3267
+ handle.material.opacity = 1.0;
3268
+ } else if (shown && !pickable) {
3269
+ handle.material.color.copy(handle.material._originalColor);
3270
+ handle.material.opacity = SimpleTransformGizmo.DISABLED_AXIS_OPACITY;
3271
+ } else {
3229
3272
  handle.material.color.copy(handle.material._originalColor);
3230
3273
  handle.material.opacity = handle.material._originalOpacity;
3231
- if (controls.enabled && controls.axis) {
3232
- if (handle.name === controls.axis) {
3233
- handle.material.color.setHex(0xffff00);
3234
- handle.material.opacity = 1.0;
3235
- }
3236
- }
3237
3274
  }
3238
3275
  }
3239
3276
  } catch (err) {
@@ -3283,6 +3320,7 @@ var SimpleTransformGizmo = /*#__PURE__*/function (_THREE$Object3D2) {
3283
3320
  * Optimized plane for transformation interactions
3284
3321
  * Simplified geometry and material for better performance
3285
3322
  */
3323
+ _defineProperty(SimpleTransformGizmo, "DISABLED_AXIS_OPACITY", 0.3);
3286
3324
  var SimpleTransformPlane = /*#__PURE__*/function (_THREE$Mesh) {
3287
3325
  function SimpleTransformPlane() {
3288
3326
  var _this4;
@@ -4171,6 +4209,7 @@ var TransformControlsManager = /*#__PURE__*/function () {
4171
4209
  this.eventHandlers.transformStart = function () {
4172
4210
  console.log("transformControls transformStart");
4173
4211
  _this2.transformState.isTransforming = true;
4212
+ _this2._suppressNextClick = true;
4174
4213
 
4175
4214
  // Store initial transforms for all selected objects
4176
4215
  if (_this2.selectedObjects.length > 0) {
@@ -4374,6 +4413,10 @@ var TransformControlsManager = /*#__PURE__*/function () {
4374
4413
  // Translate mode
4375
4414
  _this3.setMode('translate');
4376
4415
  break;
4416
+ case 'KeyR':
4417
+ // Rotate mode
4418
+ _this3.setMode('rotate');
4419
+ break;
4377
4420
  }
4378
4421
  };
4379
4422
  window.addEventListener('keydown', this.eventHandlers.keydown);
@@ -4558,8 +4601,16 @@ var TransformControlsManager = /*#__PURE__*/function () {
4558
4601
  }
4559
4602
 
4560
4603
  // Left-click: select for transform only (bounding box, no tooltip)
4604
+ var isComponent = objectType === 'component';
4605
+ var isSameSingleSelection = isComponent && _this4.selectedObjects.length === 1 && _this4.selectedObjects[0] === targetObject;
4606
+ if (isSameSingleSelection) {
4607
+ _this4.toggleTransformMode();
4608
+ return;
4609
+ }
4561
4610
  if (!_this4.selectedObjects.includes(targetObject)) {
4562
4611
  _this4.deselectObject();
4612
+ } else if (!isComponent) {
4613
+ _this4.setMode('translate');
4563
4614
  }
4564
4615
  _this4.selectObjectForTransformOnly(targetObject);
4565
4616
  };
@@ -5231,6 +5282,7 @@ var TransformControlsManager = /*#__PURE__*/function () {
5231
5282
  this._hideTransformControls();
5232
5283
  console.log("\uD83D\uDD12 Transform controls hidden after deselection");
5233
5284
  }
5285
+ this.setMode('translate');
5234
5286
 
5235
5287
  // Execute callback
5236
5288
  if (this.callbacks.onObjectSelect) {
@@ -5393,14 +5445,14 @@ var TransformControlsManager = /*#__PURE__*/function () {
5393
5445
  }, {
5394
5446
  key: "setMode",
5395
5447
  value: function setMode(mode) {
5396
- // Only allow translate mode - lock rotation and scale controls
5397
- if (mode !== 'translate') {
5448
+ if (mode !== 'translate' && mode !== 'rotate') {
5398
5449
  console.warn("\u26A0\uFE0F Transform mode ".concat(mode, " is disabled. Locking to translate."));
5399
5450
  mode = 'translate';
5400
5451
  }
5401
5452
  var previousMode = this.currentMode;
5402
5453
  this.currentMode = mode;
5403
5454
  this.transformControls.setMode(mode);
5455
+ this._updateAxisVisibilityForMode(mode);
5404
5456
 
5405
5457
  // Execute callback
5406
5458
  if (this.callbacks.onModeChange) {
@@ -5409,6 +5461,47 @@ var TransformControlsManager = /*#__PURE__*/function () {
5409
5461
  console.log("\uD83D\uDD27 Transform mode changed: ".concat(previousMode, " \u2192 ").concat(mode));
5410
5462
  }
5411
5463
 
5464
+ /**
5465
+ * Update gizmo axis visibility based on transform mode.
5466
+ * Rotation shows all rings but only Z-axis is interactive.
5467
+ * @param {'translate' | 'rotate'} mode
5468
+ * @private
5469
+ */
5470
+ }, {
5471
+ key: "_updateAxisVisibilityForMode",
5472
+ value: function _updateAxisVisibilityForMode(mode) {
5473
+ if (!this.transformControls) return;
5474
+ if (mode === 'rotate') {
5475
+ this.transformControls.showX = true;
5476
+ this.transformControls.showY = true;
5477
+ this.transformControls.showZ = true;
5478
+ this.transformControls.pickX = false;
5479
+ this.transformControls.pickY = false;
5480
+ this.transformControls.pickZ = true;
5481
+ return;
5482
+ }
5483
+ this.transformControls.showX = this.config.showX;
5484
+ this.transformControls.showY = this.config.showY;
5485
+ this.transformControls.showZ = this.config.showZ;
5486
+ this.transformControls.pickX = true;
5487
+ this.transformControls.pickY = true;
5488
+ this.transformControls.pickZ = true;
5489
+ }
5490
+
5491
+ /**
5492
+ * Toggle between translate and rotate transform modes
5493
+ */
5494
+ }, {
5495
+ key: "toggleTransformMode",
5496
+ value: function toggleTransformMode() {
5497
+ var _this$transformContro;
5498
+ var newMode = this.currentMode === 'translate' ? 'rotate' : 'translate';
5499
+ this.setMode(newMode);
5500
+ if ((_this$transformContro = this.transformControls) !== null && _this$transformContro !== void 0 && _this$transformContro.object) {
5501
+ this.transformControls.updateMatrixWorld(true);
5502
+ }
5503
+ }
5504
+
5412
5505
  /**
5413
5506
  * Set the transformation space
5414
5507
  * @param {'world' | 'local'} space - The transformation space
@@ -5847,12 +5940,10 @@ var TransformControlsManager = /*#__PURE__*/function () {
5847
5940
  obj.position.copy(newWorldPos);
5848
5941
  obj.updateMatrix();
5849
5942
  } else if (_this8.currentMode === 'rotate') {
5850
- // Calculate offset from centroid
5943
+ // Calculate offset from centroid (Z-axis rotation only)
5851
5944
  var offsetFromCentroid = originalWorldPos.clone().sub(originalCentroid);
5852
-
5853
- // Rotate the offset vector
5854
5945
  var rotatedOffset = offsetFromCentroid.clone();
5855
- var rotationQuat = new THREE__namespace.Quaternion().setFromEuler(rotationDelta);
5946
+ var rotationQuat = new THREE__namespace.Quaternion().setFromAxisAngle(new THREE__namespace.Vector3(0, 0, 1), rotationDelta.z);
5856
5947
  rotatedOffset.applyQuaternion(rotationQuat);
5857
5948
 
5858
5949
  // Calculate new world position
@@ -5868,9 +5959,9 @@ var TransformControlsManager = /*#__PURE__*/function () {
5868
5959
  }
5869
5960
  obj.position.copy(_newWorldPos);
5870
5961
 
5871
- // Apply rotation to the object itself
5872
- obj.rotation.x = originalRot.x + rotationDelta.x;
5873
- obj.rotation.y = originalRot.y + rotationDelta.y;
5962
+ // Apply Z-axis rotation only
5963
+ obj.rotation.x = originalRot.x;
5964
+ obj.rotation.y = originalRot.y;
5874
5965
  obj.rotation.z = originalRot.z + rotationDelta.z;
5875
5966
  obj.updateMatrix();
5876
5967
  }
@@ -5900,7 +5991,15 @@ var TransformControlsManager = /*#__PURE__*/function () {
5900
5991
  return _context2.a(2);
5901
5992
  case 1:
5902
5993
  console.log("\uD83D\uDD27 Applying multi-selection transform to ".concat(this.selectedObjects.length, " objects"));
5903
-
5994
+ if (!(this.currentMode === 'rotate')) {
5995
+ _context2.n = 3;
5996
+ break;
5997
+ }
5998
+ _context2.n = 2;
5999
+ return this._applyMultiSelectionRotation();
6000
+ case 2:
6001
+ return _context2.a(2);
6002
+ case 3:
5904
6003
  // Calculate transformation deltas
5905
6004
  _this$_calculateTrans = this._calculateTransformDeltas(), deltaX = _this$_calculateTrans.deltaX, deltaY = _this$_calculateTrans.deltaY, deltaZ = _this$_calculateTrans.deltaZ, threshold = _this$_calculateTrans.threshold;
5906
6005
  console.log('🔧 Transform delta:', {
@@ -5910,12 +6009,12 @@ var TransformControlsManager = /*#__PURE__*/function () {
5910
6009
  });
5911
6010
 
5912
6011
  // Only process if there's a meaningful translation
5913
- if (!(this.currentMode !== 'translate' || Math.abs(deltaX) < threshold && Math.abs(deltaY) < threshold && Math.abs(deltaZ) < threshold)) {
5914
- _context2.n = 2;
6012
+ if (!(Math.abs(deltaX) < threshold && Math.abs(deltaY) < threshold && Math.abs(deltaZ) < threshold)) {
6013
+ _context2.n = 4;
5915
6014
  break;
5916
6015
  }
5917
6016
  return _context2.a(2);
5918
- case 2:
6017
+ case 4:
5919
6018
  // Reset all objects to original positions
5920
6019
  this._resetObjectsToOriginalPositions();
5921
6020
 
@@ -5929,20 +6028,20 @@ var TransformControlsManager = /*#__PURE__*/function () {
5929
6028
  components = this.selectedObjects.filter(function (obj) {
5930
6029
  return !isSegment(obj) && !isGateway(obj);
5931
6030
  }); // Translate each object type
5932
- _context2.n = 3;
6031
+ _context2.n = 5;
5933
6032
  return this._translateSegments(segments, deltaX, deltaY, deltaZ, threshold);
5934
- case 3:
5935
- _context2.n = 4;
6033
+ case 5:
6034
+ _context2.n = 6;
5936
6035
  return this._translateGateways(gateways, deltaX, deltaY, deltaZ, threshold);
5937
- case 4:
5938
- _context2.n = 5;
6036
+ case 6:
6037
+ _context2.n = 7;
5939
6038
  return this._translateComponents(components, deltaX, deltaY, deltaZ, threshold);
5940
- case 5:
6039
+ case 7:
5941
6040
  console.log("\u2705 All ".concat(this.selectedObjects.length, " objects translated"));
5942
6041
 
5943
6042
  // Cleanup and refresh
5944
6043
  this._finalizeMultiSelectionTransform();
5945
- case 6:
6044
+ case 8:
5946
6045
  return _context2.a(2);
5947
6046
  }
5948
6047
  }, _callee2, this);
@@ -5952,6 +6051,123 @@ var TransformControlsManager = /*#__PURE__*/function () {
5952
6051
  }
5953
6052
  return applyMultiSelectionTransform;
5954
6053
  }()
6054
+ /**
6055
+ * Apply rotation from multi-selection group to selected components via API
6056
+ * @private
6057
+ */
6058
+ )
6059
+ }, {
6060
+ key: "_applyMultiSelectionRotation",
6061
+ value: (function () {
6062
+ var _applyMultiSelectionRotation2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee3() {
6063
+ var threshold, rotationDeltas, _iterator6, _step6, _obj2, originalRot, originalPos, _deltaRad, _i, _rotationDeltas, _obj$userData5, _obj$userData6, _obj$userData7, _this$centralPlant, _rotationDeltas$_i, obj, deltaRad, componentId, deltaDeg, snappedDeg, _t;
6064
+ return _regenerator().w(function (_context3) {
6065
+ while (1) switch (_context3.n) {
6066
+ case 0:
6067
+ threshold = 0.001;
6068
+ rotationDeltas = [];
6069
+ _iterator6 = _createForOfIteratorHelper(this.selectedObjects);
6070
+ _context3.p = 1;
6071
+ _iterator6.s();
6072
+ case 2:
6073
+ if ((_step6 = _iterator6.n()).done) {
6074
+ _context3.n = 6;
6075
+ break;
6076
+ }
6077
+ _obj2 = _step6.value;
6078
+ originalRot = _obj2.userData._multiSelectOriginalRotation;
6079
+ originalPos = _obj2.userData._multiSelectOriginalPosition;
6080
+ if (originalRot) {
6081
+ _context3.n = 3;
6082
+ break;
6083
+ }
6084
+ return _context3.a(3, 5);
6085
+ case 3:
6086
+ _deltaRad = _obj2.rotation.z - originalRot.z;
6087
+ if (!(Math.abs(_deltaRad) < threshold)) {
6088
+ _context3.n = 4;
6089
+ break;
6090
+ }
6091
+ return _context3.a(3, 5);
6092
+ case 4:
6093
+ rotationDeltas.push({
6094
+ obj: _obj2,
6095
+ originalPos: originalPos === null || originalPos === void 0 ? void 0 : originalPos.clone(),
6096
+ originalRot: originalRot.clone(),
6097
+ deltaRad: _deltaRad
6098
+ });
6099
+ case 5:
6100
+ _context3.n = 2;
6101
+ break;
6102
+ case 6:
6103
+ _context3.n = 8;
6104
+ break;
6105
+ case 7:
6106
+ _context3.p = 7;
6107
+ _t = _context3.v;
6108
+ _iterator6.e(_t);
6109
+ case 8:
6110
+ _context3.p = 8;
6111
+ _iterator6.f();
6112
+ return _context3.f(8);
6113
+ case 9:
6114
+ if (!(rotationDeltas.length === 0)) {
6115
+ _context3.n = 10;
6116
+ break;
6117
+ }
6118
+ return _context3.a(2);
6119
+ case 10:
6120
+ rotationDeltas.forEach(function (_ref2) {
6121
+ var obj = _ref2.obj,
6122
+ originalPos = _ref2.originalPos,
6123
+ originalRot = _ref2.originalRot;
6124
+ if (originalPos) {
6125
+ obj.position.copy(originalPos);
6126
+ }
6127
+ obj.rotation.copy(originalRot);
6128
+ obj.updateMatrixWorld(true);
6129
+ });
6130
+ _i = 0, _rotationDeltas = rotationDeltas;
6131
+ case 11:
6132
+ if (!(_i < _rotationDeltas.length)) {
6133
+ _context3.n = 15;
6134
+ break;
6135
+ }
6136
+ _rotationDeltas$_i = _rotationDeltas[_i], obj = _rotationDeltas$_i.obj, deltaRad = _rotationDeltas$_i.deltaRad;
6137
+ if (!(((_obj$userData5 = obj.userData) === null || _obj$userData5 === void 0 ? void 0 : _obj$userData5.objectType) !== 'component' || !((_obj$userData6 = obj.userData) !== null && _obj$userData6 !== void 0 && _obj$userData6.libraryId))) {
6138
+ _context3.n = 12;
6139
+ break;
6140
+ }
6141
+ return _context3.a(3, 14);
6142
+ case 12:
6143
+ componentId = obj.uuid || ((_obj$userData7 = obj.userData) === null || _obj$userData7 === void 0 ? void 0 : _obj$userData7.originalUuid);
6144
+ if (!(!componentId || !((_this$centralPlant = this.centralPlant) !== null && _this$centralPlant !== void 0 && _this$centralPlant.rotate))) {
6145
+ _context3.n = 13;
6146
+ break;
6147
+ }
6148
+ return _context3.a(3, 14);
6149
+ case 13:
6150
+ deltaDeg = THREE__namespace.MathUtils.radToDeg(deltaRad);
6151
+ snappedDeg = Math.round(deltaDeg / 90) * 90;
6152
+ if (Math.abs(snappedDeg) >= 90) {
6153
+ this.centralPlant.rotate(componentId, 'z', snappedDeg);
6154
+ }
6155
+ case 14:
6156
+ _i++;
6157
+ _context3.n = 11;
6158
+ break;
6159
+ case 15:
6160
+ this._finalizeMultiSelectionTransform();
6161
+ case 16:
6162
+ return _context3.a(2);
6163
+ }
6164
+ }, _callee3, this, [[1, 7, 8, 9]]);
6165
+ }));
6166
+ function _applyMultiSelectionRotation() {
6167
+ return _applyMultiSelectionRotation2.apply(this, arguments);
6168
+ }
6169
+ return _applyMultiSelectionRotation;
6170
+ }()
5955
6171
  /**
5956
6172
  * Calculate transformation deltas from multi-selection group
5957
6173
  * @returns {Object} Delta values and threshold
@@ -6007,17 +6223,17 @@ var TransformControlsManager = /*#__PURE__*/function () {
6007
6223
  }, {
6008
6224
  key: "_translateSegments",
6009
6225
  value: (function () {
6010
- var _translateSegments2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee3(segments, deltaX, deltaY, deltaZ, threshold) {
6226
+ var _translateSegments2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee4(segments, deltaX, deltaY, deltaZ, threshold) {
6011
6227
  var _this$sceneViewer, _this$sceneViewer2;
6012
- var transformOpsManager, useOptimizedPath, throttleDelay, AXIS_THROTTLE, OBJECT_DELAY, i, segment, axes, _i, _axes, _axes$_i, axis, delta;
6013
- return _regenerator().w(function (_context3) {
6014
- while (1) switch (_context3.n) {
6228
+ var transformOpsManager, useOptimizedPath, throttleDelay, AXIS_THROTTLE, OBJECT_DELAY, i, segment, axes, _i2, _axes, _axes$_i, axis, delta;
6229
+ return _regenerator().w(function (_context4) {
6230
+ while (1) switch (_context4.n) {
6015
6231
  case 0:
6016
6232
  if (!(segments.length === 0)) {
6017
- _context3.n = 1;
6233
+ _context4.n = 1;
6018
6234
  break;
6019
6235
  }
6020
- return _context3.a(2);
6236
+ return _context4.a(2);
6021
6237
  case 1:
6022
6238
  transformOpsManager = (_this$sceneViewer = this.sceneViewer) === null || _this$sceneViewer === void 0 ? void 0 : _this$sceneViewer.transformOperationsManager;
6023
6239
  useOptimizedPath = transformOpsManager != null;
@@ -6036,7 +6252,7 @@ var TransformControlsManager = /*#__PURE__*/function () {
6036
6252
  i = 0;
6037
6253
  case 2:
6038
6254
  if (!(i < segments.length)) {
6039
- _context3.n = 8;
6255
+ _context4.n = 8;
6040
6256
  break;
6041
6257
  }
6042
6258
  segment = segments[i];
@@ -6050,26 +6266,26 @@ var TransformControlsManager = /*#__PURE__*/function () {
6050
6266
  axis: 'z',
6051
6267
  delta: deltaZ
6052
6268
  }]; // Translate on each axis
6053
- _i = 0, _axes = axes;
6269
+ _i2 = 0, _axes = axes;
6054
6270
  case 3:
6055
- if (!(_i < _axes.length)) {
6056
- _context3.n = 6;
6271
+ if (!(_i2 < _axes.length)) {
6272
+ _context4.n = 6;
6057
6273
  break;
6058
6274
  }
6059
- _axes$_i = _axes[_i], axis = _axes$_i.axis, delta = _axes$_i.delta;
6275
+ _axes$_i = _axes[_i2], axis = _axes$_i.axis, delta = _axes$_i.delta;
6060
6276
  if (!(Math.abs(delta) > threshold)) {
6061
- _context3.n = 5;
6277
+ _context4.n = 5;
6062
6278
  break;
6063
6279
  }
6064
- _context3.n = 4;
6280
+ _context4.n = 4;
6065
6281
  return this._translateSegmentOnAxis(segment, axis, delta, useOptimizedPath, i);
6066
6282
  case 4:
6067
- segment = _context3.v;
6068
- _context3.n = 5;
6283
+ segment = _context4.v;
6284
+ _context4.n = 5;
6069
6285
  return throttleDelay(AXIS_THROTTLE);
6070
6286
  case 5:
6071
- _i++;
6072
- _context3.n = 3;
6287
+ _i2++;
6288
+ _context4.n = 3;
6073
6289
  break;
6074
6290
  case 6:
6075
6291
  // Update selected objects array with refreshed reference
@@ -6080,14 +6296,14 @@ var TransformControlsManager = /*#__PURE__*/function () {
6080
6296
  console.warn("\u26A0\uFE0F Segment reference became null (".concat(i + 1, "/").concat(segments.length, ")"));
6081
6297
  }
6082
6298
  if (!(i < segments.length - 1)) {
6083
- _context3.n = 7;
6299
+ _context4.n = 7;
6084
6300
  break;
6085
6301
  }
6086
- _context3.n = 7;
6302
+ _context4.n = 7;
6087
6303
  return throttleDelay(OBJECT_DELAY);
6088
6304
  case 7:
6089
6305
  i++;
6090
- _context3.n = 2;
6306
+ _context4.n = 2;
6091
6307
  break;
6092
6308
  case 8:
6093
6309
  // Batch update paths for optimized path only
@@ -6097,9 +6313,9 @@ var TransformControlsManager = /*#__PURE__*/function () {
6097
6313
  console.log('✅ Paths regenerated successfully');
6098
6314
  }
6099
6315
  case 9:
6100
- return _context3.a(2);
6316
+ return _context4.a(2);
6101
6317
  }
6102
- }, _callee3, this);
6318
+ }, _callee4, this);
6103
6319
  }));
6104
6320
  function _translateSegments(_x, _x2, _x3, _x4, _x5) {
6105
6321
  return _translateSegments2.apply(this, arguments);
@@ -6138,11 +6354,11 @@ var TransformControlsManager = /*#__PURE__*/function () {
6138
6354
  }, {
6139
6355
  key: "_translateSegmentOnAxis",
6140
6356
  value: (function () {
6141
- var _translateSegmentOnAxis2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee4(segment, axis, delta, useOptimizedPath, index) {
6357
+ var _translateSegmentOnAxis2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee5(segment, axis, delta, useOptimizedPath, index) {
6142
6358
  var _segment$userData3;
6143
6359
  var segmentId, success;
6144
- return _regenerator().w(function (_context4) {
6145
- while (1) switch (_context4.n) {
6360
+ return _regenerator().w(function (_context5) {
6361
+ while (1) switch (_context5.n) {
6146
6362
  case 0:
6147
6363
  segmentId = segment.uuid || ((_segment$userData3 = segment.userData) === null || _segment$userData3 === void 0 ? void 0 : _segment$userData3.originalUuid);
6148
6364
  if (useOptimizedPath) {
@@ -6155,9 +6371,9 @@ var TransformControlsManager = /*#__PURE__*/function () {
6155
6371
  }
6156
6372
 
6157
6373
  // Refresh segment reference after manualization
6158
- return _context4.a(2, this._refreshSegmentReference(segment));
6374
+ return _context5.a(2, this._refreshSegmentReference(segment));
6159
6375
  }
6160
- }, _callee4, this);
6376
+ }, _callee5, this);
6161
6377
  }));
6162
6378
  function _translateSegmentOnAxis(_x6, _x7, _x8, _x9, _x0) {
6163
6379
  return _translateSegmentOnAxis2.apply(this, arguments);
@@ -6173,8 +6389,8 @@ var TransformControlsManager = /*#__PURE__*/function () {
6173
6389
  key: "_updateSegmentReference",
6174
6390
  value: function _updateSegmentReference(oldSegment, newSegment, index) {
6175
6391
  var selectedIndex = this.selectedObjects.findIndex(function (obj) {
6176
- var _obj$userData5;
6177
- return obj.uuid === oldSegment.uuid || ((_obj$userData5 = obj.userData) === null || _obj$userData5 === void 0 ? void 0 : _obj$userData5.originalUuid) === oldSegment.uuid;
6392
+ var _obj$userData8;
6393
+ return obj.uuid === oldSegment.uuid || ((_obj$userData8 = obj.userData) === null || _obj$userData8 === void 0 ? void 0 : _obj$userData8.originalUuid) === oldSegment.uuid;
6178
6394
  });
6179
6395
  if (selectedIndex !== -1 && newSegment) {
6180
6396
  // Clear bounding box cache
@@ -6192,17 +6408,17 @@ var TransformControlsManager = /*#__PURE__*/function () {
6192
6408
  }, {
6193
6409
  key: "_translateGateways",
6194
6410
  value: (function () {
6195
- var _translateGateways2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee5(gateways, deltaX, deltaY, deltaZ, threshold) {
6411
+ var _translateGateways2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee6(gateways, deltaX, deltaY, deltaZ, threshold) {
6196
6412
  var _this9 = this;
6197
6413
  var throttleDelay, AXIS_THROTTLE, OBJECT_DELAY, i, _gateway$userData, gateway, gatewayId;
6198
- return _regenerator().w(function (_context5) {
6199
- while (1) switch (_context5.n) {
6414
+ return _regenerator().w(function (_context6) {
6415
+ while (1) switch (_context6.n) {
6200
6416
  case 0:
6201
6417
  if (!(gateways.length === 0)) {
6202
- _context5.n = 1;
6418
+ _context6.n = 1;
6203
6419
  break;
6204
6420
  }
6205
- return _context5.a(2);
6421
+ return _context6.a(2);
6206
6422
  case 1:
6207
6423
  throttleDelay = function throttleDelay(ms) {
6208
6424
  return new Promise(function (resolve) {
@@ -6214,31 +6430,31 @@ var TransformControlsManager = /*#__PURE__*/function () {
6214
6430
  i = 0;
6215
6431
  case 2:
6216
6432
  if (!(i < gateways.length)) {
6217
- _context5.n = 5;
6433
+ _context6.n = 5;
6218
6434
  break;
6219
6435
  }
6220
6436
  gateway = gateways[i];
6221
6437
  gatewayId = gateway.uuid || ((_gateway$userData = gateway.userData) === null || _gateway$userData === void 0 ? void 0 : _gateway$userData.originalUuid);
6222
- _context5.n = 3;
6438
+ _context6.n = 3;
6223
6439
  return this._translateObjectOnAxes(gatewayId, deltaX, deltaY, deltaZ, threshold, throttleDelay, AXIS_THROTTLE, function (id, axis, delta) {
6224
6440
  return _this9.centralPlant.translateGateway(id, axis, delta);
6225
6441
  });
6226
6442
  case 3:
6227
6443
  console.log("\uD83D\uDEAA Gateway ".concat(gateway.name, " translated (").concat(i + 1, "/").concat(gateways.length, ")"));
6228
6444
  if (!(i < gateways.length - 1)) {
6229
- _context5.n = 4;
6445
+ _context6.n = 4;
6230
6446
  break;
6231
6447
  }
6232
- _context5.n = 4;
6448
+ _context6.n = 4;
6233
6449
  return throttleDelay(OBJECT_DELAY);
6234
6450
  case 4:
6235
6451
  i++;
6236
- _context5.n = 2;
6452
+ _context6.n = 2;
6237
6453
  break;
6238
6454
  case 5:
6239
- return _context5.a(2);
6455
+ return _context6.a(2);
6240
6456
  }
6241
- }, _callee5, this);
6457
+ }, _callee6, this);
6242
6458
  }));
6243
6459
  function _translateGateways(_x1, _x10, _x11, _x12, _x13) {
6244
6460
  return _translateGateways2.apply(this, arguments);
@@ -6253,17 +6469,17 @@ var TransformControlsManager = /*#__PURE__*/function () {
6253
6469
  }, {
6254
6470
  key: "_translateComponents",
6255
6471
  value: (function () {
6256
- var _translateComponents2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee6(components, deltaX, deltaY, deltaZ, threshold) {
6472
+ var _translateComponents2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee7(components, deltaX, deltaY, deltaZ, threshold) {
6257
6473
  var _this0 = this;
6258
6474
  var throttleDelay, AXIS_THROTTLE, OBJECT_DELAY, i, _component$userData, component, componentId;
6259
- return _regenerator().w(function (_context6) {
6260
- while (1) switch (_context6.n) {
6475
+ return _regenerator().w(function (_context7) {
6476
+ while (1) switch (_context7.n) {
6261
6477
  case 0:
6262
6478
  if (!(components.length === 0)) {
6263
- _context6.n = 1;
6479
+ _context7.n = 1;
6264
6480
  break;
6265
6481
  }
6266
- return _context6.a(2);
6482
+ return _context7.a(2);
6267
6483
  case 1:
6268
6484
  throttleDelay = function throttleDelay(ms) {
6269
6485
  return new Promise(function (resolve) {
@@ -6275,31 +6491,31 @@ var TransformControlsManager = /*#__PURE__*/function () {
6275
6491
  i = 0;
6276
6492
  case 2:
6277
6493
  if (!(i < components.length)) {
6278
- _context6.n = 5;
6494
+ _context7.n = 5;
6279
6495
  break;
6280
6496
  }
6281
6497
  component = components[i];
6282
6498
  componentId = component.uuid || ((_component$userData = component.userData) === null || _component$userData === void 0 ? void 0 : _component$userData.originalUuid);
6283
- _context6.n = 3;
6499
+ _context7.n = 3;
6284
6500
  return this._translateObjectOnAxes(componentId, deltaX, deltaY, deltaZ, threshold, throttleDelay, AXIS_THROTTLE, function (id, axis, delta) {
6285
6501
  return _this0.centralPlant.translate(id, axis, delta, true);
6286
6502
  });
6287
6503
  case 3:
6288
6504
  console.log("\uD83D\uDD27 Component ".concat(component.name, " translated (").concat(i + 1, "/").concat(components.length, ")"));
6289
6505
  if (!(i < components.length - 1)) {
6290
- _context6.n = 4;
6506
+ _context7.n = 4;
6291
6507
  break;
6292
6508
  }
6293
- _context6.n = 4;
6509
+ _context7.n = 4;
6294
6510
  return throttleDelay(OBJECT_DELAY);
6295
6511
  case 4:
6296
6512
  i++;
6297
- _context6.n = 2;
6513
+ _context7.n = 2;
6298
6514
  break;
6299
6515
  case 5:
6300
- return _context6.a(2);
6516
+ return _context7.a(2);
6301
6517
  }
6302
- }, _callee6, this);
6518
+ }, _callee7, this);
6303
6519
  }));
6304
6520
  function _translateComponents(_x14, _x15, _x16, _x17, _x18) {
6305
6521
  return _translateComponents2.apply(this, arguments);
@@ -6314,10 +6530,10 @@ var TransformControlsManager = /*#__PURE__*/function () {
6314
6530
  }, {
6315
6531
  key: "_translateObjectOnAxes",
6316
6532
  value: (function () {
6317
- var _translateObjectOnAxes2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee7(objectId, deltaX, deltaY, deltaZ, threshold, throttleDelay, axisThrottle, translateFn) {
6318
- var axes, _i2, _axes2, _axes2$_i, axis, delta;
6319
- return _regenerator().w(function (_context7) {
6320
- while (1) switch (_context7.n) {
6533
+ var _translateObjectOnAxes2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee8(objectId, deltaX, deltaY, deltaZ, threshold, throttleDelay, axisThrottle, translateFn) {
6534
+ var axes, _i3, _axes2, _axes2$_i, axis, delta;
6535
+ return _regenerator().w(function (_context8) {
6536
+ while (1) switch (_context8.n) {
6321
6537
  case 0:
6322
6538
  axes = [{
6323
6539
  axis: 'x',
@@ -6329,28 +6545,28 @@ var TransformControlsManager = /*#__PURE__*/function () {
6329
6545
  axis: 'z',
6330
6546
  delta: deltaZ
6331
6547
  }];
6332
- _i2 = 0, _axes2 = axes;
6548
+ _i3 = 0, _axes2 = axes;
6333
6549
  case 1:
6334
- if (!(_i2 < _axes2.length)) {
6335
- _context7.n = 3;
6550
+ if (!(_i3 < _axes2.length)) {
6551
+ _context8.n = 3;
6336
6552
  break;
6337
6553
  }
6338
- _axes2$_i = _axes2[_i2], axis = _axes2$_i.axis, delta = _axes2$_i.delta;
6554
+ _axes2$_i = _axes2[_i3], axis = _axes2$_i.axis, delta = _axes2$_i.delta;
6339
6555
  if (!(Math.abs(delta) > threshold)) {
6340
- _context7.n = 2;
6556
+ _context8.n = 2;
6341
6557
  break;
6342
6558
  }
6343
6559
  translateFn(objectId, axis, delta);
6344
- _context7.n = 2;
6560
+ _context8.n = 2;
6345
6561
  return throttleDelay(axisThrottle);
6346
6562
  case 2:
6347
- _i2++;
6348
- _context7.n = 1;
6563
+ _i3++;
6564
+ _context8.n = 1;
6349
6565
  break;
6350
6566
  case 3:
6351
- return _context7.a(2);
6567
+ return _context8.a(2);
6352
6568
  }
6353
- }, _callee7);
6569
+ }, _callee8);
6354
6570
  }));
6355
6571
  function _translateObjectOnAxes(_x19, _x20, _x21, _x22, _x23, _x24, _x25, _x26) {
6356
6572
  return _translateObjectOnAxes2.apply(this, arguments);
@@ -25488,6 +25704,405 @@ function interceptControlUp( event ) {
25488
25704
 
25489
25705
  }
25490
25706
 
25707
+ /** Default horizontal orbit bearing (target → camera, XY plane). */
25708
+ var DEFAULT_HOME_VIEW_DIRECTION = {
25709
+ x: -8,
25710
+ y: -9,
25711
+ z: 0
25712
+ };
25713
+
25714
+ /** Downward pitch from camera to the bottom-center look-at target (degrees). */
25715
+ var HOME_DOWNWARD_PITCH_DEG = 13;
25716
+
25717
+ /** Minimum camera height above the bbox floor (world units). */
25718
+ var HOME_CAMERA_MIN_HEIGHT = 0.8;
25719
+
25720
+ /** Ground plane height in world space (Z-up). */
25721
+ var WORLD_GROUND_Z = 0;
25722
+
25723
+ /** Camera height above the ground when the scene has no components. */
25724
+ var HOME_EMPTY_CAMERA_HEIGHT_ABOVE_GROUND = 2.5;
25725
+
25726
+ /** Default horizontal distance from world origin when the scene is empty. */
25727
+ var HOME_EMPTY_SCENE_DISTANCE = Math.hypot(DEFAULT_HOME_VIEW_DIRECTION.x, DEFAULT_HOME_VIEW_DIRECTION.y);
25728
+ var WORLD_ORIGIN = new THREE__namespace.Vector3(0, 0, 0);
25729
+ var CameraControlsManager = /*#__PURE__*/function () {
25730
+ function CameraControlsManager(component) {
25731
+ _classCallCheck(this, CameraControlsManager);
25732
+ this.sceneViewer = component;
25733
+ this.autoRotateSpeed = 1.0; // Default rotation speed
25734
+ this._isAutoRotating = false; // Internal state tracking
25735
+ }
25736
+
25737
+ /**
25738
+ * Frame the camera so that every component in the scene fits perfectly in view.
25739
+ *
25740
+ * Computes the combined bounding box of all scene components, then moves the
25741
+ * (perspective) camera along its current viewing direction to the distance
25742
+ * required to fit that bounding sphere within the frustum. The orbit target,
25743
+ * clipping planes and distance limits are updated to match.
25744
+ *
25745
+ * @param {Object} [options]
25746
+ * @param {number} [options.padding=1.04] - Multiplier on the fit distance (>1 leaves margin).
25747
+ * @param {THREE.Vector3|{x:number,y:number,z:number}} [options.direction] - Override viewing
25748
+ * direction (target -> camera). When omitted, the current orbit angle is preserved.
25749
+ * @param {boolean} [options.groundLevel] - Pin camera low with a downward pitch toward
25750
+ * the bottom-center of the assembly. Defaults to true when a home direction is used.
25751
+ * @returns {boolean} True if the camera was reframed, false if there was nothing to frame.
25752
+ */
25753
+ return _createClass(CameraControlsManager, [{
25754
+ key: "fitCameraToScene",
25755
+ value: function fitCameraToScene() {
25756
+ var _options$padding, _options$groundLevel;
25757
+ var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
25758
+ var component = this.sceneViewer;
25759
+ var camera = component === null || component === void 0 ? void 0 : component.camera;
25760
+ var scene = component === null || component === void 0 ? void 0 : component.scene;
25761
+ var controls = component === null || component === void 0 ? void 0 : component.controls;
25762
+ if (!camera || !scene || !camera.isPerspectiveCamera) {
25763
+ console.warn('⚠️ fitCameraToScene: missing perspective camera or scene');
25764
+ return false;
25765
+ }
25766
+ var padding = (_options$padding = options.padding) !== null && _options$padding !== void 0 ? _options$padding : 1.04;
25767
+
25768
+ // Make sure world matrices are current before measuring bounds
25769
+ scene.updateMatrixWorld(true);
25770
+ var box = this._collectSceneBounds(scene);
25771
+ if (!box) {
25772
+ return this._aimCameraAtWorldCenter(camera, controls, options);
25773
+ }
25774
+ var useGroundLevel = (_options$groundLevel = options.groundLevel) !== null && _options$groundLevel !== void 0 ? _options$groundLevel : this._isHomeDirection(options.direction);
25775
+ if (useGroundLevel) {
25776
+ return this._fitCameraGroundLevel(box, camera, controls, options, padding);
25777
+ }
25778
+ return this._fitCameraFromCenter(box, camera, controls, options, padding);
25779
+ }
25780
+
25781
+ /**
25782
+ * Aim the camera at world origin when there are no components to frame.
25783
+ * @private
25784
+ */
25785
+ }, {
25786
+ key: "_aimCameraAtWorldCenter",
25787
+ value: function _aimCameraAtWorldCenter(camera, controls) {
25788
+ var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
25789
+ var frameTarget = WORLD_ORIGIN.clone();
25790
+ var distance = HOME_EMPTY_SCENE_DISTANCE;
25791
+ var horizDir = new THREE__namespace.Vector3();
25792
+ if (options.direction) {
25793
+ horizDir.set(options.direction.x, options.direction.y, 0);
25794
+ } else {
25795
+ horizDir.set(DEFAULT_HOME_VIEW_DIRECTION.x, DEFAULT_HOME_VIEW_DIRECTION.y, 0);
25796
+ }
25797
+ if (horizDir.lengthSq() < 1e-6) {
25798
+ horizDir.set(DEFAULT_HOME_VIEW_DIRECTION.x, DEFAULT_HOME_VIEW_DIRECTION.y, 0);
25799
+ }
25800
+ horizDir.normalize();
25801
+ var cameraHeight = WORLD_GROUND_Z + HOME_EMPTY_CAMERA_HEIGHT_ABOVE_GROUND;
25802
+ var camPos = new THREE__namespace.Vector3(frameTarget.x + horizDir.x * distance, frameTarget.y + horizDir.y * distance, cameraHeight);
25803
+ camera.position.copy(camPos);
25804
+ camera.near = 0.01;
25805
+ camera.far = 1000;
25806
+ camera.updateProjectionMatrix();
25807
+ camera.lookAt(frameTarget);
25808
+ if (controls) {
25809
+ controls.target.copy(frameTarget);
25810
+ controls.minDistance = 1;
25811
+ controls.maxDistance = 20;
25812
+ controls.update();
25813
+ }
25814
+ console.log('🎥 Camera aimed at world origin (empty scene)');
25815
+ return true;
25816
+ }
25817
+
25818
+ /**
25819
+ * @private
25820
+ */
25821
+ }, {
25822
+ key: "_collectSceneBounds",
25823
+ value: function _collectSceneBounds(scene) {
25824
+ var includeTypes = ['component', 'gateway', 'segment'];
25825
+ var box = new THREE__namespace.Box3();
25826
+ var found = false;
25827
+ scene.traverse(function (obj) {
25828
+ var _obj$userData;
25829
+ if (includeTypes.includes((_obj$userData = obj.userData) === null || _obj$userData === void 0 ? void 0 : _obj$userData.objectType)) {
25830
+ var objBox = new THREE__namespace.Box3().setFromObject(obj);
25831
+ if (!objBox.isEmpty()) {
25832
+ box.union(objBox);
25833
+ found = true;
25834
+ }
25835
+ }
25836
+ });
25837
+ return found && !box.isEmpty() ? box : null;
25838
+ }
25839
+
25840
+ /**
25841
+ * @private
25842
+ */
25843
+ }, {
25844
+ key: "_isHomeDirection",
25845
+ value: function _isHomeDirection(direction) {
25846
+ if (!direction) return false;
25847
+ var horiz = Math.hypot(direction.x, direction.y);
25848
+ if (horiz < 1e-6) return false;
25849
+ var dx = direction.x / horiz - DEFAULT_HOME_VIEW_DIRECTION.x / Math.hypot(DEFAULT_HOME_VIEW_DIRECTION.x, DEFAULT_HOME_VIEW_DIRECTION.y);
25850
+ var dy = direction.y / horiz - DEFAULT_HOME_VIEW_DIRECTION.y / Math.hypot(DEFAULT_HOME_VIEW_DIRECTION.x, DEFAULT_HOME_VIEW_DIRECTION.y);
25851
+ return Math.hypot(dx, dy) < 0.05 && Math.abs(direction.z) < 0.5;
25852
+ }
25853
+
25854
+ /**
25855
+ * Ground-hugging camera: horizontal offset with a fixed downward pitch toward
25856
+ * the bottom-center of the component group.
25857
+ * @private
25858
+ */
25859
+ }, {
25860
+ key: "_fitCameraGroundLevel",
25861
+ value: function _fitCameraGroundLevel(box, camera, controls, options, padding) {
25862
+ var size = box.getSize(new THREE__namespace.Vector3());
25863
+ var frameTarget = this._getBoxBottomCenter(box);
25864
+ var downwardPitch = THREE__namespace.MathUtils.degToRad(HOME_DOWNWARD_PITCH_DEG);
25865
+ var horizDir = new THREE__namespace.Vector3();
25866
+ if (options.direction) {
25867
+ horizDir.set(options.direction.x, options.direction.y, 0);
25868
+ } else {
25869
+ horizDir.set(DEFAULT_HOME_VIEW_DIRECTION.x, DEFAULT_HOME_VIEW_DIRECTION.y, 0);
25870
+ }
25871
+ if (horizDir.lengthSq() < 1e-6) {
25872
+ horizDir.set(DEFAULT_HOME_VIEW_DIRECTION.x, DEFAULT_HOME_VIEW_DIRECTION.y, 0);
25873
+ }
25874
+ horizDir.normalize();
25875
+ var corners = this._getBoxCorners(box);
25876
+ var vFov = THREE__namespace.MathUtils.degToRad(camera.fov);
25877
+ var aspect = camera.aspect || 1;
25878
+ var tanHalfV = Math.tan(vFov / 2);
25879
+ var tanHalfH = tanHalfV * aspect;
25880
+ var fitsAtDistance = function fitsAtDistance(distance) {
25881
+ var cameraHeight = Math.max(frameTarget.z + distance * Math.tan(downwardPitch), box.min.z + HOME_CAMERA_MIN_HEIGHT);
25882
+ var camPos = new THREE__namespace.Vector3(frameTarget.x + horizDir.x * distance, frameTarget.y + horizDir.y * distance, cameraHeight);
25883
+ var forward = new THREE__namespace.Vector3().subVectors(frameTarget, camPos).normalize();
25884
+ var worldUp = camera.up.clone().normalize();
25885
+ var right = new THREE__namespace.Vector3().crossVectors(forward, worldUp);
25886
+ if (right.lengthSq() < 1e-6) {
25887
+ right = new THREE__namespace.Vector3().crossVectors(forward, new THREE__namespace.Vector3(1, 0, 0));
25888
+ }
25889
+ right.normalize();
25890
+ var up = new THREE__namespace.Vector3().crossVectors(right, forward).normalize();
25891
+ var _iterator = _createForOfIteratorHelper(corners),
25892
+ _step;
25893
+ try {
25894
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
25895
+ var corner = _step.value;
25896
+ var rel = new THREE__namespace.Vector3().subVectors(corner, camPos);
25897
+ var depth = rel.dot(forward);
25898
+ if (depth <= 0.01) return false;
25899
+ if (Math.abs(rel.dot(right)) / depth > tanHalfH) return false;
25900
+ if (Math.abs(rel.dot(up)) / depth > tanHalfV) return false;
25901
+ }
25902
+ } catch (err) {
25903
+ _iterator.e(err);
25904
+ } finally {
25905
+ _iterator.f();
25906
+ }
25907
+ return true;
25908
+ };
25909
+ var fitDistance = 0.5;
25910
+ while (!fitsAtDistance(fitDistance) && fitDistance < 500) {
25911
+ fitDistance *= 1.15;
25912
+ }
25913
+ if (fitDistance >= 500) {
25914
+ console.warn('⚠️ fitCameraToScene: could not frame scene from ground level');
25915
+ return false;
25916
+ }
25917
+
25918
+ // Tighten to the closest distance that still fits, then apply padding.
25919
+ var lo = 0.01;
25920
+ var hi = fitDistance;
25921
+ while (hi - lo > 0.1) {
25922
+ var mid = (lo + hi) / 2;
25923
+ if (fitsAtDistance(mid)) hi = mid;else lo = mid;
25924
+ }
25925
+ fitDistance = hi * padding;
25926
+ var cameraHeight = Math.max(frameTarget.z + fitDistance * Math.tan(downwardPitch), box.min.z + HOME_CAMERA_MIN_HEIGHT);
25927
+ var camPos = new THREE__namespace.Vector3(frameTarget.x + horizDir.x * fitDistance, frameTarget.y + horizDir.y * fitDistance, cameraHeight);
25928
+ camera.position.copy(camPos);
25929
+ var span = size.length();
25930
+ camera.near = Math.max(fitDistance / 1000, 0.01);
25931
+ camera.far = Math.max(fitDistance + span * 4, 1000);
25932
+ camera.updateProjectionMatrix();
25933
+ camera.lookAt(frameTarget);
25934
+ if (controls) {
25935
+ controls.target.copy(frameTarget);
25936
+ controls.minDistance = Math.max(span * 0.1, 0.1);
25937
+ controls.maxDistance = fitDistance * 4;
25938
+ controls.update();
25939
+ }
25940
+ console.log("\uD83C\uDFA5 Camera framed to scene at ground level (distance: ".concat(fitDistance.toFixed(2), ", height: ").concat(cameraHeight.toFixed(2), ")"));
25941
+ return true;
25942
+ }
25943
+
25944
+ /**
25945
+ * Bottom-center of a bounding box (centered in X/Y, at the floor in Z).
25946
+ * @private
25947
+ */
25948
+ }, {
25949
+ key: "_getBoxBottomCenter",
25950
+ value: function _getBoxBottomCenter(box) {
25951
+ var center = box.getCenter(new THREE__namespace.Vector3());
25952
+ return new THREE__namespace.Vector3(center.x, center.y, box.min.z);
25953
+ }
25954
+
25955
+ /**
25956
+ * @private
25957
+ */
25958
+ }, {
25959
+ key: "_getBoxCorners",
25960
+ value: function _getBoxCorners(box) {
25961
+ var min = box.min,
25962
+ max = box.max;
25963
+ var corners = [];
25964
+ for (var i = 0; i < 8; i++) {
25965
+ corners.push(new THREE__namespace.Vector3(i & 1 ? max.x : min.x, i & 2 ? max.y : min.y, i & 4 ? max.z : min.z));
25966
+ }
25967
+ return corners;
25968
+ }
25969
+
25970
+ /**
25971
+ * @private
25972
+ */
25973
+ }, {
25974
+ key: "_fitCameraFromCenter",
25975
+ value: function _fitCameraFromCenter(box, camera, controls, options, padding) {
25976
+ var center = box.getCenter(new THREE__namespace.Vector3());
25977
+ var target = controls !== null && controls !== void 0 && controls.target ? controls.target.clone() : new THREE__namespace.Vector3();
25978
+ var direction = new THREE__namespace.Vector3();
25979
+ if (options.direction) {
25980
+ direction.set(options.direction.x, options.direction.y, options.direction.z);
25981
+ } else {
25982
+ direction.subVectors(camera.position, target);
25983
+ }
25984
+ if (direction.lengthSq() < 1e-6) {
25985
+ direction.set(DEFAULT_HOME_VIEW_DIRECTION.x, DEFAULT_HOME_VIEW_DIRECTION.y, DEFAULT_HOME_VIEW_DIRECTION.z);
25986
+ }
25987
+ direction.normalize();
25988
+ var forward = direction.clone().negate();
25989
+ var worldUp = camera.up.clone().normalize();
25990
+ var right = new THREE__namespace.Vector3().crossVectors(forward, worldUp);
25991
+ if (right.lengthSq() < 1e-6) {
25992
+ right = new THREE__namespace.Vector3().crossVectors(forward, new THREE__namespace.Vector3(1, 0, 0));
25993
+ }
25994
+ right.normalize();
25995
+ var up = new THREE__namespace.Vector3().crossVectors(right, forward).normalize();
25996
+ var vFov = THREE__namespace.MathUtils.degToRad(camera.fov);
25997
+ var aspect = camera.aspect || 1;
25998
+ var tanHalfV = Math.tan(vFov / 2);
25999
+ var tanHalfH = tanHalfV * aspect;
26000
+ var min = box.min;
26001
+ var max = box.max;
26002
+ var fitDistance = 0.01;
26003
+ for (var i = 0; i < 8; i++) {
26004
+ 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);
26005
+ var vForward = v.dot(forward);
26006
+ var vRight = Math.abs(v.dot(right));
26007
+ var vUp = Math.abs(v.dot(up));
26008
+ var cornerDistance = Math.max(vRight / tanHalfH - vForward, vUp / tanHalfV - vForward, -vForward + 0.01);
26009
+ fitDistance = Math.max(fitDistance, cornerDistance);
26010
+ }
26011
+ fitDistance *= padding;
26012
+ camera.position.copy(center).addScaledVector(direction, fitDistance);
26013
+ var span = box.getSize(new THREE__namespace.Vector3()).length();
26014
+ camera.near = Math.max(fitDistance / 1000, 0.01);
26015
+ camera.far = Math.max(fitDistance + span * 4, 1000);
26016
+ camera.updateProjectionMatrix();
26017
+ camera.lookAt(center);
26018
+ if (controls) {
26019
+ controls.target.copy(center);
26020
+ controls.minDistance = Math.max(span * 0.1, 0.1);
26021
+ controls.maxDistance = fitDistance * 4;
26022
+ controls.update();
26023
+ }
26024
+ console.log("\uD83C\uDFA5 Camera framed to scene (distance: ".concat(fitDistance.toFixed(2), ")"));
26025
+ return true;
26026
+ }
26027
+
26028
+ /**
26029
+ * Frame the scene using the default low, ground-level home viewing angle.
26030
+ * @param {Object} [options] - Passed to fitCameraToScene (padding, etc.)
26031
+ * @returns {boolean}
26032
+ */
26033
+ }, {
26034
+ key: "fitCameraToSceneHome",
26035
+ value: function fitCameraToSceneHome() {
26036
+ var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
26037
+ return this.fitCameraToScene(_objectSpread2(_objectSpread2({}, options), {}, {
26038
+ direction: DEFAULT_HOME_VIEW_DIRECTION,
26039
+ groundLevel: true
26040
+ }));
26041
+ }
26042
+
26043
+ /**
26044
+ * Toggle camera auto-rotation on/off
26045
+ *
26046
+ * @returns {boolean} The new auto-rotation state
26047
+ */
26048
+ }, {
26049
+ key: "toggleCameraRotation",
26050
+ value: function toggleCameraRotation() {
26051
+ var component = this.sceneViewer;
26052
+ if (!component.controls || !component.sceneInitializationManager) {
26053
+ console.warn('⚠️ Cannot toggle camera rotation: missing controls or sceneInitializationManager');
26054
+ return false;
26055
+ }
26056
+
26057
+ // Use the sceneInitializationManager to toggle auto-rotation
26058
+ var isAutoRotating = component.sceneInitializationManager.toggleAutoRotation();
26059
+
26060
+ // Update our internal state
26061
+ this._isAutoRotating = isAutoRotating;
26062
+
26063
+ // Set autoRotate speed if needed (using the stored preference)
26064
+ if (isAutoRotating && component.controls.autoRotateSpeed !== this.autoRotateSpeed) {
26065
+ component.controls.autoRotateSpeed = this.autoRotateSpeed;
26066
+ }
26067
+ console.log("\uD83D\uDD04 Camera auto-rotation ".concat(isAutoRotating ? 'enabled' : 'disabled', " via CameraControlsManager"));
26068
+ return isAutoRotating;
26069
+ }
26070
+
26071
+ /**
26072
+ * Get the current auto-rotation state
26073
+ *
26074
+ * @returns {boolean} Whether auto-rotation is enabled
26075
+ */
26076
+ }, {
26077
+ key: "isAutoRotating",
26078
+ value: function isAutoRotating() {
26079
+ // If controls exist, sync our state with actual control state
26080
+ if (this.sceneViewer.controls) {
26081
+ this._isAutoRotating = this.sceneViewer.controls.autoRotate;
26082
+ }
26083
+ return this._isAutoRotating;
26084
+ }
26085
+
26086
+ /**
26087
+ * Directly enable or disable camera auto-rotation
26088
+ *
26089
+ * @param {boolean} enable - Whether to enable auto-rotation
26090
+ * @returns {boolean} The new auto-rotation state
26091
+ */
26092
+ }, {
26093
+ key: "setAutoRotation",
26094
+ value: function setAutoRotation(enable) {
26095
+ var component = this.sceneViewer;
26096
+ if (component.sceneInitializationManager) {
26097
+ var result = component.sceneInitializationManager.toggleAutoRotation(enable);
26098
+ this._isAutoRotating = result;
26099
+ return result;
26100
+ }
26101
+ return false;
26102
+ }
26103
+ }]);
26104
+ }();
26105
+
25491
26106
  var SceneInitializationManager = /*#__PURE__*/function () {
25492
26107
  function SceneInitializationManager(sceneViewer) {
25493
26108
  _classCallCheck(this, SceneInitializationManager);
@@ -25515,7 +26130,7 @@ var SceneInitializationManager = /*#__PURE__*/function () {
25515
26130
  containerWidth = containerRect.width;
25516
26131
  containerHeight = containerRect.height; // Create camera (Z-up coordinate system with flipped Y)
25517
26132
  component.camera = new THREE__namespace.PerspectiveCamera(50, containerWidth / containerHeight, 0.01, 1000);
25518
- component.camera.position.set(-8, -9, 2); // Flipped Y direction
26133
+ component.camera.position.set(DEFAULT_HOME_VIEW_DIRECTION.x, DEFAULT_HOME_VIEW_DIRECTION.y, HOME_EMPTY_CAMERA_HEIGHT_ABOVE_GROUND);
25519
26134
  component.camera.up.set(0, 0, 1); // Set Z as up vector
25520
26135
 
25521
26136
  // Create renderer
@@ -29209,204 +29824,344 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
29209
29824
  }
29210
29825
 
29211
29826
  /**
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)
29827
+ * Enrich sceneData with worldBoundingBox for segments and components
29828
+ * Connectors remain with just position information.
29829
+ *
29830
+ * Uses _bboxCache to avoid recomputing filtered / io-device bboxes
29831
+ * when the underlying object's world matrix has not changed.
29832
+ *
29833
+ * @param {Object} sceneData - Original scene data
29834
+ * @returns {Object} Enriched scene data (shallow copy with modifications)
29215
29835
  * @private
29216
29836
  */
29217
29837
  }, {
29218
29838
  key: "_enrichSceneDataWithBoundingBoxes",
29219
29839
  value: function _enrichSceneDataWithBoundingBoxes(sceneData) {
29220
29840
  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);
29841
+ // Create a shallow copy of sceneData structure
29842
+ var enriched = _objectSpread2({}, sceneData);
29843
+ if (!sceneData.children || !Array.isArray(sceneData.children)) {
29844
+ console.warn('⚠️ sceneData has no children array');
29845
+ return enriched;
29846
+ }
29227
29847
 
29848
+ // Process children to add worldBoundingBox to segments and components
29849
+ enriched.children = sceneData.children.map(function (child) {
29228
29850
  // Skip computed objects ( elbows etc )
29229
- if (node.userData && (node.userData.isComputed === true || node.userData.objectType === 'elbow')) {
29230
- return node;
29851
+ if (child.userData && (child.userData.isComputed === true || child.userData.objectType === 'elbow')) {
29852
+ return child;
29231
29853
  }
29232
29854
 
29233
- // ── Enrich Segments ──
29234
- if (node.userData && node.userData.objectType === 'segment') {
29235
- var segmentObject = _this4.sceneViewer.scene.getObjectByProperty('uuid', node.uuid);
29855
+ // Enrich segments (check if objectType is 'segment' in userData)
29856
+ if (child.userData && child.userData.objectType === 'segment') {
29857
+ // Find the actual segment object in the scene
29858
+ var segmentObject = _this4.sceneViewer.scene.getObjectByProperty('uuid', child.uuid);
29236
29859
  if (segmentObject) {
29860
+ // ── Cache check ──
29237
29861
  var hash = _this4._matrixHash(segmentObject);
29238
- var cached = _this4._bboxCache.get(node.uuid);
29862
+ var cached = _this4._bboxCache.get(child.uuid);
29239
29863
  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
29864
+ return _objectSpread2(_objectSpread2({}, child), {}, {
29865
+ userData: _objectSpread2(_objectSpread2({}, child.userData), {}, {
29866
+ worldBoundingBox: cached.segmentBBox
29867
+ })
29255
29868
  });
29256
29869
  }
29870
+
29871
+ // Compute world bounding box
29872
+ var worldBBox = new THREE__namespace.Box3().setFromObject(segmentObject);
29873
+ var bboxData = {
29874
+ min: [worldBBox.min.x, worldBBox.min.y, worldBBox.min.z],
29875
+ max: [worldBBox.max.x, worldBBox.max.y, worldBBox.max.z]
29876
+ };
29877
+
29878
+ // Store in cache
29879
+ _this4._bboxCache.set(child.uuid, {
29880
+ matrixHash: hash,
29881
+ segmentBBox: bboxData
29882
+ });
29883
+ return _objectSpread2(_objectSpread2({}, child), {}, {
29884
+ userData: _objectSpread2(_objectSpread2({}, child.userData), {}, {
29885
+ worldBoundingBox: bboxData
29886
+ })
29887
+ });
29888
+ } else {
29889
+ console.warn("\u26A0\uFE0F Could not find segment object in scene: ".concat(child.uuid));
29257
29890
  }
29258
29891
  }
29259
29892
 
29260
- // ── Enrich Components ──
29261
- else if (node.userData && node.userData.objectType === 'component') {
29262
- var componentObject = _this4.sceneViewer.scene.getObjectByProperty('uuid', node.uuid);
29893
+ // Enrich components (check if objectType is 'component' in userData)
29894
+ if (child.userData && child.userData.objectType === 'component') {
29895
+ // Find the actual component object in the scene
29896
+ var componentObject = _this4.sceneViewer.scene.getObjectByProperty('uuid', child.uuid);
29263
29897
  if (componentObject) {
29898
+ // Explicitly update matrices for this component subtree before computing bounding boxes
29264
29899
  componentObject.updateMatrixWorld(true);
29900
+
29901
+ // ── Cache check ──
29265
29902
  var _hash = _this4._matrixHash(componentObject);
29266
- var _cached = _this4._bboxCache.get(node.uuid);
29903
+ var _cached = _this4._bboxCache.get(child.uuid);
29267
29904
  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]
29905
+ // Rebuild enriched child from cached data
29906
+ var _enrichedChild = _objectSpread2(_objectSpread2({}, child), {}, {
29907
+ // Sync live transform even on cache hit just in case the manifest (child) is stale
29908
+ position: {
29909
+ x: componentObject.position.x,
29910
+ y: componentObject.position.y,
29911
+ z: componentObject.position.z
29912
+ },
29913
+ rotation: {
29914
+ x: THREE__namespace.MathUtils.radToDeg(componentObject.rotation.x),
29915
+ y: THREE__namespace.MathUtils.radToDeg(componentObject.rotation.y),
29916
+ z: THREE__namespace.MathUtils.radToDeg(componentObject.rotation.z)
29917
+ },
29918
+ userData: _objectSpread2(_objectSpread2({}, child.userData), {}, {
29919
+ worldBoundingBox: _cached.filteredBBox,
29920
+ position: [componentObject.position.x, componentObject.position.y, componentObject.position.z]
29921
+ })
29281
29922
  });
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);
29923
+ if (_cached.ioDeviceBBoxes && _cached.ioDeviceBBoxes.length > 0) {
29924
+ if (!_enrichedChild.children) _enrichedChild.children = [];
29925
+ _cached.ioDeviceBBoxes.forEach(function (deviceBBox) {
29926
+ var existingIndex = _enrichedChild.children.findIndex(function (c) {
29927
+ return c.uuid === deviceBBox.uuid;
29928
+ });
29929
+ if (existingIndex >= 0) {
29930
+ _enrichedChild.children[existingIndex] = _objectSpread2(_objectSpread2({}, _enrichedChild.children[existingIndex]), {}, {
29931
+ position: {
29932
+ x: deviceBBox.userData.position[0],
29933
+ y: deviceBBox.userData.position[1],
29934
+ z: deviceBBox.userData.position[2]
29935
+ },
29936
+ userData: _objectSpread2(_objectSpread2({}, _enrichedChild.children[existingIndex].userData), {}, {
29937
+ objectType: 'io-device',
29938
+ worldBoundingBox: deviceBBox.worldBoundingBox,
29939
+ position: deviceBBox.userData.position
29940
+ })
29941
+ });
29942
+ } else {
29943
+ _enrichedChild.children.push({
29944
+ uuid: deviceBBox.uuid,
29945
+ position: {
29946
+ x: deviceBBox.userData.position[0],
29947
+ y: deviceBBox.userData.position[1],
29948
+ z: deviceBBox.userData.position[2]
29949
+ },
29950
+ userData: _objectSpread2(_objectSpread2({}, deviceBBox.userData), {}, {
29951
+ worldBoundingBox: deviceBBox.worldBoundingBox,
29952
+ position: deviceBBox.userData.position
29953
+ }),
29954
+ children: []
29955
+ });
29956
+ }
29288
29957
  });
29289
29958
  }
29290
- if (_cached.ioDeviceBBoxes) {
29291
- _cached.ioDeviceBBoxes.forEach(function (dev) {
29292
- return _this4._mergeEnrichedChild(enrichedNode.children, dev);
29959
+
29960
+ // Also reinject connectors from cache
29961
+ if (_cached.connectorBBoxes && _cached.connectorBBoxes.length > 0) {
29962
+ if (!_enrichedChild.children) _enrichedChild.children = [];
29963
+ _cached.connectorBBoxes.forEach(function (connBBox) {
29964
+ var existingIndex = _enrichedChild.children.findIndex(function (c) {
29965
+ return c.uuid === connBBox.uuid;
29966
+ });
29967
+ if (existingIndex >= 0) {
29968
+ _enrichedChild.children[existingIndex] = _objectSpread2(_objectSpread2({}, _enrichedChild.children[existingIndex]), {}, {
29969
+ position: {
29970
+ x: connBBox.userData.position[0],
29971
+ y: connBBox.userData.position[1],
29972
+ z: connBBox.userData.position[2]
29973
+ },
29974
+ userData: _objectSpread2(_objectSpread2({}, _enrichedChild.children[existingIndex].userData), {}, {
29975
+ worldBoundingBox: connBBox.worldBoundingBox,
29976
+ position: connBBox.userData.position
29977
+ })
29978
+ });
29979
+ } else {
29980
+ _enrichedChild.children.push({
29981
+ uuid: connBBox.uuid,
29982
+ position: {
29983
+ x: connBBox.userData.position[0],
29984
+ y: connBBox.userData.position[1],
29985
+ z: connBBox.userData.position[2]
29986
+ },
29987
+ userData: _objectSpread2(_objectSpread2({}, connBBox.userData), {}, {
29988
+ worldBoundingBox: connBBox.worldBoundingBox,
29989
+ position: connBBox.userData.position
29990
+ }),
29991
+ children: []
29992
+ });
29993
+ }
29293
29994
  });
29294
29995
  }
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 = {
29996
+ return _enrichedChild;
29997
+ }
29998
+
29999
+ // Compute FILTERED bounding box — excludes io-device and connector subtrees
30000
+ // so the component body bbox is tight-fitting and doesn't envelop attached devices
30001
+ var filteredBBox = computeFilteredBoundingBox(componentObject, ['io-device', 'connector']);
30002
+ 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), "]"));
30003
+ var _bboxData = {
30004
+ min: [filteredBBox.min.x, filteredBBox.min.y, filteredBBox.min.z],
30005
+ max: [filteredBBox.max.x, filteredBBox.max.y, filteredBBox.max.z]
30006
+ };
30007
+
30008
+ // Build the enriched component entry
30009
+ var enrichedChild = _objectSpread2(_objectSpread2({}, child), {}, {
30010
+ // Sync live transform to ensure pathfinder has latest coordinates
30011
+ position: {
29302
30012
  x: componentObject.position.x,
29303
30013
  y: componentObject.position.y,
29304
30014
  z: componentObject.position.z
29305
- };
29306
- enrichedNode.rotation = {
30015
+ },
30016
+ rotation: {
29307
30017
  x: THREE__namespace.MathUtils.radToDeg(componentObject.rotation.x),
29308
30018
  y: THREE__namespace.MathUtils.radToDeg(componentObject.rotation.y),
29309
30019
  z: THREE__namespace.MathUtils.radToDeg(componentObject.rotation.z)
29310
- };
29311
- enrichedNode.userData = _objectSpread2(_objectSpread2({}, node.userData), {}, {
30020
+ },
30021
+ userData: _objectSpread2(_objectSpread2({}, child.userData), {}, {
29312
30022
  worldBoundingBox: _bboxData,
29313
30023
  position: [componentObject.position.x, componentObject.position.y, componentObject.position.z]
30024
+ })
30025
+ });
30026
+
30027
+ // Compute separate bounding boxes for each attached io-device
30028
+ var ioDeviceBBoxes = computeIODeviceBoundingBoxes(componentObject);
30029
+ if (ioDeviceBBoxes.length > 0) {
30030
+ // Ensure children array exists (may already contain connectors)
30031
+ if (!enrichedChild.children) {
30032
+ enrichedChild.children = [];
30033
+ }
30034
+
30035
+ // Inject io-device entries with their own worldBoundingBox
30036
+ ioDeviceBBoxes.forEach(function (deviceBBox) {
30037
+ var existingIndex = enrichedChild.children.findIndex(function (c) {
30038
+ return c.uuid === deviceBBox.uuid;
30039
+ });
30040
+ if (existingIndex >= 0) {
30041
+ enrichedChild.children[existingIndex] = _objectSpread2(_objectSpread2({}, enrichedChild.children[existingIndex]), {}, {
30042
+ position: {
30043
+ x: deviceBBox.userData.position[0],
30044
+ y: deviceBBox.userData.position[1],
30045
+ z: deviceBBox.userData.position[2]
30046
+ },
30047
+ userData: _objectSpread2(_objectSpread2({}, enrichedChild.children[existingIndex].userData), {}, {
30048
+ objectType: 'io-device',
30049
+ worldBoundingBox: deviceBBox.worldBoundingBox,
30050
+ position: deviceBBox.userData.position
30051
+ })
30052
+ });
30053
+ } else {
30054
+ enrichedChild.children.push({
30055
+ uuid: deviceBBox.uuid,
30056
+ position: {
30057
+ x: deviceBBox.userData.position[0],
30058
+ y: deviceBBox.userData.position[1],
30059
+ z: deviceBBox.userData.position[2]
30060
+ },
30061
+ userData: _objectSpread2(_objectSpread2({}, deviceBBox.userData), {}, {
30062
+ worldBoundingBox: deviceBBox.worldBoundingBox,
30063
+ position: deviceBBox.userData.position
30064
+ }),
30065
+ children: []
30066
+ });
30067
+ }
30068
+ console.log("\uD83D\uDCE6 Injected io-device bbox for ".concat(deviceBBox.uuid, ": min=[").concat(deviceBBox.worldBoundingBox.min.map(function (v) {
30069
+ return v.toFixed(2);
30070
+ }).join(', '), "], max=[").concat(deviceBBox.worldBoundingBox.max.map(function (v) {
30071
+ return v.toFixed(2);
30072
+ }).join(', '), "]"));
29314
30073
  });
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
30074
+ console.log("\uD83D\uDCE6 Injected ".concat(ioDeviceBBoxes.length, " io-device bounding box(es) for component ").concat(child.uuid));
30075
+ }
30076
+
30077
+ // PHASE 2: Enrich Connectors (CRITICAL for pathfinding API)
30078
+ // Also compute world bounding boxes for connectors and inject into children.
30079
+ // This ensures endpoints have world coordinates in sceneDataCopy.
30080
+ var connectorBBoxes = computeConnectorBoundingBoxes(componentObject);
30081
+ if (connectorBBoxes.length > 0) {
30082
+ if (!enrichedChild.children) enrichedChild.children = [];
30083
+ connectorBBoxes.forEach(function (connBBox) {
30084
+ var existingIndex = enrichedChild.children.findIndex(function (c) {
30085
+ return c.uuid === connBBox.uuid;
30086
+ });
30087
+ if (existingIndex >= 0) {
30088
+ enrichedChild.children[existingIndex] = _objectSpread2(_objectSpread2({}, enrichedChild.children[existingIndex]), {}, {
30089
+ position: {
30090
+ x: connBBox.userData.position[0],
30091
+ y: connBBox.userData.position[1],
30092
+ z: connBBox.userData.position[2]
30093
+ },
30094
+ userData: _objectSpread2(_objectSpread2({}, enrichedChild.children[existingIndex].userData), {}, {
30095
+ worldBoundingBox: connBBox.worldBoundingBox,
30096
+ position: connBBox.userData.position
30097
+ })
30098
+ });
30099
+ } else {
30100
+ enrichedChild.children.push({
30101
+ uuid: connBBox.uuid,
30102
+ position: {
30103
+ x: connBBox.userData.position[0],
30104
+ y: connBBox.userData.position[1],
30105
+ z: connBBox.userData.position[2]
30106
+ },
30107
+ userData: _objectSpread2(_objectSpread2({}, connBBox.userData), {}, {
30108
+ worldBoundingBox: connBBox.worldBoundingBox,
30109
+ position: connBBox.userData.position
30110
+ }),
30111
+ children: []
30112
+ });
30113
+ }
29329
30114
  });
29330
30115
  }
30116
+
30117
+ // Store in cache
30118
+ _this4._bboxCache.set(child.uuid, {
30119
+ matrixHash: _hash,
30120
+ filteredBBox: _bboxData,
30121
+ ioDeviceBBoxes: ioDeviceBBoxes,
30122
+ connectorBBoxes: connectorBBoxes
30123
+ });
30124
+ return enrichedChild;
30125
+ } else {
30126
+ console.warn("\u26A0\uFE0F Could not find component object in scene: ".concat(child.uuid));
29331
30127
  }
29332
30128
  }
29333
30129
 
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);
30130
+ // ────────────────────────────────────────────────────────────────────────
30131
+ // PHASE 3: Handle top-level Gateways and Connectors
30132
+ // ────────────────────────────────────────────────────────────────────────
30133
+ if (child.userData && (child.userData.objectType === 'gateway' || child.userData.objectType === 'connector')) {
30134
+ var _child$userData;
30135
+ 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
30136
  if (object) {
29339
30137
  var worldPos = new THREE__namespace.Vector3();
29340
30138
  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]
30139
+ return _objectSpread2(_objectSpread2({}, child), {}, {
30140
+ // Sync live transform to ensure pathfinder has latest coordinates
30141
+ position: {
30142
+ x: worldPos.x,
30143
+ y: worldPos.y,
30144
+ z: worldPos.z
30145
+ },
30146
+ userData: _objectSpread2(_objectSpread2({}, child.userData), {}, {
30147
+ position: [worldPos.x, worldPos.y, worldPos.z]
30148
+ })
29348
30149
  });
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
30150
  }
29358
30151
  }
29359
30152
 
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
- }
30153
+ // For non-segments and non-components (and if object search failed), return as-is
30154
+ return child;
30155
+ });
30156
+ return enriched;
29377
30157
  }
29378
30158
 
29379
30159
  /**
29380
- * Helper to merge an enriched child (connector/device) into a children array
29381
- * @private
30160
+ * Core pathfinding logic shared across initialization and updates
29382
30161
  */
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
30162
  }, {
29408
30163
  key: "_executePathfinding",
29409
- value: function () {
30164
+ value: (function () {
29410
30165
  var _executePathfinding2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(sceneData, connections) {
29411
30166
  var _this5 = this,
29412
30167
  _sceneDataCopy$childr,
@@ -29581,7 +30336,7 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
29581
30336
  return _executePathfinding2.apply(this, arguments);
29582
30337
  }
29583
30338
  return _executePathfinding;
29584
- }()
30339
+ }())
29585
30340
  }, {
29586
30341
  key: "getSimplifiedSceneData",
29587
30342
  value: function getSimplifiedSceneData() {
@@ -34091,6 +34846,7 @@ var SceneOperationsManager = /*#__PURE__*/function () {
34091
34846
  }, {
34092
34847
  key: "_finalizeScene",
34093
34848
  value: function _finalizeScene(data, crosscubeTextureSet, isImported) {
34849
+ var _component$cameraCont;
34094
34850
  var component = this.sceneViewer;
34095
34851
  component.currentSceneData = data;
34096
34852
  component.crosscubeTextureSet = crosscubeTextureSet;
@@ -34102,6 +34858,14 @@ var SceneOperationsManager = /*#__PURE__*/function () {
34102
34858
  }
34103
34859
  this._setTransformControlsState(true, false); // Enable but keep hidden initially
34104
34860
  }
34861
+
34862
+ // Frame the camera so all components fit perfectly in view (low, ground-level angle)
34863
+ if ((_component$cameraCont = component.cameraControlsManager) !== null && _component$cameraCont !== void 0 && _component$cameraCont.fitCameraToScene) {
34864
+ component.cameraControlsManager.fitCameraToScene({
34865
+ direction: DEFAULT_HOME_VIEW_DIRECTION,
34866
+ groundLevel: true
34867
+ });
34868
+ }
34105
34869
  }
34106
34870
 
34107
34871
  /**
@@ -34984,82 +35748,6 @@ var AnimationManager = /*#__PURE__*/function (_BaseDisposable) {
34984
35748
  }]);
34985
35749
  }(BaseDisposable);
34986
35750
 
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
35751
  /**
35064
35752
  * ComponentDragManager handles drag and drop from external UI elements to the 3D scene
35065
35753
  */
@@ -41125,7 +41813,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
41125
41813
  * Initialize the CentralPlant manager
41126
41814
  *
41127
41815
  * @constructor
41128
- * @version 0.3.49
41816
+ * @version 0.3.51
41129
41817
  * @updated 2025-10-22
41130
41818
  *
41131
41819
  * @description Creates a new CentralPlant instance and initializes internal managers and utilities.
@@ -43001,6 +43689,98 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
43001
43689
  }
43002
43690
  }
43003
43691
 
43692
+ /**
43693
+ * Get the world-space axis-aligned bounding box for a scene object.
43694
+ * @param {string|THREE.Object3D} objectOrId - The object's UUID/name, or the Three.js object itself
43695
+ * @param {Object} [options={}] - Bounding box options
43696
+ * @param {boolean} [options.filtered=true] - When true, exclude connector and io-device
43697
+ * subtrees so the box reflects the component body footprint. When false, the box
43698
+ * envelops all descendants (equivalent to THREE.Box3.setFromObject).
43699
+ * @param {string[]} [options.excludeTypes] - Override the userData.objectType values to
43700
+ * exclude (defaults to ['io-device', 'connector'] when filtered is true).
43701
+ * @returns {{min: {x,y,z}, max: {x,y,z}, size: {x,y,z}, center: {x,y,z}}|null}
43702
+ * The bounding box descriptor in world space, or null if the object can't be found
43703
+ * or has no geometry.
43704
+ * @description Computes a tight world-space bounding box for a component (or any scene
43705
+ * object). By default the component body is measured independently of its attached
43706
+ * connectors and io-devices, which is useful for layout and spacing operations.
43707
+ * @example
43708
+ * // Component body bounding box (excludes connectors / io-devices)
43709
+ * const bbox = centralPlant.getBoundingBox('PUMP-1');
43710
+ * console.log(bbox.size.x, bbox.size.y, bbox.size.z);
43711
+ *
43712
+ * @example
43713
+ * // Full bounding box including connectors and io-devices
43714
+ * const fullBox = centralPlant.getBoundingBox('PUMP-1', { filtered: false });
43715
+ *
43716
+ * @since 0.3.50
43717
+ */
43718
+ }, {
43719
+ key: "getBoundingBox",
43720
+ value: function getBoundingBox(objectOrId) {
43721
+ var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
43722
+ if (!this.sceneViewer || !this.sceneViewer.scene) {
43723
+ console.warn('⚠️ getBoundingBox(): Scene viewer or scene not available');
43724
+ return null;
43725
+ }
43726
+ var _options$filtered = options.filtered,
43727
+ filtered = _options$filtered === void 0 ? true : _options$filtered,
43728
+ excludeTypes = options.excludeTypes;
43729
+
43730
+ // Resolve the target object
43731
+ var object = objectOrId;
43732
+ if (typeof objectOrId === 'string') {
43733
+ object = this.sceneViewer.scene.getObjectByProperty('uuid', objectOrId) || this.sceneViewer.scene.getObjectByProperty('name', objectOrId);
43734
+ if (!object) {
43735
+ // Fall back to a full traverse (matches translate's lookup by originalUuid)
43736
+ this.sceneViewer.scene.traverse(function (child) {
43737
+ var _child$userData2;
43738
+ if (!object && ((_child$userData2 = child.userData) === null || _child$userData2 === void 0 ? void 0 : _child$userData2.originalUuid) === objectOrId) {
43739
+ object = child;
43740
+ }
43741
+ });
43742
+ }
43743
+ }
43744
+ if (!object || typeof object.traverse !== 'function') {
43745
+ console.warn("\u26A0\uFE0F getBoundingBox(): Object '".concat(objectOrId, "' not found in scene"));
43746
+ return null;
43747
+ }
43748
+ try {
43749
+ var typesToExclude = filtered ? excludeTypes || ['io-device', 'connector'] : [];
43750
+ var box = computeFilteredBoundingBox(object, typesToExclude);
43751
+ if (box.isEmpty()) {
43752
+ return null;
43753
+ }
43754
+ var size = box.getSize(new THREE__namespace.Vector3());
43755
+ var center = box.getCenter(new THREE__namespace.Vector3());
43756
+ return {
43757
+ min: {
43758
+ x: box.min.x,
43759
+ y: box.min.y,
43760
+ z: box.min.z
43761
+ },
43762
+ max: {
43763
+ x: box.max.x,
43764
+ y: box.max.y,
43765
+ z: box.max.z
43766
+ },
43767
+ size: {
43768
+ x: size.x,
43769
+ y: size.y,
43770
+ z: size.z
43771
+ },
43772
+ center: {
43773
+ x: center.x,
43774
+ y: center.y,
43775
+ z: center.z
43776
+ }
43777
+ };
43778
+ } catch (error) {
43779
+ console.error('❌ getBoundingBox(): Error computing bounding box:', error);
43780
+ return null;
43781
+ }
43782
+ }
43783
+
43004
43784
  /**
43005
43785
  * Get available component categories
43006
43786
  * @returns {Array<Object>} Array of category objects with id, label, icon, and description
@@ -43235,8 +44015,8 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
43235
44015
  var componentDictionary = ((_this$managers$compon = this.managers.componentDataManager) === null || _this$managers$compon === void 0 ? void 0 : _this$managers$compon.componentDictionary) || {};
43236
44016
  var missingIds = [];
43237
44017
  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;
44018
+ var _child$userData3;
44019
+ var libraryId = (_child$userData3 = child.userData) === null || _child$userData3 === void 0 ? void 0 : _child$userData3.libraryId;
43240
44020
  if (libraryId && !componentDictionary[libraryId]) {
43241
44021
  // Only add unique IDs
43242
44022
  if (!missingIds.includes(libraryId)) {
@@ -43314,8 +44094,8 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
43314
44094
  // If still not found, try finding by originalUuid in userData
43315
44095
  if (!targetObject) {
43316
44096
  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) {
44097
+ var _child$userData4;
44098
+ if (((_child$userData4 = child.userData) === null || _child$userData4 === void 0 ? void 0 : _child$userData4.originalUuid) === objectId) {
43319
44099
  targetObject = child;
43320
44100
  return;
43321
44101
  }
@@ -49940,9 +50720,11 @@ exports.ComponentDataManager = ComponentDataManager;
49940
50720
  exports.ComponentDragManager = ComponentDragManager;
49941
50721
  exports.ComponentManager = ComponentManager;
49942
50722
  exports.ComponentTooltipManager = ComponentTooltipManager;
50723
+ exports.DEFAULT_HOME_VIEW_DIRECTION = DEFAULT_HOME_VIEW_DIRECTION;
49943
50724
  exports.EnvironmentManager = EnvironmentManager;
49944
50725
  exports.FLOW_ATTRIBUTE_KEYS = FLOW_ATTRIBUTE_KEYS;
49945
50726
  exports.GLOBAL_CACHE_NAME = GLOBAL_CACHE_NAME;
50727
+ exports.HOME_EMPTY_CAMERA_HEIGHT_ABOVE_GROUND = HOME_EMPTY_CAMERA_HEIGHT_ABOVE_GROUND;
49946
50728
  exports.IoBehaviorManager = IoBehaviorManager;
49947
50729
  exports.KeyboardControlsManager = KeyboardControlsManager;
49948
50730
  exports.ModelManager = ModelManager;