@2112-lab/central-plant 0.1.85 → 0.1.87

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.
@@ -3817,7 +3817,8 @@ var TransformControlsManager = /*#__PURE__*/function () {
3817
3817
  onTransformEnd: null,
3818
3818
  onModeChange: null,
3819
3819
  onObjectRemoved: null,
3820
- onSelectionChanged: null
3820
+ onSelectionChanged: null,
3821
+ onIODeviceClick: null
3821
3822
  };
3822
3823
  this.init();
3823
3824
  }
@@ -3895,6 +3896,9 @@ var TransformControlsManager = /*#__PURE__*/function () {
3895
3896
  if (callbacks.onSelectionChanged) {
3896
3897
  this.callbacks.onSelectionChanged = callbacks.onSelectionChanged;
3897
3898
  }
3899
+ if (callbacks.onIODeviceClick) {
3900
+ this.callbacks.onIODeviceClick = callbacks.onIODeviceClick;
3901
+ }
3898
3902
  console.log('🔗 Transform controls callbacks registered');
3899
3903
  }
3900
3904
  /**
@@ -4140,6 +4144,31 @@ var TransformControlsManager = /*#__PURE__*/function () {
4140
4144
  _this4._calculateMousePosition(event, mouse);
4141
4145
  raycaster.setFromCamera(mouse, _this4.camera);
4142
4146
 
4147
+ // Check for direct io-device mesh click (before bounding box selection)
4148
+ if (_this4.callbacks.onIODeviceClick) {
4149
+ var allIntersects = raycaster.intersectObjects(_this4.scene.children, true);
4150
+ var _iterator = _createForOfIteratorHelper(allIntersects),
4151
+ _step;
4152
+ try {
4153
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
4154
+ var hit = _step.value;
4155
+ var obj = hit.object;
4156
+ while (obj) {
4157
+ var _obj$userData;
4158
+ if (((_obj$userData = obj.userData) === null || _obj$userData === void 0 ? void 0 : _obj$userData.objectType) === 'io-device') {
4159
+ _this4.callbacks.onIODeviceClick(obj);
4160
+ return;
4161
+ }
4162
+ obj = obj.parent;
4163
+ }
4164
+ }
4165
+ } catch (err) {
4166
+ _iterator.e(err);
4167
+ } finally {
4168
+ _iterator.f();
4169
+ }
4170
+ }
4171
+
4143
4172
  // Find target object using appropriate selection method
4144
4173
  var targetObject = _this4._findTargetObject(raycaster, objectFilter);
4145
4174
 
@@ -4337,11 +4366,11 @@ var TransformControlsManager = /*#__PURE__*/function () {
4337
4366
  var isNewSegmentHorizontal = this._isSegmentHorizontal(segment);
4338
4367
 
4339
4368
  // Check if all existing segments have the same orientation
4340
- var _iterator = _createForOfIteratorHelper(existingSegments),
4341
- _step;
4369
+ var _iterator2 = _createForOfIteratorHelper(existingSegments),
4370
+ _step2;
4342
4371
  try {
4343
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
4344
- var existingSegment = _step.value;
4372
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
4373
+ var existingSegment = _step2.value;
4345
4374
  var isExistingHorizontal = this._isSegmentHorizontal(existingSegment);
4346
4375
 
4347
4376
  // Disallow mixing horizontal and vertical
@@ -4350,9 +4379,9 @@ var TransformControlsManager = /*#__PURE__*/function () {
4350
4379
  }
4351
4380
  }
4352
4381
  } catch (err) {
4353
- _iterator.e(err);
4382
+ _iterator2.e(err);
4354
4383
  } finally {
4355
- _iterator.f();
4384
+ _iterator2.f();
4356
4385
  }
4357
4386
  return true;
4358
4387
  }
@@ -5166,11 +5195,11 @@ var TransformControlsManager = /*#__PURE__*/function () {
5166
5195
  var objectFilter = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
5167
5196
  var objectsWithBounds = this.getSelectableObjectsWithBounds(objectFilter);
5168
5197
  var intersections = [];
5169
- var _iterator2 = _createForOfIteratorHelper(objectsWithBounds),
5170
- _step2;
5198
+ var _iterator3 = _createForOfIteratorHelper(objectsWithBounds),
5199
+ _step3;
5171
5200
  try {
5172
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
5173
- var item = _step2.value;
5201
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
5202
+ var item = _step3.value;
5174
5203
  var object = item.object,
5175
5204
  boundingBox = item.boundingBox;
5176
5205
 
@@ -5190,9 +5219,9 @@ var TransformControlsManager = /*#__PURE__*/function () {
5190
5219
 
5191
5220
  // Sort by distance (closest first)
5192
5221
  } catch (err) {
5193
- _iterator2.e(err);
5222
+ _iterator3.e(err);
5194
5223
  } finally {
5195
- _iterator2.f();
5224
+ _iterator3.f();
5196
5225
  }
5197
5226
  intersections.sort(function (a, b) {
5198
5227
  return a.distance - b.distance;
@@ -5578,8 +5607,8 @@ var TransformControlsManager = /*#__PURE__*/function () {
5578
5607
  key: "_updateSegmentReference",
5579
5608
  value: function _updateSegmentReference(oldSegment, newSegment, index) {
5580
5609
  var selectedIndex = this.selectedObjects.findIndex(function (obj) {
5581
- var _obj$userData;
5582
- return obj.uuid === oldSegment.uuid || ((_obj$userData = obj.userData) === null || _obj$userData === void 0 ? void 0 : _obj$userData.originalUuid) === oldSegment.uuid;
5610
+ var _obj$userData2;
5611
+ return obj.uuid === oldSegment.uuid || ((_obj$userData2 = obj.userData) === null || _obj$userData2 === void 0 ? void 0 : _obj$userData2.originalUuid) === oldSegment.uuid;
5583
5612
  });
5584
5613
  if (selectedIndex !== -1 && newSegment) {
5585
5614
  // Clear bounding box cache
@@ -11346,6 +11375,12 @@ var SceneExportManager = /*#__PURE__*/function () {
11346
11375
  // Computed from geometry - not in input format
11347
11376
  'name',
11348
11377
  // Redundant with GLB node names - not in input format
11378
+ 'addedTimestamp',
11379
+ // Internal tracking - not needed in export
11380
+ 'addedBy',
11381
+ // Internal tracking - not needed in export
11382
+ 'initialPosition',
11383
+ // Internal tracking - not needed in export
11349
11384
  // Exclude internal segment tracking properties
11350
11385
  'segmentId',
11351
11386
  // Internal tracking
@@ -33753,6 +33788,52 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
33753
33788
  }
33754
33789
  }
33755
33790
 
33791
+ /**
33792
+ * Toggle the first binary state on an io-device object.
33793
+ * Called when the user clicks directly on an io-device mesh in the 3D scene.
33794
+ * @param {THREE.Object3D} ioDeviceObject - The io-device Group/Mesh that was clicked
33795
+ */
33796
+ }, {
33797
+ key: "toggleIODeviceBinaryState",
33798
+ value: function toggleIODeviceBinaryState(ioDeviceObject) {
33799
+ var _ref, _this$sceneViewer;
33800
+ if (!ioDeviceObject || !this._stateAdapter) return;
33801
+ var ud = ioDeviceObject.userData;
33802
+ var attachmentId = ud === null || ud === void 0 ? void 0 : ud.attachmentId;
33803
+ var dataPoints = (ud === null || ud === void 0 ? void 0 : ud.dataPoints) || [];
33804
+ if (!attachmentId) return;
33805
+
33806
+ // Walk up to find parent component UUID for scoped state/behavior handling
33807
+ var parentUuid = null;
33808
+ var obj = ioDeviceObject.parent;
33809
+ while (obj) {
33810
+ var _obj$userData;
33811
+ if (((_obj$userData = obj.userData) === null || _obj$userData === void 0 ? void 0 : _obj$userData.objectType) === 'component') {
33812
+ parentUuid = obj.uuid;
33813
+ break;
33814
+ }
33815
+ obj = obj.parent;
33816
+ }
33817
+
33818
+ // Create a scoped attachment key to prevent state sharing between instances
33819
+ // of the same smart component that share the same attachmentId
33820
+ var scopedAttachmentId = this._getScopedAttachmentKey(attachmentId, parentUuid);
33821
+
33822
+ // Find the first binary state
33823
+ var binaryState = dataPoints.find(function (dp) {
33824
+ return dp.stateType === 'binary' || dp.type === 'binary';
33825
+ });
33826
+ if (!binaryState) return;
33827
+ var dpId = binaryState.id;
33828
+ var storedVal = this._stateAdapter.getState(scopedAttachmentId, dpId);
33829
+ // Fall back to defaultValue when state is uninitialized (null/undefined)
33830
+ var currentVal = (_ref = storedVal !== null && storedVal !== void 0 ? storedVal : binaryState.defaultValue) !== null && _ref !== void 0 ? _ref : false;
33831
+ var newVal = !Boolean(currentVal);
33832
+ this._stateAdapter.setState(scopedAttachmentId, dpId, newVal);
33833
+ (_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);
33834
+ console.log("\uD83D\uDD04 [IODevice] Toggled ".concat(scopedAttachmentId, ".").concat(dpId, ": ").concat(currentVal, " \u2192 ").concat(newVal));
33835
+ }
33836
+
33756
33837
  /**
33757
33838
  * Should be called when an object is selected or deselected.
33758
33839
  * @param {THREE.Object3D|null} object
@@ -33842,23 +33923,42 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
33842
33923
  this._styleInjected = false;
33843
33924
  }
33844
33925
 
33926
+ /**
33927
+ * Generate a scoped attachment key that includes the parent component UUID.
33928
+ * This ensures each instance of a smart component has isolated IO device state.
33929
+ * @param {string} attachmentId - The original attachment ID from the component data
33930
+ * @param {string|null} parentUuid - The UUID of the parent smart component instance
33931
+ * @returns {string} A scoped key in the format "parentUuid::attachmentId" or just attachmentId if no parent
33932
+ * @private
33933
+ */
33934
+ }, {
33935
+ key: "_getScopedAttachmentKey",
33936
+ value: function _getScopedAttachmentKey(attachmentId, parentUuid) {
33937
+ if (!parentUuid) return attachmentId;
33938
+ return "".concat(parentUuid, "::").concat(attachmentId);
33939
+ }
33940
+
33845
33941
  /**
33846
33942
  * Gather I/O device children from a component's Three.js hierarchy.
33847
33943
  * Returns richer data including attachmentId and data point definitions.
33848
- * @param {THREE.Object3D} object
33849
- * @returns {{ label: string, deviceId: string, attachmentId: string, dataPoints: Array }[]}
33944
+ * @param {THREE.Object3D} object - The parent component object
33945
+ * @returns {{ label: string, deviceId: string, attachmentId: string, scopedAttachmentId: string, dataPoints: Array }[]}
33850
33946
  */
33851
33947
  }, {
33852
33948
  key: "_getIODevices",
33853
33949
  value: function _getIODevices(object) {
33950
+ var _this2 = this;
33854
33951
  var devices = [];
33952
+ var parentUuid = object.uuid; // The component's own UUID
33855
33953
  object.traverse(function (child) {
33856
33954
  var _child$userData;
33857
33955
  if (((_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.objectType) === 'io-device') {
33956
+ var attachmentId = child.userData.attachmentId || '';
33858
33957
  devices.push({
33859
33958
  label: child.userData.attachmentLabel || child.name || child.userData.deviceId || 'Unknown Device',
33860
33959
  deviceId: child.userData.deviceId || '',
33861
- attachmentId: child.userData.attachmentId || '',
33960
+ attachmentId: attachmentId,
33961
+ scopedAttachmentId: _this2._getScopedAttachmentKey(attachmentId, parentUuid),
33862
33962
  dataPoints: child.userData.dataPoints || [],
33863
33963
  direction: child.userData.ioDirection || 'output'
33864
33964
  });
@@ -33874,7 +33974,7 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
33874
33974
  }, {
33875
33975
  key: "_buildTooltip",
33876
33976
  value: function _buildTooltip(object) {
33877
- var _this2 = this;
33977
+ var _this3 = this;
33878
33978
  // Remove any existing tooltip first
33879
33979
  this.hide();
33880
33980
  // Re-assign selected object since hide() clears it
@@ -33942,9 +34042,10 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
33942
34042
  list.appendChild(item);
33943
34043
 
33944
34044
  // Data point rows (one per data point definition stored in userData)
33945
- if (device.attachmentId && device.dataPoints.length > 0) {
34045
+ // Use scopedAttachmentId to ensure state is isolated per component instance
34046
+ if (device.scopedAttachmentId && device.dataPoints.length > 0) {
33946
34047
  device.dataPoints.forEach(function (dp) {
33947
- var row = _this2._buildDataPointRow(device.attachmentId, dp, device.direction);
34048
+ var row = _this3._buildDataPointRow(device.scopedAttachmentId, dp, device.direction, device.attachmentId);
33948
34049
  list.appendChild(row);
33949
34050
  });
33950
34051
  }
@@ -33955,11 +34056,11 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
33955
34056
  // Hover expand/collapse
33956
34057
  trigger.addEventListener('mouseenter', function () {
33957
34058
  ioSection.classList.add('expanded');
33958
- _this2._ioExpanded = true;
34059
+ _this3._ioExpanded = true;
33959
34060
  });
33960
34061
  ioSection.addEventListener('mouseleave', function () {
33961
34062
  ioSection.classList.remove('expanded');
33962
- _this2._ioExpanded = false;
34063
+ _this3._ioExpanded = false;
33963
34064
  });
33964
34065
  card.appendChild(ioSection);
33965
34066
  } else {
@@ -34003,11 +34104,11 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
34003
34104
  }, {
34004
34105
  key: "_positionTooltip",
34005
34106
  value: function _positionTooltip() {
34006
- var _this$sceneViewer, _this$sceneViewer2;
34107
+ var _this$sceneViewer2, _this$sceneViewer3;
34007
34108
  if (!this.tooltipEl || !this.selectedObject) return;
34008
34109
  var container = this._getContainer();
34009
- var camera = (_this$sceneViewer = this.sceneViewer) === null || _this$sceneViewer === void 0 ? void 0 : _this$sceneViewer.camera;
34010
- var renderer = (_this$sceneViewer2 = this.sceneViewer) === null || _this$sceneViewer2 === void 0 ? void 0 : _this$sceneViewer2.renderer;
34110
+ var camera = (_this$sceneViewer2 = this.sceneViewer) === null || _this$sceneViewer2 === void 0 ? void 0 : _this$sceneViewer2.camera;
34111
+ var renderer = (_this$sceneViewer3 = this.sceneViewer) === null || _this$sceneViewer3 === void 0 ? void 0 : _this$sceneViewer3.renderer;
34011
34112
  if (!container || !camera || !renderer) return;
34012
34113
 
34013
34114
  // Compute bounding box to position above the component
@@ -34044,8 +34145,8 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
34044
34145
  }, {
34045
34146
  key: "_getContainer",
34046
34147
  value: function _getContainer() {
34047
- var _this$sceneViewer3;
34048
- return ((_this$sceneViewer3 = this.sceneViewer) === null || _this$sceneViewer3 === void 0 || (_this$sceneViewer3 = _this$sceneViewer3.renderer) === null || _this$sceneViewer3 === void 0 || (_this$sceneViewer3 = _this$sceneViewer3.domElement) === null || _this$sceneViewer3 === void 0 ? void 0 : _this$sceneViewer3.parentElement) || null;
34148
+ var _this$sceneViewer4;
34149
+ 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;
34049
34150
  }
34050
34151
 
34051
34152
  // -----------------------------------------------------------------------
@@ -34058,18 +34159,19 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
34058
34159
  * Output / read-only direction → shows a state badge (updated each frame).
34059
34160
  * Input / bidirectional → shows an interactive control.
34060
34161
  *
34061
- * @param {string} attachmentId
34162
+ * @param {string} scopedAttachmentId - Scoped attachment ID (parentUuid::attachmentId) for state isolation
34062
34163
  * @param {Object} dp - data point definition from ioConfig.dataPoints
34063
34164
  * @param {string} [deviceDirection] - device-level direction ('input'|'output'), overrides dp.direction
34165
+ * @param {string} [originalAttachmentId] - Original attachment ID for behavior triggering
34064
34166
  * @returns {HTMLElement}
34065
34167
  */
34066
34168
  }, {
34067
34169
  key: "_buildDataPointRow",
34068
- value: function _buildDataPointRow(attachmentId, dp, deviceDirection) {
34069
- var _ref,
34170
+ value: function _buildDataPointRow(scopedAttachmentId, dp, deviceDirection, originalAttachmentId) {
34171
+ var _ref2,
34070
34172
  _this$_stateAdapter$g,
34071
34173
  _this$_stateAdapter,
34072
- _this3 = this;
34174
+ _this4 = this;
34073
34175
  var row = document.createElement('div');
34074
34176
  row.className = 'cp-tooltip__dp-row';
34075
34177
  var nameEl = document.createElement('span');
@@ -34077,20 +34179,21 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
34077
34179
  nameEl.textContent = dp.name || dp.id || '?';
34078
34180
  row.appendChild(nameEl);
34079
34181
  var dpId = dp.id || dp.name;
34080
- var key = "".concat(attachmentId, "::").concat(dpId);
34182
+ var key = "".concat(scopedAttachmentId, "::").concat(dpId);
34081
34183
  // Device-level direction takes precedence; fall back to per-dp direction
34082
34184
  var resolvedDirection = deviceDirection || dp.direction || 'output';
34083
34185
  var isInput = resolvedDirection === 'input' || resolvedDirection === 'bidirectional';
34084
- var currentVal = (_ref = (_this$_stateAdapter$g = (_this$_stateAdapter = this._stateAdapter) === null || _this$_stateAdapter === void 0 ? void 0 : _this$_stateAdapter.getState(attachmentId, dpId)) !== null && _this$_stateAdapter$g !== void 0 ? _this$_stateAdapter$g : dp.defaultValue) !== null && _ref !== void 0 ? _ref : null;
34186
+ 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;
34085
34187
  if (isInput) {
34086
34188
  var ctrl = this._buildInputControl(dp, currentVal, function (newVal) {
34087
- var _this3$_stateAdapter, _this3$selectedObject, _this3$sceneViewer;
34088
- (_this3$_stateAdapter = _this3._stateAdapter) === null || _this3$_stateAdapter === void 0 || _this3$_stateAdapter.setState(attachmentId, dpId, newVal);
34189
+ var _this4$_stateAdapter, _this4$selectedObject, _this4$sceneViewer;
34190
+ (_this4$_stateAdapter = _this4._stateAdapter) === null || _this4$_stateAdapter === void 0 || _this4$_stateAdapter.setState(scopedAttachmentId, dpId, newVal);
34089
34191
  // Also fire BehaviorManager so any wired behaviors react immediately.
34090
34192
  // Pass the parent component UUID so behaviors scoped to a specific instance
34091
34193
  // don't bleed across clones that share the same attachmentId.
34092
- var parentUuid = ((_this3$selectedObject = _this3.selectedObject) === null || _this3$selectedObject === void 0 ? void 0 : _this3$selectedObject.uuid) || null;
34093
- (_this3$sceneViewer = _this3.sceneViewer) === null || _this3$sceneViewer === void 0 || (_this3$sceneViewer = _this3$sceneViewer.managers) === null || _this3$sceneViewer === void 0 || (_this3$sceneViewer = _this3$sceneViewer.behaviorManager) === null || _this3$sceneViewer === void 0 || _this3$sceneViewer.triggerState(attachmentId, dpId, newVal, parentUuid);
34194
+ // Use originalAttachmentId for behavior triggering as behaviors are keyed by original ID
34195
+ var parentUuid = ((_this4$selectedObject = _this4.selectedObject) === null || _this4$selectedObject === void 0 ? void 0 : _this4$selectedObject.uuid) || null;
34196
+ (_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);
34094
34197
  });
34095
34198
  row.appendChild(ctrl);
34096
34199
  this._stateElements.set(key, {
@@ -34240,20 +34343,23 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
34240
34343
  /**
34241
34344
  * Re-read all tracked read-only badge values from the state adapter.
34242
34345
  * Called each frame from update() — skips if no adapter is configured.
34346
+ * Key format is scopedAttachmentId::dataPointId where scopedAttachmentId
34347
+ * can be parentUuid::attachmentId, resulting in parentUuid::attachmentId::dataPointId
34243
34348
  */
34244
34349
  }, {
34245
34350
  key: "_refreshStateDisplays",
34246
34351
  value: function _refreshStateDisplays() {
34247
- var _this4 = this;
34352
+ var _this5 = this;
34248
34353
  if (!this._stateAdapter || !this._stateElements.size) return;
34249
34354
  this._stateElements.forEach(function (entry, key) {
34250
34355
  if (entry.isInput) return; // interactive controls are user-driven; don't overwrite
34251
- var sepIdx = key.indexOf('::');
34356
+ // Use lastIndexOf since scopedAttachmentId may contain '::' (parentUuid::attachmentId)
34357
+ var sepIdx = key.lastIndexOf('::');
34252
34358
  if (sepIdx === -1) return;
34253
- var attachmentId = key.slice(0, sepIdx);
34359
+ var scopedAttachmentId = key.slice(0, sepIdx);
34254
34360
  var dataPointId = key.slice(sepIdx + 2);
34255
- var val = _this4._stateAdapter.getState(attachmentId, dataPointId);
34256
- _this4._applyBadgeValue(entry.el, val, entry.dp);
34361
+ var val = _this5._stateAdapter.getState(scopedAttachmentId, dataPointId);
34362
+ _this5._applyBadgeValue(entry.el, val, entry.dp);
34257
34363
  });
34258
34364
  }
34259
34365
  }]);
@@ -36665,7 +36771,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
36665
36771
  * Initialize the CentralPlant manager
36666
36772
  *
36667
36773
  * @constructor
36668
- * @version 0.1.85
36774
+ * @version 0.1.87
36669
36775
  * @updated 2025-10-22
36670
36776
  *
36671
36777
  * @description Creates a new CentralPlant instance and initializes internal managers and utilities.
@@ -39386,6 +39492,12 @@ var sceneViewer = /*#__PURE__*/function (_BaseDisposable) {
39386
39492
  console.log("\uD83D\uDCE1 Emitting component-deselected");
39387
39493
  _this4.emit('component-deselected');
39388
39494
  }
39495
+ },
39496
+ onIODeviceClick: function onIODeviceClick(ioDeviceObject) {
39497
+ // Direct click on an io-device mesh in the scene → toggle its binary state
39498
+ if (_this4.componentTooltipManager) {
39499
+ _this4.componentTooltipManager.toggleIODeviceBinaryState(ioDeviceObject);
39500
+ }
39389
39501
  }
39390
39502
  });
39391
39503
 
@@ -19,7 +19,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
19
19
  * Initialize the CentralPlant manager
20
20
  *
21
21
  * @constructor
22
- * @version 0.1.85
22
+ * @version 0.1.87
23
23
  * @updated 2025-10-22
24
24
  *
25
25
  * @description Creates a new CentralPlant instance and initializes internal managers and utilities.
@@ -409,6 +409,12 @@ var sceneViewer = /*#__PURE__*/function (_BaseDisposable) {
409
409
  console.log("\uD83D\uDCE1 Emitting component-deselected");
410
410
  _this4.emit('component-deselected');
411
411
  }
412
+ },
413
+ onIODeviceClick: function onIODeviceClick(ioDeviceObject) {
414
+ // Direct click on an io-device mesh in the scene → toggle its binary state
415
+ if (_this4.componentTooltipManager) {
416
+ _this4.componentTooltipManager.toggleIODeviceBinaryState(ioDeviceObject);
417
+ }
412
418
  }
413
419
  });
414
420
 
@@ -117,7 +117,8 @@ var TransformControlsManager = /*#__PURE__*/function () {
117
117
  onTransformEnd: null,
118
118
  onModeChange: null,
119
119
  onObjectRemoved: null,
120
- onSelectionChanged: null
120
+ onSelectionChanged: null,
121
+ onIODeviceClick: null
121
122
  };
122
123
  this.init();
123
124
  }
@@ -195,6 +196,9 @@ var TransformControlsManager = /*#__PURE__*/function () {
195
196
  if (callbacks.onSelectionChanged) {
196
197
  this.callbacks.onSelectionChanged = callbacks.onSelectionChanged;
197
198
  }
199
+ if (callbacks.onIODeviceClick) {
200
+ this.callbacks.onIODeviceClick = callbacks.onIODeviceClick;
201
+ }
198
202
  console.log('🔗 Transform controls callbacks registered');
199
203
  }
200
204
  /**
@@ -440,6 +444,31 @@ var TransformControlsManager = /*#__PURE__*/function () {
440
444
  _this4._calculateMousePosition(event, mouse);
441
445
  raycaster.setFromCamera(mouse, _this4.camera);
442
446
 
447
+ // Check for direct io-device mesh click (before bounding box selection)
448
+ if (_this4.callbacks.onIODeviceClick) {
449
+ var allIntersects = raycaster.intersectObjects(_this4.scene.children, true);
450
+ var _iterator = _rollupPluginBabelHelpers.createForOfIteratorHelper(allIntersects),
451
+ _step;
452
+ try {
453
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
454
+ var hit = _step.value;
455
+ var obj = hit.object;
456
+ while (obj) {
457
+ var _obj$userData;
458
+ if (((_obj$userData = obj.userData) === null || _obj$userData === void 0 ? void 0 : _obj$userData.objectType) === 'io-device') {
459
+ _this4.callbacks.onIODeviceClick(obj);
460
+ return;
461
+ }
462
+ obj = obj.parent;
463
+ }
464
+ }
465
+ } catch (err) {
466
+ _iterator.e(err);
467
+ } finally {
468
+ _iterator.f();
469
+ }
470
+ }
471
+
443
472
  // Find target object using appropriate selection method
444
473
  var targetObject = _this4._findTargetObject(raycaster, objectFilter);
445
474
 
@@ -637,11 +666,11 @@ var TransformControlsManager = /*#__PURE__*/function () {
637
666
  var isNewSegmentHorizontal = this._isSegmentHorizontal(segment);
638
667
 
639
668
  // Check if all existing segments have the same orientation
640
- var _iterator = _rollupPluginBabelHelpers.createForOfIteratorHelper(existingSegments),
641
- _step;
669
+ var _iterator2 = _rollupPluginBabelHelpers.createForOfIteratorHelper(existingSegments),
670
+ _step2;
642
671
  try {
643
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
644
- var existingSegment = _step.value;
672
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
673
+ var existingSegment = _step2.value;
645
674
  var isExistingHorizontal = this._isSegmentHorizontal(existingSegment);
646
675
 
647
676
  // Disallow mixing horizontal and vertical
@@ -650,9 +679,9 @@ var TransformControlsManager = /*#__PURE__*/function () {
650
679
  }
651
680
  }
652
681
  } catch (err) {
653
- _iterator.e(err);
682
+ _iterator2.e(err);
654
683
  } finally {
655
- _iterator.f();
684
+ _iterator2.f();
656
685
  }
657
686
  return true;
658
687
  }
@@ -1466,11 +1495,11 @@ var TransformControlsManager = /*#__PURE__*/function () {
1466
1495
  var objectFilter = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
1467
1496
  var objectsWithBounds = this.getSelectableObjectsWithBounds(objectFilter);
1468
1497
  var intersections = [];
1469
- var _iterator2 = _rollupPluginBabelHelpers.createForOfIteratorHelper(objectsWithBounds),
1470
- _step2;
1498
+ var _iterator3 = _rollupPluginBabelHelpers.createForOfIteratorHelper(objectsWithBounds),
1499
+ _step3;
1471
1500
  try {
1472
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
1473
- var item = _step2.value;
1501
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
1502
+ var item = _step3.value;
1474
1503
  var object = item.object,
1475
1504
  boundingBox = item.boundingBox;
1476
1505
 
@@ -1490,9 +1519,9 @@ var TransformControlsManager = /*#__PURE__*/function () {
1490
1519
 
1491
1520
  // Sort by distance (closest first)
1492
1521
  } catch (err) {
1493
- _iterator2.e(err);
1522
+ _iterator3.e(err);
1494
1523
  } finally {
1495
- _iterator2.f();
1524
+ _iterator3.f();
1496
1525
  }
1497
1526
  intersections.sort(function (a, b) {
1498
1527
  return a.distance - b.distance;
@@ -1878,8 +1907,8 @@ var TransformControlsManager = /*#__PURE__*/function () {
1878
1907
  key: "_updateSegmentReference",
1879
1908
  value: function _updateSegmentReference(oldSegment, newSegment, index) {
1880
1909
  var selectedIndex = this.selectedObjects.findIndex(function (obj) {
1881
- var _obj$userData;
1882
- return obj.uuid === oldSegment.uuid || ((_obj$userData = obj.userData) === null || _obj$userData === void 0 ? void 0 : _obj$userData.originalUuid) === oldSegment.uuid;
1910
+ var _obj$userData2;
1911
+ return obj.uuid === oldSegment.uuid || ((_obj$userData2 = obj.userData) === null || _obj$userData2 === void 0 ? void 0 : _obj$userData2.originalUuid) === oldSegment.uuid;
1883
1912
  });
1884
1913
  if (selectedIndex !== -1 && newSegment) {
1885
1914
  // Clear bounding box cache
@@ -121,6 +121,52 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
121
121
  }
122
122
  }
123
123
 
124
+ /**
125
+ * Toggle the first binary state on an io-device object.
126
+ * Called when the user clicks directly on an io-device mesh in the 3D scene.
127
+ * @param {THREE.Object3D} ioDeviceObject - The io-device Group/Mesh that was clicked
128
+ */
129
+ }, {
130
+ key: "toggleIODeviceBinaryState",
131
+ value: function toggleIODeviceBinaryState(ioDeviceObject) {
132
+ var _ref, _this$sceneViewer;
133
+ if (!ioDeviceObject || !this._stateAdapter) return;
134
+ var ud = ioDeviceObject.userData;
135
+ var attachmentId = ud === null || ud === void 0 ? void 0 : ud.attachmentId;
136
+ var dataPoints = (ud === null || ud === void 0 ? void 0 : ud.dataPoints) || [];
137
+ if (!attachmentId) return;
138
+
139
+ // Walk up to find parent component UUID for scoped state/behavior handling
140
+ var parentUuid = null;
141
+ var obj = ioDeviceObject.parent;
142
+ while (obj) {
143
+ var _obj$userData;
144
+ if (((_obj$userData = obj.userData) === null || _obj$userData === void 0 ? void 0 : _obj$userData.objectType) === 'component') {
145
+ parentUuid = obj.uuid;
146
+ break;
147
+ }
148
+ obj = obj.parent;
149
+ }
150
+
151
+ // Create a scoped attachment key to prevent state sharing between instances
152
+ // of the same smart component that share the same attachmentId
153
+ var scopedAttachmentId = this._getScopedAttachmentKey(attachmentId, parentUuid);
154
+
155
+ // Find the first binary state
156
+ var binaryState = dataPoints.find(function (dp) {
157
+ return dp.stateType === 'binary' || dp.type === 'binary';
158
+ });
159
+ if (!binaryState) return;
160
+ var dpId = binaryState.id;
161
+ var storedVal = this._stateAdapter.getState(scopedAttachmentId, dpId);
162
+ // Fall back to defaultValue when state is uninitialized (null/undefined)
163
+ var currentVal = (_ref = storedVal !== null && storedVal !== void 0 ? storedVal : binaryState.defaultValue) !== null && _ref !== void 0 ? _ref : false;
164
+ var newVal = !Boolean(currentVal);
165
+ this._stateAdapter.setState(scopedAttachmentId, dpId, newVal);
166
+ (_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);
167
+ console.log("\uD83D\uDD04 [IODevice] Toggled ".concat(scopedAttachmentId, ".").concat(dpId, ": ").concat(currentVal, " \u2192 ").concat(newVal));
168
+ }
169
+
124
170
  /**
125
171
  * Should be called when an object is selected or deselected.
126
172
  * @param {THREE.Object3D|null} object
@@ -210,23 +256,42 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
210
256
  this._styleInjected = false;
211
257
  }
212
258
 
259
+ /**
260
+ * Generate a scoped attachment key that includes the parent component UUID.
261
+ * This ensures each instance of a smart component has isolated IO device state.
262
+ * @param {string} attachmentId - The original attachment ID from the component data
263
+ * @param {string|null} parentUuid - The UUID of the parent smart component instance
264
+ * @returns {string} A scoped key in the format "parentUuid::attachmentId" or just attachmentId if no parent
265
+ * @private
266
+ */
267
+ }, {
268
+ key: "_getScopedAttachmentKey",
269
+ value: function _getScopedAttachmentKey(attachmentId, parentUuid) {
270
+ if (!parentUuid) return attachmentId;
271
+ return "".concat(parentUuid, "::").concat(attachmentId);
272
+ }
273
+
213
274
  /**
214
275
  * Gather I/O device children from a component's Three.js hierarchy.
215
276
  * Returns richer data including attachmentId and data point definitions.
216
- * @param {THREE.Object3D} object
217
- * @returns {{ label: string, deviceId: string, attachmentId: string, dataPoints: Array }[]}
277
+ * @param {THREE.Object3D} object - The parent component object
278
+ * @returns {{ label: string, deviceId: string, attachmentId: string, scopedAttachmentId: string, dataPoints: Array }[]}
218
279
  */
219
280
  }, {
220
281
  key: "_getIODevices",
221
282
  value: function _getIODevices(object) {
283
+ var _this2 = this;
222
284
  var devices = [];
285
+ var parentUuid = object.uuid; // The component's own UUID
223
286
  object.traverse(function (child) {
224
287
  var _child$userData;
225
288
  if (((_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.objectType) === 'io-device') {
289
+ var attachmentId = child.userData.attachmentId || '';
226
290
  devices.push({
227
291
  label: child.userData.attachmentLabel || child.name || child.userData.deviceId || 'Unknown Device',
228
292
  deviceId: child.userData.deviceId || '',
229
- attachmentId: child.userData.attachmentId || '',
293
+ attachmentId: attachmentId,
294
+ scopedAttachmentId: _this2._getScopedAttachmentKey(attachmentId, parentUuid),
230
295
  dataPoints: child.userData.dataPoints || [],
231
296
  direction: child.userData.ioDirection || 'output'
232
297
  });
@@ -242,7 +307,7 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
242
307
  }, {
243
308
  key: "_buildTooltip",
244
309
  value: function _buildTooltip(object) {
245
- var _this2 = this;
310
+ var _this3 = this;
246
311
  // Remove any existing tooltip first
247
312
  this.hide();
248
313
  // Re-assign selected object since hide() clears it
@@ -310,9 +375,10 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
310
375
  list.appendChild(item);
311
376
 
312
377
  // Data point rows (one per data point definition stored in userData)
313
- if (device.attachmentId && device.dataPoints.length > 0) {
378
+ // Use scopedAttachmentId to ensure state is isolated per component instance
379
+ if (device.scopedAttachmentId && device.dataPoints.length > 0) {
314
380
  device.dataPoints.forEach(function (dp) {
315
- var row = _this2._buildDataPointRow(device.attachmentId, dp, device.direction);
381
+ var row = _this3._buildDataPointRow(device.scopedAttachmentId, dp, device.direction, device.attachmentId);
316
382
  list.appendChild(row);
317
383
  });
318
384
  }
@@ -323,11 +389,11 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
323
389
  // Hover expand/collapse
324
390
  trigger.addEventListener('mouseenter', function () {
325
391
  ioSection.classList.add('expanded');
326
- _this2._ioExpanded = true;
392
+ _this3._ioExpanded = true;
327
393
  });
328
394
  ioSection.addEventListener('mouseleave', function () {
329
395
  ioSection.classList.remove('expanded');
330
- _this2._ioExpanded = false;
396
+ _this3._ioExpanded = false;
331
397
  });
332
398
  card.appendChild(ioSection);
333
399
  } else {
@@ -371,11 +437,11 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
371
437
  }, {
372
438
  key: "_positionTooltip",
373
439
  value: function _positionTooltip() {
374
- var _this$sceneViewer, _this$sceneViewer2;
440
+ var _this$sceneViewer2, _this$sceneViewer3;
375
441
  if (!this.tooltipEl || !this.selectedObject) return;
376
442
  var container = this._getContainer();
377
- var camera = (_this$sceneViewer = this.sceneViewer) === null || _this$sceneViewer === void 0 ? void 0 : _this$sceneViewer.camera;
378
- var renderer = (_this$sceneViewer2 = this.sceneViewer) === null || _this$sceneViewer2 === void 0 ? void 0 : _this$sceneViewer2.renderer;
443
+ var camera = (_this$sceneViewer2 = this.sceneViewer) === null || _this$sceneViewer2 === void 0 ? void 0 : _this$sceneViewer2.camera;
444
+ var renderer = (_this$sceneViewer3 = this.sceneViewer) === null || _this$sceneViewer3 === void 0 ? void 0 : _this$sceneViewer3.renderer;
379
445
  if (!container || !camera || !renderer) return;
380
446
 
381
447
  // Compute bounding box to position above the component
@@ -412,8 +478,8 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
412
478
  }, {
413
479
  key: "_getContainer",
414
480
  value: function _getContainer() {
415
- var _this$sceneViewer3;
416
- return ((_this$sceneViewer3 = this.sceneViewer) === null || _this$sceneViewer3 === void 0 || (_this$sceneViewer3 = _this$sceneViewer3.renderer) === null || _this$sceneViewer3 === void 0 || (_this$sceneViewer3 = _this$sceneViewer3.domElement) === null || _this$sceneViewer3 === void 0 ? void 0 : _this$sceneViewer3.parentElement) || null;
481
+ var _this$sceneViewer4;
482
+ 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;
417
483
  }
418
484
 
419
485
  // -----------------------------------------------------------------------
@@ -426,18 +492,19 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
426
492
  * Output / read-only direction → shows a state badge (updated each frame).
427
493
  * Input / bidirectional → shows an interactive control.
428
494
  *
429
- * @param {string} attachmentId
495
+ * @param {string} scopedAttachmentId - Scoped attachment ID (parentUuid::attachmentId) for state isolation
430
496
  * @param {Object} dp - data point definition from ioConfig.dataPoints
431
497
  * @param {string} [deviceDirection] - device-level direction ('input'|'output'), overrides dp.direction
498
+ * @param {string} [originalAttachmentId] - Original attachment ID for behavior triggering
432
499
  * @returns {HTMLElement}
433
500
  */
434
501
  }, {
435
502
  key: "_buildDataPointRow",
436
- value: function _buildDataPointRow(attachmentId, dp, deviceDirection) {
437
- var _ref,
503
+ value: function _buildDataPointRow(scopedAttachmentId, dp, deviceDirection, originalAttachmentId) {
504
+ var _ref2,
438
505
  _this$_stateAdapter$g,
439
506
  _this$_stateAdapter,
440
- _this3 = this;
507
+ _this4 = this;
441
508
  var row = document.createElement('div');
442
509
  row.className = 'cp-tooltip__dp-row';
443
510
  var nameEl = document.createElement('span');
@@ -445,20 +512,21 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
445
512
  nameEl.textContent = dp.name || dp.id || '?';
446
513
  row.appendChild(nameEl);
447
514
  var dpId = dp.id || dp.name;
448
- var key = "".concat(attachmentId, "::").concat(dpId);
515
+ var key = "".concat(scopedAttachmentId, "::").concat(dpId);
449
516
  // Device-level direction takes precedence; fall back to per-dp direction
450
517
  var resolvedDirection = deviceDirection || dp.direction || 'output';
451
518
  var isInput = resolvedDirection === 'input' || resolvedDirection === 'bidirectional';
452
- var currentVal = (_ref = (_this$_stateAdapter$g = (_this$_stateAdapter = this._stateAdapter) === null || _this$_stateAdapter === void 0 ? void 0 : _this$_stateAdapter.getState(attachmentId, dpId)) !== null && _this$_stateAdapter$g !== void 0 ? _this$_stateAdapter$g : dp.defaultValue) !== null && _ref !== void 0 ? _ref : null;
519
+ 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;
453
520
  if (isInput) {
454
521
  var ctrl = this._buildInputControl(dp, currentVal, function (newVal) {
455
- var _this3$_stateAdapter, _this3$selectedObject, _this3$sceneViewer;
456
- (_this3$_stateAdapter = _this3._stateAdapter) === null || _this3$_stateAdapter === void 0 || _this3$_stateAdapter.setState(attachmentId, dpId, newVal);
522
+ var _this4$_stateAdapter, _this4$selectedObject, _this4$sceneViewer;
523
+ (_this4$_stateAdapter = _this4._stateAdapter) === null || _this4$_stateAdapter === void 0 || _this4$_stateAdapter.setState(scopedAttachmentId, dpId, newVal);
457
524
  // Also fire BehaviorManager so any wired behaviors react immediately.
458
525
  // Pass the parent component UUID so behaviors scoped to a specific instance
459
526
  // don't bleed across clones that share the same attachmentId.
460
- var parentUuid = ((_this3$selectedObject = _this3.selectedObject) === null || _this3$selectedObject === void 0 ? void 0 : _this3$selectedObject.uuid) || null;
461
- (_this3$sceneViewer = _this3.sceneViewer) === null || _this3$sceneViewer === void 0 || (_this3$sceneViewer = _this3$sceneViewer.managers) === null || _this3$sceneViewer === void 0 || (_this3$sceneViewer = _this3$sceneViewer.behaviorManager) === null || _this3$sceneViewer === void 0 || _this3$sceneViewer.triggerState(attachmentId, dpId, newVal, parentUuid);
527
+ // Use originalAttachmentId for behavior triggering as behaviors are keyed by original ID
528
+ var parentUuid = ((_this4$selectedObject = _this4.selectedObject) === null || _this4$selectedObject === void 0 ? void 0 : _this4$selectedObject.uuid) || null;
529
+ (_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);
462
530
  });
463
531
  row.appendChild(ctrl);
464
532
  this._stateElements.set(key, {
@@ -608,20 +676,23 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
608
676
  /**
609
677
  * Re-read all tracked read-only badge values from the state adapter.
610
678
  * Called each frame from update() — skips if no adapter is configured.
679
+ * Key format is scopedAttachmentId::dataPointId where scopedAttachmentId
680
+ * can be parentUuid::attachmentId, resulting in parentUuid::attachmentId::dataPointId
611
681
  */
612
682
  }, {
613
683
  key: "_refreshStateDisplays",
614
684
  value: function _refreshStateDisplays() {
615
- var _this4 = this;
685
+ var _this5 = this;
616
686
  if (!this._stateAdapter || !this._stateElements.size) return;
617
687
  this._stateElements.forEach(function (entry, key) {
618
688
  if (entry.isInput) return; // interactive controls are user-driven; don't overwrite
619
- var sepIdx = key.indexOf('::');
689
+ // Use lastIndexOf since scopedAttachmentId may contain '::' (parentUuid::attachmentId)
690
+ var sepIdx = key.lastIndexOf('::');
620
691
  if (sepIdx === -1) return;
621
- var attachmentId = key.slice(0, sepIdx);
692
+ var scopedAttachmentId = key.slice(0, sepIdx);
622
693
  var dataPointId = key.slice(sepIdx + 2);
623
- var val = _this4._stateAdapter.getState(attachmentId, dataPointId);
624
- _this4._applyBadgeValue(entry.el, val, entry.dp);
694
+ var val = _this5._stateAdapter.getState(scopedAttachmentId, dataPointId);
695
+ _this5._applyBadgeValue(entry.el, val, entry.dp);
625
696
  });
626
697
  }
627
698
  }]);
@@ -160,6 +160,12 @@ var SceneExportManager = /*#__PURE__*/function () {
160
160
  // Computed from geometry - not in input format
161
161
  'name',
162
162
  // Redundant with GLB node names - not in input format
163
+ 'addedTimestamp',
164
+ // Internal tracking - not needed in export
165
+ 'addedBy',
166
+ // Internal tracking - not needed in export
167
+ 'initialPosition',
168
+ // Internal tracking - not needed in export
163
169
  // Exclude internal segment tracking properties
164
170
  'segmentId',
165
171
  // Internal tracking
@@ -15,7 +15,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
15
15
  * Initialize the CentralPlant manager
16
16
  *
17
17
  * @constructor
18
- * @version 0.1.85
18
+ * @version 0.1.87
19
19
  * @updated 2025-10-22
20
20
  *
21
21
  * @description Creates a new CentralPlant instance and initializes internal managers and utilities.
@@ -405,6 +405,12 @@ var sceneViewer = /*#__PURE__*/function (_BaseDisposable) {
405
405
  console.log("\uD83D\uDCE1 Emitting component-deselected");
406
406
  _this4.emit('component-deselected');
407
407
  }
408
+ },
409
+ onIODeviceClick: function onIODeviceClick(ioDeviceObject) {
410
+ // Direct click on an io-device mesh in the scene → toggle its binary state
411
+ if (_this4.componentTooltipManager) {
412
+ _this4.componentTooltipManager.toggleIODeviceBinaryState(ioDeviceObject);
413
+ }
408
414
  }
409
415
  });
410
416
 
@@ -93,7 +93,8 @@ var TransformControlsManager = /*#__PURE__*/function () {
93
93
  onTransformEnd: null,
94
94
  onModeChange: null,
95
95
  onObjectRemoved: null,
96
- onSelectionChanged: null
96
+ onSelectionChanged: null,
97
+ onIODeviceClick: null
97
98
  };
98
99
  this.init();
99
100
  }
@@ -171,6 +172,9 @@ var TransformControlsManager = /*#__PURE__*/function () {
171
172
  if (callbacks.onSelectionChanged) {
172
173
  this.callbacks.onSelectionChanged = callbacks.onSelectionChanged;
173
174
  }
175
+ if (callbacks.onIODeviceClick) {
176
+ this.callbacks.onIODeviceClick = callbacks.onIODeviceClick;
177
+ }
174
178
  console.log('🔗 Transform controls callbacks registered');
175
179
  }
176
180
  /**
@@ -416,6 +420,31 @@ var TransformControlsManager = /*#__PURE__*/function () {
416
420
  _this4._calculateMousePosition(event, mouse);
417
421
  raycaster.setFromCamera(mouse, _this4.camera);
418
422
 
423
+ // Check for direct io-device mesh click (before bounding box selection)
424
+ if (_this4.callbacks.onIODeviceClick) {
425
+ var allIntersects = raycaster.intersectObjects(_this4.scene.children, true);
426
+ var _iterator = _createForOfIteratorHelper(allIntersects),
427
+ _step;
428
+ try {
429
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
430
+ var hit = _step.value;
431
+ var obj = hit.object;
432
+ while (obj) {
433
+ var _obj$userData;
434
+ if (((_obj$userData = obj.userData) === null || _obj$userData === void 0 ? void 0 : _obj$userData.objectType) === 'io-device') {
435
+ _this4.callbacks.onIODeviceClick(obj);
436
+ return;
437
+ }
438
+ obj = obj.parent;
439
+ }
440
+ }
441
+ } catch (err) {
442
+ _iterator.e(err);
443
+ } finally {
444
+ _iterator.f();
445
+ }
446
+ }
447
+
419
448
  // Find target object using appropriate selection method
420
449
  var targetObject = _this4._findTargetObject(raycaster, objectFilter);
421
450
 
@@ -613,11 +642,11 @@ var TransformControlsManager = /*#__PURE__*/function () {
613
642
  var isNewSegmentHorizontal = this._isSegmentHorizontal(segment);
614
643
 
615
644
  // Check if all existing segments have the same orientation
616
- var _iterator = _createForOfIteratorHelper(existingSegments),
617
- _step;
645
+ var _iterator2 = _createForOfIteratorHelper(existingSegments),
646
+ _step2;
618
647
  try {
619
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
620
- var existingSegment = _step.value;
648
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
649
+ var existingSegment = _step2.value;
621
650
  var isExistingHorizontal = this._isSegmentHorizontal(existingSegment);
622
651
 
623
652
  // Disallow mixing horizontal and vertical
@@ -626,9 +655,9 @@ var TransformControlsManager = /*#__PURE__*/function () {
626
655
  }
627
656
  }
628
657
  } catch (err) {
629
- _iterator.e(err);
658
+ _iterator2.e(err);
630
659
  } finally {
631
- _iterator.f();
660
+ _iterator2.f();
632
661
  }
633
662
  return true;
634
663
  }
@@ -1442,11 +1471,11 @@ var TransformControlsManager = /*#__PURE__*/function () {
1442
1471
  var objectFilter = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
1443
1472
  var objectsWithBounds = this.getSelectableObjectsWithBounds(objectFilter);
1444
1473
  var intersections = [];
1445
- var _iterator2 = _createForOfIteratorHelper(objectsWithBounds),
1446
- _step2;
1474
+ var _iterator3 = _createForOfIteratorHelper(objectsWithBounds),
1475
+ _step3;
1447
1476
  try {
1448
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
1449
- var item = _step2.value;
1477
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
1478
+ var item = _step3.value;
1450
1479
  var object = item.object,
1451
1480
  boundingBox = item.boundingBox;
1452
1481
 
@@ -1466,9 +1495,9 @@ var TransformControlsManager = /*#__PURE__*/function () {
1466
1495
 
1467
1496
  // Sort by distance (closest first)
1468
1497
  } catch (err) {
1469
- _iterator2.e(err);
1498
+ _iterator3.e(err);
1470
1499
  } finally {
1471
- _iterator2.f();
1500
+ _iterator3.f();
1472
1501
  }
1473
1502
  intersections.sort(function (a, b) {
1474
1503
  return a.distance - b.distance;
@@ -1854,8 +1883,8 @@ var TransformControlsManager = /*#__PURE__*/function () {
1854
1883
  key: "_updateSegmentReference",
1855
1884
  value: function _updateSegmentReference(oldSegment, newSegment, index) {
1856
1885
  var selectedIndex = this.selectedObjects.findIndex(function (obj) {
1857
- var _obj$userData;
1858
- return obj.uuid === oldSegment.uuid || ((_obj$userData = obj.userData) === null || _obj$userData === void 0 ? void 0 : _obj$userData.originalUuid) === oldSegment.uuid;
1886
+ var _obj$userData2;
1887
+ return obj.uuid === oldSegment.uuid || ((_obj$userData2 = obj.userData) === null || _obj$userData2 === void 0 ? void 0 : _obj$userData2.originalUuid) === oldSegment.uuid;
1859
1888
  });
1860
1889
  if (selectedIndex !== -1 && newSegment) {
1861
1890
  // Clear bounding box cache
@@ -97,6 +97,52 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
97
97
  }
98
98
  }
99
99
 
100
+ /**
101
+ * Toggle the first binary state on an io-device object.
102
+ * Called when the user clicks directly on an io-device mesh in the 3D scene.
103
+ * @param {THREE.Object3D} ioDeviceObject - The io-device Group/Mesh that was clicked
104
+ */
105
+ }, {
106
+ key: "toggleIODeviceBinaryState",
107
+ value: function toggleIODeviceBinaryState(ioDeviceObject) {
108
+ var _ref, _this$sceneViewer;
109
+ if (!ioDeviceObject || !this._stateAdapter) return;
110
+ var ud = ioDeviceObject.userData;
111
+ var attachmentId = ud === null || ud === void 0 ? void 0 : ud.attachmentId;
112
+ var dataPoints = (ud === null || ud === void 0 ? void 0 : ud.dataPoints) || [];
113
+ if (!attachmentId) return;
114
+
115
+ // Walk up to find parent component UUID for scoped state/behavior handling
116
+ var parentUuid = null;
117
+ var obj = ioDeviceObject.parent;
118
+ while (obj) {
119
+ var _obj$userData;
120
+ if (((_obj$userData = obj.userData) === null || _obj$userData === void 0 ? void 0 : _obj$userData.objectType) === 'component') {
121
+ parentUuid = obj.uuid;
122
+ break;
123
+ }
124
+ obj = obj.parent;
125
+ }
126
+
127
+ // Create a scoped attachment key to prevent state sharing between instances
128
+ // of the same smart component that share the same attachmentId
129
+ var scopedAttachmentId = this._getScopedAttachmentKey(attachmentId, parentUuid);
130
+
131
+ // Find the first binary state
132
+ var binaryState = dataPoints.find(function (dp) {
133
+ return dp.stateType === 'binary' || dp.type === 'binary';
134
+ });
135
+ if (!binaryState) return;
136
+ var dpId = binaryState.id;
137
+ var storedVal = this._stateAdapter.getState(scopedAttachmentId, dpId);
138
+ // Fall back to defaultValue when state is uninitialized (null/undefined)
139
+ var currentVal = (_ref = storedVal !== null && storedVal !== void 0 ? storedVal : binaryState.defaultValue) !== null && _ref !== void 0 ? _ref : false;
140
+ var newVal = !Boolean(currentVal);
141
+ this._stateAdapter.setState(scopedAttachmentId, dpId, newVal);
142
+ (_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);
143
+ console.log("\uD83D\uDD04 [IODevice] Toggled ".concat(scopedAttachmentId, ".").concat(dpId, ": ").concat(currentVal, " \u2192 ").concat(newVal));
144
+ }
145
+
100
146
  /**
101
147
  * Should be called when an object is selected or deselected.
102
148
  * @param {THREE.Object3D|null} object
@@ -186,23 +232,42 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
186
232
  this._styleInjected = false;
187
233
  }
188
234
 
235
+ /**
236
+ * Generate a scoped attachment key that includes the parent component UUID.
237
+ * This ensures each instance of a smart component has isolated IO device state.
238
+ * @param {string} attachmentId - The original attachment ID from the component data
239
+ * @param {string|null} parentUuid - The UUID of the parent smart component instance
240
+ * @returns {string} A scoped key in the format "parentUuid::attachmentId" or just attachmentId if no parent
241
+ * @private
242
+ */
243
+ }, {
244
+ key: "_getScopedAttachmentKey",
245
+ value: function _getScopedAttachmentKey(attachmentId, parentUuid) {
246
+ if (!parentUuid) return attachmentId;
247
+ return "".concat(parentUuid, "::").concat(attachmentId);
248
+ }
249
+
189
250
  /**
190
251
  * Gather I/O device children from a component's Three.js hierarchy.
191
252
  * Returns richer data including attachmentId and data point definitions.
192
- * @param {THREE.Object3D} object
193
- * @returns {{ label: string, deviceId: string, attachmentId: string, dataPoints: Array }[]}
253
+ * @param {THREE.Object3D} object - The parent component object
254
+ * @returns {{ label: string, deviceId: string, attachmentId: string, scopedAttachmentId: string, dataPoints: Array }[]}
194
255
  */
195
256
  }, {
196
257
  key: "_getIODevices",
197
258
  value: function _getIODevices(object) {
259
+ var _this2 = this;
198
260
  var devices = [];
261
+ var parentUuid = object.uuid; // The component's own UUID
199
262
  object.traverse(function (child) {
200
263
  var _child$userData;
201
264
  if (((_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.objectType) === 'io-device') {
265
+ var attachmentId = child.userData.attachmentId || '';
202
266
  devices.push({
203
267
  label: child.userData.attachmentLabel || child.name || child.userData.deviceId || 'Unknown Device',
204
268
  deviceId: child.userData.deviceId || '',
205
- attachmentId: child.userData.attachmentId || '',
269
+ attachmentId: attachmentId,
270
+ scopedAttachmentId: _this2._getScopedAttachmentKey(attachmentId, parentUuid),
206
271
  dataPoints: child.userData.dataPoints || [],
207
272
  direction: child.userData.ioDirection || 'output'
208
273
  });
@@ -218,7 +283,7 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
218
283
  }, {
219
284
  key: "_buildTooltip",
220
285
  value: function _buildTooltip(object) {
221
- var _this2 = this;
286
+ var _this3 = this;
222
287
  // Remove any existing tooltip first
223
288
  this.hide();
224
289
  // Re-assign selected object since hide() clears it
@@ -286,9 +351,10 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
286
351
  list.appendChild(item);
287
352
 
288
353
  // Data point rows (one per data point definition stored in userData)
289
- if (device.attachmentId && device.dataPoints.length > 0) {
354
+ // Use scopedAttachmentId to ensure state is isolated per component instance
355
+ if (device.scopedAttachmentId && device.dataPoints.length > 0) {
290
356
  device.dataPoints.forEach(function (dp) {
291
- var row = _this2._buildDataPointRow(device.attachmentId, dp, device.direction);
357
+ var row = _this3._buildDataPointRow(device.scopedAttachmentId, dp, device.direction, device.attachmentId);
292
358
  list.appendChild(row);
293
359
  });
294
360
  }
@@ -299,11 +365,11 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
299
365
  // Hover expand/collapse
300
366
  trigger.addEventListener('mouseenter', function () {
301
367
  ioSection.classList.add('expanded');
302
- _this2._ioExpanded = true;
368
+ _this3._ioExpanded = true;
303
369
  });
304
370
  ioSection.addEventListener('mouseleave', function () {
305
371
  ioSection.classList.remove('expanded');
306
- _this2._ioExpanded = false;
372
+ _this3._ioExpanded = false;
307
373
  });
308
374
  card.appendChild(ioSection);
309
375
  } else {
@@ -347,11 +413,11 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
347
413
  }, {
348
414
  key: "_positionTooltip",
349
415
  value: function _positionTooltip() {
350
- var _this$sceneViewer, _this$sceneViewer2;
416
+ var _this$sceneViewer2, _this$sceneViewer3;
351
417
  if (!this.tooltipEl || !this.selectedObject) return;
352
418
  var container = this._getContainer();
353
- var camera = (_this$sceneViewer = this.sceneViewer) === null || _this$sceneViewer === void 0 ? void 0 : _this$sceneViewer.camera;
354
- var renderer = (_this$sceneViewer2 = this.sceneViewer) === null || _this$sceneViewer2 === void 0 ? void 0 : _this$sceneViewer2.renderer;
419
+ var camera = (_this$sceneViewer2 = this.sceneViewer) === null || _this$sceneViewer2 === void 0 ? void 0 : _this$sceneViewer2.camera;
420
+ var renderer = (_this$sceneViewer3 = this.sceneViewer) === null || _this$sceneViewer3 === void 0 ? void 0 : _this$sceneViewer3.renderer;
355
421
  if (!container || !camera || !renderer) return;
356
422
 
357
423
  // Compute bounding box to position above the component
@@ -388,8 +454,8 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
388
454
  }, {
389
455
  key: "_getContainer",
390
456
  value: function _getContainer() {
391
- var _this$sceneViewer3;
392
- return ((_this$sceneViewer3 = this.sceneViewer) === null || _this$sceneViewer3 === void 0 || (_this$sceneViewer3 = _this$sceneViewer3.renderer) === null || _this$sceneViewer3 === void 0 || (_this$sceneViewer3 = _this$sceneViewer3.domElement) === null || _this$sceneViewer3 === void 0 ? void 0 : _this$sceneViewer3.parentElement) || null;
457
+ var _this$sceneViewer4;
458
+ 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;
393
459
  }
394
460
 
395
461
  // -----------------------------------------------------------------------
@@ -402,18 +468,19 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
402
468
  * Output / read-only direction → shows a state badge (updated each frame).
403
469
  * Input / bidirectional → shows an interactive control.
404
470
  *
405
- * @param {string} attachmentId
471
+ * @param {string} scopedAttachmentId - Scoped attachment ID (parentUuid::attachmentId) for state isolation
406
472
  * @param {Object} dp - data point definition from ioConfig.dataPoints
407
473
  * @param {string} [deviceDirection] - device-level direction ('input'|'output'), overrides dp.direction
474
+ * @param {string} [originalAttachmentId] - Original attachment ID for behavior triggering
408
475
  * @returns {HTMLElement}
409
476
  */
410
477
  }, {
411
478
  key: "_buildDataPointRow",
412
- value: function _buildDataPointRow(attachmentId, dp, deviceDirection) {
413
- var _ref,
479
+ value: function _buildDataPointRow(scopedAttachmentId, dp, deviceDirection, originalAttachmentId) {
480
+ var _ref2,
414
481
  _this$_stateAdapter$g,
415
482
  _this$_stateAdapter,
416
- _this3 = this;
483
+ _this4 = this;
417
484
  var row = document.createElement('div');
418
485
  row.className = 'cp-tooltip__dp-row';
419
486
  var nameEl = document.createElement('span');
@@ -421,20 +488,21 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
421
488
  nameEl.textContent = dp.name || dp.id || '?';
422
489
  row.appendChild(nameEl);
423
490
  var dpId = dp.id || dp.name;
424
- var key = "".concat(attachmentId, "::").concat(dpId);
491
+ var key = "".concat(scopedAttachmentId, "::").concat(dpId);
425
492
  // Device-level direction takes precedence; fall back to per-dp direction
426
493
  var resolvedDirection = deviceDirection || dp.direction || 'output';
427
494
  var isInput = resolvedDirection === 'input' || resolvedDirection === 'bidirectional';
428
- var currentVal = (_ref = (_this$_stateAdapter$g = (_this$_stateAdapter = this._stateAdapter) === null || _this$_stateAdapter === void 0 ? void 0 : _this$_stateAdapter.getState(attachmentId, dpId)) !== null && _this$_stateAdapter$g !== void 0 ? _this$_stateAdapter$g : dp.defaultValue) !== null && _ref !== void 0 ? _ref : null;
495
+ 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;
429
496
  if (isInput) {
430
497
  var ctrl = this._buildInputControl(dp, currentVal, function (newVal) {
431
- var _this3$_stateAdapter, _this3$selectedObject, _this3$sceneViewer;
432
- (_this3$_stateAdapter = _this3._stateAdapter) === null || _this3$_stateAdapter === void 0 || _this3$_stateAdapter.setState(attachmentId, dpId, newVal);
498
+ var _this4$_stateAdapter, _this4$selectedObject, _this4$sceneViewer;
499
+ (_this4$_stateAdapter = _this4._stateAdapter) === null || _this4$_stateAdapter === void 0 || _this4$_stateAdapter.setState(scopedAttachmentId, dpId, newVal);
433
500
  // Also fire BehaviorManager so any wired behaviors react immediately.
434
501
  // Pass the parent component UUID so behaviors scoped to a specific instance
435
502
  // don't bleed across clones that share the same attachmentId.
436
- var parentUuid = ((_this3$selectedObject = _this3.selectedObject) === null || _this3$selectedObject === void 0 ? void 0 : _this3$selectedObject.uuid) || null;
437
- (_this3$sceneViewer = _this3.sceneViewer) === null || _this3$sceneViewer === void 0 || (_this3$sceneViewer = _this3$sceneViewer.managers) === null || _this3$sceneViewer === void 0 || (_this3$sceneViewer = _this3$sceneViewer.behaviorManager) === null || _this3$sceneViewer === void 0 || _this3$sceneViewer.triggerState(attachmentId, dpId, newVal, parentUuid);
503
+ // Use originalAttachmentId for behavior triggering as behaviors are keyed by original ID
504
+ var parentUuid = ((_this4$selectedObject = _this4.selectedObject) === null || _this4$selectedObject === void 0 ? void 0 : _this4$selectedObject.uuid) || null;
505
+ (_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);
438
506
  });
439
507
  row.appendChild(ctrl);
440
508
  this._stateElements.set(key, {
@@ -584,20 +652,23 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
584
652
  /**
585
653
  * Re-read all tracked read-only badge values from the state adapter.
586
654
  * Called each frame from update() — skips if no adapter is configured.
655
+ * Key format is scopedAttachmentId::dataPointId where scopedAttachmentId
656
+ * can be parentUuid::attachmentId, resulting in parentUuid::attachmentId::dataPointId
587
657
  */
588
658
  }, {
589
659
  key: "_refreshStateDisplays",
590
660
  value: function _refreshStateDisplays() {
591
- var _this4 = this;
661
+ var _this5 = this;
592
662
  if (!this._stateAdapter || !this._stateElements.size) return;
593
663
  this._stateElements.forEach(function (entry, key) {
594
664
  if (entry.isInput) return; // interactive controls are user-driven; don't overwrite
595
- var sepIdx = key.indexOf('::');
665
+ // Use lastIndexOf since scopedAttachmentId may contain '::' (parentUuid::attachmentId)
666
+ var sepIdx = key.lastIndexOf('::');
596
667
  if (sepIdx === -1) return;
597
- var attachmentId = key.slice(0, sepIdx);
668
+ var scopedAttachmentId = key.slice(0, sepIdx);
598
669
  var dataPointId = key.slice(sepIdx + 2);
599
- var val = _this4._stateAdapter.getState(attachmentId, dataPointId);
600
- _this4._applyBadgeValue(entry.el, val, entry.dp);
670
+ var val = _this5._stateAdapter.getState(scopedAttachmentId, dataPointId);
671
+ _this5._applyBadgeValue(entry.el, val, entry.dp);
601
672
  });
602
673
  }
603
674
  }]);
@@ -138,6 +138,12 @@ var SceneExportManager = /*#__PURE__*/function () {
138
138
  // Computed from geometry - not in input format
139
139
  'name',
140
140
  // Redundant with GLB node names - not in input format
141
+ 'addedTimestamp',
142
+ // Internal tracking - not needed in export
143
+ 'addedBy',
144
+ // Internal tracking - not needed in export
145
+ 'initialPosition',
146
+ // Internal tracking - not needed in export
141
147
  // Exclude internal segment tracking properties
142
148
  'segmentId',
143
149
  // Internal tracking
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@2112-lab/central-plant",
3
- "version": "0.1.85",
3
+ "version": "0.1.87",
4
4
  "description": "Utility modules for the Central Plant Application",
5
5
  "main": "dist/bundle/index.js",
6
6
  "module": "dist/esm/src/index.js",