feat(nmRender): 实现通用旋转中心拾取与独立枢轴交互

- 新增主网格一次性拾取和固定像素三轴中心标记
- 使用独立枢轴 Trackball,设置中心不再移动画面
- 完善 QAction、光标、交互模式及容器生命周期同步
- 隔离系统与业务 Scene,收口命令状态写接口
- 接入 PEBI 工具栏并同步工程、翻译和开发清单
feature/nmNum-postprocessing-view-20260910
lh 2 hours ago
parent 3eef7f3c82
commit 291aa998c6

Binary file not shown.

@ -5040,6 +5040,7 @@ Please check your input coordinates.</source>
<message><source>Right View</source><translation></translation></message>
<message><source>Isometric View</source><translation></translation></message>
<message><source>Reset Rotation Center</source><translation></translation></message>
<message><source>Set Rotation Center</source><translation></translation></message>
<message><source>Save as Image</source><translation></translation></message>
<message><source>Copy Image</source><translation></translation></message>
<message><source>Print</source><translation></translation></message>

@ -40,10 +40,16 @@ public:
bool setRightView();
/** @brief 设置透视等轴视图。 */
bool setIsometricView();
/** @brief 保持观察方向和距离,将焦点平移到指定世界坐标。 */
/** @brief 保存独立世界坐标旋转中心,不修改相机参数。 */
bool setRotationCenter(double dX, double dY, double dZ);
/** @brief 将旋转中心恢复为当前可见边界中心。 */
/** @brief 将独立旋转中心恢复为当前可见边界中心,不修改画面。 */
bool resetRotationCenter();
/** @brief 返回是否已经设置有效的独立旋转中心。 */
bool hasRotationCenter() const;
/** @brief 复制当前独立旋转中心;未设置或参数无效时返回 false。 */
bool getRotationCenter(double dCenter[3]) const;
/** @brief 清除独立旋转中心状态,不修改相机。 */
void clearRotationCenter();
private:
Q_DISABLE_COPY(nmVTKCameraController)
@ -62,6 +68,8 @@ private:
bool zoom(double dFactor);
vtkRenderer* m_pRenderer; ///< 非所有权指针,由渲染容器持有。
bool m_bHasRotationCenter;
double m_dRotationCenter[3]; ///< 独立于 vtkCamera::FocalPoint 的世界坐标。
};
#endif // NMVTKCAMERACONTROLLER_H

@ -5,8 +5,12 @@
#include <vtkSmartPointer.h>
class nmVTKCameraController;
class nmVTKPivotCameraInteractorStyle;
class vtkInteractorStyleTrackballCamera;
class vtkInteractorStyle;
class vtkRenderer;
class vtkRenderWindowInteractor;
/**
@ -17,13 +21,15 @@ class vtkRenderWindowInteractor;
class NM_RENDER_EXPORT nmVTKInteractionManager
{
public:
/** @brief 创建当前实例独占的默认 TrackballCamera 样式。 */
nmVTKInteractionManager();
/** @brief 创建当前实例独占的默认枢轴 TrackballCamera 样式。 */
explicit nmVTKInteractionManager(
nmVTKCameraController* pCameraController = nullptr);
/** @brief 先解除 InteractorStyle再释放默认样式。 */
~nmVTKInteractionManager();
/** @brief 绑定非所有权 Interactor并立即安装默认样式。 */
bool bindInteractor(vtkRenderWindowInteractor* pInteractor);
/** @brief 绑定非所有权 Interactor 和主 Renderer并立即安装默认样式。 */
bool bindInteractor(vtkRenderWindowInteractor* pInteractor,
vtkRenderer* pRenderer = nullptr);
/**
* @brief VTK
* @return Interactor true
@ -42,12 +48,14 @@ public:
vtkInteractorStyleTrackballCamera* getDefaultStyle() const;
/** @brief 返回当前活动样式的非所有权指针;未安装时返回 nullptr。 */
vtkInteractorStyle* getActiveStyle() const;
/** @brief 缓存状态和 Interactor 实际状态都指向默认样式时返回 true。 */
bool isDefaultInteractionActive() const;
private:
Q_DISABLE_COPY(nmVTKInteractionManager)
vtkRenderWindowInteractor* m_pInteractor; ///< 非所有权指针。
vtkSmartPointer<vtkInteractorStyleTrackballCamera> m_pDefaultStyle; ///< 当前实例独占。
vtkSmartPointer<nmVTKPivotCameraInteractorStyle> m_pDefaultStyle; ///< 当前实例独占。
vtkSmartPointer<vtkInteractorStyle> m_pActiveStyle; ///< 保持当前活动样式的 VTK 引用。
};

@ -0,0 +1,54 @@
#ifndef NMVTKPIVOTCAMERAINTERACTORSTYLE_H
#define NMVTKPIVOTCAMERAINTERACTORSTYLE_H
#include "nmRender_global.h"
#include <vtkInteractorStyleTrackballCamera.h>
class nmVTKCameraController;
/**
* @brief 使 TrackballCamera
*
* Rotate Spin ViewUp
* vtkCamera::FocalPointPanDolly
* VTK 7.1
*/
class NM_RENDER_EXPORT nmVTKPivotCameraInteractorStyle
: public vtkInteractorStyleTrackballCamera
{
public:
static nmVTKPivotCameraInteractorStyle* New();
vtkTypeMacro(nmVTKPivotCameraInteractorStyle,
vtkInteractorStyleTrackballCamera);
/** @brief 绑定旋转中心状态提供者;不增加其所有权。 */
void setCameraController(nmVTKCameraController* pController);
/** @brief 返回当前旋转中心状态提供者的非所有权指针。 */
nmVTKCameraController* getCameraController() const;
/** @brief 按 VTK Trackball 灵敏度绕独立枢轴执行水平和垂直旋转。 */
virtual void Rotate();
/** @brief 绕穿过独立枢轴的观察方向轴执行屏幕内旋转。 */
virtual void Spin();
protected:
nmVTKPivotCameraInteractorStyle();
virtual ~nmVTKPivotCameraInteractorStyle();
private:
nmVTKPivotCameraInteractorStyle(
const nmVTKPivotCameraInteractorStyle&);
void operator=(const nmVTKPivotCameraInteractorStyle&);
/** @brief 查询与当前 Renderer 匹配的有限旋转中心。 */
bool getCurrentRotationCenter(double dCenter[3]) const;
/** @brief 写回相机姿态、维护裁剪和灯光,并提交一次交互渲染。 */
void finishCameraMotion(const double dPosition[3],
const double dFocalPoint[3],
const double dViewUp[3]);
nmVTKCameraController* m_pCameraController; ///< 非所有权指针。
};
#endif // NMVTKPIVOTCAMERAINTERACTORSTYLE_H

@ -0,0 +1,56 @@
#ifndef NMVTKROTATIONCENTERMARKERLAYER_H
#define NMVTKROTATIONCENTERMARKERLAYER_H
#include "nmVTKRenderLayer.h"
class nmVTKRotationCenterMarkerLayerPrivate;
class vtkRenderer;
/**
* @brief
*
* X/Y/Z 线
* vtkCamera::FocalPoint沿
*
* Qt GUI 线
*/
class NM_RENDER_EXPORT nmVTKRotationCenterMarkerLayer
: public nmVTKRenderLayer
{
public:
/** @brief 创建初始隐藏的旋转中心标记图层。 */
explicit nmVTKRotationCenterMarkerLayer(const QString& sLayerId);
/** @brief 先解除相机观察关系,再释放标记渲染管线。 */
virtual ~nmVTKRotationCenterMarkerLayer();
/**
* @brief Scene Renderer
* @return Renderer true
*/
bool bindRenderer(vtkRenderer* pRenderer);
/** @brief 幂等移除 Renderer/Camera 观察关系及其非所有权引用。 */
void unbindRenderer();
/**
* @brief
* @note PolyData Render
*/
bool synchronize();
/** @brief 保存独立世界坐标中心并更新已有几何,不主动渲染。 */
bool setWorldCenter(double dX, double dY, double dZ);
/** @brief 返回是否已经保存有效的独立世界坐标中心。 */
bool hasWorldCenter() const;
/** @brief 修改标记显隐;无有效中心时不会显示,也不主动渲染。 */
void setMarkerVisible(bool bVisible);
/** @brief 返回调用方最后设置的标记显隐状态。 */
bool isMarkerVisible() const;
private:
Q_DISABLE_COPY(nmVTKRotationCenterMarkerLayer)
nmVTKRotationCenterMarkerLayerPrivate* m_pPrivate; ///< 独占实现对象。
};
#endif // NMVTKROTATIONCENTERMARKERLAYER_H

@ -0,0 +1,96 @@
#ifndef NMVTKROTATIONCENTERPICKER_H
#define NMVTKROTATIONCENTERPICKER_H
#include "nmRender_global.h"
#include <QCursor>
#include <vtkWeakPointer.h>
class QEvent;
class QMouseEvent;
class QVTKWidget;
class vtkProp;
class vtkRenderer;
/**
* @brief
*
* Qt Renderer
* Qt
*/
class NM_RENDER_EXPORT nmVTKRotationCenterPicker
{
public:
enum PickOutcome
{
PickOutcome_None = 0,
PickOutcome_Hit,
PickOutcome_Miss,
PickOutcome_Cancelled
};
struct PickEventResult
{
PickEventResult(bool bEventConsumed = false,
PickOutcome ePickOutcome = PickOutcome_None)
: bConsumed(bEventConsumed),
eOutcome(ePickOutcome)
{
}
bool bConsumed;
PickOutcome eOutcome;
};
nmVTKRotationCenterPicker();
~nmVTKRotationCenterPicker();
/** @brief 设置唯一可拾取的主几何;使用弱引用,不延长 Prop 生命周期。 */
void setPickTarget(vtkProp* pTarget);
vtkProp* getPickTarget() const;
bool hasPickTarget() const;
/**
* @brief
* @return Renderer true
*/
bool start(QVTKWidget* pWidget, vtkRenderer* pRenderer);
/** @brief 主动取消当前操作,清除待消费 Release 并恢复原光标。 */
PickEventResult cancel();
/** @brief 无语义清理临时状态,供失焦、清场和析构调用。 */
void reset();
bool isActive() const;
bool hasPendingRelease() const;
/**
* @brief Qt
*
* Press Release
*/
PickEventResult handleEvent(QEvent* pEvent);
/** @brief 复制最近一次 Hit 的世界坐标;没有有效命中时返回 false。 */
bool getPickedPosition(double dPosition[3]) const;
private:
Q_DISABLE_COPY(nmVTKRotationCenterPicker)
bool pickAt(const QMouseEvent* pMouseEvent);
void finishActiveMode();
void restoreCursor();
vtkWeakPointer<vtkProp> m_pPickTarget;
QVTKWidget* m_pWidget; ///< 非所有权指针,仅在活动模式期间有效。
vtkRenderer* m_pRenderer; ///< 非所有权指针,仅在活动模式期间有效。
QCursor m_oPreviousCursor;
int m_nPendingReleaseButton;
bool m_bHadExplicitCursor;
bool m_bCursorOverridden;
bool m_bActive;
bool m_bHasPickedPosition;
double m_dPickedPosition[3];
};
#endif // NMVTKROTATIONCENTERPICKER_H

@ -36,7 +36,8 @@ public:
ViewCommand_SaveImage = 0x0100,
ViewCommand_CopyImage = 0x0200,
ViewCommand_Print = 0x0400,
ViewCommand_PrintPreview = 0x0800
ViewCommand_PrintPreview = 0x0800,
ViewCommand_SetRotationCenter = 0x1000
};
Q_DECLARE_FLAGS(ViewCommands, ViewCommand)
@ -49,8 +50,6 @@ public:
QObject* pParent = nullptr);
virtual ~nmVTKViewCommandController();
/** @brief 设置当前视图对外提供的命令集合。 */
void setCapabilities(ViewCommands eCommands);
/** @brief 返回当前命令集合。 */
ViewCommands getCapabilities() const;
/** @brief 返回长期持有的 QAction调用方不得删除或修改其父对象。 */
@ -61,31 +60,43 @@ public:
/** @brief 按统一顺序构造右键菜单,并同步请求业务层追加扩展动作。 */
void populateContextMenu(QMenu* pMenu) const;
/** @brief 就绪状态启用命令,空、错误和加载状态统一禁用命令。 */
void setViewAvailable(bool bAvailable);
/** @brief 返回所属容器是否处于可显示画面的就绪状态。 */
bool isViewAvailable() const;
signals:
/** @brief 在视图命令和打印命令之间同步追加业务 QAction。 */
void contextMenuAboutToShow(QMenu* pMenu) const;
private slots:
/** @brief 仅供容器同步一次性拾取状态,不向容器反向发起请求。 */
void setRotationCenterPicking(bool bPicking);
private:
friend class nmWxVTKRenderContainerWidget;
Q_DISABLE_COPY(nmVTKViewCommandController)
enum { ViewCommandCount = 12 };
enum { ViewCommandCount = 13 };
/** @brief 仅供容器设置当前视图对外提供的命令集合。 */
void setCapabilities(ViewCommands eCommands);
/** @brief 仅供容器同步就绪、空、错误和加载状态。 */
void setViewAvailable(bool bAvailable);
QAction* createAction(ViewCommand eCommand,
const QString& sText,
const QString& sIconFile,
const char* pSlot);
int actionIndex(ViewCommand eCommand) const;
bool supports(ViewCommand eCommand) const;
/** @brief 仅供容器同步拾取目标、标记和交互模式的综合可用状态。 */
void setRotationCenterPickingAvailable(bool bAvailable);
void updateActionStates();
nmWxVTKRenderContainerWidget* m_pContainer; ///< 非所有权命令接收者。
QAction* m_pActions[ViewCommandCount]; ///< 本对象通过 Qt 父子关系拥有。
ViewCommands m_eCapabilities;
bool m_bViewAvailable;
bool m_bRotationCenterPickingAvailable;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(nmVTKViewCommandController::ViewCommands)

@ -18,8 +18,11 @@ class nmVTKCameraController;
class nmVTKImageExporter;
class nmVTKInteractionManager;
class nmVTKOrientationMarkerController;
class nmVTKRotationCenterMarkerLayer;
class nmVTKRotationCenterPicker;
class nmVTKScene;
class vtkProp;
class vtkRenderer;
class vtkRenderWindow;
class vtkRenderWindowInteractor;
@ -28,8 +31,9 @@ class vtkInteractorStyle;
/**
* @brief Qt + VTK
*
* RendererScene Actor
* Scene renderView
* Renderer Scene Scene
* Actor Scene
* Scene
*/
class NM_RENDER_EXPORT nmWxVTKRenderContainerWidget : public QWidget
{
@ -38,7 +42,7 @@ class NM_RENDER_EXPORT nmWxVTKRenderContainerWidget : public QWidget
public:
/** @brief 创建独立的 VTK 显示外壳,并初始化场景、相机、交互和方向轴服务。 */
explicit nmWxVTKRenderContainerWidget(QWidget* pParent = nullptr);
/** @brief 按命令、方向轴、交互、图层、相机和 Renderer 顺序解除资源。 */
/** @brief 按命令、拾取、方向轴、交互、图层、相机和 Renderer 顺序解除资源。 */
virtual ~nmWxVTKRenderContainerWidget();
/** @brief 返回 Qt 承载控件;调用方不得删除或重新设置父对象。 */
@ -52,7 +56,7 @@ public:
* @warning InteractorStyle使
*/
vtkRenderWindowInteractor* getInteractor() const;
/** @brief 返回当前容器独占的 Scene 非所有权指针。 */
/** @brief 返回业务 Sceneclear() 不影响容器内部系统图层。 */
nmVTKScene* getScene() const;
/** @brief 返回当前容器独占的通用命令控制器非所有权指针。 */
nmVTKViewCommandController* getViewCommandController() const;
@ -64,6 +68,13 @@ public:
void setViewCommandCapabilities(
nmVTKViewCommandController::ViewCommands eCommands);
/** @brief 注册唯一可拾取几何;传入 nullptr 会取消模式并隐藏中心标记。 */
void setRotationCenterPickTarget(vtkProp* pTarget);
/** @brief 返回当前是否正在等待用户选择旋转中心。 */
bool isRotationCenterPicking() const;
/** @brief 隐藏旋转中心标记,不修改独立中心或相机且不主动渲染。 */
void hideRotationCenterMarker();
/** @brief 安装临时交互样式,并由容器保持其 VTK 引用。 */
bool installInteractionStyle(vtkInteractorStyle* pStyle);
/** @brief 将 Interactor 恢复为当前容器独占的默认相机交互样式。 */
@ -79,6 +90,8 @@ public:
* 使 renderView
*/
bool fitTopViewWithoutRender();
/** @brief 将独立旋转中心设为可见边界中心,但不修改画面或立即渲染。 */
bool resetRotationCenterWithoutRender();
/** @brief 切换到 VTK 页面并提交一次渲染。 */
void showReadyState();
/** @brief 显示无数据状态,消息由业务调用方提供。 */
@ -94,6 +107,8 @@ signals:
* @warning pMenu
*/
void contextMenuAboutToShow(QMenu* pMenu);
/** @brief 将容器确认后的拾取状态同步给公共 QAction。 */
void rotationCenterPickingChanged(bool bPicking);
public slots:
/** @brief 按固定倍率 1.2 放大,成功后统一渲染一次。 */
@ -112,16 +127,20 @@ public slots:
void setRightView();
/** @brief 设置透视等轴视图。 */
void setIsometricView();
/** @brief 平移相机焦点和位置,将指定世界坐标设为旋转中心。 */
/** @brief 设置独立世界坐标旋转中心,不移动当前画面。 */
void setRotationCenter(double dX, double dY, double dZ);
/** @brief 将旋转中心恢复为当前可见边界中心。 */
/** @brief 将旋转中心恢复为当前可见边界中心,不移动当前画面。 */
void resetRotationCenter();
/** @brief 请求开始或取消一次性旋转中心拾取模式。 */
void setRotationCenterPickingEnabled(bool bEnabled);
protected:
/** @brief 只消费 QVTKWidget 的右键按下事件,其余事件保持原分发链。 */
/** @brief 优先处理拾取事件,再处理 QVTKWidget 的右键菜单。 */
virtual bool eventFilter(QObject* pObject, QEvent* pEvent);
private slots:
/** @brief 焦点切换完成后取消仍处于活动或待消费释放状态的拾取。 */
void cancelRotationCenterPickingAfterFocusOut();
/** @brief 弹出文件对话框并保存当前 RenderWindow 图像。 */
void saveImage();
/** @brief 将当前 RenderWindow 图像复制到系统剪贴板。 */
@ -144,6 +163,23 @@ private:
/** @brief 统一设置非就绪状态的文字、颜色和当前页面。 */
void showMessageState(ViewState eState, const QString& sMessage);
/** @brief 修改独立中心和可选标记但不渲染,由外层统一提交画面。 */
bool applyRotationCenter(double dX,
double dY,
double dZ,
bool bShowMarker);
/** @brief 取消模式、恢复光标并同步 QAction不修改既有中心标记。 */
void cancelRotationCenterPicking();
/** @brief 处理一次拾取事件;返回 true 表示事件必须停止向 VTK 传播。 */
bool handleRotationCenterPickEvent(QEvent* pEvent);
/** @brief 仅同步 Marker 世界几何,不主动渲染。 */
void synchronizeRotationCenterMarker();
/** @brief Ready、能力、主目标及独立中心均有效时显示标记。 */
void updateRotationCenterMarkerVisibility();
/** @brief 按目标、Marker 和当前交互样式同步命令的额外可用条件。 */
void updateRotationCenterCommandAvailability();
/** @brief 通过系统 Scene 稳定 ID 校验 Marker Layer 非所有权指针。 */
nmVTKRotationCenterMarkerLayer* getRotationCenterMarkerLayer() const;
/** @brief 判断调用线程是否为 Qt GUI 线程。 */
bool isGuiThread() const;
@ -156,12 +192,15 @@ private:
// Renderer 与通用服务均为当前容器实例独占,不与其他视图共享状态。
vtkSmartPointer<vtkRenderer> m_pRenderer;
nmVTKScene* m_pScene;
nmVTKScene* m_pScene; ///< 公开给业务控制器的图层集合。
nmVTKScene* m_pSystemScene; ///< 容器私有装饰图层集合,不对外暴露。
nmVTKCameraController* m_pCameraController;
nmVTKInteractionManager* m_pInteractionManager;
nmVTKImageExporter* m_pImageExporter;
nmVTKViewCommandController* m_pViewCommandController;
nmVTKOrientationMarkerController* m_pOrientationMarkerController;
nmVTKRotationCenterPicker* m_pRotationCenterPicker; ///< 本容器独占。
nmVTKRotationCenterMarkerLayer* m_pRotationCenterMarkerLayer; ///< 系统 Scene 所有。
};
#endif // NMWXVTKRENDERCONTAINERWIDGET_H

@ -20,9 +20,13 @@ bool isCameraGuiThread()
}
nmVTKCameraController::nmVTKCameraController(vtkRenderer* pRenderer)
: m_pRenderer(pRenderer)
: m_pRenderer(pRenderer),
m_bHasRotationCenter(false)
{
Q_ASSERT(isCameraGuiThread());
m_dRotationCenter[0] = 0.0;
m_dRotationCenter[1] = 0.0;
m_dRotationCenter[2] = 0.0;
}
nmVTKCameraController::~nmVTKCameraController()
@ -36,6 +40,9 @@ void nmVTKCameraController::setRenderer(vtkRenderer* pRenderer)
{
Q_ASSERT(isCameraGuiThread());
if(isCameraGuiThread()) {
if(m_pRenderer != pRenderer) {
clearRotationCenter();
}
m_pRenderer = pRenderer;
}
}
@ -129,22 +136,11 @@ bool nmVTKCameraController::setRotationCenter(
return false;
}
vtkCamera* pCamera = m_pRenderer->GetActiveCamera();
if(pCamera == nullptr) {
return false;
}
double dOldFocalPoint[3];
double dOldPosition[3];
pCamera->GetFocalPoint(dOldFocalPoint);
pCamera->GetPosition(dOldPosition);
pCamera->SetFocalPoint(dX, dY, dZ);
// 同步平移相机位置,保持原观察方向和相机到焦点的距离。
pCamera->SetPosition(
dOldPosition[0] + dX - dOldFocalPoint[0],
dOldPosition[1] + dY - dOldFocalPoint[1],
dOldPosition[2] + dZ - dOldFocalPoint[2]);
m_pRenderer->ResetCameraClippingRange();
// 旋转中心是独立交互状态;选择中心时不得改写当前相机画面。
m_dRotationCenter[0] = dX;
m_dRotationCenter[1] = dY;
m_dRotationCenter[2] = dZ;
m_bHasRotationCenter = true;
return true;
}
@ -155,12 +151,33 @@ bool nmVTKCameraController::resetRotationCenter()
if(!isCameraGuiThread() || !getVisibleBounds(dBounds)) {
return false;
}
// 复用平移逻辑,保持当前观察方向、投影方式和观察距离不变。
// 只更新独立枢轴;当前相机 Position/FocalPoint/ViewUp 保持不变。
return setRotationCenter((dBounds[0] + dBounds[1]) * 0.5,
(dBounds[2] + dBounds[3]) * 0.5,
(dBounds[4] + dBounds[5]) * 0.5);
}
bool nmVTKCameraController::hasRotationCenter() const
{
return m_bHasRotationCenter;
}
bool nmVTKCameraController::getRotationCenter(double dCenter[3]) const
{
if(dCenter == nullptr || !m_bHasRotationCenter) {
return false;
}
dCenter[0] = m_dRotationCenter[0];
dCenter[1] = m_dRotationCenter[1];
dCenter[2] = m_dRotationCenter[2];
return true;
}
void nmVTKCameraController::clearRotationCenter()
{
m_bHasRotationCenter = false;
}
bool nmVTKCameraController::getVisibleBounds(double dBounds[6]) const
{
if(m_pRenderer == nullptr || dBounds == nullptr) {

@ -1,10 +1,12 @@
#include "nmVTKInteractionManager.h"
#include "nmVTKPivotCameraInteractorStyle.h"
#include <QCoreApplication>
#include <QThread>
#include <vtkInteractorStyle.h>
#include <vtkInteractorStyleTrackballCamera.h>
#include <vtkRenderer.h>
#include <vtkRenderWindowInteractor.h>
namespace {
@ -19,13 +21,15 @@ bool isInteractionGuiThread()
}
nmVTKInteractionManager::nmVTKInteractionManager()
nmVTKInteractionManager::nmVTKInteractionManager(
nmVTKCameraController* pCameraController)
: m_pInteractor(nullptr),
m_pDefaultStyle(
vtkSmartPointer<vtkInteractorStyleTrackballCamera>::New()),
vtkSmartPointer<nmVTKPivotCameraInteractorStyle>::New()),
m_pActiveStyle(nullptr)
{
Q_ASSERT(isInteractionGuiThread());
m_pDefaultStyle->setCameraController(pCameraController);
}
nmVTKInteractionManager::~nmVTKInteractionManager()
@ -34,11 +38,15 @@ nmVTKInteractionManager::~nmVTKInteractionManager()
// Interactor 持有 Style 引用,必须先清除绑定再释放智能指针。
unbindInteractor();
m_pActiveStyle = nullptr;
if(m_pDefaultStyle != nullptr) {
m_pDefaultStyle->setCameraController(nullptr);
}
m_pDefaultStyle = nullptr;
}
bool nmVTKInteractionManager::bindInteractor(
vtkRenderWindowInteractor* pInteractor)
vtkRenderWindowInteractor* pInteractor,
vtkRenderer* pRenderer)
{
Q_ASSERT(isInteractionGuiThread());
if(!isInteractionGuiThread() || pInteractor == nullptr ||
@ -51,6 +59,8 @@ bool nmVTKInteractionManager::bindInteractor(
unbindInteractor();
m_pInteractor = pInteractor;
}
m_pDefaultStyle->SetDefaultRenderer(pRenderer);
m_pDefaultStyle->SetCurrentRenderer(pRenderer);
return restoreDefaultInteraction();
}
@ -101,6 +111,11 @@ void nmVTKInteractionManager::unbindInteractor()
// 本管理器定义单一活动模式边界,解绑时不保留任何旧 Style。
clearInteractionStyle();
if(m_pDefaultStyle != nullptr) {
// DefaultRenderer 会覆盖 CurrentRenderer必须先解除前者。
m_pDefaultStyle->SetDefaultRenderer(nullptr);
m_pDefaultStyle->SetCurrentRenderer(nullptr);
}
m_pInteractor = nullptr;
}
@ -112,10 +127,19 @@ vtkRenderWindowInteractor* nmVTKInteractionManager::getInteractor() const
vtkInteractorStyleTrackballCamera*
nmVTKInteractionManager::getDefaultStyle() const
{
return m_pDefaultStyle;
return m_pDefaultStyle.GetPointer();
}
vtkInteractorStyle* nmVTKInteractionManager::getActiveStyle() const
{
return m_pActiveStyle;
}
bool nmVTKInteractionManager::isDefaultInteractionActive() const
{
vtkInteractorStyle* pDefaultStyle = m_pDefaultStyle.GetPointer();
vtkInteractorStyle* pActiveStyle = m_pActiveStyle.GetPointer();
return m_pInteractor != nullptr && pDefaultStyle != nullptr &&
pActiveStyle == pDefaultStyle &&
m_pInteractor->GetInteractorStyle() == pDefaultStyle;
}

@ -0,0 +1,295 @@
#include "nmVTKPivotCameraInteractorStyle.h"
#include "nmVTKCameraController.h"
#include <vtkCamera.h>
#include <vtkMath.h>
#include <vtkObjectFactory.h>
#include <vtkRenderer.h>
#include <vtkRenderWindowInteractor.h>
#include <cmath>
vtkStandardNewMacro(nmVTKPivotCameraInteractorStyle);
namespace {
const double ROTATION_ANGLE_EPSILON = 1.0e-12;
bool isFiniteVector3(const double dVector[3])
{
return dVector != nullptr &&
vtkMath::IsFinite(dVector[0]) &&
vtkMath::IsFinite(dVector[1]) &&
vtkMath::IsFinite(dVector[2]);
}
bool rotateVector(const double dInput[3],
const double dAxis[3],
double dAngleDegrees,
double dOutput[3])
{
if(!isFiniteVector3(dInput) || !isFiniteVector3(dAxis) ||
!vtkMath::IsFinite(dAngleDegrees) || dOutput == nullptr) {
return false;
}
if(std::fabs(dAngleDegrees) <= ROTATION_ANGLE_EPSILON) {
dOutput[0] = dInput[0];
dOutput[1] = dInput[1];
dOutput[2] = dInput[2];
return true;
}
double dUnitAxis[3] = {
dAxis[0], dAxis[1], dAxis[2]
};
if(vtkMath::Normalize(dUnitAxis) <= 0.0) {
return false;
}
const double dAngleRadians =
vtkMath::RadiansFromDegrees(dAngleDegrees);
const double dCosine = std::cos(dAngleRadians);
const double dSine = std::sin(dAngleRadians);
const double dProjection = vtkMath::Dot(dUnitAxis, dInput);
double dCross[3];
vtkMath::Cross(dUnitAxis, dInput, dCross);
for(int nComponent = 0; nComponent < 3; ++nComponent) {
dOutput[nComponent] =
dInput[nComponent] * dCosine +
dCross[nComponent] * dSine +
dUnitAxis[nComponent] * dProjection *
(1.0 - dCosine);
}
return isFiniteVector3(dOutput);
}
bool rotateCameraFrame(double dPosition[3],
double dFocalPoint[3],
double dViewUp[3],
const double dCenter[3],
const double dAxis[3],
double dAngleDegrees)
{
if(!isFiniteVector3(dPosition) ||
!isFiniteVector3(dFocalPoint) ||
!isFiniteVector3(dViewUp) ||
!isFiniteVector3(dCenter)) {
return false;
}
double dRelativePosition[3];
double dRelativeFocalPoint[3];
for(int nComponent = 0; nComponent < 3; ++nComponent) {
dRelativePosition[nComponent] =
dPosition[nComponent] - dCenter[nComponent];
dRelativeFocalPoint[nComponent] =
dFocalPoint[nComponent] - dCenter[nComponent];
}
double dRotatedPosition[3];
double dRotatedFocalPoint[3];
double dRotatedViewUp[3];
if(!rotateVector(dRelativePosition, dAxis, dAngleDegrees,
dRotatedPosition) ||
!rotateVector(dRelativeFocalPoint, dAxis, dAngleDegrees,
dRotatedFocalPoint) ||
!rotateVector(dViewUp, dAxis, dAngleDegrees,
dRotatedViewUp)) {
return false;
}
for(int nComponent = 0; nComponent < 3; ++nComponent) {
dPosition[nComponent] =
dCenter[nComponent] + dRotatedPosition[nComponent];
dFocalPoint[nComponent] =
dCenter[nComponent] + dRotatedFocalPoint[nComponent];
dViewUp[nComponent] = dRotatedViewUp[nComponent];
}
return isFiniteVector3(dPosition) &&
isFiniteVector3(dFocalPoint) &&
isFiniteVector3(dViewUp);
}
}
nmVTKPivotCameraInteractorStyle::nmVTKPivotCameraInteractorStyle()
: m_pCameraController(nullptr)
{
}
nmVTKPivotCameraInteractorStyle::~nmVTKPivotCameraInteractorStyle()
{
m_pCameraController = nullptr;
}
void nmVTKPivotCameraInteractorStyle::setCameraController(
nmVTKCameraController* pController)
{
m_pCameraController = pController;
}
nmVTKCameraController*
nmVTKPivotCameraInteractorStyle::getCameraController() const
{
return m_pCameraController;
}
void nmVTKPivotCameraInteractorStyle::Rotate()
{
double dCenter[3];
if(!getCurrentRotationCenter(dCenter)) {
vtkInteractorStyleTrackballCamera::Rotate();
return;
}
if(this->CurrentRenderer == nullptr || this->Interactor == nullptr) {
return;
}
vtkCamera* pCamera = this->CurrentRenderer->GetActiveCamera();
int* pRendererSize = this->CurrentRenderer->GetSize();
int* pEventPosition = this->Interactor->GetEventPosition();
int* pLastEventPosition = this->Interactor->GetLastEventPosition();
if(pCamera == nullptr || pRendererSize == nullptr ||
pRendererSize[0] <= 0 || pRendererSize[1] <= 0 ||
pEventPosition == nullptr || pLastEventPosition == nullptr) {
return;
}
const double dAzimuth =
(pEventPosition[0] - pLastEventPosition[0]) *
(-20.0 / pRendererSize[0]) * this->MotionFactor;
const double dElevation =
(pEventPosition[1] - pLastEventPosition[1]) *
(-20.0 / pRendererSize[1]) * this->MotionFactor;
double dPosition[3];
double dFocalPoint[3];
double dViewUp[3];
pCamera->GetPosition(dPosition);
pCamera->GetFocalPoint(dFocalPoint);
pCamera->GetViewUp(dViewUp);
if(!rotateCameraFrame(dPosition, dFocalPoint, dViewUp,
dCenter, dViewUp, dAzimuth)) {
return;
}
// vtkCamera::Elevation 使用 (-DirectionOfProjection) x ViewUp。
double dNegativeDirection[3] = {
dPosition[0] - dFocalPoint[0],
dPosition[1] - dFocalPoint[1],
dPosition[2] - dFocalPoint[2]
};
double dElevationAxis[3];
vtkMath::Cross(dNegativeDirection, dViewUp, dElevationAxis);
if(!rotateCameraFrame(dPosition, dFocalPoint, dViewUp,
dCenter, dElevationAxis, dElevation)) {
return;
}
finishCameraMotion(dPosition, dFocalPoint, dViewUp);
}
void nmVTKPivotCameraInteractorStyle::Spin()
{
double dCenter[3];
if(!getCurrentRotationCenter(dCenter)) {
vtkInteractorStyleTrackballCamera::Spin();
return;
}
if(this->CurrentRenderer == nullptr || this->Interactor == nullptr) {
return;
}
vtkCamera* pCamera = this->CurrentRenderer->GetActiveCamera();
int* pEventPosition = this->Interactor->GetEventPosition();
int* pLastEventPosition = this->Interactor->GetLastEventPosition();
if(pCamera == nullptr || pEventPosition == nullptr ||
pLastEventPosition == nullptr) {
return;
}
this->CurrentRenderer->SetWorldPoint(
dCenter[0], dCenter[1], dCenter[2], 1.0);
this->CurrentRenderer->WorldToDisplay();
const double* pDisplayCenter =
this->CurrentRenderer->GetDisplayPoint();
if(!isFiniteVector3(pDisplayCenter)) {
return;
}
const double dNewAngle = std::atan2(
pEventPosition[1] - pDisplayCenter[1],
pEventPosition[0] - pDisplayCenter[0]);
const double dOldAngle = std::atan2(
pLastEventPosition[1] - pDisplayCenter[1],
pLastEventPosition[0] - pDisplayCenter[0]);
double dSpinAngle = vtkMath::DegreesFromRadians(
dNewAngle - dOldAngle);
if(dSpinAngle > 180.0) {
dSpinAngle -= 360.0;
} else if(dSpinAngle < -180.0) {
dSpinAngle += 360.0;
}
double dPosition[3];
double dFocalPoint[3];
double dViewUp[3];
pCamera->GetPosition(dPosition);
pCamera->GetFocalPoint(dFocalPoint);
pCamera->GetViewUp(dViewUp);
double dDirectionOfProjection[3] = {
dFocalPoint[0] - dPosition[0],
dFocalPoint[1] - dPosition[1],
dFocalPoint[2] - dPosition[2]
};
if(!rotateCameraFrame(dPosition, dFocalPoint, dViewUp,
dCenter, dDirectionOfProjection,
dSpinAngle)) {
return;
}
finishCameraMotion(dPosition, dFocalPoint, dViewUp);
}
bool nmVTKPivotCameraInteractorStyle::getCurrentRotationCenter(
double dCenter[3]) const
{
return dCenter != nullptr && m_pCameraController != nullptr &&
this->CurrentRenderer != nullptr &&
m_pCameraController->getRenderer() == this->CurrentRenderer &&
m_pCameraController->getRotationCenter(dCenter);
}
void nmVTKPivotCameraInteractorStyle::finishCameraMotion(
const double dPosition[3],
const double dFocalPoint[3],
const double dViewUp[3])
{
if(this->CurrentRenderer == nullptr || this->Interactor == nullptr ||
!isFiniteVector3(dPosition) ||
!isFiniteVector3(dFocalPoint) ||
!isFiniteVector3(dViewUp)) {
return;
}
vtkCamera* pCamera = this->CurrentRenderer->GetActiveCamera();
if(pCamera == nullptr) {
return;
}
pCamera->SetPosition(dPosition);
pCamera->SetFocalPoint(dFocalPoint);
pCamera->SetViewUp(dViewUp);
pCamera->OrthogonalizeViewUp();
if(this->AutoAdjustCameraClippingRange != 0) {
this->CurrentRenderer->ResetCameraClippingRange();
}
if(this->Interactor->GetLightFollowCamera() != 0) {
this->CurrentRenderer->UpdateLightsGeometryToFollowCamera();
}
this->Interactor->Render();
}

@ -0,0 +1,530 @@
#include "nmVTKRotationCenterMarkerLayer.h"
#include <QCoreApplication>
#include <QThread>
#include <vtkActor.h>
#include <vtkCallbackCommand.h>
#include <vtkCamera.h>
#include <vtkCellArray.h>
#include <vtkCellData.h>
#include <vtkCommand.h>
#include <vtkMath.h>
#include <vtkPoints.h>
#include <vtkPolyData.h>
#include <vtkPolyDataMapper.h>
#include <vtkProperty.h>
#include <vtkRenderer.h>
#include <vtkUnsignedCharArray.h>
#include <vtkWeakPointer.h>
#include <cmath>
#include <limits>
namespace {
const double ROTATION_CENTER_HALF_LENGTH_PIXELS = 18.0;
const double ROTATION_CENTER_DEPTH_OFFSET_PIXELS = 1.5;
const double ROTATION_CENTER_HOMOGENEOUS_EPSILON =
std::numeric_limits<double>::epsilon();
bool isRotationCenterMarkerGuiThread()
{
QCoreApplication* pApplication = QCoreApplication::instance();
return pApplication == nullptr ||
pApplication->thread() == QThread::currentThread();
}
bool isFinitePoint3(const double dPoint[3])
{
return dPoint != nullptr &&
vtkMath::IsFinite(dPoint[0]) &&
vtkMath::IsFinite(dPoint[1]) &&
vtkMath::IsFinite(dPoint[2]);
}
bool worldToDisplay(vtkRenderer* pRenderer,
const double dWorld[3],
double dDisplay[3])
{
if(pRenderer == nullptr || dWorld == nullptr || dDisplay == nullptr) {
return false;
}
pRenderer->SetWorldPoint(dWorld[0], dWorld[1], dWorld[2], 1.0);
pRenderer->WorldToDisplay();
const double* pDisplay = pRenderer->GetDisplayPoint();
if(!isFinitePoint3(pDisplay)) {
return false;
}
dDisplay[0] = pDisplay[0];
dDisplay[1] = pDisplay[1];
dDisplay[2] = pDisplay[2];
return true;
}
bool displayToWorld(vtkRenderer* pRenderer,
const double dDisplay[3],
double dWorld[3])
{
if(pRenderer == nullptr || dDisplay == nullptr || dWorld == nullptr ||
!isFinitePoint3(dDisplay)) {
return false;
}
pRenderer->SetDisplayPoint(
dDisplay[0], dDisplay[1], dDisplay[2]);
pRenderer->DisplayToWorld();
const double* pHomogeneousWorld = pRenderer->GetWorldPoint();
if(pHomogeneousWorld == nullptr ||
!vtkMath::IsFinite(pHomogeneousWorld[0]) ||
!vtkMath::IsFinite(pHomogeneousWorld[1]) ||
!vtkMath::IsFinite(pHomogeneousWorld[2]) ||
!vtkMath::IsFinite(pHomogeneousWorld[3]) ||
std::fabs(pHomogeneousWorld[3]) <=
ROTATION_CENTER_HOMOGENEOUS_EPSILON) {
return false;
}
const double dInverseW = 1.0 / pHomogeneousWorld[3];
dWorld[0] = pHomogeneousWorld[0] * dInverseW;
dWorld[1] = pHomogeneousWorld[1] * dInverseW;
dWorld[2] = pHomogeneousWorld[2] * dInverseW;
return isFinitePoint3(dWorld);
}
}
class nmVTKRotationCenterMarkerLayerPrivate
{
public:
nmVTKRotationCenterMarkerLayerPrivate()
: m_pPoints(vtkSmartPointer<vtkPoints>::New()),
m_pLines(vtkSmartPointer<vtkCellArray>::New()),
m_pColors(vtkSmartPointer<vtkUnsignedCharArray>::New()),
m_pPolyData(vtkSmartPointer<vtkPolyData>::New()),
m_pMapper(vtkSmartPointer<vtkPolyDataMapper>::New()),
m_pActor(vtkSmartPointer<vtkActor>::New()),
m_pRendererCallback(vtkSmartPointer<vtkCallbackCommand>::New()),
m_pCameraCallback(vtkSmartPointer<vtkCallbackCommand>::New()),
m_pRenderer(nullptr),
m_pCamera(nullptr),
m_nRendererObserverTag(0),
m_nCameraObserverTag(0),
m_bHasWorldCenter(false)
{
// 必须通过 vtkActor::New() 的对象工厂路径获得实际 OpenGL Actor。
initializeGeometry();
m_dWorldCenter[0] = 0.0;
m_dWorldCenter[1] = 0.0;
m_dWorldCenter[2] = 0.0;
m_pRendererCallback->SetClientData(this);
m_pRendererCallback->SetCallback(
nmVTKRotationCenterMarkerLayerPrivate::rendererStarted);
m_pCameraCallback->SetClientData(this);
m_pCameraCallback->SetCallback(
nmVTKRotationCenterMarkerLayerPrivate::cameraModified);
}
~nmVTKRotationCenterMarkerLayerPrivate()
{
// 先移除 Renderer 和 Camera Observer再阻断裸回调目标。
unbindRenderer();
m_pRendererCallback->SetClientData(nullptr);
m_pRendererCallback->SetCallback(nullptr);
m_pCameraCallback->SetClientData(nullptr);
m_pCameraCallback->SetCallback(nullptr);
}
vtkActor* getActor() const
{
return m_pActor;
}
bool bindRenderer(vtkRenderer* pRenderer)
{
if(pRenderer == nullptr) {
return false;
}
if(m_pRenderer.GetPointer() != pRenderer) {
unbindRenderer();
m_pRenderer = pRenderer;
}
if(!ensureRendererObserver(pRenderer) ||
!ensureCameraObserver(pRenderer->GetActiveCamera())) {
unbindRenderer();
return false;
}
return true;
}
void unbindRenderer()
{
removeCameraObserver();
removeRendererObserver();
m_pRenderer = nullptr;
}
bool synchronize()
{
return synchronizeForRenderer(m_pRenderer.GetPointer());
}
bool synchronizeForRenderer(vtkRenderer* pRenderer)
{
if(pRenderer == nullptr || m_pRenderer.GetPointer() != pRenderer ||
!m_bHasWorldCenter) {
return false;
}
vtkCamera* pCamera = pRenderer->GetActiveCamera();
if(pCamera == nullptr || !ensureCameraObserver(pCamera)) {
return false;
}
int* pViewportSize = pRenderer->GetSize();
if(pViewportSize == nullptr || pViewportSize[0] <= 0 ||
pViewportSize[1] <= 0) {
return false;
}
const double dLogicalCenter[3] = {
m_dWorldCenter[0], m_dWorldCenter[1], m_dWorldCenter[2]
};
double dCameraPosition[3];
pCamera->GetPosition(dCameraPosition);
if(!isFinitePoint3(dLogicalCenter) ||
!isFinitePoint3(dCameraPosition)) {
return false;
}
double dCenterDisplay[3];
if(!worldToDisplay(pRenderer, dLogicalCenter, dCenterDisplay)) {
return false;
}
double dOffsetDisplay[3] = {
dCenterDisplay[0],
dCenterDisplay[1] + ROTATION_CENTER_HALF_LENGTH_PIXELS,
dCenterDisplay[2]
};
double dCenterAtDepth[3];
double dOffsetAtDepth[3];
if(!displayToWorld(pRenderer, dCenterDisplay, dCenterAtDepth) ||
!displayToWorld(pRenderer, dOffsetDisplay, dOffsetAtDepth)) {
return false;
}
const double dScaleX = dOffsetAtDepth[0] - dCenterAtDepth[0];
const double dScaleY = dOffsetAtDepth[1] - dCenterAtDepth[1];
const double dScaleZ = dOffsetAtDepth[2] - dCenterAtDepth[2];
const double dHalfLength = std::sqrt(
dScaleX * dScaleX +
dScaleY * dScaleY +
dScaleZ * dScaleZ);
if(!vtkMath::IsFinite(dHalfLength) || dHalfLength <= 0.0) {
return false;
}
double dTowardCamera[3];
if(pCamera->GetParallelProjection() != 0) {
const double* pDirectionOfProjection =
pCamera->GetDirectionOfProjection();
if(!isFinitePoint3(pDirectionOfProjection)) {
return false;
}
dTowardCamera[0] = -pDirectionOfProjection[0];
dTowardCamera[1] = -pDirectionOfProjection[1];
dTowardCamera[2] = -pDirectionOfProjection[2];
} else {
dTowardCamera[0] = dCameraPosition[0] - dLogicalCenter[0];
dTowardCamera[1] = dCameraPosition[1] - dLogicalCenter[1];
dTowardCamera[2] = dCameraPosition[2] - dLogicalCenter[2];
}
if(vtkMath::Normalize(dTowardCamera) <= 0.0) {
return false;
}
const double dWorldPerPixel =
dHalfLength / ROTATION_CENTER_HALF_LENGTH_PIXELS;
const double dDepthOffset =
dWorldPerPixel * ROTATION_CENTER_DEPTH_OFFSET_PIXELS;
const double dDisplayCenter[3] = {
dLogicalCenter[0] + dTowardCamera[0] * dDepthOffset,
dLogicalCenter[1] + dTowardCamera[1] * dDepthOffset,
dLogicalCenter[2] + dTowardCamera[2] * dDepthOffset
};
const double dAxisPoints[6][3] = {
{ dDisplayCenter[0] - dHalfLength,
dDisplayCenter[1], dDisplayCenter[2] },
{ dDisplayCenter[0] + dHalfLength,
dDisplayCenter[1], dDisplayCenter[2] },
{ dDisplayCenter[0],
dDisplayCenter[1] - dHalfLength, dDisplayCenter[2] },
{ dDisplayCenter[0],
dDisplayCenter[1] + dHalfLength, dDisplayCenter[2] },
{ dDisplayCenter[0], dDisplayCenter[1],
dDisplayCenter[2] - dHalfLength },
{ dDisplayCenter[0], dDisplayCenter[1],
dDisplayCenter[2] + dHalfLength }
};
for(vtkIdType nPoint = 0; nPoint < 6; ++nPoint) {
if(!isFinitePoint3(dAxisPoints[nPoint])) {
return false;
}
}
for(vtkIdType nPoint = 0; nPoint < 6; ++nPoint) {
m_pPoints->SetPoint(nPoint, dAxisPoints[nPoint]);
}
m_pPoints->Modified();
m_pPolyData->Modified();
return true;
}
bool setWorldCenter(double dX, double dY, double dZ)
{
const double dCenter[3] = { dX, dY, dZ };
if(!isFinitePoint3(dCenter)) {
return false;
}
m_dWorldCenter[0] = dX;
m_dWorldCenter[1] = dY;
m_dWorldCenter[2] = dZ;
m_bHasWorldCenter = true;
synchronize();
return true;
}
bool hasWorldCenter() const
{
return m_bHasWorldCenter;
}
private:
static void rendererStarted(vtkObject* pCaller,
unsigned long,
void* pClientData,
void*)
{
nmVTKRotationCenterMarkerLayerPrivate* pThis =
static_cast<nmVTKRotationCenterMarkerLayerPrivate*>(
pClientData);
if(pThis != nullptr) {
// StartEvent 位于 Prop 遍历之前,可使用最终视口尺寸覆盖 Resize 路径。
pThis->synchronizeForRenderer(
vtkRenderer::SafeDownCast(pCaller));
}
}
static void cameraModified(vtkObject*,
unsigned long,
void* pClientData,
void*)
{
nmVTKRotationCenterMarkerLayerPrivate* pThis =
static_cast<nmVTKRotationCenterMarkerLayerPrivate*>(
pClientData);
if(pThis != nullptr) {
// 相机回调只同步几何,最终 Render 仍由交互器或容器统一提交。
pThis->synchronize();
}
}
void initializeGeometry()
{
m_pPoints->SetDataTypeToDouble();
m_pPoints->SetNumberOfPoints(6);
for(vtkIdType nPoint = 0; nPoint < 6; ++nPoint) {
m_pPoints->SetPoint(nPoint, 0.0, 0.0, 0.0);
}
for(vtkIdType nAxis = 0; nAxis < 3; ++nAxis) {
vtkIdType nPointIds[2] = {
nAxis * 2, nAxis * 2 + 1
};
m_pLines->InsertNextCell(2, nPointIds);
}
m_pColors->SetName("RotationCenterAxisColors");
m_pColors->SetNumberOfComponents(3);
m_pColors->InsertNextTuple3(255.0, 0.0, 0.0);
m_pColors->InsertNextTuple3(254.0, 255.0, 0.0);
m_pColors->InsertNextTuple3(0.0, 255.0, 0.0);
m_pPolyData->SetPoints(m_pPoints);
m_pPolyData->SetLines(m_pLines);
m_pPolyData->GetCellData()->SetScalars(m_pColors);
m_pMapper->SetInputData(m_pPolyData);
m_pMapper->ScalarVisibilityOn();
m_pMapper->SetScalarModeToUseCellData();
m_pMapper->SetColorModeToDirectScalars();
m_pActor->SetMapper(m_pMapper);
m_pActor->GetProperty()->SetLineWidth(2.0f);
m_pActor->GetProperty()->SetOpacity(1.0);
m_pActor->GetProperty()->LightingOff();
m_pActor->PickableOff();
m_pActor->UseBoundsOff();
}
bool ensureRendererObserver(vtkRenderer* pRenderer)
{
if(pRenderer == nullptr || m_pRendererCallback == nullptr) {
return false;
}
if(m_pRenderer.GetPointer() == pRenderer &&
m_nRendererObserverTag != 0) {
return true;
}
removeRendererObserver();
m_nRendererObserverTag = pRenderer->AddObserver(
vtkCommand::StartEvent, m_pRendererCallback);
return m_nRendererObserverTag != 0;
}
void removeRendererObserver()
{
vtkRenderer* pRenderer = m_pRenderer.GetPointer();
if(pRenderer != nullptr && m_nRendererObserverTag != 0) {
pRenderer->RemoveObserver(m_nRendererObserverTag);
}
m_nRendererObserverTag = 0;
}
bool ensureCameraObserver(vtkCamera* pCamera)
{
if(pCamera == nullptr || m_pCameraCallback == nullptr) {
return false;
}
if(m_pCamera.GetPointer() == pCamera &&
m_nCameraObserverTag != 0) {
return true;
}
removeCameraObserver();
m_pCamera = pCamera;
m_nCameraObserverTag = pCamera->AddObserver(
vtkCommand::ModifiedEvent, m_pCameraCallback);
if(m_nCameraObserverTag == 0) {
m_pCamera = nullptr;
return false;
}
return true;
}
void removeCameraObserver()
{
vtkCamera* pCamera = m_pCamera.GetPointer();
if(pCamera != nullptr && m_nCameraObserverTag != 0) {
pCamera->RemoveObserver(m_nCameraObserverTag);
}
m_nCameraObserverTag = 0;
m_pCamera = nullptr;
}
vtkSmartPointer<vtkPoints> m_pPoints;
vtkSmartPointer<vtkCellArray> m_pLines;
vtkSmartPointer<vtkUnsignedCharArray> m_pColors;
vtkSmartPointer<vtkPolyData> m_pPolyData;
vtkSmartPointer<vtkPolyDataMapper> m_pMapper;
vtkSmartPointer<vtkActor> m_pActor;
vtkSmartPointer<vtkCallbackCommand> m_pRendererCallback;
vtkSmartPointer<vtkCallbackCommand> m_pCameraCallback;
vtkWeakPointer<vtkRenderer> m_pRenderer; ///< 非所有权引用。
vtkWeakPointer<vtkCamera> m_pCamera; ///< 非所有权引用。
unsigned long m_nRendererObserverTag; ///< 为零表示尚未注册观察者。
unsigned long m_nCameraObserverTag; ///< 为零表示尚未注册观察者。
double m_dWorldCenter[3]; ///< 独立于相机焦点的逻辑中心。
bool m_bHasWorldCenter;
};
nmVTKRotationCenterMarkerLayer::nmVTKRotationCenterMarkerLayer(
const QString& sLayerId)
: nmVTKRenderLayer(sLayerId),
m_pPrivate(new nmVTKRotationCenterMarkerLayerPrivate)
{
Q_ASSERT(isRotationCenterMarkerGuiThread());
setRenderProp(m_pPrivate->getActor());
setVisible(false);
}
nmVTKRotationCenterMarkerLayer::~nmVTKRotationCenterMarkerLayer()
{
Q_ASSERT(isRotationCenterMarkerGuiThread());
if(m_pPrivate != nullptr) {
// Renderer/Camera Observer 必须在 Renderer 和回调目标释放前解除。
m_pPrivate->unbindRenderer();
detach();
delete m_pPrivate;
m_pPrivate = nullptr;
}
}
bool nmVTKRotationCenterMarkerLayer::bindRenderer(vtkRenderer* pRenderer)
{
Q_ASSERT(isRotationCenterMarkerGuiThread());
if(!isRotationCenterMarkerGuiThread() || m_pPrivate == nullptr ||
pRenderer == nullptr || getAttachedRenderer() != pRenderer) {
return false;
}
return m_pPrivate->bindRenderer(pRenderer);
}
void nmVTKRotationCenterMarkerLayer::unbindRenderer()
{
Q_ASSERT(isRotationCenterMarkerGuiThread());
if(isRotationCenterMarkerGuiThread() && m_pPrivate != nullptr) {
m_pPrivate->unbindRenderer();
}
}
bool nmVTKRotationCenterMarkerLayer::synchronize()
{
Q_ASSERT(isRotationCenterMarkerGuiThread());
return isRotationCenterMarkerGuiThread() && m_pPrivate != nullptr &&
m_pPrivate->synchronize();
}
bool nmVTKRotationCenterMarkerLayer::setWorldCenter(
double dX,
double dY,
double dZ)
{
Q_ASSERT(isRotationCenterMarkerGuiThread());
return isRotationCenterMarkerGuiThread() && m_pPrivate != nullptr &&
m_pPrivate->setWorldCenter(dX, dY, dZ);
}
bool nmVTKRotationCenterMarkerLayer::hasWorldCenter() const
{
return m_pPrivate != nullptr && m_pPrivate->hasWorldCenter();
}
void nmVTKRotationCenterMarkerLayer::setMarkerVisible(bool bVisible)
{
Q_ASSERT(isRotationCenterMarkerGuiThread());
if(!isRotationCenterMarkerGuiThread()) {
return;
}
const bool bCanShow = bVisible && m_pPrivate != nullptr &&
m_pPrivate->hasWorldCenter();
setVisible(bCanShow);
if(bCanShow) {
// 首次显示先准备几何;视口尚未建立时由绘制前同步再次补齐。
m_pPrivate->synchronize();
}
}
bool nmVTKRotationCenterMarkerLayer::isMarkerVisible() const
{
return isVisible();
}

@ -0,0 +1,316 @@
#include "nmVTKRotationCenterPicker.h"
#include <QCoreApplication>
#include <QEvent>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QThread>
#include <QVTKWidget.h>
#include <vtkCellPicker.h>
#include <vtkMath.h>
#include <vtkProp.h>
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>
#include <vtkSmartPointer.h>
namespace {
// Qt 控件状态和 VTK Picker 均只允许在 GUI 线程中修改。
bool isRotationCenterPickerGuiThread()
{
QCoreApplication* pApplication = QCoreApplication::instance();
return pApplication == nullptr ||
pApplication->thread() == QThread::currentThread();
}
int scalePixelCoordinate(int nPosition,
int nWidgetExtent,
int nRenderExtent)
{
const double dScaled =
(static_cast<double>(nPosition) + 0.5) *
static_cast<double>(nRenderExtent) /
static_cast<double>(nWidgetExtent) - 0.5;
int nResult = static_cast<int>(dScaled + 0.5);
if(nResult < 0) {
nResult = 0;
} else if(nResult >= nRenderExtent) {
nResult = nRenderExtent - 1;
}
return nResult;
}
}
nmVTKRotationCenterPicker::nmVTKRotationCenterPicker()
: m_pPickTarget(nullptr),
m_pWidget(nullptr),
m_pRenderer(nullptr),
m_oPreviousCursor(),
m_nPendingReleaseButton(static_cast<int>(Qt::NoButton)),
m_bHadExplicitCursor(false),
m_bCursorOverridden(false),
m_bActive(false),
m_bHasPickedPosition(false)
{
Q_ASSERT(isRotationCenterPickerGuiThread());
m_dPickedPosition[0] = 0.0;
m_dPickedPosition[1] = 0.0;
m_dPickedPosition[2] = 0.0;
}
nmVTKRotationCenterPicker::~nmVTKRotationCenterPicker()
{
Q_ASSERT(isRotationCenterPickerGuiThread());
// 先恢复 Qt 临时状态,再解除对拾取目标的弱引用观察关系。
reset();
m_pPickTarget = nullptr;
}
void nmVTKRotationCenterPicker::setPickTarget(vtkProp* pTarget)
{
Q_ASSERT(isRotationCenterPickerGuiThread());
if(!isRotationCenterPickerGuiThread()) {
return;
}
if(m_pPickTarget.GetPointer() == pTarget) {
// 弱目标已经失效时,显式清空仍必须结束可能遗留的交互状态。
if(pTarget == nullptr && (m_bActive || hasPendingRelease())) {
reset();
}
return;
}
// 网格更换时旧 Press/Release 序列不再有效,必须先结束当前模式。
reset();
m_pPickTarget = pTarget;
}
vtkProp* nmVTKRotationCenterPicker::getPickTarget() const
{
return m_pPickTarget.GetPointer();
}
bool nmVTKRotationCenterPicker::hasPickTarget() const
{
return m_pPickTarget.GetPointer() != nullptr;
}
bool nmVTKRotationCenterPicker::start(
QVTKWidget* pWidget,
vtkRenderer* pRenderer)
{
Q_ASSERT(isRotationCenterPickerGuiThread());
if(!isRotationCenterPickerGuiThread() || m_bActive ||
hasPendingRelease() || pWidget == nullptr || pRenderer == nullptr ||
!hasPickTarget()) {
return false;
}
vtkRenderWindow* pRenderWindow = pRenderer->GetRenderWindow();
if(pRenderWindow == nullptr ||
pWidget->GetRenderWindow() != pRenderWindow) {
return false;
}
m_pWidget = pWidget;
m_pRenderer = pRenderer;
m_bHasPickedPosition = false;
m_bHadExplicitCursor = pWidget->testAttribute(Qt::WA_SetCursor);
m_oPreviousCursor = pWidget->cursor();
pWidget->setCursor(Qt::CrossCursor);
m_bCursorOverridden = true;
m_bActive = true;
pWidget->setFocus(Qt::OtherFocusReason);
return true;
}
nmVTKRotationCenterPicker::PickEventResult
nmVTKRotationCenterPicker::cancel()
{
const bool bWasActive = m_bActive;
reset();
return PickEventResult(false,
bWasActive ? PickOutcome_Cancelled :
PickOutcome_None);
}
void nmVTKRotationCenterPicker::reset()
{
Q_ASSERT(isRotationCenterPickerGuiThread());
if(!isRotationCenterPickerGuiThread()) {
return;
}
m_bActive = false;
m_nPendingReleaseButton = static_cast<int>(Qt::NoButton);
restoreCursor();
m_pWidget = nullptr;
m_pRenderer = nullptr;
m_bHasPickedPosition = false;
}
bool nmVTKRotationCenterPicker::isActive() const
{
return m_bActive;
}
bool nmVTKRotationCenterPicker::hasPendingRelease() const
{
return m_nPendingReleaseButton != static_cast<int>(Qt::NoButton);
}
nmVTKRotationCenterPicker::PickEventResult
nmVTKRotationCenterPicker::handleEvent(QEvent* pEvent)
{
Q_ASSERT(isRotationCenterPickerGuiThread());
if(!isRotationCenterPickerGuiThread() || pEvent == nullptr) {
return PickEventResult();
}
if(pEvent->type() == QEvent::MouseButtonRelease &&
hasPendingRelease()) {
QMouseEvent* pMouseEvent = static_cast<QMouseEvent*>(pEvent);
if(static_cast<int>(pMouseEvent->button()) ==
m_nPendingReleaseButton) {
m_nPendingReleaseButton = static_cast<int>(Qt::NoButton);
return PickEventResult(true, PickOutcome_None);
}
return PickEventResult();
}
if(!m_bActive) {
return PickEventResult();
}
if(pEvent->type() == QEvent::ShortcutOverride ||
pEvent->type() == QEvent::KeyPress) {
QKeyEvent* pKeyEvent = static_cast<QKeyEvent*>(pEvent);
if(pKeyEvent->key() == Qt::Key_Escape) {
if(pEvent->type() == QEvent::ShortcutOverride) {
pEvent->accept();
return PickEventResult(true, PickOutcome_None);
}
finishActiveMode();
return PickEventResult(true, PickOutcome_Cancelled);
}
}
if(pEvent->type() != QEvent::MouseButtonPress) {
return PickEventResult();
}
QMouseEvent* pMouseEvent = static_cast<QMouseEvent*>(pEvent);
if(pMouseEvent->button() != Qt::LeftButton &&
pMouseEvent->button() != Qt::RightButton) {
return PickEventResult();
}
m_nPendingReleaseButton = static_cast<int>(pMouseEvent->button());
if(pMouseEvent->button() == Qt::RightButton) {
finishActiveMode();
return PickEventResult(true, PickOutcome_Cancelled);
}
const bool bHit = pickAt(pMouseEvent);
finishActiveMode();
return PickEventResult(true,
bHit ? PickOutcome_Hit : PickOutcome_Miss);
}
bool nmVTKRotationCenterPicker::getPickedPosition(
double dPosition[3]) const
{
if(dPosition == nullptr || !m_bHasPickedPosition) {
return false;
}
dPosition[0] = m_dPickedPosition[0];
dPosition[1] = m_dPickedPosition[1];
dPosition[2] = m_dPickedPosition[2];
return true;
}
bool nmVTKRotationCenterPicker::pickAt(
const QMouseEvent* pMouseEvent)
{
m_bHasPickedPosition = false;
if(pMouseEvent == nullptr || m_pWidget == nullptr ||
m_pRenderer == nullptr) {
return false;
}
vtkProp* pTarget = m_pPickTarget.GetPointer();
vtkRenderWindow* pRenderWindow = m_pRenderer->GetRenderWindow();
if(pTarget == nullptr || pRenderWindow == nullptr ||
m_pWidget->GetRenderWindow() != pRenderWindow) {
return false;
}
const int nWidgetWidth = m_pWidget->width();
const int nWidgetHeight = m_pWidget->height();
int* pRenderSize = pRenderWindow->GetActualSize();
if(nWidgetWidth <= 0 || nWidgetHeight <= 0 || pRenderSize == nullptr ||
pRenderSize[0] <= 0 || pRenderSize[1] <= 0 ||
pMouseEvent->x() < 0 || pMouseEvent->x() >= nWidgetWidth ||
pMouseEvent->y() < 0 || pMouseEvent->y() >= nWidgetHeight) {
return false;
}
const int nRenderX = scalePixelCoordinate(
pMouseEvent->x(), nWidgetWidth, pRenderSize[0]);
const int nRenderYFromTop = scalePixelCoordinate(
pMouseEvent->y(), nWidgetHeight, pRenderSize[1]);
const int nRenderY = pRenderSize[1] - 1 - nRenderYFromTop;
// Picker 也限定在本次调用内,避免命中结果缓存延长 Mesh Actor 生命周期。
vtkSmartPointer<vtkCellPicker> pCellPicker =
vtkSmartPointer<vtkCellPicker>::New();
if(pCellPicker == nullptr) {
return false;
}
pCellPicker->PickFromListOn();
pCellPicker->InitializePickList();
pCellPicker->AddPickList(pTarget);
const int nPicked = pCellPicker->Pick(
nRenderX, nRenderY, 0.0, m_pRenderer);
const vtkIdType nCellId = pCellPicker->GetCellId();
double* pPosition = pCellPicker->GetPickPosition();
const bool bValid = nPicked != 0 && nCellId >= 0 &&
pPosition != nullptr &&
vtkMath::IsFinite(pPosition[0]) &&
vtkMath::IsFinite(pPosition[1]) &&
vtkMath::IsFinite(pPosition[2]);
if(bValid) {
m_dPickedPosition[0] = pPosition[0];
m_dPickedPosition[1] = pPosition[1];
m_dPickedPosition[2] = pPosition[2];
m_bHasPickedPosition = true;
}
pCellPicker->InitializePickList();
return bValid;
}
void nmVTKRotationCenterPicker::finishActiveMode()
{
m_bActive = false;
restoreCursor();
m_pWidget = nullptr;
m_pRenderer = nullptr;
}
void nmVTKRotationCenterPicker::restoreCursor()
{
if(!m_bCursorOverridden || m_pWidget == nullptr) {
m_bCursorOverridden = false;
return;
}
if(m_bHadExplicitCursor) {
m_pWidget->setCursor(m_oPreviousCursor);
} else {
m_pWidget->unsetCursor();
}
m_bCursorOverridden = false;
}

@ -27,7 +27,8 @@ nmVTKViewCommandController::nmVTKViewCommandController(
: QObject(pParent),
m_pContainer(pContainer),
m_eCapabilities(0),
m_bViewAvailable(false)
m_bViewAvailable(false),
m_bRotationCenterPickingAvailable(false)
{
for(int nIndex = 0; nIndex < ViewCommandCount; ++nIndex) {
m_pActions[nIndex] = nullptr;
@ -51,6 +52,17 @@ nmVTKViewCommandController::nmVTKViewCommandController(
createAction(ViewCommand_ResetRotationCenter,
tr("Reset Rotation Center"), QString(),
SLOT(resetRotationCenter()));
QAction* pSetRotationCenterAction = createAction(
ViewCommand_SetRotationCenter,
tr("Set Rotation Center"), "NmSetCenter.png", nullptr);
if(pSetRotationCenterAction != nullptr) {
pSetRotationCenterAction->setCheckable(true);
if(m_pContainer != nullptr) {
connect(pSetRotationCenterAction, SIGNAL(toggled(bool)),
m_pContainer,
SLOT(setRotationCenterPickingEnabled(bool)));
}
}
createAction(ViewCommand_SaveImage, tr("Save as Image"), "SaveImg.png",
SLOT(saveImage()));
createAction(ViewCommand_CopyImage, tr("Copy Image"),
@ -101,10 +113,13 @@ void nmVTKViewCommandController::populateToolBar(QToolBar* pToolBar) const
ViewCommand_Front,
ViewCommand_Right,
ViewCommand_Isometric,
ViewCommand_ResetRotationCenter
ViewCommand_ResetRotationCenter,
ViewCommand_SetRotationCenter
};
bool bHasNavigation = false;
for(int nIndex = 0; nIndex < 8; ++nIndex) {
const int nNavigationCommandCount =
sizeof(eNavigationCommands) / sizeof(eNavigationCommands[0]);
for(int nIndex = 0; nIndex < nNavigationCommandCount; ++nIndex) {
if(supports(eNavigationCommands[nIndex])) {
pToolBar->addAction(getAction(eNavigationCommands[nIndex]));
bHasNavigation = true;
@ -144,10 +159,13 @@ void nmVTKViewCommandController::populateContextMenu(QMenu* pMenu) const
ViewCommand_Front,
ViewCommand_Right,
ViewCommand_Isometric,
ViewCommand_ResetRotationCenter
ViewCommand_ResetRotationCenter,
ViewCommand_SetRotationCenter
};
bool bHasViewCommands = false;
for(int nIndex = 0; nIndex < 8; ++nIndex) {
const int nViewCommandCount =
sizeof(eViewCommands) / sizeof(eViewCommands[0]);
for(int nIndex = 0; nIndex < nViewCommandCount; ++nIndex) {
bHasViewCommands = bHasViewCommands || supports(eViewCommands[nIndex]);
}
if(bHasViewCommands) {
@ -155,7 +173,7 @@ void nmVTKViewCommandController::populateContextMenu(QMenu* pMenu) const
pMenu->addSeparator();
}
QMenu* pViewMenu = pMenu->addMenu(tr("View"));
for(int nIndex = 0; nIndex < 8; ++nIndex) {
for(int nIndex = 0; nIndex < nViewCommandCount; ++nIndex) {
if(supports(eViewCommands[nIndex])) {
pViewMenu->addAction(getAction(eViewCommands[nIndex]));
}
@ -196,6 +214,25 @@ bool nmVTKViewCommandController::isViewAvailable() const
return m_bViewAvailable;
}
void nmVTKViewCommandController::setRotationCenterPickingAvailable(
bool bAvailable)
{
m_bRotationCenterPickingAvailable = bAvailable;
updateActionStates();
}
void nmVTKViewCommandController::setRotationCenterPicking(bool bPicking)
{
QAction* pAction = getAction(ViewCommand_SetRotationCenter);
if(pAction == nullptr || !pAction->isCheckable()) {
return;
}
const bool bOldBlockState = pAction->blockSignals(true);
pAction->setChecked(bPicking);
pAction->blockSignals(bOldBlockState);
}
QAction* nmVTKViewCommandController::createAction(
ViewCommand eCommand,
const QString& sText,
@ -234,6 +271,7 @@ int nmVTKViewCommandController::actionIndex(ViewCommand eCommand) const
case ViewCommand_CopyImage: return 9;
case ViewCommand_Print: return 10;
case ViewCommand_PrintPreview: return 11;
case ViewCommand_SetRotationCenter: return 12;
default: return -1;
}
}
@ -257,14 +295,23 @@ void nmVTKViewCommandController::updateActionStates()
ViewCommand_SaveImage,
ViewCommand_CopyImage,
ViewCommand_Print,
ViewCommand_PrintPreview
ViewCommand_PrintPreview,
ViewCommand_SetRotationCenter
};
for(int nIndex = 0; nIndex < ViewCommandCount; ++nIndex) {
QAction* pAction = m_pActions[nIndex];
if(pAction != nullptr) {
const bool bSupported = supports(eCommands[nIndex]);
bool bEnabled = bSupported && m_bViewAvailable;
if(eCommands[nIndex] == ViewCommand_SetRotationCenter) {
bEnabled = bEnabled &&
m_bRotationCenterPickingAvailable;
if(!bEnabled) {
setRotationCenterPicking(false);
}
}
pAction->setVisible(bSupported);
pAction->setEnabled(bSupported && m_bViewAvailable);
pAction->setEnabled(bEnabled);
}
}
}

@ -4,6 +4,8 @@
#include "nmVTKImageExporter.h"
#include "nmVTKInteractionManager.h"
#include "nmVTKOrientationMarkerController.h"
#include "nmVTKRotationCenterMarkerLayer.h"
#include "nmVTKRotationCenterPicker.h"
#include "nmVTKScene.h"
#include "nmVTKViewCommandController.h"
@ -16,6 +18,7 @@
#include <QMouseEvent>
#include <QStackedWidget>
#include <QThread>
#include <QTimer>
#include <QVBoxLayout>
#include <QVTKWidget.h>
@ -28,6 +31,13 @@ VTK_MODULE_INIT(vtkInteractionStyle)
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
namespace {
const char* const ROTATION_CENTER_MARKER_LAYER_ID =
"nm.render.rotation_center_marker";
}
nmWxVTKRenderContainerWidget::nmWxVTKRenderContainerWidget(QWidget* pParent)
: QWidget(pParent),
m_pMainLayout(nullptr),
@ -37,11 +47,14 @@ nmWxVTKRenderContainerWidget::nmWxVTKRenderContainerWidget(QWidget* pParent)
m_pVtkWidget(nullptr),
m_pRenderer(nullptr),
m_pScene(nullptr),
m_pSystemScene(nullptr),
m_pCameraController(nullptr),
m_pInteractionManager(nullptr),
m_pImageExporter(nullptr),
m_pViewCommandController(nullptr),
m_pOrientationMarkerController(nullptr)
m_pOrientationMarkerController(nullptr),
m_pRotationCenterPicker(nullptr),
m_pRotationCenterMarkerLayer(nullptr)
{
Q_ASSERT(isGuiThread());
@ -79,9 +92,26 @@ nmWxVTKRenderContainerWidget::nmWxVTKRenderContainerWidget(QWidget* pParent)
}
// 5. 管理对象、图片服务和命令对象均只服务当前容器实例。
// 业务图层和容器内部装饰分别管理;公开业务清场不会删除系统 Marker。
m_pScene = new nmVTKScene(m_pRenderer);
m_pSystemScene = new nmVTKScene(m_pRenderer);
m_pCameraController = new nmVTKCameraController(m_pRenderer);
m_pInteractionManager = new nmVTKInteractionManager();
m_pInteractionManager = new nmVTKInteractionManager(
m_pCameraController);
nmVTKRotationCenterMarkerLayer* pRotationCenterMarkerLayer =
new nmVTKRotationCenterMarkerLayer(
ROTATION_CENTER_MARKER_LAYER_ID);
if(m_pSystemScene != nullptr &&
m_pSystemScene->addLayer(pRotationCenterMarkerLayer)) {
if(pRotationCenterMarkerLayer->bindRenderer(m_pRenderer)) {
m_pRotationCenterMarkerLayer = pRotationCenterMarkerLayer;
} else {
m_pSystemScene->removeLayer(ROTATION_CENTER_MARKER_LAYER_ID);
}
} else {
delete pRotationCenterMarkerLayer;
}
m_pRotationCenterPicker = new nmVTKRotationCenterPicker();
m_pImageExporter = new nmVTKImageExporter();
m_pImageExporter->setRenderWindow(pRenderWindow);
m_pViewCommandController =
@ -103,8 +133,13 @@ nmWxVTKRenderContainerWidget::nmWxVTKRenderContainerWidget(QWidget* pParent)
connect(m_pViewCommandController,
SIGNAL(contextMenuAboutToShow(QMenu*)),
this, SIGNAL(contextMenuAboutToShow(QMenu*)));
connect(this, SIGNAL(rotationCenterPickingChanged(bool)),
m_pViewCommandController,
SLOT(setRotationCenterPicking(bool)));
const bool bInteractionReady =
m_pInteractionManager->bindInteractor(pInteractor);
m_pInteractionManager->bindInteractor(
pInteractor, m_pRenderer);
updateRotationCenterCommandAvailability();
// 方向轴是独立辅助 Renderer绑定失败只隐藏方向轴不影响主视图初始化。
m_pOrientationMarkerController =
@ -138,42 +173,60 @@ nmWxVTKRenderContainerWidget::~nmWxVTKRenderContainerWidget()
m_pViewCommandController = nullptr;
}
// 3. MarkerWidget 持有 Interactor 和辅助 Renderer 引用,必须优先解绑。
// 3. 拾取器先恢复光标;场景标记必须在 Camera 和 Renderer 前解除观察关系。
if(m_pRotationCenterPicker != nullptr) {
m_pRotationCenterPicker->reset();
delete m_pRotationCenterPicker;
m_pRotationCenterPicker = nullptr;
}
nmVTKRotationCenterMarkerLayer* pRotationCenterMarkerLayer =
getRotationCenterMarkerLayer();
if(pRotationCenterMarkerLayer != nullptr) {
pRotationCenterMarkerLayer->unbindRenderer();
}
// 4. MarkerWidget 持有 Interactor 和辅助 Renderer 引用,必须优先解绑。
if(m_pOrientationMarkerController != nullptr) {
m_pOrientationMarkerController->unbindInteractor();
delete m_pOrientationMarkerController;
m_pOrientationMarkerController = nullptr;
}
// 4. 图片服务不拥有 RenderWindow先解除引用再销毁服务。
// 5. 图片服务不拥有 RenderWindow先解除引用再销毁服务。
if(m_pImageExporter != nullptr) {
m_pImageExporter->setRenderWindow(nullptr);
delete m_pImageExporter;
m_pImageExporter = nullptr;
}
// 5. Interactor 必须先释放对默认 Style 的 VTK 引用。
// 6. Interactor 必须先释放对默认 Style 的 VTK 引用。
if(m_pInteractionManager != nullptr) {
m_pInteractionManager->unbindInteractor();
delete m_pInteractionManager;
m_pInteractionManager = nullptr;
}
// 6. Scene 在 Renderer 移除前清空所有 Layer 和 Prop。
// 7. 两个 Scene 均在 Camera 和 Renderer 前清空;系统装饰先解除所有权。
if(m_pSystemScene != nullptr) {
m_pRotationCenterMarkerLayer = nullptr;
m_pSystemScene->clear();
delete m_pSystemScene;
m_pSystemScene = nullptr;
}
if(m_pScene != nullptr) {
m_pScene->clear();
delete m_pScene;
m_pScene = nullptr;
}
// 7. 相机控制器仅解除 Renderer 非所有权引用。
// 8. 相机控制器仅解除 Renderer 非所有权引用。
if(m_pCameraController != nullptr) {
m_pCameraController->setRenderer(nullptr);
delete m_pCameraController;
m_pCameraController = nullptr;
}
// 8. 最后从 RenderWindow 移除并释放当前实例 Renderer。
// 9. 最后从 RenderWindow 移除并释放当前实例 Renderer。
vtkRenderWindow* pRenderWindow = getRenderWindow();
if(pRenderWindow != nullptr && m_pRenderer != nullptr) {
pRenderWindow->RemoveRenderer(m_pRenderer);
@ -224,32 +277,84 @@ nmWxVTKRenderContainerWidget::getOrientationMarkerController() const
void nmWxVTKRenderContainerWidget::setViewCommandCapabilities(
nmVTKViewCommandController::ViewCommands eCommands)
{
if(!eCommands.testFlag(
nmVTKViewCommandController::ViewCommand_SetRotationCenter)) {
// 能力移除前先结束模式QAction 仅在随后同步隐藏和禁用。
cancelRotationCenterPicking();
}
if(m_pViewCommandController != nullptr) {
m_pViewCommandController->setCapabilities(eCommands);
}
updateRotationCenterMarkerVisibility();
}
void nmWxVTKRenderContainerWidget::setRotationCenterPickTarget(
vtkProp* pTarget)
{
Q_ASSERT(isGuiThread());
if(!isGuiThread() || m_pRotationCenterPicker == nullptr) {
return;
}
if(pTarget == nullptr ||
m_pRotationCenterPicker->getPickTarget() != pTarget) {
cancelRotationCenterPicking();
m_pRotationCenterPicker->setPickTarget(pTarget);
}
if(pTarget == nullptr) {
hideRotationCenterMarker();
}
updateRotationCenterCommandAvailability();
updateRotationCenterMarkerVisibility();
}
bool nmWxVTKRenderContainerWidget::isRotationCenterPicking() const
{
return m_pRotationCenterPicker != nullptr &&
m_pRotationCenterPicker->isActive();
}
void nmWxVTKRenderContainerWidget::hideRotationCenterMarker()
{
Q_ASSERT(isGuiThread());
nmVTKRotationCenterMarkerLayer* pMarkerLayer =
getRotationCenterMarkerLayer();
if(isGuiThread() && pMarkerLayer != nullptr) {
pMarkerLayer->setMarkerVisible(false);
}
}
bool nmWxVTKRenderContainerWidget::installInteractionStyle(
vtkInteractorStyle* pStyle)
{
Q_ASSERT(isGuiThread());
return isGuiThread() && m_pInteractionManager != nullptr &&
cancelRotationCenterPicking();
const bool bSucceeded = isGuiThread() &&
m_pInteractionManager != nullptr &&
m_pInteractionManager->installInteractionStyle(pStyle);
updateRotationCenterCommandAvailability();
return bSucceeded;
}
bool nmWxVTKRenderContainerWidget::restoreDefaultInteraction()
{
Q_ASSERT(isGuiThread());
return isGuiThread() && m_pInteractionManager != nullptr &&
cancelRotationCenterPicking();
const bool bSucceeded = isGuiThread() &&
m_pInteractionManager != nullptr &&
m_pInteractionManager->restoreDefaultInteraction();
updateRotationCenterCommandAvailability();
return bSucceeded;
}
void nmWxVTKRenderContainerWidget::clearInteractionStyle()
{
Q_ASSERT(isGuiThread());
cancelRotationCenterPicking();
if(isGuiThread() && m_pInteractionManager != nullptr) {
m_pInteractionManager->clearInteractionStyle();
}
updateRotationCenterCommandAvailability();
}
void nmWxVTKRenderContainerWidget::renderView()
@ -258,6 +363,7 @@ void nmWxVTKRenderContainerWidget::renderView()
vtkRenderWindow* pRenderWindow = getRenderWindow();
// Scene、Layer 和相机均不隐式渲染,批量更新最终汇聚到此入口。
if(isGuiThread() && pRenderWindow != nullptr) {
synchronizeRotationCenterMarker();
pRenderWindow->Render();
}
}
@ -265,11 +371,29 @@ void nmWxVTKRenderContainerWidget::renderView()
bool nmWxVTKRenderContainerWidget::fitTopViewWithoutRender()
{
Q_ASSERT(isGuiThread());
cancelRotationCenterPicking();
// 相机控制器本身不触发 Render便于业务图层完成整批更新后只渲染一次。
return isGuiThread() && m_pCameraController != nullptr &&
m_pCameraController->setTopView();
}
bool nmWxVTKRenderContainerWidget::resetRotationCenterWithoutRender()
{
Q_ASSERT(isGuiThread());
cancelRotationCenterPicking();
if(!isGuiThread() || m_pCameraController == nullptr ||
!m_pCameraController->resetRotationCenter()) {
return false;
}
double dCenter[3];
if(!m_pCameraController->getRotationCenter(dCenter)) {
return false;
}
return applyRotationCenter(
dCenter[0], dCenter[1], dCenter[2], true);
}
void nmWxVTKRenderContainerWidget::showReadyState()
{
Q_ASSERT(isGuiThread());
@ -281,6 +405,7 @@ void nmWxVTKRenderContainerWidget::showReadyState()
if(m_pViewCommandController != nullptr) {
m_pViewCommandController->setViewAvailable(true);
}
updateRotationCenterMarkerVisibility();
if(m_pOrientationMarkerController != nullptr) {
m_pOrientationMarkerController->setViewAvailable(true);
}
@ -305,6 +430,7 @@ void nmWxVTKRenderContainerWidget::showRenderingState(
void nmWxVTKRenderContainerWidget::zoomIn()
{
cancelRotationCenterPicking();
if(m_pCameraController != nullptr && m_pCameraController->zoomIn()) {
renderView();
}
@ -312,6 +438,7 @@ void nmWxVTKRenderContainerWidget::zoomIn()
void nmWxVTKRenderContainerWidget::zoomOut()
{
cancelRotationCenterPicking();
if(m_pCameraController != nullptr && m_pCameraController->zoomOut()) {
renderView();
}
@ -319,6 +446,7 @@ void nmWxVTKRenderContainerWidget::zoomOut()
void nmWxVTKRenderContainerWidget::fitView()
{
cancelRotationCenterPicking();
if(m_pCameraController != nullptr && m_pCameraController->fitView()) {
renderView();
}
@ -326,6 +454,7 @@ void nmWxVTKRenderContainerWidget::fitView()
void nmWxVTKRenderContainerWidget::resetCamera()
{
cancelRotationCenterPicking();
// 控制器用返回值表示相机是否确实改变,成功时只提交一次渲染。
if(m_pCameraController != nullptr && m_pCameraController->resetCamera()) {
renderView();
@ -334,6 +463,7 @@ void nmWxVTKRenderContainerWidget::resetCamera()
void nmWxVTKRenderContainerWidget::setTopView()
{
cancelRotationCenterPicking();
if(m_pCameraController != nullptr && m_pCameraController->setTopView()) {
renderView();
}
@ -341,6 +471,7 @@ void nmWxVTKRenderContainerWidget::setTopView()
void nmWxVTKRenderContainerWidget::setFrontView()
{
cancelRotationCenterPicking();
if(m_pCameraController != nullptr && m_pCameraController->setFrontView()) {
renderView();
}
@ -348,6 +479,7 @@ void nmWxVTKRenderContainerWidget::setFrontView()
void nmWxVTKRenderContainerWidget::setRightView()
{
cancelRotationCenterPicking();
if(m_pCameraController != nullptr && m_pCameraController->setRightView()) {
renderView();
}
@ -355,6 +487,7 @@ void nmWxVTKRenderContainerWidget::setRightView()
void nmWxVTKRenderContainerWidget::setIsometricView()
{
cancelRotationCenterPicking();
if(m_pCameraController != nullptr &&
m_pCameraController->setIsometricView()) {
renderView();
@ -366,26 +499,201 @@ void nmWxVTKRenderContainerWidget::setRotationCenter(
double dY,
double dZ)
{
if(m_pCameraController != nullptr &&
m_pCameraController->setRotationCenter(dX, dY, dZ)) {
cancelRotationCenterPicking();
if(applyRotationCenter(dX, dY, dZ, true)) {
renderView();
}
}
void nmWxVTKRenderContainerWidget::resetRotationCenter()
{
if(m_pCameraController != nullptr &&
m_pCameraController->resetRotationCenter()) {
if(resetRotationCenterWithoutRender()) {
renderView();
}
}
bool nmWxVTKRenderContainerWidget::applyRotationCenter(
double dX,
double dY,
double dZ,
bool bShowMarker)
{
Q_ASSERT(isGuiThread());
if(!isGuiThread() || m_pCameraController == nullptr ||
!m_pCameraController->setRotationCenter(dX, dY, dZ)) {
return false;
}
// CameraController 是独立枢轴的唯一逻辑状态源Marker 只保存显示副本。
nmVTKRotationCenterMarkerLayer* pMarkerLayer =
getRotationCenterMarkerLayer();
if(pMarkerLayer != nullptr) {
if(!pMarkerLayer->setWorldCenter(dX, dY, dZ)) {
return false;
}
if(bShowMarker) {
pMarkerLayer->setMarkerVisible(true);
}
}
synchronizeRotationCenterMarker();
return true;
}
void nmWxVTKRenderContainerWidget::cancelRotationCenterPicking()
{
Q_ASSERT(isGuiThread());
if(!isGuiThread()) {
return;
}
if(m_pRotationCenterPicker != nullptr) {
// reset 同时清除待消费 Release并按进入模式前状态恢复光标。
m_pRotationCenterPicker->reset();
}
emit rotationCenterPickingChanged(false);
}
bool nmWxVTKRenderContainerWidget::handleRotationCenterPickEvent(
QEvent* pEvent)
{
if(m_pRotationCenterPicker == nullptr || pEvent == nullptr) {
return false;
}
const nmVTKRotationCenterPicker::PickEventResult oResult =
m_pRotationCenterPicker->handleEvent(pEvent);
if(oResult.eOutcome ==
nmVTKRotationCenterPicker::PickOutcome_Hit) {
double dPosition[3];
if(m_pRotationCenterPicker->getPickedPosition(dPosition) &&
applyRotationCenter(dPosition[0], dPosition[1],
dPosition[2], true)) {
// 独立旋转中心和 Marker 已作为一批提交,只渲染一次。
renderView();
}
}
if(oResult.eOutcome !=
nmVTKRotationCenterPicker::PickOutcome_None) {
emit rotationCenterPickingChanged(false);
}
return oResult.bConsumed;
}
void nmWxVTKRenderContainerWidget::synchronizeRotationCenterMarker()
{
nmVTKRotationCenterMarkerLayer* pMarkerLayer =
getRotationCenterMarkerLayer();
if(pMarkerLayer != nullptr && pMarkerLayer->isMarkerVisible()) {
// synchronize 只更新世界几何,绝不从图层内部触发 Render。
pMarkerLayer->synchronize();
}
}
void nmWxVTKRenderContainerWidget::updateRotationCenterMarkerVisibility()
{
nmVTKRotationCenterMarkerLayer* pMarkerLayer =
getRotationCenterMarkerLayer();
if(pMarkerLayer == nullptr) {
return;
}
const bool bVisible = m_pViewCommandController != nullptr &&
m_pViewCommandController->isViewAvailable() &&
m_pViewCommandController->getCapabilities().testFlag(
nmVTKViewCommandController::ViewCommand_SetRotationCenter) &&
m_pRotationCenterPicker != nullptr &&
m_pRotationCenterPicker->hasPickTarget() &&
pMarkerLayer->hasWorldCenter();
pMarkerLayer->setMarkerVisible(bVisible);
}
void nmWxVTKRenderContainerWidget::updateRotationCenterCommandAvailability()
{
if(m_pViewCommandController == nullptr) {
return;
}
const bool bAvailable = m_pRotationCenterPicker != nullptr &&
m_pRotationCenterPicker->hasPickTarget() &&
getRotationCenterMarkerLayer() != nullptr &&
m_pInteractionManager != nullptr &&
m_pInteractionManager->isDefaultInteractionActive();
m_pViewCommandController->setRotationCenterPickingAvailable(
bAvailable);
}
nmVTKRotationCenterMarkerLayer*
nmWxVTKRenderContainerWidget::getRotationCenterMarkerLayer() const
{
if(m_pSystemScene == nullptr || m_pRotationCenterMarkerLayer == nullptr ||
m_pSystemScene->getLayer(ROTATION_CENTER_MARKER_LAYER_ID) !=
m_pRotationCenterMarkerLayer) {
return nullptr;
}
return m_pRotationCenterMarkerLayer;
}
void nmWxVTKRenderContainerWidget::setRotationCenterPickingEnabled(
bool bEnabled)
{
Q_ASSERT(isGuiThread());
if(!isGuiThread()) {
return;
}
if(!bEnabled) {
cancelRotationCenterPicking();
return;
}
const bool bCommandAvailable = m_pViewCommandController != nullptr &&
m_pViewCommandController->isViewAvailable() &&
m_pViewCommandController->getCapabilities().testFlag(
nmVTKViewCommandController::ViewCommand_SetRotationCenter);
const bool bCanStart = bCommandAvailable &&
m_pRotationCenterPicker != nullptr &&
m_pRotationCenterPicker->getPickTarget() != nullptr &&
getRotationCenterMarkerLayer() != nullptr &&
m_pInteractionManager != nullptr &&
m_pInteractionManager->isDefaultInteractionActive();
if(!bCanStart ||
!m_pRotationCenterPicker->start(m_pVtkWidget, m_pRenderer)) {
if(m_pRotationCenterPicker != nullptr) {
m_pRotationCenterPicker->reset();
}
updateRotationCenterCommandAvailability();
emit rotationCenterPickingChanged(false);
return;
}
emit rotationCenterPickingChanged(true);
}
bool nmWxVTKRenderContainerWidget::eventFilter(
QObject* pObject,
QEvent* pEvent)
{
if(pObject == m_pVtkWidget && pEvent != nullptr &&
pEvent->type() == QEvent::MouseButtonPress) {
if(pObject != m_pVtkWidget || pEvent == nullptr) {
return QWidget::eventFilter(pObject, pEvent);
}
// 一次性拾取优先于 VTK 相机交互和右键菜单,并继续吞掉对应 Release。
if(m_pRotationCenterPicker != nullptr &&
(m_pRotationCenterPicker->isActive() ||
m_pRotationCenterPicker->hasPendingRelease()) &&
handleRotationCenterPickEvent(pEvent)) {
return true;
}
if(pEvent->type() == QEvent::FocusOut) {
// 延后到本轮焦点切换结束,避免点击工具按钮启动模式时被旧 FocusOut 取消。
QTimer::singleShot(
0, this,
SLOT(cancelRotationCenterPickingAfterFocusOut()));
}
if(pEvent->type() == QEvent::MouseButtonPress) {
QMouseEvent* pMouseEvent = static_cast<QMouseEvent*>(pEvent);
if(pMouseEvent->button() == Qt::RightButton) {
// 菜单引用命令控制器长期持有的 QAction不在每次右键时重复创建。
@ -404,11 +712,22 @@ bool nmWxVTKRenderContainerWidget::eventFilter(
return QWidget::eventFilter(pObject, pEvent);
}
void nmWxVTKRenderContainerWidget::cancelRotationCenterPickingAfterFocusOut()
{
if(m_pVtkWidget != nullptr && !m_pVtkWidget->hasFocus() &&
m_pRotationCenterPicker != nullptr &&
(m_pRotationCenterPicker->isActive() ||
m_pRotationCenterPicker->hasPendingRelease())) {
cancelRotationCenterPicking();
}
}
void nmWxVTKRenderContainerWidget::saveImage()
{
if(m_pImageExporter == nullptr) {
return;
}
synchronizeRotationCenterMarker();
// 实际编码格式由 QImage 按调用方选择的扩展名决定。
const QString sFileName = QFileDialog::getSaveFileName(
this,
@ -424,6 +743,7 @@ void nmWxVTKRenderContainerWidget::saveImage()
void nmWxVTKRenderContainerWidget::copyImage()
{
synchronizeRotationCenterMarker();
if(m_pImageExporter != nullptr &&
!m_pImageExporter->copyImageToClipboard()) {
QMessageBox::warning(this,
@ -435,6 +755,7 @@ void nmWxVTKRenderContainerWidget::copyImage()
void nmWxVTKRenderContainerWidget::printImage()
{
if(m_pImageExporter != nullptr) {
synchronizeRotationCenterMarker();
m_pImageExporter->printImage(this);
}
}
@ -442,6 +763,7 @@ void nmWxVTKRenderContainerWidget::printImage()
void nmWxVTKRenderContainerWidget::printPreview()
{
if(m_pImageExporter != nullptr) {
synchronizeRotationCenterMarker();
m_pImageExporter->printPreview(this);
}
}
@ -464,6 +786,9 @@ void nmWxVTKRenderContainerWidget::showMessageState(
}
m_pStateLabel->setText(sMessage);
m_pStateStack->setCurrentWidget(m_pStatePage);
// 非就绪状态先结束临时工具并清除手工标记,再禁用和取消 QAction。
cancelRotationCenterPicking();
hideRotationCenterMarker();
if(m_pViewCommandController != nullptr) {
m_pViewCommandController->setViewAvailable(false);
}

@ -202,11 +202,20 @@ public:
// 属性不可用只回退固定面色,不影响已经验证成功的网格几何提交。
applyProperty(sRequestedProperty);
// 只有整份网格绑定成功后才允许公共拾取器命中主 Mesh Actor。
if(m_pContainer != nullptr) {
m_pContainer->setRotationCenterPickTarget(
m_pMeshLayer->getActor());
}
return true;
}
void releaseGridData()
{
// 先取消临时拾取并清除弱目标,再解除 Mapper 对网格的共享引用。
if(m_pContainer != nullptr) {
m_pContainer->setRotationCenterPickTarget(nullptr);
}
// 先断开 Mapper 对旧网格的引用,再释放 ViewData 的共享引用。
if(m_pMeshLayer != nullptr) {
m_pMeshLayer->setScalarVisibility(false);
@ -321,8 +330,10 @@ bool nmPebiGridSceneController::setViewData(
return false;
}
// 几何变化始终恢复正交顶视图并适配;整批更新最终只在就绪入口渲染一次。
// 几何变化恢复正交顶视图,并将独立枢轴初始化为新网格可见边界中心。
// 两步都不立即渲染,整批更新最终只在就绪入口提交一次。
m_pPrivate->m_pContainer->fitTopViewWithoutRender();
m_pPrivate->m_pContainer->resetRotationCenterWithoutRender();
m_pPrivate->m_pContainer->showReadyState();
return true;
}

@ -78,6 +78,7 @@ void nmWxPebiGridViewWidget::createUi()
nmVTKViewCommandController::ViewCommand_ZoomOut |
nmVTKViewCommandController::ViewCommand_Fit |
nmVTKViewCommandController::ViewCommand_Top |
nmVTKViewCommandController::ViewCommand_SetRotationCenter |
nmVTKViewCommandController::ViewCommand_SaveImage |
nmVTKViewCommandController::ViewCommand_CopyImage |
nmVTKViewCommandController::ViewCommand_Print |
@ -87,7 +88,7 @@ void nmWxPebiGridViewWidget::createUi()
m_pToolBar->setOrientation(Qt::Vertical);
m_pToolBar->setMovable(false);
m_pToolBar->setFloatable(false);
m_pToolBar->setIconSize(QSize(24, 24));
m_pToolBar->setIconSize(QSize(32, 32));
m_pToolBar->setToolButtonStyle(Qt::ToolButtonIconOnly);
m_pToolBar->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);
@ -121,6 +122,9 @@ void nmWxPebiGridViewWidget::createUi()
nmVTKViewCommandController::ViewCommand_ZoomIn);
QAction* pTopAction = pCommandController->getAction(
nmVTKViewCommandController::ViewCommand_Top);
QAction* pSetRotationCenterAction = pCommandController->getAction(
nmVTKViewCommandController::
ViewCommand_SetRotationCenter);
QAction* pCopyAction = pCommandController->getAction(
nmVTKViewCommandController::ViewCommand_CopyImage);
bool bHasPreviousGroup = false;
@ -136,13 +140,19 @@ void nmWxPebiGridViewWidget::createUi()
m_pToolBar->addAction(pZoomInAction);
bHasPreviousGroup = true;
}
const bool bHasViewGroup = pTopAction != nullptr ||
pSetRotationCenterAction != nullptr;
if(bHasViewGroup && bHasPreviousGroup) {
m_pToolBar->addSeparator();
}
if(pTopAction != nullptr) {
if(bHasPreviousGroup) {
m_pToolBar->addSeparator();
}
m_pToolBar->addAction(pTopAction);
bHasPreviousGroup = true;
}
if(pSetRotationCenterAction != nullptr) {
m_pToolBar->addAction(pSetRotationCenterAction);
bHasPreviousGroup = true;
}
if(bHasPreviousGroup) {
m_pToolBar->addSeparator();
}
@ -260,6 +270,7 @@ void nmWxPebiGridViewWidget::showFailure(const QString& sMessage)
{
if(m_bHasViewData) {
// 失败时旧网格引用保持不变,状态条明确说明当前画面并非本次成果。
m_pRenderContainer->setRotationCenterPickingEnabled(false);
m_pStatusLabel->setText(sMessage);
m_pStatusLabel->show();
setControlsEnabled(true);

@ -20,9 +20,12 @@ SOURCES += $${wtSrc}/nmNum/nmRender/nmVTKCameraController.cpp \
$${wtSrc}/nmNum/nmRender/nmVTKImageExporter.cpp \
$${wtSrc}/nmNum/nmRender/nmVTKInteractionManager.cpp \
$${wtSrc}/nmNum/nmRender/nmVTKOrientationMarkerController.cpp \
$${wtSrc}/nmNum/nmRender/nmVTKPivotCameraInteractorStyle.cpp \
$${wtSrc}/nmNum/nmRender/nmVTKMeshLayer.cpp \
$${wtSrc}/nmNum/nmRender/nmVTKPlanarScaleGridLayer.cpp \
$${wtSrc}/nmNum/nmRender/nmVTKRenderLayer.cpp \
$${wtSrc}/nmNum/nmRender/nmVTKRotationCenterMarkerLayer.cpp \
$${wtSrc}/nmNum/nmRender/nmVTKRotationCenterPicker.cpp \
$${wtSrc}/nmNum/nmRender/nmVTKScalarBarLayer.cpp \
$${wtSrc}/nmNum/nmRender/nmVTKScene.cpp \
$${wtSrc}/nmNum/nmRender/nmVTKViewCommandController.cpp \
@ -34,9 +37,12 @@ HEADERS += $${wtInclude}/nmNum/nmRender/nmRender_global.h \
$${wtInclude}/nmNum/nmRender/nmVTKImageExporter.h \
$${wtInclude}/nmNum/nmRender/nmVTKInteractionManager.h \
$${wtInclude}/nmNum/nmRender/nmVTKOrientationMarkerController.h \
$${wtInclude}/nmNum/nmRender/nmVTKPivotCameraInteractorStyle.h \
$${wtInclude}/nmNum/nmRender/nmVTKMeshLayer.h \
$${wtInclude}/nmNum/nmRender/nmVTKPlanarScaleGridLayer.h \
$${wtInclude}/nmNum/nmRender/nmVTKRenderLayer.h \
$${wtInclude}/nmNum/nmRender/nmVTKRotationCenterMarkerLayer.h \
$${wtInclude}/nmNum/nmRender/nmVTKRotationCenterPicker.h \
$${wtInclude}/nmNum/nmRender/nmVTKScalarBarLayer.h \
$${wtInclude}/nmNum/nmRender/nmVTKScene.h \
$${wtInclude}/nmNum/nmRender/nmVTKViewCommandController.h \

Loading…
Cancel
Save