Compare commits
10 Commits
a467b7385b
...
andrea-alg
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
876b8f4592 | ||
|
|
ec2027e980 | ||
|
|
69b47623f8 | ||
|
|
f5c1e317e8 | ||
|
|
e0ffeff5b7 | ||
|
|
2c5d6842c3 | ||
|
|
422113a0e9 | ||
|
|
e4a8499104 | ||
|
|
6a65fe94c8 | ||
|
|
7d4acaef6d |
7
.agents/rules/micromamba.md
Normal file
7
.agents/rules/micromamba.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
trigger: always_on
|
||||||
|
---
|
||||||
|
|
||||||
|
build in build directory using always micromamba "mutom" env.
|
||||||
|
build with make flag -j$(nproc).
|
||||||
|
|
||||||
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
*.vtk filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.vti filter=lfs diff=lfs merge=lfs -text
|
||||||
90
CLAUDE.md
Normal file
90
CLAUDE.md
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
|
||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Build Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Activate the conda environment (required before any build/run)
|
||||||
|
export MAMBA_EXE="/home/share/micromamba/bin/micromamba"
|
||||||
|
export MAMBA_ROOT_PREFIX="/home/share/micromamba"
|
||||||
|
eval "$(/home/share/micromamba/bin/micromamba shell hook --shell bash)"
|
||||||
|
micromamba activate mutom
|
||||||
|
|
||||||
|
# Configure (from repo root, using Conan preset)
|
||||||
|
cmake --preset conan-release
|
||||||
|
|
||||||
|
# Build everything
|
||||||
|
cmake --build build -j$(nproc)
|
||||||
|
|
||||||
|
# Build a specific target
|
||||||
|
cmake --build build --target gcompose -j$(nproc)
|
||||||
|
|
||||||
|
# Run tests
|
||||||
|
cmake --build build --target test
|
||||||
|
# or
|
||||||
|
ctest --test-dir build
|
||||||
|
|
||||||
|
# Run a single test binary (example)
|
||||||
|
./build/src/Core/testing/CoreTest
|
||||||
|
|
||||||
|
# Run the gcompose GUI app
|
||||||
|
./build/app/gcompose/gcompose
|
||||||
|
```
|
||||||
|
|
||||||
|
First-time setup (if `build/` does not exist):
|
||||||
|
```bash
|
||||||
|
conan profile detect
|
||||||
|
conan install . --output-folder=build --build=missing
|
||||||
|
cmake --preset conan-release
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
**uLib** is a C++ framework for Cosmic Muon Tomography (CMT), structured as layered shared libraries:
|
||||||
|
|
||||||
|
```
|
||||||
|
mutomCore → mutomMath → mutomDetectors → mutomGeant
|
||||||
|
↘
|
||||||
|
mutomVtk → gcompose (Qt6 GUI app)
|
||||||
|
mutomRoot
|
||||||
|
```
|
||||||
|
|
||||||
|
### Core Object Model (`src/Core/`)
|
||||||
|
- All framework objects inherit from `uLib::Object`
|
||||||
|
- **Property system**: `Property<T>` wraps member pointers with change notification via `PropertyChanged` signal
|
||||||
|
- **Signal/slot**: `uLib::Object::connect(sender, &Sender::Signal, callback)` — resembles Qt but works for non-QObject classes
|
||||||
|
- **Serialization**: Boost archives (`xml_oarchive`, `text_oarchive`, `hrt_oarchive`); `hrp<T>` marks fields as "human-readable properties"
|
||||||
|
- `ObjectsContext` is a container owning a list of `Object*` pointers; signals `ObjectAdded`/`ObjectRemoved`
|
||||||
|
|
||||||
|
### VTK Layer (`src/Vtk/`)
|
||||||
|
- `Puppet` (inherits `uLib::Object`): wraps a VTK `vtkProp` for rendering. Has `GetContent()` returning the underlying domain object. Display-only properties are registered via `ULIB_ACTIVATE_DISPLAY_PROPERTIES` macro.
|
||||||
|
- `Viewport`: base class managing the VTK renderer, picking, selection logic. Maintains `m_Puppets` vector and `m_ObjectToPuppet` map.
|
||||||
|
- `QViewport` (inherits `QWidget` + `Viewport`): Qt-embedded VTK widget. Emits Qt signal `puppetSelected(Puppet*)` on click-selection via `OnSelectionChanged`.
|
||||||
|
- `vtkObjectsContext`: wraps `ObjectsContext`, creating/destroying `Puppet`s as objects come/go. Emits `PuppetAdded`/`PuppetRemoved`.
|
||||||
|
- Display properties: `serialize_display()` + `display_properties_archive` registers selected `hrp<T>` fields as `PropertyBase*` in the puppet's `m_DisplayProperties`. `PropertyEditor::setObject(obj, displayOnly=true)` shows only those.
|
||||||
|
|
||||||
|
### gcompose GUI App (`app/gcompose/src/`)
|
||||||
|
- `MainPanel`: top-level widget. Owns `ContextPanel` (left) and `ViewportPane` (right). Wires together viewport↔context selection via signals.
|
||||||
|
- `ContextPanel`: tree view of `ObjectsContext`. Emits `objectSelected(Object*)`. Contains an embedded `PropertiesPanel`.
|
||||||
|
- `PropertiesPanel`: shows `uLib::Object` properties via `PropertyEditor`.
|
||||||
|
- `ViewportPane`: embeds `QViewport` + a slide-out "Display Properties" panel (`PropertyEditor` in display-only mode).
|
||||||
|
- `PropertyEditor`: populates widgets from `Object::GetProperties()` (all) or `Puppet::GetDisplayProperties()` (display-only mode).
|
||||||
|
|
||||||
|
### Selection Sync Flow
|
||||||
|
```
|
||||||
|
Viewport click → Viewport::SelectPuppet() → QViewport::OnSelectionChanged()
|
||||||
|
→ emit puppetSelected(p)
|
||||||
|
→ MainPanel: contextPanel->selectObject(p->GetContent()) [updates tree + PropertiesPanel]
|
||||||
|
→ MainPanel: firstPane->setObject(p) [updates Display Properties panel]
|
||||||
|
|
||||||
|
ContextPanel tree click → emit objectSelected(obj)
|
||||||
|
→ MainPanel: viewport->SelectPuppet(puppet) [visual selection in VTK]
|
||||||
|
→ MainPanel: firstPane->setObject(puppet) [updates Display Properties panel]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Key Patterns
|
||||||
|
- **Two signal systems coexist**: Qt signals (`Q_OBJECT`, `connect(...)`) for GUI; `uLib::Object::connect(...)` for domain signals.
|
||||||
|
- **Display properties** flow: `Puppet::serialize_display()` → `display_properties_archive` → `RegisterDisplayProperty()` → `PropertyEditor(displayOnly=true)`. Must call `ULIB_ACTIVATE_DISPLAY_PROPERTIES` in the puppet constructor.
|
||||||
|
- **Puppet ↔ Object map**: `Viewport::m_ObjectToPuppet` allows lookup by domain object; `vtkObjectsContext::GetPuppet(obj)` does the same.
|
||||||
@@ -84,6 +84,7 @@ macro(uLib_add_tests name)
|
|||||||
foreach(tn ${TESTS})
|
foreach(tn ${TESTS})
|
||||||
add_executable(${tn} ${tn}.cpp)
|
add_executable(${tn} ${tn}.cpp)
|
||||||
add_test(NAME ${tn} COMMAND ${tn})
|
add_test(NAME ${tn} COMMAND ${tn})
|
||||||
|
set_tests_properties(${tn} PROPERTIES ENVIRONMENT "CTEST_PROJECT_NAME=uLib;QT_QPA_PLATFORM=offscreen")
|
||||||
|
|
||||||
target_link_libraries(${tn} ${LIBRARIES})
|
target_link_libraries(${tn} ${LIBRARIES})
|
||||||
|
|
||||||
|
|||||||
@@ -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")
|
||||||
@@ -103,6 +103,7 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_CXX_WARNING_OPTION}")
|
|||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -UULIB_SERIALIZATION_ON -Wno-cpp")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -UULIB_SERIALIZATION_ON -Wno-cpp")
|
||||||
|
|
||||||
# CTEST framework
|
# CTEST framework
|
||||||
|
set(CTEST_PROJECT_NAME "uLib")
|
||||||
include(CTest)
|
include(CTest)
|
||||||
enable_testing()
|
enable_testing()
|
||||||
|
|
||||||
@@ -114,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})
|
||||||
|
|||||||
1
Testing/Temporary/CTestCostData.txt
Normal file
1
Testing/Temporary/CTestCostData.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
---
|
||||||
3
Testing/Temporary/LastTest.log
Normal file
3
Testing/Temporary/LastTest.log
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
Start testing: Mar 25 18:59 UTC
|
||||||
|
----------------------------------------------------------
|
||||||
|
End testing: Mar 25 18:59 UTC
|
||||||
@@ -14,10 +14,25 @@ void ContextModel::setContext(uLib::ObjectsContext* context) {
|
|||||||
beginResetModel();
|
beginResetModel();
|
||||||
m_rootContext = context;
|
m_rootContext = context;
|
||||||
if (m_rootContext) {
|
if (m_rootContext) {
|
||||||
uLib::Object::connect(m_rootContext, &uLib::Object::Updated, [this]() {
|
auto refresh = [this]() {
|
||||||
this->beginResetModel();
|
this->beginResetModel();
|
||||||
this->endResetModel();
|
this->endResetModel();
|
||||||
|
};
|
||||||
|
|
||||||
|
uLib::Object::connect(m_rootContext, &uLib::Object::Updated, refresh);
|
||||||
|
uLib::Object::connect(m_rootContext, &uLib::ObjectsContext::ObjectAdded, [this, refresh](uLib::Object* obj) {
|
||||||
|
uLib::Object::connect(obj, &uLib::Object::Updated, refresh);
|
||||||
|
refresh();
|
||||||
});
|
});
|
||||||
|
uLib::Object::connect(m_rootContext, &uLib::ObjectsContext::ObjectRemoved, [this, refresh](uLib::Object* obj) {
|
||||||
|
// Disconnect would be good here but not strictly required if refresh handles it
|
||||||
|
refresh();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Connect existing objects
|
||||||
|
for (auto* obj : m_rootContext->GetObjects()) {
|
||||||
|
uLib::Object::connect(obj, &uLib::Object::Updated, refresh);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
endResetModel();
|
endResetModel();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,3 +92,27 @@ void ContextPanel::onSelectionChanged(const QItemSelection& selected, const QIte
|
|||||||
emit objectSelected(target);
|
emit objectSelected(target);
|
||||||
m_propertiesPanel->setObject(target);
|
m_propertiesPanel->setObject(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ContextPanel::selectObject(uLib::Object* obj) {
|
||||||
|
if (!obj) {
|
||||||
|
clearSelection();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < m_model->rowCount(); ++i) {
|
||||||
|
QModelIndex idx = m_model->index(i, 0);
|
||||||
|
if (idx.internalPointer() == obj) {
|
||||||
|
QSignalBlocker blocker(m_treeView->selectionModel());
|
||||||
|
m_treeView->selectionModel()->select(idx, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
|
||||||
|
m_treeView->scrollTo(idx);
|
||||||
|
m_propertiesPanel->setObject(obj); // Explicitly update properties too
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextPanel::clearSelection() {
|
||||||
|
QSignalBlocker blocker(m_treeView->selectionModel());
|
||||||
|
m_treeView->selectionModel()->clearSelection();
|
||||||
|
m_propertiesPanel->setObject(nullptr);
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ public:
|
|||||||
~ContextPanel();
|
~ContextPanel();
|
||||||
|
|
||||||
void setContext(uLib::ObjectsContext* context);
|
void setContext(uLib::ObjectsContext* context);
|
||||||
|
void selectObject(uLib::Object* obj);
|
||||||
|
void clearSelection();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void objectSelected(uLib::Object* obj);
|
void objectSelected(uLib::Object* obj);
|
||||||
|
|||||||
@@ -14,7 +14,10 @@
|
|||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
#include <QAction>
|
#include <QAction>
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
|
#include <QFileDialog>
|
||||||
|
#include <QFileInfo>
|
||||||
#include "StyleManager.h"
|
#include "StyleManager.h"
|
||||||
|
#include "Math/VoxImage.h"
|
||||||
|
|
||||||
MainPanel::MainPanel(QWidget* parent) : QWidget(parent), m_context(nullptr), m_mainVtkContext(nullptr) {
|
MainPanel::MainPanel(QWidget* parent) : QWidget(parent), m_context(nullptr), m_mainVtkContext(nullptr) {
|
||||||
this->setObjectName("MainPanel");
|
this->setObjectName("MainPanel");
|
||||||
@@ -124,6 +127,17 @@ void MainPanel::setContext(uLib::ObjectsContext* context) {
|
|||||||
m_mainVtkContext = new uLib::Vtk::vtkObjectsContext(context);
|
m_mainVtkContext = new uLib::Vtk::vtkObjectsContext(context);
|
||||||
// viewport->AddPuppet(*m_mainVtkContext); // redundant
|
// viewport->AddPuppet(*m_mainVtkContext); // redundant
|
||||||
|
|
||||||
|
auto syncSelection = [this](uLib::Vtk::Puppet* p) {
|
||||||
|
if (!p) {
|
||||||
|
m_contextPanel->clearSelection();
|
||||||
|
m_firstPane->setObject(nullptr);
|
||||||
|
} else {
|
||||||
|
m_contextPanel->selectObject(p->GetContent());
|
||||||
|
m_firstPane->setObject(p);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
connect(viewport, &uLib::Vtk::QViewport::puppetSelected, syncSelection);
|
||||||
|
|
||||||
uLib::Object::connect(m_mainVtkContext, &uLib::Vtk::vtkObjectsContext::PuppetAdded, [this](uLib::Vtk::Puppet* p) {
|
uLib::Object::connect(m_mainVtkContext, &uLib::Vtk::vtkObjectsContext::PuppetAdded, [this](uLib::Vtk::Puppet* p) {
|
||||||
if (p) {
|
if (p) {
|
||||||
auto panes = this->findChildren<ViewportPane*>();
|
auto panes = this->findChildren<ViewportPane*>();
|
||||||
@@ -179,7 +193,35 @@ void MainPanel::onCreateObject(const std::string& className) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MainPanel::onOpen() {
|
void MainPanel::onOpen() {
|
||||||
// Placeholder for open logic
|
QString fileName = QFileDialog::getOpenFileName(this, "Open File", "",
|
||||||
|
"VTK/VTI Images (*.vtk *.vti);;All Files (*.*)");
|
||||||
|
|
||||||
|
if (fileName.isEmpty()) return;
|
||||||
|
|
||||||
|
QFileInfo info(fileName);
|
||||||
|
QString ext = info.suffix().toLower();
|
||||||
|
|
||||||
|
if (ext == "vti" || ext == "vtk") {
|
||||||
|
auto* obj = uLib::ObjectFactory::Instance().Create("VoxImage");
|
||||||
|
auto* vox = dynamic_cast<uLib::Abstract::VoxImage*>(obj);
|
||||||
|
if (vox) {
|
||||||
|
bool success = false;
|
||||||
|
if (ext == "vti") {
|
||||||
|
success = vox->ImportFromVti(fileName.toStdString().c_str());
|
||||||
|
} else {
|
||||||
|
success = vox->ImportFromVtk(fileName.toStdString().c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
obj->SetInstanceName(info.fileName().toStdString());
|
||||||
|
m_context->AddObject(obj);
|
||||||
|
} else {
|
||||||
|
delete obj;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
delete obj;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainPanel::onSave() {
|
void MainPanel::onSave() {
|
||||||
|
|||||||
@@ -2,9 +2,12 @@
|
|||||||
#include <QSignalBlocker>
|
#include <QSignalBlocker>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QRegularExpressionMatch>
|
#include <QRegularExpressionMatch>
|
||||||
|
#include <QComboBox>
|
||||||
|
#include <QCheckBox>
|
||||||
#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 "Settings.h"
|
||||||
|
|
||||||
namespace uLib {
|
namespace uLib {
|
||||||
namespace Qt {
|
namespace Qt {
|
||||||
@@ -13,14 +16,29 @@ 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()) {
|
||||||
|
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() {
|
||||||
|
m_Connection.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
// Helper for unit parsing
|
// Helper for unit parsing
|
||||||
static double parseWithUnits(const QString& text, double* factorOut = nullptr, QString* suffixOut = nullptr) {
|
double parseWithUnits(const QString& text, double* factorOut, QString* suffixOut) {
|
||||||
static QRegularExpression re("^\\s*([-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?)\\s*(_?[a-zA-Z]+)?\\s*$");
|
static QRegularExpression re("^\\s*([-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?)\\s*(_?[a-zA-Z]+)?\\s*$");
|
||||||
QRegularExpressionMatch match = re.match(text);
|
QRegularExpressionMatch match = re.match(text);
|
||||||
if (!match.hasMatch()) return 0.0;
|
if (!match.hasMatch()) return 0.0;
|
||||||
@@ -57,14 +75,23 @@ static double parseWithUnits(const QString& text, double* factorOut = nullptr, Q
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UnitLineEdit implementation
|
// UnitLineEdit implementation
|
||||||
UnitLineEdit::UnitLineEdit(QWidget* parent) : QLineEdit(parent), m_Value(0), m_Factor(1.0), m_Suffix("mm"), m_IsInteger(false) {
|
UnitLineEdit::UnitLineEdit(QWidget* parent) : QLineEdit(parent), m_Value(0), m_Factor(1.0), m_Suffix(""), m_IsInteger(false) {
|
||||||
connect(this, &QLineEdit::editingFinished, this, &UnitLineEdit::onEditingFinished);
|
connect(this, &QLineEdit::editingFinished, this, &UnitLineEdit::onEditingFinished);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UnitLineEdit::setUnits(const QString& suffix, double factor) {
|
||||||
|
m_Suffix = suffix;
|
||||||
|
m_Factor = factor;
|
||||||
|
updateText();
|
||||||
|
}
|
||||||
|
|
||||||
void UnitLineEdit::setValue(double val) {
|
void UnitLineEdit::setValue(double val) {
|
||||||
if (m_Value != val) {
|
if (m_Value != val) {
|
||||||
m_Value = val;
|
m_Value = val;
|
||||||
// Initial heuristic for unit if it was mm and value becomes large
|
// Suffix heuristic ONLY if it was mm and no explicit unit was given?
|
||||||
|
// Actually, if m_Suffix is empty or we have a specific one, we should respect it.
|
||||||
|
// The original code had a heuristic, but it's better to let property decide.
|
||||||
|
// Let's keep it ONLY if m_Suffix was mm (legacy behavior)
|
||||||
if (!m_IsInteger && m_Suffix == "mm" && std::abs(val) >= 1000.0) { m_Suffix = "m"; m_Factor = CLHEP::meter; }
|
if (!m_IsInteger && m_Suffix == "mm" && std::abs(val) >= 1000.0) { m_Suffix = "m"; m_Factor = CLHEP::meter; }
|
||||||
updateText();
|
updateText();
|
||||||
}
|
}
|
||||||
@@ -91,12 +118,18 @@ void UnitLineEdit::onEditingFinished() {
|
|||||||
|
|
||||||
void UnitLineEdit::updateText() {
|
void UnitLineEdit::updateText() {
|
||||||
QSignalBlocker blocker(this);
|
QSignalBlocker blocker(this);
|
||||||
|
QString s;
|
||||||
if (m_IsInteger) {
|
if (m_IsInteger) {
|
||||||
setText(QString::number((int)m_Value));
|
s = QString::number((int)m_Value);
|
||||||
|
if (s.isEmpty()) s = "0";
|
||||||
} else {
|
} else {
|
||||||
double displayVal = m_Value / m_Factor;
|
double displayVal = m_Value / m_Factor;
|
||||||
setText(QString::number(displayVal, 'g', 6) + " " + m_Suffix);
|
s = QString::number(displayVal, 'g', 6);
|
||||||
|
if (!s.contains('.') && !s.contains('e')) {
|
||||||
|
s += ".0";
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
setText(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
void UnitLineEdit::setIntegerOnly(bool integerOnly) {
|
void UnitLineEdit::setIntegerOnly(bool integerOnly) {
|
||||||
@@ -107,10 +140,17 @@ 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);
|
||||||
m_Layout->addWidget(m_Edit, 1);
|
std::string unit = prop->GetUnits();
|
||||||
|
if (!unit.empty()) {
|
||||||
|
auto dim = Settings::Instance().IdentifyDimension(unit);
|
||||||
|
std::string pref = Settings::Instance().GetPreferredUnit(dim);
|
||||||
|
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);
|
||||||
connect(m_Edit, &UnitLineEdit::valueManualChanged, [this](double val){ m_Prop->Set(val); });
|
connect(m_Edit, &UnitLineEdit::valueManualChanged, [this](double val){ m_Prop->Set(val); });
|
||||||
uLib::Object::connect(m_Prop, &Property<double>::PropertyChanged, [this](){
|
m_Connection = uLib::Object::connect(m_Prop, &Property<double>::PropertyChanged, [this](){
|
||||||
m_Edit->setValue(m_Prop->Get());
|
m_Edit->setValue(m_Prop->Get());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -118,10 +158,17 @@ 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);
|
||||||
m_Layout->addWidget(m_Edit, 1);
|
std::string unit = prop->GetUnits();
|
||||||
|
if (!unit.empty()) {
|
||||||
|
auto dim = Settings::Instance().IdentifyDimension(unit);
|
||||||
|
std::string pref = Settings::Instance().GetPreferredUnit(dim);
|
||||||
|
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);
|
||||||
connect(m_Edit, &UnitLineEdit::valueManualChanged, [this](double val){ m_Prop->Set((float)val); });
|
connect(m_Edit, &UnitLineEdit::valueManualChanged, [this](double val){ m_Prop->Set((float)val); });
|
||||||
uLib::Object::connect(m_Prop, &Property<float>::PropertyChanged, [this](){
|
m_Connection = uLib::Object::connect(m_Prop, &Property<float>::PropertyChanged, [this](){
|
||||||
m_Edit->setValue((double)m_Prop->Get());
|
m_Edit->setValue((double)m_Prop->Get());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -130,10 +177,17 @@ 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);
|
||||||
m_Layout->addWidget(m_Edit, 1);
|
std::string unit = prop->GetUnits();
|
||||||
|
if (!unit.empty()) {
|
||||||
|
auto dim = Settings::Instance().IdentifyDimension(unit);
|
||||||
|
std::string pref = Settings::Instance().GetPreferredUnit(dim);
|
||||||
|
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);
|
||||||
connect(m_Edit, &UnitLineEdit::valueManualChanged, [this](double val){ m_Prop->Set((int)val); });
|
connect(m_Edit, &UnitLineEdit::valueManualChanged, [this](double val){ m_Prop->Set((int)val); });
|
||||||
uLib::Object::connect(m_Prop, &Property<int>::PropertyChanged, [this](){
|
m_Connection = uLib::Object::connect(m_Prop, &Property<int>::PropertyChanged, [this](){
|
||||||
m_Edit->setValue((double)m_Prop->Get());
|
m_Edit->setValue((double)m_Prop->Get());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -144,7 +198,7 @@ BoolPropertyWidget::BoolPropertyWidget(Property<bool>* prop, QWidget* parent)
|
|||||||
m_CheckBox->setChecked(prop->Get());
|
m_CheckBox->setChecked(prop->Get());
|
||||||
m_Layout->addWidget(m_CheckBox, 1);
|
m_Layout->addWidget(m_CheckBox, 1);
|
||||||
connect(m_CheckBox, &QCheckBox::toggled, [this](bool val){ if (m_Prop->Get() != val) m_Prop->Set(val); });
|
connect(m_CheckBox, &QCheckBox::toggled, [this](bool val){ if (m_Prop->Get() != val) m_Prop->Set(val); });
|
||||||
uLib::Object::connect(m_Prop, &Property<bool>::PropertyChanged, [this](){
|
m_Connection = uLib::Object::connect(m_Prop, &Property<bool>::PropertyChanged, [this](){
|
||||||
if (m_CheckBox->isChecked() != m_Prop->Get()) {
|
if (m_CheckBox->isChecked() != m_Prop->Get()) {
|
||||||
QSignalBlocker blocker(m_CheckBox);
|
QSignalBlocker blocker(m_CheckBox);
|
||||||
m_CheckBox->setChecked(m_Prop->Get());
|
m_CheckBox->setChecked(m_Prop->Get());
|
||||||
@@ -162,7 +216,7 @@ StringPropertyWidget::StringPropertyWidget(Property<std::string>* prop, QWidget*
|
|||||||
std::string val = m_LineEdit->text().toStdString();
|
std::string val = m_LineEdit->text().toStdString();
|
||||||
if (m_Prop->Get() != val) m_Prop->Set(val);
|
if (m_Prop->Get() != val) m_Prop->Set(val);
|
||||||
});
|
});
|
||||||
uLib::Object::connect(m_Prop, &Property<std::string>::PropertyChanged, [this](){
|
m_Connection = uLib::Object::connect(m_Prop, &Property<std::string>::PropertyChanged, [this](){
|
||||||
if (m_LineEdit->text().toStdString() != m_Prop->Get()) {
|
if (m_LineEdit->text().toStdString() != m_Prop->Get()) {
|
||||||
QSignalBlocker blocker(m_LineEdit);
|
QSignalBlocker blocker(m_LineEdit);
|
||||||
m_LineEdit->setText(QString::fromStdString(m_Prop->Get()));
|
m_LineEdit->setText(QString::fromStdString(m_Prop->Get()));
|
||||||
@@ -171,6 +225,57 @@ 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 {
|
||||||
|
PropertyBase* m_Prop;
|
||||||
|
QComboBox* m_Combo;
|
||||||
|
public:
|
||||||
|
EnumPropertyWidget(PropertyBase* prop, QWidget* parent)
|
||||||
|
: PropertyWidgetBase(prop, parent), m_Prop(prop) {
|
||||||
|
m_Combo = new QComboBox(this);
|
||||||
|
const auto& labels = prop->GetEnumLabels();
|
||||||
|
for (const auto& label : labels) {
|
||||||
|
m_Combo->addItem(QString::fromStdString(label));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get initial value
|
||||||
|
if (auto* p = dynamic_cast<Property<int>*>(prop)) {
|
||||||
|
m_Combo->setCurrentIndex(p->Get());
|
||||||
|
connect(m_Combo, &QComboBox::currentIndexChanged, [p](int index){
|
||||||
|
p->Set(index);
|
||||||
|
});
|
||||||
|
// Store connection in base m_Connection so it's auto-disconnected on destruction.
|
||||||
|
m_Connection = uLib::Object::connect(p, &Property<int>::PropertyChanged, [this, p](){
|
||||||
|
if (m_Combo->currentIndex() != p->Get()) {
|
||||||
|
QSignalBlocker blocker(m_Combo);
|
||||||
|
m_Combo->setCurrentIndex(p->Get());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
m_Layout->addWidget(m_Combo, 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
PropertyEditor::PropertyEditor(QWidget* parent) : QWidget(parent), m_Object(nullptr) {
|
PropertyEditor::PropertyEditor(QWidget* parent) : QWidget(parent), m_Object(nullptr) {
|
||||||
m_MainLayout = new QVBoxLayout(this);
|
m_MainLayout = new QVBoxLayout(this);
|
||||||
m_MainLayout->setContentsMargins(0, 0, 0, 0);
|
m_MainLayout->setContentsMargins(0, 0, 0, 0);
|
||||||
@@ -198,6 +303,11 @@ PropertyEditor::PropertyEditor(QWidget* parent) : QWidget(parent), m_Object(null
|
|||||||
return new StringPropertyWidget(static_cast<Property<std::string>*>(p), parent);
|
return new StringPropertyWidget(static_cast<Property<std::string>*>(p), parent);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Register EnumProperty specifically (needs to check type since it holds Property<int> but is EnumProperty)
|
||||||
|
m_Factories[std::type_index(typeid(EnumProperty))] = [](PropertyBase* p, QWidget* parent) {
|
||||||
|
return new EnumPropertyWidget(p, parent);
|
||||||
|
};
|
||||||
|
|
||||||
// Vector Registration
|
// Vector Registration
|
||||||
registerFactory<Vector2i>([](PropertyBase* p, QWidget* parent){ return new VectorPropertyWidget<Vector2i, 2>(static_cast<Property<Vector2i>*>(p), parent); });
|
registerFactory<Vector2i>([](PropertyBase* p, QWidget* parent){ return new VectorPropertyWidget<Vector2i, 2>(static_cast<Property<Vector2i>*>(p), parent); });
|
||||||
registerFactory<Vector2f>([](PropertyBase* p, QWidget* parent){ return new VectorPropertyWidget<Vector2f, 2>(static_cast<Property<Vector2f>*>(p), parent); });
|
registerFactory<Vector2f>([](PropertyBase* p, QWidget* parent){ return new VectorPropertyWidget<Vector2f, 2>(static_cast<Property<Vector2f>*>(p), parent); });
|
||||||
@@ -229,15 +339,51 @@ 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) {
|
||||||
|
std::string group = prop->GetGroup();
|
||||||
|
if (groupedProps.find(group) == groupedProps.end()) {
|
||||||
|
groupOrder.push_back(group);
|
||||||
|
}
|
||||||
|
groupedProps[group].push_back(prop);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& groupName : groupOrder) {
|
||||||
|
if (!groupName.empty()) {
|
||||||
|
m_ContainerLayout->addWidget(new GroupHeaderWidget(QString::fromStdString(groupName), m_Container));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto* prop : groupedProps[groupName]) {
|
||||||
|
QWidget* widget = nullptr;
|
||||||
|
|
||||||
|
// Priority 1: Check if it provides enum labels
|
||||||
|
if (!prop->GetEnumLabels().empty()) {
|
||||||
|
widget = new EnumPropertyWidget(prop, m_Container);
|
||||||
|
} else {
|
||||||
|
// Priority 2: Standard factory lookup
|
||||||
auto it = m_Factories.find(prop->GetTypeIndex());
|
auto it = m_Factories.find(prop->GetTypeIndex());
|
||||||
if (it != m_Factories.end()) {
|
if (it != m_Factories.end()) {
|
||||||
QWidget* widget = it->second(prop, m_Container);
|
widget = it->second(prop, m_Container);
|
||||||
m_ContainerLayout->addWidget(widget);
|
|
||||||
} else {
|
} else {
|
||||||
QWidget* fallback = new PropertyWidgetBase(prop, m_Container);
|
// Debug info for unknown types
|
||||||
fallback->layout()->addWidget(new QLabel("(Read-only: " + QString::fromStdString(prop->GetValueAsString()) + ")"));
|
std::cout << "PropertyEditor: No factory for " << prop->GetQualifiedName()
|
||||||
m_ContainerLayout->addWidget(fallback);
|
<< " (Type: " << prop->GetTypeName() << ")" << std::endl;
|
||||||
|
|
||||||
|
widget = new PropertyWidgetBase(prop, m_Container);
|
||||||
|
widget->layout()->addWidget(new QLabel("(Read-only: " + QString::fromStdString(prop->GetValueAsString()) + ")"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|||||||
@@ -14,11 +14,15 @@
|
|||||||
|
|
||||||
#include "Core/Property.h"
|
#include "Core/Property.h"
|
||||||
#include "Core/Object.h"
|
#include "Core/Object.h"
|
||||||
|
#include "Core/Signal.h"
|
||||||
#include "Math/Dense.h"
|
#include "Math/Dense.h"
|
||||||
|
#include "Settings.h"
|
||||||
|
|
||||||
namespace uLib {
|
namespace uLib {
|
||||||
namespace Qt {
|
namespace Qt {
|
||||||
|
|
||||||
|
double parseWithUnits(const QString& text, double* factorOut = nullptr, QString* suffixOut = nullptr);
|
||||||
|
|
||||||
class PropertyWidgetBase : public QWidget {
|
class PropertyWidgetBase : public QWidget {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
@@ -30,6 +34,9 @@ protected:
|
|||||||
PropertyBase* m_BaseProperty;
|
PropertyBase* m_BaseProperty;
|
||||||
QHBoxLayout* m_Layout;
|
QHBoxLayout* m_Layout;
|
||||||
QLabel* m_Label;
|
QLabel* m_Label;
|
||||||
|
// Stores the uLib signal connection so it can be disconnected on destruction,
|
||||||
|
// preventing use-after-free when PropertyEditor::clear() deletes widgets.
|
||||||
|
Connection m_Connection;
|
||||||
};
|
};
|
||||||
|
|
||||||
class UnitLineEdit : public QLineEdit {
|
class UnitLineEdit : public QLineEdit {
|
||||||
@@ -37,6 +44,7 @@ class UnitLineEdit : public QLineEdit {
|
|||||||
public:
|
public:
|
||||||
UnitLineEdit(QWidget* parent = nullptr);
|
UnitLineEdit(QWidget* parent = nullptr);
|
||||||
void setValue(double val);
|
void setValue(double val);
|
||||||
|
void setUnits(const QString& suffix, double factor = 1.0);
|
||||||
double getValue() const { return m_Value; }
|
double getValue() const { return m_Value; }
|
||||||
void setIntegerOnly(bool b);
|
void setIntegerOnly(bool b);
|
||||||
|
|
||||||
@@ -86,11 +94,25 @@ 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) {
|
||||||
|
|
||||||
|
std::string unit = prop->GetUnits();
|
||||||
|
double factor = 1.0;
|
||||||
|
QString prefSuffix;
|
||||||
|
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 (!prefSuffix.isEmpty()) {
|
||||||
|
m_Edits[i]->setUnits(prefSuffix, factor);
|
||||||
|
}
|
||||||
m_Layout->addWidget(m_Edits[i], 1);
|
m_Layout->addWidget(m_Edits[i], 1);
|
||||||
|
|
||||||
connect(m_Edits[i], &UnitLineEdit::valueManualChanged, [this, i](double val){
|
connect(m_Edits[i], &UnitLineEdit::valueManualChanged, [this, i](double val){
|
||||||
@@ -100,10 +122,11 @@ public:
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
updateEdits();
|
updateEdits();
|
||||||
uLib::Object::connect(m_Prop, &Property<VecT>::PropertyChanged, [this](){
|
m_Connection = uLib::Object::connect(m_Prop, &Property<VecT>::PropertyChanged, [this](){
|
||||||
updateEdits();
|
updateEdits();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
~VectorPropertyWidget() { m_Connection.disconnect(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void updateEdits() {
|
void updateEdits() {
|
||||||
|
|||||||
75
app/gcompose/src/Settings.h
Normal file
75
app/gcompose/src/Settings.h
Normal 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
|
||||||
@@ -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);
|
||||||
|
|
||||||
@@ -73,8 +75,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);
|
||||||
connect(closeBtn, &QToolButton::clicked, this, &ViewportPane::onCloseRequested);
|
connect(closeBtn, &QToolButton::clicked, this, &ViewportPane::onCloseRequested);
|
||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
BIN
assets/exmaples/vtk/2026_03_24_C1_Prod11_test_img_40_trim55505_scale1.00_sigma1.0.vtk
(Stored with Git LFS)
Normal file
BIN
assets/exmaples/vtk/2026_03_24_C1_Prod11_test_img_40_trim55505_scale1.00_sigma1.0.vtk
(Stored with Git LFS)
Normal file
Binary file not shown.
338
docs/algorithms/algoritm.md
Normal file
338
docs/algorithms/algoritm.md
Normal file
@@ -0,0 +1,338 @@
|
|||||||
|
# Algorithm Infrastructure
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
An algorithm in the uLib infrastructure is a class for containing a functional that can be dynamically loaded into memory as a plug-in.
|
||||||
|
It derives from the base `Object` class (`Core/Object.h`) and therefore can contain properties that define the serialization of operating parameters or the implementation of widgets for interactive parameter manipulation.
|
||||||
|
|
||||||
|
The algorithm class is designed to be inserted into an `AlgorithmTask`, a class for managing the execution of scheduled operations. A task contains `Run` and `Stop` methods to start and stop execution. A task can be configured to work in two modes:
|
||||||
|
|
||||||
|
- **Cyclic mode**: the algorithm is executed periodically with a configurable cycle time.
|
||||||
|
- **Asynchronous mode**: the task waits for a trigger before each execution. Triggers can come from the uLib signal-slot system (`Object::connect`) or from a condition variable as defined in the monitor pattern (`Core/Monitor.h`).
|
||||||
|
|
||||||
|
The algorithm is defined as a template class on two types `T_enc` and `T_dec`. The encoder is a type for data input or another algorithm that is chained with this one and outputs data in a compatible format. The decoder is the type of data output or a downstream algorithm compatible with it.
|
||||||
|
|
||||||
|
## Class Hierarchy
|
||||||
|
|
||||||
|
```
|
||||||
|
Object (Core/Object.h)
|
||||||
|
|
|
||||||
|
+-- Algorithm<T_enc, T_dec> (Core/Algorithm.h)
|
||||||
|
| |
|
||||||
|
| +-- VoxImageFilter<VoxelT, CrtpImplT> (Math/VoxImageFilter.h)
|
||||||
|
| |
|
||||||
|
| +-- VoxFilterAlgorithmLinear (Math/VoxImageFilterLinear.hpp)
|
||||||
|
| +-- VoxFilterAlgorithmMedian (Math/VoxImageFilterMedian.hpp)
|
||||||
|
| +-- VoxFilterAlgorithmAbtrim (Math/VoxImageFilterABTrim.hpp)
|
||||||
|
| +-- VoxFilterAlgorithmSPR (Math/VoxImageFilterABTrim.hpp)
|
||||||
|
| +-- VoxFilterAlgorithmThreshold (Math/VoxImageFilterThreshold.hpp)
|
||||||
|
| +-- VoxFilterAlgorithmBilateral (Math/VoxImageFilterBilateral.hpp)
|
||||||
|
| +-- VoxFilterAlgorithmBilateralTrim(Math/VoxImageFilterBilateral.hpp)
|
||||||
|
| +-- VoxFilterAlgorithm2ndStat (Math/VoxImageFilter2ndStat.hpp)
|
||||||
|
| +-- VoxFilterAlgorithmCustom (Math/VoxImageFilterCustom.hpp)
|
||||||
|
|
|
||||||
|
+-- Thread (Core/Threads.h)
|
||||||
|
|
|
||||||
|
+-- AlgorithmTask<T_enc, T_dec> (Core/Algorithm.h)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Algorithm (`Core/Algorithm.h`)
|
||||||
|
|
||||||
|
### Template Parameters
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
template <typename T_enc, typename T_dec>
|
||||||
|
class Algorithm : public Object;
|
||||||
|
```
|
||||||
|
|
||||||
|
- **`T_enc`** (Encoder): the input data type. Can be a raw data type or a pointer to a data structure. When chaining algorithms, the upstream algorithm's `T_dec` must be compatible with this algorithm's `T_enc`.
|
||||||
|
- **`T_dec`** (Decoder): the output data type. Produced by `Process()` and consumed by the next algorithm in the chain.
|
||||||
|
|
||||||
|
### Core Interface
|
||||||
|
|
||||||
|
| Method | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `virtual T_dec Process(const T_enc& input) = 0` | Pure virtual. Implement the algorithm logic here. |
|
||||||
|
| `T_dec operator()(const T_enc& input)` | Calls `Process()`. Enables functional syntax: `result = alg(data)`. |
|
||||||
|
|
||||||
|
### Algorithm Chaining
|
||||||
|
|
||||||
|
Algorithms can be linked in processing pipelines via encoder/decoder pointers:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Algorithm* upstream; // SetEncoder() / GetEncoder()
|
||||||
|
Algorithm* downstream; // SetDecoder() / GetDecoder()
|
||||||
|
```
|
||||||
|
|
||||||
|
This allows building chains like:
|
||||||
|
|
||||||
|
```
|
||||||
|
[RawData] --> AlgorithmA --> AlgorithmB --> [Result]
|
||||||
|
encoder decoder
|
||||||
|
```
|
||||||
|
|
||||||
|
### Signals
|
||||||
|
|
||||||
|
| Signal | Emitted when |
|
||||||
|
|--------|-------------|
|
||||||
|
| `Started()` | The algorithm begins processing (caller responsibility). |
|
||||||
|
| `Finished()` | The algorithm completes processing (caller responsibility). |
|
||||||
|
|
||||||
|
### Device Preference (CUDA)
|
||||||
|
|
||||||
|
Algorithms report their preferred execution device via `GetPreferredDevice()`:
|
||||||
|
|
||||||
|
| Method | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `virtual MemoryDevice GetPreferredDevice() const` | Returns `RAM` or `VRAM`. Subclasses override. |
|
||||||
|
| `void SetPreferredDevice(MemoryDevice dev)` | Manually set the device preference. |
|
||||||
|
| `bool IsGPU() const` | Shorthand for `GetPreferredDevice() == VRAM`. |
|
||||||
|
|
||||||
|
GPU-based algorithms are responsible for calling `cudaDeviceSynchronize()` inside their `Process()` implementation before returning, so that results are available to the caller or downstream algorithm.
|
||||||
|
|
||||||
|
### Example: Defining a Custom Algorithm
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class MyFilter : public Algorithm<VoxImage<Voxel>*, VoxImage<Voxel>*> {
|
||||||
|
public:
|
||||||
|
const char* GetClassName() const override { return "MyFilter"; }
|
||||||
|
|
||||||
|
VoxImage<Voxel>* Process(VoxImage<Voxel>* const& image) override {
|
||||||
|
// ... filter the image in-place ...
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## AlgorithmTask (`Core/Algorithm.h`)
|
||||||
|
|
||||||
|
`AlgorithmTask` manages the execution of an `Algorithm` within a scheduled, threaded context. It inherits from `Thread` (`Core/Threads.h`) and uses `Mutex` (`Core/Monitor.h`) for synchronization.
|
||||||
|
|
||||||
|
### Template Parameters
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
template <typename T_enc, typename T_dec>
|
||||||
|
class AlgorithmTask : public Thread;
|
||||||
|
```
|
||||||
|
|
||||||
|
Must match the `Algorithm<T_enc, T_dec>` it manages.
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
| Method | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `void SetAlgorithm(AlgorithmType* alg)` | Set the algorithm to execute. |
|
||||||
|
| `void SetMode(Mode mode)` | `Cyclic` or `Async`. |
|
||||||
|
| `void SetCycleTime(int ms)` | Period for cyclic mode (milliseconds). |
|
||||||
|
|
||||||
|
### Execution Modes
|
||||||
|
|
||||||
|
#### Cyclic Mode
|
||||||
|
|
||||||
|
The algorithm's `Process()` is called periodically. The cycle waits on a `condition_variable_any` with timeout, so `Stop()` can interrupt immediately without waiting for the full cycle.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
AlgorithmTask<int, int> task;
|
||||||
|
task.SetAlgorithm(&myAlgorithm);
|
||||||
|
task.SetMode(AlgorithmTask<int, int>::Cyclic);
|
||||||
|
task.SetCycleTime(100); // every 100ms
|
||||||
|
task.Run(inputData);
|
||||||
|
// ... later ...
|
||||||
|
task.Stop();
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Asynchronous Mode
|
||||||
|
|
||||||
|
The task thread blocks on a condition variable until `Notify()` is called. Each notification triggers exactly one `Process()` invocation.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
task.SetMode(AlgorithmTask<int, int>::Async);
|
||||||
|
task.Run(inputData);
|
||||||
|
|
||||||
|
// Trigger manually:
|
||||||
|
task.Notify();
|
||||||
|
|
||||||
|
// Or connect to a signal:
|
||||||
|
task.ConnectTrigger(sender, &SenderClass::DataReady);
|
||||||
|
// Now each emission of DataReady() triggers one Process() call.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Lifecycle
|
||||||
|
|
||||||
|
| Method | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `void Run(const T_enc& input)` | Starts the background thread with the given input. |
|
||||||
|
| `void Stop()` | Requests stop and joins the thread. |
|
||||||
|
| `bool IsRunning()` | Inherited from `Thread`. |
|
||||||
|
|
||||||
|
### Signals
|
||||||
|
|
||||||
|
| Signal | Emitted when |
|
||||||
|
|--------|-------------|
|
||||||
|
| `Stopped()` | The task thread has completed (after last `Process()` and before thread exit). |
|
||||||
|
|
||||||
|
### Signal-Slot Triggering
|
||||||
|
|
||||||
|
`ConnectTrigger()` connects any uLib `Object` signal to the task's `Notify()` method:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
task.ConnectTrigger(detector, &Detector::EventReady);
|
||||||
|
```
|
||||||
|
|
||||||
|
This uses the uLib signal system (`Core/Signal.h`), not Qt signals. The connection is type-safe and works with the `Object::connect` infrastructure.
|
||||||
|
|
||||||
|
## VoxImageFilter (`Math/VoxImageFilter.h`)
|
||||||
|
|
||||||
|
`VoxImageFilter` specializes `Algorithm` for kernel-based volumetric image filtering. It uses CRTP (Curiously Recurring Template Pattern) so that concrete filters provide their `Evaluate()` method without virtual dispatch overhead in the inner loop.
|
||||||
|
|
||||||
|
### Template Parameters
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
template <typename VoxelT, typename CrtpImplT>
|
||||||
|
class VoxImageFilter : public Abstract::VoxImageFilter,
|
||||||
|
public Algorithm<VoxImage<VoxelT>*, VoxImage<VoxelT>*>;
|
||||||
|
```
|
||||||
|
|
||||||
|
- **`VoxelT`**: the voxel data type (must satisfy `Interface::Voxel` — requires `.Value` and `.Count` fields).
|
||||||
|
- **`CrtpImplT`**: the concrete filter subclass. Must implement:
|
||||||
|
```cpp
|
||||||
|
float Evaluate(const VoxImage<VoxelT>& buffer, int index);
|
||||||
|
```
|
||||||
|
|
||||||
|
### How It Works
|
||||||
|
|
||||||
|
1. `Process(image)` creates a read-only buffer copy of the input image.
|
||||||
|
2. For each voxel in parallel (OpenMP), it calls `CrtpImplT::Evaluate(buffer, index)`.
|
||||||
|
3. `Evaluate()` reads from the buffer using the kernel offsets and writes the result.
|
||||||
|
4. The filtered image is returned (in-place modification).
|
||||||
|
|
||||||
|
```
|
||||||
|
Process(image)
|
||||||
|
|
|
||||||
|
+-- buffer = copy of image (read-only snapshot)
|
||||||
|
|
|
||||||
|
+-- #pragma omp parallel for
|
||||||
|
| for each voxel i:
|
||||||
|
| image[i].Value = CrtpImplT::Evaluate(buffer, i)
|
||||||
|
|
|
||||||
|
+-- return image
|
||||||
|
```
|
||||||
|
|
||||||
|
### Kernel System
|
||||||
|
|
||||||
|
The `Kernel<VoxelT>` class stores convolution weights and precomputed index offsets:
|
||||||
|
|
||||||
|
| Method | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `SetKernelNumericXZY(values)` | Set kernel weights from a flat vector (XZY order). |
|
||||||
|
| `SetKernelSpherical(shape)` | Set weights via a radial function `f(distance^2)`. |
|
||||||
|
| `SetKernelWeightFunction(shape)` | Set weights via a 3D position function `f(Vector3f)`. |
|
||||||
|
|
||||||
|
### CUDA Support
|
||||||
|
|
||||||
|
Concrete filters can override `Process()` with a CUDA implementation:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#if defined(USE_CUDA) && defined(__CUDACC__)
|
||||||
|
VoxImage<VoxelT>* Process(VoxImage<VoxelT>* const& image) override {
|
||||||
|
if (this->GetPreferredDevice() == MemoryDevice::VRAM) {
|
||||||
|
// Launch CUDA kernel, synchronize, return
|
||||||
|
} else {
|
||||||
|
return BaseClass::Process(image); // CPU fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
```
|
||||||
|
|
||||||
|
The base class `GetPreferredDevice()` automatically returns `VRAM` when the image or kernel data resides on the GPU, enabling transparent device dispatch.
|
||||||
|
|
||||||
|
Filters with CUDA implementations: `VoxFilterAlgorithmLinear`, `VoxFilterAlgorithmAbtrim`, `VoxFilterAlgorithmSPR`.
|
||||||
|
|
||||||
|
### Concrete Filters
|
||||||
|
|
||||||
|
| Filter | File | Description |
|
||||||
|
|--------|------|-------------|
|
||||||
|
| `VoxFilterAlgorithmLinear` | `VoxImageFilterLinear.hpp` | Weighted linear convolution (FIR filter). CUDA-enabled. |
|
||||||
|
| `VoxFilterAlgorithmMedian` | `VoxImageFilterMedian.hpp` | Median filter with kernel-weighted sorting. |
|
||||||
|
| `VoxFilterAlgorithmAbtrim` | `VoxImageFilterABTrim.hpp` | Alpha-beta trimmed mean filter. CUDA-enabled. |
|
||||||
|
| `VoxFilterAlgorithmSPR` | `VoxImageFilterABTrim.hpp` | Robespierre filter: trimmed mean applied only to outlier voxels. CUDA-enabled. |
|
||||||
|
| `VoxFilterAlgorithmThreshold` | `VoxImageFilterThreshold.hpp` | Binary threshold filter. |
|
||||||
|
| `VoxFilterAlgorithmBilateral` | `VoxImageFilterBilateral.hpp` | Edge-preserving bilateral filter (intensity-weighted Gaussian). |
|
||||||
|
| `VoxFilterAlgorithmBilateralTrim` | `VoxImageFilterBilateral.hpp` | Bilateral filter with alpha-beta trimming. |
|
||||||
|
| `VoxFilterAlgorithm2ndStat` | `VoxImageFilter2ndStat.hpp` | Local variance (second-order statistic). |
|
||||||
|
| `VoxFilterAlgorithmCustom` | `VoxImageFilterCustom.hpp` | User-supplied evaluation function via function pointer. |
|
||||||
|
|
||||||
|
### Example: Using a Filter with AlgorithmTask
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Create filter and configure kernel
|
||||||
|
VoxFilterAlgorithmLinear<Voxel> filter(Vector3i(3, 3, 3));
|
||||||
|
std::vector<float> weights(27, 1.0f); // uniform 3x3x3
|
||||||
|
filter.SetKernelNumericXZY(weights);
|
||||||
|
|
||||||
|
// Direct use
|
||||||
|
filter.SetImage(&image);
|
||||||
|
filter.Run();
|
||||||
|
|
||||||
|
// Or via Algorithm interface
|
||||||
|
VoxImage<Voxel>* result = filter.Process(&image);
|
||||||
|
|
||||||
|
// Or scheduled in a task
|
||||||
|
AlgorithmTask<VoxImage<Voxel>*, VoxImage<Voxel>*> task;
|
||||||
|
task.SetAlgorithm(&filter);
|
||||||
|
task.SetMode(AlgorithmTask<VoxImage<Voxel>*, VoxImage<Voxel>*>::Cyclic);
|
||||||
|
task.SetCycleTime(500);
|
||||||
|
task.Run(&image);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Structural Benefits
|
||||||
|
|
||||||
|
### 1. Uniform Processing Interface
|
||||||
|
|
||||||
|
Every algorithm — from a simple threshold to a GPU-accelerated convolution — exposes the same `Process(input) -> output` interface. Client code does not need to know the concrete type:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Algorithm<VoxImage<Voxel>*, VoxImage<Voxel>*>* alg = &anyFilter;
|
||||||
|
alg->Process(&image);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Pipeline Composition
|
||||||
|
|
||||||
|
The encoder/decoder chaining allows building data processing pipelines where each stage transforms data and passes it to the next. Type safety is enforced at compile time through template parameters.
|
||||||
|
|
||||||
|
### 3. Scheduled and Event-Driven Execution
|
||||||
|
|
||||||
|
`AlgorithmTask` decouples the algorithm from its execution schedule. The same algorithm can be:
|
||||||
|
- Called directly (`Process()`)
|
||||||
|
- Run periodically (Cyclic mode for monitoring/acquisition)
|
||||||
|
- Triggered by events (Async mode for reactive processing)
|
||||||
|
|
||||||
|
### 4. Transparent CPU/GPU Dispatch
|
||||||
|
|
||||||
|
The `MemoryDevice` preference and `GetPreferredDevice()` virtual allow the same algorithm interface to dispatch to CPU or GPU implementations. The `DataAllocator` transparently manages RAM/VRAM transfers, and concrete filters override `Process()` with CUDA kernels when data is on the GPU.
|
||||||
|
|
||||||
|
### 5. Integration with the Object System
|
||||||
|
|
||||||
|
Since `Algorithm` inherits from `Object`, algorithms gain:
|
||||||
|
- **Properties**: serializable parameters via the `Property<T>` system, enabling persistent configuration and GUI widget generation.
|
||||||
|
- **Signals**: `Started`/`Finished` notifications for connecting to monitoring or logging.
|
||||||
|
- **Serialization**: save/load algorithm configuration via Boost archives.
|
||||||
|
- **Instance naming**: `SetInstanceName()` for runtime identification in contexts.
|
||||||
|
|
||||||
|
### 6. CRTP Performance for Inner Loops
|
||||||
|
|
||||||
|
`VoxImageFilter` uses CRTP to dispatch to `Evaluate()` without virtual function overhead. The per-voxel evaluation runs at full speed inside OpenMP parallel loops, while the outer `Process()` method remains virtual for polymorphic use through the Algorithm interface.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
```
|
||||||
|
Core/Object.h — base class, properties, signals, serialization
|
||||||
|
Core/Signal.h — signal-slot connection infrastructure
|
||||||
|
Core/Monitor.h — Mutex, condition variables, ULIB_MUTEX_LOCK
|
||||||
|
Core/Threads.h — Thread base class for AlgorithmTask
|
||||||
|
Core/DataAllocator.h — MemoryDevice enum, RAM/VRAM data management
|
||||||
|
Math/VoxImage.h — volumetric image container
|
||||||
|
Math/VoxImageFilter.h — kernel-based filter framework
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
263
src/Core/Algorithm.h
Normal file
263
src/Core/Algorithm.h
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
/*//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// 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 U_CORE_ALGORITHM_H
|
||||||
|
#define U_CORE_ALGORITHM_H
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
|
#include <condition_variable>
|
||||||
|
|
||||||
|
#include "Core/Object.h"
|
||||||
|
#include "Core/Monitor.h"
|
||||||
|
#include "Core/Threads.h"
|
||||||
|
#include "Core/DataAllocator.h"
|
||||||
|
|
||||||
|
namespace uLib {
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//// ALGORITHM /////////////////////////////////////////////////////////////////
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Algorithm is a template class for containing a functional that can be
|
||||||
|
* dynamically loaded as a plug-in. It derives from Object and supports
|
||||||
|
* properties for serialization and interactive parameter widgets.
|
||||||
|
*
|
||||||
|
* Algorithms are responsible for their own GPU synchronization: if Process()
|
||||||
|
* launches CUDA kernels, it must call cudaDeviceSynchronize() before returning
|
||||||
|
* so that the result is available to the caller or downstream algorithm.
|
||||||
|
*
|
||||||
|
* @tparam T_enc Encoder type: the input data type, or a chained algorithm
|
||||||
|
* whose output is compatible with this algorithm's input.
|
||||||
|
* @tparam T_dec Decoder type: the output data type, or a chained algorithm
|
||||||
|
* whose input is compatible with this algorithm's output.
|
||||||
|
*/
|
||||||
|
template <typename T_enc, typename T_dec>
|
||||||
|
class Algorithm : public Object {
|
||||||
|
public:
|
||||||
|
using EncoderType = T_enc;
|
||||||
|
using DecoderType = T_dec;
|
||||||
|
|
||||||
|
Algorithm()
|
||||||
|
: Object()
|
||||||
|
, m_Encoder(nullptr)
|
||||||
|
, m_Decoder(nullptr)
|
||||||
|
, m_PreferredDevice(MemoryDevice::RAM)
|
||||||
|
{}
|
||||||
|
virtual ~Algorithm() = default;
|
||||||
|
|
||||||
|
virtual const char* GetClassName() const override { return "Algorithm"; }
|
||||||
|
|
||||||
|
// Processing ///////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Process input data and produce output.
|
||||||
|
* Override this in subclasses to implement the algorithm logic.
|
||||||
|
* GPU-based implementations must synchronize before returning.
|
||||||
|
*/
|
||||||
|
virtual T_dec Process(const T_enc& input) = 0;
|
||||||
|
|
||||||
|
/** @brief Operator form of Process for functional chaining. */
|
||||||
|
T_dec operator()(const T_enc& input) { return Process(input); }
|
||||||
|
|
||||||
|
// Chaining /////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
void SetEncoder(Algorithm* enc) { m_Encoder = enc; }
|
||||||
|
Algorithm* GetEncoder() const { return m_Encoder; }
|
||||||
|
|
||||||
|
void SetDecoder(Algorithm* dec) { m_Decoder = dec; }
|
||||||
|
Algorithm* GetDecoder() const { return m_Decoder; }
|
||||||
|
|
||||||
|
// Device preference ////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the preferred memory device for this algorithm.
|
||||||
|
* CUDA-capable algorithms should override to return VRAM when their
|
||||||
|
* data resides on the GPU.
|
||||||
|
*/
|
||||||
|
virtual MemoryDevice GetPreferredDevice() const { return m_PreferredDevice; }
|
||||||
|
void SetPreferredDevice(MemoryDevice dev) { m_PreferredDevice = dev; }
|
||||||
|
|
||||||
|
/** @brief Returns true if this algorithm prefers GPU execution. */
|
||||||
|
bool IsGPU() const { return GetPreferredDevice() == MemoryDevice::VRAM; }
|
||||||
|
|
||||||
|
// Signals //////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
signals:
|
||||||
|
virtual void Started() { ULIB_SIGNAL_EMIT(Algorithm::Started); }
|
||||||
|
virtual void Finished() { ULIB_SIGNAL_EMIT(Algorithm::Finished); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Algorithm* m_Encoder;
|
||||||
|
Algorithm* m_Decoder;
|
||||||
|
MemoryDevice m_PreferredDevice;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//// ALGORITHM TASK ////////////////////////////////////////////////////////////
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief AlgorithmTask manages the execution of an Algorithm within a
|
||||||
|
* scheduled context. Uses uLib::Thread for execution and uLib::Mutex for
|
||||||
|
* synchronization.
|
||||||
|
*
|
||||||
|
* Two execution modes:
|
||||||
|
* - Cyclic: executes Process() periodically with configurable cycle time.
|
||||||
|
* - Async: waits for Notify() or a connected signal before each execution.
|
||||||
|
*
|
||||||
|
* GPU synchronization is the algorithm's responsibility (see Algorithm::Process).
|
||||||
|
*/
|
||||||
|
template <typename T_enc, typename T_dec>
|
||||||
|
class AlgorithmTask : public Thread {
|
||||||
|
public:
|
||||||
|
using AlgorithmType = Algorithm<T_enc, T_dec>;
|
||||||
|
|
||||||
|
enum Mode { Cyclic, Async };
|
||||||
|
|
||||||
|
AlgorithmTask()
|
||||||
|
: Thread()
|
||||||
|
, m_Algorithm(nullptr)
|
||||||
|
, m_Mode(Cyclic)
|
||||||
|
, m_CycleTime_ms(1000)
|
||||||
|
, m_StopRequested(false)
|
||||||
|
, m_Triggered(false)
|
||||||
|
{}
|
||||||
|
|
||||||
|
virtual ~AlgorithmTask() { Stop(); }
|
||||||
|
|
||||||
|
virtual const char* GetClassName() const override { return "AlgorithmTask"; }
|
||||||
|
|
||||||
|
// Configuration ////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
void SetAlgorithm(AlgorithmType* alg) { m_Algorithm = alg; }
|
||||||
|
AlgorithmType* GetAlgorithm() const { return m_Algorithm; }
|
||||||
|
|
||||||
|
void SetMode(Mode mode) { m_Mode = mode; }
|
||||||
|
Mode GetMode() const { return m_Mode; }
|
||||||
|
|
||||||
|
void SetCycleTime(int milliseconds) { m_CycleTime_ms = milliseconds; }
|
||||||
|
int GetCycleTime() const { return m_CycleTime_ms; }
|
||||||
|
|
||||||
|
// Lifecycle ////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Start the task execution in a separate thread (via Thread::Start).
|
||||||
|
* In Cyclic mode, the algorithm is executed periodically.
|
||||||
|
* In Async mode, call Notify() or connect a signal to trigger execution.
|
||||||
|
*/
|
||||||
|
void Run(const T_enc& input) {
|
||||||
|
if (IsRunning()) return;
|
||||||
|
m_StopRequested.store(false);
|
||||||
|
m_Triggered.store(false);
|
||||||
|
m_Input = input;
|
||||||
|
Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @brief Stop the task execution and join the thread. */
|
||||||
|
void Stop() {
|
||||||
|
m_StopRequested.store(true);
|
||||||
|
ULIB_MUTEX_LOCK(m_WaitMutex, -1) {
|
||||||
|
m_Condition.notify_all();
|
||||||
|
}
|
||||||
|
if (IsJoinable()) Join();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Async triggering /////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Notify the task to execute one iteration (Async mode).
|
||||||
|
* Can be called from a signal-slot connection or externally.
|
||||||
|
*/
|
||||||
|
void Notify() {
|
||||||
|
m_Triggered.store(true);
|
||||||
|
ULIB_MUTEX_LOCK(m_WaitMutex, -1) {
|
||||||
|
m_Condition.notify_one();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Connect an Object signal to trigger async execution.
|
||||||
|
* Usage: task.ConnectTrigger(sender, &SenderClass::SomeSignal);
|
||||||
|
*/
|
||||||
|
template <typename Func1>
|
||||||
|
Connection ConnectTrigger(typename FunctionPointer<Func1>::Object* sender, Func1 sigf) {
|
||||||
|
return Object::connect(sender, sigf, [this]() { Notify(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signals //////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
signals:
|
||||||
|
virtual void Stopped() { ULIB_SIGNAL_EMIT(AlgorithmTask::Stopped); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/** @brief Thread entry point — dispatches to cyclic or async loop. */
|
||||||
|
void Run() override {
|
||||||
|
if (m_Mode == Cyclic)
|
||||||
|
RunCyclic();
|
||||||
|
else
|
||||||
|
RunAsync();
|
||||||
|
Stopped();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void RunCyclic() {
|
||||||
|
while (!m_StopRequested.load()) {
|
||||||
|
if (m_Algorithm) m_Algorithm->Process(m_Input);
|
||||||
|
std::unique_lock<std::timed_mutex> lock(m_WaitMutex.GetNative());
|
||||||
|
m_Condition.wait_for(lock,
|
||||||
|
std::chrono::milliseconds(m_CycleTime_ms),
|
||||||
|
[this]() { return m_StopRequested.load(); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RunAsync() {
|
||||||
|
while (!m_StopRequested.load()) {
|
||||||
|
std::unique_lock<std::timed_mutex> lock(m_WaitMutex.GetNative());
|
||||||
|
m_Condition.wait(lock, [this]() {
|
||||||
|
return m_StopRequested.load() || m_Triggered.load();
|
||||||
|
});
|
||||||
|
if (m_StopRequested.load()) break;
|
||||||
|
m_Triggered.store(false);
|
||||||
|
if (m_Algorithm) m_Algorithm->Process(m_Input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AlgorithmType* m_Algorithm;
|
||||||
|
Mode m_Mode;
|
||||||
|
int m_CycleTime_ms;
|
||||||
|
T_enc m_Input;
|
||||||
|
|
||||||
|
std::atomic<bool> m_StopRequested;
|
||||||
|
std::atomic<bool> m_Triggered;
|
||||||
|
Mutex m_WaitMutex;
|
||||||
|
std::condition_variable_any m_Condition;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace uLib
|
||||||
|
|
||||||
|
#endif // U_CORE_ALGORITHM_H
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
|
|
||||||
set(HEADERS
|
set(HEADERS
|
||||||
|
Algorithm.h
|
||||||
Archives.h
|
Archives.h
|
||||||
Array.h
|
Array.h
|
||||||
Collection.h
|
Collection.h
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ public:
|
|||||||
else
|
else
|
||||||
m_RamData = static_cast<T *>(::operator new(m_Size * sizeof(T)));
|
m_RamData = static_cast<T *>(::operator new(m_Size * sizeof(T)));
|
||||||
}
|
}
|
||||||
|
// std::cout << "DataAllocator Constructor: ptr=" << m_RamData << " size=" << m_Size << " own=" << m_OwnsObjects << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
DataAllocator(const DataAllocator<T> &other)
|
DataAllocator(const DataAllocator<T> &other)
|
||||||
@@ -63,8 +64,13 @@ public:
|
|||||||
m_RamData = new T[m_Size];
|
m_RamData = new T[m_Size];
|
||||||
else
|
else
|
||||||
m_RamData = static_cast<T *>(::operator new(m_Size * sizeof(T)));
|
m_RamData = static_cast<T *>(::operator new(m_Size * sizeof(T)));
|
||||||
|
|
||||||
|
if (m_OwnsObjects) {
|
||||||
|
std::copy(other.m_RamData, other.m_RamData + m_Size, m_RamData);
|
||||||
|
} else {
|
||||||
std::memcpy(m_RamData, other.m_RamData, m_Size * sizeof(T));
|
std::memcpy(m_RamData, other.m_RamData, m_Size * sizeof(T));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
#ifdef USE_CUDA
|
#ifdef USE_CUDA
|
||||||
if (other.m_VramData) {
|
if (other.m_VramData) {
|
||||||
cudaMalloc((void **)&m_VramData, m_Size * sizeof(T));
|
cudaMalloc((void **)&m_VramData, m_Size * sizeof(T));
|
||||||
@@ -73,14 +79,17 @@ public:
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
// std::cout << "DataAllocator CopyConstructor: from=" << other.m_RamData << " to=" << m_RamData << " size=" << m_Size << " own=" << m_OwnsObjects << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
~DataAllocator() {
|
~DataAllocator() {
|
||||||
|
// std::cout << "DataAllocator Destructor: ptr=" << m_RamData << " size=" << m_Size << " own=" << m_OwnsObjects << std::endl;
|
||||||
if (m_RamData) {
|
if (m_RamData) {
|
||||||
if (m_OwnsObjects)
|
if (m_OwnsObjects)
|
||||||
delete[] m_RamData;
|
delete[] m_RamData;
|
||||||
else
|
else
|
||||||
::operator delete(m_RamData);
|
::operator delete(m_RamData);
|
||||||
|
m_RamData = nullptr;
|
||||||
}
|
}
|
||||||
#ifdef USE_CUDA
|
#ifdef USE_CUDA
|
||||||
if (m_VramData) {
|
if (m_VramData) {
|
||||||
@@ -91,6 +100,13 @@ public:
|
|||||||
|
|
||||||
DataAllocator &operator=(const DataAllocator &other) {
|
DataAllocator &operator=(const DataAllocator &other) {
|
||||||
if (this != &other) {
|
if (this != &other) {
|
||||||
|
if (m_Size == other.m_Size && m_OwnsObjects != other.m_OwnsObjects) {
|
||||||
|
// Ownership changed but size is same: we must force reallocation
|
||||||
|
// to avoid using the wrong delete operator later.
|
||||||
|
size_t oldSize = m_Size;
|
||||||
|
m_Size = 0;
|
||||||
|
resize(oldSize); // This will free the old buffer with the OLD ownership
|
||||||
|
}
|
||||||
m_OwnsObjects = other.m_OwnsObjects;
|
m_OwnsObjects = other.m_OwnsObjects;
|
||||||
resize(other.m_Size);
|
resize(other.m_Size);
|
||||||
m_Device = other.m_Device;
|
m_Device = other.m_Device;
|
||||||
@@ -101,8 +117,12 @@ public:
|
|||||||
else
|
else
|
||||||
m_RamData = static_cast<T *>(::operator new(m_Size * sizeof(T)));
|
m_RamData = static_cast<T *>(::operator new(m_Size * sizeof(T)));
|
||||||
}
|
}
|
||||||
|
if (m_OwnsObjects) {
|
||||||
|
std::copy(other.m_RamData, other.m_RamData + m_Size, m_RamData);
|
||||||
|
} else {
|
||||||
std::memcpy(m_RamData, other.m_RamData, m_Size * sizeof(T));
|
std::memcpy(m_RamData, other.m_RamData, m_Size * sizeof(T));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
#ifdef USE_CUDA
|
#ifdef USE_CUDA
|
||||||
if (other.m_VramData) {
|
if (other.m_VramData) {
|
||||||
if (!m_VramData)
|
if (!m_VramData)
|
||||||
@@ -112,6 +132,7 @@ public:
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
// std::cout << "DataAllocator AssigmentOp: otherPtr=" << other.m_RamData << " thisPtr=" << m_RamData << " size=" << m_Size << " own=" << m_OwnsObjects << std::endl;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,6 +173,8 @@ public:
|
|||||||
if (m_Size == size)
|
if (m_Size == size)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// std::cout << "DataAllocator Resize: from=" << m_Size << " to=" << size << " ptr=" << m_RamData << " own=" << m_OwnsObjects << std::endl;
|
||||||
|
|
||||||
T *newRam = nullptr;
|
T *newRam = nullptr;
|
||||||
T *newVram = nullptr;
|
T *newVram = nullptr;
|
||||||
|
|
||||||
@@ -162,8 +185,12 @@ public:
|
|||||||
newRam = static_cast<T *>(::operator new(size * sizeof(T)));
|
newRam = static_cast<T *>(::operator new(size * sizeof(T)));
|
||||||
|
|
||||||
if (m_RamData) {
|
if (m_RamData) {
|
||||||
|
if (m_OwnsObjects) {
|
||||||
|
std::copy(m_RamData, m_RamData + std::min(m_Size, size), newRam);
|
||||||
|
} else {
|
||||||
std::memcpy(newRam, m_RamData, std::min(m_Size, size) * sizeof(T));
|
std::memcpy(newRam, m_RamData, std::min(m_Size, size) * sizeof(T));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef USE_CUDA
|
#ifdef USE_CUDA
|
||||||
cudaMalloc((void **)&newVram, size * sizeof(T));
|
cudaMalloc((void **)&newVram, size * sizeof(T));
|
||||||
|
|||||||
@@ -61,8 +61,8 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
std::string m_InstanceName;
|
std::string m_InstanceName;
|
||||||
Vector<Signal> sigv;
|
std::vector<Signal> sigv;
|
||||||
Vector<Slot> slov;
|
std::vector<Slot> slov;
|
||||||
std::vector<PropertyBase*> m_Properties;
|
std::vector<PropertyBase*> m_Properties;
|
||||||
std::vector<PropertyBase*> m_DynamicProperties;
|
std::vector<PropertyBase*> m_DynamicProperties;
|
||||||
bool m_SignalsBlocked;
|
bool m_SignalsBlocked;
|
||||||
@@ -70,14 +70,17 @@ public:
|
|||||||
|
|
||||||
// Implementations of Property methods
|
// Implementations of Property methods
|
||||||
void Object::RegisterProperty(PropertyBase* prop) {
|
void Object::RegisterProperty(PropertyBase* prop) {
|
||||||
if (prop) d->m_Properties.push_back(prop);
|
if (prop) {
|
||||||
|
d->m_Properties.push_back(prop);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Object::RegisterDynamicProperty(PropertyBase* prop) {
|
void Object::RegisterDynamicProperty(PropertyBase* prop) {
|
||||||
if (prop) {
|
if (prop) {
|
||||||
|
for (auto* existing : d->m_DynamicProperties) {
|
||||||
|
if (existing == prop) return;
|
||||||
|
}
|
||||||
d->m_DynamicProperties.push_back(prop);
|
d->m_DynamicProperties.push_back(prop);
|
||||||
// Note: prop already added itself to m_Properties
|
|
||||||
// during its own constructor call to owner->RegisterProperty()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,6 +88,16 @@ const std::vector<PropertyBase*>& Object::GetProperties() const {
|
|||||||
return d->m_Properties;
|
return d->m_Properties;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PropertyBase* Object::GetProperty(const std::string& name) const {
|
||||||
|
for (auto* p : d->m_Properties) {
|
||||||
|
if (p->GetName() == name || p->GetQualifiedName() == name) return p;
|
||||||
|
}
|
||||||
|
for (auto* p : d->m_DynamicProperties) {
|
||||||
|
if (p->GetName() == name || p->GetQualifiedName() == name) return p;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
// In Object.h, the template serialize needs to be updated to call property serialization.
|
// In Object.h, the template serialize needs to be updated to call property serialization.
|
||||||
// However, since Object::serialize is a template in the header, we might need a helper here.
|
// However, since Object::serialize is a template in the header, we might need a helper here.
|
||||||
|
|
||||||
@@ -125,6 +138,18 @@ Object::Object(const Object ©) : d(new ObjectPrivate) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Object& Object::operator=(const Object &other) {
|
||||||
|
// Intentionally does NOT share 'd'. Each Object owns its own ObjectPrivate.
|
||||||
|
// Without this, the compiler-generated operator= would copy the 'd' pointer,
|
||||||
|
// causing two objects to share the same ObjectPrivate. When both are
|
||||||
|
// destroyed, 'd' would be deleted twice, corrupting the heap.
|
||||||
|
if (this != &other && other.d) {
|
||||||
|
d->m_InstanceName = other.d->m_InstanceName;
|
||||||
|
d->m_SignalsBlocked = other.d->m_SignalsBlocked;
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
Object::~Object() {
|
Object::~Object() {
|
||||||
for (auto* p : d->m_DynamicProperties) {
|
for (auto* p : d->m_DynamicProperties) {
|
||||||
delete p;
|
delete p;
|
||||||
@@ -135,6 +160,7 @@ Object::~Object() {
|
|||||||
void Object::DeepCopy(const Object ©) {
|
void Object::DeepCopy(const Object ©) {
|
||||||
if (this == ©) return;
|
if (this == ©) return;
|
||||||
if (copy.d) d->m_InstanceName = copy.d->m_InstanceName;
|
if (copy.d) d->m_InstanceName = copy.d->m_InstanceName;
|
||||||
|
std::cout << "Object DeepCopy: from d=" << copy.d << " to d=" << d << std::endl;
|
||||||
// Note: signals, slots and properties are intentionally not copied
|
// Note: signals, slots and properties are intentionally not copied
|
||||||
// to maintain instance uniquely and avoid duplicate registrations.
|
// to maintain instance uniquely and avoid duplicate registrations.
|
||||||
this->Updated();
|
this->Updated();
|
||||||
@@ -161,9 +187,8 @@ void Object::LoadConfig(std::istream &is, int version) {
|
|||||||
|
|
||||||
void Object::PrintSelf(std::ostream &o) const {
|
void Object::PrintSelf(std::ostream &o) const {
|
||||||
o << "OBJECT signals: ------------------\n";
|
o << "OBJECT signals: ------------------\n";
|
||||||
Vector<ObjectPrivate::Signal>::Iterator itr;
|
for (const auto& sig : d->sigv) {
|
||||||
for (itr = d->sigv.begin(); itr < d->sigv.end(); itr++) {
|
o << " signal:[ " << sig.sigstr << " ]\n";
|
||||||
o << " signal:[ " << itr->sigstr << " ]\n";
|
|
||||||
}
|
}
|
||||||
o << "--------------------------------------\n\n";
|
o << "--------------------------------------\n\n";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ public:
|
|||||||
|
|
||||||
Object();
|
Object();
|
||||||
Object(const Object ©);
|
Object(const Object ©);
|
||||||
~Object();
|
virtual ~Object();
|
||||||
|
|
||||||
virtual const char * GetClassName() const { return "Object"; }
|
virtual const char * GetClassName() const { return "Object"; }
|
||||||
|
|
||||||
@@ -93,6 +93,7 @@ public:
|
|||||||
void RegisterProperty(PropertyBase* prop);
|
void RegisterProperty(PropertyBase* prop);
|
||||||
void RegisterDynamicProperty(PropertyBase* prop);
|
void RegisterDynamicProperty(PropertyBase* prop);
|
||||||
const std::vector<PropertyBase*>& GetProperties() const;
|
const std::vector<PropertyBase*>& GetProperties() const;
|
||||||
|
PropertyBase* GetProperty(const std::string& name) const;
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
// PARAMETERS //
|
// PARAMETERS //
|
||||||
@@ -227,10 +228,7 @@ public:
|
|||||||
|
|
||||||
void PrintSelf(std::ostream &o) const;
|
void PrintSelf(std::ostream &o) const;
|
||||||
|
|
||||||
inline const Object &operator=(const Object ©) {
|
Object &operator=(const Object &other);
|
||||||
this->DeepCopy(copy);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool addSignalImpl(SignalBase *sig, GenericMFPtr fptr, const char *name);
|
bool addSignalImpl(SignalBase *sig, GenericMFPtr fptr, const char *name);
|
||||||
|
|||||||
@@ -20,13 +20,13 @@ public:
|
|||||||
* @brief Adds an object to the context.
|
* @brief Adds an object to the context.
|
||||||
* @param obj Pointer to the object to add.
|
* @param obj Pointer to the object to add.
|
||||||
*/
|
*/
|
||||||
void AddObject(Object* obj);
|
virtual void AddObject(Object* obj);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Removes an object from the context.
|
* @brief Removes an object from the context.
|
||||||
* @param obj Pointer to the object to remove.
|
* @param obj Pointer to the object to remove.
|
||||||
*/
|
*/
|
||||||
void RemoveObject(Object* obj);
|
virtual void RemoveObject(Object* obj);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Clears all objects from the context.
|
* @brief Clears all objects from the context.
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -23,6 +28,18 @@ public:
|
|||||||
virtual const char* GetTypeName() const = 0;
|
virtual const char* GetTypeName() const = 0;
|
||||||
virtual std::string GetValueAsString() const = 0;
|
virtual std::string GetValueAsString() const = 0;
|
||||||
virtual std::type_index GetTypeIndex() const = 0; // Added
|
virtual std::type_index GetTypeIndex() const = 0; // Added
|
||||||
|
virtual const std::string& GetUnits() const = 0;
|
||||||
|
virtual void SetUnits(const std::string& units) = 0;
|
||||||
|
virtual const std::vector<std::string>& GetEnumLabels() const {
|
||||||
|
static std::vector<std::string> empty;
|
||||||
|
return empty;
|
||||||
|
}
|
||||||
|
virtual const std::string& GetGroup() const = 0;
|
||||||
|
virtual void SetGroup(const std::string& group) = 0;
|
||||||
|
std::string GetQualifiedName() const {
|
||||||
|
if (GetGroup().empty()) return GetName();
|
||||||
|
return GetGroup() + "." + GetName();
|
||||||
|
}
|
||||||
|
|
||||||
// Signal support
|
// Signal support
|
||||||
signals:
|
signals:
|
||||||
@@ -45,16 +62,16 @@ 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)
|
Property(Object* owner, const std::string& name, T* valuePtr, const std::string& units = "", const std::string& group = "")
|
||||||
: m_owner(owner), m_name(name), m_value(valuePtr), m_own(false) {
|
: m_owner(owner), m_name(name), m_units(units), m_group(group), m_value(valuePtr), m_own(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())
|
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_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) {
|
||||||
if (m_owner) {
|
if (m_owner) {
|
||||||
m_owner->RegisterProperty(this);
|
m_owner->RegisterProperty(this);
|
||||||
}
|
}
|
||||||
@@ -68,6 +85,10 @@ public:
|
|||||||
virtual const std::string& GetName() const override { return m_name; }
|
virtual const std::string& GetName() const override { return m_name; }
|
||||||
virtual const char* GetTypeName() const override { return typeid(T).name(); }
|
virtual const char* GetTypeName() const override { return typeid(T).name(); }
|
||||||
virtual std::type_index GetTypeIndex() const override { return std::type_index(typeid(T)); }
|
virtual std::type_index GetTypeIndex() const override { return std::type_index(typeid(T)); }
|
||||||
|
virtual const std::string& GetUnits() const override { return m_units; }
|
||||||
|
virtual void SetUnits(const std::string& units) override { m_units = units; }
|
||||||
|
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 {
|
||||||
@@ -118,6 +139,8 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
std::string m_name;
|
std::string m_name;
|
||||||
|
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;
|
||||||
@@ -135,6 +158,22 @@ typedef Property<float> FloatProperty;
|
|||||||
typedef Property<double> DoubleProperty;
|
typedef Property<double> DoubleProperty;
|
||||||
typedef Property<Bool_t> BoolProperty;
|
typedef Property<Bool_t> BoolProperty;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Property specialized for enumerations, providing labels for GUI representations.
|
||||||
|
*/
|
||||||
|
class EnumProperty : public Property<int> {
|
||||||
|
public:
|
||||||
|
EnumProperty(Object* owner, const std::string& name, int* valuePtr, const std::vector<std::string>& labels, const std::string& units = "", const std::string& group = "")
|
||||||
|
: Property<int>(owner, name, valuePtr, units, group), m_Labels(labels) {}
|
||||||
|
|
||||||
|
const std::vector<std::string>& GetEnumLabels() const override { return m_Labels; }
|
||||||
|
const char* GetTypeName() const override { return "Enum"; }
|
||||||
|
virtual std::type_index GetTypeIndex() const override { return std::type_index(typeid(EnumProperty)); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<std::string> m_Labels;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Macro to simplify property declaration within a class.
|
* @brief Macro to simplify property declaration within a class.
|
||||||
* Usage: ULIB_PROPERTY(float, Width, 1.0f)
|
* Usage: ULIB_PROPERTY(float, Width, 1.0f)
|
||||||
@@ -184,25 +223,53 @@ 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) {
|
||||||
// We use const_cast because we are just creating a proxy to the member
|
Property<T>* p = new Property<T>(m_Object, t.name(), &const_cast<boost::serialization::hrp<T>&>(t).value(), t.units() ? t.units() : "", GetCurrentGroup());
|
||||||
m_Object->RegisterDynamicProperty(
|
m_Object->RegisterDynamicProperty(p);
|
||||||
new Property<T>(m_Object, t.name(), &const_cast<boost::serialization::hrp<T>&>(t).value())
|
}
|
||||||
);
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
void save_override(const boost::serialization::hrp_enum<T> &t) {
|
||||||
|
if (m_Object) {
|
||||||
|
EnumProperty* p = new EnumProperty(m_Object, t.name(), (int*)&const_cast<boost::serialization::hrp_enum<T>&>(t).value(), t.labels(), t.units() ? t.units() : "", GetCurrentGroup());
|
||||||
|
m_Object->RegisterDynamicProperty(p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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) {}
|
||||||
@@ -213,6 +280,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;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -75,12 +75,14 @@ template <class T> struct access2 {};
|
|||||||
template <class T>
|
template <class T>
|
||||||
class hrp : public boost::serialization::wrapper_traits<hrp<T>> {
|
class hrp : public boost::serialization::wrapper_traits<hrp<T>> {
|
||||||
const char *m_name;
|
const char *m_name;
|
||||||
|
const char *m_units;
|
||||||
T &m_value;
|
T &m_value;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit hrp(const char *name_, T &t) : m_name(name_), m_value(t) {}
|
explicit hrp(const char *name_, T &t, const char* units_ = nullptr) : m_name(name_), m_units(units_), m_value(t) {}
|
||||||
|
|
||||||
const char *name() const { return this->m_name; }
|
const char *name() const { return this->m_name; }
|
||||||
|
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; }
|
||||||
|
|
||||||
@@ -98,11 +100,46 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
inline hrp<T> make_hrp(const char *name, T &t) {
|
inline hrp<T> make_hrp(const char *name, T &t, const char* units = nullptr) {
|
||||||
return hrp<T>(name, t);
|
return hrp<T>(name, t, units);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
class hrp_enum : public boost::serialization::wrapper_traits<hrp_enum<T>> {
|
||||||
|
const char *m_name;
|
||||||
|
const char *m_units;
|
||||||
|
T &m_value;
|
||||||
|
std::vector<std::string> m_labels;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit hrp_enum(const char *name_, T &t, const std::vector<std::string>& labels, const char* units_ = nullptr)
|
||||||
|
: m_name(name_), m_units(units_), m_value(t), m_labels(labels) {}
|
||||||
|
|
||||||
|
const char *name() const { return this->m_name; }
|
||||||
|
const char *units() const { return this->m_units; }
|
||||||
|
T &value() { return this->m_value; }
|
||||||
|
const std::vector<std::string>& labels() const { return m_labels; }
|
||||||
|
|
||||||
|
BOOST_SERIALIZATION_SPLIT_MEMBER()
|
||||||
|
|
||||||
|
template <class Archivex>
|
||||||
|
void save(Archivex &ar, const unsigned int /* version */) const {
|
||||||
|
ar << boost::serialization::make_nvp(m_name, m_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class Archivex>
|
||||||
|
void load(Archivex &ar, const unsigned int /* version */) {
|
||||||
|
ar >> boost::serialization::make_nvp(m_name, m_value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
inline hrp_enum<T> make_hrp_enum(const char *name, T &t, const std::vector<std::string>& labels, const char* units = nullptr) {
|
||||||
|
return hrp_enum<T>(name, t, labels, units);
|
||||||
}
|
}
|
||||||
|
|
||||||
#define HRP(name) boost::serialization::make_hrp(BOOST_PP_STRINGIZE(name), name)
|
#define HRP(name) boost::serialization::make_hrp(BOOST_PP_STRINGIZE(name), name)
|
||||||
|
#define HRPU(name, units) boost::serialization::make_hrp(BOOST_PP_STRINGIZE(name), name, units)
|
||||||
|
|
||||||
} // namespace serialization
|
} // namespace serialization
|
||||||
} // namespace boost
|
} // namespace boost
|
||||||
|
|||||||
206
src/Core/testing/AlgorithmTest.cpp
Normal file
206
src/Core/testing/AlgorithmTest.cpp
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
#include "Core/Algorithm.h"
|
||||||
|
#include <iostream>
|
||||||
|
#include <atomic>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
using namespace uLib;
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Test algorithms
|
||||||
|
|
||||||
|
class DoubleAlgorithm : public Algorithm<int, int> {
|
||||||
|
public:
|
||||||
|
const char* GetClassName() const override { return "DoubleAlgorithm"; }
|
||||||
|
int Process(const int& input) override {
|
||||||
|
m_CallCount++;
|
||||||
|
return input * 2;
|
||||||
|
}
|
||||||
|
std::atomic<int> m_CallCount{0};
|
||||||
|
};
|
||||||
|
|
||||||
|
class StringifyAlgorithm : public Algorithm<int, std::string> {
|
||||||
|
public:
|
||||||
|
const char* GetClassName() const override { return "StringifyAlgorithm"; }
|
||||||
|
std::string Process(const int& input) override {
|
||||||
|
return std::to_string(input);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Signal source to test ConnectTrigger
|
||||||
|
class TriggerSource : public Object {
|
||||||
|
public:
|
||||||
|
const char* GetClassName() const override { return "TriggerSource"; }
|
||||||
|
signals:
|
||||||
|
virtual void DataReady() { ULIB_SIGNAL_EMIT(TriggerSource::DataReady); }
|
||||||
|
};
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Tests
|
||||||
|
|
||||||
|
void TestBasicProcess() {
|
||||||
|
std::cout << "Testing basic Algorithm::Process..." << std::endl;
|
||||||
|
DoubleAlgorithm alg;
|
||||||
|
assert(alg.Process(5) == 10);
|
||||||
|
assert(alg.Process(-3) == -6);
|
||||||
|
assert(alg.Process(0) == 0);
|
||||||
|
std::cout << " Passed." << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestOperatorCall() {
|
||||||
|
std::cout << "Testing Algorithm::operator()..." << std::endl;
|
||||||
|
DoubleAlgorithm alg;
|
||||||
|
assert(alg(7) == 14);
|
||||||
|
assert(alg(0) == 0);
|
||||||
|
std::cout << " Passed." << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestEncoderDecoderChain() {
|
||||||
|
std::cout << "Testing encoder/decoder chain pointers..." << std::endl;
|
||||||
|
DoubleAlgorithm a, b;
|
||||||
|
a.SetDecoder(&b);
|
||||||
|
b.SetEncoder(&a);
|
||||||
|
assert(a.GetDecoder() == &b);
|
||||||
|
assert(b.GetEncoder() == &a);
|
||||||
|
assert(a.GetEncoder() == nullptr);
|
||||||
|
assert(b.GetDecoder() == nullptr);
|
||||||
|
std::cout << " Passed." << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestAlgorithmSignals() {
|
||||||
|
std::cout << "Testing Algorithm signals..." << std::endl;
|
||||||
|
DoubleAlgorithm alg;
|
||||||
|
bool started = false;
|
||||||
|
bool finished = false;
|
||||||
|
Object::connect(&alg, &DoubleAlgorithm::Started, [&]() { started = true; });
|
||||||
|
Object::connect(&alg, &DoubleAlgorithm::Finished, [&]() { finished = true; });
|
||||||
|
alg.Started();
|
||||||
|
alg.Finished();
|
||||||
|
assert(started);
|
||||||
|
assert(finished);
|
||||||
|
std::cout << " Passed." << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestCyclicTask() {
|
||||||
|
std::cout << "Testing AlgorithmTask cyclic mode (Thread-based)..." << std::endl;
|
||||||
|
DoubleAlgorithm alg;
|
||||||
|
AlgorithmTask<int, int> task;
|
||||||
|
task.SetAlgorithm(&alg);
|
||||||
|
task.SetMode(AlgorithmTask<int, int>::Cyclic);
|
||||||
|
task.SetCycleTime(50);
|
||||||
|
|
||||||
|
assert(!task.IsRunning());
|
||||||
|
task.Run(5);
|
||||||
|
|
||||||
|
// Let it run for ~200ms -> expect ~4 cycles
|
||||||
|
Thread::Sleep(220);
|
||||||
|
task.Stop();
|
||||||
|
|
||||||
|
assert(!task.IsRunning());
|
||||||
|
int count = alg.m_CallCount.load();
|
||||||
|
std::cout << " Cyclic iterations: " << count << std::endl;
|
||||||
|
assert(count >= 3 && count <= 6);
|
||||||
|
std::cout << " Passed." << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestAsyncTask() {
|
||||||
|
std::cout << "Testing AlgorithmTask async mode (Mutex + condition_variable)..." << std::endl;
|
||||||
|
DoubleAlgorithm alg;
|
||||||
|
AlgorithmTask<int, int> task;
|
||||||
|
task.SetAlgorithm(&alg);
|
||||||
|
task.SetMode(AlgorithmTask<int, int>::Async);
|
||||||
|
|
||||||
|
task.Run(42);
|
||||||
|
Thread::Sleep(50); // let the thread start and wait
|
||||||
|
|
||||||
|
// Trigger 3 notifications
|
||||||
|
for (int i = 0; i < 3; ++i) {
|
||||||
|
task.Notify();
|
||||||
|
Thread::Sleep(30);
|
||||||
|
}
|
||||||
|
|
||||||
|
task.Stop();
|
||||||
|
int count = alg.m_CallCount.load();
|
||||||
|
std::cout << " Async invocations: " << count << std::endl;
|
||||||
|
assert(count == 3);
|
||||||
|
std::cout << " Passed." << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestConnectTrigger() {
|
||||||
|
std::cout << "Testing AlgorithmTask::ConnectTrigger (signal-slot async)..." << std::endl;
|
||||||
|
DoubleAlgorithm alg;
|
||||||
|
AlgorithmTask<int, int> task;
|
||||||
|
task.SetAlgorithm(&alg);
|
||||||
|
task.SetMode(AlgorithmTask<int, int>::Async);
|
||||||
|
|
||||||
|
TriggerSource source;
|
||||||
|
task.ConnectTrigger(&source, &TriggerSource::DataReady);
|
||||||
|
|
||||||
|
task.Run(10);
|
||||||
|
Thread::Sleep(50);
|
||||||
|
|
||||||
|
// Emit signal 3 times
|
||||||
|
for (int i = 0; i < 3; ++i) {
|
||||||
|
source.DataReady();
|
||||||
|
Thread::Sleep(30);
|
||||||
|
}
|
||||||
|
|
||||||
|
task.Stop();
|
||||||
|
int count = alg.m_CallCount.load();
|
||||||
|
std::cout << " Signal-triggered invocations: " << count << std::endl;
|
||||||
|
assert(count == 3);
|
||||||
|
std::cout << " Passed." << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestTaskStoppedSignal() {
|
||||||
|
std::cout << "Testing AlgorithmTask Stopped signal..." << std::endl;
|
||||||
|
DoubleAlgorithm alg;
|
||||||
|
AlgorithmTask<int, int> task;
|
||||||
|
task.SetAlgorithm(&alg);
|
||||||
|
task.SetMode(AlgorithmTask<int, int>::Cyclic);
|
||||||
|
task.SetCycleTime(20);
|
||||||
|
|
||||||
|
std::atomic<bool> stopped{false};
|
||||||
|
Object::connect(&task, &AlgorithmTask<int, int>::Stopped,
|
||||||
|
[&]() { stopped.store(true); });
|
||||||
|
|
||||||
|
task.Run(1);
|
||||||
|
Thread::Sleep(50);
|
||||||
|
task.Stop();
|
||||||
|
Thread::Sleep(50);
|
||||||
|
|
||||||
|
assert(stopped.load());
|
||||||
|
std::cout << " Passed." << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestClassName() {
|
||||||
|
std::cout << "Testing GetClassName..." << std::endl;
|
||||||
|
DoubleAlgorithm alg;
|
||||||
|
AlgorithmTask<int, int> task;
|
||||||
|
assert(std::string(alg.GetClassName()) == "DoubleAlgorithm");
|
||||||
|
assert(std::string(task.GetClassName()) == "AlgorithmTask");
|
||||||
|
std::cout << " Passed." << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestDifferentTypes() {
|
||||||
|
std::cout << "Testing Algorithm with different enc/dec types..." << std::endl;
|
||||||
|
StringifyAlgorithm alg;
|
||||||
|
assert(alg.Process(42) == "42");
|
||||||
|
assert(alg.Process(-1) == "-1");
|
||||||
|
assert(alg(100) == "100");
|
||||||
|
std::cout << " Passed." << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
TestBasicProcess();
|
||||||
|
TestOperatorCall();
|
||||||
|
TestEncoderDecoderChain();
|
||||||
|
TestAlgorithmSignals();
|
||||||
|
TestDifferentTypes();
|
||||||
|
TestCyclicTask();
|
||||||
|
TestAsyncTask();
|
||||||
|
TestConnectTrigger();
|
||||||
|
TestTaskStoppedSignal();
|
||||||
|
TestClassName();
|
||||||
|
std::cout << "All Algorithm tests passed!" << std::endl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -23,11 +23,13 @@ set( TESTS
|
|||||||
VectorMetaAllocatorTest
|
VectorMetaAllocatorTest
|
||||||
PropertyTypesTest
|
PropertyTypesTest
|
||||||
HRPTest
|
HRPTest
|
||||||
|
PropertyGroupingTest
|
||||||
MutexTest
|
MutexTest
|
||||||
ThreadsTest
|
ThreadsTest
|
||||||
OpenMPTest
|
OpenMPTest
|
||||||
TeamTest
|
TeamTest
|
||||||
AffinityTest
|
AffinityTest
|
||||||
|
AlgorithmTest
|
||||||
)
|
)
|
||||||
|
|
||||||
set(LIBRARIES
|
set(LIBRARIES
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "Core/Monitor.h"
|
#include "Core/Monitor.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <chrono>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
|||||||
78
src/Core/testing/PropertyGroupingTest.cpp
Normal file
78
src/Core/testing/PropertyGroupingTest.cpp
Normal 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;
|
||||||
|
}
|
||||||
@@ -13,6 +13,15 @@ ActionInitialization::ActionInitialization(EmitterPrimary *emitter, SimulationCo
|
|||||||
|
|
||||||
ActionInitialization::~ActionInitialization() {}
|
ActionInitialization::~ActionInitialization() {}
|
||||||
|
|
||||||
|
// Lightweight wrapper to avoid double-free in Geant4 EventManager
|
||||||
|
class SteppingActionWrapper : public G4UserSteppingAction {
|
||||||
|
public:
|
||||||
|
SteppingActionWrapper(SteppingAction *real) : m_Real(real) {}
|
||||||
|
virtual void UserSteppingAction(const G4Step* step) override { m_Real->UserSteppingAction(step); }
|
||||||
|
private:
|
||||||
|
SteppingAction *m_Real;
|
||||||
|
};
|
||||||
|
|
||||||
void ActionInitialization::BuildForMaster() const {}
|
void ActionInitialization::BuildForMaster() const {}
|
||||||
|
|
||||||
void ActionInitialization::Build() const {
|
void ActionInitialization::Build() const {
|
||||||
@@ -23,8 +32,10 @@ void ActionInitialization::Build() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SteppingAction *sa = new SteppingAction(m_Context);
|
SteppingAction *sa = new SteppingAction(m_Context);
|
||||||
SetUserAction(static_cast<G4UserSteppingAction*>(sa));
|
// EventManager will delete sa via this slot
|
||||||
SetUserAction(static_cast<G4UserEventAction*>(sa));
|
SetUserAction(static_cast<G4UserEventAction*>(sa));
|
||||||
|
// EventManager will delete the wrapper, leaving sa alive to be deleted once.
|
||||||
|
SetUserAction(new SteppingActionWrapper(sa));
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Geant
|
} // namespace Geant
|
||||||
|
|||||||
@@ -18,6 +18,14 @@ DetectorActionInitialization::DetectorActionInitialization(EmitterPrimary *emitt
|
|||||||
|
|
||||||
DetectorActionInitialization::~DetectorActionInitialization() {}
|
DetectorActionInitialization::~DetectorActionInitialization() {}
|
||||||
|
|
||||||
|
class DetectorSteppingActionWrapper : public G4UserSteppingAction {
|
||||||
|
public:
|
||||||
|
DetectorSteppingActionWrapper(DetectorSteppingAction *real) : m_Real(real) {}
|
||||||
|
virtual void UserSteppingAction(const G4Step* step) override { m_Real->UserSteppingAction(step); }
|
||||||
|
private:
|
||||||
|
DetectorSteppingAction *m_Real;
|
||||||
|
};
|
||||||
|
|
||||||
void DetectorActionInitialization::BuildForMaster() const {}
|
void DetectorActionInitialization::BuildForMaster() const {}
|
||||||
|
|
||||||
void DetectorActionInitialization::Build() const {
|
void DetectorActionInitialization::Build() const {
|
||||||
@@ -34,8 +42,10 @@ void DetectorActionInitialization::Build() const {
|
|||||||
if (m_Output) {
|
if (m_Output) {
|
||||||
DetectorSteppingAction *sa = new DetectorSteppingAction(m_Output, m_Planes);
|
DetectorSteppingAction *sa = new DetectorSteppingAction(m_Output, m_Planes);
|
||||||
sa->SetVerbosity(m_Verbosity);
|
sa->SetVerbosity(m_Verbosity);
|
||||||
SetUserAction(static_cast<G4UserSteppingAction*>(sa));
|
// EventManager will delete sa via the Event slot
|
||||||
SetUserAction(static_cast<G4UserEventAction*>(sa));
|
SetUserAction(static_cast<G4UserEventAction*>(sa));
|
||||||
|
// EventManager will delete the wrapper, leaving sa alive for the other deletion.
|
||||||
|
SetUserAction(new DetectorSteppingActionWrapper(sa));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ static void CheckGeant4Environment() {
|
|||||||
|
|
||||||
class SceneImpl {
|
class SceneImpl {
|
||||||
public:
|
public:
|
||||||
SceneImpl() : m_RunManager(G4RunManagerFactory::CreateRunManager(G4RunManagerType::Default)),
|
SceneImpl() : m_RunManager(G4RunManagerFactory::CreateRunManager(G4RunManagerType::Serial)),
|
||||||
m_Emitter(nullptr),
|
m_Emitter(nullptr),
|
||||||
m_InitCalled(false) {
|
m_InitCalled(false) {
|
||||||
m_RunManager->SetUserInitialization(new PhysicsList);
|
m_RunManager->SetUserInitialization(new PhysicsList);
|
||||||
@@ -102,6 +102,7 @@ void Scene::AddSolid(Solid *solid, Solid *parent) {
|
|||||||
|
|
||||||
const Solid* Scene::GetWorld() const { return d->m_World; }
|
const Solid* Scene::GetWorld() const { return d->m_World; }
|
||||||
ContainerBox* Scene::GetWorldBox() const { return &d->m_WorldBox; }
|
ContainerBox* Scene::GetWorldBox() const { return &d->m_WorldBox; }
|
||||||
|
const Vector<Solid*>& Scene::GetSolids() const { return d->m_Solids; }
|
||||||
|
|
||||||
void Scene::ConstructWorldBox(const Vector3f &size, const char *material) {
|
void Scene::ConstructWorldBox(const Vector3f &size, const char *material) {
|
||||||
d->m_WorldBox.Scale(size);
|
d->m_WorldBox.Scale(size);
|
||||||
|
|||||||
@@ -58,6 +58,9 @@ public:
|
|||||||
|
|
||||||
ContainerBox* GetWorldBox() const;
|
ContainerBox* GetWorldBox() const;
|
||||||
|
|
||||||
|
/// Get the list of solids in the scene
|
||||||
|
const Vector<Solid*>& GetSolids() const;
|
||||||
|
|
||||||
/// Set the primary generator (emitter) for the simulation.
|
/// Set the primary generator (emitter) for the simulation.
|
||||||
/// The Scene does NOT take ownership of the emitter.
|
/// The Scene does NOT take ownership of the emitter.
|
||||||
void SetEmitter(EmitterPrimary *emitter);
|
void SetEmitter(EmitterPrimary *emitter);
|
||||||
|
|||||||
@@ -151,6 +151,7 @@ TessellatedSolid::TessellatedSolid(const char *name)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void TessellatedSolid::SetMesh(TriangleMesh &mesh) {
|
void TessellatedSolid::SetMesh(TriangleMesh &mesh) {
|
||||||
|
this->m_Mesh = mesh;
|
||||||
G4TessellatedSolid *ts = this->m_Solid;
|
G4TessellatedSolid *ts = this->m_Solid;
|
||||||
for (int i = 0; i < mesh.Triangles().size(); ++i) {
|
for (int i = 0; i < mesh.Triangles().size(); ++i) {
|
||||||
const Vector3i &trg = mesh.Triangles().at(i);
|
const Vector3i &trg = mesh.Triangles().at(i);
|
||||||
@@ -165,6 +166,9 @@ void TessellatedSolid::SetMesh(TriangleMesh &mesh) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TessellatedSolid::Update() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -94,10 +94,13 @@ public:
|
|||||||
uLibGetMacro(Solid, G4TessellatedSolid *)
|
uLibGetMacro(Solid, G4TessellatedSolid *)
|
||||||
virtual G4VSolid* GetG4Solid() const override { return (G4VSolid*)m_Solid; }
|
virtual G4VSolid* GetG4Solid() const override { return (G4VSolid*)m_Solid; }
|
||||||
|
|
||||||
|
const TriangleMesh& GetMesh() const { return m_Mesh; }
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void Update();
|
void Update();
|
||||||
|
|
||||||
private :
|
private :
|
||||||
|
TriangleMesh m_Mesh;
|
||||||
G4TessellatedSolid *m_Solid;
|
G4TessellatedSolid *m_Solid;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
146
src/Math/Assembly.cpp
Normal file
146
src/Math/Assembly.cpp
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
/*//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// 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 "Math/Assembly.h"
|
||||||
|
#include "Math/ContainerBox.h"
|
||||||
|
#include "Math/Cylinder.h"
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
namespace uLib {
|
||||||
|
|
||||||
|
Assembly::Assembly()
|
||||||
|
: ObjectsContext(),
|
||||||
|
AffineTransform(),
|
||||||
|
m_BBoxMin(Vector3f::Zero()),
|
||||||
|
m_BBoxMax(Vector3f::Zero()),
|
||||||
|
m_ShowBoundingBox(false),
|
||||||
|
m_GroupSelection(true) {}
|
||||||
|
|
||||||
|
Assembly::Assembly(const Assembly ©)
|
||||||
|
: ObjectsContext(copy),
|
||||||
|
AffineTransform(copy),
|
||||||
|
m_BBoxMin(copy.m_BBoxMin),
|
||||||
|
m_BBoxMax(copy.m_BBoxMax),
|
||||||
|
m_ShowBoundingBox(copy.m_ShowBoundingBox),
|
||||||
|
m_GroupSelection(copy.m_GroupSelection) {}
|
||||||
|
|
||||||
|
Assembly::~Assembly() {}
|
||||||
|
|
||||||
|
void Assembly::AddObject(Object *obj) {
|
||||||
|
if (auto *at = dynamic_cast<AffineTransform *>(obj)) {
|
||||||
|
at->SetParent(this);
|
||||||
|
}
|
||||||
|
ObjectsContext::AddObject(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Assembly::RemoveObject(Object *obj) {
|
||||||
|
if (auto *at = dynamic_cast<AffineTransform *>(obj)) {
|
||||||
|
if (at->GetParent() == this)
|
||||||
|
at->SetParent(nullptr);
|
||||||
|
}
|
||||||
|
ObjectsContext::RemoveObject(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Assembly::ComputeBoundingBox() {
|
||||||
|
const auto &objects = this->GetObjects();
|
||||||
|
if (objects.empty()) {
|
||||||
|
m_BBoxMin = Vector3f::Zero();
|
||||||
|
m_BBoxMax = Vector3f::Zero();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float inf = std::numeric_limits<float>::max();
|
||||||
|
m_BBoxMin = Vector3f(inf, inf, inf);
|
||||||
|
m_BBoxMax = Vector3f(-inf, -inf, -inf);
|
||||||
|
|
||||||
|
Matrix4f invAsm = this->GetWorldMatrix().inverse();
|
||||||
|
|
||||||
|
for (Object *obj : objects) {
|
||||||
|
if (auto *box = dynamic_cast<ContainerBox *>(obj)) {
|
||||||
|
// ContainerBox: wm is matrix from unit cube [0,1] to assembly base
|
||||||
|
Matrix4f m = invAsm * box->GetWorldMatrix();
|
||||||
|
for (int i = 0; i < 8; ++i) {
|
||||||
|
float x = (i & 1) ? 1.0f : 0.0f;
|
||||||
|
float y = (i & 2) ? 1.0f : 0.0f;
|
||||||
|
float z = (i & 4) ? 1.0f : 0.0f;
|
||||||
|
Vector4f corner = m * Vector4f(x, y, z, 1.0f);
|
||||||
|
for (int a = 0; a < 3; ++a) {
|
||||||
|
m_BBoxMin(a) = std::min(m_BBoxMin(a), corner(a));
|
||||||
|
m_BBoxMax(a) = std::max(m_BBoxMax(a), corner(a));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (auto *cyl = dynamic_cast<Cylinder *>(obj)) {
|
||||||
|
// Cylinder: centered [-1, 1] radial, [-0.5, 0.5] height
|
||||||
|
Matrix4f m = invAsm * cyl->GetWorldMatrix();
|
||||||
|
for (int i = 0; i < 8; ++i) {
|
||||||
|
float x = (i & 1) ? 1.0f : -1.0f;
|
||||||
|
float y = (i & 2) ? 0.5f : -0.5f;
|
||||||
|
float z = (i & 4) ? 1.0f : -1.0f;
|
||||||
|
Vector4f corner = m * Vector4f(x, y, z, 1.0f);
|
||||||
|
for (int a = 0; a < 3; ++a) {
|
||||||
|
m_BBoxMin(a) = std::min(m_BBoxMin(a), corner(a));
|
||||||
|
m_BBoxMax(a) = std::max(m_BBoxMax(a), corner(a));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (auto *subAsm = dynamic_cast<Assembly *>(obj)) {
|
||||||
|
// Recursive AABB for nested assemblies
|
||||||
|
subAsm->ComputeBoundingBox();
|
||||||
|
Vector3f subMin, subMax;
|
||||||
|
subAsm->GetBoundingBox(subMin, subMax);
|
||||||
|
Matrix4f m = invAsm * subAsm->GetWorldMatrix();
|
||||||
|
for (int i = 0; i < 8; ++i) {
|
||||||
|
float x = (i & 1) ? subMax(0) : subMin(0);
|
||||||
|
float y = (i & 2) ? subMax(1) : subMin(1);
|
||||||
|
float z = (i & 4) ? subMax(2) : subMin(2);
|
||||||
|
Vector4f corner = m * Vector4f(x, y, z, 1.0f);
|
||||||
|
for (int a = 0; a < 3; ++a) {
|
||||||
|
m_BBoxMin(a) = std::min(m_BBoxMin(a), corner(a));
|
||||||
|
m_BBoxMax(a) = std::max(m_BBoxMax(a), corner(a));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Assembly::GetBoundingBox(Vector3f &bbMin, Vector3f &bbMax) const {
|
||||||
|
bbMin = m_BBoxMin;
|
||||||
|
bbMax = m_BBoxMax;
|
||||||
|
}
|
||||||
|
|
||||||
|
ContainerBox Assembly::GetBoundingBoxAsContainer() const {
|
||||||
|
ContainerBox bb;
|
||||||
|
Vector3f size = m_BBoxMax - m_BBoxMin;
|
||||||
|
bb.SetSize(size);
|
||||||
|
bb.SetPosition(m_BBoxMin);
|
||||||
|
return bb;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Assembly::SetShowBoundingBox(bool show) {
|
||||||
|
m_ShowBoundingBox = show;
|
||||||
|
this->Updated();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Assembly::GetShowBoundingBox() const {
|
||||||
|
return m_ShowBoundingBox;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Assembly::SetGroupSelection(bool group) {
|
||||||
|
m_GroupSelection = group;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Assembly::GetGroupSelection() const {
|
||||||
|
return m_GroupSelection;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace uLib
|
||||||
109
src/Math/Assembly.h
Normal file
109
src/Math/Assembly.h
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
/*//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// 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 U_ASSEMBLY_H
|
||||||
|
#define U_ASSEMBLY_H
|
||||||
|
|
||||||
|
#include "Core/ObjectsContext.h"
|
||||||
|
#include "Math/ContainerBox.h"
|
||||||
|
#include "Math/Transform.h"
|
||||||
|
|
||||||
|
namespace uLib {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Assembly groups geometric objects (ContainerBox, Cylinder, etc.)
|
||||||
|
* under a common transformation.
|
||||||
|
*
|
||||||
|
* Assembly derives from ObjectsContext so objects can be added/removed
|
||||||
|
* dynamically. It also inherits AffineTransform to provide a group-level
|
||||||
|
* transformation that is applied on top of each child's own transform.
|
||||||
|
*
|
||||||
|
* A bounding box is automatically computed from all contained objects and
|
||||||
|
* can be queried or shown/hidden through the VTK puppet.
|
||||||
|
*/
|
||||||
|
class Assembly : public ObjectsContext, public AffineTransform {
|
||||||
|
public:
|
||||||
|
virtual const char *GetClassName() const override { return "Assembly"; }
|
||||||
|
|
||||||
|
Assembly();
|
||||||
|
Assembly(const Assembly ©);
|
||||||
|
virtual ~Assembly();
|
||||||
|
|
||||||
|
virtual void AddObject(Object* obj) override;
|
||||||
|
virtual void RemoveObject(Object* obj) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Recomputes the axis-aligned bounding box enclosing all children.
|
||||||
|
* Stores the result internally.
|
||||||
|
*/
|
||||||
|
void ComputeBoundingBox();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the bounding box as min/max corners (in assembly-local
|
||||||
|
* coordinates).
|
||||||
|
*/
|
||||||
|
void GetBoundingBox(Vector3f &bbMin, Vector3f &bbMax) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the bounding box as a ContainerBox (positioned
|
||||||
|
* at bbMin, sized bbMax-bbMin, parented to this transform).
|
||||||
|
*/
|
||||||
|
ContainerBox GetBoundingBoxAsContainer() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Controls whether the bounding box wireframe should be shown
|
||||||
|
* in the viewer (used by the VTK puppet).
|
||||||
|
*/
|
||||||
|
void SetShowBoundingBox(bool show);
|
||||||
|
bool GetShowBoundingBox() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Controls selection behavior.
|
||||||
|
* If true (default), clicking any child within the assembly will select
|
||||||
|
* the assembly itself. If false, individual children can be picked.
|
||||||
|
*/
|
||||||
|
void SetGroupSelection(bool group);
|
||||||
|
bool GetGroupSelection() const;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
virtual void Updated() override {
|
||||||
|
if (m_InUpdated) return; // break signal recursion
|
||||||
|
m_InUpdated = true;
|
||||||
|
this->ComputeBoundingBox();
|
||||||
|
ULIB_SIGNAL_EMIT(Assembly::Updated);
|
||||||
|
m_InUpdated = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Vector3f m_BBoxMin;
|
||||||
|
Vector3f m_BBoxMax;
|
||||||
|
bool m_ShowBoundingBox;
|
||||||
|
bool m_GroupSelection;
|
||||||
|
bool m_InUpdated = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace uLib
|
||||||
|
|
||||||
|
#endif // U_ASSEMBLY_H
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
|
|
||||||
set(HEADERS ContainerBox.h
|
set(HEADERS ContainerBox.h
|
||||||
Cylinder.h
|
Cylinder.h
|
||||||
|
Assembly.h
|
||||||
Dense.h
|
Dense.h
|
||||||
Geometry.h
|
Geometry.h
|
||||||
Transform.h
|
Transform.h
|
||||||
@@ -33,6 +34,7 @@ set(SOURCES VoxRaytracer.cpp
|
|||||||
VoxImage.cpp
|
VoxImage.cpp
|
||||||
TriangleMesh.cpp
|
TriangleMesh.cpp
|
||||||
QuadMesh.cpp
|
QuadMesh.cpp
|
||||||
|
Assembly.cpp
|
||||||
Dense.cpp
|
Dense.cpp
|
||||||
Structured2DGrid.cpp
|
Structured2DGrid.cpp
|
||||||
Structured4DGrid.cpp
|
Structured4DGrid.cpp
|
||||||
|
|||||||
@@ -85,10 +85,11 @@ public:
|
|||||||
* @param copy The ContainerBox instance to copy from.
|
* @param copy The ContainerBox instance to copy from.
|
||||||
*/
|
*/
|
||||||
ContainerBox(const ContainerBox ©)
|
ContainerBox(const ContainerBox ©)
|
||||||
: m_LocalT(this), // BaseClass is Parent of m_LocalTransform
|
: m_LocalT(copy.m_LocalT), // Copy local transform state
|
||||||
AffineTransform(copy),
|
AffineTransform(copy),
|
||||||
p_Size(this, "Size", copy.p_Size),
|
p_Size(this, "Size", copy.p_Size),
|
||||||
p_Origin(this, "Origin", copy.p_Origin) {
|
p_Origin(this, "Origin", copy.p_Origin) {
|
||||||
|
m_LocalT.SetParent(this); // Reset parent to the new object
|
||||||
Object::connect(&p_Size, &Property<Vector3f>::PropertyChanged, this, &ContainerBox::SyncSize);
|
Object::connect(&p_Size, &Property<Vector3f>::PropertyChanged, this, &ContainerBox::SyncSize);
|
||||||
Object::connect(&p_Origin, &Property<Vector3f>::PropertyChanged, this, &ContainerBox::SyncOrigin);
|
Object::connect(&p_Origin, &Property<Vector3f>::PropertyChanged, this, &ContainerBox::SyncOrigin);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,9 @@ public:
|
|||||||
Vector3f GetPoint(const Id_t id) const;
|
Vector3f GetPoint(const Id_t id) const;
|
||||||
|
|
||||||
inline std::vector<Vector3f> & Points() { return this->m_Points; }
|
inline std::vector<Vector3f> & Points() { return this->m_Points; }
|
||||||
|
inline const std::vector<Vector3f> & Points() const { return this->m_Points; }
|
||||||
inline std::vector<Vector4i> & Quads() { return this->m_Quads; }
|
inline std::vector<Vector4i> & Quads() { return this->m_Quads; }
|
||||||
|
inline const std::vector<Vector4i> & Quads() const { return this->m_Quads; }
|
||||||
|
|
||||||
const Vector4i & GetQuad(const Id_t id) const { return m_Quads.at(id); }
|
const Vector4i & GetQuad(const Id_t id) const { return m_Quads.at(id); }
|
||||||
Vector3f GetNormal(const Id_t id) const;
|
Vector3f GetNormal(const Id_t id) const;
|
||||||
|
|||||||
@@ -69,6 +69,8 @@ public:
|
|||||||
m_Parent(NULL)
|
m_Parent(NULL)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
virtual ~AffineTransform() {}
|
||||||
|
|
||||||
AffineTransform(AffineTransform *parent) :
|
AffineTransform(AffineTransform *parent) :
|
||||||
m_T(Matrix4f::Identity()),
|
m_T(Matrix4f::Identity()),
|
||||||
m_Parent(parent)
|
m_Parent(parent)
|
||||||
|
|||||||
@@ -55,7 +55,9 @@ public:
|
|||||||
Vector3f GetPoint(const Id_t id) const;
|
Vector3f GetPoint(const Id_t id) const;
|
||||||
|
|
||||||
inline std::vector<Vector3f> & Points() { return this->m_Points; }
|
inline std::vector<Vector3f> & Points() { return this->m_Points; }
|
||||||
|
inline const std::vector<Vector3f> & Points() const { return this->m_Points; }
|
||||||
inline std::vector<Vector3i> & Triangles() { return this->m_Triangles; }
|
inline std::vector<Vector3i> & Triangles() { return this->m_Triangles; }
|
||||||
|
inline const std::vector<Vector3i> & Triangles() const { return this->m_Triangles; }
|
||||||
|
|
||||||
const Vector3i & GetTriangle(const Id_t id) const { return m_Triangles.at(id); }
|
const Vector3i & GetTriangle(const Id_t id) const { return m_Triangles.at(id); }
|
||||||
Vector3f GetNormal(const Id_t id) const;
|
Vector3f GetNormal(const Id_t id) const;
|
||||||
|
|||||||
@@ -109,8 +109,20 @@ public:
|
|||||||
|
|
||||||
VoxImage(const Vector3i &size);
|
VoxImage(const Vector3i &size);
|
||||||
|
|
||||||
VoxImage(const VoxImage<T> ©) : BaseClass(copy) {
|
// Use compiler-generated copy constructor and assignment operator
|
||||||
this->m_Data = copy.m_Data;
|
|
||||||
|
VoxImage<T>& operator=(const VoxImage<T>& other) {
|
||||||
|
if (this != &other) {
|
||||||
|
// Copy the base class non-virtual parts (dims, spacing, position, etc.)
|
||||||
|
// WITHOUT going through the virtual SetDims chain (which would call
|
||||||
|
// m_Data.resize() THEN DataAllocator::operator= will resize again → double-free).
|
||||||
|
// Instead, directly copy DataAllocator and update the StructuredGrid state.
|
||||||
|
this->m_Data = other.m_Data;
|
||||||
|
StructuredGrid::SetDims(other.GetDims());
|
||||||
|
this->SetSpacing(other.GetSpacing());
|
||||||
|
this->SetPosition(other.GetPosition());
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline DataAllocator<T> &Data() { return this->m_Data; }
|
inline DataAllocator<T> &Data() { return this->m_Data; }
|
||||||
|
|||||||
@@ -27,12 +27,16 @@
|
|||||||
#define VOXIMAGEFILTER_H
|
#define VOXIMAGEFILTER_H
|
||||||
|
|
||||||
#include "Core/StaticInterface.h"
|
#include "Core/StaticInterface.h"
|
||||||
|
#include "Core/Algorithm.h"
|
||||||
#include "Math/Dense.h"
|
#include "Math/Dense.h"
|
||||||
|
|
||||||
#include "Math/VoxImage.h"
|
#include "Math/VoxImage.h"
|
||||||
|
|
||||||
namespace uLib {
|
namespace uLib {
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Kernel shape interface (static check for operator()(float) and operator()(Vector3f))
|
||||||
|
|
||||||
namespace Interface {
|
namespace Interface {
|
||||||
struct VoxImageFilterShape {
|
struct VoxImageFilterShape {
|
||||||
template <class Self> void check_structural() {
|
template <class Self> void check_structural() {
|
||||||
@@ -42,63 +46,95 @@ struct VoxImageFilterShape {
|
|||||||
};
|
};
|
||||||
} // namespace Interface
|
} // namespace Interface
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Forward declaration
|
||||||
|
|
||||||
template <typename VoxelT> class Kernel;
|
template <typename VoxelT> class Kernel;
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Abstract interface (type-erased, used by python bindings)
|
||||||
|
|
||||||
namespace Abstract {
|
namespace Abstract {
|
||||||
class VoxImageFilter {
|
class VoxImageFilter {
|
||||||
public:
|
public:
|
||||||
virtual void Run() = 0;
|
virtual void Run() = 0;
|
||||||
|
|
||||||
virtual void SetImage(Abstract::VoxImage *image) = 0;
|
virtual void SetImage(Abstract::VoxImage *image) = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual ~VoxImageFilter() {}
|
virtual ~VoxImageFilter() {}
|
||||||
};
|
};
|
||||||
} // namespace Abstract
|
} // namespace Abstract
|
||||||
|
|
||||||
template <typename VoxelT, typename AlgorithmT>
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
class VoxImageFilter : public Abstract::VoxImageFilter, public Object {
|
// VoxImageFilter — kernel-based voxel filter using CRTP + Algorithm
|
||||||
|
//
|
||||||
|
// Template parameters:
|
||||||
|
// VoxelT — voxel data type (must satisfy Interface::Voxel)
|
||||||
|
// CrtpImplT — concrete filter subclass (CRTP), must provide:
|
||||||
|
// float Evaluate(const VoxImage<VoxelT>& buffer, int index)
|
||||||
|
//
|
||||||
|
// Inherits Algorithm<VoxImage<VoxelT>*, VoxImage<VoxelT>*> so that filters
|
||||||
|
// can be used with AlgorithmTask for scheduled/async execution, and chained
|
||||||
|
// via encoder/decoder.
|
||||||
|
|
||||||
|
template <typename VoxelT, typename CrtpImplT>
|
||||||
|
class VoxImageFilter : public Abstract::VoxImageFilter,
|
||||||
|
public Algorithm<VoxImage<VoxelT>*, VoxImage<VoxelT>*> {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
virtual const char * GetClassName() const { return "VoxImageFilter"; }
|
virtual const char* GetClassName() const { return "VoxImageFilter"; }
|
||||||
|
|
||||||
VoxImageFilter(const Vector3i &size);
|
VoxImageFilter(const Vector3i &size);
|
||||||
|
|
||||||
|
// Algorithm interface ////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Process implements Algorithm::Process.
|
||||||
|
* Applies the filter in-place on the input image and returns it.
|
||||||
|
*/
|
||||||
|
VoxImage<VoxelT>* Process(VoxImage<VoxelT>* const& image) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Run implements Abstract::VoxImageFilter::Run.
|
||||||
|
* Calls Process on the current image.
|
||||||
|
*/
|
||||||
void Run();
|
void Run();
|
||||||
|
|
||||||
|
// Device awareness ///////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/** @brief Returns VRAM if image or kernel data is on GPU, RAM otherwise. */
|
||||||
|
MemoryDevice GetPreferredDevice() const override {
|
||||||
|
if (m_Image && m_Image->Data().GetDevice() == MemoryDevice::VRAM)
|
||||||
|
return MemoryDevice::VRAM;
|
||||||
|
if (m_KernelData.ConstData().GetDevice() == MemoryDevice::VRAM)
|
||||||
|
return MemoryDevice::VRAM;
|
||||||
|
return MemoryDevice::RAM;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kernel setup ///////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
void SetKernelNumericXZY(const std::vector<float> &numeric);
|
void SetKernelNumericXZY(const std::vector<float> &numeric);
|
||||||
|
|
||||||
void SetKernelSpherical(float (*shape)(float));
|
void SetKernelSpherical(float (*shape)(float));
|
||||||
|
|
||||||
template <class ShapeT> void SetKernelSpherical(ShapeT shape);
|
template <class ShapeT> void SetKernelSpherical(ShapeT shape);
|
||||||
|
|
||||||
void SetKernelWeightFunction(float (*shape)(const Vector3f &));
|
void SetKernelWeightFunction(float (*shape)(const Vector3f &));
|
||||||
|
|
||||||
template <class ShapeT> void SetKernelWeightFunction(ShapeT shape);
|
template <class ShapeT> void SetKernelWeightFunction(ShapeT shape);
|
||||||
|
|
||||||
inline const Kernel<VoxelT> &GetKernelData() const {
|
// Accessors //////////////////////////////////////////////////////////////////
|
||||||
return this->m_KernelData;
|
|
||||||
}
|
|
||||||
inline Kernel<VoxelT> &GetKernelData() { return this->m_KernelData; }
|
|
||||||
|
|
||||||
inline VoxImage<VoxelT> *GetImage() const { return this->m_Image; }
|
const Kernel<VoxelT> &GetKernelData() const { return m_KernelData; }
|
||||||
|
Kernel<VoxelT> &GetKernelData() { return m_KernelData; }
|
||||||
|
|
||||||
|
VoxImage<VoxelT> *GetImage() const { return m_Image; }
|
||||||
void SetImage(Abstract::VoxImage *image);
|
void SetImage(Abstract::VoxImage *image);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
float Convolve(const VoxImage<VoxelT> &buffer, int index); // remove //
|
|
||||||
|
|
||||||
void SetKernelOffset();
|
void SetKernelOffset();
|
||||||
|
|
||||||
float Distance2(const Vector3i &v);
|
float Distance2(const Vector3i &v);
|
||||||
|
|
||||||
// protected members for algorithm access //
|
|
||||||
Kernel<VoxelT> m_KernelData;
|
Kernel<VoxelT> m_KernelData;
|
||||||
VoxImage<VoxelT> *m_Image;
|
VoxImage<VoxelT> *m_Image;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AlgorithmT *t_Algoritm;
|
CrtpImplT *m_CrtpImpl;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace uLib
|
} // namespace uLib
|
||||||
|
|||||||
@@ -33,7 +33,9 @@
|
|||||||
|
|
||||||
namespace uLib {
|
namespace uLib {
|
||||||
|
|
||||||
// KERNEL //////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//// KERNEL ////////////////////////////////////////////////////////////////////
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
template <typename T> class Kernel : public StructuredData {
|
template <typename T> class Kernel : public StructuredData {
|
||||||
typedef StructuredData BaseClass;
|
typedef StructuredData BaseClass;
|
||||||
@@ -41,13 +43,12 @@ template <typename T> class Kernel : public StructuredData {
|
|||||||
public:
|
public:
|
||||||
Kernel(const Vector3i &size);
|
Kernel(const Vector3i &size);
|
||||||
|
|
||||||
inline T &operator[](const Vector3i &id) { return m_Data[Map(id)]; }
|
T &operator[](const Vector3i &id) { return m_Data[Map(id)]; }
|
||||||
inline T &operator[](const int &id) { return m_Data[id]; }
|
T &operator[](const int &id) { return m_Data[id]; }
|
||||||
inline int GetCenterData() const;
|
int GetCenterData() const;
|
||||||
|
|
||||||
inline DataAllocator<T> &Data() { return this->m_Data; }
|
DataAllocator<T> &Data() { return m_Data; }
|
||||||
|
const DataAllocator<T> &ConstData() const { return m_Data; }
|
||||||
inline const DataAllocator<T> &ConstData() const { return this->m_Data; }
|
|
||||||
|
|
||||||
void PrintSelf(std::ostream &o) const;
|
void PrintSelf(std::ostream &o) const;
|
||||||
|
|
||||||
@@ -60,12 +61,14 @@ Kernel<T>::Kernel(const Vector3i &size) : BaseClass(size), m_Data(size.prod()) {
|
|||||||
Interface::IsA<T, Interface::Voxel>();
|
Interface::IsA<T, Interface::Voxel>();
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T> inline int Kernel<T>::GetCenterData() const {
|
template <typename T>
|
||||||
|
int Kernel<T>::GetCenterData() const {
|
||||||
static int center = Map(this->GetDims() / 2);
|
static int center = Map(this->GetDims() / 2);
|
||||||
return center;
|
return center;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T> void Kernel<T>::PrintSelf(std::ostream &o) const {
|
template <typename T>
|
||||||
|
void Kernel<T>::PrintSelf(std::ostream &o) const {
|
||||||
o << " Filter Kernel Dump [XZ_Y]: \n";
|
o << " Filter Kernel Dump [XZ_Y]: \n";
|
||||||
Vector3i index;
|
Vector3i index;
|
||||||
o << "\n Value: \n\n"
|
o << "\n Value: \n\n"
|
||||||
@@ -96,26 +99,42 @@ template <typename T> void Kernel<T>::PrintSelf(std::ostream &o) const {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//// VOXIMAGEFILTER IMPLEMENTATION /////////////////////////////////////////////
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
#define _TPL_ template <typename VoxelT, typename AlgorithmT>
|
template <typename VoxelT, typename CrtpImplT>
|
||||||
#define _TPLT_ VoxelT, AlgorithmT
|
VoxImageFilter<VoxelT, CrtpImplT>::VoxImageFilter(const Vector3i &size)
|
||||||
|
: m_KernelData(size)
|
||||||
|
, m_Image(nullptr)
|
||||||
|
, m_CrtpImpl(static_cast<CrtpImplT *>(this))
|
||||||
|
{}
|
||||||
|
|
||||||
_TPL_
|
template <typename VoxelT, typename CrtpImplT>
|
||||||
VoxImageFilter<_TPLT_>::VoxImageFilter(const Vector3i &size)
|
VoxImage<VoxelT>* VoxImageFilter<VoxelT, CrtpImplT>::Process(
|
||||||
: m_KernelData(size), t_Algoritm(static_cast<AlgorithmT *>(this)) {}
|
VoxImage<VoxelT>* const& image) {
|
||||||
|
if (m_Image != image) SetImage(image);
|
||||||
_TPL_
|
|
||||||
void VoxImageFilter<_TPLT_>::Run() {
|
|
||||||
VoxImage<VoxelT> buffer = *m_Image;
|
VoxImage<VoxelT> buffer = *m_Image;
|
||||||
#pragma omp parallel for
|
#pragma omp parallel for
|
||||||
for (int i = 0; i < m_Image->Data().size(); ++i)
|
for (int i = 0; i < m_Image->Data().size(); ++i)
|
||||||
m_Image->operator[](i).Value = this->t_Algoritm->Evaluate(buffer, i);
|
m_Image->operator[](i).Value = m_CrtpImpl->Evaluate(buffer, i);
|
||||||
#pragma omp barrier
|
#pragma omp barrier
|
||||||
|
return m_Image;
|
||||||
}
|
}
|
||||||
|
|
||||||
_TPL_
|
template <typename VoxelT, typename CrtpImplT>
|
||||||
void VoxImageFilter<_TPLT_>::SetKernelOffset() {
|
void VoxImageFilter<VoxelT, CrtpImplT>::Run() {
|
||||||
|
Process(m_Image);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename VoxelT, typename CrtpImplT>
|
||||||
|
void VoxImageFilter<VoxelT, CrtpImplT>::SetImage(Abstract::VoxImage *image) {
|
||||||
|
m_Image = reinterpret_cast<VoxImage<VoxelT> *>(image);
|
||||||
|
SetKernelOffset();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename VoxelT, typename CrtpImplT>
|
||||||
|
void VoxImageFilter<VoxelT, CrtpImplT>::SetKernelOffset() {
|
||||||
Vector3i id(0, 0, 0);
|
Vector3i id(0, 0, 0);
|
||||||
for (int z = 0; z < m_KernelData.GetDims()(2); ++z) {
|
for (int z = 0; z < m_KernelData.GetDims()(2); ++z) {
|
||||||
for (int x = 0; x < m_KernelData.GetDims()(0); ++x) {
|
for (int x = 0; x < m_KernelData.GetDims()(0); ++x) {
|
||||||
@@ -127,10 +146,10 @@ void VoxImageFilter<_TPLT_>::SetKernelOffset() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_TPL_
|
template <typename VoxelT, typename CrtpImplT>
|
||||||
float VoxImageFilter<_TPLT_>::Distance2(const Vector3i &v) {
|
float VoxImageFilter<VoxelT, CrtpImplT>::Distance2(const Vector3i &v) {
|
||||||
Vector3i tmp = v;
|
Vector3i tmp = v;
|
||||||
const Vector3i &dim = this->m_KernelData.GetDims();
|
const Vector3i &dim = m_KernelData.GetDims();
|
||||||
Vector3i center = dim / 2;
|
Vector3i center = dim / 2;
|
||||||
tmp = tmp - center;
|
tmp = tmp - center;
|
||||||
center = center.cwiseProduct(center);
|
center = center.cwiseProduct(center);
|
||||||
@@ -140,12 +159,9 @@ float VoxImageFilter<_TPLT_>::Distance2(const Vector3i &v) {
|
|||||||
0.25 * (3 - (dim(0) % 2) - (dim(1) % 2) - (dim(2) % 2)));
|
0.25 * (3 - (dim(0) % 2) - (dim(1) % 2) - (dim(2) % 2)));
|
||||||
}
|
}
|
||||||
|
|
||||||
_TPL_
|
template <typename VoxelT, typename CrtpImplT>
|
||||||
void VoxImageFilter<_TPLT_>::SetKernelNumericXZY(
|
void VoxImageFilter<VoxelT, CrtpImplT>::SetKernelNumericXZY(
|
||||||
const std::vector<float> &numeric) {
|
const std::vector<float> &numeric) {
|
||||||
// set data order //
|
|
||||||
StructuredData::Order order = m_KernelData.GetDataOrder();
|
|
||||||
// m_KernelData.SetDataOrder(StructuredData::XZY);
|
|
||||||
Vector3i id;
|
Vector3i id;
|
||||||
int index = 0;
|
int index = 0;
|
||||||
for (int y = 0; y < m_KernelData.GetDims()(1); ++y) {
|
for (int y = 0; y < m_KernelData.GetDims()(1); ++y) {
|
||||||
@@ -156,38 +172,39 @@ void VoxImageFilter<_TPLT_>::SetKernelNumericXZY(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// m_KernelData.SetDataOrder(order);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_TPL_
|
template <typename VoxelT, typename CrtpImplT>
|
||||||
void VoxImageFilter<_TPLT_>::SetKernelSpherical(float (*shape)(float)) {
|
void VoxImageFilter<VoxelT, CrtpImplT>::SetKernelSpherical(
|
||||||
|
float (*shape)(float)) {
|
||||||
Vector3i id;
|
Vector3i id;
|
||||||
for (int y = 0; y < m_KernelData.GetDims()(1); ++y) {
|
for (int y = 0; y < m_KernelData.GetDims()(1); ++y) {
|
||||||
for (int z = 0; z < m_KernelData.GetDims()(2); ++z) {
|
for (int z = 0; z < m_KernelData.GetDims()(2); ++z) {
|
||||||
for (int x = 0; x < m_KernelData.GetDims()(0); ++x) {
|
for (int x = 0; x < m_KernelData.GetDims()(0); ++x) {
|
||||||
id << x, y, z;
|
id << x, y, z;
|
||||||
m_KernelData[id].Value = shape(this->Distance2(id));
|
m_KernelData[id].Value = shape(Distance2(id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_TPL_ template <class ShapeT>
|
template <typename VoxelT, typename CrtpImplT>
|
||||||
void VoxImageFilter<_TPLT_>::SetKernelSpherical(ShapeT shape) {
|
template <class ShapeT>
|
||||||
|
void VoxImageFilter<VoxelT, CrtpImplT>::SetKernelSpherical(ShapeT shape) {
|
||||||
Interface::IsA<ShapeT, Interface::VoxImageFilterShape>();
|
Interface::IsA<ShapeT, Interface::VoxImageFilterShape>();
|
||||||
Vector3i id;
|
Vector3i id;
|
||||||
for (int y = 0; y < m_KernelData.GetDims()(1); ++y) {
|
for (int y = 0; y < m_KernelData.GetDims()(1); ++y) {
|
||||||
for (int z = 0; z < m_KernelData.GetDims()(2); ++z) {
|
for (int z = 0; z < m_KernelData.GetDims()(2); ++z) {
|
||||||
for (int x = 0; x < m_KernelData.GetDims()(0); ++x) {
|
for (int x = 0; x < m_KernelData.GetDims()(0); ++x) {
|
||||||
id << x, y, z;
|
id << x, y, z;
|
||||||
m_KernelData[id].Value = shape(this->Distance2(id));
|
m_KernelData[id].Value = shape(Distance2(id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_TPL_
|
template <typename VoxelT, typename CrtpImplT>
|
||||||
void VoxImageFilter<_TPLT_>::SetKernelWeightFunction(
|
void VoxImageFilter<VoxelT, CrtpImplT>::SetKernelWeightFunction(
|
||||||
float (*shape)(const Vector3f &)) {
|
float (*shape)(const Vector3f &)) {
|
||||||
const Vector3i &dim = m_KernelData.GetDims();
|
const Vector3i &dim = m_KernelData.GetDims();
|
||||||
Vector3i id;
|
Vector3i id;
|
||||||
@@ -195,20 +212,19 @@ void VoxImageFilter<_TPLT_>::SetKernelWeightFunction(
|
|||||||
for (int y = 0; y < dim(1); ++y) {
|
for (int y = 0; y < dim(1); ++y) {
|
||||||
for (int z = 0; z < dim(2); ++z) {
|
for (int z = 0; z < dim(2); ++z) {
|
||||||
for (int x = 0; x < dim(0); ++x) {
|
for (int x = 0; x < dim(0); ++x) {
|
||||||
// get voxels centroid coords from kernel center //
|
|
||||||
id << x, y, z;
|
id << x, y, z;
|
||||||
pt << id(0) - dim(0) / 2 + 0.5 * !(dim(0) % 2),
|
pt << id(0) - dim(0) / 2 + 0.5 * !(dim(0) % 2),
|
||||||
id(1) - dim(1) / 2 + 0.5 * !(dim(1) % 2),
|
id(1) - dim(1) / 2 + 0.5 * !(dim(1) % 2),
|
||||||
id(2) - dim(2) / 2 + 0.5 * !(dim(2) % 2);
|
id(2) - dim(2) / 2 + 0.5 * !(dim(2) % 2);
|
||||||
// compute function using given shape //
|
|
||||||
m_KernelData[id].Value = shape(pt);
|
m_KernelData[id].Value = shape(pt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_TPL_ template <class ShapeT>
|
template <typename VoxelT, typename CrtpImplT>
|
||||||
void VoxImageFilter<_TPLT_>::SetKernelWeightFunction(ShapeT shape) {
|
template <class ShapeT>
|
||||||
|
void VoxImageFilter<VoxelT, CrtpImplT>::SetKernelWeightFunction(ShapeT shape) {
|
||||||
Interface::IsA<ShapeT, Interface::VoxImageFilterShape>();
|
Interface::IsA<ShapeT, Interface::VoxImageFilterShape>();
|
||||||
const Vector3i &dim = m_KernelData.GetDims();
|
const Vector3i &dim = m_KernelData.GetDims();
|
||||||
Vector3i id;
|
Vector3i id;
|
||||||
@@ -216,45 +232,16 @@ void VoxImageFilter<_TPLT_>::SetKernelWeightFunction(ShapeT shape) {
|
|||||||
for (int y = 0; y < dim(1); ++y) {
|
for (int y = 0; y < dim(1); ++y) {
|
||||||
for (int z = 0; z < dim(2); ++z) {
|
for (int z = 0; z < dim(2); ++z) {
|
||||||
for (int x = 0; x < dim(0); ++x) {
|
for (int x = 0; x < dim(0); ++x) {
|
||||||
// get voxels centroid coords from kernel center //
|
|
||||||
id << x, y, z;
|
id << x, y, z;
|
||||||
pt << id(0) - dim(0) / 2 + 0.5 * !(dim(0) % 2),
|
pt << id(0) - dim(0) / 2 + 0.5 * !(dim(0) % 2),
|
||||||
id(1) - dim(1) / 2 + 0.5 * !(dim(1) % 2),
|
id(1) - dim(1) / 2 + 0.5 * !(dim(1) % 2),
|
||||||
id(2) - dim(2) / 2 + 0.5 * !(dim(2) % 2);
|
id(2) - dim(2) / 2 + 0.5 * !(dim(2) % 2);
|
||||||
// compute function using given shape //
|
|
||||||
m_KernelData[id].Value = shape(pt);
|
m_KernelData[id].Value = shape(pt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_TPL_
|
|
||||||
void VoxImageFilter<_TPLT_>::SetImage(Abstract::VoxImage *image) {
|
|
||||||
this->m_Image = reinterpret_cast<VoxImage<VoxelT> *>(image);
|
|
||||||
this->SetKernelOffset();
|
|
||||||
}
|
|
||||||
|
|
||||||
_TPL_
|
|
||||||
float VoxImageFilter<_TPLT_>::Convolve(const VoxImage<VoxelT> &buffer,
|
|
||||||
int index) {
|
|
||||||
const DataAllocator<VoxelT> &vbuf = buffer.ConstData();
|
|
||||||
const DataAllocator<VoxelT> &vker = m_KernelData.ConstData();
|
|
||||||
int vox_size = vbuf.size();
|
|
||||||
int ker_size = vker.size();
|
|
||||||
int pos;
|
|
||||||
float conv = 0, ksum = 0;
|
|
||||||
for (int ik = 0; ik < ker_size; ++ik) {
|
|
||||||
pos = index + vker[ik].Count - vker[m_KernelData.GetCenterData()].Count;
|
|
||||||
pos = (pos + vox_size) % vox_size;
|
|
||||||
conv += vbuf[pos].Value * vker[ik].Value;
|
|
||||||
ksum += vker[ik].Value;
|
|
||||||
}
|
|
||||||
return conv / ksum;
|
|
||||||
}
|
|
||||||
|
|
||||||
#undef _TPLT_
|
|
||||||
#undef _TPL_
|
|
||||||
|
|
||||||
} // namespace uLib
|
} // namespace uLib
|
||||||
|
|
||||||
#endif // VOXIMAGEFILTER_HPP
|
#endif // VOXIMAGEFILTER_HPP
|
||||||
|
|||||||
@@ -109,7 +109,8 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if defined(USE_CUDA) && defined(__CUDACC__)
|
#if defined(USE_CUDA) && defined(__CUDACC__)
|
||||||
void Run() {
|
VoxImage<VoxelT>* Process(VoxImage<VoxelT>* const& image) override {
|
||||||
|
if (this->m_Image != image) this->SetImage(image);
|
||||||
if (this->m_Image->Data().GetDevice() == MemoryDevice::VRAM ||
|
if (this->m_Image->Data().GetDevice() == MemoryDevice::VRAM ||
|
||||||
this->m_KernelData.Data().GetDevice() == MemoryDevice::VRAM) {
|
this->m_KernelData.Data().GetDevice() == MemoryDevice::VRAM) {
|
||||||
|
|
||||||
@@ -136,8 +137,9 @@ public:
|
|||||||
d_img_in, d_img_out, d_kernel, vox_size, ker_size, center_count,
|
d_img_in, d_img_out, d_kernel, vox_size, ker_size, center_count,
|
||||||
mAtrim, mBtrim);
|
mAtrim, mBtrim);
|
||||||
cudaDeviceSynchronize();
|
cudaDeviceSynchronize();
|
||||||
|
return this->m_Image;
|
||||||
} else {
|
} else {
|
||||||
BaseClass::Run();
|
return BaseClass::Process(image);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -207,7 +209,8 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if defined(USE_CUDA) && defined(__CUDACC__)
|
#if defined(USE_CUDA) && defined(__CUDACC__)
|
||||||
void Run() {
|
VoxImage<VoxelT>* Process(VoxImage<VoxelT>* const& image) override {
|
||||||
|
if (this->m_Image != image) this->SetImage(image);
|
||||||
if (this->m_Image->Data().GetDevice() == MemoryDevice::VRAM ||
|
if (this->m_Image->Data().GetDevice() == MemoryDevice::VRAM ||
|
||||||
this->m_KernelData.Data().GetDevice() == MemoryDevice::VRAM) {
|
this->m_KernelData.Data().GetDevice() == MemoryDevice::VRAM) {
|
||||||
|
|
||||||
@@ -234,8 +237,9 @@ public:
|
|||||||
d_img_in, d_img_out, d_kernel, vox_size, ker_size, center_count,
|
d_img_in, d_img_out, d_kernel, vox_size, ker_size, center_count,
|
||||||
mAtrim, mBtrim);
|
mAtrim, mBtrim);
|
||||||
cudaDeviceSynchronize();
|
cudaDeviceSynchronize();
|
||||||
|
return this->m_Image;
|
||||||
} else {
|
} else {
|
||||||
BaseClass::Run();
|
return BaseClass::Process(image);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -30,8 +30,6 @@
|
|||||||
#include "VoxImageFilter.h"
|
#include "VoxImageFilter.h"
|
||||||
#include <Math/Dense.h>
|
#include <Math/Dense.h>
|
||||||
|
|
||||||
#define likely(expr) __builtin_expect(!!(expr), 1)
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
///// VOXIMAGE FILTER CUSTOM /////////////////////////////////////////////////
|
///// VOXIMAGE FILTER CUSTOM /////////////////////////////////////////////////
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
@@ -50,7 +48,7 @@ public:
|
|||||||
: BaseClass(size), m_CustomEvaluate(NULL) {}
|
: BaseClass(size), m_CustomEvaluate(NULL) {}
|
||||||
|
|
||||||
float Evaluate(const VoxImage<VoxelT> &buffer, int index) {
|
float Evaluate(const VoxImage<VoxelT> &buffer, int index) {
|
||||||
if (likely(m_CustomEvaluate)) {
|
if (m_CustomEvaluate) {
|
||||||
const DataAllocator<VoxelT> &vbuf = buffer.ConstData();
|
const DataAllocator<VoxelT> &vbuf = buffer.ConstData();
|
||||||
const DataAllocator<VoxelT> &vker = this->m_KernelData.ConstData();
|
const DataAllocator<VoxelT> &vker = this->m_KernelData.ConstData();
|
||||||
int vox_size = vbuf.size();
|
int vox_size = vbuf.size();
|
||||||
|
|||||||
@@ -67,7 +67,8 @@ public:
|
|||||||
VoxFilterAlgorithmLinear(const Vector3i &size) : BaseClass(size) {}
|
VoxFilterAlgorithmLinear(const Vector3i &size) : BaseClass(size) {}
|
||||||
|
|
||||||
#if defined(USE_CUDA) && defined(__CUDACC__)
|
#if defined(USE_CUDA) && defined(__CUDACC__)
|
||||||
void Run() {
|
VoxImage<VoxelT>* Process(VoxImage<VoxelT>* const& image) override {
|
||||||
|
if (this->m_Image != image) this->SetImage(image);
|
||||||
if (this->m_Image->Data().GetDevice() == MemoryDevice::VRAM ||
|
if (this->m_Image->Data().GetDevice() == MemoryDevice::VRAM ||
|
||||||
this->m_KernelData.Data().GetDevice() == MemoryDevice::VRAM) {
|
this->m_KernelData.Data().GetDevice() == MemoryDevice::VRAM) {
|
||||||
|
|
||||||
@@ -92,8 +93,9 @@ public:
|
|||||||
LinearFilterKernel<<<blocksPerGrid, threadsPerBlock>>>(
|
LinearFilterKernel<<<blocksPerGrid, threadsPerBlock>>>(
|
||||||
d_img_in, d_img_out, d_kernel, vox_size, ker_size, center_count);
|
d_img_in, d_img_out, d_kernel, vox_size, ker_size, center_count);
|
||||||
cudaDeviceSynchronize();
|
cudaDeviceSynchronize();
|
||||||
|
return this->m_Image;
|
||||||
} else {
|
} else {
|
||||||
BaseClass::Run();
|
return BaseClass::Process(image);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -23,8 +23,6 @@
|
|||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#ifndef VOXIMAGEFILTERTHRESHOLD_HPP
|
#ifndef VOXIMAGEFILTERTHRESHOLD_HPP
|
||||||
#define VOXIMAGEFILTERTHRESHOLD_HPP
|
#define VOXIMAGEFILTERTHRESHOLD_HPP
|
||||||
|
|
||||||
@@ -39,40 +37,24 @@
|
|||||||
namespace uLib {
|
namespace uLib {
|
||||||
|
|
||||||
template <typename VoxelT>
|
template <typename VoxelT>
|
||||||
class VoxFilterAlgorithmThreshold :
|
class VoxFilterAlgorithmThreshold
|
||||||
public VoxImageFilter<VoxelT, VoxFilterAlgorithmThreshold<VoxelT> > {
|
: public VoxImageFilter<VoxelT, VoxFilterAlgorithmThreshold<VoxelT>> {
|
||||||
|
|
||||||
typedef VoxImageFilter<VoxelT, VoxFilterAlgorithmThreshold<VoxelT> > BaseClass;
|
typedef VoxImageFilter<VoxelT, VoxFilterAlgorithmThreshold<VoxelT>> BaseClass;
|
||||||
// ULIB_OBJECT_PARAMETERS(BaseClass) {
|
|
||||||
// float threshold;
|
|
||||||
// };
|
|
||||||
|
|
||||||
float m_threshold;
|
float m_threshold;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
VoxFilterAlgorithmThreshold(const Vector3i &size) : BaseClass(size)
|
VoxFilterAlgorithmThreshold(const Vector3i &size)
|
||||||
{
|
: BaseClass(size), m_threshold(0) {}
|
||||||
// init_parameters();
|
|
||||||
m_threshold = 0;
|
void SetThreshold(float th) { m_threshold = th; }
|
||||||
|
|
||||||
|
float Evaluate(const VoxImage<VoxelT> &buffer, int index) {
|
||||||
|
return static_cast<float>(buffer.ConstData().at(index).Value >= m_threshold);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void SetThreshold(float th) { m_threshold = th; }
|
|
||||||
|
|
||||||
float Evaluate(const VoxImage<VoxelT> &buffer, int index)
|
|
||||||
{
|
|
||||||
return static_cast<float>(buffer.ConstData().at(index).Value >=
|
|
||||||
// parameters().threshold);
|
|
||||||
m_threshold );
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
//template <typename VoxelT>
|
} // namespace uLib
|
||||||
//inline void VoxFilterAlgorithmThreshold<VoxelT>::init_parameters()
|
|
||||||
//{
|
|
||||||
// parameters().threshold = 0;
|
|
||||||
//}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // VOXIMAGEFILTERTHRESHOLD_HPP
|
#endif // VOXIMAGEFILTERTHRESHOLD_HPP
|
||||||
|
|||||||
408
src/Math/testing/AlgorithmCudaChainTest.cpp
Normal file
408
src/Math/testing/AlgorithmCudaChainTest.cpp
Normal file
@@ -0,0 +1,408 @@
|
|||||||
|
/*//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
#include "testing-prototype.h"
|
||||||
|
|
||||||
|
#include "Core/Algorithm.h"
|
||||||
|
#include "Math/VoxImage.h"
|
||||||
|
#include "Math/VoxImageFilter.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <thread>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
using namespace uLib;
|
||||||
|
|
||||||
|
struct TestVoxel {
|
||||||
|
Scalarf Value;
|
||||||
|
unsigned int Count;
|
||||||
|
};
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
BEGIN_TESTING(AlgorithmCudaChain);
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// TEST 1: Single filter — GetPreferredDevice reflects data location
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
{
|
||||||
|
std::cout << "\n--- Test 1: GetPreferredDevice reflects data location ---\n";
|
||||||
|
|
||||||
|
VoxImage<TestVoxel> image(Vector3i(10, 10, 10));
|
||||||
|
image[Vector3i(5, 5, 5)].Value = 1;
|
||||||
|
|
||||||
|
VoxFilterAlgorithmLinear<TestVoxel> filter(Vector3i(3, 3, 3));
|
||||||
|
std::vector<float> weights(27, 1.0f);
|
||||||
|
filter.SetImage(&image);
|
||||||
|
filter.SetKernelNumericXZY(weights);
|
||||||
|
|
||||||
|
// Before VRAM move: should prefer RAM
|
||||||
|
TEST1(filter.GetPreferredDevice() == MemoryDevice::RAM);
|
||||||
|
TEST1(!filter.IsGPU());
|
||||||
|
std::cout << " RAM mode: PreferredDevice=RAM, IsGPU=false OK\n";
|
||||||
|
|
||||||
|
// Move image data to VRAM
|
||||||
|
image.Data().MoveToVRAM();
|
||||||
|
|
||||||
|
// After VRAM move: should prefer VRAM
|
||||||
|
TEST1(filter.GetPreferredDevice() == MemoryDevice::VRAM);
|
||||||
|
TEST1(filter.IsGPU());
|
||||||
|
std::cout << " VRAM mode: PreferredDevice=VRAM, IsGPU=true OK\n";
|
||||||
|
|
||||||
|
// Move back to RAM
|
||||||
|
image.Data().MoveToRAM();
|
||||||
|
TEST1(filter.GetPreferredDevice() == MemoryDevice::RAM);
|
||||||
|
std::cout << " Back to RAM: PreferredDevice=RAM OK\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// TEST 2: Kernel data on VRAM also triggers GPU preference
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
{
|
||||||
|
std::cout << "\n--- Test 2: Kernel on VRAM triggers GPU preference ---\n";
|
||||||
|
|
||||||
|
VoxImage<TestVoxel> image(Vector3i(8, 8, 8));
|
||||||
|
VoxFilterAlgorithmLinear<TestVoxel> filter(Vector3i(3, 3, 3));
|
||||||
|
std::vector<float> weights(27, 1.0f);
|
||||||
|
filter.SetImage(&image);
|
||||||
|
filter.SetKernelNumericXZY(weights);
|
||||||
|
|
||||||
|
TEST1(filter.GetPreferredDevice() == MemoryDevice::RAM);
|
||||||
|
|
||||||
|
// Only kernel on VRAM
|
||||||
|
filter.GetKernelData().Data().MoveToVRAM();
|
||||||
|
TEST1(filter.GetPreferredDevice() == MemoryDevice::VRAM);
|
||||||
|
std::cout << " Kernel on VRAM: PreferredDevice=VRAM OK\n";
|
||||||
|
|
||||||
|
filter.GetKernelData().Data().MoveToRAM();
|
||||||
|
TEST1(filter.GetPreferredDevice() == MemoryDevice::RAM);
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// TEST 3: Algorithm interface — Process through base pointer
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
{
|
||||||
|
std::cout << "\n--- Test 3: Process through Algorithm base pointer ---\n";
|
||||||
|
|
||||||
|
VoxImage<TestVoxel> image(Vector3i(10, 10, 10));
|
||||||
|
image[Vector3i(5, 5, 5)].Value = 10;
|
||||||
|
|
||||||
|
VoxFilterAlgorithmLinear<TestVoxel> filter(Vector3i(3, 3, 3));
|
||||||
|
std::vector<float> weights(27, 1.0f);
|
||||||
|
filter.SetImage(&image);
|
||||||
|
filter.SetKernelNumericXZY(weights);
|
||||||
|
|
||||||
|
// Use through Algorithm base class pointer
|
||||||
|
Algorithm<VoxImage<TestVoxel>*, VoxImage<TestVoxel>*>* alg = &filter;
|
||||||
|
|
||||||
|
VoxImage<TestVoxel>* result = alg->Process(&image);
|
||||||
|
TEST1(result == &image);
|
||||||
|
std::cout << " Process through base pointer returned correct image OK\n";
|
||||||
|
|
||||||
|
// Verify filter actually ran (center voxel should be averaged)
|
||||||
|
// With uniform 3x3x3 kernel and single non-zero voxel at center,
|
||||||
|
// the center value should be 10/27 ≈ 0.37
|
||||||
|
TEST1(image[Vector3i(5, 5, 5)].Value < 10.0f);
|
||||||
|
std::cout << " Filter modified voxel values OK\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// TEST 4: Encoder/decoder chain — two filters linked
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
{
|
||||||
|
std::cout << "\n--- Test 4: Encoder/decoder chain ---\n";
|
||||||
|
|
||||||
|
VoxImage<TestVoxel> image(Vector3i(10, 10, 10));
|
||||||
|
image[Vector3i(5, 5, 5)].Value = 100;
|
||||||
|
|
||||||
|
// First filter: linear smoothing
|
||||||
|
VoxFilterAlgorithmLinear<TestVoxel> filter1(Vector3i(3, 3, 3));
|
||||||
|
std::vector<float> weights1(27, 1.0f);
|
||||||
|
filter1.SetImage(&image);
|
||||||
|
filter1.SetKernelNumericXZY(weights1);
|
||||||
|
|
||||||
|
// Second filter: threshold
|
||||||
|
VoxFilterAlgorithmThreshold<TestVoxel> filter2(Vector3i(1, 1, 1));
|
||||||
|
filter2.SetThreshold(0.5f);
|
||||||
|
filter2.SetImage(&image);
|
||||||
|
// 1x1x1 kernel with value 1
|
||||||
|
std::vector<float> weights2(1, 1.0f);
|
||||||
|
filter2.SetKernelNumericXZY(weights2);
|
||||||
|
|
||||||
|
// Chain: filter1 → filter2
|
||||||
|
filter1.SetDecoder(&filter2);
|
||||||
|
filter2.SetEncoder(&filter1);
|
||||||
|
|
||||||
|
TEST1(filter1.GetDecoder() == &filter2);
|
||||||
|
TEST1(filter2.GetEncoder() == &filter1);
|
||||||
|
std::cout << " Chain linked: filter1 -> filter2 OK\n";
|
||||||
|
|
||||||
|
// Execute chain manually (encoder first, then decoder)
|
||||||
|
filter1.Process(&image);
|
||||||
|
float smoothed_center = image[Vector3i(5, 5, 5)].Value;
|
||||||
|
std::cout << " After linear: center = " << smoothed_center << "\n";
|
||||||
|
|
||||||
|
filter2.Process(&image);
|
||||||
|
float thresholded_center = image[Vector3i(5, 5, 5)].Value;
|
||||||
|
std::cout << " After threshold: center = " << thresholded_center << "\n";
|
||||||
|
|
||||||
|
// After threshold, values should be 0 or 1
|
||||||
|
TEST1(thresholded_center == 0.0f || thresholded_center == 1.0f);
|
||||||
|
std::cout << " Chain execution produced valid results OK\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// TEST 5: CUDA chain — VRAM data through chained filters
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
{
|
||||||
|
std::cout << "\n--- Test 5: VRAM data through chained filters ---\n";
|
||||||
|
|
||||||
|
VoxImage<TestVoxel> image(Vector3i(10, 10, 10));
|
||||||
|
image[Vector3i(5, 5, 5)].Value = 50;
|
||||||
|
|
||||||
|
VoxFilterAlgorithmLinear<TestVoxel> filter1(Vector3i(3, 3, 3));
|
||||||
|
std::vector<float> weights1(27, 1.0f);
|
||||||
|
filter1.SetImage(&image);
|
||||||
|
filter1.SetKernelNumericXZY(weights1);
|
||||||
|
|
||||||
|
VoxFilterAlgorithmAbtrim<TestVoxel> filter2(Vector3i(3, 3, 3));
|
||||||
|
std::vector<float> weights2(27, 1.0f);
|
||||||
|
filter2.SetImage(&image);
|
||||||
|
filter2.SetKernelNumericXZY(weights2);
|
||||||
|
filter2.SetABTrim(1, 1);
|
||||||
|
|
||||||
|
// Chain
|
||||||
|
filter1.SetDecoder(&filter2);
|
||||||
|
filter2.SetEncoder(&filter1);
|
||||||
|
|
||||||
|
// Move data to VRAM
|
||||||
|
image.Data().MoveToVRAM();
|
||||||
|
filter1.GetKernelData().Data().MoveToVRAM();
|
||||||
|
filter2.GetKernelData().Data().MoveToVRAM();
|
||||||
|
|
||||||
|
// Both filters should report VRAM preference
|
||||||
|
TEST1(filter1.GetPreferredDevice() == MemoryDevice::VRAM);
|
||||||
|
TEST1(filter2.GetPreferredDevice() == MemoryDevice::VRAM);
|
||||||
|
TEST1(filter1.IsGPU());
|
||||||
|
TEST1(filter2.IsGPU());
|
||||||
|
std::cout << " Both filters detect VRAM preference OK\n";
|
||||||
|
|
||||||
|
// Verify the chain's device consistency
|
||||||
|
auto* encoder = filter2.GetEncoder();
|
||||||
|
TEST1(encoder != nullptr);
|
||||||
|
TEST1(encoder->IsGPU());
|
||||||
|
std::cout << " Encoder in chain also reports GPU OK\n";
|
||||||
|
|
||||||
|
#ifdef USE_CUDA
|
||||||
|
// With CUDA: filters execute on GPU via Process()
|
||||||
|
image.Data().MoveToRAM(); // reset for clean test
|
||||||
|
image[Vector3i(5, 5, 5)].Value = 50;
|
||||||
|
image.Data().MoveToVRAM();
|
||||||
|
|
||||||
|
filter1.Process(&image);
|
||||||
|
TEST1(image.Data().GetDevice() == MemoryDevice::VRAM);
|
||||||
|
std::cout << " CUDA: data stays in VRAM after filter1 OK\n";
|
||||||
|
|
||||||
|
filter2.Process(&image);
|
||||||
|
TEST1(image.Data().GetDevice() == MemoryDevice::VRAM);
|
||||||
|
std::cout << " CUDA: data stays in VRAM after filter2 OK\n";
|
||||||
|
#else
|
||||||
|
// Without CUDA: verify Process still works via CPU fallback
|
||||||
|
image.Data().MoveToRAM();
|
||||||
|
image[Vector3i(5, 5, 5)].Value = 50;
|
||||||
|
|
||||||
|
filter1.GetKernelData().Data().MoveToRAM();
|
||||||
|
filter2.GetKernelData().Data().MoveToRAM();
|
||||||
|
|
||||||
|
filter1.Process(&image);
|
||||||
|
filter2.Process(&image);
|
||||||
|
std::cout << " No CUDA: CPU fallback executed correctly OK\n";
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// TEST 6: AlgorithmTask with VRAM-aware filter
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
{
|
||||||
|
std::cout << "\n--- Test 6: AlgorithmTask with VRAM-aware filter ---\n";
|
||||||
|
|
||||||
|
VoxImage<TestVoxel> image(Vector3i(8, 8, 8));
|
||||||
|
image[Vector3i(4, 4, 4)].Value = 20;
|
||||||
|
|
||||||
|
VoxFilterAlgorithmLinear<TestVoxel> filter(Vector3i(3, 3, 3));
|
||||||
|
std::vector<float> weights(27, 1.0f);
|
||||||
|
filter.SetImage(&image);
|
||||||
|
filter.SetKernelNumericXZY(weights);
|
||||||
|
|
||||||
|
// Set up task
|
||||||
|
AlgorithmTask<VoxImage<TestVoxel>*, VoxImage<TestVoxel>*> task;
|
||||||
|
task.SetAlgorithm(&filter);
|
||||||
|
task.SetMode(AlgorithmTask<VoxImage<TestVoxel>*, VoxImage<TestVoxel>*>::Cyclic);
|
||||||
|
task.SetCycleTime(50);
|
||||||
|
|
||||||
|
// Run task for a few cycles
|
||||||
|
task.Run(&image);
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||||
|
task.Stop();
|
||||||
|
|
||||||
|
// After cyclic execution, the filter should have smoothed values
|
||||||
|
TEST1(image[Vector3i(4, 4, 4)].Value < 20.0f);
|
||||||
|
std::cout << " Task cyclic execution modified image OK\n";
|
||||||
|
std::cout << " Center value after smoothing: "
|
||||||
|
<< image[Vector3i(4, 4, 4)].Value << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// TEST 7: AlgorithmTask async with chained filters
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
{
|
||||||
|
std::cout << "\n--- Test 7: AlgorithmTask async with filter ---\n";
|
||||||
|
|
||||||
|
VoxImage<TestVoxel> image(Vector3i(8, 8, 8));
|
||||||
|
image[Vector3i(4, 4, 4)].Value = 30;
|
||||||
|
|
||||||
|
VoxFilterAlgorithmLinear<TestVoxel> filter(Vector3i(3, 3, 3));
|
||||||
|
std::vector<float> weights(27, 1.0f);
|
||||||
|
filter.SetImage(&image);
|
||||||
|
filter.SetKernelNumericXZY(weights);
|
||||||
|
|
||||||
|
AlgorithmTask<VoxImage<TestVoxel>*, VoxImage<TestVoxel>*> task;
|
||||||
|
task.SetAlgorithm(&filter);
|
||||||
|
task.SetMode(AlgorithmTask<VoxImage<TestVoxel>*, VoxImage<TestVoxel>*>::Async);
|
||||||
|
|
||||||
|
float before = image[Vector3i(4, 4, 4)].Value;
|
||||||
|
|
||||||
|
task.Run(&image);
|
||||||
|
|
||||||
|
// Trigger one execution
|
||||||
|
task.Notify();
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
|
||||||
|
task.Stop();
|
||||||
|
|
||||||
|
float after = image[Vector3i(4, 4, 4)].Value;
|
||||||
|
TEST1(after < before);
|
||||||
|
std::cout << " Async trigger: value " << before << " -> " << after << " OK\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// TEST 8: Device preference propagation in chain
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
{
|
||||||
|
std::cout << "\n--- Test 8: Device preference propagation check ---\n";
|
||||||
|
|
||||||
|
VoxImage<TestVoxel> image(Vector3i(8, 8, 8));
|
||||||
|
image[Vector3i(4, 4, 4)].Value = 10;
|
||||||
|
|
||||||
|
VoxFilterAlgorithmLinear<TestVoxel> filterA(Vector3i(3, 3, 3));
|
||||||
|
VoxFilterAlgorithmAbtrim<TestVoxel> filterB(Vector3i(3, 3, 3));
|
||||||
|
VoxFilterAlgorithmThreshold<TestVoxel> filterC(Vector3i(1, 1, 1));
|
||||||
|
|
||||||
|
std::vector<float> w27(27, 1.0f);
|
||||||
|
std::vector<float> w1(1, 1.0f);
|
||||||
|
|
||||||
|
filterA.SetImage(&image);
|
||||||
|
filterA.SetKernelNumericXZY(w27);
|
||||||
|
filterB.SetImage(&image);
|
||||||
|
filterB.SetKernelNumericXZY(w27);
|
||||||
|
filterB.SetABTrim(1, 1);
|
||||||
|
filterC.SetImage(&image);
|
||||||
|
filterC.SetKernelNumericXZY(w1);
|
||||||
|
filterC.SetThreshold(0.1f);
|
||||||
|
|
||||||
|
// Chain: A → B → C
|
||||||
|
filterA.SetDecoder(&filterB);
|
||||||
|
filterB.SetEncoder(&filterA);
|
||||||
|
filterB.SetDecoder(&filterC);
|
||||||
|
filterC.SetEncoder(&filterB);
|
||||||
|
|
||||||
|
// All on RAM
|
||||||
|
TEST1(!filterA.IsGPU());
|
||||||
|
TEST1(!filterB.IsGPU());
|
||||||
|
TEST1(!filterC.IsGPU());
|
||||||
|
std::cout << " All filters on RAM OK\n";
|
||||||
|
|
||||||
|
// Move image to VRAM — filters A and B should detect it
|
||||||
|
image.Data().MoveToVRAM();
|
||||||
|
TEST1(filterA.IsGPU());
|
||||||
|
TEST1(filterB.IsGPU());
|
||||||
|
// filterC with 1x1x1 kernel doesn't have CUDA override, but still detects VRAM
|
||||||
|
TEST1(filterC.IsGPU());
|
||||||
|
std::cout << " Image on VRAM: all filters report GPU OK\n";
|
||||||
|
|
||||||
|
// Can walk the chain and check device consistency
|
||||||
|
auto* step = static_cast<Algorithm<VoxImage<TestVoxel>*, VoxImage<TestVoxel>*>*>(&filterA);
|
||||||
|
bool all_gpu = true;
|
||||||
|
while (step) {
|
||||||
|
if (!step->IsGPU()) all_gpu = false;
|
||||||
|
step = static_cast<Algorithm<VoxImage<TestVoxel>*, VoxImage<TestVoxel>*>*>(step->GetDecoder());
|
||||||
|
}
|
||||||
|
TEST1(all_gpu);
|
||||||
|
std::cout << " Chain walk: all steps report GPU OK\n";
|
||||||
|
|
||||||
|
image.Data().MoveToRAM();
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// TEST 9: Process through chain with Algorithm interface
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
{
|
||||||
|
std::cout << "\n--- Test 9: Sequential chain processing via Algorithm interface ---\n";
|
||||||
|
|
||||||
|
VoxImage<TestVoxel> image(Vector3i(10, 10, 10));
|
||||||
|
// Set a pattern: single bright voxel
|
||||||
|
image[Vector3i(5, 5, 5)].Value = 100;
|
||||||
|
|
||||||
|
VoxFilterAlgorithmLinear<TestVoxel> filterA(Vector3i(3, 3, 3));
|
||||||
|
std::vector<float> w(27, 1.0f);
|
||||||
|
filterA.SetImage(&image);
|
||||||
|
filterA.SetKernelNumericXZY(w);
|
||||||
|
|
||||||
|
VoxFilterAlgorithmLinear<TestVoxel> filterB(Vector3i(3, 3, 3));
|
||||||
|
filterB.SetImage(&image);
|
||||||
|
filterB.SetKernelNumericXZY(w);
|
||||||
|
|
||||||
|
// Chain
|
||||||
|
filterA.SetDecoder(&filterB);
|
||||||
|
filterB.SetEncoder(&filterA);
|
||||||
|
|
||||||
|
// Process chain through base pointer
|
||||||
|
using AlgType = Algorithm<VoxImage<TestVoxel>*, VoxImage<TestVoxel>*>;
|
||||||
|
AlgType* chain = &filterA;
|
||||||
|
|
||||||
|
// Walk and process
|
||||||
|
AlgType* current = chain;
|
||||||
|
while (current) {
|
||||||
|
current->Process(&image);
|
||||||
|
current = static_cast<AlgType*>(current->GetDecoder());
|
||||||
|
}
|
||||||
|
|
||||||
|
// After two rounds of smoothing, the peak should be smaller than original
|
||||||
|
float final_val = image[Vector3i(5, 5, 5)].Value;
|
||||||
|
TEST1(final_val < 100.0f);
|
||||||
|
std::cout << " Two-stage smoothing: peak = " << final_val << " OK\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
END_TESTING;
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ set(TESTS
|
|||||||
QuadMeshTest
|
QuadMeshTest
|
||||||
BitCodeTest
|
BitCodeTest
|
||||||
UnitsTest
|
UnitsTest
|
||||||
|
AlgorithmCudaChainTest
|
||||||
)
|
)
|
||||||
|
|
||||||
set(LIBRARIES
|
set(LIBRARIES
|
||||||
@@ -28,6 +29,6 @@ set(LIBRARIES
|
|||||||
uLib_add_tests(Math)
|
uLib_add_tests(Math)
|
||||||
|
|
||||||
if(USE_CUDA)
|
if(USE_CUDA)
|
||||||
set_source_files_properties(VoxImageTest.cpp VoxImageCopyTest.cpp VoxImageFilterTest.cpp VoxRaytracerTest.cpp VoxRaytracerTestExtended.cpp PROPERTIES LANGUAGE CUDA)
|
set_source_files_properties(VoxImageTest.cpp VoxImageCopyTest.cpp VoxImageFilterTest.cpp VoxRaytracerTest.cpp VoxRaytracerTestExtended.cpp AlgorithmCudaChainTest.cpp PROPERTIES LANGUAGE CUDA)
|
||||||
set_source_files_properties(VoxRaytracerTest.cpp VoxRaytracerTestExtended.cpp PROPERTIES CXX_STANDARD 17 CUDA_STANDARD 17)
|
set_source_files_properties(VoxRaytracerTest.cpp VoxRaytracerTestExtended.cpp PROPERTIES CXX_STANDARD 17 CUDA_STANDARD 17)
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ int main()
|
|||||||
|
|
||||||
// Test 1: Basic identity transformation and cylinder parameters
|
// Test 1: Basic identity transformation and cylinder parameters
|
||||||
{
|
{
|
||||||
Cylinder cyl(2.0, 10.0);
|
Cylinder cyl(2.0, 10.0, 2);
|
||||||
std::cout << "Cyl R=" << cyl.GetRadius() << " H=" << cyl.GetHeight() << std::endl;
|
std::cout << "Cyl R=" << cyl.GetRadius() << " H=" << cyl.GetHeight() << std::endl;
|
||||||
std::cout << "Cyl World Matrix:\n" << cyl.GetWorldMatrix() << std::endl;
|
std::cout << "Cyl World Matrix:\n" << cyl.GetWorldMatrix() << std::endl;
|
||||||
|
|
||||||
@@ -82,7 +82,7 @@ int main()
|
|||||||
|
|
||||||
// Test 2: Translation
|
// Test 2: Translation
|
||||||
{
|
{
|
||||||
Cylinder cyl(1.0, 2.0);
|
Cylinder cyl(1.0, 2.0, 2);
|
||||||
cyl.SetPosition(Vector3f(10, 20, 30));
|
cyl.SetPosition(Vector3f(10, 20, 30));
|
||||||
|
|
||||||
// Local base origin (0, 0, 0) -> World (10, 20, 30)
|
// Local base origin (0, 0, 0) -> World (10, 20, 30)
|
||||||
@@ -96,7 +96,7 @@ int main()
|
|||||||
|
|
||||||
// Test 3: Rotation and complex mapping
|
// Test 3: Rotation and complex mapping
|
||||||
{
|
{
|
||||||
Cylinder cyl(5.0, 20.0);
|
Cylinder cyl(5.0, 20.0, 2);
|
||||||
cyl.SetPosition(Vector3f(1.0, 2.0, 3.0));
|
cyl.SetPosition(Vector3f(1.0, 2.0, 3.0));
|
||||||
// Rotate 90 degrees around X: Local Y becomes World Z, Local Z becomes World -Y
|
// Rotate 90 degrees around X: Local Y becomes World Z, Local Z becomes World -Y
|
||||||
cyl.Rotate(M_PI/2.0, Vector3f(1, 0, 0));
|
cyl.Rotate(M_PI/2.0, Vector3f(1, 0, 0));
|
||||||
|
|||||||
@@ -432,9 +432,9 @@ void init_math(py::module_ &m) {
|
|||||||
.def("AddPoint", &TriangleMesh::AddPoint)
|
.def("AddPoint", &TriangleMesh::AddPoint)
|
||||||
.def("AddTriangle",
|
.def("AddTriangle",
|
||||||
py::overload_cast<const Vector3i &>(&TriangleMesh::AddTriangle))
|
py::overload_cast<const Vector3i &>(&TriangleMesh::AddTriangle))
|
||||||
.def("Points", &TriangleMesh::Points,
|
.def("Points", py::overload_cast<>(&TriangleMesh::Points),
|
||||||
py::return_value_policy::reference_internal)
|
py::return_value_policy::reference_internal)
|
||||||
.def("Triangles", &TriangleMesh::Triangles,
|
.def("Triangles", py::overload_cast<>(&TriangleMesh::Triangles),
|
||||||
py::return_value_policy::reference_internal)
|
py::return_value_policy::reference_internal)
|
||||||
.def("GetTriangle", &TriangleMesh::GetTriangle)
|
.def("GetTriangle", &TriangleMesh::GetTriangle)
|
||||||
.def("GetNormal", &TriangleMesh::GetNormal);
|
.def("GetNormal", &TriangleMesh::GetNormal);
|
||||||
@@ -444,9 +444,9 @@ void init_math(py::module_ &m) {
|
|||||||
.def("AddPoint", &QuadMesh::AddPoint)
|
.def("AddPoint", &QuadMesh::AddPoint)
|
||||||
.def("AddQuad",
|
.def("AddQuad",
|
||||||
py::overload_cast<const Vector4i &>(&QuadMesh::AddQuad))
|
py::overload_cast<const Vector4i &>(&QuadMesh::AddQuad))
|
||||||
.def("Points", &QuadMesh::Points,
|
.def("Points", py::overload_cast<>(&QuadMesh::Points),
|
||||||
py::return_value_policy::reference_internal)
|
py::return_value_policy::reference_internal)
|
||||||
.def("Quads", &QuadMesh::Quads,
|
.def("Quads", py::overload_cast<>(&QuadMesh::Quads),
|
||||||
py::return_value_policy::reference_internal)
|
py::return_value_policy::reference_internal)
|
||||||
.def("GetQuad", &QuadMesh::GetQuad)
|
.def("GetQuad", &QuadMesh::GetQuad)
|
||||||
.def("GetNormal", &QuadMesh::GetNormal);
|
.def("GetNormal", &QuadMesh::GetNormal);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "QCanvas.h"
|
#include "QCanvas.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -40,8 +41,14 @@ QCanvas::QCanvas(QWidget *parent) : QWidget(parent), m_canvas(nullptr) {
|
|||||||
if (!gApplication) {
|
if (!gApplication) {
|
||||||
static int argc = 1;
|
static int argc = 1;
|
||||||
static char* argv[] = {(char*)"App", nullptr};
|
static char* argv[] = {(char*)"App", nullptr};
|
||||||
|
if (std::getenv("CTEST_PROJECT_NAME")) {
|
||||||
|
static int bargc = 2;
|
||||||
|
static char* bargv[] = {(char*)"App", (char*)"-b", nullptr};
|
||||||
|
new TApplication("App", &bargc, bargv);
|
||||||
|
} else {
|
||||||
new TApplication("App", &argc, argv);
|
new TApplication("App", &argc, argv);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create the TCanvas associated with this QWidget
|
// Create the TCanvas associated with this QWidget
|
||||||
m_canvas = nullptr;
|
m_canvas = nullptr;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <TCanvas.h>
|
#include <TCanvas.h>
|
||||||
#include <TRandom.h>
|
#include <TRandom.h>
|
||||||
#include <TEnv.h>
|
#include <TEnv.h>
|
||||||
|
#include <cstdlib>
|
||||||
#include "Root/QCanvas.h"
|
#include "Root/QCanvas.h"
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
@@ -37,10 +38,11 @@ int main(int argc, char **argv) {
|
|||||||
} else {
|
} else {
|
||||||
std::cerr << "FAIL: Canvas is still NULL after show() and processEvents()" << std::endl;
|
std::cerr << "FAIL: Canvas is still NULL after show() and processEvents()" << std::endl;
|
||||||
}
|
}
|
||||||
|
if (std::getenv("CTEST_PROJECT_NAME")) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
return app.exec();
|
return app.exec();
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
int main() { return 0; }
|
int main() { return 0; }
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -5,11 +5,19 @@
|
|||||||
|
|
||||||
set(HEP_GEANT_SOURCES
|
set(HEP_GEANT_SOURCES
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/vtkGeantEvent.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/vtkGeantEvent.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/vtkGeantSolid.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/vtkBoxSolid.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/vtkTessellatedSolid.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/vtkGeantScene.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/vtkEmitterPrimary.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/vtkEmitterPrimary.cpp
|
||||||
PARENT_SCOPE)
|
PARENT_SCOPE)
|
||||||
|
|
||||||
set(HEP_GEANT_HEADERS
|
set(HEP_GEANT_HEADERS
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/vtkGeantEvent.h
|
${CMAKE_CURRENT_SOURCE_DIR}/vtkGeantEvent.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/vtkGeantSolid.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/vtkBoxSolid.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/vtkTessellatedSolid.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/vtkGeantScene.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/vtkEmitterPrimary.h
|
${CMAKE_CURRENT_SOURCE_DIR}/vtkEmitterPrimary.h
|
||||||
PARENT_SCOPE)
|
PARENT_SCOPE)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
# TESTS
|
# TESTS
|
||||||
set(TESTS
|
set(TESTS
|
||||||
vtkGeantEventTest
|
vtkGeantEventTest
|
||||||
|
vtkGeantSceneTest
|
||||||
|
vtkSolidsTest
|
||||||
vtkEmitterPrimaryTest
|
vtkEmitterPrimaryTest
|
||||||
vtkSkyPlaneEmitterPrimaryTest
|
vtkSkyPlaneEmitterPrimaryTest
|
||||||
vtkCylinderEmitterPrimaryTest
|
vtkCylinderEmitterPrimaryTest
|
||||||
|
|||||||
65
src/Vtk/HEP/Geant/testing/vtkGeantSceneTest.cpp
Normal file
65
src/Vtk/HEP/Geant/testing/vtkGeantSceneTest.cpp
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
/*//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// 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 "Geant/Solid.h"
|
||||||
|
#include "HEP/Geant/Scene.h"
|
||||||
|
#include "Math/ContainerBox.h"
|
||||||
|
#include "Math/Dense.h"
|
||||||
|
#include "Math/Units.h"
|
||||||
|
#include "Vtk/uLibVtkViewer.h"
|
||||||
|
#include "Vtk/HEP/Geant/vtkGeantScene.h"
|
||||||
|
|
||||||
|
#include <vtkSmartPointer.h>
|
||||||
|
#include <vtkRenderer.h>
|
||||||
|
#include <vtkRenderWindow.h>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
using namespace uLib;
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
bool interactive = (argc > 1 && std::string(argv[1]) == "-i");
|
||||||
|
|
||||||
|
// 1. Setup Geant4 Scene
|
||||||
|
Geant::Scene scene;
|
||||||
|
scene.ConstructWorldBox(Vector3f(30_m, 30_m, 30_m), "G4_AIR");
|
||||||
|
|
||||||
|
// Add an iron cube inside the world
|
||||||
|
ContainerBox iron_box;
|
||||||
|
iron_box.Scale(Vector3f(10_m, 10_m, 10_m));
|
||||||
|
iron_box.SetPosition(Vector3f(-5_m, -5_m, -5_m));
|
||||||
|
Geant::BoxSolid* iron_cube = new Geant::BoxSolid("IronCube", &iron_box);
|
||||||
|
iron_cube->SetNistMaterial("G4_Fe");
|
||||||
|
iron_cube->Update();
|
||||||
|
scene.AddSolid(iron_cube);
|
||||||
|
scene.Initialize();
|
||||||
|
|
||||||
|
// 2. Build VTK scene representation
|
||||||
|
Vtk::Viewer viewer;
|
||||||
|
viewer.GetRenderer()->SetBackground(0.05, 0.05, 0.1);
|
||||||
|
|
||||||
|
Vtk::vtkGeantScene vtkScene(&scene);
|
||||||
|
vtkScene.AddToViewer(viewer);
|
||||||
|
|
||||||
|
std::cout << "==================================================" << std::endl;
|
||||||
|
std::cout << " vtkGeantScene Test" << std::endl;
|
||||||
|
std::cout << " World box + 1 iron cube displayed" << std::endl;
|
||||||
|
std::cout << "==================================================" << std::endl;
|
||||||
|
|
||||||
|
if (interactive) {
|
||||||
|
viewer.ZoomAuto();
|
||||||
|
viewer.Start();
|
||||||
|
} else {
|
||||||
|
std::cout << "Non-interactive test: scene initialized successfully" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
84
src/Vtk/HEP/Geant/testing/vtkSolidsTest.cpp
Normal file
84
src/Vtk/HEP/Geant/testing/vtkSolidsTest.cpp
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
/*//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// 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 "Vtk/HEP/Geant/vtkBoxSolid.h"
|
||||||
|
#include "Vtk/HEP/Geant/vtkTessellatedSolid.h"
|
||||||
|
#include "Vtk/uLibVtkViewer.h"
|
||||||
|
#include "HEP/Geant/Solid.h"
|
||||||
|
#include "Math/ContainerBox.h"
|
||||||
|
#include "Math/TriangleMesh.h"
|
||||||
|
#include "Math/Units.h"
|
||||||
|
|
||||||
|
#include <vtkProperty.h>
|
||||||
|
#include <vtkActor.h>
|
||||||
|
|
||||||
|
using namespace uLib;
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
bool interactive = (argc > 1 && std::string(argv[1]) == "-i");
|
||||||
|
|
||||||
|
// 1. Create a BoxSolid
|
||||||
|
ContainerBox box;
|
||||||
|
box.Scale(Vector3f(1_m, 2_m, 3_m));
|
||||||
|
Geant::BoxSolid gBox("MyBox", &box);
|
||||||
|
gBox.Update();
|
||||||
|
|
||||||
|
Vtk::vtkBoxSolid vtkBox(&gBox);
|
||||||
|
|
||||||
|
// 2. Create a TessellatedSolid
|
||||||
|
Geant::TessellatedSolid gTess("MyTess");
|
||||||
|
TriangleMesh mesh;
|
||||||
|
// Create a simple pyramid
|
||||||
|
mesh.Points().push_back(Vector3f(0, 0, 1_m)); // apex
|
||||||
|
mesh.Points().push_back(Vector3f(-1_m, -1_m, 0));
|
||||||
|
mesh.Points().push_back(Vector3f( 1_m, -1_m, 0));
|
||||||
|
mesh.Points().push_back(Vector3f( 1_m, 1_m, 0));
|
||||||
|
mesh.Points().push_back(Vector3f(-1_m, 1_m, 0));
|
||||||
|
|
||||||
|
mesh.Triangles().push_back(Vector3i(1, 2, 0));
|
||||||
|
mesh.Triangles().push_back(Vector3i(2, 3, 0));
|
||||||
|
mesh.Triangles().push_back(Vector3i(3, 4, 0));
|
||||||
|
mesh.Triangles().push_back(Vector3i(4, 1, 0));
|
||||||
|
mesh.Triangles().push_back(Vector3i(1, 3, 2)); // base
|
||||||
|
mesh.Triangles().push_back(Vector3i(1, 4, 3)); // base
|
||||||
|
|
||||||
|
gTess.SetMesh(mesh);
|
||||||
|
gTess.Update();
|
||||||
|
|
||||||
|
Vtk::vtkTessellatedSolid vtkTess(&gTess);
|
||||||
|
|
||||||
|
// 3. Visualization setup
|
||||||
|
Vtk::Viewer viewer;
|
||||||
|
vtkBox.AddToViewer(viewer);
|
||||||
|
vtkTess.AddToViewer(viewer);
|
||||||
|
|
||||||
|
// Color them differently
|
||||||
|
vtkActor::SafeDownCast(vtkBox.GetProp())->GetProperty()->SetColor(0.8, 0.2, 0.2); // Redish box
|
||||||
|
vtkActor::SafeDownCast(vtkTess.GetProp())->GetProperty()->SetColor(0.2, 0.8, 0.2); // Greenish tess
|
||||||
|
|
||||||
|
// Position tessellated solid away from box
|
||||||
|
Matrix4f trans = Matrix4f::Identity();
|
||||||
|
trans.block<3,1>(0,3) = Vector3f(5_m, 0, 0);
|
||||||
|
gTess.SetTransform(trans);
|
||||||
|
vtkTess.Update();
|
||||||
|
|
||||||
|
std::cout << "..:: Testing vtkSolidsTest ::.." << std::endl;
|
||||||
|
std::cout << "Box and Tessellated solids initialized." << std::endl;
|
||||||
|
|
||||||
|
if (interactive) {
|
||||||
|
viewer.ZoomAuto();
|
||||||
|
viewer.Start();
|
||||||
|
} else {
|
||||||
|
std::cout << "Non-interactive test passed." << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
57
src/Vtk/HEP/Geant/vtkBoxSolid.cpp
Normal file
57
src/Vtk/HEP/Geant/vtkBoxSolid.cpp
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
/*//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// CMT Cosmic Muon Tomography project //////////////////////////////////////////
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
Copyright (c) 2014, Universita' degli Studi Padova, INFN sez. di Padova
|
||||||
|
All rights reserved
|
||||||
|
|
||||||
|
Authors: Andrea Rigoni Garola < andrea.rigoni@pd.infn.it >
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
#include "vtkBoxSolid.h"
|
||||||
|
#include <vtkCubeSource.h>
|
||||||
|
#include <vtkPolyDataMapper.h>
|
||||||
|
#include <vtkActor.h>
|
||||||
|
|
||||||
|
namespace uLib {
|
||||||
|
namespace Vtk {
|
||||||
|
|
||||||
|
vtkBoxSolid::vtkBoxSolid(Geant::BoxSolid *content)
|
||||||
|
: vtkGeantSolid(content), m_BoxContent(content) {
|
||||||
|
// Re-run Update for box-specific pipe
|
||||||
|
this->Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
vtkBoxSolid::~vtkBoxSolid() {}
|
||||||
|
|
||||||
|
void vtkBoxSolid::Update() {
|
||||||
|
this->UpdateGeometry();
|
||||||
|
this->UpdateTransform();
|
||||||
|
}
|
||||||
|
|
||||||
|
void vtkBoxSolid::UpdateGeometry() {
|
||||||
|
if (!m_BoxContent || !m_BoxContent->GetObject()) {
|
||||||
|
// Fallback to base tessellation if no model object
|
||||||
|
vtkGeantSolid::UpdateGeometry();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the underlying ContainerBox for precise geometry
|
||||||
|
Vector3f size = m_BoxContent->GetObject()->GetSize();
|
||||||
|
|
||||||
|
vtkNew<vtkCubeSource> cube;
|
||||||
|
cube->SetXLength(size(0));
|
||||||
|
cube->SetYLength(size(1));
|
||||||
|
cube->SetZLength(size(2));
|
||||||
|
cube->Update();
|
||||||
|
|
||||||
|
vtkPolyData *poly = GetPolyData();
|
||||||
|
if (poly) {
|
||||||
|
poly->ShallowCopy(cube->GetOutput());
|
||||||
|
poly->Modified();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Vtk
|
||||||
|
} // namespace uLib
|
||||||
52
src/Vtk/HEP/Geant/vtkBoxSolid.h
Normal file
52
src/Vtk/HEP/Geant/vtkBoxSolid.h
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
/*//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// 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 U_VTKBOXSOLID_H
|
||||||
|
#define U_VTKBOXSOLID_H
|
||||||
|
|
||||||
|
#include "vtkGeantSolid.h"
|
||||||
|
|
||||||
|
namespace uLib {
|
||||||
|
namespace Vtk {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief VTK Puppet for visualizing a Geant::BoxSolid.
|
||||||
|
*/
|
||||||
|
class vtkBoxSolid : public vtkGeantSolid {
|
||||||
|
public:
|
||||||
|
vtkBoxSolid(Geant::BoxSolid *content);
|
||||||
|
virtual ~vtkBoxSolid();
|
||||||
|
|
||||||
|
virtual void Update() override;
|
||||||
|
virtual void UpdateGeometry() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Geant::BoxSolid *m_BoxContent;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Vtk
|
||||||
|
} // namespace uLib
|
||||||
|
|
||||||
|
#endif // U_VTKBOXSOLID_H
|
||||||
102
src/Vtk/HEP/Geant/vtkGeantScene.cpp
Normal file
102
src/Vtk/HEP/Geant/vtkGeantScene.cpp
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
/*//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
#include "vtkGeantScene.h"
|
||||||
|
#include "vtkGeantSolid.h"
|
||||||
|
#include "vtkBoxSolid.h"
|
||||||
|
#include "vtkTessellatedSolid.h"
|
||||||
|
#include "Vtk/vtkViewport.h"
|
||||||
|
|
||||||
|
namespace uLib {
|
||||||
|
namespace Vtk {
|
||||||
|
|
||||||
|
vtkGeantScene::vtkGeantScene(Geant::Scene *scene)
|
||||||
|
: m_Scene(scene), m_WorldPuppet(nullptr) {
|
||||||
|
if (!m_Scene)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// 1. Create the world box wireframe puppet
|
||||||
|
ContainerBox *worldBox = m_Scene->GetWorldBox();
|
||||||
|
if (worldBox) {
|
||||||
|
m_WorldPuppet = new vtkContainerBox(worldBox);
|
||||||
|
m_WorldPuppet->SetRepresentation(Puppet::Wireframe);
|
||||||
|
m_WorldPuppet->ShowScaleMeasures(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Create puppets for each non-world solid
|
||||||
|
const Geant::Solid *world = m_Scene->GetWorld();
|
||||||
|
const Vector<Geant::Solid *> &solids = m_Scene->GetSolids();
|
||||||
|
|
||||||
|
for (Geant::Solid *solid : solids) {
|
||||||
|
// Skip the world volume itself — it's already shown as the wireframe box
|
||||||
|
if (solid == world)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Only create a puppet if the solid has a valid G4VSolid
|
||||||
|
if (solid->GetG4Solid()) {
|
||||||
|
vtkGeantSolid *vtkSolid = nullptr;
|
||||||
|
if (auto *box = dynamic_cast<Geant::BoxSolid *>(solid)) {
|
||||||
|
vtkSolid = new vtkBoxSolid(box);
|
||||||
|
} else if (auto *tess = dynamic_cast<Geant::TessellatedSolid *>(solid)) {
|
||||||
|
vtkSolid = new vtkTessellatedSolid(tess);
|
||||||
|
} else {
|
||||||
|
vtkSolid = new vtkGeantSolid(solid);
|
||||||
|
vtkSolid->Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vtkSolid) {
|
||||||
|
m_SolidPuppets.push_back(vtkSolid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vtkGeantScene::~vtkGeantScene() {
|
||||||
|
delete m_WorldPuppet;
|
||||||
|
for (auto *p : m_SolidPuppets) {
|
||||||
|
delete p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void vtkGeantScene::AddToViewer(Viewport &viewer) {
|
||||||
|
if (m_WorldPuppet) {
|
||||||
|
viewer.AddPuppet(*m_WorldPuppet);
|
||||||
|
}
|
||||||
|
for (auto *p : m_SolidPuppets) {
|
||||||
|
viewer.AddPuppet(*p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void vtkGeantScene::RemoveFromViewer(Viewport &viewer) {
|
||||||
|
if (m_WorldPuppet) {
|
||||||
|
viewer.RemovePuppet(*m_WorldPuppet);
|
||||||
|
}
|
||||||
|
for (auto *p : m_SolidPuppets) {
|
||||||
|
viewer.RemovePuppet(*p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Vtk
|
||||||
|
} // namespace uLib
|
||||||
87
src/Vtk/HEP/Geant/vtkGeantScene.h
Normal file
87
src/Vtk/HEP/Geant/vtkGeantScene.h
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
/*//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// 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 U_VTKGEANTSCENE_H
|
||||||
|
#define U_VTKGEANTSCENE_H
|
||||||
|
|
||||||
|
#include "HEP/Geant/Scene.h"
|
||||||
|
#include "uLibVtkInterface.h"
|
||||||
|
#include "Vtk/Math/vtkContainerBox.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace uLib {
|
||||||
|
namespace Vtk {
|
||||||
|
|
||||||
|
class vtkGeantSolid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief VTK Puppet representing the entire Geant::Scene.
|
||||||
|
*
|
||||||
|
* When constructed, it creates child puppets for the world box (as a
|
||||||
|
* vtkContainerBox wireframe) and for each non-world Solid in the scene
|
||||||
|
* (as vtkGeantSolid surfaces).
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* @code
|
||||||
|
* Geant::Scene scene;
|
||||||
|
* scene.ConstructWorldBox(Vector3f(30_m, 30_m, 30_m), "G4_AIR");
|
||||||
|
* // ... add solids ...
|
||||||
|
* scene.Initialize();
|
||||||
|
*
|
||||||
|
* Vtk::Viewer viewer;
|
||||||
|
* Vtk::vtkGeantScene vtkScene(&scene);
|
||||||
|
* vtkScene.AddToViewer(viewer);
|
||||||
|
* viewer.Start();
|
||||||
|
* @endcode
|
||||||
|
*/
|
||||||
|
class vtkGeantScene : public Object {
|
||||||
|
public:
|
||||||
|
vtkGeantScene(Geant::Scene *scene);
|
||||||
|
~vtkGeantScene();
|
||||||
|
|
||||||
|
/// Add all puppets (world box + solids) to a viewer.
|
||||||
|
void AddToViewer(class Viewport &viewer);
|
||||||
|
|
||||||
|
/// Remove all puppets from viewport.
|
||||||
|
void RemoveFromViewer(class Viewport &viewer);
|
||||||
|
|
||||||
|
/// Get the world box puppet
|
||||||
|
vtkContainerBox* GetWorldPuppet() const { return m_WorldPuppet; }
|
||||||
|
|
||||||
|
/// Get the solid puppets
|
||||||
|
const std::vector<vtkGeantSolid*>& GetSolidPuppets() const { return m_SolidPuppets; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
Geant::Scene *m_Scene;
|
||||||
|
vtkContainerBox *m_WorldPuppet;
|
||||||
|
std::vector<vtkGeantSolid*> m_SolidPuppets;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Vtk
|
||||||
|
} // namespace uLib
|
||||||
|
|
||||||
|
#endif // U_VTKGEANTSCENE_H
|
||||||
165
src/Vtk/HEP/Geant/vtkGeantSolid.cpp
Normal file
165
src/Vtk/HEP/Geant/vtkGeantSolid.cpp
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
/*//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
#include "vtkGeantSolid.h"
|
||||||
|
|
||||||
|
#include <vtkActor.h>
|
||||||
|
#include <vtkPolyData.h>
|
||||||
|
#include <vtkPoints.h>
|
||||||
|
#include <vtkCellArray.h>
|
||||||
|
#include <vtkPolyDataMapper.h>
|
||||||
|
#include <vtkProperty.h>
|
||||||
|
#include <vtkSmartPointer.h>
|
||||||
|
#include <vtkTransform.h>
|
||||||
|
#include <vtkMatrix4x4.h>
|
||||||
|
|
||||||
|
#include <Geant4/G4VSolid.hh>
|
||||||
|
#include <Geant4/G4Polyhedron.hh>
|
||||||
|
#include <Geant4/G4VPhysicalVolume.hh>
|
||||||
|
|
||||||
|
namespace uLib {
|
||||||
|
namespace Vtk {
|
||||||
|
|
||||||
|
vtkGeantSolid::vtkGeantSolid(Content *content)
|
||||||
|
: m_SolidActor(vtkActor::New()), m_Content(content) {
|
||||||
|
this->InstallPipe();
|
||||||
|
}
|
||||||
|
|
||||||
|
vtkGeantSolid::~vtkGeantSolid() {
|
||||||
|
m_SolidActor->Delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
vtkPolyData *vtkGeantSolid::GetPolyData() const {
|
||||||
|
if (!m_SolidActor || !m_SolidActor->GetMapper())
|
||||||
|
return NULL;
|
||||||
|
return vtkPolyData::SafeDownCast(m_SolidActor->GetMapper()->GetInput());
|
||||||
|
}
|
||||||
|
|
||||||
|
void vtkGeantSolid::Update() {
|
||||||
|
this->UpdateGeometry();
|
||||||
|
this->UpdateTransform();
|
||||||
|
}
|
||||||
|
|
||||||
|
void vtkGeantSolid::UpdateGeometry() {
|
||||||
|
if (!m_Content)
|
||||||
|
return;
|
||||||
|
|
||||||
|
G4VSolid *g4solid = m_Content->GetG4Solid();
|
||||||
|
if (!g4solid)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Get the polyhedron tessellation from Geant4
|
||||||
|
G4Polyhedron *polyhedron = g4solid->GetPolyhedron();
|
||||||
|
if (!polyhedron)
|
||||||
|
return;
|
||||||
|
|
||||||
|
vtkSmartPointer<vtkPoints> points = vtkSmartPointer<vtkPoints>::New();
|
||||||
|
vtkSmartPointer<vtkCellArray> polys = vtkSmartPointer<vtkCellArray>::New();
|
||||||
|
|
||||||
|
// Extract vertices
|
||||||
|
int nVertices = polyhedron->GetNoVertices();
|
||||||
|
for (int i = 1; i <= nVertices; ++i) {
|
||||||
|
G4Point3D vtx = polyhedron->GetVertex(i);
|
||||||
|
points->InsertNextPoint(vtx.x(), vtx.y(), vtx.z());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract facets (polygons)
|
||||||
|
int nFacets = polyhedron->GetNoFacets();
|
||||||
|
for (int f = 1; f <= nFacets; ++f) {
|
||||||
|
G4int nEdges;
|
||||||
|
G4int iVertex[4]; // G4Polyhedron facets have at most 4 vertices
|
||||||
|
// GetNextFacet returns edges; for quads nEdges=4, for triangles nEdges=3
|
||||||
|
polyhedron->GetFacet(f, nEdges, iVertex);
|
||||||
|
|
||||||
|
vtkIdType ids[4];
|
||||||
|
for (int e = 0; e < nEdges; ++e) {
|
||||||
|
// G4Polyhedron vertices are 1-indexed; VTK expects 0-indexed
|
||||||
|
ids[e] = static_cast<vtkIdType>(std::abs(iVertex[e]) - 1);
|
||||||
|
}
|
||||||
|
polys->InsertNextCell(nEdges, ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
vtkPolyData *polyData = GetPolyData();
|
||||||
|
if (polyData) {
|
||||||
|
polyData->SetPoints(points);
|
||||||
|
polyData->SetPolys(polys);
|
||||||
|
polyData->Modified();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void vtkGeantSolid::UpdateTransform() {
|
||||||
|
if (!m_Content || !m_SolidActor)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Apply the Geant4 transform (position/rotation) if placed
|
||||||
|
if (m_Content->GetPhysical()) {
|
||||||
|
auto *phys = m_Content->GetPhysical();
|
||||||
|
G4ThreeVector pos = phys->GetTranslation();
|
||||||
|
const G4RotationMatrix *rot = phys->GetRotation();
|
||||||
|
|
||||||
|
vtkSmartPointer<vtkTransform> transform = vtkSmartPointer<vtkTransform>::New();
|
||||||
|
transform->Identity();
|
||||||
|
transform->Translate(pos.x(), pos.y(), pos.z());
|
||||||
|
|
||||||
|
if (rot) {
|
||||||
|
// G4RotationMatrix stores the inverse of the rotation for placement
|
||||||
|
G4RotationMatrix invRot = rot->inverse();
|
||||||
|
double elements[16] = {
|
||||||
|
invRot.xx(), invRot.xy(), invRot.xz(), 0,
|
||||||
|
invRot.yx(), invRot.yy(), invRot.yz(), 0,
|
||||||
|
invRot.zx(), invRot.zy(), invRot.zz(), 0,
|
||||||
|
0, 0, 0, 1
|
||||||
|
};
|
||||||
|
vtkSmartPointer<vtkMatrix4x4> mat = vtkSmartPointer<vtkMatrix4x4>::New();
|
||||||
|
mat->DeepCopy(elements);
|
||||||
|
transform->Concatenate(mat);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_SolidActor->SetUserTransform(transform);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void vtkGeantSolid::InstallPipe() {
|
||||||
|
vtkSmartPointer<vtkPolyData> polyData = vtkSmartPointer<vtkPolyData>::New();
|
||||||
|
vtkSmartPointer<vtkPolyDataMapper> mapper =
|
||||||
|
vtkSmartPointer<vtkPolyDataMapper>::New();
|
||||||
|
mapper->SetInputData(polyData);
|
||||||
|
|
||||||
|
m_SolidActor->SetMapper(mapper);
|
||||||
|
|
||||||
|
// Default look: semi-transparent blue surface
|
||||||
|
m_SolidActor->GetProperty()->SetColor(0.4, 0.6, 0.9);
|
||||||
|
m_SolidActor->GetProperty()->SetOpacity(0.3);
|
||||||
|
m_SolidActor->GetProperty()->SetAmbient(0.5);
|
||||||
|
m_SolidActor->GetProperty()->SetDiffuse(0.6);
|
||||||
|
m_SolidActor->GetProperty()->SetSpecular(0.2);
|
||||||
|
m_SolidActor->GetProperty()->SetEdgeVisibility(1);
|
||||||
|
m_SolidActor->GetProperty()->SetEdgeColor(0.2, 0.3, 0.5);
|
||||||
|
|
||||||
|
this->SetProp(m_SolidActor);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Vtk
|
||||||
|
} // namespace uLib
|
||||||
69
src/Vtk/HEP/Geant/vtkGeantSolid.h
Normal file
69
src/Vtk/HEP/Geant/vtkGeantSolid.h
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
/*//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// 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 U_VTKGEANTSOLID_H
|
||||||
|
#define U_VTKGEANTSOLID_H
|
||||||
|
|
||||||
|
#include "HEP/Geant/Solid.h"
|
||||||
|
#include "uLibVtkInterface.h"
|
||||||
|
#include "vtkPolydata.h"
|
||||||
|
|
||||||
|
class vtkActor;
|
||||||
|
|
||||||
|
namespace uLib {
|
||||||
|
namespace Vtk {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief VTK Puppet for visualizing a Geant::Solid.
|
||||||
|
*
|
||||||
|
* Renders the G4VSolid geometry as a tessellated polydata surface.
|
||||||
|
* Works with BoxSolid, TessellatedSolid, or any Solid that provides
|
||||||
|
* a valid G4VSolid via GetG4Solid().
|
||||||
|
*/
|
||||||
|
class vtkGeantSolid : public Puppet, public Polydata {
|
||||||
|
typedef Geant::Solid Content;
|
||||||
|
|
||||||
|
public:
|
||||||
|
vtkGeantSolid(Content *content);
|
||||||
|
~vtkGeantSolid();
|
||||||
|
|
||||||
|
virtual class vtkPolyData *GetPolyData() const override;
|
||||||
|
|
||||||
|
virtual void Update() override;
|
||||||
|
|
||||||
|
virtual void UpdateGeometry();
|
||||||
|
virtual void UpdateTransform();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void InstallPipe();
|
||||||
|
|
||||||
|
vtkActor *m_SolidActor;
|
||||||
|
Content *m_Content;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Vtk
|
||||||
|
} // namespace uLib
|
||||||
|
|
||||||
|
#endif // U_VTKGEANTSOLID_H
|
||||||
62
src/Vtk/HEP/Geant/vtkTessellatedSolid.cpp
Normal file
62
src/Vtk/HEP/Geant/vtkTessellatedSolid.cpp
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
/*//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// CMT Cosmic Muon Tomography project //////////////////////////////////////////
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
Copyright (c) 2014, Universita' degli Studi Padova, INFN sez. di Padova
|
||||||
|
All rights reserved
|
||||||
|
|
||||||
|
Authors: Andrea Rigoni Garola < andrea.rigoni@pd.infn.it >
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
#include "vtkTessellatedSolid.h"
|
||||||
|
|
||||||
|
#include <vtkPoints.h>
|
||||||
|
#include <vtkCellArray.h>
|
||||||
|
#include <vtkPolyData.h>
|
||||||
|
|
||||||
|
namespace uLib {
|
||||||
|
namespace Vtk {
|
||||||
|
|
||||||
|
vtkTessellatedSolid::vtkTessellatedSolid(Geant::TessellatedSolid *content)
|
||||||
|
: vtkGeantSolid(content), m_TessContent(content) {
|
||||||
|
this->Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
vtkTessellatedSolid::~vtkTessellatedSolid() {}
|
||||||
|
|
||||||
|
void vtkTessellatedSolid::Update() {
|
||||||
|
this->UpdateGeometry();
|
||||||
|
this->UpdateTransform();
|
||||||
|
}
|
||||||
|
|
||||||
|
void vtkTessellatedSolid::UpdateGeometry() {
|
||||||
|
if (!m_TessContent || m_TessContent->GetMesh().Points().empty()) {
|
||||||
|
// Fallback to base tessellation if no model mesh
|
||||||
|
vtkGeantSolid::UpdateGeometry();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TriangleMesh &mesh = m_TessContent->GetMesh();
|
||||||
|
|
||||||
|
vtkNew<vtkPoints> points;
|
||||||
|
for (const auto& pt : mesh.Points()) {
|
||||||
|
points->InsertNextPoint(pt(0), pt(1), pt(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
vtkNew<vtkCellArray> polys;
|
||||||
|
for (const auto& trg : mesh.Triangles()) {
|
||||||
|
vtkIdType ids[3] = { (vtkIdType)trg(0), (vtkIdType)trg(1), (vtkIdType)trg(2) };
|
||||||
|
polys->InsertNextCell(3, ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
vtkPolyData *poly = GetPolyData();
|
||||||
|
if (poly) {
|
||||||
|
poly->SetPoints(points);
|
||||||
|
poly->SetPolys(polys);
|
||||||
|
poly->Modified();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Vtk
|
||||||
|
} // namespace uLib
|
||||||
38
src/Vtk/HEP/Geant/vtkTessellatedSolid.h
Normal file
38
src/Vtk/HEP/Geant/vtkTessellatedSolid.h
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
/*//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// 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 >
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
#ifndef U_VTKTESSELLATEDSOLID_H
|
||||||
|
#define U_VTKTESSELLATEDSOLID_H
|
||||||
|
|
||||||
|
#include "vtkGeantSolid.h"
|
||||||
|
|
||||||
|
namespace uLib {
|
||||||
|
namespace Vtk {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief VTK Puppet for visualizing a Geant::TessellatedSolid.
|
||||||
|
*/
|
||||||
|
class vtkTessellatedSolid : public vtkGeantSolid {
|
||||||
|
public:
|
||||||
|
vtkTessellatedSolid(Geant::TessellatedSolid *content);
|
||||||
|
virtual ~vtkTessellatedSolid();
|
||||||
|
|
||||||
|
virtual void Update() override;
|
||||||
|
virtual void UpdateGeometry() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Geant::TessellatedSolid *m_TessContent;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Vtk
|
||||||
|
} // namespace uLib
|
||||||
|
|
||||||
|
#endif // U_VTKTESSELLATEDSOLID_H
|
||||||
@@ -10,6 +10,7 @@ set(MATH_SOURCES
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/vtkVoxImage.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/vtkVoxImage.cpp
|
||||||
${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
|
||||||
PARENT_SCOPE)
|
PARENT_SCOPE)
|
||||||
|
|
||||||
set(MATH_HEADERS
|
set(MATH_HEADERS
|
||||||
@@ -20,6 +21,7 @@ set(MATH_HEADERS
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/vtkVoxImage.h
|
${CMAKE_CURRENT_SOURCE_DIR}/vtkVoxImage.h
|
||||||
${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
|
||||||
PARENT_SCOPE)
|
PARENT_SCOPE)
|
||||||
|
|
||||||
if(BUILD_TESTING)
|
if(BUILD_TESTING)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ set(TESTS
|
|||||||
vtkVoxImageInteractiveTest
|
vtkVoxImageInteractiveTest
|
||||||
vtkContainerBoxTest
|
vtkContainerBoxTest
|
||||||
vtkContainerBoxTest2
|
vtkContainerBoxTest2
|
||||||
|
vtkAssemblyTest
|
||||||
)
|
)
|
||||||
|
|
||||||
set(LIBRARIES
|
set(LIBRARIES
|
||||||
|
|||||||
@@ -1,109 +1,104 @@
|
|||||||
/*//////////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////////
|
||||||
// CMT Cosmic Muon Tomography project //////////////////////////////////////////
|
// CMT Cosmic Muon Tomography project //////////////////////////////////////////
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
// Copyright (c) 2014, Universita' degli Studi di Padova, INFN sez. di Padova
|
|
||||||
// All rights reserved
|
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 "Vtk/uLibVtkViewer.h"
|
|
||||||
#include "Vtk/Math/vtkAssembly.h"
|
|
||||||
#include "Vtk/Math/vtkCylinder.h"
|
|
||||||
#include "Vtk/Math/vtkContainerBox.h"
|
|
||||||
#include "Math/Assembly.h"
|
#include "Math/Assembly.h"
|
||||||
#include "Math/Cylinder.h"
|
|
||||||
#include "Math/ContainerBox.h"
|
#include "Math/ContainerBox.h"
|
||||||
|
#include "Math/Cylinder.h"
|
||||||
|
#include "Vtk/Math/vtkAssembly.h"
|
||||||
|
#include "Vtk/vtkObjectsContext.h"
|
||||||
|
#include "Vtk/uLibVtkViewer.h"
|
||||||
#include "Math/Units.h"
|
#include "Math/Units.h"
|
||||||
|
|
||||||
|
#include <vtkActor.h>
|
||||||
|
#include <vtkProperty.h>
|
||||||
|
#include <vtkPropCollection.h>
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
using namespace uLib;
|
using namespace uLib;
|
||||||
|
|
||||||
/**
|
int main(int argc, char **argv) {
|
||||||
* @brief This test verifies that uLib::Vtk::Assembly correctly visualizes a collection
|
bool interactive = (argc > 1 && std::string(argv[1]) == "-i");
|
||||||
* of objects and that transformations applied to the assembly are propagated to its children.
|
|
||||||
* It also checks that the assembly appears as a bounding box of its contents.
|
|
||||||
*/
|
|
||||||
int main() {
|
|
||||||
std::cout << "Starting vtkAssemblyTest..." << std::endl;
|
|
||||||
|
|
||||||
// 1. Setup Core Geometry
|
// ---- 1. Build model objects ----
|
||||||
std::cout << " - Creating core Assembly..." << std::endl;
|
ContainerBox box1;
|
||||||
uLib::Assembly* core_assembly = new uLib::Assembly();
|
box1.Scale(Vector3f(1_m, 2_m, 0.5_m));
|
||||||
core_assembly->SetInstanceName("MainAssembly");
|
box1.SetPosition(Vector3f(0, 0, 0));
|
||||||
|
|
||||||
// Add a box
|
ContainerBox box2;
|
||||||
std::cout << " - Adding ChildBox (Red)..." << std::endl;
|
box2.Scale(Vector3f(0.5_m, 0.5_m, 3_m));
|
||||||
ContainerBox* box = new ContainerBox();
|
box2.SetPosition(Vector3f(2_m, 0, 0));
|
||||||
box->SetInstanceName("ChildBox");
|
|
||||||
box->Translate(Vector3f(1.0, 0, 0));
|
|
||||||
core_assembly->AddObject(box);
|
|
||||||
|
|
||||||
// Add a cylinder
|
Cylinder cyl(0.3_m, 1.5_m, 1);
|
||||||
// std::cout << " - Adding ChildCylinder (Blue)..." << std::endl;
|
cyl.SetPosition(Vector3f(0, 3_m, 0));
|
||||||
// Cylinder* cyl = new Cylinder(0.5, 2.0);
|
|
||||||
// cyl->SetInstanceName("ChildCylinder");
|
|
||||||
// cyl->Translate(Vector3f(-2.0, 0, 0));
|
|
||||||
// core_assembly->AddObject(cyl);
|
|
||||||
|
|
||||||
std::cout << " - Adding another box (Green)..." << std::endl;
|
// ---- 2. Create an Assembly and add objects ----
|
||||||
ContainerBox* box2 = new ContainerBox();
|
Assembly assembly;
|
||||||
box2->Scale(Vector3f(1.0, 1.0, 2.0));
|
assembly.AddObject(&box1);
|
||||||
box2->SetInstanceName("ChildBox2");
|
assembly.AddObject(&box2);
|
||||||
box2->Translate(Vector3f(0, 0, 1.0));
|
assembly.AddObject(&cyl);
|
||||||
core_assembly->AddObject(box2);
|
assembly.SetShowBoundingBox(true);
|
||||||
|
|
||||||
// 2. Setup VTK Representation
|
// ---- 3. Apply a group transform ----
|
||||||
std::cout << " - Creating VTK Assembly representation..." << std::endl;
|
assembly.SetPosition(Vector3f(1_m, 1_m, 0));
|
||||||
Vtk::Assembly vtk_assembly(core_assembly);
|
|
||||||
|
|
||||||
// Find and colorized the children puppets
|
// ---- 5. Visualize (create puppets to set properties) ----
|
||||||
Vtk::Puppet* p_box = vtk_assembly.GetPuppet(box);
|
Vtk::Assembly vtkAsm(&assembly);
|
||||||
if (p_box) {
|
|
||||||
p_box->SetColor(1.0, 0.0, 0.0); // Red
|
|
||||||
} else {
|
|
||||||
std::cerr << "Warning: Could not find puppet for box!" << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Vtk::Puppet* p_cyl = vtk_assembly.GetPuppet(cyl);
|
|
||||||
// if (p_cyl) {
|
|
||||||
// p_cyl->SetColor(0.0, 0.0, 1.0); // Blue
|
|
||||||
// } else {
|
|
||||||
// std::cerr << "Warning: Could not find puppet for cylinder!" << std::endl;
|
|
||||||
// }
|
|
||||||
|
|
||||||
Vtk::Puppet* p_box2 = vtk_assembly.GetPuppet(box2);
|
|
||||||
if (p_box2) {
|
|
||||||
p_box2->SetColor(0.0, 1.0, 0.0); // Green
|
|
||||||
} else {
|
|
||||||
std::cerr << "Warning: Could not find puppet for box2!" << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Test Transformation Propagation
|
|
||||||
std::cout << " - Rotating Assembly 45 degrees around Z..." << std::endl;
|
|
||||||
core_assembly->Rotate(45.0_deg, Vector3f::UnitZ());
|
|
||||||
|
|
||||||
std::cout << " - Translating Assembly up (+Y)..." << std::endl;
|
|
||||||
core_assembly->Translate(Vector3f(0, 2.0, 0));
|
|
||||||
|
|
||||||
// Notify all puppets of the change
|
|
||||||
core_assembly->Updated();
|
|
||||||
|
|
||||||
// 4. Run Visualization
|
|
||||||
std::cout << "Starting viewer (close to exit)..." << std::endl;
|
|
||||||
std::cout << "Expected: A white bounding box containing a red box and a blue cylinder, "
|
|
||||||
<< "all rotated and translated as a group." << std::endl;
|
|
||||||
|
|
||||||
Vtk::Viewer viewer;
|
Vtk::Viewer viewer;
|
||||||
viewer.AddPuppet(*p_box);
|
vtkAsm.AddToViewer(viewer); // This triggers puppet creation via ConnectRenderer which eventually calls Puppet::GetProp
|
||||||
viewer.AddPuppet(*p_box2);
|
|
||||||
viewer.AddPuppet(vtk_assembly);
|
|
||||||
viewer.Start();
|
|
||||||
|
|
||||||
// Clean up
|
// Explicitly update to ensure puppets exist and are added to assemblies
|
||||||
delete core_assembly;
|
vtkAsm.Update();
|
||||||
delete box;
|
|
||||||
delete box2;
|
// Use the child context to find child puppets and set colors
|
||||||
// delete cyl;
|
if (auto* childCtx = vtkAsm.GetChildrenContext()) {
|
||||||
|
auto setProps = [](Vtk::Puppet* p, float r, float g, float b) {
|
||||||
|
if (!p) return;
|
||||||
|
vtkPropCollection* props = p->GetProps();
|
||||||
|
props->InitTraversal();
|
||||||
|
for (int i=0; i < props->GetNumberOfItems(); ++i) {
|
||||||
|
if (auto* actor = vtkActor::SafeDownCast(props->GetNextProp())) {
|
||||||
|
actor->GetProperty()->SetColor(r, g, b);
|
||||||
|
actor->GetProperty()->SetRepresentationToSurface();
|
||||||
|
actor->GetProperty()->SetOpacity(0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
setProps(childCtx->GetPuppet(&box1), 1.0, 0.0, 0.0); // Red
|
||||||
|
setProps(childCtx->GetPuppet(&box2), 0.0, 1.0, 0.0); // Green
|
||||||
|
setProps(childCtx->GetPuppet(&cyl), 0.0, 0.0, 1.0); // Blue
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "Puppets in viewport: " << viewer.getPuppets().size() << " (Expected 4: 1 assembly + 3 children)" << std::endl;
|
||||||
|
|
||||||
|
// ---- 4. Query the bounding box for terminal output ----
|
||||||
|
Vector3f bbMin, bbMax;
|
||||||
|
assembly.GetBoundingBox(bbMin, bbMax);
|
||||||
|
std::cout << "Assembly bounding box:" << std::endl;
|
||||||
|
std::cout << " min = " << bbMin.transpose() << std::endl;
|
||||||
|
std::cout << " max = " << bbMax.transpose() << std::endl;
|
||||||
|
|
||||||
|
std::cout << "==================================================\n";
|
||||||
|
std::cout << " vtkAssemblyTest\n";
|
||||||
|
std::cout << " 2 boxes + 1 cylinder grouped in an assembly\n";
|
||||||
|
std::cout << "==================================================" << std::endl;
|
||||||
|
|
||||||
|
if (interactive) {
|
||||||
|
viewer.ZoomAuto();
|
||||||
|
viewer.Start();
|
||||||
|
} else {
|
||||||
|
std::cout << "Non-interactive test passed." << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
210
src/Vtk/Math/vtkAssembly.cpp
Normal file
210
src/Vtk/Math/vtkAssembly.cpp
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
/*//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// 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 "vtkAssembly.h" // uLib::Vtk::Assembly
|
||||||
|
#include "Vtk/vtkObjectsContext.h"
|
||||||
|
#include "Math/vtkDense.h"
|
||||||
|
|
||||||
|
#include <vtkAssembly.h> // VTK library ::vtkAssembly
|
||||||
|
#include <vtkActor.h>
|
||||||
|
#include <vtkCubeSource.h>
|
||||||
|
#include <vtkPolyDataMapper.h>
|
||||||
|
#include <vtkProperty.h>
|
||||||
|
#include <vtkMatrix4x4.h>
|
||||||
|
#include <vtkPropCollection.h>
|
||||||
|
#include <vtkNew.h>
|
||||||
|
#include <vtkProp3D.h>
|
||||||
|
#include <vtkPoints.h>
|
||||||
|
#include <vtkCellArray.h>
|
||||||
|
#include <vtkPolyData.h>
|
||||||
|
|
||||||
|
namespace uLib {
|
||||||
|
namespace Vtk {
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------ //
|
||||||
|
Assembly::Assembly(uLib::Assembly *content)
|
||||||
|
: m_Content(content),
|
||||||
|
m_ChildContext(nullptr),
|
||||||
|
m_BBoxActor(nullptr),
|
||||||
|
m_VtkAsm(nullptr),
|
||||||
|
m_InUpdate(false),
|
||||||
|
m_BlockUpdate(false) {
|
||||||
|
this->InstallPipe();
|
||||||
|
if (m_Content) {
|
||||||
|
Object::connect(m_Content, &uLib::Assembly::Updated,
|
||||||
|
this, &Assembly::contentUpdate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Assembly::~Assembly() {
|
||||||
|
delete m_ChildContext;
|
||||||
|
if (m_BBoxActor) m_BBoxActor->Delete();
|
||||||
|
if (m_VtkAsm) m_VtkAsm->Delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------ //
|
||||||
|
void Assembly::InstallPipe() {
|
||||||
|
// 1. Create the VTK library assembly that groups everything
|
||||||
|
m_VtkAsm = ::vtkAssembly::New();
|
||||||
|
this->SetProp(m_VtkAsm);
|
||||||
|
|
||||||
|
// 2. Create the bounding-box wireframe actor
|
||||||
|
vtkNew<vtkCubeSource> cube;
|
||||||
|
cube->SetBounds(0, 1, 0, 1, 0, 1);
|
||||||
|
cube->Update();
|
||||||
|
|
||||||
|
vtkNew<vtkPolyDataMapper> mapper;
|
||||||
|
mapper->SetInputConnection(cube->GetOutputPort());
|
||||||
|
|
||||||
|
m_BBoxActor = vtkActor::New();
|
||||||
|
m_BBoxActor->SetMapper(mapper);
|
||||||
|
m_BBoxActor->GetProperty()->SetRepresentationToWireframe();
|
||||||
|
m_BBoxActor->GetProperty()->SetColor(1.0, 0.85, 0.0); // gold wireframe
|
||||||
|
m_BBoxActor->GetProperty()->SetLineWidth(1.5);
|
||||||
|
m_BBoxActor->GetProperty()->SetOpacity(0.6);
|
||||||
|
m_BBoxActor->SetVisibility(m_Content ? m_Content->GetShowBoundingBox() : false);
|
||||||
|
|
||||||
|
m_VtkAsm->AddPart(m_BBoxActor);
|
||||||
|
|
||||||
|
// 3. Build a child-objects context (auto-creates puppets for each child)
|
||||||
|
if (m_Content) {
|
||||||
|
m_ChildContext = new vtkObjectsContext(m_Content);
|
||||||
|
// The vtkObjectsContext's own prop is already a ::vtkAssembly;
|
||||||
|
// nest it inside ours so everything moves together.
|
||||||
|
if (auto *childProp = vtkProp3D::SafeDownCast(m_ChildContext->GetProp()))
|
||||||
|
m_VtkAsm->AddPart(childProp);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Apply initial transform
|
||||||
|
this->UpdateTransform();
|
||||||
|
this->UpdateBoundingBox();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------ //
|
||||||
|
void Assembly::contentUpdate() {
|
||||||
|
if (m_InUpdate) return;
|
||||||
|
m_InUpdate = true;
|
||||||
|
|
||||||
|
this->UpdateTransform();
|
||||||
|
this->UpdateBoundingBox();
|
||||||
|
if (m_ChildContext)
|
||||||
|
m_ChildContext->Update();
|
||||||
|
|
||||||
|
m_BlockUpdate = true;
|
||||||
|
Puppet::Update();
|
||||||
|
m_InUpdate = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------ //
|
||||||
|
void Assembly::Update() {
|
||||||
|
if (m_InUpdate) return;
|
||||||
|
if (!m_Content || !m_VtkAsm) return;
|
||||||
|
|
||||||
|
if (m_BlockUpdate) {
|
||||||
|
m_BlockUpdate = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_InUpdate = true;
|
||||||
|
|
||||||
|
// Pull VTK transform back into the uLib model
|
||||||
|
vtkMatrix4x4* vmat = m_VtkAsm->GetUserMatrix();
|
||||||
|
if (vmat) {
|
||||||
|
Matrix4f transform = VtkToMatrix4f(vmat);
|
||||||
|
m_Content->SetMatrix(transform);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->UpdateBoundingBox();
|
||||||
|
if (m_ChildContext)
|
||||||
|
m_ChildContext->Update();
|
||||||
|
|
||||||
|
m_Content->Updated(); // Notify change in model
|
||||||
|
|
||||||
|
m_InUpdate = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------ //
|
||||||
|
void Assembly::UpdateTransform() {
|
||||||
|
if (!m_Content || !m_VtkAsm) return;
|
||||||
|
|
||||||
|
Matrix4f mat = m_Content->GetMatrix();
|
||||||
|
vtkNew<vtkMatrix4x4> vmat;
|
||||||
|
Matrix4fToVtk(mat, vmat);
|
||||||
|
m_VtkAsm->SetUserMatrix(vmat);
|
||||||
|
m_VtkAsm->Modified();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------ //
|
||||||
|
void Assembly::UpdateBoundingBox() {
|
||||||
|
if (!m_Content || !m_BBoxActor) return;
|
||||||
|
|
||||||
|
m_BBoxActor->SetVisibility(m_Content->GetShowBoundingBox());
|
||||||
|
|
||||||
|
Vector3f bbMin, bbMax;
|
||||||
|
m_Content->GetBoundingBox(bbMin, bbMax);
|
||||||
|
|
||||||
|
// Avoid degenerate boxes
|
||||||
|
Vector3f size = bbMax - bbMin;
|
||||||
|
if (size.norm() < 1e-6f) return;
|
||||||
|
|
||||||
|
// Rebuild the corner segments to match the AABB
|
||||||
|
vtkNew<vtkPoints> points;
|
||||||
|
vtkNew<vtkCellArray> lines;
|
||||||
|
|
||||||
|
float bounds[2][3] = {
|
||||||
|
{bbMin(0), bbMin(1), bbMin(2)},
|
||||||
|
{bbMax(0), bbMax(1), bbMax(2)}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Corner segment length: 10% of dimension
|
||||||
|
float len[3] = {
|
||||||
|
(bbMax(0) - bbMin(0)) * 0.1f,
|
||||||
|
(bbMax(1) - bbMin(1)) * 0.1f,
|
||||||
|
(bbMax(2) - bbMin(2)) * 0.1f
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int i = 0; i < 8; ++i) {
|
||||||
|
float p[3];
|
||||||
|
p[0] = bounds[(i & 1) ? 1 : 0][0];
|
||||||
|
p[1] = bounds[(i & 2) ? 1 : 0][1];
|
||||||
|
p[2] = bounds[(i & 4) ? 1 : 0][2];
|
||||||
|
|
||||||
|
for (int axis = 0; axis < 3; ++axis) {
|
||||||
|
float p2[3] = {p[0], p[1], p[2]};
|
||||||
|
float delta = (i & (1 << axis)) ? -len[axis] : len[axis];
|
||||||
|
p2[axis] += delta;
|
||||||
|
|
||||||
|
vtkIdType id1 = points->InsertNextPoint(p);
|
||||||
|
vtkIdType id2 = points->InsertNextPoint(p2);
|
||||||
|
lines->InsertNextCell(2);
|
||||||
|
lines->InsertCellPoint(id1);
|
||||||
|
lines->InsertCellPoint(id2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vtkNew<vtkPolyData> poly;
|
||||||
|
poly->SetPoints(points);
|
||||||
|
poly->SetLines(lines);
|
||||||
|
|
||||||
|
vtkNew<vtkPolyDataMapper> mapper;
|
||||||
|
mapper->SetInputData(poly);
|
||||||
|
|
||||||
|
m_BBoxActor->SetMapper(mapper);
|
||||||
|
m_BBoxActor->Modified();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------ //
|
||||||
|
vtkObjectsContext *Assembly::GetChildrenContext() const {
|
||||||
|
return m_ChildContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Vtk
|
||||||
|
} // namespace uLib
|
||||||
71
src/Vtk/Math/vtkAssembly.h
Normal file
71
src/Vtk/Math/vtkAssembly.h
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
/*//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// 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 >
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
#ifndef U_VTK_ASSEMBLY_H
|
||||||
|
#define U_VTK_ASSEMBLY_H
|
||||||
|
|
||||||
|
#include "Math/Assembly.h"
|
||||||
|
#include "uLibVtkInterface.h"
|
||||||
|
|
||||||
|
class vtkActor;
|
||||||
|
class vtkAssembly; // VTK library forward declaration (must be before namespace)
|
||||||
|
|
||||||
|
namespace uLib {
|
||||||
|
namespace Vtk {
|
||||||
|
|
||||||
|
class vtkObjectsContext; // forward
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief VTK Puppet for visualizing uLib::Assembly.
|
||||||
|
*
|
||||||
|
* Manages a VTK assembly (vtkAssembly from the VTK library) that groups
|
||||||
|
* all child puppets and applies the Assembly's AffineTransform. It also
|
||||||
|
* renders an optional bounding box wireframe computed from the Assembly's AABB.
|
||||||
|
*
|
||||||
|
* @note This class is uLib::Vtk::Assembly. It internally uses
|
||||||
|
* the VTK library class vtkAssembly for grouping, but the two
|
||||||
|
* are distinct.
|
||||||
|
*/
|
||||||
|
class Assembly : public Puppet {
|
||||||
|
public:
|
||||||
|
virtual const char *GetClassName() const override { return "Vtk.Assembly"; }
|
||||||
|
|
||||||
|
Assembly(uLib::Assembly *content);
|
||||||
|
virtual ~Assembly();
|
||||||
|
|
||||||
|
/** @brief Updates the VTK representation from the model (model→VTK). */
|
||||||
|
virtual void Update() override;
|
||||||
|
|
||||||
|
virtual uLib::Object* GetContent() const override { return (uLib::Object*)m_Content; }
|
||||||
|
|
||||||
|
/** @brief Called when the model signals an update (model→VTK push). */
|
||||||
|
void contentUpdate();
|
||||||
|
|
||||||
|
/** @brief Returns the puppet managing child objects. */
|
||||||
|
vtkObjectsContext *GetChildrenContext() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void UpdateTransform();
|
||||||
|
void UpdateBoundingBox();
|
||||||
|
void InstallPipe();
|
||||||
|
|
||||||
|
uLib::Assembly *m_Content;
|
||||||
|
vtkObjectsContext *m_ChildContext;
|
||||||
|
vtkActor *m_BBoxActor;
|
||||||
|
::vtkAssembly *m_VtkAsm; // VTK library assembly — NOT this class
|
||||||
|
bool m_InUpdate; // re-entrancy guard
|
||||||
|
bool m_BlockUpdate; // block feedback from contentUpdate→Update
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Vtk
|
||||||
|
} // namespace uLib
|
||||||
|
|
||||||
|
#endif // U_VTK_ASSEMBLY_H
|
||||||
@@ -47,18 +47,23 @@
|
|||||||
namespace uLib {
|
namespace uLib {
|
||||||
namespace Vtk {
|
namespace Vtk {
|
||||||
|
|
||||||
|
struct ContainerBoxData {
|
||||||
|
vtkSmartPointer<vtkActor> m_Cube;
|
||||||
|
vtkSmartPointer<vtkActor> m_Axes;
|
||||||
|
|
||||||
|
ContainerBoxData() : m_Cube(vtkSmartPointer<vtkActor>::New()), m_Axes(vtkSmartPointer<vtkActor>::New()) {}
|
||||||
|
~ContainerBoxData() {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
vtkContainerBox::vtkContainerBox(vtkContainerBox::Content *content)
|
vtkContainerBox::vtkContainerBox(vtkContainerBox::Content *content)
|
||||||
: m_Cube(vtkActor::New()), m_Axes(vtkActor::New()),
|
: d(new ContainerBoxData()), m_Content(content) {
|
||||||
// m_Pivot(vtkActor::New()),
|
|
||||||
m_Content(content) {
|
|
||||||
this->InstallPipe();
|
this->InstallPipe();
|
||||||
Object::connect(m_Content, &Content::Updated, this, &vtkContainerBox::contentUpdate);
|
Object::connect(m_Content, &Content::Updated, this, &vtkContainerBox::contentUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
vtkContainerBox::~vtkContainerBox() {
|
vtkContainerBox::~vtkContainerBox() {
|
||||||
m_Cube->Delete();
|
delete d;
|
||||||
m_Axes->Delete();
|
|
||||||
// m_Pivot->Delete();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vtkPolyData *vtkContainerBox::GetPolyData() const {
|
vtkPolyData *vtkContainerBox::GetPolyData() const {
|
||||||
@@ -83,8 +88,8 @@ void vtkContainerBox::contentUpdate() {
|
|||||||
vmat = mat;
|
vmat = mat;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_Cube->SetUserMatrix(nullptr);
|
d->m_Cube->SetUserMatrix(nullptr);
|
||||||
m_Axes->SetUserMatrix(nullptr);
|
d->m_Axes->SetUserMatrix(nullptr);
|
||||||
|
|
||||||
Matrix4f transform = m_Content->GetMatrix();
|
Matrix4f transform = m_Content->GetMatrix();
|
||||||
Matrix4fToVtk(transform, vmat);
|
Matrix4fToVtk(transform, vmat);
|
||||||
@@ -143,9 +148,9 @@ void vtkContainerBox::InstallPipe() {
|
|||||||
cube->SetBounds(0, 1, 0, 1, 0, 1);
|
cube->SetBounds(0, 1, 0, 1, 0, 1);
|
||||||
mapper->SetInputConnection(cube->GetOutputPort());
|
mapper->SetInputConnection(cube->GetOutputPort());
|
||||||
mapper->Update();
|
mapper->Update();
|
||||||
m_Cube->SetMapper(mapper);
|
d->m_Cube->SetMapper(mapper);
|
||||||
m_Cube->GetProperty()->SetRepresentationToWireframe();
|
d->m_Cube->GetProperty()->SetRepresentationToWireframe();
|
||||||
m_Cube->GetProperty()->SetAmbient(0.7);
|
d->m_Cube->GetProperty()->SetAmbient(0.7);
|
||||||
|
|
||||||
// AXES //
|
// AXES //
|
||||||
vtkSmartPointer<vtkAxes> axes = vtkSmartPointer<vtkAxes>::New();
|
vtkSmartPointer<vtkAxes> axes = vtkSmartPointer<vtkAxes>::New();
|
||||||
@@ -153,10 +158,10 @@ void vtkContainerBox::InstallPipe() {
|
|||||||
mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
|
mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
|
||||||
mapper->SetInputConnection(axes->GetOutputPort());
|
mapper->SetInputConnection(axes->GetOutputPort());
|
||||||
mapper->Update();
|
mapper->Update();
|
||||||
m_Axes->SetMapper(mapper);
|
d->m_Axes->SetMapper(mapper);
|
||||||
m_Axes->GetProperty()->SetLineWidth(3);
|
d->m_Axes->GetProperty()->SetLineWidth(3);
|
||||||
m_Axes->GetProperty()->SetAmbient(0.4);
|
d->m_Axes->GetProperty()->SetAmbient(0.4);
|
||||||
m_Axes->GetProperty()->SetSpecular(0);
|
d->m_Axes->GetProperty()->SetSpecular(0);
|
||||||
|
|
||||||
// PIVOT //
|
// PIVOT //
|
||||||
axes = vtkSmartPointer<vtkAxes>::New();
|
axes = vtkSmartPointer<vtkAxes>::New();
|
||||||
@@ -165,8 +170,8 @@ void vtkContainerBox::InstallPipe() {
|
|||||||
mapper->SetInputConnection(axes->GetOutputPort());
|
mapper->SetInputConnection(axes->GetOutputPort());
|
||||||
mapper->Update();
|
mapper->Update();
|
||||||
|
|
||||||
this->SetProp(m_Cube);
|
this->SetProp(d->m_Cube);
|
||||||
this->SetProp(m_Axes);
|
this->SetProp(d->m_Axes);
|
||||||
|
|
||||||
vtkProp3D* root = vtkProp3D::SafeDownCast(this->GetProp());
|
vtkProp3D* root = vtkProp3D::SafeDownCast(this->GetProp());
|
||||||
if (root) {
|
if (root) {
|
||||||
|
|||||||
@@ -29,12 +29,15 @@
|
|||||||
#include "Math/ContainerBox.h"
|
#include "Math/ContainerBox.h"
|
||||||
#include "uLibVtkInterface.h"
|
#include "uLibVtkInterface.h"
|
||||||
#include "vtkPolydata.h"
|
#include "vtkPolydata.h"
|
||||||
#include <vtkActor.h>
|
|
||||||
#include <boost/signals2/connection.hpp>
|
#include <boost/signals2/connection.hpp>
|
||||||
|
|
||||||
|
class vtkActor;
|
||||||
|
|
||||||
namespace uLib {
|
namespace uLib {
|
||||||
namespace Vtk {
|
namespace Vtk {
|
||||||
|
|
||||||
|
struct ContainerBoxData;
|
||||||
|
|
||||||
class vtkContainerBox : public Puppet, public Polydata {
|
class vtkContainerBox : public Puppet, public Polydata {
|
||||||
typedef ContainerBox Content;
|
typedef ContainerBox Content;
|
||||||
|
|
||||||
@@ -48,16 +51,14 @@ public:
|
|||||||
|
|
||||||
virtual void Update();
|
virtual void Update();
|
||||||
|
|
||||||
|
virtual uLib::Object* GetContent() const override { return (uLib::Object*)m_Content; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void InstallPipe();
|
virtual void InstallPipe();
|
||||||
|
|
||||||
vtkActor *m_Cube;
|
struct ContainerBoxData *d;
|
||||||
vtkActor *m_Axes;
|
|
||||||
// vtkActor *m_Pivot;
|
|
||||||
|
|
||||||
Content *m_Content;
|
Content *m_Content;
|
||||||
bool m_BlockUpdate = false;
|
bool m_BlockUpdate = false;
|
||||||
// boost::signals2::connection m_Connection;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Vtk
|
} // namespace Vtk
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
|
|
||||||
#include "Vtk/Math/vtkCylinder.h"
|
#include "Vtk/Math/vtkCylinder.h"
|
||||||
#include <vtkActor.h>
|
#include <vtkActor.h>
|
||||||
|
#include <vtkAssembly.h>
|
||||||
#include <vtkCylinderSource.h>
|
#include <vtkCylinderSource.h>
|
||||||
#include <vtkMatrix4x4.h>
|
#include <vtkMatrix4x4.h>
|
||||||
#include <vtkPolyDataMapper.h>
|
#include <vtkPolyDataMapper.h>
|
||||||
@@ -37,13 +38,14 @@ namespace uLib {
|
|||||||
namespace Vtk {
|
namespace Vtk {
|
||||||
|
|
||||||
vtkCylinder::vtkCylinder(vtkCylinder::Content *content)
|
vtkCylinder::vtkCylinder(vtkCylinder::Content *content)
|
||||||
: m_Actor(vtkActor::New()), m_Content(content) {
|
: m_Content(content), m_Actor(nullptr), m_VtkAsm(nullptr) {
|
||||||
this->InstallPipe();
|
this->InstallPipe();
|
||||||
Object::connect(m_Content, &Content::Updated, this, &vtkCylinder::contentUpdate);
|
Object::connect(m_Content, &Content::Updated, this, &vtkCylinder::contentUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
vtkCylinder::~vtkCylinder() {
|
vtkCylinder::~vtkCylinder() {
|
||||||
m_Actor->Delete();
|
if (m_Actor) m_Actor->Delete();
|
||||||
|
if (m_VtkAsm) m_VtkAsm->Delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
void vtkCylinder::contentUpdate() {
|
void vtkCylinder::contentUpdate() {
|
||||||
@@ -53,28 +55,31 @@ 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
|
||||||
vtkMatrix4x4* vmat = root->GetUserMatrix();
|
vtkMatrix4x4* vmat = root->GetUserMatrix();
|
||||||
if (!vmat) {
|
if (!vmat) {
|
||||||
vtkNew<vtkMatrix4x4> mat;
|
vtkNew<vtkMatrix4x4> mat;
|
||||||
root->SetUserMatrix(mat);
|
root->SetUserMatrix(mat);
|
||||||
vmat = mat;
|
vmat = mat;
|
||||||
}
|
}
|
||||||
|
Matrix4f transform = m_Content->GetMatrix();
|
||||||
// Multiply the placement matrix by the volume scaling (Radius, Radius, Height)
|
|
||||||
Matrix4f transform = m_Content->GetMatrix() * m_Content->GetLocalMatrix();
|
|
||||||
Matrix4fToVtk(transform, vmat);
|
Matrix4fToVtk(transform, vmat);
|
||||||
|
|
||||||
// Update internal alignment based on active axis
|
// 2. Shape-local properties (Radius, Height, Axis alignment) go to the internal actor
|
||||||
vtkTransform* alignment = vtkTransform::SafeDownCast(m_Actor->GetUserTransform());
|
vtkTransform* alignment = vtkTransform::SafeDownCast(m_Actor->GetUserTransform());
|
||||||
if (alignment) {
|
if (alignment) {
|
||||||
alignment->Identity();
|
alignment->Identity();
|
||||||
|
alignment->PostMultiply();
|
||||||
|
|
||||||
|
// Initial source is centered Y-cylinder (Radial XZ [-1,1], Height Y [-0.5, 0.5])
|
||||||
|
// Apply Radius and Height scaling
|
||||||
|
alignment->Scale(m_Content->GetRadius(), m_Content->GetHeight(), m_Content->GetRadius());
|
||||||
|
|
||||||
|
// Apply Axis alignment
|
||||||
int axis = m_Content->GetAxis();
|
int axis = m_Content->GetAxis();
|
||||||
if (axis == 0) alignment->RotateZ(-90); // Y -> X
|
if (axis == 0) alignment->RotateZ(-90); // Y -> X
|
||||||
else if (axis == 1) ; // Y -> Y (identity)
|
else if (axis == 1) ; // Y -> Y (identity)
|
||||||
else if (axis == 2) alignment->RotateX(90); // Y -> Z
|
else if (axis == 2) alignment->RotateX(90); // Y -> Z
|
||||||
|
|
||||||
// We keep it centered as per latest user preference in Step 677
|
|
||||||
// alignment->Translate(0, 0, 0); // Implicit
|
|
||||||
}
|
}
|
||||||
|
|
||||||
root->Modified();
|
root->Modified();
|
||||||
@@ -90,16 +95,9 @@ void vtkCylinder::Update() {
|
|||||||
vtkMatrix4x4* vmat = root->GetUserMatrix();
|
vtkMatrix4x4* vmat = root->GetUserMatrix();
|
||||||
if (!vmat) return;
|
if (!vmat) return;
|
||||||
|
|
||||||
Matrix4f fullTransform = VtkToMatrix4f(vmat);
|
// Pull the placement matrix directly from VTK
|
||||||
Matrix4f placementScale = m_Content->GetLocalMatrix().inverse();
|
Matrix4f transform = VtkToMatrix4f(vmat);
|
||||||
Matrix4f transform = fullTransform * placementScale;
|
|
||||||
|
|
||||||
if (m_Content->GetParent()) {
|
|
||||||
Matrix4f localT = m_Content->GetParent()->GetWorldMatrix().inverse() * transform;
|
|
||||||
m_Content->SetMatrix(localT);
|
|
||||||
} else {
|
|
||||||
m_Content->SetMatrix(transform);
|
m_Content->SetMatrix(transform);
|
||||||
}
|
|
||||||
|
|
||||||
m_Content->Updated();
|
m_Content->Updated();
|
||||||
}
|
}
|
||||||
@@ -108,36 +106,27 @@ void vtkCylinder::InstallPipe() {
|
|||||||
if (!m_Content)
|
if (!m_Content)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
m_VtkAsm = ::vtkAssembly::New();
|
||||||
|
this->SetProp(m_VtkAsm);
|
||||||
|
|
||||||
vtkNew<vtkCylinderSource> cylinder;
|
vtkNew<vtkCylinderSource> cylinder;
|
||||||
cylinder->SetRadius(1.0);
|
cylinder->SetRadius(1.0);
|
||||||
cylinder->SetHeight(1.0);
|
cylinder->SetHeight(1.0);
|
||||||
cylinder->SetResolution(32);
|
cylinder->SetResolution(32);
|
||||||
|
|
||||||
|
m_Actor = vtkActor::New();
|
||||||
vtkNew<vtkTransform> alignment;
|
vtkNew<vtkTransform> alignment;
|
||||||
alignment->Identity();
|
m_Actor->SetUserTransform(alignment);
|
||||||
alignment->Translate(0, 0, -0.5);
|
|
||||||
|
|
||||||
// Default to Y alignment (Identity) as per latest request
|
|
||||||
int axis = m_Content->GetAxis();
|
|
||||||
if (axis == 0) alignment->RotateZ(-90);
|
|
||||||
else if (axis == 2) alignment->RotateX(90);
|
|
||||||
|
|
||||||
vtkNew<vtkPolyDataMapper> mapper;
|
vtkNew<vtkPolyDataMapper> mapper;
|
||||||
mapper->SetInputConnection(cylinder->GetOutputPort());
|
mapper->SetInputConnection(cylinder->GetOutputPort());
|
||||||
|
|
||||||
m_Actor->SetMapper(mapper);
|
m_Actor->SetMapper(mapper);
|
||||||
m_Actor->SetUserTransform(alignment);
|
|
||||||
m_Actor->GetProperty()->SetRepresentationToWireframe();
|
m_Actor->GetProperty()->SetRepresentationToWireframe();
|
||||||
m_Actor->GetProperty()->SetAmbient(0.6);
|
m_Actor->GetProperty()->SetAmbient(0.6);
|
||||||
|
|
||||||
this->SetProp(m_Actor);
|
m_VtkAsm->AddPart(m_Actor);
|
||||||
|
|
||||||
vtkProp3D* root = vtkProp3D::SafeDownCast(this->GetProp());
|
this->contentUpdate();
|
||||||
if (root) {
|
|
||||||
vtkNew<vtkMatrix4x4> vmat;
|
|
||||||
Matrix4fToVtk(m_Content->GetMatrix() * m_Content->GetLocalMatrix(), vmat);
|
|
||||||
root->SetUserMatrix(vmat);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Vtk
|
} // namespace Vtk
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
#include "Math/Cylinder.h"
|
#include "Math/Cylinder.h"
|
||||||
#include "Vtk/uLibVtkInterface.h"
|
#include "Vtk/uLibVtkInterface.h"
|
||||||
#include <vtkActor.h>
|
#include <vtkActor.h>
|
||||||
|
class vtkAssembly;
|
||||||
|
|
||||||
namespace uLib {
|
namespace uLib {
|
||||||
namespace Vtk {
|
namespace Vtk {
|
||||||
@@ -53,11 +54,14 @@ public:
|
|||||||
/** 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();
|
||||||
|
|
||||||
|
virtual uLib::Object* GetContent() const override { return (uLib::Object*)m_Content; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/** Sets up the VTK visualization pipeline */
|
/** Sets up the VTK visualization pipeline */
|
||||||
virtual void InstallPipe();
|
virtual void InstallPipe();
|
||||||
|
|
||||||
vtkActor *m_Actor;
|
vtkActor *m_Actor;
|
||||||
|
::vtkAssembly *m_VtkAsm;
|
||||||
Content *m_Content;
|
Content *m_Content;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -126,9 +126,11 @@ vtkVoxImage::vtkVoxImage(Content &content)
|
|||||||
: m_Content(content), m_Actor(vtkVolume::New()),
|
: m_Content(content), m_Actor(vtkVolume::New()),
|
||||||
m_Image(vtkImageData::New()), m_Outline(vtkCubeSource::New()),
|
m_Image(vtkImageData::New()), m_Outline(vtkCubeSource::New()),
|
||||||
m_OutlineActor(vtkActor::New()),
|
m_OutlineActor(vtkActor::New()),
|
||||||
m_Reader(NULL), m_Writer(NULL), writer_factor(1.E6) {
|
m_Reader(NULL), m_Writer(NULL), writer_factor(1.E6),
|
||||||
|
m_Window(40/1.E6), m_Level(20/1.E6), m_ShadingPreset(0) {
|
||||||
GetContent();
|
GetContent();
|
||||||
InstallPipe();
|
InstallPipe();
|
||||||
|
ULIB_ACTIVATE_DISPLAY_PROPERTIES;
|
||||||
}
|
}
|
||||||
|
|
||||||
vtkVoxImage::~vtkVoxImage() {
|
vtkVoxImage::~vtkVoxImage() {
|
||||||
@@ -199,14 +201,15 @@ void vtkVoxImage::ReadFromXMLFile(const char *fname) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void vtkVoxImage::setShadingPreset(int blendType) {
|
void vtkVoxImage::setShadingPreset(int blendType) {
|
||||||
|
m_ShadingPreset = blendType;
|
||||||
vtkSmartVolumeMapper *mapper = (vtkSmartVolumeMapper *)m_Actor->GetMapper();
|
vtkSmartVolumeMapper *mapper = (vtkSmartVolumeMapper *)m_Actor->GetMapper();
|
||||||
vtkVolumeProperty *property = m_Actor->GetProperty();
|
vtkVolumeProperty *property = m_Actor->GetProperty();
|
||||||
|
|
||||||
static vtkColorTransferFunction *colorFun = vtkColorTransferFunction::New();
|
static vtkColorTransferFunction *colorFun = vtkColorTransferFunction::New();
|
||||||
static vtkPiecewiseFunction *opacityFun = vtkPiecewiseFunction::New();
|
static vtkPiecewiseFunction *opacityFun = vtkPiecewiseFunction::New();
|
||||||
|
|
||||||
float window = 40 / writer_factor;
|
float window = m_Window;
|
||||||
float level = 20 / writer_factor;
|
float level = m_Level;
|
||||||
|
|
||||||
property->SetColor(colorFun);
|
property->SetColor(colorFun);
|
||||||
property->SetScalarOpacity(opacityFun);
|
property->SetScalarOpacity(opacityFun);
|
||||||
@@ -281,13 +284,24 @@ void vtkVoxImage::SetRepresentation(Representation mode) {
|
|||||||
m_OutlineActor->SetVisibility(1);
|
m_OutlineActor->SetVisibility(1);
|
||||||
} else if (mode == Surface) {
|
} else if (mode == Surface) {
|
||||||
m_Actor->SetVisibility(1);
|
m_Actor->SetVisibility(1);
|
||||||
m_OutlineActor->SetVisibility(0);
|
m_OutlineActor->SetVisibility(1); // Keep outline visible as boundary
|
||||||
} else {
|
} else {
|
||||||
Puppet::SetRepresentation(mode);
|
Puppet::SetRepresentation(mode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void vtkVoxImage::serialize_display(uLib::Archive::display_properties_archive & ar, const unsigned int version) {
|
||||||
|
// Call base class if it has display properties
|
||||||
|
Puppet::serialize_display(ar, version);
|
||||||
|
|
||||||
|
// Use the member variables if they are available
|
||||||
|
ar & boost::serialization::make_hrp("Window", m_Window);
|
||||||
|
ar & boost::serialization::make_hrp("Level", m_Level);
|
||||||
|
ar & boost::serialization::make_hrp_enum("Shading", m_ShadingPreset, {"MIP", "Composite", "Composite Shaded", "MIP Bone", "MIP Hot", "Additive"});
|
||||||
|
}
|
||||||
|
|
||||||
void vtkVoxImage::Update() {
|
void vtkVoxImage::Update() {
|
||||||
|
setShadingPreset(m_ShadingPreset);
|
||||||
m_Actor->Update();
|
m_Actor->Update();
|
||||||
m_Outline->SetBounds(m_Image->GetBounds());
|
m_Outline->SetBounds(m_Image->GetBounds());
|
||||||
m_Outline->Update();
|
m_Outline->Update();
|
||||||
|
|||||||
@@ -65,7 +65,8 @@ public:
|
|||||||
void setShadingPreset(int blendType = 2);
|
void setShadingPreset(int blendType = 2);
|
||||||
void SetRepresentation(Representation mode);
|
void SetRepresentation(Representation mode);
|
||||||
|
|
||||||
void Update();
|
void Update() override;
|
||||||
|
void serialize_display(uLib::Archive::display_properties_archive & ar, const unsigned int version = 0) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void InstallPipe();
|
void InstallPipe();
|
||||||
@@ -84,6 +85,7 @@ private:
|
|||||||
|
|
||||||
float m_Window;
|
float m_Window;
|
||||||
float m_Level;
|
float m_Level;
|
||||||
|
int m_ShadingPreset;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Vtk
|
} // namespace Vtk
|
||||||
|
|||||||
@@ -46,13 +46,14 @@ int main() {
|
|||||||
box.Scale(Vector3f(2.0, 3.0, 4.0));
|
box.Scale(Vector3f(2.0, 3.0, 4.0));
|
||||||
box.SetPosition(Vector3f(1.0, 1.0, 1.0));
|
box.SetPosition(Vector3f(1.0, 1.0, 1.0));
|
||||||
|
|
||||||
|
// 3. Setup the Viewer
|
||||||
|
Vtk::Viewer viewer;
|
||||||
|
|
||||||
// 2. Wrap it in a Vtk::vtkContainerBox (Vtk Puppet)
|
// 2. Wrap it in a Vtk::vtkContainerBox (Vtk Puppet)
|
||||||
Vtk::vtkContainerBox v_box(&box);
|
Vtk::vtkContainerBox v_box(&box);
|
||||||
v_box.SetRepresentation(Vtk::Puppet::Surface);
|
v_box.SetRepresentation(Vtk::Puppet::Surface);
|
||||||
v_box.SetOpacity(0.5);
|
v_box.SetOpacity(0.5);
|
||||||
|
|
||||||
// 3. Setup the Viewer
|
|
||||||
Vtk::Viewer viewer;
|
|
||||||
viewer.AddPuppet(v_box);
|
viewer.AddPuppet(v_box);
|
||||||
|
|
||||||
// 4. Create and setup the vtkHandlerWidget
|
// 4. Create and setup the vtkHandlerWidget
|
||||||
@@ -112,5 +113,6 @@ int main() {
|
|||||||
<< std::endl;
|
<< std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handler->EnabledOff();
|
||||||
END_TESTING;
|
END_TESTING;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
#ifdef HAVE_CONFIG_H
|
#ifdef HAVE_CONFIG_H
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#endif
|
#endif
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
#include <Vtk/uLibVtkViewer.h>
|
#include <Vtk/uLibVtkViewer.h>
|
||||||
|
|
||||||
@@ -63,9 +64,10 @@ int main()
|
|||||||
actor->SetMapper(mapper);
|
actor->SetMapper(mapper);
|
||||||
|
|
||||||
v_viewer.addProp(actor);
|
v_viewer.addProp(actor);
|
||||||
|
if (!std::getenv("CTEST_PROJECT_NAME")) {
|
||||||
v_viewer.GetRenderer()->Render();
|
v_viewer.GetRenderer()->Render();
|
||||||
v_viewer.Start();
|
v_viewer.Start();
|
||||||
|
}
|
||||||
|
|
||||||
END_TESTING;
|
END_TESTING;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,11 +39,11 @@
|
|||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vtkVersion.h>
|
#include <vtkVersion.h>
|
||||||
#include <vtkProp.h>
|
#include "vtkViewport.h"
|
||||||
#include <vtkActor.h>
|
#include "uLibVtkInterface.h"
|
||||||
#include <vtkSmartPointer.h>
|
|
||||||
|
|
||||||
#include <vtkActor.h>
|
#include <vtkActor.h>
|
||||||
|
#include <vtkPolyDataMapper.h>
|
||||||
|
#include <vtkProperty.h>
|
||||||
#include <vtkPropCollection.h>
|
#include <vtkPropCollection.h>
|
||||||
#include <vtkProp3DCollection.h>
|
#include <vtkProp3DCollection.h>
|
||||||
#include <vtkRendererCollection.h>
|
#include <vtkRendererCollection.h>
|
||||||
@@ -57,9 +57,11 @@
|
|||||||
#include <vtkPolyData.h>
|
#include <vtkPolyData.h>
|
||||||
#include <vtkFeatureEdges.h>
|
#include <vtkFeatureEdges.h>
|
||||||
#include <vtkTransform.h>
|
#include <vtkTransform.h>
|
||||||
|
#include <vtkRenderWindow.h>
|
||||||
|
|
||||||
#include "uLibVtkInterface.h"
|
#include "uLibVtkInterface.h"
|
||||||
#include "vtkHandlerWidget.h"
|
#include "vtkHandlerWidget.h"
|
||||||
|
#include "Math/Dense.h"
|
||||||
#include "Core/Property.h"
|
#include "Core/Property.h"
|
||||||
|
|
||||||
|
|
||||||
@@ -87,12 +89,17 @@ public:
|
|||||||
m_Assembly(vtkSmartPointer<vtkAssembly>::New()),
|
m_Assembly(vtkSmartPointer<vtkAssembly>::New()),
|
||||||
m_ShowBoundingBox(false),
|
m_ShowBoundingBox(false),
|
||||||
m_ShowScaleMeasures(false),
|
m_ShowScaleMeasures(false),
|
||||||
m_Representation(-1),
|
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_Dragable(true)
|
||||||
{
|
{
|
||||||
m_Color[0] = m_Color[1] = m_Color[2] = -1.0;
|
m_Color[0] = m_Color[1] = m_Color[2] = -1.0;
|
||||||
|
m_Position = Vector3d::Zero();
|
||||||
|
m_Orientation = Vector3d::Zero();
|
||||||
|
m_Scale = Vector3d::Ones();
|
||||||
}
|
}
|
||||||
|
|
||||||
~PuppetData() {
|
~PuppetData() {
|
||||||
@@ -110,19 +117,34 @@ public:
|
|||||||
|
|
||||||
bool m_ShowBoundingBox;
|
bool m_ShowBoundingBox;
|
||||||
bool m_ShowScaleMeasures;
|
bool m_ShowScaleMeasures;
|
||||||
|
|
||||||
int m_Representation;
|
int m_Representation;
|
||||||
double m_Color[3];
|
double m_Color[3];
|
||||||
double m_Opacity;
|
double m_Opacity;
|
||||||
|
|
||||||
|
bool m_Selectable;
|
||||||
|
bool m_Selected;
|
||||||
|
bool m_Visibility;
|
||||||
|
bool m_Dragable;
|
||||||
|
Vector3d m_Position;
|
||||||
|
Vector3d m_Orientation;
|
||||||
|
Vector3d m_Scale;
|
||||||
|
|
||||||
void ApplyAppearance(vtkProp *p) {
|
void ApplyAppearance(vtkProp *p) {
|
||||||
|
p->SetVisibility(m_Visibility);
|
||||||
|
p->SetPickable(m_Selectable);
|
||||||
|
p->SetDragable(m_Dragable);
|
||||||
|
|
||||||
vtkActor *actor = vtkActor::SafeDownCast(p);
|
vtkActor *actor = vtkActor::SafeDownCast(p);
|
||||||
if (!actor) return;
|
if (actor) {
|
||||||
|
|
||||||
if (m_Representation != -1) {
|
if (m_Representation != -1) {
|
||||||
|
if (m_Representation == Puppet::SurfaceWithEdges) {
|
||||||
|
actor->GetProperty()->SetRepresentation(VTK_SURFACE);
|
||||||
|
actor->GetProperty()->SetEdgeVisibility(1);
|
||||||
|
} else {
|
||||||
actor->GetProperty()->SetRepresentation(m_Representation);
|
actor->GetProperty()->SetRepresentation(m_Representation);
|
||||||
|
actor->GetProperty()->SetEdgeVisibility(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_Color[0] != -1.0) {
|
if (m_Color[0] != -1.0) {
|
||||||
actor->GetProperty()->SetColor(m_Color);
|
actor->GetProperty()->SetColor(m_Color);
|
||||||
}
|
}
|
||||||
@@ -132,6 +154,15 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle transformation if it's a Prop3D
|
||||||
|
if (auto* p3d = vtkProp3D::SafeDownCast(p)) {
|
||||||
|
// NOTE: Usually managed by Puppet::Update from model, but here for direct prop manipulation
|
||||||
|
// p3d->SetPosition(m_Position.data());
|
||||||
|
// p3d->SetOrientation(m_Orientation.data());
|
||||||
|
// p3d->SetScale(m_Scale.data());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void UpdateHighlight() {
|
void UpdateHighlight() {
|
||||||
if (m_Selected) {
|
if (m_Selected) {
|
||||||
if (!m_HighlightActor) {
|
if (!m_HighlightActor) {
|
||||||
@@ -188,9 +219,6 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool m_Selectable;
|
|
||||||
bool m_Selected;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// -------------------------------------------------------------------------- //
|
// -------------------------------------------------------------------------- //
|
||||||
@@ -198,7 +226,7 @@ public:
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
Puppet::Puppet() : Object(), d(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()) {
|
||||||
uLib::Object::connect(p, &uLib::PropertyBase::Updated, this, &Puppet::Update);
|
uLib::Object::connect(p, &uLib::PropertyBase::Updated, this, &Puppet::Update);
|
||||||
@@ -207,25 +235,40 @@ Puppet::Puppet() : Object(), d(new PuppetData) {
|
|||||||
|
|
||||||
Puppet::~Puppet()
|
Puppet::~Puppet()
|
||||||
{
|
{
|
||||||
delete d;
|
delete pd;
|
||||||
}
|
}
|
||||||
|
|
||||||
vtkProp *Puppet::GetProp()
|
vtkProp *Puppet::GetProp()
|
||||||
{
|
{
|
||||||
if (d->m_Assembly->GetParts()->GetNumberOfItems() == 1)
|
if (pd->m_Assembly->GetParts()->GetNumberOfItems() == 1)
|
||||||
return d->m_Assembly->GetParts()->GetLastProp();
|
return pd->m_Assembly->GetParts()->GetLastProp();
|
||||||
else
|
else
|
||||||
return d->m_Assembly;
|
return pd->m_Assembly;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Puppet::SetProp(vtkProp *prop)
|
void Puppet::SetProp(vtkProp *prop)
|
||||||
{
|
{
|
||||||
if(prop) {
|
if(prop) {
|
||||||
prop->SetPickable(d->m_Selectable);
|
prop->SetPickable(pd->m_Selectable);
|
||||||
if (auto* p3d = vtkProp3D::SafeDownCast(prop)) {
|
if (auto* p3d = vtkProp3D::SafeDownCast(prop)) {
|
||||||
d->m_Assembly->AddPart(p3d);
|
pd->m_Assembly->AddPart(p3d);
|
||||||
|
}
|
||||||
|
pd->ApplyAppearance(prop);
|
||||||
|
|
||||||
|
// For the first actor added, seed the tracked display values from the VTK
|
||||||
|
// actor's current state so the display properties panel shows meaningful
|
||||||
|
// initial values instead of the -1 "not-overriding" sentinels.
|
||||||
|
if (pd->m_Assembly->GetParts()->GetNumberOfItems() == 1) {
|
||||||
|
if (auto* actor = vtkActor::SafeDownCast(prop)) {
|
||||||
|
vtkProperty* vp = actor->GetProperty();
|
||||||
|
if (pd->m_Representation < 0)
|
||||||
|
pd->m_Representation = vp->GetRepresentation();
|
||||||
|
if (pd->m_Opacity < 0)
|
||||||
|
pd->m_Opacity = vp->GetOpacity();
|
||||||
|
if (pd->m_Color[0] < 0)
|
||||||
|
vp->GetColor(pd->m_Color);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
d->ApplyAppearance(prop);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,12 +280,12 @@ void Puppet::RemoveProp(vtkProp *prop)
|
|||||||
|
|
||||||
vtkPropCollection *Puppet::GetParts()
|
vtkPropCollection *Puppet::GetParts()
|
||||||
{
|
{
|
||||||
return d->m_Assembly->GetParts();
|
return pd->m_Assembly->GetParts();
|
||||||
}
|
}
|
||||||
|
|
||||||
vtkPropCollection *Puppet::GetProps()
|
vtkPropCollection *Puppet::GetProps()
|
||||||
{
|
{
|
||||||
return d->m_Assembly->GetParts();
|
return pd->m_Assembly->GetParts();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Puppet::ConnectRenderer(vtkRenderer *renderer)
|
void Puppet::ConnectRenderer(vtkRenderer *renderer)
|
||||||
@@ -253,14 +296,14 @@ void Puppet::ConnectRenderer(vtkRenderer *renderer)
|
|||||||
renderer->AddViewProp(prop);
|
renderer->AddViewProp(prop);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (d->m_ShowBoundingBox && d->m_OutlineActor) renderer->AddActor(d->m_OutlineActor);
|
if (pd->m_ShowBoundingBox && pd->m_OutlineActor) renderer->AddActor(pd->m_OutlineActor);
|
||||||
if (d->m_ShowScaleMeasures && d->m_CubeAxesActor) {
|
if (pd->m_ShowScaleMeasures && pd->m_CubeAxesActor) {
|
||||||
d->m_CubeAxesActor->SetCamera(renderer->GetActiveCamera());
|
pd->m_CubeAxesActor->SetCamera(renderer->GetActiveCamera());
|
||||||
renderer->AddActor(d->m_CubeAxesActor);
|
renderer->AddActor(pd->m_CubeAxesActor);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (d->m_Selected && d->m_HighlightActor) {
|
if (pd->m_Selected && pd->m_HighlightActor) {
|
||||||
renderer->AddActor(d->m_HighlightActor);
|
renderer->AddActor(pd->m_HighlightActor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -271,66 +314,66 @@ void Puppet::DisconnectRenderer(vtkRenderer *renderer)
|
|||||||
if(vtkProp* prop = this->GetProp())
|
if(vtkProp* prop = this->GetProp())
|
||||||
renderer->RemoveViewProp(prop);
|
renderer->RemoveViewProp(prop);
|
||||||
|
|
||||||
if (d->m_ShowBoundingBox && d->m_OutlineActor) renderer->RemoveActor(d->m_OutlineActor);
|
if (pd->m_ShowBoundingBox && pd->m_OutlineActor) renderer->RemoveActor(pd->m_OutlineActor);
|
||||||
if (d->m_ShowScaleMeasures && d->m_CubeAxesActor) renderer->RemoveActor(d->m_CubeAxesActor);
|
if (pd->m_ShowScaleMeasures && pd->m_CubeAxesActor) renderer->RemoveActor(pd->m_CubeAxesActor);
|
||||||
|
|
||||||
this->GetRenderers()->RemoveItem(renderer);
|
this->GetRenderers()->RemoveItem(renderer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Puppet::ConnectViewer(Viewer *viewer)
|
void Puppet::AddToViewer(Viewport &viewer)
|
||||||
{
|
{
|
||||||
// TODO
|
viewer.AddPuppet(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Puppet::DisonnectViewer(Viewer *viewer)
|
void Puppet::RemoveFromViewer(Viewport &viewer)
|
||||||
{
|
{
|
||||||
// TODO
|
viewer.RemovePuppet(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
vtkRendererCollection *Puppet::GetRenderers() const
|
vtkRendererCollection *Puppet::GetRenderers() const
|
||||||
{
|
{
|
||||||
return d->m_Renderers;
|
return pd->m_Renderers;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Puppet::PrintSelf(std::ostream &o) const
|
void Puppet::PrintSelf(std::ostream &o) const
|
||||||
{
|
{
|
||||||
o << "Props Assembly: \n";
|
o << "Props Assembly: \n";
|
||||||
d->m_Assembly->PrintSelf(o,vtkIndent(1));
|
pd->m_Assembly->PrintSelf(o,vtkIndent(1));
|
||||||
|
|
||||||
o << "Connected Renderers: \n";
|
o << "Connected Renderers: \n";
|
||||||
d->m_Renderers->PrintSelf(o,vtkIndent(1));
|
pd->m_Renderers->PrintSelf(o,vtkIndent(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Puppet::ShowBoundingBox(bool show)
|
void Puppet::ShowBoundingBox(bool show)
|
||||||
{
|
{
|
||||||
if (d->m_ShowBoundingBox == show) return;
|
if (pd->m_ShowBoundingBox == show) return;
|
||||||
d->m_ShowBoundingBox = show;
|
pd->m_ShowBoundingBox = show;
|
||||||
if (show) {
|
if (show) {
|
||||||
if (!d->m_OutlineActor) {
|
if (!pd->m_OutlineActor) {
|
||||||
d->m_OutlineSource = vtkSmartPointer<vtkOutlineSource>::New();
|
pd->m_OutlineSource = vtkSmartPointer<vtkOutlineSource>::New();
|
||||||
d->m_OutlineActor = vtkSmartPointer<vtkActor>::New();
|
pd->m_OutlineActor = vtkSmartPointer<vtkActor>::New();
|
||||||
vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
|
vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
|
||||||
mapper->SetInputConnection(d->m_OutlineSource->GetOutputPort());
|
mapper->SetInputConnection(pd->m_OutlineSource->GetOutputPort());
|
||||||
d->m_OutlineActor->SetMapper(mapper);
|
pd->m_OutlineActor->SetMapper(mapper);
|
||||||
d->m_OutlineActor->GetProperty()->SetColor(1.0, 1.0, 1.0);
|
pd->m_OutlineActor->GetProperty()->SetColor(1.0, 1.0, 1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
double* bounds = d->m_Assembly->GetBounds();
|
double* bounds = pd->m_Assembly->GetBounds();
|
||||||
d->m_OutlineSource->SetBounds(bounds);
|
pd->m_OutlineSource->SetBounds(bounds);
|
||||||
d->m_OutlineSource->Update();
|
pd->m_OutlineSource->Update();
|
||||||
|
|
||||||
d->m_Renderers->InitTraversal();
|
pd->m_Renderers->InitTraversal();
|
||||||
for (int i = 0; i < d->m_Renderers->GetNumberOfItems(); ++i) {
|
for (int i = 0; i < pd->m_Renderers->GetNumberOfItems(); ++i) {
|
||||||
vtkRenderer *renderer = d->m_Renderers->GetNextItem();
|
vtkRenderer *renderer = pd->m_Renderers->GetNextItem();
|
||||||
renderer->AddActor(d->m_OutlineActor);
|
renderer->AddActor(pd->m_OutlineActor);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (d->m_OutlineActor) {
|
if (pd->m_OutlineActor) {
|
||||||
d->m_Renderers->InitTraversal();
|
pd->m_Renderers->InitTraversal();
|
||||||
for (int i = 0; i < d->m_Renderers->GetNumberOfItems(); ++i) {
|
for (int i = 0; i < pd->m_Renderers->GetNumberOfItems(); ++i) {
|
||||||
vtkRenderer *renderer = d->m_Renderers->GetNextItem();
|
vtkRenderer *renderer = pd->m_Renderers->GetNextItem();
|
||||||
renderer->RemoveActor(d->m_OutlineActor);
|
renderer->RemoveActor(pd->m_OutlineActor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -338,31 +381,31 @@ void Puppet::ShowBoundingBox(bool show)
|
|||||||
|
|
||||||
void Puppet::ShowScaleMeasures(bool show)
|
void Puppet::ShowScaleMeasures(bool show)
|
||||||
{
|
{
|
||||||
if (d->m_ShowScaleMeasures == show) return;
|
if (pd->m_ShowScaleMeasures == show) return;
|
||||||
d->m_ShowScaleMeasures = show;
|
pd->m_ShowScaleMeasures = show;
|
||||||
if (show) {
|
if (show) {
|
||||||
if (!d->m_CubeAxesActor) {
|
if (!pd->m_CubeAxesActor) {
|
||||||
d->m_CubeAxesActor = vtkSmartPointer<vtkCubeAxesActor>::New();
|
pd->m_CubeAxesActor = vtkSmartPointer<vtkCubeAxesActor>::New();
|
||||||
d->m_CubeAxesActor->SetFlyModeToOuterEdges();
|
pd->m_CubeAxesActor->SetFlyModeToOuterEdges();
|
||||||
d->m_CubeAxesActor->SetUseTextActor3D(1);
|
pd->m_CubeAxesActor->SetUseTextActor3D(1);
|
||||||
d->m_CubeAxesActor->GetProperty()->SetColor(1.0, 1.0, 1.0);
|
pd->m_CubeAxesActor->GetProperty()->SetColor(1.0, 1.0, 1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
double* bounds = d->m_Assembly->GetBounds();
|
double* bounds = pd->m_Assembly->GetBounds();
|
||||||
d->m_CubeAxesActor->SetBounds(bounds);
|
pd->m_CubeAxesActor->SetBounds(bounds);
|
||||||
|
|
||||||
d->m_Renderers->InitTraversal();
|
pd->m_Renderers->InitTraversal();
|
||||||
for (int i = 0; i < d->m_Renderers->GetNumberOfItems(); ++i) {
|
for (int i = 0; i < pd->m_Renderers->GetNumberOfItems(); ++i) {
|
||||||
vtkRenderer *renderer = d->m_Renderers->GetNextItem();
|
vtkRenderer *renderer = pd->m_Renderers->GetNextItem();
|
||||||
d->m_CubeAxesActor->SetCamera(renderer->GetActiveCamera());
|
pd->m_CubeAxesActor->SetCamera(renderer->GetActiveCamera());
|
||||||
renderer->AddActor(d->m_CubeAxesActor);
|
renderer->AddActor(pd->m_CubeAxesActor);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (d->m_CubeAxesActor) {
|
if (pd->m_CubeAxesActor) {
|
||||||
d->m_Renderers->InitTraversal();
|
pd->m_Renderers->InitTraversal();
|
||||||
for (int i = 0; i < d->m_Renderers->GetNumberOfItems(); ++i) {
|
for (int i = 0; i < pd->m_Renderers->GetNumberOfItems(); ++i) {
|
||||||
vtkRenderer *renderer = d->m_Renderers->GetNextItem();
|
vtkRenderer *renderer = pd->m_Renderers->GetNextItem();
|
||||||
renderer->RemoveActor(d->m_CubeAxesActor);
|
renderer->RemoveActor(pd->m_CubeAxesActor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -370,18 +413,12 @@ void Puppet::ShowScaleMeasures(bool show)
|
|||||||
|
|
||||||
void Puppet::SetRepresentation(Representation mode)
|
void Puppet::SetRepresentation(Representation mode)
|
||||||
{
|
{
|
||||||
int rep = VTK_SURFACE;
|
pd->m_Representation = static_cast<int>(mode);
|
||||||
switch (mode) {
|
|
||||||
case Points: rep = VTK_POINTS; break;
|
|
||||||
case Wireframe: rep = VTK_WIREFRAME; break;
|
|
||||||
case Surface: rep = VTK_SURFACE; break;
|
|
||||||
}
|
|
||||||
d->m_Representation = rep;
|
|
||||||
|
|
||||||
vtkProp3DCollection *props = d->m_Assembly->GetParts();
|
vtkProp3DCollection *props = pd->m_Assembly->GetParts();
|
||||||
props->InitTraversal();
|
props->InitTraversal();
|
||||||
for (int i = 0; i < props->GetNumberOfItems(); ++i) {
|
for (int i = 0; i < props->GetNumberOfItems(); ++i) {
|
||||||
d->ApplyAppearance(props->GetNextProp3D());
|
pd->ApplyAppearance(props->GetNextProp3D());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -391,29 +428,33 @@ void Puppet::SetRepresentation(const char *mode)
|
|||||||
if (s == "points") SetRepresentation(Points);
|
if (s == "points") SetRepresentation(Points);
|
||||||
else if (s == "wireframe") SetRepresentation(Wireframe);
|
else if (s == "wireframe") SetRepresentation(Wireframe);
|
||||||
else if (s == "shaded" || s == "surface") SetRepresentation(Surface);
|
else if (s == "shaded" || s == "surface") SetRepresentation(Surface);
|
||||||
|
else if (s == "edges" || s == "surface+edges" || s == "surfacewithedges") SetRepresentation(SurfaceWithEdges);
|
||||||
|
else if (s == "volume") SetRepresentation(Volume);
|
||||||
|
else if (s == "outline") SetRepresentation(Outline);
|
||||||
|
else if (s == "slice") SetRepresentation(Slice);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Puppet::SetColor(double r, double g, double b)
|
void Puppet::SetColor(double r, double g, double b)
|
||||||
{
|
{
|
||||||
d->m_Color[0] = r;
|
pd->m_Color[0] = r;
|
||||||
d->m_Color[1] = g;
|
pd->m_Color[1] = g;
|
||||||
d->m_Color[2] = b;
|
pd->m_Color[2] = b;
|
||||||
|
|
||||||
vtkProp3DCollection *props = d->m_Assembly->GetParts();
|
vtkProp3DCollection *props = pd->m_Assembly->GetParts();
|
||||||
props->InitTraversal();
|
props->InitTraversal();
|
||||||
for (int i = 0; i < props->GetNumberOfItems(); ++i) {
|
for (int i = 0; i < props->GetNumberOfItems(); ++i) {
|
||||||
d->ApplyAppearance(props->GetNextProp3D());
|
pd->ApplyAppearance(props->GetNextProp3D());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Puppet::SetOpacity(double alpha)
|
void Puppet::SetOpacity(double alpha)
|
||||||
{
|
{
|
||||||
d->m_Opacity = alpha;
|
pd->m_Opacity = alpha;
|
||||||
|
|
||||||
vtkProp3DCollection *props = d->m_Assembly->GetParts();
|
vtkProp3DCollection *props = pd->m_Assembly->GetParts();
|
||||||
props->InitTraversal();
|
props->InitTraversal();
|
||||||
for (int i = 0; i < props->GetNumberOfItems(); ++i) {
|
for (int i = 0; i < props->GetNumberOfItems(); ++i) {
|
||||||
d->ApplyAppearance(props->GetNextProp3D());
|
pd->ApplyAppearance(props->GetNextProp3D());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -425,8 +466,8 @@ void Puppet::SetOpacity(double alpha)
|
|||||||
|
|
||||||
void Puppet::SetSelectable(bool selectable)
|
void Puppet::SetSelectable(bool selectable)
|
||||||
{
|
{
|
||||||
d->m_Selectable = selectable;
|
pd->m_Selectable = selectable;
|
||||||
vtkProp3DCollection *props = d->m_Assembly->GetParts();
|
vtkProp3DCollection *props = pd->m_Assembly->GetParts();
|
||||||
props->InitTraversal();
|
props->InitTraversal();
|
||||||
for (int i = 0; i < props->GetNumberOfItems(); ++i) {
|
for (int i = 0; i < props->GetNumberOfItems(); ++i) {
|
||||||
props->GetNextProp3D()->SetPickable(selectable);
|
props->GetNextProp3D()->SetPickable(selectable);
|
||||||
@@ -435,43 +476,91 @@ void Puppet::SetSelectable(bool selectable)
|
|||||||
|
|
||||||
bool Puppet::IsSelectable() const
|
bool Puppet::IsSelectable() const
|
||||||
{
|
{
|
||||||
return d->m_Selectable;
|
return pd->m_Selectable;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Puppet::SetSelected(bool selected)
|
void Puppet::SetSelected(bool selected)
|
||||||
{
|
{
|
||||||
if (!d->m_Selectable) return;
|
if (!pd->m_Selectable) return;
|
||||||
if (d->m_Selected == selected) return;
|
if (pd->m_Selected == selected) return;
|
||||||
d->m_Selected = selected;
|
pd->m_Selected = selected;
|
||||||
d->UpdateHighlight();
|
pd->UpdateHighlight();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Puppet::IsSelected() const
|
bool Puppet::IsSelected() const
|
||||||
{
|
{
|
||||||
return d->m_Selected;
|
return pd->m_Selected;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Puppet::Update()
|
void Puppet::Update()
|
||||||
{
|
{
|
||||||
vtkProp3DCollection *props = d->m_Assembly->GetParts();
|
vtkProp* root = this->GetProp();
|
||||||
|
if (root) {
|
||||||
|
pd->ApplyAppearance(root);
|
||||||
|
|
||||||
|
// Apply transformation if it's a Prop3D
|
||||||
|
if (auto* p3d = vtkProp3D::SafeDownCast(root)) {
|
||||||
|
p3d->SetPosition(pd->m_Position.data());
|
||||||
|
p3d->SetOrientation(pd->m_Orientation.data());
|
||||||
|
p3d->SetScale(pd->m_Scale.data());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vtkProp3DCollection *props = pd->m_Assembly->GetParts();
|
||||||
props->InitTraversal();
|
props->InitTraversal();
|
||||||
for (int i = 0; i < props->GetNumberOfItems(); ++i) {
|
for (int i = 0; i < props->GetNumberOfItems(); ++i) {
|
||||||
d->ApplyAppearance(props->GetNextProp3D());
|
pd->ApplyAppearance(props->GetNextProp3D());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (d->m_Selected) {
|
if (pd->m_Selected) {
|
||||||
d->UpdateHighlight();
|
pd->UpdateHighlight();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (d->m_ShowBoundingBox) {
|
if (pd->m_ShowBoundingBox) {
|
||||||
double* bounds = d->m_Assembly->GetBounds();
|
double* bounds = pd->m_Assembly->GetBounds();
|
||||||
d->m_OutlineSource->SetBounds(bounds);
|
pd->m_OutlineSource->SetBounds(bounds);
|
||||||
d->m_OutlineSource->Update();
|
pd->m_OutlineSource->Update();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (d->m_ShowScaleMeasures) {
|
if (pd->m_ShowScaleMeasures) {
|
||||||
double* bounds = d->m_Assembly->GetBounds();
|
double* bounds = pd->m_Assembly->GetBounds();
|
||||||
d->m_CubeAxesActor->SetBounds(bounds);
|
pd->m_CubeAxesActor->SetBounds(bounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify that the object has been updated (important for UI refresh)
|
||||||
|
this->Object::Updated();
|
||||||
|
|
||||||
|
// Trigger immediate re-render of all connected viewports
|
||||||
|
pd->m_Renderers->InitTraversal();
|
||||||
|
for (int i = 0; i < pd->m_Renderers->GetNumberOfItems(); ++i) {
|
||||||
|
if (auto* ren = pd->m_Renderers->GetNextItem()) {
|
||||||
|
if (ren->GetRenderWindow()) ren->GetRenderWindow()->Render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Puppet::SyncFromVtk()
|
||||||
|
{
|
||||||
|
vtkProp* root = this->GetProp();
|
||||||
|
if (auto* p3d = vtkProp3D::SafeDownCast(root)) {
|
||||||
|
double pos[3], ori[3], scale[3];
|
||||||
|
p3d->GetPosition(pos);
|
||||||
|
p3d->GetOrientation(ori);
|
||||||
|
p3d->GetScale(scale);
|
||||||
|
|
||||||
|
// Update properties
|
||||||
|
for (int i=0; i<3; ++i) {
|
||||||
|
pd->m_Position(i) = pos[i];
|
||||||
|
pd->m_Orientation(i) = ori[i];
|
||||||
|
pd->m_Scale(i) = scale[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the properties from the object
|
||||||
|
if (auto* propPos = this->GetProperty("Position")) propPos->Updated();
|
||||||
|
if (auto* propOri = this->GetProperty("Orientation")) propOri->Updated();
|
||||||
|
if (auto* propScale = this->GetProperty("Scale")) propScale->Updated();
|
||||||
|
|
||||||
|
this->Object::Updated();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -479,12 +568,37 @@ void Puppet::ConnectInteractor(vtkRenderWindowInteractor *interactor)
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct TransformProxy {
|
||||||
|
PuppetData* pd;
|
||||||
|
template<class Archive>
|
||||||
|
void serialize(Archive & ar, const unsigned int version) {
|
||||||
|
ar & boost::serialization::make_hrp("Position", pd->m_Position, "mm");
|
||||||
|
ar & boost::serialization::make_hrp("Orientation", pd->m_Orientation, "deg");
|
||||||
|
ar & boost::serialization::make_hrp("Scale", pd->m_Scale, "");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AppearanceProxy {
|
||||||
|
PuppetData* pd;
|
||||||
|
template<class Archive>
|
||||||
|
void serialize(Archive & ar, const unsigned int version) {
|
||||||
|
ar & boost::serialization::make_hrp("ColorR", pd->m_Color[0]);
|
||||||
|
ar & boost::serialization::make_hrp("ColorG", pd->m_Color[1]);
|
||||||
|
ar & boost::serialization::make_hrp("ColorB", pd->m_Color[2]);
|
||||||
|
ar & boost::serialization::make_hrp("Opacity", pd->m_Opacity);
|
||||||
|
ar & boost::serialization::make_hrp_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", d->m_Color[0]);
|
AppearanceProxy appearance{pd};
|
||||||
ar & boost::serialization::make_hrp("ColorG", d->m_Color[1]);
|
ar & boost::serialization::make_nvp("Appearance", appearance);
|
||||||
ar & boost::serialization::make_hrp("ColorB", d->m_Color[2]);
|
|
||||||
ar & boost::serialization::make_hrp("Opacity", d->m_Opacity);
|
TransformProxy transform{pd};
|
||||||
ar & boost::serialization::make_hrp("Representation", d->m_Representation);
|
ar & boost::serialization::make_nvp("Transform", transform);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Puppet::serialize(Archive::xml_oarchive & ar, const unsigned int v) { }
|
void Puppet::serialize(Archive::xml_oarchive & ar, const unsigned int v) { }
|
||||||
|
|||||||
@@ -29,6 +29,9 @@
|
|||||||
#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"
|
||||||
@@ -61,6 +64,8 @@ public:
|
|||||||
|
|
||||||
virtual vtkPropCollection *GetProps();
|
virtual vtkPropCollection *GetProps();
|
||||||
|
|
||||||
|
virtual uLib::Object *GetContent() const { return nullptr; }
|
||||||
|
|
||||||
void ConnectRenderer(vtkRenderer *renderer);
|
void ConnectRenderer(vtkRenderer *renderer);
|
||||||
|
|
||||||
void DisconnectRenderer(vtkRenderer *renderer);
|
void DisconnectRenderer(vtkRenderer *renderer);
|
||||||
@@ -80,8 +85,9 @@ public:
|
|||||||
bool IsSelected() const;
|
bool IsSelected() const;
|
||||||
|
|
||||||
virtual void Update();
|
virtual void Update();
|
||||||
|
virtual void SyncFromVtk();
|
||||||
|
|
||||||
enum Representation { Points, Wireframe, Surface };
|
enum Representation { Points = 0, Wireframe = 1, Surface = 2, SurfaceWithEdges = 3, Volume = 4, Outline = 5, Slice = 6 };
|
||||||
void SetRepresentation(Representation mode);
|
void SetRepresentation(Representation mode);
|
||||||
void SetRepresentation(const char *mode);
|
void SetRepresentation(const char *mode);
|
||||||
|
|
||||||
@@ -107,6 +113,9 @@ public:
|
|||||||
|
|
||||||
virtual void ConnectInteractor(class vtkRenderWindowInteractor *interactor);
|
virtual void ConnectInteractor(class vtkRenderWindowInteractor *interactor);
|
||||||
|
|
||||||
|
void AddToViewer(class Viewport &viewer);
|
||||||
|
void RemoveFromViewer(class Viewport &viewer);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void SetProp(vtkProp *prop);
|
void SetProp(vtkProp *prop);
|
||||||
|
|
||||||
@@ -120,12 +129,23 @@ private:
|
|||||||
Puppet& operator=(const Puppet&) = delete;
|
Puppet& operator=(const Puppet&) = delete;
|
||||||
|
|
||||||
friend class PuppetData;
|
friend class PuppetData;
|
||||||
class PuppetData *d;
|
class PuppetData *pd;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Vtk
|
} // namespace Vtk
|
||||||
} // namespace uLib
|
} // namespace uLib
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------- //
|
||||||
|
// DISPLAY PROPERTIES SERIALIZE
|
||||||
// -------------------------------------------------------------------------- //
|
// -------------------------------------------------------------------------- //
|
||||||
|
|
||||||
namespace uLib {
|
namespace uLib {
|
||||||
@@ -140,20 +160,53 @@ 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) {
|
||||||
m_Puppet->RegisterDisplayProperty(
|
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());
|
||||||
new uLib::Property<T>(m_Puppet, t.name(), &const_cast<boost::serialization::hrp<T>&>(t).value())
|
m_Puppet->RegisterDisplayProperty(p);
|
||||||
);
|
Vtk::Puppet* puppet = m_Puppet;
|
||||||
|
uLib::Object::connect(p, &uLib::PropertyBase::Updated, [puppet](){ puppet->Update(); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
void save_override(const boost::serialization::hrp_enum<T> &t) {
|
||||||
|
if (m_Puppet) {
|
||||||
|
uLib::EnumProperty* p = new uLib::EnumProperty(m_Puppet, t.name(), (int*)&const_cast<boost::serialization::hrp_enum<T>&>(t).value(), t.labels(), t.units() ? t.units() : "", GetCurrentGroup());
|
||||||
|
m_Puppet->RegisterDisplayProperty(p);
|
||||||
|
Vtk::Puppet* puppet = m_Puppet;
|
||||||
|
uLib::Object::connect(p, &uLib::PropertyBase::Updated, [puppet](){ puppet->Update(); });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class T> void save_override(const boost::serialization::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) {}
|
||||||
@@ -166,6 +219,7 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
Vtk::Puppet* m_Puppet;
|
Vtk::Puppet* m_Puppet;
|
||||||
|
std::vector<std::string> m_GroupStack;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Archive
|
} // namespace Archive
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
#include <vtkRenderWindowInteractor.h>
|
#include <vtkRenderWindowInteractor.h>
|
||||||
#include <vtkRendererCollection.h>
|
#include <vtkRendererCollection.h>
|
||||||
#include <vtkSmartPointer.h>
|
#include <vtkSmartPointer.h>
|
||||||
@@ -67,53 +68,83 @@ vtkStandardNewMacro(vtkInteractorStyleNoSpin);
|
|||||||
namespace uLib {
|
namespace uLib {
|
||||||
namespace Vtk {
|
namespace Vtk {
|
||||||
|
|
||||||
|
struct ViewerData {
|
||||||
|
vtkRenderWindow *m_RenderWindow;
|
||||||
|
vtkSmartPointer<vtkRenderWindowInteractor> m_Interactor;
|
||||||
|
vtkSmartPointer<vtkButtonWidget> m_GridButton;
|
||||||
|
|
||||||
|
ViewerData() : m_RenderWindow(vtkRenderWindow::New()) {}
|
||||||
|
~ViewerData() {
|
||||||
|
if (m_Interactor) {
|
||||||
|
m_Interactor->SetRenderWindow(nullptr);
|
||||||
|
}
|
||||||
|
m_RenderWindow->Delete();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
///// VTK VIEWER //////////////////////////////////////////////////////////////
|
///// VTK VIEWER //////////////////////////////////////////////////////////////
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
Viewer::Viewer()
|
Viewer::Viewer()
|
||||||
: Viewport(), m_RenderWindow(vtkRenderWindow::New()) {
|
: Viewport(), dv(new ViewerData()) {
|
||||||
InstallPipe();
|
InstallPipe();
|
||||||
}
|
}
|
||||||
|
|
||||||
Viewer::~Viewer() {
|
Viewer::~Viewer() {
|
||||||
|
this->DisableHandler();
|
||||||
|
if (dv->m_GridButton) {
|
||||||
|
dv->m_GridButton->Off();
|
||||||
|
dv->m_GridButton->SetInteractor(nullptr);
|
||||||
|
dv->m_GridButton = nullptr;
|
||||||
|
}
|
||||||
|
if (this->GetRenderWindow()) {
|
||||||
|
this->GetRenderWindow()->RemoveAllObservers();
|
||||||
|
}
|
||||||
|
if (this->GetInteractor()) {
|
||||||
|
this->GetInteractor()->RemoveAllObservers();
|
||||||
|
}
|
||||||
UninstallPipe();
|
UninstallPipe();
|
||||||
m_RenderWindow->Delete();
|
delete dv;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Viewer::InstallPipe() {
|
void Viewer::InstallPipe() {
|
||||||
m_RenderWindow->AddRenderer(m_Renderer);
|
dv->m_RenderWindow->AddRenderer(this->GetRenderer());
|
||||||
m_RenderWindow->SetSize(600,600);
|
dv->m_RenderWindow->SetSize(600,600);
|
||||||
vtkSmartPointer<vtkRenderWindowInteractor> renderWindowInteractor =
|
if (std::getenv("CTEST_PROJECT_NAME")) {
|
||||||
vtkSmartPointer<vtkRenderWindowInteractor>::New();
|
dv->m_RenderWindow->SetOffScreenRendering(1);
|
||||||
renderWindowInteractor->SetRenderWindow(m_RenderWindow);
|
}
|
||||||
|
|
||||||
|
dv->m_Interactor = vtkSmartPointer<vtkRenderWindowInteractor>::New();
|
||||||
|
dv->m_Interactor->SetRenderWindow(dv->m_RenderWindow);
|
||||||
|
|
||||||
// Common setup
|
// Common setup
|
||||||
Viewport::SetupPipeline(renderWindowInteractor);
|
Viewport::SetupPipeline(dv->m_Interactor);
|
||||||
|
|
||||||
// Setup native grid button
|
// Setup native grid button
|
||||||
|
if (!std::getenv("CTEST_PROJECT_NAME")) {
|
||||||
SetupGridButton();
|
SetupGridButton();
|
||||||
|
}
|
||||||
|
|
||||||
// BUT we want to override the style with our custom NoSpin version
|
// BUT we want to override the style with our custom NoSpin version
|
||||||
vtkSmartPointer<vtkInteractorStyleNoSpin> style =
|
vtkSmartPointer<vtkInteractorStyleNoSpin> style =
|
||||||
vtkSmartPointer<vtkInteractorStyleNoSpin>::New();
|
vtkSmartPointer<vtkInteractorStyleNoSpin>::New();
|
||||||
renderWindowInteractor->SetInteractorStyle(style);
|
dv->m_Interactor->SetInteractorStyle(style);
|
||||||
|
|
||||||
// Must be rendered here in Vtk-6.0 or seg-fault //
|
// Must be rendered here in Vtk-6.0 or seg-fault //
|
||||||
m_RenderWindow->Render();
|
if (!std::getenv("CTEST_PROJECT_NAME")) {
|
||||||
}
|
dv->m_RenderWindow->Render();
|
||||||
|
|
||||||
void Viewer::UninstallPipe() {
|
|
||||||
if (m_Renderer) {
|
|
||||||
m_Renderer->RemoveAllViewProps();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Viewer::Render() {
|
void Viewer::UninstallPipe() {
|
||||||
if (m_RenderWindow)
|
|
||||||
m_RenderWindow->Render();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vtkSmartPointer<vtkCameraOrientationWidget>
|
void Viewer::Render() {
|
||||||
|
if (dv->m_RenderWindow)
|
||||||
|
dv->m_RenderWindow->Render();
|
||||||
|
}
|
||||||
|
|
||||||
|
vtkCameraOrientationWidget *
|
||||||
Viewer::MakeCameraOrientationWidget(vtkRenderWindowInteractor *interactor,
|
Viewer::MakeCameraOrientationWidget(vtkRenderWindowInteractor *interactor,
|
||||||
vtkRenderer *renderer) {
|
vtkRenderer *renderer) {
|
||||||
vtkSmartPointer<vtkCameraOrientationWidget> widget =
|
vtkSmartPointer<vtkCameraOrientationWidget> widget =
|
||||||
@@ -125,7 +156,7 @@ Viewer::MakeCameraOrientationWidget(vtkRenderWindowInteractor *interactor,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Viewer::SetupGridButton() {
|
void Viewer::SetupGridButton() {
|
||||||
if (!m_RenderWindow || !m_RenderWindow->GetInteractor()) return;
|
if (!dv->m_RenderWindow || !dv->m_RenderWindow->GetInteractor()) return;
|
||||||
|
|
||||||
// Create procedural textures for the button using canvas
|
// Create procedural textures for the button using canvas
|
||||||
vtkNew<vtkImageCanvasSource2D> canvas;
|
vtkNew<vtkImageCanvasSource2D> canvas;
|
||||||
@@ -158,9 +189,9 @@ void Viewer::SetupGridButton() {
|
|||||||
rep->SetButtonTexture(0, imgOff);
|
rep->SetButtonTexture(0, imgOff);
|
||||||
rep->SetButtonTexture(1, imgOn);
|
rep->SetButtonTexture(1, imgOn);
|
||||||
|
|
||||||
m_GridButton = vtkSmartPointer<vtkButtonWidget>::New();
|
dv->m_GridButton = vtkSmartPointer<vtkButtonWidget>::New();
|
||||||
m_GridButton->SetInteractor(m_RenderWindow->GetInteractor());
|
dv->m_GridButton->SetInteractor(dv->m_RenderWindow->GetInteractor());
|
||||||
m_GridButton->SetRepresentation(rep);
|
dv->m_GridButton->SetRepresentation(rep);
|
||||||
|
|
||||||
// Position it initially
|
// Position it initially
|
||||||
UpdateGridButtonPosition();
|
UpdateGridButtonPosition();
|
||||||
@@ -172,7 +203,7 @@ void Viewer::SetupGridButton() {
|
|||||||
auto* v = static_cast<Viewer*>(clientdata);
|
auto* v = static_cast<Viewer*>(clientdata);
|
||||||
v->UpdateGridButtonPosition();
|
v->UpdateGridButtonPosition();
|
||||||
});
|
});
|
||||||
m_RenderWindow->AddObserver(vtkCommand::ModifiedEvent, resizeCallback);
|
dv->m_RenderWindow->AddObserver(vtkCommand::ModifiedEvent, resizeCallback);
|
||||||
|
|
||||||
// Callback for state change
|
// Callback for state change
|
||||||
vtkNew<vtkCallbackCommand> stateCallback;
|
vtkNew<vtkCallbackCommand> stateCallback;
|
||||||
@@ -184,19 +215,19 @@ void Viewer::SetupGridButton() {
|
|||||||
v->SetGridVisible(r->GetState() == 1);
|
v->SetGridVisible(r->GetState() == 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
m_GridButton->AddObserver(vtkCommand::StateChangedEvent, stateCallback);
|
dv->m_GridButton->AddObserver(vtkCommand::StateChangedEvent, stateCallback);
|
||||||
m_GridButton->On();
|
dv->m_GridButton->On();
|
||||||
|
|
||||||
// Set initial state
|
// Set initial state
|
||||||
rep->SetState(GetGridVisible() ? 1 : 0);
|
rep->SetState(GetGridVisible() ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Viewer::UpdateGridButtonPosition() {
|
void Viewer::UpdateGridButtonPosition() {
|
||||||
if (!m_GridButton || !m_RenderWindow) return;
|
if (!dv->m_GridButton || !dv->m_RenderWindow) return;
|
||||||
auto* rep = vtkTexturedButtonRepresentation2D::SafeDownCast(m_GridButton->GetRepresentation());
|
auto* rep = vtkTexturedButtonRepresentation2D::SafeDownCast(dv->m_GridButton->GetRepresentation());
|
||||||
if (!rep) return;
|
if (!rep) return;
|
||||||
|
|
||||||
int *sz = m_RenderWindow->GetSize();
|
int *sz = dv->m_RenderWindow->GetSize();
|
||||||
if (sz[0] == 0 || sz[1] == 0) return; // Window not yet sized or hidden
|
if (sz[0] == 0 || sz[1] == 0) return; // Window not yet sized or hidden
|
||||||
|
|
||||||
int margin_rigth = 23;
|
int margin_rigth = 23;
|
||||||
@@ -207,12 +238,15 @@ void Viewer::UpdateGridButtonPosition() {
|
|||||||
rep->PlaceWidget(bds);
|
rep->PlaceWidget(bds);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Viewer::Start() { m_RenderWindow->GetInteractor()->Start(); }
|
void Viewer::Start() {
|
||||||
|
if (std::getenv("CTEST_PROJECT_NAME")) return;
|
||||||
|
dv->m_RenderWindow->GetInteractor()->Start();
|
||||||
|
}
|
||||||
|
|
||||||
vtkRenderWindow *Viewer::GetRenderWindow() { return m_RenderWindow; }
|
vtkRenderWindow *Viewer::GetRenderWindow() { return dv->m_RenderWindow; }
|
||||||
|
|
||||||
vtkRenderWindowInteractor *Viewer::GetInteractor() {
|
vtkRenderWindowInteractor *Viewer::GetInteractor() {
|
||||||
return m_RenderWindow->GetInteractor();
|
return dv->m_RenderWindow->GetInteractor();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Vtk
|
} // namespace Vtk
|
||||||
|
|||||||
@@ -1,44 +1,17 @@
|
|||||||
/*//////////////////////////////////////////////////////////////////////////////
|
|
||||||
// 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 ULIBVTKVIEWER_H
|
#ifndef ULIBVTKVIEWER_H
|
||||||
#define ULIBVTKVIEWER_H
|
#define ULIBVTKVIEWER_H
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
#include "vtkViewport.h"
|
#include "vtkViewport.h"
|
||||||
|
|
||||||
|
class vtkRenderWindow;
|
||||||
|
class vtkRenderWindowInteractor;
|
||||||
|
class vtkRenderer;
|
||||||
|
class vtkCameraOrientationWidget;
|
||||||
|
|
||||||
namespace uLib {
|
namespace uLib {
|
||||||
namespace Vtk {
|
namespace Vtk {
|
||||||
|
|
||||||
// template <class T> class Tie {
|
struct ViewerData;
|
||||||
// public:
|
|
||||||
// void DoAction() {
|
|
||||||
// std::cout << "Tie::DoAction -> generic Tie does nothing\n";
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
class Viewer : public Viewport {
|
class Viewer : public Viewport {
|
||||||
|
|
||||||
@@ -49,7 +22,7 @@ public:
|
|||||||
// Render scene
|
// Render scene
|
||||||
virtual void Render() override;
|
virtual void Render() override;
|
||||||
|
|
||||||
static vtkSmartPointer<vtkCameraOrientationWidget>
|
static vtkCameraOrientationWidget *
|
||||||
MakeCameraOrientationWidget(vtkRenderWindowInteractor *interactor,
|
MakeCameraOrientationWidget(vtkRenderWindowInteractor *interactor,
|
||||||
vtkRenderer *renderer);
|
vtkRenderer *renderer);
|
||||||
|
|
||||||
@@ -65,15 +38,9 @@ private:
|
|||||||
void SetupGridButton();
|
void SetupGridButton();
|
||||||
void UpdateGridButtonPosition();
|
void UpdateGridButtonPosition();
|
||||||
|
|
||||||
vtkRenderWindow *m_RenderWindow;
|
struct ViewerData *dv;
|
||||||
vtkSmartPointer<class vtkButtonWidget> m_GridButton;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// template <> class Tie<Viewer> {
|
|
||||||
// public:
|
|
||||||
// void DoAction() { std::cout << " VIEWER TIE !!! \n"; }
|
|
||||||
// };
|
|
||||||
|
|
||||||
} // namespace Vtk
|
} // namespace Vtk
|
||||||
} // namespace uLib
|
} // namespace uLib
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
|
|
||||||
#include "vtkHandlerWidget.h"
|
#include "vtkHandlerWidget.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <vtkActor.h>
|
||||||
#include <vtkArcSource.h>
|
#include <vtkArcSource.h>
|
||||||
#include <vtkArrowSource.h>
|
#include <vtkArrowSource.h>
|
||||||
#include <vtkCallbackCommand.h>
|
#include <vtkCallbackCommand.h>
|
||||||
@@ -51,32 +52,61 @@
|
|||||||
namespace uLib {
|
namespace uLib {
|
||||||
namespace Vtk {
|
namespace Vtk {
|
||||||
|
|
||||||
|
struct HandlerWidgetData {
|
||||||
|
vtkSmartPointer<::vtkRenderer> m_OverlayRenderer;
|
||||||
|
::vtkProp *m_HighlightedProp;
|
||||||
|
|
||||||
|
// Visual components //
|
||||||
|
vtkSmartPointer<::vtkActor> m_AxesX, m_AxesY, m_AxesZ; // Arrows
|
||||||
|
vtkSmartPointer<::vtkActor> m_RotX, m_RotY, m_RotZ; // Rings
|
||||||
|
vtkSmartPointer<::vtkActor> m_RotCam; // Camera ring
|
||||||
|
vtkSmartPointer<::vtkActor> m_ScaleX, m_ScaleY, m_ScaleZ; // Cubes
|
||||||
|
|
||||||
|
vtkSmartPointer<::vtkPlane> m_ClipPlane;
|
||||||
|
|
||||||
|
vtkSmartPointer<::vtkCellPicker> m_Picker;
|
||||||
|
vtkSmartPointer<::vtkTransform> m_InitialTransform;
|
||||||
|
|
||||||
|
std::vector<vtkSmartPointer<::vtkTransform>> m_TransformChain;
|
||||||
|
vtkSmartPointer<::vtkMatrix4x4> m_BaseMatrix;
|
||||||
|
|
||||||
|
HandlerWidgetData() {
|
||||||
|
m_Picker = vtkSmartPointer<::vtkCellPicker>::New();
|
||||||
|
m_InitialTransform = vtkSmartPointer<::vtkTransform>::New();
|
||||||
|
m_ClipPlane = vtkSmartPointer<::vtkPlane>::New();
|
||||||
|
m_OverlayRenderer = vtkSmartPointer<::vtkRenderer>::New();
|
||||||
|
m_BaseMatrix = vtkSmartPointer<::vtkMatrix4x4>::New();
|
||||||
|
m_HighlightedProp = nullptr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
vtkStandardNewMacro(vtkHandlerWidget);
|
vtkStandardNewMacro(vtkHandlerWidget);
|
||||||
|
|
||||||
vtkHandlerWidget::vtkHandlerWidget() {
|
vtkHandlerWidget::vtkHandlerWidget() : d(new HandlerWidgetData()) {
|
||||||
this->Interaction = IDLE;
|
this->Interaction = IDLE;
|
||||||
this->m_Picker = vtkSmartPointer<::vtkCellPicker>::New();
|
d->m_Picker->SetTolerance(0.01); // Increased tolerance for thin gizmos
|
||||||
this->m_Picker->SetTolerance(0.01); // Increased tolerance for thin gizmos
|
|
||||||
this->m_InitialTransform = vtkSmartPointer<::vtkTransform>::New();
|
|
||||||
this->EventCallbackCommand->SetCallback(vtkHandlerWidget::ProcessEvents);
|
this->EventCallbackCommand->SetCallback(vtkHandlerWidget::ProcessEvents);
|
||||||
this->EventCallbackCommand->SetClientData(this);
|
this->EventCallbackCommand->SetClientData(this);
|
||||||
this->m_Frame = LOCAL;
|
this->m_Frame = LOCAL;
|
||||||
this->m_HighlightedProp = nullptr;
|
d->m_OverlayRenderer->SetLayer(1);
|
||||||
this->m_ClipPlane = vtkSmartPointer<::vtkPlane>::New();
|
d->m_OverlayRenderer->EraseOff();
|
||||||
this->m_OverlayRenderer = vtkSmartPointer<::vtkRenderer>::New();
|
d->m_OverlayRenderer->InteractiveOff();
|
||||||
this->m_OverlayRenderer->SetLayer(1);
|
|
||||||
this->m_OverlayRenderer->EraseOff();
|
|
||||||
this->m_OverlayRenderer->InteractiveOff();
|
|
||||||
this->Priority = 50.0; // Higher priority to beat camera style
|
this->Priority = 50.0; // Higher priority to beat camera style
|
||||||
this->m_TranslationEnabled = true;
|
this->m_TranslationEnabled = true;
|
||||||
this->m_RotationEnabled = true;
|
this->m_RotationEnabled = true;
|
||||||
this->m_ScalingEnabled = true;
|
this->m_ScalingEnabled = true;
|
||||||
this->m_BaseMatrix = vtkSmartPointer<::vtkMatrix4x4>::New();
|
d->m_BaseMatrix->Identity();
|
||||||
this->m_BaseMatrix->Identity();
|
|
||||||
this->CreateGizmos();
|
this->CreateGizmos();
|
||||||
}
|
}
|
||||||
|
|
||||||
vtkHandlerWidget::~vtkHandlerWidget() {}
|
vtkHandlerWidget::~vtkHandlerWidget() {
|
||||||
|
this->SetEnabled(0);
|
||||||
|
delete d;
|
||||||
|
}
|
||||||
|
|
||||||
|
::vtkRenderer *vtkHandlerWidget::GetOverlayRenderer() {
|
||||||
|
return d->m_OverlayRenderer;
|
||||||
|
}
|
||||||
|
|
||||||
void vtkHandlerWidget::SetProp3D(::vtkProp3D *prop) {
|
void vtkHandlerWidget::SetProp3D(::vtkProp3D *prop) {
|
||||||
if (this->Prop3D == prop) {
|
if (this->Prop3D == prop) {
|
||||||
@@ -84,13 +114,13 @@ void vtkHandlerWidget::SetProp3D(::vtkProp3D *prop) {
|
|||||||
}
|
}
|
||||||
this->Prop3D = prop;
|
this->Prop3D = prop;
|
||||||
if (this->Prop3D) {
|
if (this->Prop3D) {
|
||||||
// Initialize m_BaseMatrix from the object's current matrix
|
// Initialize d->m_BaseMatrix from the object's current matrix
|
||||||
if (this->Prop3D->GetUserMatrix()) {
|
if (this->Prop3D->GetUserMatrix()) {
|
||||||
this->m_BaseMatrix->DeepCopy(this->Prop3D->GetUserMatrix());
|
this->d->m_BaseMatrix->DeepCopy(this->Prop3D->GetUserMatrix());
|
||||||
} else {
|
} else {
|
||||||
this->m_BaseMatrix->Identity();
|
this->d->m_BaseMatrix->Identity();
|
||||||
}
|
}
|
||||||
this->m_TransformChain.clear(); // Clear any previous transform chain
|
this->d->m_TransformChain.clear(); // Clear any previous transform chain
|
||||||
this->UpdateGizmoPosition();
|
this->UpdateGizmoPosition();
|
||||||
}
|
}
|
||||||
this->Modified();
|
this->Modified();
|
||||||
@@ -141,20 +171,20 @@ void vtkHandlerWidget::SetEnabled(int enabling) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sync Viewport and Camera
|
// Sync Viewport and Camera
|
||||||
this->m_OverlayRenderer->SetViewport(this->CurrentRenderer->GetViewport());
|
this->d->m_OverlayRenderer->SetViewport(this->CurrentRenderer->GetViewport());
|
||||||
this->m_OverlayRenderer->SetActiveCamera(this->CurrentRenderer->GetActiveCamera());
|
this->d->m_OverlayRenderer->SetActiveCamera(this->CurrentRenderer->GetActiveCamera());
|
||||||
win->AddRenderer(this->m_OverlayRenderer);
|
win->AddRenderer(this->d->m_OverlayRenderer);
|
||||||
|
|
||||||
this->m_OverlayRenderer->AddActor(m_AxesX);
|
this->d->m_OverlayRenderer->AddActor(d->m_AxesX);
|
||||||
this->m_OverlayRenderer->AddActor(m_AxesY);
|
this->d->m_OverlayRenderer->AddActor(d->m_AxesY);
|
||||||
this->m_OverlayRenderer->AddActor(m_AxesZ);
|
this->d->m_OverlayRenderer->AddActor(d->m_AxesZ);
|
||||||
this->m_OverlayRenderer->AddActor(m_RotX);
|
this->d->m_OverlayRenderer->AddActor(d->m_RotX);
|
||||||
this->m_OverlayRenderer->AddActor(m_RotY);
|
this->d->m_OverlayRenderer->AddActor(d->m_RotY);
|
||||||
this->m_OverlayRenderer->AddActor(m_RotZ);
|
this->d->m_OverlayRenderer->AddActor(d->m_RotZ);
|
||||||
this->m_OverlayRenderer->AddActor(m_RotCam);
|
this->d->m_OverlayRenderer->AddActor(d->m_RotCam);
|
||||||
this->m_OverlayRenderer->AddActor(m_ScaleX);
|
this->d->m_OverlayRenderer->AddActor(d->m_ScaleX);
|
||||||
this->m_OverlayRenderer->AddActor(m_ScaleY);
|
this->d->m_OverlayRenderer->AddActor(d->m_ScaleY);
|
||||||
this->m_OverlayRenderer->AddActor(m_ScaleZ);
|
this->d->m_OverlayRenderer->AddActor(d->m_ScaleZ);
|
||||||
|
|
||||||
this->UpdateVisibility();
|
this->UpdateVisibility();
|
||||||
|
|
||||||
@@ -165,11 +195,14 @@ void vtkHandlerWidget::SetEnabled(int enabling) {
|
|||||||
|
|
||||||
this->Enabled = 0;
|
this->Enabled = 0;
|
||||||
this->Highlight(nullptr);
|
this->Highlight(nullptr);
|
||||||
|
if (this->Interactor) {
|
||||||
this->Interactor->RemoveObserver(this->EventCallbackCommand);
|
this->Interactor->RemoveObserver(this->EventCallbackCommand);
|
||||||
if (this->Interactor->GetRenderWindow()) {
|
if (this->Interactor->GetRenderWindow()) {
|
||||||
this->Interactor->GetRenderWindow()->RemoveRenderer(this->m_OverlayRenderer);
|
this->Interactor->GetRenderWindow()->MakeCurrent();
|
||||||
|
this->Interactor->GetRenderWindow()->RemoveRenderer(this->d->m_OverlayRenderer);
|
||||||
}
|
}
|
||||||
this->m_OverlayRenderer->RemoveAllViewProps();
|
}
|
||||||
|
this->d->m_OverlayRenderer->RemoveAllViewProps();
|
||||||
this->InvokeEvent(::vtkCommand::DisableEvent, nullptr);
|
this->InvokeEvent(::vtkCommand::DisableEvent, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,15 +247,15 @@ 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->m_TransformChain.empty()) {
|
if (!this->d->m_TransformChain.empty()) {
|
||||||
std::cout << "Undoing last transform action..." << std::endl;
|
std::cout << "Undoing last transform action..." << std::endl;
|
||||||
this->m_TransformChain.pop_back();
|
this->d->m_TransformChain.pop_back();
|
||||||
|
|
||||||
// Update object from chain
|
// Update object from chain
|
||||||
vtkNew<vtkTransform> total;
|
vtkNew<vtkTransform> total;
|
||||||
total->PostMultiply();
|
total->PostMultiply();
|
||||||
total->SetMatrix(this->m_BaseMatrix.GetPointer());
|
total->SetMatrix(this->d->m_BaseMatrix.GetPointer());
|
||||||
for (auto& t : m_TransformChain) {
|
for (auto& t : d->m_TransformChain) {
|
||||||
total->Concatenate(t);
|
total->Concatenate(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,33 +278,33 @@ void vtkHandlerWidget::OnLeftButtonDown() {
|
|||||||
this->CurrentRenderer = this->Interactor->FindPokedRenderer(X, Y);
|
this->CurrentRenderer = this->Interactor->FindPokedRenderer(X, Y);
|
||||||
}
|
}
|
||||||
|
|
||||||
this->m_Picker->Pick(X, Y, 0.0, this->m_OverlayRenderer);
|
this->d->m_Picker->Pick(X, Y, 0.0, this->d->m_OverlayRenderer);
|
||||||
::vtkProp *prop = this->m_Picker->GetViewProp();
|
::vtkProp *prop = this->d->m_Picker->GetViewProp();
|
||||||
this->m_Picker->GetPickPosition(this->m_StartPickPosition);
|
this->d->m_Picker->GetPickPosition(this->m_StartPickPosition);
|
||||||
|
|
||||||
if (!prop)
|
if (!prop)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this->Interaction = IDLE;
|
this->Interaction = IDLE;
|
||||||
if (prop == m_AxesX)
|
if (prop == d->m_AxesX)
|
||||||
this->Interaction = TRANS_X;
|
this->Interaction = TRANS_X;
|
||||||
else if (prop == m_AxesY)
|
else if (prop == d->m_AxesY)
|
||||||
this->Interaction = TRANS_Y;
|
this->Interaction = TRANS_Y;
|
||||||
else if (prop == m_AxesZ)
|
else if (prop == d->m_AxesZ)
|
||||||
this->Interaction = TRANS_Z;
|
this->Interaction = TRANS_Z;
|
||||||
else if (prop == m_RotX)
|
else if (prop == d->m_RotX)
|
||||||
this->Interaction = ROT_X;
|
this->Interaction = ROT_X;
|
||||||
else if (prop == m_RotY)
|
else if (prop == d->m_RotY)
|
||||||
this->Interaction = ROT_Y;
|
this->Interaction = ROT_Y;
|
||||||
else if (prop == m_RotZ)
|
else if (prop == d->m_RotZ)
|
||||||
this->Interaction = ROT_Z;
|
this->Interaction = ROT_Z;
|
||||||
else if (prop == m_ScaleX)
|
else if (prop == d->m_ScaleX)
|
||||||
this->Interaction = SCALE_X;
|
this->Interaction = SCALE_X;
|
||||||
else if (prop == m_ScaleY)
|
else if (prop == d->m_ScaleY)
|
||||||
this->Interaction = SCALE_Y;
|
this->Interaction = SCALE_Y;
|
||||||
else if (prop == m_ScaleZ)
|
else if (prop == d->m_ScaleZ)
|
||||||
this->Interaction = SCALE_Z;
|
this->Interaction = SCALE_Z;
|
||||||
else if (prop == m_RotCam)
|
else if (prop == d->m_RotCam)
|
||||||
this->Interaction = ROT_CAM;
|
this->Interaction = ROT_CAM;
|
||||||
|
|
||||||
if (this->Interaction != IDLE) {
|
if (this->Interaction != IDLE) {
|
||||||
@@ -285,14 +318,14 @@ void vtkHandlerWidget::OnLeftButtonDown() {
|
|||||||
|
|
||||||
// If the chain is empty, initialize base from current state?
|
// If the chain is empty, initialize base from current state?
|
||||||
// Actually, if we just started selecting this object, we should have initialized BaseMatrix.
|
// Actually, if we just started selecting this object, we should have initialized BaseMatrix.
|
||||||
// For now, let's keep m_InitialTransform as the state BEFORE this drag
|
// For now, let's keep d->m_InitialTransform as the state BEFORE this drag
|
||||||
vtkNew<vtkTransform> current;
|
vtkNew<vtkTransform> current;
|
||||||
current->PostMultiply();
|
current->PostMultiply();
|
||||||
current->SetMatrix(this->m_BaseMatrix.GetPointer());
|
current->SetMatrix(this->d->m_BaseMatrix.GetPointer());
|
||||||
for (auto& t : m_TransformChain) {
|
for (auto& t : d->m_TransformChain) {
|
||||||
current->Concatenate(t);
|
current->Concatenate(t);
|
||||||
}
|
}
|
||||||
this->m_InitialTransform->SetMatrix(current->GetMatrix());
|
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);
|
||||||
@@ -310,10 +343,10 @@ void vtkHandlerWidget::OnLeftButtonUp() {
|
|||||||
|
|
||||||
// We need to re-calculate the final 'op' to store it
|
// 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
|
// Actually, we could have stored it in OnMouseMove, but let's re-calculate or
|
||||||
// just capture the delta between m_InitialTransform and current UserMatrix.
|
// just capture the delta between d->m_InitialTransform and current UserMatrix.
|
||||||
if (this->Prop3D && this->Prop3D->GetUserMatrix()) {
|
if (this->Prop3D && this->Prop3D->GetUserMatrix()) {
|
||||||
vtkNew<vtkMatrix4x4> inv;
|
vtkNew<vtkMatrix4x4> inv;
|
||||||
vtkMatrix4x4::Invert(this->m_InitialTransform->GetMatrix(), inv);
|
vtkMatrix4x4::Invert(this->d->m_InitialTransform->GetMatrix(), inv);
|
||||||
|
|
||||||
vtkNew<vtkMatrix4x4> final_op_mat;
|
vtkNew<vtkMatrix4x4> final_op_mat;
|
||||||
vtkMatrix4x4::Multiply4x4(this->Prop3D->GetUserMatrix(), inv, final_op_mat);
|
vtkMatrix4x4::Multiply4x4(this->Prop3D->GetUserMatrix(), inv, final_op_mat);
|
||||||
@@ -321,8 +354,8 @@ void vtkHandlerWidget::OnLeftButtonUp() {
|
|||||||
vtkNew<vtkTransform> final_op;
|
vtkNew<vtkTransform> final_op;
|
||||||
final_op->SetMatrix(final_op_mat);
|
final_op->SetMatrix(final_op_mat);
|
||||||
|
|
||||||
this->m_TransformChain.push_back(final_op);
|
this->d->m_TransformChain.push_back(final_op);
|
||||||
std::cout << "Action finalized. Chain size: " << this->m_TransformChain.size() << std::endl;
|
std::cout << "Action finalized. Chain size: " << this->d->m_TransformChain.size() << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
this->Interaction = IDLE;
|
this->Interaction = IDLE;
|
||||||
@@ -339,8 +372,8 @@ void vtkHandlerWidget::OnMouseMove() {
|
|||||||
int Y = this->Interactor->GetEventPosition()[1];
|
int Y = this->Interactor->GetEventPosition()[1];
|
||||||
|
|
||||||
if (this->Interaction == IDLE) {
|
if (this->Interaction == IDLE) {
|
||||||
this->m_Picker->Pick(X, Y, 0.0, this->m_OverlayRenderer);
|
this->d->m_Picker->Pick(X, Y, 0.0, this->d->m_OverlayRenderer);
|
||||||
::vtkProp *prop = this->m_Picker->GetViewProp();
|
::vtkProp *prop = this->d->m_Picker->GetViewProp();
|
||||||
this->Highlight(prop);
|
this->Highlight(prop);
|
||||||
this->UpdateGizmoPosition(); // Ensure camera adjustments happen
|
this->UpdateGizmoPosition(); // Ensure camera adjustments happen
|
||||||
return;
|
return;
|
||||||
@@ -353,7 +386,7 @@ void vtkHandlerWidget::OnMouseMove() {
|
|||||||
// std::cout << "Interaction " << this->Interaction << " dx=" << dx << " dy=" << dy << std::endl;
|
// std::cout << "Interaction " << this->Interaction << " dx=" << dx << " dy=" << dy << std::endl;
|
||||||
|
|
||||||
// Get current gizmo properties from its actors
|
// Get current gizmo properties from its actors
|
||||||
vtkMatrix4x4 *gizmo_mat = m_AxesX->GetUserMatrix();
|
vtkMatrix4x4 *gizmo_mat = d->m_AxesX->GetUserMatrix();
|
||||||
if (!gizmo_mat)
|
if (!gizmo_mat)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -542,7 +575,7 @@ void vtkHandlerWidget::OnMouseMove() {
|
|||||||
// Total transform = Base * Chain * Interaction
|
// Total transform = Base * Chain * Interaction
|
||||||
vtkNew<vtkTransform> total;
|
vtkNew<vtkTransform> total;
|
||||||
total->PostMultiply();
|
total->PostMultiply();
|
||||||
total->SetMatrix(this->m_InitialTransform->GetMatrix()); // 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();
|
vtkMatrix4x4* targetMat = this->Prop3D->GetUserMatrix();
|
||||||
@@ -588,41 +621,41 @@ void vtkHandlerWidget::SetScalingEnabled(bool enabled) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void vtkHandlerWidget::UpdateVisibility() {
|
void vtkHandlerWidget::UpdateVisibility() {
|
||||||
if (!m_AxesX) return;
|
if (!d->m_AxesX) return;
|
||||||
|
|
||||||
m_AxesX->SetVisibility(m_TranslationEnabled);
|
d->m_AxesX->SetVisibility(m_TranslationEnabled);
|
||||||
m_AxesY->SetVisibility(m_TranslationEnabled);
|
d->m_AxesY->SetVisibility(m_TranslationEnabled);
|
||||||
m_AxesZ->SetVisibility(m_TranslationEnabled);
|
d->m_AxesZ->SetVisibility(m_TranslationEnabled);
|
||||||
|
|
||||||
m_RotX->SetVisibility(m_RotationEnabled);
|
d->m_RotX->SetVisibility(m_RotationEnabled);
|
||||||
m_RotY->SetVisibility(m_RotationEnabled);
|
d->m_RotY->SetVisibility(m_RotationEnabled);
|
||||||
m_RotZ->SetVisibility(m_RotationEnabled);
|
d->m_RotZ->SetVisibility(m_RotationEnabled);
|
||||||
m_RotCam->SetVisibility(m_RotationEnabled);
|
d->m_RotCam->SetVisibility(m_RotationEnabled);
|
||||||
|
|
||||||
m_ScaleX->SetVisibility(m_ScalingEnabled);
|
d->m_ScaleX->SetVisibility(m_ScalingEnabled);
|
||||||
m_ScaleY->SetVisibility(m_ScalingEnabled);
|
d->m_ScaleY->SetVisibility(m_ScalingEnabled);
|
||||||
m_ScaleZ->SetVisibility(m_ScalingEnabled);
|
d->m_ScaleZ->SetVisibility(m_ScalingEnabled);
|
||||||
|
|
||||||
// Update picker list
|
// Update picker list
|
||||||
if (m_Picker) {
|
if (d->m_Picker) {
|
||||||
m_Picker->InitializePickList();
|
d->m_Picker->InitializePickList();
|
||||||
if (m_TranslationEnabled) {
|
if (m_TranslationEnabled) {
|
||||||
m_Picker->AddPickList(m_AxesX);
|
d->m_Picker->AddPickList(d->m_AxesX);
|
||||||
m_Picker->AddPickList(m_AxesY);
|
d->m_Picker->AddPickList(d->m_AxesY);
|
||||||
m_Picker->AddPickList(m_AxesZ);
|
d->m_Picker->AddPickList(d->m_AxesZ);
|
||||||
}
|
}
|
||||||
if (m_RotationEnabled) {
|
if (m_RotationEnabled) {
|
||||||
m_Picker->AddPickList(m_RotX);
|
d->m_Picker->AddPickList(d->m_RotX);
|
||||||
m_Picker->AddPickList(m_RotY);
|
d->m_Picker->AddPickList(d->m_RotY);
|
||||||
m_Picker->AddPickList(m_RotZ);
|
d->m_Picker->AddPickList(d->m_RotZ);
|
||||||
m_Picker->AddPickList(m_RotCam);
|
d->m_Picker->AddPickList(d->m_RotCam);
|
||||||
}
|
}
|
||||||
if (m_ScalingEnabled) {
|
if (m_ScalingEnabled) {
|
||||||
m_Picker->AddPickList(m_ScaleX);
|
d->m_Picker->AddPickList(d->m_ScaleX);
|
||||||
m_Picker->AddPickList(m_ScaleY);
|
d->m_Picker->AddPickList(d->m_ScaleY);
|
||||||
m_Picker->AddPickList(m_ScaleZ);
|
d->m_Picker->AddPickList(d->m_ScaleZ);
|
||||||
}
|
}
|
||||||
m_Picker->PickFromListOn();
|
d->m_Picker->PickFromListOn();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -676,7 +709,7 @@ void vtkHandlerWidget::CreateGizmos() {
|
|||||||
|
|
||||||
auto mapper = vtkSmartPointer<::vtkPolyDataMapper>::New();
|
auto mapper = vtkSmartPointer<::vtkPolyDataMapper>::New();
|
||||||
mapper->SetInputConnection(circle->GetOutputPort());
|
mapper->SetInputConnection(circle->GetOutputPort());
|
||||||
mapper->AddClippingPlane(this->m_ClipPlane);
|
mapper->AddClippingPlane(this->d->m_ClipPlane);
|
||||||
|
|
||||||
auto actor = vtkSmartPointer<::vtkActor>::New();
|
auto actor = vtkSmartPointer<::vtkActor>::New();
|
||||||
actor->SetMapper(mapper);
|
actor->SetMapper(mapper);
|
||||||
@@ -690,15 +723,15 @@ void vtkHandlerWidget::CreateGizmos() {
|
|||||||
blue[] = {0.0, 0.0, 1.0}, white[] = {1.0, 1.0, 1.0};
|
blue[] = {0.0, 0.0, 1.0}, white[] = {1.0, 1.0, 1.0};
|
||||||
|
|
||||||
double x[] = {1, 0, 0}, y[] = {0, 1, 0}, z[] = {0, 0, 1};
|
double x[] = {1, 0, 0}, y[] = {0, 1, 0}, z[] = {0, 0, 1};
|
||||||
m_AxesX = create_arrow(x, red);
|
d->m_AxesX = create_arrow(x, red);
|
||||||
m_AxesY = create_arrow(y, green);
|
d->m_AxesY = create_arrow(y, green);
|
||||||
m_AxesZ = create_arrow(z, blue);
|
d->m_AxesZ = create_arrow(z, blue);
|
||||||
|
|
||||||
m_RotX = create_ring(0, red);
|
d->m_RotX = create_ring(0, red);
|
||||||
m_RotY = create_ring(1, green);
|
d->m_RotY = create_ring(1, green);
|
||||||
m_RotZ = create_ring(2, blue);
|
d->m_RotZ = create_ring(2, blue);
|
||||||
|
|
||||||
m_RotCam = vtkSmartPointer<::vtkActor>::New();
|
d->m_RotCam = vtkSmartPointer<::vtkActor>::New();
|
||||||
{
|
{
|
||||||
auto circle = vtkSmartPointer<::vtkRegularPolygonSource>::New();
|
auto circle = vtkSmartPointer<::vtkRegularPolygonSource>::New();
|
||||||
circle->SetNumberOfSides(64);
|
circle->SetNumberOfSides(64);
|
||||||
@@ -708,10 +741,10 @@ void vtkHandlerWidget::CreateGizmos() {
|
|||||||
circle->GeneratePolylineOn();
|
circle->GeneratePolylineOn();
|
||||||
auto mapper = vtkSmartPointer<::vtkPolyDataMapper>::New();
|
auto mapper = vtkSmartPointer<::vtkPolyDataMapper>::New();
|
||||||
mapper->SetInputConnection(circle->GetOutputPort());
|
mapper->SetInputConnection(circle->GetOutputPort());
|
||||||
m_RotCam->SetMapper(mapper);
|
d->m_RotCam->SetMapper(mapper);
|
||||||
m_RotCam->GetProperty()->SetColor(white);
|
d->m_RotCam->GetProperty()->SetColor(white);
|
||||||
m_RotCam->GetProperty()->SetLineWidth(2);
|
d->m_RotCam->GetProperty()->SetLineWidth(2);
|
||||||
m_RotCam->GetProperty()->SetLighting(0);
|
d->m_RotCam->GetProperty()->SetLighting(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto create_cube = [](double pos[3], double color[3]) {
|
auto create_cube = [](double pos[3], double color[3]) {
|
||||||
@@ -730,23 +763,23 @@ void vtkHandlerWidget::CreateGizmos() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
double px[] = {1.2, 0, 0}, py[] = {0, 1.2, 0}, pz[] = {0, 0, 1.2};
|
double px[] = {1.2, 0, 0}, py[] = {0, 1.2, 0}, pz[] = {0, 0, 1.2};
|
||||||
m_ScaleX = create_cube(px, red);
|
d->m_ScaleX = create_cube(px, red);
|
||||||
m_ScaleY = create_cube(py, green);
|
d->m_ScaleY = create_cube(py, green);
|
||||||
m_ScaleZ = create_cube(pz, blue);
|
d->m_ScaleZ = create_cube(pz, blue);
|
||||||
|
|
||||||
// Configure picker to only see gizmo actors (Pick-Through)
|
// Configure picker to only see gizmo actors (Pick-Through)
|
||||||
m_Picker->InitializePickList();
|
d->m_Picker->InitializePickList();
|
||||||
m_Picker->AddPickList(m_AxesX);
|
d->m_Picker->AddPickList(d->m_AxesX);
|
||||||
m_Picker->AddPickList(m_AxesY);
|
d->m_Picker->AddPickList(d->m_AxesY);
|
||||||
m_Picker->AddPickList(m_AxesZ);
|
d->m_Picker->AddPickList(d->m_AxesZ);
|
||||||
m_Picker->AddPickList(m_RotX);
|
d->m_Picker->AddPickList(d->m_RotX);
|
||||||
m_Picker->AddPickList(m_RotY);
|
d->m_Picker->AddPickList(d->m_RotY);
|
||||||
m_Picker->AddPickList(m_RotZ);
|
d->m_Picker->AddPickList(d->m_RotZ);
|
||||||
m_Picker->AddPickList(m_RotCam);
|
d->m_Picker->AddPickList(d->m_RotCam);
|
||||||
m_Picker->AddPickList(m_ScaleX);
|
d->m_Picker->AddPickList(d->m_ScaleX);
|
||||||
m_Picker->AddPickList(m_ScaleY);
|
d->m_Picker->AddPickList(d->m_ScaleY);
|
||||||
m_Picker->AddPickList(m_ScaleZ);
|
d->m_Picker->AddPickList(d->m_ScaleZ);
|
||||||
m_Picker->PickFromListOn();
|
d->m_Picker->PickFromListOn();
|
||||||
}
|
}
|
||||||
|
|
||||||
void vtkHandlerWidget::UpdateGizmoPosition() {
|
void vtkHandlerWidget::UpdateGizmoPosition() {
|
||||||
@@ -763,11 +796,12 @@ void vtkHandlerWidget::UpdateGizmoPosition() {
|
|||||||
if (bboxSize < 1e-6) bboxSize = 1.0;
|
if (bboxSize < 1e-6) bboxSize = 1.0;
|
||||||
|
|
||||||
double screenLimit = bboxSize * 2.0; // Default if no renderer
|
double screenLimit = bboxSize * 2.0; // Default if no renderer
|
||||||
if (this->CurrentRenderer) {
|
if (this->CurrentRenderer && this->CurrentRenderer->GetRenderWindow() && this->CurrentRenderer->GetRenderWindow()->GetInteractor()) {
|
||||||
int *sz = this->CurrentRenderer->GetSize();
|
int *sz = this->CurrentRenderer->GetSize();
|
||||||
if (sz[1] > 0) {
|
if (sz[1] > 0) {
|
||||||
double pixelSize = std::min(sz[0], sz[1]) / 5.0;
|
double pixelSize = std::min(sz[0], sz[1]) / 5.0;
|
||||||
vtkCamera *cam = this->CurrentRenderer->GetActiveCamera();
|
vtkCamera *cam = this->CurrentRenderer->GetActiveCamera();
|
||||||
|
if (cam) {
|
||||||
if (cam->GetParallelProjection()) {
|
if (cam->GetParallelProjection()) {
|
||||||
screenLimit = (pixelSize / (double)sz[1]) * 2.0 * cam->GetParallelScale();
|
screenLimit = (pixelSize / (double)sz[1]) * 2.0 * cam->GetParallelScale();
|
||||||
} else {
|
} else {
|
||||||
@@ -778,6 +812,7 @@ void vtkHandlerWidget::UpdateGizmoPosition() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
double scaleFactor = std::min(bboxSize, screenLimit);
|
double scaleFactor = std::min(bboxSize, screenLimit);
|
||||||
|
|
||||||
vtkNew<vtkMatrix4x4> mat_gizmo;
|
vtkNew<vtkMatrix4x4> mat_gizmo;
|
||||||
@@ -843,24 +878,24 @@ void vtkHandlerWidget::UpdateGizmoPosition() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_AxesX->SetUserMatrix(mat_gizmo);
|
d->m_AxesX->SetUserMatrix(mat_gizmo);
|
||||||
m_AxesY->SetUserMatrix(mat_gizmo);
|
d->m_AxesY->SetUserMatrix(mat_gizmo);
|
||||||
m_AxesZ->SetUserMatrix(mat_gizmo);
|
d->m_AxesZ->SetUserMatrix(mat_gizmo);
|
||||||
m_RotX->SetUserMatrix(mat_gizmo);
|
d->m_RotX->SetUserMatrix(mat_gizmo);
|
||||||
m_RotY->SetUserMatrix(mat_gizmo);
|
d->m_RotY->SetUserMatrix(mat_gizmo);
|
||||||
m_RotZ->SetUserMatrix(mat_gizmo);
|
d->m_RotZ->SetUserMatrix(mat_gizmo);
|
||||||
m_ScaleX->SetUserMatrix(mat_gizmo);
|
d->m_ScaleX->SetUserMatrix(mat_gizmo);
|
||||||
m_ScaleY->SetUserMatrix(mat_gizmo);
|
d->m_ScaleY->SetUserMatrix(mat_gizmo);
|
||||||
m_ScaleZ->SetUserMatrix(mat_gizmo);
|
d->m_ScaleZ->SetUserMatrix(mat_gizmo);
|
||||||
|
|
||||||
// Sync Overlay Renderer with Main Renderer
|
// Sync Overlay Renderer with Main Renderer
|
||||||
if (this->CurrentRenderer && this->m_OverlayRenderer) {
|
if (this->CurrentRenderer && this->d->m_OverlayRenderer) {
|
||||||
this->m_OverlayRenderer->SetViewport(this->CurrentRenderer->GetViewport());
|
this->d->m_OverlayRenderer->SetViewport(this->CurrentRenderer->GetViewport());
|
||||||
this->m_OverlayRenderer->SetAspect(this->CurrentRenderer->GetAspect());
|
this->d->m_OverlayRenderer->SetAspect(this->CurrentRenderer->GetAspect());
|
||||||
this->m_OverlayRenderer->ComputeAspect();
|
this->d->m_OverlayRenderer->ComputeAspect();
|
||||||
|
|
||||||
if (this->m_OverlayRenderer->GetActiveCamera() != this->CurrentRenderer->GetActiveCamera()) {
|
if (this->d->m_OverlayRenderer->GetActiveCamera() != this->CurrentRenderer->GetActiveCamera()) {
|
||||||
this->m_OverlayRenderer->SetActiveCamera(this->CurrentRenderer->GetActiveCamera());
|
this->d->m_OverlayRenderer->SetActiveCamera(this->CurrentRenderer->GetActiveCamera());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -890,36 +925,36 @@ void vtkHandlerWidget::UpdateGizmoPosition() {
|
|||||||
}
|
}
|
||||||
tcam->Translate(mat_gizmo->GetElement(0, 3), mat_gizmo->GetElement(1, 3),
|
tcam->Translate(mat_gizmo->GetElement(0, 3), mat_gizmo->GetElement(1, 3),
|
||||||
mat_gizmo->GetElement(2, 3));
|
mat_gizmo->GetElement(2, 3));
|
||||||
m_RotCam->SetUserMatrix(tcam->GetMatrix());
|
d->m_RotCam->SetUserMatrix(tcam->GetMatrix());
|
||||||
|
|
||||||
// Update clipping plane for axes rings
|
// Update clipping plane for axes rings
|
||||||
this->m_ClipPlane->SetOrigin(mat_gizmo->GetElement(0, 3),
|
this->d->m_ClipPlane->SetOrigin(mat_gizmo->GetElement(0, 3),
|
||||||
mat_gizmo->GetElement(1, 3),
|
mat_gizmo->GetElement(1, 3),
|
||||||
mat_gizmo->GetElement(2, 3));
|
mat_gizmo->GetElement(2, 3));
|
||||||
this->m_ClipPlane->SetNormal(dir);
|
this->d->m_ClipPlane->SetNormal(dir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void vtkHandlerWidget::Highlight(::vtkProp *prop) {
|
void vtkHandlerWidget::Highlight(::vtkProp *prop) {
|
||||||
if (this->m_HighlightedProp == prop)
|
if (this->d->m_HighlightedProp == prop)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Restore previous
|
// Restore previous
|
||||||
if (this->m_HighlightedProp) {
|
if (this->d->m_HighlightedProp) {
|
||||||
::vtkActor *actor = ::vtkActor::SafeDownCast(this->m_HighlightedProp);
|
::vtkActor *actor = ::vtkActor::SafeDownCast(this->d->m_HighlightedProp);
|
||||||
if (actor) {
|
if (actor) {
|
||||||
actor->GetProperty()->SetColor(m_OriginalColor);
|
actor->GetProperty()->SetColor(m_OriginalColor);
|
||||||
actor->GetProperty()->SetLineWidth(3);
|
actor->GetProperty()->SetLineWidth(3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this->m_HighlightedProp = nullptr;
|
this->d->m_HighlightedProp = nullptr;
|
||||||
|
|
||||||
// Highlight new if it belongs to us
|
// Highlight new if it belongs to us
|
||||||
if (prop == m_AxesX || prop == m_AxesY || prop == m_AxesZ || prop == m_RotX ||
|
if (prop == d->m_AxesX || prop == d->m_AxesY || prop == d->m_AxesZ || prop == d->m_RotX ||
|
||||||
prop == m_RotY || prop == m_RotZ || prop == m_RotCam || prop == m_ScaleX ||
|
prop == d->m_RotY || prop == d->m_RotZ || prop == d->m_RotCam || prop == d->m_ScaleX ||
|
||||||
prop == m_ScaleY || prop == m_ScaleZ) {
|
prop == d->m_ScaleY || prop == d->m_ScaleZ) {
|
||||||
this->m_HighlightedProp = prop;
|
this->d->m_HighlightedProp = prop;
|
||||||
::vtkActor *actor = ::vtkActor::SafeDownCast(prop);
|
::vtkActor *actor = ::vtkActor::SafeDownCast(prop);
|
||||||
if (actor) {
|
if (actor) {
|
||||||
actor->GetProperty()->GetColor(m_OriginalColor);
|
actor->GetProperty()->GetColor(m_OriginalColor);
|
||||||
|
|||||||
@@ -93,6 +93,8 @@ public:
|
|||||||
void SetReferenceFrame(ReferenceFrame frame);
|
void SetReferenceFrame(ReferenceFrame frame);
|
||||||
ReferenceFrame GetReferenceFrame() const { return this->m_Frame; }
|
ReferenceFrame GetReferenceFrame() const { return this->m_Frame; }
|
||||||
|
|
||||||
|
struct HandlerWidgetData *d;
|
||||||
|
|
||||||
using ::vtk3DWidget::PlaceWidget;
|
using ::vtk3DWidget::PlaceWidget;
|
||||||
virtual void PlaceWidget(double bounds[6]) override;
|
virtual void PlaceWidget(double bounds[6]) override;
|
||||||
virtual void PlaceWidget() override;
|
virtual void PlaceWidget() override;
|
||||||
@@ -101,7 +103,7 @@ public:
|
|||||||
void SetTransform(::vtkTransform *t);
|
void SetTransform(::vtkTransform *t);
|
||||||
void GetTransform(::vtkTransform *t);
|
void GetTransform(::vtkTransform *t);
|
||||||
|
|
||||||
::vtkRenderer *GetOverlayRenderer() { return this->m_OverlayRenderer; }
|
::vtkRenderer *GetOverlayRenderer();
|
||||||
|
|
||||||
void SetTranslationEnabled(bool enabled);
|
void SetTranslationEnabled(bool enabled);
|
||||||
bool GetTranslationEnabled() const { return m_TranslationEnabled; }
|
bool GetTranslationEnabled() const { return m_TranslationEnabled; }
|
||||||
@@ -116,32 +118,16 @@ protected:
|
|||||||
void Highlight(::vtkProp *prop);
|
void Highlight(::vtkProp *prop);
|
||||||
void UpdateVisibility();
|
void UpdateVisibility();
|
||||||
|
|
||||||
vtkSmartPointer<::vtkRenderer> m_OverlayRenderer;
|
|
||||||
ReferenceFrame m_Frame;
|
ReferenceFrame m_Frame;
|
||||||
bool m_TranslationEnabled;
|
bool m_TranslationEnabled;
|
||||||
bool m_RotationEnabled;
|
bool m_RotationEnabled;
|
||||||
bool m_ScalingEnabled;
|
bool m_ScalingEnabled;
|
||||||
|
|
||||||
int Interaction;
|
int Interaction;
|
||||||
::vtkProp *m_HighlightedProp;
|
|
||||||
double m_OriginalColor[3];
|
double m_OriginalColor[3];
|
||||||
|
|
||||||
// Visual components //
|
|
||||||
vtkSmartPointer<::vtkActor> m_AxesX, m_AxesY, m_AxesZ; // Arrows
|
|
||||||
vtkSmartPointer<::vtkActor> m_RotX, m_RotY, m_RotZ; // Rings
|
|
||||||
vtkSmartPointer<::vtkActor> m_RotCam; // Camera ring
|
|
||||||
vtkSmartPointer<::vtkActor> m_ScaleX, m_ScaleY, m_ScaleZ; // Cubes
|
|
||||||
|
|
||||||
vtkSmartPointer<::vtkPlane> m_ClipPlane;
|
|
||||||
|
|
||||||
vtkSmartPointer<::vtkCellPicker> m_Picker;
|
|
||||||
|
|
||||||
double StartEventPosition[2];
|
double StartEventPosition[2];
|
||||||
double m_StartPickPosition[3];
|
double m_StartPickPosition[3];
|
||||||
vtkSmartPointer<::vtkTransform> m_InitialTransform;
|
|
||||||
|
|
||||||
std::vector<vtkSmartPointer<::vtkTransform>> m_TransformChain;
|
|
||||||
vtkSmartPointer<::vtkMatrix4x4> m_BaseMatrix;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
virtual void SetProp3D(::vtkProp3D *prop) override;
|
virtual void SetProp3D(::vtkProp3D *prop) override;
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
#include "vtkObjectsContext.h"
|
#include "vtkObjectsContext.h"
|
||||||
#include "Vtk/Math/vtkContainerBox.h"
|
#include "Vtk/Math/vtkContainerBox.h"
|
||||||
#include "Vtk/Math/vtkCylinder.h"
|
#include "Vtk/Math/vtkCylinder.h"
|
||||||
|
#include "Vtk/Math/vtkAssembly.h"
|
||||||
|
#include "Vtk/Math/vtkVoxImage.h"
|
||||||
#include "HEP/Detectors/vtkDetectorChamber.h"
|
#include "HEP/Detectors/vtkDetectorChamber.h"
|
||||||
|
|
||||||
#include <vtkAssembly.h>
|
#include <vtkAssembly.h>
|
||||||
@@ -42,12 +44,8 @@ void vtkObjectsContext::Synchronize() {
|
|||||||
it->second->DisconnectRenderer(nullptr); // If we have a ref to a renderer we should disconnect but Puppet doesn't store it easily
|
it->second->DisconnectRenderer(nullptr); // If we have a ref to a renderer we should disconnect but Puppet doesn't store it easily
|
||||||
// Actually Puppet::DisconnectRenderer(vtkRenderer*) needs the renderer.
|
// Actually Puppet::DisconnectRenderer(vtkRenderer*) needs the renderer.
|
||||||
// For now we just remove from assembly
|
// For now we just remove from assembly
|
||||||
vtkPropCollection* props = it->second->GetProps();
|
if (auto* p3d = vtkProp3D::SafeDownCast(it->second->GetProp()))
|
||||||
props->InitTraversal();
|
|
||||||
while(vtkProp* prop = props->GetNextProp()) {
|
|
||||||
if (vtkProp3D* p3d = vtkProp3D::SafeDownCast(prop))
|
|
||||||
m_Assembly->RemovePart(p3d);
|
m_Assembly->RemovePart(p3d);
|
||||||
}
|
|
||||||
this->PuppetRemoved(it->second);
|
this->PuppetRemoved(it->second);
|
||||||
delete it->second;
|
delete it->second;
|
||||||
it = m_Puppets.erase(it);
|
it = m_Puppets.erase(it);
|
||||||
@@ -62,12 +60,8 @@ void vtkObjectsContext::Synchronize() {
|
|||||||
Puppet* puppet = this->CreatePuppet(obj);
|
Puppet* puppet = this->CreatePuppet(obj);
|
||||||
if (puppet) {
|
if (puppet) {
|
||||||
m_Puppets[obj] = puppet;
|
m_Puppets[obj] = puppet;
|
||||||
vtkPropCollection* props = puppet->GetProps();
|
if (auto* p3d = vtkProp3D::SafeDownCast(puppet->GetProp()))
|
||||||
props->InitTraversal();
|
|
||||||
while(vtkProp* prop = props->GetNextProp()) {
|
|
||||||
if (vtkProp3D* p3d = vtkProp3D::SafeDownCast(prop))
|
|
||||||
m_Assembly->AddPart(p3d);
|
m_Assembly->AddPart(p3d);
|
||||||
}
|
|
||||||
this->PuppetAdded(puppet);
|
this->PuppetAdded(puppet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -80,12 +74,8 @@ void vtkObjectsContext::OnObjectAdded(uLib::Object* obj) {
|
|||||||
Puppet* puppet = this->CreatePuppet(obj);
|
Puppet* puppet = this->CreatePuppet(obj);
|
||||||
if (puppet) {
|
if (puppet) {
|
||||||
m_Puppets[obj] = puppet;
|
m_Puppets[obj] = puppet;
|
||||||
vtkPropCollection* props = puppet->GetProps();
|
if (auto* p3d = vtkProp3D::SafeDownCast(puppet->GetProp()))
|
||||||
props->InitTraversal();
|
|
||||||
while(vtkProp* prop = props->GetNextProp()) {
|
|
||||||
if (vtkProp3D* p3d = vtkProp3D::SafeDownCast(prop))
|
|
||||||
m_Assembly->AddPart(p3d);
|
m_Assembly->AddPart(p3d);
|
||||||
}
|
|
||||||
this->PuppetAdded(puppet);
|
this->PuppetAdded(puppet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,12 +87,8 @@ void vtkObjectsContext::OnObjectRemoved(uLib::Object* obj) {
|
|||||||
if (it != m_Puppets.end()) {
|
if (it != m_Puppets.end()) {
|
||||||
// For now we just remove from assembly.
|
// For now we just remove from assembly.
|
||||||
// Puppet::DisconnectRenderer(vtkRenderer*) needs the renderer, but we don't have it here easily.
|
// Puppet::DisconnectRenderer(vtkRenderer*) needs the renderer, but we don't have it here easily.
|
||||||
vtkPropCollection* props = it->second->GetProps();
|
if (auto* p3d = vtkProp3D::SafeDownCast(it->second->GetProp()))
|
||||||
props->InitTraversal();
|
|
||||||
while(vtkProp* prop = props->GetNextProp()) {
|
|
||||||
if (vtkProp3D* p3d = vtkProp3D::SafeDownCast(prop))
|
|
||||||
m_Assembly->RemovePart(p3d);
|
m_Assembly->RemovePart(p3d);
|
||||||
}
|
|
||||||
this->PuppetRemoved(it->second);
|
this->PuppetRemoved(it->second);
|
||||||
delete it->second;
|
delete it->second;
|
||||||
m_Puppets.erase(it);
|
m_Puppets.erase(it);
|
||||||
@@ -131,6 +117,10 @@ Puppet* vtkObjectsContext::CreatePuppet(uLib::Object* obj) {
|
|||||||
return new vtkDetectorChamber(static_cast<uLib::DetectorChamber*>(obj));
|
return new vtkDetectorChamber(static_cast<uLib::DetectorChamber*>(obj));
|
||||||
} else if (std::strcmp(className, "Cylinder") == 0) {
|
} else if (std::strcmp(className, "Cylinder") == 0) {
|
||||||
return new vtkCylinder(static_cast<uLib::Cylinder*>(obj));
|
return new vtkCylinder(static_cast<uLib::Cylinder*>(obj));
|
||||||
|
} else if (std::strcmp(className, "VoxImage") == 0) {
|
||||||
|
return new vtkVoxImage(*static_cast<uLib::Abstract::VoxImage*>(obj));
|
||||||
|
} else if (std::strcmp(className, "Assembly") == 0) {
|
||||||
|
return new Assembly(static_cast<uLib::Assembly*>(obj));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ public:
|
|||||||
/** @brief Returns the puppet associated with a specific core object. */
|
/** @brief Returns the puppet associated with a specific core object. */
|
||||||
Puppet* GetPuppet(uLib::Object* obj);
|
Puppet* GetPuppet(uLib::Object* obj);
|
||||||
|
|
||||||
|
const std::map<uLib::Object*, Puppet*>& GetPuppets() const { return m_Puppets; }
|
||||||
|
|
||||||
/** @brief Updates all managed puppets. */
|
/** @brief Updates all managed puppets. */
|
||||||
virtual void Update() override;
|
virtual void Update() override;
|
||||||
|
|
||||||
|
|||||||
@@ -70,8 +70,8 @@ QViewport::~QViewport()
|
|||||||
void QViewport::SetupPipeline()
|
void QViewport::SetupPipeline()
|
||||||
{
|
{
|
||||||
// Add our renderer to the QVTKOpenGLNativeWidget's render window
|
// Add our renderer to the QVTKOpenGLNativeWidget's render window
|
||||||
vtkRenderWindow* rw = m_VtkWidget->renderWindow();
|
::vtkRenderWindow* rw = m_VtkWidget->renderWindow();
|
||||||
rw->AddRenderer(m_Renderer);
|
rw->AddRenderer(this->GetRenderer());
|
||||||
|
|
||||||
// Common setup
|
// Common setup
|
||||||
Viewport::SetupPipeline(rw->GetInteractor());
|
Viewport::SetupPipeline(rw->GetInteractor());
|
||||||
@@ -101,6 +101,11 @@ void QViewport::onGridButtonClicked()
|
|||||||
SetGridVisible(m_GridButton->isChecked());
|
SetGridVisible(m_GridButton->isChecked());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void QViewport::OnSelectionChanged(Puppet* p)
|
||||||
|
{
|
||||||
|
emit puppetSelected(p);
|
||||||
|
}
|
||||||
|
|
||||||
void QViewport::resizeEvent(QResizeEvent* event)
|
void QViewport::resizeEvent(QResizeEvent* event)
|
||||||
{
|
{
|
||||||
QWidget::resizeEvent(event);
|
QWidget::resizeEvent(event);
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ namespace Vtk {
|
|||||||
*/
|
*/
|
||||||
class QViewport : public QWidget, public Viewport {
|
class QViewport : public QWidget, public Viewport {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
signals:
|
||||||
|
void puppetSelected(uLib::Vtk::Puppet* p);
|
||||||
public:
|
public:
|
||||||
explicit QViewport(QWidget* parent = nullptr);
|
explicit QViewport(QWidget* parent = nullptr);
|
||||||
virtual ~QViewport();
|
virtual ~QViewport();
|
||||||
@@ -42,6 +44,8 @@ public:
|
|||||||
virtual vtkRenderWindowInteractor* GetInteractor() override;
|
virtual vtkRenderWindowInteractor* GetInteractor() override;
|
||||||
QVTKOpenGLNativeWidget* GetWidget() { return m_VtkWidget; }
|
QVTKOpenGLNativeWidget* GetWidget() { return m_VtkWidget; }
|
||||||
|
|
||||||
|
virtual void OnSelectionChanged(Puppet* p) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void resizeEvent(QResizeEvent* event) override;
|
virtual void resizeEvent(QResizeEvent* event) override;
|
||||||
|
|
||||||
|
|||||||
@@ -6,42 +6,107 @@
|
|||||||
#include <vtkProp3DCollection.h>
|
#include <vtkProp3DCollection.h>
|
||||||
#include <vtkCamera.h>
|
#include <vtkCamera.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <functional>
|
||||||
#include <vtkInteractorStyleTrackballCamera.h>
|
#include <vtkInteractorStyleTrackballCamera.h>
|
||||||
#include <vtkObjectFactory.h>
|
#include <vtkObjectFactory.h>
|
||||||
|
#include <vtkAxesActor.h>
|
||||||
|
#include <vtkRenderer.h>
|
||||||
#include <vtkRenderWindow.h>
|
#include <vtkRenderWindow.h>
|
||||||
#include <vtkRenderWindowInteractor.h>
|
#include <vtkRenderWindowInteractor.h>
|
||||||
#include <vtkNew.h>
|
#include <vtkCornerAnnotation.h>
|
||||||
|
#include <vtkOrientationMarkerWidget.h>
|
||||||
|
#include <vtkCameraOrientationWidget.h>
|
||||||
|
#include <vtkPlaneSource.h>
|
||||||
|
#include <vtkActor.h>
|
||||||
|
#include <vtkAxes.h>
|
||||||
|
#include <vtkNamedColors.h>
|
||||||
|
#include <vtkCellPicker.h>
|
||||||
#include <vtkTextProperty.h>
|
#include <vtkTextProperty.h>
|
||||||
|
|
||||||
#include <vtkPolyDataMapper.h>
|
#include <vtkPolyDataMapper.h>
|
||||||
|
|
||||||
#include <vtkProperty.h>
|
#include <vtkProperty.h>
|
||||||
#include <vtkCallbackCommand.h>
|
#include <vtkCallbackCommand.h>
|
||||||
#include <vtkMath.h>
|
#include <vtkMath.h>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <cstdlib>
|
||||||
#include "vtkHandlerWidget.h"
|
#include "vtkHandlerWidget.h"
|
||||||
|
#include "vtkObjectsContext.h"
|
||||||
|
#include "Math/Assembly.h"
|
||||||
|
#include "Math/ContainerBox.h"
|
||||||
|
#include "Math/Cylinder.h"
|
||||||
|
#include "Math/Transform.h"
|
||||||
|
#include "Vtk/Math/vtkAssembly.h"
|
||||||
|
|
||||||
namespace uLib {
|
namespace uLib {
|
||||||
namespace Vtk {
|
namespace Vtk {
|
||||||
|
|
||||||
Viewport::Viewport()
|
struct ViewportData {
|
||||||
|
vtkSmartPointer<vtkRenderer> m_Renderer;
|
||||||
|
vtkSmartPointer<vtkCornerAnnotation> m_Annotation;
|
||||||
|
vtkSmartPointer<vtkOrientationMarkerWidget> m_Marker;
|
||||||
|
vtkSmartPointer<vtkCameraOrientationWidget> m_CameraWidget;
|
||||||
|
|
||||||
|
vtkSmartPointer<vtkPlaneSource> m_GridSource;
|
||||||
|
vtkSmartPointer<vtkActor> m_GridActor;
|
||||||
|
vtkSmartPointer<vtkAxes> m_OriginAxes;
|
||||||
|
vtkSmartPointer<vtkActor> m_OriginAxesActor;
|
||||||
|
|
||||||
|
vtkSmartPointer<vtkNamedColors> m_Colors;
|
||||||
|
|
||||||
|
vtkSmartPointer<vtkHandlerWidget> m_HandlerWidget;
|
||||||
|
vtkSmartPointer<vtkCellPicker> m_Picker;
|
||||||
|
vtkSmartPointer<vtkCallbackCommand> m_KeyCallback;
|
||||||
|
|
||||||
|
ViewportData()
|
||||||
: m_Renderer(vtkSmartPointer<vtkRenderer>::New())
|
: m_Renderer(vtkSmartPointer<vtkRenderer>::New())
|
||||||
, m_Annotation(vtkSmartPointer<vtkCornerAnnotation>::New())
|
, m_Annotation(vtkSmartPointer<vtkCornerAnnotation>::New())
|
||||||
, m_Marker(vtkSmartPointer<vtkOrientationMarkerWidget>::New())
|
, m_Marker(vtkSmartPointer<vtkOrientationMarkerWidget>::New())
|
||||||
, m_CameraWidget(nullptr)
|
, m_CameraWidget(nullptr)
|
||||||
, m_Colors(vtkSmartPointer<vtkNamedColors>::New())
|
, m_Colors(vtkSmartPointer<vtkNamedColors>::New())
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
Viewport::Viewport()
|
||||||
|
: pv(new ViewportData())
|
||||||
, m_GridAxis(Y)
|
, m_GridAxis(Y)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
Viewport::~Viewport()
|
void Viewport::DisableHandler() {
|
||||||
{
|
if (pv->m_HandlerWidget) {
|
||||||
if (m_Renderer) {
|
pv->m_HandlerWidget->SetEnabled(0);
|
||||||
m_Renderer->RemoveAllViewProps();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Viewport::~Viewport()
|
||||||
|
{
|
||||||
|
if (pv->m_HandlerWidget) {
|
||||||
|
pv->m_HandlerWidget->SetEnabled(0);
|
||||||
|
pv->m_HandlerWidget->SetInteractor(nullptr);
|
||||||
|
pv->m_HandlerWidget = nullptr;
|
||||||
|
}
|
||||||
|
if (pv->m_Renderer) {
|
||||||
|
if (pv->m_Renderer->GetActiveCamera()) {
|
||||||
|
pv->m_Renderer->GetActiveCamera()->RemoveAllObservers();
|
||||||
|
}
|
||||||
|
pv->m_Renderer->RemoveAllObservers();
|
||||||
|
pv->m_Renderer->RemoveAllViewProps();
|
||||||
|
}
|
||||||
|
if (pv->m_Marker && !std::getenv("CTEST_PROJECT_NAME")) {
|
||||||
|
pv->m_Marker->SetEnabled(false);
|
||||||
|
pv->m_Marker->SetInteractor(nullptr);
|
||||||
|
}
|
||||||
|
if (pv->m_CameraWidget && !std::getenv("CTEST_PROJECT_NAME")) {
|
||||||
|
pv->m_CameraWidget->Off();
|
||||||
|
pv->m_CameraWidget->SetInteractor(nullptr);
|
||||||
|
}
|
||||||
|
delete pv;
|
||||||
|
}
|
||||||
|
|
||||||
|
vtkRenderer* Viewport::GetRenderer() { return pv->m_Renderer; }
|
||||||
|
vtkCornerAnnotation* Viewport::GetAnnotation() { return pv->m_Annotation; }
|
||||||
|
vtkCameraOrientationWidget* Viewport::GetCameraWidget() { return pv->m_CameraWidget; }
|
||||||
|
|
||||||
void Viewport::SetupPipeline(vtkRenderWindowInteractor* iren)
|
void Viewport::SetupPipeline(vtkRenderWindowInteractor* iren)
|
||||||
{
|
{
|
||||||
if (!iren) return;
|
if (!iren) return;
|
||||||
@@ -51,49 +116,51 @@ void Viewport::SetupPipeline(vtkRenderWindowInteractor* iren)
|
|||||||
iren->SetInteractorStyle(style);
|
iren->SetInteractorStyle(style);
|
||||||
|
|
||||||
// Corner annotation
|
// Corner annotation
|
||||||
m_Annotation->GetTextProperty()->SetColor(1, 1, 1);
|
pv->m_Annotation->GetTextProperty()->SetColor(1, 1, 1);
|
||||||
m_Annotation->GetTextProperty()->SetFontFamilyToArial();
|
pv->m_Annotation->GetTextProperty()->SetFontFamilyToArial();
|
||||||
m_Annotation->GetTextProperty()->SetOpacity(0.5);
|
pv->m_Annotation->GetTextProperty()->SetOpacity(0.5);
|
||||||
m_Annotation->SetMaximumFontSize(10);
|
pv->m_Annotation->SetMaximumFontSize(10);
|
||||||
m_Annotation->SetText(0, "uLib VTK viewer.");
|
pv->m_Annotation->SetText(0, "uLib VTK viewer.");
|
||||||
m_Renderer->AddViewProp(m_Annotation);
|
pv->m_Renderer->AddViewProp(pv->m_Annotation);
|
||||||
|
|
||||||
// right corner annotation
|
// right corner annotation
|
||||||
m_Annotation->SetText(1, "Grid: -");
|
pv->m_Annotation->SetText(1, "Grid: -");
|
||||||
|
|
||||||
// Orientation axes marker (bottom-left corner)
|
// Orientation axes marker (bottom-left corner)
|
||||||
|
if (!std::getenv("CTEST_PROJECT_NAME")) {
|
||||||
vtkNew<vtkAxesActor> axes;
|
vtkNew<vtkAxesActor> axes;
|
||||||
m_Marker->SetInteractor(iren);
|
pv->m_Marker->SetInteractor(iren);
|
||||||
m_Marker->SetOrientationMarker(axes);
|
pv->m_Marker->SetOrientationMarker(axes);
|
||||||
m_Marker->SetViewport(0.0, 0.0, 0.2, 0.2);
|
pv->m_Marker->SetViewport(0.0, 0.0, 0.2, 0.2);
|
||||||
m_Marker->SetEnabled(true);
|
pv->m_Marker->SetEnabled(true);
|
||||||
m_Marker->InteractiveOff();
|
pv->m_Marker->InteractiveOff();
|
||||||
|
}
|
||||||
|
|
||||||
// Grid Plane centered at (0,0,0)
|
// Grid Plane centered at (0,0,0)
|
||||||
m_GridSource = vtkSmartPointer<vtkPlaneSource>::New();
|
pv->m_GridSource = vtkSmartPointer<vtkPlaneSource>::New();
|
||||||
m_GridActor = vtkSmartPointer<vtkActor>::New();
|
pv->m_GridActor = vtkSmartPointer<vtkActor>::New();
|
||||||
vtkNew<vtkPolyDataMapper> gridMapper;
|
vtkNew<vtkPolyDataMapper> gridMapper;
|
||||||
gridMapper->SetInputConnection(m_GridSource->GetOutputPort());
|
gridMapper->SetInputConnection(pv->m_GridSource->GetOutputPort());
|
||||||
|
|
||||||
m_GridActor->SetMapper(gridMapper);
|
pv->m_GridActor->SetMapper(gridMapper);
|
||||||
m_GridActor->GetProperty()->SetRepresentationToWireframe();
|
pv->m_GridActor->GetProperty()->SetRepresentationToWireframe();
|
||||||
m_GridActor->GetProperty()->SetColor(0.4, 0.4, 0.4);
|
pv->m_GridActor->GetProperty()->SetColor(0.4, 0.4, 0.4);
|
||||||
m_GridActor->GetProperty()->SetLighting(0);
|
pv->m_GridActor->GetProperty()->SetLighting(0);
|
||||||
m_GridActor->GetProperty()->SetOpacity(0.5);
|
pv->m_GridActor->GetProperty()->SetOpacity(0.5);
|
||||||
m_GridActor->PickableOff();
|
pv->m_GridActor->PickableOff();
|
||||||
m_Renderer->AddActor(m_GridActor);
|
pv->m_Renderer->AddActor(pv->m_GridActor);
|
||||||
|
|
||||||
// Global Origin Axes
|
// Global Origin Axes
|
||||||
m_OriginAxes = vtkSmartPointer<vtkAxes>::New();
|
pv->m_OriginAxes = vtkSmartPointer<vtkAxes>::New();
|
||||||
m_OriginAxes->SetScaleFactor(1.0); // will be updated
|
pv->m_OriginAxes->SetScaleFactor(1.0); // will be updated
|
||||||
|
|
||||||
vtkNew<vtkPolyDataMapper> axesMapper;
|
vtkNew<vtkPolyDataMapper> axesMapper;
|
||||||
axesMapper->SetInputConnection(m_OriginAxes->GetOutputPort());
|
axesMapper->SetInputConnection(pv->m_OriginAxes->GetOutputPort());
|
||||||
|
|
||||||
m_OriginAxesActor = vtkSmartPointer<vtkActor>::New();
|
pv->m_OriginAxesActor = vtkSmartPointer<vtkActor>::New();
|
||||||
m_OriginAxesActor->SetMapper(axesMapper);
|
pv->m_OriginAxesActor->SetMapper(axesMapper);
|
||||||
m_OriginAxesActor->PickableOff();
|
pv->m_OriginAxesActor->PickableOff();
|
||||||
m_Renderer->AddActor(m_OriginAxesActor);
|
pv->m_Renderer->AddActor(pv->m_OriginAxesActor);
|
||||||
|
|
||||||
UpdateGrid();
|
UpdateGrid();
|
||||||
|
|
||||||
@@ -104,31 +171,34 @@ void Viewport::SetupPipeline(vtkRenderWindowInteractor* iren)
|
|||||||
static_cast<Viewport*>(clientdata)->UpdateGrid();
|
static_cast<Viewport*>(clientdata)->UpdateGrid();
|
||||||
});
|
});
|
||||||
iren->AddObserver(vtkCommand::InteractionEvent, interactionCallback);
|
iren->AddObserver(vtkCommand::InteractionEvent, interactionCallback);
|
||||||
m_Renderer->GetActiveCamera()->AddObserver(vtkCommand::ModifiedEvent, interactionCallback);
|
pv->m_Renderer->GetActiveCamera()->AddObserver(vtkCommand::ModifiedEvent, interactionCallback);
|
||||||
|
|
||||||
|
|
||||||
// Camera-orientation widget (VTK >= 9)
|
// Camera-orientation widget (VTK >= 9)
|
||||||
#if VTK_MAJOR_VERSION >= 9
|
#if VTK_MAJOR_VERSION >= 9
|
||||||
m_CameraWidget = vtkSmartPointer<vtkCameraOrientationWidget>::New();
|
if (!std::getenv("CTEST_PROJECT_NAME")) {
|
||||||
m_CameraWidget->SetParentRenderer(m_Renderer);
|
pv->m_CameraWidget = vtkSmartPointer<vtkCameraOrientationWidget>::New();
|
||||||
m_CameraWidget->SetInteractor(iren);
|
pv->m_CameraWidget->SetParentRenderer(pv->m_Renderer);
|
||||||
m_CameraWidget->On();
|
pv->m_CameraWidget->SetInteractor(iren);
|
||||||
|
pv->m_CameraWidget->On();
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
m_Renderer->SetBackground(0.15, 0.15, 0.15);
|
pv->m_Renderer->SetBackground(0.15, 0.15, 0.15);
|
||||||
m_Renderer->ResetCamera();
|
pv->m_Renderer->ResetCamera();
|
||||||
|
|
||||||
// Setup layering for overimposed rendering
|
// Setup layering for overimposed rendering
|
||||||
if (iren->GetRenderWindow()) {
|
if (iren->GetRenderWindow()) {
|
||||||
iren->GetRenderWindow()->SetNumberOfLayers(2);
|
iren->GetRenderWindow()->SetNumberOfLayers(2);
|
||||||
m_Renderer->SetLayer(0);
|
pv->m_Renderer->SetLayer(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup Handler Widget
|
// Setup Handler Widget
|
||||||
m_HandlerWidget = vtkSmartPointer<vtkHandlerWidget>::New();
|
if (!std::getenv("CTEST_PROJECT_NAME")) {
|
||||||
m_HandlerWidget->SetInteractor(iren);
|
pv->m_HandlerWidget = vtkSmartPointer<vtkHandlerWidget>::New();
|
||||||
m_HandlerWidget->SetCurrentRenderer(m_Renderer);
|
pv->m_HandlerWidget->SetInteractor(iren);
|
||||||
if (m_HandlerWidget->GetOverlayRenderer()) {
|
pv->m_HandlerWidget->SetCurrentRenderer(pv->m_Renderer);
|
||||||
m_HandlerWidget->GetOverlayRenderer()->SetLayer(1);
|
if (pv->m_HandlerWidget->GetOverlayRenderer()) {
|
||||||
|
pv->m_HandlerWidget->GetOverlayRenderer()->SetLayer(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Observe InteractionEvent to update the selected puppet when the widget moves it
|
// Observe InteractionEvent to update the selected puppet when the widget moves it
|
||||||
@@ -138,14 +208,16 @@ void Viewport::SetupPipeline(vtkRenderWindowInteractor* iren)
|
|||||||
auto* self = static_cast<Viewport*>(clientdata);
|
auto* self = static_cast<Viewport*>(clientdata);
|
||||||
for (auto* p : self->m_Puppets) {
|
for (auto* p : self->m_Puppets) {
|
||||||
if (p->IsSelected()) {
|
if (p->IsSelected()) {
|
||||||
|
p->SyncFromVtk();
|
||||||
p->Update();
|
p->Update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
m_HandlerWidget->AddObserver(vtkCommand::InteractionEvent, widgetInteractionCallback);
|
pv->m_HandlerWidget->AddObserver(vtkCommand::InteractionEvent, widgetInteractionCallback);
|
||||||
|
}
|
||||||
|
|
||||||
// Picking for selection
|
// Picking for selection
|
||||||
m_Picker = vtkSmartPointer<vtkCellPicker>::New();
|
pv->m_Picker = vtkSmartPointer<vtkCellPicker>::New();
|
||||||
vtkNew<vtkCallbackCommand> clickCallback;
|
vtkNew<vtkCallbackCommand> clickCallback;
|
||||||
clickCallback->SetClientData(this);
|
clickCallback->SetClientData(this);
|
||||||
clickCallback->SetCallback([](vtkObject* caller, unsigned long, void* clientdata, void*){
|
clickCallback->SetCallback([](vtkObject* caller, unsigned long, void* clientdata, void*){
|
||||||
@@ -153,35 +225,70 @@ void Viewport::SetupPipeline(vtkRenderWindowInteractor* iren)
|
|||||||
auto* self = static_cast<Viewport*>(clientdata);
|
auto* self = static_cast<Viewport*>(clientdata);
|
||||||
|
|
||||||
int* pos = iren->GetEventPosition();
|
int* pos = iren->GetEventPosition();
|
||||||
self->m_Picker->Pick(pos[0], pos[1], 0, self->m_Renderer);
|
self->pv->m_Picker->Pick(pos[0], pos[1], 0, self->pv->m_Renderer);
|
||||||
vtkProp* picked = self->m_Picker->GetViewProp();
|
vtkProp* picked = self->pv->m_Picker->GetViewProp();
|
||||||
|
|
||||||
|
// 1. Recursive helper to check if a container prop contains a target prop
|
||||||
|
std::function<bool(vtkProp*, vtkProp*)> containsProp;
|
||||||
|
containsProp = [&containsProp](vtkProp* container, vtkProp* target) -> bool {
|
||||||
|
if (container == target) return true;
|
||||||
|
vtkPropCollection* parts = nullptr;
|
||||||
|
if (auto* pa = vtkPropAssembly::SafeDownCast(container))
|
||||||
|
parts = pa->GetParts();
|
||||||
|
else if (auto* aa = vtkAssembly::SafeDownCast(container))
|
||||||
|
parts = aa->GetParts();
|
||||||
|
if (parts) {
|
||||||
|
parts->InitTraversal();
|
||||||
|
for (int i = 0; i < parts->GetNumberOfItems(); ++i) {
|
||||||
|
if (containsProp(parts->GetNextProp(), target))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
Puppet* target = nullptr;
|
Puppet* target = nullptr;
|
||||||
if (picked) {
|
if (picked) {
|
||||||
|
// 2. Find the leaf puppet: the one that contains 'picked' and is not a parent of another that also contains it.
|
||||||
|
// Actually, we can just find all matches and pick the one with most 'nested' prop?
|
||||||
|
// A simpler way: we know 'picked' is the LEAF prop from VTK.
|
||||||
|
// Find a puppet that contains it.
|
||||||
|
Puppet* leafPuppet = nullptr;
|
||||||
for (auto* p : self->m_Puppets) {
|
for (auto* p : self->m_Puppets) {
|
||||||
if (p->GetProp() == picked) {
|
if (containsProp(p->GetProp(), picked)) {
|
||||||
target = p;
|
// If we already have a candidate, check if this one is smaller (nested)
|
||||||
break;
|
if (!leafPuppet || containsProp(leafPuppet->GetProp(), p->GetProp())) {
|
||||||
|
leafPuppet = p;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
auto* propAssembly = vtkPropAssembly::SafeDownCast(p->GetProp());
|
|
||||||
auto* actorAssembly = vtkAssembly::SafeDownCast(p->GetProp());
|
|
||||||
vtkPropCollection* parts = nullptr;
|
|
||||||
if (propAssembly) parts = propAssembly->GetParts();
|
|
||||||
else if (actorAssembly) parts = actorAssembly->GetParts();
|
|
||||||
|
|
||||||
if (parts) {
|
if (leafPuppet) {
|
||||||
bool found = false;
|
target = leafPuppet;
|
||||||
parts->InitTraversal();
|
|
||||||
for (int i=0; i<parts->GetNumberOfItems(); ++i) {
|
// 3. Model-driven hierarchy climb:
|
||||||
if (parts->GetNextProp() == picked) {
|
// If the leaf puppet has a uLib object, climb its parents.
|
||||||
found = true;
|
// If any parent is an Assembly with GroupSelection=true, select the assembly puppet instead.
|
||||||
break;
|
uLib::Object* currentObj = leafPuppet->GetContent();
|
||||||
|
|
||||||
|
while (currentObj) {
|
||||||
|
// Object doesn't have parent, but AffineTransform does
|
||||||
|
uLib::Object* parentObj = nullptr;
|
||||||
|
if (auto* at = dynamic_cast<uLib::AffineTransform*>(currentObj)) {
|
||||||
|
parentObj = dynamic_cast<uLib::Object*>(at->GetParent());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto* parentAsm = dynamic_cast<::uLib::Assembly*>(parentObj)) {
|
||||||
|
if (parentAsm->GetGroupSelection()) {
|
||||||
|
// Find the puppet for this parent assembly
|
||||||
|
auto it = self->m_ObjectToPuppet.find(parentAsm);
|
||||||
|
if (it != self->m_ObjectToPuppet.end()) {
|
||||||
|
target = it->second;
|
||||||
|
// Keep climbing to find even larger groups
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (found) {
|
|
||||||
target = p;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
currentObj = parentObj;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -190,59 +297,59 @@ void Viewport::SetupPipeline(vtkRenderWindowInteractor* iren)
|
|||||||
iren->AddObserver(vtkCommand::LeftButtonPressEvent, clickCallback);
|
iren->AddObserver(vtkCommand::LeftButtonPressEvent, clickCallback);
|
||||||
|
|
||||||
// Keyboard events for widget coordinate frame
|
// Keyboard events for widget coordinate frame
|
||||||
m_KeyCallback = vtkSmartPointer<vtkCallbackCommand>::New();
|
pv->m_KeyCallback = vtkSmartPointer<vtkCallbackCommand>::New();
|
||||||
m_KeyCallback->SetClientData(this);
|
pv->m_KeyCallback->SetClientData(this);
|
||||||
m_KeyCallback->SetCallback([](vtkObject* caller, unsigned long event, void* clientdata, void*){
|
pv->m_KeyCallback->SetCallback([](vtkObject* caller, unsigned long event, void* clientdata, void*){
|
||||||
auto* iren = static_cast<vtkRenderWindowInteractor*>(caller);
|
auto* iren = static_cast<vtkRenderWindowInteractor*>(caller);
|
||||||
auto* self = static_cast<Viewport*>(clientdata);
|
auto* self = static_cast<Viewport*>(clientdata);
|
||||||
|
|
||||||
std::string key = iren->GetKeySym();
|
std::string key = iren->GetKeySym();
|
||||||
bool handled = false;
|
bool handled = false;
|
||||||
|
|
||||||
if (self->m_HandlerWidget && self->m_HandlerWidget->GetEnabled()) {
|
if (self->pv->m_HandlerWidget && self->pv->m_HandlerWidget->GetEnabled()) {
|
||||||
if (key == "l") {
|
if (key == "l") {
|
||||||
if (event == vtkCommand::KeyPressEvent) {
|
if (event == vtkCommand::KeyPressEvent) {
|
||||||
self->m_HandlerWidget->SetReferenceFrame(vtkHandlerWidget::LOCAL);
|
self->pv->m_HandlerWidget->SetReferenceFrame(vtkHandlerWidget::LOCAL);
|
||||||
std::cout << "Widget Frame: LOCAL" << std::endl;
|
std::cout << "Widget Frame: LOCAL" << std::endl;
|
||||||
}
|
}
|
||||||
handled = true;
|
handled = true;
|
||||||
}
|
}
|
||||||
else if (key == "g") {
|
else if (key == "g") {
|
||||||
if (event == vtkCommand::KeyPressEvent) {
|
if (event == vtkCommand::KeyPressEvent) {
|
||||||
self->m_HandlerWidget->SetReferenceFrame(vtkHandlerWidget::GLOBAL);
|
self->pv->m_HandlerWidget->SetReferenceFrame(vtkHandlerWidget::GLOBAL);
|
||||||
std::cout << "Widget Frame: GLOBAL" << std::endl;
|
std::cout << "Widget Frame: GLOBAL" << std::endl;
|
||||||
}
|
}
|
||||||
handled = true;
|
handled = true;
|
||||||
}
|
}
|
||||||
else if (key == "c") {
|
else if (key == "c") {
|
||||||
if (event == vtkCommand::KeyPressEvent) {
|
if (event == vtkCommand::KeyPressEvent) {
|
||||||
self->m_HandlerWidget->SetReferenceFrame(vtkHandlerWidget::CENTER);
|
self->pv->m_HandlerWidget->SetReferenceFrame(vtkHandlerWidget::CENTER);
|
||||||
std::cout << "Widget Frame: CENTER" << std::endl;
|
std::cout << "Widget Frame: CENTER" << std::endl;
|
||||||
}
|
}
|
||||||
handled = true;
|
handled = true;
|
||||||
}
|
}
|
||||||
else if (key == "k") {
|
else if (key == "k") {
|
||||||
if (event == vtkCommand::KeyPressEvent) {
|
if (event == vtkCommand::KeyPressEvent) {
|
||||||
self->m_HandlerWidget->SetReferenceFrame(vtkHandlerWidget::CENTER_LOCAL);
|
self->pv->m_HandlerWidget->SetReferenceFrame(vtkHandlerWidget::CENTER_LOCAL);
|
||||||
std::cout << "Widget Frame: CENTER_LOCAL" << std::endl;
|
std::cout << "Widget Frame: CENTER_LOCAL" << std::endl;
|
||||||
}
|
}
|
||||||
handled = true;
|
handled = true;
|
||||||
}
|
}
|
||||||
else if (key == "1") {
|
else if (key == "1") {
|
||||||
if (event == vtkCommand::KeyPressEvent) {
|
if (event == vtkCommand::KeyPressEvent) {
|
||||||
self->m_HandlerWidget->SetTranslationEnabled(!self->m_HandlerWidget->GetTranslationEnabled());
|
self->pv->m_HandlerWidget->SetTranslationEnabled(!self->pv->m_HandlerWidget->GetTranslationEnabled());
|
||||||
}
|
}
|
||||||
handled = true;
|
handled = true;
|
||||||
}
|
}
|
||||||
else if (key == "2") {
|
else if (key == "2") {
|
||||||
if (event == vtkCommand::KeyPressEvent) {
|
if (event == vtkCommand::KeyPressEvent) {
|
||||||
self->m_HandlerWidget->SetRotationEnabled(!self->m_HandlerWidget->GetRotationEnabled());
|
self->pv->m_HandlerWidget->SetRotationEnabled(!self->pv->m_HandlerWidget->GetRotationEnabled());
|
||||||
}
|
}
|
||||||
handled = true;
|
handled = true;
|
||||||
}
|
}
|
||||||
else if (key == "3") {
|
else if (key == "3") {
|
||||||
if (event == vtkCommand::KeyPressEvent) {
|
if (event == vtkCommand::KeyPressEvent) {
|
||||||
self->m_HandlerWidget->SetScalingEnabled(!self->m_HandlerWidget->GetScalingEnabled());
|
self->pv->m_HandlerWidget->SetScalingEnabled(!self->pv->m_HandlerWidget->GetScalingEnabled());
|
||||||
}
|
}
|
||||||
handled = true;
|
handled = true;
|
||||||
}
|
}
|
||||||
@@ -256,12 +363,12 @@ void Viewport::SetupPipeline(vtkRenderWindowInteractor* iren)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (handled) {
|
if (handled) {
|
||||||
self->m_KeyCallback->SetAbortFlag(1);
|
self->pv->m_KeyCallback->SetAbortFlag(1);
|
||||||
iren->Render();
|
iren->Render();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
iren->AddObserver(vtkCommand::KeyPressEvent, m_KeyCallback, 1.0);
|
iren->AddObserver(vtkCommand::KeyPressEvent, pv->m_KeyCallback, 1.0);
|
||||||
iren->AddObserver(vtkCommand::CharEvent, m_KeyCallback, 1.0);
|
iren->AddObserver(vtkCommand::CharEvent, pv->m_KeyCallback, 1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Viewport::Reset()
|
void Viewport::Reset()
|
||||||
@@ -272,15 +379,15 @@ void Viewport::Reset()
|
|||||||
|
|
||||||
void Viewport::ZoomAuto()
|
void Viewport::ZoomAuto()
|
||||||
{
|
{
|
||||||
if (m_Renderer) {
|
if (pv->m_Renderer) {
|
||||||
m_Renderer->ResetCameraClippingRange();
|
pv->m_Renderer->ResetCameraClippingRange();
|
||||||
m_Renderer->ResetCamera();
|
pv->m_Renderer->ResetCamera();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Viewport::ZoomSelected()
|
void Viewport::ZoomSelected()
|
||||||
{
|
{
|
||||||
if (!m_Renderer) return;
|
if (!pv->m_Renderer) return;
|
||||||
|
|
||||||
Puppet* selected = nullptr;
|
Puppet* selected = nullptr;
|
||||||
for (auto* p : m_Puppets) {
|
for (auto* p : m_Puppets) {
|
||||||
@@ -316,62 +423,117 @@ void Viewport::ZoomSelected()
|
|||||||
newBounds[2*i+1] = center[i] + 2.5 * current_h;
|
newBounds[2*i+1] = center[i] + 2.5 * current_h;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_Renderer->ResetCamera(newBounds);
|
pv->m_Renderer->ResetCamera(newBounds);
|
||||||
m_Renderer->ResetCameraClippingRange();
|
pv->m_Renderer->ResetCameraClippingRange();
|
||||||
this->Render();
|
this->Render();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Viewport::AddPuppet(Puppet& prop)
|
void Viewport::AddPuppet(Puppet& prop)
|
||||||
{
|
{
|
||||||
m_Puppets.push_back(&prop);
|
this->RegisterPuppet(&prop, false);
|
||||||
prop.ConnectRenderer(m_Renderer);
|
|
||||||
Render();
|
Render();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Viewport::RemovePuppet(Puppet& prop)
|
void Viewport::RemovePuppet(Puppet& prop)
|
||||||
{
|
{
|
||||||
if (prop.IsSelected()) SelectPuppet(nullptr);
|
this->UnregisterPuppet(&prop);
|
||||||
auto it = std::find(m_Puppets.begin(), m_Puppets.end(), &prop);
|
|
||||||
if (it != m_Puppets.end()) m_Puppets.erase(it);
|
|
||||||
prop.DisconnectRenderer(m_Renderer);
|
|
||||||
Render();
|
Render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Viewport::RegisterPuppet(Puppet* p, bool isPart) {
|
||||||
|
if (!p) return;
|
||||||
|
if (std::find(m_Puppets.begin(), m_Puppets.end(), p) != m_Puppets.end()) return;
|
||||||
|
|
||||||
|
m_Puppets.push_back(p);
|
||||||
|
p->ConnectRenderer(pv->m_Renderer);
|
||||||
|
|
||||||
|
// If it's a part of an assembly, we don't want to draw it twice.
|
||||||
|
// Assembly itself already draws its parts.
|
||||||
|
// But we need ConnectRenderer above to allow highliting and property updates.
|
||||||
|
if (isPart) {
|
||||||
|
pv->m_Renderer->RemoveViewProp(p->GetProp());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the object and register in map
|
||||||
|
uLib::Object* obj = p->GetContent();
|
||||||
|
|
||||||
|
// If it's an assembly, we need to observe its children
|
||||||
|
if (auto* as = dynamic_cast<::uLib::Vtk::Assembly*>(p)) {
|
||||||
|
this->ObserveContext(as->GetChildrenContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj) m_ObjectToPuppet[obj] = p;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Viewport::UnregisterPuppet(Puppet* p) {
|
||||||
|
if (!p) return;
|
||||||
|
if (p->IsSelected()) SelectPuppet(nullptr);
|
||||||
|
|
||||||
|
auto it = std::find(m_Puppets.begin(), m_Puppets.end(), p);
|
||||||
|
if (it != m_Puppets.end()) m_Puppets.erase(it);
|
||||||
|
|
||||||
|
// Remove from map
|
||||||
|
for (auto mapIt = m_ObjectToPuppet.begin(); mapIt != m_ObjectToPuppet.end(); ) {
|
||||||
|
if (mapIt->second == p) mapIt = m_ObjectToPuppet.erase(mapIt);
|
||||||
|
else ++mapIt;
|
||||||
|
}
|
||||||
|
|
||||||
|
p->DisconnectRenderer(pv->m_Renderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Viewport::ObserveContext(vtkObjectsContext* ctx) {
|
||||||
|
if (!ctx) return;
|
||||||
|
|
||||||
|
// Process existing puppets
|
||||||
|
for (auto const& [obj, puppet] : ctx->GetPuppets()) {
|
||||||
|
this->RegisterPuppet(puppet, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen for future puppets
|
||||||
|
uLib::Object::connect(ctx, &vtkObjectsContext::PuppetAdded, [this](Puppet* p){
|
||||||
|
this->RegisterPuppet(p, true);
|
||||||
|
});
|
||||||
|
uLib::Object::connect(ctx, &vtkObjectsContext::PuppetRemoved, [this](Puppet* p){
|
||||||
|
this->UnregisterPuppet(p);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void Viewport::SelectPuppet(Puppet* prop)
|
void Viewport::SelectPuppet(Puppet* prop)
|
||||||
{
|
{
|
||||||
for (auto* p : m_Puppets) {
|
for (auto* p : m_Puppets) {
|
||||||
p->SetSelected(p == prop);
|
p->SetSelected(p == prop);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_HandlerWidget) {
|
if (pv->m_HandlerWidget) {
|
||||||
if (prop) {
|
if (prop) {
|
||||||
vtkProp3D* prop3d = vtkProp3D::SafeDownCast(prop->GetProp());
|
vtkProp3D* prop3d = vtkProp3D::SafeDownCast(prop->GetProp());
|
||||||
if (prop3d) {
|
if (prop3d) {
|
||||||
m_HandlerWidget->SetProp3D(prop3d);
|
pv->m_HandlerWidget->SetProp3D(prop3d);
|
||||||
m_HandlerWidget->SetEnabled(1);
|
pv->m_HandlerWidget->SetEnabled(1);
|
||||||
m_HandlerWidget->PlaceWidget(prop3d->GetBounds());
|
pv->m_HandlerWidget->PlaceWidget(prop3d->GetBounds());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
m_HandlerWidget->SetEnabled(0);
|
pv->m_HandlerWidget->SetEnabled(0);
|
||||||
m_HandlerWidget->SetProp3D(nullptr);
|
pv->m_HandlerWidget->SetProp3D(nullptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Render();
|
Render();
|
||||||
|
OnSelectionChanged(prop);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Viewport::SetGridVisible(bool visible)
|
void Viewport::SetGridVisible(bool visible)
|
||||||
{
|
{
|
||||||
if (m_GridActor) {
|
if (pv->m_GridActor) {
|
||||||
m_GridActor->SetVisibility(visible);
|
pv->m_GridActor->SetVisibility(visible);
|
||||||
Render();
|
Render();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Viewport::GetGridVisible() const
|
bool Viewport::GetGridVisible() const
|
||||||
{
|
{
|
||||||
if (m_GridActor) {
|
if (pv->m_GridActor) {
|
||||||
return m_GridActor->GetVisibility() != 0;
|
return pv->m_GridActor->GetVisibility() != 0;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -385,26 +547,26 @@ void Viewport::SetGridAxis(Axis axis)
|
|||||||
|
|
||||||
void Viewport::addProp(vtkProp* prop)
|
void Viewport::addProp(vtkProp* prop)
|
||||||
{
|
{
|
||||||
if (m_Renderer) {
|
if (pv->m_Renderer) {
|
||||||
m_Renderer->AddActor(prop);
|
pv->m_Renderer->AddActor(prop);
|
||||||
Render();
|
Render();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Viewport::RemoveProp(vtkProp* prop)
|
void Viewport::RemoveProp(vtkProp* prop)
|
||||||
{
|
{
|
||||||
if (m_Renderer) {
|
if (pv->m_Renderer) {
|
||||||
m_Renderer->RemoveViewProp(prop);
|
pv->m_Renderer->RemoveViewProp(prop);
|
||||||
Render();
|
Render();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Viewport::UpdateGrid()
|
void Viewport::UpdateGrid()
|
||||||
{
|
{
|
||||||
if (!m_Renderer || !m_GridSource) return;
|
if (!pv->m_Renderer || !pv->m_GridSource) return;
|
||||||
if (m_GridActor && !m_GridActor->GetVisibility()) return;
|
if (pv->m_GridActor && !pv->m_GridActor->GetVisibility()) return;
|
||||||
|
|
||||||
vtkCamera* camera = m_Renderer->GetActiveCamera();
|
vtkCamera* camera = pv->m_Renderer->GetActiveCamera();
|
||||||
if (!camera) return;
|
if (!camera) return;
|
||||||
|
|
||||||
// Determine the "scale" of the view (how many units are visible vertically)
|
// Determine the "scale" of the view (how many units are visible vertically)
|
||||||
@@ -425,6 +587,9 @@ void Viewport::UpdateGrid()
|
|||||||
double log10Spacing = std::floor(std::log10(viewHeight / 5.0));
|
double log10Spacing = std::floor(std::log10(viewHeight / 5.0));
|
||||||
double spacing = std::pow(10.0, log10Spacing);
|
double spacing = std::pow(10.0, log10Spacing);
|
||||||
|
|
||||||
|
// Spacing should be at least 1mm
|
||||||
|
if (spacing < 1.0) spacing = 1.0;
|
||||||
|
|
||||||
// Get current focal point to center the grid near what we're looking at
|
// Get current focal point to center the grid near what we're looking at
|
||||||
double focalPoint[3];
|
double focalPoint[3];
|
||||||
camera->GetFocalPoint(focalPoint);
|
camera->GetFocalPoint(focalPoint);
|
||||||
@@ -455,31 +620,28 @@ void Viewport::UpdateGrid()
|
|||||||
p1[idxH] = maxH; p1[idxV] = minV; p1[idxN] = centerN;
|
p1[idxH] = maxH; p1[idxV] = minV; p1[idxN] = centerN;
|
||||||
p2[idxH] = minH; p2[idxV] = maxV; p2[idxN] = centerN;
|
p2[idxH] = minH; p2[idxV] = maxV; p2[idxN] = centerN;
|
||||||
|
|
||||||
m_GridSource->SetOrigin(origin);
|
pv->m_GridSource->SetOrigin(origin);
|
||||||
m_GridSource->SetPoint1(p1);
|
pv->m_GridSource->SetPoint1(p1);
|
||||||
m_GridSource->SetPoint2(p2);
|
pv->m_GridSource->SetPoint2(p2);
|
||||||
m_GridSource->SetXResolution(numLines);
|
pv->m_GridSource->SetXResolution(numLines);
|
||||||
m_GridSource->SetYResolution(numLines);
|
pv->m_GridSource->SetYResolution(numLines);
|
||||||
m_GridSource->Update();
|
pv->m_GridSource->Update();
|
||||||
|
|
||||||
if (m_OriginAxes) {
|
if (pv->m_OriginAxes) {
|
||||||
m_OriginAxes->SetScaleFactor(spacing);
|
pv->m_OriginAxes->SetScaleFactor(spacing);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update annotation for grid size
|
// Update annotation for grid size
|
||||||
char gridLabel[32];
|
char gridLabel[32];
|
||||||
if (spacing >= 1000.0) {
|
if (spacing >= 1000.0) {
|
||||||
sprintf(gridLabel, "Grid: %.0f m", spacing / 1000.0);
|
sprintf(gridLabel, "Grid: %.1f m", spacing / 1000.0);
|
||||||
} else if (spacing >= 10.0) {
|
} else if (spacing >= 10.0) {
|
||||||
sprintf(gridLabel, "Grid: %.0f cm", spacing / 10.0);
|
sprintf(gridLabel, "Grid: %.1f cm", spacing / 10.0);
|
||||||
} else {
|
} else {
|
||||||
sprintf(gridLabel, "Grid: %.0f mm", spacing);
|
sprintf(gridLabel, "Grid: %.0f mm", spacing);
|
||||||
}
|
}
|
||||||
m_Annotation->SetText(1, gridLabel);
|
pv->m_Annotation->SetText(1, gridLabel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
} // namespace Vtk
|
} // namespace Vtk
|
||||||
} // namespace uLib
|
} // namespace uLib
|
||||||
|
|||||||
@@ -2,23 +2,22 @@
|
|||||||
#define ULIB_VTK_VIEWPORT_H
|
#define ULIB_VTK_VIEWPORT_H
|
||||||
|
|
||||||
#include "uLibVtkInterface.h"
|
#include "uLibVtkInterface.h"
|
||||||
#include <vtkCornerAnnotation.h>
|
|
||||||
#include <vtkOrientationMarkerWidget.h>
|
|
||||||
#include <vtkRenderer.h>
|
|
||||||
#include <vtkSmartPointer.h>
|
|
||||||
#include <vtkVersion.h>
|
|
||||||
#include <vtkRenderer.h>
|
|
||||||
#include <vtkCornerAnnotation.h>
|
|
||||||
#include <vtkOrientationMarkerWidget.h>
|
|
||||||
#include <vtkCameraOrientationWidget.h>
|
|
||||||
#include <vtkNamedColors.h>
|
|
||||||
#include <vtkAxes.h>
|
|
||||||
#include <vtkAxesActor.h>
|
|
||||||
#include <vtkPlaneSource.h>
|
|
||||||
#include <vtkActor.h>
|
|
||||||
#include <vtkCellPicker.h>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
namespace uLib { class Object; }
|
||||||
|
|
||||||
|
// VTK classes are in the global namespace
|
||||||
|
class vtkRenderer;
|
||||||
|
class vtkCornerAnnotation;
|
||||||
|
class vtkOrientationMarkerWidget;
|
||||||
|
class vtkCameraOrientationWidget;
|
||||||
|
class vtkPlaneSource;
|
||||||
|
class vtkActor;
|
||||||
|
class vtkAxes;
|
||||||
|
class vtkNamedColors;
|
||||||
|
class vtkCellPicker;
|
||||||
|
class vtkCallbackCommand;
|
||||||
class vtkProp;
|
class vtkProp;
|
||||||
class vtk3DWidget;
|
class vtk3DWidget;
|
||||||
class vtkRenderWindow;
|
class vtkRenderWindow;
|
||||||
@@ -28,6 +27,7 @@ class vtkCamera;
|
|||||||
namespace uLib {
|
namespace uLib {
|
||||||
namespace Vtk {
|
namespace Vtk {
|
||||||
|
|
||||||
|
struct ViewportData;
|
||||||
class vtkHandlerWidget;
|
class vtkHandlerWidget;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -56,11 +56,13 @@ public:
|
|||||||
void AddWidget(vtk3DWidget *widget);
|
void AddWidget(vtk3DWidget *widget);
|
||||||
|
|
||||||
// Direct access to VTK internals
|
// Direct access to VTK internals
|
||||||
vtkRenderer* GetRenderer() { return m_Renderer; }
|
vtkRenderer* GetRenderer();
|
||||||
virtual vtkRenderWindow* GetRenderWindow() = 0;
|
virtual vtkRenderWindow* GetRenderWindow() = 0;
|
||||||
virtual vtkRenderWindowInteractor* GetInteractor() = 0;
|
virtual vtkRenderWindowInteractor* GetInteractor() = 0;
|
||||||
vtkCornerAnnotation* GetAnnotation() { return m_Annotation; }
|
vtkCornerAnnotation* GetAnnotation();
|
||||||
vtkCameraOrientationWidget* GetCameraWidget(){ return m_CameraWidget; }
|
vtkCameraOrientationWidget* GetCameraWidget();
|
||||||
|
void DisableHandler();
|
||||||
|
virtual void OnSelectionChanged(Puppet* p) {}
|
||||||
const std::vector<Puppet*>& getPuppets() const { return m_Puppets; }
|
const std::vector<Puppet*>& getPuppets() const { return m_Puppets; }
|
||||||
|
|
||||||
// Grid control
|
// Grid control
|
||||||
@@ -76,23 +78,15 @@ protected:
|
|||||||
|
|
||||||
void UpdateGrid();
|
void UpdateGrid();
|
||||||
|
|
||||||
vtkSmartPointer<vtkRenderer> m_Renderer;
|
// Internal puppet registration
|
||||||
vtkSmartPointer<vtkCornerAnnotation> m_Annotation;
|
void RegisterPuppet(Puppet* p, bool isPart = false);
|
||||||
vtkSmartPointer<vtkOrientationMarkerWidget> m_Marker;
|
void UnregisterPuppet(Puppet* p);
|
||||||
vtkSmartPointer<vtkCameraOrientationWidget> m_CameraWidget;
|
void ObserveContext(class vtkObjectsContext* ctx);
|
||||||
|
|
||||||
vtkSmartPointer<vtkPlaneSource> m_GridSource;
|
|
||||||
vtkSmartPointer<vtkActor> m_GridActor;
|
|
||||||
vtkSmartPointer<vtkAxes> m_OriginAxes;
|
|
||||||
vtkSmartPointer<vtkActor> m_OriginAxesActor;
|
|
||||||
|
|
||||||
vtkSmartPointer<vtkNamedColors> m_Colors;
|
|
||||||
|
|
||||||
|
struct ViewportData *pv;
|
||||||
Axis m_GridAxis;
|
Axis m_GridAxis;
|
||||||
vtkSmartPointer<vtkHandlerWidget> m_HandlerWidget;
|
|
||||||
std::vector<Puppet*> m_Puppets;
|
std::vector<Puppet*> m_Puppets;
|
||||||
vtkSmartPointer<vtkCellPicker> m_Picker;
|
std::map<uLib::Object*, Puppet*> m_ObjectToPuppet;
|
||||||
vtkSmartPointer<vtkCallbackCommand> m_KeyCallback;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Vtk
|
} // namespace Vtk
|
||||||
|
|||||||
Reference in New Issue
Block a user