ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
MainWindow.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 "MainWindow.h"
9 
10 // Local
11 #include "ecvLayoutManager.h"
12 #include "ecvShortcutDialog.h"
13 
14 // Qt
15 #include <QGuiApplication>
16 #include <QScreen>
17 #include <QSettings>
18 #include <QThread>
19 #include <QTimer>
20 
21 // Standard
22 #include <algorithm>
23 
24 #include "ecvAnnotationsTool.h"
25 #include "ecvApplication.h"
26 #include "ecvConsole.h"
27 #include "ecvCropTool.h"
28 #include "ecvFilterTool.h"
31 #include "ecvHistogramWindow.h"
32 #include "ecvInnerRect2DFinder.h"
33 #include "ecvLibAlgorithms.h"
34 #include "ecvMeasurementTool.h"
35 #include "ecvPersistentSettings.h"
36 #include "ecvRecentFiles.h"
37 #include "ecvRegistrationTools.h"
38 #include "ecvScaleDlg.h"
39 #include "ecvSettingManager.h"
40 #include "ecvTracePolylineTool.h"
41 #include "ecvTranslationManager.h"
42 #include "ecvUIManager.h"
43 #include "ecvUtils.h"
44 
45 // CV_CORE_LIB
46 #include <CVLog.h>
47 #include <CVMath.h>
48 #include <CVPointCloud.h>
49 #include <CloudSamplingTools.h>
50 #include <Delaunay2dMesh.h>
51 #include <Jacobi.h>
52 #include <MemoryInfo.h>
53 #include <MeshSamplingTools.h>
54 #include <NormalDistribution.h>
55 #include <ParallelSort.h>
56 #include <RegistrationTools.h>
57 #include <ScalarFieldTools.h>
59 #include <WeibullDistribution.h>
60 #include <ecvVolumeCalcTool.h>
61 
62 // for tests
65 
66 // CV_DB_LIB
67 #include <ecv2DLabel.h>
68 #include <ecv2DViewportObject.h>
69 #include <ecvCameraSensor.h>
70 #include <ecvCircle.h>
71 #include <ecvColorScalesManager.h>
72 #include <ecvCylinder.h>
73 #include <ecvDisc.h>
74 #include <ecvDisplayTools.h>
75 #include <ecvFacet.h>
76 #include <ecvFileUtils.h>
77 #include <ecvGBLSensor.h>
78 #include <ecvGenericPointCloud.h>
79 #include <ecvImage.h>
80 #include <ecvKdTree.h>
81 #include <ecvPlane.h>
82 #include <ecvPointCloud.h>
83 #include <ecvPolyline.h>
84 #include <ecvProgressDialog.h>
85 #include <ecvQuadric.h>
86 #include <ecvRenderingTools.h>
87 #include <ecvScalarField.h>
88 #include <ecvSphere.h>
89 #include <ecvSubMesh.h>
90 
91 // CV_IO_LIB
92 #include <AsciiFilter.h>
93 #include <BinFilter.h>
94 #include <DepthMapFileFilter.h>
95 #include <ecvGlobalShiftManager.h>
97 
98 // common
99 #include <CommonSettings.h>
100 #include <ecvCommon.h>
102 #include <ecvOptions.h>
103 #include <ecvPickingHub.h>
104 
105 // common dialogs
106 #include <ecvCameraParamEditDlg.h>
107 #include <ecvDisplayOptionsDlg.h>
108 #include <ecvPickOneElementDlg.h>
109 
110 // Qt UI files
111 #include "ui_MainWindow.h"
112 #include "ui_distanceMapDlg.h"
113 #include "ui_globalShiftSettingsDlg.h"
114 
115 // Qt5/Qt6 Compatibility
116 #include <QtCompat.h>
117 
118 // dialogs
119 #include "ecvAboutDialog.h"
120 #include "ecvAlignDlg.h"
121 #include "ecvAnimationParamDlg.h"
125 #include "ecvColorFromScalarDlg.h"
126 #include "ecvColorScaleEditorDlg.h"
127 #include "ecvComparisonDlg.h"
128 #include "ecvEntitySelectionDlg.h"
129 #include "ecvFilterByLabelDlg.h"
130 #include "ecvFilterByValueDlg.h"
132 #include "ecvGeomFeaturesDlg.h"
133 #include "ecvItemSelectionDlg.h"
134 #include "ecvLabelingDlg.h"
135 #include "ecvMatchScalesDlg.h"
136 #include "ecvNoiseFilterDlg.h"
137 #include "ecvOrderChoiceDlg.h"
138 #include "ecvPlaneEditDlg.h"
139 #include "ecvPointListPickingDlg.h"
141 #include "ecvPointPropertiesDlg.h"
142 #include "ecvPoissonReconDlg.h"
143 #include "ecvPrimitiveDistanceDlg.h"
144 #include "ecvPrimitiveFactoryDlg.h"
145 #include "ecvPtsSamplingDlg.h"
146 #include "ecvRegistrationDlg.h"
147 #include "ecvRenderToFileDlg.h"
148 #include "ecvSORFilterDlg.h"
152 #include "ecvSmoothPolylineDlg.h"
153 #include "ecvSubsamplingDlg.h"
154 #include "ecvUnrollDlg.h"
155 #include "ecvUpdateDlg.h"
156 #include "ecvWaveformDialog.h"
157 
158 // other
159 #include "db_tree/ecvDBRoot.h"
162 
163 // 3D mouse handler
164 #ifdef CC_3DXWARE_SUPPORT
165 #include "cc3DMouseManager.h"
166 #endif
167 
168 // Gamepads
169 #ifdef CC_GAMEPAD_SUPPORT
170 #include "ccGamepadManager.h"
171 #endif
172 
173 // Reconstruction
174 #ifdef BUILD_RECONSTRUCTION
176 #endif
177 
178 // QPCL_ENGINE_LIB
179 #ifdef USE_PCL_BACKEND
180 #include <PclUtils/PCLDisplayTools.h>
181 #include <Tools/AnnotationTools/PclAnnotationTool.h>
182 #include <Tools/CameraTools/EditCameraTool.h>
183 #include <Tools/Common/CurveFitting.h>
184 #include <Tools/FilterTools/PclFiltersTool.h>
185 #include <Tools/MeasurementTools/PclMeasurementTools.h>
186 #include <Tools/SelectionTools/cvFindDataDockWidget.h>
187 #include <Tools/SelectionTools/cvSelectionData.h>
188 #include <Tools/SelectionTools/cvSelectionHighlighter.h>
189 #include <Tools/SelectionTools/cvSelectionToolController.h>
190 #include <Tools/SelectionTools/cvViewSelectionManager.h>
191 #include <Tools/TransformTools/PclTransformTool.h>
192 #endif
193 
194 // CV_PYTHON_LIB
195 #ifdef USE_PYTHON_MODULE
196 #include <recognition/PythonInterface.h>
197 
199 #endif
200 
201 // SYSTEM
202 #ifdef CV_WINDOWS
203 #include <omp.h>
204 #endif
205 
206 #ifdef USE_VLD
207 // VLD
208 #include <vld.h>
209 #endif
210 
211 #ifdef USE_TBB
212 #include <tbb/tbb_stddef.h>
213 #endif
214 
215 // global static pointer (as there should only be one instance of MainWindow!)
216 static MainWindow* s_instance = nullptr;
217 
218 // default 'All files' file filter
219 static const QString s_allFilesFilter("All (*.*)");
220 // default file filter separator
221 static const QString s_fileFilterSeparator(";;");
222 
223 static const float GLOBAL_OPACITY = 0.5;
224 
229 };
231 static std::vector<cc2DLabel*> s_levelLabels;
233 static ccHObject* s_levelEntity = nullptr;
234 
235 static QFileDialog::Options ECVFileDialogOptions() {
236  // dialog options
237  QFileDialog::Options dialogOptions = QFileDialog::Options();
238  dialogOptions |= QFileDialog::DontResolveSymlinks;
239  if (!ecvOptions::Instance().useNativeDialogs) {
240  dialogOptions |= QFileDialog::DontUseNativeDialog;
241  }
242  return dialogOptions;
243 }
244 
245 // Helper: check for a filename validity
246 static bool IsValidFileName(QString filename) {
247 #ifdef CV_WINDOWS
248  QString sPattern(
249  "^(?!^(PRN|AUX|CLOCK\\$|NUL|CON|COM\\d|LPT\\d|\\..*)(\\..+)?$)[^"
250  "\\x00-\\x1f\\\\?*:\\"
251  ";|/]+$");
252 #else
253  QString sPattern(
254  "^(([a-zA-Z]:|\\\\)\\\\)?(((\\.)|(\\.\\.)|([^\\\\/:\\*\\?"
255  "\\|<>\\. ](([^\\\\/:\\*\\?"
256  "\\|<>\\. ])|([^\\\\/:\\*\\?"
257  "\\|<>]*[^\\\\/:\\*\\?"
258  "\\|<>\\. ]))?))\\\\)*[^\\\\/:\\*\\?"
259  "\\|<>\\. ](([^\\\\/:\\*\\?"
260  "\\|<>\\. ])|([^\\\\/:\\*\\?"
261  "\\|<>]*[^\\\\/:\\*\\?"
262  "\\|<>\\. ]))?$");
263 #endif
264 
265  // Use QtCompat for Qt5/Qt6 compatibility
266  QtCompatRegExp regex(sPattern);
267 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
268  QRegularExpressionMatch match = regex.match(filename);
269  return match.hasMatch();
270 #else
271  return regex.exactMatch(filename);
272 #endif
273 }
274 
276  : m_FirstShow(true),
277  m_ui(new Ui::MainViewerClass),
278  m_ccRoot(nullptr),
279  m_uiFrozen(false),
280  m_recentFiles(new ecvRecentFiles(this)),
281  m_viewModePopupButton(nullptr),
282  m_pickingHub(nullptr),
283  m_updateDlg(nullptr),
284  m_cpeDlg(nullptr),
285  m_animationDlg(nullptr),
286  m_gsTool(nullptr),
287  m_tplTool(nullptr),
288  m_transTool(nullptr),
289  m_compDlg(nullptr),
290  m_ppDlg(nullptr),
291  m_plpDlg(nullptr),
292  m_pprDlg(nullptr),
293  m_pfDlg(nullptr),
294  m_filterTool(nullptr),
295  m_annoTool(nullptr),
296  m_filterLabelTool(nullptr),
297  m_measurementTool(nullptr),
298  m_dssTool(nullptr),
299 #ifdef USE_PCL_BACKEND
300  m_selectionController(nullptr),
301  m_findDataDock(nullptr),
302 #endif
303  m_layout(nullptr),
304  m_uiManager(nullptr),
305  m_mousePosLabel(nullptr),
306  m_systemInfoLabel(nullptr),
307  m_memoryUsageWidget(nullptr),
308  m_memoryUsageProgressBar(nullptr),
309  m_memoryUsageLabel(nullptr),
310  m_memoryUsageTimer(nullptr),
311  m_currentFullWidget(nullptr),
312  m_exclusiveFullscreen(false),
313  m_lastViewMode(VIEWMODE::ORTHOGONAL),
314  m_shortcutDlg(nullptr)
315 #ifdef BUILD_RECONSTRUCTION
316  ,
317  m_rcw(nullptr)
318 #endif
319 {
320  m_ui->setupUi(this);
321 
322  setWindowTitle(QStringLiteral("ACloudViewer v") +
323  ecvApp->versionLongStr(false));
324 
325  m_pluginUIManager = new ccPluginUIManager(this, this);
326 
327  // Create layout manager (after m_pluginUIManager is created)
328  m_layoutManager = new ecvLayoutManager(this, m_pluginUIManager);
329 
330  ccTranslationManager::get().populateMenu(m_ui->langAction,
331  ecvApp->translationPath());
332 
333 #ifdef Q_OS_MAC
334  m_ui->actionAbout->setMenuRole(QAction::AboutRole);
335  m_ui->actionAboutPlugins->setMenuRole(QAction::ApplicationSpecificRole);
336 
337  m_ui->actionFullScreen->setText(tr("Enter Full Screen"));
338  m_ui->actionFullScreen->setShortcut(
339  QKeySequence(Qt::CTRL | Qt::META | Qt::Key_F));
340 #endif
341 
342  // Initialization
343  initial();
344 
345  // restore the state of the 'auto-restore' menu entry
346  // (do that before connecting the actions)
347  {
348  QSettings settings;
349  bool doNotAutoRestoreGeometry =
350  settings.value(ecvPS::DoNotRestoreWindowGeometry(),
351  !m_ui->actionRestoreWindowOnStartup->isChecked())
352  .toBool();
353  m_ui->actionRestoreWindowOnStartup->setChecked(
354  !doNotAutoRestoreGeometry);
355  }
356 
357  // connect actions
358  connectActions();
359 
360  setupInputDevices();
361 
362  freezeUI(false);
363 
364  updateUI();
365 
366  // Register ViewToolBar as a left-side toolbar
367  m_layoutManager->registerLeftSideToolBar(m_ui->ViewToolBar);
368 
369  // Register Console dock widget as a bottom dock widget
370  m_layoutManager->registerBottomDockWidget(m_ui->consoleDock);
371 
372  // Create Find Data dock widget (ParaView-style selection properties panel)
373  // This dock is independent of selection tool state and can be shown/hidden
374  // by the user
375 #ifdef USE_PCL_BACKEND
376  {
377  m_findDataDock = new cvFindDataDockWidget(this);
378  // Add dock to the right side (like ParaView)
379  addDockWidget(Qt::RightDockWidgetArea, m_findDataDock);
380 
381  // Hide by default (user can show it via View menu)
382  m_findDataDock->hide();
383 
384  // Connect extractedObjectReady to add extracted objects to scene
385  connect(m_findDataDock, &cvFindDataDockWidget::extractedObjectReady,
386  this, [this](ccHObject* obj) {
387  if (obj) {
388  addToDB(obj, false, true, false);
389  // Refresh data producer list after adding new object
390  // Use QTimer to delay refresh until VTK rendering is
391  // complete
392  if (m_findDataDock) {
393  QTimer::singleShot(100, this, [this]() {
394  if (m_findDataDock) {
395  m_findDataDock->refreshDataProducers();
396  }
397  });
398  }
399  }
400  });
401 
402  // Register with layout manager for proper default layout handling
403  m_layoutManager->registerRightSideDockWidget(m_findDataDock);
404 
405  // Add Find Data dock's toggle action to Toolbars menu
406  // Using QDockWidget's built-in toggleViewAction for standard behavior
407  QAction* selectionPropsAction = m_findDataDock->toggleViewAction();
408  selectionPropsAction->setText(tr("Find Data (Selection)"));
409  m_ui->menuToolbars->addAction(selectionPropsAction);
410 
411  // Store action for external access (e.g., sync with selection tool
412  // state)
413  m_selectionPropsAction = selectionPropsAction;
414  }
415 
416  // Initialize selection controller AFTER m_findDataDock is created
417  // This ensures configure() can properly set up the dock widget
418  initSelectionController();
419 #else
421  "[MainWindow] USE_PCL_BACKEND not defined - Find Data dock not "
422  "created");
423 #endif
424 
425  // advanced widgets not handled by QDesigner
426  { // view mode pop-up menu
427  m_viewModePopupButton = new QToolButton();
428  QMenu* menu = new QMenu(m_viewModePopupButton);
429  menu->addAction(m_ui->actionOrthogonalProjection);
430  menu->addAction(m_ui->actionPerspectiveProjection);
431 
432  m_viewModePopupButton->setMenu(menu);
433  m_viewModePopupButton->setPopupMode(QToolButton::InstantPopup);
434  m_viewModePopupButton->setToolTip("Set current view mode");
435  m_viewModePopupButton->setStatusTip(m_viewModePopupButton->toolTip());
436  m_ui->ViewToolBar->insertWidget(m_ui->actionZoomToBox,
437  m_viewModePopupButton);
438  m_viewModePopupButton->setEnabled(false);
439  }
440 
441  { // custom viewports configuration
442  QToolBar* customViewpointsToolbar =
443  new ecvCustomViewpointsToolbar(this);
444  customViewpointsToolbar->setObjectName("customViewpointsToolbar");
445  customViewpointsToolbar->layout()->setSpacing(0);
446  this->addToolBar(Qt::TopToolBarArea, customViewpointsToolbar);
447  }
448 
449  { // orthogonal projection mode (default)
450  m_ui->actionOrthogonalProjection->trigger();
451  ecvConsole::Print("Perspective off!");
452  }
453 
454  { // restore options
455  QSettings settings;
456 
457  // auto pick center
458  bool autoPickRotationCenter =
459  settings.value(ecvPS::AutoPickRotationCenter(), false).toBool();
460  if (autoPickRotationCenter) {
461  m_ui->actionAutoPickPivot->toggle();
462  }
463 
464  // show center
465  bool autoShowCenterAxis =
466  settings.value(ecvPS::AutoShowCenter(), true).toBool();
467  m_ui->actionShowPivot->blockSignals(true);
468  m_ui->actionShowPivot->setChecked(autoShowCenterAxis);
469  m_ui->actionShowPivot->blockSignals(false);
470  toggleRotationCenterVisibility(autoShowCenterAxis);
471 
472  // Camera Orientation Widget (ParaView-style)
473  bool showCameraOrientationWidget =
474  settings.value("CameraOrientationWidget/Visible", false)
475  .toBool();
476  m_ui->actionToggleCameraOrientationWidget->blockSignals(true);
477  m_ui->actionToggleCameraOrientationWidget->setChecked(
478  showCameraOrientationWidget);
479  m_ui->actionToggleCameraOrientationWidget->blockSignals(false);
482  showCameraOrientationWidget);
483  }
484  }
485 
486  // Shortcut management
487  {
488  populateActionList();
489  // Alphabetical sort
490  std::sort(m_actions.begin(), m_actions.end(),
491  [](const QAction* a, const QAction* b) {
492  return a->text() < b->text();
493  });
494 
495  m_shortcutDlg = new ecvShortcutDialog(m_actions, this);
496  m_shortcutDlg->restoreShortcutsFromQSettings();
497 
498  connect(m_ui->actionShortcutSettings, &QAction::triggered, this,
499  &MainWindow::showShortcutDialog);
500  }
501 
502  refreshAll();
503 
504 #ifdef CV_WINDOWS
505 #ifdef QT_DEBUG
506  // speed up debugging on windows
507  doActionToggleOrientationMarker(false);
508 #else
509  doActionToggleOrientationMarker(true);
510 #endif
511 #else
512  doActionToggleOrientationMarker(true);
513 #endif
514 
515 #ifdef USE_PYTHON_MODULE
516 // QString applicationPath = QCoreApplication::applicationDirPath();
517 // QString pyHome = applicationPath + "/python38";
518 // if (!PythonInterface::SetPythonHome(CVTools::FromQString(pyHome).c_str())) {
519 // CVLog::Warning(QString("Setting python home failed! Invalid path: [%1].")
520 // .arg(pyHome));
521 // } else {
522 // CVLog::Print(QString("Setting python home [%1]
523 // successfully!").arg(pyHome));
524 // }
525 #else
526  m_ui->actionSemanticSegmentation->setEnabled(false);
527 #endif
528 
529  // Apply unified icon size and style to all toolbars created in constructor
530  // This handles UI toolbars, customViewpointsToolbar, and reconstruction
531  // toolbars
532  updateAllToolbarIconSizes();
533 
534 #ifdef USE_TBB
535  ecvConsole::Print(tr("[TBB] Using Intel's Threading Building Blocks %1.%2")
536  .arg(QString::number(TBB_VERSION_MAJOR),
537  QString::number(TBB_VERSION_MINOR)));
538 #endif
539 
540  // print welcome message
542  tr("[ACloudViewer Software start], Welcome to use ACloudViewer"));
543 }
544 
546  destroyInputDevices();
547 
548  cancelPreviousPickingOperation(false); // just in case
549 
550  // Reconstruction must before m_ccRoot
551 #ifdef BUILD_RECONSTRUCTION
552  if (m_rcw) {
553  m_rcw->release();
554  }
555  m_rcw = nullptr;
556 #endif
557 
558  assert(m_ccRoot && m_mdiArea);
559  m_ccRoot->unloadAll();
560  m_ccRoot->disconnect();
561  m_mdiArea->disconnect();
562 
563  // we don't want any other dialog/function to use the following structures
564  ccDBRoot* ccRoot = m_ccRoot;
565  m_ccRoot = nullptr;
566 
567  m_updateDlg = nullptr;
568  m_cpeDlg = nullptr;
569  m_animationDlg = nullptr;
570  m_mousePosLabel = nullptr;
571  m_systemInfoLabel = nullptr;
572  m_memoryUsageWidget = nullptr;
573  m_memoryUsageProgressBar = nullptr;
574  m_memoryUsageLabel = nullptr;
575  m_memoryUsageTimer = nullptr;
576 
577  m_measurementTool = nullptr;
578  m_gsTool = nullptr;
579  m_transTool = nullptr;
580  m_filterTool = nullptr;
581  m_annoTool = nullptr;
582  m_dssTool = nullptr;
583  m_filterLabelTool = nullptr;
584 
585  // Selection tools are now managed by cvSelectionToolController
586  // The controller is a singleton and handles its own cleanup
587 
588  m_compDlg = nullptr;
589  m_ppDlg = nullptr;
590  m_plpDlg = nullptr;
591  m_pprDlg = nullptr;
592  m_pfDlg = nullptr;
593 
594  // release all 'overlay' dialogs
595  while (!m_mdiDialogs.empty()) {
596  ccMDIDialogs mdiDialog = m_mdiDialogs.back();
597  m_mdiDialogs.pop_back();
598 
599  mdiDialog.dialog->disconnect();
600  mdiDialog.dialog->stop(false);
601  mdiDialog.dialog->setParent(nullptr);
602  delete mdiDialog.dialog;
603  }
604 
605  // m_mdiDialogs.clear();
606  std::vector<QWidget*> windows;
607  GetRenderWindows(windows);
608  for (QWidget* window : windows) {
609  m_mdiArea->removeSubWindow(window);
610  }
611 
614 
615  if (ccRoot) {
616  delete ccRoot;
617  ccRoot = nullptr;
618  }
619 
620  delete m_ui;
621  m_ui = nullptr;
622 
623  // if we flush the console, it will try to display the console window while
624  // we are destroying everything!
626 }
627 
629  // Call the base class implementation to get the standard toolbar actions
630  QMenu* menu = QMainWindow::createPopupMenu();
631 
632  // Add separator and custom actions
633  if (menu && m_selectionPropsAction) {
634  menu->addSeparator();
635  menu->addAction(m_selectionPropsAction);
636  }
637 
638  return menu;
639 }
640 
641 // MainWindow Initialization
642 void MainWindow::initial() {
643  // MDI Area
644  {
645  m_mdiArea = new QMdiArea(this);
646  setCentralWidget(m_mdiArea);
647  connect(m_mdiArea, &QMdiArea::subWindowActivated, this,
648  &MainWindow::updateMenus);
649  connect(m_mdiArea, &QMdiArea::subWindowActivated, this,
650  &MainWindow::on3DViewActivated);
651  m_mdiArea->installEventFilter(this);
652  }
653 
654  bool stereoMode = QSurfaceFormat::defaultFormat().stereo();
655  ecvDisplayTools::Init(new PCLDisplayTools(), this, stereoMode);
656 
657  // init themes
658  initThemes();
659 
660  // init languages
661  initLanguages();
662 
663  // init console
664  initConsole();
665 
666  // init db root
667  initDBRoot();
668 
669  // init status bar
670  initStatusBar();
671 
672 // Reconstruction
673 #ifdef BUILD_RECONSTRUCTION
674  initReconstructions();
675 #endif
676 
677  QWidget* viewWidget = ecvDisplayTools::GetMainScreen();
678  viewWidget->setMinimumSize(400, 300);
679  m_mdiArea->addSubWindow(viewWidget);
680 
681  // Install event filter on the VTK render widget to capture ESC key
682  // VTK render window doesn't pass key events to Qt by default
683  viewWidget->installEventFilter(this);
684 
685  // picking hub
686  {
687  m_pickingHub = new ccPickingHub(this, this);
688  connect(m_mdiArea, &QMdiArea::subWindowActivated, m_pickingHub,
690  }
691 
692  if (m_pickingHub) {
693  // we must notify the picking hub as well if the window is destroyed
694  connect(this, &QObject::destroyed, m_pickingHub,
696  }
697 
698  viewWidget->showMaximized();
699  viewWidget->update();
700 }
701 
702 void MainWindow::updateAllToolbarIconSizes() {
703  // Apply unified icon size and style to all existing toolbars
704  // This ensures consistency across all toolbars
705  if (!m_layoutManager) return;
706 
707  QScreen* screen = QGuiApplication::primaryScreen();
708  int screenWidth = screen ? screen->geometry().width() : 1920;
709  QList<QToolBar*> allToolbars = findChildren<QToolBar*>();
710 
711  for (QToolBar* toolbar : allToolbars) {
712  if (toolbar && toolbar->parent() == this) {
713  m_layoutManager->setToolbarIconSize(toolbar, screenWidth);
714  }
715  }
716 }
717 
718 void MainWindow::initConsole() {
719  // set Console
720  ecvConsole::Init(m_ui->consoleWidget, this, this);
721  m_ui->actionEnableQtWarnings->setChecked(ecvConsole::QtMessagesEnabled());
722 
723  // Initialize log verbosity level from options (similar to CloudCompare)
724  {
726  static_cast<int>(ecvOptions::Instance().logVerbosityLevel));
727  // Print current log verbosity level
728  QString levelName;
729  switch (ecvOptions::Instance().logVerbosityLevel) {
730  case CVLog::LOG_VERBOSE:
731  levelName = "Verbose";
732  break;
733  case CVLog::LOG_STANDARD:
734  levelName = "Standard";
735  break;
737  levelName = "Important";
738  break;
739  case CVLog::LOG_WARNING:
740  levelName = "Warning";
741  break;
742  default:
743  levelName = QString::number(
744  ecvOptions::Instance().logVerbosityLevel);
745  break;
746  }
747  CVLog::Print(QString("Log verbosity level: %1").arg(levelName));
748  }
749 }
750 
751 void MainWindow::connectActions() {
752  assert(m_ccRoot);
753  /***** Slots connection of QMenuBar and QToolBar *****/
754  //"File" menu
755  connect(m_ui->actionOpen, &QAction::triggered, this,
756  &MainWindow::doActionOpenFile);
757  connect(m_ui->actionSave, &QAction::triggered, this,
758  &MainWindow::doActionSaveFile);
759  connect(m_ui->actionSaveProject, &QAction::triggered, this,
760  &MainWindow::doActionSaveProject);
761  connect(m_ui->actionPrimitiveFactory, &QAction::triggered, this,
762  &MainWindow::doShowPrimitiveFactory);
763  connect(m_ui->actionClearAll, &QAction::triggered, this,
764  &MainWindow::clearAll);
765  connect(m_ui->actionExit, &QAction::triggered, this, &QWidget::close);
766 
767  //"Edit > Colors" menu
768  connect(m_ui->actionSetUniqueColor, &QAction::triggered, this,
769  &MainWindow::doActionSetUniqueColor);
770  connect(m_ui->actionSetColorGradient, &QAction::triggered, this,
771  &MainWindow::doActionSetColorGradient);
772  connect(m_ui->actionChangeColorLevels, &QAction::triggered, this,
773  &MainWindow::doActionChangeColorLevels);
774  connect(m_ui->actionColorize, &QAction::triggered, this,
775  &MainWindow::doActionColorize);
776  connect(m_ui->actionRGBToGreyScale, &QAction::triggered, this,
777  &MainWindow::doActionRGBToGreyScale);
778  connect(m_ui->actionInterpolateColors, &QAction::triggered, this,
779  &MainWindow::doActionInterpolateColors);
780  connect(m_ui->actionEnhanceRGBWithIntensities, &QAction::triggered, this,
781  &MainWindow::doActionEnhanceRGBWithIntensities);
782  connect(m_ui->actionColorFromScalarField, &QAction::triggered, this,
783  &MainWindow::doActionColorFromScalars);
784  connect(m_ui->actionRGBGaussianFilter, &QAction::triggered, this,
785  &MainWindow::doActionRGBGaussianFilter);
786  connect(m_ui->actionRGBBilateralFilter, &QAction::triggered, this,
787  &MainWindow::doActionRGBBilateralFilter);
788  connect(m_ui->actionRGBMeanFilter, &QAction::triggered, this,
789  &MainWindow::doActionRGBMeanFilter);
790  connect(m_ui->actionRGBMedianFilter, &QAction::triggered, this,
791  &MainWindow::doActionRGBMedianFilter);
792  connect(m_ui->actionClearColor, &QAction::triggered, this, [=]() {
793  clearSelectedEntitiesProperty(ccEntityAction::CLEAR_PROPERTY::COLORS);
794  });
795 
796  //"Edit > Clean" menu
797  connect(m_ui->actionSORFilter, &QAction::triggered, this,
798  &MainWindow::doActionSORFilter);
799  connect(m_ui->actionNoiseFilter, &QAction::triggered, this,
800  &MainWindow::doActionFilterNoise);
801  connect(m_ui->actionVoxelSampling, &QAction::triggered, this,
802  &MainWindow::doActionVoxelSampling);
803 
804  //"Edit" menu
805  connect(m_ui->actionSegment, &QAction::triggered, this,
806  &MainWindow::activateSegmentationMode);
807  connect(m_ui->actionRemoveDuplicatePoints, &QAction::triggered, this,
808  &MainWindow::doRemoveDuplicatePoints);
809  connect(m_ui->actionSubsample, &QAction::triggered, this,
810  &MainWindow::doActionSubsample);
811  connect(m_ui->actionEditGlobalShiftAndScale, &QAction::triggered, this,
812  &MainWindow::doActionEditGlobalShiftAndScale);
813  connect(m_ui->actionClone, &QAction::triggered, this,
814  &MainWindow::doActionClone);
815  connect(m_ui->actionMerge, &QAction::triggered, this,
816  &MainWindow::doActionMerge);
817  connect(m_ui->actionTracePolyline, &QAction::triggered, this,
818  &MainWindow::activateTracePolylineMode);
819  connect(m_ui->actionDelete, &QAction::triggered, m_ccRoot,
821  connect(m_ui->actionApplyTransformation, &QAction::triggered, this,
822  &MainWindow::doActionApplyTransformation);
823  connect(m_ui->actionApplyScale, &QAction::triggered, this,
824  &MainWindow::doActionApplyScale);
825  connect(m_ui->actionTranslateRotate, &QAction::triggered, this,
826  &MainWindow::activateTranslateRotateMode);
827 
828  // "Edit > Octree" menu
829  connect(m_ui->actionComputeOctree, &QAction::triggered, this,
830  &MainWindow::doActionComputeOctree);
831  connect(m_ui->actionResampleWithOctree, &QAction::triggered, this,
832  &MainWindow::doActionResampleWithOctree);
833 
834  //"Edit > Cloud" menu
835  connect(m_ui->actionCreateSinglePointCloud, &QAction::triggered, this,
836  &MainWindow::createSinglePointCloud);
837  connect(m_ui->actionPasteCloudFromClipboard, &QAction::triggered, this,
838  &MainWindow::createPointCloudFromClipboard);
839  // the 'Paste from clipboard' tool depends on the clipboard state
840  {
841  const QClipboard* clipboard = QApplication::clipboard();
842  assert(clipboard);
843  m_ui->actionPasteCloudFromClipboard->setEnabled(
844  clipboard->mimeData()->hasText());
845  connect(clipboard, &QClipboard::dataChanged, [&]() {
846  m_ui->actionPasteCloudFromClipboard->setEnabled(
847  clipboard->mimeData()->hasText());
848  });
849  }
850 
851  // "Edit > Normals" menu
852  connect(m_ui->actionComputeNormals, &QAction::triggered, this,
853  &MainWindow::doActionComputeNormals);
854  connect(m_ui->actionInvertNormals, &QAction::triggered, this,
855  &MainWindow::doActionInvertNormals);
856  connect(m_ui->actionConvertNormalToHSV, &QAction::triggered, this,
857  &MainWindow::doActionConvertNormalsToHSV);
858  connect(m_ui->actionConvertNormalToDipDir, &QAction::triggered, this,
859  &MainWindow::doActionConvertNormalsToDipDir);
860  connect(m_ui->actionExportNormalToSF, &QAction::triggered, this,
861  &MainWindow::doActionExportNormalToSF);
862  connect(m_ui->actionOrientNormalsMST, &QAction::triggered, this,
863  &MainWindow::doActionOrientNormalsMST);
864  connect(m_ui->actionOrientNormalsFM, &QAction::triggered, this,
865  &MainWindow::doActionOrientNormalsFM);
866  connect(m_ui->actionClearNormals, &QAction::triggered, this, [=]() {
867  clearSelectedEntitiesProperty(ccEntityAction::CLEAR_PROPERTY::NORMALS);
868  });
869 
870  // "Edit > Mesh" menu
871  connect(m_ui->actionComputeMeshAA, &QAction::triggered, this,
872  &MainWindow::doActionComputeMeshAA);
873  connect(m_ui->actionComputeMeshLS, &QAction::triggered, this,
874  &MainWindow::doActionComputeMeshLS);
875  connect(m_ui->actionConvexHull, &QAction::triggered, this,
876  &MainWindow::doActionConvexHull);
877  connect(m_ui->actionPoissonReconstruction, &QAction::triggered, this,
878  &MainWindow::doActionPoissonReconstruction);
879  connect(m_ui->actionMeshTwoPolylines, &QAction::triggered, this,
880  &MainWindow::doMeshTwoPolylines);
881  connect(m_ui->actionMeshScanGrids, &QAction::triggered, this,
882  &MainWindow::doActionMeshScanGrids);
883  connect(m_ui->actionConvertTextureToColor, &QAction::triggered, this,
884  &MainWindow::doActionConvertTextureToColor);
885  connect(m_ui->actionSamplePointsOnMesh, &QAction::triggered, this,
886  &MainWindow::doActionSamplePointsOnMesh);
887  connect(m_ui->actionSmoothMeshLaplacian, &QAction::triggered, this,
888  &MainWindow::doActionSmoothMeshLaplacian);
889  connect(m_ui->actionFlipMeshTriangles, &QAction::triggered, this,
890  &MainWindow::doActionFlipMeshTriangles);
891  connect(m_ui->actionSubdivideMesh, &QAction::triggered, this,
892  &MainWindow::doActionSubdivideMesh);
893  connect(m_ui->actionMeasureMeshSurface, &QAction::triggered, this,
894  &MainWindow::doActionMeasureMeshSurface);
895  connect(m_ui->actionMeasureMeshVolume, &QAction::triggered, this,
896  &MainWindow::doActionMeasureMeshVolume);
897  connect(m_ui->actionFlagMeshVertices, &QAction::triggered, this,
898  &MainWindow::doActionFlagMeshVertices);
899  // "Edit > Mesh > Scalar Field" menu
900  connect(m_ui->actionSmoothMeshSF, &QAction::triggered, this,
901  &MainWindow::doActionSmoothMeshSF);
902  connect(m_ui->actionEnhanceMeshSF, &QAction::triggered, this,
903  &MainWindow::doActionEnhanceMeshSF);
904  // "Edit > Polyline" menu
905  connect(m_ui->actionSamplePointsOnPolyline, &QAction::triggered, this,
906  &MainWindow::doActionSamplePointsOnPolyline);
907  connect(m_ui->actionSmoothPolyline, &QAction::triggered, this,
908  &MainWindow::doActionSmoohPolyline);
909  connect(m_ui->actionConvertPolylinesToMesh, &QAction::triggered, this,
910  &MainWindow::doConvertPolylinesToMesh);
911  connect(m_ui->actionBSplineFittingOnCloud, &QAction::triggered, this,
912  &MainWindow::doBSplineFittingFromCloud);
913  // "Edit > Plane" menu
914  connect(m_ui->actionCreatePlane, &QAction::triggered, this,
915  &MainWindow::doActionCreatePlane);
916  connect(m_ui->actionEditPlane, &QAction::triggered, this,
917  &MainWindow::doActionEditPlane);
918  connect(m_ui->actionFlipPlane, &QAction::triggered, this,
919  &MainWindow::doActionFlipPlane);
920  connect(m_ui->actionComparePlanes, &QAction::triggered, this,
921  &MainWindow::doActionComparePlanes);
922 
923  //"Edit > Circle" menu
924  connect(m_ui->actionPromoteCircleToCylinder, &QAction::triggered, this,
925  &MainWindow::doActionPromoteCircleToCylinder);
926 
927  //"Edit > Scalar fields" menu
928  connect(m_ui->actionShowHistogram, &QAction::triggered, this,
929  &MainWindow::showSelectedEntitiesHistogram);
930  connect(m_ui->actionComputeStatParams, &QAction::triggered, this,
931  &MainWindow::doActionComputeStatParams);
932  connect(m_ui->actionSFGradient, &QAction::triggered, this,
933  &MainWindow::doActionSFGradient);
934  connect(m_ui->actionOpenColorScalesManager, &QAction::triggered, this,
935  &MainWindow::doActionOpenColorScalesManager);
936  connect(m_ui->actionGaussianFilter, &QAction::triggered, this,
937  &MainWindow::doActionSFGaussianFilter);
938  connect(m_ui->actionBilateralFilter, &QAction::triggered, this,
939  &MainWindow::doActionSFBilateralFilter);
940  connect(m_ui->actionFilterByLabel, &QAction::triggered, this,
941  &MainWindow::doActionFilterByLabel);
942  connect(m_ui->actionFilterByValue, &QAction::triggered, this,
943  &MainWindow::doActionFilterByValue);
944  connect(m_ui->actionScalarFieldFromColor, &QAction::triggered, this,
945  &MainWindow::doActionScalarFieldFromColor);
946  connect(m_ui->actionConvertToRGB, &QAction::triggered, this,
947  &MainWindow::doActionSFConvertToRGB);
948  connect(m_ui->actionConvertToRandomRGB, &QAction::triggered, this,
949  &MainWindow::doActionSFConvertToRandomRGB);
950  connect(m_ui->actionRenameSF, &QAction::triggered, this,
951  &MainWindow::doActionRenameSF);
952  connect(m_ui->actionAddConstantSF, &QAction::triggered, this,
953  &MainWindow::doActionAddConstantSF);
954  connect(m_ui->actionImportSFFromFile, &QAction::triggered, this,
955  &MainWindow::doActionImportSFFromFile);
956  connect(m_ui->actionAddIdField, &QAction::triggered, this,
957  &MainWindow::doActionAddIdField);
958  connect(m_ui->actionExportCoordToSF, &QAction::triggered, this,
959  &MainWindow::doActionExportCoordToSF);
960  connect(m_ui->actionSetSFAsCoord, &QAction::triggered, this,
961  &MainWindow::doActionSetSFAsCoord);
962  connect(m_ui->actionInterpolateSFs, &QAction::triggered, this,
963  &MainWindow::doActionInterpolateScalarFields);
964  connect(m_ui->actionScalarFieldArithmetic, &QAction::triggered, this,
965  &MainWindow::doActionScalarFieldArithmetic);
966  connect(m_ui->actionDeleteScalarField, &QAction::triggered, this, [=]() {
967  clearSelectedEntitiesProperty(
968  ccEntityAction::CLEAR_PROPERTY::CURRENT_SCALAR_FIELD);
969  });
970  connect(m_ui->actionDeleteAllSF, &QAction::triggered, this, [=]() {
971  clearSelectedEntitiesProperty(
972  ccEntityAction::CLEAR_PROPERTY::ALL_SCALAR_FIELDS);
973  });
974 
975  //"Edit > Sensor > Ground-Based lidar" menu
976  connect(m_ui->actionShowDepthBuffer, &QAction::triggered, this,
977  &MainWindow::doActionShowDepthBuffer);
978  connect(m_ui->actionExportDepthBuffer, &QAction::triggered, this,
979  &MainWindow::doActionExportDepthBuffer);
980  connect(m_ui->actionComputePointsVisibility, &QAction::triggered, this,
981  &MainWindow::doActionComputePointsVisibility);
982 
983  //"Edit > Sensor" menu
984  connect(m_ui->actionModifySensor, &QAction::triggered, this,
985  &MainWindow::doActionModifySensor);
986  connect(m_ui->actionCreateGBLSensor, &QAction::triggered, this,
987  &MainWindow::doActionCreateGBLSensor);
988  connect(m_ui->actionCreateCameraSensor, &QAction::triggered, this,
989  &MainWindow::doActionCreateCameraSensor);
990  connect(m_ui->actionProjectUncertainty, &QAction::triggered, this,
991  &MainWindow::doActionProjectUncertainty);
992  connect(m_ui->actionCheckPointsInsideFrustum, &QAction::triggered, this,
993  &MainWindow::doActionCheckPointsInsideFrustum);
994  connect(m_ui->actionComputeDistancesFromSensor, &QAction::triggered, this,
995  &MainWindow::doActionComputeDistancesFromSensor);
996  connect(m_ui->actionComputeScatteringAngles, &QAction::triggered, this,
997  &MainWindow::doActionComputeScatteringAngles);
998  connect(m_ui->actionViewFromSensor, &QAction::triggered, this,
999  &MainWindow::doActionSetViewFromSensor);
1000 
1001  //"Edit > Waveform" menu
1002  connect(m_ui->actionShowWaveDialog, &QAction::triggered, this,
1003  &MainWindow::doActionShowWaveDialog);
1004  connect(m_ui->actionCompressFWFData, &QAction::triggered, this,
1005  &MainWindow::doActionCompressFWFData);
1006 
1007  //"Tools > Filter" menu
1008  connect(m_ui->actionClipFilter, &QAction::triggered, this,
1009  &MainWindow::activateClippingMode);
1010  connect(m_ui->actionSliceFilter, &QAction::triggered, this,
1011  &MainWindow::activateSliceMode);
1012  connect(m_ui->actionProbeFilter, &QAction::triggered, this,
1013  &MainWindow::activateProbeMode);
1014  connect(m_ui->actionDecimateFilter, &QAction::triggered, this,
1015  &MainWindow::activateDecimateMode);
1016  connect(m_ui->actionIsoSurfaceFilter, &QAction::triggered, this,
1017  &MainWindow::activateIsoSurfaceMode);
1018  connect(m_ui->actionThresholdFilter, &QAction::triggered, this,
1019  &MainWindow::activateThresholdMode);
1020  connect(m_ui->actionSmoothFilter, &QAction::triggered, this,
1021  &MainWindow::activateSmoothMode);
1022  connect(m_ui->actionStreamlineFilter, &QAction::triggered, this,
1023  &MainWindow::activateStreamlineMode);
1024  connect(m_ui->actionGlyphFilter, &QAction::triggered, this,
1025  &MainWindow::activateGlyphMode);
1026 
1027  // "Tools > Measurements" menu
1028  connect(m_ui->actionDistanceWidget, &QAction::triggered, this,
1029  &MainWindow::activateDistanceMode);
1030  connect(m_ui->actionProtractorWidget, &QAction::triggered, this,
1031  &MainWindow::activateProtractorMode);
1032  connect(m_ui->actionContourWidget, &QAction::triggered, this,
1033  &MainWindow::activateContourMode);
1034 
1035  // "Tools > Distances" menu
1036  connect(m_ui->actionCloudCloudDist, &QAction::triggered, this,
1037  &MainWindow::doActionCloudCloudDist);
1038  connect(m_ui->actionCloudMeshDist, &QAction::triggered, this,
1039  &MainWindow::doActionCloudMeshDist);
1040  connect(m_ui->actionCloudPrimitiveDist, &QAction::triggered, this,
1041  &MainWindow::doActionCloudPrimitiveDist);
1042  connect(m_ui->actionCPS, &QAction::triggered, this,
1043  &MainWindow::doActionComputeCPS);
1044 
1045  // "Tools > Annotations" menu
1046  connect(m_ui->actionBoxAnnotation, &QAction::triggered, this,
1047  &MainWindow::doBoxAnnotation);
1048  connect(m_ui->actionSemanticAnnotation, &QAction::triggered, this,
1049  &MainWindow::doSemanticAnnotation);
1050 
1051  //"Tools > Recognition" menu
1052  connect(m_ui->actionSemanticSegmentation, &QAction::triggered, this,
1053  &MainWindow::doSemanticSegmentation);
1054 
1055  //"Tools > Segmentation" menu
1056  connect(m_ui->actionDBScanCluster, &QAction::triggered, this,
1057  &MainWindow::doActionDBScanCluster);
1058  connect(m_ui->actionPlaneSegmentation, &QAction::triggered, this,
1059  &MainWindow::doActionPlaneSegmentation);
1060 
1061  //"Tools > Registration" menu
1062  connect(m_ui->actionMatchBBCenters, &QAction::triggered, this,
1063  &MainWindow::doActionMatchBBCenters);
1064  connect(m_ui->actionMatchScales, &QAction::triggered, this,
1065  &MainWindow::doActionMatchScales);
1066  connect(m_ui->actionRegister, &QAction::triggered, this,
1067  &MainWindow::doActionRegister);
1068  connect(m_ui->actionPointPairsAlign, &QAction::triggered, this,
1069  &MainWindow::activateRegisterPointPairTool);
1070  connect(m_ui->actionBBCenterToOrigin, &QAction::triggered, this,
1071  &MainWindow::doActionMoveBBCenterToOrigin);
1072  connect(m_ui->actionBBMinCornerToOrigin, &QAction::triggered, this,
1073  &MainWindow::doActionMoveBBMinCornerToOrigin);
1074  connect(m_ui->actionBBMaxCornerToOrigin, &QAction::triggered, this,
1075  &MainWindow::doActionMoveBBMaxCornerToOrigin);
1076 
1077  // "Tools > Fit" menu
1078  connect(m_ui->actionFitPlane, &QAction::triggered, this,
1079  &MainWindow::doActionFitPlane);
1080  connect(m_ui->actionFitSphere, &QAction::triggered, this,
1081  &MainWindow::doActionFitSphere);
1082  connect(m_ui->actionFitCircle, &QAction::triggered, this,
1083  &MainWindow::doActionFitCircle);
1084  connect(m_ui->actionFitFacet, &QAction::triggered, this,
1085  &MainWindow::doActionFitFacet);
1086  connect(m_ui->actionFitQuadric, &QAction::triggered, this,
1087  &MainWindow::doActionFitQuadric);
1088 
1089  // "Tools > Batch export" menu
1090  connect(m_ui->actionExportCloudInfo, &QAction::triggered, this,
1091  &MainWindow::doActionExportCloudInfo);
1092  connect(m_ui->actionExportPlaneInfo, &QAction::triggered, this,
1093  &MainWindow::doActionExportPlaneInfo);
1094 
1095  connect(m_ui->actionLabelConnectedComponents, &QAction::triggered, this,
1096  &MainWindow::doActionLabelConnectedComponents);
1097  connect(m_ui->actionComputeGeometricFeature, &QAction::triggered, this,
1098  &MainWindow::doComputeGeometricFeature);
1099  connect(m_ui->actionUnroll, &QAction::triggered, this,
1100  &MainWindow::doActionUnroll);
1101  connect(m_ui->actionPointListPicking, &QAction::triggered, this,
1102  &MainWindow::activatePointListPickingMode);
1103  connect(m_ui->actionPointPicking, &QAction::triggered, this,
1104  &MainWindow::activatePointPickingMode);
1105 
1106  //"Tools > Sand box (research)" menu
1107  connect(m_ui->actionComputeKdTree, &QAction::triggered, this,
1108  &MainWindow::doActionComputeKdTree);
1109  connect(m_ui->actionDistanceMap, &QAction::triggered, this,
1110  &MainWindow::doActionComputeDistanceMap);
1111  connect(m_ui->actionDistanceToBestFitQuadric3D, &QAction::triggered, this,
1112  &MainWindow::doActionComputeDistToBestFitQuadric3D);
1113  connect(m_ui->actionComputeBestFitBB, &QAction::triggered, this,
1114  &MainWindow::doComputeBestFitBB);
1115  connect(m_ui->actionAlign, &QAction::triggered, this,
1116  &MainWindow::doAction4pcsRegister); // Aurelien BEY le 13/11/2008
1117  connect(m_ui->actionSNETest, &QAction::triggered, this,
1118  &MainWindow::doSphericalNeighbourhoodExtractionTest);
1119  connect(m_ui->actionCNETest, &QAction::triggered, this,
1120  &MainWindow::doCylindricalNeighbourhoodExtractionTest);
1121  connect(m_ui->actionFindBiggestInnerRectangle, &QAction::triggered, this,
1122  &MainWindow::doActionFindBiggestInnerRectangle);
1123  connect(m_ui->actionCreateCloudFromEntCenters, &QAction::triggered, this,
1124  &MainWindow::doActionCreateCloudFromEntCenters);
1125  connect(m_ui->actionComputeBestICPRmsMatrix, &QAction::triggered, this,
1126  &MainWindow::doActionComputeBestICPRmsMatrix);
1127 
1128  // Display (connect)
1129  connect(m_ui->actionFullScreen, &QAction::toggled, this,
1130  &MainWindow::toggleFullScreen);
1131  connect(m_ui->actionExclusiveFullScreen, &QAction::toggled, this,
1133  connect(m_ui->action3DView, &QAction::toggled, this,
1135  connect(m_ui->actionResetGUIElementsPos, &QAction::triggered, this,
1136  &MainWindow::doActionResetGUIElementsPos);
1137  connect(m_ui->actionRestoreWindowOnStartup, &QAction::toggled, this,
1138  &MainWindow::doActionRestoreWindowOnStartup);
1139  connect(m_ui->actionSaveCustomLayout, &QAction::triggered, this,
1140  &MainWindow::doActionSaveCustomLayout);
1141  connect(m_ui->actionRestoreDefaultLayout, &QAction::triggered, this,
1142  &MainWindow::doActionRestoreDefaultLayout);
1143  connect(m_ui->actionRestoreCustomLayout, &QAction::triggered, this,
1144  &MainWindow::doActionRestoreCustomLayout);
1145  connect(m_ui->actionZoomAndCenter, &QAction::triggered, this,
1147  connect(m_ui->actionGlobalZoom, &QAction::triggered, this,
1149 
1150  // "Edit > Selection" menu - Initialize selection controller
1151  // NOTE: initSelectionController() is called AFTER m_findDataDock is created
1152  // (in MainWindow constructor), not here. See the note below.
1153  // The actual initialization is done after m_findDataDock is created.
1154 
1155  connect(m_ui->actionLockRotationAxis, &QAction::triggered, this,
1156  &MainWindow::toggleLockRotationAxis);
1157  connect(m_ui->actionSaveViewportAsObject, &QAction::triggered, this,
1159  //"Display > Active SF" menu
1160  connect(m_ui->actionToggleActiveSFColorScale, &QAction::triggered, this,
1161  &MainWindow::doActionToggleActiveSFColorScale);
1162  connect(m_ui->actionShowActiveSFPrevious, &QAction::triggered, this,
1163  &MainWindow::doActionShowActiveSFPrevious);
1164  connect(m_ui->actionShowActiveSFNext, &QAction::triggered, this,
1165  &MainWindow::doActionShowActiveSFNext);
1166 
1167  connect(m_ui->actionDisplayOptions, &QAction::triggered, this,
1168  &MainWindow::showDisplayOptions);
1169  connect(m_ui->actionSetViewTop, &QAction::triggered, this,
1170  [=]() { setView(CC_TOP_VIEW); });
1171  connect(m_ui->actionSetViewBottom, &QAction::triggered, this,
1172  [=]() { setView(CC_BOTTOM_VIEW); });
1173  connect(m_ui->actionSetViewFront, &QAction::triggered, this,
1174  [=]() { setView(CC_FRONT_VIEW); });
1175  connect(m_ui->actionSetViewBack, &QAction::triggered, this,
1176  [=]() { setView(CC_BACK_VIEW); });
1177  connect(m_ui->actionSetViewLeft, &QAction::triggered, this,
1178  [=]() { setView(CC_LEFT_VIEW); });
1179  connect(m_ui->actionSetViewRight, &QAction::triggered, this,
1180  [=]() { setView(CC_RIGHT_VIEW); });
1181  connect(m_ui->actionSetViewIso1, &QAction::triggered, this,
1182  [=]() { setView(CC_ISO_VIEW_1); });
1183  connect(m_ui->actionSetViewIso2, &QAction::triggered, this,
1184  [=]() { setView(CC_ISO_VIEW_2); });
1185 
1186  connect(m_ui->actionAutoPickPivot, &QAction::toggled, this,
1187  &MainWindow::toggleActiveWindowAutoPickRotCenter);
1188  connect(m_ui->actionShowPivot, &QAction::toggled, this,
1189  &MainWindow::toggleRotationCenterVisibility);
1190  connect(m_ui->actionResetPivot, &QAction::triggered, this,
1191  &MainWindow::doActionResetRotCenter);
1192  connect(m_ui->actionPerspectiveProjection, &QAction::triggered, this,
1194  connect(m_ui->actionOrthogonalProjection, &QAction::triggered, this,
1196 
1197  connect(m_ui->actionEditCamera, &QAction::triggered, this,
1198  &MainWindow::doActionEditCamera);
1199  connect(m_ui->actionAnimation, &QAction::triggered, this,
1200  &MainWindow::doActionAnimation);
1201  connect(m_ui->actionScreenShot, &QAction::triggered, this,
1202  &MainWindow::doActionScreenShot);
1203  connect(m_ui->actionToggleOrientationMarker, &QAction::triggered, this,
1204  &MainWindow::doActionToggleOrientationMarker);
1205  connect(m_ui->actionToggleCameraOrientationWidget, &QAction::toggled, this,
1206  &MainWindow::doActionToggleCameraOrientationWidget);
1207  connect(m_ui->actionGlobalShiftSettings, &QAction::triggered, this,
1208  &MainWindow::doActionGlobalShiftSeetings);
1209 
1210  // About (connect)
1211  connect(m_ui->helpAction, &QAction::triggered, this, &MainWindow::help);
1212  connect(m_ui->actionAboutPlugins, &QAction::triggered, m_pluginUIManager,
1214  connect(m_ui->actionEnableQtWarnings, &QAction::toggled, this,
1215  &MainWindow::doEnableQtWarnings);
1216  connect(m_ui->actionAbout, &QAction::triggered, this, [this]() {
1217  ecvAboutDialog* aboutDialog = new ecvAboutDialog(this);
1218  aboutDialog->exec();
1219  });
1220 
1221  // echo mode
1222  m_ui->consoleWidget->setProperty("contextMenuPolicy",
1223  Qt::CustomContextMenu);
1224  // Enable multi-selection (Ctrl+Click and Shift+Click)
1225  m_ui->consoleWidget->setSelectionMode(QAbstractItemView::ExtendedSelection);
1226  connect(m_ui->consoleWidget,
1227  &ecvCustomQListWidget::customContextMenuRequested, this,
1228  &MainWindow::popMenuInConsole);
1229  // DGM: we don't want to block the 'dropEvent' method of MainWindow!
1231  this, &MainWindow::addToDBAuto, Qt::QueuedConnection);
1232 
1233  // hidden
1234  connect(m_ui->actionEnableVisualDebugTraces, &QAction::triggered, this,
1235  &MainWindow::toggleVisualDebugTraces);
1236 
1238  &MainWindow::handleNewLabel);
1241  connect(ecvDisplayTools::TheInstance(),
1244 
1245  // Not yet implemented!
1246  connect(m_ui->actionKMeans, &QAction::triggered, this,
1247  &MainWindow::doActionKMeans);
1248  connect(m_ui->actionFrontPropagation, &QAction::triggered, this,
1249  &MainWindow::doActionFrontPropagation);
1250 
1251  // update
1252  initApplicationUpdate();
1253 
1254  // Set up dynamic menus
1255  m_ui->menuFile->insertMenu(m_ui->actionSave, m_recentFiles->menu());
1256 }
1257 
1258 void MainWindow::initApplicationUpdate() {
1259  // TODO: check update when application start!
1260  if (!m_updateDlg) {
1261  m_updateDlg = new ecvUpdateDlg(this);
1262  connect(m_ui->actionCheckForUpdates, &QAction::triggered, this,
1263  &MainWindow::doCheckForUpdate);
1264  }
1265 }
1266 
1267 void MainWindow::initThemes() {
1268  // Option (connect)
1269  m_ui->DfaultThemeAction->setData(QVariant(Themes::THEME_DEFAULT));
1270  m_ui->BlueThemeAction->setData(QVariant(Themes::THEME_BLUE));
1271  m_ui->LightBlueThemeAction->setData(QVariant(Themes::THEME_LIGHTBLUE));
1272  m_ui->DarkBlueThemeAction->setData(QVariant(Themes::THEME_DARKBLUE));
1273  m_ui->BlackThemeAction->setData(QVariant(Themes::THEME_BLACK));
1274  m_ui->LightBlackThemeAction->setData(QVariant(Themes::THEME_LIGHTBLACK));
1275  m_ui->FlatBlackThemeAction->setData(QVariant(Themes::THEME_FLATBLACK));
1276  m_ui->DarkBlackThemeAction->setData(QVariant(Themes::THEME_DarkBLACK));
1277  m_ui->GrayThemeAction->setData(QVariant(Themes::THEME_GRAY));
1278  m_ui->LightGrayThemeAction->setData(QVariant(Themes::THEME_LIGHTGRAY));
1279  m_ui->DarkGrayThemeAction->setData(QVariant(Themes::THEME_DarkGRAY));
1280  m_ui->FlatWhiteThemeAction->setData(QVariant(Themes::THEME_FLATWHITE));
1281  m_ui->PsBlackThemeAction->setData(QVariant(Themes::THEME_PSBLACK));
1282  m_ui->SilverThemeAction->setData(QVariant(Themes::THEME_SILVER));
1283  m_ui->BFThemeAction->setData(QVariant(Themes::THEME_BF));
1284  m_ui->TestThemeAction->setData(QVariant(Themes::THEME_TEST));
1285  m_ui->ParaviewThemeAction->setData(QVariant(Themes::THEME_PARAVIEW));
1286  m_ui->MaterialDarkThemeAction->setData(
1287  QVariant(Themes::THEME_MATERIALDARK));
1288  m_ui->MaterialLightThemeAction->setData(
1289  QVariant(Themes::THEME_MATERIALLIGHT));
1290  m_ui->NordThemeAction->setData(QVariant(Themes::THEME_NORD));
1291  m_ui->DraculaThemeAction->setData(QVariant(Themes::THEME_DRACULA));
1292  m_ui->FluentThemeAction->setData(QVariant(Themes::THEME_FLUENT));
1293  m_ui->MacOSThemeAction->setData(QVariant(Themes::THEME_MACOS));
1294  m_ui->OneDarkThemeAction->setData(QVariant(Themes::THEME_ONEDARK));
1295  m_ui->CatppuccinThemeAction->setData(QVariant(Themes::THEME_CATPPUCCIN));
1296  m_ui->TokyoNightThemeAction->setData(QVariant(Themes::THEME_TOKYONIGHT));
1297  m_ui->GruvboxThemeAction->setData(QVariant(Themes::THEME_GRUVBOX));
1298 
1299  connect(m_ui->DfaultThemeAction, &QAction::triggered, this,
1300  &MainWindow::changeTheme);
1301  connect(m_ui->BlueThemeAction, &QAction::triggered, this,
1302  &MainWindow::changeTheme);
1303  connect(m_ui->LightBlueThemeAction, &QAction::triggered, this,
1304  &MainWindow::changeTheme);
1305  connect(m_ui->DarkBlueThemeAction, &QAction::triggered, this,
1306  &MainWindow::changeTheme);
1307  connect(m_ui->BlackThemeAction, &QAction::triggered, this,
1308  &MainWindow::changeTheme);
1309  connect(m_ui->LightBlackThemeAction, &QAction::triggered, this,
1310  &MainWindow::changeTheme);
1311  connect(m_ui->FlatBlackThemeAction, &QAction::triggered, this,
1312  &MainWindow::changeTheme);
1313  connect(m_ui->DarkBlackThemeAction, &QAction::triggered, this,
1314  &MainWindow::changeTheme);
1315  connect(m_ui->GrayThemeAction, &QAction::triggered, this,
1316  &MainWindow::changeTheme);
1317  connect(m_ui->LightGrayThemeAction, &QAction::triggered, this,
1318  &MainWindow::changeTheme);
1319  connect(m_ui->DarkGrayThemeAction, &QAction::triggered, this,
1320  &MainWindow::changeTheme);
1321  connect(m_ui->FlatWhiteThemeAction, &QAction::triggered, this,
1322  &MainWindow::changeTheme);
1323  connect(m_ui->PsBlackThemeAction, &QAction::triggered, this,
1324  &MainWindow::changeTheme);
1325  connect(m_ui->SilverThemeAction, &QAction::triggered, this,
1326  &MainWindow::changeTheme);
1327  connect(m_ui->BFThemeAction, &QAction::triggered, this,
1328  &MainWindow::changeTheme);
1329  connect(m_ui->TestThemeAction, &QAction::triggered, this,
1330  &MainWindow::changeTheme);
1331  connect(m_ui->ParaviewThemeAction, &QAction::triggered, this,
1332  &MainWindow::changeTheme);
1333  connect(m_ui->MaterialDarkThemeAction, &QAction::triggered, this,
1334  &MainWindow::changeTheme);
1335  connect(m_ui->MaterialLightThemeAction, &QAction::triggered, this,
1336  &MainWindow::changeTheme);
1337  connect(m_ui->NordThemeAction, &QAction::triggered, this,
1338  &MainWindow::changeTheme);
1339  connect(m_ui->DraculaThemeAction, &QAction::triggered, this,
1340  &MainWindow::changeTheme);
1341  connect(m_ui->FluentThemeAction, &QAction::triggered, this,
1342  &MainWindow::changeTheme);
1343  connect(m_ui->MacOSThemeAction, &QAction::triggered, this,
1344  &MainWindow::changeTheme);
1345  connect(m_ui->OneDarkThemeAction, &QAction::triggered, this,
1346  &MainWindow::changeTheme);
1347  connect(m_ui->CatppuccinThemeAction, &QAction::triggered, this,
1348  &MainWindow::changeTheme);
1349  connect(m_ui->TokyoNightThemeAction, &QAction::triggered, this,
1350  &MainWindow::changeTheme);
1351  connect(m_ui->GruvboxThemeAction, &QAction::triggered, this,
1352  &MainWindow::changeTheme);
1353 }
1354 
1355 void MainWindow::initLanguages() {
1356  m_ui->englishAction->setData(QVariant(CLOUDVIEWER_LANG_ENGLISH));
1357  m_ui->chineseAction->setData(QVariant(CLOUDVIEWER_LANG_CHINESE));
1358  connect(m_ui->englishAction, &QAction::triggered, this,
1359  &MainWindow::changeLanguage);
1360  connect(m_ui->chineseAction, &QAction::triggered, this,
1361  &MainWindow::changeLanguage);
1362 }
1363 
1364 void MainWindow::initStatusBar() {
1365  // set mouse position label
1366  {
1367  m_mousePosLabel = new QLabel(this);
1368  QFont ft;
1369  ft.setBold(true);
1370  m_mousePosLabel->setFont(ft);
1371  m_mousePosLabel->setMinimumSize(m_mousePosLabel->sizeHint());
1372  m_mousePosLabel->setAlignment(Qt::AlignHCenter);
1373  m_ui->statusBar->insertWidget(0, m_mousePosLabel, 1);
1374  connect(ecvDisplayTools::TheInstance(),
1376  &MainWindow::onMousePosChanged);
1377  }
1378 
1379  // set memory usage display widget (ParaView-style)
1380  {
1381  m_memoryUsageWidget = new QWidget(this);
1382  // No layout needed - we'll use absolute positioning for overlay
1383 
1384  // Progress bar for memory usage (thicker, like ParaView)
1385  m_memoryUsageProgressBar = new QProgressBar(m_memoryUsageWidget);
1386  m_memoryUsageProgressBar->setMinimumWidth(
1387  240); // Minimum width (doubled)
1388  m_memoryUsageProgressBar->setMaximumWidth(
1389  500); // Maximum width (doubled)
1390  m_memoryUsageProgressBar->setFixedHeight(
1391  20); // Thicker height (doubled from 10)
1392  m_memoryUsageProgressBar->setMinimum(0);
1393  m_memoryUsageProgressBar->setMaximum(100);
1394  m_memoryUsageProgressBar->setTextVisible(false);
1395  m_memoryUsageProgressBar->setSizePolicy(QSizePolicy::Fixed,
1396  QSizePolicy::Fixed);
1397  m_memoryUsageProgressBar->move(0, 0); // Position at top-left of widget
1398  m_memoryUsageProgressBar->setStyleSheet(
1399  "QProgressBar {"
1400  " border: 1px solid #999;"
1401  " border-radius: 2px;"
1402  " background-color: #e0e0e0;"
1403  "}"
1404  "QProgressBar::chunk {"
1405  " background-color: #B8E6B8;" // Light green, matches
1406  // ParaView
1407  " border-radius: 1px;"
1408  "}");
1409 
1410  // Label for memory usage text (overlay on progress bar)
1411  m_memoryUsageLabel = new QLabel(m_memoryUsageWidget);
1412  m_memoryUsageLabel->setMinimumWidth(240); // Minimum width (doubled)
1413  m_memoryUsageLabel->setMaximumWidth(500); // Maximum width (doubled)
1414  m_memoryUsageLabel->setFixedHeight(20); // Same height as progress bar
1415  m_memoryUsageLabel->setAlignment(Qt::AlignCenter); // Center text
1416  m_memoryUsageLabel->setSizePolicy(QSizePolicy::Fixed,
1417  QSizePolicy::Fixed);
1418  m_memoryUsageLabel->move(0, 0); // Overlay on progress bar
1419  QFont labelFont = m_memoryUsageLabel->font();
1420  labelFont.setPointSize(labelFont.pointSize() - 1);
1421  m_memoryUsageLabel->setFont(labelFont);
1422  // Make label transparent so progress bar shows through
1423  m_memoryUsageLabel->setStyleSheet("background: transparent;");
1424 
1425  // Set widget size to match progress bar
1426  m_memoryUsageWidget->setFixedSize(
1427  240, 20); // Will be updated by updateMemoryUsageWidgetSize
1428  m_memoryUsageWidget->setSizePolicy(QSizePolicy::Fixed,
1429  QSizePolicy::Fixed);
1430  m_ui->statusBar->addPermanentWidget(m_memoryUsageWidget, 0);
1431 
1432  // Create timer to update memory usage periodically
1433  m_memoryUsageTimer = new QTimer(this);
1434  connect(m_memoryUsageTimer, &QTimer::timeout, this,
1435  &MainWindow::updateMemoryUsage);
1436  m_memoryUsageTimer->start(5000); // Update every 5 seconds
1437 
1438  // Initial update
1439  updateMemoryUsage();
1440  updateMemoryUsageWidgetSize();
1441  }
1442 
1443  statusBar()->showMessage(tr("Ready"));
1444 }
1445 
1446 void MainWindow::updateMemoryUsage() {
1447  if (!m_memoryUsageProgressBar || !m_memoryUsageLabel) {
1448  return;
1449  }
1450 
1451  // Get system memory information
1454 
1455  if (memInfo.totalRam > 0) {
1456  // Calculate used memory (total - available)
1457  qint64 bytesUsed =
1458  static_cast<qint64>(memInfo.totalRam - memInfo.availableRam);
1459  qint64 bytesTotal = static_cast<qint64>(memInfo.totalRam);
1460 
1461  // Calculate percentage
1462  int percentage = static_cast<int>((bytesUsed * 100) / bytesTotal);
1463  m_memoryUsageProgressBar->setValue(percentage);
1464 
1465  // Format sizes
1466  QString usedStr = formatBytes(bytesUsed);
1467  QString totalStr = formatBytes(bytesTotal);
1468 
1469  // Get hostname
1470  QString hostname = QHostInfo::localHostName();
1471 
1472  // Update label text: "hostname: used/total percentage%"
1473  QString text = QString("%1: %2/%3 %4%")
1474  .arg(hostname)
1475  .arg(usedStr)
1476  .arg(totalStr)
1477  .arg(percentage);
1478  m_memoryUsageLabel->setText(text);
1479  }
1480 }
1481 
1482 void MainWindow::updateMemoryUsageWidgetSize() {
1483  if (!m_memoryUsageWidget || !m_memoryUsageProgressBar ||
1484  !m_memoryUsageLabel) {
1485  return;
1486  }
1487 
1488  // Get window width
1489  int windowWidth = width();
1490 
1491  // Calculate widget width based on window size (ParaView-style scaling)
1492  // Scale between 240px (min) and 500px (max) based on window width (doubled)
1493  const int minWidth = 240;
1494  const int maxWidth = 500;
1495 
1496  int widgetWidth;
1497  if (windowWidth <= 1280) {
1498  // Small windows: use minimum width
1499  widgetWidth = minWidth;
1500  } else if (windowWidth >= 2560) {
1501  // Large windows: use maximum width
1502  widgetWidth = maxWidth;
1503  } else {
1504  // Medium windows: linear interpolation
1505  double ratio = static_cast<double>(windowWidth - 1280) / (2560 - 1280);
1506  widgetWidth =
1507  static_cast<int>(minWidth + ratio * (maxWidth - minWidth));
1508  }
1509 
1510  // Update progress bar and label width and widget size
1511  m_memoryUsageProgressBar->setFixedWidth(widgetWidth);
1512  m_memoryUsageLabel->setFixedWidth(widgetWidth);
1513  m_memoryUsageWidget->setFixedSize(widgetWidth,
1514  20); // Height is fixed at 20
1515 
1516  // Force update to reflect size changes
1517  m_memoryUsageWidget->updateGeometry();
1518  m_memoryUsageProgressBar->update();
1519  m_memoryUsageLabel->update();
1520 }
1521 
1522 QString MainWindow::formatBytes(qint64 bytes) {
1523  const qint64 KB = 1024;
1524  const qint64 MB = KB * 1024;
1525  const qint64 GB = MB * 1024;
1526  const qint64 TB = GB * 1024;
1527 
1528  if (bytes >= TB) {
1529  return QString("%1 TiB").arg(bytes / static_cast<double>(TB), 0, 'f',
1530  1);
1531  } else if (bytes >= GB) {
1532  return QString("%1 GiB").arg(bytes / static_cast<double>(GB), 0, 'f',
1533  1);
1534  } else if (bytes >= MB) {
1535  return QString("%1 MiB").arg(bytes / static_cast<double>(MB), 0, 'f',
1536  1);
1537  } else if (bytes >= KB) {
1538  return QString("%1 KiB").arg(bytes / static_cast<double>(KB), 0, 'f',
1539  1);
1540  } else {
1541  return QString("%1 B").arg(bytes);
1542  }
1543 }
1544 
1546  m_pluginUIManager->init();
1547 
1548  // Set up dynamic tool bars
1549  QToolBar* glPclToolbar = m_pluginUIManager->glPclToolbar();
1550  QToolBar* mainPluginToolbar = m_pluginUIManager->mainPluginToolbar();
1551  addToolBar(Qt::RightToolBarArea, glPclToolbar);
1552  addToolBar(Qt::RightToolBarArea, mainPluginToolbar);
1553  // Register plugin toolbars with layout manager
1554  m_layoutManager->registerRightSideToolBar(glPclToolbar);
1555  m_layoutManager->registerRightSideToolBar(mainPluginToolbar);
1556 
1557  // Combine all additional plugin toolbars into a single unified toolbar
1558  // But exclude Python plugins - they should be handled separately
1559  QList<QToolBar*> additionalToolbars =
1560  m_pluginUIManager->additionalPluginToolbars();
1561 
1562  // Separate Python plugin toolbars from other toolbars
1563  QList<QToolBar*> pythonPluginToolbars;
1564  QList<QToolBar*> otherPluginToolbars;
1565 
1566  for (QToolBar* toolbar : additionalToolbars) {
1568  pythonPluginToolbars.append(toolbar);
1569  } else {
1570  otherPluginToolbars.append(toolbar);
1571  }
1572  }
1573 
1575  QString("[MainWindow] Found %1 additional plugin toolbars (%2 "
1576  "Python, %3 others)")
1577  .arg(additionalToolbars.size())
1578  .arg(pythonPluginToolbars.size())
1579  .arg(otherPluginToolbars.size()));
1580 
1581  // Handle Python plugin toolbars separately - add them individually
1582  for (QToolBar* pythonToolbar : pythonPluginToolbars) {
1584  QString("[MainWindow] Adding Python plugin toolbar '%1' "
1585  "separately")
1586  .arg(pythonToolbar->objectName()));
1587  addToolBar(Qt::TopToolBarArea, pythonToolbar);
1588  pythonToolbar->setVisible(true);
1589  pythonToolbar->show();
1590  }
1591 
1592  // Check if UnifiedPluginToolbar already exists (to avoid duplicate
1593  // creation)
1594  QToolBar* existingUnifiedToolbar =
1595  findChild<QToolBar*>("UnifiedPluginToolbar");
1596  if (existingUnifiedToolbar) {
1597  // Remove existing toolbar first to avoid duplicates
1598  CVLog::Print("[MainWindow] Removing existing UnifiedPluginToolbar");
1599  removeToolBar(existingUnifiedToolbar);
1600  existingUnifiedToolbar->deleteLater();
1601  }
1602 
1603  if (!otherPluginToolbars.isEmpty()) {
1604  QToolBar* unifiedPluginToolbar =
1605  new QToolBar(tr("MultipleActionsPlugins"), this);
1606  unifiedPluginToolbar->setObjectName("UnifiedPluginToolbar");
1607 
1608  // Collect all actions from additional toolbars, avoiding duplicates
1609  QSet<QAction*> addedActions;
1610 
1611  for (QToolBar* toolbar : otherPluginToolbars) {
1612  QList<QAction*> actions = toolbar->actions();
1614  QString("[MainWindow] Processing toolbar '%1' with %2 "
1615  "actions")
1616  .arg(toolbar->objectName())
1617  .arg(actions.size()));
1618 
1619  for (QAction* action : actions) {
1620  // Only add action if it's not already added to unified toolbar
1621  if (!addedActions.contains(action)) {
1622  unifiedPluginToolbar->addAction(action);
1623  addedActions.insert(action);
1624  }
1625  }
1626 
1627  // Add separator after each toolbar's actions (except after the last
1628  // toolbar)
1629  if (toolbar != otherPluginToolbars.last()) {
1630  unifiedPluginToolbar->addSeparator();
1631  }
1632 
1633  // IMPORTANT: Completely remove and hide the original toolbar
1634  // Set parent to nullptr to prevent it from being restored by
1635  // restoreState()
1636  removeToolBar(toolbar);
1637  toolbar->setParent(nullptr);
1638  toolbar->setVisible(false);
1639  toolbar->hide();
1640  }
1641 
1642  // Only add unified toolbar if it has actions
1643  if (!unifiedPluginToolbar->actions().isEmpty()) {
1644  // Add the unified toolbar to the top
1645  addToolBar(Qt::TopToolBarArea, unifiedPluginToolbar);
1646  unifiedPluginToolbar->setVisible(true);
1647  unifiedPluginToolbar->show();
1648 
1650  QString("[MainWindow] Created UnifiedPluginToolbar "
1651  "with %1 actions from %2 toolbars")
1652  .arg(unifiedPluginToolbar->actions().size())
1653  .arg(otherPluginToolbars.size()));
1654  } else {
1655  // No actions, delete the empty toolbar
1657  "[MainWindow] UnifiedPluginToolbar has no actions, "
1658  "deleting");
1659  delete unifiedPluginToolbar;
1660  }
1661  }
1662 
1663  // Set up dynamic menus
1664  m_ui->menuBar->insertMenu(m_ui->menuDisplay->menuAction(),
1665  m_pluginUIManager->pclAlgorithmMenu());
1666  m_ui->menuBar->insertMenu(m_ui->menuDisplay->menuAction(),
1667  m_pluginUIManager->pluginMenu());
1668 
1669  m_ui->menuToolbars->addAction(
1670  m_pluginUIManager->actionShowMainPluginToolbar());
1671  m_ui->menuToolbars->addAction(
1672  m_pluginUIManager->actionShowPCLAlgorithmToolbar());
1673 
1674  // Apply unified icon size and style to all plugin toolbars
1675  // This includes glPclToolbar, mainPluginToolbar, and UnifiedPluginToolbar
1676  updateAllToolbarIconSizes();
1677 }
1678 
1679 void MainWindow::initDBRoot() {
1680  // db-tree
1681  {
1682  m_ccRoot =
1683  new ccDBRoot(m_ui->dbTreeView, m_ui->propertiesTreeView, this);
1684  connect(m_ccRoot, &ccDBRoot::selectionChanged, this,
1685  &MainWindow::updateUIWithSelection);
1686  connect(m_ccRoot, &ccDBRoot::dbIsEmpty, [&]() {
1687  updateUIWithSelection();
1688  updateMenus();
1689  }); // we don't call updateUI because there's no need to update the
1690  // properties dialog
1691  connect(m_ccRoot, &ccDBRoot::dbIsNotEmptyAnymore, [&]() {
1692  updateUIWithSelection();
1693  updateMenus();
1694  }); // we don't call updateUI because there's no need to update the
1695  // properties dialog
1696  }
1697 
1699  m_ccRoot->updatePropertiesView();
1700 
1701  connect(ecvDisplayTools::TheInstance(),
1703  [=](ccHObject* entity) { m_ccRoot->selectEntity(entity); });
1704  connect(ecvDisplayTools::TheInstance(),
1706  [=](std::unordered_set<int> entities) {
1707  m_ccRoot->selectEntities(entities);
1708  });
1709 }
1710 
1711 #ifdef BUILD_RECONSTRUCTION
1712 void MainWindow::initReconstructions() {
1713  // init reconstructions
1714  if (!m_rcw) {
1715  m_rcw = new cloudViewer::ReconstructionWidget(this);
1716  }
1717 
1718  // Set up dynamic tool bars
1719  QSettings settings;
1720  bool autoShowFlag =
1721  settings.value(ecvPS::AutoShowReconstructionToolBar(), true)
1722  .toBool();
1723  QAction* showToolbarAction = new QAction(tr("Reconstruction"), this);
1724  connect(showToolbarAction, &QAction::toggled, this,
1725  &MainWindow::autoShowReconstructionToolBar);
1726  showToolbarAction->setCheckable(true);
1727  showToolbarAction->setEnabled(true);
1728  // Get screen width for icon size calculation
1729  QScreen* screen = QGuiApplication::primaryScreen();
1730  int screenWidth = screen ? screen->geometry().width() : 1920;
1731 
1732  for (QToolBar* toolbar : m_rcw->getReconstructionToolbars()) {
1733  addToolBar(Qt::TopToolBarArea, toolbar);
1734  connect(showToolbarAction, &QAction::toggled, toolbar,
1735  &QToolBar::setVisible);
1736  }
1737  m_ui->menuToolbars->addAction(showToolbarAction);
1738  showToolbarAction->setChecked(autoShowFlag);
1739 
1740  // Set up dynamic menus
1741  QMenu* rc_menu = new QMenu(tr("Reconstruction"), this);
1742  for (QMenu* menu : m_rcw->getReconstructionMenus()) {
1743  rc_menu->addMenu(menu);
1744  }
1745  m_ui->menuBar->insertMenu(m_ui->menuDisplay->menuAction(), rc_menu);
1746 
1747  // Set docker widget
1748  QDockWidget* logWidget = m_rcw->getLogWidget();
1749  this->addDockWidget(Qt::RightDockWidgetArea, logWidget);
1750 
1751  // Set reconstruction status bar
1752  m_ui->statusBar->insertPermanentWidget(1, m_rcw->getImageStatusBar(), 0);
1753  m_ui->statusBar->insertPermanentWidget(1, m_rcw->getTimerStatusBar(), 0);
1754 }
1755 
1756 void MainWindow::autoShowReconstructionToolBar(bool state) {
1757  QSettings settings;
1758  settings.setValue(ecvPS::AutoShowReconstructionToolBar(), state);
1759 }
1760 #endif
1761 
1762 void MainWindow::toggleActiveWindowAutoPickRotCenter(bool state) {
1765 
1766  // save the option
1767  {
1768  QSettings settings;
1769  settings.setValue(ecvPS::AutoPickRotationCenter(), state);
1770  }
1771  }
1772 }
1773 
1774 void MainWindow::doActionResetRotCenter() {
1777  }
1778 }
1779 
1780 void MainWindow::toggleRotationCenterVisibility(bool state) {
1782  if (state) {
1785  } else {
1787  }
1788 
1789  // save the option
1790  {
1791  QSettings settings;
1792  settings.setValue(ecvPS::AutoShowCenter(), state);
1793  }
1794  }
1795 }
1796 
1797 void MainWindow::doActionToggleCameraOrientationWidget(bool state) {
1798  // ParaView-style Camera Orientation Widget control
1801 
1802  // Save the option
1803  {
1804  QSettings settings;
1805  settings.setValue("CameraOrientationWidget/Visible", state);
1806  }
1807 
1808  CVLog::Print(QString("[MainWindow] Camera Orientation Widget: %1")
1809  .arg(state ? "ON" : "OFF"));
1810  }
1811 }
1812 
1813 void MainWindow::onMousePosChanged(const QPoint& pos) {
1814  if (m_mousePosLabel) {
1815  double x = pos.x();
1816  double y = pos.y();
1817  QString labelText = QString("Location | (%1, %2)")
1818  .arg(QString::number(x))
1819  .arg(QString::number(y));
1820  m_mousePosLabel->setText(labelText);
1821  }
1822 }
1823 
1825  m_ui->actionAutoPickPivot->blockSignals(true);
1826  m_ui->actionAutoPickPivot->setChecked(state);
1827  m_ui->actionAutoPickPivot->blockSignals(false);
1828  toggleActiveWindowAutoPickRotCenter(state);
1829 }
1830 
1834 
1835  // update pop-up menu 'top' icon
1836  if (m_viewModePopupButton)
1837  m_viewModePopupButton->setIcon(
1838  m_ui->actionOrthogonalProjection->icon());
1839  }
1840 }
1841 
1845 
1846  // update pop-up menu 'top' icon
1847  if (m_viewModePopupButton)
1848  m_viewModePopupButton->setIcon(
1849  m_ui->actionPerspectiveProjection->icon());
1850  }
1851 }
1852 
1854  return m_mdiArea ? m_mdiArea->subWindowList().size() : 0;
1855 }
1856 
1857 QMdiSubWindow* MainWindow::getMDISubWindow(QWidget* win) {
1858  QList<QMdiSubWindow*> subWindowList = m_mdiArea->subWindowList();
1859  for (int i = 0; i < subWindowList.size(); ++i) {
1860  if (subWindowList[i]->widget() == win) return subWindowList[i];
1861  }
1862 
1863  // not found!
1864  return nullptr;
1865 }
1866 
1868  QList<QMdiSubWindow*> windows = m_mdiArea->subWindowList();
1869  if (!windows.isEmpty()) {
1870  // Dynamic Separator
1871  QAction* separator = new QAction(this);
1872  separator->setSeparator(true);
1873 
1874  int i = 0;
1875 
1876  for (QMdiSubWindow* window : windows) {
1877  QWidget* child = window->widget();
1878 
1879  QString text = QString("&%1 %2").arg(++i).arg(child->windowTitle());
1880 
1881  // connect(action, &QAction::triggered, this, [=]() {
1882  // setActiveSubWindow(window);
1883  // });
1884  }
1885  }
1886 }
1887 
1889  if (!m_viewModePopupButton) return;
1890 
1891  // update the view mode pop-up 'top' icon
1893  bool perspectiveEnabled = ecvDisplayTools::GetPerspectiveState();
1894 
1895  QAction* currentModeAction = nullptr;
1896  if (!perspectiveEnabled) {
1897  currentModeAction = m_ui->actionOrthogonalProjection;
1898  } else {
1899  currentModeAction = m_ui->actionPerspectiveProjection;
1900  }
1901 
1902  assert(currentModeAction);
1903  m_viewModePopupButton->setIcon(currentModeAction->icon());
1904  m_viewModePopupButton->setEnabled(true);
1905  } else {
1906  m_viewModePopupButton->setIcon(QIcon());
1907  m_viewModePopupButton->setEnabled(false);
1908  }
1909 }
1910 
1911 void MainWindow::addWidgetToQMdiArea(QWidget* viewWidget) {
1912  if (viewWidget && !MainWindow::GetRenderWindow(viewWidget->windowTitle())) {
1913  viewWidget->showMaximized();
1914  m_mdiArea->addSubWindow(viewWidget);
1915  } else {
1916  m_mdiArea->setActiveSubWindow(getMDISubWindow(viewWidget));
1917  }
1918 }
1919 
1921  return TheInstance()->getActiveWindow();
1922 }
1923 
1924 void MainWindow::GetRenderWindows(std::vector<QWidget*>& glWindows) {
1925  const QList<QMdiSubWindow*> windows =
1926  TheInstance()->m_mdiArea->subWindowList();
1927 
1928  if (windows.empty()) return;
1929 
1930  glWindows.clear();
1931  glWindows.reserve(windows.size());
1932 
1933  for (QMdiSubWindow* window : windows) {
1934  glWindows.push_back(window->widget());
1935  }
1936 }
1937 
1938 QWidget* MainWindow::GetRenderWindow(const QString& title) {
1939  const QList<QMdiSubWindow*> windows =
1940  TheInstance()->m_mdiArea->subWindowList();
1941 
1942  if (windows.empty()) return nullptr;
1943 
1944  for (QMdiSubWindow* window : windows) {
1945  QWidget* win = window->widget();
1946  if (win->windowTitle() == title) return win;
1947  }
1948 
1949  return nullptr;
1950 }
1951 
1952 QWidget* MainWindow::getWindow(int index) const {
1953  QList<QMdiSubWindow*> subWindowList = m_mdiArea->subWindowList();
1954  if (index >= 0 && index < subWindowList.size()) {
1955  QWidget* win = subWindowList[index]->widget();
1956  assert(win);
1957  return win;
1958  } else {
1959  assert(false);
1960  return nullptr;
1961  }
1962 }
1963 
1965  if (!m_mdiArea) {
1966  return nullptr;
1967  }
1968 
1969  QMdiSubWindow* activeSubWindow = m_mdiArea->activeSubWindow();
1970  if (activeSubWindow) {
1971  return activeSubWindow->widget();
1972  } else {
1973  QList<QMdiSubWindow*> subWindowList = m_mdiArea->subWindowList();
1974  if (!subWindowList.isEmpty()) {
1975  return subWindowList[0]->widget();
1976  }
1977  }
1978 
1979  return nullptr;
1980 }
1981 
1982 void MainWindow::ChangeStyle(const QString& qssFile) {
1983  QString fileName = qssFile;
1984 
1985  if (!fileName.isEmpty()) {
1986  QFile file(fileName);
1987 
1988  if (file.open(QFile::ReadOnly)) {
1989  QString str = file.readAll();
1990  static QString qssStyle;
1991 
1992  if (qssStyle == str) {
1993  return;
1994  }
1995 
1996  qssStyle = str;
1997  QString paletteColor = str.mid(20, 7);
1998  ecvApp->setPalette(QPalette(QColor(paletteColor)));
1999  ecvApp->setStyleSheet(qssStyle);
2000  }
2001  } else {
2002  // default
2003  ecvApp->setPalette(QPalette(QColor(240, 240, 240, 255)));
2004  ecvApp->setStyleSheet(QString());
2005  }
2006 }
2007 
2009  this->m_uiManager = uiManager;
2010 }
2011 
2012 void MainWindow::toggleVisualDebugTraces() {
2015  ecvDisplayTools::RedrawDisplay(true, false);
2016  }
2017 }
2018 
2020  // if the console is hidden, we automatically display it!
2021  if (m_ui->consoleDock && m_ui->consoleDock->isHidden()) {
2022  m_ui->consoleDock->show();
2023  QApplication::processEvents();
2024  }
2025 }
2026 
2028  return (m_ccRoot ? m_ccRoot->getRootEntity() : nullptr);
2029 }
2030 
2031 void MainWindow::doEnableQtWarnings(bool state) {
2033 }
2034 
2035 /************** STATIC METHODS ******************/
2036 
2038  if (!s_instance) s_instance = new MainWindow();
2039  return s_instance;
2040 }
2041 
2043  delete s_instance;
2044  s_instance = nullptr;
2045 }
2046 
2047 void MainWindow::on3DViewActivated(QMdiSubWindow* mdiWin) {
2048  if (!mdiWin) {
2049  return;
2050  }
2051 
2052  QWidget* screen = mdiWin->widget();
2053  if (screen) {
2054  m_ui->actionExclusiveFullScreen->blockSignals(true);
2055  m_ui->actionExclusiveFullScreen->setChecked(
2057  m_ui->actionExclusiveFullScreen->blockSignals(false);
2058  }
2059  m_ui->actionExclusiveFullScreen->setEnabled(screen != nullptr);
2060 }
2061 
2063  QString message, ConsoleMessageLevel level /*=STD_CONSOLE_MESSAGE*/) {
2064  switch (level) {
2065  case STD_CONSOLE_MESSAGE:
2066  ecvConsole::Print(message);
2067  break;
2068  case WRN_CONSOLE_MESSAGE:
2069  ecvConsole::Warning(message);
2070  break;
2071  case ERR_CONSOLE_MESSAGE:
2072  ecvConsole::Error(message);
2073  break;
2074  }
2075 }
2076 
2079 }
2080 
2081 // Open point cloud
2082 void MainWindow::getFileFilltersAndHistory(QStringList& fileFilters,
2083  QString& currentOpenDlgFilter) {
2084  currentOpenDlgFilter =
2088  .toString();
2089 
2090  // Add all available file I/O filters (with import capabilities)
2091  fileFilters.append(s_allFilesFilter);
2092  bool defaultFilterFound = false;
2093  {
2094  for (const FileIOFilter::Shared& filter : FileIOFilter::GetFilters()) {
2095  if (filter->importSupported()) {
2096  const QStringList fileFilterList = filter->getFileFilters(true);
2097 
2098  for (const QString& fileFilter : fileFilterList) {
2099  fileFilters.append(fileFilter);
2100  // is it the (last) default filter?
2101  if (!defaultFilterFound &&
2102  (currentOpenDlgFilter == fileFilter)) {
2103  defaultFilterFound = true;
2104  }
2105  }
2106  }
2107  }
2108  }
2109 
2110  // default filter is still valid?
2111  if (!defaultFilterFound) currentOpenDlgFilter = s_allFilesFilter;
2112 }
2113 
2114 void MainWindow::doActionOpenFile() {
2115  // persistent settings
2116  QString currentPath =
2119  .toString();
2120 
2121  QString currentOpenDlgFilter;
2122  QStringList fileFilters;
2123  getFileFilltersAndHistory(fileFilters, currentOpenDlgFilter);
2124 
2125  // file choosing dialog
2126  QStringList selectedFiles = QFileDialog::getOpenFileNames(
2127  this, tr("Open file(s)"), currentPath,
2128  fileFilters.join(s_fileFilterSeparator), &currentOpenDlgFilter,
2130 
2131  if (selectedFiles.isEmpty()) return;
2132 
2133  // persistent save last loading parameters
2134  currentPath = QFileInfo(selectedFiles[0]).absolutePath();
2136  currentPath);
2138  currentOpenDlgFilter);
2139 
2140  // this way FileIOFilter will try to guess the file type automatically!
2141  if (currentOpenDlgFilter == s_allFilesFilter) currentOpenDlgFilter.clear();
2142 
2143  // load files
2144  addToDB(selectedFiles, currentOpenDlgFilter);
2145 }
2146 
2147 void MainWindow::addToDBAuto(const QStringList& filenames,
2148  bool displayDialog /* = true*/) {
2149  addToDB(filenames, QString(), displayDialog);
2150 }
2151 
2152 void MainWindow::addToDB(const QStringList& filenames,
2153  QString fileFilter /*=QString()*/,
2154  bool displayDialog /* = true*/) {
2155  // to use the same 'global shift' for multiple files
2156  CCVector3d loadCoordinatesShift(0, 0, 0);
2157  bool loadCoordinatesTransEnabled = false;
2158 
2159  FileIOFilter::LoadParameters parameters;
2160  {
2161  parameters.alwaysDisplayLoadDialog = displayDialog;
2162  parameters.shiftHandlingMode =
2164  parameters.coordinatesShift = &loadCoordinatesShift;
2165  parameters.coordinatesShiftEnabled = &loadCoordinatesTransEnabled;
2166  parameters.parentWidget = this;
2167  }
2168 
2169  // the same for 'addToDB' (if the first one is not supported, or if the
2170  // scale remains too big)
2171  CCVector3d addCoordinatesShift(0, 0, 0);
2172 
2173  const ecvOptions& options = ecvOptions::Instance();
2175 
2176  for (const QString& filename : filenames) {
2178  ccHObject* newGroup = FileIOFilter::LoadFromFile(filename, parameters,
2179  result, fileFilter);
2180 
2181  if (newGroup) {
2182  if (!options.normalsDisplayedByDefault) {
2183  // disable the normals on all loaded clouds!
2184  ccHObject::Container clouds;
2185  newGroup->filterChildren(clouds, true, CV_TYPES::POINT_CLOUD);
2186  for (ccHObject* cloud : clouds) {
2187  if (cloud) {
2188  static_cast<ccGenericPointCloud*>(cloud)->showNormals(
2189  false);
2190  }
2191  }
2192  }
2193 
2194  addToDB(newGroup, true, true, false);
2195 
2196  m_recentFiles->addFilePath(filename);
2197  }
2198 
2200  // stop importing the file if the user has cancelled the current
2201  // process!
2202  break;
2203  }
2204  }
2205 
2206  statusBar()->showMessage(tr("%1 file(s) loaded").arg(filenames.size()),
2207  2000);
2208 }
2209 
2211  bool updateZoom /*=false*/,
2212  bool autoExpandDBTree /*=true*/,
2213  bool checkDimensions /*=false*/,
2214  bool autoRedraw /*=true*/) {
2215  // let's check that the new entity is not too big nor too far from scene
2216  // center!
2217  if (checkDimensions) {
2218  // get entity bounding box
2219  ccBBox bBox = obj->getBB_recursive();
2220 
2221  CCVector3 center = bBox.getCenter();
2222  PointCoordinateType diag = bBox.getDiagNorm();
2223 
2224  CCVector3d P = CCVector3d::fromArray(center.u);
2225  CCVector3d Pshift(0, 0, 0);
2226  double scale = 1.0;
2227  bool preserveCoordinateShift = true;
2228  // here we must test that coordinates are not too big whatever the case
2229  // because OpenGL really doesn't like big ones (even if we work with
2230  // GLdoubles :( ).
2233  Pshift, &preserveCoordinateShift, &scale)) {
2234  bool needRescale = (scale != 1.0);
2235  bool needShift = (Pshift.norm2() > 0);
2236 
2237  if (needRescale || needShift) {
2238  ccGLMatrix mat;
2239  mat.toIdentity();
2240  mat.data()[0] = mat.data()[5] = mat.data()[10] =
2241  static_cast<float>(scale);
2242  mat.setTranslation(Pshift);
2245  tr("Entity '%1' has been translated: (%2,%3,%4) and "
2246  "rescaled of a factor %5 [original position will be "
2247  "restored when saving]")
2248  .arg(obj->getName())
2249  .arg(Pshift.x, 0, 'f', 2)
2250  .arg(Pshift.y, 0, 'f', 2)
2251  .arg(Pshift.z, 0, 'f', 2)
2252  .arg(scale, 0, 'f', 6));
2253  }
2254 
2255  // update 'global shift' and 'global scale' for ALL clouds
2256  // recursively
2257  if (preserveCoordinateShift) {
2258  // FIXME: why don't we do that all the time by the way?!
2259  ccHObject::Container children;
2260  children.push_back(obj);
2261  while (!children.empty()) {
2262  ccHObject* child = children.back();
2263  children.pop_back();
2264 
2265  if (child->isKindOf(CV_TYPES::POINT_CLOUD)) {
2266  ccGenericPointCloud* pc =
2268  pc->setGlobalShift(pc->getGlobalShift() + Pshift);
2269  pc->setGlobalScale(pc->getGlobalScale() * scale);
2270  }
2271 
2272  for (unsigned i = 0; i < child->getChildrenNumber(); ++i) {
2273  children.push_back(child->getChild(i));
2274  }
2275  }
2276  }
2277  }
2278  }
2279 
2280  // add object to DB root
2281  if (m_ccRoot) {
2282  // force a 'global zoom' if the DB was emtpy!
2283  if (!m_ccRoot->getRootEntity() ||
2284  m_ccRoot->getRootEntity()->getChildrenNumber() == 0) {
2285  updateZoom = true;
2286  }
2287 
2288  // avoid rendering other object this time
2290  // redraw new added obj
2292 
2293  ccHObject::Container childs;
2294  obj->filterChildren(childs, true, CV_TYPES::IMAGE);
2295  if (!childs.empty()) {
2296  updateZoom = false;
2297  autoRedraw = true;
2298  }
2299 
2300  m_ccRoot->addElement(obj, autoExpandDBTree);
2301  } else {
2303  tr("[MainWindow::addToDB] Internal error: no associated db?!"));
2304  assert(false);
2305  }
2306 
2307  // eventually we update the corresponding display
2308  if (updateZoom) {
2309  ecvDisplayTools::ZoomGlobal(); // automatically calls redrawDisplay()
2310  } else if (autoRedraw) {
2311  refreshObject(obj);
2312  }
2313 
2314 #ifdef USE_PCL_BACKEND
2315  // ParaView-style: When new entities are added, refresh Data Producer combo
2316  // but do NOT disable selection tools (ParaView doesn't do this)
2317  // Reference: ParaView's pqSelectionManager::onSourceAdded() simply connects
2318  // to the new source's selectionChanged signal without disabling selection
2319  if (m_findDataDock) {
2320  // Use QTimer to defer the refresh to ensure VTK actors are fully
2321  // created
2322  QTimer::singleShot(100, this, [this]() {
2323  if (m_findDataDock) {
2324  m_findDataDock->refreshDataProducers();
2326  "[MainWindow::addToDB] Data Producer combo refreshed "
2327  "after "
2328  "adding new entity");
2329  }
2330  });
2331  }
2332 
2333 #endif
2334 }
2335 
2336 void MainWindow::doActionEditCamera() {
2337  // current active MDI area
2338  QMdiSubWindow* qWin = m_mdiArea->activeSubWindow();
2339  if (!qWin) return;
2340 
2341 #ifdef USE_PCL_BACKEND
2342  if (!m_cpeDlg) {
2343  m_cpeDlg = new ecvCameraParamEditDlg(qWin, m_pickingHub);
2344  EditCameraTool* tool =
2345  new EditCameraTool(ecvDisplayTools::GetVisualizer3D());
2346  m_cpeDlg->setCameraTool(tool);
2347 
2348  connect(m_mdiArea, &QMdiArea::subWindowActivated, m_cpeDlg,
2349  static_cast<void (ecvCameraParamEditDlg::*)(QMdiSubWindow*)>(
2351 
2352  registerOverlayDialog(m_cpeDlg, Qt::BottomLeftCorner);
2353  }
2354 
2355  m_cpeDlg->linkWith(qWin);
2356  m_cpeDlg->start();
2357 #else
2359  "[MainWindow] please use pcl as backend and then try again!");
2360  return;
2361 #endif
2362 
2364 }
2365 
2366 static unsigned s_viewportIndex = 0;
2368  QWidget* win = getActiveWindow();
2369  if (!win) return;
2370 
2371  cc2DViewportObject* viewportObject = new cc2DViewportObject(
2372  QString("Viewport #%1").arg(++s_viewportIndex));
2374 
2375  addToDB(viewportObject);
2376 }
2377 
2378 void MainWindow::toggleLockRotationAxis() {
2379  QMainWindow* win = ecvDisplayTools::GetMainWindow();
2380  if (win) {
2381  bool wasLocked = ecvDisplayTools::IsRotationAxisLocked();
2382  bool isLocked = !wasLocked;
2383 
2384  static CCVector3d s_lastAxis(0.0, 0.0, 1.0);
2385  if (isLocked) {
2386  ccAskThreeDoubleValuesDlg axisDlg(
2387  "x", "y", "z", -1.0e12, 1.0e12, s_lastAxis.x, s_lastAxis.y,
2388  s_lastAxis.z, 4, tr("Lock rotation axis"), this);
2389  if (axisDlg.buttonBox->button(QDialogButtonBox::Ok))
2390  axisDlg.buttonBox->button(QDialogButtonBox::Ok)->setFocus();
2391  if (!axisDlg.exec()) return;
2392  s_lastAxis.x = axisDlg.doubleSpinBox1->value();
2393  s_lastAxis.y = axisDlg.doubleSpinBox2->value();
2394  s_lastAxis.z = axisDlg.doubleSpinBox3->value();
2395  }
2396  ecvDisplayTools::LockRotationAxis(isLocked, s_lastAxis);
2397 
2398  m_ui->actionLockRotationAxis->blockSignals(true);
2399  m_ui->actionLockRotationAxis->setChecked(isLocked);
2400  m_ui->actionLockRotationAxis->blockSignals(false);
2401 
2402  if (isLocked) {
2404  tr("[ROTATION LOCKED]"),
2405  ecvDisplayTools::UPPER_CENTER_MESSAGE, false, 24 * 3600,
2407  } else {
2409  QString(), ecvDisplayTools::UPPER_CENTER_MESSAGE, false, 0,
2411  }
2413  ecvDisplayTools::RedrawDisplay(true, false);
2414  }
2415 }
2416 
2417 void MainWindow::doActionAnimation() {
2418  // current active MDI area
2419  QMdiSubWindow* qWin = m_mdiArea->activeSubWindow();
2420  if (!qWin) return;
2421 
2422  if (!m_animationDlg) {
2423  m_animationDlg = new ecvAnimationParamDlg(qWin, this, m_pickingHub);
2424 
2425  connect(m_mdiArea, &QMdiArea::subWindowActivated, m_animationDlg,
2426  static_cast<void (ecvAnimationParamDlg::*)(QMdiSubWindow*)>(
2428 
2429  registerOverlayDialog(m_animationDlg, Qt::BottomLeftCorner);
2430  }
2431 
2432  m_animationDlg->linkWith(qWin);
2433  m_animationDlg->start();
2435 }
2436 
2437 void MainWindow::doActionScreenShot() {
2438  QWidget* win = getActiveWindow();
2439  if (!win) return;
2440 
2441  ccRenderToFileDlg rtfDlg(static_cast<unsigned>(win->width()),
2442  static_cast<unsigned>(win->height()), this);
2443 
2444  if (rtfDlg.exec()) {
2445  QApplication::processEvents();
2446  ecvDisplayTools::RenderToFile(rtfDlg.getFilename(), rtfDlg.getZoom(),
2447  rtfDlg.dontScalePoints(),
2448  rtfDlg.renderOverlayItems());
2449  }
2450 }
2451 
2452 void MainWindow::doActionToggleOrientationMarker(bool state) {
2454 }
2455 
2456 void MainWindow::doActionSaveFile() {
2457  if (!haveSelection()) return;
2458 
2459  ccHObject clouds(tr("clouds"));
2460  ccHObject meshes(tr("meshes"));
2461  ccHObject polylines(tr("polylines"));
2462  ccHObject other(tr("other"));
2463  ccHObject otherSerializable(tr("serializable"));
2464  ccHObject::Container entitiesToDispatch;
2465  entitiesToDispatch.insert(entitiesToDispatch.begin(),
2466  m_selectedEntities.begin(),
2467  m_selectedEntities.end());
2468  ccHObject entitiesToSave;
2469  while (!entitiesToDispatch.empty()) {
2470  ccHObject* child = entitiesToDispatch.back();
2471  entitiesToDispatch.pop_back();
2472 
2473  if (child->isA(CV_TYPES::HIERARCHY_OBJECT)) {
2474  for (unsigned j = 0; j < child->getChildrenNumber(); ++j)
2475  entitiesToDispatch.push_back(child->getChild(j));
2476  } else {
2477  // we put the entity in the container corresponding to its type
2478  ccHObject* dest = nullptr;
2479  if (child->isA(CV_TYPES::POINT_CLOUD))
2480  dest = &clouds;
2481  else if (child->isKindOf(CV_TYPES::MESH))
2482  dest = &meshes;
2483  else if (child->isKindOf(CV_TYPES::POLY_LINE))
2484  dest = &polylines;
2485  else if (child->isSerializable())
2486  dest = &otherSerializable;
2487  else
2488  dest = &other;
2489 
2490  assert(dest);
2491 
2492  // we don't want double insertions if the user has clicked both the
2493  // father and child
2494  if (!dest->find(child->getUniqueID())) {
2495  dest->addChild(child, ccHObject::DP_NONE);
2496  entitiesToSave.addChild(child, ccHObject::DP_NONE);
2497  }
2498  }
2499  }
2500 
2501  bool hasCloud = (clouds.getChildrenNumber() != 0);
2502  bool hasMesh = (meshes.getChildrenNumber() != 0);
2503  bool hasPolylines = (polylines.getChildrenNumber() != 0);
2504  bool hasSerializable = (otherSerializable.getChildrenNumber() != 0);
2505  bool hasOther = (other.getChildrenNumber() != 0);
2506 
2507  int stdSaveTypes = static_cast<int>(hasCloud) + static_cast<int>(hasMesh) +
2508  static_cast<int>(hasPolylines) +
2509  static_cast<int>(hasSerializable);
2510  if (stdSaveTypes == 0) {
2511  ecvConsole::Error(tr("Can't save selected entity(ies) this way!"));
2512  return;
2513  }
2514 
2515  // we set up the right file filters, depending on the selected
2516  // entities type (cloud, mesh, etc.).
2517  QStringList fileFilters;
2518  {
2519  for (const FileIOFilter::Shared& filter : FileIOFilter::GetFilters()) {
2520  bool atLeastOneExclusive = false;
2521 
2522  // does this filter can export one or several clouds?
2523  bool canExportClouds = true;
2524  if (hasCloud) {
2525  bool isExclusive = true;
2526  bool multiple = false;
2527  canExportClouds =
2528  (filter->canSave(CV_TYPES::POINT_CLOUD, multiple,
2529  isExclusive) &&
2530  (multiple || clouds.getChildrenNumber() == 1));
2531  atLeastOneExclusive |= isExclusive;
2532  }
2533 
2534  // does this filter can export one or several meshes?
2535  bool canExportMeshes = true;
2536  if (hasMesh) {
2537  bool isExclusive = true;
2538  bool multiple = false;
2539  canExportMeshes =
2540  (filter->canSave(CV_TYPES::MESH, multiple,
2541  isExclusive) &&
2542  (multiple || meshes.getChildrenNumber() == 1));
2543  atLeastOneExclusive |= isExclusive;
2544  }
2545 
2546  // does this filter can export one or several polylines?
2547  bool canExportPolylines = true;
2548  if (hasPolylines) {
2549  bool isExclusive = true;
2550  bool multiple = false;
2551  canExportPolylines =
2552  (filter->canSave(CV_TYPES::POLY_LINE, multiple,
2553  isExclusive) &&
2554  (multiple || polylines.getChildrenNumber() == 1));
2555  atLeastOneExclusive |= isExclusive;
2556  }
2557 
2558  // does this filter can export one or several images?
2559  bool canExportImages = true;
2560 
2561  // does this filter can export one or several other serializable
2562  // entities?
2563  bool canExportSerializables = true;
2564  if (hasSerializable) {
2565  // check if all entities have the same type
2566  {
2567  CV_CLASS_ENUM firstClassID =
2568  otherSerializable.getChild(0)->getUniqueID();
2569  for (unsigned j = 1;
2570  j < otherSerializable.getChildrenNumber(); ++j) {
2571  if (otherSerializable.getChild(j)->getUniqueID() !=
2572  firstClassID) {
2573  // we add a virtual second 'stdSaveType' so as to
2574  // properly handle exlusivity
2575  ++stdSaveTypes;
2576  break;
2577  }
2578  }
2579  }
2580 
2581  for (unsigned j = 0; j < otherSerializable.getChildrenNumber();
2582  ++j) {
2583  ccHObject* child = otherSerializable.getChild(j);
2584  bool isExclusive = true;
2585  bool multiple = false;
2586  canExportSerializables &=
2587  (filter->canSave(child->getClassID(), multiple,
2588  isExclusive) &&
2589  (multiple ||
2590  otherSerializable.getChildrenNumber() == 1));
2591  atLeastOneExclusive |= isExclusive;
2592  }
2593  }
2594 
2595  bool useThisFilter = canExportClouds && canExportMeshes &&
2596  canExportImages && canExportPolylines &&
2597  canExportSerializables &&
2598  (!atLeastOneExclusive || stdSaveTypes == 1);
2599 
2600  if (useThisFilter) {
2601  QStringList ff = filter->getFileFilters(false);
2602  for (int j = 0; j < ff.size(); ++j) fileFilters.append(ff[j]);
2603  }
2604  }
2605  }
2606 
2607  // persistent settings
2608  // default filter
2609  QString selectedFilter = fileFilters.first();
2610  if (hasCloud)
2611  selectedFilter =
2614  selectedFilter)
2615  .toString();
2616  else if (hasMesh)
2617  selectedFilter =
2620  selectedFilter)
2621  .toString();
2622  else if (hasPolylines)
2623  selectedFilter =
2626  selectedFilter)
2627  .toString();
2628 
2629  // default output path (+ filename)
2630  QString currentPath =
2633  .toString();
2634  QString fullPathName = currentPath;
2635 
2636  if (haveOneSelection()) {
2637  // hierarchy objects have generally as name: 'filename.ext (fullpath)'
2638  // so we must only take the first part! (otherwise this type of name
2639  // with a path inside perturbs the QFileDialog a lot ;))
2640  QString defaultFileName(m_selectedEntities.front()->getName());
2641  if (m_selectedEntities.front()->isA(CV_TYPES::HIERARCHY_OBJECT)) {
2642  QStringList parts =
2643  defaultFileName.split(' ', QtCompat::SkipEmptyParts);
2644  if (!parts.empty()) {
2645  defaultFileName = parts[0];
2646  }
2647  }
2648 
2649  // we remove the extension
2650  defaultFileName = QFileInfo(defaultFileName).baseName();
2651 
2652  if (!IsValidFileName(defaultFileName)) {
2654  tr("[I/O] First entity's name would make an invalid "
2655  "filename! Can't use it..."));
2656  defaultFileName = tr("project");
2657  }
2658 
2659  fullPathName += QString("/") + defaultFileName;
2660  }
2661 
2662  // ask the user for the output filename
2663  QString selectedFilename = QFileDialog::getSaveFileName(
2664  this, tr("Save file"), fullPathName,
2665  fileFilters.join(s_fileFilterSeparator), &selectedFilter,
2667 
2668  if (selectedFilename.isEmpty()) {
2669  // process cancelled by the user
2670  return;
2671  }
2672 
2673  // ignored items
2674  if (hasOther) {
2676  tr("[I/O] The following selected entities won't be saved:"));
2677  for (unsigned i = 0; i < other.getChildrenNumber(); ++i) {
2679  tr("\t- %1s").arg(other.getChild(i)->getName()));
2680  }
2681  }
2682 
2684  FileIOFilter::SaveParameters parameters;
2685  {
2686  parameters.alwaysDisplaySaveDialog = true;
2687  parameters.parentWidget = this;
2688  }
2689 
2690  // specific case: BIN format
2691  if (selectedFilter == BinFilter::GetFileFilter()) {
2692  if (haveOneSelection()) {
2693  result = FileIOFilter::SaveToFile(m_selectedEntities.front(),
2694  selectedFilename, parameters,
2695  selectedFilter);
2696  } else {
2697  // we'll regroup all selected entities in a temporary group
2698  ccHObject tempContainer;
2699  ConvertToGroup(m_selectedEntities, tempContainer,
2701  if (tempContainer.getChildrenNumber()) {
2702  result = FileIOFilter::SaveToFile(&tempContainer,
2703  selectedFilename, parameters,
2704  selectedFilter);
2705  } else {
2707  tr("[I/O] None of the selected entities can be saved "
2708  "this way..."));
2710  }
2711  }
2712 
2713  // display the compatible version info for BIN files
2714  if (result == CC_FERR_NO_ERROR) {
2715  short fileVersion = BinFilter::GetLastSavedFileVersion();
2716  if (fileVersion != 0) {
2717  QString minVersion =
2719  fileVersion);
2720  CVLog::Print(tr("This file can be loaded by ACloudViewer "
2721  "version %1 and later")
2722  .arg(minVersion));
2723  }
2724  }
2725  } else if (entitiesToSave.getChildrenNumber() != 0) {
2726  result = FileIOFilter::SaveToFile(entitiesToSave.getChildrenNumber() > 1
2727  ? &entitiesToSave
2728  : entitiesToSave.getChild(0),
2729  selectedFilename, parameters,
2730  selectedFilter);
2731 
2732  if (result == CC_FERR_NO_ERROR && m_ccRoot) {
2733  m_ccRoot->unselectAllEntities();
2734  }
2735  }
2736 
2737  // update default filters
2738  if (hasCloud)
2741  selectedFilter);
2742  if (hasMesh)
2745  selectedFilter);
2746  if (hasPolylines)
2749  selectedFilter);
2750 
2751  // we update current file path
2752  currentPath = QFileInfo(selectedFilename).absolutePath();
2754  currentPath);
2755 }
2756 
2757 void MainWindow::doActionSaveProject() {
2758  if (!m_ccRoot || !m_ccRoot->getRootEntity()) {
2759  assert(false);
2760  return;
2761  }
2762 
2763  ccHObject* rootEntity = m_ccRoot->getRootEntity();
2764  if (rootEntity->getChildrenNumber() == 0) {
2765  return;
2766  }
2767 
2768  // default output path (+ filename)
2769  QSettings settings;
2770  settings.beginGroup(ecvPS::SaveFile());
2771  QString currentPath =
2773  .toString();
2774  CVLog::PrintDebug(currentPath);
2775  QString fullPathName = currentPath;
2776 
2777  static QString s_previousProjectName{"project"};
2778  QString defaultFileName = s_previousProjectName;
2779  if (rootEntity->getChildrenNumber() == 1) {
2780  // If there's only on top entity, we can try to use its name as the
2781  // project name.
2782  ccHObject* topEntity = rootEntity->getChild(0);
2783  defaultFileName = topEntity->getName();
2784  if (topEntity->isA(CV_TYPES::HIERARCHY_OBJECT)) {
2785  // Hierarchy objects have generally as name: 'filename.ext
2786  // (fullpath)' so we must only take the first part! (otherwise this
2787  // type of name with a path inside disturbs the QFileDialog a lot
2788  // ;))
2789  QStringList parts =
2790  defaultFileName.split(' ', QtCompat::SkipEmptyParts);
2791  if (!parts.empty()) {
2792  defaultFileName = parts[0];
2793  }
2794  }
2795 
2796  // we remove the extension
2797  defaultFileName = QFileInfo(defaultFileName).completeBaseName();
2798 
2799  if (!IsValidFileName(defaultFileName)) {
2801  tr("[I/O] Top entity's name would make an invalid "
2802  "filename! Can't use it..."));
2803  defaultFileName = "project";
2804  }
2805  }
2806  fullPathName += QString("/") + defaultFileName;
2807 
2808  QString binFilter = BinFilter::GetFileFilter();
2809 
2810  // ask the user for the output filename
2811  QString selectedFilename = QFileDialog::getSaveFileName(
2812  this, tr("Save file"), fullPathName, binFilter, &binFilter,
2814 
2815  if (selectedFilename.isEmpty()) {
2816  // process cancelled by the user
2817  return;
2818  }
2819 
2820  FileIOFilter::SaveParameters parameters;
2821  {
2822  parameters.alwaysDisplaySaveDialog = true;
2823  parameters.parentWidget = this;
2824  }
2825 
2827  rootEntity->getChildrenNumber() == 1 ? rootEntity->getChild(0)
2828  : rootEntity,
2829  selectedFilename, parameters, binFilter);
2830 
2831  // display the compatible version info for BIN files
2832  if (result == CC_FERR_NO_ERROR) {
2833  short fileVersion = BinFilter::GetLastSavedFileVersion();
2834  if (fileVersion != 0) {
2835  QString minVersion =
2837  CVLog::Print(tr("This file can be loaded by ACloudViewer version "
2838  "%1 and later")
2839  .arg(minVersion));
2840  }
2841  }
2842 
2843  // we update the current 'save' path
2844  QFileInfo fi(selectedFilename);
2845  s_previousProjectName = fi.fileName();
2846  currentPath = fi.absolutePath();
2847  settings.setValue(ecvPS::CurrentPath(), currentPath);
2848  settings.endGroup();
2849 }
2850 
2851 void MainWindow::doActionApplyTransformation() {
2852  ccApplyTransformationDlg dlg(this);
2853  if (!dlg.exec()) return;
2854 
2855  ccGLMatrixd transMat = dlg.getTransformation();
2856  applyTransformation(transMat);
2857 }
2858 
2859 void MainWindow::applyTransformation(const ccGLMatrixd& mat) {
2860  // if the transformation is partly converted to global shift/scale
2861  bool updateGlobalShiftAndScale = false;
2862  double scaleChange = 1.0;
2863  CCVector3d shiftChange(0, 0, 0);
2864  ccGLMatrixd transMat = mat;
2865 
2866  // we must backup 'm_selectedEntities' as removeObjectTemporarilyFromDBTree
2867  // can modify it!
2868  ccHObject::Container selectedEntities = getSelectedEntities();
2869 
2870  bool firstCloud = true;
2871 
2872  for (ccHObject* entity : selectedEntities) // warning, getSelectedEntites
2873  // may change during this loop!
2874  {
2875  // we don't test primitives (it's always ok while the 'vertices lock'
2876  // test would fail)
2877  if (!entity->isKindOf(CV_TYPES::PRIMITIVE)) {
2878  // specific test for locked vertices
2879  bool lockedVertices;
2881  entity, &lockedVertices);
2882  if (cloud) {
2883  if (lockedVertices) {
2885  haveOneSelection());
2886  continue;
2887  }
2888 
2889  if (firstCloud) {
2890  // test if the translated cloud was already "too big"
2891  //(in which case we won't bother the user about the fact
2892  // that the transformed cloud will be too big...)
2893  ccBBox localBBox = entity->getOwnBB();
2894  CCVector3d Pl =
2895  CCVector3d::fromArray(localBBox.minCorner().u);
2896  double Dl = localBBox.getDiagNormd();
2897 
2898  // the cloud was alright
2901  // test if the translated cloud is not "too big" (in
2902  // local coordinate space)
2903  ccBBox rotatedBox = entity->getOwnBB() * transMat;
2904  double Dl2 = rotatedBox.getDiagNorm();
2905  CCVector3d Pl2 =
2906  CCVector3d::fromArray(rotatedBox.getCenter().u);
2907 
2908  bool needShift = ecvGlobalShiftManager::NeedShift(Pl2);
2909  bool needRescale =
2911 
2912  if (needShift || needRescale) {
2913  // existing shift information
2914  CCVector3d globalShift = cloud->getGlobalShift();
2915  double globalScale = cloud->getGlobalScale();
2916 
2917  // we compute the transformation matrix in the
2918  // global coordinate space
2919  ccGLMatrixd globalTransMat = transMat;
2920  globalTransMat.scale(1.0 / globalScale);
2921  globalTransMat.setTranslation(
2922  globalTransMat.getTranslationAsVec3D() -
2923  globalShift);
2924  // and we apply it to the cloud bounding-box
2925  ccBBox rotatedBox =
2926  cloud->getOwnBB() * globalTransMat;
2927  double Dg = rotatedBox.getDiagNorm();
2929  rotatedBox.getCenter().u);
2930 
2931  // ask the user the right values!
2932  ecvShiftAndScaleCloudDlg sasDlg(Pl2, Dl2, Pg, Dg,
2933  this);
2934  sasDlg.showApplyAllButton(false);
2935  sasDlg.showTitle(true);
2936  sasDlg.setKeepGlobalPos(true);
2937  sasDlg.showKeepGlobalPosCheckbox(
2938  false); // we don't want the user to mess
2939  // with this!
2940  sasDlg.showPreserveShiftOnSave(true);
2941 
2942  // add "original" entry
2943  int index = sasDlg.addShiftInfo(
2945  tr("Original"), globalShift,
2946  globalScale));
2947  // sasDlg.setCurrentProfile(index);
2948  // add "suggested" entry
2949  CCVector3d suggestedShift =
2951  double suggestedScale =
2953  index = sasDlg.addShiftInfo(
2955  tr("Suggested"), suggestedShift,
2956  suggestedScale));
2957  sasDlg.setCurrentProfile(index);
2958  // add "last" entry (if available)
2959  std::vector<ecvGlobalShiftManager::ShiftInfo>
2960  lastInfos;
2961  if (ecvGlobalShiftManager::GetLast(lastInfos)) {
2962  sasDlg.addShiftInfo(lastInfos);
2963  }
2964  // add entries from file (if any)
2965  sasDlg.addFileInfo();
2966 
2967  if (sasDlg.exec()) {
2968  // get the relative modification to existing
2969  // global shift/scale info
2970  assert(cloud->getGlobalScale() != 0);
2971  scaleChange = sasDlg.getScale() /
2972  cloud->getGlobalScale();
2973  shiftChange = (sasDlg.getShift() -
2974  cloud->getGlobalShift());
2975 
2976  updateGlobalShiftAndScale =
2977  (scaleChange != 1.0 ||
2978  shiftChange.norm2() != 0);
2979 
2980  // update transformation matrix accordingly
2981  if (updateGlobalShiftAndScale) {
2982  transMat.scale(scaleChange);
2983  transMat.setTranslation(
2984  transMat.getTranslationAsVec3D() +
2985  shiftChange * scaleChange);
2986  }
2987  } else if (sasDlg.cancelled()) {
2989  tr("[ApplyTransformation] Process "
2990  "cancelled by user"));
2991  return;
2992  }
2993  }
2994  }
2995 
2996  firstCloud = false;
2997  }
2998 
2999  if (updateGlobalShiftAndScale) {
3000  // apply translation as global shift
3001  cloud->setGlobalShift(cloud->getGlobalShift() +
3002  shiftChange);
3003  cloud->setGlobalScale(cloud->getGlobalScale() *
3004  scaleChange);
3005  const CCVector3d& T = cloud->getGlobalShift();
3006  double scale = cloud->getGlobalScale();
3008  tr("[ApplyTransformation] Cloud '%1' global "
3009  "shift/scale information has been updated: "
3010  "shift = (%2,%3,%4) / scale = %5")
3011  .arg(cloud->getName())
3012  .arg(T.x)
3013  .arg(T.y)
3014  .arg(T.z)
3015  .arg(scale));
3016  }
3017  }
3018  }
3019 
3020  // we temporarily detach entity, as it may undergo
3021  //"severe" modifications (octree deletion, etc.) --> see
3022  // ccHObject::applyRigidTransformation
3023  ccHObjectContext objContext = removeObjectTemporarilyFromDBTree(entity);
3024  entity->setGLTransformation(ccGLMatrix(transMat.data()));
3025  // DGM FIXME: we only test the entity own bounding box (and we update
3026  // its shift & scale info) but we apply the transformation to all its
3027  // children?!
3029  // entity->prepareDisplayForRefresh_recursive();
3030  putObjectBackIntoDBTree(entity, objContext);
3031  }
3032 
3033  // reselect previously selected entities!
3034  if (m_ccRoot) m_ccRoot->selectEntities(selectedEntities);
3035 
3036  CVLog::Print(tr("[ApplyTransformation] Applied transformation matrix:"));
3037  CVLog::Print(transMat.toString(12, ' ')); // full precision
3038  CVLog::Print(
3039  tr("Hint: copy it (CTRL+C) and apply it - or its inverse - on any "
3040  "entity with the 'Edit > Apply transformation' tool"));
3041 
3042  refreshSelected();
3043 }
3044 
3045 typedef std::pair<ccHObject*, ccGenericPointCloud*> EntityCloudAssociation;
3046 void MainWindow::doActionApplyScale() {
3047  ccScaleDlg dlg(this);
3048  if (!dlg.exec()) return;
3049  dlg.saveState();
3050 
3051  // save values for next time
3052  CCVector3d scales = dlg.getScales();
3053  bool keepInPlace = dlg.keepInPlace();
3054  bool rescaleGlobalShift = dlg.rescaleGlobalShift();
3055 
3056  // we must backup 'm_selectedEntities' as removeObjectTemporarilyFromDBTree
3057  // can modify it!
3058  ccHObject::Container selectedEntities = m_selectedEntities;
3059 
3060  // first check that all coordinates are kept 'small'
3061  std::vector<EntityCloudAssociation> candidates;
3062  {
3063  bool testBigCoordinates = true;
3064  // size_t processNum = 0;
3065 
3066  for (ccHObject* entity :
3067  selectedEntities) // warning, getSelectedEntites may change during
3068  // this loop!
3069  {
3070  bool lockedVertices;
3071  // try to get the underlying cloud (or the vertices set for a mesh)
3073  entity, &lockedVertices);
3074  // otherwise we can look if the selected entity is a polyline
3075  if (!cloud && entity->isA(CV_TYPES::POLY_LINE)) {
3076  cloud = dynamic_cast<ccGenericPointCloud*>(
3077  static_cast<ccPolyline*>(entity)->getAssociatedCloud());
3078  if (!cloud || cloud->isAncestorOf(entity))
3079  lockedVertices = true;
3080  }
3081  if (!cloud || !cloud->isKindOf(CV_TYPES::POINT_CLOUD)) {
3083  tr("[Apply scale] Entity '%1' can't be scaled this way")
3084  .arg(entity->getName()));
3085  continue;
3086  }
3087  if (lockedVertices) {
3089  haveOneSelection());
3090  //++processNum;
3091  continue;
3092  }
3093 
3094  CCVector3 C(0, 0, 0);
3095  if (keepInPlace) C = cloud->getOwnBB().getCenter();
3096 
3097  // we must check that the resulting cloud coordinates are not too
3098  // big
3099  if (testBigCoordinates) {
3100  ccBBox bbox = cloud->getOwnBB();
3101  CCVector3 bbMin = bbox.minCorner();
3102  CCVector3 bbMax = bbox.maxCorner();
3103 
3104  double maxx = static_cast<double>(
3105  std::max(std::abs(bbMin.x), std::abs(bbMax.x)));
3106  double maxy = static_cast<double>(
3107  std::max(std::abs(bbMin.y), std::abs(bbMax.y)));
3108  double maxz = static_cast<double>(
3109  std::max(std::abs(bbMin.z), std::abs(bbMax.z)));
3110 
3111  const double maxCoord =
3113  bool oldCoordsWereTooBig =
3114  (maxx > maxCoord || maxy > maxCoord || maxz > maxCoord);
3115 
3116  if (!oldCoordsWereTooBig) {
3117  maxx = static_cast<double>(std::max(
3118  std::abs((bbMin.x - C.x) * scales.x + C.x),
3119  std::abs((bbMax.x - C.x) * scales.x + C.x)));
3120  maxy = static_cast<double>(std::max(
3121  std::abs((bbMin.y - C.y) * scales.y + C.y),
3122  std::abs((bbMax.y - C.y) * scales.y + C.y)));
3123  maxz = static_cast<double>(std::max(
3124  std::abs((bbMin.z - C.z) * scales.z + C.z),
3125  std::abs((bbMax.z - C.z) * scales.z + C.z)));
3126 
3127  bool newCoordsAreTooBig =
3128  (maxx > maxCoord || maxy > maxCoord ||
3129  maxz > maxCoord);
3130 
3131  if (newCoordsAreTooBig) {
3132  if (QMessageBox::question(
3133  this, tr("Big coordinates"),
3134  tr("Resutling coordinates will be too big "
3135  "(original precision may be lost!). "
3136  "Proceed anyway?"),
3137  QMessageBox::Yes,
3138  QMessageBox::No) == QMessageBox::Yes) {
3139  // ok, we won't test anymore and proceed
3140  testBigCoordinates = false;
3141  } else {
3142  // we stop the process
3143  return;
3144  }
3145  }
3146  }
3147  }
3148 
3149  assert(cloud);
3150  ccHObject* parent = entity->getParent();
3151  if (parent && parent->isKindOf(CV_TYPES::MESH)) {
3152  candidates.emplace_back(parent, cloud);
3153  } else {
3154  candidates.emplace_back(entity, cloud);
3155  }
3156  }
3157  }
3158 
3159  if (candidates.empty()) {
3161  tr("[Apply scale] No eligible entities (point clouds or "
3162  "meshes) were selected!"));
3163  return;
3164  }
3165 
3166  // now do the real scaling work
3167  {
3168  for (auto& candidate : candidates) {
3169  ccHObject* ent = candidate.first;
3170  ccGenericPointCloud* cloud = candidate.second;
3171 
3172  CCVector3 C(0, 0, 0);
3173  if (keepInPlace) {
3174  C = cloud->getOwnBB().getCenter();
3175  }
3176 
3177  // we temporarily detach entity, as it may undergo
3178  //"severe" modifications (octree deletion, etc.) --> see
3179  // ccPointCloud::scale
3180  ccHObjectContext objContext =
3182 
3183  cloud->scale(static_cast<PointCoordinateType>(scales.x),
3184  static_cast<PointCoordinateType>(scales.y),
3185  static_cast<PointCoordinateType>(scales.z), C);
3186 
3187  putObjectBackIntoDBTree(cloud, objContext);
3188 
3189  // don't forget the 'global shift'!
3190  // DGM: but not the global scale!
3191  if (rescaleGlobalShift) {
3192  const CCVector3d& shift = cloud->getGlobalShift();
3193  cloud->setGlobalShift(CCVector3d(shift.x * scales.x,
3194  shift.y * scales.y,
3195  shift.z * scales.z));
3196  }
3197  }
3198  }
3199 
3200  // reselect previously selected entities!
3201  if (m_ccRoot) m_ccRoot->selectEntities(selectedEntities);
3202 
3203  if (!keepInPlace) zoomOnSelectedEntities();
3204 
3207  for (auto& candidate : candidates) {
3208  ccHObject* ent = candidate.first;
3209  if (ent && ent->isKindOf(CV_TYPES::MESH)) {
3210  ent->setRedrawFlagRecursive(true);
3211  }
3212  ccGenericPointCloud* cloud = candidate.second;
3213  if (cloud) {
3214  cloud->setRedrawFlagRecursive(true);
3215  }
3216  }
3217  refreshAll();
3218  updateUI();
3219 }
3220 
3221 void MainWindow::activateTranslateRotateMode() {
3222  if (!haveSelection()) return;
3223 
3224  if (!getActiveWindow()) return;
3225 
3226 #ifdef USE_PCL_BACKEND
3227  PclTransformTool* pclTransTool =
3228  new PclTransformTool(ecvDisplayTools::GetVisualizer3D());
3229  if (!m_transTool) m_transTool = new ccGraphicalTransformationTool(this);
3230  if (m_transTool->getNumberOfValidEntities() != 0) {
3231  m_transTool->clear();
3232  }
3233 #else
3235  "[MainWindow] please use pcl as backend and then try again!");
3236  return;
3237 #endif // USE_PCL_BACKEND
3238 
3239  if (!m_transTool->setTansformTool(pclTransTool) ||
3240  !m_transTool->linkWith(ecvDisplayTools::GetCurrentScreen())) {
3242  "[MainWindow::activateTranslateRotateMode] Initialization "
3243  "failed!");
3244  return;
3245  }
3246 
3247  bool rejectedEntities = false;
3248  for (ccHObject* entity : getSelectedEntities()) {
3249  if (m_transTool->addEntity(entity)) {
3250  m_ccRoot->unselectEntity(entity);
3251  } else {
3252  rejectedEntities = true;
3253  }
3254  }
3255 
3256  if (m_transTool->getNumberOfValidEntities() == 0) {
3257  ecvConsole::Error(tr(
3258  "No entity eligible for manual transformation! (see console)"));
3259  return;
3260  } else if (rejectedEntities) {
3261  ecvConsole::Error(tr("Some entities were ingored! (see console)"));
3262  }
3263 
3264  // try to activate "moving mode" in current GL window
3265  if (m_transTool->start()) {
3266  connect(m_transTool, &ccOverlayDialog::processFinished, this,
3267  &MainWindow::deactivateTranslateRotateMode);
3268  registerOverlayDialog(m_transTool, Qt::TopRightCorner);
3269  freezeUI(true);
3271  } else {
3272  ecvConsole::Error(tr("Unexpected error!")); // indeed...
3273  }
3274 }
3275 
3276 void MainWindow::deactivateTranslateRotateMode(bool state) {
3277  if (m_transTool) {
3278  // reselect previously selected entities!
3279  if (state && m_ccRoot) {
3280  const ccHObject& transformedSet = m_transTool->getValidEntities();
3281  try {
3282  ccHObject::Container transformedEntities;
3283  transformedEntities.resize(transformedSet.getChildrenNumber());
3284  for (unsigned i = 0; i < transformedSet.getChildrenNumber();
3285  ++i) {
3286  transformedEntities[i] = transformedSet.getChild(i);
3287  }
3288  m_ccRoot->selectEntities(transformedEntities);
3289  } catch (const std::bad_alloc&) {
3290  // not enough memory (nothing to do)
3291  }
3292  }
3293  // m_transTool->close();
3294  }
3295 
3296  freezeUI(false);
3297 
3298  updateUI();
3299 }
3300 
3301 void MainWindow::clearAll() {
3302  if (!m_ccRoot) return;
3303 
3304  if (QMessageBox::question(
3305  this, tr("Close all"),
3306  tr("Are you sure you want to remove all loaded entities?"),
3307  QMessageBox::Yes, QMessageBox::No) != QMessageBox::Yes)
3308  return;
3309 
3310  m_ccRoot->unloadAll();
3311 }
3312 
3314  for (QMdiSubWindow* window : m_mdiArea->subWindowList()) {
3315  window->setEnabled(true);
3316  }
3317 }
3318 
3320  for (QMdiSubWindow* window : m_mdiArea->subWindowList()) {
3321  window->setEnabled(false);
3322  }
3323 }
3324 
3325 void MainWindow::updateUIWithSelection() {
3326  dbTreeSelectionInfo selInfo;
3327 
3328  m_selectedEntities.clear();
3329 
3330  if (m_ccRoot) {
3331  m_ccRoot->getSelectedEntities(m_selectedEntities, CV_TYPES::OBJECT,
3332  &selInfo);
3333  }
3334 
3335  enableUIItems(selInfo);
3336 }
3337 
3338 void MainWindow::enableUIItems(dbTreeSelectionInfo& selInfo) {
3339  bool dbIsEmpty = (!m_ccRoot || !m_ccRoot->getRootEntity() ||
3340  m_ccRoot->getRootEntity()->getChildrenNumber() == 0);
3341  bool atLeastOneEntity = (selInfo.selCount > 0);
3342  bool atLeastOneCloud = (selInfo.cloudCount > 0);
3343  bool atLeastOneMesh = (selInfo.meshCount > 0);
3344  bool atLeastOneOctree = (selInfo.octreeCount > 0);
3345  bool atLeastOneNormal = (selInfo.normalsCount > 0);
3346  bool atLeastOneColor = (selInfo.colorCount > 0);
3347  bool atLeastOneSF = (selInfo.sfCount > 0);
3348  bool atLeastOneGrid = (selInfo.gridCound > 0);
3349 
3350  bool atLeastOneSensor = (selInfo.sensorCount > 0);
3351  bool atLeastOneGBLSensor = (selInfo.gblSensorCount > 0);
3352  bool atLeastOneCameraSensor = (selInfo.cameraSensorCount > 0);
3353  bool atLeastOnePolyline = (selInfo.polylineCount > 0);
3354 
3355  m_ui->actionTracePolyline->setEnabled(!dbIsEmpty);
3356  m_ui->actionZoomAndCenter->setEnabled(atLeastOneEntity);
3357  m_ui->actionSave->setEnabled(atLeastOneEntity);
3358  m_ui->actionSaveProject->setEnabled(!dbIsEmpty);
3359  m_ui->actionClone->setEnabled(atLeastOneEntity);
3360  m_ui->actionDelete->setEnabled(atLeastOneEntity);
3361  m_ui->actionImportSFFromFile->setEnabled(atLeastOneEntity);
3362  m_ui->actionExportCoordToSF->setEnabled(atLeastOneEntity);
3363  m_ui->actionSegment->setEnabled(atLeastOneEntity);
3364  m_ui->actionContourWidget->setEnabled(atLeastOneEntity);
3365  m_ui->actionDistanceWidget->setEnabled(atLeastOneEntity);
3366  m_ui->actionProtractorWidget->setEnabled(atLeastOneEntity);
3367  m_ui->actionTranslateRotate->setEnabled(atLeastOneEntity);
3368  m_ui->actionShowDepthBuffer->setEnabled(atLeastOneGBLSensor);
3369  m_ui->actionExportDepthBuffer->setEnabled(atLeastOneGBLSensor);
3370  m_ui->actionComputePointsVisibility->setEnabled(atLeastOneGBLSensor);
3371  m_ui->actionResampleWithOctree->setEnabled(atLeastOneCloud);
3372  m_ui->actionApplyScale->setEnabled(atLeastOneCloud || atLeastOneMesh ||
3373  atLeastOnePolyline);
3374  m_ui->actionApplyTransformation->setEnabled(atLeastOneEntity);
3375  m_ui->actionComputeOctree->setEnabled(atLeastOneCloud || atLeastOneMesh);
3376  m_ui->actionComputeNormals->setEnabled(atLeastOneCloud || atLeastOneMesh);
3377  m_ui->actionChangeColorLevels->setEnabled(atLeastOneCloud ||
3378  atLeastOneMesh);
3379  m_ui->actionEditGlobalShiftAndScale->setEnabled(
3380  atLeastOneCloud || atLeastOneMesh || atLeastOnePolyline);
3381  m_ui->actionSetUniqueColor->setEnabled(
3382  atLeastOneEntity /*atLeastOneCloud || atLeastOneMesh*/); // DGM: we
3383  // can set
3384  // color
3385  // to a
3386  // group
3387  // now!
3388  m_ui->actionColorize->setEnabled(
3389  atLeastOneEntity /*atLeastOneCloud || atLeastOneMesh*/); // DGM: we
3390  // can set
3391  // color
3392  // to a
3393  // group
3394  // now!
3395  // m_ui->actionDeleteScanGrid->setEnabled(atLeastOneGrid);
3396 
3397  m_ui->actionScalarFieldFromColor->setEnabled(atLeastOneEntity &&
3398  atLeastOneColor);
3399  m_ui->actionComputeMeshAA->setEnabled(atLeastOneCloud);
3400  m_ui->actionComputeMeshLS->setEnabled(atLeastOneCloud);
3401  m_ui->actionConvexHull->setEnabled(atLeastOneCloud);
3402  m_ui->actionPoissonReconstruction->setEnabled(atLeastOneCloud);
3403  m_ui->actionMeshScanGrids->setEnabled(atLeastOneGrid);
3404  // actionComputeQuadric3D->setEnabled(atLeastOneCloud);
3405  m_ui->actionComputeBestFitBB->setEnabled(atLeastOneEntity);
3406  m_ui->actionComputeGeometricFeature->setEnabled(atLeastOneCloud);
3407  m_ui->actionRemoveDuplicatePoints->setEnabled(atLeastOneCloud);
3408  m_ui->actionFitPlane->setEnabled(atLeastOneEntity);
3409  m_ui->actionFitPlaneProxy->setEnabled(atLeastOneEntity);
3410  m_ui->actionFitSphere->setEnabled(atLeastOneCloud);
3411  m_ui->actionFitCircle->setEnabled(atLeastOneCloud);
3412  // m_ui->actionLevel->setEnabled(atLeastOneEntity);
3413  m_ui->actionFitFacet->setEnabled(atLeastOneEntity);
3414  m_ui->actionFitQuadric->setEnabled(atLeastOneCloud);
3415  m_ui->actionSubsample->setEnabled(atLeastOneCloud);
3416 
3417  m_ui->actionSNETest->setEnabled(atLeastOneCloud);
3418  m_ui->actionExportCloudInfo->setEnabled(atLeastOneEntity);
3419  m_ui->actionExportPlaneInfo->setEnabled(atLeastOneEntity);
3420 
3421  m_ui->actionFilterByLabel->setEnabled(atLeastOneSF);
3422  m_ui->actionFilterByValue->setEnabled(atLeastOneSF);
3423  m_ui->actionConvertToRGB->setEnabled(atLeastOneSF);
3424  m_ui->actionConvertToRandomRGB->setEnabled(atLeastOneSF);
3425  m_ui->actionRenameSF->setEnabled(atLeastOneSF);
3426  m_ui->actionAddIdField->setEnabled(atLeastOneCloud);
3427  m_ui->actionComputeStatParams->setEnabled(atLeastOneSF);
3428  // m_ui->actionComputeStatParams2->setEnabled(atLeastOneSF);
3429  m_ui->actionShowHistogram->setEnabled(atLeastOneSF);
3430  m_ui->actionGaussianFilter->setEnabled(atLeastOneSF);
3431  m_ui->actionBilateralFilter->setEnabled(atLeastOneSF);
3432  m_ui->actionDeleteScalarField->setEnabled(atLeastOneSF);
3433  m_ui->actionDeleteAllSF->setEnabled(atLeastOneSF);
3434  // m_ui->actionMultiplySF->setEnabled(/*TODO: atLeastOneSF*/false);
3435  m_ui->actionSFGradient->setEnabled(atLeastOneSF);
3436  m_ui->actionSetSFAsCoord->setEnabled(atLeastOneSF && atLeastOneCloud);
3437  m_ui->actionInterpolateSFs->setEnabled(atLeastOneCloud || atLeastOneMesh);
3438 
3439  m_ui->actionSamplePointsOnMesh->setEnabled(atLeastOneMesh);
3440  m_ui->actionMeasureMeshSurface->setEnabled(atLeastOneMesh);
3441  m_ui->actionMeasureMeshVolume->setEnabled(atLeastOneMesh);
3442  m_ui->actionFlagMeshVertices->setEnabled(atLeastOneMesh);
3443  m_ui->actionSmoothMeshLaplacian->setEnabled(atLeastOneMesh);
3444  m_ui->actionConvertTextureToColor->setEnabled(atLeastOneMesh);
3445  m_ui->actionSubdivideMesh->setEnabled(atLeastOneMesh);
3446  m_ui->actionDistanceToBestFitQuadric3D->setEnabled(atLeastOneCloud);
3447  m_ui->actionDistanceMap->setEnabled(atLeastOneMesh || atLeastOneCloud);
3448 
3449  m_ui->menuMeshScalarField->setEnabled(atLeastOneSF && atLeastOneMesh);
3450  // actionSmoothMeshSF->setEnabled(atLeastOneSF && atLeastOneMesh);
3451  // actionEnhanceMeshSF->setEnabled(atLeastOneSF && atLeastOneMesh);
3452 
3453  m_ui->actionOrientNormalsMST->setEnabled(atLeastOneCloud &&
3454  atLeastOneNormal);
3455  m_ui->actionOrientNormalsFM->setEnabled(atLeastOneCloud &&
3456  atLeastOneNormal);
3457  m_ui->actionClearNormals->setEnabled(atLeastOneNormal);
3458  m_ui->actionInvertNormals->setEnabled(atLeastOneNormal);
3459  m_ui->actionConvertNormalToHSV->setEnabled(atLeastOneNormal);
3460  m_ui->actionConvertNormalToDipDir->setEnabled(atLeastOneNormal);
3461  m_ui->actionExportNormalToSF->setEnabled(atLeastOneNormal);
3462  m_ui->actionClearColor->setEnabled(atLeastOneColor);
3463  m_ui->actionRGBToGreyScale->setEnabled(atLeastOneColor);
3464  m_ui->actionEnhanceRGBWithIntensities->setEnabled(atLeastOneColor);
3465  m_ui->actionRGBGaussianFilter->setEnabled(atLeastOneColor);
3466  m_ui->actionRGBBilateralFilter->setEnabled(atLeastOneColor);
3467  m_ui->actionRGBMeanFilter->setEnabled(atLeastOneColor);
3468  m_ui->actionRGBMedianFilter->setEnabled(atLeastOneColor);
3469  m_ui->actionColorFromScalarField->setEnabled(atLeastOneSF);
3470 
3471  // == 1
3472  bool exactlyOneEntity = (selInfo.selCount == 1);
3473  bool exactlyOneGroup = (selInfo.groupCount == 1);
3474  bool exactlyOneCloud = (selInfo.cloudCount == 1);
3475  bool exactlyOneMesh = (selInfo.meshCount == 1);
3476  bool exactlyOneSF = (selInfo.sfCount == 1);
3477  bool exactlyOneSensor = (selInfo.sensorCount == 1);
3478  bool exactlyOneCameraSensor = (selInfo.cameraSensorCount == 1);
3479 
3480  m_ui->actionSliceFilter->setEnabled(exactlyOneMesh);
3481  m_ui->actionDecimateFilter->setEnabled(exactlyOneMesh);
3482  m_ui->actionIsoSurfaceFilter->setEnabled(exactlyOneMesh);
3483  m_ui->actionSmoothFilter->setEnabled(exactlyOneMesh);
3484 
3485  m_ui->actionConvertPolylinesToMesh->setEnabled(atLeastOnePolyline ||
3486  exactlyOneGroup);
3487  m_ui->actionSamplePointsOnPolyline->setEnabled(atLeastOnePolyline);
3488  m_ui->actionSmoothPolyline->setEnabled(atLeastOnePolyline);
3489  m_ui->actionMeshTwoPolylines->setEnabled(selInfo.selCount == 2 &&
3490  selInfo.polylineCount == 2);
3491  m_ui->actionModifySensor->setEnabled(exactlyOneSensor);
3492  m_ui->actionComputeDistancesFromSensor->setEnabled(atLeastOneCameraSensor ||
3493  atLeastOneGBLSensor);
3494  m_ui->actionComputeScatteringAngles->setEnabled(exactlyOneSensor);
3495  m_ui->actionViewFromSensor->setEnabled(exactlyOneSensor);
3496  m_ui->actionCreateGBLSensor->setEnabled(atLeastOneCloud);
3497  m_ui->actionCreateCameraSensor->setEnabled(selInfo.selCount <=
3498  1); // free now
3499  m_ui->actionProjectUncertainty->setEnabled(exactlyOneCameraSensor);
3500  m_ui->actionCheckPointsInsideFrustum->setEnabled(exactlyOneCameraSensor);
3501  m_ui->actionLabelConnectedComponents->setEnabled(atLeastOneCloud);
3502  m_ui->actionSORFilter->setEnabled(atLeastOneCloud);
3503  m_ui->actionNoiseFilter->setEnabled(atLeastOneCloud);
3504  m_ui->actionVoxelSampling->setEnabled(atLeastOneCloud);
3505  m_ui->actionUnroll->setEnabled(exactlyOneEntity);
3506  // m_ui->actionStatisticalTest->setEnabled(exactlyOneEntity &&
3507  // exactlyOneSF);
3508  m_ui->actionAddConstantSF->setEnabled(exactlyOneCloud || exactlyOneMesh);
3509  // m_ui->actionEditGlobalScale->setEnabled(exactlyOneCloud ||
3510  // exactlyOneMesh);
3511  m_ui->actionComputeKdTree->setEnabled(exactlyOneCloud || exactlyOneMesh);
3512  m_ui->actionShowWaveDialog->setEnabled(exactlyOneCloud);
3513  m_ui->actionCompressFWFData->setEnabled(atLeastOneCloud);
3514 
3515  m_ui->actionKMeans->setEnabled(
3516  /*TODO: exactlyOneEntity && exactlyOneSF*/ false);
3517  m_ui->actionFrontPropagation->setEnabled(
3518  /*TODO: exactlyOneEntity && exactlyOneSF*/ false);
3519 
3520  // actionCreatePlane->setEnabled(true);
3521  m_ui->actionEditPlane->setEnabled(selInfo.planeCount == 1);
3522  m_ui->actionFlipPlane->setEnabled(selInfo.planeCount != 0);
3523  m_ui->actionComparePlanes->setEnabled(selInfo.planeCount == 2);
3524 
3525  m_ui->actionPromoteCircleToCylinder->setEnabled((selInfo.selCount == 1) &&
3526  (selInfo.circleCount == 1));
3527 
3528  m_ui->actionFindBiggestInnerRectangle->setEnabled(exactlyOneCloud);
3529 
3530  // m_ui->menuActiveScalarField->setEnabled((exactlyOneCloud ||
3531  // exactlyOneMesh) && selInfo.sfCount > 0);
3532  m_ui->actionClipFilter->setEnabled(atLeastOneCloud || atLeastOneMesh ||
3533  (selInfo.groupCount != 0));
3534  m_ui->actionProbeFilter->setEnabled(atLeastOneCloud || atLeastOneMesh ||
3535  (selInfo.groupCount != 0));
3536  m_ui->actionGlyphFilter->setEnabled(atLeastOneCloud || atLeastOneMesh ||
3537  (selInfo.groupCount != 0));
3538  m_ui->actionStreamlineFilter->setEnabled(
3539  atLeastOneCloud || atLeastOneMesh || (selInfo.groupCount != 0));
3540  m_ui->actionThresholdFilter->setEnabled(atLeastOneCloud || atLeastOneMesh ||
3541  (selInfo.groupCount != 0));
3542 
3543 #ifdef USE_PYTHON_MODULE
3544  m_ui->actionSemanticSegmentation->setEnabled(atLeastOneCloud);
3545 #endif // USE_PYTHON_MODULE
3546 
3547  m_ui->actionDBScanCluster->setEnabled(atLeastOneCloud);
3548  m_ui->actionPlaneSegmentation->setEnabled(atLeastOneCloud);
3549  // m_ui->actionExtractSections->setEnabled(atLeastOneCloud);
3550  // m_ui->actionRasterize->setEnabled(exactlyOneCloud);
3551  m_ui->actionBoxAnnotation->setEnabled(exactlyOneCloud);
3552  m_ui->actionSemanticAnnotation->setEnabled(exactlyOneCloud);
3553 
3554  m_ui->actionCompute2HalfDimVolume->setEnabled(
3555  selInfo.cloudCount == selInfo.selCount && selInfo.cloudCount >= 1 &&
3556  selInfo.cloudCount <= 2); // one or two clouds!
3557  m_ui->actionPointListPicking->setEnabled(exactlyOneEntity);
3558 
3559  // == 2
3560  bool exactlyTwoEntities = (selInfo.selCount == 2);
3561  bool exactlyTwoClouds = (selInfo.cloudCount == 2);
3562  // bool exactlyTwoSF = (selInfo.sfCount == 2);
3563 
3564  m_ui->actionRegister->setEnabled(exactlyTwoEntities);
3565  m_ui->actionInterpolateColors->setEnabled(exactlyTwoEntities &&
3566  atLeastOneColor);
3567  m_ui->actionPointPairsAlign->setEnabled(atLeastOneEntity);
3568  m_ui->actionBBCenterToOrigin->setEnabled(atLeastOneEntity);
3569  m_ui->actionBBMinCornerToOrigin->setEnabled(atLeastOneEntity);
3570  m_ui->actionBBMaxCornerToOrigin->setEnabled(atLeastOneEntity);
3571  m_ui->actionAlign->setEnabled(
3572  exactlyTwoEntities); // Aurelien BEY le 13/11/2008
3573  m_ui->actionCloudCloudDist->setEnabled(exactlyTwoClouds);
3574  m_ui->actionCloudMeshDist->setEnabled(exactlyTwoEntities && atLeastOneMesh);
3575  m_ui->actionCloudPrimitiveDist->setEnabled(
3576  atLeastOneCloud && (atLeastOneMesh || atLeastOnePolyline));
3577  m_ui->actionCPS->setEnabled(exactlyTwoClouds);
3578  m_ui->actionScalarFieldArithmetic->setEnabled(exactlyOneEntity &&
3579  atLeastOneSF);
3580 
3581  //>1
3582  bool atLeastTwoEntities = (selInfo.selCount > 1);
3583 
3584  m_ui->actionMerge->setEnabled(atLeastTwoEntities);
3585  m_ui->actionMatchBBCenters->setEnabled(atLeastTwoEntities);
3586  m_ui->actionMatchScales->setEnabled(atLeastTwoEntities);
3587 
3588  // standard plugins
3589  m_pluginUIManager->handleSelectionChanged();
3590 }
3591 
3592 void MainWindow::moveEvent(QMoveEvent* event) {
3593  QMainWindow::moveEvent(event);
3594 
3596 }
3597 
3598 void MainWindow::resizeEvent(QResizeEvent* event) {
3599  QMainWindow::resizeEvent(event);
3600 
3602  updateMemoryUsageWidgetSize();
3603 }
3604 
3605 bool MainWindow::eventFilter(QObject* obj, QEvent* event) {
3606  switch (event->type()) {
3607  case QEvent::Resize:
3608  case QEvent::Move:
3610  break;
3611  case QEvent::KeyPress: {
3612  // Handle ESC key globally to exit selection tools
3613  // This is needed because VTK render window captures key events
3614  QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
3615  if (keyEvent->key() == Qt::Key_Escape) {
3617  "[MainWindow::eventFilter] ESC key detected, calling "
3618  "handleEscapeKey");
3619  // Handle ESC key the same way as keyPressEvent
3620  handleEscapeKey();
3621  return true; // Event handled
3622  }
3623  break;
3624  }
3625  default:
3626  // nothing to do
3627  break;
3628  }
3629 
3630  // standard event processing
3631  return QObject::eventFilter(obj, event);
3632 }
3633 
3634 void MainWindow::handleEscapeKey() {
3635  // First, stop any active measurement tool and uncheck its button
3636  if (m_measurementTool && m_measurementTool->started()) {
3637  m_measurementTool->stop(false);
3638 
3639  // Uncheck measurement tool actions if they are checkable
3640  if (m_ui->actionDistanceWidget &&
3641  m_ui->actionDistanceWidget->isCheckable()) {
3642  m_ui->actionDistanceWidget->setChecked(false);
3643  }
3644  if (m_ui->actionProtractorWidget &&
3645  m_ui->actionProtractorWidget->isCheckable()) {
3646  m_ui->actionProtractorWidget->setChecked(false);
3647  }
3648  if (m_ui->actionContourWidget &&
3649  m_ui->actionContourWidget->isCheckable()) {
3650  m_ui->actionContourWidget->setChecked(false);
3651  }
3652  }
3653 
3654  // Second, disable all active selection tools (SelectionTools
3655  // module) This ensures ESC exits selection modes like Rectangle
3656  // Select, Polygon Select, etc.
3657  CVLog::PrintDebug("[MainWindow] Disabling all selection tools");
3658  // Disable all selection tools via controller
3659  // The controller handles unchecking all actions
3660 #ifdef USE_PCL_BACKEND
3661  if (m_selectionController) {
3662  m_selectionController->handleEscapeKey();
3663  }
3664 #endif
3665 
3666  // Then handle picking and fullscreen
3668 
3669  // Handle exclusive fullscreen mode (when a sub-widget is fullscreen, not
3670  // MainWindow itself)
3671  if (m_exclusiveFullscreen) {
3673  }
3674  // Handle normal fullscreen mode (when MainWindow itself is fullscreen)
3675  else if (this->isFullScreen()) {
3676  this->showNormal();
3677  }
3678 }
3679 
3680 void MainWindow::keyPressEvent(QKeyEvent* event) {
3681  switch (event->key()) {
3682  case Qt::Key_Escape:
3683  CVLog::Print(
3684  "[MainWindow::keyPressEvent] ESC key received, calling "
3685  "handleEscapeKey");
3686  handleEscapeKey();
3687  break;
3688  default:
3689  QMainWindow::keyPressEvent(event);
3690  }
3691 }
3692 
3694  ccHObject tempGroup(QString("TempGroup"));
3695  size_t selNum = m_selectedEntities.size();
3696  for (size_t i = 0; i < selNum; ++i) {
3697  ccHObject* entity = m_selectedEntities[i];
3698  tempGroup.addChild(entity, ccHObject::DP_NONE);
3699  }
3700 
3701  ccBBox box;
3702  if (tempGroup.getChildrenNumber() != 0) {
3703  box = tempGroup.getDisplayBB_recursive(false);
3704  }
3705  return box;
3706 }
3707 
3708 void MainWindow::addEditPlaneAction(QMenu& menu) const {
3709  menu.addAction(m_ui->actionEditPlane);
3710 }
3711 
3712 void MainWindow::zoomOn(ccHObject* object) {
3714  ccBBox box = object->getDisplayBB_recursive(false);
3716  }
3717 }
3718 
3721 }
3722 
3723 void MainWindow::updateMenus() {
3724  QWidget* active3DView = getActiveWindow();
3725  bool hasMdiChild = (active3DView != nullptr);
3726  int mdiChildCount = getRenderWindowCount();
3727  bool hasLoadedEntities =
3728  (m_ccRoot && m_ccRoot->getRootEntity() &&
3729  m_ccRoot->getRootEntity()->getChildrenNumber() != 0);
3730  bool hasSelectedEntities =
3731  (m_ccRoot && m_ccRoot->countSelectedEntities() > 0);
3732 
3733  // General Menu
3734  m_ui->menuEdit->setEnabled(true /*hasSelectedEntities*/);
3735  m_ui->menuTools->setEnabled(true /*hasSelectedEntities*/);
3736 
3738  m_ui->ViewToolBar->setEnabled(hasMdiChild);
3739 
3741  m_ui->actionSegment->setEnabled(hasMdiChild && hasSelectedEntities);
3742  m_ui->actionContourWidget->setEnabled(hasMdiChild && hasSelectedEntities);
3743  m_ui->actionDistanceWidget->setEnabled(hasMdiChild && hasSelectedEntities);
3744  m_ui->actionProtractorWidget->setEnabled(hasMdiChild &&
3745  hasSelectedEntities);
3746  m_ui->actionTranslateRotate->setEnabled(hasMdiChild && hasSelectedEntities);
3747  m_ui->actionPointPicking->setEnabled(hasMdiChild && hasLoadedEntities);
3748  m_ui->actionPointListPicking->setEnabled(hasLoadedEntities);
3749  // m_ui->actionTestFrameRate->setEnabled(hasMdiChild);
3750  // m_ui->actionRenderToFile->setEnabled(hasMdiChild);
3751  // m_ui->actionToggleCenteredPerspective->setEnabled(hasMdiChild);
3752  // m_ui->actionToggleViewerBasedPerspective->setEnabled(hasMdiChild);
3753 
3754  // plugins
3755  m_pluginUIManager->updateMenus();
3756 }
3757 
3759  const ccHObjectContext& context) {
3760  assert(obj);
3761  if (!obj || !m_ccRoot) return;
3762 
3763  if (context.parent) {
3764  context.parent->addChild(obj, context.parentFlags);
3765  obj->addDependency(context.parent, context.childFlags);
3766  }
3767 
3768  // DGM: we must call 'notifyGeometryUpdate' as any call to this method
3769  // while the object was temporarily 'cut' from the DB tree were
3770  // ineffective!
3771  obj->notifyGeometryUpdate();
3772 
3773  m_ccRoot->addElement(obj, false);
3774 }
3775 
3777  ccHObject* obj) {
3779 
3780  assert(obj);
3781  if (!m_ccRoot || !obj) return context;
3782 
3783  // mandatory (to call putObjectBackIntoDBTree)
3784  context.parent = obj->getParent();
3785 
3786  // remove the object's dependency to its father (in case it undergoes
3787  // "severe" modifications)
3788  if (context.parent) {
3789  context.parentFlags = context.parent->getDependencyFlagsWith(obj);
3790  context.childFlags = obj->getDependencyFlagsWith(context.parent);
3791 
3792  context.parent->removeDependencyWith(obj);
3793  obj->removeDependencyWith(context.parent);
3794  }
3795 
3796  m_ccRoot->removeElement(obj);
3797 
3798  return context;
3799 }
3800 
3803 }
3804 
3806 void MainWindow::doActionResetGUIElementsPos() {
3807  // show the user it will be maximized
3808  showMaximized();
3809  if (this->m_uiManager) {
3810  this->m_uiManager->showMaximized();
3811  }
3812 
3813  QSettings settings;
3814  settings.remove(ecvPS::MainWinGeom());
3815  settings.remove(ecvPS::MainWinState());
3816 
3817  QMessageBox::information(this, tr("Restart"),
3818  tr("To finish the process, you'll have to close "
3819  "and restart ACloudViewer"));
3820 
3821  // to avoid saving them right away!
3822  s_autoSaveGuiElementPos = false;
3823 }
3824 
3825 void MainWindow::doActionSaveCustomLayout() {
3826  if (m_layoutManager) {
3827  m_layoutManager->saveCustomLayout();
3828  QMessageBox::information(
3829  this, tr("Save Custom Layout"),
3830  tr("Current layout has been saved as custom layout. You can "
3831  "restore it later using the 'Restore Custom Layout' "
3832  "action."));
3833  } else {
3834  CVLog::Error("[MainWindow] Layout manager is not initialized!");
3835  }
3836 }
3837 
3838 void MainWindow::doActionRestoreDefaultLayout() {
3839  if (m_layoutManager) {
3840  m_layoutManager->restoreDefaultLayout();
3841  } else {
3842  CVLog::Error("[MainWindow] Layout manager is not initialized!");
3843  }
3844 }
3845 
3846 void MainWindow::doActionRestoreCustomLayout() {
3847  if (m_layoutManager) {
3848  if (!m_layoutManager->restoreCustomLayout()) {
3849  QMessageBox::warning(this, tr("Restore Custom Layout"),
3850  tr("No saved custom layout found. Please save "
3851  "current layout first."));
3852  }
3853  } else {
3854  CVLog::Error("[MainWindow] Layout manager is not initialized!");
3855  }
3856 }
3857 
3858 void MainWindow::doActionRestoreWindowOnStartup(bool state) {
3859  QSettings settings;
3860  settings.setValue(ecvPS::DoNotRestoreWindowGeometry(), !state);
3861 }
3862 
3863 void MainWindow::toggleFullScreen(bool state) {
3864  if (m_uiManager != nullptr) {
3865  m_uiManager->toggleFullScreen(state);
3866  } else {
3867  state ? showFullScreen() : showNormal();
3868  }
3869 
3870 #ifdef Q_OS_MAC
3871  if (state) {
3872  m_ui->actionFullScreen->setText(tr("Exit Full Screen"));
3873  } else {
3874  m_ui->actionFullScreen->setText(tr("Enter Full Screen"));
3875  }
3876 #endif
3877 
3878  m_ui->actionFullScreen->setChecked(state);
3879 }
3880 
3882  if (state) {
3883  // we are currently in normal screen mode
3884  if (!m_exclusiveFullscreen) {
3885  m_currentFullWidget = getActiveWindow();
3886  if (m_currentFullWidget) {
3887  m_formerGeometry = m_currentFullWidget->saveGeometry();
3888  m_currentFullWidget->setWindowFlags(Qt::Dialog);
3889  // Install event filter to capture ESC key in fullscreen mode
3890  m_currentFullWidget->installEventFilter(this);
3891  }
3892 
3893  m_exclusiveFullscreen = true;
3894  if (m_currentFullWidget) {
3895  m_currentFullWidget->showFullScreen();
3896  // Ensure the widget has keyboard focus to receive ESC key
3897  m_currentFullWidget->setFocus();
3898  } else {
3899  showFullScreen();
3900  }
3901 
3902  onExclusiveFullScreenToggled(state);
3904  "Press F11 or ESC to disable full-screen mode",
3907  }
3908  } else {
3909  // if we are currently in full-screen mode
3910  if (m_exclusiveFullscreen) {
3911  if (m_currentFullWidget) {
3912  m_currentFullWidget->setWindowFlags(Qt::SubWindow);
3913  }
3914 
3915  m_exclusiveFullscreen = false;
3916  onExclusiveFullScreenToggled(state);
3918  QString(), ecvDisplayTools::UPPER_CENTER_MESSAGE, false, 0,
3919  ecvDisplayTools::FULL_SCREEN_MESSAGE); // remove any
3920  // message
3921 
3922  if (m_currentFullWidget) {
3923  m_currentFullWidget->showNormal();
3924  if (!m_formerGeometry.isNull()) {
3925  m_currentFullWidget->restoreGeometry(m_formerGeometry);
3926  m_formerGeometry.clear();
3927  }
3928  } else {
3929  showNormal();
3930  }
3931  }
3932  }
3933 
3934  QCoreApplication::processEvents();
3935  if (m_currentFullWidget) {
3936  m_currentFullWidget->setFocus();
3937  }
3938 
3940  ecvDisplayTools::RedrawDisplay(true, false);
3941 }
3942 
3943 void MainWindow::toggle3DView(bool state) {
3946  }
3947 }
3948 
3949 void MainWindow::onExclusiveFullScreenToggled(bool state) {
3950  // we simply update the full-screen action method icon (whatever the window)
3953  m_ui->actionExclusiveFullScreen->blockSignals(true);
3954  m_ui->actionExclusiveFullScreen->setChecked(state);
3955  m_ui->actionExclusiveFullScreen->blockSignals(false);
3956  }
3957 }
3958 
3960  m_ui->actionFullScreen->setChecked(state);
3961 }
3962 
3963 void MainWindow::handleNewLabel(ccHObject* entity) {
3964  if (entity) {
3965  addToDB(entity);
3966  } else {
3967  assert(false);
3968  }
3969 }
3970 
3971 void MainWindow::activatePointListPickingMode() {
3972  // there should be only one point cloud in current selection!
3973  if (!haveOneSelection()) {
3974  ecvConsole::Error(tr("Select one and only one entity!"));
3975  return;
3976  }
3977 
3978  ccPointCloud* pc = ccHObjectCaster::ToPointCloud(m_selectedEntities[0]);
3979  if (!pc) {
3980  ecvConsole::Error(tr("Wrong type of entity"));
3981  return;
3982  }
3983 
3984  if (!pc->isVisible() || !pc->isEnabled()) {
3985  ecvConsole::Error(tr("Points must be visible!"));
3986  return;
3987  }
3988 
3989  if (!m_plpDlg) {
3990  m_plpDlg = new ccPointListPickingDlg(m_pickingHub, this);
3991  connect(m_plpDlg, &ccOverlayDialog::processFinished, this,
3992  &MainWindow::deactivatePointListPickingMode);
3993 
3994  registerOverlayDialog(m_plpDlg, Qt::TopRightCorner);
3995  }
3996 
3997  // DGM: we must update marker size spin box value (as it may have changed by
3998  // the user with the "display dialog")
3999  m_plpDlg->markerSizeSpinBox->setValue(
4000  ecvDisplayTools::GetDisplayParameters().labelMarkerSize);
4001 
4003  m_plpDlg->linkWithCloud(pc);
4004 
4005  freezeUI(true);
4006 
4007  if (!m_plpDlg->start())
4008  deactivatePointListPickingMode(false);
4009  else
4011 }
4012 
4013 void MainWindow::deactivatePointListPickingMode(bool state) {
4014  Q_UNUSED(state);
4015  if (m_plpDlg) {
4016  m_plpDlg->linkWithCloud(nullptr);
4017  }
4018 
4019  freezeUI(false);
4020 
4021  updateUI();
4022 }
4023 
4024 void MainWindow::activatePointPickingMode() {
4025  if (m_ccRoot) {
4026  m_ccRoot->unselectAllEntities(); // we don't want any entity selected
4027  // (especially existing labels!)
4028  }
4029 
4030  if (!m_ppDlg) {
4031  m_ppDlg = new ccPointPropertiesDlg(m_pickingHub, this);
4032  connect(m_ppDlg, &ccOverlayDialog::processFinished, this,
4033  &MainWindow::deactivatePointPickingMode);
4034  connect(m_ppDlg, &ccPointPropertiesDlg::newLabel, this,
4035  &MainWindow::handleNewLabel);
4036 
4037  registerOverlayDialog(m_ppDlg, Qt::TopRightCorner);
4038  }
4039 
4041 
4042  freezeUI(true);
4043 
4044  if (!m_ppDlg->start())
4045  deactivatePointPickingMode(false);
4046  else
4048 }
4049 
4050 void MainWindow::deactivatePointPickingMode(bool state) {
4051  Q_UNUSED(state);
4052  freezeUI(false);
4053  updateUI();
4054 }
4055 
4056 void MainWindow::activateTracePolylineMode() {
4057  if (!m_tplTool) {
4058  m_tplTool = new ccTracePolylineTool(m_pickingHub, this);
4059  connect(m_tplTool, &ccOverlayDialog::processFinished, this,
4060  &MainWindow::deactivateTracePolylineMode);
4061  registerOverlayDialog(m_tplTool, Qt::TopRightCorner);
4062  }
4063 
4065 
4066  freezeUI(true);
4067  m_ui->ViewToolBar->setDisabled(false);
4068 
4069  if (!m_tplTool->start())
4070  deactivateTracePolylineMode(false);
4071  else
4073 }
4074 
4075 void MainWindow::deactivateTracePolylineMode(bool) {
4076  freezeUI(false);
4077  updateUI();
4078 }
4079 
4081  // check for existence
4082  for (ccMDIDialogs& mdi : m_mdiDialogs) {
4083  if (mdi.dialog == dlg) {
4084  // we only update its position in this case
4085  mdi.position = pos;
4086  repositionOverlayDialog(mdi);
4087  return;
4088  }
4089  }
4090 
4091  // otherwise we add it to DB
4092  m_mdiDialogs.push_back(ccMDIDialogs(dlg, pos));
4093 
4094  // automatically update the dialog placement when its shown
4095  connect(dlg, &ccOverlayDialog::shown, this, [=]() {
4096  // check for existence
4097  for (ccMDIDialogs& mdi : m_mdiDialogs) {
4098  if (mdi.dialog == dlg) {
4099  repositionOverlayDialog(mdi);
4100  break;
4101  }
4102  }
4103  });
4104 
4105  repositionOverlayDialog(m_mdiDialogs.back());
4106 }
4107 
4109  for (std::vector<ccMDIDialogs>::iterator it = m_mdiDialogs.begin();
4110  it != m_mdiDialogs.end(); ++it) {
4111  if (it->dialog == dialog) {
4112  m_mdiDialogs.erase(it);
4113  break;
4114  }
4115  }
4116 }
4117 
4119  for (ccMDIDialogs& mdiDlg : m_mdiDialogs) {
4120  repositionOverlayDialog(mdiDlg);
4121  }
4122 }
4123 
4124 ccHObject* MainWindow::loadFile(QString filename, bool silent) {
4125  FileIOFilter::LoadParameters parameters;
4126  {
4127  parameters.alwaysDisplayLoadDialog = silent ? false : true;
4128  parameters.shiftHandlingMode =
4130  parameters.parentWidget = silent ? nullptr : this;
4131  }
4132 
4134  ccHObject* newGroup =
4136 
4137  return newGroup;
4138 }
4139 
4140 void MainWindow::repositionOverlayDialog(ccMDIDialogs& mdiDlg) {
4141  if (!mdiDlg.dialog || !mdiDlg.dialog->isVisible() || !m_mdiArea) return;
4142 
4143  int dx = 0;
4144  int dy = 0;
4145  static const int margin = 5;
4146  // QRect screenRect = ecvDisplayTools::GetScreenRect();
4147  switch (mdiDlg.position) {
4148  case Qt::TopLeftCorner:
4149  dx = margin;
4150  dy = margin;
4151  break;
4152  case Qt::TopRightCorner:
4153  dx = std::max(margin,
4154  m_mdiArea->width() - mdiDlg.dialog->width() - margin);
4155  dy = margin;
4156  break;
4157  case Qt::BottomLeftCorner:
4158  dx = margin;
4159  dy = std::max(margin, m_mdiArea->height() -
4160  mdiDlg.dialog->height() - margin);
4161  break;
4162  case Qt::BottomRightCorner:
4163  dx = std::max(margin,
4164  m_mdiArea->width() - mdiDlg.dialog->width() - margin);
4165  dy = std::max(margin, m_mdiArea->height() -
4166  mdiDlg.dialog->height() - margin);
4167  break;
4168  }
4169 
4170  // show();
4171  mdiDlg.dialog->move(m_mdiArea->mapToGlobal(QPoint(dx, dy)));
4172  mdiDlg.dialog->raise();
4173 }
4174 
4175 // helper for doActionMerge
4177  ccHObject::Container& toBeRemovedList) {
4178  // is a parent or sibling already in the "toBeRemoved" list?
4179  std::size_t j = 0;
4180  std::size_t count = toBeRemovedList.size();
4181  while (j < count) {
4182  if (toBeRemovedList[j]->isAncestorOf(toRemove)) {
4183  toRemove = nullptr;
4184  break;
4185  } else if (toRemove->isAncestorOf(toBeRemovedList[j])) {
4186  toBeRemovedList[j] = toBeRemovedList.back();
4187  toBeRemovedList.pop_back();
4188  count--;
4189  j++;
4190  } else {
4191  // forward
4192  j++;
4193  }
4194  }
4195 
4196  if (toRemove) toBeRemovedList.push_back(toRemove);
4197 }
4198 
4199 void MainWindow::doActionMerge() {
4200  // let's look for clouds or meshes (warning: we don't mix them)
4201  std::vector<ccPointCloud*> clouds;
4202  std::vector<ccMesh*> meshes;
4203 
4204  try {
4205  for (ccHObject* entity : getSelectedEntities()) {
4206  if (!entity) continue;
4207 
4208  if (entity->isA(CV_TYPES::POINT_CLOUD)) {
4210  clouds.push_back(cloud);
4211  } else if (entity->isKindOf(CV_TYPES::MESH)) {
4212  ccMesh* mesh = ccHObjectCaster::ToMesh(entity);
4213  // this is a purely theoretical test for now!
4214  if (mesh && mesh->getAssociatedCloud() &&
4216  meshes.push_back(mesh);
4217  } else {
4219  tr("Only meshes with standard vertices are handled "
4220  "for now! Can't merge entity '%1'...")
4221  .arg(entity->getName()));
4222  }
4223  } else {
4224  ecvConsole::Warning(tr("Entity '%1' is neither a cloud nor a "
4225  "mesh, can't merge it!")
4226  .arg(entity->getName()));
4227  }
4228  }
4229  } catch (const std::bad_alloc&) {
4230  CVLog::Error(tr("Not enough memory!"));
4231  return;
4232  }
4233 
4234  if (clouds.empty() && meshes.empty()) {
4235  CVLog::Error(tr("Select only clouds or meshes!"));
4236  return;
4237  }
4238  if (!clouds.empty() && !meshes.empty()) {
4239  CVLog::Error(tr("Can't mix point clouds and meshes!"));
4240  }
4241 
4242  // merge clouds?
4243  if (!clouds.empty()) {
4244  // we deselect all selected entities (as most of them are going to
4245  // disappear)
4246  if (m_ccRoot) {
4247  m_ccRoot->unselectAllEntities();
4248  assert(!haveSelection());
4249  // m_selectedEntities.clear();
4250  }
4251 
4252  // we will remove the useless clouds/meshes later
4253  ccHObject::Container toBeRemoved;
4254 
4255  ccPointCloud* firstCloud = nullptr;
4256  ccHObjectContext firstCloudContext;
4257 
4258  // whether to generate the 'original cloud index' scalar field or not
4259  cloudViewer::ScalarField* ocIndexSF = nullptr;
4260  size_t cloudIndex = 0;
4261 
4262  for (size_t i = 0; i < clouds.size(); ++i) {
4263  ccPointCloud* pc = clouds[i];
4264  if (!firstCloud) {
4265  // we don't delete the first cloud (we'll merge the other one
4266  // 'inside' it
4267  firstCloud = pc;
4268  // we still have to temporarily detach the first cloud, as it
4269  // may undergo "severe" modifications (octree deletion, etc.)
4270  //--> see ccPointCloud::operator +=
4271  firstCloudContext =
4273 
4274  if (QMessageBox::question(
4275  this, tr("Original cloud index"),
4276  tr("Do you want to generate a scalar field with "
4277  "the original cloud index?")) ==
4278  QMessageBox::Yes) {
4279  int sfIdx = pc->getScalarFieldIndexByName(
4281  if (sfIdx < 0) {
4282  sfIdx = pc->addScalarField(
4284  }
4285  if (sfIdx < 0) {
4287  tr("Couldn't allocate a new scalar field for "
4288  "storing the original cloud index! Try to "
4289  "free some memory ..."));
4290  return;
4291  } else {
4292  ocIndexSF = pc->getScalarField(sfIdx);
4293  ocIndexSF->fill(0);
4294  firstCloud->setCurrentDisplayedScalarField(sfIdx);
4295  }
4296  }
4297  } else {
4298  unsigned countBefore = firstCloud->size();
4299  unsigned countAdded = pc->size();
4300  *firstCloud += pc;
4301 
4302  // success?
4303  if (firstCloud->size() == countBefore + countAdded) {
4304  // firstCloud->prepareDisplayForRefresh_recursive();
4305 
4306  ccHObject* toRemove = nullptr;
4307  // if the entity to remove is a group with a unique child,
4308  // we can remove it as well
4309  ccHObject* parent = pc->getParent();
4310  if (parent && parent->isA(CV_TYPES::HIERARCHY_OBJECT) &&
4311  parent->getChildrenNumber() == 1 &&
4312  parent != firstCloudContext.parent)
4313  toRemove = parent;
4314  else
4315  toRemove = pc;
4316 
4317  AddToRemoveList(toRemove, toBeRemoved);
4318 
4319  if (ocIndexSF) {
4320  ScalarType index =
4321  static_cast<ScalarType>(++cloudIndex);
4322  for (unsigned i = 0; i < countAdded; ++i) {
4323  ocIndexSF->setValue(countBefore + i, index);
4324  }
4325  }
4326  } else {
4328  tr("Fusion failed! (not enough memory?)"));
4329  break;
4330  }
4331  pc = nullptr;
4332  }
4333  }
4334 
4335  if (ocIndexSF) {
4336  ocIndexSF->computeMinAndMax();
4337  firstCloud->showSF(true);
4338  }
4339 
4340  // something to remove?
4341  while (!toBeRemoved.empty()) {
4342  if (toBeRemoved.back() && m_ccRoot) {
4343  m_ccRoot->removeElement(toBeRemoved.back());
4344  }
4345  toBeRemoved.pop_back();
4346  }
4347 
4348  // put back first cloud in DB
4349  if (firstCloud) {
4350  putObjectBackIntoDBTree(firstCloud, firstCloudContext);
4351  if (m_ccRoot) m_ccRoot->selectEntity(firstCloud);
4352  }
4353  }
4354  // merge meshes?
4355  else if (!meshes.empty()) {
4356  bool createSubMeshes = true;
4357  // createSubMeshes = (QMessageBox::question(this, "Create sub-meshes",
4358  // "Do you want to create sub-mesh entities corresponding to each source
4359  // mesh? (requires more memory)", QMessageBox::Yes, QMessageBox::No) ==
4360  // QMessageBox::Yes);
4361 
4362  // meshes are merged
4363  ccPointCloud* baseVertices = new ccPointCloud("vertices");
4364  ccMesh* baseMesh = new ccMesh(baseVertices);
4365  baseMesh->setName("Merged mesh");
4366  baseMesh->addChild(baseVertices);
4367  baseVertices->setEnabled(false);
4368 
4369  for (ccMesh* mesh : meshes) {
4370  // if (mesh->isA(CV_TYPES::PRIMITIVE))
4371  //{
4372  // mesh = mesh->ccMesh::cloneMesh(); //we want a clone of the mesh
4373  // part, not the primitive!
4374  // }
4375 
4376  if (!baseMesh->merge(mesh, createSubMeshes)) {
4377  ecvConsole::Error(tr("Fusion failed! (not enough memory?)"));
4378  break;
4379  }
4380  }
4381 
4382  baseMesh->setVisible(true);
4383  addToDB(baseMesh);
4384 
4385  if (m_ccRoot) m_ccRoot->selectEntity(baseMesh);
4386  }
4387 
4388  updateUI();
4389 }
4390 
4391 void MainWindow::refreshAll(bool only2D /* = false*/,
4392  bool forceRedraw /* = true*/) {
4393  ecvDisplayTools::RedrawDisplay(only2D, forceRedraw);
4394 }
4395 
4396 void MainWindow::refreshSelected(bool only2D /* = false*/,
4397  bool forceRedraw /* = true*/) {
4399  for (ccHObject* entity : getSelectedEntities()) {
4400  if (entity) {
4401  entity->setRedrawFlagRecursive(true);
4402  }
4403  }
4404  ecvDisplayTools::RedrawDisplay(only2D, forceRedraw);
4405 }
4406 
4407 void MainWindow::refreshObject(ccHObject* obj, bool only2D, bool forceRedraw) {
4408  if (!obj) {
4409  return;
4410  }
4411 
4413  obj->setRedrawFlagRecursive(true);
4414  ecvDisplayTools::RedrawDisplay(only2D, forceRedraw);
4415 }
4416 
4418  bool only2D,
4419  bool forceRedraw) {
4421  for (ccHObject* entity : objs) {
4422  if (entity) {
4423  entity->setRedrawFlagRecursive(true);
4424  }
4425  }
4426  ecvDisplayTools::RedrawDisplay(only2D, forceRedraw);
4427 }
4428 
4430  for (ccHObject* entity : getSelectedEntities()) {
4431  if (entity) {
4433  }
4434  }
4435 }
4436 
4437 void MainWindow::toggleActiveWindowCenteredPerspective() {
4438  QWidget* win = getActiveWindow();
4439  if (win) {
4440  const ecvViewportParameters& params =
4442  // we need to check this only if we are already in object-centered
4443  // perspective mode
4444  if (params.perspectiveView && params.objectCenteredView) {
4445  return;
4446  }
4448  refreshAll(true, false);
4450  }
4451 }
4452 
4453 void MainWindow::toggleActiveWindowViewerBasedPerspective() {
4454  QWidget* win = getActiveWindow();
4455  if (win) {
4456  const ecvViewportParameters& params =
4458  // we need to check this only if we are already in viewer-based
4459  // perspective mode
4460  if (params.perspectiveView && !params.objectCenteredView) {
4461  return;
4462  }
4464  refreshAll(true, false);
4466  // updatePivotVisibilityPopUpMenu(win);
4467  }
4468 }
4469 
4470 void MainWindow::createSinglePointCloud() {
4471  // ask the user to input the point coordinates
4472  static CCVector3d s_lastPoint(0, 0, 0);
4473  static size_t s_lastPointIndex = 0;
4474  ccAskThreeDoubleValuesDlg axisDlg(
4475  "x", "y", "z", -1.0e12, 1.0e12, s_lastPoint.x, s_lastPoint.y,
4476  s_lastPoint.z, 4, tr("Point coordinates"), this);
4477  if (axisDlg.buttonBox->button(QDialogButtonBox::Ok))
4478  axisDlg.buttonBox->button(QDialogButtonBox::Ok)->setFocus();
4479  if (!axisDlg.exec()) return;
4480  s_lastPoint.x = axisDlg.doubleSpinBox1->value();
4481  s_lastPoint.y = axisDlg.doubleSpinBox2->value();
4482  s_lastPoint.z = axisDlg.doubleSpinBox3->value();
4483 
4484  // create the cloud
4485  ccPointCloud* cloud = new ccPointCloud();
4486  if (!cloud->reserve(1)) {
4487  delete cloud;
4488  CVLog::Error(tr("Not enough memory!"));
4489  return;
4490  }
4491  cloud->setName(tr("Point #%1").arg(++s_lastPointIndex));
4492  cloud->addPoint(CCVector3::fromArray(s_lastPoint.u));
4493  cloud->setPointSize(5);
4494 
4495  // add it to the DB tree
4496  addToDB(cloud, true, true, true, true);
4497 
4498  // select it
4499  m_ccRoot->unselectAllEntities();
4500  setSelectedInDB(cloud, true);
4501 }
4502 
4503 void MainWindow::createPointCloudFromClipboard() {
4504  const QClipboard* clipboard = QApplication::clipboard();
4505  assert(clipboard);
4506  const QMimeData* mimeData = clipboard->mimeData();
4507  if (!mimeData) {
4508  CVLog::Warning(tr("Clipboard is empty"));
4509  return;
4510  }
4511 
4512  if (!mimeData->hasText()) {
4513  CVLog::Error("ASCII/text data expected");
4514  return;
4515  }
4516 
4517  // try to convert the data to a point cloud
4518  FileIOFilter::LoadParameters parameters;
4519  {
4520  parameters.alwaysDisplayLoadDialog = true;
4521  parameters.shiftHandlingMode =
4523  parameters.parentWidget = this;
4524  }
4525 
4526  ccHObject container;
4527  QByteArray data = mimeData->data("text/plain");
4528  CC_FILE_ERROR result = AsciiFilter().loadAsciiData(data, tr("Clipboard"),
4529  container, parameters);
4530  if (result != CC_FERR_NO_ERROR) {
4532  tr("from the clipboard"));
4533  return;
4534  }
4535 
4536  // we only expect clouds
4537  ccHObject::Container clouds;
4538  if (container.filterChildren(clouds, true, CV_TYPES::POINT_CLOUD) == 0) {
4539  assert(false);
4540  CVLog::Error(tr("No cloud loaded"));
4541  return;
4542  }
4543 
4544  // detach the clouds from the loading container
4545  for (ccHObject* cloud : clouds) {
4546  if (cloud) {
4547  container.removeDependencyWith(cloud);
4548  }
4549  }
4550  container.removeAllChildren();
4551 
4552  // retrieve or create the group to store the 'clipboard' clouds
4553  ccHObject* clipboardGroup = nullptr;
4554  {
4555  static unsigned s_clipboardGroupID = 0;
4556 
4557  if (s_clipboardGroupID != 0) {
4558  clipboardGroup = dbRootObject()->find(s_clipboardGroupID);
4559  if (nullptr == clipboardGroup) {
4560  // can't find the previous group
4561  s_clipboardGroupID = 0;
4562  }
4563  }
4564 
4565  if (s_clipboardGroupID == 0) {
4566  clipboardGroup = new ccHObject(tr("Clipboard"));
4567  s_clipboardGroupID = clipboardGroup->getUniqueID();
4568  addToDB(clipboardGroup, false, false, false, false);
4569  }
4570  }
4571  assert(clipboardGroup);
4572 
4573  bool normalsDisplayedByDefault =
4575  for (ccHObject* cloud : clouds) {
4576  if (cloud) {
4577  clipboardGroup->addChild(cloud);
4578  cloud->setName(
4579  tr("Cloud #%1").arg(clipboardGroup->getChildrenNumber()));
4580 
4581  if (!normalsDisplayedByDefault) {
4582  // disable the normals on all loaded clouds!
4583  static_cast<ccGenericPointCloud*>(cloud)->showNormals(false);
4584  }
4585  }
4586  }
4587 
4588  // eventually, we can add the clouds to the DB tree
4589  for (size_t i = 0; i < clouds.size(); ++i) {
4590  ccHObject* cloud = clouds[i];
4591  if (cloud) {
4592  bool lastCloud = (i + 1 == clouds.size());
4593  addToDB(cloud, lastCloud, lastCloud, true, lastCloud);
4594  }
4595  }
4596 
4597  QMainWindow::statusBar()->showMessage(
4598  tr("%1 cloud(s) loaded from the clipboard").arg(clouds.size()),
4599  2000);
4600 }
4601 
4602 void MainWindow::removeFromDB(ccHObject* obj, bool autoDelete) {
4603  if (!obj) return;
4604 
4605  obj->removeFromRenderScreen(true);
4606 
4607  // remove dependency to avoid deleting the object when removing it from DB
4608  // tree
4609  if (!autoDelete && obj->getParent())
4610  obj->getParent()->removeDependencyWith(obj);
4611 
4612  if (m_ccRoot) m_ccRoot->removeElement(obj);
4613 }
4614 
4615 void MainWindow::setSelectedInDB(ccHObject* obj, bool selected) {
4616  if (obj && m_ccRoot) {
4617  if (selected)
4618  m_ccRoot->selectEntity(obj);
4619  else
4620  m_ccRoot->unselectEntity(obj);
4621  }
4622 }
4623 
4624 void MainWindow::freezeUI(bool state) {
4625  // freeze standard toolbar
4626  m_ui->menuBar->setDisabled(state);
4627  m_ui->DockableDBTree->setDisabled(state);
4628  m_ui->mainToolBar->setDisabled(state);
4629  m_ui->SFToolBar->setDisabled(state);
4630  m_ui->FilterToolBar->setDisabled(state);
4631  m_ui->AnnotationToolBar->setDisabled(state);
4632 
4633  // freeze plugin toolbars
4634  m_pluginUIManager->mainPluginToolbar()->setDisabled(state);
4635  for (QToolBar* toolbar : m_pluginUIManager->additionalPluginToolbars()) {
4636  toolbar->setDisabled(state);
4637  }
4638 
4639  if (!state) {
4640  updateMenus();
4641  }
4642 
4643  m_uiFrozen = state;
4644 }
4645 
4647  ccBBox bbox = getSelectedEntityBbox();
4648  if (bbox.isValid()) {
4650  } else {
4651  CVLog::Warning(tr("Selected entities have no valid bounding-box!"));
4652  }
4653 }
4654 
4656  if (obj) {
4657  ccBBox bbox = obj->getDisplayBB_recursive(false);
4658  if (bbox.isValid()) {
4660  } else {
4661  CVLog::Warning(tr("entity [%1] has no valid bounding-box!")
4662  .arg(obj->getName()));
4663  }
4664  }
4665 }
4666 
4671  }
4672 }
4673 
4674 //=============================================================================
4675 // SELECTION TOOLS - Using centralized cvSelectionToolController
4676 // (ParaView-style)
4677 //=============================================================================
4678 
4679 #if defined(USE_PCL_BACKEND)
4680 void MainWindow::initSelectionController() {
4681  // Get the singleton controller
4682  m_selectionController = cvSelectionToolController::instance();
4683  m_selectionController->initialize(this);
4684 
4685  // Set visualizer
4687  if (viewer) {
4688  m_selectionController->setVisualizer(viewer);
4689  }
4690 
4691  // Setup all actions using the SelectionActions struct
4692  cvSelectionToolController::SelectionActions actions;
4693  actions.selectSurfaceCells = m_ui->actionSelectSurfaceCells;
4694  actions.selectSurfacePoints = m_ui->actionSelectSurfacePoints;
4695  actions.selectFrustumCells = m_ui->actionSelectFrustumCells;
4696  actions.selectFrustumPoints = m_ui->actionSelectFrustumPoints;
4697  actions.selectPolygonCells = m_ui->actionSelectPolygonCells;
4698  actions.selectPolygonPoints = m_ui->actionSelectPolygonPoints;
4699  actions.selectBlocks = m_ui->actionSelectBlocks;
4700  actions.selectFrustumBlocks = m_ui->actionSelectFrustumBlocks;
4701  actions.interactiveSelectCells = m_ui->actionInteractiveSelectCells;
4702  actions.interactiveSelectPoints = m_ui->actionInteractiveSelectPoints;
4703  actions.hoverCells = m_ui->actionHoverCells;
4704  actions.hoverPoints = m_ui->actionHoverPoints;
4705  actions.addSelection = m_ui->actionAddSelection;
4706  actions.subtractSelection = m_ui->actionSubtractSelection;
4707  actions.toggleSelection = m_ui->actionToggleSelection;
4708  actions.growSelection = m_ui->actionGrowSelection;
4709  actions.shrinkSelection = m_ui->actionShrinkSelection;
4710  actions.clearSelection = m_ui->actionClearSelection;
4711  actions.zoomToBox = m_ui->actionZoomToBox;
4712 
4713  m_selectionController->setupActions(actions);
4714 
4715  // Connect controller signals to MainWindow slots
4716  connect(m_selectionController,
4717  &cvSelectionToolController::selectionFinished, this,
4718  &MainWindow::onSelectionFinished);
4719 
4720  // Note: Selection tool state is now decoupled from Find Data dock
4721  // visibility (per ParaView design). The dock can be shown/hidden
4722  // independently by the user through the View menu. Selection tools work
4723  // regardless of dock visibility.
4724  connect(m_selectionController,
4725  &cvSelectionToolController::selectionToolStateChanged, this,
4726  [this](bool active) {
4727  Q_UNUSED(active);
4728  // Dock visibility is now user-controlled, not tied to tool
4729  // state The user can show/hide the Find Data dock independently
4730  });
4731 
4732  connect(m_selectionController,
4733  &cvSelectionToolController::selectionPropertiesUpdateRequested,
4734  this, [this](const cvSelectionData& data) {
4735  // Update the Find Data dock with new selection data
4736  if (m_findDataDock) {
4737  m_findDataDock->updateSelection(data);
4738  }
4739  });
4740 
4741  // CRITICAL FIX: Connect properties delegate's clear request to selection
4742  // manager This prevents crashes from dangling pointers when objects are
4743  // deleted
4744  if (m_ccRoot && m_ccRoot->getPropertiesDelegate()) {
4745  connect(m_ccRoot->getPropertiesDelegate(),
4747  [this]() {
4748  CVLog::Print(
4749  "[MainWindow] Clearing selection data due to "
4750  "object changes");
4751  auto* manager = getSelectionManager();
4752  if (manager) {
4753  manager->clearCurrentSelection();
4754  }
4755  // Also clear highlights
4756  if (m_selectionController &&
4757  m_selectionController->highlighter()) {
4758  m_selectionController->highlighter()->clearHighlights();
4759  }
4760  });
4761  }
4762 
4763  // Connect zoom to box signal for notification (zoom is handled by
4764  // cvZoomToBoxTool)
4765  connect(m_selectionController,
4766  &cvSelectionToolController::zoomToBoxRequested, this,
4767  [this](int xmin, int ymin, int xmax, int ymax) {
4769  QString("[MainWindow] Zoom to box completed: [%1, "
4770  "%2, %3, %4]")
4771  .arg(xmin)
4772  .arg(ymin)
4773  .arg(xmax)
4774  .arg(ymax));
4775  // Zoom is already performed by cvZoomToBoxTool using VTK
4776  // This signal is for notification/logging purposes
4778  // CRITICAL: Update 2D labels after zoom to box to ensure they
4779  // align with their 3D anchor points
4781  });
4782 
4783  // Set the properties delegate for the controller
4784  if (m_ccRoot && m_ccRoot->getPropertiesDelegate()) {
4785  m_selectionController->setPropertiesDelegate(
4786  m_ccRoot->getPropertiesDelegate());
4787  }
4788 
4789  if (m_findDataDock) {
4791  cvSelectionHighlighter* highlighter =
4792  m_selectionController->highlighter();
4793  cvViewSelectionManager* manager = getSelectionManager();
4794 
4796  QString("[MainWindow::initSelectionController] Calling "
4797  "configure: "
4798  "highlighter=%1, manager=%2, visualizer=%3")
4799  .arg(highlighter != nullptr)
4800  .arg(manager != nullptr)
4801  .arg(visualizer != nullptr));
4802 
4803  m_findDataDock->configure(highlighter, manager, visualizer);
4804  } else {
4806  "[MainWindow::initSelectionController] m_findDataDock is "
4807  "nullptr!");
4808  }
4809 }
4810 
4811 void MainWindow::disableAllSelectionTools(void* except) {
4812  // Delegate to the controller - it handles all tool management
4813  if (m_selectionController) {
4814  // Pass nullptr to disable all tools
4815  m_selectionController->disableAllTools(nullptr);
4816  }
4817 }
4818 
4819 cvViewSelectionManager* MainWindow::getSelectionManager() const {
4820  if (m_selectionController) {
4821  return m_selectionController->manager();
4822  }
4823  return nullptr;
4824 }
4825 
4826 void MainWindow::onSelectionFinished(const cvSelectionData& selectionData) {
4827  // CRITICAL FIX: Don't call setCurrentSelection here!
4828  // The tool has already set it via manager->setCurrentSelection()
4829  // Calling it again causes infinite recursion:
4830  // setCurrentSelection → selectionChanged → selectionFinished → here →
4831  // setCurrentSelection...
4832 
4833  // Get manager from controller
4834  cvViewSelectionManager* manager = getSelectionManager();
4835  if (!manager) {
4836  return;
4837  }
4838 
4839  // NOTE: Selection is already stored by the tool/controller
4840  // We just need to update the UI based on current selection state
4841  bool hasSelection = !selectionData.isEmpty();
4842 
4843  // Enable/disable manipulation actions based on selection state
4844  m_ui->actionGrowSelection->setEnabled(hasSelection);
4845  m_ui->actionShrinkSelection->setEnabled(hasSelection);
4846  m_ui->actionClearSelection->setEnabled(hasSelection);
4847 
4848  // Update Find Data dock widget with selection data
4849  // (Selection properties are now in standalone cvFindDataDockWidget)
4850  if (m_findDataDock) {
4851  m_findDataDock->updateSelection(selectionData);
4852  }
4853 
4854  // NOTE: Highlighting is already done in
4855  // cvRenderViewSelectionReaction::finalizeSelection() Do NOT call
4856  // highlighter->highlightSelection() here again as it causes double
4857  // highlighting and potential crashes due to actor management issues. We
4858  // only need to clear highlights when selection is empty.
4859  if (!hasSelection) {
4860  cvSelectionHighlighter* highlighter =
4861  m_selectionController ? m_selectionController->highlighter()
4862  : nullptr;
4863  if (highlighter) {
4864  highlighter->clearHighlights();
4865  }
4866  }
4867 
4869 
4870  CVLog::PrintDebug(QString("[MainWindow] Selection UI updated: %1 elements")
4871  .arg(selectionData.count()));
4872 }
4873 
4874 void MainWindow::onSelectionToolActivated(QAction* action) {
4875  bool isSelectionTool = (action && action->isChecked());
4876 
4878  QString("[MainWindow] Selection tool %1: %2")
4879  .arg(action ? action->text() : "unknown")
4880  .arg(isSelectionTool ? "activated" : "deactivated"));
4881 
4882  // Set visualizer for other property editors if needed
4883  if (m_ccRoot && m_ccRoot->getPropertiesDelegate()) {
4884  if (isSelectionTool) {
4886  if (viewer) {
4887  m_ccRoot->getPropertiesDelegate()->setVisualizer(viewer);
4888  }
4889  }
4890  }
4891 }
4892 
4893 void MainWindow::onSelectionRestored(const cvSelectionData& selection) {
4894  cvViewSelectionManager* manager = getSelectionManager();
4895  if (manager) {
4896  manager->setCurrentSelection(selection);
4897  CVLog::PrintDebug(QString("[MainWindow] Selection restored: %1 %2")
4898  .arg(selection.count())
4899  .arg(selection.fieldTypeString()));
4900  if (m_ccRoot) {
4901  m_ccRoot->updatePropertiesView();
4902  }
4903  }
4904 }
4905 #endif
4906 
4907 //=============================================================================
4908 // SELECTION TOOLS - Using centralized cvSelectionToolController
4909 // (ParaView-style)
4910 //=============================================================================
4911 
4914  ecvDisplayTools::GetViewportParameters().defaultPointSize + 1);
4915  refreshAll();
4916 }
4917 
4920  ecvDisplayTools::GetViewportParameters().defaultPointSize - 1);
4921  refreshAll();
4922 }
4923 
4924 void MainWindow::setupInputDevices() {
4925 #ifdef CC_3DXWARE_SUPPORT
4926  m_3DMouseManager = new cc3DMouseManager(this, this);
4927  m_ui->menuFile->insertMenu(m_UI->actionCloseAll, m_3DMouseManager->menu());
4928 #endif
4929 
4930 #ifdef CC_GAMEPAD_SUPPORT
4931  m_gamepadManager = new ccGamepadManager(this, this);
4932  m_ui->menuFile->insertMenu(m_ui->actionClearAll, m_gamepadManager->menu());
4933 #endif
4934 
4935 #if defined(CC_3DXWARE_SUPPORT) || defined(CC_GAMEPAD_SUPPORT)
4936  m_ui->menuFile->insertSeparator(m_ui->actionClearAll);
4937 #endif
4938 }
4939 
4940 void MainWindow::destroyInputDevices() {
4941 #ifdef CC_GAMEPAD_SUPPORT
4942  delete m_gamepadManager;
4943  m_gamepadManager = nullptr;
4944 #endif
4945 
4946 #ifdef CC_3DXWARE_SUPPORT
4947  delete m_3DMouseManager;
4948  m_3DMouseManager = nullptr;
4949 #endif
4950 }
4951 
4952 void MainWindow::showDisplayOptions() {
4953  ccDisplayOptionsDlg displayOptionsDlg(this);
4954  connect(&displayOptionsDlg, &ccDisplayOptionsDlg::aspectHasChanged, this,
4955  [=]() { refreshAll(); });
4956 
4957  displayOptionsDlg.exec();
4958 
4959  disconnect(&displayOptionsDlg);
4960 }
4961 
4965  }
4966 
4968 
4970 }
4971 
4975  }
4976 
4977  setOrthoView();
4978 
4980 }
4981 
4983  updateUIWithSelection();
4984  updateMenus();
4986 }
4987 
4989  if (m_ccRoot) {
4990  m_ccRoot->updatePropertiesView();
4991  }
4992 }
4993 
4994 void MainWindow::enablePickingOperation(QString message) {
4995  assert(m_pickingHub);
4996  if (!m_pickingHub->addListener(this)) {
4997  CVLog::Error(
4998  tr("Can't start the picking mechanism (another tool is already "
4999  "using it)"));
5000  return;
5001  }
5002 
5003  // specific case: we prevent the 'point-pair based alignment' tool to
5004  // process the picked point! if (m_pprDlg) m_pprDlg->pause(true);
5005 
5007  message, ecvDisplayTools::LOWER_LEFT_MESSAGE, true, 24 * 3600);
5008  ecvDisplayTools::RedrawDisplay(true, true);
5009 
5010  freezeUI(true);
5011 }
5012 
5014  switch (s_currentPickingOperation) {
5016  // nothing to do
5017  break;
5018  case PICKING_LEVEL_POINTS:
5019  if (s_levelMarkersCloud) {
5021  delete s_levelMarkersCloud;
5022  s_levelMarkersCloud = nullptr;
5023  }
5024  break;
5025  default:
5026  // assert(false);
5027  break;
5028  }
5029 
5030  if (aborted) {
5032  QString(),
5033  ecvDisplayTools::LOWER_LEFT_MESSAGE); // clear previous
5034  // messages
5035  ecvDisplayTools::DisplayNewMessage("Picking operation aborted",
5037  }
5039  ecvDisplayTools::RedrawDisplay(true, false);
5040 
5041  // specific case: we allow the 'point-pair based alignment' tool to process
5042  // the picked point!
5043  if (m_pprDlg) m_pprDlg->pause(false);
5044 
5045  freezeUI(false);
5046 
5047  m_pickingHub->removeListener(this);
5049 }
5050 
5052  if (!m_pickingHub) {
5053  return;
5054  }
5055 
5056  if (!pi.entity) {
5057  return;
5058  } else {
5059  pi.entity->setRedrawFlagRecursive(false);
5060  }
5061 
5062  CCVector3 pickedPoint = pi.P3D;
5063  switch (s_currentPickingOperation) {
5064  case PICKING_LEVEL_POINTS: {
5065  if (!s_levelMarkersCloud) {
5066  assert(false);
5068  }
5069 
5070  for (unsigned i = 0; i < s_levelMarkersCloud->size(); ++i) {
5071  const CCVector3* P = s_levelMarkersCloud->getPoint(i);
5072  if ((pickedPoint - *P).norm() < 1.0e-6) {
5074  tr("[Level] Point is too close from the others!"));
5075  return;
5076  }
5077  }
5078 
5079  // add the corresponding marker
5080  s_levelMarkersCloud->addPoint(pickedPoint);
5081  unsigned markerCount = s_levelMarkersCloud->size();
5082  cc2DLabel* label = new cc2DLabel();
5083  label->addPickedPoint(s_levelMarkersCloud, markerCount - 1);
5084  label->setName(tr("P#%1").arg(markerCount));
5085  label->setDisplayedIn2D(false);
5086  label->setVisible(true);
5087  s_levelMarkersCloud->addChild(label);
5089 
5090  if (markerCount == 3) {
5091  // we have enough points!
5092  const CCVector3* A = s_levelMarkersCloud->getPoint(0);
5093  const CCVector3* B = s_levelMarkersCloud->getPoint(1);
5094  const CCVector3* C = s_levelMarkersCloud->getPoint(2);
5095  CCVector3 X = *B - *A;
5096  CCVector3 Y = *C - *A;
5097  CCVector3 Z = X.cross(Y);
5098  // we choose 'Z' so that it points 'upward' relatively to the
5099  // camera (assuming the user will be looking from the top)
5101  if (CCVector3d::fromArray(Z.u).dot(viewDir) > 0) {
5102  Z = -Z;
5103  }
5104  Y = Z.cross(X);
5105  X.normalize();
5106  Y.normalize();
5107  Z.normalize();
5108 
5109  ccGLMatrixd trans;
5110  double* mat = trans.data();
5111  mat[0] = X.x;
5112  mat[4] = X.y;
5113  mat[8] = X.z;
5114  mat[12] = 0;
5115  mat[1] = Y.x;
5116  mat[5] = Y.y;
5117  mat[9] = Y.z;
5118  mat[13] = 0;
5119  mat[2] = Z.x;
5120  mat[6] = Z.y;
5121  mat[10] = Z.z;
5122  mat[14] = 0;
5123  mat[3] = 0;
5124  mat[7] = 0;
5125  mat[11] = 0;
5126  mat[15] = 1;
5127 
5129  trans.apply(T);
5130  T += CCVector3d::fromArray(A->u);
5131  trans.setTranslation(T);
5132 
5133  assert(haveOneSelection() &&
5134  m_selectedEntities.front() == s_levelEntity);
5135  applyTransformation(trans);
5136 
5137  // clear message
5140  false); // clear previous message
5142  } else {
5143  // we need more points!
5144  return;
5145  }
5146  }
5147  // we use the next 'case' entry (PICKING_ROTATION_CENTER) to
5148  // redefine the rotation center as well!
5149  assert(s_levelMarkersCloud && s_levelMarkersCloud->size() != 0);
5150  pickedPoint = *s_levelMarkersCloud->getPoint(0);
5151  // break;
5152 
5153  case PICKING_ROTATION_CENTER: {
5154  CCVector3d newPivot = CCVector3d::fromArray(pickedPoint.u);
5155  // specific case: transformation tool is enabled
5156  if (m_transTool && m_transTool->started()) {
5157  m_transTool->setRotationCenter(newPivot);
5158  const unsigned& precision =
5163  false); // clear previous message
5165  tr("Point (%1 ; %2 ; %3) set as rotation center for "
5166  "interactive transformation")
5167  .arg(pickedPoint.x, 0, 'f', precision)
5168  .arg(pickedPoint.y, 0, 'f', precision)
5169  .arg(pickedPoint.z, 0, 'f', precision),
5171  } else {
5172  const ecvViewportParameters& params =
5174  if (!params.perspectiveView || params.objectCenteredView) {
5175  // apply current GL transformation (if any)
5176  pi.entity->getGLTransformation().apply(newPivot);
5177  ecvDisplayTools::SetPivotPoint(newPivot, true, true);
5178  }
5179  }
5180  // s_pickingWindow->redraw(); //already called by
5181  // 'cancelPreviousPickingOperation' (see below)
5182  } break;
5183 
5184  default:
5185  assert(false);
5186  break;
5187  }
5188 
5190 }
5191 
5193 
5194 void MainWindow::showEvent(QShowEvent* event) {
5195  QMainWindow::showEvent(event);
5196  // Update memory usage widget size when window is shown
5197  updateMemoryUsageWidgetSize();
5198 
5199  if (!m_FirstShow) {
5200  return;
5201  }
5202 
5203  // Use layout manager to restore or setup layout
5204  if (m_layoutManager) {
5205  m_layoutManager->restoreGUILayout(false);
5206  // After restoring layout, ensure all toolbar icon sizes are updated
5207  // This is critical because restoreGUILayout may restore saved icon
5208  // sizes
5209  updateAllToolbarIconSizes();
5210  } else {
5211  CVLog::Error("[MainWindow] Layout manager is not initialized!");
5212  }
5213 
5214  m_FirstShow = false;
5215 
5216  if (isFullScreen()) {
5217  m_ui->actionFullScreen->setChecked(true);
5218  }
5219 }
5220 
5221 // exit event
5222 void MainWindow::closeEvent(QCloseEvent* event) {
5223  // Check if we should ask for confirmation before quitting
5224  bool askForConfirmation =
5226 
5227  // If no entities loaded, quit without asking
5228  if (m_ccRoot && m_ccRoot->getRootEntity()->getChildrenNumber() == 0) {
5229  event->accept();
5232  }
5233  return;
5234  }
5235 
5236  // If confirmation is disabled, quit directly
5237  if (!askForConfirmation) {
5238  event->accept();
5241  }
5242  return;
5243  }
5244 
5245  // Create custom message box with three buttons (similar to CloudCompare)
5246  QMessageBox msgBox(this);
5247  msgBox.setWindowTitle(tr("Quit"));
5248  msgBox.setText(tr("Are you sure you want to quit?"));
5249  msgBox.setIcon(QMessageBox::Question);
5250 
5251  // Add three buttons: No, Yes (don't ask again), Yes
5252  QPushButton* noButton = msgBox.addButton(tr("No"), QMessageBox::RejectRole);
5253  QPushButton* yesDontAskButton = msgBox.addButton(tr("Yes, don't ask again"),
5254  QMessageBox::ActionRole);
5255  QPushButton* yesButton =
5256  msgBox.addButton(tr("Yes"), QMessageBox::AcceptRole);
5257 
5258  // Set default button
5259  msgBox.setDefaultButton(noButton);
5260 
5261  // Show the dialog
5262  msgBox.exec();
5263 
5264  // Handle the result
5265  QAbstractButton* clickedButton = msgBox.clickedButton();
5266  if (clickedButton == yesButton || clickedButton == yesDontAskButton) {
5267  // User wants to quit
5268  if (clickedButton == yesDontAskButton) {
5269  // User clicked "Yes, don't ask again" - disable confirmation and
5270  // save setting
5271  ecvOptions options = ecvOptions::Instance();
5272  options.askForConfirmationBeforeQuitting = false;
5273  ecvOptions::Set(options);
5274  options.toPersistentSettings();
5275  }
5276  event->accept();
5277  } else {
5278  // User clicked "No" or closed the dialog
5279  event->ignore();
5280  }
5281 
5284  }
5285 }
5286 
5288  // Use layout manager to save layout
5289  if (m_layoutManager) {
5290  m_layoutManager->saveGUILayout();
5291  } else {
5292  CVLog::Error("[MainWindow] Layout manager is not initialized!");
5293  }
5294 }
5295 
5296 void MainWindow::doShowPrimitiveFactory() {
5297  if (!m_pfDlg) m_pfDlg = new ecvPrimitiveFactoryDlg(this);
5298 
5299  m_pfDlg->setModal(false);
5300  m_pfDlg->setWindowModality(Qt::NonModal);
5301  m_pfDlg->show();
5302 }
5303 
5304 void MainWindow::doCheckForUpdate() {
5305  if (m_updateDlg) {
5306  m_updateDlg->setModal(false);
5307  m_updateDlg->setWindowModality(Qt::NonModal);
5308  m_updateDlg->show();
5309  }
5310 }
5311 
5312 void MainWindow::doActionComputeNormals() {
5313  if (!ccEntityAction::computeNormals(m_selectedEntities, this)) return;
5314 
5315  refreshSelected();
5316  updateUI();
5317 }
5318 
5319 void MainWindow::doActionInvertNormals() {
5320  if (!ccEntityAction::invertNormals(m_selectedEntities)) return;
5321 
5322  refreshSelected();
5323 }
5324 
5325 void MainWindow::doActionConvertNormalsToDipDir() {
5327  m_selectedEntities,
5329  return;
5330  }
5331 
5332  refreshSelected();
5333  updateUI();
5334 }
5335 
5336 void MainWindow::doActionExportNormalToSF() {
5337  if (!ccEntityAction::exportNormalToSF(m_selectedEntities, this)) {
5338  return;
5339  }
5340 
5341  refreshSelected();
5342  updateUI();
5343 }
5344 
5345 void MainWindow::doActionConvertNormalsToHSV() {
5347  m_selectedEntities,
5349  return;
5350  }
5351 
5352  refreshSelected();
5353  updateUI();
5354 }
5355 
5356 void MainWindow::doActionOrientNormalsMST() {
5357  if (!ccEntityAction::orientNormalsMST(m_selectedEntities, this)) return;
5358 
5359  refreshSelected();
5360  updateUI();
5361 }
5362 
5363 void MainWindow::doActionOrientNormalsFM() {
5364  if (!ccEntityAction::orientNormalsFM(m_selectedEntities, this)) return;
5365 
5366  refreshSelected();
5367  updateUI();
5368 }
5369 
5370 static double s_kdTreeMaxErrorPerCell = 0.1;
5371 void MainWindow::doActionComputeKdTree() {
5372  ccGenericPointCloud* cloud = nullptr;
5373 
5374  if (haveOneSelection()) {
5375  ccHObject* ent = m_selectedEntities.back();
5376  bool lockedVertices;
5377  cloud = ccHObjectCaster::ToGenericPointCloud(ent, &lockedVertices);
5378  if (lockedVertices) {
5380  return;
5381  }
5382  }
5383 
5384  if (!cloud) {
5385  CVLog::Error(tr("Selected one and only one point cloud or mesh!"));
5386  return;
5387  }
5388 
5389  bool ok;
5390  s_kdTreeMaxErrorPerCell = QInputDialog::getDouble(
5391  this, tr("Compute Kd-tree"), tr("Max error per leaf cell:"),
5392  s_kdTreeMaxErrorPerCell, 1.0e-6, 1.0e6, 6, &ok);
5393  if (!ok) return;
5394 
5395  ecvProgressDialog pDlg(true, this);
5396 
5397  // computation
5398  QElapsedTimer eTimer;
5399  eTimer.start();
5400  ccKdTree* kdtree = new ccKdTree(cloud);
5401 
5402  if (kdtree->build(
5405  1000, &pDlg)) {
5406  qint64 elapsedTime_ms = eTimer.elapsed();
5407 
5408  ecvConsole::Print("[doActionComputeKdTree] Timing: %2.3f s",
5409  static_cast<double>(elapsedTime_ms) / 1.0e3);
5410  cloud->setEnabled(true); // for mesh vertices!
5411  cloud->addChild(kdtree);
5412  kdtree->setVisible(true);
5413 
5414 #ifdef QT_DEBUG
5415  kdtree->convertCellIndexToSF();
5416 #else
5418 #endif
5419 
5420  addToDB(kdtree);
5421  // update added point cloud
5422  refreshObject(cloud);
5423  updateUI();
5424  } else {
5425  CVLog::Error(tr("An error occurred!"));
5426  delete kdtree;
5427  kdtree = nullptr;
5428  }
5429 }
5430 
5431 void MainWindow::doActionComputeOctree() {
5432  if (!ccEntityAction::computeOctree(m_selectedEntities, this)) return;
5433 
5434  refreshSelected();
5435  updateUI();
5436 }
5437 
5438 void MainWindow::doActionResampleWithOctree() {
5439  bool ok;
5440  int pointCount = QInputDialog::getInt(this, tr("Resample with octree"),
5441  tr("Points (approx.)"), 1000000, 1,
5442  INT_MAX, 100000, &ok);
5443  if (!ok) return;
5444 
5445  ecvProgressDialog pDlg(false, this);
5446  pDlg.setAutoClose(false);
5447 
5448  assert(pointCount > 0);
5449  unsigned aimedPoints = static_cast<unsigned>(pointCount);
5450 
5451  bool errors = false;
5452 
5453  for (ccHObject* entity : getSelectedEntities()) {
5454  ccPointCloud* cloud = nullptr;
5455 
5456  /*if (ent->isKindOf(CV_TYPES::MESH)) //TODO
5457  cloud =
5458  ccHObjectCaster::ToGenericMesh(ent)->getAssociatedCloud(); else */
5459  if (entity->isKindOf(CV_TYPES::POINT_CLOUD)) {
5460  cloud = static_cast<ccPointCloud*>(entity);
5461  }
5462 
5463  if (cloud) {
5464  ccOctree::Shared octree = cloud->getOctree();
5465  if (!octree) {
5466  octree = cloud->computeOctree(&pDlg);
5467  if (!octree) {
5469  tr("Could not compute octree for cloud '%1'")
5470  .arg(cloud->getName()));
5471  continue;
5472  }
5473  }
5474 
5475  cloud->setEnabled(false);
5476  QElapsedTimer eTimer;
5477  eTimer.start();
5480  cloud, aimedPoints,
5482  CELL_GRAVITY_CENTER,
5483  &pDlg, octree.data());
5484 
5485  if (result) {
5486  ecvConsole::Print("[ResampleWithOctree] Timing: %3.2f s.",
5487  eTimer.elapsed() / 1.0e3);
5488  ccPointCloud* newCloud = ccPointCloud::From(result, cloud);
5489 
5490  delete result;
5491  result = nullptr;
5492 
5493  if (newCloud) {
5494  addToDB(newCloud);
5495  } else {
5496  errors = true;
5497  }
5498  }
5499  }
5500  }
5501 
5502  if (errors) {
5503  CVLog::Error(
5504  tr("[ResampleWithOctree] Errors occurred during the process! "
5505  "Result may be incomplete!"));
5506  }
5507 }
5508 
5509 void MainWindow::doActionComputeMeshAA() {
5510  doActionComputeMesh(cloudViewer::DELAUNAY_2D_AXIS_ALIGNED);
5511 }
5512 
5513 void MainWindow::doActionComputeMeshLS() {
5514  doActionComputeMesh(cloudViewer::DELAUNAY_2D_BEST_LS_PLANE);
5515 }
5516 
5517 void MainWindow::doActionConvexHull() {
5518  if (!haveSelection()) {
5519  return;
5520  }
5521 
5522  ccHObject::Container clouds;
5523  for (auto ent : getSelectedEntities()) {
5524  if (!ent->isKindOf(CV_TYPES::POINT_CLOUD)) {
5525  CVLog::Warning("only point cloud is supported!");
5526  continue;
5527  }
5528  clouds.push_back(ent);
5529  }
5530 
5531  ccHObject::Container meshes;
5532  if (ccEntityAction::ConvexHull(clouds, meshes, this)) {
5533  for (size_t i = 0; i < meshes.size(); ++i) {
5534  addToDB(meshes[i]);
5535  }
5536  } else {
5537  ecvConsole::Error(tr("Error(s) occurred! See the Console messages"));
5538  return;
5539  }
5540 
5541  updateUI();
5542 }
5543 
5544 void MainWindow::doActionPoissonReconstruction() {
5545  if (!haveSelection()) {
5546  return;
5547  }
5548 
5549  // select candidates
5550  ecvPoissonReconDlg prpDlg(this);
5551  {
5552  for (ccHObject* entity : getSelectedEntities()) {
5553  if (!prpDlg.addEntity(entity)) {
5554  }
5555  }
5556  }
5557 
5558  if (prpDlg.start()) {
5559  ccHObject::Container& meshes = prpDlg.getReconstructions();
5560  for (size_t i = 0; i < meshes.size(); ++i) {
5561  addToDB(meshes[i]);
5562  }
5563  } else {
5564  ecvConsole::Error(tr("Error(s) occurred! See the Console messages"));
5565  }
5566 
5567  updateUI();
5568 }
5569 
5570 void MainWindow::doActionComputeMesh(cloudViewer::TRIANGULATION_TYPES type) {
5571  // ask the user for the max edge length
5572  static double s_meshMaxEdgeLength = 0.0;
5573  {
5574  bool ok = true;
5575  double maxEdgeLength = QInputDialog::getDouble(
5576  this, tr("Triangulate"), tr("Max edge length (0 = no limit)"),
5577  s_meshMaxEdgeLength, 0, 1.0e9, 8, &ok);
5578  if (!ok) return;
5579  s_meshMaxEdgeLength = maxEdgeLength;
5580  }
5581 
5582  // select candidates
5583  ccHObject::Container clouds;
5584  bool hadNormals = false;
5585  {
5586  for (ccHObject* entity : getSelectedEntities()) {
5587  if (entity->isKindOf(CV_TYPES::POINT_CLOUD)) {
5588  clouds.push_back(entity);
5589  if (entity->isA(CV_TYPES::POINT_CLOUD)) {
5590  hadNormals |=
5591  static_cast<ccPointCloud*>(entity)->hasNormals();
5592  }
5593  }
5594  }
5595  }
5596 
5597  // if the cloud(s) already had normals, ask the use if wants to update them
5598  // or keep them as is (can look strange!)
5599  bool updateNormals = false;
5600  if (hadNormals) {
5601  updateNormals =
5602  (QMessageBox::question(
5603  this, tr("Keep old normals?"),
5604  tr("Cloud(s) already have normals. Do you want to "
5605  "update them (yes) or keep the old ones (no)?"),
5606  QMessageBox::Yes,
5607  QMessageBox::No) == QMessageBox::Yes);
5608  }
5609 
5610  ecvProgressDialog pDlg(false, this);
5611  pDlg.setAutoClose(false);
5612  pDlg.setWindowTitle(tr("Triangulation"));
5613  pDlg.setInfo(tr("Triangulation in progress..."));
5614  pDlg.setRange(0, 0);
5615  pDlg.show();
5616  QApplication::processEvents();
5617 
5618  bool errors = false;
5619  for (size_t i = 0; i < clouds.size(); ++i) {
5620  ccHObject* ent = clouds[i];
5621  assert(ent->isKindOf(CV_TYPES::POINT_CLOUD));
5622 
5623  // compute mesh
5625  ccMesh* mesh = ccMesh::Triangulate(
5626  cloud, type, updateNormals,
5627  static_cast<PointCoordinateType>(s_meshMaxEdgeLength),
5628  2 // XY plane by default
5629  );
5630  if (mesh) {
5631  cloud->setVisible(false); // can't disable the cloud as the
5632  // resulting mesh will be its child!
5633  cloud->addChild(mesh);
5634  addToDB(mesh);
5635  if (i == 0) {
5636  m_ccRoot->selectEntity(mesh); // auto-select first element
5637  }
5638  } else {
5639  errors = true;
5640  }
5641  }
5642 
5643  if (errors) {
5644  ecvConsole::Error(tr("Error(s) occurred! See the Console messages"));
5645  }
5646 
5647  updateUI();
5648 }
5649 
5650 void MainWindow::doMeshTwoPolylines() {
5651  if (m_selectedEntities.size() != 2) return;
5652 
5653  ccPolyline* p1 = ccHObjectCaster::ToPolyline(m_selectedEntities[0]);
5654  ccPolyline* p2 = ccHObjectCaster::ToPolyline(m_selectedEntities[1]);
5655  if (!p1 || !p2) {
5656  ecvConsole::Error(tr("Select 2 and only 2 polylines"));
5657  return;
5658  }
5659 
5660  // Ask the user how the 2D projection should be computed
5661  bool useViewingDir = false;
5662  CCVector3 viewingDir(0, 0, 0);
5664  useViewingDir =
5665  (QMessageBox::question(this, tr("Projection method"),
5666  tr("Use best fit plane (yes) or the "
5667  "current viewing direction (no)"),
5668  QMessageBox::Yes,
5669  QMessageBox::No) == QMessageBox::No);
5670  if (useViewingDir) {
5671  viewingDir = -CCVector3::fromArray(
5673  }
5674  }
5675 
5677  p1, p2, useViewingDir ? &viewingDir : 0);
5678  if (mesh) {
5679  addToDB(mesh);
5680  if (mesh->computePerVertexNormals()) {
5681  mesh->showNormals(true);
5682  } else {
5684  tr("[Mesh two polylines] Failed to compute normals!"));
5685  }
5686  } else {
5687  CVLog::Error(tr("Failed to create mesh (see Console)"));
5689  }
5690 }
5691 
5692 void MainWindow::doActionMeshScanGrids() {
5693  // ask the user for the min angle (inside triangles)
5694  static double s_meshMinTriangleAngle_deg = 1.0;
5695  {
5696  bool ok = true;
5697  double minAngle_deg = QInputDialog::getDouble(
5698  this, tr("Triangulate"), tr("Min triangle angle (in degrees)"),
5699  s_meshMinTriangleAngle_deg, 0, 90.0, 3, &ok);
5700  if (!ok) return;
5701  s_meshMinTriangleAngle_deg = minAngle_deg;
5702  }
5703 
5704  // look for clouds with scan grids
5705  for (ccHObject* entity : getSelectedEntities()) {
5706  if (!entity || !entity->isA(CV_TYPES::POINT_CLOUD)) {
5707  continue;
5708  }
5709 
5711  assert(cloud);
5712 
5713  for (size_t i = 0; i < cloud->gridCount(); ++i) {
5714  ccPointCloud::Grid::Shared grid = cloud->grid(i);
5715  if (!grid) {
5716  assert(false);
5717  continue;
5718  }
5719 
5720  ccMesh* gridMesh =
5721  cloud->triangulateGrid(*grid, s_meshMinTriangleAngle_deg);
5722  if (gridMesh) {
5723  cloud->addChild(gridMesh);
5724  cloud->setVisible(false); // hide the cloud
5725  addToDB(gridMesh, false, true, false, false);
5726  }
5727  }
5728  }
5729 
5730  updateUI();
5731 }
5732 
5733 void MainWindow::doActionComputeDistancesFromSensor() {
5734  // we support more than just one sensor in selection
5735  if (!haveSelection()) {
5736  ecvConsole::Error("Select at least a sensor.");
5737  return;
5738  }
5739 
5740  // start dialog
5741  ccSensorComputeDistancesDlg cdDlg(this);
5742  if (!cdDlg.exec()) return;
5743 
5744  for (ccHObject* entity : getSelectedEntities()) {
5745  ccSensor* sensor = ccHObjectCaster::ToSensor(entity);
5746  assert(sensor);
5747  if (!sensor) continue; // skip this entity
5748 
5749  // get associated cloud
5750  ccHObject* defaultCloud =
5751  sensor->getParent() &&
5752  sensor->getParent()->isA(CV_TYPES::POINT_CLOUD)
5753  ? sensor->getParent()
5754  : 0;
5755  ccPointCloud* cloud = askUserToSelectACloud(
5756  defaultCloud,
5757  "Select a cloud on which to project the uncertainty:");
5758  if (!cloud) {
5759  return;
5760  }
5761 
5762  // sensor center
5763  CCVector3 sensorCenter;
5764  if (!sensor->getActiveAbsoluteCenter(sensorCenter)) return;
5765 
5766  // squared required?
5767  bool squared = cdDlg.computeSquaredDistances();
5768 
5769  // set up a new scalar field
5770  const char* defaultRangesSFname =
5773  int sfIdx = cloud->getScalarFieldIndexByName(defaultRangesSFname);
5774  if (sfIdx < 0) {
5775  sfIdx = cloud->addScalarField(defaultRangesSFname);
5776  if (sfIdx < 0) {
5777  ecvConsole::Error("Not enough memory!");
5778  return;
5779  }
5780  }
5781  cloudViewer::ScalarField* distances = cloud->getScalarField(sfIdx);
5782 
5783  for (unsigned i = 0; i < cloud->size(); ++i) {
5784  const CCVector3* P = cloud->getPoint(i);
5785  ScalarType s = static_cast<ScalarType>(
5786  squared ? (*P - sensorCenter).norm2()
5787  : (*P - sensorCenter).norm());
5788  distances->setValue(i, s);
5789  }
5790 
5791  distances->computeMinAndMax();
5792  cloud->setCurrentDisplayedScalarField(sfIdx);
5793  cloud->showSF(true);
5794  }
5795 
5796  refreshSelected();
5797  updateUI();
5798 }
5799 
5800 void MainWindow::doActionComputeScatteringAngles() {
5801  // there should be only one sensor in current selection!
5802  if (!haveOneSelection() ||
5803  !m_selectedEntities[0]->isKindOf(CV_TYPES::GBL_SENSOR)) {
5804  ecvConsole::Error("Select one and only one GBL sensor!");
5805  return;
5806  }
5807 
5808  ccSensor* sensor = ccHObjectCaster::ToSensor(m_selectedEntities[0]);
5809  assert(sensor);
5810 
5811  // sensor center
5812  CCVector3 sensorCenter;
5813  if (!sensor->getActiveAbsoluteCenter(sensorCenter)) return;
5814 
5815  // get associated cloud
5816  ccHObject* defaultCloud =
5817  sensor->getParent() &&
5818  sensor->getParent()->isA(CV_TYPES::POINT_CLOUD)
5819  ? sensor->getParent()
5820  : nullptr;
5821  ccPointCloud* cloud = askUserToSelectACloud(
5822  defaultCloud,
5823  "Select a cloud on which to project the uncertainty:");
5824  if (!cloud) {
5825  return;
5826  }
5827  if (!cloud->hasNormals()) {
5828  ecvConsole::Error("The cloud must have normals!");
5829  return;
5830  }
5831 
5833  if (!cdDlg.exec()) return;
5834 
5835  bool toDegreeFlag = cdDlg.anglesInDegrees();
5836 
5837  // prepare a new scalar field
5838  const char* defaultScatAnglesSFname =
5841  int sfIdx = cloud->getScalarFieldIndexByName(defaultScatAnglesSFname);
5842  if (sfIdx < 0) {
5843  sfIdx = cloud->addScalarField(defaultScatAnglesSFname);
5844  if (sfIdx < 0) {
5845  ecvConsole::Error("Not enough memory!");
5846  return;
5847  }
5848  }
5849  cloudViewer::ScalarField* angles = cloud->getScalarField(sfIdx);
5850 
5851  // perform computations
5852  for (unsigned i = 0; i < cloud->size(); ++i) {
5853  // the point position
5854  const CCVector3* P = cloud->getPoint(i);
5855 
5856  // build the ray
5857  CCVector3 ray = *P - sensorCenter;
5858  ray.normalize();
5859 
5860  // get the current normal
5861  CCVector3 normal(cloud->getPointNormal(i));
5862  // normal.normalize(); //should already be the case!
5863 
5864  // compute the angle
5865  PointCoordinateType cosTheta = ray.dot(normal);
5866  ScalarType theta = std::acos(std::min(std::abs(cosTheta), 1.0f));
5867 
5868  if (toDegreeFlag) theta = cloudViewer::RadiansToDegrees(theta);
5869 
5870  angles->setValue(i, theta);
5871  }
5872 
5873  angles->computeMinAndMax();
5874  cloud->setCurrentDisplayedScalarField(sfIdx);
5875  cloud->showSF(true);
5876 
5877  refreshObject(cloud);
5878  updateUI();
5879 }
5880 
5881 void MainWindow::doActionSetViewFromSensor() {
5882  // there should be only one sensor in current selection!
5883  if (!haveOneSelection() ||
5884  !m_selectedEntities[0]->isKindOf(CV_TYPES::SENSOR)) {
5885  ecvConsole::Error("Select one and only one sensor!");
5886  return;
5887  }
5888 
5889  ccSensor* sensor = ccHObjectCaster::ToSensor(m_selectedEntities[0]);
5890  assert(sensor);
5891 
5892  if (sensor->applyViewport()) {
5893  ecvConsole::Print("[doActionSetViewFromSensor] Viewport applied");
5894  }
5895 }
5896 
5897 void MainWindow::doActionCreateGBLSensor() {
5898  ccGBLSensorProjectionDlg spDlg(this);
5899  if (!spDlg.exec()) return;
5900 
5901  // We create the corresponding sensor for each input cloud (in a perfect
5902  // world, there should be only one ;)
5903  for (ccHObject* entity : getSelectedEntities()) {
5904  if (entity->isKindOf(CV_TYPES::POINT_CLOUD)) {
5905  ccGenericPointCloud* cloud =
5907 
5908  // we create a new sensor
5909  ccGBLSensor* sensor = new ccGBLSensor();
5910 
5911  // we init its parameters with the dialog
5912  spDlg.updateGBLSensor(sensor);
5913 
5914  // we compute projection
5915  if (sensor->computeAutoParameters(cloud)) {
5916  cloud->addChild(sensor);
5917 
5918  // we try to guess the sensor relative size (dirty)
5919  ccBBox bb = cloud->getOwnBB();
5920  double diag = bb.getDiagNorm();
5921  if (diag < 1.0)
5922  sensor->setGraphicScale(
5923  static_cast<PointCoordinateType>(1.0e-3));
5924  else if (diag > 10000.0)
5925  sensor->setGraphicScale(
5926  static_cast<PointCoordinateType>(1.0e3));
5927 
5928  // we display depth buffer
5929  int errorCode;
5930  if (sensor->computeDepthBuffer(cloud, errorCode)) {
5931  ccRenderingTools::ShowDepthBuffer(sensor, this);
5932  } else {
5934  }
5935 
5937  //{
5938  // //add positions
5939  // const unsigned count = 1000;
5940  // const PointCoordinateType R = 100;
5941  // const PointCoordinateType dh = 100;
5942  // for (unsigned i=0; i<1000; ++i)
5943  // {
5944  // float angle = (float)i/(float)count * 6 * M_PI;
5945  // float X = R * cos(angle);
5946  // float Y = R * sin(angle);
5947  // float Z = (float)i/(float)count * dh;
5948 
5949  // ccIndexedTransformation trans;
5950  // trans.initFromParameters(-angle,CCVector3(0,0,1),CCVector3(X,Y,Z));
5951  // sensor->addPosition(trans,i);
5952  // }
5953  //}
5954 
5955  // set position
5956  // ccIndexedTransformation trans;
5957  // sensor->addPosition(trans,0);
5958 
5959  QWidget* win = static_cast<QWidget*>(getActiveWindow());
5960  if (win) {
5961  // sensor->setDisplay_recursive(win);
5962  sensor->setVisible(true);
5963  ccBBox box = cloud->getOwnBB();
5965  }
5966 
5967  addToDB(sensor);
5968  } else {
5969  CVLog::Error("Failed to create sensor");
5970  delete sensor;
5971  sensor = nullptr;
5972  }
5973  }
5974  }
5975 
5976  updateUI();
5977 }
5978 
5979 void MainWindow::doActionCreateCameraSensor() {
5980  // we create the camera sensor
5981  ccCameraSensor* sensor = new ccCameraSensor();
5982 
5983  ccHObject* ent = nullptr;
5984  if (haveSelection()) {
5985  assert(haveOneSelection());
5986  ent = m_selectedEntities.front();
5987  }
5988 
5989  // we try to guess the sensor relative size (dirty)
5990  if (ent && ent->isKindOf(CV_TYPES::POINT_CLOUD)) {
5992  ccBBox bb = cloud->getOwnBB();
5993  double diag = bb.getDiagNorm();
5994  if (diag < 1.0)
5995  sensor->setGraphicScale(static_cast<PointCoordinateType>(1.0e-3));
5996  else if (diag > 10000.0)
5997  sensor->setGraphicScale(static_cast<PointCoordinateType>(1.0e3));
5998 
5999  // set position
6001  sensor->addPosition(trans, 0);
6002  }
6003 
6004  ccCamSensorProjectionDlg spDlg(this);
6005  // spDlg.initWithCamSensor(sensor); //DGM: we'd better leave the default
6006  // parameters of the dialog!
6007  if (!spDlg.exec()) {
6008  delete sensor;
6009  return;
6010  }
6011  spDlg.updateCamSensor(sensor);
6012 
6013  QWidget* win = nullptr;
6014  if (ent) {
6015  ent->addChild(sensor);
6016  win = static_cast<QWidget*>(ecvDisplayTools::GetCurrentScreen());
6017  } else {
6018  win = getActiveWindow();
6019  }
6020 
6021  if (win) {
6022  // sensor->setDisplay(win);
6023  sensor->setVisible(true);
6024  if (ent) {
6025  ccBBox box = ent->getOwnBB();
6027  }
6028  }
6029 
6030  addToDB(sensor);
6031 
6032  updateUI();
6033 }
6034 
6035 void MainWindow::doActionModifySensor() {
6036  // there should be only one point cloud with sensor in current selection!
6037  if (!haveOneSelection() ||
6038  !m_selectedEntities[0]->isKindOf(CV_TYPES::SENSOR)) {
6039  ecvConsole::Error("Select one and only one sensor!");
6040  return;
6041  }
6042 
6043  ccSensor* sensor = static_cast<ccSensor*>(m_selectedEntities[0]);
6044 
6045  // Ground based laser sensors
6046  if (sensor->isA(CV_TYPES::GBL_SENSOR)) {
6047  ccGBLSensor* gbl = static_cast<ccGBLSensor*>(sensor);
6048 
6049  ccGBLSensorProjectionDlg spDlg(this);
6050  spDlg.initWithGBLSensor(gbl);
6051 
6052  if (!spDlg.exec()) return;
6053 
6054  // we update its parameters
6055  spDlg.updateGBLSensor(gbl);
6056 
6057  // we re-project the associated cloud (if any)
6058  if (gbl->getParent() &&
6060  ccGenericPointCloud* cloud =
6062 
6063  int errorCode;
6064  if (gbl->computeDepthBuffer(cloud, errorCode)) {
6065  // we display depth buffer
6067  } else {
6069  }
6070  } else {
6071  // ecvConsole::Warning(QString("Internal error: sensor ('%1') parent
6072  // is not a point cloud!").arg(sensor->getName()));
6073  }
6074  }
6075  // Camera sensors
6076  else if (sensor->isA(CV_TYPES::CAMERA_SENSOR)) {
6077  ccCameraSensor* cam = static_cast<ccCameraSensor*>(sensor);
6078 
6079  ccCamSensorProjectionDlg spDlg(this);
6080  spDlg.initWithCamSensor(cam);
6081 
6082  if (!spDlg.exec()) return;
6083 
6084  // we update its parameters
6085  spDlg.updateCamSensor(cam);
6086  } else {
6087  ecvConsole::Error("Can't modify this kind of sensor!");
6088  return;
6089  }
6090 
6091  if (sensor->isVisible() && sensor->isEnabled()) {
6092  refreshObject(sensor);
6093  }
6094 
6095  updateUI();
6096 }
6097 
6098 void MainWindow::doActionProjectUncertainty() {
6099  // there should only be one sensor in the current selection!
6100  if (!haveOneSelection() ||
6101  !m_selectedEntities[0]->isKindOf(CV_TYPES::CAMERA_SENSOR)) {
6103  "Select one and only one camera (projective) sensor!");
6104  return;
6105  }
6106 
6107  ccCameraSensor* sensor =
6108  ccHObjectCaster::ToCameraSensor(m_selectedEntities[0]);
6109  if (!sensor) {
6110  assert(false);
6111  return;
6112  }
6113 
6115  sensor->getDistortionParameters();
6116  if (!distParams ||
6117  distParams->getModel() != ccCameraSensor::BROWN_DISTORTION) {
6118  CVLog::Error(
6119  "Sensor has no associated uncertainty model! (Brown, etc.)");
6120  return;
6121  }
6122 
6123  // we need a cloud to project the uncertainty on!
6124  ccHObject* defaultCloud =
6125  sensor->getParent() &&
6126  sensor->getParent()->isA(CV_TYPES::POINT_CLOUD)
6127  ? sensor->getParent()
6128  : nullptr;
6129  ccPointCloud* pointCloud = askUserToSelectACloud(
6130  defaultCloud,
6131  "Select a cloud on which to project the uncertainty:");
6132  if (!pointCloud) {
6133  return;
6134  }
6135 
6136  cloudViewer::ReferenceCloud points(pointCloud);
6137  if (!points.reserve(pointCloud->size())) {
6138  ecvConsole::Error("Not enough memory!");
6139  return;
6140  }
6141  points.addPointIndex(0, pointCloud->size());
6142 
6143  // compute uncertainty
6144  std::vector<Vector3Tpl<ScalarType>> accuracy;
6145  if (!sensor->computeUncertainty(&points, accuracy /*, false*/)) {
6146  ecvConsole::Error("Not enough memory!");
6147  return;
6148  }
6149 
6151  // SIGMA D //
6153  const char dimChar[3] = {'x', 'y', 'z'};
6154  for (unsigned d = 0; d < 3; ++d) {
6155  // add scalar field
6156  QString sfName = QString("[%1] Uncertainty (%2)")
6157  .arg(sensor->getName())
6158  .arg(dimChar[d]);
6159  int sfIdx = pointCloud->getScalarFieldIndexByName(qPrintable(sfName));
6160  if (sfIdx < 0) sfIdx = pointCloud->addScalarField(qPrintable(sfName));
6161  if (sfIdx < 0) {
6162  CVLog::Error("An error occurred! (see console)");
6163  return;
6164  }
6165 
6166  // fill scalar field
6167  cloudViewer::ScalarField* sf = pointCloud->getScalarField(sfIdx);
6168  assert(sf);
6169  if (sf) {
6170  unsigned count = static_cast<unsigned>(accuracy.size());
6171  assert(count == pointCloud->size());
6172  for (unsigned i = 0; i < count; i++)
6173  sf->setValue(i, accuracy[i].u[d]);
6174  sf->computeMinAndMax();
6175  }
6176  }
6177 
6179  // SIGMA TOTAL //
6181 
6182  // add scalar field
6183  {
6184  QString sfName =
6185  QString("[%1] Uncertainty (3D)").arg(sensor->getName());
6186  int sfIdx = pointCloud->getScalarFieldIndexByName(qPrintable(sfName));
6187  if (sfIdx < 0) sfIdx = pointCloud->addScalarField(qPrintable(sfName));
6188  if (sfIdx < 0) {
6189  CVLog::Error("An error occurred! (see console)");
6190  return;
6191  }
6192 
6193  // fill scalar field
6194  cloudViewer::ScalarField* sf = pointCloud->getScalarField(sfIdx);
6195  assert(sf);
6196  if (sf) {
6197  unsigned count = static_cast<unsigned>(accuracy.size());
6198  assert(count == pointCloud->size());
6199  for (unsigned i = 0; i < count; i++)
6200  sf->setValue(i, accuracy[i].norm());
6201  sf->computeMinAndMax();
6202  }
6203 
6204  pointCloud->showSF(true);
6205  pointCloud->setCurrentDisplayedScalarField(sfIdx);
6206  }
6207 
6208  refreshObject(pointCloud);
6209 }
6210 
6211 void MainWindow::doActionCheckPointsInsideFrustum() {
6212  // there should be only one camera sensor in the current selection!
6213  if (!haveOneSelection() ||
6214  !m_selectedEntities[0]->isKindOf(CV_TYPES::CAMERA_SENSOR)) {
6215  ecvConsole::Error("Select one and only one camera sensor!");
6216  return;
6217  }
6218 
6219  ccCameraSensor* sensor =
6220  ccHObjectCaster::ToCameraSensor(m_selectedEntities[0]);
6221  if (!sensor) return;
6222 
6223  // we need a cloud to filter!
6224  ccHObject* defaultCloud =
6225  sensor->getParent() &&
6226  sensor->getParent()->isA(CV_TYPES::POINT_CLOUD)
6227  ? sensor->getParent()
6228  : 0;
6229  ccPointCloud* pointCloud =
6230  askUserToSelectACloud(defaultCloud, "Select a cloud to filter:");
6231  if (!pointCloud) {
6232  return;
6233  }
6234 
6235  // comupte/get the point cloud's octree
6236  ccOctree::Shared octree = pointCloud->getOctree();
6237  if (!octree) {
6238  octree = pointCloud->computeOctree();
6239  if (!octree) {
6240  ecvConsole::Error("Failed to compute the octree!");
6241  return;
6242  }
6243  }
6244  assert(octree);
6245 
6246  // filter octree then project the points
6247  std::vector<unsigned> inCameraFrustum;
6248  if (!octree->intersectWithFrustum(sensor, inCameraFrustum)) {
6249  ecvConsole::Error("Failed to intersect sensor frustum with octree!");
6250  } else {
6251  // scalar field
6252  const char sfName[] = "Frustum visibility";
6253  int sfIdx = pointCloud->getScalarFieldIndexByName(sfName);
6254 
6255  if (inCameraFrustum.empty()) {
6256  ecvConsole::Error("No point fell inside the frustum!");
6257  if (sfIdx >= 0) pointCloud->deleteScalarField(sfIdx);
6258  } else {
6259  if (sfIdx < 0) sfIdx = pointCloud->addScalarField(sfName);
6260  if (sfIdx < 0) {
6261  CVLog::Error(
6262  "Failed to allocate memory for output scalar field!");
6263  return;
6264  }
6265 
6266  cloudViewer::ScalarField* sf = pointCloud->getScalarField(sfIdx);
6267  assert(sf);
6268  if (sf) {
6269  sf->fill(0);
6270 
6271  const ScalarType c_insideValue = static_cast<ScalarType>(1);
6272 
6273  for (unsigned index : inCameraFrustum) {
6274  sf->setValue(index, c_insideValue);
6275  }
6276 
6277  sf->computeMinAndMax();
6278  pointCloud->setCurrentDisplayedScalarField(sfIdx);
6279  pointCloud->showSF(true);
6280  refreshObject(pointCloud);
6281  }
6282  }
6283  }
6284 
6285  updateUI();
6286 }
6287 
6288 void MainWindow::doActionShowDepthBuffer() {
6289  if (!haveSelection()) return;
6290 
6291  for (ccHObject* entity : getSelectedEntities()) {
6292  if (entity->isKindOf(CV_TYPES::GBL_SENSOR)) {
6293  ccGBLSensor* sensor =
6294  static_cast<ccGBLSensor*>(m_selectedEntities[0]);
6295  if (sensor->getDepthBuffer().zBuff.empty()) {
6296  // look for depending cloud
6297  ccGenericPointCloud* cloud =
6299  entity->getParent());
6300  if (cloud) {
6301  // force depth buffer computation
6302  int errorCode;
6303  if (!sensor->computeDepthBuffer(cloud, errorCode)) {
6305  ccGBLSensor::GetErrorString(errorCode));
6306  }
6307  } else {
6308  ecvConsole::Error(QString("Internal error: sensor ('%1') "
6309  "parent is not a point cloud!")
6310  .arg(sensor->getName()));
6311  return;
6312  }
6313  }
6314 
6315  ccRenderingTools::ShowDepthBuffer(sensor, this);
6316  }
6317  }
6318 }
6319 
6320 void MainWindow::doActionExportDepthBuffer() {
6321  if (!haveSelection()) return;
6322 
6323  // persistent settings
6324  QSettings settings;
6325  settings.beginGroup(ecvPS::SaveFile());
6326  QString currentPath =
6328  .toString();
6329 
6330  QString filename = QFileDialog::getSaveFileName(
6331  this, "Select output file", currentPath,
6334  if (filename.isEmpty()) {
6335  // process cancelled by user
6336  return;
6337  }
6338 
6339  // save last saving location
6340  settings.setValue(ecvPS::CurrentPath(), QFileInfo(filename).absolutePath());
6341  settings.endGroup();
6342 
6343  ccHObject* toSave = nullptr;
6344  bool multEntities = false;
6345  if (haveOneSelection()) {
6346  toSave = m_selectedEntities.front();
6347  } else {
6348  toSave = new ccHObject("Temp Group");
6349 
6350  for (ccHObject* entity : getSelectedEntities()) {
6351  toSave->addChild(entity, ccHObject::DP_NONE);
6352  }
6353  multEntities = true;
6354  }
6355 
6357  { parameters.alwaysDisplaySaveDialog = true; }
6359  DepthMapFileFilter().saveToFile(toSave, filename, parameters);
6360 
6361  if (result != CC_FERR_NO_ERROR) {
6363  } else {
6364  CVLog::Print(
6365  QString("[I/O] File '%1' saved successfully").arg(filename));
6366  }
6367 
6368  if (multEntities) {
6369  delete toSave;
6370  toSave = nullptr;
6371  }
6372 }
6373 
6374 void MainWindow::doActionComputePointsVisibility() {
6375  // there should be only one camera sensor in the current selection!
6376  if (!haveOneSelection() ||
6377  !m_selectedEntities[0]->isKindOf(CV_TYPES::GBL_SENSOR)) {
6378  ecvConsole::Error("Select one and only one GBL/TLS sensor!");
6379  return;
6380  }
6381 
6382  ccGBLSensor* sensor = ccHObjectCaster::ToGBLSensor(m_selectedEntities[0]);
6383  if (!sensor) return;
6384 
6385  // we need a cloud to filter!
6386  ccHObject* defaultCloud =
6387  sensor->getParent() &&
6388  sensor->getParent()->isA(CV_TYPES::POINT_CLOUD)
6389  ? sensor->getParent()
6390  : 0;
6391  ccPointCloud* pointCloud =
6392  askUserToSelectACloud(defaultCloud, "Select a cloud to filter:");
6393  if (!pointCloud) {
6394  return;
6395  }
6396 
6397  if (sensor->getDepthBuffer().zBuff.empty()) {
6398  if (defaultCloud) {
6399  // the sensor has no depth buffer, we'll ask the user if he wants to
6400  // compute it first
6401  if (QMessageBox::warning(this, "Depth buffer.",
6402  "Sensor has no depth buffer: do you want "
6403  "to compute it now?",
6404  QMessageBox::Yes | QMessageBox::No,
6405  QMessageBox::Yes) == QMessageBox::No) {
6406  // we can stop then...
6407  return;
6408  }
6409 
6410  int errorCode;
6411  if (sensor->computeDepthBuffer(
6412  static_cast<ccPointCloud*>(defaultCloud), errorCode)) {
6413  ccRenderingTools::ShowDepthBuffer(sensor, this);
6414  } else {
6416  return;
6417  }
6418  } else {
6420  "Sensor has no depth buffer (and no associated cloud?)");
6421  return;
6422  }
6423  }
6424 
6425  // scalar field
6426  const char sfName[] = "Sensor visibility";
6427  int sfIdx = pointCloud->getScalarFieldIndexByName(sfName);
6428  if (sfIdx < 0) sfIdx = pointCloud->addScalarField(sfName);
6429  if (sfIdx < 0) {
6430  CVLog::Error("Failed to allocate memory for output scalar field!");
6431  return;
6432  }
6433 
6434  cloudViewer::ScalarField* sf = pointCloud->getScalarField(sfIdx);
6435  assert(sf);
6436  if (sf) {
6437  sf->fill(0);
6438 
6439  // progress bar
6440  ecvProgressDialog pdlg(true);
6441  cloudViewer::NormalizedProgress nprogress(&pdlg, pointCloud->size());
6442  pdlg.setMethodTitle(tr("Compute visibility"));
6443  pdlg.setInfo(tr("Points: %L1").arg(pointCloud->size()));
6444  pdlg.start();
6445  QApplication::processEvents();
6446 
6447  for (unsigned i = 0; i < pointCloud->size(); i++) {
6448  const CCVector3* P = pointCloud->getPoint(i);
6449  unsigned char visibility = sensor->checkVisibility(*P);
6450  ScalarType visValue = static_cast<ScalarType>(visibility);
6451 
6452  sf->setValue(i, visValue);
6453 
6454  if (!nprogress.oneStep()) {
6455  // cancelled by user
6456  pointCloud->deleteScalarField(sfIdx);
6457  sf = nullptr;
6458  break;
6459  }
6460  }
6461 
6462  if (sf) {
6463  sf->computeMinAndMax();
6464  pointCloud->setCurrentDisplayedScalarField(sfIdx);
6465  pointCloud->showSF(true);
6466 
6467  ecvConsole::Print(QString("Visibility computed for cloud '%1'")
6468  .arg(pointCloud->getName()));
6469  ecvConsole::Print(QString("\tVisible = %1").arg(POINT_VISIBLE));
6470  ecvConsole::Print(QString("\tHidden = %1").arg(POINT_HIDDEN));
6472  QString("\tOut of range = %1").arg(POINT_OUT_OF_RANGE));
6474  QString("\tOut of fov = %1").arg(POINT_OUT_OF_FOV));
6475  }
6476  refreshObject(pointCloud);
6477  }
6478 
6479  updateUI();
6480 }
6481 
6482 void MainWindow::doActionCompressFWFData() {
6483  for (ccHObject* entity : getSelectedEntities()) {
6484  if (!entity || !entity->isKindOf(CV_TYPES::POINT_CLOUD)) {
6485  continue;
6486  }
6487 
6488  ccPointCloud* cloud = static_cast<ccPointCloud*>(entity);
6489  cloud->compressFWFData();
6490  }
6491 }
6492 
6493 void MainWindow::doActionShowWaveDialog() {
6494  if (!haveSelection()) return;
6495 
6496  ccHObject* entity = haveOneSelection() ? m_selectedEntities[0] : nullptr;
6497  if (!entity || !entity->isKindOf(CV_TYPES::POINT_CLOUD)) {
6498  ecvConsole::Error("Select one point cloud!");
6499  return;
6500  }
6501 
6502  ccPointCloud* cloud = static_cast<ccPointCloud*>(entity);
6503  if (!cloud->hasFWF()) {
6504  ecvConsole::Error("Cloud has no associated waveform information");
6505  return;
6506  }
6507 
6508  ccWaveDialog* wDlg = new ccWaveDialog(cloud, m_pickingHub, this);
6509  wDlg->setAttribute(Qt::WA_DeleteOnClose);
6510  wDlg->setModal(false);
6511  wDlg->show();
6512 }
6513 
6514 void MainWindow::doActionConvertTextureToColor() {
6515  if (!ccEntityAction::convertTextureToColor(m_selectedEntities, this))
6516  return;
6517 
6518  refreshSelected();
6519  updateUI();
6520 }
6521 
6522 void MainWindow::doActionSamplePointsOnMesh() {
6523  static unsigned s_ptsSamplingCount = 1000000;
6524  static double s_ptsSamplingDensity = 10.0;
6525  static bool s_ptsSampleNormals = true;
6526  static bool s_useDensity = false;
6527 
6528  ccPtsSamplingDlg dlg(this);
6529  // restore last parameters
6530  dlg.setPointsNumber(s_ptsSamplingCount);
6531  dlg.setDensityValue(s_ptsSamplingDensity);
6532  dlg.setGenerateNormals(s_ptsSampleNormals);
6533  dlg.setUseDensity(s_useDensity);
6534  if (!dlg.exec()) return;
6535 
6536  ecvProgressDialog pDlg(false, this);
6537  pDlg.setAutoClose(false);
6538 
6539  bool withNormals = dlg.generateNormals();
6540  bool withRGB = dlg.interpolateRGB();
6541  bool withTexture = dlg.interpolateTexture();
6542  s_useDensity = dlg.useDensity();
6543  assert(dlg.getPointsNumber() >= 0);
6544  s_ptsSamplingCount = static_cast<unsigned>(dlg.getPointsNumber());
6545  s_ptsSamplingDensity = dlg.getDensityValue();
6546  s_ptsSampleNormals = withNormals;
6547 
6548  bool errors = false;
6549 
6550  for (ccHObject* entity : getSelectedEntities()) {
6551  if (!entity->isKindOf(CV_TYPES::MESH)) continue;
6552 
6554  assert(mesh);
6555 
6556  ccPointCloud* cloud = mesh->samplePoints(
6557  s_useDensity,
6558  s_useDensity ? s_ptsSamplingDensity : s_ptsSamplingCount,
6559  withNormals, withRGB, withTexture, &pDlg);
6560 
6561  if (cloud) {
6562  cloud->showNormals(false);
6563  addToDB(cloud);
6564  } else {
6565  errors = true;
6566  }
6567  }
6568 
6569  if (errors)
6570  CVLog::Error(
6571  tr("[doActionSamplePointsOnMesh] Errors occurred during the "
6572  "process! Result may be incomplete!"));
6573 }
6574 
6575 void MainWindow::doActionSamplePointsOnPolyline() {
6576  static unsigned s_ptsSamplingCount = 1000;
6577  static double s_ptsSamplingDensity = 10.0;
6578  static bool s_useDensity = false;
6579 
6580  ccPtsSamplingDlg dlg(this);
6581  dlg.setWindowTitle(tr("Points Sampling on polyline"));
6582  // restore last parameters
6583  dlg.setPointsNumber(s_ptsSamplingCount);
6584  dlg.setDensityValue(s_ptsSamplingDensity);
6585  dlg.setUseDensity(s_useDensity);
6586  dlg.optionsFrame->setVisible(false);
6587  if (!dlg.exec()) return;
6588 
6589  assert(dlg.getPointsNumber() >= 0);
6590  s_ptsSamplingCount = static_cast<unsigned>(dlg.getPointsNumber());
6591  s_ptsSamplingDensity = dlg.getDensityValue();
6592  s_useDensity = dlg.useDensity();
6593 
6594  bool errors = false;
6595 
6596  for (ccHObject* entity : getSelectedEntities()) {
6597  if (!entity->isKindOf(CV_TYPES::POLY_LINE)) continue;
6598 
6599  ccPolyline* poly = ccHObjectCaster::ToPolyline(entity);
6600  assert(poly);
6601 
6602  ccPointCloud* cloud = poly->samplePoints(
6603  s_useDensity,
6604  s_useDensity ? s_ptsSamplingDensity : s_ptsSamplingCount, true);
6605 
6606  if (cloud) {
6607  addToDB(cloud);
6608  } else {
6609  errors = true;
6610  }
6611  }
6612 
6613  if (errors) {
6614  CVLog::Error(
6615  tr("[doActionSamplePointsOnPolyline] Errors occurred during "
6616  "the process! Result may be incomplete!"));
6617  }
6618 }
6619 
6620 void MainWindow::doActionSmoohPolyline() {
6621  static int s_iterationCount = 5;
6622  static double s_ratio = 0.25;
6623 
6624  ccSmoothPolylineDialog dlg(this);
6625  // restore last parameters
6626  dlg.setIerationCount(s_iterationCount);
6627  dlg.setRatio(s_ratio);
6628  if (!dlg.exec()) return;
6629 
6630  s_iterationCount = dlg.getIerationCount();
6631  s_ratio = dlg.getRatio();
6632 
6633  bool errors = false;
6634 
6635  ccHObject::Container selectedEntities = getSelectedEntities();
6636  m_ccRoot->unselectAllEntities();
6637 
6638  for (ccHObject* entity : selectedEntities) {
6639  if (!entity->isKindOf(CV_TYPES::POLY_LINE)) continue;
6640 
6641  ccPolyline* poly = ccHObjectCaster::ToPolyline(entity);
6642  assert(poly);
6643 
6644  ccPolyline* smoothPoly = poly->smoothChaikin(
6645  s_ratio, static_cast<unsigned>(s_iterationCount));
6646  if (smoothPoly) {
6647  if (poly->getParent()) {
6648  poly->getParent()->addChild(smoothPoly);
6649  }
6650  poly->setEnabled(false);
6651  addToDB(smoothPoly);
6652 
6653  m_ccRoot->selectEntity(smoothPoly, true);
6654  } else {
6655  errors = true;
6656  }
6657  }
6658 
6659  if (errors) {
6660  CVLog::Error(
6661  tr("[DoActionSmoohPolyline] Errors occurred during the "
6662  "process! Result may be incomplete!"));
6663  }
6664 
6665  refreshAll();
6666 }
6667 
6668 void MainWindow::doConvertPolylinesToMesh() {
6669  if (!haveSelection()) return;
6670 
6671  std::vector<ccPolyline*> polylines;
6672  try {
6673  if (haveOneSelection() &&
6674  m_selectedEntities.back()->isA(CV_TYPES::HIERARCHY_OBJECT)) {
6675  ccHObject* obj = m_selectedEntities.back();
6676  for (unsigned i = 0; i < obj->getChildrenNumber(); ++i) {
6677  if (obj->getChild(i)->isA(CV_TYPES::POLY_LINE))
6678  polylines.push_back(
6679  static_cast<ccPolyline*>(obj->getChild(i)));
6680  }
6681  } else {
6682  for (ccHObject* entity : getSelectedEntities()) {
6683  if (entity->isA(CV_TYPES::POLY_LINE)) {
6684  polylines.push_back(static_cast<ccPolyline*>(entity));
6685  }
6686  }
6687  }
6688  } catch (const std::bad_alloc&) {
6689  ecvConsole::Error(tr("Not enough memory!"));
6690  return;
6691  }
6692 
6693  if (polylines.empty()) {
6695  tr("Select a group of polylines or multiple polylines (contour "
6696  "plot)!"));
6697  return;
6698  }
6699 
6700  ccPickOneElementDlg poeDlg(tr("Projection dimension"),
6701  tr("Contour plot to mesh"), this);
6702  poeDlg.addElement("X");
6703  poeDlg.addElement("Y");
6704  poeDlg.addElement("Z");
6705  poeDlg.setDefaultIndex(2);
6706  if (!poeDlg.exec()) return;
6707 
6708  int dim = poeDlg.getSelectedIndex();
6709  assert(dim >= 0 && dim < 3);
6710 
6711  const unsigned char Z = static_cast<unsigned char>(dim);
6712  const unsigned char X = Z == 2 ? 0 : Z + 1;
6713  const unsigned char Y = X == 2 ? 0 : X + 1;
6714 
6715  // number of segments
6716  unsigned segmentCount = 0;
6717  unsigned vertexCount = 0;
6718  {
6719  for (ccPolyline* poly : polylines) {
6720  if (poly) {
6721  // count the total number of vertices and segments
6722  vertexCount += poly->size();
6723  segmentCount += poly->segmentCount();
6724  }
6725  }
6726  }
6727 
6728  if (segmentCount < 2) {
6729  // not enough points/segments
6730  CVLog::Error(tr("Not enough segments!"));
6731  return;
6732  }
6733 
6734  // we assume we link with CGAL now (if not the call to
6735  // Delaunay2dMesh::buildMesh will fail anyway)
6736  std::vector<CCVector2> points2D;
6737  std::vector<int> segments2D;
6738  try {
6739  points2D.reserve(vertexCount);
6740  segments2D.reserve(segmentCount * 2);
6741  } catch (const std::bad_alloc&) {
6742  // not enough memory
6743  CVLog::Error(tr("Not enough memory"));
6744  return;
6745  }
6746 
6747  // fill arrays
6748  {
6749  for (ccPolyline* poly : polylines) {
6750  if (poly == nullptr) continue;
6751 
6752  unsigned vertCount = poly->size();
6753  int vertIndex0 = static_cast<int>(points2D.size());
6754  bool closed = poly->isClosed();
6755  for (unsigned v = 0; v < vertCount; ++v) {
6756  const CCVector3* P = poly->getPoint(v);
6757  int vertIndex = static_cast<int>(points2D.size());
6758  points2D.push_back(CCVector2(P->u[X], P->u[Y]));
6759 
6760  if (v + 1 < vertCount) {
6761  segments2D.push_back(vertIndex);
6762  segments2D.push_back(vertIndex + 1);
6763  } else if (closed) {
6764  segments2D.push_back(vertIndex);
6765  segments2D.push_back(vertIndex0);
6766  }
6767  }
6768  }
6769  assert(points2D.size() == vertexCount);
6770  assert(segments2D.size() == segmentCount * 2);
6771  }
6772 
6774  std::string errorStr;
6775  if (!delaunayMesh->buildMesh(points2D, segments2D, errorStr)) {
6776  CVLog::Error(tr("Third party library error: %1")
6777  .arg(QString::fromStdString(errorStr)));
6778  delete delaunayMesh;
6779  return;
6780  }
6781 
6782  ccPointCloud* vertices = new ccPointCloud(tr("vertices"));
6783  if (!vertices->reserve(vertexCount)) {
6784  // not enough memory
6785  CVLog::Error(tr("Not enough memory"));
6786  delete vertices;
6787  delete delaunayMesh;
6788  return;
6789  }
6790 
6791  // fill vertices cloud
6792  {
6793  for (ccPolyline* poly : polylines) {
6794  unsigned vertCount = poly->size();
6795  for (unsigned v = 0; v < vertCount; ++v) {
6796  const CCVector3* P = poly->getPoint(v);
6797  vertices->addPoint(*P);
6798  }
6799  }
6800  delaunayMesh->linkMeshWith(vertices, false);
6801  }
6802 
6803 #ifdef QT_DEBUG
6804  // Test delaunay output
6805  {
6806  unsigned vertCount = vertices->size();
6807  for (unsigned i = 0; i < delaunayMesh->size(); ++i) {
6808  const cloudViewer::VerticesIndexes* tsi =
6809  delaunayMesh->getTriangleVertIndexes(i);
6810  assert(tsi->i1 < vertCount && tsi->i2 < vertCount &&
6811  tsi->i3 < vertCount);
6812  }
6813  }
6814 #endif
6815 
6816  ccMesh* mesh = new ccMesh(delaunayMesh, vertices);
6817  if (mesh->size() != delaunayMesh->size()) {
6818  // not enough memory (error will be issued later)
6819  delete mesh;
6820  mesh = nullptr;
6821  }
6822 
6823  // don't need this anymore
6824  delete delaunayMesh;
6825  delaunayMesh = nullptr;
6826 
6827  if (mesh) {
6828  mesh->addChild(vertices);
6829  mesh->setVisible(true);
6830  vertices->setEnabled(false);
6831 
6832  if (mesh->computePerVertexNormals()) {
6833  mesh->showNormals(true);
6834  } else {
6836  tr("[Contour plot to mesh] Failed to compute normals!"));
6837  }
6838 
6839  // global shift & scale (we copy it from the first polyline by default)
6840  vertices->setGlobalShift(polylines.front()->getGlobalShift());
6841  vertices->setGlobalScale(polylines.front()->getGlobalScale());
6842 
6843  addToDB(mesh);
6844  } else {
6845  CVLog::Error(tr("Not enough memory!"));
6846  delete vertices;
6847  vertices = nullptr;
6848  }
6849 }
6850 
6851 void MainWindow::doBSplineFittingFromCloud() {
6852 #ifdef USE_PCL_BACKEND
6853  // find candidates
6854  std::vector<ccPointCloud*> clouds;
6855  {
6856  for (ccHObject* entity : getSelectedEntities()) {
6857  if (entity->isA(CV_TYPES::POINT_CLOUD)) {
6858  ccPointCloud* cloud = static_cast<ccPointCloud*>(entity);
6859  clouds.push_back(cloud);
6860  }
6861  }
6862  }
6863 
6864  if (clouds.empty()) {
6865  ecvConsole::Error(tr("Select at least one point cloud!"));
6866  return;
6867  }
6868 
6869  ccPolyline* polyLine =
6870  CurveFittingTool::CurveFitting::BsplineFitting(*clouds[0]);
6871  if (polyLine) {
6872  addToDB(polyLine);
6873  }
6874 
6875  if (polyLine && m_ccRoot) {
6876  m_ccRoot->selectEntity(polyLine);
6877  }
6878 
6879  updateUI();
6880 
6881 #else
6883  "[doBSplineFittingFromCloud] please use pcl as backend and then "
6884  "try again!");
6885  return;
6886 #endif
6887 }
6888 
6889 void MainWindow::doActionSmoothMeshSF() {
6890  if (!ccEntityAction::processMeshSF(m_selectedEntities,
6891  ccMesh::SMOOTH_MESH_SF, this))
6892  return;
6893 
6894  refreshSelected();
6895  updateUI();
6896 }
6897 
6898 void MainWindow::doActionEnhanceMeshSF() {
6899  if (!ccEntityAction::processMeshSF(m_selectedEntities,
6900  ccMesh::ENHANCE_MESH_SF, this))
6901  return;
6902 
6903  refreshSelected();
6904  updateUI();
6905 }
6906 
6907 static double s_subdivideMaxArea = 1.0;
6908 void MainWindow::doActionSubdivideMesh() {
6909  bool ok;
6910  s_subdivideMaxArea = QInputDialog::getDouble(
6911  this, tr("Subdivide mesh"), tr("Max area per triangle:"),
6912  s_subdivideMaxArea, 1e-6, 1e6, 8, &ok);
6913  if (!ok) return;
6914 
6915  for (ccHObject* entity : getSelectedEntities()) {
6916  if (entity->isKindOf(CV_TYPES::MESH)) {
6917  // single mesh?
6918  if (entity->isA(CV_TYPES::MESH)) {
6919  ccMesh* mesh = static_cast<ccMesh*>(entity);
6920  // avoid rendering this object this time
6921  mesh->setEnabled(false);
6922 
6923  ccMesh* subdividedMesh = nullptr;
6924  try {
6925  subdividedMesh =
6926  mesh->subdivide(static_cast<PointCoordinateType>(
6928  } catch (...) {
6929  CVLog::Error(
6930  tr("[Subdivide] An error occurred while trying to "
6931  "subdivide mesh '%1' (not enough memory?)")
6932  .arg(mesh->getName()));
6933  }
6934 
6935  if (subdividedMesh) {
6936  subdividedMesh->setName(tr("%1.subdivided(S<%2)")
6937  .arg(mesh->getName())
6938  .arg(s_subdivideMaxArea));
6939  refreshObject(mesh, true, true);
6940  // avoid rendering this object this time
6941  mesh->setEnabled(false);
6942  addToDB(subdividedMesh);
6943  } else {
6944  ecvConsole::Warning(tr("[Subdivide] Failed to subdivide "
6945  "mesh '%1' (not enough memory?)")
6946  .arg(mesh->getName()));
6947  }
6948  } else {
6949  CVLog::Warning(tr("[Subdivide] Works only on real meshes!"));
6950  }
6951  }
6952  }
6953 
6954  updateUI();
6955 }
6956 
6957 void MainWindow::doActionFlipMeshTriangles() {
6958  bool warningIssued = false;
6960  for (ccHObject* entity : getSelectedEntities()) {
6961  if (entity->isKindOf(CV_TYPES::MESH)) {
6962  // single mesh?
6963  if (entity->isA(CV_TYPES::MESH)) {
6964  ccMesh* mesh = static_cast<ccMesh*>(entity);
6965  mesh->flipTriangles();
6966  mesh->setRedrawFlagRecursive(true);
6967  } else if (!warningIssued) {
6968  CVLog::Warning("[Flip triangles] Works only on real meshes!");
6969  warningIssued = true;
6970  }
6971  }
6972  }
6973 
6974  refreshAll();
6975 }
6976 
6977 void MainWindow::doActionSmoothMeshLaplacian() {
6978  static unsigned s_laplacianSmooth_nbIter = 20;
6979  static double s_laplacianSmooth_factor = 0.2;
6980 
6981  bool ok;
6982  s_laplacianSmooth_nbIter =
6983  QInputDialog::getInt(this, tr("Smooth mesh"), tr("Iterations:"),
6984  s_laplacianSmooth_nbIter, 1, 1000, 1, &ok);
6985  if (!ok) return;
6986  s_laplacianSmooth_factor = QInputDialog::getDouble(
6987  this, tr("Smooth mesh"), tr("Smoothing factor:"),
6988  s_laplacianSmooth_factor, 0, 100, 3, &ok);
6989  if (!ok) return;
6990 
6991  ecvProgressDialog pDlg(true, this);
6992  pDlg.setAutoClose(false);
6993 
6995  for (ccHObject* entity : getSelectedEntities()) {
6996  if (entity->isA(CV_TYPES::MESH) ||
6997  entity->isA(CV_TYPES::PRIMITIVE)) // FIXME: can we really do this
6998  // with primitives?
6999  {
7000  ccMesh* mesh = ccHObjectCaster::ToMesh(entity);
7001 
7002  if (mesh->laplacianSmooth(s_laplacianSmooth_nbIter,
7003  static_cast<PointCoordinateType>(
7004  s_laplacianSmooth_factor),
7005  &pDlg)) {
7006  mesh->setRedrawFlagRecursive(true);
7007  } else {
7009  tr("Failed to apply Laplacian smoothing to mesh '%1'")
7010  .arg(mesh->getName()));
7011  }
7012  }
7013  }
7014 
7015  refreshAll();
7016  updateUI();
7017 }
7018 
7019 void MainWindow::doActionFlagMeshVertices() {
7020  bool errors = false;
7021  bool success = false;
7022 
7024  for (ccHObject* entity : getSelectedEntities()) {
7025  if (entity->isKindOf(CV_TYPES::MESH)) {
7028  mesh ? mesh->getAssociatedCloud() : 0);
7029  if (mesh && vertices) {
7030  // prepare a new scalar field
7031  int sfIdx = vertices->getScalarFieldIndexByName(
7033  if (sfIdx < 0) {
7034  sfIdx = vertices->addScalarField(
7036  if (sfIdx < 0) {
7037  ecvConsole::Warning(tr("Not enough memory to flag the "
7038  "vertices of mesh '%1'!")
7039  .arg(mesh->getName()));
7040  errors = true;
7041  continue;
7042  }
7043  }
7044  cloudViewer::ScalarField* flags =
7045  vertices->getScalarField(sfIdx);
7046 
7049  mesh, flags, &stats)) {
7050  vertices->setCurrentDisplayedScalarField(sfIdx);
7051  ccScalarField* sf =
7052  vertices->getCurrentDisplayedScalarField();
7053  if (sf) {
7056  // sf->setColorRampSteps(3); //ugly :(
7057  }
7058  vertices->showSF(true);
7059  mesh->showSF(true);
7060  mesh->setRedrawFlagRecursive(true);
7061  success = true;
7062 
7063  // display stats in the Console as well
7064  ecvConsole::Print(tr("[Mesh Quality] Mesh '%1' edges: %2 "
7065  "total (normal: %3 / on hole borders: "
7066  "%4 / non-manifold: %5)")
7067  .arg(entity->getName())
7068  .arg(stats.edgesCount)
7069  .arg(stats.edgesSharedByTwo)
7070  .arg(stats.edgesNotShared)
7071  .arg(stats.edgesSharedByMore));
7072  } else {
7073  vertices->deleteScalarField(sfIdx);
7074  sfIdx = -1;
7075  ecvConsole::Warning(tr("Not enough memory to flag the "
7076  "vertices of mesh '%1'!")
7077  .arg(mesh->getName()));
7078  errors = true;
7079  }
7080  } else {
7081  assert(false);
7082  }
7083  }
7084  }
7085 
7086  refreshAll();
7087  updateUI();
7088 
7089  if (success) {
7090  // display reminder
7093  tr("[Mesh Quality] SF flags: %1 (NORMAL) / %2 (BORDER) / (%3) "
7094  "NON-MANIFOLD")
7098  VERTEX_NON_MANIFOLD));
7099  }
7100 
7101  if (errors) {
7102  ecvConsole::Error(tr("Error(s) occurred! Check the console..."));
7103  }
7104 }
7105 
7106 void MainWindow::doActionMeasureMeshVolume() {
7107  for (ccHObject* entity : getSelectedEntities()) {
7108  if (entity->isKindOf(CV_TYPES::MESH)) {
7109  ccMesh* mesh = ccHObjectCaster::ToMesh(entity);
7110  if (mesh) {
7111  // we compute the mesh volume
7112  double V =
7114  // we force the console to display itself
7117  tr("[Mesh Volume] Mesh '%1': V=%2 (cube units)")
7118  .arg(entity->getName())
7119  .arg(V));
7120 
7121  // check that the mesh is closed
7124  computeMeshEdgesConnectivity(mesh, stats)) {
7125  if (stats.edgesNotShared != 0) {
7127  tr("[Mesh Volume] The above volume might be "
7128  "invalid (mesh has holes)"));
7129  } else if (stats.edgesSharedByMore != 0) {
7131  tr("[Mesh Volume] The above volume might be "
7132  "invalid (mesh has non-manifold edges)"));
7133  }
7134  } else {
7136  tr("[Mesh Volume] The above volume might be "
7137  "invalid (not enough memory to check if the "
7138  "mesh is closed)"));
7139  }
7140  } else {
7141  assert(false);
7142  }
7143  }
7144  }
7145 }
7146 
7147 void MainWindow::doActionMeasureMeshSurface() {
7148  for (ccHObject* entity : getSelectedEntities()) {
7149  if (entity->isKindOf(CV_TYPES::MESH)) {
7151  if (mesh) {
7152  double S =
7154  // we force the console to display itself
7157  tr("[Mesh Surface] Mesh '%1': S=%2 (square units)")
7158  .arg(entity->getName())
7159  .arg(S));
7160  if (mesh->size()) {
7161  ecvConsole::Print(tr("[Mesh Surface] Average triangle "
7162  "surface: %1 (square units)")
7163  .arg(S / double(mesh->size())));
7164  }
7165  } else {
7166  assert(false);
7167  }
7168  }
7169  }
7170 }
7171 
7172 void MainWindow::doActionCreatePlane() {
7173  ccPlaneEditDlg* peDlg = new ccPlaneEditDlg(m_pickingHub, this);
7174  peDlg->show();
7175 }
7176 
7177 void MainWindow::doActionEditPlane() {
7178  if (!haveSelection()) {
7179  assert(false);
7180  return;
7181  }
7182 
7183  ccPlane* plane = ccHObjectCaster::ToPlane(m_selectedEntities.front());
7184  if (!plane) {
7185  assert(false);
7186  return;
7187  }
7188 
7189  ccPlaneEditDlg* peDlg = new ccPlaneEditDlg(m_pickingHub, this);
7190  peDlg->initWithPlane(plane);
7191  peDlg->show();
7192 }
7193 
7194 void MainWindow::doActionFlipPlane() {
7195  if (!haveSelection()) {
7196  assert(false);
7197  return;
7198  }
7199 
7201  for (ccHObject* entity : m_selectedEntities) {
7202  ccPlane* plane = ccHObjectCaster::ToPlane(entity);
7203  if (plane) {
7204  plane->flip();
7205  plane->setRedrawFlagRecursive(true);
7206  }
7207  }
7208 
7209  refreshAll();
7211 }
7212 
7213 void MainWindow::doActionPromoteCircleToCylinder() {
7214  if (!haveOneSelection()) {
7215  assert(false);
7216  return;
7217  }
7218 
7219  ccCircle* circle = ccHObjectCaster::ToCircle(m_selectedEntities.front());
7220  if (!circle) {
7221  assert(false);
7222  return;
7223  }
7224 
7225  static double CylinderHeight = 0.0;
7226  if (CylinderHeight == 0.0) {
7227  CylinderHeight = 2 * circle->getRadius();
7228  }
7229  bool ok = false;
7230  double value = QInputDialog::getDouble(
7231  this, tr("Cylinder height"), tr("Height"), CylinderHeight, 0.0,
7233  if (!ok) {
7234  return;
7235  }
7236 
7237  CylinderHeight = value;
7238 
7239  ccCylinder* cylinder = new ccCylinder(
7240  static_cast<PointCoordinateType>(circle->getRadius()),
7241  static_cast<PointCoordinateType>(CylinderHeight),
7242  &circle->getGLTransformationHistory(),
7243  tr("Cylinder from ") + circle->getName());
7244 
7245  circle->setEnabled(false);
7246  if (circle->getParent()) {
7247  circle->getParent()->addChild(cylinder);
7248  }
7249 
7250  addToDB(cylinder, true, true);
7251  setSelectedInDB(circle, false);
7252  setSelectedInDB(cylinder, true);
7253 }
7254 
7255 void MainWindow::doActionComparePlanes() {
7256  if (m_selectedEntities.size() != 2) {
7257  ecvConsole::Error("Select 2 planes!");
7258  return;
7259  }
7260 
7261  if (!m_selectedEntities[0]->isKindOf(CV_TYPES::PLANE) ||
7262  !m_selectedEntities[1]->isKindOf(CV_TYPES::PLANE)) {
7263  ecvConsole::Error("Select 2 planes!");
7264  return;
7265  }
7266 
7267  ccPlane* p1 = ccHObjectCaster::ToPlane(m_selectedEntities[0]);
7268  ccPlane* p2 = ccHObjectCaster::ToPlane(m_selectedEntities[1]);
7269 
7270  QStringList info;
7271  info << QString("Plane 1: %1").arg(p1->getName());
7272  CVLog::Print(QString("[Compare] ") + info.last());
7273 
7274  info << QString("Plane 2: %1").arg(p2->getName());
7275  CVLog::Print(QString("[Compare] ") + info.last());
7276 
7277  CCVector3 N1;
7278  CCVector3 N2;
7281  p1->getEquation(N1, d1);
7282  p2->getEquation(N2, d2);
7283 
7284  double angle_rad = N1.angle_rad(N2);
7285  info << QString("Angle P1/P2: %1 deg.")
7286  .arg(cloudViewer::RadiansToDegrees(angle_rad));
7287  CVLog::Print(QString("[Compare] ") + info.last());
7288 
7289  PointCoordinateType planeEq1[4] = {N1.x, N1.y, N1.z, d1};
7290  PointCoordinateType planeEq2[4] = {N2.x, N2.y, N2.z, d2};
7291  CCVector3 C1 = p1->getCenter();
7292  ScalarType distCenter1ToPlane2 =
7294  &C1, planeEq2);
7295  info << QString("Distance Center(P1)/P2: %1").arg(distCenter1ToPlane2);
7296  CVLog::Print(QString("[Compare] ") + info.last());
7297 
7298  CCVector3 C2 = p2->getCenter();
7299  ScalarType distCenter2ToPlane1 =
7301  &C2, planeEq1);
7302  info << QString("Distance Center(P2)/P1: %1").arg(distCenter2ToPlane1);
7303  CVLog::Print(QString("[Compare] ") + info.last());
7304 
7305  // pop-up summary
7306  QMessageBox::information(this, "Plane comparison", info.join("\n"));
7308 }
7309 
7310 // help
7311 void MainWindow::help() {
7312  QDesktopServices::openUrl(QUrl(
7313  QStringLiteral("https://asher-1.github.io/ACloudViewer/docs")));
7315  tr("[ACloudViewer help] "
7316  "https://asher-1.github.io/ACloudViewer/docs!"));
7317 }
7318 
7319 // Change theme: Windows/Darcula
7320 void MainWindow::changeTheme() {
7321  QAction* action = qobject_cast<QAction*>(sender());
7322  QVariant v = action->data();
7323 
7324  QString qssfile = (QString)v.value<QString>();
7325  ChangeStyle(qssfile);
7326 
7327  // persistent saving
7329  qssfile);
7330 }
7331 
7332 // Change language: English/Chinese
7333 void MainWindow::changeLanguage() {
7334  QAction* action = qobject_cast<QAction*>(sender());
7335  QVariant v = action->data();
7336  int language = (int)v.value<int>();
7337 
7338  switch (language) {
7339  case CLOUDVIEWER_LANG_ENGLISH: {
7341  tr("[changeLanguage] Change to English language"));
7342  break;
7343  }
7344  case CLOUDVIEWER_LANG_CHINESE: {
7346  tr("[changeLanguage] Doesn't support Chinese temporarily"));
7347  break;
7348  }
7349  }
7350 }
7351 
7352 void MainWindow::doActionGlobalShiftSeetings() {
7353  QDialog dialog(this);
7354  Ui_GlobalShiftSettingsDialog ui;
7355  ui.setupUi(&dialog);
7356 
7357  ui.maxAbsCoordSpinBox->setValue(static_cast<int>(
7359  ui.maxAbsDiagSpinBox->setValue(static_cast<int>(
7361 
7362  if (!dialog.exec()) {
7363  return;
7364  }
7365 
7366  double maxAbsCoord =
7367  pow(10.0, static_cast<double>(ui.maxAbsCoordSpinBox->value()));
7368  double maxAbsDiag =
7369  pow(10.0, static_cast<double>(ui.maxAbsDiagSpinBox->value()));
7370 
7373 
7374  CVLog::Print(tr("[Global Shift] Max abs. coord = %1 / max abs. diag = %2")
7376  'e', 0)
7378  'e', 0));
7379 
7380  // save to persistent settings
7381  {
7383  maxAbsCoord);
7385  maxAbsDiag);
7386  }
7387 }
7388 
7389 ccPointCloud* MainWindow::askUserToSelectACloud(ccHObject* defaultCloudEntity,
7390  QString inviteMessage) {
7391  ccHObject::Container clouds;
7392  m_ccRoot->getRootEntity()->filterChildren(clouds, true,
7393  CV_TYPES::POINT_CLOUD, true);
7394  if (clouds.empty()) {
7395  ecvConsole::Error(tr("No cloud in database!"));
7396  return 0;
7397  }
7398  // default selected index
7399  int selectedIndex = 0;
7400  if (defaultCloudEntity) {
7401  for (size_t i = 1; i < clouds.size(); ++i) {
7402  if (clouds[i] == defaultCloudEntity) {
7403  selectedIndex = static_cast<int>(i);
7404  break;
7405  }
7406  }
7407  }
7408  // ask the user to choose a cloud
7409  {
7410  selectedIndex = ccItemSelectionDlg::SelectEntity(clouds, selectedIndex,
7411  this, inviteMessage);
7412  if (selectedIndex < 0) return 0;
7413  }
7414 
7415  assert(selectedIndex >= 0 &&
7416  static_cast<size_t>(selectedIndex) < clouds.size());
7417  return ccHObjectCaster::ToPointCloud(clouds[selectedIndex]);
7418 }
7419 
7420 void MainWindow::toggleSelectedEntitiesProperty(
7422  if (!ccEntityAction::toggleProperty(m_selectedEntities, property)) {
7423  return;
7424  }
7425 
7426  refreshSelected();
7427  updateUI();
7428 }
7429 
7430 void MainWindow::clearSelectedEntitiesProperty(
7431  ccEntityAction::CLEAR_PROPERTY property) {
7433  if (!ccEntityAction::clearProperty(m_selectedEntities, property, this)) {
7434  return;
7435  }
7436 
7437  refreshSelected();
7438  updateUI();
7439 }
7440 
7441 void MainWindow::doActionFastRegistration(FastRegistrationMode mode) {
7442  // we need at least 1 entity
7443  if (m_selectedEntities.empty()) return;
7444 
7445  // we must backup 'm_selectedEntities' as removeObjectTemporarilyFromDBTree
7446  // can modify it!
7447  ccHObject::Container selectedEntities = m_selectedEntities;
7448 
7449  for (ccHObject* entity : selectedEntities) {
7450  ccBBox box = entity->getBB_recursive();
7451 
7452  CCVector3 T; // translation
7453 
7454  switch (mode) {
7455  case MoveBBCenterToOrigin:
7456  T = -box.getCenter();
7457  break;
7458  case MoveBBMinCornerToOrigin:
7459  T = -box.minCorner();
7460  break;
7461  case MoveBBMaxCornerToOrigin:
7462  T = -box.maxCorner();
7463  break;
7464  default:
7465  assert(false);
7466  return;
7467  }
7468 
7469  // transformation (used only for translation)
7470  ccGLMatrix glTrans;
7471  glTrans.setTranslation(T);
7472 
7474  ecvConsole::Print(QString("[Synchronize] Transformation matrix (%1):")
7475  .arg(entity->getName()));
7476  ecvConsole::Print(glTrans.toString(12, ' ')); // full precision
7478  "Hint: copy it (CTRL+C) and apply it - or its inverse - on any "
7479  "entity with the 'Edit > Apply transformation' tool");
7480 
7481  // we temporarily detach entity, as it may undergo
7482  //"severe" modifications (octree deletion, etc.) --> see
7483  // ccHObject::applyGLTransformation
7484  ccHObjectContext objContext = removeObjectTemporarilyFromDBTree(entity);
7485  entity->applyGLTransformation_recursive(&glTrans);
7486  putObjectBackIntoDBTree(entity, objContext);
7487  }
7488 
7489  // reselect previously selected entities!
7490  if (m_ccRoot) m_ccRoot->selectEntities(selectedEntities);
7491 
7492  refreshSelected();
7494 
7495  updateUI();
7496 }
7497 
7498 void MainWindow::doActionColorize() { doActionSetColor(true); }
7499 
7500 void MainWindow::doActionSetUniqueColor() { doActionSetColor(false); }
7501 
7502 void MainWindow::doActionSetColor(bool colorize) {
7504  if (!ccEntityAction::setColor(m_selectedEntities, colorize, this)) return;
7505 
7506  updateUI();
7507 }
7508 
7509 void MainWindow::doActionRGBToGreyScale() {
7510  if (!ccEntityAction::rgbToGreyScale(m_selectedEntities)) return;
7511 
7512  refreshSelected();
7513 }
7514 
7515 void MainWindow::doActionSetColorGradient() {
7516  if (!ccEntityAction::setColorGradient(m_selectedEntities, this)) return;
7517 
7518  refreshSelected();
7519  updateUI();
7520 }
7521 
7522 void MainWindow::doActionChangeColorLevels() {
7523  ccEntityAction::changeColorLevels(m_selectedEntities, this);
7524  refreshSelected();
7525 }
7526 
7527 void MainWindow::doActionInterpolateColors() {
7528  if (!ccEntityAction::interpolateColors(m_selectedEntities, this)) return;
7529 
7530  refreshSelected();
7531  updateUI();
7532 }
7533 
7534 void MainWindow::doActionEnhanceRGBWithIntensities() {
7535  if (!ccEntityAction::enhanceRGBWithIntensities(m_selectedEntities, this))
7536  return;
7537 
7538  refreshSelected();
7539 }
7540 
7541 void MainWindow::doActionColorFromScalars() {
7542  for (ccHObject* entity : getSelectedEntities()) {
7543  // for "real" point clouds only
7545  if (cloud) {
7546  // create color from scalar dialogue
7547  ccColorFromScalarDlg* cfsDlg =
7548  new ccColorFromScalarDlg(this, cloud);
7549  cfsDlg->setAttribute(Qt::WA_DeleteOnClose, true);
7550  cfsDlg->show();
7551  }
7552  }
7553 }
7554 
7555 void MainWindow::doActionSORFilter() {
7556  ecvSORFilterDlg sorDlg(this);
7557 
7558  // set semi-persistent/dynamic parameters
7559  static int s_sorFilterKnn = 6;
7560  static double s_sorFilterNSigma = 1.0;
7561  sorDlg.knnSpinBox->setValue(s_sorFilterKnn);
7562  sorDlg.nSigmaDoubleSpinBox->setValue(s_sorFilterNSigma);
7563  if (!sorDlg.exec()) return;
7564 
7565  // update semi-persistent/dynamic parameters
7566  s_sorFilterKnn = sorDlg.knnSpinBox->value();
7567  s_sorFilterNSigma = sorDlg.nSigmaDoubleSpinBox->value();
7568 
7569  ecvProgressDialog pDlg(true, this);
7570  pDlg.setAutoClose(false);
7571 
7572  bool firstCloud = true;
7573 
7574  ccHObject::Container selectedEntities =
7575  getSelectedEntities(); // we have to use a local copy:
7576  // 'selectEntity' will change the set of
7577  // currently selected entities!
7578 
7579  for (ccHObject* entity : selectedEntities) {
7580  // specific test for locked vertices
7581  bool lockedVertices;
7582  ccPointCloud* cloud =
7583  ccHObjectCaster::ToPointCloud(entity, &lockedVertices);
7584  if (cloud && lockedVertices) {
7586  haveOneSelection());
7587  continue;
7588  }
7589 
7590  // computation
7591  cloudViewer::ReferenceCloud* selection =
7593  cloud, s_sorFilterKnn, s_sorFilterNSigma, 0, &pDlg);
7594 
7595  if (selection && cloud) {
7596  if (selection->size() == cloud->size()) {
7597  CVLog::Warning(QString("[doActionSORFilter] No points were "
7598  "removed from cloud '%1'")
7599  .arg(cloud->getName()));
7600  } else {
7601  ccPointCloud* cleanCloud = cloud->partialClone(selection);
7602  if (cleanCloud) {
7603  cleanCloud->setName(cloud->getName() + QString(".clean"));
7604  // cleanCloud->setDisplay(cloud->getDisplay());
7605  if (cloud->getParent())
7606  cloud->getParent()->addChild(cleanCloud);
7607  addToDB(cleanCloud);
7608 
7609  cloud->setEnabled(false);
7610  if (firstCloud) {
7612  "Previously selected entities (sources) have "
7613  "been hidden!");
7614  firstCloud = false;
7615  m_ccRoot->selectEntity(cleanCloud, true);
7616  }
7617  } else {
7619  QString("[doActionSORFilter] Not enough memory to "
7620  "create a clean version of cloud '%1'!")
7621  .arg(cloud->getName()));
7622  }
7623  }
7624 
7625  delete selection;
7626  selection = nullptr;
7627  } else {
7628  // no points fall inside selection!
7629  if (cloud != nullptr) {
7631  QString("[doActionSORFilter] Failed to apply the noise "
7632  "filter to cloud '%1'! (not enough memory?)")
7633  .arg(cloud->getName()));
7634  } else {
7636  "[doActionSORFilter] Trying to apply the noise filter "
7637  "to null cloud");
7638  }
7639  }
7640  }
7641 
7642  updateUI();
7643 }
7644 
7645 void MainWindow::doActionFilterNoise() {
7646  PointCoordinateType kernelRadius =
7647  ccLibAlgorithms::GetDefaultCloudKernelSize(m_selectedEntities);
7648 
7649  ecvNoiseFilterDlg noiseDlg(this);
7650 
7651  // set semi-persistent/dynamic parameters
7652  static bool s_noiseFilterUseKnn = false;
7653  static int s_noiseFilterKnn = 6;
7654  static bool s_noiseFilterUseAbsError = false;
7655  static double s_noiseFilterAbsError = 1.0;
7656  static double s_noiseFilterNSigma = 1.0;
7657  static bool s_noiseFilterRemoveIsolatedPoints = false;
7658  noiseDlg.radiusDoubleSpinBox->setValue(kernelRadius);
7659  noiseDlg.knnSpinBox->setValue(s_noiseFilterKnn);
7660  noiseDlg.nSigmaDoubleSpinBox->setValue(s_noiseFilterNSigma);
7661  noiseDlg.absErrorDoubleSpinBox->setValue(s_noiseFilterAbsError);
7662  noiseDlg.removeIsolatedPointsCheckBox->setChecked(
7663  s_noiseFilterRemoveIsolatedPoints);
7664  if (s_noiseFilterUseAbsError)
7665  noiseDlg.absErrorRadioButton->setChecked(true);
7666  else
7667  noiseDlg.relativeRadioButton->setChecked(true);
7668  if (s_noiseFilterUseKnn)
7669  noiseDlg.knnRadioButton->setChecked(true);
7670  else
7671  noiseDlg.radiusRadioButton->setChecked(true);
7672 
7673  if (!noiseDlg.exec()) return;
7674 
7675  // update semi-persistent/dynamic parameters
7676  kernelRadius = static_cast<PointCoordinateType>(
7677  noiseDlg.radiusDoubleSpinBox->value());
7678  s_noiseFilterUseKnn = noiseDlg.knnRadioButton->isChecked();
7679  s_noiseFilterKnn = noiseDlg.knnSpinBox->value();
7680  s_noiseFilterUseAbsError = noiseDlg.absErrorRadioButton->isChecked();
7681  s_noiseFilterNSigma = noiseDlg.nSigmaDoubleSpinBox->value();
7682  s_noiseFilterAbsError = noiseDlg.absErrorDoubleSpinBox->value();
7683  s_noiseFilterRemoveIsolatedPoints =
7684  noiseDlg.removeIsolatedPointsCheckBox->isChecked();
7685 
7686  ecvProgressDialog pDlg(true, this);
7687  pDlg.setAutoClose(false);
7688 
7689  bool firstCloud = true;
7690 
7691  ccHObject::Container selectedEntities =
7692  getSelectedEntities(); // we have to use a local copy: and
7693  // 'selectEntity' will change the set of
7694  // currently selected entities!
7695 
7696  for (ccHObject* entity : selectedEntities) {
7697  // specific test for locked vertices
7698  bool lockedVertices;
7699  ccPointCloud* cloud =
7700  ccHObjectCaster::ToPointCloud(entity, &lockedVertices);
7701  if (cloud && lockedVertices) {
7703  haveOneSelection());
7704  continue;
7705  }
7706 
7707  // computation
7708  cloudViewer::ReferenceCloud* selection =
7710  cloud, kernelRadius, s_noiseFilterNSigma,
7711  s_noiseFilterRemoveIsolatedPoints, s_noiseFilterUseKnn,
7712  s_noiseFilterKnn, s_noiseFilterUseAbsError,
7713  s_noiseFilterAbsError, 0, &pDlg);
7714 
7715  if (selection && cloud) {
7716  if (selection->size() == cloud->size()) {
7717  CVLog::Warning(QString("[doActionFilterNoise] No points were "
7718  "removed from cloud '%1'")
7719  .arg(cloud->getName()));
7720  } else {
7721  ccPointCloud* cleanCloud = cloud->partialClone(selection);
7722  if (cleanCloud) {
7723  cleanCloud->setName(cloud->getName() + QString(".clean"));
7724  // cleanCloud->setDisplay(cloud->getDisplay());
7725  if (cloud->getParent())
7726  cloud->getParent()->addChild(cleanCloud);
7727  addToDB(cleanCloud);
7728 
7729  cloud->setEnabled(false);
7730  if (firstCloud) {
7732  "Previously selected entities (sources) have "
7733  "been hidden!");
7734  firstCloud = false;
7735  m_ccRoot->selectEntity(cleanCloud, true);
7736  }
7737  } else {
7739  QString("[doActionFilterNoise] Not enough memory "
7740  "to create a clean version of cloud '%1'!")
7741  .arg(cloud->getName()));
7742  }
7743  }
7744 
7745  delete selection;
7746  selection = nullptr;
7747  } else {
7748  // no points fall inside selection!
7749  if (cloud != nullptr) {
7750  ecvConsole::Warning(QString("[doActionFilterNoise] Failed to "
7751  "apply the noise filter to cloud "
7752  "'%1'! (not enough memory?)")
7753  .arg(cloud->getName()));
7754  } else {
7756  "[doActionFilterNoise] Trying to apply the noise "
7757  "filter to null cloud");
7758  }
7759  }
7760  }
7761 
7762  updateUI();
7763 }
7764 
7765 void MainWindow::doActionVoxelSampling() {
7766  if (!haveSelection()) {
7767  return;
7768  }
7769 
7770  ccHObject::Container selectedClouds;
7771  for (auto ent : getSelectedEntities()) {
7772  if (!ent->isKindOf(CV_TYPES::POINT_CLOUD)) {
7773  ecvConsole::Warning("only point cloud is supported!");
7774  continue;
7775  }
7776 
7777  bool lockedVertices = false;
7778  ccPointCloud* pc = ccHObjectCaster::ToPointCloud(ent, &lockedVertices);
7779  if (!pc || lockedVertices) {
7780  continue;
7781  }
7782 
7783  selectedClouds.push_back(ent);
7784  }
7785 
7786  ccHObject::Container outClouds;
7787  if (!ccEntityAction::VoxelSampling(selectedClouds, outClouds, this)) {
7789  "[MainWindow::doActionVoxelSampling] voxel sampling failed!");
7790  return;
7791  }
7792 
7793  assert(outClouds.size() == selectedClouds.size());
7794 
7795  bool firstCloud = true;
7796  for (size_t i = 0; i < outClouds.size(); ++i) {
7797  ccPointCloud* cloud = ccHObjectCaster::ToPointCloud(selectedClouds[i]);
7798  ccPointCloud* cleanCloud = ccHObjectCaster::ToPointCloud(outClouds[i]);
7799  if (cleanCloud) {
7800  cleanCloud->setName(cloud->getName() + QString(".clean"));
7801  if (cloud->getParent()) cloud->getParent()->addChild(cleanCloud);
7802  addToDB(cleanCloud);
7803 
7804  CVLog::Print(QString("%1 down sampled from %2 points to %3 points.")
7805  .arg(cloud->getName())
7806  .arg(cloud->size())
7807  .arg(cleanCloud->size()));
7808 
7809  cloud->setEnabled(false);
7810  if (firstCloud) {
7812  "Previously selected entities (sources) have been "
7813  "hidden!");
7814  firstCloud = false;
7815  m_ccRoot->selectEntity(cleanCloud, true);
7816  }
7817  } else {
7819  QString("[doActionSORFilter] Not enough memory to create a "
7820  "clean version of cloud '%1'!")
7821  .arg(cloud->getName()));
7822  }
7823  }
7824 
7825  updateUI();
7826 }
7827 
7828 void MainWindow::doActionClone() {
7829  ccHObject* lastClone = nullptr;
7830 
7831  for (ccHObject* entity : getSelectedEntities()) {
7832  ccHObject* clone = nullptr;
7833 
7834  if (entity->isKindOf(CV_TYPES::POINT_CLOUD)) {
7835  clone = ccHObjectCaster::ToGenericPointCloud(entity)->clone();
7836  if (!clone) {
7837  ecvConsole::Error(tr("An error occurred while cloning cloud %1")
7838  .arg(entity->getName()));
7839  }
7840  } else if (entity->isKindOf(CV_TYPES::PRIMITIVE)) {
7841  clone = static_cast<ccGenericPrimitive*>(entity)->clone();
7842  if (!clone) {
7844  tr("An error occurred while cloning primitive %1")
7845  .arg(entity->getName()));
7846  }
7847  } else if (entity->isA(CV_TYPES::MESH)) {
7848  clone = ccHObjectCaster::ToMesh(entity)->cloneMesh();
7849  if (!clone) {
7850  ecvConsole::Error(tr("An error occurred while cloning mesh %1")
7851  .arg(entity->getName()));
7852  }
7853  } else if (entity->isA(CV_TYPES::POLY_LINE)) {
7854  ccPolyline* poly = ccHObjectCaster::ToPolyline(entity);
7855  clone = (poly ? new ccPolyline(*poly) : 0);
7856  if (!clone) {
7858  tr("An error occurred while cloning polyline %1")
7859  .arg(entity->getName()));
7860  }
7861  } else if (entity->isA(CV_TYPES::CIRCLE)) {
7862  clone = ccHObjectCaster::ToCircle(entity)->clone();
7863  if (!clone) {
7865  tr("An error occurred while cloning circle %1")
7866  .arg(entity->getName()));
7867  }
7868  } else if (entity->isA(CV_TYPES::DISC)) {
7869  ccDisc* disc = ccHObjectCaster::ToDisc(entity);
7870  clone = (disc ? disc->clone() : 0);
7871  if (!clone) {
7872  ecvConsole::Error(tr("An error occurred while cloning disc %1")
7873  .arg(entity->getName()));
7874  }
7875  } else if (entity->isA(CV_TYPES::FACET)) {
7876  ccFacet* facet = ccHObjectCaster::ToFacet(entity);
7877  clone = (facet ? facet->clone() : 0);
7878  if (!clone) {
7879  ecvConsole::Error(tr("An error occurred while cloning facet %1")
7880  .arg(entity->getName()));
7881  }
7882  } else {
7884  tr("Entity '%1' can't be cloned (type not supported yet!)")
7885  .arg(entity->getName()));
7886  }
7887 
7888  if (clone) {
7889  // copy GL transformation history
7891  entity->getGLTransformationHistory());
7892  addToDB(clone);
7893  lastClone = clone;
7894  }
7895  }
7896 
7897  if (lastClone && m_ccRoot) {
7898  m_ccRoot->selectEntity(lastClone);
7899  }
7900 
7901  updateUI();
7902 }
7903 
7904 // consoleTable right click envent
7905 void MainWindow::popMenuInConsole(const QPoint& pos) {
7906  QAction copyAction(tr("Copy"), this);
7907  QAction clearItemsAction(tr("Clear selected items"), this);
7908  QAction clearConsoleAction(tr("Clear console"), this);
7909 
7910  // Check if there are selected items
7911  QList<QListWidgetItem*> selectedItems =
7912  m_ui->consoleWidget->selectedItems();
7913  bool hasSelection = selectedItems.count() > 0;
7914 
7915  // Enable/disable copy and clear selected items based on selection
7916  copyAction.setEnabled(hasSelection);
7917  clearItemsAction.setEnabled(hasSelection);
7918 
7919  connect(&copyAction, &QAction::triggered, this,
7920  &MainWindow::copyConsoleItems);
7921  connect(&clearItemsAction, &QAction::triggered, this,
7922  &MainWindow::clearConsoleItems);
7923  connect(&clearConsoleAction, &QAction::triggered, this,
7924  &MainWindow::clearConsole);
7925 
7926  QMenu menu(m_ui->consoleWidget);
7927  menu.addAction(&copyAction);
7928  menu.addSeparator();
7929  menu.addAction(&clearItemsAction);
7930  menu.addAction(&clearConsoleAction);
7931  menu.exec(QCursor::pos()); // show in mouse position
7932 }
7933 
7934 // Clear consoleTable
7935 void MainWindow::clearConsole() { m_ui->consoleWidget->clear(); }
7936 
7937 // Copy selected items in consoleTable
7938 void MainWindow::copyConsoleItems() {
7939  QList<QListWidgetItem*> items = m_ui->consoleWidget->selectedItems();
7940 
7941  if (items.count() > 0) {
7942  QStringList strings;
7943  // Sort items by their row index to maintain order
7944  QList<QListWidgetItem*> sortedItems = items;
7945  std::sort(sortedItems.begin(), sortedItems.end(),
7946  [](QListWidgetItem* a, QListWidgetItem* b) {
7947  return a->listWidget()->row(a) < b->listWidget()->row(b);
7948  });
7949 
7950  foreach (QListWidgetItem* item, sortedItems) {
7951  strings << item->text();
7952  }
7953 
7954  QApplication::clipboard()->setText(strings.join("\n"));
7955  }
7956 }
7957 
7958 // Remove selected items in consoleTable
7959 void MainWindow::clearConsoleItems() {
7960  /*get selected items*/
7961  QList<QListWidgetItem*> items = m_ui->consoleWidget->selectedItems();
7962 
7963  if (items.count() > 0) {
7964  if (QMessageBox::Yes ==
7965  QMessageBox::question(this, QStringLiteral("Remove Item"),
7966  QStringLiteral("Remove %1 log information(s)")
7967  .arg(QString::number(items.count())),
7968  QMessageBox::Yes | QMessageBox::No,
7969  QMessageBox::Yes)) {
7970  foreach (QListWidgetItem* var, items) {
7971  m_ui->consoleWidget->removeItemWidget(var);
7972  items.removeOne(var);
7973  delete var;
7974  }
7975  }
7976  }
7977 }
7978 
7979 // sand box research
7980 void MainWindow::doComputeBestFitBB() {
7981  if (QMessageBox::warning(
7982  this, tr("This method is for test purpose only"),
7983  tr("Cloud(s) are going to be rotated while still displayed in "
7984  "their previous position! Proceed?"),
7985  QMessageBox::Yes | QMessageBox::No,
7986  QMessageBox::No) != QMessageBox::Yes) {
7987  return;
7988  }
7989 
7990  // avoid rendering obj that not been selected again
7992 
7993  // backup selected entities as removeObjectTemporarilyFromDBTree can modify
7994  // them
7995  ccHObject::Container selectedEntities = getSelectedEntities();
7996  for (ccHObject* entity : selectedEntities) // warning, getSelectedEntites
7997  // may change during this loop!
7998  {
7999  ccGenericPointCloud* cloud =
8001 
8002  if (cloud && cloud->isA(CV_TYPES::POINT_CLOUD)) // TODO
8003  {
8004  cloudViewer::Neighbourhood Yk(cloud);
8005 
8006  cloudViewer::SquareMatrixd covMat = Yk.computeCovarianceMatrix();
8007  if (covMat.isValid()) {
8008  cloudViewer::SquareMatrixd eigVectors;
8009  std::vector<double> eigValues;
8011  covMat, eigVectors, eigValues, true)) {
8013  eigValues);
8014 
8015  ccGLMatrix trans;
8016  GLfloat* rotMat = trans.data();
8017  for (unsigned j = 0; j < 3; ++j) {
8018  double u[3];
8019  Jacobi<double>::GetEigenVector(eigVectors, j, u);
8020  CCVector3 v(static_cast<PointCoordinateType>(u[0]),
8021  static_cast<PointCoordinateType>(u[1]),
8022  static_cast<PointCoordinateType>(u[2]));
8023  v.normalize();
8024  rotMat[j * 4] = static_cast<float>(v.x);
8025  rotMat[j * 4 + 1] = static_cast<float>(v.y);
8026  rotMat[j * 4 + 2] = static_cast<float>(v.z);
8027  }
8028 
8029  const CCVector3* G = Yk.getGravityCenter();
8030  assert(G);
8031  trans.shiftRotationCenter(*G);
8032 
8033  cloud->setGLTransformation(trans);
8034  trans.invert();
8035 
8036  // we temporarily detach entity, as it may undergo
8037  //"severe" modifications (octree deletion, etc.) --> see
8038  // ccPointCloud::applyRigidTransformation
8039  ccHObjectContext objContext =
8041  static_cast<ccPointCloud*>(cloud)->applyRigidTransformation(
8042  trans);
8043  putObjectBackIntoDBTree(cloud, objContext);
8044  entity->setRedrawFlagRecursive(true);
8046  context.removeViewID =
8047  QString::number(entity->getUniqueID());
8049  }
8050  }
8051  }
8052  }
8053 
8054  refreshAll();
8055 }
8056 
8057 void MainWindow::doActionComputeDistanceMap() {
8058  static unsigned steps = 128;
8059  static double margin = 0.0;
8060  static bool filterRange = false;
8061  static double range[2] = {0.0, 1.0};
8062 
8063  // show dialog
8064  {
8065  QDialog dialog(this);
8066  Ui_DistanceMapDialog ui;
8067  ui.setupUi(&dialog);
8068 
8069  ui.stepsSpinBox->setValue(static_cast<int>(steps));
8070  ui.marginDoubleSpinBox->setValue(margin);
8071  ui.rangeCheckBox->setChecked(filterRange);
8072  ui.minDistDoubleSpinBox->setValue(range[0]);
8073  ui.maxDistDoubleSpinBox->setValue(range[1]);
8074 
8075  if (!dialog.exec()) {
8076  return;
8077  }
8078 
8079  steps = static_cast<unsigned>(ui.stepsSpinBox->value());
8080  margin = ui.marginDoubleSpinBox->value();
8081  filterRange = ui.rangeCheckBox->isChecked();
8082  range[0] = ui.minDistDoubleSpinBox->value();
8083  range[1] = ui.maxDistDoubleSpinBox->value();
8084  }
8085 
8086  ecvProgressDialog pDlg(true, this);
8087  pDlg.setAutoClose(false);
8088 
8089  for (ccHObject* entity : getSelectedEntities()) {
8090  if (!entity->isKindOf(CV_TYPES::MESH) &&
8091  !entity->isKindOf(CV_TYPES::POINT_CLOUD)) {
8092  // non handled entity type
8093  continue;
8094  }
8095 
8096  // cloudViewer::ChamferDistanceTransform cdt;
8098  if (!cdt.initGrid(Tuple3ui(steps, steps, steps))) {
8099  // not enough memory
8100  CVLog::Error(tr("Not enough memory!"));
8101  return;
8102  }
8103 
8104  ccBBox box = entity->getOwnBB();
8105  PointCoordinateType largestDim =
8106  box.getMaxBoxDim() + static_cast<PointCoordinateType>(margin);
8107  PointCoordinateType cellDim = largestDim / steps;
8108  CCVector3 minCorner =
8109  box.getCenter() - CCVector3(1, 1, 1) * (largestDim / 2);
8110 
8111  bool result = false;
8112  if (entity->isKindOf(CV_TYPES::MESH)) {
8113  ccMesh* mesh = static_cast<ccMesh*>(entity);
8114  result = cdt.initDT(mesh, cellDim, minCorner, &pDlg);
8115  } else {
8116  ccGenericPointCloud* cloud =
8117  static_cast<ccGenericPointCloud*>(entity);
8118  result = cdt.initDT(cloud, cellDim, minCorner, &pDlg);
8119  }
8120 
8121  if (!result) {
8122  CVLog::Error(tr("Not enough memory!"));
8123  return;
8124  }
8125 
8126  // cdt.propagateDistance(CHAMFER_345, &pDlg);
8127  cdt.propagateDistance(&pDlg);
8128 
8129  // convert the grid to a cloud
8130  ccPointCloud* gridCloud = new ccPointCloud(
8131  entity->getName() + tr(".distance_grid(%1)").arg(steps));
8132  {
8133  unsigned pointCount = steps * steps * steps;
8134  if (!gridCloud->reserve(pointCount)) {
8135  CVLog::Error(tr("Not enough memory!"));
8136  delete gridCloud;
8137  return;
8138  }
8139 
8140  ccScalarField* sf = new ccScalarField("DT values");
8141  if (!sf->reserveSafe(pointCount)) {
8142  CVLog::Error(tr("Not enough memory!"));
8143  delete gridCloud;
8144  sf->release();
8145  return;
8146  }
8147 
8148  for (unsigned i = 0; i < steps; ++i) {
8149  for (unsigned j = 0; j < steps; ++j) {
8150  for (unsigned k = 0; k < steps; ++k) {
8151  ScalarType d = std::sqrt(static_cast<ScalarType>(
8152  cdt.getValue(i, j, k))) *
8153  cellDim;
8154 
8155  if (!filterRange || (d >= range[0] && d <= range[1])) {
8156  gridCloud->addPoint(minCorner + CCVector3(i + 0.5,
8157  j + 0.5,
8158  k + 0.5) *
8159  cellDim);
8160  sf->addElement(d);
8161  }
8162  }
8163  }
8164  }
8165 
8166  sf->computeMinAndMax();
8167  int sfIdx = gridCloud->addScalarField(sf);
8168 
8169  if (gridCloud->size() == 0) {
8170  CVLog::Warning(tr("[DistanceMap] Cloud '%1': no point falls "
8171  "inside the specified range")
8172  .arg(entity->getName()));
8173  delete gridCloud;
8174  gridCloud = nullptr;
8175  } else {
8176  gridCloud->setCurrentDisplayedScalarField(sfIdx);
8177  gridCloud->showSF(true);
8178  gridCloud->shrinkToFit();
8179  addToDB(gridCloud);
8180  }
8181  }
8182  }
8183 }
8184 
8185 void MainWindow::doActionComputeDistToBestFitQuadric3D() {
8186  bool ok = true;
8187  int steps = QInputDialog::getInt(
8188  this, tr("Distance to best fit quadric (3D)"),
8189  tr("Steps (per dim.)"), 50, 10, 10000, 10, &ok);
8190  if (!ok) return;
8191 
8192  for (ccHObject* entity : getSelectedEntities()) {
8193  if (entity->isKindOf(CV_TYPES::POINT_CLOUD)) {
8194  ccGenericPointCloud* cloud =
8196  cloudViewer::Neighbourhood Yk(cloud);
8197 
8198  double Q[10];
8199  if (Yk.compute3DQuadric(Q)) {
8200  const double& a = Q[0];
8201  const double& b = Q[1];
8202  const double& c = Q[2];
8203  const double& e = Q[3];
8204  const double& f = Q[4];
8205  const double& g = Q[5];
8206  const double& l = Q[6];
8207  const double& m = Q[7];
8208  const double& n = Q[8];
8209  const double& d = Q[9];
8210 
8211  // gravity center
8212  const CCVector3* G = Yk.getGravityCenter();
8213  if (!G) {
8214  ecvConsole::Warning(tr("Failed to get the center of "
8215  "gravity of cloud '%1'!")
8216  .arg(cloud->getName()));
8217  continue;
8218  }
8219 
8220  const ccBBox bbox = cloud->getOwnBB();
8221  PointCoordinateType maxDim = bbox.getMaxBoxDim();
8222  CCVector3 C = bbox.getCenter();
8223 
8224  // Sample points on a cube and compute for each of them the
8225  // distance to the Quadric
8226  ccPointCloud* newCloud = new ccPointCloud();
8227  if (!newCloud->reserve(steps * steps * steps)) {
8228  ecvConsole::Error(tr("Not enough memory!"));
8229  }
8230 
8231  const char defaultSFName[] = "Dist. to 3D quadric";
8232  int sfIdx = newCloud->getScalarFieldIndexByName(defaultSFName);
8233  if (sfIdx < 0) sfIdx = newCloud->addScalarField(defaultSFName);
8234  if (sfIdx < 0) {
8236  tr("Couldn't allocate a new scalar field for "
8237  "computing distances! Try to free some memory "
8238  "..."));
8239  delete newCloud;
8240  continue;
8241  }
8242 
8243  ccScalarField* sf = static_cast<ccScalarField*>(
8244  newCloud->getScalarField(sfIdx));
8245  assert(sf);
8246 
8247  // FILE* fp = fopen("doActionComputeQuadric3D_trace.txt","wt");
8248  for (int x = 0; x < steps; ++x) {
8249  CCVector3 P;
8250  P.x = C.x +
8251  maxDim * (static_cast<PointCoordinateType>(x) /
8252  static_cast<PointCoordinateType>(
8253  steps - 1) -
8254  PC_ONE / 2);
8255  for (int y = 0; y < steps; ++y) {
8256  P.y = C.y +
8257  maxDim *
8258  (static_cast<PointCoordinateType>(y) /
8259  static_cast<PointCoordinateType>(
8260  steps - 1) -
8261  PC_ONE / 2);
8262  for (int z = 0; z < steps; ++z) {
8263  P.z = C.z +
8264  maxDim *
8265  (static_cast<PointCoordinateType>(z) /
8266  static_cast<
8268  steps - 1) -
8269  PC_ONE / 2);
8270  newCloud->addPoint(P);
8271 
8272  // compute distance to quadric
8273  CCVector3 Pc = P - *G;
8274  ScalarType dist = static_cast<ScalarType>(
8275  a * Pc.x * Pc.x + b * Pc.y * Pc.y +
8276  c * Pc.z * Pc.z + e * Pc.x * Pc.y +
8277  f * Pc.y * Pc.z + g * Pc.x * Pc.z +
8278  l * Pc.x + m * Pc.y + n * Pc.z + d);
8279 
8280  sf->addElement(dist);
8281  // fprintf(fp,"%f %f %f %f\n",Pc.x,Pc.y,Pc.z,dist);
8282  }
8283  }
8284  }
8285  // fclose(fp);
8286 
8287  if (sf) {
8288  sf->computeMinAndMax();
8289  newCloud->setCurrentDisplayedScalarField(sfIdx);
8290  newCloud->showSF(true);
8291  }
8292  newCloud->setName(tr("Distance map to 3D quadric"));
8293 
8294  addToDB(newCloud);
8295  } else {
8297  tr("Failed to compute 3D quadric on cloud '%1'")
8298  .arg(cloud->getName()));
8299  }
8300  }
8301  }
8302 }
8303 
8304 // Aurelien BEY le 13/11/2008
8305 void MainWindow::doAction4pcsRegister() {
8306  if (QMessageBox::warning(
8307  this, tr("Work in progress"),
8308  tr("This method is still under development: are you sure you "
8309  "want to use it? (a crash may likely happen)"),
8310  QMessageBox::Yes, QMessageBox::No) == QMessageBox::No)
8311  return;
8312 
8313  if (m_selectedEntities.size() != 2) {
8314  ecvConsole::Error(tr("Select 2 point clouds!"));
8315  return;
8316  }
8317 
8318  if (!m_selectedEntities[0]->isKindOf(CV_TYPES::POINT_CLOUD) ||
8319  !m_selectedEntities[1]->isKindOf(CV_TYPES::POINT_CLOUD)) {
8320  ecvConsole::Error(tr("Select 2 point clouds!"));
8321  return;
8322  }
8323 
8324  ccGenericPointCloud* model =
8325  ccHObjectCaster::ToGenericPointCloud(m_selectedEntities[0]);
8326  ccGenericPointCloud* data =
8327  ccHObjectCaster::ToGenericPointCloud(m_selectedEntities[1]);
8328 
8329  ccAlignDlg aDlg(model, data);
8330  if (!aDlg.exec()) return;
8331 
8332  // model = aDlg.getModelObject();
8333  data = aDlg.getDataObject();
8334 
8335  // Take the correct number of points among the clouds
8336  cloudViewer::ReferenceCloud* subModel = aDlg.getSampledModel();
8337  cloudViewer::ReferenceCloud* subData = aDlg.getSampledData();
8338 
8339  unsigned nbMaxCandidates = aDlg.isNumberOfCandidatesLimited()
8340  ? aDlg.getMaxNumberOfCandidates()
8341  : 0;
8342 
8343  ecvProgressDialog pDlg(true, this);
8344 
8347  subModel, subData, transform,
8348  static_cast<ScalarType>(aDlg.getDelta()),
8349  static_cast<ScalarType>(aDlg.getDelta() / 2),
8350  static_cast<PointCoordinateType>(aDlg.getOverlap()),
8351  aDlg.getNbTries(), 5000, &pDlg, nbMaxCandidates)) {
8352  // output resulting transformation matrix
8353  {
8354  ccGLMatrix transMat =
8355  FromCCLibMatrix<double, float>(transform.R, transform.T);
8357  ecvConsole::Print(tr("[Align] Resulting matrix:"));
8358  ecvConsole::Print(transMat.toString(12, ' ')); // full precision
8359  ecvConsole::Print(tr(
8360  "Hint: copy it (CTRL+C) and apply it - or its inverse - on "
8361  "any entity with the 'Edit > Apply transformation' tool"));
8362  }
8363 
8364  ccPointCloud* newDataCloud =
8365  data->isA(CV_TYPES::POINT_CLOUD)
8366  ? static_cast<ccPointCloud*>(data)->cloneThis()
8367  : ccPointCloud::From(data, data);
8368 
8369  if (data->getParent()) data->getParent()->addChild(newDataCloud);
8370  newDataCloud->setName(data->getName() + tr(".registered"));
8371  transform.apply(*newDataCloud);
8372  newDataCloud->invalidateBoundingBox(); // invalidate bb
8373  addToDB(newDataCloud);
8374  zoomOn(newDataCloud);
8375 
8376  data->setEnabled(false);
8377  } else {
8378  ecvConsole::Warning(tr("[Align] Registration failed!"));
8379  }
8380 
8381  if (subModel) delete subModel;
8382  if (subData) delete subData;
8383 
8384  updateUI();
8385 }
8386 
8387 // semi-persistent parameters
8390 static double s_msRmsDiff = 1.0e-5;
8391 static int s_msFinalOverlap = 100;
8392 
8393 void MainWindow::doActionMatchScales() {
8394  // we need at least 2 entities
8395  if (m_selectedEntities.size() < 2) return;
8396 
8397  // we must select the point clouds and meshes
8398  ccHObject::Container selectedEntities;
8399  try {
8400  for (ccHObject* entity : getSelectedEntities()) {
8401  if (entity->isKindOf(CV_TYPES::POINT_CLOUD) ||
8402  entity->isKindOf(CV_TYPES::MESH)) {
8403  selectedEntities.push_back(entity);
8404  }
8405  }
8406  } catch (const std::bad_alloc&) {
8407  ecvConsole::Error(tr("Not enough memory!"));
8408  return;
8409  }
8410 
8411  ccMatchScalesDlg msDlg(selectedEntities, 0, this);
8413  msDlg.rmsDifferenceLineEdit->setText(QString::number(s_msRmsDiff, 'e', 1));
8414  msDlg.overlapSpinBox->setValue(s_msFinalOverlap);
8415 
8416  if (!msDlg.exec()) return;
8417 
8418  // save semi-persistent parameters
8421  s_msRmsDiff = msDlg.rmsDifferenceLineEdit->text().toDouble();
8422  s_msFinalOverlap = msDlg.overlapSpinBox->value();
8423  }
8424 
8426  s_msAlgorithm, selectedEntities, s_msRmsDiff, s_msFinalOverlap,
8427  msDlg.getSelectedIndex(), this);
8428 
8429  // reselect previously selected entities!
8430  if (m_ccRoot) m_ccRoot->selectEntities(selectedEntities);
8431 
8432  updateUI();
8433 }
8434 
8435 void MainWindow::doActionMatchBBCenters() {
8436  // we need at least 2 entities
8437  if (m_selectedEntities.size() < 2) return;
8438 
8439  // we must backup 'm_selectedEntities' as removeObjectTemporarilyFromDBTree
8440  // can modify it!
8441  ccHObject::Container selectedEntities = m_selectedEntities;
8442 
8443  // by default, we take the first entity as reference
8444  // TODO: maybe the user would like to select the reference himself ;)
8445  ccHObject* refEnt = selectedEntities[0];
8446  CCVector3 refCenter = refEnt->getBB_recursive().getCenter();
8447 
8448  for (ccHObject* entity : selectedEntities) // warning, getSelectedEntites
8449  // may change during this loop!
8450  {
8451  CCVector3 center = entity->getBB_recursive().getCenter();
8452 
8453  CCVector3 T = refCenter - center;
8454 
8455  // transformation (used only for translation)
8456  ccGLMatrix glTrans;
8457  glTrans += T;
8458 
8460  ecvConsole::Print(tr("[Synchronize] Transformation matrix (%1 --> %2):")
8461  .arg(entity->getName(),
8462  selectedEntities[0]->getName()));
8463  ecvConsole::Print(glTrans.toString(12, ' ')); // full precision
8465  tr("Hint: copy it (CTRL+C) and apply it - or its inverse - on "
8466  "any entity with the 'Edit > Apply transformation' tool"));
8467 
8468  // we temporarily detach entity, as it may undergo
8469  //"severe" modifications (octree deletion, etc.) --> see
8470  // ccHObject::applyGLTransformation
8471  ccHObjectContext objContext = removeObjectTemporarilyFromDBTree(entity);
8472  entity->applyGLTransformation_recursive(&glTrans);
8473  putObjectBackIntoDBTree(entity, objContext);
8474  }
8475 
8476  // reselect previously selected entities!
8477  if (m_ccRoot) m_ccRoot->selectEntities(selectedEntities);
8478 
8480  refreshSelected();
8481  updateUI();
8482 }
8483 
8484 void MainWindow::doActionRegister() {
8485  if (m_selectedEntities.size() != 2 ||
8486  (!m_selectedEntities[0]->isKindOf(CV_TYPES::POINT_CLOUD) &&
8487  !m_selectedEntities[0]->isKindOf(CV_TYPES::MESH)) ||
8488  (!m_selectedEntities[1]->isKindOf(CV_TYPES::POINT_CLOUD) &&
8489  !m_selectedEntities[1]->isKindOf(CV_TYPES::MESH))) {
8490  ecvConsole::Error(tr("Select 2 point clouds or meshes!"));
8491  return;
8492  }
8493 
8494  ccHObject* data = static_cast<ccHObject*>(m_selectedEntities[0]);
8495  ccHObject* model = static_cast<ccHObject*>(m_selectedEntities[1]);
8496  if (data->isKindOf(CV_TYPES::MESH) &&
8497  model->isKindOf(CV_TYPES::POINT_CLOUD)) {
8498  // by default, prefer the mesh as the reference
8499  std::swap(data, model);
8500  }
8501 
8502  ccRegistrationDlg rDlg(data, model, this);
8503  if (!rDlg.exec()) return;
8504 
8505  // model and data order may have changed!
8506  model = rDlg.getModelEntity();
8507  data = rDlg.getDataEntity();
8508 
8509  double minRMSDecrease = rDlg.getMinRMSDecrease();
8510  if (std::isnan(minRMSDecrease)) {
8511  CVLog::Error(tr("Invalid minimum RMS decrease value"));
8512  return;
8513  }
8514  if (minRMSDecrease < ccRegistrationDlg::GetAbsoluteMinRMSDecrease()) {
8516  CVLog::Error(tr("Minimum RMS decrease value is too small.\n%1 will be "
8517  "used instead (numerical accuracy limit).")
8518  .arg(minRMSDecrease, 0, 'E', 1));
8519  rDlg.setMinRMSDecrease(minRMSDecrease);
8520  }
8521 
8523  {
8524  parameters.convType = rDlg.getConvergenceMethod();
8525  parameters.minRMSDecrease = minRMSDecrease;
8526  parameters.nbMaxIterations = rDlg.getMaxIterationCount();
8527  parameters.adjustScale = rDlg.adjustScale();
8528  parameters.filterOutFarthestPoints = rDlg.removeFarthestPoints();
8529  parameters.samplingLimit = rDlg.randomSamplingLimit();
8530  parameters.finalOverlapRatio = rDlg.getFinalOverlap() / 100.0;
8531  parameters.transformationFilters = rDlg.getTransformationFilters();
8532  parameters.maxThreadCount = rDlg.getMaxThreadCount();
8533  parameters.useC2MSignedDistances = rDlg.useC2MSignedDistances();
8534  parameters.normalsMatching = rDlg.normalsMatchingOption();
8535  }
8536  bool useDataSFAsWeights = rDlg.useDataSFAsWeights();
8537  bool useModelSFAsWeights = rDlg.useModelSFAsWeights();
8538 
8539  // semi-persistent storage (for next call)
8540  rDlg.saveParameters();
8541 
8542  ccGLMatrix transMat;
8543  double finalError = 0.0;
8544  double finalScale = 1.0;
8545  unsigned finalPointCount = 0;
8546 
8548  data, model, transMat, finalScale, finalError, finalPointCount,
8549  parameters, useDataSFAsWeights, useModelSFAsWeights, this)) {
8550  QString rmsString = tr("Final RMS*: %1 (computed on %2 points)")
8551  .arg(finalError)
8552  .arg(finalPointCount);
8553  QString rmsDisclaimerString =
8554  tr("(* RMS is potentially weighted, depending on the selected "
8555  "options)");
8556  CVLog::Print(QString("[Register] ") + rmsString);
8557  CVLog::Print(QString("[Register] ") + rmsDisclaimerString);
8558 
8559  QStringList summary;
8560  summary << rmsString;
8561  summary << rmsDisclaimerString;
8562  summary << "----------------";
8563 
8564  // transformation matrix
8565  {
8566  summary << "Transformation matrix";
8567  summary << transMat.toString(
8568  3, '\t'); // low precision, just for display
8569  summary << "----------------";
8570 
8571  CVLog::Print(tr("[Register] Applied transformation matrix:"));
8572  CVLog::Print(transMat.toString(12, ' ')); // full precision
8573  CVLog::Print(tr(
8574  "Hint: copy it (CTRL+C) and apply it - or its inverse - on "
8575  "any entity with the 'Edit > Apply transformation' tool"));
8576  }
8577 
8578  if (parameters.adjustScale) {
8579  QString scaleString =
8580  tr("Scale: %1 (already integrated in above matrix!)")
8581  .arg(finalScale);
8582  CVLog::Warning(tr("[Register] ") + scaleString);
8583  summary << scaleString;
8584  } else {
8585  CVLog::Print(tr("[Register] Scale: fixed (1.0)"));
8586  summary << tr("Scale: fixed (1.0)");
8587  }
8588 
8589  // overlap
8590  summary << "----------------";
8591  QString overlapString =
8592  tr("Theoretical overlap: %1%")
8593  .arg(static_cast<int>(parameters.finalOverlapRatio *
8594  100));
8595  CVLog::Print(tr("[Register] %1").arg(overlapString));
8596  summary << overlapString;
8597 
8598  summary << "----------------";
8599  summary << tr("This report has been output to Console (F8)");
8600 
8601  // cloud to move
8602  ccGenericPointCloud* pc = nullptr;
8603 
8604  if (data->isKindOf(CV_TYPES::POINT_CLOUD)) {
8606  } else if (data->isKindOf(CV_TYPES::MESH)) {
8608  pc = mesh->getAssociatedCloud();
8609 
8610  // warning: point cloud is locked!
8611  if (pc->isLocked()) {
8612  pc = nullptr;
8613  // we ask the user about cloning the 'data' mesh
8614  QMessageBox::StandardButton result = QMessageBox::question(
8615  this, tr("Registration"),
8616  tr("Data mesh vertices are locked (they may be shared "
8617  "with other meshes): Do you wish to clone this mesh "
8618  "to apply transformation?"),
8619  QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Ok);
8620 
8621  // continue process?
8622  if (result == QMessageBox::Ok) {
8623  ccGenericMesh* newMesh = nullptr;
8624  if (mesh->isA(CV_TYPES::MESH))
8625  newMesh = static_cast<ccMesh*>(mesh)->cloneMesh();
8626  else {
8627  // FIXME TODO
8628  CVLog::Error(tr("Doesn't work on sub-meshes yet!"));
8629  }
8630 
8631  if (newMesh) {
8632  // newMesh->setDisplay(data->getDisplay());
8633  addToDB(newMesh);
8634  data = newMesh;
8635  pc = newMesh->getAssociatedCloud();
8636  } else {
8637  CVLog::Error(
8638  tr("Failed to clone 'data' mesh! (not enough "
8639  "memory?)"));
8640  }
8641  }
8642  }
8643  }
8644 
8645  // if we managed to get a point cloud to move!
8646  if (pc) {
8647  // we temporarily detach cloud, as it may undergo
8648  //"severe" modifications (octree deletion, etc.) --> see
8649  // ccPointCloud::applyRigidTransformation
8650  ccHObjectContext objContext = removeObjectTemporarilyFromDBTree(pc);
8651  pc->applyRigidTransformation(transMat);
8652  putObjectBackIntoDBTree(pc, objContext);
8653 
8654  // don't forget to update mesh bounding box also!
8655  if (data->isKindOf(CV_TYPES::MESH))
8657 
8658  // don't forget global shift
8659  ccGenericPointCloud* refPc =
8661  if (refPc) {
8662  if (refPc->isShifted()) {
8663  const CCVector3d& Pshift = refPc->getGlobalShift();
8664  const double& scale = refPc->getGlobalScale();
8665  pc->setGlobalShift(Pshift);
8666  pc->setGlobalScale(scale);
8667  CVLog::Warning(tr("[ICP] Aligned entity global shift has "
8668  "been updated to match the reference: "
8669  "(%1,%2,%3) [x%4]")
8670  .arg(Pshift.x)
8671  .arg(Pshift.y)
8672  .arg(Pshift.z)
8673  .arg(scale));
8674  } else if (pc->isShifted()) // we'll ask the user first before
8675  // dropping the shift information
8676  // on the aligned cloud
8677  {
8678  if (QMessageBox::question(
8679  this, tr("Drop shift information?"),
8680  tr("Aligned entity is shifted but reference "
8681  "cloud is not: drop global shift "
8682  "information?"),
8683  QMessageBox::Yes,
8684  QMessageBox::No) == QMessageBox::Yes) {
8685  pc->setGlobalShift(0, 0, 0);
8686  pc->setGlobalScale(1.0);
8688  tr("[ICP] Aligned entity global shift has been "
8689  "reset to match the reference!"));
8690  }
8691  }
8692  }
8693 
8694  data->setName(data->getName() + tr(".registered"));
8695  // avoid rendering other object this time
8697  zoomOn(data);
8698  }
8699 
8700  // pop-up summary
8701  QMessageBox::information(this, tr("Register info"), summary.join("\n"));
8703  }
8704 
8705  updateUI();
8706 }
8707 
8708 void MainWindow::activateRegisterPointPairTool() {
8709  if (!haveSelection()) {
8711  tr("Select one or two entities (point cloud or mesh)!"));
8712  return;
8713  }
8714 
8715  ccHObject::Container alignedEntities;
8716  ccHObject::Container refEntities;
8717  try {
8718  ccHObject::Container entities;
8719  entities.reserve(m_selectedEntities.size());
8720 
8721  for (ccHObject* entity : m_selectedEntities) {
8722  // for now, we only handle clouds or meshes
8723  if (entity->isKindOf(CV_TYPES::POINT_CLOUD) ||
8724  entity->isKindOf(CV_TYPES::MESH)) {
8725  entities.push_back(entity);
8726  }
8727  }
8728 
8729  if (entities.empty()) {
8731  "Select at least one entity (point cloud or mesh)!");
8732  return;
8733  } else if (entities.size() == 1) {
8734  alignedEntities = entities;
8735  } else {
8736  std::vector<int> indexes;
8738  entities, indexes, this,
8739  tr("Select aligned entities"))) {
8740  // process cancelled by the user
8741  return;
8742  }
8743 
8744  // add the selected indexes as 'aligned' entities
8745  alignedEntities.reserve(indexes.size());
8746  for (size_t i = 0; i < indexes.size(); ++i) {
8747  alignedEntities.push_back(entities[indexes[i]]);
8748  }
8749 
8750  // add the others as 'reference' entities
8751  assert(indexes.size() <= entities.size());
8752  refEntities.reserve(entities.size() - indexes.size());
8753  for (size_t i = 0; i < entities.size(); ++i) {
8754  if (std::find(indexes.begin(), indexes.end(), i) ==
8755  indexes.end()) {
8756  refEntities.push_back(entities[i]);
8757  }
8758  }
8759  }
8760  } catch (const std::bad_alloc&) {
8761  CVLog::Error(tr("Not enough memory"));
8762  return;
8763  }
8764 
8765  if (alignedEntities.empty()) {
8766  CVLog::Error(tr("No aligned entity selected"));
8767  return;
8768  }
8769 
8770  // deselect all entities
8771  if (m_ccRoot) {
8772  m_ccRoot->unselectAllEntities();
8773  }
8774 
8775  if (!m_pprDlg) {
8776  m_pprDlg = new ccPointPairRegistrationDlg(m_pickingHub, this, this);
8777  connect(m_pprDlg, &ccOverlayDialog::processFinished, this,
8778  &MainWindow::deactivateRegisterPointPairTool);
8779  registerOverlayDialog(m_pprDlg, Qt::TopRightCorner);
8780  }
8781 
8782  if (!getActiveWindow()) {
8783  CVLog::Error(tr(
8784  "[PointPairRegistration] Failed to create dedicated 3D view!"));
8785  return;
8786  }
8787 
8788  if (!m_pprDlg->init(ecvDisplayTools::GetCurrentScreen(), alignedEntities,
8789  &refEntities))
8790  deactivateRegisterPointPairTool(false);
8791 
8792  freezeUI(true);
8793 
8794  if (!m_pprDlg->start()) {
8795  // reselect previously selected entities!
8796  if (m_ccRoot) {
8797  m_ccRoot->selectEntities(alignedEntities);
8798  m_ccRoot->selectEntities(refEntities);
8799  }
8804  }
8805  deactivateRegisterPointPairTool(false);
8806  } else
8808 }
8809 
8810 void MainWindow::deactivateRegisterPointPairTool(bool state) {
8811  if (m_pprDlg) m_pprDlg->clear();
8812 
8813  QList<QMdiSubWindow*> subWindowList = m_mdiArea->subWindowList();
8814  if (!subWindowList.isEmpty()) subWindowList.first()->showMaximized();
8815 
8816  freezeUI(false);
8817 
8818  updateUI();
8819 }
8820 
8821 void MainWindow::doSphericalNeighbourhoodExtractionTest() {
8822  size_t selNum = m_selectedEntities.size();
8823  if (selNum < 1) return;
8824 
8825  // spherical neighborhood extraction radius
8826  PointCoordinateType sphereRadius =
8827  ccLibAlgorithms::GetDefaultCloudKernelSize(m_selectedEntities);
8828  if (sphereRadius < 0) {
8829  ecvConsole::Error(tr("Invalid kernel size!"));
8830  return;
8831  }
8832 
8833  bool ok;
8834  double val = QInputDialog::getDouble(this, tr("SNE test"), tr("Radius:"),
8835  static_cast<double>(sphereRadius),
8836  DBL_MIN, 1.0e9, 8, &ok);
8837  if (!ok) return;
8838  sphereRadius = static_cast<PointCoordinateType>(val);
8839 
8840  QString sfName = tr("Spherical extraction test (%1)").arg(sphereRadius);
8841 
8842  ecvProgressDialog pDlg(true, this);
8843  pDlg.setAutoClose(false);
8845  for (size_t i = 0; i < selNum; ++i) {
8846  // we only process clouds
8847  if (!m_selectedEntities[i]->isA(CV_TYPES::POINT_CLOUD)) {
8848  continue;
8849  }
8850  ccPointCloud* cloud =
8851  ccHObjectCaster::ToPointCloud(m_selectedEntities[i]);
8852 
8853  int sfIdx = cloud->getScalarFieldIndexByName(qPrintable(sfName));
8854  if (sfIdx < 0) sfIdx = cloud->addScalarField(qPrintable(sfName));
8855  if (sfIdx < 0) {
8856  ecvConsole::Error(tr("Failed to create scalar field on cloud '%1' "
8857  "(not enough memory?)")
8858  .arg(cloud->getName()));
8859  return;
8860  }
8861 
8862  ccOctree::Shared octree = cloud->getOctree();
8863  if (!octree) {
8864  pDlg.reset();
8865  pDlg.show();
8866  octree = cloud->computeOctree(&pDlg);
8867  if (!octree) {
8868  ecvConsole::Error(tr("Couldn't compute octree for cloud '%1'!")
8869  .arg(cloud->getName()));
8870  return;
8871  }
8872  }
8873 
8874  cloudViewer::ScalarField* sf = cloud->getScalarField(sfIdx);
8875  sf->fill(NAN_VALUE);
8876  cloud->setCurrentScalarField(sfIdx);
8877 
8878  QElapsedTimer eTimer;
8879  eTimer.start();
8880 
8881  size_t extractedPoints = 0;
8882  unsigned char level =
8884  sphereRadius);
8885  std::random_device rd; // non-deterministic generator
8886  std::mt19937 gen(rd()); // to seed mersenne twister.
8887  std::uniform_int_distribution<unsigned> dist(0, cloud->size() - 1);
8888 
8889  const unsigned samples = 1000;
8890  for (unsigned j = 0; j < samples; ++j) {
8891  unsigned randIndex = dist(gen);
8894  *cloud->getPoint(randIndex), sphereRadius, neighbours,
8895  level);
8896  size_t neihgboursCount = neighbours.size();
8897  extractedPoints += neihgboursCount;
8898  for (size_t k = 0; k < neihgboursCount; ++k)
8899  cloud->setPointScalarValue(neighbours[k].pointIndex,
8900  static_cast<ScalarType>(sqrt(
8901  neighbours[k].squareDistd)));
8902  }
8904  tr("[SNE_TEST] Mean extraction time = %1 ms (radius = %2, "
8905  "mean(neighbours) = %3)")
8906  .arg(eTimer.elapsed())
8907  .arg(sphereRadius)
8908  .arg(extractedPoints / static_cast<double>(samples)));
8909 
8910  sf->computeMinAndMax();
8911  cloud->setCurrentDisplayedScalarField(sfIdx);
8912  cloud->showSF(true);
8913  cloud->setRedrawFlagRecursive(true);
8914  }
8915 
8916  refreshAll();
8917  updateUI();
8918 }
8919 
8920 void MainWindow::doCylindricalNeighbourhoodExtractionTest() {
8921  bool ok;
8922  double radius = QInputDialog::getDouble(this, tr("CNE Test"), tr("radius"),
8923  0.02, 1.0e-6, 1.0e6, 6, &ok);
8924  if (!ok) return;
8925 
8926  double height = QInputDialog::getDouble(this, tr("CNE Test"), tr("height"),
8927  0.05, 1.0e-6, 1.0e6, 6, &ok);
8928  if (!ok) return;
8929 
8930  ccPointCloud* cloud = new ccPointCloud(tr("cube"));
8931  const unsigned ptsCount = 1000000;
8932  if (!cloud->reserve(ptsCount)) {
8933  ecvConsole::Error(tr("Not enough memory!"));
8934  delete cloud;
8935  return;
8936  }
8937 
8938  // fill a unit cube with random points
8939  {
8940  std::random_device rd; // non-deterministic generator
8941  std::mt19937 gen(rd()); // to seed mersenne twister.
8942  std::uniform_real_distribution<double> dist(0, 1);
8943 
8944  for (unsigned i = 0; i < ptsCount; ++i) {
8945  CCVector3 P(dist(gen), dist(gen), dist(gen));
8946 
8947  cloud->addPoint(P);
8948  }
8949  }
8950 
8951  // get/Add scalar field
8952  static const char DEFAULT_CNE_TEST_TEMP_SF_NAME[] = "CNE test";
8953  int sfIdx = cloud->getScalarFieldIndexByName(DEFAULT_CNE_TEST_TEMP_SF_NAME);
8954  if (sfIdx < 0) sfIdx = cloud->addScalarField(DEFAULT_CNE_TEST_TEMP_SF_NAME);
8955  if (sfIdx < 0) {
8956  ecvConsole::Error(tr("Not enough memory!"));
8957  delete cloud;
8958  return;
8959  }
8960  cloud->setCurrentScalarField(sfIdx);
8961 
8962  // reset scalar field
8963  cloud->getScalarField(sfIdx)->fill(NAN_VALUE);
8964 
8965  ecvProgressDialog pDlg(true, this);
8966  ccOctree::Shared octree = cloud->computeOctree(&pDlg);
8967  if (octree) {
8968  QElapsedTimer subTimer;
8969  subTimer.start();
8970  unsigned long long extractedPoints = 0;
8971  unsigned char level =
8973  static_cast<PointCoordinateType>(
8974  2.5 * radius)); // 2.5 = empirical
8975  const unsigned samples = 1000;
8976  std::random_device rd; // non-deterministic generator
8977  std::mt19937 gen(rd()); // to seed mersenne twister.
8978  std::uniform_real_distribution<PointCoordinateType> distAngle(
8979  0, static_cast<PointCoordinateType>(2 * M_PI));
8980  std::uniform_int_distribution<unsigned> distIndex(0, ptsCount - 1);
8981 
8982  for (unsigned j = 0; j < samples; ++j) {
8983  // generate random normal vector
8984  CCVector3 dir(0, 0, 1);
8985  {
8986  ccGLMatrix rot;
8987  rot.initFromParameters(distAngle(gen), distAngle(gen),
8988  distAngle(gen), CCVector3(0, 0, 0));
8989  rot.applyRotation(dir);
8990  }
8991  unsigned randIndex = distIndex(gen);
8992 
8994  cn.center = *cloud->getPoint(randIndex);
8995  cn.dir = dir;
8996  cn.level = level;
8997  cn.radius = static_cast<PointCoordinateType>(radius);
8998  cn.maxHalfLength = static_cast<PointCoordinateType>(height / 2);
8999 
9001  // octree->getPointsInSphericalNeighbourhood(*cloud->getPoint(randIndex),radius,neighbours,level);
9002  size_t neihgboursCount = cn.neighbours.size();
9003  extractedPoints += static_cast<unsigned long long>(neihgboursCount);
9004  for (size_t k = 0; k < neihgboursCount; ++k) {
9005  cloud->setPointScalarValue(
9006  cn.neighbours[k].pointIndex,
9007  static_cast<ScalarType>(
9008  sqrt(cn.neighbours[k].squareDistd)));
9009  }
9010  }
9012  tr("[CNE_TEST] Mean extraction time = %1 ms (radius = %2, "
9013  "height = %3, mean(neighbours) = %4)")
9014  .arg(subTimer.elapsed())
9015  .arg(radius)
9016  .arg(height)
9017  .arg(static_cast<double>(extractedPoints) / samples));
9018  } else {
9019  ecvConsole::Error(tr("Failed to compute octree!"));
9020  }
9021 
9022  ccScalarField* sf =
9023  static_cast<ccScalarField*>(cloud->getScalarField(sfIdx));
9024  sf->computeMinAndMax();
9025  sf->showNaNValuesInGrey(false);
9026  cloud->setCurrentDisplayedScalarField(sfIdx);
9027  cloud->showSF(true);
9028 
9029  addToDB(cloud);
9030 
9031  updateUI();
9032 }
9033 
9034 void MainWindow::doActionCreateCloudFromEntCenters() {
9035  size_t selNum = getSelectedEntities().size();
9036 
9037  ccPointCloud* centers = new ccPointCloud(tr("centers"));
9038  if (!centers->reserve(static_cast<unsigned>(selNum))) {
9039  CVLog::Error(tr("Not enough memory!"));
9040  delete centers;
9041  centers = nullptr;
9042  return;
9043  }
9044 
9045  // look for clouds
9046  {
9047  for (ccHObject* entity : getSelectedEntities()) {
9049 
9050  if (cloud == nullptr) {
9051  continue;
9052  }
9053 
9054  centers->addPoint(cloud->getOwnBB().getCenter());
9055 
9056  // we display the cloud in the same window as the first (selected)
9057  // cloud we encounter if (!centers->getDisplay())
9058  //{
9059  // centers->setDisplay(cloud->getDisplay());
9060  // }
9061  }
9062  }
9063 
9064  if (centers->size() == 0) {
9065  CVLog::Error(tr("No cloud in selection?!"));
9066  delete centers;
9067  centers = nullptr;
9068  } else {
9069  centers->resize(centers->size());
9070  centers->setPointSize(10);
9071  centers->setVisible(true);
9072  addToDB(centers);
9073  }
9074 }
9075 
9076 void MainWindow::doActionComputeBestICPRmsMatrix() {
9077  // look for clouds
9078  std::vector<ccPointCloud*> clouds;
9079  try {
9080  for (ccHObject* entity : getSelectedEntities()) {
9082  if (cloud) {
9083  clouds.push_back(cloud);
9084  }
9085  }
9086  } catch (const std::bad_alloc&) {
9087  CVLog::Error(tr("Not enough memory!"));
9088  return;
9089  }
9090 
9091  size_t cloudCount = clouds.size();
9092  if (cloudCount < 2) {
9093  CVLog::Error(tr("Need at least two clouds!"));
9094  return;
9095  }
9096 
9097  // init matrices
9098  std::vector<double> rmsMatrix;
9099  std::vector<ccGLMatrix> matrices;
9100  std::vector<std::pair<double, double>> matrixAngles;
9101  try {
9102  rmsMatrix.resize(cloudCount * cloudCount, 0);
9103 
9104  // init all possible transformations
9105  static const double angularStep_deg = 45.0;
9106  unsigned phiSteps = static_cast<unsigned>(360.0 / angularStep_deg);
9108  std::abs(360.0 - phiSteps * angularStep_deg)));
9109  unsigned thetaSteps = static_cast<unsigned>(180.0 / angularStep_deg);
9111  std::abs(180.0 - thetaSteps * angularStep_deg)));
9112  unsigned rotCount = phiSteps * (thetaSteps - 1) + 2;
9113  matrices.reserve(rotCount);
9114  matrixAngles.reserve(rotCount);
9115 
9116  for (unsigned j = 0; j <= thetaSteps; ++j) {
9117  // we want to cover the full [0-180] interval! ([-90;90] in fact)
9118  double theta_deg = j * angularStep_deg - 90.0;
9119  for (unsigned i = 0; i < phiSteps; ++i) {
9120  double phi_deg = i * angularStep_deg;
9121  ccGLMatrix trans;
9122  trans.initFromParameters(
9123  static_cast<float>(
9125  static_cast<float>(
9126  cloudViewer::DegreesToRadians(theta_deg)),
9127  0, CCVector3(0, 0, 0));
9128  matrices.push_back(trans);
9129  matrixAngles.push_back(
9130  std::pair<double, double>(phi_deg, theta_deg));
9131 
9132  // for poles, no need to rotate!
9133  if (j == 0 || j == thetaSteps) break;
9134  }
9135  }
9136  } catch (const std::bad_alloc&) {
9137  CVLog::Error(tr("Not enough memory!"));
9138  return;
9139  }
9140 
9141  // let's start!
9142  {
9143  ecvProgressDialog pDlg(true, this);
9144  pDlg.setMethodTitle(tr("Testing all possible positions"));
9145  pDlg.setInfo(tr("%1 clouds and %2 positions")
9146  .arg(cloudCount)
9147  .arg(matrices.size()));
9149  &pDlg,
9150  static_cast<unsigned>(((cloudCount * (cloudCount - 1)) / 2) *
9151  matrices.size()));
9152  pDlg.start();
9153  QApplication::processEvents();
9154 
9155  // #define TEST_GENERATION
9156 #ifdef TEST_GENERATION
9157  ccPointCloud* testSphere = new ccPointCloud();
9158  testSphere->reserve(matrices.size());
9159 #endif
9160 
9161  for (size_t i = 0; i < cloudCount - 1; ++i) {
9162  ccPointCloud* A = clouds[i];
9163  A->computeOctree();
9164 
9165  for (size_t j = i + 1; j < cloudCount; ++j) {
9166  ccGLMatrix transBToZero;
9167  transBToZero.toIdentity();
9168  transBToZero.setTranslation(-clouds[j]->getOwnBB().getCenter());
9169 
9170  ccGLMatrix transFromZeroToA;
9171  transFromZeroToA.toIdentity();
9172  transFromZeroToA.setTranslation(A->getOwnBB().getCenter());
9173 
9174 #ifndef TEST_GENERATION
9175  double minRMS = -1.0;
9176  int bestMatrixIndex = -1;
9177  ccPointCloud* bestB = nullptr;
9178 #endif
9179  for (size_t k = 0; k < matrices.size(); ++k) {
9180  ccPointCloud* B = clouds[j]->cloneThis();
9181  if (!B) {
9182  CVLog::Error(tr("Not enough memory!"));
9183  return;
9184  }
9185 
9186  ccGLMatrix BtoA =
9187  transFromZeroToA * matrices[k] * transBToZero;
9188  B->applyRigidTransformation(BtoA);
9189 
9190 #ifndef TEST_GENERATION
9191  double finalRMS = 0.0;
9192  unsigned finalPointCount = 0;
9195  registerTrans;
9197  {
9200  params.minRMSDecrease = 1.0e-6;
9201  }
9202 
9204  A, 0, B, params, registerTrans, finalRMS,
9205  finalPointCount);
9206 
9207  if (result >=
9209  delete B;
9210  if (bestB) delete bestB;
9211  CVLog::Error(
9212  tr("An error occurred while performing ICP!"));
9213  return;
9214  }
9215 
9216  if (minRMS < 0 || finalRMS < minRMS) {
9217  minRMS = finalRMS;
9218  bestMatrixIndex = static_cast<int>(k);
9219  std::swap(bestB, B);
9220  }
9221 
9222  if (B) {
9223  delete B;
9224  B = nullptr;
9225  }
9226 #else
9227  addToDB(B);
9228 
9229  // Test sphere
9230  CCVector3 Y(0, 1, 0);
9231  matrices[k].apply(Y);
9232  testSphere->addPoint(Y);
9233 #endif
9234 
9235  if (!nProgress.oneStep()) {
9236  // process cancelled by user
9237  return;
9238  }
9239  }
9240 
9241 #ifndef TEST_GENERATION
9242  if (bestMatrixIndex >= 0) {
9243  assert(bestB);
9244  ccHObject* group =
9245  new ccHObject(tr("Best case #%1 / #%2 - RMS = %3")
9246  .arg(i + 1)
9247  .arg(j + 1)
9248  .arg(minRMS));
9249  group->addChild(bestB);
9250  // group->setDisplay_recursive(A->getDisplay());
9251  addToDB(group);
9252  CVLog::Print(
9253  tr("[doActionComputeBestICPRmsMatrix] Comparison "
9254  "#%1 / #%2: min RMS = %3 (phi = %4 / theta = %5 "
9255  "deg.)")
9256  .arg(i + 1)
9257  .arg(j + 1)
9258  .arg(minRMS)
9259  .arg(matrixAngles[bestMatrixIndex].first)
9260  .arg(matrixAngles[bestMatrixIndex].second));
9261  } else {
9262  assert(!bestB);
9263  CVLog::Warning(tr("[doActionComputeBestICPRmsMatrix] "
9264  "Comparison #%1 / #%2: INVALID")
9265  .arg(i + 1)
9266  .arg(j + 1));
9267  }
9268 
9269  rmsMatrix[i * cloudCount + j] = minRMS;
9270 #else
9271  addToDB(testSphere);
9272  i = cloudCount;
9273  break;
9274 #endif
9275  }
9276  }
9277  }
9278 
9279  // export result as a CSV file
9280 #ifdef TEST_GENERATION
9281  if (false)
9282 #endif
9283  {
9284  // persistent settings
9285  QString currentPath = ecvSettingManager::getValue(
9288  .toString();
9289 
9290  QString outputFilename = QFileDialog::getSaveFileName(
9291  this, tr("Select output file"), currentPath, "*.csv", nullptr,
9293 
9294  if (outputFilename.isEmpty()) return;
9295 
9296  QFile fp(outputFilename);
9297  if (fp.open(QFile::Text | QFile::WriteOnly)) {
9298  QTextStream stream(&fp);
9299  // header
9300  {
9301  stream << "RMS";
9302  for (ccPointCloud* cloud : clouds) {
9303  stream << ";";
9304  stream << cloud->getName();
9305  }
9306  stream << QtCompat::endl;
9307  }
9308 
9309  // rows
9310  for (size_t j = 0; j < cloudCount; ++j) {
9311  stream << clouds[j]->getName();
9312  stream << ";";
9313  for (size_t i = 0; i < cloudCount; ++i) {
9314  stream << rmsMatrix[j * cloudCount + i];
9315  stream << ";";
9316  }
9317  stream << QtCompat::endl;
9318  }
9319 
9320  CVLog::Print(tr("[doActionComputeBestICPRmsMatrix] Job done"));
9321  } else {
9322  CVLog::Error(tr("Failed to save output file?!"));
9323  }
9324  }
9325 }
9326 
9327 static int s_innerRectDim = 2;
9328 void MainWindow::doActionFindBiggestInnerRectangle() {
9329  if (!haveSelection()) return;
9330 
9331  ccHObject* entity = haveOneSelection() ? m_selectedEntities[0] : nullptr;
9332  if (!entity || !entity->isKindOf(CV_TYPES::POINT_CLOUD)) {
9333  ecvConsole::Error(tr("Select one point cloud!"));
9334  return;
9335  }
9336 
9337  bool ok;
9338  int dim = QInputDialog::getInt(this, tr("Dimension"),
9339  tr("Orthogonal dim (X=0 / Y=1 / Z=2)"),
9340  s_innerRectDim, 0, 2, 1, &ok);
9341  if (!ok) return;
9342  s_innerRectDim = dim;
9343 
9344  ccGenericPointCloud* cloud = static_cast<ccGenericPointCloud*>(entity);
9345  ccBox* box = ccInnerRect2DFinder().process(cloud,
9346  static_cast<unsigned char>(dim));
9347 
9348  if (box) {
9349  cloud->addChild(box);
9350  box->setVisible(true);
9351  addToDB(box);
9352  }
9353 
9354  updateUI();
9355 }
9356 
9357 // Edit scalar field
9358 void MainWindow::spawnHistogramDialog(const std::vector<unsigned>& histoValues,
9359  double minVal,
9360  double maxVal,
9361  QString title,
9362  QString xAxisLabel) {
9363  ccHistogramWindowDlg* hDlg = new ccHistogramWindowDlg(this);
9364  hDlg->setAttribute(Qt::WA_DeleteOnClose, true);
9365  hDlg->setWindowTitle("Histogram");
9366 
9367  ccHistogramWindow* histogram = hDlg->window();
9368  {
9369  histogram->setTitle(title);
9370  histogram->fromBinArray(histoValues, minVal, maxVal);
9371  histogram->setAxisLabels(xAxisLabel, "Count");
9372  histogram->refresh();
9373  }
9374 
9375  hDlg->show();
9376 }
9377 
9378 void MainWindow::showSelectedEntitiesHistogram() {
9379  for (ccHObject* entity : getSelectedEntities()) {
9380  // for "real" point clouds only
9382  if (cloud) {
9383  // we display the histogram of the current scalar field
9384  ccScalarField* sf = static_cast<ccScalarField*>(
9386  if (sf) {
9387  ccHistogramWindowDlg* hDlg = new ccHistogramWindowDlg(this);
9388  hDlg->setAttribute(Qt::WA_DeleteOnClose, true);
9389  hDlg->setWindowTitle(
9390  tr("Histogram [%1]").arg(cloud->getName()));
9391 
9392  ccHistogramWindow* histogram = hDlg->window();
9393  {
9394  unsigned numberOfPoints = cloud->size();
9395  unsigned numberOfClasses = static_cast<unsigned>(
9396  sqrt(static_cast<double>(numberOfPoints)));
9397  // we take the 'nearest' multiple of 4
9398  numberOfClasses &= (~3);
9399  numberOfClasses = std::max<unsigned>(4, numberOfClasses);
9400  numberOfClasses = std::min<unsigned>(256, numberOfClasses);
9401 
9402  histogram->setTitle(tr("%1 (%2 values) ")
9403  .arg(sf->getName())
9404  .arg(numberOfPoints));
9405  bool showNaNValuesInGrey = sf->areNaNValuesShownInGrey();
9406  histogram->fromSF(sf, numberOfClasses, true,
9407  showNaNValuesInGrey);
9408  histogram->setAxisLabels(sf->getName(), tr("Count"));
9409  histogram->refresh();
9410  }
9411  hDlg->show();
9412  }
9413  }
9414  }
9415 }
9416 
9417 void MainWindow::doActionComputeStatParams() {
9418  ccEntityAction::computeStatParams(m_selectedEntities, this);
9419 }
9420 
9421 void MainWindow::doActionSFGradient() {
9423  ccLibAlgorithms::CCLIB_ALGO_SF_GRADIENT, m_selectedEntities,
9424  this))
9425  return;
9426  refreshSelected();
9427  updateUI();
9428 }
9429 
9430 void MainWindow::doActionOpenColorScalesManager() {
9432  this, ccColorScale::Shared(0), this);
9433 
9434  if (cseDlg.exec()) {
9435  // save current scale manager state to persistent settings
9437  }
9438 
9439  updateUI();
9440 }
9441 
9442 void MainWindow::doActionRGBGaussianFilter() {
9443  ccPointCloud::RgbFilterOptions filterParams;
9444  filterParams.filterType = ccPointCloud::RGB_FILTER_TYPES::GAUSSIAN;
9445  if (!ccEntityAction::rgbGaussianFilter(m_selectedEntities, filterParams,
9446  this))
9447  return;
9448 
9449  refreshSelected();
9450  updateUI();
9451 }
9452 
9453 void MainWindow::doActionRGBBilateralFilter() {
9454  ccPointCloud::RgbFilterOptions filterParams;
9455  filterParams.filterType = ccPointCloud::RGB_FILTER_TYPES::BILATERAL;
9456  if (!ccEntityAction::rgbGaussianFilter(m_selectedEntities, filterParams,
9457  this))
9458  return;
9459 
9460  refreshSelected();
9461  updateUI();
9462 }
9463 
9464 void MainWindow::doActionRGBMeanFilter() {
9465  ccPointCloud::RgbFilterOptions filterParams;
9466  filterParams.filterType = ccPointCloud::RGB_FILTER_TYPES::MEAN;
9467  if (!ccEntityAction::rgbGaussianFilter(m_selectedEntities, filterParams,
9468  this))
9469  return;
9470 
9471  refreshSelected();
9472  updateUI();
9473 }
9474 
9475 void MainWindow::doActionRGBMedianFilter() {
9476  ccPointCloud::RgbFilterOptions filterParams;
9477  filterParams.filterType = ccPointCloud::RGB_FILTER_TYPES::MEDIAN;
9478  if (!ccEntityAction::rgbGaussianFilter(m_selectedEntities, filterParams,
9479  this))
9480  return;
9481 
9482  refreshSelected();
9483  updateUI();
9484 }
9485 
9486 void MainWindow::doActionSFGaussianFilter() {
9487  ccPointCloud::RgbFilterOptions filterParams;
9488  filterParams.filterType = ccPointCloud::RGB_FILTER_TYPES::GAUSSIAN;
9489  if (!ccEntityAction::sfGaussianFilter(m_selectedEntities, filterParams,
9490  this))
9491  return;
9492 
9493  refreshSelected();
9494  updateUI();
9495 }
9496 
9497 void MainWindow::doActionSFBilateralFilter() {
9498  ccPointCloud::RgbFilterOptions filterParams;
9499  filterParams.filterType = ccPointCloud::RGB_FILTER_TYPES::BILATERAL;
9500  if (!ccEntityAction::sfGaussianFilter(m_selectedEntities, filterParams,
9501  this))
9502  return;
9503 
9504  refreshSelected();
9505  updateUI();
9506 }
9507 
9508 void MainWindow::doActionFilterByLabel() {
9509  if (!haveOneSelection()) {
9510  ecvConsole::Warning(tr("Select one and only one entity!"));
9511  return;
9512  }
9513 
9514  ccHObject* entity = m_selectedEntities[0];
9516  if (!cloud || !cloud->isKindOf(CV_TYPES::POINT_CLOUD)) {
9517  ecvConsole::Warning(tr("only cloud is supported!"));
9518  return;
9519  }
9520 
9521  if (!m_filterLabelTool) {
9522  m_filterLabelTool = new ecvFilterByLabelDlg(this);
9523  connect(m_filterLabelTool, &ccOverlayDialog::processFinished, this,
9524  [=]() {
9526  freezeUI(false);
9527  updateUI();
9528  });
9529  registerOverlayDialog(m_filterLabelTool, Qt::TopRightCorner);
9530  }
9531 
9532  if (!m_filterLabelTool->linkWith(ecvDisplayTools::GetCurrentScreen())) {
9534  "[MainWindow::doSemanticSegmentation] Initialization failed!");
9535  return;
9536  }
9537 
9538  if (!m_filterLabelTool->setInputEntity(entity)) {
9539  return;
9540  }
9541 
9542  if (m_filterLabelTool->start()) {
9543  freezeUI(true);
9545  } else {
9546  freezeUI(false);
9547  updateUI();
9548  ecvConsole::Error(tr("Unexpected error!")); // indeed...
9549  }
9550 }
9551 
9552 void MainWindow::doActionFilterByValue() {
9553  typedef std::pair<ccHObject*, ccPointCloud*> EntityAndVerticesType;
9554  std::vector<EntityAndVerticesType> toFilter;
9555 
9556  for (ccHObject* entity : getSelectedEntities()) {
9557  ccGenericPointCloud* cloud =
9559  if (cloud && cloud->isA(CV_TYPES::POINT_CLOUD)) {
9560  ccPointCloud* pc = static_cast<ccPointCloud*>(cloud);
9562  if (sf) {
9563  toFilter.emplace_back(entity, pc);
9564  } else {
9566  tr("Entity [%1] has no active scalar field !")
9567  .arg(entity->getName()));
9568  }
9569  }
9570  }
9571 
9572  if (toFilter.empty()) return;
9573 
9574  double minVald = 0.0;
9575  double maxVald = 1.0;
9576 
9577  // compute min and max "displayed" scalar values of currently selected
9578  // entities (the ones with an active scalar field only!)
9579  {
9580  for (size_t i = 0; i < toFilter.size(); ++i) {
9581  ccScalarField* sf =
9582  toFilter[i].second->getCurrentDisplayedScalarField();
9583  assert(sf);
9584 
9585  if (i == 0) {
9586  minVald = static_cast<double>(sf->displayRange().start());
9587  maxVald = static_cast<double>(sf->displayRange().stop());
9588  } else {
9589  if (minVald > static_cast<double>(sf->displayRange().start()))
9590  minVald = static_cast<double>(sf->displayRange().start());
9591  if (maxVald < static_cast<double>(sf->displayRange().stop()))
9592  maxVald = static_cast<double>(sf->displayRange().stop());
9593  }
9594  }
9595  }
9596 
9597  ccFilterByValueDlg dlg(minVald, maxVald, -1.0e9, 1.0e9, this);
9598  if (!dlg.exec()) return;
9599 
9600  ccFilterByValueDlg::Mode mode = dlg.mode();
9601  assert(mode != ccFilterByValueDlg::CANCEL);
9602 
9603  ScalarType minVal = static_cast<ScalarType>(dlg.minDoubleSpinBox->value());
9604  ScalarType maxVal = static_cast<ScalarType>(dlg.maxDoubleSpinBox->value());
9605 
9606  ccHObject::Container results;
9607  {
9608  for (auto& item : toFilter) {
9609  ccHObject* ent = item.first;
9610  ccPointCloud* pc = item.second;
9611  // we set as output (OUT) the currently displayed scalar field
9612  int outSfIdx = pc->getCurrentDisplayedScalarFieldIndex();
9613  assert(outSfIdx >= 0);
9614  pc->setCurrentOutScalarField(outSfIdx);
9615  // pc->setCurrentScalarField(outSfIdx);
9616 
9617  ccHObject* resultInside = nullptr;
9618  ccHObject* resultOutside = nullptr;
9619  if (ent->isKindOf(CV_TYPES::MESH)) {
9620  pc->hidePointsByScalarValue(minVal, maxVal);
9621  if (ent->isA(CV_TYPES::MESH)/*|| ent->isKindOf(CV_TYPES::PRIMITIVE)*/) //TODO
9622  resultInside = ccHObjectCaster::ToMesh(ent)
9623  ->createNewMeshFromSelection(false);
9624  else if (ent->isA(CV_TYPES::SUB_MESH))
9625  resultInside =
9628 
9629  if (mode == ccFilterByValueDlg::SPLIT) {
9630  pc->invertVisibilityArray();
9631  if (ent->isA(CV_TYPES::MESH)/*|| ent->isKindOf(CV_TYPES::PRIMITIVE)*/) //TODO
9632  resultOutside =
9634  ->createNewMeshFromSelection(false);
9635  else if (ent->isA(CV_TYPES::SUB_MESH))
9636  resultOutside =
9639  }
9640 
9642  } else if (ent->isKindOf(CV_TYPES::POINT_CLOUD)) {
9643  // pc->hidePointsByScalarValue(minVal,maxVal);
9644  // result =
9645  // ccHObjectCaster::ToGenericPointCloud(ent)->hidePointsByScalarValue(false);
9646  // pc->unallocateVisibilityArray();
9647 
9648  // shortcut, as we know here that the point cloud is a
9649  // "ccPointCloud"
9650  resultInside =
9651  pc->filterPointsByScalarValue(minVal, maxVal, false);
9652 
9653  if (mode == ccFilterByValueDlg::SPLIT) {
9654  resultOutside =
9655  pc->filterPointsByScalarValue(minVal, maxVal, true);
9656  }
9657  }
9658 
9659  if (resultInside) {
9660  ent->setEnabled(false);
9661  addToDB(resultInside);
9662 
9663  results.push_back(resultInside);
9664  }
9665  if (resultOutside) {
9666  ent->setEnabled(false);
9667  resultOutside->setName(resultOutside->getName() + ".outside");
9668  addToDB(resultOutside);
9669 
9670  results.push_back(resultOutside);
9671  }
9672  }
9673  }
9674 
9675  if (!results.empty()) {
9677  tr("Previously selected entities (sources) have been hidden!"));
9678  if (m_ccRoot) {
9679  m_ccRoot->selectEntities(results);
9680  }
9681  }
9682 }
9683 
9684 void MainWindow::doActionScalarFieldFromColor() {
9685  if (!ccEntityAction::sfFromColor(m_selectedEntities, this)) return;
9686 
9687  refreshSelected();
9688  updateUI();
9689 }
9690 
9691 void MainWindow::doActionSFConvertToRGB() {
9692  if (!ccEntityAction::sfConvertToRGB(m_selectedEntities, this)) return;
9693 
9694  refreshSelected();
9695  updateUI();
9696 }
9697 
9698 void MainWindow::doActionSFConvertToRandomRGB() {
9699  if (!ccEntityAction::sfConvertToRandomRGB(m_selectedEntities, this)) return;
9700 
9701  refreshSelected();
9702  updateUI();
9703 }
9704 
9705 void MainWindow::doActionToggleActiveSFColorScale() {
9706  doApplyActiveSFAction(0);
9707 }
9708 
9709 void MainWindow::doActionShowActiveSFPrevious() { doApplyActiveSFAction(1); }
9710 
9711 void MainWindow::doActionShowActiveSFNext() { doApplyActiveSFAction(2); }
9712 
9713 void MainWindow::doApplyActiveSFAction(int action) {
9714  if (!haveOneSelection()) {
9715  if (haveSelection()) {
9716  ecvConsole::Error(tr("Select only one cloud or one mesh!"));
9717  }
9718  return;
9719  }
9720  ccHObject* ent = m_selectedEntities[0];
9721 
9722  bool lockedVertices;
9723  ccPointCloud* cloud = ccHObjectCaster::ToPointCloud(ent, &lockedVertices);
9724 
9725  // for "real" point clouds only
9726  if (!cloud) return;
9727  if (lockedVertices && !ent->isAncestorOf(cloud)) {
9728  // see ccPropertiesTreeDelegate::fillWithMesh
9730  return;
9731  }
9732 
9733  assert(cloud);
9734  int sfIdx = cloud->getCurrentDisplayedScalarFieldIndex();
9735  switch (action) {
9736  case 0: // Toggle SF color scale
9737  if (sfIdx >= 0) {
9738  cloud->showSFColorsScale(!cloud->sfColorScaleShown());
9739  } else {
9740  ecvConsole::Warning(tr("No active scalar field on entity '%1'")
9741  .arg(ent->getName()));
9742  }
9743  break;
9744  case 1: // Activate previous SF
9745  if (sfIdx >= 0) {
9746  cloud->setCurrentDisplayedScalarField(sfIdx - 1);
9747  }
9748  break;
9749  case 2: // Activate next SF
9750  if (sfIdx + 1 <
9751  static_cast<int>(cloud->getNumberOfScalarFields())) {
9752  cloud->setCurrentDisplayedScalarField(sfIdx + 1);
9753  }
9754  break;
9755  }
9756 
9757  refreshSelected();
9758  updateUI();
9759 }
9760 
9761 void MainWindow::doActionRenameSF() {
9762  if (!ccEntityAction::sfRename(m_selectedEntities, this)) return;
9763 
9764  updateUI();
9765 }
9766 
9767 static double s_constantSFValue = 0.0;
9768 void MainWindow::doActionAddConstantSF() {
9769  if (!haveOneSelection()) {
9770  if (haveSelection())
9771  ecvConsole::Error(tr("Select only one cloud or one mesh!"));
9772  return;
9773  }
9774 
9775  ccHObject* ent = m_selectedEntities[0];
9776 
9777  bool lockedVertices;
9778  ccPointCloud* cloud = ccHObjectCaster::ToPointCloud(ent, &lockedVertices);
9779 
9780  // for "real" point clouds only
9781  if (!cloud) return;
9782  if (lockedVertices && !ent->isAncestorOf(cloud)) {
9784  return;
9785  }
9786 
9787  QString defaultName = tr("Constant");
9788  unsigned trys = 1;
9789  while (cloud->getScalarFieldIndexByName(qPrintable(defaultName)) >= 0 ||
9790  trys > 99) {
9791  defaultName = tr("Constant #%1").arg(++trys);
9792  }
9793 
9794  // ask for a name
9795  bool ok;
9796  QString sfName = QInputDialog::getText(this, tr("New SF name"),
9797  tr("SF name (must be unique)"),
9798  QLineEdit::Normal, defaultName, &ok);
9799  if (!ok) return;
9800  if (sfName.isNull()) {
9801  CVLog::Error(tr("Invalid name"));
9802  return;
9803  }
9804  if (cloud->getScalarFieldIndexByName(qPrintable(sfName)) >= 0) {
9805  CVLog::Error(tr("Name already exists!"));
9806  return;
9807  }
9808 
9809  ScalarType sfValue = static_cast<ScalarType>(
9810  QInputDialog::getDouble(this, tr("Add constant value"), tr("value"),
9811  s_constantSFValue, -1.0e9, 1.0e9, 8, &ok));
9812  if (!ok) return;
9813 
9814  int sfIdx = cloud->getScalarFieldIndexByName(qPrintable(sfName));
9815  if (sfIdx < 0) sfIdx = cloud->addScalarField(qPrintable(sfName));
9816  if (sfIdx < 0) {
9817  CVLog::Error(tr("An error occurred! (see console)"));
9818  return;
9819  }
9820 
9821  cloudViewer::ScalarField* sf = cloud->getScalarField(sfIdx);
9822  assert(sf);
9823  if (sf) {
9825  sf->fill(sfValue);
9826  sf->computeMinAndMax();
9827  cloud->setCurrentDisplayedScalarField(sfIdx);
9828  cloud->showSF(true);
9829  updateUI();
9830  }
9831 
9832  CVLog::Print(tr("New scalar field added to %1 (constant value: %2)")
9833  .arg(cloud->getName())
9834  .arg(sfValue));
9835 }
9836 
9837 void MainWindow::doActionImportSFFromFile() {
9838  if (!haveOneSelection()) {
9839  if (haveSelection())
9840  ecvConsole::Error(tr("Select only one cloud or one mesh!"));
9841  return;
9842  }
9843 
9844  bool lockedVertices;
9845  ccHObject* ent = m_selectedEntities[0];
9846  ccPointCloud* cloud = ccHObjectCaster::ToPointCloud(ent, &lockedVertices);
9847 
9848  // for "real" point clouds only
9849  if (!cloud) return;
9850  if (lockedVertices && !ent->isAncestorOf(cloud)) {
9852  return;
9853  }
9854 
9855  QString currentPath =
9858  .toString();
9859  QString filters = "*.labels";
9860  QString selectedFilter = filters;
9861  QString selectedFilename =
9862  QFileDialog::getOpenFileName(this, tr("import sf value from file"),
9863  currentPath, filters, &selectedFilter);
9864 
9865  if (selectedFilename.isEmpty()) {
9866  // process cancelled by the user
9867  return;
9868  }
9869 
9870  // we update current file path
9871  currentPath = QFileInfo(selectedFilename).absolutePath();
9873  currentPath);
9874  std::string filename = CVTools::FromQString(selectedFilename);
9875 
9876  std::string scalarName =
9877  CVTools::FromQString(QFileInfo(selectedFilename).baseName());
9878 
9879  std::vector<size_t> scalars;
9880  if (!CVTools::QMappingReader(filename, scalars)) {
9881  return;
9882  }
9883 
9884  if (scalars.size() != cloud->size()) {
9885  CVLog::Warning("scalar files are probably corrupted and drop it!");
9886  return;
9887  }
9888 
9889  std::vector<std::vector<ScalarType>> scalarsVector;
9890  std::vector<std::vector<size_t>> tempScalarsvector;
9891  tempScalarsvector.push_back(scalars);
9892  ccEntityAction::ConvertToScalarType<size_t>(tempScalarsvector,
9893  scalarsVector);
9894  if (!ccEntityAction::importToSF(m_selectedEntities, scalarsVector,
9895  scalarName.c_str())) {
9896  CVLog::Error(
9897  "[MainWindow::doActionImportSFFromFile] import sf failed!");
9898  } else {
9899  CVLog::Print(tr("[MainWindow::doActionImportSFFromFile] "
9900  "Import sf from file %1 successfully!")
9901  .arg(filename.c_str()));
9902  updateUI();
9903  }
9904 }
9905 
9906 void MainWindow::doActionAddIdField() {
9907  if (!ccEntityAction::sfAddIdField(m_selectedEntities)) return;
9908 
9909  refreshSelected();
9910  updateUI();
9911 }
9912 
9913 void MainWindow::doActionExportCoordToSF() {
9914  if (!ccEntityAction::exportCoordToSF(m_selectedEntities, this)) {
9915  return;
9916  }
9917 
9918  refreshSelected();
9919  updateUI();
9920 }
9921 
9922 void MainWindow::doActionSetSFAsCoord() {
9923  if (!ccEntityAction::sfSetAsCoord(m_selectedEntities, this)) return;
9924 
9925  refreshSelected();
9926  updateUI();
9927 }
9928 
9929 void MainWindow::doActionInterpolateScalarFields() {
9930  if (!ccEntityAction::interpolateSFs(m_selectedEntities, this)) return;
9931 
9932  refreshSelected();
9933  updateUI();
9934 }
9935 
9936 void MainWindow::doActionScalarFieldArithmetic() {
9937  if (!ccEntityAction::sfArithmetic(m_selectedEntities, this)) return;
9938 
9939  refreshSelected();
9940  updateUI();
9941 }
9942 
9943 void MainWindow::doRemoveDuplicatePoints() {
9944  if (!haveSelection()) return;
9945 
9946  bool first = true;
9947 
9948  // persistent setting(s)
9949  double minDistanceBetweenPoints =
9952  1.0e-12)
9953  .toDouble();
9954 
9955  bool ok;
9956  minDistanceBetweenPoints = QInputDialog::getDouble(
9957  this, tr("Remove duplicate points"),
9958  tr("Min distance between points:"), minDistanceBetweenPoints, 0,
9959  1.0e8, 12, &ok);
9960  if (!ok) return;
9961 
9962  // save parameter
9965  minDistanceBetweenPoints);
9966 
9967  static const char DEFAULT_DUPLICATE_TEMP_SF_NAME[] = "DuplicateFlags";
9968 
9969  ecvProgressDialog pDlg(true, this);
9970  pDlg.setAutoClose(false);
9971 
9972  ccHObject::Container selectedEntities =
9973  getSelectedEntities(); // we have to use a local copy:
9974  // 'unselectAllEntities' and 'selectEntity'
9975  // will change the set of currently selected
9976  // entities!
9977 
9978  for (ccHObject* entity : selectedEntities) {
9980  if (cloud) {
9981  // create temporary SF for 'duplicate flags'
9982  int sfIdx = cloud->getScalarFieldIndexByName(
9983  DEFAULT_DUPLICATE_TEMP_SF_NAME);
9984  if (sfIdx < 0)
9985  sfIdx = cloud->addScalarField(DEFAULT_DUPLICATE_TEMP_SF_NAME);
9986  if (sfIdx >= 0)
9987  cloud->setCurrentScalarField(sfIdx);
9988  else {
9990  tr("Couldn't create temporary scalar field! Not enough "
9991  "memory?"));
9992  break;
9993  }
9994 
9995  ccOctree::Shared octree = cloud->getOctree();
9996 
9999  cloud, minDistanceBetweenPoints, &pDlg,
10000  octree.data());
10001 
10003  // count the number of duplicate points!
10004  cloudViewer::ScalarField* flagSF = cloud->getScalarField(sfIdx);
10005  unsigned duplicateCount = 0;
10006  assert(flagSF);
10007  if (flagSF) {
10008  for (unsigned j = 0; j < flagSF->currentSize(); ++j) {
10009  if (flagSF->getValue(j) != 0) {
10010  ++duplicateCount;
10011  }
10012  }
10013  }
10014 
10015  if (duplicateCount == 0) {
10016  ecvConsole::Print(tr("Cloud '%1' has no duplicate points")
10017  .arg(cloud->getName()));
10018  } else {
10020  tr("Cloud '%1' has %2 duplicate point(s)")
10021  .arg(cloud->getName())
10022  .arg(duplicateCount));
10023 
10024  ccPointCloud* filteredCloud =
10025  cloud->filterPointsByScalarValue(0, 0);
10026  if (filteredCloud) {
10027  int sfIdx2 = filteredCloud->getScalarFieldIndexByName(
10028  DEFAULT_DUPLICATE_TEMP_SF_NAME);
10029  assert(sfIdx2 >= 0);
10030  filteredCloud->deleteScalarField(sfIdx2);
10031  filteredCloud->setName(
10032  tr("%1.clean").arg(cloud->getName()));
10033  addToDB(filteredCloud);
10034  if (first) {
10035  m_ccRoot->unselectAllEntities();
10036  first = false;
10037  }
10038  cloud->setEnabled(false);
10039  m_ccRoot->selectEntity(filteredCloud, true);
10040  }
10041  }
10042  } else {
10044  tr("An error occurred! (Not enough memory?)"));
10045  }
10046 
10047  cloud->deleteScalarField(sfIdx);
10048  }
10049  }
10050 
10051  if (!first)
10053  tr("Previously selected entities (sources) have been hidden!"));
10054 }
10055 
10056 void MainWindow::doActionSubsample() {
10057  // find candidates
10058  std::vector<ccPointCloud*> clouds;
10059  unsigned maxPointCount = 0;
10060  double maxCloudRadius = 0;
10061  ScalarType sfMin = NAN_VALUE;
10062  ScalarType sfMax = NAN_VALUE;
10063  {
10064  for (ccHObject* entity : getSelectedEntities()) {
10065  if (entity->isA(CV_TYPES::POINT_CLOUD)) {
10066  ccPointCloud* cloud = static_cast<ccPointCloud*>(entity);
10067  clouds.push_back(cloud);
10068 
10069  maxPointCount =
10070  std::max<unsigned>(maxPointCount, cloud->size());
10071  maxCloudRadius = std::max<double>(
10072  maxCloudRadius, cloud->getOwnBB().getDiagNorm());
10073 
10074  // we also look for the min and max sf values
10076  if (sf) {
10077  if (!ccScalarField::ValidValue(sfMin) ||
10078  sfMin > sf->getMin())
10079  sfMin = sf->getMin();
10080  if (!ccScalarField::ValidValue(sfMax) ||
10081  sfMax < sf->getMax())
10082  sfMax = sf->getMax();
10083  }
10084  }
10085  }
10086  }
10087 
10088  if (clouds.empty()) {
10089  ecvConsole::Error(tr("Select at least one point cloud!"));
10090  return;
10091  }
10092 
10093  // Display dialog
10094  ccSubsamplingDlg sDlg(maxPointCount, maxCloudRadius, this);
10095  bool hasValidSF = ccScalarField::ValidValue(sfMin) &&
10097  if (hasValidSF) sDlg.enableSFModulation(sfMin, sfMax);
10098  if (!sDlg.exec()) return;
10099 
10100  // process clouds
10101  ccHObject::Container resultingClouds;
10102  {
10103  ecvProgressDialog pDlg(false, this);
10104  pDlg.setAutoClose(false);
10105 
10106  pDlg.setMethodTitle(tr("Subsampling"));
10107 
10108  bool errors = false;
10109 
10110  QElapsedTimer eTimer;
10111  eTimer.start();
10112 
10113  for (size_t i = 0; i < clouds.size(); ++i) {
10114  ccPointCloud* cloud = clouds[i];
10115  cloudViewer::ReferenceCloud* sampledCloud =
10116  sDlg.getSampledCloud(cloud, &pDlg);
10117  if (!sampledCloud) {
10119  tr("[Subsampling] Failed to subsample cloud '%1'!")
10120  .arg(cloud->getName()));
10121  errors = true;
10122  continue;
10123  }
10124 
10125  int warnings = 0;
10126  ccPointCloud* newPointCloud =
10127  cloud->partialClone(sampledCloud, &warnings);
10128 
10129  delete sampledCloud;
10130  sampledCloud = 0;
10131 
10132  if (newPointCloud) {
10133  newPointCloud->setName(cloud->getName() +
10134  QString(".subsampled"));
10135  newPointCloud->setGlobalShift(cloud->getGlobalShift());
10136  newPointCloud->setGlobalScale(cloud->getGlobalScale());
10137  if (cloud->getParent())
10138  cloud->getParent()->addChild(newPointCloud);
10139  cloud->setEnabled(false);
10140  addToDB(newPointCloud);
10141 
10142  resultingClouds.push_back(newPointCloud);
10143 
10144  if (warnings) {
10146  tr("[Subsampling] Not enough memory: colors, "
10147  "normals or scalar fields may be missing!"));
10148  errors = true;
10149  }
10150  } else {
10151  CVLog::Error(tr("Not enough memory!"));
10152  break;
10153  }
10154  }
10155 
10156  CVLog::Print(tr("[Subsampling] Timing: %1 s.")
10157  .arg(eTimer.elapsed() / 1000.0, 7));
10158 
10159  if (errors) {
10160  CVLog::Error(tr("Errors occurred (see console)"));
10161  }
10162  }
10163 
10164  if (m_ccRoot) m_ccRoot->selectEntities(resultingClouds);
10165 
10166  updateUI();
10167 }
10168 
10169 void MainWindow::doActionEditGlobalShiftAndScale() {
10170  // get the global shift/scale info and bounding box of all selected clouds
10171  std::vector<std::pair<ccShiftedObject*, ccHObject*>> shiftedEntities;
10172  CCVector3d Pl(0, 0, 0);
10173  double Dl = 1.0;
10174  CCVector3d Pg(0, 0, 0);
10175  double Dg = 1.0;
10176  // shift and scale (if unique)
10177  CCVector3d shift(0, 0, 0);
10178  double scale = 1.0;
10179  {
10180  bool uniqueShift = true;
10181  bool uniqueScale = true;
10182  ccBBox localBB;
10183  // sadly we don't have a double-typed bounding box class yet ;)
10184  CCVector3d globalBBmin(0, 0, 0), globalBBmax(0, 0, 0);
10185 
10186  for (ccHObject* entity : getSelectedEntities()) {
10187  bool lockedVertices;
10188  ccShiftedObject* shifted =
10189  ccHObjectCaster::ToShifted(entity, &lockedVertices);
10190  if (!shifted) {
10191  continue;
10192  }
10193  // for (unlocked) entities only
10194  if (lockedVertices) {
10195  // get the vertices
10196  assert(entity->isKindOf(CV_TYPES::MESH));
10197  ccGenericPointCloud* vertices =
10198  static_cast<ccGenericMesh*>(entity)
10199  ->getAssociatedCloud();
10200  if (!vertices || !entity->isAncestorOf(vertices)) {
10202  haveOneSelection());
10203  continue;
10204  }
10205  entity = vertices;
10206  }
10207 
10208  CCVector3 Al = entity->getOwnBB().minCorner();
10209  CCVector3 Bl = entity->getOwnBB().maxCorner();
10210  CCVector3d Ag = shifted->toGlobal3d<PointCoordinateType>(Al);
10211  CCVector3d Bg = shifted->toGlobal3d<PointCoordinateType>(Bl);
10212 
10213  // update local BB
10214  localBB.add(Al);
10215  localBB.add(Bl);
10216 
10217  // update global BB
10218  if (shiftedEntities.empty()) {
10219  globalBBmin = Ag;
10220  globalBBmax = Bg;
10221  shift = shifted->getGlobalShift();
10222  uniqueScale = shifted->getGlobalScale();
10223  } else {
10224  globalBBmin = CCVector3d(std::min(globalBBmin.x, Ag.x),
10225  std::min(globalBBmin.y, Ag.y),
10226  std::min(globalBBmin.z, Ag.z));
10227  globalBBmax = CCVector3d(std::max(globalBBmax.x, Bg.x),
10228  std::max(globalBBmax.y, Bg.y),
10229  std::max(globalBBmax.z, Bg.z));
10230 
10231  if (uniqueShift) {
10232  uniqueShift = cloudViewer::LessThanEpsilon(
10233  (shifted->getGlobalShift() - shift).norm());
10234  }
10235  if (uniqueScale) {
10236  uniqueScale = cloudViewer::LessThanEpsilon(
10237  std::abs(shifted->getGlobalScale() - scale));
10238  }
10239  }
10240 
10241  shiftedEntities.emplace_back(shifted, entity);
10242  }
10243 
10244  Pg = globalBBmin;
10245  Dg = (globalBBmax - globalBBmin).norm();
10246 
10247  Pl = CCVector3d::fromArray(localBB.minCorner().u);
10248  Dl = (localBB.maxCorner() - localBB.minCorner()).normd();
10249 
10250  if (!uniqueShift) shift = Pl - Pg;
10251  if (!uniqueScale) scale = Dg / Dl;
10252  }
10253 
10254  if (shiftedEntities.empty()) {
10255  return;
10256  }
10257 
10258  ecvShiftAndScaleCloudDlg sasDlg(Pl, Dl, Pg, Dg, this);
10259  sasDlg.showApplyAllButton(shiftedEntities.size() > 1);
10260  sasDlg.showApplyButton(shiftedEntities.size() == 1);
10261  sasDlg.showNoButton(false);
10262  sasDlg.setShiftFieldsPrecision(6);
10263  // add "original" entry
10264  int index = sasDlg.addShiftInfo(
10265  ecvGlobalShiftManager::ShiftInfo(tr("Original"), shift, scale));
10266  sasDlg.setCurrentProfile(index);
10267  // add "last" entry (if available)
10268  std::vector<ecvGlobalShiftManager::ShiftInfo> lastInfos;
10269  if (ecvGlobalShiftManager::GetLast(lastInfos)) {
10270  sasDlg.addShiftInfo(lastInfos);
10271  }
10272  // add entries from file (if any)
10273  sasDlg.addFileInfo();
10274 
10275  if (!sasDlg.exec()) return;
10276 
10277  shift = sasDlg.getShift();
10278  scale = sasDlg.getScale();
10279  bool preserveGlobalPos = sasDlg.keepGlobalPos();
10280 
10281  CVLog::Print(tr("[Global Shift/Scale] New shift: (%1, %2, %3)")
10282  .arg(shift.x)
10283  .arg(shift.y)
10284  .arg(shift.z));
10285  CVLog::Print(tr("[Global Shift/Scale] New scale: %1").arg(scale));
10286 
10287  // apply new shift
10288  {
10289  for (auto& entity : shiftedEntities) {
10290  ccShiftedObject* shifted = entity.first;
10291  ccHObject* ent = entity.second;
10292  if (preserveGlobalPos) {
10293  // to preserve the global position of the cloud, we may have to
10294  // translate and/or rescale the cloud
10295  CCVector3d Ql =
10297  CCVector3d Qg = shifted->toGlobal3d(Ql);
10298  CCVector3d Ql2 = Qg * scale + shift;
10299  CCVector3d T = Ql2 - Ql;
10300 
10301  assert(shifted->getGlobalScale() > 0);
10302  double scaleCoef = scale / shifted->getGlobalScale();
10303 
10306  std::abs(scaleCoef - 1.0))) {
10307  ccGLMatrix transMat;
10308  transMat.toIdentity();
10309  transMat.scale(static_cast<float>(scaleCoef));
10310  transMat.setTranslation(T);
10311 
10312  // DGM FIXME: we only test the entity own bounding box (and
10313  // we update its shift & scale info) but we apply the
10314  // transformation to all its children?!
10315  ent->applyGLTransformation_recursive(&transMat);
10316 
10318  tr("[Global Shift/Scale] To preserve its original "
10319  "position, the entity '%1' has been translated "
10320  "of (%2,%3,%4) and rescaled of a factor %5")
10321  .arg(ent->getName())
10322  .arg(T.x)
10323  .arg(T.y)
10324  .arg(T.z)
10325  .arg(scaleCoef));
10326  }
10327  }
10328  shifted->setGlobalShift(shift);
10329  shifted->setGlobalScale(scale);
10330  }
10331  }
10332 
10333  refreshSelected();
10334  updateUI();
10335 }
10336 
10337 // Tools measurement menu methods
10338 void MainWindow::activateDistanceMode() {
10339 #ifdef USE_PCL_BACKEND
10340  doActionMeasurementMode(
10341  ecvGenericMeasurementTools::MeasurementType::DISTANCE_WIDGET);
10342 #else
10344  "[MainWindow] please use pcl as backend and then try again!");
10345  return;
10346 #endif // USE_PCL_BACKEND
10347 }
10348 
10349 void MainWindow::activateProtractorMode() {
10350 #ifdef USE_PCL_BACKEND
10351  doActionMeasurementMode(
10352  ecvGenericMeasurementTools::MeasurementType::PROTRACTOR_WIDGET);
10353 #else
10355  "[MainWindow] please use pcl as backend and then try again!");
10356  return;
10357 #endif // USE_PCL_BACKEND
10358 }
10359 
10360 void MainWindow::activateContourMode() {
10361 #ifdef USE_PCL_BACKEND
10362  doActionMeasurementMode(
10363  ecvGenericMeasurementTools::MeasurementType::CONTOUR_WIDGET);
10364 #else
10366  "[MainWindow] please use pcl as backend and then try again!");
10367  return;
10368 #endif // USE_PCL_BACKEND
10369 }
10370 
10371 void MainWindow::doActionMeasurementMode(int mode) {
10372  if (!haveOneSelection()) return;
10373 
10374  // we have to use a local copy: 'unselectEntity' will change the set of
10375  // currently selected entities!
10376  ccHObject::Container selectedEntities = getSelectedEntities();
10377 
10378  if (!m_measurementTool) {
10379  m_measurementTool = new ecvMeasurementTool(this);
10380  connect(m_measurementTool, &ccOverlayDialog::processFinished, this,
10381  [=]() {
10382  ccHObject::Container outs = m_measurementTool->getOutputs();
10383  for (ccHObject* entity : outs) {
10384  entity->setEnabled(true);
10385  }
10386 
10387  if (!outs.empty()) {
10388  // hide origin entities.
10389  for (ccHObject* entity : selectedEntities) {
10390  entity->setEnabled(false);
10391  }
10392 
10393  m_ccRoot->selectEntities(outs);
10394  refreshSelected();
10395  }
10396 
10397  freezeUI(false);
10398  updateUI();
10399  });
10400  registerOverlayDialog(m_measurementTool, Qt::TopRightCorner);
10401  }
10402 
10403 #ifdef USE_PCL_BACKEND
10405  if (!viewer) {
10406  CVLog::Error("[MainWindow] No visualizer available!");
10407  return;
10408  }
10409 
10410  ecvGenericMeasurementTools* measurementTool = new PclMeasurementTools(
10412 
10413  // Add the new tool instance to the measurement tool dialog
10414  m_measurementTool->setMeasurementTool(measurementTool);
10415  m_measurementTool->linkWith(ecvDisplayTools::GetCurrentScreen());
10416 
10417  for (ccHObject* entity : selectedEntities) {
10418  if (m_measurementTool->addAssociatedEntity(entity)) {
10419  // automatically deselect the entity (to avoid seeing its bounding
10420  // box ;)
10421  m_ccRoot->unselectEntity(entity);
10422  }
10423  }
10424 
10425  if (m_measurementTool->getNumberOfAssociatedEntity() == 0) {
10426  CVLog::Warning("[MainWindow] No valid entities for measurement!");
10427  return;
10428  }
10429 
10430  if (m_measurementTool->start()) {
10431  updateOverlayDialogsPlacement();
10433  } else {
10434  freezeUI(false);
10435  updateUI();
10436  ecvConsole::Error(tr("Unexpected error!"));
10437  }
10438 #else
10440  "[MainWindow] please use pcl as backend and then try again!");
10441  return;
10442 #endif // USE_PCL_BACKEND
10443 }
10444 
10445 void MainWindow::activateClippingMode() {
10446 #ifdef USE_PCL_BACKEND
10447  doActionFilterMode(ecvGenericFiltersTool::FilterType::CLIP_FILTER);
10448 #else
10450  "[MainWindow] please use pcl as backend and then try again!");
10451  return;
10452 #endif // USE_PCL_BACKEND
10453 }
10454 
10455 void MainWindow::activateSliceMode() {
10456 #ifdef USE_PCL_BACKEND
10457  doActionFilterMode(ecvGenericFiltersTool::FilterType::SLICE_FILTER);
10458 #else
10460  "[MainWindow] please use pcl as backend and then try again!");
10461  return;
10462 #endif // USE_PCL_BACKEND
10463 }
10464 
10465 void MainWindow::activateProbeMode() {
10466 #ifdef USE_PCL_BACKEND
10467  doActionFilterMode(ecvGenericFiltersTool::FilterType::PROBE_FILTER);
10468 #else
10470  "[MainWindow] please use pcl as backend and then try again!");
10471  return;
10472 #endif // USE_PCL_BACKEND
10473 }
10474 
10475 void MainWindow::activateDecimateMode() {
10476 #ifdef USE_PCL_BACKEND
10477  doActionFilterMode(ecvGenericFiltersTool::FilterType::DECIMATE_FILTER);
10478 #else
10480  "[MainWindow] please use pcl as backend and then try again!");
10481  return;
10482 #endif // USE_PCL_BACKEND
10483 }
10484 
10485 void MainWindow::activateIsoSurfaceMode() {
10486 #ifdef USE_PCL_BACKEND
10487  doActionFilterMode(ecvGenericFiltersTool::FilterType::ISOSURFACE_FILTER);
10488 #else
10490  "[MainWindow] please use pcl as backend and then try again!");
10491  return;
10492 #endif // USE_PCL_BACKEND
10493 }
10494 
10495 void MainWindow::activateThresholdMode() {
10496 #ifdef USE_PCL_BACKEND
10497  doActionFilterMode(ecvGenericFiltersTool::FilterType::THRESHOLD_FILTER);
10498 #else
10500  "[MainWindow] please use pcl as backend and then try again!");
10501  return;
10502 #endif // USE_PCL_BACKEND
10503 }
10504 
10505 void MainWindow::activateSmoothMode() {
10506 #ifdef USE_PCL_BACKEND
10507  doActionFilterMode(ecvGenericFiltersTool::FilterType::SMOOTH_FILTER);
10508 #else
10510  "[MainWindow] please use pcl as backend and then try again!");
10511  return;
10512 #endif // USE_PCL_BACKEND
10513 }
10514 
10515 void MainWindow::activateGlyphMode() {
10516 #ifdef USE_PCL_BACKEND
10517  doActionFilterMode(ecvGenericFiltersTool::FilterType::GLYPH_FILTER);
10518 #else
10520  "[MainWindow] please use pcl as backend and then try again!");
10521  return;
10522 #endif // USE_PCL_BACKEND
10523 }
10524 
10525 void MainWindow::activateStreamlineMode() {
10526 #ifdef USE_PCL_BACKEND
10527  doActionFilterMode(ecvGenericFiltersTool::FilterType::STREAMLINE_FILTER);
10528 #else
10530  "[MainWindow] please use pcl as backend and then try again!");
10531  return;
10532 #endif // USE_PCL_BACKEND
10533 }
10534 
10535 void MainWindow::doActionFilterMode(int mode) {
10536  if (!haveOneSelection()) return;
10537 
10538 #ifdef USE_PCL_BACKEND
10539  ecvGenericFiltersTool* filter =
10540  new PclFiltersTool(ecvDisplayTools::GetVisualizer3D(),
10542 #else
10544  "[MainWindow] please use pcl as backend and then try again!");
10545  return;
10546 #endif // USE_PCL_BACKEND
10547 
10548  // we have to use a local copy: 'unselectEntity' will change the set of
10549  // currently selected entities!
10550  ccHObject::Container selectedEntities = getSelectedEntities();
10551  if (!m_filterTool) {
10552  m_filterTool = new ecvFilterTool(this);
10553  connect(m_filterTool, &ccOverlayDialog::processFinished, this, [=]() {
10554  ccHObject::Container outs = m_filterTool->getOutputs();
10555  for (ccHObject* entity : outs) {
10556  entity->setEnabled(true);
10557  }
10558 
10559  if (!outs.empty()) {
10560  // hide origin entities.
10561  for (ccHObject* entity : selectedEntities) {
10562  entity->setEnabled(false);
10563  }
10564 
10565  m_ccRoot->selectEntities(outs);
10566  refreshSelected();
10567  }
10568 
10569  freezeUI(false);
10570  updateUI();
10571  });
10572  }
10573 
10574  m_filterTool->setFilter(filter);
10575  m_filterTool->linkWith(ecvDisplayTools::GetCurrentScreen());
10576 
10577  for (ccHObject* entity : selectedEntities) {
10578  if (m_filterTool->addAssociatedEntity(entity)) {
10579  // automatically deselect the entity (to avoid seeing its bounding
10580  // box ;)
10581  m_ccRoot->unselectEntity(entity);
10582  }
10583  }
10584 
10585  if (m_filterTool->getNumberOfAssociatedEntity() == 0) {
10586  m_filterTool->close();
10587  return;
10588  }
10589 
10590  freezeUI(true);
10591  m_ui->ViewToolBar->setDisabled(false);
10592 
10593  if (m_filterTool->start()) {
10594  registerOverlayDialog(m_filterTool, Qt::TopRightCorner);
10595  freezeUI(true);
10596  updateOverlayDialogsPlacement();
10597  } else {
10598  ecvConsole::Error(tr("Unexpected error!")); // indeed...
10599  }
10600 }
10601 
10602 void MainWindow::doBoxAnnotation() {
10603 #ifdef USE_PCL_BACKEND
10604  doAnnotations(ecvGenericAnnotationTool::AnnotationMode::BOUNDINGBOX);
10605 #else
10607  "[MainWindow] please use pcl as backend and then try again!");
10608  return;
10609 #endif // USE_PCL_BACKEND
10610 }
10611 
10612 void MainWindow::doSemanticAnnotation() {
10613 #ifdef USE_PCL_BACKEND
10614  doAnnotations(ecvGenericAnnotationTool::AnnotationMode::SEMANTICS);
10615 #else
10617  "[MainWindow] please use pcl as backend and then try again!");
10618  return;
10619 #endif // USE_PCL_BACKEND
10620 }
10621 
10622 void MainWindow::doAnnotations(int mode) {
10623  if (!haveOneSelection()) {
10624  if (haveSelection()) ecvConsole::Error(tr("Select only one cloud!"));
10625  return;
10626  }
10627 
10628  ccHObject* ent = m_selectedEntities[0];
10629  if (!ent->isKindOf(CV_TYPES::POINT_CLOUD)) {
10630  ecvConsole::Error(tr("only point cloud is supported!"));
10631  return;
10632  }
10633 
10634 #ifdef USE_PCL_BACKEND
10635  PclAnnotationTool* annoTools = new PclAnnotationTool(
10638  if (!m_annoTool) {
10639  m_annoTool = new ecvAnnotationsTool(this);
10640  connect(m_annoTool, &ccOverlayDialog::processFinished, this, [=]() {
10642  freezeUI(false);
10643  updateUI();
10644  });
10645  }
10646 #else
10648  "[MainWindow] please use pcl as backend and then try again!");
10649  return;
10650 #endif // USE_PCL_BACKEND
10651 
10652  if (!m_annoTool->setAnnotationsTool(annoTools) ||
10653  !m_annoTool->linkWith(ecvDisplayTools::GetCurrentScreen())) {
10654  CVLog::Warning("[MainWindow::doAnnotations] Initialization failed!");
10655  return;
10656  }
10657 
10658  if (m_annoTool->addAssociatedEntity(ent)) {
10659  // automatically deselect the entity (to avoid seeing its bounding box
10660  // ;)
10661  m_ccRoot->unselectEntity(ent);
10662  }
10663 
10664  if (m_annoTool->getNumberOfAssociatedEntity() == 0) {
10665  m_annoTool->close();
10666  return;
10667  }
10668 
10669  freezeUI(true);
10670  m_ui->ViewToolBar->setDisabled(false);
10671 
10672  if (m_annoTool->start()) {
10673  registerOverlayDialog(m_annoTool, Qt::TopRightCorner);
10674  freezeUI(true);
10676  } else {
10677  ecvConsole::Error(tr("Unexpected error!")); // indeed...
10678  }
10679 }
10680 
10681 void MainWindow::doSemanticSegmentation() {
10682 #ifdef USE_PYTHON_MODULE
10683  if (!haveSelection()) return;
10684 
10685  if (!m_dssTool) {
10686  m_dssTool = new ecvDeepSemanticSegmentationTool(this);
10687  connect(m_dssTool, &ccOverlayDialog::processFinished, this,
10688  &MainWindow::deactivateSemanticSegmentation);
10689  registerOverlayDialog(m_dssTool, Qt::TopRightCorner);
10690  }
10691 
10692  if (!m_dssTool->linkWith(ecvDisplayTools::GetCurrentScreen())) {
10694  "[MainWindow::doSemanticSegmentation] Initialization failed!");
10695  return;
10696  }
10697 
10698  for (ccHObject* ent : getSelectedEntities()) {
10699  if (m_dssTool->addEntity(ent)) {
10700  // automatically deselect the entity (to avoid seeing its bounding
10701  // box ;)
10702  m_ccRoot->unselectEntity(ent);
10703  }
10704  }
10705 
10706  if (m_dssTool->getNumberOfValidEntities() == 0) {
10707  m_dssTool->close();
10708  ecvConsole::Warning(tr("no valid point cloud is selected!"));
10709  return;
10710  }
10711 
10712  if (m_dssTool->start()) {
10713  freezeUI(true);
10715  } else {
10716  ecvConsole::Error(tr("Unexpected error!")); // indeed...
10717  }
10718 
10719 #else
10720  CVLog::Warning("python interface library has not been compiled!");
10721  return;
10722 #endif // USE_PYTHON_MODULE
10723 }
10724 
10725 void MainWindow::deactivateSemanticSegmentation(bool state) {
10726 #ifdef USE_PYTHON_MODULE
10727  if (m_dssTool && state) {
10729  m_dssTool->getSegmentations(result);
10730  ccHObject::Container segmentedEntities;
10731  if (!result.empty()) {
10732  for (ccHObject* obj : result) {
10733  addToDB(obj);
10734  for (unsigned i = 0; i < obj->getChildrenNumber(); ++i) {
10735  segmentedEntities.push_back(obj->getChild(i));
10736  }
10737  }
10738  m_ccRoot->selectEntities(segmentedEntities);
10739  } else {
10740  CVLog::Print(
10741  tr("segmentation info has been exported to sf field!"));
10742  }
10743  }
10744 
10745  freezeUI(false);
10746 
10747  updateUI();
10748 #endif // USE_PYTHON_MODULE
10749 }
10750 
10751 void MainWindow::doActionDBScanCluster() {
10752  if (!haveSelection()) {
10753  return;
10754  }
10755 
10756  ccHObject::Container clouds;
10757  for (auto ent : getSelectedEntities()) {
10758  if (!ent->isKindOf(CV_TYPES::POINT_CLOUD)) {
10759  CVLog::Warning("only point cloud is supported!");
10760  continue;
10761  }
10762  clouds.push_back(ent);
10763  }
10764 
10765  if (!ccEntityAction::DBScanCluster(clouds, this)) {
10766  ecvConsole::Error(tr("Error(s) occurred! See the Console messages"));
10767  return;
10768  }
10769 
10770  refreshSelected();
10771  updateUI();
10772 }
10773 
10774 void MainWindow::doActionPlaneSegmentation() {
10775  if (!haveSelection()) {
10776  return;
10777  }
10778 
10779  ccHObject::Container clouds;
10780  for (auto ent : getSelectedEntities()) {
10781  if (!ent->isKindOf(CV_TYPES::POINT_CLOUD)) {
10782  CVLog::Warning("only point cloud is supported!");
10783  continue;
10784  }
10785  clouds.push_back(ent);
10786  }
10787 
10788  ccHObject::Container entities;
10789  if (ccEntityAction::RansacSegmentation(clouds, entities, this)) {
10790  for (size_t i = 0; i < entities.size(); ++i) {
10791  addToDB(entities[i]);
10792  }
10793  } else {
10794  ecvConsole::Error(tr("Error(s) occurred! See the Console messages"));
10795  return;
10796  }
10797 
10798  updateUI();
10799 }
10800 
10801 void MainWindow::activateSegmentationMode() {
10802  if (!haveSelection()) return;
10803 
10804  if (!m_gsTool) {
10805  m_gsTool = new ccGraphicalSegmentationTool(this);
10806  connect(m_gsTool, &ccOverlayDialog::processFinished, this,
10807  &MainWindow::deactivateSegmentationMode);
10808  registerOverlayDialog(m_gsTool, Qt::TopRightCorner);
10809  }
10810 
10812 
10813  for (ccHObject* entity : getSelectedEntities()) {
10814  entity->setSelected_recursive(false);
10815  m_gsTool->addEntity(entity);
10816  }
10817 
10818  if (m_gsTool->getNumberOfValidEntities() == 0) {
10819  ecvConsole::Error(tr("No segmentable entity in active window!"));
10820  return;
10821  }
10822 
10823  freezeUI(true);
10824  m_ui->ViewToolBar->setDisabled(false);
10825 
10826  if (!m_gsTool->start()) {
10827  deactivateSegmentationMode(false);
10828  } else {
10830  bool perspectiveEnabled = ecvDisplayTools::GetPerspectiveState();
10831  if (!perspectiveEnabled) // segmentation must work in perspective mode
10832  {
10834  m_lastViewMode = VIEWMODE::ORTHOGONAL;
10835  } else {
10836  m_lastViewMode = VIEWMODE::PERSPECTIVE;
10837  }
10838  }
10839 }
10840 
10841 void MainWindow::deactivateSegmentationMode(bool state) {
10842  bool deleteHiddenParts = false;
10843 
10844  // shall we apply segmentation?
10845  if (state) {
10846  ccHObject* firstResult = nullptr;
10847 
10848  deleteHiddenParts = m_gsTool->deleteHiddenParts();
10849 
10850  // aditional vertices of which visibility array should be manually reset
10851  std::unordered_set<ccGenericPointCloud*> verticesToReset;
10852 
10853  QSet<ccHObject*>& segmentedEntities = m_gsTool->entities();
10854  for (QSet<ccHObject*>::iterator p = segmentedEntities.begin();
10855  p != segmentedEntities.end();) {
10856  ccHObject* entity = (*p);
10857 
10858  if (entity->isKindOf(CV_TYPES::POINT_CLOUD) ||
10859  entity->isKindOf(CV_TYPES::MESH)) {
10860  // first, do the things that must absolutely be done BEFORE
10861  // removing the entity from DB (even temporarily) bool
10862  // lockedVertices;
10864  entity /*,&lockedVertices*/);
10865  assert(cloud);
10866  if (cloud) {
10867  // specific case: labels (do this before temporarily
10868  // removing 'entity' from DB!)
10869  ccHObject::Container labels;
10870  if (m_ccRoot) {
10871  m_ccRoot->getRootEntity()->filterChildren(
10872  labels, true, CV_TYPES::LABEL_2D);
10873  }
10874  for (ccHObject::Container::iterator it = labels.begin();
10875  it != labels.end(); ++it) {
10876  // Warning: cc2DViewportLabel is also a kind of
10877  // 'CV_TYPES::LABEL_2D'!
10878  if ((*it)->isA(CV_TYPES::LABEL_2D)) {
10879  // we must search for all dependent labels and
10880  // remove them!!!
10881  // TODO: couldn't we be more clever and update the
10882  // label instead?
10883  cc2DLabel* label = static_cast<cc2DLabel*>(*it);
10884  bool removeLabel = false;
10885  for (unsigned i = 0; i < label->size(); ++i) {
10886  if (label->getPickedPoint(i).cloud == entity) {
10887  removeLabel = true;
10888  break;
10889  }
10890  }
10891 
10892  if (removeLabel && label->getParent()) {
10894  tr("[Segmentation] Label %1 depends on "
10895  "cloud %2 and will be removed")
10896  .arg(label->getName(),
10897  cloud->getName()));
10898  ccHObject* labelParent = label->getParent();
10899  ccHObjectContext objContext =
10901  labelParent);
10902  labelParent->removeChild(label);
10903  label = nullptr;
10904  putObjectBackIntoDBTree(labelParent,
10905  objContext);
10906  }
10907  }
10908  } // for each label
10909  } // if (cloud)
10910 
10911  // we temporarily detach the entity, as it may undergo
10912  //"severe" modifications (octree deletion, etc.) --> see
10913  // ccPointCloud::createNewCloudFromVisibilitySelection
10914  ccHObjectContext objContext =
10916 
10917  // apply segmentation
10918  ccHObject* segmentationResult = nullptr;
10919  bool deleteOriginalEntity = deleteHiddenParts;
10920  if (entity->isKindOf(CV_TYPES::POINT_CLOUD)) {
10921  ccGenericPointCloud* genCloud =
10923  ccGenericPointCloud* segmentedCloud =
10925  !deleteHiddenParts);
10926  if (segmentedCloud && segmentedCloud->size() == 0) {
10927  delete segmentationResult;
10928  segmentationResult = nullptr;
10929  } else {
10930  segmentationResult = segmentedCloud;
10931  }
10932 
10933  deleteOriginalEntity |= (genCloud->size() == 0);
10934  }
10935  else if (entity->isKindOf(CV_TYPES::MESH)/*|| entity->isA(CV_TYPES::PRIMITIVE)*/) //TODO
10936  {
10937  if (entity->isA(CV_TYPES::MESH)) {
10938  segmentationResult =
10939  ccHObjectCaster::ToMesh(entity)
10941  !deleteHiddenParts);
10942  } else if (entity->isA(CV_TYPES::SUB_MESH)) {
10943  segmentationResult =
10946  !deleteHiddenParts);
10947  }
10948 
10949  deleteOriginalEntity |=
10950  (ccHObjectCaster::ToGenericMesh(entity)->size() ==
10951  0);
10952  }
10953 
10954  if (segmentationResult) {
10955  assert(cloud);
10956 
10957  // we must take care of the remaining part
10958  if (!deleteHiddenParts) {
10959  // no need to put back the entity in DB if we delete it
10960  // afterwards!
10961  if (!deleteOriginalEntity) {
10962  entity->setName(entity->getName() +
10963  QString(".remaining"));
10964  putObjectBackIntoDBTree(entity, objContext);
10965  }
10966  } else {
10967  // keep original name(s)
10968  segmentationResult->setName(entity->getName());
10969  if (entity->isKindOf(CV_TYPES::MESH) &&
10970  segmentationResult->isKindOf(CV_TYPES::MESH)) {
10971  ccGenericMesh* meshEntity =
10973  ccHObjectCaster::ToGenericMesh(segmentationResult)
10974  ->getAssociatedCloud()
10975  ->setName(meshEntity->getAssociatedCloud()
10976  ->getName());
10977 
10978  // specific case: if the sub mesh is deleted
10979  // afterwards (see below) then its associated
10980  // vertices won't be 'reset' by the segmentation
10981  // tool!
10982  if (deleteHiddenParts &&
10983  meshEntity->isA(CV_TYPES::SUB_MESH)) {
10984  verticesToReset.insert(
10985  meshEntity->getAssociatedCloud());
10986  }
10987  }
10988  assert(deleteOriginalEntity);
10989  // deleteOriginalEntity = true;
10990  }
10991 
10992  if (segmentationResult->isA(CV_TYPES::SUB_MESH)) {
10993  // for sub-meshes, we have no choice but to use its
10994  // parent mesh!
10995  objContext.parent =
10996  static_cast<ccSubMesh*>(segmentationResult)
10997  ->getAssociatedMesh();
10998  } else {
10999  // otherwise we look for first non-mesh or non-cloud
11000  // parent
11001  while (objContext.parent &&
11002  (objContext.parent->isKindOf(CV_TYPES::MESH) ||
11003  objContext.parent->isKindOf(
11005  objContext.parent = objContext.parent->getParent();
11006  }
11007  }
11008 
11009  if (objContext.parent) {
11010  objContext.parent->addChild(
11011  segmentationResult); // FiXME:
11012  // objContext.parentFlags?
11013  }
11014 
11015  addToDB(segmentationResult);
11016 
11017  if (!firstResult) {
11018  firstResult = segmentationResult;
11019  }
11020  } else if (!deleteOriginalEntity) {
11021  // ecvConsole::Error("An error occurred! (not enough
11022  // memory?)");
11023  putObjectBackIntoDBTree(entity, objContext);
11024  }
11025 
11026  if (deleteOriginalEntity) {
11027  p = segmentedEntities.erase(p);
11028 
11029  delete entity;
11030  entity = nullptr;
11031  } else {
11032  ++p;
11033  }
11034  }
11035  }
11036 
11037  // specific actions
11038  {
11039  for (ccGenericPointCloud* cloud : verticesToReset) {
11040  cloud->resetVisibilityArray();
11041  }
11042  }
11043 
11044  if (firstResult && m_ccRoot) {
11045  m_ccRoot->selectEntity(firstResult);
11046  }
11047  }
11048 
11049  if (m_gsTool) {
11050  if (m_lastViewMode == VIEWMODE::ORTHOGONAL) {
11052  } else if (m_lastViewMode == VIEWMODE::PERSPECTIVE) {
11054  }
11055 
11057  QString(),
11058  ecvDisplayTools::UPPER_CENTER_MESSAGE); // clear the area
11060  m_gsTool->removeAllEntities();
11062  refreshAll();
11063  }
11066  }
11067 
11068  freezeUI(false);
11069 
11070  updateUI();
11071 }
11072 
11074  unsigned index;
11075  unsigned size;
11076 
11077  ComponentIndexAndSize(unsigned i, unsigned s) : index(i), size(s) {}
11078 
11080  const ComponentIndexAndSize& b) {
11081  return a.size > b.size;
11082  }
11083 };
11084 
11085 void MainWindow::createComponentsClouds(
11086  ccGenericPointCloud* cloud,
11088  unsigned minPointsPerComponent,
11089  bool randomColors,
11090  bool selectComponents,
11091  bool sortBysize /*=true*/) {
11092  if (!cloud || components.empty()) return;
11093 
11094  std::vector<ComponentIndexAndSize> sortedIndexes;
11095  std::vector<ComponentIndexAndSize>* _sortedIndexes = nullptr;
11096  if (sortBysize) {
11097  try {
11098  sortedIndexes.reserve(components.size());
11099  } catch (const std::bad_alloc&) {
11101  tr("[CreateComponentsClouds] Not enough memory to sort "
11102  "components by size!"));
11103  sortBysize = false;
11104  }
11105 
11106  if (sortBysize) // still ok?
11107  {
11108  unsigned compCount = static_cast<unsigned>(components.size());
11109  for (unsigned i = 0; i < compCount; ++i) {
11110  sortedIndexes.emplace_back(i, components[i]->size());
11111  }
11112 
11113  ParallelSort(sortedIndexes.begin(), sortedIndexes.end(),
11115 
11116  _sortedIndexes = &sortedIndexes;
11117  }
11118  }
11119 
11120  // we create "real" point clouds for all input components
11121  {
11122  ccPointCloud* pc = cloud->isA(CV_TYPES::POINT_CLOUD)
11123  ? static_cast<ccPointCloud*>(cloud)
11124  : 0;
11125 
11126  // we create a new group to store all CCs
11127  ccHObject* ccGroup =
11128  new ccHObject(cloud->getName() + QString(" [CCs]"));
11129 
11130  // for each component
11131  for (size_t i = 0; i < components.size(); ++i) {
11132  cloudViewer::ReferenceCloud* compIndexes =
11133  _sortedIndexes ? components[_sortedIndexes->at(i).index]
11134  : components[i];
11135 
11136  // if it has enough points
11137  if (compIndexes->size() >= minPointsPerComponent) {
11138  // we create a new entity
11139  ccPointCloud* compCloud =
11140  (pc ? pc->partialClone(compIndexes)
11141  : ccPointCloud::From(compIndexes));
11142  if (compCloud) {
11143  // shall we colorize it with random color?
11144  if (randomColors) {
11146  compCloud->setRGBColor(col);
11147  compCloud->showColors(true);
11148  compCloud->showSF(false);
11149  }
11150 
11151  //'shift on load' information
11152  if (pc) {
11153  compCloud->setGlobalShift(pc->getGlobalShift());
11154  compCloud->setGlobalScale(pc->getGlobalScale());
11155  }
11156  compCloud->setVisible(true);
11157  compCloud->setName(
11158  QString("CC#%1").arg(ccGroup->getChildrenNumber()));
11159 
11160  // we add new CC to group
11161  ccGroup->addChild(compCloud);
11162 
11163  if (selectComponents && m_ccRoot)
11164  m_ccRoot->selectEntity(compCloud, true);
11165  } else {
11167  tr("[createComponentsClouds] Failed to create "
11168  "component #%1! (not enough memory)")
11169  .arg(ccGroup->getChildrenNumber() + 1));
11170  }
11171  }
11172 
11173  delete compIndexes;
11174  compIndexes = nullptr;
11175  }
11176 
11177  components.clear();
11178 
11179  if (ccGroup->getChildrenNumber() == 0) {
11181  "No component was created! Check the minimum size...");
11182  delete ccGroup;
11183  ccGroup = nullptr;
11184  } else {
11185  addToDB(ccGroup);
11186  ecvConsole::Print(tr("[createComponentsClouds] %1 component(s) "
11187  "were created from cloud '%2'")
11188  .arg(ccGroup->getChildrenNumber())
11189  .arg(cloud->getName()));
11190  }
11191 
11192  // auto-hide original cloud
11193  if (ccGroup) {
11194  cloud->setEnabled(false);
11196  tr("[createComponentsClouds] Original cloud has been "
11197  "automatically hidden"));
11198  }
11199  }
11200 }
11201 
11202 void MainWindow::doActionLabelConnectedComponents() {
11203  // keep only the point clouds!
11204  std::vector<ccGenericPointCloud*> clouds;
11205  {
11206  for (ccHObject* entity : getSelectedEntities()) {
11207  if (entity->isKindOf(CV_TYPES::POINT_CLOUD))
11208  clouds.push_back(ccHObjectCaster::ToGenericPointCloud(entity));
11209  }
11210  }
11211 
11212  size_t count = clouds.size();
11213  if (count == 0) return;
11214 
11215  ccLabelingDlg dlg(this);
11216  if (count == 1) dlg.octreeLevelSpinBox->setCloud(clouds.front());
11217  if (!dlg.exec()) return;
11218 
11219  int octreeLevel = dlg.getOctreeLevel();
11220  unsigned minComponentSize =
11221  static_cast<unsigned>(std::max(0, dlg.getMinPointsNb()));
11222  bool randColors = dlg.randomColors();
11223 
11224  ecvProgressDialog pDlg(false, this);
11225  pDlg.setAutoClose(false);
11226 
11227  // we unselect all entities as we are going to automatically select the
11228  // created components (otherwise the user won't perceive the change!)
11229  if (m_ccRoot) {
11230  m_ccRoot->unselectAllEntities();
11231  }
11232 
11233  for (ccGenericPointCloud* cloud : clouds) {
11234  if (cloud && cloud->isA(CV_TYPES::POINT_CLOUD)) {
11235  ccPointCloud* pc = static_cast<ccPointCloud*>(cloud);
11236 
11237  ccOctree::Shared theOctree = cloud->getOctree();
11238  if (!theOctree) {
11239  ecvProgressDialog pOctreeDlg(true, this);
11240  theOctree = cloud->computeOctree(&pOctreeDlg);
11241  if (!theOctree) {
11243  tr("Couldn't compute octree for cloud '%s'!")
11244  .arg(cloud->getName()));
11245  break;
11246  }
11247  }
11248 
11249  // we create/activate CCs label scalar field
11250  int sfIdx = pc->getScalarFieldIndexByName(
11252  if (sfIdx < 0) {
11253  sfIdx = pc->addScalarField(
11255  }
11256  if (sfIdx < 0) {
11258  tr("Couldn't allocate a new scalar field for computing "
11259  "ECV labels! Try to free some memory ..."));
11260  break;
11261  }
11262  pc->setCurrentScalarField(sfIdx);
11263 
11264  // we try to label all CCs
11266  int componentCount = cloudViewer::AutoSegmentationTools::
11268  cloud, static_cast<unsigned char>(octreeLevel),
11269  false, &pDlg, theOctree.data());
11270 
11271  if (componentCount >= 0) {
11272  // if successful, we extract each ECV (stored in "components")
11273 
11274  // safety test
11275  int realComponentCount = 0;
11276  {
11277  for (size_t i = 0; i < components.size(); ++i) {
11278  if (components[i]->size() >= minComponentSize) {
11279  ++realComponentCount;
11280  }
11281  }
11282  }
11283 
11284  if (realComponentCount > 500) {
11285  // too many components
11286  if (QMessageBox::warning(
11287  this, tr("Many components"),
11288  tr("Do you really expect up to %1 "
11289  "components?\n(this may take a lot of time "
11290  "to process and display)")
11291  .arg(realComponentCount),
11292  QMessageBox::Yes,
11293  QMessageBox::No) == QMessageBox::No) {
11294  // cancel
11295  pc->deleteScalarField(sfIdx);
11296  if (pc->getNumberOfScalarFields() != 0) {
11298  static_cast<int>(
11299  pc->getNumberOfScalarFields()) -
11300  1);
11301  } else {
11302  pc->showSF(false);
11303  }
11304  continue;
11305  }
11306  }
11307 
11310  extractConnectedComponents(cloud, components)) {
11311  ecvConsole::Warning(tr("[doActionLabelConnectedComponents] "
11312  "Something went wrong while "
11313  "extracting CCs from cloud %1...")
11314  .arg(cloud->getName()));
11315  }
11316  } else {
11318  tr("[doActionLabelConnectedComponents] Something went "
11319  "wrong while extracting CCs from cloud %1...")
11320  .arg(cloud->getName()));
11321  }
11322 
11323  // we delete the CCs label scalar field (we don't need it anymore)
11324  pc->deleteScalarField(sfIdx);
11325  sfIdx = -1;
11326 
11327  // we create "real" point clouds for all CCs
11328  if (!components.empty()) {
11329  createComponentsClouds(cloud, components, minComponentSize,
11330  randColors, true);
11331  }
11332  }
11333  }
11334 
11335  updateUI();
11336 }
11337 
11338 void MainWindow::doActionKMeans() // TODO
11339 {
11340  ecvConsole::Error(tr("Not yet implemented! Sorry ..."));
11341 }
11342 
11343 void MainWindow::doActionFrontPropagation() // TODO
11344 {
11345  ecvConsole::Error(tr("Not yet implemented! Sorry ..."));
11346 }
11347 
11348 void MainWindow::doActionCloudCloudDist() {
11349  if (getSelectedEntities().size() != 2) {
11350  ecvConsole::Error("Select 2 point clouds!");
11351  return;
11352  }
11353 
11354  if (!m_selectedEntities[0]->isKindOf(CV_TYPES::POINT_CLOUD) ||
11355  !m_selectedEntities[1]->isKindOf(CV_TYPES::POINT_CLOUD)) {
11356  ecvConsole::Error(tr("Select 2 point clouds!"));
11357  return;
11358  }
11359 
11360  ccOrderChoiceDlg dlg(m_selectedEntities[0], tr("Compared"),
11361  m_selectedEntities[1], tr("Reference"), this);
11362  if (!dlg.exec()) return;
11363 
11364  ccGenericPointCloud* compCloud =
11365  ccHObjectCaster::ToGenericPointCloud(dlg.getFirstEntity());
11366  ccGenericPointCloud* refCloud =
11367  ccHObjectCaster::ToGenericPointCloud(dlg.getSecondEntity());
11368 
11369  // assert(!m_compDlg);
11370  if (m_compDlg) delete m_compDlg;
11371  m_compDlg = new ccComparisonDlg(compCloud, refCloud,
11373  connect(m_compDlg, &QDialog::finished, this,
11374  &MainWindow::deactivateComparisonMode);
11375  m_compDlg->show();
11376  // cDlg.setModal(false);
11377  // cDlg.exec();
11378  freezeUI(true);
11379 }
11380 
11381 void MainWindow::doActionCloudMeshDist() {
11382  if (getSelectedEntities().size() != 2) {
11383  ecvConsole::Error(tr("Select 2 entities!"));
11384  return;
11385  }
11386 
11387  bool isMesh[2] = {false, false};
11388  unsigned meshNum = 0;
11389  unsigned cloudNum = 0;
11390  for (unsigned i = 0; i < 2; ++i) {
11391  if (m_selectedEntities[i]->isKindOf(CV_TYPES::MESH)) {
11392  ++meshNum;
11393  isMesh[i] = true;
11394  } else if (m_selectedEntities[i]->isKindOf(CV_TYPES::POINT_CLOUD)) {
11395  ++cloudNum;
11396  }
11397  }
11398 
11399  if (meshNum == 0) {
11400  ecvConsole::Error(tr("Select at least one mesh!"));
11401  return;
11402  } else if (meshNum + cloudNum < 2) {
11403  ecvConsole::Error(tr("Select one mesh and one cloud or two meshes!"));
11404  return;
11405  }
11406 
11407  ccHObject* compEnt = nullptr;
11408  ccGenericMesh* refMesh = nullptr;
11409 
11410  if (meshNum == 1) {
11411  compEnt = m_selectedEntities[isMesh[0] ? 1 : 0];
11413  m_selectedEntities[isMesh[0] ? 0 : 1]);
11414  } else {
11415  ccOrderChoiceDlg dlg(m_selectedEntities[0], tr("Compared"),
11416  m_selectedEntities[1], tr("Reference"), this);
11417  if (!dlg.exec()) return;
11418 
11419  compEnt = dlg.getFirstEntity();
11420  refMesh = ccHObjectCaster::ToGenericMesh(dlg.getSecondEntity());
11421  }
11422 
11423  // assert(!m_compDlg);
11424  if (m_compDlg) delete m_compDlg;
11425  m_compDlg = new ccComparisonDlg(compEnt, refMesh,
11427  connect(m_compDlg, &QDialog::finished, this,
11428  &MainWindow::deactivateComparisonMode);
11429  m_compDlg->show();
11430 
11431  freezeUI(true);
11432 }
11433 
11434 void MainWindow::doActionCloudPrimitiveDist() {
11435  bool foundPrimitive = false;
11436  ccHObject::Container clouds;
11437  ccHObject* refEntity = nullptr;
11438  CV_CLASS_ENUM entityType = CV_TYPES::OBJECT;
11439  const char* errString =
11440  "[Compute Primitive Distances] Cloud to %s failed, error code = "
11441  "%i!";
11442 
11443  for (unsigned i = 0; i < getSelectedEntities().size(); ++i) {
11444  if (m_selectedEntities[i]->isKindOf(CV_TYPES::PRIMITIVE) ||
11445  m_selectedEntities[i]->isA(CV_TYPES::POLY_LINE)) {
11446  if (m_selectedEntities[i]->isA(CV_TYPES::PLANE) ||
11447  m_selectedEntities[i]->isA(CV_TYPES::SPHERE) ||
11448  m_selectedEntities[i]->isA(CV_TYPES::CYLINDER) ||
11449  m_selectedEntities[i]->isA(CV_TYPES::CONE) ||
11450  m_selectedEntities[i]->isA(CV_TYPES::BOX) ||
11451  m_selectedEntities[i]->isA(CV_TYPES::DISC) ||
11452  m_selectedEntities[i]->isA(CV_TYPES::POLY_LINE)) {
11453  if (foundPrimitive) {
11455  "[Compute Primitive Distances] Select only a "
11456  "single Plane/Box/Sphere/Cylinder/Cone/Polyline "
11457  "Primitive");
11458  return;
11459  }
11460  foundPrimitive = true;
11461  refEntity = m_selectedEntities[i];
11462  entityType = refEntity->getClassID();
11463  }
11464  } else if (m_selectedEntities[i]->isKindOf(CV_TYPES::POINT_CLOUD)) {
11465  clouds.push_back(m_selectedEntities[i]);
11466  }
11467  }
11468 
11469  if (!foundPrimitive) {
11471  "[Compute Primitive Distances] Select at least one "
11472  "Plane/Box/Sphere/Cylinder/Cone/Disc/Polyline Primitive!");
11473  return;
11474  }
11475  if (clouds.size() <= 0) {
11477  "[Compute Primitive Distances] Select at least one cloud!");
11478  return;
11479  }
11480 
11481  ecvPrimitiveDistanceDlg pDD{this};
11482  if (refEntity->isA(CV_TYPES::PLANE)) {
11483  pDD.treatPlanesAsBoundedCheckBox->setUpdatesEnabled(true);
11484  }
11485  bool execute = true;
11486  if (!refEntity->isA(CV_TYPES::POLY_LINE)) {
11487  execute = pDD.exec();
11488  }
11489  if (execute) {
11490  bool signedDist = pDD.signedDistances();
11491  bool flippedNormals = signedDist && pDD.flipNormals();
11492  bool treatPlanesAsBounded = pDD.treatPlanesAsBounded();
11493  for (auto& cloud : clouds) {
11494  ccPointCloud* compEnt = ccHObjectCaster::ToPointCloud(cloud);
11495  int sfIdx = compEnt->getScalarFieldIndexByName(
11497  if (sfIdx < 0) {
11498  // we need to create a new scalar field
11499  sfIdx = compEnt->addScalarField(
11501  if (sfIdx < 0) {
11502  CVLog::Error(
11503  QString("[Compute Primitive Distances] [Cloud: %1] "
11504  "Couldn't allocate a new scalar field for "
11505  "computing distances! Try to free some "
11506  "memory ...")
11507  .arg(compEnt->getName()));
11508  continue;
11509  }
11510  }
11511  compEnt->setCurrentScalarField(sfIdx);
11512  compEnt->enableScalarField();
11513  compEnt->forEach(
11515  int returnCode;
11516  switch (entityType) {
11517  case CV_TYPES::SPHERE: {
11518  if (!(returnCode = cloudViewer::DistanceComputationTools::
11519  computeCloud2SphereEquation(
11520  compEnt,
11521  refEntity->getOwnBB().getCenter(),
11522  static_cast<ccSphere*>(refEntity)
11523  ->getRadius(),
11524  signedDist)))
11525  ecvConsole::Error(errString, "Sphere", returnCode);
11526  break;
11527  }
11528  case CV_TYPES::PLANE: {
11529  ccPlane* plane = static_cast<ccPlane*>(refEntity);
11530  if (treatPlanesAsBounded) {
11531  cloudViewer::SquareMatrix rotationTransform(
11532  plane->getTransformation().data(), true);
11533  if (!(returnCode =
11535  computeCloud2RectangleEquation(
11536  compEnt,
11537  plane->getXWidth(),
11538  plane->getYWidth(),
11539  rotationTransform,
11540  plane->getCenter(),
11541  signedDist)))
11542  ecvConsole::Error(errString, "Bounded Plane",
11543  returnCode);
11544  } else {
11545  if (!(returnCode =
11547  computeCloud2PlaneEquation(
11548  compEnt,
11549  static_cast<ccPlane*>(
11550  refEntity)
11551  ->getEquation(),
11552  signedDist)))
11553  ecvConsole::Error(errString, "Infinite Plane",
11554  returnCode);
11555  }
11556  break;
11557  }
11558  case CV_TYPES::CYLINDER: {
11559  if (!(returnCode = cloudViewer::DistanceComputationTools::
11560  computeCloud2CylinderEquation(
11561  compEnt,
11562  static_cast<ccCylinder*>(refEntity)
11563  ->getBottomCenter(),
11564  static_cast<ccCylinder*>(refEntity)
11565  ->getTopCenter(),
11566  static_cast<ccCylinder*>(refEntity)
11567  ->getBottomRadius(),
11568  signedDist)))
11569  ecvConsole::Error(errString, "Cylinder", returnCode);
11570  break;
11571  }
11572  case CV_TYPES::CONE: {
11573  if (!(returnCode = cloudViewer::DistanceComputationTools::
11574  computeCloud2ConeEquation(
11575  compEnt,
11576  static_cast<ccCone*>(refEntity)
11577  ->getLargeCenter(),
11578  static_cast<ccCone*>(refEntity)
11579  ->getSmallCenter(),
11580  static_cast<ccCone*>(refEntity)
11581  ->getLargeRadius(),
11582  static_cast<ccCone*>(refEntity)
11583  ->getSmallRadius(),
11584  signedDist)))
11585  ecvConsole::Error(errString, "Cone", returnCode);
11586  break;
11587  }
11588  case CV_TYPES::BOX: {
11589  const ccGLMatrix& glTransform =
11590  refEntity->getGLTransformationHistory();
11591  cloudViewer::SquareMatrix rotationTransform(
11592  glTransform.data(), true);
11593  CCVector3 boxCenter = glTransform.getColumnAsVec3D(3);
11594  if (!(returnCode = cloudViewer::DistanceComputationTools::
11595  computeCloud2BoxEquation(
11596  compEnt,
11597  static_cast<ccBox*>(refEntity)
11598  ->getDimensions(),
11599  rotationTransform, boxCenter,
11600  signedDist)))
11601  ecvConsole::Error(errString, "Box", returnCode);
11602  break;
11603  }
11604  case CV_TYPES::DISC: {
11605  ccDisc* disc = static_cast<ccDisc*>(refEntity);
11606  cloudViewer::SquareMatrix rotationTransform(
11607  disc->getTransformation().data(), true);
11608  if (!(returnCode = cloudViewer::DistanceComputationTools::
11609  computeCloud2DiscEquation(
11610  compEnt,
11611  refEntity->getOwnBB().getCenter(),
11612  static_cast<ccDisc*>(refEntity)
11613  ->getRadius(),
11614  rotationTransform, signedDist)))
11615  ecvConsole::Error(errString, "Disc", returnCode);
11616  break;
11617  }
11618  case CV_TYPES::POLY_LINE: {
11619  signedDist = false;
11620  flippedNormals = false;
11621  ccPolyline* line = static_cast<ccPolyline*>(refEntity);
11623  computeCloud2PolylineEquation(compEnt, line);
11624  if (!returnCode) {
11625  ecvConsole::Error(errString, "Polyline", returnCode);
11626  }
11627  break;
11628  }
11629  default: {
11631  "[Compute Primitive Distances] Unsupported "
11632  "primitive type"); // Shouldn't ever reach here...
11633  break;
11634  }
11635  }
11636  QString sfName;
11637  sfName.clear();
11638  sfName = QString(
11639  signedDist
11642  if (flippedNormals) {
11643  compEnt->forEach(
11645  sfName += QString("[-]");
11646  }
11647 
11648  int _sfIdx = compEnt->getScalarFieldIndexByName(qPrintable(sfName));
11649  if (_sfIdx >= 0) {
11650  compEnt->deleteScalarField(_sfIdx);
11651  // we update sfIdx because indexes are all messed up after
11652  // deletion
11653  sfIdx = compEnt->getScalarFieldIndexByName(
11655  }
11656  compEnt->renameScalarField(sfIdx, qPrintable(sfName));
11657 
11658  ccScalarField* sf =
11659  static_cast<ccScalarField*>(compEnt->getScalarField(sfIdx));
11660  if (sf) {
11661  ScalarType mean;
11662  ScalarType variance;
11663  sf->computeMinAndMax();
11664  sf->computeMeanAndVariance(mean, &variance);
11665  CVLog::Print(
11666  "[Compute Primitive Distances] [Primitive: %s] [Cloud: "
11667  "%s] [%s] Mean distance = %f / std deviation = %f",
11668  qPrintable(refEntity->getName()),
11669  qPrintable(compEnt->getName()), qPrintable(sfName),
11670  mean, sqrt(variance));
11671  }
11672  compEnt->setCurrentDisplayedScalarField(sfIdx);
11673  compEnt->showSF(sfIdx >= 0);
11674  compEnt->setRedrawFlagRecursive(true);
11675  }
11676 
11677  refreshAll();
11679  }
11680 }
11681 
11682 void MainWindow::deactivateComparisonMode(int result) {
11683  // DGM: a bug apperead with recent changes (from CC or QT?)
11684  // which prevent us from deleting the dialog right away...
11685  //(it seems that QT has not yet finished the dialog closing
11686  // when the 'finished' signal is sent).
11687  // if(m_compDlg)
11688  // delete m_compDlg;
11689  // m_compDlg = 0;
11690 
11691  // if the comparison is a success, we select only the compared entity
11692  if (m_compDlg && result == QDialog::Accepted && m_ccRoot) {
11693  ccHObject* compEntity = m_compDlg->getComparedEntity();
11694  if (compEntity) {
11695  m_ccRoot->selectEntity(compEntity);
11696  }
11697  }
11698 
11699  freezeUI(false);
11700 
11701  updateUI();
11702 }
11703 
11704 void MainWindow::doActionExportPlaneInfo() {
11705  ccHObject::Container planes;
11706 
11707  const ccHObject::Container& selectedEntities = getSelectedEntities();
11708  if (selectedEntities.size() == 1 &&
11709  selectedEntities.front()->isA(CV_TYPES::HIERARCHY_OBJECT)) {
11710  // a group
11711  selectedEntities.front()->filterChildren(planes, true, CV_TYPES::PLANE,
11712  false);
11713  } else {
11714  for (ccHObject* ent : selectedEntities) {
11715  if (ent->isKindOf(CV_TYPES::PLANE)) {
11716  // a single plane
11717  planes.push_back(static_cast<ccPlane*>(ent));
11718  }
11719  }
11720  }
11721 
11722  if (planes.empty()) {
11723  CVLog::Error(tr("No plane in selection"));
11724  return;
11725  }
11726 
11727  // persistent settings
11728  QString currentPath =
11731  .toString();
11732 
11733  QString outputFilename = QFileDialog::getSaveFileName(
11734  this, tr("Select output file"), currentPath, "*.csv", nullptr,
11736 
11737  if (outputFilename.isEmpty()) {
11738  // process cancelled by the user
11739  return;
11740  }
11741 
11742  QFile csvFile(outputFilename);
11743  if (!csvFile.open(QFile::WriteOnly | QFile::Text)) {
11744  ecvConsole::Error(tr(
11745  "Failed to open file for writing! (check file permissions)"));
11746  return;
11747  }
11748 
11749  // save last saving location
11751  QFileInfo(outputFilename).absolutePath());
11752 
11753  // write CSV header
11754  QTextStream csvStream(&csvFile);
11755  csvStream << "Name,";
11756  csvStream << "Width,";
11757  csvStream << "Height,";
11758  csvStream << "Cx,";
11759  csvStream << "Cy,";
11760  csvStream << "Cz,";
11761  csvStream << "Nx,";
11762  csvStream << "Ny,";
11763  csvStream << "Nz,";
11764  csvStream << "Dip,";
11765  csvStream << "Dip dir,";
11766  csvStream << QtCompat::endl;
11767 
11768  QChar separator(',');
11769 
11770  // write one line per plane
11771  for (ccHObject* ent : planes) {
11772  ccPlane* plane = static_cast<ccPlane*>(ent);
11773 
11774  CCVector3 C = plane->getOwnBB().getCenter();
11775  CCVector3 N = plane->getNormal();
11776  PointCoordinateType dip_deg = 0, dipDir_deg = 0;
11777  ccNormalVectors::ConvertNormalToDipAndDipDir(N, dip_deg, dipDir_deg);
11778 
11779  csvStream << plane->getName() << separator; // Name
11780  csvStream << plane->getXWidth() << separator; // Width
11781  csvStream << plane->getYWidth() << separator; // Height
11782  csvStream << C.x << separator; // Cx
11783  csvStream << C.y << separator; // Cy
11784  csvStream << C.z << separator; // Cz
11785  csvStream << N.x << separator; // Nx
11786  csvStream << N.y << separator; // Ny
11787  csvStream << N.z << separator; // Nz
11788  csvStream << dip_deg << separator; // Dip
11789  csvStream << dipDir_deg << separator; // Dip direction
11790  csvStream << QtCompat::endl;
11791  }
11792 
11793  ecvConsole::Print(tr("[I/O] File '%1' successfully saved (%2 plane(s))")
11794  .arg(outputFilename)
11795  .arg(planes.size()));
11796  csvFile.close();
11797 }
11798 
11799 void MainWindow::doActionExportCloudInfo() {
11800  // look for clouds
11801  ccHObject::Container clouds;
11802 
11803  const ccHObject::Container& selectedEntities = getSelectedEntities();
11804  if (selectedEntities.size() == 1 &&
11805  selectedEntities.front()->isA(CV_TYPES::HIERARCHY_OBJECT)) {
11806  // a group
11807  selectedEntities.front()->filterChildren(clouds, true,
11808  CV_TYPES::POINT_CLOUD, true);
11809  } else {
11810  for (ccHObject* entity : selectedEntities) {
11812  if (cloud) {
11813  clouds.push_back(cloud);
11814  }
11815  }
11816  }
11817 
11818  if (clouds.empty()) {
11819  ecvConsole::Error(tr("Select at least one point cloud!"));
11820  return;
11821  }
11822 
11823  // persistent settings
11824  QString currentPath =
11827  .toString();
11828 
11829  QString outputFilename = QFileDialog::getSaveFileName(
11830  this, tr("Select output file"), currentPath, "*.csv", nullptr,
11832  if (outputFilename.isEmpty()) {
11833  // process cancelled by the user
11834  return;
11835  }
11836 
11837  QFile csvFile(outputFilename);
11838  if (!csvFile.open(QFile::WriteOnly | QFile::Text)) {
11839  ecvConsole::Error(tr(
11840  "Failed to open file for writing! (check file permissions)"));
11841  return;
11842  }
11843 
11844  // save last saving location
11846  QFileInfo(outputFilename).absolutePath());
11847 
11848  // determine the maximum number of SFs
11849  unsigned maxSFCount = 0;
11850  for (ccHObject* entity : clouds) {
11851  maxSFCount = std::max<unsigned>(
11852  maxSFCount,
11853  static_cast<ccPointCloud*>(entity)->getNumberOfScalarFields());
11854  }
11855 
11856  // write CSV header
11857  QTextStream csvStream(&csvFile);
11858  csvStream << "Name,";
11859  csvStream << "Points,";
11860  csvStream << "meanX,";
11861  csvStream << "meanY,";
11862  csvStream << "meanZ,";
11863  {
11864  for (unsigned i = 0; i < maxSFCount; ++i) {
11865  QString sfIndex = QString("SF#%1").arg(i + 1);
11866  csvStream << sfIndex << " name,";
11867  csvStream << sfIndex << " valid values,";
11868  csvStream << sfIndex << " mean,";
11869  csvStream << sfIndex << " std.dev.,";
11870  csvStream << sfIndex << " sum,";
11871  }
11872  }
11873  csvStream << QtCompat::endl;
11874 
11875  // write one line per cloud
11876  {
11877  for (ccHObject* entity : clouds) {
11878  ccPointCloud* cloud = static_cast<ccPointCloud*>(entity);
11879 
11881  csvStream << cloud->getName() << "," /*"Name;"*/;
11882  csvStream << cloud->size() << "," /*"Points;"*/;
11883  csvStream << G.x << "," /*"meanX;"*/;
11884  csvStream << G.y << "," /*"meanY;"*/;
11885  csvStream << G.z << "," /*"meanZ;"*/;
11886  for (unsigned j = 0; j < cloud->getNumberOfScalarFields(); ++j) {
11887  cloudViewer::ScalarField* sf = cloud->getScalarField(j);
11888  csvStream << sf->getName() << "," /*"SF name;"*/;
11889 
11890  unsigned validCount = 0;
11891  double sfSum = 0.0;
11892  double sfSum2 = 0.0;
11893  for (unsigned k = 0; k < sf->currentSize(); ++k) {
11894  const ScalarType& val = sf->getValue(k);
11896  ++validCount;
11897  sfSum += val;
11898  sfSum2 += val * val;
11899  }
11900  }
11901  csvStream << validCount << "," /*"SF valid values;"*/;
11902  double mean = sfSum / validCount;
11903  csvStream << mean << "," /*"SF mean;"*/;
11904  csvStream << sqrt(std::abs(sfSum2 / validCount - mean * mean))
11905  << "," /*"SF std.dev.;"*/;
11906  csvStream << sfSum << "," /*"SF sum;"*/;
11907  }
11908  csvStream << QtCompat::endl;
11909  }
11910  }
11911 
11912  ecvConsole::Print(tr("[I/O] File '%1' successfully saved (%2 cloud(s))")
11913  .arg(outputFilename)
11914  .arg(clouds.size()));
11915  csvFile.close();
11916 }
11917 
11918 void MainWindow::doActionComputeCPS() {
11919  if (m_selectedEntities.size() != 2) {
11920  ecvConsole::Error(tr("Select 2 point clouds!"));
11921  return;
11922  }
11923 
11924  if (!m_selectedEntities[0]->isKindOf(CV_TYPES::POINT_CLOUD) ||
11925  !m_selectedEntities[1]->isKindOf(CV_TYPES::POINT_CLOUD)) {
11926  ecvConsole::Error(tr("Select 2 point clouds!"));
11927  return;
11928  }
11929 
11930  ccOrderChoiceDlg dlg(m_selectedEntities[0], tr("Compared"),
11931  m_selectedEntities[1], tr("Reference"), this);
11932  if (!dlg.exec()) return;
11933 
11934  ccGenericPointCloud* compCloud =
11935  ccHObjectCaster::ToGenericPointCloud(dlg.getFirstEntity());
11936  ccGenericPointCloud* srcCloud =
11937  ccHObjectCaster::ToGenericPointCloud(dlg.getSecondEntity());
11938 
11939  if (!compCloud->isA(CV_TYPES::POINT_CLOUD)) // TODO
11940  {
11941  ecvConsole::Error(tr("Compared cloud must be a real point cloud!"));
11942  return;
11943  }
11944  ccPointCloud* cmpPC = static_cast<ccPointCloud*>(compCloud);
11945 
11946  static const char DEFAULT_CPS_TEMP_SF_NAME[] = "CPS temporary";
11947  int sfIdx = cmpPC->getScalarFieldIndexByName(DEFAULT_CPS_TEMP_SF_NAME);
11948  if (sfIdx < 0) sfIdx = cmpPC->addScalarField(DEFAULT_CPS_TEMP_SF_NAME);
11949  if (sfIdx < 0) {
11951  tr("Couldn't allocate a new scalar field for computing "
11952  "distances! Try to free some memory ..."));
11953  return;
11954  }
11955  cmpPC->setCurrentScalarField(sfIdx);
11956  if (!cmpPC->enableScalarField()) {
11957  ecvConsole::Error(tr("Not enough memory!"));
11958  return;
11959  }
11960  // cmpPC->forEach(cloudViewer::ScalarFieldTools::SetScalarValueToNaN); //now
11961  // done by default by computeCloud2CloudDistances
11962 
11963  cloudViewer::ReferenceCloud CPSet(srcCloud);
11964  ecvProgressDialog pDlg(true, this);
11966  params;
11967  params.CPSet = &CPSet;
11968  int result =
11970  compCloud, srcCloud, params, &pDlg);
11971  cmpPC->deleteScalarField(sfIdx);
11972 
11973  if (result >= 0) {
11974  ccPointCloud* newCloud = nullptr;
11975  // if the source cloud is a "true" cloud, the extracted CPS
11976  // will also get its attributes
11977  newCloud = srcCloud->isA(CV_TYPES::POINT_CLOUD)
11978  ? static_cast<ccPointCloud*>(srcCloud)->partialClone(
11979  &CPSet)
11980  : ccPointCloud::From(&CPSet, srcCloud);
11981 
11982  newCloud->setName(
11983  QString("[%1]->CPSet(%2)")
11984  .arg(srcCloud->getName(), compCloud->getName()));
11985  addToDB(newCloud);
11986 
11987  // we hide the source cloud (for a clearer display)
11988  srcCloud->setEnabled(false);
11989  }
11990 }
11991 
11992 void MainWindow::doActionFitSphere() {
11993  double outliersRatio = 0.5;
11994  double confidence = 0.99;
11995 
11996  ecvProgressDialog pDlg(true, this);
11997  pDlg.setAutoClose(false);
11998 
11999  for (ccHObject* entity : getSelectedEntities()) {
12001  if (!cloud) continue;
12002 
12003  CCVector3 center;
12004  PointCoordinateType radius;
12005  double rms;
12007  cloud, outliersRatio, center, radius, rms, &pDlg,
12008  confidence) !=
12011  tr("[Fit sphere] Failed to fit a sphere on cloud '%1'")
12012  .arg(cloud->getName()));
12013  continue;
12014  }
12015 
12016  CVLog::Print(tr("[Fit sphere] Cloud '%1': center (%2,%3,%4) - radius = "
12017  "%5 [RMS = %6]")
12018  .arg(cloud->getName())
12019  .arg(center.x)
12020  .arg(center.y)
12021  .arg(center.z)
12022  .arg(radius)
12023  .arg(rms));
12024 
12025  ccGLMatrix trans;
12026  trans.setTranslation(center);
12027  ccSphere* sphere =
12028  new ccSphere(radius, &trans,
12029  tr("Sphere r=%1 [rms %2]").arg(radius).arg(rms));
12030  cloud->addChild(sphere);
12031  sphere->setOpacity(GLOBAL_OPACITY);
12032  addToDB(sphere, false, false, false);
12033  }
12034 }
12035 
12036 void MainWindow::doActionFitCircle() {
12037  ecvProgressDialog pDlg(true, this);
12038  pDlg.setAutoClose(false);
12039 
12041  for (ccHObject* entity : getSelectedEntities()) {
12043  if (!cloud) continue;
12044 
12045  CCVector3 center;
12046  CCVector3 normal;
12047  PointCoordinateType radius = 0;
12048  double rms = std::numeric_limits<double>::quiet_NaN();
12050  cloud, center, normal, radius, rms, &pDlg) !=
12053  tr("[Fit circle] Failed to fit a circle on cloud '%1'")
12054  .arg(cloud->getName()));
12055  continue;
12056  }
12057 
12058  CVLog::Print(tr("[Fit circle] Cloud '%1': center (%2,%3,%4) - radius = "
12059  "%5 [RMS = %6]")
12060  .arg(cloud->getName())
12061  .arg(center.x)
12062  .arg(center.y)
12063  .arg(center.z)
12064  .arg(radius)
12065  .arg(rms));
12066 
12067  CVLog::Print(tr("[Fit circle] Normal (%1,%2,%3)")
12068  .arg(normal.x)
12069  .arg(normal.y)
12070  .arg(normal.z));
12071 
12072  // create the circle representation as a polyline
12073  ccCircle* circle = new ccCircle(radius, 128);
12074  if (circle) {
12075  circle->setName(QObject::tr("Circle r=%1").arg(radius));
12076  cloud->addChild(circle);
12077  circle->copyGlobalShiftAndScale(*cloud);
12078  circle->setMetaData("RMS", rms);
12079 
12080  ccGLMatrix trans =
12082  trans.setTranslation(center);
12083  circle->applyGLTransformation_recursive(&trans);
12084  circle->setRedrawFlagRecursive(true);
12085 
12086  addToDB(circle, false, false, false);
12087  }
12088  }
12089 
12090  refreshAll();
12091 }
12092 
12093 void MainWindow::doActionFitPlane() { doComputePlaneOrientation(false); }
12094 
12095 void MainWindow::doComputePlaneOrientation(bool fitFacet) {
12096  if (!haveSelection()) return;
12097 
12098  double maxEdgeLength = 0.0;
12099  if (fitFacet) {
12100  bool ok = true;
12101  static double s_polygonMaxEdgeLength = 0.0;
12102  maxEdgeLength = QInputDialog::getDouble(
12103  this, tr("Fit facet"), tr("Max edge length (0 = no limit)"),
12104  s_polygonMaxEdgeLength, 0, 1.0e9, 8, &ok);
12105  if (!ok) return;
12106  s_polygonMaxEdgeLength = maxEdgeLength;
12107  }
12108 
12109  ccHObject::Container selectedEntities =
12110  getSelectedEntities(); // warning, getSelectedEntites may change
12111  // during this loop!
12112  bool firstEntity = true;
12113 
12114  for (ccHObject* entity : selectedEntities) {
12115  ccShiftedObject* shifted = nullptr;
12116  cloudViewer::GenericIndexedCloudPersist* cloud = nullptr;
12117 
12118  if (entity->isKindOf(CV_TYPES::POLY_LINE)) {
12119  ccPolyline* poly = ccHObjectCaster::ToPolyline(entity);
12120  cloud = static_cast<cloudViewer::GenericIndexedCloudPersist*>(poly);
12121  shifted = poly;
12122  } else {
12123  ccGenericPointCloud* gencloud =
12125  if (gencloud) {
12126  cloud = static_cast<cloudViewer::GenericIndexedCloudPersist*>(
12127  gencloud);
12128  shifted = gencloud;
12129  }
12130  }
12131 
12132  if (cloud) {
12133  double rms = 0.0;
12134  CCVector3 C, N;
12135 
12136  ccHObject* plane = nullptr;
12137  if (fitFacet) {
12138  ccFacet* facet = ccFacet::Create(
12139  cloud, static_cast<PointCoordinateType>(maxEdgeLength));
12140  if (facet) {
12143  facet->getContour()->setColor(ecvColor::green);
12144  facet->getContour()->showColors(true);
12145 
12146  plane = static_cast<ccHObject*>(facet);
12147  N = facet->getNormal();
12148  C = facet->getCenter();
12149  rms = facet->getRMS();
12150 
12151  // manually copy shift & scale info!
12152  if (shifted) {
12153  ccPolyline* contour = facet->getContour();
12154  if (contour) {
12155  contour->setGlobalScale(shifted->getGlobalScale());
12156  contour->setGlobalShift(shifted->getGlobalShift());
12157  }
12158  }
12159  }
12160  } else {
12161  ccPlane* pPlane = ccPlane::Fit(cloud, &rms);
12162  if (pPlane) {
12163  plane = static_cast<ccHObject*>(pPlane);
12164  N = pPlane->getNormal();
12166  pPlane->enableStippling(true);
12167  }
12168  }
12169 
12170  // as all information appears in Console...
12172 
12173  if (plane) {
12175  tr("[Orientation] Entity '%1'").arg(entity->getName()));
12176  ecvConsole::Print(tr("\t- plane fitting RMS: %1").arg(rms));
12177 
12178  // We always consider the normal with a positive 'Z' by default!
12179  if (N.z < 0.0) N *= -1.0;
12180  ecvConsole::Print(tr("\t- normal: (%1,%2,%3)")
12181  .arg(N.x)
12182  .arg(N.y)
12183  .arg(N.z));
12184 
12185  // we compute strike & dip by the way
12186  PointCoordinateType dip = 0.0f;
12187  PointCoordinateType dipDir = 0.0f;
12189  QString dipAndDipDirStr =
12191  dipDir);
12192  ecvConsole::Print(QString("\t- %1").arg(dipAndDipDirStr));
12193 
12194  // hack: output the transformation matrix that would make this
12195  // normal points towards +Z
12196  ccGLMatrix makeZPosMatrix =
12198  CCVector3 Gt = C;
12199  makeZPosMatrix.applyRotation(Gt);
12200  makeZPosMatrix.setTranslation(C - Gt);
12202  tr("[Orientation] A matrix that would make this plane "
12203  "horizontal (normal towards Z+) is:"));
12205  makeZPosMatrix.toString(12, ' ')); // full precision
12207  tr("[Orientation] You can copy this matrix values "
12208  "(CTRL+C) and paste them in the 'Apply "
12209  "transformation tool' dialog"));
12210 
12211  plane->setName(dipAndDipDirStr);
12212  plane->applyGLTransformation_recursive(); // not yet in DB
12213  plane->setVisible(true);
12215 
12216  entity->addChild(plane);
12218  plane->setOpacity(GLOBAL_OPACITY);
12219  addToDB(plane);
12220 
12221  if (firstEntity) {
12222  m_ccRoot->unselectAllEntities();
12223  m_ccRoot->selectEntity(plane);
12224  }
12225  } else {
12227  tr("Failed to fit a plane/facet on entity '%1'")
12228  .arg(entity->getName()));
12229  }
12230  }
12231  }
12232 
12233  updateUI();
12234 }
12235 
12236 void MainWindow::doActionFitFacet() { doComputePlaneOrientation(true); }
12237 
12238 void MainWindow::doActionFitQuadric() {
12239  bool errors = false;
12240 
12241  // for all selected entities
12242  for (ccHObject* entity : getSelectedEntities()) {
12243  // look for clouds
12244  if (entity->isKindOf(CV_TYPES::POINT_CLOUD)) {
12245  ccGenericPointCloud* cloud =
12247 
12248  double rms = 0.0;
12249  ccQuadric* quadric = ccQuadric::Fit(cloud, &rms);
12250  if (quadric) {
12251  cloud->addChild(quadric);
12252  quadric->setName(tr("Quadric (%1)").arg(cloud->getName()));
12253  quadric->setOpacity(GLOBAL_OPACITY);
12254  addToDB(quadric);
12255 
12257  tr("[doActionFitQuadric] Quadric local coordinate "
12258  "system:"));
12260  12, ' ')); // full precision
12261  ecvConsole::Print(tr("[doActionFitQuadric] Quadric equation "
12262  "(in local coordinate system): ") +
12263  quadric->getEquationString());
12264  ecvConsole::Print(tr("[doActionFitQuadric] RMS: %1").arg(rms));
12265 
12266 #if 0
12267  //test: project the input cloud on the quadric
12268  if (cloud->isA(CV_TYPES::POINT_CLOUD))
12269  {
12270  ccPointCloud* newCloud = static_cast<ccPointCloud*>(cloud)->cloneThis();
12271  if (newCloud)
12272  {
12273  const PointCoordinateType* eq = quadric->getEquationCoefs();
12274  const Tuple3ub& dims = quadric->getEquationDims();
12275 
12276  const unsigned char dX = dims.x;
12277  const unsigned char dY = dims.y;
12278  const unsigned char dZ = dims.z;
12279 
12280  const ccGLMatrix& trans = quadric->getTransformation();
12281  ccGLMatrix invTrans = trans.inverse();
12282  for (unsigned i = 0; i < newCloud->size(); ++i)
12283  {
12284  CCVector3* P = const_cast<CCVector3*>(newCloud->getPoint(i));
12285  CCVector3 Q = invTrans * (*P);
12286  Q.u[dZ] = eq[0] + eq[1] * Q.u[dX] + eq[2] * Q.u[dY] + eq[3] * Q.u[dX] * Q.u[dX] + eq[4] * Q.u[dX] * Q.u[dY] + eq[5] * Q.u[dY] * Q.u[dY];
12287  *P = trans * Q;
12288  }
12289  newCloud->invalidateBoundingBox();
12290  newCloud->setName(newCloud->getName() + ".projection_on_quadric");
12291  addToDB(newCloud);
12292  }
12293  }
12294 #endif
12295  } else {
12297  tr("Failed to compute quadric on cloud '%1'")
12298  .arg(cloud->getName()));
12299  errors = true;
12300  }
12301  }
12302  }
12303 
12304  if (errors) {
12305  ecvConsole::Error(tr("Error(s) occurred: see console"));
12306  }
12307 }
12308 
12309 void MainWindow::doActionUnroll() {
12310  // there should be only one point cloud with sensor in current selection!
12311  if (!haveOneSelection()) {
12312  ecvConsole::Error(tr("Select one and only one entity!"));
12313  return;
12314  }
12315 
12316  // if selected entity is a mesh, the method will be applied to its vertices
12317  bool lockedVertices;
12319  m_selectedEntities[0], &lockedVertices);
12320  if (lockedVertices) {
12321  ecvUtils::DisplayLockedVerticesWarning(m_selectedEntities[0]->getName(),
12322  true);
12323  return;
12324  }
12325 
12326  // for "real" point clouds only
12327  if (!cloud || !cloud->isA(CV_TYPES::POINT_CLOUD)) {
12329  tr("Method can't be applied on locked vertices or virtual "
12330  "point clouds!"));
12331  return;
12332  }
12333  ccPointCloud* pc = static_cast<ccPointCloud*>(cloud);
12334 
12335  ccUnrollDlg unrollDlg(this);
12336  unrollDlg.fromPersistentSettings();
12337  if (!unrollDlg.exec()) return;
12338  unrollDlg.toPersistentSettings();
12339 
12340  ccPointCloud::UnrollMode mode = unrollDlg.getType();
12341  PointCoordinateType radius =
12342  static_cast<PointCoordinateType>(unrollDlg.getRadius());
12343  unsigned char dim =
12344  static_cast<unsigned char>(unrollDlg.getAxisDimension());
12345  bool exportDeviationSF = unrollDlg.exportDeviationSF();
12346  CCVector3 center = unrollDlg.getAxisPosition();
12347 
12348  // let's rock unroll ;)
12349  ecvProgressDialog pDlg(true, this);
12350 
12351  double startAngle_deg = 0.0;
12352  double stopAngle_deg = 360.0;
12353  unrollDlg.getAngleRange(startAngle_deg, stopAngle_deg);
12354  if (startAngle_deg >= stopAngle_deg) {
12355  QMessageBox::critical(this, "Error", "Invalid angular range");
12356  return;
12357  }
12358 
12359  ccPointCloud* output = nullptr;
12360  switch (mode) {
12361  case ccPointCloud::CYLINDER: {
12363  params.radius = radius;
12364  params.axisDim = dim;
12365  if (unrollDlg.isAxisPositionAuto()) {
12366  center = pc->getOwnBB().getCenter();
12367  }
12368  params.center = center;
12369  output = pc->unroll(mode, &params, exportDeviationSF,
12370  startAngle_deg, stopAngle_deg, &pDlg);
12371  } break;
12372 
12373  case ccPointCloud::CONE:
12377  params.radius = (mode == ccPointCloud::CONE ? 0 : radius);
12378  params.apex = center;
12379  params.coneAngle_deg = unrollDlg.getConeHalfAngle();
12380  params.axisDim = dim;
12381  output = pc->unroll(mode, &params, exportDeviationSF,
12382  startAngle_deg, stopAngle_deg, &pDlg);
12383  } break;
12384 
12385  default:
12386  assert(false);
12387  break;
12388  }
12389 
12390  if (output) {
12391  if (m_selectedEntities[0]->isA(CV_TYPES::MESH)) {
12392  ccMesh* mesh = ccHObjectCaster::ToMesh(m_selectedEntities[0]);
12393  mesh->setEnabled(false);
12395  "[Unroll] Original mesh has been automatically hidden");
12396  ccMesh* outputMesh = mesh->cloneMesh(output);
12397  outputMesh->addChild(output);
12398  outputMesh->setEnabled(true);
12399  outputMesh->setVisible(true);
12400  addToDB(outputMesh, true, true, false, true);
12401  } else {
12402  pc->setEnabled(false);
12404  "[Unroll] Original cloud has been automatically hidden");
12405  if (pc->getParent()) {
12406  pc->getParent()->addChild(output);
12407  }
12408  addToDB(output, true, true, false, true);
12409  }
12410  updateUI();
12411  }
12412 }
12413 
12414 void MainWindow::doComputeGeometricFeature() {
12415  static ccLibAlgorithms::GeomCharacteristicSet s_selectedCharacteristics;
12416  static CCVector3 s_upDir(0, 0, 1);
12417  static bool s_upDirDefined = false;
12418 
12419  ccGeomFeaturesDlg gfDlg(this);
12420  double radius =
12421  ccLibAlgorithms::GetDefaultCloudKernelSize(m_selectedEntities);
12422  gfDlg.setRadius(radius);
12423 
12424  // restore semi-persistent parameters
12425  gfDlg.setSelectedFeatures(s_selectedCharacteristics);
12426  if (s_upDirDefined) {
12427  gfDlg.setUpDirection(s_upDir);
12428  }
12429 
12430  if (!gfDlg.exec()) return;
12431 
12432  radius = gfDlg.getRadius();
12433  if (!gfDlg.getSelectedFeatures(s_selectedCharacteristics)) {
12434  CVLog::Error(tr("Not enough memory"));
12435  return;
12436  }
12437 
12438  CCVector3* upDir = gfDlg.getUpDirection();
12439 
12440  // remember semi-persistent parameters
12441  s_upDirDefined = (upDir != nullptr);
12442  if (s_upDirDefined) {
12443  s_upDir = *upDir;
12444  }
12445 
12447  s_selectedCharacteristics, static_cast<PointCoordinateType>(radius),
12448  m_selectedEntities, upDir, this);
12449 
12450  refreshSelected();
12451  updateUI();
12452 }
12453 
12454 // Helper function to recursively collect functional actions from a menu
12455 // Only collects leaf actions (actions without submenus) that are not excluded
12456 static void collectActionsFromMenu(QMenu* menu,
12457  QList<QAction*>& actions,
12458  QSet<QAction*>& collected,
12459  const QSet<QMenu*>& excludedMenus) {
12460  if (!menu || excludedMenus.contains(menu)) {
12461  return;
12462  }
12463 
12464  for (QAction* action : menu->actions()) {
12465  if (!action) {
12466  continue;
12467  }
12468 
12469  // Skip separators
12470  if (action->isSeparator()) {
12471  continue;
12472  }
12473 
12474  // Skip actions without text (not user-visible)
12475  if (action->text().isEmpty()) {
12476  continue;
12477  }
12478 
12479  // Skip if already collected (avoid duplicates)
12480  if (collected.contains(action)) {
12481  continue;
12482  }
12483 
12484  // Skip toolbar toggle actions by objectName pattern
12485  // All toolbar toggle actions have objectName starting with
12486  // "actionDisplay"
12487  if (!action->objectName().isEmpty() &&
12488  action->objectName().startsWith("actionDisplay")) {
12489  continue;
12490  }
12491 
12492  // If action has a submenu, recursively process it
12493  QMenu* submenu = action->menu();
12494  if (submenu) {
12495  collectActionsFromMenu(submenu, actions, collected, excludedMenus);
12496  continue; // Don't add menu items themselves
12497  }
12498 
12499  // This is a functional action (leaf node), add it
12500  actions.append(action);
12501  collected.insert(action);
12502  }
12503 }
12504 
12505 void MainWindow::populateActionList() {
12506  m_actions.clear();
12507 
12508  // Build set of excluded menus (dynamic menus that shouldn't have shortcuts)
12509  QSet<QMenu*> excludedMenus;
12510 
12511  // Exclude recent files menu
12512  if (m_recentFiles) {
12513  QMenu* recentFilesMenu = m_recentFiles->menu();
12514  if (recentFilesMenu) {
12515  excludedMenus.insert(recentFilesMenu);
12516  }
12517  }
12518 
12519  // Exclude 3D mouse manager menu
12520 #ifdef CC_3DXWARE_SUPPORT
12521  if (m_3DMouseManager) {
12522  QMenu* mouseMenu = m_3DMouseManager->menu();
12523  if (mouseMenu) {
12524  excludedMenus.insert(mouseMenu);
12525  }
12526  }
12527 #endif
12528 
12529  // Exclude gamepad manager menu
12530 #ifdef CC_GAMEPAD_SUPPORT
12531  if (m_gamepadManager) {
12532  QMenu* gamepadMenu = m_gamepadManager->menu();
12533  if (gamepadMenu) {
12534  excludedMenus.insert(gamepadMenu);
12535  }
12536  }
12537 #endif
12538 
12539  // Exclude toolbar menu (contains toolbar toggle actions)
12540  excludedMenus.insert(m_ui->menuToolbars);
12541 
12542  // Track collected actions to avoid duplicates
12543  QSet<QAction*> collected;
12544 
12545  // Collect actions from menuBar menus (efficient: only traverse menu
12546  // structure)
12547  for (QAction* menuBarAction : m_ui->menuBar->actions()) {
12548  QMenu* menu = menuBarAction->menu();
12549  if (menu) {
12550  collectActionsFromMenu(menu, m_actions, collected, excludedMenus);
12551  }
12552  }
12553 
12554  // Also collect actions from toolbars (for toolbar actions not in menus)
12555  for (QToolBar* toolbar : findChildren<QToolBar*>()) {
12556  // Skip plugin toolbars (they contain plugin actions)
12557  QString toolbarName = toolbar->objectName();
12558  if (toolbarName.contains("Plugin", Qt::CaseInsensitive) ||
12559  toolbarName.contains("PCL", Qt::CaseInsensitive)) {
12560  continue;
12561  }
12562 
12563  for (QAction* action : toolbar->actions()) {
12564  if (!action || action->isSeparator() || action->text().isEmpty()) {
12565  continue;
12566  }
12567 
12568  // Skip if already collected
12569  if (collected.contains(action)) {
12570  continue;
12571  }
12572 
12573  // Skip toolbar toggle actions
12574  if (!action->objectName().isEmpty() &&
12575  action->objectName().startsWith("actionDisplay")) {
12576  continue;
12577  }
12578 
12579  // Skip actions with submenus
12580  if (action->menu()) {
12581  continue;
12582  }
12583 
12584  m_actions.append(action);
12585  collected.insert(action);
12586  }
12587  }
12588 }
12589 
12590 void MainWindow::showShortcutDialog() {
12591  if (m_shortcutDlg) {
12592  m_shortcutDlg->exec();
12593  }
12594 }
MouseEvent event
constexpr unsigned char POINT_VISIBLE
Definition: CVConst.h:92
constexpr PointCoordinateType PC_ONE
'1' as a PointCoordinateType value
Definition: CVConst.h:67
constexpr unsigned char POINT_HIDDEN
Definition: CVConst.h:94
constexpr double M_PI
Pi.
Definition: CVConst.h:19
constexpr unsigned char POINT_OUT_OF_FOV
Definition: CVConst.h:98
CC_VIEW_ORIENTATION
View orientation.
Definition: CVConst.h:102
@ CC_TOP_VIEW
Definition: CVConst.h:103
constexpr ScalarType NAN_VALUE
NaN as a ScalarType value.
Definition: CVConst.h:76
constexpr unsigned char POINT_OUT_OF_RANGE
Definition: CVConst.h:96
Vector2Tpl< PointCoordinateType > CCVector2
Default 2D Vector.
Definition: CVGeom.h:780
Vector3Tpl< double > CCVector3d
Double 3D Vector.
Definition: CVGeom.h:804
Vector3Tpl< PointCoordinateType > CCVector3
Default 3D Vector.
Definition: CVGeom.h:798
float PointCoordinateType
Type of the coordinates of a (N-D) point.
Definition: CVTypes.h:16
int64_t CV_CLASS_ENUM
Type of object type flags (64 bits)
Definition: CVTypes.h:97
std::string filename
double normal[3]
int width
int size
int height
int count
int points
char type
CC_FILE_ERROR
Typical I/O filter errors.
Definition: FileIOFilter.h:20
@ CC_FERR_CANCELED_BY_USER
Definition: FileIOFilter.h:30
@ CC_FERR_NO_SAVE
Definition: FileIOFilter.h:27
@ CC_FERR_NO_ERROR
Definition: FileIOFilter.h:21
static QFileDialog::Options ECVFileDialogOptions()
Definition: MainWindow.cpp:235
static int s_innerRectDim
static std::vector< cc2DLabel * > s_levelLabels
Definition: MainWindow.cpp:231
PickingOperation
Definition: MainWindow.cpp:225
@ PICKING_ROTATION_CENTER
Definition: MainWindow.cpp:227
@ PICKING_LEVEL_POINTS
Definition: MainWindow.cpp:228
@ NO_PICKING_OPERATION
Definition: MainWindow.cpp:226
static const QString s_fileFilterSeparator(";;")
static const float GLOBAL_OPACITY
Definition: MainWindow.cpp:223
static double s_constantSFValue
static const QString s_allFilesFilter("All (*.*)")
void AddToRemoveList(ccHObject *toRemove, ccHObject::Container &toBeRemovedList)
static double s_msRmsDiff
static ccLibAlgorithms::ScaleMatchingAlgorithm s_msAlgorithm
std::pair< ccHObject *, ccGenericPointCloud * > EntityCloudAssociation
static PickingOperation s_currentPickingOperation
Definition: MainWindow.cpp:230
static ccPointCloud * s_levelMarkersCloud
Definition: MainWindow.cpp:232
static void collectActionsFromMenu(QMenu *menu, QList< QAction * > &actions, QSet< QAction * > &collected, const QSet< QMenu * > &excludedMenus)
static double s_kdTreeMaxErrorPerCell
static MainWindow * s_instance
Definition: MainWindow.cpp:216
static double s_subdivideMaxArea
static unsigned s_viewportIndex
static int s_msFinalOverlap
static ccHObject * s_levelEntity
Definition: MainWindow.cpp:233
static bool IsValidFileName(QString filename)
Definition: MainWindow.cpp:246
const int CLOUDVIEWER_LANG_CHINESE
Definition: MainWindow.h:54
const int CLOUDVIEWER_LANG_ENGLISH
Definition: MainWindow.h:53
#define ParallelSort
Definition: ParallelSort.h:33
QRegularExpression QtCompatRegExp
Definition: QtCompat.h:170
void * X
Definition: SmallVector.cpp:45
cmdLineReadable * params[]
void * bytes
core::Tensor result
Definition: VtkUtils.cpp:76
ASCII point cloud I/O filter.
Definition: AsciiFilter.h:21
CC_FILE_ERROR loadAsciiData(const QByteArray &data, QString sourceName, ccHObject &container, LoadParameters &parameters)
Loads a cloud from a QByteArray.
static QString GetFileFilter()
Definition: AsciiFilter.h:26
static short GetLastSavedFileVersion()
Returns the last saved file version.
static QString GetFileFilter()
Definition: BinFilter.h:20
virtual void release()
Decrease counter and deletes object when 0.
Definition: CVShareable.cpp:35
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 void SetVerbosityLevel(int level)
Sets the verbosity level.
Definition: CVLog.cpp:59
@ LOG_IMPORTANT
Definition: CVLog.h:45
@ LOG_STANDARD
Definition: CVLog.h:44
@ LOG_VERBOSE
Definition: CVLog.h:43
@ LOG_WARNING
Definition: CVLog.h:46
static bool Error(const char *format,...)
Display an error dialog with formatted message.
Definition: CVLog.cpp:143
static std::string FromQString(const QString &qs)
Definition: CVTools.cpp:100
static bool QMappingReader(const std::string &filename, std::vector< size_t > &indices)
Definition: CVTools.cpp:280
Depth map I/O filter.
virtual CC_FILE_ERROR saveToFile(ccHObject *entity, const QString &filename, const SaveParameters &parameters) override
Saves an entity (or a group of) to a file.
static QString GetFileFilter()
static CC_FILE_ERROR SaveToFile(ccHObject *entities, const QString &filename, const SaveParameters &parameters, Shared filter)
static void ResetSesionCounter()
static const FilterContainer & GetFilters()
Returns the set of all registered filters.
QSharedPointer< FileIOFilter > Shared
Shared type.
Definition: FileIOFilter.h:97
static void DisplayErrorMessage(CC_FILE_ERROR err, const QString &action, const QString &filename)
Displays (to console) the message corresponding to a given error code.
static ccHObject * LoadFromFile(const QString &filename, LoadParameters &parameters, Shared filter, CC_FILE_ERROR &result)
Loads one or more entities from a file with a known filter.
Jacobi eigen vectors/values decomposition.
Definition: Jacobi.h:23
static bool GetEigenVector(const SquareMatrix &eigenVectors, unsigned index, Scalar eigenVector[])
Returns the given eigenvector.
Definition: Jacobi.h:364
static bool SortEigenValuesAndVectors(SquareMatrix &eigenVectors, EigenValues &eigenValues)
Definition: Jacobi.h:333
QMenu * createPopupMenu() override
Override to add custom actions to right-click menu on toolbars.
Definition: MainWindow.cpp:628
static QWidget * GetRenderWindow(const QString &title)
Returns a given GL sub-window (determined by its title)
QWidget * getWindow(int index) const
void enablePickingOperation(QString message)
ccHObject * dbRootObject() override
Returns DB root (as a ccHObject)
ccHObjectContext removeObjectTemporarilyFromDBTree(ccHObject *obj) override
Removes object temporarily from DB tree.
void increasePointSize() override
QMdiSubWindow * getMDISubWindow(QWidget *win)
Returns MDI area subwindow corresponding to a given 3D view.
void setOrthoView()
void decreasePointSize() override
int getRenderWindowCount() const
Returns the number of 3D views.
~MainWindow() override
Definition: MainWindow.cpp:545
const ccHObject::Container & getSelectedEntities() const override
Returns currently selected entities ("read only")
Definition: MainWindow.h:238
void freezeUI(bool state) override
Freezes/unfreezes UI.
static MainWindow * TheInstance()
Returns the unique instance of this object.
void cancelPreviousPickingOperation(bool aborted)
void setUiManager(QUIWidget *uiManager)
void refreshObjects(ccHObject::Container objs, bool only2D=false, bool forceRedraw=true) override
void update3DViewsMenu()
void registerOverlayDialog(ccOverlayDialog *dlg, Qt::Corner pos) override
Registers a MDI area 'overlay' dialog.
void addToDB(const QStringList &filenames, QString fileFilter=QString(), bool displayDialog=true)
void dispToConsole(QString message, ConsoleMessageLevel level=STD_CONSOLE_MESSAGE) override
void updateOverlayDialogsPlacement() override
Forces the update of all registered MDI 'overlay' dialogs.
static void GetRenderWindows(std::vector< QWidget * > &windows)
Returns all GL sub-windows.
void doActionSaveViewportAsCamera()
void addToDBAuto(const QStringList &filenames, bool displayDialog=true)
static QWidget * GetActiveRenderWindow()
Static shortcut to MainWindow::getActiveWindow.
void setAutoPickPivot(bool state)
ccUniqueIDGenerator::Shared getUniqueIDGenerator() override
Returns the unique ID generator.
static bool s_autoSaveGuiElementPos
Definition: MainWindow.h:207
static void UpdateUI()
Static shortcut to MainWindow::updateUI.
void setGlobalZoom() override
void zoomOnEntities(ccHObject *obj) override
void refreshObject(ccHObject *obj, bool only2D=false, bool forceRedraw=true) override
void doActionOrthogonalProjection()
void removeFromDB(ccHObject *obj, bool autoDelete=true) override
Removes an entity from main db tree.
void onItemPicked(const PickedItem &pi) override
Inherited from ccPickingListener.
ccColorScalesManager * getColorScalesManager() override
Returns color scale manager (unique instance)
void spawnHistogramDialog(const std::vector< unsigned > &histoValues, double minVal, double maxVal, QString title, QString xAxisLabel) override
void addWidgetToQMdiArea(QWidget *widget) override
void refreshAll(bool only2D=false, bool forceRedraw=true) override
Redraws all GL windows that have the 'refresh' flag on.
void toggleExclusiveFullScreen(bool state) override
ccHObject * loadFile(QString filename, bool silent) override
Attempts to load a file.
void toggle3DView(bool state) override
void refreshSelected(bool only2D=false, bool forceRedraw=true) override
void forceConsoleDisplay() override
Forces display of console widget.
void doActionPerspectiveProjection()
void updateFullScreenMenu(bool state)
void updateUI() override
bool m_FirstShow
Flag: first time the window is made visible.
Definition: MainWindow.h:206
void unregisterOverlayDialog(ccOverlayDialog *dlg) override
Unregisters a MDI area 'overlay' dialog.
ccBBox getSelectedEntityBbox()
void updateViewModePopUpMenu()
static void ChangeStyle(const QString &qssFile)
QWidget * getActiveWindow() override
void resetSelectedBBox() override
void initPlugins()
Sets up the UI (menus and toolbars) based on loaded plugins.
void putObjectBackIntoDBTree(ccHObject *obj, const ccHObjectContext &context) override
Adds back object to DB tree.
void zoomOnSelectedEntities() override
void enableAll() override
Enables all windows.
void setView(CC_VIEW_ORIENTATION view) override
void updatePropertiesView()
Updates the 'Properties' view.
void setPerspectiveView()
static void DestroyInstance()
Deletes current main window instance.
void disableAll() override
Disables all windows.
void saveGUIElementsPos()
Saves position and state of all GUI elements.
void setSelectedInDB(ccHObject *obj, bool selected) override
Selects or unselects an entity (in db tree)
void toggleFullScreen(bool state)
toggles full screen
Type y
Definition: CVGeom.h:137
Type u[3]
Definition: CVGeom.h:139
Type x
Definition: CVGeom.h:137
Type z
Definition: CVGeom.h:137
void normalize()
Sets vector norm to unity.
Definition: CVGeom.h:428
Type dot(const Vector3Tpl &v) const
Dot product.
Definition: CVGeom.h:408
Type norm2() const
Returns vector square norm.
Definition: CVGeom.h:417
Type norm() const
Returns vector norm.
Definition: CVGeom.h:424
Vector3Tpl cross(const Vector3Tpl &v) const
Cross product.
Definition: CVGeom.h:412
static Vector3Tpl fromArray(const int a[3])
Constructor from an int array.
Definition: CVGeom.h:268
Type angle_rad(const Vector3Tpl &v) const
Returns the angle to another vector (in radians - in [0, pi].
Definition: CVGeom.h:497
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.
const PickedPoint & getPickedPoint(unsigned index) const
Returns a given point.
Definition: ecv2DLabel.h:194
void setDisplayedIn2D(bool state)
Whether to display the label in 2D.
Definition: ecv2DLabel.h:114
unsigned size() const
Returns current size.
Definition: ecv2DLabel.h:74
virtual QString getName() const override
Returns object name.
2D viewport object
void setParameters(const ecvViewportParameters &params)
Sets perspective view state.
Rough registration dialog.
Definition: ecvAlignDlg.h:25
Dialog to input a 4x4 matrix.
Generic dialog to query 3 (double) values.
Bounding box structure.
Definition: ecvBBox.h:25
Box (primitive)
Definition: ecvBox.h:16
Camera sensor parameters dialog.
Camera (projective) sensor.
const LensDistortionParameters::Shared & getDistortionParameters() const
Returns uncertainty parameters.
bool computeUncertainty(const CCVector2 &pixel, const float depth, Vector3Tpl< ScalarType > &sigma) const
Circle (as a polyline)
Definition: ecvCircle.h:16
ccCircle * clone() const
Clones this circle.
double getRadius() const
Returns the radius of the circle.
Definition: ecvCircle.h:50
Dialog to change the color levels.
Dialog to edit/create color scales.
QSharedPointer< ccColorScale > Shared
Shared pointer type.
Definition: ecvColorScale.h:74
Color scales manager/container.
static ccColorScale::Shared GetDefaultScale(DEFAULT_SCALES scale=BGYR)
Returns a pre-defined color scale (static shortcut)
void toPersistentSettings() const
Save custom color scales to persistent settings.
static ccColorScalesManager * GetUniqueInstance()
Returns unique instance.
Dialog for cloud/cloud or cloud/mesh comparison setting.
ccHObject * getComparedEntity() const
Returns compared entity.
Cone (primitive)
Definition: ecvCone.h:16
Cylinder (primitive)
Definition: ecvCylinder.h:16
GUI database tree root.
Definition: ecvDBRoot.h:65
ccPropertiesTreeDelegate * getPropertiesDelegate()
Get properties tree delegate.
Definition: ecvDBRoot.h:93
void updatePropertiesView()
Updates properties view.
Definition: ecvDBRoot.cpp:1225
void dbIsNotEmptyAnymore()
int countSelectedEntities(CV_CLASS_ENUM filter=CV_TYPES::OBJECT)
Definition: ecvDBRoot.cpp:1256
void unselectEntity(ccHObject *obj)
Unselects a given entity.
Definition: ecvDBRoot.cpp:1035
void selectEntity(ccHObject *obj, bool forceAdditiveSelection=false)
Selects a given entity.
Definition: ecvDBRoot.cpp:1054
void selectEntities(std::unordered_set< int > entIDs)
Selects multiple entities at once (shortcut to the other version)
Definition: ecvDBRoot.cpp:1109
ccHObject * getRootEntity()
Returns associated root object.
Definition: ecvDBRoot.cpp:421
size_t getSelectedEntities(ccHObject::Container &selectedEntities, CV_CLASS_ENUM filter=CV_TYPES::OBJECT, dbTreeSelectionInfo *info=nullptr)
Definition: ecvDBRoot.cpp:1273
void unselectAllEntities()
Unselects all entities.
Definition: ecvDBRoot.cpp:1047
void unloadAll()
Unloads all entities.
Definition: ecvDBRoot.cpp:398
void removeElement(ccHObject *object)
Removes an element from the DB tree.
Definition: ecvDBRoot.cpp:554
void dbIsEmpty()
void deleteSelectedEntities()
Definition: ecvDBRoot.cpp:593
void addElement(ccHObject *object, bool autoExpand=true)
Adds an element to the DB tree.
Definition: ecvDBRoot.cpp:423
void selectionChanged()
std::vector< PointCoordinateType > zBuff
Z-Buffer grid.
Disc (primitive)
Definition: ecvDisc.h:16
PointCoordinateType getRadius() const
Returns radius.
Definition: ecvDisc.h:41
ccGenericPrimitive * clone() const override
Clones primitive.
Dialog to setup display settings.
virtual bool isVisible() const
Returns whether entity is visible or not.
virtual void setTempColor(const ecvColor::Rgb &col, bool autoActivate=true)
Sets current temporary (unique)
virtual void setVisible(bool state)
Sets entity visibility.
virtual const ccGLMatrix & getGLTransformation() const
Returns associated GL transformation.
virtual void showNormals(bool state)
Sets normals visibility.
virtual void showColors(bool state)
Sets colors visibility.
virtual void showSF(bool state)
Sets active scalarfield visibility.
virtual void setOpacity(float opacity)
Set opacity activation state.
virtual void setGLTransformation(const ccGLMatrix &trans)
Associates entity with a GL transformation (rotation + translation)
Facet.
Definition: ecvFacet.h:25
ccFacet * clone() const
Clones this facet.
ccMesh * getPolygon()
Returns polygon mesh (if any)
Definition: ecvFacet.h:81
ccPolyline * getContour()
Returns contour polyline (if any)
Definition: ecvFacet.h:86
CCVector3 getNormal() const override
Returns the entity normal.
Definition: ecvFacet.h:63
double getRMS() const
Returns associated RMS.
Definition: ecvFacet.h:68
static ccFacet * Create(cloudViewer::GenericIndexedCloudPersist *cloud, PointCoordinateType maxEdgeLength=0, bool transferOwnership=false, const PointCoordinateType *planeEquation=nullptr)
Creates a facet from a set of points.
const CCVector3 & getCenter() const
Returns the facet center.
Definition: ecvFacet.h:78
Ground-based (lidar) sensor parameters dialog.
Ground-based Laser sensor.
Definition: ecvGBLSensor.h:26
bool computeDepthBuffer(cloudViewer::GenericCloud *cloud, int &errorCode, ccPointCloud *projectedCloud=nullptr)
bool computeAutoParameters(cloudViewer::GenericCloud *theCloud)
Computes angular parameters automatically (all but the angular steps!)
static QString GetErrorString(int errorCode)
Returns the error string corresponding to an error code.
const ccDepthBuffer & getDepthBuffer() const
Returns the associated depth buffer.
Definition: ecvGBLSensor.h:225
unsigned char checkVisibility(const CCVector3 &P) const override
QString toString(int precision=12, QChar separator=' ') const
Returns matrix as a string.
Vector3Tpl< T > getTranslationAsVec3D() const
Returns a copy of the translation as a CCVector3.
void applyRotation(Vector3Tpl< float > &vec) const
Applies rotation only to a 3D vector (in place) - float version.
static ccGLMatrixTpl< float > FromToRotation(const Vector3Tpl< float > &from, const Vector3Tpl< float > &to)
Creates a transformation matrix that rotates a vector to another.
T * data()
Returns a pointer to internal data.
void shiftRotationCenter(const Vector3Tpl< T > &vec)
Shifts rotation center.
void invert()
Inverts transformation.
ccGLMatrixTpl< T > inverse() const
Returns inverse transformation.
void apply(float vec[3]) const
Applies transformation to a 3D vector (in place) - float version.
void setTranslation(const Vector3Tpl< float > &Tr)
Sets translation (float version)
Vector3Tpl< T > getColumnAsVec3D(unsigned index) const
Returns a copy of a given column as a CCVector3.
void scale(T coef)
Scales the whole matrix.
void initFromParameters(T alpha_rad, const Vector3Tpl< T > &axis3D, const Vector3Tpl< T > &t3D)
Inits transformation from a rotation axis, an angle and a translation.
virtual void toIdentity()
Sets matrix to identity.
Float version of ccGLMatrixTpl.
Definition: ecvGLMatrix.h:19
Double version of ccGLMatrixTpl.
Definition: ecvGLMatrix.h:56
Generic mesh interface.
void showNormals(bool state) override
Sets normals visibility.
ccPointCloud * samplePoints(bool densityBased, double samplingParameter, bool withNormals, bool withRGB, bool withTexture, cloudViewer::GenericProgressCallback *pDlg=nullptr)
Samples points on a mesh.
virtual ccGenericPointCloud * getAssociatedCloud() const =0
Returns the vertices cloud.
void enableStippling(bool state)
Enables polygon stippling.
virtual void refreshBB()=0
Forces bounding-box update.
A 3D cloud interface with associated features (color, normals, octree, etc.)
virtual void scale(PointCoordinateType fx, PointCoordinateType fy, PointCoordinateType fz, CCVector3 center=CCVector3(0, 0, 0))=0
Multiplies all coordinates by constant factors (one per dimension)
virtual ccOctree::Shared computeOctree(cloudViewer::GenericProgressCallback *progressCb=nullptr, bool autoAddChild=true)
Computes the cloud octree.
virtual ccGenericPointCloud * createNewCloudFromVisibilitySelection(bool removeSelectedPoints=false, VisibilityTableType *visTable=nullptr, std::vector< int > *newIndexesOfRemainingPoints=nullptr, bool silent=false, cloudViewer::ReferenceCloud *selection=nullptr)=0
void setPointSize(unsigned size=0)
Sets point size.
virtual void invertVisibilityArray()
Inverts the visibility array.
virtual void applyRigidTransformation(const ccGLMatrix &trans)=0
Applies a rigid transformation (rotation + translation)
virtual ccGenericPointCloud * clone(ccGenericPointCloud *destCloud=nullptr, bool ignoreChildren=false)=0
Clones this entity.
ccBBox getOwnBB(bool withGLFeatures=false) override
Returns the entity's own bounding-box.
virtual ccOctree::Shared getOctree() const
Returns the associated octree (if any)
virtual bool resetVisibilityArray()
Resets the associated visibility array.
virtual void unallocateVisibilityArray()
Erases the points visibility information.
Generic primitive interface.
virtual ccGLMatrix & getTransformation()
Returns the transformation that is currently applied to the vertices.
Dialog for computing the density of a point clouds.
Graphical segmentation mechanism (with polyline)
virtual bool linkWith(QWidget *win) override
Links the overlay dialog with a MDI window.
void removeAllEntities()
Remove entities from the 'to be segmented' pool.
bool addEntity(ccHObject *anObject, bool silent=false)
Adds an entity (and/or its children) to the 'to be segmented' pool.
bool deleteHiddenParts() const
Returns whether hidden parts should be delete after segmentation.
virtual bool start() override
Starts process.
QSet< ccHObject * > & entities()
Returns the active 'to be segmented' set.
virtual bool start() override
Starts process.
const ccHObject & getValidEntities() const
Returns the 'to be transformed' entities set (see addEntity)
void setRotationCenter(CCVector3d &center)
Sets the rotation center.
bool addEntity(ccHObject *anObject)
Adds an entity to the 'selected' entities set.
void clear()
Clear all variables and 'unlink' dialog.
unsigned getNumberOfValidEntities() const
Returns the number of valid entities (see addEntity)
virtual bool linkWith(QWidget *win) override
Links the overlay dialog with a MDI window.
bool setTansformTool(ecvGenericTransformTool *tool)
static ccMesh * ToMesh(ccHObject *obj)
Converts current object to ccMesh (if possible)
static ccDisc * ToDisc(ccHObject *obj)
Converts current object to ccDisc (if possible)
static ccPointCloud * ToPointCloud(ccHObject *obj, bool *isLockedVertices=nullptr)
Converts current object to 'equivalent' ccPointCloud.
static ccShiftedObject * ToShifted(ccHObject *obj, bool *isLockedVertices=nullptr)
Converts current object to 'equivalent' ccShiftedObject.
static ccCameraSensor * ToCameraSensor(ccHObject *obj)
static ccPolyline * ToPolyline(ccHObject *obj)
Converts current object to ccPolyline (if possible)
static ccGenericPointCloud * ToGenericPointCloud(ccHObject *obj, bool *isLockedVertices=nullptr)
Converts current object to 'equivalent' ccGenericPointCloud.
static ccSensor * ToSensor(ccHObject *obj)
Converts current object to ccSensor (if possible)
static ccSubMesh * ToSubMesh(ccHObject *obj)
Converts current object to ccSubMesh (if possible)
static ccFacet * ToFacet(ccHObject *obj)
Converts current object to ccFacet (if possible)
static ccGenericMesh * ToGenericMesh(ccHObject *obj)
Converts current object to ccGenericMesh (if possible)
static ccGBLSensor * ToGBLSensor(ccHObject *obj)
static ccPlane * ToPlane(ccHObject *obj)
Converts current object to ccPlane (if possible)
static ccCircle * ToCircle(ccHObject *obj)
Converts current object to ccCircle (if possible)
Hierarchical CLOUDVIEWER Object.
Definition: ecvHObject.h:25
virtual void notifyGeometryUpdate()
virtual ccBBox getOwnBB(bool withGLFeatures=false)
Returns the entity's own bounding-box.
virtual const ccGLMatrix & getGLTransformationHistory() const
Returns the transformation 'history' matrix.
Definition: ecvHObject.h:631
ccHObject * find(unsigned uniqueID)
Finds an entity in this object hierarchy.
@ SELECTION_FIT_BBOX
Definition: ecvHObject.h:607
void removeFromRenderScreen(bool recursive=true)
bool isSerializable() const override
Returns whether object is serializable of not.
int getDependencyFlagsWith(const ccHObject *otherObject)
Returns the dependency flags with a given object.
void removeAllChildren()
Removes all children.
virtual ccBBox getDisplayBB_recursive(bool relative)
Returns the bounding-box of this entity and it's children WHEN DISPLAYED.
void removeDependencyWith(ccHObject *otherObject)
Removes any dependency flags with a given object.
void addDependency(ccHObject *otherObject, int flags, bool additive=true)
Adds a new dependence (additive or not)
bool isAncestorOf(const ccHObject *anObject) const
Returns true if the current object is an ancestor of the specified one.
QString getViewId() const
Definition: ecvHObject.h:225
void applyGLTransformation_recursive(const ccGLMatrix *trans=nullptr)
Applies the active OpenGL transformation to the entity (recursive)
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.
virtual void setGLTransformationHistory(const ccGLMatrix &mat)
Sets the transformation 'history' matrix (handle with care!)
Definition: ecvHObject.h:635
void setRedrawFlagRecursive(bool redraw=false)
virtual void setSelectionBehavior(SelectionBehavior mode)
Sets selection behavior (when displayed)
Definition: ecvHObject.h:616
virtual void setSelected_recursive(bool p)
Definition: ecvHObject.h:541
virtual ccBBox getBB_recursive(bool withGLFeatures=false, bool onlyEnabledChildren=true)
Returns the bounding-box of this entity and it's children.
ccHObject * getParent() const
Returns parent object.
Definition: ecvHObject.h:245
void removeChild(ccHObject *child)
unsigned filterChildren(Container &filteredChildren, bool recursive=false, CV_CLASS_ENUM filter=CV_TYPES::OBJECT, bool strict=false) const
Collects the children corresponding to a certain pattern.
std::vector< ccHObject * > Container
Standard instances container (for children, etc.)
Definition: ecvHObject.h:337
ccHObject * getChild(unsigned childPos) const
Returns the ith child.
Definition: ecvHObject.h:325
CV_CLASS_ENUM getClassID() const override
Returns class ID.
Definition: ecvHObject.h:232
Encapsulating dialog for ccHistogramWindow.
ccHistogramWindow * window()
Returns encapsulated ccHistogramWindow.
Histogram widget.
void setTitle(const QString &str)
Sets title.
void refresh()
Updates the display.
void fromSF(ccScalarField *sf, unsigned initialNumberOfClasses=0, bool numberOfClassesCanBeChanged=true, bool showNaNValuesInGrey=true)
Computes histogram from a scalar field.
void fromBinArray(const std::vector< unsigned > &histoValues, double minVal, double maxVal)
void setAxisLabels(const QString &xLabel, const QString &yLabel)
Sets axis labels.
Finds a the biggets enclosed rectangle in a point cloud (2D)
ccBox * process(ccGenericPointCloud *cloud, unsigned char zDim=2)
Finds the biggest enclosed rectangle.
static int SelectEntity(const ccHObject::Container &entities, int defaultSelectedIndex=0, QWidget *parent=0, QString label=QString())
Static shortcut: unique selection mode.
KD-tree structure.
Definition: ecvKdTree.h:25
bool convertCellIndexToRandomColor()
Flag points with a random color per leaf.
bool convertCellIndexToSF()
Flag points with cell index (as a scalar field)
Dialog to define connected components labelinng parameters.
Scales matching tool dialog.
ccLibAlgorithms::ScaleMatchingAlgorithm getSelectedAlgorithm() const
Returns the selected matching algorithm.
int getSelectedIndex() const
Returns selected index.
void setSelectedAlgorithm(ccLibAlgorithms::ScaleMatchingAlgorithm algorithm)
Sets the selected matching algorithm.
Triangular mesh.
Definition: ecvMesh.h:35
bool merge(const ccMesh *mesh, bool createSubMesh)
Merges another mesh into this one.
ccMesh * cloneMesh(ccGenericPointCloud *vertices=nullptr, ccMaterialSet *clonedMaterials=nullptr, NormsIndexesTableType *clonedNormsTable=nullptr, TextureCoordsContainer *cloneTexCoords=nullptr)
Clones this entity.
ccMesh * subdivide(PointCoordinateType maxArea) const
bool laplacianSmooth(unsigned nbIteration=100, PointCoordinateType factor=static_cast< PointCoordinateType >(0.01), ecvProgressDialog *progressCb=nullptr)
Laplacian smoothing.
void flipTriangles()
Flips the triangle.
static ccMesh * TriangulateTwoPolylines(ccPolyline *p1, ccPolyline *p2, CCVector3 *projectionDir=nullptr)
Creates a Delaunay 2.5D mesh from two polylines.
@ ENHANCE_MESH_SF
Definition: ecvMesh.h:556
@ SMOOTH_MESH_SF
Definition: ecvMesh.h:555
static ccMesh * Triangulate(ccGenericPointCloud *cloud, cloudViewer::TRIANGULATION_TYPES type, bool updateNormals=false, PointCoordinateType maxEdgeLength=0, unsigned char dim=2)
Creates a Delaunay 2.5D mesh from a point cloud.
ccGenericPointCloud * getAssociatedCloud() const override
Returns the vertices cloud.
Definition: ecvMesh.h:143
ccBBox getOwnBB(bool withGLFeatures=false) override
Returns the entity's own bounding-box.
ccMesh * createNewMeshFromSelection(bool removeSelectedTriangles, std::vector< int > *newIndexesOfRemainingTriangles=nullptr, bool withChildEntities=false)
Creates a new mesh with the selected vertices only.
bool computePerVertexNormals()
Computes per-vertex normals.
virtual unsigned size() const override
Returns the number of triangles.
static QString ConvertDipAndDipDirToString(PointCoordinateType dip_deg, PointCoordinateType dipDir_deg)
Converts geological 'dip direction & dip' parameters to a string.
static void ConvertNormalToDipAndDipDir(const CCVector3 &N, PointCoordinateType &dip_deg, PointCoordinateType &dipDir_deg)
Converts a normal vector to geological 'dip direction & dip' parameters.
virtual bool isLocked() const
Returns whether the object is locked or not.
Definition: ecvObject.h:112
virtual QString getName() const
Returns object name.
Definition: ecvObject.h:72
virtual unsigned getUniqueID() const
Returns object unique ID.
Definition: ecvObject.h:86
void setMetaData(const QString &key, const QVariant &data)
Sets a meta-data element.
bool isA(CV_CLASS_ENUM type) const
Definition: ecvObject.h:131
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
virtual bool isEnabled() const
Returns whether the object is enabled or not.
Definition: ecvObject.h:97
bool isKindOf(CV_CLASS_ENUM type) const
Definition: ecvObject.h:128
static ccUniqueIDGenerator::Shared GetUniqueIDGenerator()
Returns the unique ID generator.
QSharedPointer< ccOctree > Shared
Shared pointer.
Definition: ecvOctree.h:32
Dialog to assign roles to two entities (e.g. compared/reference)
Generic overlay dialog interface.
void shown()
Signal emitted when a 'show' event is detected.
bool started() const
Returns whether the tool is currently started or not.
void processFinished(bool accepted)
Signal emitted when process is finished.
Minimal dialog to pick one element in a list (combox box)
Point/triangle picking hub.
Definition: ecvPickingHub.h:29
void removeListener(ccPickingListener *listener, bool autoStopPickingIfLast=true)
Removes a listener.
void onActiveWindowDeleted(QObject *)
bool addListener(ccPickingListener *listener, bool exclusive=false, bool autoStartPicking=true, ecvDisplayTools::PICKING_MODE mode=ecvDisplayTools::POINT_OR_TRIANGLE_PICKING)
Adds a listener.
void onActiveWindowChanged(QMdiSubWindow *)
Dialog to create (or edit the parameters) of a plane.
void initWithPlane(ccPlane *plane)
Links this dialog with an existing plane.
Plane (primitive)
Definition: ecvPlane.h:18
static ccPlane * Fit(cloudViewer::GenericIndexedCloudPersist *cloud, double *rms=0)
Fits a plane primitive on a cloud.
CCVector3 getCenter() const
Returns the center.
Definition: ecvPlane.h:56
CCVector3 getNormal() const override
Returns the entity normal.
Definition: ecvPlane.h:73
void flip()
Flips the plane.
void getEquation(CCVector3 &N, PointCoordinateType &constVal) const
Returns the equation of the plane.
PointCoordinateType getXWidth() const
Returns 'X' width.
Definition: ecvPlane.h:50
PointCoordinateType getYWidth() const
Returns 'Y' width.
Definition: ecvPlane.h:53
Plugin UI manager.
void showAboutDialog() const
QList< QToolBar * > & additionalPluginToolbars()
QToolBar * mainPluginToolbar()
QMenu * pluginMenu() const
QMenu * pclAlgorithmMenu() const
QAction * actionShowPCLAlgorithmToolbar()
static bool isPythonPluginToolbar(QToolBar *toolbar)
QAction * actionShowMainPluginToolbar()
A 3D cloud and its associated features (color, normals, scalar fields, etc.)
void setCurrentDisplayedScalarField(int index)
Sets the currently displayed scalar field.
bool sfColorScaleShown() const
Returns whether color scale should be displayed or not.
virtual void applyRigidTransformation(const ccGLMatrix &trans) override
Applies a rigid transformation (rotation + translation)
int addScalarField(const char *uniqueName) override
Creates a new scalar field and registers it.
bool hasNormals() const override
Returns whether normals are enabled or not.
ccPointCloud * unroll(UnrollMode mode, UnrollBaseParams *params, bool exportDeviationSF=false, double startAngle_deg=0.0, double stopAngle_deg=360.0, cloudViewer::GenericProgressCallback *progressCb=nullptr) const
Unrolls the cloud and its normals on a cylinder or a cone.
void showSFColorsScale(bool state)
Sets whether color scale should be displayed or not.
void invalidateBoundingBox() override
Invalidates bounding box.
bool reserve(unsigned numberOfPoints) override
Reserves memory for all the active features.
bool hasFWF() const
Returns whether the cloud has associated Full WaveForm data.
ccMesh * triangulateGrid(const Grid &grid, double minTriangleAngle_deg=0.0) const
Meshes a scan grid.
void hidePointsByScalarValue(ScalarType minVal, ScalarType maxVal)
Hides points whose scalar values falls into an interval.
bool compressFWFData()
Compresses the associated FWF data container.
int getCurrentDisplayedScalarFieldIndex() const
Returns the currently displayed scalar field index (or -1 if none)
ccPointCloud * cloneThis(ccPointCloud *destCloud=nullptr, bool ignoreChildren=false)
Clones this entity.
static ccPointCloud * From(const cloudViewer::GenericIndexedCloud *cloud, const ccGenericPointCloud *sourceCloud=nullptr)
Creates a new point cloud object from a GenericIndexedCloud.
bool resize(unsigned numberOfPoints) override
Resizes all the active features arrays.
void deleteScalarField(int index) override
Deletes a specific scalar field.
ccScalarField * getCurrentDisplayedScalarField() const
Returns the currently displayed scalar (or 0 if none)
Grid::Shared & grid(size_t gridIndex)
Returns an associated grid.
void shrinkToFit()
Removes unused capacity.
const CCVector3 & getPointNormal(unsigned pointIndex) const override
Returns normal corresponding to a given point.
ccPointCloud * filterPointsByScalarValue(ScalarType minVal, ScalarType maxVal, bool outside=false)
Filters out points whose scalar values falls into an interval.
bool setRGBColor(ColorCompType r, ColorCompType g, ColorCompType b)
Set a unique color for the whole cloud (shortcut)
ccPointCloud * partialClone(const cloudViewer::ReferenceCloud *selection, int *warnings=nullptr, bool withChildEntities=true) const
Creates a new point cloud object from a ReferenceCloud (selection)
size_t gridCount() const
Returns the number of associated grids.
Dialog/interactor to graphically pick a list of points.
void linkWithCloud(ccPointCloud *cloud)
Associates dialog with cloud.
bool start() override
Starts process.
bool init(QWidget *win, const ccHObject::Container &alignedEntities, const ccHObject::Container *referenceEntities=nullptr)
Inits dialog.
void pause(bool state)
Pauses the dialog.
bool linkWith(QWidget *win) override
Links the overlay dialog with a MDI window.
Dialog for simple point picking (information, distance, etc.)
void newLabel(ccHObject *)
Signal emitted when a new label is created.
virtual bool linkWith(QWidget *win) override
Links the overlay dialog with a MDI window.
virtual bool start() override
Starts process.
Colored polyline.
Definition: ecvPolyline.h:24
virtual void setGlobalShift(const CCVector3d &shift) override
Sets shift applied to original coordinates (information storage only)
ccPointCloud * samplePoints(bool densityBased, double samplingParameter, bool withRGB)
Samples points on the polyline.
ccPolyline * smoothChaikin(PointCoordinateType ratio, unsigned iterationCount) const
Smoothes the polyline (Chaikin algorithm)
unsigned segmentCount() const
Returns the number of segments.
virtual void setGlobalScale(double scale) override
void setColor(const ecvColor::Rgb &col)
Sets the polyline color.
Definition: ecvPolyline.h:81
void setVisualizer(ecvGenericVisualizer3D *viewer)
Set visualizer for other property editors.
void requestClearSelection() const
Request to clear all selection data (prevents crashes from stale references)
Dialog: points sampling on a mesh.
Quadric (primitive)
Definition: ecvQuadric.h:16
const Tuple3ub & getEquationDims() const
Definition: ecvQuadric.h:69
QString getEquationString() const
Returns the quadric equation coefficients as a string.
const PointCoordinateType * getEquationCoefs() const
Returns the quadric equation coefficients.
Definition: ecvQuadric.h:65
static ccQuadric * Fit(cloudViewer::GenericIndexedCloudPersist *cloud, double *rms)
Fits a quadric primitive on a cloud.
Point cloud or mesh registration dialog.
static double GetAbsoluteMinRMSDecrease()
static bool ICP(ccHObject *data, ccHObject *model, ccGLMatrix &transMat, double &finalScale, double &finalRMS, unsigned &finalPointCount, const cloudViewer::ICPRegistrationTools::Parameters &inputParameters, bool useDataSFAsWeights=false, bool useModelSFAsWeights=false, QWidget *parent=nullptr)
Applies ICP registration on two entities.
Dialog for screen to file rendering.
static void ShowDepthBuffer(ccGBLSensor *lidar, QWidget *parent=nullptr, unsigned maxDim=1024)
Displays a depth buffer as an image.
ScalarType stop() const
ScalarType start() const
A scalar field associated to display-related parameters.
void setColorScale(ccColorScale::Shared scale)
Sets associated color scale.
void showNaNValuesInGrey(bool state)
const Range & displayRange() const
Access to the range of displayed values.
void computeMinAndMax() override
Determines the min and max values.
bool areNaNValuesShownInGrey() const
Returns whether NaN values are displayed in gray or hidden.
Scale / multiply dialog.
Definition: ecvScaleDlg.h:19
void saveState()
Saves state.
Definition: ecvScaleDlg.cpp:37
bool keepInPlace() const
Whether the entity should be 'kept in place' or not.
Definition: ecvScaleDlg.cpp:56
bool rescaleGlobalShift() const
Whether the Global shift should be rescaled as well.
Definition: ecvScaleDlg.cpp:60
CCVector3d getScales() const
Returns scales.
Definition: ecvScaleDlg.cpp:51
Dialog for sensor range computation.
Dialog for scattering angles computation.
Generic sensor interface.
Definition: ecvSensor.h:27
bool addPosition(ccGLMatrix &trans, double index)
Adds a new position (shortcut)
bool getActiveAbsoluteCenter(CCVector3 &vec) const
Gets currently active absolute position.
virtual bool applyViewport()
Apply sensor 'viewport' to a 3D view.
void setGraphicScale(PointCoordinateType scale)
Sets the sensor graphic representation scale.
Definition: ecvSensor.h:125
Shifted entity interface.
virtual void setGlobalScale(double scale)
CCVector3d toGlobal3d(const Vector3Tpl< T > &Plocal) const
Returns the point back-projected into the original coordinates system.
bool isShifted() const
Returns whether the cloud is shifted or not.
virtual void setGlobalShift(double x, double y, double z)
Sets shift applied to original coordinates (information storage only)
virtual const CCVector3d & getGlobalShift() const
Returns the shift applied to original coordinates.
virtual double getGlobalScale() const
Returns the scale applied to original coordinates.
void copyGlobalShiftAndScale(const ccShiftedObject &s)
Copies the Global Shift and Scale from another entity.
Dialog to smooth a polyline (Chaikin algorithm)
Sphere (primitive)
Definition: ecvSphere.h:16
PointCoordinateType getRadius() const
Returns radius.
Definition: ecvSphere.h:45
A sub-mesh.
Definition: ecvSubMesh.h:19
ccSubMesh * createNewSubMeshFromSelection(bool removeSelectedTriangles, const std::vector< int > &selectedTriangleIndexes, IndexMap *newRemainingTriangleIndexes=nullptr)
Creates a new sub mesh with the visible vertices only.
Subsampling cloud dialog.
Graphical Polyline Tracing tool.
virtual bool linkWith(QWidget *win) override
Links the overlay dialog with a MDI window.
virtual bool start() override
Starts process.
void populateMenu(QMenu *menu, const QString &pathToTranslationFiles)
static ccTranslationManager & get()
QSharedPointer< ccUniqueIDGenerator > Shared
Shared type.
Definition: ecvObject.h:28
Dialog: unroll clould on a cylinder or a cone.
Definition: ecvUnrollDlg.h:19
Waveform dialog.
static int labelConnectedComponents(GenericIndexedCloudPersist *theCloud, unsigned char level, bool sixConnexity=false, cloudViewer::GenericProgressCallback *progressCb=nullptr, cloudViewer::DgmOctree *inputOctree=nullptr)
Labels connected components from a point cloud.
double getDiagNormd() const
Returns diagonal length (double precision)
Definition: BoundingBox.h:175
Vector3Tpl< T > getCenter() const
Returns center.
Definition: BoundingBox.h:164
const Vector3Tpl< T > & maxCorner() const
Returns max corner (const)
Definition: BoundingBox.h:156
T getDiagNorm() const
Returns diagonal length.
Definition: BoundingBox.h:172
T getMaxBoxDim() const
Returns maximal box dimension.
Definition: BoundingBox.h:185
const Vector3Tpl< T > & minCorner() const
Returns min corner (const)
Definition: BoundingBox.h:154
bool isValid() const
Returns whether bounding box is valid or not.
Definition: BoundingBox.h:203
void add(const Vector3Tpl< T > &P)
'Enlarges' the bounding box with a point
Definition: BoundingBox.h:131
Several point cloud resampling algorithms (octree-based, random, etc.)
static GenericIndexedCloud * resampleCloudWithOctree(GenericIndexedCloudPersist *cloud, int newNumberOfPoints, RESAMPLING_CELL_METHOD resamplingMethod, GenericProgressCallback *progressCb=nullptr, DgmOctree *inputOctree=nullptr)
Resamples a point cloud (process based on the octree)
static ReferenceCloud * noiseFilter(GenericIndexedCloudPersist *cloud, PointCoordinateType kernelRadius, double nSigma, bool removeIsolatedPoints=false, bool useKnn=false, int knn=6, bool useAbsoluteError=true, double absoluteError=0.0, DgmOctree *octree=nullptr, GenericProgressCallback *progressCb=nullptr)
Noise filter based on the distance to the approximate local surface.
static ReferenceCloud * sorFilter(GenericIndexedCloudPersist *cloud, int knn=6, double nSigma=1.0, DgmOctree *octree=nullptr, GenericProgressCallback *progressCb=nullptr)
Statistical Outliers Removal (SOR) filter.
A class to compute and handle a Delaunay 2D mesh on a subset of points.
VerticesIndexes * getTriangleVertIndexes(unsigned triangleIndex) override
Returns the indexes of the vertices of a given triangle.
virtual unsigned size() const override
Returns the number of triangles.
virtual bool buildMesh(const std::vector< CCVector2 > &points2D, std::size_t pointCountToUse, std::string &outputErrorStr)
Build the Delaunay mesh on top a set of 2D points.
virtual void linkMeshWith(GenericIndexedCloud *aCloud, bool passOwnership=false)
Associate this mesh to a point cloud.
unsigned char findBestLevelForAGivenNeighbourhoodSizeExtraction(PointCoordinateType radius) const
Definition: DgmOctree.cpp:2664
int getPointsInSphericalNeighbourhood(const CCVector3 &sphereCenter, PointCoordinateType radius, NeighboursSet &neighbours, unsigned char level) const
Returns the points falling inside a sphere.
Definition: DgmOctree.cpp:1846
std::vector< PointDescriptor > NeighboursSet
A set of neighbours.
Definition: DgmOctree.h:133
std::size_t getPointsInCylindricalNeighbourhood(CylindricalNeighbourhood &params) const
Returns the points falling inside a cylinder.
Definition: DgmOctree.cpp:2111
static ScalarType computePoint2PlaneDistance(const CCVector3 *P, const PointCoordinateType *planeEquation)
Computes the (signed) distance between a point and a plane.
static int computeCloud2CloudDistances(GenericIndexedCloudPersist *comparedCloud, GenericIndexedCloudPersist *referenceCloud, Cloud2CloudDistancesComputationParams &params, GenericProgressCallback *progressCb=nullptr, DgmOctree *compOctree=nullptr, DgmOctree *refOctree=nullptr)
static int computeCloud2PolylineEquation(GenericIndexedCloudPersist *cloud, const Polyline *polyline, double *rms=nullptr)
Computes the distance between each point in a cloud and a polyline.
static bool RegisterClouds(GenericIndexedCloud *modelCloud, GenericIndexedCloud *dataCloud, ScaledTransformation &transform, ScalarType delta, ScalarType beta, PointCoordinateType overlap, unsigned nbBases, unsigned nbTries, GenericProgressCallback *progressCb=nullptr, unsigned nbMaxCandidates=0)
Registers two point clouds.
virtual unsigned size() const =0
Returns the number of points.
A generic 3D point cloud with index-based and presistent access to points.
A generic 3D point cloud with index-based point access.
virtual unsigned size() const =0
Returns the number of triangles.
static ErrorCode DetectSphereRobust(GenericIndexedCloudPersist *cloud, double outliersRatio, CCVector3 &center, PointCoordinateType &radius, double &rms, GenericProgressCallback *progressCb=nullptr, double confidence=0.99, unsigned seed=0)
Tries to detect a sphere in a point cloud.
static ErrorCode DetectCircle(GenericIndexedCloudPersist *cloud, CCVector3 &center, CCVector3 &normal, PointCoordinateType &radius, double &rms, GenericProgressCallback *progressCb=nullptr)
Detects a circle from a point cloud.
static ErrorCode FlagDuplicatePoints(GenericIndexedCloudPersist *theCloud, double minDistanceBetweenPoints=std::numeric_limits< double >::epsilon(), GenericProgressCallback *progressCb=nullptr, DgmOctree *inputOctree=nullptr)
Flag duplicate points.
const GridElement & getValue(int i, int j, int k) const
Returns the value of a given cell (const version)
Definition: Grid3D.h:498
static RESULT_TYPE Register(GenericIndexedCloudPersist *modelCloud, GenericIndexedMesh *modelMesh, GenericIndexedCloudPersist *dataCloud, const Parameters &params, ScaledTransformation &totalTrans, double &finalRMS, unsigned &finalPointCount, GenericProgressCallback *progressCb=nullptr)
Registers two clouds or a cloud and a mesh.
Mesh sampling algorithms.
static bool flagMeshVerticesByType(GenericIndexedMesh *mesh, ScalarField *flags, EdgeConnectivityStats *stats=nullptr)
Flags the vertices of a mesh depending on their type.
static double computeMeshVolume(GenericMesh *mesh)
Computes the mesh volume.
static double computeMeshArea(GenericMesh *mesh)
Computes the mesh area.
const CCVector3 * getGravityCenter()
Returns gravity center.
bool oneStep()
Increments total progress value of a single unit.
int getScalarFieldIndexByName(const char *name) const
Returns the index of a scalar field represented by its name.
void forEach(GenericCloud::genericPointAction action) override
Definition: PointCloudTpl.h:42
void setCurrentOutScalarField(int index)
Sets the OUTPUT scalar field.
ScalarField * getScalarField(int index) const
Returns a pointer to a specific scalar field.
unsigned getNumberOfScalarFields() const
Returns the number of associated (and active) scalar fields.
void setPointScalarValue(unsigned pointIndex, ScalarType value) override
void setCurrentScalarField(int index)
Sets both the INPUT & OUTPUT scalar field.
void addPoint(const CCVector3 &P)
Adds a 3D point to the database.
unsigned size() const override
Definition: PointCloudTpl.h:38
bool renameScalarField(int index, const char *newName)
Renames a specific scalar field.
const CCVector3 * getPoint(unsigned index) const override
bool enableScalarField() override
Definition: PointCloudTpl.h:77
ScalarField * getCurrentInScalarField() const
Returns the scalar field currently associated to the cloud input.
bool isClosed() const
Returns whether the polyline is closed or not.
Definition: Polyline.h:26
A very simple point cloud (no point duplication)
unsigned size() const override
Returns the number of points.
virtual void clear(bool releaseMemory=false)
Clears the cloud.
const CCVector3 * getPoint(unsigned index) const override
Returns the ith point.
bool propagateDistance(GenericProgressCallback *progressCb=nullptr)
Computes the exact Squared Distance Transform on the whole grid.
bool initDT(GenericIndexedMesh *mesh, PointCoordinateType cellLength, const CCVector3 &gridMinCorner, GenericProgressCallback *progressCb=nullptr)
Initializes the distance transform with a mesh.
bool initGrid(const Tuple3ui &gridSize)
Initializes the grid.
static void SetScalarValueToNaN(const CCVector3 &P, ScalarType &scalarValue)
Sets the distance value associated to a point.
static void SetScalarValueInverted(const CCVector3 &P, ScalarType &scalarValue)
A simple scalar field (to be associated to a point cloud)
Definition: ScalarField.h:25
void fill(ScalarType fillValue=0)
Fills the array with a particular value.
Definition: ScalarField.h:77
virtual void computeMinAndMax()
Determines the min and max values.
Definition: ScalarField.h:123
ScalarType getMin() const
Returns the minimum value.
Definition: ScalarField.h:72
void addElement(ScalarType value)
Definition: ScalarField.h:99
ScalarType & getValue(std::size_t index)
Definition: ScalarField.h:92
void computeMeanAndVariance(ScalarType &mean, ScalarType *variance=nullptr) const
Definition: ScalarField.cpp:41
void setValue(std::size_t index, ScalarType value)
Definition: ScalarField.h:96
const char * getName() const
Returns scalar field name.
Definition: ScalarField.h:43
bool reserveSafe(std::size_t count)
Reserves memory (no exception thrown)
Definition: ScalarField.cpp:71
static bool ValidValue(ScalarType value)
Returns whether a scalar value is valid or not.
Definition: ScalarField.h:61
unsigned currentSize() const
Definition: ScalarField.h:100
ScalarType getMax() const
Returns the maximum value.
Definition: ScalarField.h:74
bool isValid() const
Returns matrix validity.
Definition: SquareMatrix.h:138
bool build(double maxError, DistanceComputationTools::ERROR_MEASURES errorMeasure=DistanceComputationTools::RMS, unsigned minPointCountPerCell=3, unsigned maxPointCountPerCell=0, GenericProgressCallback *progressCb=nullptr)
Builds KD-tree.
Definition: TrueKdTree.cpp:247
Dialog to interactively edit the camera pose parameters.
bool start() override
Starts process.
bool linkWith(QWidget *win) override
Links the overlay dialog with a MDI window.
Dialog for managing a clipping box.
bool addAssociatedEntity(ccHObject *anObject)
Adds an entity.
virtual bool linkWith(QWidget *win) override
Links the overlay dialog with a MDI window.
virtual bool start() override
Starts process.
unsigned getNumberOfAssociatedEntity() const
Returns the current number of associated entities.
bool setAnnotationsTool(ecvGenericAnnotationTool *annotationTool)
static QString GetMinVersionForFileVersion(short fileVersion)
Dialog to interactively edit the camera pose parameters.
bool setCameraTool(ecvGenericCameraTool *tool)
bool start() override
Starts process.
bool linkWith(QWidget *win) override
Links the overlay dialog with a MDI window.
static Rgb Random(bool lightOnly=true)
Generates a random color.
RGB color structure.
Definition: ecvColorTypes.h:49
static void ReleaseInstance(bool flush=true)
Releases unique instance.
Definition: ecvConsole.cpp:90
static void Init(QListWidget *textDisplay=nullptr, QWidget *parentWidget=nullptr, MainWindow *parentWindow=nullptr, bool redirectToStdOut=false)
Inits console (and optionaly associates it with a text output widget)
Definition: ecvConsole.cpp:178
static void EnableQtMessages(bool state)
Whether to show Qt messages (qDebug / qWarning / etc.) in Console.
Definition: ecvConsole.cpp:170
static bool QtMessagesEnabled()
Definition: ecvConsole.h:80
static void SetSceneDB(ccHObject *root)
static bool RenderToFile(QString filename, float zoomFactor=1.0f, bool dontScaleFeatures=false, bool renderOverlayItems=false)
Renders screen to a file.
static void SetPerspectiveProjection(int viewport=0)
static CCVector3d GetCurrentViewDir()
Returns current viewing direction.
static void RemoveFromOwnDB(ccHObject *obj)
Removes an entity from window own DB.
static bool ExclusiveFullScreen()
Returns whether the window is in exclusive full screen mode or not.
static void SetAutoPickPivotAtCenter(bool state)
static void Init(ecvDisplayTools *displayTools, QMainWindow *win, bool stereoMode=false)
static void Toggle2Dviewer(bool state)
void newLabel(ccHObject *obj)
Signal emitted when a new label is created.
static const ecvGui::ParamStruct & GetDisplayParameters()
Returns current parameters for this display (const version)
void entitiesSelectionChanged(std::unordered_set< int > entIDs)
Signal emitted when multiple entities are selected in the 3D view.
static void LockRotationAxis(bool state, const CCVector3d &axis)
Lock the rotation axis.
static void SetOrthoProjection(int viewport=0)
void entitySelectionChanged(ccHObject *entity)
Signal emitted when an entity is selected in the 3D view.
static const ecvViewportParameters & GetViewportParameters()
static void ToggleOrientationMarker(bool state=true)
static INTERACTION_FLAGS TRANSFORM_CAMERA()
static QMainWindow * GetMainWindow()
static void ResetCenterOfRotation(int viewport=0)
void mousePosChanged(const QPoint &pos)
static void ToggleCameraOrientationWidget(bool show)
static ecvDisplayTools * TheInstance()
static void SetView(CC_VIEW_ORIENTATION orientation, ccBBox *bbox)
static void SetInteractionMode(INTERACTION_FLAGS flags)
static QWidget * GetCurrentScreen()
static void SetRedrawRecursive(bool redraw=false)
static void ToggleDebugTrace()
Toggles debug info on screen.
void exclusiveFullScreenToggled(bool exclusive)
Signal emitted when the exclusive fullscreen is toggled.
static QWidget * GetMainScreen()
static void SetPerspectiveState(bool state, bool objectCenteredView)
Set perspective state/mode.
void filesDropped(const QStringList &filenames, bool displayDialog)
Signal emitted when files are dropped on the window.
static void RemoveBB(CC_DRAW_CONTEXT context)
static ecvGenericVisualizer3D * GetVisualizer3D()
void autoPickPivot(bool state)
static void UpdateScreen()
static void Update2DLabel(bool immediateUpdate=false)
static void SetPivotPoint(const CCVector3d &P, bool autoUpdateCameraPos=false, bool verbose=false)
Sets pivot point.
static void SetPointSize(float size, bool silent=false, int viewport=0)
static bool IsRotationAxisLocked()
Returns whether the rotation axis is locaked or not.
static void SetExclusiveFullScreenFlage(bool state)
static void DisplayNewMessage(const QString &message, MessagePosition pos, bool append=false, int displayMaxDelay_sec=2, MessageType type=CUSTOM_MESSAGE)
Displays a status message in the bottom-left corner.
static void ReleaseInstance()
static void UpdateConstellationCenterAndZoom(const ccBBox *aBox=nullptr, bool redraw=true)
Center and zoom on a given bounding box.
static void RedrawDisplay(bool only2D=false, bool forceRedraw=true)
static void ZoomGlobal()
static void SetPivotVisibility(PivotVisibility vis)
Sets pivot visibility.
static bool SelectEntities(const ccHObject::Container &entities, std::vector< int > &indexes, QWidget *parent=0, QString label=QString())
Static shortcut: multi-selection mode.
bool setInputEntity(ccHObject *entity)
Adds an entity to the 'selected' entities set.
virtual bool start() override
Starts process.
virtual bool linkWith(QWidget *win) override
Links the overlay dialog with a MDI window.
Dialog for managing a clipping box.
Definition: ecvFilterTool.h:29
ccHObject::Container getOutputs() const
Definition: ecvFilterTool.h:54
AnnotationMode
Default constructor.
static bool GetPerspectiveState()
Returns perspective mode.
Generic Filters Tool interface.
Generic Measurement Tools interface.
Generic visualizer 3D interface.
static bool Handle(const CCVector3d &P, double diagonal, Mode mode, bool useInputCoordinatesShiftIfPossible, CCVector3d &coordinatesShift, bool *preserveCoordinateShift, double *coordinatesScale, bool *applyAll=0)
static double MaxCoordinateAbsValue()
Returns the max coordinate (absolute) value.
static bool GetLast(ShiftInfo &info)
static void SetMaxCoordinateAbsValue(double value)
Sets the max coordinate (absolute) value.
static void SetMaxBoundgBoxDiagonal(double value)
Sets the max bounding-box diagonal.
static bool NeedShift(const CCVector3d &P)
Returns whether a particular point (coordinates) is too big or not.
static CCVector3d BestShift(const CCVector3d &P)
Suggests a shift for a given point expressed in global coordinate space.
static bool NeedRescale(double d)
Returns whether a particular dimension (e.g. diagonal) is too big or not.
static double MaxBoundgBoxDiagonal()
Returns max bounding-box diagonal.
static double BestScale(double d)
Layout manager for MainWindow.
void registerBottomDockWidget(QDockWidget *dockWidget)
Register a dock widget to be placed at the bottom.
void registerLeftSideToolBar(QToolBar *toolbar)
Register a toolbar to be placed on the left side.
void restoreGUILayout(bool forceDefault=false)
Restore GUI layout from settings.
void setToolbarIconSize(QToolBar *toolbar, int screenWidth)
bool restoreCustomLayout()
Restore previously saved custom layout.
void registerRightSideToolBar(QToolBar *toolbar)
Register a toolbar to be placed on the right side.
void saveGUILayout()
Save current GUI layout to settings.
void registerRightSideDockWidget(QDockWidget *dockWidget)
Register a dock widget to be placed on the right side.
void restoreDefaultLayout()
Restore the default layout.
void saveCustomLayout()
Save current layout as custom layout.
bool haveSelection() const
Checks if we have any selections.
ConsoleMessageLevel
Console message level (see dispToConsole)
bool haveOneSelection() const
Checks if we have exactly one selection.
Dialog for managing measurement tools (Distance, Contour, Protractor)
ccHObject::Container getOutputs() const
virtual void stop(bool state) override
Stops process/dialog.
Main application options.
Definition: ecvOptions.h:19
static void Set(const ecvOptions &options)
Sets parameters.
bool normalsDisplayedByDefault
Whether to display the normals by default or not.
Definition: ecvOptions.h:22
bool askForConfirmationBeforeQuitting
Ask for confirmation before quitting.
Definition: ecvOptions.h:31
void toPersistentSettings() const
Saves to persistent DB.
static const ecvOptions & Instance()
Returns the stored values of each parameter.
Definition: ecvOptions.h:48
static const QString SelectedOutputFilterCloud()
static const QString AutoShowCenter()
static const QString CurrentTheme()
static const QString MainWinState()
static const QString CurrentPath()
static const QString GlobalShift()
static const QString MainWinGeom()
static const QString MaxAbsCoord()
static const QString AutoPickRotationCenter()
static const QString MaxAbsDiag()
static const QString SelectedOutputFilterPoly()
static const QString DuplicatePointsGroup()
static const QString SelectedOutputFilterMesh()
static const QString ThemeSettings()
static const QString AutoShowReconstructionToolBar()
static const QString SaveFile()
static const QString DuplicatePointsMinDist()
static const QString DoNotRestoreWindowGeometry()
static const QString LoadFile()
static const QString SelectedInputFilter()
Dialog for cloud sphere or cloud plane comparison setting.
Graphical progress indicator (thread-safe)
QMenu * menu()
Returns a "most recently used file" menu.
void addFilePath(const QString &filePath)
Adds a file path to the recently used menu.
Dialog to choose which dimension(s) (X, Y or Z) should be exported as SF(s)
static QVariant getValue(const QString &section, const QString &key, const QVariant &defaultValue=QVariant())
static void setValue(const QString &section, const QString &key, const QVariant &value)
Dialog for selection of cloud center.
void restoreShortcutsFromQSettings() const
Standard parameters for GL displays/viewports.
int min(int a, int b)
Definition: cutil_math.h:53
__host__ __device__ int2 abs(int2 v)
Definition: cutil_math.h:1267
int max(int a, int b)
Definition: cutil_math.h:48
#define ecvApp
Mimic Qt's qApp for easy access to the application instance.
#define CC_DEFAULT_MESH_VERT_FLAGS_SF_NAME
Definition: ecvCommon.h:40
#define CC_DEFAULT_DEG_SCATTERING_ANGLES_SF_NAME
Definition: ecvCommon.h:35
#define CC_ORIGINAL_CLOUD_INDEX_SF_NAME
Definition: ecvCommon.h:43
#define CC_DEFAULT_RAD_SCATTERING_ANGLES_SF_NAME
Definition: ecvCommon.h:34
#define CC_DEFAULT_RANGES_SF_NAME
Definition: ecvCommon.h:36
#define CC_TEMP_DISTANCES_DEFAULT_SF_NAME
Definition: ecvCommon.h:14
#define CC_DEFAULT_SQUARED_RANGES_SF_NAME
Definition: ecvCommon.h:37
#define CC_CONNECTED_COMPONENTS_DEFAULT_LABEL_NAME
Definition: ecvCommon.h:23
#define CC_CLOUD2PRIMITIVE_DISTANCES_DEFAULT_SF_NAME
Definition: ecvCommon.h:19
#define CC_CLOUD2PRIMITIVE_SIGNED_DISTANCES_DEFAULT_SF_NAME
Definition: ecvCommon.h:20
void ConvertToGroup(const ccHObject::Container &origin, ccHObject &dest, int dependencyFlags=ccHObject::DP_NONE)
Puts all entities inside a container in a group.
Definition: ecvHObject.h:742
ImGuiContext * context
Definition: Window.cpp:76
static double dist(double x1, double y1, double x2, double y2)
Definition: lsd.c:207
#define KB
Definition: lz4.c:226
#define GB
Definition: lz4.c:228
#define MB
Definition: lz4.c:227
@ SENSOR
Definition: CVTypes.h:116
@ HIERARCHY_OBJECT
Definition: CVTypes.h:103
@ PRIMITIVE
Definition: CVTypes.h:119
@ MESH
Definition: CVTypes.h:105
@ GBL_SENSOR
Definition: CVTypes.h:117
@ IMAGE
Definition: CVTypes.h:114
@ CONE
Definition: CVTypes.h:123
@ POINT_CLOUD
Definition: CVTypes.h:104
@ CIRCLE
Definition: CVTypes.h:113
@ LABEL_2D
Definition: CVTypes.h:140
@ POLY_LINE
Definition: CVTypes.h:112
@ FACET
Definition: CVTypes.h:109
@ SUB_MESH
Definition: CVTypes.h:106
@ SPHERE
Definition: CVTypes.h:121
@ CAMERA_SENSOR
Definition: CVTypes.h:118
@ PLANE
Definition: CVTypes.h:120
@ DISC
Definition: CVTypes.h:146
@ CYLINDER
Definition: CVTypes.h:126
@ OBJECT
Definition: CVTypes.h:102
constexpr QRegularExpression::PatternOption CaseInsensitive
Definition: QtCompat.h:174
constexpr Qt::SplitBehavior SkipEmptyParts
Definition: QtCompat.h:302
QTextStream & endl(QTextStream &stream)
Definition: QtCompat.h:718
static const QString THEME_DEFAULT
static const QString THEME_MATERIALDARK
static const QString THEME_MATERIALLIGHT
static const QString THEME_LIGHTBLUE
static const QString THEME_PSBLACK
static const QString THEME_DarkGRAY
static const QString THEME_ONEDARK
static const QString THEME_NORD
static const QString THEME_FLATBLACK
static const QString THEME_LIGHTGRAY
static const QString THEME_MACOS
static const QString THEME_LIGHTBLACK
static const QString THEME_BF
static const QString THEME_GRUVBOX
static const QString THEME_DarkBLACK
static const QString THEME_DARKBLUE
static const QString THEME_FLUENT
static const QString THEME_TOKYONIGHT
static const QString THEME_GRAY
static const QString THEME_TEST
static const QString THEME_CATPPUCCIN
static const QString THEME_PARAVIEW
static const QString THEME_BLUE
static const QString THEME_DRACULA
static const QString THEME_SILVER
static const QString THEME_FLATWHITE
static const QString THEME_BLACK
bool VoxelSampling(const ccHObject::Container &selectedEntities, ccHObject::Container &outEntities, QWidget *parent)
bool exportNormalToSF(const ccHObject::Container &selectedEntities, QWidget *parent, bool *exportDimensions)
bool invertNormals(const ccHObject::Container &selectedEntities)
bool sfSetAsCoord(const ccHObject::Container &selectedEntities, QWidget *parent)
bool sfRename(const ccHObject::Container &selectedEntities, QWidget *parent)
bool convertTextureToColor(const ccHObject::Container &selectedEntities, QWidget *parent)
bool computeOctree(const ccHObject::Container &selectedEntities, QWidget *parent)
bool orientNormalsFM(const ccHObject::Container &selectedEntities, QWidget *parent)
bool sfFromColor(const ccHObject::Container &selectedEntities, QWidget *parent)
bool changeColorLevels(const ccHObject::Container &selectedEntities, QWidget *parent)
bool computeStatParams(const ccHObject::Container &selectedEntities, QWidget *parent)
bool orientNormalsMST(const ccHObject::Container &selectedEntities, QWidget *parent)
bool interpolateColors(const ccHObject::Container &selectedEntities, QWidget *parent)
Interpolate colors from on entity and transfer them to another one.
bool rgbToGreyScale(const ccHObject::Container &selectedEntities)
bool ConvexHull(const ccHObject::Container &selectedEntities, ccHObject::Container &outEntities, QWidget *parent)
bool clearProperty(ccHObject::Container selectedEntities, CLEAR_PROPERTY property, QWidget *parent)
bool sfGaussianFilter(const ccHObject::Container &selectedEntities, ccPointCloud::RgbFilterOptions filterParams, QWidget *parent)
bool sfAddIdField(const ccHObject::Container &selectedEntities)
bool toggleProperty(const ccHObject::Container &selectedEntities, TOGGLE_PROPERTY property)
bool enhanceRGBWithIntensities(const ccHObject::Container &selectedEntities, QWidget *parent)
bool setColorGradient(const ccHObject::Container &selectedEntities, QWidget *parent)
bool exportCoordToSF(const ccHObject::Container &selectedEntities, QWidget *parent)
bool setColor(ccHObject::Container selectedEntities, bool colorize, QWidget *parent)
bool interpolateSFs(const ccHObject::Container &selectedEntities, ecvMainAppInterface *app)
Interpolate scalar fields from on entity and transfer them to another one.
bool RansacSegmentation(const ccHObject::Container &selectedEntities, ccHObject::Container &outEntities, QWidget *parent)
bool convertNormalsTo(const ccHObject::Container &selectedEntities, NORMAL_CONVERSION_DEST dest)
Converts a cloud's normals.
bool importToSF(const ccHObject::Container &selectedEntities, const std::vector< std::vector< ScalarType >> &scalarsVector, const std::string &name)
bool sfArithmetic(const ccHObject::Container &selectedEntities, QWidget *parent)
bool DBScanCluster(const ccHObject::Container &selectedEntities, QWidget *parent)
bool processMeshSF(const ccHObject::Container &selectedEntities, ccMesh::MESH_SCALAR_FIELD_PROCESS process, QWidget *parent)
bool sfConvertToRandomRGB(const ccHObject::Container &selectedEntities, QWidget *parent)
bool computeNormals(const ccHObject::Container &selectedEntities, QWidget *parent)
bool sfConvertToRGB(const ccHObject::Container &selectedEntities, QWidget *parent)
bool rgbGaussianFilter(const ccHObject::Container &selectedEntities, ccPointCloud::RgbFilterOptions filterParams, QWidget *parent)
bool ApplyScaleMatchingAlgorithm(ScaleMatchingAlgorithm algo, ccHObject::Container &entities, double icpRmsDiff, int icpFinalOverlap, unsigned refEntityIndex, QWidget *parent)
bool ApplyCCLibAlgorithm(CC_LIB_ALGORITHM algo, ccHObject::Container &entities, QWidget *parent, void **additionalParameters)
bool ComputeGeomCharacteristics(const GeomCharacteristicSet &characteristics, PointCoordinateType radius, ccHObject::Container &entities, const CCVector3 *roughnessUpDir, QWidget *parent)
ScaleMatchingAlgorithm
Scale matching algorithms.
std::vector< GeomCharacteristic > GeomCharacteristicSet
Set of GeomCharacteristic instances.
PointCoordinateType GetDefaultCloudKernelSize(ccGenericPointCloud *cloud, unsigned knn)
Returns a default first guess for algorithms kernel size (one cloud)
MemoryInfo getMemoryInfo()
Definition: MemoryInfo.cpp:63
void Resize(const core::Tensor &src_im, core::Tensor &dst_im, Image::InterpType interp_type)
Definition: IPPImage.cpp:94
float RadiansToDegrees(int radians)
Convert radians to degrees.
Definition: CVMath.h:71
bool GreaterThanEpsilon(float x)
Test a floating point number against our epsilon (a very small number).
Definition: CVMath.h:37
float DegreesToRadians(int degrees)
Convert degrees to radians.
Definition: CVMath.h:98
TRIANGULATION_TYPES
Triangulation types.
std::vector< ReferenceCloud * > ReferenceCloudContainer
A standard container to store several subsets of points.
bool LessThanEpsilon(float x)
Test a floating point number against our epsilon (a very small number).
Definition: CVMath.h:23
constexpr Rgb darkGrey(MAX/2, MAX/2, MAX/2)
constexpr Rgb green(0, MAX, 0)
QString defaultDocPath()
Shortcut for getting the documents location path.
Definition: ecvFileUtils.h:30
void DisplayLockedVerticesWarning(const QString &meshName, bool displayAsError)
Display a warning or error for locked verts.
Definition: ecvUtils.cpp:13
void swap(cloudViewer::core::SmallVectorImpl< T > &LHS, cloudViewer::core::SmallVectorImpl< T > &RHS)
Implement std::swap in terms of SmallVector swap.
Definition: SmallVector.h:1370
cloudViewer::NormalizedProgress * nProgress
cloudViewer::DgmOctree * octree
unsigned char octreeLevel
static bool DescendingCompOperator(const ComponentIndexAndSize &a, const ComponentIndexAndSize &b)
ComponentIndexAndSize(unsigned i, unsigned s)
Generic loading parameters.
Definition: FileIOFilter.h:51
CCVector3d * coordinatesShift
If applicable, applied shift on load (optional)
Definition: FileIOFilter.h:71
ecvGlobalShiftManager::Mode shiftHandlingMode
How to handle big coordinates.
Definition: FileIOFilter.h:64
QWidget * parentWidget
Parent widget (if any)
Definition: FileIOFilter.h:78
bool * coordinatesShiftEnabled
Whether shift on load has been applied after loading (optional)
Definition: FileIOFilter.h:69
Generic saving parameters.
Definition: FileIOFilter.h:84
QWidget * parentWidget
Parent widget (if any)
Definition: FileIOFilter.h:93
ccGenericPointCloud * cloud
Cloud.
Definition: ecv2DLabel.h:124
QSharedPointer< LensDistortionParameters > Shared
Shared pointer type.
Display context.
QSharedPointer< Grid > Shared
Shared type.
unsigned char level
subdivision level at which to apply the extraction process
Definition: DgmOctree.h:670
PointCoordinateType maxHalfLength
Cylinder (half) length.
Definition: DgmOctree.h:666
NeighboursSet neighbours
Neighbour points falling inside the cylinder.
Definition: DgmOctree.h:668
PointCoordinateType radius
Cylinder radius.
Definition: DgmOctree.h:664
CCVector3 dir
Cylinder axis (direction)
Definition: DgmOctree.h:662
Cloud-to-cloud "Hausdorff" distance computation parameters.
CONVERGENCE_TYPE convType
Convergence type.
NORMALS_MATCHING normalsMatching
Normals matching method.
int maxThreadCount
Maximum number of threads to use (0 = max)
bool useC2MSignedDistances
Whether to compute signed C2M distances.
Statistics on the edges connectivty of a mesh.
unsigned edgesSharedByTwo
Edges shared by exactly two triangles.
unsigned edgesNotShared
Edges not shared (i.e. used by only one triangle)
unsigned edgesSharedByMore
Edges shared by more than two triangles.
A scaled geometrical transformation (scale + rotation + translation)
CCVector3d apply(const CCVector3d &P) const
Applies the transformation to a point.
Triangle described by the indexes of its 3 vertices.
Precise statistics about current selection.
Definition: ecvDBRoot.h:28
size_t gblSensorCount
Definition: ecvDBRoot.h:43
size_t polylineCount
Definition: ecvDBRoot.h:37
size_t cameraSensorCount
Definition: ecvDBRoot.h:44
unsigned displayedNumPrecision
Displayed numbers precision.
Backup "context" for an object.