You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
nmWTAI-Platform/Src/nmNum/nmSubWxs/nmVolumeSliceInteraction.cpp

179 lines
7.7 KiB
C++

#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);
}