ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
cvContourTool.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 "cvContourTool.h"
9 
10 #include <VtkUtils/vtkutils.h>
11 #include <vtkContourRepresentation.h>
12 #include <vtkContourWidget.h>
13 #include <vtkOrientedGlyphContourRepresentation.h>
14 #include <vtkPolyData.h>
15 #include <vtkProperty.h>
16 #include <vtkRenderWindow.h>
17 #include <vtkRenderWindowInteractor.h>
18 #include <vtkRenderer.h>
19 #include <vtkTextActor.h>
20 #include <vtkTextProperty.h>
21 
22 // LOCAL
23 #include <Utils/vtk2cc.h>
24 
27 
28 // QT
29 #include <QApplication>
30 #include <QLayout>
31 #include <QLayoutItem>
32 #include <QSizePolicy>
33 
34 // CV_DB_LIB
35 #include <CVLog.h>
36 #include <ecvColorTypes.h>
37 #include <ecvPolyline.h>
38 
39 // Static counter initialization
40 int cvContourTool::s_contourIdCounter = 0;
41 
43  : cvGenericMeasurementTool(parent),
44  m_configUi(nullptr),
45  m_toolId(++s_contourIdCounter),
46  m_exportCounter(0) {
47  setWindowTitle(tr("Contour Measurement Tool"));
48  // Set default font size to 12 for better readability
49  m_fontSize = 12;
50 }
51 
53  // CRITICAL: Explicitly cleanup all contour widgets and their
54  // representations
55  for (auto& pair : m_contours) {
56  if (pair.second) {
57  // Hide representation first
58  if (auto* rep = vtkContourRepresentation::SafeDownCast(
59  pair.second->GetRepresentation())) {
60  rep->SetVisibility(0);
61 
62  // If using custom representation with label, hide it too
63  if (auto* customRep =
64  cvConstrainedContourRepresentation::SafeDownCast(
65  rep)) {
66  if (auto* labelActor = customRep->GetLabelActor()) {
67  labelActor->SetVisibility(0);
68  }
69  }
70  }
71 
72  pair.second->SetInteractor(nullptr);
73  pair.second->Off();
74  }
75  }
76  m_contours.clear();
77 
78  // Force immediate render to clear visual elements
79  if (m_interactor && m_interactor->GetRenderWindow()) {
80  m_interactor->GetRenderWindow()->Render();
81  }
82 
83  if (m_configUi) {
84  delete m_configUi;
85  m_configUi = nullptr;
86  }
87 }
88 
89 void cvContourTool::initTool() { createNewContour(); }
90 
91 void cvContourTool::createNewContour() {
92  // If we have a current contour, ensure it's in a state that allows a new
93  // one vtkContourWidget doesn't auto-close, but if we create a new one, the
94  // old one will just sit there (hopefully in Manipulate state).
95 
98  VtkUtils::vtkInitOnce(newContour);
99 
100  // Create and set the custom representation with label support
103  newContour->SetRepresentation(rep);
104 
105  // Set default node visibility
106  rep->SetShowSelectedNodes(true);
107 
108  // Set default color
109  if (auto* linesProp = rep->GetLinesProperty()) {
110  linesProp->SetColor(m_currentColor[0], m_currentColor[1],
111  m_currentColor[2]);
112  }
113  if (auto* nodesProp = rep->GetProperty()) {
114  nodesProp->SetColor(m_currentColor[0], m_currentColor[1],
115  m_currentColor[2]);
116  }
117  if (auto* activeNodesProp = rep->GetActiveProperty()) {
118  activeNodesProp->SetColor(m_currentColor[0], m_currentColor[1],
119  m_currentColor[2]);
120  }
121 
122  if (m_interactor) {
123  newContour->SetInteractor(m_interactor);
124  }
125  if (m_renderer) {
126  rep->SetRenderer(m_renderer);
127  newContour->SetCurrentRenderer(m_renderer);
128  }
129  newContour->On();
130 
131  m_currentContourId++;
132  m_contours[m_currentContourId] = newContour;
133 
134  // Apply font properties to the newly created contour
135  applyFontProperties();
136 }
137 
139  // CRITICAL: Only setup base UI once to avoid resetting configLayout
140  // Each tool instance has its own m_ui, but setupUi clears all children
141  // so we must ensure it's only called once per tool instance
142  // Check if base UI is already set up by checking if widget has a layout
143  // NOTE: Cannot check m_ui->configLayout directly as it's uninitialized
144  // before setupUi()
145  if (!m_ui) {
146  CVLog::Error("[cvContourTool::createUi] m_ui is null!");
147  return;
148  }
149  if (!layout()) {
150  m_ui->setupUi(this);
151  }
152 
153  // CRITICAL: Always clean up existing config UI before creating new one
154  // This prevents UI interference when createUi() is called multiple times
155  // (e.g., when tool is restarted or switched)
156  if (m_configUi && m_ui->configLayout) {
157  // Remove all existing widgets from configLayout
158  QLayoutItem* item;
159  while ((item = m_ui->configLayout->takeAt(0)) != nullptr) {
160  if (item->widget()) {
161  item->widget()->setParent(nullptr);
162  item->widget()->deleteLater();
163  }
164  delete item;
165  }
166  delete m_configUi;
167  m_configUi = nullptr;
168  }
169 
170  // Create fresh config UI for this tool instance
171  m_configUi = new Ui::ContourToolDlg;
172  QWidget* configWidget = new QWidget(this);
173  // CRITICAL: Set size policy to Minimum to prevent horizontal expansion
174  // This ensures the widget only takes the space it needs
175  configWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
176  m_configUi->setupUi(configWidget);
177  // CRITICAL: Set layout size constraint to ensure minimum size calculation
178  // This prevents extra whitespace on the right
179  if (configWidget->layout()) {
180  configWidget->layout()->setSizeConstraint(QLayout::SetMinimumSize);
181  }
182  m_ui->configLayout->addWidget(configWidget);
183  m_ui->groupBox->setTitle(tr("Contour Parameters"));
184 
185  // CRITICAL: Use Qt's automatic sizing based on sizeHint
186  // This ensures each tool adapts to its own content without interference
187  // Reset size constraints to allow Qt's layout system to work properly
188  // ParaView-style: use Minimum (horizontal) to prevent unnecessary expansion
189  this->setMinimumSize(0, 0);
190  this->setMaximumSize(16777215, 16777215); // QWIDGETSIZE_MAX equivalent
191  this->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
192 
193  // Let Qt calculate the optimal size based on content
194  // Order matters: adjust configWidget first, then the main widget
195  configWidget->adjustSize();
196  this->adjustSize();
197  // Force layout update to apply size changes
198  this->updateGeometry();
199  // CRITICAL: Process events to ensure layout is fully updated
200  QApplication::processEvents();
201 
202  // Connect display options
203  connect(m_configUi->widgetVisibilityCheckBox, &QCheckBox::toggled, this,
204  &cvContourTool::on_widgetVisibilityCheckBox_toggled);
205  connect(m_configUi->showNodesCheckBox, &QCheckBox::toggled, this,
206  &cvContourTool::on_showNodesCheckBox_toggled);
207  connect(m_configUi->closedLoopCheckBox, &QCheckBox::toggled, this,
208  &cvContourTool::on_closedLoopCheckBox_toggled);
209  connect(m_configUi->lineWidthSpinBox,
210  QOverload<double>::of(&QDoubleSpinBox::valueChanged), this,
211  &cvContourTool::on_lineWidthSpinBox_valueChanged);
212 }
213 
215 
217  // Clear only the current contour
218  if (m_contours.find(m_currentContourId) != m_contours.end()) {
219  auto widget = m_contours[m_currentContourId];
220  if (widget) {
221  widget->Initialize(nullptr);
222  update();
224  }
225  }
226 }
227 
228 void cvContourTool::showWidget(bool state) {
229  for (auto& pair : m_contours) {
230  if (pair.second) {
231  if (state) {
232  pair.second->On();
233  } else {
234  pair.second->Off();
235  }
236  }
237  }
238  update();
239 }
240 
241 void cvContourTool::setColor(double r, double g, double b) {
242  // Store current color
243  m_currentColor[0] = r;
244  m_currentColor[1] = g;
245  m_currentColor[2] = b;
246 
247  // Set color for all contour widgets
248  for (auto& pair : m_contours) {
249  if (pair.second) {
250  vtkContourRepresentation* rep =
251  vtkContourRepresentation::SafeDownCast(
252  pair.second->GetRepresentation());
253  if (rep) {
254  vtkOrientedGlyphContourRepresentation* orientedRep =
255  vtkOrientedGlyphContourRepresentation::SafeDownCast(
256  rep);
257  if (orientedRep) {
258  if (auto* prop = orientedRep->GetLinesProperty()) {
259  prop->SetColor(r, g, b);
260  }
261  if (auto* activeNodeProp =
262  orientedRep->GetActiveProperty()) {
263  activeNodeProp->SetColor(r, g, b);
264  }
265  rep->BuildRepresentation();
266  }
267  }
268  }
269  }
270  update();
271 }
272 
273 bool cvContourTool::getColor(double& r, double& g, double& b) const {
274  r = m_currentColor[0];
275  g = m_currentColor[1];
276  b = m_currentColor[2];
277  return true;
278 }
279 
281  CVLog::PrintDebug(QString("[cvContourTool::lockInteraction] Tool=%1, "
282  "m_contours.size()=%2")
283  .arg((quintptr)this, 0, 16)
284  .arg(m_contours.size()));
285 
286  // Disable all contour widgets' interaction
287  for (auto& pair : m_contours) {
288  if (pair.second) {
289  pair.second->SetProcessEvents(0); // Disable event processing
290  }
291  }
292 
293  // Change widget color to indicate locked state (very dimmed, 10%
294  // brightness)
295  for (auto& pair : m_contours) {
296  if (pair.second) {
297  vtkContourRepresentation* rep =
298  vtkContourRepresentation::SafeDownCast(
299  pair.second->GetRepresentation());
300  if (rep) {
301  vtkOrientedGlyphContourRepresentation* orientedRep =
302  vtkOrientedGlyphContourRepresentation::SafeDownCast(
303  rep);
304  if (orientedRep) {
305  // Use a very dimmed color to indicate locked state (10%
306  // brightness, 50% opacity)
307  if (auto* prop = orientedRep->GetLinesProperty()) {
308  prop->SetColor(m_currentColor[0] * 0.1,
309  m_currentColor[1] * 0.1,
310  m_currentColor[2] * 0.1);
311  prop->SetOpacity(0.5);
312  }
313  if (auto* activeNodeProp =
314  orientedRep->GetActiveProperty()) {
315  activeNodeProp->SetColor(m_currentColor[0] * 0.1,
316  m_currentColor[1] * 0.1,
317  m_currentColor[2] * 0.1);
318  activeNodeProp->SetOpacity(0.5);
319  }
320  if (auto* nodeProp = orientedRep->GetProperty()) {
321  nodeProp->SetOpacity(0.5);
322  }
323 
324  // Dim the label if using custom representation
326  cvConstrainedContourRepresentation::SafeDownCast(
327  orientedRep);
328  if (customRep) {
329  if (auto* labelActor = customRep->GetLabelActor()) {
330  if (auto* textProp =
331  labelActor->GetTextProperty()) {
332  textProp->SetOpacity(0.5);
333  textProp->SetColor(
334  0.5, 0.5,
335  0.5); // Dark gray for locked state
336  }
337  }
338  }
339 
340  rep->BuildRepresentation();
341  }
342  }
343  }
344  }
345 
346  // Disable UI controls
347  if (m_configUi) {
348  m_configUi->widgetVisibilityCheckBox->setEnabled(false);
349  m_configUi->showNodesCheckBox->setEnabled(false);
350  m_configUi->closedLoopCheckBox->setEnabled(false);
351  m_configUi->lineWidthSpinBox->setEnabled(false);
352  }
353 
354  // Disable keyboard shortcuts
356 
357  // Force render window update
358  if (m_interactor && m_interactor->GetRenderWindow()) {
359  m_interactor->GetRenderWindow()->Render();
360  }
361 
362  update();
363 }
364 
366  CVLog::PrintDebug(QString("[cvContourTool::unlockInteraction] Tool=%1, "
367  "m_contours.size()=%2")
368  .arg((quintptr)this, 0, 16)
369  .arg(m_contours.size()));
370 
371  // Enable all contour widgets' interaction
372  for (auto& pair : m_contours) {
373  if (pair.second) {
374  pair.second->SetProcessEvents(1); // Enable event processing
375  }
376  }
377 
378  // Restore widget color to indicate active/unlocked state
379  for (auto& pair : m_contours) {
380  if (pair.second) {
381  vtkContourRepresentation* rep =
382  vtkContourRepresentation::SafeDownCast(
383  pair.second->GetRepresentation());
384  if (rep) {
385  vtkOrientedGlyphContourRepresentation* orientedRep =
386  vtkOrientedGlyphContourRepresentation::SafeDownCast(
387  rep);
388  if (orientedRep) {
389  // Restore original color (full brightness and opacity)
390  if (auto* prop = orientedRep->GetLinesProperty()) {
391  prop->SetColor(m_currentColor[0], m_currentColor[1],
392  m_currentColor[2]);
393  prop->SetOpacity(1.0);
394  }
395  if (auto* activeNodeProp =
396  orientedRep->GetActiveProperty()) {
397  activeNodeProp->SetColor(m_currentColor[0],
398  m_currentColor[1],
399  m_currentColor[2]);
400  activeNodeProp->SetOpacity(1.0);
401  }
402  if (auto* nodeProp = orientedRep->GetProperty()) {
403  nodeProp->SetOpacity(1.0);
404  }
405 
406  // Restore the label to user-configured settings
408  cvConstrainedContourRepresentation::SafeDownCast(
409  orientedRep);
410  if (customRep) {
411  if (auto* labelActor = customRep->GetLabelActor()) {
412  if (auto* textProp =
413  labelActor->GetTextProperty()) {
414  textProp->SetOpacity(m_fontOpacity);
415  textProp->SetColor(m_fontColor[0],
416  m_fontColor[1],
417  m_fontColor[2]);
418  }
419  }
420  }
421 
422  rep->BuildRepresentation();
423  }
424  }
425  }
426  }
427 
428  // Re-enable keyboard shortcuts
429  if (m_pickingHelpers.isEmpty()) {
430  // Shortcuts haven't been created yet - create them now
431  if (m_vtkWidget) {
433  QString("[cvContourTool::unlockInteraction] Creating "
434  "shortcuts for tool=%1, using saved vtkWidget=%2")
435  .arg((quintptr)this, 0, 16)
436  .arg((quintptr)m_vtkWidget, 0, 16));
439  QString("[cvContourTool::unlockInteraction] After "
440  "setupShortcuts, m_pickingHelpers.size()=%1")
441  .arg(m_pickingHelpers.size()));
442  } else {
444  QString("[cvContourTool::unlockInteraction] m_vtkWidget is "
445  "null for tool=%1, cannot create shortcuts")
446  .arg((quintptr)this, 0, 16));
447  }
448  } else {
449  // Shortcuts already exist - just enable them
450  CVLog::PrintDebug(QString("[cvContourTool::unlockInteraction] Enabling "
451  "%1 existing shortcuts for tool=%2")
452  .arg(m_pickingHelpers.size())
453  .arg((quintptr)this, 0, 16));
454  for (cvPointPickingHelper* helper : m_pickingHelpers) {
455  if (helper) {
456  helper->setEnabled(true,
457  false); // Enable without setting focus
458  }
459  }
460  }
461 
462  // Enable UI controls
463  if (m_configUi) {
464  m_configUi->widgetVisibilityCheckBox->setEnabled(true);
465  m_configUi->showNodesCheckBox->setEnabled(true);
466  m_configUi->closedLoopCheckBox->setEnabled(true);
467  m_configUi->lineWidthSpinBox->setEnabled(true);
468  }
469 
470  // Force render window update
471  if (m_interactor && m_interactor->GetRenderWindow()) {
472  m_interactor->GetRenderWindow()->Render();
473  }
474 
475  update();
476 }
477 
478 void cvContourTool::setInstanceLabel(const QString& label) {
479  // Store the instance label
480  m_instanceLabel = label;
481 
482  // Update window title to show the instance label
483  QString title = tr("Contour Measurement Tool");
484  if (!m_instanceLabel.isEmpty()) {
485  title += QString(" %1").arg(m_instanceLabel);
486  }
487  setWindowTitle(title);
488 
489  // Update VTK representation's label suffix for all contours
490  for (auto& pair : m_contours) {
491  if (pair.second) {
492  vtkContourRepresentation* rep =
493  vtkContourRepresentation::SafeDownCast(
494  pair.second->GetRepresentation());
495  if (rep) {
497  cvConstrainedContourRepresentation::SafeDownCast(rep);
498  if (customRep) {
499  customRep->SetLabelSuffix(
500  m_instanceLabel.toUtf8().constData());
501  customRep->BuildRepresentation();
502  }
503  }
504  }
505  }
506 
507  update();
508 }
509 
511  // Export only the current contour (as requested)
512  auto it = m_contours.find(m_currentContourId);
513  if (it == m_contours.end() || !it->second) {
514  CVLog::Warning("[cvContourTool] No current contour active");
515  return nullptr;
516  }
517 
518  vtkContourWidget* widget = it->second;
519 
520  // Check if contour widget is enabled and has valid data
521  if (!widget->GetEnabled()) {
522  CVLog::Warning("[cvContourTool] Current contour widget is not enabled");
523  return nullptr;
524  }
525 
526  // Get contour representation
527  vtkContourRepresentation* rep =
528  vtkContourRepresentation::SafeDownCast(widget->GetRepresentation());
529  if (!rep) {
530  CVLog::Warning("[cvContourTool] Failed to get contour representation");
531  return nullptr;
532  }
533 
534  // Force update of the representation to ensure polydata is up-to-date
535  rep->BuildRepresentation();
536 
537  // Get polyData from contour representation
538  vtkPolyData* polyData = rep->GetContourRepresentationAsPolyData();
539  if (!polyData) {
541  "[cvContourTool] Failed to get polyData from contour "
542  "representation");
543  return nullptr;
544  }
545 
546  // Check if contour has valid points
547  if (polyData->GetNumberOfPoints() == 0) {
548  CVLog::Warning("[cvContourTool] Contour has no points");
549  return nullptr;
550  }
551 
552  // Get properties from the contour representation
553  bool isClosed = rep->GetClosedLoop();
554  double lineWidth = 2.0; // default value
555 
556  // Try to get line width from the representation
557  vtkOrientedGlyphContourRepresentation* orientedRep =
558  vtkOrientedGlyphContourRepresentation::SafeDownCast(rep);
559  if (orientedRep) {
560  vtkProperty* prop = orientedRep->GetLinesProperty();
561  if (prop) {
562  lineWidth = prop->GetLineWidth();
563  }
564  }
565 
566  // Create a deep copy of polyData to avoid modifying the original contour
567  vtkSmartPointer<vtkPolyData> polyDataCopy =
569  polyDataCopy->DeepCopy(polyData);
570 
571  // Convert vtkPolyData to ccPolyline
572  ccPolyline* polyline = vtk2cc::ConvertToPolyline(polyDataCopy, true);
573  if (polyline) {
574  // Determine if polyline should be in 2D mode based on point coordinates
575  // Check if all points are coplanar (e.g., all have the same Z
576  // coordinate)
578  polyline->getAssociatedCloud();
579  if (vertices && vertices->size() > 0) {
580  // Get first point's Z coordinate as reference
581  const CCVector3* firstPoint = vertices->getPoint(0);
582  PointCoordinateType refZ = firstPoint->z;
583 
584  // Check if all points have the same Z coordinate (within epsilon)
585  bool is2D = true;
586  for (unsigned i = 1; i < vertices->size(); ++i) {
587  const CCVector3* point = vertices->getPoint(i);
588  PointCoordinateType zDiff = point->z - refZ;
589  // Use absolute value and check if difference is less than
590  // epsilon PointCoordinateType is float, so use LessThanEpsilon
591  // with abs value
592  if (!cloudViewer::LessThanEpsilon(std::abs(zDiff))) {
593  is2D = false;
594  break;
595  }
596  }
597 
598  // Set 2D mode if all points are coplanar
599  polyline->set2DMode(is2D);
600  }
601 
602  // Set name with contour ID and export counter
603  polyline->setName(QString("Contour_%1_%2")
604  .arg(m_currentContourId)
605  .arg(m_exportCounter));
606  polyline->setColor(ecvColor::green);
607 
608  // Apply properties from vtkContourWidget to ccPolyline
609  polyline->setWidth(static_cast<PointCoordinateType>(lineWidth));
610  polyline->setClosed(isClosed);
611  polyline->setVisible(true);
612 
613  CVLog::Print(QString("[cvContourTool] Exported contour %1 with %2 "
614  "points (closed: %3, width: %4)")
615  .arg(polyline->getName())
616  .arg(polyData->GetNumberOfPoints())
617  .arg(isClosed ? "true" : "false")
618  .arg(lineWidth));
619 
620  // Increment export counter for this contour instance
621  m_exportCounter++;
622 
623  return polyline;
624  } else {
625  CVLog::Error("[cvContourTool] Failed to convert polyData to polyline");
626  return nullptr;
627  }
628 }
629 
630 void cvContourTool::onDataChanged(vtkPolyData* pd) {
631  // Handle contour data changes
632  Q_UNUSED(pd);
633  update();
634 }
635 
636 void cvContourTool::on_widgetVisibilityCheckBox_toggled(bool checked) {
637  if (!m_configUi) return;
638 
639  // Show or hide all contour widgets
640  for (auto& pair : m_contours) {
641  if (pair.second) {
642  if (checked) {
643  pair.second->On();
644  } else {
645  pair.second->Off();
646  }
647  }
648  }
649 
650  update();
651 }
652 
653 void cvContourTool::on_showNodesCheckBox_toggled(bool checked) {
654  if (!m_configUi) return;
655 
656  // Show or hide nodes for all contours
657  for (auto& pair : m_contours) {
658  if (pair.second) {
659  vtkContourRepresentation* rep =
660  vtkContourRepresentation::SafeDownCast(
661  pair.second->GetRepresentation());
662  if (rep) {
663  vtkOrientedGlyphContourRepresentation* orientedRep =
664  vtkOrientedGlyphContourRepresentation::SafeDownCast(
665  rep);
666  if (orientedRep) {
667  orientedRep->SetShowSelectedNodes(checked);
668  }
669  }
670  }
671  }
672 
673  update();
674 }
675 
676 void cvContourTool::on_closedLoopCheckBox_toggled(bool checked) {
677  if (!m_configUi) return;
678 
679  // Set closed loop for all contours
680  for (auto& pair : m_contours) {
681  if (pair.second) {
682  vtkContourRepresentation* rep =
683  vtkContourRepresentation::SafeDownCast(
684  pair.second->GetRepresentation());
685  if (rep) {
686  rep->SetClosedLoop(checked);
687  rep->BuildRepresentation();
688  }
689  }
690  }
691 
692  update();
693 }
694 
695 void cvContourTool::on_lineWidthSpinBox_valueChanged(double value) {
696  if (!m_configUi) return;
697 
698  // Update line width for all contours
699  for (auto& pair : m_contours) {
700  if (pair.second) {
701  vtkContourRepresentation* rep =
702  vtkContourRepresentation::SafeDownCast(
703  pair.second->GetRepresentation());
704  if (rep) {
705  // Try to get the property through
706  // vtkOrientedGlyphContourRepresentation
707  vtkOrientedGlyphContourRepresentation* orientedRep =
708  vtkOrientedGlyphContourRepresentation::SafeDownCast(
709  rep);
710  if (orientedRep) {
711  // Use GetLinesProperty() to set the line width for the
712  // contour lines
713  vtkProperty* linesProp = orientedRep->GetLinesProperty();
714  if (linesProp) {
715  linesProp->SetLineWidth(value);
716  }
717 
718  // Also update the property for control points and active
719  // state
720  vtkProperty* prop = orientedRep->GetProperty();
721  if (prop) {
722  prop->SetLineWidth(value);
723  }
724  vtkProperty* activeProp = orientedRep->GetActiveProperty();
725  if (activeProp) {
726  activeProp->SetLineWidth(value);
727  }
728  }
729  rep->BuildRepresentation();
730  }
731  }
732  }
733 
734  update();
735 }
736 
737 void cvContourTool::applyFontProperties() {
738  // Apply font properties to all contour label actors
739  for (auto& pair : m_contours) {
740  if (pair.second) {
741  vtkContourRepresentation* rep =
742  vtkContourRepresentation::SafeDownCast(
743  pair.second->GetRepresentation());
744  if (rep) {
745  // Check if using custom representation with label support
747  cvConstrainedContourRepresentation::SafeDownCast(rep);
748  if (customRep) {
749  if (auto* labelActor = customRep->GetLabelActor()) {
750  if (auto* textProp = labelActor->GetTextProperty()) {
751  textProp->SetFontFamilyAsString(
752  m_fontFamily.toUtf8().constData());
753  textProp->SetFontSize(m_fontSize);
754  textProp->SetColor(m_fontColor[0], m_fontColor[1],
755  m_fontColor[2]);
756  textProp->SetBold(m_fontBold ? 1 : 0);
757  textProp->SetItalic(m_fontItalic ? 1 : 0);
758  textProp->SetShadow(m_fontShadow ? 1 : 0);
759  textProp->SetOpacity(m_fontOpacity);
760 
761  // Apply justification
762  if (m_horizontalJustification == "Left") {
763  textProp->SetJustificationToLeft();
764  } else if (m_horizontalJustification == "Center") {
765  textProp->SetJustificationToCentered();
766  } else if (m_horizontalJustification == "Right") {
767  textProp->SetJustificationToRight();
768  }
769 
770  if (m_verticalJustification == "Top") {
771  textProp->SetVerticalJustificationToTop();
772  } else if (m_verticalJustification == "Center") {
773  textProp->SetVerticalJustificationToCentered();
774  } else if (m_verticalJustification == "Bottom") {
775  textProp->SetVerticalJustificationToBottom();
776  }
777 
778  textProp->Modified(); // Mark as modified to ensure
779  // VTK updates
780  }
781  labelActor->Modified(); // Mark actor as modified to
782  // trigger re-render
783  }
784  }
785  rep->BuildRepresentation();
786  }
787  }
788  }
789 
790  // Force render window update
791  if (m_interactor && m_interactor->GetRenderWindow()) {
792  m_interactor->GetRenderWindow()->Render();
793  }
794 
795  update();
796 }
float PointCoordinateType
Type of the coordinates of a (N-D) point.
Definition: CVTypes.h:16
static bool PrintDebug(const char *format,...)
Same as Print, but works only in Debug mode.
Definition: CVLog.cpp:153
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 Error(const char *format,...)
Display an error dialog with formatted message.
Definition: CVLog.cpp:143
Type z
Definition: CVGeom.h:137
virtual void setVisible(bool state)
Sets entity visibility.
Hierarchical CLOUDVIEWER Object.
Definition: ecvHObject.h:25
virtual QString getName() const
Returns object name.
Definition: ecvObject.h:72
virtual void setName(const QString &name)
Sets object name.
Definition: ecvObject.h:75
Colored polyline.
Definition: ecvPolyline.h:24
void set2DMode(bool state)
Defines if the polyline is considered as 2D or 3D.
void setColor(const ecvColor::Rgb &col)
Sets the polyline color.
Definition: ecvPolyline.h:81
void setWidth(PointCoordinateType width)
Sets the width of the line.
virtual unsigned size() const =0
Returns the number of points.
A generic 3D point cloud with index-based and presistent access to points.
virtual const CCVector3 * getPoint(unsigned index) const =0
Returns the ith point.
void setClosed(bool state)
Sets whether the polyline is closed or not.
Definition: Polyline.h:29
virtual GenericIndexedCloudPersist * getAssociatedCloud()
Returns the associated (source) cloud.
Extended contour representation with instance label support.
vtkTextActor * GetLabelActor()
Get the label actor (for property access)
void SetLabelSuffix(const char *suffix)
Set the label suffix to identify this contour instance.
void BuildRepresentation() override
Build the representation (override to update label position)
virtual void createUi() override
virtual void start() override
~cvContourTool() override
virtual void reset() override
virtual void initTool() override
virtual void setColor(double r, double g, double b) override
Set measurement color (RGB values in range [0.0, 1.0])
virtual ccHObject * getOutput() override
cvContourTool(QWidget *parent=nullptr)
virtual void unlockInteraction() override
Unlock tool interaction (enable VTK widget interaction and UI controls)
virtual bool getColor(double &r, double &g, double &b) const override
virtual void lockInteraction() override
Lock tool interaction (disable VTK widget interaction and UI controls)
virtual void setInstanceLabel(const QString &label) override
Set instance label suffix (e.g., "#1", "#2") for display in 3D view.
virtual void showWidget(bool state) override
QWidget * m_vtkWidget
VTK widget reference for creating shortcuts (saved from linkWith)
vtkRenderWindowInteractor * m_interactor
QList< cvPointPickingHelper * > m_pickingHelpers
List of point picking helpers for keyboard shortcuts.
QString m_fontFamily
Font properties for measurement labels (shared by all tools)
void disableShortcuts()
Disable all keyboard shortcuts (call before tool destruction)
void setupShortcuts(QWidget *vtkWidget)
Ui::GenericMeasurementToolDlg * m_ui
void measurementValueChanged()
Signal sent when the measurement value changes.
cvPointPickingHelper is a helper class for supporting keyboard shortcut-based point picking in measur...
static ccPolyline * ConvertToPolyline(vtkPolyData *polydata, bool silent=false)
Definition: vtk2cc.cpp:392
void vtkInitOnce(T **obj)
Definition: vtkutils.h:44
Tensor Minimum(const Tensor &input, const Tensor &other)
Computes the element-wise minimum of input and other. The tensors must have same data type and device...
bool LessThanEpsilon(float x)
Test a floating point number against our epsilon (a very small number).
Definition: CVMath.h:23
constexpr Rgb green(0, MAX, 0)