@2112-lab/central-plant 0.3.26 → 0.3.27

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.
@@ -168,6 +168,169 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
168
168
  console.log("\uD83D\uDD04 [IODevice] Toggled ".concat(scopedAttachmentId, ".").concat(dpId, ": ").concat(currentVal, " \u2192 ").concat(newVal));
169
169
  }
170
170
 
171
+ // ── IO device drag-to-state ─────────────────────────────────────────────
172
+
173
+ /**
174
+ * Begin tracking a drag gesture on an IO device mesh.
175
+ * Records the initial state of each animation data point so that
176
+ * `updateIODeviceDrag` can compute relative offsets from it.
177
+ *
178
+ * @param {THREE.Object3D} ioDeviceObject
179
+ */
180
+ }, {
181
+ key: "startIODeviceDrag",
182
+ value: function startIODeviceDrag(ioDeviceObject) {
183
+ var _this$sceneViewer3,
184
+ _this2 = this;
185
+ if (!ioDeviceObject || !this._stateAdapter) return;
186
+ var ud = ioDeviceObject.userData;
187
+ var attachmentId = ud === null || ud === void 0 ? void 0 : ud.attachmentId;
188
+ if (!attachmentId) return;
189
+ var parentUuid = null;
190
+ var obj = ioDeviceObject.parent;
191
+ while (obj) {
192
+ var _obj$userData2;
193
+ if (((_obj$userData2 = obj.userData) === null || _obj$userData2 === void 0 ? void 0 : _obj$userData2.objectType) === 'component') {
194
+ parentUuid = obj.uuid;
195
+ break;
196
+ }
197
+ obj = obj.parent;
198
+ }
199
+ var scopedAttachmentId = this._getScopedAttachmentKey(attachmentId, parentUuid);
200
+ var ioAnimMgr = (_this$sceneViewer3 = this.sceneViewer) === null || _this$sceneViewer3 === void 0 || (_this$sceneViewer3 = _this$sceneViewer3.managers) === null || _this$sceneViewer3 === void 0 ? void 0 : _this$sceneViewer3.ioAnimationManager;
201
+ var dataPoints = ((ioAnimMgr === null || ioAnimMgr === void 0 ? void 0 : ioAnimMgr.getAnimationDataPoints(parentUuid, attachmentId)) || []).concat((ud === null || ud === void 0 ? void 0 : ud.dataPoints) || [])
202
+ // deduplicate by id
203
+ .filter(function (dp, i, arr) {
204
+ return arr.findIndex(function (d) {
205
+ return d.id === dp.id;
206
+ }) === i;
207
+ });
208
+ var dpSessions = [];
209
+ var _iterator = _rollupPluginBabelHelpers.createForOfIteratorHelper(dataPoints),
210
+ _step;
211
+ try {
212
+ var _loop = function _loop() {
213
+ var _this2$_stateAdapter$;
214
+ var dp = _step.value;
215
+ var stateType = (dp.stateType || '').toLowerCase();
216
+ if (stateType !== 'binary' && stateType !== 'boolean' && stateType !== 'enum') return 1; // continue
217
+ var curVal = (_this2$_stateAdapter$ = _this2._stateAdapter.getState(scopedAttachmentId, dp.id)) !== null && _this2$_stateAdapter$ !== void 0 ? _this2$_stateAdapter$ : dp.defaultValue;
218
+ if (stateType === 'binary' || stateType === 'boolean') {
219
+ dpSessions.push({
220
+ dp: dp,
221
+ scopedAttachmentId: scopedAttachmentId,
222
+ attachmentId: attachmentId,
223
+ parentUuid: parentUuid,
224
+ stateType: 'binary',
225
+ lastApplied: curVal
226
+ });
227
+ } else {
228
+ var _dp$stateConfig;
229
+ var opts = ((_dp$stateConfig = dp.stateConfig) === null || _dp$stateConfig === void 0 ? void 0 : _dp$stateConfig.options) || [];
230
+ var curIdx = opts.findIndex(function (o) {
231
+ return String(o) === String(curVal);
232
+ });
233
+ dpSessions.push({
234
+ dp: dp,
235
+ scopedAttachmentId: scopedAttachmentId,
236
+ attachmentId: attachmentId,
237
+ parentUuid: parentUuid,
238
+ stateType: 'enum',
239
+ opts: opts,
240
+ startIdx: curIdx >= 0 ? curIdx : 0,
241
+ lastAppliedIdx: curIdx >= 0 ? curIdx : 0
242
+ });
243
+ }
244
+ };
245
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
246
+ if (_loop()) continue;
247
+ }
248
+ } catch (err) {
249
+ _iterator.e(err);
250
+ } finally {
251
+ _iterator.f();
252
+ }
253
+ this._ioDragSession = dpSessions.length ? {
254
+ dpSessions: dpSessions
255
+ } : null;
256
+ }
257
+
258
+ /**
259
+ * Update animated mesh state while a drag is in progress.
260
+ * Called continuously during pointermove.
261
+ *
262
+ * Sign convention: up/right = positive `signedDelta`.
263
+ * - Binary: > +20 px → true/on state, < −20 px → false/off state.
264
+ * - Enum: each ±30 px step advances/retreats one option in the list.
265
+ *
266
+ * @param {number} signedDelta - Cumulative signed pixel displacement since drag start
267
+ */
268
+ }, {
269
+ key: "updateIODeviceDrag",
270
+ value: function updateIODeviceDrag(signedDelta) {
271
+ var session = this._ioDragSession;
272
+ if (!session) return;
273
+ var BINARY_THRESHOLD = 20;
274
+ var ENUM_STEP_PX = 30;
275
+ var _iterator2 = _rollupPluginBabelHelpers.createForOfIteratorHelper(session.dpSessions),
276
+ _step2;
277
+ try {
278
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
279
+ var dps = _step2.value;
280
+ if (dps.stateType === 'binary') {
281
+ var newVal = void 0;
282
+ if (signedDelta > BINARY_THRESHOLD) {
283
+ newVal = true;
284
+ } else if (signedDelta < -BINARY_THRESHOLD) {
285
+ newVal = false;
286
+ } else {
287
+ continue; // dead zone
288
+ }
289
+ if (newVal === dps.lastApplied) continue;
290
+ dps.lastApplied = newVal;
291
+ this._applyDpState(dps, newVal);
292
+ } else if (dps.stateType === 'enum') {
293
+ var steps = Math.round(signedDelta / ENUM_STEP_PX);
294
+ var newIdx = Math.max(0, Math.min(dps.opts.length - 1, dps.startIdx + steps));
295
+ if (newIdx === dps.lastAppliedIdx) continue;
296
+ dps.lastAppliedIdx = newIdx;
297
+ this._applyDpState(dps, dps.opts[newIdx]);
298
+ }
299
+ }
300
+ } catch (err) {
301
+ _iterator2.e(err);
302
+ } finally {
303
+ _iterator2.f();
304
+ }
305
+ }
306
+
307
+ /**
308
+ * Clean up drag session state on pointerup.
309
+ */
310
+ }, {
311
+ key: "endIODeviceDrag",
312
+ value: function endIODeviceDrag() {
313
+ this._ioDragSession = null;
314
+ }
315
+
316
+ /**
317
+ * Apply a new value to a data point, updating Vuex state and firing behavior/animation triggers.
318
+ * @private
319
+ */
320
+ }, {
321
+ key: "_applyDpState",
322
+ value: function _applyDpState(_ref2, newVal) {
323
+ var _this$_stateAdapter, _this$sceneViewer4, _this$sceneViewer5;
324
+ var scopedAttachmentId = _ref2.scopedAttachmentId,
325
+ attachmentId = _ref2.attachmentId,
326
+ parentUuid = _ref2.parentUuid,
327
+ dp = _ref2.dp;
328
+ var dpId = dp.id;
329
+ (_this$_stateAdapter = this._stateAdapter) === null || _this$_stateAdapter === void 0 || _this$_stateAdapter.setState(scopedAttachmentId, dpId, newVal);
330
+ (_this$sceneViewer4 = this.sceneViewer) === null || _this$sceneViewer4 === void 0 || (_this$sceneViewer4 = _this$sceneViewer4.managers) === null || _this$sceneViewer4 === void 0 || (_this$sceneViewer4 = _this$sceneViewer4.behaviorManager) === null || _this$sceneViewer4 === void 0 || _this$sceneViewer4.triggerState(attachmentId, dpId, newVal, parentUuid);
331
+ (_this$sceneViewer5 = this.sceneViewer) === null || _this$sceneViewer5 === void 0 || (_this$sceneViewer5 = _this$sceneViewer5.managers) === null || _this$sceneViewer5 === void 0 || (_this$sceneViewer5 = _this$sceneViewer5.ioAnimationManager) === null || _this$sceneViewer5 === void 0 || _this$sceneViewer5.triggerState(attachmentId, dpId, newVal, parentUuid);
332
+ }
333
+
171
334
  /**
172
335
  * Should be called when an object is selected or deselected.
173
336
  * @param {THREE.Object3D|null} object
@@ -281,18 +444,18 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
281
444
  }, {
282
445
  key: "_getIODevices",
283
446
  value: function _getIODevices(object) {
284
- var _this2 = this;
447
+ var _this3 = this;
285
448
  var devices = [];
286
449
  var parentUuid = object.uuid; // The component's own UUID
287
450
  object.traverse(function (child) {
288
451
  var _child$userData;
289
452
  if (((_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.objectType) === 'io-device') {
290
- var _this2$sceneViewer$ma, _this2$sceneViewer;
453
+ var _this3$sceneViewer$ma, _this3$sceneViewer;
291
454
  var attachmentId = child.userData.attachmentId || '';
292
455
 
293
456
  // Use only data points from the animate window (animationConfig).
294
457
  // The static ioConfig.states[] snapshot on userData is intentionally ignored.
295
- var dataPoints = (_this2$sceneViewer$ma = (_this2$sceneViewer = _this2.sceneViewer) === null || _this2$sceneViewer === void 0 || (_this2$sceneViewer = _this2$sceneViewer.managers) === null || _this2$sceneViewer === void 0 || (_this2$sceneViewer = _this2$sceneViewer.ioAnimationManager) === null || _this2$sceneViewer === void 0 ? void 0 : _this2$sceneViewer.getAnimationDataPoints(parentUuid, attachmentId)) !== null && _this2$sceneViewer$ma !== void 0 ? _this2$sceneViewer$ma : [];
458
+ var dataPoints = (_this3$sceneViewer$ma = (_this3$sceneViewer = _this3.sceneViewer) === null || _this3$sceneViewer === void 0 || (_this3$sceneViewer = _this3$sceneViewer.managers) === null || _this3$sceneViewer === void 0 || (_this3$sceneViewer = _this3$sceneViewer.ioAnimationManager) === null || _this3$sceneViewer === void 0 ? void 0 : _this3$sceneViewer.getAnimationDataPoints(parentUuid, attachmentId)) !== null && _this3$sceneViewer$ma !== void 0 ? _this3$sceneViewer$ma : [];
296
459
 
297
460
  // When data points come from animationConfig they already carry direction:'input'.
298
461
  // Pass null so _buildDataPointRow uses the per-dp direction instead of the
@@ -302,7 +465,7 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
302
465
  label: child.userData.attachmentLabel || child.name || child.userData.deviceId || 'Unknown Device',
303
466
  deviceId: child.userData.deviceId || '',
304
467
  attachmentId: attachmentId,
305
- scopedAttachmentId: _this2._getScopedAttachmentKey(attachmentId, parentUuid),
468
+ scopedAttachmentId: _this3._getScopedAttachmentKey(attachmentId, parentUuid),
306
469
  dataPoints: dataPoints,
307
470
  direction: deviceDirection
308
471
  });
@@ -318,7 +481,7 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
318
481
  }, {
319
482
  key: "_buildTooltip",
320
483
  value: function _buildTooltip(object) {
321
- var _this3 = this;
484
+ var _this4 = this;
322
485
  // Remove any existing tooltip first
323
486
  this.hide();
324
487
  // Re-assign selected object since hide() clears it
@@ -389,7 +552,7 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
389
552
  // Use scopedAttachmentId to ensure state is isolated per component instance
390
553
  if (device.scopedAttachmentId && device.dataPoints.length > 0) {
391
554
  device.dataPoints.forEach(function (dp) {
392
- var row = _this3._buildDataPointRow(device.scopedAttachmentId, dp, device.direction, device.attachmentId);
555
+ var row = _this4._buildDataPointRow(device.scopedAttachmentId, dp, device.direction, device.attachmentId);
393
556
  list.appendChild(row);
394
557
  });
395
558
  }
@@ -400,11 +563,11 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
400
563
  // Hover expand/collapse
401
564
  trigger.addEventListener('mouseenter', function () {
402
565
  ioSection.classList.add('expanded');
403
- _this3._ioExpanded = true;
566
+ _this4._ioExpanded = true;
404
567
  });
405
568
  ioSection.addEventListener('mouseleave', function () {
406
569
  ioSection.classList.remove('expanded');
407
- _this3._ioExpanded = false;
570
+ _this4._ioExpanded = false;
408
571
  });
409
572
  card.appendChild(ioSection);
410
573
  } else {
@@ -448,11 +611,11 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
448
611
  }, {
449
612
  key: "_positionTooltip",
450
613
  value: function _positionTooltip() {
451
- var _this$sceneViewer3, _this$sceneViewer4;
614
+ var _this$sceneViewer6, _this$sceneViewer7;
452
615
  if (!this.tooltipEl || !this.selectedObject) return;
453
616
  var container = this._getContainer();
454
- var camera = (_this$sceneViewer3 = this.sceneViewer) === null || _this$sceneViewer3 === void 0 ? void 0 : _this$sceneViewer3.camera;
455
- var renderer = (_this$sceneViewer4 = this.sceneViewer) === null || _this$sceneViewer4 === void 0 ? void 0 : _this$sceneViewer4.renderer;
617
+ var camera = (_this$sceneViewer6 = this.sceneViewer) === null || _this$sceneViewer6 === void 0 ? void 0 : _this$sceneViewer6.camera;
618
+ var renderer = (_this$sceneViewer7 = this.sceneViewer) === null || _this$sceneViewer7 === void 0 ? void 0 : _this$sceneViewer7.renderer;
456
619
  if (!container || !camera || !renderer) return;
457
620
 
458
621
  // Compute bounding box to position above the component
@@ -489,8 +652,8 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
489
652
  }, {
490
653
  key: "_getContainer",
491
654
  value: function _getContainer() {
492
- var _this$sceneViewer5;
493
- return ((_this$sceneViewer5 = this.sceneViewer) === null || _this$sceneViewer5 === void 0 || (_this$sceneViewer5 = _this$sceneViewer5.renderer) === null || _this$sceneViewer5 === void 0 || (_this$sceneViewer5 = _this$sceneViewer5.domElement) === null || _this$sceneViewer5 === void 0 ? void 0 : _this$sceneViewer5.parentElement) || null;
655
+ var _this$sceneViewer8;
656
+ return ((_this$sceneViewer8 = this.sceneViewer) === null || _this$sceneViewer8 === void 0 || (_this$sceneViewer8 = _this$sceneViewer8.renderer) === null || _this$sceneViewer8 === void 0 || (_this$sceneViewer8 = _this$sceneViewer8.domElement) === null || _this$sceneViewer8 === void 0 ? void 0 : _this$sceneViewer8.parentElement) || null;
494
657
  }
495
658
 
496
659
  // -----------------------------------------------------------------------
@@ -512,10 +675,10 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
512
675
  }, {
513
676
  key: "_buildDataPointRow",
514
677
  value: function _buildDataPointRow(scopedAttachmentId, dp, deviceDirection, originalAttachmentId) {
515
- var _ref2,
678
+ var _ref3,
516
679
  _this$_stateAdapter$g,
517
- _this$_stateAdapter,
518
- _this4 = this;
680
+ _this$_stateAdapter2,
681
+ _this5 = this;
519
682
  var row = document.createElement('div');
520
683
  row.className = 'cp-tooltip__dp-row';
521
684
  var nameEl = document.createElement('span');
@@ -527,18 +690,18 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
527
690
  // Device-level direction takes precedence; fall back to per-dp direction
528
691
  var resolvedDirection = deviceDirection || dp.direction || 'output';
529
692
  var isInput = resolvedDirection === 'input' || resolvedDirection === 'bidirectional';
530
- 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;
693
+ var currentVal = (_ref3 = (_this$_stateAdapter$g = (_this$_stateAdapter2 = this._stateAdapter) === null || _this$_stateAdapter2 === void 0 ? void 0 : _this$_stateAdapter2.getState(scopedAttachmentId, dpId)) !== null && _this$_stateAdapter$g !== void 0 ? _this$_stateAdapter$g : dp.defaultValue) !== null && _ref3 !== void 0 ? _ref3 : null;
531
694
  if (isInput) {
532
695
  var ctrl = this._buildInputControl(dp, currentVal, function (newVal) {
533
- var _this4$_stateAdapter, _this4$selectedObject, _this4$sceneViewer, _this4$sceneViewer2;
534
- (_this4$_stateAdapter = _this4._stateAdapter) === null || _this4$_stateAdapter === void 0 || _this4$_stateAdapter.setState(scopedAttachmentId, dpId, newVal);
696
+ var _this5$_stateAdapter, _this5$selectedObject, _this5$sceneViewer, _this5$sceneViewer2;
697
+ (_this5$_stateAdapter = _this5._stateAdapter) === null || _this5$_stateAdapter === void 0 || _this5$_stateAdapter.setState(scopedAttachmentId, dpId, newVal);
535
698
  // Also fire BehaviorManager so any wired behaviors react immediately.
536
699
  // Pass the parent component UUID so behaviors scoped to a specific instance
537
700
  // don't bleed across clones that share the same attachmentId.
538
701
  // Use originalAttachmentId for behavior triggering as behaviors are keyed by original ID
539
- var parentUuid = ((_this4$selectedObject = _this4.selectedObject) === null || _this4$selectedObject === void 0 ? void 0 : _this4$selectedObject.uuid) || null;
540
- (_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);
541
- (_this4$sceneViewer2 = _this4.sceneViewer) === null || _this4$sceneViewer2 === void 0 || (_this4$sceneViewer2 = _this4$sceneViewer2.managers) === null || _this4$sceneViewer2 === void 0 || (_this4$sceneViewer2 = _this4$sceneViewer2.ioAnimationManager) === null || _this4$sceneViewer2 === void 0 || _this4$sceneViewer2.triggerState(originalAttachmentId || scopedAttachmentId, dpId, newVal, parentUuid);
702
+ var parentUuid = ((_this5$selectedObject = _this5.selectedObject) === null || _this5$selectedObject === void 0 ? void 0 : _this5$selectedObject.uuid) || null;
703
+ (_this5$sceneViewer = _this5.sceneViewer) === null || _this5$sceneViewer === void 0 || (_this5$sceneViewer = _this5$sceneViewer.managers) === null || _this5$sceneViewer === void 0 || (_this5$sceneViewer = _this5$sceneViewer.behaviorManager) === null || _this5$sceneViewer === void 0 || _this5$sceneViewer.triggerState(originalAttachmentId || scopedAttachmentId, dpId, newVal, parentUuid);
704
+ (_this5$sceneViewer2 = _this5.sceneViewer) === null || _this5$sceneViewer2 === void 0 || (_this5$sceneViewer2 = _this5$sceneViewer2.managers) === null || _this5$sceneViewer2 === void 0 || (_this5$sceneViewer2 = _this5$sceneViewer2.ioAnimationManager) === null || _this5$sceneViewer2 === void 0 || _this5$sceneViewer2.triggerState(originalAttachmentId || scopedAttachmentId, dpId, newVal, parentUuid);
542
705
  });
543
706
  row.appendChild(ctrl);
544
707
  this._stateElements.set(key, {
@@ -547,9 +710,9 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
547
710
  isInput: true
548
711
  });
549
712
  } else {
550
- var _dp$stateConfig;
713
+ var _dp$stateConfig2;
551
714
  // unit suffix (optional, shown between name and badge)
552
- var unit = (_dp$stateConfig = dp.stateConfig) === null || _dp$stateConfig === void 0 ? void 0 : _dp$stateConfig.unit;
715
+ var unit = (_dp$stateConfig2 = dp.stateConfig) === null || _dp$stateConfig2 === void 0 ? void 0 : _dp$stateConfig2.unit;
553
716
  if (unit) {
554
717
  var unitEl = document.createElement('span');
555
718
  unitEl.className = 'cp-tooltip__dp-unit';
@@ -694,7 +857,7 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
694
857
  }, {
695
858
  key: "_refreshStateDisplays",
696
859
  value: function _refreshStateDisplays() {
697
- var _this5 = this;
860
+ var _this6 = this;
698
861
  if (!this._stateAdapter || !this._stateElements.size) return;
699
862
  this._stateElements.forEach(function (entry, key) {
700
863
  if (entry.isInput) return; // interactive controls are user-driven; don't overwrite
@@ -703,8 +866,8 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
703
866
  if (sepIdx === -1) return;
704
867
  var scopedAttachmentId = key.slice(0, sepIdx);
705
868
  var dataPointId = key.slice(sepIdx + 2);
706
- var val = _this5._stateAdapter.getState(scopedAttachmentId, dataPointId);
707
- _this5._applyBadgeValue(entry.el, val, entry.dp);
869
+ var val = _this6._stateAdapter.getState(scopedAttachmentId, dataPointId);
870
+ _this6._applyBadgeValue(entry.el, val, entry.dp);
708
871
  });
709
872
  }
710
873
  }]);
@@ -77,7 +77,7 @@ var ModelManager = /*#__PURE__*/function () {
77
77
  key: "loadLibraryModel",
78
78
  value: function () {
79
79
  var _loadLibraryModel = _rollupPluginBabelHelpers.asyncToGenerator(/*#__PURE__*/_rollupPluginBabelHelpers.regenerator().m(function _callee(targetMesh, jsonEntry, componentData) {
80
- var component, _jsonEntry$userData, _jsonEntry$userData2, _jsonEntry$userData3, originalProps, connectorChildren, gltfScene, libraryModel, _this$sceneViewer, ioAnimMgr, _loop, _i, _Object$entries, _jsonEntry$userData4, _t;
80
+ var component, _jsonEntry$userData, _jsonEntry$userData2, _jsonEntry$userData3, originalProps, connectorChildren, gltfScene, libraryModel, _this$sceneViewer, ioAnimMgr, _loop, _i, _Object$entries, warmFn, _jsonEntry$userData4, _t;
81
81
  return _rollupPluginBabelHelpers.regenerator().w(function (_context2) {
82
82
  while (1) switch (_context2.n) {
83
83
  case 0:
@@ -174,6 +174,20 @@ var ModelManager = /*#__PURE__*/function () {
174
174
  case 8:
175
175
  // Replace mesh in scene
176
176
  this._replaceMeshInScene(targetMesh, libraryModel, originalProps.parent, component);
177
+
178
+ // Pre-warm the filtered bounding-box cache for smart components so the
179
+ // first selection is instant. Deferred to idle time so it does not
180
+ // block the current frame.
181
+ if (componentData.isSmart) {
182
+ warmFn = function warmFn() {
183
+ return boundingBoxUtils.computeFilteredBoundingBoxCached(libraryModel, ['io-device', 'connector']);
184
+ };
185
+ if (typeof requestIdleCallback !== 'undefined') {
186
+ requestIdleCallback(warmFn);
187
+ } else {
188
+ setTimeout(warmFn, 0);
189
+ }
190
+ }
177
191
  console.log("\uD83C\uDF89 ".concat((_jsonEntry$userData3 = jsonEntry.userData) === null || _jsonEntry$userData3 === void 0 ? void 0 : _jsonEntry$userData3.libraryId, " GLB model successfully rendered in scene"));
178
192
  return _context2.a(2, libraryModel);
179
193
  case 9:
@@ -233,6 +233,34 @@ function computeIODeviceBoundingBoxes(componentObject) {
233
233
  * const helpers = createSelectionBoxHelpers(pumpModel, 0x00ff00)
234
234
  * helpers.forEach(h => scene.add(h))
235
235
  */
236
+ /**
237
+ * Returns a filtered bounding box for `object`, using a cache stored on
238
+ * `object.userData._filteredBBoxCache`. The cache key is the serialised
239
+ * world-matrix elements string; if the object has moved the cache is
240
+ * automatically invalidated and recomputed.
241
+ *
242
+ * This avoids the expensive full-traverse on every selection event for
243
+ * large smart components with many child meshes.
244
+ *
245
+ * @param {THREE.Object3D} object
246
+ * @param {string[]} excludeTypes
247
+ * @returns {THREE.Box3}
248
+ */
249
+ function computeFilteredBoundingBoxCached(object, excludeTypes) {
250
+ object.updateMatrixWorld(true);
251
+ var matrixKey = object.matrixWorld.elements.join(',');
252
+ var cache = object.userData._filteredBBoxCache;
253
+ if (cache && cache.matrixKey === matrixKey) {
254
+ return new THREE__namespace.Box3(_rollupPluginBabelHelpers.construct(THREE__namespace.Vector3, _rollupPluginBabelHelpers.toConsumableArray(cache.min)), _rollupPluginBabelHelpers.construct(THREE__namespace.Vector3, _rollupPluginBabelHelpers.toConsumableArray(cache.max)));
255
+ }
256
+ var box = computeFilteredBoundingBox(object, excludeTypes);
257
+ object.userData._filteredBBoxCache = {
258
+ matrixKey: matrixKey,
259
+ min: box.min.toArray(),
260
+ max: box.max.toArray()
261
+ };
262
+ return box;
263
+ }
236
264
  function createSelectionBoxHelpers(object) {
237
265
  var _object$children;
238
266
  var color = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0x00ff00;
@@ -246,7 +274,7 @@ function createSelectionBoxHelpers(object) {
246
274
  });
247
275
  if (hasIODevices) {
248
276
  // 1. Create filtered helper for the component body
249
- var filteredBox = computeFilteredBoundingBox(object, excludeTypes);
277
+ var filteredBox = computeFilteredBoundingBoxCached(object, excludeTypes);
250
278
  var componentHelper = _createBoxHelperFromBox3(filteredBox, color);
251
279
  componentHelper.isHelper = true;
252
280
  componentHelper.userData = {
@@ -256,33 +284,6 @@ function createSelectionBoxHelpers(object) {
256
284
  excludeTypes: excludeTypes
257
285
  };
258
286
  helpers.push(componentHelper);
259
-
260
- // 2. Create individual helpers for each io-device
261
- var _iterator2 = _rollupPluginBabelHelpers.createForOfIteratorHelper(object.children),
262
- _step2;
263
- try {
264
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
265
- var _child$userData3;
266
- var child = _step2.value;
267
- if (((_child$userData3 = child.userData) === null || _child$userData3 === void 0 ? void 0 : _child$userData3.objectType) !== 'io-device') continue;
268
- var deviceBox = new THREE__namespace.Box3().setFromObject(child);
269
- if (deviceBox.isEmpty()) continue;
270
- var deviceHelper = _createBoxHelperFromBox3(deviceBox, color);
271
- deviceHelper.isHelper = true;
272
- deviceHelper.userData = {
273
- isBoundingBox: true,
274
- sourceObjectUuid: child.uuid,
275
- isFiltered: false,
276
- isIODevice: true,
277
- parentComponentUuid: object.uuid
278
- };
279
- helpers.push(deviceHelper);
280
- }
281
- } catch (err) {
282
- _iterator2.e(err);
283
- } finally {
284
- _iterator2.f();
285
- }
286
287
  } else {
287
288
  // Standard BoxHelper for non-smart objects
288
289
  var boxHelper = new THREE__namespace.BoxHelper(object, color);
@@ -306,11 +307,11 @@ function createSelectionBoxHelpers(object) {
306
307
  * @param {THREE.Scene} scene - The scene (for finding objects by uuid)
307
308
  */
308
309
  function updateSelectionBoxHelpers(helpers, selectedObjects, scene) {
309
- var _iterator3 = _rollupPluginBabelHelpers.createForOfIteratorHelper(helpers),
310
- _step3;
310
+ var _iterator2 = _rollupPluginBabelHelpers.createForOfIteratorHelper(helpers),
311
+ _step2;
311
312
  try {
312
313
  var _loop = function _loop() {
313
- var helper = _step3.value;
314
+ var helper = _step2.value;
314
315
  var _helper$userData = helper.userData,
315
316
  sourceObjectUuid = _helper$userData.sourceObjectUuid,
316
317
  isFiltered = _helper$userData.isFiltered,
@@ -333,29 +334,26 @@ function updateSelectionBoxHelpers(helpers, selectedObjects, scene) {
333
334
  if (!sourceObject) return 1; // continue
334
335
  sourceObject.updateMatrixWorld(true);
335
336
  if (isFiltered && excludeTypes) {
336
- // Recompute filtered bbox
337
- var box = computeFilteredBoundingBox(sourceObject, excludeTypes);
337
+ // Recompute filtered bbox (uses cache when the object hasn't moved)
338
+ var box = computeFilteredBoundingBoxCached(sourceObject, excludeTypes);
338
339
  _updateBoxHelperPositions(helper, box);
339
- } else if (isIODevice) {
340
- // Recompute io-device bbox
341
- var _box = new THREE__namespace.Box3().setFromObject(sourceObject);
342
- _updateBoxHelperPositions(helper, _box);
343
340
  } else if (helper.update) {
344
341
  // Standard BoxHelper — use built-in update
345
342
  helper.update();
346
343
  }
347
344
  };
348
- for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
345
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
349
346
  if (_loop()) continue;
350
347
  }
351
348
  } catch (err) {
352
- _iterator3.e(err);
349
+ _iterator2.e(err);
353
350
  } finally {
354
- _iterator3.f();
351
+ _iterator2.f();
355
352
  }
356
353
  }
357
354
 
358
355
  exports.computeFilteredBoundingBox = computeFilteredBoundingBox;
356
+ exports.computeFilteredBoundingBoxCached = computeFilteredBoundingBoxCached;
359
357
  exports.computeIODeviceBoundingBoxes = computeIODeviceBoundingBoxes;
360
358
  exports.createSelectionBoxHelpers = createSelectionBoxHelpers;
361
359
  exports.updateSelectionBoxHelpers = updateSelectionBoxHelpers;
@@ -31,7 +31,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
31
31
  * Initialize the CentralPlant manager
32
32
  *
33
33
  * @constructor
34
- * @version 0.3.26
34
+ * @version 0.3.27
35
35
  * @updated 2025-10-22
36
36
  *
37
37
  * @description Creates a new CentralPlant instance and initializes internal managers and utilities.
@@ -24,8 +24,10 @@ import { SceneTooltipsManager } from '../managers/scene/sceneTooltipsManager.js'
24
24
  import { ComponentTooltipManager } from '../managers/scene/componentTooltipManager.js';
25
25
  import { Viewport2DManager } from '../managers/scene/viewport2DManager.js';
26
26
  import { IoAnimationManager } from '../managers/behaviors/IoAnimationManager.js';
27
+ import { IoOutlineManager } from '../managers/behaviors/IoOutlineManager.js';
27
28
  import { generateUuidFromName, getHardcodedUuid, findObjectByHardcodedUuid, generateUniqueComponentId } from '../utils/nameUtils.js';
28
29
  import { attachIODevicesToComponent } from '../utils/ioDeviceUtils.js';
30
+ import { computeFilteredBoundingBoxCached } from '../utils/boundingBoxUtils.js';
29
31
  import modelPreloader from '../rendering/modelPreloader.js';
30
32
 
31
33
  // ─────────────────────────────────────────────────────────────────────────────
@@ -150,6 +152,7 @@ var CentralPlantInternals = /*#__PURE__*/function () {
150
152
  this.centralPlant.managers.componentDragManager = new ComponentDragManager(this.centralPlant.sceneViewer);
151
153
  this.centralPlant.managers.viewport2DManager = new Viewport2DManager(this.centralPlant.sceneViewer);
152
154
  this.centralPlant.managers.ioAnimationManager = new IoAnimationManager(this.centralPlant.sceneViewer);
155
+ this.centralPlant.managers.ioOutlineManager = new IoOutlineManager(this.centralPlant.sceneViewer);
153
156
 
154
157
  // All managers are now stored in the managers collection and will be attached via attachToComponent()
155
158
  }
@@ -1166,6 +1169,20 @@ var CentralPlantInternals = /*#__PURE__*/function () {
1166
1169
  componentManager.registerComponent(componentModel);
1167
1170
  }
1168
1171
 
1172
+ // Pre-warm the filtered bounding-box cache for smart components so the
1173
+ // first selection is instant. Deferred to idle time so it does not
1174
+ // block the current frame.
1175
+ if (componentData.isSmart) {
1176
+ var warmFn = function warmFn() {
1177
+ return computeFilteredBoundingBoxCached(componentModel, ['io-device', 'connector']);
1178
+ };
1179
+ if (typeof requestIdleCallback !== 'undefined') {
1180
+ requestIdleCallback(warmFn);
1181
+ } else {
1182
+ setTimeout(warmFn, 0);
1183
+ }
1184
+ }
1185
+
1169
1186
  // EMIT COMPONENT ADDED EVENT
1170
1187
  // This allows UI components (like SceneHierarchy) to update reactively
1171
1188
  if (this.centralPlant.sceneViewer.emit) {
@@ -98,7 +98,7 @@ var sceneViewer = /*#__PURE__*/function (_BaseDisposable) {
98
98
  this.centralPlant.attachToComponent();
99
99
 
100
100
  // Sync our managers tracking object after attachment
101
- managerKeys = ['threeJSResourceManager', 'performanceMonitorManager', 'settingsManager', 'sceneExportManager', 'componentManager', 'sceneInitializationManager', 'environmentManager', 'keyboardControlsManager', 'pathfindingManager', 'pathFlowManager', 'behaviorManager', 'ioAnimationManager', 'sceneOperationsManager', 'animationManager', 'cameraControlsManager', 'componentDragManager', 'tooltipsManager', 'componentTooltipManager']; // Populate our managers tracking object
101
+ managerKeys = ['threeJSResourceManager', 'performanceMonitorManager', 'settingsManager', 'sceneExportManager', 'componentManager', 'sceneInitializationManager', 'environmentManager', 'keyboardControlsManager', 'pathfindingManager', 'pathFlowManager', 'behaviorManager', 'ioAnimationManager', 'ioOutlineManager', 'sceneOperationsManager', 'animationManager', 'cameraControlsManager', 'componentDragManager', 'tooltipsManager', 'componentTooltipManager']; // Populate our managers tracking object
102
102
  managerKeys.forEach(function (key) {
103
103
  if (_this2[key]) {
104
104
  _this2.managers[key] = _this2[key];
@@ -286,7 +286,21 @@ var sceneViewer = /*#__PURE__*/function (_BaseDisposable) {
286
286
  }
287
287
 
288
288
  // Update camera aspect ratio
289
- this.camera.aspect = width / height;
289
+ if (this.camera.isPerspectiveCamera) {
290
+ this.camera.aspect = width / height;
291
+ } else if (this.camera.isOrthographicCamera) {
292
+ var _this$camera$userData;
293
+ var aspect = width / height;
294
+ var extents = (_this$camera$userData = this.camera.userData) === null || _this$camera$userData === void 0 ? void 0 : _this$camera$userData._orthoHalfExtents;
295
+ if (extents) {
296
+ var frustumHalfH = Math.max(extents.halfH, extents.halfW / aspect);
297
+ var frustumHalfW = frustumHalfH * aspect;
298
+ this.camera.left = -frustumHalfW;
299
+ this.camera.right = frustumHalfW;
300
+ this.camera.top = frustumHalfH;
301
+ this.camera.bottom = -frustumHalfH;
302
+ }
303
+ }
290
304
  this.camera.updateProjectionMatrix();
291
305
 
292
306
  // Update renderer size (updateStyle=true to sync canvas CSS)
@@ -413,6 +427,45 @@ var sceneViewer = /*#__PURE__*/function (_BaseDisposable) {
413
427
  if (_this4.componentTooltipManager) {
414
428
  _this4.componentTooltipManager.toggleIODeviceBinaryState(ioDeviceObject);
415
429
  }
430
+ },
431
+ onIODeviceDrag: function onIODeviceDrag(ioDeviceObject, signedDelta, isStart) {
432
+ if (isStart) {
433
+ var _ioDeviceObject$userD, _this4$managers$ioAni, _this4$managers, _this4$managers2;
434
+ // Resolve parentUuid by walking up to the host component.
435
+ // Use userData.originalUuid (the custom componentId) because that
436
+ // is what IoAnimationManager uses as the map key — NOT obj.uuid.
437
+ var parentUuid = null;
438
+ var obj = ioDeviceObject.parent;
439
+ while (obj) {
440
+ var _obj$userData;
441
+ if (((_obj$userData = obj.userData) === null || _obj$userData === void 0 ? void 0 : _obj$userData.objectType) === 'component') {
442
+ parentUuid = obj.userData.originalUuid || obj.uuid;
443
+ break;
444
+ }
445
+ obj = obj.parent;
446
+ }
447
+ var attachmentId = (_ioDeviceObject$userD = ioDeviceObject.userData) === null || _ioDeviceObject$userD === void 0 ? void 0 : _ioDeviceObject$userD.attachmentId;
448
+ // When animated meshes are available, outline ONLY them so their
449
+ // silhouette is isolated and the outline ring is visible around
450
+ // them specifically (not swallowed by the larger device body).
451
+ // Fall back to the whole device group when none are registered.
452
+ var animatedMeshes = attachmentId && parentUuid ? (_this4$managers$ioAni = (_this4$managers = _this4.managers) === null || _this4$managers === void 0 || (_this4$managers = _this4$managers.ioAnimationManager) === null || _this4$managers === void 0 ? void 0 : _this4$managers.getAnimatedMeshes(parentUuid, attachmentId)) !== null && _this4$managers$ioAni !== void 0 ? _this4$managers$ioAni : [] : [];
453
+ var targets = animatedMeshes.length > 0 ? animatedMeshes : [ioDeviceObject];
454
+ (_this4$managers2 = _this4.managers) === null || _this4$managers2 === void 0 || (_this4$managers2 = _this4$managers2.ioOutlineManager) === null || _this4$managers2 === void 0 || _this4$managers2.setTargets(targets);
455
+ }
456
+ if (!_this4.componentTooltipManager) return;
457
+ if (isStart) {
458
+ _this4.componentTooltipManager.startIODeviceDrag(ioDeviceObject);
459
+ } else {
460
+ _this4.componentTooltipManager.updateIODeviceDrag(signedDelta);
461
+ }
462
+ },
463
+ onIODeviceDragEnd: function onIODeviceDragEnd(ioDeviceObject) {
464
+ var _this4$managers3;
465
+ (_this4$managers3 = _this4.managers) === null || _this4$managers3 === void 0 || (_this4$managers3 = _this4$managers3.ioOutlineManager) === null || _this4$managers3 === void 0 || _this4$managers3.setTargets([]);
466
+ if (_this4.componentTooltipManager) {
467
+ _this4.componentTooltipManager.endIODeviceDrag();
468
+ }
416
469
  }
417
470
  });
418
471