@2112-lab/central-plant 0.3.38 → 0.3.41

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 +1096 -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 +23 -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 +82 -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 +5 -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 +56 -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 +186 -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,257 @@ 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
+
37775
+ /**
37776
+ * Apply a model-root-space translation from a cached rest world position (preview).
37777
+ *
37778
+ * @param {THREE.Object3D} mesh
37779
+ * @param {THREE.Object3D} modelRoot
37780
+ * @param {THREE.Vector3} baseWorldPos
37781
+ * @param {{ x?: number, y?: number, z?: number }} modelOffset
37782
+ */
37783
+ function applyModelRootTranslationFromWorldBase(mesh, modelRoot, baseWorldPos, modelOffset) {
37784
+ if (!mesh || !modelRoot || !baseWorldPos) return;
37785
+ var newWorldPos = baseWorldPos.clone().add(modelOffsetToWorldDelta(modelRoot, modelOffset));
37786
+ if (mesh.parent) mesh.parent.worldToLocal(newWorldPos);
37787
+ mesh.position.copy(newWorldPos);
37788
+ }
37789
+
37383
37790
  var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
37384
37791
  function IoBehaviorManager(sceneViewer) {
37385
37792
  var _this;
@@ -37397,12 +37804,11 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
37397
37804
  */
37398
37805
  _this._entries = new Map();
37399
37806
  _this._crossComponentBehaviors = [];
37400
-
37401
- /**
37402
- * Map: `${componentUuid}` → Array<{ id, input, outputs }>
37403
- * Component-level behaviors for intra-component io-device linking
37404
- */
37405
37807
  _this._componentBehaviors = new Map();
37808
+ /** @type {Map<string, string>} parentUuid::attachmentId → parentUuid */
37809
+ _this._attachmentParentMap = new Map();
37810
+ /** @type {Map<string, Object[]>} cache key → data point definitions */
37811
+ _this._dataPointsCache = new Map();
37406
37812
 
37407
37813
  /**
37408
37814
  * Injected by the host application to read and write I/O device state.
@@ -37492,7 +37898,10 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
37492
37898
  }
37493
37899
  if (entries.length) {
37494
37900
  this._entries.set(key, entries);
37495
- // Loaded ${entries.length} animation(s) for attachment "${attachmentId}"
37901
+ if (parentUuid && attachmentId) {
37902
+ this._attachmentParentMap.set(this._key(parentUuid, attachmentId), parentUuid);
37903
+ }
37904
+ this._invalidateDataPointsCache(parentUuid, attachmentId);
37496
37905
  } else {
37497
37906
  console.warn("[IoBehaviorManager] No mesh entries resolved for attachment \"".concat(attachmentId, "\" \u2014 behaviorConfig had ").concat(anims.length, " entries but none matched a mesh"));
37498
37907
  }
@@ -37500,7 +37909,6 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
37500
37909
 
37501
37910
  /**
37502
37911
  * Apply animations triggered by an IO device state change.
37503
- * Should be called in parallel with BehaviorManager.triggerState().
37504
37912
  *
37505
37913
  * @param {string} attachmentId - Raw attachment key (not scoped)
37506
37914
  * @param {string} dataPointId - The data point / state variable id that changed
@@ -37510,70 +37918,36 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
37510
37918
  }, {
37511
37919
  key: "triggerState",
37512
37920
  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;
37921
+ var _this$_crossComponent;
37922
+ var resolvedParent = parentUuid || this._findComponentByAttachment(attachmentId);
37923
+ if (!resolvedParent) return;
37924
+ var key = this._key(resolvedParent, attachmentId);
37925
+ var entries = this._entries.get(key);
37926
+ if (entries) {
37927
+ var _iterator2 = _createForOfIteratorHelper(entries),
37928
+ _step2;
37538
37929
  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
- }
37930
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
37931
+ var entry = _step2.value;
37932
+ if (entry.anim.stateVariable !== dataPointId) continue;
37933
+ this._applyAnimation(entry, value);
37558
37934
  }
37559
37935
  } catch (err) {
37560
- _iterator3.e(err);
37936
+ _iterator2.e(err);
37561
37937
  } finally {
37562
- _iterator3.f();
37938
+ _iterator2.f();
37563
37939
  }
37564
37940
  }
37565
37941
 
37566
37942
  // 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);
37943
+ if (this._componentBehaviors.has(resolvedParent)) {
37944
+ var componentBehaviors = this._componentBehaviors.get(resolvedParent);
37945
+ this.triggerCrossComponentBehaviors(componentBehaviors, resolvedParent, attachmentId, dataPointId, value, {
37946
+ sameComponent: true
37947
+ });
37571
37948
  }
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);
37949
+ if (((_this$_crossComponent = this._crossComponentBehaviors) === null || _this$_crossComponent === void 0 ? void 0 : _this$_crossComponent.length) > 0) {
37950
+ this.triggerCrossComponentBehaviors(this._crossComponentBehaviors, resolvedParent, attachmentId, dataPointId, value);
37577
37951
  }
37578
37952
  }
37579
37953
 
@@ -37586,9 +37960,8 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
37586
37960
  }, {
37587
37961
  key: "setCrossComponentBehaviors",
37588
37962
  value: function setCrossComponentBehaviors(behaviors) {
37589
- var _this2 = this;
37590
37963
  this._crossComponentBehaviors = (behaviors || []).map(function (b) {
37591
- return _this2._normalizeBehavior(b);
37964
+ return normalizeBehavior(b);
37592
37965
  });
37593
37966
  // Loaded ${this._crossComponentBehaviors.length} cross-component behavior(s)
37594
37967
  }
@@ -37603,72 +37976,17 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
37603
37976
  }, {
37604
37977
  key: "registerComponentBehaviors",
37605
37978
  value: function registerComponentBehaviors(componentUuid, behaviors) {
37606
- var _this3 = this;
37607
- if (!behaviors || behaviors.length === 0) return;
37979
+ if (!behaviors || behaviors.length === 0) {
37980
+ this._componentBehaviors.delete(componentUuid);
37981
+ return;
37982
+ }
37608
37983
  var normalized = behaviors.map(function (b) {
37609
- return _this3._normalizeBehavior(b);
37984
+ return normalizeBehavior(b);
37610
37985
  });
37611
37986
  this._componentBehaviors.set(componentUuid, normalized);
37612
37987
  // Registered ${normalized.length} component-level behavior(s) for component ${componentUuid}
37613
37988
  }
37614
37989
 
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
37990
  /**
37673
37991
  * Find the parent component UUID for a given attachment ID.
37674
37992
  * Searches the scene tree for the io-device with this attachment ID,
@@ -37682,12 +38000,10 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
37682
38000
  value: function _findComponentByAttachment(attachmentId) {
37683
38001
  var _this$sceneViewer;
37684
38002
  if (!((_this$sceneViewer = this.sceneViewer) !== null && _this$sceneViewer !== void 0 && _this$sceneViewer.scene)) return null;
37685
- var scene = this.sceneViewer.scene;
37686
38003
  var found = null;
37687
- scene.traverse(function (obj) {
38004
+ this.sceneViewer.scene.traverse(function (obj) {
37688
38005
  var _obj$userData, _obj$userData2;
37689
38006
  if (found) return;
37690
- // Find the io-device object with this attachmentId
37691
38007
  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
38008
  found = obj.userData.parentComponentId;
37693
38009
  }
@@ -37731,20 +38047,21 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
37731
38047
  * @param {string} triggerAttachmentId - Attachment ID of the triggering device
37732
38048
  * @param {string} triggerStateId - The state variable ID that changed
37733
38049
  * @param {*} value - The new state value
38050
+ * @param {{ sameComponent?: boolean }} [options] - Intra-component links share one parent instance
37734
38051
  */
37735
38052
  }, {
37736
38053
  key: "triggerCrossComponentBehaviors",
37737
38054
  value: function triggerCrossComponentBehaviors(behaviors, triggerParentUuid, triggerAttachmentId, triggerStateId, value) {
38055
+ var options = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : {};
37738
38056
  if (!behaviors || !Array.isArray(behaviors)) {
37739
38057
  return;
37740
38058
  }
37741
-
37742
- // Evaluating ${behaviors.length} behavior(s)
37743
- var _iterator5 = _createForOfIteratorHelper(behaviors),
37744
- _step5;
38059
+ var sameComponent = options.sameComponent === true;
38060
+ var _iterator3 = _createForOfIteratorHelper(behaviors),
38061
+ _step3;
37745
38062
  try {
37746
- for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
37747
- var behavior = _step5.value;
38063
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
38064
+ var behavior = _step3.value;
37748
38065
  var input = behavior.input,
37749
38066
  output = behavior.output,
37750
38067
  _outputs = behavior._outputs,
@@ -37753,94 +38070,69 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
37753
38070
  console.warn('[Behavior] Skipping behavior - missing input or output(s):', behavior);
37754
38071
  continue;
37755
38072
  }
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) {
38073
+ var inputComponent = input.component || (sameComponent ? triggerParentUuid : this._findComponentByAttachment(input.attachment));
38074
+ var inputMatches = sameComponent ? input.attachment === triggerAttachmentId && input.state === triggerStateId : inputComponent === triggerParentUuid && input.attachment === triggerAttachmentId && input.state === triggerStateId;
38075
+ if (inputMatches) {
37777
38076
  // Behavior "${behavior.id}" matched
37778
38077
 
37779
38078
  // Collect all outputs (single or multiple)
37780
38079
  var outputs = _outputs || (output ? [output] : []);
37781
38080
 
37782
38081
  // Process each output
37783
- var _iterator6 = _createForOfIteratorHelper(outputs),
37784
- _step6;
38082
+ var _iterator4 = _createForOfIteratorHelper(outputs),
38083
+ _step4;
37785
38084
  try {
37786
- for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
37787
- var out = _step6.value;
38085
+ for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
38086
+ var out = _step4.value;
37788
38087
  // NEW: State-to-state pass-through pattern
37789
38088
  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
38089
+ var outputComponent = out.component || (sameComponent ? triggerParentUuid : this._findComponentByAttachment(out.attachment));
37794
38090
  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)
38091
+ var scopedKey = getScopedAttachmentKey(out.attachment, outputComponent);
38092
+ this._stateAdapter.setState(scopedKey, out.state, value);
37800
38093
  if (outputComponent) {
37801
- // Triggering animations on output component
37802
38094
  this.triggerState(out.attachment, out.state, value, outputComponent);
37803
38095
  } else {
37804
38096
  console.warn("[Behavior] Could not find component for attachment \"".concat(out.attachment, "\""));
37805
38097
  }
37806
38098
  } else {
37807
- console.warn('[Behavior] State adapter not configured for state-to-state behavior');
38099
+ console.warn('[Behavior] State adapter not configured for state-to-state behavior');
37808
38100
  }
37809
38101
  }
37810
38102
  // LEGACY: Mesh-based pattern with conditions
37811
38103
  else if (conditions && out.child) {
37812
38104
  // Using legacy mesh-based pattern with conditions
37813
38105
  // Evaluate conditions
37814
- var _iterator7 = _createForOfIteratorHelper(conditions),
37815
- _step7;
38106
+ var _iterator5 = _createForOfIteratorHelper(conditions),
38107
+ _step5;
37816
38108
  try {
37817
- for (_iterator7.s(); !(_step7 = _iterator7.n()).done;) {
37818
- var condition = _step7.value;
38109
+ for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
38110
+ var condition = _step5.value;
37819
38111
  if (this._evaluateCondition(condition.when, value)) {
37820
38112
  // Apply actions to the target output component/attachment/child mesh
37821
38113
  this._applyCrossComponentActions(out, condition.actions);
37822
38114
  }
37823
38115
  }
37824
38116
  } catch (err) {
37825
- _iterator7.e(err);
38117
+ _iterator5.e(err);
37826
38118
  } finally {
37827
- _iterator7.f();
38119
+ _iterator5.f();
37828
38120
  }
37829
38121
  } else {
37830
38122
  console.warn('[Behavior] Output has neither state nor (child + conditions):', out);
37831
38123
  }
37832
38124
  } // end outputs loop
37833
38125
  } catch (err) {
37834
- _iterator6.e(err);
38126
+ _iterator4.e(err);
37835
38127
  } finally {
37836
- _iterator6.f();
38128
+ _iterator4.f();
37837
38129
  }
37838
38130
  }
37839
38131
  }
37840
38132
  } catch (err) {
37841
- _iterator5.e(err);
38133
+ _iterator3.e(err);
37842
38134
  } finally {
37843
- _iterator5.f();
38135
+ _iterator3.f();
37844
38136
  }
37845
38137
  }
37846
38138
 
@@ -37910,17 +38202,17 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
37910
38202
  }
37911
38203
 
37912
38204
  // 4. Apply actions to targetObj
37913
- var _iterator8 = _createForOfIteratorHelper(actions),
37914
- _step8;
38205
+ var _iterator6 = _createForOfIteratorHelper(actions),
38206
+ _step6;
37915
38207
  try {
37916
- for (_iterator8.s(); !(_step8 = _iterator8.n()).done;) {
37917
- var action = _step8.value;
38208
+ for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
38209
+ var action = _step6.value;
37918
38210
  this._applyCrossComponentAction(targetObj, action);
37919
38211
  }
37920
38212
  } catch (err) {
37921
- _iterator8.e(err);
38213
+ _iterator6.e(err);
37922
38214
  } finally {
37923
- _iterator8.f();
38215
+ _iterator6.f();
37924
38216
  }
37925
38217
  }
37926
38218
 
@@ -38012,8 +38304,12 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
38012
38304
  }, {
38013
38305
  key: "getAnimationDataPoints",
38014
38306
  value: function getAnimationDataPoints(parentUuid, attachmentId) {
38015
- var _this4 = this;
38307
+ var _this2 = this;
38016
38308
  var hitMesh = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
38309
+ var cacheKey = "".concat(this._key(parentUuid, attachmentId), "::").concat((hitMesh === null || hitMesh === void 0 ? void 0 : hitMesh.uuid) || '');
38310
+ if (this._dataPointsCache.has(cacheKey)) {
38311
+ return this._dataPointsCache.get(cacheKey);
38312
+ }
38017
38313
  var key = this._key(parentUuid, attachmentId);
38018
38314
  var entries = this._entries.get(key);
38019
38315
  if (!(entries !== null && entries !== void 0 && entries.length)) return [];
@@ -38024,35 +38320,35 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
38024
38320
  var filtered = entries;
38025
38321
  if (hitMesh) {
38026
38322
  var matching = entries.filter(function (e) {
38027
- return _this4._isMeshOrDescendant(hitMesh, e.mesh);
38323
+ return _this2._isMeshOrDescendant(hitMesh, e.mesh);
38028
38324
  });
38029
38325
  if (matching.length > 0) filtered = matching;
38030
38326
  }
38031
38327
 
38032
38328
  // Collapse multiple mesh entries that share the same stateVariable
38033
38329
  var seen = new Map(); // stateVariable → anim
38034
- var _iterator9 = _createForOfIteratorHelper(filtered),
38035
- _step9;
38330
+ var _iterator7 = _createForOfIteratorHelper(filtered),
38331
+ _step7;
38036
38332
  try {
38037
- for (_iterator9.s(); !(_step9 = _iterator9.n()).done;) {
38038
- var anim = _step9.value.anim;
38333
+ for (_iterator7.s(); !(_step7 = _iterator7.n()).done;) {
38334
+ var anim = _step7.value.anim;
38039
38335
  if (!seen.has(anim.stateVariable)) {
38040
38336
  seen.set(anim.stateVariable, anim);
38041
38337
  }
38042
38338
  }
38043
38339
  } catch (err) {
38044
- _iterator9.e(err);
38340
+ _iterator7.e(err);
38045
38341
  } finally {
38046
- _iterator9.f();
38342
+ _iterator7.f();
38047
38343
  }
38048
38344
  var dps = [];
38049
- var _iterator0 = _createForOfIteratorHelper(seen),
38050
- _step0;
38345
+ var _iterator8 = _createForOfIteratorHelper(seen),
38346
+ _step8;
38051
38347
  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];
38348
+ for (_iterator8.s(); !(_step8 = _iterator8.n()).done;) {
38349
+ var _step8$value = _slicedToArray(_step8.value, 2),
38350
+ stateVar = _step8$value[0],
38351
+ _anim = _step8$value[1];
38056
38352
  // Normalise stateType from AnimateDevicesDialog variants
38057
38353
  var stateType = void 0;
38058
38354
  var raw = (_anim.stateType || '').toLowerCase();
@@ -38103,10 +38399,11 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
38103
38399
  });
38104
38400
  }
38105
38401
  } catch (err) {
38106
- _iterator0.e(err);
38402
+ _iterator8.e(err);
38107
38403
  } finally {
38108
- _iterator0.f();
38404
+ _iterator8.f();
38109
38405
  }
38406
+ this._dataPointsCache.set(cacheKey, dps);
38110
38407
  return dps;
38111
38408
  }
38112
38409
 
@@ -38140,25 +38437,46 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
38140
38437
  key: "unloadForComponent",
38141
38438
  value: function unloadForComponent(parentUuid) {
38142
38439
  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
- }
38440
+ for (var _i = 0, _arr = _toConsumableArray(this._entries.keys()); _i < _arr.length; _i++) {
38441
+ var key = _arr[_i];
38442
+ if (key.startsWith(prefix)) {
38443
+ this._attachmentParentMap.delete(key);
38444
+ this._entries.delete(key);
38151
38445
  }
38152
- } catch (err) {
38153
- _iterator1.e(err);
38154
- } finally {
38155
- _iterator1.f();
38156
38446
  }
38447
+ this._componentBehaviors.delete(parentUuid);
38448
+ this._invalidateDataPointsCacheForParent(parentUuid);
38449
+ }
38450
+
38451
+ /**
38452
+ * Remove animation entries for a single attachment on a component instance.
38453
+ */
38454
+ }, {
38455
+ key: "unloadForAttachment",
38456
+ value: function unloadForAttachment(parentUuid, attachmentId) {
38457
+ var key = this._key(parentUuid, attachmentId);
38458
+ this._entries.delete(key);
38459
+ this._attachmentParentMap.delete(key);
38460
+ this._invalidateDataPointsCache(parentUuid, attachmentId);
38461
+ }
38462
+
38463
+ /**
38464
+ * Clear all runtime behavior state when the scene is cleared or replaced.
38465
+ */
38466
+ }, {
38467
+ key: "resetForScene",
38468
+ value: function resetForScene() {
38469
+ this._entries.clear();
38470
+ this._componentBehaviors.clear();
38471
+ this._crossComponentBehaviors = [];
38472
+ this._attachmentParentMap.clear();
38473
+ this._dataPointsCache.clear();
38157
38474
  }
38158
38475
  }, {
38159
38476
  key: "dispose",
38160
38477
  value: function dispose() {
38161
- this._entries.clear();
38478
+ this.resetForScene();
38479
+ this._stateAdapter = null;
38162
38480
  _superPropGet(IoBehaviorManager, "dispose", this, 3)([]);
38163
38481
  }
38164
38482
 
@@ -38225,20 +38543,20 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
38225
38543
  key: "_applyAnimation",
38226
38544
  value: function _applyAnimation(entry, value) {
38227
38545
  var anim = entry.anim,
38228
- mesh = entry.mesh,
38229
- origPos = entry.origPos;
38546
+ mesh = entry.mesh;
38547
+ entry.origPos;
38230
38548
  entry.origRot;
38231
38549
  var viewerMaxDim = entry.viewerMaxDim;
38232
38550
  var mapping = this._resolveMapping(anim, value);
38233
38551
  if (!mapping) return;
38234
38552
  var types = anim.transformTypes || [];
38235
- var _iterator10 = _createForOfIteratorHelper(types),
38236
- _step10;
38553
+ var _iterator9 = _createForOfIteratorHelper(types),
38554
+ _step9;
38237
38555
  try {
38238
- for (_iterator10.s(); !(_step10 = _iterator10.n()).done;) {
38239
- var type = _step10.value;
38556
+ for (_iterator9.s(); !(_step9 = _iterator9.n()).done;) {
38557
+ var type = _step9.value;
38240
38558
  if (type === 'translation') {
38241
- this._applyTranslation(mesh, origPos, mapping.transform);
38559
+ this._applyTranslation(entry, mapping.transform);
38242
38560
  } else if (type === 'rotation') {
38243
38561
  this._applyRotation(entry, anim, mapping.rotationTransform, viewerMaxDim);
38244
38562
  } else if (type === 'color') {
@@ -38246,9 +38564,9 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
38246
38564
  }
38247
38565
  }
38248
38566
  } catch (err) {
38249
- _iterator10.e(err);
38567
+ _iterator9.e(err);
38250
38568
  } finally {
38251
- _iterator10.f();
38569
+ _iterator9.f();
38252
38570
  }
38253
38571
  }
38254
38572
 
@@ -38368,20 +38686,21 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
38368
38686
  // ─────────────────────────────────────────────────────────────────────────
38369
38687
 
38370
38688
  /**
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
38689
+ * Apply a translation offset in device-model-root space so every animated mesh
38690
+ * shares the same axis directions regardless of intermediate parent transforms.
38691
+ *
38692
+ * @param {{ mesh, origPos, deviceModelRoot }} entry
38693
+ * @param {{ x, y, z }} transform - Offset in model-root local space
38375
38694
  */
38376
38695
  }, {
38377
38696
  key: "_applyTranslation",
38378
- value: function _applyTranslation(mesh, origPos, transform) {
38379
- var _transform$x, _transform$y, _transform$z;
38697
+ value: function _applyTranslation(entry, transform) {
38380
38698
  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));
38699
+ var mesh = entry.mesh,
38700
+ origPos = entry.origPos,
38701
+ deviceModelRoot = entry.deviceModelRoot;
38702
+ if (!mesh || !origPos || !deviceModelRoot) return;
38703
+ applyModelRootTranslation(mesh, deviceModelRoot, origPos, transform);
38385
38704
  }
38386
38705
 
38387
38706
  /**
@@ -38400,6 +38719,9 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
38400
38719
  origRot = entry.origRot,
38401
38720
  deviceModelRoot = entry.deviceModelRoot;
38402
38721
  if (!mesh || !origPos || !origRot) return null;
38722
+ if (entry._restWorldCache) {
38723
+ return entry._restWorldCache;
38724
+ }
38403
38725
  var savedPos = mesh.position.clone();
38404
38726
  var savedQuat = mesh.quaternion.clone();
38405
38727
  mesh.position.copy(origPos);
@@ -38418,12 +38740,35 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
38418
38740
  }
38419
38741
  mesh.position.copy(savedPos);
38420
38742
  mesh.quaternion.copy(savedQuat);
38421
- return {
38743
+ entry._restWorldCache = {
38422
38744
  origWorldPos: origWorldPos,
38423
38745
  origWorldQuat: origWorldQuat,
38424
38746
  origWorldCenter: origWorldCenter,
38425
38747
  deviceWorldQuat: deviceWorldQuat
38426
38748
  };
38749
+ return entry._restWorldCache;
38750
+ }
38751
+ }, {
38752
+ key: "_invalidateDataPointsCache",
38753
+ value: function _invalidateDataPointsCache(parentUuid, attachmentId) {
38754
+ var prefix = "".concat(this._key(parentUuid, attachmentId), "::");
38755
+ for (var _i2 = 0, _arr2 = _toConsumableArray(this._dataPointsCache.keys()); _i2 < _arr2.length; _i2++) {
38756
+ var key = _arr2[_i2];
38757
+ if (key.startsWith(prefix)) {
38758
+ this._dataPointsCache.delete(key);
38759
+ }
38760
+ }
38761
+ }
38762
+ }, {
38763
+ key: "_invalidateDataPointsCacheForParent",
38764
+ value: function _invalidateDataPointsCacheForParent(parentUuid) {
38765
+ var prefix = "".concat(parentUuid, "::");
38766
+ for (var _i3 = 0, _arr3 = _toConsumableArray(this._dataPointsCache.keys()); _i3 < _arr3.length; _i3++) {
38767
+ var key = _arr3[_i3];
38768
+ if (key.startsWith(prefix)) {
38769
+ this._dataPointsCache.delete(key);
38770
+ }
38771
+ }
38427
38772
  }
38428
38773
 
38429
38774
  /**
@@ -38956,6 +39301,7 @@ var CentralPlantInternals = /*#__PURE__*/function () {
38956
39301
  // Initialize the component tooltip manager (screen-space tooltip on selection)
38957
39302
  this.centralPlant.managers.componentTooltipManager = new ComponentTooltipManager(this.centralPlant.sceneViewer);
38958
39303
  this.centralPlant.sceneViewer.componentTooltipManager = this.centralPlant.managers.componentTooltipManager;
39304
+ this.centralPlant.sceneViewer.managers.componentTooltipManager = this.centralPlant.managers.componentTooltipManager;
38959
39305
  }
38960
39306
  }
38961
39307
 
@@ -39864,37 +40210,14 @@ var CentralPlantInternals = /*#__PURE__*/function () {
39864
40210
  // Add attached IO device models for smart components
39865
40211
  if (componentData.isSmart && componentData.attachedDevices) {
39866
40212
  var _this$centralPlant$sc8;
39867
- attachIODevicesToComponent(componentModel, componentData, modelPreloader, componentId);
39868
-
39869
- // Register behavior configs so IoBehaviorManager can respond to state changes
39870
40213
  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;
40214
+ attachIODevicesToComponent(componentModel, componentData, modelPreloader, componentId).then(function () {
40215
+ if (ioBehavMgr) {
40216
+ registerBehaviorsForComponent(ioBehavMgr, componentId, componentData, componentModel, modelPreloader.componentDictionary);
39890
40217
  }
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);
39896
- }
39897
- }
40218
+ }).catch(function (err) {
40219
+ console.error("\u274C Error attaching IO devices for ".concat(libraryId, ":"), err);
40220
+ });
39898
40221
  }
39899
40222
 
39900
40223
  // Notify the component manager about the new component
@@ -40024,8 +40347,8 @@ var CentralPlantInternals = /*#__PURE__*/function () {
40024
40347
  // the component (e.g., dynamically added but not yet synced to sceneData)
40025
40348
  if (connectorIds.size === 0 && threeScene) {
40026
40349
  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') {
40350
+ var _obj$userData2, _obj$userData3;
40351
+ 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
40352
  obj.children.forEach(function (child) {
40030
40353
  var _child$userData3;
40031
40354
  if (((_child$userData3 = child.userData) === null || _child$userData3 === void 0 ? void 0 : _child$userData3.objectType) === 'connector') {
@@ -40069,18 +40392,23 @@ var CentralPlantInternals = /*#__PURE__*/function () {
40069
40392
  // Step 5: Remove the component from the Three.js scene
40070
40393
  var success = componentManager.removeComponentFromScene(componentId);
40071
40394
  if (success) {
40395
+ var _this$centralPlant$sc10;
40396
+ 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;
40397
+ if (ioBehavMgr !== null && ioBehavMgr !== void 0 && ioBehavMgr.unloadForComponent) {
40398
+ ioBehavMgr.unloadForComponent(resolvedUuid);
40399
+ }
40072
40400
  // Step 6: Directly remove SEGMENT-* and Gateway objects from Three.js whose
40073
40401
  // pathFrom/pathTo references one of this component's connectors.
40074
40402
  // This is surgical cleanup that works regardless of shouldUpdatePaths.
40075
40403
  if (connectorIds.size > 0 && threeScene) {
40076
40404
  var objectsToRemove = [];
40077
40405
  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;
40406
+ var _obj$uuid, _obj$userData4, _obj$uuid2, _obj$userData5;
40407
+ 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;
40408
+ 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
40409
  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)) {
40410
+ var _obj$userData6, _obj$userData7;
40411
+ 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
40412
  objectsToRemove.push(obj);
40085
40413
  }
40086
40414
  }
@@ -40169,6 +40497,149 @@ var CentralPlantInternals = /*#__PURE__*/function () {
40169
40497
  }]);
40170
40498
  }();
40171
40499
 
40500
+ /**
40501
+ * Scene-level behavior helpers: scan endpoints, register behaviors, cross-component links.
40502
+ */
40503
+ function getComponentDictionary(centralPlant) {
40504
+ var _centralPlant$getUtil, _centralPlant$manager;
40505
+ 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;
40506
+ }
40507
+ function statesFromBehaviorConfig(device) {
40508
+ if (!(device !== null && device !== void 0 && device.behaviorConfig) || !Array.isArray(device.behaviorConfig)) return [];
40509
+ return device.behaviorConfig.map(function (b) {
40510
+ return b.stateVariable;
40511
+ }).filter(Boolean).filter(function (v, i, arr) {
40512
+ return arr.indexOf(v) === i;
40513
+ });
40514
+ }
40515
+ function resolveStatesForDevice(ioObj, deviceId, componentDictionary) {
40516
+ var _ioObj$userData;
40517
+ var device = componentDictionary === null || componentDictionary === void 0 ? void 0 : componentDictionary[deviceId];
40518
+ var fromBehaviorConfig = statesFromBehaviorConfig(device);
40519
+ if (fromBehaviorConfig.length) return fromBehaviorConfig;
40520
+ return (((_ioObj$userData = ioObj.userData) === null || _ioObj$userData === void 0 ? void 0 : _ioObj$userData.dataPoints) || []).map(function (dp) {
40521
+ return dp.id || dp.name;
40522
+ }).filter(Boolean).filter(function (v, i, arr) {
40523
+ return arr.indexOf(v) === i;
40524
+ });
40525
+ }
40526
+
40527
+ /**
40528
+ * Scan the live Three.js scene for smart-component io-device endpoints.
40529
+ */
40530
+ function scanSceneIoEndpoints(centralPlant) {
40531
+ var _centralPlant$sceneVi;
40532
+ var scene = centralPlant === null || centralPlant === void 0 || (_centralPlant$sceneVi = centralPlant.sceneViewer) === null || _centralPlant$sceneVi === void 0 ? void 0 : _centralPlant$sceneVi.scene;
40533
+ if (!scene) return [];
40534
+ var dict = getComponentDictionary(centralPlant);
40535
+ var endpoints = [];
40536
+ scene.traverse(function (obj) {
40537
+ var _obj$userData, _parent$userData, _parent$userData2;
40538
+ if (((_obj$userData = obj.userData) === null || _obj$userData === void 0 ? void 0 : _obj$userData.objectType) !== 'io-device') return;
40539
+ var parent = obj.parent;
40540
+ var parentUuid = obj.userData.parentComponentId || (parent === null || parent === void 0 ? void 0 : parent.uuid) || '';
40541
+ 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;
40542
+ var libraryId = (parent === null || parent === void 0 || (_parent$userData2 = parent.userData) === null || _parent$userData2 === void 0 ? void 0 : _parent$userData2.libraryId) || '';
40543
+ var attachmentId = obj.userData.attachmentId || obj.name || obj.uuid;
40544
+ var deviceId = obj.userData.deviceId || '';
40545
+ endpoints.push({
40546
+ key: getScopedAttachmentKey(attachmentId, parentUuid),
40547
+ parentUuid: parentUuid,
40548
+ parentName: parentName,
40549
+ libraryId: libraryId,
40550
+ attachmentId: attachmentId,
40551
+ attachmentLabel: obj.userData.attachmentLabel || attachmentId,
40552
+ deviceId: deviceId,
40553
+ states: resolveStatesForDevice(obj, deviceId, dict)
40554
+ });
40555
+ });
40556
+ return endpoints;
40557
+ }
40558
+
40559
+ /**
40560
+ * Load cross-component behaviors from scene data mirrors.
40561
+ */
40562
+ function loadCrossComponentBehaviors(centralPlant) {
40563
+ var _centralPlant$importe, _centralPlant$sceneVi2;
40564
+ 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) || [];
40565
+ }
40566
+
40567
+ /**
40568
+ * Persist cross-component behaviors to scene data mirrors and the runtime manager.
40569
+ */
40570
+ function applyCrossComponentBehaviors(centralPlant, behaviors) {
40571
+ var normalized = behaviors || [];
40572
+ if (centralPlant.importedSceneData) {
40573
+ if (normalized.length) {
40574
+ centralPlant.importedSceneData.behaviors = normalized;
40575
+ } else {
40576
+ delete centralPlant.importedSceneData.behaviors;
40577
+ }
40578
+ }
40579
+ var sceneViewer = centralPlant.sceneViewer;
40580
+ if (sceneViewer && !sceneViewer.currentSceneData) {
40581
+ var _centralPlant$getConn;
40582
+ sceneViewer.currentSceneData = {
40583
+ version: '2.3',
40584
+ connections: ((_centralPlant$getConn = centralPlant.getConnections) === null || _centralPlant$getConn === void 0 ? void 0 : _centralPlant$getConn.call(centralPlant)) || [],
40585
+ scene: {
40586
+ children: []
40587
+ }
40588
+ };
40589
+ }
40590
+ var currentSceneData = sceneViewer === null || sceneViewer === void 0 ? void 0 : sceneViewer.currentSceneData;
40591
+ if (currentSceneData) {
40592
+ if (normalized.length) {
40593
+ currentSceneData.behaviors = normalized;
40594
+ } else {
40595
+ delete currentSceneData.behaviors;
40596
+ }
40597
+ }
40598
+ var ioBehavMgr = getIoBehaviorManager(sceneViewer);
40599
+ if (ioBehavMgr) {
40600
+ ioBehavMgr.setCrossComponentBehaviors(normalized);
40601
+ }
40602
+ }
40603
+
40604
+ /**
40605
+ * Re-register intra-component behaviors on all scene instances of a smart component.
40606
+ */
40607
+ function refreshSceneIntraBehaviors(centralPlant, libraryId, behaviors) {
40608
+ var _centralPlant$sceneVi3;
40609
+ var ioBehavMgr = getIoBehaviorManager(centralPlant === null || centralPlant === void 0 ? void 0 : centralPlant.sceneViewer);
40610
+ var scene = centralPlant === null || centralPlant === void 0 || (_centralPlant$sceneVi3 = centralPlant.sceneViewer) === null || _centralPlant$sceneVi3 === void 0 ? void 0 : _centralPlant$sceneVi3.scene;
40611
+ if (!ioBehavMgr || !scene || !libraryId) return;
40612
+ scene.traverse(function (obj) {
40613
+ var _obj$userData2, _obj$userData3;
40614
+ 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) {
40615
+ ioBehavMgr.registerComponentBehaviors(obj.uuid, behaviors || []);
40616
+ }
40617
+ });
40618
+ }
40619
+
40620
+ /**
40621
+ * Re-register intra- and cross-component behaviors for all instances in the live scene.
40622
+ */
40623
+ function reregisterSceneBehaviors(centralPlant) {
40624
+ var _centralPlant$sceneVi4;
40625
+ var ioBehavMgr = getIoBehaviorManager(centralPlant === null || centralPlant === void 0 ? void 0 : centralPlant.sceneViewer);
40626
+ var scene = centralPlant === null || centralPlant === void 0 || (_centralPlant$sceneVi4 = centralPlant.sceneViewer) === null || _centralPlant$sceneVi4 === void 0 ? void 0 : _centralPlant$sceneVi4.scene;
40627
+ var dict = getComponentDictionary(centralPlant);
40628
+ if (!ioBehavMgr || !scene) return;
40629
+ var crossBehaviors = loadCrossComponentBehaviors(centralPlant);
40630
+ ioBehavMgr.setCrossComponentBehaviors(crossBehaviors);
40631
+ if (!dict) return;
40632
+ scene.traverse(function (obj) {
40633
+ var _obj$userData4, _obj$userData5;
40634
+ if (((_obj$userData4 = obj.userData) === null || _obj$userData4 === void 0 ? void 0 : _obj$userData4.objectType) !== 'component') return;
40635
+ var libraryId = (_obj$userData5 = obj.userData) === null || _obj$userData5 === void 0 ? void 0 : _obj$userData5.libraryId;
40636
+ if (!libraryId) return;
40637
+ var componentData = dict[libraryId];
40638
+ if (!componentData) return;
40639
+ registerBehaviorsForComponent(ioBehavMgr, obj.uuid, componentData, obj, dict);
40640
+ });
40641
+ }
40642
+
40172
40643
  // ─────────────────────────────────────────────────────────────────────────────
40173
40644
  // Flow-direction compatibility helper (module-level, no class dependency)
40174
40645
  // ─────────────────────────────────────────────────────────────────────────────
@@ -40195,7 +40666,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
40195
40666
  * Initialize the CentralPlant manager
40196
40667
  *
40197
40668
  * @constructor
40198
- * @version 0.3.38
40669
+ * @version 0.3.41
40199
40670
  * @updated 2025-10-22
40200
40671
  *
40201
40672
  * @description Creates a new CentralPlant instance and initializes internal managers and utilities.
@@ -40369,8 +40840,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
40369
40840
  key: "setImportedSceneData",
40370
40841
  value: (function () {
40371
40842
  var _setImportedSceneData = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2(sceneData) {
40372
- var _this$importedSceneDa, _this$importedSceneDa2, _this$importedSceneDa3, _this$sceneViewer;
40373
- var ioBehavMgr;
40843
+ var _this$importedSceneDa, _this$importedSceneDa2, _this$importedSceneDa3;
40374
40844
  return _regenerator().w(function (_context2) {
40375
40845
  while (1) switch (_context2.n) {
40376
40846
  case 0:
@@ -40387,14 +40857,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
40387
40857
 
40388
40858
  // Scene behaviors loaded
40389
40859
 
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
- }
40860
+ // Cross-component behaviors are registered by sceneOperationsManager.loadSceneData()
40398
40861
 
40399
40862
  // Reset component counter based on imported components to avoid ID conflicts
40400
40863
  this.internals.resetComponentCounter();
@@ -40575,8 +41038,8 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
40575
41038
  }, {
40576
41039
  key: "selectObject",
40577
41040
  value: function selectObject(objectOrId) {
40578
- var _this$sceneViewer2, _object;
40579
- if (!((_this$sceneViewer2 = this.sceneViewer) !== null && _this$sceneViewer2 !== void 0 && _this$sceneViewer2.transformManager)) {
41041
+ var _this$sceneViewer, _object;
41042
+ if (!((_this$sceneViewer = this.sceneViewer) !== null && _this$sceneViewer !== void 0 && _this$sceneViewer.transformManager)) {
40580
41043
  console.warn('⚠️ Transform manager not initialized');
40581
41044
  return false;
40582
41045
  }
@@ -40622,8 +41085,8 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
40622
41085
  }, {
40623
41086
  key: "toggleObject",
40624
41087
  value: function toggleObject(objectOrId) {
40625
- var _this$sceneViewer3, _object2;
40626
- if (!((_this$sceneViewer3 = this.sceneViewer) !== null && _this$sceneViewer3 !== void 0 && _this$sceneViewer3.transformManager)) {
41088
+ var _this$sceneViewer2, _object2;
41089
+ if (!((_this$sceneViewer2 = this.sceneViewer) !== null && _this$sceneViewer2 !== void 0 && _this$sceneViewer2.transformManager)) {
40627
41090
  console.warn('⚠️ Transform manager not initialized');
40628
41091
  return false;
40629
41092
  }
@@ -40659,8 +41122,8 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
40659
41122
  }, {
40660
41123
  key: "deselectObject",
40661
41124
  value: function deselectObject() {
40662
- var _this$sceneViewer4;
40663
- if (!((_this$sceneViewer4 = this.sceneViewer) !== null && _this$sceneViewer4 !== void 0 && _this$sceneViewer4.transformManager)) {
41125
+ var _this$sceneViewer3;
41126
+ if (!((_this$sceneViewer3 = this.sceneViewer) !== null && _this$sceneViewer3 !== void 0 && _this$sceneViewer3.transformManager)) {
40664
41127
  console.warn('⚠️ Transform manager not initialized');
40665
41128
  return false;
40666
41129
  }
@@ -41240,68 +41703,70 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
41240
41703
  }
41241
41704
 
41242
41705
  /**
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)
41706
+ * Internal single path for I/O state changes: persist, animate, link, emit.
41707
+ * @private
41270
41708
  */
41271
41709
  }, {
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;
41710
+ key: "_dispatchIoState",
41711
+ value: function _dispatchIoState(attachmentId, stateId, value, parentUuid) {
41712
+ var _this$managers, _this$sceneViewer4, _this$sceneViewer5, _this$managers2, _this$sceneViewer6, _this$sceneViewer7, _this$sceneViewer8;
41713
+ 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);
41714
+ var stateAdapter = tooltipMgr === null || tooltipMgr === void 0 ? void 0 : tooltipMgr._stateAdapter;
41277
41715
  if (stateAdapter !== null && stateAdapter !== void 0 && stateAdapter.setState) {
41278
- var scopedKey = parentUuid ? "".concat(parentUuid, "::").concat(attachmentId) : attachmentId;
41716
+ var scopedKey = getScopedAttachmentKey(attachmentId, parentUuid);
41279
41717
  try {
41280
41718
  stateAdapter.setState(scopedKey, stateId, value);
41281
41719
  } catch (err) {
41282
- console.warn('⚠️ setIoDeviceState(): stateAdapter.setState() threw:', err);
41720
+ console.warn('⚠️ _dispatchIoState(): stateAdapter.setState() threw:', err);
41283
41721
  }
41284
41722
  }
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;
41723
+ 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
41724
  if (ioBehavMgr) {
41289
- var _this$importedSceneDa4;
41290
41725
  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
41726
  }
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', {
41727
+ (_this$sceneViewer8 = this.sceneViewer) === null || _this$sceneViewer8 === void 0 || _this$sceneViewer8.emit('io-device-state-changed', {
41300
41728
  attachmentId: attachmentId,
41301
41729
  stateId: stateId,
41302
41730
  value: value,
41303
41731
  parentUuid: parentUuid || null
41304
41732
  });
41733
+ }
41734
+
41735
+ /**
41736
+ * Configure the state adapter on both tooltip and behavior managers.
41737
+ * @param {{ getState: Function, setState: Function }} stateAdapter
41738
+ */
41739
+ }, {
41740
+ key: "configureStateAdapter",
41741
+ value: function configureStateAdapter(stateAdapter) {
41742
+ var _this$managers3, _this$sceneViewer9, _this$managers4, _this$sceneViewer1, _this$sceneViewer10;
41743
+ 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);
41744
+ if (tooltipMgr !== null && tooltipMgr !== void 0 && tooltipMgr.configure) {
41745
+ var _this$sceneViewer0;
41746
+ tooltipMgr.configure(stateAdapter);
41747
+ if ((_this$sceneViewer0 = this.sceneViewer) !== null && _this$sceneViewer0 !== void 0 && _this$sceneViewer0.managers) {
41748
+ this.sceneViewer.managers.componentTooltipManager = tooltipMgr;
41749
+ }
41750
+ }
41751
+ 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);
41752
+ if (ioBehavMgr !== null && ioBehavMgr !== void 0 && ioBehavMgr.configure) {
41753
+ ioBehavMgr.configure(stateAdapter);
41754
+ }
41755
+ }
41756
+
41757
+ /**
41758
+ * Set the state of an I/O device instance in the Three.js scene.
41759
+ *
41760
+ * @param {string} attachmentId - IO device attachment ID
41761
+ * @param {string} stateId - Data-point / state ID (e.g. 'power', 'level')
41762
+ * @param {*} value - New state value
41763
+ * @param {string} [parentUuid] - Parent component instance UUID
41764
+ * @returns {boolean}
41765
+ */
41766
+ }, {
41767
+ key: "setIoDeviceState",
41768
+ value: function setIoDeviceState(attachmentId, stateId, value, parentUuid) {
41769
+ this._dispatchIoState(attachmentId, stateId, value, parentUuid);
41305
41770
  return true;
41306
41771
  }
41307
41772
 
@@ -41318,8 +41783,8 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
41318
41783
  }, {
41319
41784
  key: "getSceneAttachments",
41320
41785
  value: function getSceneAttachments() {
41321
- var _this$sceneViewer8;
41322
- var scene = (_this$sceneViewer8 = this.sceneViewer) === null || _this$sceneViewer8 === void 0 ? void 0 : _this$sceneViewer8.scene;
41786
+ var _this$sceneViewer11;
41787
+ var scene = (_this$sceneViewer11 = this.sceneViewer) === null || _this$sceneViewer11 === void 0 ? void 0 : _this$sceneViewer11.scene;
41323
41788
  if (!scene) return [];
41324
41789
  var results = [];
41325
41790
  scene.traverse(function (obj) {
@@ -42027,6 +42492,58 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
42027
42492
  }
42028
42493
  return extendComponentDictionary;
42029
42494
  }()
42495
+ /**
42496
+ * Merge mesh animation configs into the component dictionary.
42497
+ * @param {Object<string, { behaviorConfig?: Array, meshNameMap?: Object }>} configsByAssetId
42498
+ * @returns {number} Count of dictionary entries updated
42499
+ */
42500
+ )
42501
+ }, {
42502
+ key: "mergeBehaviorConfigsIntoDictionary",
42503
+ value: function mergeBehaviorConfigsIntoDictionary(configsByAssetId) {
42504
+ if (!this.managers.componentDataManager) {
42505
+ console.warn('⚠️ mergeBehaviorConfigsIntoDictionary(): Component data manager not available');
42506
+ return 0;
42507
+ }
42508
+ return this.managers.componentDataManager.mergeBehaviorConfigsIntoDictionary(configsByAssetId);
42509
+ }
42510
+
42511
+ /**
42512
+ * Scan live scene for I/O device endpoints (for behavior authoring UIs).
42513
+ */
42514
+ }, {
42515
+ key: "scanSceneIoEndpoints",
42516
+ value: function scanSceneIoEndpoints$1() {
42517
+ return scanSceneIoEndpoints(this);
42518
+ }
42519
+
42520
+ /**
42521
+ * Apply cross-component behaviors to scene data and the runtime manager.
42522
+ */
42523
+ }, {
42524
+ key: "setSceneBehaviors",
42525
+ value: function setSceneBehaviors(behaviors) {
42526
+ applyCrossComponentBehaviors(this, behaviors);
42527
+ }
42528
+
42529
+ /**
42530
+ * Read cross-component behaviors from the current scene data mirrors.
42531
+ */
42532
+ }, {
42533
+ key: "getSceneBehaviors",
42534
+ value: function getSceneBehaviors() {
42535
+ return loadCrossComponentBehaviors(this);
42536
+ }
42537
+
42538
+ /**
42539
+ * Re-register all intra- and cross-component behaviors on placed instances.
42540
+ */
42541
+ }, {
42542
+ key: "reregisterSceneBehaviors",
42543
+ value: function reregisterSceneBehaviors$1() {
42544
+ reregisterSceneBehaviors(this);
42545
+ }
42546
+
42030
42547
  /**
42031
42548
  * Remove S3 components from component dictionary
42032
42549
  * @returns {Promise<boolean>} True if components were removed successfully, false otherwise
@@ -42039,7 +42556,6 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
42039
42556
  * console.log('S3 components removed');
42040
42557
  * }
42041
42558
  */
42042
- )
42043
42559
  }, {
42044
42560
  key: "removeS3Components",
42045
42561
  value: (function () {
@@ -42946,9 +43462,9 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
42946
43462
  }, {
42947
43463
  key: "clearScene",
42948
43464
  value: function clearScene() {
42949
- var _this$sceneViewer9;
43465
+ var _this$sceneViewer12;
42950
43466
  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;
43467
+ 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
43468
  if (ioBehavMgr) {
42953
43469
  ioBehavMgr.setCrossComponentBehaviors([]);
42954
43470
  }
@@ -48827,6 +49343,11 @@ exports.SceneOperationsManager = SceneOperationsManager;
48827
49343
  exports.SceneTooltipsManager = SceneTooltipsManager;
48828
49344
  exports.SnapshotManager = SnapshotManager;
48829
49345
  exports.ThreeJSResourceManager = ThreeJSResourceManager;
49346
+ exports.applyCrossComponentBehaviors = applyCrossComponentBehaviors;
49347
+ exports.applyModelRootTranslation = applyModelRootTranslation;
49348
+ exports.applyModelRootTranslationFromWorldBase = applyModelRootTranslationFromWorldBase;
49349
+ exports.buildCrossBehavior = buildCrossBehavior;
49350
+ exports.buildIntraBehavior = buildIntraBehavior;
48830
49351
  exports.cacheJsonData = cacheJsonData;
48831
49352
  exports.cacheManager = cacheManager;
48832
49353
  exports.cleanExpiredCache = cleanExpiredCache;
@@ -48852,8 +49373,10 @@ exports.getCurrentCacheName = getCurrentCacheName;
48852
49373
  exports.getGlobalCacheStats = getGlobalCacheStats;
48853
49374
  exports.getGlobalOnlyCacheStats = getGlobalOnlyCacheStats;
48854
49375
  exports.getHardcodedUuid = getHardcodedUuid;
49376
+ exports.getIoBehaviorManager = getIoBehaviorManager;
48855
49377
  exports.getObjectTypeName = getObjectTypeName;
48856
49378
  exports.getObjectsByType = getObjectsByType;
49379
+ exports.getScopedAttachmentKey = getScopedAttachmentKey;
48857
49380
  exports.getThumbnailKey = getThumbnailKey;
48858
49381
  exports.getUserOnlyCacheStats = getUserOnlyCacheStats;
48859
49382
  exports.isCached = isCached;
@@ -48865,17 +49388,28 @@ exports.isExportable = isExportable;
48865
49388
  exports.isGateway = isGateway;
48866
49389
  exports.isSegment = isSegment;
48867
49390
  exports.isThumbnailCached = isThumbnailCached;
49391
+ exports.loadCrossComponentBehaviors = loadCrossComponentBehaviors;
48868
49392
  exports.loadTextureSetAndCreateMaterial = loadTextureSetAndCreateMaterial;
48869
49393
  exports.markAsComputed = markAsComputed;
48870
49394
  exports.markAsDeclared = markAsDeclared;
48871
49395
  exports.measureS3Transfer = measureS3Transfer;
49396
+ exports.modelOffsetToWorldDelta = modelOffsetToWorldDelta;
48872
49397
  exports.modelPreloader = modelPreloader;
49398
+ exports.normalizeBehavior = normalizeBehavior;
49399
+ exports.parseCrossBehavior = parseCrossBehavior;
49400
+ exports.parseIntraBehavior = parseIntraBehavior;
48873
49401
  exports.preloadLocalFiles = preloadLocalFiles;
48874
49402
  exports.preloadS3Objects = preloadS3Objects;
49403
+ exports.refreshSceneIntraBehaviors = refreshSceneIntraBehaviors;
49404
+ exports.registerBehaviorsForComponent = registerBehaviorsForComponent;
49405
+ exports.reloadBehaviorsForDeviceAsset = reloadBehaviorsForDeviceAsset;
48875
49406
  exports.removeCachedJsonData = removeCachedJsonData;
49407
+ exports.reregisterSceneBehaviors = reregisterSceneBehaviors;
48876
49408
  exports.resetCacheIdentity = resetCacheIdentity;
48877
49409
  exports.resetGlobalCacheStats = resetGlobalCacheStats;
49410
+ exports.resolveDataPoints = resolveDataPoints;
48878
49411
  exports.s3MetadataCache = s3MetadataCache;
49412
+ exports.scanSceneIoEndpoints = scanSceneIoEndpoints;
48879
49413
  exports.sceneViewer = sceneViewer;
48880
49414
  exports.shouldRemoveOnRegeneration = shouldRemoveOnRegeneration;
48881
49415
  exports.switchCachePartition = switchCachePartition;