From 5d74d9e1534bdb8b3e853c79df66d22aca504260 Mon Sep 17 00:00:00 2001 From: matlabbe Date: Fri, 3 Jul 2026 19:24:45 -0700 Subject: [PATCH] Added dialogs when starting/stopping sensors/detection. Fixed nullptr bug on MainWindow::handleEvents --- app/src/main.cpp | 4 ++ guilib/src/MainWindow.cpp | 35 ++++++++++++- guilib/src/PreferencesDialog.cpp | 84 +++++++++++++++++++++++++++++--- tools/CameraRGBD/main.cpp | 5 +- tools/OdometryViewer/main.cpp | 3 ++ 5 files changed, 121 insertions(+), 10 deletions(-) diff --git a/app/src/main.cpp b/app/src/main.cpp index ce529a7b..ae433431 100644 --- a/app/src/main.cpp +++ b/app/src/main.cpp @@ -52,6 +52,10 @@ int main(int argc, char* argv[]) ULogger::setLevel(ULogger::kWarning); #ifdef WIN32 + // STA (single-threaded apartment) is required for the native file dialogs / File Explorer + // (see commit d75cc04, "Fixed File Explorer hanging (Qt 5.12)"). Do NOT switch to MTA + // (CoInitializeEx COINIT_MULTITHREADED): it deadlocks the + // native file dialogs. CoInitialize(nullptr); #endif diff --git a/guilib/src/MainWindow.cpp b/guilib/src/MainWindow.cpp index 8daf26b2..93641985 100644 --- a/guilib/src/MainWindow.cpp +++ b/guilib/src/MainWindow.cpp @@ -78,6 +78,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #include #include #include @@ -958,7 +959,7 @@ bool MainWindow::handleEvent(UEvent* anEvent) else { Q_EMIT cameraInfoReceived(sensorEvent->info()); - if (_odomThread == 0 && (_sensorCapture->odomProvided()) && _preferencesDialog->isRGBDMode()) + if (_odomThread == 0 && _sensorCapture && _sensorCapture->odomProvided() && _preferencesDialog->isRGBDMode()) { OdometryInfo odomInfo; odomInfo.reg.covariance = sensorEvent->info().odomCovariance; @@ -5932,6 +5933,18 @@ void MainWindow::startDetection() Camera * camera = 0; Lidar * lidar = 0; + // Creating the sensors below opens the devices (createLidar/createCamera/createOdomSensor -> + // init(), a few seconds for ZED/RealSense) on the GUI thread; show a busy dialog (min==max==0 + // => indeterminate) so the window isn't just frozen. Hidden once all sensors are created below. + QProgressDialog progress(tr("Starting detection..."), QString(), 0, 0, this); + progress.setWindowModality(Qt::ApplicationModal); + progress.setCancelButton(0); + progress.setMinimumDuration(0); + progress.setValue(0); + progress.show(); + QApplication::processEvents(); + QApplication::processEvents(); // make sure it is drawn + if(_preferencesDialog->getLidarSourceDriver() != PreferencesDialog::kSrcUndef) { lidar = _preferencesDialog->createLidar(); @@ -5989,6 +6002,8 @@ void MainWindow::startDetection() } } + progress.hide(); // all sensors created/opened + _sensorCapture = new SensorCaptureThread(lidar, camera, odomSensor, extrinsics, poseTimeOffset, scaleFactor, waitTime, parameters); _sensorCapture->setOdomAsGroundTruth(_preferencesDialog->isOdomSensorAsGt()); @@ -6229,6 +6244,24 @@ void MainWindow::stopDetection() } ULOGGER_DEBUG(""); + + // Closing the camera runs on the GUI thread in "delete _sensorCapture" below (via + // ~SensorCaptureThread -> ~Camera::close()) and can block for a while - e.g. the first 2-3 + // RealSense closes per launch stall ~20s in the Motion Module stop() (librealsense warm-up). + // Show a busy dialog (min==max==0 => indeterminate) so the window isn't just frozen. It is + // declared here so it stays visible across the joins/deletes and closes on scope exit. + QProgressDialog progress(tr("Stopping detection..."), QString(), 0, 0, this); + if(_sensorCapture) + { + progress.setWindowModality(Qt::ApplicationModal); + progress.setCancelButton(0); + progress.setMinimumDuration(0); + progress.setValue(0); + progress.show(); + QApplication::processEvents(); + QApplication::processEvents(); // make sure it is drawn + } + // kill the processes if(_imuThread) { diff --git a/guilib/src/PreferencesDialog.cpp b/guilib/src/PreferencesDialog.cpp index ebc22d04..d76733cf 100644 --- a/guilib/src/PreferencesDialog.cpp +++ b/guilib/src/PreferencesDialog.cpp @@ -49,6 +49,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include +#include #include #include #include @@ -6837,11 +6839,11 @@ double PreferencesDialog::getSourceScanForceGroundNormalsUp() const Camera * PreferencesDialog::createCamera(bool useRawImages, bool useColor) { return createCamera( - this->getSourceDriver(), - _ui->lineEdit_sourceDevice->text(), - _ui->lineEdit_calibrationFile->text(), - useRawImages, - useColor, + this->getSourceDriver(), + _ui->lineEdit_sourceDevice->text(), + _ui->lineEdit_calibrationFile->text(), + useRawImages, + useColor, false, false); } @@ -7745,7 +7747,20 @@ void PreferencesDialog::setSLAMMode(bool enabled) void PreferencesDialog::testOdometry() { + // One progress dialog, reused for both the (slow) camera init and the (slow) close. + // Declared here so it outlives cameraThread below and stays visible while cameraThread's + // destructor closes the device at function scope end. + QProgressDialog progress(tr("Starting camera..."), QString(), 0, 0, this); + progress.setWindowModality(Qt::ApplicationModal); + progress.setCancelButton(0); + progress.setMinimumDuration(0); + progress.setValue(0); + progress.show(); + QApplication::processEvents(); + QApplication::processEvents(); // make sure it is drawn + Camera * camera = this->createCamera(); + progress.hide(); if(!camera) { return; @@ -7861,6 +7876,7 @@ void PreferencesDialog::testOdometry() } odomViewer->exec(); + UDEBUG("Dialog closed, stopping sensor..."); // Tear down the pipes first so no more events are routed to the threads/viewer being // destroyed, then stop the threads, then delete the viewer. This avoids delivering @@ -7877,6 +7893,14 @@ void PreferencesDialog::testOdometry() { imuThread->join(true); } + + // Reuse the same dialog for the close. The device close() runs in cameraThread's destructor + // at function scope end (not in join()), so 'progress' stays visible across it. On Windows + // the first 2-3 RealSense closes per launch stall ~20s in the Motion Module stop(). + progress.setLabelText(tr("Closing camera...")); + progress.show(); + QApplication::processEvents(); + QApplication::processEvents(); // make sure it is drawn cameraThread.join(true); odomThread.join(true); @@ -7900,7 +7924,21 @@ void PreferencesDialog::testCamera() window->resize(1280, 480+QPushButton().minimumHeight()); window->registerToEventsManager(); + // One progress dialog, reused for both the (slow) camera init and the (slow) close. + // min==max==0 => indeterminate/busy bar. Declared in this outer scope so it outlives + // cameraThread below and stays visible while cameraThread's destructor closes the device. + QProgressDialog progress(tr("Starting camera..."), QString(), 0, 0, this); + progress.setWindowModality(Qt::ApplicationModal); + progress.setCancelButton(0); + progress.setMinimumDuration(0); + progress.setValue(0); + progress.show(); + QApplication::processEvents(); + QApplication::processEvents(); // make sure it is drawn + + // createCamera() init()s the device on the GUI thread (required by ZED) and takes a few seconds. Camera * camera = this->createCamera(); + progress.hide(); if(camera) { SensorCaptureThread cameraThread(camera, this->getAllParameters()); @@ -7945,8 +7983,18 @@ void PreferencesDialog::testCamera() cameraThread.start(); window->exec(); + UDEBUG("Dialog closed, stopping sensor..."); UEventsManager::removePipe(&cameraThread, window, "SensorEvent"); - cameraThread.join(true); + + // Reuse the same dialog for the close. The device close() runs in cameraThread's + // destructor at scope end (not in join()), so 'progress' - declared in the outer scope - + // stays visible across it. On Windows the first 2-3 RealSense closes per launch stall + // ~20s in the Motion Module stop() (librealsense warm-up); this keeps the user informed. + progress.setLabelText(tr("Closing camera...")); + progress.show(); + QApplication::processEvents(); + QApplication::processEvents(); // make sure it is drawn + cameraThread.join(true); // cameraThread's destructor (scope end) closes the device // deleteLater() (not delete): defer destruction to the event loop so Qt finishes // tearing down the OpenGL widget's context and window-proc subclass and drains // pending activation messages first. @@ -8428,7 +8476,20 @@ void PreferencesDialog::testLidar() window->registerToEventsManager(); window->setDecimation(1); + // One progress dialog, reused for both the (slow) sensor init and the (slow) close. + // Declared in this outer scope so it outlives lidarThread below and stays visible while + // lidarThread's destructor closes the device. + QProgressDialog progress(tr("Starting sensor..."), QString(), 0, 0, this); + progress.setWindowModality(Qt::ApplicationModal); + progress.setCancelButton(0); + progress.setMinimumDuration(0); + progress.setValue(0); + progress.show(); + QApplication::processEvents(); + QApplication::processEvents(); // make sure it is drawn + Lidar * lidar = this->createLidar(); + progress.hide(); if(lidar) { SensorCaptureThread lidarThread(lidar, this->getAllParameters()); @@ -8447,8 +8508,17 @@ void PreferencesDialog::testLidar() lidarThread.start(); window->exec(); + UDEBUG("Dialog closed, stopping sensor..."); UEventsManager::removePipe(&lidarThread, window, "SensorEvent"); - lidarThread.join(true); + + // Reuse the same dialog for the close. The device close() runs in lidarThread's + // destructor at scope end (not in join()), so 'progress' - declared in the outer + // scope - stays visible across it. + progress.setLabelText(tr("Closing sensor...")); + progress.show(); + QApplication::processEvents(); + QApplication::processEvents(); // make sure it is drawn + lidarThread.join(true); // lidarThread's destructor (scope end) closes the device // deleteLater() (not delete): see testCamera() - avoids a dangling OpenGL platform // window that crashes in QWindowsWindow::alertWindow when Preferences later closes. window->deleteLater(); diff --git a/tools/CameraRGBD/main.cpp b/tools/CameraRGBD/main.cpp index adfa5e9c..1d701b39 100644 --- a/tools/CameraRGBD/main.cpp +++ b/tools/CameraRGBD/main.cpp @@ -97,7 +97,7 @@ void sighandler(int sig) int main(int argc, char * argv[]) { ULogger::setType(ULogger::kTypeConsole); - ULogger::setLevel(ULogger::kInfo); + ULogger::setLevel(ULogger::kDebug); //ULogger::setPrintTime(false); //ULogger::setPrintWhere(false); @@ -272,7 +272,7 @@ int main(int argc, char * argv[]) UERROR("Not built with ZED sdk support..."); exit(-1); } - camera = new rtabmap::CameraStereoZed(deviceId.empty()?0:uStr2Int(deviceId), -1, 1, 100, false, rate); + camera = new rtabmap::CameraStereoZed(deviceId.empty()?0:uStr2Int(deviceId), -1, 1, 0, 100, false, rate); } else if (driver == 9) { @@ -523,5 +523,6 @@ int main(int argc, char * argv[]) cameraViewer.exec(); cameraThread.join(true); } + printf("Exiting cleanly.\n"); return 0; } diff --git a/tools/OdometryViewer/main.cpp b/tools/OdometryViewer/main.cpp index 67c65a0d..5729ad1c 100644 --- a/tools/OdometryViewer/main.cpp +++ b/tools/OdometryViewer/main.cpp @@ -404,13 +404,16 @@ int main (int argc, char * argv[]) { printf("The camera is not calibrated! You should calibrate the camera first.\n"); delete camera; + return 1; } } else { printf("Failed to initialize the camera! Please select another driver (see \"--help\").\n"); delete camera; + return 1; } + printf("Exiting cleanly.\n"); return 0; }