ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
cvDistanceTool.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 "cvDistanceTool.h"
9 
11 #include <VtkUtils/signalblocker.h>
12 #include <VtkUtils/vtkutils.h>
13 #include <vtkAxisActor2D.h>
14 #include <vtkCommand.h>
15 #include <vtkHandleRepresentation.h>
16 #include <vtkLineRepresentation.h>
17 #include <vtkMath.h>
18 #include <vtkPointHandleRepresentation3D.h>
19 #include <vtkProperty.h>
20 #include <vtkProperty2D.h>
21 #include <vtkRenderWindow.h>
22 #include <vtkRenderWindowInteractor.h>
23 #include <vtkRenderer.h>
24 #include <vtkTextProperty.h>
25 
26 #include <QApplication>
27 #include <QLayout>
28 #include <QLayoutItem>
29 #include <QShortcut>
30 #include <QSizePolicy>
31 #include <algorithm>
32 
38 
39 // CV_CORE_LIB
40 #include <CVLog.h>
41 
42 // CV_DB_LIB
43 #include <ecv2DLabel.h>
44 #include <ecvBBox.h>
45 #include <ecvGenericMesh.h>
46 #include <ecvGenericPointCloud.h>
47 #include <ecvHObject.h>
48 #include <ecvHObjectCaster.h>
49 #include <ecvPointCloud.h>
50 
51 namespace {
52 
53 using namespace cvMeasurementTools;
54 
56 void configureLineRepresentation(cvConstrainedLineRepresentation* rep) {
57  if (!rep) return;
58 
59  // Configure 3D handles
60  auto* h1 = vtkPointHandleRepresentation3D::SafeDownCast(
61  rep->GetPoint1Representation());
62  auto* h2 = vtkPointHandleRepresentation3D::SafeDownCast(
63  rep->GetPoint2Representation());
64  auto* hLine = vtkPointHandleRepresentation3D::SafeDownCast(
65  rep->GetLineHandleRepresentation());
68  configureHandle3D(hLine);
69 
70  // Configure line properties (matching ParaView's LineProperty)
71  if (auto* lineProp = rep->GetLineProperty()) {
72  lineProp->SetColor(FOREGROUND_COLOR[0], FOREGROUND_COLOR[1],
73  FOREGROUND_COLOR[2]);
74  lineProp->SetLineWidth(2.0); // ParaView default line width
75  lineProp->SetAmbient(1.0); // ParaView sets ambient to 1.0
76  }
77 
78  // Configure distance label text properties for better readability
79  if (auto* axis = rep->GetAxisActor()) {
80  // Configure the Title text property (this displays the distance value)
81  if (auto* titleProp = axis->GetTitleTextProperty()) {
82  titleProp->SetFontSize(
83  6); // Default font size for distance display
84  titleProp->SetBold(0); // Not bold for better readability
85  titleProp->SetShadow(1); // Add shadow for better visibility
86  titleProp->SetColor(1.0, 1.0, 1.0); // White text
87  }
88 
89  // Configure the Label text property (for ruler tick labels)
90  if (auto* labelProp = axis->GetLabelTextProperty()) {
91  labelProp->SetFontSize(
92  5); // Slightly smaller font size for tick labels
93  labelProp->SetBold(0);
94  labelProp->SetShadow(1);
95  labelProp->SetColor(1.0, 1.0, 1.0);
96  }
97  }
98 
99  // Configure distance display and ruler features (custom functionality)
100  rep->SetShowLabel(1); // Show distance label by default
101  rep->SetLabelFormat("%-#6.3g"); // Distance format (ParaView default)
102  rep->SetRulerMode(0); // Ruler mode off by default
103  rep->SetRulerDistance(1.0); // Default tick spacing
104  rep->SetNumberOfRulerTicks(5); // Default number of ticks
105  rep->SetScale(1.0); // Default scale factor
106 }
107 
108 } // anonymous namespace
109 
111  : cvGenericMeasurementTool(parent), m_configUi(nullptr) {
112  setWindowTitle(tr("Distance Measurement Tool"));
113 }
114 
116  // CRITICAL: Explicitly hide and cleanup widget/representation before
117  // destruction
118  if (m_widget) {
119  m_widget->Off(); // Turn off widget
120  m_widget->SetEnabled(0); // Disable widget
121  }
122 
123  // Explicitly hide all representation elements
124  if (m_rep) {
125  m_rep->SetVisibility(0); // Hide everything
126 
127  // Force immediate render to clear visual elements
128  if (m_interactor && m_interactor->GetRenderWindow()) {
129  m_interactor->GetRenderWindow()->Render();
130  }
131  }
132 
133  if (m_configUi) {
134  delete m_configUi;
135  m_configUi = nullptr;
136  }
137 }
138 
140  VtkUtils::vtkInitOnce(m_rep);
141 
142  // Use constrained widget - automatically supports XYZL shortcuts
144 
145  // Set representation BEFORE calling SetInteractor/SetRenderer
146  m_widget->SetRepresentation(m_rep);
147 
148  if (m_interactor) {
149  m_widget->SetInteractor(m_interactor);
150  }
151  if (m_renderer) {
152  m_rep->SetRenderer(m_renderer);
153  }
154 
155  // Following ParaView's approach:
156  // 1. InstantiateHandleRepresentation is already called in
157  // vtkLineRepresentation constructor
158  // 2. Replace the default handles with custom axis handles for full XYZL
159  // support Use template version for type-safe creation
160  m_rep->ReplaceHandleRepresentationsTyped<
162 
163  // 2. Configure appearance AFTER instantiation but BEFORE enabling
164  configureLineRepresentation(m_rep); // 3D mode only
165 
166  // 3. Apply default green color (override configure defaults)
167  if (auto* lineProp = m_rep->GetLineProperty()) {
168  lineProp->SetColor(m_currentColor[0], m_currentColor[1],
169  m_currentColor[2]);
170  }
171  if (auto* selectedLineProp = m_rep->GetSelectedLineProperty()) {
172  selectedLineProp->SetColor(m_currentColor[0], m_currentColor[1],
173  m_currentColor[2]);
174  }
175 
176  // 4. Set initial positions before building representation
177  // This ensures the widget is visible outside the object from the start
178  double defaultPos1[3] = {0.0, 0.0, 0.0};
179  double defaultPos2[3] = {1.0, 0.0, 0.0};
180 
182  const ccBBox& bbox = m_entity->getBB_recursive();
183  CCVector3 center = bbox.getCenter();
184  CCVector3 diag = bbox.getDiagVec();
185 
186  // Calculate offset based on the bounding box diagonal length
187  // Use a larger offset (30%) to ensure visibility outside any object
188  // orientation
189  double diagLength = diag.norm();
190  double offset = diagLength * 0.3;
191 
192  // Ensure minimum offset for very small objects
193  if (offset < 0.5) {
194  offset = 0.5;
195  }
196 
197  // Place the ruler above and in front of the object for better
198  // visibility Use offset in Y and Z directions to avoid objects in any
199  // orientation
200  defaultPos1[0] = center.x - diag.x * 0.25;
201  defaultPos1[1] = center.y + offset; // Offset in Y direction
202  defaultPos1[2] = center.z + offset; // Offset in Z direction
203 
204  defaultPos2[0] = center.x + diag.x * 0.25;
205  defaultPos2[1] = center.y + offset; // Offset in Y direction
206  defaultPos2[2] = center.z + offset; // Offset in Z direction
207  }
208 
209  m_rep->SetPoint1WorldPosition(defaultPos1);
210  m_rep->SetPoint2WorldPosition(defaultPos2);
211 
212  // 5. Build representation (before updating UI)
213  m_rep->BuildRepresentation();
214 
215  // 6. Apply font properties to override configureLineRepresentation defaults
216  // This ensures user-configured font properties (size, bold, italic, etc.)
217  // are applied
218  applyFontProperties();
219 
220  // 7. Update UI controls with initial positions (if UI is already created)
221  if (m_configUi) {
222  VtkUtils::SignalBlocker blocker1(m_configUi->point1XSpinBox);
223  VtkUtils::SignalBlocker blocker2(m_configUi->point1YSpinBox);
224  VtkUtils::SignalBlocker blocker3(m_configUi->point1ZSpinBox);
225  VtkUtils::SignalBlocker blocker4(m_configUi->point2XSpinBox);
226  VtkUtils::SignalBlocker blocker5(m_configUi->point2YSpinBox);
227  VtkUtils::SignalBlocker blocker6(m_configUi->point2ZSpinBox);
228 
229  m_configUi->point1XSpinBox->setValue(defaultPos1[0]);
230  m_configUi->point1YSpinBox->setValue(defaultPos1[1]);
231  m_configUi->point1ZSpinBox->setValue(defaultPos1[2]);
232  m_configUi->point2XSpinBox->setValue(defaultPos2[0]);
233  m_configUi->point2YSpinBox->setValue(defaultPos2[1]);
234  m_configUi->point2ZSpinBox->setValue(defaultPos2[2]);
235 
236  // Update distance display to show initial distance
237  updateDistanceDisplay();
238  }
239 
240  // 7. Enable widget
241  m_widget->On();
242 
243  hookWidget(m_widget);
244 }
245 
247  // CRITICAL: Only setup base UI once to avoid resetting configLayout
248  // Each tool instance has its own m_ui, but setupUi clears all children
249  // so we must ensure it's only called once per tool instance
250  // Check if base UI is already set up by checking if widget has a layout
251  // NOTE: Cannot check m_ui->configLayout directly as it's uninitialized
252  // before setupUi()
253  if (!m_ui) {
254  CVLog::Error("[cvDistanceTool::createUi] m_ui is null!");
255  return;
256  }
257  if (!layout()) {
258  m_ui->setupUi(this);
259  }
260 
261  // CRITICAL: Always clean up existing config UI before creating new one
262  // This prevents UI interference when createUi() is called multiple times
263  // (e.g., when tool is restarted or switched)
264  if (m_configUi && m_ui->configLayout) {
265  // Remove all existing widgets from configLayout
266  QLayoutItem* item;
267  while ((item = m_ui->configLayout->takeAt(0)) != nullptr) {
268  if (item->widget()) {
269  item->widget()->setParent(nullptr);
270  item->widget()->deleteLater();
271  }
272  delete item;
273  }
274  delete m_configUi;
275  m_configUi = nullptr;
276  }
277 
278  // Create fresh config UI for this tool instance
279  m_configUi = new Ui::DistanceToolDlg;
280  QWidget* configWidget = new QWidget(this);
281  // CRITICAL: Set size policy to Minimum to prevent horizontal expansion
282  // This ensures the widget only takes the space it needs
283  configWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
284  m_configUi->setupUi(configWidget);
285  // CRITICAL: Set layout size constraint to ensure minimum size calculation
286  // This prevents extra whitespace on the right
287  if (configWidget->layout()) {
288  configWidget->layout()->setSizeConstraint(QLayout::SetMinimumSize);
289  }
290  m_ui->configLayout->addWidget(configWidget);
291  m_ui->groupBox->setTitle(tr("Distance Parameters"));
292 
293  m_configUi->distanceSpinBox->setValue(1.0);
294 
295 #ifdef Q_OS_MAC
296  m_configUi->instructionLabel->setText(
297  m_configUi->instructionLabel->text().replace("Ctrl", "Cmd"));
298 #endif
299 
300  // CRITICAL: Ensure Tips label can display full text with ParaView-style
301  // compact layout ParaView uses Minimum sizePolicy to prevent horizontal
302  // expansion This must be done AFTER text is set (including macOS text
303  // replacement)
304  if (m_configUi->instructionLabel) {
305  // ParaView-style: Use Minimum sizePolicy to prevent horizontal
306  // expansion The label will wrap text based on its natural width, not a
307  // fixed maximum
308  m_configUi->instructionLabel->setSizePolicy(QSizePolicy::Minimum,
310  // CRITICAL: Remove any maximum height constraint to allow full text
311  // display
312  m_configUi->instructionLabel->setMaximumHeight(
313  16777215); // QWIDGETSIZE_MAX equivalent
314  // Remove maximum width constraint - let it wrap naturally based on
315  // parent width
316  m_configUi->instructionLabel->setMaximumWidth(
317  16777215); // QWIDGETSIZE_MAX equivalent
318  m_configUi->instructionLabel->setWordWrap(true);
319  // Force the label to update its size based on wrapped text
320  // This ensures the label expands vertically to show all text
321  m_configUi->instructionLabel->adjustSize();
322  // CRITICAL: Update geometry to ensure layout recalculates
323  m_configUi->instructionLabel->updateGeometry();
324  }
325 
326  // CRITICAL: Use Qt's automatic sizing based on sizeHint
327  // This ensures each tool adapts to its own content without interference
328  // Reset size constraints to allow Qt's layout system to work properly
329  // ParaView-style: use Minimum (horizontal) to prevent unnecessary expansion
330  this->setMinimumSize(0, 0);
331  this->setMaximumSize(16777215, 16777215); // QWIDGETSIZE_MAX equivalent
332  this->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
333 
334  // Let Qt calculate the optimal size based on content
335  // Order matters: adjust configWidget first, then the main widget
336  configWidget->adjustSize();
337  this->adjustSize();
338  // Force layout update to apply size changes
339  this->updateGeometry();
340  // CRITICAL: Process events to ensure layout is fully updated
341  QApplication::processEvents();
342 
343  connect(m_configUi->point1XSpinBox,
344  QOverload<double>::of(&QDoubleSpinBox::valueChanged), this,
345  &cvDistanceTool::on_point1XSpinBox_valueChanged);
346  connect(m_configUi->point1YSpinBox,
347  QOverload<double>::of(&QDoubleSpinBox::valueChanged), this,
348  &cvDistanceTool::on_point1YSpinBox_valueChanged);
349  connect(m_configUi->point1ZSpinBox,
350  QOverload<double>::of(&QDoubleSpinBox::valueChanged), this,
351  &cvDistanceTool::on_point1ZSpinBox_valueChanged);
352  connect(m_configUi->point2XSpinBox,
353  QOverload<double>::of(&QDoubleSpinBox::valueChanged), this,
354  &cvDistanceTool::on_point2XSpinBox_valueChanged);
355  connect(m_configUi->point2YSpinBox,
356  QOverload<double>::of(&QDoubleSpinBox::valueChanged), this,
357  &cvDistanceTool::on_point2YSpinBox_valueChanged);
358  connect(m_configUi->point2ZSpinBox,
359  QOverload<double>::of(&QDoubleSpinBox::valueChanged), this,
360  &cvDistanceTool::on_point2ZSpinBox_valueChanged);
361 
362  // Connect point picking buttons
363  connect(m_configUi->pickPoint1ToolButton, &QToolButton::toggled, this,
364  &cvDistanceTool::on_pickPoint1_toggled);
365  connect(m_configUi->pickPoint2ToolButton, &QToolButton::toggled, this,
366  &cvDistanceTool::on_pickPoint2_toggled);
367 
368  // Connect ruler mode controls
369  connect(m_configUi->rulerModeCheckBox, &QCheckBox::toggled, this,
370  &cvDistanceTool::on_rulerModeCheckBox_toggled);
371  connect(m_configUi->rulerDistanceSpinBox,
372  QOverload<double>::of(&QDoubleSpinBox::valueChanged), this,
373  &cvDistanceTool::on_rulerDistanceSpinBox_valueChanged);
374  connect(m_configUi->numberOfTicksSpinBox,
375  QOverload<int>::of(&QSpinBox::valueChanged), this,
376  &cvDistanceTool::on_numberOfTicksSpinBox_valueChanged);
377  connect(m_configUi->scaleSpinBox,
378  QOverload<double>::of(&QDoubleSpinBox::valueChanged), this,
379  &cvDistanceTool::on_scaleSpinBox_valueChanged);
380  connect(m_configUi->labelFormatLineEdit, &QLineEdit::textChanged, this,
381  &cvDistanceTool::on_labelFormatLineEdit_textChanged);
382 
383  // Connect display options
384  connect(m_configUi->widgetVisibilityCheckBox, &QCheckBox::toggled, this,
385  &cvDistanceTool::on_widgetVisibilityCheckBox_toggled);
386  connect(m_configUi->labelVisibilityCheckBox, &QCheckBox::toggled, this,
387  &cvDistanceTool::on_labelVisibilityCheckBox_toggled);
388  connect(m_configUi->lineWidthSpinBox,
389  QOverload<double>::of(&QDoubleSpinBox::valueChanged), this,
390  &cvDistanceTool::on_lineWidthSpinBox_valueChanged);
391 }
392 
394 
396  // Reset points to default positions (above the bounding box for better
397  // visibility and accessibility)
398  double defaultPos1[3] = {0.0, 0.0, 0.0};
399  double defaultPos2[3] = {1.0, 0.0, 0.0};
400 
402  const ccBBox& bbox = m_entity->getBB_recursive();
403  CCVector3 center = bbox.getCenter();
404  CCVector3 diag = bbox.getDiagVec();
405 
406  // Calculate offset based on the bounding box diagonal length
407  // Use a larger offset (30%) to ensure visibility outside any object
408  // orientation
409  double diagLength = diag.norm();
410  double offset = diagLength * 0.3;
411 
412  // Ensure minimum offset for very small objects
413  if (offset < 0.5) {
414  offset = 0.5;
415  }
416 
417  // Place the ruler above and in front of the object for better
418  // visibility Use offset in Y and Z directions to avoid objects in any
419  // orientation
420  defaultPos1[0] = center.x - diag.x * 0.25;
421  defaultPos1[1] = center.y + offset; // Offset in Y direction
422  defaultPos1[2] = center.z + offset; // Offset in Z direction
423 
424  defaultPos2[0] = center.x + diag.x * 0.25;
425  defaultPos2[1] = center.y + offset; // Offset in Y direction
426  defaultPos2[2] = center.z + offset; // Offset in Z direction
427  }
428 
429  // Reset widget points
430  if (m_widget && m_widget->GetEnabled()) {
431  m_rep->SetPoint1WorldPosition(defaultPos1);
432  m_rep->SetPoint2WorldPosition(defaultPos2);
433  m_rep->BuildRepresentation();
434  m_widget->Modified();
435  }
436 
437  // Update UI
438  if (m_configUi) {
439  VtkUtils::SignalBlocker blocker1(m_configUi->point1XSpinBox);
440  VtkUtils::SignalBlocker blocker2(m_configUi->point1YSpinBox);
441  VtkUtils::SignalBlocker blocker3(m_configUi->point1ZSpinBox);
442  VtkUtils::SignalBlocker blocker4(m_configUi->point2XSpinBox);
443  VtkUtils::SignalBlocker blocker5(m_configUi->point2YSpinBox);
444  VtkUtils::SignalBlocker blocker6(m_configUi->point2ZSpinBox);
445 
446  m_configUi->point1XSpinBox->setValue(defaultPos1[0]);
447  m_configUi->point1YSpinBox->setValue(defaultPos1[1]);
448  m_configUi->point1ZSpinBox->setValue(defaultPos1[2]);
449  m_configUi->point2XSpinBox->setValue(defaultPos2[0]);
450  m_configUi->point2YSpinBox->setValue(defaultPos2[1]);
451  m_configUi->point2ZSpinBox->setValue(defaultPos2[2]);
452  }
453 
454  update();
456 }
457 
458 void cvDistanceTool::showWidget(bool state) {
459  if (m_widget) {
460  if (state) {
461  m_widget->On();
462  } else {
463  m_widget->Off();
464  }
465  }
466  update();
467 }
468 
470  // Export distance measurement as cc2DLabel with 2 points
471  // Returns a new cc2DLabel that can be added to the DB tree
472 
473  if (!m_entity) {
475  "[cvDistanceTool::getOutput] No entity associated with this "
476  "measurement");
477  return nullptr;
478  }
479 
480  // Get the point coordinates
481  double p1[3], p2[3];
482  getPoint1(p1);
483  getPoint2(p2);
484 
485  // Try to get the associated point cloud
486  ccGenericPointCloud* cloud = nullptr;
488  cloud = static_cast<ccGenericPointCloud*>(m_entity);
489  } else if (m_entity->isKindOf(CV_TYPES::MESH)) {
490  ccGenericMesh* mesh = static_cast<ccGenericMesh*>(m_entity);
491  if (mesh) {
492  cloud = mesh->getAssociatedCloud();
493  }
494  }
495 
496  if (!cloud || cloud->size() == 0) {
498  "[cvDistanceTool::getOutput] Could not find associated point "
499  "cloud or cloud is empty");
500  return nullptr;
501  }
502 
503  // Convert distance tool's exact 3D coordinates to CCVector3
504  CCVector3 point1(static_cast<PointCoordinateType>(p1[0]),
505  static_cast<PointCoordinateType>(p1[1]),
506  static_cast<PointCoordinateType>(p1[2]));
507  CCVector3 point2(static_cast<PointCoordinateType>(p2[0]),
508  static_cast<PointCoordinateType>(p2[1]),
509  static_cast<PointCoordinateType>(p2[2]));
510 
511  // Find the nearest points in the cloud for both measurement endpoints
512  // CRITICAL: We need to use the exact distance tool coordinates, not just
513  // the nearest points in the cloud. If the nearest point is too far away, we
514  // should add the exact point to the cloud to ensure the exported label
515  // matches the distance tool exactly.
516  unsigned nearestIndex1 = 0;
517  unsigned nearestIndex2 = 0;
518  double minDist1 = std::numeric_limits<double>::max();
519  double minDist2 = std::numeric_limits<double>::max();
520 
521  // Threshold for considering a point "close enough" (1mm in world units)
522  // If the nearest point is farther than this, we'll add the exact point
523  const double DISTANCE_THRESHOLD = 0.001;
524 
525  for (unsigned i = 0; i < cloud->size(); ++i) {
526  const CCVector3* P = cloud->getPoint(i);
527  if (!P) continue;
528 
529  double d1 = (*P - point1).norm();
530  if (d1 < minDist1) {
531  minDist1 = d1;
532  nearestIndex1 = i;
533  }
534 
535  double d2 = (*P - point2).norm();
536  if (d2 < minDist2) {
537  minDist2 = d2;
538  nearestIndex2 = i;
539  }
540  }
541 
542  // CRITICAL: If the nearest points are too far from the exact distance tool
543  // coordinates, add the exact points to the cloud to ensure perfect
544  // alignment This ensures the exported label's line matches the distance
545  // tool's line exactly
546  ccPointCloud* pointCloud = ccHObjectCaster::ToPointCloud(cloud);
547  if (pointCloud) {
548  // Calculate how many new points we need to add
549  unsigned pointsToAdd = 0;
550  if (minDist1 > DISTANCE_THRESHOLD) pointsToAdd++;
551  if (minDist2 > DISTANCE_THRESHOLD) pointsToAdd++;
552 
553  // Reserve memory for all new points at once (more efficient)
554  if (pointsToAdd > 0) {
555  unsigned currentSize = pointCloud->size();
556  if (pointCloud->reserve(currentSize + pointsToAdd)) {
557  // Check and add point1 if needed
558  if (minDist1 > DISTANCE_THRESHOLD) {
559  pointCloud->addPoint(point1);
560  nearestIndex1 = pointCloud->size() - 1;
561  }
562 
563  // Check and add point2 if needed
564  if (minDist2 > DISTANCE_THRESHOLD) {
565  pointCloud->addPoint(point2);
566  nearestIndex2 = pointCloud->size() - 1;
567  }
568  }
569  }
570  }
571 
572  // Create a new 2D label with the two points
573  cc2DLabel* label = new cc2DLabel(
574  QString("Distance: %1").arg(getMeasurementValue(), 0, 'f', 6));
575 
576  // Add the two picked points to the label
577  if (!label->addPickedPoint(cloud, nearestIndex1)) {
579  "[cvDistanceTool::getOutput] Failed to add first point to "
580  "label");
581  delete label;
582  return nullptr;
583  }
584 
585  if (!label->addPickedPoint(cloud, nearestIndex2)) {
587  "[cvDistanceTool::getOutput] Failed to add second point to "
588  "label");
589  delete label;
590  return nullptr;
591  }
592 
593  // Configure the label display settings
594  label->setVisible(true);
595  label->setEnabled(true);
596  label->setDisplayedIn2D(true);
597  label->displayPointLegend(true);
598  label->setCollapsed(false);
599 
600  // Set a position for the label (relative to screen)
601  label->setPosition(label->getPosition()[0], label->getPosition()[1]);
602 
603  return label;
604 }
605 
607  if (m_configUi) {
608  return m_configUi->distanceSpinBox->value();
609  }
610  return 0.0;
611 }
612 
613 void cvDistanceTool::getPoint1(double pos[3]) const {
614  if (m_configUi && pos) {
615  pos[0] = m_configUi->point1XSpinBox->value();
616  pos[1] = m_configUi->point1YSpinBox->value();
617  pos[2] = m_configUi->point1ZSpinBox->value();
618  } else if (pos) {
619  pos[0] = pos[1] = pos[2] = 0.0;
620  }
621 }
622 
623 void cvDistanceTool::getPoint2(double pos[3]) const {
624  if (m_configUi && pos) {
625  pos[0] = m_configUi->point2XSpinBox->value();
626  pos[1] = m_configUi->point2YSpinBox->value();
627  pos[2] = m_configUi->point2ZSpinBox->value();
628  } else if (pos) {
629  pos[0] = pos[1] = pos[2] = 0.0;
630  }
631 }
632 
633 void cvDistanceTool::setPoint1(double pos[3]) {
634  if (!m_configUi) {
635  CVLog::Warning("[cvDistanceTool] setPoint1: m_configUi or pos is null");
636  return;
637  }
638 
639  // Uncheck the pick button
640  if (m_configUi->pickPoint1ToolButton->isChecked()) {
641  m_configUi->pickPoint1ToolButton->setChecked(false);
642  }
643 
644  // Update spinboxes without triggering signals
645  VtkUtils::SignalBlocker blocker1(m_configUi->point1XSpinBox);
646  VtkUtils::SignalBlocker blocker2(m_configUi->point1YSpinBox);
647  VtkUtils::SignalBlocker blocker3(m_configUi->point1ZSpinBox);
648  m_configUi->point1XSpinBox->setValue(pos[0]);
649  m_configUi->point1YSpinBox->setValue(pos[1]);
650  m_configUi->point1ZSpinBox->setValue(pos[2]);
651 
652  // Update widget directly
653  if (m_widget && m_widget->GetEnabled()) {
654  m_rep->SetPoint1WorldPosition(pos);
655  m_rep->BuildRepresentation();
656  m_widget->Modified();
657  m_widget->Render();
658  }
659 
660  // Update distance display
661  updateDistanceDisplay();
662  update();
663 }
664 
665 void cvDistanceTool::setPoint2(double pos[3]) {
666  if (!m_configUi) {
667  CVLog::Warning("[cvDistanceTool] setPoint2: m_configUi or pos is null");
668  return;
669  }
670 
671  // Uncheck the pick button
672  if (m_configUi->pickPoint2ToolButton->isChecked()) {
673  m_configUi->pickPoint2ToolButton->setChecked(false);
674  }
675 
676  // Update spinboxes without triggering signals
677  VtkUtils::SignalBlocker blocker1(m_configUi->point2XSpinBox);
678  VtkUtils::SignalBlocker blocker2(m_configUi->point2YSpinBox);
679  VtkUtils::SignalBlocker blocker3(m_configUi->point2ZSpinBox);
680  m_configUi->point2XSpinBox->setValue(pos[0]);
681  m_configUi->point2YSpinBox->setValue(pos[1]);
682  m_configUi->point2ZSpinBox->setValue(pos[2]);
683 
684  // Update widget directly
685  if (m_widget && m_widget->GetEnabled()) {
686  m_rep->SetPoint2WorldPosition(pos);
687  m_rep->BuildRepresentation();
688  m_widget->Modified();
689  m_widget->Render();
690  }
691 
692  // Update distance display
693  updateDistanceDisplay();
694  update();
695 }
696 
697 void cvDistanceTool::setColor(double r, double g, double b) {
698  // Store current color
699  m_currentColor[0] = r;
700  m_currentColor[1] = g;
701  m_currentColor[2] = b;
702 
703  // Set color for line representation
704  if (m_rep) {
705  if (auto* lineProp = m_rep->GetLineProperty()) {
706  lineProp->SetColor(r, g, b);
707  }
708  if (auto* selectedLineProp = m_rep->GetSelectedLineProperty()) {
709  selectedLineProp->SetColor(r, g, b);
710  }
711  m_rep->BuildRepresentation();
712  if (m_widget) {
713  m_widget->Modified();
714  }
715  }
716  update();
717 }
718 
719 bool cvDistanceTool::getColor(double& r, double& g, double& b) const {
720  r = m_currentColor[0];
721  g = m_currentColor[1];
722  b = m_currentColor[2];
723  return true;
724 }
725 
727  CVLog::PrintDebug(QString("[cvDistanceTool::lockInteraction] Tool=%1, "
728  "m_pickingHelpers.size()=%2")
729  .arg((quintptr)this, 0, 16)
730  .arg(m_pickingHelpers.size()));
731 
732  // Disable VTK widget interaction (handles cannot be moved)
733  if (m_widget) {
734  m_widget->SetProcessEvents(0); // Disable event processing
735  }
736 
737  // Change widget color to indicate locked state (very dimmed, 10%
738  // brightness)
739  if (m_rep) {
740  // Use a very dimmed color to indicate locked state (10% brightness, 50%
741  // opacity)
742  if (auto* lineProp = m_rep->GetLineProperty()) {
743  lineProp->SetColor(m_currentColor[0] * 0.1, m_currentColor[1] * 0.1,
744  m_currentColor[2] * 0.1);
745  lineProp->SetOpacity(0.5); // Make it semi-transparent
746  }
747  if (auto* selectedLineProp = m_rep->GetSelectedLineProperty()) {
748  selectedLineProp->SetColor(m_currentColor[0] * 0.1,
749  m_currentColor[1] * 0.1,
750  m_currentColor[2] * 0.1);
751  selectedLineProp->SetOpacity(0.5);
752  }
753 
754  // Dim handles (points) by accessing them through Point1Representation
755  // and Point2Representation
756  if (auto* point1Rep = dynamic_cast<vtkPointHandleRepresentation3D*>(
757  m_rep->GetPoint1Representation())) {
758  if (auto* prop = point1Rep->GetProperty()) {
759  prop->SetOpacity(0.5);
760  }
761  if (auto* selectedProp = point1Rep->GetSelectedProperty()) {
762  selectedProp->SetOpacity(0.5);
763  }
764  }
765  if (auto* point2Rep = dynamic_cast<vtkPointHandleRepresentation3D*>(
766  m_rep->GetPoint2Representation())) {
767  if (auto* prop = point2Rep->GetProperty()) {
768  prop->SetOpacity(0.5);
769  }
770  if (auto* selectedProp = point2Rep->GetSelectedProperty()) {
771  selectedProp->SetOpacity(0.5);
772  }
773  }
774 
775  m_rep->BuildRepresentation();
776 
777  // Set distance text (title) and axis properties AFTER
778  // BuildRepresentation
779  if (auto* axis = m_rep->GetAxisActor()) {
780  // Dim the axis line
781  if (auto* axisProp = axis->GetProperty()) {
782  axisProp->SetOpacity(0.5);
783  }
784 
785  // Dim the distance text (title)
786  if (auto* titleProp = axis->GetTitleTextProperty()) {
787  titleProp->SetOpacity(0.5);
788  titleProp->SetColor(0.5, 0.5,
789  0.5); // Dark gray for locked state
790  }
791 
792  // Dim axis labels (if visible)
793  if (auto* labelProp = axis->GetLabelTextProperty()) {
794  labelProp->SetOpacity(0.5);
795  }
796  }
797 
798  if (m_widget) {
799  m_widget->Modified();
800  m_widget->Render();
801  }
802  }
803 
804  // Force render window update
805  if (m_interactor && m_interactor->GetRenderWindow()) {
806  m_interactor->GetRenderWindow()->Render();
807  }
808 
809  // Disable UI controls
810  if (m_configUi) {
811  m_configUi->point1XSpinBox->setEnabled(false);
812  m_configUi->point1YSpinBox->setEnabled(false);
813  m_configUi->point1ZSpinBox->setEnabled(false);
814  m_configUi->point2XSpinBox->setEnabled(false);
815  m_configUi->point2YSpinBox->setEnabled(false);
816  m_configUi->point2ZSpinBox->setEnabled(false);
817  m_configUi->pickPoint1ToolButton->setEnabled(false);
818  m_configUi->pickPoint2ToolButton->setEnabled(false);
819  m_configUi->rulerModeCheckBox->setEnabled(false);
820  m_configUi->rulerDistanceSpinBox->setEnabled(false);
821  m_configUi->numberOfTicksSpinBox->setEnabled(false);
822  m_configUi->scaleSpinBox->setEnabled(false);
823  m_configUi->labelFormatLineEdit->setEnabled(false);
824  m_configUi->widgetVisibilityCheckBox->setEnabled(false);
825  m_configUi->labelVisibilityCheckBox->setEnabled(false);
826  m_configUi->lineWidthSpinBox->setEnabled(false);
827  }
828 
829  // Disable keyboard shortcuts
831 }
832 
834  // Enable VTK widget interaction
835  if (m_widget) {
836  m_widget->SetProcessEvents(1); // Enable event processing
837  }
838 
839  // Restore widget color to indicate active/unlocked state
840  if (m_rep) {
841  // Restore original color (full brightness and opacity)
842  if (auto* lineProp = m_rep->GetLineProperty()) {
843  lineProp->SetColor(m_currentColor[0], m_currentColor[1],
844  m_currentColor[2]);
845  lineProp->SetOpacity(1.0); // Fully opaque
846  }
847  if (auto* selectedLineProp = m_rep->GetSelectedLineProperty()) {
848  selectedLineProp->SetColor(m_currentColor[0], m_currentColor[1],
849  m_currentColor[2]);
850  selectedLineProp->SetOpacity(1.0);
851  }
852 
853  // Restore handles (points) by accessing them through
854  // Point1Representation and Point2Representation
855  if (auto* point1Rep = dynamic_cast<vtkPointHandleRepresentation3D*>(
856  m_rep->GetPoint1Representation())) {
857  if (auto* prop = point1Rep->GetProperty()) {
858  prop->SetOpacity(1.0);
859  }
860  if (auto* selectedProp = point1Rep->GetSelectedProperty()) {
861  selectedProp->SetOpacity(1.0);
862  }
863  }
864  if (auto* point2Rep = dynamic_cast<vtkPointHandleRepresentation3D*>(
865  m_rep->GetPoint2Representation())) {
866  if (auto* prop = point2Rep->GetProperty()) {
867  prop->SetOpacity(1.0);
868  }
869  if (auto* selectedProp = point2Rep->GetSelectedProperty()) {
870  selectedProp->SetOpacity(1.0);
871  }
872  }
873 
874  m_rep->BuildRepresentation();
875 
876  // Restore distance text (title) and axis properties AFTER
877  // BuildRepresentation
878  if (auto* axis = m_rep->GetAxisActor()) {
879  // Restore axis line
880  if (auto* axisProp = axis->GetProperty()) {
881  axisProp->SetOpacity(1.0);
882  }
883 
884  // Restore distance text (title) to user-configured settings
885  if (auto* titleProp = axis->GetTitleTextProperty()) {
886  titleProp->SetOpacity(m_fontOpacity);
887  titleProp->SetColor(m_fontColor[0], m_fontColor[1],
888  m_fontColor[2]);
889  }
890 
891  // Restore axis labels (if visible) to user-configured settings
892  if (auto* labelProp = axis->GetLabelTextProperty()) {
893  labelProp->SetOpacity(m_fontOpacity);
894  labelProp->SetColor(m_fontColor[0], m_fontColor[1],
895  m_fontColor[2]);
896  }
897  }
898 
899  if (m_widget) {
900  m_widget->Modified();
901  m_widget->Render();
902  }
903  }
904 
905  // Force render window update
906  if (m_interactor && m_interactor->GetRenderWindow()) {
907  m_interactor->GetRenderWindow()->Render();
908  }
909 
910  // Enable UI controls
911  if (m_configUi) {
912  m_configUi->point1XSpinBox->setEnabled(true);
913  m_configUi->point1YSpinBox->setEnabled(true);
914  m_configUi->point1ZSpinBox->setEnabled(true);
915  m_configUi->point2XSpinBox->setEnabled(true);
916  m_configUi->point2YSpinBox->setEnabled(true);
917  m_configUi->point2ZSpinBox->setEnabled(true);
918  m_configUi->pickPoint1ToolButton->setEnabled(true);
919  m_configUi->pickPoint2ToolButton->setEnabled(true);
920  m_configUi->rulerModeCheckBox->setEnabled(true);
921 
922  // Ruler distance is only enabled when ruler mode is on
923  bool rulerMode = m_configUi->rulerModeCheckBox->isChecked();
924  m_configUi->rulerDistanceSpinBox->setEnabled(rulerMode);
925  m_configUi->numberOfTicksSpinBox->setEnabled(!rulerMode);
926 
927  m_configUi->scaleSpinBox->setEnabled(true);
928  m_configUi->labelFormatLineEdit->setEnabled(true);
929  m_configUi->widgetVisibilityCheckBox->setEnabled(true);
930  m_configUi->labelVisibilityCheckBox->setEnabled(true);
931  m_configUi->lineWidthSpinBox->setEnabled(true);
932  }
933 
934  if (m_pickingHelpers.isEmpty()) {
935  // Shortcuts haven't been created yet - create them now
936  if (m_vtkWidget) {
938  QString("[cvDistanceTool::unlockInteraction] Creating "
939  "shortcuts for tool=%1, using saved vtkWidget=%2")
940  .arg((quintptr)this, 0, 16)
941  .arg((quintptr)m_vtkWidget, 0, 16));
944  QString("[cvDistanceTool::unlockInteraction] After "
945  "setupShortcuts, m_pickingHelpers.size()=%1")
946  .arg(m_pickingHelpers.size()));
947  } else {
949  QString("[cvDistanceTool::unlockInteraction] m_vtkWidget "
950  "is null for tool=%1, cannot create shortcuts")
951  .arg((quintptr)this, 0, 16));
952  }
953  } else {
954  // Shortcuts already exist - just enable them
955  CVLog::PrintDebug(QString("[cvDistanceTool::unlockInteraction] "
956  "Enabling %1 existing shortcuts for tool=%2")
957  .arg(m_pickingHelpers.size())
958  .arg((quintptr)this, 0, 16));
959  for (cvPointPickingHelper* helper : m_pickingHelpers) {
960  if (helper) {
961  helper->setEnabled(true,
962  false); // Enable without setting focus
963  }
964  }
965  }
966 }
967 
968 void cvDistanceTool::setInstanceLabel(const QString& label) {
969  // Store the instance label
970  m_instanceLabel = label;
971 
972  // Update the VTK representation's label suffix
973  if (m_rep) {
974  m_rep->SetLabelSuffix(m_instanceLabel.toUtf8().constData());
975  m_rep->BuildRepresentation();
976  if (m_widget) {
977  m_widget->Modified();
978  }
979  update();
980  }
981 }
982 
983 void cvDistanceTool::on_point1XSpinBox_valueChanged(double arg1) {
984  if (!m_configUi) return;
985  double pos[3];
986  double newPos[3];
987  newPos[0] = arg1;
988 
989  VtkUtils::SignalBlocker blocker(m_configUi->point1XSpinBox);
990 
991  if (m_widget && m_widget->GetEnabled()) {
992  m_rep->GetPoint1WorldPosition(pos);
993  newPos[1] = pos[1];
994  newPos[2] = pos[2];
995  m_rep->SetPoint1WorldPosition(newPos);
996  m_rep->BuildRepresentation();
997  m_widget->Modified();
998  updateDistanceDisplay();
999  }
1000  update();
1001 }
1002 
1003 void cvDistanceTool::on_point1YSpinBox_valueChanged(double arg1) {
1004  if (!m_configUi) return;
1005  double pos[3];
1006  double newPos[3];
1007  newPos[1] = arg1;
1008 
1009  VtkUtils::SignalBlocker blocker(m_configUi->point1YSpinBox);
1010 
1011  if (m_widget && m_widget->GetEnabled()) {
1012  m_rep->GetPoint1WorldPosition(pos);
1013  newPos[0] = pos[0];
1014  newPos[2] = pos[2];
1015  m_rep->SetPoint1WorldPosition(newPos);
1016  m_rep->BuildRepresentation();
1017  m_widget->Modified();
1018  updateDistanceDisplay();
1019  }
1020  update();
1021 }
1022 
1023 void cvDistanceTool::on_point1ZSpinBox_valueChanged(double arg1) {
1024  if (!m_configUi) return;
1025  double pos[3];
1026  double newPos[3];
1027  newPos[2] = arg1;
1028 
1029  VtkUtils::SignalBlocker blocker(m_configUi->point1ZSpinBox);
1030 
1031  if (m_widget && m_widget->GetEnabled()) {
1032  m_rep->GetPoint1WorldPosition(pos);
1033  newPos[0] = pos[0];
1034  newPos[1] = pos[1];
1035  m_rep->SetPoint1WorldPosition(newPos);
1036  m_rep->BuildRepresentation();
1037  m_widget->Modified();
1038  updateDistanceDisplay();
1039  }
1040  update();
1041 }
1042 
1043 void cvDistanceTool::on_point2XSpinBox_valueChanged(double arg1) {
1044  if (!m_configUi) return;
1045  double pos[3];
1046  double newPos[3];
1047  newPos[0] = arg1;
1048 
1049  VtkUtils::SignalBlocker blocker(m_configUi->point2XSpinBox);
1050 
1051  if (m_widget && m_widget->GetEnabled()) {
1052  m_rep->GetPoint2WorldPosition(pos);
1053  newPos[1] = pos[1];
1054  newPos[2] = pos[2];
1055  m_rep->SetPoint2WorldPosition(newPos);
1056  m_rep->BuildRepresentation();
1057  m_widget->Modified();
1058  updateDistanceDisplay();
1059  }
1060  update();
1061 }
1062 
1063 void cvDistanceTool::on_point2YSpinBox_valueChanged(double arg1) {
1064  if (!m_configUi) return;
1065  double pos[3];
1066  double newPos[3];
1067  newPos[1] = arg1;
1068 
1069  VtkUtils::SignalBlocker blocker(m_configUi->point2YSpinBox);
1070 
1071  if (m_widget && m_widget->GetEnabled()) {
1072  m_rep->GetPoint2WorldPosition(pos);
1073  newPos[0] = pos[0];
1074  newPos[2] = pos[2];
1075  m_rep->SetPoint2WorldPosition(newPos);
1076  m_rep->BuildRepresentation();
1077  m_widget->Modified();
1078  updateDistanceDisplay();
1079  }
1080  update();
1081 }
1082 
1083 void cvDistanceTool::on_point2ZSpinBox_valueChanged(double arg1) {
1084  if (!m_configUi) return;
1085  double pos[3];
1086  double newPos[3];
1087  newPos[2] = arg1;
1088 
1089  VtkUtils::SignalBlocker blocker(m_configUi->point2ZSpinBox);
1090 
1091  if (m_widget && m_widget->GetEnabled()) {
1092  m_rep->GetPoint2WorldPosition(pos);
1093  newPos[0] = pos[0];
1094  newPos[1] = pos[1];
1095  m_rep->SetPoint2WorldPosition(newPos);
1096  m_rep->BuildRepresentation();
1097  m_widget->Modified();
1098  updateDistanceDisplay();
1099  }
1100  update();
1101 }
1102 
1103 void cvDistanceTool::onDistanceChanged(double dist) {
1104  if (!m_configUi) return;
1105 
1106  // Apply scale factor (following ParaView - scale is applied to display
1107  // value)
1108  double scale = m_configUi->scaleSpinBox->value();
1109  double scaledDistance = dist * scale;
1110 
1111  VtkUtils::SignalBlocker blocker(m_configUi->distanceSpinBox);
1112  m_configUi->distanceSpinBox->setValue(scaledDistance);
1113  emit measurementValueChanged();
1114 }
1115 
1116 void cvDistanceTool::onWorldPoint1Changed(double* pos) {
1117  if (!m_configUi || !pos) return;
1118  VtkUtils::SignalBlocker blocker1(m_configUi->point1XSpinBox);
1119  VtkUtils::SignalBlocker blocker2(m_configUi->point1YSpinBox);
1120  VtkUtils::SignalBlocker blocker3(m_configUi->point1ZSpinBox);
1121  m_configUi->point1XSpinBox->setValue(pos[0]);
1122  m_configUi->point1YSpinBox->setValue(pos[1]);
1123  m_configUi->point1ZSpinBox->setValue(pos[2]);
1124  emit measurementValueChanged();
1125 }
1126 
1127 void cvDistanceTool::onWorldPoint2Changed(double* pos) {
1128  if (!m_configUi || !pos) return;
1129  VtkUtils::SignalBlocker blocker1(m_configUi->point2XSpinBox);
1130  VtkUtils::SignalBlocker blocker2(m_configUi->point2YSpinBox);
1131  VtkUtils::SignalBlocker blocker3(m_configUi->point2ZSpinBox);
1132  m_configUi->point2XSpinBox->setValue(pos[0]);
1133  m_configUi->point2YSpinBox->setValue(pos[1]);
1134  m_configUi->point2ZSpinBox->setValue(pos[2]);
1135  emit measurementValueChanged();
1136 }
1137 
1138 void cvDistanceTool::hookWidget(
1142  observer->attach(widget.Get());
1143  connect(observer, &VtkUtils::DistanceWidgetObserver::distanceChanged, this,
1144  &cvDistanceTool::onDistanceChanged);
1146  this, &cvDistanceTool::onWorldPoint1Changed);
1148  this, &cvDistanceTool::onWorldPoint2Changed);
1149 }
1150 
1151 void cvDistanceTool::updateDistanceDisplay() {
1152  if (!m_configUi) return;
1153 
1154  double distance = 0.0;
1155  double scale = m_configUi->scaleSpinBox->value();
1156 
1157  if (m_rep) {
1158  // Get unscaled distance and apply scale (following ParaView)
1159  distance = m_rep->GetDistance() * scale;
1160  }
1161 
1162  VtkUtils::SignalBlocker blocker(m_configUi->distanceSpinBox);
1163  m_configUi->distanceSpinBox->setValue(distance);
1164  emit measurementValueChanged();
1165 }
1166 
1167 void cvDistanceTool::on_pickPoint1_toggled(bool checked) {
1168  if (checked) {
1169  // Uncheck other pick button
1170  if (m_configUi->pickPoint2ToolButton->isChecked()) {
1171  m_configUi->pickPoint2ToolButton->setChecked(false);
1172  }
1173  // Enable point picking mode for point 1
1174  emit pointPickingRequested(1);
1175  } else {
1176  // Disable point picking
1177  emit pointPickingCancelled();
1178  }
1179 }
1180 
1181 void cvDistanceTool::on_pickPoint2_toggled(bool checked) {
1182  if (checked) {
1183  // Uncheck other pick button
1184  if (m_configUi->pickPoint1ToolButton->isChecked()) {
1185  m_configUi->pickPoint1ToolButton->setChecked(false);
1186  }
1187  // Enable point picking mode for point 2
1188  emit pointPickingRequested(2);
1189  } else {
1190  // Disable point picking
1191  emit pointPickingCancelled();
1192  }
1193 }
1194 
1195 void cvDistanceTool::on_rulerModeCheckBox_toggled(bool checked) {
1196  if (!m_configUi) return;
1197 
1198  // Use cvConstrainedLineRepresentation's ruler mode functionality
1199  if (m_rep) {
1200  m_rep->SetRulerMode(checked ? 1 : 0);
1201  m_rep->BuildRepresentation();
1202  }
1203 
1204  // Enable/disable appropriate controls
1205  m_configUi->rulerDistanceSpinBox->setEnabled(checked);
1206  m_configUi->numberOfTicksSpinBox->setEnabled(!checked);
1207 
1208  update();
1209 }
1210 
1211 void cvDistanceTool::on_rulerDistanceSpinBox_valueChanged(double value) {
1212  if (!m_configUi) return;
1213 
1214  // Use cvConstrainedLineRepresentation's ruler distance functionality
1215  if (m_rep) {
1216  m_rep->SetRulerDistance(value);
1217  m_rep->BuildRepresentation();
1218  }
1219  update();
1220 }
1221 
1222 void cvDistanceTool::on_numberOfTicksSpinBox_valueChanged(int value) {
1223  if (!m_configUi) return;
1224 
1225  // Use cvConstrainedLineRepresentation's ruler ticks functionality
1226  if (m_rep) {
1227  m_rep->SetNumberOfRulerTicks(value);
1228  m_rep->BuildRepresentation();
1229  }
1230  update();
1231 }
1232 
1233 void cvDistanceTool::on_scaleSpinBox_valueChanged(double value) {
1234  if (!m_configUi) return;
1235 
1236  // Use cvConstrainedLineRepresentation's scale functionality
1237  if (m_rep) {
1238  m_rep->SetScale(value);
1239  m_rep->BuildRepresentation();
1240  }
1241 
1242  // Update distance display to reflect the scaled value
1243  updateDistanceDisplay();
1244  update();
1245 }
1246 
1247 void cvDistanceTool::on_labelFormatLineEdit_textChanged(const QString& text) {
1248  if (!m_configUi) return;
1249 
1250  // Use cvConstrainedLineRepresentation's label format functionality
1251  std::string formatStr = text.toStdString();
1252 
1253  // Basic validation: check if format specifier is present
1254  if (formatStr.find('%') == std::string::npos) {
1256  "[cvDistanceTool] Invalid label format: missing '%' specifier");
1257  return;
1258  }
1259 
1260  if (m_rep) {
1261  m_rep->SetLabelFormat(formatStr.c_str());
1262  m_rep->BuildRepresentation();
1263  }
1264 
1265  update();
1266 }
1267 
1268 void cvDistanceTool::on_widgetVisibilityCheckBox_toggled(bool checked) {
1269  if (!m_configUi) return;
1270 
1271  // Show or hide the widget
1272  if (m_widget) {
1273  if (checked) {
1274  m_widget->On();
1275  } else {
1276  m_widget->Off();
1277  }
1278  }
1279  update();
1280 }
1281 
1282 void cvDistanceTool::on_labelVisibilityCheckBox_toggled(bool checked) {
1283  if (!m_configUi) return;
1284 
1285  // Use cvConstrainedLineRepresentation's ShowLabel functionality
1286  if (m_rep) {
1287  m_rep->SetShowLabel(checked ? 1 : 0);
1288  m_rep->BuildRepresentation();
1289  }
1290  update();
1291 }
1292 
1293 void cvDistanceTool::on_lineWidthSpinBox_valueChanged(double value) {
1294  if (!m_configUi) return;
1295 
1296  // vtkLineRepresentation 使用 GetLineProperty (与 ParaView vtkLineWidget2
1297  // 一致)
1298  if (m_rep) {
1299  if (auto* prop = m_rep->GetLineProperty()) {
1300  prop->SetLineWidth(value);
1301  }
1302  m_rep->BuildRepresentation();
1303  }
1304 
1305  update();
1306 }
1307 
1309  if (!vtkWidget) return;
1310 
1311  // 'P' - Pick alternating points on surface cell
1312  cvPointPickingHelper* pickHelper =
1313  new cvPointPickingHelper(QKeySequence(tr("P")), false, vtkWidget);
1314  pickHelper->setInteractor(m_interactor);
1315  pickHelper->setRenderer(m_renderer);
1316  pickHelper->setContextWidget(this);
1317  connect(pickHelper, &cvPointPickingHelper::pick, this,
1318  &cvDistanceTool::pickAlternatingPoint);
1319  m_pickingHelpers.append(pickHelper);
1320 
1321  // 'Ctrl+P' - Pick alternating points, snap to mesh points
1322  cvPointPickingHelper* pickHelper2 = new cvPointPickingHelper(
1323  QKeySequence(tr("Ctrl+P")), true, vtkWidget);
1324  pickHelper2->setInteractor(m_interactor);
1325  pickHelper2->setRenderer(m_renderer);
1326  pickHelper2->setContextWidget(this);
1327  connect(pickHelper2, &cvPointPickingHelper::pick, this,
1328  &cvDistanceTool::pickAlternatingPoint);
1329  m_pickingHelpers.append(pickHelper2);
1330 
1331  // '1' - Pick point 1 on surface cell
1332  cvPointPickingHelper* pickHelper3 =
1333  new cvPointPickingHelper(QKeySequence(tr("1")), false, vtkWidget);
1334  pickHelper3->setInteractor(m_interactor);
1335  pickHelper3->setRenderer(m_renderer);
1336  pickHelper3->setContextWidget(this);
1337  connect(pickHelper3, &cvPointPickingHelper::pick, this,
1338  &cvDistanceTool::pickKeyboardPoint1);
1339  m_pickingHelpers.append(pickHelper3);
1340 
1341  // 'Ctrl+1' - Pick point 1, snap to mesh points
1342  cvPointPickingHelper* pickHelper4 = new cvPointPickingHelper(
1343  QKeySequence(tr("Ctrl+1")), true, vtkWidget);
1344  pickHelper4->setInteractor(m_interactor);
1345  pickHelper4->setRenderer(m_renderer);
1346  pickHelper4->setContextWidget(this);
1347  connect(pickHelper4, &cvPointPickingHelper::pick, this,
1348  &cvDistanceTool::pickKeyboardPoint1);
1349  m_pickingHelpers.append(pickHelper4);
1350 
1351  // '2' - Pick point 2 on surface cell
1352  cvPointPickingHelper* pickHelper5 =
1353  new cvPointPickingHelper(QKeySequence(tr("2")), false, vtkWidget);
1354  pickHelper5->setInteractor(m_interactor);
1355  pickHelper5->setRenderer(m_renderer);
1356  pickHelper5->setContextWidget(this);
1357  connect(pickHelper5, &cvPointPickingHelper::pick, this,
1358  &cvDistanceTool::pickKeyboardPoint2);
1359  m_pickingHelpers.append(pickHelper5);
1360 
1361  // 'Ctrl+2' - Pick point 2, snap to mesh points
1362  cvPointPickingHelper* pickHelper6 = new cvPointPickingHelper(
1363  QKeySequence(tr("Ctrl+2")), true, vtkWidget);
1364  pickHelper6->setInteractor(m_interactor);
1365  pickHelper6->setRenderer(m_renderer);
1366  pickHelper6->setContextWidget(this);
1367  connect(pickHelper6, &cvPointPickingHelper::pick, this,
1368  &cvDistanceTool::pickKeyboardPoint2);
1369  m_pickingHelpers.append(pickHelper6);
1370 
1371  // 'N' - Pick point and set normal direction
1372  cvPointPickingHelper* pickHelperNormal = new cvPointPickingHelper(
1373  QKeySequence(tr("N")), false, vtkWidget,
1375  pickHelperNormal->setInteractor(m_interactor);
1376  pickHelperNormal->setRenderer(m_renderer);
1377  pickHelperNormal->setContextWidget(this);
1378  connect(pickHelperNormal, &cvPointPickingHelper::pickNormal, this,
1379  &cvDistanceTool::pickNormalDirection);
1380  m_pickingHelpers.append(pickHelperNormal);
1381 }
1382 
1383 void cvDistanceTool::pickAlternatingPoint(double x, double y, double z) {
1384  if (m_pickPoint1Next) {
1385  pickKeyboardPoint1(x, y, z);
1386  } else {
1387  pickKeyboardPoint2(x, y, z);
1388  }
1389  m_pickPoint1Next = !m_pickPoint1Next;
1390 }
1391 
1392 void cvDistanceTool::pickKeyboardPoint1(double x, double y, double z) {
1393  double pos[3] = {x, y, z};
1394  setPoint1(pos);
1395 }
1396 
1397 void cvDistanceTool::pickKeyboardPoint2(double x, double y, double z) {
1398  double pos[3] = {x, y, z};
1399  setPoint2(pos);
1400 }
1401 
1402 void cvDistanceTool::pickNormalDirection(
1403  double px, double py, double pz, double nx, double ny, double nz) {
1404  // Set point 1 at the picked position
1405  double p1[3] = {px, py, pz};
1406  setPoint1(p1);
1407 
1408  // Set point 2 along the normal direction
1409  double p2[3] = {px + nx, py + ny, pz + nz};
1410  setPoint2(p2);
1411 }
1412 
1413 void cvDistanceTool::applyFontProperties() {
1414  if (!m_rep) return;
1415 
1416  // Apply font properties to the axis actor's text properties
1417  if (auto* axis = m_rep->GetAxisActor()) {
1418  // CRITICAL: vtkAxisActor2D uses automatic font scaling based on
1419  // FontFactor We need to disable automatic adjustment and set properties
1420  // correctly
1421 
1422  // Disable automatic label adjustment to prevent font size override
1423  axis->AdjustLabelsOff();
1424 
1425  // Calculate font factor based on desired font size
1426  // vtkAxisActor2D internally scales fonts, so we adjust the factor
1427  // accordingly Base reference size is typically around 12-14 in VTK
1428  double baseFontSize = 12.0;
1429  double fontFactor = static_cast<double>(m_fontSize) / baseFontSize;
1430  if (fontFactor < 0.1) fontFactor = 0.1; // Prevent too small values
1431  if (fontFactor > 10.0) fontFactor = 10.0; // Prevent too large values
1432 
1433  // Set font factors for both title and labels
1434  axis->SetFontFactor(fontFactor);
1435  axis->SetLabelFactor(fontFactor * 0.8); // Labels slightly smaller
1436 
1437  // Apply to title text property (distance display)
1438  // The Title is what shows the distance value
1439  if (auto* titleProp = axis->GetTitleTextProperty()) {
1440  titleProp->SetFontFamilyAsString(m_fontFamily.toUtf8().constData());
1441  titleProp->SetFontSize(m_fontSize);
1442  titleProp->SetColor(m_fontColor[0], m_fontColor[1], m_fontColor[2]);
1443  titleProp->SetBold(m_fontBold ? 1 : 0);
1444  titleProp->SetItalic(m_fontItalic ? 1 : 0);
1445  titleProp->SetShadow(m_fontShadow ? 1 : 0);
1446  titleProp->SetOpacity(m_fontOpacity);
1447 
1448  // Apply justification
1449  if (m_horizontalJustification == "Left") {
1450  titleProp->SetJustificationToLeft();
1451  } else if (m_horizontalJustification == "Center") {
1452  titleProp->SetJustificationToCentered();
1453  } else if (m_horizontalJustification == "Right") {
1454  titleProp->SetJustificationToRight();
1455  }
1456 
1457  if (m_verticalJustification == "Top") {
1458  titleProp->SetVerticalJustificationToTop();
1459  } else if (m_verticalJustification == "Center") {
1460  titleProp->SetVerticalJustificationToCentered();
1461  } else if (m_verticalJustification == "Bottom") {
1462  titleProp->SetVerticalJustificationToBottom();
1463  }
1464 
1465  titleProp->Modified(); // Mark as modified to ensure VTK updates
1466  }
1467 
1468  // Apply to label text property (tick labels for ruler mode)
1469  if (auto* labelProp = axis->GetLabelTextProperty()) {
1470  labelProp->SetFontFamilyAsString(m_fontFamily.toUtf8().constData());
1471  labelProp->SetFontSize(m_fontSize);
1472  labelProp->SetColor(m_fontColor[0], m_fontColor[1], m_fontColor[2]);
1473  labelProp->SetBold(m_fontBold ? 1 : 0);
1474  labelProp->SetItalic(m_fontItalic ? 1 : 0);
1475  labelProp->SetShadow(m_fontShadow ? 1 : 0);
1476  labelProp->SetOpacity(m_fontOpacity);
1477 
1478  // Apply justification
1479  if (m_horizontalJustification == "Left") {
1480  labelProp->SetJustificationToLeft();
1481  } else if (m_horizontalJustification == "Center") {
1482  labelProp->SetJustificationToCentered();
1483  } else if (m_horizontalJustification == "Right") {
1484  labelProp->SetJustificationToRight();
1485  }
1486 
1487  if (m_verticalJustification == "Top") {
1488  labelProp->SetVerticalJustificationToTop();
1489  } else if (m_verticalJustification == "Center") {
1490  labelProp->SetVerticalJustificationToCentered();
1491  } else if (m_verticalJustification == "Bottom") {
1492  labelProp->SetVerticalJustificationToBottom();
1493  }
1494 
1495  labelProp->Modified(); // Mark as modified to ensure VTK updates
1496  }
1497 
1498  // Mark axis actor as modified to trigger re-render
1499  axis->Modified();
1500  }
1501 
1502  // Rebuild representation and update display
1503  m_rep->BuildRepresentation();
1504  if (m_widget) {
1505  m_widget->Modified();
1506  m_widget->Render();
1507  }
1508 
1509  // Force render window update
1510  if (m_interactor && m_interactor->GetRenderWindow()) {
1511  m_interactor->GetRenderWindow()->Render();
1512  }
1513 
1514  update();
1515 }
float PointCoordinateType
Type of the coordinates of a (N-D) point.
Definition: CVTypes.h:16
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 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
void attach(vtkInteractorObserver *widget)
void distanceChanged(double dist)
void worldPoint1Changed(double *pos)
void worldPoint2Changed(double *pos)
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
const float * getPosition() const
Returns relative position.
Definition: ecv2DLabel.h:63
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 LineRepresentation with distance display and ruler features.
void SetScale(double scale)
Set/Get scale factor.
Handle representation with custom translation axis support.
virtual void unlockInteraction() override
Unlock tool interaction (enable 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 start() override
virtual void initTool() override
virtual void setPoint2(double pos[3]) override
Set point 2 coordinates.
virtual void setupPointPickingShortcuts(QWidget *vtkWidget) override
virtual void setColor(double r, double g, double b) override
Set measurement color (RGB values in range [0.0, 1.0])
virtual void setPoint1(double pos[3]) override
Set point 1 coordinates.
virtual void createUi() override
virtual double getMeasurementValue() const override
Get measurement value (distance or angle)
virtual void getPoint1(double pos[3]) const override
Get point 1 coordinates.
virtual void getPoint2(double pos[3]) const override
Get point 2 coordinates.
virtual bool getColor(double &r, double &g, double &b) const override
virtual void showWidget(bool state) override
virtual void reset() override
~cvDistanceTool() override
cvDistanceTool(QWidget *parent=nullptr)
virtual ccHObject * getOutput() override
virtual void lockInteraction() override
Lock tool interaction (disable VTK widget interaction and UI controls)
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)
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...
@ CoordinatesAndNormal
Pick both coordinates and normal.
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 pickNormal(double px, double py, double pz, double nx, double ny, double nz)
Emitted when a point and normal are picked.
void pick(double x, double y, double z)
Emitted when a point is picked.
normal_z y
normal_z x
normal_z z
@ MESH
Definition: CVTypes.h:105
@ POINT_CLOUD
Definition: CVTypes.h:104
static double distance(T *pot1, T *pot2)
Definition: utils.h:111
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]
void configureHandle3D(vtkPointHandleRepresentation3D *handle)
Configure 3D handle representation with ParaView-style properties.