@2112-lab/central-plant 0.3.47 → 0.3.48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
 
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var _rollupPluginBabelHelpers = require('../../../_virtual/_rollupPluginBabelHelpers.js');
6
- require('three');
6
+ var THREE = require('three');
7
7
  var GLTFExporter = require('../../../node_modules/three/examples/jsm/exporters/GLTFExporter.js');
8
8
  var nameUtils = require('../../utils/nameUtils.js');
9
9
 
@@ -25,6 +25,8 @@ function _interopNamespace(e) {
25
25
  return Object.freeze(n);
26
26
  }
27
27
 
28
+ var THREE__namespace = /*#__PURE__*/_interopNamespace(THREE);
29
+
28
30
  var SceneExportManager = /*#__PURE__*/function () {
29
31
  function SceneExportManager(sceneViewer) {
30
32
  _rollupPluginBabelHelpers.classCallCheck(this, SceneExportManager);
@@ -103,6 +105,11 @@ var SceneExportManager = /*#__PURE__*/function () {
103
105
  // Helper function to convert Three.js object to minimal JSON format
104
106
  var convertObjectToJson = function convertObjectToJson(threeObject) {
105
107
  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;
108
+ // Ensure world matrices are updated for this subtree before exporting
109
+ if (threeObject.updateMatrixWorld) {
110
+ threeObject.updateMatrixWorld(true);
111
+ }
112
+
106
113
  // Skip certain objects that shouldn't be exported
107
114
  if (!threeObject || (_threeObject$name = threeObject.name) !== null && _threeObject$name !== void 0 && _threeObject$name.includes('Polyline') ||
108
115
  // Skip pipe paths
@@ -260,19 +267,23 @@ var SceneExportManager = /*#__PURE__*/function () {
260
267
  }
261
268
  });
262
269
  } 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) {
270
+ // For components, export all connectors (including deep children within GLFs)
271
+ threeObject.traverse(function (child) {
266
272
  var _child$userData2;
267
273
  if (((_child$userData2 = child.userData) === null || _child$userData2 === void 0 ? void 0 : _child$userData2.objectType) === 'connector') {
274
+ // Calculate position relative to component root
275
+ var componentMatrixWorldInverse = threeObject.matrixWorld.clone().invert();
276
+ var childWorldPos = new THREE__namespace.Vector3();
277
+ child.getWorldPosition(childWorldPos);
278
+ var relativePos = childWorldPos.applyMatrix4(componentMatrixWorldInverse);
268
279
  exportableChildren.push({
269
280
  uuid: child.uuid,
270
281
  name: child.name,
271
282
  type: 'Mesh',
272
283
  position: {
273
- x: roundIfClose(child.position.x),
274
- y: roundIfClose(child.position.y),
275
- z: roundIfClose(child.position.z)
284
+ x: roundIfClose(relativePos.x),
285
+ y: roundIfClose(relativePos.y),
286
+ z: roundIfClose(relativePos.z)
276
287
  },
277
288
  userData: _rollupPluginBabelHelpers.objectSpread2({}, child.userData)
278
289
  });
@@ -1348,24 +1348,29 @@ var SceneOperationsManager = /*#__PURE__*/function () {
1348
1348
  children: [] // Initialize children array
1349
1349
  };
1350
1350
 
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
- }
1351
+ // Collect children that are connectors (using traverse to find deep children in models)
1352
+ componentModel.traverse(function (child) {
1353
+ if (child.userData && child.userData.objectType === 'connector') {
1354
+ // Calculate position relative to component root
1355
+ // This ensures that when we re-add it as a direct child of the component,
1356
+ // it maintains the same world position relative to the component.
1357
+ var componentMatrixWorldInverse = componentModel.matrixWorld.clone().invert();
1358
+ var childWorldPos = new THREE__namespace.Vector3();
1359
+ child.getWorldPosition(childWorldPos);
1360
+ var relativePos = childWorldPos.applyMatrix4(componentMatrixWorldInverse);
1361
+ componentSceneData.children.push({
1362
+ uuid: child.uuid,
1363
+ name: child.name,
1364
+ type: 'Mesh',
1365
+ position: {
1366
+ x: relativePos.x,
1367
+ y: relativePos.y,
1368
+ z: relativePos.z
1369
+ },
1370
+ userData: _rollupPluginBabelHelpers.objectSpread2({}, child.userData)
1371
+ });
1372
+ }
1373
+ });
1369
1374
 
1370
1375
  // Add the component to the scene data
1371
1376
  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.48
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];
@@ -20,7 +20,7 @@ var ComponentManager = /*#__PURE__*/function () {
20
20
  key: "addComponentToScene",
21
21
  value: (function () {
22
22
  var _addComponentToScene = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(componentData) {
23
- var _gltfScene$userData, uuid, componentDictionary, libraryComponent, position, gltfScene, componentMesh, event, _t, _t2;
23
+ var _gltfScene$userData, uuid, componentDictionary, libraryComponent, position, gltfScene, componentMesh, connectorIndex, event, _t, _t2;
24
24
  return _regenerator().w(function (_context) {
25
25
  while (1) switch (_context.n) {
26
26
  case 0:
@@ -124,6 +124,7 @@ var ComponentManager = /*#__PURE__*/function () {
124
124
  }
125
125
 
126
126
  // Enable shadows for all meshes in the component
127
+ connectorIndex = 0;
127
128
  componentMesh.traverse(function (child) {
128
129
  if (child.isMesh) {
129
130
  child.castShadow = true;
@@ -133,12 +134,19 @@ var ComponentManager = /*#__PURE__*/function () {
133
134
  }
134
135
 
135
136
  // Set connector properties for connectors within the component
136
- if (child.name && child.name.toLowerCase().includes('connector')) {
137
+ if (child.name && child.name.toLowerCase().includes('connector') || child.userData && child.userData.objectType === 'connector') {
138
+ connectorIndex++;
137
139
  if (!child.userData) {
138
140
  child.userData = {};
139
141
  }
140
142
  child.userData.objectType = 'connector';
141
- console.log("\uD83D\uDD27 Set objectType='connector' for: ".concat(child.name));
143
+
144
+ // Assign a predictable, stable UUID to the connector
145
+ // This ensures connections made in the sandbox survive export/import
146
+ var connectorUuid = "".concat(uuid, "-CONNECTOR-").concat(connectorIndex);
147
+ child.uuid = connectorUuid;
148
+ child.userData.originalUuid = connectorUuid;
149
+ console.log("\uD83D\uDD27 Set predictable connector UUID: ".concat(connectorUuid, " for: ").concat(child.name));
142
150
  }
143
151
  }
144
152
  });