@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.
@@ -4,6 +4,7 @@ Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var _rollupPluginBabelHelpers = require('../../../_virtual/_rollupPluginBabelHelpers.js');
6
6
  var THREE = require('three');
7
+ var nameUtils = require('../../utils/nameUtils.js');
7
8
  var ioDeviceUtils = require('../../utils/ioDeviceUtils.js');
8
9
  var behaviorRegistration = require('../../utils/behaviorRegistration.js');
9
10
  var modelPreloader = require('../../rendering/modelPreloader.js');
@@ -162,36 +163,58 @@ var ModelManager = /*#__PURE__*/function () {
162
163
  * @param {THREE.Object3D} targetMesh - The mesh whose connectors to preserve
163
164
  * @param {string} parentUuid - The UUID of the parent component
164
165
  */
166
+ /**
167
+ * Identifies all connector objects within a model hierarchy and prepares them
168
+ * to be preserved when the model is replaced or modified.
169
+ * Uses traverse() to find connectors at any depth in the hierarchy.
170
+ * @param {THREE.Object3D} targetMesh - The root of the model to search
171
+ * @param {string} parentUuid - The UUID of the parent component
172
+ * @returns {Array<THREE.Object3D>} Array of cloned and prepared connector objects
173
+ */
165
174
  }, {
166
175
  key: "_preserveConnectorChildren",
167
176
  value: function _preserveConnectorChildren(targetMesh, parentUuid) {
168
177
  var _this = this;
169
178
  var connectorChildren = [];
170
- targetMesh.children.forEach(function (child, index) {
179
+ var connectorIndex = 0;
180
+
181
+ // Use traverse to find connectors at ANY depth in the hierarchy
182
+ targetMesh.traverse(function (child) {
171
183
  var _child$userData;
172
184
  var isConnectorGeometry = child.geometry && (child.geometry.uuid === 'CONNECTOR-GEO' || child.geometry.type === 'SphereGeometry' && child.geometry.parameters);
173
185
  var isConnectorByName = child.name && child.name.toLowerCase().includes('connector');
174
- // Also recognise connectors declared via userData (e.g. inline connectors from SAMPLE_1.json
175
- // whose geometry may be a fallback BufferGeometry rather than a SphereGeometry).
176
186
  var isConnectorByUserData = ((_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.objectType) === 'connector';
177
187
  if (isConnectorGeometry && isConnectorByName || isConnectorByUserData) {
188
+ connectorIndex++;
189
+
178
190
  // Ensure userData exists and has worldBoundingBox
179
191
  if (!child.userData) child.userData = {};
180
192
  if (!child.userData.worldBoundingBox) {
181
193
  child.userData.worldBoundingBox = _this._calculateWorldBoundingBox(child);
182
194
  }
183
195
 
184
- // Clone and preserve critical userData
196
+ // Clone the connector
185
197
  var clonedConnector = child.clone();
186
198
 
187
- // Generate proper connector UUID based on parent component UUID
188
- // Format: COMPONENT-UUID-CONNECTOR-INDEX (matching JSON import pattern)
189
- var connectorUuid = "".concat(parentUuid, "-CONNECTOR-").concat(index + 1);
199
+ // CRITICAL: Convert nested world position to root-relative position
200
+ // This ensures the connector stays in the same place when re-added as a direct child of targetMesh
201
+ var targetInverse = targetMesh.matrixWorld.clone().invert();
202
+ var worldPos = new THREE__namespace.Vector3();
203
+ child.getWorldPosition(worldPos);
204
+ clonedConnector.position.copy(worldPos.applyMatrix4(targetInverse));
205
+
206
+ // Get hardcoded UUID if it exists (e.g. from JSON mesh placeholder)
207
+ // This ensures compatibility with existing scene JSON naming conventions
208
+ var existingUuid = nameUtils.getHardcodedUuid(child);
209
+
210
+ // Generate fallback UUID only if existing one isn't already parent-prefixed
211
+ // Format: COMPONENT-UUID-CONNECTOR-INDEX (stable fallback)
212
+ var connectorUuid = existingUuid && existingUuid.includes(parentUuid) ? existingUuid : "".concat(parentUuid, "-CONNECTOR-").concat(connectorIndex);
190
213
  clonedConnector.uuid = connectorUuid;
191
214
  if (child.userData) {
192
215
  clonedConnector.userData = _rollupPluginBabelHelpers.objectSpread2({}, child.userData);
193
216
 
194
- // Deep copy critical data - handle both array [x,y,z] and object {x,y,z} formats
217
+ // Deep copy critical data
195
218
  if (child.userData.worldBoundingBox) {
196
219
  var wbb = child.userData.worldBoundingBox;
197
220
  clonedConnector.userData.worldBoundingBox = {
@@ -200,7 +223,6 @@ var ModelManager = /*#__PURE__*/function () {
200
223
  };
201
224
  }
202
225
  if (child.userData.direction) {
203
- // Handle both array [x,y,z] and object {x,y,z} formats
204
226
  if (Array.isArray(child.userData.direction)) {
205
227
  clonedConnector.userData.direction = _rollupPluginBabelHelpers.toConsumableArray(child.userData.direction);
206
228
  } else if (_rollupPluginBabelHelpers["typeof"](child.userData.direction) === 'object') {
@@ -212,11 +234,12 @@ var ModelManager = /*#__PURE__*/function () {
212
234
 
213
235
  // Set originalUuid to match the actual uuid (maintains consistency)
214
236
  clonedConnector.userData.originalUuid = connectorUuid;
215
- clonedConnector.userData.objectType = child.userData.objectType || (child.name.toLowerCase().includes('connector') ? 'connector' : 'gateway');
237
+ clonedConnector.userData.objectType = child.userData.objectType || 'connector';
216
238
  }
217
239
  connectorChildren.push(clonedConnector);
218
240
  }
219
241
  });
242
+ console.log("\uD83D\uDEE1\uFE0F Preserved ".concat(connectorChildren.length, " connector(s) for component ").concat(parentUuid));
220
243
  return connectorChildren;
221
244
  }
222
245
 
@@ -103,6 +103,11 @@ var SceneExportManager = /*#__PURE__*/function () {
103
103
  // Helper function to convert Three.js object to minimal JSON format
104
104
  var convertObjectToJson = function convertObjectToJson(threeObject) {
105
105
  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;
106
+ // Ensure world matrices are updated for this subtree before exporting
107
+ if (threeObject.updateMatrixWorld) {
108
+ threeObject.updateMatrixWorld(true);
109
+ }
110
+
106
111
  // Skip certain objects that shouldn't be exported
107
112
  if (!threeObject || (_threeObject$name = threeObject.name) !== null && _threeObject$name !== void 0 && _threeObject$name.includes('Polyline') ||
108
113
  // Skip pipe paths
@@ -241,11 +246,10 @@ var SceneExportManager = /*#__PURE__*/function () {
241
246
  };
242
247
  }
243
248
 
244
- // For components: only export child connectors if they were manually added/defined.
245
- // Most connectors are injected from dictionary at import time, but some (like manually placed ones)
246
- // need to be persisted to maintain connections in the exported scene.
249
+ // Only manual segments persist their connector children here.
250
+ // Component connectors are NOT exported they are regenerated from the
251
+ // component dictionary on load (see note below), keeping the scene JSON minimal.
247
252
  if (threeObject.children && threeObject.children.length > 0) {
248
- var _threeObject$userData11;
249
253
  var exportableChildren = [];
250
254
  if (isManualSegment) {
251
255
  // For manual segments, export their connector children
@@ -259,26 +263,15 @@ var SceneExportManager = /*#__PURE__*/function () {
259
263
  }
260
264
  }
261
265
  });
262
- } else if (((_threeObject$userData11 = threeObject.userData) === null || _threeObject$userData11 === void 0 ? void 0 : _threeObject$userData11.objectType) === 'component') {
263
- // For components, only export connectors that have objectType='connector'
264
- // Standard dictionary-injected connectors should be exported so connections work on re-import
265
- threeObject.children.forEach(function (child) {
266
- var _child$userData2;
267
- if (((_child$userData2 = child.userData) === null || _child$userData2 === void 0 ? void 0 : _child$userData2.objectType) === 'connector') {
268
- exportableChildren.push({
269
- uuid: child.uuid,
270
- name: child.name,
271
- type: 'Mesh',
272
- position: {
273
- x: roundIfClose(child.position.x),
274
- y: roundIfClose(child.position.y),
275
- z: roundIfClose(child.position.z)
276
- },
277
- userData: _rollupPluginBabelHelpers.objectSpread2({}, child.userData)
278
- });
279
- }
280
- });
281
266
  }
267
+ // NOTE: Component connectors are intentionally NOT exported.
268
+ // They are defined in the component dictionary and regenerated on load by
269
+ // sceneOperationsManager._injectConnectorChildrenFromDictionary using the
270
+ // matching uuid scheme (`${componentUuid}_${dictConnectorUuid}`), and the
271
+ // pathfinder rebuilds their world positions/bounding boxes on every run
272
+ // (computeConnectorBoundingBoxes). Connections still resolve because the
273
+ // regenerated connector uuids match the connection endpoints.
274
+
282
275
  if (exportableChildren.length > 0) {
283
276
  jsonObject.children = exportableChildren;
284
277
  }
@@ -292,9 +285,9 @@ var SceneExportManager = /*#__PURE__*/function () {
292
285
  // Extract main scene objects (components and standalone connectors)
293
286
  var sceneChildren = [];
294
287
  this.sceneViewer.scene.children.forEach(function (child) {
295
- var _child$userData3;
288
+ var _child$userData2;
296
289
  // Only export components and connectors; skip segments, gateways, polylines, etc.
297
- var objectType = (_child$userData3 = child.userData) === null || _child$userData3 === void 0 ? void 0 : _child$userData3.objectType;
290
+ var objectType = (_child$userData2 = child.userData) === null || _child$userData2 === void 0 ? void 0 : _child$userData2.objectType;
298
291
  if (objectType !== 'component' && objectType !== 'connector') {
299
292
  return;
300
293
  }
@@ -458,14 +451,14 @@ var SceneExportManager = /*#__PURE__*/function () {
458
451
  BufferGeometryUtils = BufferGeometryUtilsModule.BufferGeometryUtils || BufferGeometryUtilsModule.default || BufferGeometryUtilsModule; // Create a new scene for export instead of cloning
459
452
  exportScene = new _THREE.Scene(); // Helper function to check if an object should be exported
460
453
  shouldExport = function shouldExport(child) {
461
- var _child$name, _child$userData4, _child$userData5, _child$userData6, _child$userData7;
454
+ var _child$name, _child$userData3, _child$userData4, _child$userData5, _child$userData6;
462
455
  if ((_child$name = child.name) !== null && _child$name !== void 0 && _child$name.includes('Polyline')) return false; // Will handle separately
463
456
  if (child.name === 'fogPlane') return false; // Skip fog plane
464
- if ((_child$userData4 = child.userData) !== null && _child$userData4 !== void 0 && _child$userData4.isBrickWall) return false; // Skip environment
465
- if ((_child$userData5 = child.userData) !== null && _child$userData5 !== void 0 && _child$userData5.isBaseGround) return false; // Skip environment
466
- if ((_child$userData6 = child.userData) !== null && _child$userData6 !== void 0 && _child$userData6.isBaseGrid) return false; // Skip environment
457
+ if ((_child$userData3 = child.userData) !== null && _child$userData3 !== void 0 && _child$userData3.isBrickWall) return false; // Skip environment
458
+ if ((_child$userData4 = child.userData) !== null && _child$userData4 !== void 0 && _child$userData4.isBaseGround) return false; // Skip environment
459
+ if ((_child$userData5 = child.userData) !== null && _child$userData5 !== void 0 && _child$userData5.isBaseGrid) return false; // Skip environment
467
460
  if (child.isLight) return false; // Skip lights
468
- if ((_child$userData7 = child.userData) !== null && _child$userData7 !== void 0 && _child$userData7.isTransformControls) return false; // Skip transform controls
461
+ if ((_child$userData6 = child.userData) !== null && _child$userData6 !== void 0 && _child$userData6.isTransformControls) return false; // Skip transform controls
469
462
  if (child.isTransformControls) return false; // Skip transform controls
470
463
  if (child.type && child.type.includes('TransformControls')) return false;
471
464
  if (child.type && child.type.includes('Helper')) return false; // Skip helpers
@@ -890,6 +890,19 @@ var SceneOperationsManager = /*#__PURE__*/function () {
890
890
  if (!(data !== null && data !== void 0 && (_data$scene2 = data.scene) !== null && _data$scene2 !== void 0 && _data$scene2.children) || !componentDictionary) {
891
891
  return;
892
892
  }
893
+
894
+ // Collect every connector uuid referenced by the scene's connections so we
895
+ // can inject connectors under the SAME uuid scheme the scene actually uses.
896
+ // Two schemes exist in the wild:
897
+ // - Preferred (runtime add path + minimal export): `${componentUuid}_${dictConnectorUuid}`
898
+ // - Legacy (older hand-authored scenes): `${componentUuid}-CONNECTOR-${index+1}`
899
+ var connectionEndpoints = new Set();
900
+ if (Array.isArray(data.connections)) {
901
+ data.connections.forEach(function (conn) {
902
+ if (conn !== null && conn !== void 0 && conn.from) connectionEndpoints.add(conn.from);
903
+ if (conn !== null && conn !== void 0 && conn.to) connectionEndpoints.add(conn.to);
904
+ });
905
+ }
893
906
  var componentsProcessed = 0;
894
907
  var connectorsInjected = 0;
895
908
  data.scene.children.forEach(function (child) {
@@ -912,9 +925,18 @@ var SceneOperationsManager = /*#__PURE__*/function () {
912
925
 
913
926
  // Deep clone the connector children from the dictionary
914
927
  child.children = dictEntry.children.map(function (connector, index) {
915
- // Generate unique UUID for this connector instance
916
- // Format: COMPONENT-UUID-CONNECTOR-INDEX
917
- var connectorUuid = "".concat(child.uuid, "-CONNECTOR-").concat(index + 1);
928
+ // Choose the connector uuid that matches the scheme the scene's
929
+ // connections reference. Prefer `${componentUuid}_${dictConnectorUuid}`
930
+ // (sandbox add path + minimal export); fall back to the legacy
931
+ // index-based scheme `${componentUuid}-CONNECTOR-${index+1}` when the
932
+ // connections reference that form. If neither is referenced (e.g. an
933
+ // unconnected connector), default to the preferred scheme.
934
+ var legacyUuid = "".concat(child.uuid, "-CONNECTOR-").concat(index + 1);
935
+ var preferredUuid = connector.uuid ? "".concat(child.uuid, "_").concat(connector.uuid) : legacyUuid;
936
+ var connectorUuid = preferredUuid;
937
+ if (!connectionEndpoints.has(preferredUuid) && connectionEndpoints.has(legacyUuid)) {
938
+ connectorUuid = legacyUuid;
939
+ }
918
940
 
919
941
  // Clone connector with deep copy of userData
920
942
  var clonedConnector = _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, connector), {}, {
@@ -1348,24 +1370,29 @@ var SceneOperationsManager = /*#__PURE__*/function () {
1348
1370
  children: [] // Initialize children array
1349
1371
  };
1350
1372
 
1351
- // Collect children that are connectors
1352
- if (componentModel.children) {
1353
- componentModel.children.forEach(function (child) {
1354
- if (child.userData && child.userData.objectType === 'connector') {
1355
- componentSceneData.children.push({
1356
- uuid: child.uuid,
1357
- name: child.name,
1358
- type: 'Mesh',
1359
- position: {
1360
- x: child.position.x,
1361
- y: child.position.y,
1362
- z: child.position.z
1363
- },
1364
- userData: _rollupPluginBabelHelpers.objectSpread2({}, child.userData)
1365
- });
1366
- }
1367
- });
1368
- }
1373
+ // Collect children that are connectors (using traverse to find deep children in models)
1374
+ componentModel.traverse(function (child) {
1375
+ if (child.userData && child.userData.objectType === 'connector') {
1376
+ // Calculate position relative to component root
1377
+ // This ensures that when we re-add it as a direct child of the component,
1378
+ // it maintains the same world position relative to the component.
1379
+ var componentMatrixWorldInverse = componentModel.matrixWorld.clone().invert();
1380
+ var childWorldPos = new THREE__namespace.Vector3();
1381
+ child.getWorldPosition(childWorldPos);
1382
+ var relativePos = childWorldPos.applyMatrix4(componentMatrixWorldInverse);
1383
+ componentSceneData.children.push({
1384
+ uuid: child.uuid,
1385
+ name: child.name,
1386
+ type: 'Mesh',
1387
+ position: {
1388
+ x: relativePos.x,
1389
+ y: relativePos.y,
1390
+ z: relativePos.z
1391
+ },
1392
+ userData: _rollupPluginBabelHelpers.objectSpread2({}, child.userData)
1393
+ });
1394
+ }
1395
+ });
1369
1396
 
1370
1397
  // Add the component to the scene data
1371
1398
  if (!currentSceneData.scene.children) {
@@ -179,41 +179,36 @@ function computeFilteredBoundingBox(object) {
179
179
  */
180
180
  function computeIODeviceBoundingBoxes(componentObject) {
181
181
  var results = [];
182
- var _iterator = _rollupPluginBabelHelpers.createForOfIteratorHelper(componentObject.children),
183
- _step;
184
- try {
185
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
186
- var _child$userData;
187
- var child = _step.value;
188
- if (((_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.objectType) !== 'io-device') continue;
189
- var bbox = new THREE__namespace.Box3().setFromObject(child);
190
- if (!bbox.isEmpty()) {
191
- results.push({
192
- uuid: child.uuid,
193
- userData: {
194
- objectType: 'io-device',
195
- deviceId: child.userData.deviceId || null,
196
- attachmentId: child.userData.attachmentId || null,
197
- parentComponentId: child.userData.parentComponentId || componentObject.uuid
198
- },
199
- worldBoundingBox: {
200
- min: [bbox.min.x, bbox.min.y, bbox.min.z],
201
- max: [bbox.max.x, bbox.max.y, bbox.max.z]
202
- }
203
- });
204
- }
182
+ componentObject.traverse(function (child) {
183
+ var _child$userData;
184
+ if (((_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.objectType) !== 'io-device') return;
185
+ var bbox = new THREE__namespace.Box3().setFromObject(child);
186
+ var worldPos = new THREE__namespace.Vector3();
187
+ child.getWorldPosition(worldPos);
188
+ if (!bbox.isEmpty()) {
189
+ results.push({
190
+ uuid: child.uuid,
191
+ userData: {
192
+ objectType: 'io-device',
193
+ deviceId: child.userData.deviceId || null,
194
+ attachmentId: child.userData.attachmentId || null,
195
+ parentComponentId: child.userData.parentComponentId || componentObject.uuid,
196
+ // Sync position for pathfinder
197
+ position: [worldPos.x, worldPos.y, worldPos.z]
198
+ },
199
+ worldBoundingBox: {
200
+ min: [bbox.min.x, bbox.min.y, bbox.min.z],
201
+ max: [bbox.max.x, bbox.max.y, bbox.max.z]
202
+ }
203
+ });
205
204
  }
206
- } catch (err) {
207
- _iterator.e(err);
208
- } finally {
209
- _iterator.f();
210
- }
205
+ });
211
206
  return results;
212
207
  }
213
208
 
214
209
  /**
215
210
  * Computes individual world-space bounding boxes for each connector child
216
- * of a component.
211
+ * of a component. Supports deep children (e.g. within GLB model hierarchy).
217
212
  *
218
213
  * @param {THREE.Object3D} componentObject - The component's Three.js object
219
214
  * @returns {Array<{uuid: string, userData: Object, worldBoundingBox: {min: number[], max: number[]}}>}
@@ -221,38 +216,40 @@ function computeIODeviceBoundingBoxes(componentObject) {
221
216
  */
222
217
  function computeConnectorBoundingBoxes(componentObject) {
223
218
  var results = [];
224
- var _iterator2 = _rollupPluginBabelHelpers.createForOfIteratorHelper(componentObject.children),
225
- _step2;
226
- try {
227
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
228
- var _child$userData2;
229
- var child = _step2.value;
230
- if (((_child$userData2 = child.userData) === null || _child$userData2 === void 0 ? void 0 : _child$userData2.objectType) !== 'connector') continue;
231
- var bbox = new THREE__namespace.Box3().setFromObject(child);
219
+ componentObject.traverse(function (child) {
220
+ var _child$userData2;
221
+ if (((_child$userData2 = child.userData) === null || _child$userData2 === void 0 ? void 0 : _child$userData2.objectType) !== 'connector') return;
222
+ var bbox = new THREE__namespace.Box3().setFromObject(child);
223
+ var worldPos = new THREE__namespace.Vector3();
224
+ child.getWorldPosition(worldPos);
232
225
 
233
- // Fallback if mesh is too small or empty (sometimes connectors are just points)
234
- if (bbox.isEmpty() || bbox.getSize(new THREE__namespace.Vector3()).length() < 0.01) {
235
- var worldPos = new THREE__namespace.Vector3();
236
- child.getWorldPosition(worldPos);
237
- var size = 0.1;
238
- bbox.setFromCenterAndSize(worldPos, new THREE__namespace.Vector3(size, size, size));
239
- }
240
- results.push({
241
- uuid: child.uuid,
242
- userData: _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, child.userData), {}, {
243
- objectType: 'connector'
244
- }),
245
- worldBoundingBox: {
246
- min: [bbox.min.x, bbox.min.y, bbox.min.z],
247
- max: [bbox.max.x, bbox.max.y, bbox.max.z]
248
- }
249
- });
226
+ // Compute world-space direction vector (Crucial for rotation-aware pathfinding)
227
+ // Default to [0, 0, 1] if not specified (Standard for our coordinate system)
228
+ var localDirData = child.userData && Array.isArray(child.userData.direction) ? child.userData.direction : [0, 0, 1];
229
+ var localDir = new THREE__namespace.Vector3(localDirData[0], localDirData[1], localDirData[2]);
230
+ var worldQuat = new THREE__namespace.Quaternion();
231
+ child.getWorldQuaternion(worldQuat);
232
+ var worldDir = localDir.clone().applyQuaternion(worldQuat).normalize();
233
+
234
+ // Fallback if mesh is too small or empty (sometimes connectors are just points)
235
+ if (bbox.isEmpty() || bbox.getSize(new THREE__namespace.Vector3()).length() < 0.01) {
236
+ var size = 0.1;
237
+ bbox.setFromCenterAndSize(worldPos, new THREE__namespace.Vector3(size, size, size));
250
238
  }
251
- } catch (err) {
252
- _iterator2.e(err);
253
- } finally {
254
- _iterator2.f();
255
- }
239
+ results.push({
240
+ uuid: child.uuid,
241
+ userData: _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, child.userData), {}, {
242
+ objectType: 'connector',
243
+ // Update both position AND direction for pathfinder
244
+ position: [worldPos.x, worldPos.y, worldPos.z],
245
+ direction: [worldDir.x, worldDir.y, worldDir.z]
246
+ }),
247
+ worldBoundingBox: {
248
+ min: [bbox.min.x, bbox.min.y, bbox.min.z],
249
+ max: [bbox.max.x, bbox.max.y, bbox.max.z]
250
+ }
251
+ });
252
+ });
256
253
  return results;
257
254
  }
258
255
 
@@ -352,11 +349,11 @@ function createSelectionBoxHelpers(object) {
352
349
  * @param {THREE.Scene} scene - The scene (for finding objects by uuid)
353
350
  */
354
351
  function updateSelectionBoxHelpers(helpers, selectedObjects, scene) {
355
- var _iterator3 = _rollupPluginBabelHelpers.createForOfIteratorHelper(helpers),
356
- _step3;
352
+ var _iterator = _rollupPluginBabelHelpers.createForOfIteratorHelper(helpers),
353
+ _step;
357
354
  try {
358
355
  var _loop = function _loop() {
359
- var helper = _step3.value;
356
+ var helper = _step.value;
360
357
  var _helper$userData = helper.userData,
361
358
  sourceObjectUuid = _helper$userData.sourceObjectUuid,
362
359
  isFiltered = _helper$userData.isFiltered,
@@ -387,13 +384,13 @@ function updateSelectionBoxHelpers(helpers, selectedObjects, scene) {
387
384
  helper.update();
388
385
  }
389
386
  };
390
- for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
387
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
391
388
  if (_loop()) continue;
392
389
  }
393
390
  } catch (err) {
394
- _iterator3.e(err);
391
+ _iterator.e(err);
395
392
  } finally {
396
- _iterator3.f();
393
+ _iterator.f();
397
394
  }
398
395
  }
399
396
 
@@ -33,7 +33,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
33
33
  * Initialize the CentralPlant manager
34
34
  *
35
35
  * @constructor
36
- * @version 0.3.47
36
+ * @version 0.3.49
37
37
  * @updated 2025-10-22
38
38
  *
39
39
  * @description Creates a new CentralPlant instance and initializes internal managers and utilities.
@@ -363,7 +363,8 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
363
363
  }, {
364
364
  key: "translate",
365
365
  value: function translate(componentId, axis, value) {
366
- return this.internals.translateComponent(componentId, axis, value);
366
+ var skipPathUpdate = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
367
+ return this.internals.translateComponent(componentId, axis, value, skipPathUpdate);
367
368
  }
368
369
 
369
370
  /**
@@ -1061,18 +1062,19 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
1061
1062
  return [];
1062
1063
  }
1063
1064
 
1064
- // Traverse through all components in the scene data
1065
- sceneData.scene.children.forEach(function (component) {
1066
- // Each component may have connector children
1067
- if (component.children && Array.isArray(component.children)) {
1068
- component.children.forEach(function (child) {
1069
- // Check if this child is a connector
1070
- if (child.userData && child.userData.objectType === 'connector' && child.uuid) {
1071
- allConnectorIds.push(child.uuid);
1072
- }
1073
- });
1074
- }
1075
- });
1065
+ // Recursive search for connector IDs
1066
+ var _findConnectorIds = function findConnectorIds(nodes) {
1067
+ if (!nodes || !Array.isArray(nodes)) return;
1068
+ nodes.forEach(function (node) {
1069
+ if (node.userData && node.userData.objectType === 'connector' && node.uuid) {
1070
+ allConnectorIds.push(node.uuid);
1071
+ }
1072
+ if (node.children && Array.isArray(node.children)) {
1073
+ _findConnectorIds(node.children);
1074
+ }
1075
+ });
1076
+ };
1077
+ _findConnectorIds(sceneData.scene.children);
1076
1078
 
1077
1079
  // Get existing connections
1078
1080
  var existingConnections = this.getConnections();
@@ -1102,28 +1104,28 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
1102
1104
  * const infos = centralPlant.getAvailableConnectionsInfo()
1103
1105
  * // [{ id: 'PUMP-1-CONNECTOR-1', flow: 'out' }, ...]
1104
1106
  */
1107
+ /**
1108
+ * Get all connectors in the scene that are currently available (not used in a connection).
1109
+ * Searches the active Three.js scene directly for the most accurate results.
1110
+ * @returns {Array<{id: string, flow: string}>} Array of connector info objects
1111
+ * @since 0.1.72 (Updated to search Three.js scene directly)
1112
+ */
1105
1113
  }, {
1106
1114
  key: "getAvailableConnectionsInfo",
1107
1115
  value: function getAvailableConnectionsInfo() {
1108
- if (!this.sceneViewer || !this.sceneViewer.currentSceneData) {
1109
- console.warn('⚠️ getAvailableConnectionsInfo(): Scene viewer or current scene data not available');
1110
- return [];
1111
- }
1112
- var sceneData = this.sceneViewer.currentSceneData;
1113
- if (!sceneData.scene || !sceneData.scene.children) {
1114
- console.warn('⚠️ getAvailableConnectionsInfo(): Invalid scene data structure');
1116
+ if (!this.sceneViewer || !this.sceneViewer.scene) {
1117
+ console.warn('⚠️ getAvailableConnectionsInfo(): Scene viewer or scene not available');
1115
1118
  return [];
1116
1119
  }
1117
1120
  var allConnectorInfos = [];
1118
- sceneData.scene.children.forEach(function (component) {
1119
- if (component.children && Array.isArray(component.children)) {
1120
- component.children.forEach(function (child) {
1121
- if (child.userData && child.userData.objectType === 'connector' && child.uuid) {
1122
- allConnectorInfos.push({
1123
- id: child.uuid,
1124
- flow: child.userData.flow || 'bi'
1125
- });
1126
- }
1121
+
1122
+ // Search the Three.js scene directly for connectors
1123
+ // This is more reliable than searching currentSceneData which might be stale
1124
+ this.sceneViewer.scene.traverse(function (node) {
1125
+ if (node.userData && node.userData.objectType === 'connector' && node.uuid) {
1126
+ allConnectorInfos.push({
1127
+ id: node.uuid,
1128
+ flow: node.userData.flow || 'bi'
1127
1129
  });
1128
1130
  }
1129
1131
  });
@@ -1145,30 +1147,29 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
1145
1147
  * const result = centralPlant.validateConnections()
1146
1148
  * result.invalid.forEach(({ connection, reason }) => console.warn(reason, connection))
1147
1149
  */
1150
+ /**
1151
+ * Validate all connections in the current scene for flow direction compatibility.
1152
+ * Searches the active Three.js scene for connector flow markers.
1153
+ * @returns {{ valid: Array<Object>, invalid: Array<{connection: Object, reason: string}> }}
1154
+ * @since 0.1.72 (Updated to search Three.js scene directly)
1155
+ */
1148
1156
  }, {
1149
1157
  key: "validateConnections",
1150
1158
  value: function validateConnections() {
1151
- if (!this.sceneViewer || !this.sceneViewer.currentSceneData) {
1152
- console.warn('⚠️ validateConnections(): Scene viewer or current scene data not available');
1159
+ if (!this.sceneViewer || !this.sceneViewer.scene) {
1160
+ console.warn('⚠️ validateConnections(): Scene viewer or scene not available');
1153
1161
  return {
1154
1162
  valid: [],
1155
1163
  invalid: []
1156
1164
  };
1157
1165
  }
1158
1166
  var connections = this.getConnections();
1159
- var sceneData = this.sceneViewer.currentSceneData;
1160
1167
 
1161
- // Build lookup map: connectorId → flow
1168
+ // Build lookup map: connectorId → flow from the Three.js scene
1162
1169
  var flowMap = {};
1163
- var scene = sceneData.scene || {};
1164
- var children = scene.children || [];
1165
- children.forEach(function (component) {
1166
- if (component.children && Array.isArray(component.children)) {
1167
- component.children.forEach(function (child) {
1168
- if (child.userData && child.userData.objectType === 'connector' && child.uuid) {
1169
- flowMap[child.uuid] = child.userData.flow || 'bi';
1170
- }
1171
- });
1170
+ this.sceneViewer.scene.traverse(function (node) {
1171
+ if (node.userData && node.userData.objectType === 'connector' && node.uuid) {
1172
+ flowMap[node.uuid] = node.userData.flow || 'bi';
1172
1173
  }
1173
1174
  });
1174
1175
  var valid = [];
@@ -450,17 +450,19 @@ var CentralPlantInternals = /*#__PURE__*/function () {
450
450
  * @param {string} componentId - The UUID of the component to translate
451
451
  * @param {string} axis - The axis to translate on ('x', 'y', or 'z')
452
452
  * @param {number} value - The value to translate by
453
+ * @param {boolean} [skipPathUpdate=false] - Whether to skip triggering pathfinding
453
454
  * @returns {boolean} True if translation was successful, false otherwise
454
455
  */
455
456
  }, {
456
457
  key: "translateComponent",
457
458
  value: function translateComponent(componentId, axis, value) {
458
459
  var _this$centralPlant$ma;
460
+ var skipPathUpdate = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
459
461
  if (!((_this$centralPlant$ma = this.centralPlant.managers) !== null && _this$centralPlant$ma !== void 0 && _this$centralPlant$ma.transformOperationsManager)) {
460
462
  console.error('❌ translateComponent(): TransformOperationsManager not available');
461
463
  return false;
462
464
  }
463
- return this.centralPlant.managers.transformOperationsManager.translateComponent(componentId, axis, value);
465
+ return this.centralPlant.managers.transformOperationsManager.translateComponent(componentId, axis, value, skipPathUpdate);
464
466
  }
465
467
 
466
468
  /**
@@ -552,7 +554,7 @@ var CentralPlantInternals = /*#__PURE__*/function () {
552
554
  } else if (objectType === 'gateway') {
553
555
  success = this.translateGateway(objectId, axis, value);
554
556
  } else if (objectType === 'component') {
555
- success = this.translateComponent(objectId, axis, value);
557
+ success = this.translateComponent(objectId, axis, value, skipPathUpdate);
556
558
  } else {
557
559
  result.errors.push("Unknown object type: ".concat(objectType, " (").concat(objectId, ")"));
558
560
  console.warn("\u26A0\uFE0F Unknown object type: ".concat(objectType, " for ").concat(objectId));
@@ -98,7 +98,7 @@ var sceneViewer = /*#__PURE__*/function (_BaseDisposable) {
98
98
  this.centralPlant.attachToComponent();
99
99
 
100
100
  // Sync our managers tracking object after attachment
101
- managerKeys = ['threeJSResourceManager', 'performanceMonitorManager', 'settingsManager', 'sceneExportManager', 'componentManager', 'sceneInitializationManager', 'environmentManager', 'keyboardControlsManager', 'pathfindingManager', 'pathFlowManager', 'ioBehaviorManager', 'ioOutlineManager', 'sceneOperationsManager', 'animationManager', 'cameraControlsManager', 'componentDragManager', 'tooltipsManager', 'componentTooltipManager']; // Populate our managers tracking object
101
+ 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
102
102
  managerKeys.forEach(function (key) {
103
103
  if (_this2[key]) {
104
104
  _this2.managers[key] = _this2[key];