fix some on properties and signal connection
This commit is contained in:
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*.vtk filter=lfs diff=lfs merge=lfs -text
|
||||
*.vti filter=lfs diff=lfs merge=lfs -text
|
||||
90
CLAUDE.md
Normal file
90
CLAUDE.md
Normal file
@@ -0,0 +1,90 @@
|
||||
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Build Commands
|
||||
|
||||
```bash
|
||||
# Activate the conda environment (required before any build/run)
|
||||
export MAMBA_EXE="/home/share/micromamba/bin/micromamba"
|
||||
export MAMBA_ROOT_PREFIX="/home/share/micromamba"
|
||||
eval "$(/home/share/micromamba/bin/micromamba shell hook --shell bash)"
|
||||
micromamba activate mutom
|
||||
|
||||
# Configure (from repo root, using Conan preset)
|
||||
cmake --preset conan-release
|
||||
|
||||
# Build everything
|
||||
cmake --build build -j$(nproc)
|
||||
|
||||
# Build a specific target
|
||||
cmake --build build --target gcompose -j$(nproc)
|
||||
|
||||
# Run tests
|
||||
cmake --build build --target test
|
||||
# or
|
||||
ctest --test-dir build
|
||||
|
||||
# Run a single test binary (example)
|
||||
./build/src/Core/testing/CoreTest
|
||||
|
||||
# Run the gcompose GUI app
|
||||
./build/app/gcompose/gcompose
|
||||
```
|
||||
|
||||
First-time setup (if `build/` does not exist):
|
||||
```bash
|
||||
conan profile detect
|
||||
conan install . --output-folder=build --build=missing
|
||||
cmake --preset conan-release
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
**uLib** is a C++ framework for Cosmic Muon Tomography (CMT), structured as layered shared libraries:
|
||||
|
||||
```
|
||||
mutomCore → mutomMath → mutomDetectors → mutomGeant
|
||||
↘
|
||||
mutomVtk → gcompose (Qt6 GUI app)
|
||||
mutomRoot
|
||||
```
|
||||
|
||||
### Core Object Model (`src/Core/`)
|
||||
- All framework objects inherit from `uLib::Object`
|
||||
- **Property system**: `Property<T>` wraps member pointers with change notification via `PropertyChanged` signal
|
||||
- **Signal/slot**: `uLib::Object::connect(sender, &Sender::Signal, callback)` — resembles Qt but works for non-QObject classes
|
||||
- **Serialization**: Boost archives (`xml_oarchive`, `text_oarchive`, `hrt_oarchive`); `hrp<T>` marks fields as "human-readable properties"
|
||||
- `ObjectsContext` is a container owning a list of `Object*` pointers; signals `ObjectAdded`/`ObjectRemoved`
|
||||
|
||||
### VTK Layer (`src/Vtk/`)
|
||||
- `Puppet` (inherits `uLib::Object`): wraps a VTK `vtkProp` for rendering. Has `GetContent()` returning the underlying domain object. Display-only properties are registered via `ULIB_ACTIVATE_DISPLAY_PROPERTIES` macro.
|
||||
- `Viewport`: base class managing the VTK renderer, picking, selection logic. Maintains `m_Puppets` vector and `m_ObjectToPuppet` map.
|
||||
- `QViewport` (inherits `QWidget` + `Viewport`): Qt-embedded VTK widget. Emits Qt signal `puppetSelected(Puppet*)` on click-selection via `OnSelectionChanged`.
|
||||
- `vtkObjectsContext`: wraps `ObjectsContext`, creating/destroying `Puppet`s as objects come/go. Emits `PuppetAdded`/`PuppetRemoved`.
|
||||
- Display properties: `serialize_display()` + `display_properties_archive` registers selected `hrp<T>` fields as `PropertyBase*` in the puppet's `m_DisplayProperties`. `PropertyEditor::setObject(obj, displayOnly=true)` shows only those.
|
||||
|
||||
### gcompose GUI App (`app/gcompose/src/`)
|
||||
- `MainPanel`: top-level widget. Owns `ContextPanel` (left) and `ViewportPane` (right). Wires together viewport↔context selection via signals.
|
||||
- `ContextPanel`: tree view of `ObjectsContext`. Emits `objectSelected(Object*)`. Contains an embedded `PropertiesPanel`.
|
||||
- `PropertiesPanel`: shows `uLib::Object` properties via `PropertyEditor`.
|
||||
- `ViewportPane`: embeds `QViewport` + a slide-out "Display Properties" panel (`PropertyEditor` in display-only mode).
|
||||
- `PropertyEditor`: populates widgets from `Object::GetProperties()` (all) or `Puppet::GetDisplayProperties()` (display-only mode).
|
||||
|
||||
### Selection Sync Flow
|
||||
```
|
||||
Viewport click → Viewport::SelectPuppet() → QViewport::OnSelectionChanged()
|
||||
→ emit puppetSelected(p)
|
||||
→ MainPanel: contextPanel->selectObject(p->GetContent()) [updates tree + PropertiesPanel]
|
||||
→ MainPanel: firstPane->setObject(p) [updates Display Properties panel]
|
||||
|
||||
ContextPanel tree click → emit objectSelected(obj)
|
||||
→ MainPanel: viewport->SelectPuppet(puppet) [visual selection in VTK]
|
||||
→ MainPanel: firstPane->setObject(puppet) [updates Display Properties panel]
|
||||
```
|
||||
|
||||
### Key Patterns
|
||||
- **Two signal systems coexist**: Qt signals (`Q_OBJECT`, `connect(...)`) for GUI; `uLib::Object::connect(...)` for domain signals.
|
||||
- **Display properties** flow: `Puppet::serialize_display()` → `display_properties_archive` → `RegisterDisplayProperty()` → `PropertyEditor(displayOnly=true)`. Must call `ULIB_ACTIVATE_DISPLAY_PROPERTIES` in the puppet constructor.
|
||||
- **Puppet ↔ Object map**: `Viewport::m_ObjectToPuppet` allows lookup by domain object; `vtkObjectsContext::GetPuppet(obj)` does the same.
|
||||
@@ -14,10 +14,25 @@ void ContextModel::setContext(uLib::ObjectsContext* context) {
|
||||
beginResetModel();
|
||||
m_rootContext = context;
|
||||
if (m_rootContext) {
|
||||
uLib::Object::connect(m_rootContext, &uLib::Object::Updated, [this]() {
|
||||
auto refresh = [this]() {
|
||||
this->beginResetModel();
|
||||
this->endResetModel();
|
||||
};
|
||||
|
||||
uLib::Object::connect(m_rootContext, &uLib::Object::Updated, refresh);
|
||||
uLib::Object::connect(m_rootContext, &uLib::ObjectsContext::ObjectAdded, [this, refresh](uLib::Object* obj) {
|
||||
uLib::Object::connect(obj, &uLib::Object::Updated, refresh);
|
||||
refresh();
|
||||
});
|
||||
uLib::Object::connect(m_rootContext, &uLib::ObjectsContext::ObjectRemoved, [this, refresh](uLib::Object* obj) {
|
||||
// Disconnect would be good here but not strictly required if refresh handles it
|
||||
refresh();
|
||||
});
|
||||
|
||||
// Connect existing objects
|
||||
for (auto* obj : m_rootContext->GetObjects()) {
|
||||
uLib::Object::connect(obj, &uLib::Object::Updated, refresh);
|
||||
}
|
||||
}
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
@@ -92,3 +92,27 @@ void ContextPanel::onSelectionChanged(const QItemSelection& selected, const QIte
|
||||
emit objectSelected(target);
|
||||
m_propertiesPanel->setObject(target);
|
||||
}
|
||||
|
||||
void ContextPanel::selectObject(uLib::Object* obj) {
|
||||
if (!obj) {
|
||||
clearSelection();
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < m_model->rowCount(); ++i) {
|
||||
QModelIndex idx = m_model->index(i, 0);
|
||||
if (idx.internalPointer() == obj) {
|
||||
QSignalBlocker blocker(m_treeView->selectionModel());
|
||||
m_treeView->selectionModel()->select(idx, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
|
||||
m_treeView->scrollTo(idx);
|
||||
m_propertiesPanel->setObject(obj); // Explicitly update properties too
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ContextPanel::clearSelection() {
|
||||
QSignalBlocker blocker(m_treeView->selectionModel());
|
||||
m_treeView->selectionModel()->clearSelection();
|
||||
m_propertiesPanel->setObject(nullptr);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ public:
|
||||
~ContextPanel();
|
||||
|
||||
void setContext(uLib::ObjectsContext* context);
|
||||
void selectObject(uLib::Object* obj);
|
||||
void clearSelection();
|
||||
|
||||
signals:
|
||||
void objectSelected(uLib::Object* obj);
|
||||
|
||||
@@ -14,7 +14,10 @@
|
||||
#include <QMenu>
|
||||
#include <QAction>
|
||||
#include <QApplication>
|
||||
#include <QFileDialog>
|
||||
#include <QFileInfo>
|
||||
#include "StyleManager.h"
|
||||
#include "Math/VoxImage.h"
|
||||
|
||||
MainPanel::MainPanel(QWidget* parent) : QWidget(parent), m_context(nullptr), m_mainVtkContext(nullptr) {
|
||||
this->setObjectName("MainPanel");
|
||||
@@ -124,6 +127,17 @@ void MainPanel::setContext(uLib::ObjectsContext* context) {
|
||||
m_mainVtkContext = new uLib::Vtk::vtkObjectsContext(context);
|
||||
// viewport->AddPuppet(*m_mainVtkContext); // redundant
|
||||
|
||||
auto syncSelection = [this](uLib::Vtk::Puppet* p) {
|
||||
if (!p) {
|
||||
m_contextPanel->clearSelection();
|
||||
m_firstPane->setObject(nullptr);
|
||||
} else {
|
||||
m_contextPanel->selectObject(p->GetContent());
|
||||
m_firstPane->setObject(p);
|
||||
}
|
||||
};
|
||||
connect(viewport, &uLib::Vtk::QViewport::puppetSelected, syncSelection);
|
||||
|
||||
uLib::Object::connect(m_mainVtkContext, &uLib::Vtk::vtkObjectsContext::PuppetAdded, [this](uLib::Vtk::Puppet* p) {
|
||||
if (p) {
|
||||
auto panes = this->findChildren<ViewportPane*>();
|
||||
@@ -179,7 +193,35 @@ void MainPanel::onCreateObject(const std::string& className) {
|
||||
}
|
||||
|
||||
void MainPanel::onOpen() {
|
||||
// Placeholder for open logic
|
||||
QString fileName = QFileDialog::getOpenFileName(this, "Open File", "",
|
||||
"VTK/VTI Images (*.vtk *.vti);;All Files (*.*)");
|
||||
|
||||
if (fileName.isEmpty()) return;
|
||||
|
||||
QFileInfo info(fileName);
|
||||
QString ext = info.suffix().toLower();
|
||||
|
||||
if (ext == "vti" || ext == "vtk") {
|
||||
auto* obj = uLib::ObjectFactory::Instance().Create("VoxImage");
|
||||
auto* vox = dynamic_cast<uLib::Abstract::VoxImage*>(obj);
|
||||
if (vox) {
|
||||
bool success = false;
|
||||
if (ext == "vti") {
|
||||
success = vox->ImportFromVti(fileName.toStdString().c_str());
|
||||
} else {
|
||||
success = vox->ImportFromVtk(fileName.toStdString().c_str());
|
||||
}
|
||||
|
||||
if (success) {
|
||||
obj->SetInstanceName(info.fileName().toStdString());
|
||||
m_context->AddObject(obj);
|
||||
} else {
|
||||
delete obj;
|
||||
}
|
||||
} else {
|
||||
delete obj;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainPanel::onSave() {
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
#include <QSignalBlocker>
|
||||
#include <QRegularExpression>
|
||||
#include <QRegularExpressionMatch>
|
||||
#include <QComboBox>
|
||||
#include <QCheckBox>
|
||||
#include "Vtk/uLibVtkInterface.h"
|
||||
#include "Math/Units.h"
|
||||
#include "Math/Dense.h"
|
||||
@@ -17,10 +19,12 @@ PropertyWidgetBase::PropertyWidgetBase(PropertyBase* prop, QWidget* parent)
|
||||
m_Label->setMinimumWidth(100);
|
||||
m_Layout->addWidget(m_Label);
|
||||
}
|
||||
PropertyWidgetBase::~PropertyWidgetBase() {}
|
||||
PropertyWidgetBase::~PropertyWidgetBase() {
|
||||
m_Connection.disconnect();
|
||||
}
|
||||
|
||||
// Helper for unit parsing
|
||||
static double parseWithUnits(const QString& text, double* factorOut = nullptr, QString* suffixOut = nullptr) {
|
||||
double parseWithUnits(const QString& text, double* factorOut, QString* suffixOut) {
|
||||
static QRegularExpression re("^\\s*([-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?)\\s*(_?[a-zA-Z]+)?\\s*$");
|
||||
QRegularExpressionMatch match = re.match(text);
|
||||
if (!match.hasMatch()) return 0.0;
|
||||
@@ -57,14 +61,23 @@ static double parseWithUnits(const QString& text, double* factorOut = nullptr, Q
|
||||
}
|
||||
|
||||
// UnitLineEdit implementation
|
||||
UnitLineEdit::UnitLineEdit(QWidget* parent) : QLineEdit(parent), m_Value(0), m_Factor(1.0), m_Suffix("mm"), m_IsInteger(false) {
|
||||
UnitLineEdit::UnitLineEdit(QWidget* parent) : QLineEdit(parent), m_Value(0), m_Factor(1.0), m_Suffix(""), m_IsInteger(false) {
|
||||
connect(this, &QLineEdit::editingFinished, this, &UnitLineEdit::onEditingFinished);
|
||||
}
|
||||
|
||||
void UnitLineEdit::setUnits(const QString& suffix, double factor) {
|
||||
m_Suffix = suffix;
|
||||
m_Factor = factor;
|
||||
updateText();
|
||||
}
|
||||
|
||||
void UnitLineEdit::setValue(double val) {
|
||||
if (m_Value != val) {
|
||||
m_Value = val;
|
||||
// Initial heuristic for unit if it was mm and value becomes large
|
||||
// Suffix heuristic ONLY if it was mm and no explicit unit was given?
|
||||
// Actually, if m_Suffix is empty or we have a specific one, we should respect it.
|
||||
// The original code had a heuristic, but it's better to let property decide.
|
||||
// Let's keep it ONLY if m_Suffix was mm (legacy behavior)
|
||||
if (!m_IsInteger && m_Suffix == "mm" && std::abs(val) >= 1000.0) { m_Suffix = "m"; m_Factor = CLHEP::meter; }
|
||||
updateText();
|
||||
}
|
||||
@@ -91,12 +104,21 @@ void UnitLineEdit::onEditingFinished() {
|
||||
|
||||
void UnitLineEdit::updateText() {
|
||||
QSignalBlocker blocker(this);
|
||||
QString s;
|
||||
if (m_IsInteger) {
|
||||
setText(QString::number((int)m_Value));
|
||||
s = QString::number((int)m_Value);
|
||||
if (s.isEmpty()) s = "0";
|
||||
} else {
|
||||
double displayVal = m_Value / m_Factor;
|
||||
setText(QString::number(displayVal, 'g', 6) + " " + m_Suffix);
|
||||
s = QString::number(displayVal, 'g', 6);
|
||||
if (!s.contains('.') && !s.contains('e')) {
|
||||
s += ".0";
|
||||
}
|
||||
}
|
||||
if (!m_Suffix.isEmpty()) {
|
||||
s += " " + m_Suffix;
|
||||
}
|
||||
setText(s);
|
||||
}
|
||||
|
||||
void UnitLineEdit::setIntegerOnly(bool integerOnly) {
|
||||
@@ -107,10 +129,16 @@ void UnitLineEdit::setIntegerOnly(bool integerOnly) {
|
||||
DoublePropertyWidget::DoublePropertyWidget(Property<double>* prop, QWidget* parent)
|
||||
: PropertyWidgetBase(prop, parent), m_Prop(prop) {
|
||||
m_Edit = new UnitLineEdit(this);
|
||||
m_Layout->addWidget(m_Edit, 1);
|
||||
QString units = QString::fromStdString(prop->GetUnits());
|
||||
if (!units.isEmpty()) {
|
||||
double factor = 1.0;
|
||||
parseWithUnits("1 " + units, &factor);
|
||||
m_Edit->setUnits(units, factor);
|
||||
}
|
||||
m_Edit->setValue(prop->Get());
|
||||
m_Layout->addWidget(m_Edit, 1);
|
||||
connect(m_Edit, &UnitLineEdit::valueManualChanged, [this](double val){ m_Prop->Set(val); });
|
||||
uLib::Object::connect(m_Prop, &Property<double>::PropertyChanged, [this](){
|
||||
m_Connection = uLib::Object::connect(m_Prop, &Property<double>::PropertyChanged, [this](){
|
||||
m_Edit->setValue(m_Prop->Get());
|
||||
});
|
||||
}
|
||||
@@ -118,10 +146,16 @@ DoublePropertyWidget::DoublePropertyWidget(Property<double>* prop, QWidget* pare
|
||||
FloatPropertyWidget::FloatPropertyWidget(Property<float>* prop, QWidget* parent)
|
||||
: PropertyWidgetBase(prop, parent), m_Prop(prop) {
|
||||
m_Edit = new UnitLineEdit(this);
|
||||
m_Layout->addWidget(m_Edit, 1);
|
||||
QString units = QString::fromStdString(prop->GetUnits());
|
||||
if (!units.isEmpty()) {
|
||||
double factor = 1.0;
|
||||
parseWithUnits("1 " + units, &factor);
|
||||
m_Edit->setUnits(units, factor);
|
||||
}
|
||||
m_Edit->setValue(prop->Get());
|
||||
m_Layout->addWidget(m_Edit, 1);
|
||||
connect(m_Edit, &UnitLineEdit::valueManualChanged, [this](double val){ m_Prop->Set((float)val); });
|
||||
uLib::Object::connect(m_Prop, &Property<float>::PropertyChanged, [this](){
|
||||
m_Connection = uLib::Object::connect(m_Prop, &Property<float>::PropertyChanged, [this](){
|
||||
m_Edit->setValue((double)m_Prop->Get());
|
||||
});
|
||||
}
|
||||
@@ -130,10 +164,16 @@ IntPropertyWidget::IntPropertyWidget(Property<int>* prop, QWidget* parent)
|
||||
: PropertyWidgetBase(prop, parent), m_Prop(prop) {
|
||||
m_Edit = new UnitLineEdit(this);
|
||||
m_Edit->setIntegerOnly(true);
|
||||
m_Layout->addWidget(m_Edit, 1);
|
||||
QString units = QString::fromStdString(prop->GetUnits());
|
||||
if (!units.isEmpty()) {
|
||||
double factor = 1.0;
|
||||
parseWithUnits("1 " + units, &factor);
|
||||
m_Edit->setUnits(units, factor);
|
||||
}
|
||||
m_Edit->setValue(prop->Get());
|
||||
m_Layout->addWidget(m_Edit, 1);
|
||||
connect(m_Edit, &UnitLineEdit::valueManualChanged, [this](double val){ m_Prop->Set((int)val); });
|
||||
uLib::Object::connect(m_Prop, &Property<int>::PropertyChanged, [this](){
|
||||
m_Connection = uLib::Object::connect(m_Prop, &Property<int>::PropertyChanged, [this](){
|
||||
m_Edit->setValue((double)m_Prop->Get());
|
||||
});
|
||||
}
|
||||
@@ -144,7 +184,7 @@ BoolPropertyWidget::BoolPropertyWidget(Property<bool>* prop, QWidget* parent)
|
||||
m_CheckBox->setChecked(prop->Get());
|
||||
m_Layout->addWidget(m_CheckBox, 1);
|
||||
connect(m_CheckBox, &QCheckBox::toggled, [this](bool val){ if (m_Prop->Get() != val) m_Prop->Set(val); });
|
||||
uLib::Object::connect(m_Prop, &Property<bool>::PropertyChanged, [this](){
|
||||
m_Connection = uLib::Object::connect(m_Prop, &Property<bool>::PropertyChanged, [this](){
|
||||
if (m_CheckBox->isChecked() != m_Prop->Get()) {
|
||||
QSignalBlocker blocker(m_CheckBox);
|
||||
m_CheckBox->setChecked(m_Prop->Get());
|
||||
@@ -158,11 +198,11 @@ StringPropertyWidget::StringPropertyWidget(Property<std::string>* prop, QWidget*
|
||||
m_LineEdit = new QLineEdit(this);
|
||||
m_LineEdit->setText(QString::fromStdString(prop->Get()));
|
||||
m_Layout->addWidget(m_LineEdit, 1);
|
||||
connect(m_LineEdit, &QLineEdit::editingFinished, [this](){
|
||||
connect(m_LineEdit, &QLineEdit::editingFinished, [this](){
|
||||
std::string val = m_LineEdit->text().toStdString();
|
||||
if (m_Prop->Get() != val) m_Prop->Set(val);
|
||||
if (m_Prop->Get() != val) m_Prop->Set(val);
|
||||
});
|
||||
uLib::Object::connect(m_Prop, &Property<std::string>::PropertyChanged, [this](){
|
||||
m_Connection = uLib::Object::connect(m_Prop, &Property<std::string>::PropertyChanged, [this](){
|
||||
if (m_LineEdit->text().toStdString() != m_Prop->Get()) {
|
||||
QSignalBlocker blocker(m_LineEdit);
|
||||
m_LineEdit->setText(QString::fromStdString(m_Prop->Get()));
|
||||
@@ -171,6 +211,37 @@ StringPropertyWidget::StringPropertyWidget(Property<std::string>* prop, QWidget*
|
||||
}
|
||||
StringPropertyWidget::~StringPropertyWidget() {}
|
||||
|
||||
class EnumPropertyWidget : public PropertyWidgetBase {
|
||||
PropertyBase* m_Prop;
|
||||
QComboBox* m_Combo;
|
||||
public:
|
||||
EnumPropertyWidget(PropertyBase* prop, QWidget* parent)
|
||||
: PropertyWidgetBase(prop, parent), m_Prop(prop) {
|
||||
m_Combo = new QComboBox(this);
|
||||
const auto& labels = prop->GetEnumLabels();
|
||||
for (const auto& label : labels) {
|
||||
m_Combo->addItem(QString::fromStdString(label));
|
||||
}
|
||||
|
||||
// Get initial value
|
||||
if (auto* p = dynamic_cast<Property<int>*>(prop)) {
|
||||
m_Combo->setCurrentIndex(p->Get());
|
||||
connect(m_Combo, &QComboBox::currentIndexChanged, [p](int index){
|
||||
p->Set(index);
|
||||
});
|
||||
// Store connection in base m_Connection so it's auto-disconnected on destruction.
|
||||
m_Connection = uLib::Object::connect(p, &Property<int>::PropertyChanged, [this, p](){
|
||||
if (m_Combo->currentIndex() != p->Get()) {
|
||||
QSignalBlocker blocker(m_Combo);
|
||||
m_Combo->setCurrentIndex(p->Get());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
m_Layout->addWidget(m_Combo, 1);
|
||||
}
|
||||
};
|
||||
|
||||
PropertyEditor::PropertyEditor(QWidget* parent) : QWidget(parent), m_Object(nullptr) {
|
||||
m_MainLayout = new QVBoxLayout(this);
|
||||
m_MainLayout->setContentsMargins(0, 0, 0, 0);
|
||||
@@ -197,6 +268,11 @@ PropertyEditor::PropertyEditor(QWidget* parent) : QWidget(parent), m_Object(null
|
||||
registerFactory<std::string>([](PropertyBase* p, QWidget* parent){
|
||||
return new StringPropertyWidget(static_cast<Property<std::string>*>(p), parent);
|
||||
});
|
||||
|
||||
// Register EnumProperty specifically (needs to check type since it holds Property<int> but is EnumProperty)
|
||||
m_Factories[std::type_index(typeid(EnumProperty))] = [](PropertyBase* p, QWidget* parent) {
|
||||
return new EnumPropertyWidget(p, parent);
|
||||
};
|
||||
|
||||
// Vector Registration
|
||||
registerFactory<Vector2i>([](PropertyBase* p, QWidget* parent){ return new VectorPropertyWidget<Vector2i, 2>(static_cast<Property<Vector2i>*>(p), parent); });
|
||||
@@ -230,11 +306,22 @@ void PropertyEditor::setObject(::uLib::Object* obj, bool displayOnly) {
|
||||
}
|
||||
|
||||
for (auto* prop : *props) {
|
||||
// Priority 1: Check if it provides enum labels
|
||||
if (!prop->GetEnumLabels().empty()) {
|
||||
m_ContainerLayout->addWidget(new EnumPropertyWidget(prop, m_Container));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Priority 2: Standard factory lookup
|
||||
auto it = m_Factories.find(prop->GetTypeIndex());
|
||||
if (it != m_Factories.end()) {
|
||||
QWidget* widget = it->second(prop, m_Container);
|
||||
m_ContainerLayout->addWidget(widget);
|
||||
} else {
|
||||
// Debug info for unknown types
|
||||
std::cout << "PropertyEditor: No factory for " << prop->GetName()
|
||||
<< " (Type: " << prop->GetTypeName() << ")" << std::endl;
|
||||
|
||||
QWidget* fallback = new PropertyWidgetBase(prop, m_Container);
|
||||
fallback->layout()->addWidget(new QLabel("(Read-only: " + QString::fromStdString(prop->GetValueAsString()) + ")"));
|
||||
m_ContainerLayout->addWidget(fallback);
|
||||
|
||||
@@ -14,11 +14,14 @@
|
||||
|
||||
#include "Core/Property.h"
|
||||
#include "Core/Object.h"
|
||||
#include "Core/Signal.h"
|
||||
#include "Math/Dense.h"
|
||||
|
||||
namespace uLib {
|
||||
namespace Qt {
|
||||
|
||||
double parseWithUnits(const QString& text, double* factorOut = nullptr, QString* suffixOut = nullptr);
|
||||
|
||||
class PropertyWidgetBase : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
@@ -30,6 +33,9 @@ protected:
|
||||
PropertyBase* m_BaseProperty;
|
||||
QHBoxLayout* m_Layout;
|
||||
QLabel* m_Label;
|
||||
// Stores the uLib signal connection so it can be disconnected on destruction,
|
||||
// preventing use-after-free when PropertyEditor::clear() deletes widgets.
|
||||
Connection m_Connection;
|
||||
};
|
||||
|
||||
class UnitLineEdit : public QLineEdit {
|
||||
@@ -37,6 +43,7 @@ class UnitLineEdit : public QLineEdit {
|
||||
public:
|
||||
UnitLineEdit(QWidget* parent = nullptr);
|
||||
void setValue(double val);
|
||||
void setUnits(const QString& suffix, double factor = 1.0);
|
||||
double getValue() const { return m_Value; }
|
||||
void setIntegerOnly(bool b);
|
||||
|
||||
@@ -86,11 +93,19 @@ class VectorPropertyWidget : public PropertyWidgetBase {
|
||||
public:
|
||||
VectorPropertyWidget(Property<VecT>* prop, QWidget* parent = nullptr)
|
||||
: PropertyWidgetBase(prop, parent), m_Prop(prop) {
|
||||
QString units = QString::fromStdString(prop->GetUnits());
|
||||
double factor = 1.0;
|
||||
if (!units.isEmpty()) {
|
||||
parseWithUnits("1 " + units, &factor);
|
||||
}
|
||||
for (int i = 0; i < Size; ++i) {
|
||||
m_Edits[i] = new UnitLineEdit(this);
|
||||
if (std::is_integral<typename VecT::Scalar>::value) {
|
||||
m_Edits[i]->setIntegerOnly(true);
|
||||
}
|
||||
if (!units.isEmpty()) {
|
||||
m_Edits[i]->setUnits(units, factor);
|
||||
}
|
||||
m_Layout->addWidget(m_Edits[i], 1);
|
||||
|
||||
connect(m_Edits[i], &UnitLineEdit::valueManualChanged, [this, i](double val){
|
||||
@@ -100,10 +115,11 @@ public:
|
||||
});
|
||||
}
|
||||
updateEdits();
|
||||
uLib::Object::connect(m_Prop, &Property<VecT>::PropertyChanged, [this](){
|
||||
m_Connection = uLib::Object::connect(m_Prop, &Property<VecT>::PropertyChanged, [this](){
|
||||
updateEdits();
|
||||
});
|
||||
}
|
||||
~VectorPropertyWidget() { m_Connection.disconnect(); }
|
||||
|
||||
private:
|
||||
void updateEdits() {
|
||||
|
||||
BIN
assets/exmaples/vtk/2026_03_24_C1_Prod11_test_img_40_trim55505_scale1.00_sigma1.0.vtk
(Stored with Git LFS)
Normal file
BIN
assets/exmaples/vtk/2026_03_24_C1_Prod11_test_img_40_trim55505_scale1.00_sigma1.0.vtk
(Stored with Git LFS)
Normal file
Binary file not shown.
@@ -88,6 +88,16 @@ const std::vector<PropertyBase*>& Object::GetProperties() const {
|
||||
return d->m_Properties;
|
||||
}
|
||||
|
||||
PropertyBase* Object::GetProperty(const std::string& name) const {
|
||||
for (auto* p : d->m_Properties) {
|
||||
if (p->GetName() == name) return p;
|
||||
}
|
||||
for (auto* p : d->m_DynamicProperties) {
|
||||
if (p->GetName() == name) return p;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// In Object.h, the template serialize needs to be updated to call property serialization.
|
||||
// However, since Object::serialize is a template in the header, we might need a helper here.
|
||||
|
||||
|
||||
@@ -93,6 +93,7 @@ public:
|
||||
void RegisterProperty(PropertyBase* prop);
|
||||
void RegisterDynamicProperty(PropertyBase* prop);
|
||||
const std::vector<PropertyBase*>& GetProperties() const;
|
||||
PropertyBase* GetProperty(const std::string& name) const;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// PARAMETERS //
|
||||
|
||||
@@ -23,6 +23,12 @@ public:
|
||||
virtual const char* GetTypeName() const = 0;
|
||||
virtual std::string GetValueAsString() const = 0;
|
||||
virtual std::type_index GetTypeIndex() const = 0; // Added
|
||||
virtual const std::string& GetUnits() const = 0;
|
||||
virtual void SetUnits(const std::string& units) = 0;
|
||||
virtual const std::vector<std::string>& GetEnumLabels() const {
|
||||
static std::vector<std::string> empty;
|
||||
return empty;
|
||||
}
|
||||
|
||||
// Signal support
|
||||
signals:
|
||||
@@ -45,16 +51,16 @@ template <typename T>
|
||||
class Property : public PropertyBase {
|
||||
public:
|
||||
// PROXY: Use an existing variable as back-end storage
|
||||
Property(Object* owner, const std::string& name, T* valuePtr)
|
||||
: m_owner(owner), m_name(name), m_value(valuePtr), m_own(false) {
|
||||
Property(Object* owner, const std::string& name, T* valuePtr, const std::string& units = "")
|
||||
: m_owner(owner), m_name(name), m_units(units), m_value(valuePtr), m_own(false) {
|
||||
if (m_owner) {
|
||||
m_owner->RegisterProperty(this);
|
||||
}
|
||||
}
|
||||
|
||||
// MANAGED: Create and own internal storage
|
||||
Property(Object* owner, const std::string& name, const T& defaultValue = T())
|
||||
: m_owner(owner), m_name(name), m_value(new T(defaultValue)), m_own(true) {
|
||||
Property(Object* owner, const std::string& name, const T& defaultValue = T(), const std::string& units = "")
|
||||
: m_owner(owner), m_name(name), m_units(units), m_value(new T(defaultValue)), m_own(true) {
|
||||
if (m_owner) {
|
||||
m_owner->RegisterProperty(this);
|
||||
}
|
||||
@@ -68,6 +74,8 @@ public:
|
||||
virtual const std::string& GetName() const override { return m_name; }
|
||||
virtual const char* GetTypeName() const override { return typeid(T).name(); }
|
||||
virtual std::type_index GetTypeIndex() const override { return std::type_index(typeid(T)); }
|
||||
virtual const std::string& GetUnits() const override { return m_units; }
|
||||
virtual void SetUnits(const std::string& units) override { m_units = units; }
|
||||
|
||||
|
||||
std::string GetValueAsString() const override {
|
||||
@@ -118,6 +126,7 @@ public:
|
||||
|
||||
private:
|
||||
std::string m_name;
|
||||
std::string m_units;
|
||||
T* m_value;
|
||||
bool m_own;
|
||||
Object* m_owner;
|
||||
@@ -135,6 +144,22 @@ typedef Property<float> FloatProperty;
|
||||
typedef Property<double> DoubleProperty;
|
||||
typedef Property<Bool_t> BoolProperty;
|
||||
|
||||
/**
|
||||
* @brief Property specialized for enumerations, providing labels for GUI representations.
|
||||
*/
|
||||
class EnumProperty : public Property<int> {
|
||||
public:
|
||||
EnumProperty(Object* owner, const std::string& name, int* valuePtr, const std::vector<std::string>& labels, const std::string& units = "")
|
||||
: Property<int>(owner, name, valuePtr, units), m_Labels(labels) {}
|
||||
|
||||
const std::vector<std::string>& GetEnumLabels() const override { return m_Labels; }
|
||||
const char* GetTypeName() const override { return "Enum"; }
|
||||
virtual std::type_index GetTypeIndex() const override { return std::type_index(typeid(EnumProperty)); }
|
||||
|
||||
private:
|
||||
std::vector<std::string> m_Labels;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Macro to simplify property declaration within a class.
|
||||
* Usage: ULIB_PROPERTY(float, Width, 1.0f)
|
||||
@@ -188,10 +213,16 @@ public:
|
||||
template<class T>
|
||||
void save_override(const boost::serialization::hrp<T> &t) {
|
||||
if (m_Object) {
|
||||
// We use const_cast because we are just creating a proxy to the member
|
||||
m_Object->RegisterDynamicProperty(
|
||||
new Property<T>(m_Object, t.name(), &const_cast<boost::serialization::hrp<T>&>(t).value())
|
||||
);
|
||||
Property<T>* p = new Property<T>(m_Object, t.name(), &const_cast<boost::serialization::hrp<T>&>(t).value(), t.units() ? t.units() : "");
|
||||
m_Object->RegisterDynamicProperty(p);
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void save_override(const boost::serialization::hrp_enum<T> &t) {
|
||||
if (m_Object) {
|
||||
EnumProperty* p = new EnumProperty(m_Object, t.name(), (int*)&const_cast<boost::serialization::hrp_enum<T>&>(t).value(), t.labels(), t.units() ? t.units() : "");
|
||||
m_Object->RegisterDynamicProperty(p);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -75,12 +75,14 @@ template <class T> struct access2 {};
|
||||
template <class T>
|
||||
class hrp : public boost::serialization::wrapper_traits<hrp<T>> {
|
||||
const char *m_name;
|
||||
const char *m_units;
|
||||
T &m_value;
|
||||
|
||||
public:
|
||||
explicit hrp(const char *name_, T &t) : m_name(name_), m_value(t) {}
|
||||
explicit hrp(const char *name_, T &t, const char* units_ = nullptr) : m_name(name_), m_units(units_), m_value(t) {}
|
||||
|
||||
const char *name() const { return this->m_name; }
|
||||
const char *units() const { return this->m_units; }
|
||||
T &value() { return this->m_value; }
|
||||
const T &const_value() const { return this->m_value; }
|
||||
|
||||
@@ -98,11 +100,46 @@ public:
|
||||
};
|
||||
|
||||
template <class T>
|
||||
inline hrp<T> make_hrp(const char *name, T &t) {
|
||||
return hrp<T>(name, t);
|
||||
inline hrp<T> make_hrp(const char *name, T &t, const char* units = nullptr) {
|
||||
return hrp<T>(name, t, units);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
class hrp_enum : public boost::serialization::wrapper_traits<hrp_enum<T>> {
|
||||
const char *m_name;
|
||||
const char *m_units;
|
||||
T &m_value;
|
||||
std::vector<std::string> m_labels;
|
||||
|
||||
public:
|
||||
explicit hrp_enum(const char *name_, T &t, const std::vector<std::string>& labels, const char* units_ = nullptr)
|
||||
: m_name(name_), m_units(units_), m_value(t), m_labels(labels) {}
|
||||
|
||||
const char *name() const { return this->m_name; }
|
||||
const char *units() const { return this->m_units; }
|
||||
T &value() { return this->m_value; }
|
||||
const std::vector<std::string>& labels() const { return m_labels; }
|
||||
|
||||
BOOST_SERIALIZATION_SPLIT_MEMBER()
|
||||
|
||||
template <class Archivex>
|
||||
void save(Archivex &ar, const unsigned int /* version */) const {
|
||||
ar << boost::serialization::make_nvp(m_name, m_value);
|
||||
}
|
||||
|
||||
template <class Archivex>
|
||||
void load(Archivex &ar, const unsigned int /* version */) {
|
||||
ar >> boost::serialization::make_nvp(m_name, m_value);
|
||||
}
|
||||
};
|
||||
|
||||
template <class T>
|
||||
inline hrp_enum<T> make_hrp_enum(const char *name, T &t, const std::vector<std::string>& labels, const char* units = nullptr) {
|
||||
return hrp_enum<T>(name, t, labels, units);
|
||||
}
|
||||
|
||||
#define HRP(name) boost::serialization::make_hrp(BOOST_PP_STRINGIZE(name), name)
|
||||
#define HRPU(name, units) boost::serialization::make_hrp(BOOST_PP_STRINGIZE(name), name, units)
|
||||
|
||||
} // namespace serialization
|
||||
} // namespace boost
|
||||
|
||||
@@ -126,9 +126,11 @@ vtkVoxImage::vtkVoxImage(Content &content)
|
||||
: m_Content(content), m_Actor(vtkVolume::New()),
|
||||
m_Image(vtkImageData::New()), m_Outline(vtkCubeSource::New()),
|
||||
m_OutlineActor(vtkActor::New()),
|
||||
m_Reader(NULL), m_Writer(NULL), writer_factor(1.E6) {
|
||||
m_Reader(NULL), m_Writer(NULL), writer_factor(1.E6),
|
||||
m_Window(40/1.E6), m_Level(20/1.E6), m_ShadingPreset(0) {
|
||||
GetContent();
|
||||
InstallPipe();
|
||||
ULIB_ACTIVATE_DISPLAY_PROPERTIES;
|
||||
}
|
||||
|
||||
vtkVoxImage::~vtkVoxImage() {
|
||||
@@ -199,14 +201,15 @@ void vtkVoxImage::ReadFromXMLFile(const char *fname) {
|
||||
}
|
||||
|
||||
void vtkVoxImage::setShadingPreset(int blendType) {
|
||||
m_ShadingPreset = blendType;
|
||||
vtkSmartVolumeMapper *mapper = (vtkSmartVolumeMapper *)m_Actor->GetMapper();
|
||||
vtkVolumeProperty *property = m_Actor->GetProperty();
|
||||
|
||||
static vtkColorTransferFunction *colorFun = vtkColorTransferFunction::New();
|
||||
static vtkPiecewiseFunction *opacityFun = vtkPiecewiseFunction::New();
|
||||
|
||||
float window = 40 / writer_factor;
|
||||
float level = 20 / writer_factor;
|
||||
float window = m_Window;
|
||||
float level = m_Level;
|
||||
|
||||
property->SetColor(colorFun);
|
||||
property->SetScalarOpacity(opacityFun);
|
||||
@@ -281,13 +284,24 @@ void vtkVoxImage::SetRepresentation(Representation mode) {
|
||||
m_OutlineActor->SetVisibility(1);
|
||||
} else if (mode == Surface) {
|
||||
m_Actor->SetVisibility(1);
|
||||
m_OutlineActor->SetVisibility(0);
|
||||
m_OutlineActor->SetVisibility(1); // Keep outline visible as boundary
|
||||
} else {
|
||||
Puppet::SetRepresentation(mode);
|
||||
}
|
||||
}
|
||||
|
||||
void vtkVoxImage::serialize_display(uLib::Archive::display_properties_archive & ar, const unsigned int version) {
|
||||
// Call base class if it has display properties
|
||||
Puppet::serialize_display(ar, version);
|
||||
|
||||
// Use the member variables if they are available
|
||||
ar & boost::serialization::make_hrp("Window", m_Window);
|
||||
ar & boost::serialization::make_hrp("Level", m_Level);
|
||||
ar & boost::serialization::make_hrp_enum("Shading", m_ShadingPreset, {"MIP", "Composite", "Composite Shaded", "MIP Bone", "MIP Hot", "Additive"});
|
||||
}
|
||||
|
||||
void vtkVoxImage::Update() {
|
||||
setShadingPreset(m_ShadingPreset);
|
||||
m_Actor->Update();
|
||||
m_Outline->SetBounds(m_Image->GetBounds());
|
||||
m_Outline->Update();
|
||||
|
||||
@@ -65,7 +65,8 @@ public:
|
||||
void setShadingPreset(int blendType = 2);
|
||||
void SetRepresentation(Representation mode);
|
||||
|
||||
void Update();
|
||||
void Update() override;
|
||||
void serialize_display(uLib::Archive::display_properties_archive & ar, const unsigned int version = 0) override;
|
||||
|
||||
protected:
|
||||
void InstallPipe();
|
||||
@@ -84,6 +85,7 @@ private:
|
||||
|
||||
float m_Window;
|
||||
float m_Level;
|
||||
int m_ShadingPreset;
|
||||
};
|
||||
|
||||
} // namespace Vtk
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
#include <vtkPolyData.h>
|
||||
#include <vtkFeatureEdges.h>
|
||||
#include <vtkTransform.h>
|
||||
#include <vtkRenderWindow.h>
|
||||
|
||||
#include "uLibVtkInterface.h"
|
||||
#include "vtkHandlerWidget.h"
|
||||
@@ -87,12 +88,17 @@ public:
|
||||
m_Assembly(vtkSmartPointer<vtkAssembly>::New()),
|
||||
m_ShowBoundingBox(false),
|
||||
m_ShowScaleMeasures(false),
|
||||
m_Representation(-1),
|
||||
m_Representation(Puppet::Surface),
|
||||
m_Opacity(-1.0),
|
||||
m_Selectable(true),
|
||||
m_Selected(false)
|
||||
m_Selected(false),
|
||||
m_Visibility(true),
|
||||
m_Dragable(true)
|
||||
{
|
||||
m_Color[0] = m_Color[1] = m_Color[2] = -1.0;
|
||||
m_Position[0] = m_Position[1] = m_Position[2] = 0.0;
|
||||
m_Orientation[0] = m_Orientation[1] = m_Orientation[2] = 0.0;
|
||||
m_Scale[0] = m_Scale[1] = m_Scale[2] = 1.0;
|
||||
}
|
||||
|
||||
~PuppetData() {
|
||||
@@ -110,25 +116,49 @@ public:
|
||||
|
||||
bool m_ShowBoundingBox;
|
||||
bool m_ShowScaleMeasures;
|
||||
|
||||
int m_Representation;
|
||||
double m_Color[3];
|
||||
double m_Opacity;
|
||||
|
||||
bool m_Selectable;
|
||||
bool m_Selected;
|
||||
bool m_Visibility;
|
||||
bool m_Dragable;
|
||||
double m_Position[3];
|
||||
double m_Orientation[3];
|
||||
double m_Scale[3];
|
||||
|
||||
void ApplyAppearance(vtkProp *p) {
|
||||
p->SetVisibility(m_Visibility);
|
||||
p->SetPickable(m_Selectable);
|
||||
p->SetDragable(m_Dragable);
|
||||
|
||||
vtkActor *actor = vtkActor::SafeDownCast(p);
|
||||
if (!actor) return;
|
||||
if (actor) {
|
||||
if (m_Representation != -1) {
|
||||
if (m_Representation == Puppet::SurfaceWithEdges) {
|
||||
actor->GetProperty()->SetRepresentation(VTK_SURFACE);
|
||||
actor->GetProperty()->SetEdgeVisibility(1);
|
||||
} else {
|
||||
actor->GetProperty()->SetRepresentation(m_Representation);
|
||||
actor->GetProperty()->SetEdgeVisibility(0);
|
||||
}
|
||||
}
|
||||
if (m_Color[0] != -1.0) {
|
||||
actor->GetProperty()->SetColor(m_Color);
|
||||
}
|
||||
|
||||
if (m_Representation != -1) {
|
||||
actor->GetProperty()->SetRepresentation(m_Representation);
|
||||
if (m_Opacity != -1.0) {
|
||||
actor->GetProperty()->SetOpacity(m_Opacity);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_Color[0] != -1.0) {
|
||||
actor->GetProperty()->SetColor(m_Color);
|
||||
}
|
||||
|
||||
if (m_Opacity != -1.0) {
|
||||
actor->GetProperty()->SetOpacity(m_Opacity);
|
||||
// Handle transformation if it's a Prop3D
|
||||
if (auto* p3d = vtkProp3D::SafeDownCast(p)) {
|
||||
// NOTE: Usually managed by Puppet::Update from model, but here for direct prop manipulation
|
||||
// p3d->SetPosition(m_Position);
|
||||
// p3d->SetOrientation(m_Orientation);
|
||||
// p3d->SetScale(m_Scale);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,9 +218,6 @@ public:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool m_Selectable;
|
||||
bool m_Selected;
|
||||
};
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
@@ -226,6 +253,21 @@ void Puppet::SetProp(vtkProp *prop)
|
||||
pd->m_Assembly->AddPart(p3d);
|
||||
}
|
||||
pd->ApplyAppearance(prop);
|
||||
|
||||
// For the first actor added, seed the tracked display values from the VTK
|
||||
// actor's current state so the display properties panel shows meaningful
|
||||
// initial values instead of the -1 "not-overriding" sentinels.
|
||||
if (pd->m_Assembly->GetParts()->GetNumberOfItems() == 1) {
|
||||
if (auto* actor = vtkActor::SafeDownCast(prop)) {
|
||||
vtkProperty* vp = actor->GetProperty();
|
||||
if (pd->m_Representation < 0)
|
||||
pd->m_Representation = vp->GetRepresentation();
|
||||
if (pd->m_Opacity < 0)
|
||||
pd->m_Opacity = vp->GetOpacity();
|
||||
if (pd->m_Color[0] < 0)
|
||||
vp->GetColor(pd->m_Color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -370,13 +412,7 @@ void Puppet::ShowScaleMeasures(bool show)
|
||||
|
||||
void Puppet::SetRepresentation(Representation mode)
|
||||
{
|
||||
int rep = VTK_SURFACE;
|
||||
switch (mode) {
|
||||
case Points: rep = VTK_POINTS; break;
|
||||
case Wireframe: rep = VTK_WIREFRAME; break;
|
||||
case Surface: rep = VTK_SURFACE; break;
|
||||
}
|
||||
pd->m_Representation = rep;
|
||||
pd->m_Representation = static_cast<int>(mode);
|
||||
|
||||
vtkProp3DCollection *props = pd->m_Assembly->GetParts();
|
||||
props->InitTraversal();
|
||||
@@ -391,6 +427,10 @@ void Puppet::SetRepresentation(const char *mode)
|
||||
if (s == "points") SetRepresentation(Points);
|
||||
else if (s == "wireframe") SetRepresentation(Wireframe);
|
||||
else if (s == "shaded" || s == "surface") SetRepresentation(Surface);
|
||||
else if (s == "edges" || s == "surface+edges" || s == "surfacewithedges") SetRepresentation(SurfaceWithEdges);
|
||||
else if (s == "volume") SetRepresentation(Volume);
|
||||
else if (s == "outline") SetRepresentation(Outline);
|
||||
else if (s == "slice") SetRepresentation(Slice);
|
||||
}
|
||||
|
||||
void Puppet::SetColor(double r, double g, double b)
|
||||
@@ -453,6 +493,18 @@ bool Puppet::IsSelected() const
|
||||
|
||||
void Puppet::Update()
|
||||
{
|
||||
vtkProp* root = this->GetProp();
|
||||
if (root) {
|
||||
pd->ApplyAppearance(root);
|
||||
|
||||
// Apply transformation if it's a Prop3D
|
||||
if (auto* p3d = vtkProp3D::SafeDownCast(root)) {
|
||||
p3d->SetPosition(pd->m_Position);
|
||||
p3d->SetOrientation(pd->m_Orientation);
|
||||
p3d->SetScale(pd->m_Scale);
|
||||
}
|
||||
}
|
||||
|
||||
vtkProp3DCollection *props = pd->m_Assembly->GetParts();
|
||||
props->InitTraversal();
|
||||
for (int i = 0; i < props->GetNumberOfItems(); ++i) {
|
||||
@@ -473,6 +525,42 @@ void Puppet::Update()
|
||||
double* bounds = pd->m_Assembly->GetBounds();
|
||||
pd->m_CubeAxesActor->SetBounds(bounds);
|
||||
}
|
||||
|
||||
// Notify that the object has been updated (important for UI refresh)
|
||||
this->Object::Updated();
|
||||
|
||||
// Trigger immediate re-render of all connected viewports
|
||||
pd->m_Renderers->InitTraversal();
|
||||
for (int i = 0; i < pd->m_Renderers->GetNumberOfItems(); ++i) {
|
||||
if (auto* ren = pd->m_Renderers->GetNextItem()) {
|
||||
if (ren->GetRenderWindow()) ren->GetRenderWindow()->Render();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Puppet::SyncFromVtk()
|
||||
{
|
||||
vtkProp* root = this->GetProp();
|
||||
if (auto* p3d = vtkProp3D::SafeDownCast(root)) {
|
||||
double pos[3], ori[3], scale[3];
|
||||
p3d->GetPosition(pos);
|
||||
p3d->GetOrientation(ori);
|
||||
p3d->GetScale(scale);
|
||||
|
||||
// Update properties
|
||||
for (int i=0; i<3; ++i) {
|
||||
pd->m_Position[i] = pos[i];
|
||||
pd->m_Orientation[i] = ori[i];
|
||||
pd->m_Scale[i] = scale[i];
|
||||
}
|
||||
|
||||
// Get the properties from the object
|
||||
if (auto* propPos = this->GetProperty("Position")) propPos->Updated();
|
||||
if (auto* propOri = this->GetProperty("Orientation")) propOri->Updated();
|
||||
if (auto* propScale = this->GetProperty("Scale")) propScale->Updated();
|
||||
|
||||
this->Object::Updated();
|
||||
}
|
||||
}
|
||||
|
||||
void Puppet::ConnectInteractor(vtkRenderWindowInteractor *interactor)
|
||||
@@ -484,7 +572,21 @@ void Puppet::serialize_display(Archive::display_properties_archive & ar, const u
|
||||
ar & boost::serialization::make_hrp("ColorG", pd->m_Color[1]);
|
||||
ar & boost::serialization::make_hrp("ColorB", pd->m_Color[2]);
|
||||
ar & boost::serialization::make_hrp("Opacity", pd->m_Opacity);
|
||||
ar & boost::serialization::make_hrp("Representation", pd->m_Representation);
|
||||
ar & boost::serialization::make_hrp_enum("Representation", pd->m_Representation, {"Points", "Wireframe", "Surface", "SurfaceWithEdges", "Volume", "Outline", "Slice"});
|
||||
ar & boost::serialization::make_hrp("Visibility", pd->m_Visibility);
|
||||
ar & boost::serialization::make_hrp("Pickable", pd->m_Selectable);
|
||||
ar & boost::serialization::make_hrp("Dragable", pd->m_Dragable);
|
||||
|
||||
// Geometry knobs (caution: these might be overridden by internal matrices)
|
||||
ar & boost::serialization::make_hrp("PosX", pd->m_Position[0], "mm");
|
||||
ar & boost::serialization::make_hrp("PosY", pd->m_Position[1], "mm");
|
||||
ar & boost::serialization::make_hrp("PosZ", pd->m_Position[2], "mm");
|
||||
ar & boost::serialization::make_hrp("OriX", pd->m_Orientation[0], "deg");
|
||||
ar & boost::serialization::make_hrp("OriY", pd->m_Orientation[1], "deg");
|
||||
ar & boost::serialization::make_hrp("OriZ", pd->m_Orientation[2], "deg");
|
||||
ar & boost::serialization::make_hrp("ScaleX", pd->m_Scale[0]);
|
||||
ar & boost::serialization::make_hrp("ScaleY", pd->m_Scale[1]);
|
||||
ar & boost::serialization::make_hrp("ScaleZ", pd->m_Scale[2]);
|
||||
}
|
||||
|
||||
void Puppet::serialize(Archive::xml_oarchive & ar, const unsigned int v) { }
|
||||
|
||||
@@ -82,8 +82,9 @@ public:
|
||||
bool IsSelected() const;
|
||||
|
||||
virtual void Update();
|
||||
virtual void SyncFromVtk();
|
||||
|
||||
enum Representation { Points, Wireframe, Surface };
|
||||
enum Representation { Points = 0, Wireframe = 1, Surface = 2, SurfaceWithEdges = 3, Volume = 4, Outline = 5, Slice = 6 };
|
||||
void SetRepresentation(Representation mode);
|
||||
void SetRepresentation(const char *mode);
|
||||
|
||||
@@ -131,6 +132,17 @@ private:
|
||||
} // namespace Vtk
|
||||
} // namespace uLib
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
// DISPLAY PROPERTIES SERIALIZE
|
||||
// -------------------------------------------------------------------------- //
|
||||
|
||||
namespace uLib {
|
||||
@@ -148,9 +160,20 @@ public:
|
||||
template<class T>
|
||||
void save_override(const boost::serialization::hrp<T> &t) {
|
||||
if (m_Puppet) {
|
||||
m_Puppet->RegisterDisplayProperty(
|
||||
new uLib::Property<T>(m_Puppet, t.name(), &const_cast<boost::serialization::hrp<T>&>(t).value())
|
||||
);
|
||||
uLib::Property<T>* p = new uLib::Property<T>(m_Puppet, t.name(), &const_cast<boost::serialization::hrp<T>&>(t).value(), t.units() ? t.units() : "");
|
||||
m_Puppet->RegisterDisplayProperty(p);
|
||||
Vtk::Puppet* puppet = m_Puppet;
|
||||
uLib::Object::connect(p, &uLib::PropertyBase::Updated, [puppet](){ puppet->Update(); });
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void save_override(const boost::serialization::hrp_enum<T> &t) {
|
||||
if (m_Puppet) {
|
||||
uLib::EnumProperty* p = new uLib::EnumProperty(m_Puppet, t.name(), (int*)&const_cast<boost::serialization::hrp_enum<T>&>(t).value(), t.labels(), t.units() ? t.units() : "");
|
||||
m_Puppet->RegisterDisplayProperty(p);
|
||||
Vtk::Puppet* puppet = m_Puppet;
|
||||
uLib::Object::connect(p, &uLib::PropertyBase::Updated, [puppet](){ puppet->Update(); });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "Vtk/Math/vtkContainerBox.h"
|
||||
#include "Vtk/Math/vtkCylinder.h"
|
||||
#include "Vtk/Math/vtkAssembly.h"
|
||||
#include "Vtk/Math/vtkVoxImage.h"
|
||||
#include "HEP/Detectors/vtkDetectorChamber.h"
|
||||
|
||||
#include <vtkAssembly.h>
|
||||
@@ -116,6 +117,8 @@ Puppet* vtkObjectsContext::CreatePuppet(uLib::Object* obj) {
|
||||
return new vtkDetectorChamber(static_cast<uLib::DetectorChamber*>(obj));
|
||||
} else if (std::strcmp(className, "Cylinder") == 0) {
|
||||
return new vtkCylinder(static_cast<uLib::Cylinder*>(obj));
|
||||
} else if (std::strcmp(className, "VoxImage") == 0) {
|
||||
return new vtkVoxImage(*static_cast<uLib::Abstract::VoxImage*>(obj));
|
||||
} else if (std::strcmp(className, "Assembly") == 0) {
|
||||
return new Assembly(static_cast<uLib::Assembly*>(obj));
|
||||
}
|
||||
|
||||
@@ -101,6 +101,11 @@ void QViewport::onGridButtonClicked()
|
||||
SetGridVisible(m_GridButton->isChecked());
|
||||
}
|
||||
|
||||
void QViewport::OnSelectionChanged(Puppet* p)
|
||||
{
|
||||
emit puppetSelected(p);
|
||||
}
|
||||
|
||||
void QViewport::resizeEvent(QResizeEvent* event)
|
||||
{
|
||||
QWidget::resizeEvent(event);
|
||||
|
||||
@@ -30,6 +30,8 @@ namespace Vtk {
|
||||
*/
|
||||
class QViewport : public QWidget, public Viewport {
|
||||
Q_OBJECT
|
||||
signals:
|
||||
void puppetSelected(uLib::Vtk::Puppet* p);
|
||||
public:
|
||||
explicit QViewport(QWidget* parent = nullptr);
|
||||
virtual ~QViewport();
|
||||
@@ -42,6 +44,8 @@ public:
|
||||
virtual vtkRenderWindowInteractor* GetInteractor() override;
|
||||
QVTKOpenGLNativeWidget* GetWidget() { return m_VtkWidget; }
|
||||
|
||||
virtual void OnSelectionChanged(Puppet* p) override;
|
||||
|
||||
protected:
|
||||
virtual void resizeEvent(QResizeEvent* event) override;
|
||||
|
||||
|
||||
@@ -208,6 +208,7 @@ void Viewport::SetupPipeline(vtkRenderWindowInteractor* iren)
|
||||
auto* self = static_cast<Viewport*>(clientdata);
|
||||
for (auto* p : self->m_Puppets) {
|
||||
if (p->IsSelected()) {
|
||||
p->SyncFromVtk();
|
||||
p->Update();
|
||||
}
|
||||
}
|
||||
@@ -518,6 +519,7 @@ void Viewport::SelectPuppet(Puppet* prop)
|
||||
}
|
||||
|
||||
Render();
|
||||
OnSelectionChanged(prop);
|
||||
}
|
||||
|
||||
void Viewport::SetGridVisible(bool visible)
|
||||
|
||||
@@ -62,6 +62,7 @@ public:
|
||||
vtkCornerAnnotation* GetAnnotation();
|
||||
vtkCameraOrientationWidget* GetCameraWidget();
|
||||
void DisableHandler();
|
||||
virtual void OnSelectionChanged(Puppet* p) {}
|
||||
const std::vector<Puppet*>& getPuppets() const { return m_Puppets; }
|
||||
|
||||
// Grid control
|
||||
|
||||
Reference in New Issue
Block a user