feat: add HighlightCorners mode to Prop3D and document Prop3D class functionality
This commit is contained in:
48
docs/code/vtk/vtk_Prop3D.md
Normal file
48
docs/code/vtk/vtk_Prop3D.md
Normal 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.
|
||||||
@@ -93,7 +93,8 @@ public:
|
|||||||
m_Selectable(true),
|
m_Selectable(true),
|
||||||
m_Selected(false),
|
m_Selected(false),
|
||||||
m_Visibility(true),
|
m_Visibility(true),
|
||||||
m_Dragable(true)
|
m_Dragable(true),
|
||||||
|
m_HighlightMode(Prop3D::HighlightPlain)
|
||||||
{
|
{
|
||||||
m_Color = Vector3d(-1, -1, -1);
|
m_Color = Vector3d(-1, -1, -1);
|
||||||
}
|
}
|
||||||
@@ -125,6 +126,8 @@ public:
|
|||||||
bool m_Visibility;
|
bool m_Visibility;
|
||||||
bool m_Dragable;
|
bool m_Dragable;
|
||||||
|
|
||||||
|
int m_HighlightMode; // 0: Plain, 1: Corners
|
||||||
|
|
||||||
//
|
//
|
||||||
TRS m_Transform;
|
TRS m_Transform;
|
||||||
|
|
||||||
@@ -212,39 +215,71 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!m_HighlightActor) {
|
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();
|
vtkSmartPointer<vtkCubeSource> cube = vtkSmartPointer<vtkCubeSource>::New();
|
||||||
double bounds[6];
|
double bounds[6];
|
||||||
polydata->GetBounds(bounds);
|
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 maxDim = std::max({bounds[1]-bounds[0], bounds[3]-bounds[2], bounds[5]-bounds[4]});
|
||||||
double pad = maxDim * 0.02;
|
double pad = maxDim * 0.02;
|
||||||
if(pad < 1e-4) pad = 0.05;
|
if(pad < 1e-4) pad = 0.05;
|
||||||
cube->SetBounds(bounds[0]-pad, bounds[1]+pad,
|
cube->SetBounds(bounds[0]-pad, bounds[1]+pad,
|
||||||
bounds[2]-pad, bounds[3]+pad,
|
bounds[2]-pad, bounds[3]+pad,
|
||||||
bounds[4]-pad, bounds[5]+pad);
|
bounds[4]-pad, bounds[5]+pad);
|
||||||
|
cube->Update();
|
||||||
m_HighlightActor = vtkSmartPointer<vtkActor>::New();
|
m_HighlightActor->GetMapper()->SetInputConnection(cube->GetOutputPort());
|
||||||
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);
|
|
||||||
} else {
|
} else {
|
||||||
if (auto* mapper = vtkPolyDataMapper::SafeDownCast(m_HighlightActor->GetMapper())) {
|
// Corners mode logic
|
||||||
if (auto* cube = vtkCubeSource::SafeDownCast(mapper->GetInputAlgorithm())) {
|
double bounds[6];
|
||||||
double bounds[6];
|
polydata->GetBounds(bounds);
|
||||||
polydata->GetBounds(bounds);
|
double maxDim = std::max({bounds[1]-bounds[0], bounds[3]-bounds[2], bounds[5]-bounds[4]});
|
||||||
double maxDim = std::max({bounds[1]-bounds[0], bounds[3]-bounds[2], bounds[5]-bounds[4]});
|
double pad = maxDim * 0.02;
|
||||||
double pad = maxDim * 0.02;
|
if(pad < 1e-4) pad = 0.05;
|
||||||
if(pad < 1e-4) pad = 0.05;
|
|
||||||
cube->SetBounds(bounds[0]-pad, bounds[1]+pad,
|
double b[6] = {bounds[0]-pad, bounds[1]+pad, bounds[2]-pad, bounds[3]+pad, bounds[4]-pad, bounds[5]+pad};
|
||||||
bounds[2]-pad, bounds[3]+pad,
|
|
||||||
bounds[4]-pad, bounds[5]+pad);
|
vtkNew<vtkPoints> points;
|
||||||
cube->Modified();
|
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
|
// Update highlight matrix from the model world matrix
|
||||||
@@ -514,6 +549,12 @@ void Prop3D::SetRepresentation(const char *mode)
|
|||||||
else if (s == "slice") SetRepresentation(Slice);
|
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)
|
void Prop3D::SetColor(double r, double g, double b)
|
||||||
{
|
{
|
||||||
pd->m_Color[0] = r;
|
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("Dragable", pd->m_Dragable);
|
||||||
ar & boost::serialization::make_hrp("ShowBoundingBox", pd->m_ShowBoundingBox);
|
ar & boost::serialization::make_hrp("ShowBoundingBox", pd->m_ShowBoundingBox);
|
||||||
ar & boost::serialization::make_hrp("ShowScaleMeasures", pd->m_ShowScaleMeasures);
|
ar & boost::serialization::make_hrp("ShowScaleMeasures", pd->m_ShowScaleMeasures);
|
||||||
|
ar & boost::serialization::make_hrp_enum("HighlightMode",
|
||||||
|
pd->m_HighlightMode, {"Plain", "Corners"});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -123,6 +123,12 @@ public:
|
|||||||
void SetRepresentation(Representation mode);
|
void SetRepresentation(Representation mode);
|
||||||
void SetRepresentation(const char *mode);
|
void SetRepresentation(const char *mode);
|
||||||
|
|
||||||
|
enum HighlightMode {
|
||||||
|
HighlightPlain = 0,
|
||||||
|
HighlightCorners = 1
|
||||||
|
};
|
||||||
|
void SetHighlightMode(HighlightMode mode);
|
||||||
|
|
||||||
virtual void PrintSelf(std::ostream &o) const;
|
virtual void PrintSelf(std::ostream &o) const;
|
||||||
|
|
||||||
void ShowBoundingBox(bool show);
|
void ShowBoundingBox(bool show);
|
||||||
|
|||||||
Reference in New Issue
Block a user