ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
ecvMeasurementTool.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 "ecvMeasurementTool.h"
9 
10 // LOCAL
11 #include "MainWindow.h"
12 
13 // CV_DB_LIB
15 
16 // ECV_CORE_LIB
18 #include <ecvPointCloud.h>
19 
20 // CV_DB_LIB
21 #include <ecvDisplayTools.h>
22 #include <ecvPolyline.h>
23 
24 // CV_CORE_LIB
25 #include <CVLog.h>
26 
27 // CV_PLUGIN_API
28 #include <ecvPickingHub.h>
29 
30 // QT
31 #include <QApplication>
32 #include <QColorDialog>
33 #include <QMessageBox>
34 #include <QSizePolicy>
35 
36 #ifdef USE_PCL_BACKEND
37 #include <Tools/MeasurementTools/PclMeasurementTools.h>
38 #endif
39 
41  : ccOverlayDialog(parent),
42  Ui::MeasurementToolDlg(),
43  m_tool(nullptr),
44  m_updatingFromTool(false),
45  m_pickingHub(nullptr),
46  m_pickPointMode(0),
47  m_currentColor(QColor(0, 255, 0)) // Default green color
48 {
49  setupUi(this);
50 
51  // Get picking hub from MainWindow
54  }
55 
56  connect(resetButton, &QToolButton::clicked, this,
58  connect(closeButton, &QToolButton::clicked, this,
60  connect(exportButton, &QToolButton::clicked, this,
62 
63  // Instance management
64  connect(instancesComboBox,
65  QOverload<int>::of(&QComboBox::currentIndexChanged), this,
67  connect(addInstanceButton, &QToolButton::clicked, this,
69  connect(removeInstanceButton, &QToolButton::clicked, this,
71 
72  // Color selection
73  connect(colorButton, &QPushButton::clicked, this,
75 
76  // Create and add font property widget
77  // Color picker is now visible in the font widget (ParaView style)
80  true); // Show color picker in font widget
81  fontLayout->addWidget(m_fontPropertyWidget);
84 
85  addOverridenShortcut(Qt::Key_Escape);
86  connect(this, &ccOverlayDialog::shortcutTriggered, this, [this](int key) {
87  if (key == Qt::Key_Escape) {
88  closeDialog();
89  }
90  });
91 
92  // Initially disable instance management buttons
93  removeInstanceButton->setEnabled(false);
94 
95  // Set initial color button appearance
97 }
98 
100  // Remove picking listener
101  if (m_pickingHub) {
102  m_pickingHub->removeListener(this, false);
103  }
104 
105  // Clean up all tool instances
107  if (tool) {
108  delete tool;
109  }
110  }
111  m_toolInstances.clear();
112 }
113 
116 #ifdef USE_PCL_BACKEND
118  if (!viewer) {
119  CVLog::Error("[ecvMeasurementTool] No visualizer available!");
120  return nullptr;
121  }
122 
123  ecvGenericMeasurementTools* tool = new PclMeasurementTools(viewer, type);
124  if (tool) {
127  }
128  tool->start();
129 
130  // CRITICAL: New tools always use their own default values
131  // Different tool types are completely isolated - they don't inherit
132  // from UI "Apply all" only affects instances of the SAME tool type If
133  // "Apply all" is checked and there are existing instances, apply their
134  // settings
135  if (applyToAllCheckBox && applyToAllCheckBox->isChecked() &&
136  !m_toolInstances.isEmpty() && m_toolInstances[0]) {
137  // Apply settings from first existing instance of same type
138  ecvGenericMeasurementTools* firstInstance = m_toolInstances[0];
139  if (firstInstance->getMeasurementType() == type) {
140 #ifdef USE_PCL_BACKEND
141  PclMeasurementTools* firstPclTool =
142  qobject_cast<PclMeasurementTools*>(firstInstance);
143  if (firstPclTool) {
144  double r = 0.0, g = 1.0, b = 0.0;
145  if (firstPclTool->getColor(r, g, b)) {
146  tool->setColor(r, g, b);
147  }
148  tool->setFontFamily(firstPclTool->getFontFamily());
149  tool->setFontSize(firstPclTool->getFontSize());
150  double fontR = 1.0, fontG = 1.0, fontB = 1.0;
151  firstPclTool->getFontColor(fontR, fontG, fontB);
152  tool->setFontColor(fontR, fontG, fontB);
153  tool->setBold(firstPclTool->getFontBold());
154  tool->setItalic(firstPclTool->getFontItalic());
155  tool->setShadow(firstPclTool->getFontShadow());
156  tool->setFontOpacity(firstPclTool->getFontOpacity());
158  firstPclTool->getHorizontalJustification());
160  firstPclTool->getVerticalJustification());
161  }
162 #endif
163  }
164  }
165  // Otherwise, tool uses its own default values (set in constructor)
166 
167  // Connect measurement changed signal
170  }
171  return tool;
172 #else
173  return nullptr;
174 #endif
175 }
176 
178  if (!tool) return;
179 
180  // CRITICAL: Save current tool's UI state before switching
181  // This ensures UI state is preserved when switching between tool types
183  // Save current tool's color and font properties to the tool itself
184  // (they are already stored in the tool, but ensure they're up-to-date)
185  // No need to save here as tools already store their own state
186  }
187 
189 
190  // CRITICAL: When switching tool types, clean up old tools of different
191  // types This ensures each tool type has its own fresh instance and UI,
192  // preventing UI interference between different tool types
193  QList<ecvGenericMeasurementTools*> toolsToRemove;
194  for (ecvGenericMeasurementTools* existingTool : m_toolInstances) {
195  if (existingTool &&
196  existingTool->getMeasurementType() != m_measurementType) {
197  // Mark different type tools for removal
198  toolsToRemove.append(existingTool);
199  }
200  }
201 
202  // Remove and delete old tools of different types
203  for (ecvGenericMeasurementTools* toolToRemove : toolsToRemove) {
204  // Disable shortcuts before deletion
205  toolToRemove->disableShortcuts();
206 
207  // Remove widget from layout
208  QWidget* widget = toolToRemove->getMeasurementWidget();
209  if (widget) {
210  parametersLayout->removeWidget(widget);
211  widget->setVisible(false);
212  }
213 
214  // Remove from list
215  m_toolInstances.removeAll(toolToRemove);
216 
217  // Delete the tool (this will also delete its UI)
218  toolToRemove->clear();
219  delete toolToRemove;
220  }
221 
222  // Add to instances list if not already present
223  if (!m_toolInstances.contains(tool)) {
224  m_toolInstances.append(tool);
225 
226  // Hide the widget initially - we'll show it when it becomes active
227  tool->getMeasurementWidget()->setVisible(false);
228 
229  // Lock the tool initially - it will be unlocked when it becomes active
230  tool->lockInteraction();
231 
232  // CRITICAL: New tools always use their own default values
233  // Different tool types are completely isolated - they don't inherit
234  // from UI "Apply all" only affects instances of the SAME tool type If
235  // "Apply all" is checked and there are existing instances, apply their
236  // settings
237  if (applyToAllCheckBox && applyToAllCheckBox->isChecked() &&
238  !m_toolInstances.isEmpty() && m_toolInstances[0]) {
239  // Apply settings from first existing instance of same type
240  ecvGenericMeasurementTools* firstInstance = m_toolInstances[0];
241  if (firstInstance->getMeasurementType() ==
242  tool->getMeasurementType()) {
243 #ifdef USE_PCL_BACKEND
244  PclMeasurementTools* firstPclTool =
245  qobject_cast<PclMeasurementTools*>(firstInstance);
246  if (firstPclTool) {
247  double r = 0.0, g = 1.0, b = 0.0;
248  if (firstPclTool->getColor(r, g, b)) {
249  tool->setColor(r, g, b);
250  }
251  tool->setFontFamily(firstPclTool->getFontFamily());
252  tool->setFontSize(firstPclTool->getFontSize());
253  double fontR = 1.0, fontG = 1.0, fontB = 1.0;
254  firstPclTool->getFontColor(fontR, fontG, fontB);
255  tool->setFontColor(fontR, fontG, fontB);
256  tool->setBold(firstPclTool->getFontBold());
257  tool->setItalic(firstPclTool->getFontItalic());
258  tool->setShadow(firstPclTool->getFontShadow());
259  tool->setFontOpacity(firstPclTool->getFontOpacity());
261  firstPclTool->getHorizontalJustification());
263  firstPclTool->getVerticalJustification());
264  }
265 #endif
266  }
267  }
268  // Otherwise, tool uses its own default values (set in constructor)
269 
270  // Connect measurement changed signal
273 
274  // Connect point picking signals
279  }
280 
281  // Set as current tool
282  m_tool = tool;
283 
284  // Update instances combo box
286 
287  // Switch to the new tool's UI
288  switchToToolUI(tool);
289 
291 }
292 
294  if (!tool) return;
295 
296  // Lock non-active tools and hide their widgets
298  if (t && t->getMeasurementWidget()) {
299  QWidget* widget = t->getMeasurementWidget();
300  // Remove from layout if present
301  if (widget->parent() == nullptr ||
302  parametersLayout->indexOf(widget) >= 0) {
303  parametersLayout->removeWidget(widget);
304  }
305  widget->setVisible(false);
306 
307  // Lock non-active tools (disable interaction, shortcuts, and UI
308  // controls)
309  if (t != tool) {
310  t->lockInteraction();
311  }
312  }
313  }
314 
315  // Show current tool's widget and unlock it
316  QWidget* currentWidget = tool->getMeasurementWidget();
317  if (currentWidget) {
318  parametersLayout->addWidget(currentWidget);
319  currentWidget->setVisible(true);
320 
321  // CRITICAL: Reset size constraints and let Qt's layout system handle
322  // sizing This ensures each tool adapts to its own content without
323  // interference ParaView-style: use Minimum (horizontal) to prevent
324  // unnecessary expansion
325  currentWidget->setMinimumSize(0, 0);
326  currentWidget->setMaximumSize(16777215,
327  16777215); // QWIDGETSIZE_MAX equivalent
328  currentWidget->setSizePolicy(QSizePolicy::Minimum,
329  QSizePolicy::Preferred);
330  // Force Qt to recalculate size based on content
331  currentWidget->adjustSize();
332  currentWidget->updateGeometry();
333  // CRITICAL: Process events to ensure layout is fully updated
334  QApplication::processEvents();
335 
336  // Unlock the active tool (enable interaction, shortcuts, and UI
337  // controls)
338  tool->unlockInteraction();
339  }
340 
341  // CRITICAL: Update UI controls (color button and font widget) from the
342  // current tool This prevents UI interference between different tool
343  // instances Each tool instance should have its own independent settings
345 }
346 
348  instancesComboBox->blockSignals(true);
349  instancesComboBox->clear();
350 
351  for (int i = 0; i < m_toolInstances.size(); ++i) {
352  QString typeName;
353  switch (m_toolInstances[i]->getMeasurementType()) {
355  typeName = "Ruler";
356  break;
358  typeName = "Protractor";
359  break;
361  typeName = "Contour";
362  break;
363  }
364  QString instanceName = QString("%1 #%2").arg(typeName).arg(i + 1);
365  instancesComboBox->addItem(instanceName);
366 
367  // Update tool's instance label for display in 3D view (e.g., " #1", "
368  // #2") Important: When instances are added/removed, labels are
369  // automatically updated
370  QString label = QString(" #%1").arg(i + 1);
371  m_toolInstances[i]->setInstanceLabel(label);
372  }
373 
374  // Set current index
375  int currentIndex = m_toolInstances.indexOf(m_tool);
376  if (currentIndex >= 0) {
377  instancesComboBox->setCurrentIndex(currentIndex);
378  }
379 
380  instancesComboBox->blockSignals(false);
381 }
382 
384  if (index < 0 || index >= m_toolInstances.size()) return;
385 
387  if (newTool == m_tool) return; // Already showing this tool
388 
389  m_tool = newTool;
391 
392  // Switch to the selected tool's UI
394 
396 }
397 
399  // Allow multiple instances for all measurement types including contour
400  ecvGenericMeasurementTools* newTool =
402  if (newTool) {
403  // Set up the VTK widget reference for the new tool
404  // This ensures new instances can create shortcuts when unlocked
405  if (m_linkedWidget) {
406  // Directly set the m_vtkWidget for the new tool
407  // This will be used by unlockInteraction() to create shortcuts
408  CVLog::PrintDebug(QString("[ecvMeasurementTool::addInstance] "
409  "Setting m_vtkWidget=%1 for new tool")
410  .arg((quintptr)m_linkedWidget, 0, 16));
411  // We need to access the tool's internal member
412  // Since we can't access it directly (it's in
413  // cvGenericMeasurementTool), we'll call setupShortcuts which will
414  // save the widget reference
415  newTool->setupShortcuts(m_linkedWidget);
416  } else {
418  "[ecvMeasurementTool::addInstance] m_linkedWidget is null, "
419  "new tool may not have shortcuts");
420  }
421 
422  setMeasurementTool(newTool);
423  removeInstanceButton->setEnabled(true);
424  }
425 }
426 
428  if (m_toolInstances.size() <= 1) {
429  CVLog::Warning("Cannot remove the last instance");
430  return;
431  }
432 
433  int index = instancesComboBox->currentIndex();
434  if (index < 0 || index >= m_toolInstances.size()) return;
435 
436  ecvGenericMeasurementTools* toolToRemove = m_toolInstances[index];
437 
438  // Remove widget from layout (it should already be removed by
439  // switchToToolUI, but ensure it's clean)
440  QWidget* widget = toolToRemove->getMeasurementWidget();
441  if (widget) {
442  parametersLayout->removeWidget(widget);
443  widget->setVisible(false);
444  }
445 
446  // Remove from list
447  m_toolInstances.removeAt(index);
448 
449  // Delete tool (CRITICAL: disable shortcuts first to prevent crash)
450  if (toolToRemove) {
451  toolToRemove->disableShortcuts(); // Disable shortcuts before deletion
452  toolToRemove->clear();
453  delete toolToRemove;
454  }
455 
456  // Set new current tool
457  if (m_toolInstances.size() > 0) {
458  int newIndex = (index < m_toolInstances.size())
459  ? index
460  : m_toolInstances.size() - 1;
461  m_tool = m_toolInstances[newIndex];
462 
463  // Switch to the new tool's UI
465 
467  instancesComboBox->setCurrentIndex(newIndex);
469  } else {
470  m_tool = nullptr;
472  removeInstanceButton->setEnabled(false);
473  }
475 }
476 
478  if (!entity) {
479  assert(false);
480  return false;
481  }
482 
483  // special case
484  if (entity->isGroup()) {
485  for (unsigned i = 0; i < entity->getChildrenNumber(); ++i) {
486  if (!addAssociatedEntity(entity->getChild(i))) {
487  return false;
488  }
489  }
490  return true;
491  }
492 
494  CVLog::Error("An error occurred (see Console)");
495  return false;
496  }
497 
498  // force visibility
499  entity->setVisible(true);
500  entity->setEnabled(true);
501 
502  // Set input for all tool instances
504  if (tool) {
505  tool->setInputData(entity);
506  }
507  }
508 
509  return true;
510 }
511 
514 }
515 
516 bool ecvMeasurementTool::linkWith(QWidget* win) {
517  if (!ccOverlayDialog::linkWith(win)) {
518  return false;
519  }
520 
521  // Save the widget reference for later use (when creating new instances)
522  m_linkedWidget = win;
523 
524  // Setup keyboard shortcuts bound to the VTK widget
526  if (tool) {
527  tool->setupShortcuts(win);
528  }
529  }
530 
531  return true;
532 }
533 
535  assert(!m_processing);
536  if (m_toolInstances.empty()) return false;
537 
538  // Ensure picking hub is available
541  }
542 
543  if (!m_pickingHub) {
544  CVLog::Warning("[ecvMeasurementTool] Picking hub not available!");
545  }
546 
547  // Start all tool instances
549  if (tool) {
551  tool->setInputData(m_entityContainer.getFirstChild());
552  }
553  tool->start(); // This initializes VTK widgets and m_interactor
554 
555  // CRITICAL: After start() initializes m_interactor, manage
556  // shortcuts:
557  // - Lock non-active tools (disable shortcuts if they exist)
558  // - Unlock the active tool (create and enable shortcuts)
559  if (tool != m_tool) {
560  tool->lockInteraction();
561  } else {
562  // Unlock the active tool to ensure shortcuts are created
563  // (m_interactor is now available after start())
564  tool->unlockInteraction();
565  }
566  }
567  }
568 
569  return ccOverlayDialog::start();
570 }
571 
572 void ecvMeasurementTool::stop(bool state) {
573  // Remove picking listener
574  if (m_pickingHub) {
575  m_pickingHub->removeListener(this, false);
576  }
577 
578  m_pickPointMode = 0;
579 
580  // Clean up all tool instances
582  if (tool) {
583  // Disable shortcuts before deleting the tool
584  // This prevents keyboard shortcuts from triggering after the tool
585  // is closed
586  tool->disableShortcuts();
587 
588  parametersLayout->removeWidget(tool->getMeasurementWidget());
589  tool->clear();
590  delete tool;
591  }
592  }
593  m_toolInstances.clear();
594  m_tool = nullptr;
595 
597  ccOverlayDialog::stop(state);
598 }
599 
601  if (m_tool) {
602  m_tool->reset();
603  }
604 }
605 
607 
609  if (m_tool == sender()) {
611  }
612 }
613 
615  // UI updates are now handled by the individual tool widgets
616  // No need to update removed spinboxes
617 
618  // CRITICAL: Update shared UI controls (color button and font widget) from
619  // current tool This prevents UI interference between different tool
620  // instances Each tool instance should have its own independent settings
621  // displayed in the UI
622  if (!m_tool) return;
623 
624  // Prevent recursive updates
625  if (m_updatingFromTool) return;
626  m_updatingFromTool = true;
627 
628  // Update color button and font widget from current tool's properties
629 #ifdef USE_PCL_BACKEND
630  PclMeasurementTools* pclTool = qobject_cast<PclMeasurementTools*>(m_tool);
631  if (pclTool) {
632  // CRITICAL: Always update UI from current tool's actual color
633  // Different tool types are completely isolated - UI always reflects
634  // current tool's state
635  double r = 0.0, g = 1.0, b = 0.0; // Default green
636  if (pclTool->getColor(r, g, b)) {
637  QColor toolColor = QColor::fromRgbF(r, g, b);
638  // Always update button appearance to show current tool's color
639  updateColorButtonAppearance(toolColor);
640  }
641 
642  // Update font widget from current tool's font properties
643  if (m_fontPropertyWidget) {
644  // Block signals to prevent triggering applyFontToTools during
645  // update
646  m_fontPropertyWidget->blockSignals(true);
647 
648  // Get font properties from tool
650  props.family = pclTool->getFontFamily();
651  props.size = pclTool->getFontSize();
652  double fontR = 1.0, fontG = 1.0, fontB = 1.0;
653  pclTool->getFontColor(fontR, fontG, fontB);
654  props.color = QColor::fromRgbF(fontR, fontG, fontB);
655  props.bold = pclTool->getFontBold();
656  props.italic = pclTool->getFontItalic();
657  props.shadow = pclTool->getFontShadow();
658  props.opacity = pclTool->getFontOpacity();
660  pclTool->getHorizontalJustification();
661  props.verticalJustification = pclTool->getVerticalJustification();
662 
663  // Update font widget using setFontProperties (sets all properties
664  // at once)
666 
667  m_fontPropertyWidget->blockSignals(false);
668  }
669  }
670 #endif
671 
672  m_updatingFromTool = false;
673 }
674 
676  if (m_tool) {
677  QWidget* widget = m_tool->getMeasurementWidget();
678  if (widget) {
679  widget->setVisible(state);
680  }
681  }
682 }
683 
685  if (!m_tool) {
686  CVLog::Warning("[ecvMeasurementTool] No current tool selected");
687  return;
688  }
689 
690  // Get output from current tool (selected contour instance)
691  ccHObject* output = m_tool->getOutput();
692  if (!output) {
694  "[ecvMeasurementTool] No measurement result to export from "
695  "current tool");
696  return;
697  }
698 
699  if (MainWindow::TheInstance()) {
700  output->setEnabled(true);
701 
702  // For contour, the name is already set in getOutput() with ID
703  // For other tools, set name based on type and instance index
705  QString typeName;
706  switch (m_measurementType) {
708  typeName = "Ruler";
709  break;
711  typeName = "Protractor";
712  break;
713  default:
714  typeName = "Measurement";
715  break;
716  }
717  output->setName(QString("%1_%2").arg(typeName).arg(
718  m_toolInstances.indexOf(m_tool) + 1));
719  }
720 
721  MainWindow::TheInstance()->addToDB(output);
722  m_out_entities.push_back(output);
723 
724  CVLog::Print(QString("[ecvMeasurementTool] Exported %1")
725  .arg(output->getName()));
726  } else {
727  CVLog::Error("[ecvMeasurementTool] MainWindow instance not available");
728  delete output;
729  }
730 }
731 
734  m_out_entities.clear();
735 }
736 
738  // Check if processing and valid pick mode
739  if (!m_processing || !m_tool || m_pickPointMode == 0) {
740  CVLog::Warning(QString("[ecvMeasurementTool] Ignoring pick: "
741  "m_processing=%1, m_tool=%2, m_pickPointMode=%3")
742  .arg(m_processing)
743  .arg(m_tool ? "valid" : "null")
744  .arg(m_pickPointMode));
745  return;
746  }
747 
748  // Check if entity is valid
749  if (!pi.entity) {
750  CVLog::Warning("[ecvMeasurementTool] Picked item has no entity");
751  return;
752  }
753 
754  // Get picked point coordinates
755  double pos[3];
756  pos[0] = pi.P3D.x;
757  pos[1] = pi.P3D.y;
758  pos[2] = pi.P3D.z;
759 
760  CVLog::Print(
761  QString("[ecvMeasurementTool] Point picked: (%1, %2, %3), mode: %4")
762  .arg(pos[0])
763  .arg(pos[1])
764  .arg(pos[2])
765  .arg(m_pickPointMode));
766 
767  // Set the point based on current pick mode
768  switch (m_pickPointMode) {
769  case 1: // Point 1
770  m_tool->setPoint1(pos);
771  m_pickPointMode = 0;
772  if (m_pickingHub) {
773  m_pickingHub->removeListener(this, true);
774  }
775  break;
776  case 2: // Point 2
777  m_tool->setPoint2(pos);
778  m_pickPointMode = 0;
779  if (m_pickingHub) {
780  m_pickingHub->removeListener(this, true);
781  }
782  break;
783  case 3: // Center (Protractor only)
784  if (m_measurementType ==
786  m_tool->setCenter(pos);
787  m_pickPointMode = 0;
788  if (m_pickingHub) {
789  m_pickingHub->removeListener(this, true);
790  }
791  } else {
793  "[ecvMeasurementTool] Center picking only available "
794  "for Protractor");
795  m_pickPointMode = 0;
796  if (m_pickingHub) {
797  m_pickingHub->removeListener(this, true);
798  }
799  }
800  break;
801  default:
802  CVLog::Warning(QString("[ecvMeasurementTool] Unknown pick mode: %1")
803  .arg(m_pickPointMode));
804  break;
805  }
806 
807  // Update UI
809 }
810 
812  // Ensure picking hub is available
815  }
816 
817  m_pickPointMode = pointIndex;
818 
819  if (m_pickingHub) {
820  if (!m_pickingHub->addListener(this, true, true,
823  "[ecvMeasurementTool] Failed to register picking listener");
824  m_pickPointMode = 0;
825  }
826  } else {
827  CVLog::Warning("[ecvMeasurementTool] Picking hub not available!");
828  m_pickPointMode = 0;
829  }
830 }
831 
833  m_pickPointMode = 0;
834  if (m_pickingHub) {
835  m_pickingHub->removeListener(this, true);
836  }
837 }
838 
840  // CRITICAL: Get current tool's color as the initial color for the dialog
841  // This ensures the color picker shows the correct color for the current
842  // tool
843  QColor initialColor = QColor(0, 255, 0); // Default green
844 #ifdef USE_PCL_BACKEND
845  PclMeasurementTools* pclTool = qobject_cast<PclMeasurementTools*>(m_tool);
846  if (pclTool) {
847  double r = 0.0, g = 1.0, b = 0.0;
848  if (pclTool->getColor(r, g, b)) {
849  initialColor = QColor::fromRgbF(r, g, b);
850  }
851  }
852 #endif
853 
854  QColor newColor = QColorDialog::getColor(initialColor, this,
855  tr("Select Measurement Color"));
856 
857  if (newColor.isValid()) {
858  // Apply color to tools (all instances of current tool type, or just
859  // current based on checkbox)
860  applyColorToAllTools(newColor);
861 
862  // CRITICAL: Always update UI from current tool to reflect the actual
863  // applied color This ensures UI always shows the current tool's state,
864  // regardless of "Apply all" setting
866  }
867 }
868 
870  // Use provided color, or fall back to m_currentColor if not provided
871  QColor colorToDisplay = color.isValid() ? color : m_currentColor;
872 
873  if (colorButton) {
874  QString styleSheet =
875  QString("QPushButton { background-color: rgb(%1, %2, %3); }")
876  .arg(colorToDisplay.red())
877  .arg(colorToDisplay.green())
878  .arg(colorToDisplay.blue());
879  colorButton->setStyleSheet(styleSheet);
880  }
881 }
882 
884  if (!color.isValid()) return;
885 
886  // Convert QColor to normalized RGB [0.0, 1.0]
887  double r = color.redF();
888  double g = color.greenF();
889  double b = color.blueF();
890 
891  // CRITICAL: "Apply all" only affects instances of the SAME tool type
892  // m_toolInstances already contains only instances of the current tool type
893  // (different types are cleaned up in setMeasurementTool)
894  if (applyToAllCheckBox && applyToAllCheckBox->isChecked()) {
895  // Apply to all instances of the current tool type
897  if (tool && tool->getMeasurementType() == m_measurementType) {
898  tool->setColor(r, g, b);
899  }
900  }
901  } else {
902  // Apply only to the current active tool instance
903  if (m_tool) {
904  m_tool->setColor(r, g, b);
905  }
906  }
907 }
908 
911 
912  // CRITICAL: Always update UI from current tool to reflect the actual
913  // applied properties This ensures UI always shows the current tool's state,
914  // regardless of "Apply all" setting Use QTimer::singleShot to avoid
915  // recursive updates during signal processing
916  QTimer::singleShot(0, this, [this]() { updateUIFromTool(); });
917 }
918 
920  if (!m_fontPropertyWidget) return;
921 
922  auto props = m_fontPropertyWidget->fontProperties();
923 
924  // CRITICAL: "Apply all" only affects instances of the SAME tool type
925  // m_toolInstances already contains only instances of the current tool type
926  // (different types are cleaned up in setMeasurementTool)
927  if (applyToAllCheckBox && applyToAllCheckBox->isChecked()) {
928  // Apply to all instances of the current tool type
930  if (tool && tool->getMeasurementType() == m_measurementType) {
931  tool->setFontFamily(props.family);
932  tool->setFontSize(props.size);
933  tool->setFontColor(props.color.redF(), props.color.greenF(),
934  props.color.blueF());
935  tool->setBold(props.bold);
936  tool->setItalic(props.italic);
937  tool->setShadow(props.shadow);
938  tool->setFontOpacity(props.opacity);
939  tool->setHorizontalJustification(props.horizontalJustification);
940  tool->setVerticalJustification(props.verticalJustification);
941  }
942  }
943  } else {
944  // Apply only to the current active tool instance
945  if (m_tool) {
946  m_tool->setFontFamily(props.family);
947  m_tool->setFontSize(props.size);
948  m_tool->setFontColor(props.color.redF(), props.color.greenF(),
949  props.color.blueF());
950  m_tool->setBold(props.bold);
951  m_tool->setItalic(props.italic);
952  m_tool->setShadow(props.shadow);
953  m_tool->setFontOpacity(props.opacity);
954  m_tool->setHorizontalJustification(props.horizontalJustification);
955  m_tool->setVerticalJustification(props.verticalJustification);
956  }
957  }
958 }
char type
math::float4 color
static bool PrintDebug(const char *format,...)
Same as Print, but works only in Debug mode.
Definition: CVLog.cpp:153
static bool Warning(const char *format,...)
Prints out a formatted warning message in console.
Definition: CVLog.cpp:133
static bool Print(const char *format,...)
Prints out a formatted message in console.
Definition: CVLog.cpp:113
static bool Error(const char *format,...)
Display an error dialog with formatted message.
Definition: CVLog.cpp:143
static MainWindow * TheInstance()
Returns the unique instance of this object.
void addToDB(const QStringList &filenames, QString fileFilter=QString(), bool displayDialog=true)
ccPickingHub * pickingHub() override
Definition: MainWindow.h:215
Type y
Definition: CVGeom.h:137
Type x
Definition: CVGeom.h:137
Type z
Definition: CVGeom.h:137
virtual void setVisible(bool state)
Sets entity visibility.
Hierarchical CLOUDVIEWER Object.
Definition: ecvHObject.h:25
void removeAllChildren()
Removes all children.
ccHObject * getFirstChild() const
Shortcut: returns first child.
Definition: ecvHObject.h:396
unsigned getChildrenNumber() const
Returns the number of children.
Definition: ecvHObject.h:312
virtual bool addChild(ccHObject *child, int dependencyFlags=DP_PARENT_OF_OTHER, int insertIndex=-1)
Adds a child.
ccHObject * getChild(unsigned childPos) const
Returns the ith child.
Definition: ecvHObject.h:325
bool isGroup() const
Returns whether the instance is a group.
Definition: ecvHObject.h:237
virtual QString getName() const
Returns object name.
Definition: ecvObject.h:72
virtual void setName(const QString &name)
Sets object name.
Definition: ecvObject.h:75
virtual void setEnabled(bool state)
Sets the "enabled" property.
Definition: ecvObject.h:102
Generic overlay dialog interface.
void shortcutTriggered(int key)
Signal emitted when an overridden key shortcut is pressed.
virtual void stop(bool accepted)
Stops process/dialog.
virtual bool start()
Starts process.
bool m_processing
Running/processing state.
virtual bool linkWith(QWidget *win)
Links the overlay dialog with a MDI window.
void addOverridenShortcut(Qt::Key key)
void removeListener(ccPickingListener *listener, bool autoStopPickingIfLast=true)
Removes a listener.
bool addListener(ccPickingListener *listener, bool exclusive=false, bool autoStartPicking=true, ecvDisplayTools::PICKING_MODE mode=ecvDisplayTools::POINT_OR_TRIANGLE_PICKING)
Adds a listener.
static ecvGenericVisualizer3D * GetVisualizer3D()
static void UpdateScreen()
A reusable font property widget matching ParaView's font editor style.
void setColorPickerVisible(bool visible)
Show/hide the color picker button.
void setFontProperties(const FontProperties &props)
FontProperties fontProperties() const
void fontPropertiesChanged()
Emitted when any font property changes.
Generic Measurement Tools interface.
virtual ccHObject * getOutput() const =0
Returns the output (if any)
virtual void lockInteraction()=0
Lock tool interaction (disable VTK widget and UI controls)
virtual void setFontColor(double r, double g, double b)=0
Set font color for measurement labels (RGB values 0.0-1.0)
virtual void reset()=0
Resets the measurement tool.
virtual bool setInputData(ccHObject *entity)=0
Sets the input entity.
void pointPickingCancelled()
Signal sent when point picking is cancelled.
virtual void setFontFamily(const QString &family)=0
virtual void setupShortcuts(QWidget *win)
Setup keyboard shortcuts bound to the render window widget.
virtual void setVerticalJustification(const QString &justification)=0
virtual void setItalic(bool italic)=0
Set font italic state for measurement labels.
virtual void setColor(double r, double g, double b)=0
Set measurement color (RGB values in range [0.0, 1.0])
virtual void setPoint2(double pos[3])=0
Set point 2 coordinates.
virtual QWidget * getMeasurementWidget()=0
Returns the measurement widget.
virtual void setHorizontalJustification(const QString &justification)=0
virtual bool start()=0
Starts the measurement tool.
virtual void setFontOpacity(double opacity)=0
Set font opacity for measurement labels (0.0 to 1.0)
virtual void unlockInteraction()=0
Unlock tool interaction (enable VTK widget and UI controls)
virtual void setFontSize(int size)=0
Set font size for measurement labels.
virtual void setBold(bool bold)=0
Set font bold state for measurement labels.
void pointPickingRequested(int pointIndex)
virtual void setShadow(bool shadow)=0
Set font shadow state for measurement labels.
virtual void disableShortcuts()
Disable keyboard shortcuts (called before tool destruction)
void measurementChanged()
Signal sent when the measurement changes.
virtual void clear()=0
Clears the measurement tool.
virtual void setPoint1(double pos[3])=0
Set point 1 coordinates.
virtual void setCenter(double pos[3])=0
Set center point coordinates (for angle/protractor)
Generic visualizer 3D interface.
bool m_updatingFromTool
Flag to prevent recursive updates.
ecvMeasurementTool(QWidget *parent)
Default constructor.
void setMeasurementTool(ecvGenericMeasurementTools *tool)
virtual void stop(bool state) override
Stops process/dialog.
void toggleWidget(bool state)
void onItemPicked(const PickedItem &pi) override
Inherited from ccPickingListener.
void releaseAssociatedEntities()
Releases all associated entities.
QList< ecvGenericMeasurementTools * > m_toolInstances
List of all measurement tool instances.
virtual ~ecvMeasurementTool()
Default destructor.
void onInstanceChanged(int index)
void applyColorToAllTools(const QColor &color=QColor())
void switchToToolUI(ecvGenericMeasurementTools *tool)
Switches to the specified tool's UI.
ecvGenericMeasurementTools::MeasurementType m_measurementType
ecvGenericMeasurementTools * m_tool
Current measurement tool.
ccHObject::Container m_out_entities
void onPointPickingRequested(int pointIndex)
void updateInstancesComboBox()
Updates instances combo box.
unsigned getNumberOfAssociatedEntity() const
Returns the current number of associated entities.
ccPickingHub * m_pickingHub
Picking hub for point selection.
int m_pickPointMode
Current point selection mode (0=none, 1=point1, 2=point2, 3=center)
bool addAssociatedEntity(ccHObject *anObject)
Adds an entity.
QColor m_currentColor
Current measurement color (default: green)
ecvGenericMeasurementTools * createMeasurementTool(ecvGenericMeasurementTools::MeasurementType type)
Creates a new measurement tool instance.
void updateUIFromTool()
Updates UI from current tool.
virtual bool start() override
Starts process.
void updateColorButtonAppearance(const QColor &color=QColor())
ccHObject m_entityContainer
Associated entities container.
virtual bool linkWith(QWidget *win) override
Links the overlay dialog with a MDI window.
ecvFontPropertyWidget * m_fontPropertyWidget
Font property widget.
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...
Font property structure for convenience.