@2112-lab/central-plant 0.1.88 → 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.
@@ -1980,7 +1980,7 @@ var CentralPlantValidator = /*#__PURE__*/function () {
1980
1980
  key: "validateImportedSceneData",
1981
1981
  value: function validateImportedSceneData(sceneData) {
1982
1982
  var results = [];
1983
- var hardcodedVersion = "2.1";
1983
+ var hardcodedVersion = "2.3";
1984
1984
 
1985
1985
  // Version validation - MUST be the value of hardcodedVersion
1986
1986
  if (!sceneData.version || sceneData.version !== hardcodedVersion) {
@@ -18749,12 +18749,16 @@ var ComponentManager = /*#__PURE__*/function () {
18749
18749
  componentMesh.position.set(position.x, position.y, position.z);
18750
18750
 
18751
18751
  // Set userData for the component including dimensions
18752
- componentMesh.userData = _objectSpread2({
18752
+ componentMesh.userData = _objectSpread2(_objectSpread2({
18753
18753
  libraryId: componentData.libraryId,
18754
18754
  objectType: 'component',
18755
18755
  originalUuid: uuid
18756
18756
  }, ((_gltfScene$userData = gltfScene.userData) === null || _gltfScene$userData === void 0 ? void 0 : _gltfScene$userData.dimensions) && {
18757
18757
  dimensions: gltfScene.userData.dimensions
18758
+ }), libraryComponent.isS3Component && {
18759
+ isS3Component: true,
18760
+ s3Path: libraryComponent.s3Path,
18761
+ componentKey: libraryComponent.componentKey
18758
18762
  });
18759
18763
 
18760
18764
  // Also ensure dimensions are preserved in the userData
@@ -28556,6 +28560,103 @@ var BehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
28556
28560
  }]);
28557
28561
  }(BaseDisposable);
28558
28562
 
28563
+ /**
28564
+ * IO Device Utilities
28565
+ * Shared utility functions for attaching IO devices to smart components.
28566
+ * Used by both drag-and-drop (addComponent) and import (loadLibraryModel) flows.
28567
+ */
28568
+
28569
+ /**
28570
+ * Attach IO device models to a smart component from cached models.
28571
+ * Each device referenced in componentData.attachedDevices is looked up
28572
+ * in the model preloader cache, cloned, positioned, and added as a child.
28573
+ *
28574
+ * @param {THREE.Object3D} componentModel - The parent component model
28575
+ * @param {Object} componentData - Component dictionary entry (has attachedDevices)
28576
+ * @param {Object} modelPreloader - ModelPreloader instance with cache and componentDictionary
28577
+ * @param {string} parentComponentId - The parent component's UUID
28578
+ * @returns {void}
28579
+ */
28580
+ function attachIODevicesToComponent(componentModel, componentData, modelPreloader, parentComponentId) {
28581
+ var attachedDevices = componentData.attachedDevices;
28582
+ if (!attachedDevices || Object.keys(attachedDevices).length === 0) {
28583
+ return;
28584
+ }
28585
+ console.log("\uD83D\uDD0C attachIODevicesToComponent(): Attaching ".concat(Object.keys(attachedDevices).length, " IO devices to smart component"));
28586
+ for (var _i = 0, _Object$entries = Object.entries(attachedDevices); _i < _Object$entries.length; _i++) {
28587
+ var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2),
28588
+ attachmentId = _Object$entries$_i[0],
28589
+ attachment = _Object$entries$_i[1];
28590
+ try {
28591
+ var _modelPreloader$compo, _deviceData$ioConfig, _deviceData$ioConfig2, _deviceData$ioConfig3, _attachment$attachmen;
28592
+ var deviceData = (_modelPreloader$compo = modelPreloader.componentDictionary) === null || _modelPreloader$compo === void 0 ? void 0 : _modelPreloader$compo[attachment.deviceId];
28593
+ if (!deviceData || !deviceData.modelKey) {
28594
+ console.warn("\u26A0\uFE0F IO device ".concat(attachment.deviceId, " not found in dictionary, skipping"));
28595
+ continue;
28596
+ }
28597
+ var cachedDevice = modelPreloader.getCachedModelWithDimensions(deviceData.modelKey, attachment.deviceId);
28598
+ if (!cachedDevice) {
28599
+ console.warn("\u26A0\uFE0F IO device model not in cache: ".concat(deviceData.modelKey, ", skipping"));
28600
+ continue;
28601
+ }
28602
+
28603
+ // Clone so each component instance owns its own io-device subtree and materials.
28604
+ // Without this, all placed copies of the same smart component share the cached
28605
+ // object, causing material mutations (from behaviors) to bleed across instances.
28606
+ var deviceModel = cachedDevice.clone();
28607
+ deviceModel.traverse(function (child) {
28608
+ if (child.isMesh && child.material) {
28609
+ child.material = Array.isArray(child.material) ? child.material.map(function (m) {
28610
+ return m.clone();
28611
+ }) : child.material.clone();
28612
+ }
28613
+ });
28614
+
28615
+ // Name the device model
28616
+ deviceModel.name = "".concat(attachment.attachmentLabel || 'IO Device', " (").concat(attachmentId, ")");
28617
+
28618
+ // Set user data for identification — include ioConfig data points so the
28619
+ // component tooltip can render state displays without an extra lookup.
28620
+ deviceModel.userData = {
28621
+ objectType: 'io-device',
28622
+ deviceId: attachment.deviceId,
28623
+ attachmentId: attachmentId,
28624
+ attachmentLabel: attachment.attachmentLabel,
28625
+ parentComponentId: parentComponentId,
28626
+ deviceName: deviceData.name || '',
28627
+ // Snapshot of the device's data point definitions (stateType, stateConfig, direction, etc.)
28628
+ // ioConfig can use either 'states' (preferred) or legacy 'dataPoints' as the array key
28629
+ 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) || [],
28630
+ // Device-level I/O direction: 'input' means the user can write state via the tooltip
28631
+ ioDirection: ((_deviceData$ioConfig3 = deviceData.ioConfig) === null || _deviceData$ioConfig3 === void 0 ? void 0 : _deviceData$ioConfig3.direction) || 'output',
28632
+ // Signal wiring sourced from this attachment (for state propagation reference)
28633
+ signalOutputs: attachment.signalOutputs || []
28634
+ };
28635
+
28636
+ // Position at the attachment point
28637
+ if ((_attachment$attachmen = attachment.attachmentPoint) !== null && _attachment$attachmen !== void 0 && _attachment$attachmen.position) {
28638
+ var pos = attachment.attachmentPoint.position;
28639
+ deviceModel.position.set(pos.x || 0, pos.y || 0, pos.z || 0);
28640
+ }
28641
+
28642
+ // IO device models are authored at the same real-world unit scale
28643
+ // as the host component, so keep them at their natural (1:1) size.
28644
+ // Note: attachmentPoint.scale is the connector marker sphere size,
28645
+ // NOT a desired device model scale.
28646
+ deviceModel.scale.setScalar(1);
28647
+
28648
+ // Add as child of the component
28649
+ componentModel.add(deviceModel);
28650
+ console.log("\u2705 Attached IO device: ".concat(attachment.attachmentLabel || attachment.deviceId, " at"), {
28651
+ position: deviceModel.position,
28652
+ scale: deviceModel.scale
28653
+ });
28654
+ } catch (err) {
28655
+ console.error("\u274C Error attaching IO device ".concat(attachment.deviceId, ":"), err);
28656
+ }
28657
+ }
28658
+ }
28659
+
28559
28660
  var ModelManager = /*#__PURE__*/function () {
28560
28661
  function ModelManager(sceneViewer) {
28561
28662
  _classCallCheck(this, ModelManager);
@@ -28639,6 +28740,11 @@ var ModelManager = /*#__PURE__*/function () {
28639
28740
  libraryModel.add(connector);
28640
28741
  });
28641
28742
 
28743
+ // Attach IO devices for smart components (import flow)
28744
+ if (componentData.isSmart && componentData.attachedDevices) {
28745
+ attachIODevicesToComponent(libraryModel, componentData, modelPreloader, originalProps.uuid);
28746
+ }
28747
+
28642
28748
  // Replace mesh in scene
28643
28749
  this._replaceMeshInScene(targetMesh, libraryModel, originalProps.parent, component);
28644
28750
  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"));
@@ -28715,7 +28821,7 @@ var ModelManager = /*#__PURE__*/function () {
28715
28821
  value: (function () {
28716
28822
  var _getLibraryModel2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2(modelKey, libraryId) {
28717
28823
  var _this2 = this;
28718
- var gltfScene, preloaderStatus, modelPath, gltf, _t2, _t3;
28824
+ var gltfScene, preloaderStatus, modelPath, gltf, _t2, _t3, _t4;
28719
28825
  return _regenerator().w(function (_context2) {
28720
28826
  while (1) switch (_context2.n) {
28721
28827
  case 0:
@@ -28752,25 +28858,47 @@ var ModelManager = /*#__PURE__*/function () {
28752
28858
  console.warn("\u26A0\uFE0F Preloading failed:", _t2);
28753
28859
  case 6:
28754
28860
  _context2.p = 6;
28861
+ if (!modelPreloader.urlResolver) {
28862
+ _context2.n = 11;
28863
+ break;
28864
+ }
28865
+ _context2.p = 7;
28866
+ _context2.n = 8;
28867
+ return modelPreloader.urlResolver(modelKey);
28868
+ case 8:
28869
+ modelPath = _context2.v;
28870
+ console.log("\uD83D\uDD17 Resolved URL for ".concat(modelKey));
28871
+ _context2.n = 10;
28872
+ break;
28873
+ case 9:
28874
+ _context2.p = 9;
28875
+ _t3 = _context2.v;
28876
+ console.warn("\u26A0\uFE0F URL resolver failed for ".concat(modelKey, ", falling back to local path:"), _t3);
28877
+ modelPath = "".concat(modelPreloader.modelsBasePath).concat(modelKey);
28878
+ case 10:
28879
+ _context2.n = 12;
28880
+ break;
28881
+ case 11:
28755
28882
  modelPath = "".concat(modelPreloader.modelsBasePath).concat(modelKey);
28883
+ case 12:
28756
28884
  console.log("\uD83D\uDCC2 Fallback loading from: ".concat(modelPath));
28757
- _context2.n = 7;
28885
+ _context2.n = 13;
28758
28886
  return new Promise(function (resolve, reject) {
28759
28887
  _this2.sceneViewer.gltfLoader.load(modelPath, resolve, undefined, reject);
28760
28888
  });
28761
- case 7:
28889
+ case 13:
28762
28890
  gltf = _context2.v;
28763
28891
  if (libraryId) {
28764
28892
  modelPreloader.cacheModel(modelKey, gltf.scene.clone(), libraryId);
28765
28893
  }
28766
28894
  return _context2.a(2, gltf.scene);
28767
- case 8:
28768
- _context2.p = 8;
28769
- _t3 = _context2.v;
28770
- console.error("Failed to load model ".concat(modelKey, ":"), _t3);
28895
+ case 14:
28896
+ _context2.p = 14;
28897
+ _t4 = _context2.v;
28898
+ console.error("Failed to load model ".concat(modelKey, ":"), _t4);
28771
28899
  return _context2.a(2, null);
28772
28900
  }
28773
- }, _callee2, null, [[6, 8], [2, 5]]);
28901
+ }, _callee2, null, [[7, 9], [6, 14], [2, 5]]);
28774
28902
  }));
28775
28903
  function _getLibraryModel(_x4, _x5) {
28776
28904
  return _getLibraryModel2.apply(this, arguments);
@@ -28869,7 +28997,7 @@ var ModelManager = /*#__PURE__*/function () {
28869
28997
  key: "verifyModelPreloaderCache",
28870
28998
  value: (function () {
28871
28999
  var _verifyModelPreloaderCache = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee3() {
28872
- var preloaderStatus, _t4;
29000
+ var preloaderStatus, _t5;
28873
29001
  return _regenerator().w(function (_context3) {
28874
29002
  while (1) switch (_context3.n) {
28875
29003
  case 0:
@@ -28892,8 +29020,8 @@ var ModelManager = /*#__PURE__*/function () {
28892
29020
  break;
28893
29021
  case 3:
28894
29022
  _context3.p = 3;
28895
- _t4 = _context3.v;
28896
- console.warn('⚠️ Model preloading failed, some models may load directly:', _t4);
29023
+ _t5 = _context3.v;
29024
+ console.warn('⚠️ Model preloading failed, some models may load directly:', _t5);
28897
29025
  case 4:
28898
29026
  return _context3.a(2, preloaderStatus);
28899
29027
  }
@@ -28912,7 +29040,7 @@ var ModelManager = /*#__PURE__*/function () {
28912
29040
  key: "preloadMissingModels",
28913
29041
  value: (function () {
28914
29042
  var _preloadMissingModels = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee4(data, componentDictionary) {
28915
- var _data$scene, _data$scene2, requiredModels, preloaderStatus, cachedModels, missingModels, basePath, modelUrls, _iterator, _step, modelKey, _t5, _t6;
29043
+ var _data$scene, _data$scene2, requiredModels, preloaderStatus, cachedModels, missingModels, basePath, modelUrls, _iterator, _step, modelKey, _t6, _t7;
28916
29044
  return _regenerator().w(function (_context4) {
28917
29045
  while (1) switch (_context4.n) {
28918
29046
  case 0:
@@ -29007,8 +29135,8 @@ var ModelManager = /*#__PURE__*/function () {
29007
29135
  break;
29008
29136
  case 7:
29009
29137
  _context4.p = 7;
29010
- _t5 = _context4.v;
29011
- console.warn("\u274C Failed to preload missing model ".concat(modelKey, ":"), _t5);
29138
+ _t6 = _context4.v;
29139
+ console.warn("\u274C Failed to preload missing model ".concat(modelKey, ":"), _t6);
29012
29140
  case 8:
29013
29141
  _context4.n = 4;
29014
29142
  break;
@@ -29017,8 +29145,8 @@ var ModelManager = /*#__PURE__*/function () {
29017
29145
  break;
29018
29146
  case 10:
29019
29147
  _context4.p = 10;
29020
- _t6 = _context4.v;
29021
- _iterator.e(_t6);
29148
+ _t7 = _context4.v;
29149
+ _iterator.e(_t7);
29022
29150
  case 11:
29023
29151
  _context4.p = 11;
29024
29152
  _iterator.f();
@@ -29123,7 +29251,7 @@ var ModelManager = /*#__PURE__*/function () {
29123
29251
  key: "loadComponentDictionary",
29124
29252
  value: (function () {
29125
29253
  var _loadComponentDictionary = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee6() {
29126
- var response, dict, _t7;
29254
+ var response, dict, _t8;
29127
29255
  return _regenerator().w(function (_context6) {
29128
29256
  while (1) switch (_context6.n) {
29129
29257
  case 0:
@@ -29141,8 +29269,8 @@ var ModelManager = /*#__PURE__*/function () {
29141
29269
  return _context6.a(2, dict);
29142
29270
  case 3:
29143
29271
  _context6.p = 3;
29144
- _t7 = _context6.v;
29145
- console.warn('⚠️ Could not load component dictionary:', _t7);
29272
+ _t8 = _context6.v;
29273
+ console.warn('⚠️ Could not load component dictionary:', _t8);
29146
29274
  return _context6.a(2, {});
29147
29275
  }
29148
29276
  }, _callee6, null, [[0, 3]]);
@@ -29938,7 +30066,8 @@ var SceneOperationsManager = /*#__PURE__*/function () {
29938
30066
  return createSceneMaterials;
29939
30067
  }()
29940
30068
  /**
29941
- * Helper function to create geometries from component library
30069
+ * Helper function to create geometries for connectors and gateways
30070
+ * Components use GLB geometry directly, no placeholder needed
29942
30071
  */
29943
30072
  )
29944
30073
  }, {
@@ -29953,7 +30082,7 @@ var SceneOperationsManager = /*#__PURE__*/function () {
29953
30082
  return geometries;
29954
30083
  }
29955
30084
 
29956
- // Create geometries based on component library bounding boxes instead of JSON scene.geometries
30085
+ // Create geometries only for connectors and gateways (components use GLB geometry)
29957
30086
  data.scene.children.forEach(function (child) {
29958
30087
  var _child$userData, _child$userData2;
29959
30088
  // Skip manual segments - they create their own geometry
@@ -29962,42 +30091,28 @@ var SceneOperationsManager = /*#__PURE__*/function () {
29962
30091
  console.log("\u23ED\uFE0F Skipping geometry creation for manual segment: ".concat(child.uuid));
29963
30092
  return;
29964
30093
  }
29965
- if ((_child$userData2 = child.userData) !== null && _child$userData2 !== void 0 && _child$userData2.libraryId && componentDictionary[child.userData.libraryId]) {
29966
- var component = componentDictionary[child.userData.libraryId];
29967
- var boundingBox = component.boundingBox;
29968
30094
 
29969
- // Create a box geometry based on the component's bounding box
29970
- var geometry = new THREE__namespace.BoxGeometry(boundingBox.x || 1, boundingBox.y || 1, boundingBox.z || 1);
30095
+ // Skip components - they will use GLB geometry directly
30096
+ if ((_child$userData2 = child.userData) !== null && _child$userData2 !== void 0 && _child$userData2.libraryId) {
30097
+ return;
30098
+ }
29971
30099
 
29972
- // Use the child's geometry UUID as the key
29973
- geometries[child.userData.libraryId] = geometry;
29974
- console.log("\uD83D\uDCE6 Created placeholder geometry for ".concat(child.userData.libraryId, ":"), boundingBox);
29975
- } else {
29976
- // For non-library objects (connectors, gateways), create shared sphere geometry
29977
- var isConnectorOrGateway = child.uuid && (child.uuid.toLowerCase().includes('connector') || child.uuid.toLowerCase().includes('gateway'));
29978
- console.log("createSceneGeometries child:", child);
29979
- if (isConnectorOrGateway) {
29980
- // Check if position exists and is not at origin
29981
- if (child.position && !(child.position.x == 0 && child.position.y == 0 && child.position.z == 0)) {
29982
- // Determine if this is a gateway or connector
29983
- var isGateway = child.uuid.toLowerCase().includes('gateway');
29984
- var geometryKey = isGateway ? 'GATEWAY_SPHERE' : 'CONNECTOR_GATEWAY_SPHERE';
29985
- if (!geometries[geometryKey]) {
29986
- // Match computed gateway size (0.15) for gateways, keep 0.1 for connectors
29987
- var sphereRadius = isGateway ? 0.15 : 0.1;
29988
- geometries[geometryKey] = new THREE__namespace.SphereGeometry(sphereRadius, 16, 16);
29989
- console.log("\uD83D\uDD2E Created shared sphere geometry for ".concat(isGateway ? 'gateways' : 'connectors', " (radius: ").concat(sphereRadius, ")"));
29990
- }
30100
+ // For non-library objects (connectors, gateways), create shared sphere geometry
30101
+ var isConnectorOrGateway = child.uuid && (child.uuid.toLowerCase().includes('connector') || child.uuid.toLowerCase().includes('gateway'));
30102
+ console.log("createSceneGeometries child:", child);
30103
+ if (isConnectorOrGateway) {
30104
+ // Check if position exists and is not at origin
30105
+ if (child.position && !(child.position.x == 0 && child.position.y == 0 && child.position.z == 0)) {
30106
+ // Determine if this is a gateway or connector
30107
+ var isGateway = child.uuid.toLowerCase().includes('gateway');
30108
+ var geometryKey = isGateway ? 'GATEWAY_SPHERE' : 'CONNECTOR_GATEWAY_SPHERE';
30109
+ if (!geometries[geometryKey]) {
30110
+ // Match computed gateway size (0.15) for gateways, keep 0.1 for connectors
30111
+ var sphereRadius = isGateway ? 0.15 : 0.1;
30112
+ geometries[geometryKey] = new THREE__namespace.SphereGeometry(sphereRadius, 16, 16);
30113
+ console.log("\uD83D\uDD2E Created shared sphere geometry for ".concat(isGateway ? 'gateways' : 'connectors', " (radius: ").concat(sphereRadius, ")"));
29991
30114
  }
29992
30115
  }
29993
- // else {
29994
- // // Default fallback geometry
29995
- // const geometryKey = child.geometry || 'FALLBACK_BOX'
29996
- // if (!geometries[geometryKey]) {
29997
- // geometries[geometryKey] = new THREE.BoxGeometry(1, 1, 1)
29998
- // console.log(`📦 Created fallback box geometry`)
29999
- // }
30000
- // }
30001
30116
  }
30002
30117
  });
30003
30118
  return geometries;
@@ -30761,7 +30876,8 @@ var SceneOperationsManager = /*#__PURE__*/function () {
30761
30876
  key: "_createBasicSceneObjects",
30762
30877
  value: (function () {
30763
30878
  var _createBasicSceneObjects2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee6(data) {
30764
- var _this4 = this;
30879
+ var _this$sceneViewer$cen,
30880
+ _this4 = this;
30765
30881
  var componentDictionary, _yield$this$createSce, materials, crosscubeTextureSet, geometries, libraryObjectsToReplace;
30766
30882
  return _regenerator().w(function (_context6) {
30767
30883
  while (1) switch (_context6.n) {
@@ -30772,20 +30888,33 @@ var SceneOperationsManager = /*#__PURE__*/function () {
30772
30888
  }
30773
30889
  throw new Error('Invalid scene data structure: data.scene.children must be an array');
30774
30890
  case 1:
30891
+ // Use the extended component dictionary (includes S3 components) if available,
30892
+ // otherwise fall back to loading from static file
30893
+ 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;
30894
+ if (componentDictionary) {
30895
+ _context6.n = 3;
30896
+ break;
30897
+ }
30898
+ console.log('⚠️ Extended dictionary not available, loading from static file');
30775
30899
  _context6.n = 2;
30776
30900
  return this.modelManager.loadComponentDictionary();
30777
30901
  case 2:
30778
30902
  componentDictionary = _context6.v;
30903
+ _context6.n = 4;
30904
+ break;
30905
+ case 3:
30906
+ console.log("\u2705 Using extended component dictionary (".concat(Object.keys(componentDictionary).length, " components)"));
30907
+ case 4:
30779
30908
  // Inject connector children from component dictionary into scene data
30780
30909
  this._injectConnectorChildrenFromDictionary(data, componentDictionary);
30781
30910
 
30782
30911
  // Ensure models are preloaded
30783
- _context6.n = 3;
30912
+ _context6.n = 5;
30784
30913
  return this.modelManager.preloadMissingModels(data, componentDictionary);
30785
- case 3:
30786
- _context6.n = 4;
30914
+ case 5:
30915
+ _context6.n = 6;
30787
30916
  return this.createSceneMaterials(data);
30788
- case 4:
30917
+ case 6:
30789
30918
  _yield$this$createSce = _context6.v;
30790
30919
  materials = _yield$this$createSce.materials;
30791
30920
  crosscubeTextureSet = _yield$this$createSce.crosscubeTextureSet;
@@ -36527,7 +36656,7 @@ var CentralPlantInternals = /*#__PURE__*/function () {
36527
36656
 
36528
36657
  // Add attached IO device models for smart components
36529
36658
  if (componentData.isSmart && componentData.attachedDevices) {
36530
- this._attachIODevicesToComponent(componentModel, componentData, modelPreloader, componentId);
36659
+ attachIODevicesToComponent(componentModel, componentData, modelPreloader, componentId);
36531
36660
  }
36532
36661
 
36533
36662
  // Notify the component manager about the new component
@@ -36580,95 +36709,6 @@ var CentralPlantInternals = /*#__PURE__*/function () {
36580
36709
  }
36581
36710
  }
36582
36711
 
36583
- /**
36584
- * Attach IO device models to a smart component from cached models.
36585
- * Each device referenced in componentData.attachedDevices is looked up
36586
- * in the model preloader cache, cloned, positioned, and added as a child.
36587
- * @param {THREE.Object3D} componentModel - The parent component model
36588
- * @param {Object} componentData - Component dictionary entry (has attachedDevices)
36589
- * @param {Object} modelPreloader - ModelPreloader instance
36590
- * @param {string} parentComponentId - The parent component's UUID
36591
- * @private
36592
- */
36593
- }, {
36594
- key: "_attachIODevicesToComponent",
36595
- value: function _attachIODevicesToComponent(componentModel, componentData, modelPreloader, parentComponentId) {
36596
- var attachedDevices = componentData.attachedDevices;
36597
- console.log("\uD83D\uDD0C addComponent(): Attaching ".concat(Object.keys(attachedDevices).length, " IO devices to smart component"));
36598
- for (var _i = 0, _Object$entries = Object.entries(attachedDevices); _i < _Object$entries.length; _i++) {
36599
- var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2),
36600
- attachmentId = _Object$entries$_i[0],
36601
- attachment = _Object$entries$_i[1];
36602
- try {
36603
- var _modelPreloader$compo, _deviceData$ioConfig, _deviceData$ioConfig2, _deviceData$ioConfig3, _attachment$attachmen;
36604
- var deviceData = (_modelPreloader$compo = modelPreloader.componentDictionary) === null || _modelPreloader$compo === void 0 ? void 0 : _modelPreloader$compo[attachment.deviceId];
36605
- if (!deviceData || !deviceData.modelKey) {
36606
- console.warn("\u26A0\uFE0F IO device ".concat(attachment.deviceId, " not found in dictionary, skipping"));
36607
- continue;
36608
- }
36609
- var cachedDevice = modelPreloader.getCachedModelWithDimensions(deviceData.modelKey, attachment.deviceId);
36610
- if (!cachedDevice) {
36611
- console.warn("\u26A0\uFE0F IO device model not in cache: ".concat(deviceData.modelKey, ", skipping"));
36612
- continue;
36613
- }
36614
-
36615
- // Clone so each component instance owns its own io-device subtree and materials.
36616
- // Without this, all placed copies of the same smart component share the cached
36617
- // object, causing material mutations (from behaviors) to bleed across instances.
36618
- var deviceModel = cachedDevice.clone();
36619
- deviceModel.traverse(function (child) {
36620
- if (child.isMesh && child.material) {
36621
- child.material = Array.isArray(child.material) ? child.material.map(function (m) {
36622
- return m.clone();
36623
- }) : child.material.clone();
36624
- }
36625
- });
36626
-
36627
- // Name the device model
36628
- deviceModel.name = "".concat(attachment.attachmentLabel || 'IO Device', " (").concat(attachmentId, ")");
36629
-
36630
- // Set user data for identification — include ioConfig data points so the
36631
- // component tooltip can render state displays without an extra lookup.
36632
- deviceModel.userData = {
36633
- objectType: 'io-device',
36634
- deviceId: attachment.deviceId,
36635
- attachmentId: attachmentId,
36636
- attachmentLabel: attachment.attachmentLabel,
36637
- parentComponentId: parentComponentId,
36638
- deviceName: deviceData.name || '',
36639
- // Snapshot of the device's data point definitions (stateType, stateConfig, direction, etc.)
36640
- // ioConfig can use either 'states' (preferred) or legacy 'dataPoints' as the array key
36641
- 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) || [],
36642
- // Device-level I/O direction: 'input' means the user can write state via the tooltip
36643
- ioDirection: ((_deviceData$ioConfig3 = deviceData.ioConfig) === null || _deviceData$ioConfig3 === void 0 ? void 0 : _deviceData$ioConfig3.direction) || 'output',
36644
- // Signal wiring sourced from this attachment (for state propagation reference)
36645
- signalOutputs: attachment.signalOutputs || []
36646
- };
36647
-
36648
- // Position at the attachment point
36649
- if ((_attachment$attachmen = attachment.attachmentPoint) !== null && _attachment$attachmen !== void 0 && _attachment$attachmen.position) {
36650
- var pos = attachment.attachmentPoint.position;
36651
- deviceModel.position.set(pos.x || 0, pos.y || 0, pos.z || 0);
36652
- }
36653
-
36654
- // IO device models are authored at the same real-world unit scale
36655
- // as the host component, so keep them at their natural (1:1) size.
36656
- // Note: attachmentPoint.scale is the connector marker sphere size,
36657
- // NOT a desired device model scale.
36658
- deviceModel.scale.setScalar(1);
36659
-
36660
- // Add as child of the component
36661
- componentModel.add(deviceModel);
36662
- console.log("\u2705 Attached IO device: ".concat(attachment.attachmentLabel || attachment.deviceId, " at"), {
36663
- position: deviceModel.position,
36664
- scale: deviceModel.scale
36665
- });
36666
- } catch (err) {
36667
- console.error("\u274C Error attaching IO device ".concat(attachment.deviceId, ":"), err);
36668
- }
36669
- }
36670
- }
36671
-
36672
36712
  /**
36673
36713
  * Delete a component from the scene by componentId (internal implementation)
36674
36714
  * @param {string} componentId - The UUID of the component to delete
@@ -36774,7 +36814,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
36774
36814
  * Initialize the CentralPlant manager
36775
36815
  *
36776
36816
  * @constructor
36777
- * @version 0.1.88
36817
+ * @version 0.1.90
36778
36818
  * @updated 2025-10-22
36779
36819
  *
36780
36820
  * @description Creates a new CentralPlant instance and initializes internal managers and utilities.
@@ -38274,6 +38314,50 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
38274
38314
  }
38275
38315
  return removeComponentFromDictionary;
38276
38316
  }()
38317
+ /**
38318
+ * Get library IDs from scene data that are missing from the component dictionary
38319
+ * @param {Object} sceneData - Scene JSON data to analyze
38320
+ * @returns {string[]} Array of library IDs that are not in the current component dictionary
38321
+ * @description Analyzes scene data to find components whose libraryId is not present in the
38322
+ * component dictionary. This is useful for detecting S3 components that need to be fetched
38323
+ * before importing a scene.
38324
+ * @example
38325
+ * // Check for missing components before import
38326
+ * const missingIds = centralPlant.getMissingLibraryIds(sceneJson);
38327
+ * if (missingIds.length > 0) {
38328
+ * // Fetch missing S3 components
38329
+ * await fetchS3Components(missingIds);
38330
+ * await centralPlant.extendComponentDictionary(fetchedComponents);
38331
+ * }
38332
+ * // Now safe to import
38333
+ * await centralPlant.importScene(sceneJson);
38334
+ */
38335
+ )
38336
+ }, {
38337
+ key: "getMissingLibraryIds",
38338
+ value: function getMissingLibraryIds(sceneData) {
38339
+ var _sceneData$scene, _this$managers$compon;
38340
+ if (!(sceneData !== null && sceneData !== void 0 && (_sceneData$scene = sceneData.scene) !== null && _sceneData$scene !== void 0 && _sceneData$scene.children) || !Array.isArray(sceneData.scene.children)) {
38341
+ return [];
38342
+ }
38343
+ var componentDictionary = ((_this$managers$compon = this.managers.componentDataManager) === null || _this$managers$compon === void 0 ? void 0 : _this$managers$compon.componentDictionary) || {};
38344
+ var missingIds = [];
38345
+ sceneData.scene.children.forEach(function (child) {
38346
+ var _child$userData;
38347
+ var libraryId = (_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.libraryId;
38348
+ if (libraryId && !componentDictionary[libraryId]) {
38349
+ // Only add unique IDs
38350
+ if (!missingIds.includes(libraryId)) {
38351
+ missingIds.push(libraryId);
38352
+ }
38353
+ }
38354
+ });
38355
+ if (missingIds.length > 0) {
38356
+ console.log("\uD83D\uDD0D Found ".concat(missingIds.length, " missing library IDs in scene data:"), missingIds);
38357
+ }
38358
+ return missingIds;
38359
+ }
38360
+
38277
38361
  /**
38278
38362
  * Select an object (component, connector, or gateway) in the scene by its ID
38279
38363
  * @param {string} objectId - The UUID or name of the object to select
@@ -38310,7 +38394,6 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
38310
38394
  * centralPlant.selectComponent(targetObject.uuid);
38311
38395
  * }
38312
38396
  */
38313
- )
38314
38397
  }, {
38315
38398
  key: "selectComponent",
38316
38399
  value: function selectComponent(objectId) {
@@ -38339,8 +38422,8 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
38339
38422
  // If still not found, try finding by originalUuid in userData
38340
38423
  if (!targetObject) {
38341
38424
  this.sceneViewer.scene.traverse(function (child) {
38342
- var _child$userData;
38343
- if (((_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.originalUuid) === objectId) {
38425
+ var _child$userData2;
38426
+ if (((_child$userData2 = child.userData) === null || _child$userData2 === void 0 ? void 0 : _child$userData2.originalUuid) === objectId) {
38344
38427
  targetObject = child;
38345
38428
  return;
38346
38429
  }