feat(nmData): 冻结 PEBI 结果快照属性

- 冻结求解时的 k/phi/h 并按 OriginalCellId 映射结果属性
- 升级 V5 快照持久化及属性目录校验
- 支持通用属性帧切换和双缓冲渲染
- 加固基础网格映射与井曲线完整性验证
feature/nmNum-postprocessing-view-20260910
lh 3 weeks ago
parent 36f17ae321
commit e98267c9c3

@ -20,6 +20,7 @@ class nmPebiResultSnapshot;
class vtkDoubleArray; class vtkDoubleArray;
struct nmPebiSolverInputSnapshot; struct nmPebiSolverInputSnapshot;
struct nmPebiManualCaptureState; struct nmPebiManualCaptureState;
struct nmPebiStaticPropertyValues;
// 主窗口现在直接创建DLL求解线程任务,因此类需要导出,供nmSubWnd模块跨DLL使用。 // 主窗口现在直接创建DLL求解线程任务,因此类需要导出,供nmSubWnd模块跨DLL使用。
class NMCALCULATION_EXPORT nmCalculationDllPebiSolverTask : public QThread { class NMCALCULATION_EXPORT nmCalculationDllPebiSolverTask : public QThread {
@ -101,9 +102,10 @@ class NMCALCULATION_EXPORT nmCalculationDllPebiSolverTask : public QThread {
/** @brief 按求解器井顺序组装压力、曲线和场结果快照,不修改 DataManager。 */ /** @brief 按求解器井顺序组装压力、曲线和场结果快照,不修改 DataManager。 */
bool buildPebiModeResult(HX_NWTM_MODEL_OUTPUT& p1, bool buildPebiModeResult(HX_NWTM_MODEL_OUTPUT& p1,
int nModelType, int nModelType,
const HX_NWTM_GRID_OUTPUT1& oGridOutput); nmPebiStaticPropertyValues& oStaticProperties);
/** @brief 用任务局部输出构造并封闭完整结果候选,不访问实时数据对象。 */ /** @brief 用任务局部输出构造并封闭完整结果候选,不访问实时数据对象。 */
bool buildPebiResultSnapshotCandidate(); bool buildPebiResultSnapshotCandidate(
nmPebiStaticPropertyValues& oStaticProperties);
/** @brief 输出精简版 DLL 输入日志,用于定位求解异常。 */ /** @brief 输出精简版 DLL 输入日志,用于定位求解异常。 */
void logHX_NWTM_MODEL_INPUT_Simplified(const HX_NWTM_MODEL_INPUT& p0); void logHX_NWTM_MODEL_INPUT_Simplified(const HX_NWTM_MODEL_INPUT& p0);

@ -3,6 +3,7 @@
#include "nmData_global.h" #include "nmData_global.h"
#include "nmDataNumericalAnalysisCase.h" #include "nmDataNumericalAnalysisCase.h"
#include "nmPebiStaticPropertyValues.h"
#include <QPointF> #include <QPointF>
#include <QSharedPointer> #include <QSharedPointer>
@ -248,7 +249,7 @@ struct NM_DATA_EXPORT nmPebiResultSolverSettings
/** /**
* @brief 最后一次成功 PEBI 求解的完整只读结果。 * @brief 最后一次成功 PEBI 求解的完整只读结果。
* *
* 普通调用方只能读取值类型,或要求快照把结构和压力绑定到调用方拥有的 * 普通调用方只能读取值类型,或要求快照把结构和属性绑定到调用方拥有的
* VTK 对象,不能取得快照内部 VTK 对象的可写指针。 * VTK 对象,不能取得快照内部 VTK 对象的可写指针。
*/ */
class NM_DATA_EXPORT nmPebiResultSnapshot class NM_DATA_EXPORT nmPebiResultSnapshot
@ -284,6 +285,22 @@ public:
/** @brief 返回结果基础网格单元数。 */ /** @brief 返回结果基础网格单元数。 */
vtkIdType getResultCellCount() const; vtkIdType getResultCellCount() const;
/** @brief 按属性目录顺序返回当前快照包含的正式属性 ID。 */
QStringList getAvailablePropertyIds() const;
/** @brief 判断快照是否包含指定正式属性。 */
bool hasProperty(const QString& sPropertyId) const;
/** @brief 返回属性帧数;未知属性返回 0,静态属性固定返回 1。 */
int getPropertyFrameCount(const QString& sPropertyId) const;
/**
* @brief 返回动态属性指定帧的时间。
* @note 静态属性、未知属性或越界帧返回 false,且不修改 dTime。
*/
bool getPropertyTimeAt(const QString& sPropertyId,
int nFrameIndex,
double& dTime) const;
/** @brief 返回指定属性的冻结显示范围。 */
bool getPropertyRange(const QString& sPropertyId,
double aRange[2]) const;
/** @brief 返回压力时间步数量。 */ /** @brief 返回压力时间步数量。 */
int getPressureFrameCount() const; int getPressureFrameCount() const;
/** @brief 返回指定压力帧的时间,越界时返回 0。 */ /** @brief 返回指定压力帧的时间,越界时返回 0。 */
@ -318,22 +335,24 @@ private:
nmPebiResultSnapshot& operator=(const nmPebiResultSnapshot&); nmPebiResultSnapshot& operator=(const nmPebiResultSnapshot&);
/** /**
* @brief 只复制拓扑到适配器拥有的渲染网格,不复制快照的 CellData。 * @brief 复制拓扑和内部单元映射,不复制任何正式结果属性数组。
* @note VTK 7.1 的 CopyStructure 会共享点和单元拓扑,适配器不得修改它们。 * @note VTK 7.1 的 CopyStructure 会共享点和单元拓扑,适配器不得修改它们。
*/ */
bool copyResultGridStructure(vtkUnstructuredGrid* pTargetGrid) const; bool copyResultGridStructure(vtkUnstructuredGrid* pTargetGrid) const;
/** /**
* @brief 将快照压力帧临时共享绑定到适配器的独立渲染网格。 * @brief 将指定正式属性帧临时共享绑定到适配器的独立渲染网格。
* @note 目标网格会临时持有同一 VTK 数组引用,下游流程不得改写数组。 * @note 目标网格会临时持有同一 VTK 数组引用,下游流程不得改写数组。
*/ */
bool bindPressureFrame(int nIndex, bool bindPropertyFrame(const QString& sPropertyId,
int nFrameIndex,
vtkUnstructuredGrid* pTargetGrid) const; vtkUnstructuredGrid* pTargetGrid) const;
/** @brief 将一帧压力复制到适配器复用的缓冲数组。 */ /** @brief 将一帧正式属性复制到适配器提供的备用缓冲数组。 */
bool copyPressureFrame(int nIndex, bool copyPropertyFrame(const QString& sPropertyId,
vtkDoubleArray* pTargetPressure) const; int nFrameIndex,
vtkDoubleArray* pTargetScalars) const;
private: private:
// 网格与压力分开保存,避免基础网格通过 CellData 暴露可变压力数组。 // 网格与结果属性分开保存,避免基础网格通过 CellData 暴露可变数组。
/** @brief 快照 UUID。 */ /** @brief 快照 UUID。 */
QString m_sSnapshotId; QString m_sSnapshotId;
/** @brief 生成结果时使用的网格输入版本。 */ /** @brief 生成结果时使用的网格输入版本。 */
@ -342,8 +361,18 @@ private:
quint64 m_nResultInputRevision; quint64 m_nResultInputRevision;
/** @brief 是否已由 Builder 完整校验并封闭。 */ /** @brief 是否已由 Builder 完整校验并封闭。 */
bool m_bComplete; bool m_bComplete;
/** @brief 不含压力 p 数组和活动标量的结果基础网格。 */ /** @brief 只含几何和内部映射、不含正式结果标量的基础网格。 */
vtkSmartPointer<vtkUnstructuredGrid> m_pResultGrid; vtkSmartPointer<vtkUnstructuredGrid> m_pResultGrid;
/** @brief 按原始 PEBI 单元顺序保存的三项规范静态属性。 */
nmPebiStaticPropertyValues m_oStaticProperties;
/** @brief 按可见 VTK Cell 顺序缓存的渗透率。 */
vtkSmartPointer<vtkDoubleArray> m_pPermeability;
/** @brief 按可见 VTK Cell 顺序缓存的孔隙度。 */
vtkSmartPointer<vtkDoubleArray> m_pPorosity;
/** @brief 按可见 VTK Cell 顺序缓存的厚度。 */
vtkSmartPointer<vtkDoubleArray> m_pThickness;
/** @brief 三项静态属性的可见单元范围,顺序为 k/phi/h。 */
double m_aStaticRanges[3][2];
/** @brief 按时间严格递增保存的全部压力帧。 */ /** @brief 按时间严格递增保存的全部压力帧。 */
QVector<PressureFrame> m_vecPressureFrames; QVector<PressureFrame> m_vecPressureFrames;
/** @brief 全部压力帧中的全局最小值。 */ /** @brief 全部压力帧中的全局最小值。 */

@ -29,6 +29,11 @@ public:
/** @brief 成功后接管网格且清空调用方智能指针,不执行 DeepCopy。 */ /** @brief 成功后接管网格且清空调用方智能指针,不执行 DeepCopy。 */
bool takeResultGrid( bool takeResultGrid(
vtkSmartPointer<vtkUnstructuredGrid>& pResultGrid); vtkSmartPointer<vtkUnstructuredGrid>& pResultGrid);
/**
* @brief 接管原始顺序的三项静态属性并建立只读 VTK 可见单元缓存。
* @note 失败时调用方三个数组保持不变;成功后通过 qSwap 清空调用方。
*/
bool takeStaticProperties(nmPebiStaticPropertyValues& oValues);
/** @brief 成功后接管压力数组且清空调用方智能指针,不复制整帧数据。 */ /** @brief 成功后接管压力数组且清空调用方智能指针,不复制整帧数据。 */
bool addPressureFrame( bool addPressureFrame(
double dTime, double dTime,
@ -66,7 +71,7 @@ private:
bool canWrite(); bool canWrite();
/** @brief 将 Builder 永久置为失败状态并输出错误信息。 */ /** @brief 将 Builder 永久置为失败状态并输出错误信息。 */
bool fail(const QString& sError, QString* pError = NULL); bool fail(const QString& sError, QString* pError = NULL);
/** @brief 校验候选全部结构,并移除基础网格的压力标量。 */ /** @brief 校验候选全部结构,不修改已经接管的候选载荷。 */
bool validate(QString* pError); bool validate(QString* pError);
private: private:
@ -79,12 +84,18 @@ private:
bool m_bFailed; bool m_bFailed;
/** @brief 调用方是否显式设置过标量范围。 */ /** @brief 调用方是否显式设置过标量范围。 */
bool m_bHasScalarRange; bool m_bHasScalarRange;
/** @brief 是否已经接管并建立完整静态属性缓存。 */
bool m_bHasStaticProperties;
/** @brief 调用方是否显式设置过储层参数。 */ /** @brief 调用方是否显式设置过储层参数。 */
bool m_bHasReservoirParameters; bool m_bHasReservoirParameters;
/** @brief 调用方是否显式设置过 PVT 参数。 */ /** @brief 调用方是否显式设置过 PVT 参数。 */
bool m_bHasPvtParameters; bool m_bHasPvtParameters;
/** @brief 调用方是否显式设置过求解设置。 */ /** @brief 调用方是否显式设置过求解设置。 */
bool m_bHasSolverSettings; bool m_bHasSolverSettings;
/** @brief 基础网格中 VTK Cell 到原始 PEBI 单元的冻结映射。 */
QVector<vtkIdType> m_vecVtkToOriginal;
/** @brief 本次候选的原始 PEBI 单元总数。 */
vtkIdType m_nOriginalCellCount;
/** @brief 最近一次失败的英文内部诊断文本。 */ /** @brief 最近一次失败的英文内部诊断文本。 */
QString m_sLastError; QString m_sLastError;
}; };

@ -23,11 +23,18 @@ class vtkUnstructuredGrid;
class NM_DATA_EXPORT nmPebiResultSnapshotRenderAdapter class NM_DATA_EXPORT nmPebiResultSnapshotRenderAdapter
{ {
public: public:
/** @brief 压力帧与渲染网格之间的绑定方式。 */ /** @brief 正式属性数组与渲染网格之间的绑定方式。 */
enum PropertyBindingMode
{
SharedPropertyArray = 0, ///< 临时共享快照只读数组,不产生整帧复制。
CopiedPropertyArray = 1 ///< 复制到双缓冲,隔离不可信下游流程。
};
/** @brief 旧压力调用方使用的兼容枚举。 */
enum PressureBindingMode enum PressureBindingMode
{ {
SharedPressureFrame = 0, ///< 临时共享快照压力数组,不产生整帧复制。 SharedPressureFrame = SharedPropertyArray,
CopiedPressureFrame = 1 ///< 复制到可复用缓冲,隔离不可信下游流程。 CopiedPressureFrame = CopiedPropertyArray
}; };
/** @brief 创建尚未绑定快照的渲染适配器。 */ /** @brief 创建尚未绑定快照的渲染适配器。 */
@ -36,26 +43,36 @@ public:
~nmPebiResultSnapshotRenderAdapter(); ~nmPebiResultSnapshotRenderAdapter();
/** /**
* @brief 绑定一套完整快照并建立不含 CellData 的渲染网格结构。 * @brief 绑定快照并建立仅含内部单元映射的渲染网格结构。
* @param pSnapshot 调用期间保持只读的结果快照。 * @param pSnapshot 调用期间保持只读的结果快照。
* @param eBindingMode 共享压力或单帧复制模式。 * @param eBindingMode 共享属性或单帧复制模式。
*/ */
bool initialize(
const QSharedPointer<const nmPebiResultSnapshot>& pSnapshot,
PropertyBindingMode eBindingMode);
/** @brief 使用旧压力枚举初始化,行为与通用绑定模式一致。 */
bool initialize( bool initialize(
const QSharedPointer<const nmPebiResultSnapshot>& pSnapshot, const QSharedPointer<const nmPebiResultSnapshot>& pSnapshot,
PressureBindingMode eBindingMode); PressureBindingMode eBindingMode);
/** @brief 解除输出、压力数组和快照引用,使适配器恢复为空状态。 */ /** @brief 解除输出、属性数组和快照引用,使适配器恢复为空状态。 */
void clear(); void clear();
/** @brief 事务式发布指定属性帧,失败时保持原画面和状态不变。 */
bool setPropertyFrame(const QString& sPropertyId, int nFrameIndex);
/** @brief 将指定压力帧发布到当前 VTK 输出端口。 */ /** @brief 将指定压力帧发布到当前 VTK 输出端口。 */
bool setPressureFrame(int nFrameIndex); bool setPressureFrame(int nFrameIndex);
/** @brief 返回供 Mapper 或过滤器建立连接的 VTK 输出端口。 */ /** @brief 返回供 Mapper 或过滤器建立连接的 VTK 输出端口。 */
vtkAlgorithmOutput* getOutputPort() const; vtkAlgorithmOutput* getOutputPort() const;
/** @brief 返回当前已经绑定的压力帧下标,尚未切帧时为 -1。 */ /** @brief 返回当前已经绑定的压力帧下标,尚未切帧时为 -1。 */
int getCurrentFrameIndex() const; int getCurrentFrameIndex() const;
/** @brief 返回当前已经绑定的属性 ID,尚未绑定时为空。 */
QString getCurrentPropertyId() const;
/** @brief 返回适配器渲染网格的单元数。 */ /** @brief 返回适配器渲染网格的单元数。 */
vtkIdType getCellCount() const; vtkIdType getCellCount() const;
/** @brief 返回当前是否使用零整帧复制的共享压力模式。 */ /** @brief 返回当前是否使用零整帧复制的共享压力模式。 */
bool isUsingSharedPressure() const; bool isUsingSharedPressure() const;
/** @brief 返回当前是否使用零整帧复制的共享属性模式。 */
bool isUsingSharedProperties() const;
private: private:
/** @brief 禁止复制包含 VTK 管道和快照引用的适配器。 */ /** @brief 禁止复制包含 VTK 管道和快照引用的适配器。 */
@ -70,13 +87,17 @@ private:
QSharedPointer<const nmPebiResultSnapshot> m_pSnapshot; QSharedPointer<const nmPebiResultSnapshot> m_pSnapshot;
/** @brief 适配器独占的渲染网格,结构可与快照基础网格共享。 */ /** @brief 适配器独占的渲染网格,结构可与快照基础网格共享。 */
vtkSmartPointer<vtkUnstructuredGrid> m_pRenderGrid; vtkSmartPointer<vtkUnstructuredGrid> m_pRenderGrid;
/** @brief 复制模式复用的单帧压力缓冲。 */ /** @brief 复制模式当前绑定到 CellData 的活动缓冲。 */
vtkSmartPointer<vtkDoubleArray> m_pPressureBuffer; vtkSmartPointer<vtkDoubleArray> m_pActiveBuffer;
/** @brief 复制模式在切换成功前独立填充和校验的备用缓冲。 */
vtkSmartPointer<vtkDoubleArray> m_pStandbyBuffer;
/** @brief 将独立渲染网格接入 VTK 管道的生产者。 */ /** @brief 将独立渲染网格接入 VTK 管道的生产者。 */
vtkSmartPointer<vtkTrivialProducer> m_pProducer; vtkSmartPointer<vtkTrivialProducer> m_pProducer;
/** @brief 当前压力帧绑定方式。 */ /** @brief 当前属性数组绑定方式。 */
PressureBindingMode m_eBindingMode; PropertyBindingMode m_eBindingMode;
/** @brief 当前已经绑定的压力帧下标。 */ /** @brief 当前已经绑定的属性 ID。 */
QString m_sCurrentPropertyId;
/** @brief 当前已经绑定的属性帧下标。 */
int m_nCurrentFrameIndex; int m_nCurrentFrameIndex;
}; };

@ -11,7 +11,7 @@
class nmPebiResultSnapshot; class nmPebiResultSnapshot;
/** /**
* @brief 负责当前快照三文件载荷及 Snapshot.json 的流式保存和加载。 * @brief 负责当前快照四文件载荷及 Snapshot.json 的流式保存和加载。
* *
* 序列化器是快照的受限友元,只在保存时读取内部 VTK 对象;加载仍通过 * 序列化器是快照的受限友元,只在保存时读取内部 VTK 对象;加载仍通过
* nmPebiResultSnapshotBuilder 构造和封闭候选。 * nmPebiResultSnapshotBuilder 构造和封闭候选。
@ -26,13 +26,13 @@ public:
nmNumericalFileReference& oSnapshotJsonReference, nmNumericalFileReference& oSnapshotJsonReference,
QString* pError = NULL); QString* pError = NULL);
/** @brief 校验 Snapshot.json 及其三条引用,不创建快照对象。 */ /** @brief 校验 Snapshot.json 及其四条引用,不发布快照对象。 */
static bool validate( static bool validate(
const QString& sWindowDirectory, const QString& sWindowDirectory,
const nmNumericalFileReference& oSnapshotJsonReference, const nmNumericalFileReference& oSnapshotJsonReference,
QString* pError = NULL); QString* pError = NULL);
/** @brief 返回 Snapshot.json 中三个规范化载荷路径,供窗口级全局查重。 */ /** @brief 返回 Snapshot.json 中四个规范化载荷路径,供窗口级全局查重。 */
static bool getPayloadRelativePaths( static bool getPayloadRelativePaths(
const QString& sWindowDirectory, const QString& sWindowDirectory,
const nmNumericalFileReference& oSnapshotJsonReference, const nmNumericalFileReference& oSnapshotJsonReference,
@ -47,6 +47,11 @@ public:
QString* pError = NULL); QString* pError = NULL);
private: private:
/** @brief 写出原始 PEBI 单元顺序的三项规范静态属性。 */
static bool writeStaticProperties(
const nmPebiResultSnapshot& oSnapshot,
const QString& sFilePath,
QString* pError);
/** @brief 从快照内部数组流式写出全部压力帧。 */ /** @brief 从快照内部数组流式写出全部压力帧。 */
static bool writePressureFrames(const nmPebiResultSnapshot& oSnapshot, static bool writePressureFrames(const nmPebiResultSnapshot& oSnapshot,
const QString& sFilePath, const QString& sFilePath,

@ -15,6 +15,7 @@
#include "nmDataFault.h" #include "nmDataFault.h"
#include "nmDataTimeStepSetting.h" #include "nmDataTimeStepSetting.h"
#include "nmPebiResultSnapshotBuilder.h" #include "nmPebiResultSnapshotBuilder.h"
#include "nmPebiCellMapping.h"
#include "nmPebiStaticPropertyCalculator.h" #include "nmPebiStaticPropertyCalculator.h"
#include "nmCalculationPebiGrid.h" #include "nmCalculationPebiGrid.h"
@ -1460,7 +1461,7 @@ bool nmCalculationDllPebiSolverTask::execPebiMode()
const bool bSucceeded = buildPebiModeResult( const bool bSucceeded = buildPebiModeResult(
oModelOutput, oModelOutput,
oModelInput.T, oModelInput.T,
oGridResult.m_oGridOutput1); oStaticProperties);
if(!bSucceeded && !isCancelRequested() && m_sFailureReason.isEmpty()) { if(!bSucceeded && !isCancelRequested() && m_sFailureReason.isEmpty()) {
fail(tr("The solver result data is invalid.")); fail(tr("The solver result data is invalid."));
} }
@ -1491,7 +1492,7 @@ std::vector<double> HX_logderivative(const std::vector<double>& x, const std::ve
bool nmCalculationDllPebiSolverTask::buildPebiModeResult( bool nmCalculationDllPebiSolverTask::buildPebiModeResult(
HX_NWTM_MODEL_OUTPUT& p1, HX_NWTM_MODEL_OUTPUT& p1,
int modelType, int modelType,
const HX_NWTM_GRID_OUTPUT1& oGridOutput) nmPebiStaticPropertyValues& oStaticProperties)
{ {
if(m_pInputSnapshot == nullptr) { if(m_pInputSnapshot == nullptr) {
return fail(tr("The solver result data is invalid.")); return fail(tr("The solver result data is invalid."));
@ -1821,47 +1822,51 @@ bool nmCalculationDllPebiSolverTask::buildPebiModeResult(
: fail(tr("The calculated pressure curves are invalid.")); : fail(tr("The calculated pressure curves are invalid."));
} }
// 第四步:完整求解还必须具有与时间轴、网格绘图单元严格对应的场压力。 // 第四步:完整求解只接受携带有效 OriginalCellId 的冻结基础网格。
// 场压力按同一映射过滤,不能再根据 isplot 顺序推测 VTK Cell 身份。
if(p1.p.size() != p1.t.size() || if(p1.p.size() != p1.t.size() ||
oGridOutput.PEBI_cell.isplot.empty()) { m_pInputSnapshot->m_pBaseGrid == nullptr ||
m_pInputSnapshot->m_pBaseGrid->GetNumberOfCells() <= 0) {
return fail(tr("The solver returned incomplete field pressure data.")); return fail(tr("The solver returned incomplete field pressure data."));
} }
QVector<vtkIdType> vecVtkToOriginal;
// 计算有效单元数量 QVector<vtkIdType> vecOriginalToVtk;
size_t actualPlotCellsCount = 0; QString sMappingError;
if(nmPebiCellMapping::read(m_pInputSnapshot->m_pBaseGrid,
for(size_t i = 0; i < oGridOutput.PEBI_cell.isplot.size(); ++i) { &vecVtkToOriginal, &vecOriginalToVtk, &sMappingError) !=
if((i % 256) == 0 && isCancelRequested()) { nmPebiCellMapping::MappingStatus_Valid ||
return false; vecVtkToOriginal.isEmpty() || vecOriginalToVtk.isEmpty() ||
} vecVtkToOriginal.size() !=
if(oGridOutput.PEBI_cell.isplot[i] == 1) { m_pInputSnapshot->m_pBaseGrid->GetNumberOfCells()) {
actualPlotCellsCount++; qWarning() << "Invalid PEBI result cell mapping:" << sMappingError;
}
}
if(actualPlotCellsCount == 0) {
return fail(tr("The generated grid is invalid.")); return fail(tr("The generated grid is invalid."));
} }
const int nOriginalCellCount = vecOriginalToVtk.size();
const int nVisibleCellCount = vecVtkToOriginal.size();
for(size_t nTimeIndex = 0; nTimeIndex < p1.p.size(); ++nTimeIndex) { for(size_t nTimeIndex = 0; nTimeIndex < p1.p.size(); ++nTimeIndex) {
if(isCancelRequested()) { if(isCancelRequested()) {
return false; return false;
} }
if(p1.p[nTimeIndex].size() < oGridOutput.PEBI_cell.isplot.size()) { if(p1.p[nTimeIndex].size() !=
static_cast<size_t>(nOriginalCellCount)) {
qWarning() << "Incomplete field pressure returned for time index:" qWarning() << "Incomplete field pressure returned for time index:"
<< static_cast<int>(nTimeIndex); << static_cast<int>(nTimeIndex);
return fail(tr("The solver returned incomplete field pressure data.")); return fail(tr("The solver returned incomplete field pressure data."));
} }
for(size_t nCellIndex = 0; for(int nVtkCellId = 0;
nCellIndex < oGridOutput.PEBI_cell.isplot.size(); nVtkCellId < nVisibleCellCount; ++nVtkCellId) {
++nCellIndex) { if((nVtkCellId % 256) == 0 && isCancelRequested()) {
if((nCellIndex % 256) == 0 && isCancelRequested()) {
return false; return false;
} }
if(oGridOutput.PEBI_cell.isplot[nCellIndex] == 1 && const vtkIdType nOriginalCellId =
!isFiniteSolverNumber(p1.p[nTimeIndex][nCellIndex])) { vecVtkToOriginal[nVtkCellId];
if(nOriginalCellId < 0 ||
nOriginalCellId >= nOriginalCellCount ||
!isFiniteSolverNumber(p1.p[nTimeIndex][
static_cast<size_t>(nOriginalCellId)])) {
qWarning() << "PEBI returned a non-finite field pressure at time/cell:" qWarning() << "PEBI returned a non-finite field pressure at time/cell:"
<< static_cast<int>(nTimeIndex) << static_cast<int>(nTimeIndex)
<< static_cast<int>(nCellIndex); << static_cast<int>(nOriginalCellId);
return fail(tr("The solver returned invalid field pressure data.")); return fail(tr("The solver returned invalid field pressure data."));
} }
} }
@ -1888,27 +1893,24 @@ bool nmCalculationDllPebiSolverTask::buildPebiModeResult(
vtkSmartPointer<vtkDoubleArray> pressureData = vtkSmartPointer<vtkDoubleArray>::New(); vtkSmartPointer<vtkDoubleArray> pressureData = vtkSmartPointer<vtkDoubleArray>::New();
pressureData->SetName("p"); // 设置标量数据的名称 pressureData->SetName("p"); // 设置标量数据的名称
// 直接使用外面计算好的 actualPlotCellsCount pressureData->SetNumberOfComponents(1);
pressureData->SetNumberOfValues(actualPlotCellsCount); pressureData->SetNumberOfValues(nVisibleCellCount);
size_t destIdx = 0; // 目标数组的索引
double dCurrentTimeMin = DBL_MAX; // 当前时间步的最小值 double dCurrentTimeMin = DBL_MAX; // 当前时间步的最小值
double dCurrentTimeMax = -DBL_MAX; // 当前时间步的最大值 double dCurrentTimeMax = -DBL_MAX; // 当前时间步的最大值
// 遍历原始数据,并根据 isplot 填充到 pressureData for(int nVtkCellId = 0;
for(size_t i = 0; i < oGridOutput.PEBI_cell.isplot.size(); ++i) { nVtkCellId < nVisibleCellCount; ++nVtkCellId) {
if((i % 256) == 0 && isCancelRequested()) { if((nVtkCellId % 256) == 0 && isCancelRequested()) {
return false; return false;
} }
if(oGridOutput.PEBI_cell.isplot[i] == 1) { // 只有当 isplot 为1时才考虑这个单元格 const vtkIdType nOriginalCellId =
pressureData->SetValue(destIdx, p1.p[timeIdx][i]); vecVtkToOriginal[nVtkCellId];
const double dPressure = p1.p[timeIdx][
// 更新当前时间步的范围 static_cast<size_t>(nOriginalCellId)];
dCurrentTimeMin = qMin(dCurrentTimeMin, p1.p[timeIdx][i]); pressureData->SetValue(nVtkCellId, dPressure);
dCurrentTimeMax = qMax(dCurrentTimeMax, p1.p[timeIdx][i]); dCurrentTimeMin = qMin(dCurrentTimeMin, dPressure);
dCurrentTimeMax = qMax(dCurrentTimeMax, dPressure);
destIdx++;
}
} }
// 更新全局范围 // 更新全局范围
@ -1951,7 +1953,8 @@ bool nmCalculationDllPebiSolverTask::buildPebiModeResult(
m_dPendingScalarMin = dMinP; m_dPendingScalarMin = dMinP;
m_dPendingScalarMax = dMaxP; m_dPendingScalarMax = dMaxP;
m_bPendingFullResultReady = buildPebiResultSnapshotCandidate(); m_bPendingFullResultReady =
buildPebiResultSnapshotCandidate(oStaticProperties);
if(!m_bPendingFullResultReady) { if(!m_bPendingFullResultReady) {
// 候选结构或内存校验失败时立即释放大结果,旧快照保持原状。 // 候选结构或内存校验失败时立即释放大结果,旧快照保持原状。
fail(tr("Failed to build the result snapshot.")); fail(tr("Failed to build the result snapshot."));
@ -1978,7 +1981,8 @@ void nmCalculationDllPebiSolverTask::discardPendingFullResult()
} }
} }
bool nmCalculationDllPebiSolverTask::buildPebiResultSnapshotCandidate() bool nmCalculationDllPebiSolverTask::buildPebiResultSnapshotCandidate(
nmPebiStaticPropertyValues& oStaticProperties)
{ {
if(m_pInputSnapshot == NULL || if(m_pInputSnapshot == NULL ||
m_pInputSnapshot->m_bAutoFitTargetOnly || m_pInputSnapshot->m_bAutoFitTargetOnly ||
@ -2000,6 +2004,9 @@ bool nmCalculationDllPebiSolverTask::buildPebiResultSnapshotCandidate()
if(!oBuilder.takeResultGrid(m_pInputSnapshot->m_pBaseGrid)) { if(!oBuilder.takeResultGrid(m_pInputSnapshot->m_pBaseGrid)) {
return false; return false;
} }
if(!oBuilder.takeStaticProperties(oStaticProperties)) {
return false;
}
QMap<double, vtkSmartPointer<vtkDoubleArray> >::const_iterator QMap<double, vtkSmartPointer<vtkDoubleArray> >::const_iterator
oTimeIt = m_mapPendingTimeSteps.constBegin(); oTimeIt = m_mapPendingTimeSteps.constBegin();

@ -1,11 +1,49 @@
#include "nmPebiResultSnapshot.h" #include "nmPebiResultSnapshot.h"
#include "nmPebiCellMapping.h"
#include "nmPebiResultPropertyCatalog.h"
#include <vtkAbstractArray.h>
#include <vtkCellData.h> #include <vtkCellData.h>
#include <vtkDataArray.h>
#include <vtkDoubleArray.h> #include <vtkDoubleArray.h>
#include <vtkFieldData.h>
#include <vtkUnstructuredGrid.h> #include <vtkUnstructuredGrid.h>
#include <QUuid> #include <QUuid>
namespace {
bool deepCopyNamedArray(vtkFieldData* pSource,
vtkFieldData* pTarget,
const char* pArrayName)
{
if(pSource == NULL || pTarget == NULL || pArrayName == NULL ||
pTarget->GetAbstractArray(pArrayName) != NULL)
{
return false;
}
vtkAbstractArray* pSourceArray =
pSource->GetAbstractArray(pArrayName);
if(pSourceArray == NULL)
{
return false;
}
vtkSmartPointer<vtkAbstractArray> pTargetArray;
pTargetArray.TakeReference(pSourceArray->NewInstance());
if(pTargetArray == NULL)
{
return false;
}
pTargetArray->DeepCopy(pSourceArray);
return pTarget->AddArray(pTargetArray) >= 0 &&
pTarget->GetAbstractArray(pArrayName) == pTargetArray.GetPointer();
}
}
// VTK 结构和压力操作仅供渲染适配器调用,普通结果读取接口不暴露可写对象。 // VTK 结构和压力操作仅供渲染适配器调用,普通结果读取接口不暴露可写对象。
nmPebiResultSolverSlot::nmPebiResultSolverSlot() nmPebiResultSolverSlot::nmPebiResultSolverSlot()
: m_nSolverIndex(-1), : m_nSolverIndex(-1),
@ -86,6 +124,11 @@ nmPebiResultSnapshot::nmPebiResultSnapshot()
m_dScalarMin(0.0), m_dScalarMin(0.0),
m_dScalarMax(0.0) m_dScalarMax(0.0)
{ {
for(int nPropertyIndex = 0; nPropertyIndex < 3; ++nPropertyIndex)
{
m_aStaticRanges[nPropertyIndex][0] = 0.0;
m_aStaticRanges[nPropertyIndex][1] = 0.0;
}
} }
nmPebiResultSnapshot::~nmPebiResultSnapshot() nmPebiResultSnapshot::~nmPebiResultSnapshot()
@ -171,6 +214,107 @@ vtkIdType nmPebiResultSnapshot::getResultCellCount() const
return m_pResultGrid != NULL ? m_pResultGrid->GetNumberOfCells() : 0; return m_pResultGrid != NULL ? m_pResultGrid->GetNumberOfCells() : 0;
} }
QStringList nmPebiResultSnapshot::getAvailablePropertyIds() const
{
QStringList listPropertyIds;
if(!m_bComplete)
{
return listPropertyIds;
}
const QList<nmPebiResultPropertyDescriptor> listDescriptors =
nmPebiResultPropertyCatalog::descriptors();
for(int nIndex = 0; nIndex < listDescriptors.size(); ++nIndex)
{
if(hasProperty(listDescriptors[nIndex].m_sPropertyId))
{
listPropertyIds.append(listDescriptors[nIndex].m_sPropertyId);
}
}
return listPropertyIds;
}
bool nmPebiResultSnapshot::hasProperty(const QString& sPropertyId) const
{
if(!m_bComplete)
{
return false;
}
if(sPropertyId == QString::fromLatin1("permeability"))
{
return m_pPermeability != NULL;
}
if(sPropertyId == QString::fromLatin1("porosity"))
{
return m_pPorosity != NULL;
}
if(sPropertyId == QString::fromLatin1("thickness"))
{
return m_pThickness != NULL;
}
if(sPropertyId == QString::fromLatin1("pressure"))
{
return !m_vecPressureFrames.isEmpty();
}
return false;
}
int nmPebiResultSnapshot::getPropertyFrameCount(
const QString& sPropertyId) const
{
if(!hasProperty(sPropertyId))
{
return 0;
}
return sPropertyId == QString::fromLatin1("pressure")
? m_vecPressureFrames.size() : 1;
}
bool nmPebiResultSnapshot::getPropertyTimeAt(
const QString& sPropertyId,
int nFrameIndex,
double& dTime) const
{
if(sPropertyId != QString::fromLatin1("pressure") ||
!hasProperty(sPropertyId) || nFrameIndex < 0 ||
nFrameIndex >= m_vecPressureFrames.size())
{
return false;
}
dTime = m_vecPressureFrames[nFrameIndex].m_dTime;
return true;
}
bool nmPebiResultSnapshot::getPropertyRange(
const QString& sPropertyId,
double aRange[2]) const
{
if(aRange == NULL || !hasProperty(sPropertyId))
{
return false;
}
if(sPropertyId == QString::fromLatin1("permeability"))
{
aRange[0] = m_aStaticRanges[0][0];
aRange[1] = m_aStaticRanges[0][1];
return true;
}
if(sPropertyId == QString::fromLatin1("porosity"))
{
aRange[0] = m_aStaticRanges[1][0];
aRange[1] = m_aStaticRanges[1][1];
return true;
}
if(sPropertyId == QString::fromLatin1("thickness"))
{
aRange[0] = m_aStaticRanges[2][0];
aRange[1] = m_aStaticRanges[2][1];
return true;
}
aRange[0] = m_dScalarMin;
aRange[1] = m_dScalarMax;
return true;
}
int nmPebiResultSnapshot::getPressureFrameCount() const int nmPebiResultSnapshot::getPressureFrameCount() const
{ {
return m_vecPressureFrames.size(); return m_vecPressureFrames.size();
@ -187,13 +331,7 @@ double nmPebiResultSnapshot::getPressureTimeAt(int nIndex) const
bool nmPebiResultSnapshot::getScalarRange(double aRange[2]) const bool nmPebiResultSnapshot::getScalarRange(double aRange[2]) const
{ {
if(aRange == NULL || !m_bComplete) return getPropertyRange(QString::fromLatin1("pressure"), aRange);
{
return false;
}
aRange[0] = m_dScalarMin;
aRange[1] = m_dScalarMax;
return true;
} }
const nmPebiResultReservoirParameters& const nmPebiResultReservoirParameters&
@ -222,61 +360,117 @@ bool nmPebiResultSnapshot::copyResultGridStructure(
} }
// CopyStructure 在当前 VTK 7.1 中共享点和单元拓扑,不复制大网格。 // CopyStructure 在当前 VTK 7.1 中共享点和单元拓扑,不复制大网格。
// 随后清空目标 CellData,确保压力只能通过受控接口按帧挂载。 // 结果属性仍由受控接口按帧挂载;内部映射单独深拷贝,避免渲染端
// 修改数组时污染不可变快照。
pTargetGrid->Initialize(); pTargetGrid->Initialize();
pTargetGrid->CopyStructure(m_pResultGrid); pTargetGrid->CopyStructure(m_pResultGrid);
pTargetGrid->GetCellData()->Initialize(); pTargetGrid->GetCellData()->Initialize();
return pTargetGrid->GetNumberOfCells() == pTargetGrid->GetFieldData()->Initialize();
m_pResultGrid->GetNumberOfCells(); if(pTargetGrid->GetNumberOfCells() !=
m_pResultGrid->GetNumberOfCells() ||
!deepCopyNamedArray(m_pResultGrid->GetCellData(),
pTargetGrid->GetCellData(),
nmPebiCellMapping::originalCellIdArrayName()) ||
!deepCopyNamedArray(m_pResultGrid->GetFieldData(),
pTargetGrid->GetFieldData(),
nmPebiCellMapping::originalCellCountArrayName()) ||
pTargetGrid->GetCellData()->GetScalars() != NULL)
{
pTargetGrid->Initialize();
return false;
}
return true;
} }
bool nmPebiResultSnapshot::bindPressureFrame( bool nmPebiResultSnapshot::bindPropertyFrame(
int nIndex, const QString& sPropertyId,
int nFrameIndex,
vtkUnstructuredGrid* pTargetGrid) const vtkUnstructuredGrid* pTargetGrid) const
{ {
if(!m_bComplete || pTargetGrid == NULL || nIndex < 0 || if(!m_bComplete || pTargetGrid == NULL || nFrameIndex < 0 ||
nIndex >= m_vecPressureFrames.size() || nFrameIndex >= getPropertyFrameCount(sPropertyId) ||
pTargetGrid->GetNumberOfCells() != getResultCellCount()) pTargetGrid->GetNumberOfCells() != getResultCellCount())
{ {
return false; return false;
} }
vtkDoubleArray* pPressure = vtkDoubleArray* pScalars = NULL;
m_vecPressureFrames[nIndex].m_pPressure.GetPointer(); if(sPropertyId == QString::fromLatin1("permeability"))
if(pPressure == NULL || {
pPressure->GetNumberOfTuples() != getResultCellCount()) pScalars = m_pPermeability.GetPointer();
}
else if(sPropertyId == QString::fromLatin1("porosity"))
{
pScalars = m_pPorosity.GetPointer();
}
else if(sPropertyId == QString::fromLatin1("thickness"))
{
pScalars = m_pThickness.GetPointer();
}
else if(sPropertyId == QString::fromLatin1("pressure"))
{
pScalars = m_vecPressureFrames[nFrameIndex].m_pPressure.GetPointer();
}
const QString sExpectedArrayName =
sPropertyId == QString::fromLatin1("pressure")
? QString::fromLatin1("p") : sPropertyId;
if(pScalars == NULL || pScalars->GetName() == NULL ||
QString::fromLatin1(pScalars->GetName()) != sExpectedArrayName ||
pScalars->GetNumberOfComponents() != 1 ||
pScalars->GetNumberOfTuples() != getResultCellCount())
{ {
return false; return false;
} }
// SetScalars 会增加 VTK 引用计数,压力的规范所有权仍属于快照。 // 共享模式只在完整校验后替换活动数组;事后校验失败时恢复旧引用。
// 渲染网格销毁或切换快照时应及时解除这个临时引用。 vtkDataArray* pPreviousScalars =
pTargetGrid->GetCellData()->SetScalars(pPressure); pTargetGrid->GetCellData()->GetScalars();
pTargetGrid->GetCellData()->SetScalars(pScalars);
if(pTargetGrid->GetCellData()->GetScalars() != pScalars)
{
pTargetGrid->GetCellData()->SetScalars(pPreviousScalars);
return false;
}
pTargetGrid->Modified(); pTargetGrid->Modified();
return pTargetGrid->GetCellData()->GetScalars() == pPressure; return true;
} }
bool nmPebiResultSnapshot::copyPressureFrame( bool nmPebiResultSnapshot::copyPropertyFrame(
int nIndex, const QString& sPropertyId,
vtkDoubleArray* pTargetPressure) const int nFrameIndex,
vtkDoubleArray* pTargetScalars) const
{ {
if(!m_bComplete || pTargetPressure == NULL || nIndex < 0 || if(!m_bComplete || pTargetScalars == NULL || nFrameIndex < 0 ||
nIndex >= m_vecPressureFrames.size()) nFrameIndex >= getPropertyFrameCount(sPropertyId))
{ {
return false; return false;
} }
vtkDoubleArray* pSourcePressure = vtkDoubleArray* pSourceScalars = NULL;
m_vecPressureFrames[nIndex].m_pPressure.GetPointer(); if(sPropertyId == QString::fromLatin1("permeability"))
if(pSourcePressure == NULL) {
pSourceScalars = m_pPermeability.GetPointer();
}
else if(sPropertyId == QString::fromLatin1("porosity"))
{
pSourceScalars = m_pPorosity.GetPointer();
}
else if(sPropertyId == QString::fromLatin1("thickness"))
{
pSourceScalars = m_pThickness.GetPointer();
}
else if(sPropertyId == QString::fromLatin1("pressure"))
{
pSourceScalars =
m_vecPressureFrames[nFrameIndex].m_pPressure.GetPointer();
}
if(pSourceScalars == NULL)
{ {
return false; return false;
} }
// 回退路径只复制当前帧,调用方应在动画期间复用同一个缓冲数组。 pTargetScalars->DeepCopy(pSourceScalars);
pTargetPressure->DeepCopy(pSourcePressure); return pTargetScalars->GetNumberOfComponents() == 1 &&
pTargetPressure->SetName("p"); pTargetScalars->GetNumberOfTuples() == getResultCellCount();
return pTargetPressure->GetNumberOfComponents() == 1 &&
pTargetPressure->GetNumberOfTuples() == getResultCellCount();
} }

@ -1,5 +1,7 @@
#include "nmPebiResultSnapshotBuilder.h" #include "nmPebiResultSnapshotBuilder.h"
#include "nmPebiCellMapping.h"
#include <QSet> #include <QSet>
#include <QUuid> #include <QUuid>
#include <QtGlobal> #include <QtGlobal>
@ -30,6 +32,22 @@ bool isFiniteValue(double dValue)
#endif #endif
} }
// 这四个名称由结果快照独占。基础网格若已携带同名数组,无法判断其
// 来源和语义,必须在接管所有权前拒绝,而不是在 finalize() 中静默删除。
bool hasReservedResultPropertyArray(vtkUnstructuredGrid* pGrid)
{
if(pGrid == NULL || pGrid->GetCellData() == NULL)
{
return false;
}
vtkCellData* pCellData = pGrid->GetCellData();
return pCellData->GetAbstractArray("p") != NULL ||
pCellData->GetAbstractArray("permeability") != NULL ||
pCellData->GetAbstractArray("porosity") != NULL ||
pCellData->GetAbstractArray("thickness") != NULL;
}
bool isValidInstanceId(const QString& sInstanceId) bool isValidInstanceId(const QString& sInstanceId)
{ {
return !sInstanceId.isEmpty() && !QUuid(sInstanceId).isNull(); return !sInstanceId.isEmpty() && !QUuid(sInstanceId).isNull();
@ -377,15 +395,111 @@ bool areSolverSettingsValid(const nmPebiResultSolverSettings& oSettings)
oSettings.m_dMaxDeltaT >= oSettings.m_dMinDeltaT); oSettings.m_dMaxDeltaT >= oSettings.m_dMinDeltaT);
} }
bool buildStaticPropertyArray(
const QVector<double>& vecOriginalValues,
const QVector<vtkIdType>& vecVtkToOriginal,
const char* pArrayName,
vtkSmartPointer<vtkDoubleArray>& pArray,
double aRange[2])
{
if(pArrayName == NULL || vecOriginalValues.isEmpty() ||
vecVtkToOriginal.isEmpty() || aRange == NULL)
{
return false;
}
vtkSmartPointer<vtkDoubleArray> pCandidate =
vtkSmartPointer<vtkDoubleArray>::New();
if(pCandidate == NULL)
{
return false;
}
pCandidate->SetName(pArrayName);
pCandidate->SetNumberOfComponents(1);
pCandidate->SetNumberOfTuples(vecVtkToOriginal.size());
double dMinimum = DBL_MAX;
double dMaximum = -DBL_MAX;
for(int nVtkCellId = 0;
nVtkCellId < vecVtkToOriginal.size(); ++nVtkCellId)
{
const vtkIdType nOriginalCellId = vecVtkToOriginal[nVtkCellId];
if(nOriginalCellId < 0 ||
nOriginalCellId >= vecOriginalValues.size())
{
return false;
}
const double dValue =
vecOriginalValues[static_cast<int>(nOriginalCellId)];
if(!isFiniteValue(dValue))
{
return false;
}
pCandidate->SetValue(nVtkCellId, dValue);
dMinimum = qMin(dMinimum, dValue);
dMaximum = qMax(dMaximum, dValue);
}
if(pCandidate->GetNumberOfComponents() != 1 ||
pCandidate->GetNumberOfTuples() != vecVtkToOriginal.size() ||
dMinimum == DBL_MAX || dMaximum == -DBL_MAX)
{
return false;
}
pArray = pCandidate;
aRange[0] = dMinimum;
aRange[1] = dMaximum;
return true;
}
bool validateStaticPropertyArray(
const QVector<double>& vecOriginalValues,
const QVector<vtkIdType>& vecVtkToOriginal,
vtkDoubleArray* pArray,
const char* pArrayName,
const double aRange[2])
{
if(pArray == NULL || pArrayName == NULL || aRange == NULL ||
pArray->GetName() == NULL ||
QString::fromLatin1(pArray->GetName()) !=
QString::fromLatin1(pArrayName) ||
pArray->GetNumberOfComponents() != 1 ||
pArray->GetNumberOfTuples() != vecVtkToOriginal.size())
{
return false;
}
double dMinimum = DBL_MAX;
double dMaximum = -DBL_MAX;
for(int nVtkCellId = 0;
nVtkCellId < vecVtkToOriginal.size(); ++nVtkCellId)
{
const vtkIdType nOriginalCellId = vecVtkToOriginal[nVtkCellId];
if(nOriginalCellId < 0 ||
nOriginalCellId >= vecOriginalValues.size())
{
return false;
}
const double dExpected =
vecOriginalValues[static_cast<int>(nOriginalCellId)];
const double dActual = pArray->GetValue(nVtkCellId);
if(!isFiniteValue(dActual) || dActual != dExpected)
{
return false;
}
dMinimum = qMin(dMinimum, dActual);
dMaximum = qMax(dMaximum, dActual);
}
return dMinimum == aRange[0] && dMaximum == aRange[1];
}
} }
nmPebiResultSnapshotBuilder::nmPebiResultSnapshotBuilder() nmPebiResultSnapshotBuilder::nmPebiResultSnapshotBuilder()
: m_bFinalized(false), : m_bFinalized(false),
m_bFailed(false), m_bFailed(false),
m_bHasScalarRange(false), m_bHasScalarRange(false),
m_bHasStaticProperties(false),
m_bHasReservoirParameters(false), m_bHasReservoirParameters(false),
m_bHasPvtParameters(false), m_bHasPvtParameters(false),
m_bHasSolverSettings(false) m_bHasSolverSettings(false),
m_nOriginalCellCount(0)
{ {
try try
{ {
@ -460,16 +574,93 @@ bool nmPebiResultSnapshotBuilder::takeResultGrid(
vtkSmartPointer<vtkUnstructuredGrid>& pResultGrid) vtkSmartPointer<vtkUnstructuredGrid>& pResultGrid)
{ {
if(!canWrite() || pResultGrid == NULL || if(!canWrite() || pResultGrid == NULL ||
pResultGrid->GetNumberOfCells() <= 0) pResultGrid->GetNumberOfCells() <= 0 ||
m_pCandidate->m_pResultGrid != NULL)
{ {
return fail("Invalid PEBI result grid."); return fail("Invalid PEBI result grid.");
} }
if(hasReservedResultPropertyArray(pResultGrid))
{
return fail("PEBI result grid contains reserved property arrays.");
}
QVector<vtkIdType> vecVtkToOriginal;
QVector<vtkIdType> vecOriginalToVtk;
QString sMappingError;
if(nmPebiCellMapping::read(pResultGrid,
&vecVtkToOriginal, &vecOriginalToVtk, &sMappingError) !=
nmPebiCellMapping::MappingStatus_Valid ||
vecVtkToOriginal.size() != pResultGrid->GetNumberOfCells() ||
vecOriginalToVtk.isEmpty())
{
return fail(sMappingError.isEmpty()
? QString::fromLatin1("Invalid PEBI result cell mapping.")
: sMappingError);
}
// 通过交换所有权避免大网格 DeepCopy;成功后调用方不能再修改源网格。 // 通过交换所有权避免大网格 DeepCopy;成功后调用方不能再修改源网格。
m_vecVtkToOriginal.swap(vecVtkToOriginal);
m_nOriginalCellCount = vecOriginalToVtk.size();
m_pCandidate->m_pResultGrid = pResultGrid; m_pCandidate->m_pResultGrid = pResultGrid;
pResultGrid = NULL; pResultGrid = NULL;
return true; return true;
} }
bool nmPebiResultSnapshotBuilder::takeStaticProperties(
nmPebiStaticPropertyValues& oValues)
{
if(!canWrite() || m_pCandidate->m_pResultGrid == NULL ||
m_bHasStaticProperties || m_nOriginalCellCount <= 0 ||
!oValues.isValidForCellCount(
static_cast<int>(m_nOriginalCellCount)))
{
return fail("Invalid PEBI static property values.");
}
vtkSmartPointer<vtkDoubleArray> pPermeability;
vtkSmartPointer<vtkDoubleArray> pPorosity;
vtkSmartPointer<vtkDoubleArray> pThickness;
double aPermeabilityRange[2] = { 0.0, 0.0 };
double aPorosityRange[2] = { 0.0, 0.0 };
double aThicknessRange[2] = { 0.0, 0.0 };
if(!buildStaticPropertyArray(
oValues.m_vecPermeabilityMilliDarcy,
m_vecVtkToOriginal, "permeability",
pPermeability, aPermeabilityRange) ||
!buildStaticPropertyArray(
oValues.m_vecPorosity,
m_vecVtkToOriginal, "porosity",
pPorosity, aPorosityRange) ||
!buildStaticPropertyArray(
oValues.m_vecThicknessMeters,
m_vecVtkToOriginal, "thickness",
pThickness, aThicknessRange))
{
return fail("Cannot build PEBI static property render caches.");
}
// 到这里之后不再执行可能失败的操作,连续交换保证调用方只在成功时清空。
m_pCandidate->m_pPermeability = pPermeability;
m_pCandidate->m_pPorosity = pPorosity;
m_pCandidate->m_pThickness = pThickness;
for(int nRangeIndex = 0; nRangeIndex < 2; ++nRangeIndex)
{
m_pCandidate->m_aStaticRanges[0][nRangeIndex] =
aPermeabilityRange[nRangeIndex];
m_pCandidate->m_aStaticRanges[1][nRangeIndex] =
aPorosityRange[nRangeIndex];
m_pCandidate->m_aStaticRanges[2][nRangeIndex] =
aThicknessRange[nRangeIndex];
}
qSwap(m_pCandidate->m_oStaticProperties.
m_vecPermeabilityMilliDarcy,
oValues.m_vecPermeabilityMilliDarcy);
qSwap(m_pCandidate->m_oStaticProperties.m_vecPorosity,
oValues.m_vecPorosity);
qSwap(m_pCandidate->m_oStaticProperties.m_vecThicknessMeters,
oValues.m_vecThicknessMeters);
m_bHasStaticProperties = true;
return true;
}
bool nmPebiResultSnapshotBuilder::addPressureFrame( bool nmPebiResultSnapshotBuilder::addPressureFrame(
double dTime, double dTime,
vtkSmartPointer<vtkDoubleArray>& pPressure) vtkSmartPointer<vtkDoubleArray>& pPressure)
@ -711,6 +902,41 @@ bool nmPebiResultSnapshotBuilder::validate(QString* pError)
{ {
return fail("PEBI result grid is missing.", pError); return fail("PEBI result grid is missing.", pError);
} }
QVector<vtkIdType> vecVerifiedVtkToOriginal;
QVector<vtkIdType> vecVerifiedOriginalToVtk;
QString sMappingError;
if(nmPebiCellMapping::read(m_pCandidate->m_pResultGrid,
&vecVerifiedVtkToOriginal, &vecVerifiedOriginalToVtk,
&sMappingError) != nmPebiCellMapping::MappingStatus_Valid ||
vecVerifiedVtkToOriginal != m_vecVtkToOriginal ||
vecVerifiedOriginalToVtk.size() != m_nOriginalCellCount)
{
return fail(sMappingError.isEmpty()
? QString::fromLatin1(
"PEBI result cell mapping changed during construction.")
: sMappingError,
pError);
}
if(!m_bHasStaticProperties ||
!m_pCandidate->m_oStaticProperties.isValidForCellCount(
static_cast<int>(m_nOriginalCellCount)) ||
!validateStaticPropertyArray(
m_pCandidate->m_oStaticProperties.
m_vecPermeabilityMilliDarcy,
m_vecVtkToOriginal, m_pCandidate->m_pPermeability,
"permeability", m_pCandidate->m_aStaticRanges[0]) ||
!validateStaticPropertyArray(
m_pCandidate->m_oStaticProperties.m_vecPorosity,
m_vecVtkToOriginal, m_pCandidate->m_pPorosity,
"porosity", m_pCandidate->m_aStaticRanges[1]) ||
!validateStaticPropertyArray(
m_pCandidate->m_oStaticProperties.m_vecThicknessMeters,
m_vecVtkToOriginal, m_pCandidate->m_pThickness,
"thickness", m_pCandidate->m_aStaticRanges[2]))
{
return fail("PEBI static properties are incomplete or inconsistent.",
pError);
}
if(m_pCandidate->m_vecPressureFrames.isEmpty()) if(m_pCandidate->m_vecPressureFrames.isEmpty())
{ {
return fail("PEBI pressure frames are missing.", pError); return fail("PEBI pressure frames are missing.", pError);
@ -746,6 +972,9 @@ bool nmPebiResultSnapshotBuilder::validate(QString* pError)
if(!isFiniteValue(oFrame.m_dTime) || if(!isFiniteValue(oFrame.m_dTime) ||
(nIndex > 0 && oFrame.m_dTime <= dPreviousTime) || (nIndex > 0 && oFrame.m_dTime <= dPreviousTime) ||
oFrame.m_pPressure == NULL || oFrame.m_pPressure == NULL ||
oFrame.m_pPressure->GetName() == NULL ||
QString::fromLatin1(oFrame.m_pPressure->GetName()) !=
QString::fromLatin1("p") ||
oFrame.m_pPressure->GetNumberOfComponents() != 1 || oFrame.m_pPressure->GetNumberOfComponents() != 1 ||
oFrame.m_pPressure->GetNumberOfTuples() != nCellCount) oFrame.m_pPressure->GetNumberOfTuples() != nCellCount)
{ {
@ -885,13 +1114,12 @@ bool nmPebiResultSnapshotBuilder::validate(QString* pError)
setDisplayWellIds.insert(sWellInstanceId); setDisplayWellIds.insert(sWellInstanceId);
} }
// 发布前基础网格必须与压力帧解耦,渲染网格只能按帧受控绑定。 // finalize() 只验证候选,不再修改基础网格。正式结果数组必须由快照
m_pCandidate->m_pResultGrid->GetCellData()->SetScalars(NULL); // 独立持有,其他模块写入的非保留数组和活动属性则保持原样。
m_pCandidate->m_pResultGrid->GetCellData()->RemoveArray("p"); if(hasReservedResultPropertyArray(m_pCandidate->m_pResultGrid))
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 fail("PEBI result grid still owns result property scalars.",
pError);
} }
return true; return true;
} }

@ -2,15 +2,59 @@
#include <vtkAlgorithmOutput.h> #include <vtkAlgorithmOutput.h>
#include <vtkCellData.h> #include <vtkCellData.h>
#include <vtkDataArray.h>
#include <vtkDoubleArray.h> #include <vtkDoubleArray.h>
#include <vtkTrivialProducer.h> #include <vtkTrivialProducer.h>
#include <vtkUnstructuredGrid.h> #include <vtkUnstructuredGrid.h>
#include <QtGlobal>
#include <math.h>
#if !defined(_MSC_VER)
#include <cmath>
#endif
namespace {
bool isFiniteValue(double dValue)
{
#if defined(_MSC_VER)
return _finite(dValue) != 0;
#else
return std::isfinite(dValue);
#endif
}
bool isValidCopiedScalars(vtkDoubleArray* pScalars,
vtkIdType nExpectedCellCount,
const QString& sExpectedArrayName)
{
if(pScalars == NULL || pScalars->GetName() == NULL ||
QString::fromLatin1(pScalars->GetName()) != sExpectedArrayName ||
pScalars->GetNumberOfComponents() != 1 ||
pScalars->GetNumberOfTuples() != nExpectedCellCount)
{
return false;
}
for(vtkIdType nCellId = 0; nCellId < nExpectedCellCount; ++nCellId)
{
if(!isFiniteValue(pScalars->GetValue(nCellId)))
{
return false;
}
}
return true;
}
}
nmPebiResultSnapshotRenderAdapter::nmPebiResultSnapshotRenderAdapter() nmPebiResultSnapshotRenderAdapter::nmPebiResultSnapshotRenderAdapter()
: m_pRenderGrid(vtkSmartPointer<vtkUnstructuredGrid>::New()), : m_pRenderGrid(vtkSmartPointer<vtkUnstructuredGrid>::New()),
m_pPressureBuffer(vtkSmartPointer<vtkDoubleArray>::New()), m_pActiveBuffer(vtkSmartPointer<vtkDoubleArray>::New()),
m_pStandbyBuffer(vtkSmartPointer<vtkDoubleArray>::New()),
m_pProducer(vtkSmartPointer<vtkTrivialProducer>::New()), m_pProducer(vtkSmartPointer<vtkTrivialProducer>::New()),
m_eBindingMode(SharedPressureFrame), m_eBindingMode(SharedPropertyArray),
m_nCurrentFrameIndex(-1) m_nCurrentFrameIndex(-1)
{ {
} }
@ -22,12 +66,12 @@ nmPebiResultSnapshotRenderAdapter::~nmPebiResultSnapshotRenderAdapter()
bool nmPebiResultSnapshotRenderAdapter::initialize( bool nmPebiResultSnapshotRenderAdapter::initialize(
const QSharedPointer<const nmPebiResultSnapshot>& pSnapshot, const QSharedPointer<const nmPebiResultSnapshot>& pSnapshot,
PressureBindingMode eBindingMode) PropertyBindingMode eBindingMode)
{ {
clear(); clear();
if(pSnapshot.isNull() || !pSnapshot->isComplete() || if(pSnapshot.isNull() || !pSnapshot->isComplete() ||
(eBindingMode != SharedPressureFrame && (eBindingMode != SharedPropertyArray &&
eBindingMode != CopiedPressureFrame)) eBindingMode != CopiedPropertyArray))
{ {
return false; return false;
} }
@ -46,6 +90,20 @@ bool nmPebiResultSnapshotRenderAdapter::initialize(
return true; return true;
} }
bool nmPebiResultSnapshotRenderAdapter::initialize(
const QSharedPointer<const nmPebiResultSnapshot>& pSnapshot,
PressureBindingMode eBindingMode)
{
if(eBindingMode != SharedPressureFrame &&
eBindingMode != CopiedPressureFrame)
{
return false;
}
return initialize(pSnapshot,
eBindingMode == CopiedPressureFrame
? CopiedPropertyArray : SharedPropertyArray);
}
void nmPebiResultSnapshotRenderAdapter::clear() void nmPebiResultSnapshotRenderAdapter::clear()
{ {
// 先断开管道和 CellData,及时释放对旧快照压力数组的临时 VTK 引用。 // 先断开管道和 CellData,及时释放对旧快照压力数组的临时 VTK 引用。
@ -59,38 +117,62 @@ void nmPebiResultSnapshotRenderAdapter::clear()
m_pRenderGrid->GetCellData()->Initialize(); m_pRenderGrid->GetCellData()->Initialize();
m_pRenderGrid->Initialize(); m_pRenderGrid->Initialize();
} }
if(m_pPressureBuffer != NULL) if(m_pActiveBuffer != NULL)
{ {
m_pPressureBuffer->Initialize(); m_pActiveBuffer->Initialize();
}
if(m_pStandbyBuffer != NULL)
{
m_pStandbyBuffer->Initialize();
} }
m_pSnapshot.clear(); m_pSnapshot.clear();
m_eBindingMode = SharedPressureFrame; m_eBindingMode = SharedPropertyArray;
m_sCurrentPropertyId.clear();
m_nCurrentFrameIndex = -1; m_nCurrentFrameIndex = -1;
} }
bool nmPebiResultSnapshotRenderAdapter::setPressureFrame(int nFrameIndex) bool nmPebiResultSnapshotRenderAdapter::setPropertyFrame(
const QString& sPropertyId,
int nFrameIndex)
{ {
if(m_pSnapshot.isNull() || m_pRenderGrid == NULL || if(m_pSnapshot.isNull() || m_pRenderGrid == NULL ||
nFrameIndex < 0 || nFrameIndex < 0 || !m_pSnapshot->hasProperty(sPropertyId) ||
nFrameIndex >= m_pSnapshot->getPressureFrameCount()) nFrameIndex >= m_pSnapshot->getPropertyFrameCount(sPropertyId))
{ {
return false; return false;
} }
bool bBound = false; bool bBound = false;
if(m_eBindingMode == SharedPressureFrame) if(m_eBindingMode == SharedPropertyArray)
{ {
bBound = m_pSnapshot->bindPressureFrame( bBound = m_pSnapshot->bindPropertyFrame(
nFrameIndex, m_pRenderGrid); sPropertyId, nFrameIndex, m_pRenderGrid);
} }
else else
{ {
bBound = m_pSnapshot->copyPressureFrame( // 活动缓冲在整个准备阶段保持不变;只有备用缓冲完整有效后才切换。
nFrameIndex, m_pPressureBuffer); bBound = m_pSnapshot->copyPropertyFrame(
sPropertyId, nFrameIndex, m_pStandbyBuffer) &&
isValidCopiedScalars(
m_pStandbyBuffer, m_pSnapshot->getResultCellCount(),
sPropertyId == QString::fromLatin1("pressure")
? QString::fromLatin1("p") : sPropertyId);
if(bBound) if(bBound)
{ {
m_pRenderGrid->GetCellData()->SetScalars(m_pPressureBuffer); vtkDataArray* pPreviousScalars =
m_pRenderGrid->Modified(); m_pRenderGrid->GetCellData()->GetScalars();
m_pRenderGrid->GetCellData()->SetScalars(m_pStandbyBuffer);
if(m_pRenderGrid->GetCellData()->GetScalars() !=
m_pStandbyBuffer.GetPointer())
{
m_pRenderGrid->GetCellData()->SetScalars(pPreviousScalars);
bBound = false;
}
else
{
qSwap(m_pActiveBuffer, m_pStandbyBuffer);
m_pRenderGrid->Modified();
}
} }
} }
@ -98,11 +180,17 @@ bool nmPebiResultSnapshotRenderAdapter::setPressureFrame(int nFrameIndex)
{ {
return false; return false;
} }
m_sCurrentPropertyId = sPropertyId;
m_nCurrentFrameIndex = nFrameIndex; m_nCurrentFrameIndex = nFrameIndex;
m_pProducer->Modified(); m_pProducer->Modified();
return true; return true;
} }
bool nmPebiResultSnapshotRenderAdapter::setPressureFrame(int nFrameIndex)
{
return setPropertyFrame(QString::fromLatin1("pressure"), nFrameIndex);
}
vtkAlgorithmOutput* vtkAlgorithmOutput*
nmPebiResultSnapshotRenderAdapter::getOutputPort() const nmPebiResultSnapshotRenderAdapter::getOutputPort() const
{ {
@ -115,6 +203,11 @@ int nmPebiResultSnapshotRenderAdapter::getCurrentFrameIndex() const
return m_nCurrentFrameIndex; return m_nCurrentFrameIndex;
} }
QString nmPebiResultSnapshotRenderAdapter::getCurrentPropertyId() const
{
return m_sCurrentPropertyId;
}
vtkIdType nmPebiResultSnapshotRenderAdapter::getCellCount() const vtkIdType nmPebiResultSnapshotRenderAdapter::getCellCount() const
{ {
return m_pRenderGrid == NULL ? 0 : return m_pRenderGrid == NULL ? 0 :
@ -122,7 +215,12 @@ vtkIdType nmPebiResultSnapshotRenderAdapter::getCellCount() const
} }
bool nmPebiResultSnapshotRenderAdapter::isUsingSharedPressure() const bool nmPebiResultSnapshotRenderAdapter::isUsingSharedPressure() const
{
return isUsingSharedProperties();
}
bool nmPebiResultSnapshotRenderAdapter::isUsingSharedProperties() const
{ {
return !m_pSnapshot.isNull() && return !m_pSnapshot.isNull() &&
m_eBindingMode == SharedPressureFrame; m_eBindingMode == SharedPropertyArray;
} }

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save