feat: add HighlightCorners mode to Prop3D and document Prop3D class functionality

This commit is contained in:
AndreaRigoni
2026-04-10 17:13:00 +00:00
parent 22262d3dc6
commit e8c10daf6d
3 changed files with 119 additions and 22 deletions

View File

@@ -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.

View File

@@ -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<vtkActor>::New();
vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::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<vtkCubeSource> cube = vtkSmartPointer<vtkCubeSource>::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<vtkActor>::New();
vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::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<vtkPoints> points;
vtkNew<vtkCellArray> 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<vtkPolyData> 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<int>(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"});
}
};

View File

@@ -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);