feat: implement MultiSelectionProp to support grouped object transformation and selection in Viewport
This commit is contained in:
@@ -13,6 +13,7 @@
|
||||
#include <QPushButton>
|
||||
#include <QMenu>
|
||||
#include <QAction>
|
||||
#include <QShortcut>
|
||||
#include <QApplication>
|
||||
#include <QFileDialog>
|
||||
#include <QFileInfo>
|
||||
@@ -104,6 +105,14 @@ MainPanel::MainPanel(QWidget* parent) : QWidget(parent), m_context(nullptr), m_m
|
||||
m_rootSplitter->setSizes(sizes);
|
||||
|
||||
mainLayout->addWidget(m_rootSplitter, 1);
|
||||
|
||||
// Shortcuts
|
||||
auto* groupShortcut = new QShortcut(QKeySequence("Ctrl+G"), this);
|
||||
connect(groupShortcut, &QShortcut::activated, [this]() {
|
||||
if (auto* viewport = qobject_cast<uLib::Vtk::QViewport*>(m_firstPane->currentViewport())) {
|
||||
viewport->GroupSelection(m_context);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void MainPanel::setContext(uLib::ObjectsContext* context) {
|
||||
|
||||
@@ -4,6 +4,7 @@ set(HEADERS uLibVtkInterface.h
|
||||
vtkQViewport.h
|
||||
vtkViewport.h
|
||||
vtkObjectsContext.h
|
||||
vtkMultiSelectionProp.h
|
||||
)
|
||||
|
||||
set(SOURCES uLibVtkInterface.cxx
|
||||
@@ -12,6 +13,7 @@ set(SOURCES uLibVtkInterface.cxx
|
||||
vtkQViewport.cpp
|
||||
vtkViewport.cpp
|
||||
vtkObjectsContext.cpp
|
||||
vtkMultiSelectionProp.cpp
|
||||
)
|
||||
|
||||
## Pull in Math VTK wrappers (sets MATH_SOURCES / MATH_HEADERS)
|
||||
|
||||
154
src/Vtk/vtkMultiSelectionProp.cpp
Normal file
154
src/Vtk/vtkMultiSelectionProp.cpp
Normal file
@@ -0,0 +1,154 @@
|
||||
#include "vtkMultiSelectionProp.h"
|
||||
#include <vtkActor.h>
|
||||
#include <vtkPolyDataMapper.h>
|
||||
#include <vtkCubeSource.h>
|
||||
#include <vtkProperty.h>
|
||||
#include <vtkMatrix4x4.h>
|
||||
#include <vtkRenderer.h>
|
||||
#include <vtkRendererCollection.h>
|
||||
#include "Math/Transform.h"
|
||||
#include "Vtk/Math/vtkDense.h"
|
||||
|
||||
namespace uLib {
|
||||
namespace Vtk {
|
||||
|
||||
MultiSelectionProp::MultiSelectionProp() : Prop3D() {
|
||||
this->SetInstanceName("Selection Group");
|
||||
m_PrevMatrix = vtkSmartPointer<vtkMatrix4x4>::New();
|
||||
m_GroupHighlightActor = vtkSmartPointer<vtkActor>::New();
|
||||
|
||||
vtkNew<vtkPolyDataMapper> mapper;
|
||||
m_GroupHighlightActor->SetMapper(mapper);
|
||||
m_GroupHighlightActor->GetProperty()->SetRepresentationToWireframe();
|
||||
m_GroupHighlightActor->GetProperty()->SetColor(0.0, 1.0, 0.0); // Green for group
|
||||
m_GroupHighlightActor->GetProperty()->SetLineWidth(2.0);
|
||||
m_GroupHighlightActor->GetProperty()->SetLighting(0);
|
||||
m_GroupHighlightActor->PickableOff();
|
||||
}
|
||||
|
||||
MultiSelectionProp::~MultiSelectionProp() {
|
||||
}
|
||||
|
||||
MultiSelectionProp* MultiSelectionProp::Clone() const {
|
||||
auto* copy = new MultiSelectionProp();
|
||||
copy->SetMembers(this->m_Members);
|
||||
copy->SetInstanceName(this->GetInstanceName());
|
||||
return copy;
|
||||
}
|
||||
|
||||
void MultiSelectionProp::SetMembers(const std::vector<Prop3D*>& members) {
|
||||
m_Members = members;
|
||||
Update();
|
||||
|
||||
// Reset prev matrix to current highlight position
|
||||
if (m_GroupHighlightActor->GetUserMatrix()) {
|
||||
m_PrevMatrix->DeepCopy(m_GroupHighlightActor->GetUserMatrix());
|
||||
} else {
|
||||
m_PrevMatrix->Identity();
|
||||
}
|
||||
}
|
||||
|
||||
void MultiSelectionProp::Update() {
|
||||
if (m_Members.empty()) {
|
||||
m_GroupHighlightActor->VisibilityOff();
|
||||
return;
|
||||
}
|
||||
m_GroupHighlightActor->VisibilityOn();
|
||||
double combinedBounds[6] = {VTK_DOUBLE_MAX, VTK_DOUBLE_MIN,
|
||||
VTK_DOUBLE_MAX, VTK_DOUBLE_MIN,
|
||||
VTK_DOUBLE_MAX, VTK_DOUBLE_MIN};
|
||||
|
||||
for (auto* member : m_Members) {
|
||||
if (vtkProp* prop = member->GetProp()) {
|
||||
double* b = prop->GetBounds();
|
||||
if (b) {
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
if (b[2*i] < combinedBounds[2*i]) combinedBounds[2*i] = b[2*i];
|
||||
if (b[2*i+1] > combinedBounds[2*i+1]) combinedBounds[2*i+1] = b[2*i+1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (combinedBounds[0] > combinedBounds[1]) return;
|
||||
|
||||
vtkNew<vtkCubeSource> cube;
|
||||
double maxDim = std::max({combinedBounds[1]-combinedBounds[0],
|
||||
combinedBounds[3]-combinedBounds[2],
|
||||
combinedBounds[5]-combinedBounds[4]});
|
||||
double pad = maxDim * 0.02;
|
||||
if (pad < 1e-4) pad = 0.05;
|
||||
|
||||
cube->SetBounds(combinedBounds[0]-pad, combinedBounds[1]+pad,
|
||||
combinedBounds[2]-pad, combinedBounds[3]+pad,
|
||||
combinedBounds[4]-pad, combinedBounds[5]+pad);
|
||||
cube->Update();
|
||||
|
||||
if (auto* mapper = vtkPolyDataMapper::SafeDownCast(m_GroupHighlightActor->GetMapper())) {
|
||||
mapper->SetInputConnection(cube->GetOutputPort());
|
||||
}
|
||||
|
||||
// The highlight actor itself should have identity user matrix initially,
|
||||
// as it's defined in world space bounds.
|
||||
m_GroupHighlightActor->SetUserMatrix(nullptr);
|
||||
m_PrevMatrix->Identity();
|
||||
|
||||
// Ensure it's in the renderers
|
||||
vtkRendererCollection* rens = this->GetRenderers();
|
||||
rens->InitTraversal();
|
||||
for (int i = 0; i < rens->GetNumberOfItems(); ++i) {
|
||||
vtkRenderer* ren = rens->GetNextItem();
|
||||
ren->AddActor(m_GroupHighlightActor);
|
||||
}
|
||||
}
|
||||
|
||||
void MultiSelectionProp::SyncFromVtk() {
|
||||
if (m_Members.empty()) return;
|
||||
|
||||
vtkMatrix4x4* currentMatrix = m_GroupHighlightActor->GetUserMatrix();
|
||||
if (!currentMatrix) return;
|
||||
|
||||
// Calculate Delta: currentMatrix * Inv(m_PrevMatrix)
|
||||
vtkNew<vtkMatrix4x4> invPrev;
|
||||
vtkMatrix4x4::Invert(m_PrevMatrix, invPrev);
|
||||
|
||||
vtkNew<vtkMatrix4x4> delta;
|
||||
vtkMatrix4x4::Multiply4x4(currentMatrix, invPrev, delta);
|
||||
|
||||
// Apply delta to all members
|
||||
for (auto* member : m_Members) {
|
||||
if (auto* content = member->GetContent()) {
|
||||
if (auto* tr = dynamic_cast<uLib::TRS*>(content)) {
|
||||
vtkNew<vtkMatrix4x4> memberWorldMatrix;
|
||||
Matrix4fToVtk(tr->GetWorldMatrix(), memberWorldMatrix);
|
||||
|
||||
vtkNew<vtkMatrix4x4> nextWorldMatrix;
|
||||
vtkMatrix4x4::Multiply4x4(delta, memberWorldMatrix, nextWorldMatrix);
|
||||
|
||||
// Set the new world matrix.
|
||||
// We need to calculate the new local matrix if there's a parent.
|
||||
if (tr->GetParent()) {
|
||||
Matrix4f invParentWorld = tr->GetParent()->GetWorldMatrix().inverse();
|
||||
Matrix4f nextLocalMatrix = invParentWorld * VtkToMatrix4f(nextWorldMatrix);
|
||||
tr->FromMatrix(nextLocalMatrix);
|
||||
} else {
|
||||
tr->FromMatrix(VtkToMatrix4f(nextWorldMatrix));
|
||||
}
|
||||
member->Update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_PrevMatrix->DeepCopy(currentMatrix);
|
||||
}
|
||||
|
||||
vtkProp* MultiSelectionProp::GetProp() {
|
||||
return m_GroupHighlightActor;
|
||||
}
|
||||
|
||||
vtkProp3D* MultiSelectionProp::GetProxyProp() {
|
||||
return m_GroupHighlightActor;
|
||||
}
|
||||
|
||||
} // namespace Vtk
|
||||
} // namespace uLib
|
||||
49
src/Vtk/vtkMultiSelectionProp.h
Normal file
49
src/Vtk/vtkMultiSelectionProp.h
Normal file
@@ -0,0 +1,49 @@
|
||||
#ifndef ULIB_VTK_MULTISELECTIONPROP_H
|
||||
#define ULIB_VTK_MULTISELECTIONPROP_H
|
||||
|
||||
#include "uLibVtkInterface.h"
|
||||
#include <vector>
|
||||
#include <vtkSmartPointer.h>
|
||||
#include <vtkMatrix4x4.h>
|
||||
|
||||
class vtkActor;
|
||||
class vtkProp;
|
||||
class vtkProp3D;
|
||||
|
||||
namespace uLib {
|
||||
namespace Vtk {
|
||||
|
||||
/**
|
||||
* @class MultiSelectionProp
|
||||
* @brief A proxy Prop3D that represents a group of selected Prop3Ds.
|
||||
* It manages a combined highlight and propagates transformations to its members.
|
||||
*/
|
||||
class MultiSelectionProp : public Prop3D {
|
||||
public:
|
||||
uLibTypeMacro(MultiSelectionProp, Prop3D)
|
||||
|
||||
MultiSelectionProp();
|
||||
virtual ~MultiSelectionProp();
|
||||
|
||||
/** @brief Creates a new instance that is a copy of this one's selection state. */
|
||||
MultiSelectionProp* Clone() const;
|
||||
|
||||
void SetMembers(const std::vector<Prop3D*>& members);
|
||||
const std::vector<Prop3D*>& GetMembers() const { return m_Members; }
|
||||
|
||||
virtual void Update() override;
|
||||
virtual void SyncFromVtk() override;
|
||||
|
||||
virtual vtkProp* GetProp() override;
|
||||
virtual vtkProp3D* GetProxyProp() override;
|
||||
|
||||
private:
|
||||
std::vector<Prop3D*> m_Members;
|
||||
vtkSmartPointer<vtkMatrix4x4> m_PrevMatrix;
|
||||
vtkSmartPointer<vtkActor> m_GroupHighlightActor;
|
||||
};
|
||||
|
||||
} // namespace Vtk
|
||||
} // namespace uLib
|
||||
|
||||
#endif // ULIB_VTK_MULTISELECTIONPROP_H
|
||||
@@ -136,6 +136,10 @@ Prop3D *ObjectsContext::CreateProp3D(uLib::Object *obj) {
|
||||
if (!obj)
|
||||
return nullptr;
|
||||
|
||||
if (auto* p3d = dynamic_cast<Prop3D*>(obj)) {
|
||||
return p3d;
|
||||
}
|
||||
|
||||
if (auto *vox = dynamic_cast<uLib::Abstract::VoxImage *>(obj)) {
|
||||
return new VoxImage(vox);
|
||||
} else if (auto *box = dynamic_cast<uLib::ContainerBox *>(obj)) {
|
||||
|
||||
@@ -36,6 +36,8 @@
|
||||
#include "Vtk/Math/vtkCylinder.h"
|
||||
#include "Math/Transform.h"
|
||||
#include "Vtk/Math/vtkAssembly.h"
|
||||
#include "vtkMultiSelectionProp.h"
|
||||
#include <vtkRendererCollection.h>
|
||||
|
||||
namespace uLib {
|
||||
namespace Vtk {
|
||||
@@ -69,6 +71,7 @@ struct ViewportData {
|
||||
Viewport::Viewport()
|
||||
: pv(new ViewportData())
|
||||
, m_GridAxis(Y)
|
||||
, m_MultiSelectionProp(new MultiSelectionProp())
|
||||
{
|
||||
}
|
||||
|
||||
@@ -100,6 +103,10 @@ Viewport::~Viewport()
|
||||
pv->m_CameraWidget->Off();
|
||||
pv->m_CameraWidget->SetInteractor(nullptr);
|
||||
}
|
||||
if (m_MultiSelectionProp) {
|
||||
delete m_MultiSelectionProp;
|
||||
m_MultiSelectionProp = nullptr;
|
||||
}
|
||||
delete pv;
|
||||
}
|
||||
|
||||
@@ -192,6 +199,11 @@ void Viewport::SetupPipeline(vtkRenderWindowInteractor* iren)
|
||||
pv->m_Renderer->SetLayer(0);
|
||||
}
|
||||
|
||||
// Connect MultiSelectionProp
|
||||
if (m_MultiSelectionProp) {
|
||||
m_MultiSelectionProp->ConnectRenderer(pv->m_Renderer);
|
||||
}
|
||||
|
||||
// Setup Handler Widget
|
||||
if (!std::getenv("CTEST_PROJECT_NAME")) {
|
||||
pv->m_HandlerWidget = vtkSmartPointer<HandlerWidget>::New();
|
||||
@@ -206,8 +218,10 @@ void Viewport::SetupPipeline(vtkRenderWindowInteractor* iren)
|
||||
widgetInteractionCallback->SetClientData(this);
|
||||
widgetInteractionCallback->SetCallback([](vtkObject*, unsigned long, void* clientdata, void*){
|
||||
auto* self = static_cast<Viewport*>(clientdata);
|
||||
for (auto* p : self->m_Prop3Ds) {
|
||||
if (p->IsSelected()) {
|
||||
if (self->m_SelectedProps.size() > 1 && self->m_MultiSelectionProp) {
|
||||
self->m_MultiSelectionProp->SyncFromVtk();
|
||||
} else {
|
||||
for (auto* p : self->m_SelectedProps) {
|
||||
p->SyncFromVtk();
|
||||
}
|
||||
}
|
||||
@@ -222,6 +236,7 @@ void Viewport::SetupPipeline(vtkRenderWindowInteractor* iren)
|
||||
clickCallback->SetCallback([](vtkObject* caller, unsigned long, void* clientdata, void*){
|
||||
auto* iren = static_cast<vtkRenderWindowInteractor*>(caller);
|
||||
auto* self = static_cast<Viewport*>(clientdata);
|
||||
bool multiSelect = iren->GetShiftKey() != 0;
|
||||
|
||||
int* pos = iren->GetEventPosition();
|
||||
self->pv->m_Picker->Pick(pos[0], pos[1], 0, self->pv->m_Renderer);
|
||||
@@ -291,7 +306,7 @@ void Viewport::SetupPipeline(vtkRenderWindowInteractor* iren)
|
||||
}
|
||||
}
|
||||
}
|
||||
self->SelectProp3D(target);
|
||||
self->SelectProp3D(target, multiSelect);
|
||||
});
|
||||
iren->AddObserver(vtkCommand::LeftButtonPressEvent, clickCallback);
|
||||
|
||||
@@ -446,6 +461,11 @@ void Viewport::RegisterProp3D(Prop3D* p, bool isPart) {
|
||||
m_Prop3Ds.push_back(p);
|
||||
p->ConnectRenderer(pv->m_Renderer);
|
||||
|
||||
// Ensure m_MultiSelectionProp also has the same renderers
|
||||
if (m_MultiSelectionProp) {
|
||||
m_MultiSelectionProp->GetRenderers()->AddItem(pv->m_Renderer);
|
||||
}
|
||||
|
||||
// If it's a part of an assembly, we don't want to draw it twice.
|
||||
// Assembly itself already draws its parts.
|
||||
// But we need ConnectRenderer above to allow highliting and property updates.
|
||||
@@ -497,28 +517,59 @@ void Viewport::ObserveContext(ObjectsContext* ctx) {
|
||||
});
|
||||
}
|
||||
|
||||
void Viewport::SelectProp3D(Prop3D* prop)
|
||||
void Viewport::SelectProp3D(Prop3D* prop, bool multi)
|
||||
{
|
||||
for (auto* p : m_Prop3Ds) {
|
||||
p->SetSelected(p == prop);
|
||||
}
|
||||
|
||||
if (pv->m_HandlerWidget) {
|
||||
if (multi) {
|
||||
if (prop) {
|
||||
vtkProp3D* prop3d = prop->GetProxyProp();
|
||||
auto it = std::find(m_SelectedProps.begin(), m_SelectedProps.end(), prop);
|
||||
if (it != m_SelectedProps.end()) {
|
||||
prop->SetSelected(false);
|
||||
m_SelectedProps.erase(it);
|
||||
} else {
|
||||
prop->SetSelected(true);
|
||||
m_SelectedProps.push_back(prop);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (auto* p : m_SelectedProps) {
|
||||
p->SetSelected(false);
|
||||
}
|
||||
m_SelectedProps.clear();
|
||||
if (prop) {
|
||||
prop->SetSelected(true);
|
||||
m_SelectedProps.push_back(prop);
|
||||
}
|
||||
}
|
||||
|
||||
// Update HandlerWidget
|
||||
if (pv->m_HandlerWidget) {
|
||||
if (m_SelectedProps.empty()) {
|
||||
pv->m_HandlerWidget->SetEnabled(0);
|
||||
pv->m_HandlerWidget->SetProp3D(nullptr);
|
||||
if (m_MultiSelectionProp) m_MultiSelectionProp->SetMembers({});
|
||||
} else if (m_SelectedProps.size() == 1) {
|
||||
Prop3D* selected = m_SelectedProps[0];
|
||||
vtkProp3D* prop3d = selected->GetProxyProp();
|
||||
if (prop3d) {
|
||||
pv->m_HandlerWidget->SetProp3D(prop3d);
|
||||
pv->m_HandlerWidget->SetEnabled(1);
|
||||
pv->m_HandlerWidget->PlaceWidget(prop3d->GetBounds()); //TODO: FIX !
|
||||
pv->m_HandlerWidget->PlaceWidget(prop3d->GetBounds());
|
||||
}
|
||||
if (m_MultiSelectionProp) m_MultiSelectionProp->SetMembers({});
|
||||
} else {
|
||||
pv->m_HandlerWidget->SetEnabled(0);
|
||||
pv->m_HandlerWidget->SetProp3D(nullptr);
|
||||
// Multi-selection
|
||||
if (m_MultiSelectionProp) {
|
||||
m_MultiSelectionProp->SetMembers(m_SelectedProps);
|
||||
vtkProp3D* proxy = m_MultiSelectionProp->GetProxyProp();
|
||||
pv->m_HandlerWidget->SetProp3D(proxy);
|
||||
pv->m_HandlerWidget->SetEnabled(1);
|
||||
pv->m_HandlerWidget->PlaceWidget(proxy->GetBounds());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Render();
|
||||
OnSelectionChanged(prop);
|
||||
OnSelectionChanged(m_SelectedProps.empty() ? nullptr : m_SelectedProps.back());
|
||||
}
|
||||
|
||||
void Viewport::SetGridVisible(bool visible)
|
||||
@@ -657,5 +708,19 @@ void Viewport::UpdateGrid()
|
||||
pv->m_Annotation->SetText(1, gridLabel);
|
||||
}
|
||||
|
||||
void Viewport::GroupSelection(uLib::ObjectsContext* targetCtx) {
|
||||
if (!targetCtx || m_SelectedProps.size() <= 1 || !m_MultiSelectionProp) return;
|
||||
|
||||
// Clone the current multi-selection proxy
|
||||
MultiSelectionProp* group = m_MultiSelectionProp->Clone();
|
||||
|
||||
// Add it to the context
|
||||
targetCtx->AddObject(group);
|
||||
|
||||
// Select the new group and clear multi-selection
|
||||
m_SelectedProps.clear();
|
||||
SelectProp3D(group);
|
||||
}
|
||||
|
||||
} // namespace Vtk
|
||||
} // namespace uLib
|
||||
|
||||
@@ -29,6 +29,7 @@ namespace Vtk {
|
||||
|
||||
struct ViewportData;
|
||||
class HandlerWidget;
|
||||
class MultiSelectionProp;
|
||||
class ObjectsContext;
|
||||
|
||||
/**
|
||||
@@ -49,7 +50,11 @@ public:
|
||||
// Prop3D / prop management
|
||||
void AddProp3D(Prop3D &prop);
|
||||
void RemoveProp3D(Prop3D &prop);
|
||||
void SelectProp3D(Prop3D *prop);
|
||||
/** @brief Selects a specific Prop3D. If multi is true, it toggles selection in a group. */
|
||||
void SelectProp3D(Prop3D* target, bool multi = false);
|
||||
|
||||
/** @brief Creates a persistent Selection Group from the current multi-selection. */
|
||||
void GroupSelection(uLib::ObjectsContext* targetCtx);
|
||||
void addProp(vtkProp *prop);
|
||||
void RemoveProp(vtkProp *prop);
|
||||
|
||||
@@ -91,6 +96,8 @@ protected:
|
||||
struct ViewportData *pv;
|
||||
Axis m_GridAxis;
|
||||
std::vector<Prop3D*> m_Prop3Ds;
|
||||
std::vector<Prop3D*> m_SelectedProps;
|
||||
MultiSelectionProp* m_MultiSelectionProp;
|
||||
std::map<uLib::Object*, Prop3D*> m_ObjectToProp3D;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user