fix(numerical): 消除PEBI网格与求解的后台数据竞争

- 使用不可变快照向后台网格和求解任务传递输入
- 在主线程一次性提交网格及求解结果
- 移除网格生成中的CSV中转和重复建网
- 增加网格生成中及失败状态,避免窗口长时间黑屏
- 适配自动拟合调用新的求解任务接口
feature/refactor-numerical-model-panel-20260824
lh 1 month ago
parent 9bf9e41faf
commit b3a09b82ab

Binary file not shown.

@ -2351,6 +2351,14 @@ Reason: %1</source>
<source>nmSubWndGrid</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Generating grid...</source>
<translation>正在生成网格...</translation>
</message>
<message>
<source>Grid generation failed</source>
<translation>网格生成失败</translation>
</message>
</context>
<context>
<name>nmSubWndMain</name>

@ -17,6 +17,7 @@
class nmDataAnalyzeManager;
class vtkDoubleArray;
class vtkUnstructuredGrid;
struct nmPebiSolverInputSnapshot;
// 主窗口现在直接创建DLL求解线程任务,因此类需要导出,供nmSubWnd模块跨DLL使用。
class NMCALCULATION_EXPORT nmCalculationDllPebiSolverTask : public QThread {
@ -26,10 +27,12 @@ class NMCALCULATION_EXPORT nmCalculationDllPebiSolverTask : public QThread {
* @brief 创建绑定到指定分析窗口的 PEBI 求解任务。
* @param sPostprocessingDir 后处理文件输出目录。
* @param pDataManager 任务所属数据管理器,不转移所有权。
* @param sAutoFitTargetWellName 自动拟合目标井名;空值表示完整成果计算。
* @param parent Qt 父对象。
*/
nmCalculationDllPebiSolverTask(QString sPostprocessingDir,
nmDataAnalyzeManager* pDataManager = nullptr,
const QString& sAutoFitTargetWellName = QString(),
QObject *parent = nullptr);
~nmCalculationDllPebiSolverTask();
@ -50,11 +53,6 @@ class NMCALCULATION_EXPORT nmCalculationDllPebiSolverTask : public QThread {
*/
bool commitResult(nmDataAnalyzeManager* pDataManager);
/**
* @brief 按显示井名设置自动拟合目标井,内部立即转换并保存 WellCode。
* @note 井名为空或查找失败时保持完整结果保存模式。
*/
void setAutoFitTargetWell(const QString& wellName);
/** @brief 返回自动拟合目标井的原始压力结果副本。 */
QVector<QVector<double> > getAutoFitResultPressure() const;
/** @brief 返回自动拟合目标井的双对数结果副本。 */
@ -67,6 +65,8 @@ class NMCALCULATION_EXPORT nmCalculationDllPebiSolverTask : public QThread {
bool execute();
/** @brief 成对释放构造时登记的 DataManager 后台使用权。 */
void releaseDataManagerUse();
/** @brief 在任务创建线程一次性捕获网格、井、储层和求解器设置。 */
bool captureInputSnapshot(const QString& sAutoFitTargetWellName);
/** @brief 构造 DLL 输入、执行求解并生成仍然有效的局部结果快照。 */
bool execPebiMode();
@ -94,7 +94,10 @@ class NMCALCULATION_EXPORT nmCalculationDllPebiSolverTask : public QThread {
QString m_sPostprocessingDir; ///< 本次求解使用的后处理输出目录。
/** @brief 创建任务时捕获所属数据中心,工作线程不得读取全局当前窗口。 */
nmDataAnalyzeManager* m_pDataManager;
/** @brief 任务独占的完整值快照;析构时释放,后台禁止访问 DataManager。 */
nmPebiSolverInputSnapshot* m_pInputSnapshot;
bool m_bManagerUseActive; ///< 是否仍持有 DataManager 后台使用权。
bool m_bInputSnapshotValid; ///< 构造阶段是否已完成全部输入校验。
/** @brief 创建任务时捕获网格输入版本,防止回填过期网格上的结果。 */
quint64 m_nGridInputRevision;
/** @brief 创建任务时捕获求解输入版本,防止角色或参数变化后回填旧结果。 */

@ -4,14 +4,60 @@
#include "nmCalculation_global.h"
#include <QVector>
#include <QPointF>
#include <QString>
#include "pch.h"
#include "nmDataBinaryTools.h"
#include "nmDataNumericalAnalysisCase.h"
#include <vtkUnstructuredGrid.h>
#include <vtkSmartPointer.h>
class nmDataAnalyzeManager;
/**
* @brief 一次 PEBI 网格任务使用的不可变输入快照。
*
* 该结构只保存值类型,不持有 DataManager、QObject 或井对象指针。主线程在
* 启动任务前完整填充,后台线程随后只读取本结构,从而避免与 Map 编辑并发访问。
*/
struct nmPebiGridInputSnapshot
{
/** @brief 创建空快照;m_bValid 为 false,不能直接提交给网格 DLL。 */
nmPebiGridInputSnapshot()
: m_nGridInputRevision(0),
m_bValid(false)
{
}
HX_NWTM_GRID_INPUT m_oGridInput; ///< 边界、井、断层和裂缝等 DLL 输入。
nmDataBinaryTools::NM_PEBI_SCENE m_oScene; ///< 与本次网格输入一致的 ML 场景数据。
QVector<nmSolverWellRef> m_vecSolverWellOrder; ///< DLL 数组槽位对应的稳定井编码顺序。
QString m_sLicensePath; ///< 网格 DLL 授权文件路径副本。
quint64 m_nGridInputRevision; ///< 捕获时的几何输入版本。
bool m_bValid; ///< 主线程是否已完成全部输入校验。
};
/**
* @brief 后台 PEBI 计算产生、尚未写回分析成果的局部结果。
*/
struct nmPebiGridResult
{
/** @brief 创建空结果;只有 m_bSucceeded 为 true 时才允许提交。 */
nmPebiGridResult()
: m_pUnstructuredGrid(nullptr),
m_nPebiCount(-1),
m_bSucceeded(false)
{
}
HX_NWTM_GRID_OUTPUT1 m_oGridOutput1; ///< 网格 DLL 主输出。
HX_NWTM_GRID_OUTPUT2 m_oGridOutput2; ///< 网格 DLL 辅助输出。
vtkSmartPointer<vtkUnstructuredGrid> m_pUnstructuredGrid; ///< 局部构造的 VTK 网格。
int m_nPebiCount; ///< DLL 返回的 PEBI 单元数量。
bool m_bSucceeded; ///< DLL 和 VTK 转换是否全部成功。
};
class NMCALCULATION_EXPORT nmCalculationPebiGrid
{
public:
@ -20,8 +66,17 @@ public:
nmCalculationPebiGrid();
~nmCalculationPebiGrid();
/** @brief 为指定数据管理器生成 PEBI 网格并登记输入版本。 */
bool meshGenPebi(nmDataAnalyzeManager* pDataManager = nullptr);
/** @brief 在调用线程从 DataManager 复制一份完整且不含指针的网格输入。 */
bool captureInputSnapshot(nmDataAnalyzeManager* pDataManager,
nmPebiGridInputSnapshot& oSnapshot);
/** @brief 仅使用值快照调用网格 DLL,并把输出保存在局部结果中。 */
bool calculateSnapshot(const nmPebiGridInputSnapshot& oSnapshot,
nmPebiGridResult& oResult,
bool bCreateUnstructuredGrid = true);
/** @brief 在主线程校验输入版本,并一次性提交井顺序、网格和单例缓存。 */
bool commitSnapshotResult(nmDataAnalyzeManager* pDataManager,
const nmPebiGridInputSnapshot& oSnapshot,
const nmPebiGridResult& oResult);
/** @brief 设置 PEBI 网格控制值,并同步到指定分析方案。 */
void setGridControl(double dGridControl,
nmDataAnalyzeManager* pDataManager = nullptr);
@ -30,13 +85,28 @@ public:
private:
/** @brief 从绑定数据管理器构造边界输入。 */
bool meshGenPebiBoundary(HX_NWTM_GRID_INPUT& inputObj);
bool meshGenPebiBoundary(nmDataAnalyzeManager* pDataManager,
HX_NWTM_GRID_INPUT& inputObj);
/** @brief 按有效计算井集合构造井几何并记录 DLL 井顺序。 */
bool meshGenPebiWells(HX_NWTM_GRID_INPUT& inputObj);
bool meshGenPebiWells(nmDataAnalyzeManager* pDataManager,
HX_NWTM_GRID_INPUT& inputObj,
QVector<nmSolverWellRef>& vecSolverWellOrder);
/** @brief 从 Map 构造断层输入。 */
bool meshGenPebiFault(HX_NWTM_GRID_INPUT& inputObj);
bool meshGenPebiFault(nmDataAnalyzeManager* pDataManager,
HX_NWTM_GRID_INPUT& inputObj);
/** @brief 从 Map 构造手工裂缝输入和求解器占位条目。 */
bool meshGenPebiCrack(HX_NWTM_GRID_INPUT& inputObj);
bool meshGenPebiCrack(nmDataAnalyzeManager* pDataManager,
HX_NWTM_GRID_INPUT& inputObj,
QVector<nmSolverWellRef>& vecSolverWellOrder);
/** @brief 从主线程数据构造与网格输入一致的 ML 场景值快照。 */
bool buildPebiScene(nmDataAnalyzeManager* pDataManager,
const HX_NWTM_GRID_INPUT& oGridInput,
const QVector<nmSolverWellRef>& vecSolverWellOrder,
nmDataBinaryTools::NM_PEBI_SCENE& oScene);
/** @brief 更新单例中的 DLL 输入输出缓存,不改变网格有效版本。 */
void commitOutputCache(nmDataAnalyzeManager* pDataManager,
const nmPebiGridInputSnapshot& oSnapshot,
const nmPebiGridResult& oResult);
// 生成vtk数据
void genPebiVTK(const HX_NWTM_GRID_OUTPUT1& P1, QString vtkDir);
@ -49,11 +119,7 @@ private:
void logInputParameters(const HX_NWTM_GRID_INPUT& input);
public:
/** @brief 调用网格 DLL 生成 p1、p2,并校验有效井与求解器顺序一致。 */
bool generateOutputPara(nmDataAnalyzeManager* pDataManager = nullptr);
/** @brief 判断单例缓存是否确实属于指定分析窗口且输入版本仍然有效。 */
bool isGridCurrentFor(const nmDataAnalyzeManager* pDataManager) const;
/** @brief 在同一把锁内校验所属分析并复制完整网格快照。 */
/** @brief 非阻塞复制当前有效网格;DLL忙碌或缓存不匹配时返回 false。 */
bool copyCurrentGridFor(const nmDataAnalyzeManager* pDataManager,
HX_NWTM_GRID_OUTPUT1& oGridOutput1,
HX_NWTM_GRID_OUTPUT2& oGridOutput2,
@ -74,6 +140,7 @@ private:
HX_NWTM_GRID_OUTPUT2 p2; ///< 最近一次网格 DLL 辅助输出。
double m_dGridControl; ///< 对应 HX_NWTM_GRID_INPUT::GridControl。
int m_nPebiCount; ///< 最近一次成功网格的单元数量,未生成时为 -1。
quint64 m_nCachedGridInputRevision; ///< 单例 DLL 输出对应的网格输入版本。
/** @brief 当前单例缓存所属的数据管理器,不拥有对象。 */
nmDataAnalyzeManager* m_pDataManager;

@ -4,6 +4,7 @@
#include <QThread>
#include "nmCalculation_global.h"
#include "nmCalculationPebiGrid.h"
class nmDataAnalyzeManager;
@ -17,6 +18,8 @@ public:
QObject *parent = 0);
/** @brief 等待线程结束,并释放 DataManager 后台使用权。 */
~nmCalculationPebiGridTask();
/** @brief 在主线程按版本提交后台局部结果;过期结果返回 false。 */
bool commitResult(nmDataAnalyzeManager* pDataManager);
signals:
/** @brief 无论成功或失败均通知主线程结束本轮网格任务。 */
@ -33,6 +36,9 @@ private:
/** @brief 启动任务时捕获所属数据管理器,线程内不再读取全局当前窗口。 */
nmDataAnalyzeManager* m_pDataManager;
bool m_bManagerUseActive; ///< 是否仍持有 DataManager 后台使用权。
bool m_bSnapshotValid; ///< 构造阶段是否成功获得完整输入快照。
nmPebiGridInputSnapshot m_oInputSnapshot; ///< 主线程创建、后台只读的值快照。
nmPebiGridResult m_oResult; ///< 后台局部结果,只允许主线程提交。
};
#endif // NMCALCULATIONPEBIGRIDTASK_H

@ -487,6 +487,16 @@ public:
void insertSolverWell(int nIndex, const nmSolverWellRef& oWellRef);
/** @brief 返回网格确定的 DLL 井槽位顺序,与包含井选择相互独立。 */
QVector<nmSolverWellRef> getSolverWellOrder() const;
/** @brief 整体替换 DLL 井槽位顺序,并由分析方案统一重编号。 */
void setSolverWellOrder(const QVector<nmSolverWellRef>& vecSolverWellOrder);
/**
* @brief 按输入版本一次性提交后台生成的井顺序和 VTK 网格。
* @return 输入仍为当前版本且提交成功时返回 true;过期结果不修改现有成果。
*/
bool commitPebiGridResult(
quint64 nGridInputRevision,
const QVector<nmSolverWellRef>& vecSolverWellOrder,
vtkSmartPointer<vtkUnstructuredGrid> pGrid);
/** @brief 将当前输入版本登记为已成功生成网格。 */
void markPebiGridBuilt();
/** @brief 仅在后台划分期间输入未变化时登记网格成功。 */

@ -7,6 +7,8 @@ class nmWxGridVTKContainerWidget;
class nmDataAnalyzeManager;
class nmCalculationPebiGridTask;
class QTimer;
class QLabel;
class QStackedLayout;
class NM_SUB_WND_EXPORT nmSubWndGrid : public iSubWndBaseFit {
Q_OBJECT
@ -53,9 +55,16 @@ class NM_SUB_WND_EXPORT nmSubWndGrid : public iSubWndBaseFit {
bool generatePebiGrid();
private:
/** @brief 切换到渐变加载背景,并恢复“正在生成网格”提示。 */
void showGridLoading();
/** @brief 用 DataManager 中的最新网格重建 VTK 页并切换到结果显示。 */
void showGridResult();
/** @brief 网格失败时恢复旧网格;没有旧网格则显示明确失败提示。 */
void showGridFailure();
QVBoxLayout* m_mainLayout; ///< 网格窗口主布局。
QStackedLayout* m_mainLayout; ///< 在加载背景和 VTK 结果之间切换的主布局。
nmWxGridVTKContainerWidget* m_vtkWidget; ///< 显示所属分析网格的 VTK 控件。
QLabel* m_pGridLoadingLabel; ///< 后台划分期间显示的渐变背景和状态文字。
QTimer* m_pDebounceTimer; ///< 本窗口独立防抖定时器,避免跨窗口刷新。
QPointer<nmDataAnalyzeManager> m_pDataManager; ///< 窗口所属数据管理器,销毁后自动置空。
QPointer<nmCalculationPebiGridTask> m_pGridTask; ///< 当前后台网格任务,完成后自动回收。

@ -3486,26 +3486,6 @@ bool nmCalculationAutoFitPSO::startAutoFitting()
emit logMessageGenerated(tr("Applying optimized parameters to model..."));
applyParametersToDataManager(m_globalBestPosition);
// 即使用户此时停止、不再执行最终完整计算,也要把 PEBI 缓存恢复为
// 最终已接受的裂缝参数,避免缓存仍停留在最后一个被拒绝的候选值。
const bool fractureGridParameterSelected =
(m_parameterSelected.size() > 8 && m_parameterSelected[8]) ||
(m_parameterSelected.size() > 9 && m_parameterSelected[9]);
if(fractureGridParameterSelected) {
// 第一步:最终参数已写回当前分析,网格刷新必须继续绑定同一数据管理器。
nmDataAnalyzeManager* pDataManager =
nmDataAnalyzeManager::getCurrentInstance();
if(pDataManager == nullptr) {
throw std::runtime_error("Current data manager is unavailable");
}
// 第二步:仅重建裂缝参数相关的 PEBI 输出,供最终全场求解复用。
nmCalculationPebiGrid* pebiGrid = nmCalculationPebiGrid::getInstance();
if(!pebiGrid || !pebiGrid->generateOutputPara(pDataManager)) {
throw std::runtime_error("Failed to refresh final fracture parameters");
}
}
if(m_shouldStop) {
// 手动停止优先保持快速返回,仅写回已确认的最优参数。
emit logMessageGenerated(tr("Final full-field calculation skipped after user stop"));
@ -5664,23 +5644,8 @@ double nmCalculationAutoFitPSO::evaluateFitness(const QVector<double>& parameter
return 1e10;
}
// Dfc 和裂缝半长都通过 PEBI 裂缝数组传入,不属于每次求解都会重新组装的 Base/CS 参数。
// 勾选任一裂缝参数时刷新网格输出,保证本次真实试算使用新的导流能力和端点坐标。
const bool fractureGridParameterSelected =
(m_parameterSelected.size() > 8 && m_parameterSelected[8]) ||
(m_parameterSelected.size() > 9 && m_parameterSelected[9]);
if(fractureGridParameterSelected) {
// 粒子参数改变裂缝几何时,必须刷新同一分析窗口的网格 DLL 输入。
nmCalculationPebiGrid* pebiGrid = nmCalculationPebiGrid::getInstance();
if(!pebiGrid || !pebiGrid->generateOutputPara(dataManager)) {
DEBUG_OUT(QString("%1: Call #%2 - Failed to refresh PEBI fracture parameters")
.arg(funcName).arg(callCount));
return 1e10;
}
}
// 4. 运行求解器。真实求解器偶发失败时允许重试,避免一次 DLL 调用异常
// 直接让整个粒子评价失败。
// 4. 运行求解器。裂缝参数使网格失效时,任务会从不可变输入快照生成一次
// 局部网格并直接用于本次模型计算,不再预生成后又重复建网。
QVector<QVector<double>> solverResult;
const int maxRetries = 2;
bool solverSuccess = false;
@ -7483,8 +7448,10 @@ QVector<QVector<double>> nmCalculationAutoFitPSO::runSolverDll()
try {
DEBUG_OUT("Creating DLL solver task");
dllTask = new nmCalculationDllPebiSolverTask(m_tempDirectory,
pDataManager);
dllTask = new nmCalculationDllPebiSolverTask(
m_tempDirectory,
pDataManager,
m_targetWellName);
if(m_targetWellName.isEmpty()) {
DEBUG_OUT("Target well name is empty - target-only solver cannot start");
@ -7493,8 +7460,6 @@ QVector<QVector<double>> nmCalculationAutoFitPSO::runSolverDll()
return result;
}
dllTask->setAutoFitTargetWell(m_targetWellName);
if(m_shouldStop) {
DEBUG_OUT("Should stop - cleaning up and returning empty result");
delete dllTask;
@ -7703,7 +7668,9 @@ bool nmCalculationAutoFitPSO::runFinalFullSolver()
}
dllTask.wait();
const bool succeeded = dllTask.wasSuccessful();
// 完整求解只在当前线程一次提交网格和全部井结果。自动拟合粒子任务不会走这里。
const bool succeeded = dllTask.wasSuccessful() &&
dllTask.commitResult(pDataManager);
--m_evaluationInProgress;
return succeeded;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -8,11 +8,18 @@ nmCalculationPebiGridTask::nmCalculationPebiGridTask(
QObject *parent)
: QThread(parent),
m_pDataManager(pDataManager),
m_bManagerUseActive(false)
m_bManagerUseActive(false),
m_bSnapshotValid(false)
{
if(m_pDataManager != nullptr) {
m_pDataManager->beginBackgroundUse();
m_bManagerUseActive = true;
// 第一步:构造函数运行在主线程,此处一次性复制全部 Map 和求解输入。
// run() 启动后不得再读取 DataManager 中的井、断层或参数对象。
m_bSnapshotValid =
nmCalculationPebiGrid::getInstance()->captureInputSnapshot(
m_pDataManager, m_oInputSnapshot);
}
}
@ -26,16 +33,30 @@ nmCalculationPebiGridTask::~nmCalculationPebiGridTask()
void nmCalculationPebiGridTask::run()
{
// 当前网格生成链路只保留PEBI.
// 生成网格.
bool success = nmCalculationPebiGrid::getInstance()->meshGenPebi(
m_pDataManager);
// 第二步:后台只消费不可变快照,所有 DLL 输出和 VTK 数据先保存在任务内部。
const bool success = m_bSnapshotValid &&
nmCalculationPebiGrid::getInstance()->calculateSnapshot(
m_oInputSnapshot, m_oResult);
// 完成通知可能引起成果窗口销毁,发信号前先解除 DataManager 使用权。
releaseDataManagerUse();
// 无论成功或失败都通知窗口,使其可以结束本轮任务状态并处理后续待刷新请求。
emit pebiGridGenerated(success);
}
bool nmCalculationPebiGridTask::commitResult(
nmDataAnalyzeManager* pDataManager)
{
// 第三步:该方法由网格窗口的主线程槽调用。版本不一致时只丢弃局部结果,
// 绝不覆盖用户编辑后已经失效或重新生成的网格。
if(!m_bSnapshotValid || !m_oResult.m_bSucceeded ||
pDataManager == nullptr) {
return false;
}
return nmCalculationPebiGrid::getInstance()->commitSnapshotResult(
pDataManager, m_oInputSnapshot, m_oResult);
}
void nmCalculationPebiGridTask::releaseDataManagerUse()
{
if(m_bManagerUseActive && m_pDataManager != nullptr) {

@ -3584,6 +3584,41 @@ QVector<nmSolverWellRef> nmDataAnalyzeManager::getSolverWellOrder() const
return m_oNumericalAnalysisCase.getSolverWellOrder();
}
void nmDataAnalyzeManager::setSolverWellOrder(
const QVector<nmSolverWellRef>& vecSolverWellOrder)
{
m_oNumericalAnalysisCase.setSolverWellOrder(vecSolverWellOrder);
}
bool nmDataAnalyzeManager::commitPebiGridResult(
quint64 nGridInputRevision,
const QVector<nmSolverWellRef>& vecSolverWellOrder,
vtkSmartPointer<vtkUnstructuredGrid> pGrid)
{
// 第一步:空井顺序或空网格都不是可求解成果,不能破坏当前有效网格。
if(vecSolverWellOrder.isEmpty() || pGrid == nullptr ||
pGrid->GetNumberOfCells() <= 0) {
return false;
}
// 第二步:先检查后台任务捕获的输入版本。该函数只在主线程调用,检查和
// 后续写入之间不会处理界面事件,因此可以作为一次不可分割的成果提交。
if(m_oNumericalAnalysisCase.getGridInputRevision() !=
nGridInputRevision) {
return false;
}
// 第三步:整体替换 DLL 槽位顺序,再登记同一输入版本并替换 VTK 网格。
// setSolverWellOrder() 会统一重编号,避免逐井追加期间暴露半成品顺序。
m_oNumericalAnalysisCase.setSolverWellOrder(vecSolverWellOrder);
if(!m_oNumericalAnalysisCase.markGridBuiltIfCurrent(
nGridInputRevision)) {
return false;
}
m_pVtkUnstructuredGrid = pGrid;
return true;
}
void nmDataAnalyzeManager::markPebiGridBuilt()
{
m_oNumericalAnalysisCase.markGridBuilt();

@ -49,6 +49,8 @@
#include <QVector>
#include <QProcess>
#include <QTimer>
#include <QLabel>
#include <QStackedLayout>
#include "ZxRstWnd.h"
@ -60,6 +62,7 @@ nmSubWndGrid::nmSubWndGrid(QWidget *parent,
iSubWndBaseFit(parent, sExt),
m_mainLayout(nullptr),
m_vtkWidget(nullptr),
m_pGridLoadingLabel(nullptr),
m_pDebounceTimer(nullptr),
m_pDataManager(pDataManager),
m_pGridTask(nullptr),
@ -134,7 +137,7 @@ void nmSubWndGrid::initUI()
{
QWidget* mainWidget = new QWidget(this);
// 设置 mainWidget 的布局和尺寸策略
mainWidget->setLayout(new QVBoxLayout(mainWidget)); // 确保 mainWidget 有自己的布局
mainWidget->setLayout(new QStackedLayout(mainWidget)); // 加载背景和网格共用稳定显示区域。
mainWidget->layout()->setContentsMargins(0, 0, 0, 0); // 移除边距
mainWidget->layout()->setSpacing(0); // 移除间距
mainWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); // 扩展策略
@ -142,25 +145,27 @@ void nmSubWndGrid::initUI()
setCentralWidget(mainWidget);
// m_mainLayout 应该直接是 mainWidget 的布局
m_mainLayout = qobject_cast<QVBoxLayout*>(mainWidget->layout());
// 当前不是加载进来的网格,都需要根据Map几何生成新的PEBI网格
if (m_bGeneratePebiGrid)
{
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();
}
m_vtkWidget = new nmWxGridVTKContainerWidget(m_pDataManager.data(), this);
m_vtkWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); // 扩展策略
m_mainLayout->addWidget(m_vtkWidget);
// 首次创建
//if(generatePebiGrid()) {
// // nmWxGridVTKContainerWidget 的尺寸策略
// m_vtkWidget = new nmWxGridVTKContainerWidget(this);
// m_vtkWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); // 扩展策略
// m_mainLayout->addWidget(m_vtkWidget);
//}
}
QWidget* nmSubWndGrid::initUiMainWx()
@ -192,8 +197,11 @@ bool nmSubWndGrid::generatePebiGrid()
// 将对话框中的GridControl传给PEBI输入参数GridControl
pPebiGridGeneratorInstance->setGridControl(m_dPebiGridControl,
pDataManager);
// 生成PEBI非结构化网格对象
return pPebiGridGeneratorInstance->meshGenPebi(pDataManager);
// 首次手工生成无需等待防抖窗口。此时 VTK 背景已经完成初始化,任务启动
// 后界面可以立即绘制;连续 dataChanged 仍由 updateGrid() 合并处理。
showGridLoading();
onDebounceTimeout();
return true;
}
void nmSubWndGrid::updateGrid()
@ -204,6 +212,8 @@ void nmSubWndGrid::updateGrid()
return;
}
showGridLoading();
// dataChanged可能连续触发,使用当前窗口自己的定时器合并为一次网格重建
if(!m_pDebounceTimer) {
m_pDebounceTimer = new QTimer(this);
@ -230,9 +240,9 @@ void nmSubWndGrid::onDebounceTimeout()
// 第二步:把窗口参数写回所属分析方案,再启动绑定同一管理器的后台任务。
nmCalculationPebiGrid::getInstance()->setGridControl(
m_dPebiGridControl, pDataManager);
m_pGridTask = new nmCalculationPebiGridTask(pDataManager, this);
m_pGridTask = new nmCalculationPebiGridTask(pDataManager, this);
connect(m_pGridTask, SIGNAL(pebiGridGenerated(bool)),
this, SLOT(onPebiGridGenerated(bool)));
this, SLOT(onPebiGridGenerated(bool)), Qt::QueuedConnection);
connect(m_pGridTask, SIGNAL(finished()),
this, SLOT(onPebiGridTaskFinished()));
connect(m_pGridTask, SIGNAL(finished()),
@ -242,21 +252,24 @@ void nmSubWndGrid::onDebounceTimeout()
void nmSubWndGrid::onPebiGridGenerated(bool success)
{
if (!success) {
return;
}
// 删除旧的 VTK 控件
if (m_vtkWidget) {
m_mainLayout->removeWidget(m_vtkWidget);
delete m_vtkWidget;
m_vtkWidget = nullptr;
}
// 第一步:信号通过队列回到主线程后才允许写 DataManager。后台任务只保存
// 局部结果;如果用户在划分期间修改了输入,commitResult() 会按版本拒绝。
nmCalculationPebiGridTask* pCompletedTask =
qobject_cast<nmCalculationPebiGridTask*>(sender());
nmDataAnalyzeManager* pDataManager = m_pDataManager.data();
if(success && pCompletedTask != nullptr && pDataManager != nullptr) {
success = pCompletedTask->commitResult(pDataManager);
} else {
success = false;
}
// 创建新的 VTK 控件,并将其加入布局
m_vtkWidget = new nmWxGridVTKContainerWidget(m_pDataManager.data(), this);
// 设置新创建的 nmWxGridVTKContainerWidget 的尺寸策略**
m_vtkWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); // 扩展策略
m_mainLayout->addWidget(m_vtkWidget);
// 第二步:提交成功后创建绑定最新网格的 VTK 页。失败或版本过期时,如果
// 没有排队中的更新,则恢复旧网格或显示失败状态,不能永久停留在加载文字。
if(success) {
showGridResult();
} else if(!m_bGridUpdatePending) {
showGridFailure();
}
}
void nmSubWndGrid::onPebiGridTaskFinished()
@ -272,3 +285,51 @@ void nmSubWndGrid::onPebiGridTaskFinished()
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 的中间状态。
if(m_vtkWidget != nullptr) {
m_mainLayout->removeWidget(m_vtkWidget);
delete m_vtkWidget;
m_vtkWidget = nullptr;
}
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::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);
}
}

@ -1644,8 +1644,8 @@ void nmSubWndMain::solveAndAnalyze()
// TODO:关联到父窗口
m_pProgressDlg = new QProgressDialog(tr("Calculating, please wait..."), QString(), 0, 100, getMainWindow());
m_pProgressDlg->setWindowTitle(tr("Solver Progress"));
// 求解任务仍需读取 DataManager 中的 Qt 容器。应用级模态会冻结所有成果的
// 编辑入口,避免 Qt 4.8 下后台读与前台写同时发生数据竞争。
// 后台只读取任务值快照;应用级模态仍用于阻止重复启动、切换成果等会让
// 用户误判当前计算归属的操作,数据安全不再依赖该对话框冻结界面。
m_pProgressDlg->setWindowModality(Qt::ApplicationModal);
// 某些系统下,对话框右上角的关闭按钮可能仍存
@ -2521,20 +2521,9 @@ void nmSubWndMain::onWellSelected(const QString& sWellCode)
void nmSubWndMain::onGenerateButtonClicked()
{
//QString sDir = ZxBaseUtil::getCurWellDirOf("Nm/PreProcessing");
if(nmDataAnalyzeManager::getCurrentInstance()->getGridType() == NM_Grid_PEBI) {
nmCalculationPebiGrid* pPebiGridGeneratorInstance = nmCalculationPebiGrid::getInstance();
nmDataAnalyzeManager* pDataManager =
nmDataAnalyzeManager::getCurrentInstance();
if(!pPebiGridGeneratorInstance->isGridCurrentFor(pDataManager)) {
// Map、包含井或几何输入变化后,旧网格即使非空也不能继续求解。
if(!pPebiGridGeneratorInstance->meshGenPebi(pDataManager)) {
return;
}
}
// 求解计算
// 求解任务创建时会捕获完整输入。网格无效时,工作线程只基于值快照生成
// 一次局部网格,求解成功后再和全部结果一起回主线程提交。
this->solveAndAnalyze();
}
}

Loading…
Cancel
Save