@2112-lab/central-plant 0.3.21 → 0.3.22

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.
@@ -31463,12 +31463,12 @@ var ModelManager = /*#__PURE__*/function () {
31463
31463
  key: "loadLibraryModel",
31464
31464
  value: function () {
31465
31465
  var _loadLibraryModel = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(targetMesh, jsonEntry, componentData) {
31466
- var component, _jsonEntry$userData, _jsonEntry$userData2, _jsonEntry$userData3, originalProps, connectorChildren, gltfScene, libraryModel, _jsonEntry$userData4, _t;
31467
- return _regenerator().w(function (_context) {
31468
- while (1) switch (_context.n) {
31466
+ var component, _jsonEntry$userData, _jsonEntry$userData2, _jsonEntry$userData3, originalProps, connectorChildren, gltfScene, libraryModel, _this$sceneViewer, ioAnimMgr, _loop, _i, _Object$entries, _jsonEntry$userData4, _t;
31467
+ return _regenerator().w(function (_context2) {
31468
+ while (1) switch (_context2.n) {
31469
31469
  case 0:
31470
31470
  component = this.sceneViewer;
31471
- _context.p = 1;
31471
+ _context2.p = 1;
31472
31472
  console.log("Loading library GLB model for ".concat((_jsonEntry$userData = jsonEntry.userData) === null || _jsonEntry$userData === void 0 ? void 0 : _jsonEntry$userData.libraryId, "..."));
31473
31473
 
31474
31474
  // Store original mesh properties before async operation
@@ -31481,16 +31481,16 @@ var ModelManager = /*#__PURE__*/function () {
31481
31481
  uuid: targetMesh.uuid
31482
31482
  }; // Preserve connector children (pass parent UUID for proper connector UUID generation)
31483
31483
  connectorChildren = this._preserveConnectorChildren(targetMesh, originalProps.uuid); // Get model from cache or load directly
31484
- _context.n = 2;
31484
+ _context2.n = 2;
31485
31485
  return this._getLibraryModel(componentData.modelKey, (_jsonEntry$userData2 = jsonEntry.userData) === null || _jsonEntry$userData2 === void 0 ? void 0 : _jsonEntry$userData2.libraryId);
31486
31486
  case 2:
31487
- gltfScene = _context.v;
31487
+ gltfScene = _context2.v;
31488
31488
  if (gltfScene) {
31489
- _context.n = 3;
31489
+ _context2.n = 3;
31490
31490
  break;
31491
31491
  }
31492
31492
  console.warn("\u26A0\uFE0F Could not load model ".concat(componentData.modelKey, ", keeping original mesh"));
31493
- return _context.a(2, targetMesh);
31493
+ return _context2.a(2, targetMesh);
31494
31494
  case 3:
31495
31495
  // Configure the loaded model
31496
31496
  libraryModel = this._configureLibraryModel(gltfScene, jsonEntry, componentData, originalProps); // Add preserved connectors
@@ -31500,23 +31500,74 @@ var ModelManager = /*#__PURE__*/function () {
31500
31500
 
31501
31501
  // Attach IO devices for smart components (import flow)
31502
31502
  if (!(componentData.isSmart && componentData.attachedDevices)) {
31503
- _context.n = 4;
31503
+ _context2.n = 8;
31504
31504
  break;
31505
31505
  }
31506
- _context.n = 4;
31506
+ _context2.n = 4;
31507
31507
  return attachIODevicesToComponent(libraryModel, componentData, modelPreloader, originalProps.uuid);
31508
31508
  case 4:
31509
+ // Register animation configs for each attached device
31510
+ ioAnimMgr = (_this$sceneViewer = this.sceneViewer) === null || _this$sceneViewer === void 0 || (_this$sceneViewer = _this$sceneViewer.managers) === null || _this$sceneViewer === void 0 ? void 0 : _this$sceneViewer.ioAnimationManager;
31511
+ if (!ioAnimMgr) {
31512
+ _context2.n = 8;
31513
+ break;
31514
+ }
31515
+ _loop = /*#__PURE__*/_regenerator().m(function _loop() {
31516
+ var _modelPreloader$compo;
31517
+ var _Object$entries$_i, attachmentId, attachment, deviceData, deviceRoot;
31518
+ return _regenerator().w(function (_context) {
31519
+ while (1) switch (_context.n) {
31520
+ case 0:
31521
+ _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2), attachmentId = _Object$entries$_i[0], attachment = _Object$entries$_i[1];
31522
+ deviceData = (_modelPreloader$compo = modelPreloader.componentDictionary) === null || _modelPreloader$compo === void 0 ? void 0 : _modelPreloader$compo[attachment.deviceId];
31523
+ if (deviceData !== null && deviceData !== void 0 && deviceData.animationConfig) {
31524
+ _context.n = 1;
31525
+ break;
31526
+ }
31527
+ return _context.a(2, 1);
31528
+ case 1:
31529
+ deviceRoot = null;
31530
+ libraryModel.traverse(function (obj) {
31531
+ var _obj$userData;
31532
+ if (!deviceRoot && ((_obj$userData = obj.userData) === null || _obj$userData === void 0 ? void 0 : _obj$userData.attachmentId) === attachmentId) deviceRoot = obj;
31533
+ });
31534
+ if (deviceRoot) {
31535
+ ioAnimMgr.loadAnimations(attachmentId, deviceData.animationConfig, deviceRoot, originalProps.uuid);
31536
+ }
31537
+ case 2:
31538
+ return _context.a(2);
31539
+ }
31540
+ }, _loop);
31541
+ });
31542
+ _i = 0, _Object$entries = Object.entries(componentData.attachedDevices);
31543
+ case 5:
31544
+ if (!(_i < _Object$entries.length)) {
31545
+ _context2.n = 8;
31546
+ break;
31547
+ }
31548
+ return _context2.d(_regeneratorValues(_loop()), 6);
31549
+ case 6:
31550
+ if (!_context2.v) {
31551
+ _context2.n = 7;
31552
+ break;
31553
+ }
31554
+ return _context2.a(3, 7);
31555
+ case 7:
31556
+ _i++;
31557
+ _context2.n = 5;
31558
+ break;
31559
+ case 8:
31509
31560
  // Replace mesh in scene
31510
31561
  this._replaceMeshInScene(targetMesh, libraryModel, originalProps.parent, component);
31511
31562
  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"));
31512
- return _context.a(2, libraryModel);
31513
- case 5:
31514
- _context.p = 5;
31515
- _t = _context.v;
31563
+ return _context2.a(2, libraryModel);
31564
+ case 9:
31565
+ _context2.p = 9;
31566
+ _t = _context2.v;
31516
31567
  console.error("\u274C Error loading ".concat((_jsonEntry$userData4 = jsonEntry.userData) === null || _jsonEntry$userData4 === void 0 ? void 0 : _jsonEntry$userData4.libraryId, " GLB model:"), _t);
31517
- return _context.a(2, targetMesh);
31568
+ return _context2.a(2, targetMesh);
31518
31569
  }
31519
- }, _callee, this, [[1, 5]]);
31570
+ }, _callee, this, [[1, 9]]);
31520
31571
  }));
31521
31572
  function loadLibraryModel(_x, _x2, _x3) {
31522
31573
  return _loadLibraryModel.apply(this, arguments);
@@ -31595,84 +31646,84 @@ var ModelManager = /*#__PURE__*/function () {
31595
31646
  var _getLibraryModel2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2(modelKey, libraryId) {
31596
31647
  var _this2 = this;
31597
31648
  var gltfScene, preloaderStatus, modelPath, gltf, _t2, _t3, _t4;
31598
- return _regenerator().w(function (_context2) {
31599
- while (1) switch (_context2.n) {
31649
+ return _regenerator().w(function (_context3) {
31650
+ while (1) switch (_context3.n) {
31600
31651
  case 0:
31601
31652
  // Try cache first
31602
31653
  gltfScene = modelPreloader.getCachedModelWithDimensions(modelKey, libraryId);
31603
31654
  if (!gltfScene) {
31604
- _context2.n = 1;
31655
+ _context3.n = 1;
31605
31656
  break;
31606
31657
  }
31607
31658
  console.log("\uD83C\uDFAF GLB cache HIT: ".concat(modelKey));
31608
- return _context2.a(2, gltfScene);
31659
+ return _context3.a(2, gltfScene);
31609
31660
  case 1:
31610
31661
  // Check if preloading is in progress
31611
31662
  preloaderStatus = modelPreloader.getStatus();
31612
31663
  if (!preloaderStatus.isPreloading) {
31613
- _context2.n = 6;
31664
+ _context3.n = 6;
31614
31665
  break;
31615
31666
  }
31616
31667
  console.log("\u23F3 Waiting for preloader: ".concat(modelKey));
31617
- _context2.p = 2;
31618
- _context2.n = 3;
31668
+ _context3.p = 2;
31669
+ _context3.n = 3;
31619
31670
  return modelPreloader.preloadingPromise;
31620
31671
  case 3:
31621
31672
  gltfScene = modelPreloader.getCachedModelWithDimensions(modelKey, libraryId);
31622
31673
  if (!gltfScene) {
31623
- _context2.n = 4;
31674
+ _context3.n = 4;
31624
31675
  break;
31625
31676
  }
31626
31677
  console.log("\uD83C\uDFAF GLB cache HIT (after preload): ".concat(modelKey));
31627
- return _context2.a(2, gltfScene);
31678
+ return _context3.a(2, gltfScene);
31628
31679
  case 4:
31629
- _context2.n = 6;
31680
+ _context3.n = 6;
31630
31681
  break;
31631
31682
  case 5:
31632
- _context2.p = 5;
31633
- _t2 = _context2.v;
31683
+ _context3.p = 5;
31684
+ _t2 = _context3.v;
31634
31685
  console.warn("\u26A0\uFE0F Preloading failed:", _t2);
31635
31686
  case 6:
31636
- _context2.p = 6;
31687
+ _context3.p = 6;
31637
31688
  if (!modelPreloader.urlResolver) {
31638
- _context2.n = 11;
31689
+ _context3.n = 11;
31639
31690
  break;
31640
31691
  }
31641
- _context2.p = 7;
31642
- _context2.n = 8;
31692
+ _context3.p = 7;
31693
+ _context3.n = 8;
31643
31694
  return modelPreloader.urlResolver(modelKey);
31644
31695
  case 8:
31645
- modelPath = _context2.v;
31696
+ modelPath = _context3.v;
31646
31697
  console.log("\uD83D\uDD17 Resolved URL for ".concat(modelKey));
31647
- _context2.n = 10;
31698
+ _context3.n = 10;
31648
31699
  break;
31649
31700
  case 9:
31650
- _context2.p = 9;
31651
- _t3 = _context2.v;
31701
+ _context3.p = 9;
31702
+ _t3 = _context3.v;
31652
31703
  console.warn("\u26A0\uFE0F URL resolver failed for ".concat(modelKey, ", falling back to local path:"), _t3);
31653
31704
  modelPath = "".concat(modelPreloader.modelsBasePath).concat(modelKey);
31654
31705
  case 10:
31655
- _context2.n = 12;
31706
+ _context3.n = 12;
31656
31707
  break;
31657
31708
  case 11:
31658
31709
  modelPath = "".concat(modelPreloader.modelsBasePath).concat(modelKey);
31659
31710
  case 12:
31660
31711
  console.log("\uD83D\uDCC2 Fallback loading from: ".concat(modelPath));
31661
- _context2.n = 13;
31712
+ _context3.n = 13;
31662
31713
  return new Promise(function (resolve, reject) {
31663
31714
  _this2.sceneViewer.gltfLoader.load(modelPath, resolve, undefined, reject);
31664
31715
  });
31665
31716
  case 13:
31666
- gltf = _context2.v;
31717
+ gltf = _context3.v;
31667
31718
  if (libraryId) {
31668
31719
  modelPreloader.cacheModel(modelKey, gltf.scene.clone(), libraryId);
31669
31720
  }
31670
- return _context2.a(2, gltf.scene);
31721
+ return _context3.a(2, gltf.scene);
31671
31722
  case 14:
31672
- _context2.p = 14;
31673
- _t4 = _context2.v;
31723
+ _context3.p = 14;
31724
+ _t4 = _context3.v;
31674
31725
  console.error("Failed to load model ".concat(modelKey, ":"), _t4);
31675
- return _context2.a(2, null);
31726
+ return _context3.a(2, null);
31676
31727
  }
31677
31728
  }, _callee2, null, [[7, 9], [6, 14], [2, 5]]);
31678
31729
  }));
@@ -31779,8 +31830,8 @@ var ModelManager = /*#__PURE__*/function () {
31779
31830
  value: (function () {
31780
31831
  var _verifyModelPreloaderCache = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee3() {
31781
31832
  var preloaderStatus, _t5;
31782
- return _regenerator().w(function (_context3) {
31783
- while (1) switch (_context3.n) {
31833
+ return _regenerator().w(function (_context4) {
31834
+ while (1) switch (_context4.n) {
31784
31835
  case 0:
31785
31836
  console.log('🔍 Verifying model preloader cache state...');
31786
31837
  preloaderStatus = modelPreloader.getStatus();
@@ -31788,23 +31839,23 @@ var ModelManager = /*#__PURE__*/function () {
31788
31839
 
31789
31840
  // If preloading is still in progress, wait for completion
31790
31841
  if (!preloaderStatus.isPreloading) {
31791
- _context3.n = 4;
31842
+ _context4.n = 4;
31792
31843
  break;
31793
31844
  }
31794
31845
  console.log('⏳ Waiting for model preloading to complete...');
31795
- _context3.p = 1;
31796
- _context3.n = 2;
31846
+ _context4.p = 1;
31847
+ _context4.n = 2;
31797
31848
  return modelPreloader.preloadingPromise;
31798
31849
  case 2:
31799
31850
  console.log('✅ Model preloading completed');
31800
- _context3.n = 4;
31851
+ _context4.n = 4;
31801
31852
  break;
31802
31853
  case 3:
31803
- _context3.p = 3;
31804
- _t5 = _context3.v;
31854
+ _context4.p = 3;
31855
+ _t5 = _context4.v;
31805
31856
  console.warn('⚠️ Model preloading failed, some models may load directly:', _t5);
31806
31857
  case 4:
31807
- return _context3.a(2, preloaderStatus);
31858
+ return _context4.a(2, preloaderStatus);
31808
31859
  }
31809
31860
  }, _callee3, null, [[1, 3]]);
31810
31861
  }));
@@ -31822,11 +31873,11 @@ var ModelManager = /*#__PURE__*/function () {
31822
31873
  value: (function () {
31823
31874
  var _preloadMissingModels = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee4(data, componentDictionary) {
31824
31875
  var _data$scene, _data$scene2, requiredModels, preloaderStatus, cachedModels, missingModels, basePath, modelUrls, _iterator, _step, modelKey, _t6, _t7;
31825
- return _regenerator().w(function (_context4) {
31826
- while (1) switch (_context4.n) {
31876
+ return _regenerator().w(function (_context5) {
31877
+ while (1) switch (_context5.n) {
31827
31878
  case 0:
31828
31879
  if (!(!data || !data.scene || !Array.isArray(data.scene.children))) {
31829
- _context4.n = 1;
31880
+ _context5.n = 1;
31830
31881
  break;
31831
31882
  }
31832
31883
  console.warn('⚠️ Invalid scene data structure in preloadMissingModels:', {
@@ -31835,7 +31886,7 @@ var ModelManager = /*#__PURE__*/function () {
31835
31886
  hasChildren: !!(data !== null && data !== void 0 && (_data$scene = data.scene) !== null && _data$scene !== void 0 && _data$scene.children),
31836
31887
  childrenType: data !== null && data !== void 0 && (_data$scene2 = data.scene) !== null && _data$scene2 !== void 0 && _data$scene2.children ? _typeof(data.scene.children) : 'undefined'
31837
31888
  });
31838
- return _context4.a(2);
31889
+ return _context5.a(2);
31839
31890
  case 1:
31840
31891
  requiredModels = new Set();
31841
31892
  console.log("\uD83D\uDD0D Checking ".concat(data.scene.children.length, " scene objects for required models..."));
@@ -31857,10 +31908,10 @@ var ModelManager = /*#__PURE__*/function () {
31857
31908
  }
31858
31909
  });
31859
31910
  console.log('🔍 Required models for this scene (Set):', Array.from(requiredModels));
31860
- _context4.n = 2;
31911
+ _context5.n = 2;
31861
31912
  return this.verifyModelPreloaderCache();
31862
31913
  case 2:
31863
- preloaderStatus = _context4.v;
31914
+ preloaderStatus = _context5.v;
31864
31915
  cachedModels = preloaderStatus.cachedModels;
31865
31916
  console.log('ModelPreloader cached models:', cachedModels);
31866
31917
 
@@ -31891,7 +31942,7 @@ var ModelManager = /*#__PURE__*/function () {
31891
31942
  });
31892
31943
  }
31893
31944
  if (!(missingModels.length > 0)) {
31894
- _context4.n = 12;
31945
+ _context5.n = 12;
31895
31946
  break;
31896
31947
  }
31897
31948
  console.warn('⚠️ Some required models are not cached:', missingModels);
@@ -31899,41 +31950,41 @@ var ModelManager = /*#__PURE__*/function () {
31899
31950
 
31900
31951
  // Preload missing models (Main in-memory preloader)
31901
31952
  _iterator = _createForOfIteratorHelper(missingModels);
31902
- _context4.p = 3;
31953
+ _context5.p = 3;
31903
31954
  _iterator.s();
31904
31955
  case 4:
31905
31956
  if ((_step = _iterator.n()).done) {
31906
- _context4.n = 9;
31957
+ _context5.n = 9;
31907
31958
  break;
31908
31959
  }
31909
31960
  modelKey = _step.value;
31910
- _context4.p = 5;
31911
- _context4.n = 6;
31961
+ _context5.p = 5;
31962
+ _context5.n = 6;
31912
31963
  return modelPreloader.preloadSingleModel(modelKey);
31913
31964
  case 6:
31914
31965
  console.log("\u2705 Successfully preloaded missing model: ".concat(modelKey));
31915
- _context4.n = 8;
31966
+ _context5.n = 8;
31916
31967
  break;
31917
31968
  case 7:
31918
- _context4.p = 7;
31919
- _t6 = _context4.v;
31969
+ _context5.p = 7;
31970
+ _t6 = _context5.v;
31920
31971
  console.warn("\u274C Failed to preload missing model ".concat(modelKey, ":"), _t6);
31921
31972
  case 8:
31922
- _context4.n = 4;
31973
+ _context5.n = 4;
31923
31974
  break;
31924
31975
  case 9:
31925
- _context4.n = 11;
31976
+ _context5.n = 11;
31926
31977
  break;
31927
31978
  case 10:
31928
- _context4.p = 10;
31929
- _t7 = _context4.v;
31979
+ _context5.p = 10;
31980
+ _t7 = _context5.v;
31930
31981
  _iterator.e(_t7);
31931
31982
  case 11:
31932
- _context4.p = 11;
31983
+ _context5.p = 11;
31933
31984
  _iterator.f();
31934
- return _context4.f(11);
31985
+ return _context5.f(11);
31935
31986
  case 12:
31936
- return _context4.a(2);
31987
+ return _context5.a(2);
31937
31988
  }
31938
31989
  }, _callee4, this, [[5, 7], [3, 10, 11, 12]]);
31939
31990
  }));
@@ -31952,14 +32003,14 @@ var ModelManager = /*#__PURE__*/function () {
31952
32003
  var _replaceWithGLBModels = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee5(libraryObjectsToReplace) {
31953
32004
  var _this3 = this;
31954
32005
  var startTime, glbLoadingPromises;
31955
- return _regenerator().w(function (_context5) {
31956
- while (1) switch (_context5.n) {
32006
+ return _regenerator().w(function (_context6) {
32007
+ while (1) switch (_context6.n) {
31957
32008
  case 0:
31958
32009
  if (!(libraryObjectsToReplace.length === 0)) {
31959
- _context5.n = 1;
32010
+ _context6.n = 1;
31960
32011
  break;
31961
32012
  }
31962
- return _context5.a(2);
32013
+ return _context6.a(2);
31963
32014
  case 1:
31964
32015
  startTime = performance.now();
31965
32016
  console.log("\uD83D\uDD04 Replacing ".concat(libraryObjectsToReplace.length, " objects with GLB models..."));
@@ -31979,7 +32030,7 @@ var ModelManager = /*#__PURE__*/function () {
31979
32030
  return basicObject;
31980
32031
  });
31981
32032
  });
31982
- _context5.n = 2;
32033
+ _context6.n = 2;
31983
32034
  return Promise.all(glbLoadingPromises);
31984
32035
  case 2:
31985
32036
  console.log("\u23F1\uFE0F All GLB models loaded in ".concat((performance.now() - startTime).toFixed(0), "ms"));
@@ -32022,7 +32073,7 @@ var ModelManager = /*#__PURE__*/function () {
32022
32073
  }));
32023
32074
  }
32024
32075
  case 3:
32025
- return _context5.a(2);
32076
+ return _context6.a(2);
32026
32077
  }
32027
32078
  }, _callee5);
32028
32079
  }));
@@ -32057,26 +32108,26 @@ var ModelManager = /*#__PURE__*/function () {
32057
32108
  value: (function () {
32058
32109
  var _loadComponentDictionary = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee6() {
32059
32110
  var response, dict, _t8;
32060
- return _regenerator().w(function (_context6) {
32061
- while (1) switch (_context6.n) {
32111
+ return _regenerator().w(function (_context7) {
32112
+ while (1) switch (_context7.n) {
32062
32113
  case 0:
32063
- _context6.p = 0;
32114
+ _context7.p = 0;
32064
32115
  console.log('🔄 ModelManager loading component dictionary...');
32065
- _context6.n = 1;
32116
+ _context7.n = 1;
32066
32117
  return fetch('/library/component-dictionary.json');
32067
32118
  case 1:
32068
- response = _context6.v;
32069
- _context6.n = 2;
32119
+ response = _context7.v;
32120
+ _context7.n = 2;
32070
32121
  return response.json();
32071
32122
  case 2:
32072
- dict = _context6.v;
32123
+ dict = _context7.v;
32073
32124
  console.log('✅ ModelManager loaded dictionary:', Object.keys(dict).length, 'entries');
32074
- return _context6.a(2, dict);
32125
+ return _context7.a(2, dict);
32075
32126
  case 3:
32076
- _context6.p = 3;
32077
- _t8 = _context6.v;
32127
+ _context7.p = 3;
32128
+ _t8 = _context7.v;
32078
32129
  console.warn('⚠️ Could not load component dictionary:', _t8);
32079
- return _context6.a(2, {});
32130
+ return _context7.a(2, {});
32080
32131
  }
32081
32132
  }, _callee6, null, [[0, 3]]);
32082
32133
  }));
@@ -36978,7 +37029,7 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
36978
37029
  }, {
36979
37030
  key: "toggleIODeviceBinaryState",
36980
37031
  value: function toggleIODeviceBinaryState(ioDeviceObject) {
36981
- var _ref, _this$sceneViewer;
37032
+ var _ref, _this$sceneViewer, _this$sceneViewer2;
36982
37033
  if (!ioDeviceObject || !this._stateAdapter) return;
36983
37034
  var ud = ioDeviceObject.userData;
36984
37035
  var attachmentId = ud === null || ud === void 0 ? void 0 : ud.attachmentId;
@@ -37013,6 +37064,7 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
37013
37064
  var newVal = !Boolean(currentVal);
37014
37065
  this._stateAdapter.setState(scopedAttachmentId, dpId, newVal);
37015
37066
  (_this$sceneViewer = this.sceneViewer) === null || _this$sceneViewer === void 0 || (_this$sceneViewer = _this$sceneViewer.managers) === null || _this$sceneViewer === void 0 || (_this$sceneViewer = _this$sceneViewer.behaviorManager) === null || _this$sceneViewer === void 0 || _this$sceneViewer.triggerState(attachmentId, dpId, newVal, parentUuid);
37067
+ (_this$sceneViewer2 = this.sceneViewer) === null || _this$sceneViewer2 === void 0 || (_this$sceneViewer2 = _this$sceneViewer2.managers) === null || _this$sceneViewer2 === void 0 || (_this$sceneViewer2 = _this$sceneViewer2.ioAnimationManager) === null || _this$sceneViewer2 === void 0 || _this$sceneViewer2.triggerState(attachmentId, dpId, newVal, parentUuid);
37016
37068
  console.log("\uD83D\uDD04 [IODevice] Toggled ".concat(scopedAttachmentId, ".").concat(dpId, ": ").concat(currentVal, " \u2192 ").concat(newVal));
37017
37069
  }
37018
37070
 
@@ -37286,11 +37338,11 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
37286
37338
  }, {
37287
37339
  key: "_positionTooltip",
37288
37340
  value: function _positionTooltip() {
37289
- var _this$sceneViewer2, _this$sceneViewer3;
37341
+ var _this$sceneViewer3, _this$sceneViewer4;
37290
37342
  if (!this.tooltipEl || !this.selectedObject) return;
37291
37343
  var container = this._getContainer();
37292
- var camera = (_this$sceneViewer2 = this.sceneViewer) === null || _this$sceneViewer2 === void 0 ? void 0 : _this$sceneViewer2.camera;
37293
- var renderer = (_this$sceneViewer3 = this.sceneViewer) === null || _this$sceneViewer3 === void 0 ? void 0 : _this$sceneViewer3.renderer;
37344
+ var camera = (_this$sceneViewer3 = this.sceneViewer) === null || _this$sceneViewer3 === void 0 ? void 0 : _this$sceneViewer3.camera;
37345
+ var renderer = (_this$sceneViewer4 = this.sceneViewer) === null || _this$sceneViewer4 === void 0 ? void 0 : _this$sceneViewer4.renderer;
37294
37346
  if (!container || !camera || !renderer) return;
37295
37347
 
37296
37348
  // Compute bounding box to position above the component
@@ -37327,8 +37379,8 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
37327
37379
  }, {
37328
37380
  key: "_getContainer",
37329
37381
  value: function _getContainer() {
37330
- var _this$sceneViewer4;
37331
- return ((_this$sceneViewer4 = this.sceneViewer) === null || _this$sceneViewer4 === void 0 || (_this$sceneViewer4 = _this$sceneViewer4.renderer) === null || _this$sceneViewer4 === void 0 || (_this$sceneViewer4 = _this$sceneViewer4.domElement) === null || _this$sceneViewer4 === void 0 ? void 0 : _this$sceneViewer4.parentElement) || null;
37382
+ var _this$sceneViewer5;
37383
+ return ((_this$sceneViewer5 = this.sceneViewer) === null || _this$sceneViewer5 === void 0 || (_this$sceneViewer5 = _this$sceneViewer5.renderer) === null || _this$sceneViewer5 === void 0 || (_this$sceneViewer5 = _this$sceneViewer5.domElement) === null || _this$sceneViewer5 === void 0 ? void 0 : _this$sceneViewer5.parentElement) || null;
37332
37384
  }
37333
37385
 
37334
37386
  // -----------------------------------------------------------------------
@@ -37368,7 +37420,7 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
37368
37420
  var currentVal = (_ref2 = (_this$_stateAdapter$g = (_this$_stateAdapter = this._stateAdapter) === null || _this$_stateAdapter === void 0 ? void 0 : _this$_stateAdapter.getState(scopedAttachmentId, dpId)) !== null && _this$_stateAdapter$g !== void 0 ? _this$_stateAdapter$g : dp.defaultValue) !== null && _ref2 !== void 0 ? _ref2 : null;
37369
37421
  if (isInput) {
37370
37422
  var ctrl = this._buildInputControl(dp, currentVal, function (newVal) {
37371
- var _this4$_stateAdapter, _this4$selectedObject, _this4$sceneViewer;
37423
+ var _this4$_stateAdapter, _this4$selectedObject, _this4$sceneViewer, _this4$sceneViewer2;
37372
37424
  (_this4$_stateAdapter = _this4._stateAdapter) === null || _this4$_stateAdapter === void 0 || _this4$_stateAdapter.setState(scopedAttachmentId, dpId, newVal);
37373
37425
  // Also fire BehaviorManager so any wired behaviors react immediately.
37374
37426
  // Pass the parent component UUID so behaviors scoped to a specific instance
@@ -37376,6 +37428,7 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
37376
37428
  // Use originalAttachmentId for behavior triggering as behaviors are keyed by original ID
37377
37429
  var parentUuid = ((_this4$selectedObject = _this4.selectedObject) === null || _this4$selectedObject === void 0 ? void 0 : _this4$selectedObject.uuid) || null;
37378
37430
  (_this4$sceneViewer = _this4.sceneViewer) === null || _this4$sceneViewer === void 0 || (_this4$sceneViewer = _this4$sceneViewer.managers) === null || _this4$sceneViewer === void 0 || (_this4$sceneViewer = _this4$sceneViewer.behaviorManager) === null || _this4$sceneViewer === void 0 || _this4$sceneViewer.triggerState(originalAttachmentId || scopedAttachmentId, dpId, newVal, parentUuid);
37431
+ (_this4$sceneViewer2 = _this4.sceneViewer) === null || _this4$sceneViewer2 === void 0 || (_this4$sceneViewer2 = _this4$sceneViewer2.managers) === null || _this4$sceneViewer2 === void 0 || (_this4$sceneViewer2 = _this4$sceneViewer2.ioAnimationManager) === null || _this4$sceneViewer2 === void 0 || _this4$sceneViewer2.triggerState(originalAttachmentId || scopedAttachmentId, dpId, newVal, parentUuid);
37379
37432
  });
37380
37433
  row.appendChild(ctrl);
37381
37434
  this._stateElements.set(key, {
@@ -37547,6 +37600,399 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
37547
37600
  }]);
37548
37601
  }(BaseDisposable);
37549
37602
 
37603
+ var IoAnimationManager = /*#__PURE__*/function (_BaseDisposable) {
37604
+ function IoAnimationManager(sceneViewer) {
37605
+ var _this;
37606
+ _classCallCheck(this, IoAnimationManager);
37607
+ _this = _callSuper(this, IoAnimationManager);
37608
+ _this.sceneViewer = sceneViewer;
37609
+
37610
+ /**
37611
+ * Map: `${parentUuid}::${attachmentId}` → Array<{
37612
+ * anim: Object,
37613
+ * mesh: THREE.Object3D,
37614
+ * origPos: THREE.Vector3,
37615
+ * origRot: THREE.Euler
37616
+ * }>
37617
+ */
37618
+ _this._entries = new Map();
37619
+ return _this;
37620
+ }
37621
+
37622
+ // ─────────────────────────────────────────────────────────────────────────
37623
+ // PUBLIC API
37624
+ // ─────────────────────────────────────────────────────────────────────────
37625
+
37626
+ /**
37627
+ * Register animation entries for one attached I/O device.
37628
+ * Should be called after the device GLB has been merged into the scene
37629
+ * so that mesh references are live.
37630
+ *
37631
+ * @param {string} attachmentId - The attachment key (e.g. 'attch-switch-01')
37632
+ * @param {Object|Array} animationConfig - Serialized config; either the full object
37633
+ * { animations: [...] } or a plain array of animation entries.
37634
+ * @param {THREE.Object3D} deviceModelRoot - The device's root Object3D as added to the scene
37635
+ * @param {string} parentUuid - UUID of the host component Object3D
37636
+ */
37637
+ _inherits(IoAnimationManager, _BaseDisposable);
37638
+ return _createClass(IoAnimationManager, [{
37639
+ key: "loadAnimations",
37640
+ value: function loadAnimations(attachmentId, animationConfig, deviceModelRoot, parentUuid) {
37641
+ var _animationConfig$anim;
37642
+ if (!animationConfig || !deviceModelRoot) return;
37643
+ var anims = Array.isArray(animationConfig) ? animationConfig : (_animationConfig$anim = animationConfig.animations) !== null && _animationConfig$anim !== void 0 ? _animationConfig$anim : [];
37644
+ if (!anims.length) return;
37645
+ var key = this._key(parentUuid, attachmentId);
37646
+ var entries = [];
37647
+ var _iterator = _createForOfIteratorHelper(anims),
37648
+ _step;
37649
+ try {
37650
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
37651
+ var anim = _step.value;
37652
+ var mesh = this._resolveMesh(anim, deviceModelRoot);
37653
+ if (!mesh) {
37654
+ console.warn("[IoAnimationManager] Could not find mesh for animation \"".concat(anim.name || anim.stateVariable, "\" (uuid: ").concat(anim.meshUuid, ", name: \"").concat(anim.meshName, "\")"));
37655
+ continue;
37656
+ }
37657
+ entries.push({
37658
+ anim: anim,
37659
+ mesh: mesh,
37660
+ origPos: mesh.position.clone(),
37661
+ origRot: mesh.rotation.clone()
37662
+ });
37663
+ }
37664
+ } catch (err) {
37665
+ _iterator.e(err);
37666
+ } finally {
37667
+ _iterator.f();
37668
+ }
37669
+ if (entries.length) {
37670
+ this._entries.set(key, entries);
37671
+ console.log("[IoAnimationManager] Loaded ".concat(entries.length, " animation(s) for attachment \"").concat(attachmentId, "\" (parent: ").concat(parentUuid, ")"));
37672
+ }
37673
+ }
37674
+
37675
+ /**
37676
+ * Apply animations triggered by an IO device state change.
37677
+ * Should be called in parallel with BehaviorManager.triggerState().
37678
+ *
37679
+ * @param {string} attachmentId - Raw attachment key (not scoped)
37680
+ * @param {string} dataPointId - The data point / state variable id that changed
37681
+ * @param {*} value - New state value
37682
+ * @param {string} parentUuid - UUID of the host component
37683
+ */
37684
+ }, {
37685
+ key: "triggerState",
37686
+ value: function triggerState(attachmentId, dataPointId, value, parentUuid) {
37687
+ var key = this._key(parentUuid, attachmentId);
37688
+ var entries = this._entries.get(key);
37689
+ if (!(entries !== null && entries !== void 0 && entries.length)) return;
37690
+ var _iterator2 = _createForOfIteratorHelper(entries),
37691
+ _step2;
37692
+ try {
37693
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
37694
+ var entry = _step2.value;
37695
+ if (entry.anim.stateVariable !== dataPointId) continue;
37696
+ this._applyAnimation(entry, value);
37697
+ }
37698
+ } catch (err) {
37699
+ _iterator2.e(err);
37700
+ } finally {
37701
+ _iterator2.f();
37702
+ }
37703
+ }
37704
+
37705
+ /**
37706
+ * Remove all animation entries associated with a given host component.
37707
+ * Call when a component is removed from the scene.
37708
+ *
37709
+ * @param {string} parentUuid
37710
+ */
37711
+ }, {
37712
+ key: "unloadForComponent",
37713
+ value: function unloadForComponent(parentUuid) {
37714
+ var prefix = "".concat(parentUuid, "::");
37715
+ var _iterator3 = _createForOfIteratorHelper(this._entries.keys()),
37716
+ _step3;
37717
+ try {
37718
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
37719
+ var key = _step3.value;
37720
+ if (key.startsWith(prefix)) {
37721
+ this._entries.delete(key);
37722
+ }
37723
+ }
37724
+ } catch (err) {
37725
+ _iterator3.e(err);
37726
+ } finally {
37727
+ _iterator3.f();
37728
+ }
37729
+ }
37730
+ }, {
37731
+ key: "dispose",
37732
+ value: function dispose() {
37733
+ this._entries.clear();
37734
+ _superPropGet(IoAnimationManager, "dispose", this, 3)([]);
37735
+ }
37736
+
37737
+ // ─────────────────────────────────────────────────────────────────────────
37738
+ // PRIVATE HELPERS
37739
+ // ─────────────────────────────────────────────────────────────────────────
37740
+ }, {
37741
+ key: "_key",
37742
+ value: function _key(parentUuid, attachmentId) {
37743
+ return "".concat(parentUuid, "::").concat(attachmentId);
37744
+ }
37745
+
37746
+ /**
37747
+ * Find the mesh inside `root` using UUID first, then name as fallback.
37748
+ * GLTFLoader assigns fresh UUIDs on every load, so name is the reliable key.
37749
+ * @param {Object} anim
37750
+ * @param {THREE.Object3D} root
37751
+ * @returns {THREE.Object3D|null}
37752
+ */
37753
+ }, {
37754
+ key: "_resolveMesh",
37755
+ value: function _resolveMesh(anim, root) {
37756
+ var found = null;
37757
+ root.traverse(function (obj) {
37758
+ if (found) return;
37759
+
37760
+ // Prefer UUID match (works when the device was cloned from a cached instance
37761
+ // whose UUIDs were preserved)
37762
+ if (anim.meshUuid && obj.uuid === anim.meshUuid) {
37763
+ found = obj;
37764
+ return;
37765
+ }
37766
+
37767
+ // Reliable fallback: mesh name
37768
+ if (anim.meshName && obj.name === anim.meshName) {
37769
+ found = obj;
37770
+ }
37771
+ });
37772
+ return found;
37773
+ }
37774
+
37775
+ /**
37776
+ * Resolve which mapping row to use and apply all declared transform types.
37777
+ * @param {{ anim, mesh, origPos, origRot }} entry
37778
+ * @param {*} value - Current state value
37779
+ */
37780
+ }, {
37781
+ key: "_applyAnimation",
37782
+ value: function _applyAnimation(entry, value) {
37783
+ var anim = entry.anim,
37784
+ mesh = entry.mesh,
37785
+ origPos = entry.origPos,
37786
+ origRot = entry.origRot;
37787
+ var mapping = this._resolveMapping(anim, value);
37788
+ if (!mapping) return;
37789
+ var types = anim.transformTypes || [];
37790
+ var _iterator4 = _createForOfIteratorHelper(types),
37791
+ _step4;
37792
+ try {
37793
+ for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
37794
+ var type = _step4.value;
37795
+ if (type === 'translation') {
37796
+ this._applyTranslation(mesh, origPos, mapping.transform);
37797
+ } else if (type === 'rotation') {
37798
+ this._applyRotation(mesh, origPos, origRot, anim, mapping.rotationTransform);
37799
+ } else if (type === 'color') {
37800
+ this._applyColor(mesh, mapping.colorTransform);
37801
+ }
37802
+ }
37803
+ } catch (err) {
37804
+ _iterator4.e(err);
37805
+ } finally {
37806
+ _iterator4.f();
37807
+ }
37808
+ }
37809
+
37810
+ /**
37811
+ * Find the mapping row that matches `value` for the given animation entry.
37812
+ *
37813
+ * - binary / enum: find where mapping.stateValue == value (loose equality so
37814
+ * JSON-deserialised "true" matches boolean true)
37815
+ * - continuous: linear interpolation between ordered anchor points
37816
+ *
37817
+ * @returns {Object|null} The matched/interpolated mapping row, or null.
37818
+ */
37819
+ }, {
37820
+ key: "_resolveMapping",
37821
+ value: function _resolveMapping(anim, value) {
37822
+ var stateType = anim.stateType,
37823
+ mappings = anim.mappings;
37824
+ if (!(mappings !== null && mappings !== void 0 && mappings.length)) return null;
37825
+ if (stateType === 'binary' || stateType === 'enum') {
37826
+ var _mappings$find;
37827
+ // eslint-disable-next-line eqeqeq
37828
+ return (_mappings$find = mappings.find(function (m) {
37829
+ return m.stateValue == value;
37830
+ })) !== null && _mappings$find !== void 0 ? _mappings$find : null;
37831
+ }
37832
+ if (stateType === 'continuous') {
37833
+ var num = Number(value);
37834
+ if (isNaN(num)) return null;
37835
+
37836
+ // Sort anchors by numeric stateValue
37837
+ var sorted = _toConsumableArray(mappings).filter(function (m) {
37838
+ return m.stateValue != null && !isNaN(Number(m.stateValue));
37839
+ }).sort(function (a, b) {
37840
+ return Number(a.stateValue) - Number(b.stateValue);
37841
+ });
37842
+ if (!sorted.length) return null;
37843
+ if (sorted.length === 1) return sorted[0];
37844
+
37845
+ // Clamp to range
37846
+ if (num <= Number(sorted[0].stateValue)) return sorted[0];
37847
+ if (num >= Number(sorted[sorted.length - 1].stateValue)) return sorted[sorted.length - 1];
37848
+
37849
+ // Find surrounding anchors and interpolate
37850
+ for (var i = 0; i < sorted.length - 1; i++) {
37851
+ var lo = sorted[i];
37852
+ var hi = sorted[i + 1];
37853
+ var loVal = Number(lo.stateValue);
37854
+ var hiVal = Number(hi.stateValue);
37855
+ if (num >= loVal && num <= hiVal) {
37856
+ var t = (num - loVal) / (hiVal - loVal);
37857
+ return this._lerpMapping(lo, hi, t);
37858
+ }
37859
+ }
37860
+ }
37861
+ return null;
37862
+ }
37863
+
37864
+ /**
37865
+ * Linear-interpolate between two mapping rows.
37866
+ * @param {Object} lo - lower anchor mapping
37867
+ * @param {Object} hi - upper anchor mapping
37868
+ * @param {number} t - 0..1
37869
+ */
37870
+ }, {
37871
+ key: "_lerpMapping",
37872
+ value: function _lerpMapping(lo, hi, t) {
37873
+ var _lo$transform$x, _lo$transform, _hi$transform$x, _hi$transform, _lo$transform$y, _lo$transform2, _hi$transform$y, _hi$transform2, _lo$transform$z, _lo$transform3, _hi$transform$z, _hi$transform3;
37874
+ var lerp = function lerp(a, b) {
37875
+ return a + (b - a) * t;
37876
+ };
37877
+ var transform = {
37878
+ x: lerp((_lo$transform$x = (_lo$transform = lo.transform) === null || _lo$transform === void 0 ? void 0 : _lo$transform.x) !== null && _lo$transform$x !== void 0 ? _lo$transform$x : 0, (_hi$transform$x = (_hi$transform = hi.transform) === null || _hi$transform === void 0 ? void 0 : _hi$transform.x) !== null && _hi$transform$x !== void 0 ? _hi$transform$x : 0),
37879
+ y: lerp((_lo$transform$y = (_lo$transform2 = lo.transform) === null || _lo$transform2 === void 0 ? void 0 : _lo$transform2.y) !== null && _lo$transform$y !== void 0 ? _lo$transform$y : 0, (_hi$transform$y = (_hi$transform2 = hi.transform) === null || _hi$transform2 === void 0 ? void 0 : _hi$transform2.y) !== null && _hi$transform$y !== void 0 ? _hi$transform$y : 0),
37880
+ z: lerp((_lo$transform$z = (_lo$transform3 = lo.transform) === null || _lo$transform3 === void 0 ? void 0 : _lo$transform3.z) !== null && _lo$transform$z !== void 0 ? _lo$transform$z : 0, (_hi$transform$z = (_hi$transform3 = hi.transform) === null || _hi$transform3 === void 0 ? void 0 : _hi$transform3.z) !== null && _hi$transform$z !== void 0 ? _hi$transform$z : 0)
37881
+ };
37882
+ var rotationTransform = lerp(typeof lo.rotationTransform === 'number' ? lo.rotationTransform : 0, typeof hi.rotationTransform === 'number' ? hi.rotationTransform : 0);
37883
+
37884
+ // Color interpolation: convert hex → RGB components → lerp → back to hex
37885
+ var colorTransform = this._lerpHex(lo.colorTransform, hi.colorTransform, t);
37886
+ return {
37887
+ stateValue: null,
37888
+ transform: transform,
37889
+ rotationTransform: rotationTransform,
37890
+ colorTransform: colorTransform
37891
+ };
37892
+ }
37893
+
37894
+ /**
37895
+ * Interpolate between two hex colour strings.
37896
+ */
37897
+ }, {
37898
+ key: "_lerpHex",
37899
+ value: function _lerpHex(hexA, hexB, t) {
37900
+ try {
37901
+ var ca = new THREE__namespace.Color(hexA);
37902
+ var cb = new THREE__namespace.Color(hexB);
37903
+ ca.lerp(cb, t);
37904
+ return "#".concat(ca.getHexString());
37905
+ } catch (_unused) {
37906
+ return hexA !== null && hexA !== void 0 ? hexA : '#ffffff';
37907
+ }
37908
+ }
37909
+
37910
+ // ─────────────────────────────────────────────────────────────────────────
37911
+ // TRANSFORM APPLIERS
37912
+ // ─────────────────────────────────────────────────────────────────────────
37913
+
37914
+ /**
37915
+ * Apply a position delta relative to the mesh's original position.
37916
+ * @param {THREE.Object3D} mesh
37917
+ * @param {THREE.Vector3} origPos
37918
+ * @param {{ x, y, z }} transform - Deltas
37919
+ */
37920
+ }, {
37921
+ key: "_applyTranslation",
37922
+ value: function _applyTranslation(mesh, origPos, transform) {
37923
+ var _transform$x, _transform$y, _transform$z;
37924
+ if (!transform) return;
37925
+ 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));
37926
+ }
37927
+
37928
+ /**
37929
+ * Apply a rotation around an arbitrary pivot point in device-local space,
37930
+ * optionally also displacing the mesh position to simulate orbital motion.
37931
+ *
37932
+ * Math (all in device-local space):
37933
+ * pivot = rotAxisOffset
37934
+ * delta = origPos - pivot
37935
+ * newDelta = rotate(delta, angle, axis)
37936
+ * newPos = pivot + newDelta
37937
+ * newRot[axis] = origRot[axis] + angle
37938
+ *
37939
+ * @param {THREE.Object3D} mesh
37940
+ * @param {THREE.Vector3} origPos
37941
+ * @param {THREE.Euler} origRot
37942
+ * @param {Object} anim
37943
+ * @param {number} angleDeg - Degrees
37944
+ */
37945
+ }, {
37946
+ key: "_applyRotation",
37947
+ value: function _applyRotation(mesh, origPos, origRot, anim, angleDeg) {
37948
+ var _anim$rotAxis, _anim$rotAxisOffset, _off$x, _off$y, _off$z;
37949
+ var angle = THREE__namespace.MathUtils.degToRad(typeof angleDeg === 'number' ? angleDeg : 0);
37950
+ var axis = ((_anim$rotAxis = anim.rotAxis) !== null && _anim$rotAxis !== void 0 ? _anim$rotAxis : 'x').toLowerCase();
37951
+ var off = (_anim$rotAxisOffset = anim.rotAxisOffset) !== null && _anim$rotAxisOffset !== void 0 ? _anim$rotAxisOffset : {
37952
+ x: 0,
37953
+ y: 0,
37954
+ z: 0
37955
+ };
37956
+
37957
+ // Unit vector for the chosen axis
37958
+ var axisVec = new THREE__namespace.Vector3(axis === 'x' ? 1 : 0, axis === 'y' ? 1 : 0, axis === 'z' ? 1 : 0);
37959
+ var pivot = new THREE__namespace.Vector3((_off$x = off.x) !== null && _off$x !== void 0 ? _off$x : 0, (_off$y = off.y) !== null && _off$y !== void 0 ? _off$y : 0, (_off$z = off.z) !== null && _off$z !== void 0 ? _off$z : 0);
37960
+ var delta = origPos.clone().sub(pivot);
37961
+ delta.applyAxisAngle(axisVec, angle);
37962
+ mesh.position.copy(pivot).add(delta);
37963
+ mesh.rotation[axis] = origRot[axis] + angle;
37964
+ }
37965
+
37966
+ /**
37967
+ * Apply a colour to all Mesh descendants of `mesh`.
37968
+ * Creates a cloned material per mesh on first call to avoid shared-material
37969
+ * cross-contamination between different device instances.
37970
+ *
37971
+ * @param {THREE.Object3D} mesh
37972
+ * @param {string} colorHex - e.g. '#ff0000'
37973
+ */
37974
+ }, {
37975
+ key: "_applyColor",
37976
+ value: function _applyColor(mesh, colorHex) {
37977
+ if (!colorHex) return;
37978
+ mesh.traverse(function (obj) {
37979
+ if (!obj.isMesh || !obj.material) return;
37980
+
37981
+ // Ensure this mesh has its own material instance
37982
+ if (!obj.userData._materialCloned) {
37983
+ obj.material = obj.material.clone();
37984
+ obj.userData._materialCloned = true;
37985
+ }
37986
+ try {
37987
+ obj.material.color.set(colorHex);
37988
+ } catch (e) {
37989
+ // Material may not have a color property (e.g. MeshDepthMaterial)
37990
+ }
37991
+ });
37992
+ }
37993
+ }]);
37994
+ }(BaseDisposable);
37995
+
37550
37996
  // ─────────────────────────────────────────────────────────────────────────────
37551
37997
  // Flow-direction helpers (module-level)
37552
37998
  // ─────────────────────────────────────────────────────────────────────────────
@@ -37668,6 +38114,7 @@ var CentralPlantInternals = /*#__PURE__*/function () {
37668
38114
  this.centralPlant.managers.cameraControlsManager = new CameraControlsManager(this.centralPlant.sceneViewer);
37669
38115
  this.centralPlant.managers.componentDragManager = new ComponentDragManager(this.centralPlant.sceneViewer);
37670
38116
  this.centralPlant.managers.viewport2DManager = new Viewport2DManager(this.centralPlant.sceneViewer);
38117
+ this.centralPlant.managers.ioAnimationManager = new IoAnimationManager(this.centralPlant.sceneViewer);
37671
38118
 
37672
38119
  // All managers are now stored in the managers collection and will be attached via attachToComponent()
37673
38120
  }
@@ -38938,7 +39385,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
38938
39385
  * Initialize the CentralPlant manager
38939
39386
  *
38940
39387
  * @constructor
38941
- * @version 0.3.21
39388
+ * @version 0.3.22
38942
39389
  * @updated 2025-10-22
38943
39390
  *
38944
39391
  * @description Creates a new CentralPlant instance and initializes internal managers and utilities.
@@ -41955,7 +42402,7 @@ var sceneViewer = /*#__PURE__*/function (_BaseDisposable) {
41955
42402
  this.centralPlant.attachToComponent();
41956
42403
 
41957
42404
  // Sync our managers tracking object after attachment
41958
- managerKeys = ['threeJSResourceManager', 'performanceMonitorManager', 'settingsManager', 'sceneExportManager', 'componentManager', 'sceneInitializationManager', 'environmentManager', 'keyboardControlsManager', 'pathfindingManager', 'pathFlowManager', 'behaviorManager', 'sceneOperationsManager', 'animationManager', 'cameraControlsManager', 'componentDragManager', 'tooltipsManager', 'componentTooltipManager']; // Populate our managers tracking object
42405
+ managerKeys = ['threeJSResourceManager', 'performanceMonitorManager', 'settingsManager', 'sceneExportManager', 'componentManager', 'sceneInitializationManager', 'environmentManager', 'keyboardControlsManager', 'pathfindingManager', 'pathFlowManager', 'behaviorManager', 'ioAnimationManager', 'sceneOperationsManager', 'animationManager', 'cameraControlsManager', 'componentDragManager', 'tooltipsManager', 'componentTooltipManager']; // Populate our managers tracking object
41959
42406
  managerKeys.forEach(function (key) {
41960
42407
  if (_this2[key]) {
41961
42408
  _this2.managers[key] = _this2[key];
@@ -47646,6 +48093,7 @@ exports.ComponentTooltipManager = ComponentTooltipManager;
47646
48093
  exports.EnvironmentManager = EnvironmentManager;
47647
48094
  exports.FLOW_ATTRIBUTE_KEYS = FLOW_ATTRIBUTE_KEYS;
47648
48095
  exports.GLOBAL_CACHE_NAME = GLOBAL_CACHE_NAME;
48096
+ exports.IoAnimationManager = IoAnimationManager;
47649
48097
  exports.KeyboardControlsManager = KeyboardControlsManager;
47650
48098
  exports.ModelManager = ModelManager;
47651
48099
  exports.OperationHistoryManager = OperationHistoryManager;