Compare commits
38 Commits
ca5f576b99
...
andrea-alg
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
876b8f4592 | ||
|
|
ec2027e980 | ||
|
|
69b47623f8 | ||
|
|
f5c1e317e8 | ||
|
|
e0ffeff5b7 | ||
|
|
2c5d6842c3 | ||
|
|
422113a0e9 | ||
|
|
e4a8499104 | ||
|
|
6a65fe94c8 | ||
|
|
7d4acaef6d | ||
|
|
a467b7385b | ||
|
|
0c8ef7337c | ||
|
|
913a1f7b3a | ||
|
|
5397baa50c | ||
|
|
51e6dbb4f5 | ||
|
|
b45cde0bad | ||
|
|
f13342ff30 | ||
|
|
5d0efb3078 | ||
|
|
94843de711 | ||
|
|
b52ae808b8 | ||
|
|
d87f3a984e | ||
|
|
324aaa91b7 | ||
|
|
a8f786d8d1 | ||
|
|
add9d37aea | ||
|
|
0bff36f8ba | ||
|
|
cd95f16221 | ||
|
|
bbd7493d9f | ||
|
|
033fb598c7 | ||
|
|
c44a7738c0 | ||
|
|
85e1f1448f | ||
|
|
6234dffaa7 | ||
|
|
80952cc706 | ||
|
|
ae27e9d46d | ||
|
|
a8a313e5cf | ||
|
|
7c8c7beae4 | ||
|
|
a8c0d5edc2 | ||
|
|
dbb5f24933 | ||
|
|
1e6e3ae4f4 |
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})
|
||||||
@@ -157,7 +158,10 @@ else()
|
|||||||
GUISupportQt)
|
GUISupportQt)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
find_package(Qt6 COMPONENTS Widgets REQUIRED)
|
find_package(Qt6 COMPONENTS Widgets)
|
||||||
|
if(Qt6_FOUND)
|
||||||
|
add_compile_definitions(HAVE_QT)
|
||||||
|
endif()
|
||||||
|
|
||||||
find_package(Geant4)
|
find_package(Geant4)
|
||||||
if(Geant4_FOUND)
|
if(Geant4_FOUND)
|
||||||
|
|||||||
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
|
||||||
@@ -3,6 +3,20 @@ add_executable(gcompose
|
|||||||
src/main.cpp
|
src/main.cpp
|
||||||
src/MainWindow.h
|
src/MainWindow.h
|
||||||
src/MainWindow.cpp
|
src/MainWindow.cpp
|
||||||
|
src/ViewportPane.h
|
||||||
|
src/ViewportPane.cpp
|
||||||
|
src/MainPanel.h
|
||||||
|
src/MainPanel.cpp
|
||||||
|
src/ContextPanel.h
|
||||||
|
src/ContextPanel.cpp
|
||||||
|
src/ContextModel.h
|
||||||
|
src/ContextModel.cpp
|
||||||
|
src/StyleManager.h
|
||||||
|
src/StyleManager.cpp
|
||||||
|
src/PropertyWidgets.h
|
||||||
|
src/PropertyWidgets.cpp
|
||||||
|
src/PropertiesPanel.h
|
||||||
|
src/PropertiesPanel.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set_target_properties(gcompose PROPERTIES
|
set_target_properties(gcompose PROPERTIES
|
||||||
@@ -29,6 +43,7 @@ target_link_libraries(gcompose
|
|||||||
mutomMath
|
mutomMath
|
||||||
mutomGeant
|
mutomGeant
|
||||||
mutomVtk
|
mutomVtk
|
||||||
|
mutomRoot
|
||||||
${Geant4_LIBS_FILTERED}
|
${Geant4_LIBS_FILTERED}
|
||||||
${VTK_LIBRARIES}
|
${VTK_LIBRARIES}
|
||||||
Qt6::Widgets
|
Qt6::Widgets
|
||||||
|
|||||||
176
app/gcompose/src/ContextModel.cpp
Normal file
176
app/gcompose/src/ContextModel.cpp
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
#include "ContextModel.h"
|
||||||
|
#include <QString>
|
||||||
|
#include <typeinfo>
|
||||||
|
#include <cxxabi.h>
|
||||||
|
#include <functional>
|
||||||
|
#include "Core/Object.h"
|
||||||
|
|
||||||
|
ContextModel::ContextModel(QObject* parent)
|
||||||
|
: QAbstractItemModel(parent), m_rootContext(nullptr) {}
|
||||||
|
|
||||||
|
ContextModel::~ContextModel() {}
|
||||||
|
|
||||||
|
void ContextModel::setContext(uLib::ObjectsContext* context) {
|
||||||
|
beginResetModel();
|
||||||
|
m_rootContext = context;
|
||||||
|
if (m_rootContext) {
|
||||||
|
auto refresh = [this]() {
|
||||||
|
this->beginResetModel();
|
||||||
|
this->endResetModel();
|
||||||
|
};
|
||||||
|
|
||||||
|
uLib::Object::connect(m_rootContext, &uLib::Object::Updated, refresh);
|
||||||
|
uLib::Object::connect(m_rootContext, &uLib::ObjectsContext::ObjectAdded, [this, refresh](uLib::Object* obj) {
|
||||||
|
uLib::Object::connect(obj, &uLib::Object::Updated, refresh);
|
||||||
|
refresh();
|
||||||
|
});
|
||||||
|
uLib::Object::connect(m_rootContext, &uLib::ObjectsContext::ObjectRemoved, [this, refresh](uLib::Object* obj) {
|
||||||
|
// Disconnect would be good here but not strictly required if refresh handles it
|
||||||
|
refresh();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Connect existing objects
|
||||||
|
for (auto* obj : m_rootContext->GetObjects()) {
|
||||||
|
uLib::Object::connect(obj, &uLib::Object::Updated, refresh);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
endResetModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
QModelIndex ContextModel::index(int row, int column, const QModelIndex& parent) const {
|
||||||
|
if (!hasIndex(row, column, parent) || !m_rootContext) {
|
||||||
|
return QModelIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!parent.isValid()) {
|
||||||
|
if (row < m_rootContext->GetCount()) {
|
||||||
|
return createIndex(row, column, m_rootContext->GetObject(row));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
uLib::Object* parentObj = static_cast<uLib::Object*>(parent.internalPointer());
|
||||||
|
uLib::ObjectsContext* parentCtx = dynamic_cast<uLib::ObjectsContext*>(parentObj);
|
||||||
|
if (parentCtx && row < parentCtx->GetCount()) {
|
||||||
|
return createIndex(row, column, parentCtx->GetObject(row));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return QModelIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
QModelIndex ContextModel::parent(const QModelIndex& child) const {
|
||||||
|
if (!child.isValid() || !m_rootContext) {
|
||||||
|
return QModelIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
uLib::Object* childObj = static_cast<uLib::Object*>(child.internalPointer());
|
||||||
|
|
||||||
|
// Finding the parent of childObj is O(N) since there is no parent pointer.
|
||||||
|
// We just do a recursive search starting from root context.
|
||||||
|
std::function<uLib::ObjectsContext*(uLib::ObjectsContext*, uLib::Object*)> findParent =
|
||||||
|
[&findParent](uLib::ObjectsContext* ctx, uLib::Object* target) -> uLib::ObjectsContext* {
|
||||||
|
for (const auto& obj : ctx->GetObjects()) {
|
||||||
|
if (obj == target) return ctx;
|
||||||
|
if (auto subCtx = dynamic_cast<uLib::ObjectsContext*>(obj)) {
|
||||||
|
if (auto p = findParent(subCtx, target)) return p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
uLib::ObjectsContext* parentCtx = findParent(m_rootContext, childObj);
|
||||||
|
if (!parentCtx || parentCtx == m_rootContext) {
|
||||||
|
return QModelIndex(); // Root items have invalid parent index
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now need to find the row of parentCtx in its own parent Context.
|
||||||
|
uLib::ObjectsContext* grandParentCtx = findParent(m_rootContext, parentCtx);
|
||||||
|
if (!grandParentCtx) grandParentCtx = m_rootContext;
|
||||||
|
|
||||||
|
int row = -1;
|
||||||
|
for (size_t i = 0; i < grandParentCtx->GetCount(); ++i) {
|
||||||
|
if (grandParentCtx->GetObject(i) == parentCtx) {
|
||||||
|
row = (int)i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (row != -1) {
|
||||||
|
return createIndex(row, 0, parentCtx);
|
||||||
|
}
|
||||||
|
return QModelIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
int ContextModel::rowCount(const QModelIndex& parent) const {
|
||||||
|
if (!m_rootContext) return 0;
|
||||||
|
|
||||||
|
if (!parent.isValid()) {
|
||||||
|
return m_rootContext->GetCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
uLib::Object* parentObj = static_cast<uLib::Object*>(parent.internalPointer());
|
||||||
|
if (auto parentCtx = dynamic_cast<uLib::ObjectsContext*>(parentObj)) {
|
||||||
|
return parentCtx->GetCount();
|
||||||
|
}
|
||||||
|
return 0; // leaf node
|
||||||
|
}
|
||||||
|
|
||||||
|
int ContextModel::columnCount(const QModelIndex& parent) const {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static QString getDemangledName(const std::type_info& info) {
|
||||||
|
int status = -4;
|
||||||
|
char* demangled = abi::__cxa_demangle(info.name(), nullptr, nullptr, &status);
|
||||||
|
QString res = (status == 0 && demangled) ? QString::fromUtf8(demangled) : QString::fromUtf8(info.name());
|
||||||
|
if (demangled) free(demangled);
|
||||||
|
|
||||||
|
// Remove namespaces
|
||||||
|
int lastColon = res.lastIndexOf("::");
|
||||||
|
if (lastColon != -1) {
|
||||||
|
res = res.mid(lastColon + 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove "class " prefix if any
|
||||||
|
if (res.startsWith("class ")) res = res.mid(6);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant ContextModel::data(const QModelIndex& index, int role) const {
|
||||||
|
if (!index.isValid()) return QVariant();
|
||||||
|
|
||||||
|
uLib::Object* obj = static_cast<uLib::Object*>(index.internalPointer());
|
||||||
|
|
||||||
|
if (role == Qt::DisplayRole) {
|
||||||
|
QString typeName = getDemangledName(typeid(*obj));
|
||||||
|
std::string instName = obj->GetInstanceName();
|
||||||
|
if (instName.empty()) return typeName;
|
||||||
|
return QString("%1 (%2)").arg(QString::fromStdString(instName)).arg(typeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role == Qt::EditRole) {
|
||||||
|
return QString::fromStdString(obj->GetInstanceName());
|
||||||
|
}
|
||||||
|
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant ContextModel::headerData(int section, Qt::Orientation orientation, int role) const {
|
||||||
|
if (orientation == Qt::Horizontal && role == Qt::DisplayRole && section == 0) {
|
||||||
|
return "Object Context";
|
||||||
|
}
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
Qt::ItemFlags ContextModel::flags(const QModelIndex& index) const {
|
||||||
|
if (!index.isValid()) return Qt::NoItemFlags;
|
||||||
|
return Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ContextModel::setData(const QModelIndex& index, const QVariant& value, int role) {
|
||||||
|
if (index.isValid() && role == Qt::EditRole) {
|
||||||
|
uLib::Object* obj = static_cast<uLib::Object*>(index.internalPointer());
|
||||||
|
obj->SetInstanceName(value.toString().toStdString());
|
||||||
|
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
28
app/gcompose/src/ContextModel.h
Normal file
28
app/gcompose/src/ContextModel.h
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
#ifndef CONTEXT_MODEL_H
|
||||||
|
#define CONTEXT_MODEL_H
|
||||||
|
|
||||||
|
#include <QAbstractItemModel>
|
||||||
|
#include "Core/ObjectsContext.h"
|
||||||
|
|
||||||
|
class ContextModel : public QAbstractItemModel {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit ContextModel(QObject* parent = nullptr);
|
||||||
|
virtual ~ContextModel();
|
||||||
|
|
||||||
|
void setContext(uLib::ObjectsContext* context);
|
||||||
|
|
||||||
|
QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override;
|
||||||
|
QModelIndex parent(const QModelIndex& child) const override;
|
||||||
|
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||||
|
int columnCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||||
|
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
||||||
|
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||||
|
Qt::ItemFlags flags(const QModelIndex& index) const override;
|
||||||
|
bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
uLib::ObjectsContext* m_rootContext;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // CONTEXT_MODEL_H
|
||||||
118
app/gcompose/src/ContextPanel.cpp
Normal file
118
app/gcompose/src/ContextPanel.cpp
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
#include "ContextPanel.h"
|
||||||
|
#include "ContextModel.h"
|
||||||
|
#include "PropertyWidgets.h"
|
||||||
|
#include "PropertiesPanel.h"
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QTreeView>
|
||||||
|
#include <QSplitter>
|
||||||
|
#include <QList>
|
||||||
|
#include <QShortcut>
|
||||||
|
#include <QItemSelectionModel>
|
||||||
|
|
||||||
|
ContextPanel::ContextPanel(QWidget* parent)
|
||||||
|
: QWidget(parent)
|
||||||
|
, m_context(nullptr) {
|
||||||
|
this->setObjectName("ContextPanel");
|
||||||
|
this->setAttribute(Qt::WA_StyledBackground);
|
||||||
|
m_layout = new QVBoxLayout(this);
|
||||||
|
m_layout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
m_layout->setSpacing(0);
|
||||||
|
|
||||||
|
// Title bar setup
|
||||||
|
m_titleBar = new QWidget(this);
|
||||||
|
m_titleBar->setObjectName("PaneTitleBar");
|
||||||
|
m_titleBar->setFixedHeight(22);
|
||||||
|
|
||||||
|
auto* titleLayout = new QHBoxLayout(m_titleBar);
|
||||||
|
titleLayout->setContentsMargins(5, 0, 5, 0);
|
||||||
|
|
||||||
|
m_titleLabel = new QLabel("Context Panel", m_titleBar);
|
||||||
|
m_titleLabel->setObjectName("TitleLabel");
|
||||||
|
titleLayout->addWidget(m_titleLabel);
|
||||||
|
titleLayout->addStretch();
|
||||||
|
|
||||||
|
m_layout->addWidget(m_titleBar);
|
||||||
|
|
||||||
|
m_treeView = new QTreeView(this);
|
||||||
|
m_treeView->setObjectName("ContextTree");
|
||||||
|
m_treeView->setHeaderHidden(false);
|
||||||
|
|
||||||
|
m_model = new ContextModel(this);
|
||||||
|
m_treeView->setModel(m_model);
|
||||||
|
|
||||||
|
m_splitter = new QSplitter(Qt::Vertical, this);
|
||||||
|
m_splitter->addWidget(m_treeView);
|
||||||
|
|
||||||
|
m_propertiesPanel = new PropertiesPanel(m_splitter);
|
||||||
|
m_splitter->addWidget(m_propertiesPanel);
|
||||||
|
|
||||||
|
QList<int> sizes;
|
||||||
|
sizes << 400 << 600;
|
||||||
|
m_splitter->setSizes(sizes);
|
||||||
|
|
||||||
|
m_layout->addWidget(m_splitter);
|
||||||
|
|
||||||
|
connect(m_treeView->selectionModel(), &QItemSelectionModel::selectionChanged,
|
||||||
|
this, &ContextPanel::onSelectionChanged);
|
||||||
|
|
||||||
|
auto* deleteShortcut = new QShortcut(QKeySequence::Delete, this);
|
||||||
|
connect(deleteShortcut, &QShortcut::activated, [this]() {
|
||||||
|
auto selectedIndexes = m_treeView->selectionModel()->selectedIndexes();
|
||||||
|
if (selectedIndexes.isEmpty() || !m_context) return;
|
||||||
|
|
||||||
|
std::vector<uLib::Object*> toRemove;
|
||||||
|
for (const auto& idx : selectedIndexes) {
|
||||||
|
if (idx.column() == 0) {
|
||||||
|
toRemove.push_back(static_cast<uLib::Object*>(idx.internalPointer()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto* obj : toRemove) {
|
||||||
|
m_context->RemoveObject(obj);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ContextPanel::~ContextPanel() {}
|
||||||
|
|
||||||
|
void ContextPanel::setContext(uLib::ObjectsContext* context) {
|
||||||
|
m_context = context;
|
||||||
|
m_model->setContext(context);
|
||||||
|
m_treeView->expandAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextPanel::onSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected) {
|
||||||
|
uLib::Object* target = nullptr;
|
||||||
|
if (!selected.indexes().isEmpty()) {
|
||||||
|
target = static_cast<uLib::Object*>(selected.indexes().first().internalPointer());
|
||||||
|
}
|
||||||
|
|
||||||
|
emit objectSelected(target);
|
||||||
|
m_propertiesPanel->setObject(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextPanel::selectObject(uLib::Object* obj) {
|
||||||
|
if (!obj) {
|
||||||
|
clearSelection();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < m_model->rowCount(); ++i) {
|
||||||
|
QModelIndex idx = m_model->index(i, 0);
|
||||||
|
if (idx.internalPointer() == obj) {
|
||||||
|
QSignalBlocker blocker(m_treeView->selectionModel());
|
||||||
|
m_treeView->selectionModel()->select(idx, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
|
||||||
|
m_treeView->scrollTo(idx);
|
||||||
|
m_propertiesPanel->setObject(obj); // Explicitly update properties too
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextPanel::clearSelection() {
|
||||||
|
QSignalBlocker blocker(m_treeView->selectionModel());
|
||||||
|
m_treeView->selectionModel()->clearSelection();
|
||||||
|
m_propertiesPanel->setObject(nullptr);
|
||||||
|
}
|
||||||
45
app/gcompose/src/ContextPanel.h
Normal file
45
app/gcompose/src/ContextPanel.h
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
#ifndef CONTEXTPANEL_H
|
||||||
|
#define CONTEXTPANEL_H
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
#include <QItemSelection>
|
||||||
|
#include "Core/Object.h"
|
||||||
|
|
||||||
|
class QVBoxLayout;
|
||||||
|
class QHBoxLayout;
|
||||||
|
class QLabel;
|
||||||
|
class QTreeView;
|
||||||
|
class QSplitter;
|
||||||
|
class ContextModel;
|
||||||
|
namespace uLib { class ObjectsContext; }
|
||||||
|
|
||||||
|
class ContextPanel : public QWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
ContextPanel(QWidget* parent = nullptr);
|
||||||
|
~ContextPanel();
|
||||||
|
|
||||||
|
void setContext(uLib::ObjectsContext* context);
|
||||||
|
void selectObject(uLib::Object* obj);
|
||||||
|
void clearSelection();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void objectSelected(uLib::Object* obj);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QVBoxLayout* m_layout;
|
||||||
|
QWidget* m_titleBar;
|
||||||
|
QLabel* m_titleLabel;
|
||||||
|
|
||||||
|
QTreeView* m_treeView;
|
||||||
|
ContextModel* m_model;
|
||||||
|
QSplitter* m_splitter;
|
||||||
|
|
||||||
|
class PropertiesPanel* m_propertiesPanel;
|
||||||
|
uLib::ObjectsContext* m_context;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // CONTEXTPANEL_H
|
||||||
247
app/gcompose/src/MainPanel.cpp
Normal file
247
app/gcompose/src/MainPanel.cpp
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
#include "MainPanel.h"
|
||||||
|
#include "ViewportPane.h"
|
||||||
|
#include "ContextPanel.h"
|
||||||
|
#include "PropertiesPanel.h"
|
||||||
|
#include "Core/ObjectFactory.h"
|
||||||
|
#include "Core/ObjectsContext.h"
|
||||||
|
#include "Vtk/vtkObjectsContext.h"
|
||||||
|
#include "Vtk/vtkQViewport.h"
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QSplitter>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QMenu>
|
||||||
|
#include <QAction>
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QFileDialog>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include "StyleManager.h"
|
||||||
|
#include "Math/VoxImage.h"
|
||||||
|
|
||||||
|
MainPanel::MainPanel(QWidget* parent) : QWidget(parent), m_context(nullptr), m_mainVtkContext(nullptr) {
|
||||||
|
this->setObjectName("MainPanel");
|
||||||
|
this->setAttribute(Qt::WA_StyledBackground);
|
||||||
|
auto* mainLayout = new QVBoxLayout(this);
|
||||||
|
mainLayout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
mainLayout->setSpacing(0);
|
||||||
|
|
||||||
|
// 1. Top Menu Panel
|
||||||
|
auto* menuPanel = new QWidget(this);
|
||||||
|
menuPanel->setObjectName("MenuPanel");
|
||||||
|
menuPanel->setFixedHeight(36);
|
||||||
|
|
||||||
|
auto* menuLayout = new QHBoxLayout(menuPanel);
|
||||||
|
menuLayout->setContentsMargins(10, 0, 10, 0);
|
||||||
|
menuLayout->setSpacing(15);
|
||||||
|
|
||||||
|
auto* logo = new QLabel("G-COMPOSE", menuPanel);
|
||||||
|
logo->setObjectName("LogoLabel");
|
||||||
|
|
||||||
|
// File Menu Button
|
||||||
|
auto* btnFile = new QPushButton("File", menuPanel);
|
||||||
|
btnFile->setObjectName("MenuButton");
|
||||||
|
auto* fileMenu = new QMenu(btnFile);
|
||||||
|
fileMenu->addAction("Open", this, &MainPanel::onOpen);
|
||||||
|
fileMenu->addAction("Save", this, &MainPanel::onSave);
|
||||||
|
fileMenu->addAction("Save As", this, &MainPanel::onSaveAs);
|
||||||
|
fileMenu->addAction("Exit", this, &MainPanel::onExit);
|
||||||
|
btnFile->setMenu(fileMenu);
|
||||||
|
|
||||||
|
// Theme Menu Button
|
||||||
|
auto* btnTheme = new QPushButton("Theme", menuPanel);
|
||||||
|
btnTheme->setObjectName("MenuButton");
|
||||||
|
auto* themeMenu = new QMenu(btnTheme);
|
||||||
|
themeMenu->addAction("Dark", this, &MainPanel::onDarkTheme);
|
||||||
|
themeMenu->addAction("Bright", this, &MainPanel::onBrightTheme);
|
||||||
|
btnTheme->setMenu(themeMenu);
|
||||||
|
|
||||||
|
// New Menu Button
|
||||||
|
auto* btnNew = new QPushButton("Add", menuPanel);
|
||||||
|
btnNew->setObjectName("MenuButton");
|
||||||
|
auto* newMenu = new QMenu(btnNew);
|
||||||
|
|
||||||
|
auto classes = uLib::ObjectFactory::Instance().GetRegisteredClasses();
|
||||||
|
for (const auto& className : classes) {
|
||||||
|
auto* action = newMenu->addAction(QString::fromStdString(className));
|
||||||
|
connect(action, &QAction::triggered, [this, className]() {
|
||||||
|
this->onCreateObject(className);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
btnNew->setMenu(newMenu);
|
||||||
|
|
||||||
|
menuLayout->addWidget(logo);
|
||||||
|
menuLayout->addWidget(btnFile);
|
||||||
|
menuLayout->addWidget(btnNew);
|
||||||
|
menuLayout->addWidget(btnTheme);
|
||||||
|
menuLayout->addStretch();
|
||||||
|
|
||||||
|
mainLayout->addWidget(menuPanel);
|
||||||
|
|
||||||
|
// 2. Central Splitter Area
|
||||||
|
m_rootSplitter = new QSplitter(Qt::Horizontal, this);
|
||||||
|
m_contextPanel = new ContextPanel(m_rootSplitter);
|
||||||
|
m_rootSplitter->addWidget(m_contextPanel);
|
||||||
|
m_rootSplitter->setStretchFactor(0, 0);
|
||||||
|
|
||||||
|
m_firstPane = new ViewportPane(m_rootSplitter);
|
||||||
|
m_rootSplitter->addWidget(m_firstPane);
|
||||||
|
m_rootSplitter->setStretchFactor(1, 1);
|
||||||
|
|
||||||
|
connect(m_contextPanel, &ContextPanel::objectSelected, [this](uLib::Object* obj) {
|
||||||
|
if (auto* viewport = qobject_cast<uLib::Vtk::QViewport*>(m_firstPane->currentViewport())) {
|
||||||
|
uLib::Vtk::Puppet* puppet = nullptr;
|
||||||
|
if (m_mainVtkContext) {
|
||||||
|
puppet = m_mainVtkContext->GetPuppet(obj);
|
||||||
|
}
|
||||||
|
viewport->SelectPuppet(puppet);
|
||||||
|
// Update the display properties in the viewport pane itself - use the puppet proxy if possible
|
||||||
|
m_firstPane->setObject(puppet ? (uLib::Object*)puppet : obj);
|
||||||
|
} else {
|
||||||
|
m_firstPane->setObject(obj);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set initial sizes: Context(250), Viewport(600), Properties(250)
|
||||||
|
QList<int> sizes;
|
||||||
|
sizes << 250 << 600 << 250;
|
||||||
|
m_rootSplitter->setSizes(sizes);
|
||||||
|
|
||||||
|
mainLayout->addWidget(m_rootSplitter, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainPanel::setContext(uLib::ObjectsContext* context) {
|
||||||
|
m_context = context;
|
||||||
|
m_contextPanel->setContext(context);
|
||||||
|
|
||||||
|
if (m_mainVtkContext) {
|
||||||
|
if (auto* viewport = qobject_cast<uLib::Vtk::QViewport*>(m_firstPane->currentViewport())) {
|
||||||
|
viewport->RemovePuppet(*m_mainVtkContext);
|
||||||
|
}
|
||||||
|
delete m_mainVtkContext;
|
||||||
|
m_mainVtkContext = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context) {
|
||||||
|
if (auto* viewport = qobject_cast<uLib::Vtk::QViewport*>(m_firstPane->currentViewport())) {
|
||||||
|
m_mainVtkContext = new uLib::Vtk::vtkObjectsContext(context);
|
||||||
|
// viewport->AddPuppet(*m_mainVtkContext); // redundant
|
||||||
|
|
||||||
|
auto syncSelection = [this](uLib::Vtk::Puppet* p) {
|
||||||
|
if (!p) {
|
||||||
|
m_contextPanel->clearSelection();
|
||||||
|
m_firstPane->setObject(nullptr);
|
||||||
|
} else {
|
||||||
|
m_contextPanel->selectObject(p->GetContent());
|
||||||
|
m_firstPane->setObject(p);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
connect(viewport, &uLib::Vtk::QViewport::puppetSelected, syncSelection);
|
||||||
|
|
||||||
|
uLib::Object::connect(m_mainVtkContext, &uLib::Vtk::vtkObjectsContext::PuppetAdded, [this](uLib::Vtk::Puppet* p) {
|
||||||
|
if (p) {
|
||||||
|
auto panes = this->findChildren<ViewportPane*>();
|
||||||
|
for (auto* pane : panes) {
|
||||||
|
if (auto* vp = qobject_cast<uLib::Vtk::QViewport*>(pane->currentViewport())) {
|
||||||
|
vp->AddPuppet(*p);
|
||||||
|
vp->ZoomAuto();
|
||||||
|
vp->Render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
uLib::Object::connect(m_mainVtkContext, &uLib::Vtk::vtkObjectsContext::PuppetRemoved, [this](uLib::Vtk::Puppet* p) {
|
||||||
|
if (p) {
|
||||||
|
auto panes = this->findChildren<ViewportPane*>();
|
||||||
|
for (auto* pane : panes) {
|
||||||
|
if (auto* vp = qobject_cast<uLib::Vtk::QViewport*>(pane->currentViewport())) {
|
||||||
|
vp->RemovePuppet(*p);
|
||||||
|
vp->Render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add any puppets that were created during m_mainVtkContext's construction to all panes
|
||||||
|
auto panes = this->findChildren<ViewportPane*>();
|
||||||
|
for (auto* obj : context->GetObjects()) {
|
||||||
|
if (auto* p = m_mainVtkContext->GetPuppet(obj)) {
|
||||||
|
for (auto* pane : panes) {
|
||||||
|
if (auto* vp = qobject_cast<uLib::Vtk::QViewport*>(pane->currentViewport())) {
|
||||||
|
vp->AddPuppet(*p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uLib::Object::connect(context, &uLib::Object::Updated, [viewport]() {
|
||||||
|
viewport->Render();
|
||||||
|
});
|
||||||
|
viewport->ZoomAuto();
|
||||||
|
viewport->Render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainPanel::onCreateObject(const std::string& className) {
|
||||||
|
if (!m_context) return;
|
||||||
|
auto* obj = uLib::ObjectFactory::Instance().Create(className);
|
||||||
|
if (obj) {
|
||||||
|
m_context->AddObject(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainPanel::onOpen() {
|
||||||
|
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() {
|
||||||
|
// Placeholder for save logic
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainPanel::onSaveAs() {
|
||||||
|
// Placeholder for save as logic
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainPanel::onExit() {
|
||||||
|
qApp->quit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainPanel::onDarkTheme() {
|
||||||
|
StyleManager::applyStyle(qApp, "dark");
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainPanel::onBrightTheme() {
|
||||||
|
StyleManager::applyStyle(qApp, "bright");
|
||||||
|
}
|
||||||
|
|
||||||
|
MainPanel::~MainPanel() {}
|
||||||
46
app/gcompose/src/MainPanel.h
Normal file
46
app/gcompose/src/MainPanel.h
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
#ifndef MAINPANEL_H
|
||||||
|
#define MAINPANEL_H
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
class QSplitter;
|
||||||
|
class ViewportPane;
|
||||||
|
class ContextPanel;
|
||||||
|
class PropertiesPanel;
|
||||||
|
|
||||||
|
namespace uLib {
|
||||||
|
class ObjectsContext;
|
||||||
|
namespace Vtk {
|
||||||
|
class vtkObjectsContext;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MainPanel : public QWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit MainPanel(QWidget* parent = nullptr);
|
||||||
|
virtual ~MainPanel();
|
||||||
|
|
||||||
|
void setContext(uLib::ObjectsContext* context);
|
||||||
|
ViewportPane* getFirstPane() const { return m_firstPane; }
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onOpen();
|
||||||
|
void onSave();
|
||||||
|
void onSaveAs();
|
||||||
|
void onExit();
|
||||||
|
|
||||||
|
void onDarkTheme();
|
||||||
|
void onBrightTheme();
|
||||||
|
|
||||||
|
void onCreateObject(const std::string& className);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QSplitter* m_rootSplitter;
|
||||||
|
ViewportPane* m_firstPane;
|
||||||
|
ContextPanel* m_contextPanel;
|
||||||
|
uLib::ObjectsContext* m_context;
|
||||||
|
uLib::Vtk::vtkObjectsContext* m_mainVtkContext;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // MAINPANEL_H
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
#include "MainWindow.h"
|
#include "MainWindow.h"
|
||||||
#include <Vtk/vtkQViewport.h>
|
#include <QSplitter>
|
||||||
|
#include "MainPanel.h"
|
||||||
|
#include "Core/ObjectsContext.h"
|
||||||
|
|
||||||
using namespace uLib;
|
using namespace uLib;
|
||||||
|
|
||||||
MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) {
|
MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) {
|
||||||
m_viewport = new Vtk::QViewport(this);
|
m_panel = new MainPanel(this);
|
||||||
setCentralWidget(m_viewport);
|
setCentralWidget(m_panel);
|
||||||
|
|
||||||
setWindowTitle("gcompose - Qt VTK Interface");
|
setWindowTitle("gcompose - Qt VTK Interface");
|
||||||
resize(1200, 800);
|
resize(1200, 800);
|
||||||
@@ -13,3 +15,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) {
|
|||||||
|
|
||||||
MainWindow::~MainWindow() {
|
MainWindow::~MainWindow() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::setContext(uLib::ObjectsContext* context) {
|
||||||
|
m_panel->setContext(context);
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,10 +4,13 @@
|
|||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
#include <QVTKOpenGLNativeWidget.h>
|
#include <QVTKOpenGLNativeWidget.h>
|
||||||
|
|
||||||
|
class MainPanel;
|
||||||
|
class ViewportPane;
|
||||||
|
|
||||||
namespace uLib {
|
namespace uLib {
|
||||||
namespace Vtk {
|
namespace Vtk {
|
||||||
class QViewport;
|
|
||||||
}
|
}
|
||||||
|
class ObjectsContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
class MainWindow : public QMainWindow {
|
class MainWindow : public QMainWindow {
|
||||||
@@ -16,10 +19,11 @@ public:
|
|||||||
MainWindow(QWidget* parent = nullptr);
|
MainWindow(QWidget* parent = nullptr);
|
||||||
virtual ~MainWindow();
|
virtual ~MainWindow();
|
||||||
|
|
||||||
uLib::Vtk::QViewport* getViewport() { return m_viewport; }
|
void setContext(uLib::ObjectsContext* context);
|
||||||
|
MainPanel* getPanel() { return m_panel; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uLib::Vtk::QViewport* m_viewport;
|
MainPanel* m_panel;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
46
app/gcompose/src/PropertiesPanel.cpp
Normal file
46
app/gcompose/src/PropertiesPanel.cpp
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
#include "PropertiesPanel.h"
|
||||||
|
#include "PropertyWidgets.h"
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QLabel>
|
||||||
|
#include "Core/Object.h"
|
||||||
|
|
||||||
|
PropertiesPanel::PropertiesPanel(QWidget* parent) : QWidget(parent) {
|
||||||
|
this->setObjectName("PropertiesPanel");
|
||||||
|
this->setAttribute(Qt::WA_StyledBackground);
|
||||||
|
m_layout = new QVBoxLayout(this);
|
||||||
|
m_layout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
m_layout->setSpacing(0);
|
||||||
|
|
||||||
|
// Title bar
|
||||||
|
m_titleBar = new QWidget(this);
|
||||||
|
m_titleBar->setObjectName("PaneTitleBar");
|
||||||
|
m_titleBar->setFixedHeight(22);
|
||||||
|
|
||||||
|
auto* titleLayout = new QHBoxLayout(m_titleBar);
|
||||||
|
titleLayout->setContentsMargins(5, 0, 5, 0);
|
||||||
|
|
||||||
|
m_titleLabel = new QLabel("Properties", m_titleBar);
|
||||||
|
m_titleLabel->setObjectName("TitleLabel");
|
||||||
|
titleLayout->addWidget(m_titleLabel);
|
||||||
|
titleLayout->addStretch();
|
||||||
|
|
||||||
|
m_layout->addWidget(m_titleBar);
|
||||||
|
|
||||||
|
// Editor
|
||||||
|
m_editor = new uLib::Qt::PropertyEditor(this);
|
||||||
|
m_layout->addWidget(m_editor, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PropertiesPanel::setObject(uLib::Object* obj) {
|
||||||
|
if (obj) {
|
||||||
|
m_titleLabel->setText(QString("Properties: %1 (%2)")
|
||||||
|
.arg(QString::fromStdString(obj->GetInstanceName()))
|
||||||
|
.arg(obj->GetClassName()));
|
||||||
|
} else {
|
||||||
|
m_titleLabel->setText("Properties: (No selection)");
|
||||||
|
}
|
||||||
|
m_editor->setObject(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
PropertiesPanel::~PropertiesPanel() {}
|
||||||
35
app/gcompose/src/PropertiesPanel.h
Normal file
35
app/gcompose/src/PropertiesPanel.h
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#ifndef PROPERTIES_PANEL_H
|
||||||
|
#define PROPERTIES_PANEL_H
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
namespace uLib {
|
||||||
|
class Object;
|
||||||
|
namespace Qt { class PropertyEditor; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class QVBoxLayout;
|
||||||
|
class QLabel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class PropertiesPanel
|
||||||
|
* @brief A panel dedicated to inspecting and editing properties of a selected uLib::Object.
|
||||||
|
*/
|
||||||
|
class PropertiesPanel : public QWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit PropertiesPanel(QWidget* parent = nullptr);
|
||||||
|
virtual ~PropertiesPanel();
|
||||||
|
|
||||||
|
/** @brief Sets the object to be inspected. */
|
||||||
|
void setObject(uLib::Object* obj);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QVBoxLayout* m_layout;
|
||||||
|
QWidget* m_titleBar;
|
||||||
|
QLabel* m_titleLabel;
|
||||||
|
|
||||||
|
uLib::Qt::PropertyEditor* m_editor;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // PROPERTIES_PANEL_H
|
||||||
401
app/gcompose/src/PropertyWidgets.cpp
Normal file
401
app/gcompose/src/PropertyWidgets.cpp
Normal file
@@ -0,0 +1,401 @@
|
|||||||
|
#include "PropertyWidgets.h"
|
||||||
|
#include <QSignalBlocker>
|
||||||
|
#include <QRegularExpression>
|
||||||
|
#include <QRegularExpressionMatch>
|
||||||
|
#include <QComboBox>
|
||||||
|
#include <QCheckBox>
|
||||||
|
#include "Vtk/uLibVtkInterface.h"
|
||||||
|
#include "Math/Units.h"
|
||||||
|
#include "Math/Dense.h"
|
||||||
|
#include "Settings.h"
|
||||||
|
|
||||||
|
namespace uLib {
|
||||||
|
namespace Qt {
|
||||||
|
|
||||||
|
PropertyWidgetBase::PropertyWidgetBase(PropertyBase* prop, QWidget* parent)
|
||||||
|
: QWidget(parent), m_BaseProperty(prop) {
|
||||||
|
m_Layout = new QHBoxLayout(this);
|
||||||
|
m_Layout->setContentsMargins(4, 2, 4, 2);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
PropertyWidgetBase::~PropertyWidgetBase() {
|
||||||
|
m_Connection.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper for unit parsing
|
||||||
|
double parseWithUnits(const QString& text, double* factorOut, QString* suffixOut) {
|
||||||
|
static QRegularExpression re("^\\s*([-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?)\\s*(_?[a-zA-Z]+)?\\s*$");
|
||||||
|
QRegularExpressionMatch match = re.match(text);
|
||||||
|
if (!match.hasMatch()) return 0.0;
|
||||||
|
|
||||||
|
double num = match.captured(1).toDouble();
|
||||||
|
QString unit = match.captured(3);
|
||||||
|
double factor = 1.0;
|
||||||
|
|
||||||
|
if (!unit.isEmpty()) {
|
||||||
|
QString u = unit.startsWith('_') ? unit.mid(1) : unit;
|
||||||
|
if (u == "m") factor = CLHEP::meter;
|
||||||
|
else if (u == "cm") factor = CLHEP::centimeter;
|
||||||
|
else if (u == "mm") factor = CLHEP::millimeter;
|
||||||
|
else if (u == "um") factor = CLHEP::micrometer;
|
||||||
|
else if (u == "nm") factor = CLHEP::nanometer;
|
||||||
|
else if (u == "km") factor = CLHEP::kilometer;
|
||||||
|
else if (u == "deg") factor = CLHEP::degree;
|
||||||
|
else if (u == "rad") factor = CLHEP::radian;
|
||||||
|
else if (u == "ns") factor = CLHEP::nanosecond;
|
||||||
|
else if (u == "s") factor = CLHEP::second;
|
||||||
|
else if (u == "ms") factor = CLHEP::millisecond;
|
||||||
|
else if (u == "MeV") factor = CLHEP::megaelectronvolt;
|
||||||
|
else if (u == "eV") factor = CLHEP::electronvolt;
|
||||||
|
else if (u == "keV") factor = CLHEP::kiloelectronvolt;
|
||||||
|
else if (u == "GeV") factor = CLHEP::gigaelectronvolt;
|
||||||
|
else if (u == "TeV") factor = CLHEP::teraelectronvolt;
|
||||||
|
if (suffixOut) *suffixOut = u;
|
||||||
|
} else if (suffixOut) {
|
||||||
|
// Reuse previous suffix if none provided, or empty
|
||||||
|
}
|
||||||
|
|
||||||
|
if (factorOut) *factorOut = factor;
|
||||||
|
return num * factor;
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnitLineEdit implementation
|
||||||
|
UnitLineEdit::UnitLineEdit(QWidget* parent) : QLineEdit(parent), m_Value(0), m_Factor(1.0), m_Suffix(""), m_IsInteger(false) {
|
||||||
|
connect(this, &QLineEdit::editingFinished, this, &UnitLineEdit::onEditingFinished);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UnitLineEdit::setUnits(const QString& suffix, double factor) {
|
||||||
|
m_Suffix = suffix;
|
||||||
|
m_Factor = factor;
|
||||||
|
updateText();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UnitLineEdit::setValue(double val) {
|
||||||
|
if (m_Value != val) {
|
||||||
|
m_Value = val;
|
||||||
|
// Suffix heuristic ONLY if it was mm and no explicit unit was given?
|
||||||
|
// Actually, if m_Suffix is empty or we have a specific one, we should respect it.
|
||||||
|
// The original code had a heuristic, but it's better to let property decide.
|
||||||
|
// Let's keep it ONLY if m_Suffix was mm (legacy behavior)
|
||||||
|
if (!m_IsInteger && m_Suffix == "mm" && std::abs(val) >= 1000.0) { m_Suffix = "m"; m_Factor = CLHEP::meter; }
|
||||||
|
updateText();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UnitLineEdit::onEditingFinished() {
|
||||||
|
double factor = m_Factor;
|
||||||
|
QString suffix = m_Suffix;
|
||||||
|
double parsedVal = parseWithUnits(text(), &factor, &suffix);
|
||||||
|
if (!suffix.isEmpty()) {
|
||||||
|
m_Suffix = suffix;
|
||||||
|
m_Factor = factor;
|
||||||
|
}
|
||||||
|
if (m_IsInteger) {
|
||||||
|
parsedVal = std::round(parsedVal);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_Value != parsedVal) {
|
||||||
|
m_Value = parsedVal;
|
||||||
|
emit valueManualChanged(m_Value);
|
||||||
|
}
|
||||||
|
updateText();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UnitLineEdit::updateText() {
|
||||||
|
QSignalBlocker blocker(this);
|
||||||
|
QString s;
|
||||||
|
if (m_IsInteger) {
|
||||||
|
s = QString::number((int)m_Value);
|
||||||
|
if (s.isEmpty()) s = "0";
|
||||||
|
} else {
|
||||||
|
double displayVal = m_Value / m_Factor;
|
||||||
|
s = QString::number(displayVal, 'g', 6);
|
||||||
|
if (!s.contains('.') && !s.contains('e')) {
|
||||||
|
s += ".0";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setText(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UnitLineEdit::setIntegerOnly(bool integerOnly) {
|
||||||
|
m_IsInteger = integerOnly;
|
||||||
|
updateText();
|
||||||
|
}
|
||||||
|
|
||||||
|
DoublePropertyWidget::DoublePropertyWidget(Property<double>* prop, QWidget* parent)
|
||||||
|
: PropertyWidgetBase(prop, parent), m_Prop(prop) {
|
||||||
|
m_Edit = new UnitLineEdit(this);
|
||||||
|
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_Layout->addWidget(m_Edit, 1);
|
||||||
|
connect(m_Edit, &UnitLineEdit::valueManualChanged, [this](double val){ m_Prop->Set(val); });
|
||||||
|
m_Connection = uLib::Object::connect(m_Prop, &Property<double>::PropertyChanged, [this](){
|
||||||
|
m_Edit->setValue(m_Prop->Get());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
FloatPropertyWidget::FloatPropertyWidget(Property<float>* prop, QWidget* parent)
|
||||||
|
: PropertyWidgetBase(prop, parent), m_Prop(prop) {
|
||||||
|
m_Edit = new UnitLineEdit(this);
|
||||||
|
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_Layout->addWidget(m_Edit, 1);
|
||||||
|
connect(m_Edit, &UnitLineEdit::valueManualChanged, [this](double val){ m_Prop->Set((float)val); });
|
||||||
|
m_Connection = uLib::Object::connect(m_Prop, &Property<float>::PropertyChanged, [this](){
|
||||||
|
m_Edit->setValue((double)m_Prop->Get());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
IntPropertyWidget::IntPropertyWidget(Property<int>* prop, QWidget* parent)
|
||||||
|
: PropertyWidgetBase(prop, parent), m_Prop(prop) {
|
||||||
|
m_Edit = new UnitLineEdit(this);
|
||||||
|
m_Edit->setIntegerOnly(true);
|
||||||
|
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_Layout->addWidget(m_Edit, 1);
|
||||||
|
connect(m_Edit, &UnitLineEdit::valueManualChanged, [this](double val){ m_Prop->Set((int)val); });
|
||||||
|
m_Connection = uLib::Object::connect(m_Prop, &Property<int>::PropertyChanged, [this](){
|
||||||
|
m_Edit->setValue((double)m_Prop->Get());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
BoolPropertyWidget::BoolPropertyWidget(Property<bool>* prop, QWidget* parent)
|
||||||
|
: PropertyWidgetBase(prop, parent), m_Prop(prop) {
|
||||||
|
m_CheckBox = new QCheckBox(this);
|
||||||
|
m_CheckBox->setChecked(prop->Get());
|
||||||
|
m_Layout->addWidget(m_CheckBox, 1);
|
||||||
|
connect(m_CheckBox, &QCheckBox::toggled, [this](bool val){ if (m_Prop->Get() != val) m_Prop->Set(val); });
|
||||||
|
m_Connection = uLib::Object::connect(m_Prop, &Property<bool>::PropertyChanged, [this](){
|
||||||
|
if (m_CheckBox->isChecked() != m_Prop->Get()) {
|
||||||
|
QSignalBlocker blocker(m_CheckBox);
|
||||||
|
m_CheckBox->setChecked(m_Prop->Get());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
BoolPropertyWidget::~BoolPropertyWidget() {}
|
||||||
|
|
||||||
|
StringPropertyWidget::StringPropertyWidget(Property<std::string>* prop, QWidget* parent)
|
||||||
|
: PropertyWidgetBase(prop, parent), m_Prop(prop) {
|
||||||
|
m_LineEdit = new QLineEdit(this);
|
||||||
|
m_LineEdit->setText(QString::fromStdString(prop->Get()));
|
||||||
|
m_Layout->addWidget(m_LineEdit, 1);
|
||||||
|
connect(m_LineEdit, &QLineEdit::editingFinished, [this](){
|
||||||
|
std::string val = m_LineEdit->text().toStdString();
|
||||||
|
if (m_Prop->Get() != val) m_Prop->Set(val);
|
||||||
|
});
|
||||||
|
m_Connection = uLib::Object::connect(m_Prop, &Property<std::string>::PropertyChanged, [this](){
|
||||||
|
if (m_LineEdit->text().toStdString() != m_Prop->Get()) {
|
||||||
|
QSignalBlocker blocker(m_LineEdit);
|
||||||
|
m_LineEdit->setText(QString::fromStdString(m_Prop->Get()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
m_MainLayout = new QVBoxLayout(this);
|
||||||
|
m_MainLayout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
m_ScrollArea = new QScrollArea(this);
|
||||||
|
m_ScrollArea->setWidgetResizable(true);
|
||||||
|
m_MainLayout->addWidget(m_ScrollArea);
|
||||||
|
m_Container = new QWidget();
|
||||||
|
m_ContainerLayout = new QVBoxLayout(m_Container);
|
||||||
|
m_ContainerLayout->setAlignment(::Qt::AlignTop);
|
||||||
|
m_ScrollArea->setWidget(m_Container);
|
||||||
|
|
||||||
|
registerFactory<double>([](PropertyBase* p, QWidget* parent){
|
||||||
|
return new DoublePropertyWidget(static_cast<Property<double>*>(p), parent);
|
||||||
|
});
|
||||||
|
registerFactory<float>([](PropertyBase* p, QWidget* parent){
|
||||||
|
return new FloatPropertyWidget(static_cast<Property<float>*>(p), parent);
|
||||||
|
});
|
||||||
|
registerFactory<int>([](PropertyBase* p, QWidget* parent){
|
||||||
|
return new IntPropertyWidget(static_cast<Property<int>*>(p), parent);
|
||||||
|
});
|
||||||
|
registerFactory<bool>([](PropertyBase* p, QWidget* parent){
|
||||||
|
return new BoolPropertyWidget(static_cast<Property<bool>*>(p), parent);
|
||||||
|
});
|
||||||
|
registerFactory<std::string>([](PropertyBase* p, QWidget* parent){
|
||||||
|
return new StringPropertyWidget(static_cast<Property<std::string>*>(p), parent);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Register EnumProperty specifically (needs to check type since it holds Property<int> but is EnumProperty)
|
||||||
|
m_Factories[std::type_index(typeid(EnumProperty))] = [](PropertyBase* p, QWidget* parent) {
|
||||||
|
return new EnumPropertyWidget(p, parent);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Vector Registration
|
||||||
|
registerFactory<Vector2i>([](PropertyBase* p, QWidget* parent){ return new VectorPropertyWidget<Vector2i, 2>(static_cast<Property<Vector2i>*>(p), parent); });
|
||||||
|
registerFactory<Vector2f>([](PropertyBase* p, QWidget* parent){ return new VectorPropertyWidget<Vector2f, 2>(static_cast<Property<Vector2f>*>(p), parent); });
|
||||||
|
registerFactory<Vector2d>([](PropertyBase* p, QWidget* parent){ return new VectorPropertyWidget<Vector2d, 2>(static_cast<Property<Vector2d>*>(p), parent); });
|
||||||
|
registerFactory<Vector3i>([](PropertyBase* p, QWidget* parent){ return new VectorPropertyWidget<Vector3i, 3>(static_cast<Property<Vector3i>*>(p), parent); });
|
||||||
|
registerFactory<Vector3f>([](PropertyBase* p, QWidget* parent){ return new VectorPropertyWidget<Vector3f, 3>(static_cast<Property<Vector3f>*>(p), parent); });
|
||||||
|
registerFactory<Vector3d>([](PropertyBase* p, QWidget* parent){ return new VectorPropertyWidget<Vector3d, 3>(static_cast<Property<Vector3d>*>(p), parent); });
|
||||||
|
registerFactory<Vector4i>([](PropertyBase* p, QWidget* parent){ return new VectorPropertyWidget<Vector4i, 4>(static_cast<Property<Vector4i>*>(p), parent); });
|
||||||
|
registerFactory<Vector4f>([](PropertyBase* p, QWidget* parent){ return new VectorPropertyWidget<Vector4f, 4>(static_cast<Property<Vector4f>*>(p), parent); });
|
||||||
|
registerFactory<Vector4d>([](PropertyBase* p, QWidget* parent){ return new VectorPropertyWidget<Vector4d, 4>(static_cast<Property<Vector4d>*>(p), parent); });
|
||||||
|
}
|
||||||
|
|
||||||
|
PropertyEditor::~PropertyEditor() {}
|
||||||
|
|
||||||
|
void PropertyEditor::setObject(::uLib::Object* obj, bool displayOnly) {
|
||||||
|
m_Object = obj;
|
||||||
|
clear();
|
||||||
|
if (!obj) return;
|
||||||
|
|
||||||
|
// Choose which properties to show
|
||||||
|
const std::vector<::uLib::PropertyBase*>* props = &obj->GetProperties();
|
||||||
|
|
||||||
|
if (displayOnly) {
|
||||||
|
if (auto* puppet = dynamic_cast<::uLib::Vtk::Puppet*>(obj)) {
|
||||||
|
props = &puppet->GetDisplayProperties();
|
||||||
|
} else {
|
||||||
|
// If it's not a puppet but displayOnly is requested, showing nothing or fallback?
|
||||||
|
// Fallback: core properties.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group properties by their group string
|
||||||
|
std::map<std::string, std::vector<::uLib::PropertyBase*>> groupedProps;
|
||||||
|
std::vector<std::string> groupOrder;
|
||||||
|
|
||||||
|
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());
|
||||||
|
if (it != m_Factories.end()) {
|
||||||
|
widget = it->second(prop, m_Container);
|
||||||
|
} else {
|
||||||
|
// Debug info for unknown types
|
||||||
|
std::cout << "PropertyEditor: No factory for " << prop->GetQualifiedName()
|
||||||
|
<< " (Type: " << prop->GetTypeName() << ")" << std::endl;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PropertyEditor::clear() {
|
||||||
|
QLayoutItem* item;
|
||||||
|
while ((item = m_ContainerLayout->takeAt(0)) != nullptr) {
|
||||||
|
delete item->widget();
|
||||||
|
delete item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Qt
|
||||||
|
} // namespace uLib
|
||||||
188
app/gcompose/src/PropertyWidgets.h
Normal file
188
app/gcompose/src/PropertyWidgets.h
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
#ifndef PROPERTY_WIDGETS_H
|
||||||
|
#define PROPERTY_WIDGETS_H
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <QLineEdit>
|
||||||
|
#include <QCheckBox>
|
||||||
|
#include <QScrollArea>
|
||||||
|
#include <map>
|
||||||
|
#include <typeindex>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
#include "Core/Property.h"
|
||||||
|
#include "Core/Object.h"
|
||||||
|
#include "Core/Signal.h"
|
||||||
|
#include "Math/Dense.h"
|
||||||
|
#include "Settings.h"
|
||||||
|
|
||||||
|
namespace uLib {
|
||||||
|
namespace Qt {
|
||||||
|
|
||||||
|
double parseWithUnits(const QString& text, double* factorOut = nullptr, QString* suffixOut = nullptr);
|
||||||
|
|
||||||
|
class PropertyWidgetBase : public QWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
PropertyWidgetBase(PropertyBase* prop, QWidget* parent = nullptr);
|
||||||
|
virtual ~PropertyWidgetBase();
|
||||||
|
PropertyBase* getProperty() const { return m_BaseProperty; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
PropertyBase* m_BaseProperty;
|
||||||
|
QHBoxLayout* m_Layout;
|
||||||
|
QLabel* m_Label;
|
||||||
|
// Stores the uLib signal connection so it can be disconnected on destruction,
|
||||||
|
// preventing use-after-free when PropertyEditor::clear() deletes widgets.
|
||||||
|
Connection m_Connection;
|
||||||
|
};
|
||||||
|
|
||||||
|
class UnitLineEdit : public QLineEdit {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
UnitLineEdit(QWidget* parent = nullptr);
|
||||||
|
void setValue(double val);
|
||||||
|
void setUnits(const QString& suffix, double factor = 1.0);
|
||||||
|
double getValue() const { return m_Value; }
|
||||||
|
void setIntegerOnly(bool b);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void valueManualChanged(double val);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onEditingFinished();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void updateText();
|
||||||
|
double m_Value;
|
||||||
|
double m_Factor;
|
||||||
|
QString m_Suffix;
|
||||||
|
bool m_IsInteger;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DoublePropertyWidget : public PropertyWidgetBase {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
DoublePropertyWidget(Property<double>* prop, QWidget* parent = nullptr);
|
||||||
|
private:
|
||||||
|
Property<double>* m_Prop;
|
||||||
|
UnitLineEdit* m_Edit;
|
||||||
|
};
|
||||||
|
|
||||||
|
class FloatPropertyWidget : public PropertyWidgetBase {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
FloatPropertyWidget(Property<float>* prop, QWidget* parent = nullptr);
|
||||||
|
private:
|
||||||
|
Property<float>* m_Prop;
|
||||||
|
UnitLineEdit* m_Edit;
|
||||||
|
};
|
||||||
|
|
||||||
|
class IntPropertyWidget : public PropertyWidgetBase {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
IntPropertyWidget(Property<int>* prop, QWidget* parent = nullptr);
|
||||||
|
private:
|
||||||
|
Property<int>* m_Prop;
|
||||||
|
UnitLineEdit* m_Edit;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename VecT, int Size>
|
||||||
|
class VectorPropertyWidget : public PropertyWidgetBase {
|
||||||
|
public:
|
||||||
|
VectorPropertyWidget(Property<VecT>* prop, QWidget* parent = nullptr)
|
||||||
|
: 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) {
|
||||||
|
m_Edits[i] = new UnitLineEdit(this);
|
||||||
|
if (std::is_integral<typename VecT::Scalar>::value) {
|
||||||
|
m_Edits[i]->setIntegerOnly(true);
|
||||||
|
}
|
||||||
|
if (!prefSuffix.isEmpty()) {
|
||||||
|
m_Edits[i]->setUnits(prefSuffix, factor);
|
||||||
|
}
|
||||||
|
m_Layout->addWidget(m_Edits[i], 1);
|
||||||
|
|
||||||
|
connect(m_Edits[i], &UnitLineEdit::valueManualChanged, [this, i](double val){
|
||||||
|
VecT v = m_Prop->Get();
|
||||||
|
v(i) = (typename VecT::Scalar)val;
|
||||||
|
if (m_Prop->Get() != v) m_Prop->Set(v);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
updateEdits();
|
||||||
|
m_Connection = uLib::Object::connect(m_Prop, &Property<VecT>::PropertyChanged, [this](){
|
||||||
|
updateEdits();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
~VectorPropertyWidget() { m_Connection.disconnect(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void updateEdits() {
|
||||||
|
VecT v = m_Prop->Get();
|
||||||
|
for (int i = 0; i < Size; ++i) {
|
||||||
|
if (!m_Edits[i]->hasFocus()) {
|
||||||
|
m_Edits[i]->setValue((double)v(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Property<VecT>* m_Prop;
|
||||||
|
UnitLineEdit* m_Edits[Size];
|
||||||
|
};
|
||||||
|
|
||||||
|
class BoolPropertyWidget : public PropertyWidgetBase {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
BoolPropertyWidget(Property<bool>* prop, QWidget* parent = nullptr);
|
||||||
|
virtual ~BoolPropertyWidget();
|
||||||
|
private:
|
||||||
|
Property<bool>* m_Prop;
|
||||||
|
QCheckBox* m_CheckBox;
|
||||||
|
};
|
||||||
|
|
||||||
|
class StringPropertyWidget : public PropertyWidgetBase {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
StringPropertyWidget(Property<std::string>* prop, QWidget* parent = nullptr);
|
||||||
|
virtual ~StringPropertyWidget();
|
||||||
|
private:
|
||||||
|
Property<std::string>* m_Prop;
|
||||||
|
QLineEdit* m_LineEdit;
|
||||||
|
};
|
||||||
|
|
||||||
|
class PropertyEditor : public QWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
PropertyEditor(QWidget* parent = nullptr);
|
||||||
|
virtual ~PropertyEditor();
|
||||||
|
void setObject(uLib::Object* obj, bool displayOnly = false);
|
||||||
|
template<typename T>
|
||||||
|
void registerFactory(std::function<QWidget*(PropertyBase*, QWidget*)> factory) {
|
||||||
|
m_Factories[std::type_index(typeid(T))] = factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void clear();
|
||||||
|
uLib::Object* m_Object;
|
||||||
|
QVBoxLayout* m_MainLayout;
|
||||||
|
QScrollArea* m_ScrollArea;
|
||||||
|
QWidget* m_Container;
|
||||||
|
QVBoxLayout* m_ContainerLayout;
|
||||||
|
std::map<std::type_index, std::function<QWidget*(PropertyBase*, QWidget*)>> m_Factories;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Qt
|
||||||
|
} // namespace uLib
|
||||||
|
|
||||||
|
#endif // PROPERTY_WIDGETS_H
|
||||||
231
app/gcompose/src/QViewportPane.cpp
Normal file
231
app/gcompose/src/QViewportPane.cpp
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
#include "QViewportPane.h"
|
||||||
|
#include <Vtk/vtkQViewport.h>
|
||||||
|
#include <Root/QCanvas.h>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QToolButton>
|
||||||
|
#include <QMenu>
|
||||||
|
#include <QAction>
|
||||||
|
#include <QSplitter>
|
||||||
|
#include <vtkCamera.h>
|
||||||
|
#include "PropertyWidgets.h"
|
||||||
|
#include <QSignalBlocker>
|
||||||
|
|
||||||
|
QViewportPane::QViewportPane(QWidget* parent) : QWidget(parent), m_viewport(nullptr) {
|
||||||
|
m_layout = new QVBoxLayout(this);
|
||||||
|
m_layout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
m_layout->setSpacing(0);
|
||||||
|
|
||||||
|
// Title bar setup
|
||||||
|
m_titleBar = new QWidget(this);
|
||||||
|
m_titleBar->setObjectName("PaneTitleBar");
|
||||||
|
m_titleBar->setFixedHeight(22);
|
||||||
|
|
||||||
|
auto* titleLayout = new QHBoxLayout(m_titleBar);
|
||||||
|
titleLayout->setContentsMargins(5, 0, 0, 0);
|
||||||
|
|
||||||
|
m_titleLabel = new QLabel("Viewport", m_titleBar);
|
||||||
|
m_titleLabel->setObjectName("TitleLabel");
|
||||||
|
|
||||||
|
m_toggleBtn = new QPushButton("Display", m_titleBar);
|
||||||
|
m_toggleBtn->setCheckable(true);
|
||||||
|
m_toggleBtn->setFixedSize(60, 18);
|
||||||
|
m_toggleBtn->setObjectName("DisplayToggleBtn");
|
||||||
|
|
||||||
|
auto* closeBtn = new QToolButton(m_titleBar);
|
||||||
|
closeBtn->setObjectName("PaneCloseButton");
|
||||||
|
closeBtn->setText("X");
|
||||||
|
closeBtn->setFixedSize(18, 18);
|
||||||
|
|
||||||
|
titleLayout->addWidget(m_titleLabel);
|
||||||
|
titleLayout->addStretch();
|
||||||
|
titleLayout->addWidget(m_toggleBtn);
|
||||||
|
titleLayout->addSpacing(5);
|
||||||
|
titleLayout->addWidget(closeBtn);
|
||||||
|
|
||||||
|
m_layout->addWidget(m_titleBar);
|
||||||
|
|
||||||
|
// Main horizontal container for viewport and display panel
|
||||||
|
QWidget* mainArea = new QWidget(this);
|
||||||
|
QHBoxLayout* hLayout = new QHBoxLayout(mainArea);
|
||||||
|
hLayout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
hLayout->setSpacing(0);
|
||||||
|
m_layout->addWidget(mainArea);
|
||||||
|
|
||||||
|
// Viewport will be added here via setViewport
|
||||||
|
m_viewport = new uLib::Vtk::QViewport(mainArea);
|
||||||
|
hLayout->addWidget(m_viewport);
|
||||||
|
|
||||||
|
// Display Panel (Overlay/Slide-out)
|
||||||
|
m_displayPanel = new QFrame(mainArea);
|
||||||
|
m_displayPanel->setObjectName("DisplayPropertiesPanel");
|
||||||
|
m_displayPanel->setFixedWidth(250);
|
||||||
|
m_displayPanel->hide();
|
||||||
|
|
||||||
|
QVBoxLayout* panelLayout = new QVBoxLayout(m_displayPanel);
|
||||||
|
panelLayout->setContentsMargins(5, 5, 5, 5);
|
||||||
|
|
||||||
|
QLabel* panelHeader = new QLabel("Display Properties", m_displayPanel);
|
||||||
|
panelHeader->setStyleSheet("font-weight: bold; padding: 5px;");
|
||||||
|
panelLayout->addWidget(panelHeader);
|
||||||
|
|
||||||
|
m_displayEditor = new uLib::Qt::PropertyEditor(m_displayPanel);
|
||||||
|
panelLayout->addWidget(m_displayEditor);
|
||||||
|
|
||||||
|
hLayout->addWidget(m_displayPanel);
|
||||||
|
|
||||||
|
connect(m_toggleBtn, &QPushButton::toggled, this, &QViewportPane::toggleDisplayPanel);
|
||||||
|
connect(m_titleBar, &QWidget::customContextMenuRequested, this, &QViewportPane::showContextMenu);
|
||||||
|
connect(closeBtn, &QToolButton::clicked, this, &QViewportPane::onCloseRequested);
|
||||||
|
|
||||||
|
m_titleBar->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QViewportPane::toggleDisplayPanel() {
|
||||||
|
m_displayPanel->setVisible(m_toggleBtn->isChecked());
|
||||||
|
}
|
||||||
|
|
||||||
|
void QViewportPane::setObject(uLib::Object* obj) {
|
||||||
|
m_displayEditor->setObject(obj, true);
|
||||||
|
// Auto-show panel if it's a puppet and we want to highlight this feature?
|
||||||
|
// User asked for "hiding panel", so maybe we don't auto-show.
|
||||||
|
}
|
||||||
|
|
||||||
|
void QViewportPane::setViewport(QWidget* viewport, const QString& title) {
|
||||||
|
if (m_viewport) {
|
||||||
|
m_viewport->parentWidget()->layout()->removeWidget(m_viewport);
|
||||||
|
delete m_viewport;
|
||||||
|
}
|
||||||
|
m_viewport = viewport;
|
||||||
|
m_titleLabel->setText(title);
|
||||||
|
|
||||||
|
m_viewport->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||||
|
auto* mainAreaLayout = static_cast<QHBoxLayout*>(m_displayPanel->parentWidget()->layout());
|
||||||
|
mainAreaLayout->insertWidget(0, m_viewport);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
QViewportPane::~QViewportPane() {}
|
||||||
|
|
||||||
|
void QViewportPane::setViewport(QWidget* viewport, const QString& title) {
|
||||||
|
if (m_viewport) {
|
||||||
|
m_layout->removeWidget(m_viewport);
|
||||||
|
delete m_viewport;
|
||||||
|
}
|
||||||
|
m_viewport = viewport;
|
||||||
|
m_titleLabel->setText(title);
|
||||||
|
|
||||||
|
m_viewport->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||||
|
m_layout->addWidget(m_viewport);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QViewportPane::addVtkViewport() {
|
||||||
|
auto* viewport = new uLib::Vtk::QViewport(this);
|
||||||
|
setViewport(viewport, "VTK Viewport");
|
||||||
|
}
|
||||||
|
|
||||||
|
void QViewportPane::addRootCanvas() {
|
||||||
|
auto* canvas = new uLib::Root::QCanvas(this);
|
||||||
|
setViewport(canvas, "ROOT Canvas");
|
||||||
|
}
|
||||||
|
|
||||||
|
void QViewportPane::onCloseRequested() {
|
||||||
|
QSplitter* parentSplitter = qobject_cast<QSplitter*>(parentWidget());
|
||||||
|
if (parentSplitter && parentSplitter->count() > 1) {
|
||||||
|
deleteLater();
|
||||||
|
} else {
|
||||||
|
// Can't close the last viewport in the splitter safely. Re-initialize to default VTK canvas.
|
||||||
|
addVtkViewport();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void QViewportPane::showContextMenu(const QPoint& pos) {
|
||||||
|
QMenu menu(this);
|
||||||
|
QAction* hSplit = menu.addAction("H split");
|
||||||
|
QAction* vSplit = menu.addAction("V split");
|
||||||
|
menu.addSeparator();
|
||||||
|
|
||||||
|
bool isVtk = (qobject_cast<uLib::Vtk::QViewport*>(m_viewport) != nullptr);
|
||||||
|
|
||||||
|
QAction* changeType = menu.addAction(isVtk ? "Change to ROOT Canvas" : "Change to VTK Viewport");
|
||||||
|
|
||||||
|
QAction* selected = menu.exec(m_titleBar->mapToGlobal(pos));
|
||||||
|
|
||||||
|
if (selected == hSplit) {
|
||||||
|
AttemptSplit(Qt::Horizontal);
|
||||||
|
} else if (selected == vSplit) {
|
||||||
|
AttemptSplit(Qt::Vertical);
|
||||||
|
} else if (selected == changeType) {
|
||||||
|
if (isVtk) {
|
||||||
|
addRootCanvas();
|
||||||
|
} else {
|
||||||
|
addVtkViewport();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void QViewportPane::AttemptSplit(Qt::Orientation orientation) {
|
||||||
|
QWidget* p = parentWidget();
|
||||||
|
if (!p) return;
|
||||||
|
|
||||||
|
QSplitter* parentSplitter = qobject_cast<QSplitter*>(p);
|
||||||
|
if (!parentSplitter) return;
|
||||||
|
|
||||||
|
QViewportPane* newPane = new QViewportPane();
|
||||||
|
|
||||||
|
// 1. Synchronize viewport content and camera (VTK Viewport only for now)
|
||||||
|
auto* currentVtk = qobject_cast<uLib::Vtk::QViewport*>(m_viewport);
|
||||||
|
if (currentVtk) {
|
||||||
|
auto* newVtk = qobject_cast<uLib::Vtk::QViewport*>(newPane->currentViewport());
|
||||||
|
if (newVtk) {
|
||||||
|
// Copy puppets
|
||||||
|
for (auto* puppet : currentVtk->getPuppets()) {
|
||||||
|
newVtk->AddPuppet(*puppet);
|
||||||
|
}
|
||||||
|
// Copy camera
|
||||||
|
if (currentVtk->GetRenderer() && newVtk->GetRenderer()) {
|
||||||
|
vtkCamera* currentCam = currentVtk->GetRenderer()->GetActiveCamera();
|
||||||
|
vtkCamera* newCam = newVtk->GetRenderer()->GetActiveCamera();
|
||||||
|
if (currentCam && newCam) {
|
||||||
|
newCam->DeepCopy(currentCam);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Sync grid visible and axis
|
||||||
|
newVtk->SetGridVisible(currentVtk->GetGridVisible());
|
||||||
|
newVtk->SetGridAxis(currentVtk->GetGridAxis());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Adjust for ROOT Canvas if that was the active view
|
||||||
|
bool isRoot = (qobject_cast<uLib::Root::QCanvas*>(m_viewport) != nullptr);
|
||||||
|
if (isRoot) {
|
||||||
|
newPane->addRootCanvas();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parentSplitter->orientation() == orientation) {
|
||||||
|
int index = parentSplitter->indexOf(this);
|
||||||
|
QList<int> sizes = parentSplitter->sizes();
|
||||||
|
int currentSize = sizes.value(index, 0);
|
||||||
|
int half = currentSize / 2;
|
||||||
|
sizes[index] = half;
|
||||||
|
sizes.insert(index + 1, currentSize - half);
|
||||||
|
|
||||||
|
parentSplitter->insertWidget(index + 1, newPane);
|
||||||
|
parentSplitter->setSizes(sizes);
|
||||||
|
} else {
|
||||||
|
int index = parentSplitter->indexOf(this);
|
||||||
|
QList<int> parentSizes = parentSplitter->sizes();
|
||||||
|
|
||||||
|
QSplitter* newSplitter = new QSplitter(orientation);
|
||||||
|
newSplitter->addWidget(this);
|
||||||
|
newSplitter->addWidget(newPane);
|
||||||
|
|
||||||
|
QList<int> subSizes;
|
||||||
|
subSizes << 500 << 500;
|
||||||
|
newSplitter->setSizes(subSizes);
|
||||||
|
|
||||||
|
parentSplitter->insertWidget(index, newSplitter);
|
||||||
|
parentSplitter->setSizes(parentSizes);
|
||||||
|
}
|
||||||
|
}
|
||||||
50
app/gcompose/src/QViewportPane.h
Normal file
50
app/gcompose/src/QViewportPane.h
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
#ifndef QVIEWPORTPANE_H
|
||||||
|
#define QVIEWPORTPANE_H
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
#include <QFrame>
|
||||||
|
#include <QPushButton>
|
||||||
|
|
||||||
|
namespace uLib {
|
||||||
|
class Object;
|
||||||
|
namespace Qt { class PropertyEditor; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class QVBoxLayout;
|
||||||
|
class QLabel;
|
||||||
|
|
||||||
|
class QViewportPane : public QWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit QViewportPane(QWidget* parent = nullptr);
|
||||||
|
virtual ~QViewportPane();
|
||||||
|
|
||||||
|
void addVtkViewport();
|
||||||
|
void addRootCanvas();
|
||||||
|
|
||||||
|
QWidget* currentViewport() const { return m_viewport; }
|
||||||
|
|
||||||
|
/** @brief Update the display properties for the given object. */
|
||||||
|
void setObject(uLib::Object* obj);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onCloseRequested();
|
||||||
|
void showContextMenu(const QPoint& pos);
|
||||||
|
void toggleDisplayPanel();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void AttemptSplit(Qt::Orientation orientation);
|
||||||
|
void setViewport(QWidget* viewport, const QString& title);
|
||||||
|
|
||||||
|
QVBoxLayout* m_layout;
|
||||||
|
QWidget* m_titleBar;
|
||||||
|
QLabel* m_titleLabel;
|
||||||
|
QWidget* m_viewport;
|
||||||
|
|
||||||
|
// Display Properties Overlay
|
||||||
|
QFrame* m_displayPanel;
|
||||||
|
uLib::Qt::PropertyEditor* m_displayEditor;
|
||||||
|
QPushButton* m_toggleBtn;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // QVIEWPORTPANE_H
|
||||||
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
|
||||||
98
app/gcompose/src/StyleManager.cpp
Normal file
98
app/gcompose/src/StyleManager.cpp
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
#include "StyleManager.h"
|
||||||
|
#include <QApplication>
|
||||||
|
|
||||||
|
static const QString DARK_THEME = R"(
|
||||||
|
QWidget#MenuPanel { background-color: #2b2b2b; border-bottom: 1px solid #111; }
|
||||||
|
QLabel#LogoLabel { font-weight: bold; color: #0078d7; font-size: 14px; letter-spacing: 1px; }
|
||||||
|
QPushButton#MenuButton { background: transparent; color: #ccc; border: none; padding: 5px 10px; }
|
||||||
|
QPushButton#MenuButton:hover { background: #3c3c3c; color: white; border-radius: 4px; }
|
||||||
|
QWidget#PaneTitleBar { background-color: #333; color: white; border-bottom: 2px solid #222; }
|
||||||
|
QLabel#TitleLabel { font-weight: bold; margin-left: 2px; }
|
||||||
|
QToolButton#PaneCloseButton { border: none; font-weight: bold; background: transparent; color: #ccc; }
|
||||||
|
QToolButton#PaneCloseButton:hover { color: white; background: #c42b1c; }
|
||||||
|
|
||||||
|
/* Global & Panel Backgrounds */
|
||||||
|
QMainWindow, QWidget#MainPanel { background-color: #1e1e1e; }
|
||||||
|
QWidget#DisplayPropertiesPanel, QWidget#PropertiesPanel, QWidget#ContextPanel { background-color: #252526; border-left: 1px solid #3e3e42; }
|
||||||
|
QPushButton#DisplayToggleBtn { background-color: #333337; border: 1px solid #3e3e42; border-radius: 2px; color: #f1f1f1; font-size: 11px; }
|
||||||
|
QPushButton#DisplayToggleBtn:checked { background-color: #0078d7; color: white; border-color: #005a9e; font-weight: bold; }
|
||||||
|
QPushButton#DisplayToggleBtn:hover { border-color: #0078d7; }
|
||||||
|
|
||||||
|
QScrollArea { border: none; background: transparent; }
|
||||||
|
QScrollArea > QWidget > QWidget { background: transparent; }
|
||||||
|
|
||||||
|
/* Property Widgets Styling */
|
||||||
|
QLabel { color: #cccccc; }
|
||||||
|
QDoubleSpinBox, QSpinBox, QLineEdit { background: #3c3c3c; color: #f1f1f1; border: 1px solid #3e3e42; padding: 2px 4px; border-radius: 2px; selection-background-color: #0078d7; }
|
||||||
|
QDoubleSpinBox:focus, QSpinBox:focus, QLineEdit:focus { border-color: #0078d7; }
|
||||||
|
QCheckBox { color: #cccccc; spacing: 5px; }
|
||||||
|
QCheckBox::indicator { width: 14px; height: 14px; border: 1px solid #3e3e42; background: #333337; border-radius: 2px; }
|
||||||
|
QCheckBox::indicator:checked { background: #0078d7; border-color: #005a9e; }
|
||||||
|
QCheckBox::indicator:hover { border-color: #0078d7; }
|
||||||
|
|
||||||
|
QMenu { background-color: #2b2b2b; color: white; border: 1px solid #111; }
|
||||||
|
QMenu::item:selected { background-color: #3c3c3c; }
|
||||||
|
QTreeView#ContextTree { background-color: #1e1e1e; color: #ccc; border: none; }
|
||||||
|
QTreeView#ContextTree::item:hover { background-color: #2a2d2e; }
|
||||||
|
QTreeView#ContextTree::item:selected { background-color: #094771; color: white; }
|
||||||
|
QHeaderView::section { background-color: #252526; color: #ccc; border: 1px solid #323233; padding: 4px; }
|
||||||
|
|
||||||
|
/* ScrollBars */
|
||||||
|
QScrollBar:vertical { background: #1e1e1e; width: 12px; margin: 0px; }
|
||||||
|
QScrollBar::handle:vertical { background: #3e3e42; min-height: 20px; border-radius: 6px; margin: 2px; }
|
||||||
|
QScrollBar::handle:vertical:hover { background: #505050; }
|
||||||
|
QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { height: 0px; }
|
||||||
|
)";
|
||||||
|
|
||||||
|
static const QString BRIGHT_THEME = R"(
|
||||||
|
QWidget#MenuPanel { background-color: #f3f3f3; border-bottom: 1px solid #ccc; }
|
||||||
|
QLabel#LogoLabel { font-weight: bold; color: #005a9e; font-size: 14px; letter-spacing: 1px; }
|
||||||
|
QPushButton#MenuButton { background: transparent; color: #333; border: none; padding: 5px 10px; }
|
||||||
|
QPushButton#MenuButton:hover { background: #e5e5e5; color: black; border-radius: 4px; }
|
||||||
|
QWidget#PaneTitleBar { background-color: #eeeeee; color: black; border-bottom: 2px solid #ddd; }
|
||||||
|
QLabel#TitleLabel { font-weight: bold; margin-left: 2px; }
|
||||||
|
QToolButton#PaneCloseButton { border: none; font-weight: bold; background: transparent; color: #666; }
|
||||||
|
QToolButton#PaneCloseButton:hover { color: white; background: #e81123; }
|
||||||
|
|
||||||
|
/* Global & Panel Backgrounds */
|
||||||
|
QMainWindow, QWidget#MainPanel { background-color: #f3f3f3; }
|
||||||
|
QWidget#DisplayPropertiesPanel, QWidget#PropertiesPanel, QWidget#ContextPanel { background-color: #ffffff; border-left: 1px solid #cccccc; }
|
||||||
|
QPushButton#DisplayToggleBtn { background-color: #ffffff; border: 1px solid #cccccc; border-radius: 2px; color: #333; font-size: 11px; }
|
||||||
|
QPushButton#DisplayToggleBtn:checked { background-color: #0078d7; color: white; border-color: #005a9e; font-weight: bold; }
|
||||||
|
QPushButton#DisplayToggleBtn:hover { border-color: #0078d7; }
|
||||||
|
|
||||||
|
QScrollArea { border: none; background: transparent; }
|
||||||
|
QScrollArea > QWidget > QWidget { background: transparent; }
|
||||||
|
|
||||||
|
/* Property Widgets Styling */
|
||||||
|
QLabel { color: #333333; }
|
||||||
|
QDoubleSpinBox, QSpinBox, QLineEdit { background: #ffffff; color: #333333; border: 1px solid #cccccc; padding: 2px 4px; border-radius: 2px; selection-background-color: #0078d7; }
|
||||||
|
QDoubleSpinBox:focus, QSpinBox:focus, QLineEdit:focus { border-color: #0078d7; }
|
||||||
|
QCheckBox { color: #333333; spacing: 5px; }
|
||||||
|
QCheckBox::indicator { width: 14px; height: 14px; border: 1px solid #cccccc; background: #ffffff; border-radius: 2px; }
|
||||||
|
QCheckBox::indicator:checked { background: #0078d7; border-color: #005a9e; }
|
||||||
|
QCheckBox::indicator:hover { border-color: #0078d7; }
|
||||||
|
|
||||||
|
QMenu { background-color: #f3f3f3; color: black; border: 1px solid #ccc; }
|
||||||
|
QMenu::item:selected { background-color: #d0d0d0; }
|
||||||
|
QTreeView#ContextTree { background-color: #ffffff; color: #333; border: none; }
|
||||||
|
QTreeView#ContextTree::item:hover { background-color: #f2f2f2; }
|
||||||
|
QTreeView#ContextTree::item:selected { background-color: #0078d7; color: white; }
|
||||||
|
QHeaderView::section { background-color: #f3f3f3; color: #333; border: 1px solid #ccc; padding: 4px; }
|
||||||
|
|
||||||
|
/* ScrollBars */
|
||||||
|
QScrollBar:vertical { background: #ffffff; width: 12px; margin: 0px; }
|
||||||
|
QScrollBar::handle:vertical { background: #cccccc; min-height: 20px; border-radius: 6px; margin: 2px; }
|
||||||
|
QScrollBar::handle:vertical:hover { background: #aaaaaa; }
|
||||||
|
QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { height: 0px; }
|
||||||
|
)";
|
||||||
|
|
||||||
|
void StyleManager::applyStyle(QApplication* app, const QString& themeName) {
|
||||||
|
if (!app) return;
|
||||||
|
|
||||||
|
if (themeName == "bright") {
|
||||||
|
app->setStyleSheet(BRIGHT_THEME);
|
||||||
|
} else {
|
||||||
|
app->setStyleSheet(DARK_THEME); // default
|
||||||
|
}
|
||||||
|
}
|
||||||
13
app/gcompose/src/StyleManager.h
Normal file
13
app/gcompose/src/StyleManager.h
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#ifndef STYLEMANAGER_H
|
||||||
|
#define STYLEMANAGER_H
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
class QApplication;
|
||||||
|
|
||||||
|
class StyleManager {
|
||||||
|
public:
|
||||||
|
static void applyStyle(QApplication* app, const QString& themeName);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // STYLEMANAGER_H
|
||||||
187
app/gcompose/src/ViewportPane.cpp
Normal file
187
app/gcompose/src/ViewportPane.cpp
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
#include "ViewportPane.h"
|
||||||
|
#include <Vtk/vtkQViewport.h>
|
||||||
|
#include <Root/QCanvas.h>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QToolButton>
|
||||||
|
#include <QMenu>
|
||||||
|
#include <QAction>
|
||||||
|
#include <QSplitter>
|
||||||
|
#include <vtkCamera.h>
|
||||||
|
#include "PropertyWidgets.h"
|
||||||
|
#include <QSignalBlocker>
|
||||||
|
|
||||||
|
ViewportPane::ViewportPane(QWidget* parent) : QWidget(parent), m_viewport(nullptr) {
|
||||||
|
m_layout = new QVBoxLayout(this);
|
||||||
|
m_layout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
m_layout->setSpacing(0);
|
||||||
|
|
||||||
|
// Title bar setup
|
||||||
|
m_titleBar = new QWidget(this);
|
||||||
|
m_titleBar->setObjectName("PaneTitleBar");
|
||||||
|
m_titleBar->setFixedHeight(22);
|
||||||
|
|
||||||
|
auto* titleLayout = new QHBoxLayout(m_titleBar);
|
||||||
|
titleLayout->setContentsMargins(5, 0, 0, 0);
|
||||||
|
|
||||||
|
m_titleLabel = new QLabel("Viewport", m_titleBar);
|
||||||
|
m_titleLabel->setObjectName("TitleLabel");
|
||||||
|
|
||||||
|
m_toggleBtn = new QPushButton("Display", m_titleBar);
|
||||||
|
m_toggleBtn->setCheckable(true);
|
||||||
|
m_toggleBtn->setFixedSize(60, 18);
|
||||||
|
m_toggleBtn->setObjectName("DisplayToggleBtn");
|
||||||
|
|
||||||
|
auto* closeBtn = new QToolButton(m_titleBar);
|
||||||
|
closeBtn->setObjectName("PaneCloseButton");
|
||||||
|
closeBtn->setText("X");
|
||||||
|
closeBtn->setFixedSize(18, 18);
|
||||||
|
|
||||||
|
titleLayout->addWidget(m_titleLabel);
|
||||||
|
titleLayout->addStretch();
|
||||||
|
titleLayout->addWidget(m_toggleBtn);
|
||||||
|
titleLayout->addSpacing(5);
|
||||||
|
titleLayout->addWidget(closeBtn);
|
||||||
|
|
||||||
|
m_layout->addWidget(m_titleBar);
|
||||||
|
|
||||||
|
// Main area with splitter for viewport and display panel
|
||||||
|
m_areaSplitter = new QSplitter(Qt::Horizontal, this);
|
||||||
|
m_areaSplitter->setObjectName("ViewportAreaSplitter");
|
||||||
|
m_layout->addWidget(m_areaSplitter, 1);
|
||||||
|
|
||||||
|
// Viewport will be added here via setViewport
|
||||||
|
m_viewport = new uLib::Vtk::QViewport(m_areaSplitter);
|
||||||
|
m_areaSplitter->addWidget(m_viewport);
|
||||||
|
|
||||||
|
// Display Panel (Overlay/Slide-out)
|
||||||
|
m_displayPanel = new QFrame(m_areaSplitter);
|
||||||
|
m_displayPanel->setObjectName("DisplayPropertiesPanel");
|
||||||
|
m_displayPanel->setMinimumWidth(150);
|
||||||
|
m_displayPanel->hide();
|
||||||
|
|
||||||
|
m_areaSplitter->addWidget(m_displayPanel);
|
||||||
|
m_areaSplitter->setStretchFactor(0, 1);
|
||||||
|
m_areaSplitter->setStretchFactor(1, 0);
|
||||||
|
|
||||||
|
QVBoxLayout* panelLayout = new QVBoxLayout(m_displayPanel);
|
||||||
|
panelLayout->setContentsMargins(5, 5, 5, 5);
|
||||||
|
|
||||||
|
QLabel* panelHeader = new QLabel("Display Properties", m_displayPanel);
|
||||||
|
panelHeader->setStyleSheet("font-weight: bold; padding: 5px;");
|
||||||
|
panelLayout->addWidget(panelHeader);
|
||||||
|
|
||||||
|
m_displayEditor = new uLib::Qt::PropertyEditor(m_displayPanel);
|
||||||
|
panelLayout->addWidget(m_displayEditor);
|
||||||
|
|
||||||
|
connect(m_toggleBtn, &QPushButton::toggled, this, &ViewportPane::toggleDisplayPanel);
|
||||||
|
connect(m_titleBar, &QWidget::customContextMenuRequested, this, &ViewportPane::showContextMenu);
|
||||||
|
connect(closeBtn, &QToolButton::clicked, this, &ViewportPane::onCloseRequested);
|
||||||
|
|
||||||
|
m_titleBar->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
ViewportPane::~ViewportPane() {}
|
||||||
|
|
||||||
|
void ViewportPane::toggleDisplayPanel() {
|
||||||
|
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) {
|
||||||
|
m_displayEditor->setObject(obj, true);
|
||||||
|
|
||||||
|
// Check if the object is a Puppet (meaning it has display properties)
|
||||||
|
bool isPuppet = (dynamic_cast<::uLib::Vtk::Puppet*>(obj) != nullptr);
|
||||||
|
|
||||||
|
// Only show the "Display" toggle button if it's a puppet
|
||||||
|
m_toggleBtn->setVisible(isPuppet);
|
||||||
|
|
||||||
|
// If it's a puppet, we might want to keep the panel state if it was already open,
|
||||||
|
// or if it's NOT a puppet, definitely hide the toggle and panel.
|
||||||
|
if (!isPuppet) {
|
||||||
|
m_toggleBtn->setChecked(false);
|
||||||
|
m_displayPanel->hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ViewportPane::setViewport(QWidget* viewport, const QString& title) {
|
||||||
|
if (m_viewport) {
|
||||||
|
delete m_viewport;
|
||||||
|
}
|
||||||
|
m_viewport = viewport;
|
||||||
|
m_titleLabel->setText(title);
|
||||||
|
|
||||||
|
m_viewport->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||||
|
m_areaSplitter->insertWidget(0, m_viewport);
|
||||||
|
m_areaSplitter->setStretchFactor(0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ViewportPane::addVtkViewport() {
|
||||||
|
auto* viewport = new uLib::Vtk::QViewport(this);
|
||||||
|
setViewport(viewport, "VTK Viewport");
|
||||||
|
}
|
||||||
|
|
||||||
|
void ViewportPane::addRootCanvas() {
|
||||||
|
auto* canvas = new uLib::Root::QCanvas(this);
|
||||||
|
setViewport(canvas, "ROOT Canvas");
|
||||||
|
}
|
||||||
|
|
||||||
|
void ViewportPane::onCloseRequested() {
|
||||||
|
QSplitter* parentSplitter = qobject_cast<QSplitter*>(parentWidget());
|
||||||
|
if (parentSplitter && parentSplitter->count() > 1) {
|
||||||
|
deleteLater();
|
||||||
|
} else {
|
||||||
|
addVtkViewport();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ViewportPane::showContextMenu(const QPoint& pos) {
|
||||||
|
QMenu menu(this);
|
||||||
|
QAction* hSplit = menu.addAction("H split");
|
||||||
|
QAction* vSplit = menu.addAction("V split");
|
||||||
|
menu.addSeparator();
|
||||||
|
bool isVtk = (qobject_cast<uLib::Vtk::QViewport*>(m_viewport) != nullptr);
|
||||||
|
QAction* changeType = menu.addAction(isVtk ? "Change to ROOT Canvas" : "Change to VTK Viewport");
|
||||||
|
QAction* selected = menu.exec(m_titleBar->mapToGlobal(pos));
|
||||||
|
if (selected == hSplit) AttemptSplit(Qt::Horizontal);
|
||||||
|
else if (selected == vSplit) AttemptSplit(Qt::Vertical);
|
||||||
|
else if (selected == changeType) isVtk ? addRootCanvas() : addVtkViewport();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ViewportPane::AttemptSplit(Qt::Orientation orientation) {
|
||||||
|
QWidget* p = parentWidget();
|
||||||
|
if (!p) return;
|
||||||
|
QSplitter* parentSplitter = qobject_cast<QSplitter*>(p);
|
||||||
|
if (!parentSplitter) return;
|
||||||
|
ViewportPane* newPane = new ViewportPane();
|
||||||
|
if (parentSplitter->orientation() == orientation) {
|
||||||
|
int index = parentSplitter->indexOf(this);
|
||||||
|
QList<int> sizes = parentSplitter->sizes();
|
||||||
|
int currentSize = sizes.value(index, 0);
|
||||||
|
int half = currentSize / 2;
|
||||||
|
sizes[index] = half;
|
||||||
|
sizes.insert(index + 1, currentSize - half);
|
||||||
|
parentSplitter->insertWidget(index + 1, newPane);
|
||||||
|
parentSplitter->setSizes(sizes);
|
||||||
|
} else {
|
||||||
|
int index = parentSplitter->indexOf(this);
|
||||||
|
QList<int> parentSizes = parentSplitter->sizes();
|
||||||
|
QSplitter* newSplitter = new QSplitter(orientation);
|
||||||
|
newSplitter->addWidget(this);
|
||||||
|
newSplitter->addWidget(newPane);
|
||||||
|
QList<int> subSizes;
|
||||||
|
subSizes << 500 << 500;
|
||||||
|
newSplitter->setSizes(subSizes);
|
||||||
|
parentSplitter->insertWidget(index, newSplitter);
|
||||||
|
parentSplitter->setSizes(parentSizes);
|
||||||
|
}
|
||||||
|
}
|
||||||
52
app/gcompose/src/ViewportPane.h
Normal file
52
app/gcompose/src/ViewportPane.h
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
#ifndef VIEWPORTPANE_H
|
||||||
|
#define VIEWPORTPANE_H
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
#include <QFrame>
|
||||||
|
#include <QPushButton>
|
||||||
|
|
||||||
|
namespace uLib {
|
||||||
|
class Object;
|
||||||
|
namespace Qt { class PropertyEditor; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class QSplitter;
|
||||||
|
class QVBoxLayout;
|
||||||
|
class QLabel;
|
||||||
|
|
||||||
|
class ViewportPane : public QWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit ViewportPane(QWidget* parent = nullptr);
|
||||||
|
virtual ~ViewportPane();
|
||||||
|
|
||||||
|
void addVtkViewport();
|
||||||
|
void addRootCanvas();
|
||||||
|
|
||||||
|
QWidget* currentViewport() const { return m_viewport; }
|
||||||
|
|
||||||
|
/** @brief Update the display properties for the given object. */
|
||||||
|
void setObject(uLib::Object* obj);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onCloseRequested();
|
||||||
|
void showContextMenu(const QPoint& pos);
|
||||||
|
void toggleDisplayPanel();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void AttemptSplit(Qt::Orientation orientation);
|
||||||
|
void setViewport(QWidget* viewport, const QString& title);
|
||||||
|
|
||||||
|
QVBoxLayout* m_layout;
|
||||||
|
QWidget* m_titleBar;
|
||||||
|
QLabel* m_titleLabel;
|
||||||
|
QSplitter* m_areaSplitter;
|
||||||
|
QWidget* m_viewport;
|
||||||
|
|
||||||
|
// Display Properties Overlay
|
||||||
|
QFrame* m_displayPanel;
|
||||||
|
uLib::Qt::PropertyEditor* m_displayEditor;
|
||||||
|
QPushButton* m_toggleBtn;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // VIEWPORTPANE_H
|
||||||
@@ -1,14 +1,19 @@
|
|||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include "MainWindow.h"
|
#include "MainWindow.h"
|
||||||
|
#include "MainPanel.h"
|
||||||
|
#include "ViewportPane.h"
|
||||||
|
#include "StyleManager.h"
|
||||||
|
|
||||||
#include "Math/ContainerBox.h"
|
#include "Math/ContainerBox.h"
|
||||||
#include <HEP/Geant/Scene.h>
|
#include <HEP/Geant/Scene.h>
|
||||||
#include "HEP/Detectors/DetectorChamber.h"
|
#include "HEP/Detectors/DetectorChamber.h"
|
||||||
#include "Vtk/HEP/Detectors/vtkDetectorChamber.h"
|
#include "Vtk/HEP/Detectors/vtkDetectorChamber.h"
|
||||||
|
|
||||||
#include <Vtk/vtkContainerBox.h>
|
#include <Vtk/Math/vtkContainerBox.h>
|
||||||
#include <Vtk/vtkQViewport.h>
|
#include <Vtk/vtkQViewport.h>
|
||||||
|
|
||||||
|
#include "Core/ObjectsContext.h"
|
||||||
|
|
||||||
#include <vtkSmartPointer.h>
|
#include <vtkSmartPointer.h>
|
||||||
#include <vtkCubeSource.h>
|
#include <vtkCubeSource.h>
|
||||||
#include <vtkPolyDataMapper.h>
|
#include <vtkPolyDataMapper.h>
|
||||||
@@ -24,42 +29,25 @@ using namespace uLib::literals;
|
|||||||
|
|
||||||
int main(int argc, char** argv) {
|
int main(int argc, char** argv) {
|
||||||
QApplication app(argc, argv);
|
QApplication app(argc, argv);
|
||||||
|
StyleManager::applyStyle(&app, "dark");
|
||||||
std::cout << "Starting gcompose Qt application..." << std::endl;
|
std::cout << "Starting gcompose Qt application..." << std::endl;
|
||||||
|
|
||||||
ContainerBox world_box(Vector3f(1, 1, 1));
|
// ContainerBox world_box(Vector3f(1, 1, 1));
|
||||||
world_box.Scale(Vector3f(20_mm, 20_mm, 20_mm));
|
// world_box.Scale(Vector3f(2_mm, 2_mm, 2_mm));
|
||||||
|
// world_box.SetPosition(Vector3f(-1_mm, -1_mm, -1_mm));
|
||||||
|
|
||||||
DetectorChamber d1, d2;
|
// Geant::Scene scene;
|
||||||
d1.SetSize(Vector3f(1, 1, 1));
|
// scene.ConstructWorldBox(world_box.GetSize(), "G4_AIR");
|
||||||
d1.SetPosition(Vector3f(0, 0, 0));
|
// scene.Initialize();
|
||||||
d1.Scale(Vector3f(5, 10, 2));
|
|
||||||
d1.Translate(Vector3f(0, 0, 0));
|
|
||||||
|
|
||||||
d2.SetSize(Vector3f(1, 1, 1));
|
uLib::ObjectsContext globalContext;
|
||||||
d2.SetPosition(Vector3f(0, 0, 0));
|
// globalContext.AddObject(&world_box);
|
||||||
d2.Scale(Vector3f(5, 10, 2));
|
// globalContext.AddObject(&scene);
|
||||||
d2.Translate(Vector3f(0, 0, 10));
|
|
||||||
|
|
||||||
|
|
||||||
Geant::Scene scene;
|
|
||||||
scene.ConstructWorldBox(world_box.GetSize(), "G4_AIR");
|
|
||||||
scene.Initialize();
|
|
||||||
|
|
||||||
// 2. Initialize MainWindow (contains embedded VTK QViewport)
|
// 2. Initialize MainWindow (contains embedded VTK QViewport)
|
||||||
MainWindow window;
|
MainWindow window;
|
||||||
Vtk::QViewport* viewport = window.getViewport();
|
window.setContext(&globalContext);
|
||||||
|
|
||||||
Vtk::vtkDetectorChamber vtk_d1(&d1);
|
|
||||||
viewport->AddPuppet(vtk_d1);
|
|
||||||
|
|
||||||
Vtk::vtkDetectorChamber vtk_d2(&d2);
|
|
||||||
viewport->AddPuppet(vtk_d2);
|
|
||||||
|
|
||||||
Vtk::vtkContainerBox vtk_box(&world_box);
|
|
||||||
viewport->AddPuppet(vtk_box);
|
|
||||||
|
|
||||||
viewport->ZoomAuto();
|
|
||||||
|
|
||||||
std::cout << "Geant4 and VTK scenes are ready." << std::endl;
|
std::cout << "Geant4 and VTK scenes are ready." << std::endl;
|
||||||
|
|
||||||
window.show();
|
window.show();
|
||||||
|
|||||||
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
|
||||||
@@ -198,20 +198,16 @@ public:
|
|||||||
return &bpos;
|
return &bpos;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class T> Archive &operator<<(T &t) {
|
template <class T> Archive &operator<<(const T &t) {
|
||||||
// to get access you must redefine save_override by typing
|
// to get access you must redefine save_override by typing
|
||||||
// "using save_override" in archive impl
|
// "using save_override" in archive impl
|
||||||
this->This()->save_override(t);
|
this->This()->save_override(t);
|
||||||
return *this->This();
|
return *this->This();
|
||||||
}
|
}
|
||||||
|
|
||||||
// the & operator
|
// the & operator
|
||||||
template <class T> Archive &operator&(T &t) {
|
template <class T> Archive &operator&(const T &t) {
|
||||||
#ifndef BOOST_NO_FUNCTION_TEMPLATE_ORDERING
|
|
||||||
return *this->This() << const_cast<const T &>(t);
|
|
||||||
#else
|
|
||||||
return *this->This() << t;
|
return *this->This() << t;
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// the == operator
|
// the == operator
|
||||||
@@ -364,7 +360,6 @@ public:
|
|||||||
boost::serialization::hrp<T> &t) {
|
boost::serialization::hrp<T> &t) {
|
||||||
this->This()->load_start(t.name());
|
this->This()->load_start(t.name());
|
||||||
this->detail_common_iarchive::load_override(t.value());
|
this->detail_common_iarchive::load_override(t.value());
|
||||||
// t.stov();
|
|
||||||
this->This()->load_end(t.name());
|
this->This()->load_end(t.name());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -432,8 +427,7 @@ public:
|
|||||||
#endif
|
#endif
|
||||||
::boost::serialization::hrp<T> &t) {
|
::boost::serialization::hrp<T> &t) {
|
||||||
this->This()->save_start(t.name());
|
this->This()->save_start(t.name());
|
||||||
// t.vtos();
|
this->detail_common_oarchive::save_override(t.const_value());
|
||||||
// this->detail_common_oarchive::save_override(t.const_value());
|
|
||||||
this->This()->save_end(t.name());
|
this->This()->save_end(t.name());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -467,14 +461,10 @@ public:
|
|||||||
text_iarchive(std::istream &is, unsigned int flags = 0)
|
text_iarchive(std::istream &is, unsigned int flags = 0)
|
||||||
: text_iarchive_impl<Archive>(is, flags) {}
|
: text_iarchive_impl<Archive>(is, flags) {}
|
||||||
|
|
||||||
using basic_text_iarchive::load_override;
|
using base::load_override;
|
||||||
|
|
||||||
void load_override(boost::archive::object_id_type &t) {}
|
void load_override(boost::archive::object_id_type &t) {}
|
||||||
|
|
||||||
// class_name_type can't be handled here as it depends upon the
|
|
||||||
// char type used by the stream. So require the derived implementation.
|
|
||||||
// derived in this case is xml_iarchive_impl or base ..
|
|
||||||
using base::load_override;
|
|
||||||
|
|
||||||
void load_override(const char *str) {
|
void load_override(const char *str) {
|
||||||
StringReader sr(basic_text_iprimitive::is);
|
StringReader sr(basic_text_iprimitive::is);
|
||||||
@@ -532,7 +522,7 @@ public:
|
|||||||
hrt_iarchive(std::istream &is, unsigned int flags = 0)
|
hrt_iarchive(std::istream &is, unsigned int flags = 0)
|
||||||
: base(is, flags | boost::archive::no_header) {}
|
: base(is, flags | boost::archive::no_header) {}
|
||||||
|
|
||||||
using basic_text_iarchive::load_override;
|
using base::load_override;
|
||||||
|
|
||||||
// hide all archive props //
|
// hide all archive props //
|
||||||
void load_override(boost::archive::object_id_type &t) {}
|
void load_override(boost::archive::object_id_type &t) {}
|
||||||
@@ -544,10 +534,6 @@ public:
|
|||||||
void load_override(boost::archive::class_name_type &t) {}
|
void load_override(boost::archive::class_name_type &t) {}
|
||||||
void load_override(boost::archive::tracking_type &t) {}
|
void load_override(boost::archive::tracking_type &t) {}
|
||||||
|
|
||||||
// class_name_type can't be handled here as it depends upon the
|
|
||||||
// char type used by the stream. So require the derived implementation.
|
|
||||||
// derived in this case is xml_iarchive_impl or base ..
|
|
||||||
using base::load_override;
|
|
||||||
|
|
||||||
void load_override(const char *str) {
|
void load_override(const char *str) {
|
||||||
StringReader sr(basic_text_iprimitive::is);
|
StringReader sr(basic_text_iprimitive::is);
|
||||||
@@ -583,6 +569,13 @@ public:
|
|||||||
|
|
||||||
void save_override(const char *str) { basic_text_oprimitive::save(str); }
|
void save_override(const char *str) { basic_text_oprimitive::save(str); }
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
void save_override(const boost::serialization::hrp<T> &t) {
|
||||||
|
*this << t.name() << ": ";
|
||||||
|
*this << t.const_value();
|
||||||
|
*this << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
~hrt_oarchive() {}
|
~hrt_oarchive() {}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -611,7 +604,7 @@ public:
|
|||||||
// basic_text_oprimitive::save(str);
|
// basic_text_oprimitive::save(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class T> void save_override(T &t) {
|
template <class T> void save_override(const T &t) {
|
||||||
base::save_override(boost::serialization::make_nvp(NULL, t));
|
base::save_override(boost::serialization::make_nvp(NULL, t));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -627,6 +620,10 @@ public:
|
|||||||
base::save_override(t);
|
base::save_override(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <class T> void save_override(const boost::serialization::hrp<T> &t) {
|
||||||
|
base::save_override(boost::serialization::make_nvp(t.name(), t.const_value()));
|
||||||
|
}
|
||||||
|
|
||||||
// specific overrides for attributes - not name value pairs so we
|
// specific overrides for attributes - not name value pairs so we
|
||||||
// want to trap them before the above "fall through"
|
// want to trap them before the above "fall through"
|
||||||
// since we don't want to see these in the output - make them no-ops.
|
// since we don't want to see these in the output - make them no-ops.
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
|
|
||||||
set(HEADERS
|
set(HEADERS
|
||||||
Archives.h
|
Algorithm.h
|
||||||
Array.h
|
Archives.h
|
||||||
|
Array.h
|
||||||
Collection.h
|
Collection.h
|
||||||
DataAllocator.h
|
DataAllocator.h
|
||||||
Debug.h
|
Debug.h
|
||||||
@@ -10,6 +11,8 @@ set(HEADERS
|
|||||||
Macros.h
|
Macros.h
|
||||||
Mpl.h
|
Mpl.h
|
||||||
Object.h
|
Object.h
|
||||||
|
ObjectFactory.h
|
||||||
|
ObjectsContext.h
|
||||||
Options.h
|
Options.h
|
||||||
Serializable.h
|
Serializable.h
|
||||||
Signal.h
|
Signal.h
|
||||||
@@ -17,6 +20,8 @@ set(HEADERS
|
|||||||
SmartPointer.h
|
SmartPointer.h
|
||||||
StaticInterface.h
|
StaticInterface.h
|
||||||
StringReader.h
|
StringReader.h
|
||||||
|
Threads.h
|
||||||
|
Monitor.h
|
||||||
Types.h
|
Types.h
|
||||||
Uuid.h
|
Uuid.h
|
||||||
Vector.h
|
Vector.h
|
||||||
@@ -26,13 +31,20 @@ set(SOURCES
|
|||||||
Archives.cpp
|
Archives.cpp
|
||||||
Debug.cpp
|
Debug.cpp
|
||||||
Object.cpp
|
Object.cpp
|
||||||
|
ObjectFactory.cpp
|
||||||
|
ObjectsContext.cpp
|
||||||
Options.cpp
|
Options.cpp
|
||||||
Serializable.cpp
|
Serializable.cpp
|
||||||
Signal.cpp
|
Signal.cpp
|
||||||
Uuid.cpp
|
Uuid.cpp
|
||||||
|
Threads.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set(LIBRARIES Boost::program_options Boost::serialization)
|
set(LIBRARIES
|
||||||
|
Boost::program_options
|
||||||
|
Boost::serialization
|
||||||
|
OpenMP::OpenMP_CXX
|
||||||
|
)
|
||||||
|
|
||||||
set(libname ${PACKAGE_LIBPREFIX}Core)
|
set(libname ${PACKAGE_LIBPREFIX}Core)
|
||||||
set(ULIB_SHARED_LIBRARIES ${ULIB_SHARED_LIBRARIES} ${libname} PARENT_SCOPE)
|
set(ULIB_SHARED_LIBRARIES ${ULIB_SHARED_LIBRARIES} ${libname} PARENT_SCOPE)
|
||||||
|
|||||||
@@ -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,7 +64,12 @@ 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)));
|
||||||
std::memcpy(m_RamData, other.m_RamData, 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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#ifdef USE_CUDA
|
#ifdef USE_CUDA
|
||||||
if (other.m_VramData) {
|
if (other.m_VramData) {
|
||||||
@@ -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,7 +117,11 @@ 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::memcpy(m_RamData, other.m_RamData, 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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#ifdef USE_CUDA
|
#ifdef USE_CUDA
|
||||||
if (other.m_VramData) {
|
if (other.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,7 +185,11 @@ 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) {
|
||||||
std::memcpy(newRam, m_RamData, std::min(m_Size, size) * sizeof(T));
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef USE_CUDA
|
#ifdef USE_CUDA
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
#include "SmartPointer.h"
|
#include "SmartPointer.h"
|
||||||
|
|
||||||
#include <boost/any.hpp>
|
#include <boost/any.hpp>
|
||||||
#include <TObject.h>
|
|
||||||
|
|
||||||
namespace uLib {
|
namespace uLib {
|
||||||
|
|
||||||
|
|||||||
215
src/Core/Monitor.h
Normal file
215
src/Core/Monitor.h
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
/*//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// 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_MONITOR_H
|
||||||
|
#define U_CORE_MONITOR_H
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <chrono>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace uLib {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Mutex class wraps std::timed_mutex and is used for thread synchronization.
|
||||||
|
*/
|
||||||
|
class Mutex {
|
||||||
|
public:
|
||||||
|
Mutex() = default;
|
||||||
|
~Mutex() = default;
|
||||||
|
|
||||||
|
/** @brief Locks the mutex, blocking if necessary. */
|
||||||
|
void Lock() { m_Mutex.lock(); }
|
||||||
|
|
||||||
|
/** @brief Unlocks the mutex. */
|
||||||
|
void Unlock() { m_Mutex.unlock(); }
|
||||||
|
|
||||||
|
/** @brief Tries to lock the mutex without blocking. */
|
||||||
|
bool TryLock() { return m_Mutex.try_lock(); }
|
||||||
|
|
||||||
|
/** @brief Tries to lock the mutex within a timeout in milliseconds. */
|
||||||
|
bool TryLockFor(int timeout_ms) {
|
||||||
|
if (timeout_ms < 0) { Lock(); return true; }
|
||||||
|
return m_Mutex.try_lock_for(std::chrono::milliseconds(timeout_ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @brief RAII helper for scoped locking. */
|
||||||
|
class ScopedLock {
|
||||||
|
public:
|
||||||
|
ScopedLock(Mutex &mutex) : m_Mutex(mutex) { m_Mutex.Lock(); }
|
||||||
|
~ScopedLock() { m_Mutex.Unlock(); }
|
||||||
|
private:
|
||||||
|
Mutex &m_Mutex;
|
||||||
|
// Non-copyable
|
||||||
|
ScopedLock(const ScopedLock&) = delete;
|
||||||
|
ScopedLock& operator=(const ScopedLock&) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @brief Returns the underlying std::timed_mutex. */
|
||||||
|
std::timed_mutex& GetNative() { return m_Mutex; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::timed_mutex m_Mutex;
|
||||||
|
// Non-copyable
|
||||||
|
Mutex(const Mutex &) = delete;
|
||||||
|
Mutex &operator=(const Mutex &) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
/** @brief Internal implementation for the ULIB_MUTEX_LOCK macros. */
|
||||||
|
class ScopedTimedLock {
|
||||||
|
public:
|
||||||
|
ScopedTimedLock(Mutex& mutex, int timeout_ms)
|
||||||
|
: m_RawMutex(nullptr), m_MutexWrapper(&mutex), m_Locked(false) {
|
||||||
|
m_Locked = m_MutexWrapper->TryLockFor(timeout_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
ScopedTimedLock(std::timed_mutex& mutex, int timeout_ms)
|
||||||
|
: m_RawMutex(&mutex), m_MutexWrapper(nullptr), m_Locked(false) {
|
||||||
|
if (timeout_ms < 0) { m_RawMutex->lock(); m_Locked = true; }
|
||||||
|
else m_Locked = m_RawMutex->try_lock_for(std::chrono::milliseconds(timeout_ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
~ScopedTimedLock() {
|
||||||
|
if (m_Locked) {
|
||||||
|
if (m_RawMutex) m_RawMutex->unlock();
|
||||||
|
else if (m_MutexWrapper) m_MutexWrapper->Unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
operator bool() const { return m_Locked; }
|
||||||
|
void unlock() { if (m_Locked) {
|
||||||
|
if (m_RawMutex) m_RawMutex->unlock();
|
||||||
|
else if (m_MutexWrapper) m_MutexWrapper->Unlock();
|
||||||
|
m_Locked = false;
|
||||||
|
} }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::timed_mutex* m_RawMutex = nullptr;
|
||||||
|
Mutex* m_MutexWrapper = nullptr;
|
||||||
|
bool m_Locked;
|
||||||
|
|
||||||
|
// Non-copyable/movable to be safe in the 'for' loop
|
||||||
|
ScopedTimedLock(const ScopedTimedLock&) = delete;
|
||||||
|
ScopedTimedLock& operator=(const ScopedTimedLock&) = delete;
|
||||||
|
ScopedTimedLock(ScopedTimedLock&&) = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline ScopedTimedLock makeScopedMutexLock(Mutex& mutex, int timeout_ms) {
|
||||||
|
return ScopedTimedLock(mutex, timeout_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ScopedTimedLock makeScopedMutexLock(std::timed_mutex& mutex, int timeout_ms) {
|
||||||
|
return ScopedTimedLock(mutex, timeout_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Macro for block-scoped locking of a static mutex.
|
||||||
|
* @param timeout Timeout in ms (-1 for infinite).
|
||||||
|
*/
|
||||||
|
#define ULIB_STATIC_LOCK(timeout) \
|
||||||
|
static std::timed_mutex __ulib_static_mutex; \
|
||||||
|
for (auto __ulib_lock = uLib::detail::makeScopedMutexLock(__ulib_static_mutex, timeout); \
|
||||||
|
__ulib_lock; \
|
||||||
|
__ulib_lock.unlock())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Macro for block-scoped locking of a provided mutex.
|
||||||
|
* @param mutex The uLib::Mutex or std::timed_mutex to lock.
|
||||||
|
* @param timeout Timeout in ms (-1 for infinite).
|
||||||
|
*/
|
||||||
|
#define ULIB_MUTEX_LOCK(mutex, timeout) \
|
||||||
|
for (auto __ulib_lock = uLib::detail::makeScopedMutexLock(mutex, timeout); \
|
||||||
|
__ulib_lock; \
|
||||||
|
__ulib_lock.unlock())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief RecursiveMutex class wraps std::recursive_timed_mutex.
|
||||||
|
*/
|
||||||
|
class RecursiveMutex {
|
||||||
|
public:
|
||||||
|
RecursiveMutex() = default;
|
||||||
|
~RecursiveMutex() = default;
|
||||||
|
|
||||||
|
/** @brief Locks the mutex, blocking if necessary. */
|
||||||
|
void Lock() { m_Mutex.lock(); }
|
||||||
|
|
||||||
|
/** @brief Unlocks the mutex. */
|
||||||
|
void Unlock() { m_Mutex.unlock(); }
|
||||||
|
|
||||||
|
/** @brief Tries to lock the mutex without blocking. */
|
||||||
|
bool TryLock() { return m_Mutex.try_lock(); }
|
||||||
|
|
||||||
|
/** @brief Tries to lock the mutex within a timeout in milliseconds. */
|
||||||
|
bool TryLockFor(int timeout_ms) {
|
||||||
|
if (timeout_ms < 0) { Lock(); return true; }
|
||||||
|
return m_Mutex.try_lock_for(std::chrono::milliseconds(timeout_ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @brief RAII helper for scoped locking. */
|
||||||
|
class ScopedLock {
|
||||||
|
public:
|
||||||
|
ScopedLock(RecursiveMutex &mutex) : m_Mutex(mutex) { m_Mutex.Lock(); }
|
||||||
|
~ScopedLock() { m_Mutex.Unlock(); }
|
||||||
|
private:
|
||||||
|
RecursiveMutex &m_Mutex;
|
||||||
|
ScopedLock(const ScopedLock&) = delete;
|
||||||
|
ScopedLock& operator=(const ScopedLock&) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::recursive_timed_mutex m_Mutex;
|
||||||
|
RecursiveMutex(const RecursiveMutex &) = delete;
|
||||||
|
RecursiveMutex &operator=(const RecursiveMutex &) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Monitor class provides a base for objects that need thread-safe access.
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
class Monitor {
|
||||||
|
protected:
|
||||||
|
T* m_Resource;
|
||||||
|
Mutex m_Mutex;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Monitor(T* resource) : m_Resource(resource) {}
|
||||||
|
virtual ~Monitor() { delete m_Resource; }
|
||||||
|
|
||||||
|
/** @brief Thread-safe access to the resource through a lambda. */
|
||||||
|
template <typename F>
|
||||||
|
auto Access(F f) -> decltype(f(*m_Resource)) {
|
||||||
|
Mutex::ScopedLock lock(m_Mutex);
|
||||||
|
return f(*m_Resource);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace uLib
|
||||||
|
|
||||||
|
#endif // U_CORE_MONITOR_H
|
||||||
@@ -35,6 +35,9 @@
|
|||||||
#include "boost/archive/polymorphic_xml_iarchive.hpp"
|
#include "boost/archive/polymorphic_xml_iarchive.hpp"
|
||||||
#include "boost/archive/polymorphic_xml_oarchive.hpp"
|
#include "boost/archive/polymorphic_xml_oarchive.hpp"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include "Property.h"
|
||||||
|
|
||||||
namespace uLib {
|
namespace uLib {
|
||||||
|
|
||||||
const char *Version::PackageName = PACKAGE_NAME;
|
const char *Version::PackageName = PACKAGE_NAME;
|
||||||
@@ -56,26 +59,111 @@ public:
|
|||||||
GenericMFPtr sloptr;
|
GenericMFPtr sloptr;
|
||||||
std::string slostr;
|
std::string slostr;
|
||||||
};
|
};
|
||||||
|
|
||||||
Vector<Signal> sigv;
|
std::string m_InstanceName;
|
||||||
Vector<Slot> slov;
|
std::vector<Signal> sigv;
|
||||||
|
std::vector<Slot> slov;
|
||||||
|
std::vector<PropertyBase*> m_Properties;
|
||||||
|
std::vector<PropertyBase*> m_DynamicProperties;
|
||||||
|
bool m_SignalsBlocked;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Implementations of Property methods
|
||||||
|
void Object::RegisterProperty(PropertyBase* prop) {
|
||||||
|
if (prop) {
|
||||||
|
d->m_Properties.push_back(prop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Object::RegisterDynamicProperty(PropertyBase* prop) {
|
||||||
|
if (prop) {
|
||||||
|
for (auto* existing : d->m_DynamicProperties) {
|
||||||
|
if (existing == prop) return;
|
||||||
|
}
|
||||||
|
d->m_DynamicProperties.push_back(prop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<PropertyBase*>& Object::GetProperties() const {
|
||||||
|
return d->m_Properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
PropertyBase* Object::GetProperty(const std::string& name) const {
|
||||||
|
for (auto* p : d->m_Properties) {
|
||||||
|
if (p->GetName() == name || 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.
|
||||||
|
// However, since Object::serialize is a template in the header, we might need a helper here.
|
||||||
|
|
||||||
|
template <class ArchiveT>
|
||||||
|
void Object::serialize(ArchiveT &ar, const unsigned int version) {
|
||||||
|
ar & boost::serialization::make_nvp("InstanceName", d->m_InstanceName);
|
||||||
|
for (auto* prop : d->m_Properties) {
|
||||||
|
prop->serialize(ar, version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Object::Updated() { ULIB_SIGNAL_EMIT(Object::Updated); }
|
||||||
|
|
||||||
|
template <class ArchiveT>
|
||||||
|
void Object::save_override(ArchiveT &ar, const unsigned int version) {}
|
||||||
|
|
||||||
|
// Explicitly instantiate for all uLib archives
|
||||||
|
template void Object::serialize(Archive::xml_oarchive &, const unsigned int);
|
||||||
|
template void Object::serialize(Archive::xml_iarchive &, const unsigned int);
|
||||||
|
template void Object::serialize(Archive::text_oarchive &, const unsigned int);
|
||||||
|
template void Object::serialize(Archive::text_iarchive &, const unsigned int);
|
||||||
|
template void Object::serialize(Archive::hrt_oarchive &, const unsigned int);
|
||||||
|
template void Object::serialize(Archive::hrt_iarchive &, const unsigned int);
|
||||||
|
template void Object::serialize(Archive::log_archive &, const unsigned int);
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
// OBJECT IMPLEMENTATION
|
// OBJECT IMPLEMENTATION
|
||||||
|
|
||||||
Object::Object() : d(new ObjectPrivate) {}
|
Object::Object() : d(new ObjectPrivate) {
|
||||||
|
d->m_SignalsBlocked = false;
|
||||||
|
}
|
||||||
|
Object::Object(const Object ©) : d(new ObjectPrivate) {
|
||||||
|
if (copy.d) {
|
||||||
|
d->m_InstanceName = copy.d->m_InstanceName;
|
||||||
|
d->m_SignalsBlocked = copy.d->m_SignalsBlocked;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Object::Object(const Object ©) : d(new ObjectPrivate(*copy.d)) {}
|
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() { delete d; }
|
Object::~Object() {
|
||||||
|
for (auto* p : d->m_DynamicProperties) {
|
||||||
|
delete p;
|
||||||
|
}
|
||||||
|
delete d;
|
||||||
|
}
|
||||||
|
|
||||||
void Object::DeepCopy(const Object ©) {
|
void Object::DeepCopy(const Object ©) {
|
||||||
// should lock to be tread safe //
|
if (this == ©) return;
|
||||||
memcpy(d, copy.d, sizeof(ObjectPrivate));
|
if (copy.d) d->m_InstanceName = copy.d->m_InstanceName;
|
||||||
// ERROR! does not copy parameters ... <<<< FIXXXXX
|
std::cout << "Object DeepCopy: from d=" << copy.d << " to d=" << d << std::endl;
|
||||||
|
// Note: signals, slots and properties are intentionally not copied
|
||||||
|
// to maintain instance uniquely and avoid duplicate registrations.
|
||||||
|
this->Updated();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Object::SaveXml(std::ostream &os, Object &ob) {
|
void Object::SaveXml(std::ostream &os, Object &ob) {
|
||||||
@@ -99,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";
|
||||||
}
|
}
|
||||||
@@ -145,7 +232,24 @@ GenericMFPtr *Object::findSlotImpl(const char *name) const {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Object::Updated() { ULIB_SIGNAL_EMIT(Object::Updated); }
|
const std::string& Object::GetInstanceName() const {
|
||||||
|
return d->m_InstanceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Object::SetInstanceName(const std::string& name) {
|
||||||
|
d->m_InstanceName = name;
|
||||||
|
this->Updated();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Object::blockSignals(bool block) {
|
||||||
|
bool old = d->m_SignalsBlocked;
|
||||||
|
d->m_SignalsBlocked = block;
|
||||||
|
return old;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Object::signalsBlocked() const {
|
||||||
|
return d->m_SignalsBlocked;
|
||||||
|
}
|
||||||
|
|
||||||
// std::ostream &
|
// std::ostream &
|
||||||
// operator << (std::ostream &os, uLib::Object &ob)
|
// operator << (std::ostream &os, uLib::Object &ob)
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
#define U_CORE_OBJECT_H
|
#define U_CORE_OBJECT_H
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
// WARNING: COPILE ERROR if this goes after mpl/vector //
|
// WARNING: COPILE ERROR if this goes after mpl/vector //
|
||||||
// #include "Core/Vector.h"
|
// #include "Core/Vector.h"
|
||||||
@@ -50,6 +51,8 @@ class polymorphic_oarchive;
|
|||||||
|
|
||||||
namespace uLib {
|
namespace uLib {
|
||||||
|
|
||||||
|
class PropertyBase;
|
||||||
|
|
||||||
class Version {
|
class Version {
|
||||||
public:
|
public:
|
||||||
static const char *PackageName;
|
static const char *PackageName;
|
||||||
@@ -72,7 +75,25 @@ public:
|
|||||||
|
|
||||||
Object();
|
Object();
|
||||||
Object(const Object ©);
|
Object(const Object ©);
|
||||||
~Object();
|
virtual ~Object();
|
||||||
|
|
||||||
|
virtual const char * GetClassName() const { return "Object"; }
|
||||||
|
|
||||||
|
const std::string& GetInstanceName() const;
|
||||||
|
void SetInstanceName(const std::string& name);
|
||||||
|
|
||||||
|
/** @brief Temporarily blocks all signal emissions from this object. Returns previous state. */
|
||||||
|
bool blockSignals(bool block);
|
||||||
|
|
||||||
|
/** @brief Checks if signals are currently blocked. */
|
||||||
|
bool signalsBlocked() const;
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// PROPERTIES //
|
||||||
|
void RegisterProperty(PropertyBase* prop);
|
||||||
|
void RegisterDynamicProperty(PropertyBase* prop);
|
||||||
|
const std::vector<PropertyBase*>& GetProperties() const;
|
||||||
|
PropertyBase* GetProperty(const std::string& name) const;
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
// PARAMETERS //
|
// PARAMETERS //
|
||||||
@@ -84,9 +105,18 @@ public:
|
|||||||
// SERIALIZATION //
|
// SERIALIZATION //
|
||||||
|
|
||||||
template <class ArchiveT>
|
template <class ArchiveT>
|
||||||
void serialize(ArchiveT &ar, const unsigned int version) {}
|
void serialize(ArchiveT &ar, const unsigned int version);
|
||||||
|
|
||||||
|
virtual void serialize(Archive::xml_oarchive & ar, const unsigned int version) {}
|
||||||
|
virtual void serialize(Archive::xml_iarchive & ar, const unsigned int version) {}
|
||||||
|
virtual void serialize(Archive::text_oarchive & ar, const unsigned int version) {}
|
||||||
|
virtual void serialize(Archive::text_iarchive & ar, const unsigned int version) {}
|
||||||
|
virtual void serialize(Archive::hrt_oarchive & ar, const unsigned int version) {}
|
||||||
|
virtual void serialize(Archive::hrt_iarchive & ar, const unsigned int version) {}
|
||||||
|
virtual void serialize(Archive::log_archive & ar, const unsigned int version) {}
|
||||||
|
|
||||||
template <class ArchiveT>
|
template <class ArchiveT>
|
||||||
void save_override(ArchiveT &ar, const unsigned int version) {}
|
void save_override(ArchiveT &ar, const unsigned int version);
|
||||||
|
|
||||||
void SaveConfig(std::ostream &os, int version = 0);
|
void SaveConfig(std::ostream &os, int version = 0);
|
||||||
void LoadConfig(std::istream &is, int version = 0);
|
void LoadConfig(std::istream &is, int version = 0);
|
||||||
@@ -115,24 +145,22 @@ public:
|
|||||||
|
|
||||||
// Qt5 style connector //
|
// Qt5 style connector //
|
||||||
template <typename Func1, typename Func2>
|
template <typename Func1, typename Func2>
|
||||||
static bool
|
static Connection
|
||||||
connect(typename FunctionPointer<Func1>::Object *sender, Func1 sigf,
|
connect(typename FunctionPointer<Func1>::Object *sender, Func1 sigf,
|
||||||
typename FunctionPointer<Func2>::Object *receiver, Func2 slof) {
|
typename FunctionPointer<Func2>::Object *receiver, Func2 slof) {
|
||||||
SignalBase *sigb = sender->findOrAddSignal(sigf);
|
SignalBase *sigb = sender->findOrAddSignal(sigf);
|
||||||
ConnectSignal<typename FunctionPointer<Func1>::SignalSignature>(sigb, slof,
|
return ConnectSignal<typename FunctionPointer<Func1>::SignalSignature>(sigb, slof,
|
||||||
receiver);
|
receiver);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lambda/Function object connector //
|
// Lambda/Function object connector //
|
||||||
template <typename Func1, typename SlotT>
|
template <typename Func1, typename SlotT>
|
||||||
static bool connect(typename FunctionPointer<Func1>::Object *sender,
|
static Connection connect(typename FunctionPointer<Func1>::Object *sender,
|
||||||
Func1 sigf, SlotT slof) {
|
Func1 sigf, SlotT slof) {
|
||||||
SignalBase *sigb = sender->findOrAddSignal(sigf);
|
SignalBase *sigb = sender->findOrAddSignal(sigf);
|
||||||
typedef typename FunctionPointer<Func1>::SignalSignature SigSignature;
|
typedef typename FunctionPointer<Func1>::SignalSignature SigSignature;
|
||||||
typedef typename Signal<SigSignature>::type SigT;
|
typedef typename Signal<SigSignature>::type SigT;
|
||||||
reinterpret_cast<SigT *>(sigb)->connect(slof);
|
return reinterpret_cast<SigT *>(sigb)->connect(slof);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Func1, typename Func2>
|
template <typename Func1, typename Func2>
|
||||||
@@ -144,10 +172,9 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename FuncT>
|
template <typename FuncT>
|
||||||
static inline bool connect(SignalBase *sigb, FuncT slof, Object *receiver) {
|
static inline Connection connect(SignalBase *sigb, FuncT slof, Object *receiver) {
|
||||||
ConnectSignal<typename FunctionPointer<FuncT>::SignalSignature>(sigb, slof,
|
return ConnectSignal<typename FunctionPointer<FuncT>::SignalSignature>(sigb, slof,
|
||||||
receiver);
|
receiver);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename FuncT>
|
template <typename FuncT>
|
||||||
@@ -201,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);
|
||||||
|
|||||||
32
src/Core/ObjectFactory.cpp
Normal file
32
src/Core/ObjectFactory.cpp
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
#include "ObjectFactory.h"
|
||||||
|
|
||||||
|
namespace uLib {
|
||||||
|
|
||||||
|
ObjectFactory& ObjectFactory::Instance() {
|
||||||
|
static ObjectFactory instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ObjectFactory::Register(const std::string& className, FactoryFunction func) {
|
||||||
|
if (m_factoryMap.find(className) == m_factoryMap.end()) {
|
||||||
|
m_factoryMap[className] = func;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Object* ObjectFactory::Create(const std::string& className) {
|
||||||
|
auto it = m_factoryMap.find(className);
|
||||||
|
if (it != m_factoryMap.end()) {
|
||||||
|
return (it->second)();
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> ObjectFactory::GetRegisteredClasses() const {
|
||||||
|
std::vector<std::string> classes;
|
||||||
|
for (auto const& [name, func] : m_factoryMap) {
|
||||||
|
classes.push_back(name);
|
||||||
|
}
|
||||||
|
return classes;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace uLib
|
||||||
68
src/Core/ObjectFactory.h
Normal file
68
src/Core/ObjectFactory.h
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
#ifndef U_CORE_OBJECTFACTORY_H
|
||||||
|
#define U_CORE_OBJECTFACTORY_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
|
#include <functional>
|
||||||
|
#include "Core/Object.h"
|
||||||
|
|
||||||
|
namespace uLib {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Singleton factory for dynamic Object instantiation based on class name.
|
||||||
|
*/
|
||||||
|
class ObjectFactory {
|
||||||
|
public:
|
||||||
|
typedef std::function<Object*()> FactoryFunction;
|
||||||
|
|
||||||
|
/** @brief Get the singleton instance. */
|
||||||
|
static ObjectFactory& Instance();
|
||||||
|
|
||||||
|
/** @brief Register a factory function for a given class name. */
|
||||||
|
void Register(const std::string& className, FactoryFunction func);
|
||||||
|
|
||||||
|
/** @brief Create a new instance of the specified class. */
|
||||||
|
Object* Create(const std::string& className);
|
||||||
|
|
||||||
|
/** @brief Get the names of all registered classes. */
|
||||||
|
std::vector<std::string> GetRegisteredClasses() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
ObjectFactory() = default;
|
||||||
|
~ObjectFactory() = default;
|
||||||
|
|
||||||
|
// Prevent copy and assignment
|
||||||
|
ObjectFactory(const ObjectFactory&) = delete;
|
||||||
|
ObjectFactory& operator=(const ObjectFactory&) = delete;
|
||||||
|
|
||||||
|
std::map<std::string, FactoryFunction> m_factoryMap;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Helper class to statically register a factory function.
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
class ObjectRegistrar {
|
||||||
|
public:
|
||||||
|
ObjectRegistrar(const std::string& className) {
|
||||||
|
ObjectFactory::Instance().Register(className, []() -> Object* { return new T(); });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#define ULIB_REG_CONCAT_IMPL(a, b) a##b
|
||||||
|
#define ULIB_REG_CONCAT(a, b) ULIB_REG_CONCAT_IMPL(a, b)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Macro to register a class to the factory.
|
||||||
|
* Put this in the .cpp file of the class.
|
||||||
|
*/
|
||||||
|
#define ULIB_REGISTER_OBJECT(className) \
|
||||||
|
static uLib::ObjectRegistrar<className> ULIB_REG_CONCAT(g_ObjectRegistrar_, __LINE__)(#className);
|
||||||
|
|
||||||
|
#define ULIB_REGISTER_OBJECT_NAME(className, registeredName) \
|
||||||
|
static uLib::ObjectRegistrar<className> ULIB_REG_CONCAT(g_ObjectRegistrar_, __LINE__)(registeredName);
|
||||||
|
|
||||||
|
} // namespace uLib
|
||||||
|
|
||||||
|
#endif // U_CORE_OBJECTFACTORY_H
|
||||||
63
src/Core/ObjectsContext.cpp
Normal file
63
src/Core/ObjectsContext.cpp
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
#include "Core/ObjectsContext.h"
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace uLib {
|
||||||
|
|
||||||
|
ObjectsContext::ObjectsContext() : Object() {}
|
||||||
|
|
||||||
|
ObjectsContext::~ObjectsContext() {}
|
||||||
|
|
||||||
|
void ObjectsContext::AddObject(Object* obj) {
|
||||||
|
if (obj && std::find(m_objects.begin(), m_objects.end(), obj) == m_objects.end()) {
|
||||||
|
m_objects.push_back(obj);
|
||||||
|
// Connect child's update to context's update to trigger re-renders
|
||||||
|
Object::connect(obj, &Object::Updated, this, &Object::Updated);
|
||||||
|
ULIB_SIGNAL_EMIT(ObjectsContext::ObjectAdded, obj);
|
||||||
|
this->Updated(); // Signal that the context has been updated
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ObjectsContext::RemoveObject(Object* obj) {
|
||||||
|
auto it = std::find(m_objects.begin(), m_objects.end(), obj);
|
||||||
|
if (it != m_objects.end()) {
|
||||||
|
Object* removedObj = *it;
|
||||||
|
m_objects.erase(it);
|
||||||
|
ULIB_SIGNAL_EMIT(ObjectsContext::ObjectRemoved, removedObj);
|
||||||
|
this->Updated(); // Signal that the context has been updated
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ObjectsContext::Clear() {
|
||||||
|
if (!m_objects.empty()) {
|
||||||
|
for (auto obj : m_objects) {
|
||||||
|
ULIB_SIGNAL_EMIT(ObjectsContext::ObjectRemoved, obj);
|
||||||
|
}
|
||||||
|
m_objects.clear();
|
||||||
|
this->Updated();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<Object*>& ObjectsContext::GetObjects() const {
|
||||||
|
return m_objects;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t ObjectsContext::GetCount() const {
|
||||||
|
return m_objects.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
Object* ObjectsContext::GetObject(size_t index) const {
|
||||||
|
if (index < m_objects.size()) {
|
||||||
|
return m_objects[index];
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ObjectsContext::ObjectAdded(Object* obj) {
|
||||||
|
ULIB_SIGNAL_EMIT(ObjectsContext::ObjectAdded, obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ObjectsContext::ObjectRemoved(Object* obj) {
|
||||||
|
ULIB_SIGNAL_EMIT(ObjectsContext::ObjectRemoved, obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace uLib
|
||||||
67
src/Core/ObjectsContext.h
Normal file
67
src/Core/ObjectsContext.h
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
#ifndef U_CORE_OBJECTS_CONTEXT_H
|
||||||
|
#define U_CORE_OBJECTS_CONTEXT_H
|
||||||
|
|
||||||
|
#include "Core/Object.h"
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace uLib {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief ObjectsContext represents a collection of Object instances.
|
||||||
|
*/
|
||||||
|
class ObjectsContext : public Object {
|
||||||
|
public:
|
||||||
|
ObjectsContext();
|
||||||
|
virtual ~ObjectsContext();
|
||||||
|
|
||||||
|
virtual const char * GetClassName() const { return "ObjectsContext"; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Adds an object to the context.
|
||||||
|
* @param obj Pointer to the object to add.
|
||||||
|
*/
|
||||||
|
virtual void AddObject(Object* obj);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Removes an object from the context.
|
||||||
|
* @param obj Pointer to the object to remove.
|
||||||
|
*/
|
||||||
|
virtual void RemoveObject(Object* obj);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Clears all objects from the context.
|
||||||
|
*/
|
||||||
|
void Clear();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the collection of objects.
|
||||||
|
* @return Const reference to the vector of object pointers.
|
||||||
|
*/
|
||||||
|
const std::vector<Object*>& GetObjects() const;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
/** @brief Signal emitted when an object is added. */
|
||||||
|
virtual void ObjectAdded(Object* obj);
|
||||||
|
/** @brief Signal emitted when an object is removed. */
|
||||||
|
virtual void ObjectRemoved(Object* obj);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the number of objects in the context.
|
||||||
|
* @return Size of the collection.
|
||||||
|
*/
|
||||||
|
size_t GetCount() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns an object by index.
|
||||||
|
* @param index The index of the object.
|
||||||
|
* @return Pointer to the object or nullptr if index is out of bounds.
|
||||||
|
*/
|
||||||
|
Object* GetObject(size_t index) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<Object*> m_objects;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace uLib
|
||||||
|
|
||||||
|
#endif // U_CORE_OBJECTS_CONTEXT_H
|
||||||
298
src/Core/Property.h
Normal file
298
src/Core/Property.h
Normal file
@@ -0,0 +1,298 @@
|
|||||||
|
#ifndef U_CORE_PROPERTY_H
|
||||||
|
#define U_CORE_PROPERTY_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <sstream>
|
||||||
|
#include <typeinfo>
|
||||||
|
#include <typeindex> // Added
|
||||||
|
#include <boost/serialization/nvp.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/Signal.h"
|
||||||
|
#include "Core/Object.h"
|
||||||
|
|
||||||
|
namespace uLib {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Base class for properties to allow runtime listing and identification.
|
||||||
|
*/
|
||||||
|
class PropertyBase : public Object {
|
||||||
|
public:
|
||||||
|
virtual ~PropertyBase() {}
|
||||||
|
virtual const std::string& GetName() const = 0;
|
||||||
|
virtual const char* GetTypeName() const = 0;
|
||||||
|
virtual std::string GetValueAsString() const = 0;
|
||||||
|
virtual std::type_index GetTypeIndex() const = 0; // Added
|
||||||
|
virtual const std::string& GetUnits() const = 0;
|
||||||
|
virtual void SetUnits(const std::string& units) = 0;
|
||||||
|
virtual const std::vector<std::string>& GetEnumLabels() const {
|
||||||
|
static std::vector<std::string> empty;
|
||||||
|
return empty;
|
||||||
|
}
|
||||||
|
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
|
||||||
|
signals:
|
||||||
|
virtual void Updated() override { ULIB_SIGNAL_EMIT(PropertyBase::Updated); }
|
||||||
|
|
||||||
|
// Serialization support for different uLib archives
|
||||||
|
virtual void serialize(Archive::xml_oarchive & ar, const unsigned int version) = 0;
|
||||||
|
virtual void serialize(Archive::xml_iarchive & ar, const unsigned int version) = 0;
|
||||||
|
virtual void serialize(Archive::text_oarchive & ar, const unsigned int version) = 0;
|
||||||
|
virtual void serialize(Archive::text_iarchive & ar, const unsigned int version) = 0;
|
||||||
|
virtual void serialize(Archive::hrt_oarchive & ar, const unsigned int version) = 0;
|
||||||
|
virtual void serialize(Archive::hrt_iarchive & ar, const unsigned int version) = 0;
|
||||||
|
virtual void serialize(Archive::log_archive & ar, const unsigned int version) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Template class for typed properties.
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
class Property : public PropertyBase {
|
||||||
|
public:
|
||||||
|
// PROXY: Use an existing variable as back-end storage
|
||||||
|
Property(Object* owner, const std::string& name, T* valuePtr, const std::string& units = "", const std::string& group = "")
|
||||||
|
: m_owner(owner), m_name(name), m_units(units), m_group(group), m_value(valuePtr), m_own(false) {
|
||||||
|
if (m_owner) {
|
||||||
|
m_owner->RegisterProperty(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MANAGED: Create and own internal storage
|
||||||
|
Property(Object* owner, const std::string& name, const T& defaultValue = T(), const std::string& units = "", const std::string& group = "")
|
||||||
|
: m_owner(owner), m_name(name), m_units(units), m_group(group), m_value(new T(defaultValue)), m_own(true) {
|
||||||
|
if (m_owner) {
|
||||||
|
m_owner->RegisterProperty(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~Property() {
|
||||||
|
if (m_own) delete m_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Identification
|
||||||
|
virtual const std::string& GetName() const override { return m_name; }
|
||||||
|
virtual const char* GetTypeName() const override { return typeid(T).name(); }
|
||||||
|
virtual std::type_index GetTypeIndex() const override { return std::type_index(typeid(T)); }
|
||||||
|
virtual const std::string& GetUnits() const override { return m_units; }
|
||||||
|
virtual void SetUnits(const std::string& units) override { m_units = units; }
|
||||||
|
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 {
|
||||||
|
try {
|
||||||
|
return boost::lexical_cast<std::string>(*m_value);
|
||||||
|
} catch (const boost::bad_lexical_cast&) {
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << *m_value;
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accessors
|
||||||
|
const T& Get() const { return *m_value; }
|
||||||
|
void Set(const T& value) {
|
||||||
|
if (*m_value != value) {
|
||||||
|
*m_value = value;
|
||||||
|
ULIB_SIGNAL_EMIT(Property<T>::PropertyChanged);
|
||||||
|
this->Updated();
|
||||||
|
if (m_owner) m_owner->Updated();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Operators for seamless usage
|
||||||
|
operator const T&() const { return *m_value; }
|
||||||
|
Property& operator=(const T& value) {
|
||||||
|
Set(value);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signals
|
||||||
|
signals:
|
||||||
|
virtual void PropertyChanged() { ULIB_SIGNAL_EMIT(Property<T>::PropertyChanged); }
|
||||||
|
|
||||||
|
// Serialization
|
||||||
|
template <class ArchiveT>
|
||||||
|
void serialize_impl(ArchiveT & ar, const unsigned int version) {
|
||||||
|
ar & boost::serialization::make_nvp(m_name.c_str(), *m_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void serialize(Archive::xml_oarchive & ar, const unsigned int v) override { serialize_impl(ar, v); }
|
||||||
|
void serialize(Archive::xml_iarchive & ar, const unsigned int v) override { serialize_impl(ar, v); }
|
||||||
|
void serialize(Archive::text_oarchive & ar, const unsigned int v) override { serialize_impl(ar, v); }
|
||||||
|
void serialize(Archive::text_iarchive & ar, const unsigned int v) override { serialize_impl(ar, v); }
|
||||||
|
void serialize(Archive::hrt_oarchive & ar, const unsigned int v) override { serialize_impl(ar, v); }
|
||||||
|
void serialize(Archive::hrt_iarchive & ar, const unsigned int v) override { serialize_impl(ar, v); }
|
||||||
|
void serialize(Archive::log_archive & ar, const unsigned int v) override { serialize_impl(ar, v); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string m_name;
|
||||||
|
std::string m_units;
|
||||||
|
std::string m_group;
|
||||||
|
T* m_value;
|
||||||
|
bool m_own;
|
||||||
|
Object* m_owner;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Conveninent typedefs for common property types.
|
||||||
|
*/
|
||||||
|
typedef Property<std::string> StringProperty;
|
||||||
|
typedef Property<int> IntProperty;
|
||||||
|
typedef Property<unsigned int> UIntProperty;
|
||||||
|
typedef Property<long> LongProperty;
|
||||||
|
typedef Property<unsigned long> ULongProperty;
|
||||||
|
typedef Property<float> FloatProperty;
|
||||||
|
typedef Property<double> DoubleProperty;
|
||||||
|
typedef Property<Bool_t> BoolProperty;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Property specialized for enumerations, providing labels for GUI representations.
|
||||||
|
*/
|
||||||
|
class EnumProperty : public Property<int> {
|
||||||
|
public:
|
||||||
|
EnumProperty(Object* owner, const std::string& name, int* valuePtr, const std::vector<std::string>& labels, const std::string& units = "", 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.
|
||||||
|
* Usage: ULIB_PROPERTY(float, Width, 1.0f)
|
||||||
|
* It creates a raw member m_Width and a Property proxy Width.
|
||||||
|
*/
|
||||||
|
#define ULIB_PROPERTY(type, name, defaultValue) \
|
||||||
|
type m_##name = defaultValue; \
|
||||||
|
Property<type> name = Property<type>(this, #name, &m_##name);
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace uLib
|
||||||
|
|
||||||
|
namespace uLib {
|
||||||
|
namespace Archive {
|
||||||
|
class property_register_archive;
|
||||||
|
} // namespace Archive
|
||||||
|
} // namespace uLib
|
||||||
|
|
||||||
|
namespace boost {
|
||||||
|
namespace archive {
|
||||||
|
namespace detail {
|
||||||
|
template <>
|
||||||
|
class interface_oarchive<uLib::Archive::property_register_archive>
|
||||||
|
: public uLib_interface_oarchive<uLib::Archive::property_register_archive> {};
|
||||||
|
} // namespace detail
|
||||||
|
} // namespace archive
|
||||||
|
} // namespace boost
|
||||||
|
|
||||||
|
namespace uLib {
|
||||||
|
namespace Archive {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A special archive that creates and registers Property proxies
|
||||||
|
* for any member it encounters wrapped in HRP().
|
||||||
|
*/
|
||||||
|
class property_register_archive :
|
||||||
|
public boost::archive::detail::common_oarchive<property_register_archive>
|
||||||
|
{
|
||||||
|
Object* m_Object;
|
||||||
|
public:
|
||||||
|
friend class boost::archive::detail::interface_oarchive<property_register_archive>;
|
||||||
|
friend class boost::archive::save_access;
|
||||||
|
|
||||||
|
typedef boost::archive::detail::common_oarchive<property_register_archive> detail_common_oarchive;
|
||||||
|
|
||||||
|
property_register_archive(Object* obj) :
|
||||||
|
boost::archive::detail::common_oarchive<property_register_archive>(boost::archive::no_header),
|
||||||
|
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
|
||||||
|
template<class T>
|
||||||
|
void save_override(const boost::serialization::hrp<T> &t) {
|
||||||
|
if (m_Object) {
|
||||||
|
Property<T>* p = new Property<T>(m_Object, t.name(), &const_cast<boost::serialization::hrp<T>&>(t).value(), t.units() ? t.units() : "", GetCurrentGroup());
|
||||||
|
m_Object->RegisterDynamicProperty(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
void save_override(const boost::serialization::hrp_enum<T> &t) {
|
||||||
|
if (m_Object) {
|
||||||
|
EnumProperty* p = new EnumProperty(m_Object, t.name(), (int*)&const_cast<boost::serialization::hrp_enum<T>&>(t).value(), t.labels(), t.units() ? t.units() : "", GetCurrentGroup());
|
||||||
|
m_Object->RegisterDynamicProperty(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle standard NVPs by recursing (important for base classes)
|
||||||
|
template<class T>
|
||||||
|
void save_override(const boost::serialization::nvp<T> &t) {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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_) {}
|
||||||
|
|
||||||
|
// Required attribute overrides for common_oarchive
|
||||||
|
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::version_type & t) {}
|
||||||
|
void save_override(const boost::archive::class_id_type & t) {}
|
||||||
|
void save_override(const boost::archive::class_id_optional_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::tracking_type & t) {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<std::string> m_GroupStack;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Convenience macro to automatically activate and register all HRP members
|
||||||
|
* as uLib properties. Usage: ULIB_ACTIVATE_PROPERTIES(obj)
|
||||||
|
*/
|
||||||
|
#define ULIB_ACTIVATE_PROPERTIES(obj) \
|
||||||
|
{ uLib::Archive::property_register_archive _ar_tmp(&(obj)); (obj).serialize(_ar_tmp, 0); }
|
||||||
|
|
||||||
|
} // namespace Archive
|
||||||
|
} // namespace uLib
|
||||||
|
|
||||||
|
#endif // U_CORE_PROPERTY_H
|
||||||
@@ -72,43 +72,74 @@ namespace serialization {
|
|||||||
template <class T> struct access2 {};
|
template <class T> struct access2 {};
|
||||||
|
|
||||||
// NON FUNZIONA ... SISTEMARE !!!! // ------------------------------------------
|
// NON FUNZIONA ... SISTEMARE !!!! // ------------------------------------------
|
||||||
template <class T> class hrp : public wrapper_traits<const hrp<T>> {
|
template <class T>
|
||||||
|
class hrp : public boost::serialization::wrapper_traits<hrp<T>> {
|
||||||
const char *m_name;
|
const char *m_name;
|
||||||
T *m_value;
|
const char *m_units;
|
||||||
std::string *m_str;
|
T &m_value;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit hrp(const char *name_, T &t)
|
explicit hrp(const char *name_, T &t, const char* units_ = nullptr) : m_name(name_), m_units(units_), m_value(t) {}
|
||||||
: m_str(new std::string), m_name(name_), 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; }
|
||||||
|
const T &const_value() const { return this->m_value; }
|
||||||
|
|
||||||
|
BOOST_SERIALIZATION_SPLIT_MEMBER()
|
||||||
|
|
||||||
template <class Archivex>
|
template <class Archivex>
|
||||||
void save(Archivex &ar, const unsigned int /* file_version */) const {
|
void save(Archivex &ar, const unsigned int /* version */) const {
|
||||||
//// ar.operator<<(const_value());
|
ar << boost::serialization::make_nvp(m_name, m_value);
|
||||||
// std::stringstream ss;
|
|
||||||
// uLib::Archive::hrt_oarchive har(ss);
|
|
||||||
// har << make_nvp(m_name,*m_value);
|
|
||||||
// // (*m_str) = ss.str();
|
|
||||||
//// ar.operator << (make_nvp(m_name, ss.str());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class Archivex>
|
template <class Archivex>
|
||||||
void load(Archivex &ar, const unsigned int /* file_version */) {
|
void load(Archivex &ar, const unsigned int /* version */) {
|
||||||
// ar.operator>>(value());
|
ar >> boost::serialization::make_nvp(m_name, m_value);
|
||||||
}
|
}
|
||||||
BOOST_SERIALIZATION_SPLIT_MEMBER()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
inline
|
inline hrp<T> make_hrp(const char *name, T &t, const char* units = nullptr) {
|
||||||
#ifndef BOOST_NO_FUNCTION_TEMPLATE_ORDERING
|
return hrp<T>(name, t, units);
|
||||||
const
|
}
|
||||||
#endif
|
|
||||||
hrp<T> make_hrp(const char *name, T &t) {
|
template <class T>
|
||||||
return hrp<T>(name, 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
|
||||||
|
|||||||
@@ -31,6 +31,8 @@
|
|||||||
#include <boost/signals2/signal.hpp>
|
#include <boost/signals2/signal.hpp>
|
||||||
#include <boost/signals2/signal_type.hpp>
|
#include <boost/signals2/signal_type.hpp>
|
||||||
#include <boost/signals2/slot.hpp>
|
#include <boost/signals2/slot.hpp>
|
||||||
|
#include <boost/signals2/connection.hpp>
|
||||||
|
#include <boost/signals2/shared_connection_block.hpp>
|
||||||
|
|
||||||
#include "Function.h"
|
#include "Function.h"
|
||||||
#include <boost/bind/bind.hpp>
|
#include <boost/bind/bind.hpp>
|
||||||
@@ -43,17 +45,31 @@ using namespace boost::placeholders;
|
|||||||
// Signals macro //
|
// Signals macro //
|
||||||
|
|
||||||
#define default(vlaue)
|
#define default(vlaue)
|
||||||
#define slots
|
#ifndef Q_MOC_RUN
|
||||||
|
#ifndef signals
|
||||||
#define signals /*virtual void init_signals();*/ public
|
#define signals /*virtual void init_signals();*/ public
|
||||||
|
#endif
|
||||||
|
#ifndef slots
|
||||||
|
#define slots
|
||||||
|
#endif
|
||||||
|
#ifndef emit
|
||||||
#define emit
|
#define emit
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
#ifndef SLOT
|
||||||
#define SLOT(a) BOOST_STRINGIZE(a)
|
#define SLOT(a) BOOST_STRINGIZE(a)
|
||||||
|
#endif
|
||||||
|
#ifndef SIGNAL
|
||||||
#define SIGNAL(a) BOOST_STRINGIZE(a)
|
#define SIGNAL(a) BOOST_STRINGIZE(a)
|
||||||
|
#endif
|
||||||
|
|
||||||
#define _ULIB_DETAIL_SIGNAL_EMIT(_name, ...) \
|
#define _ULIB_DETAIL_SIGNAL_EMIT(_name, ...) \
|
||||||
do { \
|
do { \
|
||||||
BOOST_AUTO(sig, this->findOrAddSignal(&_name)); \
|
if (!this->signalsBlocked()) { \
|
||||||
if (sig) \
|
BOOST_AUTO(sig, this->findOrAddSignal(&_name)); \
|
||||||
sig->operator()(__VA_ARGS__); \
|
if (sig) \
|
||||||
|
sig->operator()(__VA_ARGS__); \
|
||||||
|
} \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -78,6 +94,7 @@ namespace uLib {
|
|||||||
// TODO ...
|
// TODO ...
|
||||||
|
|
||||||
typedef boost::signals2::signal_base SignalBase;
|
typedef boost::signals2::signal_base SignalBase;
|
||||||
|
typedef boost::signals2::connection Connection;
|
||||||
|
|
||||||
template <typename T> struct Signal {
|
template <typename T> struct Signal {
|
||||||
typedef boost::signals2::signal<T> type;
|
typedef boost::signals2::signal<T> type;
|
||||||
@@ -92,57 +109,57 @@ struct ConnectSignal {};
|
|||||||
|
|
||||||
template <typename FuncT, typename SigSignature>
|
template <typename FuncT, typename SigSignature>
|
||||||
struct ConnectSignal<FuncT, SigSignature, 0> {
|
struct ConnectSignal<FuncT, SigSignature, 0> {
|
||||||
static void connect(SignalBase *sigb, FuncT slof,
|
static Connection connect(SignalBase *sigb, FuncT slof,
|
||||||
typename FunctionPointer<FuncT>::Object *receiver) {
|
typename FunctionPointer<FuncT>::Object *receiver) {
|
||||||
typedef typename Signal<SigSignature>::type SigT;
|
typedef typename Signal<SigSignature>::type SigT;
|
||||||
reinterpret_cast<SigT *>(sigb)->connect(slof);
|
return reinterpret_cast<SigT *>(sigb)->connect(slof);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename FuncT, typename SigSignature>
|
template <typename FuncT, typename SigSignature>
|
||||||
struct ConnectSignal<FuncT, SigSignature, 1> {
|
struct ConnectSignal<FuncT, SigSignature, 1> {
|
||||||
static void connect(SignalBase *sigb, FuncT slof,
|
static Connection connect(SignalBase *sigb, FuncT slof,
|
||||||
typename FunctionPointer<FuncT>::Object *receiver) {
|
typename FunctionPointer<FuncT>::Object *receiver) {
|
||||||
typedef typename Signal<SigSignature>::type SigT;
|
typedef typename Signal<SigSignature>::type SigT;
|
||||||
reinterpret_cast<SigT *>(sigb)->connect(boost::bind(slof, receiver));
|
return reinterpret_cast<SigT *>(sigb)->connect(boost::bind(slof, receiver));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename FuncT, typename SigSignature>
|
template <typename FuncT, typename SigSignature>
|
||||||
struct ConnectSignal<FuncT, SigSignature, 2> {
|
struct ConnectSignal<FuncT, SigSignature, 2> {
|
||||||
static void connect(SignalBase *sigb, FuncT slof,
|
static Connection connect(SignalBase *sigb, FuncT slof,
|
||||||
typename FunctionPointer<FuncT>::Object *receiver) {
|
typename FunctionPointer<FuncT>::Object *receiver) {
|
||||||
typedef typename Signal<SigSignature>::type SigT;
|
typedef typename Signal<SigSignature>::type SigT;
|
||||||
reinterpret_cast<SigT *>(sigb)->connect(boost::bind(slof, receiver, _1));
|
return reinterpret_cast<SigT *>(sigb)->connect(boost::bind(slof, receiver, _1));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename FuncT, typename SigSignature>
|
template <typename FuncT, typename SigSignature>
|
||||||
struct ConnectSignal<FuncT, SigSignature, 3> {
|
struct ConnectSignal<FuncT, SigSignature, 3> {
|
||||||
static void connect(SignalBase *sigb, FuncT slof,
|
static Connection connect(SignalBase *sigb, FuncT slof,
|
||||||
typename FunctionPointer<FuncT>::Object *receiver) {
|
typename FunctionPointer<FuncT>::Object *receiver) {
|
||||||
typedef typename Signal<SigSignature>::type SigT;
|
typedef typename Signal<SigSignature>::type SigT;
|
||||||
reinterpret_cast<SigT *>(sigb)->connect(
|
return reinterpret_cast<SigT *>(sigb)->connect(
|
||||||
boost::bind(slof, receiver, _1, _2));
|
boost::bind(slof, receiver, _1, _2));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename FuncT, typename SigSignature>
|
template <typename FuncT, typename SigSignature>
|
||||||
struct ConnectSignal<FuncT, SigSignature, 4> {
|
struct ConnectSignal<FuncT, SigSignature, 4> {
|
||||||
static void connect(SignalBase *sigb, FuncT slof,
|
static Connection connect(SignalBase *sigb, FuncT slof,
|
||||||
typename FunctionPointer<FuncT>::Object *receiver) {
|
typename FunctionPointer<FuncT>::Object *receiver) {
|
||||||
typedef typename Signal<SigSignature>::type SigT;
|
typedef typename Signal<SigSignature>::type SigT;
|
||||||
reinterpret_cast<SigT *>(sigb)->connect(
|
return reinterpret_cast<SigT *>(sigb)->connect(
|
||||||
boost::bind(slof, receiver, _1, _2, _3));
|
boost::bind(slof, receiver, _1, _2, _3));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename FuncT, typename SigSignature>
|
template <typename FuncT, typename SigSignature>
|
||||||
struct ConnectSignal<FuncT, SigSignature, 5> {
|
struct ConnectSignal<FuncT, SigSignature, 5> {
|
||||||
static void connect(SignalBase *sigb, FuncT slof,
|
static Connection connect(SignalBase *sigb, FuncT slof,
|
||||||
typename FunctionPointer<FuncT>::Object *receiver) {
|
typename FunctionPointer<FuncT>::Object *receiver) {
|
||||||
typedef typename Signal<SigSignature>::type SigT;
|
typedef typename Signal<SigSignature>::type SigT;
|
||||||
reinterpret_cast<SigT *>(sigb)->connect(
|
return reinterpret_cast<SigT *>(sigb)->connect(
|
||||||
boost::bind(slof, receiver, _1, _2, _3, _4));
|
boost::bind(slof, receiver, _1, _2, _3, _4));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -155,11 +172,11 @@ template <typename FuncT> SignalBase *NewSignal(FuncT f) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename SigSignature, typename FuncT>
|
template <typename SigSignature, typename FuncT>
|
||||||
void ConnectSignal(SignalBase *sigb, FuncT slof,
|
Connection ConnectSignal(SignalBase *sigb, FuncT slof,
|
||||||
typename FunctionPointer<FuncT>::Object *receiver) {
|
typename FunctionPointer<FuncT>::Object *receiver) {
|
||||||
detail::ConnectSignal<FuncT, SigSignature,
|
return detail::ConnectSignal<FuncT, SigSignature,
|
||||||
FunctionPointer<FuncT>::arity>::connect(sigb, slof,
|
FunctionPointer<FuncT>::arity>::connect(sigb, slof,
|
||||||
receiver);
|
receiver);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace uLib
|
} // namespace uLib
|
||||||
|
|||||||
202
src/Core/Threads.cpp
Normal file
202
src/Core/Threads.cpp
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
/*//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// 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 "Threads.h"
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
#ifdef _OPENMP
|
||||||
|
#include <omp.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __linux__
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <sched.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace uLib {
|
||||||
|
|
||||||
|
Thread::Thread() : m_Running(false) {}
|
||||||
|
|
||||||
|
Thread::~Thread() {
|
||||||
|
if (m_Thread.joinable()) {
|
||||||
|
m_Thread.detach();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Thread::Start() {
|
||||||
|
Mutex::ScopedLock lock(m_ThreadMutex);
|
||||||
|
if (m_Running) return;
|
||||||
|
|
||||||
|
m_Running = true;
|
||||||
|
m_Thread = std::thread(&Thread::ThreadEntryPoint, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Thread::Join() {
|
||||||
|
if (m_Thread.joinable()) {
|
||||||
|
m_Thread.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Thread::Detach() {
|
||||||
|
if (m_Thread.joinable()) {
|
||||||
|
m_Thread.detach();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Thread::IsJoinable() const {
|
||||||
|
return m_Thread.joinable();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Thread::IsRunning() const {
|
||||||
|
return m_Running;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Thread::Run() {
|
||||||
|
// Override in subclasses
|
||||||
|
}
|
||||||
|
|
||||||
|
void Thread::ThreadEntryPoint() {
|
||||||
|
this->Run();
|
||||||
|
m_Running = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Thread::Sleep(int milliseconds) {
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Thread::Yield() {
|
||||||
|
std::this_thread::yield();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Thread::SetAffinity(int cpu) {
|
||||||
|
#ifdef __linux__
|
||||||
|
if (m_Thread.joinable()) {
|
||||||
|
cpu_set_t cpuset;
|
||||||
|
CPU_ZERO(&cpuset);
|
||||||
|
CPU_SET(cpu, &cpuset);
|
||||||
|
pthread_setaffinity_np(m_Thread.native_handle(), sizeof(cpu_set_t), &cpuset);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void Thread::SetAffinity(const std::vector<int>& cpus) {
|
||||||
|
#ifdef __linux__
|
||||||
|
if (m_Thread.joinable()) {
|
||||||
|
cpu_set_t cpuset;
|
||||||
|
CPU_ZERO(&cpuset);
|
||||||
|
for (int cpu : cpus) {
|
||||||
|
CPU_SET(cpu, &cpuset);
|
||||||
|
}
|
||||||
|
pthread_setaffinity_np(m_Thread.native_handle(), sizeof(cpu_set_t), &cpuset);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void Thread::SetNumThreads(int n) {
|
||||||
|
#ifdef _OPENMP
|
||||||
|
omp_set_num_threads(n);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
int Thread::GetNumThreads() {
|
||||||
|
#ifdef _OPENMP
|
||||||
|
return omp_get_max_threads();
|
||||||
|
#else
|
||||||
|
return 1;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
int Thread::GetThreadNum() {
|
||||||
|
#ifdef _OPENMP
|
||||||
|
return omp_get_thread_num();
|
||||||
|
#else
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// Team Implementation //
|
||||||
|
|
||||||
|
Team::Team(int num_threads) : m_Size(num_threads), m_UseOpenMP(false) {
|
||||||
|
#ifdef _OPENMP
|
||||||
|
m_UseOpenMP = true;
|
||||||
|
if (m_Size > 0) omp_set_num_threads(m_Size);
|
||||||
|
else m_Size = omp_get_max_threads();
|
||||||
|
#else
|
||||||
|
if (m_Size <= 0) m_Size = 1;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
Team::~Team() {
|
||||||
|
Wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Team::Run(Task* task) {
|
||||||
|
if (!task) return;
|
||||||
|
#ifdef _OPENMP
|
||||||
|
if (m_UseOpenMP) {
|
||||||
|
#pragma omp task
|
||||||
|
task->Execute();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
// Fallback to synchronous execution if no OpenMP
|
||||||
|
task->Execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Team::Wait() {
|
||||||
|
#ifdef _OPENMP
|
||||||
|
if (m_UseOpenMP) {
|
||||||
|
#pragma omp taskwait
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void Team::SetSize(int n) {
|
||||||
|
m_Size = n;
|
||||||
|
#ifdef _OPENMP
|
||||||
|
if (m_UseOpenMP) omp_set_num_threads(m_Size);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void Team::SetAffinity(const std::vector<int>& cpus) {
|
||||||
|
if (cpus.empty()) return;
|
||||||
|
#ifdef __linux__
|
||||||
|
#ifdef _OPENMP
|
||||||
|
if (m_UseOpenMP) {
|
||||||
|
#pragma omp parallel
|
||||||
|
{
|
||||||
|
int tid = omp_get_thread_num();
|
||||||
|
int cpu = cpus[tid % cpus.size()];
|
||||||
|
cpu_set_t cpuset;
|
||||||
|
CPU_ZERO(&cpuset);
|
||||||
|
CPU_SET(cpu, &cpuset);
|
||||||
|
pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace uLib
|
||||||
147
src/Core/Threads.h
Normal file
147
src/Core/Threads.h
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
/*//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// 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_THREADS_H
|
||||||
|
#define U_CORE_THREADS_H
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
|
#include <functional>
|
||||||
|
#include <atomic>
|
||||||
|
#include <vector>
|
||||||
|
#include <deque>
|
||||||
|
#include "Core/Monitor.h"
|
||||||
|
#include "Core/Object.h"
|
||||||
|
|
||||||
|
namespace uLib {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Thread class wraps std::thread and provides a common interface.
|
||||||
|
*/
|
||||||
|
class Thread : public Object {
|
||||||
|
public:
|
||||||
|
Thread();
|
||||||
|
virtual ~Thread();
|
||||||
|
|
||||||
|
/** @brief Starts the thread by calling Run(). */
|
||||||
|
void Start();
|
||||||
|
|
||||||
|
/** @brief Joins the thread. */
|
||||||
|
void Join();
|
||||||
|
|
||||||
|
/** @brief Detaches the thread. */
|
||||||
|
void Detach();
|
||||||
|
|
||||||
|
/** @brief Returns true if the thread is currently joinable. */
|
||||||
|
bool IsJoinable() const;
|
||||||
|
|
||||||
|
/** @brief Returns true if the thread is currently running. */
|
||||||
|
bool IsRunning() const;
|
||||||
|
|
||||||
|
/** @brief The entry point for the thread. Override this in subclasses. */
|
||||||
|
virtual void Run();
|
||||||
|
|
||||||
|
/** @brief Static helper to sleep the current thread. */
|
||||||
|
static void Sleep(int milliseconds);
|
||||||
|
|
||||||
|
/** @brief Static helper to yield the current thread. */
|
||||||
|
static void Yield();
|
||||||
|
|
||||||
|
/** @brief Returns the native handle of the thread. */
|
||||||
|
std::thread::native_handle_type GetNativeHandle() { return m_Thread.native_handle(); }
|
||||||
|
|
||||||
|
/** @brief Sets CPU affinity for the thread. (Linux only) */
|
||||||
|
void SetAffinity(int cpu);
|
||||||
|
|
||||||
|
/** @brief Sets CPU affinity for the thread using a list of CPUs. (Linux only) */
|
||||||
|
void SetAffinity(const std::vector<int>& cpus);
|
||||||
|
|
||||||
|
// OpenMP Support //
|
||||||
|
|
||||||
|
/** @brief Sets the number of threads for OpenMP parallel regions. */
|
||||||
|
static void SetNumThreads(int n);
|
||||||
|
|
||||||
|
/** @brief Returns the number of threads for OpenMP parallel regions. */
|
||||||
|
static int GetNumThreads();
|
||||||
|
|
||||||
|
/** @brief Returns the ID of the current thread in an OpenMP parallel region. */
|
||||||
|
static int GetThreadNum();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// Internal thread entry point
|
||||||
|
void ThreadEntryPoint();
|
||||||
|
|
||||||
|
std::thread m_Thread;
|
||||||
|
std::atomic<bool> m_Running;
|
||||||
|
mutable Mutex m_ThreadMutex;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Task class wraps a function call to be executed by a Team.
|
||||||
|
*/
|
||||||
|
class Task : public Object {
|
||||||
|
public:
|
||||||
|
Task(std::function<void()> func) : m_Func(func) {}
|
||||||
|
virtual ~Task() = default;
|
||||||
|
|
||||||
|
/** @brief Executes the task. */
|
||||||
|
virtual void Execute() { if (m_Func) m_Func(); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::function<void()> m_Func;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Team class manages a group of threads and can execute Tasks.
|
||||||
|
* This is designed to be compatible with OpenMP tasks and teams.
|
||||||
|
*/
|
||||||
|
class Team : public Object {
|
||||||
|
public:
|
||||||
|
Team(int num_threads = -1);
|
||||||
|
virtual ~Team();
|
||||||
|
|
||||||
|
/** @brief Runs a task within the team. Uses OpenMP task if available. */
|
||||||
|
void Run(Task* task);
|
||||||
|
|
||||||
|
/** @brief Waits for all tasks in the team to finish. */
|
||||||
|
void Wait();
|
||||||
|
|
||||||
|
/** @brief Sets the number of threads for this team. */
|
||||||
|
void SetSize(int n);
|
||||||
|
|
||||||
|
/** @brief Returns the number of threads in the team. */
|
||||||
|
int GetSize() const { return m_Size; }
|
||||||
|
|
||||||
|
/** @brief Sets CPU affinity for all threads in the team. */
|
||||||
|
void SetAffinity(const std::vector<int>& cpus);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
int m_Size;
|
||||||
|
bool m_UseOpenMP;
|
||||||
|
std::vector<Thread*> m_Threads;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace uLib
|
||||||
|
|
||||||
|
#endif // U_CORE_THREADS_H
|
||||||
@@ -139,6 +139,7 @@ typedef id_t Id_t;
|
|||||||
typedef void *Pointer_t;
|
typedef void *Pointer_t;
|
||||||
typedef bool Bool_t; // Boolean (0=false, 1=true) (bool)
|
typedef bool Bool_t; // Boolean (0=false, 1=true) (bool)
|
||||||
|
|
||||||
|
|
||||||
//--- bit manipulation ---------------------------------------------------------
|
//--- bit manipulation ---------------------------------------------------------
|
||||||
#ifndef BIT
|
#ifndef BIT
|
||||||
#define BIT(n) (1ULL << (n))
|
#define BIT(n) (1ULL << (n))
|
||||||
|
|||||||
65
src/Core/testing/AffinityTest.cpp
Normal file
65
src/Core/testing/AffinityTest.cpp
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
#include "Core/Threads.h"
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
#ifdef __linux__
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <sched.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
using namespace uLib;
|
||||||
|
|
||||||
|
void TestThreadAffinity() {
|
||||||
|
std::cout << "Testing Thread Affinity..." << std::endl;
|
||||||
|
#ifdef __linux__
|
||||||
|
Thread t;
|
||||||
|
t.Start();
|
||||||
|
t.SetAffinity(0); // Bind to CPU 0
|
||||||
|
|
||||||
|
cpu_set_t cpuset;
|
||||||
|
CPU_ZERO(&cpuset);
|
||||||
|
pthread_getaffinity_np(t.GetNativeHandle(), sizeof(cpu_set_t), &cpuset);
|
||||||
|
assert(CPU_ISSET(0, &cpuset));
|
||||||
|
|
||||||
|
t.Join();
|
||||||
|
std::cout << " Passed (Thread bound to CPU 0)." << std::endl;
|
||||||
|
#else
|
||||||
|
std::cout << " Affinity not supported on this OS, skipping." << std::endl;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestTeamAffinity() {
|
||||||
|
std::cout << "Testing Team Affinity..." << std::endl;
|
||||||
|
#ifdef __linux__
|
||||||
|
#ifdef _OPENMP
|
||||||
|
Team team(2);
|
||||||
|
std::vector<int> cpus = {0, 1};
|
||||||
|
team.SetAffinity(cpus);
|
||||||
|
|
||||||
|
// We check affinity inside a parallel region
|
||||||
|
#pragma omp parallel
|
||||||
|
{
|
||||||
|
cpu_set_t cpuset;
|
||||||
|
CPU_ZERO(&cpuset);
|
||||||
|
pthread_getaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset);
|
||||||
|
int tid = Thread::GetThreadNum();
|
||||||
|
int expected_cpu = cpus[tid % cpus.size()];
|
||||||
|
assert(CPU_ISSET(expected_cpu, &cpuset));
|
||||||
|
}
|
||||||
|
std::cout << " Passed (Team threads bound correctly)." << std::endl;
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
std::cout << " Affinity not supported on this OS, skipping." << std::endl;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to get native handle if needed (oops, I forgot to add it to Thread class)
|
||||||
|
// I'll add GetNativeHandle() to Thread class in Threads.h
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
TestThreadAffinity();
|
||||||
|
TestTeamAffinity();
|
||||||
|
std::cout << "All Affinity tests finished!" << std::endl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
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;
|
||||||
|
}
|
||||||
@@ -21,6 +21,15 @@ set( TESTS
|
|||||||
OptionsTest
|
OptionsTest
|
||||||
PingPongTest
|
PingPongTest
|
||||||
VectorMetaAllocatorTest
|
VectorMetaAllocatorTest
|
||||||
|
PropertyTypesTest
|
||||||
|
HRPTest
|
||||||
|
PropertyGroupingTest
|
||||||
|
MutexTest
|
||||||
|
ThreadsTest
|
||||||
|
OpenMPTest
|
||||||
|
TeamTest
|
||||||
|
AffinityTest
|
||||||
|
AlgorithmTest
|
||||||
)
|
)
|
||||||
|
|
||||||
set(LIBRARIES
|
set(LIBRARIES
|
||||||
@@ -29,6 +38,7 @@ set(LIBRARIES
|
|||||||
Boost::serialization
|
Boost::serialization
|
||||||
Boost::program_options
|
Boost::program_options
|
||||||
${ROOT_LIBRARIES}
|
${ROOT_LIBRARIES}
|
||||||
|
OpenMP::OpenMP_CXX
|
||||||
)
|
)
|
||||||
uLib_add_tests(Core)
|
uLib_add_tests(Core)
|
||||||
|
|
||||||
|
|||||||
83
src/Core/testing/HRPTest.cpp
Normal file
83
src/Core/testing/HRPTest.cpp
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
#include "Core/Object.h"
|
||||||
|
#include "Core/Property.h"
|
||||||
|
#include "Core/Serializable.h"
|
||||||
|
#include "Core/Archives.h"
|
||||||
|
|
||||||
|
using namespace uLib;
|
||||||
|
|
||||||
|
struct SimpleObject {
|
||||||
|
int value;
|
||||||
|
std::string name;
|
||||||
|
|
||||||
|
template<class Archive>
|
||||||
|
void serialize(Archive & ar, const unsigned int version) {
|
||||||
|
ar & HRP(value);
|
||||||
|
ar & HRP(name);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DynamicObject : public Object {
|
||||||
|
float width;
|
||||||
|
int height;
|
||||||
|
|
||||||
|
DynamicObject() : width(10.0f), height(20) {
|
||||||
|
// Automatic registration of properties based on serialize/HRP
|
||||||
|
ULIB_ACTIVATE_PROPERTIES(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Archive>
|
||||||
|
void serialize(Archive & ar, const unsigned int version) {
|
||||||
|
ar & HRP(width);
|
||||||
|
ar & HRP(height);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
SimpleObject obj;
|
||||||
|
obj.value = 42;
|
||||||
|
obj.name = "TestObject";
|
||||||
|
|
||||||
|
std::cout << "Testing HRP Serialization to Log..." << std::endl;
|
||||||
|
std::stringstream ss;
|
||||||
|
{
|
||||||
|
uLib::Archive::log_archive ar(ss);
|
||||||
|
ar << boost::serialization::make_nvp("Object", obj);
|
||||||
|
}
|
||||||
|
std::cout << ss.str() << std::endl;
|
||||||
|
|
||||||
|
std::cout << "Testing HRP Serialization to HRT..." << std::endl;
|
||||||
|
ss.str("");
|
||||||
|
{
|
||||||
|
uLib::Archive::hrt_oarchive ar(ss);
|
||||||
|
ar << obj;
|
||||||
|
}
|
||||||
|
std::cout << ss.str() << std::endl;
|
||||||
|
|
||||||
|
std::cout << "Testing HRP Serialization to XML..." << std::endl;
|
||||||
|
ss.str("");
|
||||||
|
{
|
||||||
|
uLib::Archive::xml_oarchive ar(ss);
|
||||||
|
ar << boost::serialization::make_nvp("Object", obj);
|
||||||
|
}
|
||||||
|
std::cout << ss.str() << std::endl;
|
||||||
|
|
||||||
|
std::cout << "Testing Dynamic Property Creation via ULIB_ACTIVATE_PROPERTIES macro..." << std::endl;
|
||||||
|
DynamicObject dynObj;
|
||||||
|
// (properties were already created in DynamicObject constructor via macro)
|
||||||
|
|
||||||
|
std::cout << "Registered Properties in dynObj:" << std::endl;
|
||||||
|
const auto& props = dynObj.GetProperties();
|
||||||
|
for (auto* p : props) {
|
||||||
|
std::cout << " - [" << p->GetTypeName() << "] " << p->GetName() << " = " << p->GetValueAsString() << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.size() == 2) {
|
||||||
|
std::cout << "Dynamic Property Creation SUCCESS!" << std::endl;
|
||||||
|
} else {
|
||||||
|
std::cout << "Dynamic Property Creation FAILED (Expected 2, got " << props.size() << ")" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
109
src/Core/testing/MutexTest.cpp
Normal file
109
src/Core/testing/MutexTest.cpp
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
#include "Core/Monitor.h"
|
||||||
|
#include <iostream>
|
||||||
|
#include <chrono>
|
||||||
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
using namespace uLib;
|
||||||
|
|
||||||
|
void TestBasicLock() {
|
||||||
|
std::cout << "Testing basic Mutex Lock/Unlock..." << std::endl;
|
||||||
|
Mutex m;
|
||||||
|
m.Lock();
|
||||||
|
m.Unlock();
|
||||||
|
assert(m.TryLock());
|
||||||
|
m.Unlock();
|
||||||
|
std::cout << " Passed." << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestScopedLock() {
|
||||||
|
std::cout << "Testing Mutex::ScopedLock..." << std::endl;
|
||||||
|
Mutex m;
|
||||||
|
{
|
||||||
|
Mutex::ScopedLock lock(m);
|
||||||
|
assert(!m.TryLock());
|
||||||
|
}
|
||||||
|
assert(m.TryLock());
|
||||||
|
m.Unlock();
|
||||||
|
std::cout << " Passed." << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestTimedLock() {
|
||||||
|
std::cout << "Testing Mutex TryLockFor..." << std::endl;
|
||||||
|
Mutex m;
|
||||||
|
m.Lock();
|
||||||
|
auto start = std::chrono::steady_clock::now();
|
||||||
|
bool locked = m.TryLockFor(100);
|
||||||
|
auto end = std::chrono::steady_clock::now();
|
||||||
|
auto diff = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
|
||||||
|
|
||||||
|
assert(!locked);
|
||||||
|
assert(diff >= 100);
|
||||||
|
m.Unlock();
|
||||||
|
std::cout << " Passed (waited " << diff << "ms)." << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestMacros() {
|
||||||
|
std::cout << "Testing ULIB_STATIC_LOCK and ULIB_MUTEX_LOCK macros..." << std::endl;
|
||||||
|
|
||||||
|
int counter = 0;
|
||||||
|
auto task = [&]() {
|
||||||
|
for(int i=0; i<500; ++i) {
|
||||||
|
ULIB_STATIC_LOCK(-1) {
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::thread> threads;
|
||||||
|
for(int i=0; i<4; ++i) threads.emplace_back(task);
|
||||||
|
for(auto& t : threads) t.join();
|
||||||
|
|
||||||
|
assert(counter == 2000);
|
||||||
|
|
||||||
|
Mutex m;
|
||||||
|
int counter2 = 0;
|
||||||
|
ULIB_MUTEX_LOCK(m, -1) {
|
||||||
|
counter2++;
|
||||||
|
}
|
||||||
|
assert(counter2 == 1);
|
||||||
|
|
||||||
|
std::cout << " Passed." << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestMonitor() {
|
||||||
|
std::cout << "Testing Monitor pattern..." << std::endl;
|
||||||
|
struct Resource {
|
||||||
|
int value = 0;
|
||||||
|
void increment() { value++; }
|
||||||
|
};
|
||||||
|
|
||||||
|
Monitor<Resource> monitor(new Resource());
|
||||||
|
|
||||||
|
auto task = [&]() {
|
||||||
|
for(int i=0; i<1000; ++i) {
|
||||||
|
monitor.Access([](Resource& r) {
|
||||||
|
r.increment();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::thread> threads;
|
||||||
|
for(int i=0; i<5; ++i) threads.emplace_back(task);
|
||||||
|
for(auto& t : threads) t.join();
|
||||||
|
|
||||||
|
int final_value = monitor.Access([](Resource& r) { return r.value; });
|
||||||
|
assert(final_value == 5000);
|
||||||
|
std::cout << " Passed (final value: " << final_value << ")." << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
TestBasicLock();
|
||||||
|
TestScopedLock();
|
||||||
|
TestTimedLock();
|
||||||
|
TestMacros();
|
||||||
|
TestMonitor();
|
||||||
|
std::cout << "All Mutex and Monitor tests passed!" << std::endl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
47
src/Core/testing/OpenMPTest.cpp
Normal file
47
src/Core/testing/OpenMPTest.cpp
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
#include "Core/Threads.h"
|
||||||
|
#include <iostream>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
#ifdef _OPENMP
|
||||||
|
#include <omp.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
using namespace uLib;
|
||||||
|
|
||||||
|
class OpenMPThread : public Thread {
|
||||||
|
public:
|
||||||
|
void Run() override {
|
||||||
|
#ifdef _OPENMP
|
||||||
|
Thread::SetNumThreads(2);
|
||||||
|
int max = Thread::GetNumThreads();
|
||||||
|
std::cout << " OpenMP max threads in uLib::Thread: " << max << std::endl;
|
||||||
|
|
||||||
|
int shared_counter = 0;
|
||||||
|
#pragma omp parallel reduction(+:shared_counter)
|
||||||
|
{
|
||||||
|
shared_counter += 1;
|
||||||
|
}
|
||||||
|
std::cout << " Parallel region executed with " << shared_counter << " threads." << std::endl;
|
||||||
|
assert(shared_counter <= max);
|
||||||
|
#else
|
||||||
|
std::cout << " OpenMP not available, skipping parallel check." << std::endl;
|
||||||
|
assert(Thread::GetNumThreads() == 1);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
std::cout << "Testing OpenMP compatibility..." << std::endl;
|
||||||
|
#ifdef _OPENMP
|
||||||
|
std::cout << " OpenMP is AVAILABLE." << std::endl;
|
||||||
|
#else
|
||||||
|
std::cout << " OpenMP is NOT available." << std::endl;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
OpenMPThread t;
|
||||||
|
t.Start();
|
||||||
|
t.Join();
|
||||||
|
|
||||||
|
std::cout << "OpenMP compatibility test finished!" << std::endl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
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;
|
||||||
|
}
|
||||||
64
src/Core/testing/PropertySystemTest.cpp
Normal file
64
src/Core/testing/PropertySystemTest.cpp
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
#include "Core/Object.h"
|
||||||
|
#include "Core/Property.h"
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
using namespace uLib;
|
||||||
|
|
||||||
|
class TestObject : public Object {
|
||||||
|
public:
|
||||||
|
TestObject() : Object(),
|
||||||
|
IntProp(this, "IntProp", 10),
|
||||||
|
StringProp(this, "StringProp", "Initial")
|
||||||
|
{}
|
||||||
|
|
||||||
|
virtual const char* GetClassName() const override { return "TestObject"; }
|
||||||
|
|
||||||
|
Property<int> IntProp;
|
||||||
|
Property<std::string> StringProp;
|
||||||
|
};
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
TestObject obj;
|
||||||
|
|
||||||
|
std::cout << "Testing Properties..." << std::endl;
|
||||||
|
|
||||||
|
// 1. Check registration
|
||||||
|
const auto& props = obj.GetProperties();
|
||||||
|
assert(props.size() == 2);
|
||||||
|
assert(props[0]->GetName() == "IntProp");
|
||||||
|
assert(props[1]->GetName() == "StringProp");
|
||||||
|
|
||||||
|
// 2. Check value access and signals
|
||||||
|
bool signalCalled = false;
|
||||||
|
uLib::Object::connect(&obj.IntProp, &Property<int>::PropertyChanged, [&signalCalled]() {
|
||||||
|
signalCalled = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
assert(obj.IntProp.Get() == 10);
|
||||||
|
obj.IntProp = 20;
|
||||||
|
assert(obj.IntProp.Get() == 20);
|
||||||
|
assert(signalCalled == true);
|
||||||
|
|
||||||
|
// 3. Check serialization
|
||||||
|
std::stringstream ss;
|
||||||
|
Object::SaveXml(ss, obj);
|
||||||
|
std::string xml = ss.str();
|
||||||
|
std::cout << "Serialized XML: \n" << xml << std::endl;
|
||||||
|
|
||||||
|
assert(xml.find("<IntProp>20</IntProp>") != std::string::npos);
|
||||||
|
assert(xml.find("<StringProp>Initial</StringProp>") != std::string::npos);
|
||||||
|
|
||||||
|
// 4. Check deserialization
|
||||||
|
TestObject obj2;
|
||||||
|
std::stringstream ss2(xml);
|
||||||
|
Object::LoadXml(ss2, obj2);
|
||||||
|
|
||||||
|
assert(obj2.IntProp.Get() == 20);
|
||||||
|
assert(obj2.StringProp.Get() == "Initial");
|
||||||
|
|
||||||
|
std::cout << "All Property Tests PASSED!" << std::endl;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
68
src/Core/testing/PropertyTypesTest.cpp
Normal file
68
src/Core/testing/PropertyTypesTest.cpp
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <cassert>
|
||||||
|
#include "Core/Object.h"
|
||||||
|
#include "Core/Property.h"
|
||||||
|
#include "Math/Dense.h"
|
||||||
|
|
||||||
|
using namespace uLib;
|
||||||
|
|
||||||
|
class TestObject : public Object {
|
||||||
|
public:
|
||||||
|
TestObject() : Object() {}
|
||||||
|
|
||||||
|
virtual const char* GetClassName() const override { return "TestObject"; }
|
||||||
|
|
||||||
|
// Use new typedefs
|
||||||
|
StringProperty StringProp = StringProperty(this, "StringProp", "Initial");
|
||||||
|
IntProperty IntProp = IntProperty(this, "IntProp", 42);
|
||||||
|
FloatProperty FloatProp = FloatProperty(this, "FloatProp", 3.14f);
|
||||||
|
BoolProperty BoolProp = BoolProperty(this, "BoolProp", true);
|
||||||
|
|
||||||
|
// Use new macro
|
||||||
|
ULIB_PROPERTY(Matrix3f, MatrixProp, Matrix3f::Identity())
|
||||||
|
|
||||||
|
// Use new Dense typedefs
|
||||||
|
Vector3fProperty Vector3fProp = Vector3fProperty(this, "Vector3fProp", Vector3f(1.1f, 2.2f, 3.3f));
|
||||||
|
Matrix4fProperty Matrix4fProp = Matrix4fProperty(this, "Matrix4fProp", Matrix4f::Identity());
|
||||||
|
};
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
TestObject obj;
|
||||||
|
|
||||||
|
std::cout << "Testing Property Types..." << std::endl;
|
||||||
|
|
||||||
|
// 1. Verify string representation
|
||||||
|
std::cout << "StringProp: " << obj.StringProp.GetValueAsString() << std::endl;
|
||||||
|
assert(obj.StringProp.GetValueAsString() == "Initial");
|
||||||
|
|
||||||
|
std::cout << "IntProp: " << obj.IntProp.GetValueAsString() << std::endl;
|
||||||
|
assert(obj.IntProp.GetValueAsString() == "42");
|
||||||
|
|
||||||
|
std::cout << "FloatProp: " << obj.FloatProp.GetValueAsString() << std::endl;
|
||||||
|
// boost::lexical_cast might have different precision, but for 3.14 it should be okay or we check find
|
||||||
|
assert(obj.FloatProp.GetValueAsString().find("3.14") != std::string::npos);
|
||||||
|
|
||||||
|
std::cout << "BoolProp: " << obj.BoolProp.GetValueAsString() << std::endl;
|
||||||
|
// Bool might be "1" or "true" depending on lexical_cast/stringstream
|
||||||
|
assert(obj.BoolProp.GetValueAsString() == "1" || obj.BoolProp.GetValueAsString() == "true");
|
||||||
|
|
||||||
|
// 2. Verify Matrix/Vector string representation (uses operator<<)
|
||||||
|
std::cout << "MatrixProp: \n" << obj.MatrixProp.GetValueAsString() << std::endl;
|
||||||
|
assert(obj.MatrixProp.GetValueAsString().find("1 0 0") != std::string::npos);
|
||||||
|
|
||||||
|
std::cout << "Vector3fProp: " << obj.Vector3fProp.GetValueAsString() << std::endl;
|
||||||
|
assert(obj.Vector3fProp.GetValueAsString().find("1.1 2.2 3.3") != std::string::npos);
|
||||||
|
|
||||||
|
std::cout << "Matrix4fProp: \n" << obj.Matrix4fProp.GetValueAsString() << std::endl;
|
||||||
|
assert(obj.Matrix4fProp.GetValueAsString().find("1 0 0 0") != std::string::npos);
|
||||||
|
|
||||||
|
// 3. Verify updates and signals
|
||||||
|
obj.IntProp = 100;
|
||||||
|
assert(obj.IntProp.Get() == 100);
|
||||||
|
assert(obj.IntProp.GetValueAsString() == "100");
|
||||||
|
|
||||||
|
std::cout << "All Property Type Tests PASSED!" << std::endl;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
40
src/Core/testing/TeamTest.cpp
Normal file
40
src/Core/testing/TeamTest.cpp
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#include "Core/Threads.h"
|
||||||
|
#include <iostream>
|
||||||
|
#include <atomic>
|
||||||
|
#include <cassert>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
using namespace uLib;
|
||||||
|
|
||||||
|
void TestTaskTeam() {
|
||||||
|
std::cout << "Testing Task and Team..." << std::endl;
|
||||||
|
|
||||||
|
std::atomic<int> counter(0);
|
||||||
|
auto task_func = [&]() {
|
||||||
|
counter++;
|
||||||
|
Thread::Sleep(10);
|
||||||
|
};
|
||||||
|
|
||||||
|
Team team(4);
|
||||||
|
std::cout << " Team size: " << team.GetSize() << std::endl;
|
||||||
|
|
||||||
|
#ifdef _OPENMP
|
||||||
|
#pragma omp parallel
|
||||||
|
#pragma omp single
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 20; ++i) {
|
||||||
|
team.Run(new Task(task_func));
|
||||||
|
}
|
||||||
|
team.Wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(counter == 20);
|
||||||
|
std::cout << " Passed (counter: " << counter << ")." << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
TestTaskTeam();
|
||||||
|
std::cout << "All Team tests passed!" << std::endl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
72
src/Core/testing/ThreadsTest.cpp
Normal file
72
src/Core/testing/ThreadsTest.cpp
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
#include "Core/Threads.h"
|
||||||
|
#include <iostream>
|
||||||
|
#include <atomic>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
using namespace uLib;
|
||||||
|
|
||||||
|
class MyThread : public Thread {
|
||||||
|
public:
|
||||||
|
MyThread() : counter(0) {}
|
||||||
|
void Run() override {
|
||||||
|
for (int i = 0; i < 5; ++i) {
|
||||||
|
counter++;
|
||||||
|
Thread::Sleep(10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::atomic<int> counter;
|
||||||
|
};
|
||||||
|
|
||||||
|
void TestBasicThread() {
|
||||||
|
std::cout << "Testing basic Thread lifecycle..." << std::endl;
|
||||||
|
MyThread t;
|
||||||
|
assert(!t.IsRunning());
|
||||||
|
t.Start();
|
||||||
|
assert(t.IsRunning());
|
||||||
|
t.Join();
|
||||||
|
assert(!t.IsRunning());
|
||||||
|
assert(t.counter == 5);
|
||||||
|
std::cout << " Passed." << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestThreadDetach() {
|
||||||
|
std::cout << "Testing Thread Detach..." << std::endl;
|
||||||
|
std::atomic<bool> done(false);
|
||||||
|
|
||||||
|
// Using a lambda or a simple subclass
|
||||||
|
class DetachedThread : public Thread {
|
||||||
|
public:
|
||||||
|
DetachedThread(std::atomic<bool>& d) : m_done(d) {}
|
||||||
|
void Run() override {
|
||||||
|
Thread::Sleep(50);
|
||||||
|
m_done = true;
|
||||||
|
}
|
||||||
|
std::atomic<bool>& m_done;
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
DetachedThread* t = new DetachedThread(done);
|
||||||
|
t->Start();
|
||||||
|
t->Detach();
|
||||||
|
// The thread object 't' is still alive here,
|
||||||
|
// but it will be destroyed soon if we delete it.
|
||||||
|
// For a detached thread using members, we MUST keep it alive.
|
||||||
|
|
||||||
|
int wait_count = 0;
|
||||||
|
while(!done && wait_count < 20) {
|
||||||
|
Thread::Sleep(10);
|
||||||
|
wait_count++;
|
||||||
|
}
|
||||||
|
delete t;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(done);
|
||||||
|
std::cout << " Passed." << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
TestBasicThread();
|
||||||
|
TestThreadDetach();
|
||||||
|
std::cout << "All Thread tests passed!" << std::endl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
#include "HEP/Detectors/DetectorChamber.h"
|
#include "HEP/Detectors/DetectorChamber.h"
|
||||||
|
#include "Core/ObjectFactory.h"
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
namespace uLib {
|
namespace uLib {
|
||||||
@@ -14,14 +15,7 @@ MuonEvent DetectorChamber::ProjectMuonEvent(const MuonEvent &muon) const {
|
|||||||
HPoint3f X_in = muon.LineIn().origin;
|
HPoint3f X_in = muon.LineIn().origin;
|
||||||
HPoint3f X_out = muon.LineOut().origin;
|
HPoint3f X_out = muon.LineOut().origin;
|
||||||
|
|
||||||
// Calculate squared distances to the plane normal point for comparison
|
// Let's use distance to the plane for the first part and keep the logic consistent.
|
||||||
// Actually, we should probably follow the user's description literally:
|
|
||||||
// "closest ... with the projection plane ( so the colsest direction point with the point of the normal defining the plane )"
|
|
||||||
// This could mean point-to-plane or point-to-point.
|
|
||||||
// Given "closest with the projection plane", point-to-plane is more natural.
|
|
||||||
// However, "closest direction point with the point of the normal" strongly suggests point-to-point distance.
|
|
||||||
// Let's use distance to the plane for the first part and keep the logic consistent.
|
|
||||||
|
|
||||||
float dist_in = std::abs((X_in - P).dot(N));
|
float dist_in = std::abs((X_in - P).dot(N));
|
||||||
float dist_out = std::abs((X_out - P).dot(N));
|
float dist_out = std::abs((X_out - P).dot(N));
|
||||||
|
|
||||||
@@ -50,4 +44,6 @@ MuonEvent DetectorChamber::ProjectMuonEvent(const MuonEvent &muon) const {
|
|||||||
return projectedMuon;
|
return projectedMuon;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ULIB_REGISTER_OBJECT(DetectorChamber)
|
||||||
|
|
||||||
} // namespace uLib
|
} // namespace uLib
|
||||||
@@ -45,6 +45,8 @@ class DetectorChamber : public ContainerBox {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
virtual const char * GetClassName() const { return "DetectorChamber"; }
|
||||||
|
|
||||||
DetectorChamber() : BaseClass() {
|
DetectorChamber() : BaseClass() {
|
||||||
m_ProjectionPlane.origin = HPoint3f(0, 0, 0);
|
m_ProjectionPlane.origin = HPoint3f(0, 0, 0);
|
||||||
m_ProjectionPlane.direction = HVector3f(0, 0, 1);
|
m_ProjectionPlane.direction = HVector3f(0, 0, 1);
|
||||||
@@ -65,7 +67,9 @@ public:
|
|||||||
HLine3f worldPlane;
|
HLine3f worldPlane;
|
||||||
Matrix4f M = this->GetWorldMatrix();
|
Matrix4f M = this->GetWorldMatrix();
|
||||||
worldPlane.origin = M * m_ProjectionPlane.origin;
|
worldPlane.origin = M * m_ProjectionPlane.origin;
|
||||||
worldPlane.direction = M * m_ProjectionPlane.direction;
|
HVector3f dirNorm = M * m_ProjectionPlane.direction;
|
||||||
|
dirNorm.normalize(); // Normalize for consistent dot products
|
||||||
|
worldPlane.direction = dirNorm;
|
||||||
return worldPlane;
|
return worldPlane;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,34 +5,37 @@
|
|||||||
namespace uLib {
|
namespace uLib {
|
||||||
namespace Geant {
|
namespace Geant {
|
||||||
|
|
||||||
ActionInitialization::ActionInitialization(EmitterPrimary *emitter,
|
ActionInitialization::ActionInitialization(EmitterPrimary *emitter, SimulationContext *context)
|
||||||
Vector<GeantEvent> *output)
|
|
||||||
: G4VUserActionInitialization(),
|
: G4VUserActionInitialization(),
|
||||||
m_Emitter(emitter),
|
m_Emitter(emitter),
|
||||||
m_Output(output)
|
m_Context(context)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
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 {}
|
||||||
// Master thread: no per-event actions needed
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActionInitialization::Build() const {
|
void ActionInitialization::Build() const {
|
||||||
// Register the primary generator
|
|
||||||
if (m_Emitter) {
|
if (m_Emitter) {
|
||||||
SetUserAction(m_Emitter);
|
SetUserAction(m_Emitter->Clone());
|
||||||
} else {
|
} else {
|
||||||
// Fallback: default EmitterPrimary
|
|
||||||
SetUserAction(new EmitterPrimary());
|
SetUserAction(new EmitterPrimary());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register actions
|
SteppingAction *sa = new SteppingAction(m_Context);
|
||||||
if (m_Output) {
|
// EventManager will delete sa via this slot
|
||||||
SteppingAction *sa = new SteppingAction(m_Output);
|
SetUserAction(static_cast<G4UserEventAction*>(sa));
|
||||||
SetUserAction(static_cast<G4UserSteppingAction*>(sa));
|
// EventManager will delete the wrapper, leaving sa alive to be deleted once.
|
||||||
SetUserAction(static_cast<G4UserEventAction*>(sa));
|
SetUserAction(new SteppingActionWrapper(sa));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Geant
|
} // namespace Geant
|
||||||
|
|||||||
@@ -2,31 +2,24 @@
|
|||||||
#define ActionInitialization_h
|
#define ActionInitialization_h
|
||||||
|
|
||||||
#include "G4VUserActionInitialization.hh"
|
#include "G4VUserActionInitialization.hh"
|
||||||
#include "Core/Vector.h"
|
#include "SimulationContext.h"
|
||||||
|
|
||||||
namespace uLib {
|
namespace uLib {
|
||||||
namespace Geant {
|
namespace Geant {
|
||||||
|
|
||||||
class EmitterPrimary;
|
class EmitterPrimary;
|
||||||
class GeantEvent;
|
|
||||||
|
|
||||||
class ActionInitialization : public G4VUserActionInitialization {
|
class ActionInitialization : public G4VUserActionInitialization {
|
||||||
public:
|
public:
|
||||||
/// @param emitter the primary generator to use (owned by caller)
|
ActionInitialization(EmitterPrimary *emitter, SimulationContext *context);
|
||||||
/// @param output pointer to the results vector (owned by caller)
|
|
||||||
ActionInitialization(EmitterPrimary *emitter = nullptr,
|
|
||||||
Vector<GeantEvent> *output = nullptr);
|
|
||||||
~ActionInitialization();
|
~ActionInitialization();
|
||||||
|
|
||||||
// Metodo chiamato solo dal thread principale (Master)
|
virtual void BuildForMaster() const override;
|
||||||
virtual void BuildForMaster() const;
|
virtual void Build() const override;
|
||||||
|
|
||||||
// Metodo chiamato dai thread di lavoro (Worker) o in modalità sequenziale
|
|
||||||
virtual void Build() const;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
EmitterPrimary *m_Emitter;
|
EmitterPrimary *m_Emitter;
|
||||||
Vector<GeantEvent> *m_Output;
|
SimulationContext *m_Context;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Geant
|
} // namespace Geant
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ set(HEADERS
|
|||||||
PhysicsList.hh
|
PhysicsList.hh
|
||||||
ActionInitialization.hh
|
ActionInitialization.hh
|
||||||
SteppingAction.hh
|
SteppingAction.hh
|
||||||
|
SimulationContext.h
|
||||||
)
|
)
|
||||||
|
|
||||||
set(SOURCES
|
set(SOURCES
|
||||||
|
|||||||
53
src/HEP/Geant/DetectorActionInitialization.cpp
Normal file
53
src/HEP/Geant/DetectorActionInitialization.cpp
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
#include "DetectorActionInitialization.hh"
|
||||||
|
#include "EmitterPrimary.hh"
|
||||||
|
#include "DetectorSteppingAction.hh"
|
||||||
|
|
||||||
|
namespace uLib {
|
||||||
|
namespace Geant {
|
||||||
|
|
||||||
|
DetectorActionInitialization::DetectorActionInitialization(EmitterPrimary *emitter,
|
||||||
|
Vector<MuonEvent> *output,
|
||||||
|
const Vector<HLine3f> &planes,
|
||||||
|
int verbosity)
|
||||||
|
: G4VUserActionInitialization(),
|
||||||
|
m_Emitter(emitter),
|
||||||
|
m_Output(output),
|
||||||
|
m_Planes(planes),
|
||||||
|
m_Verbosity(verbosity)
|
||||||
|
{}
|
||||||
|
|
||||||
|
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::Build() const {
|
||||||
|
if (m_Verbosity > 0) {
|
||||||
|
std::cout << "[Geant] Worker thread Building actions... Output ptr: " << m_Output
|
||||||
|
<< ", Planes count: " << m_Planes.size() << std::endl;
|
||||||
|
}
|
||||||
|
if (m_Emitter) {
|
||||||
|
SetUserAction(m_Emitter->Clone());
|
||||||
|
} else {
|
||||||
|
SetUserAction(new EmitterPrimary());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_Output) {
|
||||||
|
DetectorSteppingAction *sa = new DetectorSteppingAction(m_Output, m_Planes);
|
||||||
|
sa->SetVerbosity(m_Verbosity);
|
||||||
|
// EventManager will delete sa via the Event slot
|
||||||
|
SetUserAction(static_cast<G4UserEventAction*>(sa));
|
||||||
|
// EventManager will delete the wrapper, leaving sa alive for the other deletion.
|
||||||
|
SetUserAction(new DetectorSteppingActionWrapper(sa));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Geant
|
||||||
|
} // namespace uLib
|
||||||
35
src/HEP/Geant/DetectorActionInitialization.hh
Normal file
35
src/HEP/Geant/DetectorActionInitialization.hh
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#ifndef U_GEANT_DETECTORACTIONINITIALIZATION_HH
|
||||||
|
#define U_GEANT_DETECTORACTIONINITIALIZATION_HH
|
||||||
|
|
||||||
|
#include "G4VUserActionInitialization.hh"
|
||||||
|
#include "Core/Vector.h"
|
||||||
|
#include "HEP/Detectors/MuonEvent.h"
|
||||||
|
#include "Math/Dense.h"
|
||||||
|
|
||||||
|
namespace uLib {
|
||||||
|
namespace Geant {
|
||||||
|
|
||||||
|
class EmitterPrimary;
|
||||||
|
|
||||||
|
class DetectorActionInitialization : public G4VUserActionInitialization {
|
||||||
|
public:
|
||||||
|
DetectorActionInitialization(EmitterPrimary *emitter,
|
||||||
|
Vector<MuonEvent> *output,
|
||||||
|
const Vector<HLine3f> &planes,
|
||||||
|
int verbosity = 0);
|
||||||
|
~DetectorActionInitialization();
|
||||||
|
|
||||||
|
virtual void BuildForMaster() const override;
|
||||||
|
virtual void Build() const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
EmitterPrimary *m_Emitter;
|
||||||
|
Vector<MuonEvent> *m_Output;
|
||||||
|
Vector<HLine3f> m_Planes;
|
||||||
|
int m_Verbosity;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Geant
|
||||||
|
} // namespace uLib
|
||||||
|
|
||||||
|
#endif
|
||||||
110
src/HEP/Geant/DetectorSteppingAction.cpp
Normal file
110
src/HEP/Geant/DetectorSteppingAction.cpp
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
#include "DetectorSteppingAction.hh"
|
||||||
|
#include <Geant4/G4Step.hh>
|
||||||
|
#include <Geant4/G4Track.hh>
|
||||||
|
#include <Geant4/G4Event.hh>
|
||||||
|
#include <Geant4/G4SystemOfUnits.hh>
|
||||||
|
#include <cmath>
|
||||||
|
#include <mutex>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
static std::mutex g_DetectorOutputMutex;
|
||||||
|
|
||||||
|
namespace uLib {
|
||||||
|
namespace Geant {
|
||||||
|
|
||||||
|
DetectorSteppingAction::DetectorSteppingAction(Vector<MuonEvent> *output, const Vector<HLine3f> &planes)
|
||||||
|
: G4UserSteppingAction(),
|
||||||
|
G4UserEventAction(),
|
||||||
|
m_Output(output),
|
||||||
|
m_Planes(planes),
|
||||||
|
m_CrossCount(0),
|
||||||
|
m_Verbosity(1)
|
||||||
|
{
|
||||||
|
// std::cout << "[Geant] SteppingAction created with " << m_Planes.size() << " planes." << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
DetectorSteppingAction::~DetectorSteppingAction() {}
|
||||||
|
|
||||||
|
void DetectorSteppingAction::BeginOfEventAction(const G4Event* /*event*/) {
|
||||||
|
m_CrossCount = 0;
|
||||||
|
|
||||||
|
// Initialize with NaN
|
||||||
|
float nan = std::numeric_limits<float>::quiet_NaN();
|
||||||
|
m_Current.LineIn().origin = HPoint3f(nan, nan, nan);
|
||||||
|
m_Current.LineIn().direction = HVector3f(nan, nan, nan);
|
||||||
|
m_Current.LineOut().origin = HPoint3f(nan, nan, nan);
|
||||||
|
m_Current.LineOut().direction = HVector3f(nan, nan, nan);
|
||||||
|
m_Current.Momentum() = nan;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DetectorSteppingAction::EndOfEventAction(const G4Event* /*event*/) {
|
||||||
|
if (m_Output) {
|
||||||
|
std::lock_guard<std::mutex> lock(g_DetectorOutputMutex);
|
||||||
|
m_Output->push_back(m_Current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DetectorSteppingAction::UserSteppingAction(const G4Step *step) {
|
||||||
|
if (!step) return;
|
||||||
|
if (!m_Output) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const G4Track *track = step->GetTrack();
|
||||||
|
if (!track) return;
|
||||||
|
|
||||||
|
static size_t step_count = 0;
|
||||||
|
if (++step_count % 1000 == 0 && m_Verbosity > 0) {
|
||||||
|
std::cout << "[GeantMT] Processed " << step_count << " total steps across events." << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only consider primary muons
|
||||||
|
if (track->GetParentID() != 0) return;
|
||||||
|
|
||||||
|
// Track the momentum at generation/first step if not set
|
||||||
|
if (std::isnan(m_Current.Momentum())) {
|
||||||
|
m_Current.Momentum() = static_cast<Scalarf>(track->GetMomentum().mag() / MeV);
|
||||||
|
}
|
||||||
|
|
||||||
|
G4ThreeVector p1 = step->GetPreStepPoint()->GetPosition();
|
||||||
|
G4ThreeVector p2 = step->GetPostStepPoint()->GetPosition();
|
||||||
|
G4ThreeVector dir_g4 = track->GetMomentumDirection();
|
||||||
|
|
||||||
|
HPoint3f p1f(p1.x(), p1.y(), p1.z());
|
||||||
|
HPoint3f p2f(p2.x(), p2.y(), p2.z());
|
||||||
|
HVector3f dirf(dir_g4.x(), dir_g4.y(), dir_g4.z());
|
||||||
|
|
||||||
|
// Check intersection with each detector plane
|
||||||
|
for (const auto& plane : m_Planes) {
|
||||||
|
// Plane: origin=O, direction=N (normal)
|
||||||
|
HPoint3f O = plane.origin;
|
||||||
|
HVector3f N = plane.direction;
|
||||||
|
|
||||||
|
float d1 = (p1f - O).dot(N);
|
||||||
|
float d2 = (p2f - O).dot(N);
|
||||||
|
|
||||||
|
// Check if the step crossed the plane
|
||||||
|
if ((d1 > 0 && d2 <= 0) || (d1 < 0 && d2 >= 0)) {
|
||||||
|
// Intersection point t = d1 / (d1 - d2)
|
||||||
|
float t = d1 / (d1 - d2);
|
||||||
|
HPoint3f intersection = p1f + t * (p2f - p1f);
|
||||||
|
|
||||||
|
if (m_CrossCount == 0) {
|
||||||
|
m_Current.LineIn().origin = intersection;
|
||||||
|
m_Current.LineIn().direction = dirf;
|
||||||
|
m_CrossCount++;
|
||||||
|
if (m_Verbosity > 0) std::cout << "[GeantMT] Hit first plane at " << intersection.transpose() << std::endl;
|
||||||
|
} else if (m_CrossCount == 1) {
|
||||||
|
m_Current.LineOut().origin = intersection;
|
||||||
|
m_Current.LineOut().direction = dirf;
|
||||||
|
m_CrossCount++;
|
||||||
|
if (m_Verbosity > 0) std::cout << "[GeantMT] Hit second plane at " << intersection.transpose() << std::endl;
|
||||||
|
}
|
||||||
|
// We break to avoid crossing multiple planes in one infinitesimal step (unlikely but possible)
|
||||||
|
// Actually, we should check ALL planes.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Geant
|
||||||
|
} // namespace uLib
|
||||||
36
src/HEP/Geant/DetectorSteppingAction.hh
Normal file
36
src/HEP/Geant/DetectorSteppingAction.hh
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
#ifndef U_GEANT_DETECTORSTEPPINGACTION_HH
|
||||||
|
#define U_GEANT_DETECTORSTEPPINGACTION_HH
|
||||||
|
|
||||||
|
#include "G4UserSteppingAction.hh"
|
||||||
|
#include "G4UserEventAction.hh"
|
||||||
|
#include "Core/Vector.h"
|
||||||
|
#include "HEP/Detectors/MuonEvent.h"
|
||||||
|
#include "HEP/Detectors/DetectorChamber.h"
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
namespace uLib {
|
||||||
|
namespace Geant {
|
||||||
|
|
||||||
|
class DetectorSteppingAction : public G4UserSteppingAction, public G4UserEventAction {
|
||||||
|
public:
|
||||||
|
DetectorSteppingAction(Vector<MuonEvent> *output, const Vector<HLine3f> &planes);
|
||||||
|
virtual ~DetectorSteppingAction();
|
||||||
|
|
||||||
|
virtual void UserSteppingAction(const G4Step *step) override;
|
||||||
|
virtual void BeginOfEventAction(const G4Event *event) override;
|
||||||
|
virtual void EndOfEventAction(const G4Event *event) override;
|
||||||
|
|
||||||
|
void SetVerbosity(int level) { m_Verbosity = level; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
Vector<MuonEvent> *m_Output;
|
||||||
|
Vector<HLine3f> m_Planes; // World projection planes
|
||||||
|
MuonEvent m_Current;
|
||||||
|
int m_CrossCount = 0;
|
||||||
|
int m_Verbosity = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Geant
|
||||||
|
} // namespace uLib
|
||||||
|
|
||||||
|
#endif
|
||||||
1203
src/HEP/Geant/EcoMug.hh
Normal file
1203
src/HEP/Geant/EcoMug.hh
Normal file
File diff suppressed because it is too large
Load Diff
@@ -10,6 +10,10 @@
|
|||||||
#include "G4SystemOfUnits.hh"
|
#include "G4SystemOfUnits.hh"
|
||||||
#include "Randomize.hh"
|
#include "Randomize.hh"
|
||||||
|
|
||||||
|
#include "EcoMug.hh"
|
||||||
|
#include "Math/Cylinder.h"
|
||||||
|
|
||||||
|
|
||||||
namespace uLib {
|
namespace uLib {
|
||||||
namespace Geant {
|
namespace Geant {
|
||||||
|
|
||||||
@@ -29,14 +33,14 @@ EmitterPrimary::EmitterPrimary()
|
|||||||
// Configuriamo le proprietà iniziali della particella
|
// Configuriamo le proprietà iniziali della particella
|
||||||
fParticleGun->SetParticleDefinition(particle);
|
fParticleGun->SetParticleDefinition(particle);
|
||||||
|
|
||||||
// Impostiamo la direzione della quantità di moto (es. lungo l'asse Z)
|
|
||||||
fParticleGun->SetParticleMomentumDirection(G4ThreeVector(0., 0., -1.));
|
|
||||||
|
|
||||||
// Impostiamo l'energia cinetica a 1 GeV
|
// Impostiamo l'energia cinetica a 1 GeV
|
||||||
fParticleGun->SetParticleEnergy(1.0 * GeV);
|
fParticleGun->SetParticleEnergy(1.0 * GeV);
|
||||||
|
|
||||||
// Impostiamo la posizione di partenza (origine)
|
// Initial position and direction through AffineTransform
|
||||||
fParticleGun->SetParticlePosition(G4ThreeVector(0., 0., 10. * m));
|
// 10m on Z axis, pointing towards origin
|
||||||
|
this->SetPosition(Vector3f(0, 0, 10000.0));
|
||||||
|
// Default orientation is identity (pointing along -Z if we rotate the puppet accordingly)
|
||||||
|
// But fParticleGun defaults are set here and overridden in GeneratePrimaries
|
||||||
}
|
}
|
||||||
|
|
||||||
EmitterPrimary::~EmitterPrimary() {
|
EmitterPrimary::~EmitterPrimary() {
|
||||||
@@ -45,14 +49,181 @@ EmitterPrimary::~EmitterPrimary() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void EmitterPrimary::GeneratePrimaries(G4Event *anEvent) {
|
void EmitterPrimary::GeneratePrimaries(G4Event *anEvent) {
|
||||||
// Questo metodo viene invocato all'inizio di ogni evento.
|
// Use position and direction from AffineTransform
|
||||||
// Qui potresti anche aggiungere una randomizzazione della posizione o
|
Vector3f pos = this->GetPosition();
|
||||||
// dell'energia.
|
// Assume default direction is along the -Z axis of the local frame
|
||||||
|
Vector4f dir4 = this->GetWorldMatrix() * Vector4f(0, 0, -1, 0);
|
||||||
|
Vector3f dir = dir4.head<3>().normalized();
|
||||||
|
|
||||||
|
fParticleGun->SetParticlePosition(G4ThreeVector(pos(0), pos(1), pos(2)));
|
||||||
|
fParticleGun->SetParticleMomentumDirection(G4ThreeVector(dir(0), dir(1), dir(2)));
|
||||||
|
|
||||||
fParticleGun->GeneratePrimaryVertex(anEvent);
|
fParticleGun->GeneratePrimaryVertex(anEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EmitterPrimary* EmitterPrimary::Clone() const {
|
||||||
|
auto* clone = new EmitterPrimary();
|
||||||
|
clone->SetMatrix(this->GetMatrix());
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------- //
|
// -------------------------------------------------------------------------- //
|
||||||
|
// SkyPlaneEmitterPrimary using EcoMug
|
||||||
|
|
||||||
|
SkyPlaneEmitterPrimary::SkyPlaneEmitterPrimary()
|
||||||
|
: EmitterPrimary(), m_EcoMug(new EcoMug()), m_Size(1000.0, 1000.0) {
|
||||||
|
// Initial configuration for EcoMug in sky mode
|
||||||
|
m_EcoMug->SetUseSky();
|
||||||
|
m_EcoMug->SetSkySize({m_Size(0)/1000.0, m_Size(1)/1000.0});
|
||||||
|
}
|
||||||
|
|
||||||
|
SkyPlaneEmitterPrimary::~SkyPlaneEmitterPrimary() {
|
||||||
|
delete m_EcoMug;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SkyPlaneEmitterPrimary::SetSkySize(const uLib::Vector2f& size) {
|
||||||
|
m_Size = size;
|
||||||
|
// EcoMug units are in meters (m=1), Geant4 units are in mm.
|
||||||
|
m_EcoMug->SetSkySize({m_Size(0)/1000.0, m_Size(1)/1000.0});
|
||||||
|
this->Updated();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SkyPlaneEmitterPrimary::SetPlane(const uLib::Vector3f& p0, const uLib::Vector3f& normal) {
|
||||||
|
this->SetPosition(p0);
|
||||||
|
// Orient the emitter so that local Z is the normal.
|
||||||
|
// This is useful for unconventional planes, though EcoMug sky is usually horizontal.
|
||||||
|
// If we want a truly 'sky' plane, it usually stays horizontal.
|
||||||
|
this->Updated();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SkyPlaneEmitterPrimary::GeneratePrimaries(G4Event* anEvent) {
|
||||||
|
if (!m_EcoMug) return;
|
||||||
|
m_EcoMug->Generate();
|
||||||
|
|
||||||
|
// EcoMug position is relative to its internal sky center in meters.
|
||||||
|
// Our wrapper uses the AffineTransform for the overall positioning.
|
||||||
|
std::array<double, 3> pos_m = m_EcoMug->GetGenerationPosition();
|
||||||
|
G4ThreeVector local_pos(pos_m[0]*1000.0, pos_m[1]*1000.0, pos_m[2]*1000.0);
|
||||||
|
|
||||||
|
// EcoMug momentum (direction and magnitude in GeV/c)
|
||||||
|
double p_mag = m_EcoMug->GetGenerationMomentum();
|
||||||
|
double theta = m_EcoMug->GetGenerationTheta();
|
||||||
|
double phi = m_EcoMug->GetGenerationPhi();
|
||||||
|
|
||||||
|
// EcoMug theta is generated in a way that PI means pointing down (-Z)
|
||||||
|
G4ThreeVector local_dir(
|
||||||
|
sin(theta) * cos(phi),
|
||||||
|
sin(theta) * sin(phi),
|
||||||
|
cos(theta)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Transform local coordinates to world
|
||||||
|
Matrix4f world_mat = this->GetWorldMatrix();
|
||||||
|
Vector3f world_pos = (world_mat * Vector4f(local_pos.x(), local_pos.y(), local_pos.z(), 1.0)).head<3>();
|
||||||
|
Vector3f world_dir = (world_mat * Vector4f(local_dir.x(), local_dir.y(), local_dir.z(), 0.0)).head<3>().normalized();
|
||||||
|
|
||||||
|
// Set particle charge
|
||||||
|
G4ParticleTable *particleTable = G4ParticleTable::GetParticleTable();
|
||||||
|
G4String particleName = (m_EcoMug->GetCharge() > 0) ? "mu+" : "mu-";
|
||||||
|
fParticleGun->SetParticleDefinition(particleTable->FindParticle(particleName));
|
||||||
|
|
||||||
|
fParticleGun->SetParticlePosition(G4ThreeVector(world_pos(0), world_pos(1), world_pos(2)));
|
||||||
|
fParticleGun->SetParticleMomentumDirection(G4ThreeVector(world_dir(0), world_dir(1), world_dir(2)));
|
||||||
|
fParticleGun->SetParticleEnergy(p_mag * GeV);
|
||||||
|
|
||||||
|
fParticleGun->GeneratePrimaryVertex(anEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
EmitterPrimary* SkyPlaneEmitterPrimary::Clone() const {
|
||||||
|
auto* clone = new SkyPlaneEmitterPrimary();
|
||||||
|
clone->SetSkySize(this->m_Size);
|
||||||
|
clone->SetMatrix(this->GetMatrix());
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------- //
|
||||||
|
// CylinderEmitterPrimary using EcoMug
|
||||||
|
|
||||||
|
CylinderEmitterPrimary::CylinderEmitterPrimary()
|
||||||
|
: EmitterPrimary(), m_EcoMug(new EcoMug()), m_Radius(1000.0), m_Height(1000.0) {
|
||||||
|
m_EcoMug->SetUseCylinder();
|
||||||
|
m_EcoMug->SetCylinderRadius(m_Radius/1000.0);
|
||||||
|
m_EcoMug->SetCylinderHeight(m_Height/1000.0);
|
||||||
|
m_EcoMug->SetCylinderCenterPosition({0.0, 0.0, m_Height/2000.0});
|
||||||
|
}
|
||||||
|
|
||||||
|
CylinderEmitterPrimary::~CylinderEmitterPrimary() {
|
||||||
|
delete m_EcoMug;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CylinderEmitterPrimary::SetRadius(float r) {
|
||||||
|
m_Radius = r;
|
||||||
|
m_EcoMug->SetCylinderRadius(m_Radius/1000.0);
|
||||||
|
this->Updated();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CylinderEmitterPrimary::SetHeight(float h) {
|
||||||
|
m_Height = h;
|
||||||
|
m_EcoMug->SetCylinderHeight(m_Height/1000.0);
|
||||||
|
m_EcoMug->SetCylinderCenterPosition({0.0, 0.0, m_Height/2000.0});
|
||||||
|
this->Updated();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CylinderEmitterPrimary::GeneratePrimaries(G4Event* anEvent) {
|
||||||
|
if (!m_EcoMug) return;
|
||||||
|
m_EcoMug->Generate();
|
||||||
|
|
||||||
|
std::array<double, 3> pos_m = m_EcoMug->GetGenerationPosition();
|
||||||
|
G4ThreeVector local_pos(pos_m[0]*1000.0, pos_m[1]*1000.0, pos_m[2]*1000.0);
|
||||||
|
|
||||||
|
double p_mag = m_EcoMug->GetGenerationMomentum();
|
||||||
|
double theta = m_EcoMug->GetGenerationTheta();
|
||||||
|
double phi = m_EcoMug->GetGenerationPhi();
|
||||||
|
|
||||||
|
G4ThreeVector local_dir(
|
||||||
|
sin(theta) * cos(phi),
|
||||||
|
sin(theta) * sin(phi),
|
||||||
|
cos(theta)
|
||||||
|
);
|
||||||
|
|
||||||
|
Matrix4f world_mat = this->GetWorldMatrix();
|
||||||
|
Vector3f world_pos = (world_mat * Vector4f(local_pos.x(), local_pos.y(), local_pos.z(), 1.0)).head<3>();
|
||||||
|
Vector3f world_dir = (world_mat * Vector4f(local_dir.x(), local_dir.y(), local_dir.z(), 0.0)).head<3>().normalized();
|
||||||
|
|
||||||
|
G4ParticleTable *particleTable = G4ParticleTable::GetParticleTable();
|
||||||
|
G4String particleName = (m_EcoMug->GetCharge() > 0) ? "mu+" : "mu-";
|
||||||
|
fParticleGun->SetParticleDefinition(particleTable->FindParticle(particleName));
|
||||||
|
|
||||||
|
fParticleGun->SetParticlePosition(G4ThreeVector(world_pos(0), world_pos(1), world_pos(2)));
|
||||||
|
fParticleGun->SetParticleMomentumDirection(G4ThreeVector(world_dir(0), world_dir(1), world_dir(2)));
|
||||||
|
fParticleGun->SetParticleEnergy(p_mag * GeV);
|
||||||
|
|
||||||
|
fParticleGun->GeneratePrimaryVertex(anEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
EmitterPrimary* CylinderEmitterPrimary::Clone() const {
|
||||||
|
auto* clone = new CylinderEmitterPrimary();
|
||||||
|
clone->SetRadius(this->m_Radius);
|
||||||
|
clone->SetHeight(this->m_Height);
|
||||||
|
clone->SetMatrix(this->GetMatrix());
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------- //
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------- //
|
||||||
|
|
||||||
|
|
||||||
QuadMeshEmitterPrimary::QuadMeshEmitterPrimary()
|
QuadMeshEmitterPrimary::QuadMeshEmitterPrimary()
|
||||||
: EmitterPrimary(), m_Mesh(nullptr), m_TotalArea(0.0) {
|
: EmitterPrimary(), m_Mesh(nullptr), m_TotalArea(0.0) {
|
||||||
@@ -148,6 +319,12 @@ void QuadMeshEmitterPrimary::GeneratePrimaries(G4Event *anEvent) {
|
|||||||
fParticleGun->GeneratePrimaryVertex(anEvent);
|
fParticleGun->GeneratePrimaryVertex(anEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
EmitterPrimary* QuadMeshEmitterPrimary::Clone() const {
|
||||||
|
auto* clone = new QuadMeshEmitterPrimary();
|
||||||
|
if (m_Mesh) clone->SetMesh(m_Mesh);
|
||||||
|
clone->SetMatrix(this->GetMatrix());
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Geant
|
} // namespace Geant
|
||||||
} // namespace uLib
|
} // namespace uLib
|
||||||
@@ -4,32 +4,103 @@
|
|||||||
#include "G4VUserPrimaryGeneratorAction.hh"
|
#include "G4VUserPrimaryGeneratorAction.hh"
|
||||||
#include "globals.hh"
|
#include "globals.hh"
|
||||||
|
|
||||||
|
|
||||||
|
namespace uLib {
|
||||||
|
class QuadMesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
class EcoMug;
|
||||||
|
|
||||||
#include "Math/QuadMesh.h"
|
#include "Math/QuadMesh.h"
|
||||||
|
#include "Core/Object.h"
|
||||||
|
#include "Math/Transform.h"
|
||||||
#include <vector> // Added for std::vector
|
#include <vector> // Added for std::vector
|
||||||
|
|
||||||
class G4ParticleGun;
|
class G4ParticleGun;
|
||||||
class G4Event;
|
class G4Event;
|
||||||
|
|
||||||
|
|
||||||
namespace uLib {
|
namespace uLib {
|
||||||
namespace Geant {
|
namespace Geant {
|
||||||
|
|
||||||
class EmitterPrimary : public G4VUserPrimaryGeneratorAction
|
class EmitterPrimary : public G4VUserPrimaryGeneratorAction, public Object, public AffineTransform
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
virtual const char* GetClassName() const override { return "Geant.EmitterPrimary"; }
|
||||||
|
|
||||||
EmitterPrimary();
|
EmitterPrimary();
|
||||||
virtual ~EmitterPrimary();
|
virtual ~EmitterPrimary();
|
||||||
|
|
||||||
// Metodo principale chiamato all'inizio di ogni evento
|
// Metodo principale chiamato all'inizio di ogni evento
|
||||||
virtual void GeneratePrimaries(G4Event*);
|
virtual void GeneratePrimaries(G4Event*);
|
||||||
|
|
||||||
|
virtual void Updated() override { ULIB_SIGNAL_EMIT(EmitterPrimary::Updated); }
|
||||||
|
|
||||||
|
/// Create a clone of this emitter for multi-threading
|
||||||
|
virtual EmitterPrimary* Clone() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
G4ParticleGun* fParticleGun; // Puntatore al cannone di particelle
|
G4ParticleGun* fParticleGun; // Puntatore al cannone di particelle
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class SkyPlaneEmitterPrimary : public EmitterPrimary
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
virtual const char* GetClassName() const override { return "Geant.SkyPlaneEmitterPrimary"; }
|
||||||
|
|
||||||
|
SkyPlaneEmitterPrimary();
|
||||||
|
virtual ~SkyPlaneEmitterPrimary();
|
||||||
|
|
||||||
|
virtual void GeneratePrimaries(G4Event*);
|
||||||
|
|
||||||
|
void SetPlane(const uLib::Vector3f& p0, const uLib::Vector3f& normal);
|
||||||
|
void SetSkySize(const uLib::Vector2f& size);
|
||||||
|
uLib::Vector2f GetSkySize() const { return m_Size; }
|
||||||
|
|
||||||
|
virtual EmitterPrimary* Clone() const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
EcoMug* m_EcoMug;
|
||||||
|
uLib::Vector2f m_Size;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CylinderEmitterPrimary : public EmitterPrimary
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
virtual const char* GetClassName() const override { return "Geant.CylinderEmitterPrimary"; }
|
||||||
|
|
||||||
|
CylinderEmitterPrimary();
|
||||||
|
virtual ~CylinderEmitterPrimary();
|
||||||
|
|
||||||
|
virtual void GeneratePrimaries(G4Event*);
|
||||||
|
|
||||||
|
void SetRadius(float r);
|
||||||
|
float GetRadius() const { return m_Radius; }
|
||||||
|
void SetHeight(float h);
|
||||||
|
float GetHeight() const { return m_Height; }
|
||||||
|
|
||||||
|
virtual EmitterPrimary* Clone() const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
EcoMug* m_EcoMug;
|
||||||
|
float m_Radius;
|
||||||
|
float m_Height;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class QuadMeshEmitterPrimary : public EmitterPrimary
|
class QuadMeshEmitterPrimary : public EmitterPrimary
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
virtual const char* GetClassName() const override { return "Geant.QuadMeshEmitterPrimary"; }
|
||||||
|
|
||||||
QuadMeshEmitterPrimary();
|
QuadMeshEmitterPrimary();
|
||||||
virtual ~QuadMeshEmitterPrimary();
|
virtual ~QuadMeshEmitterPrimary();
|
||||||
|
|
||||||
@@ -37,6 +108,8 @@ class QuadMeshEmitterPrimary : public EmitterPrimary
|
|||||||
virtual void GeneratePrimaries(G4Event*);
|
virtual void GeneratePrimaries(G4Event*);
|
||||||
|
|
||||||
void SetMesh(uLib::QuadMesh* mesh);
|
void SetMesh(uLib::QuadMesh* mesh);
|
||||||
|
|
||||||
|
virtual EmitterPrimary* Clone() const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uLib::QuadMesh* m_Mesh;
|
uLib::QuadMesh* m_Mesh;
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
#ifndef U_GEANTEVENT_H
|
#ifndef U_GEANTEVENT_H
|
||||||
#define U_GEANTEVENT_H
|
#define U_GEANTEVENT_H
|
||||||
|
|
||||||
|
#include "Core/Object.h"
|
||||||
#include "Core/Types.h"
|
#include "Core/Types.h"
|
||||||
#include "Core/Vector.h"
|
#include "Core/Vector.h"
|
||||||
#include "Math/Dense.h"
|
#include "Math/Dense.h"
|
||||||
@@ -46,10 +47,12 @@ class SteppingAction;
|
|||||||
/// recording the change of momentum and direction at each step boundary.
|
/// recording the change of momentum and direction at each step boundary.
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
class GeantEvent {
|
class GeantEvent : public Object {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
virtual const char* GetClassName() const override { return "Geant.GeantEvent"; }
|
||||||
|
|
||||||
/// A single interaction step along the muon path.
|
/// A single interaction step along the muon path.
|
||||||
struct Delta {
|
struct Delta {
|
||||||
Scalarf m_Length; ///< step length through the solid
|
Scalarf m_Length; ///< step length through the solid
|
||||||
@@ -73,6 +76,7 @@ public:
|
|||||||
|
|
||||||
void Print(const size_t size = 10) const;
|
void Print(const size_t size = 10) const;
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Id_t m_EventID;
|
Id_t m_EventID;
|
||||||
Scalarf m_Momentum;
|
Scalarf m_Momentum;
|
||||||
|
|||||||
@@ -59,6 +59,8 @@ private:
|
|||||||
class Material : public Object {
|
class Material : public Object {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
virtual const char* GetClassName() const override { return "Geant.Material"; }
|
||||||
|
|
||||||
uLibRefMacro(G4Data,G4Material *)
|
uLibRefMacro(G4Data,G4Material *)
|
||||||
private:
|
private:
|
||||||
G4Material *m_G4Data;
|
G4Material *m_G4Data;
|
||||||
|
|||||||
@@ -1,28 +1,3 @@
|
|||||||
/*//////////////////////////////////////////////////////////////////////////////
|
|
||||||
// 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 <Geant4/G4Box.hh>
|
#include <Geant4/G4Box.hh>
|
||||||
#include <Geant4/G4LogicalVolume.hh>
|
#include <Geant4/G4LogicalVolume.hh>
|
||||||
#include <Geant4/G4Material.hh>
|
#include <Geant4/G4Material.hh>
|
||||||
@@ -42,13 +17,15 @@
|
|||||||
#include "Scene.h"
|
#include "Scene.h"
|
||||||
#include "PhysicsList.hh"
|
#include "PhysicsList.hh"
|
||||||
#include "ActionInitialization.hh"
|
#include "ActionInitialization.hh"
|
||||||
|
#include "SimulationContext.h"
|
||||||
|
#include "HEP/Detectors/DetectorChamber.h"
|
||||||
|
|
||||||
namespace uLib {
|
namespace uLib {
|
||||||
namespace Geant {
|
namespace Geant {
|
||||||
|
|
||||||
class SceneDetectorConstruction : public DetectorConstruction {
|
class SceneDetectorConstruction : public DetectorConstruction {
|
||||||
public:
|
public:
|
||||||
SceneDetectorConstruction(class SceneImpl *owner);
|
SceneDetectorConstruction(class SceneImpl *owner) : DetectorConstruction("Scene"), m_Owner(owner) {}
|
||||||
G4VPhysicalVolume *Construct() override;
|
G4VPhysicalVolume *Construct() override;
|
||||||
private:
|
private:
|
||||||
class SceneImpl *m_Owner;
|
class SceneImpl *m_Owner;
|
||||||
@@ -69,53 +46,50 @@ static void CheckGeant4Environment() {
|
|||||||
|
|
||||||
class SceneImpl {
|
class SceneImpl {
|
||||||
public:
|
public:
|
||||||
// constructor //
|
|
||||||
SceneImpl() : m_RunManager(G4RunManagerFactory::CreateRunManager(G4RunManagerType::Serial)),
|
SceneImpl() : m_RunManager(G4RunManagerFactory::CreateRunManager(G4RunManagerType::Serial)),
|
||||||
m_Emitter(nullptr),
|
m_Emitter(nullptr),
|
||||||
m_Output(nullptr) {
|
m_InitCalled(false) {
|
||||||
m_RunManager->SetUserInitialization(new PhysicsList);
|
m_RunManager->SetUserInitialization(new PhysicsList);
|
||||||
}
|
}
|
||||||
|
|
||||||
// destructor //
|
|
||||||
~SceneImpl() {
|
~SceneImpl() {
|
||||||
if (m_RunManager) delete m_RunManager;
|
if (m_RunManager) delete m_RunManager;
|
||||||
if (m_World) delete m_World;
|
// m_World deletion is handled in Scene destructor or here
|
||||||
}
|
}
|
||||||
|
|
||||||
void Initialize() {
|
void Initialize() {
|
||||||
// Set mandatory initialization classes for Geant4
|
if (m_InitCalled) return;
|
||||||
|
|
||||||
m_RunManager->SetUserInitialization(new SceneDetectorConstruction(this));
|
m_RunManager->SetUserInitialization(new SceneDetectorConstruction(this));
|
||||||
m_RunManager->SetUserInitialization(
|
m_RunManager->SetUserInitialization(new ActionInitialization(m_Emitter, &m_Context));
|
||||||
new ActionInitialization(m_Emitter, m_Output));
|
|
||||||
|
|
||||||
// Initialize Geant4
|
|
||||||
m_RunManager->Initialize();
|
m_RunManager->Initialize();
|
||||||
|
m_InitCalled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// members //
|
|
||||||
Vector<Solid *> m_Solids;
|
Vector<Solid *> m_Solids;
|
||||||
Solid *m_World = nullptr;
|
Solid *m_World = nullptr;
|
||||||
ContainerBox m_WorldBox;
|
ContainerBox m_WorldBox;
|
||||||
G4RunManager *m_RunManager;
|
G4RunManager *m_RunManager;
|
||||||
EmitterPrimary *m_Emitter;
|
EmitterPrimary *m_Emitter;
|
||||||
Vector<GeantEvent> *m_Output;
|
SimulationContext m_Context;
|
||||||
|
bool m_InitCalled;
|
||||||
};
|
};
|
||||||
|
|
||||||
SceneDetectorConstruction::SceneDetectorConstruction(SceneImpl *owner)
|
|
||||||
: DetectorConstruction("Scene"), m_Owner(owner) {}
|
|
||||||
|
|
||||||
G4VPhysicalVolume *SceneDetectorConstruction::Construct() {
|
G4VPhysicalVolume *SceneDetectorConstruction::Construct() {
|
||||||
return m_Owner->m_World->GetPhysical();
|
return m_Owner->m_World->GetPhysical();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Scene::Scene() {
|
Scene::Scene() {
|
||||||
CheckGeant4Environment();
|
CheckGeant4Environment();
|
||||||
d = new SceneImpl();
|
d = new SceneImpl();
|
||||||
}
|
}
|
||||||
Scene::~Scene() { delete d; }
|
|
||||||
|
Scene::~Scene() {
|
||||||
|
// Delete solids
|
||||||
|
for(auto s : d->m_Solids) delete s;
|
||||||
|
delete d;
|
||||||
|
}
|
||||||
|
|
||||||
void Scene::AddSolid(Solid *solid, Solid *parent) {
|
void Scene::AddSolid(Solid *solid, Solid *parent) {
|
||||||
d->m_Solids.push_back(solid);
|
d->m_Solids.push_back(solid);
|
||||||
@@ -127,12 +101,10 @@ 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) {
|
||||||
// Get nist material manager
|
|
||||||
|
|
||||||
d->m_WorldBox.Scale(size);
|
d->m_WorldBox.Scale(size);
|
||||||
d->m_WorldBox.SetPosition(-size/2.0f);
|
d->m_WorldBox.SetPosition(-size/2.0f);
|
||||||
|
|
||||||
@@ -142,54 +114,48 @@ void Scene::ConstructWorldBox(const Vector3f &size, const char *material) {
|
|||||||
AddSolid(d->m_World);
|
AddSolid(d->m_World);
|
||||||
}
|
}
|
||||||
|
|
||||||
G4Box *solidWorld = new G4Box("World",
|
G4Box *solidWorld = new G4Box("World", 0.5 * size(0), 0.5 * size(1), 0.5 * size(2));
|
||||||
0.5 * size(0),
|
G4LogicalVolume *logicWorld = new G4LogicalVolume(solidWorld, d->m_World->GetMaterial(), d->m_World->GetName());
|
||||||
0.5 * size(1),
|
|
||||||
0.5 * size(2));
|
|
||||||
|
|
||||||
G4LogicalVolume *logicWorld = new G4LogicalVolume(solidWorld,
|
|
||||||
d->m_World->GetMaterial(),
|
|
||||||
d->m_World->GetName());
|
|
||||||
|
|
||||||
d->m_World->SetLogical(logicWorld);
|
d->m_World->SetLogical(logicWorld);
|
||||||
|
|
||||||
G4PVPlacement *physWorld = new G4PVPlacement(
|
G4PVPlacement *physWorld = new G4PVPlacement(nullptr, G4ThreeVector(0, 0, 0), logicWorld, d->m_World->GetName(), 0, false, 0, true);
|
||||||
nullptr,
|
|
||||||
G4ThreeVector(0, 0, 0),
|
|
||||||
logicWorld,
|
|
||||||
d->m_World->GetName(),
|
|
||||||
0,
|
|
||||||
false,
|
|
||||||
0,
|
|
||||||
true);
|
|
||||||
|
|
||||||
d->m_World->SetPhysical(physWorld);
|
d->m_World->SetPhysical(physWorld);
|
||||||
|
|
||||||
// no transforms are allowed for the world box
|
|
||||||
// Matrix4f transform = box->GetMatrix();
|
|
||||||
// d->m_World->SetTransform(transform);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Scene::SetEmitter(EmitterPrimary *emitter) {
|
void Scene::SetEmitter(EmitterPrimary *emitter) { d->m_Emitter = emitter; }
|
||||||
d->m_Emitter = emitter;
|
void Scene::Initialize() { d->Initialize(); }
|
||||||
}
|
void Scene::SetVerbosity(int level) {
|
||||||
|
d->m_Context.verbosity = level;
|
||||||
void Scene::Initialize() {
|
if (d->m_RunManager) d->m_RunManager->SetVerboseLevel(level);
|
||||||
d->Initialize();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Scene::RunSimulation(int nEvents, Vector<GeantEvent> &results) {
|
void Scene::RunSimulation(int nEvents, Vector<GeantEvent> &results) {
|
||||||
d->m_Output = &results;
|
d->Initialize(); // Ensure initialized
|
||||||
|
d->m_Context.mode = SimulationMode::DETAILED;
|
||||||
|
d->m_Context.outputGeant = &results;
|
||||||
|
d->m_Context.outputMuon = nullptr;
|
||||||
|
|
||||||
|
d->m_RunManager->BeamOn(nEvents);
|
||||||
|
}
|
||||||
|
|
||||||
// Re-initialize ActionInitialization with the output buffer
|
void Scene::RunDetectorSimulation(int nEvents, Vector<MuonEvent> &results) {
|
||||||
// (ActionInitialization was already set during Initialize, but we need
|
d->Initialize(); // Ensure initialized
|
||||||
// to ensure the output pointer is current)
|
d->m_Context.mode = SimulationMode::DETECTOR;
|
||||||
d->m_RunManager->SetUserInitialization(
|
d->m_Context.outputGeant = nullptr;
|
||||||
new ActionInitialization(d->m_Emitter, &results));
|
d->m_Context.outputMuon = &results;
|
||||||
|
|
||||||
|
// Find detector planes
|
||||||
|
d->m_Context.detectorPlanes.clear();
|
||||||
|
for (Solid* s : d->m_Solids) {
|
||||||
|
if (BoxSolid* bs = dynamic_cast<BoxSolid*>(s)) {
|
||||||
|
if (DetectorChamber* dc = dynamic_cast<DetectorChamber*>(bs->GetObject())) {
|
||||||
|
d->m_Context.detectorPlanes.push_back(dc->GetWorldProjectionPlane());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
d->m_RunManager->BeamOn(nEvents);
|
d->m_RunManager->BeamOn(nEvents);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Geant
|
} // namespace Geant
|
||||||
} // namespace uLib
|
} // namespace uLib
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
#include "Core/Vector.h"
|
#include "Core/Vector.h"
|
||||||
#include "Solid.h"
|
#include "Solid.h"
|
||||||
#include "GeantEvent.h"
|
#include "GeantEvent.h"
|
||||||
|
#include "HEP/Detectors/MuonEvent.h"
|
||||||
|
|
||||||
class G4VPhysicalVolume;
|
class G4VPhysicalVolume;
|
||||||
|
|
||||||
@@ -42,6 +43,9 @@ class EmitterPrimary;
|
|||||||
|
|
||||||
class Scene : public Object {
|
class Scene : public Object {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
virtual const char* GetClassName() const override { return "Geant.Scene"; }
|
||||||
|
|
||||||
Scene();
|
Scene();
|
||||||
~Scene();
|
~Scene();
|
||||||
|
|
||||||
@@ -54,17 +58,26 @@ 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);
|
||||||
|
|
||||||
/// Initialize the Geant4 run manager with detector, physics, and action.
|
/// Initialize the Geant4 run manager with detector, physics, and action.
|
||||||
void Initialize();
|
void Initialize();
|
||||||
|
|
||||||
|
/// Set the verbosity level for console output (default 0)
|
||||||
|
void SetVerbosity(int level);
|
||||||
|
|
||||||
/// Run the simulation for nEvents muons.
|
/// Run the simulation for nEvents muons.
|
||||||
/// Results are appended to the provided vector.
|
/// Results are appended to the provided vector.
|
||||||
void RunSimulation(int nEvents, Vector<GeantEvent> &results);
|
void RunSimulation(int nEvents, Vector<GeantEvent> &results);
|
||||||
|
|
||||||
|
/// Specialized detector simulation trackingMuonEvent line crossings.
|
||||||
|
void RunDetectorSimulation(int nEvents, Vector<MuonEvent> &results);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
class SceneImpl *d;
|
class SceneImpl *d;
|
||||||
};
|
};
|
||||||
|
|||||||
30
src/HEP/Geant/SimulationContext.h
Normal file
30
src/HEP/Geant/SimulationContext.h
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#ifndef U_GEANT_SIMULATIONCONTEXT_H
|
||||||
|
#define U_GEANT_SIMULATIONCONTEXT_H
|
||||||
|
|
||||||
|
#include "Core/Vector.h"
|
||||||
|
#include "GeantEvent.h"
|
||||||
|
#include "HEP/Detectors/MuonEvent.h"
|
||||||
|
#include "Math/Dense.h"
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
namespace uLib {
|
||||||
|
namespace Geant {
|
||||||
|
|
||||||
|
enum class SimulationMode {
|
||||||
|
DETAILED,
|
||||||
|
DETECTOR
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SimulationContext {
|
||||||
|
SimulationMode mode = SimulationMode::DETAILED;
|
||||||
|
Vector<GeantEvent> *outputGeant = nullptr;
|
||||||
|
Vector<MuonEvent> *outputMuon = nullptr;
|
||||||
|
Vector<HLine3f> detectorPlanes;
|
||||||
|
int verbosity = 0;
|
||||||
|
std::mutex outputMutex;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Geant
|
||||||
|
} // namespace uLib
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -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() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,9 @@ namespace Geant {
|
|||||||
|
|
||||||
class Solid : public Object {
|
class Solid : public Object {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
virtual const char* GetClassName() const override { return "Geant.Solid"; }
|
||||||
|
|
||||||
Solid();
|
Solid();
|
||||||
Solid(const char *name);
|
Solid(const char *name);
|
||||||
virtual ~Solid();
|
virtual ~Solid();
|
||||||
@@ -83,15 +86,21 @@ protected:
|
|||||||
class TessellatedSolid : public Solid {
|
class TessellatedSolid : public Solid {
|
||||||
typedef Solid BaseClass;
|
typedef Solid BaseClass;
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
virtual const char* GetClassName() const override { return "Geant.TessellatedSolid"; }
|
||||||
|
|
||||||
TessellatedSolid(const char *name);
|
TessellatedSolid(const char *name);
|
||||||
void SetMesh(TriangleMesh &mesh);
|
void SetMesh(TriangleMesh &mesh);
|
||||||
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;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -104,8 +113,13 @@ class BoxSolid : public Solid {
|
|||||||
typedef Solid BaseClass;
|
typedef Solid BaseClass;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
virtual const char* GetClassName() const override { return "Geant.BoxSolid"; }
|
||||||
|
|
||||||
BoxSolid(const char *name, ContainerBox *box);
|
BoxSolid(const char *name, ContainerBox *box);
|
||||||
virtual G4VSolid* GetG4Solid() const override { return (G4VSolid*)m_Solid; }
|
virtual G4VSolid* GetG4Solid() const override { return (G4VSolid*)m_Solid; }
|
||||||
|
|
||||||
|
ContainerBox* GetObject() const { return m_Object; }
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void Update();
|
void Update();
|
||||||
@@ -115,6 +129,12 @@ private:
|
|||||||
G4Box *m_Solid;
|
G4Box *m_Solid;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
} // namespace Geant
|
} // namespace Geant
|
||||||
} // namespace uLib
|
} // namespace uLib
|
||||||
|
|
||||||
|
|||||||
@@ -1,103 +1,124 @@
|
|||||||
#include "SteppingAction.hh"
|
#include "SteppingAction.hh"
|
||||||
|
#include <Geant4/G4Step.hh>
|
||||||
#include "G4Step.hh"
|
#include <Geant4/G4Track.hh>
|
||||||
#include "G4Track.hh"
|
#include <Geant4/G4Event.hh>
|
||||||
#include "G4Event.hh"
|
#include <Geant4/G4SystemOfUnits.hh>
|
||||||
#include "G4RunManager.hh"
|
|
||||||
#include "G4LogicalVolume.hh"
|
|
||||||
#include "G4SystemOfUnits.hh"
|
|
||||||
#include "G4ParticleDefinition.hh"
|
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <mutex>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
namespace uLib {
|
namespace uLib {
|
||||||
namespace Geant {
|
namespace Geant {
|
||||||
|
|
||||||
SteppingAction::SteppingAction(Vector<GeantEvent> *output)
|
SteppingAction::SteppingAction(SimulationContext *context)
|
||||||
: G4UserSteppingAction(),
|
: G4UserSteppingAction(),
|
||||||
m_Output(output),
|
G4UserEventAction(),
|
||||||
m_Current(),
|
m_Context(context),
|
||||||
m_LastEventID(-1)
|
m_Verbosity(0)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
SteppingAction::~SteppingAction() {}
|
SteppingAction::~SteppingAction() {}
|
||||||
|
|
||||||
void SteppingAction::BeginOfEventAction(const G4Event *event) {
|
void SteppingAction::BeginOfEventAction(const G4Event *event) {
|
||||||
if (!event || !m_Output) return;
|
if (!event || !m_Context) return;
|
||||||
|
|
||||||
// Start a new GeantEvent
|
if (m_Context->mode == SimulationMode::DETAILED) {
|
||||||
m_Current = GeantEvent();
|
m_CurrentGeant = GeantEvent();
|
||||||
m_Current.m_EventID = static_cast<Id_t>(event->GetEventID());
|
m_CurrentGeant.m_EventID = static_cast<Id_t>(event->GetEventID());
|
||||||
|
|
||||||
// Set initial momentum and generation vector from primary vertex
|
if (event->GetNumberOfPrimaryVertex() > 0) {
|
||||||
if (event->GetNumberOfPrimaryVertex() > 0) {
|
G4PrimaryVertex *vtx = event->GetPrimaryVertex(0);
|
||||||
G4PrimaryVertex *vtx = event->GetPrimaryVertex(0);
|
G4ThreeVector pos = vtx->GetPosition();
|
||||||
G4ThreeVector pos = vtx->GetPosition();
|
m_CurrentGeant.m_GenVector.origin = HPoint3f(pos.x(), pos.y(), pos.z());
|
||||||
m_Current.m_GenVector.origin = HPoint3f(pos.x(), pos.y(), pos.z());
|
|
||||||
|
|
||||||
if (vtx->GetNumberOfParticle() > 0) {
|
if (vtx->GetNumberOfParticle() > 0) {
|
||||||
G4PrimaryParticle *prim = vtx->GetPrimary(0);
|
G4PrimaryParticle *prim = vtx->GetPrimary(0);
|
||||||
G4ThreeVector mom = prim->GetMomentumDirection();
|
G4ThreeVector mom = prim->GetMomentumDirection();
|
||||||
m_Current.m_GenVector.direction = HVector3f(mom.x(), mom.y(), mom.z());
|
m_CurrentGeant.m_GenVector.direction = HVector3f(mom.x(), mom.y(), mom.z());
|
||||||
m_Current.m_Momentum = static_cast<Scalarf>(prim->GetTotalMomentum() / MeV);
|
m_CurrentGeant.m_Momentum = static_cast<Scalarf>(prim->GetTotalMomentum() / MeV);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Detector mode
|
||||||
|
m_MuonCrossCount = 0;
|
||||||
|
float nan = std::numeric_limits<float>::quiet_NaN();
|
||||||
|
m_CurrentMuon.LineIn().origin = HPoint3f(nan, nan, nan);
|
||||||
|
m_CurrentMuon.LineIn().direction = HVector3f(nan, nan, nan);
|
||||||
|
m_CurrentMuon.LineOut().origin = HPoint3f(nan, nan, nan);
|
||||||
|
m_CurrentMuon.LineOut().direction = HVector3f(nan, nan, nan);
|
||||||
|
m_CurrentMuon.Momentum() = nan;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SteppingAction::EndOfEventAction(const G4Event *event) {
|
void SteppingAction::EndOfEventAction(const G4Event *event) {
|
||||||
if (m_Output && !m_Current.m_Path.empty()) {
|
if (!m_Context) return;
|
||||||
std::cout << "[Geant] Finished Event " << m_Current.m_EventID
|
|
||||||
<< " with " << m_Current.m_Path.size() << " steps." << std::endl;
|
|
||||||
|
|
||||||
// Check if we hit anything other than World
|
|
||||||
std::set<std::string> volumes;
|
|
||||||
for (const auto& delta : m_Current.m_Path) {
|
|
||||||
if (!delta.m_SolidName.empty()) volumes.insert(delta.m_SolidName);
|
|
||||||
}
|
|
||||||
if (volumes.size() > 1) {
|
|
||||||
std::cout << " - Hit volumes: ";
|
|
||||||
for (const auto& v : volumes) std::cout << v << " ";
|
|
||||||
std::cout << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_Output->push_back(m_Current);
|
if (m_Context->mode == SimulationMode::DETAILED && m_Context->outputGeant) {
|
||||||
|
if (!m_CurrentGeant.m_Path.empty()) {
|
||||||
|
std::lock_guard<std::mutex> lock(m_Context->outputMutex);
|
||||||
|
m_Context->outputGeant->push_back(m_CurrentGeant);
|
||||||
|
}
|
||||||
|
} else if (m_Context->mode == SimulationMode::DETECTOR && m_Context->outputMuon) {
|
||||||
|
// In detector mode, we always push the event (to keep indexing consistent)
|
||||||
|
// or only if hit? User requested "all muon event line in and out ar at first set to nan".
|
||||||
|
// So we push everything.
|
||||||
|
std::lock_guard<std::mutex> lock(m_Context->outputMutex);
|
||||||
|
m_Context->outputMuon->push_back(m_CurrentMuon);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SteppingAction::UserSteppingAction(const G4Step *step) {
|
void SteppingAction::UserSteppingAction(const G4Step *step) {
|
||||||
if (!step || !m_Output) return;
|
if (!step || !m_Context) return;
|
||||||
|
|
||||||
const G4Track *track = step->GetTrack();
|
const G4Track *track = step->GetTrack();
|
||||||
if (!track) return;
|
if (!track || track->GetParentID() != 0) return;
|
||||||
|
|
||||||
// Only record primary particle (muon)
|
if (m_Context->mode == SimulationMode::DETAILED) {
|
||||||
if (track->GetParentID() != 0) return;
|
GeantEvent::Delta delta;
|
||||||
|
delta.m_Length = static_cast<Scalarf>(step->GetStepLength() / mm);
|
||||||
|
delta.m_Momentum = static_cast<Scalarf>(track->GetMomentum().mag() / MeV);
|
||||||
|
G4ThreeVector dir = track->GetMomentumDirection();
|
||||||
|
delta.m_Direction = HVector3f(dir.x(), dir.y(), dir.z());
|
||||||
|
|
||||||
// Record a Delta for this step
|
if (track->GetVolume()) {
|
||||||
GeantEvent::Delta delta;
|
delta.m_SolidName = track->GetVolume()->GetName();
|
||||||
|
}
|
||||||
|
m_CurrentGeant.m_Path.push_back(delta);
|
||||||
|
} else {
|
||||||
|
// Detector Mode
|
||||||
|
if (std::isnan(m_CurrentMuon.Momentum())) {
|
||||||
|
m_CurrentMuon.Momentum() = static_cast<Scalarf>(track->GetMomentum().mag() / MeV);
|
||||||
|
}
|
||||||
|
|
||||||
// Step length
|
G4ThreeVector p1 = step->GetPreStepPoint()->GetPosition();
|
||||||
delta.m_Length = static_cast<Scalarf>(step->GetStepLength() / mm);
|
G4ThreeVector p2 = step->GetPostStepPoint()->GetPosition();
|
||||||
|
G4ThreeVector dir_g4 = track->GetMomentumDirection();
|
||||||
|
|
||||||
|
HPoint3f p1f(p1.x(), p1.y(), p1.z());
|
||||||
|
HPoint3f p2f(p2.x(), p2.y(), p2.z());
|
||||||
|
HVector3f dirf(dir_g4.x(), dir_g4.y(), dir_g4.z());
|
||||||
|
|
||||||
// Post-step momentum
|
for (const auto& plane : m_Context->detectorPlanes) {
|
||||||
G4ThreeVector postMom = track->GetMomentum();
|
float d1 = (p1f - plane.origin).dot(plane.direction);
|
||||||
delta.m_Momentum = static_cast<Scalarf>(postMom.mag() / MeV);
|
float d2 = (p2f - plane.origin).dot(plane.direction);
|
||||||
|
|
||||||
// Post-step direction
|
if ((d1 > 0 && d2 <= 0) || (d1 < 0 && d2 >= 0)) {
|
||||||
G4ThreeVector dir = track->GetMomentumDirection();
|
float t = d1 / (d1 - d2);
|
||||||
delta.m_Direction = HVector3f(static_cast<float>(dir.x()),
|
HPoint3f intersection = p1f + t * (p2f - p1f);
|
||||||
static_cast<float>(dir.y()),
|
|
||||||
static_cast<float>(dir.z()));
|
|
||||||
|
|
||||||
// Solid name where the step occurred
|
if (m_MuonCrossCount == 0) {
|
||||||
const G4LogicalVolume *vol = track->GetVolume()
|
m_CurrentMuon.LineIn().origin = intersection;
|
||||||
? track->GetVolume()->GetLogicalVolume()
|
m_CurrentMuon.LineIn().direction = dirf;
|
||||||
: nullptr;
|
m_MuonCrossCount++;
|
||||||
if (vol) {
|
} else if (m_MuonCrossCount == 1) {
|
||||||
delta.m_SolidName = vol->GetName();
|
m_CurrentMuon.LineOut().origin = intersection;
|
||||||
|
m_CurrentMuon.LineOut().direction = dirf;
|
||||||
|
m_MuonCrossCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_Current.m_Path.push_back(delta);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Geant
|
} // namespace Geant
|
||||||
|
|||||||
@@ -5,26 +5,30 @@
|
|||||||
#include "G4UserEventAction.hh"
|
#include "G4UserEventAction.hh"
|
||||||
#include "Core/Vector.h"
|
#include "Core/Vector.h"
|
||||||
#include "GeantEvent.h"
|
#include "GeantEvent.h"
|
||||||
|
#include "HEP/Detectors/MuonEvent.h"
|
||||||
|
#include "SimulationContext.h"
|
||||||
|
|
||||||
namespace uLib {
|
namespace uLib {
|
||||||
namespace Geant {
|
namespace Geant {
|
||||||
|
|
||||||
/// SteppingAction collects scattering data at each Geant4 step and
|
/// SteppingAction collects scattering data at each Geant4 step.
|
||||||
/// builds GeantEvent objects in the output buffer.
|
|
||||||
class SteppingAction : public G4UserSteppingAction, public G4UserEventAction {
|
class SteppingAction : public G4UserSteppingAction, public G4UserEventAction {
|
||||||
public:
|
public:
|
||||||
/// @param output pointer to the results vector owned by the Scene
|
SteppingAction(SimulationContext *context);
|
||||||
SteppingAction(Vector<GeantEvent> *output);
|
|
||||||
virtual ~SteppingAction();
|
virtual ~SteppingAction();
|
||||||
|
|
||||||
virtual void UserSteppingAction(const G4Step *step) override;
|
virtual void UserSteppingAction(const G4Step *step) override;
|
||||||
virtual void BeginOfEventAction(const G4Event *event) override;
|
virtual void BeginOfEventAction(const G4Event *event) override;
|
||||||
virtual void EndOfEventAction(const G4Event *event) override;
|
virtual void EndOfEventAction(const G4Event *event) override;
|
||||||
|
|
||||||
|
void SetVerbosity(int level) { m_Verbosity = level; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Vector<GeantEvent> *m_Output; ///< destination for finished events
|
SimulationContext *m_Context;
|
||||||
GeantEvent m_Current; ///< event being built
|
GeantEvent m_CurrentGeant;
|
||||||
int m_LastEventID; ///< track event transitions
|
MuonEvent m_CurrentMuon;
|
||||||
|
int m_MuonCrossCount = 0;
|
||||||
|
int m_Verbosity = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Geant
|
} // namespace Geant
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ int main(int argc, char **argv) {
|
|||||||
// runManager->SetUserInitialization(new PhysicsList());
|
// runManager->SetUserInitialization(new PhysicsList());
|
||||||
|
|
||||||
// 3. INIZIALIZZAZIONE DELLE AZIONI (Il nostro generatore!)
|
// 3. INIZIALIZZAZIONE DELLE AZIONI (Il nostro generatore!)
|
||||||
runManager->SetUserInitialization(new uLib::Geant::ActionInitialization());
|
runManager->SetUserInitialization(new uLib::Geant::ActionInitialization(nullptr, nullptr));
|
||||||
|
|
||||||
// ... Inizializzazione del kernel ( runManager->Initialize(); ), UI manager,
|
// ... Inizializzazione del kernel ( runManager->Initialize(); ), UI manager,
|
||||||
// vis manager, ecc.
|
// vis manager, ecc.
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ set(TESTS
|
|||||||
EventTest
|
EventTest
|
||||||
GeantApp
|
GeantApp
|
||||||
ActionInitialization
|
ActionInitialization
|
||||||
|
SkyPlaneEmitterTest
|
||||||
)
|
)
|
||||||
|
|
||||||
set(LIBRARIES
|
set(LIBRARIES
|
||||||
|
|||||||
104
src/HEP/Geant/testing/SkyPlaneEmitterTest.cpp
Normal file
104
src/HEP/Geant/testing/SkyPlaneEmitterTest.cpp
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
#include "Geant/Solid.h"
|
||||||
|
#include "HEP/Geant/GeantEvent.h"
|
||||||
|
#include "HEP/Geant/Scene.h"
|
||||||
|
#include "HEP/Geant/EmitterPrimary.hh"
|
||||||
|
#include "Math/ContainerBox.h"
|
||||||
|
#include "Math/Dense.h"
|
||||||
|
#include "Math/Units.h"
|
||||||
|
#include "HEP/Detectors/DetectorChamber.h"
|
||||||
|
|
||||||
|
#include <Geant4/G4SystemOfUnits.hh>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
using namespace uLib;
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
|
||||||
|
int nEvents = 10000;
|
||||||
|
if (argc > 1) {
|
||||||
|
nEvents = std::stoi(argv[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Setup Geant4 Scene
|
||||||
|
Geant::Scene scene;
|
||||||
|
scene.ConstructWorldBox(Vector3f(30_m, 30_m, 30_m), "G4_AIR");
|
||||||
|
|
||||||
|
ContainerBox iron_box;
|
||||||
|
iron_box.Scale(Vector3f(18_m, 10_cm, 18_m));
|
||||||
|
iron_box.SetPosition(Vector3f(-9_m, -5_cm, -9_m));
|
||||||
|
Geant::BoxSolid* iron_cube = new Geant::BoxSolid("IronCube", &iron_box);
|
||||||
|
iron_cube->SetNistMaterial("G4_Fe");
|
||||||
|
iron_cube->Update();
|
||||||
|
scene.AddSolid(iron_cube);
|
||||||
|
|
||||||
|
// Top Detector Chamber (along Y axis)
|
||||||
|
DetectorChamber* top_chamber_box = new DetectorChamber();
|
||||||
|
top_chamber_box->Scale(Vector3f(20_m, 40_cm, 20_m));
|
||||||
|
top_chamber_box->Rotate(90_deg, Vector3f(1, 0, 0));
|
||||||
|
top_chamber_box->SetPosition(Vector3f(-10_m, 12_m, -10_m));
|
||||||
|
Geant::BoxSolid* top_chamber = new Geant::BoxSolid("TopChamber", top_chamber_box);
|
||||||
|
top_chamber->SetNistMaterial("G4_AIR");
|
||||||
|
top_chamber->Update();
|
||||||
|
scene.AddSolid(top_chamber);
|
||||||
|
|
||||||
|
// Bottom Detector Chamber (along Y axis)
|
||||||
|
DetectorChamber* bottom_chamber_box = new DetectorChamber();
|
||||||
|
bottom_chamber_box->Scale(Vector3f(20_m, 40_cm, 20_m));
|
||||||
|
bottom_chamber_box->Rotate(90_deg, Vector3f(1, 0, 0));
|
||||||
|
bottom_chamber_box->SetPosition(Vector3f(-10_m, -12_m, -10_m));
|
||||||
|
Geant::BoxSolid* bottom_chamber = new Geant::BoxSolid("BottomChamber", bottom_chamber_box);
|
||||||
|
bottom_chamber->SetNistMaterial("G4_AIR");
|
||||||
|
bottom_chamber->Update();
|
||||||
|
scene.AddSolid(bottom_chamber);
|
||||||
|
|
||||||
|
// Setup SkyPlaneEmitterPrimary
|
||||||
|
Geant::SkyPlaneEmitterPrimary* emitter = new Geant::SkyPlaneEmitterPrimary();
|
||||||
|
emitter->SetPosition(Vector3f(0, 14.9_m, 0));
|
||||||
|
emitter->Rotate(-90_deg, Vector3f(1, 0, 0));
|
||||||
|
emitter->SetSkySize(Vector2f(20_m, 20_m));
|
||||||
|
|
||||||
|
scene.SetEmitter(emitter);
|
||||||
|
scene.SetVerbosity(1);
|
||||||
|
// scene.Initialize(); // Removed to avoid premature initialization
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::cout << "Starting simulation of " << nEvents << " events..." << std::endl;
|
||||||
|
Vector<Geant::GeantEvent> results;
|
||||||
|
scene.RunSimulation(nEvents, results);
|
||||||
|
|
||||||
|
std::cout << "Simulation finished. Collected " << results.size() << " events." << std::endl;
|
||||||
|
|
||||||
|
// Sample output to verify data collection
|
||||||
|
if (!results.empty()) {
|
||||||
|
std::cout << "Summary: " << std::endl;
|
||||||
|
std::cout << " Total events generated: " << results.size() << std::endl;
|
||||||
|
size_t total_steps = 0;
|
||||||
|
for (const auto& event : results) {
|
||||||
|
total_steps += event.Path().size();
|
||||||
|
}
|
||||||
|
std::cout << " Total simulation steps: " << total_steps << std::endl;
|
||||||
|
std::cout << " Average steps per event: " << static_cast<double>(total_steps) / results.size() << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "\nStarting Detector Simulation of " << nEvents << " events..." << std::endl;
|
||||||
|
Vector<MuonEvent> detectorResults;
|
||||||
|
scene.RunDetectorSimulation(nEvents, detectorResults);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::cout << "Detector Simulation finished." << std::endl;
|
||||||
|
size_t hit_count = 0;
|
||||||
|
for (const auto& ev : detectorResults) {
|
||||||
|
if (!std::isnan(ev.LineIn().origin.x())) {
|
||||||
|
hit_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::cout << " Muons crossing at least one detector: " << hit_count << std::endl;
|
||||||
|
if (nEvents > 0) {
|
||||||
|
std::cout << " Efficiency: " << (100.0 * hit_count / nEvents) << "%" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
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,5 +1,7 @@
|
|||||||
|
|
||||||
set(HEADERS ContainerBox.h
|
set(HEADERS ContainerBox.h
|
||||||
|
Cylinder.h
|
||||||
|
Assembly.h
|
||||||
Dense.h
|
Dense.h
|
||||||
Geometry.h
|
Geometry.h
|
||||||
Transform.h
|
Transform.h
|
||||||
@@ -32,9 +34,11 @@ 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
|
||||||
|
MathRegistrations.cpp)
|
||||||
|
|
||||||
set(LIBRARIES ${PACKAGE_LIBPREFIX}Core
|
set(LIBRARIES ${PACKAGE_LIBPREFIX}Core
|
||||||
Eigen3::Eigen
|
Eigen3::Eigen
|
||||||
@@ -69,4 +73,3 @@ if(BUILD_TESTING)
|
|||||||
include(uLibTargetMacros)
|
include(uLibTargetMacros)
|
||||||
add_subdirectory(testing)
|
add_subdirectory(testing)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
|
|
||||||
#include "Geometry.h"
|
#include "Geometry.h"
|
||||||
#include "Core/Object.h"
|
#include "Core/Object.h"
|
||||||
|
#include "Core/Property.h"
|
||||||
#include "Math/Dense.h"
|
#include "Math/Dense.h"
|
||||||
#include "Math/Transform.h"
|
#include "Math/Transform.h"
|
||||||
#include <utility>
|
#include <utility>
|
||||||
@@ -48,42 +49,65 @@ class ContainerBox : public AffineTransform, public Object {
|
|||||||
typedef AffineTransform BaseClass;
|
typedef AffineTransform BaseClass;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// PROPERTIES //
|
||||||
|
Property<Vector3f> p_Size;
|
||||||
|
Property<Vector3f> p_Origin;
|
||||||
|
virtual const char * GetClassName() const { return "ContainerBox"; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Default constructor.
|
* @brief Default constructor.
|
||||||
* Initializes the local transformation with this instance as its parent.
|
* Initializes the local transformation with this instance as its parent.
|
||||||
*/
|
*/
|
||||||
ContainerBox()
|
ContainerBox()
|
||||||
: m_LocalT(this) // BaseClass is Parent of m_LocalTransform
|
: m_LocalT(this), // BaseClass is Parent of m_LocalTransform
|
||||||
{}
|
p_Size(this, "Size", Vector3f(1.0f, 1.0f, 1.0f)),
|
||||||
|
p_Origin(this, "Origin", Vector3f(0.0f, 0.0f, 0.0f)) {
|
||||||
|
Object::connect(&p_Size, &Property<Vector3f>::PropertyChanged, this, &ContainerBox::SyncSize);
|
||||||
|
Object::connect(&p_Origin, &Property<Vector3f>::PropertyChanged, this, &ContainerBox::SyncOrigin);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Constructor with size.
|
* @brief Constructor with size.
|
||||||
* @param size The size vector.
|
* @param size The size vector.
|
||||||
*/
|
*/
|
||||||
ContainerBox(const Vector3f &size) : m_LocalT(this) { this->SetSize(size); }
|
ContainerBox(const Vector3f &size)
|
||||||
|
: m_LocalT(this),
|
||||||
|
p_Size(this, "Size", size),
|
||||||
|
p_Origin(this, "Origin", Vector3f(0.0f, 0.0f, 0.0f)) {
|
||||||
|
Object::connect(&p_Size, &Property<Vector3f>::PropertyChanged, this, &ContainerBox::SyncSize);
|
||||||
|
Object::connect(&p_Origin, &Property<Vector3f>::PropertyChanged, this, &ContainerBox::SyncOrigin);
|
||||||
|
this->SetSize(size);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Copy constructor.
|
* @brief Copy constructor.
|
||||||
* @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),
|
||||||
this->SetOrigin(copy.GetOrigin());
|
p_Size(this, "Size", copy.p_Size),
|
||||||
this->SetSize(copy.GetSize());
|
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_Origin, &Property<Vector3f>::PropertyChanged, this, &ContainerBox::SyncOrigin);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Sets the box origin relative to its coordinate system.
|
* @brief Sets the box origin relative to its coordinate system.
|
||||||
* @param v The origin position vector.
|
* @param v The origin position vector.
|
||||||
*/
|
*/
|
||||||
inline void SetOrigin(const Vector3f &v) { m_LocalT.SetPosition(v); }
|
void SetOrigin(const Vector3f &v) {
|
||||||
|
p_Origin = v;
|
||||||
|
m_LocalT.SetPosition(v);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Gets the box origin relative to its coordinate system.
|
* @brief Gets the box origin relative to its coordinate system.
|
||||||
* @return The origin position vector.
|
* @return The origin position vector.
|
||||||
*/
|
*/
|
||||||
inline Vector3f GetOrigin() const { return m_LocalT.GetPosition(); }
|
Vector3f GetOrigin() const { return m_LocalT.GetPosition(); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Sets the size of the box.
|
* @brief Sets the size of the box.
|
||||||
@@ -91,6 +115,7 @@ public:
|
|||||||
* @param v The size vector (width, height, depth).
|
* @param v The size vector (width, height, depth).
|
||||||
*/
|
*/
|
||||||
void SetSize(const Vector3f &v) {
|
void SetSize(const Vector3f &v) {
|
||||||
|
p_Size = v;
|
||||||
Vector3f pos = this->GetOrigin();
|
Vector3f pos = this->GetOrigin();
|
||||||
m_LocalT = AffineTransform(this); // regenerate local transform
|
m_LocalT = AffineTransform(this); // regenerate local transform
|
||||||
m_LocalT.Scale(v);
|
m_LocalT.Scale(v);
|
||||||
@@ -101,7 +126,7 @@ public:
|
|||||||
* @brief Gets the current size (scale) of the box.
|
* @brief Gets the current size (scale) of the box.
|
||||||
* @return The size vector.
|
* @return The size vector.
|
||||||
*/
|
*/
|
||||||
inline Vector3f GetSize() const {
|
Vector3f GetSize() const {
|
||||||
Vector3f s = this->GetScale();
|
Vector3f s = this->GetScale();
|
||||||
Vector3f ls = m_LocalT.GetScale();
|
Vector3f ls = m_LocalT.GetScale();
|
||||||
return Vector3f(s(0) * ls(0), s(1) * ls(1), s(2) * ls(2));
|
return Vector3f(s(0) * ls(0), s(1) * ls(1), s(2) * ls(2));
|
||||||
@@ -112,7 +137,7 @@ public:
|
|||||||
* @param first Index of the first axis (0=X, 1=Y, 2=Z).
|
* @param first Index of the first axis (0=X, 1=Y, 2=Z).
|
||||||
* @param second Index of the second axis (0=X, 1=Y, 2=Z).
|
* @param second Index of the second axis (0=X, 1=Y, 2=Z).
|
||||||
*/
|
*/
|
||||||
inline void FlipLocalAxes(int first, int second) {
|
void FlipLocalAxes(int first, int second) {
|
||||||
m_LocalT.FlipAxes(first, second);
|
m_LocalT.FlipAxes(first, second);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,7 +158,7 @@ public:
|
|||||||
* @param v The local point (4D homogeneous vector).
|
* @param v The local point (4D homogeneous vector).
|
||||||
* @return The transformed point in world space.
|
* @return The transformed point in world space.
|
||||||
*/
|
*/
|
||||||
inline Vector4f GetWorldPoint(const Vector4f &v) const {
|
Vector4f GetWorldPoint(const Vector4f &v) const {
|
||||||
return m_LocalT.GetWorldMatrix() * v;
|
return m_LocalT.GetWorldMatrix() * v;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,7 +169,7 @@ public:
|
|||||||
* @param z Z coordinate in local space.
|
* @param z Z coordinate in local space.
|
||||||
* @return The transformed point in world space.
|
* @return The transformed point in world space.
|
||||||
*/
|
*/
|
||||||
inline Vector4f GetWorldPoint(const float x, const float y, const float z) {
|
Vector4f GetWorldPoint(const float x, const float y, const float z) {
|
||||||
return this->GetWorldPoint(Vector4f(x, y, z, 1));
|
return this->GetWorldPoint(Vector4f(x, y, z, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,7 +178,7 @@ public:
|
|||||||
* @param v The world point (4D homogeneous vector).
|
* @param v The world point (4D homogeneous vector).
|
||||||
* @return The transformed point in box-local space.
|
* @return The transformed point in box-local space.
|
||||||
*/
|
*/
|
||||||
inline Vector4f GetLocalPoint(const Vector4f &v) const {
|
Vector4f GetLocalPoint(const Vector4f &v) const {
|
||||||
return m_LocalT.GetWorldMatrix().inverse() * v;
|
return m_LocalT.GetWorldMatrix().inverse() * v;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,7 +189,7 @@ public:
|
|||||||
* @param z Z coordinate in world space.
|
* @param z Z coordinate in world space.
|
||||||
* @return The transformed point in box-local space.
|
* @return The transformed point in box-local space.
|
||||||
*/
|
*/
|
||||||
inline Vector4f GetLocalPoint(const float x, const float y, const float z) {
|
Vector4f GetLocalPoint(const float x, const float y, const float z) {
|
||||||
return this->GetLocalPoint(Vector4f(x, y, z, 1));
|
return this->GetLocalPoint(Vector4f(x, y, z, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,6 +207,16 @@ signals:
|
|||||||
// signal to emit when the box is updated //
|
// signal to emit when the box is updated //
|
||||||
virtual void Updated() override { ULIB_SIGNAL_EMIT(ContainerBox::Updated); }
|
virtual void Updated() override { ULIB_SIGNAL_EMIT(ContainerBox::Updated); }
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void SyncSize() {
|
||||||
|
this->SetSize(p_Size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SyncOrigin() {
|
||||||
|
this->SetOrigin(p_Origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AffineTransform m_LocalT;
|
AffineTransform m_LocalT;
|
||||||
};
|
};
|
||||||
|
|||||||
184
src/Math/Cylinder.h
Normal file
184
src/Math/Cylinder.h
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
/*//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// 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_CYLINDER_H
|
||||||
|
#define U_CYLINDER_H
|
||||||
|
|
||||||
|
#include "Geometry.h"
|
||||||
|
#include "Core/Object.h"
|
||||||
|
#include "Math/Dense.h"
|
||||||
|
#include "Math/Transform.h"
|
||||||
|
|
||||||
|
namespace uLib {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Represents a cylindrical volume.
|
||||||
|
*
|
||||||
|
* The cylinder orientation is defined by the Axis property (0=X, 1=Y, 2=Z).
|
||||||
|
* By default, it is aligned with the Y axis (Axis=1).
|
||||||
|
*/
|
||||||
|
class Cylinder : public AffineTransform, public Object {
|
||||||
|
|
||||||
|
public:
|
||||||
|
uLibTypeMacro(Cylinder, Object)
|
||||||
|
|
||||||
|
virtual const char * GetClassName() const override { return "Cylinder"; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Default constructor. Aligns with Y by default.
|
||||||
|
*/
|
||||||
|
Cylinder() : m_LocalT(this), Radius(1.0), Height(1.0), Axis(1) {
|
||||||
|
ULIB_ACTIVATE_PROPERTIES(*this);
|
||||||
|
UpdateLocalMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructor with radius and height.
|
||||||
|
*/
|
||||||
|
Cylinder(float radius, float height, int axis = 1)
|
||||||
|
: m_LocalT(this), Radius(radius), Height(height), Axis(axis) {
|
||||||
|
ULIB_ACTIVATE_PROPERTIES(*this);
|
||||||
|
UpdateLocalMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Copy constructor.
|
||||||
|
*/
|
||||||
|
Cylinder(const Cylinder ©)
|
||||||
|
: m_LocalT(this), AffineTransform(copy), Radius(copy.Radius), Height(copy.Height), Axis(copy.Axis) {
|
||||||
|
ULIB_ACTIVATE_PROPERTIES(*this);
|
||||||
|
this->UpdateLocalMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Serialization template for property registration and persistence.
|
||||||
|
*/
|
||||||
|
template <class ArchiveT>
|
||||||
|
void serialize(ArchiveT & ar, const unsigned int version) {
|
||||||
|
ar & HRP(Radius);
|
||||||
|
ar & HRP(Height);
|
||||||
|
ar & HRP(Axis);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets the radius of the cylinder */
|
||||||
|
inline void SetRadius(float r) {
|
||||||
|
Radius = r;
|
||||||
|
UpdateLocalMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets the radius of the cylinder */
|
||||||
|
inline float GetRadius() const { return Radius; }
|
||||||
|
|
||||||
|
/** Sets the height of the cylinder */
|
||||||
|
inline void SetHeight(float h) {
|
||||||
|
Height = h;
|
||||||
|
UpdateLocalMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets the height of the cylinder */
|
||||||
|
inline float GetHeight() const { return Height; }
|
||||||
|
|
||||||
|
/** Sets the main axis (0=X, 1=Y, 2=Z) */
|
||||||
|
inline void SetAxis(int axis) {
|
||||||
|
Axis = axis;
|
||||||
|
UpdateLocalMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets the main axis */
|
||||||
|
inline int GetAxis() const { return Axis; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the world transformation matrix.
|
||||||
|
*/
|
||||||
|
Matrix4f GetWorldMatrix() const { return m_LocalT.GetWorldMatrix(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the local transformation matrix.
|
||||||
|
*/
|
||||||
|
Matrix4f GetLocalMatrix() const { return m_LocalT.GetMatrix(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Transforms local cylindrical coordinates to world space.
|
||||||
|
* @param r Local radius.
|
||||||
|
* @param theta Local angle in radians (around main axis).
|
||||||
|
* @param h Local height along main axis.
|
||||||
|
*/
|
||||||
|
inline Vector4f GetWorldPoint(float r, float theta, float h) const {
|
||||||
|
Vector3f p;
|
||||||
|
if (Axis == 0) p = Vector3f(h, r * std::cos(theta), r * std::sin(theta));
|
||||||
|
else if (Axis == 1) p = Vector3f(r * std::cos(theta), h, r * std::sin(theta));
|
||||||
|
else p = Vector3f(r * std::cos(theta), r * std::sin(theta), h);
|
||||||
|
|
||||||
|
return AffineTransform::GetWorldMatrix() * Vector4f(p.x(), p.y(), p.z(), 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Transforms a world point to cylindrical local space.
|
||||||
|
* @return Vector3f(r, theta, h)
|
||||||
|
*/
|
||||||
|
inline Vector3f GetCylindricalLocal(const Vector4f &world_v) const {
|
||||||
|
Vector4f local_v = AffineTransform::GetWorldMatrix().inverse() * world_v;
|
||||||
|
float r, theta, h;
|
||||||
|
if (Axis == 0) {
|
||||||
|
h = local_v.x();
|
||||||
|
r = std::sqrt(local_v.y() * local_v.y() + local_v.z() * local_v.z());
|
||||||
|
theta = std::atan2(local_v.z(), local_v.y());
|
||||||
|
} else if (Axis == 1) {
|
||||||
|
h = local_v.y();
|
||||||
|
r = std::sqrt(local_v.x() * local_v.x() + local_v.z() * local_v.z());
|
||||||
|
theta = std::atan2(local_v.z(), local_v.x());
|
||||||
|
} else {
|
||||||
|
h = local_v.z();
|
||||||
|
r = std::sqrt(local_v.x() * local_v.x() + local_v.y() * local_v.y());
|
||||||
|
theta = std::atan2(local_v.y(), local_v.x());
|
||||||
|
}
|
||||||
|
return Vector3f(r, theta, h);
|
||||||
|
}
|
||||||
|
|
||||||
|
signals:
|
||||||
|
/** Signal emitted when properties change */
|
||||||
|
virtual void Updated() override {
|
||||||
|
this->UpdateLocalMatrix();
|
||||||
|
ULIB_SIGNAL_EMIT(Cylinder::Updated);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
/** Recalculates the internal local matrix based on dimensions and axis */
|
||||||
|
void UpdateLocalMatrix() {
|
||||||
|
m_LocalT = AffineTransform(this);
|
||||||
|
if (Axis == 0) m_LocalT.Scale(Vector3f(Height, Radius, Radius));
|
||||||
|
else if (Axis == 1) m_LocalT.Scale(Vector3f(Radius, Height, Radius));
|
||||||
|
else m_LocalT.Scale(Vector3f(Radius, Radius, Height));
|
||||||
|
}
|
||||||
|
|
||||||
|
float Radius;
|
||||||
|
float Height;
|
||||||
|
int Axis;
|
||||||
|
AffineTransform m_LocalT;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace uLib
|
||||||
|
|
||||||
|
#endif // U_CYLINDER_H
|
||||||
@@ -51,6 +51,8 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
#include <Eigen/Dense>
|
#include <Eigen/Dense>
|
||||||
|
#include "Core/Types.h"
|
||||||
|
#include "Core/Property.h"
|
||||||
|
|
||||||
//// BOOST SERIALIZATION ///////////////////////////////////////////////////////
|
//// BOOST SERIALIZATION ///////////////////////////////////////////////////////
|
||||||
|
|
||||||
@@ -107,7 +109,6 @@ std::ostream &operator<<(std::ostream &os,
|
|||||||
namespace uLib {
|
namespace uLib {
|
||||||
|
|
||||||
typedef id_t Id_t;
|
typedef id_t Id_t;
|
||||||
|
|
||||||
typedef int Scalari;
|
typedef int Scalari;
|
||||||
typedef unsigned int Scalarui;
|
typedef unsigned int Scalarui;
|
||||||
typedef long Scalarl;
|
typedef long Scalarl;
|
||||||
@@ -249,15 +250,53 @@ struct _HError3f {
|
|||||||
HVector3f position_error;
|
HVector3f position_error;
|
||||||
HVector3f direction_error;
|
HVector3f direction_error;
|
||||||
};
|
};
|
||||||
typedef struct _HError3f HError3f;
|
typedef struct _HError3f HError3f;
|
||||||
|
|
||||||
|
inline std::ostream &operator<<(std::ostream &stream, const HError3f &err) {
|
||||||
|
stream << "HError3f(" << "ept[" << err.position_error.transpose()
|
||||||
|
<< "] , edr[" << err.direction_error.transpose() << "]) ";
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef Property<Scalari> ScalariProperty;
|
||||||
|
typedef Property<Scalarui> ScalaruiProperty;
|
||||||
|
typedef Property<Scalarl> ScalarlProperty;
|
||||||
|
typedef Property<Scalarul> ScalarulProperty;
|
||||||
|
typedef Property<Scalarf> ScalarfProperty;
|
||||||
|
typedef Property<Scalard> ScalardProperty;
|
||||||
|
|
||||||
inline std::ostream &operator<<(std::ostream &stream, const HError3f &err) {
|
typedef Property<Vector1i> Vector1iProperty;
|
||||||
stream << "HError3f(" << "ept[" << err.position_error.transpose()
|
typedef Property<Vector1f> Vector1fProperty;
|
||||||
<< "] , edr[" << err.direction_error.transpose() << "]) ";
|
typedef Property<Vector1d> Vector1dProperty;
|
||||||
return stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace uLib
|
typedef Property<Vector2i> Vector2iProperty;
|
||||||
|
typedef Property<Vector3i> Vector3iProperty;
|
||||||
|
typedef Property<Vector4i> Vector4iProperty;
|
||||||
|
|
||||||
|
typedef Property<Vector2f> Vector2fProperty;
|
||||||
|
typedef Property<Vector3f> Vector3fProperty;
|
||||||
|
typedef Property<Vector4f> Vector4fProperty;
|
||||||
|
|
||||||
|
typedef Property<Vector2d> Vector2dProperty;
|
||||||
|
typedef Property<Vector3d> Vector3dProperty;
|
||||||
|
typedef Property<Vector4d> Vector4dProperty;
|
||||||
|
|
||||||
|
typedef Property<Matrix2i> Matrix2iProperty;
|
||||||
|
typedef Property<Matrix3i> Matrix3iProperty;
|
||||||
|
typedef Property<Matrix4i> Matrix4iProperty;
|
||||||
|
|
||||||
|
typedef Property<Matrix2f> Matrix2fProperty;
|
||||||
|
typedef Property<Matrix3f> Matrix3fProperty;
|
||||||
|
typedef Property<Matrix4f> Matrix4fProperty;
|
||||||
|
|
||||||
|
typedef Property<Matrix2d> Matrix2dProperty;
|
||||||
|
typedef Property<Matrix3d> Matrix3dProperty;
|
||||||
|
typedef Property<Matrix4d> Matrix4dProperty;
|
||||||
|
|
||||||
|
typedef Property<HVector3f> HVector3fProperty;
|
||||||
|
typedef Property<HPoint3f> HPoint3fProperty;
|
||||||
|
|
||||||
|
} // namespace uLib
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|||||||
@@ -28,16 +28,29 @@
|
|||||||
#ifndef U_GEOMETRY_H
|
#ifndef U_GEOMETRY_H
|
||||||
#define U_GEOMETRY_H
|
#define U_GEOMETRY_H
|
||||||
|
|
||||||
|
#include "Core/Object.h"
|
||||||
#include "Math/Dense.h"
|
#include "Math/Dense.h"
|
||||||
#include "Math/Transform.h"
|
#include "Math/Transform.h"
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
namespace uLib {
|
namespace uLib {
|
||||||
|
|
||||||
class Geometry : public AffineTransform {
|
class Geometry : public AffineTransform, public Object {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
virtual const char * GetClassName() const { return "Geometry"; }
|
||||||
|
|
||||||
|
virtual Vector3f ToLinear(const Vector3f& curved_space) const {
|
||||||
|
return curved_space;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual Vector3f FromLinear(const Vector3f& cartesian_space) const {
|
||||||
|
return cartesian_space;
|
||||||
|
}
|
||||||
|
|
||||||
inline Vector4f GetWorldPoint(const Vector4f v) const {
|
inline Vector4f GetWorldPoint(const Vector4f v) const {
|
||||||
return this->GetWorldMatrix() * v;
|
Vector3f lin = ToLinear(Vector3f(v.x(), v.y(), v.z()));
|
||||||
|
return this->GetWorldMatrix() * Vector4f(lin.x(), lin.y(), lin.z(), v.w());
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Vector4f GetWorldPoint(const float x, const float y, const float z) {
|
inline Vector4f GetWorldPoint(const float x, const float y, const float z) {
|
||||||
@@ -45,7 +58,9 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline Vector4f GetLocalPoint(const Vector4f v) const {
|
inline Vector4f GetLocalPoint(const Vector4f v) const {
|
||||||
return this->GetWorldMatrix().inverse() * v;
|
Vector4f loc_lin = this->GetWorldMatrix().inverse() * v;
|
||||||
|
Vector3f curv = FromLinear(Vector3f(loc_lin.x(), loc_lin.y(), loc_lin.z()));
|
||||||
|
return Vector4f(curv.x(), curv.y(), curv.z(), loc_lin.w());
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Vector4f GetLocalPoint(const float x, const float y, const float z) {
|
inline Vector4f GetLocalPoint(const float x, const float y, const float z) {
|
||||||
@@ -53,6 +68,77 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class CylindricalGeometry : public Geometry {
|
||||||
|
public:
|
||||||
|
CylindricalGeometry() {}
|
||||||
|
|
||||||
|
Vector3f ToLinear(const Vector3f& cylindrical) const {
|
||||||
|
return Vector3f(cylindrical.x() * std::cos(cylindrical.y()),
|
||||||
|
cylindrical.x() * std::sin(cylindrical.y()),
|
||||||
|
cylindrical.z());
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3f FromLinear(const Vector3f& linear) const {
|
||||||
|
float r = std::sqrt(linear.x() * linear.x() + linear.y() * linear.y());
|
||||||
|
float phi = std::atan2(linear.y(), linear.x());
|
||||||
|
return Vector3f(r, phi, linear.z());
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class SphericalGeometry : public Geometry {
|
||||||
|
public:
|
||||||
|
SphericalGeometry() {}
|
||||||
|
|
||||||
|
virtual const char * GetClassName() const { return "SphericalGeometry"; }
|
||||||
|
|
||||||
|
Vector3f ToLinear(const Vector3f& spherical) const {
|
||||||
|
float r = spherical.x();
|
||||||
|
float theta = spherical.y();
|
||||||
|
float phi = spherical.z();
|
||||||
|
return Vector3f(r * std::sin(theta) * std::cos(phi),
|
||||||
|
r * std::sin(theta) * std::sin(phi),
|
||||||
|
r * std::cos(theta));
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3f FromLinear(const Vector3f& linear) const {
|
||||||
|
float r = linear.norm();
|
||||||
|
float theta = (r == 0.0f) ? 0.0f : std::acos(linear.z() / r);
|
||||||
|
float phi = std::atan2(linear.y(), linear.x());
|
||||||
|
return Vector3f(r, theta, phi);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class ToroidalGeometry : public Geometry {
|
||||||
|
public:
|
||||||
|
ToroidalGeometry(float Rtor) : m_Rtor(Rtor) {}
|
||||||
|
|
||||||
|
virtual const char * GetClassName() const { return "ToroidalGeometry"; }
|
||||||
|
|
||||||
|
Vector3f ToLinear(const Vector3f& toroidal) const {
|
||||||
|
float r = toroidal.x();
|
||||||
|
float theta = toroidal.y();
|
||||||
|
float phi = toroidal.z();
|
||||||
|
return Vector3f((m_Rtor + r * std::cos(theta)) * std::cos(phi),
|
||||||
|
(m_Rtor + r * std::cos(theta)) * std::sin(phi),
|
||||||
|
r * std::sin(theta));
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3f FromLinear(const Vector3f& linear) const {
|
||||||
|
float phi = std::atan2(linear.y(), linear.x());
|
||||||
|
float r_xy = std::sqrt(linear.x() * linear.x() + linear.y() * linear.y());
|
||||||
|
float delta_r = r_xy - m_Rtor;
|
||||||
|
float z = linear.z();
|
||||||
|
float r = std::sqrt(delta_r * delta_r + z * z);
|
||||||
|
float theta = std::atan2(z, delta_r);
|
||||||
|
return Vector3f(r, theta, phi);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
float m_Rtor;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
20
src/Math/MathRegistrations.cpp
Normal file
20
src/Math/MathRegistrations.cpp
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#include "Core/ObjectFactory.h"
|
||||||
|
#include "Math/ContainerBox.h"
|
||||||
|
#include "Math/Cylinder.h"
|
||||||
|
#include "Math/Geometry.h"
|
||||||
|
#include "Math/TriangleMesh.h"
|
||||||
|
#include "Math/QuadMesh.h"
|
||||||
|
#include "Math/VoxImage.h"
|
||||||
|
#include "Math/StructuredData.h"
|
||||||
|
|
||||||
|
namespace uLib {
|
||||||
|
|
||||||
|
ULIB_REGISTER_OBJECT(ContainerBox)
|
||||||
|
ULIB_REGISTER_OBJECT(Cylinder)
|
||||||
|
ULIB_REGISTER_OBJECT(CylindricalGeometry)
|
||||||
|
ULIB_REGISTER_OBJECT(SphericalGeometry)
|
||||||
|
ULIB_REGISTER_OBJECT(TriangleMesh)
|
||||||
|
ULIB_REGISTER_OBJECT(QuadMesh)
|
||||||
|
ULIB_REGISTER_OBJECT_NAME(VoxImage<Voxel>, "VoxImage")
|
||||||
|
|
||||||
|
} // namespace uLib
|
||||||
@@ -36,6 +36,8 @@ class Polydata : public Object {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
virtual const char * GetClassName() const { return "Polydata"; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -37,6 +37,9 @@ namespace uLib {
|
|||||||
class QuadMesh : public AffineTransform, public Object
|
class QuadMesh : public AffineTransform, public Object
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
virtual const char * GetClassName() const { return "QuadMesh"; }
|
||||||
|
|
||||||
void PrintSelf(std::ostream &o);
|
void PrintSelf(std::ostream &o);
|
||||||
|
|
||||||
/** @brief Adds a point in global coordinates. Stored in local coordinates. */
|
/** @brief Adds a point in global coordinates. Stored in local coordinates. */
|
||||||
@@ -49,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;
|
||||||
|
|||||||
@@ -50,6 +50,7 @@
|
|||||||
#define U_TRANSFORM_H
|
#define U_TRANSFORM_H
|
||||||
|
|
||||||
#include <Eigen/Geometry>
|
#include <Eigen/Geometry>
|
||||||
|
#include "Math/Dense.h"
|
||||||
|
|
||||||
|
|
||||||
namespace uLib {
|
namespace uLib {
|
||||||
@@ -68,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)
|
||||||
@@ -80,12 +83,12 @@ public:
|
|||||||
|
|
||||||
Eigen::Affine3f& GetTransform() { return m_T; }
|
Eigen::Affine3f& GetTransform() { return m_T; }
|
||||||
|
|
||||||
inline AffineTransform *GetParent() const { return this->m_Parent; }
|
AffineTransform *GetParent() const { return this->m_Parent; }
|
||||||
|
|
||||||
inline void SetParent(AffineTransform *name) { this->m_Parent = name; }
|
void SetParent(AffineTransform *name) { this->m_Parent = name; }
|
||||||
|
|
||||||
inline void SetMatrix (Matrix4f mat) { m_T.matrix() = mat; }
|
void SetMatrix (Matrix4f mat) { m_T.matrix() = mat; }
|
||||||
inline Matrix4f GetMatrix() const { return m_T.matrix(); }
|
Matrix4f GetMatrix() const { return m_T.matrix(); }
|
||||||
|
|
||||||
Matrix4f GetWorldMatrix() const
|
Matrix4f GetWorldMatrix() const
|
||||||
{
|
{
|
||||||
@@ -93,45 +96,45 @@ public:
|
|||||||
else return m_Parent->GetWorldMatrix() * m_T.matrix(); // T = B * A //
|
else return m_Parent->GetWorldMatrix() * m_T.matrix(); // T = B * A //
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void SetPosition(const Vector3f v) { this->m_T.translation() = v; }
|
void SetPosition(const Vector3f v) { this->m_T.translation() = v; }
|
||||||
|
|
||||||
inline Vector3f GetPosition() const { return this->m_T.translation(); }
|
Vector3f GetPosition() const { return this->m_T.translation(); }
|
||||||
|
|
||||||
inline void SetRotation(const Matrix3f m) { this->m_T.linear() = m; }
|
void SetRotation(const Matrix3f m) { this->m_T.linear() = m; }
|
||||||
|
|
||||||
inline Matrix3f GetRotation() const { return this->m_T.rotation(); }
|
Matrix3f GetRotation() const { return this->m_T.rotation(); }
|
||||||
|
|
||||||
inline void Translate(const Vector3f v) { this->m_T.pretranslate(v); }
|
void Translate(const Vector3f v) { this->m_T.translate(v); }
|
||||||
|
|
||||||
inline void Scale(const Vector3f v) { this->m_T.scale(v); }
|
void Scale(const Vector3f v) { this->m_T.scale(v); }
|
||||||
|
|
||||||
inline Vector3f GetScale() const {
|
Vector3f GetScale() const {
|
||||||
return Vector3f(m_T.linear().col(0).norm(),
|
return Vector3f(m_T.linear().col(0).norm(),
|
||||||
m_T.linear().col(1).norm(),
|
m_T.linear().col(1).norm(),
|
||||||
m_T.linear().col(2).norm());
|
m_T.linear().col(2).norm());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
inline void Rotate(const Matrix3f m) { this->m_T.rotate(m); }
|
void Rotate(const Matrix3f m) { this->m_T.rotate(m); }
|
||||||
|
|
||||||
inline void Rotate(const float angle, Vector3f axis)
|
void Rotate(const float angle, Vector3f axis)
|
||||||
{
|
{
|
||||||
axis.normalize(); // prehaps not necessary ( see eigens )
|
axis.normalize(); // prehaps not necessary ( see eigens )
|
||||||
Eigen::AngleAxisf ax(angle,axis);
|
Eigen::AngleAxisf ax(angle,axis);
|
||||||
this->m_T.rotate(Eigen::Quaternion<float>(ax));
|
this->m_T.rotate(Eigen::Quaternion<float>(ax));
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Rotate(const Vector3f euler_axis) {
|
void Rotate(const Vector3f euler_axis) {
|
||||||
float angle = euler_axis.norm();
|
float angle = euler_axis.norm();
|
||||||
Rotate(angle,euler_axis);
|
Rotate(angle,euler_axis);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void PreRotate(const Matrix3f m) { this->m_T.prerotate(m); }
|
void PreRotate(const Matrix3f m) { this->m_T.prerotate(m); }
|
||||||
|
|
||||||
inline void QuaternionRotate(const Vector4f q)
|
void QuaternionRotate(const Vector4f q)
|
||||||
{ this->m_T.rotate(Eigen::Quaternion<float>(q)); }
|
{ this->m_T.rotate(Eigen::Quaternion<float>(q)); }
|
||||||
|
|
||||||
inline void EulerYZYRotate(const Vector3f e) {
|
void EulerYZYRotate(const Vector3f e) {
|
||||||
Matrix3f mat;
|
Matrix3f mat;
|
||||||
mat = Eigen::AngleAxisf(e.x(), Vector3f::UnitY())
|
mat = Eigen::AngleAxisf(e.x(), Vector3f::UnitY())
|
||||||
* Eigen::AngleAxisf(e.y(), Vector3f::UnitZ())
|
* Eigen::AngleAxisf(e.y(), Vector3f::UnitZ())
|
||||||
@@ -139,7 +142,7 @@ public:
|
|||||||
m_T.rotate(mat);
|
m_T.rotate(mat);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void FlipAxes(int first, int second)
|
void FlipAxes(int first, int second)
|
||||||
{
|
{
|
||||||
Matrix3f mat = Matrix3f::Identity();
|
Matrix3f mat = Matrix3f::Identity();
|
||||||
mat.col(first).swap(mat.col(second));
|
mat.col(first).swap(mat.col(second));
|
||||||
|
|||||||
@@ -40,6 +40,9 @@ namespace uLib {
|
|||||||
class TriangleMesh : public AffineTransform, public Object
|
class TriangleMesh : public AffineTransform, public Object
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
virtual const char * GetClassName() const { return "TriangleMesh"; }
|
||||||
|
|
||||||
void PrintSelf(std::ostream &o);
|
void PrintSelf(std::ostream &o);
|
||||||
|
|
||||||
/** @brief Adds a point in global coordinates. Stored in local coordinates. */
|
/** @brief Adds a point in global coordinates. Stored in local coordinates. */
|
||||||
@@ -52,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;
|
||||||
|
|||||||
@@ -46,6 +46,9 @@ namespace Abstract {
|
|||||||
|
|
||||||
class VoxImage : public uLib::StructuredGrid {
|
class VoxImage : public uLib::StructuredGrid {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
virtual const char * GetClassName() const { return "VoxImage"; }
|
||||||
|
|
||||||
typedef uLib::StructuredGrid BaseClass;
|
typedef uLib::StructuredGrid BaseClass;
|
||||||
|
|
||||||
virtual float GetValue(const Vector3i &id) const = 0;
|
virtual float GetValue(const Vector3i &id) const = 0;
|
||||||
@@ -106,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,60 +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 {
|
// 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"; }
|
||||||
|
|
||||||
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();
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user