@2112-lab/central-plant 0.3.26 β†’ 0.3.28

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.
Files changed (28) hide show
  1. package/dist/bundle/index.js +922 -974
  2. package/dist/cjs/src/core/centralPlant.js +8 -115
  3. package/dist/cjs/src/core/centralPlantInternals.js +23 -17
  4. package/dist/cjs/src/core/sceneViewer.js +55 -2
  5. package/dist/cjs/src/index.js +0 -2
  6. package/dist/cjs/src/managers/behaviors/IoAnimationManager.js +24 -1
  7. package/dist/cjs/src/managers/behaviors/IoOutlineManager.js +258 -0
  8. package/dist/cjs/src/managers/controls/transformControlsManager.js +319 -43
  9. package/dist/cjs/src/managers/scene/animationManager.js +9 -2
  10. package/dist/cjs/src/managers/scene/componentTooltipManager.js +190 -34
  11. package/dist/cjs/src/managers/scene/modelManager.js +15 -1
  12. package/dist/cjs/src/managers/scene/sceneExportManager.js +3 -29
  13. package/dist/cjs/src/managers/scene/sceneOperationsManager.js +12 -289
  14. package/dist/cjs/src/utils/boundingBoxUtils.js +38 -40
  15. package/dist/esm/src/core/centralPlant.js +8 -115
  16. package/dist/esm/src/core/centralPlantInternals.js +23 -17
  17. package/dist/esm/src/core/sceneViewer.js +55 -2
  18. package/dist/esm/src/index.js +0 -1
  19. package/dist/esm/src/managers/behaviors/IoAnimationManager.js +24 -1
  20. package/dist/esm/src/managers/behaviors/IoOutlineManager.js +234 -0
  21. package/dist/esm/src/managers/controls/transformControlsManager.js +319 -43
  22. package/dist/esm/src/managers/scene/animationManager.js +9 -2
  23. package/dist/esm/src/managers/scene/componentTooltipManager.js +191 -35
  24. package/dist/esm/src/managers/scene/modelManager.js +16 -2
  25. package/dist/esm/src/managers/scene/sceneExportManager.js +4 -30
  26. package/dist/esm/src/managers/scene/sceneOperationsManager.js +12 -289
  27. package/dist/esm/src/utils/boundingBoxUtils.js +39 -42
  28. package/package.json +1 -1
@@ -118,8 +118,17 @@ var TransformControlsManager = /*#__PURE__*/function () {
118
118
  onModeChange: null,
119
119
  onObjectRemoved: null,
120
120
  onSelectionChanged: null,
121
- onIODeviceClick: null
121
+ onIODeviceClick: null,
122
+ onIODeviceDrag: null,
123
+ onIODeviceDragEnd: null
122
124
  };
125
+
126
+ // IO device drag tracking state
127
+ this._ioDragMesh = null;
128
+ this._ioDragStartX = 0;
129
+ this._ioDragStartY = 0;
130
+ this._ioDragMoved = false;
131
+ this._suppressNextClick = false;
123
132
  this.init();
124
133
  }
125
134
 
@@ -199,6 +208,12 @@ var TransformControlsManager = /*#__PURE__*/function () {
199
208
  if (callbacks.onIODeviceClick) {
200
209
  this.callbacks.onIODeviceClick = callbacks.onIODeviceClick;
201
210
  }
211
+ if (callbacks.onIODeviceDrag) {
212
+ this.callbacks.onIODeviceDrag = callbacks.onIODeviceDrag;
213
+ }
214
+ if (callbacks.onIODeviceDragEnd) {
215
+ this.callbacks.onIODeviceDragEnd = callbacks.onIODeviceDragEnd;
216
+ }
202
217
  console.log('πŸ”— Transform controls callbacks registered');
203
218
  }
204
219
  /**
@@ -405,6 +420,11 @@ var TransformControlsManager = /*#__PURE__*/function () {
405
420
  value: function setupKeyboardControls() {
406
421
  var _this3 = this;
407
422
  this.eventHandlers.keydown = function (event) {
423
+ // Restore perspective camera when pressing Escape from ortho face view
424
+ if (event.code === 'Escape') {
425
+ _this3._restorePerspectiveCamera();
426
+ }
427
+
408
428
  // Only handle keys when transform controls are active
409
429
  if (!_this3.transformControls.enabled || _this3.transformState.isTransforming) {
410
430
  return;
@@ -439,9 +459,82 @@ var TransformControlsManager = /*#__PURE__*/function () {
439
459
  var raycaster = new THREE__namespace.Raycaster();
440
460
  var mouse = new THREE__namespace.Vector2();
441
461
 
442
- // Click handler: first click selects (bounding box), second click on same object shows tooltip
462
+ // ── IO device drag ────────────────────────────────────────────────────
463
+ // Detect pointerdown on an IO device mesh and convert a drag gesture into
464
+ // state changes. Up/right = positive direction, down/left = negative.
465
+ this.eventHandlers.pointerdown = function (event) {
466
+ if (_this4.transformState.isTransforming) return;
467
+ if (!_this4.callbacks.onIODeviceDrag) return;
468
+ _this4._calculateMousePosition(event, mouse);
469
+ raycaster.setFromCamera(mouse, _this4.camera);
470
+ var allIntersects = raycaster.intersectObjects(_this4.scene.children, true);
471
+ var ioDeviceObject = null;
472
+ var _iterator = _rollupPluginBabelHelpers.createForOfIteratorHelper(allIntersects),
473
+ _step;
474
+ try {
475
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
476
+ var hit = _step.value;
477
+ var obj = hit.object;
478
+ while (obj) {
479
+ var _obj$userData;
480
+ if (((_obj$userData = obj.userData) === null || _obj$userData === void 0 ? void 0 : _obj$userData.objectType) === 'io-device') {
481
+ ioDeviceObject = obj;
482
+ break;
483
+ }
484
+ obj = obj.parent;
485
+ }
486
+ if (ioDeviceObject) break;
487
+ }
488
+ } catch (err) {
489
+ _iterator.e(err);
490
+ } finally {
491
+ _iterator.f();
492
+ }
493
+ if (!ioDeviceObject) return;
494
+
495
+ // Begin session
496
+ _this4._ioDragMesh = ioDeviceObject;
497
+ _this4._ioDragStartX = event.clientX;
498
+ _this4._ioDragStartY = event.clientY;
499
+ _this4._ioDragMoved = false;
500
+ if (_this4.orbitControls) _this4.orbitControls.enabled = false;
501
+ _this4.callbacks.onIODeviceDrag(ioDeviceObject, 0, true);
502
+ var onMove = function onMove(e) {
503
+ var dx = e.clientX - _this4._ioDragStartX;
504
+ var dy = e.clientY - _this4._ioDragStartY;
505
+ if (Math.abs(dx) > 4 || Math.abs(dy) > 4) _this4._ioDragMoved = true;
506
+ // Up (βˆ’screenY) and right (+screenX) are positive
507
+ var signedDelta = _this4._ioDragStartY - e.clientY + (e.clientX - _this4._ioDragStartX);
508
+ _this4.callbacks.onIODeviceDrag(_this4._ioDragMesh, signedDelta, false);
509
+ };
510
+ var _onUp = function onUp() {
511
+ window.removeEventListener('pointermove', onMove);
512
+ window.removeEventListener('pointerup', _onUp);
513
+ if (_this4.orbitControls) _this4.orbitControls.enabled = true;
514
+ if (_this4._ioDragMoved) {
515
+ // Suppress the click event that will fire after this pointerup
516
+ _this4._suppressNextClick = true;
517
+ }
518
+ if (_this4.callbacks.onIODeviceDragEnd) {
519
+ _this4.callbacks.onIODeviceDragEnd(_this4._ioDragMesh);
520
+ }
521
+ _this4._ioDragMesh = null;
522
+ };
523
+ window.addEventListener('pointermove', onMove);
524
+ window.addEventListener('pointerup', _onUp);
525
+ };
526
+ this.renderer.domElement.addEventListener('pointerdown', this.eventHandlers.pointerdown);
527
+
528
+ // Click handler: left-click selects the object (bounding box + transform controls).
529
+ // Right-click on a component shows the tooltip.
443
530
  this.eventHandlers.click = function (event) {
444
531
  var _targetObject$userDat;
532
+ // Suppress click that follows an IO device drag
533
+ if (_this4._suppressNextClick) {
534
+ _this4._suppressNextClick = false;
535
+ return;
536
+ }
537
+
445
538
  // Skip if currently transforming
446
539
  if (_this4.transformState.isTransforming) {
447
540
  return;
@@ -454,15 +547,15 @@ var TransformControlsManager = /*#__PURE__*/function () {
454
547
  // Check for direct io-device mesh click (before bounding box selection)
455
548
  if (_this4.callbacks.onIODeviceClick) {
456
549
  var allIntersects = raycaster.intersectObjects(_this4.scene.children, true);
457
- var _iterator = _rollupPluginBabelHelpers.createForOfIteratorHelper(allIntersects),
458
- _step;
550
+ var _iterator2 = _rollupPluginBabelHelpers.createForOfIteratorHelper(allIntersects),
551
+ _step2;
459
552
  try {
460
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
461
- var hit = _step.value;
553
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
554
+ var hit = _step2.value;
462
555
  var obj = hit.object;
463
556
  while (obj) {
464
- var _obj$userData;
465
- if (((_obj$userData = obj.userData) === null || _obj$userData === void 0 ? void 0 : _obj$userData.objectType) === 'io-device') {
557
+ var _obj$userData2;
558
+ if (((_obj$userData2 = obj.userData) === null || _obj$userData2 === void 0 ? void 0 : _obj$userData2.objectType) === 'io-device') {
466
559
  _this4.callbacks.onIODeviceClick(obj);
467
560
  return;
468
561
  }
@@ -470,9 +563,9 @@ var TransformControlsManager = /*#__PURE__*/function () {
470
563
  }
471
564
  }
472
565
  } catch (err) {
473
- _iterator.e(err);
566
+ _iterator2.e(err);
474
567
  } finally {
475
- _iterator.f();
568
+ _iterator2.f();
476
569
  }
477
570
  }
478
571
 
@@ -507,23 +600,194 @@ var TransformControlsManager = /*#__PURE__*/function () {
507
600
  return;
508
601
  }
509
602
 
510
- // Check if the clicked object is already selected
511
- var isAlreadySelected = _this4.selectedObjects.length === 1 && _this4.selectedObjects[0] === targetObject;
512
- if (isAlreadySelected) {
513
- // Second click on already-selected object: Full selection including tooltips
514
- _this4.selectObject(targetObject);
515
- } else {
516
- // First click on a new object: Transform controls and bounding box only
517
- // Always deselect first (this clears tooltips via the callback)
603
+ // Left-click: select for transform only (bounding box, no tooltip)
604
+ if (!_this4.selectedObjects.includes(targetObject)) {
518
605
  _this4.deselectObject();
519
-
520
- // Then select the new object if there is one (this shows transform controls and bounding box)
521
- if (targetObject) {
522
- _this4.selectObjectForTransformOnly(targetObject);
523
- }
524
606
  }
607
+ _this4.selectObjectForTransformOnly(targetObject);
525
608
  };
526
609
  this.renderer.domElement.addEventListener('click', this.eventHandlers.click);
610
+
611
+ // Right-click handler: show tooltip for the clicked component.
612
+ this.eventHandlers.contextmenu = function (event) {
613
+ var _targetObject$userDat2;
614
+ event.preventDefault();
615
+ if (_this4.transformState.isTransforming) return;
616
+ _this4._calculateMousePosition(event, mouse);
617
+ raycaster.setFromCamera(mouse, _this4.camera);
618
+ var targetObject = _this4._findTargetObject(raycaster, objectFilter);
619
+ if (!targetObject) return;
620
+ var objectType = (_targetObject$userDat2 = targetObject.userData) === null || _targetObject$userDat2 === void 0 ? void 0 : _targetObject$userDat2.objectType;
621
+ if (objectType !== 'component' && objectType !== 'gateway' && objectType !== 'segment') return;
622
+
623
+ // Ensure the object is selected, then show the full tooltip.
624
+ if (!_this4.selectedObjects.includes(targetObject)) {
625
+ _this4.deselectObject();
626
+ _this4.selectObjectForTransformOnly(targetObject);
627
+ }
628
+ _this4.selectObject(targetObject);
629
+ };
630
+ this.renderer.domElement.addEventListener('contextmenu', this.eventHandlers.contextmenu);
631
+
632
+ // Double-click handler: switch to orthographic camera fixed to the clicked face.
633
+ // Press Escape to restore the perspective camera.
634
+ this.eventHandlers.dblclick = function (event) {
635
+ if (_this4.transformState.isTransforming) return;
636
+ var sv = _this4.sceneViewer;
637
+ if (!sv) return;
638
+ _this4._calculateMousePosition(event, mouse);
639
+ raycaster.setFromCamera(_this4.camera, _this4.camera); // ensure raycaster uses current camera
640
+ raycaster.setFromCamera(mouse, _this4.camera);
641
+
642
+ // Raycast directly against scene meshes to obtain the face normal
643
+ var allHits = raycaster.intersectObjects(_this4.scene.children, true).filter(function (h) {
644
+ var _h$object$userData, _h$object$userData2;
645
+ 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);
646
+ });
647
+ if (!allHits.length || !allHits[0].face) return;
648
+ var hit = allHits[0];
649
+
650
+ // Walk up to find the component root
651
+ var componentObj = hit.object;
652
+ while (componentObj) {
653
+ var _componentObj$userDat;
654
+ if (((_componentObj$userDat = componentObj.userData) === null || _componentObj$userDat === void 0 ? void 0 : _componentObj$userDat.objectType) === 'component') break;
655
+ componentObj = componentObj.parent;
656
+ }
657
+ if (!componentObj) return;
658
+
659
+ // Transform face normal from local object space to world space
660
+ var normalMatrix = new THREE__namespace.Matrix3().getNormalMatrix(hit.object.matrixWorld);
661
+ var worldNormal = hit.face.normal.clone().applyMatrix3(normalMatrix).normalize();
662
+
663
+ // Snap to the closest cardinal axis (Z-up: Β±X, Β±Y, Β±Z)
664
+ var cardinals = [new THREE__namespace.Vector3(1, 0, 0), new THREE__namespace.Vector3(-1, 0, 0), new THREE__namespace.Vector3(0, 1, 0), new THREE__namespace.Vector3(0, -1, 0), new THREE__namespace.Vector3(0, 0, 1), new THREE__namespace.Vector3(0, 0, -1)];
665
+ var faceDir = cardinals.reduce(function (best, c) {
666
+ return c.dot(worldNormal) > best.dot(worldNormal) ? c : best;
667
+ });
668
+
669
+ // Bounding box of the component
670
+ var bbox = new THREE__namespace.Box3().setFromObject(componentObj);
671
+ var center = bbox.getCenter(new THREE__namespace.Vector3());
672
+ var size = bbox.getSize(new THREE__namespace.Vector3());
673
+
674
+ // Ortho half-extents: axes perpendicular to the face direction
675
+ var isXFace = Math.abs(faceDir.x) > 0.5;
676
+ var isYFace = Math.abs(faceDir.y) > 0.5;
677
+ var halfW, halfH;
678
+ if (isXFace) {
679
+ halfW = size.y / 2;
680
+ halfH = size.z / 2;
681
+ } else if (isYFace) {
682
+ halfW = size.x / 2;
683
+ halfH = size.z / 2;
684
+ } else {
685
+ halfW = size.x / 2;
686
+ halfH = size.y / 2;
687
+ }
688
+ var padding = 1.6;
689
+ var domEl = sv.renderer.domElement;
690
+ var aspect = domEl.clientWidth / (domEl.clientHeight || 1);
691
+
692
+ // Frustum must satisfy both:
693
+ // frustumHalfW >= halfW * padding (object fits horizontally)
694
+ // frustumHalfH >= halfH * padding (object fits vertically)
695
+ // frustumHalfW / frustumHalfH == aspect (no squishing)
696
+ var minHalfH = halfH * padding;
697
+ var minHalfW = halfW * padding;
698
+ var frustumHalfH = Math.max(minHalfH, minHalfW / aspect);
699
+ var frustumHalfW = frustumHalfH * aspect;
700
+ var isZFace = Math.abs(faceDir.z) > 0.5;
701
+ var upVec = isZFace ? new THREE__namespace.Vector3(0, 1, 0) : new THREE__namespace.Vector3(0, 0, 1);
702
+
703
+ // Store the perspective camera snapshot on first entry
704
+ if (!sv._perspCameraSnapshot) {
705
+ var _sv$controls$target$c, _sv$controls, _sv$controls2, _sv$controls3, _sv$controls4;
706
+ sv._perspCameraSnapshot = {
707
+ camera: sv.camera,
708
+ 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__namespace.Vector3(),
709
+ controlsMinDist: (_sv$controls2 = sv.controls) === null || _sv$controls2 === void 0 ? void 0 : _sv$controls2.minDistance,
710
+ controlsMaxDist: (_sv$controls3 = sv.controls) === null || _sv$controls3 === void 0 ? void 0 : _sv$controls3.maxDistance,
711
+ controlsMaxPolar: (_sv$controls4 = sv.controls) === null || _sv$controls4 === void 0 ? void 0 : _sv$controls4.maxPolarAngle
712
+ };
713
+ }
714
+
715
+ // Build the orthographic camera
716
+ var ortho = new THREE__namespace.OrthographicCamera(-frustumHalfW, frustumHalfW, frustumHalfH, -frustumHalfH, 0.01, 1000);
717
+ ortho.up.copy(upVec);
718
+ var dist = Math.max(size.x, size.y, size.z) * 3;
719
+ ortho.position.copy(center).addScaledVector(faceDir, dist);
720
+ ortho.lookAt(center);
721
+ // Store half-extents for resize updates (pre-aspect values so resize can recompute)
722
+ ortho.userData._orthoHalfExtents = {
723
+ halfW: halfW * padding,
724
+ halfH: halfH * padding
725
+ };
726
+ ortho.updateProjectionMatrix();
727
+ sv.camera = ortho;
728
+ _this4.camera = ortho;
729
+ if (sv.controls) {
730
+ sv.controls.object = ortho;
731
+ sv.controls.target.copy(center);
732
+ sv.controls.minDistance = 0.1;
733
+ sv.controls.maxDistance = 500;
734
+ sv.controls.maxPolarAngle = Math.PI;
735
+ sv.controls.update();
736
+
737
+ // Exit ortho mode when the camera actually moves (orbit/pan/zoom).
738
+ // Using 'change' instead of 'start' so a simple click (e.g. io-device
739
+ // toggle) never triggers the exit β€” only real camera movement does.
740
+ // Skip the first 'change' that fires from controls.update() below.
741
+ var _skipChanges = 1;
742
+ var _onOrbitChange = function onOrbitChange() {
743
+ if (_skipChanges-- > 0) return;
744
+ sv.controls.removeEventListener('change', _onOrbitChange);
745
+ _this4._orthoOrbitStartListener = null;
746
+ _this4._restorePerspectiveCamera();
747
+ };
748
+ sv.controls.addEventListener('change', _onOrbitChange);
749
+ _this4._orthoOrbitStartListener = {
750
+ controls: sv.controls,
751
+ fn: _onOrbitChange
752
+ };
753
+ }
754
+ console.log('πŸ“ Switched to orthographic face view:', faceDir);
755
+ };
756
+ this.renderer.domElement.addEventListener('dblclick', this.eventHandlers.dblclick);
757
+ }
758
+
759
+ /**
760
+ * Restore the perspective camera after an orthographic face view.
761
+ * Safe to call when not in ortho mode (no-op).
762
+ * @private
763
+ */
764
+ }, {
765
+ key: "_restorePerspectiveCamera",
766
+ value: function _restorePerspectiveCamera() {
767
+ var sv = this.sceneViewer;
768
+ if (!(sv !== null && sv !== void 0 && sv._perspCameraSnapshot)) return;
769
+
770
+ // Clean up any pending orbit-change listener
771
+ if (this._orthoOrbitStartListener) {
772
+ var _this$_orthoOrbitStar = this._orthoOrbitStartListener,
773
+ controls = _this$_orthoOrbitStar.controls,
774
+ fn = _this$_orthoOrbitStar.fn;
775
+ controls.removeEventListener('change', fn);
776
+ this._orthoOrbitStartListener = null;
777
+ }
778
+ var snap = sv._perspCameraSnapshot;
779
+ sv.camera = snap.camera;
780
+ this.camera = snap.camera;
781
+ if (sv.controls) {
782
+ sv.controls.object = snap.camera;
783
+ sv.controls.target.copy(snap.controlsTarget);
784
+ if (snap.controlsMinDist != null) sv.controls.minDistance = snap.controlsMinDist;
785
+ if (snap.controlsMaxDist != null) sv.controls.maxDistance = snap.controlsMaxDist;
786
+ if (snap.controlsMaxPolar != null) sv.controls.maxPolarAngle = snap.controlsMaxPolar;
787
+ sv.controls.update();
788
+ }
789
+ sv._perspCameraSnapshot = null;
790
+ console.log('πŸ“ Restored perspective camera from ortho face view');
527
791
  }
528
792
 
529
793
  /**
@@ -673,11 +937,11 @@ var TransformControlsManager = /*#__PURE__*/function () {
673
937
  var isNewSegmentHorizontal = this._isSegmentHorizontal(segment);
674
938
 
675
939
  // Check if all existing segments have the same orientation
676
- var _iterator2 = _rollupPluginBabelHelpers.createForOfIteratorHelper(existingSegments),
677
- _step2;
940
+ var _iterator3 = _rollupPluginBabelHelpers.createForOfIteratorHelper(existingSegments),
941
+ _step3;
678
942
  try {
679
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
680
- var existingSegment = _step2.value;
943
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
944
+ var existingSegment = _step3.value;
681
945
  var isExistingHorizontal = this._isSegmentHorizontal(existingSegment);
682
946
 
683
947
  // Disallow mixing horizontal and vertical
@@ -686,9 +950,9 @@ var TransformControlsManager = /*#__PURE__*/function () {
686
950
  }
687
951
  }
688
952
  } catch (err) {
689
- _iterator2.e(err);
953
+ _iterator3.e(err);
690
954
  } finally {
691
- _iterator2.f();
955
+ _iterator3.f();
692
956
  }
693
957
  return true;
694
958
  }
@@ -1140,12 +1404,12 @@ var TransformControlsManager = /*#__PURE__*/function () {
1140
1404
  value: function _applyDeltaToBoundingBoxHelpers() {
1141
1405
  if (!this._dragStartGroupPosition || this.boundingBoxHelpers.length === 0) return;
1142
1406
  var delta = this.multiSelectionGroup.position.clone().sub(this._dragStartGroupPosition);
1143
- var _iterator3 = _rollupPluginBabelHelpers.createForOfIteratorHelper(this.boundingBoxHelpers),
1144
- _step3;
1407
+ var _iterator4 = _rollupPluginBabelHelpers.createForOfIteratorHelper(this.boundingBoxHelpers),
1408
+ _step4;
1145
1409
  try {
1146
- for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
1410
+ for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
1147
1411
  var _helper$geometry2;
1148
- var helper = _step3.value;
1412
+ var helper = _step4.value;
1149
1413
  var startPositions = helper.userData._dragStartPositions;
1150
1414
  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;
1151
1415
  if (!startPositions || !posAttr) continue;
@@ -1159,9 +1423,9 @@ var TransformControlsManager = /*#__PURE__*/function () {
1159
1423
  posAttr.needsUpdate = true;
1160
1424
  }
1161
1425
  } catch (err) {
1162
- _iterator3.e(err);
1426
+ _iterator4.e(err);
1163
1427
  } finally {
1164
- _iterator3.f();
1428
+ _iterator4.f();
1165
1429
  }
1166
1430
  }
1167
1431
 
@@ -1539,11 +1803,11 @@ var TransformControlsManager = /*#__PURE__*/function () {
1539
1803
  var objectFilter = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
1540
1804
  var objectsWithBounds = this.getSelectableObjectsWithBounds(objectFilter);
1541
1805
  var intersections = [];
1542
- var _iterator4 = _rollupPluginBabelHelpers.createForOfIteratorHelper(objectsWithBounds),
1543
- _step4;
1806
+ var _iterator5 = _rollupPluginBabelHelpers.createForOfIteratorHelper(objectsWithBounds),
1807
+ _step5;
1544
1808
  try {
1545
- for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
1546
- var item = _step4.value;
1809
+ for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
1810
+ var item = _step5.value;
1547
1811
  var object = item.object,
1548
1812
  boundingBox = item.boundingBox;
1549
1813
 
@@ -1563,9 +1827,9 @@ var TransformControlsManager = /*#__PURE__*/function () {
1563
1827
 
1564
1828
  // Sort by distance (closest first)
1565
1829
  } catch (err) {
1566
- _iterator4.e(err);
1830
+ _iterator5.e(err);
1567
1831
  } finally {
1568
- _iterator4.f();
1832
+ _iterator5.f();
1569
1833
  }
1570
1834
  intersections.sort(function (a, b) {
1571
1835
  return a.distance - b.distance;
@@ -1951,8 +2215,8 @@ var TransformControlsManager = /*#__PURE__*/function () {
1951
2215
  key: "_updateSegmentReference",
1952
2216
  value: function _updateSegmentReference(oldSegment, newSegment, index) {
1953
2217
  var selectedIndex = this.selectedObjects.findIndex(function (obj) {
1954
- var _obj$userData2;
1955
- return obj.uuid === oldSegment.uuid || ((_obj$userData2 = obj.userData) === null || _obj$userData2 === void 0 ? void 0 : _obj$userData2.originalUuid) === oldSegment.uuid;
2218
+ var _obj$userData3;
2219
+ return obj.uuid === oldSegment.uuid || ((_obj$userData3 = obj.userData) === null || _obj$userData3 === void 0 ? void 0 : _obj$userData3.originalUuid) === oldSegment.uuid;
1956
2220
  });
1957
2221
  if (selectedIndex !== -1 && newSegment) {
1958
2222
  // Clear bounding box cache
@@ -2339,9 +2603,21 @@ var TransformControlsManager = /*#__PURE__*/function () {
2339
2603
  if (this.eventHandlers.keydown) {
2340
2604
  window.removeEventListener('keydown', this.eventHandlers.keydown);
2341
2605
  }
2606
+ if (this.eventHandlers.pointerdown) {
2607
+ this.renderer.domElement.removeEventListener('pointerdown', this.eventHandlers.pointerdown);
2608
+ }
2342
2609
  if (this.eventHandlers.click) {
2343
2610
  this.renderer.domElement.removeEventListener('click', this.eventHandlers.click);
2344
2611
  }
2612
+ if (this.eventHandlers.contextmenu) {
2613
+ this.renderer.domElement.removeEventListener('contextmenu', this.eventHandlers.contextmenu);
2614
+ }
2615
+ if (this.eventHandlers.dblclick) {
2616
+ this.renderer.domElement.removeEventListener('dblclick', this.eventHandlers.dblclick);
2617
+ }
2618
+
2619
+ // Restore perspective camera if disposed while in ortho face view
2620
+ this._restorePerspectiveCamera();
2345
2621
 
2346
2622
  // Remove sceneViewer event listener
2347
2623
  if (this._objectTransformedListener && this.sceneViewer && typeof this.sceneViewer.off === 'function') {
@@ -63,11 +63,18 @@ var AnimationManager = /*#__PURE__*/function (_BaseDisposable) {
63
63
  sceneViewer.performanceMonitorManager.beginFrame();
64
64
  }
65
65
  try {
66
+ var _sceneViewer$managers;
66
67
  // Update controls
67
68
  sceneViewer.controls.update();
68
69
 
69
- // Render the scene
70
- sceneViewer.renderer.render(sceneViewer.scene, sceneViewer.camera);
70
+ // Render the scene β€” route through the outline manager when active so
71
+ // the mask pass and screen-space composite run after the main render.
72
+ var ioOutline = (_sceneViewer$managers = sceneViewer.managers) === null || _sceneViewer$managers === void 0 ? void 0 : _sceneViewer$managers.ioOutlineManager;
73
+ if (ioOutline !== null && ioOutline !== void 0 && ioOutline.isActive) {
74
+ ioOutline.render();
75
+ } else {
76
+ sceneViewer.renderer.render(sceneViewer.scene, sceneViewer.camera);
77
+ }
71
78
  } catch (renderError) {
72
79
  // Catch WebGL or rendering errors to prevent the animation loop from
73
80
  // producing a permanent white screen. Log once and continue so that