@2112-lab/central-plant 0.3.46 → 0.3.48

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.
Files changed (39) hide show
  1. package/dist/bundle/index.js +624 -425
  2. package/dist/cjs/src/core/centralPlant.js +46 -46
  3. package/dist/cjs/src/core/centralPlantInternals.js +4 -2
  4. package/dist/cjs/src/core/sceneViewer.js +1 -2
  5. package/dist/cjs/src/managers/behaviors/IoBehaviorManager.js +1 -2
  6. package/dist/cjs/src/managers/components/componentDataManager.js +0 -1
  7. package/dist/cjs/src/managers/components/componentManager.js +11 -3
  8. package/dist/cjs/src/managers/components/transformOperationsManager.js +9 -11
  9. package/dist/cjs/src/managers/controls/componentDragManager.js +2 -7
  10. package/dist/cjs/src/managers/controls/transformControlsManager.js +83 -44
  11. package/dist/cjs/src/managers/pathfinding/pathfindingManager.js +274 -192
  12. package/dist/cjs/src/managers/scene/collisionManager.js +1 -2
  13. package/dist/cjs/src/managers/scene/componentTooltipManager.js +2 -3
  14. package/dist/cjs/src/managers/scene/modelManager.js +33 -10
  15. package/dist/cjs/src/managers/scene/sceneExportManager.js +42 -12
  16. package/dist/cjs/src/managers/scene/sceneOperationsManager.js +26 -1
  17. package/dist/cjs/src/utils/behaviorDispatch.js +11 -42
  18. package/dist/cjs/src/utils/boundingBoxUtils.js +79 -36
  19. package/dist/cjs/src/utils/ioDeviceUtils.js +3 -9
  20. package/dist/esm/src/core/centralPlant.js +46 -46
  21. package/dist/esm/src/core/centralPlantInternals.js +4 -2
  22. package/dist/esm/src/core/sceneViewer.js +1 -2
  23. package/dist/esm/src/managers/behaviors/IoBehaviorManager.js +1 -2
  24. package/dist/esm/src/managers/components/componentDataManager.js +0 -1
  25. package/dist/esm/src/managers/components/componentManager.js +11 -3
  26. package/dist/esm/src/managers/components/transformOperationsManager.js +9 -11
  27. package/dist/esm/src/managers/controls/componentDragManager.js +2 -7
  28. package/dist/esm/src/managers/controls/transformControlsManager.js +83 -44
  29. package/dist/esm/src/managers/pathfinding/pathfindingManager.js +276 -194
  30. package/dist/esm/src/managers/scene/collisionManager.js +1 -2
  31. package/dist/esm/src/managers/scene/componentTooltipManager.js +2 -3
  32. package/dist/esm/src/managers/scene/modelManager.js +33 -10
  33. package/dist/esm/src/managers/scene/sceneExportManager.js +41 -13
  34. package/dist/esm/src/managers/scene/sceneOperationsManager.js +26 -1
  35. package/dist/esm/src/utils/behaviorDispatch.js +11 -42
  36. package/dist/esm/src/utils/boundingBoxUtils.js +80 -38
  37. package/dist/esm/src/utils/ioDeviceUtils.js +3 -9
  38. package/dist/index.d.ts +0 -6
  39. package/package.json +1 -1
@@ -1190,11 +1190,11 @@ function getIoBehaviorManager(sceneViewer) {
1190
1190
 
1191
1191
  /**
1192
1192
  * Resolve tooltip/drag data points for an I/O device attachment.
1193
- * Prefers behaviorConfig-driven animation data points; merges legacy ioConfig snapshot.
1193
+ * Prefers behaviorConfig-driven animation data points.
1194
1194
  *
1195
1195
  * @param {string} parentUuid
1196
1196
  * @param {string} attachmentId
1197
- * @param {Object} userData - io-device userData (may include dataPoints snapshot)
1197
+ * @param {Object} userData - io-device userData
1198
1198
  * @param {import('../managers/behaviors/IoBehaviorManager.js').IoBehaviorManager|null} ioBehaviorManager
1199
1199
  * @param {THREE.Object3D|null} [hitMesh]
1200
1200
  * @returns {Object[]}
@@ -1202,21 +1202,7 @@ function getIoBehaviorManager(sceneViewer) {
1202
1202
  function resolveDataPoints(parentUuid, attachmentId, userData, ioBehaviorManager) {
1203
1203
  var hitMesh = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : null;
1204
1204
  var fromAnimations = (ioBehaviorManager === null || ioBehaviorManager === void 0 ? void 0 : ioBehaviorManager.getAnimationDataPoints(parentUuid, attachmentId, hitMesh)) || [];
1205
- if (fromAnimations.length) return fromAnimations;
1206
- var legacy = (userData === null || userData === void 0 ? void 0 : userData.dataPoints) || [];
1207
- return legacy.map(function (dp) {
1208
- var _dp$defaultValue;
1209
- return {
1210
- id: dp.id || dp.name,
1211
- name: dp.name || dp.id,
1212
- stateType: dp.stateType || 'binary',
1213
- stateConfig: dp.stateConfig || {},
1214
- defaultValue: (_dp$defaultValue = dp.defaultValue) !== null && _dp$defaultValue !== void 0 ? _dp$defaultValue : null,
1215
- direction: dp.direction || (userData === null || userData === void 0 ? void 0 : userData.ioDirection) || 'output'
1216
- };
1217
- }).filter(function (dp) {
1218
- return dp.id;
1219
- });
1205
+ return fromAnimations;
1220
1206
  }
1221
1207
 
1222
1208
  /**
@@ -1247,35 +1233,18 @@ function applyDefaultIoDeviceStates(centralPlant) {
1247
1233
  if (seen.has(scopedKey)) return;
1248
1234
  seen.add(scopedKey);
1249
1235
  var dps = ioBehavMgr.getAnimationDataPoints(objParentUuid, objAttachmentId) || [];
1250
- if (dps.length) {
1251
- var _iterator = _createForOfIteratorHelper(dps),
1252
- _step;
1253
- try {
1254
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
1255
- var dp = _step.value;
1256
- if (!(dp !== null && dp !== void 0 && dp.id) || dp.defaultValue === undefined || dp.defaultValue === null) continue;
1257
- centralPlant.setIoDeviceState(objAttachmentId, dp.id, dp.defaultValue, objParentUuid);
1258
- }
1259
- } catch (err) {
1260
- _iterator.e(err);
1261
- } finally {
1262
- _iterator.f();
1263
- }
1264
- return;
1265
- }
1266
- var _iterator2 = _createForOfIteratorHelper(obj.userData.dataPoints || []),
1267
- _step2;
1236
+ var _iterator = _createForOfIteratorHelper(dps),
1237
+ _step;
1268
1238
  try {
1269
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
1270
- var _dp = _step2.value;
1271
- var dpId = _dp.id || _dp.name;
1272
- if (!dpId || _dp.defaultValue === undefined || _dp.defaultValue === null) continue;
1273
- centralPlant.setIoDeviceState(objAttachmentId, dpId, _dp.defaultValue, objParentUuid);
1239
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
1240
+ var dp = _step.value;
1241
+ if (!(dp !== null && dp !== void 0 && dp.id) || dp.defaultValue === undefined || dp.defaultValue === null) continue;
1242
+ centralPlant.setIoDeviceState(objAttachmentId, dp.id, dp.defaultValue, objParentUuid);
1274
1243
  }
1275
1244
  } catch (err) {
1276
- _iterator2.e(err);
1245
+ _iterator.e(err);
1277
1246
  } finally {
1278
- _iterator2.f();
1247
+ _iterator.f();
1279
1248
  }
1280
1249
  });
1281
1250
  }
@@ -3751,35 +3720,77 @@ function computeFilteredBoundingBox(object) {
3751
3720
  */
3752
3721
  function computeIODeviceBoundingBoxes(componentObject) {
3753
3722
  var results = [];
3754
- var _iterator = _createForOfIteratorHelper(componentObject.children),
3755
- _step;
3756
- try {
3757
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
3758
- var _child$userData;
3759
- var child = _step.value;
3760
- if (((_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.objectType) !== 'io-device') continue;
3761
- var bbox = new THREE__namespace.Box3().setFromObject(child);
3762
- if (!bbox.isEmpty()) {
3763
- results.push({
3764
- uuid: child.uuid,
3765
- userData: {
3766
- objectType: 'io-device',
3767
- deviceId: child.userData.deviceId || null,
3768
- attachmentId: child.userData.attachmentId || null,
3769
- parentComponentId: child.userData.parentComponentId || componentObject.uuid
3770
- },
3771
- worldBoundingBox: {
3772
- min: [bbox.min.x, bbox.min.y, bbox.min.z],
3773
- max: [bbox.max.x, bbox.max.y, bbox.max.z]
3774
- }
3775
- });
3776
- }
3723
+ componentObject.traverse(function (child) {
3724
+ var _child$userData;
3725
+ if (((_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.objectType) !== 'io-device') return;
3726
+ var bbox = new THREE__namespace.Box3().setFromObject(child);
3727
+ var worldPos = new THREE__namespace.Vector3();
3728
+ child.getWorldPosition(worldPos);
3729
+ if (!bbox.isEmpty()) {
3730
+ results.push({
3731
+ uuid: child.uuid,
3732
+ userData: {
3733
+ objectType: 'io-device',
3734
+ deviceId: child.userData.deviceId || null,
3735
+ attachmentId: child.userData.attachmentId || null,
3736
+ parentComponentId: child.userData.parentComponentId || componentObject.uuid,
3737
+ // Sync position for pathfinder
3738
+ position: [worldPos.x, worldPos.y, worldPos.z]
3739
+ },
3740
+ worldBoundingBox: {
3741
+ min: [bbox.min.x, bbox.min.y, bbox.min.z],
3742
+ max: [bbox.max.x, bbox.max.y, bbox.max.z]
3743
+ }
3744
+ });
3777
3745
  }
3778
- } catch (err) {
3779
- _iterator.e(err);
3780
- } finally {
3781
- _iterator.f();
3782
- }
3746
+ });
3747
+ return results;
3748
+ }
3749
+
3750
+ /**
3751
+ * Computes individual world-space bounding boxes for each connector child
3752
+ * of a component. Supports deep children (e.g. within GLB model hierarchy).
3753
+ *
3754
+ * @param {THREE.Object3D} componentObject - The component's Three.js object
3755
+ * @returns {Array<{uuid: string, userData: Object, worldBoundingBox: {min: number[], max: number[]}}>}
3756
+ * Array of connector bounding box descriptors ready for injection into scene data
3757
+ */
3758
+ function computeConnectorBoundingBoxes(componentObject) {
3759
+ var results = [];
3760
+ componentObject.traverse(function (child) {
3761
+ var _child$userData2;
3762
+ if (((_child$userData2 = child.userData) === null || _child$userData2 === void 0 ? void 0 : _child$userData2.objectType) !== 'connector') return;
3763
+ var bbox = new THREE__namespace.Box3().setFromObject(child);
3764
+ var worldPos = new THREE__namespace.Vector3();
3765
+ child.getWorldPosition(worldPos);
3766
+
3767
+ // Compute world-space direction vector (Crucial for rotation-aware pathfinding)
3768
+ // Default to [0, 0, 1] if not specified (Standard for our coordinate system)
3769
+ var localDirData = child.userData && Array.isArray(child.userData.direction) ? child.userData.direction : [0, 0, 1];
3770
+ var localDir = new THREE__namespace.Vector3(localDirData[0], localDirData[1], localDirData[2]);
3771
+ var worldQuat = new THREE__namespace.Quaternion();
3772
+ child.getWorldQuaternion(worldQuat);
3773
+ var worldDir = localDir.clone().applyQuaternion(worldQuat).normalize();
3774
+
3775
+ // Fallback if mesh is too small or empty (sometimes connectors are just points)
3776
+ if (bbox.isEmpty() || bbox.getSize(new THREE__namespace.Vector3()).length() < 0.01) {
3777
+ var size = 0.1;
3778
+ bbox.setFromCenterAndSize(worldPos, new THREE__namespace.Vector3(size, size, size));
3779
+ }
3780
+ results.push({
3781
+ uuid: child.uuid,
3782
+ userData: _objectSpread2(_objectSpread2({}, child.userData), {}, {
3783
+ objectType: 'connector',
3784
+ // Update both position AND direction for pathfinder
3785
+ position: [worldPos.x, worldPos.y, worldPos.z],
3786
+ direction: [worldDir.x, worldDir.y, worldDir.z]
3787
+ }),
3788
+ worldBoundingBox: {
3789
+ min: [bbox.min.x, bbox.min.y, bbox.min.z],
3790
+ max: [bbox.max.x, bbox.max.y, bbox.max.z]
3791
+ }
3792
+ });
3793
+ });
3783
3794
  return results;
3784
3795
  }
3785
3796
 
@@ -3841,8 +3852,8 @@ function createSelectionBoxHelpers(object) {
3841
3852
 
3842
3853
  // Check if this object has io-device children (smart component)
3843
3854
  var hasIODevices = (_object$children = object.children) === null || _object$children === void 0 ? void 0 : _object$children.some(function (child) {
3844
- var _child$userData2;
3845
- return ((_child$userData2 = child.userData) === null || _child$userData2 === void 0 ? void 0 : _child$userData2.objectType) === 'io-device';
3855
+ var _child$userData3;
3856
+ return ((_child$userData3 = child.userData) === null || _child$userData3 === void 0 ? void 0 : _child$userData3.objectType) === 'io-device';
3846
3857
  });
3847
3858
  if (hasIODevices) {
3848
3859
  // 1. Create filtered helper for the component body
@@ -3879,11 +3890,11 @@ function createSelectionBoxHelpers(object) {
3879
3890
  * @param {THREE.Scene} scene - The scene (for finding objects by uuid)
3880
3891
  */
3881
3892
  function updateSelectionBoxHelpers(helpers, selectedObjects, scene) {
3882
- var _iterator2 = _createForOfIteratorHelper(helpers),
3883
- _step2;
3893
+ var _iterator = _createForOfIteratorHelper(helpers),
3894
+ _step;
3884
3895
  try {
3885
3896
  var _loop = function _loop() {
3886
- var helper = _step2.value;
3897
+ var helper = _step.value;
3887
3898
  var _helper$userData = helper.userData,
3888
3899
  sourceObjectUuid = _helper$userData.sourceObjectUuid,
3889
3900
  isFiltered = _helper$userData.isFiltered,
@@ -3914,13 +3925,13 @@ function updateSelectionBoxHelpers(helpers, selectedObjects, scene) {
3914
3925
  helper.update();
3915
3926
  }
3916
3927
  };
3917
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
3928
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
3918
3929
  if (_loop()) continue;
3919
3930
  }
3920
3931
  } catch (err) {
3921
- _iterator2.e(err);
3932
+ _iterator.e(err);
3922
3933
  } finally {
3923
- _iterator2.f();
3934
+ _iterator.f();
3924
3935
  }
3925
3936
  }
3926
3937
 
@@ -3960,7 +3971,7 @@ var TransformControlsManager = /*#__PURE__*/function () {
3960
3971
  this.forceInvisible = false;
3961
3972
 
3962
3973
  // SceneViewer reference for event listening
3963
- this.sceneViewer = null;
3974
+ this.sceneViewer = centralPlant ? centralPlant.sceneViewer : null;
3964
3975
  this._objectTransformedListener = null;
3965
3976
 
3966
3977
  // Event handlers storage
@@ -4162,23 +4173,28 @@ var TransformControlsManager = /*#__PURE__*/function () {
4162
4173
  _this2.transformState.isTransforming = true;
4163
4174
 
4164
4175
  // Store initial transforms for all selected objects
4165
- if (_this2.selectedObjects.length > 0 && _this2.multiSelectionGroup) {
4176
+ if (_this2.selectedObjects.length > 0) {
4166
4177
  _this2.selectedObjects.forEach(function (obj) {
4167
- obj.userData._multiSelectOriginalPosition = obj.position.clone();
4168
- obj.userData._multiSelectOriginalRotation = obj.rotation.clone();
4169
- obj.userData._multiSelectOriginalScale = obj.scale.clone();
4170
- });
4171
-
4172
- // Snapshot group position and helper geometry vertices so we can do
4173
- // cheap per-frame bbox translation without re-traversing the mesh hierarchy.
4174
- _this2._dragStartGroupPosition = _this2.multiSelectionGroup.position.clone();
4175
- _this2.boundingBoxHelpers.forEach(function (helper) {
4176
- var _helper$geometry;
4177
- var posAttr = (_helper$geometry = helper.geometry) === null || _helper$geometry === void 0 || (_helper$geometry = _helper$geometry.attributes) === null || _helper$geometry === void 0 ? void 0 : _helper$geometry.position;
4178
- if (posAttr) {
4179
- helper.userData._dragStartPositions = new Float32Array(posAttr.array);
4178
+ obj.userData._dragStartRotation = obj.rotation.clone();
4179
+ obj.userData._dragStartPosition = obj.position.clone();
4180
+ if (_this2.multiSelectionGroup) {
4181
+ obj.userData._multiSelectOriginalPosition = obj.position.clone();
4182
+ obj.userData._multiSelectOriginalRotation = obj.rotation.clone();
4183
+ obj.userData._multiSelectOriginalScale = obj.scale.clone();
4180
4184
  }
4181
4185
  });
4186
+ if (_this2.multiSelectionGroup) {
4187
+ // Snapshot group position and helper geometry vertices so we can do
4188
+ // cheap per-frame bbox translation without re-traversing the mesh hierarchy.
4189
+ _this2._dragStartGroupPosition = _this2.multiSelectionGroup.position.clone();
4190
+ _this2.boundingBoxHelpers.forEach(function (helper) {
4191
+ var _helper$geometry;
4192
+ var posAttr = (_helper$geometry = helper.geometry) === null || _helper$geometry === void 0 || (_helper$geometry = _helper$geometry.attributes) === null || _helper$geometry === void 0 ? void 0 : _helper$geometry.position;
4193
+ if (posAttr) {
4194
+ helper.userData._dragStartPositions = new Float32Array(posAttr.array);
4195
+ }
4196
+ });
4197
+ }
4182
4198
  }
4183
4199
 
4184
4200
  // Disable orbit controls during transformation
@@ -4195,7 +4211,7 @@ var TransformControlsManager = /*#__PURE__*/function () {
4195
4211
 
4196
4212
  // Transform end event
4197
4213
  this.eventHandlers.transformEnd = /*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee() {
4198
- var hasComponents, _this2$selectedObject, sceneCompleteEvent;
4214
+ var isTranslate, isRotate, hasComponents, _this2$selectedObject, sceneCompleteEvent;
4199
4215
  return _regenerator().w(function (_context) {
4200
4216
  while (1) switch (_context.n) {
4201
4217
  case 0:
@@ -4217,29 +4233,52 @@ var TransformControlsManager = /*#__PURE__*/function () {
4217
4233
  console.error('❌ Error in applyMultiSelectionTransform:', error);
4218
4234
  });
4219
4235
  case 1:
4220
- // applyMultiSelectionTransform() already calls updatePaths() once at the end
4221
- // so we skip the additional updatePaths() call below
4222
- console.log('✅ Multi-selection transform complete (updatePaths already called)');
4236
+ console.log('✅ Multi-selection transform complete');
4223
4237
  _context.n = 3;
4224
4238
  break;
4225
4239
  case 2:
4226
- if (_this2.currentMode === 'translate' && _this2.sceneViewer && typeof _this2.sceneViewer.updatePaths === 'function') {
4227
- // Update paths after translation is complete
4228
- // Only if we translated components (not segments/gateways - they handle paths internally)
4229
- // This branch only executes when NOT in multi-selection mode
4230
- hasComponents = _this2.selectedObjects.some(function (obj) {
4231
- return !isSegment(obj) && !isGateway(obj);
4232
- });
4233
- if (hasComponents) {
4234
- console.log('🔄 Updating paths after component translation...');
4235
- try {
4236
- _this2.sceneViewer.updatePaths();
4237
- console.log('✅ Paths updated successfully after translation');
4238
- } catch (error) {
4239
- console.error('❌ Error updating paths after translation:', error);
4240
+ if (_this2.sceneViewer && typeof _this2.sceneViewer.updatePaths === 'function') {
4241
+ // Update paths after transformation is complete (translation or rotation)
4242
+ isTranslate = _this2.currentMode === 'translate';
4243
+ isRotate = _this2.currentMode === 'rotate';
4244
+ if (isTranslate || isRotate) {
4245
+ hasComponents = _this2.selectedObjects.some(function (obj) {
4246
+ return !isSegment(obj) && !isGateway(obj);
4247
+ });
4248
+ if (hasComponents) {
4249
+ console.log("\uD83D\uDD04 Updating paths after component ".concat(_this2.currentMode, "..."));
4250
+ try {
4251
+ // Ensure scene data is synced before updating paths
4252
+ _this2.selectedObjects.forEach(function (obj) {
4253
+ var _obj$userData;
4254
+ if (((_obj$userData = obj.userData) === null || _obj$userData === void 0 ? void 0 : _obj$userData.objectType) === 'component') {
4255
+ var _this2$sceneViewer$ma;
4256
+ var transformMgr = (_this2$sceneViewer$ma = _this2.sceneViewer.managers) === null || _this2$sceneViewer$ma === void 0 ? void 0 : _this2$sceneViewer$ma.transformOperationsManager;
4257
+ if (transformMgr) {
4258
+ if (isTranslate) {
4259
+ transformMgr.updateComponentPositionInSceneData(obj);
4260
+ }
4261
+ if (isRotate) {
4262
+ transformMgr.updateComponentRotationInSceneData(obj);
4263
+
4264
+ // Calculate rotation delta for direction vector patching
4265
+ var startRot = obj.userData._dragStartRotation || obj.rotation;
4266
+ var deltaRad = obj.rotation.z - startRot.z;
4267
+ var deltaDeg = Math.round(THREE__namespace.MathUtils.radToDeg(deltaRad) / 90) * 90;
4268
+ if (deltaDeg !== 0) {
4269
+ console.log("\uD83E\uDDED Patching connector directions by ".concat(deltaDeg, " degrees after gizmo rotation"));
4270
+ transformMgr.updateConnectorDirections(obj, 'z', deltaDeg);
4271
+ }
4272
+ }
4273
+ }
4274
+ }
4275
+ });
4276
+ _this2.sceneViewer.updatePaths();
4277
+ console.log("\u2705 Paths updated successfully after ".concat(_this2.currentMode));
4278
+ } catch (error) {
4279
+ console.error("\u274C Error updating paths after ".concat(_this2.currentMode, ":"), error);
4280
+ }
4240
4281
  }
4241
- } else {
4242
- console.log('ℹ️ Skipping updatePaths - segments/gateways handle their own path updates');
4243
4282
  }
4244
4283
  }
4245
4284
  case 3:
@@ -4370,8 +4409,8 @@ var TransformControlsManager = /*#__PURE__*/function () {
4370
4409
  var hit = _step.value;
4371
4410
  var _obj = hit.object;
4372
4411
  while (_obj) {
4373
- var _obj$userData2;
4374
- if (((_obj$userData2 = _obj.userData) === null || _obj$userData2 === void 0 ? void 0 : _obj$userData2.objectType) === 'io-device') {
4412
+ var _obj$userData3;
4413
+ if (((_obj$userData3 = _obj.userData) === null || _obj$userData3 === void 0 ? void 0 : _obj$userData3.objectType) === 'io-device') {
4375
4414
  ioDeviceObject = _obj;
4376
4415
  break;
4377
4416
  }
@@ -4394,8 +4433,8 @@ var TransformControlsManager = /*#__PURE__*/function () {
4394
4433
  var parentUuid = null;
4395
4434
  var obj = ioDeviceObject.parent;
4396
4435
  while (obj) {
4397
- var _obj$userData;
4398
- if (((_obj$userData = obj.userData) === null || _obj$userData === void 0 ? void 0 : _obj$userData.objectType) === 'component') {
4436
+ var _obj$userData2;
4437
+ if (((_obj$userData2 = obj.userData) === null || _obj$userData2 === void 0 ? void 0 : _obj$userData2.objectType) === 'component') {
4399
4438
  parentUuid = obj.userData.originalUuid || obj.uuid;
4400
4439
  break;
4401
4440
  }
@@ -4472,8 +4511,8 @@ var TransformControlsManager = /*#__PURE__*/function () {
4472
4511
  var hit = _step2.value;
4473
4512
  var obj = hit.object;
4474
4513
  while (obj) {
4475
- var _obj$userData3;
4476
- if (((_obj$userData3 = obj.userData) === null || _obj$userData3 === void 0 ? void 0 : _obj$userData3.objectType) === 'io-device') {
4514
+ var _obj$userData4;
4515
+ if (((_obj$userData4 = obj.userData) === null || _obj$userData4 === void 0 ? void 0 : _obj$userData4.objectType) === 'io-device') {
4477
4516
  _this4.callbacks.onIODeviceClick(obj);
4478
4517
  return;
4479
4518
  }
@@ -6134,8 +6173,8 @@ var TransformControlsManager = /*#__PURE__*/function () {
6134
6173
  key: "_updateSegmentReference",
6135
6174
  value: function _updateSegmentReference(oldSegment, newSegment, index) {
6136
6175
  var selectedIndex = this.selectedObjects.findIndex(function (obj) {
6137
- var _obj$userData4;
6138
- return obj.uuid === oldSegment.uuid || ((_obj$userData4 = obj.userData) === null || _obj$userData4 === void 0 ? void 0 : _obj$userData4.originalUuid) === oldSegment.uuid;
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;
6139
6178
  });
6140
6179
  if (selectedIndex !== -1 && newSegment) {
6141
6180
  // Clear bounding box cache
@@ -6243,7 +6282,7 @@ var TransformControlsManager = /*#__PURE__*/function () {
6243
6282
  componentId = component.uuid || ((_component$userData = component.userData) === null || _component$userData === void 0 ? void 0 : _component$userData.originalUuid);
6244
6283
  _context6.n = 3;
6245
6284
  return this._translateObjectOnAxes(componentId, deltaX, deltaY, deltaZ, threshold, throttleDelay, AXIS_THROTTLE, function (id, axis, delta) {
6246
- return _this0.centralPlant.translate(id, axis, delta);
6285
+ return _this0.centralPlant.translate(id, axis, delta, true);
6247
6286
  });
6248
6287
  case 3:
6249
6288
  console.log("\uD83D\uDD27 Component ".concat(component.name, " translated (").concat(i + 1, "/").concat(components.length, ")"));
@@ -6342,6 +6381,17 @@ var TransformControlsManager = /*#__PURE__*/function () {
6342
6381
  if (this.selectedObjects.length > 0) {
6343
6382
  this.updateMultiSelection();
6344
6383
  }
6384
+
6385
+ // Trigger pathfinding update once after all objects in the batch are translated
6386
+ if (this.sceneViewer && typeof this.sceneViewer.updatePaths === 'function') {
6387
+ var hasComponentsOrSegments = this.selectedObjects.some(function (obj) {
6388
+ return !isGateway(obj);
6389
+ });
6390
+ if (hasComponentsOrSegments) {
6391
+ console.log('🔄 Triggering batched pathfinding update after multi-object translation...');
6392
+ this.sceneViewer.updatePaths();
6393
+ }
6394
+ }
6345
6395
  console.log("\u2705 Multi-selection transform applied");
6346
6396
  }
6347
6397
 
@@ -11867,6 +11917,11 @@ var SceneExportManager = /*#__PURE__*/function () {
11867
11917
  // Helper function to convert Three.js object to minimal JSON format
11868
11918
  var convertObjectToJson = function convertObjectToJson(threeObject) {
11869
11919
  var _threeObject$name, _threeObject$userData, _threeObject$userData2, _threeObject$userData3, _threeObject$userData4, _threeObject$userData5, _threeObject$userData6, _threeObject$userData7, _threeObject$userData8, _threeObject$userData9, _threeObject$userData0, _threeObject$userData1, _threeObject$userData10;
11920
+ // Ensure world matrices are updated for this subtree before exporting
11921
+ if (threeObject.updateMatrixWorld) {
11922
+ threeObject.updateMatrixWorld(true);
11923
+ }
11924
+
11870
11925
  // Skip certain objects that shouldn't be exported
11871
11926
  if (!threeObject || (_threeObject$name = threeObject.name) !== null && _threeObject$name !== void 0 && _threeObject$name.includes('Polyline') ||
11872
11927
  // Skip pipe paths
@@ -12005,11 +12060,11 @@ var SceneExportManager = /*#__PURE__*/function () {
12005
12060
  };
12006
12061
  }
12007
12062
 
12008
- // For components: no children exported connector positions are defined in the component
12009
- // dictionary GLB and will be re-injected at import time via _injectConnectorChildrenFromDictionary.
12010
- // Exporting them here would prevent that injection (it skips if children already exist)
12011
- // and would leave the pathfinder with connectors in incompatible local-position format.
12063
+ // For components: only export child connectors if they were manually added/defined.
12064
+ // Most connectors are injected from dictionary at import time, but some (like manually placed ones)
12065
+ // need to be persisted to maintain connections in the exported scene.
12012
12066
  if (threeObject.children && threeObject.children.length > 0) {
12067
+ var _threeObject$userData11;
12013
12068
  var exportableChildren = [];
12014
12069
  if (isManualSegment) {
12015
12070
  // For manual segments, export their connector children
@@ -12023,6 +12078,29 @@ var SceneExportManager = /*#__PURE__*/function () {
12023
12078
  }
12024
12079
  }
12025
12080
  });
12081
+ } else if (((_threeObject$userData11 = threeObject.userData) === null || _threeObject$userData11 === void 0 ? void 0 : _threeObject$userData11.objectType) === 'component') {
12082
+ // For components, export all connectors (including deep children within GLFs)
12083
+ threeObject.traverse(function (child) {
12084
+ var _child$userData2;
12085
+ if (((_child$userData2 = child.userData) === null || _child$userData2 === void 0 ? void 0 : _child$userData2.objectType) === 'connector') {
12086
+ // Calculate position relative to component root
12087
+ var componentMatrixWorldInverse = threeObject.matrixWorld.clone().invert();
12088
+ var childWorldPos = new THREE__namespace.Vector3();
12089
+ child.getWorldPosition(childWorldPos);
12090
+ var relativePos = childWorldPos.applyMatrix4(componentMatrixWorldInverse);
12091
+ exportableChildren.push({
12092
+ uuid: child.uuid,
12093
+ name: child.name,
12094
+ type: 'Mesh',
12095
+ position: {
12096
+ x: roundIfClose(relativePos.x),
12097
+ y: roundIfClose(relativePos.y),
12098
+ z: roundIfClose(relativePos.z)
12099
+ },
12100
+ userData: _objectSpread2({}, child.userData)
12101
+ });
12102
+ }
12103
+ });
12026
12104
  }
12027
12105
  if (exportableChildren.length > 0) {
12028
12106
  jsonObject.children = exportableChildren;
@@ -12037,9 +12115,9 @@ var SceneExportManager = /*#__PURE__*/function () {
12037
12115
  // Extract main scene objects (components and standalone connectors)
12038
12116
  var sceneChildren = [];
12039
12117
  this.sceneViewer.scene.children.forEach(function (child) {
12040
- var _child$userData2;
12118
+ var _child$userData3;
12041
12119
  // Only export components and connectors; skip segments, gateways, polylines, etc.
12042
- var objectType = (_child$userData2 = child.userData) === null || _child$userData2 === void 0 ? void 0 : _child$userData2.objectType;
12120
+ var objectType = (_child$userData3 = child.userData) === null || _child$userData3 === void 0 ? void 0 : _child$userData3.objectType;
12043
12121
  if (objectType !== 'component' && objectType !== 'connector') {
12044
12122
  return;
12045
12123
  }
@@ -12203,14 +12281,14 @@ var SceneExportManager = /*#__PURE__*/function () {
12203
12281
  BufferGeometryUtils$1 = BufferGeometryUtilsModule.BufferGeometryUtils || BufferGeometryUtilsModule.default || BufferGeometryUtilsModule; // Create a new scene for export instead of cloning
12204
12282
  exportScene = new _THREE.Scene(); // Helper function to check if an object should be exported
12205
12283
  shouldExport = function shouldExport(child) {
12206
- var _child$name, _child$userData3, _child$userData4, _child$userData5, _child$userData6;
12284
+ var _child$name, _child$userData4, _child$userData5, _child$userData6, _child$userData7;
12207
12285
  if ((_child$name = child.name) !== null && _child$name !== void 0 && _child$name.includes('Polyline')) return false; // Will handle separately
12208
12286
  if (child.name === 'fogPlane') return false; // Skip fog plane
12209
- if ((_child$userData3 = child.userData) !== null && _child$userData3 !== void 0 && _child$userData3.isBrickWall) return false; // Skip environment
12210
- if ((_child$userData4 = child.userData) !== null && _child$userData4 !== void 0 && _child$userData4.isBaseGround) return false; // Skip environment
12211
- if ((_child$userData5 = child.userData) !== null && _child$userData5 !== void 0 && _child$userData5.isBaseGrid) return false; // Skip environment
12287
+ if ((_child$userData4 = child.userData) !== null && _child$userData4 !== void 0 && _child$userData4.isBrickWall) return false; // Skip environment
12288
+ if ((_child$userData5 = child.userData) !== null && _child$userData5 !== void 0 && _child$userData5.isBaseGround) return false; // Skip environment
12289
+ if ((_child$userData6 = child.userData) !== null && _child$userData6 !== void 0 && _child$userData6.isBaseGrid) return false; // Skip environment
12212
12290
  if (child.isLight) return false; // Skip lights
12213
- if ((_child$userData6 = child.userData) !== null && _child$userData6 !== void 0 && _child$userData6.isTransformControls) return false; // Skip transform controls
12291
+ if ((_child$userData7 = child.userData) !== null && _child$userData7 !== void 0 && _child$userData7.isTransformControls) return false; // Skip transform controls
12214
12292
  if (child.isTransformControls) return false; // Skip transform controls
12215
12293
  if (child.type && child.type.includes('TransformControls')) return false;
12216
12294
  if (child.type && child.type.includes('Helper')) return false; // Skip helpers
@@ -19868,7 +19946,7 @@ var ComponentManager = /*#__PURE__*/function () {
19868
19946
  key: "addComponentToScene",
19869
19947
  value: (function () {
19870
19948
  var _addComponentToScene = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(componentData) {
19871
- var _gltfScene$userData, uuid, componentDictionary, libraryComponent, position, gltfScene, componentMesh, event, _t, _t2;
19949
+ var _gltfScene$userData, uuid, componentDictionary, libraryComponent, position, gltfScene, componentMesh, connectorIndex, event, _t, _t2;
19872
19950
  return _regenerator().w(function (_context) {
19873
19951
  while (1) switch (_context.n) {
19874
19952
  case 0:
@@ -19972,6 +20050,7 @@ var ComponentManager = /*#__PURE__*/function () {
19972
20050
  }
19973
20051
 
19974
20052
  // Enable shadows for all meshes in the component
20053
+ connectorIndex = 0;
19975
20054
  componentMesh.traverse(function (child) {
19976
20055
  if (child.isMesh) {
19977
20056
  child.castShadow = true;
@@ -19981,12 +20060,19 @@ var ComponentManager = /*#__PURE__*/function () {
19981
20060
  }
19982
20061
 
19983
20062
  // Set connector properties for connectors within the component
19984
- if (child.name && child.name.toLowerCase().includes('connector')) {
20063
+ if (child.name && child.name.toLowerCase().includes('connector') || child.userData && child.userData.objectType === 'connector') {
20064
+ connectorIndex++;
19985
20065
  if (!child.userData) {
19986
20066
  child.userData = {};
19987
20067
  }
19988
20068
  child.userData.objectType = 'connector';
19989
- console.log("\uD83D\uDD27 Set objectType='connector' for: ".concat(child.name));
20069
+
20070
+ // Assign a predictable, stable UUID to the connector
20071
+ // This ensures connections made in the sandbox survive export/import
20072
+ var connectorUuid = "".concat(uuid, "-CONNECTOR-").concat(connectorIndex);
20073
+ child.uuid = connectorUuid;
20074
+ child.userData.originalUuid = connectorUuid;
20075
+ console.log("\uD83D\uDD27 Set predictable connector UUID: ".concat(connectorUuid, " for: ").concat(child.name));
19990
20076
  }
19991
20077
  }
19992
20078
  });
@@ -21031,7 +21117,6 @@ var ComponentDataManager = /*#__PURE__*/function (_BaseDisposable) {
21031
21117
  };
21032
21118
  return _objectSpread2(_objectSpread2({}, baseData), {}, {
21033
21119
  metadata: component.metadata || {},
21034
- ioConfig: component.ioConfig || null,
21035
21120
  boundingBox: component.boundingBox || null,
21036
21121
  adaptedBoundingBox: component.adaptedBoundingBox || null,
21037
21122
  children: component.children || [],
@@ -21182,12 +21267,14 @@ var TransformOperationsManager = /*#__PURE__*/function () {
21182
21267
  * @param {string} componentId - The UUID of the component to translate
21183
21268
  * @param {string} axis - The axis to translate on ('x', 'y', or 'z')
21184
21269
  * @param {number} value - The value to translate by
21270
+ * @param {boolean} [skipPathUpdate=false] - Whether to skip triggering pathfinding after translation
21185
21271
  * @returns {boolean} True if translation was successful, false otherwise
21186
21272
  */
21187
21273
  return _createClass(TransformOperationsManager, [{
21188
21274
  key: "translateComponent",
21189
21275
  value: function translateComponent(componentId, axis, value) {
21190
21276
  var _this$sceneViewer$man, _this$sceneViewer;
21277
+ var skipPathUpdate = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
21191
21278
  // Store transform parameters using the OperationHistoryManager
21192
21279
  if ((_this$sceneViewer$man = this.sceneViewer.managers) !== null && _this$sceneViewer$man !== void 0 && _this$sceneViewer$man.operationHistoryManager) {
21193
21280
  this.sceneViewer.managers.operationHistoryManager.addToOperationHistory('translateComponent', {
@@ -21255,7 +21342,7 @@ var TransformOperationsManager = /*#__PURE__*/function () {
21255
21342
  }
21256
21343
 
21257
21344
  // Auto-update paths if enabled (matches behavior of translateSegment and translateGateway)
21258
- if (this.sceneViewer.shouldUpdatePaths) {
21345
+ if (!skipPathUpdate && this.sceneViewer.shouldUpdatePaths) {
21259
21346
  try {
21260
21347
  if (this.sceneViewer && typeof this.sceneViewer.updatePaths === 'function') {
21261
21348
  this.sceneViewer.updatePaths();
@@ -21266,6 +21353,8 @@ var TransformOperationsManager = /*#__PURE__*/function () {
21266
21353
  } catch (error) {
21267
21354
  console.error('❌ Error auto-updating paths:', error);
21268
21355
  }
21356
+ } else if (skipPathUpdate) {
21357
+ console.log('ℹ️ skipPathUpdate is true: skipping automatic path update');
21269
21358
  }
21270
21359
 
21271
21360
  // Emit transform event if available
@@ -23084,11 +23173,8 @@ var TransformOperationsManager = /*#__PURE__*/function () {
23084
23173
  sceneDataComponent.children.forEach(function (child) {
23085
23174
  var _child$userData21;
23086
23175
  if (((_child$userData21 = child.userData) === null || _child$userData21 === void 0 ? void 0 : _child$userData21.objectType) === 'connector') {
23087
- // Find the actual connector object in the scene
23088
- var connectorObj = component.children.find(function (c) {
23089
- var _c$userData;
23090
- return c.uuid === child.uuid || ((_c$userData = c.userData) === null || _c$userData === void 0 ? void 0 : _c$userData.originalUuid) === child.uuid;
23091
- });
23176
+ // Find the actual connector object in the scene (recursive search for nested connectors)
23177
+ var connectorObj = component.getObjectByProperty('uuid', child.uuid) || component.getObjectByProperty('name', child.uuid);
23092
23178
  if (connectorObj) {
23093
23179
  // Get world position of connector
23094
23180
  var worldPos = new THREE__namespace.Vector3();
@@ -23147,11 +23233,8 @@ var TransformOperationsManager = /*#__PURE__*/function () {
23147
23233
  sceneDataComponent.children.forEach(function (child) {
23148
23234
  var _child$userData22;
23149
23235
  if (((_child$userData22 = child.userData) === null || _child$userData22 === void 0 ? void 0 : _child$userData22.objectType) === 'connector') {
23150
- // Find the actual connector object in the scene
23151
- var connectorObj = component.children.find(function (c) {
23152
- var _c$userData2;
23153
- return c.uuid === child.uuid || ((_c$userData2 = c.userData) === null || _c$userData2 === void 0 ? void 0 : _c$userData2.originalUuid) === child.uuid;
23154
- });
23236
+ // Find the actual connector object in the scene (recursive search for nested connectors)
23237
+ var connectorObj = component.getObjectByProperty('uuid', child.uuid) || component.getObjectByProperty('name', child.uuid);
23155
23238
  if (connectorObj) {
23156
23239
  var _connectorObj$userDat;
23157
23240
  // Get world position of connector
@@ -29077,10 +29160,11 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
29077
29160
  }, {
29078
29161
  key: "_matrixHash",
29079
29162
  value: function _matrixHash(obj) {
29163
+ // Force matrix update to ensure we're looking at the latest data
29164
+ obj.updateMatrixWorld(true);
29080
29165
  var e = obj.matrixWorld.elements;
29081
- // Use position (12-14) + first diagonal entries (0,5,10) — covers
29082
- // translation AND rotation/scale changes with minimal work.
29083
- return "".concat(e[0].toFixed(5), ",").concat(e[5].toFixed(5), ",").concat(e[10].toFixed(5), ",").concat(e[12].toFixed(5), ",").concat(e[13].toFixed(5), ",").concat(e[14].toFixed(5));
29166
+ // Just hash the position/rotation/scale part of the matrix (12 elements)
29167
+ return "".concat(e[0].toFixed(3), ",").concat(e[1].toFixed(3), ",").concat(e[2].toFixed(3), ",").concat(e[4].toFixed(3), ",").concat(e[5].toFixed(3), ",").concat(e[6].toFixed(3), ",").concat(e[8].toFixed(3), ",").concat(e[9].toFixed(3), ",").concat(e[10].toFixed(3), ",").concat(e[12].toFixed(3), ",").concat(e[13].toFixed(3), ",").concat(e[14].toFixed(3));
29084
29168
  }
29085
29169
 
29086
29170
  /**
@@ -29098,11 +29182,7 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
29098
29182
  * Compute a lightweight fingerprint of the pathfinding inputs so that
29099
29183
  * consecutive identical runs can be skipped entirely.
29100
29184
  *
29101
- * The fingerprint captures:
29102
- * • The connections list (from / to pairs)
29103
- * • The world transform of every top-level child in sceneData
29104
- *
29105
- * @param {Object} sceneData - scene data object with .children
29185
+ * @param {Object} sceneData - scene object or manifest
29106
29186
  * @param {Array} connections - array of {from, to}
29107
29187
  * @returns {string}
29108
29188
  * @private
@@ -29110,223 +29190,241 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
29110
29190
  }, {
29111
29191
  key: "_computePathfindingFingerprint",
29112
29192
  value: function _computePathfindingFingerprint(sceneData, connections) {
29193
+ var _this3 = this;
29113
29194
  var parts = [];
29114
29195
 
29115
29196
  // 1. Connections (sorted so order doesn't matter)
29116
- parts.push(connections.map(function (c) {
29197
+ parts.push((connections || []).map(function (c) {
29117
29198
  return "".concat(c.from, "->").concat(c.to);
29118
29199
  }).sort().join(','));
29119
29200
 
29120
- // 2. World transforms of every referenced scene object
29121
- if (sceneData.children) {
29122
- var _iterator = _createForOfIteratorHelper(sceneData.children),
29123
- _step;
29124
- try {
29125
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
29126
- var child = _step.value;
29127
- var obj = this.sceneViewer.scene.getObjectByProperty('uuid', child.uuid);
29128
- if (obj) {
29129
- parts.push("".concat(child.uuid, ":").concat(this._matrixHash(obj)));
29130
- } else {
29131
- var _p$x, _p$y, _p$z;
29132
- // Object only in data, not in scene — include its serialised position
29133
- var p = child.position || {};
29134
- parts.push("".concat(child.uuid, ":").concat(((_p$x = p.x) !== null && _p$x !== void 0 ? _p$x : 0).toFixed(5), ",").concat(((_p$y = p.y) !== null && _p$y !== void 0 ? _p$y : 0).toFixed(5), ",").concat(((_p$z = p.z) !== null && _p$z !== void 0 ? _p$z : 0).toFixed(5)));
29135
- }
29201
+ // 2. World transforms of every referenced scene object (recursive)
29202
+ var _addTransformsRecursive = function addTransformsRecursive(node) {
29203
+ if (node.uuid) {
29204
+ var obj = _this3.sceneViewer.scene.getObjectByProperty('uuid', node.uuid);
29205
+ if (obj) {
29206
+ parts.push("".concat(node.uuid, ":").concat(_this3._matrixHash(obj)));
29207
+ } else {
29208
+ var _p$x, _p$y, _p$z;
29209
+ // Object only in data, not in scene — include its serialised position
29210
+ var p = node.position || {};
29211
+ parts.push("".concat(node.uuid, ":").concat(((_p$x = p.x) !== null && _p$x !== void 0 ? _p$x : 0).toFixed(5), ",").concat(((_p$y = p.y) !== null && _p$y !== void 0 ? _p$y : 0).toFixed(5), ",").concat(((_p$z = p.z) !== null && _p$z !== void 0 ? _p$z : 0).toFixed(5)));
29136
29212
  }
29137
- } catch (err) {
29138
- _iterator.e(err);
29139
- } finally {
29140
- _iterator.f();
29141
29213
  }
29214
+
29215
+ // Handle both flattened array and nested Three.js JSON structures
29216
+ var children = node.children || node.object && node.object.children;
29217
+ if (children && Array.isArray(children)) {
29218
+ children.forEach(_addTransformsRecursive);
29219
+ }
29220
+ };
29221
+ if (sceneData) {
29222
+ _addTransformsRecursive(sceneData);
29142
29223
  }
29143
29224
  return parts.join('|');
29144
29225
  }
29145
29226
 
29146
29227
  /**
29147
- * Enrich sceneData with worldBoundingBox for segments and components
29148
- * Connectors remain with just position information.
29149
- *
29150
- * Uses _bboxCache to avoid recomputing filtered / io-device bboxes
29151
- * when the underlying object's world matrix has not changed.
29152
- *
29153
- * @param {Object} sceneData - Original scene data
29154
- * @returns {Object} Enriched scene data (shallow copy with modifications)
29228
+ * Enrich scene data with world-space bounding boxes and positions for collision avoidance
29229
+ * @param {Object|Array} sceneData - Original scene data or array of children
29230
+ * @returns {Object|Array} Enriched scene data (shallow copy with modifications)
29155
29231
  * @private
29156
29232
  */
29157
29233
  }, {
29158
29234
  key: "_enrichSceneDataWithBoundingBoxes",
29159
29235
  value: function _enrichSceneDataWithBoundingBoxes(sceneData) {
29160
- var _this3 = this;
29161
- // Create a shallow copy of sceneData structure
29162
- var enriched = _objectSpread2({}, sceneData);
29163
- if (!sceneData.children || !Array.isArray(sceneData.children)) {
29164
- console.warn('⚠️ sceneData has no children array');
29165
- return enriched;
29166
- }
29167
-
29168
- // Process children to add worldBoundingBox to segments and components
29169
- enriched.children = sceneData.children.map(function (child) {
29170
- // Enrich segments (check if objectType is 'segment' in userData)
29171
- if (child.userData && child.userData.objectType === 'segment') {
29172
- // Find the actual segment object in the scene
29173
- var segmentObject = _this3.sceneViewer.scene.getObjectByProperty('uuid', child.uuid);
29236
+ var _this4 = this;
29237
+ if (!sceneData) return sceneData;
29238
+
29239
+ // Recursive helper to enrich a node and its children
29240
+ var _enrichNode = function enrichNode(node) {
29241
+ if (!node) return node;
29242
+ var enrichedNode = _objectSpread2({}, node);
29243
+
29244
+ // Skip computed objects ( elbows etc )
29245
+ if (node.userData && (node.userData.isComputed === true || node.userData.objectType === 'elbow')) {
29246
+ return node;
29247
+ }
29248
+
29249
+ // ── Enrich Segments ──
29250
+ if (node.userData && node.userData.objectType === 'segment') {
29251
+ var segmentObject = _this4.sceneViewer.scene.getObjectByProperty('uuid', node.uuid);
29174
29252
  if (segmentObject) {
29175
- // ── Cache check ──
29176
- var hash = _this3._matrixHash(segmentObject);
29177
- var cached = _this3._bboxCache.get(child.uuid);
29253
+ var hash = _this4._matrixHash(segmentObject);
29254
+ var cached = _this4._bboxCache.get(node.uuid);
29178
29255
  if (cached && cached.matrixHash === hash && cached.segmentBBox) {
29179
- return _objectSpread2(_objectSpread2({}, child), {}, {
29180
- userData: _objectSpread2(_objectSpread2({}, child.userData), {}, {
29181
- worldBoundingBox: cached.segmentBBox
29182
- })
29256
+ enrichedNode.userData = _objectSpread2(_objectSpread2({}, node.userData), {}, {
29257
+ worldBoundingBox: cached.segmentBBox
29183
29258
  });
29184
- }
29185
-
29186
- // Compute world bounding box
29187
- var worldBBox = new THREE__namespace.Box3().setFromObject(segmentObject);
29188
- var bboxData = {
29189
- min: [worldBBox.min.x, worldBBox.min.y, worldBBox.min.z],
29190
- max: [worldBBox.max.x, worldBBox.max.y, worldBBox.max.z]
29191
- };
29192
-
29193
- // Store in cache
29194
- _this3._bboxCache.set(child.uuid, {
29195
- matrixHash: hash,
29196
- segmentBBox: bboxData
29197
- });
29198
- return _objectSpread2(_objectSpread2({}, child), {}, {
29199
- userData: _objectSpread2(_objectSpread2({}, child.userData), {}, {
29259
+ } else {
29260
+ var worldBBox = new THREE__namespace.Box3().setFromObject(segmentObject);
29261
+ var bboxData = {
29262
+ min: [worldBBox.min.x, worldBBox.min.y, worldBBox.min.z],
29263
+ max: [worldBBox.max.x, worldBBox.max.y, worldBBox.max.z]
29264
+ };
29265
+ _this4._bboxCache.set(node.uuid, {
29266
+ matrixHash: hash,
29267
+ segmentBBox: bboxData
29268
+ });
29269
+ enrichedNode.userData = _objectSpread2(_objectSpread2({}, node.userData), {}, {
29200
29270
  worldBoundingBox: bboxData
29201
- })
29202
- });
29203
- } else {
29204
- console.warn("\u26A0\uFE0F Could not find segment object in scene: ".concat(child.uuid));
29271
+ });
29272
+ }
29205
29273
  }
29206
29274
  }
29207
29275
 
29208
- // Enrich components (check if objectType is 'component' in userData)
29209
- if (child.userData && child.userData.objectType === 'component') {
29210
- // Find the actual component object in the scene
29211
- var componentObject = _this3.sceneViewer.scene.getObjectByProperty('uuid', child.uuid);
29276
+ // ── Enrich Components ──
29277
+ else if (node.userData && node.userData.objectType === 'component') {
29278
+ var componentObject = _this4.sceneViewer.scene.getObjectByProperty('uuid', node.uuid);
29212
29279
  if (componentObject) {
29213
- // ── Cache check ──
29214
- var _hash = _this3._matrixHash(componentObject);
29215
- var _cached = _this3._bboxCache.get(child.uuid);
29280
+ componentObject.updateMatrixWorld(true);
29281
+ var _hash = _this4._matrixHash(componentObject);
29282
+ var _cached = _this4._bboxCache.get(node.uuid);
29216
29283
  if (_cached && _cached.matrixHash === _hash && _cached.filteredBBox) {
29217
- // Rebuild enriched child from cached data
29218
- var _enrichedChild = _objectSpread2(_objectSpread2({}, child), {}, {
29219
- userData: _objectSpread2(_objectSpread2({}, child.userData), {}, {
29220
- worldBoundingBox: _cached.filteredBBox
29221
- })
29284
+ enrichedNode.position = {
29285
+ x: componentObject.position.x,
29286
+ y: componentObject.position.y,
29287
+ z: componentObject.position.z
29288
+ };
29289
+ enrichedNode.rotation = {
29290
+ x: THREE__namespace.MathUtils.radToDeg(componentObject.rotation.x),
29291
+ y: THREE__namespace.MathUtils.radToDeg(componentObject.rotation.y),
29292
+ z: THREE__namespace.MathUtils.radToDeg(componentObject.rotation.z)
29293
+ };
29294
+ enrichedNode.userData = _objectSpread2(_objectSpread2({}, node.userData), {}, {
29295
+ worldBoundingBox: _cached.filteredBBox,
29296
+ position: [componentObject.position.x, componentObject.position.y, componentObject.position.z]
29222
29297
  });
29223
- if (_cached.ioDeviceBBoxes && _cached.ioDeviceBBoxes.length > 0) {
29224
- if (!_enrichedChild.children) _enrichedChild.children = [];
29225
- _cached.ioDeviceBBoxes.forEach(function (deviceBBox) {
29226
- var existingIndex = _enrichedChild.children.findIndex(function (c) {
29227
- return c.uuid === deviceBBox.uuid;
29228
- });
29229
- if (existingIndex >= 0) {
29230
- _enrichedChild.children[existingIndex] = _objectSpread2(_objectSpread2({}, _enrichedChild.children[existingIndex]), {}, {
29231
- userData: _objectSpread2(_objectSpread2({}, _enrichedChild.children[existingIndex].userData), {}, {
29232
- objectType: 'io-device',
29233
- worldBoundingBox: deviceBBox.worldBoundingBox
29234
- })
29235
- });
29236
- } else {
29237
- _enrichedChild.children.push({
29238
- uuid: deviceBBox.uuid,
29239
- userData: _objectSpread2(_objectSpread2({}, deviceBBox.userData), {}, {
29240
- worldBoundingBox: deviceBBox.worldBoundingBox
29241
- }),
29242
- children: []
29243
- });
29244
- }
29298
+
29299
+ // Re-merge cached connectors and devices if they exist
29300
+ if (!enrichedNode.children) enrichedNode.children = [];
29301
+ if (_cached.connectorBBoxes) {
29302
+ _cached.connectorBBoxes.forEach(function (conn) {
29303
+ return _this4._mergeEnrichedChild(enrichedNode.children, conn);
29304
+ });
29305
+ }
29306
+ if (_cached.ioDeviceBBoxes) {
29307
+ _cached.ioDeviceBBoxes.forEach(function (dev) {
29308
+ return _this4._mergeEnrichedChild(enrichedNode.children, dev);
29245
29309
  });
29246
29310
  }
29247
- return _enrichedChild;
29311
+ } else {
29312
+ var filteredBBox = computeFilteredBoundingBox(componentObject, ['io-device', 'connector']);
29313
+ var _bboxData = {
29314
+ min: [filteredBBox.min.x, filteredBBox.min.y, filteredBBox.min.z],
29315
+ max: [filteredBBox.max.x, filteredBBox.max.y, filteredBBox.max.z]
29316
+ };
29317
+ enrichedNode.position = {
29318
+ x: componentObject.position.x,
29319
+ y: componentObject.position.y,
29320
+ z: componentObject.position.z
29321
+ };
29322
+ enrichedNode.rotation = {
29323
+ x: THREE__namespace.MathUtils.radToDeg(componentObject.rotation.x),
29324
+ y: THREE__namespace.MathUtils.radToDeg(componentObject.rotation.y),
29325
+ z: THREE__namespace.MathUtils.radToDeg(componentObject.rotation.z)
29326
+ };
29327
+ enrichedNode.userData = _objectSpread2(_objectSpread2({}, node.userData), {}, {
29328
+ worldBoundingBox: _bboxData,
29329
+ position: [componentObject.position.x, componentObject.position.y, componentObject.position.z]
29330
+ });
29331
+ var connectorBBoxes = computeConnectorBoundingBoxes(componentObject);
29332
+ var ioDeviceBBoxes = computeIODeviceBoundingBoxes(componentObject);
29333
+ if (!enrichedNode.children) enrichedNode.children = [];
29334
+ connectorBBoxes.forEach(function (conn) {
29335
+ return _this4._mergeEnrichedChild(enrichedNode.children, conn);
29336
+ });
29337
+ ioDeviceBBoxes.forEach(function (dev) {
29338
+ return _this4._mergeEnrichedChild(enrichedNode.children, dev);
29339
+ });
29340
+ _this4._bboxCache.set(node.uuid, {
29341
+ matrixHash: _hash,
29342
+ filteredBBox: _bboxData,
29343
+ ioDeviceBBoxes: ioDeviceBBoxes,
29344
+ connectorBBoxes: connectorBBoxes
29345
+ });
29248
29346
  }
29347
+ }
29348
+ }
29249
29349
 
29250
- // Compute FILTERED bounding box excludes io-device and connector subtrees
29251
- // so the component body bbox is tight-fitting and doesn't envelop attached devices
29252
- var filteredBBox = computeFilteredBoundingBox(componentObject, ['io-device', 'connector']);
29253
- 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), "]"));
29254
- var _bboxData = {
29255
- min: [filteredBBox.min.x, filteredBBox.min.y, filteredBBox.min.z],
29256
- max: [filteredBBox.max.x, filteredBBox.max.y, filteredBBox.max.z]
29350
+ // ── Enrich Standalone Objects (Gateways/Connectors) ──
29351
+ else if (node.userData && (node.userData.objectType === 'gateway' || node.userData.objectType === 'connector')) {
29352
+ var _node$userData;
29353
+ 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);
29354
+ if (object) {
29355
+ var worldPos = new THREE__namespace.Vector3();
29356
+ object.getWorldPosition(worldPos);
29357
+ enrichedNode.position = {
29358
+ x: worldPos.x,
29359
+ y: worldPos.y,
29360
+ z: worldPos.z
29257
29361
  };
29258
-
29259
- // Build the enriched component entry
29260
- var enrichedChild = _objectSpread2(_objectSpread2({}, child), {}, {
29261
- userData: _objectSpread2(_objectSpread2({}, child.userData), {}, {
29262
- worldBoundingBox: _bboxData
29263
- })
29362
+ enrichedNode.userData = _objectSpread2(_objectSpread2({}, node.userData), {}, {
29363
+ position: [worldPos.x, worldPos.y, worldPos.z]
29264
29364
  });
29265
29365
 
29266
- // Compute separate bounding boxes for each attached io-device
29267
- var ioDeviceBBoxes = computeIODeviceBoundingBoxes(componentObject);
29268
- if (ioDeviceBBoxes.length > 0) {
29269
- // Ensure children array exists (may already contain connectors)
29270
- if (!enrichedChild.children) {
29271
- enrichedChild.children = [];
29272
- }
29273
-
29274
- // Inject io-device entries with their own worldBoundingBox
29275
- ioDeviceBBoxes.forEach(function (deviceBBox) {
29276
- var existingIndex = enrichedChild.children.findIndex(function (c) {
29277
- return c.uuid === deviceBBox.uuid;
29278
- });
29279
- if (existingIndex >= 0) {
29280
- enrichedChild.children[existingIndex] = _objectSpread2(_objectSpread2({}, enrichedChild.children[existingIndex]), {}, {
29281
- userData: _objectSpread2(_objectSpread2({}, enrichedChild.children[existingIndex].userData), {}, {
29282
- objectType: 'io-device',
29283
- worldBoundingBox: deviceBBox.worldBoundingBox
29284
- })
29285
- });
29286
- } else {
29287
- enrichedChild.children.push({
29288
- uuid: deviceBBox.uuid,
29289
- userData: _objectSpread2(_objectSpread2({}, deviceBBox.userData), {}, {
29290
- worldBoundingBox: deviceBBox.worldBoundingBox
29291
- }),
29292
- children: []
29293
- });
29294
- }
29295
- console.log("\uD83D\uDCE6 Injected io-device bbox for ".concat(deviceBBox.uuid, ": min=[").concat(deviceBBox.worldBoundingBox.min.map(function (v) {
29296
- return v.toFixed(2);
29297
- }).join(', '), "], max=[").concat(deviceBBox.worldBoundingBox.max.map(function (v) {
29298
- return v.toFixed(2);
29299
- }).join(', '), "]"));
29300
- });
29301
- console.log("\uD83D\uDCE6 Injected ".concat(ioDeviceBBoxes.length, " io-device bounding box(es) for component ").concat(child.uuid));
29366
+ // If it's a connector, also sync direction
29367
+ if (node.userData.objectType === 'connector') {
29368
+ var worldDir = new THREE__namespace.Vector3(0, 0, 1);
29369
+ if (node.userData.direction) worldDir.set(node.userData.direction[0], node.userData.direction[1], node.userData.direction[2]);
29370
+ worldDir.applyQuaternion(object.getWorldQuaternion(new THREE__namespace.Quaternion())).normalize();
29371
+ enrichedNode.userData.direction = [worldDir.x, worldDir.y, worldDir.z];
29302
29372
  }
29303
-
29304
- // Store in cache
29305
- _this3._bboxCache.set(child.uuid, {
29306
- matrixHash: _hash,
29307
- filteredBBox: _bboxData,
29308
- ioDeviceBBoxes: ioDeviceBBoxes
29309
- });
29310
- return enrichedChild;
29311
- } else {
29312
- console.warn("\u26A0\uFE0F Could not find component object in scene: ".concat(child.uuid));
29313
29373
  }
29314
29374
  }
29315
29375
 
29316
- // For non-segments and non-components (including connectors), return as-is
29317
- return child;
29318
- });
29319
- return enriched;
29376
+ // Recurse into children
29377
+ if (node.children && Array.isArray(node.children)) {
29378
+ enrichedNode.children = node.children.map(_enrichNode);
29379
+ }
29380
+ return enrichedNode;
29381
+ };
29382
+
29383
+ // Handle root being an array or object
29384
+ if (Array.isArray(sceneData)) {
29385
+ return sceneData.map(_enrichNode);
29386
+ } else if (sceneData.children && Array.isArray(sceneData.children)) {
29387
+ var enrichedRoot = _objectSpread2({}, sceneData);
29388
+ enrichedRoot.children = sceneData.children.map(_enrichNode);
29389
+ return enrichedRoot;
29390
+ } else {
29391
+ return _enrichNode(sceneData);
29392
+ }
29320
29393
  }
29321
29394
 
29322
29395
  /**
29323
- * Core pathfinding logic shared across initialization and updates
29396
+ * Helper to merge an enriched child (connector/device) into a children array
29397
+ * @private
29324
29398
  */
29399
+ }, {
29400
+ key: "_mergeEnrichedChild",
29401
+ value: function _mergeEnrichedChild(childrenArray, enrichedData) {
29402
+ var existingIndex = childrenArray.findIndex(function (c) {
29403
+ return c.uuid === enrichedData.uuid;
29404
+ });
29405
+ var nodeToMerge = {
29406
+ uuid: enrichedData.uuid,
29407
+ position: {
29408
+ x: enrichedData.userData.position[0],
29409
+ y: enrichedData.userData.position[1],
29410
+ z: enrichedData.userData.position[2]
29411
+ },
29412
+ userData: _objectSpread2(_objectSpread2({}, enrichedData.userData), {}, {
29413
+ worldBoundingBox: enrichedData.worldBoundingBox
29414
+ }),
29415
+ children: []
29416
+ };
29417
+ if (existingIndex >= 0) {
29418
+ childrenArray[existingIndex] = _objectSpread2(_objectSpread2({}, childrenArray[existingIndex]), nodeToMerge);
29419
+ } else {
29420
+ childrenArray.push(nodeToMerge);
29421
+ }
29422
+ }
29325
29423
  }, {
29326
29424
  key: "_executePathfinding",
29327
- value: (function () {
29425
+ value: function () {
29328
29426
  var _executePathfinding2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(sceneData, connections) {
29329
- var _this4 = this,
29427
+ var _this5 = this,
29330
29428
  _sceneDataCopy$childr,
29331
29429
  _sceneDataCopy$childr2,
29332
29430
  _pathfindingResult$pa,
@@ -29338,6 +29436,12 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
29338
29436
  totalStart,
29339
29437
  inputGenStart,
29340
29438
  enrichedSceneData,
29439
+ _from$userData,
29440
+ _to$userData,
29441
+ firstConn,
29442
+ findEndpoint,
29443
+ from,
29444
+ to,
29341
29445
  connectionsCopy,
29342
29446
  sceneDataCopy,
29343
29447
  algoStart,
@@ -29354,6 +29458,10 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
29354
29458
  while (1) switch (_context.n) {
29355
29459
  case 0:
29356
29460
  options = _args.length > 2 && _args[2] !== undefined ? _args[2] : {};
29461
+ // Fallback to sceneData connections if not explicitly provided (e.g. during transform updates)
29462
+ if (!connections && sceneData && sceneData.connections) {
29463
+ connections = sceneData.connections;
29464
+ }
29357
29465
  options.createGateways, _options$context = options.context, context = _options$context === void 0 ? 'Pathfinding' : _options$context;
29358
29466
  timers = {};
29359
29467
  totalStart = performance.now(); // Create pathfinder instance with configuration only
@@ -29382,7 +29490,50 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
29382
29490
  console.log("\uD83D\uDD04 Updated matrix world for ".concat(context, " pathfinding execution"));
29383
29491
 
29384
29492
  // Enrich sceneData with worldBoundingBox for segments before deep copy
29385
- enrichedSceneData = this._enrichSceneDataWithBoundingBoxes(sceneData); // Deep copy connections and sceneData to prevent pathfinder from mutating the original
29493
+ enrichedSceneData = this._enrichSceneDataWithBoundingBoxes(sceneData); // DEBUG: Log first connection endpoints in enriched data
29494
+ if (connections && connections.length > 0 && enrichedSceneData.children) {
29495
+ firstConn = connections[0];
29496
+ findEndpoint = function findEndpoint(uuid) {
29497
+ var _iterator = _createForOfIteratorHelper(enrichedSceneData.children),
29498
+ _step;
29499
+ try {
29500
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
29501
+ var child = _step.value;
29502
+ if (child.uuid === uuid) return child;
29503
+ if (child.children) {
29504
+ var _iterator2 = _createForOfIteratorHelper(child.children),
29505
+ _step2;
29506
+ try {
29507
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
29508
+ var nested = _step2.value;
29509
+ if (nested.uuid === uuid) return nested;
29510
+ }
29511
+ } catch (err) {
29512
+ _iterator2.e(err);
29513
+ } finally {
29514
+ _iterator2.f();
29515
+ }
29516
+ }
29517
+ }
29518
+ } catch (err) {
29519
+ _iterator.e(err);
29520
+ } finally {
29521
+ _iterator.f();
29522
+ }
29523
+ return null;
29524
+ };
29525
+ from = findEndpoint(firstConn.from);
29526
+ to = findEndpoint(firstConn.to);
29527
+ console.log("\uD83D\uDCE1 [DEBUG] Pathfinding from ".concat(firstConn.from, " to ").concat(firstConn.to));
29528
+ if (from) console.log(" From BBox: min=[".concat((_from$userData = from.userData) === null || _from$userData === void 0 || (_from$userData = _from$userData.worldBoundingBox) === null || _from$userData === void 0 || (_from$userData = _from$userData.min) === null || _from$userData === void 0 ? void 0 : _from$userData.map(function (v) {
29529
+ return v.toFixed(2);
29530
+ }), "]"));
29531
+ if (to) console.log(" To BBox: min=[".concat((_to$userData = to.userData) === null || _to$userData === void 0 || (_to$userData = _to$userData.worldBoundingBox) === null || _to$userData === void 0 || (_to$userData = _to$userData.min) === null || _to$userData === void 0 ? void 0 : _to$userData.map(function (v) {
29532
+ return v.toFixed(2);
29533
+ }), "]"));
29534
+ }
29535
+
29536
+ // Deep copy connections and sceneData to prevent pathfinder from mutating the original
29386
29537
  connectionsCopy = structuredClone(connections);
29387
29538
  sceneDataCopy = structuredClone(enrichedSceneData);
29388
29539
  timers.inputGeneration = performance.now() - inputGenStart;
@@ -29413,7 +29564,7 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
29413
29564
  if (pathfindingResult.paths) {
29414
29565
  pathfindingResult.paths.forEach(function (path) {
29415
29566
  var pathId = "".concat(path.from, "-->").concat(path.to);
29416
- _this4._getOrCreatePathData(pathId, path.from, path.to);
29567
+ _this5._getOrCreatePathData(pathId, path.from, path.to);
29417
29568
  });
29418
29569
  }
29419
29570
  timers.pathRendering = performance.now() - renderStart;
@@ -29446,7 +29597,7 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
29446
29597
  return _executePathfinding2.apply(this, arguments);
29447
29598
  }
29448
29599
  return _executePathfinding;
29449
- }())
29600
+ }()
29450
29601
  }, {
29451
29602
  key: "getSimplifiedSceneData",
29452
29603
  value: function getSimplifiedSceneData() {
@@ -29485,7 +29636,7 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
29485
29636
  }, {
29486
29637
  key: "_propagateFlowAttributesAndApplyVisualizations",
29487
29638
  value: function _propagateFlowAttributesAndApplyVisualizations(connections, pathfindingResult) {
29488
- var _this5 = this,
29639
+ var _this6 = this,
29489
29640
  _this$sceneViewer;
29490
29641
  if (!Array.isArray(connections) || !pathfindingResult) return;
29491
29642
 
@@ -29513,7 +29664,7 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
29513
29664
  var subPathIds = new Set(originalToSubPaths.get(origId) || []);
29514
29665
  subPathIds.add(origId);
29515
29666
  subPathIds.forEach(function (subPathId) {
29516
- var pd = _this5.pathDataStore.get(subPathId);
29667
+ var pd = _this6.pathDataStore.get(subPathId);
29517
29668
  if (pd && Object.keys(pd.flowAttributes).length === 0) {
29518
29669
  Object.entries(conn.flowAttributes).forEach(function (_ref2) {
29519
29670
  var _ref3 = _slicedToArray(_ref2, 2),
@@ -29577,7 +29728,7 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
29577
29728
  key: "initializePathfinder",
29578
29729
  value: (function () {
29579
29730
  var _initializePathfinder = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2(data, crosscubeTextureSet) {
29580
- var _this6 = this;
29731
+ var _this7 = this;
29581
29732
  var pathfindingResult;
29582
29733
  return _regenerator().w(function (_context2) {
29583
29734
  while (1) switch (_context2.n) {
@@ -29591,7 +29742,7 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
29591
29742
  data.connections.forEach(function (conn) {
29592
29743
  if (conn.flowAttributes && _typeof(conn.flowAttributes) === 'object') {
29593
29744
  var pathId = "".concat(conn.from, "-->").concat(conn.to);
29594
- var pd = _this6._getOrCreatePathData(pathId, conn.from, conn.to);
29745
+ var pd = _this7._getOrCreatePathData(pathId, conn.from, conn.to);
29595
29746
  Object.entries(conn.flowAttributes).forEach(function (_ref6) {
29596
29747
  var _ref7 = _slicedToArray(_ref6, 2),
29597
29748
  key = _ref7[0],
@@ -29709,33 +29860,47 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
29709
29860
  key: "updatePathfindingAfterTransform",
29710
29861
  value: (function () {
29711
29862
  var _updatePathfindingAfterTransform = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee3(currentSceneData) {
29712
- var fingerprint, result;
29863
+ var _currentSceneData$sce;
29864
+ var fingerprint, dataRoot, connections, result;
29713
29865
  return _regenerator().w(function (_context3) {
29714
29866
  while (1) switch (_context3.n) {
29715
29867
  case 0:
29868
+ if (!(!currentSceneData || !currentSceneData.scene)) {
29869
+ _context3.n = 1;
29870
+ break;
29871
+ }
29872
+ console.warn('⚠️ updatePathfindingAfterTransform: Invalid scene data');
29873
+ return _context3.a(2, null);
29874
+ case 1:
29716
29875
  // ── Skip-unchanged-paths check ──────────────────────────────────
29717
29876
  // Ensure matrices are fresh so the fingerprint is accurate.
29718
29877
  this.sceneViewer.scene.updateMatrixWorld(true);
29719
29878
  fingerprint = this._computePathfindingFingerprint(currentSceneData.scene, currentSceneData.connections);
29879
+ console.log("\uD83D\uDD0D Pathfinding update check. Fingerprint: ".concat(fingerprint.substring(0, 50), "..."));
29720
29880
  if (!(fingerprint === this._lastPathfindingFingerprint && this._lastPathfindingResult)) {
29721
- _context3.n = 1;
29881
+ _context3.n = 2;
29722
29882
  break;
29723
29883
  }
29724
29884
  console.log('⏭️ Pathfinding skipped — inputs unchanged since last run');
29725
29885
  return _context3.a(2, this._lastPathfindingResult);
29726
- case 1:
29886
+ case 2:
29887
+ // Clear the cache for the specific fingerprint mismatch to ensure fresh enrichment
29888
+ this.invalidateBBoxCache();
29889
+
29727
29890
  // Remove all existing paths from the scene
29728
29891
  this.removeComputedObjects();
29729
29892
  console.log('🔄 Starting pathfinding with updated scene data...');
29730
29893
 
29731
- // Note: Matrix updates and bounding box recomputation now handled in _executePathfinding
29894
+ // Robust root discovery for different manifest structures
29895
+ dataRoot = ((_currentSceneData$sce = currentSceneData.scene) === null || _currentSceneData$sce === void 0 ? void 0 : _currentSceneData$sce.object) || currentSceneData.scene || currentSceneData;
29896
+ connections = currentSceneData.connections || []; // Note: Matrix updates and bounding box recomputation now handled in _executePathfinding
29732
29897
  // Use shared pathfinding logic (no gateways needed for transform updates)
29733
- _context3.n = 2;
29734
- return this._executePathfinding(currentSceneData.scene, currentSceneData.connections, {
29898
+ _context3.n = 3;
29899
+ return this._executePathfinding(dataRoot, connections, {
29735
29900
  createGateways: true,
29736
29901
  context: 'Transform Update'
29737
29902
  });
29738
- case 2:
29903
+ case 3:
29739
29904
  result = _context3.v;
29740
29905
  // Re-apply flow attribute colors (new materials are created on each execution)
29741
29906
  this._propagateFlowAttributesAndApplyVisualizations(currentSceneData.connections, result);
@@ -30074,7 +30239,7 @@ function attachIODevicesToComponent(_x, _x2, _x3, _x4) {
30074
30239
  }
30075
30240
  function _attachIODevicesToComponent() {
30076
30241
  _attachIODevicesToComponent = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(componentModel, componentData, modelPreloader, parentComponentId) {
30077
- var attachedDevices, _i, _Object$entries, _Object$entries$_i, attachmentId, attachment, _modelPreloader$compo, _deviceData$ioConfig, _deviceData$ioConfig2, _deviceData$ioConfig3, _attachment$attachmen, _attachment$attachmen2, deviceData, cachedDevice, _modelPreloader$loadi, deviceModel, pos, rot, deg2rad, _t, _t2;
30242
+ var attachedDevices, _i, _Object$entries, _Object$entries$_i, attachmentId, attachment, _modelPreloader$compo, _attachment$attachmen, _attachment$attachmen2, deviceData, cachedDevice, _modelPreloader$loadi, deviceModel, pos, rot, deg2rad, _t, _t2;
30078
30243
  return _regenerator().w(function (_context) {
30079
30244
  while (1) switch (_context.n) {
30080
30245
  case 0:
@@ -30152,20 +30317,14 @@ function _attachIODevicesToComponent() {
30152
30317
  // Name the device model
30153
30318
  deviceModel.name = "".concat(attachment.attachmentLabel || 'IO Device', " (").concat(attachmentId, ")");
30154
30319
 
30155
- // Set user data for identification — include ioConfig data points so the
30156
- // component tooltip can render state displays without an extra lookup.
30320
+ // Set user data for identification
30157
30321
  deviceModel.userData = {
30158
30322
  objectType: 'io-device',
30159
30323
  deviceId: attachment.deviceId,
30160
30324
  attachmentId: attachmentId,
30161
30325
  attachmentLabel: attachment.attachmentLabel,
30162
30326
  parentComponentId: parentComponentId,
30163
- deviceName: deviceData.name || '',
30164
- // Snapshot of the device's data point definitions (stateType, stateConfig, direction, etc.)
30165
- // ioConfig can use either 'states' (preferred) or legacy 'dataPoints' as the array key
30166
- dataPoints: ((_deviceData$ioConfig = deviceData.ioConfig) === null || _deviceData$ioConfig === void 0 ? void 0 : _deviceData$ioConfig.states) || ((_deviceData$ioConfig2 = deviceData.ioConfig) === null || _deviceData$ioConfig2 === void 0 ? void 0 : _deviceData$ioConfig2.dataPoints) || [],
30167
- // Device-level I/O direction: 'input' means the user can write state via the tooltip
30168
- ioDirection: ((_deviceData$ioConfig3 = deviceData.ioConfig) === null || _deviceData$ioConfig3 === void 0 ? void 0 : _deviceData$ioConfig3.direction) || 'output'
30327
+ deviceName: deviceData.name || ''
30169
30328
  };
30170
30329
 
30171
30330
  // Position at the attachment point
@@ -31647,36 +31806,58 @@ var ModelManager = /*#__PURE__*/function () {
31647
31806
  * @param {THREE.Object3D} targetMesh - The mesh whose connectors to preserve
31648
31807
  * @param {string} parentUuid - The UUID of the parent component
31649
31808
  */
31809
+ /**
31810
+ * Identifies all connector objects within a model hierarchy and prepares them
31811
+ * to be preserved when the model is replaced or modified.
31812
+ * Uses traverse() to find connectors at any depth in the hierarchy.
31813
+ * @param {THREE.Object3D} targetMesh - The root of the model to search
31814
+ * @param {string} parentUuid - The UUID of the parent component
31815
+ * @returns {Array<THREE.Object3D>} Array of cloned and prepared connector objects
31816
+ */
31650
31817
  }, {
31651
31818
  key: "_preserveConnectorChildren",
31652
31819
  value: function _preserveConnectorChildren(targetMesh, parentUuid) {
31653
31820
  var _this = this;
31654
31821
  var connectorChildren = [];
31655
- targetMesh.children.forEach(function (child, index) {
31822
+ var connectorIndex = 0;
31823
+
31824
+ // Use traverse to find connectors at ANY depth in the hierarchy
31825
+ targetMesh.traverse(function (child) {
31656
31826
  var _child$userData;
31657
31827
  var isConnectorGeometry = child.geometry && (child.geometry.uuid === 'CONNECTOR-GEO' || child.geometry.type === 'SphereGeometry' && child.geometry.parameters);
31658
31828
  var isConnectorByName = child.name && child.name.toLowerCase().includes('connector');
31659
- // Also recognise connectors declared via userData (e.g. inline connectors from SAMPLE_1.json
31660
- // whose geometry may be a fallback BufferGeometry rather than a SphereGeometry).
31661
31829
  var isConnectorByUserData = ((_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.objectType) === 'connector';
31662
31830
  if (isConnectorGeometry && isConnectorByName || isConnectorByUserData) {
31831
+ connectorIndex++;
31832
+
31663
31833
  // Ensure userData exists and has worldBoundingBox
31664
31834
  if (!child.userData) child.userData = {};
31665
31835
  if (!child.userData.worldBoundingBox) {
31666
31836
  child.userData.worldBoundingBox = _this._calculateWorldBoundingBox(child);
31667
31837
  }
31668
31838
 
31669
- // Clone and preserve critical userData
31839
+ // Clone the connector
31670
31840
  var clonedConnector = child.clone();
31671
31841
 
31672
- // Generate proper connector UUID based on parent component UUID
31673
- // Format: COMPONENT-UUID-CONNECTOR-INDEX (matching JSON import pattern)
31674
- var connectorUuid = "".concat(parentUuid, "-CONNECTOR-").concat(index + 1);
31842
+ // CRITICAL: Convert nested world position to root-relative position
31843
+ // This ensures the connector stays in the same place when re-added as a direct child of targetMesh
31844
+ var targetInverse = targetMesh.matrixWorld.clone().invert();
31845
+ var worldPos = new THREE__namespace.Vector3();
31846
+ child.getWorldPosition(worldPos);
31847
+ clonedConnector.position.copy(worldPos.applyMatrix4(targetInverse));
31848
+
31849
+ // Get hardcoded UUID if it exists (e.g. from JSON mesh placeholder)
31850
+ // This ensures compatibility with existing scene JSON naming conventions
31851
+ var existingUuid = getHardcodedUuid(child);
31852
+
31853
+ // Generate fallback UUID only if existing one isn't already parent-prefixed
31854
+ // Format: COMPONENT-UUID-CONNECTOR-INDEX (stable fallback)
31855
+ var connectorUuid = existingUuid && existingUuid.includes(parentUuid) ? existingUuid : "".concat(parentUuid, "-CONNECTOR-").concat(connectorIndex);
31675
31856
  clonedConnector.uuid = connectorUuid;
31676
31857
  if (child.userData) {
31677
31858
  clonedConnector.userData = _objectSpread2({}, child.userData);
31678
31859
 
31679
- // Deep copy critical data - handle both array [x,y,z] and object {x,y,z} formats
31860
+ // Deep copy critical data
31680
31861
  if (child.userData.worldBoundingBox) {
31681
31862
  var wbb = child.userData.worldBoundingBox;
31682
31863
  clonedConnector.userData.worldBoundingBox = {
@@ -31685,7 +31866,6 @@ var ModelManager = /*#__PURE__*/function () {
31685
31866
  };
31686
31867
  }
31687
31868
  if (child.userData.direction) {
31688
- // Handle both array [x,y,z] and object {x,y,z} formats
31689
31869
  if (Array.isArray(child.userData.direction)) {
31690
31870
  clonedConnector.userData.direction = _toConsumableArray(child.userData.direction);
31691
31871
  } else if (_typeof(child.userData.direction) === 'object') {
@@ -31697,11 +31877,12 @@ var ModelManager = /*#__PURE__*/function () {
31697
31877
 
31698
31878
  // Set originalUuid to match the actual uuid (maintains consistency)
31699
31879
  clonedConnector.userData.originalUuid = connectorUuid;
31700
- clonedConnector.userData.objectType = child.userData.objectType || (child.name.toLowerCase().includes('connector') ? 'connector' : 'gateway');
31880
+ clonedConnector.userData.objectType = child.userData.objectType || 'connector';
31701
31881
  }
31702
31882
  connectorChildren.push(clonedConnector);
31703
31883
  }
31704
31884
  });
31885
+ console.log("\uD83D\uDEE1\uFE0F Preserved ".concat(connectorChildren.length, " connector(s) for component ").concat(parentUuid));
31705
31886
  return connectorChildren;
31706
31887
  }
31707
31888
 
@@ -34128,9 +34309,34 @@ var SceneOperationsManager = /*#__PURE__*/function () {
34128
34309
  min: boundingBox.min.toArray(),
34129
34310
  max: boundingBox.max.toArray()
34130
34311
  }
34131
- })
34312
+ }),
34313
+ children: [] // Initialize children array
34132
34314
  };
34133
34315
 
34316
+ // Collect children that are connectors (using traverse to find deep children in models)
34317
+ componentModel.traverse(function (child) {
34318
+ if (child.userData && child.userData.objectType === 'connector') {
34319
+ // Calculate position relative to component root
34320
+ // This ensures that when we re-add it as a direct child of the component,
34321
+ // it maintains the same world position relative to the component.
34322
+ var componentMatrixWorldInverse = componentModel.matrixWorld.clone().invert();
34323
+ var childWorldPos = new THREE__namespace.Vector3();
34324
+ child.getWorldPosition(childWorldPos);
34325
+ var relativePos = childWorldPos.applyMatrix4(componentMatrixWorldInverse);
34326
+ componentSceneData.children.push({
34327
+ uuid: child.uuid,
34328
+ name: child.name,
34329
+ type: 'Mesh',
34330
+ position: {
34331
+ x: relativePos.x,
34332
+ y: relativePos.y,
34333
+ z: relativePos.z
34334
+ },
34335
+ userData: _objectSpread2({}, child.userData)
34336
+ });
34337
+ }
34338
+ });
34339
+
34134
34340
  // Add the component to the scene data
34135
34341
  if (!currentSceneData.scene.children) {
34136
34342
  currentSceneData.scene.children = [];
@@ -34539,8 +34745,7 @@ var CollisionManager = /*#__PURE__*/function () {
34539
34745
  if (child === object || _this._isDescendantOf(object, child)) return;
34540
34746
 
34541
34747
  // Filter by CP object types at the root level (skip internal meshes during traverse)
34542
- var isCPRootObject = ((_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.objectType) === 'component' || ((_child$userData2 = child.userData) === null || _child$userData2 === void 0 ? void 0 : _child$userData2.objectType) === 'segment' || ((_child$userData3 = child.userData) === null || _child$userData3 === void 0 ? void 0 : _child$userData3.objectType) === 'gateway' || ((_child$userData4 = child.userData) === null || _child$userData4 === void 0 ? void 0 : _child$userData4.objectType) === 'connector' || ((_child$userData5 = child.userData) === null || _child$userData5 === void 0 ? void 0 : _child$userData5.ioConfig); // IO Device
34543
-
34748
+ var isCPRootObject = ((_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.objectType) === 'component' || ((_child$userData2 = child.userData) === null || _child$userData2 === void 0 ? void 0 : _child$userData2.objectType) === 'segment' || ((_child$userData3 = child.userData) === null || _child$userData3 === void 0 ? void 0 : _child$userData3.objectType) === 'gateway' || ((_child$userData4 = child.userData) === null || _child$userData4 === void 0 ? void 0 : _child$userData4.objectType) === 'connector' || ((_child$userData5 = child.userData) === null || _child$userData5 === void 0 ? void 0 : _child$userData5.objectType) === 'io-device';
34544
34749
  if (isCPRootObject && !_this._shouldExclude(child, excludeTypes)) {
34545
34750
  // Use cached worldBoundingBox if available, otherwise compute it (and cache it)
34546
34751
  var childBBox;
@@ -35168,7 +35373,7 @@ var ComponentDragManager = /*#__PURE__*/function (_BaseDisposable) {
35168
35373
  key: "_attachIODeviceModelsToPreview",
35169
35374
  value: (function () {
35170
35375
  var _attachIODeviceModelsToPreview2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee3(parentObject, componentData, modelPreloader) {
35171
- var _i, _Object$entries, _Object$entries$_i, attachmentId, attachment, _modelPreloader$compo, _deviceData$ioConfig, _deviceData$ioConfig2, _deviceData$ioConfig3, _attachment$attachmen, _attachment$attachmen2, deviceData, cachedDevice, _modelPreloader$loadi, deviceModel, pos, rot, deg2rad, _t3;
35376
+ var _i, _Object$entries, _Object$entries$_i, attachmentId, attachment, _modelPreloader$compo, _attachment$attachmen, _attachment$attachmen2, deviceData, cachedDevice, _modelPreloader$loadi, deviceModel, pos, rot, deg2rad, _t3;
35172
35377
  return _regenerator().w(function (_context3) {
35173
35378
  while (1) switch (_context3.n) {
35174
35379
  case 0:
@@ -35231,12 +35436,7 @@ var ComponentDragManager = /*#__PURE__*/function (_BaseDisposable) {
35231
35436
  deviceId: attachment.deviceId,
35232
35437
  attachmentId: attachmentId,
35233
35438
  attachmentLabel: attachment.attachmentLabel,
35234
- deviceName: deviceData.name || '',
35235
- // Snapshot of data point definitions so the tooltip can render state
35236
- // ioConfig can use either 'states' (preferred) or legacy 'dataPoints' as the array key
35237
- dataPoints: ((_deviceData$ioConfig = deviceData.ioConfig) === null || _deviceData$ioConfig === void 0 ? void 0 : _deviceData$ioConfig.states) || ((_deviceData$ioConfig2 = deviceData.ioConfig) === null || _deviceData$ioConfig2 === void 0 ? void 0 : _deviceData$ioConfig2.dataPoints) || [],
35238
- // Device-level I/O direction: 'input' means the user can write state via the tooltip
35239
- ioDirection: ((_deviceData$ioConfig3 = deviceData.ioConfig) === null || _deviceData$ioConfig3 === void 0 ? void 0 : _deviceData$ioConfig3.direction) || 'output'
35439
+ deviceName: deviceData.name || ''
35240
35440
  };
35241
35441
  if ((_attachment$attachmen = attachment.attachmentPoint) !== null && _attachment$attachmen !== void 0 && _attachment$attachmen.position) {
35242
35442
  pos = attachment.attachmentPoint.position;
@@ -37272,8 +37472,7 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
37272
37472
  if (((_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.objectType) === 'io-device') {
37273
37473
  var attachmentId = child.userData.attachmentId || '';
37274
37474
 
37275
- // Use only data points from the animate window (behaviorConfig).
37276
- // The static ioConfig.states[] snapshot on userData is intentionally ignored.
37475
+ // Use data points from the animate window (behaviorConfig).
37277
37476
  var dataPoints = resolveDataPoints(parentUuid, attachmentId, child.userData, getIoBehaviorManager(_this3.sceneViewer));
37278
37477
 
37279
37478
  // When data points come from behaviorConfig they already carry direction:'input'.
@@ -37486,7 +37685,7 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
37486
37685
  * Input / bidirectional → shows an interactive control.
37487
37686
  *
37488
37687
  * @param {string} scopedAttachmentId - Scoped attachment ID (parentUuid::attachmentId) for state isolation
37489
- * @param {Object} dp - data point definition from ioConfig.dataPoints
37688
+ * @param {Object} dp - data point definition
37490
37689
  * @param {string} [deviceDirection] - device-level direction ('input'|'output'), overrides dp.direction
37491
37690
  * @param {string} [originalAttachmentId] - Original attachment ID for behavior triggering
37492
37691
  * @returns {HTMLElement}
@@ -38502,8 +38701,7 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
38502
38701
  /**
38503
38702
  * Return tooltip-compatible data point definitions derived from the loaded
38504
38703
  * animation entries for a given attachment. Used by componentTooltipManager
38505
- * to replace the static ioConfig.states[] snapshot with the richer animation
38506
- * state definitions created in the Animate window.
38704
+ * to provide the richer animation state definitions created in the Animate window.
38507
38705
  *
38508
38706
  * One dp object is emitted per unique stateVariable; multiple mesh entries
38509
38707
  * that share the same stateVariable are collapsed into one.
@@ -39784,17 +39982,19 @@ var CentralPlantInternals = /*#__PURE__*/function () {
39784
39982
  * @param {string} componentId - The UUID of the component to translate
39785
39983
  * @param {string} axis - The axis to translate on ('x', 'y', or 'z')
39786
39984
  * @param {number} value - The value to translate by
39985
+ * @param {boolean} [skipPathUpdate=false] - Whether to skip triggering pathfinding
39787
39986
  * @returns {boolean} True if translation was successful, false otherwise
39788
39987
  */
39789
39988
  }, {
39790
39989
  key: "translateComponent",
39791
39990
  value: function translateComponent(componentId, axis, value) {
39792
39991
  var _this$centralPlant$ma;
39992
+ var skipPathUpdate = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
39793
39993
  if (!((_this$centralPlant$ma = this.centralPlant.managers) !== null && _this$centralPlant$ma !== void 0 && _this$centralPlant$ma.transformOperationsManager)) {
39794
39994
  console.error('❌ translateComponent(): TransformOperationsManager not available');
39795
39995
  return false;
39796
39996
  }
39797
- return this.centralPlant.managers.transformOperationsManager.translateComponent(componentId, axis, value);
39997
+ return this.centralPlant.managers.transformOperationsManager.translateComponent(componentId, axis, value, skipPathUpdate);
39798
39998
  }
39799
39999
 
39800
40000
  /**
@@ -39886,7 +40086,7 @@ var CentralPlantInternals = /*#__PURE__*/function () {
39886
40086
  } else if (objectType === 'gateway') {
39887
40087
  success = this.translateGateway(objectId, axis, value);
39888
40088
  } else if (objectType === 'component') {
39889
- success = this.translateComponent(objectId, axis, value);
40089
+ success = this.translateComponent(objectId, axis, value, skipPathUpdate);
39890
40090
  } else {
39891
40091
  result.errors.push("Unknown object type: ".concat(objectType, " (").concat(objectId, ")"));
39892
40092
  console.warn("\u26A0\uFE0F Unknown object type: ".concat(objectType, " for ").concat(objectId));
@@ -40919,7 +41119,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
40919
41119
  * Initialize the CentralPlant manager
40920
41120
  *
40921
41121
  * @constructor
40922
- * @version 0.3.46
41122
+ * @version 0.3.48
40923
41123
  * @updated 2025-10-22
40924
41124
  *
40925
41125
  * @description Creates a new CentralPlant instance and initializes internal managers and utilities.
@@ -41249,7 +41449,8 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
41249
41449
  }, {
41250
41450
  key: "translate",
41251
41451
  value: function translate(componentId, axis, value) {
41252
- return this.internals.translateComponent(componentId, axis, value);
41452
+ var skipPathUpdate = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
41453
+ return this.internals.translateComponent(componentId, axis, value, skipPathUpdate);
41253
41454
  }
41254
41455
 
41255
41456
  /**
@@ -41947,18 +42148,19 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
41947
42148
  return [];
41948
42149
  }
41949
42150
 
41950
- // Traverse through all components in the scene data
41951
- sceneData.scene.children.forEach(function (component) {
41952
- // Each component may have connector children
41953
- if (component.children && Array.isArray(component.children)) {
41954
- component.children.forEach(function (child) {
41955
- // Check if this child is a connector
41956
- if (child.userData && child.userData.objectType === 'connector' && child.uuid) {
41957
- allConnectorIds.push(child.uuid);
41958
- }
41959
- });
41960
- }
41961
- });
42151
+ // Recursive search for connector IDs
42152
+ var _findConnectorIds = function findConnectorIds(nodes) {
42153
+ if (!nodes || !Array.isArray(nodes)) return;
42154
+ nodes.forEach(function (node) {
42155
+ if (node.userData && node.userData.objectType === 'connector' && node.uuid) {
42156
+ allConnectorIds.push(node.uuid);
42157
+ }
42158
+ if (node.children && Array.isArray(node.children)) {
42159
+ _findConnectorIds(node.children);
42160
+ }
42161
+ });
42162
+ };
42163
+ _findConnectorIds(sceneData.scene.children);
41962
42164
 
41963
42165
  // Get existing connections
41964
42166
  var existingConnections = this.getConnections();
@@ -41988,28 +42190,28 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
41988
42190
  * const infos = centralPlant.getAvailableConnectionsInfo()
41989
42191
  * // [{ id: 'PUMP-1-CONNECTOR-1', flow: 'out' }, ...]
41990
42192
  */
42193
+ /**
42194
+ * Get all connectors in the scene that are currently available (not used in a connection).
42195
+ * Searches the active Three.js scene directly for the most accurate results.
42196
+ * @returns {Array<{id: string, flow: string}>} Array of connector info objects
42197
+ * @since 0.1.72 (Updated to search Three.js scene directly)
42198
+ */
41991
42199
  }, {
41992
42200
  key: "getAvailableConnectionsInfo",
41993
42201
  value: function getAvailableConnectionsInfo() {
41994
- if (!this.sceneViewer || !this.sceneViewer.currentSceneData) {
41995
- console.warn('⚠️ getAvailableConnectionsInfo(): Scene viewer or current scene data not available');
41996
- return [];
41997
- }
41998
- var sceneData = this.sceneViewer.currentSceneData;
41999
- if (!sceneData.scene || !sceneData.scene.children) {
42000
- console.warn('⚠️ getAvailableConnectionsInfo(): Invalid scene data structure');
42202
+ if (!this.sceneViewer || !this.sceneViewer.scene) {
42203
+ console.warn('⚠️ getAvailableConnectionsInfo(): Scene viewer or scene not available');
42001
42204
  return [];
42002
42205
  }
42003
42206
  var allConnectorInfos = [];
42004
- sceneData.scene.children.forEach(function (component) {
42005
- if (component.children && Array.isArray(component.children)) {
42006
- component.children.forEach(function (child) {
42007
- if (child.userData && child.userData.objectType === 'connector' && child.uuid) {
42008
- allConnectorInfos.push({
42009
- id: child.uuid,
42010
- flow: child.userData.flow || 'bi'
42011
- });
42012
- }
42207
+
42208
+ // Search the Three.js scene directly for connectors
42209
+ // This is more reliable than searching currentSceneData which might be stale
42210
+ this.sceneViewer.scene.traverse(function (node) {
42211
+ if (node.userData && node.userData.objectType === 'connector' && node.uuid) {
42212
+ allConnectorInfos.push({
42213
+ id: node.uuid,
42214
+ flow: node.userData.flow || 'bi'
42013
42215
  });
42014
42216
  }
42015
42217
  });
@@ -42031,30 +42233,29 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
42031
42233
  * const result = centralPlant.validateConnections()
42032
42234
  * result.invalid.forEach(({ connection, reason }) => console.warn(reason, connection))
42033
42235
  */
42236
+ /**
42237
+ * Validate all connections in the current scene for flow direction compatibility.
42238
+ * Searches the active Three.js scene for connector flow markers.
42239
+ * @returns {{ valid: Array<Object>, invalid: Array<{connection: Object, reason: string}> }}
42240
+ * @since 0.1.72 (Updated to search Three.js scene directly)
42241
+ */
42034
42242
  }, {
42035
42243
  key: "validateConnections",
42036
42244
  value: function validateConnections() {
42037
- if (!this.sceneViewer || !this.sceneViewer.currentSceneData) {
42038
- console.warn('⚠️ validateConnections(): Scene viewer or current scene data not available');
42245
+ if (!this.sceneViewer || !this.sceneViewer.scene) {
42246
+ console.warn('⚠️ validateConnections(): Scene viewer or scene not available');
42039
42247
  return {
42040
42248
  valid: [],
42041
42249
  invalid: []
42042
42250
  };
42043
42251
  }
42044
42252
  var connections = this.getConnections();
42045
- var sceneData = this.sceneViewer.currentSceneData;
42046
42253
 
42047
- // Build lookup map: connectorId → flow
42254
+ // Build lookup map: connectorId → flow from the Three.js scene
42048
42255
  var flowMap = {};
42049
- var scene = sceneData.scene || {};
42050
- var children = scene.children || [];
42051
- children.forEach(function (component) {
42052
- if (component.children && Array.isArray(component.children)) {
42053
- component.children.forEach(function (child) {
42054
- if (child.userData && child.userData.objectType === 'connector' && child.uuid) {
42055
- flowMap[child.uuid] = child.userData.flow || 'bi';
42056
- }
42057
- });
42256
+ this.sceneViewer.scene.traverse(function (node) {
42257
+ if (node.userData && node.userData.objectType === 'connector' && node.uuid) {
42258
+ flowMap[node.uuid] = node.userData.flow || 'bi';
42058
42259
  }
42059
42260
  });
42060
42261
  var valid = [];
@@ -42219,7 +42420,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
42219
42420
  * List all available I/O Device assets from the component dictionary.
42220
42421
  * @param {Object} [options={}]
42221
42422
  * @param {'all'|'bundled'|'user'} [options.source='all'] - Filter by asset origin
42222
- * @returns {Array<{uuid: string, name: string, assetType: string, ioConfig: Object}>}
42423
+ * @returns {Array<{uuid: string, name: string, assetType: string}>}
42223
42424
  * @example
42224
42425
  * const devices = centralPlant.getIoDevices({ source: 'all' })
42225
42426
  * const bundledOnly = centralPlant.getIoDevices({ source: 'bundled' })
@@ -42246,8 +42447,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
42246
42447
  return {
42247
42448
  uuid: entry.uuid || entry.id,
42248
42449
  name: entry.name,
42249
- assetType: entry.assetType,
42250
- ioConfig: entry.ioConfig || {}
42450
+ assetType: entry.assetType
42251
42451
  };
42252
42452
  });
42253
42453
  }
@@ -44023,7 +44223,7 @@ var sceneViewer = /*#__PURE__*/function (_BaseDisposable) {
44023
44223
  this.centralPlant.attachToComponent();
44024
44224
 
44025
44225
  // Sync our managers tracking object after attachment
44026
- managerKeys = ['threeJSResourceManager', 'performanceMonitorManager', 'settingsManager', 'sceneExportManager', 'componentManager', 'sceneInitializationManager', 'environmentManager', 'keyboardControlsManager', 'pathfindingManager', 'pathFlowManager', 'ioBehaviorManager', 'ioOutlineManager', 'sceneOperationsManager', 'animationManager', 'cameraControlsManager', 'componentDragManager', 'tooltipsManager', 'componentTooltipManager']; // Populate our managers tracking object
44226
+ managerKeys = ['threeJSResourceManager', 'performanceMonitorManager', 'settingsManager', 'sceneExportManager', 'componentManager', 'transformOperationsManager', 'sceneInitializationManager', 'environmentManager', 'keyboardControlsManager', 'pathfindingManager', 'pathFlowManager', 'ioBehaviorManager', 'ioOutlineManager', 'sceneOperationsManager', 'animationManager', 'cameraControlsManager', 'componentDragManager', 'tooltipsManager', 'componentTooltipManager']; // Populate our managers tracking object
44027
44227
  managerKeys.forEach(function (key) {
44028
44228
  if (_this2[key]) {
44029
44229
  _this2.managers[key] = _this2[key];
@@ -44568,7 +44768,6 @@ var sceneViewer = /*#__PURE__*/function (_BaseDisposable) {
44568
44768
  }, {
44569
44769
  key: "updatePaths",
44570
44770
  value: function updatePaths() {
44571
- console.log("updatePaths started");
44572
44771
  // Delegate pathfinding update to PathfindingManager
44573
44772
  if (this.pathfindingManager) {
44574
44773
  this.pathfindingManager.updatePathfindingAfterTransform(this.currentSceneData);