ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
cvRenderViewSelectionReaction.cpp
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
20 
21 #include "cvSelectionHighlighter.h"
22 #include "cvSelectionPipeline.h"
23 // cvSelectionToolHelper merged into cvSelectionPipeline
24 #include "cvViewSelectionManager.h"
25 
26 // LOCAL
27 #include "PclUtils/PCLVis.h"
28 
29 // CV_CORE_LIB
30 #include <CVLog.h>
31 #include <ecvDisplayTools.h>
32 
33 // CV_APP (forward declaration to avoid circular dependency)
34 class MainWindow;
35 
36 // VTK
37 #include <vtkCell.h>
38 #include <vtkCellData.h>
39 #include <vtkCellType.h>
40 #include <vtkCommand.h>
41 #include <vtkDataArray.h>
42 #include <vtkFieldData.h>
43 #include <vtkHardwareSelector.h>
44 #include <vtkIdTypeArray.h>
45 #include <vtkIntArray.h>
46 #include <vtkInteractorStyle.h>
47 #include <vtkInteractorStyleDrawPolygon.h>
48 #include <vtkInteractorStyleRubberBand3D.h>
49 #include <vtkInteractorStyleRubberBandPick.h>
50 #include <vtkInteractorStyleRubberBandZoom.h>
51 #include <vtkInteractorStyleTrackballCamera.h>
52 #include <vtkPointData.h>
53 #include <vtkPolyData.h>
54 #include <vtkRenderWindow.h>
55 #include <vtkRenderWindowInteractor.h>
56 #include <vtkRenderer.h>
57 #include <vtkStringArray.h>
58 #include <vtkVector.h>
59 
60 // QT
61 #include <QActionGroup>
62 #include <QApplication>
63 #include <QClipboard>
64 #include <QKeySequence>
65 #include <QMainWindow>
66 #include <QMessageBox>
67 #include <QPixmap>
68 #include <QShortcut>
69 #include <QTimer>
70 #include <QToolTip>
71 #include <QWidget>
72 
73 // Include zoom cursor XPM
74 #include "zoom.xpm"
75 
76 //-----------------------------------------------------------------------------
77 // Static member initialization
78 //-----------------------------------------------------------------------------
79 QPointer<cvRenderViewSelectionReaction>
80  cvRenderViewSelectionReaction::ActiveReaction;
81 
82 //-----------------------------------------------------------------------------
83 // Constructor/Destructor
84 //-----------------------------------------------------------------------------
85 
87  QAction* parentAction, SelectionMode mode, QActionGroup* modifierGroup)
88  : QObject(parentAction),
89  m_parentAction(parentAction),
90  m_modifierGroup(modifierGroup),
91  m_mode(mode),
92  m_zoomCursor(QCursor(QPixmap((const char**)zoom_xpm))),
93  m_mouseMovingTimer(this) {
94  // Initialize observer IDs
95  for (size_t i = 0; i < sizeof(m_observerIds) / sizeof(m_observerIds[0]);
96  ++i) {
97  m_observerIds[i] = 0;
98  }
99 
100  // Connect action triggered signal
101  // Reference: pqRenderViewSelectionReaction.cxx line 68
102  connect(parentAction, &QAction::triggered, this,
104 
105  // For non-interactive modes (Clear, Grow, Shrink), connect to selection
106  // manager Reference: pqRenderViewSelectionReaction.cxx lines 80-88
107  if (m_mode == SelectionMode::CLEAR_SELECTION ||
108  m_mode == SelectionMode::GROW_SELECTION ||
111  connect(manager,
114  }
115 
116  // For interactive/tooltip modes, connect to data update signal
117  // Reference: pqRenderViewSelectionReaction.cxx lines 99-112
118  switch (m_mode) {
125  // Connect to data update for cache invalidation
126  // This would be connected to pqActiveObjects::dataUpdated in
127  // ParaView
128  break;
129  default:
130  break;
131  }
132 
133  // Setup mouse moving timer for tooltip
134  // Reference: pqRenderViewSelectionReaction.cxx line 115-116
135  m_mouseMovingTimer.setSingleShot(true);
136  connect(&m_mouseMovingTimer, &QTimer::timeout, this,
138 
139  // Copy tooltip shortcut (ParaView-style)
140  // Reference: pqRenderViewSelectionReaction.cxx line 59, 118-120
141  // Use main window widget as parent (like ParaView's
142  // pqCoreUtilities::mainWidget()) ParaView uses
143  // pqCoreUtilities::mainWidget() which finds QMainWindow from top-level
144  // widgets
145  QWidget* parentWidget = nullptr;
146  // Find main window from top-level widgets (ParaView-style)
147  for (QWidget* widget : QApplication::topLevelWidgets()) {
148  if (widget->isWindow() && widget->isVisible() &&
149  qobject_cast<QMainWindow*>(widget)) {
150  parentWidget = widget;
151  break;
152  }
153  }
154  if (!parentWidget) {
155  // Fallback: find any main window (even if not visible)
156  for (QWidget* widget : QApplication::topLevelWidgets()) {
157  if (widget->isWindow() && qobject_cast<QMainWindow*>(widget)) {
158  parentWidget = widget;
159  break;
160  }
161  }
162  }
163  // If still no parent, use nullptr (shortcut will be application-wide)
164  m_copyTooltipShortcut = new QShortcut(parentWidget);
165  connect(m_copyTooltipShortcut, &QShortcut::activated, this, [this]() {
166  if (!m_plainTooltipText.isEmpty()) {
167  QApplication::clipboard()->setText(m_plainTooltipText);
168  }
169  });
170  m_copyTooltipShortcut->setEnabled(false);
171 
172  // Initial enable state update
174 }
175 
176 //-----------------------------------------------------------------------------
178  // Clean up observers before destruction
179  // Reference: pqRenderViewSelectionReaction.cxx line 124-127
181 
182  // Clean up copy tooltip shortcut
183  if (m_copyTooltipShortcut) {
184  delete m_copyTooltipShortcut;
185  m_copyTooltipShortcut = nullptr;
186  }
187 }
188 
189 //-----------------------------------------------------------------------------
190 // Public methods
191 //-----------------------------------------------------------------------------
192 
194  ecvGenericVisualizer3D* viewer) {
195  if (m_viewer == viewer) {
196  return;
197  }
198 
199  // End any active selection before changing visualizer
200  if (isActive()) {
201  endSelection();
202  }
203 
204  m_viewer = viewer;
205  m_interactor = nullptr;
206  m_renderer = nullptr;
207 
208  // Get VTK objects from visualizer
209  PclUtils::PCLVis* pclVis = getPCLVis();
210  if (pclVis) {
211  m_interactor = pclVis->getRenderWindowInteractor();
212  m_renderer = pclVis->getCurrentRenderer();
213  }
214 
215  // Update enable state with new visualizer
216  // This ensures polygon selection actions are enabled when viewer is set
218 }
219 
220 //-----------------------------------------------------------------------------
222  return ActiveReaction == this;
223 }
224 
225 //-----------------------------------------------------------------------------
227  if (ActiveReaction) {
228  ActiveReaction->endSelection();
229  }
230 }
231 
232 //-----------------------------------------------------------------------------
233 // Public event handlers (forwarded to protected methods)
234 //-----------------------------------------------------------------------------
235 
237  vtkObject* caller, unsigned long eventId, void* callData) {
238  selectionChanged(caller, eventId, callData);
239 }
240 
242 
245 }
246 
249 }
250 
252 
255 }
256 
259 }
260 
261 //-----------------------------------------------------------------------------
262 // VTK callback functions (ParaView-style direct member function callbacks)
263 //-----------------------------------------------------------------------------
264 
265 // VTK observer callback adapters - required signature for AddObserver
267  unsigned long eventId,
268  void* callData) {
269  selectionChanged(caller, eventId, callData);
270 }
271 
273  unsigned long,
274  void*) {
275  onMouseMove();
276 }
277 
279  unsigned long,
280  void*) {
282 }
283 
285  unsigned long,
286  void*) {
288 }
289 
291  unsigned long,
292  void*) {
293  onWheelRotate();
294 }
295 
297  unsigned long,
298  void*) {
300 }
301 
303  unsigned long,
304  void*) {
306 }
307 
309  unsigned long,
310  void*) {
312 }
313 
315  unsigned long,
316  void*) {
318 }
319 
320 //-----------------------------------------------------------------------------
321 // Public slots
322 //-----------------------------------------------------------------------------
323 
325  // Reference: pqRenderViewSelectionReaction::actionTriggered()
326  // lines 144-163
327 
328  QAction* actn = m_parentAction;
329  if (!actn) {
330  return;
331  }
332 
333  if (actn->isCheckable()) {
334  if (val) {
335  beginSelection();
336  } else {
337  endSelection();
338  }
339  } else {
340  // Non-checkable actions (like Clear, Grow, Shrink)
341  // Reference: ParaView pqRenderViewSelectionReaction.cxx lines 158-162
342  // Call both beginSelection() and endSelection() in sequence
343  beginSelection();
344  endSelection();
345  }
346 }
347 
348 //-----------------------------------------------------------------------------
350  // Reference: pqRenderViewSelectionReaction::updateEnableState()
351  // lines 166-259
352 
353  if (!m_parentAction) {
354  return;
355  }
356 
357  // End selection if currently active and updating state
358  // Reference: line 168
359  endSelection();
360 
362 
363  switch (m_mode) {
366  // Enable if there's an active selection
367  m_parentAction->setEnabled(manager->hasSelection());
368  break;
369 
371  // Enable if selection can be shrunk (has layers >= 1)
372  // Reference: pqRenderViewSelectionReaction::updateEnableState()
373  // lines 184-212 - checks NumberOfLayers property
374  m_parentAction->setEnabled(manager->canShrinkSelection());
375  break;
376 
379  // These require a colored representation
380  // Simplified: enable if visualizer is available
381  m_parentAction->setEnabled(m_viewer != nullptr);
382  break;
383 
386  // Polygon selection modes: enable if visualizer is available
387  // These modes require viewer/interactor to work, but should be
388  // enabled even if viewer is not yet set (shortcut should still
389  // work)
390  m_parentAction->setEnabled(true);
391  break;
392 
393  default:
394  // Other modes: enable if visualizer is available
395  m_parentAction->setEnabled(m_viewer != nullptr);
396  break;
397  }
398 }
399 
400 //-----------------------------------------------------------------------------
401 // Protected slots
402 //-----------------------------------------------------------------------------
403 
405  // Reference: pqRenderViewSelectionReaction::beginSelection()
406  // lines 307-494
407 
408  // GROW/SHRINK/CLEAR don't require viewer/interactor - they work directly
409  // with the selection manager
410  bool requiresViewer = (m_mode != SelectionMode::GROW_SELECTION &&
413 
414  if (requiresViewer && (!m_viewer || !m_interactor)) {
416  "[cvRenderViewSelectionReaction::beginSelection] No viewer or "
417  "interactor");
418  return;
419  }
420 
421  // Already active - nothing to do
422  // Reference: lines 314-318
423  if (ActiveReaction == this) {
424  return;
425  }
426 
427  // Check compatibility with current selection and handle modifier group
428  // Reference: lines 320-331
429  if (ActiveReaction) {
430  if (!ActiveReaction->isCompatible(m_mode)) {
432  }
433  // End other active reaction
434  ActiveReaction->endSelection();
435  }
436 
437  // Set this as the active reaction
438  // Reference: line 333
439  ActiveReaction = this;
440 
441  // Store previous interaction mode for restoration
442  // Reference: lines 335-336
443  // In ParaView this uses vtkSMPropertyHelper to get InteractionMode
444  // We store the interactor style instead
445  m_previousRenderViewMode = 0; // Placeholder (valid value, not -1)
446 
447  // Handle each mode type
448  // Reference: lines 340-437 (first switch statement)
450 
451  switch (m_mode) {
452  // CLEAR/GROW/SHRINK are one-shot commands
453  // Reference: lines 415-431
455  if (manager) {
456  manager->clearSelection();
457  }
458  break;
459 
461  if (manager) {
462  manager->growSelection();
463  }
464  break;
465 
467  if (manager) {
468  manager->shrinkSelection();
469  }
470  break;
471 
472  // Interactive and tooltip modes
479  // Show instruction dialog for interactive/tooltip modes
480  // Reference: lines 352-401
482  // Fall through to setup cursor and style
483  [[fallthrough]];
484 
485  default:
486  // CRITICAL: Disable PCLVis's PointPickingCallback to prevent
487  // conflicts
488  if (PclUtils::PCLVis* pclVis = getPCLVis()) {
489  if (pclVis->isPointPickingEnabled()) {
490  pclVis->setPointPickingEnabled(false);
491  }
492  }
493 
494  // Enter selection mode for cache optimization
495  if (cvSelectionPipeline* pipeline = getSelectionPipeline()) {
496  pipeline->enterSelectionMode();
497  }
498 
499  // Set cursor and interactor style based on mode
500  // Reference: lines 340-437
501  // NOTE: setupInteractorStyle() calls storeCurrentStyle() internally
503  break;
504  }
505 
506  // Set up event observers based on mode
507  // Reference: lines 439-491 (second switch statement)
508  setupObservers();
509 
510  // Update action state
511  // Reference: line 493
512  if (m_parentAction) {
513  m_parentAction->setChecked(true);
514  }
515 }
516 
517 //-----------------------------------------------------------------------------
519  // Reference: pqRenderViewSelectionReaction::endSelection()
520  // lines 497-533
521 
522  if (!m_viewer) {
523  return;
524  }
525 
526  // Only end if this is the active reaction
527  // Reference: lines 504-507
528  if (ActiveReaction != this || m_previousRenderViewMode == -1) {
529  return;
530  }
531 
532  // Clear active reaction FIRST to prevent re-entry
533  // Reference: line 509
534  ActiveReaction = nullptr;
535  m_previousRenderViewMode = -1;
536 
537  // Stop mouse moving timer
538  // Reference: lines 517-518
539  m_mouseMovingTimer.stop();
540  m_mouseMoving = false;
541 
542  // Clear hover state
543  m_hoveredId = -1;
544  m_currentPolyData = nullptr;
545 
546  // Disable copy tooltip shortcut
547  // Reference: pqRenderViewSelectionReaction.cxx line 930-931
548  if (m_copyTooltipShortcut) {
549  m_copyTooltipShortcut->setKey(QKeySequence());
550  m_copyTooltipShortcut->setEnabled(false);
551  }
552 
553  // CRITICAL FIX: Defer cleanup operations to avoid crashing when called
554  // from within VTK event handlers (e.g.,
555  // vtkInteractorStyleRubberBand3D::OnLeftButtonUp) The VTK event loop may
556  // still invoke additional events after SelectionChangedEvent, so we must
557  // not cleanup observers until the event loop completes.
558  QTimer::singleShot(0, this, [this]() {
559  // If a new mode has started, only skip the mode-exit operations
560  // but still clean up observers from this mode
561  bool newModeActive = (ActiveReaction != nullptr);
562 
563  if (newModeActive) {
564  // Only clean up observers - the new mode will handle style/cursor
566  return;
567  }
568 
569  // Full cleanup when no new mode is active:
570 
571  // Restore previous interactor style
572  // Reference: lines 510-513
573  restoreStyle();
574 
575  // Restore cursor to default arrow
576  // Reference: line 514
577  unsetCursor();
578 
579  // Clean up observers - SAFE now since we're outside VTK event loop
580  // Reference: line 515
582 
583  // Update tooltip
584  // Reference: line 519
585  updateTooltip();
586 
587  // Exit selection mode to release cached buffers
589  if (pipeline) {
590  pipeline->exitSelectionMode();
591  }
592 
593  // Re-enable PCLVis's PointPickingCallback
594  PclUtils::PCLVis* pclVis = getPCLVis();
595  if (pclVis && !pclVis->isPointPickingEnabled()) {
596  pclVis->setPointPickingEnabled(true);
597  }
598  });
599 
600  // Uncheck the action immediately (safe to do)
601  // Reference: line 516
602  if (m_parentAction && m_parentAction->isCheckable() &&
603  m_parentAction->isChecked()) {
604  m_parentAction->blockSignals(true);
605  m_parentAction->setChecked(false);
606  m_parentAction->blockSignals(false);
607  }
608 
610  QString("[cvRenderViewSelectionReaction] Selection mode %1 ended")
611  .arg(static_cast<int>(m_mode)));
612 }
613 
614 //-----------------------------------------------------------------------------
616  // Reference: pqRenderViewSelectionReaction::onMouseStop()
617  // lines 875-879
618  m_mouseMoving = false;
619  updateTooltip();
620 }
621 
622 //-----------------------------------------------------------------------------
624  // Reference: pqRenderViewSelectionReaction::clearSelectionCache()
625  // lines 882-888
626  if (ActiveReaction == this) {
628  if (pipeline) {
629  pipeline->invalidateCachedSelection();
630  }
631  }
632 }
633 
634 //-----------------------------------------------------------------------------
635 // Protected methods
636 //-----------------------------------------------------------------------------
637 
639  unsigned long eventId,
640  void* callData) {
641  // Reference: pqRenderViewSelectionReaction::selectionChanged()
642  // lines 536-603
643 
644  if (ActiveReaction != this) {
645  // This can happen due to delayed cleanup of VTK observers
646  // It's not an error, just ignore it
647  return;
648  }
649 
650  int selectionModifier = getSelectionModifier();
651 
652  // For polygon selection modes, we need to get polygon points from the
653  // interactor style, not from callData. vtkInteractorStyleDrawPolygon fires
654  // SelectionChangedEvent without passing polygon data - we need to call
655  // GetPolygonPoints() on the style.
656  // Reference: ParaView vtkPVRenderView.cxx lines 919-935
657  bool isPolygonMode =
661 
662  if (isPolygonMode) {
663  vtkInteractorStyleDrawPolygon* polygonStyle =
664  vtkInteractorStyleDrawPolygon::SafeDownCast(m_selectionStyle);
665  if (!polygonStyle) {
667  "[cvRenderViewSelectionReaction] Polygon mode but style is "
668  "not vtkInteractorStyleDrawPolygon");
669 
670  // Try to get style from interactor directly
671  if (m_interactor) {
672  vtkInteractorObserver* currentStyle =
673  m_interactor->GetInteractorStyle();
674  polygonStyle = vtkInteractorStyleDrawPolygon::SafeDownCast(
675  currentStyle);
676  }
677 
678  if (!polygonStyle) {
679  return;
680  }
681  }
682 
683  // Get polygon points from the style (ParaView approach)
684  std::vector<vtkVector2i> points = polygonStyle->GetPolygonPoints();
685 
686  if (points.size() < 3) {
687  CVLog::Warning(QString("[cvRenderViewSelectionReaction] Polygon "
688  "has fewer than 3 "
689  "vertices (%1), ignoring")
690  .arg(points.size()));
691  return;
692  }
693 
694  // Construct vtkIntArray with polygon points (like ParaView)
695  vtkSmartPointer<vtkIntArray> polygonPointsArray =
697  polygonPointsArray->SetNumberOfComponents(2);
698  polygonPointsArray->SetNumberOfTuples(
699  static_cast<vtkIdType>(points.size()));
700  for (unsigned int j = 0; j < points.size(); ++j) {
701  const vtkVector2i& v = points[j];
702  int pos[2] = {v[0], v[1]};
703  polygonPointsArray->SetTypedTuple(j, pos);
704  }
705 
706  // Handle polygon selection
707  switch (m_mode) {
709  selectPolygonCells(polygonPointsArray, selectionModifier);
710  break;
712  selectPolygonPoints(polygonPointsArray, selectionModifier);
713  break;
715  emit selectedCustomPolygon(polygonPointsArray);
716  break;
717  default:
718  break;
719  }
720 
721  // Polygon selection is complete - end the selection mode
722  // Reference: ParaView behavior - polygon selection auto-exits after
723  // completion
724  endSelection();
725  return;
726  }
727 
728  // For non-polygon modes, we need valid callData
729  if (!callData) {
731  "[cvRenderViewSelectionReaction] selectionChanged: callData is "
732  "null");
733  return;
734  }
735 
736  int* region = reinterpret_cast<int*>(callData);
737 
738  // Handle based on mode
739  // Reference: lines 550-598
740  switch (m_mode) {
742  selectCellsOnSurface(region, selectionModifier);
743  break;
744 
746  selectPointsOnSurface(region, selectionModifier);
747  break;
748 
749  // NOTE: Interactive modes are NOT handled here!
750  // They use LeftButtonReleaseEvent and are handled in
751  // onLeftButtonRelease() Reference: ParaView
752  // pqRenderViewSelectionReaction.cxx lines 938-1000
753 
755  selectFrustumCells(region, selectionModifier);
756  break;
757 
759  selectFrustumPoints(region, selectionModifier);
760  break;
761 
763  selectBlock(region, selectionModifier);
764  break;
765 
767  // Zoom handled separately
768  break;
769 
770  default:
771  break;
772  }
773 
774  // Only end selection for non-interactive modes
775  // Interactive modes should stay active to allow cumulative selection
776  // Reference: ParaView pqRenderViewSelectionReaction.cxx lines 602-612
777  bool isInteractiveMode =
782 
783  if (!isInteractiveMode) {
784  endSelection();
785  }
786 }
787 
788 //-----------------------------------------------------------------------------
790  // Reference: pqRenderViewSelectionReaction::onMouseMove()
791  // lines 606-642
792 
793  if (m_disablePreSelection) {
794  return;
795  }
796 
797  switch (m_mode) {
800  m_mouseMovingTimer.start(TOOLTIP_WAITING_TIME);
801  m_mouseMoving = true;
802  preSelection();
803  break;
804 
809  // Use preSelection for highlighting
810  preSelection();
811  break;
812 
816  // Polygon modes: Mouse events handled by
817  // vtkInteractorStyleDrawPolygon No action needed here
818  break;
819 
820  default:
822  "[cvRenderViewSelectionReaction] Invalid call to "
823  "onMouseMove");
824  break;
825  }
826 }
827 
828 //-----------------------------------------------------------------------------
830  // Record starting position for ZoomToBox
831  if (ActiveReaction != this) {
832  return;
833  }
834 
835  if (!m_interactor) {
836  return;
837  }
838 
839  // For ZoomToBox mode, track the start position
840  if (m_mode == SelectionMode::ZOOM_TO_BOX) {
841  int* pos = m_interactor->GetEventPosition();
842  if (pos) {
843  m_zoomStartPosition[0] = pos[0];
844  m_zoomStartPosition[1] = pos[1];
845  m_zoomTracking = true;
846  }
847  }
848 }
849 
850 //-----------------------------------------------------------------------------
852  // Reference: pqRenderViewSelectionReaction::onLeftButtonRelease()
853  // lines 938-1010
854 
855  if (ActiveReaction != this) {
856  return;
857  }
858 
859  if (!m_interactor) {
860  return;
861  }
862 
863  int x = m_interactor->GetEventPosition()[0];
864  int y = m_interactor->GetEventPosition()[1];
865 
866  if (x < 0 || y < 0) {
867  return;
868  }
869 
870  // Handle ZoomToBox mode
871  if (m_mode == SelectionMode::ZOOM_TO_BOX) {
872  if (m_zoomTracking) {
873  // Emit zoom completed signal
874  // Note: The actual zoom is performed by
875  // vtkInteractorStyleRubberBandZoom
876  int xmin = std::min(m_zoomStartPosition[0], x);
877  int ymin = std::min(m_zoomStartPosition[1], y);
878  int xmax = std::max(m_zoomStartPosition[0], x);
879  int ymax = std::max(m_zoomStartPosition[1], y);
880 
881  emit zoomToBoxCompleted(xmin, ymin, xmax, ymax);
882  m_zoomTracking = false;
883  }
884  // End selection after zoom is complete
885  endSelection();
886  return;
887  }
888 
889  int selectionModifier = getSelectionModifier();
890 
891  // In interactive mode, default is ADDITION (not replace)
892  // Reference: lines 958-962
893  if (selectionModifier ==
894  static_cast<int>(SelectionModifier::SELECTION_DEFAULT)) {
895  selectionModifier =
896  static_cast<int>(SelectionModifier::SELECTION_ADDITION);
897  }
898 
899  int region[4] = {x, y, x, y};
900 
901  switch (m_mode) {
904  selectCellsOnSurface(region, selectionModifier);
905  break;
906 
909  selectPointsOnSurface(region, selectionModifier);
910  break;
911 
912  default:
913  CVLog::Warning(QString("[cvRenderViewSelectionReaction::"
914  "onLeftButtonRelease] "
915  "Mode %1 not handled in switch")
916  .arg(static_cast<int>(m_mode)));
917  break;
918  }
919 }
920 
921 //-----------------------------------------------------------------------------
923  // Reference: pqRenderViewSelectionReaction::onWheelRotate()
924  // lines 1063-1069
925  if (ActiveReaction == this) {
926  // CRITICAL: Invalidate selection cache after zoom
927  // Camera changed, so cached selection buffer is no longer valid
929  if (pipeline) {
930  pipeline->invalidateCachedSelection();
931  }
932  onMouseMove();
933  }
934 }
935 
936 //-----------------------------------------------------------------------------
938  // Reference: pqRenderViewSelectionReaction::onRightButtonPressed()
939  // lines 1072-1075
940  m_disablePreSelection = true;
941 }
942 
943 //-----------------------------------------------------------------------------
945  // Reference: pqRenderViewSelectionReaction::onRightButtonRelease()
946  // lines 1077-1080
947  m_disablePreSelection = false;
948 
949  // CRITICAL: Invalidate selection cache after rotate/zoom
950  // Camera changed, so cached selection buffer is no longer valid
952  if (pipeline) {
953  pipeline->invalidateCachedSelection();
954  }
955 }
956 
957 //-----------------------------------------------------------------------------
959  // Middle button is used for panning in vtkInteractorStyleRubberBand3D
960  m_disablePreSelection = true;
961 }
962 
963 //-----------------------------------------------------------------------------
965  // Middle button released after panning
966  m_disablePreSelection = false;
967 
968  // CRITICAL: Invalidate selection cache after pan
969  // Camera changed, so cached selection buffer is no longer valid
971  if (pipeline) {
972  pipeline->invalidateCachedSelection();
973  }
974 }
975 
976 //-----------------------------------------------------------------------------
978  // Reference: pqRenderViewSelectionReaction::preSelection()
979  // lines 645-765
980 
981  if (ActiveReaction != this || !m_interactor || !m_renderer) {
982  return;
983  }
984 
985  int x = m_interactor->GetEventPosition()[0];
986  int y = m_interactor->GetEventPosition()[1];
987  int* size = m_interactor->GetSize();
988 
989  m_mousePosition[0] = x;
990  m_mousePosition[1] = y;
991 
992  // Check bounds
993  if (x < 0 || y < 0 || x >= size[0] || y >= size[1]) {
994  // Hide highlight when cursor is out of view
996  if (highlighter) {
997  highlighter->clearHoverHighlight();
998  }
999  updateTooltip();
1000  return;
1001  }
1002 
1003  // Perform single-pixel selection for highlighting
1005  if (!pipeline) {
1006  return;
1007  }
1008 
1009  bool selectCells = isSelectingCells();
1011  pipeline->getPixelSelectionInfo(x, y, selectCells);
1012 
1013  if (info.valid && info.attributeID >= 0) {
1014  m_hoveredId = info.attributeID;
1015  m_currentPolyData = info.polyData;
1016 
1017  // Show highlight using highlightElement
1019  if (highlighter && info.polyData) {
1020  // fieldAssociation: 0 for cells, 1 for points
1021  int fieldAssociation = selectCells ? 0 : 1;
1022  highlighter->highlightElement(info.polyData, info.attributeID,
1023  fieldAssociation);
1024  }
1025  } else {
1026  m_hoveredId = -1;
1027  m_currentPolyData = nullptr;
1028 
1030  if (highlighter) {
1031  highlighter->clearHoverHighlight();
1032  }
1033  }
1034 
1035  // ParaView-style: Trigger render after selection update
1036  // Reference: pqRenderViewSelectionReaction::fastPreSelection() line 871
1037  if (m_interactor) {
1038  vtkRenderWindow* renWin = m_interactor->GetRenderWindow();
1039  if (renWin) {
1040  renWin->Render();
1041  }
1042  }
1043 
1044  updateTooltip();
1045 }
1046 
1047 //-----------------------------------------------------------------------------
1049  // Reference: pqRenderViewSelectionReaction::fastPreSelection()
1050  // lines 768-872
1051 
1052  // For now, use regular preSelection
1053  // Fast pre-selection optimization can be added later
1054  preSelection();
1055 }
1056 
1057 //-----------------------------------------------------------------------------
1059  // Reference: pqRenderViewSelectionReaction::UpdateTooltip()
1060  // lines 891-935
1061 
1062  if (!isTooltipMode()) {
1063  return;
1064  }
1065 
1066  // Don't show tooltip while mouse is moving
1067  if (m_mouseMoving) {
1068  QToolTip::hideText();
1069  return;
1070  }
1071 
1072  // Check if we have valid hover data
1073  if (m_hoveredId < 0) {
1074  QToolTip::hideText();
1075  return;
1076  }
1077 
1078  if (!m_currentPolyData) {
1079  QToolTip::hideText();
1080  return;
1081  }
1082 
1083  // Build tooltip text
1084  QString tooltipText;
1085  bool selectCells = isSelectingCells();
1086 
1087  // Helper lambda to format array values for tooltip
1088  // ParaView reference: vtkSMTooltipSelectionPipeline.cxx lines 246-264
1089  // ParaView shows ALL components for multi-component arrays in format:
1090  // "Name: (v1, v2, ...)"
1091  auto formatArrayValue = [](vtkDataArray* arr, vtkIdType id) -> QString {
1092  if (!arr || !arr->GetName()) return QString();
1093 
1094  QString name = QString::fromUtf8(arr->GetName());
1095  int numComp = arr->GetNumberOfComponents();
1096 
1097  // Skip internal VTK arrays (ParaView skips vtkOriginalPointIds/CellIds)
1098  if (name.startsWith("vtk", Qt::CaseInsensitive) ||
1099  name == "vtkOriginalPointIds" || name == "vtkOriginalCellIds") {
1100  return QString();
1101  }
1102 
1103  // ParaView format: always show all components
1104  // Single component: "Name: value"
1105  // Multi-component: "Name: (v1, v2, ...)"
1106  if (numComp == 1) {
1107  double value = arr->GetTuple1(id);
1108  return QString("\n %1: %2").arg(name).arg(value, 0, 'g', 6);
1109  }
1110 
1111  // Multi-component arrays - check for special handling
1112  double* tuple = arr->GetTuple(id);
1113 
1114  // RGB/RGBA colors: show as integer values
1115  if ((numComp == 3 || numComp == 4) &&
1116  (name.compare("RGB", Qt::CaseInsensitive) == 0 ||
1117  name.compare("RGBA", Qt::CaseInsensitive) == 0 ||
1118  name.compare("Colors", Qt::CaseInsensitive) == 0)) {
1119  QString result = QString("\n %1: (").arg(name);
1120  for (int i = 0; i < numComp; ++i) {
1121  if (i > 0) result += ", ";
1122  result += QString::number(static_cast<int>(tuple[i]));
1123  }
1124  result += ")";
1125  return result;
1126  }
1127 
1128  // All other multi-component arrays (Normals, TCoords, etc.)
1129  // ParaView shows all components with full precision
1130  QString result = QString("\n %1: (").arg(name);
1131  for (int i = 0; i < numComp; ++i) {
1132  if (i > 0) result += ", ";
1133  result += QString::number(tuple[i], 'g', 6);
1134  }
1135  result += ")";
1136  return result;
1137  };
1138 
1139  // Get dataset name from FieldData (ParaView-style)
1140  QString datasetName;
1141  vtkFieldData* fieldData = m_currentPolyData->GetFieldData();
1142  if (fieldData) {
1143  vtkStringArray* nameArray = vtkStringArray::SafeDownCast(
1144  fieldData->GetAbstractArray("DatasetName"));
1145  if (nameArray && nameArray->GetNumberOfTuples() > 0) {
1146  datasetName = QString::fromStdString(nameArray->GetValue(0));
1147  }
1148  }
1149 
1150  if (selectCells) {
1151  vtkIdType numCells = m_currentPolyData->GetNumberOfCells();
1152  if (m_hoveredId >= 0 && m_hoveredId < numCells) {
1153  // ParaView format: Dataset name first (if available)
1154  if (!datasetName.isEmpty()) {
1155  tooltipText = datasetName;
1156  tooltipText += QString("\n Id: %1").arg(m_hoveredId);
1157  } else {
1158  tooltipText = QString("Cell ID: %1").arg(m_hoveredId);
1159  }
1160 
1161  // Add cell type (ParaView-style)
1162  vtkCell* cell = m_currentPolyData->GetCell(m_hoveredId);
1163  if (cell) {
1164  QString cellType;
1165  switch (cell->GetCellType()) {
1166  case VTK_VERTEX:
1167  cellType = "Vertex";
1168  break;
1169  case VTK_POLY_VERTEX:
1170  cellType = "Poly Vertex";
1171  break;
1172  case VTK_LINE:
1173  cellType = "Line";
1174  break;
1175  case VTK_POLY_LINE:
1176  cellType = "Poly Line";
1177  break;
1178  case VTK_TRIANGLE:
1179  cellType = "Triangle";
1180  break;
1181  case VTK_TRIANGLE_STRIP:
1182  cellType = "Triangle Strip";
1183  break;
1184  case VTK_POLYGON:
1185  cellType = "Polygon";
1186  break;
1187  case VTK_QUAD:
1188  cellType = "Quad";
1189  break;
1190  case VTK_TETRA:
1191  cellType = "Tetra";
1192  break;
1193  case VTK_HEXAHEDRON:
1194  cellType = "Hexahedron";
1195  break;
1196  default:
1197  cellType = QString("Type %1").arg(cell->GetCellType());
1198  break;
1199  }
1200  tooltipText += QString("\n Type: %1").arg(cellType);
1201 
1202  // Add number of points
1203  vtkIdType npts = cell->GetNumberOfPoints();
1204  tooltipText += QString("\n Number of Points: %1").arg(npts);
1205 
1206  // Add cell center
1207  if (npts > 0) {
1208  double center[3] = {0, 0, 0};
1209  for (vtkIdType i = 0; i < npts; ++i) {
1210  double pt[3];
1211  m_currentPolyData->GetPoint(cell->GetPointId(i), pt);
1212  center[0] += pt[0];
1213  center[1] += pt[1];
1214  center[2] += pt[2];
1215  }
1216  center[0] /= npts;
1217  center[1] /= npts;
1218  center[2] /= npts;
1219  tooltipText += QString("\n Center: (%1, %2, %3)")
1220  .arg(center[0], 0, 'g', 6)
1221  .arg(center[1], 0, 'g', 6)
1222  .arg(center[2], 0, 'g', 6);
1223  }
1224  }
1225 
1226  // Add cell data arrays to tooltip
1227  // Track shown color arrays to avoid duplicates (RGB and Colors are
1228  // the same)
1229  vtkCellData* cellData = m_currentPolyData->GetCellData();
1230  if (cellData) {
1231  bool hasShownColorArray = false;
1232  for (int i = 0; i < cellData->GetNumberOfArrays(); ++i) {
1233  vtkDataArray* arr = cellData->GetArray(i);
1234  if (!arr || !arr->GetName()) continue;
1235 
1236  QString arrName = QString::fromUtf8(arr->GetName());
1237  int numComp = arr->GetNumberOfComponents();
1238 
1239  // Skip duplicate color arrays (RGB and Colors are the same
1240  // thing)
1241  if ((numComp == 3 || numComp == 4) &&
1242  (arrName.compare("RGB", Qt::CaseInsensitive) == 0 ||
1243  arrName.compare("RGBA", Qt::CaseInsensitive) == 0 ||
1244  arrName.compare("Colors", Qt::CaseInsensitive) == 0)) {
1245  if (hasShownColorArray) {
1246  continue; // Skip duplicate color array
1247  }
1248  hasShownColorArray = true;
1249  }
1250 
1251  QString arrayText = formatArrayValue(arr, m_hoveredId);
1252  if (!arrayText.isEmpty()) {
1253  tooltipText += arrayText;
1254  }
1255  }
1256  }
1257  }
1258  } else {
1259  vtkIdType numPoints = m_currentPolyData->GetNumberOfPoints();
1260  if (m_hoveredId >= 0 && m_hoveredId < numPoints) {
1261  double pt[3];
1262  m_currentPolyData->GetPoint(m_hoveredId, pt);
1263 
1264  // ParaView format: Dataset name first (if available)
1265  if (!datasetName.isEmpty()) {
1266  tooltipText = datasetName;
1267  tooltipText += QString("\n Id: %1").arg(m_hoveredId);
1268  tooltipText += QString("\n Coords: (%1, %2, %3)")
1269  .arg(pt[0], 0, 'g', 6)
1270  .arg(pt[1], 0, 'g', 6)
1271  .arg(pt[2], 0, 'g', 6);
1272  } else {
1273  tooltipText = QString("Point ID: %1\nPosition: (%2, %3, %4)")
1274  .arg(m_hoveredId)
1275  .arg(pt[0], 0, 'f', 6)
1276  .arg(pt[1], 0, 'f', 6)
1277  .arg(pt[2], 0, 'f', 6);
1278  }
1279 
1280  // Add point data arrays to tooltip
1281  // Track shown color arrays to avoid duplicates (RGB and Colors are
1282  // the same)
1283  vtkPointData* pointData = m_currentPolyData->GetPointData();
1284  if (pointData) {
1285  bool hasShownColorArray = false;
1286  for (int i = 0; i < pointData->GetNumberOfArrays(); ++i) {
1287  vtkDataArray* arr = pointData->GetArray(i);
1288  if (!arr || !arr->GetName()) continue;
1289 
1290  QString arrName = QString::fromUtf8(arr->GetName());
1291  int numComp = arr->GetNumberOfComponents();
1292 
1293  // Skip duplicate color arrays (RGB and Colors are the same
1294  // thing)
1295  if ((numComp == 3 || numComp == 4) &&
1296  (arrName.compare("RGB", Qt::CaseInsensitive) == 0 ||
1297  arrName.compare("RGBA", Qt::CaseInsensitive) == 0 ||
1298  arrName.compare("Colors", Qt::CaseInsensitive) == 0)) {
1299  if (hasShownColorArray) {
1300  continue; // Skip duplicate color array
1301  }
1302  hasShownColorArray = true;
1303  }
1304 
1305  QString arrayText = formatArrayValue(arr, m_hoveredId);
1306  if (!arrayText.isEmpty()) {
1307  tooltipText += arrayText;
1308  }
1309  }
1310  }
1311  }
1312  }
1313 
1314  if (!tooltipText.isEmpty()) {
1315  // Get widget position for tooltip
1316  // Reference: ParaView uses DPI scaling - we should too
1317  QWidget* widget = ecvDisplayTools::GetCurrentScreen();
1318  if (widget) {
1319  // Take DPI scaling into account (ParaView-style)
1320  qreal dpr = widget->devicePixelRatioF();
1321 
1322  // Convert VTK coordinates (origin bottom-left) to Qt coordinates
1323  // (origin top-left) VTK Y increases upward, Qt Y increases downward
1324  QPoint localPos(static_cast<int>(m_mousePosition[0] / dpr),
1325  widget->height() -
1326  static_cast<int>(m_mousePosition[1] / dpr));
1327  QPoint globalPos = widget->mapToGlobal(localPos);
1328 
1329  QToolTip::showText(globalPos, tooltipText);
1330 
1331  // Copy to clipboard mechanism (ParaView-style)
1332  // Reference: pqRenderViewSelectionReaction.cxx line 924-926
1333  // ParaView sets PlainTooltipText first, then enables shortcut and
1334  // sets key
1335  m_plainTooltipText = tooltipText;
1336  if (m_copyTooltipShortcut) {
1337  m_copyTooltipShortcut->setEnabled(true);
1338  m_copyTooltipShortcut->setKey(QKeySequence::Copy);
1339  }
1340  }
1341  } else {
1342  QToolTip::hideText();
1343  // Disable copy tooltip shortcut when no tooltip
1344  // Reference: pqRenderViewSelectionReaction.cxx line 930-931
1345  if (m_copyTooltipShortcut) {
1346  m_copyTooltipShortcut->setKey(QKeySequence());
1347  m_copyTooltipShortcut->setEnabled(false);
1348  }
1349  }
1350 }
1351 
1352 //-----------------------------------------------------------------------------
1354  // Reference: pqSelectionReaction::getSelectionModifier() and
1355  // pqRenderViewSelectionReaction::getSelectionModifier()
1356  // lines 1013-1035
1357 
1358  // First check modifier group
1359  // IMPORTANT: We cannot use QActionGroup::checkedAction() since the
1360  // ModifierGroup may not be exclusive (exclusive=false). We need to iterate
1361  // through all actions to find which one is checked, following ParaView's
1362  // pqSelectionReaction pattern.
1363  int selectionModifier =
1364  static_cast<int>(SelectionModifier::SELECTION_DEFAULT);
1365 
1366  if (m_modifierGroup) {
1367  // ParaView pattern: iterate through all actions in the group
1368  for (QAction* maction : m_modifierGroup->actions()) {
1369  if (maction && maction->isChecked() && maction->data().isValid()) {
1370  selectionModifier = maction->data().toInt();
1372  QString("[getSelectionModifier] From modifierGroup: "
1373  "action='%1', modifier=%2")
1374  .arg(maction->text())
1375  .arg(selectionModifier));
1376  break; // Only one should be checked at a time
1377  }
1378  }
1379  }
1380 
1381  // Check keyboard modifiers (override button selection)
1382  if (m_interactor) {
1383  bool ctrl = m_interactor->GetControlKey() == 1;
1384  bool shift = m_interactor->GetShiftKey() == 1;
1385 
1386  if (ctrl && shift) {
1387  selectionModifier =
1388  static_cast<int>(SelectionModifier::SELECTION_TOGGLE);
1389  } else if (ctrl) {
1390  selectionModifier =
1391  static_cast<int>(SelectionModifier::SELECTION_ADDITION);
1392  } else if (shift) {
1393  selectionModifier =
1394  static_cast<int>(SelectionModifier::SELECTION_SUBTRACTION);
1395  }
1396  }
1397 
1398  return selectionModifier;
1399 }
1400 
1401 //-----------------------------------------------------------------------------
1403  // Reference: pqRenderViewSelectionReaction::isCompatible()
1404  // lines 1037-1060
1405 
1406  if (m_mode == otherMode) {
1407  return true;
1408  }
1409 
1410  // Cell selection modes are compatible with each other
1411  if ((m_mode == SelectionMode::SELECT_SURFACE_CELLS ||
1415  (otherMode == SelectionMode::SELECT_SURFACE_CELLS ||
1418  otherMode == SelectionMode::SELECT_FRUSTUM_CELLS)) {
1419  return true;
1420  }
1421 
1422  // Point selection modes are compatible with each other
1423  if ((m_mode == SelectionMode::SELECT_SURFACE_POINTS ||
1427  (otherMode == SelectionMode::SELECT_SURFACE_POINTS ||
1430  otherMode == SelectionMode::SELECT_FRUSTUM_POINTS)) {
1431  return true;
1432  }
1433 
1434  return false;
1435 }
1436 
1437 //-----------------------------------------------------------------------------
1439  // Reference: pqRenderViewSelectionReaction::cleanupObservers()
1440  // lines 130-141
1441  // CRITICAL: Remove observers from their respective objects
1442  // m_observedObject is typically m_interactor for most events
1443  // m_styleObserverId is used for SelectionChangedEvent on m_selectionStyle
1444 
1445  // Remove observers from the main observed object (interactor)
1446  for (size_t i = 0; i < sizeof(m_observerIds) / sizeof(m_observerIds[0]);
1447  ++i) {
1448  if (m_observerIds[i] > 0 && m_observedObject) {
1449  m_observedObject->RemoveObserver(m_observerIds[i]);
1450  }
1451  m_observerIds[i] = 0;
1452  }
1453 
1454  // Remove observer from selection style separately (if any)
1455  if (m_styleObserverId > 0 && m_selectionStyle) {
1456  m_selectionStyle->RemoveObserver(m_styleObserverId);
1457  m_styleObserverId = 0;
1458  }
1459 
1460  m_observedObject = nullptr;
1461 }
1462 
1463 //-----------------------------------------------------------------------------
1465  if (m_modifierGroup) {
1466  QAction* checkedAction = m_modifierGroup->checkedAction();
1467  if (checkedAction) {
1468  checkedAction->setChecked(false);
1469  }
1470  }
1471 }
1472 
1473 //-----------------------------------------------------------------------------
1474 // Selection execution methods
1475 //-----------------------------------------------------------------------------
1476 
1478  int region[4], int selectionModifier) {
1480  if (!pipeline) {
1482  "[cvRenderViewSelectionReaction::selectCellsOnSurface] "
1483  "No pipeline available");
1484  return;
1485  }
1486 
1487  // CRITICAL: Temporarily hide highlight actors before hardware selection
1488  // This prevents the highlight actor from occluding the original data in
1489  // the depth buffer, which would cause subtract selection to select wrong
1490  // points.
1492  if (highlighter) {
1493  highlighter->setHighlightsVisible(false);
1494  }
1495 
1496  cvSelectionData selection = pipeline->selectCellsOnSurface(region);
1497 
1498  // Restore highlight visibility
1499  if (highlighter) {
1500  highlighter->setHighlightsVisible(true);
1501  }
1502 
1503  finalizeSelection(selection, selectionModifier, "SurfaceCells");
1504 }
1505 
1506 //-----------------------------------------------------------------------------
1508  int region[4], int selectionModifier) {
1510  if (!pipeline) {
1512  "[cvRenderViewSelectionReaction::selectPointsOnSurface] "
1513  "No pipeline available");
1514  return;
1515  }
1516 
1517  // CRITICAL: Temporarily hide highlight actors before hardware selection
1518  // This prevents the highlight actor from occluding the original data in
1519  // the depth buffer, which would cause subtract selection to select wrong
1520  // points.
1522  if (highlighter) {
1523  highlighter->setHighlightsVisible(false);
1524  }
1525 
1526  cvSelectionData selection = pipeline->selectPointsOnSurface(region);
1527 
1528  // Restore highlight visibility
1529  if (highlighter) {
1530  highlighter->setHighlightsVisible(true);
1531  }
1532 
1533  finalizeSelection(selection, selectionModifier, "SurfacePoints");
1534 }
1535 
1536 //-----------------------------------------------------------------------------
1538  int selectionModifier) {
1539  // Frustum selection implementation
1540  // For now, use surface selection as fallback
1541  selectCellsOnSurface(region, selectionModifier);
1542 }
1543 
1544 //-----------------------------------------------------------------------------
1546  int selectionModifier) {
1547  // Frustum selection implementation
1548  // For now, use surface selection as fallback
1549  selectPointsOnSurface(region, selectionModifier);
1550 }
1551 
1552 //-----------------------------------------------------------------------------
1553 // Unified selection finalization (ParaView-style)
1554 // This method handles combining with existing selection and updating highlights
1555 //-----------------------------------------------------------------------------
1557  const cvSelectionData& newSelection,
1558  int selectionModifier,
1559  const QString& description) {
1560  Q_UNUSED(description);
1561 
1562  if (newSelection.isEmpty()) {
1563  return;
1564  }
1565 
1567  if (!manager) {
1568  return;
1569  }
1570  cvSelectionData currentSel = manager->currentSelection();
1571 
1572  // Combine with existing selection based on modifier
1574  currentSel, newSelection,
1576  selectionModifier));
1577 
1578  // Update source object from PCLVis for direct extraction
1579  // This allows extraction operations to use the original ccPointCloud/ccMesh
1580  // instead of converting from VTK polydata
1581  PclUtils::PCLVis* pclVis = getPCLVis();
1582  if (pclVis && combined.hasActorInfo()) {
1583  // Get viewID from the primary (front-most) actor
1584  vtkActor* primaryActor = combined.primaryActor();
1585  if (primaryActor) {
1586  std::string viewID = pclVis->getIdByActor(primaryActor);
1587  if (!viewID.empty()) {
1588  ccHObject* sourceObj = pclVis->getSourceObject(viewID);
1589  if (sourceObj) {
1590  manager->setSourceObject(sourceObj);
1591  } else {
1593  QString("[finalizeSelection] No source object "
1594  "found "
1595  "for viewID='%1'")
1596  .arg(QString::fromStdString(viewID)));
1597  }
1598  } else {
1600  "[finalizeSelection] Could not find viewID for "
1601  "primary actor");
1602  }
1603  }
1604  }
1605 
1606  // Update manager's current selection
1607  manager->setCurrentSelection(combined);
1608 
1609  // Update persistent SELECTED highlight (ParaView-style)
1610  // This shows accumulated selection as a persistent visual indicator
1612  if (highlighter && !combined.isEmpty()) {
1613  highlighter->highlightSelection(combined,
1615  } else if (highlighter && combined.isEmpty()) {
1616  // Clear highlight if selection is empty
1617  highlighter->clearHighlights();
1618  }
1619 
1620  emit selectionFinished(combined);
1621 }
1622 
1623 //-----------------------------------------------------------------------------
1625  int selectionModifier) {
1627  if (!pipeline || !polygon) return;
1628 
1629  // CRITICAL: Temporarily hide highlight actors before hardware selection
1631  if (highlighter) {
1632  highlighter->setHighlightsVisible(false);
1633  }
1634 
1635  cvSelectionData selection = pipeline->selectCellsInPolygon(polygon);
1636 
1637  if (highlighter) {
1638  highlighter->setHighlightsVisible(true);
1639  }
1640 
1641  finalizeSelection(selection, selectionModifier, "PolygonCells");
1642 }
1643 
1644 //-----------------------------------------------------------------------------
1646  int selectionModifier) {
1648  if (!pipeline || !polygon) return;
1649 
1650  // CRITICAL: Temporarily hide highlight actors before hardware selection
1652  if (highlighter) {
1653  highlighter->setHighlightsVisible(false);
1654  }
1655 
1656  cvSelectionData selection = pipeline->selectPointsInPolygon(polygon);
1657 
1658  if (highlighter) {
1659  highlighter->setHighlightsVisible(true);
1660  }
1661 
1662  finalizeSelection(selection, selectionModifier, "PolygonPoints");
1663 }
1664 
1665 //-----------------------------------------------------------------------------
1667  int selectionModifier) {
1668  // Block selection - for now delegate to cell selection
1669  selectCellsOnSurface(region, selectionModifier);
1670 }
1671 
1672 //-----------------------------------------------------------------------------
1673 // Internal helpers
1674 //-----------------------------------------------------------------------------
1675 
1677  if (!m_viewer) return nullptr;
1678 
1679  // Dynamic cast to PCLVis
1680  return dynamic_cast<PclUtils::PCLVis*>(m_viewer);
1681 }
1682 
1683 //-----------------------------------------------------------------------------
1685  const {
1687  return manager ? manager->getPipeline() : nullptr;
1688 }
1689 
1690 //-----------------------------------------------------------------------------
1692  const {
1694  return manager ? manager->getHighlighter() : nullptr;
1695 }
1696 
1697 //-----------------------------------------------------------------------------
1699  switch (m_mode) {
1708  return true;
1709  default:
1710  return false;
1711  }
1712 }
1713 
1714 //-----------------------------------------------------------------------------
1716  switch (m_mode) {
1721  return true;
1722  default:
1723  return false;
1724  }
1725 }
1726 
1727 //-----------------------------------------------------------------------------
1729  return m_mode == SelectionMode::HOVER_POINTS_TOOLTIP ||
1731 }
1732 
1733 //-----------------------------------------------------------------------------
1734 void cvRenderViewSelectionReaction::setCursor(const QCursor& cursor) {
1735  QWidget* widget = ecvDisplayTools::GetCurrentScreen();
1736  if (widget) {
1737  widget->setCursor(cursor);
1738  }
1739 }
1740 
1741 //-----------------------------------------------------------------------------
1743  QWidget* widget = ecvDisplayTools::GetCurrentScreen();
1744  if (widget) {
1745  widget->setCursor(Qt::ArrowCursor); // Explicitly set arrow cursor
1746  widget->unsetCursor(); // Also unset to remove any override
1747  }
1748 }
1749 
1750 //-----------------------------------------------------------------------------
1752  if (m_interactor) {
1753  vtkInteractorObserver* currentStyle =
1754  m_interactor->GetInteractorStyle();
1755  vtkInteractorStyle* style =
1756  vtkInteractorStyle::SafeDownCast(currentStyle);
1757 
1758  // Don't store selection-specific styles as the "previous" style
1759  // This prevents issues when switching between selection modes
1760  if (style) {
1761  // Check if current style is a selection style (RubberBand3D,
1762  // RubberBandZoom, DrawPolygon) - these should not be stored as
1763  // "previous"
1764  bool isSelectionStyle =
1765  (vtkInteractorStyleRubberBand3D::SafeDownCast(style) !=
1766  nullptr) ||
1767  (vtkInteractorStyleRubberBandZoom::SafeDownCast(style) !=
1768  nullptr) ||
1769  (vtkInteractorStyleDrawPolygon::SafeDownCast(style) !=
1770  nullptr);
1771 
1772  if (!isSelectionStyle) {
1773  m_previousStyle = style;
1774  CVLog::PrintVerbose(QString("[storeCurrentStyle] Stored "
1775  "non-selection style: %1")
1776  .arg(style->GetClassName()));
1777  }
1778  }
1779  }
1780 }
1781 
1782 //-----------------------------------------------------------------------------
1784  if (m_interactor && m_previousStyle) {
1785  m_interactor->SetInteractorStyle(m_previousStyle);
1786  m_previousStyle = nullptr;
1787  } else if (m_interactor) {
1788  // Fallback: If no previous style saved, try to get PCLVis's default
1789  // style
1790  PclUtils::PCLVis* pclVis = getPCLVis();
1791  if (pclVis) {
1792  // PCLVis typically uses vtkInteractorStyleTrackballCamera
1793  auto defaultStyle =
1795  if (m_renderer) {
1796  defaultStyle->SetDefaultRenderer(m_renderer);
1797  }
1798  m_interactor->SetInteractorStyle(defaultStyle);
1799  }
1800  }
1801  m_selectionStyle = nullptr;
1802 }
1803 
1804 //-----------------------------------------------------------------------------
1806  bool renderOnMouseMove) {
1807  // Helper: Create and set vtkInteractorStyleRubberBand3D
1808  // This style: Left=RubberBand, Middle=Pan, Right=Zoom (NO rotation!)
1810  if (!renderOnMouseMove) {
1811  style->RenderOnMouseMoveOff();
1812  }
1813  if (m_renderer) {
1814  style->SetDefaultRenderer(m_renderer);
1815  }
1816  m_selectionStyle = style;
1817  m_interactor->SetInteractorStyle(style);
1818 }
1819 
1820 //-----------------------------------------------------------------------------
1822  // Reference: pqRenderViewSelectionReaction::beginSelection() lines 338-437
1823  // ParaView uses INTERACTION_MODE_SELECTION which maps to
1824  // vtkInteractorStyleRubberBand3D This style: Left=RubberBand, Middle=Pan,
1825  // Right=Zoom (NO rotation!)
1826 
1827  if (!m_interactor) return;
1829 
1830  switch (m_mode) {
1837  // Rectangle selection - cross cursor
1838  setCursor(Qt::CrossCursor);
1839  setRubberBand3DStyle(false); // No RenderOnMouseMove
1840  break;
1841 
1846  // Interactive selection - cross cursor, no render on mouse move
1847  setCursor(Qt::CrossCursor);
1848  setRubberBand3DStyle(false);
1849  break;
1850 
1853  // Tooltip mode - default arrow cursor, no render on mouse move
1854  unsetCursor();
1855  setRubberBand3DStyle(false);
1856  break;
1857 
1859  setCursor(m_zoomCursor);
1860  auto zoomStyle =
1862  m_selectionStyle = zoomStyle;
1863  m_interactor->SetInteractorStyle(zoomStyle);
1864  break;
1865  }
1866 
1870  // Polygon selection
1871  setCursor(Qt::PointingHandCursor);
1874  if (m_renderer) {
1875  polygonStyle->SetDefaultRenderer(m_renderer);
1876  }
1877  m_selectionStyle = polygonStyle;
1878  m_interactor->SetInteractorStyle(polygonStyle);
1879  break;
1880  }
1881 
1882  default:
1883  break;
1884  }
1885 }
1886 
1887 //-----------------------------------------------------------------------------
1889  // Reference: pqRenderViewSelectionReaction::beginSelection() lines 439-491
1890  if (!m_interactor) return;
1891 
1892  m_observedObject = m_interactor;
1893 
1894  switch (m_mode) {
1895  // One-shot commands - no observers needed
1896  // Reference: lines 449-452
1900  break;
1901 
1903  m_observerIds[0] = m_interactor->AddObserver(
1904  vtkCommand::LeftButtonPressEvent, this,
1906  m_observerIds[1] = m_interactor->AddObserver(
1907  vtkCommand::LeftButtonReleaseEvent, this,
1909  break;
1910 
1915  // Interactive selection: MouseMove + LeftButtonRelease for click
1916  // selection Plus camera movement observers for cache invalidation
1917  m_observerIds[0] = m_interactor->AddObserver(
1918  vtkCommand::MouseMoveEvent, this,
1920  m_observerIds[1] = m_interactor->AddObserver(
1921  vtkCommand::LeftButtonReleaseEvent, this,
1923  addCameraMovementObservers(2); // Start from index 2
1924  break;
1925 
1928  // Tooltip mode: MouseMove only, plus camera movement observers
1929  // No LeftButtonRelease - tooltip doesn't do selection
1930  m_observerIds[0] = m_interactor->AddObserver(
1931  vtkCommand::MouseMoveEvent, this,
1933  addCameraMovementObservers(1); // Start from index 1
1934  break;
1935 
1936  default:
1937  // Rectangle selection: SelectionChangedEvent on style for
1938  // rubber-band completion Plus camera movement observers for cache
1939  // invalidation
1940  if (m_selectionStyle) {
1941  m_styleObserverId = m_selectionStyle->AddObserver(
1942  vtkCommand::SelectionChangedEvent, this,
1944  addCameraMovementObservers(0); // Start from index 0
1945  }
1946  break;
1947  }
1948 }
1949 
1950 //-----------------------------------------------------------------------------
1952  // Add observers for camera movements that require selection cache
1953  // invalidation vtkInteractorStyleRubberBand3D: Middle=Pan, Right=Zoom,
1954  // Wheel=Zoom
1955  if (!m_interactor) return;
1956 
1957  m_observerIds[startIndex++] = m_interactor->AddObserver(
1958  vtkCommand::MouseWheelForwardEvent, this,
1960  m_observerIds[startIndex++] = m_interactor->AddObserver(
1961  vtkCommand::MouseWheelBackwardEvent, this,
1963  m_observerIds[startIndex++] = m_interactor->AddObserver(
1964  vtkCommand::RightButtonPressEvent, this,
1966  m_observerIds[startIndex++] = m_interactor->AddObserver(
1967  vtkCommand::RightButtonReleaseEvent, this,
1969  m_observerIds[startIndex++] = m_interactor->AddObserver(
1970  vtkCommand::MiddleButtonPressEvent, this,
1972  m_observerIds[startIndex] = m_interactor->AddObserver(
1973  vtkCommand::MiddleButtonReleaseEvent, this,
1975 }
1976 
1977 //-----------------------------------------------------------------------------
1979  // Reference: pqRenderViewSelectionReaction::beginSelection()
1980  // Shows instruction dialogs for interactive/tooltip modes
1981 
1982  QString settingsKey, title, message;
1983 
1984  switch (m_mode) {
1986  settingsKey = "pqTooltipSelection";
1987  title = tr("Tooltip Selection Information");
1988  message = tr(
1989  "You are entering tooltip selection mode to display cell "
1990  "information. "
1991  "Simply move the mouse point over the dataset to "
1992  "interactively highlight "
1993  "cells and display a tooltip with cell "
1994  "information.\n\n"
1995  "Use the 'Esc' key or the same toolbar button to exit this "
1996  "mode.");
1997  break;
1998 
2000  settingsKey = "pqTooltipSelection";
2001  title = tr("Tooltip Selection Information");
2002  message =
2003  tr("You are entering tooltip selection mode to display "
2004  "points information. "
2005  "Simply move the mouse point over the dataset to "
2006  "interactively highlight "
2007  "points and display a tooltip with points "
2008  "information.\n\n"
2009  "Use the 'Esc' key or the same toolbar button to exit "
2010  "this mode.");
2011  break;
2012 
2015  settingsKey = "pqInteractiveSelection";
2016  title = tr("Interactive Selection Information");
2017  message =
2018  tr("You are entering interactive selection mode to "
2019  "highlight cells (or points). "
2020  "Simply move the mouse point over the dataset to "
2021  "interactively highlight elements.\n\n"
2022  "To add the currently highlighted element to the active "
2023  "selection, simply click on that element.\n\n"
2024  "You can click on selection modifier button or use "
2025  "modifier keys to subtract or "
2026  "even toggle the selection. Click outside of mesh to "
2027  "clear selection.\n\n"
2028  "Use the 'Esc' key or the same toolbar button to exit "
2029  "this mode.");
2030  break;
2031 
2032  default:
2033  // No dialog for other modes
2034  return;
2035  }
2036 
2037  // Use helper to show dialog (respects "don't show again" setting)
2038  if (!settingsKey.isEmpty()) {
2039  cvSelectionPipeline::promptUser(settingsKey, title, message, nullptr);
2040  }
2041 }
int size
std::string name
int points
boost::geometry::model::polygon< point_xy > polygon
Definition: TreeIso.cpp:37
core::Tensor result
Definition: VtkUtils.cpp:76
static bool Warning(const char *format,...)
Prints out a formatted warning message in console.
Definition: CVLog.cpp:133
static bool PrintVerbose(const char *format,...)
Prints out a verbose formatted message in console.
Definition: CVLog.cpp:103
vtkRenderer * getCurrentRenderer(int viewport=0)
Definition: PCLVis.cpp:2924
bool isPointPickingEnabled()
Definition: PCLVis.h:667
std::string getIdByActor(vtkProp *actor)
Definition: PCLVis.cpp:3079
ccHObject * getSourceObject(const std::string &viewID) const
Get the source object for a given viewID.
Definition: PCLVis.cpp:2425
vtkSmartPointer< vtkRenderWindowInteractor > getRenderWindowInteractor()
Get a pointer to the current interactor style used.
Definition: PCLVis.h:148
void setPointPickingEnabled(bool state)
Definition: PCLVis.h:668
Hierarchical CLOUDVIEWER Object.
Definition: ecvHObject.h:25
virtual void onLeftButtonRelease()
Handle left button release (for interactive selection)
Q_INVOKABLE void handleRightButtonRelease()
Handle right button release (public for callback access)
bool isTooltipMode() const
Check if this is a tooltip mode.
void vtkOnRightButtonPressed(vtkObject *caller, unsigned long eventId, void *callData)
void zoomToBoxCompleted(int xmin, int ymin, int xmax, int ymax)
Emitted when zoom to box is completed.
void selectPolygonCells(vtkIntArray *polygon, int selectionModifier)
Select cells in polygon.
Q_INVOKABLE void handleWheelRotate()
Handle wheel rotation (public for callback access)
void vtkOnSelectionChanged(vtkObject *caller, unsigned long eventId, void *callData)
VTK callback functions for observer pattern.
virtual void beginSelection()
Starts the selection mode.
virtual void updateEnableState()
Updates the enabled state of the action.
void vtkOnLeftButtonRelease(vtkObject *caller, unsigned long eventId, void *callData)
void vtkOnMiddleButtonPressed(vtkObject *caller, unsigned long eventId, void *callData)
void selectCellsOnSurface(int region[4], int selectionModifier)
Select cells on surface.
void selectFrustumCells(int region[4], int selectionModifier)
Select cells in frustum.
void vtkOnMiddleButtonRelease(vtkObject *caller, unsigned long eventId, void *callData)
void finalizeSelection(const cvSelectionData &newSelection, int selectionModifier, const QString &description)
Unified selection finalization (ParaView-style) Combines new selection with existing,...
void setupInteractorStyle()
Set up interactor style for this selection mode.
PclUtils::PCLVis * getPCLVis() const
Get the PCLVis instance.
Q_INVOKABLE void handleLeftButtonPress()
Handle left button press (public for callback access)
void selectPointsOnSurface(int region[4], int selectionModifier)
Select points on surface.
cvRenderViewSelectionReaction(QAction *parentAction, SelectionMode mode, QActionGroup *modifierGroup=nullptr)
Constructor.
Q_INVOKABLE void handleMouseMove()
Handle mouse move event (public for callback access)
void vtkOnLeftButtonPress(vtkObject *caller, unsigned long eventId, void *callData)
void selectedCustomPolygon(vtkIntArray *polygon)
Emitted for custom polygon selection.
virtual void onMiddleButtonPressed()
Handle middle button press (disable pre-selection during pan)
void vtkOnMouseMove(vtkObject *caller, unsigned long eventId, void *callData)
void selectFrustumPoints(int region[4], int selectionModifier)
Select points in frustum.
void uncheckSelectionModifiers()
Uncheck selection modifier buttons.
void vtkOnRightButtonRelease(vtkObject *caller, unsigned long eventId, void *callData)
virtual void onMouseMove()
Handle mouse move events (for interactive selection)
QAction * parentAction() const
Get the parent action.
virtual void cleanupObservers()
Clean up event observers.
void selectPolygonPoints(vtkIntArray *polygon, int selectionModifier)
Select points in polygon.
bool isInteractiveMode() const
Check if this is an interactive selection mode.
void selectionFinished(const cvSelectionData &selectionData)
Emitted when selection is finished.
void vtkOnWheelRotate(vtkObject *caller, unsigned long eventId, void *callData)
Q_INVOKABLE void handleRightButtonPressed()
Handle right button press (public for callback access)
virtual void updateTooltip()
Update tooltip display.
virtual void onRightButtonPressed()
Handle right button press (disable pre-selection)
virtual void endSelection()
Ends the selection mode.
void setupObservers()
Set up event observers for this selection mode.
virtual void onWheelRotate()
Handle wheel rotation events.
cvSelectionHighlighter * getSelectionHighlighter() const
Get the selection highlighter.
virtual void preSelection()
Perform pre-selection highlighting.
virtual void onRightButtonRelease()
Handle right button release (re-enable pre-selection)
void setRubberBand3DStyle(bool renderOnMouseMove)
Set vtkInteractorStyleRubberBand3D as the interactor style.
bool isSelectingCells() const
Check if currently selecting cells (vs points)
virtual void clearSelectionCache()
Clear selection cache when data changes.
virtual void onMiddleButtonRelease()
Handle middle button release (re-enable pre-selection, invalidate cache)
virtual int getSelectionModifier()
Get the selection modifier from keyboard state.
void setCursor(const QCursor &cursor)
Set cursor on the view.
void handleSelectionChanged(vtkObject *caller, unsigned long eventId, void *callData)
Handle selection changed event from VTK (public for callback access)
void storeCurrentStyle()
Store current interactor style.
virtual void fastPreSelection()
Perform fast pre-selection using cached buffers.
cvSelectionPipeline * getSelectionPipeline() const
Get the selection pipeline.
virtual void onMouseStop()
Handle mouse stop event (for tooltip display)
void showInstructionDialog()
Show instruction dialog for interactive modes.
void selectBlock(int region[4], int selectionModifier)
Select blocks.
bool isActive() const
Check if this reaction's selection is currently active.
virtual void actionTriggered(bool val)
Called when the action is triggered.
virtual void onLeftButtonPress()
Handle left button press (for zoom tracking)
virtual bool isCompatible(SelectionMode mode)
Check if this selection is compatible with another mode.
virtual void selectionChanged(vtkObject *caller, unsigned long eventId, void *callData)
Callback for selection changed event from VTK.
void setVisualizer(ecvGenericVisualizer3D *viewer)
Set the visualizer for selection operations.
static void endActiveSelection()
Force end any active selection.
void addCameraMovementObservers(int startIndex)
Add camera movement observers for cache invalidation.
void restoreStyle()
Restore previous interactor style.
Q_INVOKABLE void handleLeftButtonRelease()
Handle left button release (public for callback access)
Encapsulates selection data without exposing VTK types.
bool isEmpty() const
Check if selection is empty.
bool hasActorInfo() const
Check if actor information is available.
vtkActor * primaryActor() const
Get the primary (front-most) actor.
Helper class for highlighting selected elements in the visualizer.
bool highlightSelection(const vtkSmartPointer< vtkIdTypeArray > &selection, int fieldAssociation, HighlightMode mode=SELECTED)
Highlight selected elements (automatically gets polyData from visualizer)
void setHighlightsVisible(bool visible)
Set visibility of all highlight actors.
bool highlightElement(vtkPolyData *polyData, vtkIdType elementId, int fieldAssociation)
Highlight a single element (for hover preview)
void clearHoverHighlight()
Clear only hover highlight (keep selected/preselected)
void clearHighlights()
Clear all highlights.
Selection pipeline abstraction layer.
void invalidateCachedSelection()
Clear selection cache and invalidate cached buffers.
PixelSelectionInfo getPixelSelectionInfo(int x, int y, bool selectCells)
Get complete pixel selection information at a screen position.
cvSelectionData selectCellsOnSurface(const int region[4])
High-level selection API (ParaView-style)
cvSelectionData selectPointsInPolygon(vtkIntArray *polygon)
Select points in polygon region.
static cvSelectionData combineSelections(const cvSelectionData &sel1, const cvSelectionData &sel2, CombineOperation operation)
Combine two selections.
cvSelectionData selectCellsInPolygon(vtkIntArray *polygon)
Select cells in polygon region.
cvSelectionData selectPointsOnSurface(const int region[4])
Select points on surface in a rectangular region.
static bool promptUser(const QString &settingsKey, const QString &title, const QString &message, QWidget *parent=nullptr)
Shows instruction dialog if not disabled by user (ParaView-style)
void exitSelectionMode()
Exit selection mode and release cached buffers.
CombineOperation
Selection combination methods (ParaView-style)
Central manager for all selection operations in the view.
bool canShrinkSelection() const
Check if the selection can be shrunk.
void setCurrentSelection(const cvSelectionData &selectionData, bool resetLayers=true)
Set the current selection data.
void shrinkSelection()
Shrink the current selection by one layer.
bool hasSelection() const
Check if there is a current selection.
cvSelectionPipeline * getPipeline()
Get the selection pipeline.
void clearSelection()
Clear all selections.
static cvViewSelectionManager * instance()
Get the singleton instance.
void growSelection()
Grow the current selection by one layer.
const cvSelectionData & currentSelection() const
Get the current selection data (VTK-independent)
void setSourceObject(ccHObject *obj)
Set the source object for selection operations.
cvSelectionHighlighter * getHighlighter()
Get the shared highlighter.
void selectionChanged()
Emitted when the selection has changed (legacy - for backward compatibility)
static QWidget * GetCurrentScreen()
Generic visualizer 3D interface.
Simplified selection reaction class aligned with ParaView architecture.
@ SELECT_BLOCKS
Select blocks (rectangle)
@ SELECT_FRUSTUM_BLOCKS
Select blocks in frustum.
@ SELECT_FRUSTUM_POINTS
Select points in frustum.
@ SELECT_SURFACE_POINTS
Select points on surface (rectangle)
@ HOVER_CELLS_TOOLTIP
Show cell data tooltip on hover (read-only)
@ SELECT_SURFACE_POINTS_POLYGON
Select points on surface (polygon)
@ GROW_SELECTION
Expand selection by one layer.
@ SELECT_CUSTOM_POLYGON
Custom polygon selection (signal only)
@ SHRINK_SELECTION
Shrink selection by one layer.
@ SELECT_SURFACE_POINTS_INTERACTIVELY
@ SELECT_SURFACE_CELLS
Select cells on surface (rectangle)
@ SELECT_SURFACE_CELLS_INTERACTIVELY
@ HOVER_POINTS_TOOLTIP
Show point data tooltip on hover (read-only)
@ SELECT_SURFACE_CELLDATA_INTERACTIVELY
Hover highlight cell data.
@ SELECT_SURFACE_POINTDATA_INTERACTIVELY
Hover highlight point data.
@ ZOOM_TO_BOX
Zoom to box region.
@ SELECT_SURFACE_CELLS_POLYGON
Select cells on surface (polygon)
@ SELECT_FRUSTUM_CELLS
Select cells in frustum.
@ CLEAR_SELECTION
Clear current selection.
@ SELECTION_TOGGLE
Toggle selection (Ctrl+Shift)
@ SELECTION_DEFAULT
Replace selection (default)
@ SELECTION_ADDITION
Add to selection (Ctrl)
@ SELECTION_SUBTRACTION
Subtract from selection (Shift)
normal_z y
normal_z x
constexpr QRegularExpression::PatternOption CaseInsensitive
Definition: QtCompat.h:174
bool Copy(const std::string &from, const std::string &to, bool include_parent_dir=false, const std::string &extname="")
Copy a file or directory.
Definition: FileSystem.cpp:249
Fast Pre-Selection API (ParaView-aligned)