@2112-lab/central-plant 0.3.46 → 0.3.47

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/dist/bundle/index.js +182 -92
  2. package/dist/cjs/src/core/centralPlant.js +3 -4
  3. package/dist/cjs/src/core/sceneViewer.js +0 -1
  4. package/dist/cjs/src/managers/behaviors/IoBehaviorManager.js +1 -2
  5. package/dist/cjs/src/managers/components/componentDataManager.js +0 -1
  6. package/dist/cjs/src/managers/controls/componentDragManager.js +2 -7
  7. package/dist/cjs/src/managers/pathfinding/pathfindingManager.js +55 -1
  8. package/dist/cjs/src/managers/scene/collisionManager.js +1 -2
  9. package/dist/cjs/src/managers/scene/componentTooltipManager.js +2 -3
  10. package/dist/cjs/src/managers/scene/sceneExportManager.js +30 -11
  11. package/dist/cjs/src/managers/scene/sceneOperationsManager.js +21 -1
  12. package/dist/cjs/src/utils/behaviorDispatch.js +11 -42
  13. package/dist/cjs/src/utils/boundingBoxUtils.js +54 -8
  14. package/dist/cjs/src/utils/ioDeviceUtils.js +3 -9
  15. package/dist/esm/src/core/centralPlant.js +3 -4
  16. package/dist/esm/src/core/sceneViewer.js +0 -1
  17. package/dist/esm/src/managers/behaviors/IoBehaviorManager.js +1 -2
  18. package/dist/esm/src/managers/components/componentDataManager.js +0 -1
  19. package/dist/esm/src/managers/controls/componentDragManager.js +2 -7
  20. package/dist/esm/src/managers/pathfinding/pathfindingManager.js +56 -2
  21. package/dist/esm/src/managers/scene/collisionManager.js +1 -2
  22. package/dist/esm/src/managers/scene/componentTooltipManager.js +2 -3
  23. package/dist/esm/src/managers/scene/sceneExportManager.js +31 -12
  24. package/dist/esm/src/managers/scene/sceneOperationsManager.js +21 -1
  25. package/dist/esm/src/utils/behaviorDispatch.js +11 -42
  26. package/dist/esm/src/utils/boundingBoxUtils.js +55 -10
  27. package/dist/esm/src/utils/ioDeviceUtils.js +3 -9
  28. package/dist/index.d.ts +0 -6
  29. package/package.json +1 -1
@@ -64,8 +64,7 @@ var CollisionManager = /*#__PURE__*/function () {
64
64
  if (child === object || _this._isDescendantOf(object, child)) return;
65
65
 
66
66
  // Filter by CP object types at the root level (skip internal meshes during traverse)
67
- var isCPRootObject = ((_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.objectType) === 'component' || ((_child$userData2 = child.userData) === null || _child$userData2 === void 0 ? void 0 : _child$userData2.objectType) === 'segment' || ((_child$userData3 = child.userData) === null || _child$userData3 === void 0 ? void 0 : _child$userData3.objectType) === 'gateway' || ((_child$userData4 = child.userData) === null || _child$userData4 === void 0 ? void 0 : _child$userData4.objectType) === 'connector' || ((_child$userData5 = child.userData) === null || _child$userData5 === void 0 ? void 0 : _child$userData5.ioConfig); // IO Device
68
-
67
+ var isCPRootObject = ((_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.objectType) === 'component' || ((_child$userData2 = child.userData) === null || _child$userData2 === void 0 ? void 0 : _child$userData2.objectType) === 'segment' || ((_child$userData3 = child.userData) === null || _child$userData3 === void 0 ? void 0 : _child$userData3.objectType) === 'gateway' || ((_child$userData4 = child.userData) === null || _child$userData4 === void 0 ? void 0 : _child$userData4.objectType) === 'connector' || ((_child$userData5 = child.userData) === null || _child$userData5 === void 0 ? void 0 : _child$userData5.objectType) === 'io-device';
69
68
  if (isCPRootObject && !_this._shouldExclude(child, excludeTypes)) {
70
69
  // Use cached worldBoundingBox if available, otherwise compute it (and cache it)
71
70
  var childBBox;
@@ -482,8 +482,7 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
482
482
  if (((_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.objectType) === 'io-device') {
483
483
  var attachmentId = child.userData.attachmentId || '';
484
484
 
485
- // Use only data points from the animate window (behaviorConfig).
486
- // The static ioConfig.states[] snapshot on userData is intentionally ignored.
485
+ // Use data points from the animate window (behaviorConfig).
487
486
  var dataPoints = behaviorDispatch.resolveDataPoints(parentUuid, attachmentId, child.userData, behaviorDispatch.getIoBehaviorManager(_this3.sceneViewer));
488
487
 
489
488
  // When data points come from behaviorConfig they already carry direction:'input'.
@@ -696,7 +695,7 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
696
695
  * Input / bidirectional → shows an interactive control.
697
696
  *
698
697
  * @param {string} scopedAttachmentId - Scoped attachment ID (parentUuid::attachmentId) for state isolation
699
- * @param {Object} dp - data point definition from ioConfig.dataPoints
698
+ * @param {Object} dp - data point definition
700
699
  * @param {string} [deviceDirection] - device-level direction ('input'|'output'), overrides dp.direction
701
700
  * @param {string} [originalAttachmentId] - Original attachment ID for behavior triggering
702
701
  * @returns {HTMLElement}
@@ -241,11 +241,11 @@ var SceneExportManager = /*#__PURE__*/function () {
241
241
  };
242
242
  }
243
243
 
244
- // For components: no children exported connector positions are defined in the component
245
- // dictionary GLB and will be re-injected at import time via _injectConnectorChildrenFromDictionary.
246
- // Exporting them here would prevent that injection (it skips if children already exist)
247
- // and would leave the pathfinder with connectors in incompatible local-position format.
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.
248
247
  if (threeObject.children && threeObject.children.length > 0) {
248
+ var _threeObject$userData11;
249
249
  var exportableChildren = [];
250
250
  if (isManualSegment) {
251
251
  // For manual segments, export their connector children
@@ -259,6 +259,25 @@ var SceneExportManager = /*#__PURE__*/function () {
259
259
  }
260
260
  }
261
261
  });
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
+ });
262
281
  }
263
282
  if (exportableChildren.length > 0) {
264
283
  jsonObject.children = exportableChildren;
@@ -273,9 +292,9 @@ var SceneExportManager = /*#__PURE__*/function () {
273
292
  // Extract main scene objects (components and standalone connectors)
274
293
  var sceneChildren = [];
275
294
  this.sceneViewer.scene.children.forEach(function (child) {
276
- var _child$userData2;
295
+ var _child$userData3;
277
296
  // Only export components and connectors; skip segments, gateways, polylines, etc.
278
- var objectType = (_child$userData2 = child.userData) === null || _child$userData2 === void 0 ? void 0 : _child$userData2.objectType;
297
+ var objectType = (_child$userData3 = child.userData) === null || _child$userData3 === void 0 ? void 0 : _child$userData3.objectType;
279
298
  if (objectType !== 'component' && objectType !== 'connector') {
280
299
  return;
281
300
  }
@@ -439,14 +458,14 @@ var SceneExportManager = /*#__PURE__*/function () {
439
458
  BufferGeometryUtils = BufferGeometryUtilsModule.BufferGeometryUtils || BufferGeometryUtilsModule.default || BufferGeometryUtilsModule; // Create a new scene for export instead of cloning
440
459
  exportScene = new _THREE.Scene(); // Helper function to check if an object should be exported
441
460
  shouldExport = function shouldExport(child) {
442
- var _child$name, _child$userData3, _child$userData4, _child$userData5, _child$userData6;
461
+ var _child$name, _child$userData4, _child$userData5, _child$userData6, _child$userData7;
443
462
  if ((_child$name = child.name) !== null && _child$name !== void 0 && _child$name.includes('Polyline')) return false; // Will handle separately
444
463
  if (child.name === 'fogPlane') return false; // Skip fog plane
445
- if ((_child$userData3 = child.userData) !== null && _child$userData3 !== void 0 && _child$userData3.isBrickWall) return false; // Skip environment
446
- if ((_child$userData4 = child.userData) !== null && _child$userData4 !== void 0 && _child$userData4.isBaseGround) return false; // Skip environment
447
- if ((_child$userData5 = child.userData) !== null && _child$userData5 !== void 0 && _child$userData5.isBaseGrid) return false; // Skip environment
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
448
467
  if (child.isLight) return false; // Skip lights
449
- if ((_child$userData6 = child.userData) !== null && _child$userData6 !== void 0 && _child$userData6.isTransformControls) return false; // Skip transform controls
468
+ if ((_child$userData7 = child.userData) !== null && _child$userData7 !== void 0 && _child$userData7.isTransformControls) return false; // Skip transform controls
450
469
  if (child.isTransformControls) return false; // Skip transform controls
451
470
  if (child.type && child.type.includes('TransformControls')) return false;
452
471
  if (child.type && child.type.includes('Helper')) return false; // Skip helpers
@@ -1344,9 +1344,29 @@ var SceneOperationsManager = /*#__PURE__*/function () {
1344
1344
  min: boundingBox.min.toArray(),
1345
1345
  max: boundingBox.max.toArray()
1346
1346
  }
1347
- })
1347
+ }),
1348
+ children: [] // Initialize children array
1348
1349
  };
1349
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
+ }
1369
+
1350
1370
  // Add the component to the scene data
1351
1371
  if (!currentSceneData.scene.children) {
1352
1372
  currentSceneData.scene.children = [];
@@ -30,11 +30,11 @@ function getIoBehaviorManager(sceneViewer) {
30
30
 
31
31
  /**
32
32
  * Resolve tooltip/drag data points for an I/O device attachment.
33
- * Prefers behaviorConfig-driven animation data points; merges legacy ioConfig snapshot.
33
+ * Prefers behaviorConfig-driven animation data points.
34
34
  *
35
35
  * @param {string} parentUuid
36
36
  * @param {string} attachmentId
37
- * @param {Object} userData - io-device userData (may include dataPoints snapshot)
37
+ * @param {Object} userData - io-device userData
38
38
  * @param {import('../managers/behaviors/IoBehaviorManager.js').IoBehaviorManager|null} ioBehaviorManager
39
39
  * @param {THREE.Object3D|null} [hitMesh]
40
40
  * @returns {Object[]}
@@ -42,21 +42,7 @@ function getIoBehaviorManager(sceneViewer) {
42
42
  function resolveDataPoints(parentUuid, attachmentId, userData, ioBehaviorManager) {
43
43
  var hitMesh = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : null;
44
44
  var fromAnimations = (ioBehaviorManager === null || ioBehaviorManager === void 0 ? void 0 : ioBehaviorManager.getAnimationDataPoints(parentUuid, attachmentId, hitMesh)) || [];
45
- if (fromAnimations.length) return fromAnimations;
46
- var legacy = (userData === null || userData === void 0 ? void 0 : userData.dataPoints) || [];
47
- return legacy.map(function (dp) {
48
- var _dp$defaultValue;
49
- return {
50
- id: dp.id || dp.name,
51
- name: dp.name || dp.id,
52
- stateType: dp.stateType || 'binary',
53
- stateConfig: dp.stateConfig || {},
54
- defaultValue: (_dp$defaultValue = dp.defaultValue) !== null && _dp$defaultValue !== void 0 ? _dp$defaultValue : null,
55
- direction: dp.direction || (userData === null || userData === void 0 ? void 0 : userData.ioDirection) || 'output'
56
- };
57
- }).filter(function (dp) {
58
- return dp.id;
59
- });
45
+ return fromAnimations;
60
46
  }
61
47
 
62
48
  /**
@@ -87,35 +73,18 @@ function applyDefaultIoDeviceStates(centralPlant) {
87
73
  if (seen.has(scopedKey)) return;
88
74
  seen.add(scopedKey);
89
75
  var dps = ioBehavMgr.getAnimationDataPoints(objParentUuid, objAttachmentId) || [];
90
- if (dps.length) {
91
- var _iterator = _rollupPluginBabelHelpers.createForOfIteratorHelper(dps),
92
- _step;
93
- try {
94
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
95
- var dp = _step.value;
96
- if (!(dp !== null && dp !== void 0 && dp.id) || dp.defaultValue === undefined || dp.defaultValue === null) continue;
97
- centralPlant.setIoDeviceState(objAttachmentId, dp.id, dp.defaultValue, objParentUuid);
98
- }
99
- } catch (err) {
100
- _iterator.e(err);
101
- } finally {
102
- _iterator.f();
103
- }
104
- return;
105
- }
106
- var _iterator2 = _rollupPluginBabelHelpers.createForOfIteratorHelper(obj.userData.dataPoints || []),
107
- _step2;
76
+ var _iterator = _rollupPluginBabelHelpers.createForOfIteratorHelper(dps),
77
+ _step;
108
78
  try {
109
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
110
- var _dp = _step2.value;
111
- var dpId = _dp.id || _dp.name;
112
- if (!dpId || _dp.defaultValue === undefined || _dp.defaultValue === null) continue;
113
- centralPlant.setIoDeviceState(objAttachmentId, dpId, _dp.defaultValue, objParentUuid);
79
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
80
+ var dp = _step.value;
81
+ if (!(dp !== null && dp !== void 0 && dp.id) || dp.defaultValue === undefined || dp.defaultValue === null) continue;
82
+ centralPlant.setIoDeviceState(objAttachmentId, dp.id, dp.defaultValue, objParentUuid);
114
83
  }
115
84
  } catch (err) {
116
- _iterator2.e(err);
85
+ _iterator.e(err);
117
86
  } finally {
118
- _iterator2.f();
87
+ _iterator.f();
119
88
  }
120
89
  });
121
90
  }
@@ -211,6 +211,51 @@ function computeIODeviceBoundingBoxes(componentObject) {
211
211
  return results;
212
212
  }
213
213
 
214
+ /**
215
+ * Computes individual world-space bounding boxes for each connector child
216
+ * of a component.
217
+ *
218
+ * @param {THREE.Object3D} componentObject - The component's Three.js object
219
+ * @returns {Array<{uuid: string, userData: Object, worldBoundingBox: {min: number[], max: number[]}}>}
220
+ * Array of connector bounding box descriptors ready for injection into scene data
221
+ */
222
+ function computeConnectorBoundingBoxes(componentObject) {
223
+ 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);
232
+
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
+ });
250
+ }
251
+ } catch (err) {
252
+ _iterator2.e(err);
253
+ } finally {
254
+ _iterator2.f();
255
+ }
256
+ return results;
257
+ }
258
+
214
259
  /**
215
260
  * Creates bounding box helpers for a selected object. For smart components
216
261
  * (components with io-device children), this produces:
@@ -269,8 +314,8 @@ function createSelectionBoxHelpers(object) {
269
314
 
270
315
  // Check if this object has io-device children (smart component)
271
316
  var hasIODevices = (_object$children = object.children) === null || _object$children === void 0 ? void 0 : _object$children.some(function (child) {
272
- var _child$userData2;
273
- return ((_child$userData2 = child.userData) === null || _child$userData2 === void 0 ? void 0 : _child$userData2.objectType) === 'io-device';
317
+ var _child$userData3;
318
+ return ((_child$userData3 = child.userData) === null || _child$userData3 === void 0 ? void 0 : _child$userData3.objectType) === 'io-device';
274
319
  });
275
320
  if (hasIODevices) {
276
321
  // 1. Create filtered helper for the component body
@@ -307,11 +352,11 @@ function createSelectionBoxHelpers(object) {
307
352
  * @param {THREE.Scene} scene - The scene (for finding objects by uuid)
308
353
  */
309
354
  function updateSelectionBoxHelpers(helpers, selectedObjects, scene) {
310
- var _iterator2 = _rollupPluginBabelHelpers.createForOfIteratorHelper(helpers),
311
- _step2;
355
+ var _iterator3 = _rollupPluginBabelHelpers.createForOfIteratorHelper(helpers),
356
+ _step3;
312
357
  try {
313
358
  var _loop = function _loop() {
314
- var helper = _step2.value;
359
+ var helper = _step3.value;
315
360
  var _helper$userData = helper.userData,
316
361
  sourceObjectUuid = _helper$userData.sourceObjectUuid,
317
362
  isFiltered = _helper$userData.isFiltered,
@@ -342,16 +387,17 @@ function updateSelectionBoxHelpers(helpers, selectedObjects, scene) {
342
387
  helper.update();
343
388
  }
344
389
  };
345
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
390
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
346
391
  if (_loop()) continue;
347
392
  }
348
393
  } catch (err) {
349
- _iterator2.e(err);
394
+ _iterator3.e(err);
350
395
  } finally {
351
- _iterator2.f();
396
+ _iterator3.f();
352
397
  }
353
398
  }
354
399
 
400
+ exports.computeConnectorBoundingBoxes = computeConnectorBoundingBoxes;
355
401
  exports.computeFilteredBoundingBox = computeFilteredBoundingBox;
356
402
  exports.computeFilteredBoundingBoxCached = computeFilteredBoundingBoxCached;
357
403
  exports.computeIODeviceBoundingBoxes = computeIODeviceBoundingBoxes;
@@ -27,7 +27,7 @@ function attachIODevicesToComponent(_x, _x2, _x3, _x4) {
27
27
  }
28
28
  function _attachIODevicesToComponent() {
29
29
  _attachIODevicesToComponent = _rollupPluginBabelHelpers.asyncToGenerator(/*#__PURE__*/_rollupPluginBabelHelpers.regenerator().m(function _callee(componentModel, componentData, modelPreloader, parentComponentId) {
30
- var attachedDevices, _i, _Object$entries, _Object$entries$_i, attachmentId, attachment, _modelPreloader$compo, _deviceData$ioConfig, _deviceData$ioConfig2, _deviceData$ioConfig3, _attachment$attachmen, _attachment$attachmen2, deviceData, cachedDevice, _modelPreloader$loadi, deviceModel, pos, rot, deg2rad, _t, _t2;
30
+ var attachedDevices, _i, _Object$entries, _Object$entries$_i, attachmentId, attachment, _modelPreloader$compo, _attachment$attachmen, _attachment$attachmen2, deviceData, cachedDevice, _modelPreloader$loadi, deviceModel, pos, rot, deg2rad, _t, _t2;
31
31
  return _rollupPluginBabelHelpers.regenerator().w(function (_context) {
32
32
  while (1) switch (_context.n) {
33
33
  case 0:
@@ -105,20 +105,14 @@ function _attachIODevicesToComponent() {
105
105
  // Name the device model
106
106
  deviceModel.name = "".concat(attachment.attachmentLabel || 'IO Device', " (").concat(attachmentId, ")");
107
107
 
108
- // Set user data for identification — include ioConfig data points so the
109
- // component tooltip can render state displays without an extra lookup.
108
+ // Set user data for identification
110
109
  deviceModel.userData = {
111
110
  objectType: 'io-device',
112
111
  deviceId: attachment.deviceId,
113
112
  attachmentId: attachmentId,
114
113
  attachmentLabel: attachment.attachmentLabel,
115
114
  parentComponentId: parentComponentId,
116
- deviceName: deviceData.name || '',
117
- // Snapshot of the device's data point definitions (stateType, stateConfig, direction, etc.)
118
- // ioConfig can use either 'states' (preferred) or legacy 'dataPoints' as the array key
119
- dataPoints: ((_deviceData$ioConfig = deviceData.ioConfig) === null || _deviceData$ioConfig === void 0 ? void 0 : _deviceData$ioConfig.states) || ((_deviceData$ioConfig2 = deviceData.ioConfig) === null || _deviceData$ioConfig2 === void 0 ? void 0 : _deviceData$ioConfig2.dataPoints) || [],
120
- // Device-level I/O direction: 'input' means the user can write state via the tooltip
121
- ioDirection: ((_deviceData$ioConfig3 = deviceData.ioConfig) === null || _deviceData$ioConfig3 === void 0 ? void 0 : _deviceData$ioConfig3.direction) || 'output'
115
+ deviceName: deviceData.name || ''
122
116
  };
123
117
 
124
118
  // Position at the attachment point
@@ -33,7 +33,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
33
33
  * Initialize the CentralPlant manager
34
34
  *
35
35
  * @constructor
36
- * @version 0.3.46
36
+ * @version 0.3.47
37
37
  * @updated 2025-10-22
38
38
  *
39
39
  * @description Creates a new CentralPlant instance and initializes internal managers and utilities.
@@ -1333,7 +1333,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
1333
1333
  * List all available I/O Device assets from the component dictionary.
1334
1334
  * @param {Object} [options={}]
1335
1335
  * @param {'all'|'bundled'|'user'} [options.source='all'] - Filter by asset origin
1336
- * @returns {Array<{uuid: string, name: string, assetType: string, ioConfig: Object}>}
1336
+ * @returns {Array<{uuid: string, name: string, assetType: string}>}
1337
1337
  * @example
1338
1338
  * const devices = centralPlant.getIoDevices({ source: 'all' })
1339
1339
  * const bundledOnly = centralPlant.getIoDevices({ source: 'bundled' })
@@ -1360,8 +1360,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
1360
1360
  return {
1361
1361
  uuid: entry.uuid || entry.id,
1362
1362
  name: entry.name,
1363
- assetType: entry.assetType,
1364
- ioConfig: entry.ioConfig || {}
1363
+ assetType: entry.assetType
1365
1364
  };
1366
1365
  });
1367
1366
  }
@@ -643,7 +643,6 @@ var sceneViewer = /*#__PURE__*/function (_BaseDisposable) {
643
643
  }, {
644
644
  key: "updatePaths",
645
645
  value: function updatePaths() {
646
- console.log("updatePaths started");
647
646
  // Delegate pathfinding update to PathfindingManager
648
647
  if (this.pathfindingManager) {
649
648
  this.pathfindingManager.updatePathfindingAfterTransform(this.currentSceneData);
@@ -513,8 +513,7 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
513
513
  /**
514
514
  * Return tooltip-compatible data point definitions derived from the loaded
515
515
  * animation entries for a given attachment. Used by componentTooltipManager
516
- * to replace the static ioConfig.states[] snapshot with the richer animation
517
- * state definitions created in the Animate window.
516
+ * to provide the richer animation state definitions created in the Animate window.
518
517
  *
519
518
  * One dp object is emitted per unique stateVariable; multiple mesh entries
520
519
  * that share the same stateVariable are collapsed into one.
@@ -864,7 +864,6 @@ var ComponentDataManager = /*#__PURE__*/function (_BaseDisposable) {
864
864
  };
865
865
  return _objectSpread2(_objectSpread2({}, baseData), {}, {
866
866
  metadata: component.metadata || {},
867
- ioConfig: component.ioConfig || null,
868
867
  boundingBox: component.boundingBox || null,
869
868
  adaptedBoundingBox: component.adaptedBoundingBox || null,
870
869
  children: component.children || [],
@@ -321,7 +321,7 @@ var ComponentDragManager = /*#__PURE__*/function (_BaseDisposable) {
321
321
  key: "_attachIODeviceModelsToPreview",
322
322
  value: (function () {
323
323
  var _attachIODeviceModelsToPreview2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee3(parentObject, componentData, modelPreloader) {
324
- var _i, _Object$entries, _Object$entries$_i, attachmentId, attachment, _modelPreloader$compo, _deviceData$ioConfig, _deviceData$ioConfig2, _deviceData$ioConfig3, _attachment$attachmen, _attachment$attachmen2, deviceData, cachedDevice, _modelPreloader$loadi, deviceModel, pos, rot, deg2rad, _t3;
324
+ var _i, _Object$entries, _Object$entries$_i, attachmentId, attachment, _modelPreloader$compo, _attachment$attachmen, _attachment$attachmen2, deviceData, cachedDevice, _modelPreloader$loadi, deviceModel, pos, rot, deg2rad, _t3;
325
325
  return _regenerator().w(function (_context3) {
326
326
  while (1) switch (_context3.n) {
327
327
  case 0:
@@ -384,12 +384,7 @@ var ComponentDragManager = /*#__PURE__*/function (_BaseDisposable) {
384
384
  deviceId: attachment.deviceId,
385
385
  attachmentId: attachmentId,
386
386
  attachmentLabel: attachment.attachmentLabel,
387
- deviceName: deviceData.name || '',
388
- // Snapshot of data point definitions so the tooltip can render state
389
- // ioConfig can use either 'states' (preferred) or legacy 'dataPoints' as the array key
390
- dataPoints: ((_deviceData$ioConfig = deviceData.ioConfig) === null || _deviceData$ioConfig === void 0 ? void 0 : _deviceData$ioConfig.states) || ((_deviceData$ioConfig2 = deviceData.ioConfig) === null || _deviceData$ioConfig2 === void 0 ? void 0 : _deviceData$ioConfig2.dataPoints) || [],
391
- // Device-level I/O direction: 'input' means the user can write state via the tooltip
392
- ioDirection: ((_deviceData$ioConfig3 = deviceData.ioConfig) === null || _deviceData$ioConfig3 === void 0 ? void 0 : _deviceData$ioConfig3.direction) || 'output'
387
+ deviceName: deviceData.name || ''
393
388
  };
394
389
  if ((_attachment$attachmen = attachment.attachmentPoint) !== null && _attachment$attachmen !== void 0 && _attachment$attachmen.position) {
395
390
  pos = attachment.attachmentPoint.position;
@@ -3,7 +3,7 @@ import * as THREE from 'three';
3
3
  import { Pathfinder } from '@2112-lab/pathfinder';
4
4
  import { BaseDisposable } from '../../core/baseDisposable.js';
5
5
  import { PathData } from '../../core/pathfindingData.js';
6
- import { computeFilteredBoundingBox, computeIODeviceBoundingBoxes } from '../../utils/boundingBoxUtils.js';
6
+ import { computeFilteredBoundingBox, computeIODeviceBoundingBoxes, computeConnectorBoundingBoxes } from '../../utils/boundingBoxUtils.js';
7
7
  import { SceneDataManager } from './sceneDataManager.js';
8
8
  import { PathRenderingManager } from './PathRenderingManager.js';
9
9
  import { ConnectorManager } from './ConnectorManager.js';
@@ -304,6 +304,31 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
304
304
  }
305
305
  });
306
306
  }
307
+
308
+ // Also reinject connectors from cache
309
+ if (_cached.connectorBBoxes && _cached.connectorBBoxes.length > 0) {
310
+ if (!_enrichedChild.children) _enrichedChild.children = [];
311
+ _cached.connectorBBoxes.forEach(function (connBBox) {
312
+ var existingIndex = _enrichedChild.children.findIndex(function (c) {
313
+ return c.uuid === connBBox.uuid;
314
+ });
315
+ if (existingIndex >= 0) {
316
+ _enrichedChild.children[existingIndex] = _objectSpread2(_objectSpread2({}, _enrichedChild.children[existingIndex]), {}, {
317
+ userData: _objectSpread2(_objectSpread2({}, _enrichedChild.children[existingIndex].userData), {}, {
318
+ worldBoundingBox: connBBox.worldBoundingBox
319
+ })
320
+ });
321
+ } else {
322
+ _enrichedChild.children.push({
323
+ uuid: connBBox.uuid,
324
+ userData: _objectSpread2(_objectSpread2({}, connBBox.userData), {}, {
325
+ worldBoundingBox: connBBox.worldBoundingBox
326
+ }),
327
+ children: []
328
+ });
329
+ }
330
+ });
331
+ }
307
332
  return _enrichedChild;
308
333
  }
309
334
 
@@ -361,11 +386,40 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
361
386
  console.log("\uD83D\uDCE6 Injected ".concat(ioDeviceBBoxes.length, " io-device bounding box(es) for component ").concat(child.uuid));
362
387
  }
363
388
 
389
+ // PHASE 2: Enrich Connectors (CRITICAL for pathfinding API)
390
+ // Also compute world bounding boxes for connectors and inject into children.
391
+ // This ensures endpoints have world coordinates in sceneDataCopy.
392
+ var connectorBBoxes = computeConnectorBoundingBoxes(componentObject);
393
+ if (connectorBBoxes.length > 0) {
394
+ if (!enrichedChild.children) enrichedChild.children = [];
395
+ connectorBBoxes.forEach(function (connBBox) {
396
+ var existingIndex = enrichedChild.children.findIndex(function (c) {
397
+ return c.uuid === connBBox.uuid;
398
+ });
399
+ if (existingIndex >= 0) {
400
+ enrichedChild.children[existingIndex] = _objectSpread2(_objectSpread2({}, enrichedChild.children[existingIndex]), {}, {
401
+ userData: _objectSpread2(_objectSpread2({}, enrichedChild.children[existingIndex].userData), {}, {
402
+ worldBoundingBox: connBBox.worldBoundingBox
403
+ })
404
+ });
405
+ } else {
406
+ enrichedChild.children.push({
407
+ uuid: connBBox.uuid,
408
+ userData: _objectSpread2(_objectSpread2({}, connBBox.userData), {}, {
409
+ worldBoundingBox: connBBox.worldBoundingBox
410
+ }),
411
+ children: []
412
+ });
413
+ }
414
+ });
415
+ }
416
+
364
417
  // Store in cache
365
418
  _this3._bboxCache.set(child.uuid, {
366
419
  matrixHash: _hash,
367
420
  filteredBBox: _bboxData,
368
- ioDeviceBBoxes: ioDeviceBBoxes
421
+ ioDeviceBBoxes: ioDeviceBBoxes,
422
+ connectorBBoxes: connectorBBoxes
369
423
  });
370
424
  return enrichedChild;
371
425
  } else {
@@ -40,8 +40,7 @@ var CollisionManager = /*#__PURE__*/function () {
40
40
  if (child === object || _this._isDescendantOf(object, child)) return;
41
41
 
42
42
  // Filter by CP object types at the root level (skip internal meshes during traverse)
43
- var isCPRootObject = ((_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.objectType) === 'component' || ((_child$userData2 = child.userData) === null || _child$userData2 === void 0 ? void 0 : _child$userData2.objectType) === 'segment' || ((_child$userData3 = child.userData) === null || _child$userData3 === void 0 ? void 0 : _child$userData3.objectType) === 'gateway' || ((_child$userData4 = child.userData) === null || _child$userData4 === void 0 ? void 0 : _child$userData4.objectType) === 'connector' || ((_child$userData5 = child.userData) === null || _child$userData5 === void 0 ? void 0 : _child$userData5.ioConfig); // IO Device
44
-
43
+ var isCPRootObject = ((_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.objectType) === 'component' || ((_child$userData2 = child.userData) === null || _child$userData2 === void 0 ? void 0 : _child$userData2.objectType) === 'segment' || ((_child$userData3 = child.userData) === null || _child$userData3 === void 0 ? void 0 : _child$userData3.objectType) === 'gateway' || ((_child$userData4 = child.userData) === null || _child$userData4 === void 0 ? void 0 : _child$userData4.objectType) === 'connector' || ((_child$userData5 = child.userData) === null || _child$userData5 === void 0 ? void 0 : _child$userData5.objectType) === 'io-device';
45
44
  if (isCPRootObject && !_this._shouldExclude(child, excludeTypes)) {
46
45
  // Use cached worldBoundingBox if available, otherwise compute it (and cache it)
47
46
  var childBBox;
@@ -458,8 +458,7 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
458
458
  if (((_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.objectType) === 'io-device') {
459
459
  var attachmentId = child.userData.attachmentId || '';
460
460
 
461
- // Use only data points from the animate window (behaviorConfig).
462
- // The static ioConfig.states[] snapshot on userData is intentionally ignored.
461
+ // Use data points from the animate window (behaviorConfig).
463
462
  var dataPoints = resolveDataPoints(parentUuid, attachmentId, child.userData, getIoBehaviorManager(_this3.sceneViewer));
464
463
 
465
464
  // When data points come from behaviorConfig they already carry direction:'input'.
@@ -672,7 +671,7 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
672
671
  * Input / bidirectional → shows an interactive control.
673
672
  *
674
673
  * @param {string} scopedAttachmentId - Scoped attachment ID (parentUuid::attachmentId) for state isolation
675
- * @param {Object} dp - data point definition from ioConfig.dataPoints
674
+ * @param {Object} dp - data point definition
676
675
  * @param {string} [deviceDirection] - device-level direction ('input'|'output'), overrides dp.direction
677
676
  * @param {string} [originalAttachmentId] - Original attachment ID for behavior triggering
678
677
  * @returns {HTMLElement}
@@ -1,4 +1,4 @@
1
- import { createClass as _createClass, classCallCheck as _classCallCheck, toConsumableArray as _toConsumableArray, asyncToGenerator as _asyncToGenerator, regenerator as _regenerator } from '../../../_virtual/_rollupPluginBabelHelpers.js';
1
+ import { createClass as _createClass, classCallCheck as _classCallCheck, toConsumableArray as _toConsumableArray, objectSpread2 as _objectSpread2, asyncToGenerator as _asyncToGenerator, regenerator as _regenerator } from '../../../_virtual/_rollupPluginBabelHelpers.js';
2
2
  import 'three';
3
3
  import { GLTFExporter } from '../../../node_modules/three/examples/jsm/exporters/GLTFExporter.js';
4
4
  import { getHardcodedUuid } from '../../utils/nameUtils.js';
@@ -219,11 +219,11 @@ var SceneExportManager = /*#__PURE__*/function () {
219
219
  };
220
220
  }
221
221
 
222
- // For components: no children exported connector positions are defined in the component
223
- // dictionary GLB and will be re-injected at import time via _injectConnectorChildrenFromDictionary.
224
- // Exporting them here would prevent that injection (it skips if children already exist)
225
- // and would leave the pathfinder with connectors in incompatible local-position format.
222
+ // For components: only export child connectors if they were manually added/defined.
223
+ // Most connectors are injected from dictionary at import time, but some (like manually placed ones)
224
+ // need to be persisted to maintain connections in the exported scene.
226
225
  if (threeObject.children && threeObject.children.length > 0) {
226
+ var _threeObject$userData11;
227
227
  var exportableChildren = [];
228
228
  if (isManualSegment) {
229
229
  // For manual segments, export their connector children
@@ -237,6 +237,25 @@ var SceneExportManager = /*#__PURE__*/function () {
237
237
  }
238
238
  }
239
239
  });
240
+ } else if (((_threeObject$userData11 = threeObject.userData) === null || _threeObject$userData11 === void 0 ? void 0 : _threeObject$userData11.objectType) === 'component') {
241
+ // For components, only export connectors that have objectType='connector'
242
+ // Standard dictionary-injected connectors should be exported so connections work on re-import
243
+ threeObject.children.forEach(function (child) {
244
+ var _child$userData2;
245
+ if (((_child$userData2 = child.userData) === null || _child$userData2 === void 0 ? void 0 : _child$userData2.objectType) === 'connector') {
246
+ exportableChildren.push({
247
+ uuid: child.uuid,
248
+ name: child.name,
249
+ type: 'Mesh',
250
+ position: {
251
+ x: roundIfClose(child.position.x),
252
+ y: roundIfClose(child.position.y),
253
+ z: roundIfClose(child.position.z)
254
+ },
255
+ userData: _objectSpread2({}, child.userData)
256
+ });
257
+ }
258
+ });
240
259
  }
241
260
  if (exportableChildren.length > 0) {
242
261
  jsonObject.children = exportableChildren;
@@ -251,9 +270,9 @@ var SceneExportManager = /*#__PURE__*/function () {
251
270
  // Extract main scene objects (components and standalone connectors)
252
271
  var sceneChildren = [];
253
272
  this.sceneViewer.scene.children.forEach(function (child) {
254
- var _child$userData2;
273
+ var _child$userData3;
255
274
  // Only export components and connectors; skip segments, gateways, polylines, etc.
256
- var objectType = (_child$userData2 = child.userData) === null || _child$userData2 === void 0 ? void 0 : _child$userData2.objectType;
275
+ var objectType = (_child$userData3 = child.userData) === null || _child$userData3 === void 0 ? void 0 : _child$userData3.objectType;
257
276
  if (objectType !== 'component' && objectType !== 'connector') {
258
277
  return;
259
278
  }
@@ -417,14 +436,14 @@ var SceneExportManager = /*#__PURE__*/function () {
417
436
  BufferGeometryUtils = BufferGeometryUtilsModule.BufferGeometryUtils || BufferGeometryUtilsModule.default || BufferGeometryUtilsModule; // Create a new scene for export instead of cloning
418
437
  exportScene = new _THREE.Scene(); // Helper function to check if an object should be exported
419
438
  shouldExport = function shouldExport(child) {
420
- var _child$name, _child$userData3, _child$userData4, _child$userData5, _child$userData6;
439
+ var _child$name, _child$userData4, _child$userData5, _child$userData6, _child$userData7;
421
440
  if ((_child$name = child.name) !== null && _child$name !== void 0 && _child$name.includes('Polyline')) return false; // Will handle separately
422
441
  if (child.name === 'fogPlane') return false; // Skip fog plane
423
- if ((_child$userData3 = child.userData) !== null && _child$userData3 !== void 0 && _child$userData3.isBrickWall) return false; // Skip environment
424
- if ((_child$userData4 = child.userData) !== null && _child$userData4 !== void 0 && _child$userData4.isBaseGround) return false; // Skip environment
425
- if ((_child$userData5 = child.userData) !== null && _child$userData5 !== void 0 && _child$userData5.isBaseGrid) return false; // Skip environment
442
+ if ((_child$userData4 = child.userData) !== null && _child$userData4 !== void 0 && _child$userData4.isBrickWall) return false; // Skip environment
443
+ if ((_child$userData5 = child.userData) !== null && _child$userData5 !== void 0 && _child$userData5.isBaseGround) return false; // Skip environment
444
+ if ((_child$userData6 = child.userData) !== null && _child$userData6 !== void 0 && _child$userData6.isBaseGrid) return false; // Skip environment
426
445
  if (child.isLight) return false; // Skip lights
427
- if ((_child$userData6 = child.userData) !== null && _child$userData6 !== void 0 && _child$userData6.isTransformControls) return false; // Skip transform controls
446
+ if ((_child$userData7 = child.userData) !== null && _child$userData7 !== void 0 && _child$userData7.isTransformControls) return false; // Skip transform controls
428
447
  if (child.isTransformControls) return false; // Skip transform controls
429
448
  if (child.type && child.type.includes('TransformControls')) return false;
430
449
  if (child.type && child.type.includes('Helper')) return false; // Skip helpers