ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
cvProtractorTool.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 "cvProtractorTool.h"
9 
11 #include <VtkUtils/signalblocker.h>
12 #include <VtkUtils/vtkutils.h>
13 #include <vtkActor2D.h>
14 #include <vtkCallbackCommand.h>
15 #include <vtkCommand.h>
16 #include <vtkHandleRepresentation.h>
17 #include <vtkMath.h>
18 #include <vtkPointHandleRepresentation3D.h>
19 #include <vtkPolyLineRepresentation.h>
20 #include <vtkProperty.h>
21 #include <vtkProperty2D.h>
22 #include <vtkRenderWindow.h>
23 #include <vtkRenderWindowInteractor.h>
24 #include <vtkRenderer.h>
25 #include <vtkTextActor.h>
26 #include <vtkTextProperty.h>
27 
28 #include <QApplication>
29 #include <QLayout>
30 #include <QLayoutItem>
31 #include <QShortcut>
32 #include <QSizePolicy>
33 #include <algorithm>
34 #include <cmath>
35 
40 
41 // CV_CORE_LIB
42 #include <CVLog.h>
43 
44 // CV_DB_LIB
45 #include <ecv2DLabel.h>
46 #include <ecvBBox.h>
47 #include <ecvGenericMesh.h>
48 #include <ecvGenericPointCloud.h>
49 #include <ecvHObject.h>
50 #include <ecvHObjectCaster.h>
51 #include <ecvPointCloud.h>
52 
53 namespace {
54 
55 using namespace cvMeasurementTools;
56 
59 void configurePolyLineRepresentation(cvConstrainedPolyLineRepresentation* rep,
60  bool use3DHandles = true) {
61  if (!rep) return;
62 
63  // Set number of handles to 3 for angle measurement (Point1, Center, Point2)
64  rep->SetNumberOfHandles(3);
65 
66  // Configure line properties (matching ParaView's LineProperty)
67  if (auto* lineProp = rep->GetLineProperty()) {
68  lineProp->SetColor(RAY_COLOR[0], RAY_COLOR[1],
69  RAY_COLOR[2]); // Red lines
70  lineProp->SetLineWidth(2.0); // ParaView default line width
71  lineProp->SetAmbient(1.0); // ParaView sets ambient to 1.0
72  }
73 
74  // Configure handle properties (matching ParaView)
75  if (auto* handleProp = rep->GetHandleProperty()) {
76  handleProp->SetColor(FOREGROUND_COLOR[0], FOREGROUND_COLOR[1],
77  FOREGROUND_COLOR[2]);
78  }
79  if (auto* selectedHandleProp = rep->GetSelectedHandleProperty()) {
80  selectedHandleProp->SetColor(INTERACTION_COLOR[0], INTERACTION_COLOR[1],
82  }
83 
84  // Configure angle label text properties for better readability
85  if (auto* labelActor = rep->GetAngleLabelActor()) {
86  if (auto* textProp = labelActor->GetTextProperty()) {
87  textProp->SetFontSize(20); // Default font size for angle display
88  textProp->SetBold(0); // Not bold for better readability
89  textProp->SetShadow(1); // Add shadow for better visibility
90  textProp->SetColor(1.0, 1.0, 1.0); // White text
91  }
92  }
93 
94  // Configure angle display features
95  rep->SetShowAngleLabel(1); // Show angle label by default
96  rep->SetShowAngleArc(1); // Show angle arc by default
97  rep->SetArcRadius(1.0); // Default arc radius
98 }
99 
100 } // anonymous namespace
101 
103  : cvGenericMeasurementTool(parent), m_configUi(nullptr) {
104  setWindowTitle(tr("Protractor Measurement Tool"));
105 
106  // Override base class font size default for angle measurements
107  // Angles need larger font for better readability
108  m_fontSize = 20;
109 }
110 
112  // CRITICAL: Explicitly hide and cleanup widget/representation before
113  // destruction
114  if (m_widget) {
115  m_widget->Off(); // Turn off widget
116  m_widget->SetEnabled(0); // Disable widget
117  }
118 
119  // Explicitly hide all representation elements
120  if (m_rep) {
121  m_rep->SetVisibility(0); // Hide everything
122  if (auto* labelActor = m_rep->GetAngleLabelActor()) {
123  labelActor->SetVisibility(0);
124  }
125  if (auto* arcActor = m_rep->GetAngleArcActor()) {
126  arcActor->SetVisibility(0);
127  }
128 
129  // Force immediate render to clear visual elements
130  if (m_interactor && m_interactor->GetRenderWindow()) {
131  m_interactor->GetRenderWindow()->Render();
132  }
133  }
134 
135  if (m_configUi) {
136  delete m_configUi;
137  m_configUi = nullptr;
138  }
139 }
140 
142  // Initialize 3D widget only (simplified - no 2D/3D switching)
143  VtkUtils::vtkInitOnce(m_rep);
144 
145  // Use constrained PolyLine widget - automatically supports XYZ shortcuts
146  // (ParaView way)
148 
149  // Set representation BEFORE calling SetInteractor/SetRenderer
150  m_widget->SetRepresentation(m_rep);
151 
152  if (m_interactor) {
153  m_widget->SetInteractor(m_interactor);
154  }
155  if (m_renderer) {
156  m_rep->SetRenderer(m_renderer);
157  }
158 
159  // Following ParaView's approach:
160  // 1. Configure appearance (set 3 handles for angle measurement)
161  configurePolyLineRepresentation(m_rep, true); // 3D mode
162 
163  // 2. Apply default green color (override configure defaults)
164  if (auto* lineProp = m_rep->GetLineProperty()) {
165  lineProp->SetColor(m_currentColor[0], m_currentColor[1],
166  m_currentColor[2]);
167  }
168  if (auto* selectedLineProp = m_rep->GetSelectedLineProperty()) {
169  selectedLineProp->SetColor(m_currentColor[0], m_currentColor[1],
170  m_currentColor[2]);
171  }
172 
173  // 3. Initialize handle positions based on bounding box
174  // This ensures the protractor is visible outside the object from the start
175  double defaultPos1[3] = {0.0, 0.0, 0.0};
176  double defaultCenter[3] = {1.0, 0.0, 0.0};
177  double defaultPos2[3] = {1.0, 1.0, 0.0};
178 
180  const ccBBox& bbox = m_entity->getBB_recursive();
181  CCVector3 center = bbox.getCenter();
182  CCVector3 diag = bbox.getDiagVec();
183 
184  // Calculate offset based on the bounding box diagonal length
185  // Use a larger offset (30%) to ensure visibility outside any object
186  // orientation
187  double diagLength = diag.norm();
188  double offset = diagLength * 0.3;
189 
190  // Ensure minimum offset for very small objects
191  if (offset < 0.5) {
192  offset = 0.5;
193  }
194 
195  // Place the protractor above and in front of the object for better
196  // visibility Use offset in Y and Z directions to avoid objects in any
197  // orientation
198  defaultCenter[0] = center.x;
199  defaultCenter[1] = center.y + offset; // Offset in Y direction
200  defaultCenter[2] = center.z + offset; // Offset in Z direction
201 
202  defaultPos1[0] = center.x - diag.x * 0.25;
203  defaultPos1[1] = center.y + offset; // Offset in Y direction
204  defaultPos1[2] = center.z + offset; // Offset in Z direction
205 
206  defaultPos2[0] = center.x + diag.x * 0.25;
207  defaultPos2[1] = center.y + offset; // Offset in Y direction
208  defaultPos2[2] = center.z + offset; // Offset in Z direction
209  }
210 
211  m_rep->SetHandlePosition(0, defaultPos1);
212  m_rep->SetHandlePosition(1, defaultCenter);
213  m_rep->SetHandlePosition(2, defaultPos2);
214 
215  // 4. Build representation (before updating UI)
216  m_rep->BuildRepresentation();
217 
218  // 5. Apply font properties to ensure user-configured font properties are
219  // applied
220  applyFontProperties();
221 
222  // 6. Update UI controls with initial positions (if UI is already created)
223  if (m_configUi) {
224  VtkUtils::SignalBlocker blocker1(m_configUi->centerXSpinBox);
225  VtkUtils::SignalBlocker blocker2(m_configUi->centerYSpinBox);
226  VtkUtils::SignalBlocker blocker3(m_configUi->centerZSpinBox);
227  VtkUtils::SignalBlocker blocker4(m_configUi->point1XSpinBox);
228  VtkUtils::SignalBlocker blocker5(m_configUi->point1YSpinBox);
229  VtkUtils::SignalBlocker blocker6(m_configUi->point1ZSpinBox);
230  VtkUtils::SignalBlocker blocker7(m_configUi->point2XSpinBox);
231  VtkUtils::SignalBlocker blocker8(m_configUi->point2YSpinBox);
232  VtkUtils::SignalBlocker blocker9(m_configUi->point2ZSpinBox);
233 
234  m_configUi->centerXSpinBox->setValue(defaultCenter[0]);
235  m_configUi->centerYSpinBox->setValue(defaultCenter[1]);
236  m_configUi->centerZSpinBox->setValue(defaultCenter[2]);
237  m_configUi->point1XSpinBox->setValue(defaultPos1[0]);
238  m_configUi->point1YSpinBox->setValue(defaultPos1[1]);
239  m_configUi->point1ZSpinBox->setValue(defaultPos1[2]);
240  m_configUi->point2XSpinBox->setValue(defaultPos2[0]);
241  m_configUi->point2YSpinBox->setValue(defaultPos2[1]);
242  m_configUi->point2ZSpinBox->setValue(defaultPos2[2]);
243 
244  // Update angle display to show initial angle
245  updateAngleDisplay();
246  }
247 
248  // 6. Enable widget
249  m_widget->On();
250 
251  hookWidget(m_widget);
252 }
253 
255  // CRITICAL: Only setup base UI once to avoid resetting configLayout
256  // Each tool instance has its own m_ui, but setupUi clears all children
257  // so we must ensure it's only called once per tool instance
258  // Check if base UI is already set up by checking if widget has a layout
259  // NOTE: Cannot check m_ui->configLayout directly as it's uninitialized
260  // before setupUi()
261  if (!m_ui) {
262  CVLog::Error("[cvProtractorTool::createUi] m_ui is null!");
263  return;
264  }
265  if (!layout()) {
266  m_ui->setupUi(this);
267  }
268 
269  // CRITICAL: Always clean up existing config UI before creating new one
270  // This prevents UI interference when createUi() is called multiple times
271  // (e.g., when tool is restarted or switched)
272  if (m_configUi && m_ui->configLayout) {
273  // Remove all existing widgets from configLayout
274  QLayoutItem* item;
275  while ((item = m_ui->configLayout->takeAt(0)) != nullptr) {
276  if (item->widget()) {
277  item->widget()->setParent(nullptr);
278  item->widget()->deleteLater();
279  }
280  delete item;
281  }
282  delete m_configUi;
283  m_configUi = nullptr;
284  }
285 
286  // Create fresh config UI for this tool instance
287  m_configUi = new Ui::ProtractorToolDlg;
288  QWidget* configWidget = new QWidget(this);
289  // CRITICAL: Set size policy to Minimum to prevent horizontal expansion
290  // This ensures the widget only takes the space it needs
291  configWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
292  m_configUi->setupUi(configWidget);
293  // CRITICAL: Set layout size constraint to ensure minimum size calculation
294  // This prevents extra whitespace on the right
295  if (configWidget->layout()) {
296  configWidget->layout()->setSizeConstraint(QLayout::SetMinimumSize);
297  }
298  m_ui->configLayout->addWidget(configWidget);
299  m_ui->groupBox->setTitle(tr("Protractor Parameters"));
300 
301 #ifdef Q_OS_MAC
302  m_configUi->instructionLabel->setText(
303  m_configUi->instructionLabel->text().replace("Ctrl", "Cmd"));
304 #endif
305 
306  // CRITICAL: Ensure Tips label can display full text with ParaView-style
307  // compact layout ParaView uses Minimum sizePolicy to prevent horizontal
308  // expansion This must be done AFTER text is set (including macOS text
309  // replacement)
310  if (m_configUi->instructionLabel) {
311  // ParaView-style: Use Minimum sizePolicy to prevent horizontal
312  // expansion The label will wrap text based on its natural width, not a
313  // fixed maximum
314  m_configUi->instructionLabel->setSizePolicy(QSizePolicy::Minimum,
316  // CRITICAL: Remove any maximum height constraint to allow full text
317  // display
318  m_configUi->instructionLabel->setMaximumHeight(
319  16777215); // QWIDGETSIZE_MAX equivalent
320  // Remove maximum width constraint - let it wrap naturally based on
321  // parent width
322  m_configUi->instructionLabel->setMaximumWidth(
323  16777215); // QWIDGETSIZE_MAX equivalent
324  m_configUi->instructionLabel->setWordWrap(true);
325  // Force the label to update its size based on wrapped text
326  // This ensures the label expands vertically to show all text
327  m_configUi->instructionLabel->adjustSize();
328  // CRITICAL: Update geometry to ensure layout recalculates
329  m_configUi->instructionLabel->updateGeometry();
330  }
331 
332  // CRITICAL: Use Qt's automatic sizing based on sizeHint
333  // This ensures each tool adapts to its own content without interference
334  // Reset size constraints to allow Qt's layout system to work properly
335  // ParaView-style: use Minimum (horizontal) to prevent unnecessary expansion
336  this->setMinimumSize(0, 0);
337  this->setMaximumSize(16777215, 16777215); // QWIDGETSIZE_MAX equivalent
338  this->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
339 
340  // Let Qt calculate the optimal size based on content
341  // Order matters: adjust configWidget first, then the main widget
342  configWidget->adjustSize();
343  this->adjustSize();
344  // Force layout update to apply size changes
345  this->updateGeometry();
346  // CRITICAL: Process events to ensure layout is fully updated
347  QApplication::processEvents();
348 
349  connect(m_configUi->point1XSpinBox,
350  QOverload<double>::of(&QDoubleSpinBox::valueChanged), this,
351  &cvProtractorTool::on_point1XSpinBox_valueChanged);
352  connect(m_configUi->point1YSpinBox,
353  QOverload<double>::of(&QDoubleSpinBox::valueChanged), this,
354  &cvProtractorTool::on_point1YSpinBox_valueChanged);
355  connect(m_configUi->point1ZSpinBox,
356  QOverload<double>::of(&QDoubleSpinBox::valueChanged), this,
357  &cvProtractorTool::on_point1ZSpinBox_valueChanged);
358  connect(m_configUi->centerXSpinBox,
359  QOverload<double>::of(&QDoubleSpinBox::valueChanged), this,
360  &cvProtractorTool::on_centerXSpinBox_valueChanged);
361  connect(m_configUi->centerYSpinBox,
362  QOverload<double>::of(&QDoubleSpinBox::valueChanged), this,
363  &cvProtractorTool::on_centerYSpinBox_valueChanged);
364  connect(m_configUi->centerZSpinBox,
365  QOverload<double>::of(&QDoubleSpinBox::valueChanged), this,
366  &cvProtractorTool::on_centerZSpinBox_valueChanged);
367  connect(m_configUi->point2XSpinBox,
368  QOverload<double>::of(&QDoubleSpinBox::valueChanged), this,
369  &cvProtractorTool::on_point2XSpinBox_valueChanged);
370  connect(m_configUi->point2YSpinBox,
371  QOverload<double>::of(&QDoubleSpinBox::valueChanged), this,
372  &cvProtractorTool::on_point2YSpinBox_valueChanged);
373  connect(m_configUi->point2ZSpinBox,
374  QOverload<double>::of(&QDoubleSpinBox::valueChanged), this,
375  &cvProtractorTool::on_point2ZSpinBox_valueChanged);
376 
377  // Connect point picking buttons
378  connect(m_configUi->pickPoint1ToolButton, &QToolButton::toggled, this,
379  &cvProtractorTool::on_pickPoint1_toggled);
380  connect(m_configUi->pickCenterToolButton, &QToolButton::toggled, this,
381  &cvProtractorTool::on_pickCenter_toggled);
382  connect(m_configUi->pickPoint2ToolButton, &QToolButton::toggled, this,
383  &cvProtractorTool::on_pickPoint2_toggled);
384 
385  // Connect display options
386  connect(m_configUi->widgetVisibilityCheckBox, &QCheckBox::toggled, this,
387  &cvProtractorTool::on_widgetVisibilityCheckBox_toggled);
388  connect(m_configUi->arcVisibilityCheckBox, &QCheckBox::toggled, this,
389  &cvProtractorTool::on_arcVisibilityCheckBox_toggled);
390 }
391 
394  // if (m_renderer) {
395  // m_renderer->ResetCamera();
396  // }
397 }
398 
400  // Reset points to default positions (above the bounding box for better
401  // visibility and accessibility)
402  double defaultCenter[3] = {0.0, 0.0, 0.0};
403  double defaultPos1[3] = {-0.5, 0.0, 0.0};
404  double defaultPos2[3] = {0.5, 0.0, 0.0};
405 
407  const ccBBox& bbox = m_entity->getBB_recursive();
408  CCVector3 center = bbox.getCenter();
409  CCVector3 diag = bbox.getDiagVec();
410 
411  // Calculate offset based on the bounding box diagonal length
412  // Use a larger offset (30%) to ensure visibility outside any object
413  // orientation
414  double diagLength = diag.norm();
415  double offset = diagLength * 0.3;
416 
417  // Ensure minimum offset for very small objects
418  if (offset < 0.5) {
419  offset = 0.5;
420  }
421 
422  // Place the protractor above and in front of the object for better
423  // visibility Use offset in Y and Z directions to avoid objects in any
424  // orientation
425  defaultCenter[0] = center.x;
426  defaultCenter[1] = center.y + offset; // Offset in Y direction
427  defaultCenter[2] = center.z + offset; // Offset in Z direction
428 
429  defaultPos1[0] = center.x - diag.x * 0.25;
430  defaultPos1[1] = center.y + offset; // Offset in Y direction
431  defaultPos1[2] = center.z + offset; // Offset in Z direction
432 
433  defaultPos2[0] = center.x + diag.x * 0.25;
434  defaultPos2[1] = center.y + offset; // Offset in Y direction
435  defaultPos2[2] = center.z + offset; // Offset in Z direction
436  }
437 
438  // Reset widget points
439  if (m_widget && m_widget->GetEnabled()) {
440  m_rep->SetCenterWorldPosition(defaultCenter);
441  m_rep->SetPoint1WorldPosition(defaultPos1);
442  m_rep->SetPoint2WorldPosition(defaultPos2);
443  m_rep->BuildRepresentation();
444 
445  // Reapply text properties after BuildRepresentation
446  applyTextPropertiesToLabel();
447 
448  m_widget->Modified();
449  }
450 
451  // Update UI
452  if (m_configUi) {
453  VtkUtils::SignalBlocker blocker1(m_configUi->centerXSpinBox);
454  VtkUtils::SignalBlocker blocker2(m_configUi->centerYSpinBox);
455  VtkUtils::SignalBlocker blocker3(m_configUi->centerZSpinBox);
456  VtkUtils::SignalBlocker blocker4(m_configUi->point1XSpinBox);
457  VtkUtils::SignalBlocker blocker5(m_configUi->point1YSpinBox);
458  VtkUtils::SignalBlocker blocker6(m_configUi->point1ZSpinBox);
459  VtkUtils::SignalBlocker blocker7(m_configUi->point2XSpinBox);
460  VtkUtils::SignalBlocker blocker8(m_configUi->point2YSpinBox);
461  VtkUtils::SignalBlocker blocker9(m_configUi->point2ZSpinBox);
462 
463  m_configUi->centerXSpinBox->setValue(defaultCenter[0]);
464  m_configUi->centerYSpinBox->setValue(defaultCenter[1]);
465  m_configUi->centerZSpinBox->setValue(defaultCenter[2]);
466  m_configUi->point1XSpinBox->setValue(defaultPos1[0]);
467  m_configUi->point1YSpinBox->setValue(defaultPos1[1]);
468  m_configUi->point1ZSpinBox->setValue(defaultPos1[2]);
469  m_configUi->point2XSpinBox->setValue(defaultPos2[0]);
470  m_configUi->point2YSpinBox->setValue(defaultPos2[1]);
471  m_configUi->point2ZSpinBox->setValue(defaultPos2[2]);
472  }
473 
474  update();
476 }
477 
479  if (!vtkWidget) return;
480 
481  // '1' - Pick point 1 on surface
482  cvPointPickingHelper* pickHelper1 =
483  new cvPointPickingHelper(QKeySequence(tr("1")), false, vtkWidget);
484  pickHelper1->setInteractor(m_interactor);
485  pickHelper1->setRenderer(m_renderer);
486  pickHelper1->setContextWidget(this);
487  connect(pickHelper1, &cvPointPickingHelper::pick, this,
488  &cvProtractorTool::pickKeyboardPoint1);
489  m_pickingHelpers.append(pickHelper1);
490 
491  // 'Ctrl+1' - Pick point 1, snap to mesh points
492  cvPointPickingHelper* pickHelper1Snap = new cvPointPickingHelper(
493  QKeySequence(tr("Ctrl+1")), true, vtkWidget);
494  pickHelper1Snap->setInteractor(m_interactor);
495  pickHelper1Snap->setRenderer(m_renderer);
496  pickHelper1Snap->setContextWidget(this);
497  connect(pickHelper1Snap, &cvPointPickingHelper::pick, this,
498  &cvProtractorTool::pickKeyboardPoint1);
499  m_pickingHelpers.append(pickHelper1Snap);
500 
501  // 'C' - Pick center on surface
502  cvPointPickingHelper* pickHelperCenter =
503  new cvPointPickingHelper(QKeySequence(tr("C")), false, vtkWidget);
504  pickHelperCenter->setInteractor(m_interactor);
505  pickHelperCenter->setRenderer(m_renderer);
506  pickHelperCenter->setContextWidget(this);
507  connect(pickHelperCenter, &cvPointPickingHelper::pick, this,
508  &cvProtractorTool::pickKeyboardCenter);
509  m_pickingHelpers.append(pickHelperCenter);
510 
511  // 'Ctrl+C' - Pick center, snap to mesh points
512  cvPointPickingHelper* pickHelperCenterSnap = new cvPointPickingHelper(
513  QKeySequence(tr("Ctrl+C")), true, vtkWidget);
514  pickHelperCenterSnap->setInteractor(m_interactor);
515  pickHelperCenterSnap->setRenderer(m_renderer);
516  pickHelperCenterSnap->setContextWidget(this);
517  connect(pickHelperCenterSnap, &cvPointPickingHelper::pick, this,
518  &cvProtractorTool::pickKeyboardCenter);
519  m_pickingHelpers.append(pickHelperCenterSnap);
520 
521  // '2' - Pick point 2 on surface
522  cvPointPickingHelper* pickHelper2 =
523  new cvPointPickingHelper(QKeySequence(tr("2")), false, vtkWidget);
524  pickHelper2->setInteractor(m_interactor);
525  pickHelper2->setRenderer(m_renderer);
526  pickHelper2->setContextWidget(this);
527  connect(pickHelper2, &cvPointPickingHelper::pick, this,
528  &cvProtractorTool::pickKeyboardPoint2);
529  m_pickingHelpers.append(pickHelper2);
530 
531  // 'Ctrl+2' - Pick point 2, snap to mesh points
532  cvPointPickingHelper* pickHelper2Snap = new cvPointPickingHelper(
533  QKeySequence(tr("Ctrl+2")), true, vtkWidget);
534  pickHelper2Snap->setInteractor(m_interactor);
535  pickHelper2Snap->setRenderer(m_renderer);
536  pickHelper2Snap->setContextWidget(this);
537  connect(pickHelper2Snap, &cvPointPickingHelper::pick, this,
538  &cvProtractorTool::pickKeyboardPoint2);
539  m_pickingHelpers.append(pickHelper2Snap);
540 }
541 
543  if (m_widget) {
544  if (state) {
545  m_widget->On();
546  } else {
547  m_widget->Off();
548  }
549  }
550 
551  // Explicitly control representation visibility to ensure arc and label are
552  // hidden/shown
553  if (m_rep) {
554  m_rep->SetVisibility(state ? 1 : 0);
555  m_rep->BuildRepresentation();
556 
557  // Reapply text properties after BuildRepresentation
558  if (state) {
559  applyTextPropertiesToLabel();
560  }
561 
562  if (m_widget) {
563  m_widget->Modified();
564  m_widget->Render();
565  }
566  }
567 
568  update();
569 }
570 
572  // Export angle measurement as cc2DLabel with 3 points (triangle/plane)
573  // Returns a new cc2DLabel that can be added to the DB tree
574 
575  if (!m_entity) {
577  "[cvProtractorTool::getOutput] No entity associated with this "
578  "measurement");
579  return nullptr;
580  }
581 
582  // Get the point coordinates
583  double p1[3], center[3], p2[3];
584  getPoint1(p1);
585  getCenter(center);
586  getPoint2(p2);
587 
588  // Try to get the associated point cloud
589  ccGenericPointCloud* cloud = nullptr;
591  cloud = static_cast<ccGenericPointCloud*>(m_entity);
592  } else if (m_entity->isKindOf(CV_TYPES::MESH)) {
593  ccGenericMesh* mesh = static_cast<ccGenericMesh*>(m_entity);
594  if (mesh) {
595  cloud = mesh->getAssociatedCloud();
596  }
597  }
598 
599  if (!cloud || cloud->size() == 0) {
601  "[cvProtractorTool::getOutput] Could not find associated point "
602  "cloud or cloud is empty");
603  return nullptr;
604  }
605 
606  // Convert protractor's exact 3D coordinates to CCVector3
607  CCVector3 point1(static_cast<PointCoordinateType>(p1[0]),
608  static_cast<PointCoordinateType>(p1[1]),
609  static_cast<PointCoordinateType>(p1[2]));
610  CCVector3 pointC(static_cast<PointCoordinateType>(center[0]),
611  static_cast<PointCoordinateType>(center[1]),
612  static_cast<PointCoordinateType>(center[2]));
613  CCVector3 point2(static_cast<PointCoordinateType>(p2[0]),
614  static_cast<PointCoordinateType>(p2[1]),
615  static_cast<PointCoordinateType>(p2[2]));
616 
617  // Find the nearest points in the cloud for all three measurement endpoints
618  // CRITICAL: We need to use the exact protractor coordinates, not just the
619  // nearest points in the cloud. If the nearest point is too far away, we
620  // should add the exact point to the cloud to ensure the exported label
621  // matches the protractor exactly.
622  unsigned nearestIndex1 = 0;
623  unsigned nearestIndexC = 0; // center
624  unsigned nearestIndex2 = 0;
625  double minDist1 = std::numeric_limits<double>::max();
626  double minDistC = std::numeric_limits<double>::max();
627  double minDist2 = std::numeric_limits<double>::max();
628 
629  // Threshold for considering a point "close enough" (1mm in world units)
630  // If the nearest point is farther than this, we'll add the exact point
631  const double DISTANCE_THRESHOLD = 0.001;
632 
633  for (unsigned i = 0; i < cloud->size(); ++i) {
634  const CCVector3* P = cloud->getPoint(i);
635  if (!P) continue;
636 
637  double d1 = (*P - point1).norm();
638  if (d1 < minDist1) {
639  minDist1 = d1;
640  nearestIndex1 = i;
641  }
642 
643  double dC = (*P - pointC).norm();
644  if (dC < minDistC) {
645  minDistC = dC;
646  nearestIndexC = i;
647  }
648 
649  double d2 = (*P - point2).norm();
650  if (d2 < minDist2) {
651  minDist2 = d2;
652  nearestIndex2 = i;
653  }
654  }
655 
656  // CRITICAL: If the nearest points are too far from the exact protractor
657  // coordinates, add the exact points to the cloud to ensure perfect
658  // alignment This ensures the exported label's edges match the protractor's
659  // rays exactly
660  ccPointCloud* pointCloud = ccHObjectCaster::ToPointCloud(cloud);
661  if (pointCloud) {
662  // Calculate how many new points we need to add
663  unsigned pointsToAdd = 0;
664  if (minDist1 > DISTANCE_THRESHOLD) pointsToAdd++;
665  if (minDistC > DISTANCE_THRESHOLD) pointsToAdd++;
666  if (minDist2 > DISTANCE_THRESHOLD) pointsToAdd++;
667 
668  // Reserve memory for all new points at once (more efficient)
669  if (pointsToAdd > 0) {
670  unsigned currentSize = pointCloud->size();
671  if (pointCloud->reserve(currentSize + pointsToAdd)) {
672  // Check and add point1 if needed
673  if (minDist1 > DISTANCE_THRESHOLD) {
674  pointCloud->addPoint(point1);
675  nearestIndex1 = pointCloud->size() - 1;
676  }
677 
678  // Check and add center if needed
679  if (minDistC > DISTANCE_THRESHOLD) {
680  pointCloud->addPoint(pointC);
681  nearestIndexC = pointCloud->size() - 1;
682  }
683 
684  // Check and add point2 if needed
685  if (minDist2 > DISTANCE_THRESHOLD) {
686  pointCloud->addPoint(point2);
687  nearestIndex2 = pointCloud->size() - 1;
688  }
689  }
690  }
691  }
692 
693  // Create a new 2D label with the three points
694  // For angle/protractor measurements, the label displays as a triangle with
695  // angle info
696  cc2DLabel* label = new cc2DLabel(
697  QString("Angle: %1°").arg(getMeasurementValue(), 0, 'f', 2));
698 
699  // Add the three picked points to the label
700  // Order: A (point1), B (center/vertex), C (point2) - center is the vertex
701  // of the angle
702  if (!label->addPickedPoint(cloud, nearestIndex1)) {
704  "[cvProtractorTool::getOutput] Failed to add first point (A) "
705  "to label");
706  delete label;
707  return nullptr;
708  }
709 
710  if (!label->addPickedPoint(cloud, nearestIndexC)) {
712  "[cvProtractorTool::getOutput] Failed to add center point (B) "
713  "to label");
714  delete label;
715  return nullptr;
716  }
717 
718  if (!label->addPickedPoint(cloud, nearestIndex2)) {
720  "[cvProtractorTool::getOutput] Failed to add second point (C) "
721  "to label");
722  delete label;
723  return nullptr;
724  }
725 
726  // Configure the label display settings
727  label->setVisible(true);
728  label->setEnabled(true);
729  label->setDisplayedIn2D(true);
730  label->displayPointLegend(true);
731  label->setCollapsed(false);
732 
733  // Get the angle label's screen position from VTK representation
734  // This ensures the exported label appears at the same location as the 3D
735  // angle label
736  float labelPosX = 0.05f; // Default fallback position
737  float labelPosY = 0.90f; // Default fallback position
738 
739  if (m_rep && m_renderer) {
740  // Get the angle label actor position (in display/pixel coordinates)
741  if (auto* labelActor = m_rep->GetAngleLabelActor()) {
742  double* vtkPos = labelActor->GetPosition(); // Returns [x, y] in
743  // display coordinates
744  if (vtkPos && m_interactor && m_interactor->GetRenderWindow()) {
745  int* windowSize = m_interactor->GetRenderWindow()->GetSize();
746  if (windowSize && windowSize[0] > 0 && windowSize[1] > 0) {
747  // Convert from VTK display coordinates (pixels, bottom-left
748  // origin) to cc2DLabel relative coordinates (0.0-1.0,
749  // top-left origin)
750  float normalizedX = static_cast<float>(vtkPos[0]) /
751  static_cast<float>(windowSize[0]);
752  float normalizedY = static_cast<float>(vtkPos[1]) /
753  static_cast<float>(windowSize[1]);
754 
755  // VTK Y=0 is at bottom, cc2DLabel Y=0 is at top
756  // So invert Y: labelY = 1.0 - vtkY
757  labelPosX = normalizedX;
758  labelPosY = 1.0f - normalizedY;
759 
761  QString("[cvProtractorTool::getOutput] Retrieved "
762  "angle label "
763  "position from VTK: display=(%1, %2), "
764  "normalized=(%3, %4)")
765  .arg(vtkPos[0])
766  .arg(vtkPos[1])
767  .arg(labelPosX)
768  .arg(labelPosY));
769  }
770  }
771  }
772  }
773 
774  // Set the position for the exported label (relative to screen, 0.0-1.0)
775  label->setPosition(labelPosX, labelPosY);
776 
777  CVLog::Print(QString("[cvProtractorTool] Exported angle measurement: %1° "
778  "at position (%2, %3)")
779  .arg(getMeasurementValue(), 0, 'f', 2)
780  .arg(labelPosX)
781  .arg(labelPosY));
782 
783  return label;
784 }
785 
787  if (m_configUi) {
788  return m_configUi->angleSpinBox->value();
789  }
790  return 0.0;
791 }
792 
793 void cvProtractorTool::getPoint1(double pos[3]) const {
794  if (m_configUi && pos) {
795  pos[0] = m_configUi->point1XSpinBox->value();
796  pos[1] = m_configUi->point1YSpinBox->value();
797  pos[2] = m_configUi->point1ZSpinBox->value();
798  } else if (pos) {
799  pos[0] = pos[1] = pos[2] = 0.0;
800  }
801 }
802 
803 void cvProtractorTool::getPoint2(double pos[3]) const {
804  if (m_configUi && pos) {
805  pos[0] = m_configUi->point2XSpinBox->value();
806  pos[1] = m_configUi->point2YSpinBox->value();
807  pos[2] = m_configUi->point2ZSpinBox->value();
808  } else if (pos) {
809  pos[0] = pos[1] = pos[2] = 0.0;
810  }
811 }
812 
813 void cvProtractorTool::getCenter(double pos[3]) const {
814  if (m_configUi && pos) {
815  pos[0] = m_configUi->centerXSpinBox->value();
816  pos[1] = m_configUi->centerYSpinBox->value();
817  pos[2] = m_configUi->centerZSpinBox->value();
818  } else if (pos) {
819  pos[0] = pos[1] = pos[2] = 0.0;
820  }
821 }
822 
823 void cvProtractorTool::setPoint1(double pos[3]) {
824  if (!m_configUi || !pos) return;
825 
826  // Uncheck the pick button
827  if (m_configUi->pickPoint1ToolButton->isChecked()) {
828  m_configUi->pickPoint1ToolButton->setChecked(false);
829  }
830 
831  // Update spinboxes without triggering signals
832  VtkUtils::SignalBlocker blocker1(m_configUi->point1XSpinBox);
833  VtkUtils::SignalBlocker blocker2(m_configUi->point1YSpinBox);
834  VtkUtils::SignalBlocker blocker3(m_configUi->point1ZSpinBox);
835  m_configUi->point1XSpinBox->setValue(pos[0]);
836  m_configUi->point1YSpinBox->setValue(pos[1]);
837  m_configUi->point1ZSpinBox->setValue(pos[2]);
838 
839  // Update 3D widget directly
840  if (m_widget && m_widget->GetEnabled()) {
841  m_rep->SetPoint1WorldPosition(pos);
842  m_rep->BuildRepresentation();
843 
844  // Reapply text properties after BuildRepresentation
845  applyTextPropertiesToLabel();
846 
847  m_widget->Modified();
848  m_widget->Render();
849  }
850 
851  // Update angle display
852  updateAngleDisplay();
853  update();
854 }
855 
856 void cvProtractorTool::setPoint2(double pos[3]) {
857  if (!m_configUi || !pos) return;
858 
859  // Uncheck the pick button
860  if (m_configUi->pickPoint2ToolButton->isChecked()) {
861  m_configUi->pickPoint2ToolButton->setChecked(false);
862  }
863 
864  // Update spinboxes without triggering signals
865  VtkUtils::SignalBlocker blocker1(m_configUi->point2XSpinBox);
866  VtkUtils::SignalBlocker blocker2(m_configUi->point2YSpinBox);
867  VtkUtils::SignalBlocker blocker3(m_configUi->point2ZSpinBox);
868  m_configUi->point2XSpinBox->setValue(pos[0]);
869  m_configUi->point2YSpinBox->setValue(pos[1]);
870  m_configUi->point2ZSpinBox->setValue(pos[2]);
871 
872  // Update 3D widget directly
873  if (m_widget && m_widget->GetEnabled()) {
874  m_rep->SetPoint2WorldPosition(pos);
875  m_rep->BuildRepresentation();
876 
877  // Reapply text properties after BuildRepresentation
878  applyTextPropertiesToLabel();
879 
880  m_widget->Modified();
881  m_widget->Render();
882  }
883 
884  // Update angle display
885  updateAngleDisplay();
886  update();
887 }
888 
889 void cvProtractorTool::setCenter(double pos[3]) {
890  if (!m_configUi || !pos) return;
891 
892  // Uncheck the pick button
893  if (m_configUi->pickCenterToolButton->isChecked()) {
894  m_configUi->pickCenterToolButton->setChecked(false);
895  }
896 
897  // Update spinboxes without triggering signals
898  VtkUtils::SignalBlocker blocker1(m_configUi->centerXSpinBox);
899  VtkUtils::SignalBlocker blocker2(m_configUi->centerYSpinBox);
900  VtkUtils::SignalBlocker blocker3(m_configUi->centerZSpinBox);
901  m_configUi->centerXSpinBox->setValue(pos[0]);
902  m_configUi->centerYSpinBox->setValue(pos[1]);
903  m_configUi->centerZSpinBox->setValue(pos[2]);
904 
905  // Update 3D widget directly
906  if (m_widget && m_widget->GetEnabled()) {
907  m_rep->SetCenterWorldPosition(pos);
908  m_rep->BuildRepresentation();
909 
910  // Reapply text properties after BuildRepresentation
911  applyTextPropertiesToLabel();
912 
913  m_widget->Modified();
914  m_widget->Render();
915  }
916 
917  // Update angle display
918  updateAngleDisplay();
919  update();
920 }
921 
922 void cvProtractorTool::setColor(double r, double g, double b) {
923  // Store current color
924  m_currentColor[0] = r;
925  m_currentColor[1] = g;
926  m_currentColor[2] = b;
927 
928  // Set color for poly line representation (rays)
929  if (m_rep) {
930  if (auto* lineProp = m_rep->GetLineProperty()) {
931  lineProp->SetColor(r, g, b);
932  }
933  if (auto* selectedLineProp = m_rep->GetSelectedLineProperty()) {
934  selectedLineProp->SetColor(r, g, b);
935  }
936  m_rep->BuildRepresentation();
937 
938  // Reapply text properties after BuildRepresentation
939  applyTextPropertiesToLabel();
940 
941  if (m_widget) {
942  m_widget->Modified();
943  }
944  }
945  update();
946 }
947 
948 bool cvProtractorTool::getColor(double& r, double& g, double& b) const {
949  r = m_currentColor[0];
950  g = m_currentColor[1];
951  b = m_currentColor[2];
952  return true;
953 }
954 
956  // Disable VTK widget interaction (handles cannot be moved)
957  if (m_widget) {
958  m_widget->SetProcessEvents(0); // Disable event processing
959  }
960 
961  // Change all widget elements to indicate locked state (very dimmed, 10%
962  // brightness)
963  if (m_rep) {
964  // 1. Dim rays (10% brightness, 50% opacity for very obvious locked
965  // effect)
966  if (auto* lineProp = m_rep->GetLineProperty()) {
967  lineProp->SetColor(m_currentColor[0] * 0.1, m_currentColor[1] * 0.1,
968  m_currentColor[2] * 0.1);
969  lineProp->SetOpacity(0.5);
970  }
971  if (auto* selectedLineProp = m_rep->GetSelectedLineProperty()) {
972  selectedLineProp->SetColor(m_currentColor[0] * 0.1,
973  m_currentColor[1] * 0.1,
974  m_currentColor[2] * 0.1);
975  selectedLineProp->SetOpacity(0.5);
976  }
977 
978  // 2. Dim handles (points) - 50% opacity
979  if (auto* handleProp = m_rep->GetHandleProperty()) {
980  handleProp->SetOpacity(0.5);
981  }
982  if (auto* selectedHandleProp = m_rep->GetSelectedHandleProperty()) {
983  selectedHandleProp->SetOpacity(0.5);
984  }
985 
986  // First build representation to update geometry
987  m_rep->BuildRepresentation();
988 
989  // Then set locked state properties AFTER BuildRepresentation
990  // (BuildRepresentation may reset some properties, so we override them
991  // here)
992 
993  // 3. Dim angle label - 50% opacity with dark gray
994  if (auto* labelActor = m_rep->GetAngleLabelActor()) {
995  if (auto* textProp = labelActor->GetTextProperty()) {
996  textProp->SetOpacity(0.5);
997  textProp->SetColor(0.5, 0.5,
998  0.5); // Very dark gray for locked state
999  textProp->Modified(); // Mark as modified
1000  }
1001  labelActor->Modified(); // Mark actor as modified
1002  }
1003 
1004  // 4. Dim angle arc - 50% opacity with very dark yellow
1005  if (auto* arcActor = m_rep->GetAngleArcActor()) {
1006  if (auto* arcProp = arcActor->GetProperty()) {
1007  arcProp->SetOpacity(0.5);
1008  arcProp->SetColor(0.5, 0.5,
1009  0.0); // Very dark yellow for locked state
1010  arcProp->Modified(); // Mark as modified
1011  }
1012  arcActor->Modified(); // Mark actor as modified
1013  }
1014 
1015  if (m_widget) {
1016  m_widget->Modified();
1017  m_widget->Render(); // Force render to apply visual changes
1018  }
1019  }
1020 
1021  // Force render window update to show locked state
1022  if (m_interactor && m_interactor->GetRenderWindow()) {
1023  m_interactor->GetRenderWindow()->Render();
1024  }
1025 
1026  // Disable UI controls
1027  if (m_configUi) {
1028  m_configUi->point1XSpinBox->setEnabled(false);
1029  m_configUi->point1YSpinBox->setEnabled(false);
1030  m_configUi->point1ZSpinBox->setEnabled(false);
1031  m_configUi->centerXSpinBox->setEnabled(false);
1032  m_configUi->centerYSpinBox->setEnabled(false);
1033  m_configUi->centerZSpinBox->setEnabled(false);
1034  m_configUi->point2XSpinBox->setEnabled(false);
1035  m_configUi->point2YSpinBox->setEnabled(false);
1036  m_configUi->point2ZSpinBox->setEnabled(false);
1037  m_configUi->pickPoint1ToolButton->setEnabled(false);
1038  m_configUi->pickCenterToolButton->setEnabled(false);
1039  m_configUi->pickPoint2ToolButton->setEnabled(false);
1040  m_configUi->widgetVisibilityCheckBox->setEnabled(false);
1041  m_configUi->arcVisibilityCheckBox->setEnabled(false);
1042  }
1043 
1044  // Disable keyboard shortcuts
1045  disableShortcuts();
1046 }
1047 
1049  // Enable VTK widget interaction
1050  if (m_widget) {
1051  m_widget->SetProcessEvents(1); // Enable event processing
1052  }
1053 
1054  // Restore all widget elements to active/unlocked state (full color and
1055  // opacity)
1056  if (m_rep) {
1057  // 1. Restore rays (full brightness and opacity)
1058  if (auto* lineProp = m_rep->GetLineProperty()) {
1059  lineProp->SetColor(m_currentColor[0], m_currentColor[1],
1060  m_currentColor[2]);
1061  lineProp->SetOpacity(1.0);
1062  }
1063  if (auto* selectedLineProp = m_rep->GetSelectedLineProperty()) {
1064  selectedLineProp->SetColor(m_currentColor[0], m_currentColor[1],
1065  m_currentColor[2]);
1066  selectedLineProp->SetOpacity(1.0);
1067  }
1068 
1069  // 2. Restore handles (points)
1070  if (auto* handleProp = m_rep->GetHandleProperty()) {
1071  handleProp->SetOpacity(1.0);
1072  }
1073  if (auto* selectedHandleProp = m_rep->GetSelectedHandleProperty()) {
1074  selectedHandleProp->SetOpacity(1.0);
1075  }
1076 
1077  // First build representation to update geometry
1078  m_rep->BuildRepresentation();
1079 
1080  // Then set unlocked state properties AFTER BuildRepresentation
1081  // (BuildRepresentation may reset some properties, so we override them
1082  // here)
1083 
1084  // 3. Restore angle label to user-configured settings
1085  if (auto* labelActor = m_rep->GetAngleLabelActor()) {
1086  if (auto* textProp = labelActor->GetTextProperty()) {
1087  textProp->SetColor(m_fontColor[0], m_fontColor[1],
1088  m_fontColor[2]);
1089  textProp->SetOpacity(m_fontOpacity);
1090  textProp->Modified(); // Mark as modified
1091  }
1092  labelActor->Modified(); // Mark actor as modified
1093  }
1094 
1095  // 4. Restore angle arc (full opacity and yellow color)
1096  if (auto* arcActor = m_rep->GetAngleArcActor()) {
1097  if (auto* arcProp = arcActor->GetProperty()) {
1098  arcProp->SetOpacity(1.0);
1099  arcProp->SetColor(1.0, 1.0, 0.0); // Yellow color
1100  arcProp->Modified(); // Mark as modified
1101  }
1102  arcActor->Modified(); // Mark actor as modified
1103  }
1104 
1105  if (m_widget) {
1106  m_widget->Modified();
1107  m_widget->Render(); // Force render to apply visual changes
1108  }
1109  }
1110 
1111  // Force render window update to show unlocked state
1112  if (m_interactor && m_interactor->GetRenderWindow()) {
1113  m_interactor->GetRenderWindow()->Render();
1114  }
1115 
1116  // Enable UI controls
1117  if (m_configUi) {
1118  m_configUi->point1XSpinBox->setEnabled(true);
1119  m_configUi->point1YSpinBox->setEnabled(true);
1120  m_configUi->point1ZSpinBox->setEnabled(true);
1121  m_configUi->centerXSpinBox->setEnabled(true);
1122  m_configUi->centerYSpinBox->setEnabled(true);
1123  m_configUi->centerZSpinBox->setEnabled(true);
1124  m_configUi->point2XSpinBox->setEnabled(true);
1125  m_configUi->point2YSpinBox->setEnabled(true);
1126  m_configUi->point2ZSpinBox->setEnabled(true);
1127  m_configUi->pickPoint1ToolButton->setEnabled(true);
1128  m_configUi->pickCenterToolButton->setEnabled(true);
1129  m_configUi->pickPoint2ToolButton->setEnabled(true);
1130  m_configUi->widgetVisibilityCheckBox->setEnabled(true);
1131  m_configUi->arcVisibilityCheckBox->setEnabled(true);
1132  }
1133 
1134  // Re-enable keyboard shortcuts
1135  if (m_pickingHelpers.isEmpty()) {
1136  // Shortcuts haven't been created yet - create them now
1137  if (m_vtkWidget) {
1139  QString("[cvProtractorTool::unlockInteraction] Creating "
1140  "shortcuts for tool=%1, using saved vtkWidget=%2")
1141  .arg((quintptr)this, 0, 16)
1142  .arg((quintptr)m_vtkWidget, 0, 16));
1145  QString("[cvProtractorTool::unlockInteraction] After "
1146  "setupShortcuts, m_pickingHelpers.size()=%1")
1147  .arg(m_pickingHelpers.size()));
1148  } else {
1150  QString("[cvProtractorTool::unlockInteraction] m_vtkWidget "
1151  "is null for tool=%1, cannot create shortcuts")
1152  .arg((quintptr)this, 0, 16));
1153  }
1154  } else {
1155  // Shortcuts already exist - update interactor/renderer and enable them
1156  // CRITICAL: Update interactor/renderer in case they weren't set when
1157  // shortcuts were created (e.g., in addInstance before tool is fully
1158  // initialized)
1159  CVLog::PrintDebug(QString("[cvProtractorTool::unlockInteraction] "
1160  "Updating %1 existing shortcuts for tool=%2")
1161  .arg(m_pickingHelpers.size())
1162  .arg((quintptr)this, 0, 16));
1164 
1165  // CRITICAL: Enable all shortcuts. Now using ecvModalShortcut
1166  // (ParaView-style) which automatically handles conflicts via
1167  // ecvKeySequences. When a shortcut is enabled,
1168  // ecvKeySequences::disableSiblings() automatically disables all other
1169  // shortcuts with the same key sequence, ensuring only the active
1170  // (unlocked) tool instance's shortcuts respond.
1171  //
1172  // IMPORTANT: However, QAction shortcuts (like MainWindow's
1173  // actionContourWidget with "Ctrl+C") are NOT managed by ecvKeySequences
1174  // and may still conflict. The MainWindow action's shortcut is handled
1175  // by ecvMeasurementTool when tools are activated/deactivated.
1176  //
1177  // The pickPoint() method in cvPointPickingHelper already checks for
1178  // visibility and enabled state to ensure only the active tool instance
1179  // responds.
1180  for (cvPointPickingHelper* helper : m_pickingHelpers) {
1181  if (helper) {
1182  // Simply enable - ecvModalShortcut will handle disabling
1183  // siblings
1184  helper->setEnabled(true,
1185  false); // Enable without setting focus
1186  }
1187  }
1188  }
1189 }
1190 
1191 void cvProtractorTool::setInstanceLabel(const QString& label) {
1192  // Store the instance label
1193  m_instanceLabel = label;
1194 
1195  // Update the VTK representation's label suffix
1196  if (m_rep) {
1197  m_rep->SetLabelSuffix(m_instanceLabel.toUtf8().constData());
1198 
1199  // Call applyFontProperties() which will rebuild representation
1200  // and reapply font properties correctly
1201  applyFontProperties();
1202  }
1203 }
1204 
1205 void cvProtractorTool::on_point1XSpinBox_valueChanged(double arg1) {
1206  if (!m_configUi) return;
1207  double pos[3];
1208  double newPos[3];
1209  newPos[0] = arg1;
1210 
1211  VtkUtils::SignalBlocker blocker(m_configUi->point1XSpinBox);
1212 
1213  if (m_widget && m_widget->GetEnabled()) {
1214  m_rep->GetPoint1WorldPosition(pos);
1215  newPos[1] = pos[1];
1216  newPos[2] = pos[2];
1217  m_rep->SetPoint1WorldPosition(newPos);
1218  m_rep->BuildRepresentation();
1219  m_widget->Modified();
1220  m_widget->Render();
1221  }
1222  updateAngleDisplay();
1223  update();
1224 }
1225 
1226 void cvProtractorTool::on_point1YSpinBox_valueChanged(double arg1) {
1227  if (!m_configUi) return;
1228  double pos[3];
1229  double newPos[3];
1230  newPos[1] = arg1;
1231 
1232  VtkUtils::SignalBlocker blocker(m_configUi->point1YSpinBox);
1233 
1234  if (m_widget && m_widget->GetEnabled()) {
1235  m_rep->GetPoint1WorldPosition(pos);
1236  newPos[0] = pos[0];
1237  newPos[2] = pos[2];
1238  m_rep->SetPoint1WorldPosition(newPos);
1239  m_rep->BuildRepresentation();
1240  m_widget->Modified();
1241  m_widget->Render();
1242  }
1243  updateAngleDisplay();
1244  update();
1245 }
1246 
1247 void cvProtractorTool::on_point1ZSpinBox_valueChanged(double arg1) {
1248  if (!m_configUi) return;
1249  double pos[3];
1250  double newPos[3];
1251  newPos[2] = arg1;
1252 
1253  VtkUtils::SignalBlocker blocker(m_configUi->point1ZSpinBox);
1254 
1255  if (m_widget && m_widget->GetEnabled()) {
1256  m_rep->GetPoint1WorldPosition(pos);
1257  newPos[0] = pos[0];
1258  newPos[1] = pos[1];
1259  m_rep->SetPoint1WorldPosition(newPos);
1260  m_rep->BuildRepresentation();
1261  m_widget->Modified();
1262  m_widget->Render();
1263  }
1264  updateAngleDisplay();
1265  update();
1266 }
1267 
1268 void cvProtractorTool::on_centerXSpinBox_valueChanged(double arg1) {
1269  if (!m_configUi) return;
1270  double pos[3];
1271  double newPos[3];
1272  newPos[0] = arg1;
1273 
1274  VtkUtils::SignalBlocker blocker(m_configUi->centerXSpinBox);
1275 
1276  if (m_widget && m_widget->GetEnabled()) {
1277  m_rep->GetCenterWorldPosition(pos);
1278  newPos[1] = pos[1];
1279  newPos[2] = pos[2];
1280  m_rep->SetCenterWorldPosition(newPos);
1281  m_rep->BuildRepresentation();
1282  m_widget->Modified();
1283  m_widget->Render();
1284  }
1285  updateAngleDisplay();
1286  update();
1287 }
1288 
1289 void cvProtractorTool::on_centerYSpinBox_valueChanged(double arg1) {
1290  if (!m_configUi) return;
1291  double pos[3];
1292  double newPos[3];
1293  newPos[1] = arg1;
1294 
1295  VtkUtils::SignalBlocker blocker(m_configUi->centerYSpinBox);
1296 
1297  if (m_widget && m_widget->GetEnabled()) {
1298  m_rep->GetCenterWorldPosition(pos);
1299  newPos[0] = pos[0];
1300  newPos[2] = pos[2];
1301  m_rep->SetCenterWorldPosition(newPos);
1302  m_rep->BuildRepresentation();
1303  m_widget->Modified();
1304  m_widget->Render();
1305  }
1306  updateAngleDisplay();
1307  update();
1308 }
1309 
1310 void cvProtractorTool::on_centerZSpinBox_valueChanged(double arg1) {
1311  if (!m_configUi) return;
1312  double pos[3];
1313  double newPos[3];
1314  newPos[2] = arg1;
1315 
1316  VtkUtils::SignalBlocker blocker(m_configUi->centerZSpinBox);
1317 
1318  if (m_widget && m_widget->GetEnabled()) {
1319  m_rep->GetCenterWorldPosition(pos);
1320  newPos[0] = pos[0];
1321  newPos[1] = pos[1];
1322  m_rep->SetCenterWorldPosition(newPos);
1323  m_rep->BuildRepresentation();
1324  m_widget->Modified();
1325  m_widget->Render();
1326  }
1327  updateAngleDisplay();
1328  update();
1329 }
1330 
1331 void cvProtractorTool::on_point2XSpinBox_valueChanged(double arg1) {
1332  if (!m_configUi) return;
1333  double pos[3];
1334  double newPos[3];
1335  newPos[0] = arg1;
1336 
1337  VtkUtils::SignalBlocker blocker(m_configUi->point2XSpinBox);
1338 
1339  if (m_widget && m_widget->GetEnabled()) {
1340  m_rep->GetPoint2WorldPosition(pos);
1341  newPos[1] = pos[1];
1342  newPos[2] = pos[2];
1343  m_rep->SetPoint2WorldPosition(newPos);
1344  m_rep->BuildRepresentation();
1345  m_widget->Modified();
1346  m_widget->Render();
1347  }
1348  updateAngleDisplay();
1349  update();
1350 }
1351 
1352 void cvProtractorTool::on_point2YSpinBox_valueChanged(double arg1) {
1353  if (!m_configUi) return;
1354  double pos[3];
1355  double newPos[3];
1356  newPos[1] = arg1;
1357 
1358  VtkUtils::SignalBlocker blocker(m_configUi->point2YSpinBox);
1359 
1360  if (m_widget && m_widget->GetEnabled()) {
1361  m_rep->GetPoint2WorldPosition(pos);
1362  newPos[0] = pos[0];
1363  newPos[2] = pos[2];
1364  m_rep->SetPoint2WorldPosition(newPos);
1365  m_rep->BuildRepresentation();
1366  m_widget->Modified();
1367  m_widget->Render();
1368  }
1369  updateAngleDisplay();
1370  update();
1371 }
1372 
1373 void cvProtractorTool::on_point2ZSpinBox_valueChanged(double arg1) {
1374  if (!m_configUi) return;
1375  double pos[3];
1376  double newPos[3];
1377  newPos[2] = arg1;
1378 
1379  VtkUtils::SignalBlocker blocker(m_configUi->point2ZSpinBox);
1380 
1381  if (m_widget && m_widget->GetEnabled()) {
1382  m_rep->GetPoint2WorldPosition(pos);
1383  newPos[0] = pos[0];
1384  newPos[1] = pos[1];
1385  m_rep->SetPoint2WorldPosition(newPos);
1386  m_rep->BuildRepresentation();
1387  m_widget->Modified();
1388  m_widget->Render();
1389  }
1390  updateAngleDisplay();
1391  update();
1392 }
1393 
1394 void cvProtractorTool::onAngleChanged(double angle) {
1395  if (!m_configUi) return;
1396  VtkUtils::SignalBlocker blocker(m_configUi->angleSpinBox);
1397  m_configUi->angleSpinBox->setValue(angle);
1398  emit measurementValueChanged();
1399 }
1400 
1401 void cvProtractorTool::onWorldPoint1Changed(double* pos) {
1402  if (!m_configUi || !pos) return;
1403  VtkUtils::SignalBlocker blocker1(m_configUi->point1XSpinBox);
1404  VtkUtils::SignalBlocker blocker2(m_configUi->point1YSpinBox);
1405  VtkUtils::SignalBlocker blocker3(m_configUi->point1ZSpinBox);
1406  m_configUi->point1XSpinBox->setValue(pos[0]);
1407  m_configUi->point1YSpinBox->setValue(pos[1]);
1408  m_configUi->point1ZSpinBox->setValue(pos[2]);
1409  emit measurementValueChanged();
1410 }
1411 
1412 void cvProtractorTool::onWorldPoint2Changed(double* pos) {
1413  if (!m_configUi || !pos) return;
1414  VtkUtils::SignalBlocker blocker1(m_configUi->point2XSpinBox);
1415  VtkUtils::SignalBlocker blocker2(m_configUi->point2YSpinBox);
1416  VtkUtils::SignalBlocker blocker3(m_configUi->point2ZSpinBox);
1417  m_configUi->point2XSpinBox->setValue(pos[0]);
1418  m_configUi->point2YSpinBox->setValue(pos[1]);
1419  m_configUi->point2ZSpinBox->setValue(pos[2]);
1420  emit measurementValueChanged();
1421 }
1422 
1423 void cvProtractorTool::onWorldCenterChanged(double* pos) {
1424  if (!m_configUi || !pos) return;
1425  VtkUtils::SignalBlocker blocker1(m_configUi->centerXSpinBox);
1426  VtkUtils::SignalBlocker blocker2(m_configUi->centerYSpinBox);
1427  VtkUtils::SignalBlocker blocker3(m_configUi->centerZSpinBox);
1428  m_configUi->centerXSpinBox->setValue(pos[0]);
1429  m_configUi->centerYSpinBox->setValue(pos[1]);
1430  m_configUi->centerZSpinBox->setValue(pos[2]);
1431  emit measurementValueChanged();
1432 }
1433 
1434 void cvProtractorTool::hookWidget(
1436  // TODO: Create a PolyLineWidgetObserver similar to AngleWidgetObserver
1437  // For now, we'll use direct callbacks via vtkCommand::InteractionEvent
1438 
1439  vtkNew<vtkCallbackCommand> callback;
1440  callback->SetCallback([](vtkObject* caller, unsigned long /*eid*/,
1441  void* clientData, void* /*callData*/) {
1442  cvProtractorTool* self = static_cast<cvProtractorTool*>(clientData);
1443  self->updateAngleDisplay();
1444  });
1445  callback->SetClientData(this);
1446 
1447  widget->AddObserver(vtkCommand::InteractionEvent, callback);
1448 }
1449 
1450 void cvProtractorTool::updateAngleDisplay() {
1451  if (!m_configUi) {
1453  "[cvProtractorTool] updateAngleDisplay: m_configUi is null!");
1454  return;
1455  }
1456 
1457  // cvConstrainedPolyLineRepresentation::GetAngle() returns DEGREES
1458  double angleDegrees = 0.0;
1459 
1460  if (m_rep) {
1461  angleDegrees = m_rep->GetAngle();
1462 
1463  // Also update the point coordinates in the UI
1464  double p1[3], center[3], p2[3];
1465  m_rep->GetHandlePosition(0, p1);
1466  m_rep->GetHandlePosition(1, center);
1467  m_rep->GetHandlePosition(2, p2);
1468 
1469  VtkUtils::SignalBlocker blocker1(m_configUi->point1XSpinBox);
1470  VtkUtils::SignalBlocker blocker2(m_configUi->point1YSpinBox);
1471  VtkUtils::SignalBlocker blocker3(m_configUi->point1ZSpinBox);
1472  VtkUtils::SignalBlocker blocker4(m_configUi->centerXSpinBox);
1473  VtkUtils::SignalBlocker blocker5(m_configUi->centerYSpinBox);
1474  VtkUtils::SignalBlocker blocker6(m_configUi->centerZSpinBox);
1475  VtkUtils::SignalBlocker blocker7(m_configUi->point2XSpinBox);
1476  VtkUtils::SignalBlocker blocker8(m_configUi->point2YSpinBox);
1477  VtkUtils::SignalBlocker blocker9(m_configUi->point2ZSpinBox);
1478 
1479  m_configUi->point1XSpinBox->setValue(p1[0]);
1480  m_configUi->point1YSpinBox->setValue(p1[1]);
1481  m_configUi->point1ZSpinBox->setValue(p1[2]);
1482  m_configUi->centerXSpinBox->setValue(center[0]);
1483  m_configUi->centerYSpinBox->setValue(center[1]);
1484  m_configUi->centerZSpinBox->setValue(center[2]);
1485  m_configUi->point2XSpinBox->setValue(p2[0]);
1486  m_configUi->point2YSpinBox->setValue(p2[1]);
1487  m_configUi->point2ZSpinBox->setValue(p2[2]);
1488  }
1489 
1490  VtkUtils::SignalBlocker blocker(m_configUi->angleSpinBox);
1491  m_configUi->angleSpinBox->setValue(angleDegrees);
1492  emit measurementValueChanged();
1493 }
1494 
1495 void cvProtractorTool::on_pickPoint1_toggled(bool checked) {
1496  if (checked) {
1497  // Uncheck other pick buttons
1498  if (m_configUi->pickCenterToolButton->isChecked()) {
1499  m_configUi->pickCenterToolButton->setChecked(false);
1500  }
1501  if (m_configUi->pickPoint2ToolButton->isChecked()) {
1502  m_configUi->pickPoint2ToolButton->setChecked(false);
1503  }
1504  // Enable point picking mode for point 1
1505  emit pointPickingRequested(1);
1506  } else {
1507  // Disable point picking
1508  emit pointPickingCancelled();
1509  }
1510 }
1511 
1512 void cvProtractorTool::on_pickCenter_toggled(bool checked) {
1513  if (checked) {
1514  // Uncheck other pick buttons
1515  if (m_configUi->pickPoint1ToolButton->isChecked()) {
1516  m_configUi->pickPoint1ToolButton->setChecked(false);
1517  }
1518  if (m_configUi->pickPoint2ToolButton->isChecked()) {
1519  m_configUi->pickPoint2ToolButton->setChecked(false);
1520  }
1521  // Enable point picking mode for center
1522  emit pointPickingRequested(3);
1523  } else {
1524  // Disable point picking
1525  emit pointPickingCancelled();
1526  }
1527 }
1528 
1529 void cvProtractorTool::on_pickPoint2_toggled(bool checked) {
1530  if (checked) {
1531  // Uncheck other pick buttons
1532  if (m_configUi->pickPoint1ToolButton->isChecked()) {
1533  m_configUi->pickPoint1ToolButton->setChecked(false);
1534  }
1535  if (m_configUi->pickCenterToolButton->isChecked()) {
1536  m_configUi->pickCenterToolButton->setChecked(false);
1537  }
1538  // Enable point picking mode for point 2
1539  emit pointPickingRequested(2);
1540  } else {
1541  // Disable point picking
1542  emit pointPickingCancelled();
1543  }
1544 }
1545 
1546 void cvProtractorTool::on_widgetVisibilityCheckBox_toggled(bool checked) {
1547  if (!m_configUi) return;
1548 
1549  // Show or hide the widget
1550  if (m_widget) {
1551  if (checked) {
1552  m_widget->On();
1553  } else {
1554  m_widget->Off();
1555  }
1556  }
1557 
1558  // Explicitly control representation visibility to ensure arc and label are
1559  // hidden/shown
1560  if (m_rep) {
1561  m_rep->SetVisibility(checked ? 1 : 0);
1562  m_rep->BuildRepresentation();
1563 
1564  // Reapply text properties after BuildRepresentation
1565  if (checked) {
1566  applyTextPropertiesToLabel();
1567  }
1568 
1569  if (m_widget) {
1570  m_widget->Modified();
1571  m_widget->Render();
1572  }
1573  }
1574 
1575  // Force render window update
1576  if (m_interactor && m_interactor->GetRenderWindow()) {
1577  m_interactor->GetRenderWindow()->Render();
1578  }
1579 
1580  update();
1581 }
1582 
1583 void cvProtractorTool::on_arcVisibilityCheckBox_toggled(bool checked) {
1584  if (!m_configUi) return;
1585 
1586  // Show or hide the arc
1587  if (m_rep) {
1588  m_rep->SetShowAngleArc(checked ? 1 : 0);
1589  m_rep->BuildRepresentation();
1590 
1591  // Reapply text properties after BuildRepresentation
1592  applyTextPropertiesToLabel();
1593 
1594  // Explicitly control arc actor visibility
1595  if (auto* arcActor = m_rep->GetAngleArcActor()) {
1596  arcActor->SetVisibility(checked);
1597  }
1598 
1599  if (m_widget) {
1600  m_widget->Modified();
1601  m_widget->Render();
1602  }
1603  }
1604 
1605  update();
1606 }
1607 
1608 void cvProtractorTool::pickKeyboardPoint1(double x, double y, double z) {
1609  double pos[3] = {x, y, z};
1610  setPoint1(pos);
1611 }
1612 
1613 void cvProtractorTool::pickKeyboardCenter(double x, double y, double z) {
1614  double pos[3] = {x, y, z};
1615  setCenter(pos);
1616 }
1617 
1618 void cvProtractorTool::pickKeyboardPoint2(double x, double y, double z) {
1619  double pos[3] = {x, y, z};
1620  setPoint2(pos);
1621 }
1622 
1623 void cvProtractorTool::applyTextPropertiesToLabel() {
1624  if (!m_rep) return;
1625 
1626  // Apply font properties to the angle label actor
1627  // This does NOT call BuildRepresentation()
1628  if (auto* labelActor = m_rep->GetAngleLabelActor()) {
1629  if (auto* textProp = labelActor->GetTextProperty()) {
1630  // Set font family and size
1631  textProp->SetFontFamilyAsString(m_fontFamily.toUtf8().constData());
1632  textProp->SetFontSize(m_fontSize);
1633 
1634  // Set color and opacity
1635  textProp->SetColor(m_fontColor[0], m_fontColor[1], m_fontColor[2]);
1636  textProp->SetOpacity(m_fontOpacity);
1637 
1638  // Set style properties
1639  textProp->SetBold(m_fontBold ? 1 : 0);
1640  textProp->SetItalic(m_fontItalic ? 1 : 0);
1641  textProp->SetShadow(m_fontShadow ? 1 : 0);
1642 
1643  // Apply horizontal justification
1644  if (m_horizontalJustification == "Left") {
1645  textProp->SetJustificationToLeft();
1646  } else if (m_horizontalJustification == "Center") {
1647  textProp->SetJustificationToCentered();
1648  } else if (m_horizontalJustification == "Right") {
1649  textProp->SetJustificationToRight();
1650  }
1651 
1652  // Apply vertical justification
1653  if (m_verticalJustification == "Top") {
1654  textProp->SetVerticalJustificationToTop();
1655  } else if (m_verticalJustification == "Center") {
1656  textProp->SetVerticalJustificationToCentered();
1657  } else if (m_verticalJustification == "Bottom") {
1658  textProp->SetVerticalJustificationToBottom();
1659  }
1660 
1661  // Mark text property as modified to ensure VTK updates
1662  textProp->Modified();
1663  }
1664 
1665  // Mark label actor as modified to trigger re-render
1666  labelActor->Modified();
1667  }
1668 }
1669 
1670 void cvProtractorTool::applyFontProperties() {
1671  if (!m_rep) return;
1672 
1673  // CRITICAL: First rebuild representation to update geometry
1674  // Then set text properties AFTER BuildRepresentation()
1675  // BuildRepresentation() may reset some text properties to defaults,
1676  // so we must set them AFTER, not before (like cvDistanceTool does)
1677  m_rep->BuildRepresentation();
1678 
1679  // Apply text properties AFTER BuildRepresentation
1680  applyTextPropertiesToLabel();
1681 
1682  // Mark widget as modified and trigger render
1683  if (m_widget) {
1684  m_widget->Modified();
1685  m_widget->Render();
1686  }
1687 
1688  // Force render window update to apply changes immediately
1689  if (m_interactor && m_interactor->GetRenderWindow()) {
1690  m_interactor->GetRenderWindow()->Render();
1691  }
1692 
1693  // Update Qt widget
1694  update();
1695 }
float PointCoordinateType
Type of the coordinates of a (N-D) point.
Definition: CVTypes.h:16
std::function< void(std::shared_ptr< core::Tensor >)> callback
int offset
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 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
Type y
Definition: CVGeom.h:137
Type x
Definition: CVGeom.h:137
Type z
Definition: CVGeom.h:137
Type norm() const
Returns vector norm.
Definition: CVGeom.h:424
2D label (typically attached to points)
Definition: ecv2DLabel.h:22
bool addPickedPoint(ccGenericPointCloud *cloud, unsigned pointIndex, bool entityCenter=false)
Adds a point to this label.
Definition: ecv2DLabel.cpp:400
void setCollapsed(bool state)
Whether to collapse label or not.
Definition: ecv2DLabel.h:102
void setDisplayedIn2D(bool state)
Whether to display the label in 2D.
Definition: ecv2DLabel.h:114
void setPosition(float x, float y)
Sets relative position.
Definition: ecv2DLabel.cpp:225
void displayPointLegend(bool state)
Whether to display the point(s) legend (title only)
Definition: ecv2DLabel.h:108
Bounding box structure.
Definition: ecvBBox.h:25
virtual void setVisible(bool state)
Sets entity visibility.
Generic mesh interface.
virtual ccGenericPointCloud * getAssociatedCloud() const =0
Returns the vertices cloud.
A 3D cloud interface with associated features (color, normals, octree, etc.)
static ccPointCloud * ToPointCloud(ccHObject *obj, bool *isLockedVertices=nullptr)
Converts current object to 'equivalent' ccPointCloud.
Hierarchical CLOUDVIEWER Object.
Definition: ecvHObject.h:25
virtual ccBBox getBB_recursive(bool withGLFeatures=false, bool onlyEnabledChildren=true)
Returns the bounding-box of this entity and it's children.
Definition: ecvHObject.cpp:759
virtual void setEnabled(bool state)
Sets the "enabled" property.
Definition: ecvObject.h:102
bool isKindOf(CV_CLASS_ENUM type) const
Definition: ecvObject.h:128
A 3D cloud and its associated features (color, normals, scalar fields, etc.)
bool reserve(unsigned numberOfPoints) override
Reserves memory for all the active features.
Vector3Tpl< T > getDiagVec() const
Returns diagonal vector.
Definition: BoundingBox.h:169
Vector3Tpl< T > getCenter() const
Returns center.
Definition: BoundingBox.h:164
bool isValid() const
Returns whether bounding box is valid or not.
Definition: BoundingBox.h:203
virtual unsigned size() const =0
Returns the number of points.
virtual const CCVector3 * getPoint(unsigned index) const =0
Returns the ith point.
void addPoint(const CCVector3 &P)
Adds a 3D point to the database.
unsigned size() const override
Definition: PointCloudTpl.h:38
Extended PolyLineRepresentation adding angle display functionality.
QWidget * m_vtkWidget
VTK widget reference for creating shortcuts (saved from linkWith)
void pointPickingCancelled()
Signal sent when point picking is cancelled.
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 pointPickingRequested(int pointIndex)
void disableShortcuts()
Disable all keyboard shortcuts (call before tool destruction)
void setupShortcuts(QWidget *vtkWidget)
void updatePickingHelpers()
Update point picking helpers with current interactor/renderer.
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...
void setContextWidget(QWidget *widget)
Set the context widget for the shortcut.
void setInteractor(vtkRenderWindowInteractor *interactor)
Set the VTK interactor for picking.
void setRenderer(vtkRenderer *renderer)
Set the VTK renderer for picking.
void pick(double x, double y, double z)
Emitted when a point is picked.
virtual void getPoint1(double pos[3]) const override
Get point 1 coordinates.
virtual void setupPointPickingShortcuts(QWidget *vtkWidget) override
virtual double getMeasurementValue() const override
Get measurement value (distance or angle)
virtual void lockInteraction() override
Lock tool interaction (disable VTK widget interaction and UI controls)
virtual void getCenter(double pos[3]) const override
Get center point coordinates (for angle/protractor)
cvProtractorTool(QWidget *parent=nullptr)
~cvProtractorTool() override
virtual void setPoint2(double pos[3]) override
Set point 2 coordinates.
virtual void initTool() override
virtual void start() override
virtual void setPoint1(double pos[3]) override
Set point 1 coordinates.
virtual void setInstanceLabel(const QString &label) override
Set instance label suffix (e.g., "#1", "#2") for display in 3D view.
virtual void setColor(double r, double g, double b) override
Set measurement color (RGB values in range [0.0, 1.0])
virtual void reset() override
virtual void createUi() override
virtual void unlockInteraction() override
Unlock tool interaction (enable VTK widget interaction and UI controls)
virtual void getPoint2(double pos[3]) const override
Get point 2 coordinates.
virtual bool getColor(double &r, double &g, double &b) const override
virtual ccHObject * getOutput() override
virtual void setCenter(double pos[3]) override
Set center point coordinates (for angle/protractor)
virtual void showWidget(bool state) override
normal_z y
normal_z x
normal_z z
@ MESH
Definition: CVTypes.h:105
@ POINT_CLOUD
Definition: CVTypes.h:104
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...
constexpr double FOREGROUND_COLOR[3]
constexpr double INTERACTION_COLOR[3]
constexpr double RAY_COLOR[3]