ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
cvSelectionPipeline.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 
8 #include "cvSelectionPipeline.h"
9 
10 #include "cvHardwareSelector.h"
11 
12 // LOCAL
13 #include "PclUtils/PCLVis.h"
14 #include "cvSelectionData.h"
15 
16 // CV_CORE_LIB
17 #include <CVLog.h>
18 
19 // STL
20 #include <cmath>
21 
22 // Qt
23 #include <QApplication>
24 #include <QCheckBox>
25 #include <QDialog>
26 #include <QDialogButtonBox>
27 #include <QLabel>
28 #include <QSet>
29 #include <QSettings>
30 #include <QStyle>
31 #include <QVBoxLayout>
32 
33 // VTK
34 #include <vtkActor.h>
35 #include <vtkCellData.h>
36 #include <vtkDataObject.h>
37 #include <vtkDataSet.h>
38 #include <vtkIdTypeArray.h>
39 #include <vtkInformation.h>
40 #include <vtkIntArray.h>
41 #include <vtkMapper.h>
42 #include <vtkPointData.h>
43 #include <vtkProp.h>
44 #include <vtkRenderWindow.h>
45 #include <vtkRenderer.h>
46 #include <vtkSelection.h>
47 #include <vtkSelectionNode.h>
48 
49 // Qt
50 #include <QMap>
51 
52 //-----------------------------------------------------------------------------
54  : QObject(parent),
55  m_viewer(nullptr),
56  m_renderer(nullptr),
57  m_cachingEnabled(true),
58  m_cacheHits(0),
59  m_cacheMisses(0) {
60  // CVLog::PrintVerbose("[cvSelectionPipeline] Created");
61 }
62 
63 //-----------------------------------------------------------------------------
65  clearCache();
67  QString("[cvSelectionPipeline] Destroyed - Cache stats: %1 "
68  "hits, %2 misses")
69  .arg(m_cacheHits)
70  .arg(m_cacheMisses));
71 }
72 
73 //-----------------------------------------------------------------------------
75  if (m_viewer == viewer) {
76  return;
77  }
78 
79  m_viewer = viewer;
80  m_renderer = nullptr;
81 
82  if (m_viewer) {
83  m_renderer = m_viewer->getCurrentRenderer();
84  }
85 
86  // Clear cache when visualizer changes
87  clearCache();
88 
89  CVLog::PrintVerbose(QString("[cvSelectionPipeline] Visualizer set: %1")
90  .arg((quintptr)viewer, 0, 16));
91 }
92 
93 //-----------------------------------------------------------------------------
95  int region[4], SelectionType type) {
96  if (!m_viewer || !m_renderer) {
97  CVLog::Warning("[cvSelectionPipeline] Invalid viewer or renderer");
98  emit errorOccurred("Invalid viewer or renderer");
99  return nullptr;
100  }
101 
102  // Check cache first
103  if (m_cachingEnabled) {
104  QString cacheKey = generateCacheKey(region, type);
105  vtkSmartPointer<vtkSelection> cached = getCachedSelection(cacheKey);
106  if (cached) {
107  m_cacheHits++;
108  CVLog::Print(
109  QString("[cvSelectionPipeline] Cache hit! Total: %1/%2")
110  .arg(m_cacheHits)
111  .arg(m_cacheHits + m_cacheMisses));
112 
113  // Return a copy
116  copy->DeepCopy(cached);
117  return copy;
118  }
119  m_cacheMisses++;
120  }
121 
122  // Perform hardware selection
123  FieldAssociation fieldAssoc = getFieldAssociation(type);
125  performHardwareSelection(region, fieldAssoc);
126 
127  if (!selection) {
128  CVLog::Warning("[cvSelectionPipeline] Hardware selection failed");
129  emit errorOccurred("Hardware selection failed");
130  return nullptr;
131  }
132 
133  // Cache the result
134  if (m_cachingEnabled) {
135  QString cacheKey = generateCacheKey(region, type);
136  cacheSelection(cacheKey, selection);
137  }
138 
139  emit selectionCompleted(selection);
140  return selection;
141 }
142 
143 //-----------------------------------------------------------------------------
145  vtkIntArray* polygon, SelectionType type) {
146  if (!m_viewer || !m_renderer || !polygon) {
148  "[cvSelectionPipeline] Invalid viewer, renderer, or polygon");
149  emit errorOccurred("Invalid parameters");
150  return nullptr;
151  }
152 
153  // The polygon array has 2 components (x, y) per tuple
154  // Each tuple represents one vertex, so GetNumberOfTuples() == number of
155  // vertices NOTE: Don't divide by 2! The array is structured as 2-component
156  // tuples.
157  vtkIdType numPoints = polygon->GetNumberOfTuples();
158  // Validate polygon
159  if (numPoints < 3) {
160  CVLog::Warning("[cvSelectionPipeline] Polygon needs at least 3 points");
161  emit errorOccurred("Invalid polygon: needs at least 3 points");
162  return nullptr;
163  }
164 
165  // ParaView approach: Use vtkHardwareSelector::GeneratePolygonSelection
166  // for pixel-precise polygon selection
167  // Reference: vtkPVRenderView::SelectPolygon
168 
169  // Step 1: Find bounding box of polygon
170  int minX = INT_MAX, minY = INT_MAX;
171  int maxX = INT_MIN, maxY = INT_MIN;
172 
173  for (vtkIdType i = 0; i < numPoints; ++i) {
174  int x = polygon->GetValue(i * 2);
175  int y = polygon->GetValue(i * 2 + 1);
176  minX = std::min(minX, x);
177  minY = std::min(minY, y);
178  maxX = std::max(maxX, x);
179  maxY = std::max(maxY, y);
180  }
181 
182  // Validate bounding box
183  if (minX >= maxX || minY >= maxY) {
184  CVLog::Warning("[cvSelectionPipeline] Invalid polygon bounding box");
185  emit errorOccurred("Invalid polygon geometry");
186  return nullptr;
187  }
188 
189  // Step 2: Get render window
190  vtkRenderWindow* renderWindow = m_viewer->getRenderWindow();
191  if (!renderWindow) {
192  CVLog::Warning("[cvSelectionPipeline] Invalid render window");
193  emit errorOccurred("Invalid render window");
194  return nullptr;
195  }
196 
197  // Step 3: Create or reuse cvHardwareSelector (ParaView-style)
198  if (!m_hardwareSelector) {
199  m_hardwareSelector = vtkSmartPointer<cvHardwareSelector>::New();
200  m_hardwareSelector->SetPointPickingRadius(m_pointPickingRadius);
201  }
202 
203  m_hardwareSelector->SetRenderer(m_renderer);
204  m_hardwareSelector->SetArea(minX, minY, maxX, maxY);
205 
206  // Set field association
207  FieldAssociation fieldAssoc = getFieldAssociation(type);
208  if (fieldAssoc == FIELD_ASSOCIATION_CELLS) {
209  m_hardwareSelector->SetFieldAssociation(
210  vtkDataObject::FIELD_ASSOCIATION_CELLS);
211  } else {
212  m_hardwareSelector->SetFieldAssociation(
213  vtkDataObject::FIELD_ASSOCIATION_POINTS);
214  }
215 
216  // Step 4: Capture pixel buffers (ParaView-style)
217  // This renders the scene with special color encoding for selection
218  bool captureSuccess = m_hardwareSelector->CaptureBuffers();
219  if (!captureSuccess) {
221  "[cvSelectionPipeline] Failed to capture buffers for polygon");
222  // Try once more after forcing a render
223  renderWindow->Render();
224  captureSuccess = m_hardwareSelector->CaptureBuffers();
225  if (!captureSuccess) {
226  emit errorOccurred("Buffer capture failed");
227  return nullptr;
228  }
229  }
230 
231  // Step 5: Generate polygon selection with pixel-level testing
232  // Reference: vtkHardwareSelector::GeneratePolygonSelection
233  // This tests each pixel in the bounding box to see if it's inside the
234  // polygon
235  std::vector<int> polygonArray(numPoints * 2);
236  for (vtkIdType i = 0; i < numPoints * 2; ++i) {
237  polygonArray[i] = polygon->GetValue(i);
238  }
239 
240  vtkSelection* selection = m_hardwareSelector->GeneratePolygonSelection(
241  polygonArray.data(), static_cast<vtkIdType>(numPoints * 2));
242 
243  if (!selection) {
244  CVLog::Print(
245  "[cvSelectionPipeline] Polygon selection returned no results");
246  // This is not an error - just no items selected
247  vtkSmartPointer<vtkSelection> emptySelection =
249  m_lastSelection = emptySelection;
250  emit selectionCompleted(emptySelection);
251  return emptySelection;
252  }
253 
254  // Wrap in smart pointer for automatic cleanup
255  vtkSmartPointer<vtkSelection> smartSelection;
256  smartSelection.TakeReference(selection);
257 
258  // Cache last selection
259  m_lastSelection = smartSelection;
260 
261  // Count total selected items
262  int totalItems = 0;
263  for (unsigned int i = 0; i < smartSelection->GetNumberOfNodes(); ++i) {
264  vtkSelectionNode* node = smartSelection->GetNode(i);
265  if (node && node->GetSelectionList()) {
266  totalItems += node->GetSelectionList()->GetNumberOfTuples();
267  }
268  }
269 
270  emit selectionCompleted(smartSelection);
271  return smartSelection;
272 }
273 
274 //-----------------------------------------------------------------------------
276  vtkSelection* selection, FieldAssociation fieldAssociation) {
277  if (!selection) {
278  return nullptr;
279  }
280 
281  // Get the first selection node
282  if (selection->GetNumberOfNodes() == 0) {
283  CVLog::Print("[cvSelectionPipeline] Selection has no nodes");
284  return nullptr;
285  }
286 
287  vtkSelectionNode* node = selection->GetNode(0);
288  if (!node) {
289  CVLog::Warning("[cvSelectionPipeline] Invalid selection node");
290  return nullptr;
291  }
292 
293  // Get the selection list
294  vtkIdTypeArray* selectionList =
295  vtkIdTypeArray::SafeDownCast(node->GetSelectionList());
296  if (!selectionList) {
297  return nullptr;
298  }
299 
300  // Return a copy (automatic memory management)
303  copy->DeepCopy(selectionList);
304  return copy;
305 }
306 
307 //-----------------------------------------------------------------------------
309  if (m_cachingEnabled != enable) {
310  m_cachingEnabled = enable;
311  CVLog::Print(QString("[cvSelectionPipeline] Caching %1")
312  .arg(enable ? "enabled" : "disabled"));
313 
314  if (!enable) {
315  clearCache();
316  }
317  }
318 }
319 
320 //-----------------------------------------------------------------------------
322  // Smart pointers handle cleanup automatically
323  m_selectionCache.clear();
324 
325  // CVLog::PrintVerbose("[cvSelectionPipeline] Cache cleared");
326 }
327 
328 //-----------------------------------------------------------------------------
330  return m_selectionCache.size();
331 }
332 
333 //-----------------------------------------------------------------------------
334 int cvSelectionPipeline::getCacheHits() const { return m_cacheHits; }
335 
336 //-----------------------------------------------------------------------------
337 int cvSelectionPipeline::getCacheMisses() const { return m_cacheMisses; }
338 
339 //-----------------------------------------------------------------------------
341  // Check if hardware selector has cached buffers
342  // VTK doesn't expose this directly, so we track it ourselves
343  return m_inSelectionMode && m_hardwareSelector != nullptr;
344 }
345 
346 //-----------------------------------------------------------------------------
348  if (!m_viewer || !m_renderer) {
350  "[cvSelectionPipeline] Cannot capture buffers - no "
351  "viewer/renderer");
352  return false;
353  }
354 
355  vtkRenderWindow* renderWindow = m_viewer->getRenderWindow();
356  if (!renderWindow) {
358  "[cvSelectionPipeline] Cannot capture buffers - no render "
359  "window");
360  return false;
361  }
362 
363  // Create cvHardwareSelector if needed (ParaView-style)
364  if (!m_hardwareSelector) {
365  m_hardwareSelector = vtkSmartPointer<cvHardwareSelector>::New();
366  m_hardwareSelector->SetPointPickingRadius(m_pointPickingRadius);
367  }
368 
369  m_hardwareSelector->SetRenderer(m_renderer);
370 
371  // Set area to full viewport
372  int* size = m_renderer->GetSize();
373  int* origin = m_renderer->GetOrigin();
374  m_hardwareSelector->SetArea(origin[0], origin[1], origin[0] + size[0] - 1,
375  origin[1] + size[1] - 1);
376 
377  // Capture the buffers
378  bool success = m_hardwareSelector->CaptureBuffers();
379 
380  if (success) {
381  m_inSelectionMode = true;
382  CVLog::Print(
383  "[cvSelectionPipeline] Captured buffers for fast "
384  "pre-selection");
385  } else {
386  CVLog::Warning("[cvSelectionPipeline] Failed to capture buffers");
387  }
388 
389  return success;
390 }
391 
392 //-----------------------------------------------------------------------------
394 cvSelectionPipeline::getPixelSelectionInfo(int x, int y, bool selectCells) {
396 
397  // CRITICAL: Check if invalidation is in progress
398  // This prevents crashes when mouse events arrive during cache invalidation
399  if (m_invalidating) {
400  return result; // Return empty result - safe to skip this hover update
401  }
402 
403  if (!m_viewer || !m_renderer) {
405  "[cvSelectionPipeline::getPixelSelectionInfo] Invalid viewer "
406  "or renderer");
407  return result;
408  }
409 
410  // PARAVIEW STYLE: Always do fresh hardware selection, NO CACHING
411  // Caching causes stale actor problems and incorrect IDs
412  // Reference: ParaView never caches for hover/tooltip - always fresh render
413 
414  // Do a single-pixel hardware selection
415  int region[4] = {x, y, x, y};
416  vtkSmartPointer<vtkSelection> selection = performHardwareSelection(
417  region,
419 
420  if (selection && selection->GetNumberOfNodes() > 0) {
421  vtkSelectionNode* node = selection->GetNode(0);
422  if (node && node->GetSelectionList() &&
423  node->GetSelectionList()->GetNumberOfTuples() > 0) {
424  vtkIdTypeArray* ids =
425  vtkIdTypeArray::SafeDownCast(node->GetSelectionList());
426  if (ids && ids->GetNumberOfTuples() > 0) {
427  result.valid = true;
428  result.attributeID = ids->GetValue(0);
429 
430  // Get prop from selection node
431  vtkInformation* properties = node->GetProperties();
432  if (properties && properties->Has(vtkSelectionNode::PROP())) {
433  result.prop = vtkProp::SafeDownCast(
434  properties->Get(vtkSelectionNode::PROP()));
435 
436  // Get polyData from prop
437  vtkActor* actor = vtkActor::SafeDownCast(result.prop);
438  if (actor && actor->GetMapper()) {
439  vtkDataSet* data = actor->GetMapper()->GetInput();
440  result.polyData = vtkPolyData::SafeDownCast(data);
441  }
442  }
443  }
444  }
445  }
446 
447  return result;
448 }
449 
450 //-----------------------------------------------------------------------------
451 vtkIdType cvSelectionPipeline::fastPreSelectAt(int x, int y, bool selectCells) {
452  // Use the new comprehensive method and return only the ID for backward
453  // compatibility
454  PixelSelectionInfo info = getPixelSelectionInfo(x, y, selectCells);
455  return info.valid ? info.attributeID : -1;
456 }
457 
458 //-----------------------------------------------------------------------------
460  if (m_inSelectionMode) {
461  return;
462  }
463 
464  m_inSelectionMode = true;
465 
466  // ParaView-style: When entering selection mode, the render view
467  // switches to INTERACTION_MODE_SELECTION which tells it to cache
468  // selection render buffers for faster repeated selections.
469  // Reference: vtkPVRenderView::SetInteractionMode
470 }
471 
472 //-----------------------------------------------------------------------------
474  if (!m_inSelectionMode) {
475  return;
476  }
477 
478  m_inSelectionMode = false;
479 
480  // ParaView-style: Reset hardware selector to release cached buffers
481  // We create a fresh selector on next selection rather than trying to
482  // call protected ReleasePixBuffers()
483  // Reference: vtkPVRenderView doesn't explicitly release buffers,
484  // it just lets them be overwritten on next selection
485  // Note: vtkSmartPointer uses = nullptr instead of Reset() (unlike
486  // std::shared_ptr)
487  m_hardwareSelector = nullptr;
488 }
489 
490 //-----------------------------------------------------------------------------
492  // Set invalidating flag to prevent concurrent access
493  // This prevents crashes when mouse events arrive during invalidation
494  m_invalidating = true;
495 
496  // Clear the selection cache
497  clearCache();
498 
499  // ParaView-style: Reset hardware selector to invalidate cached buffers
500  // Reference: vtkPVRenderView::InvalidateCachedSelection() clears internal
501  // state Note: vtkSmartPointer uses = nullptr instead of Reset() (unlike
502  // std::shared_ptr)
503  m_hardwareSelector = nullptr;
504 
505  // Clear last selection
506  m_lastSelection = nullptr;
507 
508  // Clear invalidating flag
509  m_invalidating = false;
510 
511  // CVLog::PrintVerbose("[cvSelectionPipeline] Invalidated cached
512  // selection");
513 }
514 
515 //-----------------------------------------------------------------------------
517  m_pointPickingRadius = radius;
518 
519  // Update cvHardwareSelector if it exists
520  if (m_hardwareSelector) {
521  m_hardwareSelector->SetPointPickingRadius(radius);
522  }
523 
524  CVLog::Print(QString("[cvSelectionPipeline] Point picking radius set to %1 "
525  "pixels")
526  .arg(radius));
527 }
528 
529 //-----------------------------------------------------------------------------
530 vtkSmartPointer<vtkSelection> cvSelectionPipeline::performHardwareSelection(
531  int region[4], FieldAssociation fieldAssociation) {
532  // Reference: ParaView's vtkPVRenderView::Select() and
533  // vtkPVHardwareSelector::Select() This method now uses cvHardwareSelector
534  // which is adapted from ParaView's vtkPVHardwareSelector for consistent
535  // behavior.
536 
537  // CRITICAL: Check if invalidation is in progress
538  // This prevents crashes when selection is attempted during cache
539  // invalidation
540  if (m_invalidating) {
541  return nullptr; // Safe to return - selection will be retried later
542  }
543 
544  if (!m_viewer || !m_renderer) {
545  return nullptr;
546  }
547 
548  vtkRenderWindow* renderWindow = m_viewer->getRenderWindow();
549  if (!renderWindow) {
550  CVLog::Warning("[cvSelectionPipeline] Invalid render window");
551  return nullptr;
552  }
553 
554  // IMPORTANT: region coordinates come from VTK's interactor events
555  // (GetEventPosition), which already use VTK coordinate system:
556  // origin at bottom-left, Y increases upward.
557  // NO coordinate conversion needed here!
558 
559  // Ensure region is properly ordered (x1 <= x2, y1 <= y2)
560  int vtk_region[4];
561  vtk_region[0] = std::min(region[0], region[2]); // X1
562  vtk_region[1] = std::min(region[1], region[3]); // Y1
563  vtk_region[2] = std::max(region[0], region[2]); // X2
564  vtk_region[3] = std::max(region[1], region[3]); // Y2
565 
566  CVLog::PrintVerbose(QString("[cvSelectionPipeline] Selection region: "
567  "Input[%1,%2,%3,%4] -> Normalized[%5,%6,%7,%8]")
568  .arg(region[0])
569  .arg(region[1])
570  .arg(region[2])
571  .arg(region[3])
572  .arg(vtk_region[0])
573  .arg(vtk_region[1])
574  .arg(vtk_region[2])
575  .arg(vtk_region[3]));
576 
577  // ParaView-style: Disable buffer swapping during selection to avoid
578  // clobbering the user's view (BUG #16042 in ParaView)
579  // Reference: vtkPVRenderView::PrepareSelect() lines 966-967
580  int previousSwapBuffers = renderWindow->GetSwapBuffers();
581  renderWindow->SwapBuffersOff();
582 
583  // Create or reuse cvHardwareSelector (ParaView-style)
584  // Reference: vtkPVRenderView uses vtkPVHardwareSelector
585  if (!m_hardwareSelector) {
586  m_hardwareSelector = vtkSmartPointer<cvHardwareSelector>::New();
587  m_hardwareSelector->SetPointPickingRadius(m_pointPickingRadius);
589  QString("[cvSelectionPipeline] Created cvHardwareSelector "
590  "(ParaView-style) with PointPickingRadius=%1")
591  .arg(m_pointPickingRadius));
592  }
593 
594  // Configure selector
595  m_hardwareSelector->SetRenderer(m_renderer);
596 
597  // Set field association
598  if (fieldAssociation == FIELD_ASSOCIATION_CELLS) {
599  m_hardwareSelector->SetFieldAssociation(
600  vtkDataObject::FIELD_ASSOCIATION_CELLS);
601  } else {
602  m_hardwareSelector->SetFieldAssociation(
603  vtkDataObject::FIELD_ASSOCIATION_POINTS);
604  }
605 
606  // Log current state for debugging
608  QString("[cvSelectionPipeline] cvHardwareSelector config: "
609  "FieldAssociation=%1, PointPickingRadius=%2")
610  .arg(fieldAssociation == FIELD_ASSOCIATION_CELLS ? "CELLS"
611  : "POINTS")
612  .arg(m_pointPickingRadius));
613 
614  // Perform selection using cvHardwareSelector::Select()
615  // This method handles:
616  // - Buffer caching (NeedToRenderForSelection check)
617  // - Point picking radius (automatic radius search if no direct hit)
618  // ParaView-style: First try exact selection, only use radius if nothing
619  // found Reference: vtkPVHardwareSelector::Select() lines 105-131
621  selection.TakeReference(m_hardwareSelector->Select(vtk_region));
622 
623  // ParaView-style: Restore swap buffers setting
624  renderWindow->SetSwapBuffers(previousSwapBuffers);
625 
626  if (!selection) {
628  "[cvSelectionPipeline] cvHardwareSelector returned null");
629  return nullptr;
630  }
631 
632  // Log result (debug level - this is called frequently during hover)
633  if (selection->GetNumberOfNodes() > 0) {
634  vtkSelectionNode* node = selection->GetNode(0);
635  if (node && node->GetSelectionList()) {
636  vtkIdType numIds = node->GetSelectionList()->GetNumberOfTuples();
638  QString("[cvSelectionPipeline] Selection completed: %1 IDs")
639  .arg(numIds));
640  }
641  }
642 
643  // Cache last selection for getPolyData() operations
644  m_lastSelection = selection;
645 
646  return selection;
647 }
648 
649 //-----------------------------------------------------------------------------
650 vtkSmartPointer<vtkSelection> cvSelectionPipeline::getCachedSelection(
651  const QString& key) {
652  auto it = m_selectionCache.find(key);
653  if (it != m_selectionCache.end()) {
654  return it.value();
655  }
656  return nullptr;
657 }
658 
659 //-----------------------------------------------------------------------------
660 void cvSelectionPipeline::cacheSelection(const QString& key,
661  vtkSelection* selection) {
662  if (!selection) {
663  return;
664  }
665 
666  // Check cache size limit
667  if (m_selectionCache.size() >= MAX_CACHE_SIZE) {
668  // Remove oldest entry (first in hash)
669  // Smart pointer handles cleanup automatically
670  auto it = m_selectionCache.begin();
671  m_selectionCache.erase(it);
672  CVLog::Print("[cvSelectionPipeline] Cache full, removed oldest entry");
673  }
674 
675  // Store a copy (use smart pointer from the start)
677  copy->DeepCopy(selection);
678  m_selectionCache.insert(key, copy);
679 }
680 
681 //-----------------------------------------------------------------------------
682 QString cvSelectionPipeline::generateCacheKey(int region[4],
683  SelectionType type) const {
684  // Generate a unique key based on selection parameters
685  return QString("%1_%2_%3_%4_%5")
686  .arg(region[0])
687  .arg(region[1])
688  .arg(region[2])
689  .arg(region[3])
690  .arg(type);
691 }
692 
693 //-----------------------------------------------------------------------------
694 cvSelectionPipeline::FieldAssociation cvSelectionPipeline::getFieldAssociation(
695  SelectionType type) const {
696  switch (type) {
697  case SURFACE_CELLS:
698  case FRUSTUM_CELLS:
699  case POLYGON_CELLS:
701 
702  case SURFACE_POINTS:
703  case FRUSTUM_POINTS:
704  case POLYGON_POINTS:
706 
707  default:
709  }
710 }
711 
712 //-----------------------------------------------------------------------------
713 // ParaView-style selection data extraction
714 //-----------------------------------------------------------------------------
715 
717  vtkSelection* selection) {
718  QMap<vtkProp*, vtkDataSet*> result;
719 
720  if (!selection) {
722  "[cvSelectionPipeline::extractDataFromSelection] selection is "
723  "nullptr");
724  return result;
725  }
726 
727  for (unsigned int i = 0; i < selection->GetNumberOfNodes(); ++i) {
728  vtkSelectionNode* node = selection->GetNode(i);
729  if (!node) continue;
730 
731  // Get the prop (actor) from selection node properties
732  vtkInformation* properties = node->GetProperties();
733  if (!properties || !properties->Has(vtkSelectionNode::PROP())) {
734  continue;
735  }
736 
737  vtkProp* prop = vtkProp::SafeDownCast(
738  properties->Get(vtkSelectionNode::PROP()));
739  if (!prop) continue;
740 
741  // Get data from actor's mapper
742  vtkActor* actor = vtkActor::SafeDownCast(prop);
743  if (actor) {
744  vtkMapper* mapper = actor->GetMapper();
745  if (mapper) {
746  vtkDataSet* data = mapper->GetInput();
747  if (data) {
748  result[prop] = data;
750  QString("[cvSelectionPipeline] Extracted data from "
751  "actor: %1 points, %2 cells, type=%3")
752  .arg(data->GetNumberOfPoints())
753  .arg(data->GetNumberOfCells())
754  .arg(data->GetClassName()));
755  }
756  }
757  } else {
759  QString("[cvSelectionPipeline] prop is not vtkActor: %1")
760  .arg(prop ? prop->GetClassName() : "null"));
761  }
762  }
763 
764  return result;
765 }
766 
767 //-----------------------------------------------------------------------------
769  vtkSelection* selection) {
770  QMap<vtkProp*, vtkDataSet*> dataMap = extractDataFromSelection(selection);
771 
772  if (dataMap.isEmpty()) {
774  "[cvSelectionPipeline::getPrimaryDataFromSelection] No data "
775  "found in selection");
776  return nullptr;
777  }
778 
779  // Return the data with most elements (points + cells)
780  vtkDataSet* primaryData = nullptr;
781  vtkIdType maxCount = 0;
782 
783  for (auto it = dataMap.begin(); it != dataMap.end(); ++it) {
784  vtkDataSet* data = it.value();
785  vtkIdType count = data->GetNumberOfPoints() + data->GetNumberOfCells();
786  if (count > maxCount) {
787  maxCount = count;
788  primaryData = data;
789  }
790  }
791 
792  if (primaryData) {
794  QString("[cvSelectionPipeline::getPrimaryDataFromSelection] "
795  "Primary data: %1 points, %2 cells")
796  .arg(primaryData->GetNumberOfPoints())
797  .arg(primaryData->GetNumberOfCells()));
798  }
799 
800  return primaryData;
801 }
802 
803 //-----------------------------------------------------------------------------
805  vtkSelection* selection, FieldAssociation fieldAssociation) {
806  if (!selection) {
808  "[cvSelectionPipeline::convertToCvSelectionData] selection is "
809  "nullptr");
810  return cvSelectionData();
811  }
812 
813  // Extract IDs
815  extractSelectionIds(selection, fieldAssociation);
816  if (!ids || ids->GetNumberOfTuples() == 0) {
817  CVLog::Print(
818  "[cvSelectionPipeline::convertToCvSelectionData] No IDs in "
819  "selection");
820  return cvSelectionData();
821  }
822 
823  // Create selection data
825  fieldAssociation));
826 
827  // Extract and populate actor information (ParaView-style)
828  QMap<vtkProp*, vtkDataSet*> dataMap = extractDataFromSelection(selection);
829 
830  for (auto it = dataMap.begin(); it != dataMap.end(); ++it) {
831  vtkProp* prop = it.key();
832  vtkDataSet* data = it.value();
833 
834  vtkActor* actor = vtkActor::SafeDownCast(prop);
835 
836  // Handle both vtkPolyData and other data types (e.g.,
837  // vtkUnstructuredGrid) The mapper's input might be vtkPolyData or
838  // another data type
839  vtkPolyData* polyData = vtkPolyData::SafeDownCast(data);
840 
841  // If data is not vtkPolyData, try to get it from the actor's mapper
842  if (!polyData && actor) {
843  vtkMapper* mapper = actor->GetMapper();
844  if (mapper) {
845  polyData = vtkPolyData::SafeDownCast(mapper->GetInput());
846  }
847  }
848 
849  if (actor && polyData) {
850  // Get Z-value from selection node if available
851  // Z-value represents depth (closer to camera = smaller value)
852  double zValue = 1.0; // Default: far plane
853 
854  for (unsigned int i = 0; i < selection->GetNumberOfNodes(); ++i) {
855  vtkSelectionNode* node = selection->GetNode(i);
856  if (node &&
857  node->GetProperties()->Has(vtkSelectionNode::PROP())) {
858  vtkProp* nodeProp =
859  vtkProp::SafeDownCast(node->GetProperties()->Get(
860  vtkSelectionNode::PROP()));
861  if (nodeProp == prop) {
862  // Extract Z-value if available
863  // Note: VTK's hardware selector doesn't typically store
864  // Z in properties Z-buffering is handled internally
865  // during rendering For multi-actor selection, we use
866  // the order of nodes as priority
867  zValue = 1.0 - (static_cast<double>(i) /
868  selection->GetNumberOfNodes());
869  break;
870  }
871  }
872  }
873 
874  // Add actor info to selection data
876  info.actor = actor;
877  info.polyData = polyData;
878  info.zValue = zValue;
879  result.addActorInfo(info);
880  } else {
882  QString("[cvSelectionPipeline] Failed to add actor info: "
883  "actor=%1, polyData=%2")
884  .arg(actor ? "valid" : "null")
885  .arg(polyData ? "valid" : "null"));
886  }
887  }
888 
890  QString("[cvSelectionPipeline::convertToCvSelectionData] "
891  "Created selection: %1 IDs, %2 actors")
892  .arg(result.count())
893  .arg(result.actorCount()));
894 
895  return result;
896 }
897 
898 //-----------------------------------------------------------------------------
899 // Helper method to reduce code duplication
900 //-----------------------------------------------------------------------------
901 
902 cvSelectionData cvSelectionPipeline::convertSelectionToData(
903  vtkSelection* vtkSel,
904  FieldAssociation fieldAssoc,
905  const QString& errorContext) {
906  if (!vtkSel) {
908  QString("[cvSelectionPipeline] %1 failed").arg(errorContext));
909  return cvSelectionData();
910  }
911 
912  // Use convertToCvSelectionData to properly extract actor info
913  // This ensures source object lookup will work for direct extraction
914  // Note: convertToCvSelectionData expects
915  // cvSelectionPipeline::FieldAssociation
916  return convertToCvSelectionData(vtkSel, fieldAssoc);
917 }
918 
919 //-----------------------------------------------------------------------------
920 // High-level selection API (ParaView-style)
921 //-----------------------------------------------------------------------------
922 
925  executeRectangleSelection(const_cast<int*>(region), SURFACE_CELLS);
926 
927  return convertSelectionToData(vtkSel, FIELD_ASSOCIATION_CELLS,
928  "selectCellsOnSurface");
929 }
930 
931 //-----------------------------------------------------------------------------
933  const int region[4]) {
935  executeRectangleSelection(const_cast<int*>(region), SURFACE_POINTS);
936 
937  return convertSelectionToData(vtkSel, FIELD_ASSOCIATION_POINTS,
938  "selectPointsOnSurface");
939 }
940 
941 //-----------------------------------------------------------------------------
943  vtkIntArray* polygon) {
945  QString("[cvSelectionPipeline] selectCellsInPolygon: %1 vertices")
946  .arg(polygon ? polygon->GetNumberOfTuples() : 0));
947 
948  if (!polygon) {
949  CVLog::Warning("[cvSelectionPipeline] Invalid polygon");
950  return cvSelectionData();
951  }
952 
955 
956  return convertSelectionToData(vtkSel, FIELD_ASSOCIATION_CELLS,
957  "selectCellsInPolygon");
958 }
959 
960 //-----------------------------------------------------------------------------
962  vtkIntArray* polygon) {
964  QString("[cvSelectionPipeline] selectPointsInPolygon: %1 vertices")
965  .arg(polygon ? polygon->GetNumberOfTuples() : 0));
966 
967  if (!polygon) {
968  CVLog::Warning("[cvSelectionPipeline] Invalid polygon");
969  return cvSelectionData();
970  }
971 
974 
975  return convertSelectionToData(vtkSel, FIELD_ASSOCIATION_POINTS,
976  "selectPointsInPolygon");
977 }
978 
979 //-----------------------------------------------------------------------------
980 // Selection combination (ParaView-style)
981 //-----------------------------------------------------------------------------
982 
984  const cvSelectionData& sel1,
985  const cvSelectionData& sel2,
986  CombineOperation operation) {
988  QString("[cvSelectionPipeline] combineSelections: operation=%1")
989  .arg(operation));
990 
991  // Handle empty selections first (before field association check!)
992  // ParaView behavior (vtkSMSelectionHelper::CombineSelection line 156-159):
993  // - If sel1 is empty and deepCopy=false, return true (no-op, result is
994  // sel2)
995  // - If sel2 is empty, return false (cannot combine)
996  if (sel2.isEmpty()) {
997  return sel1; // Return sel1 unchanged
998  }
999 
1000  if (sel1.isEmpty()) {
1001  // ParaView: when first selection is empty, result is second selection
1002  return sel2;
1003  }
1004 
1005  // Handle DEFAULT (replace with sel2) BEFORE checking field association
1006  // ParaView behavior: When switching selection types (e.g., cells to
1007  // points), the new selection should replace the old one, not fail
1008  if (operation == OPERATION_DEFAULT) {
1009  return sel2;
1010  }
1011 
1012  // Check compatibility (only when both are non-empty AND not replacing)
1013  // This is only for ADDITION/SUBTRACTION/TOGGLE operations
1014  if (sel1.fieldAssociation() != sel2.fieldAssociation()) {
1015  // ParaView behavior: When field associations differ and trying to
1016  // add/subtract, just use the new selection
1017  return sel2;
1018  }
1019 
1020  // Get ID sets
1021  QSet<vtkIdType> set1, set2, resultSet;
1022 
1023  if (!sel1.isEmpty()) {
1025  for (vtkIdType i = 0; i < arr1->GetNumberOfTuples(); ++i) {
1026  set1.insert(arr1->GetValue(i));
1027  }
1028  }
1029 
1030  if (!sel2.isEmpty()) {
1032  for (vtkIdType i = 0; i < arr2->GetNumberOfTuples(); ++i) {
1033  set2.insert(arr2->GetValue(i));
1034  }
1035  }
1036 
1037  // Perform operation
1038  switch (operation) {
1039  case OPERATION_ADDITION:
1040  // Union: sel1 | sel2
1041  resultSet = set1 + set2;
1043  QString("[cvSelectionPipeline] ADDITION: %1 + %2 = %3")
1044  .arg(set1.size())
1045  .arg(set2.size())
1046  .arg(resultSet.size()));
1047  break;
1048 
1049  case OPERATION_SUBTRACTION:
1050  // Difference: sel1 & !sel2
1051  resultSet = set1 - set2;
1053  QString("[cvSelectionPipeline] SUBTRACTION: %1 - %2 = %3")
1054  .arg(set1.size())
1055  .arg(set2.size())
1056  .arg(resultSet.size()));
1057  break;
1058 
1059  case OPERATION_TOGGLE:
1060  // XOR: sel1 ^ sel2
1061  resultSet = (set1 - set2) + (set2 - set1);
1063  QString("[cvSelectionPipeline] TOGGLE: %1 ^ %2 = %3")
1064  .arg(set1.size())
1065  .arg(set2.size())
1066  .arg(resultSet.size()));
1067  break;
1068 
1069  default:
1071  QString("[cvSelectionPipeline] Unknown operation: %1")
1072  .arg(operation));
1073  return cvSelectionData();
1074  }
1075 
1076  // Convert result set to array
1077  if (resultSet.isEmpty()) {
1078  return cvSelectionData();
1079  }
1080 
1081  vtkSmartPointer<vtkIdTypeArray> resultArray =
1083  for (vtkIdType id : resultSet) {
1084  resultArray->InsertNextValue(id);
1085  }
1086 
1087  cvSelectionData result(resultArray, sel1.fieldAssociation());
1088 
1089  // CRITICAL: Preserve actor info from sel2 (the new selection)
1090  // This allows source object lookup to work correctly for extraction
1091  // Reference: ParaView maintains representation info across selection
1092  // operations
1093  for (int i = 0; i < sel2.actorCount(); ++i) {
1094  result.addActorInfo(sel2.actorInfo(i));
1095  }
1096  // Also add actor info from sel1 if not already present (for
1097  // ADDITION/TOGGLE)
1098  if (operation == OPERATION_ADDITION || operation == OPERATION_TOGGLE) {
1099  for (int i = 0; i < sel1.actorCount(); ++i) {
1100  // Check if actor already exists in result
1101  bool found = false;
1102  for (int j = 0; j < result.actorCount(); ++j) {
1103  if (result.actorInfo(j).actor == sel1.actorInfo(i).actor) {
1104  found = true;
1105  break;
1106  }
1107  }
1108  if (!found) {
1109  result.addActorInfo(sel1.actorInfo(i));
1110  }
1111  }
1112  }
1113 
1115  QString("[cvSelectionPipeline] combineSelections result: %1 IDs, "
1116  "%2 actors")
1117  .arg(result.count())
1118  .arg(result.actorCount()));
1119 
1120  return result;
1121 }
1122 
1123 //-----------------------------------------------------------------------------
1124 bool cvSelectionPipeline::pointInPolygon(const int point[2],
1125  vtkIntArray* polygon,
1126  vtkIdType numPoints) {
1127  // Ray casting algorithm for point-in-polygon test
1128  // Reference: ParaView's vtkHardwareSelector::Internals::PixelInsidePolygon
1129  // http://en.wikipedia.org/wiki/Point_in_polygon
1130 
1131  float px = static_cast<float>(point[0]);
1132  float py = static_cast<float>(point[1]);
1133  bool inside = false;
1134 
1135  vtkIdType count = numPoints * 2; // Total values (x,y pairs)
1136 
1137  for (vtkIdType i = 0; i < count; i += 2) {
1138  float p1X = static_cast<float>(polygon->GetValue(i));
1139  float p1Y = static_cast<float>(polygon->GetValue(i + 1));
1140  float p2X = static_cast<float>(polygon->GetValue((i + 2) % count));
1141  float p2Y = static_cast<float>(polygon->GetValue((i + 3) % count));
1142 
1143  // Check if ray from point crosses edge (p1X,p1Y)-(p2X,p2Y)
1144  if (py > std::min(p1Y, p2Y) && py <= std::max(p1Y, p2Y) && p1Y != p2Y) {
1145  if (px <= std::max(p1X, p2X)) {
1146  float xintersection =
1147  (py - p1Y) * (p2X - p1X) / (p2Y - p1Y) + p1X;
1148  if (p1X == p2X || px <= xintersection) {
1149  // Each time intersect, toggle inside
1150  inside = !inside;
1151  }
1152  }
1153  }
1154  }
1155 
1156  return inside;
1157 }
1158 
1159 //-----------------------------------------------------------------------------
1161  vtkSelection* selection, vtkIntArray* polygon, vtkIdType numPoints) {
1162  // ParaView-aligned: Refine selection by testing each point against polygon
1163  // This is a fallback for when vtkHardwareSelector::GeneratePolygonSelection
1164  // is not available or when additional filtering is needed
1165 
1166  if (!selection || !polygon || numPoints < 3) {
1168  "[cvSelectionPipeline::refinePolygonSelection] Invalid "
1169  "parameters");
1170  return nullptr;
1171  }
1172 
1173  vtkSmartPointer<vtkSelection> refinedSelection =
1175 
1176  for (unsigned int nodeIdx = 0; nodeIdx < selection->GetNumberOfNodes();
1177  ++nodeIdx) {
1178  vtkSelectionNode* node = selection->GetNode(nodeIdx);
1179  if (!node) continue;
1180 
1181  vtkIdTypeArray* selectionList =
1182  vtkIdTypeArray::SafeDownCast(node->GetSelectionList());
1183  if (!selectionList) continue;
1184 
1185  // Get the prop (actor) from selection node for coordinate conversion
1186  vtkInformation* properties = node->GetProperties();
1187  if (!properties || !properties->Has(vtkSelectionNode::PROP())) {
1188  // No prop info, copy the node as-is
1191  newNode->DeepCopy(node);
1192  refinedSelection->AddNode(newNode);
1193  continue;
1194  }
1195 
1196  vtkProp* prop = vtkProp::SafeDownCast(
1197  properties->Get(vtkSelectionNode::PROP()));
1198  vtkActor* actor = vtkActor::SafeDownCast(prop);
1199 
1200  if (!actor) {
1201  // Not an actor, copy the node as-is
1204  newNode->DeepCopy(node);
1205  refinedSelection->AddNode(newNode);
1206  continue;
1207  }
1208 
1209  // For each selected ID, check if it's inside the polygon
1210  // This requires world-to-screen coordinate conversion
1211  vtkSmartPointer<vtkIdTypeArray> filteredList =
1213  filteredList->SetName(selectionList->GetName());
1214 
1215  vtkMapper* mapper = actor->GetMapper();
1216  if (!mapper) continue;
1217 
1218  vtkDataSet* data = mapper->GetInput();
1219  if (!data) continue;
1220 
1221  int fieldAssociation = node->GetFieldType();
1222 
1223  for (vtkIdType i = 0; i < selectionList->GetNumberOfTuples(); ++i) {
1224  vtkIdType id = selectionList->GetValue(i);
1225 
1226  // Get world coordinates
1227  double worldPos[3] = {0, 0, 0};
1228  if (fieldAssociation == vtkDataObject::FIELD_ASSOCIATION_POINTS) {
1229  if (id >= 0 && id < data->GetNumberOfPoints()) {
1230  data->GetPoint(id, worldPos);
1231  } else {
1232  continue;
1233  }
1234  } else {
1235  // For cells, use cell center
1236  if (id >= 0 && id < data->GetNumberOfCells()) {
1237  double bounds[6];
1238  data->GetCellBounds(id, bounds);
1239  worldPos[0] = (bounds[0] + bounds[1]) / 2.0;
1240  worldPos[1] = (bounds[2] + bounds[3]) / 2.0;
1241  worldPos[2] = (bounds[4] + bounds[5]) / 2.0;
1242  } else {
1243  continue;
1244  }
1245  }
1246 
1247  // Convert world to display coordinates
1248  if (m_renderer) {
1249  double displayPos[3];
1250  m_renderer->SetWorldPoint(worldPos[0], worldPos[1], worldPos[2],
1251  1.0);
1252  m_renderer->WorldToDisplay();
1253  m_renderer->GetDisplayPoint(displayPos);
1254 
1255  int screenPoint[2] = {static_cast<int>(displayPos[0]),
1256  static_cast<int>(displayPos[1])};
1257 
1258  // Test if point is inside polygon
1259  if (pointInPolygon(screenPoint, polygon, numPoints)) {
1260  filteredList->InsertNextValue(id);
1261  }
1262  } else {
1263  // No renderer, keep all points
1264  filteredList->InsertNextValue(id);
1265  }
1266  }
1267 
1268  // Create new node with filtered list
1269  if (filteredList->GetNumberOfTuples() > 0) {
1272  newNode->DeepCopy(node);
1273  newNode->SetSelectionList(filteredList);
1274  refinedSelection->AddNode(newNode);
1275  }
1276  }
1277 
1278  CVLog::Print(QString("[cvSelectionPipeline::refinePolygonSelection] "
1279  "Refined from %1 to %2 nodes")
1280  .arg(selection->GetNumberOfNodes())
1281  .arg(refinedSelection->GetNumberOfNodes()));
1282 
1283  return refinedSelection;
1284 }
1285 
1286 //-----------------------------------------------------------------------------
1287 // Static Utility Methods (merged from cvSelectionToolHelper)
1288 //-----------------------------------------------------------------------------
1289 bool cvSelectionPipeline::promptUser(const QString& settingsKey,
1290  const QString& title,
1291  const QString& message,
1292  QWidget* parent) {
1293  // Check if user has disabled this instruction
1294  QSettings settings;
1295  QString key = QString("SelectionTools/DontShowAgain/%1").arg(settingsKey);
1296  bool dontShow = settings.value(key, false).toBool();
1297 
1298  if (dontShow) {
1299  return false; // Don't show dialog
1300  }
1301 
1302  // Create custom dialog (ParaView-style)
1303  QDialog dialog(parent);
1304  dialog.setWindowTitle(title);
1305  dialog.setModal(true); // Modal - blocks until user responds
1306 
1307  QVBoxLayout* mainLayout = new QVBoxLayout(&dialog);
1308  mainLayout->setContentsMargins(20, 20, 20, 20);
1309  mainLayout->setSpacing(15);
1310 
1311  // Icon + message text
1312  QHBoxLayout* contentLayout = new QHBoxLayout();
1313 
1314  // Information icon
1315  QLabel* iconLabel = new QLabel(&dialog);
1316  QIcon infoIcon =
1317  dialog.style()->standardIcon(QStyle::SP_MessageBoxInformation);
1318  iconLabel->setPixmap(infoIcon.pixmap(32, 32));
1319  iconLabel->setAlignment(Qt::AlignTop);
1320  contentLayout->addWidget(iconLabel);
1321 
1322  // Message text
1323  QLabel* textLabel = new QLabel(message, &dialog);
1324  textLabel->setWordWrap(true);
1325  textLabel->setTextFormat(Qt::RichText);
1326  textLabel->setMinimumWidth(400);
1327  contentLayout->addWidget(textLabel, 1);
1328 
1329  mainLayout->addLayout(contentLayout);
1330 
1331  // "Don't show this message again" checkbox (ParaView-style)
1332  QCheckBox* dontShowAgainCheckBox = new QCheckBox(
1333  QObject::tr("Do not show this message again"), &dialog);
1334  mainLayout->addWidget(dontShowAgainCheckBox);
1335 
1336  // OK button
1337  QDialogButtonBox* buttonBox =
1338  new QDialogButtonBox(QDialogButtonBox::Ok, &dialog);
1339  QObject::connect(buttonBox, &QDialogButtonBox::accepted, &dialog,
1340  &QDialog::accept);
1341  mainLayout->addWidget(buttonBox);
1342 
1343  // Show dialog (modal - exec() blocks until user responds)
1344  dialog.exec();
1345 
1346  // Save preference if user checked "don't show again"
1347  if (dontShowAgainCheckBox->isChecked()) {
1348  settings.setValue(key, true);
1349  CVLog::Print(QString("[cvSelectionPipeline::promptUser] User checked "
1350  "'don't show again' for key: %1")
1351  .arg(settingsKey));
1352  }
1353 
1354  return true;
1355 }
int size
int count
char type
boost::geometry::model::polygon< point_xy > polygon
Definition: TreeIso.cpp:37
Eigen::Vector3d origin
Definition: VoxelGridIO.cpp:26
core::Tensor result
Definition: VtkUtils.cpp:76
bool copy
Definition: VtkUtils.cpp:74
static bool Warning(const char *format,...)
Prints out a formatted warning message in console.
Definition: CVLog.cpp:133
static bool Print(const char *format,...)
Prints out a formatted message in console.
Definition: CVLog.cpp:113
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
Encapsulates selection data without exposing VTK types.
FieldAssociation fieldAssociation() const
Get field association.
vtkSmartPointer< vtkIdTypeArray > vtkArray() const
Get the underlying VTK array (for internal use only)
FieldAssociation
Field association for selection.
bool isEmpty() const
Check if selection is empty.
int actorCount() const
Get number of actors in this selection.
cvActorSelectionInfo actorInfo(int index=0) const
Get actor info at index.
void errorOccurred(const QString &message)
Emitted when an error occurs.
void setPointPickingRadius(unsigned int radius)
Point Picking Radius support (ParaView-aligned)
void invalidateCachedSelection()
Clear selection cache and invalidate cached buffers.
static bool pointInPolygon(const int point[2], vtkIntArray *polygon, vtkIdType numPoints)
Test if a 2D point is inside a polygon (ParaView-aligned) Uses the ray casting algorithm for robust p...
bool captureBuffersForFastPreSelection()
Capture buffers for fast pre-selection.
vtkSmartPointer< vtkSelection > refinePolygonSelection(vtkSelection *selection, vtkIntArray *polygon, vtkIdType numPoints)
Refine polygon selection with point-in-polygon testing.
vtkIdType fastPreSelectAt(int x, int y, bool selectCells)
Perform fast pre-selection at a screen position.
bool hasCachedBuffers() const
Check if fast pre-selection buffers are available.
static cvSelectionData convertToCvSelectionData(vtkSelection *selection, FieldAssociation fieldAssociation)
Convert vtkSelection to cvSelectionData with actor info (ParaView-style)
void selectionCompleted(vtkSelection *selection)
Emitted when selection is completed.
cvSelectionPipeline(QObject *parent=nullptr)
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.
vtkSmartPointer< vtkSelection > executeRectangleSelection(int region[4], SelectionType type)
Execute a rectangular selection.
void setVisualizer(PclUtils::PCLVis *viewer)
Set the visualizer for selection operations.
vtkSmartPointer< vtkSelection > executePolygonSelection(vtkIntArray *polygon, SelectionType type)
Execute a polygon selection.
FieldAssociation
Field association type.
void clearCache()
Clear the selection cache.
static cvSelectionData combineSelections(const cvSelectionData &sel1, const cvSelectionData &sel2, CombineOperation operation)
Combine two selections.
static QMap< vtkProp *, vtkDataSet * > extractDataFromSelection(vtkSelection *selection)
Get data objects from selection (ParaView-style)
cvSelectionData selectCellsInPolygon(vtkIntArray *polygon)
Select cells in polygon region.
static vtkDataSet * getPrimaryDataFromSelection(vtkSelection *selection)
Get the primary data object from selection.
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)
static vtkSmartPointer< vtkIdTypeArray > extractSelectionIds(vtkSelection *selection, FieldAssociation fieldAssociation)
Extract selected IDs from vtkSelection.
void exitSelectionMode()
Exit selection mode and release cached buffers.
SelectionType
Selection type.
@ POLYGON_POINTS
Polygon points.
@ FRUSTUM_POINTS
Frustum points.
@ FRUSTUM_CELLS
Frustum cells.
@ SURFACE_CELLS
Surface cells (rectangle)
@ SURFACE_POINTS
Surface points (rectangle)
@ POLYGON_CELLS
Polygon cells.
void setEnableCaching(bool enable)
Enable/disable selection caching.
int getCacheSize() const
Get cache statistics.
CombineOperation
Selection combination methods (ParaView-style)
@ OPERATION_TOGGLE
XOR (sel1 ^ sel2)
@ OPERATION_ADDITION
Union (sel1 | sel2)
@ OPERATION_DEFAULT
Replace (sel2 only)
@ OPERATION_SUBTRACTION
Difference (sel1 & !sel2)
void enterSelectionMode()
Enter selection mode (ParaView-style cache optimization)
GraphType data
Definition: graph_cut.cc:138
normal_z y
normal_z x
SelectionType
Definition: qcustomplot.h:402
Information about a selected actor/representation.
double zValue
Z-buffer depth value (for front-to-back ordering)
vtkPolyData * polyData
The associated polyData (weak pointer)
vtkActor * actor
The selected actor (weak pointer)
Fast Pre-Selection API (ParaView-aligned)