@2112-lab/central-plant 0.3.38 → 0.3.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/dist/bundle/index.js +1078 -562
  2. package/dist/cjs/src/core/centralPlant.js +115 -68
  3. package/dist/cjs/src/core/centralPlantInternals.js +20 -36
  4. package/dist/cjs/src/index.js +19 -0
  5. package/dist/cjs/src/managers/behaviors/IoBehaviorManager.js +175 -234
  6. package/dist/cjs/src/managers/components/componentDataManager.js +63 -11
  7. package/dist/cjs/src/managers/scene/componentTooltipManager.js +95 -65
  8. package/dist/cjs/src/managers/scene/modelManager.js +93 -145
  9. package/dist/cjs/src/managers/scene/sceneOperationsManager.js +8 -3
  10. package/dist/cjs/src/utils/animationTransformUtils.js +66 -0
  11. package/dist/cjs/src/utils/behaviorDispatch.js +62 -0
  12. package/dist/cjs/src/utils/behaviorRegistration.js +76 -0
  13. package/dist/cjs/src/utils/behaviorSceneUtils.js +155 -0
  14. package/dist/cjs/src/utils/behaviorSchema.js +209 -0
  15. package/dist/esm/src/core/centralPlant.js +115 -68
  16. package/dist/esm/src/core/centralPlantInternals.js +21 -37
  17. package/dist/esm/src/index.js +4 -0
  18. package/dist/esm/src/managers/behaviors/IoBehaviorManager.js +176 -235
  19. package/dist/esm/src/managers/components/componentDataManager.js +63 -11
  20. package/dist/esm/src/managers/scene/componentTooltipManager.js +95 -65
  21. package/dist/esm/src/managers/scene/modelManager.js +94 -146
  22. package/dist/esm/src/managers/scene/sceneOperationsManager.js +8 -3
  23. package/dist/esm/src/utils/animationTransformUtils.js +41 -0
  24. package/dist/esm/src/utils/behaviorDispatch.js +56 -0
  25. package/dist/esm/src/utils/behaviorRegistration.js +71 -0
  26. package/dist/esm/src/utils/behaviorSceneUtils.js +147 -0
  27. package/dist/esm/src/utils/behaviorSchema.js +201 -0
  28. package/dist/index.d.ts +169 -1
  29. package/package.json +1 -1
@@ -1164,6 +1164,74 @@ var DisposalUtilities = /*#__PURE__*/function () {
1164
1164
  }]);
1165
1165
  }();
1166
1166
 
1167
+ /**
1168
+ * Register mesh animations and intra-component state links when a smart
1169
+ * component is placed or imported into the scene.
1170
+ *
1171
+ * @param {import('../managers/behaviors/IoBehaviorManager.js').IoBehaviorManager} ioBehavMgr
1172
+ * @param {string} componentUuid - Instance UUID of the parent component
1173
+ * @param {Object} componentData - Smart component dictionary entry
1174
+ * @param {Object} componentModel - Root THREE.Object3D of the placed component
1175
+ * @param {Object} componentDictionary - modelPreloader.componentDictionary
1176
+ */
1177
+ function registerBehaviorsForComponent(ioBehavMgr, componentUuid, componentData, componentModel, componentDictionary) {
1178
+ var _componentData$behavi;
1179
+ if (!ioBehavMgr || !componentData || !componentModel) {
1180
+ return;
1181
+ }
1182
+ if (componentData.isSmart && componentData.attachedDevices) {
1183
+ var _loop = function _loop() {
1184
+ var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2),
1185
+ attachmentId = _Object$entries$_i[0],
1186
+ attachment = _Object$entries$_i[1];
1187
+ var deviceData = componentDictionary === null || componentDictionary === void 0 ? void 0 : componentDictionary[attachment.deviceId];
1188
+ if (!(deviceData !== null && deviceData !== void 0 && deviceData.behaviorConfig)) return 1; // continue
1189
+ var deviceRoot = null;
1190
+ componentModel.traverse(function (obj) {
1191
+ var _obj$userData;
1192
+ if (!deviceRoot && ((_obj$userData = obj.userData) === null || _obj$userData === void 0 ? void 0 : _obj$userData.attachmentId) === attachmentId) deviceRoot = obj;
1193
+ });
1194
+ if (deviceRoot) {
1195
+ ioBehavMgr.loadBehaviors(attachmentId, deviceData.behaviorConfig, deviceRoot, componentUuid);
1196
+ }
1197
+ };
1198
+ for (var _i = 0, _Object$entries = Object.entries(componentData.attachedDevices); _i < _Object$entries.length; _i++) {
1199
+ if (_loop()) continue;
1200
+ }
1201
+ }
1202
+ if (((_componentData$behavi = componentData.behaviors) === null || _componentData$behavi === void 0 ? void 0 : _componentData$behavi.length) > 0) {
1203
+ ioBehavMgr.registerComponentBehaviors(componentUuid, componentData.behaviors);
1204
+ } else if (componentData.isSmart) {
1205
+ ioBehavMgr.registerComponentBehaviors(componentUuid, []);
1206
+ }
1207
+ }
1208
+
1209
+ /**
1210
+ * Reload mesh behaviorConfig for every placed instance of an I/O device asset.
1211
+ * Call after the animation wizard saves updated behaviorConfig to the dictionary.
1212
+ *
1213
+ * @param {import('../managers/behaviors/IoBehaviorManager.js').IoBehaviorManager} ioBehavMgr
1214
+ * @param {string} deviceAssetId - Dictionary / asset id of the I/O device
1215
+ * @param {Object} componentDictionary
1216
+ * @param {Object} centralPlant
1217
+ */
1218
+ function reloadBehaviorsForDeviceAsset(ioBehavMgr, deviceAssetId, componentDictionary, centralPlant) {
1219
+ var _centralPlant$sceneVi;
1220
+ var scene = centralPlant === null || centralPlant === void 0 || (_centralPlant$sceneVi = centralPlant.sceneViewer) === null || _centralPlant$sceneVi === void 0 ? void 0 : _centralPlant$sceneVi.scene;
1221
+ var deviceData = componentDictionary === null || componentDictionary === void 0 ? void 0 : componentDictionary[deviceAssetId];
1222
+ if (!ioBehavMgr || !scene || !(deviceData !== null && deviceData !== void 0 && deviceData.behaviorConfig)) return;
1223
+ scene.traverse(function (obj) {
1224
+ var _obj$userData2, _obj$userData3, _obj$parent;
1225
+ if (((_obj$userData2 = obj.userData) === null || _obj$userData2 === void 0 ? void 0 : _obj$userData2.objectType) !== 'io-device') return;
1226
+ if (((_obj$userData3 = obj.userData) === null || _obj$userData3 === void 0 ? void 0 : _obj$userData3.deviceId) !== deviceAssetId) return;
1227
+ var parentUuid = obj.userData.parentComponentId || ((_obj$parent = obj.parent) === null || _obj$parent === void 0 ? void 0 : _obj$parent.uuid);
1228
+ var attachmentId = obj.userData.attachmentId;
1229
+ if (!parentUuid || !attachmentId) return;
1230
+ ioBehavMgr.unloadForAttachment(parentUuid, attachmentId);
1231
+ ioBehavMgr.loadBehaviors(attachmentId, deviceData.behaviorConfig, obj, parentUuid);
1232
+ });
1233
+ }
1234
+
1167
1235
  /**
1168
1236
  * Validation severity levels
1169
1237
  * @enum {string}
@@ -20325,6 +20393,14 @@ var ComponentDataManager = /*#__PURE__*/function (_BaseDisposable) {
20325
20393
  if (!_this3.componentDictionary[key]) {
20326
20394
  newComponents[key] = component;
20327
20395
  } else {
20396
+ var _component$behaviors;
20397
+ var existing = _this3.componentDictionary[key];
20398
+ if (component.behaviorConfig) existing.behaviorConfig = component.behaviorConfig;
20399
+ if (component.meshNameMap) existing.meshNameMap = component.meshNameMap;
20400
+ if ((_component$behaviors = component.behaviors) !== null && _component$behaviors !== void 0 && _component$behaviors.length) existing.behaviors = component.behaviors;
20401
+ if (component.attachedDevices) {
20402
+ existing.attachedDevices = _objectSpread2(_objectSpread2({}, existing.attachedDevices || {}), component.attachedDevices);
20403
+ }
20328
20404
  console.log("\u26A0\uFE0F Skipping duplicate component: ".concat(key, " (").concat(component.name || 'unnamed', ")"));
20329
20405
  }
20330
20406
  });
@@ -20356,17 +20432,61 @@ var ComponentDataManager = /*#__PURE__*/function (_BaseDisposable) {
20356
20432
  }
20357
20433
  return extendComponentDictionary;
20358
20434
  }()
20435
+ /**
20436
+ * Merge mesh animation configs from host persistence into the live dictionary.
20437
+ * @param {Object<string, { behaviorConfig?: Array, meshNameMap?: Object, behaviors?: Array }>} configsByAssetId
20438
+ * @returns {number} Count of entries updated
20439
+ */
20440
+ )
20441
+ }, {
20442
+ key: "mergeBehaviorConfigsIntoDictionary",
20443
+ value: function mergeBehaviorConfigsIntoDictionary(configsByAssetId) {
20444
+ var _this$sceneViewer2;
20445
+ if (!configsByAssetId || !this.componentDictionary) return 0;
20446
+ var merged = 0;
20447
+ for (var _i2 = 0, _Object$entries2 = Object.entries(configsByAssetId); _i2 < _Object$entries2.length; _i2++) {
20448
+ var _Object$entries2$_i = _slicedToArray(_Object$entries2[_i2], 2),
20449
+ assetId = _Object$entries2$_i[0],
20450
+ attrs = _Object$entries2$_i[1];
20451
+ var entry = this.componentDictionary[assetId];
20452
+ if (!entry || !attrs) continue;
20453
+ var updated = false;
20454
+ if (attrs.behaviorConfig) {
20455
+ entry.behaviorConfig = attrs.behaviorConfig;
20456
+ updated = true;
20457
+ }
20458
+ if (attrs.meshNameMap) {
20459
+ entry.meshNameMap = attrs.meshNameMap;
20460
+ updated = true;
20461
+ }
20462
+ if ('behaviors' in attrs) {
20463
+ var _attrs$behaviors;
20464
+ if ((_attrs$behaviors = attrs.behaviors) !== null && _attrs$behaviors !== void 0 && _attrs$behaviors.length) {
20465
+ entry.behaviors = attrs.behaviors;
20466
+ } else {
20467
+ delete entry.behaviors;
20468
+ }
20469
+ updated = true;
20470
+ }
20471
+ if (updated) merged++;
20472
+ }
20473
+ var modelPreloader = (_this$sceneViewer2 = this.sceneViewer) === null || _this$sceneViewer2 === void 0 || (_this$sceneViewer2 = _this$sceneViewer2.centralPlant) === null || _this$sceneViewer2 === void 0 || (_this$sceneViewer2 = _this$sceneViewer2.utilities) === null || _this$sceneViewer2 === void 0 ? void 0 : _this$sceneViewer2.modelPreloader;
20474
+ if (modelPreloader) {
20475
+ modelPreloader.componentDictionary = this.componentDictionary;
20476
+ }
20477
+ return merged;
20478
+ }
20479
+
20359
20480
  /**
20360
20481
  * Remove S3 components from the component dictionary
20361
20482
  * @returns {Promise<boolean>} True if components were removed successfully
20362
20483
  */
20363
- )
20364
20484
  }, {
20365
20485
  key: "removeS3Components",
20366
20486
  value: (function () {
20367
20487
  var _removeS3Components = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee4() {
20368
20488
  var _this4 = this;
20369
- var _this$sceneViewer2, keysToRemove, _i2, _Object$entries2, _Object$entries2$_i, key, component, modelPreloader, _t2;
20489
+ var _this$sceneViewer3, keysToRemove, _i3, _Object$entries3, _Object$entries3$_i, key, component, modelPreloader, _t2;
20370
20490
  return _regenerator().w(function (_context4) {
20371
20491
  while (1) switch (_context4.n) {
20372
20492
  case 0:
@@ -20383,8 +20503,8 @@ var ComponentDataManager = /*#__PURE__*/function (_BaseDisposable) {
20383
20503
  _context4.p = 2;
20384
20504
  // Find and remove all S3 components
20385
20505
  keysToRemove = [];
20386
- for (_i2 = 0, _Object$entries2 = Object.entries(this.componentDictionary); _i2 < _Object$entries2.length; _i2++) {
20387
- _Object$entries2$_i = _slicedToArray(_Object$entries2[_i2], 2), key = _Object$entries2$_i[0], component = _Object$entries2$_i[1];
20506
+ for (_i3 = 0, _Object$entries3 = Object.entries(this.componentDictionary); _i3 < _Object$entries3.length; _i3++) {
20507
+ _Object$entries3$_i = _slicedToArray(_Object$entries3[_i3], 2), key = _Object$entries3$_i[0], component = _Object$entries3$_i[1];
20388
20508
  if (component.isS3Component) {
20389
20509
  keysToRemove.push(key);
20390
20510
  }
@@ -20396,7 +20516,7 @@ var ComponentDataManager = /*#__PURE__*/function (_BaseDisposable) {
20396
20516
  });
20397
20517
 
20398
20518
  // Update ModelPreloader's dictionary reference
20399
- modelPreloader = (_this$sceneViewer2 = this.sceneViewer) === null || _this$sceneViewer2 === void 0 || (_this$sceneViewer2 = _this$sceneViewer2.centralPlant) === null || _this$sceneViewer2 === void 0 || (_this$sceneViewer2 = _this$sceneViewer2.utilities) === null || _this$sceneViewer2 === void 0 ? void 0 : _this$sceneViewer2.modelPreloader;
20519
+ modelPreloader = (_this$sceneViewer3 = this.sceneViewer) === null || _this$sceneViewer3 === void 0 || (_this$sceneViewer3 = _this$sceneViewer3.centralPlant) === null || _this$sceneViewer3 === void 0 || (_this$sceneViewer3 = _this$sceneViewer3.utilities) === null || _this$sceneViewer3 === void 0 ? void 0 : _this$sceneViewer3.modelPreloader;
20400
20520
  if (modelPreloader) {
20401
20521
  modelPreloader.componentDictionary = this.componentDictionary;
20402
20522
  console.log("\uD83D\uDD04 Updated ModelPreloader's dictionary reference (".concat(Object.keys(this.componentDictionary).length, " total components)"));
@@ -20429,7 +20549,7 @@ var ComponentDataManager = /*#__PURE__*/function (_BaseDisposable) {
20429
20549
  key: "removeComponentFromDictionary",
20430
20550
  value: (function () {
20431
20551
  var _removeComponentFromDictionary = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee5(componentKey) {
20432
- var _this$sceneViewer3, modelPreloader, _t3;
20552
+ var _this$sceneViewer4, modelPreloader, _t3;
20433
20553
  return _regenerator().w(function (_context5) {
20434
20554
  while (1) switch (_context5.n) {
20435
20555
  case 0:
@@ -20462,7 +20582,7 @@ var ComponentDataManager = /*#__PURE__*/function (_BaseDisposable) {
20462
20582
  delete this.componentDictionary[componentKey];
20463
20583
 
20464
20584
  // Update ModelPreloader's dictionary reference
20465
- modelPreloader = (_this$sceneViewer3 = this.sceneViewer) === null || _this$sceneViewer3 === void 0 || (_this$sceneViewer3 = _this$sceneViewer3.centralPlant) === null || _this$sceneViewer3 === void 0 || (_this$sceneViewer3 = _this$sceneViewer3.utilities) === null || _this$sceneViewer3 === void 0 ? void 0 : _this$sceneViewer3.modelPreloader;
20585
+ modelPreloader = (_this$sceneViewer4 = this.sceneViewer) === null || _this$sceneViewer4 === void 0 || (_this$sceneViewer4 = _this$sceneViewer4.centralPlant) === null || _this$sceneViewer4 === void 0 || (_this$sceneViewer4 = _this$sceneViewer4.utilities) === null || _this$sceneViewer4 === void 0 ? void 0 : _this$sceneViewer4.modelPreloader;
20466
20586
  if (modelPreloader) {
20467
20587
  modelPreloader.componentDictionary = this.componentDictionary;
20468
20588
  console.log("\uD83D\uDD04 Updated ModelPreloader's dictionary reference (".concat(Object.keys(this.componentDictionary).length, " total components)"));
@@ -20562,10 +20682,10 @@ var ComponentDataManager = /*#__PURE__*/function (_BaseDisposable) {
20562
20682
  if (!this.componentDictionary) return 'unknown';
20563
20683
 
20564
20684
  // Look for component in dictionary
20565
- for (var _i3 = 0, _Object$entries3 = Object.entries(this.componentDictionary); _i3 < _Object$entries3.length; _i3++) {
20566
- var _Object$entries3$_i = _slicedToArray(_Object$entries3[_i3], 2),
20567
- key = _Object$entries3$_i[0],
20568
- component = _Object$entries3$_i[1];
20685
+ for (var _i4 = 0, _Object$entries4 = Object.entries(this.componentDictionary); _i4 < _Object$entries4.length; _i4++) {
20686
+ var _Object$entries4$_i = _slicedToArray(_Object$entries4[_i4], 2),
20687
+ key = _Object$entries4$_i[0],
20688
+ component = _Object$entries4$_i[1];
20569
20689
  if (key === 'categories') continue;
20570
20690
  if (name && name.toLowerCase().includes(key.toLowerCase())) {
20571
20691
  return component.category || 'unknown';
@@ -31318,12 +31438,12 @@ var ModelManager = /*#__PURE__*/function () {
31318
31438
  key: "loadLibraryModel",
31319
31439
  value: function () {
31320
31440
  var _loadLibraryModel = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(targetMesh, jsonEntry, componentData) {
31321
- var component, _jsonEntry$userData, _jsonEntry$userData2, _jsonEntry$userData3, originalProps, connectorChildren, gltfScene, libraryModel, _this$sceneViewer, ioBehavMgr, _loop, _i, _Object$entries, warmFn, _jsonEntry$userData4, _t;
31322
- return _regenerator().w(function (_context2) {
31323
- while (1) switch (_context2.n) {
31441
+ var component, _jsonEntry$userData, _jsonEntry$userData2, _jsonEntry$userData3, originalProps, connectorChildren, gltfScene, libraryModel, _this$sceneViewer, ioBehavMgr, warmFn, _jsonEntry$userData4, _t;
31442
+ return _regenerator().w(function (_context) {
31443
+ while (1) switch (_context.n) {
31324
31444
  case 0:
31325
31445
  component = this.sceneViewer;
31326
- _context2.p = 1;
31446
+ _context.p = 1;
31327
31447
  console.log("Loading library GLB model for ".concat((_jsonEntry$userData = jsonEntry.userData) === null || _jsonEntry$userData === void 0 ? void 0 : _jsonEntry$userData.libraryId, "..."));
31328
31448
 
31329
31449
  // Store original mesh properties before async operation
@@ -31336,16 +31456,16 @@ var ModelManager = /*#__PURE__*/function () {
31336
31456
  uuid: targetMesh.uuid
31337
31457
  }; // Preserve connector children (pass parent UUID for proper connector UUID generation)
31338
31458
  connectorChildren = this._preserveConnectorChildren(targetMesh, originalProps.uuid); // Get model from cache or load directly
31339
- _context2.n = 2;
31459
+ _context.n = 2;
31340
31460
  return this._getLibraryModel(componentData.modelKey, (_jsonEntry$userData2 = jsonEntry.userData) === null || _jsonEntry$userData2 === void 0 ? void 0 : _jsonEntry$userData2.libraryId);
31341
31461
  case 2:
31342
- gltfScene = _context2.v;
31462
+ gltfScene = _context.v;
31343
31463
  if (gltfScene) {
31344
- _context2.n = 3;
31464
+ _context.n = 3;
31345
31465
  break;
31346
31466
  }
31347
31467
  console.warn("\u26A0\uFE0F Could not load model ".concat(componentData.modelKey, ", keeping original mesh"));
31348
- return _context2.a(2, targetMesh);
31468
+ return _context.a(2, targetMesh);
31349
31469
  case 3:
31350
31470
  // Configure the loaded model
31351
31471
  libraryModel = this._configureLibraryModel(gltfScene, jsonEntry, componentData, originalProps); // Add preserved connectors
@@ -31355,70 +31475,17 @@ var ModelManager = /*#__PURE__*/function () {
31355
31475
 
31356
31476
  // Attach IO devices for smart components (import flow)
31357
31477
  if (!(componentData.isSmart && componentData.attachedDevices)) {
31358
- _context2.n = 9;
31478
+ _context.n = 5;
31359
31479
  break;
31360
31480
  }
31361
- _context2.n = 4;
31481
+ _context.n = 4;
31362
31482
  return attachIODevicesToComponent(libraryModel, componentData, modelPreloader, originalProps.uuid);
31363
31483
  case 4:
31364
- // Register behavior configs for each attached device
31365
31484
  ioBehavMgr = (_this$sceneViewer = this.sceneViewer) === null || _this$sceneViewer === void 0 || (_this$sceneViewer = _this$sceneViewer.managers) === null || _this$sceneViewer === void 0 ? void 0 : _this$sceneViewer.ioBehaviorManager;
31366
- if (!ioBehavMgr) {
31367
- _context2.n = 9;
31368
- break;
31485
+ if (ioBehavMgr) {
31486
+ registerBehaviorsForComponent(ioBehavMgr, originalProps.uuid, componentData, libraryModel, modelPreloader.componentDictionary);
31369
31487
  }
31370
- _loop = /*#__PURE__*/_regenerator().m(function _loop() {
31371
- var _modelPreloader$compo, _deviceData$behaviorC;
31372
- var _Object$entries$_i, attachmentId, attachment, deviceData, deviceRoot;
31373
- return _regenerator().w(function (_context) {
31374
- while (1) switch (_context.n) {
31375
- case 0:
31376
- _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2), attachmentId = _Object$entries$_i[0], attachment = _Object$entries$_i[1];
31377
- deviceData = (_modelPreloader$compo = modelPreloader.componentDictionary) === null || _modelPreloader$compo === void 0 ? void 0 : _modelPreloader$compo[attachment.deviceId];
31378
- console.log("[ModelManager] attachment \"".concat(attachmentId, "\" \u2192 deviceId \"").concat(attachment.deviceId, "\" \u2192 behaviorConfig:"), (_deviceData$behaviorC = deviceData === null || deviceData === void 0 ? void 0 : deviceData.behaviorConfig) !== null && _deviceData$behaviorC !== void 0 ? _deviceData$behaviorC : 'NONE');
31379
- if (deviceData !== null && deviceData !== void 0 && deviceData.behaviorConfig) {
31380
- _context.n = 1;
31381
- break;
31382
- }
31383
- return _context.a(2, 1);
31384
- case 1:
31385
- deviceRoot = null;
31386
- libraryModel.traverse(function (obj) {
31387
- var _obj$userData;
31388
- if (!deviceRoot && ((_obj$userData = obj.userData) === null || _obj$userData === void 0 ? void 0 : _obj$userData.attachmentId) === attachmentId) deviceRoot = obj;
31389
- });
31390
- if (deviceRoot) {
31391
- ioBehavMgr.loadBehaviors(attachmentId, deviceData.behaviorConfig, deviceRoot, originalProps.uuid);
31392
- }
31393
- case 2:
31394
- return _context.a(2);
31395
- }
31396
- }, _loop);
31397
- });
31398
- _i = 0, _Object$entries = Object.entries(componentData.attachedDevices);
31399
31488
  case 5:
31400
- if (!(_i < _Object$entries.length)) {
31401
- _context2.n = 8;
31402
- break;
31403
- }
31404
- return _context2.d(_regeneratorValues(_loop()), 6);
31405
- case 6:
31406
- if (!_context2.v) {
31407
- _context2.n = 7;
31408
- break;
31409
- }
31410
- return _context2.a(3, 7);
31411
- case 7:
31412
- _i++;
31413
- _context2.n = 5;
31414
- break;
31415
- case 8:
31416
- // Register component-level behaviors (intra-component io-device linking)
31417
- if (componentData.behaviors && componentData.behaviors.length > 0) {
31418
- console.log("[ModelManager] Registering ".concat(componentData.behaviors.length, " component-level behavior(s) for ").concat(originalProps.uuid));
31419
- ioBehavMgr.registerComponentBehaviors(originalProps.uuid, componentData.behaviors);
31420
- }
31421
- case 9:
31422
31489
  // Replace mesh in scene
31423
31490
  this._replaceMeshInScene(targetMesh, libraryModel, originalProps.parent, component);
31424
31491
 
@@ -31436,14 +31503,14 @@ var ModelManager = /*#__PURE__*/function () {
31436
31503
  }
31437
31504
  }
31438
31505
  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"));
31439
- return _context2.a(2, libraryModel);
31440
- case 10:
31441
- _context2.p = 10;
31442
- _t = _context2.v;
31506
+ return _context.a(2, libraryModel);
31507
+ case 6:
31508
+ _context.p = 6;
31509
+ _t = _context.v;
31443
31510
  console.error("\u274C Error loading ".concat((_jsonEntry$userData4 = jsonEntry.userData) === null || _jsonEntry$userData4 === void 0 ? void 0 : _jsonEntry$userData4.libraryId, " GLB model:"), _t);
31444
- return _context2.a(2, targetMesh);
31511
+ return _context.a(2, targetMesh);
31445
31512
  }
31446
- }, _callee, this, [[1, 10]]);
31513
+ }, _callee, this, [[1, 6]]);
31447
31514
  }));
31448
31515
  function loadLibraryModel(_x, _x2, _x3) {
31449
31516
  return _loadLibraryModel.apply(this, arguments);
@@ -31522,84 +31589,84 @@ var ModelManager = /*#__PURE__*/function () {
31522
31589
  var _getLibraryModel2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2(modelKey, libraryId) {
31523
31590
  var _this2 = this;
31524
31591
  var gltfScene, preloaderStatus, modelPath, gltf, _t2, _t3, _t4;
31525
- return _regenerator().w(function (_context3) {
31526
- while (1) switch (_context3.n) {
31592
+ return _regenerator().w(function (_context2) {
31593
+ while (1) switch (_context2.n) {
31527
31594
  case 0:
31528
31595
  // Try cache first
31529
31596
  gltfScene = modelPreloader.getCachedModelWithDimensions(modelKey, libraryId);
31530
31597
  if (!gltfScene) {
31531
- _context3.n = 1;
31598
+ _context2.n = 1;
31532
31599
  break;
31533
31600
  }
31534
31601
  console.log("\uD83C\uDFAF GLB cache HIT: ".concat(modelKey));
31535
- return _context3.a(2, gltfScene);
31602
+ return _context2.a(2, gltfScene);
31536
31603
  case 1:
31537
31604
  // Check if preloading is in progress
31538
31605
  preloaderStatus = modelPreloader.getStatus();
31539
31606
  if (!preloaderStatus.isPreloading) {
31540
- _context3.n = 6;
31607
+ _context2.n = 6;
31541
31608
  break;
31542
31609
  }
31543
31610
  console.log("\u23F3 Waiting for preloader: ".concat(modelKey));
31544
- _context3.p = 2;
31545
- _context3.n = 3;
31611
+ _context2.p = 2;
31612
+ _context2.n = 3;
31546
31613
  return modelPreloader.preloadingPromise;
31547
31614
  case 3:
31548
31615
  gltfScene = modelPreloader.getCachedModelWithDimensions(modelKey, libraryId);
31549
31616
  if (!gltfScene) {
31550
- _context3.n = 4;
31617
+ _context2.n = 4;
31551
31618
  break;
31552
31619
  }
31553
31620
  console.log("\uD83C\uDFAF GLB cache HIT (after preload): ".concat(modelKey));
31554
- return _context3.a(2, gltfScene);
31621
+ return _context2.a(2, gltfScene);
31555
31622
  case 4:
31556
- _context3.n = 6;
31623
+ _context2.n = 6;
31557
31624
  break;
31558
31625
  case 5:
31559
- _context3.p = 5;
31560
- _t2 = _context3.v;
31626
+ _context2.p = 5;
31627
+ _t2 = _context2.v;
31561
31628
  console.warn("\u26A0\uFE0F Preloading failed:", _t2);
31562
31629
  case 6:
31563
- _context3.p = 6;
31630
+ _context2.p = 6;
31564
31631
  if (!modelPreloader.urlResolver) {
31565
- _context3.n = 11;
31632
+ _context2.n = 11;
31566
31633
  break;
31567
31634
  }
31568
- _context3.p = 7;
31569
- _context3.n = 8;
31635
+ _context2.p = 7;
31636
+ _context2.n = 8;
31570
31637
  return modelPreloader.urlResolver(modelKey);
31571
31638
  case 8:
31572
- modelPath = _context3.v;
31639
+ modelPath = _context2.v;
31573
31640
  console.log("\uD83D\uDD17 Resolved URL for ".concat(modelKey));
31574
- _context3.n = 10;
31641
+ _context2.n = 10;
31575
31642
  break;
31576
31643
  case 9:
31577
- _context3.p = 9;
31578
- _t3 = _context3.v;
31644
+ _context2.p = 9;
31645
+ _t3 = _context2.v;
31579
31646
  console.warn("\u26A0\uFE0F URL resolver failed for ".concat(modelKey, ", falling back to local path:"), _t3);
31580
31647
  modelPath = "".concat(modelPreloader.modelsBasePath).concat(modelKey);
31581
31648
  case 10:
31582
- _context3.n = 12;
31649
+ _context2.n = 12;
31583
31650
  break;
31584
31651
  case 11:
31585
31652
  modelPath = "".concat(modelPreloader.modelsBasePath).concat(modelKey);
31586
31653
  case 12:
31587
31654
  console.log("\uD83D\uDCC2 Fallback loading from: ".concat(modelPath));
31588
- _context3.n = 13;
31655
+ _context2.n = 13;
31589
31656
  return new Promise(function (resolve, reject) {
31590
31657
  _this2.sceneViewer.gltfLoader.load(modelPath, resolve, undefined, reject);
31591
31658
  });
31592
31659
  case 13:
31593
- gltf = _context3.v;
31660
+ gltf = _context2.v;
31594
31661
  if (libraryId) {
31595
31662
  modelPreloader.cacheModel(modelKey, gltf.scene.clone(), libraryId);
31596
31663
  }
31597
- return _context3.a(2, gltf.scene);
31664
+ return _context2.a(2, gltf.scene);
31598
31665
  case 14:
31599
- _context3.p = 14;
31600
- _t4 = _context3.v;
31666
+ _context2.p = 14;
31667
+ _t4 = _context2.v;
31601
31668
  console.error("Failed to load model ".concat(modelKey, ":"), _t4);
31602
- return _context3.a(2, null);
31669
+ return _context2.a(2, null);
31603
31670
  }
31604
31671
  }, _callee2, null, [[7, 9], [6, 14], [2, 5]]);
31605
31672
  }));
@@ -31706,8 +31773,8 @@ var ModelManager = /*#__PURE__*/function () {
31706
31773
  value: (function () {
31707
31774
  var _verifyModelPreloaderCache = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee3() {
31708
31775
  var preloaderStatus, _t5;
31709
- return _regenerator().w(function (_context4) {
31710
- while (1) switch (_context4.n) {
31776
+ return _regenerator().w(function (_context3) {
31777
+ while (1) switch (_context3.n) {
31711
31778
  case 0:
31712
31779
  console.log('🔍 Verifying model preloader cache state...');
31713
31780
  preloaderStatus = modelPreloader.getStatus();
@@ -31715,23 +31782,23 @@ var ModelManager = /*#__PURE__*/function () {
31715
31782
 
31716
31783
  // If preloading is still in progress, wait for completion
31717
31784
  if (!preloaderStatus.isPreloading) {
31718
- _context4.n = 4;
31785
+ _context3.n = 4;
31719
31786
  break;
31720
31787
  }
31721
31788
  console.log('⏳ Waiting for model preloading to complete...');
31722
- _context4.p = 1;
31723
- _context4.n = 2;
31789
+ _context3.p = 1;
31790
+ _context3.n = 2;
31724
31791
  return modelPreloader.preloadingPromise;
31725
31792
  case 2:
31726
31793
  console.log('✅ Model preloading completed');
31727
- _context4.n = 4;
31794
+ _context3.n = 4;
31728
31795
  break;
31729
31796
  case 3:
31730
- _context4.p = 3;
31731
- _t5 = _context4.v;
31797
+ _context3.p = 3;
31798
+ _t5 = _context3.v;
31732
31799
  console.warn('⚠️ Model preloading failed, some models may load directly:', _t5);
31733
31800
  case 4:
31734
- return _context4.a(2, preloaderStatus);
31801
+ return _context3.a(2, preloaderStatus);
31735
31802
  }
31736
31803
  }, _callee3, null, [[1, 3]]);
31737
31804
  }));
@@ -31749,11 +31816,11 @@ var ModelManager = /*#__PURE__*/function () {
31749
31816
  value: (function () {
31750
31817
  var _preloadMissingModels = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee4(data, componentDictionary) {
31751
31818
  var _data$scene, _data$scene2, requiredModels, preloaderStatus, cachedModels, missingModels, basePath, modelUrls, _iterator, _step, modelKey, _t6, _t7;
31752
- return _regenerator().w(function (_context5) {
31753
- while (1) switch (_context5.n) {
31819
+ return _regenerator().w(function (_context4) {
31820
+ while (1) switch (_context4.n) {
31754
31821
  case 0:
31755
31822
  if (!(!data || !data.scene || !Array.isArray(data.scene.children))) {
31756
- _context5.n = 1;
31823
+ _context4.n = 1;
31757
31824
  break;
31758
31825
  }
31759
31826
  console.warn('⚠️ Invalid scene data structure in preloadMissingModels:', {
@@ -31762,7 +31829,7 @@ var ModelManager = /*#__PURE__*/function () {
31762
31829
  hasChildren: !!(data !== null && data !== void 0 && (_data$scene = data.scene) !== null && _data$scene !== void 0 && _data$scene.children),
31763
31830
  childrenType: data !== null && data !== void 0 && (_data$scene2 = data.scene) !== null && _data$scene2 !== void 0 && _data$scene2.children ? _typeof(data.scene.children) : 'undefined'
31764
31831
  });
31765
- return _context5.a(2);
31832
+ return _context4.a(2);
31766
31833
  case 1:
31767
31834
  requiredModels = new Set();
31768
31835
  console.log("\uD83D\uDD0D Checking ".concat(data.scene.children.length, " scene objects for required models..."));
@@ -31784,10 +31851,10 @@ var ModelManager = /*#__PURE__*/function () {
31784
31851
  }
31785
31852
  });
31786
31853
  console.log('🔍 Required models for this scene (Set):', Array.from(requiredModels));
31787
- _context5.n = 2;
31854
+ _context4.n = 2;
31788
31855
  return this.verifyModelPreloaderCache();
31789
31856
  case 2:
31790
- preloaderStatus = _context5.v;
31857
+ preloaderStatus = _context4.v;
31791
31858
  cachedModels = preloaderStatus.cachedModels;
31792
31859
  console.log('ModelPreloader cached models:', cachedModels);
31793
31860
 
@@ -31818,7 +31885,7 @@ var ModelManager = /*#__PURE__*/function () {
31818
31885
  });
31819
31886
  }
31820
31887
  if (!(missingModels.length > 0)) {
31821
- _context5.n = 12;
31888
+ _context4.n = 12;
31822
31889
  break;
31823
31890
  }
31824
31891
  console.warn('⚠️ Some required models are not cached:', missingModels);
@@ -31826,41 +31893,41 @@ var ModelManager = /*#__PURE__*/function () {
31826
31893
 
31827
31894
  // Preload missing models (Main in-memory preloader)
31828
31895
  _iterator = _createForOfIteratorHelper(missingModels);
31829
- _context5.p = 3;
31896
+ _context4.p = 3;
31830
31897
  _iterator.s();
31831
31898
  case 4:
31832
31899
  if ((_step = _iterator.n()).done) {
31833
- _context5.n = 9;
31900
+ _context4.n = 9;
31834
31901
  break;
31835
31902
  }
31836
31903
  modelKey = _step.value;
31837
- _context5.p = 5;
31838
- _context5.n = 6;
31904
+ _context4.p = 5;
31905
+ _context4.n = 6;
31839
31906
  return modelPreloader.preloadSingleModel(modelKey);
31840
31907
  case 6:
31841
31908
  console.log("\u2705 Successfully preloaded missing model: ".concat(modelKey));
31842
- _context5.n = 8;
31909
+ _context4.n = 8;
31843
31910
  break;
31844
31911
  case 7:
31845
- _context5.p = 7;
31846
- _t6 = _context5.v;
31912
+ _context4.p = 7;
31913
+ _t6 = _context4.v;
31847
31914
  console.warn("\u274C Failed to preload missing model ".concat(modelKey, ":"), _t6);
31848
31915
  case 8:
31849
- _context5.n = 4;
31916
+ _context4.n = 4;
31850
31917
  break;
31851
31918
  case 9:
31852
- _context5.n = 11;
31919
+ _context4.n = 11;
31853
31920
  break;
31854
31921
  case 10:
31855
- _context5.p = 10;
31856
- _t7 = _context5.v;
31922
+ _context4.p = 10;
31923
+ _t7 = _context4.v;
31857
31924
  _iterator.e(_t7);
31858
31925
  case 11:
31859
- _context5.p = 11;
31926
+ _context4.p = 11;
31860
31927
  _iterator.f();
31861
- return _context5.f(11);
31928
+ return _context4.f(11);
31862
31929
  case 12:
31863
- return _context5.a(2);
31930
+ return _context4.a(2);
31864
31931
  }
31865
31932
  }, _callee4, this, [[5, 7], [3, 10, 11, 12]]);
31866
31933
  }));
@@ -31879,14 +31946,14 @@ var ModelManager = /*#__PURE__*/function () {
31879
31946
  var _replaceWithGLBModels = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee5(libraryObjectsToReplace) {
31880
31947
  var _this3 = this;
31881
31948
  var startTime, glbLoadingPromises;
31882
- return _regenerator().w(function (_context6) {
31883
- while (1) switch (_context6.n) {
31949
+ return _regenerator().w(function (_context5) {
31950
+ while (1) switch (_context5.n) {
31884
31951
  case 0:
31885
31952
  if (!(libraryObjectsToReplace.length === 0)) {
31886
- _context6.n = 1;
31953
+ _context5.n = 1;
31887
31954
  break;
31888
31955
  }
31889
- return _context6.a(2);
31956
+ return _context5.a(2);
31890
31957
  case 1:
31891
31958
  startTime = performance.now();
31892
31959
  console.log("\uD83D\uDD04 Replacing ".concat(libraryObjectsToReplace.length, " objects with GLB models..."));
@@ -31906,7 +31973,7 @@ var ModelManager = /*#__PURE__*/function () {
31906
31973
  return basicObject;
31907
31974
  });
31908
31975
  });
31909
- _context6.n = 2;
31976
+ _context5.n = 2;
31910
31977
  return Promise.all(glbLoadingPromises);
31911
31978
  case 2:
31912
31979
  console.log("\u23F1\uFE0F All GLB models loaded in ".concat((performance.now() - startTime).toFixed(0), "ms"));
@@ -31949,7 +32016,7 @@ var ModelManager = /*#__PURE__*/function () {
31949
32016
  }));
31950
32017
  }
31951
32018
  case 3:
31952
- return _context6.a(2);
32019
+ return _context5.a(2);
31953
32020
  }
31954
32021
  }, _callee5);
31955
32022
  }));
@@ -31984,26 +32051,26 @@ var ModelManager = /*#__PURE__*/function () {
31984
32051
  value: (function () {
31985
32052
  var _loadComponentDictionary = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee6() {
31986
32053
  var response, dict, _t8;
31987
- return _regenerator().w(function (_context7) {
31988
- while (1) switch (_context7.n) {
32054
+ return _regenerator().w(function (_context6) {
32055
+ while (1) switch (_context6.n) {
31989
32056
  case 0:
31990
- _context7.p = 0;
32057
+ _context6.p = 0;
31991
32058
  console.log('🔄 ModelManager loading component dictionary...');
31992
- _context7.n = 1;
32059
+ _context6.n = 1;
31993
32060
  return fetch('/library/component-dictionary.json');
31994
32061
  case 1:
31995
- response = _context7.v;
31996
- _context7.n = 2;
32062
+ response = _context6.v;
32063
+ _context6.n = 2;
31997
32064
  return response.json();
31998
32065
  case 2:
31999
- dict = _context7.v;
32066
+ dict = _context6.v;
32000
32067
  console.log('✅ ModelManager loaded dictionary:', Object.keys(dict).length, 'entries');
32001
- return _context7.a(2, dict);
32068
+ return _context6.a(2, dict);
32002
32069
  case 3:
32003
- _context7.p = 3;
32004
- _t8 = _context7.v;
32070
+ _context6.p = 3;
32071
+ _t8 = _context6.v;
32005
32072
  console.warn('⚠️ Could not load component dictionary:', _t8);
32006
- return _context7.a(2, {});
32073
+ return _context6.a(2, {});
32007
32074
  }
32008
32075
  }, _callee6, null, [[0, 3]]);
32009
32076
  }));
@@ -32706,10 +32773,15 @@ var SceneOperationsManager = /*#__PURE__*/function () {
32706
32773
  key: "clearSceneObjects",
32707
32774
  value: function () {
32708
32775
  var _clearSceneObjects = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee() {
32709
- var result;
32776
+ var _this$sceneViewer;
32777
+ var ioBehavMgr, result;
32710
32778
  return _regenerator().w(function (_context) {
32711
32779
  while (1) switch (_context.n) {
32712
32780
  case 0:
32781
+ ioBehavMgr = (_this$sceneViewer = this.sceneViewer) === null || _this$sceneViewer === void 0 || (_this$sceneViewer = _this$sceneViewer.managers) === null || _this$sceneViewer === void 0 ? void 0 : _this$sceneViewer.ioBehaviorManager;
32782
+ if (ioBehavMgr !== null && ioBehavMgr !== void 0 && ioBehavMgr.resetForScene) {
32783
+ ioBehavMgr.resetForScene();
32784
+ }
32713
32785
  _context.n = 1;
32714
32786
  return this.sceneClearingUtility.clearAllObjects();
32715
32787
  case 1:
@@ -33075,9 +33147,9 @@ var SceneOperationsManager = /*#__PURE__*/function () {
33075
33147
  // Use crosscubeTextureSet if available, otherwise fallback material
33076
33148
  var material = materials[obj.material];
33077
33149
  if (!material) {
33078
- var _this$sceneViewer;
33150
+ var _this$sceneViewer2;
33079
33151
  // Check if we have crosscubeTextureSet from scene loading
33080
- var crosscubeTextureSet = (_this$sceneViewer = this.sceneViewer) === null || _this$sceneViewer === void 0 ? void 0 : _this$sceneViewer.crosscubeTextureSet;
33152
+ var crosscubeTextureSet = (_this$sceneViewer2 = this.sceneViewer) === null || _this$sceneViewer2 === void 0 ? void 0 : _this$sceneViewer2.crosscubeTextureSet;
33081
33153
  if (crosscubeTextureSet) {
33082
33154
  // Match PathfindingManager.createPipeMaterial() with textures
33083
33155
  var pathColor = '#245e29'; // Default green color from PathfindingManager.getPathColor()
@@ -36540,6 +36612,61 @@ var SceneTooltipsManager = /*#__PURE__*/function (_BaseDisposable) {
36540
36612
  }]);
36541
36613
  }(BaseDisposable);
36542
36614
 
36615
+ /**
36616
+ * Shared I/O device state dispatch helpers used by centralPlant and componentTooltipManager.
36617
+ */
36618
+
36619
+ /**
36620
+ * @param {string} attachmentId
36621
+ * @param {string|null|undefined} parentUuid
36622
+ * @returns {string}
36623
+ */
36624
+ function getScopedAttachmentKey(attachmentId, parentUuid) {
36625
+ if (!parentUuid) return attachmentId;
36626
+ return "".concat(parentUuid, "::").concat(attachmentId);
36627
+ }
36628
+
36629
+ /**
36630
+ * @param {Object} sceneViewer
36631
+ * @returns {import('../managers/behaviors/IoBehaviorManager.js').IoBehaviorManager|null}
36632
+ */
36633
+ function getIoBehaviorManager(sceneViewer) {
36634
+ var _sceneViewer$managers, _sceneViewer$centralP;
36635
+ if (!sceneViewer) return null;
36636
+ return ((_sceneViewer$managers = sceneViewer.managers) === null || _sceneViewer$managers === void 0 ? void 0 : _sceneViewer$managers.ioBehaviorManager) || sceneViewer.ioBehaviorManager || ((_sceneViewer$centralP = sceneViewer.centralPlant) === null || _sceneViewer$centralP === void 0 || (_sceneViewer$centralP = _sceneViewer$centralP.managers) === null || _sceneViewer$centralP === void 0 ? void 0 : _sceneViewer$centralP.ioBehaviorManager) || null;
36637
+ }
36638
+
36639
+ /**
36640
+ * Resolve tooltip/drag data points for an I/O device attachment.
36641
+ * Prefers behaviorConfig-driven animation data points; merges legacy ioConfig snapshot.
36642
+ *
36643
+ * @param {string} parentUuid
36644
+ * @param {string} attachmentId
36645
+ * @param {Object} userData - io-device userData (may include dataPoints snapshot)
36646
+ * @param {import('../managers/behaviors/IoBehaviorManager.js').IoBehaviorManager|null} ioBehaviorManager
36647
+ * @param {THREE.Object3D|null} [hitMesh]
36648
+ * @returns {Object[]}
36649
+ */
36650
+ function resolveDataPoints(parentUuid, attachmentId, userData, ioBehaviorManager) {
36651
+ var hitMesh = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : null;
36652
+ var fromAnimations = (ioBehaviorManager === null || ioBehaviorManager === void 0 ? void 0 : ioBehaviorManager.getAnimationDataPoints(parentUuid, attachmentId, hitMesh)) || [];
36653
+ if (fromAnimations.length) return fromAnimations;
36654
+ var legacy = (userData === null || userData === void 0 ? void 0 : userData.dataPoints) || [];
36655
+ return legacy.map(function (dp) {
36656
+ var _dp$defaultValue;
36657
+ return {
36658
+ id: dp.id || dp.name,
36659
+ name: dp.name || dp.id,
36660
+ stateType: dp.stateType || 'binary',
36661
+ stateConfig: dp.stateConfig || {},
36662
+ defaultValue: (_dp$defaultValue = dp.defaultValue) !== null && _dp$defaultValue !== void 0 ? _dp$defaultValue : null,
36663
+ direction: dp.direction || (userData === null || userData === void 0 ? void 0 : userData.ioDirection) || 'output'
36664
+ };
36665
+ }).filter(function (dp) {
36666
+ return dp.id;
36667
+ });
36668
+ }
36669
+
36543
36670
  // ---------------------------------------------------------------------------
36544
36671
  // Inline styles (injected once into the document head)
36545
36672
  // ---------------------------------------------------------------------------
@@ -36549,6 +36676,7 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
36549
36676
  * @param {Object} sceneViewer - The sceneViewer instance
36550
36677
  */
36551
36678
  function ComponentTooltipManager(sceneViewer) {
36679
+ var _this$sceneViewer, _this$sceneViewer$on;
36552
36680
  var _this;
36553
36681
  _classCallCheck(this, ComponentTooltipManager);
36554
36682
  _this = _callSuper(this, ComponentTooltipManager);
@@ -36573,29 +36701,38 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
36573
36701
  * Map of live DOM elements that display current data point values.
36574
36702
  * Key: `${attachmentId}::${dataPointId}`
36575
36703
  * Value: { el: HTMLElement, dp: Object, isInput: boolean }
36576
- * Populated during _buildTooltip; polled each frame in _refreshStateDisplays.
36704
+ * Populated during _buildTooltip; refreshed on io-device-state-changed events.
36577
36705
  * @type {Map<string, {el: HTMLElement, dp: Object, isInput: boolean}>}
36578
36706
  */
36579
36707
  _this._stateElements = new Map();
36708
+ _this._onIoStateChanged = _this._onIoStateChanged.bind(_this);
36580
36709
  _this._injectStyles();
36581
36710
  _this._onKeyDown = _this._onKeyDown.bind(_this);
36582
36711
  document.addEventListener('keydown', _this._onKeyDown);
36712
+ (_this$sceneViewer = _this.sceneViewer) === null || _this$sceneViewer === void 0 || (_this$sceneViewer$on = _this$sceneViewer.on) === null || _this$sceneViewer$on === void 0 || _this$sceneViewer$on.call(_this$sceneViewer, 'io-device-state-changed', _this._onIoStateChanged);
36583
36713
  return _this;
36584
36714
  }
36585
-
36586
- // -----------------------------------------------------------------------
36587
- // Lifecycle
36588
- // -----------------------------------------------------------------------
36589
-
36590
- /**
36591
- * Called automatically by BaseDisposable.dispose()
36592
- * @override
36593
- */
36594
36715
  _inherits(ComponentTooltipManager, _BaseDisposable);
36595
36716
  return _createClass(ComponentTooltipManager, [{
36717
+ key: "_onIoStateChanged",
36718
+ value: function _onIoStateChanged() {
36719
+ this._refreshStateDisplays();
36720
+ }
36721
+
36722
+ // -----------------------------------------------------------------------
36723
+ // Lifecycle
36724
+ // -----------------------------------------------------------------------
36725
+
36726
+ /**
36727
+ * Called automatically by BaseDisposable.dispose()
36728
+ * @override
36729
+ */
36730
+ }, {
36596
36731
  key: "_doDispose",
36597
36732
  value: function _doDispose() {
36733
+ var _this$sceneViewer2, _this$sceneViewer2$of;
36598
36734
  document.removeEventListener('keydown', this._onKeyDown);
36735
+ (_this$sceneViewer2 = this.sceneViewer) === null || _this$sceneViewer2 === void 0 || (_this$sceneViewer2$of = _this$sceneViewer2.off) === null || _this$sceneViewer2$of === void 0 || _this$sceneViewer2$of.call(_this$sceneViewer2, 'io-device-state-changed', this._onIoStateChanged);
36599
36736
  this.hide();
36600
36737
  this._removeStyleTag();
36601
36738
  this._stateElements.clear();
@@ -36643,7 +36780,7 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
36643
36780
  }, {
36644
36781
  key: "toggleIODeviceBinaryState",
36645
36782
  value: function toggleIODeviceBinaryState(ioDeviceObject) {
36646
- var _ref, _this$sceneViewer;
36783
+ var _ref;
36647
36784
  if (!ioDeviceObject || !this._stateAdapter) return;
36648
36785
  var ud = ioDeviceObject.userData;
36649
36786
  var attachmentId = ud === null || ud === void 0 ? void 0 : ud.attachmentId;
@@ -36664,7 +36801,7 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
36664
36801
 
36665
36802
  // Create a scoped attachment key to prevent state sharing between instances
36666
36803
  // of the same smart component that share the same attachmentId
36667
- var scopedAttachmentId = this._getScopedAttachmentKey(attachmentId, parentUuid);
36804
+ var scopedAttachmentId = getScopedAttachmentKey(attachmentId, parentUuid);
36668
36805
 
36669
36806
  // Find the first binary state
36670
36807
  var binaryState = dataPoints.find(function (dp) {
@@ -36676,8 +36813,7 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
36676
36813
  // Fall back to defaultValue when state is uninitialized (null/undefined)
36677
36814
  var currentVal = (_ref = storedVal !== null && storedVal !== void 0 ? storedVal : binaryState.defaultValue) !== null && _ref !== void 0 ? _ref : false;
36678
36815
  var newVal = !Boolean(currentVal);
36679
- this._stateAdapter.setState(scopedAttachmentId, dpId, newVal);
36680
- (_this$sceneViewer = this.sceneViewer) === null || _this$sceneViewer === void 0 || (_this$sceneViewer = _this$sceneViewer.managers) === null || _this$sceneViewer === void 0 || (_this$sceneViewer = _this$sceneViewer.ioBehaviorManager) === null || _this$sceneViewer === void 0 || _this$sceneViewer.triggerState(attachmentId, dpId, newVal, parentUuid);
36816
+ this._dispatchIoState(attachmentId, dpId, newVal, parentUuid);
36681
36817
  console.log("\uD83D\uDD04 [IODevice] Toggled ".concat(scopedAttachmentId, ".").concat(dpId, ": ").concat(currentVal, " \u2192 ").concat(newVal));
36682
36818
  }
36683
36819
 
@@ -36693,8 +36829,7 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
36693
36829
  }, {
36694
36830
  key: "startIODeviceDrag",
36695
36831
  value: function startIODeviceDrag(ioDeviceObject, hitMesh) {
36696
- var _this$sceneViewer2,
36697
- _this2 = this;
36832
+ var _this2 = this;
36698
36833
  if (!ioDeviceObject || !this._stateAdapter) return;
36699
36834
  var ud = ioDeviceObject.userData;
36700
36835
  var attachmentId = ud === null || ud === void 0 ? void 0 : ud.attachmentId;
@@ -36709,15 +36844,8 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
36709
36844
  }
36710
36845
  obj = obj.parent;
36711
36846
  }
36712
- var scopedAttachmentId = this._getScopedAttachmentKey(attachmentId, parentUuid);
36713
- var ioBehavMgr = (_this$sceneViewer2 = this.sceneViewer) === null || _this$sceneViewer2 === void 0 || (_this$sceneViewer2 = _this$sceneViewer2.managers) === null || _this$sceneViewer2 === void 0 ? void 0 : _this$sceneViewer2.ioBehaviorManager;
36714
- var dataPoints = ((ioBehavMgr === null || ioBehavMgr === void 0 ? void 0 : ioBehavMgr.getAnimationDataPoints(parentUuid, attachmentId, hitMesh)) || []).concat((ud === null || ud === void 0 ? void 0 : ud.dataPoints) || [])
36715
- // deduplicate by id
36716
- .filter(function (dp, i, arr) {
36717
- return arr.findIndex(function (d) {
36718
- return d.id === dp.id;
36719
- }) === i;
36720
- });
36847
+ var scopedAttachmentId = getScopedAttachmentKey(attachmentId, parentUuid);
36848
+ var dataPoints = resolveDataPoints(parentUuid, attachmentId, ud, getIoBehaviorManager(this.sceneViewer), hitMesh);
36721
36849
  var dpSessions = [];
36722
36850
  var _iterator = _createForOfIteratorHelper(dataPoints),
36723
36851
  _step;
@@ -36726,7 +36854,6 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
36726
36854
  var _this2$_stateAdapter$;
36727
36855
  var dp = _step.value;
36728
36856
  var stateType = (dp.stateType || '').toLowerCase();
36729
- if (stateType !== 'binary' && stateType !== 'boolean' && stateType !== 'enum') return 1; // continue
36730
36857
  var curVal = (_this2$_stateAdapter$ = _this2._stateAdapter.getState(scopedAttachmentId, dp.id)) !== null && _this2$_stateAdapter$ !== void 0 ? _this2$_stateAdapter$ : dp.defaultValue;
36731
36858
  if (stateType === 'binary' || stateType === 'boolean') {
36732
36859
  dpSessions.push({
@@ -36737,7 +36864,7 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
36737
36864
  stateType: 'binary',
36738
36865
  lastApplied: curVal
36739
36866
  });
36740
- } else {
36867
+ } else if (stateType === 'enum') {
36741
36868
  var _dp$stateConfig;
36742
36869
  var opts = ((_dp$stateConfig = dp.stateConfig) === null || _dp$stateConfig === void 0 ? void 0 : _dp$stateConfig.options) || [];
36743
36870
  var curIdx = opts.findIndex(function (o) {
@@ -36753,10 +36880,27 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
36753
36880
  startIdx: curIdx >= 0 ? curIdx : 0,
36754
36881
  lastAppliedIdx: curIdx >= 0 ? curIdx : 0
36755
36882
  });
36883
+ } else if (stateType === 'number' || stateType === 'continuous') {
36884
+ var _dp$stateConfig$min, _dp$stateConfig2, _dp$stateConfig$max, _dp$stateConfig3;
36885
+ var min = (_dp$stateConfig$min = (_dp$stateConfig2 = dp.stateConfig) === null || _dp$stateConfig2 === void 0 ? void 0 : _dp$stateConfig2.min) !== null && _dp$stateConfig$min !== void 0 ? _dp$stateConfig$min : 0;
36886
+ var max = (_dp$stateConfig$max = (_dp$stateConfig3 = dp.stateConfig) === null || _dp$stateConfig3 === void 0 ? void 0 : _dp$stateConfig3.max) !== null && _dp$stateConfig$max !== void 0 ? _dp$stateConfig$max : 1;
36887
+ var parsed = Number(curVal);
36888
+ var startVal = Number.isFinite(parsed) ? Math.max(min, Math.min(max, parsed)) : min;
36889
+ dpSessions.push({
36890
+ dp: dp,
36891
+ scopedAttachmentId: scopedAttachmentId,
36892
+ attachmentId: attachmentId,
36893
+ parentUuid: parentUuid,
36894
+ stateType: 'number',
36895
+ min: min,
36896
+ max: max,
36897
+ startVal: startVal,
36898
+ lastApplied: startVal
36899
+ });
36756
36900
  }
36757
36901
  };
36758
36902
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
36759
- if (_loop()) continue;
36903
+ _loop();
36760
36904
  }
36761
36905
  } catch (err) {
36762
36906
  _iterator.e(err);
@@ -36773,8 +36917,9 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
36773
36917
  * Called continuously during pointermove.
36774
36918
  *
36775
36919
  * Sign convention: up/right = positive `signedDelta`.
36776
- * - Binary: > +20 px → true/on state, < −20 px → false/off state.
36777
- * - Enum: each ±30 px step advances/retreats one option in the list.
36920
+ * - Binary: > +20 px → true/on state, < −20 px → false/off state.
36921
+ * - Enum: each ±30 px step advances/retreats one option in the list.
36922
+ * - Continuous: ±200 px spans the configured min→max range.
36778
36923
  *
36779
36924
  * @param {number} signedDelta - Cumulative signed pixel displacement since drag start
36780
36925
  */
@@ -36785,6 +36930,7 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
36785
36930
  if (!session) return;
36786
36931
  var BINARY_THRESHOLD = 20;
36787
36932
  var ENUM_STEP_PX = 30;
36933
+ var RANGE_PX = 200;
36788
36934
  var _iterator2 = _createForOfIteratorHelper(session.dpSessions),
36789
36935
  _step2;
36790
36936
  try {
@@ -36808,6 +36954,13 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
36808
36954
  if (newIdx === dps.lastAppliedIdx) continue;
36809
36955
  dps.lastAppliedIdx = newIdx;
36810
36956
  this._applyDpState(dps, dps.opts[newIdx]);
36957
+ } else if (dps.stateType === 'number') {
36958
+ var span = dps.max - dps.min || 1;
36959
+ var delta = signedDelta / RANGE_PX * span;
36960
+ var _newVal = Math.max(dps.min, Math.min(dps.max, dps.startVal + delta));
36961
+ if (Math.abs(_newVal - dps.lastApplied) < span * 0.001) continue;
36962
+ dps.lastApplied = _newVal;
36963
+ this._applyDpState(dps, _newVal);
36811
36964
  }
36812
36965
  }
36813
36966
  } catch (err) {
@@ -36833,14 +36986,35 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
36833
36986
  }, {
36834
36987
  key: "_applyDpState",
36835
36988
  value: function _applyDpState(_ref2, newVal) {
36836
- var _this$_stateAdapter, _this$sceneViewer3;
36837
- var scopedAttachmentId = _ref2.scopedAttachmentId,
36838
- attachmentId = _ref2.attachmentId,
36989
+ _ref2.scopedAttachmentId;
36990
+ var attachmentId = _ref2.attachmentId,
36839
36991
  parentUuid = _ref2.parentUuid,
36840
36992
  dp = _ref2.dp;
36841
- var dpId = dp.id;
36842
- (_this$_stateAdapter = this._stateAdapter) === null || _this$_stateAdapter === void 0 || _this$_stateAdapter.setState(scopedAttachmentId, dpId, newVal);
36843
- (_this$sceneViewer3 = this.sceneViewer) === null || _this$sceneViewer3 === void 0 || (_this$sceneViewer3 = _this$sceneViewer3.managers) === null || _this$sceneViewer3 === void 0 || (_this$sceneViewer3 = _this$sceneViewer3.ioBehaviorManager) === null || _this$sceneViewer3 === void 0 || _this$sceneViewer3.triggerState(attachmentId, dpId, newVal, parentUuid);
36993
+ this._dispatchIoState(attachmentId, dp.id, newVal, parentUuid);
36994
+ }
36995
+
36996
+ /**
36997
+ * Unified I/O state dispatch with fallback when centralPlant is unavailable.
36998
+ * @private
36999
+ */
37000
+ }, {
37001
+ key: "_dispatchIoState",
37002
+ value: function _dispatchIoState(attachmentId, stateId, value, parentUuid) {
37003
+ var _this$sceneViewer3, _this$_stateAdapter, _getIoBehaviorManager, _this$sceneViewer4;
37004
+ var cp = (_this$sceneViewer3 = this.sceneViewer) === null || _this$sceneViewer3 === void 0 ? void 0 : _this$sceneViewer3.centralPlant;
37005
+ if (cp && typeof cp._dispatchIoState === 'function') {
37006
+ cp._dispatchIoState(attachmentId, stateId, value, parentUuid);
37007
+ return;
37008
+ }
37009
+ var scopedKey = getScopedAttachmentKey(attachmentId, parentUuid);
37010
+ (_this$_stateAdapter = this._stateAdapter) === null || _this$_stateAdapter === void 0 || _this$_stateAdapter.setState(scopedKey, stateId, value);
37011
+ (_getIoBehaviorManager = getIoBehaviorManager(this.sceneViewer)) === null || _getIoBehaviorManager === void 0 || _getIoBehaviorManager.triggerState(attachmentId, stateId, value, parentUuid);
37012
+ (_this$sceneViewer4 = this.sceneViewer) === null || _this$sceneViewer4 === void 0 || _this$sceneViewer4.emit('io-device-state-changed', {
37013
+ attachmentId: attachmentId,
37014
+ stateId: stateId,
37015
+ value: value,
37016
+ parentUuid: parentUuid || null
37017
+ });
36844
37018
  }
36845
37019
 
36846
37020
  /**
@@ -36873,7 +37047,6 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
36873
37047
  value: function update() {
36874
37048
  if (!this.tooltipEl || !this.selectedObject) return;
36875
37049
  this._positionTooltip();
36876
- this._refreshStateDisplays();
36877
37050
  }
36878
37051
 
36879
37052
  /**
@@ -36932,21 +37105,6 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
36932
37105
  this._styleInjected = false;
36933
37106
  }
36934
37107
 
36935
- /**
36936
- * Generate a scoped attachment key that includes the parent component UUID.
36937
- * This ensures each instance of a smart component has isolated IO device state.
36938
- * @param {string} attachmentId - The original attachment ID from the component data
36939
- * @param {string|null} parentUuid - The UUID of the parent smart component instance
36940
- * @returns {string} A scoped key in the format "parentUuid::attachmentId" or just attachmentId if no parent
36941
- * @private
36942
- */
36943
- }, {
36944
- key: "_getScopedAttachmentKey",
36945
- value: function _getScopedAttachmentKey(attachmentId, parentUuid) {
36946
- if (!parentUuid) return attachmentId;
36947
- return "".concat(parentUuid, "::").concat(attachmentId);
36948
- }
36949
-
36950
37108
  /**
36951
37109
  * Gather I/O device children from a component's Three.js hierarchy.
36952
37110
  * Returns richer data including attachmentId and data point definitions.
@@ -36962,12 +37120,11 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
36962
37120
  object.traverse(function (child) {
36963
37121
  var _child$userData;
36964
37122
  if (((_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.objectType) === 'io-device') {
36965
- var _this3$sceneViewer$ma, _this3$sceneViewer;
36966
37123
  var attachmentId = child.userData.attachmentId || '';
36967
37124
 
36968
37125
  // Use only data points from the animate window (behaviorConfig).
36969
37126
  // The static ioConfig.states[] snapshot on userData is intentionally ignored.
36970
- var dataPoints = (_this3$sceneViewer$ma = (_this3$sceneViewer = _this3.sceneViewer) === null || _this3$sceneViewer === void 0 || (_this3$sceneViewer = _this3$sceneViewer.managers) === null || _this3$sceneViewer === void 0 || (_this3$sceneViewer = _this3$sceneViewer.ioBehaviorManager) === null || _this3$sceneViewer === void 0 ? void 0 : _this3$sceneViewer.getAnimationDataPoints(parentUuid, attachmentId)) !== null && _this3$sceneViewer$ma !== void 0 ? _this3$sceneViewer$ma : [];
37127
+ var dataPoints = resolveDataPoints(parentUuid, attachmentId, child.userData, getIoBehaviorManager(_this3.sceneViewer));
36971
37128
 
36972
37129
  // When data points come from behaviorConfig they already carry direction:'input'.
36973
37130
  // Pass null so _buildDataPointRow uses the per-dp direction instead of the
@@ -36977,7 +37134,7 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
36977
37134
  label: child.userData.attachmentLabel || child.name || child.userData.deviceId || 'Unknown Device',
36978
37135
  deviceId: child.userData.deviceId || '',
36979
37136
  attachmentId: attachmentId,
36980
- scopedAttachmentId: _this3._getScopedAttachmentKey(attachmentId, parentUuid),
37137
+ scopedAttachmentId: getScopedAttachmentKey(attachmentId, parentUuid),
36981
37138
  dataPoints: dataPoints,
36982
37139
  direction: deviceDirection
36983
37140
  });
@@ -37123,11 +37280,11 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
37123
37280
  }, {
37124
37281
  key: "_positionTooltip",
37125
37282
  value: function _positionTooltip() {
37126
- var _this$sceneViewer4, _this$sceneViewer5;
37283
+ var _this$sceneViewer5, _this$sceneViewer6;
37127
37284
  if (!this.tooltipEl || !this.selectedObject) return;
37128
37285
  var container = this._getContainer();
37129
- var camera = (_this$sceneViewer4 = this.sceneViewer) === null || _this$sceneViewer4 === void 0 ? void 0 : _this$sceneViewer4.camera;
37130
- var renderer = (_this$sceneViewer5 = this.sceneViewer) === null || _this$sceneViewer5 === void 0 ? void 0 : _this$sceneViewer5.renderer;
37286
+ var camera = (_this$sceneViewer5 = this.sceneViewer) === null || _this$sceneViewer5 === void 0 ? void 0 : _this$sceneViewer5.camera;
37287
+ var renderer = (_this$sceneViewer6 = this.sceneViewer) === null || _this$sceneViewer6 === void 0 ? void 0 : _this$sceneViewer6.renderer;
37131
37288
  if (!container || !camera || !renderer) return;
37132
37289
 
37133
37290
  // Compute bounding box to position above the component
@@ -37164,8 +37321,8 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
37164
37321
  }, {
37165
37322
  key: "_getContainer",
37166
37323
  value: function _getContainer() {
37167
- var _this$sceneViewer6;
37168
- return ((_this$sceneViewer6 = this.sceneViewer) === null || _this$sceneViewer6 === void 0 || (_this$sceneViewer6 = _this$sceneViewer6.renderer) === null || _this$sceneViewer6 === void 0 || (_this$sceneViewer6 = _this$sceneViewer6.domElement) === null || _this$sceneViewer6 === void 0 ? void 0 : _this$sceneViewer6.parentElement) || null;
37324
+ var _this$sceneViewer7;
37325
+ return ((_this$sceneViewer7 = this.sceneViewer) === null || _this$sceneViewer7 === void 0 || (_this$sceneViewer7 = _this$sceneViewer7.renderer) === null || _this$sceneViewer7 === void 0 || (_this$sceneViewer7 = _this$sceneViewer7.domElement) === null || _this$sceneViewer7 === void 0 ? void 0 : _this$sceneViewer7.parentElement) || null;
37169
37326
  }
37170
37327
 
37171
37328
  // -----------------------------------------------------------------------
@@ -37205,10 +37362,9 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
37205
37362
  var currentVal = (_ref3 = (_this$_stateAdapter$g = (_this$_stateAdapter2 = this._stateAdapter) === null || _this$_stateAdapter2 === void 0 ? void 0 : _this$_stateAdapter2.getState(scopedAttachmentId, dpId)) !== null && _this$_stateAdapter$g !== void 0 ? _this$_stateAdapter$g : dp.defaultValue) !== null && _ref3 !== void 0 ? _ref3 : null;
37206
37363
  if (isInput) {
37207
37364
  var ctrl = this._buildInputControl(dp, currentVal, function (newVal) {
37208
- var _this5$_stateAdapter, _this5$selectedObject, _this5$sceneViewer;
37209
- (_this5$_stateAdapter = _this5._stateAdapter) === null || _this5$_stateAdapter === void 0 || _this5$_stateAdapter.setState(scopedAttachmentId, dpId, newVal);
37365
+ var _this5$selectedObject;
37210
37366
  var parentUuid = ((_this5$selectedObject = _this5.selectedObject) === null || _this5$selectedObject === void 0 ? void 0 : _this5$selectedObject.uuid) || null;
37211
- (_this5$sceneViewer = _this5.sceneViewer) === null || _this5$sceneViewer === void 0 || (_this5$sceneViewer = _this5$sceneViewer.managers) === null || _this5$sceneViewer === void 0 || (_this5$sceneViewer = _this5$sceneViewer.ioBehaviorManager) === null || _this5$sceneViewer === void 0 || _this5$sceneViewer.triggerState(originalAttachmentId || scopedAttachmentId, dpId, newVal, parentUuid);
37367
+ _this5._dispatchIoState(originalAttachmentId || scopedAttachmentId, dpId, newVal, parentUuid);
37212
37368
  });
37213
37369
  row.appendChild(ctrl);
37214
37370
  this._stateElements.set(key, {
@@ -37217,9 +37373,9 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
37217
37373
  isInput: true
37218
37374
  });
37219
37375
  } else {
37220
- var _dp$stateConfig2;
37376
+ var _dp$stateConfig4;
37221
37377
  // unit suffix (optional, shown between name and badge)
37222
- var unit = (_dp$stateConfig2 = dp.stateConfig) === null || _dp$stateConfig2 === void 0 ? void 0 : _dp$stateConfig2.unit;
37378
+ var unit = (_dp$stateConfig4 = dp.stateConfig) === null || _dp$stateConfig4 === void 0 ? void 0 : _dp$stateConfig4.unit;
37223
37379
  if (unit) {
37224
37380
  var unitEl = document.createElement('span');
37225
37381
  unitEl.className = 'cp-tooltip__dp-unit';
@@ -37380,6 +37536,242 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
37380
37536
  }]);
37381
37537
  }(BaseDisposable);
37382
37538
 
37539
+ /**
37540
+ * Shared behavior schema helpers for runtime normalization and UI authoring.
37541
+ * Used by IoBehaviorManager and sandbox BehaviorDialog.
37542
+ */
37543
+
37544
+ /**
37545
+ * Normalize behavior from shorthand to full format.
37546
+ * Supports:
37547
+ * - input: "attachment.state" → { attachment, state }
37548
+ * - outputs: ["attachment.state", ...] → converted to _outputs array
37549
+ *
37550
+ * @param {Object} behavior - Raw behavior from scene JSON
37551
+ * @returns {Object} Normalized behavior
37552
+ */
37553
+ function normalizeBehavior(behavior) {
37554
+ var normalized = _objectSpread2({}, behavior);
37555
+ if (typeof behavior.input === 'string') {
37556
+ var _behavior$input$split = behavior.input.split('.'),
37557
+ _behavior$input$split2 = _slicedToArray(_behavior$input$split, 2),
37558
+ attachment = _behavior$input$split2[0],
37559
+ state = _behavior$input$split2[1];
37560
+ normalized.input = {
37561
+ attachment: attachment,
37562
+ state: state
37563
+ };
37564
+ }
37565
+ if (behavior.outputs) {
37566
+ normalized._outputs = behavior.outputs.map(function (out) {
37567
+ if (typeof out === 'string') {
37568
+ var _out$split = out.split('.'),
37569
+ _out$split2 = _slicedToArray(_out$split, 2),
37570
+ _attachment = _out$split2[0],
37571
+ _state = _out$split2[1];
37572
+ return {
37573
+ attachment: _attachment,
37574
+ state: _state
37575
+ };
37576
+ }
37577
+ return out;
37578
+ });
37579
+ delete normalized.outputs;
37580
+ } else if (typeof behavior.output === 'string') {
37581
+ var _behavior$output$spli = behavior.output.split('.'),
37582
+ _behavior$output$spli2 = _slicedToArray(_behavior$output$spli, 2),
37583
+ _attachment2 = _behavior$output$spli2[0],
37584
+ _state2 = _behavior$output$spli2[1];
37585
+ normalized.output = {
37586
+ attachment: _attachment2,
37587
+ state: _state2
37588
+ };
37589
+ }
37590
+ return normalized;
37591
+ }
37592
+
37593
+ /**
37594
+ * Build intra-component shorthand behavior from form fields.
37595
+ */
37596
+ function buildIntraBehavior(_ref) {
37597
+ var id = _ref.id,
37598
+ inputAttachment = _ref.inputAttachment,
37599
+ inputState = _ref.inputState,
37600
+ outputs = _ref.outputs;
37601
+ return {
37602
+ id: id || "behavior-".concat(Date.now()),
37603
+ input: "".concat(inputAttachment, ".").concat(inputState),
37604
+ outputs: outputs.filter(function (o) {
37605
+ return o.attachment && o.state;
37606
+ }).map(function (o) {
37607
+ return "".concat(o.attachment, ".").concat(o.state);
37608
+ })
37609
+ };
37610
+ }
37611
+
37612
+ /**
37613
+ * Parse intra-component shorthand into form fields.
37614
+ */
37615
+ function parseIntraBehavior(behavior) {
37616
+ var inputAttachment = null;
37617
+ var inputState = null;
37618
+ if (typeof behavior.input === 'string') {
37619
+ var _behavior$input$split3 = behavior.input.split('.');
37620
+ var _behavior$input$split4 = _slicedToArray(_behavior$input$split3, 2);
37621
+ inputAttachment = _behavior$input$split4[0];
37622
+ inputState = _behavior$input$split4[1];
37623
+ } else if (behavior.input && _typeof(behavior.input) === 'object') {
37624
+ inputAttachment = behavior.input.attachment || null;
37625
+ inputState = behavior.input.state || null;
37626
+ }
37627
+ var outputs = (behavior.outputs || []).map(function (out) {
37628
+ if (typeof out === 'string') {
37629
+ var _out$split3 = out.split('.'),
37630
+ _out$split4 = _slicedToArray(_out$split3, 2),
37631
+ attachment = _out$split4[0],
37632
+ state = _out$split4[1];
37633
+ return {
37634
+ attachment: attachment,
37635
+ state: state
37636
+ };
37637
+ }
37638
+ return {
37639
+ attachment: (out === null || out === void 0 ? void 0 : out.attachment) || null,
37640
+ state: (out === null || out === void 0 ? void 0 : out.state) || null
37641
+ };
37642
+ });
37643
+ return {
37644
+ id: behavior.id || '',
37645
+ inputAttachment: inputAttachment || null,
37646
+ inputState: inputState || null,
37647
+ outputs: outputs.length ? outputs : [{
37648
+ attachment: null,
37649
+ state: null
37650
+ }]
37651
+ };
37652
+ }
37653
+
37654
+ /**
37655
+ * Build cross-component behavior with component instance scoping.
37656
+ */
37657
+ function buildCrossBehavior(_ref2) {
37658
+ var id = _ref2.id,
37659
+ inputEndpoint = _ref2.inputEndpoint,
37660
+ inputState = _ref2.inputState,
37661
+ outputs = _ref2.outputs;
37662
+ var _split = (inputEndpoint || '').split('::'),
37663
+ _split2 = _slicedToArray(_split, 2),
37664
+ inputComponent = _split2[0],
37665
+ inputAttachment = _split2[1];
37666
+ return {
37667
+ id: id || "behavior-".concat(Date.now()),
37668
+ input: {
37669
+ component: inputComponent,
37670
+ attachment: inputAttachment,
37671
+ state: inputState
37672
+ },
37673
+ outputs: outputs.filter(function (o) {
37674
+ return o.endpoint && o.state;
37675
+ }).map(function (o) {
37676
+ var _o$endpoint$split = o.endpoint.split('::'),
37677
+ _o$endpoint$split2 = _slicedToArray(_o$endpoint$split, 2),
37678
+ component = _o$endpoint$split2[0],
37679
+ attachment = _o$endpoint$split2[1];
37680
+ return {
37681
+ component: component,
37682
+ attachment: attachment,
37683
+ state: o.state
37684
+ };
37685
+ })
37686
+ };
37687
+ }
37688
+
37689
+ /**
37690
+ * Parse cross-component behavior into form fields.
37691
+ */
37692
+ function parseCrossBehavior(behavior) {
37693
+ var inputComponent, inputAttachment, inputState;
37694
+ if (typeof behavior.input === 'string') {
37695
+ var _behavior$input$split5 = behavior.input.split('.'),
37696
+ _behavior$input$split6 = _slicedToArray(_behavior$input$split5, 2),
37697
+ attachment = _behavior$input$split6[0],
37698
+ state = _behavior$input$split6[1];
37699
+ inputAttachment = attachment;
37700
+ inputState = state;
37701
+ inputComponent = null;
37702
+ } else if (behavior.input) {
37703
+ inputComponent = behavior.input.component;
37704
+ inputAttachment = behavior.input.attachment;
37705
+ inputState = behavior.input.state;
37706
+ }
37707
+ var rawOutputs = behavior.outputs || (behavior.output ? [behavior.output] : behavior._outputs || []);
37708
+ var outputs = rawOutputs.map(function (out) {
37709
+ if (typeof out === 'string') {
37710
+ var _out$split5 = out.split('.'),
37711
+ _out$split6 = _slicedToArray(_out$split5, 2),
37712
+ _attachment3 = _out$split6[0],
37713
+ _state3 = _out$split6[1];
37714
+ return {
37715
+ endpoint: _attachment3 ? "::".concat(_attachment3) : null,
37716
+ state: _state3
37717
+ };
37718
+ }
37719
+ var endpoint = out.component && out.attachment ? "".concat(out.component, "::").concat(out.attachment) : out.attachment ? "::".concat(out.attachment) : null;
37720
+ return {
37721
+ endpoint: endpoint,
37722
+ state: out.state || null
37723
+ };
37724
+ });
37725
+ var inputEndpoint = inputComponent && inputAttachment ? "".concat(inputComponent, "::").concat(inputAttachment) : inputAttachment ? "::".concat(inputAttachment) : null;
37726
+ return {
37727
+ id: behavior.id || '',
37728
+ inputEndpoint: inputEndpoint,
37729
+ inputState: inputState || null,
37730
+ outputs: outputs.length ? outputs : [{
37731
+ endpoint: null,
37732
+ state: null
37733
+ }]
37734
+ };
37735
+ }
37736
+
37737
+ /**
37738
+ * Translation helpers for I/O device animations.
37739
+ * Offsets are expressed in the device/model root's local space so every child
37740
+ * mesh moves along the same predictable axes regardless of intermediate parents.
37741
+ */
37742
+
37743
+ /**
37744
+ * @param {THREE.Object3D} modelRoot
37745
+ * @param {{ x?: number, y?: number, z?: number }|THREE.Vector3} offset
37746
+ * @returns {THREE.Vector3}
37747
+ */
37748
+ function modelOffsetToWorldDelta(modelRoot, offset) {
37749
+ var _offset$x, _offset$y, _offset$z;
37750
+ var v = offset instanceof THREE__namespace.Vector3 ? offset.clone() : new THREE__namespace.Vector3((_offset$x = offset === null || offset === void 0 ? void 0 : offset.x) !== null && _offset$x !== void 0 ? _offset$x : 0, (_offset$y = offset === null || offset === void 0 ? void 0 : offset.y) !== null && _offset$y !== void 0 ? _offset$y : 0, (_offset$z = offset === null || offset === void 0 ? void 0 : offset.z) !== null && _offset$z !== void 0 ? _offset$z : 0);
37751
+ var q = new THREE__namespace.Quaternion();
37752
+ modelRoot.getWorldQuaternion(q);
37753
+ return v.applyQuaternion(q);
37754
+ }
37755
+
37756
+ /**
37757
+ * Apply a model-root-space translation from a mesh's rest local pose.
37758
+ *
37759
+ * @param {THREE.Object3D} mesh
37760
+ * @param {THREE.Object3D} modelRoot
37761
+ * @param {THREE.Vector3} origLocalPos
37762
+ * @param {{ x?: number, y?: number, z?: number }} modelOffset
37763
+ */
37764
+ function applyModelRootTranslation(mesh, modelRoot, origLocalPos, modelOffset) {
37765
+ if (!mesh || !modelRoot || !origLocalPos) return;
37766
+ mesh.position.copy(origLocalPos);
37767
+ mesh.updateMatrixWorld(true);
37768
+ var origWorldPos = new THREE__namespace.Vector3();
37769
+ mesh.getWorldPosition(origWorldPos);
37770
+ var newWorldPos = origWorldPos.add(modelOffsetToWorldDelta(modelRoot, modelOffset));
37771
+ if (mesh.parent) mesh.parent.worldToLocal(newWorldPos);
37772
+ mesh.position.copy(newWorldPos);
37773
+ }
37774
+
37383
37775
  var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
37384
37776
  function IoBehaviorManager(sceneViewer) {
37385
37777
  var _this;
@@ -37397,12 +37789,11 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
37397
37789
  */
37398
37790
  _this._entries = new Map();
37399
37791
  _this._crossComponentBehaviors = [];
37400
-
37401
- /**
37402
- * Map: `${componentUuid}` → Array<{ id, input, outputs }>
37403
- * Component-level behaviors for intra-component io-device linking
37404
- */
37405
37792
  _this._componentBehaviors = new Map();
37793
+ /** @type {Map<string, string>} parentUuid::attachmentId → parentUuid */
37794
+ _this._attachmentParentMap = new Map();
37795
+ /** @type {Map<string, Object[]>} cache key → data point definitions */
37796
+ _this._dataPointsCache = new Map();
37406
37797
 
37407
37798
  /**
37408
37799
  * Injected by the host application to read and write I/O device state.
@@ -37492,7 +37883,10 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
37492
37883
  }
37493
37884
  if (entries.length) {
37494
37885
  this._entries.set(key, entries);
37495
- // Loaded ${entries.length} animation(s) for attachment "${attachmentId}"
37886
+ if (parentUuid && attachmentId) {
37887
+ this._attachmentParentMap.set(this._key(parentUuid, attachmentId), parentUuid);
37888
+ }
37889
+ this._invalidateDataPointsCache(parentUuid, attachmentId);
37496
37890
  } else {
37497
37891
  console.warn("[IoBehaviorManager] No mesh entries resolved for attachment \"".concat(attachmentId, "\" \u2014 behaviorConfig had ").concat(anims.length, " entries but none matched a mesh"));
37498
37892
  }
@@ -37500,7 +37894,6 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
37500
37894
 
37501
37895
  /**
37502
37896
  * Apply animations triggered by an IO device state change.
37503
- * Should be called in parallel with BehaviorManager.triggerState().
37504
37897
  *
37505
37898
  * @param {string} attachmentId - Raw attachment key (not scoped)
37506
37899
  * @param {string} dataPointId - The data point / state variable id that changed
@@ -37510,70 +37903,36 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
37510
37903
  }, {
37511
37904
  key: "triggerState",
37512
37905
  value: function triggerState(attachmentId, dataPointId, value, parentUuid) {
37513
- // triggerState: ${attachmentId}.${dataPointId} = ${value}
37514
-
37515
- if (parentUuid) {
37516
- var key = this._key(parentUuid, attachmentId);
37517
- var entries = this._entries.get(key);
37518
- if (entries) {
37519
- var _iterator2 = _createForOfIteratorHelper(entries),
37520
- _step2;
37521
- try {
37522
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
37523
- var entry = _step2.value;
37524
- if (entry.anim.stateVariable !== dataPointId) continue;
37525
- this._applyAnimation(entry, value);
37526
- }
37527
- } catch (err) {
37528
- _iterator2.e(err);
37529
- } finally {
37530
- _iterator2.f();
37531
- }
37532
- }
37533
- } else {
37534
- // Fallback when parentUuid is not provided: match by attachmentId suffix or exact key match
37535
- var suffix = "::".concat(attachmentId);
37536
- var _iterator3 = _createForOfIteratorHelper(this._entries.entries()),
37537
- _step3;
37906
+ var _this$_crossComponent;
37907
+ var resolvedParent = parentUuid || this._findComponentByAttachment(attachmentId);
37908
+ if (!resolvedParent) return;
37909
+ var key = this._key(resolvedParent, attachmentId);
37910
+ var entries = this._entries.get(key);
37911
+ if (entries) {
37912
+ var _iterator2 = _createForOfIteratorHelper(entries),
37913
+ _step2;
37538
37914
  try {
37539
- for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
37540
- var _step3$value = _slicedToArray(_step3.value, 2),
37541
- _key2 = _step3$value[0],
37542
- _entries = _step3$value[1];
37543
- if (_key2 === attachmentId || _key2.endsWith(suffix)) {
37544
- var _iterator4 = _createForOfIteratorHelper(_entries),
37545
- _step4;
37546
- try {
37547
- for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
37548
- var _entry = _step4.value;
37549
- if (_entry.anim.stateVariable !== dataPointId) continue;
37550
- this._applyAnimation(_entry, value);
37551
- }
37552
- } catch (err) {
37553
- _iterator4.e(err);
37554
- } finally {
37555
- _iterator4.f();
37556
- }
37557
- }
37915
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
37916
+ var entry = _step2.value;
37917
+ if (entry.anim.stateVariable !== dataPointId) continue;
37918
+ this._applyAnimation(entry, value);
37558
37919
  }
37559
37920
  } catch (err) {
37560
- _iterator3.e(err);
37921
+ _iterator2.e(err);
37561
37922
  } finally {
37562
- _iterator3.f();
37923
+ _iterator2.f();
37563
37924
  }
37564
37925
  }
37565
37926
 
37566
37927
  // Evaluate component-level behaviors (intra-component io-device linking)
37567
- if (parentUuid && this._componentBehaviors.has(parentUuid)) {
37568
- var componentBehaviors = this._componentBehaviors.get(parentUuid);
37569
- // Checking component-level behaviors (count: ${componentBehaviors.length})
37570
- this.triggerCrossComponentBehaviors(componentBehaviors, parentUuid, attachmentId, dataPointId, value);
37928
+ if (this._componentBehaviors.has(resolvedParent)) {
37929
+ var componentBehaviors = this._componentBehaviors.get(resolvedParent);
37930
+ this.triggerCrossComponentBehaviors(componentBehaviors, resolvedParent, attachmentId, dataPointId, value, {
37931
+ sameComponent: true
37932
+ });
37571
37933
  }
37572
-
37573
- // Evaluate cross-component behaviors if any are registered
37574
- // Checking cross-component behaviors (count: ${this._crossComponentBehaviors?.length || 0})
37575
- if (this._crossComponentBehaviors && this._crossComponentBehaviors.length > 0) {
37576
- this.triggerCrossComponentBehaviors(this._crossComponentBehaviors, parentUuid, attachmentId, dataPointId, value);
37934
+ if (((_this$_crossComponent = this._crossComponentBehaviors) === null || _this$_crossComponent === void 0 ? void 0 : _this$_crossComponent.length) > 0) {
37935
+ this.triggerCrossComponentBehaviors(this._crossComponentBehaviors, resolvedParent, attachmentId, dataPointId, value);
37577
37936
  }
37578
37937
  }
37579
37938
 
@@ -37586,9 +37945,8 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
37586
37945
  }, {
37587
37946
  key: "setCrossComponentBehaviors",
37588
37947
  value: function setCrossComponentBehaviors(behaviors) {
37589
- var _this2 = this;
37590
37948
  this._crossComponentBehaviors = (behaviors || []).map(function (b) {
37591
- return _this2._normalizeBehavior(b);
37949
+ return normalizeBehavior(b);
37592
37950
  });
37593
37951
  // Loaded ${this._crossComponentBehaviors.length} cross-component behavior(s)
37594
37952
  }
@@ -37603,72 +37961,17 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
37603
37961
  }, {
37604
37962
  key: "registerComponentBehaviors",
37605
37963
  value: function registerComponentBehaviors(componentUuid, behaviors) {
37606
- var _this3 = this;
37607
- if (!behaviors || behaviors.length === 0) return;
37964
+ if (!behaviors || behaviors.length === 0) {
37965
+ this._componentBehaviors.delete(componentUuid);
37966
+ return;
37967
+ }
37608
37968
  var normalized = behaviors.map(function (b) {
37609
- return _this3._normalizeBehavior(b);
37969
+ return normalizeBehavior(b);
37610
37970
  });
37611
37971
  this._componentBehaviors.set(componentUuid, normalized);
37612
37972
  // Registered ${normalized.length} component-level behavior(s) for component ${componentUuid}
37613
37973
  }
37614
37974
 
37615
- /**
37616
- * Normalize behavior from shorthand to full format.
37617
- * Supports:
37618
- * - input: "attachment.state" → { attachment, state }
37619
- * - outputs: ["attachment.state", ...] → converted to individual behaviors
37620
- *
37621
- * @param {Object} behavior - Raw behavior from scene JSON
37622
- * @returns {Object} Normalized behavior
37623
- */
37624
- }, {
37625
- key: "_normalizeBehavior",
37626
- value: function _normalizeBehavior(behavior) {
37627
- var normalized = _objectSpread2({}, behavior);
37628
-
37629
- // Parse shorthand input: "attachment.state"
37630
- if (typeof behavior.input === 'string') {
37631
- var _behavior$input$split = behavior.input.split('.'),
37632
- _behavior$input$split2 = _slicedToArray(_behavior$input$split, 2),
37633
- attachment = _behavior$input$split2[0],
37634
- state = _behavior$input$split2[1];
37635
- normalized.input = {
37636
- attachment: attachment,
37637
- state: state
37638
- };
37639
- }
37640
-
37641
- // Parse shorthand output/outputs
37642
- if (behavior.outputs) {
37643
- // Multiple outputs - expand to array
37644
- normalized._outputs = behavior.outputs.map(function (out) {
37645
- if (typeof out === 'string') {
37646
- var _out$split = out.split('.'),
37647
- _out$split2 = _slicedToArray(_out$split, 2),
37648
- _attachment = _out$split2[0],
37649
- _state = _out$split2[1];
37650
- return {
37651
- attachment: _attachment,
37652
- state: _state
37653
- };
37654
- }
37655
- return out;
37656
- });
37657
- delete normalized.outputs;
37658
- } else if (typeof behavior.output === 'string') {
37659
- // Single output string
37660
- var _behavior$output$spli = behavior.output.split('.'),
37661
- _behavior$output$spli2 = _slicedToArray(_behavior$output$spli, 2),
37662
- _attachment2 = _behavior$output$spli2[0],
37663
- _state2 = _behavior$output$spli2[1];
37664
- normalized.output = {
37665
- attachment: _attachment2,
37666
- state: _state2
37667
- };
37668
- }
37669
- return normalized;
37670
- }
37671
-
37672
37975
  /**
37673
37976
  * Find the parent component UUID for a given attachment ID.
37674
37977
  * Searches the scene tree for the io-device with this attachment ID,
@@ -37682,12 +37985,10 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
37682
37985
  value: function _findComponentByAttachment(attachmentId) {
37683
37986
  var _this$sceneViewer;
37684
37987
  if (!((_this$sceneViewer = this.sceneViewer) !== null && _this$sceneViewer !== void 0 && _this$sceneViewer.scene)) return null;
37685
- var scene = this.sceneViewer.scene;
37686
37988
  var found = null;
37687
- scene.traverse(function (obj) {
37989
+ this.sceneViewer.scene.traverse(function (obj) {
37688
37990
  var _obj$userData, _obj$userData2;
37689
37991
  if (found) return;
37690
- // Find the io-device object with this attachmentId
37691
37992
  if (((_obj$userData = obj.userData) === null || _obj$userData === void 0 ? void 0 : _obj$userData.objectType) === 'io-device' && ((_obj$userData2 = obj.userData) === null || _obj$userData2 === void 0 ? void 0 : _obj$userData2.attachmentId) === attachmentId) {
37692
37993
  found = obj.userData.parentComponentId;
37693
37994
  }
@@ -37731,20 +38032,21 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
37731
38032
  * @param {string} triggerAttachmentId - Attachment ID of the triggering device
37732
38033
  * @param {string} triggerStateId - The state variable ID that changed
37733
38034
  * @param {*} value - The new state value
38035
+ * @param {{ sameComponent?: boolean }} [options] - Intra-component links share one parent instance
37734
38036
  */
37735
38037
  }, {
37736
38038
  key: "triggerCrossComponentBehaviors",
37737
38039
  value: function triggerCrossComponentBehaviors(behaviors, triggerParentUuid, triggerAttachmentId, triggerStateId, value) {
38040
+ var options = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : {};
37738
38041
  if (!behaviors || !Array.isArray(behaviors)) {
37739
38042
  return;
37740
38043
  }
37741
-
37742
- // Evaluating ${behaviors.length} behavior(s)
37743
- var _iterator5 = _createForOfIteratorHelper(behaviors),
37744
- _step5;
38044
+ var sameComponent = options.sameComponent === true;
38045
+ var _iterator3 = _createForOfIteratorHelper(behaviors),
38046
+ _step3;
37745
38047
  try {
37746
- for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
37747
- var behavior = _step5.value;
38048
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
38049
+ var behavior = _step3.value;
37748
38050
  var input = behavior.input,
37749
38051
  output = behavior.output,
37750
38052
  _outputs = behavior._outputs,
@@ -37753,94 +38055,69 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
37753
38055
  console.warn('[Behavior] Skipping behavior - missing input or output(s):', behavior);
37754
38056
  continue;
37755
38057
  }
37756
-
37757
- // Auto-lookup component if not specified
37758
- var inputComponent = input.component || this._findComponentByAttachment(input.attachment);
37759
- console.log("[Behavior] Checking behavior \"".concat(behavior.id, "\":"), {
37760
- inputMatch: inputComponent === triggerParentUuid,
37761
- attachmentMatch: input.attachment === triggerAttachmentId,
37762
- stateMatch: input.state === triggerStateId,
37763
- expected: {
37764
- component: inputComponent,
37765
- attachment: input.attachment,
37766
- state: input.state
37767
- },
37768
- actual: {
37769
- component: triggerParentUuid,
37770
- attachment: triggerAttachmentId,
37771
- state: triggerStateId
37772
- }
37773
- });
37774
-
37775
- // Verify that the input matches the triggering source
37776
- if (inputComponent === triggerParentUuid && input.attachment === triggerAttachmentId && input.state === triggerStateId) {
38058
+ var inputComponent = input.component || (sameComponent ? triggerParentUuid : this._findComponentByAttachment(input.attachment));
38059
+ var inputMatches = sameComponent ? input.attachment === triggerAttachmentId && input.state === triggerStateId : inputComponent === triggerParentUuid && input.attachment === triggerAttachmentId && input.state === triggerStateId;
38060
+ if (inputMatches) {
37777
38061
  // Behavior "${behavior.id}" matched
37778
38062
 
37779
38063
  // Collect all outputs (single or multiple)
37780
38064
  var outputs = _outputs || (output ? [output] : []);
37781
38065
 
37782
38066
  // Process each output
37783
- var _iterator6 = _createForOfIteratorHelper(outputs),
37784
- _step6;
38067
+ var _iterator4 = _createForOfIteratorHelper(outputs),
38068
+ _step4;
37785
38069
  try {
37786
- for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
37787
- var out = _step6.value;
38070
+ for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
38071
+ var out = _step4.value;
37788
38072
  // NEW: State-to-state pass-through pattern
37789
38073
  if (out.state) {
37790
- // Auto-lookup output component if not specified
37791
- var outputComponent = out.component || this._findComponentByAttachment(out.attachment);
37792
-
37793
- // Direct state mapping without conditions
38074
+ var outputComponent = out.component || (sameComponent ? triggerParentUuid : this._findComponentByAttachment(out.attachment));
37794
38075
  if (this._stateAdapter) {
37795
- // Dispatching state-to-state: ${out.attachment}.${out.state} = ${value}
37796
- this._stateAdapter.setState(out.attachment, out.state, value);
37797
-
37798
- // Trigger animations on the output component
37799
- // triggerState(attachmentId, dataPointId, value, parentUuid)
38076
+ var scopedKey = getScopedAttachmentKey(out.attachment, outputComponent);
38077
+ this._stateAdapter.setState(scopedKey, out.state, value);
37800
38078
  if (outputComponent) {
37801
- // Triggering animations on output component
37802
38079
  this.triggerState(out.attachment, out.state, value, outputComponent);
37803
38080
  } else {
37804
38081
  console.warn("[Behavior] Could not find component for attachment \"".concat(out.attachment, "\""));
37805
38082
  }
37806
38083
  } else {
37807
- console.warn('[Behavior] State adapter not configured for state-to-state behavior');
38084
+ console.warn('[Behavior] State adapter not configured for state-to-state behavior');
37808
38085
  }
37809
38086
  }
37810
38087
  // LEGACY: Mesh-based pattern with conditions
37811
38088
  else if (conditions && out.child) {
37812
38089
  // Using legacy mesh-based pattern with conditions
37813
38090
  // Evaluate conditions
37814
- var _iterator7 = _createForOfIteratorHelper(conditions),
37815
- _step7;
38091
+ var _iterator5 = _createForOfIteratorHelper(conditions),
38092
+ _step5;
37816
38093
  try {
37817
- for (_iterator7.s(); !(_step7 = _iterator7.n()).done;) {
37818
- var condition = _step7.value;
38094
+ for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
38095
+ var condition = _step5.value;
37819
38096
  if (this._evaluateCondition(condition.when, value)) {
37820
38097
  // Apply actions to the target output component/attachment/child mesh
37821
38098
  this._applyCrossComponentActions(out, condition.actions);
37822
38099
  }
37823
38100
  }
37824
38101
  } catch (err) {
37825
- _iterator7.e(err);
38102
+ _iterator5.e(err);
37826
38103
  } finally {
37827
- _iterator7.f();
38104
+ _iterator5.f();
37828
38105
  }
37829
38106
  } else {
37830
38107
  console.warn('[Behavior] Output has neither state nor (child + conditions):', out);
37831
38108
  }
37832
38109
  } // end outputs loop
37833
38110
  } catch (err) {
37834
- _iterator6.e(err);
38111
+ _iterator4.e(err);
37835
38112
  } finally {
37836
- _iterator6.f();
38113
+ _iterator4.f();
37837
38114
  }
37838
38115
  }
37839
38116
  }
37840
38117
  } catch (err) {
37841
- _iterator5.e(err);
38118
+ _iterator3.e(err);
37842
38119
  } finally {
37843
- _iterator5.f();
38120
+ _iterator3.f();
37844
38121
  }
37845
38122
  }
37846
38123
 
@@ -37910,17 +38187,17 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
37910
38187
  }
37911
38188
 
37912
38189
  // 4. Apply actions to targetObj
37913
- var _iterator8 = _createForOfIteratorHelper(actions),
37914
- _step8;
38190
+ var _iterator6 = _createForOfIteratorHelper(actions),
38191
+ _step6;
37915
38192
  try {
37916
- for (_iterator8.s(); !(_step8 = _iterator8.n()).done;) {
37917
- var action = _step8.value;
38193
+ for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
38194
+ var action = _step6.value;
37918
38195
  this._applyCrossComponentAction(targetObj, action);
37919
38196
  }
37920
38197
  } catch (err) {
37921
- _iterator8.e(err);
38198
+ _iterator6.e(err);
37922
38199
  } finally {
37923
- _iterator8.f();
38200
+ _iterator6.f();
37924
38201
  }
37925
38202
  }
37926
38203
 
@@ -38012,8 +38289,12 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
38012
38289
  }, {
38013
38290
  key: "getAnimationDataPoints",
38014
38291
  value: function getAnimationDataPoints(parentUuid, attachmentId) {
38015
- var _this4 = this;
38292
+ var _this2 = this;
38016
38293
  var hitMesh = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
38294
+ var cacheKey = "".concat(this._key(parentUuid, attachmentId), "::").concat((hitMesh === null || hitMesh === void 0 ? void 0 : hitMesh.uuid) || '');
38295
+ if (this._dataPointsCache.has(cacheKey)) {
38296
+ return this._dataPointsCache.get(cacheKey);
38297
+ }
38017
38298
  var key = this._key(parentUuid, attachmentId);
38018
38299
  var entries = this._entries.get(key);
38019
38300
  if (!(entries !== null && entries !== void 0 && entries.length)) return [];
@@ -38024,35 +38305,35 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
38024
38305
  var filtered = entries;
38025
38306
  if (hitMesh) {
38026
38307
  var matching = entries.filter(function (e) {
38027
- return _this4._isMeshOrDescendant(hitMesh, e.mesh);
38308
+ return _this2._isMeshOrDescendant(hitMesh, e.mesh);
38028
38309
  });
38029
38310
  if (matching.length > 0) filtered = matching;
38030
38311
  }
38031
38312
 
38032
38313
  // Collapse multiple mesh entries that share the same stateVariable
38033
38314
  var seen = new Map(); // stateVariable → anim
38034
- var _iterator9 = _createForOfIteratorHelper(filtered),
38035
- _step9;
38315
+ var _iterator7 = _createForOfIteratorHelper(filtered),
38316
+ _step7;
38036
38317
  try {
38037
- for (_iterator9.s(); !(_step9 = _iterator9.n()).done;) {
38038
- var anim = _step9.value.anim;
38318
+ for (_iterator7.s(); !(_step7 = _iterator7.n()).done;) {
38319
+ var anim = _step7.value.anim;
38039
38320
  if (!seen.has(anim.stateVariable)) {
38040
38321
  seen.set(anim.stateVariable, anim);
38041
38322
  }
38042
38323
  }
38043
38324
  } catch (err) {
38044
- _iterator9.e(err);
38325
+ _iterator7.e(err);
38045
38326
  } finally {
38046
- _iterator9.f();
38327
+ _iterator7.f();
38047
38328
  }
38048
38329
  var dps = [];
38049
- var _iterator0 = _createForOfIteratorHelper(seen),
38050
- _step0;
38330
+ var _iterator8 = _createForOfIteratorHelper(seen),
38331
+ _step8;
38051
38332
  try {
38052
- for (_iterator0.s(); !(_step0 = _iterator0.n()).done;) {
38053
- var _step0$value = _slicedToArray(_step0.value, 2),
38054
- stateVar = _step0$value[0],
38055
- _anim = _step0$value[1];
38333
+ for (_iterator8.s(); !(_step8 = _iterator8.n()).done;) {
38334
+ var _step8$value = _slicedToArray(_step8.value, 2),
38335
+ stateVar = _step8$value[0],
38336
+ _anim = _step8$value[1];
38056
38337
  // Normalise stateType from AnimateDevicesDialog variants
38057
38338
  var stateType = void 0;
38058
38339
  var raw = (_anim.stateType || '').toLowerCase();
@@ -38103,10 +38384,11 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
38103
38384
  });
38104
38385
  }
38105
38386
  } catch (err) {
38106
- _iterator0.e(err);
38387
+ _iterator8.e(err);
38107
38388
  } finally {
38108
- _iterator0.f();
38389
+ _iterator8.f();
38109
38390
  }
38391
+ this._dataPointsCache.set(cacheKey, dps);
38110
38392
  return dps;
38111
38393
  }
38112
38394
 
@@ -38140,25 +38422,46 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
38140
38422
  key: "unloadForComponent",
38141
38423
  value: function unloadForComponent(parentUuid) {
38142
38424
  var prefix = "".concat(parentUuid, "::");
38143
- var _iterator1 = _createForOfIteratorHelper(this._entries.keys()),
38144
- _step1;
38145
- try {
38146
- for (_iterator1.s(); !(_step1 = _iterator1.n()).done;) {
38147
- var key = _step1.value;
38148
- if (key.startsWith(prefix)) {
38149
- this._entries.delete(key);
38150
- }
38425
+ for (var _i = 0, _arr = _toConsumableArray(this._entries.keys()); _i < _arr.length; _i++) {
38426
+ var key = _arr[_i];
38427
+ if (key.startsWith(prefix)) {
38428
+ this._attachmentParentMap.delete(key);
38429
+ this._entries.delete(key);
38151
38430
  }
38152
- } catch (err) {
38153
- _iterator1.e(err);
38154
- } finally {
38155
- _iterator1.f();
38156
38431
  }
38432
+ this._componentBehaviors.delete(parentUuid);
38433
+ this._invalidateDataPointsCacheForParent(parentUuid);
38434
+ }
38435
+
38436
+ /**
38437
+ * Remove animation entries for a single attachment on a component instance.
38438
+ */
38439
+ }, {
38440
+ key: "unloadForAttachment",
38441
+ value: function unloadForAttachment(parentUuid, attachmentId) {
38442
+ var key = this._key(parentUuid, attachmentId);
38443
+ this._entries.delete(key);
38444
+ this._attachmentParentMap.delete(key);
38445
+ this._invalidateDataPointsCache(parentUuid, attachmentId);
38446
+ }
38447
+
38448
+ /**
38449
+ * Clear all runtime behavior state when the scene is cleared or replaced.
38450
+ */
38451
+ }, {
38452
+ key: "resetForScene",
38453
+ value: function resetForScene() {
38454
+ this._entries.clear();
38455
+ this._componentBehaviors.clear();
38456
+ this._crossComponentBehaviors = [];
38457
+ this._attachmentParentMap.clear();
38458
+ this._dataPointsCache.clear();
38157
38459
  }
38158
38460
  }, {
38159
38461
  key: "dispose",
38160
38462
  value: function dispose() {
38161
- this._entries.clear();
38463
+ this.resetForScene();
38464
+ this._stateAdapter = null;
38162
38465
  _superPropGet(IoBehaviorManager, "dispose", this, 3)([]);
38163
38466
  }
38164
38467
 
@@ -38225,20 +38528,20 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
38225
38528
  key: "_applyAnimation",
38226
38529
  value: function _applyAnimation(entry, value) {
38227
38530
  var anim = entry.anim,
38228
- mesh = entry.mesh,
38229
- origPos = entry.origPos;
38531
+ mesh = entry.mesh;
38532
+ entry.origPos;
38230
38533
  entry.origRot;
38231
38534
  var viewerMaxDim = entry.viewerMaxDim;
38232
38535
  var mapping = this._resolveMapping(anim, value);
38233
38536
  if (!mapping) return;
38234
38537
  var types = anim.transformTypes || [];
38235
- var _iterator10 = _createForOfIteratorHelper(types),
38236
- _step10;
38538
+ var _iterator9 = _createForOfIteratorHelper(types),
38539
+ _step9;
38237
38540
  try {
38238
- for (_iterator10.s(); !(_step10 = _iterator10.n()).done;) {
38239
- var type = _step10.value;
38541
+ for (_iterator9.s(); !(_step9 = _iterator9.n()).done;) {
38542
+ var type = _step9.value;
38240
38543
  if (type === 'translation') {
38241
- this._applyTranslation(mesh, origPos, mapping.transform);
38544
+ this._applyTranslation(entry, mapping.transform);
38242
38545
  } else if (type === 'rotation') {
38243
38546
  this._applyRotation(entry, anim, mapping.rotationTransform, viewerMaxDim);
38244
38547
  } else if (type === 'color') {
@@ -38246,9 +38549,9 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
38246
38549
  }
38247
38550
  }
38248
38551
  } catch (err) {
38249
- _iterator10.e(err);
38552
+ _iterator9.e(err);
38250
38553
  } finally {
38251
- _iterator10.f();
38554
+ _iterator9.f();
38252
38555
  }
38253
38556
  }
38254
38557
 
@@ -38368,20 +38671,21 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
38368
38671
  // ─────────────────────────────────────────────────────────────────────────
38369
38672
 
38370
38673
  /**
38371
- * Apply a position delta relative to the mesh's original position.
38372
- * @param {THREE.Object3D} mesh
38373
- * @param {THREE.Vector3} origPos
38374
- * @param {{ x, y, z }} transform - Deltas
38674
+ * Apply a translation offset in device-model-root space so every animated mesh
38675
+ * shares the same axis directions regardless of intermediate parent transforms.
38676
+ *
38677
+ * @param {{ mesh, origPos, deviceModelRoot }} entry
38678
+ * @param {{ x, y, z }} transform - Offset in model-root local space
38375
38679
  */
38376
38680
  }, {
38377
38681
  key: "_applyTranslation",
38378
- value: function _applyTranslation(mesh, origPos, transform) {
38379
- var _transform$x, _transform$y, _transform$z;
38682
+ value: function _applyTranslation(entry, transform) {
38380
38683
  if (!transform) return;
38381
- // X and Y are negated to match the sign convention used in the AnimateDevicesDialog
38382
- // preview (_syncViewerTransform negates x and y before calling setMeshPreviewOffset).
38383
- // Z is added directly (no negation) — matching the dialog's z handling.
38384
- mesh.position.set(origPos.x - ((_transform$x = transform.x) !== null && _transform$x !== void 0 ? _transform$x : 0), origPos.y - ((_transform$y = transform.y) !== null && _transform$y !== void 0 ? _transform$y : 0), origPos.z + ((_transform$z = transform.z) !== null && _transform$z !== void 0 ? _transform$z : 0));
38684
+ var mesh = entry.mesh,
38685
+ origPos = entry.origPos,
38686
+ deviceModelRoot = entry.deviceModelRoot;
38687
+ if (!mesh || !origPos || !deviceModelRoot) return;
38688
+ applyModelRootTranslation(mesh, deviceModelRoot, origPos, transform);
38385
38689
  }
38386
38690
 
38387
38691
  /**
@@ -38400,6 +38704,9 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
38400
38704
  origRot = entry.origRot,
38401
38705
  deviceModelRoot = entry.deviceModelRoot;
38402
38706
  if (!mesh || !origPos || !origRot) return null;
38707
+ if (entry._restWorldCache) {
38708
+ return entry._restWorldCache;
38709
+ }
38403
38710
  var savedPos = mesh.position.clone();
38404
38711
  var savedQuat = mesh.quaternion.clone();
38405
38712
  mesh.position.copy(origPos);
@@ -38418,12 +38725,35 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
38418
38725
  }
38419
38726
  mesh.position.copy(savedPos);
38420
38727
  mesh.quaternion.copy(savedQuat);
38421
- return {
38728
+ entry._restWorldCache = {
38422
38729
  origWorldPos: origWorldPos,
38423
38730
  origWorldQuat: origWorldQuat,
38424
38731
  origWorldCenter: origWorldCenter,
38425
38732
  deviceWorldQuat: deviceWorldQuat
38426
38733
  };
38734
+ return entry._restWorldCache;
38735
+ }
38736
+ }, {
38737
+ key: "_invalidateDataPointsCache",
38738
+ value: function _invalidateDataPointsCache(parentUuid, attachmentId) {
38739
+ var prefix = "".concat(this._key(parentUuid, attachmentId), "::");
38740
+ for (var _i2 = 0, _arr2 = _toConsumableArray(this._dataPointsCache.keys()); _i2 < _arr2.length; _i2++) {
38741
+ var key = _arr2[_i2];
38742
+ if (key.startsWith(prefix)) {
38743
+ this._dataPointsCache.delete(key);
38744
+ }
38745
+ }
38746
+ }
38747
+ }, {
38748
+ key: "_invalidateDataPointsCacheForParent",
38749
+ value: function _invalidateDataPointsCacheForParent(parentUuid) {
38750
+ var prefix = "".concat(parentUuid, "::");
38751
+ for (var _i3 = 0, _arr3 = _toConsumableArray(this._dataPointsCache.keys()); _i3 < _arr3.length; _i3++) {
38752
+ var key = _arr3[_i3];
38753
+ if (key.startsWith(prefix)) {
38754
+ this._dataPointsCache.delete(key);
38755
+ }
38756
+ }
38427
38757
  }
38428
38758
 
38429
38759
  /**
@@ -38956,6 +39286,7 @@ var CentralPlantInternals = /*#__PURE__*/function () {
38956
39286
  // Initialize the component tooltip manager (screen-space tooltip on selection)
38957
39287
  this.centralPlant.managers.componentTooltipManager = new ComponentTooltipManager(this.centralPlant.sceneViewer);
38958
39288
  this.centralPlant.sceneViewer.componentTooltipManager = this.centralPlant.managers.componentTooltipManager;
39289
+ this.centralPlant.sceneViewer.managers.componentTooltipManager = this.centralPlant.managers.componentTooltipManager;
38959
39290
  }
38960
39291
  }
38961
39292
 
@@ -39864,37 +40195,14 @@ var CentralPlantInternals = /*#__PURE__*/function () {
39864
40195
  // Add attached IO device models for smart components
39865
40196
  if (componentData.isSmart && componentData.attachedDevices) {
39866
40197
  var _this$centralPlant$sc8;
39867
- attachIODevicesToComponent(componentModel, componentData, modelPreloader, componentId);
39868
-
39869
- // Register behavior configs so IoBehaviorManager can respond to state changes
39870
40198
  var ioBehavMgr = (_this$centralPlant$sc8 = this.centralPlant.sceneViewer) === null || _this$centralPlant$sc8 === void 0 || (_this$centralPlant$sc8 = _this$centralPlant$sc8.managers) === null || _this$centralPlant$sc8 === void 0 ? void 0 : _this$centralPlant$sc8.ioBehaviorManager;
39871
- if (ioBehavMgr) {
39872
- var _loop = function _loop() {
39873
- var _modelPreloader$compo;
39874
- var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2),
39875
- attachmentId = _Object$entries$_i[0],
39876
- attachment = _Object$entries$_i[1];
39877
- var deviceData = (_modelPreloader$compo = modelPreloader.componentDictionary) === null || _modelPreloader$compo === void 0 ? void 0 : _modelPreloader$compo[attachment.deviceId];
39878
- if (!(deviceData !== null && deviceData !== void 0 && deviceData.behaviorConfig)) return 1; // continue
39879
- var deviceRoot = null;
39880
- componentModel.traverse(function (obj) {
39881
- var _obj$userData2;
39882
- if (!deviceRoot && ((_obj$userData2 = obj.userData) === null || _obj$userData2 === void 0 ? void 0 : _obj$userData2.attachmentId) === attachmentId) deviceRoot = obj;
39883
- });
39884
- if (deviceRoot) {
39885
- ioBehavMgr.loadBehaviors(attachmentId, deviceData.behaviorConfig, deviceRoot, componentId);
39886
- }
39887
- };
39888
- for (var _i = 0, _Object$entries = Object.entries(componentData.attachedDevices); _i < _Object$entries.length; _i++) {
39889
- if (_loop()) continue;
39890
- }
39891
-
39892
- // Register component-level behaviors (intra-component io-device linking)
39893
- if (componentData.behaviors && componentData.behaviors.length > 0) {
39894
- // Registering ${componentData.behaviors.length} component-level behavior(s)
39895
- ioBehavMgr.registerComponentBehaviors(componentId, componentData.behaviors);
40199
+ attachIODevicesToComponent(componentModel, componentData, modelPreloader, componentId).then(function () {
40200
+ if (ioBehavMgr) {
40201
+ registerBehaviorsForComponent(ioBehavMgr, componentId, componentData, componentModel, modelPreloader.componentDictionary);
39896
40202
  }
39897
- }
40203
+ }).catch(function (err) {
40204
+ console.error("\u274C Error attaching IO devices for ".concat(libraryId, ":"), err);
40205
+ });
39898
40206
  }
39899
40207
 
39900
40208
  // Notify the component manager about the new component
@@ -40024,8 +40332,8 @@ var CentralPlantInternals = /*#__PURE__*/function () {
40024
40332
  // the component (e.g., dynamically added but not yet synced to sceneData)
40025
40333
  if (connectorIds.size === 0 && threeScene) {
40026
40334
  threeScene.traverse(function (obj) {
40027
- var _obj$userData3, _obj$userData4;
40028
- if ((obj.uuid === resolvedUuid || ((_obj$userData3 = obj.userData) === null || _obj$userData3 === void 0 ? void 0 : _obj$userData3.originalUuid) === resolvedUuid) && ((_obj$userData4 = obj.userData) === null || _obj$userData4 === void 0 ? void 0 : _obj$userData4.objectType) === 'component') {
40335
+ var _obj$userData2, _obj$userData3;
40336
+ if ((obj.uuid === resolvedUuid || ((_obj$userData2 = obj.userData) === null || _obj$userData2 === void 0 ? void 0 : _obj$userData2.originalUuid) === resolvedUuid) && ((_obj$userData3 = obj.userData) === null || _obj$userData3 === void 0 ? void 0 : _obj$userData3.objectType) === 'component') {
40029
40337
  obj.children.forEach(function (child) {
40030
40338
  var _child$userData3;
40031
40339
  if (((_child$userData3 = child.userData) === null || _child$userData3 === void 0 ? void 0 : _child$userData3.objectType) === 'connector') {
@@ -40069,18 +40377,23 @@ var CentralPlantInternals = /*#__PURE__*/function () {
40069
40377
  // Step 5: Remove the component from the Three.js scene
40070
40378
  var success = componentManager.removeComponentFromScene(componentId);
40071
40379
  if (success) {
40380
+ var _this$centralPlant$sc10;
40381
+ var ioBehavMgr = (_this$centralPlant$sc10 = this.centralPlant.sceneViewer) === null || _this$centralPlant$sc10 === void 0 || (_this$centralPlant$sc10 = _this$centralPlant$sc10.managers) === null || _this$centralPlant$sc10 === void 0 ? void 0 : _this$centralPlant$sc10.ioBehaviorManager;
40382
+ if (ioBehavMgr !== null && ioBehavMgr !== void 0 && ioBehavMgr.unloadForComponent) {
40383
+ ioBehavMgr.unloadForComponent(resolvedUuid);
40384
+ }
40072
40385
  // Step 6: Directly remove SEGMENT-* and Gateway objects from Three.js whose
40073
40386
  // pathFrom/pathTo references one of this component's connectors.
40074
40387
  // This is surgical cleanup that works regardless of shouldUpdatePaths.
40075
40388
  if (connectorIds.size > 0 && threeScene) {
40076
40389
  var objectsToRemove = [];
40077
40390
  threeScene.traverse(function (obj) {
40078
- var _obj$uuid, _obj$userData5, _obj$uuid2, _obj$userData6;
40079
- var isComputedSegment = ((_obj$uuid = obj.uuid) === null || _obj$uuid === void 0 ? void 0 : _obj$uuid.startsWith('SEGMENT-')) && ((_obj$userData5 = obj.userData) === null || _obj$userData5 === void 0 ? void 0 : _obj$userData5.isDeclared) !== true;
40080
- var isComputedGateway = ((_obj$uuid2 = obj.uuid) === null || _obj$uuid2 === void 0 ? void 0 : _obj$uuid2.includes('Gateway')) && ((_obj$userData6 = obj.userData) === null || _obj$userData6 === void 0 ? void 0 : _obj$userData6.isDeclared) !== true;
40391
+ var _obj$uuid, _obj$userData4, _obj$uuid2, _obj$userData5;
40392
+ var isComputedSegment = ((_obj$uuid = obj.uuid) === null || _obj$uuid === void 0 ? void 0 : _obj$uuid.startsWith('SEGMENT-')) && ((_obj$userData4 = obj.userData) === null || _obj$userData4 === void 0 ? void 0 : _obj$userData4.isDeclared) !== true;
40393
+ var isComputedGateway = ((_obj$uuid2 = obj.uuid) === null || _obj$uuid2 === void 0 ? void 0 : _obj$uuid2.includes('Gateway')) && ((_obj$userData5 = obj.userData) === null || _obj$userData5 === void 0 ? void 0 : _obj$userData5.isDeclared) !== true;
40081
40394
  if (isComputedSegment || isComputedGateway) {
40082
- var _obj$userData7, _obj$userData8;
40083
- if (connectorIds.has((_obj$userData7 = obj.userData) === null || _obj$userData7 === void 0 ? void 0 : _obj$userData7.pathFrom) || connectorIds.has((_obj$userData8 = obj.userData) === null || _obj$userData8 === void 0 ? void 0 : _obj$userData8.pathTo)) {
40395
+ var _obj$userData6, _obj$userData7;
40396
+ if (connectorIds.has((_obj$userData6 = obj.userData) === null || _obj$userData6 === void 0 ? void 0 : _obj$userData6.pathFrom) || connectorIds.has((_obj$userData7 = obj.userData) === null || _obj$userData7 === void 0 ? void 0 : _obj$userData7.pathTo)) {
40084
40397
  objectsToRemove.push(obj);
40085
40398
  }
40086
40399
  }
@@ -40169,6 +40482,149 @@ var CentralPlantInternals = /*#__PURE__*/function () {
40169
40482
  }]);
40170
40483
  }();
40171
40484
 
40485
+ /**
40486
+ * Scene-level behavior helpers: scan endpoints, register behaviors, cross-component links.
40487
+ */
40488
+ function getComponentDictionary(centralPlant) {
40489
+ var _centralPlant$getUtil, _centralPlant$manager;
40490
+ return (centralPlant === null || centralPlant === void 0 || (_centralPlant$getUtil = centralPlant.getUtility) === null || _centralPlant$getUtil === void 0 || (_centralPlant$getUtil = _centralPlant$getUtil.call(centralPlant, 'modelPreloader')) === null || _centralPlant$getUtil === void 0 ? void 0 : _centralPlant$getUtil.componentDictionary) || (centralPlant === null || centralPlant === void 0 || (_centralPlant$manager = centralPlant.managers) === null || _centralPlant$manager === void 0 || (_centralPlant$manager = _centralPlant$manager.componentDataManager) === null || _centralPlant$manager === void 0 ? void 0 : _centralPlant$manager.componentDictionary) || null;
40491
+ }
40492
+ function statesFromBehaviorConfig(device) {
40493
+ if (!(device !== null && device !== void 0 && device.behaviorConfig) || !Array.isArray(device.behaviorConfig)) return [];
40494
+ return device.behaviorConfig.map(function (b) {
40495
+ return b.stateVariable;
40496
+ }).filter(Boolean).filter(function (v, i, arr) {
40497
+ return arr.indexOf(v) === i;
40498
+ });
40499
+ }
40500
+ function resolveStatesForDevice(ioObj, deviceId, componentDictionary) {
40501
+ var _ioObj$userData;
40502
+ var device = componentDictionary === null || componentDictionary === void 0 ? void 0 : componentDictionary[deviceId];
40503
+ var fromBehaviorConfig = statesFromBehaviorConfig(device);
40504
+ if (fromBehaviorConfig.length) return fromBehaviorConfig;
40505
+ return (((_ioObj$userData = ioObj.userData) === null || _ioObj$userData === void 0 ? void 0 : _ioObj$userData.dataPoints) || []).map(function (dp) {
40506
+ return dp.id || dp.name;
40507
+ }).filter(Boolean).filter(function (v, i, arr) {
40508
+ return arr.indexOf(v) === i;
40509
+ });
40510
+ }
40511
+
40512
+ /**
40513
+ * Scan the live Three.js scene for smart-component io-device endpoints.
40514
+ */
40515
+ function scanSceneIoEndpoints(centralPlant) {
40516
+ var _centralPlant$sceneVi;
40517
+ var scene = centralPlant === null || centralPlant === void 0 || (_centralPlant$sceneVi = centralPlant.sceneViewer) === null || _centralPlant$sceneVi === void 0 ? void 0 : _centralPlant$sceneVi.scene;
40518
+ if (!scene) return [];
40519
+ var dict = getComponentDictionary(centralPlant);
40520
+ var endpoints = [];
40521
+ scene.traverse(function (obj) {
40522
+ var _obj$userData, _parent$userData, _parent$userData2;
40523
+ if (((_obj$userData = obj.userData) === null || _obj$userData === void 0 ? void 0 : _obj$userData.objectType) !== 'io-device') return;
40524
+ var parent = obj.parent;
40525
+ var parentUuid = obj.userData.parentComponentId || (parent === null || parent === void 0 ? void 0 : parent.uuid) || '';
40526
+ var parentName = (parent === null || parent === void 0 || (_parent$userData = parent.userData) === null || _parent$userData === void 0 ? void 0 : _parent$userData.hardcodedUuid) || (parent === null || parent === void 0 ? void 0 : parent.name) || parentUuid;
40527
+ var libraryId = (parent === null || parent === void 0 || (_parent$userData2 = parent.userData) === null || _parent$userData2 === void 0 ? void 0 : _parent$userData2.libraryId) || '';
40528
+ var attachmentId = obj.userData.attachmentId || obj.name || obj.uuid;
40529
+ var deviceId = obj.userData.deviceId || '';
40530
+ endpoints.push({
40531
+ key: getScopedAttachmentKey(attachmentId, parentUuid),
40532
+ parentUuid: parentUuid,
40533
+ parentName: parentName,
40534
+ libraryId: libraryId,
40535
+ attachmentId: attachmentId,
40536
+ attachmentLabel: obj.userData.attachmentLabel || attachmentId,
40537
+ deviceId: deviceId,
40538
+ states: resolveStatesForDevice(obj, deviceId, dict)
40539
+ });
40540
+ });
40541
+ return endpoints;
40542
+ }
40543
+
40544
+ /**
40545
+ * Load cross-component behaviors from scene data mirrors.
40546
+ */
40547
+ function loadCrossComponentBehaviors(centralPlant) {
40548
+ var _centralPlant$importe, _centralPlant$sceneVi2;
40549
+ return (centralPlant === null || centralPlant === void 0 || (_centralPlant$importe = centralPlant.importedSceneData) === null || _centralPlant$importe === void 0 ? void 0 : _centralPlant$importe.behaviors) || (centralPlant === null || centralPlant === void 0 || (_centralPlant$sceneVi2 = centralPlant.sceneViewer) === null || _centralPlant$sceneVi2 === void 0 || (_centralPlant$sceneVi2 = _centralPlant$sceneVi2.currentSceneData) === null || _centralPlant$sceneVi2 === void 0 ? void 0 : _centralPlant$sceneVi2.behaviors) || [];
40550
+ }
40551
+
40552
+ /**
40553
+ * Persist cross-component behaviors to scene data mirrors and the runtime manager.
40554
+ */
40555
+ function applyCrossComponentBehaviors(centralPlant, behaviors) {
40556
+ var normalized = behaviors || [];
40557
+ if (centralPlant.importedSceneData) {
40558
+ if (normalized.length) {
40559
+ centralPlant.importedSceneData.behaviors = normalized;
40560
+ } else {
40561
+ delete centralPlant.importedSceneData.behaviors;
40562
+ }
40563
+ }
40564
+ var sceneViewer = centralPlant.sceneViewer;
40565
+ if (sceneViewer && !sceneViewer.currentSceneData) {
40566
+ var _centralPlant$getConn;
40567
+ sceneViewer.currentSceneData = {
40568
+ version: '2.3',
40569
+ connections: ((_centralPlant$getConn = centralPlant.getConnections) === null || _centralPlant$getConn === void 0 ? void 0 : _centralPlant$getConn.call(centralPlant)) || [],
40570
+ scene: {
40571
+ children: []
40572
+ }
40573
+ };
40574
+ }
40575
+ var currentSceneData = sceneViewer === null || sceneViewer === void 0 ? void 0 : sceneViewer.currentSceneData;
40576
+ if (currentSceneData) {
40577
+ if (normalized.length) {
40578
+ currentSceneData.behaviors = normalized;
40579
+ } else {
40580
+ delete currentSceneData.behaviors;
40581
+ }
40582
+ }
40583
+ var ioBehavMgr = getIoBehaviorManager(sceneViewer);
40584
+ if (ioBehavMgr) {
40585
+ ioBehavMgr.setCrossComponentBehaviors(normalized);
40586
+ }
40587
+ }
40588
+
40589
+ /**
40590
+ * Re-register intra-component behaviors on all scene instances of a smart component.
40591
+ */
40592
+ function refreshSceneIntraBehaviors(centralPlant, libraryId, behaviors) {
40593
+ var _centralPlant$sceneVi3;
40594
+ var ioBehavMgr = getIoBehaviorManager(centralPlant === null || centralPlant === void 0 ? void 0 : centralPlant.sceneViewer);
40595
+ var scene = centralPlant === null || centralPlant === void 0 || (_centralPlant$sceneVi3 = centralPlant.sceneViewer) === null || _centralPlant$sceneVi3 === void 0 ? void 0 : _centralPlant$sceneVi3.scene;
40596
+ if (!ioBehavMgr || !scene || !libraryId) return;
40597
+ scene.traverse(function (obj) {
40598
+ var _obj$userData2, _obj$userData3;
40599
+ if (((_obj$userData2 = obj.userData) === null || _obj$userData2 === void 0 ? void 0 : _obj$userData2.objectType) === 'component' && ((_obj$userData3 = obj.userData) === null || _obj$userData3 === void 0 ? void 0 : _obj$userData3.libraryId) === libraryId) {
40600
+ ioBehavMgr.registerComponentBehaviors(obj.uuid, behaviors || []);
40601
+ }
40602
+ });
40603
+ }
40604
+
40605
+ /**
40606
+ * Re-register intra- and cross-component behaviors for all instances in the live scene.
40607
+ */
40608
+ function reregisterSceneBehaviors(centralPlant) {
40609
+ var _centralPlant$sceneVi4;
40610
+ var ioBehavMgr = getIoBehaviorManager(centralPlant === null || centralPlant === void 0 ? void 0 : centralPlant.sceneViewer);
40611
+ var scene = centralPlant === null || centralPlant === void 0 || (_centralPlant$sceneVi4 = centralPlant.sceneViewer) === null || _centralPlant$sceneVi4 === void 0 ? void 0 : _centralPlant$sceneVi4.scene;
40612
+ var dict = getComponentDictionary(centralPlant);
40613
+ if (!ioBehavMgr || !scene) return;
40614
+ var crossBehaviors = loadCrossComponentBehaviors(centralPlant);
40615
+ ioBehavMgr.setCrossComponentBehaviors(crossBehaviors);
40616
+ if (!dict) return;
40617
+ scene.traverse(function (obj) {
40618
+ var _obj$userData4, _obj$userData5;
40619
+ if (((_obj$userData4 = obj.userData) === null || _obj$userData4 === void 0 ? void 0 : _obj$userData4.objectType) !== 'component') return;
40620
+ var libraryId = (_obj$userData5 = obj.userData) === null || _obj$userData5 === void 0 ? void 0 : _obj$userData5.libraryId;
40621
+ if (!libraryId) return;
40622
+ var componentData = dict[libraryId];
40623
+ if (!componentData) return;
40624
+ registerBehaviorsForComponent(ioBehavMgr, obj.uuid, componentData, obj, dict);
40625
+ });
40626
+ }
40627
+
40172
40628
  // ─────────────────────────────────────────────────────────────────────────────
40173
40629
  // Flow-direction compatibility helper (module-level, no class dependency)
40174
40630
  // ─────────────────────────────────────────────────────────────────────────────
@@ -40195,7 +40651,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
40195
40651
  * Initialize the CentralPlant manager
40196
40652
  *
40197
40653
  * @constructor
40198
- * @version 0.3.38
40654
+ * @version 0.3.39
40199
40655
  * @updated 2025-10-22
40200
40656
  *
40201
40657
  * @description Creates a new CentralPlant instance and initializes internal managers and utilities.
@@ -40369,8 +40825,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
40369
40825
  key: "setImportedSceneData",
40370
40826
  value: (function () {
40371
40827
  var _setImportedSceneData = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2(sceneData) {
40372
- var _this$importedSceneDa, _this$importedSceneDa2, _this$importedSceneDa3, _this$sceneViewer;
40373
- var ioBehavMgr;
40828
+ var _this$importedSceneDa, _this$importedSceneDa2, _this$importedSceneDa3;
40374
40829
  return _regenerator().w(function (_context2) {
40375
40830
  while (1) switch (_context2.n) {
40376
40831
  case 0:
@@ -40387,14 +40842,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
40387
40842
 
40388
40843
  // Scene behaviors loaded
40389
40844
 
40390
- // Register behaviors with IoBehaviorManager
40391
- ioBehavMgr = (_this$sceneViewer = this.sceneViewer) === null || _this$sceneViewer === void 0 || (_this$sceneViewer = _this$sceneViewer.managers) === null || _this$sceneViewer === void 0 ? void 0 : _this$sceneViewer.ioBehaviorManager;
40392
- if (ioBehavMgr) {
40393
- // Setting cross-component behaviors
40394
- ioBehavMgr.setCrossComponentBehaviors(this.importedSceneData.behaviors || []);
40395
- } else {
40396
- console.warn('[Behavior] ioBehaviorManager not available!');
40397
- }
40845
+ // Cross-component behaviors are registered by sceneOperationsManager.loadSceneData()
40398
40846
 
40399
40847
  // Reset component counter based on imported components to avoid ID conflicts
40400
40848
  this.internals.resetComponentCounter();
@@ -40575,8 +41023,8 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
40575
41023
  }, {
40576
41024
  key: "selectObject",
40577
41025
  value: function selectObject(objectOrId) {
40578
- var _this$sceneViewer2, _object;
40579
- if (!((_this$sceneViewer2 = this.sceneViewer) !== null && _this$sceneViewer2 !== void 0 && _this$sceneViewer2.transformManager)) {
41026
+ var _this$sceneViewer, _object;
41027
+ if (!((_this$sceneViewer = this.sceneViewer) !== null && _this$sceneViewer !== void 0 && _this$sceneViewer.transformManager)) {
40580
41028
  console.warn('⚠️ Transform manager not initialized');
40581
41029
  return false;
40582
41030
  }
@@ -40622,8 +41070,8 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
40622
41070
  }, {
40623
41071
  key: "toggleObject",
40624
41072
  value: function toggleObject(objectOrId) {
40625
- var _this$sceneViewer3, _object2;
40626
- if (!((_this$sceneViewer3 = this.sceneViewer) !== null && _this$sceneViewer3 !== void 0 && _this$sceneViewer3.transformManager)) {
41073
+ var _this$sceneViewer2, _object2;
41074
+ if (!((_this$sceneViewer2 = this.sceneViewer) !== null && _this$sceneViewer2 !== void 0 && _this$sceneViewer2.transformManager)) {
40627
41075
  console.warn('⚠️ Transform manager not initialized');
40628
41076
  return false;
40629
41077
  }
@@ -40659,8 +41107,8 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
40659
41107
  }, {
40660
41108
  key: "deselectObject",
40661
41109
  value: function deselectObject() {
40662
- var _this$sceneViewer4;
40663
- if (!((_this$sceneViewer4 = this.sceneViewer) !== null && _this$sceneViewer4 !== void 0 && _this$sceneViewer4.transformManager)) {
41110
+ var _this$sceneViewer3;
41111
+ if (!((_this$sceneViewer3 = this.sceneViewer) !== null && _this$sceneViewer3 !== void 0 && _this$sceneViewer3.transformManager)) {
40664
41112
  console.warn('⚠️ Transform manager not initialized');
40665
41113
  return false;
40666
41114
  }
@@ -41240,68 +41688,70 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
41240
41688
  }
41241
41689
 
41242
41690
  /**
41243
- * Set the state of an I/O device instance in the Three.js scene.
41244
- *
41245
- * This is the primary public API for driving IO device state changes from
41246
- * external code (real-time data feeds, AI agents, automated tests, etc.).
41247
- * It performs three coordinated actions in order:
41248
- * 1. Persists the new value through the state adapter (Vuex in sandbox,
41249
- * or any custom adapter injected via componentTooltipManager.configure())
41250
- * so that the host application's store stays consistent.
41251
- * 2. Evaluates all behaviors whose input matches this (attachmentId, stateId)
41252
- * pair and applies the resulting property changes to Three.js meshes.
41253
- * 3. Emits an 'io-device-state-changed' event on the sceneViewer so that
41254
- * host applications without a Vuex store (e.g. cp3d-viewer) can react.
41255
- *
41256
- * @param {string} attachmentId - The attachment ID of the IO device (matches
41257
- * the `attachmentId` stored in the Three.js object's userData)
41258
- * @param {string} stateId - The data-point / state ID on the device (e.g. 'power', 'level')
41259
- * @param {*} value - The new state value (boolean, number, string, etc.)
41260
- * @param {string} [parentUuid] - UUID of the parent component instance.
41261
- * Required when multiple instances of the same smart component share the
41262
- * same attachmentId — prevents cross-instance state bleed.
41263
- * @returns {boolean} True if the behavior system was reached; false if unavailable.
41264
- * @example
41265
- * // Toggle a push-button on a specific pump instance
41266
- * centralPlant.setIoDeviceState('pump-push-button-01', 'power', true, pumpUuid)
41267
- *
41268
- * // Drive an analog level sensor (no parentUuid needed when only one instance)
41269
- * centralPlant.setIoDeviceState('chiller-level-sensor-01', 'level', 0.75)
41691
+ * Internal single path for I/O state changes: persist, animate, link, emit.
41692
+ * @private
41270
41693
  */
41271
41694
  }, {
41272
- key: "setIoDeviceState",
41273
- value: function setIoDeviceState(attachmentId, stateId, value, parentUuid) {
41274
- var _this$sceneViewer5, _this$sceneViewer6, _this$sceneViewer7;
41275
- // 1. Persist via state adapter if one has been configured
41276
- var stateAdapter = (_this$sceneViewer5 = this.sceneViewer) === null || _this$sceneViewer5 === void 0 || (_this$sceneViewer5 = _this$sceneViewer5.managers) === null || _this$sceneViewer5 === void 0 || (_this$sceneViewer5 = _this$sceneViewer5.componentTooltipManager) === null || _this$sceneViewer5 === void 0 ? void 0 : _this$sceneViewer5._stateAdapter;
41695
+ key: "_dispatchIoState",
41696
+ value: function _dispatchIoState(attachmentId, stateId, value, parentUuid) {
41697
+ var _this$managers, _this$sceneViewer4, _this$sceneViewer5, _this$managers2, _this$sceneViewer6, _this$sceneViewer7, _this$sceneViewer8;
41698
+ var tooltipMgr = ((_this$managers = this.managers) === null || _this$managers === void 0 ? void 0 : _this$managers.componentTooltipManager) || ((_this$sceneViewer4 = this.sceneViewer) === null || _this$sceneViewer4 === void 0 ? void 0 : _this$sceneViewer4.componentTooltipManager) || ((_this$sceneViewer5 = this.sceneViewer) === null || _this$sceneViewer5 === void 0 || (_this$sceneViewer5 = _this$sceneViewer5.managers) === null || _this$sceneViewer5 === void 0 ? void 0 : _this$sceneViewer5.componentTooltipManager);
41699
+ var stateAdapter = tooltipMgr === null || tooltipMgr === void 0 ? void 0 : tooltipMgr._stateAdapter;
41277
41700
  if (stateAdapter !== null && stateAdapter !== void 0 && stateAdapter.setState) {
41278
- var scopedKey = parentUuid ? "".concat(parentUuid, "::").concat(attachmentId) : attachmentId;
41701
+ var scopedKey = getScopedAttachmentKey(attachmentId, parentUuid);
41279
41702
  try {
41280
41703
  stateAdapter.setState(scopedKey, stateId, value);
41281
41704
  } catch (err) {
41282
- console.warn('⚠️ setIoDeviceState(): stateAdapter.setState() threw:', err);
41705
+ console.warn('⚠️ _dispatchIoState(): stateAdapter.setState() threw:', err);
41283
41706
  }
41284
41707
  }
41285
-
41286
- // 2. Apply io-behavior changes
41287
- var ioBehavMgr = (_this$sceneViewer6 = this.sceneViewer) === null || _this$sceneViewer6 === void 0 || (_this$sceneViewer6 = _this$sceneViewer6.managers) === null || _this$sceneViewer6 === void 0 ? void 0 : _this$sceneViewer6.ioBehaviorManager;
41708
+ var ioBehavMgr = ((_this$managers2 = this.managers) === null || _this$managers2 === void 0 ? void 0 : _this$managers2.ioBehaviorManager) || ((_this$sceneViewer6 = this.sceneViewer) === null || _this$sceneViewer6 === void 0 || (_this$sceneViewer6 = _this$sceneViewer6.managers) === null || _this$sceneViewer6 === void 0 ? void 0 : _this$sceneViewer6.ioBehaviorManager) || ((_this$sceneViewer7 = this.sceneViewer) === null || _this$sceneViewer7 === void 0 ? void 0 : _this$sceneViewer7.ioBehaviorManager);
41288
41709
  if (ioBehavMgr) {
41289
- var _this$importedSceneDa4;
41290
41710
  ioBehavMgr.triggerState(attachmentId, stateId, value, parentUuid);
41291
-
41292
- // Evaluate cross-component behaviors if they exist in the imported scene
41293
- if ((_this$importedSceneDa4 = this.importedSceneData) !== null && _this$importedSceneDa4 !== void 0 && _this$importedSceneDa4.behaviors) {
41294
- ioBehavMgr.triggerCrossComponentBehaviors(this.importedSceneData.behaviors, parentUuid, attachmentId, stateId, value);
41295
- }
41296
41711
  }
41297
-
41298
- // 3. Emit event for host apps that don't use the state adapter (e.g. cp3d-viewer)
41299
- (_this$sceneViewer7 = this.sceneViewer) === null || _this$sceneViewer7 === void 0 || _this$sceneViewer7.emit('io-device-state-changed', {
41712
+ (_this$sceneViewer8 = this.sceneViewer) === null || _this$sceneViewer8 === void 0 || _this$sceneViewer8.emit('io-device-state-changed', {
41300
41713
  attachmentId: attachmentId,
41301
41714
  stateId: stateId,
41302
41715
  value: value,
41303
41716
  parentUuid: parentUuid || null
41304
41717
  });
41718
+ }
41719
+
41720
+ /**
41721
+ * Configure the state adapter on both tooltip and behavior managers.
41722
+ * @param {{ getState: Function, setState: Function }} stateAdapter
41723
+ */
41724
+ }, {
41725
+ key: "configureStateAdapter",
41726
+ value: function configureStateAdapter(stateAdapter) {
41727
+ var _this$managers3, _this$sceneViewer9, _this$managers4, _this$sceneViewer1, _this$sceneViewer10;
41728
+ var tooltipMgr = ((_this$managers3 = this.managers) === null || _this$managers3 === void 0 ? void 0 : _this$managers3.componentTooltipManager) || ((_this$sceneViewer9 = this.sceneViewer) === null || _this$sceneViewer9 === void 0 ? void 0 : _this$sceneViewer9.componentTooltipManager);
41729
+ if (tooltipMgr !== null && tooltipMgr !== void 0 && tooltipMgr.configure) {
41730
+ var _this$sceneViewer0;
41731
+ tooltipMgr.configure(stateAdapter);
41732
+ if ((_this$sceneViewer0 = this.sceneViewer) !== null && _this$sceneViewer0 !== void 0 && _this$sceneViewer0.managers) {
41733
+ this.sceneViewer.managers.componentTooltipManager = tooltipMgr;
41734
+ }
41735
+ }
41736
+ var ioBehavMgr = ((_this$managers4 = this.managers) === null || _this$managers4 === void 0 ? void 0 : _this$managers4.ioBehaviorManager) || ((_this$sceneViewer1 = this.sceneViewer) === null || _this$sceneViewer1 === void 0 || (_this$sceneViewer1 = _this$sceneViewer1.managers) === null || _this$sceneViewer1 === void 0 ? void 0 : _this$sceneViewer1.ioBehaviorManager) || ((_this$sceneViewer10 = this.sceneViewer) === null || _this$sceneViewer10 === void 0 ? void 0 : _this$sceneViewer10.ioBehaviorManager);
41737
+ if (ioBehavMgr !== null && ioBehavMgr !== void 0 && ioBehavMgr.configure) {
41738
+ ioBehavMgr.configure(stateAdapter);
41739
+ }
41740
+ }
41741
+
41742
+ /**
41743
+ * Set the state of an I/O device instance in the Three.js scene.
41744
+ *
41745
+ * @param {string} attachmentId - IO device attachment ID
41746
+ * @param {string} stateId - Data-point / state ID (e.g. 'power', 'level')
41747
+ * @param {*} value - New state value
41748
+ * @param {string} [parentUuid] - Parent component instance UUID
41749
+ * @returns {boolean}
41750
+ */
41751
+ }, {
41752
+ key: "setIoDeviceState",
41753
+ value: function setIoDeviceState(attachmentId, stateId, value, parentUuid) {
41754
+ this._dispatchIoState(attachmentId, stateId, value, parentUuid);
41305
41755
  return true;
41306
41756
  }
41307
41757
 
@@ -41318,8 +41768,8 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
41318
41768
  }, {
41319
41769
  key: "getSceneAttachments",
41320
41770
  value: function getSceneAttachments() {
41321
- var _this$sceneViewer8;
41322
- var scene = (_this$sceneViewer8 = this.sceneViewer) === null || _this$sceneViewer8 === void 0 ? void 0 : _this$sceneViewer8.scene;
41771
+ var _this$sceneViewer11;
41772
+ var scene = (_this$sceneViewer11 = this.sceneViewer) === null || _this$sceneViewer11 === void 0 ? void 0 : _this$sceneViewer11.scene;
41323
41773
  if (!scene) return [];
41324
41774
  var results = [];
41325
41775
  scene.traverse(function (obj) {
@@ -42027,6 +42477,58 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
42027
42477
  }
42028
42478
  return extendComponentDictionary;
42029
42479
  }()
42480
+ /**
42481
+ * Merge mesh animation configs into the component dictionary.
42482
+ * @param {Object<string, { behaviorConfig?: Array, meshNameMap?: Object }>} configsByAssetId
42483
+ * @returns {number} Count of dictionary entries updated
42484
+ */
42485
+ )
42486
+ }, {
42487
+ key: "mergeBehaviorConfigsIntoDictionary",
42488
+ value: function mergeBehaviorConfigsIntoDictionary(configsByAssetId) {
42489
+ if (!this.managers.componentDataManager) {
42490
+ console.warn('⚠️ mergeBehaviorConfigsIntoDictionary(): Component data manager not available');
42491
+ return 0;
42492
+ }
42493
+ return this.managers.componentDataManager.mergeBehaviorConfigsIntoDictionary(configsByAssetId);
42494
+ }
42495
+
42496
+ /**
42497
+ * Scan live scene for I/O device endpoints (for behavior authoring UIs).
42498
+ */
42499
+ }, {
42500
+ key: "scanSceneIoEndpoints",
42501
+ value: function scanSceneIoEndpoints$1() {
42502
+ return scanSceneIoEndpoints(this);
42503
+ }
42504
+
42505
+ /**
42506
+ * Apply cross-component behaviors to scene data and the runtime manager.
42507
+ */
42508
+ }, {
42509
+ key: "setSceneBehaviors",
42510
+ value: function setSceneBehaviors(behaviors) {
42511
+ applyCrossComponentBehaviors(this, behaviors);
42512
+ }
42513
+
42514
+ /**
42515
+ * Read cross-component behaviors from the current scene data mirrors.
42516
+ */
42517
+ }, {
42518
+ key: "getSceneBehaviors",
42519
+ value: function getSceneBehaviors() {
42520
+ return loadCrossComponentBehaviors(this);
42521
+ }
42522
+
42523
+ /**
42524
+ * Re-register all intra- and cross-component behaviors on placed instances.
42525
+ */
42526
+ }, {
42527
+ key: "reregisterSceneBehaviors",
42528
+ value: function reregisterSceneBehaviors$1() {
42529
+ reregisterSceneBehaviors(this);
42530
+ }
42531
+
42030
42532
  /**
42031
42533
  * Remove S3 components from component dictionary
42032
42534
  * @returns {Promise<boolean>} True if components were removed successfully, false otherwise
@@ -42039,7 +42541,6 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
42039
42541
  * console.log('S3 components removed');
42040
42542
  * }
42041
42543
  */
42042
- )
42043
42544
  }, {
42044
42545
  key: "removeS3Components",
42045
42546
  value: (function () {
@@ -42946,9 +43447,9 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
42946
43447
  }, {
42947
43448
  key: "clearScene",
42948
43449
  value: function clearScene() {
42949
- var _this$sceneViewer9;
43450
+ var _this$sceneViewer12;
42950
43451
  this.importedSceneData = null;
42951
- var ioBehavMgr = (_this$sceneViewer9 = this.sceneViewer) === null || _this$sceneViewer9 === void 0 || (_this$sceneViewer9 = _this$sceneViewer9.managers) === null || _this$sceneViewer9 === void 0 ? void 0 : _this$sceneViewer9.ioBehaviorManager;
43452
+ var ioBehavMgr = (_this$sceneViewer12 = this.sceneViewer) === null || _this$sceneViewer12 === void 0 || (_this$sceneViewer12 = _this$sceneViewer12.managers) === null || _this$sceneViewer12 === void 0 ? void 0 : _this$sceneViewer12.ioBehaviorManager;
42952
43453
  if (ioBehavMgr) {
42953
43454
  ioBehavMgr.setCrossComponentBehaviors([]);
42954
43455
  }
@@ -48827,6 +49328,9 @@ exports.SceneOperationsManager = SceneOperationsManager;
48827
49328
  exports.SceneTooltipsManager = SceneTooltipsManager;
48828
49329
  exports.SnapshotManager = SnapshotManager;
48829
49330
  exports.ThreeJSResourceManager = ThreeJSResourceManager;
49331
+ exports.applyCrossComponentBehaviors = applyCrossComponentBehaviors;
49332
+ exports.buildCrossBehavior = buildCrossBehavior;
49333
+ exports.buildIntraBehavior = buildIntraBehavior;
48830
49334
  exports.cacheJsonData = cacheJsonData;
48831
49335
  exports.cacheManager = cacheManager;
48832
49336
  exports.cleanExpiredCache = cleanExpiredCache;
@@ -48852,8 +49356,10 @@ exports.getCurrentCacheName = getCurrentCacheName;
48852
49356
  exports.getGlobalCacheStats = getGlobalCacheStats;
48853
49357
  exports.getGlobalOnlyCacheStats = getGlobalOnlyCacheStats;
48854
49358
  exports.getHardcodedUuid = getHardcodedUuid;
49359
+ exports.getIoBehaviorManager = getIoBehaviorManager;
48855
49360
  exports.getObjectTypeName = getObjectTypeName;
48856
49361
  exports.getObjectsByType = getObjectsByType;
49362
+ exports.getScopedAttachmentKey = getScopedAttachmentKey;
48857
49363
  exports.getThumbnailKey = getThumbnailKey;
48858
49364
  exports.getUserOnlyCacheStats = getUserOnlyCacheStats;
48859
49365
  exports.isCached = isCached;
@@ -48865,17 +49371,27 @@ exports.isExportable = isExportable;
48865
49371
  exports.isGateway = isGateway;
48866
49372
  exports.isSegment = isSegment;
48867
49373
  exports.isThumbnailCached = isThumbnailCached;
49374
+ exports.loadCrossComponentBehaviors = loadCrossComponentBehaviors;
48868
49375
  exports.loadTextureSetAndCreateMaterial = loadTextureSetAndCreateMaterial;
48869
49376
  exports.markAsComputed = markAsComputed;
48870
49377
  exports.markAsDeclared = markAsDeclared;
48871
49378
  exports.measureS3Transfer = measureS3Transfer;
48872
49379
  exports.modelPreloader = modelPreloader;
49380
+ exports.normalizeBehavior = normalizeBehavior;
49381
+ exports.parseCrossBehavior = parseCrossBehavior;
49382
+ exports.parseIntraBehavior = parseIntraBehavior;
48873
49383
  exports.preloadLocalFiles = preloadLocalFiles;
48874
49384
  exports.preloadS3Objects = preloadS3Objects;
49385
+ exports.refreshSceneIntraBehaviors = refreshSceneIntraBehaviors;
49386
+ exports.registerBehaviorsForComponent = registerBehaviorsForComponent;
49387
+ exports.reloadBehaviorsForDeviceAsset = reloadBehaviorsForDeviceAsset;
48875
49388
  exports.removeCachedJsonData = removeCachedJsonData;
49389
+ exports.reregisterSceneBehaviors = reregisterSceneBehaviors;
48876
49390
  exports.resetCacheIdentity = resetCacheIdentity;
48877
49391
  exports.resetGlobalCacheStats = resetGlobalCacheStats;
49392
+ exports.resolveDataPoints = resolveDataPoints;
48878
49393
  exports.s3MetadataCache = s3MetadataCache;
49394
+ exports.scanSceneIoEndpoints = scanSceneIoEndpoints;
48879
49395
  exports.sceneViewer = sceneViewer;
48880
49396
  exports.shouldRemoveOnRegeneration = shouldRemoveOnRegeneration;
48881
49397
  exports.switchCachePartition = switchCachePartition;