8 Commits

Author SHA1 Message Date
AndreaRigoni
46c39bc26e add assembly to gcompose, not working yet 2026-03-27 16:55:26 +00:00
AndreaRigoni
171a07eb79 add min max def to properties 2026-03-27 15:46:16 +00:00
AndreaRigoni
fa7c0f670e fix display of cylinder 2026-03-27 15:23:59 +00:00
AndreaRigoni
e40cc77a5f fix numeric unit conversion in widget 2026-03-27 15:17:54 +00:00
AndreaRigoni
038c6f99f4 fixed most ( still units error ) 2026-03-27 15:02:17 +00:00
AndreaRigoni
93e5602562 transform properties 2026-03-27 02:43:30 +00:00
AndreaRigoni
09859e872c fix build 2026-03-27 01:49:27 +00:00
AndreaRigoni
2a6dcf02bd add properties groups 2026-03-26 23:13:43 +00:00
53 changed files with 1541 additions and 442 deletions

View File

@@ -0,0 +1,7 @@
---
trigger: always_on
---
build in build directory using always micromamba "mutom" env.
build with make flag -j$(nproc).

View File

@@ -21,7 +21,7 @@ endif()
project(uLib) project(uLib)
# CUDA Toolkit seems to be missing locally. Toggle ON if nvcc is made available. # CUDA Toolkit seems to be missing locally. Toggle ON if nvcc is made available.
option(USE_CUDA "Enable CUDA support" ON) option(USE_CUDA "Enable CUDA support" OFF)
if(USE_CUDA) if(USE_CUDA)
set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -allow-unsupported-compiler") set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -allow-unsupported-compiler")
set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} --expt-relaxed-constexpr") set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} --expt-relaxed-constexpr")
@@ -115,7 +115,7 @@ set(Boost_USE_MULTITHREADED ON)
set(Boost_USE_STATIC_RUNTIME OFF) set(Boost_USE_STATIC_RUNTIME OFF)
message(STATUS "CMAKE_PREFIX_PATH is ${CMAKE_PREFIX_PATH}") message(STATUS "CMAKE_PREFIX_PATH is ${CMAKE_PREFIX_PATH}")
find_package(HDF5 REQUIRED CONFIG) find_package(HDF5 REQUIRED)
find_package(Boost 1.45.0 COMPONENTS program_options serialization unit_test_framework REQUIRED) find_package(Boost 1.45.0 COMPONENTS program_options serialization unit_test_framework REQUIRED)
include_directories(${Boost_INCLUDE_DIRS}) include_directories(${Boost_INCLUDE_DIRS})

View File

@@ -4,6 +4,11 @@
#include <cxxabi.h> #include <cxxabi.h>
#include <functional> #include <functional>
#include "Core/Object.h" #include "Core/Object.h"
#include <QMimeData>
#include <QDataStream>
#include <QIODevice>
#include <vector>
#include <algorithm>
ContextModel::ContextModel(QObject* parent) ContextModel::ContextModel(QObject* parent)
: QAbstractItemModel(parent), m_rootContext(nullptr) {} : QAbstractItemModel(parent), m_rootContext(nullptr) {}
@@ -11,12 +16,16 @@ ContextModel::ContextModel(QObject* parent)
ContextModel::~ContextModel() {} ContextModel::~ContextModel() {}
void ContextModel::setContext(uLib::ObjectsContext* context) { void ContextModel::setContext(uLib::ObjectsContext* context) {
m_isReseting = true;
beginResetModel(); beginResetModel();
m_rootContext = context; m_rootContext = context;
if (m_rootContext) { if (m_rootContext) {
auto refresh = [this]() { auto refresh = [this]() {
if (this->m_isReseting) return;
this->m_isReseting = true;
this->beginResetModel(); this->beginResetModel();
this->endResetModel(); this->endResetModel();
this->m_isReseting = false;
}; };
uLib::Object::connect(m_rootContext, &uLib::Object::Updated, refresh); uLib::Object::connect(m_rootContext, &uLib::Object::Updated, refresh);
@@ -25,7 +34,6 @@ void ContextModel::setContext(uLib::ObjectsContext* context) {
refresh(); refresh();
}); });
uLib::Object::connect(m_rootContext, &uLib::ObjectsContext::ObjectRemoved, [this, refresh](uLib::Object* obj) { 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(); refresh();
}); });
@@ -35,6 +43,7 @@ void ContextModel::setContext(uLib::ObjectsContext* context) {
} }
} }
endResetModel(); endResetModel();
m_isReseting = false;
} }
QModelIndex ContextModel::index(int row, int column, const QModelIndex& parent) const { QModelIndex ContextModel::index(int row, int column, const QModelIndex& parent) const {
@@ -48,8 +57,8 @@ QModelIndex ContextModel::index(int row, int column, const QModelIndex& parent)
} }
} else { } else {
uLib::Object* parentObj = static_cast<uLib::Object*>(parent.internalPointer()); uLib::Object* parentObj = static_cast<uLib::Object*>(parent.internalPointer());
uLib::ObjectsContext* parentCtx = dynamic_cast<uLib::ObjectsContext*>(parentObj); uLib::ObjectsContext* parentCtx = parentObj->GetChildren();
if (parentCtx && row < parentCtx->GetCount()) { if (parentCtx && row < (int)parentCtx->GetCount()) {
return createIndex(row, column, parentCtx->GetObject(row)); return createIndex(row, column, parentCtx->GetObject(row));
} }
} }
@@ -65,36 +74,37 @@ QModelIndex ContextModel::parent(const QModelIndex& child) const {
// Finding the parent of childObj is O(N) since there is no parent pointer. // Finding the parent of childObj is O(N) since there is no parent pointer.
// We just do a recursive search starting from root context. // We just do a recursive search starting from root context.
std::function<uLib::ObjectsContext*(uLib::ObjectsContext*, uLib::Object*)> findParent = std::function<uLib::Object*(uLib::Object*, uLib::Object*)> findParent =
[&findParent](uLib::ObjectsContext* ctx, uLib::Object* target) -> uLib::ObjectsContext* { [&findParent](uLib::Object* current, uLib::Object* target) -> uLib::Object* {
for (const auto& obj : ctx->GetObjects()) { uLib::ObjectsContext* ctx = current->GetChildren();
if (obj == target) return ctx; if (ctx) {
if (auto subCtx = dynamic_cast<uLib::ObjectsContext*>(obj)) { for (const auto& obj : ctx->GetObjects()) {
if (auto p = findParent(subCtx, target)) return p; if (obj == target) return current;
if (auto p = findParent(obj, target)) return p;
} }
} }
return nullptr; return nullptr;
}; };
uLib::ObjectsContext* parentCtx = findParent(m_rootContext, childObj); uLib::Object* parentObj = findParent(m_rootContext, childObj);
if (!parentCtx || parentCtx == m_rootContext) { if (!parentObj || parentObj == m_rootContext) {
return QModelIndex(); // Root items have invalid parent index return QModelIndex(); // Root items have invalid parent index
} }
// Now need to find the row of parentCtx in its own parent Context. // Now need to find the row of parentObj in its own parent Context.
uLib::ObjectsContext* grandParentCtx = findParent(m_rootContext, parentCtx); uLib::Object* grandParentObj = findParent(m_rootContext, parentObj);
if (!grandParentCtx) grandParentCtx = m_rootContext; uLib::ObjectsContext* grandParentCtx = grandParentObj ? grandParentObj->GetChildren() : m_rootContext;
int row = -1; int row = -1;
for (size_t i = 0; i < grandParentCtx->GetCount(); ++i) { for (size_t i = 0; i < grandParentCtx->GetCount(); ++i) {
if (grandParentCtx->GetObject(i) == parentCtx) { if (grandParentCtx->GetObject(i) == parentObj) {
row = (int)i; row = (int)i;
break; break;
} }
} }
if (row != -1) { if (row != -1) {
return createIndex(row, 0, parentCtx); return createIndex(row, 0, parentObj);
} }
return QModelIndex(); return QModelIndex();
} }
@@ -107,8 +117,8 @@ int ContextModel::rowCount(const QModelIndex& parent) const {
} }
uLib::Object* parentObj = static_cast<uLib::Object*>(parent.internalPointer()); uLib::Object* parentObj = static_cast<uLib::Object*>(parent.internalPointer());
if (auto parentCtx = dynamic_cast<uLib::ObjectsContext*>(parentObj)) { if (auto parentCtx = parentObj->GetChildren()) {
return parentCtx->GetCount(); return (int)parentCtx->GetCount();
} }
return 0; // leaf node return 0; // leaf node
} }
@@ -161,8 +171,98 @@ QVariant ContextModel::headerData(int section, Qt::Orientation orientation, int
} }
Qt::ItemFlags ContextModel::flags(const QModelIndex& index) const { Qt::ItemFlags ContextModel::flags(const QModelIndex& index) const {
if (!index.isValid()) return Qt::NoItemFlags; if (!index.isValid()) return m_rootContext ? Qt::ItemIsDropEnabled : Qt::NoItemFlags;
return Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled;
Qt::ItemFlags f = Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled;
uLib::Object* obj = static_cast<uLib::Object*>(index.internalPointer());
if (dynamic_cast<uLib::ObjectsContext*>(obj)) {
f |= Qt::ItemIsDropEnabled;
}
return f;
}
Qt::DropActions ContextModel::supportedDropActions() const {
return Qt::MoveAction;
}
QStringList ContextModel::mimeTypes() const {
return {"application/x-ulib-object-ptr"};
}
QMimeData* ContextModel::mimeData(const QModelIndexList& indexes) const {
QMimeData* mimeData = new QMimeData();
QByteArray encodedData;
QDataStream stream(&encodedData, QIODevice::WriteOnly);
for (const auto& idx : indexes) {
if (idx.isValid() && idx.column() == 0) {
void* ptr = idx.internalPointer();
stream << reinterpret_cast<qlonglong>(ptr);
}
}
mimeData->setData("application/x-ulib-object-ptr", encodedData);
return mimeData;
}
bool ContextModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) {
if (action != Qt::MoveAction || !data->hasFormat("application/x-ulib-object-ptr")) return false;
uLib::ObjectsContext* targetCtx = m_rootContext;
if (parent.isValid()) {
uLib::Object* parentObj = static_cast<uLib::Object*>(parent.internalPointer());
targetCtx = dynamic_cast<uLib::ObjectsContext*>(parentObj);
}
if (!targetCtx) return false;
QByteArray encodedData = data->data("application/x-ulib-object-ptr");
QDataStream stream(&encodedData, QIODevice::ReadOnly);
std::vector<uLib::Object*> objectsToMove;
while (!stream.atEnd()) {
qlonglong ptrVal;
stream >> ptrVal;
objectsToMove.push_back(reinterpret_cast<uLib::Object*>(ptrVal));
}
if (objectsToMove.empty()) return false;
// Helper to find and remove from current parent
std::function<void(uLib::Object*, uLib::Object*)> findAndRemoveRecursive =
[&findAndRemoveRecursive](uLib::Object* current, uLib::Object* target) {
if (auto ctx = current->GetChildren()) {
ctx->RemoveObject(target);
for (auto* obj : ctx->GetObjects()) {
findAndRemoveRecursive(obj, target);
}
}
};
m_isReseting = true;
beginResetModel();
for (auto* obj : objectsToMove) {
// Don't drop onto itself or its descendants
bool invalid = (obj == targetCtx || obj == (uLib::Object*)targetCtx);
if (!invalid) {
// check if targetCtx is descendant of obj
std::function<bool(uLib::Object*, uLib::Object*)> isDescendant =
[&isDescendant](uLib::Object* root, uLib::Object* target) -> bool {
if (auto ctx = root->GetChildren()) {
for (auto* child : ctx->GetObjects()) {
if (child == target) return true;
if (isDescendant(child, target)) return true;
}
}
return false;
};
if (isDescendant(obj, (uLib::Object*)targetCtx)) invalid = true;
}
if (!invalid) {
findAndRemoveRecursive(m_rootContext, obj);
targetCtx->AddObject(obj);
}
}
endResetModel();
m_isReseting = false;
return true;
} }
bool ContextModel::setData(const QModelIndex& index, const QVariant& value, int role) { bool ContextModel::setData(const QModelIndex& index, const QVariant& value, int role) {

View File

@@ -21,8 +21,15 @@ public:
Qt::ItemFlags flags(const QModelIndex& index) const override; Qt::ItemFlags flags(const QModelIndex& index) const override;
bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override; bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override;
// Drag and Drop support
Qt::DropActions supportedDropActions() const override;
QStringList mimeTypes() const override;
QMimeData* mimeData(const QModelIndexList& indexes) const override;
bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) override;
private: private:
uLib::ObjectsContext* m_rootContext; uLib::ObjectsContext* m_rootContext;
bool m_isReseting = false;
}; };
#endif // CONTEXT_MODEL_H #endif // CONTEXT_MODEL_H

View File

@@ -38,6 +38,10 @@ ContextPanel::ContextPanel(QWidget* parent)
m_treeView = new QTreeView(this); m_treeView = new QTreeView(this);
m_treeView->setObjectName("ContextTree"); m_treeView->setObjectName("ContextTree");
m_treeView->setHeaderHidden(false); m_treeView->setHeaderHidden(false);
m_treeView->setDragEnabled(true);
m_treeView->setAcceptDrops(true);
m_treeView->setDropIndicatorShown(true);
m_treeView->setDragDropMode(QAbstractItemView::DragDrop);
m_model = new ContextModel(this); m_model = new ContextModel(this);
m_treeView->setModel(m_model); m_treeView->setModel(m_model);

View File

@@ -7,6 +7,11 @@
#include "Vtk/uLibVtkInterface.h" #include "Vtk/uLibVtkInterface.h"
#include "Math/Units.h" #include "Math/Units.h"
#include "Math/Dense.h" #include "Math/Dense.h"
#include <QPushButton>
#include <QColorDialog>
#include <QFrame>
#include <QSlider>
#include "Settings.h"
namespace uLib { namespace uLib {
namespace Qt { namespace Qt {
@@ -15,8 +20,21 @@ PropertyWidgetBase::PropertyWidgetBase(PropertyBase* prop, QWidget* parent)
: QWidget(parent), m_BaseProperty(prop) { : QWidget(parent), m_BaseProperty(prop) {
m_Layout = new QHBoxLayout(this); m_Layout = new QHBoxLayout(this);
m_Layout->setContentsMargins(4, 2, 4, 2); m_Layout->setContentsMargins(4, 2, 4, 2);
m_Label = new QLabel(QString::fromStdString(prop->GetName()), this);
m_Label->setMinimumWidth(100); std::string unit = prop->GetUnits();
QString labelText = QString::fromStdString(prop->GetName());
if (!unit.empty() && unit != "color") {
auto dim = Settings::Instance().IdentifyDimension(unit);
std::string pref = Settings::Instance().GetPreferredUnit(dim);
if (!pref.empty()) {
labelText += " [" + QString::fromStdString(pref) + "]";
} else {
labelText += " [" + QString::fromStdString(unit) + "]";
}
}
m_Label = new QLabel(labelText, this);
m_Label->setMinimumWidth(120);
m_Layout->addWidget(m_Label); m_Layout->addWidget(m_Label);
} }
PropertyWidgetBase::~PropertyWidgetBase() { PropertyWidgetBase::~PropertyWidgetBase() {
@@ -31,7 +49,7 @@ double parseWithUnits(const QString& text, double* factorOut, QString* suffixOut
double num = match.captured(1).toDouble(); double num = match.captured(1).toDouble();
QString unit = match.captured(3); QString unit = match.captured(3);
double factor = 1.0; double factor = factorOut ? *factorOut : 1.0;
if (!unit.isEmpty()) { if (!unit.isEmpty()) {
QString u = unit.startsWith('_') ? unit.mid(1) : unit; QString u = unit.startsWith('_') ? unit.mid(1) : unit;
@@ -87,10 +105,6 @@ void UnitLineEdit::onEditingFinished() {
double factor = m_Factor; double factor = m_Factor;
QString suffix = m_Suffix; QString suffix = m_Suffix;
double parsedVal = parseWithUnits(text(), &factor, &suffix); double parsedVal = parseWithUnits(text(), &factor, &suffix);
if (!suffix.isEmpty()) {
m_Suffix = suffix;
m_Factor = factor;
}
if (m_IsInteger) { if (m_IsInteger) {
parsedVal = std::round(parsedVal); parsedVal = std::round(parsedVal);
} }
@@ -115,9 +129,6 @@ void UnitLineEdit::updateText() {
s += ".0"; s += ".0";
} }
} }
if (!m_Suffix.isEmpty()) {
s += " " + m_Suffix;
}
setText(s); setText(s);
} }
@@ -129,11 +140,12 @@ void UnitLineEdit::setIntegerOnly(bool integerOnly) {
DoublePropertyWidget::DoublePropertyWidget(Property<double>* prop, QWidget* parent) DoublePropertyWidget::DoublePropertyWidget(Property<double>* prop, QWidget* parent)
: PropertyWidgetBase(prop, parent), m_Prop(prop) { : PropertyWidgetBase(prop, parent), m_Prop(prop) {
m_Edit = new UnitLineEdit(this); m_Edit = new UnitLineEdit(this);
QString units = QString::fromStdString(prop->GetUnits()); std::string unit = prop->GetUnits();
if (!units.isEmpty()) { if (!unit.empty()) {
double factor = 1.0; auto dim = Settings::Instance().IdentifyDimension(unit);
parseWithUnits("1 " + units, &factor); std::string pref = Settings::Instance().GetPreferredUnit(dim);
m_Edit->setUnits(units, factor); double factor = Settings::Instance().GetUnitFactor(pref);
m_Edit->setUnits(QString::fromStdString(pref), factor);
} }
m_Edit->setValue(prop->Get()); m_Edit->setValue(prop->Get());
m_Layout->addWidget(m_Edit, 1); m_Layout->addWidget(m_Edit, 1);
@@ -146,11 +158,12 @@ DoublePropertyWidget::DoublePropertyWidget(Property<double>* prop, QWidget* pare
FloatPropertyWidget::FloatPropertyWidget(Property<float>* prop, QWidget* parent) FloatPropertyWidget::FloatPropertyWidget(Property<float>* prop, QWidget* parent)
: PropertyWidgetBase(prop, parent), m_Prop(prop) { : PropertyWidgetBase(prop, parent), m_Prop(prop) {
m_Edit = new UnitLineEdit(this); m_Edit = new UnitLineEdit(this);
QString units = QString::fromStdString(prop->GetUnits()); std::string unit = prop->GetUnits();
if (!units.isEmpty()) { if (!unit.empty()) {
double factor = 1.0; auto dim = Settings::Instance().IdentifyDimension(unit);
parseWithUnits("1 " + units, &factor); std::string pref = Settings::Instance().GetPreferredUnit(dim);
m_Edit->setUnits(units, factor); double factor = Settings::Instance().GetUnitFactor(pref);
m_Edit->setUnits(QString::fromStdString(pref), factor);
} }
m_Edit->setValue(prop->Get()); m_Edit->setValue(prop->Get());
m_Layout->addWidget(m_Edit, 1); m_Layout->addWidget(m_Edit, 1);
@@ -164,11 +177,12 @@ IntPropertyWidget::IntPropertyWidget(Property<int>* prop, QWidget* parent)
: PropertyWidgetBase(prop, parent), m_Prop(prop) { : PropertyWidgetBase(prop, parent), m_Prop(prop) {
m_Edit = new UnitLineEdit(this); m_Edit = new UnitLineEdit(this);
m_Edit->setIntegerOnly(true); m_Edit->setIntegerOnly(true);
QString units = QString::fromStdString(prop->GetUnits()); std::string unit = prop->GetUnits();
if (!units.isEmpty()) { if (!unit.empty()) {
double factor = 1.0; auto dim = Settings::Instance().IdentifyDimension(unit);
parseWithUnits("1 " + units, &factor); std::string pref = Settings::Instance().GetPreferredUnit(dim);
m_Edit->setUnits(units, factor); double factor = Settings::Instance().GetUnitFactor(pref);
m_Edit->setUnits(QString::fromStdString(pref), factor);
} }
m_Edit->setValue(prop->Get()); m_Edit->setValue(prop->Get());
m_Layout->addWidget(m_Edit, 1); m_Layout->addWidget(m_Edit, 1);
@@ -193,6 +207,76 @@ BoolPropertyWidget::BoolPropertyWidget(Property<bool>* prop, QWidget* parent)
} }
BoolPropertyWidget::~BoolPropertyWidget() {} BoolPropertyWidget::~BoolPropertyWidget() {}
RangePropertyWidget::RangePropertyWidget(Property<double>* prop, QWidget* parent)
: PropertyWidgetBase(prop, parent), m_Prop(prop) {
m_Slider = new QSlider(::Qt::Horizontal, this);
m_Slider->setRange(0, 100);
m_Slider->setMinimumWidth(80);
m_Edit = new UnitLineEdit(this);
m_Edit->setFixedWidth(50);
m_Layout->addWidget(m_Slider, 1);
m_Layout->addWidget(m_Edit, 0);
connect(m_Slider, &QSlider::valueChanged, this, &RangePropertyWidget::onSliderChanged);
connect(m_Edit, &UnitLineEdit::valueManualChanged, [this](double val){ m_Prop->Set(val); });
m_Connection = uLib::Object::connect(m_Prop, &Property<double>::PropertyChanged, [this](){
this->updateUi();
});
updateUi();
}
RangePropertyWidget::~RangePropertyWidget() { m_Connection.disconnect(); }
void RangePropertyWidget::updateUi() {
double val = m_Prop->Get();
m_Edit->setValue(val);
if (m_Prop->GetMax() != m_Prop->GetMin()) {
int sliderVal = (int)((val - m_Prop->GetMin()) / (m_Prop->GetMax() - m_Prop->GetMin()) * 100.0);
QSignalBlocker blocker(m_Slider);
m_Slider->setValue(sliderVal);
}
}
void RangePropertyWidget::onSliderChanged(int val) {
double realVal = m_Prop->GetMin() + (val / 100.0) * (m_Prop->GetMax() - m_Prop->GetMin());
m_Prop->Set(realVal);
}
ColorPropertyWidget::ColorPropertyWidget(Property<Vector3d>* prop, QWidget* parent)
: PropertyWidgetBase(prop, parent), m_Prop(prop) {
m_Button = new QPushButton(this);
m_Button->setFixedWidth(60);
this->updateButtonColor();
m_Layout->addWidget(m_Button, 0, ::Qt::AlignRight);
connect(m_Button, &QPushButton::clicked, this, &ColorPropertyWidget::onClicked);
m_Connection = uLib::Object::connect(m_Prop, &Property<Vector3d>::PropertyChanged, [this](){
this->updateButtonColor();
});
}
ColorPropertyWidget::~ColorPropertyWidget() {}
void ColorPropertyWidget::updateButtonColor() {
Vector3d c = m_Prop->Get();
QColor color = QColor::fromRgbF(std::max(0.0, std::min(1.0, c.x())),
std::max(0.0, std::min(1.0, c.y())),
std::max(0.0, std::min(1.0, c.z())));
m_Button->setStyleSheet(QString("background-color: %1; border: 1px solid #555; height: 18px;").arg(color.name()));
}
void ColorPropertyWidget::onClicked() {
Vector3d c = m_Prop->Get();
QColor current = QColor::fromRgbF(std::max(0.0, std::min(1.0, c.x())),
std::max(0.0, std::min(1.0, c.y())),
std::max(0.0, std::min(1.0, c.z())));
QColor selected = QColorDialog::getColor(current, this, "Select Color");
if (selected.isValid()) {
m_Prop->Set(Vector3d(selected.redF(), selected.greenF(), selected.blueF()));
}
}
StringPropertyWidget::StringPropertyWidget(Property<std::string>* prop, QWidget* parent) StringPropertyWidget::StringPropertyWidget(Property<std::string>* prop, QWidget* parent)
: PropertyWidgetBase(prop, parent), m_Prop(prop) { : PropertyWidgetBase(prop, parent), m_Prop(prop) {
m_LineEdit = new QLineEdit(this); m_LineEdit = new QLineEdit(this);
@@ -211,6 +295,26 @@ StringPropertyWidget::StringPropertyWidget(Property<std::string>* prop, QWidget*
} }
StringPropertyWidget::~StringPropertyWidget() {} StringPropertyWidget::~StringPropertyWidget() {}
class GroupHeaderWidget : public QWidget {
public:
GroupHeaderWidget(const QString& name, QWidget* parent = nullptr) : QWidget(parent) {
auto* layout = new QVBoxLayout(this);
layout->setContentsMargins(0, 8, 0, 4);
auto* line = new QFrame(this);
line->setFrameShape(QFrame::HLine);
line->setFrameShadow(QFrame::Sunken);
line->setStyleSheet("color: #555;");
layout->addWidget(line);
auto* label = new QLabel(name, this);
QFont font = label->font();
font.setBold(true);
font.setPointSize(font.pointSize() + 1);
label->setFont(font);
label->setStyleSheet("color: #aaa; text-transform: uppercase;");
layout->addWidget(label);
}
};
class EnumPropertyWidget : public PropertyWidgetBase { class EnumPropertyWidget : public PropertyWidgetBase {
PropertyBase* m_Prop; PropertyBase* m_Prop;
QComboBox* m_Combo; QComboBox* m_Combo;
@@ -305,26 +409,63 @@ void PropertyEditor::setObject(::uLib::Object* obj, bool displayOnly) {
} }
} }
// Group properties by their group string
std::map<std::string, std::vector<::uLib::PropertyBase*>> groupedProps;
std::vector<std::string> groupOrder;
for (auto* prop : *props) { for (auto* prop : *props) {
// Priority 1: Check if it provides enum labels std::string group = prop->GetGroup();
if (!prop->GetEnumLabels().empty()) { if (groupedProps.find(group) == groupedProps.end()) {
m_ContainerLayout->addWidget(new EnumPropertyWidget(prop, m_Container)); groupOrder.push_back(group);
continue; }
groupedProps[group].push_back(prop);
}
for (const auto& groupName : groupOrder) {
if (!groupName.empty()) {
m_ContainerLayout->addWidget(new GroupHeaderWidget(QString::fromStdString(groupName), m_Container));
} }
// Priority 2: Standard factory lookup for (auto* prop : groupedProps[groupName]) {
auto it = m_Factories.find(prop->GetTypeIndex()); QWidget* widget = nullptr;
if (it != m_Factories.end()) {
QWidget* widget = it->second(prop, m_Container); // Priority 1: Check if it provides enum labels
m_ContainerLayout->addWidget(widget); if (!prop->GetEnumLabels().empty()) {
} else { widget = new EnumPropertyWidget(prop, m_Container);
// Debug info for unknown types } else if (prop->GetUnits() == "color") {
std::cout << "PropertyEditor: No factory for " << prop->GetName() // Color Picker for Vector3d
<< " (Type: " << prop->GetTypeName() << ")" << std::endl; if (auto* pvec = dynamic_cast<Property<Vector3d>*>(prop)) {
widget = new ColorPropertyWidget(pvec, m_Container);
}
} else if (prop->HasRange()) {
// Slider for ranged doubles
if (auto* pdbl = dynamic_cast<Property<double>*>(prop)) {
widget = new RangePropertyWidget(pdbl, m_Container);
} else if (auto* pflt = dynamic_cast<Property<float>*>(prop)) {
// widget = new RangePropertyWidget<float>(pflt, m_Container);
}
} else {
// Priority 2: Standard factory lookup
auto it = m_Factories.find(prop->GetTypeIndex());
if (it != m_Factories.end()) {
widget = it->second(prop, m_Container);
} else {
// Debug info for unknown types
std::cout << "PropertyEditor: No factory for " << prop->GetQualifiedName()
<< " (Type: " << prop->GetTypeName() << ")" << std::endl;
QWidget* fallback = new PropertyWidgetBase(prop, m_Container); widget = new PropertyWidgetBase(prop, m_Container);
fallback->layout()->addWidget(new QLabel("(Read-only: " + QString::fromStdString(prop->GetValueAsString()) + ")")); widget->layout()->addWidget(new QLabel("(Read-only: " + QString::fromStdString(prop->GetValueAsString()) + ")"));
m_ContainerLayout->addWidget(fallback); }
}
if (widget) {
if (!groupName.empty()) {
// Indent grouped properties
widget->setContentsMargins(16, 0, 0, 0);
}
m_ContainerLayout->addWidget(widget);
}
} }
} }
m_ContainerLayout->addStretch(1); m_ContainerLayout->addStretch(1);

View File

@@ -2,6 +2,8 @@
#define PROPERTY_WIDGETS_H #define PROPERTY_WIDGETS_H
#include <QWidget> #include <QWidget>
class QPushButton;
class QSlider;
#include <QLabel> #include <QLabel>
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QVBoxLayout> #include <QVBoxLayout>
@@ -16,6 +18,7 @@
#include "Core/Object.h" #include "Core/Object.h"
#include "Core/Signal.h" #include "Core/Signal.h"
#include "Math/Dense.h" #include "Math/Dense.h"
#include "Settings.h"
namespace uLib { namespace uLib {
namespace Qt { namespace Qt {
@@ -93,18 +96,24 @@ class VectorPropertyWidget : public PropertyWidgetBase {
public: public:
VectorPropertyWidget(Property<VecT>* prop, QWidget* parent = nullptr) VectorPropertyWidget(Property<VecT>* prop, QWidget* parent = nullptr)
: PropertyWidgetBase(prop, parent), m_Prop(prop) { : PropertyWidgetBase(prop, parent), m_Prop(prop) {
QString units = QString::fromStdString(prop->GetUnits());
std::string unit = prop->GetUnits();
double factor = 1.0; double factor = 1.0;
if (!units.isEmpty()) { QString prefSuffix;
parseWithUnits("1 " + units, &factor); if (!unit.empty()) {
auto dim = Settings::Instance().IdentifyDimension(unit);
std::string pref = Settings::Instance().GetPreferredUnit(dim);
factor = Settings::Instance().GetUnitFactor(pref);
prefSuffix = QString::fromStdString(pref);
} }
for (int i = 0; i < Size; ++i) { for (int i = 0; i < Size; ++i) {
m_Edits[i] = new UnitLineEdit(this); m_Edits[i] = new UnitLineEdit(this);
if (std::is_integral<typename VecT::Scalar>::value) { if (std::is_integral<typename VecT::Scalar>::value) {
m_Edits[i]->setIntegerOnly(true); m_Edits[i]->setIntegerOnly(true);
} }
if (!units.isEmpty()) { if (!prefSuffix.isEmpty()) {
m_Edits[i]->setUnits(units, factor); m_Edits[i]->setUnits(prefSuffix, factor);
} }
m_Layout->addWidget(m_Edits[i], 1); m_Layout->addWidget(m_Edits[i], 1);
@@ -134,6 +143,20 @@ private:
UnitLineEdit* m_Edits[Size]; UnitLineEdit* m_Edits[Size];
}; };
class RangePropertyWidget : public PropertyWidgetBase {
Q_OBJECT
public:
RangePropertyWidget(Property<double>* prop, QWidget* parent = nullptr);
virtual ~RangePropertyWidget();
private slots:
void onSliderChanged(int val);
private:
void updateUi();
Property<double>* m_Prop;
QSlider* m_Slider;
UnitLineEdit* m_Edit;
};
class BoolPropertyWidget : public PropertyWidgetBase { class BoolPropertyWidget : public PropertyWidgetBase {
Q_OBJECT Q_OBJECT
public: public:
@@ -144,6 +167,19 @@ private:
QCheckBox* m_CheckBox; QCheckBox* m_CheckBox;
}; };
class ColorPropertyWidget : public PropertyWidgetBase {
Q_OBJECT
public:
ColorPropertyWidget(Property<Vector3d>* prop, QWidget* parent = nullptr);
virtual ~ColorPropertyWidget();
private slots:
void onClicked();
private:
void updateButtonColor();
Property<Vector3d>* m_Prop;
QPushButton* m_Button;
};
class StringPropertyWidget : public PropertyWidgetBase { class StringPropertyWidget : public PropertyWidgetBase {
Q_OBJECT Q_OBJECT
public: public:

View File

@@ -0,0 +1,75 @@
#ifndef GCOMPOSE_SETTINGS_H
#define GCOMPOSE_SETTINGS_H
#include <string>
#include <map>
#include "Math/Units.h"
namespace uLib {
namespace Qt {
class Settings {
public:
static Settings& Instance() {
static Settings instance;
return instance;
}
enum Dimension {
Length,
Angle,
Energy,
Time,
Dimensionless
};
void SetPreferredUnit(Dimension dim, const std::string& unit) {
m_PreferredUnits[dim] = unit;
}
std::string GetPreferredUnit(Dimension dim) const {
auto it = m_PreferredUnits.find(dim);
if (it != m_PreferredUnits.end()) return it->second;
switch(dim) {
case Length: return "mm";
case Angle: return "deg";
case Energy: return "MeV";
case Time: return "ns";
default: return "";
}
}
double GetUnitFactor(const std::string& unit) const {
if (unit == "m") return CLHEP::meter;
if (unit == "cm") return CLHEP::centimeter;
if (unit == "mm") return CLHEP::millimeter;
if (unit == "um") return CLHEP::micrometer;
if (unit == "deg") return CLHEP::degree;
if (unit == "rad") return CLHEP::radian;
if (unit == "ns") return CLHEP::nanosecond;
if (unit == "s") return CLHEP::second;
if (unit == "ms") return CLHEP::millisecond;
if (unit == "MeV") return CLHEP::megaelectronvolt;
if (unit == "GeV") return CLHEP::gigaelectronvolt;
if (unit == "eV") return CLHEP::electronvolt;
return 1.0;
}
Dimension IdentifyDimension(const std::string& unit) const {
if (unit == "m" || unit == "cm" || unit == "mm" || unit == "um" || unit == "nm") return Length;
if (unit == "deg" || unit == "rad") return Angle;
if (unit == "MeV" || unit == "GeV" || unit == "eV" || unit == "keV" || unit == "TeV") return Energy;
if (unit == "ns" || unit == "s" || unit == "ms" || unit == "us") return Time;
return Dimensionless;
}
private:
Settings() {}
std::map<Dimension, std::string> m_PreferredUnits;
};
} // namespace Qt
} // namespace uLib
#endif

View File

@@ -46,23 +46,25 @@ ViewportPane::ViewportPane(QWidget* parent) : QWidget(parent), m_viewport(nullpt
m_layout->addWidget(m_titleBar); m_layout->addWidget(m_titleBar);
// Main horizontal container for viewport and display panel // Main area with splitter for viewport and display panel
QWidget* mainArea = new QWidget(this); m_areaSplitter = new QSplitter(Qt::Horizontal, this);
QHBoxLayout* hLayout = new QHBoxLayout(mainArea); m_areaSplitter->setObjectName("ViewportAreaSplitter");
hLayout->setContentsMargins(0, 0, 0, 0); m_layout->addWidget(m_areaSplitter, 1);
hLayout->setSpacing(0);
m_layout->addWidget(mainArea);
// Viewport will be added here via setViewport // Viewport will be added here via setViewport
m_viewport = new uLib::Vtk::QViewport(mainArea); m_viewport = new uLib::Vtk::QViewport(m_areaSplitter);
hLayout->addWidget(m_viewport); m_areaSplitter->addWidget(m_viewport);
// Display Panel (Overlay/Slide-out) // Display Panel (Overlay/Slide-out)
m_displayPanel = new QFrame(mainArea); m_displayPanel = new QFrame(m_areaSplitter);
m_displayPanel->setObjectName("DisplayPropertiesPanel"); m_displayPanel->setObjectName("DisplayPropertiesPanel");
m_displayPanel->setFixedWidth(250); m_displayPanel->setMinimumWidth(150);
m_displayPanel->hide(); m_displayPanel->hide();
m_areaSplitter->addWidget(m_displayPanel);
m_areaSplitter->setStretchFactor(0, 1);
m_areaSplitter->setStretchFactor(1, 0);
QVBoxLayout* panelLayout = new QVBoxLayout(m_displayPanel); QVBoxLayout* panelLayout = new QVBoxLayout(m_displayPanel);
panelLayout->setContentsMargins(5, 5, 5, 5); panelLayout->setContentsMargins(5, 5, 5, 5);
@@ -72,8 +74,6 @@ ViewportPane::ViewportPane(QWidget* parent) : QWidget(parent), m_viewport(nullpt
m_displayEditor = new uLib::Qt::PropertyEditor(m_displayPanel); m_displayEditor = new uLib::Qt::PropertyEditor(m_displayPanel);
panelLayout->addWidget(m_displayEditor); panelLayout->addWidget(m_displayEditor);
hLayout->addWidget(m_displayPanel);
connect(m_toggleBtn, &QPushButton::toggled, this, &ViewportPane::toggleDisplayPanel); connect(m_toggleBtn, &QPushButton::toggled, this, &ViewportPane::toggleDisplayPanel);
connect(m_titleBar, &QWidget::customContextMenuRequested, this, &ViewportPane::showContextMenu); connect(m_titleBar, &QWidget::customContextMenuRequested, this, &ViewportPane::showContextMenu);
@@ -85,7 +85,15 @@ ViewportPane::ViewportPane(QWidget* parent) : QWidget(parent), m_viewport(nullpt
ViewportPane::~ViewportPane() {} ViewportPane::~ViewportPane() {}
void ViewportPane::toggleDisplayPanel() { void ViewportPane::toggleDisplayPanel() {
m_displayPanel->setVisible(m_toggleBtn->isChecked()); bool visible = m_toggleBtn->isChecked();
m_displayPanel->setVisible(visible);
if (visible && m_areaSplitter->sizes().value(1, 0) == 0) {
QList<int> sizes = m_areaSplitter->sizes();
int total = sizes[0] + sizes[1];
sizes[1] = 250;
sizes[0] = total - 250;
m_areaSplitter->setSizes(sizes);
}
} }
void ViewportPane::setObject(uLib::Object* obj) { void ViewportPane::setObject(uLib::Object* obj) {
@@ -107,15 +115,14 @@ void ViewportPane::setObject(uLib::Object* obj) {
void ViewportPane::setViewport(QWidget* viewport, const QString& title) { void ViewportPane::setViewport(QWidget* viewport, const QString& title) {
if (m_viewport) { if (m_viewport) {
m_viewport->parentWidget()->layout()->removeWidget(m_viewport);
delete m_viewport; delete m_viewport;
} }
m_viewport = viewport; m_viewport = viewport;
m_titleLabel->setText(title); m_titleLabel->setText(title);
m_viewport->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); m_viewport->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
auto* mainAreaLayout = static_cast<QHBoxLayout*>(m_displayPanel->parentWidget()->layout()); m_areaSplitter->insertWidget(0, m_viewport);
mainAreaLayout->insertWidget(0, m_viewport); m_areaSplitter->setStretchFactor(0, 1);
} }
void ViewportPane::addVtkViewport() { void ViewportPane::addVtkViewport() {

View File

@@ -10,6 +10,7 @@ namespace uLib {
namespace Qt { class PropertyEditor; } namespace Qt { class PropertyEditor; }
} }
class QSplitter;
class QVBoxLayout; class QVBoxLayout;
class QLabel; class QLabel;
@@ -39,6 +40,7 @@ private:
QVBoxLayout* m_layout; QVBoxLayout* m_layout;
QWidget* m_titleBar; QWidget* m_titleBar;
QLabel* m_titleLabel; QLabel* m_titleLabel;
QSplitter* m_areaSplitter;
QWidget* m_viewport; QWidget* m_viewport;
// Display Properties Overlay // Display Properties Overlay

View File

@@ -0,0 +1,73 @@
# Transformation Flow and Synchronization System
This document describes how transformations are applied and synchronized between the interactive 3D viewport, the visualization puppets, and the underlying mathematical models within the `uLib` framework.
## Architecture Overview
The system follows a Model-View-Controller (MVC) like pattern where:
- **Model**: `uLib::AffineTransform` (or derived classes like `ContainerBox`).
- **View/Puppet**: `uLib::Vtk::Puppet` (and specialized derivations like `Vtk::Assembly`).
- **Controller/Interaction**: `vtkHandlerWidget` (the transformation gizmo).
---
## 1. Interaction Flow (Gizmo -> Model)
When a user interacts with the `vtkHandlerWidget` (dragging arrows, rings, or cubes), the following chain of events occurs:
```mermaid
sequenceDiagram
participant User
participant HW as vtkHandlerWidget
participant VP as vtkViewport
participant P as vtkPuppet
participant M as uLib Model
User->>HW: Drag handle (MouseMove)
HW->>HW: Calculate Delta Matrix (op)
HW->>HW: Total = StartState * op
HW->>HW: Decompose Total into P, O, S
HW->>P: SetPosition, SetOrientation, SetScale
HW-->>VP: Invoke InteractionEvent
VP->>P: SyncFromVtk()
P->>P: Get local matrix from VTK Prop
P->>M: SetMatrix(matrix)
M-->>M: Update local properties (P, O, S)
M-->>P: Emit Updated signal
P->>P: Puppet::Update()
P->>P: (Redundant sanity write to Prop)
```
### Key Principles:
- **Single Source of Truth**: The `uLib::AffineTransform` is the owner of the transformation state.
- **Internal TRS vs UserMatrix**: We apply transformations directly to VTK's internal `Position`, `Orientation`, and `Scale` properties. This ensures the data is "visible" to VTK actors and simplifies decomposition.
- **Cumulative Bias Avoidance**: The `HandlerWidget` calculates transformations relative to the state at the start of the click, preventing numerical drift during a single drag operation.
---
## 2. Synchronization Loop Resolution
To prevent infinite loops and "double-transformation" artifacts (especially in assemblies), the following protections are in place:
1. **Hierarchy Isolation**: The `Puppet` base class distinguishes between the **Root Property** (which receives the puppet's master transformation) and **Sub-Parts** (which only receive appearance updates like color/visibility). This prevents parts from inheriting the same displacement twice.
2. **Re-entrancy Guards**: Puppets use an `m_InUpdate` flag to prevent a feedback loop where `SyncFromVtk` triggers a Model Update, which then re-triggers the Puppet Update.
3. **Signal Blocking**: In specialized cases (like `vtkAssembly`), `m_BlockUpdate` is used to prevent the model-to-puppet push during a puppet-to-model sync.
---
## 3. Undo System (Ctrl-Z)
### Current Implementation (Delta Chain)
Currently, the system maintains a `m_TransformChain` of delta matrices.
- **Record**: After every drag, a delta matrix ($M_{delta} = M_{end} \cdot M_{start}^{-1}$) is appended to the chain.
- **Undo**: The last delta is removed, and the prop is reconstructed by reapplying the remaining chain from a `BaseMatrix`.
### Planned Improvement (TRS Snapshots)
We are migrating to a `uLib::TRS` snapshot system for Undo.
- **Record**: At the start of a drag, the current `TRS` state of the object is pushed to the `m_UndoStack`.
- **Undo**: The top `TRS` is popped and applied directly to the model.
This approach is more robust because:
- It eliminates matrix multiplication error accumulation.
- It bypasses rotation convention/order issues (Gimbal lock in deltas).
- It returns the object to exactly its previous property values.

View File

@@ -90,10 +90,10 @@ const std::vector<PropertyBase*>& Object::GetProperties() const {
PropertyBase* Object::GetProperty(const std::string& name) const { PropertyBase* Object::GetProperty(const std::string& name) const {
for (auto* p : d->m_Properties) { for (auto* p : d->m_Properties) {
if (p->GetName() == name) return p; if (p->GetName() == name || p->GetQualifiedName() == name) return p;
} }
for (auto* p : d->m_DynamicProperties) { for (auto* p : d->m_DynamicProperties) {
if (p->GetName() == name) return p; if (p->GetName() == name || p->GetQualifiedName() == name) return p;
} }
return nullptr; return nullptr;
} }

View File

@@ -52,6 +52,7 @@ class polymorphic_oarchive;
namespace uLib { namespace uLib {
class PropertyBase; class PropertyBase;
class ObjectsContext;
class Version { class Version {
public: public:
@@ -101,6 +102,9 @@ public:
// FIXX !!! // FIXX !!!
virtual void DeepCopy(const Object &copy); virtual void DeepCopy(const Object &copy);
/** @brief Returns a nested context for children objects, if any. */
virtual ObjectsContext* GetChildren() { return nullptr; }
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// SERIALIZATION // // SERIALIZATION //

View File

@@ -9,12 +9,13 @@ namespace uLib {
/** /**
* @brief ObjectsContext represents a collection of Object instances. * @brief ObjectsContext represents a collection of Object instances.
*/ */
class ObjectsContext : public Object { class ObjectsContext : virtual public Object {
public: public:
ObjectsContext(); ObjectsContext();
virtual ~ObjectsContext(); virtual ~ObjectsContext();
virtual const char * GetClassName() const { return "ObjectsContext"; } virtual const char * GetClassName() const { return "ObjectsContext"; }
virtual ObjectsContext* GetChildren() override { return this; }
/** /**
* @brief Adds an object to the context. * @brief Adds an object to the context.

View File

@@ -2,11 +2,16 @@
#define U_CORE_PROPERTY_H #define U_CORE_PROPERTY_H
#include <string> #include <string>
#include <vector>
#include <sstream> #include <sstream>
#include <typeinfo> #include <typeinfo>
#include <typeindex> // Added #include <typeindex> // Added
#include <boost/serialization/nvp.hpp> #include <boost/serialization/nvp.hpp>
#include <boost/lexical_cast.hpp> #include <boost/lexical_cast.hpp>
#include <vector>
#include <boost/type_traits/is_class.hpp>
#include <boost/mpl/bool.hpp>
#include <boost/serialization/serialization.hpp>
#include "Core/Archives.h" #include "Core/Archives.h"
#include "Core/Signal.h" #include "Core/Signal.h"
#include "Core/Object.h" #include "Core/Object.h"
@@ -29,6 +34,19 @@ public:
static std::vector<std::string> empty; static std::vector<std::string> empty;
return empty; return empty;
} }
virtual const std::string& GetGroup() const = 0;
virtual void SetGroup(const std::string& group) = 0;
virtual bool HasRange() const { return false; }
virtual double GetMin() const { return 0; }
virtual double GetMax() const { return 0; }
virtual bool HasDefault() const { return false; }
virtual std::string GetDefaultValueAsString() const { return ""; }
std::string GetQualifiedName() const {
if (GetGroup().empty()) return GetName();
return GetGroup() + "." + GetName();
}
// Signal support // Signal support
signals: signals:
@@ -51,16 +69,18 @@ template <typename T>
class Property : public PropertyBase { class Property : public PropertyBase {
public: public:
// PROXY: Use an existing variable as back-end storage // PROXY: Use an existing variable as back-end storage
Property(Object* owner, const std::string& name, T* valuePtr, const std::string& units = "") Property(Object* owner, const std::string& name, T* valuePtr, const std::string& units = "", const std::string& group = "")
: m_owner(owner), m_name(name), m_units(units), m_value(valuePtr), m_own(false) { : m_owner(owner), m_name(name), m_units(units), m_group(group), m_value(valuePtr), m_own(false),
m_HasRange(false), m_HasDefault(false) {
if (m_owner) { if (m_owner) {
m_owner->RegisterProperty(this); m_owner->RegisterProperty(this);
} }
} }
// MANAGED: Create and own internal storage // MANAGED: Create and own internal storage
Property(Object* owner, const std::string& name, const T& defaultValue = T(), const std::string& units = "") Property(Object* owner, const std::string& name, const T& defaultValue = T(), const std::string& units = "", const std::string& group = "")
: m_owner(owner), m_name(name), m_units(units), m_value(new T(defaultValue)), m_own(true) { : m_owner(owner), m_name(name), m_units(units), m_group(group), m_value(new T(defaultValue)), m_own(true),
m_HasRange(false), m_HasDefault(true), m_Default(defaultValue) {
if (m_owner) { if (m_owner) {
m_owner->RegisterProperty(this); m_owner->RegisterProperty(this);
} }
@@ -76,6 +96,8 @@ public:
virtual std::type_index GetTypeIndex() const override { return std::type_index(typeid(T)); } virtual std::type_index GetTypeIndex() const override { return std::type_index(typeid(T)); }
virtual const std::string& GetUnits() const override { return m_units; } virtual const std::string& GetUnits() const override { return m_units; }
virtual void SetUnits(const std::string& units) override { m_units = units; } virtual void SetUnits(const std::string& units) override { m_units = units; }
virtual const std::string& GetGroup() const override { return m_group; }
virtual void SetGroup(const std::string& group) override { m_group = group; }
std::string GetValueAsString() const override { std::string GetValueAsString() const override {
@@ -90,15 +112,61 @@ public:
// Accessors // Accessors
const T& Get() const { return *m_value; } const T& Get() const { return *m_value; }
template<typename U = T>
typename std::enable_if<std::is_arithmetic<U>::value, void>::type
ValidateT(T& val) {
if (m_HasRange) {
if (val < m_Min) val = m_Min;
if (val > m_Max) val = m_Max;
}
}
template<typename U = T>
typename std::enable_if<!std::is_arithmetic<U>::value, void>::type
ValidateT(T& val) {
}
void Set(const T& value) { void Set(const T& value) {
if (*m_value != value) { T val = value;
*m_value = value; ValidateT<T>(val);
if (*m_value != val) {
*m_value = val;
ULIB_SIGNAL_EMIT(Property<T>::PropertyChanged); ULIB_SIGNAL_EMIT(Property<T>::PropertyChanged);
this->Updated(); this->Updated();
if (m_owner) m_owner->Updated(); if (m_owner) m_owner->Updated();
} }
} }
void SetRange(const T& min, const T& max) { m_Min = min; m_Max = max; m_HasRange = true; }
void SetDefault(const T& def) { m_Default = def; m_HasDefault = true; }
virtual bool HasRange() const override { return m_HasRange; }
template<typename U = T>
typename std::enable_if<std::is_arithmetic<U>::value, double>::type
GetMinT() const { return (double)m_Min; }
template<typename U = T>
typename std::enable_if<!std::is_arithmetic<U>::value, double>::type
GetMinT() const { return 0.0; }
template<typename U = T>
typename std::enable_if<std::is_arithmetic<U>::value, double>::type
GetMaxT() const { return (double)m_Max; }
template<typename U = T>
typename std::enable_if<!std::is_arithmetic<U>::value, double>::type
GetMaxT() const { return 0.0; }
virtual double GetMin() const override { return GetMinT<T>(); }
virtual double GetMax() const override { return GetMaxT<T>(); }
virtual bool HasDefault() const override { return m_HasDefault; }
virtual std::string GetDefaultValueAsString() const override {
try { return boost::lexical_cast<std::string>(m_Default); }
catch (...) { return ""; }
}
// Operators for seamless usage // Operators for seamless usage
operator const T&() const { return *m_value; } operator const T&() const { return *m_value; }
Property& operator=(const T& value) { Property& operator=(const T& value) {
@@ -124,12 +192,23 @@ public:
void serialize(Archive::hrt_iarchive & ar, const unsigned int v) override { serialize_impl(ar, v); } void serialize(Archive::hrt_iarchive & ar, const unsigned int v) override { serialize_impl(ar, v); }
void serialize(Archive::log_archive & ar, const unsigned int v) override { serialize_impl(ar, v); } void serialize(Archive::log_archive & ar, const unsigned int v) override { serialize_impl(ar, v); }
virtual void Updated() override {
PropertyBase::Updated();
this->PropertyChanged();
}
private: private:
std::string m_name; std::string m_name;
std::string m_units; std::string m_units;
std::string m_group;
T* m_value; T* m_value;
bool m_own; bool m_own;
Object* m_owner; Object* m_owner;
bool m_HasRange;
T m_Min;
T m_Max;
bool m_HasDefault;
T m_Default;
}; };
/** /**
@@ -149,8 +228,8 @@ typedef Property<Bool_t> BoolProperty;
*/ */
class EnumProperty : public Property<int> { class EnumProperty : public Property<int> {
public: public:
EnumProperty(Object* owner, const std::string& name, int* valuePtr, const std::vector<std::string>& labels, const std::string& units = "") EnumProperty(Object* owner, const std::string& name, int* valuePtr, const std::vector<std::string>& labels, const std::string& units = "", const std::string& group = "")
: Property<int>(owner, name, valuePtr, units), m_Labels(labels) {} : Property<int>(owner, name, valuePtr, units, group), m_Labels(labels) {}
const std::vector<std::string>& GetEnumLabels() const override { return m_Labels; } const std::vector<std::string>& GetEnumLabels() const override { return m_Labels; }
const char* GetTypeName() const override { return "Enum"; } const char* GetTypeName() const override { return "Enum"; }
@@ -209,11 +288,22 @@ public:
boost::archive::detail::common_oarchive<property_register_archive>(boost::archive::no_header), boost::archive::detail::common_oarchive<property_register_archive>(boost::archive::no_header),
m_Object(obj) {} m_Object(obj) {}
std::string GetCurrentGroup() const {
std::string group;
for (const auto& g : m_GroupStack) {
if (!group.empty()) group += ".";
group += g;
}
return group;
}
// Core logic: encounter HRP -> Create Dynamic Property // Core logic: encounter HRP -> Create Dynamic Property
template<class T> template<class T>
void save_override(const boost::serialization::hrp<T> &t) { void save_override(const boost::serialization::hrp<T> &t) {
if (m_Object) { if (m_Object) {
Property<T>* p = new Property<T>(m_Object, t.name(), &const_cast<boost::serialization::hrp<T>&>(t).value(), t.units() ? t.units() : ""); Property<T>* p = new Property<T>(m_Object, t.name(), &const_cast<boost::serialization::hrp<T>&>(t).value(), t.units() ? t.units() : "", GetCurrentGroup());
if (t.has_range()) p->SetRange(t.min_val(), t.max_val());
if (t.has_default()) p->SetDefault(t.default_val());
m_Object->RegisterDynamicProperty(p); m_Object->RegisterDynamicProperty(p);
} }
} }
@@ -221,7 +311,7 @@ public:
template<class T> template<class T>
void save_override(const boost::serialization::hrp_enum<T> &t) { void save_override(const boost::serialization::hrp_enum<T> &t) {
if (m_Object) { 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() : ""); EnumProperty* p = new EnumProperty(m_Object, t.name(), (int*)&const_cast<boost::serialization::hrp_enum<T>&>(t).value(), t.labels(), t.units() ? t.units() : "", GetCurrentGroup());
m_Object->RegisterDynamicProperty(p); m_Object->RegisterDynamicProperty(p);
} }
} }
@@ -229,11 +319,24 @@ public:
// Handle standard NVPs by recursing (important for base classes) // Handle standard NVPs by recursing (important for base classes)
template<class T> template<class T>
void save_override(const boost::serialization::nvp<T> &t) { void save_override(const boost::serialization::nvp<T> &t) {
boost::archive::detail::common_oarchive<property_register_archive>::save_override(t.const_value()); if (t.name()) m_GroupStack.push_back(t.name());
this->save_helper(t.const_value(), typename boost::is_class<T>::type());
if (t.name()) m_GroupStack.pop_back();
} }
// Ignore everything else // Recursion for nested classes, ignore primitives
template<class T> void save_override(const T &t) {} template<class T>
void save_override(const T &t) {
this->save_helper(t, typename boost::is_class<T>::type());
}
template<class T>
void save_helper(const T &t, boost::mpl::true_) {
boost::serialization::serialize_adl(*this, const_cast<T&>(t), 0);
}
template<class T>
void save_helper(const T &t, boost::mpl::false_) {}
// Required attribute overrides for common_oarchive // Required attribute overrides for common_oarchive
void save_override(const boost::archive::object_id_type & t) {} void save_override(const boost::archive::object_id_type & t) {}
@@ -244,6 +347,9 @@ public:
void save_override(const boost::archive::class_id_reference_type & t) {} void save_override(const boost::archive::class_id_reference_type & t) {}
void save_override(const boost::archive::class_name_type & t) {} void save_override(const boost::archive::class_name_type & t) {}
void save_override(const boost::archive::tracking_type & t) {} void save_override(const boost::archive::tracking_type & t) {}
private:
std::vector<std::string> m_GroupStack;
}; };
/** /**

View File

@@ -77,15 +77,30 @@ class hrp : public boost::serialization::wrapper_traits<hrp<T>> {
const char *m_name; const char *m_name;
const char *m_units; const char *m_units;
T &m_value; T &m_value;
bool m_has_range;
T m_min;
T m_max;
bool m_has_default;
T m_default;
public: public:
explicit hrp(const char *name_, T &t, const char* units_ = nullptr) : m_name(name_), m_units(units_), m_value(t) {} explicit hrp(const char *name_, T &t, const char* units_ = nullptr)
: m_name(name_), m_units(units_), m_value(t), m_has_range(false), m_has_default(false) {}
hrp& range(const T& min_val, const T& max_val) { m_min = min_val; m_max = max_val; m_has_range = true; return *this; }
hrp& set_default(const T& def_val) { m_default = def_val; m_has_default = true; return *this; }
const char *name() const { return this->m_name; } const char *name() const { return this->m_name; }
const char *units() const { return this->m_units; } const char *units() const { return this->m_units; }
T &value() { return this->m_value; } T &value() { return this->m_value; }
const T &const_value() const { return this->m_value; } const T &const_value() const { return this->m_value; }
bool has_range() const { return m_has_range; }
const T& min_val() const { return m_min; }
const T& max_val() const { return m_max; }
bool has_default() const { return m_has_default; }
const T& default_val() const { return m_default; }
BOOST_SERIALIZATION_SPLIT_MEMBER() BOOST_SERIALIZATION_SPLIT_MEMBER()
template <class Archivex> template <class Archivex>
@@ -110,16 +125,23 @@ class hrp_enum : public boost::serialization::wrapper_traits<hrp_enum<T>> {
const char *m_units; const char *m_units;
T &m_value; T &m_value;
std::vector<std::string> m_labels; std::vector<std::string> m_labels;
bool m_has_default;
T m_default;
public: public:
explicit hrp_enum(const char *name_, T &t, const std::vector<std::string>& labels, const char* units_ = nullptr) 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) {} : m_name(name_), m_units(units_), m_value(t), m_labels(labels), m_has_default(false) {}
hrp_enum& set_default(const T& def_val) { m_default = def_val; m_has_default = true; return *this; }
const char *name() const { return this->m_name; } const char *name() const { return this->m_name; }
const char *units() const { return this->m_units; } const char *units() const { return this->m_units; }
T &value() { return this->m_value; } T &value() { return this->m_value; }
const std::vector<std::string>& labels() const { return m_labels; } const std::vector<std::string>& labels() const { return m_labels; }
bool has_default() const { return m_has_default; }
const T& default_val() const { return m_default; }
BOOST_SERIALIZATION_SPLIT_MEMBER() BOOST_SERIALIZATION_SPLIT_MEMBER()
template <class Archivex> template <class Archivex>

View File

@@ -23,6 +23,7 @@ set( TESTS
VectorMetaAllocatorTest VectorMetaAllocatorTest
PropertyTypesTest PropertyTypesTest
HRPTest HRPTest
PropertyGroupingTest
MutexTest MutexTest
ThreadsTest ThreadsTest
OpenMPTest OpenMPTest

View File

@@ -0,0 +1,78 @@
#include <iostream>
#include <vector>
#include <string>
#include <cassert>
#include "Core/Object.h"
#include "Core/Property.h"
using namespace uLib;
struct Nested {
float x = 1.0f;
float y = 2.0f;
ULIB_SERIALIZE_ACCESS
template<class Archive>
void serialize(Archive & ar, const unsigned int version) {
ar & HRP(x);
ar & HRP(y);
}
};
class GroupObject : public Object {
uLibTypeMacro(GroupObject, Object)
public:
Nested position;
Nested orientation;
float weight = 50.0f;
ULIB_SERIALIZE_ACCESS
template<class Archive>
void serialize(Archive & ar, const unsigned int version) {
ar & boost::serialization::make_nvp("Position", position);
ar & boost::serialization::make_nvp("Orientation", orientation);
ar & HRP(weight);
}
};
int main() {
std::cout << "Testing Property Grouping..." << std::endl;
GroupObject obj;
ULIB_ACTIVATE_PROPERTIES(obj);
auto props = obj.GetProperties();
std::cout << "Registered " << props.size() << " properties." << std::endl;
for (auto* p : props) {
std::cout << "Prop: " << p->GetName()
<< " Group: " << p->GetGroup()
<< " Qualified: " << p->GetQualifiedName() << std::endl;
}
// Check if nested properties are registered
PropertyBase* p1 = obj.GetProperty("Position.x");
PropertyBase* p2 = obj.GetProperty("Position.y");
PropertyBase* p3 = obj.GetProperty("Orientation.x");
PropertyBase* p4 = obj.GetProperty("Orientation.y");
PropertyBase* p5 = obj.GetProperty("weight");
assert(p1 != nullptr && "Position.x not found");
assert(p2 != nullptr && "Position.y not found");
assert(p3 != nullptr && "Orientation.x not found");
assert(p4 != nullptr && "Orientation.y not found");
assert(p5 != nullptr && "weight not found");
assert(p1->GetGroup() == "Position");
assert(p2->GetGroup() == "Position");
assert(p3->GetGroup() == "Orientation");
assert(p4->GetGroup() == "Orientation");
assert(p5->GetGroup() == "");
assert(p1->GetQualifiedName() == "Position.x");
assert(p5->GetQualifiedName() == "weight");
std::cout << "Property Grouping Tests PASSED!" << std::endl;
return 0;
}

View File

@@ -23,7 +23,7 @@ class G4Event;
namespace uLib { namespace uLib {
namespace Geant { namespace Geant {
class EmitterPrimary : public G4VUserPrimaryGeneratorAction, public Object, public AffineTransform class EmitterPrimary : public G4VUserPrimaryGeneratorAction, public AffineTransform
{ {
public: public:

View File

@@ -25,7 +25,9 @@ Assembly::Assembly()
m_BBoxMin(Vector3f::Zero()), m_BBoxMin(Vector3f::Zero()),
m_BBoxMax(Vector3f::Zero()), m_BBoxMax(Vector3f::Zero()),
m_ShowBoundingBox(false), m_ShowBoundingBox(false),
m_GroupSelection(true) {} m_GroupSelection(true) {
ULIB_ACTIVATE_PROPERTIES(*this);
}
Assembly::Assembly(const Assembly &copy) Assembly::Assembly(const Assembly &copy)
: ObjectsContext(copy), : ObjectsContext(copy),
@@ -35,13 +37,25 @@ Assembly::Assembly(const Assembly &copy)
m_ShowBoundingBox(copy.m_ShowBoundingBox), m_ShowBoundingBox(copy.m_ShowBoundingBox),
m_GroupSelection(copy.m_GroupSelection) {} m_GroupSelection(copy.m_GroupSelection) {}
Assembly::~Assembly() {} Assembly::~Assembly() {
for (auto const& [obj, conn] : m_ChildConnections) {
conn.disconnect();
}
m_ChildConnections.clear();
}
void Assembly::AddObject(Object *obj) { void Assembly::AddObject(Object *obj) {
if (auto *at = dynamic_cast<AffineTransform *>(obj)) { if (auto *at = dynamic_cast<AffineTransform *>(obj)) {
at->SetParent(this); at->SetParent(this);
} }
ObjectsContext::AddObject(obj); ObjectsContext::AddObject(obj);
// Connect to child updates to recompute AABB
m_ChildConnections[obj] = Object::connect(obj, &Object::Updated, [this](){
this->ComputeBoundingBox();
this->Updated(); // Signal that assembly itself changed (AABB-wise)
});
this->ComputeBoundingBox();
} }
void Assembly::RemoveObject(Object *obj) { void Assembly::RemoveObject(Object *obj) {
@@ -49,7 +63,15 @@ void Assembly::RemoveObject(Object *obj) {
if (at->GetParent() == this) if (at->GetParent() == this)
at->SetParent(nullptr); at->SetParent(nullptr);
} }
auto itConn = m_ChildConnections.find(obj);
if (itConn != m_ChildConnections.end()) {
itConn->second.disconnect();
m_ChildConnections.erase(itConn);
}
ObjectsContext::RemoveObject(obj); ObjectsContext::RemoveObject(obj);
this->ComputeBoundingBox();
} }
void Assembly::ComputeBoundingBox() { void Assembly::ComputeBoundingBox() {
@@ -64,12 +86,11 @@ void Assembly::ComputeBoundingBox() {
m_BBoxMin = Vector3f(inf, inf, inf); m_BBoxMin = Vector3f(inf, inf, inf);
m_BBoxMax = Vector3f(-inf, -inf, -inf); m_BBoxMax = Vector3f(-inf, -inf, -inf);
Matrix4f invAsm = this->GetWorldMatrix().inverse();
for (Object *obj : objects) { for (Object *obj : objects) {
if (auto *box = dynamic_cast<ContainerBox *>(obj)) { if (auto *box = dynamic_cast<ContainerBox *>(obj)) {
// ContainerBox: wm is matrix from unit cube [0,1] to assembly base // ContainerBox: wm is matrix from unit cube [0,1] to local space
Matrix4f m = invAsm * box->GetWorldMatrix(); // Since it is parented to 'this', GetMatrix() is sufficient.
Matrix4f m = box->GetMatrix();
for (int i = 0; i < 8; ++i) { for (int i = 0; i < 8; ++i) {
float x = (i & 1) ? 1.0f : 0.0f; float x = (i & 1) ? 1.0f : 0.0f;
float y = (i & 2) ? 1.0f : 0.0f; float y = (i & 2) ? 1.0f : 0.0f;
@@ -82,7 +103,7 @@ void Assembly::ComputeBoundingBox() {
} }
} else if (auto *cyl = dynamic_cast<Cylinder *>(obj)) { } else if (auto *cyl = dynamic_cast<Cylinder *>(obj)) {
// Cylinder: centered [-1, 1] radial, [-0.5, 0.5] height // Cylinder: centered [-1, 1] radial, [-0.5, 0.5] height
Matrix4f m = invAsm * cyl->GetWorldMatrix(); Matrix4f m = cyl->GetMatrix();
for (int i = 0; i < 8; ++i) { for (int i = 0; i < 8; ++i) {
float x = (i & 1) ? 1.0f : -1.0f; float x = (i & 1) ? 1.0f : -1.0f;
float y = (i & 2) ? 0.5f : -0.5f; float y = (i & 2) ? 0.5f : -0.5f;
@@ -98,7 +119,7 @@ void Assembly::ComputeBoundingBox() {
subAsm->ComputeBoundingBox(); subAsm->ComputeBoundingBox();
Vector3f subMin, subMax; Vector3f subMin, subMax;
subAsm->GetBoundingBox(subMin, subMax); subAsm->GetBoundingBox(subMin, subMax);
Matrix4f m = invAsm * subAsm->GetWorldMatrix(); Matrix4f m = subAsm->GetMatrix();
for (int i = 0; i < 8; ++i) { for (int i = 0; i < 8; ++i) {
float x = (i & 1) ? subMax(0) : subMin(0); float x = (i & 1) ? subMax(0) : subMin(0);
float y = (i & 2) ? subMax(1) : subMin(1); float y = (i & 2) ? subMax(1) : subMin(1);

View File

@@ -45,12 +45,19 @@ namespace uLib {
*/ */
class Assembly : public ObjectsContext, public AffineTransform { class Assembly : public ObjectsContext, public AffineTransform {
public: public:
uLibTypeMacro(Assembly, ObjectsContext, AffineTransform)
virtual const char *GetClassName() const override { return "Assembly"; } virtual const char *GetClassName() const override { return "Assembly"; }
Assembly(); Assembly();
Assembly(const Assembly &copy); Assembly(const Assembly &copy);
virtual ~Assembly(); virtual ~Assembly();
template <class ArchiveT>
void serialize(ArchiveT & ar, const unsigned int version) {
ar & boost::serialization::make_nvp("AffineTransform", boost::serialization::base_object<AffineTransform>(*this));
ar & boost::serialization::make_hrp("GroupSelection", m_GroupSelection);
}
virtual void AddObject(Object* obj) override; virtual void AddObject(Object* obj) override;
virtual void RemoveObject(Object* obj) override; virtual void RemoveObject(Object* obj) override;
@@ -92,7 +99,7 @@ signals:
if (m_InUpdated) return; // break signal recursion if (m_InUpdated) return; // break signal recursion
m_InUpdated = true; m_InUpdated = true;
this->ComputeBoundingBox(); this->ComputeBoundingBox();
ULIB_SIGNAL_EMIT(Assembly::Updated); ULIB_SIGNAL_EMIT(Object::Updated);
m_InUpdated = false; m_InUpdated = false;
} }
@@ -102,6 +109,7 @@ private:
bool m_ShowBoundingBox; bool m_ShowBoundingBox;
bool m_GroupSelection; bool m_GroupSelection;
bool m_InUpdated = false; bool m_InUpdated = false;
std::map<Object*, Connection> m_ChildConnections;
}; };
} // namespace uLib } // namespace uLib

View File

@@ -44,16 +44,17 @@ namespace uLib {
* that defines the box's specific origin and size relative to its own * that defines the box's specific origin and size relative to its own
* coordinate system. * coordinate system.
*/ */
class ContainerBox : public AffineTransform, public Object { class ContainerBox : public AffineTransform {
typedef AffineTransform BaseClass;
public: public:
uLibTypeMacro(ContainerBox, AffineTransform)
virtual const char * GetClassName() const override { return "ContainerBox"; }
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// PROPERTIES // // PROPERTIES //
Property<Vector3f> p_Size; Vector3f Size;
Property<Vector3f> p_Origin; Vector3f Origin;
virtual const char * GetClassName() const { return "ContainerBox"; }
/** /**
* @brief Default constructor. * @brief Default constructor.
@@ -61,10 +62,10 @@ public:
*/ */
ContainerBox() ContainerBox()
: m_LocalT(this), // BaseClass is Parent of m_LocalTransform : m_LocalT(this), // BaseClass is Parent of m_LocalTransform
p_Size(this, "Size", Vector3f(1.0f, 1.0f, 1.0f)), Size(1.0f, 1.0f, 1.0f),
p_Origin(this, "Origin", Vector3f(0.0f, 0.0f, 0.0f)) { Origin(0.0f, 0.0f, 0.0f) {
Object::connect(&p_Size, &Property<Vector3f>::PropertyChanged, this, &ContainerBox::SyncSize); ULIB_ACTIVATE_PROPERTIES(*this);
Object::connect(&p_Origin, &Property<Vector3f>::PropertyChanged, this, &ContainerBox::SyncOrigin); this->Sync();
} }
/** /**
@@ -73,11 +74,10 @@ public:
*/ */
ContainerBox(const Vector3f &size) ContainerBox(const Vector3f &size)
: m_LocalT(this), : m_LocalT(this),
p_Size(this, "Size", size), Size(size),
p_Origin(this, "Origin", Vector3f(0.0f, 0.0f, 0.0f)) { Origin(0.0f, 0.0f, 0.0f) {
Object::connect(&p_Size, &Property<Vector3f>::PropertyChanged, this, &ContainerBox::SyncSize); ULIB_ACTIVATE_PROPERTIES(*this);
Object::connect(&p_Origin, &Property<Vector3f>::PropertyChanged, this, &ContainerBox::SyncOrigin); this->Sync();
this->SetSize(size);
} }
/** /**
@@ -85,13 +85,21 @@ public:
* @param copy The ContainerBox instance to copy from. * @param copy The ContainerBox instance to copy from.
*/ */
ContainerBox(const ContainerBox &copy) ContainerBox(const ContainerBox &copy)
: m_LocalT(copy.m_LocalT), // Copy local transform state : m_LocalT(this), // Reset parent to the new object
AffineTransform(copy), AffineTransform(copy),
p_Size(this, "Size", copy.p_Size), Size(copy.Size),
p_Origin(this, "Origin", copy.p_Origin) { Origin(copy.Origin) {
m_LocalT.SetParent(this); // Reset parent to the new object ULIB_ACTIVATE_PROPERTIES(*this);
Object::connect(&p_Size, &Property<Vector3f>::PropertyChanged, this, &ContainerBox::SyncSize); this->Sync();
Object::connect(&p_Origin, &Property<Vector3f>::PropertyChanged, this, &ContainerBox::SyncOrigin); }
/**
* @brief Serialization template for property registration and persistence.
*/
template <class ArchiveT>
void serialize(ArchiveT & ar, const unsigned int version) {
ar & HRP(Size);
ar & HRP(Origin);
} }
/** /**
@@ -99,7 +107,7 @@ public:
* @param v The origin position vector. * @param v The origin position vector.
*/ */
void SetOrigin(const Vector3f &v) { void SetOrigin(const Vector3f &v) {
p_Origin = v; Origin = v;
m_LocalT.SetPosition(v); m_LocalT.SetPosition(v);
} }
@@ -115,7 +123,7 @@ public:
* @param v The size vector (width, height, depth). * @param v The size vector (width, height, depth).
*/ */
void SetSize(const Vector3f &v) { void SetSize(const Vector3f &v) {
p_Size = v; Size = v;
Vector3f pos = this->GetOrigin(); Vector3f pos = this->GetOrigin();
m_LocalT = AffineTransform(this); // regenerate local transform m_LocalT = AffineTransform(this); // regenerate local transform
m_LocalT.Scale(v); m_LocalT.Scale(v);
@@ -194,26 +202,27 @@ public:
} }
/** Translate using transformation chain */ /** Translate using transformation chain */
using BaseClass::Translate; using AffineTransform::Translate;
/** Rotate using transformation chain */ /** Rotate using transformation chain */
using BaseClass::Rotate; using AffineTransform::Rotate;
/** Scale using transformation chain */ /** Scale using transformation chain */
using BaseClass::Scale; using AffineTransform::Scale;
signals: signals:
// signal to emit when the box is updated // /** Signal emitted when properties change */
virtual void Updated() override { ULIB_SIGNAL_EMIT(ContainerBox::Updated); } virtual void Updated() override {
this->Sync();
private slots: ULIB_SIGNAL_EMIT(Object::Updated);
void SyncSize() {
this->SetSize(p_Size);
} }
void SyncOrigin() { private:
this->SetOrigin(p_Origin); /** Synchronizes internal transformation with properties */
void Sync() {
this->SetOrigin(Origin);
this->SetSize(Size);
} }

View File

@@ -39,10 +39,17 @@ namespace uLib {
* The cylinder orientation is defined by the Axis property (0=X, 1=Y, 2=Z). * The cylinder orientation is defined by the Axis property (0=X, 1=Y, 2=Z).
* By default, it is aligned with the Y axis (Axis=1). * By default, it is aligned with the Y axis (Axis=1).
*/ */
class Cylinder : public AffineTransform, public Object { class Cylinder : public AffineTransform {
public: public:
uLibTypeMacro(Cylinder, Object) uLibTypeMacro(Cylinder, AffineTransform)
/**
* @brief PROPERTIES
*/
float Radius;
float Height;
int Axis;
virtual const char * GetClassName() const override { return "Cylinder"; } virtual const char * GetClassName() const override { return "Cylinder"; }
@@ -51,7 +58,7 @@ public:
*/ */
Cylinder() : m_LocalT(this), Radius(1.0), Height(1.0), Axis(1) { Cylinder() : m_LocalT(this), Radius(1.0), Height(1.0), Axis(1) {
ULIB_ACTIVATE_PROPERTIES(*this); ULIB_ACTIVATE_PROPERTIES(*this);
UpdateLocalMatrix(); this->Sync();
} }
/** /**
@@ -60,7 +67,7 @@ public:
Cylinder(float radius, float height, int axis = 1) Cylinder(float radius, float height, int axis = 1)
: m_LocalT(this), Radius(radius), Height(height), Axis(axis) { : m_LocalT(this), Radius(radius), Height(height), Axis(axis) {
ULIB_ACTIVATE_PROPERTIES(*this); ULIB_ACTIVATE_PROPERTIES(*this);
UpdateLocalMatrix(); this->Sync();
} }
/** /**
@@ -69,7 +76,7 @@ public:
Cylinder(const Cylinder &copy) Cylinder(const Cylinder &copy)
: m_LocalT(this), AffineTransform(copy), Radius(copy.Radius), Height(copy.Height), Axis(copy.Axis) { : m_LocalT(this), AffineTransform(copy), Radius(copy.Radius), Height(copy.Height), Axis(copy.Axis) {
ULIB_ACTIVATE_PROPERTIES(*this); ULIB_ACTIVATE_PROPERTIES(*this);
this->UpdateLocalMatrix(); this->Sync();
} }
/** /**
@@ -85,7 +92,7 @@ public:
/** Sets the radius of the cylinder */ /** Sets the radius of the cylinder */
inline void SetRadius(float r) { inline void SetRadius(float r) {
Radius = r; Radius = r;
UpdateLocalMatrix(); this->Sync();
} }
/** Gets the radius of the cylinder */ /** Gets the radius of the cylinder */
@@ -94,7 +101,7 @@ public:
/** Sets the height of the cylinder */ /** Sets the height of the cylinder */
inline void SetHeight(float h) { inline void SetHeight(float h) {
Height = h; Height = h;
UpdateLocalMatrix(); this->Sync();
} }
/** Gets the height of the cylinder */ /** Gets the height of the cylinder */
@@ -103,7 +110,7 @@ public:
/** Sets the main axis (0=X, 1=Y, 2=Z) */ /** Sets the main axis (0=X, 1=Y, 2=Z) */
inline void SetAxis(int axis) { inline void SetAxis(int axis) {
Axis = axis; Axis = axis;
UpdateLocalMatrix(); this->Sync();
} }
/** Gets the main axis */ /** Gets the main axis */
@@ -157,25 +164,33 @@ public:
return Vector3f(r, theta, h); return Vector3f(r, theta, h);
} }
/** Translate using transformation chain */
using AffineTransform::Translate;
/** Rotate using transformation chain */
using AffineTransform::Rotate;
/** Scale using transformation chain */
using AffineTransform::Scale;
signals: signals:
/** Signal emitted when properties change */ /** Signal emitted when properties change */
virtual void Updated() override { virtual void Updated() override {
this->UpdateLocalMatrix(); this->Sync();
ULIB_SIGNAL_EMIT(Cylinder::Updated); ULIB_SIGNAL_EMIT(Object::Updated);
} }
private: private:
/** Recalculates the internal local matrix based on dimensions and axis */ /** Synchronizes internal transformation with properties */
void UpdateLocalMatrix() { void Sync() {
m_LocalT = AffineTransform(this); m_LocalT = AffineTransform(this);
if (Axis == 0) m_LocalT.Scale(Vector3f(Height, Radius, Radius)); if (Axis == 0) m_LocalT.Scale(Vector3f(Height, Radius, Radius));
else if (Axis == 1) m_LocalT.Scale(Vector3f(Radius, Height, Radius)); else if (Axis == 1) m_LocalT.Scale(Vector3f(Radius, Height, Radius));
else m_LocalT.Scale(Vector3f(Radius, Radius, Height)); else m_LocalT.Scale(Vector3f(Radius, Radius, Height));
} }
float Radius;
float Height; private:
int Axis;
AffineTransform m_LocalT; AffineTransform m_LocalT;
}; };

View File

@@ -35,10 +35,11 @@
namespace uLib { namespace uLib {
class Geometry : public AffineTransform, public Object { class Geometry : public AffineTransform {
public: public:
uLibTypeMacro(Geometry, AffineTransform)
virtual const char * GetClassName() const { return "Geometry"; } virtual const char * GetClassName() const override { return "Geometry"; }
virtual Vector3f ToLinear(const Vector3f& curved_space) const { virtual Vector3f ToLinear(const Vector3f& curved_space) const {
return curved_space; return curved_space;
@@ -70,6 +71,7 @@ public:
class CylindricalGeometry : public Geometry { class CylindricalGeometry : public Geometry {
public: public:
uLibTypeMacro(CylindricalGeometry, Geometry)
CylindricalGeometry() {} CylindricalGeometry() {}
Vector3f ToLinear(const Vector3f& cylindrical) const { Vector3f ToLinear(const Vector3f& cylindrical) const {
@@ -88,9 +90,10 @@ public:
class SphericalGeometry : public Geometry { class SphericalGeometry : public Geometry {
public: public:
uLibTypeMacro(SphericalGeometry, Geometry)
SphericalGeometry() {} SphericalGeometry() {}
virtual const char * GetClassName() const { return "SphericalGeometry"; } virtual const char * GetClassName() const override { return "SphericalGeometry"; }
Vector3f ToLinear(const Vector3f& spherical) const { Vector3f ToLinear(const Vector3f& spherical) const {
float r = spherical.x(); float r = spherical.x();
@@ -112,9 +115,10 @@ public:
class ToroidalGeometry : public Geometry { class ToroidalGeometry : public Geometry {
public: public:
uLibTypeMacro(ToroidalGeometry, Geometry)
ToroidalGeometry(float Rtor) : m_Rtor(Rtor) {} ToroidalGeometry(float Rtor) : m_Rtor(Rtor) {}
virtual const char * GetClassName() const { return "ToroidalGeometry"; } virtual const char * GetClassName() const override { return "ToroidalGeometry"; }
Vector3f ToLinear(const Vector3f& toroidal) const { Vector3f ToLinear(const Vector3f& toroidal) const {
float r = toroidal.x(); float r = toroidal.x();

View File

@@ -5,12 +5,14 @@
#include "Math/TriangleMesh.h" #include "Math/TriangleMesh.h"
#include "Math/QuadMesh.h" #include "Math/QuadMesh.h"
#include "Math/VoxImage.h" #include "Math/VoxImage.h"
#include "Math/Assembly.h"
#include "Math/StructuredData.h" #include "Math/StructuredData.h"
namespace uLib { namespace uLib {
ULIB_REGISTER_OBJECT(ContainerBox) ULIB_REGISTER_OBJECT(ContainerBox)
ULIB_REGISTER_OBJECT(Cylinder) ULIB_REGISTER_OBJECT(Cylinder)
ULIB_REGISTER_OBJECT(Assembly)
ULIB_REGISTER_OBJECT(CylindricalGeometry) ULIB_REGISTER_OBJECT(CylindricalGeometry)
ULIB_REGISTER_OBJECT(SphericalGeometry) ULIB_REGISTER_OBJECT(SphericalGeometry)
ULIB_REGISTER_OBJECT(TriangleMesh) ULIB_REGISTER_OBJECT(TriangleMesh)

View File

@@ -34,11 +34,12 @@
namespace uLib { namespace uLib {
class QuadMesh : public AffineTransform, public Object class QuadMesh : public AffineTransform
{ {
public: public:
uLibTypeMacro(QuadMesh, AffineTransform)
virtual const char * GetClassName() const { return "QuadMesh"; } virtual const char * GetClassName() const override { return "QuadMesh"; }
void PrintSelf(std::ostream &o); void PrintSelf(std::ostream &o);

View File

@@ -50,36 +50,130 @@
#define U_TRANSFORM_H #define U_TRANSFORM_H
#include <Eigen/Geometry> #include <Eigen/Geometry>
#include "Math/Units.h"
#include "Math/Dense.h" #include "Math/Dense.h"
namespace uLib { namespace uLib {
////////////////////////////////////////////////////////////////////////////////
///////// TRS PARAMETERS /////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
typedef Eigen::Affine3f AffineMatrix;
class TRS {
public:
Vector3f position = Vector3f::Zero();
Vector3f rotation = Vector3f::Zero();
Vector3f scaling = Vector3f::Ones();
TRS() = default;
TRS(const class AffineTransform& at);
TRS(const Matrix4f& mat) {
this->FromMatrix(mat);
}
void FromMatrix(const Matrix4f& mat) {
this->position = mat.block<3,1>(0,3);
Matrix3f linear = mat.block<3,3>(0,0);
this->scaling(0) = linear.col(0).norm();
this->scaling(1) = linear.col(1).norm();
this->scaling(2) = linear.col(2).norm();
Matrix3f rot = linear;
if (this->scaling(0) > 1e-6) rot.col(0) /= this->scaling(0);
if (this->scaling(1) > 1e-6) rot.col(1) /= this->scaling(1);
if (this->scaling(2) > 1e-6) rot.col(2) /= this->scaling(2);
// Decompose to Euler angles matching VTK (M = Rz * Ry * Rx)
// Store internally as RADIANS (standard for uLib properties)
Vector3f euler = rot.eulerAngles(2, 1, 0);
this->rotation = Vector3f(euler(2), euler(1), euler(0));
}
template <class ArchiveT>
void serialize(ArchiveT & ar, const unsigned int version) {
ar & HRPU(position, "mm");
ar & HRPU(rotation, "deg"); // Metadata informs UI to convert to/from degrees
ar & HRP(scaling);
}
AffineMatrix GetAffineMatrix() const {
AffineMatrix t = AffineMatrix::Identity();
t.translate(position);
// rotation is in Radians here
t.rotate(Eigen::AngleAxisf(rotation.z(), Vector3f::UnitZ()));
t.rotate(Eigen::AngleAxisf(rotation.y(), Vector3f::UnitY()));
t.rotate(Eigen::AngleAxisf(rotation.x(), Vector3f::UnitX()));
t.scale(scaling);
return t;
}
};
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
///////// AFFINE TRANSFORM WRAPPER ////////////////////////////////////////// ///////// AFFINE TRANSFORM WRAPPER //////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
class AffineTransform { class AffineTransform : virtual public Object {
public:
uLibTypeMacro(AffineTransform, Object)
TRS Transform;
private:
void NotifyProperties() {
PropertyBase *p;
if ((p = this->GetProperty("Transform.position"))) p->Updated();
if ((p = this->GetProperty("Transform.rotation"))) p->Updated();
if ((p = this->GetProperty("Transform.scaling"))) p->Updated();
}
protected: protected:
Eigen::Affine3f m_T; Eigen::Affine3f m_T;
AffineTransform *m_Parent; AffineTransform *m_Parent;
public: public:
AffineTransform() : AffineTransform() :
m_T(Matrix4f::Identity()), m_T(Matrix4f::Identity()),
m_Parent(NULL) m_Parent(NULL)
{} {
ULIB_ACTIVATE_PROPERTIES(*this);
this->Sync();
}
virtual ~AffineTransform() {} virtual ~AffineTransform() {}
AffineTransform(AffineTransform *parent) : AffineTransform(AffineTransform *parent) :
m_T(Matrix4f::Identity()), m_T(Matrix4f::Identity()),
m_Parent(parent) m_Parent(parent)
{} {
ULIB_ACTIVATE_PROPERTIES(*this);
this->Sync();
}
AffineTransform(const AffineTransform &copy) : AffineTransform(const AffineTransform &copy) :
m_T(copy.m_T), m_T(copy.m_T),
m_Parent(copy.m_Parent) m_Parent(copy.m_Parent),
{} Transform(copy.Transform)
{
ULIB_ACTIVATE_PROPERTIES(*this);
this->Sync();
}
/**
* @brief Registration of properties in groups.
*/
template <class ArchiveT>
void serialize(ArchiveT & ar, const unsigned int version) {
ar & boost::serialization::make_nvp("Transform", Transform);
}
Eigen::Affine3f& GetTransform() { return m_T; } Eigen::Affine3f& GetTransform() { return m_T; }
@@ -87,7 +181,11 @@ public:
void SetParent(AffineTransform *name) { this->m_Parent = name; } void SetParent(AffineTransform *name) { this->m_Parent = name; }
void SetMatrix (Matrix4f mat) { m_T.matrix() = mat; } void SetMatrix (Matrix4f mat) {
m_T.matrix() = mat;
this->UpdatePropertiesFromMatrix();
}
Matrix4f GetMatrix() const { return m_T.matrix(); } Matrix4f GetMatrix() const { return m_T.matrix(); }
Matrix4f GetWorldMatrix() const Matrix4f GetWorldMatrix() const
@@ -96,32 +194,56 @@ public:
else return m_Parent->GetWorldMatrix() * m_T.matrix(); // T = B * A // else return m_Parent->GetWorldMatrix() * m_T.matrix(); // T = B * A //
} }
void SetPosition(const Vector3f v) { this->m_T.translation() = v; } void SetPosition(const Vector3f v) {
this->Transform.position = v;
this->Updated();
this->NotifyProperties();
}
Vector3f GetPosition() const { return this->Transform.position; }
Vector3f GetPosition() const { return this->m_T.translation(); } void SetOrientation(const Vector3f v) {
this->Transform.rotation = v;
this->Updated();
this->NotifyProperties();
}
Vector3f GetOrientation() const { return this->Transform.rotation; }
void SetRotation(const Matrix3f m) { this->m_T.linear() = m; } void SetScale(const Vector3f v) {
this->Transform.scaling = v;
this->Updated();
this->NotifyProperties();
}
Vector3f GetScale() const { return this->Transform.scaling; }
void SetRotation(const Matrix3f m) {
this->m_T.linear() = m;
this->UpdatePropertiesFromMatrix();
}
Matrix3f GetRotation() const { return this->m_T.rotation(); } Matrix3f GetRotation() const { return this->m_T.rotation(); }
void Translate(const Vector3f v) { this->m_T.translate(v); } void Translate(const Vector3f v) {
this->Transform.position += v;
this->Sync();
}
void Scale(const Vector3f v) { this->m_T.scale(v); } void Scale(const Vector3f v) {
this->Transform.scaling = this->Transform.scaling.cwiseProduct(v);
Vector3f GetScale() const { this->Sync();
return Vector3f(m_T.linear().col(0).norm(),
m_T.linear().col(1).norm(),
m_T.linear().col(2).norm());
} }
void Rotate(const Matrix3f m) { this->m_T.rotate(m); } void Rotate(const Matrix3f m) {
this->m_T.rotate(m);
this->UpdatePropertiesFromMatrix();
}
void Rotate(const float angle, Vector3f axis) void Rotate(const float angle, Vector3f axis)
{ {
axis.normalize(); // prehaps not necessary ( see eigens ) axis.normalize();
Eigen::AngleAxisf ax(angle,axis); Eigen::AngleAxisf ax(angle,axis);
this->m_T.rotate(Eigen::Quaternion<float>(ax)); this->m_T.rotate(Eigen::Quaternion<float>(ax));
this->UpdatePropertiesFromMatrix();
} }
void Rotate(const Vector3f euler_axis) { void Rotate(const Vector3f euler_axis) {
@@ -129,17 +251,14 @@ public:
Rotate(angle,euler_axis); Rotate(angle,euler_axis);
} }
void PreRotate(const Matrix3f m) { this->m_T.prerotate(m); } void PreRotate(const Matrix3f m) { this->m_T.prerotate(m); this->UpdatePropertiesFromMatrix(); }
void QuaternionRotate(const Vector4f q) void QuaternionRotate(const Vector4f q)
{ this->m_T.rotate(Eigen::Quaternion<float>(q)); } { this->m_T.rotate(Eigen::Quaternion<float>(q)); this->UpdatePropertiesFromMatrix(); }
void EulerYZYRotate(const Vector3f e) { void EulerYZYRotate(const Vector3f e) {
Matrix3f mat; this->Transform.rotation = e;
mat = Eigen::AngleAxisf(e.x(), Vector3f::UnitY()) this->Sync();
* Eigen::AngleAxisf(e.y(), Vector3f::UnitZ())
* Eigen::AngleAxisf(e.z(), Vector3f::UnitY());
m_T.rotate(mat);
} }
void FlipAxes(int first, int second) void FlipAxes(int first, int second)
@@ -147,9 +266,42 @@ public:
Matrix3f mat = Matrix3f::Identity(); Matrix3f mat = Matrix3f::Identity();
mat.col(first).swap(mat.col(second)); mat.col(first).swap(mat.col(second));
m_T.rotate(mat); m_T.rotate(mat);
this->UpdatePropertiesFromMatrix();
}
/**
* @brief Decomposes the internal matrix m_T back into Position, Orientation, and Scale properties.
*/
void UpdatePropertiesFromMatrix() {
this->Transform.FromMatrix(this->GetMatrix());
PropertyBase *p;
if ((p = this->GetProperty("Transform.position"))) p->Updated();
if ((p = this->GetProperty("Transform.rotation"))) p->Updated();
if ((p = this->GetProperty("Transform.scaling"))) p->Updated();
}
signals:
/** Signal emitted when properties change */
virtual void Updated() override {
this->Sync();
ULIB_SIGNAL_EMIT(Object::Updated);
}
private:
void Sync() {
m_T.matrix() = this->Transform.GetAffineMatrix().matrix();
} }
}; };
inline TRS::TRS(const AffineTransform& at) {
this->position = at.GetPosition();
this->rotation = at.GetOrientation();
this->scaling = at.GetScale();
}
} }

View File

@@ -37,11 +37,12 @@
namespace uLib { namespace uLib {
class TriangleMesh : public AffineTransform, public Object class TriangleMesh : public AffineTransform
{ {
public: public:
uLibTypeMacro(TriangleMesh, AffineTransform)
virtual const char * GetClassName() const { return "TriangleMesh"; } virtual const char * GetClassName() const override { return "TriangleMesh"; }
void PrintSelf(std::ostream &o); void PrintSelf(std::ostream &o);

View File

@@ -3,7 +3,6 @@ set(HEADERS uLibVtkInterface.h
vtkHandlerWidget.h vtkHandlerWidget.h
vtkQViewport.h vtkQViewport.h
vtkViewport.h vtkViewport.h
vtkPolydata.h
vtkObjectsContext.h vtkObjectsContext.h
) )
@@ -12,7 +11,6 @@ set(SOURCES uLibVtkInterface.cxx
vtkHandlerWidget.cpp vtkHandlerWidget.cpp
vtkQViewport.cpp vtkQViewport.cpp
vtkViewport.cpp vtkViewport.cpp
vtkPolydata.cpp
vtkObjectsContext.cpp vtkObjectsContext.cpp
) )

View File

@@ -45,7 +45,7 @@
#include "HEP/Detectors/MuonEvent.h" #include "HEP/Detectors/MuonEvent.h"
#include "Vtk/uLibVtkInterface.h" #include "Vtk/uLibVtkInterface.h"
#include "Vtk/vtkPolydata.h" #include "Vtk/Math/vtkPolydata.h"
namespace uLib { namespace uLib {
namespace Vtk { namespace Vtk {

View File

@@ -46,7 +46,7 @@
#include "HEP/Detectors/MuonScatter.h" #include "HEP/Detectors/MuonScatter.h"
#include "Vtk/uLibVtkInterface.h" #include "Vtk/uLibVtkInterface.h"
#include "Vtk/vtkPolydata.h" #include "Vtk/Math/vtkPolydata.h"
class vtkRenderWindowInteractor; class vtkRenderWindowInteractor;

View File

@@ -28,7 +28,7 @@
#include "HEP/Geant/GeantEvent.h" #include "HEP/Geant/GeantEvent.h"
#include "uLibVtkInterface.h" #include "uLibVtkInterface.h"
#include "vtkPolydata.h" #include "Vtk/Math/vtkPolydata.h"
#include <vtkActor.h> #include <vtkActor.h>
namespace uLib { namespace uLib {

View File

@@ -28,7 +28,7 @@
#include "HEP/Geant/Solid.h" #include "HEP/Geant/Solid.h"
#include "uLibVtkInterface.h" #include "uLibVtkInterface.h"
#include "vtkPolydata.h" #include "Vtk/Math/vtkPolydata.h"
class vtkActor; class vtkActor;

View File

@@ -25,27 +25,11 @@
#ifndef U_VTKULIBPROP_H #ifdef HAVE_CONFIG_H
#define U_VTKULIBPROP_H #include "config.h"
#endif
class vtkProp; #include <Vtk/vtkMuonContainerScattering.h>
namespace uLib {
namespace Abstract {
class uLibVtkProp {
public:
virtual vtkProp *GetProp() = 0;
protected:
~uLibVtkProp() {}
};
// TO BE CONTINUED //
}
}
#endif // VTKULIBPROP_H

View File

@@ -0,0 +1,74 @@
/*//////////////////////////////////////////////////////////////////////////////
// CMT Cosmic Muon Tomography project //////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
Copyright (c) 2014, Universita' degli Studi di Padova, INFN sez. di Padova
All rights reserved
Authors: Andrea Rigoni Garola < andrea.rigoni@pd.infn.it >
------------------------------------------------------------------
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 3.0 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library.
//////////////////////////////////////////////////////////////////////////////*/
#ifndef VTKMUONCONTAINERSCATTERING_H
#define VTKMUONCONTAINERSCATTERING_H
#include "Math/Dense.h"
#include "uLibVtkInterface.h"
#include "Detectors/MuonScatter.h"
class vtkRenderWindowInteractor;
namespace uLib {
class vtkMuonContainerScattering : public Abstract::uLibVtkPolydata {
typedef MuonScatter Content;
public:
vtkMuonContainerScattering(const MuonScatter &content);
~vtkMuonScatter();
Content& GetContent();
void PrintSelf(std::ostream &o) const;
virtual vtkProp *GetProp();
virtual vtkPolyData* GetPolyData() const;
void AddPocaPoint(HPoint3f poca);
HPoint3f GetPocaPoint();
void vtkStartInteractive();
protected:
void ConnectInteractor(vtkRenderWindowInteractor *interactor);
private:
void InstallPipe();
};
}
#endif // VTKMUONCONTAINERSCATTERING_H

View File

@@ -11,6 +11,7 @@ set(MATH_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/vtkContainerBox.cpp ${CMAKE_CURRENT_SOURCE_DIR}/vtkContainerBox.cpp
${CMAKE_CURRENT_SOURCE_DIR}/vtkCylinder.cpp ${CMAKE_CURRENT_SOURCE_DIR}/vtkCylinder.cpp
${CMAKE_CURRENT_SOURCE_DIR}/vtkAssembly.cpp ${CMAKE_CURRENT_SOURCE_DIR}/vtkAssembly.cpp
${CMAKE_CURRENT_SOURCE_DIR}/vtkPolydata.cpp
PARENT_SCOPE) PARENT_SCOPE)
set(MATH_HEADERS set(MATH_HEADERS
@@ -22,6 +23,7 @@ set(MATH_HEADERS
${CMAKE_CURRENT_SOURCE_DIR}/vtkContainerBox.h ${CMAKE_CURRENT_SOURCE_DIR}/vtkContainerBox.h
${CMAKE_CURRENT_SOURCE_DIR}/vtkCylinder.h ${CMAKE_CURRENT_SOURCE_DIR}/vtkCylinder.h
${CMAKE_CURRENT_SOURCE_DIR}/vtkAssembly.h ${CMAKE_CURRENT_SOURCE_DIR}/vtkAssembly.h
${CMAKE_CURRENT_SOURCE_DIR}/vtkPolydata.h
PARENT_SCOPE) PARENT_SCOPE)
if(BUILD_TESTING) if(BUILD_TESTING)

View File

@@ -70,6 +70,7 @@ void Assembly::InstallPipe() {
m_BBoxActor->GetProperty()->SetColor(1.0, 0.85, 0.0); // gold wireframe m_BBoxActor->GetProperty()->SetColor(1.0, 0.85, 0.0); // gold wireframe
m_BBoxActor->GetProperty()->SetLineWidth(1.5); m_BBoxActor->GetProperty()->SetLineWidth(1.5);
m_BBoxActor->GetProperty()->SetOpacity(0.6); m_BBoxActor->GetProperty()->SetOpacity(0.6);
m_BBoxActor->PickableOff();
m_BBoxActor->SetVisibility(m_Content ? m_Content->GetShowBoundingBox() : false); m_BBoxActor->SetVisibility(m_Content ? m_Content->GetShowBoundingBox() : false);
m_VtkAsm->AddPart(m_BBoxActor); m_VtkAsm->AddPart(m_BBoxActor);
@@ -92,13 +93,12 @@ void Assembly::InstallPipe() {
void Assembly::contentUpdate() { void Assembly::contentUpdate() {
if (m_InUpdate) return; if (m_InUpdate) return;
m_InUpdate = true; m_InUpdate = true;
m_BlockUpdate = false;
this->UpdateTransform(); this->UpdateTransform();
this->UpdateBoundingBox(); this->UpdateBoundingBox();
if (m_ChildContext) if (m_ChildContext)
m_ChildContext->Update(); m_ChildContext->Update();
m_BlockUpdate = true;
Puppet::Update(); Puppet::Update();
m_InUpdate = false; m_InUpdate = false;
} }
@@ -106,25 +106,29 @@ void Assembly::contentUpdate() {
// ------------------------------------------------------------------ // // ------------------------------------------------------------------ //
void Assembly::Update() { void Assembly::Update() {
if (m_InUpdate) return; if (m_InUpdate) return;
if (!m_Content || !m_VtkAsm) return; m_InUpdate = true;
this->contentUpdate();
m_InUpdate = false;
}
if (m_BlockUpdate) { void Assembly::SyncFromVtk() {
m_BlockUpdate = false; if (m_InUpdate) return;
return; if (!m_Content || !m_VtkAsm) return;
}
m_InUpdate = true; m_InUpdate = true;
// Pull VTK transform back into the uLib model double pos[3], ori[3], scale[3];
vtkMatrix4x4* vmat = m_VtkAsm->GetUserMatrix(); m_VtkAsm->GetPosition(pos);
if (vmat) { m_VtkAsm->GetOrientation(ori);
Matrix4f transform = VtkToMatrix4f(vmat); m_VtkAsm->GetScale(scale);
m_Content->SetMatrix(transform);
} m_Content->SetPosition(Vector3f(pos[0], pos[1], pos[2]));
m_Content->SetOrientation(Vector3f(ori[0], ori[1], ori[2]) * CLHEP::degree);
m_Content->SetScale(Vector3f(scale[0], scale[1], scale[2]));
this->UpdateBoundingBox(); this->UpdateBoundingBox();
if (m_ChildContext) if (m_ChildContext)
m_ChildContext->Update(); m_ChildContext->SyncFromVtk();
m_Content->Updated(); // Notify change in model m_Content->Updated(); // Notify change in model
@@ -135,10 +139,7 @@ void Assembly::Update() {
void Assembly::UpdateTransform() { void Assembly::UpdateTransform() {
if (!m_Content || !m_VtkAsm) return; if (!m_Content || !m_VtkAsm) return;
Matrix4f mat = m_Content->GetMatrix(); this->ApplyTransform(m_VtkAsm);
vtkNew<vtkMatrix4x4> vmat;
Matrix4fToVtk(mat, vmat);
m_VtkAsm->SetUserMatrix(vmat);
m_VtkAsm->Modified(); m_VtkAsm->Modified();
} }

View File

@@ -44,7 +44,11 @@ public:
/** @brief Updates the VTK representation from the model (model→VTK). */ /** @brief Updates the VTK representation from the model (model→VTK). */
virtual void Update() override; virtual void Update() override;
/** @brief Synchronizes the model from the VTK representation (VTK→model). */
virtual void SyncFromVtk() override;
virtual uLib::Object* GetContent() const override { return (uLib::Object*)m_Content; } virtual uLib::Object* GetContent() const override { return (uLib::Object*)m_Content; }
virtual uLib::ObjectsContext* GetChildren() override { return (uLib::ObjectsContext*)m_Content; }
/** @brief Called when the model signals an update (model→VTK push). */ /** @brief Called when the model signals an update (model→VTK push). */
void contentUpdate(); void contentUpdate();

View File

@@ -80,54 +80,38 @@ void vtkContainerBox::contentUpdate() {
vtkProp3D* root = vtkProp3D::SafeDownCast(this->GetProp()); vtkProp3D* root = vtkProp3D::SafeDownCast(this->GetProp());
if (!root) return; if (!root) return;
vtkMatrix4x4* vmat = root->GetUserMatrix();
if (!vmat) {
// Should have been set in InstallPipe, but let's be safe
vtkNew<vtkMatrix4x4> mat;
root->SetUserMatrix(mat);
vmat = mat;
}
d->m_Cube->SetUserMatrix(nullptr); d->m_Cube->SetUserMatrix(nullptr);
d->m_Axes->SetUserMatrix(nullptr); d->m_Axes->SetUserMatrix(nullptr);
Matrix4f transform = m_Content->GetMatrix(); TRS trs(*m_Content);
Matrix4fToVtk(transform, vmat); this->ApplyTransform(root);
root->Modified(); root->Modified();
m_BlockUpdate = true; m_BlockUpdate = false;
Puppet::Update(); Puppet::Update();
} }
void vtkContainerBox::Update() { void vtkContainerBox::Update() {
this->contentUpdate();
}
void vtkContainerBox::SyncFromVtk() {
RecursiveMutex::ScopedLock lock(this->m_UpdateMutex); RecursiveMutex::ScopedLock lock(this->m_UpdateMutex);
if (!m_Content) return; if (!m_Content) return;
if (m_BlockUpdate) {
m_BlockUpdate = false;
return;
}
// Use Targeted Blocking: only block the feedback connection to this puppet
// boost::signals2::shared_connection_block block(m_Connection);
vtkProp3D* assembly = vtkProp3D::SafeDownCast(this->GetProp()); vtkProp3D* assembly = vtkProp3D::SafeDownCast(this->GetProp());
if (!assembly) return; if (!assembly) return;
vtkMatrix4x4* vmat = assembly->GetUserMatrix(); double pos[3], ori[3], scale[3];
if (!vmat) return; assembly->GetPosition(pos);
assembly->GetOrientation(ori);
assembly->GetScale(scale);
m_Content->SetPosition(Vector3f(pos[0], pos[1], pos[2]));
m_Content->SetOrientation(Vector3f(ori[0], ori[1], ori[2]) * CLHEP::degree);
m_Content->SetScale(Vector3f(scale[0], scale[1], scale[2]));
Matrix4f transform = VtkToMatrix4f(vmat);
// Update uLib model's affine transform
// if (m_Content->GetParent()) {
// Matrix4f localT = m_Content->GetParent()->GetWorldMatrix().inverse() * transform;
// m_Content->SetMatrix(localT);
// } else {
m_Content->SetMatrix(transform);
// }
m_Content->Updated(); // Notify change m_Content->Updated(); // Notify change
} }
@@ -175,9 +159,11 @@ void vtkContainerBox::InstallPipe() {
vtkProp3D* root = vtkProp3D::SafeDownCast(this->GetProp()); vtkProp3D* root = vtkProp3D::SafeDownCast(this->GetProp());
if (root) { if (root) {
vtkNew<vtkMatrix4x4> vmat; TRS trs(*c);
Matrix4fToVtk(c->GetMatrix(), vmat); root->SetPosition(trs.position.x(), trs.position.y(), trs.position.z());
root->SetUserMatrix(vmat); root->SetOrientation(trs.rotation.x(), trs.rotation.y(), trs.rotation.z());
root->SetScale(trs.scaling.x(), trs.scaling.y(), trs.scaling.z());
root->SetUserMatrix(nullptr);
} }
} }

View File

@@ -49,7 +49,8 @@ public:
virtual void contentUpdate(); virtual void contentUpdate();
virtual void Update(); virtual void Update() override;
virtual void SyncFromVtk() override;
virtual uLib::Object* GetContent() const override { return (uLib::Object*)m_Content; } virtual uLib::Object* GetContent() const override { return (uLib::Object*)m_Content; }

View File

@@ -55,17 +55,12 @@ void vtkCylinder::contentUpdate() {
vtkProp3D* root = vtkProp3D::SafeDownCast(this->GetProp()); vtkProp3D* root = vtkProp3D::SafeDownCast(this->GetProp());
if (!root) return; if (!root) return;
// 1. Placement (Position/Rotation/Model-level Scale) goes to the root prop // 1. Placement handled by base Puppet class via Sync / Update logic
vtkMatrix4x4* vmat = root->GetUserMatrix(); // Update internal pd->m_Transform from content
if (!vmat) { Puppet::Update();
vtkNew<vtkMatrix4x4> mat;
root->SetUserMatrix(mat);
vmat = mat;
}
Matrix4f transform = m_Content->GetMatrix();
Matrix4fToVtk(transform, vmat);
// 2. Shape-local properties (Radius, Height, Axis alignment) go to the internal actor // 2. Shape-local properties (Radius, Height, Axis alignment) go to the internal actor
// These are relative to the root assembly
vtkTransform* alignment = vtkTransform::SafeDownCast(m_Actor->GetUserTransform()); vtkTransform* alignment = vtkTransform::SafeDownCast(m_Actor->GetUserTransform());
if (alignment) { if (alignment) {
alignment->Identity(); alignment->Identity();
@@ -83,23 +78,29 @@ void vtkCylinder::contentUpdate() {
} }
root->Modified(); root->Modified();
Puppet::Update();
} }
void vtkCylinder::Update() { void vtkCylinder::Update() {
this->contentUpdate();
}
void vtkCylinder::SyncFromVtk() {
if (!m_Content) return; if (!m_Content) return;
vtkProp3D* root = vtkProp3D::SafeDownCast(this->GetProp());
if (!root) return;
vtkMatrix4x4* vmat = root->GetUserMatrix();
if (!vmat) return;
// Pull the placement matrix directly from VTK vtkProp3D* assembly = vtkProp3D::SafeDownCast(this->GetProp());
Matrix4f transform = VtkToMatrix4f(vmat); if (!assembly) return;
m_Content->SetMatrix(transform);
m_Content->Updated(); double pos[3], ori[3], scale[3];
assembly->GetPosition(pos);
assembly->GetOrientation(ori);
assembly->GetScale(scale);
m_Content->SetPosition(Vector3f(pos[0], pos[1], pos[2]));
// Convert VTK degrees to model radians
m_Content->SetOrientation(Vector3f(ori[0], ori[1], ori[2]) * CLHEP::degree);
m_Content->SetScale(Vector3f(scale[0], scale[1], scale[2]));
m_Content->Updated(); // Notify change
} }
void vtkCylinder::InstallPipe() { void vtkCylinder::InstallPipe() {

View File

@@ -52,7 +52,10 @@ public:
virtual void contentUpdate(); virtual void contentUpdate();
/** Synchronizes the uLib model matrix with the VTK actor (e.g., after UI manipulation) */ /** Synchronizes the uLib model matrix with the VTK actor (e.g., after UI manipulation) */
virtual void Update(); virtual void Update() override;
/** Synchronizes the uLib model matrix with the VTK actor specifically for gizmo interactions */
virtual void SyncFromVtk() override;
virtual uLib::Object* GetContent() const override { return (uLib::Object*)m_Content; } virtual uLib::Object* GetContent() const override { return (uLib::Object*)m_Content; }

View File

@@ -28,7 +28,7 @@
#include "Math/QuadMesh.h" #include "Math/QuadMesh.h"
#include "Vtk/uLibVtkInterface.h" #include "Vtk/uLibVtkInterface.h"
#include "Vtk/vtkPolydata.h" #include "Vtk/Math/vtkPolydata.h"
class vtkPolyData; class vtkPolyData;
class vtkActor; class vtkActor;

View File

@@ -28,7 +28,7 @@
#include "Math/TriangleMesh.h" #include "Math/TriangleMesh.h"
#include "Vtk/uLibVtkInterface.h" #include "Vtk/uLibVtkInterface.h"
#include "Vtk/vtkPolydata.h" #include "Vtk/Math/vtkPolydata.h"
class vtkPolyData; class vtkPolyData;
class vtkActor; class vtkActor;

View File

@@ -3,6 +3,7 @@ set(TESTS
vtkViewerTest vtkViewerTest
vtkHandlerWidget vtkHandlerWidget
PuppetPropertyTest PuppetPropertyTest
PuppetParentingTest
# vtkVoxImageTest # vtkVoxImageTest
# vtkTriangleMeshTest # vtkTriangleMeshTest
) )

View File

@@ -0,0 +1,106 @@
/*//////////////////////////////////////////////////////////////////////////////
// CMT Cosmic Muon Tomography project //////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2014, Universita' degli Studi di Padova, INFN sez. di Padova
// All rights reserved
//
// Authors: Andrea Rigoni Garola < andrea.rigoni@pd.infn.it >
//
//////////////////////////////////////////////////////////////////////////////*/
#include <cstdlib>
#include <Core/ObjectsContext.h>
#include <Math/Assembly.h>
#include <Math/ContainerBox.h>
#include <Vtk/uLibVtkViewer.h>
#include <Vtk/vtkObjectsContext.h>
#include <Vtk/Math/vtkAssembly.h>
#include <Vtk/Math/vtkContainerBox.h>
#include <vtkAssembly.h>
#include <vtkProp3D.h>
#include <vtkRenderer.h>
#include <vtkMatrix4x4.h>
#include "testing-prototype.h"
using namespace uLib;
int main() {
BEGIN_TESTING(Puppet Parenting Test);
ObjectsContext globalContext;
Vtk::Viewer viewer;
// Create the display context, linked to the model context.
// It will automatically create visual puppets for each model object.
Vtk::vtkObjectsContext viewerContext(&globalContext);
viewerContext.ConnectRenderer(viewer.GetRenderer());
// 1. Create a model Assembly
auto* assembly = new Assembly();
assembly->SetInstanceName("ParentAssembly");
globalContext.AddObject(assembly);
// Verify assembly puppet exists in the viewer context
Vtk::Puppet* assemblyPuppet = viewerContext.GetPuppet(assembly);
ASSERT_NOT_NULL(assemblyPuppet);
// cast to Vtk::Assembly to access child context
auto* vtkAss = dynamic_cast<Vtk::Assembly*>(assemblyPuppet);
ASSERT_NOT_NULL(vtkAss);
// 2. Create a child Box and add it to the Assembly
auto* box1 = new ContainerBox(Vector3f(10, 10, 10));
box1->SetInstanceName("ChildBox1");
box1->SetPosition(Vector3f(20, 0, 0));
assembly->AddObject(box1);
// Verify child puppet was created in the assembly's child context
Vtk::vtkObjectsContext* childVtkCtx = vtkAss->GetChildrenContext();
ASSERT_NOT_NULL(childVtkCtx);
Vtk::Puppet* box1Puppet = childVtkCtx->GetPuppet(box1);
ASSERT_NOT_NULL(box1Puppet);
// 3. Move the parent and verify the child follows
assembly->SetPosition(Vector3f(100, 0, 0));
assembly->Update();
// In VTK assemblies, the child's absolute matrix should reflect the parent's transform
vtkProp3D* box1Prop = vtkProp3D::SafeDownCast(box1Puppet->GetProp());
ASSERT_NOT_NULL(box1Prop);
vtkMatrix4x4* boxMatrix = box1Prop->GetMatrix();
// Origin (0,0,0) + local(20,0,0) + assembly(100,0,0) = world(120,0,0) ?
// Actually, box1->GetPosition() is (20,0,0).
// The puppet ApplyTransform sets the prop orientation and position.
std::cout << "Checking transformation chain..." << std::endl;
// std::cout << *boxMatrix << std::endl;
// Verify relative positioning
double* pos = box1Prop->GetPosition();
ASSERT_EQUAL(pos[0], 20.0);
// The absolute world position can be checked via GetMatrix elements
// boxMatrix->GetElement(0, 3) should be 120.0 if the vtkAssembly nesting is working
// but vtkAssembly::GetMatrix() usually returns the LOCAL matrix unless called on the top property context?
// Actually vtkProp3D::GetMatrix() is the local matrix.
// 4. Add another child
auto* box2 = new ContainerBox(Vector3f(5, 5, 5));
box2->SetInstanceName("ChildBox2");
box2->SetPosition(Vector3f(-20, 0, 0));
assembly->AddObject(box2);
Vtk::Puppet* box2Puppet = childVtkCtx->GetPuppet(box2);
ASSERT_NOT_NULL(box2Puppet);
// Render if not in batch environment
if (!std::getenv("CTEST_PROJECT_NAME")) {
viewer.GetRenderer()->ResetCamera();
viewer.Start();
}
END_TESTING;
}

View File

@@ -41,6 +41,7 @@
#include <vtkVersion.h> #include <vtkVersion.h>
#include "vtkViewport.h" #include "vtkViewport.h"
#include "uLibVtkInterface.h" #include "uLibVtkInterface.h"
#include "Math/Transform.h"
#include <vtkActor.h> #include <vtkActor.h>
#include <vtkPolyDataMapper.h> #include <vtkPolyDataMapper.h>
#include <vtkProperty.h> #include <vtkProperty.h>
@@ -61,6 +62,8 @@
#include "uLibVtkInterface.h" #include "uLibVtkInterface.h"
#include "vtkHandlerWidget.h" #include "vtkHandlerWidget.h"
#include "Math/Dense.h"
#include "Vtk/Math/vtkDense.h"
#include "Core/Property.h" #include "Core/Property.h"
@@ -73,12 +76,6 @@ namespace uLib {
namespace Vtk { namespace Vtk {
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// PUPPET //
// PIMPL -------------------------------------------------------------------- // // PIMPL -------------------------------------------------------------------- //
class PuppetData { class PuppetData {
@@ -89,16 +86,13 @@ public:
m_ShowBoundingBox(false), m_ShowBoundingBox(false),
m_ShowScaleMeasures(false), m_ShowScaleMeasures(false),
m_Representation(Puppet::Surface), m_Representation(Puppet::Surface),
m_Opacity(-1.0), m_Opacity(1.0),
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_Color[0] = m_Color[1] = m_Color[2] = -1.0; m_Color = Vector3d(-1, -1, -1);
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() { ~PuppetData() {
@@ -117,18 +111,17 @@ public:
bool m_ShowBoundingBox; bool m_ShowBoundingBox;
bool m_ShowScaleMeasures; bool m_ShowScaleMeasures;
int m_Representation; int m_Representation;
double m_Color[3]; Vector3d m_Color;
double m_Opacity; double m_Opacity;
bool m_Selectable; bool m_Selectable;
bool m_Selected; bool m_Selected;
bool m_Visibility; bool m_Visibility;
bool m_Dragable; bool m_Dragable;
double m_Position[3]; TRS m_Transform;
double m_Orientation[3];
double m_Scale[3];
void ApplyAppearance(vtkProp *p) { void ApplyAppearance(vtkProp *p) {
if (!p) return;
p->SetVisibility(m_Visibility); p->SetVisibility(m_Visibility);
p->SetPickable(m_Selectable); p->SetPickable(m_Selectable);
p->SetDragable(m_Dragable); p->SetDragable(m_Dragable);
@@ -144,21 +137,35 @@ public:
actor->GetProperty()->SetEdgeVisibility(0); actor->GetProperty()->SetEdgeVisibility(0);
} }
} }
if (m_Color[0] != -1.0) { if (m_Color.x() != -1.0) {
actor->GetProperty()->SetColor(m_Color); double c[3] = {m_Color.x(), m_Color.y(), m_Color.z()};
actor->GetProperty()->SetColor(c);
} }
if (m_Opacity != -1.0) { if (m_Opacity != -1.0) {
actor->GetProperty()->SetOpacity(m_Opacity); actor->GetProperty()->SetOpacity(m_Opacity);
} }
} else if (vtkAssembly *asm_p = vtkAssembly::SafeDownCast(p)) {
// Recursively apply to parts of the assembly
vtkProp3DCollection *parts = asm_p->GetParts();
parts->InitTraversal();
for (int i = 0; i < parts->GetNumberOfItems(); ++i) {
this->ApplyAppearance(parts->GetNextProp3D());
}
} }
}
// Handle transformation if it's a Prop3D void ApplyTransform(vtkProp3D* p3d) {
if (auto* p3d = vtkProp3D::SafeDownCast(p)) { if (p3d) {
// NOTE: Usually managed by Puppet::Update from model, but here for direct prop manipulation p3d->SetPosition(m_Transform.position.x(), m_Transform.position.y(), m_Transform.position.z());
// p3d->SetPosition(m_Position);
// p3d->SetOrientation(m_Orientation); // Convert Model Radians to VTK Degrees
// p3d->SetScale(m_Scale); p3d->SetOrientation(m_Transform.rotation.x() / CLHEP::degree,
m_Transform.rotation.y() / CLHEP::degree,
m_Transform.rotation.z() / CLHEP::degree);
p3d->SetScale(m_Transform.scaling.x(), m_Transform.scaling.y(), m_Transform.scaling.z());
p3d->SetUserMatrix(nullptr);
} }
} }
@@ -201,6 +208,7 @@ public:
} }
if (root) { if (root) {
// Now that we use internal TRS, the prop's total matrix is GetMatrix()
m_HighlightActor->SetUserMatrix(root->GetMatrix()); m_HighlightActor->SetUserMatrix(root->GetMatrix());
} }
@@ -225,6 +233,15 @@ public:
Puppet::Puppet() : Object(), pd(new PuppetData) { Puppet::Puppet() : Object(), pd(new PuppetData) {
ULIB_ACTIVATE_DISPLAY_PROPERTIES; ULIB_ACTIVATE_DISPLAY_PROPERTIES;
for (auto* p : this->GetDisplayProperties()) { for (auto* p : this->GetDisplayProperties()) {
@@ -264,8 +281,11 @@ void Puppet::SetProp(vtkProp *prop)
pd->m_Representation = vp->GetRepresentation(); pd->m_Representation = vp->GetRepresentation();
if (pd->m_Opacity < 0) if (pd->m_Opacity < 0)
pd->m_Opacity = vp->GetOpacity(); pd->m_Opacity = vp->GetOpacity();
if (pd->m_Color[0] < 0) if (pd->m_Color.x() < 0) {
vp->GetColor(pd->m_Color); double c[3];
vp->GetColor(c);
pd->m_Color = Vector3d(c[0], c[1], c[2]);
}
} }
} }
} }
@@ -276,6 +296,16 @@ void Puppet::RemoveProp(vtkProp *prop)
// TODO // TODO
} }
void Puppet::ApplyAppearance(vtkProp* prop)
{
pd->ApplyAppearance(prop);
}
void Puppet::ApplyTransform(vtkProp3D* p3d)
{
pd->ApplyTransform(p3d);
}
vtkPropCollection *Puppet::GetParts() vtkPropCollection *Puppet::GetParts()
{ {
@@ -495,14 +525,15 @@ void Puppet::Update()
{ {
vtkProp* root = this->GetProp(); vtkProp* root = this->GetProp();
if (root) { if (root) {
pd->ApplyAppearance(root); // Handle transformation synchronization from content
if (auto* content = dynamic_cast<uLib::AffineTransform*>(GetContent())) {
// Apply transformation if it's a Prop3D pd->m_Transform = *content; // Uses TRS(const AffineTransform&)
if (auto* p3d = vtkProp3D::SafeDownCast(root)) {
p3d->SetPosition(pd->m_Position);
p3d->SetOrientation(pd->m_Orientation);
p3d->SetScale(pd->m_Scale);
} }
if (auto* p3d = vtkProp3D::SafeDownCast(root)) {
pd->ApplyTransform(p3d);
}
pd->ApplyAppearance(root);
} }
vtkProp3DCollection *props = pd->m_Assembly->GetParts(); vtkProp3DCollection *props = pd->m_Assembly->GetParts();
@@ -538,23 +569,39 @@ void Puppet::Update()
} }
} }
void Puppet::SyncFromVtk() void Puppet::SyncFromVtk()
{ {
vtkProp* root = this->GetProp(); vtkProp* root = this->GetProp();
if (auto* p3d = vtkProp3D::SafeDownCast(root)) { if (auto* p3d = vtkProp3D::SafeDownCast(root)) {
double pos[3], ori[3], scale[3]; // Handle content synchronization if it's an AffineTransform
p3d->GetPosition(pos); if (auto* content = dynamic_cast<uLib::AffineTransform*>(GetContent())) {
p3d->GetOrientation(ori); double pos[3], ori[3], scale[3];
p3d->GetScale(scale); p3d->GetPosition(pos);
p3d->GetOrientation(ori);
// Update properties p3d->GetScale(scale);
for (int i=0; i<3; ++i) {
pd->m_Position[i] = pos[i]; // Convert VTK Degrees to Model Radians
pd->m_Orientation[i] = ori[i]; content->SetPosition(Vector3f(pos[0], pos[1], pos[2]));
pd->m_Scale[i] = scale[i]; content->SetOrientation(Vector3f(ori[0], ori[1], ori[2]) * CLHEP::degree);
content->SetScale(Vector3f(scale[0], scale[1], scale[2]));
// Re-sync internal puppet properties from the now-updated content
pd->m_Transform = *content;
}
else {
// Update internal puppet TRS directly from VTK components
double pos[3], ori[3], scale[3];
p3d->GetPosition(pos);
p3d->GetOrientation(ori);
p3d->GetScale(scale);
pd->m_Transform.position = Vector3f(pos[0], pos[1], pos[2]);
// Convert VTK Degrees to internal Radians
pd->m_Transform.rotation = Vector3f(ori[0], ori[1], ori[2]) * CLHEP::degree;
pd->m_Transform.scaling = Vector3f(scale[0], scale[1], scale[2]);
} }
// Get the properties from the object // Notify puppet properties updated
if (auto* propPos = this->GetProperty("Position")) propPos->Updated(); if (auto* propPos = this->GetProperty("Position")) propPos->Updated();
if (auto* propOri = this->GetProperty("Orientation")) propOri->Updated(); if (auto* propOri = this->GetProperty("Orientation")) propOri->Updated();
if (auto* propScale = this->GetProperty("Scale")) propScale->Updated(); if (auto* propScale = this->GetProperty("Scale")) propScale->Updated();
@@ -567,26 +614,33 @@ void Puppet::ConnectInteractor(vtkRenderWindowInteractor *interactor)
{ {
} }
struct TransformProxy {
PuppetData* pd;
template<class Archive>
void serialize(Archive & ar, const unsigned int version) {
ar & boost::serialization::make_nvp("Transform", pd->m_Transform);
}
};
struct AppearanceProxy {
PuppetData* pd;
template<class Archive>
void serialize(Archive & ar, const unsigned int version) {
ar & boost::serialization::make_hrp("Color", pd->m_Color, "color");
ar & boost::serialization::make_hrp("Opacity", pd->m_Opacity).range(0.0, 1.0).set_default(1.0);
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);
}
};
void Puppet::serialize_display(Archive::display_properties_archive & ar, const unsigned int version) { void Puppet::serialize_display(Archive::display_properties_archive & ar, const unsigned int version) {
ar & boost::serialization::make_hrp("ColorR", pd->m_Color[0]); AppearanceProxy appearance{pd};
ar & boost::serialization::make_hrp("ColorG", pd->m_Color[1]); ar & boost::serialization::make_nvp("Appearance", appearance);
ar & boost::serialization::make_hrp("ColorB", pd->m_Color[2]);
ar & boost::serialization::make_hrp("Opacity", pd->m_Opacity);
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) TransformProxy transform{pd};
ar & boost::serialization::make_hrp("PosX", pd->m_Position[0], "mm"); ar & boost::serialization::make_nvp("Transform", transform);
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) { } void Puppet::serialize(Archive::xml_oarchive & ar, const unsigned int v) { }

View File

@@ -29,12 +29,16 @@
#include <iomanip> #include <iomanip>
#include <ostream> #include <ostream>
#include <vector> #include <vector>
#include <boost/type_traits/is_class.hpp>
#include <boost/mpl/bool.hpp>
#include <boost/serialization/serialization.hpp>
#include "Core/Object.h" #include "Core/Object.h"
#include "Core/Property.h" #include "Core/Property.h"
#include "Core/Monitor.h" #include "Core/Monitor.h"
// vtk classes forward declaration // // vtk classes forward declaration //
class vtkProp; class vtkProp;
class vtkProp3D;
class vtkPolyData; class vtkPolyData;
class vtkPropCollection; class vtkPropCollection;
class vtkRenderer; class vtkRenderer;
@@ -118,6 +122,9 @@ protected:
void RemoveProp(vtkProp *prop); void RemoveProp(vtkProp *prop);
void ApplyAppearance(vtkProp* prop);
void ApplyTransform(vtkProp3D* p3d);
std::vector<uLib::PropertyBase*> m_DisplayProperties; std::vector<uLib::PropertyBase*> m_DisplayProperties;
mutable uLib::RecursiveMutex m_UpdateMutex; mutable uLib::RecursiveMutex m_UpdateMutex;
@@ -157,10 +164,22 @@ public:
boost::archive::detail::common_oarchive<display_properties_archive>(boost::archive::no_header), boost::archive::detail::common_oarchive<display_properties_archive>(boost::archive::no_header),
m_Puppet(puppet) {} m_Puppet(puppet) {}
std::string GetCurrentGroup() const {
std::string group;
for (const auto& g : m_GroupStack) {
if (!group.empty()) group += ".";
group += g;
}
return group;
}
template<class T> template<class T>
void save_override(const boost::serialization::hrp<T> &t) { void save_override(const boost::serialization::hrp<T> &t) {
if (m_Puppet) { if (m_Puppet) {
uLib::Property<T>* p = new uLib::Property<T>(m_Puppet, t.name(), &const_cast<boost::serialization::hrp<T>&>(t).value(), t.units() ? t.units() : ""); uLib::Property<T>* p = new uLib::Property<T>(m_Puppet, t.name(), &const_cast<boost::serialization::hrp<T>&>(t).value(), t.units() ? t.units() : "", GetCurrentGroup());
if (t.has_range()) p->SetRange(t.min_val(), t.max_val());
if (t.has_default()) p->SetDefault(t.default_val());
m_Puppet->RegisterDisplayProperty(p); m_Puppet->RegisterDisplayProperty(p);
Vtk::Puppet* puppet = m_Puppet; Vtk::Puppet* puppet = m_Puppet;
uLib::Object::connect(p, &uLib::PropertyBase::Updated, [puppet](){ puppet->Update(); }); uLib::Object::connect(p, &uLib::PropertyBase::Updated, [puppet](){ puppet->Update(); });
@@ -170,7 +189,7 @@ public:
template<class T> template<class T>
void save_override(const boost::serialization::hrp_enum<T> &t) { void save_override(const boost::serialization::hrp_enum<T> &t) {
if (m_Puppet) { 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() : ""); 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() : "", GetCurrentGroup());
m_Puppet->RegisterDisplayProperty(p); m_Puppet->RegisterDisplayProperty(p);
Vtk::Puppet* puppet = m_Puppet; Vtk::Puppet* puppet = m_Puppet;
uLib::Object::connect(p, &uLib::PropertyBase::Updated, [puppet](){ puppet->Update(); }); uLib::Object::connect(p, &uLib::PropertyBase::Updated, [puppet](){ puppet->Update(); });
@@ -178,10 +197,23 @@ public:
} }
template<class T> void save_override(const boost::serialization::nvp<T> &t) { template<class T> void save_override(const boost::serialization::nvp<T> &t) {
boost::archive::detail::common_oarchive<display_properties_archive>::save_override(t.const_value()); if (t.name()) m_GroupStack.push_back(t.name());
this->save_helper(t.const_value(), typename boost::is_class<T>::type());
if (t.name()) m_GroupStack.pop_back();
} }
template<class T> void save_override(const T &t) {} // Recursion for nested classes, ignore primitives
template<class T> void save_override(const T &t) {
this->save_helper(t, typename boost::is_class<T>::type());
}
template<class T>
void save_helper(const T &t, boost::mpl::true_) {
boost::serialization::serialize_adl(*this, const_cast<T&>(t), 0);
}
template<class T>
void save_helper(const T &t, boost::mpl::false_) {}
void save_override(const boost::archive::object_id_type & t) {} void save_override(const boost::archive::object_id_type & t) {}
void save_override(const boost::archive::object_reference_type & t) {} void save_override(const boost::archive::object_reference_type & t) {}
@@ -194,6 +226,7 @@ public:
private: private:
Vtk::Puppet* m_Puppet; Vtk::Puppet* m_Puppet;
std::vector<std::string> m_GroupStack;
}; };
} // namespace Archive } // namespace Archive

View File

@@ -48,6 +48,8 @@
#include <vtkRenderer.h> #include <vtkRenderer.h>
#include <vtkSmartPointer.h> #include <vtkSmartPointer.h>
#include <vtkTransform.h> #include <vtkTransform.h>
#include "Math/Transform.h"
#include "Vtk/Math/vtkDense.h"
namespace uLib { namespace uLib {
namespace Vtk { namespace Vtk {
@@ -62,20 +64,23 @@ struct HandlerWidgetData {
vtkSmartPointer<::vtkActor> m_RotCam; // Camera ring vtkSmartPointer<::vtkActor> m_RotCam; // Camera ring
vtkSmartPointer<::vtkActor> m_ScaleX, m_ScaleY, m_ScaleZ; // Cubes vtkSmartPointer<::vtkActor> m_ScaleX, m_ScaleY, m_ScaleZ; // Cubes
// cut plane to see only half of rotation handles
vtkSmartPointer<::vtkPlane> m_ClipPlane; vtkSmartPointer<::vtkPlane> m_ClipPlane;
// picker to select the gizmo
vtkSmartPointer<::vtkCellPicker> m_Picker; vtkSmartPointer<::vtkCellPicker> m_Picker;
// initial transform of the object
vtkSmartPointer<::vtkTransform> m_InitialTransform; vtkSmartPointer<::vtkTransform> m_InitialTransform;
std::vector<vtkSmartPointer<::vtkTransform>> m_TransformChain; // undo stack
vtkSmartPointer<::vtkMatrix4x4> m_BaseMatrix; std::vector<uLib::TRS> m_UndoStack;
HandlerWidgetData() { HandlerWidgetData() {
m_Picker = vtkSmartPointer<::vtkCellPicker>::New(); m_Picker = vtkSmartPointer<::vtkCellPicker>::New();
m_InitialTransform = vtkSmartPointer<::vtkTransform>::New(); m_InitialTransform = vtkSmartPointer<::vtkTransform>::New();
m_ClipPlane = vtkSmartPointer<::vtkPlane>::New(); m_ClipPlane = vtkSmartPointer<::vtkPlane>::New();
m_OverlayRenderer = vtkSmartPointer<::vtkRenderer>::New(); m_OverlayRenderer = vtkSmartPointer<::vtkRenderer>::New();
m_BaseMatrix = vtkSmartPointer<::vtkMatrix4x4>::New();
m_HighlightedProp = nullptr; m_HighlightedProp = nullptr;
} }
}; };
@@ -95,7 +100,6 @@ vtkHandlerWidget::vtkHandlerWidget() : d(new HandlerWidgetData()) {
this->m_TranslationEnabled = true; this->m_TranslationEnabled = true;
this->m_RotationEnabled = true; this->m_RotationEnabled = true;
this->m_ScalingEnabled = true; this->m_ScalingEnabled = true;
d->m_BaseMatrix->Identity();
this->CreateGizmos(); this->CreateGizmos();
} }
@@ -108,19 +112,14 @@ vtkHandlerWidget::~vtkHandlerWidget() {
return d->m_OverlayRenderer; return d->m_OverlayRenderer;
} }
void vtkHandlerWidget::SetProp3D(::vtkProp3D *prop) { void vtkHandlerWidget::SetProp3D(::vtkProp3D *prop) {
if (this->Prop3D == prop) { if (this->Prop3D == prop) {
return; return;
} }
this->Prop3D = prop; this->Prop3D = prop;
if (this->Prop3D) { if (this->Prop3D) {
// Initialize d->m_BaseMatrix from the object's current matrix this->d->m_UndoStack.clear(); // Clear history when selecting new object
if (this->Prop3D->GetUserMatrix()) {
this->d->m_BaseMatrix->DeepCopy(this->Prop3D->GetUserMatrix());
} else {
this->d->m_BaseMatrix->Identity();
}
this->d->m_TransformChain.clear(); // Clear any previous transform chain
this->UpdateGizmoPosition(); this->UpdateGizmoPosition();
} }
this->Modified(); this->Modified();
@@ -247,20 +246,19 @@ void vtkHandlerWidget::OnKeyPress() {
bool ctrl = (this->Interactor->GetControlKey() != 0); bool ctrl = (this->Interactor->GetControlKey() != 0);
if (ctrl && key == "z") { if (ctrl && key == "z") {
if (!this->d->m_TransformChain.empty()) { if (!this->d->m_UndoStack.empty()) {
std::cout << "Undoing last transform action..." << std::endl; std::cout << "Undoing last transform action..." << std::endl;
this->d->m_TransformChain.pop_back(); uLib::TRS target = this->d->m_UndoStack.back();
this->d->m_UndoStack.pop_back();
// Update object from chain if (this->Prop3D) {
vtkNew<vtkTransform> total; this->Prop3D->SetPosition(target.position.x(), target.position.y(), target.position.z());
total->PostMultiply(); // Convert Model Radians to VTK Degrees
total->SetMatrix(this->d->m_BaseMatrix.GetPointer()); this->Prop3D->SetOrientation(target.rotation.x() / CLHEP::degree,
for (auto& t : d->m_TransformChain) { target.rotation.y() / CLHEP::degree,
total->Concatenate(t); target.rotation.z() / CLHEP::degree);
} this->Prop3D->SetScale(target.scaling.x(), target.scaling.y(), target.scaling.z());
this->Prop3D->SetUserMatrix(nullptr);
if (this->Prop3D && this->Prop3D->GetUserMatrix()) {
this->Prop3D->GetUserMatrix()->DeepCopy(total->GetMatrix());
this->Prop3D->Modified(); this->Prop3D->Modified();
this->UpdateGizmoPosition(); this->UpdateGizmoPosition();
this->InvokeEvent(::vtkCommand::InteractionEvent, nullptr); this->InvokeEvent(::vtkCommand::InteractionEvent, nullptr);
@@ -311,21 +309,12 @@ void vtkHandlerWidget::OnLeftButtonDown() {
this->StartEventPosition[0] = X; this->StartEventPosition[0] = X;
this->StartEventPosition[1] = Y; this->StartEventPosition[1] = Y;
if (this->Prop3D) { if (this->Prop3D) {
if (!this->Prop3D->GetUserMatrix()) { // Capture current state for Undo
vtkNew<vtkMatrix4x4> vmat; this->d->m_UndoStack.push_back(uLib::TRS(uLib::Vtk::VtkToMatrix4f(this->Prop3D->GetMatrix())));
this->Prop3D->SetUserMatrix(vmat); if (this->d->m_UndoStack.size() > 50) this->d->m_UndoStack.erase(this->d->m_UndoStack.begin());
}
// Use the prop's total matrix for calculation baseline
// If the chain is empty, initialize base from current state? this->d->m_InitialTransform->SetMatrix(this->Prop3D->GetMatrix());
// Actually, if we just started selecting this object, we should have initialized BaseMatrix.
// For now, let's keep d->m_InitialTransform as the state BEFORE this drag
vtkNew<vtkTransform> current;
current->PostMultiply();
current->SetMatrix(this->d->m_BaseMatrix.GetPointer());
for (auto& t : d->m_TransformChain) {
current->Concatenate(t);
}
this->d->m_InitialTransform->SetMatrix(current->GetMatrix());
} }
this->EventCallbackCommand->SetAbortFlag(1); this->EventCallbackCommand->SetAbortFlag(1);
this->InvokeEvent(::vtkCommand::StartInteractionEvent, nullptr); this->InvokeEvent(::vtkCommand::StartInteractionEvent, nullptr);
@@ -337,27 +326,6 @@ void vtkHandlerWidget::OnLeftButtonUp() {
if (this->Interaction == IDLE) if (this->Interaction == IDLE)
return; return;
// Finalize the current interaction into the chain
int X = this->Interactor->GetEventPosition()[0];
int Y = this->Interactor->GetEventPosition()[1];
// We need to re-calculate the final 'op' to store it
// Actually, we could have stored it in OnMouseMove, but let's re-calculate or
// just capture the delta between d->m_InitialTransform and current UserMatrix.
if (this->Prop3D && this->Prop3D->GetUserMatrix()) {
vtkNew<vtkMatrix4x4> inv;
vtkMatrix4x4::Invert(this->d->m_InitialTransform->GetMatrix(), inv);
vtkNew<vtkMatrix4x4> final_op_mat;
vtkMatrix4x4::Multiply4x4(this->Prop3D->GetUserMatrix(), inv, final_op_mat);
vtkNew<vtkTransform> final_op;
final_op->SetMatrix(final_op_mat);
this->d->m_TransformChain.push_back(final_op);
std::cout << "Action finalized. Chain size: " << this->d->m_TransformChain.size() << std::endl;
}
this->Interaction = IDLE; this->Interaction = IDLE;
this->EventCallbackCommand->SetAbortFlag(1); this->EventCallbackCommand->SetAbortFlag(1);
this->InvokeEvent(::vtkCommand::EndInteractionEvent, nullptr); this->InvokeEvent(::vtkCommand::EndInteractionEvent, nullptr);
@@ -578,9 +546,17 @@ void vtkHandlerWidget::OnMouseMove() {
total->SetMatrix(this->d->m_InitialTransform->GetMatrix()); // d->m_InitialTransform is already Base*Chain total->SetMatrix(this->d->m_InitialTransform->GetMatrix()); // d->m_InitialTransform is already Base*Chain
total->Concatenate(op); total->Concatenate(op);
vtkMatrix4x4* targetMat = this->Prop3D->GetUserMatrix(); if (this->Prop3D) {
if (targetMat) { double p[3], r[3], s[3];
targetMat->DeepCopy(total->GetMatrix()); total->GetPosition(p);
total->GetOrientation(r);
total->GetScale(s);
this->Prop3D->SetPosition(p);
// VTK GetOrientation already returned degrees, so r is in degrees.
// We apply it directly back to VTK.
this->Prop3D->SetOrientation(r);
this->Prop3D->SetScale(s);
this->Prop3D->SetUserMatrix(nullptr);
} }
this->Prop3D->Modified(); this->Prop3D->Modified();
@@ -671,7 +647,7 @@ void vtkHandlerWidget::SetTransform(::vtkTransform *t) {
void vtkHandlerWidget::GetTransform(::vtkTransform *t) { void vtkHandlerWidget::GetTransform(::vtkTransform *t) {
if (!t || !this->Prop3D) if (!t || !this->Prop3D)
return; return;
t->SetMatrix(this->Prop3D->GetUserMatrix()); t->SetMatrix(this->Prop3D->GetMatrix());
} }
void vtkHandlerWidget::CreateGizmos() { void vtkHandlerWidget::CreateGizmos() {

View File

@@ -110,17 +110,16 @@ void vtkObjectsContext::Update() {
Puppet* vtkObjectsContext::CreatePuppet(uLib::Object* obj) { Puppet* vtkObjectsContext::CreatePuppet(uLib::Object* obj) {
if (!obj) return nullptr; if (!obj) return nullptr;
const char* className = obj->GetClassName(); if (auto* box = dynamic_cast<uLib::ContainerBox*>(obj)) {
if (std::strcmp(className, "ContainerBox") == 0) { return new vtkContainerBox(box);
return new vtkContainerBox(static_cast<uLib::ContainerBox*>(obj)); } else if (auto* chamber = dynamic_cast<uLib::DetectorChamber*>(obj)) {
} else if (std::strcmp(className, "DetectorChamber") == 0) { return new vtkDetectorChamber(chamber);
return new vtkDetectorChamber(static_cast<uLib::DetectorChamber*>(obj)); } else if (auto* cylinder = dynamic_cast<uLib::Cylinder*>(obj)) {
} else if (std::strcmp(className, "Cylinder") == 0) { return new vtkCylinder(cylinder);
return new vtkCylinder(static_cast<uLib::Cylinder*>(obj)); } else if (auto* vox = dynamic_cast<uLib::Abstract::VoxImage*>(obj)) {
} else if (std::strcmp(className, "VoxImage") == 0) { return new vtkVoxImage(*vox);
return new vtkVoxImage(*static_cast<uLib::Abstract::VoxImage*>(obj)); } else if (auto* assembly = dynamic_cast<uLib::Assembly*>(obj)) {
} else if (std::strcmp(className, "Assembly") == 0) { return new Assembly(assembly);
return new Assembly(static_cast<uLib::Assembly*>(obj));
} }
// Fallback if we don't know the exact class but it might be a context itself // Fallback if we don't know the exact class but it might be a context itself

View File

@@ -209,7 +209,6 @@ void Viewport::SetupPipeline(vtkRenderWindowInteractor* iren)
for (auto* p : self->m_Puppets) { for (auto* p : self->m_Puppets) {
if (p->IsSelected()) { if (p->IsSelected()) {
p->SyncFromVtk(); p->SyncFromVtk();
p->Update();
} }
} }
}); });