feat(numerical): 建立 PEBI 结果快照数据模型与渲染基线

- 为井对象增加稳定 UUID,并校验井型替换时的身份一致性
- 新增结果快照、Builder 及完整性校验
- 增加 Manager 快照发布、查询和清理接口
- 新增受控 VTK 共享与复制渲染适配器
- 补充 PVT 结构校验及快照模型测试
feature/pebi-result-snapshot-20260831
lh 2 weeks ago
parent 9df3da7b7c
commit 0086e07d88

@ -21,6 +21,7 @@
#include <QString>
#include <QList>
#include <QMutex>
#include <QSharedPointer>
#include <QWaitCondition>
#include <vector>
@ -143,6 +144,8 @@ class iSubWndFitting;
class nmDataTimeStepSetting;
class nmDataSensitive;
class nmAttrRegistry;
class nmPebiResultSnapshot;
struct nmPebiResultWellSnapshot;
class NM_DATA_EXPORT nmDataAnalyzeManager : public ZxDataObjectBin
{
@ -253,7 +256,7 @@ public:
/**
* @brief
* @param pOldWell
* @param pNewWell
* @param pNewWell UUID
* @return true
*/
bool replaceWellData(nmDataWellBase* pOldWell,
@ -520,6 +523,28 @@ public:
/** @brief 判断是否存在一套可保存和查看的 PEBI 结果。 */
bool hasPebiResults() const;
/** @brief 返回并行接入的最后成功结果快照,不改变旧结果流程。 */
QSharedPointer<const nmPebiResultSnapshot>
getPebiResultSnapshot() const;
/** @brief 是否已发布完整结果快照。 */
bool hasPebiResultSnapshot() const;
/** @brief 快照的网格和求解输入版本是否仍与当前输入一致。 */
bool isPebiResultSnapshotCurrent() const;
/** @brief 返回快照查看状态中的当前结果井 UUID。 */
QString getCurrentResultWellInstanceId() const;
/** @brief 按快照 UUID 切换结果井,空值表示选择第一口可显示井。 */
bool setCurrentResultWellInstanceId(
const QString& sWellInstanceId);
/**
* @brief 线
* @note
*/
bool commitPebiResultSnapshot(
QSharedPointer<nmPebiResultSnapshot>& pCandidate,
QString* pError = NULL);
/** @brief 清除并行快照及其结果井查看状态。 */
void clearPebiResultSnapshot();
/** @brief 设置结果查看井;不改变主分析井和参数编辑井。 */
void setCurrentResultWellCode(const QString& sWellCode);
/** @brief 返回结果查看井 WellCode。 */
@ -758,6 +783,8 @@ public:
/** @brief 当前分析最后一次排队的 PEBI 建网流程已经结束。 */
void sigPebiGridGenerationFinished(quint64 nGridInputRevision,
bool bSucceeded);
/** @brief 并行结果快照或其结果井选择已经改变。 */
void sigPebiResultSnapshotChanged();
private:
/** @brief 创建本数据管理器的成果窗口;只用于读取本成果上下文,不拥有对象。 */
@ -847,6 +874,11 @@ public:
// 当前数值分析方案:保存选井、井角色、结果井和网格产生的求解器顺序。
nmDataNumericalAnalysisCase m_oNumericalAnalysisCase;
/** @brief 最后一次成功结果;第二阶段与旧结果载荷并行存在。 */
QSharedPointer<const nmPebiResultSnapshot> m_pPebiResultSnapshot;
/** @brief 快照查看状态中当前选择的结果井 UUID。 */
QString m_sCurrentResultWellInstanceId;
// 当前查看的是哪一口井的数据,也是哪一口测试井
nmDataWellBase* m_pCurDataWell;

@ -59,6 +59,13 @@ public:
void setWellCode(const QString& code);
QString getWellCode() const;
/** @brief 返回井对象的稳定实例标识,与可编辑井编码相互独立。 */
QString getWellInstanceId() const;
/** @brief v3 加载或井型替换时恢复已有实例标识。 */
bool restoreWellInstanceId(const QString& sWellInstanceId);
/** @brief 为新建井生成不带花括号的 UUID。 */
static QString createWellInstanceId();
void setX(const nmDataAttribute& attr);
nmDataAttribute& getX(); // 修改为返回引用
@ -224,6 +231,8 @@ public:
bool exportResultPressureToCsv(const QString& outDir);
protected:
/** @brief 井实例 UUID井型替换时保持不变删除重建时必须变化。 */
QString m_sWellInstanceId;
QString m_wellName; // 井名
QString m_wellCode; // 井编码项目级唯一标识来自ZxDataWell::getCode()

@ -0,0 +1,349 @@
#ifndef NMPEBIRESULTSNAPSHOT_H
#define NMPEBIRESULTSNAPSHOT_H
#include "nmData_global.h"
#include "nmDataNumericalAnalysisCase.h"
#include <QPointF>
#include <QSharedPointer>
#include <QString>
#include <QStringList>
#include <QVector>
#include <vtkSmartPointer.h>
#include <vtkType.h>
class vtkDoubleArray;
class vtkUnstructuredGrid;
class nmPebiResultSnapshotBuilder;
class nmPebiResultSnapshotRenderAdapter;
class nmPebiResultSnapshotSerializer;
/** @brief 一条求解器槽位在最后成功结果中的冻结描述。 */
struct NM_DATA_EXPORT nmPebiResultSolverSlot
{
/** @brief 初始化为未分配的真实井槽位。 */
nmPebiResultSolverSlot();
/** @brief 求解器中的连续槽位下标。 */
int m_nSolverIndex;
/** @brief 槽位是实际井还是手工裂缝。 */
NM_SOLVER_ENTRY_KIND m_eEntryKind;
/** @brief 实际井 UUID手工裂缝槽位必须为空。 */
QString m_sWellInstanceId;
/** @brief 求解时冻结的井编码。 */
QString m_sWellCode;
/** @brief 求解时冻结的井模型类型。 */
NM_WELL_MODEL m_eWellType;
};
/** @brief 一口结果井的历史曲线和计算曲线。 */
struct NM_DATA_EXPORT nmPebiResultWellCurves
{
// 二维容器沿用现有绘图数据约定:每个子数组是一条数据序列,
// 同一组曲线内各序列点数必须一致,快照不改变序列含义和单位。
/** @brief 求解时冻结的历史压力曲线数据序列组。 */
QVector<QVector<double> > m_vecHistoryPressure;
/** @brief 求解时冻结的历史双对数曲线数据序列组。 */
QVector<QVector<double> > m_vecHistoryLogLog;
/** @brief 求解时冻结的历史半对数曲线数据序列组。 */
QVector<QVector<double> > m_vecHistorySemiLog;
/** @brief 最后成功求解的压力曲线数据序列组。 */
QVector<QVector<double> > m_vecResultPressure;
/** @brief 最后成功求解的双对数曲线数据序列组。 */
QVector<QVector<double> > m_vecResultLogLog;
/** @brief 最后成功求解的半对数曲线数据序列组。 */
QVector<QVector<double> > m_vecResultSemiLog;
};
/** @brief 一口结果井在求解成功时冻结的完整值数据。 */
struct NM_DATA_EXPORT nmPebiResultWellSnapshot
{
/** @brief 初始化空的结果井元数据。 */
nmPebiResultWellSnapshot();
/** @brief 求解时井对象的稳定 UUID。 */
QString m_sWellInstanceId;
/** @brief 求解时冻结的井编码。 */
QString m_sWellCode;
/** @brief 求解时冻结的井名称。 */
QString m_sWellName;
/** @brief 求解时冻结的井模型类型。 */
NM_WELL_MODEL m_eWellType;
/** @brief 求解时冻结的井角色。 */
NM_CASE_WELL_MODE m_eWellMode;
/** @brief 求解时冻结的井平面位置。 */
QPointF m_oLocation;
/** @brief 求解时是否存在射孔数据。 */
bool m_bHasPerforation;
/** @brief 求解时是否启用表皮参数。 */
bool m_bHasSkin;
/** @brief 求解时是否启用裂缝导流能力参数。 */
bool m_bHasDfc;
/** @brief 求解时井筒半径。 */
double m_dRadius;
/** @brief 求解时井筒储集系数。 */
double m_dWellboreStorage;
/** @brief 求解时表皮系数。 */
double m_dSkin;
/** @brief 求解时裂缝导流能力。 */
double m_dDfc;
/** @brief 求解时冻结的历史曲线和计算曲线。 */
nmPebiResultWellCurves m_oCurves;
};
/** @brief 结果参数页使用的求解时储层参数。 */
struct NM_DATA_EXPORT nmPebiResultReservoirParameters
{
/** @brief 初始化空的储层参数。 */
nmPebiResultReservoirParameters();
/** @brief 初始地层压力 Pi。 */
double m_dInitialPressure;
/** @brief 储层渗透率 K。 */
double m_dPermeability;
/** @brief 储层有效厚度 h。 */
double m_dThickness;
/** @brief 储层孔隙度 phi。 */
double m_dPorosity;
/** @brief 总压缩系数 Cti。 */
double m_dTotalCompressibility;
/** @brief 岩石压缩系数 Cf。 */
double m_dRockCompressibility;
/** @brief 初始含油饱和度 Soi。 */
double m_dOilSaturation;
/** @brief 初始含气饱和度 Sgi。 */
double m_dGasSaturation;
/** @brief 初始含水饱和度 Swi。 */
double m_dWaterSaturation;
};
/** @brief 求解时使用的常量 PVT 和全部变化 PVT 数组。 */
struct NM_DATA_EXPORT nmPebiResultPvtParameters
{
/** @brief 初始化空的 PVT 参数。 */
nmPebiResultPvtParameters();
/** @brief 是否保存了有效泡点压力。 */
bool m_bHasBubblePoint;
/** @brief 求解时泡点压力。 */
double m_dBubblePoint;
/** @brief 求解时常量原油体积系数 Bo。 */
double m_dConstantBo;
/** @brief 求解时常量原油黏度 Miuo。 */
double m_dConstantMiuo;
/** @brief 求解时常量天然气体积系数 Bg。 */
double m_dConstantBg;
/** @brief 求解时常量天然气黏度 Miug。 */
double m_dConstantMiug;
/** @brief 求解时常量地层水体积系数 Bw。 */
double m_dConstantBw;
/** @brief 求解时常量地层水黏度 Miuw。 */
double m_dConstantMiuw;
/** @brief 变化 PVT 的压力采样数组。 */
QVector<double> m_vecPressure;
/** @brief 溶解气油比 Rso 数组。 */
QVector<double> m_vecRso;
/** @brief 原油体积系数 Bo 数组。 */
QVector<double> m_vecBo;
/** @brief 原油压缩系数 Co 数组。 */
QVector<double> m_vecCo;
/** @brief 原油黏度 Miuo 数组。 */
QVector<double> m_vecMiuo;
/** @brief 原油密度 Rouo 数组。 */
QVector<double> m_vecRouo;
/** @brief 挥发油气比 Rv 数组。 */
QVector<double> m_vecRv;
/** @brief 天然气体积系数 Bg 数组。 */
QVector<double> m_vecBg;
/** @brief 天然气压缩系数 Cg 数组。 */
QVector<double> m_vecCg;
/** @brief 天然气黏度 Miug 数组。 */
QVector<double> m_vecMiug;
/** @brief 天然气密度 Roug 数组。 */
QVector<double> m_vecRoug;
/** @brief 天然气偏差因子 Z 数组。 */
QVector<double> m_vecZ;
/** @brief 地层水溶解气比 Rsw 数组。 */
QVector<double> m_vecRsw;
/** @brief 地层水体积系数 Bw 数组。 */
QVector<double> m_vecBw;
/** @brief 地层水压缩系数 Cw 数组。 */
QVector<double> m_vecCw;
/** @brief 地层水黏度 Miuw 数组。 */
QVector<double> m_vecMiuw;
/** @brief 地层水密度 Rouw 数组。 */
QVector<double> m_vecRouw;
/** @brief PVT 原始 V 数组。 */
QVector<double> m_vecV;
/** @brief PVT 原始 KkInitial 数组。 */
QVector<double> m_vecKkInitial;
/** @brief PVT 原始 CfCfInitial 数组。 */
QVector<double> m_vecCfCfInitial;
/** @brief 油相饱和度 So 数组。 */
QVector<double> m_vecSo;
/** @brief 油相相对渗透率 Kro 数组。 */
QVector<double> m_vecKro;
/** @brief 气相饱和度 Sg 数组。 */
QVector<double> m_vecSg;
/** @brief 气相相对渗透率 Krg 数组。 */
QVector<double> m_vecKrg;
/** @brief 水相饱和度 Sw 数组。 */
QVector<double> m_vecSw;
/** @brief 水相相对渗透率 Krw 数组。 */
QVector<double> m_vecKrw;
};
/** @brief 结果页面和追溯所需的求解时设置。 */
struct NM_DATA_EXPORT nmPebiResultSolverSettings
{
/** @brief 初始化空的求解设置。 */
nmPebiResultSolverSettings();
/** @brief 求解时流体模型类型。 */
int m_nSolverModelType;
/** @brief 求解时 PEBI 线性求解器类型。 */
int m_nPebiSolverType;
/** @brief 求解时 OpenMP 线程数。 */
int m_nOmpThreads;
/** @brief 求解时 ILU 预条件复用步数。 */
int m_nIluReuseSteps;
/** @brief 求解时网格控制值。 */
double m_dGridControl;
/** @brief 是否保存了时间步控制设置。 */
bool m_bHasTimeStepSettings;
/** @brief 时间步增长指数。 */
double m_dTimeGrowthExponent;
/** @brief 最小时间步长。 */
double m_dMinDeltaT;
/** @brief 最大时间步长。 */
double m_dMaxDeltaT;
};
/**
* @brief PEBI
*
*
* VTK VTK
*/
class NM_DATA_EXPORT nmPebiResultSnapshot
{
public:
/** @brief 释放快照独占的网格、压力帧和冻结结果数据。 */
~nmPebiResultSnapshot();
/** @brief 返回快照 UUID。 */
QString getSnapshotId() const;
/** @brief 返回求解候选使用的网格输入版本。 */
quint64 getGridInputRevision() const;
/** @brief 返回求解候选使用的求解输入版本。 */
quint64 getResultInputRevision() const;
/** @brief 返回快照是否已通过 Builder 的完整校验。 */
bool isComplete() const;
/** @brief 返回完整求解器槽位数量。 */
int getSolverSlotCount() const;
/** @brief 按槽位下标返回只读描述,越界时返回 NULL。 */
const nmPebiResultSolverSlot* getSolverSlotAt(int nIndex) const;
/** @brief 返回快照结果井数量。 */
int getWellCount() const;
/** @brief 按存储下标返回只读结果井,越界时返回 NULL。 */
const nmPebiResultWellSnapshot* getWellAt(int nIndex) const;
/** @brief 按井 UUID 查找只读结果井,不存在时返回 NULL。 */
const nmPebiResultWellSnapshot* findWell(
const QString& sWellInstanceId) const;
/** @brief 返回结果井下拉框使用的有序 UUID 列表。 */
const QStringList& getDisplayWellInstanceIds() const;
/** @brief 判断井 UUID 是否属于可显示结果井列表。 */
bool isDisplayWell(const QString& sWellInstanceId) const;
/** @brief 返回结果基础网格单元数。 */
vtkIdType getResultCellCount() const;
/** @brief 返回压力时间步数量。 */
int getPressureFrameCount() const;
/** @brief 返回指定压力帧的时间,越界时返回 0。 */
double getPressureTimeAt(int nIndex) const;
/** @brief 返回全部压力帧的全局标量范围。 */
bool getScalarRange(double aRange[2]) const;
/** @brief 返回求解时冻结的储层参数。 */
const nmPebiResultReservoirParameters& getReservoirParameters() const;
/** @brief 返回求解时冻结的完整 PVT 参数。 */
const nmPebiResultPvtParameters& getPvtParameters() const;
/** @brief 返回求解时冻结的求解设置。 */
const nmPebiResultSolverSettings& getSolverSettings() const;
private:
struct PressureFrame
{
/** @brief 初始化空压力帧。 */
PressureFrame();
/** @brief 该压力帧对应的求解时间。 */
double m_dTime;
/** @brief 快照规范持有的单分量单元压力数组。 */
vtkSmartPointer<vtkDoubleArray> m_pPressure;
};
/** @brief 仅允许 Builder 创建未完成候选。 */
nmPebiResultSnapshot();
/** @brief 禁止复制,避免无意深拷贝大网格和全部压力帧。 */
nmPebiResultSnapshot(const nmPebiResultSnapshot&);
/** @brief 禁止赋值,避免无意复制大结果载荷。 */
nmPebiResultSnapshot& operator=(const nmPebiResultSnapshot&);
/**
* @brief CellData
* @note VTK 7.1 CopyStructure
*/
bool copyResultGridStructure(vtkUnstructuredGrid* pTargetGrid) const;
/**
* @brief
* @note VTK
*/
bool bindPressureFrame(int nIndex,
vtkUnstructuredGrid* pTargetGrid) const;
/** @brief 将一帧压力复制到适配器复用的缓冲数组。 */
bool copyPressureFrame(int nIndex,
vtkDoubleArray* pTargetPressure) const;
private:
// 网格与压力分开保存,避免基础网格通过 CellData 暴露可变压力数组。
/** @brief 快照 UUID。 */
QString m_sSnapshotId;
/** @brief 生成结果时使用的网格输入版本。 */
quint64 m_nGridInputRevision;
/** @brief 生成结果时使用的求解输入版本。 */
quint64 m_nResultInputRevision;
/** @brief 是否已由 Builder 完整校验并封闭。 */
bool m_bComplete;
/** @brief 不含压力 p 数组和活动标量的结果基础网格。 */
vtkSmartPointer<vtkUnstructuredGrid> m_pResultGrid;
/** @brief 按时间严格递增保存的全部压力帧。 */
QVector<PressureFrame> m_vecPressureFrames;
/** @brief 全部压力帧中的全局最小值。 */
double m_dScalarMin;
/** @brief 全部压力帧中的全局最大值。 */
double m_dScalarMax;
/** @brief 完整且连续的求解器槽位顺序。 */
QVector<nmPebiResultSolverSlot> m_vecSolverSlots;
/** @brief 求解时冻结的全部结果井。 */
QVector<nmPebiResultWellSnapshot> m_vecWells;
/** @brief 结果井下拉框使用的有序井 UUID 子集。 */
QStringList m_listDisplayWellInstanceIds;
/** @brief 求解时冻结的储层参数。 */
nmPebiResultReservoirParameters m_oReservoirParameters;
/** @brief 求解时冻结的完整 PVT 参数。 */
nmPebiResultPvtParameters m_oPvtParameters;
/** @brief 求解时冻结的求解设置。 */
nmPebiResultSolverSettings m_oSolverSettings;
// 只有 Builder 和后续 v3 序列化器可以在发布前填充私有数据。
friend class nmPebiResultSnapshotBuilder;
friend class nmPebiResultSnapshotRenderAdapter;
friend class nmPebiResultSnapshotSerializer;
};
#endif // NMPEBIRESULTSNAPSHOT_H

@ -0,0 +1,90 @@
#ifndef NMPEBIRESULTSNAPSHOTBUILDER_H
#define NMPEBIRESULTSNAPSHOTBUILDER_H
#include "nmData_global.h"
#include "nmPebiResultSnapshot.h"
#include <QSharedPointer>
#include <QString>
/**
* @brief PEBI
*
* Builder finalize()
*
*/
class NM_DATA_EXPORT nmPebiResultSnapshotBuilder
{
public:
/** @brief 创建一个新的可写候选;内存不足时进入失败状态。 */
nmPebiResultSnapshotBuilder();
/** @brief 销毁尚未被 takeCandidate() 转交的候选。 */
~nmPebiResultSnapshotBuilder();
/** @brief v3 加载时恢复已保存的快照 UUID求解候选可使用自动生成值。 */
bool setSnapshotId(const QString& sSnapshotId);
/** @brief 设置候选创建时对应的网格输入版本和求解输入版本。 */
bool setInputRevisions(quint64 nGridInputRevision,
quint64 nResultInputRevision);
/** @brief 成功后接管网格且清空调用方智能指针,不执行 DeepCopy。 */
bool takeResultGrid(
vtkSmartPointer<vtkUnstructuredGrid>& pResultGrid);
/** @brief 成功后接管压力数组且清空调用方智能指针,不复制整帧数据。 */
bool addPressureFrame(
double dTime,
vtkSmartPointer<vtkDoubleArray>& pPressure);
/** @brief 设置全部压力帧的全局最小值和最大值。 */
bool setScalarRange(double dMinimum, double dMaximum);
/** @brief 按求解器顺序追加一个冻结槽位。 */
bool addSolverSlot(const nmPebiResultSolverSlot& oSlot);
/** @brief 追加一口包含元数据和曲线的冻结结果井。 */
bool addWell(const nmPebiResultWellSnapshot& oWell);
/** @brief 设置结果井下拉框使用的有序井 UUID 列表。 */
bool setDisplayWellInstanceIds(const QStringList& listWellInstanceIds);
/** @brief 设置求解时冻结的储层参数。 */
bool setReservoirParameters(
const nmPebiResultReservoirParameters& oParameters);
/** @brief 设置求解时冻结的常量 PVT 和完整变化 PVT 数组。 */
bool setPvtParameters(const nmPebiResultPvtParameters& oParameters);
/** @brief 设置求解时冻结的网格、时间步和求解器配置。 */
bool setSolverSettings(const nmPebiResultSolverSettings& oSettings);
/** @brief 完成全部结构校验并封闭候选,失败后该 Builder 不可继续使用。 */
bool finalize(QString* pError = NULL);
/**
* @brief Builder
* @note const
*/
QSharedPointer<nmPebiResultSnapshot> takeCandidate();
/** @brief 返回最近一次失败原因;尚未失败时返回空字符串。 */
QString getLastError() const;
private:
/** @brief 判断候选是否仍允许写入,并在不可写时记录错误。 */
bool canWrite();
/** @brief 将 Builder 永久置为失败状态并输出错误信息。 */
bool fail(const QString& sError, QString* pError = NULL);
/** @brief 校验候选全部结构,并移除基础网格的压力标量。 */
bool validate(QString* pError);
private:
// 这些标志区分“字段未设置”和“字段合法地等于默认值”。
/** @brief Builder 独占的可写候选。 */
QSharedPointer<nmPebiResultSnapshot> m_pCandidate;
/** @brief 候选是否已经成功封闭。 */
bool m_bFinalized;
/** @brief Builder 是否已经发生不可恢复的错误。 */
bool m_bFailed;
/** @brief 调用方是否显式设置过标量范围。 */
bool m_bHasScalarRange;
/** @brief 调用方是否显式设置过储层参数。 */
bool m_bHasReservoirParameters;
/** @brief 调用方是否显式设置过 PVT 参数。 */
bool m_bHasPvtParameters;
/** @brief 调用方是否显式设置过求解设置。 */
bool m_bHasSolverSettings;
/** @brief 最近一次失败的英文内部诊断文本。 */
QString m_sLastError;
};
#endif // NMPEBIRESULTSNAPSHOTBUILDER_H

@ -0,0 +1,83 @@
#ifndef NMPEBIRESULTSNAPSHOTRENDERADAPTER_H
#define NMPEBIRESULTSNAPSHOTRENDERADAPTER_H
#include "nmData_global.h"
#include "nmPebiResultSnapshot.h"
#include <QSharedPointer>
#include <vtkSmartPointer.h>
#include <vtkType.h>
class vtkAlgorithmOutput;
class vtkDoubleArray;
class vtkTrivialProducer;
class vtkUnstructuredGrid;
/**
* @brief VTK
*
* UI UI
*
*/
class NM_DATA_EXPORT nmPebiResultSnapshotRenderAdapter
{
public:
/** @brief 压力帧与渲染网格之间的绑定方式。 */
enum PressureBindingMode
{
SharedPressureFrame = 0, ///< 临时共享快照压力数组,不产生整帧复制。
CopiedPressureFrame = 1 ///< 复制到可复用缓冲,隔离不可信下游流程。
};
/** @brief 创建尚未绑定快照的渲染适配器。 */
nmPebiResultSnapshotRenderAdapter();
/** @brief 解除 VTK 临时引用并释放适配器持有的只读快照。 */
~nmPebiResultSnapshotRenderAdapter();
/**
* @brief CellData
* @param pSnapshot
* @param eBindingMode
*/
bool initialize(
const QSharedPointer<const nmPebiResultSnapshot>& pSnapshot,
PressureBindingMode eBindingMode);
/** @brief 解除输出、压力数组和快照引用,使适配器恢复为空状态。 */
void clear();
/** @brief 将指定压力帧发布到当前 VTK 输出端口。 */
bool setPressureFrame(int nFrameIndex);
/** @brief 返回供 Mapper 或过滤器建立连接的 VTK 输出端口。 */
vtkAlgorithmOutput* getOutputPort() const;
/** @brief 返回当前已经绑定的压力帧下标,尚未切帧时为 -1。 */
int getCurrentFrameIndex() const;
/** @brief 返回适配器渲染网格的单元数。 */
vtkIdType getCellCount() const;
/** @brief 返回当前是否使用零整帧复制的共享压力模式。 */
bool isUsingSharedPressure() const;
private:
/** @brief 禁止复制包含 VTK 管道和快照引用的适配器。 */
nmPebiResultSnapshotRenderAdapter(
const nmPebiResultSnapshotRenderAdapter&);
/** @brief 禁止给适配器赋值。 */
nmPebiResultSnapshotRenderAdapter& operator=(
const nmPebiResultSnapshotRenderAdapter&);
private:
/** @brief 当前渲染使用的只读快照。 */
QSharedPointer<const nmPebiResultSnapshot> m_pSnapshot;
/** @brief 适配器独占的渲染网格,结构可与快照基础网格共享。 */
vtkSmartPointer<vtkUnstructuredGrid> m_pRenderGrid;
/** @brief 复制模式复用的单帧压力缓冲。 */
vtkSmartPointer<vtkDoubleArray> m_pPressureBuffer;
/** @brief 将独立渲染网格接入 VTK 管道的生产者。 */
vtkSmartPointer<vtkTrivialProducer> m_pProducer;
/** @brief 当前压力帧绑定方式。 */
PressureBindingMode m_eBindingMode;
/** @brief 当前已经绑定的压力帧下标。 */
int m_nCurrentFrameIndex;
};
#endif // NMPEBIRESULTSNAPSHOTRENDERADAPTER_H

@ -58,12 +58,12 @@ private:
NM_WELL_MODEL eTargetType) const;
/** @brief 用共享字段和兼容的射孔业务数据初始化目标井。 */
void initializeWorkingWell(nmDataWellBase* pSharedSource,
bool initializeWorkingWell(nmDataWellBase* pSharedSource,
nmDataWellBase* pPerforationSource,
nmDataWellBase* pTarget) const;
/** @brief 白名单复制跨井型语义一致的数据,不复制井型专属几何。 */
void copySharedWellData(nmDataWellBase* pSource,
bool copySharedWellData(nmDataWellBase* pSource,
nmDataWellBase* pTarget) const;
/**

@ -1,6 +1,7 @@
#include "nmDataAnalyzeManager.h"
#include "nmAttrRegistry.h"
#include "nmPebiResultSnapshot.h"
#include "nmDataAnalyzeContext.h"
#include "nmDataAnalyzeContextProvider.h"
@ -14,6 +15,7 @@
#include "ZxDataWell.h"
#include "ZxBaseUtil.h"
#include <QSet>
#include <QThread>
#include "zxLogInstance.h"
#include "nmDataReservoir.h"
@ -901,6 +903,10 @@ void nmDataAnalyzeManager::clearAllWellData()
typedef QPair<QString, QString> WellIdentity;
QList<WellIdentity> removedWells;
// 全量清空表示当前分析上下文被丢弃;先发出快照清除通知,
// 此时实时井对象仍然有效,直接连接的 UI 刷新不会遇到悬空井指针。
clearPebiResultSnapshot();
// 遍历并删除所有井对象,兼容旧逻辑可能遗留的重复指针。
QSet<nmDataWellBase*> deletedWells;
foreach(nmDataWellBase* pWell, m_vWellData) {
@ -1632,7 +1638,10 @@ bool nmDataAnalyzeManager::replaceWellData(nmDataWellBase* pOldWell,
if (pOldWell == nullptr || pNewWell == nullptr ||
pOldWell == pNewWell || m_vWellData.contains(pNewWell) ||
pOldWell->getWellCode().isEmpty() ||
pOldWell->getWellCode() != pNewWell->getWellCode())
pOldWell->getWellCode() != pNewWell->getWellCode() ||
pOldWell->getWellInstanceId().isEmpty() ||
pOldWell->getWellInstanceId() !=
pNewWell->getWellInstanceId())
{
return false;
}
@ -3700,6 +3709,139 @@ bool nmDataAnalyzeManager::hasPebiResults() const
return m_oNumericalAnalysisCase.hasResults();
}
QSharedPointer<const nmPebiResultSnapshot>
nmDataAnalyzeManager::getPebiResultSnapshot() const
{
return m_pPebiResultSnapshot;
}
bool nmDataAnalyzeManager::hasPebiResultSnapshot() const
{
return !m_pPebiResultSnapshot.isNull();
}
bool nmDataAnalyzeManager::isPebiResultSnapshotCurrent() const
{
return !m_pPebiResultSnapshot.isNull() &&
m_pPebiResultSnapshot->getGridInputRevision() ==
m_oNumericalAnalysisCase.getGridInputRevision() &&
m_pPebiResultSnapshot->getResultInputRevision() ==
m_oNumericalAnalysisCase.getResultInputRevision();
}
QString nmDataAnalyzeManager::getCurrentResultWellInstanceId() const
{
return m_sCurrentResultWellInstanceId;
}
bool nmDataAnalyzeManager::setCurrentResultWellInstanceId(
const QString& sWellInstanceId)
{
// 结果井选择属于快照查看状态,不能反向修改当前分析主井。
if(m_pPebiResultSnapshot.isNull())
{
if(!sWellInstanceId.isEmpty())
{
return false;
}
m_sCurrentResultWellInstanceId.clear();
return true;
}
QString sValidatedWellInstanceId = sWellInstanceId;
if(sValidatedWellInstanceId.isEmpty())
{
const QStringList& listDisplayWellIds =
m_pPebiResultSnapshot->getDisplayWellInstanceIds();
sValidatedWellInstanceId = listDisplayWellIds.isEmpty()
? QString() : listDisplayWellIds.first();
}
if(!sValidatedWellInstanceId.isEmpty() &&
!m_pPebiResultSnapshot->isDisplayWell(sValidatedWellInstanceId))
{
return false;
}
if(m_sCurrentResultWellInstanceId == sValidatedWellInstanceId)
{
return true;
}
m_sCurrentResultWellInstanceId = sValidatedWellInstanceId;
emit sigPebiResultSnapshotChanged();
return true;
}
bool nmDataAnalyzeManager::commitPebiResultSnapshot(
QSharedPointer<nmPebiResultSnapshot>& pCandidate,
QString* pError)
{
if(pError != NULL)
{
pError->clear();
}
QString sError;
// 发布只能发生在 Manager 所属线程,避免工作线程直接替换 UI 正在读取的引用。
if(QThread::currentThread() != thread())
{
sError = "PEBI result snapshot must be committed on its owner thread.";
}
else if(pCandidate.isNull() || !pCandidate->isComplete())
{
sError = "PEBI result snapshot candidate is incomplete.";
}
else if(pCandidate->getGridInputRevision() !=
m_oNumericalAnalysisCase.getGridInputRevision() ||
pCandidate->getResultInputRevision() !=
m_oNumericalAnalysisCase.getResultInputRevision())
{
sError = "PEBI result snapshot input revisions are stale.";
}
if(!sError.isEmpty())
{
// 提交失败也要消除可写别名,但旧快照和旧结果井选择保持不变。
pCandidate.clear();
if(pError != NULL)
{
*pError = sError;
}
return false;
}
QSharedPointer<const nmPebiResultSnapshot> pPublishedCandidate(
pCandidate);
// 新结果仍包含旧选择时保持用户上下文,否则退到第一口可显示井。
QString sNextWellInstanceId = m_sCurrentResultWellInstanceId;
if(!pPublishedCandidate->isDisplayWell(sNextWellInstanceId))
{
const QStringList& listDisplayWellIds =
pPublishedCandidate->getDisplayWellInstanceIds();
sNextWellInstanceId = listDisplayWellIds.isEmpty()
? QString() : listDisplayWellIds.first();
}
// 发布点只替换只读引用;先前快照由仍在查看它的窗口延迟释放。
m_pPebiResultSnapshot = pPublishedCandidate;
m_sCurrentResultWellInstanceId = sNextWellInstanceId;
pCandidate.clear();
emit sigPebiResultSnapshotChanged();
return true;
}
void nmDataAnalyzeManager::clearPebiResultSnapshot()
{
if(m_pPebiResultSnapshot.isNull() &&
m_sCurrentResultWellInstanceId.isEmpty())
{
return;
}
m_pPebiResultSnapshot.clear();
m_sCurrentResultWellInstanceId.clear();
emit sigPebiResultSnapshotChanged();
}
void nmDataAnalyzeManager::setCurrentResultWellCode(
const QString& sWellCode)
{
@ -4446,6 +4588,9 @@ bool nmDataAnalyzeManager::ReadProjectData(const QString & filePath)
return false;
}
// 前置校验已完成,从这里开始替换当前项目;旧项目快照不能跨项目残留。
clearPebiResultSnapshot();
// 清空现有数据,确保从头加载新数据
foreach(nmDataFracture* pFractureData, m_vFractureData) {
delete pFractureData; // 释放堆上分配的 nmDataFracture 对象

@ -7,6 +7,7 @@
#include <QTextStream>
#include <QCoreApplication>
#include <QDateTime>
#include <QUuid>
nmDataWellBase::nmDataWellBase()
: m_nIndexF(0)
@ -16,6 +17,7 @@ nmDataWellBase::nmDataWellBase()
, m_dLastWellLength(0.0)
, m_pReservoir(nullptr)
{
m_sWellInstanceId = createWellInstanceId();
m_pReservoir = nmDataAnalyzeManager::getCurrentInstance()->getReservoirData();
m_wellName = "";
m_x = nmDataAttribute("X", 0.0, "m", UNIT_TYPE_LENGTH, QStringList(), QStringList() << "m" << "cm" << "mm" << "in" << "0.1 in" << "ft" << "mile" << "km");
@ -96,6 +98,7 @@ nmDataWellBase::~nmDataWellBase()
nmDataWellBase& nmDataWellBase::operator=(const nmDataWellBase& other)
{
if(this != &other) {
m_sWellInstanceId = other.m_sWellInstanceId;
m_wellName = other.m_wellName;
m_wellCode = other.m_wellCode;
m_x = other.m_x;
@ -473,6 +476,29 @@ QString nmDataWellBase::getWellCode() const
return m_wellCode;
}
QString nmDataWellBase::getWellInstanceId() const
{
return m_sWellInstanceId;
}
bool nmDataWellBase::restoreWellInstanceId(
const QString& sWellInstanceId)
{
QUuid oUuid(sWellInstanceId);
if(sWellInstanceId.isEmpty() || oUuid.isNull()) {
return false;
}
m_sWellInstanceId = oUuid.toString().remove('{').remove('}');
return true;
}
QString nmDataWellBase::createWellInstanceId()
{
// UUID 只表示井对象身份;删除后新建的同名或同编码井必须获得新值。
return QUuid::createUuid().toString().remove('{').remove('}');
}
void nmDataWellBase::setX(const nmDataAttribute& attr)
{
m_x = attr;

@ -0,0 +1,275 @@
#include "nmPebiResultSnapshot.h"
#include <vtkCellData.h>
#include <vtkDoubleArray.h>
#include <vtkUnstructuredGrid.h>
#include <QUuid>
// VTK 结构和压力操作仅供渲染适配器调用,普通结果读取接口不暴露可写对象。
nmPebiResultSolverSlot::nmPebiResultSolverSlot()
: m_nSolverIndex(-1),
m_eEntryKind(NM_SolverEntry_Well),
m_eWellType(Unknow_Well)
{
}
nmPebiResultWellSnapshot::nmPebiResultWellSnapshot()
: m_eWellType(Unknow_Well),
m_eWellMode(NM_CaseWell_Observation),
m_bHasPerforation(false),
m_bHasSkin(false),
m_bHasDfc(false),
m_dRadius(0.0),
m_dWellboreStorage(0.0),
m_dSkin(0.0),
m_dDfc(0.0)
{
}
nmPebiResultReservoirParameters::nmPebiResultReservoirParameters()
: m_dInitialPressure(0.0),
m_dPermeability(0.0),
m_dThickness(0.0),
m_dPorosity(0.0),
m_dTotalCompressibility(0.0),
m_dRockCompressibility(0.0),
m_dOilSaturation(0.0),
m_dGasSaturation(0.0),
m_dWaterSaturation(0.0)
{
}
nmPebiResultPvtParameters::nmPebiResultPvtParameters()
: m_bHasBubblePoint(false),
m_dBubblePoint(0.0),
m_dConstantBo(0.0),
m_dConstantMiuo(0.0),
m_dConstantBg(0.0),
m_dConstantMiug(0.0),
m_dConstantBw(0.0),
m_dConstantMiuw(0.0)
{
}
nmPebiResultSolverSettings::nmPebiResultSolverSettings()
: m_nSolverModelType(0),
m_nPebiSolverType(0),
m_nOmpThreads(0),
m_nIluReuseSteps(0),
m_dGridControl(0.0),
m_bHasTimeStepSettings(false),
m_dTimeGrowthExponent(0.0),
m_dMinDeltaT(0.0),
m_dMaxDeltaT(0.0)
{
}
nmPebiResultSnapshot::PressureFrame::PressureFrame()
: m_dTime(0.0)
{
}
nmPebiResultSnapshot::nmPebiResultSnapshot()
: m_sSnapshotId(QUuid::createUuid().toString()
.remove('{').remove('}')),
m_nGridInputRevision(0),
m_nResultInputRevision(0),
m_bComplete(false),
m_dScalarMin(0.0),
m_dScalarMax(0.0)
{
}
nmPebiResultSnapshot::~nmPebiResultSnapshot()
{
}
QString nmPebiResultSnapshot::getSnapshotId() const
{
return m_sSnapshotId;
}
quint64 nmPebiResultSnapshot::getGridInputRevision() const
{
return m_nGridInputRevision;
}
quint64 nmPebiResultSnapshot::getResultInputRevision() const
{
return m_nResultInputRevision;
}
bool nmPebiResultSnapshot::isComplete() const
{
return m_bComplete;
}
int nmPebiResultSnapshot::getSolverSlotCount() const
{
return m_vecSolverSlots.size();
}
const nmPebiResultSolverSlot* nmPebiResultSnapshot::getSolverSlotAt(
int nIndex) const
{
if(nIndex < 0 || nIndex >= m_vecSolverSlots.size())
{
return NULL;
}
return &m_vecSolverSlots[nIndex];
}
int nmPebiResultSnapshot::getWellCount() const
{
return m_vecWells.size();
}
const nmPebiResultWellSnapshot* nmPebiResultSnapshot::getWellAt(
int nIndex) const
{
if(nIndex < 0 || nIndex >= m_vecWells.size())
{
return NULL;
}
return &m_vecWells[nIndex];
}
const nmPebiResultWellSnapshot* nmPebiResultSnapshot::findWell(
const QString& sWellInstanceId) const
{
for(int nIndex = 0; nIndex < m_vecWells.size(); ++nIndex)
{
if(m_vecWells[nIndex].m_sWellInstanceId == sWellInstanceId)
{
return &m_vecWells[nIndex];
}
}
return NULL;
}
const QStringList& nmPebiResultSnapshot::getDisplayWellInstanceIds() const
{
return m_listDisplayWellInstanceIds;
}
bool nmPebiResultSnapshot::isDisplayWell(
const QString& sWellInstanceId) const
{
return m_listDisplayWellInstanceIds.contains(sWellInstanceId);
}
vtkIdType nmPebiResultSnapshot::getResultCellCount() const
{
return m_pResultGrid != NULL ? m_pResultGrid->GetNumberOfCells() : 0;
}
int nmPebiResultSnapshot::getPressureFrameCount() const
{
return m_vecPressureFrames.size();
}
double nmPebiResultSnapshot::getPressureTimeAt(int nIndex) const
{
if(nIndex < 0 || nIndex >= m_vecPressureFrames.size())
{
return 0.0;
}
return m_vecPressureFrames[nIndex].m_dTime;
}
bool nmPebiResultSnapshot::getScalarRange(double aRange[2]) const
{
if(aRange == NULL || !m_bComplete)
{
return false;
}
aRange[0] = m_dScalarMin;
aRange[1] = m_dScalarMax;
return true;
}
const nmPebiResultReservoirParameters&
nmPebiResultSnapshot::getReservoirParameters() const
{
return m_oReservoirParameters;
}
const nmPebiResultPvtParameters& nmPebiResultSnapshot::getPvtParameters() const
{
return m_oPvtParameters;
}
const nmPebiResultSolverSettings&
nmPebiResultSnapshot::getSolverSettings() const
{
return m_oSolverSettings;
}
bool nmPebiResultSnapshot::copyResultGridStructure(
vtkUnstructuredGrid* pTargetGrid) const
{
if(!m_bComplete || m_pResultGrid == NULL || pTargetGrid == NULL)
{
return false;
}
// CopyStructure 在当前 VTK 7.1 中共享点和单元拓扑,不复制大网格。
// 随后清空目标 CellData确保压力只能通过受控接口按帧挂载。
pTargetGrid->Initialize();
pTargetGrid->CopyStructure(m_pResultGrid);
pTargetGrid->GetCellData()->Initialize();
return pTargetGrid->GetNumberOfCells() ==
m_pResultGrid->GetNumberOfCells();
}
bool nmPebiResultSnapshot::bindPressureFrame(
int nIndex,
vtkUnstructuredGrid* pTargetGrid) const
{
if(!m_bComplete || pTargetGrid == NULL || nIndex < 0 ||
nIndex >= m_vecPressureFrames.size() ||
pTargetGrid->GetNumberOfCells() != getResultCellCount())
{
return false;
}
vtkDoubleArray* pPressure =
m_vecPressureFrames[nIndex].m_pPressure.GetPointer();
if(pPressure == NULL ||
pPressure->GetNumberOfTuples() != getResultCellCount())
{
return false;
}
// SetScalars 会增加 VTK 引用计数,压力的规范所有权仍属于快照。
// 渲染网格销毁或切换快照时应及时解除这个临时引用。
pTargetGrid->GetCellData()->SetScalars(pPressure);
pTargetGrid->Modified();
return pTargetGrid->GetCellData()->GetScalars() == pPressure;
}
bool nmPebiResultSnapshot::copyPressureFrame(
int nIndex,
vtkDoubleArray* pTargetPressure) const
{
if(!m_bComplete || pTargetPressure == NULL || nIndex < 0 ||
nIndex >= m_vecPressureFrames.size())
{
return false;
}
vtkDoubleArray* pSourcePressure =
m_vecPressureFrames[nIndex].m_pPressure.GetPointer();
if(pSourcePressure == NULL)
{
return false;
}
// 回退路径只复制当前帧,调用方应在动画期间复用同一个缓冲数组。
pTargetPressure->DeepCopy(pSourcePressure);
pTargetPressure->SetName("p");
return pTargetPressure->GetNumberOfComponents() == 1 &&
pTargetPressure->GetNumberOfTuples() == getResultCellCount();
}

@ -0,0 +1,789 @@
#include "nmPebiResultSnapshotBuilder.h"
#include <QSet>
#include <QUuid>
#include <QtGlobal>
#include <new>
#include <float.h>
#include <math.h>
#if defined(_MSC_VER)
#else
#include <cmath>
#endif
#include <vtkCellData.h>
#include <vtkDoubleArray.h>
#include <vtkUnstructuredGrid.h>
namespace {
// Builder 只校验内存模型的完整性;持久化文件的数量上限和字节上限
// 由 v3 加载器在分配 VTK/Qt 容器之前单独检查。
bool isFiniteValue(double dValue)
{
#if defined(_MSC_VER)
return _finite(dValue) != 0;
#else
return std::isfinite(dValue);
#endif
}
bool isValidInstanceId(const QString& sInstanceId)
{
return !sInstanceId.isEmpty() && !QUuid(sInstanceId).isNull();
}
bool isFiniteVector(const QVector<double>& vecValues)
{
for(int nIndex = 0; nIndex < vecValues.size(); ++nIndex)
{
if(!isFiniteValue(vecValues[nIndex]))
{
return false;
}
}
return true;
}
bool isStrictlyIncreasing(const QVector<double>& vecValues)
{
for(int nIndex = 1; nIndex < vecValues.size(); ++nIndex)
{
if(vecValues[nIndex] <= vecValues[nIndex - 1])
{
return false;
}
}
return true;
}
bool isRequiredSeriesValid(const QVector<double>& vecAxis,
const QVector<double>& vecValues)
{
return vecAxis.size() >= 2 &&
vecValues.size() == vecAxis.size();
}
bool isOptionalSeriesValid(const QVector<double>& vecAxis,
const QVector<double>& vecValues)
{
return vecValues.isEmpty() ||
(!vecAxis.isEmpty() && vecValues.size() == vecAxis.size());
}
bool isCurveValid(const QVector<QVector<double> >& vecCurve,
int nMinimumSeries,
bool bRequired)
{
if(vecCurve.isEmpty())
{
return !bRequired;
}
if(vecCurve.size() < nMinimumSeries || vecCurve[0].isEmpty())
{
return false;
}
const int nPointCount = vecCurve[0].size();
for(int nSeries = 0; nSeries < vecCurve.size(); ++nSeries)
{
if(vecCurve[nSeries].size() != nPointCount ||
!isFiniteVector(vecCurve[nSeries]))
{
return false;
}
}
return true;
}
bool areWellCurvesValid(const nmPebiResultWellCurves& oCurves)
{
return isCurveValid(oCurves.m_vecHistoryPressure, 2, false) &&
isCurveValid(oCurves.m_vecHistoryLogLog, 3, false) &&
isCurveValid(oCurves.m_vecHistorySemiLog, 2, false) &&
isCurveValid(oCurves.m_vecResultPressure, 2, true) &&
isCurveValid(oCurves.m_vecResultLogLog, 3, false) &&
isCurveValid(oCurves.m_vecResultSemiLog, 2, false);
}
bool areReservoirParametersFinite(
const nmPebiResultReservoirParameters& oParameters)
{
return isFiniteValue(oParameters.m_dInitialPressure) &&
isFiniteValue(oParameters.m_dPermeability) &&
isFiniteValue(oParameters.m_dThickness) &&
isFiniteValue(oParameters.m_dPorosity) &&
isFiniteValue(oParameters.m_dTotalCompressibility) &&
isFiniteValue(oParameters.m_dRockCompressibility) &&
isFiniteValue(oParameters.m_dOilSaturation) &&
isFiniteValue(oParameters.m_dGasSaturation) &&
isFiniteValue(oParameters.m_dWaterSaturation);
}
bool arePvtParametersFinite(const nmPebiResultPvtParameters& oParameters)
{
if((oParameters.m_bHasBubblePoint &&
!isFiniteValue(oParameters.m_dBubblePoint)) ||
!isFiniteValue(oParameters.m_dConstantBo) ||
!isFiniteValue(oParameters.m_dConstantMiuo) ||
!isFiniteValue(oParameters.m_dConstantBg) ||
!isFiniteValue(oParameters.m_dConstantMiug) ||
!isFiniteValue(oParameters.m_dConstantBw) ||
!isFiniteValue(oParameters.m_dConstantMiuw))
{
return false;
}
return isFiniteVector(oParameters.m_vecPressure) &&
isFiniteVector(oParameters.m_vecRso) &&
isFiniteVector(oParameters.m_vecBo) &&
isFiniteVector(oParameters.m_vecCo) &&
isFiniteVector(oParameters.m_vecMiuo) &&
isFiniteVector(oParameters.m_vecRouo) &&
isFiniteVector(oParameters.m_vecRv) &&
isFiniteVector(oParameters.m_vecBg) &&
isFiniteVector(oParameters.m_vecCg) &&
isFiniteVector(oParameters.m_vecMiug) &&
isFiniteVector(oParameters.m_vecRoug) &&
isFiniteVector(oParameters.m_vecZ) &&
isFiniteVector(oParameters.m_vecRsw) &&
isFiniteVector(oParameters.m_vecBw) &&
isFiniteVector(oParameters.m_vecCw) &&
isFiniteVector(oParameters.m_vecMiuw) &&
isFiniteVector(oParameters.m_vecRouw) &&
isFiniteVector(oParameters.m_vecV) &&
isFiniteVector(oParameters.m_vecKkInitial) &&
isFiniteVector(oParameters.m_vecCfCfInitial) &&
isFiniteVector(oParameters.m_vecSo) &&
isFiniteVector(oParameters.m_vecKro) &&
isFiniteVector(oParameters.m_vecSg) &&
isFiniteVector(oParameters.m_vecKrg) &&
isFiniteVector(oParameters.m_vecSw) &&
isFiniteVector(oParameters.m_vecKrw);
}
bool areOptionalPressureSeriesValid(
const nmPebiResultPvtParameters& oParameters)
{
const QVector<double>& vecPressure = oParameters.m_vecPressure;
return isOptionalSeriesValid(vecPressure, oParameters.m_vecRso) &&
isOptionalSeriesValid(vecPressure, oParameters.m_vecBo) &&
isOptionalSeriesValid(vecPressure, oParameters.m_vecCo) &&
isOptionalSeriesValid(vecPressure, oParameters.m_vecMiuo) &&
isOptionalSeriesValid(vecPressure, oParameters.m_vecRouo) &&
isOptionalSeriesValid(vecPressure, oParameters.m_vecRv) &&
isOptionalSeriesValid(vecPressure, oParameters.m_vecBg) &&
isOptionalSeriesValid(vecPressure, oParameters.m_vecCg) &&
isOptionalSeriesValid(vecPressure, oParameters.m_vecMiug) &&
isOptionalSeriesValid(vecPressure, oParameters.m_vecRoug) &&
isOptionalSeriesValid(vecPressure, oParameters.m_vecZ) &&
isOptionalSeriesValid(vecPressure, oParameters.m_vecRsw) &&
isOptionalSeriesValid(vecPressure, oParameters.m_vecBw) &&
isOptionalSeriesValid(vecPressure, oParameters.m_vecCw) &&
isOptionalSeriesValid(vecPressure, oParameters.m_vecMiuw) &&
isOptionalSeriesValid(vecPressure, oParameters.m_vecRouw) &&
isOptionalSeriesValid(vecPressure, oParameters.m_vecV) &&
isOptionalSeriesValid(vecPressure, oParameters.m_vecKkInitial) &&
isOptionalSeriesValid(vecPressure,
oParameters.m_vecCfCfInitial);
}
bool areOptionalSaturationSeriesValid(
const nmPebiResultPvtParameters& oParameters,
int nSolverModelType)
{
if(!oParameters.m_vecSo.isEmpty() &&
!isStrictlyIncreasing(oParameters.m_vecSo))
{
return false;
}
if(!oParameters.m_vecSg.isEmpty() &&
!isStrictlyIncreasing(oParameters.m_vecSg))
{
return false;
}
if(!oParameters.m_vecSw.isEmpty() &&
!isStrictlyIncreasing(oParameters.m_vecSw))
{
return false;
}
if(oParameters.m_vecSo.isEmpty() != oParameters.m_vecKro.isEmpty() ||
(!oParameters.m_vecSo.isEmpty() &&
oParameters.m_vecSo.size() != oParameters.m_vecKro.size()) ||
oParameters.m_vecSg.isEmpty() != oParameters.m_vecKrg.isEmpty() ||
(!oParameters.m_vecSg.isEmpty() &&
oParameters.m_vecSg.size() != oParameters.m_vecKrg.size()))
{
return false;
}
// 当前油水两相求解器以 So 为横坐标,同时保存 Kro 和 Krw。
if(nSolverModelType == 8)
{
return isRequiredSeriesValid(oParameters.m_vecSo,
oParameters.m_vecKro) &&
isRequiredSeriesValid(oParameters.m_vecSo,
oParameters.m_vecKrw);
}
if(oParameters.m_vecKrw.isEmpty())
{
return oParameters.m_vecSw.isEmpty();
}
if(!oParameters.m_vecSw.isEmpty() &&
oParameters.m_vecKrw.size() != oParameters.m_vecSw.size())
{
return false;
}
if(!oParameters.m_vecSo.isEmpty() &&
oParameters.m_vecKrw.size() != oParameters.m_vecSo.size())
{
return false;
}
return !oParameters.m_vecSw.isEmpty() ||
!oParameters.m_vecSo.isEmpty();
}
bool arePvtParametersStructurallyValid(
const nmPebiResultPvtParameters& oParameters,
int nSolverModelType)
{
const QVector<double>& vecPressure = oParameters.m_vecPressure;
if((!vecPressure.isEmpty() && !isStrictlyIncreasing(vecPressure)) ||
!areOptionalPressureSeriesValid(oParameters) ||
!areOptionalSaturationSeriesValid(oParameters, nSolverModelType))
{
return false;
}
switch(nSolverModelType)
{
case 1: // 常量油 PVT
case 3: // 常量水 PVT
return true;
case 2: // 变化油 PVT
return isRequiredSeriesValid(vecPressure, oParameters.m_vecBo) &&
isRequiredSeriesValid(vecPressure, oParameters.m_vecCo) &&
isRequiredSeriesValid(vecPressure, oParameters.m_vecMiuo);
case 4: // 变化水 PVT
return isRequiredSeriesValid(vecPressure, oParameters.m_vecBw) &&
isRequiredSeriesValid(vecPressure, oParameters.m_vecCw) &&
isRequiredSeriesValid(vecPressure, oParameters.m_vecMiuw);
case 5: // 变化气 PVT
case 6: // 气拟压力模型
return isRequiredSeriesValid(vecPressure, oParameters.m_vecBg) &&
isRequiredSeriesValid(vecPressure, oParameters.m_vecCg) &&
isRequiredSeriesValid(vecPressure, oParameters.m_vecMiug);
case 8: // 油水两相
return isRequiredSeriesValid(vecPressure, oParameters.m_vecBo) &&
isRequiredSeriesValid(vecPressure, oParameters.m_vecMiuo) &&
isRequiredSeriesValid(vecPressure, oParameters.m_vecBw) &&
isRequiredSeriesValid(vecPressure, oParameters.m_vecMiuw);
default:
return false;
}
}
bool areSolverSettingsValid(const nmPebiResultSolverSettings& oSettings)
{
const bool bSupportedModel =
oSettings.m_nSolverModelType == 1 ||
oSettings.m_nSolverModelType == 2 ||
oSettings.m_nSolverModelType == 3 ||
oSettings.m_nSolverModelType == 4 ||
oSettings.m_nSolverModelType == 5 ||
oSettings.m_nSolverModelType == 6 ||
oSettings.m_nSolverModelType == 8;
const bool bSupportedSolver =
oSettings.m_nPebiSolverType == 1 ||
oSettings.m_nPebiSolverType == 2;
if(!bSupportedModel || !bSupportedSolver ||
oSettings.m_nOmpThreads <= 0 ||
oSettings.m_nIluReuseSteps <= 0 ||
!isFiniteValue(oSettings.m_dGridControl) ||
oSettings.m_dGridControl <= 0.0)
{
return false;
}
return !oSettings.m_bHasTimeStepSettings ||
(isFiniteValue(oSettings.m_dTimeGrowthExponent) &&
isFiniteValue(oSettings.m_dMinDeltaT) &&
isFiniteValue(oSettings.m_dMaxDeltaT) &&
oSettings.m_dTimeGrowthExponent > 0.0 &&
oSettings.m_dMinDeltaT > 0.0 &&
oSettings.m_dMaxDeltaT >= oSettings.m_dMinDeltaT);
}
}
nmPebiResultSnapshotBuilder::nmPebiResultSnapshotBuilder()
: m_bFinalized(false),
m_bFailed(false),
m_bHasScalarRange(false),
m_bHasReservoirParameters(false),
m_bHasPvtParameters(false),
m_bHasSolverSettings(false)
{
try
{
m_pCandidate = QSharedPointer<nmPebiResultSnapshot>(
new nmPebiResultSnapshot());
}
catch(const std::bad_alloc&)
{
m_bFailed = true;
m_sLastError = "Cannot allocate PEBI result snapshot.";
}
}
nmPebiResultSnapshotBuilder::~nmPebiResultSnapshotBuilder()
{
m_pCandidate.clear();
}
bool nmPebiResultSnapshotBuilder::canWrite()
{
if(m_pCandidate.isNull() || m_bFinalized || m_bFailed)
{
if(m_sLastError.isEmpty())
{
m_sLastError = "PEBI result snapshot builder is not writable.";
}
return false;
}
return true;
}
bool nmPebiResultSnapshotBuilder::fail(
const QString& sError,
QString* pError)
{
m_bFailed = true;
m_sLastError = sError;
if(pError != NULL)
{
*pError = sError;
}
return false;
}
bool nmPebiResultSnapshotBuilder::setSnapshotId(
const QString& sSnapshotId)
{
if(!canWrite() || !isValidInstanceId(sSnapshotId))
{
return fail("Invalid PEBI result snapshot identity.");
}
m_pCandidate->m_sSnapshotId = QUuid(sSnapshotId).toString()
.remove('{').remove('}');
return true;
}
bool nmPebiResultSnapshotBuilder::setInputRevisions(
quint64 nGridInputRevision,
quint64 nResultInputRevision)
{
if(!canWrite() || nGridInputRevision == 0 || nResultInputRevision == 0)
{
return fail("Invalid PEBI result input revisions.");
}
m_pCandidate->m_nGridInputRevision = nGridInputRevision;
m_pCandidate->m_nResultInputRevision = nResultInputRevision;
return true;
}
bool nmPebiResultSnapshotBuilder::takeResultGrid(
vtkSmartPointer<vtkUnstructuredGrid>& pResultGrid)
{
if(!canWrite() || pResultGrid == NULL ||
pResultGrid->GetNumberOfCells() <= 0)
{
return fail("Invalid PEBI result grid.");
}
// 通过交换所有权避免大网格 DeepCopy成功后调用方不能再修改源网格。
m_pCandidate->m_pResultGrid = pResultGrid;
pResultGrid = NULL;
return true;
}
bool nmPebiResultSnapshotBuilder::addPressureFrame(
double dTime,
vtkSmartPointer<vtkDoubleArray>& pPressure)
{
if(!canWrite() || !isFiniteValue(dTime) || pPressure == NULL ||
pPressure->GetNumberOfComponents() != 1 ||
pPressure->GetNumberOfTuples() <= 0)
{
return fail("Invalid PEBI pressure frame.");
}
if(!m_pCandidate->m_vecPressureFrames.isEmpty() &&
dTime <= m_pCandidate->m_vecPressureFrames.last().m_dTime)
{
return fail("PEBI pressure frame times must be strictly increasing.");
}
try
{
// 压力帧同样直接转交,发布路径不会产生第二份完整时间步数据。
nmPebiResultSnapshot::PressureFrame oFrame;
oFrame.m_dTime = dTime;
pPressure->SetName("p");
oFrame.m_pPressure = pPressure;
m_pCandidate->m_vecPressureFrames.append(oFrame);
pPressure = NULL;
}
catch(const std::bad_alloc&)
{
return fail("Cannot allocate PEBI pressure frame list.");
}
return true;
}
bool nmPebiResultSnapshotBuilder::setScalarRange(
double dMinimum,
double dMaximum)
{
if(!canWrite() || !isFiniteValue(dMinimum) ||
!isFiniteValue(dMaximum) ||
dMinimum > dMaximum)
{
return fail("Invalid PEBI scalar range.");
}
m_pCandidate->m_dScalarMin = dMinimum;
m_pCandidate->m_dScalarMax = dMaximum;
m_bHasScalarRange = true;
return true;
}
bool nmPebiResultSnapshotBuilder::addSolverSlot(
const nmPebiResultSolverSlot& oSlot)
{
if(!canWrite())
{
return false;
}
try
{
m_pCandidate->m_vecSolverSlots.append(oSlot);
}
catch(const std::bad_alloc&)
{
return fail("Cannot allocate PEBI solver slot list.");
}
return true;
}
bool nmPebiResultSnapshotBuilder::addWell(
const nmPebiResultWellSnapshot& oWell)
{
if(!canWrite())
{
return false;
}
try
{
m_pCandidate->m_vecWells.append(oWell);
}
catch(const std::bad_alloc&)
{
return fail("Cannot allocate PEBI result well list.");
}
return true;
}
bool nmPebiResultSnapshotBuilder::setDisplayWellInstanceIds(
const QStringList& listWellInstanceIds)
{
if(!canWrite())
{
return false;
}
try
{
m_pCandidate->m_listDisplayWellInstanceIds = listWellInstanceIds;
}
catch(const std::bad_alloc&)
{
return fail("Cannot allocate PEBI display well list.");
}
return true;
}
bool nmPebiResultSnapshotBuilder::setReservoirParameters(
const nmPebiResultReservoirParameters& oParameters)
{
if(!canWrite())
{
return false;
}
m_pCandidate->m_oReservoirParameters = oParameters;
m_bHasReservoirParameters = true;
return true;
}
bool nmPebiResultSnapshotBuilder::setPvtParameters(
const nmPebiResultPvtParameters& oParameters)
{
if(!canWrite())
{
return false;
}
try
{
m_pCandidate->m_oPvtParameters = oParameters;
}
catch(const std::bad_alloc&)
{
return fail("Cannot allocate PEBI PVT snapshot.");
}
m_bHasPvtParameters = true;
return true;
}
bool nmPebiResultSnapshotBuilder::setSolverSettings(
const nmPebiResultSolverSettings& oSettings)
{
if(!canWrite())
{
return false;
}
m_pCandidate->m_oSolverSettings = oSettings;
m_bHasSolverSettings = true;
return true;
}
bool nmPebiResultSnapshotBuilder::validate(QString* pError)
{
// 第一部分校验快照身份、输入版本和所有必填结果载荷。
if(!isValidInstanceId(m_pCandidate->m_sSnapshotId))
{
return fail("PEBI result snapshot identity is invalid.", pError);
}
if(m_pCandidate->m_nGridInputRevision == 0 ||
m_pCandidate->m_nResultInputRevision == 0)
{
return fail("PEBI result revisions are missing.", pError);
}
if(m_pCandidate->m_pResultGrid == NULL ||
m_pCandidate->m_pResultGrid->GetNumberOfCells() <= 0)
{
return fail("PEBI result grid is missing.", pError);
}
if(m_pCandidate->m_vecPressureFrames.isEmpty())
{
return fail("PEBI pressure frames are missing.", pError);
}
if(!m_bHasScalarRange || !m_bHasReservoirParameters ||
!m_bHasPvtParameters || !m_bHasSolverSettings)
{
return fail("PEBI result parameters are incomplete.", pError);
}
if(!areReservoirParametersFinite(
m_pCandidate->m_oReservoirParameters) ||
!areSolverSettingsValid(m_pCandidate->m_oSolverSettings) ||
!arePvtParametersFinite(m_pCandidate->m_oPvtParameters) ||
!arePvtParametersStructurallyValid(
m_pCandidate->m_oPvtParameters,
m_pCandidate->m_oSolverSettings.m_nSolverModelType))
{
return fail("PEBI result parameters are invalid.", pError);
}
// 每帧必须与基础网格一一对应;同时从实际压力重新计算标量范围,
// 防止保存或求解端传入与场数据不一致的显示范围。
const vtkIdType nCellCount =
m_pCandidate->m_pResultGrid->GetNumberOfCells();
double dPreviousTime = 0.0;
double dActualMinimum = DBL_MAX;
double dActualMaximum = -DBL_MAX;
for(int nIndex = 0;
nIndex < m_pCandidate->m_vecPressureFrames.size(); ++nIndex)
{
const nmPebiResultSnapshot::PressureFrame& oFrame =
m_pCandidate->m_vecPressureFrames[nIndex];
if(!isFiniteValue(oFrame.m_dTime) ||
(nIndex > 0 && oFrame.m_dTime <= dPreviousTime) ||
oFrame.m_pPressure == NULL ||
oFrame.m_pPressure->GetNumberOfComponents() != 1 ||
oFrame.m_pPressure->GetNumberOfTuples() != nCellCount)
{
return fail("PEBI pressure frame dimensions are invalid.", pError);
}
for(vtkIdType nCellIndex = 0; nCellIndex < nCellCount;
++nCellIndex)
{
const double dValue = oFrame.m_pPressure->GetValue(nCellIndex);
if(!isFiniteValue(dValue))
{
return fail("PEBI pressure frame contains invalid values.",
pError);
}
if(dValue < dActualMinimum)
{
dActualMinimum = dValue;
}
if(dValue > dActualMaximum)
{
dActualMaximum = dValue;
}
}
dPreviousTime = oFrame.m_dTime;
}
const double dRangeScale = qMax(1.0,
qMax(fabs(dActualMinimum), fabs(dActualMaximum)));
const double dRangeTolerance = dRangeScale * 1.0e-10;
if(fabs(m_pCandidate->m_dScalarMin - dActualMinimum) >
dRangeTolerance ||
fabs(m_pCandidate->m_dScalarMax - dActualMaximum) >
dRangeTolerance)
{
return fail("PEBI scalar range does not match pressure frames.",
pError);
}
// UUID 是结果关联主键,井编码只作为求解时冻结元数据参与一致性校验。
QSet<QString> setWellIds;
QSet<QString> setWellCodes;
for(int nIndex = 0; nIndex < m_pCandidate->m_vecWells.size(); ++nIndex)
{
const nmPebiResultWellSnapshot& oWell =
m_pCandidate->m_vecWells[nIndex];
if(!isValidInstanceId(oWell.m_sWellInstanceId) ||
oWell.m_sWellCode.isEmpty() || oWell.m_sWellName.isEmpty() ||
oWell.m_eWellType == Unknow_Well ||
(oWell.m_eWellMode != NM_CaseWell_RateControlled &&
oWell.m_eWellMode != NM_CaseWell_Observation) ||
!isFiniteValue(oWell.m_oLocation.x()) ||
!isFiniteValue(oWell.m_oLocation.y()) ||
!isFiniteValue(oWell.m_dRadius) ||
!isFiniteValue(oWell.m_dWellboreStorage) ||
(oWell.m_bHasSkin && !isFiniteValue(oWell.m_dSkin)) ||
(oWell.m_bHasDfc && !isFiniteValue(oWell.m_dDfc)) ||
setWellIds.contains(oWell.m_sWellInstanceId) ||
setWellCodes.contains(oWell.m_sWellCode) ||
!areWellCurvesValid(oWell.m_oCurves))
{
return fail("PEBI result well data are invalid.", pError);
}
setWellIds.insert(oWell.m_sWellInstanceId);
setWellCodes.insert(oWell.m_sWellCode);
}
// 求解器槽位必须连续且覆盖全部真实井;手工裂缝槽位不允许伪装成井。
QSet<QString> setSlotWellIds;
int nRealWellSlotCount = 0;
for(int nIndex = 0; nIndex < m_pCandidate->m_vecSolverSlots.size();
++nIndex)
{
const nmPebiResultSolverSlot& oSlot =
m_pCandidate->m_vecSolverSlots[nIndex];
if(oSlot.m_nSolverIndex != nIndex ||
(oSlot.m_eEntryKind != NM_SolverEntry_Well &&
oSlot.m_eEntryKind != NM_SolverEntry_ManualFracture))
{
return fail("PEBI solver slot order is invalid.", pError);
}
if(oSlot.m_eEntryKind == NM_SolverEntry_ManualFracture)
{
if(!oSlot.m_sWellInstanceId.isEmpty())
{
return fail("Manual fracture slot has a well identity.", pError);
}
continue;
}
const nmPebiResultWellSnapshot* pWell =
m_pCandidate->findWell(oSlot.m_sWellInstanceId);
if(pWell == NULL || pWell->m_sWellCode != oSlot.m_sWellCode ||
pWell->m_eWellType != oSlot.m_eWellType ||
setSlotWellIds.contains(oSlot.m_sWellInstanceId))
{
return fail("PEBI solver slot well mapping is invalid.", pError);
}
setSlotWellIds.insert(oSlot.m_sWellInstanceId);
++nRealWellSlotCount;
}
if(nRealWellSlotCount != m_pCandidate->m_vecWells.size())
{
return fail("PEBI result well count does not match solver slots.", pError);
}
// 可显示列表是完整井集合的有序子集,允许为空以支持仅显示场图。
QSet<QString> setDisplayWellIds;
for(int nIndex = 0;
nIndex < m_pCandidate->m_listDisplayWellInstanceIds.size(); ++nIndex)
{
const QString& sWellInstanceId =
m_pCandidate->m_listDisplayWellInstanceIds[nIndex];
if(!setWellIds.contains(sWellInstanceId) ||
setDisplayWellIds.contains(sWellInstanceId))
{
return fail("PEBI display well list is invalid.", pError);
}
setDisplayWellIds.insert(sWellInstanceId);
}
// 发布前基础网格必须与压力帧解耦,渲染网格只能按帧受控绑定。
m_pCandidate->m_pResultGrid->GetCellData()->SetScalars(NULL);
m_pCandidate->m_pResultGrid->GetCellData()->RemoveArray("p");
if(m_pCandidate->m_pResultGrid->GetCellData()->GetScalars() != NULL ||
m_pCandidate->m_pResultGrid->GetCellData()->GetArray("p") != NULL)
{
return fail("PEBI result grid still owns pressure scalars.", pError);
}
return true;
}
bool nmPebiResultSnapshotBuilder::finalize(QString* pError)
{
if(!canWrite())
{
if(pError != NULL)
{
*pError = m_sLastError;
}
return false;
}
try
{
if(!validate(pError))
{
return false;
}
// 只有全部校验通过后才设置完整标志Manager 不接受其他候选。
m_pCandidate->m_bComplete = true;
m_bFinalized = true;
return true;
}
catch(const std::bad_alloc&)
{
return fail("Cannot finalize PEBI result snapshot.", pError);
}
}
QSharedPointer<nmPebiResultSnapshot>
nmPebiResultSnapshotBuilder::takeCandidate()
{
if(!m_bFinalized || m_bFailed || m_pCandidate.isNull() ||
!m_pCandidate->isComplete())
{
return QSharedPointer<nmPebiResultSnapshot>();
}
// take 后 Builder 不再持有可写别名,后续生命周期交给提交方管理。
QSharedPointer<nmPebiResultSnapshot> pCandidate = m_pCandidate;
m_pCandidate.clear();
return pCandidate;
}
QString nmPebiResultSnapshotBuilder::getLastError() const
{
return m_sLastError;
}

@ -0,0 +1,128 @@
#include "nmPebiResultSnapshotRenderAdapter.h"
#include <vtkAlgorithmOutput.h>
#include <vtkCellData.h>
#include <vtkDoubleArray.h>
#include <vtkTrivialProducer.h>
#include <vtkUnstructuredGrid.h>
nmPebiResultSnapshotRenderAdapter::nmPebiResultSnapshotRenderAdapter()
: m_pRenderGrid(vtkSmartPointer<vtkUnstructuredGrid>::New()),
m_pPressureBuffer(vtkSmartPointer<vtkDoubleArray>::New()),
m_pProducer(vtkSmartPointer<vtkTrivialProducer>::New()),
m_eBindingMode(SharedPressureFrame),
m_nCurrentFrameIndex(-1)
{
}
nmPebiResultSnapshotRenderAdapter::~nmPebiResultSnapshotRenderAdapter()
{
clear();
}
bool nmPebiResultSnapshotRenderAdapter::initialize(
const QSharedPointer<const nmPebiResultSnapshot>& pSnapshot,
PressureBindingMode eBindingMode)
{
clear();
if(pSnapshot.isNull() || !pSnapshot->isComplete() ||
(eBindingMode != SharedPressureFrame &&
eBindingMode != CopiedPressureFrame))
{
return false;
}
m_pSnapshot = pSnapshot;
m_eBindingMode = eBindingMode;
if(!m_pSnapshot->copyResultGridStructure(m_pRenderGrid))
{
clear();
return false;
}
// TrivialProducer 只负责把适配器网格接入管道,不会主动修改输出对象。
m_pProducer->SetOutput(m_pRenderGrid);
m_pProducer->Modified();
return true;
}
void nmPebiResultSnapshotRenderAdapter::clear()
{
// 先断开管道和 CellData及时释放对旧快照压力数组的临时 VTK 引用。
if(m_pProducer != NULL)
{
m_pProducer->SetOutput(NULL);
m_pProducer->Modified();
}
if(m_pRenderGrid != NULL)
{
m_pRenderGrid->GetCellData()->Initialize();
m_pRenderGrid->Initialize();
}
if(m_pPressureBuffer != NULL)
{
m_pPressureBuffer->Initialize();
}
m_pSnapshot.clear();
m_eBindingMode = SharedPressureFrame;
m_nCurrentFrameIndex = -1;
}
bool nmPebiResultSnapshotRenderAdapter::setPressureFrame(int nFrameIndex)
{
if(m_pSnapshot.isNull() || m_pRenderGrid == NULL ||
nFrameIndex < 0 ||
nFrameIndex >= m_pSnapshot->getPressureFrameCount())
{
return false;
}
bool bBound = false;
if(m_eBindingMode == SharedPressureFrame)
{
bBound = m_pSnapshot->bindPressureFrame(
nFrameIndex, m_pRenderGrid);
}
else
{
bBound = m_pSnapshot->copyPressureFrame(
nFrameIndex, m_pPressureBuffer);
if(bBound)
{
m_pRenderGrid->GetCellData()->SetScalars(m_pPressureBuffer);
m_pRenderGrid->Modified();
}
}
if(!bBound)
{
return false;
}
m_nCurrentFrameIndex = nFrameIndex;
m_pProducer->Modified();
return true;
}
vtkAlgorithmOutput*
nmPebiResultSnapshotRenderAdapter::getOutputPort() const
{
return m_pSnapshot.isNull() || m_pProducer == NULL
? NULL : m_pProducer->GetOutputPort();
}
int nmPebiResultSnapshotRenderAdapter::getCurrentFrameIndex() const
{
return m_nCurrentFrameIndex;
}
vtkIdType nmPebiResultSnapshotRenderAdapter::getCellCount() const
{
return m_pRenderGrid == NULL ? 0 :
m_pRenderGrid->GetNumberOfCells();
}
bool nmPebiResultSnapshotRenderAdapter::isUsingSharedPressure() const
{
return !m_pSnapshot.isNull() &&
m_eBindingMode == SharedPressureFrame;
}

@ -18,7 +18,8 @@ static const char* const s_pSessionTranslationMarkers[] =
QT_TRANSLATE_NOOP("nmWellEditorSession", "The well type is not supported."),
QT_TRANSLATE_NOOP("nmWellEditorSession", "Failed to create the well working copy."),
QT_TRANSLATE_NOOP("nmWellEditorSession", "The well editing session is unavailable."),
QT_TRANSLATE_NOOP("nmWellEditorSession", "Failed to create the selected well type.")
QT_TRANSLATE_NOOP("nmWellEditorSession", "Failed to create the selected well type."),
QT_TRANSLATE_NOOP("nmWellEditorSession", "Failed to preserve the well identity.")
};
QString sessionText(const char* pText)
@ -199,9 +200,14 @@ bool nmWellEditorSession::switchWellType(NM_WELL_MODEL eWellType,
nmDataWellBase* pSharedSource = currentWell();
nmDataWellBase* pPerforationSource =
findCompatiblePerforationSource(eWellType);
initializeWorkingWell(pSharedSource,
pPerforationSource,
pNewWell);
if (!initializeWorkingWell(pSharedSource,
pPerforationSource,
pNewWell))
{
delete pNewWell;
sError = sessionText("Failed to preserve the well identity.");
return false;
}
m_mapWorkingWells.insert(nTargetType, pNewWell);
m_nCurrentWellType = nTargetType;
sError.clear();
@ -300,18 +306,21 @@ nmDataWellBase* nmWellEditorSession::findCompatiblePerforationSource(
return nullptr;
}
void nmWellEditorSession::initializeWorkingWell(
bool nmWellEditorSession::initializeWorkingWell(
nmDataWellBase* pSharedSource,
nmDataWellBase* pPerforationSource,
nmDataWellBase* pTarget) const
{
if (pSharedSource == nullptr || pTarget == nullptr)
{
return;
return false;
}
// 第一步:白名单复制跨井型仍具有相同含义的数据。
copySharedWellData(pSharedSource, pTarget);
if (!copySharedWellData(pSharedSource, pTarget))
{
return false;
}
// 第二步:只在垂直井族内迁移射孔;跨井族保留目标构造默认值。
if (pPerforationSource != nullptr)
@ -324,20 +333,25 @@ void nmWellEditorSession::initializeWorkingWell(
// 第三步:目标对象完全初始化后统一重建派生几何。
rebuildDerivedGeometry(pTarget);
return true;
}
void nmWellEditorSession::copySharedWellData(
bool nmWellEditorSession::copySharedWellData(
nmDataWellBase* pSource,
nmDataWellBase* pTarget) const
{
if (pSource == nullptr || pTarget == nullptr)
{
return;
return false;
}
// 只复制跨井型语义完全一致的字段,严禁使用基类整体赋值覆盖目标默认值。
// 第一步:复制井身份、位置和通用井筒配置。
if (!pTarget->restoreWellInstanceId(pSource->getWellInstanceId()))
{
return false;
}
pTarget->setWellName(pSource->getWellName());
pTarget->setWellCode(pSource->getWellCode());
pTarget->setX(pSource->getX());
@ -378,6 +392,7 @@ void nmWellEditorSession::copySharedWellData(
pTarget->setDotRadius(pSource->getDotRadius());
pTarget->setDotFilling(pSource->getDotFilling());
pTarget->setShowSubObjs(pSource->getShowSubObjs());
return true;
}
void nmWellEditorSession::migratePerforationData(

@ -672,6 +672,11 @@ void nmWxEditWellPlot::onWellTypeChanged(int index)
// 将旧数据中的通用属性复制到新数据中
if(pNewWellData && pOldWellData) {
// 通用几何位置
if(!pNewWellData->restoreWellInstanceId(
pOldWellData->getWellInstanceId())) {
delete pNewWellData;
return;
}
pNewWellData->setWellName(pOldWellData->getWellName());
pNewWellData->setX(pOldWellData->getX());
pNewWellData->setY(pOldWellData->getY());

Loading…
Cancel
Save