优化网格生成与模型求解流程,增加进度与停止处理

- 拆分求解输入准备、网格生成和模型求解流程,减少界面阻塞
- 增加计算进度提示和协作式停止处理
- 完善输入快照隔离、结果提交校验及窗口关闭时的任务清理
- 保持网格与求解 DLL 的串行互斥保护
- 优化后处理结果刷新并更新中文翻译
feature/grid-solver-workflow-20260827
lh 2 weeks ago
parent d8af516478
commit 8f47ebfdad

Binary file not shown.

@ -2826,6 +2826,26 @@ Reason: %1</source>
<source>Solver Progress</source>
<translation></translation>
</message>
<message>
<source>Preparing input snapshot...</source>
<translation>...</translation>
</message>
<message>
<source>Stop</source>
<translation></translation>
</message>
<message>
<source>Stopping; waiting for the current solver call to return...</source>
<translation>...</translation>
</message>
<message>
<source>Updating result views...</source>
<translation>...</translation>
</message>
<message>
<source>Committing results...</source>
<translation>...</translation>
</message>
<message>
<source>Solver completed</source>
<translation></translation>
@ -6942,6 +6962,61 @@ Average pressure in contour: %2 MPa</source>
<source>The Kriging result contains an invalid value.</source>
<translation>Kriging </translation>
</message>
<message>
<source>Kriging calculation was cancelled.</source>
<translation>Kriging </translation>
</message>
</context>
<context>
<name>nmCalculationDllPebiSolverTask</name>
<message>
<source>Preparing input data...</source>
<translation>...</translation>
</message>
<message>
<source>Checking grid cache...</source>
<translation>...</translation>
</message>
<message>
<source>Preparing result grid...</source>
<translation>...</translation>
</message>
<message>
<source>Generating grid...</source>
<translation>...</translation>
</message>
<message>
<source>Preparing model input...</source>
<translation>...</translation>
</message>
<message>
<source>Waiting for solver...</source>
<translation>...</translation>
</message>
<message>
<source>Solving model...</source>
<translation>...</translation>
</message>
<message>
<source>Processing well results...</source>
<translation>...</translation>
</message>
<message>
<source>Processing field results...</source>
<translation>...</translation>
</message>
<message>
<source>Capturing well data...</source>
<translation>...</translation>
</message>
<message>
<source>Finalizing input snapshot...</source>
<translation>...</translation>
</message>
<message>
<source>Capturing solver settings...</source>
<translation>...</translation>
</message>
</context>
<context>
<name>nmParameterField</name>

@ -6,6 +6,7 @@
#include <QVector>
#include <QMap>
#include <QPointF>
#include <QAtomicInt>
#include <iostream>
#include <vector>
#include <Windows.h>
@ -18,6 +19,7 @@ class nmDataAnalyzeManager;
class vtkDoubleArray;
class vtkUnstructuredGrid;
struct nmPebiSolverInputSnapshot;
struct nmPebiManualCaptureState;
// 主窗口现在直接创建DLL求解线程任务因此类需要导出供nmSubWnd模块跨DLL使用。
class NMCALCULATION_EXPORT nmCalculationDllPebiSolverTask : public QThread {
@ -29,11 +31,13 @@ class NMCALCULATION_EXPORT nmCalculationDllPebiSolverTask : public QThread {
* @param pDataManager
* @param sAutoFitTargetWellName
* @param parent Qt
* @param bDeferManualSnapshot
*/
nmCalculationDllPebiSolverTask(QString sPostprocessingDir,
nmDataAnalyzeManager* pDataManager = nullptr,
const QString& sAutoFitTargetWellName = QString(),
QObject *parent = nullptr);
QObject *parent = nullptr,
bool bDeferManualSnapshot = false);
~nmCalculationDllPebiSolverTask();
/** @brief QThread 入口,记录本次 execute() 的成功状态并发送完成信号。 */
@ -45,6 +49,16 @@ class NMCALCULATION_EXPORT nmCalculationDllPebiSolverTask : public QThread {
int getSolveTimeMs() const;
/** @brief 获取本次求解的PEBI网格数量。 */
int getPebiCount() const;
/** @brief 请求协作式停止DLL 正在执行时会在返回后丢弃结果。 */
void requestCancel();
/** @brief 返回本次任务是否因手工停止而结束。 */
bool wasCancelled() const;
/**
* @brief DataManager 线
* @param nMaxWellCount
* @param bFinished true start()
*/
bool captureManualInputStep(int nMaxWellCount, bool& bFinished);
/**
* @brief 线
@ -65,8 +79,14 @@ class NMCALCULATION_EXPORT nmCalculationDllPebiSolverTask : public QThread {
bool execute();
/** @brief 成对释放构造时登记的 DataManager 后台使用权。 */
void releaseDataManagerUse();
/** @brief 在任务创建线程一次性捕获网格、井、储层和求解器设置。 */
/** @brief 同步路径在任务创建线程一次性捕获全部求解输入。 */
bool captureInputSnapshot(const QString& sAutoFitTargetWellName);
/** @brief 手工求解在线程内完成场景、缓存和 VTK 基础网格准备。 */
bool prepareManualInput();
/** @brief 无锁读取原子停止标记。 */
bool isCancelRequested() const;
/** @brief 仅为手工求解报告真实执行阶段。 */
void reportStage(const QString& sStage, int nCurrent, int nTotal);
/** @brief 构造 DLL 输入、执行求解并生成仍然有效的局部结果快照。 */
bool execPebiMode();
@ -96,6 +116,8 @@ class NMCALCULATION_EXPORT nmCalculationDllPebiSolverTask : public QThread {
nmDataAnalyzeManager* m_pDataManager;
/** @brief 任务独占的完整值快照;析构时释放,后台禁止访问 DataManager。 */
nmPebiSolverInputSnapshot* m_pInputSnapshot;
/** @brief 仅在界面线程分批捕获期间持有井对象,线程启动前必须释放。 */
nmPebiManualCaptureState* m_pManualCaptureState;
bool m_bManagerUseActive; ///< 是否仍持有 DataManager 后台使用权。
bool m_bInputSnapshotValid; ///< 构造阶段是否已完成全部输入校验。
/** @brief 创建任务时捕获网格输入版本,防止回填过期网格上的结果。 */
@ -117,12 +139,16 @@ class NMCALCULATION_EXPORT nmCalculationDllPebiSolverTask : public QThread {
double m_dPendingScalarMin; ///< 全部场压力的最小值。
double m_dPendingScalarMax; ///< 全部场压力的最大值。
bool m_bPendingFullResultReady; ///< 是否已有可由主线程一次提交的完整结果。
QAtomicInt m_nCancelRequested; ///< 跨线程协作式停止标记1 表示请求停止。
QAtomicInt m_nWasCancelled; ///< run() 已按停止请求丢弃本次局部结果。
private slots:
//void slotTaskUpdateProgress();
signals:
void sig_calculateDone(bool isSuccessed);
/** @brief 报告当前真实阶段nTotal<=0 表示 DLL 等不可测阶段。 */
void sigStageChanged(QString sStage, int nCurrent, int nTotal);
//void sigTaskProgressUpdated(int progress);
//void sigResSolverProgressUpdated(int progress);
};

@ -5,6 +5,8 @@
#include <QVector>
#include <QPointF>
#include <QString>
#include <QSet>
#include <QAtomicInt>
#include "pch.h"
#include "nmDataBinaryTools.h"
@ -14,6 +16,35 @@
#include <vtkSmartPointer.h>
class nmDataAnalyzeManager;
class nmDataWellBase;
/**
* @brief
*
* DataManager 线线使
* DataManager 线访
*/
struct nmPebiWellInputSnapshot
{
nmPebiWellInputSnapshot()
: m_nFlowSectionIndex(1),
m_dWellboreStorage(0.0),
m_dSkin(0.0),
m_bRateControlled(false),
m_bRealWell(false)
{
}
QString m_sWellCode;
QString m_sWellName;
QVector<QPointF> m_vecFlowPoints;
QPointF m_oLocation;
int m_nFlowSectionIndex;
double m_dWellboreStorage;
double m_dSkin;
bool m_bRateControlled;
bool m_bRealWell;
};
/**
* @brief PEBI 使
@ -26,6 +57,7 @@ struct nmPebiGridInputSnapshot
/** @brief 创建空快照m_bValid 为 false不能直接提交给网格 DLL。 */
nmPebiGridInputSnapshot()
: m_nGridInputRevision(0),
m_bCaptured(false),
m_bValid(false)
{
}
@ -33,9 +65,11 @@ struct nmPebiGridInputSnapshot
HX_NWTM_GRID_INPUT m_oGridInput; ///< 边界、井、断层和裂缝等 DLL 输入。
nmDataBinaryTools::NM_PEBI_SCENE m_oScene; ///< 与本次网格输入一致的 ML 场景数据。
QVector<nmSolverWellRef> m_vecSolverWellOrder; ///< DLL 数组槽位对应的稳定井编码顺序。
QVector<nmPebiWellInputSnapshot> m_vecWellInputs; ///< 与 DLL 井槽位严格对齐的值快照。
QString m_sLicensePath; ///< 网格 DLL 授权文件路径副本。
quint64 m_nGridInputRevision; ///< 捕获时的几何输入版本。
bool m_bValid; ///< 主线程是否已完成全部输入校验。
bool m_bCaptured; ///< DataManager 值是否已在所属线程完整捕获。
bool m_bValid; ///< 后台场景准备是否完成,可否提交给网格 DLL。
};
/**
@ -66,13 +100,29 @@ public:
nmCalculationPebiGrid();
~nmCalculationPebiGrid();
/** @brief 在调用线程从 DataManager 复制一份完整且不含指针的网格输入。 */
/** @brief 在调用线程从 DataManager 复制一份完整且不含对象指针的网格输入。 */
bool captureInputSnapshot(nmDataAnalyzeManager* pDataManager,
nmPebiGridInputSnapshot& oSnapshot);
nmPebiGridInputSnapshot& oSnapshot,
bool bDeferPreparation = false);
/** @brief 初始化手工求解的分批网格快照,只在 DataManager 所属线程调用。 */
bool beginManualInputSnapshot(nmDataAnalyzeManager* pDataManager,
nmPebiGridInputSnapshot& oSnapshot);
/** @brief 把一口有效井的几何和值追加到手工求解快照。 */
bool appendManualWellInputSnapshot(nmDataWellBase* pWellData,
int nWellMode,
nmPebiGridInputSnapshot& oSnapshot);
/** @brief 捕获非井输入并完成手工值快照;后台准备前 m_bValid 保持 false。 */
bool finishManualInputSnapshot(nmDataAnalyzeManager* pDataManager,
const QSet<QString>& setEffectiveWellCodes,
nmPebiGridInputSnapshot& oSnapshot);
/** @brief 只使用已捕获的值在后台组装场景;取消时保持 m_bValid 为 false。 */
bool prepareInputSnapshot(nmPebiGridInputSnapshot& oSnapshot,
const QAtomicInt* pCancelRequested = NULL);
/** @brief 仅使用值快照调用网格 DLL并把输出保存在局部结果中。 */
bool calculateSnapshot(const nmPebiGridInputSnapshot& oSnapshot,
nmPebiGridResult& oResult,
bool bCreateUnstructuredGrid = true);
bool bCreateUnstructuredGrid = true,
const QAtomicInt* pCancelRequested = NULL);
/** @brief 在主线程校验输入版本,并一次性提交井顺序、网格和单例缓存。 */
bool commitSnapshotResult(nmDataAnalyzeManager* pDataManager,
const nmPebiGridInputSnapshot& oSnapshot,
@ -90,7 +140,8 @@ private:
/** @brief 按有效计算井集合构造井几何并记录 DLL 井顺序。 */
bool meshGenPebiWells(nmDataAnalyzeManager* pDataManager,
HX_NWTM_GRID_INPUT& inputObj,
QVector<nmSolverWellRef>& vecSolverWellOrder);
QVector<nmSolverWellRef>& vecSolverWellOrder,
const QSet<QString>& setEffectiveWellCodes);
/** @brief 从 Map 构造断层输入。 */
bool meshGenPebiFault(nmDataAnalyzeManager* pDataManager,
HX_NWTM_GRID_INPUT& inputObj);
@ -98,11 +149,9 @@ private:
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 从已捕获值构造与网格输入一致的 ML 场景,不访问 DataManager。 */
bool buildPebiScene(nmPebiGridInputSnapshot& oSnapshot,
const QAtomicInt* pCancelRequested);
/** @brief 更新单例中的 DLL 输入输出缓存,不改变网格有效版本。 */
void commitOutputCache(nmDataAnalyzeManager* pDataManager,
const nmPebiGridInputSnapshot& oSnapshot,
@ -112,7 +161,9 @@ private:
void genPebiVTK(const HX_NWTM_GRID_OUTPUT1& P1, QString vtkDir);
// 根据网格输出结构体创建 vtkUnstructuredGrid 对象
vtkSmartPointer<vtkUnstructuredGrid> createPebiUnstructuredGrid(const HX_NWTM_GRID_OUTPUT1& P1);
vtkSmartPointer<vtkUnstructuredGrid> createPebiUnstructuredGrid(
const HX_NWTM_GRID_OUTPUT1& P1,
const QAtomicInt* pCancelRequested = NULL);
// 日志输出
void logCurrentState();
@ -121,9 +172,14 @@ private:
public:
/** @brief 非阻塞复制当前有效网格DLL忙碌或缓存不匹配时返回 false。 */
bool copyCurrentGridFor(const nmDataAnalyzeManager* pDataManager,
quint64 nGridInputRevision,
HX_NWTM_GRID_OUTPUT1& oGridOutput1,
HX_NWTM_GRID_OUTPUT2& oGridOutput2,
int& nPebiCount) const;
int& nPebiCount,
const QAtomicInt* pCancelRequested = NULL) const;
/** @brief 主线程快速判断缓存归属和版本,不复制大型 DLL 输出。 */
bool isCurrentGridAvailableFor(const nmDataAnalyzeManager* pDataManager,
quint64 nGridInputRevision) const;
public:
/** @brief 在线程锁保护下返回最近一次网格输出 p1 的副本。 */

@ -4,6 +4,7 @@
#include <QString>
#include <QPointF>
#include <QVector>
#include <QAtomicInt>
#include "nmCalculation_global.h"
#include "nmCalculationDefine.h"
#include <iostream>
@ -43,7 +44,8 @@ class NMCALCULATION_EXPORT nmCalculationUtils {
int model,
const QString& licensePath,
QVector<double>& outputValues,
QString* errorMessage = 0);
QString* errorMessage = 0,
const QAtomicInt* pCancelRequested = 0);
};
#endif // NMCALCULATIONUTILS_H

@ -140,7 +140,14 @@ class NM_SUB_WND_EXPORT nmSubWndMain : public iSubWndBaseFit {
void on_calculationFinished(NM_Calculation_Result);
// DLL线程只返回成功/失败,这里转换回原来的计算结果处理流程。
void on_solverTaskFinished(bool isSuccessed);
void onProgressUpdated(int progress); // 新增槽函数,用于更新进度条
/** @brief 进度框显示后再创建任务,避免输入捕获发生在首次绘制之前。 */
void startSolverTask();
/** @brief 每轮事件只捕获少量井输入,保持准备阶段界面和停止按钮可响应。 */
void continueSolverInputCapture();
/** @brief 使用后台任务报告的真实阶段更新进度框。 */
void onSolverStageChanged(QString sStage, int nCurrent, int nTotal);
/** @brief 请求当前手工任务协作式停止。 */
void onSolverCancelRequested();
/** @brief 按 WellCode 切换结果曲线,不改变主分析井和参数编辑井。 */
void onWellSelected(const QString& sWellCode);
@ -148,7 +155,6 @@ class NM_SUB_WND_EXPORT nmSubWndMain : public iSubWndBaseFit {
// 点击左侧按钮求解生成操作
void onGenerateButtonClicked();
void slotUpdateFakeProgress(); // 用于定时器触发
void onDialogCmdDestroyed(QObject*);
private:
@ -257,9 +263,8 @@ private:
QPointer<nmDataAnalyzeManager> m_pSolverDataManager;
/** @brief 启动求解时捕获的拟合窗口,只把结果合并回该窗口。 */
QPointer<iSubWndFitting> m_pSolverFitting;
QTimer* m_pFakeProgressTimer; // 用于模拟进度的定时器
int m_nVirtualProgress; // 记录当前的模拟进度值
int m_nSlowDownCounter; // 记录定时器触发次数,用于控制减速节奏
bool m_bSolverStartPending; ///< 进度框已显示,输入正分批捕获或任务等待启动。
bool m_bSolverCancelRequested; ///< 已向当前任务发送停止请求,忽略后续阶段信号。
#ifdef QT_DEBUG
// 示例演示增加Dock并且追加至程序主窗口

@ -1,4 +1,4 @@
#ifndef NMWXPOSTPROCESSINGANIMATIONWIDGET_H
#ifndef NMWXPOSTPROCESSINGANIMATIONWIDGET_H
#define NMWXPOSTPROCESSINGANIMATIONWIDGET_H
#include "nmSubWxs_global.h"
@ -106,6 +106,9 @@ public:
// 析构函数,用于清理资源,尽管 vtkSmartPointer 会自动管理大部分 VTK 对象的内存
~nmWxPostprocessingAnimationWidget();
/** @brief 复用现有 VTK 管道并切换到 DataManager 最新一次完整结果。 */
void refreshResult();
// 将 VTK 图像数据vtkImageData转换为 Qt 的 QImage 对象
QImage createQImage(vtkImageData* imageData);
// 处理单通道图像,灰度图像

File diff suppressed because it is too large Load Diff

@ -8,6 +8,7 @@
#include <QThread>
#include <cmath>
#include <QSet>
#include <QHash>
#include "nmCalculationUtils.h"
#include "zxLogInstance.h"
@ -41,6 +42,72 @@ const int CONST_PVT_POINT_COUNT = 200;
// 保留递归锁以兼容可能在持锁网格入口中调用缓存查询的旧代码路径。
QMutex s_oPebiGridMutex(QMutex::Recursive);
bool isCancellationRequested(const QAtomicInt* pCancelRequested)
{
return pCancelRequested != NULL &&
static_cast<int>(*pCancelRequested) != 0;
}
class nmInterruptibleMutexLocker
{
public:
nmInterruptibleMutexLocker()
: m_pMutex(NULL),
m_bLocked(false)
{
}
~nmInterruptibleMutexLocker()
{
unlock();
}
bool lock(QMutex* pMutex, const QAtomicInt* pCancelRequested)
{
if(pMutex == NULL || m_bLocked) {
return false;
}
if(pCancelRequested == NULL) {
pMutex->lock();
} else {
// 等锁阶段不能无限阻塞;锁顺序仍保持“网格缓存锁 -> DLL 全局锁”。
while(!pMutex->tryLock(100)) {
if(isCancellationRequested(pCancelRequested)) {
return false;
}
}
}
m_pMutex = pMutex;
m_bLocked = true;
return true;
}
bool tryLock(QMutex* pMutex)
{
if(pMutex == NULL || m_bLocked || !pMutex->tryLock()) {
return false;
}
m_pMutex = pMutex;
m_bLocked = true;
return true;
}
void unlock()
{
if(m_bLocked && m_pMutex != NULL) {
m_pMutex->unlock();
m_bLocked = false;
m_pMutex = NULL;
}
}
private:
QMutex* m_pMutex;
bool m_bLocked;
};
std::vector<double> buildConstantPvtVector(double value)
{
return std::vector<double>(CONST_PVT_POINT_COUNT, value);
@ -242,41 +309,56 @@ void nmCalculationPebiGrid::clearGridData(
bool nmCalculationPebiGrid::copyCurrentGridFor(
const nmDataAnalyzeManager* pDataManager,
quint64 nGridInputRevision,
HX_NWTM_GRID_OUTPUT1& oGridOutput1,
HX_NWTM_GRID_OUTPUT2& oGridOutput2,
int& nPebiCount) const
int& nPebiCount,
const QAtomicInt* pCancelRequested) const
{
// 求解任务在主线程构造,不能为了复制缓存等待正在运行的网格 DLL。
// 锁忙时返回 false任务会在后台基于自己的值快照生成局部网格。
if(!s_oPebiGridMutex.tryLock()) {
nmInterruptibleMutexLocker oGridLocker;
const bool bLocked = pCancelRequested == NULL
? oGridLocker.tryLock(&s_oPebiGridMutex)
: oGridLocker.lock(&s_oPebiGridMutex, pCancelRequested);
if(!bLocked) {
return false;
}
// 第一步:在复制前同时校验所有权、输入版本和实际网格内容
// 后台只比较捕获时的所有权和版本值,不能再读取 DataManager 内部状态
if(pDataManager == nullptr ||
m_pDataManager != pDataManager ||
!pDataManager->isPebiGridValid() ||
pDataManager->getNumericalAnalysisCase() == nullptr ||
m_nCachedGridInputRevision !=
pDataManager->getNumericalAnalysisCase()->getGridInputRevision() ||
m_nCachedGridInputRevision != nGridInputRevision ||
p1.PEBI_cell.p.empty()) {
s_oPebiGridMutex.unlock();
return false;
}
// 第二步:一次性复制两份 DLL 输出,保证求解期间使用同一版本的网格快照。
try {
oGridOutput1 = p1;
oGridOutput2 = p2;
nPebiCount = m_nPebiCount;
} catch(...) {
s_oPebiGridMutex.unlock();
throw;
// 一次性复制两份 DLL 输出,保证求解期间使用同一版本的网格快照。
oGridOutput1 = p1;
if(isCancellationRequested(pCancelRequested)) {
return false;
}
s_oPebiGridMutex.unlock();
oGridOutput2 = p2;
nPebiCount = m_nPebiCount;
return true;
}
bool nmCalculationPebiGrid::isCurrentGridAvailableFor(
const nmDataAnalyzeManager* pDataManager,
quint64 nGridInputRevision) const
{
// 该接口在主线程只做常量时间校验,不复制大型网格输出;锁忙时交给后台重建。
if(!s_oPebiGridMutex.tryLock()) {
return false;
}
const bool bAvailable = pDataManager != nullptr &&
m_pDataManager == pDataManager &&
pDataManager->isPebiGridValid() &&
m_nCachedGridInputRevision == nGridInputRevision &&
!p1.PEBI_cell.p.empty();
s_oPebiGridMutex.unlock();
return bAvailable;
}
void nmCalculationPebiGrid::logInputParameters(const HX_NWTM_GRID_INPUT& input)
{
QString logMsg = "Input Parameters:\n";
@ -415,7 +497,8 @@ bool nmCalculationPebiGrid::meshGenPebiBoundary(
bool nmCalculationPebiGrid::meshGenPebiWells(
nmDataAnalyzeManager* pDataManager,
HX_NWTM_GRID_INPUT& inputObj,
QVector<nmSolverWellRef>& vecSolverWellOrder)
QVector<nmSolverWellRef>& vecSolverWellOrder,
const QSet<QString>& setEffectiveWellCodes)
{
// 从数据中心获取井数据
@ -440,13 +523,6 @@ bool nmCalculationPebiGrid::meshGenPebiWells(
// 第一步:求解器顺序只写入局部快照,后台成功前不修改分析方案。
vecSolverWellOrder.clear();
QSet<QString> setEffectiveWellCodes;
QVector<nmCalculationWellRef> vecEffectiveWells =
pDataManager->getEffectiveCalculationWells();
for(int nIndex = 0; nIndex < vecEffectiveWells.size(); ++nIndex) {
setEffectiveWellCodes.insert(vecEffectiveWells[nIndex].m_sWellCode);
}
// 获取直井数据
QVector<nmDataVerticalWell*> verticalWells = pDataManager->getVerticalWellData();
// 获取垂直裂缝井数据
@ -816,7 +892,9 @@ int nmCalculationPebiGrid::getPebiCount() const
return m_nPebiCount;
}
vtkSmartPointer<vtkUnstructuredGrid> nmCalculationPebiGrid::createPebiUnstructuredGrid(const HX_NWTM_GRID_OUTPUT1& P1)
vtkSmartPointer<vtkUnstructuredGrid> nmCalculationPebiGrid::createPebiUnstructuredGrid(
const HX_NWTM_GRID_OUTPUT1& P1,
const QAtomicInt* pCancelRequested)
{
vtkSmartPointer<vtkUnstructuredGrid> pUnstructuredGrid = vtkSmartPointer<vtkUnstructuredGrid>::New();
vtkSmartPointer<vtkPoints> pPoints = vtkSmartPointer<vtkPoints>::New();
@ -827,6 +905,9 @@ vtkSmartPointer<vtkUnstructuredGrid> nmCalculationPebiGrid::createPebiUnstructur
// 1. 预处理单元数据,确定哪些单元是有效的,并收集这些单元引用的所有唯一点索引
// 这一步先不向 vtkPoints 添加点,而是收集需要添加的点的索引。
for(size_t i = 0; i < P1.PEBI_cell.pindex.size(); ++i) {
if((i % 256) == 0 && isCancellationRequested(pCancelRequested)) {
return nullptr;
}
// 只处理 isplot 为 1 的单元
if(i >= P1.PEBI_cell.isplot.size() || P1.PEBI_cell.isplot[i] != 1) {
continue;
@ -874,6 +955,9 @@ vtkSmartPointer<vtkUnstructuredGrid> nmCalculationPebiGrid::createPebiUnstructur
// 3. 再次遍历单元数据,这次是根据新的 VTK 点索引来插入单元
for(size_t i = 0; i < P1.PEBI_cell.pindex.size(); ++i) {
if((i % 256) == 0 && isCancellationRequested(pCancelRequested)) {
return nullptr;
}
// 再次检查 isplot 标志,确保只处理有效单元
if(i >= P1.PEBI_cell.isplot.size() || P1.PEBI_cell.isplot[i] != 1) {
continue;
@ -925,9 +1009,265 @@ vtkSmartPointer<vtkUnstructuredGrid> nmCalculationPebiGrid::createPebiUnstructur
return pUnstructuredGrid;
}
bool nmCalculationPebiGrid::captureInputSnapshot(
bool nmCalculationPebiGrid::beginManualInputSnapshot(
nmDataAnalyzeManager* pDataManager,
nmPebiGridInputSnapshot& oSnapshot)
{
const nmPebiGridInputSnapshot oEmptySnapshot;
oSnapshot = oEmptySnapshot;
if(pDataManager == nullptr ||
QThread::currentThread() != pDataManager->thread()) {
qWarning() << "Manual PEBI input must be captured on the DataManager thread.";
return false;
}
const nmDataNumericalAnalysisCase* pAnalysisCase =
pDataManager->getNumericalAnalysisCase();
if(pAnalysisCase == nullptr) {
return false;
}
oSnapshot.m_nGridInputRevision =
pAnalysisCase->getGridInputRevision();
oSnapshot.m_oGridInput = HX_NWTM_GRID_INPUT();
oSnapshot.m_oGridInput.GridControl =
pDataManager->getPebiGridControl();
// 边界只捕获一次;后续井批次只追加值,不重复访问已经完成的对象。
return meshGenPebiBoundary(pDataManager, oSnapshot.m_oGridInput);
}
bool nmCalculationPebiGrid::appendManualWellInputSnapshot(
nmDataWellBase* pWellData,
int nWellMode,
nmPebiGridInputSnapshot& oSnapshot)
{
if(pWellData == nullptr || pWellData->getWellCode().isEmpty() ||
oSnapshot.m_bCaptured) {
return false;
}
nmSolverWellRef oWellRef;
nmDataHorizontalFracturedWell* pHorizontalFracturedWell =
dynamic_cast<nmDataHorizontalFracturedWell*>(pWellData);
nmDataVerticalFracturedWell* pVerticalFracturedWell =
dynamic_cast<nmDataVerticalFracturedWell*>(pWellData);
nmDataVerticalWell* pVerticalWell =
dynamic_cast<nmDataVerticalWell*>(pWellData);
if(pHorizontalFracturedWell != nullptr) {
const QVector<QPair<QPointF, QPointF> > vecFracPoints =
pHorizontalFracturedWell->getFracs();
std::vector<std::vector<double> > vecFractures;
vecFractures.reserve(vecFracPoints.size());
for(int nIndex = 0; nIndex < vecFracPoints.size(); ++nIndex) {
dVec1 oCrack(6);
oCrack[0] = vecFracPoints[nIndex].first.x();
oCrack[1] = vecFracPoints[nIndex].first.y();
oCrack[2] = vecFracPoints[nIndex].second.x();
oCrack[3] = vecFracPoints[nIndex].second.y();
oCrack[4] = pHorizontalFracturedWell->getWidth()
.getValue().toDouble();
oCrack[5] = pHorizontalFracturedWell->getDfc()
.getValue().toDouble();
vecFractures.push_back(oCrack);
}
oSnapshot.m_oGridInput.MultistageFracturedHorizontalWell
.push_back(vecFractures);
oWellRef = nmSolverWellRef(
-1,
NM_WELL_MODEL::Horizontal_Fractured_Well,
pWellData->getWellCode());
} else if(pVerticalFracturedWell != nullptr) {
const QVector<QPointF> vecFracPoints =
pVerticalFracturedWell->getFracs();
if(vecFracPoints.size() != 2) {
return false;
}
dVec1 oCrack(6);
oCrack[0] = vecFracPoints[0].x();
oCrack[1] = vecFracPoints[0].y();
oCrack[2] = vecFracPoints[1].x();
oCrack[3] = vecFracPoints[1].y();
oCrack[4] = pVerticalFracturedWell->getWidth()
.getValue().toDouble();
oCrack[5] = pVerticalFracturedWell->getDfc()
.getValue().toDouble();
oSnapshot.m_oGridInput.FractureVerticalWell.push_back(oCrack);
oWellRef = nmSolverWellRef(
-1,
NM_WELL_MODEL::Vertical_Fractured_Well,
pWellData->getWellCode());
} else if(pVerticalWell != nullptr) {
dVec1 oWell(3);
oWell[0] = pVerticalWell->getX().getValue().toDouble();
oWell[1] = pVerticalWell->getY().getValue().toDouble();
oWell[2] = pVerticalWell->getRadius().getValue().toDouble();
oSnapshot.m_oGridInput.VerticalWell.push_back(oWell);
oWellRef = nmSolverWellRef(
-1,
NM_WELL_MODEL::Vertical_Well,
pWellData->getWellCode());
} else {
return false;
}
nmPebiWellInputSnapshot oWellInput;
oWellInput.m_bRealWell = true;
oWellInput.m_sWellCode = pWellData->getWellCode();
oWellInput.m_sWellName = pWellData->getWellName();
oWellInput.m_vecFlowPoints = pWellData->getFlowPoints();
oWellInput.m_nFlowSectionIndex = pWellData->getIndexF();
oWellInput.m_oLocation = QPointF(
pWellData->getX().getValue().toDouble(),
pWellData->getY().getValue().toDouble());
oWellInput.m_dWellboreStorage =
pWellData->getWellboreStorage().getValue().toDouble();
oWellInput.m_dSkin = pWellData->getPerforationCount() > 0
? pWellData->getPerforation(0)->getSkin().getValue().toDouble()
: 0.0;
oWellInput.m_bRateControlled =
nWellMode == static_cast<int>(NM_CaseWell_RateControlled);
if(oWellInput.m_bRateControlled &&
oWellInput.m_vecFlowPoints.size() < 2) {
return false;
}
oSnapshot.m_vecSolverWellOrder.append(oWellRef);
oSnapshot.m_vecWellInputs.append(oWellInput);
return true;
}
bool nmCalculationPebiGrid::finishManualInputSnapshot(
nmDataAnalyzeManager* pDataManager,
const QSet<QString>& setEffectiveWellCodes,
nmPebiGridInputSnapshot& oSnapshot)
{
if(pDataManager == nullptr || setEffectiveWellCodes.isEmpty() ||
QThread::currentThread() != pDataManager->thread() ||
oSnapshot.m_vecSolverWellOrder.size() !=
oSnapshot.m_vecWellInputs.size()) {
return false;
}
const nmDataNumericalAnalysisCase* pAnalysisCase =
pDataManager->getNumericalAnalysisCase();
if(pAnalysisCase == nullptr ||
pAnalysisCase->getGridInputRevision() !=
oSnapshot.m_nGridInputRevision) {
return false;
}
QHash<QString, nmPebiWellInputSnapshot> mapWellInputs;
for(int nIndex = 0; nIndex < oSnapshot.m_vecWellInputs.size(); ++nIndex) {
const nmPebiWellInputSnapshot& oWellInput =
oSnapshot.m_vecWellInputs[nIndex];
if(oWellInput.m_sWellCode.isEmpty() ||
mapWellInputs.contains(oWellInput.m_sWellCode)) {
return false;
}
mapWellInputs.insert(oWellInput.m_sWellCode, oWellInput);
}
if(!meshGenPebiFault(pDataManager, oSnapshot.m_oGridInput) ||
!meshGenPebiCrack(pDataManager,
oSnapshot.m_oGridInput,
oSnapshot.m_vecSolverWellOrder)) {
return false;
}
QVector<nmPebiWellInputSnapshot> vecOrderedWellInputs;
vecOrderedWellInputs.reserve(oSnapshot.m_vecSolverWellOrder.size());
QSet<QString> setOrderedWellCodes;
for(int nIndex = 0;
nIndex < oSnapshot.m_vecSolverWellOrder.size();
++nIndex) {
nmSolverWellRef& oWellRef = oSnapshot.m_vecSolverWellOrder[nIndex];
oWellRef.m_nSolverIndex = nIndex;
if(oWellRef.m_eEntryKind == NM_SolverEntry_ManualFracture) {
vecOrderedWellInputs.append(nmPebiWellInputSnapshot());
continue;
}
if(oWellRef.m_eEntryKind != NM_SolverEntry_Well ||
!setEffectiveWellCodes.contains(oWellRef.m_sWellCode) ||
setOrderedWellCodes.contains(oWellRef.m_sWellCode) ||
!mapWellInputs.contains(oWellRef.m_sWellCode)) {
return false;
}
setOrderedWellCodes.insert(oWellRef.m_sWellCode);
vecOrderedWellInputs.append(mapWellInputs.value(oWellRef.m_sWellCode));
}
if(setOrderedWellCodes != setEffectiveWellCodes) {
return false;
}
oSnapshot.m_vecWellInputs = vecOrderedWellInputs;
const nmDataBinaryTools::NM_PEBI_SCENE oEmptyScene;
oSnapshot.m_oScene = oEmptyScene;
const NM_SOLVER_MODEL_TYPE eSolverModelType =
pDataManager->getSolverModelType();
oSnapshot.m_oScene.solverType =
static_cast<int>(eSolverModelType);
nmDataReservoir* pReservoirData =
pDataManager->getReservoirData();
nmDataPvtParaForPebi* pPvtData =
pDataManager->getPebiPvtPara();
fillScenePvtByModel(oSnapshot.m_oScene,
eSolverModelType,
pPvtData,
pReservoirData);
fillScenePseudoPressureTable(oSnapshot.m_oScene,
eSolverModelType,
pDataManager);
if(pReservoirData != nullptr) {
oSnapshot.m_oScene.Base.Pi = pReservoirData->getInitialPressure()
.getValue().toDouble();
oSnapshot.m_oScene.Base.Cti = pReservoirData->getCt()
.getValue().toDouble();
oSnapshot.m_oScene.Base.Cf = pReservoirData->getCf()
.getValue().toDouble();
oSnapshot.m_oScene.Base.Soi = pReservoirData->getSoi()
.getValue().toDouble();
oSnapshot.m_oScene.Base.Sgi = pReservoirData->getSgi()
.getValue().toDouble();
oSnapshot.m_oScene.Base.Swi = pReservoirData->getSwi()
.getValue().toDouble();
oSnapshot.m_oScene.Base.k_ref =
nmCalculationUtils::milliDarcyToDarcy(
pReservoirData->getPermeability()
.getValue().toDouble());
oSnapshot.m_oScene.Base.phi_ref = pReservoirData->getPorosity()
.getValue().toDouble();
oSnapshot.m_oScene.Base.h_ref = pReservoirData->getThickness()
.getValue().toDouble();
}
nmDataTimeStepSetting* pTimeStepSetting =
pDataManager->getTimeStep();
if(pTimeStepSetting != nullptr) {
oSnapshot.m_oScene.Base.d =
pTimeStepSetting->getTimeGrowthExponent()
.getValue().toDouble();
oSnapshot.m_oScene.Base.dt_Min =
pTimeStepSetting->getMinDeltaTAttribute()
.getValue().toDouble();
oSnapshot.m_oScene.Base.dt_Max =
pTimeStepSetting->getMaxDeltaTAttribute()
.getValue().toDouble();
}
oSnapshot.m_sLicensePath = pDataManager->getLicensePath();
oSnapshot.m_bCaptured = true;
oSnapshot.m_bValid = false;
return true;
}
bool nmCalculationPebiGrid::captureInputSnapshot(
nmDataAnalyzeManager* pDataManager,
nmPebiGridInputSnapshot& oSnapshot,
bool bDeferPreparation)
{
// 使用具名常量触发复制赋值,兼容 Qt 4.8 配套的 VS2010 运行库 ABI。
const nmPebiGridInputSnapshot oEmptySnapshot;
@ -956,11 +1296,23 @@ bool nmCalculationPebiGrid::captureInputSnapshot(
oSnapshot.m_oGridInput.GridControl =
pDataManager->getPebiGridControl();
// 第二步:把 Map 中的边界、有效井、断层和手工裂缝全部转成 DLL 值类型。
// 第二步:有效井集合只计算一次,后续几何、角色和结果井快照共同复用。
const QVector<nmCalculationWellRef> vecEffectiveWells =
pDataManager->getEffectiveCalculationWells();
QSet<QString> setEffectiveWellCodes;
QHash<QString, int> mapWellModes;
for(int nIndex = 0; nIndex < vecEffectiveWells.size(); ++nIndex) {
setEffectiveWellCodes.insert(vecEffectiveWells[nIndex].m_sWellCode);
mapWellModes.insert(vecEffectiveWells[nIndex].m_sWellCode,
static_cast<int>(vecEffectiveWells[nIndex].m_eMode));
}
// 第三步:把 Map 中的边界、有效井、断层和手工裂缝全部转成 DLL 值类型。
if(!meshGenPebiBoundary(pDataManager, oSnapshot.m_oGridInput) ||
!meshGenPebiWells(pDataManager,
oSnapshot.m_oGridInput,
oSnapshot.m_vecSolverWellOrder) ||
oSnapshot.m_vecSolverWellOrder,
setEffectiveWellCodes) ||
!meshGenPebiFault(pDataManager, oSnapshot.m_oGridInput) ||
!meshGenPebiCrack(pDataManager,
oSnapshot.m_oGridInput,
@ -974,14 +1326,7 @@ bool nmCalculationPebiGrid::captureInputSnapshot(
oSnapshot.m_vecSolverWellOrder[nIndex].m_nSolverIndex = nIndex;
}
// 第三步:真实井必须与本次有效计算井一一对应;手工裂缝只占槽位,不参与集合比较。
QSet<QString> setEffectiveWellCodes;
const QVector<nmCalculationWellRef> vecEffectiveWells =
pDataManager->getEffectiveCalculationWells();
for(int nIndex = 0; nIndex < vecEffectiveWells.size(); ++nIndex) {
setEffectiveWellCodes.insert(vecEffectiveWells[nIndex].m_sWellCode);
}
// 第四步:真实井必须与本次有效计算井一一对应;手工裂缝只占槽位。
QSet<QString> setOrderedWellCodes;
bool bSolverOrderValid = !setEffectiveWellCodes.isEmpty();
for(int nIndex = 0;
@ -1009,32 +1354,159 @@ bool nmCalculationPebiGrid::captureInputSnapshot(
return false;
}
// 第四步:场景和授权路径同样在捕获阶段读取,后台不再接触任何井对象。
if(!buildPebiScene(pDataManager,
oSnapshot.m_oGridInput,
oSnapshot.m_vecSolverWellOrder,
oSnapshot.m_oScene)) {
return false;
// 第五步:建立 WellCode 索引并一次复制每口井,避免五十口井时反复线性查找。
QHash<QString, nmDataWellBase*> mapWellsByCode;
const QVector<nmDataWellBase*> vecAllWells =
pDataManager->getWellDataList();
for(int nIndex = 0; nIndex < vecAllWells.size(); ++nIndex) {
nmDataWellBase* pWellData = vecAllWells[nIndex];
if(pWellData != nullptr && !pWellData->getWellCode().isEmpty()) {
mapWellsByCode.insert(pWellData->getWellCode(), pWellData);
}
}
oSnapshot.m_vecWellInputs.clear();
oSnapshot.m_vecWellInputs.reserve(
oSnapshot.m_vecSolverWellOrder.size());
for(int nIndex = 0;
nIndex < oSnapshot.m_vecSolverWellOrder.size();
++nIndex) {
const nmSolverWellRef& oWellRef =
oSnapshot.m_vecSolverWellOrder[nIndex];
nmPebiWellInputSnapshot oWellInput;
oWellInput.m_sWellCode = oWellRef.m_sWellCode;
if(oWellRef.m_eEntryKind == NM_SolverEntry_ManualFracture) {
oSnapshot.m_vecWellInputs.append(oWellInput);
continue;
}
nmDataWellBase* pWellData =
mapWellsByCode.value(oWellRef.m_sWellCode, nullptr);
if(pWellData == nullptr || !mapWellModes.contains(oWellRef.m_sWellCode)) {
qWarning() << "Solver well code is missing from Map:"
<< oWellRef.m_sWellCode;
return false;
}
oWellInput.m_bRealWell = true;
oWellInput.m_sWellName = pWellData->getWellName();
oWellInput.m_vecFlowPoints = pWellData->getFlowPoints();
oWellInput.m_nFlowSectionIndex = pWellData->getIndexF();
oWellInput.m_oLocation = QPointF(
pWellData->getX().getValue().toDouble(),
pWellData->getY().getValue().toDouble());
oWellInput.m_dWellboreStorage =
pWellData->getWellboreStorage().getValue().toDouble();
oWellInput.m_dSkin = pWellData->getPerforationCount() > 0
? pWellData->getPerforation(0)->getSkin()
.getValue().toDouble()
: 0.0;
oWellInput.m_bRateControlled =
mapWellModes.value(oWellRef.m_sWellCode) ==
static_cast<int>(NM_CaseWell_RateControlled);
if(oWellInput.m_bRateControlled &&
oWellInput.m_vecFlowPoints.size() < 2) {
qWarning() << "Rate-controlled well has no valid rate schedule:"
<< oWellRef.m_sWellCode;
return false;
}
oSnapshot.m_vecWellInputs.append(oWellInput);
}
// 第六步PVT、储层和时间步仍在主线程复制为值较重的井制度和场景数组
// 组装可由手工求解任务延后到后台执行。
const nmDataBinaryTools::NM_PEBI_SCENE oEmptyScene;
oSnapshot.m_oScene = oEmptyScene;
const NM_SOLVER_MODEL_TYPE eSolverModelType =
pDataManager->getSolverModelType();
oSnapshot.m_oScene.solverType =
static_cast<int>(eSolverModelType);
nmDataReservoir* pReservoirData =
pDataManager->getReservoirData();
nmDataPvtParaForPebi* pPvtData =
pDataManager->getPebiPvtPara();
fillScenePvtByModel(oSnapshot.m_oScene,
eSolverModelType,
pPvtData,
pReservoirData);
fillScenePseudoPressureTable(oSnapshot.m_oScene,
eSolverModelType,
pDataManager);
if(pReservoirData != nullptr) {
oSnapshot.m_oScene.Base.Pi = pReservoirData->getInitialPressure()
.getValue().toDouble();
oSnapshot.m_oScene.Base.Cti = pReservoirData->getCt()
.getValue().toDouble();
oSnapshot.m_oScene.Base.Cf = pReservoirData->getCf()
.getValue().toDouble();
oSnapshot.m_oScene.Base.Soi = pReservoirData->getSoi()
.getValue().toDouble();
oSnapshot.m_oScene.Base.Sgi = pReservoirData->getSgi()
.getValue().toDouble();
oSnapshot.m_oScene.Base.Swi = pReservoirData->getSwi()
.getValue().toDouble();
oSnapshot.m_oScene.Base.k_ref =
nmCalculationUtils::milliDarcyToDarcy(
pReservoirData->getPermeability()
.getValue().toDouble());
oSnapshot.m_oScene.Base.phi_ref = pReservoirData->getPorosity()
.getValue().toDouble();
oSnapshot.m_oScene.Base.h_ref = pReservoirData->getThickness()
.getValue().toDouble();
}
nmDataTimeStepSetting* pTimeStepSetting =
pDataManager->getTimeStep();
if(pTimeStepSetting != nullptr) {
oSnapshot.m_oScene.Base.d =
pTimeStepSetting->getTimeGrowthExponent()
.getValue().toDouble();
oSnapshot.m_oScene.Base.dt_Min =
pTimeStepSetting->getMinDeltaTAttribute()
.getValue().toDouble();
oSnapshot.m_oScene.Base.dt_Max =
pTimeStepSetting->getMaxDeltaTAttribute()
.getValue().toDouble();
}
oSnapshot.m_sLicensePath = pDataManager->getLicensePath();
oSnapshot.m_bCaptured = true;
oSnapshot.m_bValid = true;
return true;
return bDeferPreparation
? true
: prepareInputSnapshot(oSnapshot);
}
bool nmCalculationPebiGrid::buildPebiScene(
nmDataAnalyzeManager* pDataManager,
const HX_NWTM_GRID_INPUT& oGridInput,
const QVector<nmSolverWellRef>& vecSolverWellOrder,
nmDataBinaryTools::NM_PEBI_SCENE& oScene)
bool nmCalculationPebiGrid::prepareInputSnapshot(
nmPebiGridInputSnapshot& oSnapshot,
const QAtomicInt* pCancelRequested)
{
if(pDataManager == nullptr) {
oSnapshot.m_bValid = false;
if(!oSnapshot.m_bCaptured ||
oSnapshot.m_vecWellInputs.size() !=
oSnapshot.m_vecSolverWellOrder.size() ||
isCancellationRequested(pCancelRequested)) {
return false;
}
// 第一步:复制网格基础数据和井显示信息。
const nmDataBinaryTools::NM_PEBI_SCENE oEmptyScene;
oScene = oEmptyScene;
return buildPebiScene(oSnapshot, pCancelRequested);
}
bool nmCalculationPebiGrid::buildPebiScene(
nmPebiGridInputSnapshot& oSnapshot,
const QAtomicInt* pCancelRequested)
{
const HX_NWTM_GRID_INPUT& oGridInput = oSnapshot.m_oGridInput;
const QVector<nmSolverWellRef>& vecSolverWellOrder =
oSnapshot.m_vecSolverWellOrder;
const QVector<nmPebiWellInputSnapshot>& vecWellInputs =
oSnapshot.m_vecWellInputs;
nmDataBinaryTools::NM_PEBI_SCENE& oScene = oSnapshot.m_oScene;
// 第一步复制网格基础数据。PVT、储层和时间步已在主线程值化。
oScene.version = 1;
oScene.D = oGridInput.D;
oScene.GridControl = oGridInput.GridControl;
@ -1052,20 +1524,17 @@ bool nmCalculationPebiGrid::buildPebiScene(
oScene.wellType.reserve(vecSolverWellOrder.size());
oScene.wellName.reserve(vecSolverWellOrder.size());
for(int nIndex = 0; nIndex < vecSolverWellOrder.size(); ++nIndex) {
if(isCancellationRequested(pCancelRequested)) {
return false;
}
const nmSolverWellRef& oWellRef = vecSolverWellOrder[nIndex];
oScene.wellType.push_back(static_cast<int>(oWellRef.m_eWellType));
nmDataWellBase* pWellData =
pDataManager->findWellByCode(oWellRef.m_sWellCode);
oScene.wellName.push_back(pWellData != nullptr
? pWellData->getWellName()
: QString());
oScene.wellName.push_back(vecWellInputs[nIndex].m_sWellName);
}
// 第二步:按求解器模型填充每个槽位的产量制度。
const NM_SOLVER_MODEL_TYPE eSolverModelType =
pDataManager->getSolverModelType();
oScene.solverType = static_cast<int>(eSolverModelType);
static_cast<NM_SOLVER_MODEL_TYPE>(oScene.solverType);
oScene.Rate.t.resize(vecSolverWellOrder.size());
oScene.Rate.qo.resize(vecSolverWellOrder.size());
oScene.Rate.qg.resize(vecSolverWellOrder.size());
@ -1076,21 +1545,20 @@ bool nmCalculationPebiGrid::buildPebiScene(
++nWellIndex) {
const nmSolverWellRef& oWellRef =
vecSolverWellOrder[nWellIndex];
const nmPebiWellInputSnapshot& oWellInput =
vecWellInputs[nWellIndex];
// 手工裂缝和观察井保留外层槽位,但不提供源汇项。
if(oWellRef.m_eEntryKind == NM_SolverEntry_ManualFracture ||
pDataManager->getCalculationWellMode(
oWellRef.m_sWellCode) == NM_CaseWell_Observation) {
continue;
if(isCancellationRequested(pCancelRequested)) {
return false;
}
nmDataWellBase* pWellData =
pDataManager->findWellByCode(oWellRef.m_sWellCode);
if(pWellData == nullptr) {
// 手工裂缝和观察井保留外层槽位,但不提供源汇项。
if(oWellRef.m_eEntryKind == NM_SolverEntry_ManualFracture ||
!oWellInput.m_bRateControlled) {
continue;
}
QVector<QPointF> vecTimeQ = pWellData->getFlowPoints();
QVector<QPointF> vecTimeQ = oWellInput.m_vecFlowPoints;
if(!vecTimeQ.isEmpty() &&
vecTimeQ[0].x() == 0.0 &&
vecTimeQ[0].y() == 0.0) {
@ -1104,6 +1572,10 @@ bool nmCalculationPebiGrid::buildPebiScene(
for(int nPointIndex = 0;
nPointIndex < vecTimeQ.size();
++nPointIndex) {
if((nPointIndex % 256) == 0 &&
isCancellationRequested(pCancelRequested)) {
return false;
}
vecTime.push_back(vecTimeQ[nPointIndex].x());
vecRate.push_back(vecTimeQ[nPointIndex].y());
}
@ -1129,6 +1601,9 @@ bool nmCalculationPebiGrid::buildPebiScene(
for(size_t nWellIndex = 0;
nWellIndex < oScene.Rate.t.size();
++nWellIndex) {
if(isCancellationRequested(pCancelRequested)) {
return false;
}
oScene.Rate.qg[nWellIndex].assign(
oScene.Rate.t[nWellIndex].size(), 0.0);
oScene.Rate.qw[nWellIndex].assign(
@ -1146,6 +1621,11 @@ bool nmCalculationPebiGrid::buildPebiScene(
++nWellIndex) {
const nmSolverWellRef& oWellRef =
vecSolverWellOrder[nWellIndex];
const nmPebiWellInputSnapshot& oWellInput =
vecWellInputs[nWellIndex];
if(isCancellationRequested(pCancelRequested)) {
return false;
}
if(oWellRef.m_eEntryKind == NM_SolverEntry_ManualFracture) {
oScene.CS.C[nWellIndex] = 0.0;
oScene.CS.S[nWellIndex] = 0.0;
@ -1153,89 +1633,39 @@ bool nmCalculationPebiGrid::buildPebiScene(
continue;
}
nmDataWellBase* pWellData =
pDataManager->findWellByCode(oWellRef.m_sWellCode);
if(pWellData == nullptr) {
oScene.wellFlowSectionIndex[nWellIndex] = 1;
continue;
}
oScene.CS.C[nWellIndex] =
pWellData->getWellboreStorage().getValue().toDouble();
oScene.CS.S[nWellIndex] =
pWellData->getPerforationCount() > 0
? pWellData->getPerforation(0)->getSkin()
.getValue().toDouble()
: 0.0;
oScene.CS.C[nWellIndex] = oWellInput.m_dWellboreStorage;
oScene.CS.S[nWellIndex] = oWellInput.m_dSkin;
oScene.wellFlowSectionIndex[nWellIndex] =
pWellData->getIndexF();
}
// 第四步:复制 PVT、拟压力、储层和时间步参数。
nmDataReservoir* pReservoirData =
pDataManager->getReservoirData();
nmDataPvtParaForPebi* pPvtData =
pDataManager->getPebiPvtPara();
fillScenePvtByModel(oScene,
eSolverModelType,
pPvtData,
pReservoirData);
fillScenePseudoPressureTable(oScene,
eSolverModelType,
pDataManager);
if(pReservoirData != nullptr) {
oScene.Base.Pi = pReservoirData->getInitialPressure()
.getValue().toDouble();
oScene.Base.Cti = pReservoirData->getCt()
.getValue().toDouble();
oScene.Base.Cf = pReservoirData->getCf()
.getValue().toDouble();
oScene.Base.Soi = pReservoirData->getSoi()
.getValue().toDouble();
oScene.Base.Sgi = pReservoirData->getSgi()
.getValue().toDouble();
oScene.Base.Swi = pReservoirData->getSwi()
.getValue().toDouble();
oScene.Base.k_ref = nmCalculationUtils::milliDarcyToDarcy(
pReservoirData->getPermeability()
.getValue().toDouble());
oScene.Base.phi_ref = pReservoirData->getPorosity()
.getValue().toDouble();
oScene.Base.h_ref = pReservoirData->getThickness()
.getValue().toDouble();
}
nmDataTimeStepSetting* pTimeStepSetting =
pDataManager->getTimeStep();
if(pTimeStepSetting != nullptr) {
oScene.Base.d = pTimeStepSetting->getTimeGrowthExponent()
.getValue().toDouble();
oScene.Base.dt_Min = pTimeStepSetting->getMinDeltaTAttribute()
.getValue().toDouble();
oScene.Base.dt_Max = pTimeStepSetting->getMaxDeltaTAttribute()
.getValue().toDouble();
oWellInput.m_nFlowSectionIndex;
}
return true;
oSnapshot.m_bValid = !isCancellationRequested(pCancelRequested);
return oSnapshot.m_bValid;
}
bool nmCalculationPebiGrid::calculateSnapshot(
const nmPebiGridInputSnapshot& oSnapshot,
nmPebiGridResult& oResult,
bool bCreateUnstructuredGrid)
bool bCreateUnstructuredGrid,
const QAtomicInt* pCancelRequested)
{
QMutexLocker oLocker(&s_oPebiGridMutex);
nmInterruptibleMutexLocker oGridLocker;
if(!oGridLocker.lock(&s_oPebiGridMutex, pCancelRequested)) {
return false;
}
const nmPebiGridResult oEmptyResult;
oResult = oEmptyResult;
if(!oSnapshot.m_bValid) {
if(!oSnapshot.m_bValid || isCancellationRequested(pCancelRequested)) {
return false;
}
// 建网、模型求解和 Kriging 共用 DLL 全局状态,加载至读取结果期间必须串行。
QMutexLocker oDllLocker(
nmCalculationUtils::getHxNwtmDllMutex());
nmInterruptibleMutexLocker oDllLocker;
if(!oDllLocker.lock(nmCalculationUtils::getHxNwtmDllMutex(),
pCancelRequested)) {
return false;
}
HMODULE hGridModule = LoadLibrary(L"HX_NWTM.dll");
if(hGridModule == nullptr) {
qWarning() << "Failed to load HX_NWTM.dll. Error code:"
@ -1289,6 +1719,11 @@ bool nmCalculationPebiGrid::calculateSnapshot(
zxLogInstance::getInstance()->writeLogF(
"scene exported: " + sScenePath);
if(isCancellationRequested(pCancelRequested)) {
FreeLibrary(hGridModule);
return false;
}
// 第二步DLL 输出先落在局部结果,失败时不清空单例缓存和已有成果。
pfnGenerateGrid(oResult.m_oGridOutput1,
oResult.m_oGridOutput2,
@ -1301,18 +1736,24 @@ bool nmCalculationPebiGrid::calculateSnapshot(
hGridModule = nullptr;
oDllLocker.unlock();
// DLL 本身没有取消入口;若执行期间收到停止请求,返回后立即丢弃局部输出。
if(isCancellationRequested(pCancelRequested)) {
return false;
}
// 自动拟合只需要 DLL 数组,可跳过 VTK 构造;网格任务必须生成完整 VTK。
if(bCreateUnstructuredGrid) {
oResult.m_pUnstructuredGrid =
createPebiUnstructuredGrid(
oResult.m_oGridOutput1);
oResult.m_oGridOutput1,
pCancelRequested);
if(oResult.m_pUnstructuredGrid == nullptr ||
oResult.m_pUnstructuredGrid->GetNumberOfCells() <= 0) {
return false;
}
}
oResult.m_bSucceeded = true;
oResult.m_bSucceeded = !isCancellationRequested(pCancelRequested);
} catch(const std::exception& e) {
zxLogInstance::getInstance()->writeLogF(
QString("C++ Exception: %1").arg(e.what()));

@ -17,6 +17,53 @@ namespace
// HX_NWTM.dll 的配置和结果查询接口使用进程级共享状态,所有入口共用此锁。
QMutex s_oHxNwtmDllMutex;
bool isKrigingCancellationRequested(const QAtomicInt* pCancelRequested)
{
return pCancelRequested != NULL &&
static_cast<int>(*pCancelRequested) != 0;
}
class nmKrigingMutexLocker
{
public:
nmKrigingMutexLocker()
: m_pMutex(NULL),
m_bLocked(false)
{
}
~nmKrigingMutexLocker()
{
if(m_bLocked && m_pMutex != NULL) {
m_pMutex->unlock();
}
}
bool lock(QMutex* pMutex, const QAtomicInt* pCancelRequested)
{
if(pMutex == NULL) {
return false;
}
if(pCancelRequested == NULL) {
pMutex->lock();
} else {
// 只改变等待方式,不改变 DLL 全局锁的保护范围和互斥语义。
while(!pMutex->tryLock(100)) {
if(isKrigingCancellationRequested(pCancelRequested)) {
return false;
}
}
}
m_pMutex = pMutex;
m_bLocked = true;
return true;
}
private:
QMutex* m_pMutex;
bool m_bLocked;
};
QString krigingText(const char* sourceText)
{
return QCoreApplication::translate("nmCalculationUtils", sourceText);
@ -213,11 +260,17 @@ bool nmCalculationUtils::calculateKriging(
int model,
const QString& licensePath,
QVector<double>& outputValues,
QString* errorMessage)
QString* errorMessage,
const QAtomicInt* pCancelRequested)
{
outputValues.clear();
setKrigingError(errorMessage, QString());
if(isKrigingCancellationRequested(pCancelRequested)) {
setKrigingError(errorMessage, krigingText("Kriging calculation was cancelled."));
return false;
}
if(targetPoints.isEmpty()) {
setKrigingError(errorMessage, krigingText("No interpolation points are available."));
return false;
@ -247,6 +300,11 @@ bool nmCalculationUtils::calculateKriging(
}
for(int i = 0; i < targetPoints.size(); ++i) {
if((i % 256) == 0 &&
isKrigingCancellationRequested(pCancelRequested)) {
setKrigingError(errorMessage, krigingText("Kriging calculation was cancelled."));
return false;
}
if(!isFiniteValue(targetPoints[i].x()) || !isFiniteValue(targetPoints[i].y())) {
setKrigingError(errorMessage,
krigingText("An interpolation point contains an invalid coordinate."));
@ -255,6 +313,10 @@ bool nmCalculationUtils::calculateKriging(
}
for(int i = 0; i < measurementPoints.size(); ++i) {
if(isKrigingCancellationRequested(pCancelRequested)) {
setKrigingError(errorMessage, krigingText("Kriging calculation was cancelled."));
return false;
}
if(!isFiniteValue(measurementPoints[i].x()) ||
!isFiniteValue(measurementPoints[i].y()) ||
!isFiniteValue(measurementValues[i])) {
@ -278,7 +340,11 @@ bool nmCalculationUtils::calculateKriging(
}
// Kriging 与建网、模型求解来自同一个 DLL不能在不同线程中并发进入。
QMutexLocker oDllLocker(getHxNwtmDllMutex());
nmKrigingMutexLocker oDllLocker;
if(!oDllLocker.lock(getHxNwtmDllMutex(), pCancelRequested)) {
setKrigingError(errorMessage, krigingText("Kriging calculation was cancelled."));
return false;
}
HMODULE dll = LoadLibrary(L"HX_NWTM.dll");
if(dll == NULL) {
setKrigingError(errorMessage, krigingText("Failed to load HX_NWTM.dll."));
@ -338,6 +404,13 @@ bool nmCalculationUtils::calculateKriging(
calculationError = krigingText("Kriging calculation failed.");
}
// DLL 无取消入口;调用期间收到停止请求时只丢弃返回值,不提交插值结果。
if(calculationSucceeded &&
isKrigingCancellationRequested(pCancelRequested)) {
calculationSucceeded = false;
calculationError = krigingText("Kriging calculation was cancelled.");
}
if(calculationSucceeded &&
output.v.size() != static_cast<size_t>(targetPoints.size())) {
calculationSucceeded = false;
@ -348,6 +421,12 @@ bool nmCalculationUtils::calculateKriging(
if(calculationSucceeded) {
outputValues.reserve(targetPoints.size());
for(size_t i = 0; i < output.v.size(); ++i) {
if((i % 256) == 0 &&
isKrigingCancellationRequested(pCancelRequested)) {
calculationSucceeded = false;
calculationError = krigingText("Kriging calculation was cancelled.");
break;
}
if(!isFiniteValue(output.v[i])) {
calculationSucceeded = false;
calculationError = krigingText(

@ -67,6 +67,8 @@
#include <QVector>
#include <QSet>
#include <QHash>
#include <QPushButton>
#include "nmSingalCenter.h"
@ -95,6 +97,7 @@ namespace
// 保留原来求解器单例的防重入语义全局同一时间只允许一个DLL求解任务运行。
// QPointer会在QObject销毁后自动变空避免保留已经释放的线程指针。
QPointer<nmCalculationDllPebiSolverTask> s_pRunningSolverTask;
QPointer<nmSubWndMain> s_pPendingSolverWindow;
}
nmSubWndMain::nmSubWndMain(QWidget *parent, QString sExt) :
@ -127,9 +130,8 @@ nmSubWndMain::nmSubWndMain(QWidget *parent, QString sExt) :
m_pSolverTask = nullptr;
m_pSolverDataManager = nullptr;
m_pSolverFitting = nullptr;
m_pFakeProgressTimer = nullptr;
m_nVirtualProgress = 0;
m_nSlowDownCounter = 0;
m_bSolverStartPending = false;
m_bSolverCancelRequested = false;
m_pPlotToolBar = nullptr;
}
@ -171,11 +173,11 @@ nmSubWndMain::~nmSubWndMain()
// }
//}
if (m_pFakeProgressTimer) {
m_pFakeProgressTimer->stop();
// 因为构造时传了 this这里不 delete 也可以,但 disconnect 是安全的
m_pFakeProgressTimer->disconnect();
}
m_bSolverStartPending = false;
if(s_pPendingSolverWindow == this) {
s_pPendingSolverWindow = nullptr;
}
if (m_pProgressDlg) {
delete m_pProgressDlg;
@ -185,8 +187,9 @@ nmSubWndMain::~nmSubWndMain()
if (m_pSolverTask) {
// 第一步:停止向正在析构的窗口投递完成回调。
disconnect(m_pSolverTask, SIGNAL(sig_calculateDone(bool)), this, SLOT(on_solverTaskFinished(bool)));
m_pSolverTask->requestCancel();
// 第二步DataManager 会在成果窗口销毁后释放,而任务仍读取该对象。
// Qt 4.8 下只能等待外部 DLL 正常返回,绝不能让裸指针越过管理器生命周期
// Qt 4.8 下 DLL 执行中只能等待其正常返回,绝不能强杀持锁线程
if(m_pSolverTask->isRunning()) {
m_pSolverTask->wait();
}
@ -1518,6 +1521,12 @@ void nmSubWndMain::generationMesh()
void nmSubWndMain::solveAndAnalyze()
{
if(!s_pPendingSolverWindow.isNull() ||
!s_pRunningSolverTask.isNull()) {
QMessageBox::information(this, tr("solver error"), tr("task is running!"));
return;
}
// 强制清理旧的(以防万一上次没删掉)
if (m_pProgressDlg != nullptr) {
delete m_pProgressDlg;
@ -1556,11 +1565,20 @@ void nmSubWndMain::solveAndAnalyze()
// 观察井可以完全不传产量,但不能在所有井都是观察井时启动无源汇计算。
QVector<nmCalculationWellRef> vecEffectiveWells =
pDataManager->getEffectiveCalculationWells();
QHash<QString, nmDataWellBase*> mapWellsByCode;
const QVector<nmDataWellBase*> vecAllWells =
pDataManager->getWellDataList();
for(int nIndex = 0; nIndex < vecAllWells.size(); ++nIndex) {
nmDataWellBase* pWellData = vecAllWells[nIndex];
if(pWellData != nullptr) {
mapWellsByCode.insert(pWellData->getWellCode(), pWellData);
}
}
bool bHasRateControlledWell = false;
for(int nIndex = 0; nIndex < vecEffectiveWells.size(); ++nIndex) {
const nmCalculationWellRef& oWellRef = vecEffectiveWells[nIndex];
nmDataWellBase* pWellData =
pDataManager->findWellByCode(oWellRef.m_sWellCode);
mapWellsByCode.value(oWellRef.m_sWellCode, nullptr);
if(pWellData == nullptr) {
QMessageBox::warning(this, tr("solver error"),
tr("A selected well is missing from the map."));
@ -1605,23 +1623,6 @@ void nmSubWndMain::solveAndAnalyze()
}
}
// 调用求解器
QString sPostprocessingPath = "";
// 获取计算类型
NM_Grid_Type gridType = pDataManager->getGridType();
// PEBI求解直接使用当前后处理目录
if(gridType == NM_Grid_PEBI) {
//sPostprocessingPath = sPostprocessingPath + "/Pebi";
}
// 以前由旧的DLL求解器单例阻止重复启动删掉中间层后在这里保留同样的保护。
if(!s_pRunningSolverTask.isNull() && s_pRunningSolverTask->isRunning()) {
QMessageBox::information(this, tr("solver error"), tr("task is running!"));
return;
}
if(m_pSolverTask != nullptr) {
// 上一次线程已结束但指针还未清空时先交给Qt事件循环安全释放。
m_pSolverTask->deleteLater();
@ -1631,48 +1632,184 @@ void nmSubWndMain::solveAndAnalyze()
// 第一步:捕获本次求解的完整窗口上下文,完成回调不得再读取全局当前窗口。
m_pSolverDataManager = pDataManager;
m_pSolverFitting = pSubWndFit;
m_bSolverCancelRequested = false;
// 第二步:直接创建真正执行 DLL 计算的 PEBI 线程任务。
m_pSolverTask = new nmCalculationDllPebiSolverTask(sPostprocessingPath,
pDataManager);
s_pRunningSolverTask = m_pSolverTask;
connect(m_pSolverTask, SIGNAL(sig_calculateDone(bool)), this, SLOT(on_solverTaskFinished(bool)));
// 1. 创建并配置进度对话框
// 第二步:先显示真实进度框,再在下一轮事件中捕获输入并启动线程。
if (m_pProgressDlg == nullptr)
{
// TODO:关联到父窗口
m_pProgressDlg = new QProgressDialog(tr("Calculating, please wait..."), QString(), 0, 100, getMainWindow());
m_pProgressDlg = new QProgressDialog(
tr("Preparing input snapshot..."),
tr("Stop"),
0,
0,
getMainWindow());
m_pProgressDlg->setWindowTitle(tr("Solver Progress"));
// 后台只读取任务值快照;应用级模态仍用于阻止重复启动、切换成果等会让
// 用户误判当前计算归属的操作,数据安全不再依赖该对话框冻结界面。
m_pProgressDlg->setWindowModality(Qt::ApplicationModal);
// 某些系统下,对话框右上角的关闭按钮可能仍存
// 使用 WindowFlags 彻底禁用关闭按钮
m_pProgressDlg->setWindowFlags(Qt::Dialog | Qt::WindowTitleHint | Qt::CustomizeWindowHint);
// 使用 WindowFlags 彻底禁用关闭按钮
m_pProgressDlg->setWindowFlags(Qt::Dialog | Qt::WindowTitleHint | Qt::CustomizeWindowHint);
m_pProgressDlg->setMinimumDuration(0);
m_pProgressDlg->setAutoClose(false);
m_pProgressDlg->setAutoReset(false);
connect(m_pProgressDlg, SIGNAL(canceled()),
this, SLOT(onSolverCancelRequested()));
}
m_pProgressDlg->setValue(0);
m_pProgressDlg->setLabelText(tr("Preparing input snapshot..."));
m_pProgressDlg->setRange(0, 0);
m_pProgressDlg->show();
m_pProgressDlg->raise(); // 提升层级到最前
m_pProgressDlg->activateWindow(); // 激活窗口焦点
// 2. 初始化模拟进度逻辑
// 每次计算启动前,务必清零
m_nVirtualProgress = 0;
m_nSlowDownCounter = 0;
m_bSolverStartPending = true;
s_pPendingSolverWindow = this;
QTimer::singleShot(0, this, SLOT(startSolverTask()));
}
void nmSubWndMain::startSolverTask()
{
if(!m_bSolverStartPending) {
return;
}
// 创建定时器 (如果不存在)
if (m_pFakeProgressTimer == nullptr) {
m_pFakeProgressTimer = new QTimer(this);
connect(m_pFakeProgressTimer, SIGNAL(timeout()), this, SLOT(slotUpdateFakeProgress()));
if(m_pProgressDlg == nullptr || m_pSolverDataManager.isNull() ||
m_pSolverFitting.isNull()) {
m_bSolverStartPending = false;
if(s_pPendingSolverWindow == this) {
s_pPendingSolverWindow = nullptr;
}
if(m_pProgressDlg != nullptr) {
m_pProgressDlg->deleteLater();
m_pProgressDlg = nullptr;
}
m_pSolverDataManager = nullptr;
m_pSolverFitting = nullptr;
return;
}
// 3. 启动计算并开启模拟进度
m_pSolverTask->start();
m_pFakeProgressTimer->start(500); // 每 500 毫秒增加一次
// 先创建不自动捕获的任务,使停止按钮在第一批井输入开始前就有明确接收者。
m_pSolverTask = new nmCalculationDllPebiSolverTask(
QString(),
m_pSolverDataManager.data(),
QString(),
nullptr,
true);
s_pRunningSolverTask = m_pSolverTask;
connect(m_pSolverTask, SIGNAL(sig_calculateDone(bool)),
this, SLOT(on_solverTaskFinished(bool)));
connect(m_pSolverTask, SIGNAL(sigStageChanged(QString,int,int)),
this, SLOT(onSolverStageChanged(QString,int,int)));
// 给 Windows 一次完成首帧绘制的机会;后续每个事件循环只捕获一口井。
QTimer::singleShot(20, this, SLOT(continueSolverInputCapture()));
}
void nmSubWndMain::continueSolverInputCapture()
{
if(!m_bSolverStartPending || m_pSolverTask == nullptr) {
return;
}
bool bFinished = false;
const bool bSucceeded =
m_pSolverTask->captureManualInputStep(1, bFinished);
if(!bSucceeded) {
nmCalculationDllPebiSolverTask* pTask = m_pSolverTask;
const bool bCancelled = pTask->wasCancelled();
m_pSolverTask = nullptr;
if(pTask == s_pRunningSolverTask) {
s_pRunningSolverTask = nullptr;
}
if(s_pPendingSolverWindow == this) {
s_pPendingSolverWindow = nullptr;
}
m_bSolverStartPending = false;
disconnect(pTask, nullptr, this, nullptr);
pTask->deleteLater();
if(bCancelled) {
delete m_pProgressDlg;
m_pProgressDlg = nullptr;
} else {
on_calculationFinished(NM_Calculation_Result_Fail);
}
m_pSolverDataManager = nullptr;
m_pSolverFitting = nullptr;
m_bSolverCancelRequested = false;
return;
}
if(bFinished) {
// 临时井对象指针已全部释放,只有完整值快照可以进入后台线程。
m_bSolverStartPending = false;
if(s_pPendingSolverWindow == this) {
s_pPendingSolverWindow = nullptr;
}
m_pSolverTask->start();
return;
}
QTimer::singleShot(0, this, SLOT(continueSolverInputCapture()));
}
void nmSubWndMain::onSolverStageChanged(
QString sStage,
int nCurrent,
int nTotal)
{
if(sender() != m_pSolverTask || m_pProgressDlg == nullptr ||
m_bSolverCancelRequested) {
return;
}
m_pProgressDlg->setLabelText(sStage);
if(nTotal <= 0) {
m_pProgressDlg->setRange(0, 0);
} else {
m_pProgressDlg->setRange(0, nTotal);
m_pProgressDlg->setValue(qBound(0, nCurrent, nTotal));
}
}
void nmSubWndMain::onSolverCancelRequested()
{
if(m_bSolverStartPending && m_pSolverTask == nullptr) {
// 尚未创建任务时可立即撤销DataManager 也尚未登记后台使用权。
m_bSolverStartPending = false;
if(s_pPendingSolverWindow == this) {
s_pPendingSolverWindow = nullptr;
}
// canceled() 由进度框自身发出,延迟释放避免在信号调用栈内销毁发送者。
if(m_pProgressDlg != nullptr) {
m_pProgressDlg->deleteLater();
}
m_pProgressDlg = nullptr;
m_pSolverDataManager = nullptr;
m_pSolverFitting = nullptr;
m_bSolverCancelRequested = false;
return;
}
if(m_pSolverTask != nullptr) {
m_bSolverCancelRequested = true;
m_pSolverTask->requestCancel();
if(m_pProgressDlg != nullptr) {
// DLL 没有取消导出;若已进入 DLL只能等待其返回后丢弃结果。
m_pProgressDlg->setLabelText(
tr("Stopping; waiting for the current solver call to return..."));
m_pProgressDlg->setRange(0, 0);
QPushButton* pCancelButton =
m_pProgressDlg->findChild<QPushButton*>();
if(pCancelButton != nullptr) {
pCancelButton->setEnabled(false);
}
m_pProgressDlg->show();
m_pProgressDlg->raise();
}
}
}
void nmSubWndMain::triggerToolBarAction(int index)
@ -1799,104 +1936,68 @@ void nmSubWndMain::mergeAnaResultToFitting()
QString errorMessage4;
pSubWndFit->adjustFitSubPlotBy(tag, vecHistory, true, &errorMessage4);
// 更新结果基础网格,与当前实时生成的网格保持一致
pInstance->setResultBaseGrid(pInstance->getUnstructuredGridCopy());
QWidget* widget1 = pSubWndFit->getFitSubRstWxOf(FSRT_Nm3D, true, &errorMessage);
// 创建 vtkWidget
nmWxPostprocessingAnimationWidget* vtkWidget = nullptr;
if(pInstance->getGridType() == NM_Grid_PEBI) {
vtkWidget = new nmWxPostprocessingAnimationWidget(NULL, pInstance);
}
if(widget1 == nullptr || vtkWidget == nullptr) {
delete vtkWidget;
if(widget1 == nullptr || pInstance->getGridType() != NM_Grid_PEBI) {
return;
}
// 先清理
// 获取当前布局
QLayout* layout1 = widget1->layout();
if(layout1) {
// 移除布局中的所有控件
QLayoutItem* item;
while((item = layout1->takeAt(0))) {
if(item->widget()) {
// 删除控件
delete item->widget();
nmWxPostprocessingAnimationWidget* vtkWidget =
widget1->findChild<nmWxPostprocessingAnimationWidget*>();
if(vtkWidget != nullptr) {
// 复用既有渲染窗口和 VTK 管道,只切换本轮已整体提交的数据。
vtkWidget->refreshResult();
} else {
vtkWidget = new nmWxPostprocessingAnimationWidget(NULL, pInstance);
QLayout* layout1 = widget1->layout();
if(layout1) {
QLayoutItem* item;
while((item = layout1->takeAt(0))) {
if(item->widget()) {
delete item->widget();
}
delete item;
}
delete item; // 删除布局项
delete layout1;
}
// 删除布局
delete layout1;
widget1->setLayout(nullptr);
QVBoxLayout* newLayout1 = new QVBoxLayout(widget1);
newLayout1->addWidget(vtkWidget);
widget1->setLayout(newLayout1);
}
// 确保布局被移除
widget1->setLayout(nullptr);
// 创建一个垂直布局管理器
QVBoxLayout* newLayout1 = new QVBoxLayout(widget1);
// 将现有的控件添加到布局中
newLayout1->addWidget(vtkWidget);
// 设置 widget 的布局
widget1->setLayout(newLayout1);
// 结果参数界面
QWidget* widget2 = pSubWndFit->getFitSubRstWxOf(FSRT_NmRst, true, &errorMessage);
if(widget2 == nullptr) {
return;
}
// 与左侧共用属性面板类,结果区使用独立实例和只读参数列表。
nmWxParaPropertyPebi* resultWidget = new nmWxParaPropertyPebi;
resultWidget->initUI();
resultWidget->setDataManager(pInstance);
resultWidget->rebuildResultParas();
// 先清理
// 获取当前布局
QLayout* layout2 = widget2->layout();
if(layout2) {
// 移除布局中的所有控件
QLayoutItem* item;
while((item = layout2->takeAt(0))) {
if(item->widget()) {
// 删除控件
delete item->widget();
nmWxParaPropertyPebi* resultWidget =
widget2->findChild<nmWxParaPropertyPebi*>();
if(resultWidget == nullptr) {
// 与左侧共用属性面板类,结果区使用独立实例和只读参数列表。
resultWidget = new nmWxParaPropertyPebi;
resultWidget->initUI();
QLayout* layout2 = widget2->layout();
if(layout2) {
QLayoutItem* item;
while((item = layout2->takeAt(0))) {
if(item->widget()) {
delete item->widget();
}
delete item;
}
delete item; // 删除布局项
delete layout2;
}
// 删除布局
delete layout2;
widget2->setLayout(nullptr);
QVBoxLayout* newLayout2 = new QVBoxLayout(widget2);
newLayout2->addWidget(resultWidget);
widget2->setLayout(newLayout2);
}
// 确保布局被移除
widget2->setLayout(nullptr);
// 创建一个垂直布局管理器
QVBoxLayout* newLayout2 = new QVBoxLayout(widget2);
// 将现有的控件添加到布局中
newLayout2->addWidget(resultWidget);
// 设置 widget 的布局
widget2->setLayout(newLayout2);
//nmWxResultParameters* resultWidget = new nmWxResultParameters;
//resultWidget->show();
resultWidget->setDataManager(pInstance);
resultWidget->rebuildResultParas();
}
@ -2055,7 +2156,8 @@ bool nmSubWndMain::onConfirmClosing()
{
// 正常关闭入口在求解结束前保持当前成果和 DataManager 存活;应用退出等绕过
// 该确认流程的路径仍由析构函数 wait() 兜底。
if(m_pSolverTask != nullptr && m_pSolverTask->isRunning()) {
if(m_bSolverStartPending ||
(m_pSolverTask != nullptr && m_pSolverTask->isRunning())) {
QMessageBox::information(this, tr("solver error"), tr("task is running!"));
return false;
}
@ -2113,17 +2215,12 @@ void nmSubWndMain::geoLayering()
void nmSubWndMain::on_calculationFinished(NM_Calculation_Result result)
{
// 1. 立即停止模拟定时器(防止它在处理结果时继续触发)
if (m_pFakeProgressTimer) {
m_pFakeProgressTimer->stop();
}
// 2. 强制拉满进度条(给用户一个成功的反馈)
// 结果提交成功后仍保留进度框,直到曲线和结果页刷新全部完成。
if (m_pProgressDlg) {
m_pProgressDlg->setValue(100);
m_pProgressDlg->setLabelText(tr("Updating result views..."));
m_pProgressDlg->setRange(0, 0);
}
// 3. 处理计算结果
if(result == NM_Calculation_Result_Success) {
this->mergeAnaResultToFitting();
} else {
@ -2131,7 +2228,6 @@ void nmSubWndMain::on_calculationFinished(NM_Calculation_Result result)
QMessageBox::warning(this, tr("solver error"), tr("solver failed!"));
}
// 4. 直接销毁对象并置空
if (m_pProgressDlg != nullptr) {
delete m_pProgressDlg;
m_pProgressDlg = nullptr;
@ -2149,10 +2245,17 @@ void nmSubWndMain::on_solverTaskFinished(bool isSuccessed)
int nPebiCount = pTask != nullptr ? pTask->getPebiCount() : -1;
int nSolveTimeMs = pTask != nullptr ? pTask->getSolveTimeMs() : -1;
const bool bCancelled = pTask->wasCancelled();
// 后台线程只生成局部结果快照。回到主线程后先验证所属 DataManager 和
// 输入版本,再整体替换旧成果;提交失败与求解失败使用同一条错误路径。
if(isSuccessed) {
if(isSuccessed && !bCancelled) {
if(m_pProgressDlg != nullptr) {
m_pProgressDlg->setLabelText(tr("Committing results..."));
m_pProgressDlg->setRange(0, 0);
}
isSuccessed = pTask->commitResult(m_pSolverDataManager.data());
} else if(bCancelled) {
isSuccessed = false;
}
m_pSolverTask = nullptr;
if(pTask == s_pRunningSolverTask) {
@ -2161,13 +2264,21 @@ void nmSubWndMain::on_solverTaskFinished(bool isSuccessed)
// 正常完成路径由主线程延迟释放任务;析构路径则会等待并直接释放。
pTask->deleteLater();
// 第二步:在清空捕获上下文前完成结果合并,确保始终回写原拟合窗口。
NM_Calculation_Result result = isSuccessed ? NM_Calculation_Result_Success : NM_Calculation_Result_Fail;
this->on_calculationFinished(result);
// 第二步:停止不属于失败,也不允许触发结果提交或覆盖旧成果。
if(bCancelled) {
delete m_pProgressDlg;
m_pProgressDlg = nullptr;
} else {
NM_Calculation_Result result = isSuccessed
? NM_Calculation_Result_Success
: NM_Calculation_Result_Fail;
this->on_calculationFinished(result);
}
// 第三步:结果处理结束后释放非拥有型上下文引用。
m_pSolverDataManager = nullptr;
m_pSolverFitting = nullptr;
m_bSolverCancelRequested = false;
if(isSuccessed && nPebiCount >= 0 && nSolveTimeMs >= 0) {
QMessageBox::information(this, tr("Solver completed"),
@ -2316,32 +2427,6 @@ nmGuiPlot * nmSubWndMain::getWxPlot() const
return m_pWxPlot;
}
void nmSubWndMain::onProgressUpdated(int progress)
{
if (!m_pProgressDlg) {
return;
}
// 1. 处理特殊结束信号
if (progress >= 100 || progress == -1) {
// 停止模拟定时器,防止它继续累加
if (m_pFakeProgressTimer && m_pFakeProgressTimer->isActive()) {
m_pFakeProgressTimer->stop();
}
m_pProgressDlg->setValue(100);
return;
}
// 2. 进度倒退保护
// 如果模拟进度已经跑到了 50%,但某个真实信号传回来 40%
// 为了防止进度条“倒着走”引起用户困惑,只有当前值更大时才更新。
if (progress > m_pProgressDlg->value()) {
m_pProgressDlg->setValue(progress);
// 同步更新模拟变量,防止定时器下次触发时使用旧值
m_nVirtualProgress = progress;
}
}
void nmSubWndMain::onWellSelected(const QString& sWellCode)
{
nmDataAnalyzeManager* pInstance = nmDataAnalyzeManager::getCurrentInstance();
@ -2528,65 +2613,6 @@ void nmSubWndMain::onGenerateButtonClicked()
}
}
void nmSubWndMain::slotUpdateFakeProgress()
{
// --- 1. 安全防御检查 ---
// 如果进度对话框已被销毁(例如计算提前结束或用户关闭),立即停止定时器并退出
// 防止在 delete 对象后发生野指针访问崩溃
if (m_pProgressDlg == nullptr) {
if (m_pFakeProgressTimer) {
m_pFakeProgressTimer->stop();
}
return;
}
// --- 2. 分段模拟进度逻辑 (0% - 99%) ---
// 采用“前快后慢”的心理学策略50% 之后逐级增加阻力,为 DLL 计算留出缓冲时间
if (m_nVirtualProgress < 99) {
// 步进计数器,用于实现非匀速增长
m_nSlowDownCounter++;
if (m_nVirtualProgress < 50) {
// [第一阶段] 0% - 50%: 快速增长
// 每次定时器触发(500ms)即增加 1%,给予用户“响应迅速”的反馈
m_nVirtualProgress += 1;
}
else if (m_nVirtualProgress < 75) {
// [第二阶段] 50% - 75%: 第一次减速
// 每触发 4 次(约 2秒)增加 1%,模拟进入核心计算阶段
if (m_nSlowDownCounter % 4 == 0) {
m_nVirtualProgress += 1;
}
}
else if (m_nVirtualProgress < 90) {
// [第三阶段] 75% - 90%: 第二次减速
// 每触发 8 次(约 4秒)增加 1%,应对较大规模的数据处理
if (m_nSlowDownCounter % 8 == 0) {
m_nVirtualProgress += 1;
}
}
else {
// [第四阶段] 90% - 99%: 深度减速 (极慢)
// 每触发 15 次(约 7.5秒)增加 1%。此时进度条几乎“爬行”,
// 旨在确保在 DLL 返回完成信号前,进度条不会提前撞击 100%
if (m_nSlowDownCounter % 15 == 0) {
m_nVirtualProgress += 1;
}
}
// 统一调用更新接口,同步 UI 显示
this->onProgressUpdated(m_nVirtualProgress);
}
else {
// --- 3. 终点停靠 ---
// 到达 99% 时停止自动增长,死等来自 Solver 线程的真正的 Finished 信号
if (m_pFakeProgressTimer) {
m_pFakeProgressTimer->stop();
}
}
}
void nmSubWndMain::generateDFN()
{
nmWxDFN dfnDialog(nullptr);

@ -238,6 +238,66 @@ nmWxPostprocessingAnimationWidget::~nmWxPostprocessingAnimationWidget()
}
}
void nmWxPostprocessingAnimationWidget::refreshResult()
{
nmDataAnalyzeManager* pDataManager = m_pDataManager.data();
if(pDataManager == nullptr || m_pVtkWidget == nullptr ||
m_thresholdFilter == nullptr || m_mapper == nullptr ||
m_lookupTable == nullptr) {
return;
}
// 结果提交发生在主线程;先停止旧动画和轮廓,再整体切换基础网格及时间步。
on_stop();
slotClearContour();
clearWellActors();
m_contourRepresentation = nullptr;
m_pointPlacer = nullptr;
m_polyDataForContour = nullptr;
m_pCachedBaseGrid = pDataManager->getResultBaseGrid();
m_thresholdFilter->SetInputData(m_pCachedBaseGrid);
m_nCurrentIndex = 0;
m_vecTimeStepKeys.clear();
initTimeSteps();
if(m_pSlider != nullptr) {
m_pSlider->blockSignals(true);
m_pSlider->setMinimum(1);
m_pSlider->setMaximum(qMax(1, m_vecTimeStepKeys.size()));
m_pSlider->setValue(1);
m_pSlider->blockSignals(false);
}
if(m_pProgress != nullptr) {
const int nTotal = m_vecTimeStepKeys.size();
m_pProgress->setText(nTotal > 0
? QString("%1%(%2/%3)").arg(100 / nTotal).arg(1).arg(nTotal)
: QString("0%(0/0)"));
}
double scalarRange[2];
pDataManager->getScalarRangeP(scalarRange);
m_mapper->SetScalarRange(scalarRange);
m_lookupTable->SetRange(scalarRange);
m_lookupTable->Build();
m_thresholdFilter->ThresholdBetween(scalarRange[0], scalarRange[1]);
m_thresholdFilter->Modified();
m_bFilteringEnabled = true;
m_bAboveMinEnabled = true;
m_dMinValue = scalarRange[0];
m_bBelowMaxEnabled = true;
m_dMaxValue = scalarRange[1];
if(!m_vecTimeStepKeys.isEmpty() && m_pCachedBaseGrid != nullptr) {
loadDataForIndex(0);
}
initWellDrawing();
if(m_renderer != nullptr) {
m_renderer->ResetCamera();
}
m_pVtkWidget->GetRenderWindow()->Render();
}
QImage nmWxPostprocessingAnimationWidget::createQImage1(int nWidth, int nHeight, vtkUnsignedCharArray* pScalars)
{
QImage qImage(nWidth, nHeight, QImage::Format_ARGB32);

Loading…
Cancel
Save