@2112-lab/central-plant 0.1.88 → 0.1.91

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,162 @@ 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
+ * If a device model is not in cache, it will be preloaded first.
28574
+ *
28575
+ * @param {THREE.Object3D} componentModel - The parent component model
28576
+ * @param {Object} componentData - Component dictionary entry (has attachedDevices)
28577
+ * @param {Object} modelPreloader - ModelPreloader instance with cache and componentDictionary
28578
+ * @param {string} parentComponentId - The parent component's UUID
28579
+ * @returns {Promise<void>}
28580
+ */
28581
+ function attachIODevicesToComponent(_x, _x2, _x3, _x4) {
28582
+ return _attachIODevicesToComponent.apply(this, arguments);
28583
+ }
28584
+ function _attachIODevicesToComponent() {
28585
+ _attachIODevicesToComponent = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(componentModel, componentData, modelPreloader, parentComponentId) {
28586
+ var attachedDevices, _i, _Object$entries, _Object$entries$_i, attachmentId, attachment, _modelPreloader$compo, _deviceData$ioConfig, _deviceData$ioConfig2, _deviceData$ioConfig3, _attachment$attachmen, deviceData, cachedDevice, _modelPreloader$loadi, deviceModel, pos, _t, _t2;
28587
+ return _regenerator().w(function (_context) {
28588
+ while (1) switch (_context.n) {
28589
+ case 0:
28590
+ attachedDevices = componentData.attachedDevices;
28591
+ if (!(!attachedDevices || Object.keys(attachedDevices).length === 0)) {
28592
+ _context.n = 1;
28593
+ break;
28594
+ }
28595
+ return _context.a(2);
28596
+ case 1:
28597
+ console.log("\uD83D\uDD0C attachIODevicesToComponent(): Attaching ".concat(Object.keys(attachedDevices).length, " IO devices to smart component"));
28598
+ _i = 0, _Object$entries = Object.entries(attachedDevices);
28599
+ case 2:
28600
+ if (!(_i < _Object$entries.length)) {
28601
+ _context.n = 14;
28602
+ break;
28603
+ }
28604
+ _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2), attachmentId = _Object$entries$_i[0], attachment = _Object$entries$_i[1];
28605
+ _context.p = 3;
28606
+ deviceData = (_modelPreloader$compo = modelPreloader.componentDictionary) === null || _modelPreloader$compo === void 0 ? void 0 : _modelPreloader$compo[attachment.deviceId];
28607
+ if (!(!deviceData || !deviceData.modelKey)) {
28608
+ _context.n = 4;
28609
+ break;
28610
+ }
28611
+ console.warn("\u26A0\uFE0F IO device ".concat(attachment.deviceId, " not found in dictionary, skipping"));
28612
+ return _context.a(3, 13);
28613
+ case 4:
28614
+ // Try to get from cache first
28615
+ cachedDevice = modelPreloader.getCachedModelWithDimensions(deviceData.modelKey, attachment.deviceId); // If not in cache, try to preload it
28616
+ if (cachedDevice) {
28617
+ _context.n = 10;
28618
+ break;
28619
+ }
28620
+ console.log("\uD83D\uDD04 IO device model not in cache, preloading: ".concat(deviceData.modelKey));
28621
+ _context.p = 5;
28622
+ if (!((_modelPreloader$loadi = modelPreloader.loadingPromises) !== null && _modelPreloader$loadi !== void 0 && _modelPreloader$loadi.has(deviceData.modelKey))) {
28623
+ _context.n = 7;
28624
+ break;
28625
+ }
28626
+ _context.n = 6;
28627
+ return modelPreloader.loadingPromises.get(deviceData.modelKey);
28628
+ case 6:
28629
+ _context.n = 8;
28630
+ break;
28631
+ case 7:
28632
+ _context.n = 8;
28633
+ return modelPreloader.preloadSingleModel(deviceData.modelKey);
28634
+ case 8:
28635
+ cachedDevice = modelPreloader.getCachedModelWithDimensions(deviceData.modelKey, attachment.deviceId);
28636
+ _context.n = 10;
28637
+ break;
28638
+ case 9:
28639
+ _context.p = 9;
28640
+ _t = _context.v;
28641
+ console.warn("\u26A0\uFE0F Failed to preload IO device model ".concat(deviceData.modelKey, ":"), _t);
28642
+ case 10:
28643
+ if (cachedDevice) {
28644
+ _context.n = 11;
28645
+ break;
28646
+ }
28647
+ console.warn("\u26A0\uFE0F IO device model could not be loaded: ".concat(deviceData.modelKey, ", skipping"));
28648
+ return _context.a(3, 13);
28649
+ case 11:
28650
+ // Clone so each component instance owns its own io-device subtree and materials.
28651
+ // Without this, all placed copies of the same smart component share the cached
28652
+ // object, causing material mutations (from behaviors) to bleed across instances.
28653
+ deviceModel = cachedDevice.clone();
28654
+ deviceModel.traverse(function (child) {
28655
+ if (child.isMesh && child.material) {
28656
+ child.material = Array.isArray(child.material) ? child.material.map(function (m) {
28657
+ return m.clone();
28658
+ }) : child.material.clone();
28659
+ }
28660
+ });
28661
+
28662
+ // Name the device model
28663
+ deviceModel.name = "".concat(attachment.attachmentLabel || 'IO Device', " (").concat(attachmentId, ")");
28664
+
28665
+ // Set user data for identification — include ioConfig data points so the
28666
+ // component tooltip can render state displays without an extra lookup.
28667
+ deviceModel.userData = {
28668
+ objectType: 'io-device',
28669
+ deviceId: attachment.deviceId,
28670
+ attachmentId: attachmentId,
28671
+ attachmentLabel: attachment.attachmentLabel,
28672
+ parentComponentId: parentComponentId,
28673
+ deviceName: deviceData.name || '',
28674
+ // Snapshot of the device's data point definitions (stateType, stateConfig, direction, etc.)
28675
+ // ioConfig can use either 'states' (preferred) or legacy 'dataPoints' as the array key
28676
+ 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) || [],
28677
+ // Device-level I/O direction: 'input' means the user can write state via the tooltip
28678
+ ioDirection: ((_deviceData$ioConfig3 = deviceData.ioConfig) === null || _deviceData$ioConfig3 === void 0 ? void 0 : _deviceData$ioConfig3.direction) || 'output',
28679
+ // Signal wiring sourced from this attachment (for state propagation reference)
28680
+ signalOutputs: attachment.signalOutputs || []
28681
+ };
28682
+
28683
+ // Position at the attachment point
28684
+ if ((_attachment$attachmen = attachment.attachmentPoint) !== null && _attachment$attachmen !== void 0 && _attachment$attachmen.position) {
28685
+ pos = attachment.attachmentPoint.position;
28686
+ deviceModel.position.set(pos.x || 0, pos.y || 0, pos.z || 0);
28687
+ }
28688
+
28689
+ // IO device models are authored at the same real-world unit scale
28690
+ // as the host component, so keep them at their natural (1:1) size.
28691
+ // Note: attachmentPoint.scale is the connector marker sphere size,
28692
+ // NOT a desired device model scale.
28693
+ deviceModel.scale.setScalar(1);
28694
+
28695
+ // Add as child of the component
28696
+ componentModel.add(deviceModel);
28697
+ console.log("\u2705 Attached IO device: ".concat(attachment.attachmentLabel || attachment.deviceId, " at"), {
28698
+ position: deviceModel.position,
28699
+ scale: deviceModel.scale
28700
+ });
28701
+ _context.n = 13;
28702
+ break;
28703
+ case 12:
28704
+ _context.p = 12;
28705
+ _t2 = _context.v;
28706
+ console.error("\u274C Error attaching IO device ".concat(attachment.deviceId, ":"), _t2);
28707
+ case 13:
28708
+ _i++;
28709
+ _context.n = 2;
28710
+ break;
28711
+ case 14:
28712
+ return _context.a(2);
28713
+ }
28714
+ }, _callee, null, [[5, 9], [3, 12]]);
28715
+ }));
28716
+ return _attachIODevicesToComponent.apply(this, arguments);
28717
+ }
28718
+
28559
28719
  var ModelManager = /*#__PURE__*/function () {
28560
28720
  function ModelManager(sceneViewer) {
28561
28721
  _classCallCheck(this, ModelManager);
@@ -28639,17 +28799,25 @@ var ModelManager = /*#__PURE__*/function () {
28639
28799
  libraryModel.add(connector);
28640
28800
  });
28641
28801
 
28802
+ // Attach IO devices for smart components (import flow)
28803
+ if (!(componentData.isSmart && componentData.attachedDevices)) {
28804
+ _context.n = 4;
28805
+ break;
28806
+ }
28807
+ _context.n = 4;
28808
+ return attachIODevicesToComponent(libraryModel, componentData, modelPreloader, originalProps.uuid);
28809
+ case 4:
28642
28810
  // Replace mesh in scene
28643
28811
  this._replaceMeshInScene(targetMesh, libraryModel, originalProps.parent, component);
28644
28812
  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"));
28645
28813
  return _context.a(2, libraryModel);
28646
- case 4:
28647
- _context.p = 4;
28814
+ case 5:
28815
+ _context.p = 5;
28648
28816
  _t = _context.v;
28649
28817
  console.error("\u274C Error loading ".concat((_jsonEntry$userData4 = jsonEntry.userData) === null || _jsonEntry$userData4 === void 0 ? void 0 : _jsonEntry$userData4.libraryId, " GLB model:"), _t);
28650
28818
  return _context.a(2, targetMesh);
28651
28819
  }
28652
- }, _callee, this, [[1, 4]]);
28820
+ }, _callee, this, [[1, 5]]);
28653
28821
  }));
28654
28822
  function loadLibraryModel(_x, _x2, _x3) {
28655
28823
  return _loadLibraryModel.apply(this, arguments);
@@ -28715,7 +28883,7 @@ var ModelManager = /*#__PURE__*/function () {
28715
28883
  value: (function () {
28716
28884
  var _getLibraryModel2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2(modelKey, libraryId) {
28717
28885
  var _this2 = this;
28718
- var gltfScene, preloaderStatus, modelPath, gltf, _t2, _t3;
28886
+ var gltfScene, preloaderStatus, modelPath, gltf, _t2, _t3, _t4;
28719
28887
  return _regenerator().w(function (_context2) {
28720
28888
  while (1) switch (_context2.n) {
28721
28889
  case 0:
@@ -28752,25 +28920,47 @@ var ModelManager = /*#__PURE__*/function () {
28752
28920
  console.warn("\u26A0\uFE0F Preloading failed:", _t2);
28753
28921
  case 6:
28754
28922
  _context2.p = 6;
28923
+ if (!modelPreloader.urlResolver) {
28924
+ _context2.n = 11;
28925
+ break;
28926
+ }
28927
+ _context2.p = 7;
28928
+ _context2.n = 8;
28929
+ return modelPreloader.urlResolver(modelKey);
28930
+ case 8:
28931
+ modelPath = _context2.v;
28932
+ console.log("\uD83D\uDD17 Resolved URL for ".concat(modelKey));
28933
+ _context2.n = 10;
28934
+ break;
28935
+ case 9:
28936
+ _context2.p = 9;
28937
+ _t3 = _context2.v;
28938
+ console.warn("\u26A0\uFE0F URL resolver failed for ".concat(modelKey, ", falling back to local path:"), _t3);
28755
28939
  modelPath = "".concat(modelPreloader.modelsBasePath).concat(modelKey);
28940
+ case 10:
28941
+ _context2.n = 12;
28942
+ break;
28943
+ case 11:
28944
+ modelPath = "".concat(modelPreloader.modelsBasePath).concat(modelKey);
28945
+ case 12:
28756
28946
  console.log("\uD83D\uDCC2 Fallback loading from: ".concat(modelPath));
28757
- _context2.n = 7;
28947
+ _context2.n = 13;
28758
28948
  return new Promise(function (resolve, reject) {
28759
28949
  _this2.sceneViewer.gltfLoader.load(modelPath, resolve, undefined, reject);
28760
28950
  });
28761
- case 7:
28951
+ case 13:
28762
28952
  gltf = _context2.v;
28763
28953
  if (libraryId) {
28764
28954
  modelPreloader.cacheModel(modelKey, gltf.scene.clone(), libraryId);
28765
28955
  }
28766
28956
  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);
28957
+ case 14:
28958
+ _context2.p = 14;
28959
+ _t4 = _context2.v;
28960
+ console.error("Failed to load model ".concat(modelKey, ":"), _t4);
28771
28961
  return _context2.a(2, null);
28772
28962
  }
28773
- }, _callee2, null, [[6, 8], [2, 5]]);
28963
+ }, _callee2, null, [[7, 9], [6, 14], [2, 5]]);
28774
28964
  }));
28775
28965
  function _getLibraryModel(_x4, _x5) {
28776
28966
  return _getLibraryModel2.apply(this, arguments);
@@ -28869,7 +29059,7 @@ var ModelManager = /*#__PURE__*/function () {
28869
29059
  key: "verifyModelPreloaderCache",
28870
29060
  value: (function () {
28871
29061
  var _verifyModelPreloaderCache = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee3() {
28872
- var preloaderStatus, _t4;
29062
+ var preloaderStatus, _t5;
28873
29063
  return _regenerator().w(function (_context3) {
28874
29064
  while (1) switch (_context3.n) {
28875
29065
  case 0:
@@ -28892,8 +29082,8 @@ var ModelManager = /*#__PURE__*/function () {
28892
29082
  break;
28893
29083
  case 3:
28894
29084
  _context3.p = 3;
28895
- _t4 = _context3.v;
28896
- console.warn('⚠️ Model preloading failed, some models may load directly:', _t4);
29085
+ _t5 = _context3.v;
29086
+ console.warn('⚠️ Model preloading failed, some models may load directly:', _t5);
28897
29087
  case 4:
28898
29088
  return _context3.a(2, preloaderStatus);
28899
29089
  }
@@ -28912,7 +29102,7 @@ var ModelManager = /*#__PURE__*/function () {
28912
29102
  key: "preloadMissingModels",
28913
29103
  value: (function () {
28914
29104
  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;
29105
+ var _data$scene, _data$scene2, requiredModels, preloaderStatus, cachedModels, missingModels, basePath, modelUrls, _iterator, _step, modelKey, _t6, _t7;
28916
29106
  return _regenerator().w(function (_context4) {
28917
29107
  while (1) switch (_context4.n) {
28918
29108
  case 0:
@@ -29007,8 +29197,8 @@ var ModelManager = /*#__PURE__*/function () {
29007
29197
  break;
29008
29198
  case 7:
29009
29199
  _context4.p = 7;
29010
- _t5 = _context4.v;
29011
- console.warn("\u274C Failed to preload missing model ".concat(modelKey, ":"), _t5);
29200
+ _t6 = _context4.v;
29201
+ console.warn("\u274C Failed to preload missing model ".concat(modelKey, ":"), _t6);
29012
29202
  case 8:
29013
29203
  _context4.n = 4;
29014
29204
  break;
@@ -29017,8 +29207,8 @@ var ModelManager = /*#__PURE__*/function () {
29017
29207
  break;
29018
29208
  case 10:
29019
29209
  _context4.p = 10;
29020
- _t6 = _context4.v;
29021
- _iterator.e(_t6);
29210
+ _t7 = _context4.v;
29211
+ _iterator.e(_t7);
29022
29212
  case 11:
29023
29213
  _context4.p = 11;
29024
29214
  _iterator.f();
@@ -29123,7 +29313,7 @@ var ModelManager = /*#__PURE__*/function () {
29123
29313
  key: "loadComponentDictionary",
29124
29314
  value: (function () {
29125
29315
  var _loadComponentDictionary = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee6() {
29126
- var response, dict, _t7;
29316
+ var response, dict, _t8;
29127
29317
  return _regenerator().w(function (_context6) {
29128
29318
  while (1) switch (_context6.n) {
29129
29319
  case 0:
@@ -29141,8 +29331,8 @@ var ModelManager = /*#__PURE__*/function () {
29141
29331
  return _context6.a(2, dict);
29142
29332
  case 3:
29143
29333
  _context6.p = 3;
29144
- _t7 = _context6.v;
29145
- console.warn('⚠️ Could not load component dictionary:', _t7);
29334
+ _t8 = _context6.v;
29335
+ console.warn('⚠️ Could not load component dictionary:', _t8);
29146
29336
  return _context6.a(2, {});
29147
29337
  }
29148
29338
  }, _callee6, null, [[0, 3]]);
@@ -29938,7 +30128,8 @@ var SceneOperationsManager = /*#__PURE__*/function () {
29938
30128
  return createSceneMaterials;
29939
30129
  }()
29940
30130
  /**
29941
- * Helper function to create geometries from component library
30131
+ * Helper function to create geometries for connectors and gateways
30132
+ * Components use GLB geometry directly, no placeholder needed
29942
30133
  */
29943
30134
  )
29944
30135
  }, {
@@ -29953,7 +30144,7 @@ var SceneOperationsManager = /*#__PURE__*/function () {
29953
30144
  return geometries;
29954
30145
  }
29955
30146
 
29956
- // Create geometries based on component library bounding boxes instead of JSON scene.geometries
30147
+ // Create geometries only for connectors and gateways (components use GLB geometry)
29957
30148
  data.scene.children.forEach(function (child) {
29958
30149
  var _child$userData, _child$userData2;
29959
30150
  // Skip manual segments - they create their own geometry
@@ -29962,42 +30153,28 @@ var SceneOperationsManager = /*#__PURE__*/function () {
29962
30153
  console.log("\u23ED\uFE0F Skipping geometry creation for manual segment: ".concat(child.uuid));
29963
30154
  return;
29964
30155
  }
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
30156
 
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);
30157
+ // Skip components - they will use GLB geometry directly
30158
+ if ((_child$userData2 = child.userData) !== null && _child$userData2 !== void 0 && _child$userData2.libraryId) {
30159
+ return;
30160
+ }
29971
30161
 
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
- }
30162
+ // For non-library objects (connectors, gateways), create shared sphere geometry
30163
+ var isConnectorOrGateway = child.uuid && (child.uuid.toLowerCase().includes('connector') || child.uuid.toLowerCase().includes('gateway'));
30164
+ console.log("createSceneGeometries child:", child);
30165
+ if (isConnectorOrGateway) {
30166
+ // Check if position exists and is not at origin
30167
+ if (child.position && !(child.position.x == 0 && child.position.y == 0 && child.position.z == 0)) {
30168
+ // Determine if this is a gateway or connector
30169
+ var isGateway = child.uuid.toLowerCase().includes('gateway');
30170
+ var geometryKey = isGateway ? 'GATEWAY_SPHERE' : 'CONNECTOR_GATEWAY_SPHERE';
30171
+ if (!geometries[geometryKey]) {
30172
+ // Match computed gateway size (0.15) for gateways, keep 0.1 for connectors
30173
+ var sphereRadius = isGateway ? 0.15 : 0.1;
30174
+ geometries[geometryKey] = new THREE__namespace.SphereGeometry(sphereRadius, 16, 16);
30175
+ console.log("\uD83D\uDD2E Created shared sphere geometry for ".concat(isGateway ? 'gateways' : 'connectors', " (radius: ").concat(sphereRadius, ")"));
29991
30176
  }
29992
30177
  }
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
30178
  }
30002
30179
  });
30003
30180
  return geometries;
@@ -30761,7 +30938,8 @@ var SceneOperationsManager = /*#__PURE__*/function () {
30761
30938
  key: "_createBasicSceneObjects",
30762
30939
  value: (function () {
30763
30940
  var _createBasicSceneObjects2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee6(data) {
30764
- var _this4 = this;
30941
+ var _this$sceneViewer$cen,
30942
+ _this4 = this;
30765
30943
  var componentDictionary, _yield$this$createSce, materials, crosscubeTextureSet, geometries, libraryObjectsToReplace;
30766
30944
  return _regenerator().w(function (_context6) {
30767
30945
  while (1) switch (_context6.n) {
@@ -30772,20 +30950,33 @@ var SceneOperationsManager = /*#__PURE__*/function () {
30772
30950
  }
30773
30951
  throw new Error('Invalid scene data structure: data.scene.children must be an array');
30774
30952
  case 1:
30953
+ // Use the extended component dictionary (includes S3 components) if available,
30954
+ // otherwise fall back to loading from static file
30955
+ 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;
30956
+ if (componentDictionary) {
30957
+ _context6.n = 3;
30958
+ break;
30959
+ }
30960
+ console.log('⚠️ Extended dictionary not available, loading from static file');
30775
30961
  _context6.n = 2;
30776
30962
  return this.modelManager.loadComponentDictionary();
30777
30963
  case 2:
30778
30964
  componentDictionary = _context6.v;
30965
+ _context6.n = 4;
30966
+ break;
30967
+ case 3:
30968
+ console.log("\u2705 Using extended component dictionary (".concat(Object.keys(componentDictionary).length, " components)"));
30969
+ case 4:
30779
30970
  // Inject connector children from component dictionary into scene data
30780
30971
  this._injectConnectorChildrenFromDictionary(data, componentDictionary);
30781
30972
 
30782
30973
  // Ensure models are preloaded
30783
- _context6.n = 3;
30974
+ _context6.n = 5;
30784
30975
  return this.modelManager.preloadMissingModels(data, componentDictionary);
30785
- case 3:
30786
- _context6.n = 4;
30976
+ case 5:
30977
+ _context6.n = 6;
30787
30978
  return this.createSceneMaterials(data);
30788
- case 4:
30979
+ case 6:
30789
30980
  _yield$this$createSce = _context6.v;
30790
30981
  materials = _yield$this$createSce.materials;
30791
30982
  crosscubeTextureSet = _yield$this$createSce.crosscubeTextureSet;
@@ -36527,7 +36718,7 @@ var CentralPlantInternals = /*#__PURE__*/function () {
36527
36718
 
36528
36719
  // Add attached IO device models for smart components
36529
36720
  if (componentData.isSmart && componentData.attachedDevices) {
36530
- this._attachIODevicesToComponent(componentModel, componentData, modelPreloader, componentId);
36721
+ attachIODevicesToComponent(componentModel, componentData, modelPreloader, componentId);
36531
36722
  }
36532
36723
 
36533
36724
  // Notify the component manager about the new component
@@ -36580,95 +36771,6 @@ var CentralPlantInternals = /*#__PURE__*/function () {
36580
36771
  }
36581
36772
  }
36582
36773
 
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
36774
  /**
36673
36775
  * Delete a component from the scene by componentId (internal implementation)
36674
36776
  * @param {string} componentId - The UUID of the component to delete
@@ -36774,7 +36876,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
36774
36876
  * Initialize the CentralPlant manager
36775
36877
  *
36776
36878
  * @constructor
36777
- * @version 0.1.88
36879
+ * @version 0.1.91
36778
36880
  * @updated 2025-10-22
36779
36881
  *
36780
36882
  * @description Creates a new CentralPlant instance and initializes internal managers and utilities.
@@ -38274,6 +38376,50 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
38274
38376
  }
38275
38377
  return removeComponentFromDictionary;
38276
38378
  }()
38379
+ /**
38380
+ * Get library IDs from scene data that are missing from the component dictionary
38381
+ * @param {Object} sceneData - Scene JSON data to analyze
38382
+ * @returns {string[]} Array of library IDs that are not in the current component dictionary
38383
+ * @description Analyzes scene data to find components whose libraryId is not present in the
38384
+ * component dictionary. This is useful for detecting S3 components that need to be fetched
38385
+ * before importing a scene.
38386
+ * @example
38387
+ * // Check for missing components before import
38388
+ * const missingIds = centralPlant.getMissingLibraryIds(sceneJson);
38389
+ * if (missingIds.length > 0) {
38390
+ * // Fetch missing S3 components
38391
+ * await fetchS3Components(missingIds);
38392
+ * await centralPlant.extendComponentDictionary(fetchedComponents);
38393
+ * }
38394
+ * // Now safe to import
38395
+ * await centralPlant.importScene(sceneJson);
38396
+ */
38397
+ )
38398
+ }, {
38399
+ key: "getMissingLibraryIds",
38400
+ value: function getMissingLibraryIds(sceneData) {
38401
+ var _sceneData$scene, _this$managers$compon;
38402
+ if (!(sceneData !== null && sceneData !== void 0 && (_sceneData$scene = sceneData.scene) !== null && _sceneData$scene !== void 0 && _sceneData$scene.children) || !Array.isArray(sceneData.scene.children)) {
38403
+ return [];
38404
+ }
38405
+ var componentDictionary = ((_this$managers$compon = this.managers.componentDataManager) === null || _this$managers$compon === void 0 ? void 0 : _this$managers$compon.componentDictionary) || {};
38406
+ var missingIds = [];
38407
+ sceneData.scene.children.forEach(function (child) {
38408
+ var _child$userData;
38409
+ var libraryId = (_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.libraryId;
38410
+ if (libraryId && !componentDictionary[libraryId]) {
38411
+ // Only add unique IDs
38412
+ if (!missingIds.includes(libraryId)) {
38413
+ missingIds.push(libraryId);
38414
+ }
38415
+ }
38416
+ });
38417
+ if (missingIds.length > 0) {
38418
+ console.log("\uD83D\uDD0D Found ".concat(missingIds.length, " missing library IDs in scene data:"), missingIds);
38419
+ }
38420
+ return missingIds;
38421
+ }
38422
+
38277
38423
  /**
38278
38424
  * Select an object (component, connector, or gateway) in the scene by its ID
38279
38425
  * @param {string} objectId - The UUID or name of the object to select
@@ -38310,7 +38456,6 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
38310
38456
  * centralPlant.selectComponent(targetObject.uuid);
38311
38457
  * }
38312
38458
  */
38313
- )
38314
38459
  }, {
38315
38460
  key: "selectComponent",
38316
38461
  value: function selectComponent(objectId) {
@@ -38339,8 +38484,8 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
38339
38484
  // If still not found, try finding by originalUuid in userData
38340
38485
  if (!targetObject) {
38341
38486
  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) {
38487
+ var _child$userData2;
38488
+ if (((_child$userData2 = child.userData) === null || _child$userData2 === void 0 ? void 0 : _child$userData2.originalUuid) === objectId) {
38344
38489
  targetObject = child;
38345
38490
  return;
38346
38491
  }