@2112-lab/central-plant 0.3.46 → 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.
Files changed (39) hide show
  1. package/dist/bundle/index.js +624 -425
  2. package/dist/cjs/src/core/centralPlant.js +46 -46
  3. package/dist/cjs/src/core/centralPlantInternals.js +4 -2
  4. package/dist/cjs/src/core/sceneViewer.js +1 -2
  5. package/dist/cjs/src/managers/behaviors/IoBehaviorManager.js +1 -2
  6. package/dist/cjs/src/managers/components/componentDataManager.js +0 -1
  7. package/dist/cjs/src/managers/components/componentManager.js +11 -3
  8. package/dist/cjs/src/managers/components/transformOperationsManager.js +9 -11
  9. package/dist/cjs/src/managers/controls/componentDragManager.js +2 -7
  10. package/dist/cjs/src/managers/controls/transformControlsManager.js +83 -44
  11. package/dist/cjs/src/managers/pathfinding/pathfindingManager.js +274 -192
  12. package/dist/cjs/src/managers/scene/collisionManager.js +1 -2
  13. package/dist/cjs/src/managers/scene/componentTooltipManager.js +2 -3
  14. package/dist/cjs/src/managers/scene/modelManager.js +33 -10
  15. package/dist/cjs/src/managers/scene/sceneExportManager.js +42 -12
  16. package/dist/cjs/src/managers/scene/sceneOperationsManager.js +26 -1
  17. package/dist/cjs/src/utils/behaviorDispatch.js +11 -42
  18. package/dist/cjs/src/utils/boundingBoxUtils.js +79 -36
  19. package/dist/cjs/src/utils/ioDeviceUtils.js +3 -9
  20. package/dist/esm/src/core/centralPlant.js +46 -46
  21. package/dist/esm/src/core/centralPlantInternals.js +4 -2
  22. package/dist/esm/src/core/sceneViewer.js +1 -2
  23. package/dist/esm/src/managers/behaviors/IoBehaviorManager.js +1 -2
  24. package/dist/esm/src/managers/components/componentDataManager.js +0 -1
  25. package/dist/esm/src/managers/components/componentManager.js +11 -3
  26. package/dist/esm/src/managers/components/transformOperationsManager.js +9 -11
  27. package/dist/esm/src/managers/controls/componentDragManager.js +2 -7
  28. package/dist/esm/src/managers/controls/transformControlsManager.js +83 -44
  29. package/dist/esm/src/managers/pathfinding/pathfindingManager.js +276 -194
  30. package/dist/esm/src/managers/scene/collisionManager.js +1 -2
  31. package/dist/esm/src/managers/scene/componentTooltipManager.js +2 -3
  32. package/dist/esm/src/managers/scene/modelManager.js +33 -10
  33. package/dist/esm/src/managers/scene/sceneExportManager.js +41 -13
  34. package/dist/esm/src/managers/scene/sceneOperationsManager.js +26 -1
  35. package/dist/esm/src/utils/behaviorDispatch.js +11 -42
  36. package/dist/esm/src/utils/boundingBoxUtils.js +80 -38
  37. package/dist/esm/src/utils/ioDeviceUtils.js +3 -9
  38. package/dist/index.d.ts +0 -6
  39. package/package.json +1 -1
@@ -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}
@@ -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
@@ -241,11 +248,11 @@ var SceneExportManager = /*#__PURE__*/function () {
241
248
  };
242
249
  }
243
250
 
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.
251
+ // For components: only export child connectors if they were manually added/defined.
252
+ // Most connectors are injected from dictionary at import time, but some (like manually placed ones)
253
+ // need to be persisted to maintain connections in the exported scene.
248
254
  if (threeObject.children && threeObject.children.length > 0) {
255
+ var _threeObject$userData11;
249
256
  var exportableChildren = [];
250
257
  if (isManualSegment) {
251
258
  // For manual segments, export their connector children
@@ -259,6 +266,29 @@ var SceneExportManager = /*#__PURE__*/function () {
259
266
  }
260
267
  }
261
268
  });
269
+ } else if (((_threeObject$userData11 = threeObject.userData) === null || _threeObject$userData11 === void 0 ? void 0 : _threeObject$userData11.objectType) === 'component') {
270
+ // For components, export all connectors (including deep children within GLFs)
271
+ threeObject.traverse(function (child) {
272
+ var _child$userData2;
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);
279
+ exportableChildren.push({
280
+ uuid: child.uuid,
281
+ name: child.name,
282
+ type: 'Mesh',
283
+ position: {
284
+ x: roundIfClose(relativePos.x),
285
+ y: roundIfClose(relativePos.y),
286
+ z: roundIfClose(relativePos.z)
287
+ },
288
+ userData: _rollupPluginBabelHelpers.objectSpread2({}, child.userData)
289
+ });
290
+ }
291
+ });
262
292
  }
263
293
  if (exportableChildren.length > 0) {
264
294
  jsonObject.children = exportableChildren;
@@ -273,9 +303,9 @@ var SceneExportManager = /*#__PURE__*/function () {
273
303
  // Extract main scene objects (components and standalone connectors)
274
304
  var sceneChildren = [];
275
305
  this.sceneViewer.scene.children.forEach(function (child) {
276
- var _child$userData2;
306
+ var _child$userData3;
277
307
  // 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;
308
+ var objectType = (_child$userData3 = child.userData) === null || _child$userData3 === void 0 ? void 0 : _child$userData3.objectType;
279
309
  if (objectType !== 'component' && objectType !== 'connector') {
280
310
  return;
281
311
  }
@@ -439,14 +469,14 @@ var SceneExportManager = /*#__PURE__*/function () {
439
469
  BufferGeometryUtils = BufferGeometryUtilsModule.BufferGeometryUtils || BufferGeometryUtilsModule.default || BufferGeometryUtilsModule; // Create a new scene for export instead of cloning
440
470
  exportScene = new _THREE.Scene(); // Helper function to check if an object should be exported
441
471
  shouldExport = function shouldExport(child) {
442
- var _child$name, _child$userData3, _child$userData4, _child$userData5, _child$userData6;
472
+ var _child$name, _child$userData4, _child$userData5, _child$userData6, _child$userData7;
443
473
  if ((_child$name = child.name) !== null && _child$name !== void 0 && _child$name.includes('Polyline')) return false; // Will handle separately
444
474
  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
475
+ if ((_child$userData4 = child.userData) !== null && _child$userData4 !== void 0 && _child$userData4.isBrickWall) return false; // Skip environment
476
+ if ((_child$userData5 = child.userData) !== null && _child$userData5 !== void 0 && _child$userData5.isBaseGround) return false; // Skip environment
477
+ if ((_child$userData6 = child.userData) !== null && _child$userData6 !== void 0 && _child$userData6.isBaseGrid) return false; // Skip environment
448
478
  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
479
+ if ((_child$userData7 = child.userData) !== null && _child$userData7 !== void 0 && _child$userData7.isTransformControls) return false; // Skip transform controls
450
480
  if (child.isTransformControls) return false; // Skip transform controls
451
481
  if (child.type && child.type.includes('TransformControls')) return false;
452
482
  if (child.type && child.type.includes('Helper')) return false; // Skip helpers
@@ -1344,9 +1344,34 @@ 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 (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
+ });
1374
+
1350
1375
  // Add the component to the scene data
1351
1376
  if (!currentSceneData.scene.children) {
1352
1377
  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
  }
@@ -179,35 +179,77 @@ 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
+ });
206
+ return results;
207
+ }
208
+
209
+ /**
210
+ * Computes individual world-space bounding boxes for each connector child
211
+ * of a component. Supports deep children (e.g. within GLB model hierarchy).
212
+ *
213
+ * @param {THREE.Object3D} componentObject - The component's Three.js object
214
+ * @returns {Array<{uuid: string, userData: Object, worldBoundingBox: {min: number[], max: number[]}}>}
215
+ * Array of connector bounding box descriptors ready for injection into scene data
216
+ */
217
+ function computeConnectorBoundingBoxes(componentObject) {
218
+ var results = [];
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);
225
+
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));
238
+ }
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
+ });
211
253
  return results;
212
254
  }
213
255
 
@@ -269,8 +311,8 @@ function createSelectionBoxHelpers(object) {
269
311
 
270
312
  // Check if this object has io-device children (smart component)
271
313
  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';
314
+ var _child$userData3;
315
+ return ((_child$userData3 = child.userData) === null || _child$userData3 === void 0 ? void 0 : _child$userData3.objectType) === 'io-device';
274
316
  });
275
317
  if (hasIODevices) {
276
318
  // 1. Create filtered helper for the component body
@@ -307,11 +349,11 @@ function createSelectionBoxHelpers(object) {
307
349
  * @param {THREE.Scene} scene - The scene (for finding objects by uuid)
308
350
  */
309
351
  function updateSelectionBoxHelpers(helpers, selectedObjects, scene) {
310
- var _iterator2 = _rollupPluginBabelHelpers.createForOfIteratorHelper(helpers),
311
- _step2;
352
+ var _iterator = _rollupPluginBabelHelpers.createForOfIteratorHelper(helpers),
353
+ _step;
312
354
  try {
313
355
  var _loop = function _loop() {
314
- var helper = _step2.value;
356
+ var helper = _step.value;
315
357
  var _helper$userData = helper.userData,
316
358
  sourceObjectUuid = _helper$userData.sourceObjectUuid,
317
359
  isFiltered = _helper$userData.isFiltered,
@@ -342,16 +384,17 @@ function updateSelectionBoxHelpers(helpers, selectedObjects, scene) {
342
384
  helper.update();
343
385
  }
344
386
  };
345
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
387
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
346
388
  if (_loop()) continue;
347
389
  }
348
390
  } catch (err) {
349
- _iterator2.e(err);
391
+ _iterator.e(err);
350
392
  } finally {
351
- _iterator2.f();
393
+ _iterator.f();
352
394
  }
353
395
  }
354
396
 
397
+ exports.computeConnectorBoundingBoxes = computeConnectorBoundingBoxes;
355
398
  exports.computeFilteredBoundingBox = computeFilteredBoundingBox;
356
399
  exports.computeFilteredBoundingBoxCached = computeFilteredBoundingBoxCached;
357
400
  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