@2112-lab/central-plant 0.3.47 → 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.
@@ -3720,41 +3720,36 @@ function computeFilteredBoundingBox(object) {
3720
3720
  */
3721
3721
  function computeIODeviceBoundingBoxes(componentObject) {
3722
3722
  var results = [];
3723
- var _iterator = _createForOfIteratorHelper(componentObject.children),
3724
- _step;
3725
- try {
3726
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
3727
- var _child$userData;
3728
- var child = _step.value;
3729
- if (((_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.objectType) !== 'io-device') continue;
3730
- var bbox = new THREE__namespace.Box3().setFromObject(child);
3731
- if (!bbox.isEmpty()) {
3732
- results.push({
3733
- uuid: child.uuid,
3734
- userData: {
3735
- objectType: 'io-device',
3736
- deviceId: child.userData.deviceId || null,
3737
- attachmentId: child.userData.attachmentId || null,
3738
- parentComponentId: child.userData.parentComponentId || componentObject.uuid
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
- });
3745
- }
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
+ });
3746
3745
  }
3747
- } catch (err) {
3748
- _iterator.e(err);
3749
- } finally {
3750
- _iterator.f();
3751
- }
3746
+ });
3752
3747
  return results;
3753
3748
  }
3754
3749
 
3755
3750
  /**
3756
3751
  * Computes individual world-space bounding boxes for each connector child
3757
- * of a component.
3752
+ * of a component. Supports deep children (e.g. within GLB model hierarchy).
3758
3753
  *
3759
3754
  * @param {THREE.Object3D} componentObject - The component's Three.js object
3760
3755
  * @returns {Array<{uuid: string, userData: Object, worldBoundingBox: {min: number[], max: number[]}}>}
@@ -3762,38 +3757,40 @@ function computeIODeviceBoundingBoxes(componentObject) {
3762
3757
  */
3763
3758
  function computeConnectorBoundingBoxes(componentObject) {
3764
3759
  var results = [];
3765
- var _iterator2 = _createForOfIteratorHelper(componentObject.children),
3766
- _step2;
3767
- try {
3768
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
3769
- var _child$userData2;
3770
- var child = _step2.value;
3771
- if (((_child$userData2 = child.userData) === null || _child$userData2 === void 0 ? void 0 : _child$userData2.objectType) !== 'connector') continue;
3772
- var bbox = new THREE__namespace.Box3().setFromObject(child);
3773
-
3774
- // Fallback if mesh is too small or empty (sometimes connectors are just points)
3775
- if (bbox.isEmpty() || bbox.getSize(new THREE__namespace.Vector3()).length() < 0.01) {
3776
- var worldPos = new THREE__namespace.Vector3();
3777
- child.getWorldPosition(worldPos);
3778
- var size = 0.1;
3779
- bbox.setFromCenterAndSize(worldPos, new THREE__namespace.Vector3(size, size, size));
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]
3780
3791
  }
3781
- results.push({
3782
- uuid: child.uuid,
3783
- userData: _objectSpread2(_objectSpread2({}, child.userData), {}, {
3784
- objectType: 'connector'
3785
- }),
3786
- worldBoundingBox: {
3787
- min: [bbox.min.x, bbox.min.y, bbox.min.z],
3788
- max: [bbox.max.x, bbox.max.y, bbox.max.z]
3789
- }
3790
- });
3791
- }
3792
- } catch (err) {
3793
- _iterator2.e(err);
3794
- } finally {
3795
- _iterator2.f();
3796
- }
3792
+ });
3793
+ });
3797
3794
  return results;
3798
3795
  }
3799
3796
 
@@ -3893,11 +3890,11 @@ function createSelectionBoxHelpers(object) {
3893
3890
  * @param {THREE.Scene} scene - The scene (for finding objects by uuid)
3894
3891
  */
3895
3892
  function updateSelectionBoxHelpers(helpers, selectedObjects, scene) {
3896
- var _iterator3 = _createForOfIteratorHelper(helpers),
3897
- _step3;
3893
+ var _iterator = _createForOfIteratorHelper(helpers),
3894
+ _step;
3898
3895
  try {
3899
3896
  var _loop = function _loop() {
3900
- var helper = _step3.value;
3897
+ var helper = _step.value;
3901
3898
  var _helper$userData = helper.userData,
3902
3899
  sourceObjectUuid = _helper$userData.sourceObjectUuid,
3903
3900
  isFiltered = _helper$userData.isFiltered,
@@ -3928,13 +3925,13 @@ function updateSelectionBoxHelpers(helpers, selectedObjects, scene) {
3928
3925
  helper.update();
3929
3926
  }
3930
3927
  };
3931
- for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
3928
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
3932
3929
  if (_loop()) continue;
3933
3930
  }
3934
3931
  } catch (err) {
3935
- _iterator3.e(err);
3932
+ _iterator.e(err);
3936
3933
  } finally {
3937
- _iterator3.f();
3934
+ _iterator.f();
3938
3935
  }
3939
3936
  }
3940
3937
 
@@ -3974,7 +3971,7 @@ var TransformControlsManager = /*#__PURE__*/function () {
3974
3971
  this.forceInvisible = false;
3975
3972
 
3976
3973
  // SceneViewer reference for event listening
3977
- this.sceneViewer = null;
3974
+ this.sceneViewer = centralPlant ? centralPlant.sceneViewer : null;
3978
3975
  this._objectTransformedListener = null;
3979
3976
 
3980
3977
  // Event handlers storage
@@ -4176,23 +4173,28 @@ var TransformControlsManager = /*#__PURE__*/function () {
4176
4173
  _this2.transformState.isTransforming = true;
4177
4174
 
4178
4175
  // Store initial transforms for all selected objects
4179
- if (_this2.selectedObjects.length > 0 && _this2.multiSelectionGroup) {
4176
+ if (_this2.selectedObjects.length > 0) {
4180
4177
  _this2.selectedObjects.forEach(function (obj) {
4181
- obj.userData._multiSelectOriginalPosition = obj.position.clone();
4182
- obj.userData._multiSelectOriginalRotation = obj.rotation.clone();
4183
- obj.userData._multiSelectOriginalScale = obj.scale.clone();
4184
- });
4185
-
4186
- // Snapshot group position and helper geometry vertices so we can do
4187
- // cheap per-frame bbox translation without re-traversing the mesh hierarchy.
4188
- _this2._dragStartGroupPosition = _this2.multiSelectionGroup.position.clone();
4189
- _this2.boundingBoxHelpers.forEach(function (helper) {
4190
- var _helper$geometry;
4191
- 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;
4192
- if (posAttr) {
4193
- 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();
4194
4184
  }
4195
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
+ }
4196
4198
  }
4197
4199
 
4198
4200
  // Disable orbit controls during transformation
@@ -4209,7 +4211,7 @@ var TransformControlsManager = /*#__PURE__*/function () {
4209
4211
 
4210
4212
  // Transform end event
4211
4213
  this.eventHandlers.transformEnd = /*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee() {
4212
- var hasComponents, _this2$selectedObject, sceneCompleteEvent;
4214
+ var isTranslate, isRotate, hasComponents, _this2$selectedObject, sceneCompleteEvent;
4213
4215
  return _regenerator().w(function (_context) {
4214
4216
  while (1) switch (_context.n) {
4215
4217
  case 0:
@@ -4231,29 +4233,52 @@ var TransformControlsManager = /*#__PURE__*/function () {
4231
4233
  console.error('❌ Error in applyMultiSelectionTransform:', error);
4232
4234
  });
4233
4235
  case 1:
4234
- // applyMultiSelectionTransform() already calls updatePaths() once at the end
4235
- // so we skip the additional updatePaths() call below
4236
- console.log('✅ Multi-selection transform complete (updatePaths already called)');
4236
+ console.log('✅ Multi-selection transform complete');
4237
4237
  _context.n = 3;
4238
4238
  break;
4239
4239
  case 2:
4240
- if (_this2.currentMode === 'translate' && _this2.sceneViewer && typeof _this2.sceneViewer.updatePaths === 'function') {
4241
- // Update paths after translation is complete
4242
- // Only if we translated components (not segments/gateways - they handle paths internally)
4243
- // This branch only executes when NOT in multi-selection mode
4244
- hasComponents = _this2.selectedObjects.some(function (obj) {
4245
- return !isSegment(obj) && !isGateway(obj);
4246
- });
4247
- if (hasComponents) {
4248
- console.log('🔄 Updating paths after component translation...');
4249
- try {
4250
- _this2.sceneViewer.updatePaths();
4251
- console.log('✅ Paths updated successfully after translation');
4252
- } catch (error) {
4253
- 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
+ }
4254
4281
  }
4255
- } else {
4256
- console.log('ℹ️ Skipping updatePaths - segments/gateways handle their own path updates');
4257
4282
  }
4258
4283
  }
4259
4284
  case 3:
@@ -4384,8 +4409,8 @@ var TransformControlsManager = /*#__PURE__*/function () {
4384
4409
  var hit = _step.value;
4385
4410
  var _obj = hit.object;
4386
4411
  while (_obj) {
4387
- var _obj$userData2;
4388
- 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') {
4389
4414
  ioDeviceObject = _obj;
4390
4415
  break;
4391
4416
  }
@@ -4408,8 +4433,8 @@ var TransformControlsManager = /*#__PURE__*/function () {
4408
4433
  var parentUuid = null;
4409
4434
  var obj = ioDeviceObject.parent;
4410
4435
  while (obj) {
4411
- var _obj$userData;
4412
- 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') {
4413
4438
  parentUuid = obj.userData.originalUuid || obj.uuid;
4414
4439
  break;
4415
4440
  }
@@ -4486,8 +4511,8 @@ var TransformControlsManager = /*#__PURE__*/function () {
4486
4511
  var hit = _step2.value;
4487
4512
  var obj = hit.object;
4488
4513
  while (obj) {
4489
- var _obj$userData3;
4490
- 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') {
4491
4516
  _this4.callbacks.onIODeviceClick(obj);
4492
4517
  return;
4493
4518
  }
@@ -6148,8 +6173,8 @@ var TransformControlsManager = /*#__PURE__*/function () {
6148
6173
  key: "_updateSegmentReference",
6149
6174
  value: function _updateSegmentReference(oldSegment, newSegment, index) {
6150
6175
  var selectedIndex = this.selectedObjects.findIndex(function (obj) {
6151
- var _obj$userData4;
6152
- 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;
6153
6178
  });
6154
6179
  if (selectedIndex !== -1 && newSegment) {
6155
6180
  // Clear bounding box cache
@@ -6257,7 +6282,7 @@ var TransformControlsManager = /*#__PURE__*/function () {
6257
6282
  componentId = component.uuid || ((_component$userData = component.userData) === null || _component$userData === void 0 ? void 0 : _component$userData.originalUuid);
6258
6283
  _context6.n = 3;
6259
6284
  return this._translateObjectOnAxes(componentId, deltaX, deltaY, deltaZ, threshold, throttleDelay, AXIS_THROTTLE, function (id, axis, delta) {
6260
- return _this0.centralPlant.translate(id, axis, delta);
6285
+ return _this0.centralPlant.translate(id, axis, delta, true);
6261
6286
  });
6262
6287
  case 3:
6263
6288
  console.log("\uD83D\uDD27 Component ".concat(component.name, " translated (").concat(i + 1, "/").concat(components.length, ")"));
@@ -6356,6 +6381,17 @@ var TransformControlsManager = /*#__PURE__*/function () {
6356
6381
  if (this.selectedObjects.length > 0) {
6357
6382
  this.updateMultiSelection();
6358
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
+ }
6359
6395
  console.log("\u2705 Multi-selection transform applied");
6360
6396
  }
6361
6397
 
@@ -11881,6 +11917,11 @@ var SceneExportManager = /*#__PURE__*/function () {
11881
11917
  // Helper function to convert Three.js object to minimal JSON format
11882
11918
  var convertObjectToJson = function convertObjectToJson(threeObject) {
11883
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
+
11884
11925
  // Skip certain objects that shouldn't be exported
11885
11926
  if (!threeObject || (_threeObject$name = threeObject.name) !== null && _threeObject$name !== void 0 && _threeObject$name.includes('Polyline') ||
11886
11927
  // Skip pipe paths
@@ -12038,19 +12079,23 @@ var SceneExportManager = /*#__PURE__*/function () {
12038
12079
  }
12039
12080
  });
12040
12081
  } else if (((_threeObject$userData11 = threeObject.userData) === null || _threeObject$userData11 === void 0 ? void 0 : _threeObject$userData11.objectType) === 'component') {
12041
- // For components, only export connectors that have objectType='connector'
12042
- // Standard dictionary-injected connectors should be exported so connections work on re-import
12043
- threeObject.children.forEach(function (child) {
12082
+ // For components, export all connectors (including deep children within GLFs)
12083
+ threeObject.traverse(function (child) {
12044
12084
  var _child$userData2;
12045
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);
12046
12091
  exportableChildren.push({
12047
12092
  uuid: child.uuid,
12048
12093
  name: child.name,
12049
12094
  type: 'Mesh',
12050
12095
  position: {
12051
- x: roundIfClose(child.position.x),
12052
- y: roundIfClose(child.position.y),
12053
- z: roundIfClose(child.position.z)
12096
+ x: roundIfClose(relativePos.x),
12097
+ y: roundIfClose(relativePos.y),
12098
+ z: roundIfClose(relativePos.z)
12054
12099
  },
12055
12100
  userData: _objectSpread2({}, child.userData)
12056
12101
  });
@@ -19901,7 +19946,7 @@ var ComponentManager = /*#__PURE__*/function () {
19901
19946
  key: "addComponentToScene",
19902
19947
  value: (function () {
19903
19948
  var _addComponentToScene = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(componentData) {
19904
- 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;
19905
19950
  return _regenerator().w(function (_context) {
19906
19951
  while (1) switch (_context.n) {
19907
19952
  case 0:
@@ -20005,6 +20050,7 @@ var ComponentManager = /*#__PURE__*/function () {
20005
20050
  }
20006
20051
 
20007
20052
  // Enable shadows for all meshes in the component
20053
+ connectorIndex = 0;
20008
20054
  componentMesh.traverse(function (child) {
20009
20055
  if (child.isMesh) {
20010
20056
  child.castShadow = true;
@@ -20014,12 +20060,19 @@ var ComponentManager = /*#__PURE__*/function () {
20014
20060
  }
20015
20061
 
20016
20062
  // Set connector properties for connectors within the component
20017
- 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++;
20018
20065
  if (!child.userData) {
20019
20066
  child.userData = {};
20020
20067
  }
20021
20068
  child.userData.objectType = 'connector';
20022
- 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));
20023
20076
  }
20024
20077
  }
20025
20078
  });
@@ -21214,12 +21267,14 @@ var TransformOperationsManager = /*#__PURE__*/function () {
21214
21267
  * @param {string} componentId - The UUID of the component to translate
21215
21268
  * @param {string} axis - The axis to translate on ('x', 'y', or 'z')
21216
21269
  * @param {number} value - The value to translate by
21270
+ * @param {boolean} [skipPathUpdate=false] - Whether to skip triggering pathfinding after translation
21217
21271
  * @returns {boolean} True if translation was successful, false otherwise
21218
21272
  */
21219
21273
  return _createClass(TransformOperationsManager, [{
21220
21274
  key: "translateComponent",
21221
21275
  value: function translateComponent(componentId, axis, value) {
21222
21276
  var _this$sceneViewer$man, _this$sceneViewer;
21277
+ var skipPathUpdate = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
21223
21278
  // Store transform parameters using the OperationHistoryManager
21224
21279
  if ((_this$sceneViewer$man = this.sceneViewer.managers) !== null && _this$sceneViewer$man !== void 0 && _this$sceneViewer$man.operationHistoryManager) {
21225
21280
  this.sceneViewer.managers.operationHistoryManager.addToOperationHistory('translateComponent', {
@@ -21287,7 +21342,7 @@ var TransformOperationsManager = /*#__PURE__*/function () {
21287
21342
  }
21288
21343
 
21289
21344
  // Auto-update paths if enabled (matches behavior of translateSegment and translateGateway)
21290
- if (this.sceneViewer.shouldUpdatePaths) {
21345
+ if (!skipPathUpdate && this.sceneViewer.shouldUpdatePaths) {
21291
21346
  try {
21292
21347
  if (this.sceneViewer && typeof this.sceneViewer.updatePaths === 'function') {
21293
21348
  this.sceneViewer.updatePaths();
@@ -21298,6 +21353,8 @@ var TransformOperationsManager = /*#__PURE__*/function () {
21298
21353
  } catch (error) {
21299
21354
  console.error('❌ Error auto-updating paths:', error);
21300
21355
  }
21356
+ } else if (skipPathUpdate) {
21357
+ console.log('ℹ️ skipPathUpdate is true: skipping automatic path update');
21301
21358
  }
21302
21359
 
21303
21360
  // Emit transform event if available
@@ -23116,11 +23173,8 @@ var TransformOperationsManager = /*#__PURE__*/function () {
23116
23173
  sceneDataComponent.children.forEach(function (child) {
23117
23174
  var _child$userData21;
23118
23175
  if (((_child$userData21 = child.userData) === null || _child$userData21 === void 0 ? void 0 : _child$userData21.objectType) === 'connector') {
23119
- // Find the actual connector object in the scene
23120
- var connectorObj = component.children.find(function (c) {
23121
- var _c$userData;
23122
- return c.uuid === child.uuid || ((_c$userData = c.userData) === null || _c$userData === void 0 ? void 0 : _c$userData.originalUuid) === child.uuid;
23123
- });
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);
23124
23178
  if (connectorObj) {
23125
23179
  // Get world position of connector
23126
23180
  var worldPos = new THREE__namespace.Vector3();
@@ -23179,11 +23233,8 @@ var TransformOperationsManager = /*#__PURE__*/function () {
23179
23233
  sceneDataComponent.children.forEach(function (child) {
23180
23234
  var _child$userData22;
23181
23235
  if (((_child$userData22 = child.userData) === null || _child$userData22 === void 0 ? void 0 : _child$userData22.objectType) === 'connector') {
23182
- // Find the actual connector object in the scene
23183
- var connectorObj = component.children.find(function (c) {
23184
- var _c$userData2;
23185
- return c.uuid === child.uuid || ((_c$userData2 = c.userData) === null || _c$userData2 === void 0 ? void 0 : _c$userData2.originalUuid) === child.uuid;
23186
- });
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);
23187
23238
  if (connectorObj) {
23188
23239
  var _connectorObj$userDat;
23189
23240
  // Get world position of connector
@@ -29109,10 +29160,11 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
29109
29160
  }, {
29110
29161
  key: "_matrixHash",
29111
29162
  value: function _matrixHash(obj) {
29163
+ // Force matrix update to ensure we're looking at the latest data
29164
+ obj.updateMatrixWorld(true);
29112
29165
  var e = obj.matrixWorld.elements;
29113
- // Use position (12-14) + first diagonal entries (0,5,10) — covers
29114
- // translation AND rotation/scale changes with minimal work.
29115
- 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));
29116
29168
  }
29117
29169
 
29118
29170
  /**
@@ -29130,11 +29182,7 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
29130
29182
  * Compute a lightweight fingerprint of the pathfinding inputs so that
29131
29183
  * consecutive identical runs can be skipped entirely.
29132
29184
  *
29133
- * The fingerprint captures:
29134
- * • The connections list (from / to pairs)
29135
- * • The world transform of every top-level child in sceneData
29136
- *
29137
- * @param {Object} sceneData - scene data object with .children
29185
+ * @param {Object} sceneData - scene object or manifest
29138
29186
  * @param {Array} connections - array of {from, to}
29139
29187
  * @returns {string}
29140
29188
  * @private
@@ -29142,277 +29190,241 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
29142
29190
  }, {
29143
29191
  key: "_computePathfindingFingerprint",
29144
29192
  value: function _computePathfindingFingerprint(sceneData, connections) {
29193
+ var _this3 = this;
29145
29194
  var parts = [];
29146
29195
 
29147
29196
  // 1. Connections (sorted so order doesn't matter)
29148
- parts.push(connections.map(function (c) {
29197
+ parts.push((connections || []).map(function (c) {
29149
29198
  return "".concat(c.from, "->").concat(c.to);
29150
29199
  }).sort().join(','));
29151
29200
 
29152
- // 2. World transforms of every referenced scene object
29153
- if (sceneData.children) {
29154
- var _iterator = _createForOfIteratorHelper(sceneData.children),
29155
- _step;
29156
- try {
29157
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
29158
- var child = _step.value;
29159
- var obj = this.sceneViewer.scene.getObjectByProperty('uuid', child.uuid);
29160
- if (obj) {
29161
- parts.push("".concat(child.uuid, ":").concat(this._matrixHash(obj)));
29162
- } else {
29163
- var _p$x, _p$y, _p$z;
29164
- // Object only in data, not in scene — include its serialised position
29165
- var p = child.position || {};
29166
- 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)));
29167
- }
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)));
29168
29212
  }
29169
- } catch (err) {
29170
- _iterator.e(err);
29171
- } finally {
29172
- _iterator.f();
29173
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);
29174
29223
  }
29175
29224
  return parts.join('|');
29176
29225
  }
29177
29226
 
29178
29227
  /**
29179
- * Enrich sceneData with worldBoundingBox for segments and components
29180
- * Connectors remain with just position information.
29181
- *
29182
- * Uses _bboxCache to avoid recomputing filtered / io-device bboxes
29183
- * when the underlying object's world matrix has not changed.
29184
- *
29185
- * @param {Object} sceneData - Original scene data
29186
- * @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)
29187
29231
  * @private
29188
29232
  */
29189
29233
  }, {
29190
29234
  key: "_enrichSceneDataWithBoundingBoxes",
29191
29235
  value: function _enrichSceneDataWithBoundingBoxes(sceneData) {
29192
- var _this3 = this;
29193
- // Create a shallow copy of sceneData structure
29194
- var enriched = _objectSpread2({}, sceneData);
29195
- if (!sceneData.children || !Array.isArray(sceneData.children)) {
29196
- console.warn('⚠️ sceneData has no children array');
29197
- return enriched;
29198
- }
29199
-
29200
- // Process children to add worldBoundingBox to segments and components
29201
- enriched.children = sceneData.children.map(function (child) {
29202
- // Enrich segments (check if objectType is 'segment' in userData)
29203
- if (child.userData && child.userData.objectType === 'segment') {
29204
- // Find the actual segment object in the scene
29205
- 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);
29206
29252
  if (segmentObject) {
29207
- // ── Cache check ──
29208
- var hash = _this3._matrixHash(segmentObject);
29209
- var cached = _this3._bboxCache.get(child.uuid);
29253
+ var hash = _this4._matrixHash(segmentObject);
29254
+ var cached = _this4._bboxCache.get(node.uuid);
29210
29255
  if (cached && cached.matrixHash === hash && cached.segmentBBox) {
29211
- return _objectSpread2(_objectSpread2({}, child), {}, {
29212
- userData: _objectSpread2(_objectSpread2({}, child.userData), {}, {
29213
- worldBoundingBox: cached.segmentBBox
29214
- })
29256
+ enrichedNode.userData = _objectSpread2(_objectSpread2({}, node.userData), {}, {
29257
+ worldBoundingBox: cached.segmentBBox
29215
29258
  });
29216
- }
29217
-
29218
- // Compute world bounding box
29219
- var worldBBox = new THREE__namespace.Box3().setFromObject(segmentObject);
29220
- var bboxData = {
29221
- min: [worldBBox.min.x, worldBBox.min.y, worldBBox.min.z],
29222
- max: [worldBBox.max.x, worldBBox.max.y, worldBBox.max.z]
29223
- };
29224
-
29225
- // Store in cache
29226
- _this3._bboxCache.set(child.uuid, {
29227
- matrixHash: hash,
29228
- segmentBBox: bboxData
29229
- });
29230
- return _objectSpread2(_objectSpread2({}, child), {}, {
29231
- 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), {}, {
29232
29270
  worldBoundingBox: bboxData
29233
- })
29234
- });
29235
- } else {
29236
- console.warn("\u26A0\uFE0F Could not find segment object in scene: ".concat(child.uuid));
29271
+ });
29272
+ }
29237
29273
  }
29238
29274
  }
29239
29275
 
29240
- // Enrich components (check if objectType is 'component' in userData)
29241
- if (child.userData && child.userData.objectType === 'component') {
29242
- // Find the actual component object in the scene
29243
- 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);
29244
29279
  if (componentObject) {
29245
- // ── Cache check ──
29246
- var _hash = _this3._matrixHash(componentObject);
29247
- 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);
29248
29283
  if (_cached && _cached.matrixHash === _hash && _cached.filteredBBox) {
29249
- // Rebuild enriched child from cached data
29250
- var _enrichedChild = _objectSpread2(_objectSpread2({}, child), {}, {
29251
- userData: _objectSpread2(_objectSpread2({}, child.userData), {}, {
29252
- worldBoundingBox: _cached.filteredBBox
29253
- })
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]
29254
29297
  });
29255
- if (_cached.ioDeviceBBoxes && _cached.ioDeviceBBoxes.length > 0) {
29256
- if (!_enrichedChild.children) _enrichedChild.children = [];
29257
- _cached.ioDeviceBBoxes.forEach(function (deviceBBox) {
29258
- var existingIndex = _enrichedChild.children.findIndex(function (c) {
29259
- return c.uuid === deviceBBox.uuid;
29260
- });
29261
- if (existingIndex >= 0) {
29262
- _enrichedChild.children[existingIndex] = _objectSpread2(_objectSpread2({}, _enrichedChild.children[existingIndex]), {}, {
29263
- userData: _objectSpread2(_objectSpread2({}, _enrichedChild.children[existingIndex].userData), {}, {
29264
- objectType: 'io-device',
29265
- worldBoundingBox: deviceBBox.worldBoundingBox
29266
- })
29267
- });
29268
- } else {
29269
- _enrichedChild.children.push({
29270
- uuid: deviceBBox.uuid,
29271
- userData: _objectSpread2(_objectSpread2({}, deviceBBox.userData), {}, {
29272
- worldBoundingBox: deviceBBox.worldBoundingBox
29273
- }),
29274
- children: []
29275
- });
29276
- }
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);
29277
29304
  });
29278
29305
  }
29279
-
29280
- // Also reinject connectors from cache
29281
- if (_cached.connectorBBoxes && _cached.connectorBBoxes.length > 0) {
29282
- if (!_enrichedChild.children) _enrichedChild.children = [];
29283
- _cached.connectorBBoxes.forEach(function (connBBox) {
29284
- var existingIndex = _enrichedChild.children.findIndex(function (c) {
29285
- return c.uuid === connBBox.uuid;
29286
- });
29287
- if (existingIndex >= 0) {
29288
- _enrichedChild.children[existingIndex] = _objectSpread2(_objectSpread2({}, _enrichedChild.children[existingIndex]), {}, {
29289
- userData: _objectSpread2(_objectSpread2({}, _enrichedChild.children[existingIndex].userData), {}, {
29290
- worldBoundingBox: connBBox.worldBoundingBox
29291
- })
29292
- });
29293
- } else {
29294
- _enrichedChild.children.push({
29295
- uuid: connBBox.uuid,
29296
- userData: _objectSpread2(_objectSpread2({}, connBBox.userData), {}, {
29297
- worldBoundingBox: connBBox.worldBoundingBox
29298
- }),
29299
- children: []
29300
- });
29301
- }
29306
+ if (_cached.ioDeviceBBoxes) {
29307
+ _cached.ioDeviceBBoxes.forEach(function (dev) {
29308
+ return _this4._mergeEnrichedChild(enrichedNode.children, dev);
29302
29309
  });
29303
29310
  }
29304
- 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
+ });
29305
29346
  }
29347
+ }
29348
+ }
29306
29349
 
29307
- // Compute FILTERED bounding box excludes io-device and connector subtrees
29308
- // so the component body bbox is tight-fitting and doesn't envelop attached devices
29309
- var filteredBBox = computeFilteredBoundingBox(componentObject, ['io-device', 'connector']);
29310
- 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), "]"));
29311
- var _bboxData = {
29312
- min: [filteredBBox.min.x, filteredBBox.min.y, filteredBBox.min.z],
29313
- 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
29314
29361
  };
29315
-
29316
- // Build the enriched component entry
29317
- var enrichedChild = _objectSpread2(_objectSpread2({}, child), {}, {
29318
- userData: _objectSpread2(_objectSpread2({}, child.userData), {}, {
29319
- worldBoundingBox: _bboxData
29320
- })
29362
+ enrichedNode.userData = _objectSpread2(_objectSpread2({}, node.userData), {}, {
29363
+ position: [worldPos.x, worldPos.y, worldPos.z]
29321
29364
  });
29322
29365
 
29323
- // Compute separate bounding boxes for each attached io-device
29324
- var ioDeviceBBoxes = computeIODeviceBoundingBoxes(componentObject);
29325
- if (ioDeviceBBoxes.length > 0) {
29326
- // Ensure children array exists (may already contain connectors)
29327
- if (!enrichedChild.children) {
29328
- enrichedChild.children = [];
29329
- }
29330
-
29331
- // Inject io-device entries with their own worldBoundingBox
29332
- ioDeviceBBoxes.forEach(function (deviceBBox) {
29333
- var existingIndex = enrichedChild.children.findIndex(function (c) {
29334
- return c.uuid === deviceBBox.uuid;
29335
- });
29336
- if (existingIndex >= 0) {
29337
- enrichedChild.children[existingIndex] = _objectSpread2(_objectSpread2({}, enrichedChild.children[existingIndex]), {}, {
29338
- userData: _objectSpread2(_objectSpread2({}, enrichedChild.children[existingIndex].userData), {}, {
29339
- objectType: 'io-device',
29340
- worldBoundingBox: deviceBBox.worldBoundingBox
29341
- })
29342
- });
29343
- } else {
29344
- enrichedChild.children.push({
29345
- uuid: deviceBBox.uuid,
29346
- userData: _objectSpread2(_objectSpread2({}, deviceBBox.userData), {}, {
29347
- worldBoundingBox: deviceBBox.worldBoundingBox
29348
- }),
29349
- children: []
29350
- });
29351
- }
29352
- console.log("\uD83D\uDCE6 Injected io-device bbox for ".concat(deviceBBox.uuid, ": min=[").concat(deviceBBox.worldBoundingBox.min.map(function (v) {
29353
- return v.toFixed(2);
29354
- }).join(', '), "], max=[").concat(deviceBBox.worldBoundingBox.max.map(function (v) {
29355
- return v.toFixed(2);
29356
- }).join(', '), "]"));
29357
- });
29358
- console.log("\uD83D\uDCE6 Injected ".concat(ioDeviceBBoxes.length, " io-device bounding box(es) for component ").concat(child.uuid));
29359
- }
29360
-
29361
- // PHASE 2: Enrich Connectors (CRITICAL for pathfinding API)
29362
- // Also compute world bounding boxes for connectors and inject into children.
29363
- // This ensures endpoints have world coordinates in sceneDataCopy.
29364
- var connectorBBoxes = computeConnectorBoundingBoxes(componentObject);
29365
- if (connectorBBoxes.length > 0) {
29366
- if (!enrichedChild.children) enrichedChild.children = [];
29367
- connectorBBoxes.forEach(function (connBBox) {
29368
- var existingIndex = enrichedChild.children.findIndex(function (c) {
29369
- return c.uuid === connBBox.uuid;
29370
- });
29371
- if (existingIndex >= 0) {
29372
- enrichedChild.children[existingIndex] = _objectSpread2(_objectSpread2({}, enrichedChild.children[existingIndex]), {}, {
29373
- userData: _objectSpread2(_objectSpread2({}, enrichedChild.children[existingIndex].userData), {}, {
29374
- worldBoundingBox: connBBox.worldBoundingBox
29375
- })
29376
- });
29377
- } else {
29378
- enrichedChild.children.push({
29379
- uuid: connBBox.uuid,
29380
- userData: _objectSpread2(_objectSpread2({}, connBBox.userData), {}, {
29381
- worldBoundingBox: connBBox.worldBoundingBox
29382
- }),
29383
- children: []
29384
- });
29385
- }
29386
- });
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];
29387
29372
  }
29388
-
29389
- // Store in cache
29390
- _this3._bboxCache.set(child.uuid, {
29391
- matrixHash: _hash,
29392
- filteredBBox: _bboxData,
29393
- ioDeviceBBoxes: ioDeviceBBoxes,
29394
- connectorBBoxes: connectorBBoxes
29395
- });
29396
- return enrichedChild;
29397
- } else {
29398
- console.warn("\u26A0\uFE0F Could not find component object in scene: ".concat(child.uuid));
29399
29373
  }
29400
29374
  }
29401
29375
 
29402
- // For non-segments and non-components (including connectors), return as-is
29403
- return child;
29404
- });
29405
- 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
+ }
29406
29393
  }
29407
29394
 
29408
29395
  /**
29409
- * Core pathfinding logic shared across initialization and updates
29396
+ * Helper to merge an enriched child (connector/device) into a children array
29397
+ * @private
29410
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
+ }
29411
29423
  }, {
29412
29424
  key: "_executePathfinding",
29413
- value: (function () {
29425
+ value: function () {
29414
29426
  var _executePathfinding2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(sceneData, connections) {
29415
- var _this4 = this,
29427
+ var _this5 = this,
29416
29428
  _sceneDataCopy$childr,
29417
29429
  _sceneDataCopy$childr2,
29418
29430
  _pathfindingResult$pa,
@@ -29424,6 +29436,12 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
29424
29436
  totalStart,
29425
29437
  inputGenStart,
29426
29438
  enrichedSceneData,
29439
+ _from$userData,
29440
+ _to$userData,
29441
+ firstConn,
29442
+ findEndpoint,
29443
+ from,
29444
+ to,
29427
29445
  connectionsCopy,
29428
29446
  sceneDataCopy,
29429
29447
  algoStart,
@@ -29440,6 +29458,10 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
29440
29458
  while (1) switch (_context.n) {
29441
29459
  case 0:
29442
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
+ }
29443
29465
  options.createGateways, _options$context = options.context, context = _options$context === void 0 ? 'Pathfinding' : _options$context;
29444
29466
  timers = {};
29445
29467
  totalStart = performance.now(); // Create pathfinder instance with configuration only
@@ -29468,7 +29490,50 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
29468
29490
  console.log("\uD83D\uDD04 Updated matrix world for ".concat(context, " pathfinding execution"));
29469
29491
 
29470
29492
  // Enrich sceneData with worldBoundingBox for segments before deep copy
29471
- 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
29472
29537
  connectionsCopy = structuredClone(connections);
29473
29538
  sceneDataCopy = structuredClone(enrichedSceneData);
29474
29539
  timers.inputGeneration = performance.now() - inputGenStart;
@@ -29499,7 +29564,7 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
29499
29564
  if (pathfindingResult.paths) {
29500
29565
  pathfindingResult.paths.forEach(function (path) {
29501
29566
  var pathId = "".concat(path.from, "-->").concat(path.to);
29502
- _this4._getOrCreatePathData(pathId, path.from, path.to);
29567
+ _this5._getOrCreatePathData(pathId, path.from, path.to);
29503
29568
  });
29504
29569
  }
29505
29570
  timers.pathRendering = performance.now() - renderStart;
@@ -29532,7 +29597,7 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
29532
29597
  return _executePathfinding2.apply(this, arguments);
29533
29598
  }
29534
29599
  return _executePathfinding;
29535
- }())
29600
+ }()
29536
29601
  }, {
29537
29602
  key: "getSimplifiedSceneData",
29538
29603
  value: function getSimplifiedSceneData() {
@@ -29571,7 +29636,7 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
29571
29636
  }, {
29572
29637
  key: "_propagateFlowAttributesAndApplyVisualizations",
29573
29638
  value: function _propagateFlowAttributesAndApplyVisualizations(connections, pathfindingResult) {
29574
- var _this5 = this,
29639
+ var _this6 = this,
29575
29640
  _this$sceneViewer;
29576
29641
  if (!Array.isArray(connections) || !pathfindingResult) return;
29577
29642
 
@@ -29599,7 +29664,7 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
29599
29664
  var subPathIds = new Set(originalToSubPaths.get(origId) || []);
29600
29665
  subPathIds.add(origId);
29601
29666
  subPathIds.forEach(function (subPathId) {
29602
- var pd = _this5.pathDataStore.get(subPathId);
29667
+ var pd = _this6.pathDataStore.get(subPathId);
29603
29668
  if (pd && Object.keys(pd.flowAttributes).length === 0) {
29604
29669
  Object.entries(conn.flowAttributes).forEach(function (_ref2) {
29605
29670
  var _ref3 = _slicedToArray(_ref2, 2),
@@ -29663,7 +29728,7 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
29663
29728
  key: "initializePathfinder",
29664
29729
  value: (function () {
29665
29730
  var _initializePathfinder = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2(data, crosscubeTextureSet) {
29666
- var _this6 = this;
29731
+ var _this7 = this;
29667
29732
  var pathfindingResult;
29668
29733
  return _regenerator().w(function (_context2) {
29669
29734
  while (1) switch (_context2.n) {
@@ -29677,7 +29742,7 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
29677
29742
  data.connections.forEach(function (conn) {
29678
29743
  if (conn.flowAttributes && _typeof(conn.flowAttributes) === 'object') {
29679
29744
  var pathId = "".concat(conn.from, "-->").concat(conn.to);
29680
- var pd = _this6._getOrCreatePathData(pathId, conn.from, conn.to);
29745
+ var pd = _this7._getOrCreatePathData(pathId, conn.from, conn.to);
29681
29746
  Object.entries(conn.flowAttributes).forEach(function (_ref6) {
29682
29747
  var _ref7 = _slicedToArray(_ref6, 2),
29683
29748
  key = _ref7[0],
@@ -29795,33 +29860,47 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
29795
29860
  key: "updatePathfindingAfterTransform",
29796
29861
  value: (function () {
29797
29862
  var _updatePathfindingAfterTransform = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee3(currentSceneData) {
29798
- var fingerprint, result;
29863
+ var _currentSceneData$sce;
29864
+ var fingerprint, dataRoot, connections, result;
29799
29865
  return _regenerator().w(function (_context3) {
29800
29866
  while (1) switch (_context3.n) {
29801
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:
29802
29875
  // ── Skip-unchanged-paths check ──────────────────────────────────
29803
29876
  // Ensure matrices are fresh so the fingerprint is accurate.
29804
29877
  this.sceneViewer.scene.updateMatrixWorld(true);
29805
29878
  fingerprint = this._computePathfindingFingerprint(currentSceneData.scene, currentSceneData.connections);
29879
+ console.log("\uD83D\uDD0D Pathfinding update check. Fingerprint: ".concat(fingerprint.substring(0, 50), "..."));
29806
29880
  if (!(fingerprint === this._lastPathfindingFingerprint && this._lastPathfindingResult)) {
29807
- _context3.n = 1;
29881
+ _context3.n = 2;
29808
29882
  break;
29809
29883
  }
29810
29884
  console.log('⏭️ Pathfinding skipped — inputs unchanged since last run');
29811
29885
  return _context3.a(2, this._lastPathfindingResult);
29812
- case 1:
29886
+ case 2:
29887
+ // Clear the cache for the specific fingerprint mismatch to ensure fresh enrichment
29888
+ this.invalidateBBoxCache();
29889
+
29813
29890
  // Remove all existing paths from the scene
29814
29891
  this.removeComputedObjects();
29815
29892
  console.log('🔄 Starting pathfinding with updated scene data...');
29816
29893
 
29817
- // 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
29818
29897
  // Use shared pathfinding logic (no gateways needed for transform updates)
29819
- _context3.n = 2;
29820
- return this._executePathfinding(currentSceneData.scene, currentSceneData.connections, {
29898
+ _context3.n = 3;
29899
+ return this._executePathfinding(dataRoot, connections, {
29821
29900
  createGateways: true,
29822
29901
  context: 'Transform Update'
29823
29902
  });
29824
- case 2:
29903
+ case 3:
29825
29904
  result = _context3.v;
29826
29905
  // Re-apply flow attribute colors (new materials are created on each execution)
29827
29906
  this._propagateFlowAttributesAndApplyVisualizations(currentSceneData.connections, result);
@@ -31727,36 +31806,58 @@ var ModelManager = /*#__PURE__*/function () {
31727
31806
  * @param {THREE.Object3D} targetMesh - The mesh whose connectors to preserve
31728
31807
  * @param {string} parentUuid - The UUID of the parent component
31729
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
+ */
31730
31817
  }, {
31731
31818
  key: "_preserveConnectorChildren",
31732
31819
  value: function _preserveConnectorChildren(targetMesh, parentUuid) {
31733
31820
  var _this = this;
31734
31821
  var connectorChildren = [];
31735
- 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) {
31736
31826
  var _child$userData;
31737
31827
  var isConnectorGeometry = child.geometry && (child.geometry.uuid === 'CONNECTOR-GEO' || child.geometry.type === 'SphereGeometry' && child.geometry.parameters);
31738
31828
  var isConnectorByName = child.name && child.name.toLowerCase().includes('connector');
31739
- // Also recognise connectors declared via userData (e.g. inline connectors from SAMPLE_1.json
31740
- // whose geometry may be a fallback BufferGeometry rather than a SphereGeometry).
31741
31829
  var isConnectorByUserData = ((_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.objectType) === 'connector';
31742
31830
  if (isConnectorGeometry && isConnectorByName || isConnectorByUserData) {
31831
+ connectorIndex++;
31832
+
31743
31833
  // Ensure userData exists and has worldBoundingBox
31744
31834
  if (!child.userData) child.userData = {};
31745
31835
  if (!child.userData.worldBoundingBox) {
31746
31836
  child.userData.worldBoundingBox = _this._calculateWorldBoundingBox(child);
31747
31837
  }
31748
31838
 
31749
- // Clone and preserve critical userData
31839
+ // Clone the connector
31750
31840
  var clonedConnector = child.clone();
31751
31841
 
31752
- // Generate proper connector UUID based on parent component UUID
31753
- // Format: COMPONENT-UUID-CONNECTOR-INDEX (matching JSON import pattern)
31754
- 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);
31755
31856
  clonedConnector.uuid = connectorUuid;
31756
31857
  if (child.userData) {
31757
31858
  clonedConnector.userData = _objectSpread2({}, child.userData);
31758
31859
 
31759
- // Deep copy critical data - handle both array [x,y,z] and object {x,y,z} formats
31860
+ // Deep copy critical data
31760
31861
  if (child.userData.worldBoundingBox) {
31761
31862
  var wbb = child.userData.worldBoundingBox;
31762
31863
  clonedConnector.userData.worldBoundingBox = {
@@ -31765,7 +31866,6 @@ var ModelManager = /*#__PURE__*/function () {
31765
31866
  };
31766
31867
  }
31767
31868
  if (child.userData.direction) {
31768
- // Handle both array [x,y,z] and object {x,y,z} formats
31769
31869
  if (Array.isArray(child.userData.direction)) {
31770
31870
  clonedConnector.userData.direction = _toConsumableArray(child.userData.direction);
31771
31871
  } else if (_typeof(child.userData.direction) === 'object') {
@@ -31777,11 +31877,12 @@ var ModelManager = /*#__PURE__*/function () {
31777
31877
 
31778
31878
  // Set originalUuid to match the actual uuid (maintains consistency)
31779
31879
  clonedConnector.userData.originalUuid = connectorUuid;
31780
- clonedConnector.userData.objectType = child.userData.objectType || (child.name.toLowerCase().includes('connector') ? 'connector' : 'gateway');
31880
+ clonedConnector.userData.objectType = child.userData.objectType || 'connector';
31781
31881
  }
31782
31882
  connectorChildren.push(clonedConnector);
31783
31883
  }
31784
31884
  });
31885
+ console.log("\uD83D\uDEE1\uFE0F Preserved ".concat(connectorChildren.length, " connector(s) for component ").concat(parentUuid));
31785
31886
  return connectorChildren;
31786
31887
  }
31787
31888
 
@@ -34212,24 +34313,29 @@ var SceneOperationsManager = /*#__PURE__*/function () {
34212
34313
  children: [] // Initialize children array
34213
34314
  };
34214
34315
 
34215
- // Collect children that are connectors
34216
- if (componentModel.children) {
34217
- componentModel.children.forEach(function (child) {
34218
- if (child.userData && child.userData.objectType === 'connector') {
34219
- componentSceneData.children.push({
34220
- uuid: child.uuid,
34221
- name: child.name,
34222
- type: 'Mesh',
34223
- position: {
34224
- x: child.position.x,
34225
- y: child.position.y,
34226
- z: child.position.z
34227
- },
34228
- userData: _objectSpread2({}, child.userData)
34229
- });
34230
- }
34231
- });
34232
- }
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
+ });
34233
34339
 
34234
34340
  // Add the component to the scene data
34235
34341
  if (!currentSceneData.scene.children) {
@@ -39876,17 +39982,19 @@ var CentralPlantInternals = /*#__PURE__*/function () {
39876
39982
  * @param {string} componentId - The UUID of the component to translate
39877
39983
  * @param {string} axis - The axis to translate on ('x', 'y', or 'z')
39878
39984
  * @param {number} value - The value to translate by
39985
+ * @param {boolean} [skipPathUpdate=false] - Whether to skip triggering pathfinding
39879
39986
  * @returns {boolean} True if translation was successful, false otherwise
39880
39987
  */
39881
39988
  }, {
39882
39989
  key: "translateComponent",
39883
39990
  value: function translateComponent(componentId, axis, value) {
39884
39991
  var _this$centralPlant$ma;
39992
+ var skipPathUpdate = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
39885
39993
  if (!((_this$centralPlant$ma = this.centralPlant.managers) !== null && _this$centralPlant$ma !== void 0 && _this$centralPlant$ma.transformOperationsManager)) {
39886
39994
  console.error('❌ translateComponent(): TransformOperationsManager not available');
39887
39995
  return false;
39888
39996
  }
39889
- return this.centralPlant.managers.transformOperationsManager.translateComponent(componentId, axis, value);
39997
+ return this.centralPlant.managers.transformOperationsManager.translateComponent(componentId, axis, value, skipPathUpdate);
39890
39998
  }
39891
39999
 
39892
40000
  /**
@@ -39978,7 +40086,7 @@ var CentralPlantInternals = /*#__PURE__*/function () {
39978
40086
  } else if (objectType === 'gateway') {
39979
40087
  success = this.translateGateway(objectId, axis, value);
39980
40088
  } else if (objectType === 'component') {
39981
- success = this.translateComponent(objectId, axis, value);
40089
+ success = this.translateComponent(objectId, axis, value, skipPathUpdate);
39982
40090
  } else {
39983
40091
  result.errors.push("Unknown object type: ".concat(objectType, " (").concat(objectId, ")"));
39984
40092
  console.warn("\u26A0\uFE0F Unknown object type: ".concat(objectType, " for ").concat(objectId));
@@ -41011,7 +41119,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
41011
41119
  * Initialize the CentralPlant manager
41012
41120
  *
41013
41121
  * @constructor
41014
- * @version 0.3.47
41122
+ * @version 0.3.48
41015
41123
  * @updated 2025-10-22
41016
41124
  *
41017
41125
  * @description Creates a new CentralPlant instance and initializes internal managers and utilities.
@@ -41341,7 +41449,8 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
41341
41449
  }, {
41342
41450
  key: "translate",
41343
41451
  value: function translate(componentId, axis, value) {
41344
- 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);
41345
41454
  }
41346
41455
 
41347
41456
  /**
@@ -42039,18 +42148,19 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
42039
42148
  return [];
42040
42149
  }
42041
42150
 
42042
- // Traverse through all components in the scene data
42043
- sceneData.scene.children.forEach(function (component) {
42044
- // Each component may have connector children
42045
- if (component.children && Array.isArray(component.children)) {
42046
- component.children.forEach(function (child) {
42047
- // Check if this child is a connector
42048
- if (child.userData && child.userData.objectType === 'connector' && child.uuid) {
42049
- allConnectorIds.push(child.uuid);
42050
- }
42051
- });
42052
- }
42053
- });
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);
42054
42164
 
42055
42165
  // Get existing connections
42056
42166
  var existingConnections = this.getConnections();
@@ -42080,28 +42190,28 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
42080
42190
  * const infos = centralPlant.getAvailableConnectionsInfo()
42081
42191
  * // [{ id: 'PUMP-1-CONNECTOR-1', flow: 'out' }, ...]
42082
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
+ */
42083
42199
  }, {
42084
42200
  key: "getAvailableConnectionsInfo",
42085
42201
  value: function getAvailableConnectionsInfo() {
42086
- if (!this.sceneViewer || !this.sceneViewer.currentSceneData) {
42087
- console.warn('⚠️ getAvailableConnectionsInfo(): Scene viewer or current scene data not available');
42088
- return [];
42089
- }
42090
- var sceneData = this.sceneViewer.currentSceneData;
42091
- if (!sceneData.scene || !sceneData.scene.children) {
42092
- console.warn('⚠️ getAvailableConnectionsInfo(): Invalid scene data structure');
42202
+ if (!this.sceneViewer || !this.sceneViewer.scene) {
42203
+ console.warn('⚠️ getAvailableConnectionsInfo(): Scene viewer or scene not available');
42093
42204
  return [];
42094
42205
  }
42095
42206
  var allConnectorInfos = [];
42096
- sceneData.scene.children.forEach(function (component) {
42097
- if (component.children && Array.isArray(component.children)) {
42098
- component.children.forEach(function (child) {
42099
- if (child.userData && child.userData.objectType === 'connector' && child.uuid) {
42100
- allConnectorInfos.push({
42101
- id: child.uuid,
42102
- flow: child.userData.flow || 'bi'
42103
- });
42104
- }
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'
42105
42215
  });
42106
42216
  }
42107
42217
  });
@@ -42123,30 +42233,29 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
42123
42233
  * const result = centralPlant.validateConnections()
42124
42234
  * result.invalid.forEach(({ connection, reason }) => console.warn(reason, connection))
42125
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
+ */
42126
42242
  }, {
42127
42243
  key: "validateConnections",
42128
42244
  value: function validateConnections() {
42129
- if (!this.sceneViewer || !this.sceneViewer.currentSceneData) {
42130
- 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');
42131
42247
  return {
42132
42248
  valid: [],
42133
42249
  invalid: []
42134
42250
  };
42135
42251
  }
42136
42252
  var connections = this.getConnections();
42137
- var sceneData = this.sceneViewer.currentSceneData;
42138
42253
 
42139
- // Build lookup map: connectorId → flow
42254
+ // Build lookup map: connectorId → flow from the Three.js scene
42140
42255
  var flowMap = {};
42141
- var scene = sceneData.scene || {};
42142
- var children = scene.children || [];
42143
- children.forEach(function (component) {
42144
- if (component.children && Array.isArray(component.children)) {
42145
- component.children.forEach(function (child) {
42146
- if (child.userData && child.userData.objectType === 'connector' && child.uuid) {
42147
- flowMap[child.uuid] = child.userData.flow || 'bi';
42148
- }
42149
- });
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';
42150
42259
  }
42151
42260
  });
42152
42261
  var valid = [];
@@ -44114,7 +44223,7 @@ var sceneViewer = /*#__PURE__*/function (_BaseDisposable) {
44114
44223
  this.centralPlant.attachToComponent();
44115
44224
 
44116
44225
  // Sync our managers tracking object after attachment
44117
- 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
44118
44227
  managerKeys.forEach(function (key) {
44119
44228
  if (_this2[key]) {
44120
44229
  _this2.managers[key] = _this2[key];