@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.
@@ -94,8 +94,17 @@ var TransformControlsManager = /*#__PURE__*/function () {
94
94
  onModeChange: null,
95
95
  onObjectRemoved: null,
96
96
  onSelectionChanged: null,
97
- onIODeviceClick: null
97
+ onIODeviceClick: null,
98
+ onIODeviceDrag: null,
99
+ onIODeviceDragEnd: null
98
100
  };
101
+
102
+ // IO device drag tracking state
103
+ this._ioDragMesh = null;
104
+ this._ioDragStartX = 0;
105
+ this._ioDragStartY = 0;
106
+ this._ioDragMoved = false;
107
+ this._suppressNextClick = false;
99
108
  this.init();
100
109
  }
101
110
 
@@ -175,6 +184,12 @@ var TransformControlsManager = /*#__PURE__*/function () {
175
184
  if (callbacks.onIODeviceClick) {
176
185
  this.callbacks.onIODeviceClick = callbacks.onIODeviceClick;
177
186
  }
187
+ if (callbacks.onIODeviceDrag) {
188
+ this.callbacks.onIODeviceDrag = callbacks.onIODeviceDrag;
189
+ }
190
+ if (callbacks.onIODeviceDragEnd) {
191
+ this.callbacks.onIODeviceDragEnd = callbacks.onIODeviceDragEnd;
192
+ }
178
193
  console.log('πŸ”— Transform controls callbacks registered');
179
194
  }
180
195
  /**
@@ -381,6 +396,11 @@ var TransformControlsManager = /*#__PURE__*/function () {
381
396
  value: function setupKeyboardControls() {
382
397
  var _this3 = this;
383
398
  this.eventHandlers.keydown = function (event) {
399
+ // Restore perspective camera when pressing Escape from ortho face view
400
+ if (event.code === 'Escape') {
401
+ _this3._restorePerspectiveCamera();
402
+ }
403
+
384
404
  // Only handle keys when transform controls are active
385
405
  if (!_this3.transformControls.enabled || _this3.transformState.isTransforming) {
386
406
  return;
@@ -415,9 +435,82 @@ var TransformControlsManager = /*#__PURE__*/function () {
415
435
  var raycaster = new THREE.Raycaster();
416
436
  var mouse = new THREE.Vector2();
417
437
 
418
- // Click handler: first click selects (bounding box), second click on same object shows tooltip
438
+ // ── IO device drag ────────────────────────────────────────────────────
439
+ // Detect pointerdown on an IO device mesh and convert a drag gesture into
440
+ // state changes. Up/right = positive direction, down/left = negative.
441
+ this.eventHandlers.pointerdown = function (event) {
442
+ if (_this4.transformState.isTransforming) return;
443
+ if (!_this4.callbacks.onIODeviceDrag) return;
444
+ _this4._calculateMousePosition(event, mouse);
445
+ raycaster.setFromCamera(mouse, _this4.camera);
446
+ var allIntersects = raycaster.intersectObjects(_this4.scene.children, true);
447
+ var ioDeviceObject = null;
448
+ var _iterator = _createForOfIteratorHelper(allIntersects),
449
+ _step;
450
+ try {
451
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
452
+ var hit = _step.value;
453
+ var obj = hit.object;
454
+ while (obj) {
455
+ var _obj$userData;
456
+ if (((_obj$userData = obj.userData) === null || _obj$userData === void 0 ? void 0 : _obj$userData.objectType) === 'io-device') {
457
+ ioDeviceObject = obj;
458
+ break;
459
+ }
460
+ obj = obj.parent;
461
+ }
462
+ if (ioDeviceObject) break;
463
+ }
464
+ } catch (err) {
465
+ _iterator.e(err);
466
+ } finally {
467
+ _iterator.f();
468
+ }
469
+ if (!ioDeviceObject) return;
470
+
471
+ // Begin session
472
+ _this4._ioDragMesh = ioDeviceObject;
473
+ _this4._ioDragStartX = event.clientX;
474
+ _this4._ioDragStartY = event.clientY;
475
+ _this4._ioDragMoved = false;
476
+ if (_this4.orbitControls) _this4.orbitControls.enabled = false;
477
+ _this4.callbacks.onIODeviceDrag(ioDeviceObject, 0, true);
478
+ var onMove = function onMove(e) {
479
+ var dx = e.clientX - _this4._ioDragStartX;
480
+ var dy = e.clientY - _this4._ioDragStartY;
481
+ if (Math.abs(dx) > 4 || Math.abs(dy) > 4) _this4._ioDragMoved = true;
482
+ // Up (βˆ’screenY) and right (+screenX) are positive
483
+ var signedDelta = _this4._ioDragStartY - e.clientY + (e.clientX - _this4._ioDragStartX);
484
+ _this4.callbacks.onIODeviceDrag(_this4._ioDragMesh, signedDelta, false);
485
+ };
486
+ var _onUp = function onUp() {
487
+ window.removeEventListener('pointermove', onMove);
488
+ window.removeEventListener('pointerup', _onUp);
489
+ if (_this4.orbitControls) _this4.orbitControls.enabled = true;
490
+ if (_this4._ioDragMoved) {
491
+ // Suppress the click event that will fire after this pointerup
492
+ _this4._suppressNextClick = true;
493
+ }
494
+ if (_this4.callbacks.onIODeviceDragEnd) {
495
+ _this4.callbacks.onIODeviceDragEnd(_this4._ioDragMesh);
496
+ }
497
+ _this4._ioDragMesh = null;
498
+ };
499
+ window.addEventListener('pointermove', onMove);
500
+ window.addEventListener('pointerup', _onUp);
501
+ };
502
+ this.renderer.domElement.addEventListener('pointerdown', this.eventHandlers.pointerdown);
503
+
504
+ // Click handler: left-click selects the object (bounding box + transform controls).
505
+ // Right-click on a component shows the tooltip.
419
506
  this.eventHandlers.click = function (event) {
420
507
  var _targetObject$userDat;
508
+ // Suppress click that follows an IO device drag
509
+ if (_this4._suppressNextClick) {
510
+ _this4._suppressNextClick = false;
511
+ return;
512
+ }
513
+
421
514
  // Skip if currently transforming
422
515
  if (_this4.transformState.isTransforming) {
423
516
  return;
@@ -430,15 +523,15 @@ var TransformControlsManager = /*#__PURE__*/function () {
430
523
  // Check for direct io-device mesh click (before bounding box selection)
431
524
  if (_this4.callbacks.onIODeviceClick) {
432
525
  var allIntersects = raycaster.intersectObjects(_this4.scene.children, true);
433
- var _iterator = _createForOfIteratorHelper(allIntersects),
434
- _step;
526
+ var _iterator2 = _createForOfIteratorHelper(allIntersects),
527
+ _step2;
435
528
  try {
436
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
437
- var hit = _step.value;
529
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
530
+ var hit = _step2.value;
438
531
  var obj = hit.object;
439
532
  while (obj) {
440
- var _obj$userData;
441
- if (((_obj$userData = obj.userData) === null || _obj$userData === void 0 ? void 0 : _obj$userData.objectType) === 'io-device') {
533
+ var _obj$userData2;
534
+ if (((_obj$userData2 = obj.userData) === null || _obj$userData2 === void 0 ? void 0 : _obj$userData2.objectType) === 'io-device') {
442
535
  _this4.callbacks.onIODeviceClick(obj);
443
536
  return;
444
537
  }
@@ -446,9 +539,9 @@ var TransformControlsManager = /*#__PURE__*/function () {
446
539
  }
447
540
  }
448
541
  } catch (err) {
449
- _iterator.e(err);
542
+ _iterator2.e(err);
450
543
  } finally {
451
- _iterator.f();
544
+ _iterator2.f();
452
545
  }
453
546
  }
454
547
 
@@ -483,23 +576,194 @@ var TransformControlsManager = /*#__PURE__*/function () {
483
576
  return;
484
577
  }
485
578
 
486
- // Check if the clicked object is already selected
487
- var isAlreadySelected = _this4.selectedObjects.length === 1 && _this4.selectedObjects[0] === targetObject;
488
- if (isAlreadySelected) {
489
- // Second click on already-selected object: Full selection including tooltips
490
- _this4.selectObject(targetObject);
491
- } else {
492
- // First click on a new object: Transform controls and bounding box only
493
- // Always deselect first (this clears tooltips via the callback)
579
+ // Left-click: select for transform only (bounding box, no tooltip)
580
+ if (!_this4.selectedObjects.includes(targetObject)) {
494
581
  _this4.deselectObject();
495
-
496
- // Then select the new object if there is one (this shows transform controls and bounding box)
497
- if (targetObject) {
498
- _this4.selectObjectForTransformOnly(targetObject);
499
- }
500
582
  }
583
+ _this4.selectObjectForTransformOnly(targetObject);
501
584
  };
502
585
  this.renderer.domElement.addEventListener('click', this.eventHandlers.click);
586
+
587
+ // Right-click handler: show tooltip for the clicked component.
588
+ this.eventHandlers.contextmenu = function (event) {
589
+ var _targetObject$userDat2;
590
+ event.preventDefault();
591
+ if (_this4.transformState.isTransforming) return;
592
+ _this4._calculateMousePosition(event, mouse);
593
+ raycaster.setFromCamera(mouse, _this4.camera);
594
+ var targetObject = _this4._findTargetObject(raycaster, objectFilter);
595
+ if (!targetObject) return;
596
+ var objectType = (_targetObject$userDat2 = targetObject.userData) === null || _targetObject$userDat2 === void 0 ? void 0 : _targetObject$userDat2.objectType;
597
+ if (objectType !== 'component' && objectType !== 'gateway' && objectType !== 'segment') return;
598
+
599
+ // Ensure the object is selected, then show the full tooltip.
600
+ if (!_this4.selectedObjects.includes(targetObject)) {
601
+ _this4.deselectObject();
602
+ _this4.selectObjectForTransformOnly(targetObject);
603
+ }
604
+ _this4.selectObject(targetObject);
605
+ };
606
+ this.renderer.domElement.addEventListener('contextmenu', this.eventHandlers.contextmenu);
607
+
608
+ // Double-click handler: switch to orthographic camera fixed to the clicked face.
609
+ // Press Escape to restore the perspective camera.
610
+ this.eventHandlers.dblclick = function (event) {
611
+ if (_this4.transformState.isTransforming) return;
612
+ var sv = _this4.sceneViewer;
613
+ if (!sv) return;
614
+ _this4._calculateMousePosition(event, mouse);
615
+ raycaster.setFromCamera(_this4.camera, _this4.camera); // ensure raycaster uses current camera
616
+ raycaster.setFromCamera(mouse, _this4.camera);
617
+
618
+ // Raycast directly against scene meshes to obtain the face normal
619
+ var allHits = raycaster.intersectObjects(_this4.scene.children, true).filter(function (h) {
620
+ var _h$object$userData, _h$object$userData2;
621
+ return !((_h$object$userData = h.object.userData) !== null && _h$object$userData !== void 0 && _h$object$userData.isTransformControls) && !((_h$object$userData2 = h.object.userData) !== null && _h$object$userData2 !== void 0 && _h$object$userData2.isBoundingBox);
622
+ });
623
+ if (!allHits.length || !allHits[0].face) return;
624
+ var hit = allHits[0];
625
+
626
+ // Walk up to find the component root
627
+ var componentObj = hit.object;
628
+ while (componentObj) {
629
+ var _componentObj$userDat;
630
+ if (((_componentObj$userDat = componentObj.userData) === null || _componentObj$userDat === void 0 ? void 0 : _componentObj$userDat.objectType) === 'component') break;
631
+ componentObj = componentObj.parent;
632
+ }
633
+ if (!componentObj) return;
634
+
635
+ // Transform face normal from local object space to world space
636
+ var normalMatrix = new THREE.Matrix3().getNormalMatrix(hit.object.matrixWorld);
637
+ var worldNormal = hit.face.normal.clone().applyMatrix3(normalMatrix).normalize();
638
+
639
+ // Snap to the closest cardinal axis (Z-up: Β±X, Β±Y, Β±Z)
640
+ var cardinals = [new THREE.Vector3(1, 0, 0), new THREE.Vector3(-1, 0, 0), new THREE.Vector3(0, 1, 0), new THREE.Vector3(0, -1, 0), new THREE.Vector3(0, 0, 1), new THREE.Vector3(0, 0, -1)];
641
+ var faceDir = cardinals.reduce(function (best, c) {
642
+ return c.dot(worldNormal) > best.dot(worldNormal) ? c : best;
643
+ });
644
+
645
+ // Bounding box of the component
646
+ var bbox = new THREE.Box3().setFromObject(componentObj);
647
+ var center = bbox.getCenter(new THREE.Vector3());
648
+ var size = bbox.getSize(new THREE.Vector3());
649
+
650
+ // Ortho half-extents: axes perpendicular to the face direction
651
+ var isXFace = Math.abs(faceDir.x) > 0.5;
652
+ var isYFace = Math.abs(faceDir.y) > 0.5;
653
+ var halfW, halfH;
654
+ if (isXFace) {
655
+ halfW = size.y / 2;
656
+ halfH = size.z / 2;
657
+ } else if (isYFace) {
658
+ halfW = size.x / 2;
659
+ halfH = size.z / 2;
660
+ } else {
661
+ halfW = size.x / 2;
662
+ halfH = size.y / 2;
663
+ }
664
+ var padding = 1.6;
665
+ var domEl = sv.renderer.domElement;
666
+ var aspect = domEl.clientWidth / (domEl.clientHeight || 1);
667
+
668
+ // Frustum must satisfy both:
669
+ // frustumHalfW >= halfW * padding (object fits horizontally)
670
+ // frustumHalfH >= halfH * padding (object fits vertically)
671
+ // frustumHalfW / frustumHalfH == aspect (no squishing)
672
+ var minHalfH = halfH * padding;
673
+ var minHalfW = halfW * padding;
674
+ var frustumHalfH = Math.max(minHalfH, minHalfW / aspect);
675
+ var frustumHalfW = frustumHalfH * aspect;
676
+ var isZFace = Math.abs(faceDir.z) > 0.5;
677
+ var upVec = isZFace ? new THREE.Vector3(0, 1, 0) : new THREE.Vector3(0, 0, 1);
678
+
679
+ // Store the perspective camera snapshot on first entry
680
+ if (!sv._perspCameraSnapshot) {
681
+ var _sv$controls$target$c, _sv$controls, _sv$controls2, _sv$controls3, _sv$controls4;
682
+ sv._perspCameraSnapshot = {
683
+ camera: sv.camera,
684
+ controlsTarget: (_sv$controls$target$c = (_sv$controls = sv.controls) === null || _sv$controls === void 0 || (_sv$controls = _sv$controls.target) === null || _sv$controls === void 0 ? void 0 : _sv$controls.clone()) !== null && _sv$controls$target$c !== void 0 ? _sv$controls$target$c : new THREE.Vector3(),
685
+ controlsMinDist: (_sv$controls2 = sv.controls) === null || _sv$controls2 === void 0 ? void 0 : _sv$controls2.minDistance,
686
+ controlsMaxDist: (_sv$controls3 = sv.controls) === null || _sv$controls3 === void 0 ? void 0 : _sv$controls3.maxDistance,
687
+ controlsMaxPolar: (_sv$controls4 = sv.controls) === null || _sv$controls4 === void 0 ? void 0 : _sv$controls4.maxPolarAngle
688
+ };
689
+ }
690
+
691
+ // Build the orthographic camera
692
+ var ortho = new THREE.OrthographicCamera(-frustumHalfW, frustumHalfW, frustumHalfH, -frustumHalfH, 0.01, 1000);
693
+ ortho.up.copy(upVec);
694
+ var dist = Math.max(size.x, size.y, size.z) * 3;
695
+ ortho.position.copy(center).addScaledVector(faceDir, dist);
696
+ ortho.lookAt(center);
697
+ // Store half-extents for resize updates (pre-aspect values so resize can recompute)
698
+ ortho.userData._orthoHalfExtents = {
699
+ halfW: halfW * padding,
700
+ halfH: halfH * padding
701
+ };
702
+ ortho.updateProjectionMatrix();
703
+ sv.camera = ortho;
704
+ _this4.camera = ortho;
705
+ if (sv.controls) {
706
+ sv.controls.object = ortho;
707
+ sv.controls.target.copy(center);
708
+ sv.controls.minDistance = 0.1;
709
+ sv.controls.maxDistance = 500;
710
+ sv.controls.maxPolarAngle = Math.PI;
711
+ sv.controls.update();
712
+
713
+ // Exit ortho mode when the camera actually moves (orbit/pan/zoom).
714
+ // Using 'change' instead of 'start' so a simple click (e.g. io-device
715
+ // toggle) never triggers the exit β€” only real camera movement does.
716
+ // Skip the first 'change' that fires from controls.update() below.
717
+ var _skipChanges = 1;
718
+ var _onOrbitChange = function onOrbitChange() {
719
+ if (_skipChanges-- > 0) return;
720
+ sv.controls.removeEventListener('change', _onOrbitChange);
721
+ _this4._orthoOrbitStartListener = null;
722
+ _this4._restorePerspectiveCamera();
723
+ };
724
+ sv.controls.addEventListener('change', _onOrbitChange);
725
+ _this4._orthoOrbitStartListener = {
726
+ controls: sv.controls,
727
+ fn: _onOrbitChange
728
+ };
729
+ }
730
+ console.log('πŸ“ Switched to orthographic face view:', faceDir);
731
+ };
732
+ this.renderer.domElement.addEventListener('dblclick', this.eventHandlers.dblclick);
733
+ }
734
+
735
+ /**
736
+ * Restore the perspective camera after an orthographic face view.
737
+ * Safe to call when not in ortho mode (no-op).
738
+ * @private
739
+ */
740
+ }, {
741
+ key: "_restorePerspectiveCamera",
742
+ value: function _restorePerspectiveCamera() {
743
+ var sv = this.sceneViewer;
744
+ if (!(sv !== null && sv !== void 0 && sv._perspCameraSnapshot)) return;
745
+
746
+ // Clean up any pending orbit-change listener
747
+ if (this._orthoOrbitStartListener) {
748
+ var _this$_orthoOrbitStar = this._orthoOrbitStartListener,
749
+ controls = _this$_orthoOrbitStar.controls,
750
+ fn = _this$_orthoOrbitStar.fn;
751
+ controls.removeEventListener('change', fn);
752
+ this._orthoOrbitStartListener = null;
753
+ }
754
+ var snap = sv._perspCameraSnapshot;
755
+ sv.camera = snap.camera;
756
+ this.camera = snap.camera;
757
+ if (sv.controls) {
758
+ sv.controls.object = snap.camera;
759
+ sv.controls.target.copy(snap.controlsTarget);
760
+ if (snap.controlsMinDist != null) sv.controls.minDistance = snap.controlsMinDist;
761
+ if (snap.controlsMaxDist != null) sv.controls.maxDistance = snap.controlsMaxDist;
762
+ if (snap.controlsMaxPolar != null) sv.controls.maxPolarAngle = snap.controlsMaxPolar;
763
+ sv.controls.update();
764
+ }
765
+ sv._perspCameraSnapshot = null;
766
+ console.log('πŸ“ Restored perspective camera from ortho face view');
503
767
  }
504
768
 
505
769
  /**
@@ -649,11 +913,11 @@ var TransformControlsManager = /*#__PURE__*/function () {
649
913
  var isNewSegmentHorizontal = this._isSegmentHorizontal(segment);
650
914
 
651
915
  // Check if all existing segments have the same orientation
652
- var _iterator2 = _createForOfIteratorHelper(existingSegments),
653
- _step2;
916
+ var _iterator3 = _createForOfIteratorHelper(existingSegments),
917
+ _step3;
654
918
  try {
655
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
656
- var existingSegment = _step2.value;
919
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
920
+ var existingSegment = _step3.value;
657
921
  var isExistingHorizontal = this._isSegmentHorizontal(existingSegment);
658
922
 
659
923
  // Disallow mixing horizontal and vertical
@@ -662,9 +926,9 @@ var TransformControlsManager = /*#__PURE__*/function () {
662
926
  }
663
927
  }
664
928
  } catch (err) {
665
- _iterator2.e(err);
929
+ _iterator3.e(err);
666
930
  } finally {
667
- _iterator2.f();
931
+ _iterator3.f();
668
932
  }
669
933
  return true;
670
934
  }
@@ -1116,12 +1380,12 @@ var TransformControlsManager = /*#__PURE__*/function () {
1116
1380
  value: function _applyDeltaToBoundingBoxHelpers() {
1117
1381
  if (!this._dragStartGroupPosition || this.boundingBoxHelpers.length === 0) return;
1118
1382
  var delta = this.multiSelectionGroup.position.clone().sub(this._dragStartGroupPosition);
1119
- var _iterator3 = _createForOfIteratorHelper(this.boundingBoxHelpers),
1120
- _step3;
1383
+ var _iterator4 = _createForOfIteratorHelper(this.boundingBoxHelpers),
1384
+ _step4;
1121
1385
  try {
1122
- for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
1386
+ for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
1123
1387
  var _helper$geometry2;
1124
- var helper = _step3.value;
1388
+ var helper = _step4.value;
1125
1389
  var startPositions = helper.userData._dragStartPositions;
1126
1390
  var posAttr = (_helper$geometry2 = helper.geometry) === null || _helper$geometry2 === void 0 || (_helper$geometry2 = _helper$geometry2.attributes) === null || _helper$geometry2 === void 0 ? void 0 : _helper$geometry2.position;
1127
1391
  if (!startPositions || !posAttr) continue;
@@ -1135,9 +1399,9 @@ var TransformControlsManager = /*#__PURE__*/function () {
1135
1399
  posAttr.needsUpdate = true;
1136
1400
  }
1137
1401
  } catch (err) {
1138
- _iterator3.e(err);
1402
+ _iterator4.e(err);
1139
1403
  } finally {
1140
- _iterator3.f();
1404
+ _iterator4.f();
1141
1405
  }
1142
1406
  }
1143
1407
 
@@ -1515,11 +1779,11 @@ var TransformControlsManager = /*#__PURE__*/function () {
1515
1779
  var objectFilter = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
1516
1780
  var objectsWithBounds = this.getSelectableObjectsWithBounds(objectFilter);
1517
1781
  var intersections = [];
1518
- var _iterator4 = _createForOfIteratorHelper(objectsWithBounds),
1519
- _step4;
1782
+ var _iterator5 = _createForOfIteratorHelper(objectsWithBounds),
1783
+ _step5;
1520
1784
  try {
1521
- for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
1522
- var item = _step4.value;
1785
+ for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
1786
+ var item = _step5.value;
1523
1787
  var object = item.object,
1524
1788
  boundingBox = item.boundingBox;
1525
1789
 
@@ -1539,9 +1803,9 @@ var TransformControlsManager = /*#__PURE__*/function () {
1539
1803
 
1540
1804
  // Sort by distance (closest first)
1541
1805
  } catch (err) {
1542
- _iterator4.e(err);
1806
+ _iterator5.e(err);
1543
1807
  } finally {
1544
- _iterator4.f();
1808
+ _iterator5.f();
1545
1809
  }
1546
1810
  intersections.sort(function (a, b) {
1547
1811
  return a.distance - b.distance;
@@ -1927,8 +2191,8 @@ var TransformControlsManager = /*#__PURE__*/function () {
1927
2191
  key: "_updateSegmentReference",
1928
2192
  value: function _updateSegmentReference(oldSegment, newSegment, index) {
1929
2193
  var selectedIndex = this.selectedObjects.findIndex(function (obj) {
1930
- var _obj$userData2;
1931
- return obj.uuid === oldSegment.uuid || ((_obj$userData2 = obj.userData) === null || _obj$userData2 === void 0 ? void 0 : _obj$userData2.originalUuid) === oldSegment.uuid;
2194
+ var _obj$userData3;
2195
+ return obj.uuid === oldSegment.uuid || ((_obj$userData3 = obj.userData) === null || _obj$userData3 === void 0 ? void 0 : _obj$userData3.originalUuid) === oldSegment.uuid;
1932
2196
  });
1933
2197
  if (selectedIndex !== -1 && newSegment) {
1934
2198
  // Clear bounding box cache
@@ -2315,9 +2579,21 @@ var TransformControlsManager = /*#__PURE__*/function () {
2315
2579
  if (this.eventHandlers.keydown) {
2316
2580
  window.removeEventListener('keydown', this.eventHandlers.keydown);
2317
2581
  }
2582
+ if (this.eventHandlers.pointerdown) {
2583
+ this.renderer.domElement.removeEventListener('pointerdown', this.eventHandlers.pointerdown);
2584
+ }
2318
2585
  if (this.eventHandlers.click) {
2319
2586
  this.renderer.domElement.removeEventListener('click', this.eventHandlers.click);
2320
2587
  }
2588
+ if (this.eventHandlers.contextmenu) {
2589
+ this.renderer.domElement.removeEventListener('contextmenu', this.eventHandlers.contextmenu);
2590
+ }
2591
+ if (this.eventHandlers.dblclick) {
2592
+ this.renderer.domElement.removeEventListener('dblclick', this.eventHandlers.dblclick);
2593
+ }
2594
+
2595
+ // Restore perspective camera if disposed while in ortho face view
2596
+ this._restorePerspectiveCamera();
2321
2597
 
2322
2598
  // Remove sceneViewer event listener
2323
2599
  if (this._objectTransformedListener && this.sceneViewer && typeof this.sceneViewer.off === 'function') {
@@ -59,11 +59,18 @@ var AnimationManager = /*#__PURE__*/function (_BaseDisposable) {
59
59
  sceneViewer.performanceMonitorManager.beginFrame();
60
60
  }
61
61
  try {
62
+ var _sceneViewer$managers;
62
63
  // Update controls
63
64
  sceneViewer.controls.update();
64
65
 
65
- // Render the scene
66
- sceneViewer.renderer.render(sceneViewer.scene, sceneViewer.camera);
66
+ // Render the scene β€” route through the outline manager when active so
67
+ // the mask pass and screen-space composite run after the main render.
68
+ var ioOutline = (_sceneViewer$managers = sceneViewer.managers) === null || _sceneViewer$managers === void 0 ? void 0 : _sceneViewer$managers.ioOutlineManager;
69
+ if (ioOutline !== null && ioOutline !== void 0 && ioOutline.isActive) {
70
+ ioOutline.render();
71
+ } else {
72
+ sceneViewer.renderer.render(sceneViewer.scene, sceneViewer.camera);
73
+ }
67
74
  } catch (renderError) {
68
75
  // Catch WebGL or rendering errors to prevent the animation loop from
69
76
  // producing a permanent white screen. Log once and continue so that