@2112-lab/central-plant 0.3.47 → 0.3.49

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
@@ -12019,11 +12060,10 @@ var SceneExportManager = /*#__PURE__*/function () {
12019
12060
  };
12020
12061
  }
12021
12062
 
12022
- // For components: only export child connectors if they were manually added/defined.
12023
- // Most connectors are injected from dictionary at import time, but some (like manually placed ones)
12024
- // need to be persisted to maintain connections in the exported scene.
12063
+ // Only manual segments persist their connector children here.
12064
+ // Component connectors are NOT exported they are regenerated from the
12065
+ // component dictionary on load (see note below), keeping the scene JSON minimal.
12025
12066
  if (threeObject.children && threeObject.children.length > 0) {
12026
- var _threeObject$userData11;
12027
12067
  var exportableChildren = [];
12028
12068
  if (isManualSegment) {
12029
12069
  // For manual segments, export their connector children
@@ -12037,26 +12077,15 @@ var SceneExportManager = /*#__PURE__*/function () {
12037
12077
  }
12038
12078
  }
12039
12079
  });
12040
- } 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) {
12044
- var _child$userData2;
12045
- if (((_child$userData2 = child.userData) === null || _child$userData2 === void 0 ? void 0 : _child$userData2.objectType) === 'connector') {
12046
- exportableChildren.push({
12047
- uuid: child.uuid,
12048
- name: child.name,
12049
- type: 'Mesh',
12050
- position: {
12051
- x: roundIfClose(child.position.x),
12052
- y: roundIfClose(child.position.y),
12053
- z: roundIfClose(child.position.z)
12054
- },
12055
- userData: _objectSpread2({}, child.userData)
12056
- });
12057
- }
12058
- });
12059
12080
  }
12081
+ // NOTE: Component connectors are intentionally NOT exported.
12082
+ // They are defined in the component dictionary and regenerated on load by
12083
+ // sceneOperationsManager._injectConnectorChildrenFromDictionary using the
12084
+ // matching uuid scheme (`${componentUuid}_${dictConnectorUuid}`), and the
12085
+ // pathfinder rebuilds their world positions/bounding boxes on every run
12086
+ // (computeConnectorBoundingBoxes). Connections still resolve because the
12087
+ // regenerated connector uuids match the connection endpoints.
12088
+
12060
12089
  if (exportableChildren.length > 0) {
12061
12090
  jsonObject.children = exportableChildren;
12062
12091
  }
@@ -12070,9 +12099,9 @@ var SceneExportManager = /*#__PURE__*/function () {
12070
12099
  // Extract main scene objects (components and standalone connectors)
12071
12100
  var sceneChildren = [];
12072
12101
  this.sceneViewer.scene.children.forEach(function (child) {
12073
- var _child$userData3;
12102
+ var _child$userData2;
12074
12103
  // Only export components and connectors; skip segments, gateways, polylines, etc.
12075
- var objectType = (_child$userData3 = child.userData) === null || _child$userData3 === void 0 ? void 0 : _child$userData3.objectType;
12104
+ var objectType = (_child$userData2 = child.userData) === null || _child$userData2 === void 0 ? void 0 : _child$userData2.objectType;
12076
12105
  if (objectType !== 'component' && objectType !== 'connector') {
12077
12106
  return;
12078
12107
  }
@@ -12236,14 +12265,14 @@ var SceneExportManager = /*#__PURE__*/function () {
12236
12265
  BufferGeometryUtils$1 = BufferGeometryUtilsModule.BufferGeometryUtils || BufferGeometryUtilsModule.default || BufferGeometryUtilsModule; // Create a new scene for export instead of cloning
12237
12266
  exportScene = new _THREE.Scene(); // Helper function to check if an object should be exported
12238
12267
  shouldExport = function shouldExport(child) {
12239
- var _child$name, _child$userData4, _child$userData5, _child$userData6, _child$userData7;
12268
+ var _child$name, _child$userData3, _child$userData4, _child$userData5, _child$userData6;
12240
12269
  if ((_child$name = child.name) !== null && _child$name !== void 0 && _child$name.includes('Polyline')) return false; // Will handle separately
12241
12270
  if (child.name === 'fogPlane') return false; // Skip fog plane
12242
- if ((_child$userData4 = child.userData) !== null && _child$userData4 !== void 0 && _child$userData4.isBrickWall) return false; // Skip environment
12243
- if ((_child$userData5 = child.userData) !== null && _child$userData5 !== void 0 && _child$userData5.isBaseGround) return false; // Skip environment
12244
- if ((_child$userData6 = child.userData) !== null && _child$userData6 !== void 0 && _child$userData6.isBaseGrid) return false; // Skip environment
12271
+ if ((_child$userData3 = child.userData) !== null && _child$userData3 !== void 0 && _child$userData3.isBrickWall) return false; // Skip environment
12272
+ if ((_child$userData4 = child.userData) !== null && _child$userData4 !== void 0 && _child$userData4.isBaseGround) return false; // Skip environment
12273
+ if ((_child$userData5 = child.userData) !== null && _child$userData5 !== void 0 && _child$userData5.isBaseGrid) return false; // Skip environment
12245
12274
  if (child.isLight) return false; // Skip lights
12246
- if ((_child$userData7 = child.userData) !== null && _child$userData7 !== void 0 && _child$userData7.isTransformControls) return false; // Skip transform controls
12275
+ if ((_child$userData6 = child.userData) !== null && _child$userData6 !== void 0 && _child$userData6.isTransformControls) return false; // Skip transform controls
12247
12276
  if (child.isTransformControls) return false; // Skip transform controls
12248
12277
  if (child.type && child.type.includes('TransformControls')) return false;
12249
12278
  if (child.type && child.type.includes('Helper')) return false; // Skip helpers
@@ -19901,7 +19930,7 @@ var ComponentManager = /*#__PURE__*/function () {
19901
19930
  key: "addComponentToScene",
19902
19931
  value: (function () {
19903
19932
  var _addComponentToScene = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(componentData) {
19904
- var _gltfScene$userData, uuid, componentDictionary, libraryComponent, position, gltfScene, componentMesh, event, _t, _t2;
19933
+ var _gltfScene$userData, uuid, componentDictionary, libraryComponent, position, gltfScene, componentMesh, connectorIndex, event, _t, _t2;
19905
19934
  return _regenerator().w(function (_context) {
19906
19935
  while (1) switch (_context.n) {
19907
19936
  case 0:
@@ -20005,6 +20034,7 @@ var ComponentManager = /*#__PURE__*/function () {
20005
20034
  }
20006
20035
 
20007
20036
  // Enable shadows for all meshes in the component
20037
+ connectorIndex = 0;
20008
20038
  componentMesh.traverse(function (child) {
20009
20039
  if (child.isMesh) {
20010
20040
  child.castShadow = true;
@@ -20014,12 +20044,19 @@ var ComponentManager = /*#__PURE__*/function () {
20014
20044
  }
20015
20045
 
20016
20046
  // Set connector properties for connectors within the component
20017
- if (child.name && child.name.toLowerCase().includes('connector')) {
20047
+ if (child.name && child.name.toLowerCase().includes('connector') || child.userData && child.userData.objectType === 'connector') {
20048
+ connectorIndex++;
20018
20049
  if (!child.userData) {
20019
20050
  child.userData = {};
20020
20051
  }
20021
20052
  child.userData.objectType = 'connector';
20022
- console.log("\uD83D\uDD27 Set objectType='connector' for: ".concat(child.name));
20053
+
20054
+ // Assign a predictable, stable UUID to the connector
20055
+ // This ensures connections made in the sandbox survive export/import
20056
+ var connectorUuid = "".concat(uuid, "-CONNECTOR-").concat(connectorIndex);
20057
+ child.uuid = connectorUuid;
20058
+ child.userData.originalUuid = connectorUuid;
20059
+ console.log("\uD83D\uDD27 Set predictable connector UUID: ".concat(connectorUuid, " for: ").concat(child.name));
20023
20060
  }
20024
20061
  }
20025
20062
  });
@@ -21214,12 +21251,14 @@ var TransformOperationsManager = /*#__PURE__*/function () {
21214
21251
  * @param {string} componentId - The UUID of the component to translate
21215
21252
  * @param {string} axis - The axis to translate on ('x', 'y', or 'z')
21216
21253
  * @param {number} value - The value to translate by
21254
+ * @param {boolean} [skipPathUpdate=false] - Whether to skip triggering pathfinding after translation
21217
21255
  * @returns {boolean} True if translation was successful, false otherwise
21218
21256
  */
21219
21257
  return _createClass(TransformOperationsManager, [{
21220
21258
  key: "translateComponent",
21221
21259
  value: function translateComponent(componentId, axis, value) {
21222
21260
  var _this$sceneViewer$man, _this$sceneViewer;
21261
+ var skipPathUpdate = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
21223
21262
  // Store transform parameters using the OperationHistoryManager
21224
21263
  if ((_this$sceneViewer$man = this.sceneViewer.managers) !== null && _this$sceneViewer$man !== void 0 && _this$sceneViewer$man.operationHistoryManager) {
21225
21264
  this.sceneViewer.managers.operationHistoryManager.addToOperationHistory('translateComponent', {
@@ -21287,7 +21326,7 @@ var TransformOperationsManager = /*#__PURE__*/function () {
21287
21326
  }
21288
21327
 
21289
21328
  // Auto-update paths if enabled (matches behavior of translateSegment and translateGateway)
21290
- if (this.sceneViewer.shouldUpdatePaths) {
21329
+ if (!skipPathUpdate && this.sceneViewer.shouldUpdatePaths) {
21291
21330
  try {
21292
21331
  if (this.sceneViewer && typeof this.sceneViewer.updatePaths === 'function') {
21293
21332
  this.sceneViewer.updatePaths();
@@ -21298,6 +21337,8 @@ var TransformOperationsManager = /*#__PURE__*/function () {
21298
21337
  } catch (error) {
21299
21338
  console.error('❌ Error auto-updating paths:', error);
21300
21339
  }
21340
+ } else if (skipPathUpdate) {
21341
+ console.log('ℹ️ skipPathUpdate is true: skipping automatic path update');
21301
21342
  }
21302
21343
 
21303
21344
  // Emit transform event if available
@@ -23116,11 +23157,8 @@ var TransformOperationsManager = /*#__PURE__*/function () {
23116
23157
  sceneDataComponent.children.forEach(function (child) {
23117
23158
  var _child$userData21;
23118
23159
  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
- });
23160
+ // Find the actual connector object in the scene (recursive search for nested connectors)
23161
+ var connectorObj = component.getObjectByProperty('uuid', child.uuid) || component.getObjectByProperty('name', child.uuid);
23124
23162
  if (connectorObj) {
23125
23163
  // Get world position of connector
23126
23164
  var worldPos = new THREE__namespace.Vector3();
@@ -23179,11 +23217,8 @@ var TransformOperationsManager = /*#__PURE__*/function () {
23179
23217
  sceneDataComponent.children.forEach(function (child) {
23180
23218
  var _child$userData22;
23181
23219
  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
- });
23220
+ // Find the actual connector object in the scene (recursive search for nested connectors)
23221
+ var connectorObj = component.getObjectByProperty('uuid', child.uuid) || component.getObjectByProperty('name', child.uuid);
23187
23222
  if (connectorObj) {
23188
23223
  var _connectorObj$userDat;
23189
23224
  // Get world position of connector
@@ -29109,10 +29144,11 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
29109
29144
  }, {
29110
29145
  key: "_matrixHash",
29111
29146
  value: function _matrixHash(obj) {
29147
+ // Force matrix update to ensure we're looking at the latest data
29148
+ obj.updateMatrixWorld(true);
29112
29149
  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));
29150
+ // Just hash the position/rotation/scale part of the matrix (12 elements)
29151
+ 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
29152
  }
29117
29153
 
29118
29154
  /**
@@ -29130,11 +29166,7 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
29130
29166
  * Compute a lightweight fingerprint of the pathfinding inputs so that
29131
29167
  * consecutive identical runs can be skipped entirely.
29132
29168
  *
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
29169
+ * @param {Object} sceneData - scene object or manifest
29138
29170
  * @param {Array} connections - array of {from, to}
29139
29171
  * @returns {string}
29140
29172
  * @private
@@ -29142,277 +29174,241 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
29142
29174
  }, {
29143
29175
  key: "_computePathfindingFingerprint",
29144
29176
  value: function _computePathfindingFingerprint(sceneData, connections) {
29177
+ var _this3 = this;
29145
29178
  var parts = [];
29146
29179
 
29147
29180
  // 1. Connections (sorted so order doesn't matter)
29148
- parts.push(connections.map(function (c) {
29181
+ parts.push((connections || []).map(function (c) {
29149
29182
  return "".concat(c.from, "->").concat(c.to);
29150
29183
  }).sort().join(','));
29151
29184
 
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
- }
29185
+ // 2. World transforms of every referenced scene object (recursive)
29186
+ var _addTransformsRecursive = function addTransformsRecursive(node) {
29187
+ if (node.uuid) {
29188
+ var obj = _this3.sceneViewer.scene.getObjectByProperty('uuid', node.uuid);
29189
+ if (obj) {
29190
+ parts.push("".concat(node.uuid, ":").concat(_this3._matrixHash(obj)));
29191
+ } else {
29192
+ var _p$x, _p$y, _p$z;
29193
+ // Object only in data, not in scene — include its serialised position
29194
+ var p = node.position || {};
29195
+ 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
29196
  }
29169
- } catch (err) {
29170
- _iterator.e(err);
29171
- } finally {
29172
- _iterator.f();
29173
29197
  }
29198
+
29199
+ // Handle both flattened array and nested Three.js JSON structures
29200
+ var children = node.children || node.object && node.object.children;
29201
+ if (children && Array.isArray(children)) {
29202
+ children.forEach(_addTransformsRecursive);
29203
+ }
29204
+ };
29205
+ if (sceneData) {
29206
+ _addTransformsRecursive(sceneData);
29174
29207
  }
29175
29208
  return parts.join('|');
29176
29209
  }
29177
29210
 
29178
29211
  /**
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)
29212
+ * Enrich scene data with world-space bounding boxes and positions for collision avoidance
29213
+ * @param {Object|Array} sceneData - Original scene data or array of children
29214
+ * @returns {Object|Array} Enriched scene data (shallow copy with modifications)
29187
29215
  * @private
29188
29216
  */
29189
29217
  }, {
29190
29218
  key: "_enrichSceneDataWithBoundingBoxes",
29191
29219
  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);
29220
+ var _this4 = this;
29221
+ if (!sceneData) return sceneData;
29222
+
29223
+ // Recursive helper to enrich a node and its children
29224
+ var _enrichNode = function enrichNode(node) {
29225
+ if (!node) return node;
29226
+ var enrichedNode = _objectSpread2({}, node);
29227
+
29228
+ // Skip computed objects ( elbows etc )
29229
+ if (node.userData && (node.userData.isComputed === true || node.userData.objectType === 'elbow')) {
29230
+ return node;
29231
+ }
29232
+
29233
+ // ── Enrich Segments ──
29234
+ if (node.userData && node.userData.objectType === 'segment') {
29235
+ var segmentObject = _this4.sceneViewer.scene.getObjectByProperty('uuid', node.uuid);
29206
29236
  if (segmentObject) {
29207
- // ── Cache check ──
29208
- var hash = _this3._matrixHash(segmentObject);
29209
- var cached = _this3._bboxCache.get(child.uuid);
29237
+ var hash = _this4._matrixHash(segmentObject);
29238
+ var cached = _this4._bboxCache.get(node.uuid);
29210
29239
  if (cached && cached.matrixHash === hash && cached.segmentBBox) {
29211
- return _objectSpread2(_objectSpread2({}, child), {}, {
29212
- userData: _objectSpread2(_objectSpread2({}, child.userData), {}, {
29213
- worldBoundingBox: cached.segmentBBox
29214
- })
29240
+ enrichedNode.userData = _objectSpread2(_objectSpread2({}, node.userData), {}, {
29241
+ worldBoundingBox: cached.segmentBBox
29215
29242
  });
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), {}, {
29243
+ } else {
29244
+ var worldBBox = new THREE__namespace.Box3().setFromObject(segmentObject);
29245
+ var bboxData = {
29246
+ min: [worldBBox.min.x, worldBBox.min.y, worldBBox.min.z],
29247
+ max: [worldBBox.max.x, worldBBox.max.y, worldBBox.max.z]
29248
+ };
29249
+ _this4._bboxCache.set(node.uuid, {
29250
+ matrixHash: hash,
29251
+ segmentBBox: bboxData
29252
+ });
29253
+ enrichedNode.userData = _objectSpread2(_objectSpread2({}, node.userData), {}, {
29232
29254
  worldBoundingBox: bboxData
29233
- })
29234
- });
29235
- } else {
29236
- console.warn("\u26A0\uFE0F Could not find segment object in scene: ".concat(child.uuid));
29255
+ });
29256
+ }
29237
29257
  }
29238
29258
  }
29239
29259
 
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);
29260
+ // ── Enrich Components ──
29261
+ else if (node.userData && node.userData.objectType === 'component') {
29262
+ var componentObject = _this4.sceneViewer.scene.getObjectByProperty('uuid', node.uuid);
29244
29263
  if (componentObject) {
29245
- // ── Cache check ──
29246
- var _hash = _this3._matrixHash(componentObject);
29247
- var _cached = _this3._bboxCache.get(child.uuid);
29264
+ componentObject.updateMatrixWorld(true);
29265
+ var _hash = _this4._matrixHash(componentObject);
29266
+ var _cached = _this4._bboxCache.get(node.uuid);
29248
29267
  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
- })
29268
+ enrichedNode.position = {
29269
+ x: componentObject.position.x,
29270
+ y: componentObject.position.y,
29271
+ z: componentObject.position.z
29272
+ };
29273
+ enrichedNode.rotation = {
29274
+ x: THREE__namespace.MathUtils.radToDeg(componentObject.rotation.x),
29275
+ y: THREE__namespace.MathUtils.radToDeg(componentObject.rotation.y),
29276
+ z: THREE__namespace.MathUtils.radToDeg(componentObject.rotation.z)
29277
+ };
29278
+ enrichedNode.userData = _objectSpread2(_objectSpread2({}, node.userData), {}, {
29279
+ worldBoundingBox: _cached.filteredBBox,
29280
+ position: [componentObject.position.x, componentObject.position.y, componentObject.position.z]
29254
29281
  });
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
- }
29282
+
29283
+ // Re-merge cached connectors and devices if they exist
29284
+ if (!enrichedNode.children) enrichedNode.children = [];
29285
+ if (_cached.connectorBBoxes) {
29286
+ _cached.connectorBBoxes.forEach(function (conn) {
29287
+ return _this4._mergeEnrichedChild(enrichedNode.children, conn);
29277
29288
  });
29278
29289
  }
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
- }
29290
+ if (_cached.ioDeviceBBoxes) {
29291
+ _cached.ioDeviceBBoxes.forEach(function (dev) {
29292
+ return _this4._mergeEnrichedChild(enrichedNode.children, dev);
29302
29293
  });
29303
29294
  }
29304
- return _enrichedChild;
29295
+ } else {
29296
+ var filteredBBox = computeFilteredBoundingBox(componentObject, ['io-device', 'connector']);
29297
+ var _bboxData = {
29298
+ min: [filteredBBox.min.x, filteredBBox.min.y, filteredBBox.min.z],
29299
+ max: [filteredBBox.max.x, filteredBBox.max.y, filteredBBox.max.z]
29300
+ };
29301
+ enrichedNode.position = {
29302
+ x: componentObject.position.x,
29303
+ y: componentObject.position.y,
29304
+ z: componentObject.position.z
29305
+ };
29306
+ enrichedNode.rotation = {
29307
+ x: THREE__namespace.MathUtils.radToDeg(componentObject.rotation.x),
29308
+ y: THREE__namespace.MathUtils.radToDeg(componentObject.rotation.y),
29309
+ z: THREE__namespace.MathUtils.radToDeg(componentObject.rotation.z)
29310
+ };
29311
+ enrichedNode.userData = _objectSpread2(_objectSpread2({}, node.userData), {}, {
29312
+ worldBoundingBox: _bboxData,
29313
+ position: [componentObject.position.x, componentObject.position.y, componentObject.position.z]
29314
+ });
29315
+ var connectorBBoxes = computeConnectorBoundingBoxes(componentObject);
29316
+ var ioDeviceBBoxes = computeIODeviceBoundingBoxes(componentObject);
29317
+ if (!enrichedNode.children) enrichedNode.children = [];
29318
+ connectorBBoxes.forEach(function (conn) {
29319
+ return _this4._mergeEnrichedChild(enrichedNode.children, conn);
29320
+ });
29321
+ ioDeviceBBoxes.forEach(function (dev) {
29322
+ return _this4._mergeEnrichedChild(enrichedNode.children, dev);
29323
+ });
29324
+ _this4._bboxCache.set(node.uuid, {
29325
+ matrixHash: _hash,
29326
+ filteredBBox: _bboxData,
29327
+ ioDeviceBBoxes: ioDeviceBBoxes,
29328
+ connectorBBoxes: connectorBBoxes
29329
+ });
29305
29330
  }
29331
+ }
29332
+ }
29306
29333
 
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]
29334
+ // ── Enrich Standalone Objects (Gateways/Connectors) ──
29335
+ else if (node.userData && (node.userData.objectType === 'gateway' || node.userData.objectType === 'connector')) {
29336
+ var _node$userData;
29337
+ var object = _this4.sceneViewer.scene.getObjectByProperty('uuid', node.uuid) || _this4.sceneViewer.scene.getObjectByProperty('uuid', (_node$userData = node.userData) === null || _node$userData === void 0 ? void 0 : _node$userData.originalUuid);
29338
+ if (object) {
29339
+ var worldPos = new THREE__namespace.Vector3();
29340
+ object.getWorldPosition(worldPos);
29341
+ enrichedNode.position = {
29342
+ x: worldPos.x,
29343
+ y: worldPos.y,
29344
+ z: worldPos.z
29314
29345
  };
29315
-
29316
- // Build the enriched component entry
29317
- var enrichedChild = _objectSpread2(_objectSpread2({}, child), {}, {
29318
- userData: _objectSpread2(_objectSpread2({}, child.userData), {}, {
29319
- worldBoundingBox: _bboxData
29320
- })
29346
+ enrichedNode.userData = _objectSpread2(_objectSpread2({}, node.userData), {}, {
29347
+ position: [worldPos.x, worldPos.y, worldPos.z]
29321
29348
  });
29322
29349
 
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));
29350
+ // If it's a connector, also sync direction
29351
+ if (node.userData.objectType === 'connector') {
29352
+ var worldDir = new THREE__namespace.Vector3(0, 0, 1);
29353
+ if (node.userData.direction) worldDir.set(node.userData.direction[0], node.userData.direction[1], node.userData.direction[2]);
29354
+ worldDir.applyQuaternion(object.getWorldQuaternion(new THREE__namespace.Quaternion())).normalize();
29355
+ enrichedNode.userData.direction = [worldDir.x, worldDir.y, worldDir.z];
29359
29356
  }
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
- });
29387
- }
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
29357
  }
29400
29358
  }
29401
29359
 
29402
- // For non-segments and non-components (including connectors), return as-is
29403
- return child;
29404
- });
29405
- return enriched;
29360
+ // Recurse into children
29361
+ if (node.children && Array.isArray(node.children)) {
29362
+ enrichedNode.children = node.children.map(_enrichNode);
29363
+ }
29364
+ return enrichedNode;
29365
+ };
29366
+
29367
+ // Handle root being an array or object
29368
+ if (Array.isArray(sceneData)) {
29369
+ return sceneData.map(_enrichNode);
29370
+ } else if (sceneData.children && Array.isArray(sceneData.children)) {
29371
+ var enrichedRoot = _objectSpread2({}, sceneData);
29372
+ enrichedRoot.children = sceneData.children.map(_enrichNode);
29373
+ return enrichedRoot;
29374
+ } else {
29375
+ return _enrichNode(sceneData);
29376
+ }
29406
29377
  }
29407
29378
 
29408
29379
  /**
29409
- * Core pathfinding logic shared across initialization and updates
29380
+ * Helper to merge an enriched child (connector/device) into a children array
29381
+ * @private
29410
29382
  */
29383
+ }, {
29384
+ key: "_mergeEnrichedChild",
29385
+ value: function _mergeEnrichedChild(childrenArray, enrichedData) {
29386
+ var existingIndex = childrenArray.findIndex(function (c) {
29387
+ return c.uuid === enrichedData.uuid;
29388
+ });
29389
+ var nodeToMerge = {
29390
+ uuid: enrichedData.uuid,
29391
+ position: {
29392
+ x: enrichedData.userData.position[0],
29393
+ y: enrichedData.userData.position[1],
29394
+ z: enrichedData.userData.position[2]
29395
+ },
29396
+ userData: _objectSpread2(_objectSpread2({}, enrichedData.userData), {}, {
29397
+ worldBoundingBox: enrichedData.worldBoundingBox
29398
+ }),
29399
+ children: []
29400
+ };
29401
+ if (existingIndex >= 0) {
29402
+ childrenArray[existingIndex] = _objectSpread2(_objectSpread2({}, childrenArray[existingIndex]), nodeToMerge);
29403
+ } else {
29404
+ childrenArray.push(nodeToMerge);
29405
+ }
29406
+ }
29411
29407
  }, {
29412
29408
  key: "_executePathfinding",
29413
- value: (function () {
29409
+ value: function () {
29414
29410
  var _executePathfinding2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(sceneData, connections) {
29415
- var _this4 = this,
29411
+ var _this5 = this,
29416
29412
  _sceneDataCopy$childr,
29417
29413
  _sceneDataCopy$childr2,
29418
29414
  _pathfindingResult$pa,
@@ -29424,6 +29420,12 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
29424
29420
  totalStart,
29425
29421
  inputGenStart,
29426
29422
  enrichedSceneData,
29423
+ _from$userData,
29424
+ _to$userData,
29425
+ firstConn,
29426
+ findEndpoint,
29427
+ from,
29428
+ to,
29427
29429
  connectionsCopy,
29428
29430
  sceneDataCopy,
29429
29431
  algoStart,
@@ -29440,6 +29442,10 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
29440
29442
  while (1) switch (_context.n) {
29441
29443
  case 0:
29442
29444
  options = _args.length > 2 && _args[2] !== undefined ? _args[2] : {};
29445
+ // Fallback to sceneData connections if not explicitly provided (e.g. during transform updates)
29446
+ if (!connections && sceneData && sceneData.connections) {
29447
+ connections = sceneData.connections;
29448
+ }
29443
29449
  options.createGateways, _options$context = options.context, context = _options$context === void 0 ? 'Pathfinding' : _options$context;
29444
29450
  timers = {};
29445
29451
  totalStart = performance.now(); // Create pathfinder instance with configuration only
@@ -29468,7 +29474,50 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
29468
29474
  console.log("\uD83D\uDD04 Updated matrix world for ".concat(context, " pathfinding execution"));
29469
29475
 
29470
29476
  // 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
29477
+ enrichedSceneData = this._enrichSceneDataWithBoundingBoxes(sceneData); // DEBUG: Log first connection endpoints in enriched data
29478
+ if (connections && connections.length > 0 && enrichedSceneData.children) {
29479
+ firstConn = connections[0];
29480
+ findEndpoint = function findEndpoint(uuid) {
29481
+ var _iterator = _createForOfIteratorHelper(enrichedSceneData.children),
29482
+ _step;
29483
+ try {
29484
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
29485
+ var child = _step.value;
29486
+ if (child.uuid === uuid) return child;
29487
+ if (child.children) {
29488
+ var _iterator2 = _createForOfIteratorHelper(child.children),
29489
+ _step2;
29490
+ try {
29491
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
29492
+ var nested = _step2.value;
29493
+ if (nested.uuid === uuid) return nested;
29494
+ }
29495
+ } catch (err) {
29496
+ _iterator2.e(err);
29497
+ } finally {
29498
+ _iterator2.f();
29499
+ }
29500
+ }
29501
+ }
29502
+ } catch (err) {
29503
+ _iterator.e(err);
29504
+ } finally {
29505
+ _iterator.f();
29506
+ }
29507
+ return null;
29508
+ };
29509
+ from = findEndpoint(firstConn.from);
29510
+ to = findEndpoint(firstConn.to);
29511
+ console.log("\uD83D\uDCE1 [DEBUG] Pathfinding from ".concat(firstConn.from, " to ").concat(firstConn.to));
29512
+ 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) {
29513
+ return v.toFixed(2);
29514
+ }), "]"));
29515
+ 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) {
29516
+ return v.toFixed(2);
29517
+ }), "]"));
29518
+ }
29519
+
29520
+ // Deep copy connections and sceneData to prevent pathfinder from mutating the original
29472
29521
  connectionsCopy = structuredClone(connections);
29473
29522
  sceneDataCopy = structuredClone(enrichedSceneData);
29474
29523
  timers.inputGeneration = performance.now() - inputGenStart;
@@ -29499,7 +29548,7 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
29499
29548
  if (pathfindingResult.paths) {
29500
29549
  pathfindingResult.paths.forEach(function (path) {
29501
29550
  var pathId = "".concat(path.from, "-->").concat(path.to);
29502
- _this4._getOrCreatePathData(pathId, path.from, path.to);
29551
+ _this5._getOrCreatePathData(pathId, path.from, path.to);
29503
29552
  });
29504
29553
  }
29505
29554
  timers.pathRendering = performance.now() - renderStart;
@@ -29532,7 +29581,7 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
29532
29581
  return _executePathfinding2.apply(this, arguments);
29533
29582
  }
29534
29583
  return _executePathfinding;
29535
- }())
29584
+ }()
29536
29585
  }, {
29537
29586
  key: "getSimplifiedSceneData",
29538
29587
  value: function getSimplifiedSceneData() {
@@ -29571,7 +29620,7 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
29571
29620
  }, {
29572
29621
  key: "_propagateFlowAttributesAndApplyVisualizations",
29573
29622
  value: function _propagateFlowAttributesAndApplyVisualizations(connections, pathfindingResult) {
29574
- var _this5 = this,
29623
+ var _this6 = this,
29575
29624
  _this$sceneViewer;
29576
29625
  if (!Array.isArray(connections) || !pathfindingResult) return;
29577
29626
 
@@ -29599,7 +29648,7 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
29599
29648
  var subPathIds = new Set(originalToSubPaths.get(origId) || []);
29600
29649
  subPathIds.add(origId);
29601
29650
  subPathIds.forEach(function (subPathId) {
29602
- var pd = _this5.pathDataStore.get(subPathId);
29651
+ var pd = _this6.pathDataStore.get(subPathId);
29603
29652
  if (pd && Object.keys(pd.flowAttributes).length === 0) {
29604
29653
  Object.entries(conn.flowAttributes).forEach(function (_ref2) {
29605
29654
  var _ref3 = _slicedToArray(_ref2, 2),
@@ -29663,7 +29712,7 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
29663
29712
  key: "initializePathfinder",
29664
29713
  value: (function () {
29665
29714
  var _initializePathfinder = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2(data, crosscubeTextureSet) {
29666
- var _this6 = this;
29715
+ var _this7 = this;
29667
29716
  var pathfindingResult;
29668
29717
  return _regenerator().w(function (_context2) {
29669
29718
  while (1) switch (_context2.n) {
@@ -29677,7 +29726,7 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
29677
29726
  data.connections.forEach(function (conn) {
29678
29727
  if (conn.flowAttributes && _typeof(conn.flowAttributes) === 'object') {
29679
29728
  var pathId = "".concat(conn.from, "-->").concat(conn.to);
29680
- var pd = _this6._getOrCreatePathData(pathId, conn.from, conn.to);
29729
+ var pd = _this7._getOrCreatePathData(pathId, conn.from, conn.to);
29681
29730
  Object.entries(conn.flowAttributes).forEach(function (_ref6) {
29682
29731
  var _ref7 = _slicedToArray(_ref6, 2),
29683
29732
  key = _ref7[0],
@@ -29795,33 +29844,47 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
29795
29844
  key: "updatePathfindingAfterTransform",
29796
29845
  value: (function () {
29797
29846
  var _updatePathfindingAfterTransform = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee3(currentSceneData) {
29798
- var fingerprint, result;
29847
+ var _currentSceneData$sce;
29848
+ var fingerprint, dataRoot, connections, result;
29799
29849
  return _regenerator().w(function (_context3) {
29800
29850
  while (1) switch (_context3.n) {
29801
29851
  case 0:
29852
+ if (!(!currentSceneData || !currentSceneData.scene)) {
29853
+ _context3.n = 1;
29854
+ break;
29855
+ }
29856
+ console.warn('⚠️ updatePathfindingAfterTransform: Invalid scene data');
29857
+ return _context3.a(2, null);
29858
+ case 1:
29802
29859
  // ── Skip-unchanged-paths check ──────────────────────────────────
29803
29860
  // Ensure matrices are fresh so the fingerprint is accurate.
29804
29861
  this.sceneViewer.scene.updateMatrixWorld(true);
29805
29862
  fingerprint = this._computePathfindingFingerprint(currentSceneData.scene, currentSceneData.connections);
29863
+ console.log("\uD83D\uDD0D Pathfinding update check. Fingerprint: ".concat(fingerprint.substring(0, 50), "..."));
29806
29864
  if (!(fingerprint === this._lastPathfindingFingerprint && this._lastPathfindingResult)) {
29807
- _context3.n = 1;
29865
+ _context3.n = 2;
29808
29866
  break;
29809
29867
  }
29810
29868
  console.log('⏭️ Pathfinding skipped — inputs unchanged since last run');
29811
29869
  return _context3.a(2, this._lastPathfindingResult);
29812
- case 1:
29870
+ case 2:
29871
+ // Clear the cache for the specific fingerprint mismatch to ensure fresh enrichment
29872
+ this.invalidateBBoxCache();
29873
+
29813
29874
  // Remove all existing paths from the scene
29814
29875
  this.removeComputedObjects();
29815
29876
  console.log('🔄 Starting pathfinding with updated scene data...');
29816
29877
 
29817
- // Note: Matrix updates and bounding box recomputation now handled in _executePathfinding
29878
+ // Robust root discovery for different manifest structures
29879
+ dataRoot = ((_currentSceneData$sce = currentSceneData.scene) === null || _currentSceneData$sce === void 0 ? void 0 : _currentSceneData$sce.object) || currentSceneData.scene || currentSceneData;
29880
+ connections = currentSceneData.connections || []; // Note: Matrix updates and bounding box recomputation now handled in _executePathfinding
29818
29881
  // Use shared pathfinding logic (no gateways needed for transform updates)
29819
- _context3.n = 2;
29820
- return this._executePathfinding(currentSceneData.scene, currentSceneData.connections, {
29882
+ _context3.n = 3;
29883
+ return this._executePathfinding(dataRoot, connections, {
29821
29884
  createGateways: true,
29822
29885
  context: 'Transform Update'
29823
29886
  });
29824
- case 2:
29887
+ case 3:
29825
29888
  result = _context3.v;
29826
29889
  // Re-apply flow attribute colors (new materials are created on each execution)
29827
29890
  this._propagateFlowAttributesAndApplyVisualizations(currentSceneData.connections, result);
@@ -31727,36 +31790,58 @@ var ModelManager = /*#__PURE__*/function () {
31727
31790
  * @param {THREE.Object3D} targetMesh - The mesh whose connectors to preserve
31728
31791
  * @param {string} parentUuid - The UUID of the parent component
31729
31792
  */
31793
+ /**
31794
+ * Identifies all connector objects within a model hierarchy and prepares them
31795
+ * to be preserved when the model is replaced or modified.
31796
+ * Uses traverse() to find connectors at any depth in the hierarchy.
31797
+ * @param {THREE.Object3D} targetMesh - The root of the model to search
31798
+ * @param {string} parentUuid - The UUID of the parent component
31799
+ * @returns {Array<THREE.Object3D>} Array of cloned and prepared connector objects
31800
+ */
31730
31801
  }, {
31731
31802
  key: "_preserveConnectorChildren",
31732
31803
  value: function _preserveConnectorChildren(targetMesh, parentUuid) {
31733
31804
  var _this = this;
31734
31805
  var connectorChildren = [];
31735
- targetMesh.children.forEach(function (child, index) {
31806
+ var connectorIndex = 0;
31807
+
31808
+ // Use traverse to find connectors at ANY depth in the hierarchy
31809
+ targetMesh.traverse(function (child) {
31736
31810
  var _child$userData;
31737
31811
  var isConnectorGeometry = child.geometry && (child.geometry.uuid === 'CONNECTOR-GEO' || child.geometry.type === 'SphereGeometry' && child.geometry.parameters);
31738
31812
  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
31813
  var isConnectorByUserData = ((_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.objectType) === 'connector';
31742
31814
  if (isConnectorGeometry && isConnectorByName || isConnectorByUserData) {
31815
+ connectorIndex++;
31816
+
31743
31817
  // Ensure userData exists and has worldBoundingBox
31744
31818
  if (!child.userData) child.userData = {};
31745
31819
  if (!child.userData.worldBoundingBox) {
31746
31820
  child.userData.worldBoundingBox = _this._calculateWorldBoundingBox(child);
31747
31821
  }
31748
31822
 
31749
- // Clone and preserve critical userData
31823
+ // Clone the connector
31750
31824
  var clonedConnector = child.clone();
31751
31825
 
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);
31826
+ // CRITICAL: Convert nested world position to root-relative position
31827
+ // This ensures the connector stays in the same place when re-added as a direct child of targetMesh
31828
+ var targetInverse = targetMesh.matrixWorld.clone().invert();
31829
+ var worldPos = new THREE__namespace.Vector3();
31830
+ child.getWorldPosition(worldPos);
31831
+ clonedConnector.position.copy(worldPos.applyMatrix4(targetInverse));
31832
+
31833
+ // Get hardcoded UUID if it exists (e.g. from JSON mesh placeholder)
31834
+ // This ensures compatibility with existing scene JSON naming conventions
31835
+ var existingUuid = getHardcodedUuid(child);
31836
+
31837
+ // Generate fallback UUID only if existing one isn't already parent-prefixed
31838
+ // Format: COMPONENT-UUID-CONNECTOR-INDEX (stable fallback)
31839
+ var connectorUuid = existingUuid && existingUuid.includes(parentUuid) ? existingUuid : "".concat(parentUuid, "-CONNECTOR-").concat(connectorIndex);
31755
31840
  clonedConnector.uuid = connectorUuid;
31756
31841
  if (child.userData) {
31757
31842
  clonedConnector.userData = _objectSpread2({}, child.userData);
31758
31843
 
31759
- // Deep copy critical data - handle both array [x,y,z] and object {x,y,z} formats
31844
+ // Deep copy critical data
31760
31845
  if (child.userData.worldBoundingBox) {
31761
31846
  var wbb = child.userData.worldBoundingBox;
31762
31847
  clonedConnector.userData.worldBoundingBox = {
@@ -31765,7 +31850,6 @@ var ModelManager = /*#__PURE__*/function () {
31765
31850
  };
31766
31851
  }
31767
31852
  if (child.userData.direction) {
31768
- // Handle both array [x,y,z] and object {x,y,z} formats
31769
31853
  if (Array.isArray(child.userData.direction)) {
31770
31854
  clonedConnector.userData.direction = _toConsumableArray(child.userData.direction);
31771
31855
  } else if (_typeof(child.userData.direction) === 'object') {
@@ -31777,11 +31861,12 @@ var ModelManager = /*#__PURE__*/function () {
31777
31861
 
31778
31862
  // Set originalUuid to match the actual uuid (maintains consistency)
31779
31863
  clonedConnector.userData.originalUuid = connectorUuid;
31780
- clonedConnector.userData.objectType = child.userData.objectType || (child.name.toLowerCase().includes('connector') ? 'connector' : 'gateway');
31864
+ clonedConnector.userData.objectType = child.userData.objectType || 'connector';
31781
31865
  }
31782
31866
  connectorChildren.push(clonedConnector);
31783
31867
  }
31784
31868
  });
31869
+ console.log("\uD83D\uDEE1\uFE0F Preserved ".concat(connectorChildren.length, " connector(s) for component ").concat(parentUuid));
31785
31870
  return connectorChildren;
31786
31871
  }
31787
31872
 
@@ -33754,6 +33839,19 @@ var SceneOperationsManager = /*#__PURE__*/function () {
33754
33839
  if (!(data !== null && data !== void 0 && (_data$scene2 = data.scene) !== null && _data$scene2 !== void 0 && _data$scene2.children) || !componentDictionary) {
33755
33840
  return;
33756
33841
  }
33842
+
33843
+ // Collect every connector uuid referenced by the scene's connections so we
33844
+ // can inject connectors under the SAME uuid scheme the scene actually uses.
33845
+ // Two schemes exist in the wild:
33846
+ // - Preferred (runtime add path + minimal export): `${componentUuid}_${dictConnectorUuid}`
33847
+ // - Legacy (older hand-authored scenes): `${componentUuid}-CONNECTOR-${index+1}`
33848
+ var connectionEndpoints = new Set();
33849
+ if (Array.isArray(data.connections)) {
33850
+ data.connections.forEach(function (conn) {
33851
+ if (conn !== null && conn !== void 0 && conn.from) connectionEndpoints.add(conn.from);
33852
+ if (conn !== null && conn !== void 0 && conn.to) connectionEndpoints.add(conn.to);
33853
+ });
33854
+ }
33757
33855
  var componentsProcessed = 0;
33758
33856
  var connectorsInjected = 0;
33759
33857
  data.scene.children.forEach(function (child) {
@@ -33776,9 +33874,18 @@ var SceneOperationsManager = /*#__PURE__*/function () {
33776
33874
 
33777
33875
  // Deep clone the connector children from the dictionary
33778
33876
  child.children = dictEntry.children.map(function (connector, index) {
33779
- // Generate unique UUID for this connector instance
33780
- // Format: COMPONENT-UUID-CONNECTOR-INDEX
33781
- var connectorUuid = "".concat(child.uuid, "-CONNECTOR-").concat(index + 1);
33877
+ // Choose the connector uuid that matches the scheme the scene's
33878
+ // connections reference. Prefer `${componentUuid}_${dictConnectorUuid}`
33879
+ // (sandbox add path + minimal export); fall back to the legacy
33880
+ // index-based scheme `${componentUuid}-CONNECTOR-${index+1}` when the
33881
+ // connections reference that form. If neither is referenced (e.g. an
33882
+ // unconnected connector), default to the preferred scheme.
33883
+ var legacyUuid = "".concat(child.uuid, "-CONNECTOR-").concat(index + 1);
33884
+ var preferredUuid = connector.uuid ? "".concat(child.uuid, "_").concat(connector.uuid) : legacyUuid;
33885
+ var connectorUuid = preferredUuid;
33886
+ if (!connectionEndpoints.has(preferredUuid) && connectionEndpoints.has(legacyUuid)) {
33887
+ connectorUuid = legacyUuid;
33888
+ }
33782
33889
 
33783
33890
  // Clone connector with deep copy of userData
33784
33891
  var clonedConnector = _objectSpread2(_objectSpread2({}, connector), {}, {
@@ -34212,24 +34319,29 @@ var SceneOperationsManager = /*#__PURE__*/function () {
34212
34319
  children: [] // Initialize children array
34213
34320
  };
34214
34321
 
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
- }
34322
+ // Collect children that are connectors (using traverse to find deep children in models)
34323
+ componentModel.traverse(function (child) {
34324
+ if (child.userData && child.userData.objectType === 'connector') {
34325
+ // Calculate position relative to component root
34326
+ // This ensures that when we re-add it as a direct child of the component,
34327
+ // it maintains the same world position relative to the component.
34328
+ var componentMatrixWorldInverse = componentModel.matrixWorld.clone().invert();
34329
+ var childWorldPos = new THREE__namespace.Vector3();
34330
+ child.getWorldPosition(childWorldPos);
34331
+ var relativePos = childWorldPos.applyMatrix4(componentMatrixWorldInverse);
34332
+ componentSceneData.children.push({
34333
+ uuid: child.uuid,
34334
+ name: child.name,
34335
+ type: 'Mesh',
34336
+ position: {
34337
+ x: relativePos.x,
34338
+ y: relativePos.y,
34339
+ z: relativePos.z
34340
+ },
34341
+ userData: _objectSpread2({}, child.userData)
34342
+ });
34343
+ }
34344
+ });
34233
34345
 
34234
34346
  // Add the component to the scene data
34235
34347
  if (!currentSceneData.scene.children) {
@@ -39876,17 +39988,19 @@ var CentralPlantInternals = /*#__PURE__*/function () {
39876
39988
  * @param {string} componentId - The UUID of the component to translate
39877
39989
  * @param {string} axis - The axis to translate on ('x', 'y', or 'z')
39878
39990
  * @param {number} value - The value to translate by
39991
+ * @param {boolean} [skipPathUpdate=false] - Whether to skip triggering pathfinding
39879
39992
  * @returns {boolean} True if translation was successful, false otherwise
39880
39993
  */
39881
39994
  }, {
39882
39995
  key: "translateComponent",
39883
39996
  value: function translateComponent(componentId, axis, value) {
39884
39997
  var _this$centralPlant$ma;
39998
+ var skipPathUpdate = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
39885
39999
  if (!((_this$centralPlant$ma = this.centralPlant.managers) !== null && _this$centralPlant$ma !== void 0 && _this$centralPlant$ma.transformOperationsManager)) {
39886
40000
  console.error('❌ translateComponent(): TransformOperationsManager not available');
39887
40001
  return false;
39888
40002
  }
39889
- return this.centralPlant.managers.transformOperationsManager.translateComponent(componentId, axis, value);
40003
+ return this.centralPlant.managers.transformOperationsManager.translateComponent(componentId, axis, value, skipPathUpdate);
39890
40004
  }
39891
40005
 
39892
40006
  /**
@@ -39978,7 +40092,7 @@ var CentralPlantInternals = /*#__PURE__*/function () {
39978
40092
  } else if (objectType === 'gateway') {
39979
40093
  success = this.translateGateway(objectId, axis, value);
39980
40094
  } else if (objectType === 'component') {
39981
- success = this.translateComponent(objectId, axis, value);
40095
+ success = this.translateComponent(objectId, axis, value, skipPathUpdate);
39982
40096
  } else {
39983
40097
  result.errors.push("Unknown object type: ".concat(objectType, " (").concat(objectId, ")"));
39984
40098
  console.warn("\u26A0\uFE0F Unknown object type: ".concat(objectType, " for ").concat(objectId));
@@ -41011,7 +41125,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
41011
41125
  * Initialize the CentralPlant manager
41012
41126
  *
41013
41127
  * @constructor
41014
- * @version 0.3.47
41128
+ * @version 0.3.49
41015
41129
  * @updated 2025-10-22
41016
41130
  *
41017
41131
  * @description Creates a new CentralPlant instance and initializes internal managers and utilities.
@@ -41341,7 +41455,8 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
41341
41455
  }, {
41342
41456
  key: "translate",
41343
41457
  value: function translate(componentId, axis, value) {
41344
- return this.internals.translateComponent(componentId, axis, value);
41458
+ var skipPathUpdate = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
41459
+ return this.internals.translateComponent(componentId, axis, value, skipPathUpdate);
41345
41460
  }
41346
41461
 
41347
41462
  /**
@@ -42039,18 +42154,19 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
42039
42154
  return [];
42040
42155
  }
42041
42156
 
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
- });
42157
+ // Recursive search for connector IDs
42158
+ var _findConnectorIds = function findConnectorIds(nodes) {
42159
+ if (!nodes || !Array.isArray(nodes)) return;
42160
+ nodes.forEach(function (node) {
42161
+ if (node.userData && node.userData.objectType === 'connector' && node.uuid) {
42162
+ allConnectorIds.push(node.uuid);
42163
+ }
42164
+ if (node.children && Array.isArray(node.children)) {
42165
+ _findConnectorIds(node.children);
42166
+ }
42167
+ });
42168
+ };
42169
+ _findConnectorIds(sceneData.scene.children);
42054
42170
 
42055
42171
  // Get existing connections
42056
42172
  var existingConnections = this.getConnections();
@@ -42080,28 +42196,28 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
42080
42196
  * const infos = centralPlant.getAvailableConnectionsInfo()
42081
42197
  * // [{ id: 'PUMP-1-CONNECTOR-1', flow: 'out' }, ...]
42082
42198
  */
42199
+ /**
42200
+ * Get all connectors in the scene that are currently available (not used in a connection).
42201
+ * Searches the active Three.js scene directly for the most accurate results.
42202
+ * @returns {Array<{id: string, flow: string}>} Array of connector info objects
42203
+ * @since 0.1.72 (Updated to search Three.js scene directly)
42204
+ */
42083
42205
  }, {
42084
42206
  key: "getAvailableConnectionsInfo",
42085
42207
  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');
42208
+ if (!this.sceneViewer || !this.sceneViewer.scene) {
42209
+ console.warn('⚠️ getAvailableConnectionsInfo(): Scene viewer or scene not available');
42093
42210
  return [];
42094
42211
  }
42095
42212
  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
- }
42213
+
42214
+ // Search the Three.js scene directly for connectors
42215
+ // This is more reliable than searching currentSceneData which might be stale
42216
+ this.sceneViewer.scene.traverse(function (node) {
42217
+ if (node.userData && node.userData.objectType === 'connector' && node.uuid) {
42218
+ allConnectorInfos.push({
42219
+ id: node.uuid,
42220
+ flow: node.userData.flow || 'bi'
42105
42221
  });
42106
42222
  }
42107
42223
  });
@@ -42123,30 +42239,29 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
42123
42239
  * const result = centralPlant.validateConnections()
42124
42240
  * result.invalid.forEach(({ connection, reason }) => console.warn(reason, connection))
42125
42241
  */
42242
+ /**
42243
+ * Validate all connections in the current scene for flow direction compatibility.
42244
+ * Searches the active Three.js scene for connector flow markers.
42245
+ * @returns {{ valid: Array<Object>, invalid: Array<{connection: Object, reason: string}> }}
42246
+ * @since 0.1.72 (Updated to search Three.js scene directly)
42247
+ */
42126
42248
  }, {
42127
42249
  key: "validateConnections",
42128
42250
  value: function validateConnections() {
42129
- if (!this.sceneViewer || !this.sceneViewer.currentSceneData) {
42130
- console.warn('⚠️ validateConnections(): Scene viewer or current scene data not available');
42251
+ if (!this.sceneViewer || !this.sceneViewer.scene) {
42252
+ console.warn('⚠️ validateConnections(): Scene viewer or scene not available');
42131
42253
  return {
42132
42254
  valid: [],
42133
42255
  invalid: []
42134
42256
  };
42135
42257
  }
42136
42258
  var connections = this.getConnections();
42137
- var sceneData = this.sceneViewer.currentSceneData;
42138
42259
 
42139
- // Build lookup map: connectorId → flow
42260
+ // Build lookup map: connectorId → flow from the Three.js scene
42140
42261
  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
- });
42262
+ this.sceneViewer.scene.traverse(function (node) {
42263
+ if (node.userData && node.userData.objectType === 'connector' && node.uuid) {
42264
+ flowMap[node.uuid] = node.userData.flow || 'bi';
42150
42265
  }
42151
42266
  });
42152
42267
  var valid = [];
@@ -44114,7 +44229,7 @@ var sceneViewer = /*#__PURE__*/function (_BaseDisposable) {
44114
44229
  this.centralPlant.attachToComponent();
44115
44230
 
44116
44231
  // 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
44232
+ 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
44233
  managerKeys.forEach(function (key) {
44119
44234
  if (_this2[key]) {
44120
44235
  _this2.managers[key] = _this2[key];