#include "zxLogInstance.h" #include "ZxBaHelper.h" #include "ZxResolutionHelper.h" #include "zxSysUtils.h" #include "ZxPtyDock.h" #include "iDockBaseWx.h" #include "ZxMainWindow.h" #include "ZxSubAxisX.h" #include "ZxSubAxisY.h" #include "ZxPlot.h" #include "ZxObjCurve.h" #include "ZxSubTitle.h" #include "tCurvePlotScene.h" #include "tCurvePlotView.h" #include "ZxTableView.h" #include "ZxTableModel.h" #include "mModuleDefines.h" #include "ZxDataWell.h" #include "ZxDataProject.h" #ifdef QT_DEBUG #include "ZxDataGaugeP.h" #endif #ifdef QT_DEBUG #include "nmXmlHelper.h" #endif #include "nmSubWndGrid.h" #include "nmDataDemo.h" #include "nmDataLogFile.h" #include "nmGuiPlot.h" #include "nmWxGridVTKContainerWidget.h" #include "nmCalculationUtils.h" #include "nmCalculationPebiGrid.h" #include "nmCalculationPebiGridTask.h" #include "nmDataAnalyzeManager.h" #include "QListWidget.h" #include #include #include #include #include #include "ZxRstWnd.h" nmSubWndGrid::nmSubWndGrid(QWidget *parent, QString sExt, double pebiGridControl, bool bGeneratePebiGrid, nmDataAnalyzeManager* pDataManager) : iSubWndBaseFit(parent, sExt), m_mainLayout(nullptr), m_vtkWidget(nullptr), m_pGridLoadingLabel(nullptr), m_pDebounceTimer(nullptr), m_pDataManager(nullptr), m_pGridTask(nullptr), m_pGridTaskDataManager(nullptr), m_pGridGenerationDataManager(nullptr), m_bGridUpdatePending(false), m_dPebiGridControl(pebiGridControl), m_bGeneratePebiGrid(bGeneratePebiGrid) { m_sMdiWndType = s_MdiType_DataAnal; setWindowTitle(tr("nmSubWndGrid")); setDataManager(pDataManager); } nmSubWndGrid::~nmSubWndGrid() { if(m_pDebounceTimer != nullptr) { m_pDebounceTimer->stop(); } // 外部 DLL 不支持安全中止;窗口析构前等待自己的任务退出,避免销毁运行中的 QThread。 if(!m_pGridTask.isNull() && m_pGridTask->isRunning()) { disconnect(m_pGridTask, nullptr, this, nullptr); m_pGridTask->wait(); } // 窗口已经不再提交局部结果,必须结束登记,不能让求解永久等待。 endGridGenerationState(m_pGridGenerationDataManager.data(), false); } void nmSubWndGrid::setDataManager(nmDataAnalyzeManager* pDataManager) { nmDataAnalyzeManager* pOldDataManager = m_pDataManager.data(); if(pOldDataManager == pDataManager) { return; } // 第一步:只解除旧分析与本窗口的连接,避免切换多次后旧分析继续触发重建。 if(pOldDataManager != nullptr) { disconnect(pOldDataManager, SIGNAL(dataChanged()), this, SLOT(onDataManagerChanged())); // 切换后旧任务只允许自然结束并丢弃结果,不再阻塞旧分析的求解入口。 endGridGenerationState(pOldDataManager, false); } m_pDataManager = pDataManager; if(pDataManager != nullptr) { connect(pDataManager, SIGNAL(dataChanged()), this, SLOT(onDataManagerChanged()), static_cast( Qt::QueuedConnection | Qt::UniqueConnection)); } // 构造阶段保留对话框传入的控制参数;窗口创建完成后的分析切换使用新分析自己的值。 if(m_mainLayout != nullptr && pDataManager != nullptr) { m_dPebiGridControl = pDataManager->getPebiGridControl(); } if(m_pDebounceTimer != nullptr) { m_pDebounceTimer->stop(); } // 第二步:旧 VTK 页不能作为新分析的失败回退页,否则会继续显示上一分析的网格。 clearGridResultView(); if(m_mainLayout == nullptr) { return; } if(pDataManager == nullptr) { m_bGridUpdatePending = false; showGridFailure(); return; } if(pDataManager->isPebiGridValid()) { m_bGridUpdatePending = false; showGridResult(); return; } showGridLoading(); beginGridGenerationState(pDataManager); if(!m_pGridTask.isNull()) { // 外部 DLL 不能安全中止;旧任务自然结束后只补做当前分析的最后一次更新。 m_bGridUpdatePending = true; return; } m_bGridUpdatePending = false; updateGrid(); } void nmSubWndGrid::configWnd(QString sPrevInfo) { iSubWndBaseFit::configWnd(sPrevInfo); if(NULL != m_pMainWindow) { disconnect(this, SIGNAL(sigAppendDock2Main(iDockBaseWx*, Qt::DockWidgetArea)), \ m_pMainWindow, SLOT(slotAppendDock2Main(iDockBaseWx*, Qt::DockWidgetArea))); connect(this, SIGNAL(sigAppendDock2Main(iDockBaseWx*, Qt::DockWidgetArea)), \ m_pMainWindow, SLOT(slotAppendDock2Main(iDockBaseWx*, Qt::DockWidgetArea))); } initUI(); } bool nmSubWndGrid::loadRsts() { return true; } bool nmSubWndGrid::saveRsts() { ZxRstWnd* pRstWnd = this->getRstUtilWnd(); if (nullptr == pRstWnd) { return false; } return pRstWnd->save(); } bool nmSubWndGrid::onConfirmClosing() { // 先走基类关闭确认;用户取消关闭时不清理网格数据。 if(!iSubWndBaseFit::onConfirmClosing()) { return false; } nmCalculationPebiGrid::getInstance()->clearGridData(m_pDataManager.data()); nmDataAnalyzeManager* pDataManager = m_pDataManager.data(); if(pDataManager != nullptr) { // 当前窗口选择清除网格缓存后,分析方案也必须同步失效。 pDataManager->invalidatePebiGrid(); pDataManager->clearUnstructuredGrid(); endGridGenerationState(pDataManager, false); } return true; } void nmSubWndGrid::initUI() { QWidget* mainWidget = new QWidget(this); // 设置 mainWidget 的布局和尺寸策略 mainWidget->setLayout(new QStackedLayout(mainWidget)); // 加载背景和网格共用稳定显示区域。 mainWidget->layout()->setContentsMargins(0, 0, 0, 0); // 移除边距 mainWidget->layout()->setSpacing(0); // 移除间距 mainWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); // 扩展策略 setCentralWidget(mainWidget); // m_mainLayout 应该直接是 mainWidget 的布局 m_mainLayout = qobject_cast(mainWidget->layout()); // 第一步:先建立轻量加载页。颜色与网格 Renderer 保持一致,后台任务尚未 // 返回时也能立即绘制正常背景,不会显示空 QVTKWidget 的黑色原生窗口。 m_pGridLoadingLabel = new QLabel(tr("Generating grid..."), mainWidget); m_pGridLoadingLabel->setAlignment(Qt::AlignCenter); m_pGridLoadingLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); m_pGridLoadingLabel->setStyleSheet( "QLabel { color: white; font-size: 14px; " "background: qlineargradient(x1:0, y1:0, x2:0, y2:1, " "stop:0 rgb(0, 128, 255), stop:1 rgb(171, 209, 240)); }"); m_mainLayout->addWidget(m_pGridLoadingLabel); // 第二步:新建成果在渲染窗口就绪后立即启动后台划分;加载成果则直接显示 // DataManager 中已有的网格,不进入生成状态。VTK 页只在网格有效后创建。 if(m_bGeneratePebiGrid) { generatePebiGrid(); } else { showGridResult(); } } QWidget* nmSubWndGrid::initUiMainWx() { return nullptr; } bool nmSubWndGrid::runCmdBy(QString sName, QString sID) { if(sID.toInt() == 5401) { return true; } return false; } bool nmSubWndGrid::generatePebiGrid() { // PEBI网格 nmCalculationPebiGrid* pPebiGridGeneratorInstance = nmCalculationPebiGrid::getInstance(); // 当前流程只保留PEBI网格;这里仍写入数据中心,供求解和后处理的旧判断继续使用。 nmDataAnalyzeManager* pDataManager = m_pDataManager.data(); if(pDataManager == nullptr) { return false; } pDataManager->setGridType(NM_Grid_PEBI); // 将对话框中的GridControl传给PEBI输入参数GridControl pPebiGridGeneratorInstance->setGridControl(m_dPebiGridControl, pDataManager); // 首次手工生成无需等待防抖窗口。此时 VTK 背景已经完成初始化,任务启动 // 后界面可以立即绘制;连续 dataChanged 仍由 updateGrid() 合并处理。 showGridLoading(); beginGridGenerationState(pDataManager); onDebounceTimeout(); return true; } void nmSubWndGrid::onDataManagerChanged() { // 旧分析在断开前可能已经把信号排入主线程队列,发送者不匹配时不得刷新当前分析。 if(sender() != m_pDataManager.data()) { return; } updateGrid(); } void nmSubWndGrid::updateGrid() { // 角色、产量等只影响求解的变化也会发送 dataChanged;网格仍有效时无需重划。 nmDataAnalyzeManager* pDataManager = m_pDataManager.data(); if(pDataManager != nullptr && pDataManager->isPebiGridValid()) { return; } showGridLoading(); beginGridGenerationState(pDataManager); // dataChanged可能连续触发,使用当前窗口自己的定时器合并为一次网格重建 if(!m_pDebounceTimer) { m_pDebounceTimer = new QTimer(this); m_pDebounceTimer->setSingleShot(true); // 单次触发 connect(m_pDebounceTimer, SIGNAL(timeout()), this, SLOT(onDebounceTimeout())); } m_pDebounceTimer->start(500); // 500ms 防抖 } void nmSubWndGrid::onDebounceTimeout() { // 第一步:任务结束信号回到主线程前仍视为占用,避免覆盖尚未处理的任务指针。 if(!m_pGridTask.isNull()) { m_bGridUpdatePending = true; return; } nmDataAnalyzeManager* pDataManager = m_pDataManager.data(); if(pDataManager == nullptr) { return; } // 第二步:把窗口参数写回所属分析方案,再启动绑定同一管理器的后台任务。 nmCalculationPebiGrid::getInstance()->setGridControl( m_dPebiGridControl, pDataManager); // GridControl 写回可能更新输入版本,启动任务前同步本轮最终版本。 beginGridGenerationState(pDataManager); m_pGridTaskDataManager = pDataManager; m_pGridTask = new nmCalculationPebiGridTask(pDataManager, this); connect(m_pGridTask, SIGNAL(pebiGridGenerated(bool)), this, SLOT(onPebiGridGenerated(bool)), Qt::QueuedConnection); connect(m_pGridTask, SIGNAL(finished()), this, SLOT(onPebiGridTaskFinished())); connect(m_pGridTask, SIGNAL(finished()), m_pGridTask, SLOT(deleteLater())); m_pGridTask->start(); } void nmSubWndGrid::onPebiGridGenerated(bool success) { // 第一步:信号通过队列回到主线程后才允许写 DataManager。后台任务只保存 // 局部结果;如果用户在划分期间修改了输入,commitResult() 会按版本拒绝。 nmCalculationPebiGridTask* pCompletedTask = qobject_cast(sender()); nmDataAnalyzeManager* pTaskDataManager = m_pGridTaskDataManager.data(); // 只处理窗口当前持有且仍属于当前分析的任务;切换前启动的旧任务直接丢弃。 if(pCompletedTask != m_pGridTask.data() || pTaskDataManager == nullptr || pTaskDataManager != m_pDataManager.data()) { return; } if(success) { success = pCompletedTask->commitResult(pTaskDataManager); } else { success = false; } // 第二步:提交成功后创建绑定最新网格的 VTK 页。失败或版本过期时,如果 // 没有排队中的更新,则恢复旧网格或显示失败状态,不能永久停留在加载文字。 if(success) { showGridResult(); } else if(!hasPendingGridUpdate()) { showGridFailure(); } // 连续变化尚未完成时保持活动状态;求解只能在最后一次提交之后继续。 if(!hasPendingGridUpdate()) { endGridGenerationState(pTaskDataManager, success); } } void nmSubWndGrid::onPebiGridTaskFinished() { // 第一步:只清理由当前窗口持有的任务,避免旧 finished 信号覆盖新任务指针。 if(sender() != m_pGridTask.data()) { return; } m_pGridTask = nullptr; m_pGridTaskDataManager = nullptr; // 第二步:运行期间若又有数据变化,重新启动防抖计时,只补做最后一次网格。 if(m_bGridUpdatePending) { m_bGridUpdatePending = false; nmDataAnalyzeManager* pDataManager = m_pDataManager.data(); if(pDataManager != nullptr && pDataManager->isPebiGridValid()) { if(m_vtkWidget == nullptr) { showGridResult(); } endGridGenerationState(pDataManager, true); return; } updateGrid(); } } void nmSubWndGrid::showGridLoading() { if(m_pGridLoadingLabel == nullptr || m_mainLayout == nullptr) { return; } m_pGridLoadingLabel->setText(tr("Generating grid...")); m_mainLayout->setCurrentWidget(m_pGridLoadingLabel); } void nmSubWndGrid::showGridResult() { if(m_mainLayout == nullptr || m_pDataManager.isNull()) { return; } // 网格已经在主线程完整提交,此处一次创建新的 VTK 页,不会向用户暴露 // 没有 RenderWindow 的中间状态。 clearGridResultView(); m_vtkWidget = new nmWxGridVTKContainerWidget( m_pDataManager.data(), centralWidget()); m_vtkWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); m_mainLayout->addWidget(m_vtkWidget); m_mainLayout->setCurrentWidget(m_vtkWidget); } void nmSubWndGrid::clearGridResultView() { if(m_vtkWidget == nullptr) { return; } if(m_mainLayout != nullptr) { m_mainLayout->removeWidget(m_vtkWidget); } delete m_vtkWidget; m_vtkWidget = nullptr; } void nmSubWndGrid::showGridFailure() { if(m_mainLayout == nullptr) { return; } // 更新失败但旧网格仍可显示时保留旧成果;首次生成失败时在正常背景上给出 // 明确状态,避免用户把空白或黑屏误认为程序仍在计算。 if(m_vtkWidget != nullptr) { m_mainLayout->setCurrentWidget(m_vtkWidget); } else if(m_pGridLoadingLabel != nullptr) { m_pGridLoadingLabel->setText(tr("Grid generation failed")); m_mainLayout->setCurrentWidget(m_pGridLoadingLabel); } } void nmSubWndGrid::beginGridGenerationState( nmDataAnalyzeManager* pDataManager) { if(pDataManager == nullptr) { return; } if(!m_pGridGenerationDataManager.isNull() && m_pGridGenerationDataManager.data() != pDataManager) { endGridGenerationState(m_pGridGenerationDataManager.data(), false); } const nmDataNumericalAnalysisCase* pAnalysisCase = pDataManager->getNumericalAnalysisCase(); if(pAnalysisCase == nullptr) { return; } // 只在主线程保存状态和版本,不把窗口或 DataManager 指针交给后台线程。 m_pGridGenerationDataManager = pDataManager; pDataManager->beginPebiGridGeneration( pAnalysisCase->getGridInputRevision()); } void nmSubWndGrid::endGridGenerationState( nmDataAnalyzeManager* pDataManager, bool bSucceeded) { if(pDataManager == nullptr || m_pGridGenerationDataManager.data() != pDataManager) { return; } m_pGridGenerationDataManager = nullptr; pDataManager->endPebiGridGeneration(bSucceeded); } bool nmSubWndGrid::hasPendingGridUpdate() const { return m_bGridUpdatePending || (m_pDebounceTimer != nullptr && m_pDebounceTimer->isActive()); }