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++

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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