@2112-lab/central-plant 0.1.87 → 0.1.90

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.
@@ -15,7 +15,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
15
15
  * Initialize the CentralPlant manager
16
16
  *
17
17
  * @constructor
18
- * @version 0.1.87
18
+ * @version 0.1.90
19
19
  * @updated 2025-10-22
20
20
  *
21
21
  * @description Creates a new CentralPlant instance and initializes internal managers and utilities.
@@ -1515,6 +1515,50 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
1515
1515
  }
1516
1516
  return removeComponentFromDictionary;
1517
1517
  }()
1518
+ /**
1519
+ * Get library IDs from scene data that are missing from the component dictionary
1520
+ * @param {Object} sceneData - Scene JSON data to analyze
1521
+ * @returns {string[]} Array of library IDs that are not in the current component dictionary
1522
+ * @description Analyzes scene data to find components whose libraryId is not present in the
1523
+ * component dictionary. This is useful for detecting S3 components that need to be fetched
1524
+ * before importing a scene.
1525
+ * @example
1526
+ * // Check for missing components before import
1527
+ * const missingIds = centralPlant.getMissingLibraryIds(sceneJson);
1528
+ * if (missingIds.length > 0) {
1529
+ * // Fetch missing S3 components
1530
+ * await fetchS3Components(missingIds);
1531
+ * await centralPlant.extendComponentDictionary(fetchedComponents);
1532
+ * }
1533
+ * // Now safe to import
1534
+ * await centralPlant.importScene(sceneJson);
1535
+ */
1536
+ )
1537
+ }, {
1538
+ key: "getMissingLibraryIds",
1539
+ value: function getMissingLibraryIds(sceneData) {
1540
+ var _sceneData$scene, _this$managers$compon;
1541
+ if (!(sceneData !== null && sceneData !== void 0 && (_sceneData$scene = sceneData.scene) !== null && _sceneData$scene !== void 0 && _sceneData$scene.children) || !Array.isArray(sceneData.scene.children)) {
1542
+ return [];
1543
+ }
1544
+ var componentDictionary = ((_this$managers$compon = this.managers.componentDataManager) === null || _this$managers$compon === void 0 ? void 0 : _this$managers$compon.componentDictionary) || {};
1545
+ var missingIds = [];
1546
+ sceneData.scene.children.forEach(function (child) {
1547
+ var _child$userData;
1548
+ var libraryId = (_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.libraryId;
1549
+ if (libraryId && !componentDictionary[libraryId]) {
1550
+ // Only add unique IDs
1551
+ if (!missingIds.includes(libraryId)) {
1552
+ missingIds.push(libraryId);
1553
+ }
1554
+ }
1555
+ });
1556
+ if (missingIds.length > 0) {
1557
+ console.log("\uD83D\uDD0D Found ".concat(missingIds.length, " missing library IDs in scene data:"), missingIds);
1558
+ }
1559
+ return missingIds;
1560
+ }
1561
+
1518
1562
  /**
1519
1563
  * Select an object (component, connector, or gateway) in the scene by its ID
1520
1564
  * @param {string} objectId - The UUID or name of the object to select
@@ -1551,7 +1595,6 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
1551
1595
  * centralPlant.selectComponent(targetObject.uuid);
1552
1596
  * }
1553
1597
  */
1554
- )
1555
1598
  }, {
1556
1599
  key: "selectComponent",
1557
1600
  value: function selectComponent(objectId) {
@@ -1580,8 +1623,8 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
1580
1623
  // If still not found, try finding by originalUuid in userData
1581
1624
  if (!targetObject) {
1582
1625
  this.sceneViewer.scene.traverse(function (child) {
1583
- var _child$userData;
1584
- if (((_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.originalUuid) === objectId) {
1626
+ var _child$userData2;
1627
+ if (((_child$userData2 = child.userData) === null || _child$userData2 === void 0 ? void 0 : _child$userData2.originalUuid) === objectId) {
1585
1628
  targetObject = child;
1586
1629
  return;
1587
1630
  }
@@ -1,4 +1,4 @@
1
- import { createClass as _createClass, objectSpread2 as _objectSpread2, createForOfIteratorHelper as _createForOfIteratorHelper, typeof as _typeof, slicedToArray as _slicedToArray, classCallCheck as _classCallCheck, asyncToGenerator as _asyncToGenerator, regenerator as _regenerator } from '../../_virtual/_rollupPluginBabelHelpers.js';
1
+ import { createClass as _createClass, objectSpread2 as _objectSpread2, createForOfIteratorHelper as _createForOfIteratorHelper, typeof as _typeof, classCallCheck as _classCallCheck, asyncToGenerator as _asyncToGenerator, regenerator as _regenerator } from '../../_virtual/_rollupPluginBabelHelpers.js';
2
2
  import * as THREE from 'three';
3
3
  import { CentralPlantValidator } from './centralPlantValidator.js';
4
4
  import { createTransformControls } from '../managers/controls/transformControlsManager.js';
@@ -23,6 +23,7 @@ import { SceneTooltipsManager } from '../managers/scene/sceneTooltipsManager.js'
23
23
  import { ComponentTooltipManager } from '../managers/scene/componentTooltipManager.js';
24
24
  import { Viewport2DManager } from '../managers/scene/viewport2DManager.js';
25
25
  import { generateUuidFromName, getHardcodedUuid, findObjectByHardcodedUuid, generateUniqueComponentId } from '../utils/nameUtils.js';
26
+ import { attachIODevicesToComponent } from '../utils/ioDeviceUtils.js';
26
27
  import modelPreloader from '../rendering/modelPreloader.js';
27
28
 
28
29
  /**
@@ -1054,7 +1055,7 @@ var CentralPlantInternals = /*#__PURE__*/function () {
1054
1055
 
1055
1056
  // Add attached IO device models for smart components
1056
1057
  if (componentData.isSmart && componentData.attachedDevices) {
1057
- this._attachIODevicesToComponent(componentModel, componentData, modelPreloader, componentId);
1058
+ attachIODevicesToComponent(componentModel, componentData, modelPreloader, componentId);
1058
1059
  }
1059
1060
 
1060
1061
  // Notify the component manager about the new component
@@ -1107,95 +1108,6 @@ var CentralPlantInternals = /*#__PURE__*/function () {
1107
1108
  }
1108
1109
  }
1109
1110
 
1110
- /**
1111
- * Attach IO device models to a smart component from cached models.
1112
- * Each device referenced in componentData.attachedDevices is looked up
1113
- * in the model preloader cache, cloned, positioned, and added as a child.
1114
- * @param {THREE.Object3D} componentModel - The parent component model
1115
- * @param {Object} componentData - Component dictionary entry (has attachedDevices)
1116
- * @param {Object} modelPreloader - ModelPreloader instance
1117
- * @param {string} parentComponentId - The parent component's UUID
1118
- * @private
1119
- */
1120
- }, {
1121
- key: "_attachIODevicesToComponent",
1122
- value: function _attachIODevicesToComponent(componentModel, componentData, modelPreloader, parentComponentId) {
1123
- var attachedDevices = componentData.attachedDevices;
1124
- console.log("\uD83D\uDD0C addComponent(): Attaching ".concat(Object.keys(attachedDevices).length, " IO devices to smart component"));
1125
- for (var _i = 0, _Object$entries = Object.entries(attachedDevices); _i < _Object$entries.length; _i++) {
1126
- var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2),
1127
- attachmentId = _Object$entries$_i[0],
1128
- attachment = _Object$entries$_i[1];
1129
- try {
1130
- var _modelPreloader$compo, _deviceData$ioConfig, _deviceData$ioConfig2, _deviceData$ioConfig3, _attachment$attachmen;
1131
- var deviceData = (_modelPreloader$compo = modelPreloader.componentDictionary) === null || _modelPreloader$compo === void 0 ? void 0 : _modelPreloader$compo[attachment.deviceId];
1132
- if (!deviceData || !deviceData.modelKey) {
1133
- console.warn("\u26A0\uFE0F IO device ".concat(attachment.deviceId, " not found in dictionary, skipping"));
1134
- continue;
1135
- }
1136
- var cachedDevice = modelPreloader.getCachedModelWithDimensions(deviceData.modelKey, attachment.deviceId);
1137
- if (!cachedDevice) {
1138
- console.warn("\u26A0\uFE0F IO device model not in cache: ".concat(deviceData.modelKey, ", skipping"));
1139
- continue;
1140
- }
1141
-
1142
- // Clone so each component instance owns its own io-device subtree and materials.
1143
- // Without this, all placed copies of the same smart component share the cached
1144
- // object, causing material mutations (from behaviors) to bleed across instances.
1145
- var deviceModel = cachedDevice.clone();
1146
- deviceModel.traverse(function (child) {
1147
- if (child.isMesh && child.material) {
1148
- child.material = Array.isArray(child.material) ? child.material.map(function (m) {
1149
- return m.clone();
1150
- }) : child.material.clone();
1151
- }
1152
- });
1153
-
1154
- // Name the device model
1155
- deviceModel.name = "".concat(attachment.attachmentLabel || 'IO Device', " (").concat(attachmentId, ")");
1156
-
1157
- // Set user data for identification — include ioConfig data points so the
1158
- // component tooltip can render state displays without an extra lookup.
1159
- deviceModel.userData = {
1160
- objectType: 'io-device',
1161
- deviceId: attachment.deviceId,
1162
- attachmentId: attachmentId,
1163
- attachmentLabel: attachment.attachmentLabel,
1164
- parentComponentId: parentComponentId,
1165
- deviceName: deviceData.name || '',
1166
- // Snapshot of the device's data point definitions (stateType, stateConfig, direction, etc.)
1167
- // ioConfig can use either 'states' (preferred) or legacy 'dataPoints' as the array key
1168
- 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) || [],
1169
- // Device-level I/O direction: 'input' means the user can write state via the tooltip
1170
- ioDirection: ((_deviceData$ioConfig3 = deviceData.ioConfig) === null || _deviceData$ioConfig3 === void 0 ? void 0 : _deviceData$ioConfig3.direction) || 'output',
1171
- // Signal wiring sourced from this attachment (for state propagation reference)
1172
- signalOutputs: attachment.signalOutputs || []
1173
- };
1174
-
1175
- // Position at the attachment point
1176
- if ((_attachment$attachmen = attachment.attachmentPoint) !== null && _attachment$attachmen !== void 0 && _attachment$attachmen.position) {
1177
- var pos = attachment.attachmentPoint.position;
1178
- deviceModel.position.set(pos.x || 0, pos.y || 0, pos.z || 0);
1179
- }
1180
-
1181
- // IO device models are authored at the same real-world unit scale
1182
- // as the host component, so keep them at their natural (1:1) size.
1183
- // Note: attachmentPoint.scale is the connector marker sphere size,
1184
- // NOT a desired device model scale.
1185
- deviceModel.scale.setScalar(1);
1186
-
1187
- // Add as child of the component
1188
- componentModel.add(deviceModel);
1189
- console.log("\u2705 Attached IO device: ".concat(attachment.attachmentLabel || attachment.deviceId, " at"), {
1190
- position: deviceModel.position,
1191
- scale: deviceModel.scale
1192
- });
1193
- } catch (err) {
1194
- console.error("\u274C Error attaching IO device ".concat(attachment.deviceId, ":"), err);
1195
- }
1196
- }
1197
- }
1198
-
1199
1111
  /**
1200
1112
  * Delete a component from the scene by componentId (internal implementation)
1201
1113
  * @param {string} componentId - The UUID of the component to delete
@@ -817,7 +817,7 @@ var CentralPlantValidator = /*#__PURE__*/function () {
817
817
  key: "validateImportedSceneData",
818
818
  value: function validateImportedSceneData(sceneData) {
819
819
  var results = [];
820
- var hardcodedVersion = "2.1";
820
+ var hardcodedVersion = "2.3";
821
821
 
822
822
  // Version validation - MUST be the value of hardcodedVersion
823
823
  if (!sceneData.version || sceneData.version !== hardcodedVersion) {
@@ -821,6 +821,7 @@ var ComponentDataManager = /*#__PURE__*/function (_BaseDisposable) {
821
821
  id: key,
822
822
  name: component.name,
823
823
  type: ((_component$metadata = component.metadata) === null || _component$metadata === void 0 ? void 0 : _component$metadata.type) || 'Component',
824
+ assetType: component.assetType || null,
824
825
  category: component.category,
825
826
  modelKey: component.modelKey,
826
827
  modelType: component.modelType,
@@ -829,10 +830,12 @@ var ComponentDataManager = /*#__PURE__*/function (_BaseDisposable) {
829
830
  // Preserve S3 metadata
830
831
  isS3Component: component.isS3Component,
831
832
  s3Path: component.s3Path,
832
- preLoad: component.preLoad
833
+ preLoad: component.preLoad,
834
+ preCache: component.preCache
833
835
  };
834
836
  return _objectSpread2(_objectSpread2({}, baseData), {}, {
835
837
  metadata: component.metadata || {},
838
+ ioConfig: component.ioConfig || null,
836
839
  boundingBox: component.boundingBox || null,
837
840
  adaptedBoundingBox: component.adaptedBoundingBox || null,
838
841
  children: component.children || [],
@@ -93,12 +93,16 @@ var ComponentManager = /*#__PURE__*/function () {
93
93
  componentMesh.position.set(position.x, position.y, position.z);
94
94
 
95
95
  // Set userData for the component including dimensions
96
- componentMesh.userData = _objectSpread2({
96
+ componentMesh.userData = _objectSpread2(_objectSpread2({
97
97
  libraryId: componentData.libraryId,
98
98
  objectType: 'component',
99
99
  originalUuid: uuid
100
100
  }, ((_gltfScene$userData = gltfScene.userData) === null || _gltfScene$userData === void 0 ? void 0 : _gltfScene$userData.dimensions) && {
101
101
  dimensions: gltfScene.userData.dimensions
102
+ }), libraryComponent.isS3Component && {
103
+ isS3Component: true,
104
+ s3Path: libraryComponent.s3Path,
105
+ componentKey: libraryComponent.componentKey
102
106
  });
103
107
 
104
108
  // Also ensure dimensions are preserved in the userData
@@ -1,5 +1,6 @@
1
1
  import { createClass as _createClass, objectSpread2 as _objectSpread2, toConsumableArray as _toConsumableArray, classCallCheck as _classCallCheck, asyncToGenerator as _asyncToGenerator, regenerator as _regenerator, createForOfIteratorHelper as _createForOfIteratorHelper, typeof as _typeof } from '../../../_virtual/_rollupPluginBabelHelpers.js';
2
2
  import * as THREE from 'three';
3
+ import { attachIODevicesToComponent } from '../../utils/ioDeviceUtils.js';
3
4
  import modelPreloader from '../../rendering/modelPreloader.js';
4
5
 
5
6
  var ModelManager = /*#__PURE__*/function () {
@@ -85,6 +86,11 @@ var ModelManager = /*#__PURE__*/function () {
85
86
  libraryModel.add(connector);
86
87
  });
87
88
 
89
+ // Attach IO devices for smart components (import flow)
90
+ if (componentData.isSmart && componentData.attachedDevices) {
91
+ attachIODevicesToComponent(libraryModel, componentData, modelPreloader, originalProps.uuid);
92
+ }
93
+
88
94
  // Replace mesh in scene
89
95
  this._replaceMeshInScene(targetMesh, libraryModel, originalProps.parent, component);
90
96
  console.log("\uD83C\uDF89 ".concat((_jsonEntry$userData3 = jsonEntry.userData) === null || _jsonEntry$userData3 === void 0 ? void 0 : _jsonEntry$userData3.libraryId, " GLB model successfully rendered in scene"));
@@ -161,7 +167,7 @@ var ModelManager = /*#__PURE__*/function () {
161
167
  value: (function () {
162
168
  var _getLibraryModel2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2(modelKey, libraryId) {
163
169
  var _this2 = this;
164
- var gltfScene, preloaderStatus, modelPath, gltf, _t2, _t3;
170
+ var gltfScene, preloaderStatus, modelPath, gltf, _t2, _t3, _t4;
165
171
  return _regenerator().w(function (_context2) {
166
172
  while (1) switch (_context2.n) {
167
173
  case 0:
@@ -198,25 +204,47 @@ var ModelManager = /*#__PURE__*/function () {
198
204
  console.warn("\u26A0\uFE0F Preloading failed:", _t2);
199
205
  case 6:
200
206
  _context2.p = 6;
207
+ if (!modelPreloader.urlResolver) {
208
+ _context2.n = 11;
209
+ break;
210
+ }
211
+ _context2.p = 7;
212
+ _context2.n = 8;
213
+ return modelPreloader.urlResolver(modelKey);
214
+ case 8:
215
+ modelPath = _context2.v;
216
+ console.log("\uD83D\uDD17 Resolved URL for ".concat(modelKey));
217
+ _context2.n = 10;
218
+ break;
219
+ case 9:
220
+ _context2.p = 9;
221
+ _t3 = _context2.v;
222
+ console.warn("\u26A0\uFE0F URL resolver failed for ".concat(modelKey, ", falling back to local path:"), _t3);
223
+ modelPath = "".concat(modelPreloader.modelsBasePath).concat(modelKey);
224
+ case 10:
225
+ _context2.n = 12;
226
+ break;
227
+ case 11:
201
228
  modelPath = "".concat(modelPreloader.modelsBasePath).concat(modelKey);
229
+ case 12:
202
230
  console.log("\uD83D\uDCC2 Fallback loading from: ".concat(modelPath));
203
- _context2.n = 7;
231
+ _context2.n = 13;
204
232
  return new Promise(function (resolve, reject) {
205
233
  _this2.sceneViewer.gltfLoader.load(modelPath, resolve, undefined, reject);
206
234
  });
207
- case 7:
235
+ case 13:
208
236
  gltf = _context2.v;
209
237
  if (libraryId) {
210
238
  modelPreloader.cacheModel(modelKey, gltf.scene.clone(), libraryId);
211
239
  }
212
240
  return _context2.a(2, gltf.scene);
213
- case 8:
214
- _context2.p = 8;
215
- _t3 = _context2.v;
216
- console.error("Failed to load model ".concat(modelKey, ":"), _t3);
241
+ case 14:
242
+ _context2.p = 14;
243
+ _t4 = _context2.v;
244
+ console.error("Failed to load model ".concat(modelKey, ":"), _t4);
217
245
  return _context2.a(2, null);
218
246
  }
219
- }, _callee2, null, [[6, 8], [2, 5]]);
247
+ }, _callee2, null, [[7, 9], [6, 14], [2, 5]]);
220
248
  }));
221
249
  function _getLibraryModel(_x4, _x5) {
222
250
  return _getLibraryModel2.apply(this, arguments);
@@ -315,7 +343,7 @@ var ModelManager = /*#__PURE__*/function () {
315
343
  key: "verifyModelPreloaderCache",
316
344
  value: (function () {
317
345
  var _verifyModelPreloaderCache = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee3() {
318
- var preloaderStatus, _t4;
346
+ var preloaderStatus, _t5;
319
347
  return _regenerator().w(function (_context3) {
320
348
  while (1) switch (_context3.n) {
321
349
  case 0:
@@ -338,8 +366,8 @@ var ModelManager = /*#__PURE__*/function () {
338
366
  break;
339
367
  case 3:
340
368
  _context3.p = 3;
341
- _t4 = _context3.v;
342
- console.warn('⚠️ Model preloading failed, some models may load directly:', _t4);
369
+ _t5 = _context3.v;
370
+ console.warn('⚠️ Model preloading failed, some models may load directly:', _t5);
343
371
  case 4:
344
372
  return _context3.a(2, preloaderStatus);
345
373
  }
@@ -358,7 +386,7 @@ var ModelManager = /*#__PURE__*/function () {
358
386
  key: "preloadMissingModels",
359
387
  value: (function () {
360
388
  var _preloadMissingModels = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee4(data, componentDictionary) {
361
- var _data$scene, _data$scene2, requiredModels, preloaderStatus, cachedModels, missingModels, basePath, modelUrls, _iterator, _step, modelKey, _t5, _t6;
389
+ var _data$scene, _data$scene2, requiredModels, preloaderStatus, cachedModels, missingModels, basePath, modelUrls, _iterator, _step, modelKey, _t6, _t7;
362
390
  return _regenerator().w(function (_context4) {
363
391
  while (1) switch (_context4.n) {
364
392
  case 0:
@@ -453,8 +481,8 @@ var ModelManager = /*#__PURE__*/function () {
453
481
  break;
454
482
  case 7:
455
483
  _context4.p = 7;
456
- _t5 = _context4.v;
457
- console.warn("\u274C Failed to preload missing model ".concat(modelKey, ":"), _t5);
484
+ _t6 = _context4.v;
485
+ console.warn("\u274C Failed to preload missing model ".concat(modelKey, ":"), _t6);
458
486
  case 8:
459
487
  _context4.n = 4;
460
488
  break;
@@ -463,8 +491,8 @@ var ModelManager = /*#__PURE__*/function () {
463
491
  break;
464
492
  case 10:
465
493
  _context4.p = 10;
466
- _t6 = _context4.v;
467
- _iterator.e(_t6);
494
+ _t7 = _context4.v;
495
+ _iterator.e(_t7);
468
496
  case 11:
469
497
  _context4.p = 11;
470
498
  _iterator.f();
@@ -569,7 +597,7 @@ var ModelManager = /*#__PURE__*/function () {
569
597
  key: "loadComponentDictionary",
570
598
  value: (function () {
571
599
  var _loadComponentDictionary = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee6() {
572
- var response, dict, _t7;
600
+ var response, dict, _t8;
573
601
  return _regenerator().w(function (_context6) {
574
602
  while (1) switch (_context6.n) {
575
603
  case 0:
@@ -587,8 +615,8 @@ var ModelManager = /*#__PURE__*/function () {
587
615
  return _context6.a(2, dict);
588
616
  case 3:
589
617
  _context6.p = 3;
590
- _t7 = _context6.v;
591
- console.warn('⚠️ Could not load component dictionary:', _t7);
618
+ _t8 = _context6.v;
619
+ console.warn('⚠️ Could not load component dictionary:', _t8);
592
620
  return _context6.a(2, {});
593
621
  }
594
622
  }, _callee6, null, [[0, 3]]);
@@ -188,7 +188,8 @@ var SceneOperationsManager = /*#__PURE__*/function () {
188
188
  return createSceneMaterials;
189
189
  }()
190
190
  /**
191
- * Helper function to create geometries from component library
191
+ * Helper function to create geometries for connectors and gateways
192
+ * Components use GLB geometry directly, no placeholder needed
192
193
  */
193
194
  )
194
195
  }, {
@@ -203,7 +204,7 @@ var SceneOperationsManager = /*#__PURE__*/function () {
203
204
  return geometries;
204
205
  }
205
206
 
206
- // Create geometries based on component library bounding boxes instead of JSON scene.geometries
207
+ // Create geometries only for connectors and gateways (components use GLB geometry)
207
208
  data.scene.children.forEach(function (child) {
208
209
  var _child$userData, _child$userData2;
209
210
  // Skip manual segments - they create their own geometry
@@ -212,42 +213,28 @@ var SceneOperationsManager = /*#__PURE__*/function () {
212
213
  console.log("\u23ED\uFE0F Skipping geometry creation for manual segment: ".concat(child.uuid));
213
214
  return;
214
215
  }
215
- if ((_child$userData2 = child.userData) !== null && _child$userData2 !== void 0 && _child$userData2.libraryId && componentDictionary[child.userData.libraryId]) {
216
- var component = componentDictionary[child.userData.libraryId];
217
- var boundingBox = component.boundingBox;
218
216
 
219
- // Create a box geometry based on the component's bounding box
220
- var geometry = new THREE.BoxGeometry(boundingBox.x || 1, boundingBox.y || 1, boundingBox.z || 1);
217
+ // Skip components - they will use GLB geometry directly
218
+ if ((_child$userData2 = child.userData) !== null && _child$userData2 !== void 0 && _child$userData2.libraryId) {
219
+ return;
220
+ }
221
221
 
222
- // Use the child's geometry UUID as the key
223
- geometries[child.userData.libraryId] = geometry;
224
- console.log("\uD83D\uDCE6 Created placeholder geometry for ".concat(child.userData.libraryId, ":"), boundingBox);
225
- } else {
226
- // For non-library objects (connectors, gateways), create shared sphere geometry
227
- var isConnectorOrGateway = child.uuid && (child.uuid.toLowerCase().includes('connector') || child.uuid.toLowerCase().includes('gateway'));
228
- console.log("createSceneGeometries child:", child);
229
- if (isConnectorOrGateway) {
230
- // Check if position exists and is not at origin
231
- if (child.position && !(child.position.x == 0 && child.position.y == 0 && child.position.z == 0)) {
232
- // Determine if this is a gateway or connector
233
- var isGateway = child.uuid.toLowerCase().includes('gateway');
234
- var geometryKey = isGateway ? 'GATEWAY_SPHERE' : 'CONNECTOR_GATEWAY_SPHERE';
235
- if (!geometries[geometryKey]) {
236
- // Match computed gateway size (0.15) for gateways, keep 0.1 for connectors
237
- var sphereRadius = isGateway ? 0.15 : 0.1;
238
- geometries[geometryKey] = new THREE.SphereGeometry(sphereRadius, 16, 16);
239
- console.log("\uD83D\uDD2E Created shared sphere geometry for ".concat(isGateway ? 'gateways' : 'connectors', " (radius: ").concat(sphereRadius, ")"));
240
- }
222
+ // For non-library objects (connectors, gateways), create shared sphere geometry
223
+ var isConnectorOrGateway = child.uuid && (child.uuid.toLowerCase().includes('connector') || child.uuid.toLowerCase().includes('gateway'));
224
+ console.log("createSceneGeometries child:", child);
225
+ if (isConnectorOrGateway) {
226
+ // Check if position exists and is not at origin
227
+ if (child.position && !(child.position.x == 0 && child.position.y == 0 && child.position.z == 0)) {
228
+ // Determine if this is a gateway or connector
229
+ var isGateway = child.uuid.toLowerCase().includes('gateway');
230
+ var geometryKey = isGateway ? 'GATEWAY_SPHERE' : 'CONNECTOR_GATEWAY_SPHERE';
231
+ if (!geometries[geometryKey]) {
232
+ // Match computed gateway size (0.15) for gateways, keep 0.1 for connectors
233
+ var sphereRadius = isGateway ? 0.15 : 0.1;
234
+ geometries[geometryKey] = new THREE.SphereGeometry(sphereRadius, 16, 16);
235
+ console.log("\uD83D\uDD2E Created shared sphere geometry for ".concat(isGateway ? 'gateways' : 'connectors', " (radius: ").concat(sphereRadius, ")"));
241
236
  }
242
237
  }
243
- // else {
244
- // // Default fallback geometry
245
- // const geometryKey = child.geometry || 'FALLBACK_BOX'
246
- // if (!geometries[geometryKey]) {
247
- // geometries[geometryKey] = new THREE.BoxGeometry(1, 1, 1)
248
- // console.log(`📦 Created fallback box geometry`)
249
- // }
250
- // }
251
238
  }
252
239
  });
253
240
  return geometries;
@@ -1011,7 +998,8 @@ var SceneOperationsManager = /*#__PURE__*/function () {
1011
998
  key: "_createBasicSceneObjects",
1012
999
  value: (function () {
1013
1000
  var _createBasicSceneObjects2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee6(data) {
1014
- var _this4 = this;
1001
+ var _this$sceneViewer$cen,
1002
+ _this4 = this;
1015
1003
  var componentDictionary, _yield$this$createSce, materials, crosscubeTextureSet, geometries, libraryObjectsToReplace;
1016
1004
  return _regenerator().w(function (_context6) {
1017
1005
  while (1) switch (_context6.n) {
@@ -1022,20 +1010,33 @@ var SceneOperationsManager = /*#__PURE__*/function () {
1022
1010
  }
1023
1011
  throw new Error('Invalid scene data structure: data.scene.children must be an array');
1024
1012
  case 1:
1013
+ // Use the extended component dictionary (includes S3 components) if available,
1014
+ // otherwise fall back to loading from static file
1015
+ componentDictionary = (_this$sceneViewer$cen = this.sceneViewer.centralPlant) === null || _this$sceneViewer$cen === void 0 || (_this$sceneViewer$cen = _this$sceneViewer$cen.managers) === null || _this$sceneViewer$cen === void 0 || (_this$sceneViewer$cen = _this$sceneViewer$cen.componentDataManager) === null || _this$sceneViewer$cen === void 0 ? void 0 : _this$sceneViewer$cen.componentDictionary;
1016
+ if (componentDictionary) {
1017
+ _context6.n = 3;
1018
+ break;
1019
+ }
1020
+ console.log('⚠️ Extended dictionary not available, loading from static file');
1025
1021
  _context6.n = 2;
1026
1022
  return this.modelManager.loadComponentDictionary();
1027
1023
  case 2:
1028
1024
  componentDictionary = _context6.v;
1025
+ _context6.n = 4;
1026
+ break;
1027
+ case 3:
1028
+ console.log("\u2705 Using extended component dictionary (".concat(Object.keys(componentDictionary).length, " components)"));
1029
+ case 4:
1029
1030
  // Inject connector children from component dictionary into scene data
1030
1031
  this._injectConnectorChildrenFromDictionary(data, componentDictionary);
1031
1032
 
1032
1033
  // Ensure models are preloaded
1033
- _context6.n = 3;
1034
+ _context6.n = 5;
1034
1035
  return this.modelManager.preloadMissingModels(data, componentDictionary);
1035
- case 3:
1036
- _context6.n = 4;
1036
+ case 5:
1037
+ _context6.n = 6;
1037
1038
  return this.createSceneMaterials(data);
1038
- case 4:
1039
+ case 6:
1039
1040
  _yield$this$createSce = _context6.v;
1040
1041
  materials = _yield$this$createSce.materials;
1041
1042
  crosscubeTextureSet = _yield$this$createSce.crosscubeTextureSet;
@@ -0,0 +1,100 @@
1
+ import { slicedToArray as _slicedToArray } from '../../_virtual/_rollupPluginBabelHelpers.js';
2
+
3
+ /**
4
+ * IO Device Utilities
5
+ * Shared utility functions for attaching IO devices to smart components.
6
+ * Used by both drag-and-drop (addComponent) and import (loadLibraryModel) flows.
7
+ */
8
+
9
+ /**
10
+ * Attach IO device models to a smart component from cached models.
11
+ * Each device referenced in componentData.attachedDevices is looked up
12
+ * in the model preloader cache, cloned, positioned, and added as a child.
13
+ *
14
+ * @param {THREE.Object3D} componentModel - The parent component model
15
+ * @param {Object} componentData - Component dictionary entry (has attachedDevices)
16
+ * @param {Object} modelPreloader - ModelPreloader instance with cache and componentDictionary
17
+ * @param {string} parentComponentId - The parent component's UUID
18
+ * @returns {void}
19
+ */
20
+ function attachIODevicesToComponent(componentModel, componentData, modelPreloader, parentComponentId) {
21
+ var attachedDevices = componentData.attachedDevices;
22
+ if (!attachedDevices || Object.keys(attachedDevices).length === 0) {
23
+ return;
24
+ }
25
+ console.log("\uD83D\uDD0C attachIODevicesToComponent(): Attaching ".concat(Object.keys(attachedDevices).length, " IO devices to smart component"));
26
+ for (var _i = 0, _Object$entries = Object.entries(attachedDevices); _i < _Object$entries.length; _i++) {
27
+ var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2),
28
+ attachmentId = _Object$entries$_i[0],
29
+ attachment = _Object$entries$_i[1];
30
+ try {
31
+ var _modelPreloader$compo, _deviceData$ioConfig, _deviceData$ioConfig2, _deviceData$ioConfig3, _attachment$attachmen;
32
+ var deviceData = (_modelPreloader$compo = modelPreloader.componentDictionary) === null || _modelPreloader$compo === void 0 ? void 0 : _modelPreloader$compo[attachment.deviceId];
33
+ if (!deviceData || !deviceData.modelKey) {
34
+ console.warn("\u26A0\uFE0F IO device ".concat(attachment.deviceId, " not found in dictionary, skipping"));
35
+ continue;
36
+ }
37
+ var cachedDevice = modelPreloader.getCachedModelWithDimensions(deviceData.modelKey, attachment.deviceId);
38
+ if (!cachedDevice) {
39
+ console.warn("\u26A0\uFE0F IO device model not in cache: ".concat(deviceData.modelKey, ", skipping"));
40
+ continue;
41
+ }
42
+
43
+ // Clone so each component instance owns its own io-device subtree and materials.
44
+ // Without this, all placed copies of the same smart component share the cached
45
+ // object, causing material mutations (from behaviors) to bleed across instances.
46
+ var deviceModel = cachedDevice.clone();
47
+ deviceModel.traverse(function (child) {
48
+ if (child.isMesh && child.material) {
49
+ child.material = Array.isArray(child.material) ? child.material.map(function (m) {
50
+ return m.clone();
51
+ }) : child.material.clone();
52
+ }
53
+ });
54
+
55
+ // Name the device model
56
+ deviceModel.name = "".concat(attachment.attachmentLabel || 'IO Device', " (").concat(attachmentId, ")");
57
+
58
+ // Set user data for identification — include ioConfig data points so the
59
+ // component tooltip can render state displays without an extra lookup.
60
+ deviceModel.userData = {
61
+ objectType: 'io-device',
62
+ deviceId: attachment.deviceId,
63
+ attachmentId: attachmentId,
64
+ attachmentLabel: attachment.attachmentLabel,
65
+ parentComponentId: parentComponentId,
66
+ deviceName: deviceData.name || '',
67
+ // Snapshot of the device's data point definitions (stateType, stateConfig, direction, etc.)
68
+ // ioConfig can use either 'states' (preferred) or legacy 'dataPoints' as the array key
69
+ 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) || [],
70
+ // Device-level I/O direction: 'input' means the user can write state via the tooltip
71
+ ioDirection: ((_deviceData$ioConfig3 = deviceData.ioConfig) === null || _deviceData$ioConfig3 === void 0 ? void 0 : _deviceData$ioConfig3.direction) || 'output',
72
+ // Signal wiring sourced from this attachment (for state propagation reference)
73
+ signalOutputs: attachment.signalOutputs || []
74
+ };
75
+
76
+ // Position at the attachment point
77
+ if ((_attachment$attachmen = attachment.attachmentPoint) !== null && _attachment$attachmen !== void 0 && _attachment$attachmen.position) {
78
+ var pos = attachment.attachmentPoint.position;
79
+ deviceModel.position.set(pos.x || 0, pos.y || 0, pos.z || 0);
80
+ }
81
+
82
+ // IO device models are authored at the same real-world unit scale
83
+ // as the host component, so keep them at their natural (1:1) size.
84
+ // Note: attachmentPoint.scale is the connector marker sphere size,
85
+ // NOT a desired device model scale.
86
+ deviceModel.scale.setScalar(1);
87
+
88
+ // Add as child of the component
89
+ componentModel.add(deviceModel);
90
+ console.log("\u2705 Attached IO device: ".concat(attachment.attachmentLabel || attachment.deviceId, " at"), {
91
+ position: deviceModel.position,
92
+ scale: deviceModel.scale
93
+ });
94
+ } catch (err) {
95
+ console.error("\u274C Error attaching IO device ".concat(attachment.deviceId, ":"), err);
96
+ }
97
+ }
98
+ }
99
+
100
+ export { attachIODevicesToComponent };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@2112-lab/central-plant",
3
- "version": "0.1.87",
3
+ "version": "0.1.90",
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",