Compare commits

...

8 Commits

Author SHA1 Message Date
lh 3a8b3baef9 fix(numerical): 串行化HX_NWTM.dll调用避免并发冲突
- 增加进程级DLL互斥锁
- 串行保护PEBI建网、模型求解和Kriging调用
- DLL调用完成后及时释放锁,避免阻塞VTK和结果后处理
2 weeks ago
lh 25603b4471 fix(numerical): 修复切换分析时网格窗口绑定旧数据问题
- 统一切换网格窗口的 DataManager
- 丢弃旧分析的排队信号和过期网格任务结果
- 切换时更新网格参数并清理旧 VTK 视图
2 weeks ago
lh 1c4aa7c447 Merge remote-tracking branch 'origin/develop' into feature/refactor-numerical-model-panel-20260824
# Conflicts:
#	Bin/Config/Lang/cn/nmNum_cn.qm
#	Src/nmNum/nmCalculation/nmCalculationAutoFitPSO.cpp
#	Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp
2 weeks ago
lh b3a09b82ab fix(numerical): 消除PEBI网格与求解的后台数据竞争
- 使用不可变快照向后台网格和求解任务传递输入
- 在主线程一次性提交网格及求解结果
- 移除网格生成中的CSV中转和重复建网
- 增加网格生成中及失败状态,避免窗口长时间黑屏
- 适配自动拟合调用新的求解任务接口
2 weeks ago
lh 9bf9e41faf fix(numerical): 修复多井成果恢复与异步求解安全问题
- 修复包含井、自动观察井及求解井顺序的保存加载问题
- 增加后台任务生命周期保护,避免关闭成果时访问已释放数据
- 将求解结果改为局部生成并一次性提交,防止失败或过期结果污染成果
- 修复 T5 拟压力成果串用及 Map 新井状态未及时刷新的问题
- 合入自动拟合的单位、目标首点、时间轴端点和空流量修复
2 weeks ago
lh 2020638d28 refactor(numerical): 重构多井数值分析与井角色管理
- 引入数值分析案例数据类,集中管理主分析井、包含井和结果井
- 区分有产量干扰井与无产量观察井的网格及求解行为
- 调整数值面板的 Include Other Wells 和结果井选择逻辑
- 统一 PEBI 网格、求解器与后处理使用的井顺序
- 完善多井配置保存、界面翻译及中文代码注释
2 weeks ago
lh 01914259fe refactor(numerical): 合并模型与结果区域并统一控件样式
- 合并模型选择与结果井选择分组
- 统一主选项和求解器设置的控件尺寸与布局
- 移除字段标签冒号并更新中文翻译
2 weeks ago
lh 1770047183 feat(nmSubWxs): 统一数值模型时间步组件与单位换算
- 使用 nmParameterField 重构时间步设置
- 接入底层单位框架并增加完整校验
- 将最小时间步默认值调整为 0.0025 h
- 补充参数定义及中文翻译资源
2 weeks ago

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

@ -2734,6 +2734,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>
@ -5222,20 +5230,16 @@ Please check your input coordinates.</source>
<translation></translation>
</message>
<message>
<source>View well result</source>
<translation></translation>
</message>
<message>
<source>Current well:</source>
<translation></translation>
<source>Model and result</source>
<translation></translation>
</message>
<message>
<source>Current model:</source>
<translation></translation>
<source>Result well</source>
<translation></translation>
</message>
<message>
<source>Model selection</source>
<translation></translation>
<source>Current model</source>
<translation></translation>
</message>
<message>
<source>Oil single-phase constant pvt</source>
@ -5374,8 +5378,8 @@ Please check your input coordinates.</source>
<translation></translation>
</message>
<message>
<source>Solver library:</source>
<translation></translation>
<source>Solver library</source>
<translation></translation>
</message>
<message>
<source>CPU accelerated solver</source>
@ -5386,12 +5390,12 @@ Please check your input coordinates.</source>
<translation></translation>
</message>
<message>
<source>OpenMP threads:</source>
<translation>OpenMP线</translation>
<source>OpenMP threads</source>
<translation>OpenMP线</translation>
</message>
<message>
<source>ILU reuse steps:</source>
<translation>ILU</translation>
<source>ILU reuse steps</source>
<translation>ILU</translation>
</message>
<message>
<source>Use automatic settings</source>
@ -5461,13 +5465,17 @@ Please check your input coordinates.</source>
<source>Forecast</source>
<translation></translation>
</message>
<message>
<source>Invalid time step</source>
<translation></translation>
</message>
<message>
<source>Warning</source>
<translation type="unfinished"></translation>
<translation></translation>
</message>
<message>
<source>No data manager available.</source>
<translation type="unfinished"></translation>
<translation></translation>
</message>
<message>
<source>Failed to reset from diagnostic. Please check if log-log data is available.</source>
@ -5483,7 +5491,19 @@ Please check your input coordinates.</source>
</message>
<message>
<source>Dt min must be less than Dt max.</source>
<translation type="unfinished"></translation>
<translation></translation>
</message>
<message>
<source>Time step settings are unavailable.</source>
<translation></translation>
</message>
<message>
<source>Time step fields are unavailable.</source>
<translation></translation>
</message>
<message>
<source>Please correct the invalid time step values.</source>
<translation></translation>
</message>
<message>
<source>Sensitive</source>

@ -14,6 +14,12 @@
<Root>
<ParaGroup Name="" Alias="时间步设置">
<ParaItem Name="NM_TimeGrowthExponent" Alias="时间增长指数" Unit="" Default="1.05" Max="10" Min="1" Digit="6" Desc="time growth exponent" />
<ParaItem Name="NM_DtMin" Alias="最小时间步" Unit="h" Default="0.0025" Max="1e+30" Min="1e-12" Digit="6" Desc="minimum time step" />
<ParaItem Name="NM_DtMax" Alias="最大时间步" Unit="h" Default="12.5" Max="1e+30" Min="1e-12" Digit="6" Desc="maximum time step" />
</ParaGroup>
<ParaGroup Name="" Alias="储层参数">
<ParaItem Name="h" Alias="储层厚度" Unit="m" Default="10" Max="10000" Min="1e-05" Digit="6" Desc="reservoir thickness" />
<ParaItem Name="Pi" Alias="初始压力" Unit="MPa" Default="40" Max="500" Min="0.1" Digit="6" Desc="initial formation pressure" />

@ -15,6 +15,12 @@
<Root>
<ParaGroup Name="" Alias="Time Step Settings">
<ParaItem Name="NM_TimeGrowthExponent" Alias="Time Growth Exponent" Unit="" Default="1.05" Max="10" Min="1" Digit="6" Desc="time growth exponent" />
<ParaItem Name="NM_DtMin" Alias="Minimum Time Step" Unit="hr" Default="0.0025" Max="1e+30" Min="1e-12" Digit="6" Desc="minimum time step" />
<ParaItem Name="NM_DtMax" Alias="Maximum Time Step" Unit="hr" Default="12.5" Max="1e+30" Min="1e-12" Digit="6" Desc="maximum time step" />
</ParaGroup>
<ParaGroup Name="" Alias="Reservoir Parameters">
<ParaItem Name="h" Alias="Reservoir Thickness" Unit="m" Default="10" Max="10000" Min="1e-05" Digit="3" Desc="reservoir thickness" />
<ParaItem Name="Pi" Alias="Initial Pressure" Unit="MPa" Default="40" Max="500" Min="0.1" Digit="5" Desc="initial formation pressure" />

@ -4,21 +4,39 @@
#include <QThread>
#include <QString>
#include <QVector>
#include <QMap>
#include <QPointF>
#include <iostream>
#include <vector>
#include <Windows.h>
#include <vtkSmartPointer.h>
#include "nmCalculationDefine.h"
#include "nmCalculation_global.h"
#include "pch.h"
class nmDataAnalyzeManager;
class vtkDoubleArray;
class vtkUnstructuredGrid;
struct nmPebiSolverInputSnapshot;
// 主窗口现在直接创建DLL求解线程任务因此类需要导出供nmSubWnd模块跨DLL使用。
class NMCALCULATION_EXPORT nmCalculationDllPebiSolverTask : public QThread {
Q_OBJECT
public:
/**
* @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();
/** @brief QThread 入口,记录本次 execute() 的成功状态并发送完成信号。 */
void run();
// PSO 等待线程结束后通过该接口判断本次结果是否可以继续参与误差计算.
// 返回 false 时调用方会丢弃本次结果, 防止复用上一粒子留下的旧曲线.
@ -28,36 +46,77 @@ class NMCALCULATION_EXPORT nmCalculationDllPebiSolverTask : public QThread {
/** @brief 获取本次求解的PEBI网格数量。 */
int getPebiCount() const;
// 自动拟合粒子评价只提取目标井曲线,不写回共享数据和网格压力场。
// 井名为空时保持原有完整结果保存模式。
void setAutoFitTargetWell(const QString& wellName);
/**
* @brief 线
* @param pDataManager
* @return true false
*/
bool commitResult(nmDataAnalyzeManager* pDataManager);
/** @brief 返回自动拟合目标井的原始压力结果副本。 */
QVector<QVector<double> > getAutoFitResultPressure() const;
/** @brief 返回自动拟合目标井的双对数结果副本。 */
QVector<QVector<double> > getAutoFitResultLogLog() const;
/** @brief 返回自动拟合目标井的半对数结果副本。 */
QVector<QVector<double> > getAutoFitResultSemiLog() const;
private:
/** @brief 执行当前唯一的 PEBI 求解模式。 */
bool execute();
/** @brief 成对释放构造时登记的 DataManager 后台使用权。 */
void releaseDataManagerUse();
/** @brief 在任务创建线程一次性捕获网格、井、储层和求解器设置。 */
bool captureInputSnapshot(const QString& sAutoFitTargetWellName);
// 执行PEBI求解流程。
/** @brief 构造 DLL 输入、执行求解并生成仍然有效的局部结果快照。 */
bool execPebiMode();
// 将PEBI求解结果保存回数据对象。
bool savePebiModeResult(HX_NWTM_MODEL_OUTPUT& p1, int modelType);
/** @brief 按求解器井顺序组装压力、曲线和场结果快照,不修改 DataManager。 */
bool buildPebiModeResult(HX_NWTM_MODEL_OUTPUT& p1,
int nModelType,
const HX_NWTM_GRID_OUTPUT1& oGridOutput);
/** @brief 输出精简版 DLL 输入日志,用于定位求解异常。 */
void logHX_NWTM_MODEL_INPUT_Simplified(const HX_NWTM_MODEL_INPUT& p0);
/** @brief 将完整 DLL 输入保存到文本文件,成功时返回 true。 */
bool saveHX_NWTM_MODEL_INPUT_ToTxt(const HX_NWTM_MODEL_INPUT& p0, const QString& filePath);
private:
QString m_sPostprocessingDir;
// run() 在线程内保存 execute() 结果, 等待线程结束的调用方只读取该状态.
bool m_lastRunSucceeded;
/** @brief 一口真实井在本次求解中完整生成的待提交结果。 */
struct nmPebiWellResultSnapshot
{
QString m_sWellCode; ///< 稳定井编码。
QVector<QVector<double> > m_vecPressure; ///< 时间和井底压力。
QVector<QVector<double> > m_vecLogLog; ///< 时间、压差及导数。
QVector<QVector<double> > m_vecSemiLog; ///< 时间和半对数压力。
QPointF m_oLocation; ///< 结果网格中的井坐标。
};
QString m_sPostprocessingDir; ///< 本次求解使用的后处理输出目录。
/** @brief 创建任务时捕获所属数据中心,工作线程不得读取全局当前窗口。 */
nmDataAnalyzeManager* m_pDataManager;
/** @brief 任务独占的完整值快照;析构时释放,后台禁止访问 DataManager。 */
nmPebiSolverInputSnapshot* m_pInputSnapshot;
bool m_bManagerUseActive; ///< 是否仍持有 DataManager 后台使用权。
bool m_bInputSnapshotValid; ///< 构造阶段是否已完成全部输入校验。
/** @brief 创建任务时捕获网格输入版本,防止回填过期网格上的结果。 */
quint64 m_nGridInputRevision;
/** @brief 创建任务时捕获求解输入版本,防止角色或参数变化后回填旧结果。 */
quint64 m_nResultInputRevision;
bool m_lastRunSucceeded; ///< run() 保存的执行状态,供等待线程安全读取。
/** @brief 本次DLL内部求解耗时单位为毫秒。 */
int m_nSolveTimeMs;
/** @brief 本次求解的PEBI网格数量。 */
int m_nPebiCount;
QString m_autoFitTargetWellName;
QVector<QVector<double> > m_autoFitResultPressure;
QVector<QVector<double> > m_autoFitResultLogLog;
QVector<QVector<double> > m_autoFitResultSemiLog;
QString m_sAutoFitTargetWellCode; ///< 自动拟合目标井 WellCode空值表示保存完整结果。
QVector<QVector<double> > m_autoFitResultPressure; ///< 目标井原始压力结果。
QVector<QVector<double> > m_autoFitResultLogLog; ///< 目标井双对数结果。
QVector<QVector<double> > m_autoFitResultSemiLog; ///< 目标井半对数结果。
QVector<nmPebiWellResultSnapshot> m_vecPendingWellResults; ///< 完整求解的井结果快照。
QMap<double, vtkSmartPointer<vtkDoubleArray> > m_mapPendingTimeSteps; ///< 场压力时间步快照。
vtkSmartPointer<vtkUnstructuredGrid> m_pPendingResultGrid; ///< 本次结果绑定的基础网格副本。
double m_dPendingScalarMin; ///< 全部场压力的最小值。
double m_dPendingScalarMax; ///< 全部场压力的最大值。
bool m_bPendingFullResultReady; ///< 是否已有可由主线程一次提交的完整结果。
private slots:
//void slotTaskUpdateProgress();

@ -1,38 +1,112 @@
#ifndef NMCALCULATIONPEBIGRID_H
#ifndef NMCALCULATIONPEBIGRID_H
#define NMCALCULATIONPEBIGRID_H
#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 使
*
* DataManagerQObject 线
* 线 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:
/** @brief 返回进程内唯一的 PEBI 网格服务实例。 */
static nmCalculationPebiGrid* getInstance();
nmCalculationPebiGrid();
~nmCalculationPebiGrid();
// 生成Pebi网格
bool meshGenPebi();
// 设置PEBI GridControl控制参数
void setGridControl(double gridControl);
// 清理单例中的网格输入输出数据
void clearGridData();
/** @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);
/** @brief 仅在缓存属于指定管理器时清理;空参数强制清理当前缓存。 */
void clearGridData(const nmDataAnalyzeManager* pDataManager = nullptr);
private:
// 边界
bool meshGenPebiBoundary(HX_NWTM_GRID_INPUT& inputObj);
// 井
bool meshGenPebiWells(HX_NWTM_GRID_INPUT& inputObj);
// 断层
bool meshGenPebiFault(HX_NWTM_GRID_INPUT& inputObj);
// 裂缝
bool meshGenPebiCrack(HX_NWTM_GRID_INPUT& inputObj);
/** @brief 从绑定数据管理器构造边界输入。 */
bool meshGenPebiBoundary(nmDataAnalyzeManager* pDataManager,
HX_NWTM_GRID_INPUT& inputObj);
/** @brief 按有效计算井集合构造井几何并记录 DLL 井顺序。 */
bool meshGenPebiWells(nmDataAnalyzeManager* pDataManager,
HX_NWTM_GRID_INPUT& inputObj,
QVector<nmSolverWellRef>& vecSolverWellOrder);
/** @brief 从 Map 构造断层输入。 */
bool meshGenPebiFault(nmDataAnalyzeManager* pDataManager,
HX_NWTM_GRID_INPUT& inputObj);
/** @brief 从 Map 构造手工裂缝输入和求解器占位条目。 */
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);
@ -45,26 +119,30 @@ private:
void logInputParameters(const HX_NWTM_GRID_INPUT& input);
public:
// 仅生成输出参数p1、p2
bool generateOutputPara();
/** @brief 非阻塞复制当前有效网格DLL忙碌或缓存不匹配时返回 false。 */
bool copyCurrentGridFor(const nmDataAnalyzeManager* pDataManager,
HX_NWTM_GRID_OUTPUT1& oGridOutput1,
HX_NWTM_GRID_OUTPUT2& oGridOutput2,
int& nPebiCount) const;
public:
// 获取输出结果
/** @brief 在线程锁保护下返回最近一次网格输出 p1 的副本。 */
HX_NWTM_GRID_OUTPUT1 getGridOutput1();
/** @brief 在线程锁保护下返回最近一次网格输出 p2 的副本。 */
HX_NWTM_GRID_OUTPUT2 getGridOutput2();
// 获取最近一次网格划分生成的PEBI网格数量
/** @brief 返回最近一次网格划分生成的 PEBI 单元数量。 */
int getPebiCount() const;
private:
// 静态对象
static nmCalculationPebiGrid* m_instance;
// 存储输出/输入参数
HX_NWTM_GRID_INPUT p0;
HX_NWTM_GRID_OUTPUT1 p1;
HX_NWTM_GRID_OUTPUT2 p2;
// 对应HX_NWTM_GRID_INPUT::GridControl
double m_dGridControl;
int m_nPebiCount;
static nmCalculationPebiGrid* m_instance; ///< 进程内共享的 PEBI 网格服务实例。
HX_NWTM_GRID_INPUT p0; ///< 最近一次提交给网格 DLL 的输入快照。
HX_NWTM_GRID_OUTPUT1 p1; ///< 最近一次网格 DLL 主输出。
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;
};

@ -1,24 +1,44 @@
#ifndef NMCALCULATIONPEBIGRIDTASK_H
#ifndef NMCALCULATIONPEBIGRIDTASK_H
#define NMCALCULATIONPEBIGRIDTASK_H
#include <QThread>
#include "nmCalculation_global.h"
#include "nmCalculationPebiGrid.h"
class nmDataAnalyzeManager;
class NMCALCULATION_EXPORT nmCalculationPebiGridTask : public QThread
{
Q_OBJECT
public:
explicit nmCalculationPebiGridTask(QObject *parent = 0);
/** @brief 创建绑定到指定分析窗口的后台网格生成任务。 */
explicit nmCalculationPebiGridTask(nmDataAnalyzeManager* pDataManager,
QObject *parent = 0);
/** @brief 等待线程结束,并释放 DataManager 后台使用权。 */
~nmCalculationPebiGridTask();
/** @brief 在主线程按版本提交后台局部结果;过期结果返回 false。 */
bool commitResult(nmDataAnalyzeManager* pDataManager);
signals:
// PEBI生成完成后通知主线程刷新网格窗口.
/** @brief 无论成功或失败均通知主线程结束本轮网格任务。 */
void pebiGridGenerated(bool success);
protected:
// 线程入口: 后台生成PEBI网格, 避免阻塞界面.
/** @brief 线程入口:为捕获的数据管理器后台生成 PEBI 网格。 */
void run() override;
private:
/** @brief 成对释放构造时登记的 DataManager 后台使用权。 */
void releaseDataManagerUse();
/** @brief 启动任务时捕获所属数据管理器,线程内不再读取全局当前窗口。 */
nmDataAnalyzeManager* m_pDataManager;
bool m_bManagerUseActive; ///< 是否仍持有 DataManager 后台使用权。
bool m_bSnapshotValid; ///< 构造阶段是否成功获得完整输入快照。
nmPebiGridInputSnapshot m_oInputSnapshot; ///< 主线程创建、后台只读的值快照。
nmPebiGridResult m_oResult; ///< 后台局部结果,只允许主线程提交。
};
#endif // NMCALCULATIONPEBIGRIDTASK_H

@ -8,6 +8,8 @@
#include "nmCalculationDefine.h"
#include <iostream>
class QMutex;
class NMCALCULATION_EXPORT nmCalculationUtils {
public:
nmCalculationUtils();
@ -19,6 +21,9 @@ class NMCALCULATION_EXPORT nmCalculationUtils {
static bool writeFile(const QStringList& content, const QString &filePath);
static QStringList readFile(const QString & filePath);
/** @brief 获取串行保护 HX_NWTM.dll 全部入口的进程级互斥锁。 */
static QMutex* getHxNwtmDllMutex();
/**
* @brief 使西PEBI使西
* @param dPermeabilityMilliDarcy mD

@ -10,6 +10,7 @@
#include "nmDataHorizontalWell.h"
#include "nmDataVerticalFracturedWell.h"
#include "nmDataHorizontalFracturedWell.h"
#include "nmDataNumericalAnalysisCase.h"
#include "nmGridDefine.h"
@ -19,6 +20,8 @@
#include <QPair>
#include <QString>
#include <QList>
#include <QMutex>
#include <QWaitCondition>
#include <vector>
#include <vtkUnstructuredGrid.h>
@ -229,11 +232,20 @@ public:
//nmDataWellBase* findWellByName(const QString& wellName) const;
nmDataWellBase* findWellByName(QString wellName) const;
/**
* @brief
* @note
*/
nmDataWellBase* findWellByCode(const QString& sWellCode) const;
// 初始化测试井数据,即点击数值解的时候,将当前井添加到里面
void initCurWellData();
// 添加井数据,包括对应的流量数据和压力数据
void appendWellData(ZxDataWell* pWellData);
/**
* @brief Map
* @return
*/
nmDataWellBase* appendWellData(ZxDataWell* pWellData);
// 直接添加自定义的井数据
void appendNmWellData(nmDataWellBase* pWellData);
@ -433,6 +445,78 @@ public:
// 查询当前计算井中是否有某口井
bool isContainsWellName(const QString& wellName) const;
/** @brief 返回当前窗口的数值分析方案,管理器保留对象所有权。 */
nmDataNumericalAnalysisCase* getNumericalAnalysisCase();
/** @brief 返回只读数值分析方案,管理器保留对象所有权。 */
const nmDataNumericalAnalysisCase* getNumericalAnalysisCase() const;
/** @brief 后台任务开始使用本管理器;与 endBackgroundUse() 必须成对调用。 */
void beginBackgroundUse();
/** @brief 后台任务结束使用本管理器;最后一个任务退出时唤醒析构等待。 */
void endBackgroundUse();
/** @brief 设置主分析井 WellCode空编码用于清空主井。 */
void setPrimaryWellCode(const QString& sWellCode);
/** @brief 返回当前主分析井 WellCode。 */
QString getPrimaryWellCode() const;
/** @brief 启停手工包含的其他有产量井;不影响 Map 无产量观察井。 */
void setIncludeOtherWells(bool bInclude);
/** @brief 返回 Include Other Wells 总开关状态。 */
bool getIncludeOtherWells() const;
/** @brief 设置当前方案的 PEBI 网格疏密控制值。 */
void setPebiGridControl(double dGridControl);
/** @brief 返回当前方案的 PEBI 网格疏密控制值。 */
double getPebiGridControl() const;
/** @brief 保存其他有产量井选择;非法井、主井和无产量井会被过滤。 */
void setIncludedCalculationWells(const QVector<nmCalculationWellRef>& vecWells);
/** @brief 返回用户在 Include Other Wells 中保存的有产量井。 */
QVector<nmCalculationWellRef> getIncludedCalculationWells() const;
/** @brief 返回真实求解井:主井、全部 Map 无产量井及已包含主动井。 */
QVector<nmCalculationWellRef> getEffectiveCalculationWells() const;
/** @brief 从有效求解井集合查询井角色,未参与井按观察井返回。 */
NM_CASE_WELL_MODE getCalculationWellMode(const QString& sWellCode) const;
/** @brief 判断井是否属于当前真实求解井集合。 */
bool isWellSelectedForCalculation(const QString& sWellCode) const;
/** @brief 判断井是否允许出现在结果井下拉框。 */
bool isWellAvailableForResult(const QString& sWellCode) const;
/** @brief 清空网格生成器写入的 DLL 井槽位顺序。 */
void clearSolverWellOrder();
/** @brief 向 DLL 井槽位顺序末尾追加一个真实井或裂缝条目。 */
void appendSolverWell(const nmSolverWellRef& oWellRef);
/** @brief 在指定 DLL 下标插入井或裂缝条目,并重新编号。 */
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 仅在后台划分期间输入未变化时登记网格成功。 */
bool markPebiGridBuiltIfCurrent(quint64 nGridInputRevision);
/** @brief 几何或求解井集合变化后,使 PEBI 网格和结果失效。 */
void invalidatePebiGrid();
/** @brief 产量、PVT 等变化后,仅使 PEBI 结果失效。 */
void invalidatePebiResults();
/** @brief 丢弃旧结果有效标记,不把本次重新求解视为输入变化。 */
void discardPebiResults();
/** @brief 判断当前 PEBI 网格是否与最新输入一致。 */
bool isPebiGridValid() const;
/** @brief 设置结果查看井;不改变主分析井和参数编辑井。 */
void setCurrentResultWellCode(const QString& sWellCode);
/** @brief 返回结果查看井 WellCode。 */
QString getCurrentResultWellCode() const;
/** @brief 返回结果查看井对象,井不存在时返回空指针。 */
nmDataWellBase* getCurrentResultWellData() const;
// 设置当前查看井
void setCurWellData(nmDataWellBase* wellData);
// 获取当前查看井
@ -582,6 +666,8 @@ public:
// 添加时间步数据
void addTimeStep(double time, vtkSmartPointer<vtkDoubleArray> data);
/** @brief 清空上一轮求解遗留的全部时间步压力场。 */
void clearTimeSteps();
// 获取时间步数据
vtkSmartPointer<vtkDoubleArray> getTimeStepData(double time) const;
@ -610,12 +696,16 @@ public:
void getScalarRangeP(double range[2]) const;
void setScalarRangeP(double min, double max);
// 用于获取和设置井位置信息的接口
void addWellLocation(const QString& wellName, const QPointF& location);
QPointF getWellLocation(const QString& wellName) const;
bool removeWellLocation(const QString& wellName);
/** @brief 按 WellCode 保存本次真实求解井的二维坐标。 */
void addWellLocation(const QString& sWellCode, const QPointF& location);
/** @brief 返回指定 WellCode 的求解井坐标,不存在时返回默认 QPointF。 */
QPointF getWellLocation(const QString& sWellCode) const;
/** @brief 删除指定 WellCode 的求解井坐标,实际删除时返回 true。 */
bool removeWellLocation(const QString& sWellCode);
/** @brief 清空全部求解井坐标。 */
void clearWellLocations();
QMap<QString, QPointF> getAllWellLocations() const; // 获取所有井位置的副本
/** @brief 返回全部 WellCode 到求解井坐标的映射副本。 */
QMap<QString, QPointF> getAllWellLocations() const;
// 保存/加载当前场图中的井位置相关信息
bool saveWellLocations(const QString& filePath);
@ -657,6 +747,14 @@ public:
void sigGeometryListChanged();
private:
/** @brief 创建本数据管理器的成果窗口;只用于读取本成果上下文,不拥有对象。 */
iSubWndFitting* m_pOwnerFitting;
/** @brief 保护后台使用计数和析构等待条件。 */
QMutex m_oBackgroundUseMutex;
/** @brief DataManager 析构时等待全部后台任务停止访问。 */
QWaitCondition m_oNoBackgroundUseCondition;
int m_nBackgroundUseCount; ///< 当前持有 DataManager 使用权的后台任务数量。
// 存储井的数据
QVector<nmDataWellBase*> m_vWellData;
@ -730,8 +828,8 @@ public:
// 孔射封堵
//nmDataPerforationClosing* m_pPerCloData;
// 存储PEBI求解井顺序
QVector<QPair<NM_WELL_MODEL, QString>> m_vecCalculationWells;
// 当前数值分析方案:保存选井、井角色、结果井和网格产生的求解器顺序。
nmDataNumericalAnalysisCase m_oNumericalAnalysisCase;
// 当前查看的是哪一口井的数据,也是哪一口测试井
nmDataWellBase* m_pCurDataWell;
@ -757,7 +855,7 @@ public:
// 压力值范围 [min, max]
double m_dScalarRangeP[2];
// 存储井名称及其二维位置坐标的映射,这里应该是参与计算求解的井
// 存储 WellCode 及其二维位置坐标的映射,只包含本次真实求解井。
QMap<QString, QPointF> m_mapWellLocations;
// 时间步设置数据

@ -0,0 +1,180 @@
#ifndef NMDATANUMERICALANALYSISCASE_H
#define NMDATANUMERICALANALYSISCASE_H
#include "nmData_global.h"
#include "nmDefines.h"
#include <QString>
#include <QVector>
/**
* @brief
*
* PEBI
* 线
*
*/
enum NM_CASE_WELL_MODE
{
NM_CaseWell_RateControlled = 1, ///< 有生产或注入制度,参与改变压力场。
NM_CaseWell_Observation = 2 ///< 无产量制度,只接收并保存井底压力。
};
/**
* @brief
*/
struct NM_DATA_EXPORT nmCalculationWellRef
{
/** @brief 构造空井引用,默认角色为无源汇项观察井。 */
nmCalculationWellRef();
/** @brief 使用稳定井编码和本次分析角色构造井引用。 */
nmCalculationWellRef(const QString& sWellCode, NM_CASE_WELL_MODE eMode);
QString m_sWellCode; ///< 项目内唯一井编码,所有持久化关联均使用该字段。
NM_CASE_WELL_MODE m_eMode; ///< 当前方案中的计算角色。
};
/**
* @brief
*
* 使 +
* DLL
*/
enum NM_SOLVER_ENTRY_KIND
{
NM_SolverEntry_Well = 1, ///< 真实井,占用一组 Rate/CS/pw 数组槽位。
NM_SolverEntry_ManualFracture = 2 ///< Map 手工裂缝,仅用于保持 DLL 下标对齐。
};
/**
* @brief PEBI
*/
struct NM_DATA_EXPORT nmSolverWellRef
{
/** @brief 构造尚未分配求解器下标的真实井条目。 */
nmSolverWellRef();
/** @brief 构造网格输出中的求解器槽位描述。 */
nmSolverWellRef(int nSolverIndex,
NM_WELL_MODEL eWellType,
const QString& sWellCode,
NM_SOLVER_ENTRY_KIND eEntryKind = NM_SolverEntry_Well);
int m_nSolverIndex; ///< 对应 Rate[i] 和 pw[i] 的固定下标。
NM_WELL_MODEL m_eWellType; ///< 真实井的几何模型类型。
QString m_sWellCode; ///< 真实井编码;手工裂缝条目为空。
NM_SOLVER_ENTRY_KIND m_eEntryKind; ///< 真实井或手工裂缝占位。
};
/**
* @brief
*
* Map
* Map
*/
class NM_DATA_EXPORT nmDataNumericalAnalysisCase
{
public:
/** @brief 建立空分析方案,并初始化网格与结果版本号。 */
nmDataNumericalAnalysisCase();
/** @brief 返回主分析井的项目唯一 WellCode。 */
QString getPrimaryWellCode() const;
/** @brief 设置主分析井;同时移除包含列表中的同井项并使网格失效。 */
void setPrimaryWellCode(const QString& sWellCode);
/** @brief 返回主分析井在本次方案中的主动井或观察井角色。 */
NM_CASE_WELL_MODE getPrimaryWellMode() const;
/** @brief 设置主井角色;角色只影响源汇输入,因此仅使结果失效。 */
void setPrimaryWellMode(NM_CASE_WELL_MODE eMode);
/** @brief 返回是否启用手工选择的其他有产量井。 */
bool getIncludeOtherWells() const;
/** @brief 启停其他有产量井Map 无产量井不受该开关影响。 */
void setIncludeOtherWells(bool bInclude);
/** @brief 返回当前方案的 PEBI 网格疏密控制值。 */
double getPebiGridControl() const;
/** @brief 设置正数网格控制值;实际变化时使网格和结果失效。 */
void setPebiGridControl(double dGridControl);
/** @brief 返回已保存的其他有产量井选择,关闭总开关时选择仍保留。 */
QVector<nmCalculationWellRef> getIncludedWells() const;
/** @brief 保存其他有产量井,并按 WellCode 去空、去主井和去重。 */
void setIncludedWells(const QVector<nmCalculationWellRef>& vecWells);
/** @brief 判断 WellCode 是否存在于已保存的包含井列表。 */
bool isIncludedWell(const QString& sWellCode) const;
/** @brief 查询主井或显式包含井的方案角色,未配置井返回观察井角色。 */
NM_CASE_WELL_MODE getWellMode(const QString& sWellCode) const;
/** @brief 返回方案显式选择的井Map 观察井由数据管理层自动补充。 */
QVector<nmCalculationWellRef> getEffectiveWells() const;
/** @brief 返回界面当前查看结果的井编码。 */
QString getCurrentResultWellCode() const;
/** @brief 切换结果查看井;不改变主井、网格或已计算结果。 */
void setCurrentResultWellCode(const QString& sWellCode);
/** @brief 返回网格生成后确定的 DLL 井槽位顺序。 */
QVector<nmSolverWellRef> getSolverWellOrder() const;
/** @brief 清空求解器顺序,并将已建网格和结果标记为无效。 */
void clearSolverWellOrder();
/** @brief 在末尾追加槽位,并自动写入连续求解器下标。 */
void appendSolverWell(const nmSolverWellRef& oWellRef);
/** @brief 在指定下标插入槽位,并重新编号后续全部条目。 */
void insertSolverWell(int nIndex, const nmSolverWellRef& oWellRef);
/** @brief 删除指定槽位并重新编号;下标非法时返回 false。 */
bool removeSolverWell(int nIndex);
/** @brief 整体替换求解器顺序,并按容器顺序重新编号。 */
void setSolverWellOrder(const QVector<nmSolverWellRef>& vecOrder);
/** @brief 选井或几何输入改变后,使当前网格和结果失效。 */
void invalidateGrid();
/** @brief 产量、PVT 等只影响求解的输入改变后,仅使结果失效。 */
void invalidateResults();
/** @brief 网格成功生成后记录当前输入版本。 */
void markGridBuilt();
/** @brief 仅当网格生成期间几何输入未变化时提交网格。 */
bool markGridBuiltIfCurrent(quint64 nGridInputRevision);
/** @brief 丢弃旧结果,但不改变输入版本号。 */
void discardResults();
/** @brief 求解成功并完成结果映射后记录结果有效。 */
void markResultsAvailable();
/** @brief 仅当任务启动后的网格和求解输入均未变化时提交结果。 */
bool markResultsAvailableIfCurrent(quint64 nGridInputRevision,
quint64 nResultInputRevision);
/** @brief 当前网格版本与几何输入一致且井顺序非空时返回 true。 */
bool isGridValid() const;
/** @brief 当前结果已提交且所依赖网格仍有效时返回 true。 */
bool areResultsValid() const;
/** @brief 返回最新几何/选井输入版本,供后台任务启动时捕获。 */
quint64 getGridInputRevision() const;
/** @brief 返回当前缓存网格对应的输入版本0 表示没有有效网格。 */
quint64 getBuiltGridRevision() const;
/** @brief 返回最新产量/PVT等求解输入版本供后台任务启动时捕获。 */
quint64 getResultInputRevision() const;
/** @brief 清空方案状态,用于重新加载项目。 */
void clear();
private:
/** @brief 规范化有产量包含井,移除空编码、主井和重复项。 */
void normalizeIncludedWells();
private:
QString m_sPrimaryWellCode; ///< 主分析井的项目唯一 WellCode。
NM_CASE_WELL_MODE m_ePrimaryWellMode; ///< 主井在本次方案中的源汇角色。
bool m_bIncludeOtherWells; ///< 是否启用手工选择的有产量干扰井。
double m_dPebiGridControl; ///< 当前方案使用的 PEBI 网格疏密参数。
QVector<nmCalculationWellRef> m_vecIncludedWells; ///< 已选择的其他有产量井,不含自动观察井。
QString m_sCurrentResultWellCode; ///< 当前结果查看井,仅属于展示状态。
QVector<nmSolverWellRef> m_vecSolverWellOrder; ///< 网格确定的 DLL 数组下标映射。
quint64 m_nGridInputRevision; ///< 几何、选井等网格输入的最新版本。
quint64 m_nBuiltGridRevision; ///< 当前缓存网格对应版本0 表示无有效网格。
quint64 m_nResultInputRevision; ///< 产量、角色、PVT 等求解输入版本。
bool m_bResultsValid; ///< 当前结果是否已成功映射且未过期。
};
#endif // NMDATANUMERICALANALYSISCASE_H

@ -2,17 +2,32 @@
#include "iSubWndBaseFit.h"
#include "nmSubWnd_global.h"
#include <QPointer>
class nmWxGridVTKContainerWidget;
class nmDataAnalyzeManager;
class nmCalculationPebiGridTask;
class QTimer;
class QLabel;
class QStackedLayout;
class NM_SUB_WND_EXPORT nmSubWndGrid : public iSubWndBaseFit {
Q_OBJECT
public:
/**
* @brief
* @param bGeneratePebiGrid true Map
* @param pDataManager
*/
explicit nmSubWndGrid(QWidget *parent = 0, QString sExt = "",
double pebiGridControl = 150.0, bool bGeneratePebiGrid = true);
double pebiGridControl = 150.0,
bool bGeneratePebiGrid = true,
nmDataAnalyzeManager* pDataManager = nullptr);
~nmSubWndGrid();
/** @brief 切换窗口所属数据管理器,并安全处理仍在运行的旧网格任务。 */
void setDataManager(nmDataAnalyzeManager* pDataManager);
// 配置窗体
virtual void configWnd(QString sPrevInfo);
@ -26,9 +41,16 @@ class NM_SUB_WND_EXPORT nmSubWndGrid : public iSubWndBaseFit {
virtual bool onConfirmClosing();
public slots:
/** @brief 只响应当前数据管理器的数据变化,忽略切换前已排队的旧信号。 */
void onDataManagerChanged();
/** @brief 合并连续数据变化请求,并启动本窗口的防抖计时。 */
void updateGrid();
/** @brief 防抖结束后启动或排队一次后台 PEBI 网格任务。 */
void onDebounceTimeout();
/** @brief 网格任务完成后刷新本窗口 VTK 视图。 */
void onPebiGridGenerated(bool success);
/** @brief 回收完成的任务;运行期间有新请求时补做最后一次重建。 */
void onPebiGridTaskFinished();
public:
// 执行命令通常来自Ribbon
@ -38,16 +60,26 @@ class NM_SUB_WND_EXPORT nmSubWndGrid : public iSubWndBaseFit {
bool generatePebiGrid();
private:
/** @brief 切换到渐变加载背景,并恢复“正在生成网格”提示。 */
void showGridLoading();
/** @brief 用 DataManager 中的最新网格重建 VTK 页并切换到结果显示。 */
void showGridResult();
/** @brief 网格失败时恢复旧网格;没有旧网格则显示明确失败提示。 */
void showGridFailure();
/** @brief 销毁当前 VTK 页,防止切换分析后继续显示旧分析网格。 */
void clearGridResultView();
// UI布局和VTK控件
QVBoxLayout* m_mainLayout;
nmWxGridVTKContainerWidget* m_vtkWidget;
// 每个网格窗口独立持有防抖定时器避免多个窗口共享同一个static定时器导致刷新落到旧窗口
QTimer* m_pDebounceTimer;
QStackedLayout* m_mainLayout; ///< 在加载背景和 VTK 结果之间切换的主布局。
nmWxGridVTKContainerWidget* m_vtkWidget; ///< 显示所属分析网格的 VTK 控件。
QLabel* m_pGridLoadingLabel; ///< 后台划分期间显示的渐变背景和状态文字。
QTimer* m_pDebounceTimer; ///< 本窗口独立防抖定时器,避免跨窗口刷新。
QPointer<nmDataAnalyzeManager> m_pDataManager; ///< 窗口所属数据管理器,销毁后自动置空。
QPointer<nmCalculationPebiGridTask> m_pGridTask; ///< 当前后台网格任务,完成后自动回收。
QPointer<nmDataAnalyzeManager> m_pGridTaskDataManager; ///< 当前网格任务创建时绑定的数据管理器。
bool m_bGridUpdatePending; ///< 任务运行期间是否又收到网格更新请求。
// PEBI GridControl控制参数对应HX_NWTM_GRID_INPUT::GridControl
double m_dPebiGridControl;
// 是否根据当前地图几何重新生成PEBI网格
bool m_bGeneratePebiGrid;
bool m_bGeneratePebiGrid; ///< 是否根据当前 Map 几何重新生成 PEBI 网格。
};

@ -4,6 +4,7 @@
#include "nmSubWnd_global.h"
#include "nmCalculationDefine.h"
#include <QPointer>
#include <QVector>
class ZxObjBase;
@ -18,6 +19,7 @@ class QToolBar;
class nmDataWellBase;
class nmDataAnalyzeManager;
class nmCalculationDllPebiSolverTask;
class iSubWndFitting;
class QWidget;
class QObject;
@ -140,8 +142,8 @@ class NM_SUB_WND_EXPORT nmSubWndMain : public iSubWndBaseFit {
void on_solverTaskFinished(bool isSuccessed);
void onProgressUpdated(int progress); // 新增槽函数,用于更新进度条
// 选择查看井数据
void onWellSelected(const QString& wellName);
/** @brief 按 WellCode 切换结果曲线,不改变主分析井和参数编辑井。 */
void onWellSelected(const QString& sWellCode);
// 点击左侧按钮求解生成操作
void onGenerateButtonClicked();
@ -250,7 +252,11 @@ private:
// 求解进度条
QProgressDialog* m_pProgressDlg;
nmCalculationDllPebiSolverTask* m_pSolverTask; // 当前窗口直接持有的DLL求解线程任务
nmCalculationDllPebiSolverTask* m_pSolverTask; ///< 当前窗口持有的 DLL 求解线程任务。
/** @brief 启动求解时捕获的所属数据管理器,防止完成时活动窗口已经切换。 */
QPointer<nmDataAnalyzeManager> m_pSolverDataManager;
/** @brief 启动求解时捕获的拟合窗口,只把结果合并回该窗口。 */
QPointer<iSubWndFitting> m_pSolverFitting;
QTimer* m_pFakeProgressTimer; // 用于模拟进度的定时器
int m_nVirtualProgress; // 记录当前的模拟进度值
int m_nSlowDownCounter; // 记录定时器触发次数,用于控制减速节奏

@ -5,10 +5,12 @@
#include "nmSubWxs_global.h" // 假设此头文件定义了 NM_SUB_WXS_EXPORT
#include <QEvent> // For eventFilter
#include <QMouseEvent> // For eventFilter
#include <QPointer>
class QVBoxLayout; // QVBoxLayout 的前向声明
class QVTKWidget; // QVTKWidget 的前向声明
class QImage;
class nmDataAnalyzeManager;
class vtkImageData;
class vtkUnsignedCharArray;
@ -17,8 +19,12 @@ class NM_SUB_WXS_EXPORT nmWxGridVTKContainerWidget : public QWidget
{
Q_OBJECT
public:
// 构造函数:初始化部件并设置父对象。
nmWxGridVTKContainerWidget(QWidget *parent = nullptr);
/**
* @brief VTK
* @note
*/
nmWxGridVTKContainerWidget(nmDataAnalyzeManager* pDataManager,
QWidget *parent = nullptr);
// 将 VTK 图像数据vtkImageData转换为 Qt 的 QImage 对象
QImage createQImage(vtkImageData* imageData);
@ -54,6 +60,8 @@ private:
void renderWidgetForPrint(QPrinter *printer);
private:
/** @brief 当前网格视图所属的数据管理器;对象销毁时 QPointer 自动置空。 */
QPointer<nmDataAnalyzeManager> m_pDataManager;
QVBoxLayout* m_pMainLayout; // 此容器部件的主垂直布局。
// 指向 QVTKWidget 实例的指针,用于显示 VTK 内容。

@ -1,8 +1,9 @@
#ifndef NMWXINCLUDEOTHERWELLS_H
#ifndef NMWXINCLUDEOTHERWELLS_H
#define NMWXINCLUDEOTHERWELLS_H
#include "nmSubWxs_global.h"
#include "iDlgBase.h"
#include "nmDataNumericalAnalysisCase.h"
#include <QTableWidget>
#include <QPushButton>
#include <QHBoxLayout>
@ -10,19 +11,36 @@
#include <QHeaderView>
#include <QVector>
// 定义行数据结构体
class nmDataAnalyzeManager;
/**
* @brief Include Other Wells
* @note Map
*/
struct WellDataRow {
QString sName; // Name列
bool bIsIncluded; // Included列复选框状态
QString sOilProd; // Oil production列
QString sGasProd; // Gas production列
QString sWaterProd; // Water production列
QString sPressure; // Pressure列
QString m_sWellCode; ///< 项目唯一井编码,只用于关联,不直接显示。
QString m_sName; ///< 井显示名称,允许重名或后续修改。
bool m_bIsIncluded; ///< 是否被用户勾选为其他主动井。
bool m_bIsPrimary; ///< 主井防御标记;主井正常情况下不会进入本表。
bool m_bCanCalculate; ///< 当前井型和几何参数是否可进入 PEBI 模型。
NM_CASE_WELL_MODE m_eMode; ///< 本列表固定保存有产量主动井角色。
QString m_sOilProd; ///< 油产量列的摘要文本。
QString m_sGasProd; ///< 气产量列的摘要文本。
QString m_sWaterProd; ///< 水产量列的摘要文本。
QString m_sPressure; ///< 压力历史列的摘要文本。
/** @brief 初始化未勾选、不可计算的主动井候选行。 */
WellDataRow()
: m_bIsIncluded(false),
m_bIsPrimary(false),
m_bCanCalculate(false),
m_eMode(NM_CaseWell_RateControlled)
{
}
// 检查是否有有效数据
bool hasValidData() const {
return sOilProd != "None" || sGasProd != "None" ||
sWaterProd != "None" || sPressure != "None";
/** @brief WellCode 有效且井型/几何可计算时允许用户勾选。 */
bool canCalculate() const {
return m_bCanCalculate && !m_sWellCode.isEmpty();
}
};
@ -30,37 +48,48 @@ class NM_SUB_WXS_EXPORT nmWxIncludeOtherWells : public iDlgBase
{
Q_OBJECT
public:
explicit nmWxIncludeOtherWells(QWidget* pParent = nullptr);
/** @brief 创建绑定到指定分析窗口的其他有产量井选择对话框。 */
explicit nmWxIncludeOtherWells(nmDataAnalyzeManager* pDataManager,
QWidget* pParent = nullptr);
~nmWxIncludeOtherWells();
// 获取所有井数据
/** @brief 返回表格中各候选井及最终勾选状态。 */
QVector<WellDataRow> getWellData() const;
private:
/** @brief 按固定顺序初始化表格、按钮、布局和信号槽。 */
void initUI();
/** @brief 从所属数据管理器收集候选主动井并建立表格。 */
void setupTable();
/** @brief 创建确定和取消按钮。 */
void setupButtons();
/** @brief 组装表格与底部按钮布局。 */
void setupLayouts();
/** @brief 连接按钮及表格交互信号。 */
void setupConnections();
// 添加行数据到表格
/** @brief 将一条候选井数据写入指定表格行。 */
void addWellDataToTable(const WellDataRow& data, int row);
// 更新复选框状态
//void updateCheckBoxState(int row);
private slots:
/** @brief 响应整行点击,并切换该行包含状态。 */
void onItemClicked(QTableWidgetItem* item);
/** @brief 把复选框状态同步回对应的 WellDataRow。 */
void onCheckBoxStateChanged(int state);
private:
QTableWidget* m_pTableWidget;
QPushButton* m_pOkButton;
QPushButton* m_pCancelButton;
QHBoxLayout* m_pButtonLayout;
QVBoxLayout* m_pMainLayout;
/** @brief 当前对话框所属的数据管理器,不拥有对象。 */
nmDataAnalyzeManager* m_pDataManager;
QTableWidget* m_pTableWidget; ///< 其他有产量井及其数据摘要表格。
QPushButton* m_pOkButton; ///< 提交当前勾选状态的确定按钮。
QPushButton* m_pCancelButton; ///< 放弃本次编辑的取消按钮。
QHBoxLayout* m_pButtonLayout; ///< 底部操作按钮布局。
QVBoxLayout* m_pMainLayout; ///< 对话框主布局。
QVector<WellDataRow> m_wellData; // 存储所有井数据
QVector<WellDataRow> m_wellData; ///< 与表格行一一对应的候选井数据。
};
#endif // NMWXINCLUDEOTHERWELLS_H
#endif // NMWXINCLUDEOTHERWELLS_H

@ -1,8 +1,9 @@
#ifndef NMWXANALYTICALDESIGN_H
#define NMWXANALYTICALDESIGN_H
#ifndef NMWXNUMERICALDESIGN_H
#define NMWXNUMERICALDESIGN_H
#include "nmSubWxs_global.h"
#include "iDlgBase.h"
#include "nmParameterField.h"
#include <QWidget>
// 前向声明
@ -16,38 +17,63 @@ class QLabel;
class QRadioButton;
class QComboBox;
class QSpinBox;
class QDateTimeEdit;
class QStackedLayout;
class nmDataWellBase;
class nmDataDiagnostic;
class nmDataReservoir;
class nmGUIComponentLineEdit;
class nmDataAnalyzeManager;
class NM_SUB_WXS_EXPORT nmWxNumericalDesign : public iDlgBase
/**
* @brief Dock
* @note
*/
class NM_SUB_WXS_EXPORT nmWxNumericalDesign :
public iDlgBase,
public nmIParameterUnitService
{
Q_OBJECT
public:
explicit nmWxNumericalDesign(QWidget *parent = nullptr);
/** @brief 创建绑定到指定数值分析数据管理器的设计面板。 */
explicit nmWxNumericalDesign(nmDataAnalyzeManager* pDataManager,
QWidget* pParent = nullptr);
~nmWxNumericalDesign();
/** @brief 通过iDlgBase按NM参数系列绑定单位控件。 */
virtual bool bindUnitField(QLineEdit* pLineEdit,
QComboBox* pUnitComboBox,
const QString& sBaseParameterId);
/** @brief 将业务基准单位值写入字段。 */
virtual void setUnitBaseValue(QLineEdit* pLineEdit,
double dBaseValue);
/** @brief 读取字段的业务基准单位值。 */
virtual double unitBaseValue(QLineEdit* pLineEdit) const;
/** @brief 使用参数定义校验单位字段输入。 */
virtual bool isUnitInputValid(QLineEdit* pLineEdit);
/** @brief 返回字段当前显示单位。 */
virtual QString currentUnit(QLineEdit* pLineEdit) const;
/** @brief 返回字段单位组,不转移所有权。 */
virtual iUnitGroup* unitGroup(QLineEdit* pLineEdit) const;
// 静态方法,供外部调用以通知时间变表皮状态变化
static void notifyTimeDependentSkinChanged(const QString& wellName, bool checked);
// 刷新查看井结果下拉框
static void notifyResultWellSelectorChanged(const QString& wellName = QString());
// 将当前成果面板设为活动实例,并按照当前数据中心刷新查看井结果下拉框
/** @brief 仅刷新属于指定数据管理器的活动面板结果井列表。 */
static void notifyResultWellSelectorChanged(
nmDataAnalyzeManager* pDataManager,
const QString& sWellCode = QString());
/** @brief 将本面板设为活动实例,并按所属数据管理器刷新结果井列表。 */
void activateCurrentInstance();
signals:
void sigGenerateClicked(); // 点击生成按钮时发出的信号
void sigIncludeWells(); // 包含其他井后刷新参数栏的信号
// 切换查看井结果时发出的信号
void sigResultWellChanged(const QString& wellName);
//void sigUpdateWellPlot(nmDataAnalyzeManager*); // 更新井图元
//void sigIncludeWells(); // 包含其他井后刷新参数栏的信号
/** @brief 用户切换结果井时发送稳定 WellCode。 */
void sigResultWellChanged(const QString& sWellCode);
private slots:
private slots:
// 新增的槽函数
void onGenerateClicked(); // 生成按钮点击槽
void onResetFromDiagnosticClicked(); // "从诊断复位"按钮点击槽
@ -62,12 +88,14 @@ signals:
// 现有和调整的槽函数
void onOptionsIconClicked(); // "包含其他井"按钮点击
/** @brief 同步包含井总开关,并按实际有效井集合处理网格失效。 */
void onIncludeOtherWellsToggled(bool bChecked);
/** @brief Map 井或井数据变化后刷新包含井入口和结果井列表。 */
void onWellCollectionChanged();
void onTimeIconClicked(); // "时间相关"按钮 (在Advanced组中)
void onOutputResultFieldsAutomaticToggled(bool checked); // "输出结果字段"自动单选按钮状态改变槽
void onOutputResultFieldsManualToggled(bool checked); // "输出结果字段"手动单选按钮状态改变槽
// 处理 m_pIgnoreCheck 状态变化的槽
//void onIgnoreCheckToggled(bool checked);
// 处理 Output result fields 复选框的切换
void onOutputResultFieldsToggled(bool checked);
// 查看井结果下拉框切换槽
@ -79,14 +107,10 @@ signals:
/** @brief 将求解器设置同步到当前数据管理器。 */
void onPebiSolverSettingsChanged();
// 处理时间参考系单选按钮切换
//void onTimeReferenceSystemToggled(bool checked);
// 时间步组件值改变时,同步更新数据模型
//void onTimeSteppingUiChanged();
// 时间单位更新
//void onTimeUnitChanged(const QString& newUnit);
// 重置时间步数据
//void onResetTimeStep();
/** @brief 输入变化时刷新时间步校验状态。 */
void onTimeStepValueChanged(const QString& sText);
/** @brief 编辑完成后提交完整且合法的时间步设置。 */
void onTimeStepEditingFinished();
// 保存时间变表皮状态
void onTimeDependentCheck(bool checked);
@ -94,10 +118,8 @@ private:
// 初始化UI组件的私有函数
// 初始化主选项组
void initMainOptionsGroup();
// 初始化查看井结果组
void initResultWellGroup();
// 初始化当前模型显示组
void initCurrentModelGroup();
// 初始化模型与结果组
void initModelAndResultGroup();
void initOutputGroup(); // 初始化输出组 (新)
void initAdvancedGroup(); // 初始化高级组 (新)
void initTimeSteppingGroup(); // 初始化时间步进组
@ -113,14 +135,27 @@ private:
// 根据当前数据中心的数据更新时间步设置组件的值
void updateUiFromData();
// 根据UI更新模型数据
void updateDataFromUi();
// 填充查看井结果下拉框
void fillResultWellCombo(const QString& selectedWellName = QString());
// 根据UI更新模型数据任何字段非法时不产生部分写入
bool updateDataFromUi(QString& sError);
// 收集并校验时间步字段,返回值均为求解器使用的小时基准值
bool collectTimeStepValues(double& dTimeGrowthExponent,
double& dDtMin,
double& dDtMax,
QString& sError);
// 将焦点定位到第一个非法时间步字段
void focusFirstInvalidTimeStepField();
/** @brief 按统一结果资格填充下拉框,条目数据保存 WellCode。 */
void fillResultWellCombo(const QString& sSelectedWellCode = QString());
// 刷新当前模型显示
void updateCurrentModelDisplay();
// 刷新当前井的时间变表皮状态
void updateTimeDependentSkinDisplay();
/** @brief 仅当 Map 存在其他有产量数值井时启用 Include Other Wells。 */
void updateIncludeOtherWellsAvailability();
private:
/** @brief 当前Dock所属数据管理器不拥有所有权。 */
nmDataAnalyzeManager* m_pDataManager;
// 主选项组件
QGroupBox* m_pMainOptionsGroup; // 主选项分组框
@ -131,15 +166,12 @@ private:
QPushButton* m_pResetDiagnosticButton; // "从诊断复位"按钮 (新)
QPushButton* m_pResetAnalyticalButton; // "从解析复位"按钮 (新)
// 查看井结果切换控件
QGroupBox* m_pResultWellGroup;
QComboBox* m_pResultWellCombo;
bool m_bFillingResultWellCombo;
// 当前模型切换控件
QGroupBox* m_pCurrentModelGroup;
QComboBox* m_pCurrentModelCombo;
bool m_bFillingCurrentModelCombo;
// 模型与结果切换控件
QGroupBox* m_pModelAndResultGroup; ///< 当前模型与结果井控件分组。
QComboBox* m_pResultWellCombo; ///< 可展示结果的井列表itemData 保存 WellCode。
bool m_bFillingResultWellCombo; ///< 程序填充结果井时屏蔽切换槽。
QComboBox* m_pCurrentModelCombo; ///< 当前单相数值模型显示与选择控件。
bool m_bFillingCurrentModelCombo; ///< 程序刷新模型时屏蔽切换槽。
// 输出组组件 (新)
QGroupBox* m_pOutputGroup; // 输出分组框
@ -162,25 +194,9 @@ private:
// 时间步进组件
QGroupBox* m_pTimeSteppingGroup; // 时间步进分组框
nmGUIComponentLineEdit* m_pTimeGrowthExponentCom;
nmGUIComponentLineEdit* m_pDtMinCom;
nmGUIComponentLineEdit* m_pDtMaxCom;
//QCheckBox* m_pUseCoarseSteppingCheck;
//QPushButton* m_pResetTimeStep; // 重置时间步设置
//QRadioButton* m_pAbsoluteRadio; // "绝对"时间单选按钮
//QRadioButton* m_pElapsedRadio; // "流逝"时间单选按钮
//QComboBox* m_pTimeUnitComboBox; // 固定的时间单位标签,显示"hr"
// 切换不同状态下的时间输入框
//QStackedLayout* m_pFromStackedLayout;
//QStackedLayout* m_pSimulateUntilStackedLayout;
//QLineEdit* m_pTimeGrowthExponentLineEdit; // 流逝时间输入框
//QDateTimeEdit* m_pFromAbsoluteDateTimeEdit; // 绝对时间输入框
//QLineEdit* m_pSimulateUntilElapsedLineEdit; // 流逝时间输入框
//QDateTimeEdit* m_pSimulateUntilAbsoluteDateTimeEdit; // 绝对时间输入框
nmParameterField* m_pTimeGrowthExponentField; ///< 时间增长指数字段。
nmParameterField* m_pDtMinField; ///< 最小时间步字段。
nmParameterField* m_pDtMaxField; ///< 最大时间步字段。
// 数值设置组件
QGroupBox* m_pNumericalSettingsGroup; // 数值设置分组框
@ -210,8 +226,8 @@ private:
// 图标路径
QString m_sIconDir;
static nmWxNumericalDesign* s_pCurrentInstance; // 静态指针,指向当前活动实例
static nmWxNumericalDesign* s_pCurrentInstance; ///< 当前活动成果窗口的设计面板,不拥有对象。
};
#endif // NMWXANALYTICALDESIGN_H
#endif // NMWXNUMERICALDESIGN_H

@ -6,6 +6,7 @@
#include <QVector>
#include <QList>
#include <QMap>
#include <QPointer>
class QVBoxLayout;
class QHBoxLayout;
@ -95,7 +96,13 @@ class NM_SUB_WXS_EXPORT nmWxPostprocessingAnimationWidget : public QWidget
{
Q_OBJECT
public:
nmWxPostprocessingAnimationWidget(QWidget* parent = nullptr);
/**
* @brief
* @note
*/
nmWxPostprocessingAnimationWidget(
QWidget* pParent = nullptr,
nmDataAnalyzeManager* pDataManager = nullptr);
// 析构函数,用于清理资源,尽管 vtkSmartPointer 会自动管理大部分 VTK 对象的内存
~nmWxPostprocessingAnimationWidget();
@ -188,6 +195,9 @@ private slots:
void slotSetWellActorsVisible(bool visible);
private:
/** @brief 构造时绑定的分析数据管理器;销毁后 QPointer 自动置空。 */
QPointer<nmDataAnalyzeManager> m_pDataManager;
// 布局
QVBoxLayout* m_pMainLayout;
QVBoxLayout* m_pFrameLaoyt;
@ -223,8 +233,8 @@ private:
vtkSmartPointer<vtkUnstructuredGrid> m_pCachedBaseGrid;
// === 新增:用于绘制井的 VTK 对象 (全部使用 vtkActor 指针,因为 vtkTextActor3D/vtkBillboardTextActor3D 都继承自 vtkActor) ===
QMap<QString, vtkSmartPointer<vtkBillboardTextActor3D>> m_mapWellNameActors; // 存储井名称的 vtkBillboardTextActor3D 文本 Actor
QMap<QString, vtkSmartPointer<vtkActor>> m_mapWellLineActors; // 存储井线的 3D Actor
QMap<QString, vtkSmartPointer<vtkBillboardTextActor3D>> m_mapWellNameActors; ///< WellCode 到井名文本 Actor 的映射。
QMap<QString, vtkSmartPointer<vtkActor>> m_mapWellLineActors; ///< WellCode 到井线 Actor 的映射。
bool m_bShowWellActors; // 是否显示井名和井线
// 阈值过滤器

@ -48,6 +48,10 @@ public:
QStringList getItems();
// 设置被选中的items
void setSelectedItems(QStringList selectItems);
/** @brief 获取选中项的稳定业务值;井选择菜单中该值为 WellCode。 */
QList<QVariant> getSelectedValues() const;
/** @brief 按稳定业务值恢复选择,井显示名称允许重复或修改。 */
void setSelectedValues(const QList<QVariant>& selectedValues);
private:
QListWidget* m_pListWidget;
QTextEdit* m_pLineEdit;
@ -55,6 +59,7 @@ private:
QList<QVariant> m_values;
// 已经被选中的items
QStringList m_selectItems;
QList<QVariant> m_selectedValues; ///< 已选业务值;井选择菜单中保存 WellCode。
// 上次点击的井下标用于Shift+点击范围选择
int m_nLastClickedIndex;
// 获取指定井项在列表中的下标

@ -8,8 +8,6 @@
#include "nmDataReservoir.h"
#include "nmDataAutomaticFitting.h"
#include "nmCalculationPebiGrid.h"
#include <QApplication>
#include <QDebug>
#include <QTime>
@ -1337,15 +1335,17 @@ bool nmCalculationAutoFitLM::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) {
nmCalculationPebiGrid* pebiGrid = nmCalculationPebiGrid::getInstance();
if(!pebiGrid || !pebiGrid->generateOutputPara()) {
throw std::runtime_error("Failed to refresh final fracture parameters");
nmDataAnalyzeManager* dataManager =
nmDataAnalyzeManager::getCurrentInstance();
if(!dataManager) {
throw std::runtime_error("Data manager is unavailable");
}
dataManager->invalidatePebiGrid();
}
if(m_shouldStop) {
@ -2641,19 +2641,13 @@ double nmCalculationAutoFitLM::evaluateFitness(const QVector<double>& parameters
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) {
nmCalculationPebiGrid* pebiGrid = nmCalculationPebiGrid::getInstance();
if(!pebiGrid || !pebiGrid->generateOutputPara()) {
DEBUG_OUT(QString("%1: Call #%2 - Failed to refresh PEBI fracture parameters")
.arg(funcName).arg(callCount));
return 1e10;
}
}
// Dfc 和裂缝半长属于网格输入。标记网格失效,使下一次任务基于当前参数快照重建。
const bool fractureGridParameterSelected =
(m_parameterSelected.size() > 8 && m_parameterSelected[8]) ||
(m_parameterSelected.size() > 9 && m_parameterSelected[9]);
if(fractureGridParameterSelected) {
dataManager->invalidatePebiGrid();
}
// 4. 运行求解器。真实求解器偶发失败时允许重试,避免一次 DLL 调用异常
// 直接让整个粒子评价失败。
@ -4132,6 +4126,14 @@ QVector<QVector<double>> nmCalculationAutoFitLM::runSolverDll()
// 目标井是否存在,以及 DataManager 中刚写入的参数是否导致求解器异常。
DEBUG_OUT("SOLVER DLL START");
// 创建任务时绑定当前分析,并在当前线程冻结本次求解所需的全部输入。
nmDataAnalyzeManager* dataManager =
nmDataAnalyzeManager::getCurrentInstance();
if(!dataManager || m_targetWellName.isEmpty()) {
DEBUG_OUT("Data manager or target well is unavailable");
return QVector<QVector<double> >();
}
if(m_evaluationInProgress > 0) {
DEBUG_OUT("DLL Solver already running, skipping");
return QVector<QVector<double>>();
@ -4144,16 +4146,10 @@ QVector<QVector<double>> nmCalculationAutoFitLM::runSolverDll()
try {
DEBUG_OUT("Creating DLL solver task");
dllTask = new nmCalculationDllPebiSolverTask(m_tempDirectory);
if(m_targetWellName.isEmpty()) {
DEBUG_OUT("Target well name is empty - target-only solver cannot start");
delete dllTask;
--m_evaluationInProgress;
return result;
}
dllTask->setAutoFitTargetWell(m_targetWellName);
dllTask = new nmCalculationDllPebiSolverTask(
m_tempDirectory,
dataManager,
m_targetWellName);
if(m_shouldStop) {
DEBUG_OUT("Should stop - cleaning up and returning empty result");
@ -4311,13 +4307,20 @@ QVector<QVector<double>> nmCalculationAutoFitLM::runSolverDll()
bool nmCalculationAutoFitLM::runFinalFullSolver()
{
// 不设置目标井名,任务按原完整模式保存全部井和网格结果。
nmDataAnalyzeManager* dataManager =
nmDataAnalyzeManager::getCurrentInstance();
if(!dataManager) {
DEBUG_OUT("Cannot start final full-field solver without a data manager");
return false;
}
if(m_evaluationInProgress > 0) {
DEBUG_OUT("Cannot start final full-field solver while another evaluation is running");
return false;
}
++m_evaluationInProgress;
nmCalculationDllPebiSolverTask dllTask(m_tempDirectory);
nmCalculationDllPebiSolverTask dllTask(m_tempDirectory, dataManager);
dllTask.start();
const int maxWait = 3600000;
@ -4355,7 +4358,9 @@ bool nmCalculationAutoFitLM::runFinalFullSolver()
}
dllTask.wait();
const bool succeeded = dllTask.wasSuccessful();
// 后台只生成局部结果快照;确认求解和输入版本均有效后再一次性写回当前分析。
const bool succeeded =
dllTask.wasSuccessful() && dllTask.commitResult(dataManager);
--m_evaluationInProgress;
return succeeded;
}
@ -4389,4 +4394,3 @@ QString nmCalculationAutoFitLM::getStopReasonDescription(StopReasonLM reason) co
return tr("Unknown reason");
}
}

@ -4127,24 +4127,11 @@ double nmCalculationAutoFitPSO::evaluateFitness(const QVector<double>& parameter
return 1e10;
}
// 5. 获取 LogLog 数据。runSolver() 会更新 DataManager 中目标井的计算结果,
// 这里再从目标井读取 resultLogLogData 作为模拟曲线。
QVector<QVector<double>> resultLogLogData;
// 5. 新求解任务不回写每个候选的临时结果,直接使用任务返回的曲线快照。
QVector<QVector<double>> resultLogLogData =
m_lastEvaluatedLogLogData;
try {
nmDataWellBase* pTargetWell = dataManager->findWellByName(m_targetWellName);
if(!pTargetWell) {
DEBUG_OUT(QString("%1: Call #%2 - Target well '%3' NOT FOUND")
.arg(funcName).arg(callCount).arg(m_targetWellName));
return 1e10;
}
DEBUG_OUT(QString("%1: Call #%2 - Target well found: %3")
.arg(funcName).arg(callCount).arg(m_targetWellName));
resultLogLogData = pTargetWell->getResultLogLog();
if(!validateLogLogData(resultLogLogData)) {
DEBUG_OUT(QString("%1: Call #%2 - LogLog data VALIDATION FAILED")
.arg(funcName).arg(callCount));
@ -5156,6 +5143,13 @@ QVector<QVector<double>> nmCalculationAutoFitPSO::runSolverDll()
// 目标井是否存在,以及 DataManager 中刚写入的参数是否导致求解器异常。
DEBUG_OUT("SOLVER DLL START");
// 新求解任务要求在创建线程绑定所属分析,并把目标井结果保存在任务快照中。
nmDataAnalyzeManager* pDataManager =
nmDataAnalyzeManager::getCurrentInstance();
if(pDataManager == nullptr || m_targetWellName.isEmpty()) {
return QVector<QVector<double> >();
}
if(m_evaluationInProgress > 0) {
DEBUG_OUT("DLL Solver already running, skipping");
return QVector<QVector<double>>();
@ -5167,7 +5161,10 @@ QVector<QVector<double>> nmCalculationAutoFitPSO::runSolverDll()
try {
DEBUG_OUT("Creating DLL solver task");
dllTask = new nmCalculationDllPebiSolverTask(m_tempDirectory);
dllTask = new nmCalculationDllPebiSolverTask(
m_tempDirectory,
pDataManager,
m_targetWellName);
if(m_shouldStop) {
DEBUG_OUT("Should stop - cleaning up and returning empty result");
@ -5228,22 +5225,11 @@ QVector<QVector<double>> nmCalculationAutoFitPSO::runSolverDll()
return result;
}
// 验证结果数据是否已更新。DLL 任务会把结果写回 DataManager 中的目标井对象。
nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance();
//QVector<nmDataWellBase*> wells = dataManager->getWellDataList();
nmDataWellBase* pTargetWell = dataManager->findWellByName(m_targetWellName);
if(!pTargetWell) {
DEBUG_OUT("No wells found in data manager after DLL execution");
delete dllTask;
--m_evaluationInProgress;
return result;
}
// 验证结果数据。evaluateFitness() 最终用的是 logLogResult
// 但这里返回 pressureResult 给 validateSolverResult() 做基本求解成功判断。
QVector<QVector<double>> pressureResult = pTargetWell->getResultPressure();
QVector<QVector<double>> logLogResult = pTargetWell->getResultLogLog();
// 自动拟合只读取任务自己的目标井结果,不把每个候选的临时曲线写回项目。
QVector<QVector<double>> pressureResult =
dllTask->getAutoFitResultPressure();
QVector<QVector<double>> logLogResult =
dllTask->getAutoFitResultLogLog();
DEBUG_OUT(QString("DLL result verification - Pressure arrays: %1, LogLog arrays: %2")
.arg(pressureResult.size()).arg(logLogResult.size()));
@ -5262,8 +5248,12 @@ QVector<QVector<double>> nmCalculationAutoFitPSO::runSolverDll()
}
// 数据有效性检查
if(pressureResult.size() >= 2 && pressureResult[0].size() > 0 && pressureResult[1].size() > 0) {
if(pressureResult.size() >= 2 &&
pressureResult[0].size() > 0 &&
pressureResult[1].size() > 0 &&
validateLogLogData(logLogResult)) {
result = pressureResult;
m_lastEvaluatedLogLogData = logLogResult;
DEBUG_OUT(QString("Got DLL solver result: %1 points").arg(result[0].size()));
m_consecutiveFailures = 0;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -1,19 +1,66 @@
#include "nmCalculationPebiGridTask.h"
#include "nmCalculationPebiGridTask.h"
#include "nmCalculationPebiGrid.h"
#include "nmDataAnalyzeManager.h"
nmCalculationPebiGridTask::nmCalculationPebiGridTask(QObject *parent)
: QThread(parent)
nmCalculationPebiGridTask::nmCalculationPebiGridTask(
nmDataAnalyzeManager* pDataManager,
QObject *parent)
: QThread(parent),
m_pDataManager(pDataManager),
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);
}
}
nmCalculationPebiGridTask::~nmCalculationPebiGridTask()
{
if(isRunning()) {
wait();
}
releaseDataManagerUse();
}
void nmCalculationPebiGridTask::run()
{
// 当前网格生成链路只保留PEBI.
// 生成网格.
bool success = nmCalculationPebiGrid::getInstance()->meshGenPebi();
if (success) {
// 通知主线程PEBI网格生成完成.
emit pebiGridGenerated(success);
// 第二步:后台只消费不可变快照,所有 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) {
m_pDataManager->endBackgroundUse();
m_bManagerUseActive = false;
}
}

@ -5,6 +5,8 @@
#include <QDir>
#include <QDebug>
#include <QCoreApplication>
#include <QMutex>
#include <QMutexLocker>
#include <fstream>
#include <float.h>
@ -12,6 +14,9 @@
namespace
{
// HX_NWTM.dll 的配置和结果查询接口使用进程级共享状态,所有入口共用此锁。
QMutex s_oHxNwtmDllMutex;
QString krigingText(const char* sourceText)
{
return QCoreApplication::translate("nmCalculationUtils", sourceText);
@ -37,6 +42,11 @@ void setKrigingError(QString* errorMessage, const QString& message)
nmCalculationUtils::nmCalculationUtils() {
}
QMutex* nmCalculationUtils::getHxNwtmDllMutex()
{
return &s_oHxNwtmDllMutex;
}
double nmCalculationUtils::milliDarcyToDarcy(double dPermeabilityMilliDarcy)
{
// PEBI输入结构使用D数据层统一使用mD因此仅在求解器边界换算。
@ -267,6 +277,8 @@ bool nmCalculationUtils::calculateKriging(
return false;
}
// Kriging 与建网、模型求解来自同一个 DLL不能在不同线程中并发进入。
QMutexLocker oDllLocker(getHxNwtmDllMutex());
HMODULE dll = LoadLibrary(L"HX_NWTM.dll");
if(dll == NULL) {
setKrigingError(errorMessage, krigingText("Failed to load HX_NWTM.dll."));
@ -354,4 +366,3 @@ bool nmCalculationUtils::calculateKriging(
}
return calculationSucceeded;
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,424 @@
#include "nmDataNumericalAnalysisCase.h"
#include <QSet>
#include <QtGlobal>
namespace
{
// 外部枚举值只允许落到当前方案支持的两种井角色,其他值保守按观察井处理。
NM_CASE_WELL_MODE normalizedWellMode(NM_CASE_WELL_MODE eMode)
{
return eMode == NM_CaseWell_RateControlled
? NM_CaseWell_RateControlled : NM_CaseWell_Observation;
}
}
// ==================== 轻量井引用 ====================
nmCalculationWellRef::nmCalculationWellRef()
: m_eMode(NM_CaseWell_Observation)
{
}
nmCalculationWellRef::nmCalculationWellRef(const QString& sWellCode,
NM_CASE_WELL_MODE eMode)
: m_sWellCode(sWellCode),
m_eMode(eMode)
{
}
nmSolverWellRef::nmSolverWellRef()
: m_nSolverIndex(-1),
m_eWellType(NM_WELL_MODEL::Unknow_Well),
m_eEntryKind(NM_SolverEntry_Well)
{
}
nmSolverWellRef::nmSolverWellRef(int nSolverIndex,
NM_WELL_MODEL eWellType,
const QString& sWellCode,
NM_SOLVER_ENTRY_KIND eEntryKind)
: m_nSolverIndex(nSolverIndex),
m_eWellType(eWellType),
m_sWellCode(sWellCode),
m_eEntryKind(eEntryKind)
{
}
nmDataNumericalAnalysisCase::nmDataNumericalAnalysisCase()
{
clear();
}
// ==================== 主分析井 ====================
QString nmDataNumericalAnalysisCase::getPrimaryWellCode() const
{
return m_sPrimaryWellCode;
}
void nmDataNumericalAnalysisCase::setPrimaryWellCode(const QString& sWellCode)
{
if(m_sPrimaryWellCode == sWellCode) {
return;
}
const QString sOldPrimaryWellCode = m_sPrimaryWellCode;
m_sPrimaryWellCode = sWellCode;
normalizeIncludedWells();
if(m_sCurrentResultWellCode.isEmpty() ||
m_sCurrentResultWellCode == sOldPrimaryWellCode) {
m_sCurrentResultWellCode = m_sPrimaryWellCode;
}
invalidateGrid();
}
NM_CASE_WELL_MODE nmDataNumericalAnalysisCase::getPrimaryWellMode() const
{
return m_ePrimaryWellMode;
}
void nmDataNumericalAnalysisCase::setPrimaryWellMode(NM_CASE_WELL_MODE eMode)
{
eMode = normalizedWellMode(eMode);
if(m_ePrimaryWellMode == eMode) {
return;
}
m_ePrimaryWellMode = eMode;
invalidateResults();
}
// ==================== 包含其他有产量井 ====================
bool nmDataNumericalAnalysisCase::getIncludeOtherWells() const
{
return m_bIncludeOtherWells;
}
void nmDataNumericalAnalysisCase::setIncludeOtherWells(bool bInclude)
{
if(m_bIncludeOtherWells == bInclude) {
return;
}
m_bIncludeOtherWells = bInclude;
// 只有确实选过有产量干扰井时,总开关才会改变网格中的井集合。
// Map 中的无产量观察井不经过本开关,由数据管理层始终加入计算。
// 空选择下切换开关只是保存用户设置,不应触发一次无意义的网格重建。
if(!m_vecIncludedWells.isEmpty()) {
invalidateGrid();
}
}
double nmDataNumericalAnalysisCase::getPebiGridControl() const
{
return m_dPebiGridControl;
}
void nmDataNumericalAnalysisCase::setPebiGridControl(double dGridControl)
{
// 第一步:网格控制值必须为正数,非法输入不能污染当前有效方案。
if(dGridControl <= 0.0) {
return;
}
// 第二步:相同数值不重复增加输入版本,避免界面刷新引发无意义重划分。
if(qFuzzyCompare(m_dPebiGridControl, dGridControl)) {
return;
}
// 第三步:网格密度属于几何离散输入,变化后网格和结果必须同时失效。
m_dPebiGridControl = dGridControl;
invalidateGrid();
}
// 包含井列表只保存用户显式选择Map 中无产量井由数据管理器动态补入有效集合。
QVector<nmCalculationWellRef> nmDataNumericalAnalysisCase::getIncludedWells() const
{
return m_vecIncludedWells;
}
void nmDataNumericalAnalysisCase::setIncludedWells(
const QVector<nmCalculationWellRef>& vecWells)
{
// 第一步:保存旧选择,并对新的有产量干扰井做去空和去重。
QVector<nmCalculationWellRef> vecOldWells = m_vecIncludedWells;
m_vecIncludedWells = vecWells;
normalizeIncludedWells();
// 第二步:井在对话框中的显示顺序不影响 PEBI 几何,只比较 WellCode 集合。
QSet<QString> setOldWellCodes;
QSet<QString> setNewWellCodes;
for(int nIndex = 0; nIndex < vecOldWells.size(); ++nIndex) {
setOldWellCodes.insert(vecOldWells[nIndex].m_sWellCode);
}
for(int nIndex = 0; nIndex < m_vecIncludedWells.size(); ++nIndex) {
setNewWellCodes.insert(m_vecIncludedWells[nIndex].m_sWellCode);
}
const bool bWellSetChanged = setOldWellCodes != setNewWellCodes;
// 第三步:总开关关闭时,包含井尚未进入有效计算集合,选择变化只需保存。
if(!m_bIncludeOtherWells) {
return;
}
// 第四步IncludedWells 中的井固定为主动井,增删井会改变网格。
if(bWellSetChanged) {
invalidateGrid();
}
}
bool nmDataNumericalAnalysisCase::isIncludedWell(const QString& sWellCode) const
{
for(int nIndex = 0; nIndex < m_vecIncludedWells.size(); ++nIndex) {
if(m_vecIncludedWells[nIndex].m_sWellCode == sWellCode) {
return true;
}
}
return false;
}
NM_CASE_WELL_MODE nmDataNumericalAnalysisCase::getWellMode(
const QString& sWellCode) const
{
if(sWellCode == m_sPrimaryWellCode) {
return m_ePrimaryWellMode;
}
for(int nIndex = 0; nIndex < m_vecIncludedWells.size(); ++nIndex) {
if(m_vecIncludedWells[nIndex].m_sWellCode == sWellCode) {
return m_vecIncludedWells[nIndex].m_eMode;
}
}
return NM_CaseWell_Observation;
}
QVector<nmCalculationWellRef> nmDataNumericalAnalysisCase::getEffectiveWells() const
{
QVector<nmCalculationWellRef> vecResult;
// 第一步:主分析井始终参与当前数值分析,并固定排在第一位。
if(!m_sPrimaryWellCode.isEmpty()) {
vecResult.append(nmCalculationWellRef(m_sPrimaryWellCode,
m_ePrimaryWellMode));
}
// 第二步:这里只返回方案显式保存的井。数据管理层还会把 Map 中所有
// 无产量井自动追加为观察井,它们与 Include Other Wells 完全无关。
if(m_bIncludeOtherWells) {
vecResult += m_vecIncludedWells;
}
return vecResult;
}
// ==================== 结果查看状态 ====================
QString nmDataNumericalAnalysisCase::getCurrentResultWellCode() const
{
return m_sCurrentResultWellCode;
}
void nmDataNumericalAnalysisCase::setCurrentResultWellCode(
const QString& sWellCode)
{
// 切换结果井只是界面查看状态,不应触发网格或求解结果失效。
m_sCurrentResultWellCode = sWellCode;
}
// ==================== DLL 求解器井槽位顺序 ====================
QVector<nmSolverWellRef> nmDataNumericalAnalysisCase::getSolverWellOrder() const
{
return m_vecSolverWellOrder;
}
void nmDataNumericalAnalysisCase::clearSolverWellOrder()
{
m_vecSolverWellOrder.clear();
m_nBuiltGridRevision = 0;
m_bResultsValid = false;
}
void nmDataNumericalAnalysisCase::appendSolverWell(
const nmSolverWellRef& oWellRef)
{
nmSolverWellRef oIndexedRef = oWellRef;
oIndexedRef.m_nSolverIndex = m_vecSolverWellOrder.size();
m_vecSolverWellOrder.append(oIndexedRef);
}
void nmDataNumericalAnalysisCase::insertSolverWell(
int nIndex,
const nmSolverWellRef& oWellRef)
{
if(nIndex < 0 || nIndex > m_vecSolverWellOrder.size()) {
return;
}
m_vecSolverWellOrder.insert(nIndex, oWellRef);
for(int nOrderIndex = 0;
nOrderIndex < m_vecSolverWellOrder.size();
++nOrderIndex) {
m_vecSolverWellOrder[nOrderIndex].m_nSolverIndex = nOrderIndex;
}
}
bool nmDataNumericalAnalysisCase::removeSolverWell(int nIndex)
{
if(nIndex < 0 || nIndex >= m_vecSolverWellOrder.size()) {
return false;
}
m_vecSolverWellOrder.remove(nIndex);
for(int nOrderIndex = 0;
nOrderIndex < m_vecSolverWellOrder.size();
++nOrderIndex) {
m_vecSolverWellOrder[nOrderIndex].m_nSolverIndex = nOrderIndex;
}
m_bResultsValid = false;
return true;
}
void nmDataNumericalAnalysisCase::setSolverWellOrder(
const QVector<nmSolverWellRef>& vecOrder)
{
m_vecSolverWellOrder = vecOrder;
for(int nIndex = 0; nIndex < m_vecSolverWellOrder.size(); ++nIndex) {
m_vecSolverWellOrder[nIndex].m_nSolverIndex = nIndex;
}
m_bResultsValid = false;
}
// ==================== 网格与结果版本管理 ====================
void nmDataNumericalAnalysisCase::invalidateGrid()
{
++m_nGridInputRevision;
m_nBuiltGridRevision = 0;
m_vecSolverWellOrder.clear();
invalidateResults();
}
void nmDataNumericalAnalysisCase::invalidateResults()
{
++m_nResultInputRevision;
m_bResultsValid = false;
}
void nmDataNumericalAnalysisCase::markGridBuilt()
{
m_nBuiltGridRevision = m_nGridInputRevision;
m_bResultsValid = false;
}
bool nmDataNumericalAnalysisCase::markGridBuiltIfCurrent(
quint64 nGridInputRevision)
{
// 第一步:后台划分期间几何输入发生变化时,当前输出立即作废。
if(nGridInputRevision != m_nGridInputRevision ||
m_vecSolverWellOrder.isEmpty()) {
m_nBuiltGridRevision = 0;
m_bResultsValid = false;
return false;
}
// 第二步:输入版本未变化时,才把求解器顺序和网格登记为同一版本。
markGridBuilt();
return true;
}
void nmDataNumericalAnalysisCase::discardResults()
{
// 重新求解本身不是输入修改,只清除有效标记,不增加结果输入版本号。
m_bResultsValid = false;
}
void nmDataNumericalAnalysisCase::markResultsAvailable()
{
m_bResultsValid = isGridValid();
}
bool nmDataNumericalAnalysisCase::markResultsAvailableIfCurrent(
quint64 nGridInputRevision,
quint64 nResultInputRevision)
{
// 第一步:任务使用的网格版本和求解输入版本必须仍是当前版本。
const bool bInputCurrent =
nGridInputRevision == m_nGridInputRevision &&
nResultInputRevision == m_nResultInputRevision;
// 第二步:只有输入未变化且网格仍有效时,才允许旧线程提交结果。
m_bResultsValid = bInputCurrent && isGridValid();
return m_bResultsValid;
}
bool nmDataNumericalAnalysisCase::isGridValid() const
{
return m_nBuiltGridRevision != 0 &&
m_nBuiltGridRevision == m_nGridInputRevision &&
!m_vecSolverWellOrder.isEmpty();
}
bool nmDataNumericalAnalysisCase::areResultsValid() const
{
return m_bResultsValid && isGridValid();
}
quint64 nmDataNumericalAnalysisCase::getGridInputRevision() const
{
return m_nGridInputRevision;
}
quint64 nmDataNumericalAnalysisCase::getBuiltGridRevision() const
{
return m_nBuiltGridRevision;
}
quint64 nmDataNumericalAnalysisCase::getResultInputRevision() const
{
return m_nResultInputRevision;
}
// 项目重新初始化时恢复完整默认状态,不保留上一成果的井关联或有效标记。
void nmDataNumericalAnalysisCase::clear()
{
m_sPrimaryWellCode.clear();
m_ePrimaryWellMode = NM_CaseWell_Observation;
m_bIncludeOtherWells = false;
m_dPebiGridControl = 150.0;
m_vecIncludedWells.clear();
m_sCurrentResultWellCode.clear();
m_vecSolverWellOrder.clear();
m_nGridInputRevision = 1;
m_nBuiltGridRevision = 0;
m_nResultInputRevision = 1;
m_bResultsValid = false;
}
void nmDataNumericalAnalysisCase::normalizeIncludedWells()
{
QVector<nmCalculationWellRef> vecNormalized;
QSet<QString> setSeenWellCodes;
for(int nIndex = 0; nIndex < m_vecIncludedWells.size(); ++nIndex) {
const nmCalculationWellRef& oWellRef = m_vecIncludedWells[nIndex];
if(oWellRef.m_sWellCode.isEmpty() ||
oWellRef.m_sWellCode == m_sPrimaryWellCode ||
setSeenWellCodes.contains(oWellRef.m_sWellCode)) {
continue;
}
setSeenWellCodes.insert(oWellRef.m_sWellCode);
vecNormalized.append(nmCalculationWellRef(
oWellRef.m_sWellCode,
NM_CaseWell_RateControlled));
}
m_vecIncludedWells = vecNormalized;
}

@ -3,7 +3,7 @@
nmDataTimeStepSetting::nmDataTimeStepSetting()
{
m_TimeGrowthExponent = nmDataAttribute("Time Growth Exponent", 1.05, "");
m_MinDeltaT = nmDataAttribute("Min Delta T", 0.0008, "hr", UNIT_TYPE_TIME, QStringList(), QStringList() << "hr" << "day" << "min" << "sec" << "ms" << "Week" << "Month" << "Year");
m_MinDeltaT = nmDataAttribute("Min Delta T", 0.0025, "hr", UNIT_TYPE_TIME, QStringList(), QStringList() << "hr" << "day" << "min" << "sec" << "ms" << "Week" << "Month" << "Year");
m_MaxDeltaT = nmDataAttribute("Max Delta T", 12.5, "hr", UNIT_TYPE_TIME, QStringList(), QStringList() << "hr" << "day" << "min" << "sec" << "ms" << "Week" << "Month" << "Year");
}
@ -68,4 +68,4 @@ void nmDataTimeStepSetting::setMinDeltaTAttribute(const nmDataAttribute& attr)
void nmDataTimeStepSetting::setMaxDeltaTAttribute(const nmDataAttribute& attr)
{
m_MaxDeltaT = attr;
}
}

@ -49,24 +49,100 @@
#include <QVector>
#include <QProcess>
#include <QTimer>
#include <QLabel>
#include <QStackedLayout>
#include "ZxRstWnd.h"
nmSubWndGrid::nmSubWndGrid(QWidget *parent, QString sExt, double pebiGridControl, bool bGeneratePebiGrid) :
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_bGridUpdatePending(false),
m_dPebiGridControl(pebiGridControl),
m_bGeneratePebiGrid(bGeneratePebiGrid)
{
m_sMdiWndType = s_MdiType_DataAnal;
setWindowTitle(tr("nmSubWndGrid"));
setDataManager(pDataManager);
}
nmSubWndGrid::~nmSubWndGrid()
{
// 外部 DLL 不支持安全中止;窗口析构前等待自己的任务退出,避免销毁运行中的 QThread。
if(!m_pGridTask.isNull() && m_pGridTask->isRunning()) {
disconnect(m_pGridTask, nullptr, this, nullptr);
m_pGridTask->wait();
}
}
void nmSubWndGrid::setDataManager(nmDataAnalyzeManager* pDataManager)
{
nmDataAnalyzeManager* pOldDataManager = m_pDataManager.data();
if(pOldDataManager == pDataManager) {
return;
}
// 第一步:只解除旧分析与本窗口的连接,避免切换多次后旧分析继续触发重建。
if(pOldDataManager != nullptr) {
disconnect(pOldDataManager, SIGNAL(dataChanged()),
this, SLOT(onDataManagerChanged()));
}
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();
if(!m_pGridTask.isNull()) {
// 外部 DLL 不能安全中止;旧任务自然结束后只补做当前分析的最后一次更新。
m_bGridUpdatePending = true;
return;
}
m_bGridUpdatePending = false;
updateGrid();
}
void nmSubWndGrid::configWnd(QString sPrevInfo)
@ -106,10 +182,12 @@ bool nmSubWndGrid::onConfirmClosing()
return false;
}
nmCalculationPebiGrid::getInstance()->clearGridData();
nmCalculationPebiGrid::getInstance()->clearGridData(m_pDataManager.data());
nmDataAnalyzeManager* pDataManager = nmDataAnalyzeManager::getCurrentInstance();
nmDataAnalyzeManager* pDataManager = m_pDataManager.data();
if(pDataManager != nullptr) {
// 当前窗口选择清除网格缓存后,分析方案也必须同步失效。
pDataManager->invalidatePebiGrid();
pDataManager->clearUnstructuredGrid();
}
@ -120,7 +198,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); // 扩展策略
@ -128,25 +206,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(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()
@ -169,16 +249,41 @@ bool nmSubWndGrid::generatePebiGrid()
nmCalculationPebiGrid* pPebiGridGeneratorInstance = nmCalculationPebiGrid::getInstance();
// 当前流程只保留PEBI网格这里仍写入数据中心供求解和后处理的旧判断继续使用。
nmDataAnalyzeManager::getCurrentInstance()->setGridType(NM_Grid_PEBI);
nmDataAnalyzeManager* pDataManager = m_pDataManager.data();
if(pDataManager == nullptr) {
return false;
}
pDataManager->setGridType(NM_Grid_PEBI);
// 将对话框中的GridControl传给PEBI输入参数GridControl
pPebiGridGeneratorInstance->setGridControl(m_dPebiGridControl);
// 生成PEBI非结构化网格对象
return pPebiGridGeneratorInstance->meshGenPebi();
pPebiGridGeneratorInstance->setGridControl(m_dPebiGridControl,
pDataManager);
// 首次手工生成无需等待防抖窗口。此时 VTK 背景已经完成初始化,任务启动
// 后界面可以立即绘制;连续 dataChanged 仍由 updateGrid() 合并处理。
showGridLoading();
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();
// dataChanged可能连续触发使用当前窗口自己的定时器合并为一次网格重建
if(!m_pDebounceTimer) {
m_pDebounceTimer = new QTimer(this);
@ -191,30 +296,138 @@ void nmSubWndGrid::updateGrid()
void nmSubWndGrid::onDebounceTimeout()
{
// 数据变更后重新生成网格时继续沿用当前窗口的GridControl
nmCalculationPebiGrid::getInstance()->setGridControl(m_dPebiGridControl);
nmCalculationPebiGridTask *task = new nmCalculationPebiGridTask(this);
connect(task, SIGNAL(pebiGridGenerated(bool)),
this, SLOT(onPebiGridGenerated(bool)));
connect(task, SIGNAL(finished()), task, SLOT(deleteLater()));
task->start();
// 第一步:任务结束信号回到主线程前仍视为占用,避免覆盖尚未处理的任务指针。
if(!m_pGridTask.isNull()) {
m_bGridUpdatePending = true;
return;
}
nmDataAnalyzeManager* pDataManager = m_pDataManager.data();
if(pDataManager == nullptr) {
return;
}
// 第二步:把窗口参数写回所属分析方案,再启动绑定同一管理器的后台任务。
nmCalculationPebiGrid::getInstance()->setGridControl(
m_dPebiGridControl, 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)
{
if (!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(!m_bGridUpdatePending) {
showGridFailure();
}
}
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() &&
m_vtkWidget == nullptr) {
showGridResult();
return;
}
updateGrid();
}
}
void nmSubWndGrid::showGridLoading()
{
if(m_pGridLoadingLabel == nullptr || m_mainLayout == nullptr) {
return;
}
// 删除旧的 VTK 控件
if (m_vtkWidget) {
m_mainLayout->removeWidget(m_vtkWidget);
delete m_vtkWidget;
m_vtkWidget = nullptr;
m_pGridLoadingLabel->setText(tr("Generating grid..."));
m_mainLayout->setCurrentWidget(m_pGridLoadingLabel);
}
void nmSubWndGrid::showGridResult()
{
if(m_mainLayout == nullptr || m_pDataManager.isNull()) {
return;
}
// 创建新的 VTK 控件,并将其加入布局
m_vtkWidget = new nmWxGridVTKContainerWidget(this);
// 设置新创建的 nmWxGridVTKContainerWidget 的尺寸策略**
m_vtkWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); // 扩展策略
// 网格已经在主线程完整提交,此处一次创建新的 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);
}
}

@ -125,6 +125,8 @@ nmSubWndMain::nmSubWndMain(QWidget *parent, QString sExt) :
m_openDialogCmdIds.clear();
m_pProgressDlg = nullptr;
m_pSolverTask = nullptr;
m_pSolverDataManager = nullptr;
m_pSolverFitting = nullptr;
m_pFakeProgressTimer = nullptr;
m_nVirtualProgress = 0;
m_nSlowDownCounter = 0;
@ -181,14 +183,18 @@ nmSubWndMain::~nmSubWndMain()
}
if (m_pSolverTask) {
// 窗口销毁时只解除本窗口的回调;若线程仍在运行,让线程自己结束后释放
// 第一步:停止向正在析构的窗口投递完成回调
disconnect(m_pSolverTask, SIGNAL(sig_calculateDone(bool)), this, SLOT(on_solverTaskFinished(bool)));
if (m_pSolverTask == s_pRunningSolverTask && !m_pSolverTask->isRunning()) {
s_pRunningSolverTask = nullptr;
// 第二步DataManager 会在成果窗口销毁后释放,而任务仍读取该对象。
// Qt 4.8 下只能等待外部 DLL 正常返回,绝不能让裸指针越过管理器生命周期。
if(m_pSolverTask->isRunning()) {
m_pSolverTask->wait();
}
if (!m_pSolverTask->isRunning()) {
m_pSolverTask->deleteLater();
if (m_pSolverTask == s_pRunningSolverTask) {
s_pRunningSolverTask = nullptr;
}
// 第三步:线程已经停止,当前窗口仍是任务的明确所有者,可直接释放。
delete m_pSolverTask;
m_pSolverTask = nullptr;
}
@ -1241,54 +1247,60 @@ void nmSubWndMain::selectWell()
return;
}
// 先获取已经添加到的界面中井
QStringList wellPlotNameList = m_pWxPlot->getWellNames();
// 先找到所有的井
// 第一步Map 当前选择状态使用 WellCode 保存,井名只用于界面显示。
QList<QVariant> selectedWellCodes;
QVector<ZxDataWell*> vecMapWells = m_pWxPlot->getWellDatas();
for(int nIndex = 0; nIndex < vecMapWells.size(); ++nIndex) {
ZxDataWell* pMapWell = vecMapWells[nIndex];
if(pMapWell != nullptr && !pMapWell->getCode().isEmpty()) {
selectedWellCodes.append(pMapWell->getCode());
}
}
// 第二步:读取项目井库,并建立 WellCode 到项目井对象的稳定映射。
int wellCount = zxCurProject->getChildrenCount(iDataModelType::sTypeWell);
ZxDataObjectList wellList = zxCurProject->getChildren(iDataModelType::sTypeWell);
nmDataLogFile::getInstance()->writeLog(QString(" %1 %2").arg(wellCount).arg(wellList.size()));
// 井的名字
QStringList listWellNames;
// 井的code
QList<QVariant> wellCodes;
// 井的名字到井数据指针的Map
QMap<QString, ZxDataWell*> wellNameToObjMap;
// 数据整理
QMap<QString, ZxDataWell*> wellCodeToObjMap;
for(int i = 0; i < wellList.size(); i++) {
ZxDataWell* wellObj = (ZxDataWell*)wellList[i];
ZxDataWell* wellObj = dynamic_cast<ZxDataWell*>(wellList[i]);
if(wellObj == nullptr || wellObj->getCode().isEmpty()) {
continue;
}
nmDataLogFile::getInstance()->writeLog(wellObj->getName() + " " + wellObj->getCode());
listWellNames.append(wellObj->getName());
wellCodes.append(wellObj->getCode());
wellNameToObjMap.insert(wellObj->getName(), wellObj);
wellCodeToObjMap.insert(wellObj->getCode(), wellObj);
}
// 展示dialog供选择
// 第三步:恢复 Map 中已有井的勾选状态,允许用户增删其他项目井。
nmWxSelectWellsDlg dlg;
nmWxSelectWellsWidget wellListWidget;
wellListWidget.setSelectedItems(wellPlotNameList);
wellListWidget.setSelectedValues(selectedWellCodes);
wellListWidget.addItems(listWellNames, wellCodes);
dlg.setWidget(&wellListWidget);
if(dlg.exec() == QDialog::Accepted) {
// 用户点击了"确定"按钮
nmDataLogFile::getInstance()->writeLog(" OK ");
QStringList wellNames = wellListWidget.getItems();
QList<QVariant> selectedValues = wellListWidget.getSelectedValues();
QList<ZxDataWell*> selectWellList;
// 找出新选中的井
for(int i = 0; i < wellNames.count(); i++) {
QString wellName = wellNames[i];
if (!wellName.isEmpty()) { // 确保井名不为空
// 仅将有效的井对象添加到 selectWellList
selectWellList.append(wellNameToObjMap.value(wellName));
// 第四步:按 WellCode 解析选择结果,避免同名井或井名修改导致关联错误。
QStringList selectedCodeTexts;
for(int i = 0; i < selectedValues.count(); i++) {
const QString sWellCode = selectedValues[i].toString();
ZxDataWell* pSelectedWell = wellCodeToObjMap.value(sWellCode, nullptr);
if(pSelectedWell != nullptr) {
selectWellList.append(pSelectedWell);
selectedCodeTexts.append(sWellCode);
}
}
this->updateSelectedWells(selectWellList);
// 更新完成后,通知参数界面刷新
//nmWxParameterProperty::notifyUpdateTable();
nmDataLogFile::getInstance()->writeLog("===" + wellNames.join(";") + "===");
nmDataLogFile::getInstance()->writeLog(
"===" + selectedCodeTexts.join(";") + "===");
} else {
// 用户点击了"取消"按钮
nmDataLogFile::getInstance()->writeLog(" Cancel ");
}
}
@ -1512,6 +1524,10 @@ void nmSubWndMain::solveAndAnalyze()
m_pProgressDlg = nullptr;
}
// 新一次启动前清空旧上下文;后续所有检查通过后再成对写入。
m_pSolverDataManager = nullptr;
m_pSolverFitting = nullptr;
// 根据当前数据中心的分析切换到对应的流动段分析窗口
ZxMainWindow* pMainWnd1 = const_cast<ZxMainWindow*>(m_pMainWindow);
@ -1525,13 +1541,54 @@ void nmSubWndMain::solveAndAnalyze()
if(pSubWndFit == nullptr) {
return;
}
if(pTabWx == nullptr) {
return;
}
pTabWx->setCurrentWidget(pSubWndFit);
nmDataAnalyzeManager* pDataManager = nmDataAnalyzeManager::getCurrentInstance();
nmDataAnalyzeManager* pDataManager =
nmDataAnalyzeManager::findManagerByFitting(pSubWndFit);
if(pDataManager == nullptr) {
return;
}
// 第一步:求解时间轴必须由至少一口主动井的产注制度驱动。
// 观察井可以完全不传产量,但不能在所有井都是观察井时启动无源汇计算。
QVector<nmCalculationWellRef> vecEffectiveWells =
pDataManager->getEffectiveCalculationWells();
bool bHasRateControlledWell = false;
for(int nIndex = 0; nIndex < vecEffectiveWells.size(); ++nIndex) {
const nmCalculationWellRef& oWellRef = vecEffectiveWells[nIndex];
nmDataWellBase* pWellData =
pDataManager->findWellByCode(oWellRef.m_sWellCode);
if(pWellData == nullptr) {
QMessageBox::warning(this, tr("solver error"),
tr("A selected well is missing from the map."));
return;
}
if(oWellRef.m_eMode != NM_CaseWell_RateControlled) {
continue;
}
bHasRateControlledWell = true;
if(pWellData->getFlowPoints().size() < 2) {
QMessageBox::warning(
this,
tr("solver error"),
tr("Rate-controlled well '%1' has no valid rate schedule.")
.arg(pWellData->getWellName()));
return;
}
}
if(!bHasRateControlledWell) {
QMessageBox::warning(
this,
tr("solver error"),
tr("At least one active production or injection well is required."));
return;
}
// 手动求解可能直接启动, 因此必须在创建求解线程前初始化拟压力转换器.
// pSubWndFit 保存的是当前拟合窗口克隆后的 PVT、拟压力及模型选项数据.
// configPsAbouts() 会据此初始化常规、三区或组分转换器并设置当前模式.
@ -1552,7 +1609,7 @@ void nmSubWndMain::solveAndAnalyze()
QString sPostprocessingPath = "";
// 获取计算类型
NM_Grid_Type gridType = nmDataAnalyzeManager::getCurrentInstance()->getGridType();
NM_Grid_Type gridType = pDataManager->getGridType();
// PEBI求解直接使用当前后处理目录
if(gridType == NM_Grid_PEBI) {
@ -1571,11 +1628,15 @@ void nmSubWndMain::solveAndAnalyze()
m_pSolverTask = nullptr;
}
// 直接创建真正执行DLL计算的PEBI线程任务替代原来的中间转发层。
m_pSolverTask = new nmCalculationDllPebiSolverTask(sPostprocessingPath);
// 第一步:捕获本次求解的完整窗口上下文,完成回调不得再读取全局当前窗口。
m_pSolverDataManager = pDataManager;
m_pSolverFitting = pSubWndFit;
// 第二步:直接创建真正执行 DLL 计算的 PEBI 线程任务。
m_pSolverTask = new nmCalculationDllPebiSolverTask(sPostprocessingPath,
pDataManager);
s_pRunningSolverTask = m_pSolverTask;
connect(m_pSolverTask, SIGNAL(sig_calculateDone(bool)), this, SLOT(on_solverTaskFinished(bool)));
connect(m_pSolverTask, SIGNAL(finished()), m_pSolverTask, SLOT(deleteLater()));
// 1. 创建并配置进度对话框
if (m_pProgressDlg == nullptr)
@ -1583,7 +1644,9 @@ void nmSubWndMain::solveAndAnalyze()
// TODO:关联到父窗口
m_pProgressDlg = new QProgressDialog(tr("Calculating, please wait..."), QString(), 0, 100, getMainWindow());
m_pProgressDlg->setWindowTitle(tr("Solver Progress"));
m_pProgressDlg->setWindowModality(Qt::NonModal); // 模态,锁定父窗口
// 后台只读取任务值快照;应用级模态仍用于阻止重复启动、切换成果等会让
// 用户误判当前计算归属的操作,数据安全不再依赖该对话框冻结界面。
m_pProgressDlg->setWindowModality(Qt::ApplicationModal);
// 某些系统下,对话框右上角的关闭按钮可能仍存
// 使用 WindowFlags 彻底禁用关闭按钮
@ -1643,7 +1706,8 @@ void nmSubWndMain::mergeAnaResultToFitting()
return;
}
iSubWndFitting* pSubWndFit = nmDataAnalyzeManager::getCurrentFitting();
iSubWndFitting* pSubWndFit = !m_pSolverFitting.isNull()
? m_pSolverFitting.data() : nmDataAnalyzeManager::getCurrentFitting();
Q_ASSERT(pSubWndFit != nullptr);
if(pSubWndFit == nullptr) {
return;
@ -1656,53 +1720,64 @@ void nmSubWndMain::mergeAnaResultToFitting()
// 半对数数据
QVector<QVector<double >> vvecSemiLogPreData;
nmDataAnalyzeManager* pInstance = nmDataAnalyzeManager::getCurrentInstance();
nmDataAnalyzeManager* pInstance = !m_pSolverDataManager.isNull()
? m_pSolverDataManager.data()
: nmDataAnalyzeManager::getCurrentInstance();
if(pInstance == nullptr) {
return;
}
// 获取参与计算的井
QVector<QPair<NM_WELL_MODEL, QString>> vecWellsOrder = pInstance->getCalculationWells();
// 获取当前查看井的井名
nmDataWellBase* pCurWellData = pInstance->getCurWellData();
QString currentWellName;
if(pCurWellData != nullptr) {
currentWellName = pCurWellData->getWellName();
// 捕获的拟合窗口和数据管理器必须仍保持原来的映射关系。
if(nmDataAnalyzeManager::findManagerByFitting(pSubWndFit) != pInstance) {
return;
}
// 判断当前井是否参与计算
if(!pInstance->isContainsWellName(currentWellName)) {
for(int i = 0; i < vecWellsOrder.size(); i++) {
// 跳过裂缝
if(vecWellsOrder[i].first == NM_WELL_MODEL::Unknow_Well) {
continue;
}
QVector<nmSolverWellRef> vecWellsOrder = pInstance->getSolverWellOrder();
QString sCurrentResultWellCode = pInstance->getCurrentResultWellCode();
// 未参与计算,选择展示第一口计算井的数据
currentWellName = vecWellsOrder[i].second;
// 当前结果井必须同时位于本次求解顺序并具备结果展示资格。
// 无流量观察井虽然有内部压力结果,但不能成为曲线窗口的当前结果井。
const bool bResultWellAvailable =
pInstance->isWellAvailableForResult(sCurrentResultWellCode);
bool bResultWellFound = false;
for(int nIndex = 0; nIndex < vecWellsOrder.size(); ++nIndex) {
const nmSolverWellRef& oWellRef = vecWellsOrder[nIndex];
if(oWellRef.m_eEntryKind != NM_SolverEntry_Well) {
continue;
}
if(bResultWellAvailable &&
oWellRef.m_sWellCode == sCurrentResultWellCode) {
bResultWellFound = true;
break;
}
}
// PEBI结果按井名保存直接读取当前井的三条结果曲线
if(!bResultWellFound) {
for(int nIndex = 0; nIndex < vecWellsOrder.size(); ++nIndex) {
if(vecWellsOrder[nIndex].m_eEntryKind == NM_SolverEntry_Well &&
pInstance->isWellAvailableForResult(
vecWellsOrder[nIndex].m_sWellCode)) {
sCurrentResultWellCode = vecWellsOrder[nIndex].m_sWellCode;
break;
}
}
}
nmDataWellBase* pWellData = NULL;
if(pInstance->getGridType() == NM_Grid_PEBI) {
pWellData = pInstance->findWellByName(currentWellName);
pWellData = pInstance->findWellByCode(sCurrentResultWellCode);
if(pWellData == NULL) {
return;
}
// 同步当前查看井,保证保存成果时记录的是当前显示的结果井。
pInstance->setCurWellData(pWellData);
pInstance->setCurrentResultWellCode(sCurrentResultWellCode);
// 获取该井下存储的三条数据
vvecHistoryData = pWellData->getResultPressure();
vvecLogPreData = pWellData->getResultLogLog();
vvecSemiLogPreData = pWellData->getResultSemiLog();
}
// 计算完成后同步查看井下拉框选中项
nmWxNumericalDesign::notifyResultWellSelectorChanged(currentWellName);
nmWxNumericalDesign::notifyResultWellSelectorChanged(
pInstance,
sCurrentResultWellCode);
// 半对数
QVector<iCurveDesc> vecHalfLog;
@ -1733,7 +1808,7 @@ void nmSubWndMain::mergeAnaResultToFitting()
nmWxPostprocessingAnimationWidget* vtkWidget = nullptr;
if(pInstance->getGridType() == NM_Grid_PEBI) {
vtkWidget = new nmWxPostprocessingAnimationWidget(NULL);
vtkWidget = new nmWxPostprocessingAnimationWidget(NULL, pInstance);
}
if(widget1 == nullptr || vtkWidget == nullptr) {
@ -1978,6 +2053,13 @@ bool nmSubWndMain::initPreHistory(QVector<QVector<double> > &vvecHistoryData, QV
bool nmSubWndMain::onConfirmClosing()
{
// 正常关闭入口在求解结束前保持当前成果和 DataManager 存活;应用退出等绕过
// 该确认流程的路径仍由析构函数 wait() 兜底。
if(m_pSolverTask != nullptr && m_pSolverTask->isRunning()) {
QMessageBox::information(this, tr("solver error"), tr("task is running!"));
return false;
}
return iSubWndBaseAF::onConfirmClosing();
}
@ -2059,21 +2141,34 @@ void nmSubWndMain::on_calculationFinished(NM_Calculation_Result result)
void nmSubWndMain::on_solverTaskFinished(bool isSuccessed)
{
// 线程完成后清空两个持有点:当前窗口指针和全局运行中指针
// 第一步:只接受当前窗口实际启动的任务,忽略已经失效的旧回调
nmCalculationDllPebiSolverTask* pTask = qobject_cast<nmCalculationDllPebiSolverTask*>(sender());
if(pTask == nullptr || pTask != m_pSolverTask) {
return;
}
int nPebiCount = pTask != nullptr ? pTask->getPebiCount() : -1;
int nSolveTimeMs = pTask != nullptr ? pTask->getSolveTimeMs() : -1;
if(pTask == m_pSolverTask) {
m_pSolverTask = nullptr;
// 后台线程只生成局部结果快照。回到主线程后先验证所属 DataManager 和
// 输入版本,再整体替换旧成果;提交失败与求解失败使用同一条错误路径。
if(isSuccessed) {
isSuccessed = pTask->commitResult(m_pSolverDataManager.data());
}
m_pSolverTask = nullptr;
if(pTask == s_pRunningSolverTask) {
s_pRunningSolverTask = nullptr;
}
// 正常完成路径由主线程延迟释放任务;析构路径则会等待并直接释放。
pTask->deleteLater();
// 保持旧流程不变bool结果转换为计算结果枚举再交给原来的统一后处理函数。
// 第二步:在清空捕获上下文前完成结果合并,确保始终回写原拟合窗口
NM_Calculation_Result result = isSuccessed ? NM_Calculation_Result_Success : NM_Calculation_Result_Fail;
this->on_calculationFinished(result);
// 第三步:结果处理结束后释放非拥有型上下文引用。
m_pSolverDataManager = nullptr;
m_pSolverFitting = nullptr;
if(isSuccessed && nPebiCount >= 0 && nSolveTimeMs >= 0) {
QMessageBox::information(this, tr("Solver completed"),
tr("Grid count: %1\nSolve time: %2 ms")
@ -2247,31 +2342,20 @@ void nmSubWndMain::onProgressUpdated(int progress)
}
}
void nmSubWndMain::onWellSelected(const QString& wellName)
void nmSubWndMain::onWellSelected(const QString& sWellCode)
{
nmDataAnalyzeManager* pInstance = nmDataAnalyzeManager::getCurrentInstance();
if(pInstance == nullptr) {
return;
}
// 更新数据中心的当前查看井数据
nmDataWellBase* pCurWellData = pInstance->getCurWellData();
if(pCurWellData == nullptr)
return;
// 查找切换后的井数据
nmDataWellBase* pWellData = NULL;
pWellData = pInstance->findWellByName(wellName);
nmDataWellBase* pWellData = pInstance->findWellByCode(sWellCode);
if(pWellData == NULL)
return;
if(pCurWellData->getWellName() != wellName) {
// 设置为当前查看井数据
pInstance->setCurWellData(pWellData);
}
// 切换结果井只影响曲线展示,不再改变参数编辑井或主分析井。
pInstance->setCurrentResultWellCode(sWellCode);
// 1.获取流动段分析窗口
iSubWndFitting* pSubWndFit = nmDataAnalyzeManager::getCurrentFitting();
@ -2437,18 +2521,9 @@ void nmSubWndMain::onWellSelected(const QString& wellName)
void nmSubWndMain::onGenerateButtonClicked()
{
//QString sDir = ZxBaseUtil::getCurWellDirOf("Nm/PreProcessing");
if(nmDataAnalyzeManager::getCurrentInstance()->getGridType() == NM_Grid_PEBI) {
nmCalculationPebiGrid* pPebiGridGeneratorInstance = nmCalculationPebiGrid::getInstance();
if(pPebiGridGeneratorInstance->getGridOutput2().Trinodexy.size() <= 0) {
// 没有可用PEBI网格时才重新生成
if(!pPebiGridGeneratorInstance->meshGenPebi()) {
return;
}
}
// 求解计算
// 求解任务创建时会捕获完整输入。网格无效时,工作线程只基于值快照生成
// 一次局部网格,求解成功后再和全部结果一起回主线程提交。
this->solveAndAnalyze();
}
}

@ -61,9 +61,8 @@ void nmSubWndSubWxsAnalyzeController::updateWindowsForCurrentAnalyze(ZxMainWindo
nmSubWndGrid* pSubWndGrid = dynamic_cast<nmSubWndGrid*>(pSub);
if(nullptr != pSubWndGrid) {
QObject::disconnect(pManager, SIGNAL(dataChanged()), pSubWndGrid, SLOT(updateGrid()));
QObject::connect(pManager, SIGNAL(dataChanged()), pSubWndGrid, SLOT(updateGrid()), Qt::QueuedConnection);
pSubWndGrid->updateGrid();
// 网格窗口负责切换所属数据管理器,并丢弃切换前任务的过期结果。
pSubWndGrid->setDataManager(pManager);
}
}
}

@ -1,4 +1,4 @@
#include "zxLogInstance.h"
#include "zxLogInstance.h"
#include "iRibbonXmlCmd.h"
#include <QEvent>
#include <QMdiArea>
@ -217,9 +217,17 @@ iSubWnd* nmSubWndUtils::makesureSubWnd(QString sID, QString sExt, \
}
// 创建PEBI网格窗口新建数据时生成网格加载数据时直接展示已加载结果。
nmSubWndGrid* pSubWndGrid = new nmSubWndGrid(NULL, sExt, dPebiGridControl, bGeneratePebiGrid);
disconnect(nmDataAnalyzeManager::getCurrentInstance(), SIGNAL(dataChanged()), pSubWndGrid, SLOT(updateGrid()));
connect(nmDataAnalyzeManager::getCurrentInstance(), SIGNAL(dataChanged()), pSubWndGrid, SLOT(updateGrid()), Qt::QueuedConnection);
nmDataAnalyzeManager* pDataManager =
nmDataAnalyzeManager::getCurrentInstance();
if(pDataManager == nullptr) {
return NULL;
}
if(!bGeneratePebiGrid) {
dPebiGridControl = pDataManager->getPebiGridControl();
}
nmSubWndGrid* pSubWndGrid = new nmSubWndGrid(
NULL, sExt, dPebiGridControl, bGeneratePebiGrid, pDataManager);
pSubWnd = pSubWndGrid;
} else if(nID == 5116) {
// 后处理
@ -403,7 +411,11 @@ void nmSubWndUtils::setupNumericalDockWidgets(nmDataAnalyzeManager* pDataManager
pWxDockNm2->setWindowTitle(tr("Parameters"));
// 参数面板绑定数据管理器后再重建,确保首次显示使用当前分析数据。
nmWxNumericalDesign* pAnalWx = new nmWxNumericalDesign();
// Dock首次创建时保证时间步配置存在已有成果中的配置保持不变。
if(pDataManager->getTimeStep() == nullptr) {
pDataManager->createTimeStep();
}
nmWxNumericalDesign* pAnalWx = new nmWxNumericalDesign(pDataManager);
nmWxParaPropertyPebi* pParaWx = new nmWxParaPropertyPebi();
pParaWx->initUI();
pParaWx->setDataManager(pDataManager);
@ -509,7 +521,7 @@ bool nmSubWndUtils::fillNmDockWxs(iSubWnd* pSubWnd)
pDataManager->initCurWellData();
// 注册井参数到 nmAttrRegistry必须在 initCurWellData 之后,面板构建之前)
pDataManager->syncWellAttrs();
// 1.4 初始化边界数据
// 1.4 初始化边界数据;其他项目井只通过 Map 的“选择井”菜单按需加入。
nmDataWellBase* pCurWell = pDataManager->getCurWellData();
if(pCurWell == nullptr) {
return showNmDockInitializationError(
@ -882,7 +894,9 @@ bool nmSubWndUtils::loadRsts(iSubWnd* pSubWnd, \
// 加载当前分析中的PVT数据
pDataManager->initPvtParaFromSubFit();
// 加载本地文件的数据到数据中心里的数据体里含用户选择的模型类型覆盖PVT推导结果
pDataManager->loadNmResult(sDir);
if(!pDataManager->loadNmResult(sDir)) {
return false;
}
// 注册井参数到 nmAttrRegistry必须在 loadNmResult 之后,面板构建之前)
pDataManager->syncWellAttrs();
@ -917,7 +931,8 @@ bool nmSubWndUtils::loadRsts(iSubWnd* pSubWnd, \
// 创建 vtkWidget
nmWxPostprocessingAnimationWidget* pVtkWidget = nullptr;
pVtkWidget = new nmWxPostprocessingAnimationWidget();
pVtkWidget = new nmWxPostprocessingAnimationWidget(
nullptr, pDataManager);
if(p3DWidget == nullptr || pVtkWidget == nullptr) {
delete pVtkWidget;
@ -1127,15 +1142,12 @@ void nmSubWndUtils::slotHandleTabChange(int index)
pMap->updateMapByDataManager(pDataManger);
}
// TODO网格更新
// 网格窗口跟随当前分析切换数据来源。
nmSubWndGrid* pSubWndGrid = dynamic_cast<nmSubWndGrid*>(pSub);
if(nullptr != pSubWndGrid) {
// 更新网格
disconnect(pDataManger, SIGNAL(dataChanged()), pSubWndGrid, SLOT(updateGrid()));
connect(pDataManger, SIGNAL(dataChanged()), pSubWndGrid, SLOT(updateGrid()), Qt::QueuedConnection);
// 手动更新
pSubWndGrid->updateGrid();
// 由网格窗口统一解除旧连接、绑定新分析并处理旧后台任务。
pSubWndGrid->setDataManager(pDataManger);
}
}
}

@ -1,4 +1,4 @@
#include "nmWxGridVTKContainerWidget.h"
#include "nmWxGridVTKContainerWidget.h"
// Qt 核心头文件
#include <QVBoxLayout>
#include <QFrame>
@ -52,7 +52,11 @@ VTK_MODULE_INIT(vtkInteractionStyle)
#pragma comment(lib,"AdvAPI32.lib")
#pragma comment(lib,"shell32.lib")
nmWxGridVTKContainerWidget::nmWxGridVTKContainerWidget(QWidget *parent) : QWidget(parent)
nmWxGridVTKContainerWidget::nmWxGridVTKContainerWidget(
nmDataAnalyzeManager* pDataManager,
QWidget *parent)
: QWidget(parent),
m_pDataManager(pDataManager)
{
m_pVtkWidget = nullptr;
this->initLayout();
@ -218,21 +222,19 @@ bool nmWxGridVTKContainerWidget::eventFilter(QObject *pObj, QEvent *event)
void nmWxGridVTKContainerWidget::initVTKWidget()
{
// 从数据中心获取当前非结构化网格对象
nmDataAnalyzeManager* pCurDataManager = nmDataAnalyzeManager::getCurrentInstance();
Q_ASSERT(nullptr != pCurDataManager);
vtkSmartPointer<vtkUnstructuredGrid> pGrid = pCurDataManager->getUnstructuredGrid();
Q_ASSERT(nullptr != pGrid);
// 创建自定义 nmWxQVTKWidget 的实例。
// 此部件将处理其自身的上下文菜单事件。
// 第一步:无论网格是否有效都先建立稳定的 VTK 容器,避免失败状态改变布局尺寸。
m_pVtkWidget = new QVTKWidget(this); // 'this' 是 QVTKWidget 的父部件。
m_pMainLayout->addWidget(m_pVtkWidget); // 将 VTK 部件添加到 m_pMainLayout 的布局中。
// 安装事件过滤器
m_pVtkWidget->installEventFilter(this);
// 创建映射器
// 第二步:只读取构造时绑定的数据管理器,窗口切换不能改变当前网格来源。
vtkSmartPointer<vtkUnstructuredGrid> pGrid = m_pDataManager != nullptr
? m_pDataManager->getUnstructuredGrid() : nullptr;
if(pGrid == nullptr || pGrid->GetNumberOfPoints() == 0) {
return;
}
// 第三步:有效网格才继续创建映射器、颜色表和渲染器。
vtkSmartPointer<vtkDataSetMapper> pMapper = vtkSmartPointer<vtkDataSetMapper>::New();
pMapper->SetInputData(pGrid); // 传入非结构化网格

@ -1,17 +1,30 @@
#include "nmWxIncludeOtherWells.h"
#include "ZxDataWell.h"
#include "ZxDataProject.h"
#include "zxSysUtils.h"
#include "ZxDataGaugeP.h"
#include "ZxDataGaugeF.h"
#include "ZxBaseUtil.h"
#include "nmWxIncludeOtherWells.h"
#include "nmDataAnalyzeManager.h"
#include "nmDataWellBase.h"
#include <QCheckBox>
#include <QWidget>
#include <QHBoxLayout>
nmWxIncludeOtherWells::nmWxIncludeOtherWells(QWidget* pParent)
: iDlgBase(pParent), m_pTableWidget(nullptr),
namespace
{
// Include 对话框只展示当前 PEBI 网格能够表达的真实井型。
bool isSupportedNumericalWell(nmDataWellBase* pWellData)
{
if(pWellData == nullptr || pWellData->getWellCode().isEmpty()) {
return false;
}
const NM_WELL_MODEL eWellType = pWellData->getWellType();
return eWellType == NM_WELL_MODEL::Vertical_Well ||
eWellType == NM_WELL_MODEL::Vertical_Fractured_Well ||
eWellType == NM_WELL_MODEL::Horizontal_Fractured_Well;
}
}
nmWxIncludeOtherWells::nmWxIncludeOtherWells(
nmDataAnalyzeManager* pDataManager,
QWidget* pParent)
: iDlgBase(pParent), m_pDataManager(pDataManager), m_pTableWidget(nullptr),
m_pOkButton(nullptr), m_pCancelButton(nullptr),
m_pButtonLayout(nullptr), m_pMainLayout(nullptr)
{
@ -38,14 +51,14 @@ void nmWxIncludeOtherWells::setupTable()
{
m_pTableWidget = new QTableWidget(this);
ZxDataObjectList wellList;
if(zxCurProject != nullptr) {
wellList = zxCurProject->getChildren(iDataModelType::sTypeWell);
QVector<nmDataWellBase*> vecMapWells;
if(m_pDataManager != nullptr) {
vecMapWells = m_pDataManager->getWellDataList();
}
// 设置行列数
m_pTableWidget->setColumnCount(6);
m_pTableWidget->setRowCount(wellList.size());
m_pTableWidget->setRowCount(vecMapWells.size());
m_pTableWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
m_pTableWidget->setSelectionMode(QAbstractItemView::SingleSelection);
@ -77,43 +90,45 @@ void nmWxIncludeOtherWells::setupTable()
m_pTableWidget->setVerticalHeaderLabels(emptyRowHeaders);
// 加载数据
QString currentWell;
nmDataWellBase* pCurrentWell = nmDataAnalyzeManager::getCurrentInstance()->getCurWellData();
if (pCurrentWell != nullptr)
{
currentWell = pCurrentWell->getWellName();
}
QString sPrimaryWellCode = m_pDataManager != nullptr
? m_pDataManager->getPrimaryWellCode() : QString();
int displayRow = 0;
for(int i = 0; i < wellList.size(); i++) {
ZxDataWell* pWellObj = dynamic_cast<ZxDataWell*>(wellList[i]);
if(!pWellObj || pWellObj->getName() == currentWell) {
// 第一步:候选来源是 Map而不是整个项目井库。该对话框只管理
// 其他有产量的生产/注入井;主井和无产量观察井都不在这里显示。
for(int i = 0; i < vecMapWells.size(); i++) {
nmDataWellBase* pWell = vecMapWells[i];
if(pWell == nullptr || !isSupportedNumericalWell(pWell)) {
continue;
}
WellDataRow wellRow;
wellRow.sName = pWellObj->getName();
// 查看数据中心是否已经添加到里面
nmDataWellBase* pWell =nmDataAnalyzeManager::getCurrentInstance()->findWellByName(pWellObj->getName());
if (pWell != nullptr) {
wellRow.bIsIncluded = true;
} else {
wellRow.bIsIncluded = false;
const bool bIsPrimary = pWell->getWellCode() == sPrimaryWellCode;
const bool bHasValidRate = pWell->getFlowPoints().size() >= 2;
const bool bHasValidPressure = pWell->getPressurePoints().size() >= 2;
if(bIsPrimary || !bHasValidRate) {
continue;
}
// 获取压力数据
ZxDataObjectList gaugePList = pWellObj->getChildren(iDataModelType::sTypeDataGaugeP);
wellRow.sPressure = gaugePList.isEmpty() ? "None" : "History";
// 获取流量数据
ZxDataObjectList gaugeFList = pWellObj->getChildren(iDataModelType::sTypeDataGaugeF);
wellRow.sOilProd = gaugeFList.isEmpty() ? "None" : "Rate";
WellDataRow wellRow;
wellRow.m_sWellCode = pWell->getWellCode();
wellRow.m_sName = pWell->getWellName();
wellRow.m_bIsPrimary = bIsPrimary;
// 第二步:读取这口有产量干扰井上次确认的包含状态。
wellRow.m_bCanCalculate = true;
wellRow.m_bIsIncluded =
m_pDataManager != nullptr &&
m_pDataManager->getNumericalAnalysisCase()->isIncludedWell(
wellRow.m_sWellCode);
// 第三步IncludedWells 中的候选固定是有产量主动井。
wellRow.m_sPressure = bHasValidPressure ? "History" : "None";
wellRow.m_sOilProd = "Rate";
wellRow.m_eMode = NM_CaseWell_RateControlled;
// 气相和水相暂时设为None
wellRow.sGasProd = "None";
wellRow.sWaterProd = "None";
wellRow.m_sGasProd = "None";
wellRow.m_sWaterProd = "None";
// 添加到数据列表和表格
m_wellData.append(wellRow);
@ -129,9 +144,9 @@ void nmWxIncludeOtherWells::setupTable()
void nmWxIncludeOtherWells::addWellDataToTable(const WellDataRow& data, int row)
{
// Name列 - 不可编辑
QTableWidgetItem* nameItem = new QTableWidgetItem(data.sName);
QTableWidgetItem* nameItem = new QTableWidgetItem(data.m_sName);
nameItem->setFlags(nameItem->flags() & ~Qt::ItemIsEditable);
nameItem->setData(Qt::UserRole, row); // 存储数据索引
nameItem->setData(Qt::UserRole, data.m_sWellCode); // 表格内部始终携带 WellCode
m_pTableWidget->setItem(row, 0, nameItem);
// Included列 - 使用自定义复选框组件
@ -143,8 +158,9 @@ void nmWxIncludeOtherWells::addWellDataToTable(const WellDataRow& data, int row)
pLayout->setContentsMargins(0, 0, 0, 0);
pWidget->setLayout(pLayout);
pCheckBox->setChecked(data.bIsIncluded);
pCheckBox->setEnabled(data.hasValidData());
pCheckBox->setChecked(data.m_bIsIncluded);
// 表内只有其他有产量井,用户可以决定它是否作为干扰井参与计算。
pCheckBox->setEnabled(data.canCalculate());
pCheckBox->setProperty("row", row); // 存储行索引
// 连接复选框信号
@ -153,12 +169,12 @@ void nmWxIncludeOtherWells::addWellDataToTable(const WellDataRow& data, int row)
// 设置单元格控件
m_pTableWidget->setCellWidget(row, 1, pWidget);
// 其他列 - 不可编辑
// 其他列只显示各类 simulation target与 Saphir 对话框保持一致。
QStringList listOtherData;
listOtherData.append(data.sOilProd);
listOtherData.append(data.sGasProd);
listOtherData.append(data.sWaterProd);
listOtherData.append(data.sPressure);
listOtherData.append(data.m_sOilProd);
listOtherData.append(data.m_sGasProd);
listOtherData.append(data.m_sWaterProd);
listOtherData.append(data.m_sPressure);
for(int col = 2; col < 6; col++) {
QTableWidgetItem* item = new QTableWidgetItem(listOtherData[col-2]);
@ -171,149 +187,24 @@ void nmWxIncludeOtherWells::addWellDataToTable(const WellDataRow& data, int row)
void nmWxIncludeOtherWells::onCheckBoxStateChanged(int state)
{
QCheckBox* pCheckBox = qobject_cast<QCheckBox*>(sender());
if (!pCheckBox) return;
int row = pCheckBox->property("row").toInt();
if (row >= 0 && row < m_wellData.size())
{
m_wellData[row].bIsIncluded = (state == Qt::Checked);
// 选中当前行
m_pTableWidget->selectRow(row);
// 如果选中了井,并且井有有效数据,则进行处理
if (state == Qt::Checked && m_wellData[row].hasValidData())
{
/*
QString wellName = m_wellData[row].sName;
// 1. 获取 ZxDataWell 对象
ZxDataObjectList wellList = zxCurProject->getChildren(iDataModelType::sTypeWell);
ZxDataWell* pZxWell = nullptr;
for(int i = 0; i < wellList.size(); i++)
{
ZxDataWell* pWell = dynamic_cast<ZxDataWell*>(wellList[i]);
if(pWell && pWell->getName() == wellName)
{
pZxWell = pWell;
break;
}
}
if(!pZxWell)
{
return;
}
// 2. 检查数据中心是否已经存在该井
nmDataAnalyzeManager* pManager = nmDataAnalyzeManager::getCurrentInstance();
nmDataWellBase* pExistingWell = pManager->findWellByName(wellName);
if(pExistingWell != nullptr)
{
// 井已存在,无需重复处理
return;
}
// 3. 创建对应的 nmDataWellBase 对象
QString wellClass = pZxWell->getWellClass();
nmDataWellBase* pNewWell = nullptr;
// 根据井类型创建对应的井对象
if(ZxBaseUtil::isSameStr(wellClass, "VerticalWell")) {
pNewWell = pManager->createWell(NM_WELL_MODEL::Vertical_Well);
} else if(ZxBaseUtil::isSameStr(wellClass, "VerticalFracturedWell")) {
pNewWell = pManager->createWell(NM_WELL_MODEL::Vertical_Fractured_Well);
} else if(ZxBaseUtil::isSameStr(wellClass, "HorizontalFracturedWell")
|| ZxBaseUtil::isSameStr(wellClass, "HorizontalMultiFracturedWell")) {
pNewWell = pManager->createWell(NM_WELL_MODEL::Horizontal_Fractured_Well);
}
if(!pNewWell) {
return;
}
// 4. 设置井的基本属性
pNewWell->setWellName(pZxWell->getName());
nmDataAttribute tempAttr = pNewWell->getX();
tempAttr.setValue(pZxWell->getLocationX());
pNewWell->setX(tempAttr);
tempAttr = pNewWell->getY();
tempAttr.setValue(pZxWell->getLocationY());
pNewWell->setY(tempAttr);
tempAttr = pNewWell->getRadius();
tempAttr.setValue(pZxWell->getWellRadius());
pNewWell->setRadius(tempAttr);
// 5. 获取并设置压力、流量数据
QVector<QPointF> vecPtsP, vecPtsF;
// 获取压力数据
ZxDataObjectList m_listGaugeP = pZxWell->getChildren(iDataModelType::sTypeDataGaugeP);
if(!m_listGaugeP.isEmpty()) {
ZxDataGaugeP* pGaugeP = dynamic_cast<ZxDataGaugeP*>(m_listGaugeP[0]);
if(pGaugeP) {
VecDouble vecX, vecY;
if(pGaugeP->getDataVecXY(vecX, vecY)) {
for(int i = 0; i < vecX.size() && i < vecY.size(); ++i) {
vecPtsP.append(QPointF(vecX[i], vecY[i]));
}
}
}
}
// 获取流量数据
ZxDataObjectList m_listGaugeF = pZxWell->getChildren(iDataModelType::sTypeDataGaugeF);
if(!m_listGaugeF.isEmpty()) {
ZxDataGaugeF* pGaugeF = dynamic_cast<ZxDataGaugeF*>(m_listGaugeF[0]);
if(pGaugeF) {
VecDouble vecX, vecY;
if(pGaugeF->getDataVecXY(vecX, vecY)) {
for(int i = 0; i < vecX.size() && i < vecY.size(); ++i) {
vecPtsF.append(QPointF(vecX[i], vecY[i]));
}
}
}
}
// 设置压力和流量数据
pNewWell->setPressurePoints(vecPtsP);
pNewWell->setFlowPoints(vecPtsF);
int nIndexF; //当前井的流动段索引
// 更新当前井的流动段的索引,默认为最后一段
nIndexF = vecPtsF.count() - 1;
// 设置流量段索引
pNewWell->setIndexF(nIndexF);
// 6. 如果是裂缝井,设置裂缝信息
if(wellClass.contains("Fractured")) {
nmDataVerticalFracturedWell* pVFWell = dynamic_cast<nmDataVerticalFracturedWell*>(pNewWell);
if(pVFWell) {
pVFWell->setFracs();
}
nmDataHorizontalFracturedWell* pHFWell = dynamic_cast<nmDataHorizontalFracturedWell*>(pNewWell);
if(pHFWell) {
pHFWell->setFracs();
}
}
// 7. 调用 calculationLogData 计算历史数据
QVector<QVector<double>> vvecHistoryPressureData;
QVector<QVector<double>> vvecHistoryLogData;
QVector<QVector<double>> vvecHistorySemiLogData;
pManager->calculationLogData(pNewWell, vvecHistoryPressureData, vvecHistoryLogData, vvecHistorySemiLogData);
// 8. 存储历史数据到井对象
pNewWell->setHistoryPressure(vvecHistoryPressureData);
pNewWell->setHistoryLogLog(vvecHistoryLogData);
pNewWell->setHistorySemiLog(vvecHistorySemiLogData);
*/
}
if(pCheckBox == nullptr) {
return;
}
const int nRow = pCheckBox->property("row").toInt();
if(nRow < 0 || nRow >= m_wellData.size()) {
return;
}
// 主井正常不会进入本表;保留该判断作为防御,避免未来改动误取消主井。
if(m_wellData[nRow].m_bIsPrimary) {
m_wellData[nRow].m_bIsIncluded = true;
return;
}
// 其他候选井只记录选择状态,绝不在该对话框内创建或删除 Map 井。
m_wellData[nRow].m_bIsIncluded = (state == Qt::Checked);
m_pTableWidget->selectRow(nRow);
}
void nmWxIncludeOtherWells::onItemClicked(QTableWidgetItem* item)

File diff suppressed because it is too large Load Diff

@ -1,4 +1,4 @@
#include "nmWxPostprocessingAnimationWidget.h"
#include "nmWxPostprocessingAnimationWidget.h"
#include <QDebug> // 用于调试输出
#include <algorithm> // 用于 std::sort 排序算法
@ -152,8 +152,13 @@ void nmContourResultDialog::showResult(const QString& message)
this->show(); // 使用 show() 来显示非模态对话框
}
nmWxPostprocessingAnimationWidget::nmWxPostprocessingAnimationWidget(QWidget* parent) :
QWidget(parent) // 调用 QWidget 基类的构造函数
nmWxPostprocessingAnimationWidget::nmWxPostprocessingAnimationWidget(
QWidget* pParent,
nmDataAnalyzeManager* pDataManager) :
QWidget(pParent),
m_pDataManager(pDataManager != nullptr
? pDataManager
: nmDataAnalyzeManager::getCurrentInstance())
{
// 初始化成员变量
m_nCurrentIndex = 0;
@ -454,7 +459,7 @@ void nmWxPostprocessingAnimationWidget::initVTKWidget()
m_lookupTable->Build(); // 构建颜色表
// 4. 获取基础结果网格数据
nmDataAnalyzeManager* pDataManager = nmDataAnalyzeManager::getCurrentInstance();
nmDataAnalyzeManager* pDataManager = m_pDataManager.data();
if(pDataManager) {
//m_pCachedBaseGrid = pDataManager->getResultBaseGridCopy();
@ -570,19 +575,22 @@ void nmWxPostprocessingAnimationWidget::initWellDrawing()
{
clearWellActors(); // 确保每次初始化时清空旧的 Actor避免重复添加
nmDataAnalyzeManager* pDataManager = nmDataAnalyzeManager::getCurrentInstance();
nmDataAnalyzeManager* pDataManager = m_pDataManager.data();
if(!pDataManager) {
return;
}
// 获取所有井的二维位置信息 (QMap<井名, QPointF(X,Y)>)
// 获取所有井的二维位置信息,映射键为 WellCode。
QMap<QString, QPointF> mapWellLocations = pDataManager->getAllWellLocations();
// 遍历所有井,创建并配置它们的 3D 文本和线 Actor
for(auto it = mapWellLocations.constBegin(); it != mapWellLocations.constEnd(); ++it) {
const QString& sWellName = it.key();
const QString& sWellCode = it.key();
const QPointF& ptLocation = it.value(); // 井的二维位置 (X, Y)
nmDataWellBase* pWellData = pDataManager->findWellByCode(sWellCode);
const QString sWellName = pWellData != nullptr
? pWellData->getWellName() : sWellCode;
// === 1. 创建井名称的 3D 文本 Actor ===
// 使用 vtkBillboardTextActor3D它始终面向相机文字可读性最好且是 3D 对象。
@ -598,7 +606,7 @@ void nmWxPostprocessingAnimationWidget::initWellDrawing()
textActor->SetVisibility(m_bShowWellActors ? 1 : 0);
m_renderer->AddActor(textActor); // 对于所有 3D Actor都使用 m_renderer->AddActor()
m_mapWellNameActors.insert(sWellName, textActor); // 将 Actor 存储在 QMap 中,以便后续管理和清除
m_mapWellNameActors.insert(sWellCode, textActor); // Actor内部按WellCode管理文本仍显示井名
// === 2. 绘制井的线 (为每口井绘制,基于 wellLocations 的 X, Y并延伸到 WELL_DRAWING_Z_HEIGHT) ===
// 创建点集合
@ -634,7 +642,8 @@ void nmWxPostprocessingAnimationWidget::initWellDrawing()
lineActor->SetVisibility(m_bShowWellActors ? 1 : 0);
m_renderer->AddActor(lineActor); // 将 3D Actor 添加到渲染器
m_mapWellLineActors.insert(sWellName, lineActor); // 存储井线 Actor
// 井名允许修改或重复,井线 Actor 必须和其余求解结果一样按 WellCode 管理。
m_mapWellLineActors.insert(sWellCode, lineActor);
}
// 强制渲染以显示新增的 Actor
@ -768,7 +777,7 @@ void nmWxPostprocessingAnimationWidget::initOperPannel()
// 初始化时间步列表,从 nmDataAnalyzeManager 单例获取数据
void nmWxPostprocessingAnimationWidget::initTimeSteps()
{
nmDataAnalyzeManager* pDataManager = nmDataAnalyzeManager::getCurrentInstance(); // 获取数据管理器单例实例
nmDataAnalyzeManager* pDataManager = m_pDataManager.data();
if(!pDataManager) {
return;
@ -789,7 +798,7 @@ void nmWxPostprocessingAnimationWidget::loadDataForIndex(int index)
return;
}
nmDataAnalyzeManager* pDataManager = nmDataAnalyzeManager::getCurrentInstance(); // 获取数据管理器单例实例
nmDataAnalyzeManager* pDataManager = m_pDataManager.data();
if(!pDataManager) return;
@ -1309,7 +1318,7 @@ void nmWxPostprocessingAnimationWidget::exportAnimationAsVideo()
}
// 获取数据管理器实例。
nmDataAnalyzeManager* pDataManager = nmDataAnalyzeManager::getCurrentInstance();
nmDataAnalyzeManager* pDataManager = m_pDataManager.data();
if(pDataManager) {
// 获取当前时间步的数据。

@ -1,4 +1,4 @@
#include "nmWxSelectWellsWidget.h"
#include "nmWxSelectWellsWidget.h"
#include <QLineEdit>
#include <QCheckBox>
#include <QEvent>
@ -45,13 +45,13 @@ nmWxSelectWellsWidget::nmWxSelectWellsWidget(QWidget* aParent) :
void nmWxSelectWellsWidget::addItems(QStringList items, QList<QVariant> values)
{
if (m_items.count() > values.count()) {
if(items.count() != values.count()) {
return;
}
for (int i = 0; i < items.count(); i++) {
for(int i = 0; i < items.count(); i++) {
this->addItem(items[i], values[i]);
this->onUpdateSelections();
}
this->onUpdateSelections();
}
void nmWxSelectWellsWidget::addItem(QString name, QVariant value)
@ -61,7 +61,7 @@ void nmWxSelectWellsWidget::addItem(QString name, QVariant value)
nmWxSelectWellItemWidget* itemWidget = new nmWxSelectWellItemWidget(NULL, name, value);
itemWidget->setFixedHeight(25);
itemWidget->setFixedWidth(200);
if (m_selectItems.contains(name)) {
if(m_selectedValues.contains(value) || m_selectItems.contains(name)) {
itemWidget->setSelected(true);
}
// itemWidget->setMinimumWidth(m_pListWidget->viewport()->width()); // 设置widget的最小宽度为列表视图的宽度
@ -85,6 +85,33 @@ void nmWxSelectWellsWidget::setSelectedItems(QStringList selectItems)
m_selectItems = selectItems;
}
QList<QVariant> nmWxSelectWellsWidget::getSelectedValues() const
{
QList<QVariant> listSelectedValues;
// 选择结果按左侧列表顺序返回,调用方只使用业务值,不再反查显示名称。
int nIndex = 0;
const int nItemCount = qMin(m_pListWidget->count(), m_values.count());
while(nIndex < nItemCount) {
QListWidgetItem* pListItem = m_pListWidget->item(nIndex);
nmWxSelectWellItemWidget* pItemWidget =
static_cast<nmWxSelectWellItemWidget*>(
m_pListWidget->itemWidget(pListItem));
if(pItemWidget != NULL && pItemWidget->getSelected()) {
listSelectedValues.append(m_values[nIndex]);
}
++nIndex;
}
return listSelectedValues;
}
void nmWxSelectWellsWidget::setSelectedValues(
const QList<QVariant>& selectedValues)
{
m_selectedValues = selectedValues;
}
void nmWxSelectWellsWidget::onUpdateSelections()
{
// 如果由井项点击触发则记录点击位置并处理Shift范围选择
@ -102,13 +129,22 @@ void nmWxSelectWellsWidget::onUpdateSelections()
}
QStringList nameList;
QList<QVariant> listSelectedValues;
// 遍历所有的itemWidget获取所有被选中的信息
for (int i = 0; i < m_pListWidget->count(); i++) {
nmWxSelectWellItemWidget* itemWidget = static_cast<nmWxSelectWellItemWidget*>(m_pListWidget->itemWidget(m_pListWidget->item(i)));
if (itemWidget->getSelected()) {
nameList.append(m_items[i]);
int nIndex = 0;
while(nIndex < m_pListWidget->count()) {
QListWidgetItem* pListItem = m_pListWidget->item(nIndex);
nmWxSelectWellItemWidget* pItemWidget =
static_cast<nmWxSelectWellItemWidget*>(
m_pListWidget->itemWidget(pListItem));
if(pItemWidget != NULL && pItemWidget->getSelected()) {
nameList.append(m_items[nIndex]);
listSelectedValues.append(m_values[nIndex]);
}
++nIndex;
}
m_selectItems = nameList;
m_selectedValues = listSelectedValues;
m_pLineEdit->setPlainText(nameList.join("\n"));
}

Loading…
Cancel
Save