@2112-lab/central-plant 0.1.76 → 0.1.78

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.
@@ -1,4 +1,4 @@
1
- import { inherits as _inherits, createClass as _createClass, superPropGet as _superPropGet, classCallCheck as _classCallCheck, callSuper as _callSuper, asyncToGenerator as _asyncToGenerator, regenerator as _regenerator } from '../../../_virtual/_rollupPluginBabelHelpers.js';
1
+ import { inherits as _inherits, createClass as _createClass, superPropGet as _superPropGet, classCallCheck as _classCallCheck, callSuper as _callSuper, asyncToGenerator as _asyncToGenerator, regenerator as _regenerator, slicedToArray as _slicedToArray } from '../../../_virtual/_rollupPluginBabelHelpers.js';
2
2
  import { BaseDisposable } from '../../core/baseDisposable.js';
3
3
  import * as THREE from 'three';
4
4
 
@@ -167,7 +167,7 @@ var ComponentDragManager = /*#__PURE__*/function (_BaseDisposable) {
167
167
  console.log("\uD83D\uDD0D ModelPreloader available:", !!modelPreloader);
168
168
  console.log("\uD83D\uDD0D ComponentDictionary available:", !!(modelPreloader !== null && modelPreloader !== void 0 && modelPreloader.componentDictionary));
169
169
  if (!(modelPreloader && modelPreloader.componentDictionary)) {
170
- _context2.n = 13;
170
+ _context2.n = 14;
171
171
  break;
172
172
  }
173
173
  console.log("\uD83D\uDCDA Available dictionary keys:", Object.keys(modelPreloader.componentDictionary));
@@ -183,7 +183,7 @@ var ComponentDragManager = /*#__PURE__*/function (_BaseDisposable) {
183
183
  });
184
184
  }
185
185
  if (!(componentData && componentData.modelKey)) {
186
- _context2.n = 11;
186
+ _context2.n = 12;
187
187
  break;
188
188
  }
189
189
  // Try to get cached model first
@@ -223,7 +223,7 @@ var ComponentDragManager = /*#__PURE__*/function (_BaseDisposable) {
223
223
  console.warn("\u26A0\uFE0F Failed to preload model ".concat(componentData.modelKey, ":"), _t2);
224
224
  case 8:
225
225
  if (!cachedModel) {
226
- _context2.n = 9;
226
+ _context2.n = 10;
227
227
  break;
228
228
  }
229
229
  this.dragData.previewObject = cachedModel.clone();
@@ -234,6 +234,14 @@ var ComponentDragManager = /*#__PURE__*/function (_BaseDisposable) {
234
234
  // Store original colors BEFORE making transparent
235
235
  this._storeOriginalColors(this.dragData.previewObject);
236
236
 
237
+ // For smart components, load and attach IO device models to the preview
238
+ if (!(componentData.isSmart && componentData.attachedDevices)) {
239
+ _context2.n = 9;
240
+ break;
241
+ }
242
+ _context2.n = 9;
243
+ return this._attachIODeviceModelsToPreview(this.dragData.previewObject, componentData, modelPreloader);
244
+ case 9:
237
245
  // Make the preview semi-transparent
238
246
  this._setPreviewTransparency(this.dragData.previewObject, 0.5);
239
247
  this.dragData.previewObject.userData = {
@@ -247,19 +255,19 @@ var ComponentDragManager = /*#__PURE__*/function (_BaseDisposable) {
247
255
  this.sceneViewer.scene.add(this.dragData.previewObject);
248
256
  console.log("\u2705 Created ".concat(componentData.isS3Component ? 'S3' : 'static', " GLB preview object for: ").concat(componentId));
249
257
  return _context2.a(2);
250
- case 9:
251
- console.warn("\u26A0\uFE0F Failed to load model for ".concat(componentId, ", will use fallback"));
252
258
  case 10:
253
- _context2.n = 12;
254
- break;
259
+ console.warn("\u26A0\uFE0F Failed to load model for ".concat(componentId, ", will use fallback"));
255
260
  case 11:
256
- console.warn("\u26A0\uFE0F No modelKey found for component ".concat(componentId));
257
- case 12:
258
- _context2.n = 14;
261
+ _context2.n = 13;
259
262
  break;
263
+ case 12:
264
+ console.warn("\u26A0\uFE0F No modelKey found for component ".concat(componentId));
260
265
  case 13:
261
- console.warn("\u26A0\uFE0F ModelPreloader or component dictionary not available");
266
+ _context2.n = 15;
267
+ break;
262
268
  case 14:
269
+ console.warn("\u26A0\uFE0F ModelPreloader or component dictionary not available");
270
+ case 15:
263
271
  // Fallback: Create a simple preview mesh if model not available
264
272
  geometry = new THREE.BoxGeometry(1, 1, 1);
265
273
  material = new THREE.MeshPhysicalMaterial({
@@ -282,7 +290,7 @@ var ComponentDragManager = /*#__PURE__*/function (_BaseDisposable) {
282
290
  this.dragData.previewObject.position.set(1000, 1000, 1000);
283
291
  this.sceneViewer.scene.add(this.dragData.previewObject);
284
292
  console.log("\u26A0\uFE0F Created fallback wireframe preview for: ".concat(componentId));
285
- case 15:
293
+ case 16:
286
294
  return _context2.a(2);
287
295
  }
288
296
  }, _callee2, this, [[5, 7], [1, 3]]);
@@ -292,6 +300,111 @@ var ComponentDragManager = /*#__PURE__*/function (_BaseDisposable) {
292
300
  }
293
301
  return _createPreviewObject;
294
302
  }()
303
+ /**
304
+ * Load and attach IO device models to a smart component preview
305
+ * @param {THREE.Object3D} parentObject - The parent preview object
306
+ * @param {Object} componentData - Component dictionary entry (must have attachedDevices)
307
+ * @param {Object} modelPreloader - ModelPreloader instance
308
+ * @private
309
+ */
310
+ )
311
+ }, {
312
+ key: "_attachIODeviceModelsToPreview",
313
+ value: (function () {
314
+ var _attachIODeviceModelsToPreview2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee3(parentObject, componentData, modelPreloader) {
315
+ var _i, _Object$entries, _Object$entries$_i, attachmentId, attachment, _modelPreloader$compo, _attachment$attachmen, deviceData, cachedDevice, _modelPreloader$loadi, deviceModel, pos, _t3;
316
+ return _regenerator().w(function (_context3) {
317
+ while (1) switch (_context3.n) {
318
+ case 0:
319
+ if (componentData.attachedDevices) {
320
+ _context3.n = 1;
321
+ break;
322
+ }
323
+ return _context3.a(2);
324
+ case 1:
325
+ _i = 0, _Object$entries = Object.entries(componentData.attachedDevices);
326
+ case 2:
327
+ if (!(_i < _Object$entries.length)) {
328
+ _context3.n = 12;
329
+ break;
330
+ }
331
+ _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2), attachmentId = _Object$entries$_i[0], attachment = _Object$entries$_i[1];
332
+ _context3.p = 3;
333
+ deviceData = (_modelPreloader$compo = modelPreloader.componentDictionary) === null || _modelPreloader$compo === void 0 ? void 0 : _modelPreloader$compo[attachment.deviceId];
334
+ if (!(!deviceData || !deviceData.modelKey)) {
335
+ _context3.n = 4;
336
+ break;
337
+ }
338
+ console.warn("\u26A0\uFE0F IO device ".concat(attachment.deviceId, " not found in dictionary for preview"));
339
+ return _context3.a(3, 11);
340
+ case 4:
341
+ // Ensure device model is loaded
342
+ cachedDevice = modelPreloader.getCachedModelWithDimensions(deviceData.modelKey, attachment.deviceId);
343
+ if (cachedDevice) {
344
+ _context3.n = 8;
345
+ break;
346
+ }
347
+ if (!((_modelPreloader$loadi = modelPreloader.loadingPromises) !== null && _modelPreloader$loadi !== void 0 && _modelPreloader$loadi.has(deviceData.modelKey))) {
348
+ _context3.n = 6;
349
+ break;
350
+ }
351
+ _context3.n = 5;
352
+ return modelPreloader.loadingPromises.get(deviceData.modelKey);
353
+ case 5:
354
+ _context3.n = 7;
355
+ break;
356
+ case 6:
357
+ _context3.n = 7;
358
+ return modelPreloader.preloadSingleModel(deviceData.modelKey);
359
+ case 7:
360
+ cachedDevice = modelPreloader.getCachedModelWithDimensions(deviceData.modelKey, attachment.deviceId);
361
+ case 8:
362
+ if (cachedDevice) {
363
+ _context3.n = 9;
364
+ break;
365
+ }
366
+ console.warn("\u26A0\uFE0F Could not load IO device model: ".concat(deviceData.modelKey));
367
+ return _context3.a(3, 11);
368
+ case 9:
369
+ deviceModel = cachedDevice.clone();
370
+ this._cloneMaterials(deviceModel);
371
+ this._storeOriginalColors(deviceModel);
372
+ deviceModel.userData = {
373
+ objectType: 'io-device',
374
+ deviceId: attachment.deviceId,
375
+ attachmentId: attachmentId,
376
+ attachmentLabel: attachment.attachmentLabel
377
+ };
378
+ if ((_attachment$attachmen = attachment.attachmentPoint) !== null && _attachment$attachmen !== void 0 && _attachment$attachmen.position) {
379
+ pos = attachment.attachmentPoint.position;
380
+ deviceModel.position.set(pos.x || 0, pos.y || 0, pos.z || 0);
381
+ }
382
+
383
+ // IO device models use their natural (1:1) scale — the stored
384
+ // attachmentPoint.scale value is for the connector marker sphere.
385
+ deviceModel.scale.setScalar(1);
386
+ parentObject.add(deviceModel);
387
+ console.log("\u2705 Attached IO device preview: ".concat(attachment.attachmentLabel || attachment.deviceId));
388
+ _context3.n = 11;
389
+ break;
390
+ case 10:
391
+ _context3.p = 10;
392
+ _t3 = _context3.v;
393
+ console.warn("\u26A0\uFE0F Could not attach IO device model ".concat(attachment.deviceId, " to preview:"), _t3);
394
+ case 11:
395
+ _i++;
396
+ _context3.n = 2;
397
+ break;
398
+ case 12:
399
+ return _context3.a(2);
400
+ }
401
+ }, _callee3, this, [[3, 10]]);
402
+ }));
403
+ function _attachIODeviceModelsToPreview(_x5, _x6, _x7) {
404
+ return _attachIODeviceModelsToPreview2.apply(this, arguments);
405
+ }
406
+ return _attachIODeviceModelsToPreview;
407
+ }()
295
408
  /**
296
409
  * Clone all materials in an object hierarchy to avoid shared material issues
297
410
  * @param {THREE.Object3D} object - The object to clone materials for
@@ -439,8 +552,8 @@ var ComponentDragManager = /*#__PURE__*/function (_BaseDisposable) {
439
552
  });
440
553
 
441
554
  // Check for overlaps
442
- for (var _i = 0, _sceneMeshes = sceneMeshes; _i < _sceneMeshes.length; _i++) {
443
- var mesh = _sceneMeshes[_i];
555
+ for (var _i2 = 0, _sceneMeshes = sceneMeshes; _i2 < _sceneMeshes.length; _i2++) {
556
+ var mesh = _sceneMeshes[_i2];
444
557
  var meshBBox = new THREE.Box3().setFromObject(mesh);
445
558
  if (previewBBox.intersectsBox(meshBBox)) {
446
559
  console.log('⚠️ ComponentDragManager: Overlap detected with:', mesh.userData.objectType || mesh.name || mesh.uuid);
@@ -754,50 +867,50 @@ var ComponentDragManager = /*#__PURE__*/function (_BaseDisposable) {
754
867
  var _this3 = this;
755
868
  if (!element || !componentId) return;
756
869
  var handleMouseDown = /*#__PURE__*/function () {
757
- var _ref = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee3(event) {
758
- return _regenerator().w(function (_context3) {
759
- while (1) switch (_context3.n) {
870
+ var _ref = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee4(event) {
871
+ return _regenerator().w(function (_context4) {
872
+ while (1) switch (_context4.n) {
760
873
  case 0:
761
874
  if (!(event.button !== 0)) {
762
- _context3.n = 1;
875
+ _context4.n = 1;
763
876
  break;
764
877
  }
765
- return _context3.a(2);
878
+ return _context4.a(2);
766
879
  case 1:
767
880
  // Only left mouse button
768
881
  event.preventDefault();
769
- _context3.n = 2;
882
+ _context4.n = 2;
770
883
  return _this3.startComponentDrag(componentId, element, event);
771
884
  case 2:
772
- return _context3.a(2);
885
+ return _context4.a(2);
773
886
  }
774
- }, _callee3);
887
+ }, _callee4);
775
888
  }));
776
- return function handleMouseDown(_x5) {
889
+ return function handleMouseDown(_x8) {
777
890
  return _ref.apply(this, arguments);
778
891
  };
779
892
  }();
780
893
  var handleTouchStart = /*#__PURE__*/function () {
781
- var _ref2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee4(event) {
782
- return _regenerator().w(function (_context4) {
783
- while (1) switch (_context4.n) {
894
+ var _ref2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee5(event) {
895
+ return _regenerator().w(function (_context5) {
896
+ while (1) switch (_context5.n) {
784
897
  case 0:
785
898
  if (!(event.touches.length !== 1)) {
786
- _context4.n = 1;
899
+ _context5.n = 1;
787
900
  break;
788
901
  }
789
- return _context4.a(2);
902
+ return _context5.a(2);
790
903
  case 1:
791
904
  // Only single touch
792
905
  event.preventDefault();
793
- _context4.n = 2;
906
+ _context5.n = 2;
794
907
  return _this3.startComponentDrag(componentId, element, event);
795
908
  case 2:
796
- return _context4.a(2);
909
+ return _context5.a(2);
797
910
  }
798
- }, _callee4);
911
+ }, _callee5);
799
912
  }));
800
- return function handleTouchStart(_x6) {
913
+ return function handleTouchStart(_x9) {
801
914
  return _ref2.apply(this, arguments);
802
915
  };
803
916
  }();
@@ -2,6 +2,7 @@ import { createClass as _createClass, asyncToGenerator as _asyncToGenerator, reg
2
2
  import * as THREE from 'three';
3
3
  import { transformControls } from './transformControls.js';
4
4
  import { isSegment, isGateway } from '../../utils/objectTypes.js';
5
+ import { createSelectionBoxHelpers, updateSelectionBoxHelpers } from '../../utils/boundingBoxUtils.js';
5
6
 
6
7
  var TransformControlsManager = /*#__PURE__*/function () {
7
8
  function TransformControlsManager(scene, camera, renderer) {
@@ -918,23 +919,16 @@ var TransformControlsManager = /*#__PURE__*/function () {
918
919
  return;
919
920
  }
920
921
  try {
921
- // Create individual bounding boxes for each selected object
922
+ // Create bounding box helpers for each selected object
923
+ // Smart components get filtered helpers (component body + individual io-device boxes)
922
924
  this.selectedObjects.forEach(function (obj) {
923
- var boundingBoxHelper = new THREE.BoxHelper(obj, _this5.config.boundingBoxColor);
924
-
925
- // Mark it as a helper to avoid selection
926
- boundingBoxHelper.isHelper = true;
927
- boundingBoxHelper.userData = {
928
- isBoundingBox: true
929
- };
930
-
931
- // Add to scene
932
- _this5.scene.add(boundingBoxHelper);
933
-
934
- // Store in array for later cleanup
935
- _this5.boundingBoxHelpers.push(boundingBoxHelper);
925
+ var helpers = createSelectionBoxHelpers(obj, _this5.config.boundingBoxColor);
926
+ helpers.forEach(function (helper) {
927
+ _this5.scene.add(helper);
928
+ _this5.boundingBoxHelpers.push(helper);
929
+ });
936
930
  });
937
- console.log("\uD83D\uDCE6 Bounding boxes created for ".concat(this.selectedObjects.length, " object(s)"));
931
+ console.log("\uD83D\uDCE6 Bounding boxes created for ".concat(this.selectedObjects.length, " object(s) (").concat(this.boundingBoxHelpers.length, " helpers)"));
938
932
  } catch (error) {
939
933
  console.warn('⚠️ Failed to create bounding boxes:', error);
940
934
  }
@@ -1065,21 +1059,19 @@ var TransformControlsManager = /*#__PURE__*/function () {
1065
1059
  // Update bounding boxes for all selected objects
1066
1060
  if (this.selectedObjects.length > 0 && this.boundingBoxHelpers.length > 0) {
1067
1061
  try {
1068
- // Update each bounding box helper
1069
- this.boundingBoxHelpers.forEach(function (helper, index) {
1070
- var obj = _this6.selectedObjects[index];
1071
- if (obj) {
1072
- // Force object matrix update to ensure correct bounding box
1073
- obj.updateMatrixWorld(true);
1074
-
1075
- // Update bounding box
1076
- helper.update();
1077
-
1078
- // Also update the cached bounding box if it exists
1079
- if (_this6.boundingBoxCache.has(obj)) {
1080
- var updatedBoundingBox = new THREE.Box3().setFromObject(obj);
1081
- _this6.boundingBoxCache.set(obj, updatedBoundingBox);
1082
- }
1062
+ // Ensure all selected objects have up-to-date matrices
1063
+ this.selectedObjects.forEach(function (obj) {
1064
+ return obj.updateMatrixWorld(true);
1065
+ });
1066
+
1067
+ // Use the centralized update function which handles filtered, io-device, and standard helpers
1068
+ updateSelectionBoxHelpers(this.boundingBoxHelpers, this.selectedObjects, this.scene);
1069
+
1070
+ // Also update the cached bounding box if it exists
1071
+ this.selectedObjects.forEach(function (obj) {
1072
+ if (_this6.boundingBoxCache.has(obj)) {
1073
+ var updatedBoundingBox = new THREE.Box3().setFromObject(obj);
1074
+ _this6.boundingBoxCache.set(obj, updatedBoundingBox);
1083
1075
  }
1084
1076
  });
1085
1077
  } catch (error) {
@@ -3,6 +3,7 @@ import * as THREE from 'three';
3
3
  import { Pathfinder } from '@2112-lab/pathfinder';
4
4
  import { BaseDisposable } from '../../core/baseDisposable.js';
5
5
  import { PathData } from '../../core/pathfindingData.js';
6
+ import { computeFilteredBoundingBox, computeIODeviceBoundingBoxes } from '../../utils/boundingBoxUtils.js';
6
7
  import { SceneDataManager } from './sceneDataManager.js';
7
8
  import { PathRenderingManager } from './PathRenderingManager.js';
8
9
  import { ConnectorManager } from './ConnectorManager.js';
@@ -153,20 +154,63 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
153
154
  // Find the actual component object in the scene
154
155
  var componentObject = _this3.sceneViewer.scene.getObjectByProperty('uuid', child.uuid);
155
156
  if (componentObject) {
156
- // Compute world bounding box
157
- var _worldBBox = new THREE.Box3().setFromObject(componentObject);
158
- console.log("\uD83D\uDD04 Updated worldBoundingBox for component ".concat(child.uuid, ": min=[").concat(_worldBBox.min.x.toFixed(2), ", ").concat(_worldBBox.min.y.toFixed(2), ", ").concat(_worldBBox.min.z.toFixed(2), "], max=[").concat(_worldBBox.max.x.toFixed(2), ", ").concat(_worldBBox.max.y.toFixed(2), ", ").concat(_worldBBox.max.z.toFixed(2), "]"));
157
+ // Compute FILTERED bounding box — excludes io-device and connector subtrees
158
+ // so the component body bbox is tight-fitting and doesn't envelop attached devices
159
+ var filteredBBox = computeFilteredBoundingBox(componentObject, ['io-device', 'connector']);
160
+ 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), "]"));
159
161
 
160
- // Return enriched component data with worldBoundingBox in userData
161
- // Note: pathfinder expects arrays [x, y, z] format for min/max
162
- return _objectSpread2(_objectSpread2({}, child), {}, {
162
+ // Build the enriched component entry
163
+ var enrichedChild = _objectSpread2(_objectSpread2({}, child), {}, {
163
164
  userData: _objectSpread2(_objectSpread2({}, child.userData), {}, {
164
165
  worldBoundingBox: {
165
- min: [_worldBBox.min.x, _worldBBox.min.y, _worldBBox.min.z],
166
- max: [_worldBBox.max.x, _worldBBox.max.y, _worldBBox.max.z]
166
+ min: [filteredBBox.min.x, filteredBBox.min.y, filteredBBox.min.z],
167
+ max: [filteredBBox.max.x, filteredBBox.max.y, filteredBBox.max.z]
167
168
  }
168
169
  })
169
170
  });
171
+
172
+ // Compute separate bounding boxes for each attached io-device
173
+ // These are injected as children so the pathfinder treats each as an independent obstacle
174
+ var ioDeviceBBoxes = computeIODeviceBoundingBoxes(componentObject);
175
+ if (ioDeviceBBoxes.length > 0) {
176
+ // Ensure children array exists (may already contain connectors)
177
+ if (!enrichedChild.children) {
178
+ enrichedChild.children = [];
179
+ }
180
+
181
+ // Inject io-device entries with their own worldBoundingBox
182
+ ioDeviceBBoxes.forEach(function (deviceBBox) {
183
+ // Check if this io-device already exists in scene data children
184
+ var existingIndex = enrichedChild.children.findIndex(function (c) {
185
+ return c.uuid === deviceBBox.uuid;
186
+ });
187
+ if (existingIndex >= 0) {
188
+ // Update existing entry with bounding box
189
+ enrichedChild.children[existingIndex] = _objectSpread2(_objectSpread2({}, enrichedChild.children[existingIndex]), {}, {
190
+ userData: _objectSpread2(_objectSpread2({}, enrichedChild.children[existingIndex].userData), {}, {
191
+ objectType: 'io-device',
192
+ worldBoundingBox: deviceBBox.worldBoundingBox
193
+ })
194
+ });
195
+ } else {
196
+ // Create new entry for the io-device
197
+ enrichedChild.children.push({
198
+ uuid: deviceBBox.uuid,
199
+ userData: _objectSpread2(_objectSpread2({}, deviceBBox.userData), {}, {
200
+ worldBoundingBox: deviceBBox.worldBoundingBox
201
+ }),
202
+ children: []
203
+ });
204
+ }
205
+ console.log("\uD83D\uDCE6 Injected io-device bbox for ".concat(deviceBBox.uuid, ": min=[").concat(deviceBBox.worldBoundingBox.min.map(function (v) {
206
+ return v.toFixed(2);
207
+ }).join(', '), "], max=[").concat(deviceBBox.worldBoundingBox.max.map(function (v) {
208
+ return v.toFixed(2);
209
+ }).join(', '), "]"));
210
+ });
211
+ console.log("\uD83D\uDCE6 Injected ".concat(ioDeviceBBoxes.length, " io-device bounding box(es) for component ").concat(child.uuid));
212
+ }
213
+ return enrichedChild;
170
214
  } else {
171
215
  console.warn("\u26A0\uFE0F Could not find component object in scene: ".concat(child.uuid));
172
216
  }
@@ -3,6 +3,7 @@ import * as THREE from 'three';
3
3
  import { loadTextureSet } from '../environment/textureConfig.js';
4
4
  import { ModelManager } from './modelManager.js';
5
5
  import { SceneClearingUtility } from '../../utils/sceneClearingUtility.js';
6
+ import { computeFilteredBoundingBox, computeIODeviceBoundingBoxes } from '../../utils/boundingBoxUtils.js';
6
7
 
7
8
  var _excluded = ["direction"],
8
9
  _excluded2 = ["direction"];
@@ -618,6 +619,8 @@ var SceneOperationsManager = /*#__PURE__*/function () {
618
619
 
619
620
  /**
620
621
  * Helper function to compute world bounding boxes
622
+ * For components: uses filtered bbox (excludes io-device and connector subtrees)
623
+ * For io-devices: computes separate bounding boxes and injects them as children
621
624
  */
622
625
  }, {
623
626
  key: "computeWorldBoundingBoxes",
@@ -656,12 +659,46 @@ var SceneOperationsManager = /*#__PURE__*/function () {
656
659
  };
657
660
  jsonObject = _findJsonObject(data.scene.children);
658
661
  if (jsonObject) {
659
- // Compute world bounding box
660
- var boundingBox = new THREE.Box3().setFromObject(object);
661
-
662
662
  // Store in JSON userData for pathfinder (skip for gateways - they're just routing points)
663
663
  if (!jsonObject.userData) jsonObject.userData = {};
664
- if (jsonObject.userData.objectType !== 'gateway') {
664
+ if (jsonObject.userData.objectType === 'component') {
665
+ // For components: compute filtered bounding box (excludes io-device and connector subtrees)
666
+ var filteredBBox = computeFilteredBoundingBox(object, ['io-device', 'connector']);
667
+ jsonObject.userData.worldBoundingBox = {
668
+ min: filteredBBox.min.toArray(),
669
+ max: filteredBBox.max.toArray()
670
+ };
671
+ console.log("Added filtered world bounding box for component:", jsonObject.userData.worldBoundingBox);
672
+
673
+ // Compute and inject separate io-device bounding boxes as children
674
+ var ioDeviceBBoxes = computeIODeviceBoundingBoxes(object);
675
+ if (ioDeviceBBoxes.length > 0) {
676
+ if (!jsonObject.children) jsonObject.children = [];
677
+ ioDeviceBBoxes.forEach(function (deviceBBox) {
678
+ var existingIndex = jsonObject.children.findIndex(function (c) {
679
+ return c.uuid === deviceBBox.uuid;
680
+ });
681
+ if (existingIndex >= 0) {
682
+ // Update existing entry
683
+ if (!jsonObject.children[existingIndex].userData) jsonObject.children[existingIndex].userData = {};
684
+ jsonObject.children[existingIndex].userData.objectType = 'io-device';
685
+ jsonObject.children[existingIndex].userData.worldBoundingBox = deviceBBox.worldBoundingBox;
686
+ } else {
687
+ // Create new entry
688
+ jsonObject.children.push({
689
+ uuid: deviceBBox.uuid,
690
+ userData: _objectSpread2(_objectSpread2({}, deviceBBox.userData), {}, {
691
+ worldBoundingBox: deviceBBox.worldBoundingBox
692
+ }),
693
+ children: []
694
+ });
695
+ }
696
+ });
697
+ console.log("\uD83D\uDCE6 Injected ".concat(ioDeviceBBoxes.length, " io-device bbox(es) for component ").concat(jsonObject.uuid));
698
+ }
699
+ } else if (jsonObject.userData.objectType !== 'gateway') {
700
+ // For non-component, non-gateway objects: standard bounding box
701
+ var boundingBox = new THREE.Box3().setFromObject(object);
665
702
  jsonObject.userData.worldBoundingBox = {
666
703
  min: boundingBox.min.toArray(),
667
704
  max: boundingBox.max.toArray()