@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
@@ -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,5 +1,6 @@
1
1
  import { createClass as _createClass, objectSpread2 as _objectSpread2, toConsumableArray as _toConsumableArray, typeof as _typeof, classCallCheck as _classCallCheck, asyncToGenerator as _asyncToGenerator, regenerator as _regenerator, createForOfIteratorHelper as _createForOfIteratorHelper } from '../../../_virtual/_rollupPluginBabelHelpers.js';
2
2
  import * as THREE from 'three';
3
+ import { getHardcodedUuid } from '../../utils/nameUtils.js';
3
4
  import { attachIODevicesToComponent } from '../../utils/ioDeviceUtils.js';
4
5
  import { registerBehaviorsForComponent } from '../../utils/behaviorRegistration.js';
5
6
  import modelPreloader from '../../rendering/modelPreloader.js';
@@ -138,36 +139,58 @@ var ModelManager = /*#__PURE__*/function () {
138
139
  * @param {THREE.Object3D} targetMesh - The mesh whose connectors to preserve
139
140
  * @param {string} parentUuid - The UUID of the parent component
140
141
  */
142
+ /**
143
+ * Identifies all connector objects within a model hierarchy and prepares them
144
+ * to be preserved when the model is replaced or modified.
145
+ * Uses traverse() to find connectors at any depth in the hierarchy.
146
+ * @param {THREE.Object3D} targetMesh - The root of the model to search
147
+ * @param {string} parentUuid - The UUID of the parent component
148
+ * @returns {Array<THREE.Object3D>} Array of cloned and prepared connector objects
149
+ */
141
150
  }, {
142
151
  key: "_preserveConnectorChildren",
143
152
  value: function _preserveConnectorChildren(targetMesh, parentUuid) {
144
153
  var _this = this;
145
154
  var connectorChildren = [];
146
- targetMesh.children.forEach(function (child, index) {
155
+ var connectorIndex = 0;
156
+
157
+ // Use traverse to find connectors at ANY depth in the hierarchy
158
+ targetMesh.traverse(function (child) {
147
159
  var _child$userData;
148
160
  var isConnectorGeometry = child.geometry && (child.geometry.uuid === 'CONNECTOR-GEO' || child.geometry.type === 'SphereGeometry' && child.geometry.parameters);
149
161
  var isConnectorByName = child.name && child.name.toLowerCase().includes('connector');
150
- // Also recognise connectors declared via userData (e.g. inline connectors from SAMPLE_1.json
151
- // whose geometry may be a fallback BufferGeometry rather than a SphereGeometry).
152
162
  var isConnectorByUserData = ((_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.objectType) === 'connector';
153
163
  if (isConnectorGeometry && isConnectorByName || isConnectorByUserData) {
164
+ connectorIndex++;
165
+
154
166
  // Ensure userData exists and has worldBoundingBox
155
167
  if (!child.userData) child.userData = {};
156
168
  if (!child.userData.worldBoundingBox) {
157
169
  child.userData.worldBoundingBox = _this._calculateWorldBoundingBox(child);
158
170
  }
159
171
 
160
- // Clone and preserve critical userData
172
+ // Clone the connector
161
173
  var clonedConnector = child.clone();
162
174
 
163
- // Generate proper connector UUID based on parent component UUID
164
- // Format: COMPONENT-UUID-CONNECTOR-INDEX (matching JSON import pattern)
165
- var connectorUuid = "".concat(parentUuid, "-CONNECTOR-").concat(index + 1);
175
+ // CRITICAL: Convert nested world position to root-relative position
176
+ // This ensures the connector stays in the same place when re-added as a direct child of targetMesh
177
+ var targetInverse = targetMesh.matrixWorld.clone().invert();
178
+ var worldPos = new THREE.Vector3();
179
+ child.getWorldPosition(worldPos);
180
+ clonedConnector.position.copy(worldPos.applyMatrix4(targetInverse));
181
+
182
+ // Get hardcoded UUID if it exists (e.g. from JSON mesh placeholder)
183
+ // This ensures compatibility with existing scene JSON naming conventions
184
+ var existingUuid = getHardcodedUuid(child);
185
+
186
+ // Generate fallback UUID only if existing one isn't already parent-prefixed
187
+ // Format: COMPONENT-UUID-CONNECTOR-INDEX (stable fallback)
188
+ var connectorUuid = existingUuid && existingUuid.includes(parentUuid) ? existingUuid : "".concat(parentUuid, "-CONNECTOR-").concat(connectorIndex);
166
189
  clonedConnector.uuid = connectorUuid;
167
190
  if (child.userData) {
168
191
  clonedConnector.userData = _objectSpread2({}, child.userData);
169
192
 
170
- // Deep copy critical data - handle both array [x,y,z] and object {x,y,z} formats
193
+ // Deep copy critical data
171
194
  if (child.userData.worldBoundingBox) {
172
195
  var wbb = child.userData.worldBoundingBox;
173
196
  clonedConnector.userData.worldBoundingBox = {
@@ -176,7 +199,6 @@ var ModelManager = /*#__PURE__*/function () {
176
199
  };
177
200
  }
178
201
  if (child.userData.direction) {
179
- // Handle both array [x,y,z] and object {x,y,z} formats
180
202
  if (Array.isArray(child.userData.direction)) {
181
203
  clonedConnector.userData.direction = _toConsumableArray(child.userData.direction);
182
204
  } else if (_typeof(child.userData.direction) === 'object') {
@@ -188,11 +210,12 @@ var ModelManager = /*#__PURE__*/function () {
188
210
 
189
211
  // Set originalUuid to match the actual uuid (maintains consistency)
190
212
  clonedConnector.userData.originalUuid = connectorUuid;
191
- clonedConnector.userData.objectType = child.userData.objectType || (child.name.toLowerCase().includes('connector') ? 'connector' : 'gateway');
213
+ clonedConnector.userData.objectType = child.userData.objectType || 'connector';
192
214
  }
193
215
  connectorChildren.push(clonedConnector);
194
216
  }
195
217
  });
218
+ console.log("\uD83D\uDEE1\uFE0F Preserved ".concat(connectorChildren.length, " connector(s) for component ").concat(parentUuid));
196
219
  return connectorChildren;
197
220
  }
198
221
 
@@ -1,5 +1,5 @@
1
- import { createClass as _createClass, classCallCheck as _classCallCheck, toConsumableArray as _toConsumableArray, asyncToGenerator as _asyncToGenerator, regenerator as _regenerator } from '../../../_virtual/_rollupPluginBabelHelpers.js';
2
- import 'three';
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
+ import * as THREE from 'three';
3
3
  import { GLTFExporter } from '../../../node_modules/three/examples/jsm/exporters/GLTFExporter.js';
4
4
  import { getHardcodedUuid } from '../../utils/nameUtils.js';
5
5
 
@@ -81,6 +81,11 @@ var SceneExportManager = /*#__PURE__*/function () {
81
81
  // Helper function to convert Three.js object to minimal JSON format
82
82
  var convertObjectToJson = function convertObjectToJson(threeObject) {
83
83
  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;
84
+ // Ensure world matrices are updated for this subtree before exporting
85
+ if (threeObject.updateMatrixWorld) {
86
+ threeObject.updateMatrixWorld(true);
87
+ }
88
+
84
89
  // Skip certain objects that shouldn't be exported
85
90
  if (!threeObject || (_threeObject$name = threeObject.name) !== null && _threeObject$name !== void 0 && _threeObject$name.includes('Polyline') ||
86
91
  // Skip pipe paths
@@ -219,11 +224,11 @@ var SceneExportManager = /*#__PURE__*/function () {
219
224
  };
220
225
  }
221
226
 
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.
227
+ // For components: only export child connectors if they were manually added/defined.
228
+ // Most connectors are injected from dictionary at import time, but some (like manually placed ones)
229
+ // need to be persisted to maintain connections in the exported scene.
226
230
  if (threeObject.children && threeObject.children.length > 0) {
231
+ var _threeObject$userData11;
227
232
  var exportableChildren = [];
228
233
  if (isManualSegment) {
229
234
  // For manual segments, export their connector children
@@ -237,6 +242,29 @@ var SceneExportManager = /*#__PURE__*/function () {
237
242
  }
238
243
  }
239
244
  });
245
+ } else if (((_threeObject$userData11 = threeObject.userData) === null || _threeObject$userData11 === void 0 ? void 0 : _threeObject$userData11.objectType) === 'component') {
246
+ // For components, export all connectors (including deep children within GLFs)
247
+ threeObject.traverse(function (child) {
248
+ var _child$userData2;
249
+ if (((_child$userData2 = child.userData) === null || _child$userData2 === void 0 ? void 0 : _child$userData2.objectType) === 'connector') {
250
+ // Calculate position relative to component root
251
+ var componentMatrixWorldInverse = threeObject.matrixWorld.clone().invert();
252
+ var childWorldPos = new THREE.Vector3();
253
+ child.getWorldPosition(childWorldPos);
254
+ var relativePos = childWorldPos.applyMatrix4(componentMatrixWorldInverse);
255
+ exportableChildren.push({
256
+ uuid: child.uuid,
257
+ name: child.name,
258
+ type: 'Mesh',
259
+ position: {
260
+ x: roundIfClose(relativePos.x),
261
+ y: roundIfClose(relativePos.y),
262
+ z: roundIfClose(relativePos.z)
263
+ },
264
+ userData: _objectSpread2({}, child.userData)
265
+ });
266
+ }
267
+ });
240
268
  }
241
269
  if (exportableChildren.length > 0) {
242
270
  jsonObject.children = exportableChildren;
@@ -251,9 +279,9 @@ var SceneExportManager = /*#__PURE__*/function () {
251
279
  // Extract main scene objects (components and standalone connectors)
252
280
  var sceneChildren = [];
253
281
  this.sceneViewer.scene.children.forEach(function (child) {
254
- var _child$userData2;
282
+ var _child$userData3;
255
283
  // 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;
284
+ var objectType = (_child$userData3 = child.userData) === null || _child$userData3 === void 0 ? void 0 : _child$userData3.objectType;
257
285
  if (objectType !== 'component' && objectType !== 'connector') {
258
286
  return;
259
287
  }
@@ -417,14 +445,14 @@ var SceneExportManager = /*#__PURE__*/function () {
417
445
  BufferGeometryUtils = BufferGeometryUtilsModule.BufferGeometryUtils || BufferGeometryUtilsModule.default || BufferGeometryUtilsModule; // Create a new scene for export instead of cloning
418
446
  exportScene = new _THREE.Scene(); // Helper function to check if an object should be exported
419
447
  shouldExport = function shouldExport(child) {
420
- var _child$name, _child$userData3, _child$userData4, _child$userData5, _child$userData6;
448
+ var _child$name, _child$userData4, _child$userData5, _child$userData6, _child$userData7;
421
449
  if ((_child$name = child.name) !== null && _child$name !== void 0 && _child$name.includes('Polyline')) return false; // Will handle separately
422
450
  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
451
+ if ((_child$userData4 = child.userData) !== null && _child$userData4 !== void 0 && _child$userData4.isBrickWall) return false; // Skip environment
452
+ if ((_child$userData5 = child.userData) !== null && _child$userData5 !== void 0 && _child$userData5.isBaseGround) return false; // Skip environment
453
+ if ((_child$userData6 = child.userData) !== null && _child$userData6 !== void 0 && _child$userData6.isBaseGrid) return false; // Skip environment
426
454
  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
455
+ if ((_child$userData7 = child.userData) !== null && _child$userData7 !== void 0 && _child$userData7.isTransformControls) return false; // Skip transform controls
428
456
  if (child.isTransformControls) return false; // Skip transform controls
429
457
  if (child.type && child.type.includes('TransformControls')) return false;
430
458
  if (child.type && child.type.includes('Helper')) return false; // Skip helpers
@@ -1320,9 +1320,34 @@ var SceneOperationsManager = /*#__PURE__*/function () {
1320
1320
  min: boundingBox.min.toArray(),
1321
1321
  max: boundingBox.max.toArray()
1322
1322
  }
1323
- })
1323
+ }),
1324
+ children: [] // Initialize children array
1324
1325
  };
1325
1326
 
1327
+ // Collect children that are connectors (using traverse to find deep children in models)
1328
+ componentModel.traverse(function (child) {
1329
+ if (child.userData && child.userData.objectType === 'connector') {
1330
+ // Calculate position relative to component root
1331
+ // This ensures that when we re-add it as a direct child of the component,
1332
+ // it maintains the same world position relative to the component.
1333
+ var componentMatrixWorldInverse = componentModel.matrixWorld.clone().invert();
1334
+ var childWorldPos = new THREE.Vector3();
1335
+ child.getWorldPosition(childWorldPos);
1336
+ var relativePos = childWorldPos.applyMatrix4(componentMatrixWorldInverse);
1337
+ componentSceneData.children.push({
1338
+ uuid: child.uuid,
1339
+ name: child.name,
1340
+ type: 'Mesh',
1341
+ position: {
1342
+ x: relativePos.x,
1343
+ y: relativePos.y,
1344
+ z: relativePos.z
1345
+ },
1346
+ userData: _objectSpread2({}, child.userData)
1347
+ });
1348
+ }
1349
+ });
1350
+
1326
1351
  // Add the component to the scene data
1327
1352
  if (!currentSceneData.scene.children) {
1328
1353
  currentSceneData.scene.children = [];
@@ -26,11 +26,11 @@ function getIoBehaviorManager(sceneViewer) {
26
26
 
27
27
  /**
28
28
  * Resolve tooltip/drag data points for an I/O device attachment.
29
- * Prefers behaviorConfig-driven animation data points; merges legacy ioConfig snapshot.
29
+ * Prefers behaviorConfig-driven animation data points.
30
30
  *
31
31
  * @param {string} parentUuid
32
32
  * @param {string} attachmentId
33
- * @param {Object} userData - io-device userData (may include dataPoints snapshot)
33
+ * @param {Object} userData - io-device userData
34
34
  * @param {import('../managers/behaviors/IoBehaviorManager.js').IoBehaviorManager|null} ioBehaviorManager
35
35
  * @param {THREE.Object3D|null} [hitMesh]
36
36
  * @returns {Object[]}
@@ -38,21 +38,7 @@ function getIoBehaviorManager(sceneViewer) {
38
38
  function resolveDataPoints(parentUuid, attachmentId, userData, ioBehaviorManager) {
39
39
  var hitMesh = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : null;
40
40
  var fromAnimations = (ioBehaviorManager === null || ioBehaviorManager === void 0 ? void 0 : ioBehaviorManager.getAnimationDataPoints(parentUuid, attachmentId, hitMesh)) || [];
41
- if (fromAnimations.length) return fromAnimations;
42
- var legacy = (userData === null || userData === void 0 ? void 0 : userData.dataPoints) || [];
43
- return legacy.map(function (dp) {
44
- var _dp$defaultValue;
45
- return {
46
- id: dp.id || dp.name,
47
- name: dp.name || dp.id,
48
- stateType: dp.stateType || 'binary',
49
- stateConfig: dp.stateConfig || {},
50
- defaultValue: (_dp$defaultValue = dp.defaultValue) !== null && _dp$defaultValue !== void 0 ? _dp$defaultValue : null,
51
- direction: dp.direction || (userData === null || userData === void 0 ? void 0 : userData.ioDirection) || 'output'
52
- };
53
- }).filter(function (dp) {
54
- return dp.id;
55
- });
41
+ return fromAnimations;
56
42
  }
57
43
 
58
44
  /**
@@ -83,35 +69,18 @@ function applyDefaultIoDeviceStates(centralPlant) {
83
69
  if (seen.has(scopedKey)) return;
84
70
  seen.add(scopedKey);
85
71
  var dps = ioBehavMgr.getAnimationDataPoints(objParentUuid, objAttachmentId) || [];
86
- if (dps.length) {
87
- var _iterator = _createForOfIteratorHelper(dps),
88
- _step;
89
- try {
90
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
91
- var dp = _step.value;
92
- if (!(dp !== null && dp !== void 0 && dp.id) || dp.defaultValue === undefined || dp.defaultValue === null) continue;
93
- centralPlant.setIoDeviceState(objAttachmentId, dp.id, dp.defaultValue, objParentUuid);
94
- }
95
- } catch (err) {
96
- _iterator.e(err);
97
- } finally {
98
- _iterator.f();
99
- }
100
- return;
101
- }
102
- var _iterator2 = _createForOfIteratorHelper(obj.userData.dataPoints || []),
103
- _step2;
72
+ var _iterator = _createForOfIteratorHelper(dps),
73
+ _step;
104
74
  try {
105
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
106
- var _dp = _step2.value;
107
- var dpId = _dp.id || _dp.name;
108
- if (!dpId || _dp.defaultValue === undefined || _dp.defaultValue === null) continue;
109
- centralPlant.setIoDeviceState(objAttachmentId, dpId, _dp.defaultValue, objParentUuid);
75
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
76
+ var dp = _step.value;
77
+ if (!(dp !== null && dp !== void 0 && dp.id) || dp.defaultValue === undefined || dp.defaultValue === null) continue;
78
+ centralPlant.setIoDeviceState(objAttachmentId, dp.id, dp.defaultValue, objParentUuid);
110
79
  }
111
80
  } catch (err) {
112
- _iterator2.e(err);
81
+ _iterator.e(err);
113
82
  } finally {
114
- _iterator2.f();
83
+ _iterator.f();
115
84
  }
116
85
  });
117
86
  }
@@ -1,4 +1,4 @@
1
- import { createForOfIteratorHelper as _createForOfIteratorHelper, construct as _construct, toConsumableArray as _toConsumableArray } from '../../_virtual/_rollupPluginBabelHelpers.js';
1
+ import { createForOfIteratorHelper as _createForOfIteratorHelper, objectSpread2 as _objectSpread2, construct as _construct, toConsumableArray as _toConsumableArray } from '../../_virtual/_rollupPluginBabelHelpers.js';
2
2
  import * as THREE from 'three';
3
3
 
4
4
  /**
@@ -155,35 +155,77 @@ function computeFilteredBoundingBox(object) {
155
155
  */
156
156
  function computeIODeviceBoundingBoxes(componentObject) {
157
157
  var results = [];
158
- var _iterator = _createForOfIteratorHelper(componentObject.children),
159
- _step;
160
- try {
161
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
162
- var _child$userData;
163
- var child = _step.value;
164
- if (((_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.objectType) !== 'io-device') continue;
165
- var bbox = new THREE.Box3().setFromObject(child);
166
- if (!bbox.isEmpty()) {
167
- results.push({
168
- uuid: child.uuid,
169
- userData: {
170
- objectType: 'io-device',
171
- deviceId: child.userData.deviceId || null,
172
- attachmentId: child.userData.attachmentId || null,
173
- parentComponentId: child.userData.parentComponentId || componentObject.uuid
174
- },
175
- worldBoundingBox: {
176
- min: [bbox.min.x, bbox.min.y, bbox.min.z],
177
- max: [bbox.max.x, bbox.max.y, bbox.max.z]
178
- }
179
- });
180
- }
158
+ componentObject.traverse(function (child) {
159
+ var _child$userData;
160
+ if (((_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.objectType) !== 'io-device') return;
161
+ var bbox = new THREE.Box3().setFromObject(child);
162
+ var worldPos = new THREE.Vector3();
163
+ child.getWorldPosition(worldPos);
164
+ if (!bbox.isEmpty()) {
165
+ results.push({
166
+ uuid: child.uuid,
167
+ userData: {
168
+ objectType: 'io-device',
169
+ deviceId: child.userData.deviceId || null,
170
+ attachmentId: child.userData.attachmentId || null,
171
+ parentComponentId: child.userData.parentComponentId || componentObject.uuid,
172
+ // Sync position for pathfinder
173
+ position: [worldPos.x, worldPos.y, worldPos.z]
174
+ },
175
+ worldBoundingBox: {
176
+ min: [bbox.min.x, bbox.min.y, bbox.min.z],
177
+ max: [bbox.max.x, bbox.max.y, bbox.max.z]
178
+ }
179
+ });
181
180
  }
182
- } catch (err) {
183
- _iterator.e(err);
184
- } finally {
185
- _iterator.f();
186
- }
181
+ });
182
+ return results;
183
+ }
184
+
185
+ /**
186
+ * Computes individual world-space bounding boxes for each connector child
187
+ * of a component. Supports deep children (e.g. within GLB model hierarchy).
188
+ *
189
+ * @param {THREE.Object3D} componentObject - The component's Three.js object
190
+ * @returns {Array<{uuid: string, userData: Object, worldBoundingBox: {min: number[], max: number[]}}>}
191
+ * Array of connector bounding box descriptors ready for injection into scene data
192
+ */
193
+ function computeConnectorBoundingBoxes(componentObject) {
194
+ var results = [];
195
+ componentObject.traverse(function (child) {
196
+ var _child$userData2;
197
+ if (((_child$userData2 = child.userData) === null || _child$userData2 === void 0 ? void 0 : _child$userData2.objectType) !== 'connector') return;
198
+ var bbox = new THREE.Box3().setFromObject(child);
199
+ var worldPos = new THREE.Vector3();
200
+ child.getWorldPosition(worldPos);
201
+
202
+ // Compute world-space direction vector (Crucial for rotation-aware pathfinding)
203
+ // Default to [0, 0, 1] if not specified (Standard for our coordinate system)
204
+ var localDirData = child.userData && Array.isArray(child.userData.direction) ? child.userData.direction : [0, 0, 1];
205
+ var localDir = new THREE.Vector3(localDirData[0], localDirData[1], localDirData[2]);
206
+ var worldQuat = new THREE.Quaternion();
207
+ child.getWorldQuaternion(worldQuat);
208
+ var worldDir = localDir.clone().applyQuaternion(worldQuat).normalize();
209
+
210
+ // Fallback if mesh is too small or empty (sometimes connectors are just points)
211
+ if (bbox.isEmpty() || bbox.getSize(new THREE.Vector3()).length() < 0.01) {
212
+ var size = 0.1;
213
+ bbox.setFromCenterAndSize(worldPos, new THREE.Vector3(size, size, size));
214
+ }
215
+ results.push({
216
+ uuid: child.uuid,
217
+ userData: _objectSpread2(_objectSpread2({}, child.userData), {}, {
218
+ objectType: 'connector',
219
+ // Update both position AND direction for pathfinder
220
+ position: [worldPos.x, worldPos.y, worldPos.z],
221
+ direction: [worldDir.x, worldDir.y, worldDir.z]
222
+ }),
223
+ worldBoundingBox: {
224
+ min: [bbox.min.x, bbox.min.y, bbox.min.z],
225
+ max: [bbox.max.x, bbox.max.y, bbox.max.z]
226
+ }
227
+ });
228
+ });
187
229
  return results;
188
230
  }
189
231
 
@@ -245,8 +287,8 @@ function createSelectionBoxHelpers(object) {
245
287
 
246
288
  // Check if this object has io-device children (smart component)
247
289
  var hasIODevices = (_object$children = object.children) === null || _object$children === void 0 ? void 0 : _object$children.some(function (child) {
248
- var _child$userData2;
249
- return ((_child$userData2 = child.userData) === null || _child$userData2 === void 0 ? void 0 : _child$userData2.objectType) === 'io-device';
290
+ var _child$userData3;
291
+ return ((_child$userData3 = child.userData) === null || _child$userData3 === void 0 ? void 0 : _child$userData3.objectType) === 'io-device';
250
292
  });
251
293
  if (hasIODevices) {
252
294
  // 1. Create filtered helper for the component body
@@ -283,11 +325,11 @@ function createSelectionBoxHelpers(object) {
283
325
  * @param {THREE.Scene} scene - The scene (for finding objects by uuid)
284
326
  */
285
327
  function updateSelectionBoxHelpers(helpers, selectedObjects, scene) {
286
- var _iterator2 = _createForOfIteratorHelper(helpers),
287
- _step2;
328
+ var _iterator = _createForOfIteratorHelper(helpers),
329
+ _step;
288
330
  try {
289
331
  var _loop = function _loop() {
290
- var helper = _step2.value;
332
+ var helper = _step.value;
291
333
  var _helper$userData = helper.userData,
292
334
  sourceObjectUuid = _helper$userData.sourceObjectUuid,
293
335
  isFiltered = _helper$userData.isFiltered,
@@ -318,14 +360,14 @@ function updateSelectionBoxHelpers(helpers, selectedObjects, scene) {
318
360
  helper.update();
319
361
  }
320
362
  };
321
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
363
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
322
364
  if (_loop()) continue;
323
365
  }
324
366
  } catch (err) {
325
- _iterator2.e(err);
367
+ _iterator.e(err);
326
368
  } finally {
327
- _iterator2.f();
369
+ _iterator.f();
328
370
  }
329
371
  }
330
372
 
331
- export { computeFilteredBoundingBox, computeFilteredBoundingBoxCached, computeIODeviceBoundingBoxes, createSelectionBoxHelpers, updateSelectionBoxHelpers };
373
+ export { computeConnectorBoundingBoxes, computeFilteredBoundingBox, computeFilteredBoundingBoxCached, computeIODeviceBoundingBoxes, createSelectionBoxHelpers, updateSelectionBoxHelpers };
@@ -23,7 +23,7 @@ function attachIODevicesToComponent(_x, _x2, _x3, _x4) {
23
23
  }
24
24
  function _attachIODevicesToComponent() {
25
25
  _attachIODevicesToComponent = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(componentModel, componentData, modelPreloader, parentComponentId) {
26
- 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;
26
+ 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;
27
27
  return _regenerator().w(function (_context) {
28
28
  while (1) switch (_context.n) {
29
29
  case 0:
@@ -101,20 +101,14 @@ function _attachIODevicesToComponent() {
101
101
  // Name the device model
102
102
  deviceModel.name = "".concat(attachment.attachmentLabel || 'IO Device', " (").concat(attachmentId, ")");
103
103
 
104
- // Set user data for identification — include ioConfig data points so the
105
- // component tooltip can render state displays without an extra lookup.
104
+ // Set user data for identification
106
105
  deviceModel.userData = {
107
106
  objectType: 'io-device',
108
107
  deviceId: attachment.deviceId,
109
108
  attachmentId: attachmentId,
110
109
  attachmentLabel: attachment.attachmentLabel,
111
110
  parentComponentId: parentComponentId,
112
- deviceName: deviceData.name || '',
113
- // Snapshot of the device's data point definitions (stateType, stateConfig, direction, etc.)
114
- // ioConfig can use either 'states' (preferred) or legacy 'dataPoints' as the array key
115
- 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) || [],
116
- // Device-level I/O direction: 'input' means the user can write state via the tooltip
117
- ioDirection: ((_deviceData$ioConfig3 = deviceData.ioConfig) === null || _deviceData$ioConfig3 === void 0 ? void 0 : _deviceData$ioConfig3.direction) || 'output'
111
+ deviceName: deviceData.name || ''
118
112
  };
119
113
 
120
114
  // Position at the attachment point
package/dist/index.d.ts CHANGED
@@ -35,11 +35,6 @@ export interface IoDeviceState {
35
35
  direction?: 'input' | 'output'
36
36
  }
37
37
 
38
- export interface IoDeviceConfig {
39
- direction: 'input' | 'output'
40
- states: IoDeviceState[]
41
- }
42
-
43
38
  export interface BehaviorMapping {
44
39
  stateValue: any
45
40
  transform?: { x?: number; y?: number; z?: number }
@@ -91,7 +86,6 @@ export interface IoDeviceAsset {
91
86
  uuid: string
92
87
  name: string
93
88
  assetType: 'I/O Device'
94
- ioConfig: IoDeviceConfig
95
89
  behaviorConfig?: BehaviorEntry[]
96
90
  meshNameMap?: Record<string, string>
97
91
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@2112-lab/central-plant",
3
- "version": "0.3.46",
3
+ "version": "0.3.48",
4
4
  "description": "Utility modules for the Central Plant Application",
5
5
  "main": "dist/bundle/index.js",
6
6
  "module": "dist/esm/src/index.js",