@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.
@@ -19,7 +19,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
19
19
  * Initialize the CentralPlant manager
20
20
  *
21
21
  * @constructor
22
- * @version 0.1.87
22
+ * @version 0.1.90
23
23
  * @updated 2025-10-22
24
24
  *
25
25
  * @description Creates a new CentralPlant instance and initializes internal managers and utilities.
@@ -1519,6 +1519,50 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
1519
1519
  }
1520
1520
  return removeComponentFromDictionary;
1521
1521
  }()
1522
+ /**
1523
+ * Get library IDs from scene data that are missing from the component dictionary
1524
+ * @param {Object} sceneData - Scene JSON data to analyze
1525
+ * @returns {string[]} Array of library IDs that are not in the current component dictionary
1526
+ * @description Analyzes scene data to find components whose libraryId is not present in the
1527
+ * component dictionary. This is useful for detecting S3 components that need to be fetched
1528
+ * before importing a scene.
1529
+ * @example
1530
+ * // Check for missing components before import
1531
+ * const missingIds = centralPlant.getMissingLibraryIds(sceneJson);
1532
+ * if (missingIds.length > 0) {
1533
+ * // Fetch missing S3 components
1534
+ * await fetchS3Components(missingIds);
1535
+ * await centralPlant.extendComponentDictionary(fetchedComponents);
1536
+ * }
1537
+ * // Now safe to import
1538
+ * await centralPlant.importScene(sceneJson);
1539
+ */
1540
+ )
1541
+ }, {
1542
+ key: "getMissingLibraryIds",
1543
+ value: function getMissingLibraryIds(sceneData) {
1544
+ var _sceneData$scene, _this$managers$compon;
1545
+ if (!(sceneData !== null && sceneData !== void 0 && (_sceneData$scene = sceneData.scene) !== null && _sceneData$scene !== void 0 && _sceneData$scene.children) || !Array.isArray(sceneData.scene.children)) {
1546
+ return [];
1547
+ }
1548
+ var componentDictionary = ((_this$managers$compon = this.managers.componentDataManager) === null || _this$managers$compon === void 0 ? void 0 : _this$managers$compon.componentDictionary) || {};
1549
+ var missingIds = [];
1550
+ sceneData.scene.children.forEach(function (child) {
1551
+ var _child$userData;
1552
+ var libraryId = (_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.libraryId;
1553
+ if (libraryId && !componentDictionary[libraryId]) {
1554
+ // Only add unique IDs
1555
+ if (!missingIds.includes(libraryId)) {
1556
+ missingIds.push(libraryId);
1557
+ }
1558
+ }
1559
+ });
1560
+ if (missingIds.length > 0) {
1561
+ console.log("\uD83D\uDD0D Found ".concat(missingIds.length, " missing library IDs in scene data:"), missingIds);
1562
+ }
1563
+ return missingIds;
1564
+ }
1565
+
1522
1566
  /**
1523
1567
  * Select an object (component, connector, or gateway) in the scene by its ID
1524
1568
  * @param {string} objectId - The UUID or name of the object to select
@@ -1555,7 +1599,6 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
1555
1599
  * centralPlant.selectComponent(targetObject.uuid);
1556
1600
  * }
1557
1601
  */
1558
- )
1559
1602
  }, {
1560
1603
  key: "selectComponent",
1561
1604
  value: function selectComponent(objectId) {
@@ -1584,8 +1627,8 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
1584
1627
  // If still not found, try finding by originalUuid in userData
1585
1628
  if (!targetObject) {
1586
1629
  this.sceneViewer.scene.traverse(function (child) {
1587
- var _child$userData;
1588
- if (((_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.originalUuid) === objectId) {
1630
+ var _child$userData2;
1631
+ if (((_child$userData2 = child.userData) === null || _child$userData2 === void 0 ? void 0 : _child$userData2.originalUuid) === objectId) {
1589
1632
  targetObject = child;
1590
1633
  return;
1591
1634
  }
@@ -27,6 +27,7 @@ var sceneTooltipsManager = require('../managers/scene/sceneTooltipsManager.js');
27
27
  var componentTooltipManager = require('../managers/scene/componentTooltipManager.js');
28
28
  var viewport2DManager = require('../managers/scene/viewport2DManager.js');
29
29
  var nameUtils = require('../utils/nameUtils.js');
30
+ var ioDeviceUtils = require('../utils/ioDeviceUtils.js');
30
31
  var modelPreloader = require('../rendering/modelPreloader.js');
31
32
 
32
33
  function _interopNamespace(e) {
@@ -1078,7 +1079,7 @@ var CentralPlantInternals = /*#__PURE__*/function () {
1078
1079
 
1079
1080
  // Add attached IO device models for smart components
1080
1081
  if (componentData.isSmart && componentData.attachedDevices) {
1081
- this._attachIODevicesToComponent(componentModel, componentData, modelPreloader, componentId);
1082
+ ioDeviceUtils.attachIODevicesToComponent(componentModel, componentData, modelPreloader, componentId);
1082
1083
  }
1083
1084
 
1084
1085
  // Notify the component manager about the new component
@@ -1131,95 +1132,6 @@ var CentralPlantInternals = /*#__PURE__*/function () {
1131
1132
  }
1132
1133
  }
1133
1134
 
1134
- /**
1135
- * Attach IO device models to a smart component from cached models.
1136
- * Each device referenced in componentData.attachedDevices is looked up
1137
- * in the model preloader cache, cloned, positioned, and added as a child.
1138
- * @param {THREE.Object3D} componentModel - The parent component model
1139
- * @param {Object} componentData - Component dictionary entry (has attachedDevices)
1140
- * @param {Object} modelPreloader - ModelPreloader instance
1141
- * @param {string} parentComponentId - The parent component's UUID
1142
- * @private
1143
- */
1144
- }, {
1145
- key: "_attachIODevicesToComponent",
1146
- value: function _attachIODevicesToComponent(componentModel, componentData, modelPreloader, parentComponentId) {
1147
- var attachedDevices = componentData.attachedDevices;
1148
- console.log("\uD83D\uDD0C addComponent(): Attaching ".concat(Object.keys(attachedDevices).length, " IO devices to smart component"));
1149
- for (var _i = 0, _Object$entries = Object.entries(attachedDevices); _i < _Object$entries.length; _i++) {
1150
- var _Object$entries$_i = _rollupPluginBabelHelpers.slicedToArray(_Object$entries[_i], 2),
1151
- attachmentId = _Object$entries$_i[0],
1152
- attachment = _Object$entries$_i[1];
1153
- try {
1154
- var _modelPreloader$compo, _deviceData$ioConfig, _deviceData$ioConfig2, _deviceData$ioConfig3, _attachment$attachmen;
1155
- var deviceData = (_modelPreloader$compo = modelPreloader.componentDictionary) === null || _modelPreloader$compo === void 0 ? void 0 : _modelPreloader$compo[attachment.deviceId];
1156
- if (!deviceData || !deviceData.modelKey) {
1157
- console.warn("\u26A0\uFE0F IO device ".concat(attachment.deviceId, " not found in dictionary, skipping"));
1158
- continue;
1159
- }
1160
- var cachedDevice = modelPreloader.getCachedModelWithDimensions(deviceData.modelKey, attachment.deviceId);
1161
- if (!cachedDevice) {
1162
- console.warn("\u26A0\uFE0F IO device model not in cache: ".concat(deviceData.modelKey, ", skipping"));
1163
- continue;
1164
- }
1165
-
1166
- // Clone so each component instance owns its own io-device subtree and materials.
1167
- // Without this, all placed copies of the same smart component share the cached
1168
- // object, causing material mutations (from behaviors) to bleed across instances.
1169
- var deviceModel = cachedDevice.clone();
1170
- deviceModel.traverse(function (child) {
1171
- if (child.isMesh && child.material) {
1172
- child.material = Array.isArray(child.material) ? child.material.map(function (m) {
1173
- return m.clone();
1174
- }) : child.material.clone();
1175
- }
1176
- });
1177
-
1178
- // Name the device model
1179
- deviceModel.name = "".concat(attachment.attachmentLabel || 'IO Device', " (").concat(attachmentId, ")");
1180
-
1181
- // Set user data for identification — include ioConfig data points so the
1182
- // component tooltip can render state displays without an extra lookup.
1183
- deviceModel.userData = {
1184
- objectType: 'io-device',
1185
- deviceId: attachment.deviceId,
1186
- attachmentId: attachmentId,
1187
- attachmentLabel: attachment.attachmentLabel,
1188
- parentComponentId: parentComponentId,
1189
- deviceName: deviceData.name || '',
1190
- // Snapshot of the device's data point definitions (stateType, stateConfig, direction, etc.)
1191
- // ioConfig can use either 'states' (preferred) or legacy 'dataPoints' as the array key
1192
- 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) || [],
1193
- // Device-level I/O direction: 'input' means the user can write state via the tooltip
1194
- ioDirection: ((_deviceData$ioConfig3 = deviceData.ioConfig) === null || _deviceData$ioConfig3 === void 0 ? void 0 : _deviceData$ioConfig3.direction) || 'output',
1195
- // Signal wiring sourced from this attachment (for state propagation reference)
1196
- signalOutputs: attachment.signalOutputs || []
1197
- };
1198
-
1199
- // Position at the attachment point
1200
- if ((_attachment$attachmen = attachment.attachmentPoint) !== null && _attachment$attachmen !== void 0 && _attachment$attachmen.position) {
1201
- var pos = attachment.attachmentPoint.position;
1202
- deviceModel.position.set(pos.x || 0, pos.y || 0, pos.z || 0);
1203
- }
1204
-
1205
- // IO device models are authored at the same real-world unit scale
1206
- // as the host component, so keep them at their natural (1:1) size.
1207
- // Note: attachmentPoint.scale is the connector marker sphere size,
1208
- // NOT a desired device model scale.
1209
- deviceModel.scale.setScalar(1);
1210
-
1211
- // Add as child of the component
1212
- componentModel.add(deviceModel);
1213
- console.log("\u2705 Attached IO device: ".concat(attachment.attachmentLabel || attachment.deviceId, " at"), {
1214
- position: deviceModel.position,
1215
- scale: deviceModel.scale
1216
- });
1217
- } catch (err) {
1218
- console.error("\u274C Error attaching IO device ".concat(attachment.deviceId, ":"), err);
1219
- }
1220
- }
1221
- }
1222
-
1223
1135
  /**
1224
1136
  * Delete a component from the scene by componentId (internal implementation)
1225
1137
  * @param {string} componentId - The UUID of the component to delete
@@ -821,7 +821,7 @@ var CentralPlantValidator = /*#__PURE__*/function () {
821
821
  key: "validateImportedSceneData",
822
822
  value: function validateImportedSceneData(sceneData) {
823
823
  var results = [];
824
- var hardcodedVersion = "2.1";
824
+ var hardcodedVersion = "2.3";
825
825
 
826
826
  // Version validation - MUST be the value of hardcodedVersion
827
827
  if (!sceneData.version || sceneData.version !== hardcodedVersion) {
@@ -845,6 +845,7 @@ var ComponentDataManager = /*#__PURE__*/function (_BaseDisposable) {
845
845
  id: key,
846
846
  name: component.name,
847
847
  type: ((_component$metadata = component.metadata) === null || _component$metadata === void 0 ? void 0 : _component$metadata.type) || 'Component',
848
+ assetType: component.assetType || null,
848
849
  category: component.category,
849
850
  modelKey: component.modelKey,
850
851
  modelType: component.modelType,
@@ -853,10 +854,12 @@ var ComponentDataManager = /*#__PURE__*/function (_BaseDisposable) {
853
854
  // Preserve S3 metadata
854
855
  isS3Component: component.isS3Component,
855
856
  s3Path: component.s3Path,
856
- preLoad: component.preLoad
857
+ preLoad: component.preLoad,
858
+ preCache: component.preCache
857
859
  };
858
860
  return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, baseData), {}, {
859
861
  metadata: component.metadata || {},
862
+ ioConfig: component.ioConfig || null,
860
863
  boundingBox: component.boundingBox || null,
861
864
  adaptedBoundingBox: component.adaptedBoundingBox || null,
862
865
  children: component.children || [],
@@ -97,12 +97,16 @@ var ComponentManager = /*#__PURE__*/function () {
97
97
  componentMesh.position.set(position.x, position.y, position.z);
98
98
 
99
99
  // Set userData for the component including dimensions
100
- componentMesh.userData = _rollupPluginBabelHelpers.objectSpread2({
100
+ componentMesh.userData = _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({
101
101
  libraryId: componentData.libraryId,
102
102
  objectType: 'component',
103
103
  originalUuid: uuid
104
104
  }, ((_gltfScene$userData = gltfScene.userData) === null || _gltfScene$userData === void 0 ? void 0 : _gltfScene$userData.dimensions) && {
105
105
  dimensions: gltfScene.userData.dimensions
106
+ }), libraryComponent.isS3Component && {
107
+ isS3Component: true,
108
+ s3Path: libraryComponent.s3Path,
109
+ componentKey: libraryComponent.componentKey
106
110
  });
107
111
 
108
112
  // Also ensure dimensions are preserved in the userData
@@ -4,6 +4,7 @@ Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var _rollupPluginBabelHelpers = require('../../../_virtual/_rollupPluginBabelHelpers.js');
6
6
  var THREE = require('three');
7
+ var ioDeviceUtils = require('../../utils/ioDeviceUtils.js');
7
8
  var modelPreloader = require('../../rendering/modelPreloader.js');
8
9
 
9
10
  function _interopNamespace(e) {
@@ -109,6 +110,11 @@ var ModelManager = /*#__PURE__*/function () {
109
110
  libraryModel.add(connector);
110
111
  });
111
112
 
113
+ // Attach IO devices for smart components (import flow)
114
+ if (componentData.isSmart && componentData.attachedDevices) {
115
+ ioDeviceUtils.attachIODevicesToComponent(libraryModel, componentData, modelPreloader["default"], originalProps.uuid);
116
+ }
117
+
112
118
  // Replace mesh in scene
113
119
  this._replaceMeshInScene(targetMesh, libraryModel, originalProps.parent, component);
114
120
  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"));
@@ -185,7 +191,7 @@ var ModelManager = /*#__PURE__*/function () {
185
191
  value: (function () {
186
192
  var _getLibraryModel2 = _rollupPluginBabelHelpers.asyncToGenerator(/*#__PURE__*/_rollupPluginBabelHelpers.regenerator().m(function _callee2(modelKey, libraryId) {
187
193
  var _this2 = this;
188
- var gltfScene, preloaderStatus, modelPath, gltf, _t2, _t3;
194
+ var gltfScene, preloaderStatus, modelPath, gltf, _t2, _t3, _t4;
189
195
  return _rollupPluginBabelHelpers.regenerator().w(function (_context2) {
190
196
  while (1) switch (_context2.n) {
191
197
  case 0:
@@ -222,25 +228,47 @@ var ModelManager = /*#__PURE__*/function () {
222
228
  console.warn("\u26A0\uFE0F Preloading failed:", _t2);
223
229
  case 6:
224
230
  _context2.p = 6;
231
+ if (!modelPreloader["default"].urlResolver) {
232
+ _context2.n = 11;
233
+ break;
234
+ }
235
+ _context2.p = 7;
236
+ _context2.n = 8;
237
+ return modelPreloader["default"].urlResolver(modelKey);
238
+ case 8:
239
+ modelPath = _context2.v;
240
+ console.log("\uD83D\uDD17 Resolved URL for ".concat(modelKey));
241
+ _context2.n = 10;
242
+ break;
243
+ case 9:
244
+ _context2.p = 9;
245
+ _t3 = _context2.v;
246
+ console.warn("\u26A0\uFE0F URL resolver failed for ".concat(modelKey, ", falling back to local path:"), _t3);
247
+ modelPath = "".concat(modelPreloader["default"].modelsBasePath).concat(modelKey);
248
+ case 10:
249
+ _context2.n = 12;
250
+ break;
251
+ case 11:
225
252
  modelPath = "".concat(modelPreloader["default"].modelsBasePath).concat(modelKey);
253
+ case 12:
226
254
  console.log("\uD83D\uDCC2 Fallback loading from: ".concat(modelPath));
227
- _context2.n = 7;
255
+ _context2.n = 13;
228
256
  return new Promise(function (resolve, reject) {
229
257
  _this2.sceneViewer.gltfLoader.load(modelPath, resolve, undefined, reject);
230
258
  });
231
- case 7:
259
+ case 13:
232
260
  gltf = _context2.v;
233
261
  if (libraryId) {
234
262
  modelPreloader["default"].cacheModel(modelKey, gltf.scene.clone(), libraryId);
235
263
  }
236
264
  return _context2.a(2, gltf.scene);
237
- case 8:
238
- _context2.p = 8;
239
- _t3 = _context2.v;
240
- console.error("Failed to load model ".concat(modelKey, ":"), _t3);
265
+ case 14:
266
+ _context2.p = 14;
267
+ _t4 = _context2.v;
268
+ console.error("Failed to load model ".concat(modelKey, ":"), _t4);
241
269
  return _context2.a(2, null);
242
270
  }
243
- }, _callee2, null, [[6, 8], [2, 5]]);
271
+ }, _callee2, null, [[7, 9], [6, 14], [2, 5]]);
244
272
  }));
245
273
  function _getLibraryModel(_x4, _x5) {
246
274
  return _getLibraryModel2.apply(this, arguments);
@@ -339,7 +367,7 @@ var ModelManager = /*#__PURE__*/function () {
339
367
  key: "verifyModelPreloaderCache",
340
368
  value: (function () {
341
369
  var _verifyModelPreloaderCache = _rollupPluginBabelHelpers.asyncToGenerator(/*#__PURE__*/_rollupPluginBabelHelpers.regenerator().m(function _callee3() {
342
- var preloaderStatus, _t4;
370
+ var preloaderStatus, _t5;
343
371
  return _rollupPluginBabelHelpers.regenerator().w(function (_context3) {
344
372
  while (1) switch (_context3.n) {
345
373
  case 0:
@@ -362,8 +390,8 @@ var ModelManager = /*#__PURE__*/function () {
362
390
  break;
363
391
  case 3:
364
392
  _context3.p = 3;
365
- _t4 = _context3.v;
366
- console.warn('⚠️ Model preloading failed, some models may load directly:', _t4);
393
+ _t5 = _context3.v;
394
+ console.warn('⚠️ Model preloading failed, some models may load directly:', _t5);
367
395
  case 4:
368
396
  return _context3.a(2, preloaderStatus);
369
397
  }
@@ -382,7 +410,7 @@ var ModelManager = /*#__PURE__*/function () {
382
410
  key: "preloadMissingModels",
383
411
  value: (function () {
384
412
  var _preloadMissingModels = _rollupPluginBabelHelpers.asyncToGenerator(/*#__PURE__*/_rollupPluginBabelHelpers.regenerator().m(function _callee4(data, componentDictionary) {
385
- var _data$scene, _data$scene2, requiredModels, preloaderStatus, cachedModels, missingModels, basePath, modelUrls, _iterator, _step, modelKey, _t5, _t6;
413
+ var _data$scene, _data$scene2, requiredModels, preloaderStatus, cachedModels, missingModels, basePath, modelUrls, _iterator, _step, modelKey, _t6, _t7;
386
414
  return _rollupPluginBabelHelpers.regenerator().w(function (_context4) {
387
415
  while (1) switch (_context4.n) {
388
416
  case 0:
@@ -477,8 +505,8 @@ var ModelManager = /*#__PURE__*/function () {
477
505
  break;
478
506
  case 7:
479
507
  _context4.p = 7;
480
- _t5 = _context4.v;
481
- console.warn("\u274C Failed to preload missing model ".concat(modelKey, ":"), _t5);
508
+ _t6 = _context4.v;
509
+ console.warn("\u274C Failed to preload missing model ".concat(modelKey, ":"), _t6);
482
510
  case 8:
483
511
  _context4.n = 4;
484
512
  break;
@@ -487,8 +515,8 @@ var ModelManager = /*#__PURE__*/function () {
487
515
  break;
488
516
  case 10:
489
517
  _context4.p = 10;
490
- _t6 = _context4.v;
491
- _iterator.e(_t6);
518
+ _t7 = _context4.v;
519
+ _iterator.e(_t7);
492
520
  case 11:
493
521
  _context4.p = 11;
494
522
  _iterator.f();
@@ -593,7 +621,7 @@ var ModelManager = /*#__PURE__*/function () {
593
621
  key: "loadComponentDictionary",
594
622
  value: (function () {
595
623
  var _loadComponentDictionary = _rollupPluginBabelHelpers.asyncToGenerator(/*#__PURE__*/_rollupPluginBabelHelpers.regenerator().m(function _callee6() {
596
- var response, dict, _t7;
624
+ var response, dict, _t8;
597
625
  return _rollupPluginBabelHelpers.regenerator().w(function (_context6) {
598
626
  while (1) switch (_context6.n) {
599
627
  case 0:
@@ -611,8 +639,8 @@ var ModelManager = /*#__PURE__*/function () {
611
639
  return _context6.a(2, dict);
612
640
  case 3:
613
641
  _context6.p = 3;
614
- _t7 = _context6.v;
615
- console.warn('⚠️ Could not load component dictionary:', _t7);
642
+ _t8 = _context6.v;
643
+ console.warn('⚠️ Could not load component dictionary:', _t8);
616
644
  return _context6.a(2, {});
617
645
  }
618
646
  }, _callee6, null, [[0, 3]]);
@@ -212,7 +212,8 @@ var SceneOperationsManager = /*#__PURE__*/function () {
212
212
  return createSceneMaterials;
213
213
  }()
214
214
  /**
215
- * Helper function to create geometries from component library
215
+ * Helper function to create geometries for connectors and gateways
216
+ * Components use GLB geometry directly, no placeholder needed
216
217
  */
217
218
  )
218
219
  }, {
@@ -227,7 +228,7 @@ var SceneOperationsManager = /*#__PURE__*/function () {
227
228
  return geometries;
228
229
  }
229
230
 
230
- // Create geometries based on component library bounding boxes instead of JSON scene.geometries
231
+ // Create geometries only for connectors and gateways (components use GLB geometry)
231
232
  data.scene.children.forEach(function (child) {
232
233
  var _child$userData, _child$userData2;
233
234
  // Skip manual segments - they create their own geometry
@@ -236,42 +237,28 @@ var SceneOperationsManager = /*#__PURE__*/function () {
236
237
  console.log("\u23ED\uFE0F Skipping geometry creation for manual segment: ".concat(child.uuid));
237
238
  return;
238
239
  }
239
- if ((_child$userData2 = child.userData) !== null && _child$userData2 !== void 0 && _child$userData2.libraryId && componentDictionary[child.userData.libraryId]) {
240
- var component = componentDictionary[child.userData.libraryId];
241
- var boundingBox = component.boundingBox;
242
240
 
243
- // Create a box geometry based on the component's bounding box
244
- var geometry = new THREE__namespace.BoxGeometry(boundingBox.x || 1, boundingBox.y || 1, boundingBox.z || 1);
241
+ // Skip components - they will use GLB geometry directly
242
+ if ((_child$userData2 = child.userData) !== null && _child$userData2 !== void 0 && _child$userData2.libraryId) {
243
+ return;
244
+ }
245
245
 
246
- // Use the child's geometry UUID as the key
247
- geometries[child.userData.libraryId] = geometry;
248
- console.log("\uD83D\uDCE6 Created placeholder geometry for ".concat(child.userData.libraryId, ":"), boundingBox);
249
- } else {
250
- // For non-library objects (connectors, gateways), create shared sphere geometry
251
- var isConnectorOrGateway = child.uuid && (child.uuid.toLowerCase().includes('connector') || child.uuid.toLowerCase().includes('gateway'));
252
- console.log("createSceneGeometries child:", child);
253
- if (isConnectorOrGateway) {
254
- // Check if position exists and is not at origin
255
- if (child.position && !(child.position.x == 0 && child.position.y == 0 && child.position.z == 0)) {
256
- // Determine if this is a gateway or connector
257
- var isGateway = child.uuid.toLowerCase().includes('gateway');
258
- var geometryKey = isGateway ? 'GATEWAY_SPHERE' : 'CONNECTOR_GATEWAY_SPHERE';
259
- if (!geometries[geometryKey]) {
260
- // Match computed gateway size (0.15) for gateways, keep 0.1 for connectors
261
- var sphereRadius = isGateway ? 0.15 : 0.1;
262
- geometries[geometryKey] = new THREE__namespace.SphereGeometry(sphereRadius, 16, 16);
263
- console.log("\uD83D\uDD2E Created shared sphere geometry for ".concat(isGateway ? 'gateways' : 'connectors', " (radius: ").concat(sphereRadius, ")"));
264
- }
246
+ // For non-library objects (connectors, gateways), create shared sphere geometry
247
+ var isConnectorOrGateway = child.uuid && (child.uuid.toLowerCase().includes('connector') || child.uuid.toLowerCase().includes('gateway'));
248
+ console.log("createSceneGeometries child:", child);
249
+ if (isConnectorOrGateway) {
250
+ // Check if position exists and is not at origin
251
+ if (child.position && !(child.position.x == 0 && child.position.y == 0 && child.position.z == 0)) {
252
+ // Determine if this is a gateway or connector
253
+ var isGateway = child.uuid.toLowerCase().includes('gateway');
254
+ var geometryKey = isGateway ? 'GATEWAY_SPHERE' : 'CONNECTOR_GATEWAY_SPHERE';
255
+ if (!geometries[geometryKey]) {
256
+ // Match computed gateway size (0.15) for gateways, keep 0.1 for connectors
257
+ var sphereRadius = isGateway ? 0.15 : 0.1;
258
+ geometries[geometryKey] = new THREE__namespace.SphereGeometry(sphereRadius, 16, 16);
259
+ console.log("\uD83D\uDD2E Created shared sphere geometry for ".concat(isGateway ? 'gateways' : 'connectors', " (radius: ").concat(sphereRadius, ")"));
265
260
  }
266
261
  }
267
- // else {
268
- // // Default fallback geometry
269
- // const geometryKey = child.geometry || 'FALLBACK_BOX'
270
- // if (!geometries[geometryKey]) {
271
- // geometries[geometryKey] = new THREE.BoxGeometry(1, 1, 1)
272
- // console.log(`📦 Created fallback box geometry`)
273
- // }
274
- // }
275
262
  }
276
263
  });
277
264
  return geometries;
@@ -1035,7 +1022,8 @@ var SceneOperationsManager = /*#__PURE__*/function () {
1035
1022
  key: "_createBasicSceneObjects",
1036
1023
  value: (function () {
1037
1024
  var _createBasicSceneObjects2 = _rollupPluginBabelHelpers.asyncToGenerator(/*#__PURE__*/_rollupPluginBabelHelpers.regenerator().m(function _callee6(data) {
1038
- var _this4 = this;
1025
+ var _this$sceneViewer$cen,
1026
+ _this4 = this;
1039
1027
  var componentDictionary, _yield$this$createSce, materials, crosscubeTextureSet, geometries, libraryObjectsToReplace;
1040
1028
  return _rollupPluginBabelHelpers.regenerator().w(function (_context6) {
1041
1029
  while (1) switch (_context6.n) {
@@ -1046,20 +1034,33 @@ var SceneOperationsManager = /*#__PURE__*/function () {
1046
1034
  }
1047
1035
  throw new Error('Invalid scene data structure: data.scene.children must be an array');
1048
1036
  case 1:
1037
+ // Use the extended component dictionary (includes S3 components) if available,
1038
+ // otherwise fall back to loading from static file
1039
+ 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;
1040
+ if (componentDictionary) {
1041
+ _context6.n = 3;
1042
+ break;
1043
+ }
1044
+ console.log('⚠️ Extended dictionary not available, loading from static file');
1049
1045
  _context6.n = 2;
1050
1046
  return this.modelManager.loadComponentDictionary();
1051
1047
  case 2:
1052
1048
  componentDictionary = _context6.v;
1049
+ _context6.n = 4;
1050
+ break;
1051
+ case 3:
1052
+ console.log("\u2705 Using extended component dictionary (".concat(Object.keys(componentDictionary).length, " components)"));
1053
+ case 4:
1053
1054
  // Inject connector children from component dictionary into scene data
1054
1055
  this._injectConnectorChildrenFromDictionary(data, componentDictionary);
1055
1056
 
1056
1057
  // Ensure models are preloaded
1057
- _context6.n = 3;
1058
+ _context6.n = 5;
1058
1059
  return this.modelManager.preloadMissingModels(data, componentDictionary);
1059
- case 3:
1060
- _context6.n = 4;
1060
+ case 5:
1061
+ _context6.n = 6;
1061
1062
  return this.createSceneMaterials(data);
1062
- case 4:
1063
+ case 6:
1063
1064
  _yield$this$createSce = _context6.v;
1064
1065
  materials = _yield$this$createSce.materials;
1065
1066
  crosscubeTextureSet = _yield$this$createSce.crosscubeTextureSet;
@@ -0,0 +1,104 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var _rollupPluginBabelHelpers = require('../../_virtual/_rollupPluginBabelHelpers.js');
6
+
7
+ /**
8
+ * IO Device Utilities
9
+ * Shared utility functions for attaching IO devices to smart components.
10
+ * Used by both drag-and-drop (addComponent) and import (loadLibraryModel) flows.
11
+ */
12
+
13
+ /**
14
+ * Attach IO device models to a smart component from cached models.
15
+ * Each device referenced in componentData.attachedDevices is looked up
16
+ * in the model preloader cache, cloned, positioned, and added as a child.
17
+ *
18
+ * @param {THREE.Object3D} componentModel - The parent component model
19
+ * @param {Object} componentData - Component dictionary entry (has attachedDevices)
20
+ * @param {Object} modelPreloader - ModelPreloader instance with cache and componentDictionary
21
+ * @param {string} parentComponentId - The parent component's UUID
22
+ * @returns {void}
23
+ */
24
+ function attachIODevicesToComponent(componentModel, componentData, modelPreloader, parentComponentId) {
25
+ var attachedDevices = componentData.attachedDevices;
26
+ if (!attachedDevices || Object.keys(attachedDevices).length === 0) {
27
+ return;
28
+ }
29
+ console.log("\uD83D\uDD0C attachIODevicesToComponent(): Attaching ".concat(Object.keys(attachedDevices).length, " IO devices to smart component"));
30
+ for (var _i = 0, _Object$entries = Object.entries(attachedDevices); _i < _Object$entries.length; _i++) {
31
+ var _Object$entries$_i = _rollupPluginBabelHelpers.slicedToArray(_Object$entries[_i], 2),
32
+ attachmentId = _Object$entries$_i[0],
33
+ attachment = _Object$entries$_i[1];
34
+ try {
35
+ var _modelPreloader$compo, _deviceData$ioConfig, _deviceData$ioConfig2, _deviceData$ioConfig3, _attachment$attachmen;
36
+ var deviceData = (_modelPreloader$compo = modelPreloader.componentDictionary) === null || _modelPreloader$compo === void 0 ? void 0 : _modelPreloader$compo[attachment.deviceId];
37
+ if (!deviceData || !deviceData.modelKey) {
38
+ console.warn("\u26A0\uFE0F IO device ".concat(attachment.deviceId, " not found in dictionary, skipping"));
39
+ continue;
40
+ }
41
+ var cachedDevice = modelPreloader.getCachedModelWithDimensions(deviceData.modelKey, attachment.deviceId);
42
+ if (!cachedDevice) {
43
+ console.warn("\u26A0\uFE0F IO device model not in cache: ".concat(deviceData.modelKey, ", skipping"));
44
+ continue;
45
+ }
46
+
47
+ // Clone so each component instance owns its own io-device subtree and materials.
48
+ // Without this, all placed copies of the same smart component share the cached
49
+ // object, causing material mutations (from behaviors) to bleed across instances.
50
+ var deviceModel = cachedDevice.clone();
51
+ deviceModel.traverse(function (child) {
52
+ if (child.isMesh && child.material) {
53
+ child.material = Array.isArray(child.material) ? child.material.map(function (m) {
54
+ return m.clone();
55
+ }) : child.material.clone();
56
+ }
57
+ });
58
+
59
+ // Name the device model
60
+ deviceModel.name = "".concat(attachment.attachmentLabel || 'IO Device', " (").concat(attachmentId, ")");
61
+
62
+ // Set user data for identification — include ioConfig data points so the
63
+ // component tooltip can render state displays without an extra lookup.
64
+ deviceModel.userData = {
65
+ objectType: 'io-device',
66
+ deviceId: attachment.deviceId,
67
+ attachmentId: attachmentId,
68
+ attachmentLabel: attachment.attachmentLabel,
69
+ parentComponentId: parentComponentId,
70
+ deviceName: deviceData.name || '',
71
+ // Snapshot of the device's data point definitions (stateType, stateConfig, direction, etc.)
72
+ // ioConfig can use either 'states' (preferred) or legacy 'dataPoints' as the array key
73
+ 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) || [],
74
+ // Device-level I/O direction: 'input' means the user can write state via the tooltip
75
+ ioDirection: ((_deviceData$ioConfig3 = deviceData.ioConfig) === null || _deviceData$ioConfig3 === void 0 ? void 0 : _deviceData$ioConfig3.direction) || 'output',
76
+ // Signal wiring sourced from this attachment (for state propagation reference)
77
+ signalOutputs: attachment.signalOutputs || []
78
+ };
79
+
80
+ // Position at the attachment point
81
+ if ((_attachment$attachmen = attachment.attachmentPoint) !== null && _attachment$attachmen !== void 0 && _attachment$attachmen.position) {
82
+ var pos = attachment.attachmentPoint.position;
83
+ deviceModel.position.set(pos.x || 0, pos.y || 0, pos.z || 0);
84
+ }
85
+
86
+ // IO device models are authored at the same real-world unit scale
87
+ // as the host component, so keep them at their natural (1:1) size.
88
+ // Note: attachmentPoint.scale is the connector marker sphere size,
89
+ // NOT a desired device model scale.
90
+ deviceModel.scale.setScalar(1);
91
+
92
+ // Add as child of the component
93
+ componentModel.add(deviceModel);
94
+ console.log("\u2705 Attached IO device: ".concat(attachment.attachmentLabel || attachment.deviceId, " at"), {
95
+ position: deviceModel.position,
96
+ scale: deviceModel.scale
97
+ });
98
+ } catch (err) {
99
+ console.error("\u274C Error attaching IO device ".concat(attachment.deviceId, ":"), err);
100
+ }
101
+ }
102
+ }
103
+
104
+ exports.attachIODevicesToComponent = attachIODevicesToComponent;