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

497 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 "nmWxGridVTKContainerWidget.h"
#include "nmCalculationUtils.h"
#include "nmCalculationPebiGrid.h"
#include "nmCalculationPebiGridTask.h"
#include "nmDataAnalyzeManager.h"
#include "QListWidget.h"
#include <QVector>
#include <QProcess>
#include <QTimer>
#include <QLabel>
#include <QStackedLayout>
#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::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;
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<QStackedLayout*>(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<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()) {
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());
}