diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 69a03ccc..dcfef2ad 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -26,8 +26,6 @@ SET( ${CMAKE_CURRENT_SOURCE_DIR}/translations/Dyt_zh_CN.ts ) -# 控制是否在构建时自动运行 lupdate 更新 TS 文件。 -# 关闭时仅在 TS 变更后编译生成 QM,避免每次构建都重新创建翻译文件。 option(UPDATE_TRANSLATIONS "Run lupdate to refresh TS files during build" OFF) if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) if(UPDATE_TRANSLATIONS) @@ -205,7 +203,7 @@ endif() SET_TARGET_PROPERTIES(${PROJECT_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${ProjectDIR}/bin) TARGET_LINK_LIBRARIES(${PROJECT_NAME}) -SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /SUBSYSTEM:CONSOLE") +# SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /SUBSYSTEM:CONSOLE") add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD diff --git a/src/Dyt.qrc b/src/Dyt.qrc index 95741fca..d90988dd 100644 --- a/src/Dyt.qrc +++ b/src/Dyt.qrc @@ -6,6 +6,7 @@ res/sys_min.png res/sys_restore.png res/sys_icon.png + res/sys_icon.ico res/sys_down.png res/sys_up.png res/select_file.ico diff --git a/src/app/Application.cpp b/src/app/Application.cpp index 12b026e3..bf6563c0 100644 --- a/src/app/Application.cpp +++ b/src/app/Application.cpp @@ -18,6 +18,7 @@ Application::Application(int& argc, char** argv, int /*= ApplicationFlags*/) } Application::~Application() { + Uninit(); } QString Application::GetWorkSpacePath() { @@ -30,6 +31,15 @@ QString Application::GetBinPath() { } void Application::Init() { + // Set application/taskbar icon early so all top-level windows inherit it + QIcon appIcon(":/res/sys_icon.ico"); + if (!appIcon.isNull()) { + setWindowIcon(appIcon); + } else { + // Fallback to PNG if ICO is not embedded + setWindowIcon(QIcon(":/res/sys_icon.png")); + } + Singleton::Create(this); Singleton::Create(this); Singleton::Create(this); @@ -53,8 +63,8 @@ void Application::Uninit() { timer_.stop(); } //Singleton::Destory(); - Singleton::Destory(); Singleton::Destory(); + Singleton::Destory(); Singleton::Destory(); Singleton::Destory(); Singleton::Destory(); diff --git a/src/entities/EntitiesManager.cpp b/src/entities/EntitiesManager.cpp index 58ca9916..b14cd5db 100644 --- a/src/entities/EntitiesManager.cpp +++ b/src/entities/EntitiesManager.cpp @@ -31,6 +31,10 @@ void EntitiesManager::OnDestory() { } ); for (auto* entity : entities) { + // Detach scene graph nodes before deletion + if (auto* root = entity->GetRootComponent()) { + root->OnDestroy(); + } RemoveEntity(entity); // Delete entities immediately to release scene graph resources before exit delete entity; @@ -139,8 +143,16 @@ bool EntitiesManager::DeleteEntity(Entity* entity) { return false; } + // Ensure scene graph nodes are detached before deleting the entity + if (auto* root = entity->GetRootComponent()) { + root->OnDestroy(); + } + RemoveEntity(entity); - entity->deleteLater(); + // Immediate deletion to ensure scene graph resources are released + // before viewer/context teardown. Using deleteLater() may invoke + // QObject cleanup after OSG/GL objects are gone, causing crashes. + delete entity; return true; } diff --git a/src/entities/SceneComponent.cpp b/src/entities/SceneComponent.cpp index e20a2e34..9591357c 100644 --- a/src/entities/SceneComponent.cpp +++ b/src/entities/SceneComponent.cpp @@ -210,13 +210,37 @@ void SceneComponent::RemoveRender() { if (nullptr == mt_) { return; } - int count = mt_->getNumParents(); - for (int i = 0; i < count; ++i) { + + for (auto child : children_) { + child->RemoveRender(); + } + + // If attached via GeoTransform (osgEarth), remove the GeoTransform from the scene +#ifndef USE_OCEAN + if (geo_.valid()) { + for (int i = geo_->getNumParents() - 1; i >= 0; --i) { + osg::Group* parent = geo_->getParent(i)->asGroup(); + if (nullptr != parent) { + parent->removeChild(geo_.get()); + } + } + // Break the child link to mt_ to avoid lingering references + geo_->removeChild(mt_.get()); + } +#endif + + for (int i = mt_->getNumParents() - 1; i >= 0; --i) { osg::Group* parent = mt_->getParent(i)->asGroup(); if (nullptr != parent) { parent->removeChild(mt_); } } + + // Release local references proactively +#ifndef USE_OCEAN + geo_ = nullptr; +#endif + mt_ = nullptr; } void SceneComponent::RemoveParent() { diff --git a/src/main.cpp b/src/main.cpp index fe1903a2..ea581800 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -12,6 +12,10 @@ #include #include #include +#include +#include +#include +#include #include #include @@ -22,7 +26,6 @@ #include #include #include -// #include #include "osgqt/GraphicsWindowQt.h" #include "scene/ui/CompositeWidgetManager.h" @@ -32,370 +35,48 @@ #define LC "DYT" #endif -const unsigned int MASK_2D = 0xF0000000; -std::string path = "D:\\Project\\DYT\\Source\\bin\\Release\\data\\"; - -bool scrollWindow(osgWidget::Event& ev) { - // The first thing we need to do is make sure we have a Frame object... - osgWidget::Frame* frame = dynamic_cast(ev.getWindow()); - - if(!frame) return false; - - // And now we need to make sure our Frame has a valid internal EmbeddedWindow widget. - osgWidget::Window::EmbeddedWindow* ew = - dynamic_cast(frame->getEmbeddedWindow()) - ; - - if(!ew) return false; - - // Lets get the visible area so that we can use it to make sure our scrolling action - // is necessary in the first place. - const osgWidget::Quad& va = ew->getWindow()->getVisibleArea(); - - // The user wants to scroll up; make sure that the visible area's Y origin isn't already - // at 0.0f, 0.0f. - if(ev.getWindowManager()->isMouseScrollingUp() && va[1] != 0.0f) - ew->getWindow()->addVisibleArea(0, -20) - ; - - else if(va[1] <= (ew->getWindow()->getHeight() - ew->getHeight())) - ew->getWindow()->addVisibleArea(0, 20) - ; - - // We need to manually call update to make sure the visible area scissoring is done - // properly. - frame->update(); - - return true; -} - -bool changeTheme(osgWidget::Event& ev) { - std::string theme; - - if(ev.key == osgGA::GUIEventAdapter::KEY_Right) - theme = "osgWidget/theme-1.png" - ; - - else if(ev.key == osgGA::GUIEventAdapter::KEY_Left) - theme = "osgWidget/theme-2.png" - ; - - else return false; - - osgWidget::Frame* frame = dynamic_cast(ev.getWindow()); - - if(!frame) return false; - - // This is just one way to access all our Widgets; we could just as well have used: - // - // for(osgWidget::Frame::Iterator i = frame.begin(); i != frame.end() i++) {} - // - // ...and it have worked, too. - for(unsigned int row = 0; row < 3; row++) { - for(unsigned int col = 0; col < 3; col++) { - frame->getByRowCol(row, col)->setImage(theme); - } - } - - return true; -} - - -osg::Node* createUiExample(osgViewer::View* viewer, osgWidget::WindowManager* wm) { - if(!wm) return nullptr; - - // viewer.setUpViewInWindow( - // 50, - // 50, - // static_cast(wm->getWidth()), - // static_cast(wm->getHeight()) - // ); - - osg::Group* group = new osg::Group(); - osg::Camera* camera = wm->createParentOrthoCamera(); - - group->addChild(camera); - - viewer->addEventHandler(new osgWidget::MouseHandler(wm)); - viewer->addEventHandler(new osgWidget::KeyboardHandler(wm)); - viewer->addEventHandler(new osgWidget::ResizeHandler(wm, camera)); - viewer->addEventHandler(new osgWidget::CameraSwitchHandler(wm, camera)); - viewer->addEventHandler(new osgViewer::StatsHandler()); - viewer->addEventHandler(new osgViewer::WindowSizeHandler()); - viewer->addEventHandler(new osgGA::StateSetManipulator( - viewer->getCamera()->getOrCreateStateSet() - )); - - wm->resizeAllWindows(); - return group; -} - -void creatWidget(osgViewer::View* viewer, osg::Group* root) { - osgWidget::WindowManager* wm = new osgWidget::WindowManager( - viewer, - 1280.0f, - 1024.0f, - MASK_2D, - osgWidget::WindowManager::WM_PICK_DEBUG - //osgWidget::WindowManager::WM_NO_INVERT_Y - ); - - osgWidget::Frame* frame = osgWidget::Frame::createSimpleFrameFromTheme( - "frame", - osgDB::readRefImageFile("osgWidget/theme.png"), - 40.0f, - 40.0f, - osgWidget::Frame::FRAME_ALL - ); - osgWidget::Box* box = new osgWidget::Box("images", osgWidget::Box::VERTICAL); - osgWidget::Widget* img1 = new osgWidget::Widget("im1", 512.0f, 512.0f); - osgWidget::Widget* img2 = new osgWidget::Widget("im2", 512.0f, 512.0f); - osgWidget::Widget* img3 = new osgWidget::Widget("im3", 512.0f, 512.0f); - osgWidget::Widget* img4 = new osgWidget::Widget("im4", 512.0f, 512.0f); - - img1->setImage(path + "osgWidget/scrolled1.jpg", true); - img2->setImage(path + "osgWidget/scrolled2.jpg", true); - img3->setImage(path + "osgWidget/scrolled3.jpg", true); - img4->setImage(path + "osgWidget/scrolled4.jpg", true); - - img1->setMinimumSize(10.0f, 10.0f); - img2->setMinimumSize(10.0f, 10.0f); - img3->setMinimumSize(10.0f, 10.0f); - img4->setMinimumSize(10.0f, 10.0f); - - box->addWidget(img1); - box->addWidget(img2); - box->addWidget(img3); - box->addWidget(img4); - box->setEventMask(osgWidget::EVENT_NONE); - - //frame->getEmbeddedWindow()->setWindow(box); - frame->setWindow(box); - frame->getEmbeddedWindow()->setColor(1.0f, 1.0f, 1.0f, 1.0f); - frame->resize(300.0f, 300.0f); - frame->addCallback(new osgWidget::Callback(&scrollWindow, osgWidget::EVENT_MOUSE_SCROLL)); - frame->addCallback(new osgWidget::Callback(&changeTheme, osgWidget::EVENT_KEY_DOWN)); - - wm->addChild(frame); - - osg::Node* ui = createUiExample(viewer, wm); - root->addChild(ui); -} - -void configureView( osgViewer::View* view ) -{ - // default uniform values: - // GLUtils::setGlobalDefaults(view->getCamera()->getOrCreateStateSet()); - - // add some stock OSG handlers: - view->addEventHandler(new osgViewer::StatsHandler()); - view->addEventHandler(new osgViewer::WindowSizeHandler()); - view->addEventHandler(new osgViewer::ThreadingHandler()); - view->addEventHandler(new osgViewer::LODScaleHandler()); - view->addEventHandler(new osgGA::StateSetManipulator(view->getCamera()->getOrCreateStateSet())); - view->addEventHandler(new osgViewer::RecordCameraPathHandler()); - view->addEventHandler(new osgViewer::ScreenCaptureHandler()); -} - - -osg::Node* LoadEarth(const std::string& earth, osgViewer::CompositeViewer* viewer) { - osg::ref_ptr node = osgDB::readNodeFile(earth); - - osg::ref_ptr mapNode = osgEarth::MapNode::get(node.get()); - if ( !mapNode.valid() ) - { - OE_WARN << LC << "Loaded scene graph does not contain a MapNode - aborting" << std::endl; - return 0L; - } - - // collect the views - osgViewer::Viewer::Views views; - if (viewer) - { - viewer->getViews(views); - } - - // warn about not having an earth manip - for (osgViewer::Viewer::Views::iterator view = views.begin(); view != views.end(); ++view) - { - osgEarth::Util::EarthManipulator* manip = dynamic_cast((*view)->getCameraManipulator()); - if ( manip == 0L ) - { - OE_WARN << LC << "Helper used before installing an EarthManipulator" << std::endl; - } - } - - // a root node to hold everything: - osg::Group* root = new osg::Group(); - - root->addChild( node ); - - // parses common cmdline arguments and apply to the first view: - // if ( !views.empty() ) - // { - // parse( mapNode.get(), args, views.front(), root, userContainer ); - // - // float lodscale; - // if (args.read("--lodscale", lodscale)) - // { - // LODScaleGroup* g = new LODScaleGroup(); - // g->setLODScaleFactor(osg::maximum(lodscale, 0.0001f)); - // osgEarth::insertGroup(g, mapNode->getParent(0)); - // OE_NOTICE << "LOD Scale set to: " << lodscale << std::endl; - // } - // } - - // configures each view with some stock goodies - for (osgViewer::Viewer::Views::iterator view = views.begin(); view != views.end(); ++view) - { - configureView( *view ); - } - -#ifdef OSG_GL3_AVAILABLE - if (viewer) - { - viewer->setRealizeOperation(new GL3RealizeOperation()); - } -#endif - - return root; -} - -class ViewerWidget : public QWidget, public osgViewer::CompositeViewer -{ -public: - ViewerWidget(QWidget* parent = 0, Qt::WindowFlags f = 0, osgViewer::ViewerBase::ThreadingModel threadingModel=osgViewer::CompositeViewer::SingleThreaded) : QWidget(parent, f) - { - setThreadingModel(threadingModel); - - // disable the default setting of viewer.done() by pressing Escape. - setKeyEventSetsDone(0); - - std::string earthPath = "D:/Project/DYT/Tool/TritonSample/TritonSample/triton.earth"; - // osg::Node* node = osgDB::readNodeFile(); - osg::Node* node =LoadEarth(earthPath, this); - if ( !node ) { - return; - } - - - // Group to hold all our annotation elements. - // osg::Group* annoGroup = new osg::Group(); - // osgEarth::MapNode::get(node)->addChild( annoGroup ); - - QWidget* widget1 = addViewWidget( createGraphicsWindow(0,0,100,100), node); - // QWidget* widget2 = addViewWidget( createGraphicsWindow(0,0,100,100), osgDB::readRefNodeFile("glider.osgt") ); - // QWidget* widget3 = addViewWidget( createGraphicsWindow(0,0,100,100), osgDB::readRefNodeFile("axes.osgt") ); - // QWidget* widget4 = addViewWidget( createGraphicsWindow(0,0,100,100), osgDB::readRefNodeFile("fountain.osgt") ); - // QWidget* popupWidget = addViewWidget( createGraphicsWindow(900,100,320,240,"Popup window",true), osgDB::readRefNodeFile("dumptruck.osgt") ); - // popupWidget->show(); - - - - QGridLayout* grid = new QGridLayout; - grid->addWidget( widget1, 0, 0 ); - // grid->addWidget( widget2, 0, 1 ); - // grid->addWidget( widget3, 1, 0 ); - // grid->addWidget( widget4, 1, 1 ); - setLayout( grid ); - - connect( &_timer, SIGNAL(timeout()), this, SLOT(update()) ); - _timer.start( 10 ); - } - - QWidget* addViewWidget( osgQt::GraphicsWindowQt* gw, osg::ref_ptr scene ) - { - osgViewer::View* view = new osgViewer::View; - addView( view ); - view->setCameraManipulator( new osgEarth::Util::EarthManipulator() ); - configureView(view); - - osg::Camera* camera = view->getCamera(); - camera->setGraphicsContext( gw ); - - const osg::GraphicsContext::Traits* traits = gw->getTraits(); - - camera->setClearColor( osg::Vec4(0.2, 0.2, 0.6, 1.0) ); - camera->setViewport( new osg::Viewport(0, 0, traits->width, traits->height) ); - camera->setProjectionMatrixAsPerspective(30.0f, static_cast(traits->width)/static_cast(traits->height), 1.0f, 10000.0f ); - - creatWidget(view, scene->asGroup()); - - osgEarth::MapNode* mapNode = osgEarth::MapNode::findMapNode(scene); - if ( !mapNode ) - return gw->getGLWidget(); - - auto skyDome_ = osgEarth::Util::SkyNode::create(mapNode); - if (!mapNode) { - LOG_WARN("eart map node is nullptr"); - return gw->getGLWidget(); - } - skyDome_->attach(view); - skyDome_->getSunLight()->setAmbient(osg::Vec4(0.5,0.5,0.5,1.0)); - scene->asGroup()->addChild(skyDome_); - - skyDome_->setDateTime(osgEarth::DateTime(2024, 12, 24, 3)); - - view->setSceneData( scene ); - view->addEventHandler( new osgViewer::StatsHandler ); - // view->setCameraManipulator( new osgGA::MultiTouchTrackballManipulator ); - gw->setTouchEventsEnabled( true ); - return gw->getGLWidget(); - } - - osgQt::GraphicsWindowQt* createGraphicsWindow( int x, int y, int w, int h, const std::string& name="", bool windowDecoration=false ) - { - osg::DisplaySettings* ds = osg::DisplaySettings::instance().get(); - osg::ref_ptr traits = new osg::GraphicsContext::Traits; - traits->windowName = name; - traits->windowDecoration = windowDecoration; - traits->x = x; - traits->y = y; - traits->width = w; - traits->height = h; - traits->doubleBuffer = true; - traits->alpha = ds->getMinimumNumAlphaBits(); - traits->stencil = ds->getMinimumNumStencilBits(); - traits->sampleBuffers = ds->getMultiSamples(); - traits->samples = ds->getNumMultiSamples(); - - return new osgQt::GraphicsWindowQt(traits.get()); - } - - virtual void paintEvent( QPaintEvent* /*event*/ ) - { frame(); } - -protected: - - QTimer _timer; -}; - int main(int argc, char* argv[]) { SpdLogger logger("logs/log.txt", 5); - // - Application::setAttribute(Qt::AA_EnableHighDpiScaling); - //// + + Application::setAttribute(Qt::AA_EnableHighDpiScaling); + Application app(argc, argv); - app.setAttribute(Qt::AA_DontCreateNativeWidgetSiblings); - // InstallCrashHandler(); - // - RecourceHelper::ChangeSkin("default"); - - MainFrame mainWindow; - mainWindow.showMaximized(); - // + app.setAttribute(Qt::AA_DontCreateNativeWidgetSiblings); + InstallCrashHandler(); + + // Single-instance guard to avoid multiple launches from repeated clicks + const QString lockPath = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/Dyt_app.lock"; + QLockFile lock(lockPath); + lock.setStaleLockTime(0); + if (!lock.tryLock(1)) { + // Another instance is starting or running; exit quietly + return 0; + } + + // Show splash screen immediately to improve perceived startup speed + QString splashPath = RecourceHelper::Get().GetBasePath() + "/resources/splash.png"; + QPixmap splashPixmap = QPixmap(splashPath).scaled(640, 480, Qt::KeepAspectRatio, Qt::SmoothTransformation); + QSplashScreen splash(splashPixmap); + splash.showMessage(("正在启动..."), Qt::AlignHCenter | Qt::AlignBottom, Qt::white); + splash.show(); + app.processEvents(); + + RecourceHelper::ChangeSkin("default"); + splash.showMessage(("正在加载界面皮肤..."), Qt::AlignHCenter | Qt::AlignBottom, Qt::white); + app.processEvents(); + + splash.showMessage(("正在加载数据..."), Qt::AlignHCenter | Qt::AlignBottom, Qt::white); + app.processEvents(); + + + MainFrame mainWindow; + splash.showMessage(("正在创建主窗口..."), Qt::AlignHCenter | Qt::AlignBottom, Qt::white); + mainWindow.showMaximized(); + splash.finish(&mainWindow); + int ret = app.exec(); - app.Uninit(); + // app.Uninit(); + Sleep(200); return ret; - //osg::ArgumentParser arguments(&argc, argv); - - //QMainWindow* mainWindow = new QMainWindow; - //OsgWidget* viewWidget = new OsgWidget(nullptr, Qt::Widget); - //mainWindow->setCentralWidget(viewWidget); - //// ViewerWidget* viewWidget = new ViewerWidget(nullptr, Qt::Widget, threadingModel); - ////viewWidget->setGeometry( 100, 100, 800, 600 ); - //viewWidget->Initialize(); - //mainWindow->show(); - //return app.exec(); } diff --git a/src/scene/MeshManager.cpp b/src/scene/MeshManager.cpp index 2dd4d561..2d970d40 100644 --- a/src/scene/MeshManager.cpp +++ b/src/scene/MeshManager.cpp @@ -41,16 +41,29 @@ MeshManager::~MeshManager(void) { } void MeshManager::OnDestory() { - + // Defer destruction of cached nodes to process exit to avoid + // shutdown-order crashes inside OSG ref_ptr/Drawable destructors. + // We intentionally leak the cache at exit; acceptable for application shutdown. + LOG_INFO("MeshManager::OnDestory - deferring cache release to process exit"); + static NodeMap* leakedCache = nullptr; + if (!leakedCache) leakedCache = new NodeMap(); + leakedCache->swap(nodes_); } osg::MatrixTransform* MeshManager::ReadNode(const std::string& file) { LOG_INFO("load node:{}", file); const auto iter = nodes_.find(file); if (nodes_.end() != iter) { - osg::MatrixTransform* mt = new osg::MatrixTransform; - mt->addChild(iter->second); - return mt; + // Guard against previously cleared cache entries + osg::ref_ptr cached = iter->second; + if (cached.valid()) { + osg::MatrixTransform* mt = new osg::MatrixTransform; + mt->addChild(cached.get()); + return mt; + } else { + // Remove invalid entry and fall through to reload + nodes_.erase(iter); + } } osg::Node* node = osgDB::readNodeFile(file); diff --git a/src/scene/OEScene.cpp b/src/scene/OEScene.cpp index 6b04f3fc..b2a4d40d 100644 --- a/src/scene/OEScene.cpp +++ b/src/scene/OEScene.cpp @@ -59,6 +59,12 @@ void OEScene::DetachView(osgViewer::View* view) { // Remove sky dome from this group if present; SkyNode has no detach in osgEarth 2.8 if (skyDome_.valid()) { + // Proactively disable lighting/effects to reduce interactions with the View + skyDome_->setLighting(osg::StateAttribute::OFF); + skyDome_->setSunVisible(false); + skyDome_->setMoonVisible(false); + skyDome_->setStarsVisible(false); + skyDome_->setAtmosphereVisible(false); removeChild(skyDome_.get()); skyDome_ = nullptr; } diff --git a/src/scene/OEScene.h b/src/scene/OEScene.h index f3ba50c6..3f5524f8 100644 --- a/src/scene/OEScene.h +++ b/src/scene/OEScene.h @@ -30,6 +30,10 @@ public: } OESceneUI* GetOrCreateSceneUI(); + // Read-only accessor to avoid creating UI during teardown + OESceneUI* GetSceneUI() const { + return sceneUI_.get(); + } osgEarth::Util::EarthManipulator* GetManipulater() const { return earthManipulator_.get(); } diff --git a/src/viewer/OsgWidget.cpp b/src/viewer/OsgWidget.cpp index 1c190217..181b8b90 100644 --- a/src/viewer/OsgWidget.cpp +++ b/src/viewer/OsgWidget.cpp @@ -110,7 +110,13 @@ void OsgWidget::Initialize() { void OsgWidget::Uninitialize() { LOG_INFO("OsgWidget::Uninitialize"); if (nullptr != viewUI_) { - viewUI_->RemoveUI(activeScene_->GetOrCreateSceneUI()); + // Avoid creating UI during teardown; only remove if it exists + if (activeScene_.valid()) { + OESceneUI* sceneUI = activeScene_->GetSceneUI(); + if (sceneUI) { + viewUI_->RemoveUI(sceneUI); + } + } viewUI_ = nullptr; } if (nullptr != activeScene_) { diff --git a/src/workspace/WorkSpaceManager.cpp b/src/workspace/WorkSpaceManager.cpp index 4362aae4..f890ad9c 100644 --- a/src/workspace/WorkSpaceManager.cpp +++ b/src/workspace/WorkSpaceManager.cpp @@ -25,11 +25,15 @@ void WorkSpaceManager::OnDestory() { WorkSpace* ws = kv.second; if (ws) { ws->Unlaod(); - ws->deleteLater(); + // Delete workspaces immediately to prevent deferred Qt cleanup + // after viewer/context teardown, which can lead to crashes. + delete ws; } } workSpaces_.clear(); + // Clear dangling pointers + current_ = nullptr; scene_ = nullptr; }