diff --git a/docs/code/vtk/vtk_Prop3D.md b/docs/code/vtk/vtk_Prop3D.md new file mode 100644 index 0000000..0582454 --- /dev/null +++ b/docs/code/vtk/vtk_Prop3D.md @@ -0,0 +1,48 @@ +# Prop3D + +`uLib::Vtk::Prop3D` is a bridge class that wraps VTK 3D representations (`vtkProp`, `vtkProp3D`) and integrates them into the `uLib` object model. It allows the framework to manage visual objects, synchronize them with underlying data models, and expose display-specific properties to the GUI. + +## Inheritance +`uLib::Vtk::Prop3D` : `uLib::Object` + +## Key Functionalities + +### VTK Integration +The class provides access to the underlying VTK objects: +- `GetProp()`: Returns the `vtkProp`. +- `GetProxyProp()`: Returns the `vtkProp3D`. +- `GetParts()` / `GetProps()`: Returns `vtkPropCollection` for composite objects. + +### Model-View Synchronization +`Prop3D` ensures that the visual representation stays in sync with the domain model: +- `Update()`: Synchronizes the VTK representation based on current internal state and properties. Should be called when model data changes. +- `SyncFromVtk()`: Updates internal state using data from the VTK representation (e.g., after user interaction via gizmos in the 3D view). +- `GetContent()`: Returns the `uLib::Object` that this `Prop3D` represents visually. + +### Visual Appearance +- **Color & Opacity**: `SetColor(r, g, b)` and `SetOpacity(alpha)`. +- **Selection**: `SetSelectable(bool)` and `SetSelected(bool)` to manage interactivity and highlighting. +- **BBox/Scale**: `ShowBoundingBox(bool)` and `ShowScaleMeasures(bool)`. + +### Rendering Modes +The rendering style can be controlled via the `Representation` enum: +- `Points` +- `Wireframe` +- `Surface` +- `SurfaceWithEdges` +- `Volume` +- `Outline` +- `Slice` + +### Display Properties System +`Prop3D` implements a system to expose specific properties (often marked as `hrp` - human readable properties) to a property editor in the GUI. + +- `GetDisplayProperties()`: Returns the list of properties registered for display. +- `RegisterDisplayProperty(uLib::PropertyBase*)`: Adds a property to the display list. +- `serialize_display(...)`: A virtual method that subclasses implement to define which properties should be exposed. + +#### Activating Display Properties +To automatically populate the display properties list, the `ULIB_ACTIVATE_DISPLAY_PROPERTIES` macro should be called in the constructor. This triggers `serialize_display` using a `display_properties_archive`. + +## Implementation Details +`Prop3D` uses the Pimpl idiom (via `Prop3DData *pd`) to hide VTK-specific implementation details and reduce header dependencies. diff --git a/src/Vtk/uLibVtkInterface.cxx b/src/Vtk/uLibVtkInterface.cxx index 90ad2f8..9fab3ff 100644 --- a/src/Vtk/uLibVtkInterface.cxx +++ b/src/Vtk/uLibVtkInterface.cxx @@ -93,7 +93,8 @@ public: m_Selectable(true), m_Selected(false), m_Visibility(true), - m_Dragable(true) + m_Dragable(true), + m_HighlightMode(Prop3D::HighlightPlain) { m_Color = Vector3d(-1, -1, -1); } @@ -125,6 +126,8 @@ public: bool m_Visibility; bool m_Dragable; + int m_HighlightMode; // 0: Plain, 1: Corners + // TRS m_Transform; @@ -212,39 +215,71 @@ public: } if (!m_HighlightActor) { + m_HighlightActor = vtkSmartPointer::New(); + vtkSmartPointer mapper = vtkSmartPointer::New(); + m_HighlightActor->SetMapper(mapper); + m_HighlightActor->GetProperty()->SetRepresentationToWireframe(); + m_HighlightActor->GetProperty()->SetColor(1.0, 0.0, 0.0); // Red + m_HighlightActor->GetProperty()->SetLineWidth(2.0); + m_HighlightActor->GetProperty()->SetLighting(0); + } + + if (m_HighlightMode == Prop3D::HighlightPlain) { vtkSmartPointer cube = vtkSmartPointer::New(); double bounds[6]; polydata->GetBounds(bounds); - // Add a small padding to prevent z-fighting double maxDim = std::max({bounds[1]-bounds[0], bounds[3]-bounds[2], bounds[5]-bounds[4]}); double pad = maxDim * 0.02; if(pad < 1e-4) pad = 0.05; cube->SetBounds(bounds[0]-pad, bounds[1]+pad, bounds[2]-pad, bounds[3]+pad, bounds[4]-pad, bounds[5]+pad); - - m_HighlightActor = vtkSmartPointer::New(); - vtkSmartPointer mapper = vtkSmartPointer::New(); - mapper->SetInputConnection(cube->GetOutputPort()); - m_HighlightActor->SetMapper(mapper); - m_HighlightActor->GetProperty()->SetRepresentationToWireframe(); - m_HighlightActor->GetProperty()->SetColor(1.0, 0.0, 0.0); // Red - m_HighlightActor->GetProperty()->SetLineWidth(2.0); - m_HighlightActor->GetProperty()->SetLighting(0); + cube->Update(); + m_HighlightActor->GetMapper()->SetInputConnection(cube->GetOutputPort()); } else { - if (auto* mapper = vtkPolyDataMapper::SafeDownCast(m_HighlightActor->GetMapper())) { - if (auto* cube = vtkCubeSource::SafeDownCast(mapper->GetInputAlgorithm())) { - double bounds[6]; - polydata->GetBounds(bounds); - double maxDim = std::max({bounds[1]-bounds[0], bounds[3]-bounds[2], bounds[5]-bounds[4]}); - double pad = maxDim * 0.02; - if(pad < 1e-4) pad = 0.05; - cube->SetBounds(bounds[0]-pad, bounds[1]+pad, - bounds[2]-pad, bounds[3]+pad, - bounds[4]-pad, bounds[5]+pad); - cube->Modified(); + // Corners mode logic + double bounds[6]; + polydata->GetBounds(bounds); + double maxDim = std::max({bounds[1]-bounds[0], bounds[3]-bounds[2], bounds[5]-bounds[4]}); + double pad = maxDim * 0.02; + if(pad < 1e-4) pad = 0.05; + + double b[6] = {bounds[0]-pad, bounds[1]+pad, bounds[2]-pad, bounds[3]+pad, bounds[4]-pad, bounds[5]+pad}; + + vtkNew points; + vtkNew lines; + + float len[3] = { + (float)(b[1] - b[0]) * 0.15f, + (float)(b[3] - b[2]) * 0.15f, + (float)(b[5] - b[4]) * 0.15f + }; + + for (int i = 0; i < 8; ++i) { + double p[3]; + p[0] = b[(i & 1) ? 1 : 0]; + p[1] = b[(i & 2) ? 1 : 0]; + p[2] = b[(i & 4) ? 1 : 0]; + + for (int axis = 0; axis < 3; ++axis) { + double p2[3] = {p[0], p[1], p[2]}; + double delta = (i & (1 << axis)) ? -len[axis] : len[axis]; + p2[axis] += delta; + + vtkIdType id1 = points->InsertNextPoint(p); + vtkIdType id2 = points->InsertNextPoint(p2); + lines->InsertNextCell(2); + lines->InsertCellPoint(id1); + lines->InsertCellPoint(id2); } } + + vtkNew cornerPoly; + cornerPoly->SetPoints(points); + cornerPoly->SetLines(lines); + if (auto* mapper = vtkPolyDataMapper::SafeDownCast(m_HighlightActor->GetMapper())) { + mapper->SetInputData(cornerPoly); + } } // Update highlight matrix from the model world matrix @@ -514,6 +549,12 @@ void Prop3D::SetRepresentation(const char *mode) else if (s == "slice") SetRepresentation(Slice); } +void Prop3D::SetHighlightMode(HighlightMode mode) +{ + pd->m_HighlightMode = static_cast(mode); + pd->UpdateHighlight(); +} + void Prop3D::SetColor(double r, double g, double b) { pd->m_Color[0] = r; @@ -655,6 +696,8 @@ struct AppearanceProxy { ar & boost::serialization::make_hrp("Dragable", pd->m_Dragable); ar & boost::serialization::make_hrp("ShowBoundingBox", pd->m_ShowBoundingBox); ar & boost::serialization::make_hrp("ShowScaleMeasures", pd->m_ShowScaleMeasures); + ar & boost::serialization::make_hrp_enum("HighlightMode", + pd->m_HighlightMode, {"Plain", "Corners"}); } }; diff --git a/src/Vtk/uLibVtkInterface.h b/src/Vtk/uLibVtkInterface.h index 9f41ae2..f47307c 100644 --- a/src/Vtk/uLibVtkInterface.h +++ b/src/Vtk/uLibVtkInterface.h @@ -123,6 +123,12 @@ public: void SetRepresentation(Representation mode); void SetRepresentation(const char *mode); + enum HighlightMode { + HighlightPlain = 0, + HighlightCorners = 1 + }; + void SetHighlightMode(HighlightMode mode); + virtual void PrintSelf(std::ostream &o) const; void ShowBoundingBox(bool show);