@2112-lab/central-plant 0.3.25 → 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.
@@ -1,4 +1,4 @@
1
- import { inherits as _inherits, createClass as _createClass, classCallCheck as _classCallCheck, callSuper as _callSuper } from '../../../_virtual/_rollupPluginBabelHelpers.js';
1
+ import { inherits as _inherits, createClass as _createClass, createForOfIteratorHelper as _createForOfIteratorHelper, classCallCheck as _classCallCheck, callSuper as _callSuper } from '../../../_virtual/_rollupPluginBabelHelpers.js';
2
2
  import * as THREE from 'three';
3
3
  import { BaseDisposable } from '../../core/baseDisposable.js';
4
4
 
@@ -144,6 +144,169 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
144
144
  console.log("\uD83D\uDD04 [IODevice] Toggled ".concat(scopedAttachmentId, ".").concat(dpId, ": ").concat(currentVal, " \u2192 ").concat(newVal));
145
145
  }
146
146
 
147
+ // ── IO device drag-to-state ─────────────────────────────────────────────
148
+
149
+ /**
150
+ * Begin tracking a drag gesture on an IO device mesh.
151
+ * Records the initial state of each animation data point so that
152
+ * `updateIODeviceDrag` can compute relative offsets from it.
153
+ *
154
+ * @param {THREE.Object3D} ioDeviceObject
155
+ */
156
+ }, {
157
+ key: "startIODeviceDrag",
158
+ value: function startIODeviceDrag(ioDeviceObject) {
159
+ var _this$sceneViewer3,
160
+ _this2 = this;
161
+ if (!ioDeviceObject || !this._stateAdapter) return;
162
+ var ud = ioDeviceObject.userData;
163
+ var attachmentId = ud === null || ud === void 0 ? void 0 : ud.attachmentId;
164
+ if (!attachmentId) return;
165
+ var parentUuid = null;
166
+ var obj = ioDeviceObject.parent;
167
+ while (obj) {
168
+ var _obj$userData2;
169
+ if (((_obj$userData2 = obj.userData) === null || _obj$userData2 === void 0 ? void 0 : _obj$userData2.objectType) === 'component') {
170
+ parentUuid = obj.uuid;
171
+ break;
172
+ }
173
+ obj = obj.parent;
174
+ }
175
+ var scopedAttachmentId = this._getScopedAttachmentKey(attachmentId, parentUuid);
176
+ 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;
177
+ var dataPoints = ((ioAnimMgr === null || ioAnimMgr === void 0 ? void 0 : ioAnimMgr.getAnimationDataPoints(parentUuid, attachmentId)) || []).concat((ud === null || ud === void 0 ? void 0 : ud.dataPoints) || [])
178
+ // deduplicate by id
179
+ .filter(function (dp, i, arr) {
180
+ return arr.findIndex(function (d) {
181
+ return d.id === dp.id;
182
+ }) === i;
183
+ });
184
+ var dpSessions = [];
185
+ var _iterator = _createForOfIteratorHelper(dataPoints),
186
+ _step;
187
+ try {
188
+ var _loop = function _loop() {
189
+ var _this2$_stateAdapter$;
190
+ var dp = _step.value;
191
+ var stateType = (dp.stateType || '').toLowerCase();
192
+ if (stateType !== 'binary' && stateType !== 'boolean' && stateType !== 'enum') return 1; // continue
193
+ var curVal = (_this2$_stateAdapter$ = _this2._stateAdapter.getState(scopedAttachmentId, dp.id)) !== null && _this2$_stateAdapter$ !== void 0 ? _this2$_stateAdapter$ : dp.defaultValue;
194
+ if (stateType === 'binary' || stateType === 'boolean') {
195
+ dpSessions.push({
196
+ dp: dp,
197
+ scopedAttachmentId: scopedAttachmentId,
198
+ attachmentId: attachmentId,
199
+ parentUuid: parentUuid,
200
+ stateType: 'binary',
201
+ lastApplied: curVal
202
+ });
203
+ } else {
204
+ var _dp$stateConfig;
205
+ var opts = ((_dp$stateConfig = dp.stateConfig) === null || _dp$stateConfig === void 0 ? void 0 : _dp$stateConfig.options) || [];
206
+ var curIdx = opts.findIndex(function (o) {
207
+ return String(o) === String(curVal);
208
+ });
209
+ dpSessions.push({
210
+ dp: dp,
211
+ scopedAttachmentId: scopedAttachmentId,
212
+ attachmentId: attachmentId,
213
+ parentUuid: parentUuid,
214
+ stateType: 'enum',
215
+ opts: opts,
216
+ startIdx: curIdx >= 0 ? curIdx : 0,
217
+ lastAppliedIdx: curIdx >= 0 ? curIdx : 0
218
+ });
219
+ }
220
+ };
221
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
222
+ if (_loop()) continue;
223
+ }
224
+ } catch (err) {
225
+ _iterator.e(err);
226
+ } finally {
227
+ _iterator.f();
228
+ }
229
+ this._ioDragSession = dpSessions.length ? {
230
+ dpSessions: dpSessions
231
+ } : null;
232
+ }
233
+
234
+ /**
235
+ * Update animated mesh state while a drag is in progress.
236
+ * Called continuously during pointermove.
237
+ *
238
+ * Sign convention: up/right = positive `signedDelta`.
239
+ * - Binary: > +20 px → true/on state, < −20 px → false/off state.
240
+ * - Enum: each ±30 px step advances/retreats one option in the list.
241
+ *
242
+ * @param {number} signedDelta - Cumulative signed pixel displacement since drag start
243
+ */
244
+ }, {
245
+ key: "updateIODeviceDrag",
246
+ value: function updateIODeviceDrag(signedDelta) {
247
+ var session = this._ioDragSession;
248
+ if (!session) return;
249
+ var BINARY_THRESHOLD = 20;
250
+ var ENUM_STEP_PX = 30;
251
+ var _iterator2 = _createForOfIteratorHelper(session.dpSessions),
252
+ _step2;
253
+ try {
254
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
255
+ var dps = _step2.value;
256
+ if (dps.stateType === 'binary') {
257
+ var newVal = void 0;
258
+ if (signedDelta > BINARY_THRESHOLD) {
259
+ newVal = true;
260
+ } else if (signedDelta < -BINARY_THRESHOLD) {
261
+ newVal = false;
262
+ } else {
263
+ continue; // dead zone
264
+ }
265
+ if (newVal === dps.lastApplied) continue;
266
+ dps.lastApplied = newVal;
267
+ this._applyDpState(dps, newVal);
268
+ } else if (dps.stateType === 'enum') {
269
+ var steps = Math.round(signedDelta / ENUM_STEP_PX);
270
+ var newIdx = Math.max(0, Math.min(dps.opts.length - 1, dps.startIdx + steps));
271
+ if (newIdx === dps.lastAppliedIdx) continue;
272
+ dps.lastAppliedIdx = newIdx;
273
+ this._applyDpState(dps, dps.opts[newIdx]);
274
+ }
275
+ }
276
+ } catch (err) {
277
+ _iterator2.e(err);
278
+ } finally {
279
+ _iterator2.f();
280
+ }
281
+ }
282
+
283
+ /**
284
+ * Clean up drag session state on pointerup.
285
+ */
286
+ }, {
287
+ key: "endIODeviceDrag",
288
+ value: function endIODeviceDrag() {
289
+ this._ioDragSession = null;
290
+ }
291
+
292
+ /**
293
+ * Apply a new value to a data point, updating Vuex state and firing behavior/animation triggers.
294
+ * @private
295
+ */
296
+ }, {
297
+ key: "_applyDpState",
298
+ value: function _applyDpState(_ref2, newVal) {
299
+ var _this$_stateAdapter, _this$sceneViewer4, _this$sceneViewer5;
300
+ var scopedAttachmentId = _ref2.scopedAttachmentId,
301
+ attachmentId = _ref2.attachmentId,
302
+ parentUuid = _ref2.parentUuid,
303
+ dp = _ref2.dp;
304
+ var dpId = dp.id;
305
+ (_this$_stateAdapter = this._stateAdapter) === null || _this$_stateAdapter === void 0 || _this$_stateAdapter.setState(scopedAttachmentId, dpId, newVal);
306
+ (_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);
307
+ (_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);
308
+ }
309
+
147
310
  /**
148
311
  * Should be called when an object is selected or deselected.
149
312
  * @param {THREE.Object3D|null} object
@@ -257,26 +420,30 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
257
420
  }, {
258
421
  key: "_getIODevices",
259
422
  value: function _getIODevices(object) {
260
- var _this2 = this;
423
+ var _this3 = this;
261
424
  var devices = [];
262
425
  var parentUuid = object.uuid; // The component's own UUID
263
426
  object.traverse(function (child) {
264
427
  var _child$userData;
265
428
  if (((_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.objectType) === 'io-device') {
266
- var _this2$sceneViewer;
429
+ var _this3$sceneViewer$ma, _this3$sceneViewer;
267
430
  var attachmentId = child.userData.attachmentId || '';
268
431
 
269
- // Prefer data point definitions from the animate window (richer, user-authored).
270
- // Fall back to the static ioConfig.states[] snapshot on the mesh userData.
271
- var animDps = (_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);
272
- var dataPoints = (animDps === null || animDps === void 0 ? void 0 : animDps.length) > 0 ? animDps : child.userData.dataPoints || [];
432
+ // Use only data points from the animate window (animationConfig).
433
+ // The static ioConfig.states[] snapshot on userData is intentionally ignored.
434
+ 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 : [];
435
+
436
+ // When data points come from animationConfig they already carry direction:'input'.
437
+ // Pass null so _buildDataPointRow uses the per-dp direction instead of the
438
+ // device-level ioDirection (which may be 'output' and would hide controls).
439
+ var deviceDirection = dataPoints.length > 0 ? null : child.userData.ioDirection || 'output';
273
440
  devices.push({
274
441
  label: child.userData.attachmentLabel || child.name || child.userData.deviceId || 'Unknown Device',
275
442
  deviceId: child.userData.deviceId || '',
276
443
  attachmentId: attachmentId,
277
- scopedAttachmentId: _this2._getScopedAttachmentKey(attachmentId, parentUuid),
444
+ scopedAttachmentId: _this3._getScopedAttachmentKey(attachmentId, parentUuid),
278
445
  dataPoints: dataPoints,
279
- direction: child.userData.ioDirection || 'output'
446
+ direction: deviceDirection
280
447
  });
281
448
  }
282
449
  });
@@ -290,7 +457,7 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
290
457
  }, {
291
458
  key: "_buildTooltip",
292
459
  value: function _buildTooltip(object) {
293
- var _this3 = this;
460
+ var _this4 = this;
294
461
  // Remove any existing tooltip first
295
462
  this.hide();
296
463
  // Re-assign selected object since hide() clears it
@@ -361,7 +528,7 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
361
528
  // Use scopedAttachmentId to ensure state is isolated per component instance
362
529
  if (device.scopedAttachmentId && device.dataPoints.length > 0) {
363
530
  device.dataPoints.forEach(function (dp) {
364
- var row = _this3._buildDataPointRow(device.scopedAttachmentId, dp, device.direction, device.attachmentId);
531
+ var row = _this4._buildDataPointRow(device.scopedAttachmentId, dp, device.direction, device.attachmentId);
365
532
  list.appendChild(row);
366
533
  });
367
534
  }
@@ -372,11 +539,11 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
372
539
  // Hover expand/collapse
373
540
  trigger.addEventListener('mouseenter', function () {
374
541
  ioSection.classList.add('expanded');
375
- _this3._ioExpanded = true;
542
+ _this4._ioExpanded = true;
376
543
  });
377
544
  ioSection.addEventListener('mouseleave', function () {
378
545
  ioSection.classList.remove('expanded');
379
- _this3._ioExpanded = false;
546
+ _this4._ioExpanded = false;
380
547
  });
381
548
  card.appendChild(ioSection);
382
549
  } else {
@@ -420,11 +587,11 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
420
587
  }, {
421
588
  key: "_positionTooltip",
422
589
  value: function _positionTooltip() {
423
- var _this$sceneViewer3, _this$sceneViewer4;
590
+ var _this$sceneViewer6, _this$sceneViewer7;
424
591
  if (!this.tooltipEl || !this.selectedObject) return;
425
592
  var container = this._getContainer();
426
- var camera = (_this$sceneViewer3 = this.sceneViewer) === null || _this$sceneViewer3 === void 0 ? void 0 : _this$sceneViewer3.camera;
427
- var renderer = (_this$sceneViewer4 = this.sceneViewer) === null || _this$sceneViewer4 === void 0 ? void 0 : _this$sceneViewer4.renderer;
593
+ var camera = (_this$sceneViewer6 = this.sceneViewer) === null || _this$sceneViewer6 === void 0 ? void 0 : _this$sceneViewer6.camera;
594
+ var renderer = (_this$sceneViewer7 = this.sceneViewer) === null || _this$sceneViewer7 === void 0 ? void 0 : _this$sceneViewer7.renderer;
428
595
  if (!container || !camera || !renderer) return;
429
596
 
430
597
  // Compute bounding box to position above the component
@@ -461,8 +628,8 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
461
628
  }, {
462
629
  key: "_getContainer",
463
630
  value: function _getContainer() {
464
- var _this$sceneViewer5;
465
- 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;
631
+ var _this$sceneViewer8;
632
+ 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;
466
633
  }
467
634
 
468
635
  // -----------------------------------------------------------------------
@@ -484,10 +651,10 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
484
651
  }, {
485
652
  key: "_buildDataPointRow",
486
653
  value: function _buildDataPointRow(scopedAttachmentId, dp, deviceDirection, originalAttachmentId) {
487
- var _ref2,
654
+ var _ref3,
488
655
  _this$_stateAdapter$g,
489
- _this$_stateAdapter,
490
- _this4 = this;
656
+ _this$_stateAdapter2,
657
+ _this5 = this;
491
658
  var row = document.createElement('div');
492
659
  row.className = 'cp-tooltip__dp-row';
493
660
  var nameEl = document.createElement('span');
@@ -499,18 +666,18 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
499
666
  // Device-level direction takes precedence; fall back to per-dp direction
500
667
  var resolvedDirection = deviceDirection || dp.direction || 'output';
501
668
  var isInput = resolvedDirection === 'input' || resolvedDirection === 'bidirectional';
502
- 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;
669
+ 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;
503
670
  if (isInput) {
504
671
  var ctrl = this._buildInputControl(dp, currentVal, function (newVal) {
505
- var _this4$_stateAdapter, _this4$selectedObject, _this4$sceneViewer, _this4$sceneViewer2;
506
- (_this4$_stateAdapter = _this4._stateAdapter) === null || _this4$_stateAdapter === void 0 || _this4$_stateAdapter.setState(scopedAttachmentId, dpId, newVal);
672
+ var _this5$_stateAdapter, _this5$selectedObject, _this5$sceneViewer, _this5$sceneViewer2;
673
+ (_this5$_stateAdapter = _this5._stateAdapter) === null || _this5$_stateAdapter === void 0 || _this5$_stateAdapter.setState(scopedAttachmentId, dpId, newVal);
507
674
  // Also fire BehaviorManager so any wired behaviors react immediately.
508
675
  // Pass the parent component UUID so behaviors scoped to a specific instance
509
676
  // don't bleed across clones that share the same attachmentId.
510
677
  // Use originalAttachmentId for behavior triggering as behaviors are keyed by original ID
511
- var parentUuid = ((_this4$selectedObject = _this4.selectedObject) === null || _this4$selectedObject === void 0 ? void 0 : _this4$selectedObject.uuid) || null;
512
- (_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);
513
- (_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);
678
+ var parentUuid = ((_this5$selectedObject = _this5.selectedObject) === null || _this5$selectedObject === void 0 ? void 0 : _this5$selectedObject.uuid) || null;
679
+ (_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);
680
+ (_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);
514
681
  });
515
682
  row.appendChild(ctrl);
516
683
  this._stateElements.set(key, {
@@ -519,9 +686,9 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
519
686
  isInput: true
520
687
  });
521
688
  } else {
522
- var _dp$stateConfig;
689
+ var _dp$stateConfig2;
523
690
  // unit suffix (optional, shown between name and badge)
524
- var unit = (_dp$stateConfig = dp.stateConfig) === null || _dp$stateConfig === void 0 ? void 0 : _dp$stateConfig.unit;
691
+ var unit = (_dp$stateConfig2 = dp.stateConfig) === null || _dp$stateConfig2 === void 0 ? void 0 : _dp$stateConfig2.unit;
525
692
  if (unit) {
526
693
  var unitEl = document.createElement('span');
527
694
  unitEl.className = 'cp-tooltip__dp-unit';
@@ -666,7 +833,7 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
666
833
  }, {
667
834
  key: "_refreshStateDisplays",
668
835
  value: function _refreshStateDisplays() {
669
- var _this5 = this;
836
+ var _this6 = this;
670
837
  if (!this._stateAdapter || !this._stateElements.size) return;
671
838
  this._stateElements.forEach(function (entry, key) {
672
839
  if (entry.isInput) return; // interactive controls are user-driven; don't overwrite
@@ -675,8 +842,8 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
675
842
  if (sepIdx === -1) return;
676
843
  var scopedAttachmentId = key.slice(0, sepIdx);
677
844
  var dataPointId = key.slice(sepIdx + 2);
678
- var val = _this5._stateAdapter.getState(scopedAttachmentId, dataPointId);
679
- _this5._applyBadgeValue(entry.el, val, entry.dp);
845
+ var val = _this6._stateAdapter.getState(scopedAttachmentId, dataPointId);
846
+ _this6._applyBadgeValue(entry.el, val, entry.dp);
680
847
  });
681
848
  }
682
849
  }]);
@@ -2,7 +2,7 @@ import { createClass as _createClass, objectSpread2 as _objectSpread2, toConsuma
2
2
  import * as THREE from 'three';
3
3
  import { attachIODevicesToComponent } from '../../utils/ioDeviceUtils.js';
4
4
  import modelPreloader from '../../rendering/modelPreloader.js';
5
- import { computeFilteredBoundingBox } from '../../utils/boundingBoxUtils.js';
5
+ import { computeFilteredBoundingBox, computeFilteredBoundingBoxCached } from '../../utils/boundingBoxUtils.js';
6
6
  import { cacheBasePosition } from './viewport2DManager.js';
7
7
 
8
8
  var ModelManager = /*#__PURE__*/function () {
@@ -53,7 +53,7 @@ var ModelManager = /*#__PURE__*/function () {
53
53
  key: "loadLibraryModel",
54
54
  value: function () {
55
55
  var _loadLibraryModel = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(targetMesh, jsonEntry, componentData) {
56
- var component, _jsonEntry$userData, _jsonEntry$userData2, _jsonEntry$userData3, originalProps, connectorChildren, gltfScene, libraryModel, _this$sceneViewer, ioAnimMgr, _loop, _i, _Object$entries, _jsonEntry$userData4, _t;
56
+ var component, _jsonEntry$userData, _jsonEntry$userData2, _jsonEntry$userData3, originalProps, connectorChildren, gltfScene, libraryModel, _this$sceneViewer, ioAnimMgr, _loop, _i, _Object$entries, warmFn, _jsonEntry$userData4, _t;
57
57
  return _regenerator().w(function (_context2) {
58
58
  while (1) switch (_context2.n) {
59
59
  case 0:
@@ -103,13 +103,14 @@ var ModelManager = /*#__PURE__*/function () {
103
103
  break;
104
104
  }
105
105
  _loop = /*#__PURE__*/_regenerator().m(function _loop() {
106
- var _modelPreloader$compo;
106
+ var _modelPreloader$compo, _deviceData$animation;
107
107
  var _Object$entries$_i, attachmentId, attachment, deviceData, deviceRoot;
108
108
  return _regenerator().w(function (_context) {
109
109
  while (1) switch (_context.n) {
110
110
  case 0:
111
111
  _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2), attachmentId = _Object$entries$_i[0], attachment = _Object$entries$_i[1];
112
112
  deviceData = (_modelPreloader$compo = modelPreloader.componentDictionary) === null || _modelPreloader$compo === void 0 ? void 0 : _modelPreloader$compo[attachment.deviceId];
113
+ console.log("[ModelManager] attachment \"".concat(attachmentId, "\" \u2192 deviceId \"").concat(attachment.deviceId, "\" \u2192 animationConfig:"), (_deviceData$animation = deviceData === null || deviceData === void 0 ? void 0 : deviceData.animationConfig) !== null && _deviceData$animation !== void 0 ? _deviceData$animation : 'NONE');
113
114
  if (deviceData !== null && deviceData !== void 0 && deviceData.animationConfig) {
114
115
  _context.n = 1;
115
116
  break;
@@ -149,6 +150,20 @@ var ModelManager = /*#__PURE__*/function () {
149
150
  case 8:
150
151
  // Replace mesh in scene
151
152
  this._replaceMeshInScene(targetMesh, libraryModel, originalProps.parent, component);
153
+
154
+ // Pre-warm the filtered bounding-box cache for smart components so the
155
+ // first selection is instant. Deferred to idle time so it does not
156
+ // block the current frame.
157
+ if (componentData.isSmart) {
158
+ warmFn = function warmFn() {
159
+ return computeFilteredBoundingBoxCached(libraryModel, ['io-device', 'connector']);
160
+ };
161
+ if (typeof requestIdleCallback !== 'undefined') {
162
+ requestIdleCallback(warmFn);
163
+ } else {
164
+ setTimeout(warmFn, 0);
165
+ }
166
+ }
152
167
  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"));
153
168
  return _context2.a(2, libraryModel);
154
169
  case 9:
@@ -1,4 +1,4 @@
1
- import { createForOfIteratorHelper as _createForOfIteratorHelper } from '../../_virtual/_rollupPluginBabelHelpers.js';
1
+ import { createForOfIteratorHelper as _createForOfIteratorHelper, construct as _construct, toConsumableArray as _toConsumableArray } from '../../_virtual/_rollupPluginBabelHelpers.js';
2
2
  import * as THREE from 'three';
3
3
 
4
4
  /**
@@ -209,6 +209,34 @@ function computeIODeviceBoundingBoxes(componentObject) {
209
209
  * const helpers = createSelectionBoxHelpers(pumpModel, 0x00ff00)
210
210
  * helpers.forEach(h => scene.add(h))
211
211
  */
212
+ /**
213
+ * Returns a filtered bounding box for `object`, using a cache stored on
214
+ * `object.userData._filteredBBoxCache`. The cache key is the serialised
215
+ * world-matrix elements string; if the object has moved the cache is
216
+ * automatically invalidated and recomputed.
217
+ *
218
+ * This avoids the expensive full-traverse on every selection event for
219
+ * large smart components with many child meshes.
220
+ *
221
+ * @param {THREE.Object3D} object
222
+ * @param {string[]} excludeTypes
223
+ * @returns {THREE.Box3}
224
+ */
225
+ function computeFilteredBoundingBoxCached(object, excludeTypes) {
226
+ object.updateMatrixWorld(true);
227
+ var matrixKey = object.matrixWorld.elements.join(',');
228
+ var cache = object.userData._filteredBBoxCache;
229
+ if (cache && cache.matrixKey === matrixKey) {
230
+ return new THREE.Box3(_construct(THREE.Vector3, _toConsumableArray(cache.min)), _construct(THREE.Vector3, _toConsumableArray(cache.max)));
231
+ }
232
+ var box = computeFilteredBoundingBox(object, excludeTypes);
233
+ object.userData._filteredBBoxCache = {
234
+ matrixKey: matrixKey,
235
+ min: box.min.toArray(),
236
+ max: box.max.toArray()
237
+ };
238
+ return box;
239
+ }
212
240
  function createSelectionBoxHelpers(object) {
213
241
  var _object$children;
214
242
  var color = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0x00ff00;
@@ -222,7 +250,7 @@ function createSelectionBoxHelpers(object) {
222
250
  });
223
251
  if (hasIODevices) {
224
252
  // 1. Create filtered helper for the component body
225
- var filteredBox = computeFilteredBoundingBox(object, excludeTypes);
253
+ var filteredBox = computeFilteredBoundingBoxCached(object, excludeTypes);
226
254
  var componentHelper = _createBoxHelperFromBox3(filteredBox, color);
227
255
  componentHelper.isHelper = true;
228
256
  componentHelper.userData = {
@@ -232,33 +260,6 @@ function createSelectionBoxHelpers(object) {
232
260
  excludeTypes: excludeTypes
233
261
  };
234
262
  helpers.push(componentHelper);
235
-
236
- // 2. Create individual helpers for each io-device
237
- var _iterator2 = _createForOfIteratorHelper(object.children),
238
- _step2;
239
- try {
240
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
241
- var _child$userData3;
242
- var child = _step2.value;
243
- if (((_child$userData3 = child.userData) === null || _child$userData3 === void 0 ? void 0 : _child$userData3.objectType) !== 'io-device') continue;
244
- var deviceBox = new THREE.Box3().setFromObject(child);
245
- if (deviceBox.isEmpty()) continue;
246
- var deviceHelper = _createBoxHelperFromBox3(deviceBox, color);
247
- deviceHelper.isHelper = true;
248
- deviceHelper.userData = {
249
- isBoundingBox: true,
250
- sourceObjectUuid: child.uuid,
251
- isFiltered: false,
252
- isIODevice: true,
253
- parentComponentUuid: object.uuid
254
- };
255
- helpers.push(deviceHelper);
256
- }
257
- } catch (err) {
258
- _iterator2.e(err);
259
- } finally {
260
- _iterator2.f();
261
- }
262
263
  } else {
263
264
  // Standard BoxHelper for non-smart objects
264
265
  var boxHelper = new THREE.BoxHelper(object, color);
@@ -282,11 +283,11 @@ function createSelectionBoxHelpers(object) {
282
283
  * @param {THREE.Scene} scene - The scene (for finding objects by uuid)
283
284
  */
284
285
  function updateSelectionBoxHelpers(helpers, selectedObjects, scene) {
285
- var _iterator3 = _createForOfIteratorHelper(helpers),
286
- _step3;
286
+ var _iterator2 = _createForOfIteratorHelper(helpers),
287
+ _step2;
287
288
  try {
288
289
  var _loop = function _loop() {
289
- var helper = _step3.value;
290
+ var helper = _step2.value;
290
291
  var _helper$userData = helper.userData,
291
292
  sourceObjectUuid = _helper$userData.sourceObjectUuid,
292
293
  isFiltered = _helper$userData.isFiltered,
@@ -309,26 +310,22 @@ function updateSelectionBoxHelpers(helpers, selectedObjects, scene) {
309
310
  if (!sourceObject) return 1; // continue
310
311
  sourceObject.updateMatrixWorld(true);
311
312
  if (isFiltered && excludeTypes) {
312
- // Recompute filtered bbox
313
- var box = computeFilteredBoundingBox(sourceObject, excludeTypes);
313
+ // Recompute filtered bbox (uses cache when the object hasn't moved)
314
+ var box = computeFilteredBoundingBoxCached(sourceObject, excludeTypes);
314
315
  _updateBoxHelperPositions(helper, box);
315
- } else if (isIODevice) {
316
- // Recompute io-device bbox
317
- var _box = new THREE.Box3().setFromObject(sourceObject);
318
- _updateBoxHelperPositions(helper, _box);
319
316
  } else if (helper.update) {
320
317
  // Standard BoxHelper — use built-in update
321
318
  helper.update();
322
319
  }
323
320
  };
324
- for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
321
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
325
322
  if (_loop()) continue;
326
323
  }
327
324
  } catch (err) {
328
- _iterator3.e(err);
325
+ _iterator2.e(err);
329
326
  } finally {
330
- _iterator3.f();
327
+ _iterator2.f();
331
328
  }
332
329
  }
333
330
 
334
- export { computeFilteredBoundingBox, computeIODeviceBoundingBoxes, createSelectionBoxHelpers, updateSelectionBoxHelpers };
331
+ export { computeFilteredBoundingBox, computeFilteredBoundingBoxCached, computeIODeviceBoundingBoxes, createSelectionBoxHelpers, updateSelectionBoxHelpers };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@2112-lab/central-plant",
3
- "version": "0.3.25",
3
+ "version": "0.3.27",
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",