You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
nmWTAI-Platform/Src/nmNum/nmSubWnd/nmSubWndGrid.cpp

504 lines
15 KiB
C++

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#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 "nmPebiGridViewData.h"
#include "nmPebiGridViewDataBuilder.h"
#include "nmWxPebiGridViewWidget.h"
#include "nmCalculationUtils.h"
#include "nmCalculationPebiGrid.h"
#include "nmCalculationPebiGridTask.h"
#include "nmDataAnalyzeManager.h"
#include "QListWidget.h"
#include <QVector>
#include <QProcess>
#include <QTimer>
#include <QStackedLayout>
#include "ZxRstWnd.h"
nmSubWndGrid::nmSubWndGrid(QWidget *parent,
QString sExt,
double pebiGridControl,
bool bGeneratePebiGrid,
nmDataAnalyzeManager* pDataManager) :
iSubWndBaseFit(parent, sExt),
m_mainLayout(nullptr),
m_pGridView(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::ConnectionType>(
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;
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<QStackedLayout*>(mainWidget->layout());
// 通用渲染容器随窗口创建,但没有有效数据时不会创建业务 Mapper 或 Actor。
// 因此空数据、加载和失败都能复用同一状态页,不会落回黑色原生窗口。
m_pGridView = new nmWxPebiGridViewWidget(mainWidget);
m_pGridView->setSizePolicy(QSizePolicy::Expanding,
QSizePolicy::Expanding);
m_mainLayout->addWidget(m_pGridView);
m_mainLayout->setCurrentWidget(m_pGridView);
// 新建成果启动后台划分;加载成果只在已有提交网格时绑定业务图层。
if(m_bGeneratePebiGrid) {
generatePebiGrid();
} else if(!m_pDataManager.isNull() &&
m_pDataManager->isPebiGridValid()) {
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;
}
// GridControl 由分析方案统一持有,刷新前同步,避免窗口把旧局部值写回。
nmDataAnalyzeManager* pDataManager = m_pDataManager.data();
if(pDataManager != nullptr) {
m_dPebiGridControl = pDataManager->getPebiGridControl();
}
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<nmCalculationPebiGridTask*>(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()) {
showGridResult();
endGridGenerationState(pDataManager, true);
return;
}
updateGrid();
}
}
void nmSubWndGrid::showGridLoading()
{
if(m_mainLayout == nullptr || m_pGridView == nullptr) {
return;
}
// 已有成功视图时只切换其内部状态页网格、Scene、Renderer 和相机均保留。
m_mainLayout->setCurrentWidget(m_pGridView);
m_pGridView->showLoading();
}
void nmSubWndGrid::showGridResult()
{
if(m_mainLayout == nullptr || m_pGridView == nullptr) {
return;
}
if(m_pDataManager.isNull()) {
m_pGridView->clearView();
return;
}
// 网格和井值只在 GUI 线程构建为独立 ViewData失败时不覆盖已有成功画面。
nmPebiGridViewData oViewData;
QString sError;
if(!nmPebiGridViewDataBuilder::build(
m_pDataManager.data(), &oViewData, &sError)) {
m_mainLayout->setCurrentWidget(m_pGridView);
m_pGridView->showFailure(sError);
return;
}
// 业务控件在窗口生命周期内只创建一次,后续提交只替换 MeshLayer 输入。
m_mainLayout->setCurrentWidget(m_pGridView);
if(!m_pGridView->setViewData(oViewData, &sError)) {
m_pGridView->showFailure(sError);
}
}
void nmSubWndGrid::clearGridResultView()
{
if(m_pGridView == nullptr) {
return;
}
// 切换分析只清空业务数据,稳定的 QVTKWidget、Renderer 和 Scene 不重建。
m_pGridView->clearView();
}
void nmSubWndGrid::showGridFailure()
{
if(m_mainLayout == nullptr || m_pGridView == nullptr) {
return;
}
// 更新失败但旧网格仍可显示时保留旧成果;首次生成失败时在正常背景上给出
// 明确状态,避免用户把空白或黑屏误认为程序仍在计算。
m_mainLayout->setCurrentWidget(m_pGridView);
if(m_pGridView->hasViewData()) {
m_pGridView->showFailure(tr(
"Grid generation failed. Current view is the last successful grid."));
} else {
m_pGridView->showFailure(tr("Grid generation failed."));
}
}
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());
}