feat(nmNum): 实现 PEBI 结果场景底座

- 为 MeshLayer 增加通用 VTK 管线输入与事务校验
- 增加主内容边界,隔离装饰图层对相机适配的影响
- 新增 PEBI 结果场景控制器及属性帧原子切换
- 统一 pressure 标量数组映射并更新中文翻译
feature/nmNum-postprocessing-view-20260910
lh 3 weeks ago
parent e98267c9c3
commit 4b595e0b55

Binary file not shown.

@ -2803,6 +2803,18 @@ Reason: %1</source>
<message><source>Unable to create the grid render layers.</source><translation>无法创建网格渲染图层。</translation></message>
<message><source>Unable to bind the grid render data.</source><translation>无法绑定网格渲染数据。</translation></message>
</context>
<context>
<name>nmPebiResultSceneController</name>
<message><source>The result snapshot is invalid.</source><translation>结果快照无效。</translation></message>
<message><source>The result snapshot has no renderable property.</source><translation>结果快照中没有可渲染属性。</translation></message>
<message><source>Unable to initialize the result render adapter.</source><translation>无法初始化结果渲染适配器。</translation></message>
<message><source>The result render data failed validation.</source><translation>结果渲染数据校验失败。</translation></message>
<message><source>Unable to create the result render layers.</source><translation>无法创建结果渲染图层。</translation></message>
<message><source>Unable to commit the result render data.</source><translation>无法提交结果渲染数据。</translation></message>
<message><source>The requested result property or frame is invalid.</source><translation>请求的结果属性或帧无效。</translation></message>
<message><source>The result property range is invalid.</source><translation>结果属性范围无效。</translation></message>
<message><source>Unable to apply the result property or frame.</source><translation>无法应用结果属性或帧。</translation></message>
</context>
<context>
<name>nmWxPebiGridViewWidget</name>
<message><source>No grid data.</source><translation>暂无网格数据。</translation></message>

@ -67,6 +67,8 @@ public:
int getCurrentFrameIndex() const;
/** @brief 返回当前已经绑定的属性 ID,尚未绑定时为空。 */
QString getCurrentPropertyId() const;
/** @brief 返回当前属性对应的稳定 VTK 数组名,尚未绑定时为空。 */
QString getCurrentScalarArrayName() const;
/** @brief 返回适配器渲染网格的单元数。 */
vtkIdType getCellCount() const;
/** @brief 返回当前是否使用零整帧复制的共享压力模式。 */

@ -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 的世界坐标。
};

@ -7,6 +7,7 @@
#include <vtkSmartPointer.h>
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<vtkDataSet> m_pDataSet; ///< 共享输入数据,不执行深拷贝。
vtkSmartPointer<vtkAlgorithmOutput> m_pInputConnection; ///< 共享管线输出端口。
vtkSmartPointer<vtkDataSetMapper> m_pMapper; ///< 图层独占的映射器。
vtkSmartPointer<vtkActor> m_pActor; ///< 图层主 Prop,由基类同步持有。
vtkSmartPointer<vtkLookupTable> m_pLookupTable; ///< 图层独占的颜色表。

@ -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);

@ -0,0 +1,75 @@
#ifndef NMPEBIRESULTSCENECONTROLLER_H
#define NMPEBIRESULTSCENECONTROLLER_H
#include "nmSubWxs_global.h"
#include <QSharedPointer>
#include <QString>
#include <QStringList>
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<const nmPebiResultSnapshot>& 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

@ -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 :

@ -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;
}

@ -5,8 +5,11 @@
#include <QtGlobal>
#include <vtkActor.h>
#include <vtkAlgorithm.h>
#include <vtkAlgorithmOutput.h>
#include <vtkCellData.h>
#include <vtkDataArray.h>
#include <vtkDataObject.h>
#include <vtkDataSet.h>
#include <vtkDataSetMapper.h>
#include <vtkLookupTable.h>
@ -29,6 +32,7 @@ bool isMeshLayerGuiThread()
nmVTKMeshLayer::nmVTKMeshLayer(const QString& sLayerId)
: nmVTKRenderLayer(sLayerId),
m_pDataSet(nullptr),
m_pInputConnection(nullptr),
m_pMapper(vtkSmartPointer<vtkDataSetMapper>::New()),
m_pActor(vtkSmartPointer<vtkActor>::New()),
m_pLookupTable(vtkSmartPointer<vtkLookupTable>::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());
}

@ -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)
{

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