diff --git a/Bin/Config/Lang/cn/nmNum_cn.qm b/Bin/Config/Lang/cn/nmNum_cn.qm
index ee4d0fb1..eee352b4 100644
Binary files a/Bin/Config/Lang/cn/nmNum_cn.qm and b/Bin/Config/Lang/cn/nmNum_cn.qm differ
diff --git a/Bin/Config/Lang/cn/nmNum_cn.ts b/Bin/Config/Lang/cn/nmNum_cn.ts
index 0a82dccf..d9e76056 100644
--- a/Bin/Config/Lang/cn/nmNum_cn.ts
+++ b/Bin/Config/Lang/cn/nmNum_cn.ts
@@ -2803,6 +2803,18 @@ Reason: %1
Unable to create the grid render layers.无法创建网格渲染图层。
Unable to bind the grid render data.无法绑定网格渲染数据。
+
+ nmPebiResultSceneController
+ The result snapshot is invalid.结果快照无效。
+ The result snapshot has no renderable property.结果快照中没有可渲染属性。
+ Unable to initialize the result render adapter.无法初始化结果渲染适配器。
+ The result render data failed validation.结果渲染数据校验失败。
+ Unable to create the result render layers.无法创建结果渲染图层。
+ Unable to commit the result render data.无法提交结果渲染数据。
+ The requested result property or frame is invalid.请求的结果属性或帧无效。
+ The result property range is invalid.结果属性范围无效。
+ Unable to apply the result property or frame.无法应用结果属性或帧。
+
nmWxPebiGridViewWidget
No grid data.暂无网格数据。
diff --git a/Include/nmNum/nmData/nmPebiResultSnapshotRenderAdapter.h b/Include/nmNum/nmData/nmPebiResultSnapshotRenderAdapter.h
index 3962c23b..19595ed2 100644
--- a/Include/nmNum/nmData/nmPebiResultSnapshotRenderAdapter.h
+++ b/Include/nmNum/nmData/nmPebiResultSnapshotRenderAdapter.h
@@ -67,6 +67,8 @@ public:
int getCurrentFrameIndex() const;
/** @brief 返回当前已经绑定的属性 ID,尚未绑定时为空。 */
QString getCurrentPropertyId() const;
+ /** @brief 返回当前属性对应的稳定 VTK 数组名,尚未绑定时为空。 */
+ QString getCurrentScalarArrayName() const;
/** @brief 返回适配器渲染网格的单元数。 */
vtkIdType getCellCount() const;
/** @brief 返回当前是否使用零整帧复制的共享压力模式。 */
diff --git a/Include/nmNum/nmRender/nmVTKCameraController.h b/Include/nmNum/nmRender/nmVTKCameraController.h
index 9b5faf29..d65d33a5 100644
--- a/Include/nmNum/nmRender/nmVTKCameraController.h
+++ b/Include/nmNum/nmRender/nmVTKCameraController.h
@@ -24,13 +24,18 @@ public:
/** @brief 返回当前 Renderer 非所有权指针。 */
vtkRenderer* getRenderer() const;
- /** @brief 恢复透视投影并适配当前可见边界。 */
+ /** @brief 复制并启用主内容边界;参数有限且各轴最小值不大于最大值。 */
+ bool setPrimaryViewBounds(const double dBounds[6]);
+ /** @brief 清除主内容边界,使相机操作重新使用全部可见 Prop。 */
+ void clearPrimaryViewBounds();
+
+ /** @brief 恢复透视投影并适配主内容或当前可见边界。 */
bool resetCamera();
/** @brief 按固定倍率 1.2 放大当前视图,保持观察方向和投影方式。 */
bool zoomIn();
/** @brief 按固定倍率 1.0 / 1.2 缩小当前视图。 */
bool zoomOut();
- /** @brief 保持当前观察方向和投影方式,仅适配可见边界。 */
+ /** @brief 保持当前观察方向和投影方式,仅适配主内容或可见边界。 */
bool fitView();
/** @brief 沿 +Z 方向设置正交顶视图。 */
bool setTopView();
@@ -42,7 +47,7 @@ public:
bool setIsometricView();
/** @brief 保存独立世界坐标旋转中心,不修改相机参数。 */
bool setRotationCenter(double dX, double dY, double dZ);
- /** @brief 将独立旋转中心恢复为当前可见边界中心,不修改画面。 */
+ /** @brief 将独立旋转中心恢复为主内容或可见边界中心,不修改画面。 */
bool resetRotationCenter();
/** @brief 返回是否已经设置有效的独立旋转中心。 */
bool hasRotationCenter() const;
@@ -54,9 +59,13 @@ public:
private:
Q_DISABLE_COPY(nmVTKCameraController)
+ /** @brief 优先返回主内容边界,未设置时回退到可见 Prop 边界。 */
+ bool getViewBounds(double dBounds[6]) const;
/** @brief 读取并验证当前可见 Prop 的有限包围盒。 */
bool getVisibleBounds(double dBounds[6]) const;
- /** @brief 设置观察方向和投影方式,再按可见边界适配相机。 */
+ /** @brief 优先按全部可见 Prop 更新裁剪面,无可见边界时使用构图边界。 */
+ void resetClippingRange(const double dViewBounds[6]);
+ /** @brief 设置观察方向和投影方式,再按主内容或可见边界适配相机。 */
bool applyStandardView(double dDirectionX,
double dDirectionY,
double dDirectionZ,
@@ -68,6 +77,8 @@ private:
bool zoom(double dFactor);
vtkRenderer* m_pRenderer; ///< 非所有权指针,由渲染容器持有。
+ bool m_bHasPrimaryViewBounds;
+ double m_dPrimaryViewBounds[6]; ///< 由业务控制器提供的主几何边界副本。
bool m_bHasRotationCenter;
double m_dRotationCenter[3]; ///< 独立于 vtkCamera::FocalPoint 的世界坐标。
};
diff --git a/Include/nmNum/nmRender/nmVTKMeshLayer.h b/Include/nmNum/nmRender/nmVTKMeshLayer.h
index bc41cfd8..0a226257 100644
--- a/Include/nmNum/nmRender/nmVTKMeshLayer.h
+++ b/Include/nmNum/nmRender/nmVTKMeshLayer.h
@@ -7,6 +7,7 @@
#include
class vtkActor;
+class vtkAlgorithmOutput;
class vtkDataArray;
class vtkDataSet;
class vtkDataSetMapper;
@@ -40,11 +41,29 @@ public:
/**
* @brief 共享传入数据集的 VTK 引用,不执行 DeepCopy。
- * @return 数据集非空且原标量选择在新数据集中仍有效时返回 true;没有
- * 原标量选择时只要求数据集非空。返回 false 时无效标量状态已清除。
+ * @return 数据集非空且原标量选择在新数据集中仍有效时返回 true;失败时
+ * 保留原输入、标量选择和 Mapper 状态。传入 nullptr 会清除输入。
*/
bool setDataSet(vtkDataSet* pDataSet);
- /** @brief 返回当前共享数据集的非所有权指针。 */
+ /**
+ * @brief 连接通用 VTK 管线输出,不取得 Producer 所有权。
+ * @return 输出为数据集且原标量选择仍有效时返回 true;失败时保留原输入。
+ */
+ bool setInputConnection(vtkAlgorithmOutput* pInputConnection);
+ /**
+ * @brief 原子提交管线输出、标量数组和显式显示范围。
+ *
+ * 候选输出、数组、数据范围和显示范围会在 Mapper 改动前完整验证;
+ * 返回 false 时原输入、数组选择、范围和 LookupTable 均保持不变。
+ */
+ bool setInputConnection(vtkAlgorithmOutput* pInputConnection,
+ const QString& sArrayName,
+ ScalarAssociation eAssociation,
+ double dMinimum,
+ double dMaximum);
+ /** @brief 解除直接数据或管线连接,并清除当前标量选择。 */
+ void clearInput();
+ /** @brief 返回当前直接数据或管线输出数据集的非所有权指针。 */
vtkDataSet* getDataSet() const;
/** @brief 设置表面或表面加边线显示模式。 */
@@ -72,6 +91,14 @@ public:
*/
bool setScalarArray(const QString& sArrayName,
ScalarAssociation eAssociation);
+ /**
+ * @brief 原子提交当前输入的标量数组和显式显示范围。
+ * @return 数组和两个范围均有效时返回 true;失败时保留原 Mapper 状态。
+ */
+ bool setScalarArray(const QString& sArrayName,
+ ScalarAssociation eAssociation,
+ double dMinimum,
+ double dMaximum);
/** @brief 返回当前选中的数组名称。 */
QString getScalarArrayName() const;
/** @brief 返回当前数组来自 CellData 还是 PointData。 */
@@ -92,6 +119,23 @@ public:
private:
Q_DISABLE_COPY(nmVTKMeshLayer)
+ /** @brief 解析直接输入或当前管线输出;按需先更新上游 Producer。 */
+ vtkDataSet* getInputDataSet(bool bUpdatePipeline) const;
+ /** @brief 在不修改 Mapper 的前提下校验候选数据及保留的标量选择。 */
+ bool validateInputDataSet(vtkDataSet* pDataSet,
+ double dScalarRange[2]) const;
+ /** @brief 查询指定关联中的数组,并验证其实际数据范围为有限值。 */
+ vtkDataArray* validateScalarArray(vtkDataSet* pDataSet,
+ const QByteArray& aArrayName,
+ ScalarAssociation eAssociation,
+ double dDataRange[2]) const;
+ /** @brief 提交已经完整验证的数组选择和范围;本函数内部没有失败分支。 */
+ void applyExplicitScalarState(const QByteArray& aArrayName,
+ ScalarAssociation eAssociation,
+ double dMinimum,
+ double dMaximum);
+ /** @brief 对新输入重新应用已经验证的标量模式、名称和自动范围。 */
+ void applyRetainedScalarState(const double dScalarRange[2]);
/** @brief 从当前数据集解析已记录的数组选择。 */
vtkDataArray* getSelectedScalarArray() const;
/** @brief 校验数组范围并同步到 Mapper 和 LookupTable。 */
@@ -100,6 +144,7 @@ private:
void clearInvalidScalarState();
vtkSmartPointer m_pDataSet; ///< 共享输入数据,不执行深拷贝。
+ vtkSmartPointer m_pInputConnection; ///< 共享管线输出端口。
vtkSmartPointer m_pMapper; ///< 图层独占的映射器。
vtkSmartPointer m_pActor; ///< 图层主 Prop,由基类同步持有。
vtkSmartPointer m_pLookupTable; ///< 图层独占的颜色表。
diff --git a/Include/nmNum/nmRender/nmWxVTKRenderContainerWidget.h b/Include/nmNum/nmRender/nmWxVTKRenderContainerWidget.h
index 69033976..96af9036 100644
--- a/Include/nmNum/nmRender/nmWxVTKRenderContainerWidget.h
+++ b/Include/nmNum/nmRender/nmWxVTKRenderContainerWidget.h
@@ -68,6 +68,11 @@ public:
void setViewCommandCapabilities(
nmVTKViewCommandController::ViewCommands eCommands);
+ /** @brief 设置相机适配、标准视图和重置中心优先使用的主内容边界。 */
+ bool setPrimaryViewBounds(const double dBounds[6]);
+ /** @brief 清除主内容边界,使相机重新回退到全部可见 Prop 边界。 */
+ void clearPrimaryViewBounds();
+
/** @brief 注册唯一可拾取几何;传入 nullptr 会取消模式并隐藏中心标记。 */
void setRotationCenterPickTarget(vtkProp* pTarget);
/** @brief 返回当前是否正在等待用户选择旋转中心。 */
@@ -90,7 +95,7 @@ public:
* 供业务层批量替换几何时使用,调用方应在整批更新结束后统一 renderView。
*/
bool fitTopViewWithoutRender();
- /** @brief 将独立旋转中心设为可见边界中心,但不修改画面或立即渲染。 */
+ /** @brief 将独立旋转中心设为主内容或可见边界中心,不立即渲染。 */
bool resetRotationCenterWithoutRender();
/** @brief 切换到 VTK 页面并提交一次渲染。 */
void showReadyState();
@@ -115,9 +120,9 @@ public slots:
void zoomIn();
/** @brief 按固定倍率 1.0 / 1.2 缩小。 */
void zoomOut();
- /** @brief 保持观察方向和投影方式,仅适配当前可见边界。 */
+ /** @brief 保持观察方向和投影方式,仅适配主内容或可见边界。 */
void fitView();
- /** @brief 恢复透视投影,并按当前可见 Prop 的边界适配相机。 */
+ /** @brief 恢复透视投影,并按主内容或当前可见 Prop 边界适配相机。 */
void resetCamera();
/** @brief 沿 +Z 方向设置正交顶视图。 */
void setTopView();
@@ -129,7 +134,7 @@ public slots:
void setIsometricView();
/** @brief 设置独立世界坐标旋转中心,不移动当前画面。 */
void setRotationCenter(double dX, double dY, double dZ);
- /** @brief 将旋转中心恢复为当前可见边界中心,不移动当前画面。 */
+ /** @brief 将旋转中心恢复为主内容或当前可见边界中心,不移动画面。 */
void resetRotationCenter();
/** @brief 请求开始或取消一次性旋转中心拾取模式。 */
void setRotationCenterPickingEnabled(bool bEnabled);
diff --git a/Include/nmNum/nmSubWxs/nmPebiResultSceneController.h b/Include/nmNum/nmSubWxs/nmPebiResultSceneController.h
new file mode 100644
index 00000000..2ed71845
--- /dev/null
+++ b/Include/nmNum/nmSubWxs/nmPebiResultSceneController.h
@@ -0,0 +1,75 @@
+#ifndef NMPEBIRESULTSCENECONTROLLER_H
+#define NMPEBIRESULTSCENECONTROLLER_H
+
+#include "nmSubWxs_global.h"
+
+#include
+#include
+#include
+
+class nmPebiResultSceneControllerPrivate;
+class nmPebiResultSnapshot;
+class nmWxVTKRenderContainerWidget;
+
+/**
+ * @brief 将不可变 PEBI 结果快照组合为独立的通用 VTK 场景。
+ *
+ * Scene 拥有 Mesh、ScalarBar 和平面比例网格图层;控制器独占活动
+ * RenderAdapter 和快照强引用,并通过稳定 ID 校验所观察的图层身份。
+ * 外部提前清空业务 Scene 时,控制器会丢弃活动结果而不会解引用旧图层。
+ */
+class NM_SUB_WXS_EXPORT nmPebiResultSceneController
+{
+public:
+ /**
+ * @param pContainer 非所有权指针,必须比控制器生命周期更长。
+ * @note 析构会从业务 Scene 移除本控制器创建的稳定 ID 图层。
+ */
+ explicit nmPebiResultSceneController(
+ nmWxVTKRenderContainerWidget* pContainer);
+ ~nmPebiResultSceneController();
+
+ /**
+ * @brief 事务式绑定新快照,成功后恢复顶视图、适配网格并渲染一次。
+ * @note 候选初始化失败时保留当前 Adapter、画面、范围和相机状态;
+ * 若发现 Scene 已由外部清空,则采用一致的无快照状态。
+ */
+ bool setSnapshot(
+ const QSharedPointer& pSnapshot,
+ QString* pError = nullptr);
+ /** @brief 解除管线和快照引用并隐藏结果图层,不销毁 Scene。 */
+ void clear();
+
+ /**
+ * @brief 原子切换同一快照内的属性和帧,成功时仅渲染一次。
+ * @note 静态属性只接受帧 0;失败时恢复 Adapter、Mapper 和范围。
+ */
+ bool applyPropertyFrame(const QString& sPropertyId,
+ int nFrameIndex,
+ QString* pError = nullptr);
+
+ /** 以下显示设置会作为偏好保留,并在存在活动快照时渲染一次。 */
+ void setMeshVisible(bool bVisible);
+ void setScalarBarVisible(bool bVisible);
+ void setScaleGridVisible(bool bVisible);
+ void setCellEdgesVisible(bool bVisible);
+
+ bool hasSnapshot() const;
+ QString getCurrentSnapshotId() const;
+ QStringList getAvailablePropertyIds() const;
+ QString getCurrentPropertyId() const;
+ int getCurrentFrameIndex() const;
+ /** @brief 返回快照中的业务范围;常量属性保持 [c,c]。 */
+ bool getCurrentPropertyRange(double dRange[2]) const;
+ bool isMeshVisible() const;
+ bool isScalarBarVisible() const;
+ bool isScaleGridVisible() const;
+ bool areCellEdgesVisible() const;
+
+private:
+ Q_DISABLE_COPY(nmPebiResultSceneController)
+
+ nmPebiResultSceneControllerPrivate* m_pPrivate; ///< 独占实现对象。
+};
+
+#endif // NMPEBIRESULTSCENECONTROLLER_H
diff --git a/Src/nmNum/nmData/nmPebiResultSnapshotRenderAdapter.cpp b/Src/nmNum/nmData/nmPebiResultSnapshotRenderAdapter.cpp
index abf8a45b..006f883b 100644
--- a/Src/nmNum/nmData/nmPebiResultSnapshotRenderAdapter.cpp
+++ b/Src/nmNum/nmData/nmPebiResultSnapshotRenderAdapter.cpp
@@ -208,6 +208,16 @@ QString nmPebiResultSnapshotRenderAdapter::getCurrentPropertyId() const
return m_sCurrentPropertyId;
}
+QString nmPebiResultSnapshotRenderAdapter::getCurrentScalarArrayName() const
+{
+ if(m_sCurrentPropertyId.isEmpty())
+ {
+ return QString();
+ }
+ return m_sCurrentPropertyId == QString::fromLatin1("pressure")
+ ? QString::fromLatin1("p") : m_sCurrentPropertyId;
+}
+
vtkIdType nmPebiResultSnapshotRenderAdapter::getCellCount() const
{
return m_pRenderGrid == NULL ? 0 :
diff --git a/Src/nmNum/nmRender/nmVTKCameraController.cpp b/Src/nmNum/nmRender/nmVTKCameraController.cpp
index b4f3efb6..cff80f07 100644
--- a/Src/nmNum/nmRender/nmVTKCameraController.cpp
+++ b/Src/nmNum/nmRender/nmVTKCameraController.cpp
@@ -21,9 +21,13 @@ bool isCameraGuiThread()
nmVTKCameraController::nmVTKCameraController(vtkRenderer* pRenderer)
: m_pRenderer(pRenderer),
+ m_bHasPrimaryViewBounds(false),
m_bHasRotationCenter(false)
{
Q_ASSERT(isCameraGuiThread());
+ for(int nIndex = 0; nIndex < 6; ++nIndex) {
+ m_dPrimaryViewBounds[nIndex] = 0.0;
+ }
m_dRotationCenter[0] = 0.0;
m_dRotationCenter[1] = 0.0;
m_dRotationCenter[2] = 0.0;
@@ -41,6 +45,7 @@ void nmVTKCameraController::setRenderer(vtkRenderer* pRenderer)
Q_ASSERT(isCameraGuiThread());
if(isCameraGuiThread()) {
if(m_pRenderer != pRenderer) {
+ clearPrimaryViewBounds();
clearRotationCenter();
}
m_pRenderer = pRenderer;
@@ -52,11 +57,43 @@ vtkRenderer* nmVTKCameraController::getRenderer() const
return m_pRenderer;
}
+bool nmVTKCameraController::setPrimaryViewBounds(
+ const double dBounds[6])
+{
+ Q_ASSERT(isCameraGuiThread());
+ if(!isCameraGuiThread() || dBounds == nullptr) {
+ return false;
+ }
+ for(int nIndex = 0; nIndex < 6; ++nIndex) {
+ if(!vtkMath::IsFinite(dBounds[nIndex])) {
+ return false;
+ }
+ }
+ if(dBounds[0] > dBounds[1] || dBounds[2] > dBounds[3] ||
+ dBounds[4] > dBounds[5]) {
+ return false;
+ }
+
+ for(int nIndex = 0; nIndex < 6; ++nIndex) {
+ m_dPrimaryViewBounds[nIndex] = dBounds[nIndex];
+ }
+ m_bHasPrimaryViewBounds = true;
+ return true;
+}
+
+void nmVTKCameraController::clearPrimaryViewBounds()
+{
+ Q_ASSERT(isCameraGuiThread());
+ if(isCameraGuiThread()) {
+ m_bHasPrimaryViewBounds = false;
+ }
+}
+
bool nmVTKCameraController::resetCamera()
{
Q_ASSERT(isCameraGuiThread());
double dBounds[6];
- if(!isCameraGuiThread() || !getVisibleBounds(dBounds)) {
+ if(!isCameraGuiThread() || !getViewBounds(dBounds)) {
return false;
}
@@ -64,10 +101,10 @@ bool nmVTKCameraController::resetCamera()
if(pCamera == nullptr) {
return false;
}
- // 重置入口恢复默认三维透视投影,再按当前可见边界适配。
+ // 重置入口恢复默认三维透视投影,再按主内容或当前可见边界适配。
pCamera->ParallelProjectionOff();
m_pRenderer->ResetCamera(dBounds);
- m_pRenderer->ResetCameraClippingRange(dBounds);
+ resetClippingRange(dBounds);
return true;
}
@@ -85,7 +122,7 @@ bool nmVTKCameraController::fitView()
{
Q_ASSERT(isCameraGuiThread());
double dBounds[6];
- if(!isCameraGuiThread() || !getVisibleBounds(dBounds)) {
+ if(!isCameraGuiThread() || !getViewBounds(dBounds)) {
return false;
}
@@ -96,7 +133,7 @@ bool nmVTKCameraController::fitView()
// ResetCamera 只重新适配边界,不改写调用方选定的观察方向和投影方式。
m_pRenderer->ResetCamera(dBounds);
- m_pRenderer->ResetCameraClippingRange(dBounds);
+ resetClippingRange(dBounds);
return true;
}
@@ -148,7 +185,7 @@ bool nmVTKCameraController::resetRotationCenter()
{
Q_ASSERT(isCameraGuiThread());
double dBounds[6];
- if(!isCameraGuiThread() || !getVisibleBounds(dBounds)) {
+ if(!isCameraGuiThread() || !getViewBounds(dBounds)) {
return false;
}
// 只更新独立枢轴;当前相机 Position/FocalPoint/ViewUp 保持不变。
@@ -178,6 +215,22 @@ void nmVTKCameraController::clearRotationCenter()
m_bHasRotationCenter = false;
}
+bool nmVTKCameraController::getViewBounds(double dBounds[6]) const
+{
+ // 主内容边界虽然是值副本,相机操作仍必须依赖有效 Renderer。
+ // 这里统一守住空指针,避免设置过主边界后绕过 getVisibleBounds 的校验。
+ if(m_pRenderer == nullptr || dBounds == nullptr) {
+ return false;
+ }
+ if(!m_bHasPrimaryViewBounds) {
+ return getVisibleBounds(dBounds);
+ }
+ for(int nIndex = 0; nIndex < 6; ++nIndex) {
+ dBounds[nIndex] = m_dPrimaryViewBounds[nIndex];
+ }
+ return true;
+}
+
bool nmVTKCameraController::getVisibleBounds(double dBounds[6]) const
{
if(m_pRenderer == nullptr || dBounds == nullptr) {
@@ -199,6 +252,24 @@ bool nmVTKCameraController::getVisibleBounds(double dBounds[6]) const
dBounds[4] <= dBounds[5];
}
+void nmVTKCameraController::resetClippingRange(
+ const double dViewBounds[6])
+{
+ if(m_pRenderer == nullptr || dViewBounds == nullptr) {
+ return;
+ }
+ double dVisibleBounds[6];
+ if(getVisibleBounds(dVisibleBounds)) {
+ // 裁剪范围可以包含装饰层,但相机位置和缩放仍只由主内容决定。
+ m_pRenderer->ResetCameraClippingRange(dVisibleBounds);
+ } else {
+ m_pRenderer->ResetCameraClippingRange(
+ dViewBounds[0], dViewBounds[1],
+ dViewBounds[2], dViewBounds[3],
+ dViewBounds[4], dViewBounds[5]);
+ }
+}
+
bool nmVTKCameraController::applyStandardView(
double dDirectionX,
double dDirectionY,
@@ -210,7 +281,7 @@ bool nmVTKCameraController::applyStandardView(
{
Q_ASSERT(isCameraGuiThread());
double dBounds[6];
- if(!isCameraGuiThread() || !getVisibleBounds(dBounds)) {
+ if(!isCameraGuiThread() || !getViewBounds(dBounds)) {
return false;
}
@@ -248,7 +319,7 @@ bool nmVTKCameraController::applyStandardView(
pCamera->SetParallelProjection(bParallelProjection ? 1 : 0);
pCamera->OrthogonalizeViewUp();
m_pRenderer->ResetCamera(dBounds);
- m_pRenderer->ResetCameraClippingRange(dBounds);
+ resetClippingRange(dBounds);
return true;
}
diff --git a/Src/nmNum/nmRender/nmVTKMeshLayer.cpp b/Src/nmNum/nmRender/nmVTKMeshLayer.cpp
index d19153c0..6b88d376 100644
--- a/Src/nmNum/nmRender/nmVTKMeshLayer.cpp
+++ b/Src/nmNum/nmRender/nmVTKMeshLayer.cpp
@@ -5,8 +5,11 @@
#include
#include
+#include
+#include
#include
#include
+#include
#include
#include
#include
@@ -29,6 +32,7 @@ bool isMeshLayerGuiThread()
nmVTKMeshLayer::nmVTKMeshLayer(const QString& sLayerId)
: nmVTKRenderLayer(sLayerId),
m_pDataSet(nullptr),
+ m_pInputConnection(nullptr),
m_pMapper(vtkSmartPointer::New()),
m_pActor(vtkSmartPointer::New()),
m_pLookupTable(vtkSmartPointer::New()),
@@ -56,7 +60,9 @@ nmVTKMeshLayer::~nmVTKMeshLayer()
Q_ASSERT(isMeshLayerGuiThread());
// 派生成员释放前先解除 Renderer,基类析构中的 detach 仍保持幂等。
detach();
+ clearInput();
m_pDataSet = nullptr;
+ m_pInputConnection = nullptr;
}
bool nmVTKMeshLayer::setDataSet(vtkDataSet* pDataSet)
@@ -65,41 +71,105 @@ bool nmVTKMeshLayer::setDataSet(vtkDataSet* pDataSet)
if(!isMeshLayerGuiThread()) {
return false;
}
+ if(pDataSet == nullptr) {
+ clearInput();
+ return false;
+ }
- // vtkSmartPointer 仅增加引用计数,不复制点、单元或属性数组。
- m_pDataSet = pDataSet;
+ double dScalarRange[2] = { 0.0, 1.0 };
+ if(!validateInputDataSet(pDataSet, dScalarRange)) {
+ return false;
+ }
+
+ // 所有可能失败的校验均在替换 Mapper 输入前完成。
m_pMapper->SetInputData(pDataSet);
+ m_pDataSet = pDataSet;
+ m_pInputConnection = nullptr;
+ applyRetainedScalarState(dScalarRange);
+ return true;
+}
- if(pDataSet == nullptr) {
- clearInvalidScalarState();
+bool nmVTKMeshLayer::setInputConnection(
+ vtkAlgorithmOutput* pInputConnection)
+{
+ Q_ASSERT(isMeshLayerGuiThread());
+ if(!isMeshLayerGuiThread() || pInputConnection == nullptr ||
+ pInputConnection->GetProducer() == nullptr ||
+ pInputConnection->GetIndex() < 0) {
return false;
}
- // 更换数据集时保留调用方此前的数组选择;若同名数组存在则恢复其映射方式。
- if(!m_aScalarArrayName.isEmpty()) {
- if(getSelectedScalarArray() == nullptr) {
- clearInvalidScalarState();
- return false;
- }
- if(m_eScalarAssociation == ScalarAssociation_CellData) {
- m_pMapper->SetScalarModeToUseCellFieldData();
- } else {
- m_pMapper->SetScalarModeToUsePointFieldData();
- }
- m_pMapper->SelectColorArray(m_aScalarArrayName.constData());
- if(!m_bUsingExplicitScalarRange) {
- if(!applyDataScalarRange()) {
- clearInvalidScalarState();
- return false;
- }
- }
+ vtkAlgorithm* pProducer = pInputConnection->GetProducer();
+ pProducer->Update(pInputConnection->GetIndex());
+ vtkDataSet* pCandidateDataSet = vtkDataSet::SafeDownCast(
+ pProducer->GetOutputDataObject(
+ pInputConnection->GetIndex()));
+ double dScalarRange[2] = { 0.0, 1.0 };
+ if(!validateInputDataSet(pCandidateDataSet, dScalarRange)) {
+ return false;
}
+
+ // 输出对象、保留数组和自动范围均已验证,此后才替换活动连接。
+ m_pMapper->SetInputConnection(pInputConnection);
+ m_pInputConnection = pInputConnection;
+ m_pDataSet = nullptr;
+ applyRetainedScalarState(dScalarRange);
+ return true;
+}
+
+bool nmVTKMeshLayer::setInputConnection(
+ vtkAlgorithmOutput* pInputConnection,
+ const QString& sArrayName,
+ ScalarAssociation eAssociation,
+ double dMinimum,
+ double dMaximum)
+{
+ Q_ASSERT(isMeshLayerGuiThread());
+ if(!isMeshLayerGuiThread() || pInputConnection == nullptr ||
+ pInputConnection->GetProducer() == nullptr ||
+ pInputConnection->GetIndex() < 0 || sArrayName.isEmpty() ||
+ !vtkMath::IsFinite(dMinimum) || !vtkMath::IsFinite(dMaximum) ||
+ dMinimum > dMaximum) {
+ return false;
+ }
+
+ vtkAlgorithm* pProducer = pInputConnection->GetProducer();
+ const int nOutputPort = pInputConnection->GetIndex();
+ pProducer->Update(nOutputPort);
+ vtkDataSet* pCandidateDataSet = vtkDataSet::SafeDownCast(
+ pProducer->GetOutputDataObject(nOutputPort));
+ const QByteArray aArrayName = sArrayName.toUtf8();
+ double dDataRange[2] = { 0.0, 0.0 };
+ if(validateScalarArray(pCandidateDataSet, aArrayName,
+ eAssociation, dDataRange) == nullptr) {
+ return false;
+ }
+
+ // 到这里所有可能失败的条件均已排除。下面只执行 VTK 无返回值 setter,
+ // 因而输入、数组和范围会作为一个不可分割的 Mapper 状态提交。
+ m_pMapper->SetInputConnection(pInputConnection);
+ m_pInputConnection = pInputConnection;
+ m_pDataSet = nullptr;
+ applyExplicitScalarState(aArrayName, eAssociation,
+ dMinimum, dMaximum);
return true;
}
+void nmVTKMeshLayer::clearInput()
+{
+ Q_ASSERT(isMeshLayerGuiThread());
+ if(!isMeshLayerGuiThread() || m_pMapper == nullptr) {
+ return;
+ }
+ m_pMapper->RemoveAllInputs();
+ m_pInputConnection = nullptr;
+ m_pDataSet = nullptr;
+ clearInvalidScalarState();
+}
+
vtkDataSet* nmVTKMeshLayer::getDataSet() const
{
- return m_pDataSet;
+ return getInputDataSet(false);
}
void nmVTKMeshLayer::setDisplayMode(DisplayMode eMode)
@@ -170,32 +240,20 @@ bool nmVTKMeshLayer::setScalarArray(
ScalarAssociation eAssociation)
{
Q_ASSERT(isMeshLayerGuiThread());
+ vtkDataSet* pDataSet = getInputDataSet(true);
if(!isMeshLayerGuiThread() || sArrayName.isEmpty() ||
- m_pDataSet == nullptr || m_pMapper == nullptr ||
- (eAssociation != ScalarAssociation_CellData &&
- eAssociation != ScalarAssociation_PointData)) {
+ pDataSet == nullptr || m_pMapper == nullptr) {
return false;
}
const QByteArray aArrayName = sArrayName.toUtf8();
- vtkDataArray* pArray = eAssociation == ScalarAssociation_CellData
- ? m_pDataSet->GetCellData()->GetArray(aArrayName.constData())
- : m_pDataSet->GetPointData()->GetArray(aArrayName.constData());
- if(pArray == nullptr) {
+ double dRange[2] = { 0.0, 0.0 };
+ if(validateScalarArray(pDataSet, aArrayName,
+ eAssociation, dRange) == nullptr) {
// 失败时保留原数组选择和原 Mapper 状态。
return false;
}
- double dRange[2] = { 0.0, 0.0 };
- if(!m_bUsingExplicitScalarRange) {
- pArray->GetRange(dRange);
- if(!vtkMath::IsFinite(dRange[0]) ||
- !vtkMath::IsFinite(dRange[1]) || dRange[0] > dRange[1]) {
- // 自动范围无效时不提交新数组选择,避免形成半更新状态。
- return false;
- }
- }
-
m_aScalarArrayName = aArrayName;
m_eScalarAssociation = eAssociation;
if(eAssociation == ScalarAssociation_CellData) {
@@ -214,6 +272,34 @@ bool nmVTKMeshLayer::setScalarArray(
return true;
}
+bool nmVTKMeshLayer::setScalarArray(
+ const QString& sArrayName,
+ ScalarAssociation eAssociation,
+ double dMinimum,
+ double dMaximum)
+{
+ Q_ASSERT(isMeshLayerGuiThread());
+ vtkDataSet* pDataSet = getInputDataSet(true);
+ if(!isMeshLayerGuiThread() || sArrayName.isEmpty() ||
+ pDataSet == nullptr || m_pMapper == nullptr ||
+ !vtkMath::IsFinite(dMinimum) || !vtkMath::IsFinite(dMaximum) ||
+ dMinimum > dMaximum) {
+ return false;
+ }
+
+ const QByteArray aArrayName = sArrayName.toUtf8();
+ double dDataRange[2] = { 0.0, 0.0 };
+ if(validateScalarArray(pDataSet, aArrayName,
+ eAssociation, dDataRange) == nullptr) {
+ return false;
+ }
+
+ // 数组和范围都已验证,提交阶段不会再产生可观察的半更新状态。
+ applyExplicitScalarState(aArrayName, eAssociation,
+ dMinimum, dMaximum);
+ return true;
+}
+
QString nmVTKMeshLayer::getScalarArrayName() const
{
return QString::fromUtf8(m_aScalarArrayName.constData());
@@ -273,15 +359,117 @@ vtkLookupTable* nmVTKMeshLayer::getLookupTable() const
return m_pLookupTable;
}
+vtkDataSet* nmVTKMeshLayer::getInputDataSet(bool bUpdatePipeline) const
+{
+ if(m_pInputConnection == nullptr) {
+ return m_pDataSet;
+ }
+
+ vtkAlgorithm* pProducer = m_pInputConnection->GetProducer();
+ const int nOutputPort = m_pInputConnection->GetIndex();
+ if(pProducer == nullptr || nOutputPort < 0) {
+ return nullptr;
+ }
+ if(bUpdatePipeline) {
+ pProducer->Update(nOutputPort);
+ }
+ return vtkDataSet::SafeDownCast(
+ pProducer->GetOutputDataObject(nOutputPort));
+}
+
+bool nmVTKMeshLayer::validateInputDataSet(
+ vtkDataSet* pDataSet,
+ double dScalarRange[2]) const
+{
+ if(pDataSet == nullptr || dScalarRange == nullptr) {
+ return false;
+ }
+ if(m_aScalarArrayName.isEmpty()) {
+ return true;
+ }
+
+ return validateScalarArray(pDataSet, m_aScalarArrayName,
+ m_eScalarAssociation,
+ dScalarRange) != nullptr;
+}
+
+vtkDataArray* nmVTKMeshLayer::validateScalarArray(
+ vtkDataSet* pDataSet,
+ const QByteArray& aArrayName,
+ ScalarAssociation eAssociation,
+ double dDataRange[2]) const
+{
+ if(pDataSet == nullptr || aArrayName.isEmpty() ||
+ dDataRange == nullptr ||
+ (eAssociation != ScalarAssociation_CellData &&
+ eAssociation != ScalarAssociation_PointData)) {
+ return nullptr;
+ }
+
+ vtkDataArray* pArray = eAssociation == ScalarAssociation_CellData
+ ? pDataSet->GetCellData()->GetArray(aArrayName.constData())
+ : pDataSet->GetPointData()->GetArray(aArrayName.constData());
+ if(pArray == nullptr) {
+ return nullptr;
+ }
+ pArray->GetRange(dDataRange);
+ return vtkMath::IsFinite(dDataRange[0]) &&
+ vtkMath::IsFinite(dDataRange[1]) &&
+ dDataRange[0] <= dDataRange[1]
+ ? pArray : nullptr;
+}
+
+void nmVTKMeshLayer::applyExplicitScalarState(
+ const QByteArray& aArrayName,
+ ScalarAssociation eAssociation,
+ double dMinimum,
+ double dMaximum)
+{
+ m_aScalarArrayName = aArrayName;
+ m_eScalarAssociation = eAssociation;
+ if(eAssociation == ScalarAssociation_CellData) {
+ m_pMapper->SetScalarModeToUseCellFieldData();
+ } else {
+ m_pMapper->SetScalarModeToUsePointFieldData();
+ }
+ m_pMapper->SelectColorArray(m_aScalarArrayName.constData());
+ m_pMapper->SetScalarRange(dMinimum, dMaximum);
+ m_pLookupTable->SetRange(dMinimum, dMaximum);
+ m_pLookupTable->Build();
+ m_bUsingExplicitScalarRange = true;
+}
+
+void nmVTKMeshLayer::applyRetainedScalarState(
+ const double dScalarRange[2])
+{
+ if(m_aScalarArrayName.isEmpty()) {
+ return;
+ }
+ if(m_eScalarAssociation == ScalarAssociation_CellData) {
+ m_pMapper->SetScalarModeToUseCellFieldData();
+ } else {
+ m_pMapper->SetScalarModeToUsePointFieldData();
+ }
+ m_pMapper->SelectColorArray(m_aScalarArrayName.constData());
+ if(!m_bUsingExplicitScalarRange && dScalarRange != nullptr) {
+ m_pMapper->SetScalarRange(
+ dScalarRange[0], dScalarRange[1]);
+ m_pLookupTable->SetRange(
+ dScalarRange[0], dScalarRange[1]);
+ m_pLookupTable->Build();
+ }
+}
+
vtkDataArray* nmVTKMeshLayer::getSelectedScalarArray() const
{
- if(m_pDataSet == nullptr || m_aScalarArrayName.isEmpty()) {
+ vtkDataSet* pDataSet = getInputDataSet(true);
+ if(pDataSet == nullptr || m_aScalarArrayName.isEmpty()) {
return nullptr;
}
return m_eScalarAssociation == ScalarAssociation_CellData
- ? m_pDataSet->GetCellData()->GetArray(
+ ? pDataSet->GetCellData()->GetArray(
m_aScalarArrayName.constData())
- : m_pDataSet->GetPointData()->GetArray(
+ : pDataSet->GetPointData()->GetArray(
m_aScalarArrayName.constData());
}
diff --git a/Src/nmNum/nmRender/nmWxVTKRenderContainerWidget.cpp b/Src/nmNum/nmRender/nmWxVTKRenderContainerWidget.cpp
index df02ab39..c00c673b 100644
--- a/Src/nmNum/nmRender/nmWxVTKRenderContainerWidget.cpp
+++ b/Src/nmNum/nmRender/nmWxVTKRenderContainerWidget.cpp
@@ -288,6 +288,22 @@ void nmWxVTKRenderContainerWidget::setViewCommandCapabilities(
updateRotationCenterMarkerVisibility();
}
+bool nmWxVTKRenderContainerWidget::setPrimaryViewBounds(
+ const double dBounds[6])
+{
+ Q_ASSERT(isGuiThread());
+ return isGuiThread() && m_pCameraController != nullptr &&
+ m_pCameraController->setPrimaryViewBounds(dBounds);
+}
+
+void nmWxVTKRenderContainerWidget::clearPrimaryViewBounds()
+{
+ Q_ASSERT(isGuiThread());
+ if(isGuiThread() && m_pCameraController != nullptr) {
+ m_pCameraController->clearPrimaryViewBounds();
+ }
+}
+
void nmWxVTKRenderContainerWidget::setRotationCenterPickTarget(
vtkProp* pTarget)
{
diff --git a/Src/nmNum/nmSubWxs/nmPebiResultSceneController.cpp b/Src/nmNum/nmSubWxs/nmPebiResultSceneController.cpp
new file mode 100644
index 00000000..b5168c43
--- /dev/null
+++ b/Src/nmNum/nmSubWxs/nmPebiResultSceneController.cpp
@@ -0,0 +1,1031 @@
+#include "nmPebiResultSceneController.h"
+
+#include "nmPebiResultPropertyCatalog.h"
+#include "nmPebiResultSnapshot.h"
+#include "nmPebiResultSnapshotRenderAdapter.h"
+
+#include "nmVTKMeshLayer.h"
+#include "nmVTKPlanarScaleGridLayer.h"
+#include "nmVTKScalarBarLayer.h"
+#include "nmVTKScene.h"
+#include "nmWxVTKRenderContainerWidget.h"
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+namespace {
+
+const char* const RESULT_MESH_LAYER_ID = "pebi.result.mesh";
+const char* const RESULT_SCALAR_BAR_LAYER_ID = "pebi.result.scalar_bar";
+const char* const RESULT_SCALE_GRID_LAYER_ID = "pebi.result.scale_grid";
+const char* const RESULT_WELLS_LAYER_ID = "pebi.result.wells";
+
+/* Qt 4.8 的 lupdate 无法从变量参数中推导翻译原文,必须显式登记。 */
+static const char* const s_pResultSceneTranslationMarkers[] =
+{
+ QT_TRANSLATE_NOOP("nmPebiResultSceneController",
+ "The result snapshot is invalid."),
+ QT_TRANSLATE_NOOP("nmPebiResultSceneController",
+ "The result snapshot has no renderable property."),
+ QT_TRANSLATE_NOOP("nmPebiResultSceneController",
+ "Unable to initialize the result render adapter."),
+ QT_TRANSLATE_NOOP("nmPebiResultSceneController",
+ "The result render data failed validation."),
+ QT_TRANSLATE_NOOP("nmPebiResultSceneController",
+ "Unable to create the result render layers."),
+ QT_TRANSLATE_NOOP("nmPebiResultSceneController",
+ "Unable to commit the result render data."),
+ QT_TRANSLATE_NOOP("nmPebiResultSceneController",
+ "The requested result property or frame is invalid."),
+ QT_TRANSLATE_NOOP("nmPebiResultSceneController",
+ "The result property range is invalid."),
+ QT_TRANSLATE_NOOP("nmPebiResultSceneController",
+ "Unable to apply the result property or frame.")
+};
+
+bool isResultSceneGuiThread()
+{
+ QCoreApplication* pApplication = QCoreApplication::instance();
+ return pApplication == nullptr ||
+ pApplication->thread() == QThread::currentThread();
+}
+
+QString controllerText(const char* pText)
+{
+ Q_UNUSED(s_pResultSceneTranslationMarkers);
+ return QCoreApplication::translate(
+ "nmPebiResultSceneController", pText);
+}
+
+void copyBounds(const double dSource[6], double dTarget[6])
+{
+ for(int nIndex = 0; nIndex < 6; ++nIndex) {
+ dTarget[nIndex] = dSource[nIndex];
+ }
+}
+
+bool getFiniteBounds(vtkDataSet* pDataSet, double dBounds[6])
+{
+ if(pDataSet == nullptr || dBounds == nullptr) {
+ return false;
+ }
+ pDataSet->GetBounds(dBounds);
+ if(!vtkMath::AreBoundsInitialized(dBounds)) {
+ return false;
+ }
+ for(int nIndex = 0; nIndex < 6; ++nIndex) {
+ if(!vtkMath::IsFinite(dBounds[nIndex])) {
+ return false;
+ }
+ }
+ return dBounds[0] < dBounds[1] &&
+ dBounds[2] < dBounds[3] &&
+ dBounds[4] <= dBounds[5];
+}
+
+bool getDisplayRange(const double dBusinessRange[2],
+ double dDisplayRange[2])
+{
+ if(dBusinessRange == nullptr || dDisplayRange == nullptr ||
+ !vtkMath::IsFinite(dBusinessRange[0]) ||
+ !vtkMath::IsFinite(dBusinessRange[1]) ||
+ dBusinessRange[0] > dBusinessRange[1]) {
+ return false;
+ }
+
+ dDisplayRange[0] = dBusinessRange[0];
+ dDisplayRange[1] = dBusinessRange[1];
+ if(dBusinessRange[0] < dBusinessRange[1]) {
+ return true;
+ }
+
+ // VTK 需要非零显示区间;业务层仍保存并返回原始常量范围 [c,c]。
+ const double dValue = dBusinessRange[0];
+ const double dEpsilon = qMax(1.0, fabs(dValue)) * 1.0e-6;
+ double dMinimum = dValue - dEpsilon;
+ double dMaximum = dValue + dEpsilon;
+ if(!vtkMath::IsFinite(dMinimum)) {
+ dMinimum = dValue;
+ }
+ if(!vtkMath::IsFinite(dMaximum)) {
+ dMaximum = dValue;
+ }
+ if(dMinimum >= dMaximum) {
+ return false;
+ }
+ dDisplayRange[0] = dMinimum;
+ dDisplayRange[1] = dMaximum;
+ return true;
+}
+
+bool getValidatedPropertyRanges(
+ const QSharedPointer& pSnapshot,
+ const QString& sPropertyId,
+ double dBusinessRange[2],
+ double dDisplayRange[2])
+{
+ return !pSnapshot.isNull() &&
+ pSnapshot->getPropertyRange(sPropertyId, dBusinessRange) &&
+ getDisplayRange(dBusinessRange, dDisplayRange);
+}
+
+QString chooseInitialProperty(
+ const QSharedPointer& pSnapshot)
+{
+ const QString sPressure = QString::fromLatin1("pressure");
+ if(!pSnapshot.isNull() && pSnapshot->hasProperty(sPressure) &&
+ pSnapshot->getPropertyFrameCount(sPressure) > 0) {
+ return sPressure;
+ }
+
+ // 无压力仅是防御分支;按 Catalog 顺序选择第一个可用静态属性。
+ const QList listDescriptors =
+ nmPebiResultPropertyCatalog::descriptors();
+ for(int nIndex = 0; nIndex < listDescriptors.size(); ++nIndex) {
+ const nmPebiResultPropertyDescriptor& oDescriptor =
+ listDescriptors[nIndex];
+ if(oDescriptor.m_eTimeType == NM_PebiResultProperty_Static &&
+ pSnapshot->hasProperty(oDescriptor.m_sPropertyId)) {
+ return oDescriptor.m_sPropertyId;
+ }
+ }
+ return QString();
+}
+
+bool areSnapshotWellsValid(
+ const QSharedPointer& pSnapshot)
+{
+ if(pSnapshot.isNull()) {
+ return false;
+ }
+ QSet setWellIds;
+ for(int nIndex = 0; nIndex < pSnapshot->getWellCount(); ++nIndex) {
+ const nmPebiResultWellSnapshot* pWell = pSnapshot->getWellAt(nIndex);
+ if(pWell == nullptr || pWell->m_sWellInstanceId.isEmpty() ||
+ pWell->m_sWellCode.isEmpty() ||
+ !vtkMath::IsFinite(pWell->m_oLocation.x()) ||
+ !vtkMath::IsFinite(pWell->m_oLocation.y()) ||
+ setWellIds.contains(pWell->m_sWellInstanceId)) {
+ return false;
+ }
+ setWellIds.insert(pWell->m_sWellInstanceId);
+ }
+ return true;
+}
+
+bool validateAdapterOutput(
+ nmPebiResultSnapshotRenderAdapter* pAdapter,
+ vtkIdType nExpectedCellCount,
+ double dBounds[6])
+{
+ if(pAdapter == nullptr || dBounds == nullptr ||
+ pAdapter->getCellCount() != nExpectedCellCount ||
+ nExpectedCellCount <= 0) {
+ return false;
+ }
+
+ vtkAlgorithmOutput* pOutputPort = pAdapter->getOutputPort();
+ if(pOutputPort == nullptr || pOutputPort->GetProducer() == nullptr ||
+ pOutputPort->GetIndex() < 0) {
+ return false;
+ }
+ vtkAlgorithm* pProducer = pOutputPort->GetProducer();
+ pProducer->Update(pOutputPort->GetIndex());
+ vtkDataSet* pDataSet = vtkDataSet::SafeDownCast(
+ pProducer->GetOutputDataObject(pOutputPort->GetIndex()));
+ if(pDataSet == nullptr ||
+ pDataSet->GetNumberOfCells() != nExpectedCellCount ||
+ !getFiniteBounds(pDataSet, dBounds)) {
+ return false;
+ }
+
+ const QByteArray aScalarName =
+ pAdapter->getCurrentScalarArrayName().toUtf8();
+ vtkDataArray* pScalars = aScalarName.isEmpty() ? nullptr :
+ pDataSet->GetCellData()->GetArray(aScalarName.constData());
+ if(pScalars == nullptr || pScalars->GetNumberOfComponents() != 1 ||
+ pScalars->GetNumberOfTuples() != nExpectedCellCount) {
+ return false;
+ }
+ double dActualRange[2] = { 0.0, 0.0 };
+ pScalars->GetRange(dActualRange);
+ return vtkMath::IsFinite(dActualRange[0]) &&
+ vtkMath::IsFinite(dActualRange[1]) &&
+ dActualRange[0] <= dActualRange[1];
+}
+
+}
+
+class nmPebiResultSceneControllerPrivate
+{
+public:
+ explicit nmPebiResultSceneControllerPrivate(
+ nmWxVTKRenderContainerWidget* pContainer)
+ : m_pContainer(pContainer),
+ m_pMeshLayer(nullptr),
+ m_pScalarBarLayer(nullptr),
+ m_pScaleGridLayer(nullptr),
+ m_pAdapter(nullptr),
+ m_nCurrentFrameIndex(-1),
+ m_bHasPropertyRange(false),
+ m_bHasBounds(false),
+ m_bMeshVisible(true),
+ // 5C 补齐属性名和单位标题前,避免显示语义不完整的色标。
+ m_bScalarBarVisible(false),
+ m_bScaleGridVisible(false),
+ m_bCellEdgesVisible(false)
+ {
+ m_dBusinessRange[0] = 0.0;
+ m_dBusinessRange[1] = 0.0;
+ m_dDisplayRange[0] = 0.0;
+ m_dDisplayRange[1] = 1.0;
+ for(int nIndex = 0; nIndex < 6; ++nIndex) {
+ m_dBounds[nIndex] = 0.0;
+ }
+ // 5A 固定保留该 ID,井位图层在 5C 才创建。
+ Q_UNUSED(RESULT_WELLS_LAYER_ID);
+ }
+
+ nmVTKScene* getScene() const
+ {
+ return m_pContainer != nullptr ? m_pContainer->getScene() : nullptr;
+ }
+
+ bool synchronizeLayerPointers()
+ {
+ nmVTKScene* pScene = getScene();
+ bool bPointersIntact = pScene != nullptr;
+ if(pScene == nullptr) {
+ bPointersIntact = m_pMeshLayer == nullptr &&
+ m_pScalarBarLayer == nullptr &&
+ m_pScaleGridLayer == nullptr;
+ m_pMeshLayer = nullptr;
+ m_pScalarBarLayer = nullptr;
+ m_pScaleGridLayer = nullptr;
+ return bPointersIntact;
+ }
+
+ // Scene 是图层唯一所有者。这里只比较指针身份,不解引用可能已经由
+ // 外部 Scene::clear() 删除的旧地址;不一致时立即丢弃观察指针。
+ if(m_pMeshLayer != nullptr &&
+ pScene->getLayer(QString::fromLatin1(RESULT_MESH_LAYER_ID)) !=
+ m_pMeshLayer) {
+ m_pMeshLayer = nullptr;
+ bPointersIntact = false;
+ }
+ if(m_pScalarBarLayer != nullptr &&
+ pScene->getLayer(
+ QString::fromLatin1(RESULT_SCALAR_BAR_LAYER_ID)) !=
+ m_pScalarBarLayer) {
+ m_pScalarBarLayer = nullptr;
+ bPointersIntact = false;
+ }
+ if(m_pScaleGridLayer != nullptr &&
+ pScene->getLayer(
+ QString::fromLatin1(RESULT_SCALE_GRID_LAYER_ID)) !=
+ m_pScaleGridLayer) {
+ m_pScaleGridLayer = nullptr;
+ bPointersIntact = false;
+ }
+ return bPointersIntact;
+ }
+
+ bool hasOwnedBaseLayers() const
+ {
+ if(!isResultSceneGuiThread()) {
+ return false;
+ }
+ nmVTKScene* pScene = getScene();
+ return pScene != nullptr &&
+ m_pMeshLayer != nullptr &&
+ m_pScalarBarLayer != nullptr &&
+ m_pScaleGridLayer != nullptr &&
+ pScene->getLayer(
+ QString::fromLatin1(RESULT_MESH_LAYER_ID)) ==
+ m_pMeshLayer &&
+ pScene->getLayer(
+ QString::fromLatin1(RESULT_SCALAR_BAR_LAYER_ID)) ==
+ m_pScalarBarLayer &&
+ pScene->getLayer(
+ QString::fromLatin1(RESULT_SCALE_GRID_LAYER_ID)) ==
+ m_pScaleGridLayer;
+ }
+
+ void discardActiveDataIfLayersLost()
+ {
+ const bool bPointersIntact = synchronizeLayerPointers();
+ const bool bHasActiveState = m_pAdapter != nullptr ||
+ !m_pSnapshot.isNull();
+ if(bHasActiveState &&
+ (!bPointersIntact || !hasOwnedBaseLayers())) {
+ // 外部已经清理业务 Scene 时无法继续兑现“当前快照可见”的状态,
+ // 因此采用 fail-closed:释放逻辑状态,但绝不访问已删除的 Layer。
+ releaseActiveData();
+ }
+ }
+
+ bool ensureBaseLayers(bool& bCreatedMesh,
+ bool& bCreatedScalarBar,
+ bool& bCreatedScaleGrid)
+ {
+ bCreatedMesh = false;
+ bCreatedScalarBar = false;
+ bCreatedScaleGrid = false;
+ const bool bPointersIntact = synchronizeLayerPointers();
+ nmVTKScene* pScene = getScene();
+ vtkRenderer* pRenderer = m_pContainer != nullptr
+ ? m_pContainer->getRenderer() : nullptr;
+ if(pScene == nullptr || pRenderer == nullptr ||
+ (!bPointersIntact && m_pAdapter != nullptr)) {
+ return false;
+ }
+
+ if(m_pMeshLayer == nullptr) {
+ if(pScene->getLayer(
+ QString::fromLatin1(RESULT_MESH_LAYER_ID)) != nullptr) {
+ return false;
+ }
+ nmVTKMeshLayer* pLayer = new nmVTKMeshLayer(
+ QString::fromLatin1(RESULT_MESH_LAYER_ID));
+ pLayer->setVisible(false);
+ if(!pScene->addLayer(pLayer)) {
+ delete pLayer;
+ return false;
+ }
+ m_pMeshLayer = pLayer;
+ bCreatedMesh = true;
+ m_pMeshLayer->getActor()->GetProperty()->SetEdgeColor(
+ 0.0, 0.0, 0.0);
+ m_pMeshLayer->getActor()->GetProperty()->LightingOff();
+ }
+
+ if(m_pScalarBarLayer == nullptr) {
+ if(pScene->getLayer(QString::fromLatin1(
+ RESULT_SCALAR_BAR_LAYER_ID)) != nullptr) {
+ rollbackCreatedLayers(
+ bCreatedMesh, false, false);
+ return false;
+ }
+ nmVTKScalarBarLayer* pLayer = new nmVTKScalarBarLayer(
+ QString::fromLatin1(RESULT_SCALAR_BAR_LAYER_ID));
+ pLayer->setVisible(false);
+ pLayer->setPosition(0.05, 0.1);
+ pLayer->setSize(0.1, 0.8);
+ if(!pScene->addLayer(pLayer)) {
+ delete pLayer;
+ rollbackCreatedLayers(
+ bCreatedMesh, false, false);
+ return false;
+ }
+ m_pScalarBarLayer = pLayer;
+ bCreatedScalarBar = true;
+ }
+
+ if(m_pScaleGridLayer == nullptr) {
+ if(pScene->getLayer(QString::fromLatin1(
+ RESULT_SCALE_GRID_LAYER_ID)) != nullptr) {
+ rollbackCreatedLayers(
+ bCreatedMesh, bCreatedScalarBar, false);
+ return false;
+ }
+ nmVTKPlanarScaleGridLayer* pLayer =
+ new nmVTKPlanarScaleGridLayer(
+ QString::fromLatin1(RESULT_SCALE_GRID_LAYER_ID));
+ pLayer->setVisible(false);
+ if(!pScene->addLayer(pLayer)) {
+ delete pLayer;
+ rollbackCreatedLayers(
+ bCreatedMesh, bCreatedScalarBar, false);
+ return false;
+ }
+ m_pScaleGridLayer = pLayer;
+ bCreatedScaleGrid = true;
+ }
+ return true;
+ }
+
+ void rollbackCreatedLayers(bool bCreatedMesh,
+ bool bCreatedScalarBar,
+ bool bCreatedScaleGrid)
+ {
+ synchronizeLayerPointers();
+ nmVTKScene* pScene = getScene();
+ if(pScene == nullptr) {
+ return;
+ }
+ if(bCreatedScaleGrid && m_pScaleGridLayer != nullptr &&
+ pScene->getLayer(
+ QString::fromLatin1(RESULT_SCALE_GRID_LAYER_ID)) ==
+ m_pScaleGridLayer) {
+ pScene->removeLayer(
+ QString::fromLatin1(RESULT_SCALE_GRID_LAYER_ID));
+ }
+ if(bCreatedScaleGrid) {
+ m_pScaleGridLayer = nullptr;
+ }
+ if(bCreatedScalarBar && m_pScalarBarLayer != nullptr &&
+ pScene->getLayer(
+ QString::fromLatin1(RESULT_SCALAR_BAR_LAYER_ID)) ==
+ m_pScalarBarLayer) {
+ pScene->removeLayer(
+ QString::fromLatin1(RESULT_SCALAR_BAR_LAYER_ID));
+ }
+ if(bCreatedScalarBar) {
+ m_pScalarBarLayer = nullptr;
+ }
+ if(bCreatedMesh && m_pMeshLayer != nullptr &&
+ pScene->getLayer(QString::fromLatin1(RESULT_MESH_LAYER_ID)) ==
+ m_pMeshLayer) {
+ pScene->removeLayer(
+ QString::fromLatin1(RESULT_MESH_LAYER_ID));
+ }
+ if(bCreatedMesh) {
+ m_pMeshLayer = nullptr;
+ }
+ }
+
+ bool bindMeshAdapter(nmPebiResultSnapshotRenderAdapter* pAdapter,
+ const double dDisplayRange[2])
+ {
+ if(m_pMeshLayer == nullptr || pAdapter == nullptr ||
+ dDisplayRange == nullptr) {
+ return false;
+ }
+
+ // MeshLayer 在触碰旧 Mapper 前完成全部校验,失败不会破坏旧画面。
+ return m_pMeshLayer->setInputConnection(
+ pAdapter->getOutputPort(),
+ pAdapter->getCurrentScalarArrayName(),
+ nmVTKMeshLayer::ScalarAssociation_CellData,
+ dDisplayRange[0], dDisplayRange[1]);
+ }
+
+ void applyLayerVisibility(bool bHasData)
+ {
+ synchronizeLayerPointers();
+ if(m_pMeshLayer != nullptr) {
+ m_pMeshLayer->setDisplayMode(m_bCellEdgesVisible
+ ? nmVTKMeshLayer::DisplayMode_SurfaceWithEdges
+ : nmVTKMeshLayer::DisplayMode_Surface);
+ m_pMeshLayer->setScalarVisibility(bHasData);
+ m_pMeshLayer->setVisible(bHasData && m_bMeshVisible);
+ }
+ if(m_pScalarBarLayer != nullptr) {
+ m_pScalarBarLayer->setVisible(
+ bHasData && m_bScalarBarVisible);
+ }
+ if(m_pScaleGridLayer != nullptr) {
+ m_pScaleGridLayer->setVisible(
+ bHasData && m_bScaleGridVisible);
+ }
+ if(m_pContainer != nullptr) {
+ m_pContainer->setRotationCenterPickTarget(
+ bHasData && m_bMeshVisible && m_pMeshLayer != nullptr
+ ? m_pMeshLayer->getActor() : nullptr);
+ }
+ }
+
+ bool restorePreviousScene()
+ {
+ synchronizeLayerPointers();
+ if(!hasOwnedBaseLayers() || m_pContainer == nullptr) {
+ return false;
+ }
+ if(m_pAdapter == nullptr || m_pSnapshot.isNull()) {
+ m_pMeshLayer->clearInput();
+ m_pContainer->clearPrimaryViewBounds();
+ if(m_pScalarBarLayer != nullptr) {
+ m_pScalarBarLayer->setLookupTable(nullptr);
+ m_pScalarBarLayer->setTitle(QString());
+ }
+ applyLayerVisibility(false);
+ return true;
+ }
+
+ const bool bMeshRestored = bindMeshAdapter(
+ m_pAdapter, m_dDisplayRange);
+ bool bGridRestored = !m_bHasBounds;
+ bool bBoundsRestored = !m_bHasBounds;
+ if(m_bHasBounds && m_pScaleGridLayer != nullptr) {
+ bGridRestored = m_pScaleGridLayer->setBounds(
+ m_dBounds[0], m_dBounds[1],
+ m_dBounds[2], m_dBounds[3], m_dBounds[4]);
+ bBoundsRestored = m_pContainer->setPrimaryViewBounds(m_dBounds);
+ }
+ if(m_pScalarBarLayer != nullptr) {
+ m_pScalarBarLayer->setLookupTable(
+ m_pMeshLayer->getLookupTable());
+ }
+ applyLayerVisibility(true);
+ return bMeshRestored && bGridRestored && bBoundsRestored;
+ }
+
+ void releaseActiveData()
+ {
+ // Scene 可能已由外部清空;先按稳定 ID 解除所有悬空观察指针。
+ synchronizeLayerPointers();
+ if(m_pContainer != nullptr) {
+ m_pContainer->setRotationCenterPickTarget(nullptr);
+ m_pContainer->clearPrimaryViewBounds();
+ }
+ if(m_pMeshLayer != nullptr) {
+ m_pMeshLayer->setScalarVisibility(false);
+ m_pMeshLayer->clearInput();
+ m_pMeshLayer->setVisible(false);
+ }
+ if(m_pScalarBarLayer != nullptr) {
+ m_pScalarBarLayer->setVisible(false);
+ m_pScalarBarLayer->setLookupTable(nullptr);
+ m_pScalarBarLayer->setTitle(QString());
+ }
+ if(m_pScaleGridLayer != nullptr) {
+ m_pScaleGridLayer->setVisible(false);
+ }
+ delete m_pAdapter;
+ m_pAdapter = nullptr;
+ m_pSnapshot.clear();
+ m_listAvailablePropertyIds.clear();
+ m_sCurrentPropertyId.clear();
+ m_nCurrentFrameIndex = -1;
+ m_bHasPropertyRange = false;
+ m_bHasBounds = false;
+ }
+
+ void removeOwnedLayers()
+ {
+ synchronizeLayerPointers();
+ nmVTKScene* pScene = getScene();
+ if(pScene == nullptr) {
+ return;
+ }
+
+ // 只删除仍与本控制器记录身份一致的图层,绝不误删同 ID 的外部替代层。
+ if(m_pScaleGridLayer != nullptr &&
+ pScene->getLayer(
+ QString::fromLatin1(RESULT_SCALE_GRID_LAYER_ID)) ==
+ m_pScaleGridLayer) {
+ pScene->removeLayer(
+ QString::fromLatin1(RESULT_SCALE_GRID_LAYER_ID));
+ }
+ m_pScaleGridLayer = nullptr;
+ if(m_pScalarBarLayer != nullptr &&
+ pScene->getLayer(
+ QString::fromLatin1(RESULT_SCALAR_BAR_LAYER_ID)) ==
+ m_pScalarBarLayer) {
+ pScene->removeLayer(
+ QString::fromLatin1(RESULT_SCALAR_BAR_LAYER_ID));
+ }
+ m_pScalarBarLayer = nullptr;
+ if(m_pMeshLayer != nullptr &&
+ pScene->getLayer(QString::fromLatin1(RESULT_MESH_LAYER_ID)) ==
+ m_pMeshLayer) {
+ pScene->removeLayer(
+ QString::fromLatin1(RESULT_MESH_LAYER_ID));
+ }
+ m_pMeshLayer = nullptr;
+ }
+
+ nmWxVTKRenderContainerWidget* m_pContainer; ///< 非所有权指针。
+ nmVTKMeshLayer* m_pMeshLayer; ///< Scene 所有。
+ nmVTKScalarBarLayer* m_pScalarBarLayer; ///< Scene 所有。
+ nmVTKPlanarScaleGridLayer* m_pScaleGridLayer; ///< Scene 所有。
+ nmPebiResultSnapshotRenderAdapter* m_pAdapter; ///< 控制器独占。
+ QSharedPointer m_pSnapshot;
+ QStringList m_listAvailablePropertyIds;
+ QString m_sCurrentPropertyId;
+ int m_nCurrentFrameIndex;
+ double m_dBusinessRange[2];
+ double m_dDisplayRange[2];
+ double m_dBounds[6];
+ bool m_bHasPropertyRange;
+ bool m_bHasBounds;
+ bool m_bMeshVisible;
+ bool m_bScalarBarVisible;
+ bool m_bScaleGridVisible;
+ bool m_bCellEdgesVisible;
+};
+
+nmPebiResultSceneController::nmPebiResultSceneController(
+ nmWxVTKRenderContainerWidget* pContainer)
+ : m_pPrivate(new nmPebiResultSceneControllerPrivate(pContainer))
+{
+ Q_ASSERT(isResultSceneGuiThread());
+}
+
+nmPebiResultSceneController::~nmPebiResultSceneController()
+{
+ Q_ASSERT(isResultSceneGuiThread());
+ if(m_pPrivate != nullptr) {
+ m_pPrivate->releaseActiveData();
+ // Controller 结束后立即移除自己的业务图层,使同一容器可安全创建新实例。
+ m_pPrivate->removeOwnedLayers();
+ m_pPrivate->m_pContainer = nullptr;
+ delete m_pPrivate;
+ m_pPrivate = nullptr;
+ }
+}
+
+bool nmPebiResultSceneController::setSnapshot(
+ const QSharedPointer& pSnapshot,
+ QString* pError)
+{
+ Q_ASSERT(isResultSceneGuiThread());
+ if(pError != nullptr) {
+ pError->clear();
+ }
+ if(!isResultSceneGuiThread() || m_pPrivate == nullptr ||
+ m_pPrivate->m_pContainer == nullptr || pSnapshot.isNull() ||
+ !pSnapshot->isComplete()) {
+ if(pError != nullptr) {
+ *pError = controllerText("The result snapshot is invalid.");
+ }
+ return false;
+ }
+ m_pPrivate->discardActiveDataIfLayersLost();
+ if(m_pPrivate->m_pSnapshot == pSnapshot &&
+ m_pPrivate->m_pSnapshot->getSnapshotId() ==
+ pSnapshot->getSnapshotId()) {
+ return true;
+ }
+
+ const QString sInitialProperty = chooseInitialProperty(pSnapshot);
+ nmPebiResultPropertyDescriptor oInitialDescriptor;
+ if(sInitialProperty.isEmpty() ||
+ !nmPebiResultPropertyCatalog::findDescriptor(
+ sInitialProperty, oInitialDescriptor) ||
+ oInitialDescriptor.m_eAssociation !=
+ NM_PebiResultPropertyAssociation_CellData) {
+ if(pError != nullptr) {
+ *pError = controllerText(
+ "The result snapshot has no renderable property.");
+ }
+ return false;
+ }
+
+ nmPebiResultSnapshotRenderAdapter* pCandidateAdapter =
+ new nmPebiResultSnapshotRenderAdapter();
+ if(!pCandidateAdapter->initialize(
+ pSnapshot,
+ nmPebiResultSnapshotRenderAdapter::SharedPropertyArray) ||
+ !pCandidateAdapter->setPropertyFrame(sInitialProperty, 0)) {
+ delete pCandidateAdapter;
+ if(pError != nullptr) {
+ *pError = controllerText(
+ "Unable to initialize the result render adapter.");
+ }
+ return false;
+ }
+
+ double dBusinessRange[2];
+ double dDisplayRange[2];
+ double dBounds[6];
+ if(pCandidateAdapter->getCurrentScalarArrayName().isEmpty() ||
+ !getValidatedPropertyRanges(pSnapshot, sInitialProperty,
+ dBusinessRange, dDisplayRange) ||
+ !validateAdapterOutput(pCandidateAdapter,
+ pSnapshot->getResultCellCount(), dBounds) ||
+ !areSnapshotWellsValid(pSnapshot)) {
+ delete pCandidateAdapter;
+ if(pError != nullptr) {
+ *pError = controllerText(
+ "The result render data failed validation.");
+ }
+ return false;
+ }
+
+ bool bCreatedMesh = false;
+ bool bCreatedScalarBar = false;
+ bool bCreatedScaleGrid = false;
+ if(!m_pPrivate->ensureBaseLayers(
+ bCreatedMesh, bCreatedScalarBar, bCreatedScaleGrid)) {
+ delete pCandidateAdapter;
+ if(pError != nullptr) {
+ *pError = controllerText(
+ "Unable to create the result render layers.");
+ }
+ return false;
+ }
+
+ bool bCommitted = m_pPrivate->m_pScaleGridLayer->setBounds(
+ dBounds[0], dBounds[1], dBounds[2],
+ dBounds[3], dBounds[4]) &&
+ m_pPrivate->m_pContainer->setPrimaryViewBounds(dBounds) &&
+ m_pPrivate->bindMeshAdapter(
+ pCandidateAdapter, dDisplayRange);
+ if(!bCommitted) {
+ const bool bRestored = m_pPrivate->restorePreviousScene();
+ if(!bRestored) {
+ // 不允许 Release 构建留下“逻辑有快照、实际无输入”的半恢复状态。
+ m_pPrivate->releaseActiveData();
+ }
+ m_pPrivate->rollbackCreatedLayers(
+ bCreatedMesh, bCreatedScalarBar, bCreatedScaleGrid);
+ delete pCandidateAdapter;
+ if(pError != nullptr) {
+ *pError = controllerText(
+ "Unable to commit the result render data.");
+ }
+ return false;
+ }
+
+ nmPebiResultSnapshotRenderAdapter* pPreviousAdapter =
+ m_pPrivate->m_pAdapter;
+ m_pPrivate->m_pAdapter = pCandidateAdapter;
+ m_pPrivate->m_pSnapshot = pSnapshot;
+ m_pPrivate->m_listAvailablePropertyIds =
+ pSnapshot->getAvailablePropertyIds();
+ m_pPrivate->m_sCurrentPropertyId = sInitialProperty;
+ m_pPrivate->m_nCurrentFrameIndex = 0;
+ m_pPrivate->m_dBusinessRange[0] = dBusinessRange[0];
+ m_pPrivate->m_dBusinessRange[1] = dBusinessRange[1];
+ m_pPrivate->m_dDisplayRange[0] = dDisplayRange[0];
+ m_pPrivate->m_dDisplayRange[1] = dDisplayRange[1];
+ copyBounds(dBounds, m_pPrivate->m_dBounds);
+ m_pPrivate->m_bHasPropertyRange = true;
+ m_pPrivate->m_bHasBounds = true;
+ m_pPrivate->m_pScalarBarLayer->setLookupTable(
+ m_pPrivate->m_pMeshLayer->getLookupTable());
+ m_pPrivate->applyLayerVisibility(true);
+
+ // Mapper 已不再引用旧 Producer,此时才允许释放旧 Adapter。
+ delete pPreviousAdapter;
+
+ const bool bViewFitted =
+ m_pPrivate->m_pContainer->fitTopViewWithoutRender();
+ const bool bCenterReset =
+ m_pPrivate->m_pContainer->resetRotationCenterWithoutRender();
+ Q_ASSERT(bViewFitted && bCenterReset);
+ Q_UNUSED(bViewFitted);
+ Q_UNUSED(bCenterReset);
+ // showReadyState 是本提交路径唯一的 Render 入口。
+ m_pPrivate->m_pContainer->showReadyState();
+ return true;
+}
+
+void nmPebiResultSceneController::clear()
+{
+ Q_ASSERT(isResultSceneGuiThread());
+ if(isResultSceneGuiThread() && m_pPrivate != nullptr) {
+ m_pPrivate->releaseActiveData();
+ }
+}
+
+bool nmPebiResultSceneController::applyPropertyFrame(
+ const QString& sPropertyId,
+ int nFrameIndex,
+ QString* pError)
+{
+ Q_ASSERT(isResultSceneGuiThread());
+ if(pError != nullptr) {
+ pError->clear();
+ }
+ if(isResultSceneGuiThread() && m_pPrivate != nullptr) {
+ m_pPrivate->discardActiveDataIfLayersLost();
+ }
+ if(!isResultSceneGuiThread() || m_pPrivate == nullptr ||
+ m_pPrivate->m_pAdapter == nullptr ||
+ m_pPrivate->m_pSnapshot.isNull() ||
+ !m_pPrivate->hasOwnedBaseLayers() ||
+ !m_pPrivate->m_pSnapshot->hasProperty(sPropertyId) ||
+ nFrameIndex < 0 ||
+ nFrameIndex >= m_pPrivate->m_pSnapshot->getPropertyFrameCount(
+ sPropertyId)) {
+ if(pError != nullptr) {
+ *pError = controllerText(
+ "The requested result property or frame is invalid.");
+ }
+ return false;
+ }
+
+ nmPebiResultPropertyDescriptor oDescriptor;
+ if(!nmPebiResultPropertyCatalog::findDescriptor(
+ sPropertyId, oDescriptor) ||
+ oDescriptor.m_eAssociation !=
+ NM_PebiResultPropertyAssociation_CellData ||
+ (oDescriptor.m_eTimeType == NM_PebiResultProperty_Static &&
+ nFrameIndex != 0)) {
+ if(pError != nullptr) {
+ *pError = controllerText(
+ "The requested result property or frame is invalid.");
+ }
+ return false;
+ }
+ if(sPropertyId == m_pPrivate->m_sCurrentPropertyId &&
+ nFrameIndex == m_pPrivate->m_nCurrentFrameIndex) {
+ return true;
+ }
+
+ double dBusinessRange[2];
+ double dDisplayRange[2];
+ if(!getValidatedPropertyRanges(
+ m_pPrivate->m_pSnapshot, sPropertyId,
+ dBusinessRange, dDisplayRange)) {
+ if(pError != nullptr) {
+ *pError = controllerText(
+ "The result property range is invalid.");
+ }
+ return false;
+ }
+
+ const QString sPreviousProperty =
+ m_pPrivate->m_sCurrentPropertyId;
+ const int nPreviousFrame = m_pPrivate->m_nCurrentFrameIndex;
+ const double dPreviousDisplayRange[2] = {
+ m_pPrivate->m_dDisplayRange[0],
+ m_pPrivate->m_dDisplayRange[1]
+ };
+ bool bApplied = m_pPrivate->m_pAdapter->setPropertyFrame(
+ sPropertyId, nFrameIndex);
+ double dCurrentBounds[6];
+ if(bApplied) {
+ bApplied = validateAdapterOutput(
+ m_pPrivate->m_pAdapter,
+ m_pPrivate->m_pSnapshot->getResultCellCount(),
+ dCurrentBounds) &&
+ m_pPrivate->m_pMeshLayer->setScalarArray(
+ m_pPrivate->m_pAdapter->getCurrentScalarArrayName(),
+ nmVTKMeshLayer::ScalarAssociation_CellData,
+ dDisplayRange[0], dDisplayRange[1]);
+ }
+ if(!bApplied) {
+ const bool bAdapterRestored =
+ m_pPrivate->m_pAdapter->setPropertyFrame(
+ sPreviousProperty, nPreviousFrame);
+ const bool bMapperRestored = bAdapterRestored &&
+ m_pPrivate->m_pMeshLayer->setScalarArray(
+ m_pPrivate->m_pAdapter->getCurrentScalarArrayName(),
+ nmVTKMeshLayer::ScalarAssociation_CellData,
+ dPreviousDisplayRange[0], dPreviousDisplayRange[1]);
+ if(!bMapperRestored) {
+ // 极端恢复失败时清空逻辑和 VTK 状态,保证查询结果与实际画面一致。
+ m_pPrivate->releaseActiveData();
+ }
+ if(pError != nullptr) {
+ *pError = controllerText(
+ "Unable to apply the result property or frame.");
+ }
+ return false;
+ }
+
+ m_pPrivate->m_sCurrentPropertyId = sPropertyId;
+ m_pPrivate->m_nCurrentFrameIndex = nFrameIndex;
+ m_pPrivate->m_dBusinessRange[0] = dBusinessRange[0];
+ m_pPrivate->m_dBusinessRange[1] = dBusinessRange[1];
+ m_pPrivate->m_dDisplayRange[0] = dDisplayRange[0];
+ m_pPrivate->m_dDisplayRange[1] = dDisplayRange[1];
+ m_pPrivate->m_bHasPropertyRange = true;
+ m_pPrivate->m_pContainer->renderView();
+ return true;
+}
+
+void nmPebiResultSceneController::setMeshVisible(bool bVisible)
+{
+ Q_ASSERT(isResultSceneGuiThread());
+ if(!isResultSceneGuiThread() || m_pPrivate == nullptr) {
+ return;
+ }
+ m_pPrivate->discardActiveDataIfLayersLost();
+ if(m_pPrivate->m_bMeshVisible == bVisible) {
+ return;
+ }
+ m_pPrivate->m_bMeshVisible = bVisible;
+ const bool bHasData = hasSnapshot();
+ m_pPrivate->applyLayerVisibility(bHasData);
+ if(bHasData) {
+ m_pPrivate->m_pContainer->renderView();
+ }
+}
+
+void nmPebiResultSceneController::setScalarBarVisible(bool bVisible)
+{
+ Q_ASSERT(isResultSceneGuiThread());
+ if(!isResultSceneGuiThread() || m_pPrivate == nullptr) {
+ return;
+ }
+ m_pPrivate->discardActiveDataIfLayersLost();
+ if(m_pPrivate->m_bScalarBarVisible == bVisible) {
+ return;
+ }
+ m_pPrivate->m_bScalarBarVisible = bVisible;
+ const bool bHasData = hasSnapshot();
+ m_pPrivate->applyLayerVisibility(bHasData);
+ if(bHasData) {
+ m_pPrivate->m_pContainer->renderView();
+ }
+}
+
+void nmPebiResultSceneController::setScaleGridVisible(bool bVisible)
+{
+ Q_ASSERT(isResultSceneGuiThread());
+ if(!isResultSceneGuiThread() || m_pPrivate == nullptr) {
+ return;
+ }
+ m_pPrivate->discardActiveDataIfLayersLost();
+ if(m_pPrivate->m_bScaleGridVisible == bVisible) {
+ return;
+ }
+ m_pPrivate->m_bScaleGridVisible = bVisible;
+ const bool bHasData = hasSnapshot();
+ m_pPrivate->applyLayerVisibility(bHasData);
+ if(bHasData) {
+ m_pPrivate->m_pContainer->renderView();
+ }
+}
+
+void nmPebiResultSceneController::setCellEdgesVisible(bool bVisible)
+{
+ Q_ASSERT(isResultSceneGuiThread());
+ if(!isResultSceneGuiThread() || m_pPrivate == nullptr) {
+ return;
+ }
+ m_pPrivate->discardActiveDataIfLayersLost();
+ if(m_pPrivate->m_bCellEdgesVisible == bVisible) {
+ return;
+ }
+ m_pPrivate->m_bCellEdgesVisible = bVisible;
+ const bool bHasData = hasSnapshot();
+ m_pPrivate->applyLayerVisibility(bHasData);
+ if(bHasData) {
+ m_pPrivate->m_pContainer->renderView();
+ }
+}
+
+bool nmPebiResultSceneController::hasSnapshot() const
+{
+ return isResultSceneGuiThread() &&
+ m_pPrivate != nullptr &&
+ m_pPrivate->m_pAdapter != nullptr &&
+ !m_pPrivate->m_pSnapshot.isNull() &&
+ m_pPrivate->hasOwnedBaseLayers();
+}
+
+QString nmPebiResultSceneController::getCurrentSnapshotId() const
+{
+ return hasSnapshot()
+ ? m_pPrivate->m_pSnapshot->getSnapshotId() : QString();
+}
+
+QStringList nmPebiResultSceneController::getAvailablePropertyIds() const
+{
+ return hasSnapshot()
+ ? m_pPrivate->m_listAvailablePropertyIds : QStringList();
+}
+
+QString nmPebiResultSceneController::getCurrentPropertyId() const
+{
+ return hasSnapshot()
+ ? m_pPrivate->m_sCurrentPropertyId : QString();
+}
+
+int nmPebiResultSceneController::getCurrentFrameIndex() const
+{
+ return hasSnapshot()
+ ? m_pPrivate->m_nCurrentFrameIndex : -1;
+}
+
+bool nmPebiResultSceneController::getCurrentPropertyRange(
+ double dRange[2]) const
+{
+ if(!hasSnapshot() || dRange == nullptr ||
+ !m_pPrivate->m_bHasPropertyRange) {
+ return false;
+ }
+ dRange[0] = m_pPrivate->m_dBusinessRange[0];
+ dRange[1] = m_pPrivate->m_dBusinessRange[1];
+ return true;
+}
+
+bool nmPebiResultSceneController::isMeshVisible() const
+{
+ return m_pPrivate != nullptr && m_pPrivate->m_bMeshVisible;
+}
+
+bool nmPebiResultSceneController::isScalarBarVisible() const
+{
+ return m_pPrivate != nullptr && m_pPrivate->m_bScalarBarVisible;
+}
+
+bool nmPebiResultSceneController::isScaleGridVisible() const
+{
+ return m_pPrivate != nullptr && m_pPrivate->m_bScaleGridVisible;
+}
+
+bool nmPebiResultSceneController::areCellEdgesVisible() const
+{
+ return m_pPrivate != nullptr && m_pPrivate->m_bCellEdgesVisible;
+}