ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
cvSelectionHighlighter.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 
9 
10 // LOCAL
11 #include "PclUtils/PCLVis.h"
12 
13 // CV_DB_LIB
14 #include <ecvGenericVisualizer3D.h>
15 
16 // CV_CORE_LIB
17 #include <CVLog.h>
18 
19 // VTK
20 #include <vtkAbstractArray.h>
21 #include <vtkActor.h>
22 #include <vtkActor2D.h>
23 #include <vtkCellCenters.h>
24 #include <vtkDataSet.h>
25 #include <vtkDataSetAttributes.h>
26 #include <vtkDataSetMapper.h>
27 #include <vtkExtractSelection.h>
28 #include <vtkIdTypeArray.h>
29 #include <vtkLabeledDataMapper.h>
30 #include <vtkMapper.h>
31 #include <vtkMaskPoints.h>
32 #include <vtkPolyData.h>
33 #include <vtkPolyDataMapper.h>
34 #include <vtkProp.h>
35 #include <vtkPropCollection.h>
36 #include <vtkProperty.h>
37 #include <vtkRenderWindow.h>
38 #include <vtkRenderer.h>
39 #include <vtkSelection.h>
40 #include <vtkSelectionNode.h>
41 #include <vtkTextProperty.h>
42 #include <vtkUnstructuredGrid.h>
43 
44 //-----------------------------------------------------------------------------
46  : QObject(),
48  m_hoverOpacity(1.0), // ParaView default: opaque
49  m_preselectedOpacity(1.0), // ParaView default: opaque
50  m_selectedOpacity(1.0), // ParaView default: opaque
51  m_boundaryOpacity(1.0), // ParaView default: opaque
52  m_hoverPointSize(5), // ParaView default: 5 for hover
53  m_preselectedPointSize(5), // ParaView default: 5 for preselection
54  m_selectedPointSize(5), // ParaView default: 5 for final selection
55  m_boundaryPointSize(5), // ParaView default: 5 for boundary
56  m_hoverLineWidth(2),
57  m_preselectedLineWidth(2),
58  m_selectedLineWidth(2),
59  m_boundaryLineWidth(2),
60  m_enabled(true),
61  m_pointLabelVisible(false),
62  m_cellLabelVisible(false) {
63  // ParaView default colors from utilities_remotingviews.xml ColorPalette:
64  // - SelectionColor: 1.0 0.0 1.0 (Magenta)
65  // - InteractiveSelectionColor: 0.5 0.0 1.0 (Purple/Violet)
66 
67  // Hover: Purple/Violet (InteractiveSelectionColor) - ParaView default
68  m_hoverColor[0] = 0.5; // Red = 127
69  m_hoverColor[1] = 0.0; // Green = 0
70  m_hoverColor[2] = 1.0; // Blue = 255 (Purple/Violet)
71 
72  // Pre-selected: Purple/Violet (InteractiveSelectionColor) - ParaView
73  // default
74  m_preselectedColor[0] = 0.5;
75  m_preselectedColor[1] = 0.0;
76  m_preselectedColor[2] = 1.0;
77 
78  // Selected: Magenta (SelectionColor) - ParaView default
79  // Magenta is highly visible against any point cloud color
80  m_selectedColor[0] = 1.0; // Red = 255
81  m_selectedColor[1] = 0.0; // Green = 0
82  m_selectedColor[2] = 1.0; // Blue = 255 (Magenta)
83 
84  // Boundary: Magenta (same as SelectionColor) - ParaView default
85  m_boundaryColor[0] = 1.0;
86  m_boundaryColor[1] = 0.0;
87  m_boundaryColor[2] = 1.0;
88 
89  m_hoverActorId = "__highlight_hover__";
90  m_preselectedActorId = "__highlight_preselected__";
91  m_selectedActorId = "__highlight_selected__";
92  m_boundaryActorId = "__highlight_boundary__";
93 
95  "[cvSelectionHighlighter] Initialized with ParaView default "
96  "colors: Hover=Purple(0.5,0,1), Selected=Magenta(1,0,1)");
97 }
98 
99 //-----------------------------------------------------------------------------
101 
102 // setVisualizer is inherited from cvGenericSelectionTool
103 
104 //-----------------------------------------------------------------------------
106  double g,
107  double b,
108  HighlightMode mode) {
109  double* color = nullptr;
110  vtkSmartPointer<vtkActor>* actor = nullptr;
111 
112  switch (mode) {
113  case HOVER:
114  color = m_hoverColor;
115  actor = &m_hoverActor;
116  break;
117  case PRESELECTED:
118  color = m_preselectedColor;
119  actor = &m_preselectedActor;
120  break;
121  case SELECTED:
122  color = m_selectedColor;
123  actor = &m_selectedActor;
124  break;
125  case BOUNDARY:
126  color = m_boundaryColor;
127  actor = &m_boundaryActor;
128  break;
129  }
130 
131  if (color) {
132  // Check if color actually changed
133  bool changed = (color[0] != r || color[1] != g || color[2] != b);
134 
135  color[0] = r;
136  color[1] = g;
137  color[2] = b;
138 
139  // Update existing actor's color immediately for real-time preview
140  if (actor && *actor) {
141  (*actor)->GetProperty()->SetColor(r, g, b);
142  (*actor)->Modified(); // Mark actor as modified for VTK pipeline
143 
144  // Trigger immediate render to show color change
145  PclUtils::PCLVis* pclVis = getPCLVis();
146  if (pclVis) {
147  vtkRenderWindow* renWin = pclVis->getRenderWindow();
148  if (renWin) {
149  renWin->Render();
150  }
151  }
152  }
153 
154  // Emit signals for property change notification
155  if (changed) {
156  emit colorChanged(static_cast<int>(mode));
157  emit propertiesChanged();
158  }
159 
161  QString("[cvSelectionHighlighter] Color set for mode %1: "
162  "RGB(%2, %3, %4)")
163  .arg(mode)
164  .arg(r)
165  .arg(g)
166  .arg(b));
167  }
168 }
169 
170 //-----------------------------------------------------------------------------
172  HighlightMode mode) {
173  double oldOpacity = 0.0;
174  vtkSmartPointer<vtkActor>* actor = nullptr;
175 
176  switch (mode) {
177  case HOVER:
178  oldOpacity = m_hoverOpacity;
179  m_hoverOpacity = opacity;
180  actor = &m_hoverActor;
181  break;
182  case PRESELECTED:
183  oldOpacity = m_preselectedOpacity;
184  m_preselectedOpacity = opacity;
185  actor = &m_preselectedActor;
186  break;
187  case SELECTED:
188  oldOpacity = m_selectedOpacity;
189  m_selectedOpacity = opacity;
190  actor = &m_selectedActor;
191  break;
192  case BOUNDARY:
193  oldOpacity = m_boundaryOpacity;
194  m_boundaryOpacity = opacity;
195  actor = &m_boundaryActor;
196  break;
197  }
198 
199  // Update existing actor's opacity immediately for real-time preview
200  if (actor && *actor) {
201  (*actor)->GetProperty()->SetOpacity(opacity);
202  (*actor)->Modified(); // Mark actor as modified for VTK pipeline
203 
204  // Trigger immediate render to show opacity change
205  PclUtils::PCLVis* pclVis = getPCLVis();
206  if (pclVis) {
207  vtkRenderWindow* renWin = pclVis->getRenderWindow();
208  if (renWin) {
209  renWin->Render();
210  }
211  }
212  }
213 
214  // Emit signals for property change notification
215  if (oldOpacity != opacity) {
216  emit opacityChanged(static_cast<int>(mode));
217  emit propertiesChanged();
218  }
219 
221  QString("[cvSelectionHighlighter] Opacity set for mode %1: %2")
222  .arg(mode)
223  .arg(opacity));
224 }
225 
226 //-----------------------------------------------------------------------------
228  HighlightMode mode) const {
229  switch (mode) {
230  case HOVER:
231  return m_hoverColor;
232  case PRESELECTED:
233  return m_preselectedColor;
234  case SELECTED:
235  return m_selectedColor;
236  case BOUNDARY:
237  return m_boundaryColor;
238  default:
239  return nullptr;
240  }
241 }
242 
243 //-----------------------------------------------------------------------------
245  switch (mode) {
246  case HOVER:
247  return m_hoverOpacity;
248  case PRESELECTED:
249  return m_preselectedOpacity;
250  case SELECTED:
251  return m_selectedOpacity;
252  case BOUNDARY:
253  return m_boundaryOpacity;
254  default:
255  return 1.0; // Default to opaque
256  }
257 }
258 
259 //-----------------------------------------------------------------------------
261  m_enabled = enabled;
262  if (!m_enabled) {
263  clearHighlights();
264  }
265 }
266 
267 //-----------------------------------------------------------------------------
269  const vtkSmartPointer<vtkIdTypeArray>& selection,
270  int fieldAssociation,
271  HighlightMode mode) {
272  if (!m_enabled) {
273  CVLog::Warning("[cvSelectionHighlighter] Highlighter is disabled");
274  return false;
275  }
276 
277  if (!m_viewer) {
278  CVLog::Warning("[cvSelectionHighlighter] No viewer available");
279  return false;
280  }
281 
282  if (!selection) {
283  CVLog::Warning("[cvSelectionHighlighter] Selection array is null");
284  return false;
285  }
286 
287  // Get polyData using centralized ParaView-style method
288  // Note: In ParaView, the highlighter would get data from the
289  // representation, but here we use the getPolyDataForSelection() which
290  // handles the priority logic.
291  vtkPolyData* polyData = getPolyDataForSelection();
292  if (!polyData) {
293  CVLog::Error(
294  "[cvSelectionHighlighter::highlightSelection] No polyData "
295  "available for highlighting - this is a critical error!");
296  return false;
297  }
298 
299  CVLog::PrintVerbose(QString("[cvSelectionHighlighter] Got polyData with %1 "
300  "cells, %2 points")
301  .arg(polyData->GetNumberOfCells())
302  .arg(polyData->GetNumberOfPoints()));
303 
304  // Delegate to the explicit polyData overload
305  return highlightSelection(polyData, selection, fieldAssociation, mode);
306 }
307 
308 //-----------------------------------------------------------------------------
310  vtkPolyData* polyData,
311  const vtkSmartPointer<vtkIdTypeArray>& selection,
312  int fieldAssociation,
313  HighlightMode mode) {
314  if (!m_enabled || !m_viewer || !polyData || !selection) {
315  return false;
316  }
317 
318  if (selection->GetNumberOfTuples() == 0) {
319  return false;
320  }
321 
322  // Create highlight actor
323  // IMPORTANT: Store as vtkSmartPointer to prevent premature deletion!
325  createHighlightActor(polyData, selection, fieldAssociation, mode);
326  // If actor is nullptr, it's a normal case (e.g., invalid selection IDs),
327  // not an error - just return false silently
328  if (!actor) {
329  return false;
330  }
331 
332  // Remove old actor and add new one (ParaView-style multi-level)
333  QString actorId;
334 
335  switch (mode) {
336  case HOVER:
337  removeActorFromVisualizer(m_hoverActorId);
338  m_hoverActor = actor;
339  actorId = m_hoverActorId;
340  break;
341 
342  case PRESELECTED:
343  removeActorFromVisualizer(m_preselectedActorId);
344  m_preselectedActor = actor;
345  actorId = m_preselectedActorId;
346  break;
347 
348  case SELECTED:
349  removeActorFromVisualizer(m_selectedActorId);
350  m_selectedActor = actor;
351  actorId = m_selectedActorId;
352  break;
353 
354  case BOUNDARY:
355  removeActorFromVisualizer(m_boundaryActorId);
356  m_boundaryActor = actor;
357  actorId = m_boundaryActorId;
358  break;
359  }
360 
361  addActorToVisualizer(actor, actorId);
362  return true;
363 }
364 
365 //-----------------------------------------------------------------------------
367  const cvSelectionData& selectionData, HighlightMode mode) {
368  // High-level interface that accepts cvSelectionData directly
369  // This keeps UI code (like MainWindow) free from VTK types
370 
371  if (selectionData.isEmpty()) {
372  CVLog::Warning("[cvSelectionHighlighter] Selection data is empty");
373  return false;
374  }
375 
377  QString("[cvSelectionHighlighter] Highlighting %1 %2 in mode %3")
378  .arg(selectionData.count())
379  .arg(selectionData.fieldTypeString())
380  .arg(mode));
381 
382  // Delegate to the VTK-level implementation
383  bool success = highlightSelection(selectionData.vtkArray(),
384  selectionData.fieldAssociation(), mode);
385 
386  if (!success) {
387  CVLog::Error("[cvSelectionHighlighter] Failed to highlight selection");
388  }
389 
390  return success;
391 }
392 
393 //-----------------------------------------------------------------------------
394 bool cvSelectionHighlighter::highlightElement(vtkPolyData* polyData,
395  vtkIdType elementId,
396  int fieldAssociation) {
397  if (!m_enabled) {
399  "[cvSelectionHighlighter::highlightElement] Highlighter is "
400  "disabled");
401  return false;
402  }
403  if (!m_viewer) {
405  "[cvSelectionHighlighter::highlightElement] No viewer set - "
406  "call setVisualizer() first!");
407  return false;
408  }
409  if (!polyData) {
411  "[cvSelectionHighlighter::highlightElement] No polyData "
412  "provided");
413  return false;
414  }
415 
416  // Create single-element selection array
419  selection->InsertNextValue(elementId);
420 
421  // Use HOVER mode for single element highlighting
422  bool result =
423  highlightSelection(polyData, selection, fieldAssociation, HOVER);
424 
425  // Smart pointer handles cleanup automatically
426  return result;
427 }
428 
429 //-----------------------------------------------------------------------------
431  removeActorFromVisualizer(m_hoverActorId);
432  removeActorFromVisualizer(m_preselectedActorId);
433  removeActorFromVisualizer(m_selectedActorId);
434  removeActorFromVisualizer(m_boundaryActorId);
435 
436  m_hoverActor = nullptr;
437  m_preselectedActor = nullptr;
438  m_selectedActor = nullptr;
439  m_boundaryActor = nullptr;
440 
441  // Force render to show changes immediately
442  PclUtils::PCLVis* pclVis = getPCLVis();
443  if (pclVis) {
444  vtkRenderWindow* renWin = pclVis->getRenderWindow();
445  if (renWin) {
446  renWin->Render();
447  }
448  }
449 }
450 
451 //-----------------------------------------------------------------------------
453  // Remove ONLY hover highlight, keep selected/preselected/boundary
454  // This is used during hover updates to avoid clearing persistent selections
455  removeActorFromVisualizer(m_hoverActorId);
456  m_hoverActor = nullptr;
457 }
458 
459 //-----------------------------------------------------------------------------
461  // Set visibility of all highlight actors
462  // Used to temporarily hide highlights during hardware selection
463  // to prevent depth buffer occlusion issues with subtract selection
464 
465  if (m_hoverActor) {
466  m_hoverActor->SetVisibility(visible ? 1 : 0);
467  }
468  if (m_preselectedActor) {
469  m_preselectedActor->SetVisibility(visible ? 1 : 0);
470  }
471  if (m_selectedActor) {
472  m_selectedActor->SetVisibility(visible ? 1 : 0);
473  }
474  if (m_boundaryActor) {
475  m_boundaryActor->SetVisibility(visible ? 1 : 0);
476  }
477 
478  // Force render update
479  PclUtils::PCLVis* pclVis = getPCLVis();
480  if (pclVis) {
481  vtkRenderWindow* renWin = pclVis->getRenderWindow();
482  if (renWin) {
483  renWin->Render();
484  }
485  }
486 }
487 
488 //-----------------------------------------------------------------------------
489 vtkSmartPointer<vtkActor> cvSelectionHighlighter::createHighlightActor(
490  vtkPolyData* polyData,
491  vtkIdTypeArray* selection,
492  int fieldAssociation,
493  HighlightMode mode) {
494  if (!polyData || !selection) {
495  return nullptr;
496  }
497 
498  // Validate selection IDs against polyData bounds and filter out invalid IDs
499  // This prevents extraction failures and error logs for invalid selections
500  // (e.g., when selection IDs are out of range or data has changed)
501  bool isPointSelection =
502  (fieldAssociation != 0); // 0 = CELL, non-zero = POINT
503  vtkIdType maxValidId = isPointSelection ? polyData->GetNumberOfPoints()
504  : polyData->GetNumberOfCells();
505 
506  // Filter out invalid IDs to prevent extraction failures
507  vtkSmartPointer<vtkIdTypeArray> validSelection =
509  for (vtkIdType i = 0; i < selection->GetNumberOfTuples(); ++i) {
510  vtkIdType id = selection->GetValue(i);
511  if (id >= 0 && id < maxValidId) {
512  validSelection->InsertNextValue(id);
513  }
514  }
515 
516  // If no valid IDs remain, this is a normal case (invalid selection),
517  // not an error - return nullptr silently
518  if (validSelection->GetNumberOfTuples() == 0) {
520  QString("[cvSelectionHighlighter::createHighlightActor] No "
521  "valid "
522  "selection IDs (all %1 IDs are out of range [0, %2))")
523  .arg(selection->GetNumberOfTuples())
524  .arg(maxValidId));
525  return nullptr;
526  }
527 
528  // Create selection node using validated IDs
529  vtkSmartPointer<vtkSelectionNode> selectionNode =
530  createSelectionNode(validSelection, fieldAssociation);
531  if (!selectionNode) {
532  CVLog::Error(
533  "[cvSelectionHighlighter::createHighlightActor] Failed to "
534  "create selection node");
535  return nullptr;
536  }
537 
538  // Create selection
540  vtkSel->AddNode(selectionNode);
541 
542  // Extract selected elements
545  extractor->SetInputData(0, polyData);
546  extractor->SetInputData(1, vtkSel);
547 
548  extractor->Update();
549 
550  vtkUnstructuredGrid* extracted =
551  vtkUnstructuredGrid::SafeDownCast(extractor->GetOutput());
552 
553  if (!extracted) {
554  CVLog::Error(
555  "[cvSelectionHighlighter::createHighlightActor] Extraction "
556  "failed: extracted is nullptr");
557  return nullptr;
558  }
559 
560  vtkIdType numCells = extracted->GetNumberOfCells();
561  vtkIdType numPoints = extracted->GetNumberOfPoints();
562 
563  // After validating IDs upfront, this should rarely happen, but if it does,
564  // it's still a normal case (e.g., degenerate geometry) - not an error
565  if (numCells == 0 && numPoints == 0) {
567  "[cvSelectionHighlighter::createHighlightActor] Extraction "
568  "resulted in 0 cells and 0 points (normal case, e.g., "
569  "degenerate geometry)");
570  return nullptr;
571  }
572 
573  // Create mapper
576  mapper->SetInputData(extracted);
577  mapper->ScalarVisibilityOff(); // Don't use scalar colors
578 
579  // Create actor (use smart pointer to prevent leaks)
581  actor->SetMapper(mapper);
582 
583  // Set properties based on mode (ParaView-style)
584  vtkProperty* prop = actor->GetProperty();
585  double* color = nullptr;
586  double opacity = 1.0;
587 
588  switch (mode) {
589  case HOVER:
590  // Hover feedback (ParaView-style)
591  color = m_hoverColor;
592  opacity = m_hoverOpacity;
593  prop->SetLineWidth(static_cast<float>(m_hoverLineWidth));
594  prop->SetPointSize(static_cast<float>(m_hoverPointSize));
595  prop->SetRenderLinesAsTubes(true);
596  break;
597 
598  case PRESELECTED:
599  // Preselect feedback (ParaView-style)
600  color = m_preselectedColor;
601  opacity = m_preselectedOpacity;
602  prop->SetLineWidth(static_cast<float>(m_preselectedLineWidth));
603  prop->SetPointSize(static_cast<float>(m_preselectedPointSize));
604  prop->SetRenderLinesAsTubes(true);
605  break;
606 
607  case SELECTED:
608  // Final selection (ParaView-style)
609  color = m_selectedColor;
610  opacity = m_selectedOpacity;
611  prop->SetLineWidth(static_cast<float>(m_selectedLineWidth));
612  prop->SetPointSize(static_cast<float>(m_selectedPointSize));
613  prop->SetRenderLinesAsTubes(true);
614  break;
615 
616  case BOUNDARY:
617  // Boundary highlight (ParaView-style)
618  color = m_boundaryColor;
619  opacity = m_boundaryOpacity;
620  prop->SetLineWidth(static_cast<float>(m_boundaryLineWidth));
621  prop->SetPointSize(static_cast<float>(m_boundaryPointSize));
622  prop->SetRenderLinesAsTubes(true);
623  break;
624  }
625 
626  if (color) {
627  prop->SetColor(color[0], color[1], color[2]);
628  }
629  prop->SetOpacity(opacity);
630 
631  // Enhanced rendering properties for maximum visibility
632  prop->SetAmbient(0.6); // Increased ambient light for brighter appearance
633  prop->SetDiffuse(0.8); // Increased diffuse for better color saturation
634  prop->SetSpecular(0.5); // Increased specular for more shine/visibility
635  prop->SetSpecularPower(
636  30.0); // Higher specular power for sharper highlights
637  prop->SetRenderLinesAsTubes(true); // Render lines as 3D tubes
638  prop->SetRenderPointsAsSpheres(true); // Render points as 3D spheres
639 
640  // Additional enhancement: edge visibility
641  prop->EdgeVisibilityOn(); // Show edges for better definition
642  prop->SetEdgeColor(1.0, 1.0, 1.0); // White edges for contrast
643 
644  // Set representation mode based on field association (enhanced for
645  // visibility) 0 = CELLS: show as wireframe (edges only) 1 = POINTS: show as
646  // points
647  if (fieldAssociation == 0) {
648  // CELLS: Render as wireframe (edges only)
649  prop->SetRepresentationToWireframe();
650  // Use the configured line width for the current mode (consistent with
651  // hover) Line width is already set above based on mode
652  // (hover/preselected/selected/boundary) No need to override it here -
653  // keep it consistent
654  } else {
655  // POINTS: Render as points with enhanced visibility
656  prop->SetRepresentationToPoints();
657  // Point size is already set above based on mode, ensure it's visible
658  }
659 
660  // CRITICAL: Set highlight actor as NOT pickable!
661  // This prevents hardware selection from picking highlight actors.
662  // When users try to add/subtract selection in highlighted areas,
663  // we want them to select from the original data, not the highlight actor.
664  // Without this, subtract selection would select IDs from the highlight
665  // actor (0 to N-1) instead of the original point cloud IDs.
666  actor->SetPickable(false);
667 
668  return actor;
669 }
670 
671 //-----------------------------------------------------------------------------
672 vtkSmartPointer<vtkSelectionNode> cvSelectionHighlighter::createSelectionNode(
673  vtkIdTypeArray* selection, int fieldAssociation) {
674  if (!selection) {
675  return nullptr;
676  }
677 
680  node->SetContentType(vtkSelectionNode::INDICES);
681 
682  if (fieldAssociation == 0) {
683  node->SetFieldType(vtkSelectionNode::CELL);
684  } else {
685  node->SetFieldType(vtkSelectionNode::POINT);
686  }
687 
688  node->SetSelectionList(selection);
689 
690  return node;
691 }
692 
693 //-----------------------------------------------------------------------------
694 void cvSelectionHighlighter::addActorToVisualizer(vtkActor* actor,
695  const QString& id) {
696  if (!m_viewer) {
697  CVLog::Error(
698  "[cvSelectionHighlighter::addActorToVisualizer] No viewer!");
699  return;
700  }
701 
702  if (!actor) {
703  CVLog::Error(
704  "[cvSelectionHighlighter::addActorToVisualizer] No actor!");
705  return;
706  }
707 
708  // Get PCLVis for VTK operations
709  PclUtils::PCLVis* pclVis = getPCLVis();
710  if (!pclVis) {
711  CVLog::Error(
712  "[cvSelectionHighlighter::addActorToVisualizer] Visualizer is "
713  "not PCLVis");
714  return;
715  }
716 
717  // Get current renderer
718  vtkRenderer* renderer = pclVis->getCurrentRenderer();
719  if (!renderer) {
720  CVLog::Error(
721  "[cvSelectionHighlighter::addActorToVisualizer] No renderer!");
722  return;
723  }
724 
725  // CRITICAL: Configure mapper to render highlights ON TOP of main cloud
726  // This prevents Z-fighting and ensures highlights are always visible
727  vtkMapper* mapper = actor->GetMapper();
728  if (mapper) {
729  // Use polygon offset to push highlights slightly forward
730  mapper->SetResolveCoincidentTopologyToPolygonOffset();
731  mapper->SetRelativeCoincidentTopologyPolygonOffsetParameters(-1.0,
732  -1.0);
733  mapper->SetResolveCoincidentTopologyPolygonOffsetFaces(1);
734  }
735 
736  // Add actor to renderer
737  renderer->AddActor(actor);
738  actor->SetVisibility(1); // Ensure visibility is on
739 
740  // Trigger immediate render update (ParaView-style)
741  vtkRenderWindow* renderWindow = renderer->GetRenderWindow();
742  if (renderWindow) {
743  renderWindow->Render();
744  CVLog::PrintVerbose("[cvSelectionHighlighter] Triggered render update");
745  } else {
746  CVLog::Warning("[cvSelectionHighlighter] No render window to update!");
747  }
748 }
749 
750 //-----------------------------------------------------------------------------
751 void cvSelectionHighlighter::removeActorFromVisualizer(const QString& id) {
752  if (!m_viewer) {
753  return;
754  }
755 
756  // Get PCLVis for VTK operations
757  PclUtils::PCLVis* pclVis = getPCLVis();
758  if (!pclVis) {
759  return;
760  }
761 
762  vtkRenderer* renderer = pclVis->getCurrentRenderer();
763  if (!renderer) {
764  return;
765  }
766 
767  // Remove corresponding actor (ParaView-style multi-level)
768  vtkActor* actor = nullptr;
769  if (id == m_hoverActorId && m_hoverActor) {
770  actor = m_hoverActor;
771  } else if (id == m_preselectedActorId && m_preselectedActor) {
772  actor = m_preselectedActor;
773  } else if (id == m_selectedActorId && m_selectedActor) {
774  actor = m_selectedActor;
775  } else if (id == m_boundaryActorId && m_boundaryActor) {
776  actor = m_boundaryActor;
777  }
778 
779  if (actor) {
780  renderer->RemoveActor(actor);
781 
782  // Trigger immediate render update (ParaView-style)
783  vtkRenderWindow* renderWindow = renderer->GetRenderWindow();
784  if (renderWindow) {
785  renderWindow->Render();
786  }
787 
789  QString("[cvSelectionHighlighter] Removed highlight actor: %1")
790  .arg(id));
791  }
792 }
793 
794 //-----------------------------------------------------------------------------
796  int oldSize = 0;
797  vtkSmartPointer<vtkActor>* actor = nullptr;
798 
799  switch (mode) {
800  case HOVER:
801  oldSize = m_hoverPointSize;
802  m_hoverPointSize = size;
803  actor = &m_hoverActor;
804  break;
805  case PRESELECTED:
806  oldSize = m_preselectedPointSize;
807  m_preselectedPointSize = size;
808  actor = &m_preselectedActor;
809  break;
810  case SELECTED:
811  oldSize = m_selectedPointSize;
812  m_selectedPointSize = size;
813  actor = &m_selectedActor;
814  break;
815  case BOUNDARY:
816  oldSize = m_boundaryPointSize;
817  m_boundaryPointSize = size;
818  actor = &m_boundaryActor;
819  break;
820  }
821 
822  // Update existing actor's point size immediately for real-time preview
823  if (actor && *actor) {
824  (*actor)->GetProperty()->SetPointSize(static_cast<float>(size));
825  }
826 
827  // Emit signals for property change notification
828  if (oldSize != size) {
829  emit pointSizeChanged(static_cast<int>(mode));
830  emit propertiesChanged();
831  }
832 
834  QString("[cvSelectionHighlighter] Point size set for mode %1: %2")
835  .arg(mode)
836  .arg(size));
837 }
838 
839 //-----------------------------------------------------------------------------
841  switch (mode) {
842  case HOVER:
843  return m_hoverPointSize;
844  case PRESELECTED:
845  return m_preselectedPointSize;
846  case SELECTED:
847  return m_selectedPointSize;
848  case BOUNDARY:
849  return m_boundaryPointSize;
850  default:
851  return 5; // ParaView default: SelectionPointSize = 5
852  }
853 }
854 
855 //-----------------------------------------------------------------------------
857  int oldWidth = 0;
858  vtkSmartPointer<vtkActor>* actor = nullptr;
859 
860  switch (mode) {
861  case HOVER:
862  oldWidth = m_hoverLineWidth;
863  m_hoverLineWidth = width;
864  actor = &m_hoverActor;
865  break;
866  case PRESELECTED:
867  oldWidth = m_preselectedLineWidth;
868  m_preselectedLineWidth = width;
869  actor = &m_preselectedActor;
870  break;
871  case SELECTED:
872  oldWidth = m_selectedLineWidth;
873  m_selectedLineWidth = width;
874  actor = &m_selectedActor;
875  break;
876  case BOUNDARY:
877  oldWidth = m_boundaryLineWidth;
878  m_boundaryLineWidth = width;
879  actor = &m_boundaryActor;
880  break;
881  }
882 
883  // Update existing actor's line width immediately for real-time preview
884  if (actor && *actor) {
885  (*actor)->GetProperty()->SetLineWidth(static_cast<float>(width));
886  }
887 
888  // Emit signals for property change notification
889  if (oldWidth != width) {
890  emit lineWidthChanged(static_cast<int>(mode));
891  emit propertiesChanged();
892  }
893 
895  QString("[cvSelectionHighlighter] Line width set for mode %1: %2")
896  .arg(mode)
897  .arg(width));
898 }
899 
900 //-----------------------------------------------------------------------------
902  switch (mode) {
903  case HOVER:
904  return m_hoverLineWidth;
905  case PRESELECTED:
906  return m_preselectedLineWidth;
907  case SELECTED:
908  return m_selectedLineWidth;
909  case BOUNDARY:
910  return m_boundaryLineWidth;
911  default:
912  return 2;
913  }
914 }
915 
916 //-----------------------------------------------------------------------------
918  bool interactive) const {
919  return interactive ? m_interactiveLabelProperties : m_labelProperties;
920 }
921 
922 //-----------------------------------------------------------------------------
924  const SelectionLabelProperties& props, bool interactive) {
925  SelectionLabelProperties& target =
926  interactive ? m_interactiveLabelProperties : m_labelProperties;
927 
928  // Check if properties changed (including label font properties)
929  bool changed = (target.opacity != props.opacity ||
930  target.pointSize != props.pointSize ||
931  target.lineWidth != props.lineWidth ||
932  target.pointLabelFontSize != props.pointLabelFontSize ||
933  target.pointLabelColor != props.pointLabelColor ||
934  target.pointLabelBold != props.pointLabelBold ||
935  target.pointLabelItalic != props.pointLabelItalic ||
936  target.pointLabelShadow != props.pointLabelShadow ||
937  target.cellLabelFontSize != props.cellLabelFontSize ||
938  target.cellLabelColor != props.cellLabelColor ||
939  target.cellLabelBold != props.cellLabelBold ||
940  target.cellLabelItalic != props.cellLabelItalic ||
941  target.cellLabelShadow != props.cellLabelShadow);
942 
943  target = props;
944 
945  // Apply relevant properties to highlight modes
946  HighlightMode mode = interactive ? HOVER : SELECTED;
947  setHighlightOpacity(props.opacity, mode);
948  setPointSize(props.pointSize, mode);
949  setLineWidth(props.lineWidth, mode);
950 
951  // Also apply to related mode
952  if (interactive) {
956  } else {
960  }
961 
962  if (changed) {
963  // Update existing label actors with new properties
964  if (m_pointLabelVisible) {
965  updateLabelActor(true); // true = point labels
966  }
967  if (m_cellLabelVisible) {
968  updateLabelActor(false); // false = cell labels
969  }
970 
971  emit labelPropertiesChanged(interactive);
972  emit propertiesChanged();
973  }
974 
976  QString("[cvSelectionPropertiesWidget] Label properties applied: "
977  "opacity=%1, pointSize=%2, lineWidth=%3")
978  .arg(props.opacity)
979  .arg(props.pointSize)
980  .arg(props.lineWidth));
981 }
982 
983 //-----------------------------------------------------------------------------
984 void cvSelectionHighlighter::setPointLabelArray(const QString& arrayName,
985  bool visible) {
986  m_pointLabelArrayName = arrayName;
987  m_pointLabelVisible = visible && !arrayName.isEmpty();
988 
990  QString("[cvSelectionHighlighter] Point label array set: "
991  "'%1', visible=%2")
992  .arg(arrayName)
993  .arg(m_pointLabelVisible));
994 
995  // Update label rendering
996  updateLabelActor(true); // true = point labels
997 
998  // Trigger re-render
999  emit labelPropertiesChanged(false);
1000 }
1001 
1002 //-----------------------------------------------------------------------------
1003 void cvSelectionHighlighter::setCellLabelArray(const QString& arrayName,
1004  bool visible) {
1005  m_cellLabelArrayName = arrayName;
1006  m_cellLabelVisible = visible && !arrayName.isEmpty();
1007 
1009  QString("[cvSelectionHighlighter] Cell label array set: "
1010  "'%1', visible=%2")
1011  .arg(arrayName)
1012  .arg(m_cellLabelVisible));
1013 
1014  // Update label rendering
1015  updateLabelActor(false); // false = cell labels
1016 
1017  // Trigger re-render
1018  emit labelPropertiesChanged(false);
1019 }
1020 
1021 //-----------------------------------------------------------------------------
1022 void cvSelectionHighlighter::updateLabelActor(bool isPointLabels) {
1023  PclUtils::PCLVis* pclVis = getPCLVis();
1024  if (!pclVis) {
1025  CVLog::Warning("[cvSelectionHighlighter] No visualizer for labels");
1026  return;
1027  }
1028 
1029  vtkRenderer* renderer = pclVis->getRendererCollection()->GetFirstRenderer();
1030  if (!renderer) {
1031  CVLog::Warning("[cvSelectionHighlighter] No renderer for labels");
1032  return;
1033  }
1034 
1035  // Get current label configuration
1036  QString arrayName =
1037  isPointLabels ? m_pointLabelArrayName : m_cellLabelArrayName;
1038  bool visible = isPointLabels ? m_pointLabelVisible : m_cellLabelVisible;
1039  vtkSmartPointer<vtkActor2D>& labelActor =
1040  isPointLabels ? m_pointLabelActor : m_cellLabelActor;
1041 
1042  // Remove existing label actor
1043  if (labelActor) {
1044  renderer->RemoveActor2D(labelActor);
1045  labelActor = nullptr;
1046  }
1047 
1048  if (!visible || arrayName.isEmpty()) {
1049  // Labels disabled - ensure proper cleanup and refresh
1051  QString("[cvSelectionHighlighter] Clearing %1 labels")
1052  .arg(isPointLabels ? "point" : "cell"));
1053  // Force render to update the view
1054  if (pclVis->getRenderWindow()) {
1055  pclVis->getRenderWindow()->Modified();
1056  pclVis->getRenderWindow()->Render();
1057  }
1058  return;
1059  }
1060 
1061  // Get the current selection highlight actor's data
1062  vtkSmartPointer<vtkActor>& highlightActor = m_selectedActor;
1063  if (!highlightActor) {
1065  "[cvSelectionHighlighter] No highlight actor for labels");
1066  return;
1067  }
1068 
1069  vtkMapper* mapper = highlightActor->GetMapper();
1070  if (!mapper) {
1071  return;
1072  }
1073 
1074  vtkDataSet* data = mapper->GetInput();
1075  if (!data || data->GetNumberOfPoints() == 0) {
1076  return;
1077  }
1078 
1079  // ParaView-style: Use vtkMaskPoints to limit number of labels for
1080  // performance This prevents lag when there are many selected points/cells
1081  // Reference: ParaView/Remoting/Views/vtkDataLabelRepresentation.cxx
1082  const int maxLabels =
1083  m_labelProperties.maxTooltipAttributes > 0
1084  ? qMin(100, m_labelProperties.maxTooltipAttributes * 10)
1085  : 100; // ParaView default: MaximumNumberOfLabels = 100
1086 
1087  vtkSmartPointer<vtkMaskPoints> maskFilter =
1089 
1090  // IMPORTANT: For cell labels, we need to convert cells to their center
1091  // points first ParaView does this with vtkCellCenters (line 43-58 in
1092  // vtkDataLabelRepresentation.cxx)
1093  if (!isPointLabels) {
1094  // Cell labels: convert cells to center points
1095  vtkSmartPointer<vtkCellCenters> cellCenters =
1097  cellCenters->SetInputData(data);
1098  cellCenters->Update();
1099 
1101  QString("[cvSelectionHighlighter] Cell centers: %1 cells -> %2 "
1102  "points")
1103  .arg(data->GetNumberOfCells())
1104  .arg(cellCenters->GetOutput()->GetNumberOfPoints()));
1105 
1106  // Feed cell centers to mask filter
1107  maskFilter->SetInputConnection(cellCenters->GetOutputPort());
1108  } else {
1109  // Point labels: use data directly
1110  maskFilter->SetInputData(data);
1111  }
1112 
1113  maskFilter->SetMaximumNumberOfPoints(maxLabels);
1114  maskFilter->SetOnRatio(1);
1115  maskFilter->RandomModeOn(); // ParaView uses random sampling
1116  maskFilter->Update();
1117 
1119  QString("[cvSelectionHighlighter] Label mask: %1 points "
1120  "-> %2 labels (max %3)")
1121  .arg(data->GetNumberOfPoints())
1122  .arg(maskFilter->GetOutput()->GetNumberOfPoints())
1123  .arg(maxLabels));
1124 
1125  // Create label mapper with masked input for performance
1128  labelMapper->SetInputConnection(maskFilter->GetOutputPort());
1129 
1130  // Configure label mode based on array name
1131  // ParaView uses vtkOriginalPointIds/vtkOriginalCellIds for ID labels
1132  // Reference: ParaView/Qt/Components/pqFindDataSelectionDisplayFrame.cxx
1133  // line 155-157
1134  if (arrayName == "ID" || arrayName == "PointID" || arrayName == "CellID") {
1135  // Use vtkOriginalPointIds/vtkOriginalCellIds added by
1136  // vtkExtractSelection This shows the actual Point ID from the
1137  // spreadsheet, not 0-based index
1138  labelMapper->SetLabelModeToLabelFieldData();
1139  const char* idArrayName =
1140  isPointLabels ? "vtkOriginalPointIds" : "vtkOriginalCellIds";
1141  labelMapper->SetFieldDataName(idArrayName);
1142  } else {
1143  // Verify that the field exists in the data before setting it
1144  // This prevents VTK warnings "Failed to find match for field 'xxx'"
1145  vtkDataSetAttributes* attrData = nullptr;
1146  if (isPointLabels) {
1147  attrData = maskFilter->GetOutput()->GetPointData();
1148  } else {
1149  attrData = maskFilter->GetOutput()
1150  ->GetPointData(); // Cell centers are converted
1151  // to points
1152  }
1153 
1154  const char* arrayNameCStr = arrayName.toUtf8().constData();
1155  vtkAbstractArray* array =
1156  attrData ? attrData->GetArray(arrayNameCStr) : nullptr;
1157 
1158  if (array) {
1159  // Field exists - set it for labels
1160  labelMapper->SetLabelModeToLabelFieldData();
1161  labelMapper->SetFieldDataName(arrayNameCStr);
1162  } else {
1163  // Field not found - log warning and disable labels to avoid VTK
1164  // warnings
1165  CVLog::Warning(QString("[cvSelectionHighlighter] Label field '%1' "
1166  "not found in %2 data, labels disabled")
1167  .arg(arrayName)
1168  .arg(isPointLabels ? "point" : "cell"));
1169  // Don't add the label actor if field doesn't exist
1170  return;
1171  }
1172  }
1173 
1174  // Configure text properties (ParaView-style)
1175  vtkTextProperty* textProp = labelMapper->GetLabelTextProperty();
1176  if (textProp) {
1177  const SelectionLabelProperties& props = m_labelProperties;
1178  if (isPointLabels) {
1179  textProp->SetFontSize(props.pointLabelFontSize);
1180  textProp->SetColor(props.pointLabelColor.redF(),
1181  props.pointLabelColor.greenF(),
1182  props.pointLabelColor.blueF());
1183  textProp->SetOpacity(props.pointLabelOpacity);
1184  textProp->SetBold(props.pointLabelBold);
1185  textProp->SetItalic(props.pointLabelItalic);
1186  textProp->SetShadow(props.pointLabelShadow);
1187  } else {
1188  textProp->SetFontSize(props.cellLabelFontSize);
1189  textProp->SetColor(props.cellLabelColor.redF(),
1190  props.cellLabelColor.greenF(),
1191  props.cellLabelColor.blueF());
1192  textProp->SetOpacity(props.cellLabelOpacity);
1193  textProp->SetBold(props.cellLabelBold);
1194  textProp->SetItalic(props.cellLabelItalic);
1195  textProp->SetShadow(props.cellLabelShadow);
1196  }
1197  }
1198 
1199  // Create label actor
1200  labelActor = vtkSmartPointer<vtkActor2D>::New();
1201  labelActor->SetMapper(labelMapper);
1202 
1203  // Add to renderer
1204  renderer->AddActor2D(labelActor);
1205 
1207  QString("[cvSelectionHighlighter] Added %1 labels with array '%2'")
1208  .arg(isPointLabels ? "point" : "cell")
1209  .arg(arrayName));
1210 
1211  // Refresh view
1212  if (pclVis->getRenderWindow()) {
1213  pclVis->getRenderWindow()->Render();
1214  }
1215 }
1216 
1217 //-----------------------------------------------------------------------------
1219  const double* color = getHighlightColor(mode);
1220  if (color) {
1221  return QColor::fromRgbF(color[0], color[1], color[2]);
1222  }
1223  return QColor(255, 0, 255); // Default magenta
1224 }
1225 
1226 //-----------------------------------------------------------------------------
1228  HighlightMode mode) {
1229  setHighlightColor(color.redF(), color.greenF(), color.blueF(), mode);
1230 }
1231 
1232 //=============================================================================
1233 // cvTooltipFormatter Implementation (merged from cvTooltipFormatter.cpp)
1234 //=============================================================================
1235 
1236 #include <QtCompat.h>
1237 #include <vtkCell.h>
1238 #include <vtkCellData.h>
1239 #include <vtkDataArray.h>
1240 #include <vtkFieldData.h>
1241 #include <vtkPointData.h>
1242 #include <vtkStringArray.h>
1243 
1244 #include <QStringList>
1245 
1246 //-----------------------------------------------------------------------------
1247 cvTooltipFormatter::cvTooltipFormatter() : m_maxAttributes(15) {}
1248 
1249 //-----------------------------------------------------------------------------
1251 
1252 //-----------------------------------------------------------------------------
1254  m_maxAttributes = maxAttribs;
1255 }
1256 
1257 //-----------------------------------------------------------------------------
1258 QString cvTooltipFormatter::getTooltipInfo(vtkPolyData* polyData,
1259  vtkIdType elementId,
1260  AssociationType association,
1261  const QString& datasetName) {
1262  if (!polyData) {
1263  CVLog::Error("[cvTooltipFormatter] Invalid polyData");
1264  return QString();
1265  }
1266 
1267  if (association == POINTS) {
1268  return formatPointTooltip(polyData, elementId, datasetName);
1269  } else {
1270  return formatCellTooltip(polyData, elementId, datasetName);
1271  }
1272 }
1273 
1274 //-----------------------------------------------------------------------------
1275 QString cvTooltipFormatter::getPlainTooltipInfo(vtkPolyData* polyData,
1276  vtkIdType elementId,
1277  AssociationType association,
1278  const QString& datasetName) {
1279  // Get HTML tooltip and strip HTML tags
1280  QString htmlTooltip =
1281  getTooltipInfo(polyData, elementId, association, datasetName);
1282 
1283  // Simple HTML tag removal
1284  QString plainText = htmlTooltip;
1285  plainText.remove(QtCompatRegExp("<[^>]*>"));
1286  plainText.replace("&nbsp;", " ");
1287 
1288  return plainText;
1289 }
1290 
1291 //-----------------------------------------------------------------------------
1292 QString cvTooltipFormatter::formatPointTooltip(vtkPolyData* polyData,
1293  vtkIdType pointId,
1294  const QString& datasetName) {
1295  if (pointId < 0 || pointId >= polyData->GetNumberOfPoints()) {
1296  CVLog::Error("[cvTooltipFormatter] Invalid point ID: %lld", pointId);
1297  return QString();
1298  }
1299 
1300  QString tooltip;
1301 
1302  // ParaView format: Dataset name as first line (no "Block:" prefix)
1303  if (!datasetName.isEmpty()) {
1304  tooltip += QString("<b>%1</b>").arg(datasetName);
1305  }
1306 
1307  // ParaView format: Point ID (with 2-space indent like cell tooltip)
1308  tooltip += QString("\n Id: %1").arg(pointId);
1309 
1310  // ParaView format: Coordinates with consistent formatting
1311  double point[3];
1312  polyData->GetPoint(pointId, point);
1313  tooltip += QString("\n Coords: (%1, %2, %3)")
1314  .arg(point[0], 0, 'g', 6)
1315  .arg(point[1], 0, 'g', 6)
1316  .arg(point[2], 0, 'g', 6);
1317 
1318  // ParaView format: Point data arrays
1319  vtkPointData* pointData = polyData->GetPointData();
1320  if (pointData) {
1321  int numArrays = pointData->GetNumberOfArrays();
1322  int displayedArrays = 0; // Counter for limiting displayed attributes
1323 
1324  // Show RGB colors if available
1325  vtkUnsignedCharArray* colorArray = nullptr;
1326  const char* colorNames[] = {"RGB", "Colors", "rgba", "rgb"};
1327  for (const char* name : colorNames) {
1328  vtkDataArray* arr = pointData->GetArray(name);
1329  if (arr && (arr->GetNumberOfComponents() == 3 ||
1330  arr->GetNumberOfComponents() == 4)) {
1331  colorArray = vtkUnsignedCharArray::SafeDownCast(arr);
1332  if (colorArray) break;
1333  }
1334  }
1335  if (colorArray) {
1336  unsigned char color[4] = {0, 0, 0, 255};
1337  colorArray->GetTypedTuple(pointId, color);
1338  tooltip += QString("\n RGB: (%1, %2, %3)")
1339  .arg(color[0])
1340  .arg(color[1])
1341  .arg(color[2]);
1342  displayedArrays++;
1343  }
1344 
1345  // Show normals if available (ParaView style)
1346  // First try active normals (set via SetNormals), then named "Normals"
1347  // array
1348  vtkDataArray* normalsArray = pointData->GetNormals();
1349  if (!normalsArray) {
1350  // Fallback: look for array named "Normals" with 3 components
1351  normalsArray = pointData->GetArray("Normals");
1352  // Only use if it has 3 components (actual normals, not curvature)
1353  if (normalsArray && normalsArray->GetNumberOfComponents() != 3) {
1354  normalsArray = nullptr;
1355  }
1356  }
1357  if (normalsArray && normalsArray->GetNumberOfComponents() == 3) {
1358  double* normal = normalsArray->GetTuple3(pointId);
1359  // ParaView format: "Normals: (x, y, z)" with consistent formatting
1360  tooltip += QString("\n Normals: (%1, %2, %3)")
1361  .arg(normal[0], 0, 'g', 6)
1362  .arg(normal[1], 0, 'g', 6)
1363  .arg(normal[2], 0, 'g', 6);
1364  displayedArrays++;
1365  }
1366 
1367  // Show texture coordinates (ParaView style: "TCoords: (x, y)")
1368  // Look for texture coordinate arrays - prioritize "TCoords" or arrays
1369  // starting with "TCoords"
1370  vtkDataArray* tcoordsArray = nullptr;
1371  QString tcoordsArrayName;
1372 
1373  for (int i = 0; i < numArrays; ++i) {
1374  vtkDataArray* array = pointData->GetArray(i);
1375  if (!array) continue;
1376 
1377  QString arrayName = QString::fromUtf8(array->GetName());
1378  int numComp = array->GetNumberOfComponents();
1379 
1380  // Check if this is texture coordinates (2 or 3 components)
1381  if ((numComp == 2 || numComp == 3) &&
1382  (arrayName.compare("TCoords", Qt::CaseInsensitive) == 0 ||
1383  arrayName.startsWith("TCoords", Qt::CaseInsensitive) ||
1384  arrayName.contains("texture", Qt::CaseInsensitive) ||
1385  arrayName.contains("tcoords", Qt::CaseInsensitive) ||
1386  arrayName.contains("uv", Qt::CaseInsensitive))) {
1387  // Prefer exact "TCoords" match, otherwise use first match
1388  if (arrayName.compare("TCoords", Qt::CaseInsensitive) == 0 ||
1389  !tcoordsArray) {
1390  tcoordsArray = array;
1391  tcoordsArrayName = arrayName;
1392  // If we found exact "TCoords", use it
1393  if (arrayName.compare("TCoords", Qt::CaseInsensitive) ==
1394  0) {
1395  break;
1396  }
1397  }
1398  }
1399  }
1400 
1401  if (tcoordsArray && displayedArrays < m_maxAttributes) {
1402  int numComp = tcoordsArray->GetNumberOfComponents();
1403  if (numComp == 2) {
1404  double* tc = tcoordsArray->GetTuple2(pointId);
1405  // ParaView format: "TCoords: (x, y)" - always use "TCoords" as
1406  // label
1407  tooltip += QString("\n TCoords: (%1, %2)")
1408  .arg(tc[0], 0, 'g', 6)
1409  .arg(tc[1], 0, 'g', 6);
1410  } else if (numComp == 3) {
1411  double* tc = tcoordsArray->GetTuple3(pointId);
1412  tooltip += QString("\n TCoords: (%1, %2, %3)")
1413  .arg(tc[0], 0, 'g', 6)
1414  .arg(tc[1], 0, 'g', 6)
1415  .arg(tc[2], 0, 'g', 6);
1416  }
1417  displayedArrays++;
1418  }
1419 
1420  // Show scalars if available (after normals and texture coords)
1421  if (pointData->GetScalars() && displayedArrays < m_maxAttributes) {
1422  QString scalarName =
1423  QString::fromUtf8(pointData->GetScalars()->GetName());
1424  if (!scalarName.contains("texture", Qt::CaseInsensitive) &&
1425  !scalarName.contains("tcoord", Qt::CaseInsensitive)) {
1426  double scalar = pointData->GetScalars()->GetTuple1(pointId);
1427  tooltip += QString("\n %1: %2")
1428  .arg(scalarName.isEmpty() ? "Scalars"
1429  : scalarName)
1430  .arg(formatNumber(scalar));
1431  displayedArrays++;
1432  }
1433  }
1434 
1435  // Add additional point data arrays (custom attributes)
1436  for (int i = 0; i < pointData->GetNumberOfArrays(); ++i) {
1437  if (displayedArrays >= m_maxAttributes) {
1438  tooltip += QString("\n <i>... (%1 more attributes "
1439  "hidden)</i>")
1440  .arg(pointData->GetNumberOfArrays() - i);
1441  break;
1442  }
1443 
1444  vtkDataArray* array = pointData->GetArray(i);
1445  if (!array) continue;
1446 
1447  QString arrayName = QString::fromUtf8(array->GetName());
1448 
1449  // Skip already displayed arrays (normals, scalars, textures,
1450  // colors) Skip "Normals" by name regardless of components (shown
1451  // specially above or not real normals) Skip "RGB"/"Colors" to avoid
1452  // showing them in generic loop
1453  bool isNormalsArray =
1454  (array == pointData->GetNormals()) ||
1455  (arrayName.compare("Normals", Qt::CaseInsensitive) == 0);
1456  bool isColorArray =
1457  (arrayName.compare("RGB", Qt::CaseInsensitive) == 0) ||
1458  (arrayName.compare("Colors", Qt::CaseInsensitive) == 0) ||
1459  (arrayName.compare("rgba", Qt::CaseInsensitive) == 0);
1460  // Skip texture coordinate arrays (already displayed above)
1461  bool isTcoordsArray =
1462  (arrayName.compare("TCoords", Qt::CaseInsensitive) == 0) ||
1463  arrayName.startsWith("TCoords", Qt::CaseInsensitive) ||
1464  arrayName.contains("texture", Qt::CaseInsensitive) ||
1465  arrayName.contains("tcoord", Qt::CaseInsensitive) ||
1466  arrayName.contains("uv", Qt::CaseInsensitive);
1467  if (arrayName.isEmpty() || isNormalsArray || isColorArray ||
1468  array == pointData->GetScalars() || isTcoordsArray) {
1469  continue;
1470  }
1471 
1472  QString valueStr = formatArrayValue(array, pointId);
1473  if (!valueStr.isEmpty()) {
1474  tooltip += QString("\n %1: %2").arg(arrayName).arg(valueStr);
1475  displayedArrays++;
1476  }
1477  }
1478  }
1479 
1480  // ParaView format: Field data arrays (with horizontal line separator)
1481  vtkFieldData* fieldDataPoint = polyData->GetFieldData();
1482  if (fieldDataPoint && fieldDataPoint->GetNumberOfArrays() > 0) {
1483  bool hasFieldData = false;
1484  bool isFirstField = true;
1485  for (int i = 0; i < fieldDataPoint->GetNumberOfArrays(); ++i) {
1486  vtkAbstractArray* abstractArray =
1487  fieldDataPoint->GetAbstractArray(i);
1488  if (!abstractArray) continue;
1489 
1490  QString arrayName = QString::fromUtf8(abstractArray->GetName());
1491 
1492  // Skip internal/metadata arrays
1493  if (arrayName.startsWith("vtk", Qt::CaseInsensitive) ||
1494  arrayName.startsWith(
1495  "Has",
1496  Qt::CaseInsensitive) || // Skip HasSourceRGB etc.
1497  arrayName == "DatasetName" ||
1498  arrayName == "MaterialNames" ||
1499  arrayName.compare("RGB", Qt::CaseInsensitive) == 0 ||
1500  arrayName.compare("Colors", Qt::CaseInsensitive) == 0 ||
1501  arrayName.compare("Normals", Qt::CaseInsensitive) == 0) {
1502  continue;
1503  }
1504 
1505  if (!hasFieldData) {
1506  tooltip += "\n <hr>";
1507  hasFieldData = true;
1508  }
1509 
1510  QString linePrefix = isFirstField ? " " : "\n ";
1511 
1512  vtkStringArray* stringArray =
1513  vtkStringArray::SafeDownCast(abstractArray);
1514  if (stringArray && stringArray->GetNumberOfTuples() > 0) {
1515  if (stringArray->GetNumberOfTuples() == 1) {
1516  QString value =
1517  QString::fromStdString(stringArray->GetValue(0));
1518  tooltip += QString("%1%2: %3")
1519  .arg(linePrefix)
1520  .arg(arrayName)
1521  .arg(value);
1522  } else {
1523  QStringList values;
1524  for (vtkIdType j = 0; j < stringArray->GetNumberOfTuples();
1525  ++j) {
1526  values << QString::fromStdString(
1527  stringArray->GetValue(j));
1528  }
1529  tooltip += QString("%1%2: %3")
1530  .arg(linePrefix)
1531  .arg(arrayName)
1532  .arg(values.join(", "));
1533  }
1534  isFirstField = false;
1535  } else {
1536  vtkDataArray* array = vtkDataArray::SafeDownCast(abstractArray);
1537  if (array && array->GetNumberOfTuples() > 0) {
1538  if (array->GetNumberOfTuples() == 1) {
1539  QString valueStr = formatArrayValue(array, 0);
1540  tooltip += QString("%1%2: %3")
1541  .arg(linePrefix)
1542  .arg(arrayName)
1543  .arg(valueStr);
1544  } else {
1545  QString valueStr = formatArrayValue(array, 0);
1546  tooltip += QString("%1%2: %3 (array of %4)")
1547  .arg(linePrefix)
1548  .arg(arrayName)
1549  .arg(valueStr)
1550  .arg(array->GetNumberOfTuples());
1551  }
1552  isFirstField = false;
1553  }
1554  }
1555  }
1556  }
1557 
1558  // ParaView style: wrap in <p style='white-space:pre'> to preserve
1559  // whitespace and newlines
1560  return QString("<p style='white-space:pre'>%1</p>").arg(tooltip);
1561 }
1562 
1563 //-----------------------------------------------------------------------------
1564 QString cvTooltipFormatter::formatCellTooltip(vtkPolyData* polyData,
1565  vtkIdType cellId,
1566  const QString& datasetName) {
1567  if (cellId < 0 || cellId >= polyData->GetNumberOfCells()) {
1568  CVLog::Error("[cvTooltipFormatter] Invalid cell ID: %lld", cellId);
1569  return QString();
1570  }
1571 
1572  QString tooltip;
1573 
1574  // ParaView format: Dataset name as first line
1575  if (!datasetName.isEmpty()) {
1576  tooltip += QString("<b>%1</b>").arg(datasetName);
1577  }
1578 
1579  // ParaView format: Cell ID
1580  tooltip += QString("\n Id: %1").arg(cellId);
1581 
1582  // ParaView format: Cell type
1583  vtkCell* cell = polyData->GetCell(cellId);
1584  if (cell) {
1585  QString cellType;
1586  switch (cell->GetCellType()) {
1587  case VTK_EMPTY_CELL:
1588  cellType = "Empty Cell";
1589  break;
1590  case VTK_VERTEX:
1591  cellType = "Vertex";
1592  break;
1593  case VTK_POLY_VERTEX:
1594  cellType = "Poly Vertex";
1595  break;
1596  case VTK_LINE:
1597  cellType = "Line";
1598  break;
1599  case VTK_POLY_LINE:
1600  cellType = "Poly Line";
1601  break;
1602  case VTK_TRIANGLE:
1603  cellType = "Triangle";
1604  break;
1605  case VTK_TRIANGLE_STRIP:
1606  cellType = "Triangle Strip";
1607  break;
1608  case VTK_POLYGON:
1609  cellType = "Polygon";
1610  break;
1611  case VTK_PIXEL:
1612  cellType = "Pixel";
1613  break;
1614  case VTK_QUAD:
1615  cellType = "Quad";
1616  break;
1617  case VTK_TETRA:
1618  cellType = "Tetra";
1619  break;
1620  case VTK_VOXEL:
1621  cellType = "Voxel";
1622  break;
1623  case VTK_HEXAHEDRON:
1624  cellType = "Hexahedron";
1625  break;
1626  case VTK_WEDGE:
1627  cellType = "Wedge";
1628  break;
1629  case VTK_PYRAMID:
1630  cellType = "Pyramid";
1631  break;
1632  case VTK_PENTAGONAL_PRISM:
1633  cellType = "Pentagonal Prism";
1634  break;
1635  case VTK_HEXAGONAL_PRISM:
1636  cellType = "Hexagonal Prism";
1637  break;
1638  default:
1639  cellType = QString("Unknown (%1)").arg(cell->GetCellType());
1640  }
1641  tooltip += QString("\n Type: %1").arg(cellType);
1642 
1643  vtkIdType npts = cell->GetNumberOfPoints();
1644  tooltip += QString("\n Number of Points: %1").arg(npts);
1645 
1646  if (npts > 0 && npts <= 10) {
1647  QString pointIds;
1648  for (vtkIdType i = 0; i < npts; ++i) {
1649  if (i > 0) pointIds += ", ";
1650  pointIds += QString::number(cell->GetPointId(i));
1651  }
1652  tooltip += QString("\n Point IDs: [%1]").arg(pointIds);
1653  }
1654 
1655  // Show cell center/centroid
1656  double center[3] = {0, 0, 0};
1657  for (vtkIdType i = 0; i < npts; ++i) {
1658  double pt[3];
1659  polyData->GetPoint(cell->GetPointId(i), pt);
1660  center[0] += pt[0];
1661  center[1] += pt[1];
1662  center[2] += pt[2];
1663  }
1664  if (npts > 0) {
1665  center[0] /= npts;
1666  center[1] /= npts;
1667  center[2] /= npts;
1668  tooltip += QString("\n Center: (%1, %2, %3)")
1669  .arg(center[0], 0, 'g', 6)
1670  .arg(center[1], 0, 'g', 6)
1671  .arg(center[2], 0, 'g', 6);
1672  }
1673  }
1674 
1675  // ParaView format: Cell data arrays
1676  vtkCellData* cellData = polyData->GetCellData();
1677  if (cellData) {
1678  int displayedArrays = 0;
1679 
1680  // Show normals - check both active and named "Normals" array with 3
1681  // components
1682  vtkDataArray* cellNormalsArray = cellData->GetNormals();
1683  if (!cellNormalsArray) {
1684  cellNormalsArray = cellData->GetArray("Normals");
1685  if (cellNormalsArray &&
1686  cellNormalsArray->GetNumberOfComponents() != 3) {
1687  cellNormalsArray = nullptr;
1688  }
1689  }
1690  if (cellNormalsArray &&
1691  cellNormalsArray->GetNumberOfComponents() == 3) {
1692  double* normal = cellNormalsArray->GetTuple3(cellId);
1693  tooltip += QString("\n Normals: (%1, %2, %3)")
1694  .arg(normal[0], 0, 'f', 4)
1695  .arg(normal[1], 0, 'f', 4)
1696  .arg(normal[2], 0, 'f', 4);
1697  displayedArrays++;
1698  }
1699 
1700  if (cellData->GetScalars() && displayedArrays < m_maxAttributes) {
1701  QString scalarName =
1702  QString::fromUtf8(cellData->GetScalars()->GetName());
1703  double scalar = cellData->GetScalars()->GetTuple1(cellId);
1704  tooltip +=
1705  QString("\n %1: %2")
1706  .arg(scalarName.isEmpty() ? "Scalars" : scalarName)
1707  .arg(formatNumber(scalar));
1708  displayedArrays++;
1709  }
1710 
1711  for (int i = 0; i < cellData->GetNumberOfArrays(); ++i) {
1712  if (displayedArrays >= m_maxAttributes) {
1713  tooltip += QString("\n<i>... (%1 more attributes "
1714  "hidden)</i>")
1715  .arg(cellData->GetNumberOfArrays() - i);
1716  break;
1717  }
1718 
1719  vtkDataArray* array = cellData->GetArray(i);
1720  if (!array) continue;
1721 
1722  QString arrayName = QString::fromUtf8(array->GetName());
1723 
1724  // Skip already displayed arrays (normals, scalars, colors)
1725  // Skip "Normals" by name regardless of components
1726  // Skip "RGB"/"Colors" to avoid showing them in generic loop
1727  bool isCellNormals =
1728  (array == cellData->GetNormals()) ||
1729  (arrayName.compare("Normals", Qt::CaseInsensitive) == 0);
1730  bool isColorArray =
1731  (arrayName.compare("RGB", Qt::CaseInsensitive) == 0) ||
1732  (arrayName.compare("Colors", Qt::CaseInsensitive) == 0) ||
1733  (arrayName.compare("rgba", Qt::CaseInsensitive) == 0);
1734  if (arrayName.isEmpty() || isCellNormals || isColorArray ||
1735  array == cellData->GetScalars()) {
1736  continue;
1737  }
1738 
1739  QString valueStr = formatArrayValue(array, cellId);
1740  if (!valueStr.isEmpty()) {
1741  tooltip += QString("\n%1: %2").arg(arrayName).arg(valueStr);
1742  displayedArrays++;
1743  }
1744  }
1745  }
1746 
1747  // ParaView format: Field data arrays
1748  vtkFieldData* fieldDataCell = polyData->GetFieldData();
1749  if (fieldDataCell && fieldDataCell->GetNumberOfArrays() > 0) {
1750  bool hasFieldData = false;
1751  bool isFirstField = true;
1752  for (int i = 0; i < fieldDataCell->GetNumberOfArrays(); ++i) {
1753  vtkAbstractArray* abstractArray =
1754  fieldDataCell->GetAbstractArray(i);
1755  if (!abstractArray) continue;
1756 
1757  QString arrayName = QString::fromUtf8(abstractArray->GetName());
1758 
1759  // Skip internal/metadata arrays
1760  if (arrayName.startsWith("vtk", Qt::CaseInsensitive) ||
1761  arrayName.startsWith(
1762  "Has",
1763  Qt::CaseInsensitive) || // Skip HasSourceRGB etc.
1764  arrayName == "DatasetName" ||
1765  arrayName == "MaterialNames" ||
1766  arrayName.compare("RGB", Qt::CaseInsensitive) == 0 ||
1767  arrayName.compare("Colors", Qt::CaseInsensitive) == 0 ||
1768  arrayName.compare("Normals", Qt::CaseInsensitive) == 0) {
1769  continue;
1770  }
1771 
1772  if (!hasFieldData) {
1773  tooltip += "\n<hr>";
1774  hasFieldData = true;
1775  }
1776 
1777  QString linePrefix = isFirstField ? "" : "\n";
1778 
1779  vtkStringArray* stringArray =
1780  vtkStringArray::SafeDownCast(abstractArray);
1781  if (stringArray && stringArray->GetNumberOfTuples() > 0) {
1782  if (stringArray->GetNumberOfTuples() == 1) {
1783  QString value =
1784  QString::fromStdString(stringArray->GetValue(0));
1785  tooltip += QString("%1%2: %3")
1786  .arg(linePrefix)
1787  .arg(arrayName)
1788  .arg(value);
1789  } else {
1790  QStringList values;
1791  for (vtkIdType j = 0; j < stringArray->GetNumberOfTuples();
1792  ++j) {
1793  values << QString::fromStdString(
1794  stringArray->GetValue(j));
1795  }
1796  tooltip += QString("%1%2: %3")
1797  .arg(linePrefix)
1798  .arg(arrayName)
1799  .arg(values.join(", "));
1800  }
1801  isFirstField = false;
1802  } else {
1803  vtkDataArray* array = vtkDataArray::SafeDownCast(abstractArray);
1804  if (array && array->GetNumberOfTuples() > 0) {
1805  if (array->GetNumberOfTuples() == 1) {
1806  QString valueStr = formatArrayValue(array, 0);
1807  tooltip += QString("%1%2: %3")
1808  .arg(linePrefix)
1809  .arg(arrayName)
1810  .arg(valueStr);
1811  } else {
1812  QString valueStr = formatArrayValue(array, 0);
1813  tooltip += QString("%1%2: %3 (array of %4)")
1814  .arg(linePrefix)
1815  .arg(arrayName)
1816  .arg(valueStr)
1817  .arg(array->GetNumberOfTuples());
1818  }
1819  isFirstField = false;
1820  }
1821  }
1822  }
1823  }
1824 
1825  // ParaView style: wrap in <p style='white-space:pre'> to preserve
1826  // whitespace and newlines
1827  return QString("<p style='white-space:pre'>%1</p>").arg(tooltip);
1828 }
1829 
1830 //-----------------------------------------------------------------------------
1831 void cvTooltipFormatter::addArrayValues(QString& tooltip,
1832  vtkFieldData* fieldData,
1833  vtkIdType tupleIndex) {
1834  if (!fieldData) {
1835  return;
1836  }
1837 
1838  int numArrays = fieldData->GetNumberOfArrays();
1839 
1840  for (int i = 0; i < numArrays; ++i) {
1841  vtkDataArray* array = fieldData->GetArray(i);
1842  if (!array) {
1843  continue;
1844  }
1845 
1846  QString arrayName = QString::fromUtf8(array->GetName());
1847  if (arrayName.isEmpty() ||
1848  arrayName.startsWith("vtk", Qt::CaseInsensitive) ||
1849  arrayName == "vtkOriginalPointIds" ||
1850  arrayName == "vtkOriginalCellIds" ||
1851  arrayName == "vtkCompositeIndexArray" ||
1852  arrayName == "vtkGhostType" || arrayName == "vtkValidPointMask") {
1853  continue;
1854  }
1855 
1856  QString valueStr = formatArrayValue(array, tupleIndex);
1857  if (!valueStr.isEmpty()) {
1858  tooltip += QString("\n%1: %2").arg(arrayName).arg(valueStr);
1859  }
1860  }
1861 }
1862 
1863 //-----------------------------------------------------------------------------
1864 QString cvTooltipFormatter::formatArrayValue(vtkDataArray* array,
1865  vtkIdType tupleIndex) {
1866  if (!array || tupleIndex < 0 || tupleIndex >= array->GetNumberOfTuples()) {
1867  return QString();
1868  }
1869 
1870  int numComponents = array->GetNumberOfComponents();
1871  const int maxDisplayedComp = 9;
1872 
1873  if (numComponents == 1) {
1874  double value = array->GetTuple1(tupleIndex);
1875  return formatNumber(value);
1876  } else {
1877  QString result;
1878  if (numComponents > 1) {
1879  result = "(";
1880  }
1881 
1882  for (int i = 0; i < std::min(numComponents, maxDisplayedComp); ++i) {
1883  double value = array->GetComponent(tupleIndex, i);
1884  result += formatNumber(value);
1885  if (i + 1 < numComponents && i < maxDisplayedComp) {
1886  result += ", ";
1887  }
1888  }
1889 
1890  if (numComponents > maxDisplayedComp) {
1891  result += ", ...";
1892  }
1893 
1894  if (numComponents > 1) {
1895  result += ")";
1896  }
1897  return result;
1898  }
1899 }
1900 
1901 //-----------------------------------------------------------------------------
1902 QString cvTooltipFormatter::formatNumber(double value) {
1903  double absValue = qAbs(value);
1904 
1905  if (absValue > 0 && (absValue < 1e-4 || absValue >= 1e6)) {
1906  return QString::number(value, 'e', 4);
1907  } else {
1908  return QString::number(value, 'g', 6);
1909  }
1910 }
double normal[3]
int width
int size
std::string name
math::float4 color
QRegularExpression QtCompatRegExp
Definition: QtCompat.h:170
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
static bool Error(const char *format,...)
Display an error dialog with formatted message.
Definition: CVLog.cpp:143
vtkRenderer * getCurrentRenderer(int viewport=0)
Definition: PCLVis.cpp:2924
Base class for selection tools with picking capabilities.
vtkPolyData * getPolyDataForSelection(const cvSelectionData *selectionData=nullptr) override
Get polyData for a selection using ParaView-style priority (override)
PclUtils::PCLVis * getPCLVis() const
Get PCLVis instance (for VTK-specific operations)
ecvGenericVisualizer3D * m_viewer
Visualizer instance (abstract interface)
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)
QString fieldTypeString() const
Get human-readable field type string.
bool isEmpty() const
Check if selection is empty.
int count() const
Get number of selected items.
void setLineWidth(int width, HighlightMode mode=SELECTED)
Set line width for highlight rendering.
void setHighlightQColor(const QColor &color, HighlightMode mode=SELECTED)
Set highlight color from QColor.
bool highlightSelection(const vtkSmartPointer< vtkIdTypeArray > &selection, int fieldAssociation, HighlightMode mode=SELECTED)
Highlight selected elements (automatically gets polyData from visualizer)
void propertiesChanged()
Emitted when any property changes (general notification)
const SelectionLabelProperties & getLabelProperties(bool interactive=false) const
Get label properties for selection mode.
void colorChanged(int mode)
Emitted when any highlight color changes.
void setEnabled(bool enabled)
Enable/disable highlight.
void opacityChanged(int mode)
Emitted when any opacity changes.
void labelPropertiesChanged(bool interactive)
Emitted when label properties change.
HighlightMode
Highlight mode (Enhanced multi-level highlighting)
void setHighlightColor(double r, double g, double b, HighlightMode mode=SELECTED)
Set highlight color.
const double * getHighlightColor(HighlightMode mode) const
Get highlight color for a specific mode.
void setHighlightsVisible(bool visible)
Set visibility of all highlight actors.
void setPointSize(int size, HighlightMode mode=SELECTED)
Set point size for highlight rendering.
void setCellLabelArray(const QString &arrayName, bool visible=true)
Set the cell label array name.
void setLabelProperties(const SelectionLabelProperties &props, bool interactive=false)
Set label properties for selection mode.
int getLineWidth(HighlightMode mode) const
Get line width for a specific mode.
void pointSizeChanged(int mode)
Emitted when point size changes.
double getHighlightOpacity(HighlightMode mode) const
Get highlight opacity for a specific mode.
bool highlightElement(vtkPolyData *polyData, vtkIdType elementId, int fieldAssociation)
Highlight a single element (for hover preview)
QColor getHighlightQColor(HighlightMode mode) const
Get highlight color as QColor.
int getPointSize(HighlightMode mode) const
Get point size for a specific mode.
void clearHoverHighlight()
Clear only hover highlight (keep selected/preselected)
void setPointLabelArray(const QString &arrayName, bool visible=true)
Set the point label array name.
void lineWidthChanged(int mode)
Emitted when line width changes.
void clearHighlights()
Clear all highlights.
void setHighlightOpacity(double opacity, HighlightMode mode=SELECTED)
Set highlight opacity.
QString getTooltipInfo(vtkPolyData *polyData, vtkIdType elementId, AssociationType association, const QString &datasetName=QString())
Generate tooltip information for a selected element.
AssociationType
Element association type.
void setMaxAttributes(int maxAttribs)
Set maximum number of attributes to display.
QString getPlainTooltipInfo(vtkPolyData *polyData, vtkIdType elementId, AssociationType association, const QString &datasetName=QString())
Generate plain text tooltip (no HTML formatting)
GraphType data
Definition: graph_cut.cc:138
normal_z scalar
constexpr QRegularExpression::PatternOption CaseInsensitive
Definition: QtCompat.h:174
Label properties for selection annotations.