|
|
|
|
|
#include "nmVolumeSliceInteraction.h"
|
|
|
|
|
|
#include "nmWxVTKRenderContainerWidget.h"
|
|
|
|
|
|
#include <QMetaObject>
|
|
|
|
|
|
#include <vtkActor.h>
|
|
|
|
|
|
#include <vtkCallbackCommand.h>
|
|
|
|
|
|
#include <vtkCellPicker.h>
|
|
|
|
|
|
#include <vtkCommand.h>
|
|
|
|
|
|
#include <vtkImplicitPlaneRepresentation.h>
|
|
|
|
|
|
#include <vtkImplicitPlaneWidget2.h>
|
|
|
|
|
|
#include <vtkProperty.h>
|
|
|
|
|
|
#include <vtkObjectFactory.h>
|
|
|
|
|
|
#include <vtkWidgetEventTranslator.h>
|
|
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
/** @brief VTK 7.1 的 DrawPlaneOff 仍拾取不可见填充面,需将拾取限定到可见轮廓和手柄。 */
|
|
|
|
|
|
class nmVolumePlaneRepresentation : public vtkImplicitPlaneRepresentation
|
|
|
|
|
|
{
|
|
|
|
|
|
public:
|
|
|
|
|
|
/** @brief 按 VTK 引用计数规则创建本地预览表示。 */
|
|
|
|
|
|
static nmVolumePlaneRepresentation* New();
|
|
|
|
|
|
vtkTypeMacro(nmVolumePlaneRepresentation, vtkImplicitPlaneRepresentation);
|
|
|
|
|
|
/** @brief 中心或轮廓沿法向移动平面,法向箭头继续使用 VTK 内建旋转。 */
|
|
|
|
|
|
virtual int ComputeInteractionState(int nX, int nY, int nModify = 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
const bool bMoving = this->InteractionState == Moving;
|
|
|
|
|
|
const int nState = this->Superclass::ComputeInteractionState(nX, nY, nModify);
|
|
|
|
|
|
if(bMoving && (nState == MovingOrigin || this->Picker->GetActor() == this->EdgesActor)) {
|
|
|
|
|
|
this->SetInteractionState(Pushing);
|
|
|
|
|
|
this->SetRepresentationState(Pushing);
|
|
|
|
|
|
}
|
|
|
|
|
|
return this->InteractionState;
|
|
|
|
|
|
}
|
|
|
|
|
|
protected:
|
|
|
|
|
|
/** @brief 去除填充面和固定包围盒的拾取,避免在模型空白区域抢占相机操作。 */
|
|
|
|
|
|
nmVolumePlaneRepresentation()
|
|
|
|
|
|
{
|
|
|
|
|
|
this->Picker->DeletePickList(this->CutActor);
|
|
|
|
|
|
this->Picker->DeletePickList(this->OutlineActor);
|
|
|
|
|
|
this->Picker->AddPickList(this->EdgesActor);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
vtkStandardNewMacro(nmVolumePlaneRepresentation);
|
|
|
|
|
|
|
|
|
|
|
|
/** @brief 在隐藏、导入或退出模式时安全取消未松手的拖动,不保留 Interactor 焦点。 */
|
|
|
|
|
|
class nmVolumePlaneWidget : public vtkImplicitPlaneWidget2
|
|
|
|
|
|
{
|
|
|
|
|
|
public:
|
|
|
|
|
|
/** @brief 创建本窗口独占的交互 Widget。 */
|
|
|
|
|
|
static nmVolumePlaneWidget* New();
|
|
|
|
|
|
vtkTypeMacro(nmVolumePlaneWidget, vtkImplicitPlaneWidget2);
|
|
|
|
|
|
/** @brief 禁用时先解除主动拖动及焦点,不能发出一次伪造的松手应用。 */
|
|
|
|
|
|
virtual void SetEnabled(int nEnabled)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(!nEnabled && this->Enabled && this->WidgetState == Active) {
|
|
|
|
|
|
this->ReleaseFocus();
|
|
|
|
|
|
this->WidgetState = Start;
|
|
|
|
|
|
this->GetImplicitPlaneRepresentation()->SetRepresentationState(vtkImplicitPlaneRepresentation::Outside);
|
|
|
|
|
|
this->EndInteraction();
|
|
|
|
|
|
}
|
|
|
|
|
|
this->Superclass::SetEnabled(nEnabled);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
vtkStandardNewMacro(nmVolumePlaneWidget);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
nmVolumeSliceInteraction::nmVolumeSliceInteraction(nmWxVTKRenderContainerWidget* pContainer, QObject* pParent)
|
|
|
|
|
|
: QObject(pParent), m_pContainer(pContainer),
|
|
|
|
|
|
m_pRepresentation(vtkSmartPointer<nmVolumePlaneRepresentation>::New()),
|
|
|
|
|
|
m_pWidget(vtkSmartPointer<nmVolumePlaneWidget>::New()),
|
|
|
|
|
|
m_pCallback(vtkSmartPointer<vtkCallbackCommand>::New()),
|
|
|
|
|
|
m_nGeneration(0), m_nPendingGeneration(0), m_bQueued(false), m_bPendingApply(false)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pRepresentation->SetPlaceFactor(1.0);
|
|
|
|
|
|
m_pRepresentation->DrawPlaneOff();
|
|
|
|
|
|
m_pRepresentation->OutlineTranslationOff();
|
|
|
|
|
|
m_pRepresentation->ScaleEnabledOff();
|
|
|
|
|
|
m_pRepresentation->OutsideBoundsOn();
|
|
|
|
|
|
m_pRepresentation->ConstrainToWidgetBoundsOff();
|
|
|
|
|
|
m_pRepresentation->GetOutlineProperty()->SetColor(0.45, 0.55, 0.65);
|
|
|
|
|
|
m_pWidget->SetRepresentation(m_pRepresentation);
|
|
|
|
|
|
m_pWidget->KeyPressActivationOff();
|
|
|
|
|
|
// 中键、右键及键盘保留给现有相机和菜单;仅左键手柄用于移动或旋转平面。
|
|
|
|
|
|
vtkWidgetEventTranslator* pTranslator = m_pWidget->GetEventTranslator();
|
|
|
|
|
|
pTranslator->RemoveTranslation(vtkCommand::MiddleButtonPressEvent);
|
|
|
|
|
|
pTranslator->RemoveTranslation(vtkCommand::MiddleButtonReleaseEvent);
|
|
|
|
|
|
pTranslator->RemoveTranslation(vtkCommand::RightButtonPressEvent);
|
|
|
|
|
|
pTranslator->RemoveTranslation(vtkCommand::RightButtonReleaseEvent);
|
|
|
|
|
|
pTranslator->RemoveTranslation(vtkCommand::KeyPressEvent);
|
|
|
|
|
|
pTranslator->RemoveTranslation(vtkCommand::KeyReleaseEvent);
|
|
|
|
|
|
m_pCallback->SetClientData(this);
|
|
|
|
|
|
m_pCallback->SetCallback(onPlaneEvent);
|
|
|
|
|
|
m_pWidget->AddObserver(vtkCommand::InteractionEvent, m_pCallback);
|
|
|
|
|
|
m_pWidget->AddObserver(vtkCommand::EndInteractionEvent, m_pCallback);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
nmVolumeSliceInteraction::~nmVolumeSliceInteraction()
|
|
|
|
|
|
{
|
|
|
|
|
|
unbind();
|
|
|
|
|
|
m_pWidget->RemoveObservers(vtkCommand::InteractionEvent, m_pCallback);
|
|
|
|
|
|
m_pWidget->RemoveObservers(vtkCommand::EndInteractionEvent, m_pCallback);
|
|
|
|
|
|
m_pCallback->SetClientData(nullptr);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void nmVolumeSliceInteraction::bind(const QString& sDataId, const double dBounds[6], const nmVolumeSliceSettings& oSettings)
|
|
|
|
|
|
{
|
|
|
|
|
|
unbind();
|
|
|
|
|
|
if(m_pContainer == nullptr || sDataId.isEmpty() || dBounds == nullptr) return;
|
|
|
|
|
|
m_sDataId = sDataId;
|
|
|
|
|
|
m_pWidget->SetInteractor(m_pContainer->getInteractor());
|
|
|
|
|
|
m_pWidget->SetCurrentRenderer(m_pContainer->getRenderer());
|
|
|
|
|
|
double dPlacement[6];
|
|
|
|
|
|
for(int nIndex = 0; nIndex < 6; ++nIndex) dPlacement[nIndex] = dBounds[nIndex];
|
|
|
|
|
|
m_pRepresentation->PlaceWidget(dPlacement);
|
|
|
|
|
|
setPreview(oSettings);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void nmVolumeSliceInteraction::unbind()
|
|
|
|
|
|
{
|
|
|
|
|
|
++m_nGeneration;
|
|
|
|
|
|
m_bPendingApply = false;
|
|
|
|
|
|
m_pWidget->SetEnabled(0);
|
|
|
|
|
|
m_pWidget->SetInteractor(nullptr);
|
|
|
|
|
|
m_pWidget->SetCurrentRenderer(nullptr);
|
|
|
|
|
|
m_sDataId.clear();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void nmVolumeSliceInteraction::setPreview(const nmVolumeSliceSettings& oSettings)
|
|
|
|
|
|
{
|
|
|
|
|
|
nmVolumeSliceSettings oNormalized = oSettings;
|
|
|
|
|
|
if(!oNormalized.normalize()) return;
|
|
|
|
|
|
// 数值输入、重置或成功应用取代旧拖动草稿,不能再交付尚未处理的旧松手事件。
|
|
|
|
|
|
++m_nGeneration;
|
|
|
|
|
|
m_bPendingApply = false;
|
|
|
|
|
|
m_oPreview = oNormalized;
|
|
|
|
|
|
m_pRepresentation->SetOrigin(m_oPreview.m_dOrigin);
|
|
|
|
|
|
m_pRepresentation->SetNormal(m_oPreview.m_dNormal);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void nmVolumeSliceInteraction::setEnabled(bool bEnabled)
|
|
|
|
|
|
{
|
|
|
|
|
|
bEnabled = bEnabled && !m_sDataId.isEmpty();
|
|
|
|
|
|
if(isEnabled() == bEnabled) return;
|
|
|
|
|
|
if(!bEnabled) { ++m_nGeneration; m_bPendingApply = false; }
|
|
|
|
|
|
// VTK 禁用 Widget 时会清空 CurrentRenderer;恢复时不能依赖鼠标仍停留在视口内。
|
|
|
|
|
|
if(bEnabled) m_pWidget->SetCurrentRenderer(m_pContainer->getRenderer());
|
|
|
|
|
|
m_pWidget->SetEnabled(bEnabled ? 1 : 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool nmVolumeSliceInteraction::isEnabled() const { return m_pWidget->GetEnabled() != 0; }
|
|
|
|
|
|
|
|
|
|
|
|
void nmVolumeSliceInteraction::onPlaneEvent(vtkObject* pCaller, unsigned long nEvent, void* pClientData, void*)
|
|
|
|
|
|
{
|
|
|
|
|
|
nmVolumeSliceInteraction* pSelf = static_cast<nmVolumeSliceInteraction*>(pClientData);
|
|
|
|
|
|
if(pSelf == nullptr || !pSelf->isEnabled() || pCaller != pSelf->m_pWidget.GetPointer()) return;
|
|
|
|
|
|
pSelf->m_oPending = pSelf->m_oPreview;
|
|
|
|
|
|
pSelf->m_pRepresentation->GetOrigin(pSelf->m_oPending.m_dOrigin);
|
|
|
|
|
|
pSelf->m_pRepresentation->GetNormal(pSelf->m_oPending.m_dNormal);
|
|
|
|
|
|
pSelf->m_nPendingGeneration = pSelf->m_nGeneration;
|
|
|
|
|
|
pSelf->m_bPendingApply = pSelf->m_bPendingApply || nEvent == vtkCommand::EndInteractionEvent;
|
|
|
|
|
|
if(!pSelf->m_bQueued) {
|
|
|
|
|
|
pSelf->m_bQueued = true;
|
|
|
|
|
|
QMetaObject::invokeMethod(pSelf, "deliverPendingEvent", Qt::QueuedConnection);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void nmVolumeSliceInteraction::deliverPendingEvent()
|
|
|
|
|
|
{
|
|
|
|
|
|
m_bQueued = false;
|
|
|
|
|
|
const bool bApply = m_bPendingApply;
|
|
|
|
|
|
m_bPendingApply = false;
|
|
|
|
|
|
if(!isEnabled() || m_nPendingGeneration != m_nGeneration) return;
|
|
|
|
|
|
const unsigned int nGeneration = m_nGeneration;
|
|
|
|
|
|
const QString sDataId = m_sDataId;
|
|
|
|
|
|
m_oPreview = m_oPending;
|
|
|
|
|
|
emit previewChanged(sDataId, m_oPreview);
|
|
|
|
|
|
// previewChanged 的接收者可能关闭窗口或切换模式,必须再次检查代次。
|
|
|
|
|
|
if(bApply && isEnabled() && nGeneration == m_nGeneration) emit applyRequested(sDataId, m_oPreview);
|
|
|
|
|
|
}
|