@2112-lab/central-plant 0.1.92 → 0.1.94

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.
@@ -3957,6 +3957,17 @@ var TransformControlsManager = /*#__PURE__*/function () {
3957
3957
  obj.userData._multiSelectOriginalRotation = obj.rotation.clone();
3958
3958
  obj.userData._multiSelectOriginalScale = obj.scale.clone();
3959
3959
  });
3960
+
3961
+ // Snapshot group position and helper geometry vertices so we can do
3962
+ // cheap per-frame bbox translation without re-traversing the mesh hierarchy.
3963
+ _this2._dragStartGroupPosition = _this2.multiSelectionGroup.position.clone();
3964
+ _this2.boundingBoxHelpers.forEach(function (helper) {
3965
+ var _helper$geometry;
3966
+ var posAttr = (_helper$geometry = helper.geometry) === null || _helper$geometry === void 0 || (_helper$geometry = _helper$geometry.attributes) === null || _helper$geometry === void 0 ? void 0 : _helper$geometry.position;
3967
+ if (posAttr) {
3968
+ helper.userData._dragStartPositions = new Float32Array(posAttr.array);
3969
+ }
3970
+ });
3960
3971
  }
3961
3972
 
3962
3973
  // Disable orbit controls during transformation
@@ -4021,6 +4032,13 @@ var TransformControlsManager = /*#__PURE__*/function () {
4021
4032
  }
4022
4033
  }
4023
4034
  case 3:
4035
+ // Ensure the bounding box reflects the final post-drag object position.
4036
+ // applyMultiSelectionTransform calls _finalizeMultiSelectionTransform →
4037
+ // createMultiBoundingBox for moves above the threshold. Call updateBoundingBox
4038
+ // here as a safety net for sub-threshold moves or rotation mode, where
4039
+ // _finalizeMultiSelectionTransform is not triggered.
4040
+ _this2.updateBoundingBox();
4041
+
4024
4042
  // Dispatch custom scene update event after transform completes
4025
4043
  if (typeof window !== 'undefined') {
4026
4044
  console.log('📡 Dispatching sceneUpdateComplete event after transform');
@@ -4034,6 +4052,12 @@ var TransformControlsManager = /*#__PURE__*/function () {
4034
4052
  });
4035
4053
  window.dispatchEvent(sceneCompleteEvent);
4036
4054
  }
4055
+
4056
+ // Clean up per-drag snapshots
4057
+ _this2._dragStartGroupPosition = null;
4058
+ _this2.boundingBoxHelpers.forEach(function (helper) {
4059
+ delete helper.userData._dragStartPositions;
4060
+ });
4037
4061
  console.log("\u2705 Transform completed: ".concat(_this2.currentMode, " mode"));
4038
4062
  case 4:
4039
4063
  return _context.a(2);
@@ -4042,33 +4066,16 @@ var TransformControlsManager = /*#__PURE__*/function () {
4042
4066
  }));
4043
4067
  // Transform changing event
4044
4068
  this.eventHandlers.transforming = function () {
4045
- // Apply real-time visual transformation to objects during drag
4069
+ // Apply real-time visual transformation to objects during drag.
4046
4070
  if (_this2.selectedObjects.length > 0 && _this2.multiSelectionGroup) {
4047
4071
  _this2.applyRealtimeTransform();
4048
4072
  }
4049
4073
 
4050
- // Clear the bounding box cache for all selected objects during transformation
4051
- _this2.selectedObjects.forEach(function (obj) {
4052
- if (_this2.boundingBoxCache.has(obj)) {
4053
- _this2.boundingBoxCache.delete(obj);
4054
- }
4055
- });
4056
-
4057
- // Update bounding box during transformation
4058
- _this2.updateBoundingBox();
4059
-
4060
- // Dispatch custom scene update event during transform (optional - fires continuously)
4061
- if (typeof window !== 'undefined') {
4062
- var _this2$selectedObject2;
4063
- var sceneCompleteEvent = new CustomEvent('sceneUpdateComplete', {
4064
- detail: {
4065
- timestamp: Date.now(),
4066
- transformType: _this2.currentMode,
4067
- objectName: ((_this2$selectedObject2 = _this2.selectedObjects[0]) === null || _this2$selectedObject2 === void 0 ? void 0 : _this2$selectedObject2.name) || 'unknown',
4068
- isTransforming: true
4069
- }
4070
- });
4071
- window.dispatchEvent(sceneCompleteEvent);
4074
+ // Per-frame bbox tracking: during translate we can cheaply shift the
4075
+ // pre-snapshotted helper geometry vertices by the drag delta instead of
4076
+ // re-traversing the full mesh hierarchy every pointermove.
4077
+ if (_this2.currentMode === 'translate') {
4078
+ _this2._applyDeltaToBoundingBoxHelpers();
4072
4079
  }
4073
4080
  };
4074
4081
  // First remove any existing listeners to prevent duplicates
@@ -4821,6 +4828,43 @@ var TransformControlsManager = /*#__PURE__*/function () {
4821
4828
  }
4822
4829
  }
4823
4830
 
4831
+ /**
4832
+ * Cheap per-frame bounding box update during translation drag.
4833
+ * Shifts the 8 pre-snapshotted geometry vertices of every helper by the
4834
+ * current drag delta. O(8 additions per helper) — no mesh traversal.
4835
+ * Only valid for translate mode where the box shape is invariant.
4836
+ * @private
4837
+ */
4838
+ }, {
4839
+ key: "_applyDeltaToBoundingBoxHelpers",
4840
+ value: function _applyDeltaToBoundingBoxHelpers() {
4841
+ if (!this._dragStartGroupPosition || this.boundingBoxHelpers.length === 0) return;
4842
+ var delta = this.multiSelectionGroup.position.clone().sub(this._dragStartGroupPosition);
4843
+ var _iterator3 = _createForOfIteratorHelper(this.boundingBoxHelpers),
4844
+ _step3;
4845
+ try {
4846
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
4847
+ var _helper$geometry2;
4848
+ var helper = _step3.value;
4849
+ var startPositions = helper.userData._dragStartPositions;
4850
+ var posAttr = (_helper$geometry2 = helper.geometry) === null || _helper$geometry2 === void 0 || (_helper$geometry2 = _helper$geometry2.attributes) === null || _helper$geometry2 === void 0 ? void 0 : _helper$geometry2.position;
4851
+ if (!startPositions || !posAttr) continue;
4852
+ var arr = posAttr.array;
4853
+ // 8 vertices × 3 floats (x, y, z)
4854
+ for (var i = 0; i < 24; i += 3) {
4855
+ arr[i] = startPositions[i] + delta.x;
4856
+ arr[i + 1] = startPositions[i + 1] + delta.y;
4857
+ arr[i + 2] = startPositions[i + 2] + delta.z;
4858
+ }
4859
+ posAttr.needsUpdate = true;
4860
+ }
4861
+ } catch (err) {
4862
+ _iterator3.e(err);
4863
+ } finally {
4864
+ _iterator3.f();
4865
+ }
4866
+ }
4867
+
4824
4868
  /**
4825
4869
  * Set the transformation mode
4826
4870
  * @param {'translate' | 'rotate' | 'scale'} mode - The transformation mode
@@ -5195,11 +5239,11 @@ var TransformControlsManager = /*#__PURE__*/function () {
5195
5239
  var objectFilter = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
5196
5240
  var objectsWithBounds = this.getSelectableObjectsWithBounds(objectFilter);
5197
5241
  var intersections = [];
5198
- var _iterator3 = _createForOfIteratorHelper(objectsWithBounds),
5199
- _step3;
5242
+ var _iterator4 = _createForOfIteratorHelper(objectsWithBounds),
5243
+ _step4;
5200
5244
  try {
5201
- for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
5202
- var item = _step3.value;
5245
+ for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
5246
+ var item = _step4.value;
5203
5247
  var object = item.object,
5204
5248
  boundingBox = item.boundingBox;
5205
5249
 
@@ -5219,9 +5263,9 @@ var TransformControlsManager = /*#__PURE__*/function () {
5219
5263
 
5220
5264
  // Sort by distance (closest first)
5221
5265
  } catch (err) {
5222
- _iterator3.e(err);
5266
+ _iterator4.e(err);
5223
5267
  } finally {
5224
- _iterator3.f();
5268
+ _iterator4.f();
5225
5269
  }
5226
5270
  intersections.sort(function (a, b) {
5227
5271
  return a.distance - b.distance;
@@ -11457,11 +11501,35 @@ var SceneExportManager = /*#__PURE__*/function () {
11457
11501
  // For connectors/gateways: no children (they're leaf nodes)
11458
11502
  if (threeObject.children && threeObject.children.length > 0) {
11459
11503
  var exportableChildren = [];
11460
- if (threeObject.userData.objectType === 'component') ; else if (isManualSegment) {
11461
- // For manual segments, export their connector children
11504
+ if (threeObject.userData.objectType === 'component') {
11505
+ // Export connector children (skip mesh geometry - it lives in the component dictionary GLB)
11462
11506
  threeObject.children.forEach(function (child) {
11463
11507
  var _child$userData;
11464
- if (((_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.objectType) === 'segment-connector') {
11508
+ if (((_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.objectType) === 'connector') {
11509
+ var connectorJson = {
11510
+ // Use the raw Three.js UUID — connections in currentSceneData reference this exact value
11511
+ uuid: child.uuid,
11512
+ userData: _objectSpread2(_objectSpread2({
11513
+ objectType: 'connector'
11514
+ }, child.userData.direction ? {
11515
+ direction: child.userData.direction
11516
+ } : {}), child.userData.group ? {
11517
+ group: child.userData.group
11518
+ } : {}),
11519
+ position: {
11520
+ x: roundIfClose(child.position.x),
11521
+ y: roundIfClose(child.position.y),
11522
+ z: roundIfClose(child.position.z)
11523
+ }
11524
+ };
11525
+ exportableChildren.push(connectorJson);
11526
+ }
11527
+ });
11528
+ } else if (isManualSegment) {
11529
+ // For manual segments, export their connector children
11530
+ threeObject.children.forEach(function (child) {
11531
+ var _child$userData2;
11532
+ if (((_child$userData2 = child.userData) === null || _child$userData2 === void 0 ? void 0 : _child$userData2.objectType) === 'segment-connector') {
11465
11533
  // Call the class method using bound reference
11466
11534
  var connectorJson = convertSegmentConnectorToJson(child, threeObject);
11467
11535
  if (connectorJson) {
@@ -11487,9 +11555,9 @@ var SceneExportManager = /*#__PURE__*/function () {
11487
11555
  var findManualSegments = function findManualSegments(object) {
11488
11556
  var manualSegments = [];
11489
11557
  object.traverse(function (child) {
11490
- var _child$userData2;
11558
+ var _child$userData3;
11491
11559
  // Check if this is a manual segment
11492
- if (((_child$userData2 = child.userData) === null || _child$userData2 === void 0 ? void 0 : _child$userData2.objectType) === 'segment') {
11560
+ if (((_child$userData3 = child.userData) === null || _child$userData3 === void 0 ? void 0 : _child$userData3.objectType) === 'segment') {
11493
11561
  manualSegments.push(child);
11494
11562
  }
11495
11563
  });
@@ -11518,10 +11586,23 @@ var SceneExportManager = /*#__PURE__*/function () {
11518
11586
  // Helper function to extract behaviors from current scene data
11519
11587
  var extractBehaviors = function extractBehaviors() {
11520
11588
  var _this$sceneViewer, _this$sceneViewer2;
11589
+ // Only export behaviors that are NOT re-derivable from a component's
11590
+ // defaultBehaviors[]. All component/device default behaviors are
11591
+ // reconstructed at load time by Step B of _processBehaviors() using the
11592
+ // component dictionary, so writing compact behaviorRef entries for them
11593
+ // would be redundant. The scene JSON behaviors[] array is reserved for
11594
+ // any future scene-level overrides that cannot be derived from the asset.
11521
11595
  if ((_this$sceneViewer = _this.sceneViewer) !== null && _this$sceneViewer !== void 0 && (_this$sceneViewer = _this$sceneViewer.managers) !== null && _this$sceneViewer !== void 0 && _this$sceneViewer.behaviorManager) {
11522
- return _this.sceneViewer.managers.behaviorManager.getBehaviors();
11596
+ return _this.sceneViewer.managers.behaviorManager.getBehaviors().filter(function (b) {
11597
+ return !b._isDefaultBehavior;
11598
+ });
11523
11599
  }
11524
- return ((_this$sceneViewer2 = _this.sceneViewer) === null || _this$sceneViewer2 === void 0 || (_this$sceneViewer2 = _this$sceneViewer2.currentSceneData) === null || _this$sceneViewer2 === void 0 ? void 0 : _this$sceneViewer2.behaviors) || [];
11600
+ // Fallback when BehaviorManager is not available: exclude any entry that
11601
+ // was already a behaviorRef (it was derivable from the component asset)
11602
+ // and exclude the legacy _isDefaultBehavior marker if present.
11603
+ return (((_this$sceneViewer2 = _this.sceneViewer) === null || _this$sceneViewer2 === void 0 || (_this$sceneViewer2 = _this$sceneViewer2.currentSceneData) === null || _this$sceneViewer2 === void 0 ? void 0 : _this$sceneViewer2.behaviors) || []).filter(function (b) {
11604
+ return !b.behaviorRef && !b._isDefaultBehavior;
11605
+ });
11525
11606
  };
11526
11607
 
11527
11608
  // Build the complete export data structure (matching central-plant-input.json format)
@@ -11676,14 +11757,14 @@ var SceneExportManager = /*#__PURE__*/function () {
11676
11757
  BufferGeometryUtils$1 = BufferGeometryUtilsModule.BufferGeometryUtils || BufferGeometryUtilsModule.default || BufferGeometryUtilsModule; // Create a new scene for export instead of cloning
11677
11758
  exportScene = new _THREE.Scene(); // Helper function to check if an object should be exported
11678
11759
  shouldExport = function shouldExport(child) {
11679
- var _child$name2, _child$userData3, _child$userData4, _child$userData5, _child$userData6;
11760
+ var _child$name2, _child$userData4, _child$userData5, _child$userData6, _child$userData7;
11680
11761
  if ((_child$name2 = child.name) !== null && _child$name2 !== void 0 && _child$name2.includes('Polyline')) return false; // Will handle separately
11681
11762
  if (child.name === 'fogPlane') return false; // Skip fog plane
11682
- if ((_child$userData3 = child.userData) !== null && _child$userData3 !== void 0 && _child$userData3.isBrickWall) return false; // Skip environment
11683
- if ((_child$userData4 = child.userData) !== null && _child$userData4 !== void 0 && _child$userData4.isBaseGround) return false; // Skip environment
11684
- if ((_child$userData5 = child.userData) !== null && _child$userData5 !== void 0 && _child$userData5.isBaseGrid) return false; // Skip environment
11763
+ if ((_child$userData4 = child.userData) !== null && _child$userData4 !== void 0 && _child$userData4.isBrickWall) return false; // Skip environment
11764
+ if ((_child$userData5 = child.userData) !== null && _child$userData5 !== void 0 && _child$userData5.isBaseGround) return false; // Skip environment
11765
+ if ((_child$userData6 = child.userData) !== null && _child$userData6 !== void 0 && _child$userData6.isBaseGrid) return false; // Skip environment
11685
11766
  if (child.isLight) return false; // Skip lights
11686
- if ((_child$userData6 = child.userData) !== null && _child$userData6 !== void 0 && _child$userData6.isTransformControls) return false; // Skip transform controls
11767
+ if ((_child$userData7 = child.userData) !== null && _child$userData7 !== void 0 && _child$userData7.isTransformControls) return false; // Skip transform controls
11687
11768
  if (child.isTransformControls) return false; // Skip transform controls
11688
11769
  if (child.type && child.type.includes('TransformControls')) return false;
11689
11770
  if (child.type && child.type.includes('Helper')) return false; // Skip helpers
@@ -19510,62 +19591,43 @@ var ComponentDataManager = /*#__PURE__*/function (_BaseDisposable) {
19510
19591
  key: "_loadComponentDictionary",
19511
19592
  value: (function () {
19512
19593
  var _loadComponentDictionary2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee6() {
19513
- var _yield$import, getCachedLocalJson, response, _t4, _t5;
19594
+ var response, _t4;
19514
19595
  return _regenerator().w(function (_context6) {
19515
19596
  while (1) switch (_context6.n) {
19516
19597
  case 0:
19517
19598
  _context6.p = 0;
19518
- if (!(typeof window !== 'undefined' && 'caches' in window)) {
19519
- _context6.n = 5;
19520
- break;
19521
- }
19522
- _context6.p = 1;
19523
- _context6.n = 2;
19524
- return Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespace(require('~/utils/s3Cache')); });
19525
- case 2:
19526
- _yield$import = _context6.v;
19527
- getCachedLocalJson = _yield$import.getCachedLocalJson;
19528
- _context6.n = 3;
19529
- return getCachedLocalJson('/library/component-dictionary.json');
19530
- case 3:
19531
- this.componentDictionary = _context6.v;
19532
- console.log('📚 ComponentDataManager: Component dictionary loaded from cache');
19533
- return _context6.a(2);
19534
- case 4:
19535
- _context6.p = 4;
19536
- _t4 = _context6.v;
19537
- console.warn('⚠️ ComponentDataManager: Cache load failed, falling back to direct fetch:', _t4);
19538
- case 5:
19539
- _context6.n = 6;
19540
- return fetch('/library/component-dictionary.json');
19541
- case 6:
19599
+ _context6.n = 1;
19600
+ return fetch('/library/component-dictionary.json', {
19601
+ cache: 'no-cache'
19602
+ });
19603
+ case 1:
19542
19604
  response = _context6.v;
19543
19605
  if (!response.ok) {
19544
- _context6.n = 8;
19606
+ _context6.n = 3;
19545
19607
  break;
19546
19608
  }
19547
- _context6.n = 7;
19609
+ _context6.n = 2;
19548
19610
  return response.json();
19549
- case 7:
19611
+ case 2:
19550
19612
  this.componentDictionary = _context6.v;
19551
19613
  console.log('📚 ComponentDataManager: Component dictionary loaded successfully');
19552
- _context6.n = 9;
19614
+ _context6.n = 4;
19553
19615
  break;
19554
- case 8:
19616
+ case 3:
19555
19617
  console.warn('⚠️ ComponentDataManager: Could not load component dictionary');
19556
19618
  this.componentDictionary = null;
19557
- case 9:
19558
- _context6.n = 11;
19619
+ case 4:
19620
+ _context6.n = 6;
19559
19621
  break;
19560
- case 10:
19561
- _context6.p = 10;
19562
- _t5 = _context6.v;
19563
- console.warn('⚠️ ComponentDataManager: Error loading component dictionary:', _t5);
19622
+ case 5:
19623
+ _context6.p = 5;
19624
+ _t4 = _context6.v;
19625
+ console.warn('⚠️ ComponentDataManager: Error loading component dictionary:', _t4);
19564
19626
  this.componentDictionary = null;
19565
- case 11:
19627
+ case 6:
19566
19628
  return _context6.a(2);
19567
19629
  }
19568
- }, _callee6, this, [[1, 4], [0, 10]]);
19630
+ }, _callee6, this, [[0, 5]]);
19569
19631
  }));
19570
19632
  function _loadComponentDictionary() {
19571
19633
  return _loadComponentDictionary2.apply(this, arguments);
@@ -19814,6 +19876,8 @@ var ComponentDataManager = /*#__PURE__*/function (_BaseDisposable) {
19814
19876
  boundingBox: component.boundingBox || null,
19815
19877
  adaptedBoundingBox: component.adaptedBoundingBox || null,
19816
19878
  children: component.children || [],
19879
+ meshChildren: component.meshChildren || [],
19880
+ defaultBehaviors: component.defaultBehaviors || [],
19817
19881
  specifications: ((_component$metadata2 = component.metadata) === null || _component$metadata2 === void 0 ? void 0 : _component$metadata2.specifications) || {},
19818
19882
  description: ((_component$metadata3 = component.metadata) === null || _component$metadata3 === void 0 ? void 0 : _component$metadata3.description) || ''
19819
19883
  });
@@ -30710,6 +30774,9 @@ var SceneOperationsManager = /*#__PURE__*/function () {
30710
30774
  // Phase 6: Load behaviors (after GLB models are present so output objects can be resolved)
30711
30775
  this._processBehaviors(data);
30712
30776
  console.log('✅ Scene loaded successfully');
30777
+
30778
+ // Notify UI components (e.g. SceneHierarchy) that the scene is fully loaded
30779
+ this.sceneViewer.emit('scene-loaded');
30713
30780
  _context4.n = 7;
30714
30781
  break;
30715
30782
  case 6:
@@ -31062,20 +31129,252 @@ var SceneOperationsManager = /*#__PURE__*/function () {
31062
31129
  }
31063
31130
 
31064
31131
  /**
31065
- * Process behaviors from the scene data and pass them to the BehaviorManager.
31132
+ * Process behaviors from the scene data, expand any defaultBehaviors defined
31133
+ * on component dictionary entries, resolve behaviorRef entries against device
31134
+ * assets, inject per-instance component UUIDs, and hand the flat resolved
31135
+ * array to BehaviorManager.
31136
+ *
31137
+ * Resolution cascade:
31138
+ * 1. Explicit behaviors in data.behaviors — passed through as-is (behaviorRef
31139
+ * entries are resolved against the component/device dictionaries).
31140
+ * 2. defaultBehaviors[] on each placed smart component's dictionary entry —
31141
+ * expanded per instance with the instance UUID injected. Compact
31142
+ * behaviorRef-derived entries are tagged _isDefaultBehavior:true (the
31143
+ * exporter can re-derive them); full L2 behavior objects are tagged false
31144
+ * so they are exported verbatim and survive round-trips.
31145
+ *
31066
31146
  * @param {Object} data - Scene JSON data
31067
31147
  */
31068
31148
  }, {
31069
31149
  key: "_processBehaviors",
31070
31150
  value: function _processBehaviors(data) {
31071
- var _this$sceneViewer2;
31072
- if (!(data !== null && data !== void 0 && data.behaviors) || !Array.isArray(data.behaviors)) return;
31151
+ var _this$sceneViewer2,
31152
+ _this$sceneViewer$cen2,
31153
+ _this5 = this,
31154
+ _data$scene3;
31073
31155
  var behaviorManager = (_this$sceneViewer2 = this.sceneViewer) === null || _this$sceneViewer2 === void 0 || (_this$sceneViewer2 = _this$sceneViewer2.managers) === null || _this$sceneViewer2 === void 0 ? void 0 : _this$sceneViewer2.behaviorManager;
31074
31156
  if (!behaviorManager) {
31075
31157
  console.warn('⚠️ _processBehaviors: BehaviorManager not available');
31076
31158
  return;
31077
31159
  }
31078
- behaviorManager.loadBehaviors(data.behaviors);
31160
+
31161
+ // Obtain the component dictionary (extended = includes S3/smart components)
31162
+ var componentDictionary = ((_this$sceneViewer$cen2 = this.sceneViewer.centralPlant) === null || _this$sceneViewer$cen2 === void 0 || (_this$sceneViewer$cen2 = _this$sceneViewer$cen2.managers) === null || _this$sceneViewer$cen2 === void 0 || (_this$sceneViewer$cen2 = _this$sceneViewer$cen2.componentDataManager) === null || _this$sceneViewer$cen2 === void 0 ? void 0 : _this$sceneViewer$cen2.componentDictionary) || {};
31163
+
31164
+ // ── Step A: Resolve any behaviorRef entries from data.behaviors ─────────
31165
+ var explicitBehaviors = [];
31166
+ if (Array.isArray(data === null || data === void 0 ? void 0 : data.behaviors)) {
31167
+ data.behaviors.forEach(function (entry) {
31168
+ if (entry.behaviorRef) {
31169
+ var resolved = _this5._resolveBehaviorRef(entry, componentDictionary);
31170
+ if (resolved) explicitBehaviors.push(resolved);
31171
+ } else {
31172
+ explicitBehaviors.push(entry);
31173
+ }
31174
+ });
31175
+ }
31176
+
31177
+ // Build a set of explicit behavior ids for deduplication
31178
+ var explicitIds = new Set(explicitBehaviors.map(function (b) {
31179
+ return b.id;
31180
+ }));
31181
+
31182
+ // Build a set of instanceUuids already covered by behaviorRef entries in
31183
+ // data.behaviors (written by the exporter for smart component defaults).
31184
+ // Step B skips these instances to avoid loading the same behaviors twice —
31185
+ // once via Step A (resolved from data.behaviors refs) and once via Step B
31186
+ // (auto-expanded from the component dictionary).
31187
+ var coveredInstances = new Set();
31188
+ if (Array.isArray(data === null || data === void 0 ? void 0 : data.behaviors)) {
31189
+ data.behaviors.forEach(function (entry) {
31190
+ if (entry.behaviorRef && entry.component) {
31191
+ coveredInstances.add(entry.component);
31192
+ }
31193
+ });
31194
+ }
31195
+
31196
+ // ── Step B: Walk placed components and expand their defaultBehaviors ─────
31197
+ var instanceBehaviors = [];
31198
+ if (Array.isArray(data === null || data === void 0 || (_data$scene3 = data.scene) === null || _data$scene3 === void 0 ? void 0 : _data$scene3.children)) {
31199
+ data.scene.children.forEach(function (child) {
31200
+ var _child$userData0, _compData$defaultBeha;
31201
+ var libraryId = (_child$userData0 = child.userData) === null || _child$userData0 === void 0 ? void 0 : _child$userData0.libraryId;
31202
+ if (!libraryId) return;
31203
+ var instanceUuid = child.uuid;
31204
+ // Skip instances whose defaults were already resolved by Step A
31205
+ // (they have explicit behaviorRef entries in data.behaviors).
31206
+ if (coveredInstances.has(instanceUuid)) return;
31207
+ var compData = componentDictionary[libraryId];
31208
+ if (!(compData !== null && compData !== void 0 && (_compData$defaultBeha = compData.defaultBehaviors) !== null && _compData$defaultBeha !== void 0 && _compData$defaultBeha.length)) return;
31209
+ compData.defaultBehaviors.forEach(function (template) {
31210
+ var expanded = _this5._expandDefaultBehavior(template, instanceUuid, componentDictionary);
31211
+ if (!expanded) return;
31212
+ // Skip if an explicit scene behavior already covers this id
31213
+ if (explicitIds.has(expanded.id)) return;
31214
+ // All component defaultBehaviors are re-derivable from the component
31215
+ // asset at export time (via compact behaviorRef), so mark them all.
31216
+ expanded._isDefaultBehavior = true;
31217
+ instanceBehaviors.push(expanded);
31218
+ });
31219
+ });
31220
+ }
31221
+ var allBehaviors = [].concat(explicitBehaviors, instanceBehaviors);
31222
+ if (allBehaviors.length === 0) return;
31223
+ behaviorManager.loadBehaviors(allBehaviors);
31224
+ console.log("\u2705 _processBehaviors: loaded ".concat(explicitBehaviors.length, " explicit + ").concat(instanceBehaviors.length, " default-expanded behavior(s)"));
31225
+ }
31226
+
31227
+ /**
31228
+ * Register the defaultBehaviors of a single newly placed component instance
31229
+ * into the BehaviorManager. Called from addComponent() (drag-drop), where
31230
+ * _processBehaviors() (scene-load path) is not invoked.
31231
+ *
31232
+ * @param {Object} componentData - Entry from the component dictionary
31233
+ * @param {string} instanceUuid - UUID of the placed component (componentModel.uuid)
31234
+ */
31235
+ }, {
31236
+ key: "registerBehaviorsForComponentInstance",
31237
+ value: function registerBehaviorsForComponentInstance(componentData, instanceUuid) {
31238
+ var _this$sceneViewer3,
31239
+ _componentData$defaul,
31240
+ _this$sceneViewer$cen3,
31241
+ _this6 = this;
31242
+ var behaviorManager = (_this$sceneViewer3 = this.sceneViewer) === null || _this$sceneViewer3 === void 0 || (_this$sceneViewer3 = _this$sceneViewer3.managers) === null || _this$sceneViewer3 === void 0 ? void 0 : _this$sceneViewer3.behaviorManager;
31243
+ if (!behaviorManager) return;
31244
+ if (!(componentData !== null && componentData !== void 0 && (_componentData$defaul = componentData.defaultBehaviors) !== null && _componentData$defaul !== void 0 && _componentData$defaul.length)) return;
31245
+ var componentDictionary = ((_this$sceneViewer$cen3 = this.sceneViewer.centralPlant) === null || _this$sceneViewer$cen3 === void 0 || (_this$sceneViewer$cen3 = _this$sceneViewer$cen3.managers) === null || _this$sceneViewer$cen3 === void 0 || (_this$sceneViewer$cen3 = _this$sceneViewer$cen3.componentDataManager) === null || _this$sceneViewer$cen3 === void 0 ? void 0 : _this$sceneViewer$cen3.componentDictionary) || {};
31246
+ var registered = 0;
31247
+ componentData.defaultBehaviors.forEach(function (template) {
31248
+ var expanded = _this6._expandDefaultBehavior(template, instanceUuid, componentDictionary);
31249
+ if (!expanded) return;
31250
+ expanded._isDefaultBehavior = true;
31251
+ behaviorManager.addBehavior(expanded);
31252
+ registered++;
31253
+ });
31254
+ if (registered > 0) {
31255
+ console.log("\u2705 registerBehaviorsForComponentInstance: registered ".concat(registered, " behavior(s) for instance ").concat(instanceUuid));
31256
+ }
31257
+ }
31258
+
31259
+ /**
31260
+ * Resolve a scene-level or component-level behaviorRef entry into a full
31261
+ * behavior definition by looking up the referenced template in the device
31262
+ * asset's defaultBehaviors[] and substituting 'self' with the real
31263
+ * attachmentId.
31264
+ *
31265
+ * Ref shape: { behaviorRef, deviceId, attachment, component? }
31266
+ *
31267
+ * @param {Object} ref
31268
+ * @param {Object} componentDictionary
31269
+ * @returns {Object|null} Resolved behavior or null if not found
31270
+ */
31271
+ }, {
31272
+ key: "_resolveBehaviorRef",
31273
+ value: function _resolveBehaviorRef(ref, componentDictionary) {
31274
+ var _resolved$input, _resolved$output;
31275
+ if (!ref.behaviorRef) {
31276
+ console.warn('⚠️ _resolveBehaviorRef: missing behaviorRef', ref);
31277
+ return null;
31278
+ }
31279
+
31280
+ // ── Component-level L2 ref: { behaviorRef, libraryId, component } ──────
31281
+ // The full behavior template lives on the smart component's defaultBehaviors[].
31282
+ // No 'self' substitution needed — attachment IDs are already real.
31283
+ if (ref.libraryId) {
31284
+ var compData = componentDictionary[ref.libraryId];
31285
+ if (!compData) {
31286
+ console.warn("\u26A0\uFE0F _resolveBehaviorRef: component \"".concat(ref.libraryId, "\" not in dictionary"));
31287
+ return null;
31288
+ }
31289
+ var _template = (compData.defaultBehaviors || []).find(function (b) {
31290
+ return b.id === ref.behaviorRef;
31291
+ });
31292
+ if (!_template) {
31293
+ console.warn("\u26A0\uFE0F _resolveBehaviorRef: behavior \"".concat(ref.behaviorRef, "\" not found on component \"").concat(ref.libraryId, "\""));
31294
+ return null;
31295
+ }
31296
+ var _resolved = JSON.parse(JSON.stringify(_template));
31297
+ _resolved.id = "".concat(_resolved.id, "::").concat(ref.component);
31298
+ if (ref.component) {
31299
+ _resolved.input = _objectSpread2(_objectSpread2({}, _resolved.input), {}, {
31300
+ component: ref.component
31301
+ });
31302
+ _resolved.output = _objectSpread2(_objectSpread2({}, _resolved.output), {}, {
31303
+ component: ref.component
31304
+ });
31305
+ }
31306
+ return _resolved;
31307
+ }
31308
+
31309
+ // ── Device-level L1 ref: { behaviorRef, deviceId, attachment, component } ─
31310
+ if (!ref.deviceId || !ref.attachment) {
31311
+ console.warn('⚠️ _resolveBehaviorRef: incomplete ref', ref);
31312
+ return null;
31313
+ }
31314
+ var deviceData = componentDictionary[ref.deviceId];
31315
+ if (!deviceData) {
31316
+ console.warn("\u26A0\uFE0F _resolveBehaviorRef: device \"".concat(ref.deviceId, "\" not in dictionary"));
31317
+ return null;
31318
+ }
31319
+ var template = (deviceData.defaultBehaviors || []).find(function (b) {
31320
+ return b.id === ref.behaviorRef;
31321
+ });
31322
+ if (!template) {
31323
+ console.warn("\u26A0\uFE0F _resolveBehaviorRef: behavior \"".concat(ref.behaviorRef, "\" not found on device \"").concat(ref.deviceId, "\""));
31324
+ return null;
31325
+ }
31326
+ // Deep clone and substitute 'self' -> real attachmentId
31327
+ var resolved = JSON.parse(JSON.stringify(template));
31328
+ resolved.id = "".concat(resolved.id, "::").concat(ref.attachment);
31329
+ if (((_resolved$input = resolved.input) === null || _resolved$input === void 0 ? void 0 : _resolved$input.attachment) === 'self') resolved.input.attachment = ref.attachment;
31330
+ if (((_resolved$output = resolved.output) === null || _resolved$output === void 0 ? void 0 : _resolved$output.attachment) === 'self') resolved.output.attachment = ref.attachment;
31331
+ // Inject component UUID guard if the ref carries one
31332
+ if (ref.component) {
31333
+ resolved.input = _objectSpread2(_objectSpread2({}, resolved.input), {}, {
31334
+ component: ref.component
31335
+ });
31336
+ resolved.output = _objectSpread2(_objectSpread2({}, resolved.output), {}, {
31337
+ component: ref.component
31338
+ });
31339
+ }
31340
+ return resolved;
31341
+ }
31342
+
31343
+ /**
31344
+ * Expand a single defaultBehaviors[] template entry for a specific component
31345
+ * instance placed in the scene.
31346
+ *
31347
+ * If the entry is a behaviorRef, it is first resolved against the device
31348
+ * library; otherwise it is treated as a full behavior definition.
31349
+ * In both cases the instanceUuid is injected as input.component and
31350
+ * output.component, and the behavior id is suffixed with ::instanceUuid.
31351
+ *
31352
+ * @param {Object} template - Entry from compData.defaultBehaviors[]
31353
+ * @param {string} instanceUuid - UUID of the placed component instance
31354
+ * @param {Object} componentDictionary
31355
+ * @returns {Object|null}
31356
+ */
31357
+ }, {
31358
+ key: "_expandDefaultBehavior",
31359
+ value: function _expandDefaultBehavior(template, instanceUuid, componentDictionary) {
31360
+ var base;
31361
+ if (template.behaviorRef) {
31362
+ // Resolve the device ref first (substitutes 'self' -> attachment)
31363
+ base = this._resolveBehaviorRef(template, componentDictionary);
31364
+ if (!base) return null;
31365
+ } else {
31366
+ base = JSON.parse(JSON.stringify(template));
31367
+ }
31368
+ // Suffix the id so each instance gets a unique behavior id
31369
+ base.id = "".concat(base.id, "::").concat(instanceUuid);
31370
+ // Inject the instance UUID as the component scope guard
31371
+ base.input = _objectSpread2(_objectSpread2({}, base.input), {}, {
31372
+ component: instanceUuid
31373
+ });
31374
+ base.output = _objectSpread2(_objectSpread2({}, base.output), {}, {
31375
+ component: instanceUuid
31376
+ });
31377
+ return base;
31079
31378
  }
31080
31379
 
31081
31380
  /**
@@ -31146,7 +31445,7 @@ var SceneOperationsManager = /*#__PURE__*/function () {
31146
31445
  key: "loadSceneFromData",
31147
31446
  value: (function () {
31148
31447
  var _loadSceneFromData = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee9(data) {
31149
- var _data$scene3, _data$scene4, _data$scene5, _data$scene6;
31448
+ var _data$scene4, _data$scene5, _data$scene6, _data$scene7;
31150
31449
  return _regenerator().w(function (_context9) {
31151
31450
  while (1) switch (_context9.n) {
31152
31451
  case 0:
@@ -31156,10 +31455,10 @@ var SceneOperationsManager = /*#__PURE__*/function () {
31156
31455
  dataType: _typeof(data),
31157
31456
  hasScene: !!(data !== null && data !== void 0 && data.scene),
31158
31457
  sceneType: _typeof(data === null || data === void 0 ? void 0 : data.scene),
31159
- hasChildren: !!(data !== null && data !== void 0 && (_data$scene3 = data.scene) !== null && _data$scene3 !== void 0 && _data$scene3.children),
31160
- childrenType: data !== null && data !== void 0 && (_data$scene4 = data.scene) !== null && _data$scene4 !== void 0 && _data$scene4.children ? _typeof(data.scene.children) : 'undefined',
31161
- isArray: Array.isArray(data === null || data === void 0 || (_data$scene5 = data.scene) === null || _data$scene5 === void 0 ? void 0 : _data$scene5.children),
31162
- childrenLength: data === null || data === void 0 || (_data$scene6 = data.scene) === null || _data$scene6 === void 0 || (_data$scene6 = _data$scene6.children) === null || _data$scene6 === void 0 ? void 0 : _data$scene6.length,
31458
+ hasChildren: !!(data !== null && data !== void 0 && (_data$scene4 = data.scene) !== null && _data$scene4 !== void 0 && _data$scene4.children),
31459
+ childrenType: data !== null && data !== void 0 && (_data$scene5 = data.scene) !== null && _data$scene5 !== void 0 && _data$scene5.children ? _typeof(data.scene.children) : 'undefined',
31460
+ isArray: Array.isArray(data === null || data === void 0 || (_data$scene6 = data.scene) === null || _data$scene6 === void 0 ? void 0 : _data$scene6.children),
31461
+ childrenLength: data === null || data === void 0 || (_data$scene7 = data.scene) === null || _data$scene7 === void 0 || (_data$scene7 = _data$scene7.children) === null || _data$scene7 === void 0 ? void 0 : _data$scene7.length,
31163
31462
  dataKeys: data ? Object.keys(data) : [],
31164
31463
  sceneKeys: data !== null && data !== void 0 && data.scene ? Object.keys(data.scene) : []
31165
31464
  });
@@ -31290,8 +31589,8 @@ var SceneOperationsManager = /*#__PURE__*/function () {
31290
31589
  // Process children (connectors, etc.) if they exist
31291
31590
  if (componentModel.children && componentModel.children.length > 0) {
31292
31591
  componentModel.children.forEach(function (child) {
31293
- var _child$userData0, _child$userData1;
31294
- var childType = ((_child$userData0 = child.userData) === null || _child$userData0 === void 0 ? void 0 : _child$userData0.objectType) || ((_child$userData1 = child.userData) === null || _child$userData1 === void 0 ? void 0 : _child$userData1.objectType);
31592
+ var _child$userData1, _child$userData10;
31593
+ var childType = ((_child$userData1 = child.userData) === null || _child$userData1 === void 0 ? void 0 : _child$userData1.objectType) || ((_child$userData10 = child.userData) === null || _child$userData10 === void 0 ? void 0 : _child$userData10.objectType);
31295
31594
  if (childType === 'connector') {
31296
31595
  var _child$geometry;
31297
31596
  var childBoundingBox = new THREE__namespace.Box3().setFromObject(child);
@@ -31376,8 +31675,8 @@ var SceneOperationsManager = /*#__PURE__*/function () {
31376
31675
  if (segment.children && segment.children.length > 0) {
31377
31676
  var childrenToRemove = _toConsumableArray(segment.children);
31378
31677
  childrenToRemove.forEach(function (child) {
31379
- var _child$userData10;
31380
- if ((_child$userData10 = child.userData) !== null && _child$userData10 !== void 0 && _child$userData10.isPipeElbow) {
31678
+ var _child$userData11;
31679
+ if ((_child$userData11 = child.userData) !== null && _child$userData11 !== void 0 && _child$userData11.isPipeElbow) {
31381
31680
  console.log("\uD83D\uDDD1\uFE0F Removing elbow child from segment before manualization: ".concat(child.uuid));
31382
31681
  segment.remove(child);
31383
31682
  if (child.geometry) child.geometry.dispose();
@@ -31528,7 +31827,7 @@ var SceneOperationsManager = /*#__PURE__*/function () {
31528
31827
  value: function _convertConnectedGatewaysToManual(connectors, currentSceneData) {
31529
31828
  var _connectors$,
31530
31829
  _segment$userData2,
31531
- _this5 = this;
31830
+ _this7 = this;
31532
31831
  console.log('🔍 Checking for connected gateways to convert to manual...');
31533
31832
  var sceneViewer = this.sceneViewer;
31534
31833
  var convertedGateways = [];
@@ -31565,7 +31864,7 @@ var SceneOperationsManager = /*#__PURE__*/function () {
31565
31864
  console.log("\uD83D\uDD27 Found computed gateway at endpoint: ".concat(endpointObject.uuid, " - converting to manual"));
31566
31865
 
31567
31866
  // Convert gateway to manual (declared) using manualizeGateway for consistency
31568
- _this5.manualizeGateway(endpointObject, currentSceneData);
31867
+ _this7.manualizeGateway(endpointObject, currentSceneData);
31569
31868
  convertedGateways.push(endpointObject);
31570
31869
  } else if (((_endpointObject$userD5 = endpointObject.userData) === null || _endpointObject$userD5 === void 0 ? void 0 : _endpointObject$userD5.objectType) === 'gateway' && ((_endpointObject$userD6 = endpointObject.userData) === null || _endpointObject$userD6 === void 0 ? void 0 : _endpointObject$userD6.isDeclared) === true) {
31571
31870
  console.log("\u2139\uFE0F Gateway ".concat(endpointObject.uuid, " is already declared (manual), skipping conversion"));
@@ -36512,7 +36811,7 @@ var CentralPlantInternals = /*#__PURE__*/function () {
36512
36811
  return false;
36513
36812
  }
36514
36813
  try {
36515
- var _componentData$childr, _componentData$childr2, _this$centralPlant$sc6, _componentData$childr3;
36814
+ var _componentData$childr, _componentData$childr2, _this$centralPlant$sc6, _componentData$childr3, _componentData$defaul;
36516
36815
  // Generate a unique component ID if not provided
36517
36816
  var componentId = options.customId || this.generateUniqueComponentId(libraryId);
36518
36817
 
@@ -36718,6 +37017,15 @@ var CentralPlantInternals = /*#__PURE__*/function () {
36718
37017
  attachIODevicesToComponent(componentModel, componentData, modelPreloader, componentId);
36719
37018
  }
36720
37019
 
37020
+ // Register default behaviors for smart components so the BehaviorManager
37021
+ // responds to tooltip-driven state changes immediately after drop.
37022
+ // (The scene-load path uses _processBehaviors instead, which runs on loadSceneData.)
37023
+ if ((_componentData$defaul = componentData.defaultBehaviors) !== null && _componentData$defaul !== void 0 && _componentData$defaul.length) {
37024
+ var _this$centralPlant$sc7, _som$registerBehavior;
37025
+ var som = (_this$centralPlant$sc7 = this.centralPlant.sceneViewer) === null || _this$centralPlant$sc7 === void 0 ? void 0 : _this$centralPlant$sc7.sceneOperationsManager;
37026
+ som === null || som === void 0 || (_som$registerBehavior = som.registerBehaviorsForComponentInstance) === null || _som$registerBehavior === void 0 || _som$registerBehavior.call(som, componentData, componentId);
37027
+ }
37028
+
36721
37029
  // Notify the component manager about the new component
36722
37030
  if (componentManager.registerComponent) {
36723
37031
  componentManager.registerComponent(componentModel);
@@ -36776,9 +37084,9 @@ var CentralPlantInternals = /*#__PURE__*/function () {
36776
37084
  }, {
36777
37085
  key: "deleteComponent",
36778
37086
  value: function deleteComponent(componentId) {
36779
- var _this$centralPlant$sc7;
37087
+ var _this$centralPlant$sc8;
36780
37088
  // Check if component manager is available
36781
- var componentManager = (_this$centralPlant$sc7 = this.centralPlant.sceneViewer) === null || _this$centralPlant$sc7 === void 0 ? void 0 : _this$centralPlant$sc7.componentManager;
37089
+ var componentManager = (_this$centralPlant$sc8 = this.centralPlant.sceneViewer) === null || _this$centralPlant$sc8 === void 0 ? void 0 : _this$centralPlant$sc8.componentManager;
36782
37090
  if (!componentManager) {
36783
37091
  console.error('❌ deleteComponent(): Component manager not available');
36784
37092
  return false;
@@ -36873,7 +37181,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
36873
37181
  * Initialize the CentralPlant manager
36874
37182
  *
36875
37183
  * @constructor
36876
- * @version 0.1.92
37184
+ * @version 0.1.94
36877
37185
  * @updated 2025-10-22
36878
37186
  *
36879
37187
  * @description Creates a new CentralPlant instance and initializes internal managers and utilities.