64 Commits

Author SHA1 Message Date
AndreaRigoni
a467b7385b monitor and threads 2026-03-25 11:04:37 +00:00
AndreaRigoni
0c8ef7337c threads and monitor 2026-03-25 10:40:13 +00:00
AndreaRigoni
913a1f7b3a move vtk containerbox in math 2026-03-25 10:37:38 +00:00
AndreaRigoni
5397baa50c add quit to gcompose 2026-03-24 17:46:08 +00:00
AndreaRigoni
51e6dbb4f5 add cylinder 2026-03-24 15:22:50 +00:00
AndreaRigoni
b45cde0bad fix transforms from handler 2026-03-24 11:36:46 +00:00
AndreaRigoni
f13342ff30 vtkProperties 2026-03-23 17:46:42 +00:00
AndreaRigoni
5d0efb3078 add vtk Properties 2026-03-23 15:09:35 +00:00
AndreaRigoni
94843de711 property - first attempt 2026-03-23 12:55:09 +00:00
AndreaRigoni
b52ae808b8 qcanvas passage 2026-03-22 13:55:06 +00:00
AndreaRigoni
d87f3a984e refactor: Enhance VTK puppet lifecycle management by explicitly handling puppet removal and synchronizing puppets across all viewports, and remove default scene objects from startup. 2026-03-22 13:20:44 +00:00
AndreaRigoni
324aaa91b7 attach vtk context to gcompose 2026-03-22 12:18:33 +00:00
AndreaRigoni
a8f786d8d1 add context panel 2026-03-21 20:14:29 +00:00
AndreaRigoni
add9d37aea Add Geometries 2026-03-21 19:43:56 +00:00
AndreaRigoni
0bff36f8ba context in core 2026-03-21 16:30:56 +00:00
AndreaRigoni
cd95f16221 fix name in gcompose 2026-03-21 16:30:38 +00:00
AndreaRigoni
bbd7493d9f add QCanvas Root and viewport pane in gcompose 2026-03-21 15:41:58 +00:00
AndreaRigoni
033fb598c7 add detector simulation 2026-03-20 00:16:55 +00:00
AndreaRigoni
c44a7738c0 make scene able to run parallel simulations 2026-03-19 21:51:38 +00:00
AndreaRigoni
85e1f1448f feat: Implement new MIP Rainbow and Additive rendering presets and add interactive test controls. 2026-03-19 16:44:00 +00:00
AndreaRigoni
6234dffaa7 add voximage represetation and test 2026-03-19 16:39:05 +00:00
AndreaRigoni
80952cc706 add cylinder emitter 2026-03-19 16:19:36 +00:00
AndreaRigoni
ae27e9d46d add cylinder 2026-03-19 16:03:57 +00:00
AndreaRigoni
a8a313e5cf added skyplaneEmitter 2026-03-19 15:45:48 +00:00
AndreaRigoni
7c8c7beae4 add ecomug 2026-03-19 14:57:26 +00:00
AndreaRigoni
a8c0d5edc2 handler toggle 1,2,3 keys 2026-03-19 14:28:20 +00:00
AndreaRigoni
dbb5f24933 fix zoom to selected 2026-03-19 14:18:31 +00:00
AndreaRigoni
1e6e3ae4f4 emitter representation 2026-03-19 13:57:10 +00:00
AndreaRigoni
ca5f576b99 add triangle mesh affine transform 2026-03-19 13:11:49 +00:00
AndreaRigoni
4cb4560921 qadmesh affine transform parenting 2026-03-19 12:54:31 +00:00
AndreaRigoni
887b3b36f0 add lambda connection 2026-03-19 12:51:37 +00:00
AndreaRigoni
2f163a762c make test fix test wiht correct units 2026-03-19 10:42:14 +00:00
AndreaRigoni
12657167f1 add detector chamber projection plane representation in vtk 2026-03-19 10:21:01 +00:00
AndreaRigoni
c265adadfc fix transform adding pre-translation 2026-03-19 10:20:33 +00:00
AndreaRigoni
c8eec163a6 projection plane on local coordinates 2026-03-19 09:52:19 +00:00
AndreaRigoni
ca2223e04c detector chamber 2026-03-19 09:46:36 +00:00
AndreaRigoni
176a82f108 add zoom to selected 2026-03-18 23:35:51 +00:00
AndreaRigoni
3b02bb26ac refactor: Simplify vtkDetectorChamber by removing redundant transform management and improve vtkHandlerWidget rotation calculation using ray-plane intersection. 2026-03-18 23:08:22 +00:00
AndreaRigoni
a9b66a4e12 add grid annotation 2026-03-18 21:44:29 +00:00
AndreaRigoni
cca29ef837 fixed vtk containerbox handler 2026-03-18 21:32:33 +00:00
AndreaRigoni
0e8ac47fcf grid axis 2026-03-18 07:23:58 +00:00
AndreaRigoni
92a06f6274 activate grid button and widget size 2026-03-17 22:56:56 +00:00
AndreaRigoni
553bb7fd61 widget in viewport 2026-03-17 17:28:26 +00:00
AndreaRigoni
bc437a3913 fix segfault 2026-03-17 15:47:27 +00:00
AndreaRigoni
e6e0bccffb split viewport for Qt and VtkViewer 2026-03-17 15:29:07 +00:00
AndreaRigoni
4569407d18 add vtkHandlerWidget 2026-03-17 11:13:35 +00:00
AndreaRigoni
d8ef413216 vtkGeantEvent 2026-03-16 17:51:53 +00:00
AndreaRigoni
c63a1ae047 geant events for multiple scattering 2026-03-14 23:33:31 +00:00
AndreaRigoni
692cdf7ae3 add Geant namespace 2026-03-14 14:01:44 +00:00
AndreaRigoni
e5dfb75262 add Normals to meshes 2026-03-14 13:23:28 +00:00
AndreaRigoni
35e4fb949d fix tests 2026-03-14 12:28:40 +00:00
AndreaRigoni
20d4967356 add quadmesh 2026-03-14 10:28:16 +00:00
AndreaRigoni
6bf9eaf309 add Qt viewport 2026-03-13 22:36:52 +00:00
AndreaRigoni
a142c5d060 add units 2026-03-13 21:40:14 +00:00
AndreaRigoni
61052f80bc add geant4 scene and gcompose app 2026-03-13 17:19:51 +00:00
AndreaRigoni
f2133c31d5 DetectorChamber vtk handler 2026-03-10 08:18:17 +00:00
AndreaRigoni
00275ac56d vtk camera position widget on viewer 2026-03-08 16:51:39 +00:00
AndreaRigoni
1374821344 added gizmo but not yet working 2026-03-08 10:21:38 +00:00
AndreaRigoni
2548582036 attach a widget (not working well yet) 2026-03-08 09:42:28 +00:00
AndreaRigoni
32a1104769 detector chamber in vtk 2026-03-08 08:46:21 +00:00
AndreaRigoni
3be7ec2274 add stl test 2026-03-08 08:05:22 +00:00
AndreaRigoni
38dd416ced vix raytracer representation 2026-03-07 09:07:07 +00:00
AndreaRigoni
e8f8e96521 reorganization of sources, moving cmt pertaining structures into HEP folder 2026-03-07 08:58:31 +00:00
AndreaRigoni
49cf0aeedd feat: disable camera spin/inertia by introducing a custom interactor style.r 2026-03-06 17:31:29 +00:00
226 changed files with 16875 additions and 3085 deletions

View File

@@ -34,7 +34,9 @@
#cmakedefine HAVE_FLOOR #cmakedefine HAVE_FLOOR
/* Having Geant4 installed */ /* Having Geant4 installed */
#ifndef HAVE_GEANT4
#cmakedefine HAVE_GEANT4 #cmakedefine HAVE_GEANT4
#endif
/* Define to 1 if you have the <inttypes.h> header file. */ /* Define to 1 if you have the <inttypes.h> header file. */
#cmakedefine HAVE_INTTYPES_H #cmakedefine HAVE_INTTYPES_H

View File

@@ -3,7 +3,14 @@
##### CMAKE LISTS ############################################################## ##### CMAKE LISTS ##############################################################
################################################################################ ################################################################################
if(EXISTS "${CMAKE_BINARY_DIR}/conan_toolchain.cmake")
include("${CMAKE_BINARY_DIR}/conan_toolchain.cmake")
endif()
cmake_minimum_required (VERSION 3.26) cmake_minimum_required (VERSION 3.26)
set(QT_NO_VERSION_CHECK TRUE)
if(POLICY CMP0167) if(POLICY CMP0167)
cmake_policy(SET CMP0167 NEW) cmake_policy(SET CMP0167 NEW)
endif() endif()
@@ -107,6 +114,8 @@ 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(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})
@@ -118,15 +127,12 @@ find_package(ROOT CONFIG REQUIRED)
include(${ROOT_USE_FILE}) include(${ROOT_USE_FILE})
find_package(VTK REQUIRED) find_package(VTK REQUIRED)
# include(${VTK_USE_FILE})
find_package(pybind11 REQUIRED) find_package(pybind11 REQUIRED)
option(CENTOS_SUPPORT "VTK definitions for CentOS" OFF) option(CENTOS_SUPPORT "VTK definitions for CentOS" OFF)
if(CENTOS_SUPPORT) if(CENTOS_SUPPORT)
find_package(VTK CONFIG REQUIRED) find_package(VTK CONFIG REQUIRED)
include(${VTK_USE_FILE}) # include(${VTK_USE_FILE})
else() else()
find_package(VTK REQUIRED find_package(VTK REQUIRED
COMPONENTS CommonColor COMPONENTS CommonColor
@@ -146,7 +152,36 @@ else()
RenderingFreeType RenderingFreeType
RenderingGL2PSOpenGL2 RenderingGL2PSOpenGL2
RenderingOpenGL2 RenderingOpenGL2
RenderingVolumeOpenGL2) RenderingVolumeOpenGL2
IOGeometry
GUISupportQt)
endif()
find_package(Qt6 COMPONENTS Widgets)
if(Qt6_FOUND)
add_compile_definitions(HAVE_QT)
endif()
find_package(Geant4)
if(Geant4_FOUND)
message(STATUS "Geant4 libs: ${Geant4_LIBRARIES}")
add_compile_definitions(HAVE_GEANT4)
set(HAVE_GEANT4 1)
# Sanitize Geant4 targets to remove Qt5 dependencies that conflict with VTK/Qt6
if(TARGET Geant4::G4interfaces)
set_target_properties(Geant4::G4interfaces PROPERTIES
INTERFACE_LINK_LIBRARIES "Geant4::G4global;Geant4::G4graphics_reps;Geant4::G4intercoms"
)
endif()
if(TARGET Geant4::G4OpenGL)
set_target_properties(Geant4::G4OpenGL PROPERTIES
INTERFACE_LINK_LIBRARIES "Geant4::G4vis_management;Geant4::G4graphics_reps;Geant4::G4geometry;Geant4::G4materials;Geant4::G4intercoms;Geant4::G4global;OpenGL::GL;OpenGL::GLU"
)
endif()
else()
message(STATUS "Geant4 NOT found - optional features will be disabled")
set(HAVE_GEANT4 0)
endif() endif()
set(CMAKE_REQUIRED_INCLUDES CMAKE_REQUIRED_INCLUDES math.h) set(CMAKE_REQUIRED_INCLUDES CMAKE_REQUIRED_INCLUDES math.h)
@@ -204,8 +239,8 @@ add_subdirectory(${SRC_DIR}/Core)
include_directories(${SRC_DIR}/Math) include_directories(${SRC_DIR}/Math)
add_subdirectory(${SRC_DIR}/Math) add_subdirectory(${SRC_DIR}/Math)
include_directories(${SRC_DIR}/Detectors) include_directories(${SRC_DIR}/HEP)
add_subdirectory(${SRC_DIR}/Detectors) add_subdirectory(${SRC_DIR}/HEP)
include_directories(${SRC_DIR}/Root) include_directories(${SRC_DIR}/Root)
add_subdirectory(${SRC_DIR}/Root) add_subdirectory(${SRC_DIR}/Root)
@@ -215,7 +250,7 @@ add_subdirectory(${SRC_DIR}/Vtk)
add_subdirectory(${SRC_DIR}/Python) add_subdirectory(${SRC_DIR}/Python)
#add_subdirectory("${SRC_DIR}/utils/make_recipe") add_subdirectory(app)
## Documentation and packages ## Documentation and packages

3
app/CMakeLists.txt Normal file
View File

@@ -0,0 +1,3 @@
if(HAVE_GEANT4)
add_subdirectory(gcompose)
endif()

View File

@@ -0,0 +1,53 @@
add_executable(gcompose
src/main.cpp
src/MainWindow.h
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
AUTOMOC ON
AUTOUIC ON
AUTORCC ON
)
target_include_directories(gcompose PRIVATE
${SRC_DIR}
${PROJECT_BINARY_DIR}
${Geant4_INCLUDE_DIRS}
${VTK_INCLUDE_DIRS}
)
# Filter Geant4 libraries to remove Qt-dependent ones
set(Geant4_LIBS_FILTERED ${Geant4_LIBRARIES})
if(Geant4_LIBS_FILTERED)
list(REMOVE_ITEM Geant4_LIBS_FILTERED Geant4::G4interfaces Geant4::G4OpenGL Geant4::G4visQt3D)
endif()
target_link_libraries(gcompose
mutomCore
mutomMath
mutomGeant
mutomVtk
mutomRoot
${Geant4_LIBS_FILTERED}
${VTK_LIBRARIES}
Qt6::Widgets
VTK::GUISupportQt
)
install(TARGETS gcompose RUNTIME DESTINATION bin)

View File

@@ -0,0 +1,161 @@
#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) {
uLib::Object::connect(m_rootContext, &uLib::Object::Updated, [this]() {
this->beginResetModel();
this->endResetModel();
});
}
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;
}

View 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

View File

@@ -0,0 +1,94 @@
#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);
}

View File

@@ -0,0 +1,43 @@
#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);
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

View File

@@ -0,0 +1,205 @@
#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 "StyleManager.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
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() {
// Placeholder for open logic
}
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() {}

View 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

View File

@@ -0,0 +1,21 @@
#include "MainWindow.h"
#include <QSplitter>
#include "MainPanel.h"
#include "Core/ObjectsContext.h"
using namespace uLib;
MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) {
m_panel = new MainPanel(this);
setCentralWidget(m_panel);
setWindowTitle("gcompose - Qt VTK Interface");
resize(1200, 800);
}
MainWindow::~MainWindow() {
}
void MainWindow::setContext(uLib::ObjectsContext* context) {
m_panel->setContext(context);
}

View File

@@ -0,0 +1,29 @@
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QVTKOpenGLNativeWidget.h>
class MainPanel;
class ViewportPane;
namespace uLib {
namespace Vtk {
}
class ObjectsContext;
}
class MainWindow : public QMainWindow {
Q_OBJECT
public:
MainWindow(QWidget* parent = nullptr);
virtual ~MainWindow();
void setContext(uLib::ObjectsContext* context);
MainPanel* getPanel() { return m_panel; }
private:
MainPanel* m_panel;
};
#endif

View 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() {}

View 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

View File

@@ -0,0 +1,255 @@
#include "PropertyWidgets.h"
#include <QSignalBlocker>
#include <QRegularExpression>
#include <QRegularExpressionMatch>
#include "Vtk/uLibVtkInterface.h"
#include "Math/Units.h"
#include "Math/Dense.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);
m_Label = new QLabel(QString::fromStdString(prop->GetName()), this);
m_Label->setMinimumWidth(100);
m_Layout->addWidget(m_Label);
}
PropertyWidgetBase::~PropertyWidgetBase() {}
// Helper for unit parsing
static double parseWithUnits(const QString& text, double* factorOut = nullptr, QString* suffixOut = nullptr) {
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("mm"), m_IsInteger(false) {
connect(this, &QLineEdit::editingFinished, this, &UnitLineEdit::onEditingFinished);
}
void UnitLineEdit::setValue(double val) {
if (m_Value != val) {
m_Value = val;
// Initial heuristic for unit if it was mm and value becomes large
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);
if (m_IsInteger) {
setText(QString::number((int)m_Value));
} else {
double displayVal = m_Value / m_Factor;
setText(QString::number(displayVal, 'g', 6) + " " + m_Suffix);
}
}
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);
m_Layout->addWidget(m_Edit, 1);
m_Edit->setValue(prop->Get());
connect(m_Edit, &UnitLineEdit::valueManualChanged, [this](double val){ m_Prop->Set(val); });
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);
m_Layout->addWidget(m_Edit, 1);
m_Edit->setValue(prop->Get());
connect(m_Edit, &UnitLineEdit::valueManualChanged, [this](double val){ m_Prop->Set((float)val); });
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);
m_Layout->addWidget(m_Edit, 1);
m_Edit->setValue(prop->Get());
connect(m_Edit, &UnitLineEdit::valueManualChanged, [this](double val){ m_Prop->Set((int)val); });
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); });
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);
});
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() {}
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);
});
// 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.
}
}
for (auto* prop : *props) {
auto it = m_Factories.find(prop->GetTypeIndex());
if (it != m_Factories.end()) {
QWidget* widget = it->second(prop, m_Container);
m_ContainerLayout->addWidget(widget);
} else {
QWidget* fallback = new PropertyWidgetBase(prop, m_Container);
fallback->layout()->addWidget(new QLabel("(Read-only: " + QString::fromStdString(prop->GetValueAsString()) + ")"));
m_ContainerLayout->addWidget(fallback);
}
}
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

View File

@@ -0,0 +1,165 @@
#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 "Math/Dense.h"
namespace uLib {
namespace Qt {
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;
};
class UnitLineEdit : public QLineEdit {
Q_OBJECT
public:
UnitLineEdit(QWidget* parent = nullptr);
void setValue(double val);
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) {
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);
}
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();
uLib::Object::connect(m_Prop, &Property<VecT>::PropertyChanged, [this](){
updateEdits();
});
}
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

View 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);
}
}

View 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

View 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
}
}

View 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

View File

@@ -0,0 +1,180 @@
#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 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, &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() {
m_displayPanel->setVisible(m_toggleBtn->isChecked());
}
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) {
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);
}
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);
}
}

View File

@@ -0,0 +1,50 @@
#ifndef VIEWPORTPANE_H
#define VIEWPORTPANE_H
#include <QWidget>
#include <QFrame>
#include <QPushButton>
namespace uLib {
class Object;
namespace Qt { class PropertyEditor; }
}
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;
QWidget* m_viewport;
// Display Properties Overlay
QFrame* m_displayPanel;
uLib::Qt::PropertyEditor* m_displayEditor;
QPushButton* m_toggleBtn;
};
#endif // VIEWPORTPANE_H

55
app/gcompose/src/main.cpp Normal file
View File

@@ -0,0 +1,55 @@
#include <QApplication>
#include "MainWindow.h"
#include "MainPanel.h"
#include "ViewportPane.h"
#include "StyleManager.h"
#include "Math/ContainerBox.h"
#include <HEP/Geant/Scene.h>
#include "HEP/Detectors/DetectorChamber.h"
#include "Vtk/HEP/Detectors/vtkDetectorChamber.h"
#include <Vtk/Math/vtkContainerBox.h>
#include <Vtk/vtkQViewport.h>
#include "Core/ObjectsContext.h"
#include <vtkSmartPointer.h>
#include <vtkCubeSource.h>
#include <vtkPolyDataMapper.h>
#include <vtkActor.h>
#include <vtkRenderer.h>
#include "Math/Units.h"
#include <iostream>
using namespace uLib;
using namespace uLib::literals;
int main(int argc, char** argv) {
QApplication app(argc, argv);
StyleManager::applyStyle(&app, "dark");
std::cout << "Starting gcompose Qt application..." << std::endl;
// ContainerBox world_box(Vector3f(1, 1, 1));
// world_box.Scale(Vector3f(2_mm, 2_mm, 2_mm));
// world_box.SetPosition(Vector3f(-1_mm, -1_mm, -1_mm));
// Geant::Scene scene;
// scene.ConstructWorldBox(world_box.GetSize(), "G4_AIR");
// scene.Initialize();
uLib::ObjectsContext globalContext;
// globalContext.AddObject(&world_box);
// globalContext.AddObject(&scene);
// 2. Initialize MainWindow (contains embedded VTK QViewport)
MainWindow window;
window.setContext(&globalContext);
std::cout << "Geant4 and VTK scenes are ready." << std::endl;
window.show();
return app.exec();
}

View File

@@ -1,8 +1,13 @@
[requires] [requires]
eigen/3.4.0 eigen/3.4.0
boost/1.83.0 boost/1.83.0
pybind11/3.0.2 # pybind11/3.0.2
hdf5/1.14.3
[generators] [generators]
CMakeDeps CMakeDeps
CMakeToolchain CMakeToolchain
[options]
hdf5/*:threadsafe=True
hdf5/*:enable_unsupported=True

View File

@@ -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.

View File

@@ -10,6 +10,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 +19,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 +30,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)
@@ -49,7 +60,7 @@ endif()
target_link_libraries(${libname} ${LIBRARIES}) target_link_libraries(${libname} ${LIBRARIES})
install(TARGETS ${libname} install(TARGETS ${libname}
EXPORT "${PROJECT_NAME}Targets" EXPORT "uLibTargets"
RUNTIME DESTINATION ${INSTALL_BIN_DIR} COMPONENT bin RUNTIME DESTINATION ${INSTALL_BIN_DIR} COMPONENT bin
LIBRARY DESTINATION ${INSTALL_LIB_DIR} COMPONENT lib) LIBRARY DESTINATION ${INSTALL_LIB_DIR} COMPONENT lib)

View File

@@ -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 {
@@ -119,8 +119,8 @@ public:
void AddAdapter(AdapterInterface &ad) { m_a.push_back(Adapter(ad)); } void AddAdapter(AdapterInterface &ad) { m_a.push_back(Adapter(ad)); }
void Update() { void Update() {
foreach(Adapter &ad, m_a) { for(Adapter &ad : m_a) {
foreach(DItem &item, m_v) { for(DItem &item : m_v) {
item.m_adapter->operator()(ad, item.m_value); item.m_adapter->operator()(ad, item.m_value);
} }
} }

215
src/Core/Monitor.h Normal file
View 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

View File

@@ -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,85 @@ public:
GenericMFPtr sloptr; GenericMFPtr sloptr;
std::string slostr; std::string slostr;
}; };
std::string m_InstanceName;
Vector<Signal> sigv; Vector<Signal> sigv;
Vector<Slot> slov; 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) {
d->m_DynamicProperties.push_back(prop);
// Note: prop already added itself to m_Properties
// during its own constructor call to owner->RegisterProperty()
}
}
const std::vector<PropertyBase*>& Object::GetProperties() const {
return d->m_Properties;
}
// 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 &copy) : d(new ObjectPrivate) {
if (copy.d) {
d->m_InstanceName = copy.d->m_InstanceName;
d->m_SignalsBlocked = copy.d->m_SignalsBlocked;
}
}
Object::Object(const Object &copy) : d(new ObjectPrivate(*copy.d)) {} Object::~Object() {
for (auto* p : d->m_DynamicProperties) {
Object::~Object() { delete d; } delete p;
}
delete d;
}
void Object::DeepCopy(const Object &copy) { void Object::DeepCopy(const Object &copy) {
// should lock to be tread safe // if (this == &copy) return;
memcpy(d, copy.d, sizeof(ObjectPrivate)); if (copy.d) d->m_InstanceName = copy.d->m_InstanceName;
// ERROR! does not copy parameters ... <<<< FIXXXXX // 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) {
@@ -145,6 +207,25 @@ GenericMFPtr *Object::findSlotImpl(const char *name) const {
return NULL; return NULL;
} }
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)
// { // {

View File

@@ -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;
@@ -74,6 +77,23 @@ public:
Object(const Object &copy); Object(const Object &copy);
~Object(); ~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;
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// PARAMETERS // // PARAMETERS //
@@ -84,9 +104,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);
@@ -97,6 +126,9 @@ public:
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// SIGNALS // // SIGNALS //
signals:
virtual void Updated();
// Qt4 style connector // // Qt4 style connector //
static bool connect(const Object *ob1, const char *signal_name, static bool connect(const Object *ob1, const char *signal_name,
const Object *receiver, const char *slot_name) { const Object *receiver, const char *slot_name) {
@@ -112,20 +144,36 @@ 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);
}
// Lambda/Function object connector //
template <typename Func1, typename SlotT>
static Connection connect(typename FunctionPointer<Func1>::Object *sender,
Func1 sigf, SlotT slof) {
SignalBase *sigb = sender->findOrAddSignal(sigf);
typedef typename FunctionPointer<Func1>::SignalSignature SigSignature;
typedef typename Signal<SigSignature>::type SigT;
return reinterpret_cast<SigT *>(sigb)->connect(slof);
}
template <typename Func1, typename Func2>
static bool
disconnect(typename FunctionPointer<Func1>::Object *sender, Func1 sigf,
typename FunctionPointer<Func2>::Object *receiver, Func2 slof) {
// TODO: implement actual disconnect in Signal.h //
return true; return true;
} }
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>

View 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
View 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

View 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
View 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.
*/
void AddObject(Object* obj);
/**
* @brief Removes an object from the context.
* @param obj Pointer to the object to remove.
*/
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

228
src/Core/Property.h Normal file
View File

@@ -0,0 +1,228 @@
#ifndef U_CORE_PROPERTY_H
#define U_CORE_PROPERTY_H
#include <string>
#include <sstream>
#include <typeinfo>
#include <typeindex> // Added
#include <boost/serialization/nvp.hpp>
#include <boost/lexical_cast.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
// 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)
: m_owner(owner), m_name(name), m_value(valuePtr), m_own(false) {
if (m_owner) {
m_owner->RegisterProperty(this);
}
}
// MANAGED: Create and own internal storage
Property(Object* owner, const std::string& name, const T& defaultValue = T())
: m_owner(owner), m_name(name), m_value(new T(defaultValue)), m_own(true) {
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)); }
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;
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 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) {}
// Core logic: encounter HRP -> Create Dynamic Property
template<class T>
void save_override(const boost::serialization::hrp<T> &t) {
if (m_Object) {
// We use const_cast because we are just creating a proxy to the member
m_Object->RegisterDynamicProperty(
new Property<T>(m_Object, t.name(), &const_cast<boost::serialization::hrp<T>&>(t).value())
);
}
}
// Handle standard NVPs by recursing (important for base classes)
template<class T>
void save_override(const boost::serialization::nvp<T> &t) {
boost::archive::detail::common_oarchive<property_register_archive>::save_override(t.const_value());
}
// Ignore everything else
template<class T> void save_override(const T &t) {}
// 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) {}
};
/**
* @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

View File

@@ -72,39 +72,33 @@ 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; T &m_value;
std::string *m_str;
public: public:
explicit hrp(const char *name_, T &t) explicit hrp(const char *name_, T &t) : m_name(name_), 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; }
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) {
#ifndef BOOST_NO_FUNCTION_TEMPLATE_ORDERING
const
#endif
hrp<T> make_hrp(const char *name, T &t) {
return hrp<T>(name, t); return hrp<T>(name, t);
} }

View File

@@ -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
View 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
View 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

View File

@@ -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))

View 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;
}

View File

@@ -21,6 +21,13 @@ set( TESTS
OptionsTest OptionsTest
PingPongTest PingPongTest
VectorMetaAllocatorTest VectorMetaAllocatorTest
PropertyTypesTest
HRPTest
MutexTest
ThreadsTest
OpenMPTest
TeamTest
AffinityTest
) )
set(LIBRARIES set(LIBRARIES
@@ -29,6 +36,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)

View 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;
}

View File

@@ -0,0 +1,108 @@
#include "Core/Monitor.h"
#include <iostream>
#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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View File

@@ -1,12 +0,0 @@
set(HEADERS MuonScatter.h MuonError.h MuonEvent.h)
set(ULIB_SELECTED_MODULES ${ULIB_SELECTED_MODULES} Detectors PARENT_SCOPE)
install(FILES ${HEADERS}
DESTINATION ${INSTALL_INC_DIR}/Detectors)
if(BUILD_TESTING)
include(uLibTargetMacros)
add_subdirectory(testing)
endif()

View File

@@ -1,114 +0,0 @@
/*//////////////////////////////////////////////////////////////////////////////
// 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.
//////////////////////////////////////////////////////////////////////////////*/
// G4 Solid //
#include <Geant4/G4Material.hh>
#include <Geant4/G4NistManager.hh>
#include <Geant4/G4LogicalVolume.hh>
// Tessellated solid //
#include <Geant4/G4TessellatedSolid.hh>
#include <Geant4/G4TriangularFacet.hh>
#include <Geant4/G4ThreeVector.hh>
#include "Math/Dense.h"
#include "Solid.h"
namespace uLib {
class DetectorsSolidPimpl {
public:
static G4ThreeVector getG4Vector3f(const Vector3f &vector) {
return G4ThreeVector( vector(0), vector(1), vector(2) );
}
};
Solid::Solid() :
m_Logical (new G4LogicalVolume(NULL,NULL,"unnamed_solid")),
m_Material(NULL)
{}
Solid::Solid(const char *name) :
m_Logical(new G4LogicalVolume(NULL,NULL,name)),
m_Material(NULL)
{}
void Solid::SetNistMaterial(const char *name)
{
G4NistManager *nist = G4NistManager::Instance();
if (m_Material) delete m_Material;
m_Material = nist->FindOrBuildMaterial(name);
m_Logical->SetMaterial(m_Material);
}
void Solid::SetMaterial(G4Material *material)
{
if(material)
{
m_Material = material;
m_Logical->SetMaterial(material);
}
}
TessellatedSolid::TessellatedSolid(const char *name) :
BaseClass(name),
m_Solid(new G4TessellatedSolid(name))
{}
void TessellatedSolid::SetMesh(TriangleMesh &mesh)
{
G4TessellatedSolid *ts = this->m_Solid;
for (int i=0; i<mesh.Triangles().size(); ++i) {
const Vector3i &trg = mesh.Triangles().at(i);
G4TriangularFacet *facet = new G4TriangularFacet(
DetectorsSolidPimpl::getG4Vector3f(mesh.Points().at(trg(0))),
DetectorsSolidPimpl::getG4Vector3f(mesh.Points().at(trg(1))),
DetectorsSolidPimpl::getG4Vector3f(mesh.Points().at(trg(2))),
ABSOLUTE);
ts->AddFacet((G4VFacet *)facet);
}
this->m_Logical->SetSolid(ts);
}
}

View File

@@ -1,16 +0,0 @@
include $(top_srcdir)/Common.am
#AM_DEFAULT_SOURCE_EXT = .cpp
# if HAVE_CHECK
TESTS = GDMLSolidTest
# else
# TEST =
# endif
LDADD = $(top_srcdir)/libmutom-${PACKAGE_VERSION}.la
check_PROGRAMS = $(TESTS)

13
src/HEP/CMakeLists.txt Normal file
View File

@@ -0,0 +1,13 @@
################################################################################
##### HEP - High Energy Physics modules ########################################
################################################################################
include_directories(${SRC_DIR}/HEP)
add_subdirectory(Detectors)
add_subdirectory(Geant)
# add_subdirectory(MuonTomography)
set(ULIB_SHARED_LIBRARIES ${ULIB_SHARED_LIBRARIES} PARENT_SCOPE)
set(ULIB_SELECTED_MODULES ${ULIB_SELECTED_MODULES} PARENT_SCOPE)

View File

@@ -0,0 +1,46 @@
set(HEADERS
ChamberHitEvent.h
DetectorChamber.h
ExperimentFitEvent.h
HierarchicalEncoding.h
Hit.h
HitMC.h
LinearFit.h
MuonError.h
MuonEvent.h
MuonScatter.h
)
set(SOURCES
DetectorChamber.cpp
)
set(LIBRARIES
${PACKAGE_LIBPREFIX}Core
${PACKAGE_LIBPREFIX}Math
)
set(libname ${PACKAGE_LIBPREFIX}Detectors)
set(ULIB_SHARED_LIBRARIES ${ULIB_SHARED_LIBRARIES} ${libname} PARENT_SCOPE)
set(ULIB_SELECTED_MODULES ${ULIB_SELECTED_MODULES} Detectors PARENT_SCOPE)
## SHARED library
add_library(${libname} SHARED ${SOURCES})
set_target_properties(${libname} PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_SOVERSION}
CXX_STANDARD 17)
target_link_libraries(${libname} PRIVATE ${LIBRARIES})
install(TARGETS ${libname}
EXPORT "uLibTargets"
RUNTIME DESTINATION ${INSTALL_BIN_DIR} COMPONENT bin
LIBRARY DESTINATION ${INSTALL_LIB_DIR} COMPONENT lib)
install(FILES ${HEADERS}
DESTINATION ${INSTALL_INC_DIR}/HEP/Detectors)
if(BUILD_TESTING)
include(uLibTargetMacros)
add_subdirectory(testing)
endif()

View File

@@ -29,8 +29,8 @@
#define U_CHAMBERHITEVENT_H #define U_CHAMBERHITEVENT_H
#include "Core/Vector.h" #include "Core/Vector.h"
#include "Hit.h" #include "Detectors/HitMC.h"
#include "ChamberDetector.h" #include "Detectors/DetectorChamber.h"
namespace uLib { namespace uLib {
@@ -38,17 +38,17 @@ class ChamberHitEventData
{ {
public: public:
uLibConstRefMacro (Hits, Vector<HitData> ) uLibConstRefMacro (Hits, Vector<HitData> )
uLibGetMacro (Idv, ChamberDetector::ID) uLibGetMacro (Idv, uint)
private: private:
friend class ChamberHitEvent; friend class ChamberHitEvent;
Vector<HitData> m_Hits; Vector<HitData> m_Hits;
DetectorChamber::ID m_Idv; // -> chamber/view uint m_Idv; // -> chamber/view
}; };
class ChamberHitEvent : public ChamberHitEventData { class ChamberHitEvent : public ChamberHitEventData {
public: public:
uLibRefMacro (Hits, Vector<HitData> ) uLibRefMacro (Hits, Vector<HitData> )
uLibSetMacro (Idv, ChamberDetector::ID) uLibSetMacro (Idv, uint)
}; };
} }

View File

@@ -0,0 +1,49 @@
#include "HEP/Detectors/DetectorChamber.h"
#include "Core/ObjectFactory.h"
#include <cmath>
namespace uLib {
MuonEvent DetectorChamber::ProjectMuonEvent(const MuonEvent &muon) const {
MuonEvent projectedMuon = muon;
// Transform the local projection plane to world coordinates
HLine3f worldPlane = this->GetWorldProjectionPlane();
HPoint3f P = worldPlane.origin;
HVector3f N = worldPlane.direction;
HPoint3f X_in = muon.LineIn().origin;
HPoint3f X_out = muon.LineOut().origin;
// 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_out = std::abs((X_out - P).dot(N));
const HLine3f &chosenLine = (dist_in <= dist_out) ? muon.LineIn() : muon.LineOut();
HPoint3f X_chosen = chosenLine.origin;
// Project X_chosen into the plane defined by P and normal N
// X_proj = X_chosen - ((X_chosen - P) . N / (N . N)) * N
float dot = (X_chosen - P).dot(N);
float n_sq = N.dot(N);
HPoint3f X_proj = X_chosen;
if (n_sq > 0) {
X_proj = X_chosen - (dot / n_sq) * N;
}
// Define the projected line with projected origin and original direction
HLine3f projectedLine;
projectedLine.origin = X_proj;
projectedLine.direction = chosenLine.direction;
// Set both input and output lines of the projected muon to the same projected line
projectedMuon.LineIn() = projectedLine;
projectedMuon.LineOut() = projectedLine;
return projectedMuon;
}
ULIB_REGISTER_OBJECT(DetectorChamber)
} // namespace uLib

View File

@@ -0,0 +1,86 @@
/*//////////////////////////////////////////////////////////////////////////////
// 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_CHAMBERDETECTOR_H
#define U_CHAMBERDETECTOR_H
#include "Core/Types.h"
#include "Math/ContainerBox.h"
#include "HEP/Detectors/Hit.h"
#include "HEP/Detectors/HitMC.h"
#include "HEP/Detectors/MuonEvent.h"
namespace uLib {
class DetectorChamber : public ContainerBox {
typedef ContainerBox BaseClass;
public:
virtual const char * GetClassName() const { return "DetectorChamber"; }
DetectorChamber() : BaseClass() {
m_ProjectionPlane.origin = HPoint3f(0, 0, 0);
m_ProjectionPlane.direction = HVector3f(0, 0, 1);
}
DetectorChamber(const Vector3f &size) : BaseClass(size) {
m_ProjectionPlane.origin = HPoint3f(0, 0, 0);
m_ProjectionPlane.direction = HVector3f(0, 0, 1);
}
// set the plane where muons hit is projected
// coordinates are local to the container box
void SetProjectionPlane(const HLine3f &normal) { m_ProjectionPlane = normal; }
const HLine3f &GetProjectionPlane() const { return m_ProjectionPlane; }
HLine3f GetWorldProjectionPlane() const {
HLine3f worldPlane;
Matrix4f M = this->GetWorldMatrix();
worldPlane.origin = M * m_ProjectionPlane.origin;
HVector3f dirNorm = M * m_ProjectionPlane.direction;
dirNorm.normalize(); // Normalize for consistent dot products
worldPlane.direction = dirNorm;
return worldPlane;
}
MuonEvent ProjectMuonEvent(const MuonEvent &muon) const;
private:
HLine3f m_ProjectionPlane;
};
}
#endif // CHAMBERDETECTOR_H

View File

@@ -33,9 +33,6 @@
namespace uLib { namespace uLib {
class HitRawCode_CMSDrift : class HitRawCode_CMSDrift :
public BitCode4<unsigned short,6,3,2,5> public BitCode4<unsigned short,6,3,2,5>
{ {
@@ -59,7 +56,13 @@ public:
}; };
class HitData {
public:
HitData() {}
~HitData() {}
};

View File

@@ -48,6 +48,10 @@ protected:
class MuonEvent : public MuonEventData { class MuonEvent : public MuonEventData {
public: public:
using MuonEventData::LineIn;
using MuonEventData::LineOut;
using MuonEventData::GetMomentum;
inline HLine3f & LineIn() { return this->m_LineIn; } inline HLine3f & LineIn() { return this->m_LineIn; }
inline HLine3f & LineOut() { return this->m_LineOut; } inline HLine3f & LineOut() { return this->m_LineOut; }
inline Scalarf & Momentum() { return this->m_Momentum; } inline Scalarf & Momentum() { return this->m_Momentum; }

View File

@@ -1,12 +1,13 @@
# TESTS # TESTS
set( TESTS set( TESTS
# GDMLSolidTest
HierarchicalEncodingTest HierarchicalEncodingTest
DetectorChamberTest
) )
set(LIBRARIES set(LIBRARIES
${PACKAGE_LIBPREFIX}Core ${PACKAGE_LIBPREFIX}Core
${PACKAGE_LIBPREFIX}Math ${PACKAGE_LIBPREFIX}Math
${PACKAGE_LIBPREFIX}Detectors
Boost::serialization Boost::serialization
Boost::program_options Boost::program_options
Eigen3::Eigen Eigen3::Eigen

View File

@@ -0,0 +1,139 @@
/*//////////////////////////////////////////////////////////////////////////////
// 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 "HEP/Detectors/DetectorChamber.h"
#include "testing-prototype.h"
#include <iostream>
using namespace uLib;
int main() {
BEGIN_TESTING(DetectorChamber Projection);
DetectorChamber chamber;
// Define a horizontal plane at z = 100
HLine3f plane;
plane.origin = HPoint3f(0, 0, 100);
plane.direction = HVector3f(0, 0, 1); // Normal to the plane
chamber.SetProjectionPlane(plane);
// Create a muon with two segments
MuonEvent muon;
// Segment 1 (muon.LineIn()): origin is at (10, 20, 50), direction along Z
muon.LineIn().origin = HPoint3f(10, 20, 50);
muon.LineIn().direction = HVector3f(0, 0, 1);
// Segment 2 (muon.LineOut()): origin is at (10, 20, 200), direction along Z
muon.LineOut().origin = HPoint3f(10, 20, 200);
muon.LineOut().direction = HVector3f(0, 0, 1);
// distance_in = |50 - 100| = 50
// distance_out = |200 - 100| = 100
// LineIn is closer to the plane (z=100)
MuonEvent projected = chamber.ProjectMuonEvent(muon);
// Expected:
// chosenLine = LineIn (it is closer)
// X_chosen = (10, 20, 50)
// X_proj = (10, 20, 50) - (( (10, 20, 50) - (0, 0, 100) ) . (0, 0, 1)) * (0, 0, 1)
// X_proj = (10, 20, 50) - (-50) * (0, 0, 1) = (10, 20, 100)
HPoint3f expectedPos(10, 20, 100);
std::cout << "Test Case 1: LineIn is closer" << std::endl;
std::cout << "Projected Position: " << projected.LineIn().origin.transpose() << std::endl;
std::cout << "Expected Position: " << expectedPos.transpose() << std::endl;
// Check if the projected position is correct
// norm() includes the 4th component, which for HVector3f (diff of points) should be 0.
bool posOk1 = (projected.LineIn().origin - expectedPos).norm() < 1e-5;
TEST1(posOk1);
// Check if LineIn and LineOut are the same
bool linesMatch1 = (projected.LineIn().origin == projected.LineOut().origin) &&
(projected.LineIn().direction == projected.LineOut().direction);
TEST1(linesMatch1);
// Test Case 2: LineOut is closer
muon.LineIn().origin = HPoint3f(30, 40, 0); // dist = 100
muon.LineOut().origin = HPoint3f(30, 40, 110); // dist = 10
projected = chamber.ProjectMuonEvent(muon);
expectedPos = HPoint3f(30, 40, 100); // projection of (30,40,110) onto z=100
std::cout << "\nTest Case 2: LineOut is closer" << std::endl;
std::cout << "Projected Position: " << projected.LineIn().origin.transpose() << std::endl;
std::cout << "Expected Position: " << expectedPos.transpose() << std::endl;
bool posOk2 = (projected.LineIn().origin - expectedPos).norm() < 1e-5;
TEST1(posOk2);
// Test Case 3: Oblique plane
// Plane through (0,0,0) with normal (1,1,1) (normalized)
plane.origin = HPoint3f(0, 0, 0);
plane.direction = HVector3f(1, 1, 1).normalized();
chamber.SetProjectionPlane(plane);
muon.LineIn().origin = HPoint3f(1, 1, 1); // dist = (1,1,1) . (1,1,1).norm()
muon.LineIn().direction = HVector3f(0, 0, 1);
muon.LineOut().origin = HPoint3f(10, 10, 10); // dist = 10 * sqrt(3)
projected = chamber.ProjectMuonEvent(muon);
// X_chosen = (1,1,1)
// X_proj = (1,1,1) - ((1,1,1) . N) * N where N = (1,1,1)/sqrt(3)
// X_proj = (1,1,1) - (sqrt(3)) * (1,1,1)/sqrt(3) = (1,1,1) - (1,1,1) = (0,0,0)
expectedPos = HPoint3f(0, 0, 0);
std::cout << "\nTest Case 3: Oblique plane" << std::endl;
std::cout << "Projected Position: " << projected.LineIn().origin.transpose() << std::endl;
std::cout << "Expected Position: " << expectedPos.transpose() << std::endl;
bool posOk3 = (projected.LineIn().origin - expectedPos).norm() < 1e-5;
TEST1(posOk3);
// Test Case 4: Transformed DetectorChamber
DetectorChamber chamber2;
chamber2.SetPosition(Vector3f(0, 0, 100)); // Move chamber to z=100
// chamber2.GetProjectionPlane has default origin (0,0,0) and direction (0,0,1)
// In world coordinates, this plane is at z = 100 + 0 = 100.
muon.LineIn().origin = HPoint3f(50, 60, 50); // dist to world plane (z=100) is 50
muon.LineOut().origin = HPoint3f(50, 60, 200); // dist to world plane (z=100) is 100
projected = chamber2.ProjectMuonEvent(muon);
expectedPos = HPoint3f(50, 60, 100);
std::cout << "\nTest Case 4: Transformed DetectorChamber (active world matrix)" << std::endl;
std::cout << "Projected Position: " << projected.LineIn().origin.transpose() << std::endl;
std::cout << "Expected Position: " << expectedPos.transpose() << std::endl;
bool posOk4 = (projected.LineIn().origin - expectedPos).norm() < 1e-5;
TEST1(posOk4);
END_TESTING;
}

View File

@@ -0,0 +1,31 @@
#include "ActionInitialization.hh"
#include "EmitterPrimary.hh"
#include "SteppingAction.hh"
namespace uLib {
namespace Geant {
ActionInitialization::ActionInitialization(EmitterPrimary *emitter, SimulationContext *context)
: G4VUserActionInitialization(),
m_Emitter(emitter),
m_Context(context)
{}
ActionInitialization::~ActionInitialization() {}
void ActionInitialization::BuildForMaster() const {}
void ActionInitialization::Build() const {
if (m_Emitter) {
SetUserAction(m_Emitter->Clone());
} else {
SetUserAction(new EmitterPrimary());
}
SteppingAction *sa = new SteppingAction(m_Context);
SetUserAction(static_cast<G4UserSteppingAction*>(sa));
SetUserAction(static_cast<G4UserEventAction*>(sa));
}
} // namespace Geant
} // namespace uLib

View File

@@ -0,0 +1,28 @@
#ifndef ActionInitialization_h
#define ActionInitialization_h
#include "G4VUserActionInitialization.hh"
#include "SimulationContext.h"
namespace uLib {
namespace Geant {
class EmitterPrimary;
class ActionInitialization : public G4VUserActionInitialization {
public:
ActionInitialization(EmitterPrimary *emitter, SimulationContext *context);
~ActionInitialization();
virtual void BuildForMaster() const override;
virtual void Build() const override;
private:
EmitterPrimary *m_Emitter;
SimulationContext *m_Context;
};
} // namespace Geant
} // namespace uLib
#endif

View File

@@ -0,0 +1,74 @@
################################################################################
##### HEP/Geant - Geant4 integration library ###################################
################################################################################
if(NOT Geant4_FOUND)
message(STATUS "Geant4 not found - skipping mutomGeant library")
return()
endif()
message(STATUS "Geant4 found: ${Geant4_VERSION}")
include(${Geant4_USE_FILE})
set(HEADERS
GeantEvent.h
Matter.h
Scene.h
Solid.h
DetectorConstruction.hh
PhysicsList.hh
ActionInitialization.hh
SteppingAction.hh
SimulationContext.h
)
set(SOURCES
Scene.cpp
Solid.cpp
EmitterPrimary.cpp
DetectorConstruction.cpp
PhysicsList.cpp
ActionInitialization.cpp
SteppingAction.cpp
)
set(libname ${PACKAGE_LIBPREFIX}Geant)
set(ULIB_SHARED_LIBRARIES ${ULIB_SHARED_LIBRARIES} ${libname} PARENT_SCOPE)
set(ULIB_SELECTED_MODULES ${ULIB_SELECTED_MODULES} Geant PARENT_SCOPE)
add_library(${libname} SHARED ${SOURCES})
set_target_properties(${libname} PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_SOVERSION})
target_include_directories(${libname} PRIVATE ${Geant4_INCLUDE_DIRS})
target_link_libraries(${libname}
${PACKAGE_LIBPREFIX}Core
${PACKAGE_LIBPREFIX}Math
${PACKAGE_LIBPREFIX}Detectors
)
# Filter Geant4 libraries to remove Qt-dependent ones
set(Geant4_LIBS_FILTERED ${Geant4_LIBRARIES})
if(Geant4_LIBS_FILTERED)
list(REMOVE_ITEM Geant4_LIBS_FILTERED Geant4::G4interfaces Geant4::G4OpenGL Geant4::G4visQt3D)
endif()
target_link_libraries(${libname}
${Geant4_LIBS_FILTERED}
)
install(TARGETS ${libname}
EXPORT "uLibTargets"
RUNTIME DESTINATION ${INSTALL_BIN_DIR} COMPONENT bin
LIBRARY DESTINATION ${INSTALL_LIB_DIR} COMPONENT lib)
install(FILES ${HEADERS}
DESTINATION ${INSTALL_INC_DIR}/HEP/Geant)
if(BUILD_TESTING)
include(uLibTargetMacros)
add_subdirectory(testing)
endif()

View File

@@ -0,0 +1,43 @@
#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() {}
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);
SetUserAction(static_cast<G4UserSteppingAction*>(sa));
SetUserAction(static_cast<G4UserEventAction*>(sa));
}
}
} // namespace Geant
} // namespace uLib

View 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

View File

@@ -0,0 +1,26 @@
#include "DetectorConstruction.hh"
#include "Core/Object.h"
#include "Math/ContainerBox.h"
#include "G4Box.hh"
#include "G4LogicalVolume.hh"
#include "G4NistManager.hh"
#include "G4PVPlacement.hh"
#include "G4RunManager.hh"
#include "G4SystemOfUnits.hh"
namespace uLib {
namespace Geant {
DetectorConstruction::DetectorConstruction(const char *name) : G4VUserDetectorConstruction() {}
DetectorConstruction::~DetectorConstruction() {}
G4VPhysicalVolume *DetectorConstruction::Construct() { return nullptr; }
void DetectorConstruction::ConstructSDandField() {}
} // namespace Geant
} // namespace uLib

View File

@@ -0,0 +1,30 @@
#ifndef DetectorConstruction_h
#define DetectorConstruction_h
#include "Core/Object.h"
#include "G4VUserDetectorConstruction.hh"
#include "globals.hh"
class G4VPhysicalVolume;
class G4LogicalVolume;
namespace uLib {
namespace Geant {
class DetectorConstruction : public G4VUserDetectorConstruction {
public:
DetectorConstruction(const char *name);
virtual ~DetectorConstruction();
virtual G4VPhysicalVolume *Construct();
virtual void ConstructSDandField();
};
} // namespace Geant
} // namespace uLib
#endif

View 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

View 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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,330 @@
#include "EmitterPrimary.hh"
#include "G4Box.hh"
#include "G4LogicalVolume.hh"
#include "G4LogicalVolumeStore.hh"
#include "G4ParticleDefinition.hh"
#include "G4ParticleGun.hh"
#include "G4ParticleTable.hh"
#include "G4RunManager.hh"
#include "G4SystemOfUnits.hh"
#include "Randomize.hh"
#include "EcoMug.hh"
#include "Math/Cylinder.h"
namespace uLib {
namespace Geant {
EmitterPrimary::EmitterPrimary()
: G4VUserPrimaryGeneratorAction(), fParticleGun(nullptr) {
// Creiamo il ParticleGun impostandolo per sparare 1 particella alla volta
G4int n_particle = 1;
fParticleGun = new G4ParticleGun(n_particle);
// Otteniamo la tabella delle particelle di Geant4
G4ParticleTable *particleTable = G4ParticleTable::GetParticleTable();
// Cerchiamo il muone negativo (usa "mu+" per l'antimuone)
G4String particleName = "mu-";
G4ParticleDefinition *particle = particleTable->FindParticle(particleName);
// Configuriamo le proprietà iniziali della particella
fParticleGun->SetParticleDefinition(particle);
// Impostiamo l'energia cinetica a 1 GeV
fParticleGun->SetParticleEnergy(1.0 * GeV);
// Initial position and direction through AffineTransform
// 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() {
// Importante: liberare la memoria
delete fParticleGun;
}
void EmitterPrimary::GeneratePrimaries(G4Event *anEvent) {
// Use position and direction from AffineTransform
Vector3f pos = this->GetPosition();
// 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);
}
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()
: EmitterPrimary(), m_Mesh(nullptr), m_TotalArea(0.0) {
}
QuadMeshEmitterPrimary::~QuadMeshEmitterPrimary() {
}
void QuadMeshEmitterPrimary::SetMesh(uLib::QuadMesh *mesh) {
m_Mesh = mesh;
CalculateAreas();
}
void QuadMeshEmitterPrimary::CalculateAreas() {
if (!m_Mesh) return;
m_CumulativeAreas.clear();
m_TotalArea = 0.0;
const auto &quads = m_Mesh->Quads();
for (const auto &q : quads) {
uLib::Vector3f v0 = m_Mesh->GetPoint(q(0));
uLib::Vector3f v1 = m_Mesh->GetPoint(q(1));
uLib::Vector3f v2 = m_Mesh->GetPoint(q(2));
uLib::Vector3f v3 = m_Mesh->GetPoint(q(3));
double a1 = 0.5 * (v1 - v0).cross(v2 - v0).norm();
double a2 = 0.5 * (v2 - v0).cross(v3 - v0).norm();
m_TotalArea += (a1 + a2);
m_CumulativeAreas.push_back(m_TotalArea);
}
}
void QuadMeshEmitterPrimary::GeneratePrimaries(G4Event *anEvent) {
if (!m_Mesh || m_TotalArea <= 0.0) return;
// 1. Choose a quad
double r = G4UniformRand() * m_TotalArea;
auto it = std::lower_bound(m_CumulativeAreas.begin(), m_CumulativeAreas.end(), r);
int quadIdx = std::distance(m_CumulativeAreas.begin(), it);
const auto &q = m_Mesh->Quads()[quadIdx];
uLib::Vector3f v0 = m_Mesh->GetPoint(q(0));
uLib::Vector3f v1 = m_Mesh->GetPoint(q(1));
uLib::Vector3f v2 = m_Mesh->GetPoint(q(2));
uLib::Vector3f v3 = m_Mesh->GetPoint(q(3));
// 2. Choose a point on the quad
double a1 = 0.5 * (v1 - v0).cross(v2 - v0).norm();
double a2 = 0.5 * (v2 - v0).cross(v3 - v0).norm();
G4ThreeVector pos;
uLib::Vector3f normal = m_Mesh->GetNormal(quadIdx);
if (G4UniformRand() < a1 / (a1 + a2)) {
double u = G4UniformRand();
double v = G4UniformRand();
if (u + v > 1.0) { u = 1.0 - u; v = 1.0 - v; }
uLib::Vector3f p = v0 + u * (v1 - v0) + v * (v2 - v0);
pos.set(p(0), p(1), p(2));
} else {
double u = G4UniformRand();
double v = G4UniformRand();
if (u + v > 1.0) { u = 1.0 - u; v = 1.0 - v; }
uLib::Vector3f p = v0 + u * (v2 - v0) + v * (v3 - v0);
pos.set(p(0), p(1), p(2));
}
// 3. Choose a direction (Cosmic Muon: cos^2(theta))
G4ThreeVector dir;
bool accepted = false;
int tries = 0;
while (!accepted && tries < 1000) {
tries++;
double cosTheta = std::pow(G4UniformRand(), 1.0/3.0);
double sinTheta = std::sqrt(1.0 - cosTheta * cosTheta);
double phi = 2.0 * M_PI * G4UniformRand();
// Incoming from above (+Z towards -Z)
dir.set(sinTheta * std::cos(phi), sinTheta * std::sin(phi), -cosTheta);
// Filtering: pointing on the same side of the face normal
if (dir.x() * normal(0) + dir.y() * normal(1) + dir.z() * normal(2) > 0) {
accepted = true;
}
}
if (accepted) {
fParticleGun->SetParticlePosition(pos);
fParticleGun->SetParticleMomentumDirection(dir);
// Keep energy from base class or set here if needed
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 uLib

View File

@@ -0,0 +1,127 @@
#ifndef U_GEANT_EMITTERPRIMARY_HH
#define U_GEANT_EMITTERPRIMARY_HH 1
#include "G4VUserPrimaryGeneratorAction.hh"
#include "globals.hh"
namespace uLib {
class QuadMesh;
}
class EcoMug;
#include "Math/QuadMesh.h"
#include "Core/Object.h"
#include "Math/Transform.h"
#include <vector> // Added for std::vector
class G4ParticleGun;
class G4Event;
namespace uLib {
namespace Geant {
class EmitterPrimary : public G4VUserPrimaryGeneratorAction, public Object, public AffineTransform
{
public:
virtual const char* GetClassName() const override { return "Geant.EmitterPrimary"; }
EmitterPrimary();
virtual ~EmitterPrimary();
// Metodo principale chiamato all'inizio di ogni evento
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:
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
{
public:
virtual const char* GetClassName() const override { return "Geant.QuadMeshEmitterPrimary"; }
QuadMeshEmitterPrimary();
virtual ~QuadMeshEmitterPrimary();
// Metodo principale chiamato all'inizio di ogni evento
virtual void GeneratePrimaries(G4Event*);
void SetMesh(uLib::QuadMesh* mesh);
virtual EmitterPrimary* Clone() const override;
private:
uLib::QuadMesh* m_Mesh;
std::vector<double> m_CumulativeAreas;
double m_TotalArea;
void CalculateAreas();
};
} // namespace Geant
} // namespace uLib
#endif

View File

@@ -0,0 +1,25 @@
#include "HEP/Geant/GeantEvent.h"
#include <cstddef>
#include <iostream>
using namespace uLib;
void GeantEvent::Print(const size_t size) const {
std::cout << "Event " << m_EventID << ":" << std::endl;
std::cout << " Momentum: " << m_Momentum << std::endl;
std::cout << " GenVector: " << m_GenVector << std::endl;
std::cout << " Path: " << std::endl;
size_t limit = m_Path.size();
if(size > 0 && size < m_Path.size()) {
limit = size;
}
for (size_t i = 0; i < limit; ++i) {
std::cout << " " << i << ": " << m_Path[i].m_Length << " " << m_Path[i].m_Momentum << " " << m_Path[i].m_Direction << " " << m_Path[i].m_SolidName << std::endl;
}
if (limit < m_Path.size()) {
std::cout << " ... (" << m_Path.size() - limit << " more deltas)" << std::endl;
}
}

View File

@@ -0,0 +1,96 @@
/*//////////////////////////////////////////////////////////////////////////////
// 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_GEANTEVENT_H
#define U_GEANTEVENT_H
#include "Core/Object.h"
#include "Core/Types.h"
#include "Core/Vector.h"
#include "Math/Dense.h"
#include <string>
namespace uLib {
namespace Geant {
// Forward declaration for friend access
class SteppingAction;
///////////////////////////////////////////////////////////////////////////////
/// GeantEvent — output of a Geant4 simulation run.
///
/// m_Momentum and m_GenVector are set from the EmitterPrimary at generation.
/// During simulation, each scattering interaction deposits a Delta in m_Path,
/// recording the change of momentum and direction at each step boundary.
///////////////////////////////////////////////////////////////////////////////
class GeantEvent : public Object {
public:
virtual const char* GetClassName() const override { return "Geant.GeantEvent"; }
/// A single interaction step along the muon path.
struct Delta {
Scalarf m_Length; ///< step length through the solid
Scalarf m_Momentum; ///< momentum magnitude at this step
HVector3f m_Direction; ///< direction after scattering
std::string m_SolidName; ///< name of the solid where interaction occurred
uLibGetMacro(Length, Scalarf)
uLibGetMacro(Momentum, Scalarf)
uLibConstRefMacro(Direction, HVector3f)
uLibConstRefMacro(SolidName, std::string)
Delta() : m_Length(0), m_Momentum(0), m_Direction(HVector3f::Zero()) {}
};
// --- Read-only accessors (public) --- //
uLibGetMacro(EventID, Id_t)
uLibGetMacro(Momentum, Scalarf)
uLibConstRefMacro(GenVector, HLine3f)
uLibConstRefMacro(Path, Vector<Delta>)
void Print(const size_t size = 10) const;
private:
Id_t m_EventID;
Scalarf m_Momentum;
HLine3f m_GenVector;
Vector<Delta> m_Path;
public:
GeantEvent() : m_EventID(0), m_Momentum(0), m_GenVector(HLine3f()), m_Path() {}
// SteppingAction can populate the private fields during simulation
friend class SteppingAction;
};
} // namespace Geant
} // namespace uLib
#endif // U_GEANTEVENT_H

View File

@@ -34,6 +34,7 @@ class G4Element;
class G4Material; class G4Material;
namespace uLib { namespace uLib {
namespace Geant {
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@@ -58,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;
@@ -65,6 +68,7 @@ private:
}
} }

View File

@@ -0,0 +1,28 @@
#include "PhysicsList.hh"
#include "G4DecayPhysics.hh"
#include "G4EmStandardPhysics.hh"
#include "G4RadioactiveDecayPhysics.hh"
namespace uLib {
namespace Geant {
PhysicsList::PhysicsList() : G4VModularPhysicsList() {
SetVerboseLevel(1);
// Default physics
RegisterPhysics(new G4DecayPhysics());
// EM physics
RegisterPhysics(new G4EmStandardPhysics());
// Radioactive decay
RegisterPhysics(new G4RadioactiveDecayPhysics());
}
PhysicsList::~PhysicsList() {}
void PhysicsList::SetCuts() { G4VModularPhysicsList::SetCuts(); }
} // namespace Geant
} // namespace uLib

View File

@@ -0,0 +1,20 @@
#ifndef PhysicsList_h
#define PhysicsList_h
#include "G4VModularPhysicsList.hh"
namespace uLib {
namespace Geant {
class PhysicsList : public G4VModularPhysicsList {
public:
PhysicsList();
virtual ~PhysicsList();
virtual void SetCuts();
};
} // namespace Geant
} // namespace uLib
#endif

160
src/HEP/Geant/Scene.cpp Normal file
View File

@@ -0,0 +1,160 @@
#include <Geant4/G4Box.hh>
#include <Geant4/G4LogicalVolume.hh>
#include <Geant4/G4Material.hh>
#include <Geant4/G4NistManager.hh>
#include <Geant4/G4PVPlacement.hh>
#include <Geant4/G4RunManager.hh>
#include <Geant4/G4RunManagerFactory.hh>
#include <Geant4/G4SystemOfUnits.hh>
#include <Geant4/G4VPhysicalVolume.hh>
#include "Core/Vector.h"
#include "HEP/Geant/DetectorConstruction.hh"
#include "Math/ContainerBox.h"
#include "Math/Dense.h"
#include "Solid.h"
#include "Scene.h"
#include "PhysicsList.hh"
#include "ActionInitialization.hh"
#include "SimulationContext.h"
#include "HEP/Detectors/DetectorChamber.h"
namespace uLib {
namespace Geant {
class SceneDetectorConstruction : public DetectorConstruction {
public:
SceneDetectorConstruction(class SceneImpl *owner) : DetectorConstruction("Scene"), m_Owner(owner) {}
G4VPhysicalVolume *Construct() override;
private:
class SceneImpl *m_Owner;
};
static void CheckGeant4Environment() {
static bool checked = false;
if (checked) return;
checked = true;
if (!std::getenv("G4ENSDFSTATEDATA")) {
std::cerr << "********************************************************" << std::endl;
std::cerr << " WARNING: Geant4 environment variables are not set!" << std::endl;
std::cerr << " Please activate the environment before running:" << std::endl;
std::cerr << " micromamba activate mutom" << std::endl;
std::cerr << "********************************************************" << std::endl;
}
}
class SceneImpl {
public:
SceneImpl() : m_RunManager(G4RunManagerFactory::CreateRunManager(G4RunManagerType::Default)),
m_Emitter(nullptr),
m_InitCalled(false) {
m_RunManager->SetUserInitialization(new PhysicsList);
}
~SceneImpl() {
if (m_RunManager) delete m_RunManager;
// m_World deletion is handled in Scene destructor or here
}
void Initialize() {
if (m_InitCalled) return;
m_RunManager->SetUserInitialization(new SceneDetectorConstruction(this));
m_RunManager->SetUserInitialization(new ActionInitialization(m_Emitter, &m_Context));
m_RunManager->Initialize();
m_InitCalled = true;
}
Vector<Solid *> m_Solids;
Solid *m_World = nullptr;
ContainerBox m_WorldBox;
G4RunManager *m_RunManager;
EmitterPrimary *m_Emitter;
SimulationContext m_Context;
bool m_InitCalled;
};
G4VPhysicalVolume *SceneDetectorConstruction::Construct() {
return m_Owner->m_World->GetPhysical();
}
Scene::Scene() {
CheckGeant4Environment();
d = new SceneImpl();
}
Scene::~Scene() {
// Delete solids
for(auto s : d->m_Solids) delete s;
delete d;
}
void Scene::AddSolid(Solid *solid, Solid *parent) {
d->m_Solids.push_back(solid);
if (!d->m_World) {
d->m_World = solid;
} else {
solid->SetParent(parent ? parent : d->m_World);
}
}
const Solid* Scene::GetWorld() const { return d->m_World; }
ContainerBox* Scene::GetWorldBox() const { return &d->m_WorldBox; }
void Scene::ConstructWorldBox(const Vector3f &size, const char *material) {
d->m_WorldBox.Scale(size);
d->m_WorldBox.SetPosition(-size/2.0f);
if (!d->m_World) {
d->m_World = new Solid("World");
d->m_World->SetNistMaterial(material);
AddSolid(d->m_World);
}
G4Box *solidWorld = new G4Box("World", 0.5 * size(0), 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);
G4PVPlacement *physWorld = new G4PVPlacement(nullptr, G4ThreeVector(0, 0, 0), logicWorld, d->m_World->GetName(), 0, false, 0, true);
d->m_World->SetPhysical(physWorld);
}
void Scene::SetEmitter(EmitterPrimary *emitter) { d->m_Emitter = emitter; }
void Scene::Initialize() { d->Initialize(); }
void Scene::SetVerbosity(int level) {
d->m_Context.verbosity = level;
if (d->m_RunManager) d->m_RunManager->SetVerboseLevel(level);
}
void Scene::RunSimulation(int nEvents, Vector<GeantEvent> &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);
}
void Scene::RunDetectorSimulation(int nEvents, Vector<MuonEvent> &results) {
d->Initialize(); // Ensure initialized
d->m_Context.mode = SimulationMode::DETECTOR;
d->m_Context.outputGeant = nullptr;
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);
}
} // namespace Geant
} // namespace uLib

84
src/HEP/Geant/Scene.h Normal file
View File

@@ -0,0 +1,84 @@
/*//////////////////////////////////////////////////////////////////////////////
// CMT Cosmic Muon Tomography project //////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
Copyright (c) 2014, Universita' degli Studi di Padova, INFN sez. di Padova
All rights reserved
Authors: Andrea Rigoni Garola < andrea.rigoni@pd.infn.it >
------------------------------------------------------------------
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 SCENE_H
#define SCENE_H
#include "Core/Object.h"
#include "Core/Vector.h"
#include "Solid.h"
#include "GeantEvent.h"
#include "HEP/Detectors/MuonEvent.h"
class G4VPhysicalVolume;
namespace uLib {
namespace Geant {
class EmitterPrimary;
class Scene : public Object {
public:
virtual const char* GetClassName() const override { return "Geant.Scene"; }
Scene();
~Scene();
void AddSolid(Solid *solid, Solid *parent = nullptr);
void ConstructWorldBox(const Vector3f &size, const char *material);
/// Get the world box
const Solid* GetWorld() const;
ContainerBox* GetWorldBox() const;
/// Set the primary generator (emitter) for the simulation.
/// The Scene does NOT take ownership of the emitter.
void SetEmitter(EmitterPrimary *emitter);
/// Initialize the Geant4 run manager with detector, physics, and action.
void Initialize();
/// Set the verbosity level for console output (default 0)
void SetVerbosity(int level);
/// Run the simulation for nEvents muons.
/// Results are appended to the provided vector.
void RunSimulation(int nEvents, Vector<GeantEvent> &results);
/// Specialized detector simulation trackingMuonEvent line crossings.
void RunDetectorSimulation(int nEvents, Vector<MuonEvent> &results);
private:
class SceneImpl *d;
};
} // namespace Geant
} // namespace uLib
#endif // SCENE_H

View 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

208
src/HEP/Geant/Solid.cpp Normal file
View File

@@ -0,0 +1,208 @@
/*//////////////////////////////////////////////////////////////////////////////
// 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.
//////////////////////////////////////////////////////////////////////////////*/
// G4 Solid //
#include <CLHEP/Units/SystemOfUnits.h>
#include <Geant4/G4LogicalVolume.hh>
#include <Geant4/G4Material.hh>
#include <Geant4/G4NistManager.hh>
// Tessellated solid //
#include <Geant4/G4TessellatedSolid.hh>
#include <Geant4/G4ThreeVector.hh>
#include <Geant4/G4TriangularFacet.hh>
#include <Geant4/G4Box.hh>
#include <Geant4/G4PVPlacement.hh>
#include "Math/Dense.h"
#include "Math/Transform.h"
#include "Solid.h"
namespace uLib {
namespace Geant {
class DetectorsSolidImpl {
public:
static G4ThreeVector getG4Vector3f(const Vector3f &vector) {
return G4ThreeVector(vector(0), vector(1), vector(2));
}
};
Solid::Solid()
: m_Name("unnamed_solid"), m_Material(NULL), m_Logical(NULL), m_Physical(NULL),
m_Position(new G4ThreeVector(0,0,0)), m_Rotation(NULL) {}
Solid::Solid(const char *name)
: m_Name(name), m_Material(NULL), m_Logical(NULL), m_Physical(NULL),
m_Position(new G4ThreeVector(0,0,0)), m_Rotation(NULL) {}
Solid::~Solid() {
if (m_Position) delete m_Position;
if (m_Rotation) delete m_Rotation;
}
void Solid::SetNistMaterial(const char *name) {
G4NistManager *nist = G4NistManager::Instance();
G4Material *mat = nist->FindOrBuildMaterial(name);
if (mat) SetMaterial(mat);
}
void Solid::SetMaterial(G4Material *material) {
if (material) {
m_Material = material;
if (m_Logical) {
m_Logical->SetMaterial(material);
} else if (GetG4Solid()) {
m_Logical = new G4LogicalVolume(GetG4Solid(), m_Material, GetName());
}
}
}
void Solid::SetTransform(Matrix4f transform) {
uLib::AffineTransform t;
t.SetMatrix(transform);
// 2. Extract position and rotation for Geant4
Vector3f pos = t.GetPosition();
if (!m_Position) m_Position = new G4ThreeVector();
*m_Position = G4ThreeVector(pos(0), pos(1), pos(2));
// Create a G4 rotation matrix from the 4x4 matrix
Matrix3f m = t.GetRotation();
if (!m_Rotation) m_Rotation = new G4RotationMatrix();
m_Rotation->set(G4ThreeVector(m(0,0), m(1,0), m(2,0)),
G4ThreeVector(m(0,1), m(1,1), m(2,1)),
G4ThreeVector(m(0,2), m(1,2), m(2,2)));
// 3. If object is already placed, update its transformation
if (m_Physical) {
m_Physical->SetTranslation(*m_Position);
m_Physical->SetRotation(m_Rotation);
}
}
void Solid::SetParent(Solid *parent) {
if (!m_Logical) {
std::cerr << "logical volume not created for solid " << GetName() << std::endl;
return;
}
if(m_Physical) {
std::cerr << "physical volume already created for solid " << GetName() << std::endl;
return;
}
G4LogicalVolume* parentLogical = nullptr;
if (parent) {
parentLogical = parent->GetLogical();
if (!parentLogical) {
std::cerr << "parent logical volume not created for solid " << parent->GetName() << std::endl;
return;
}
}
// G4PVPlacement
m_Physical = new G4PVPlacement(
m_Rotation, // Rotation
*m_Position, // Position (translation) inside the parent
m_Logical, // The logical volume of this solid (the child)
GetName(), // Name of the physical volume
parentLogical, // The logical volume of the parent (nullptr if it's the World volume)
false, // Boolean operations (usually false)
0, // Copy number
true // Check overlaps (useful to enable in debug phase)
);
}
TessellatedSolid::TessellatedSolid(const char *name)
: BaseClass(name), m_Solid(new G4TessellatedSolid(name)) {
}
void TessellatedSolid::SetMesh(TriangleMesh &mesh) {
G4TessellatedSolid *ts = this->m_Solid;
for (int i = 0; i < mesh.Triangles().size(); ++i) {
const Vector3i &trg = mesh.Triangles().at(i);
G4TriangularFacet *facet = new G4TriangularFacet(
DetectorsSolidImpl::getG4Vector3f(mesh.Points().at(trg(0))),
DetectorsSolidImpl::getG4Vector3f(mesh.Points().at(trg(1))),
DetectorsSolidImpl::getG4Vector3f(mesh.Points().at(trg(2))), ABSOLUTE);
ts->AddFacet((G4VFacet *)facet);
}
if (this->m_Logical) {
this->m_Logical->SetSolid(ts);
}
}
BoxSolid::BoxSolid(const char *name, ContainerBox *box) : BaseClass(name) {
m_Solid = new G4Box(name, 1,1,1);
m_Object = box;
Object::connect(box, &ContainerBox::Updated, this, &BoxSolid::Update);
if (m_Logical) {
m_Logical->SetSolid(m_Solid);
}
Update();
}
void BoxSolid::Update() {
if (m_Object) {
Vector3f size = m_Object->GetSize();
m_Solid->SetXHalfLength(size(0) * 0.5);
m_Solid->SetYHalfLength(size(1) * 0.5);
m_Solid->SetZHalfLength(size(2) * 0.5);
// Geant4 placement is relative to center. uLib Box is anchored at corner.
// 1. Get position and rotation (clean, without scale)
Vector3f pos = m_Object->GetPosition();
Matrix3f rot = m_Object->GetRotation();
// 2. Center = Corner + Rotation * (Half-Size)
// We must rotate the offset vector because uLib box can be rotated.
Vector3f center = pos + rot * (size * 0.5);
uLib::AffineTransform t;
t.SetPosition(center);
t.SetRotation(rot);
this->SetTransform(t.GetMatrix());
}
}
} // namespace Geant
} // namespace uLib

138
src/HEP/Geant/Solid.h Normal file
View File

@@ -0,0 +1,138 @@
/*//////////////////////////////////////////////////////////////////////////////
// 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 SOLID_H
#define SOLID_H
#include "Core/Object.h"
#include "Geant/Matter.h"
#include <Geant4/G4LogicalVolume.hh>
#include "Math/ContainerBox.h"
#include "Math/Dense.h"
#include "Math/TriangleMesh.h"
class G4Material;
class G4LogicalVolume;
class G4TessellatedSolid;
class G4Box;
namespace uLib {
namespace Geant {
class Solid : public Object {
public:
virtual const char* GetClassName() const override { return "Geant.Solid"; }
Solid();
Solid(const char *name);
virtual ~Solid();
void SetNistMaterial(const char *name);
void SetMaterial(G4Material *material);
void SetSizeUnit(const char *unit);
// Implementiamo SetParent qui, per tutti.
virtual void SetParent(Solid *parent);
// Setters per la posizione (necessari per il piazzamento)
void SetTransform(Matrix4f transform);
uLibGetMacro(Material, G4Material *)
uLibGetSetMacro(Logical, G4LogicalVolume *)
uLibGetSetMacro(Physical, G4VPhysicalVolume *)
virtual G4VSolid* GetG4Solid() const { return nullptr; }
inline const char *GetName() const {
return m_Logical ? m_Logical->GetName().c_str() : m_Name.c_str();
}
protected:
std::string m_Name;
G4Material *m_Material;
G4LogicalVolume *m_Logical;
G4VPhysicalVolume *m_Physical; // <-- Memorizza l'oggetto posizionato
G4ThreeVector *m_Position; // <-- Offset rispetto al centro del padre
G4RotationMatrix* m_Rotation; // <-- Rotazione rispetto al padre
};
class TessellatedSolid : public Solid {
typedef Solid BaseClass;
public:
virtual const char* GetClassName() const override { return "Geant.TessellatedSolid"; }
TessellatedSolid(const char *name);
void SetMesh(TriangleMesh &mesh);
uLibGetMacro(Solid, G4TessellatedSolid *)
virtual G4VSolid* GetG4Solid() const override { return (G4VSolid*)m_Solid; }
public slots:
void Update();
private :
G4TessellatedSolid *m_Solid;
};
class BoxSolid : public Solid {
typedef Solid BaseClass;
public:
virtual const char* GetClassName() const override { return "Geant.BoxSolid"; }
BoxSolid(const char *name, ContainerBox *box);
virtual G4VSolid* GetG4Solid() const override { return (G4VSolid*)m_Solid; }
ContainerBox* GetObject() const { return m_Object; }
public slots:
void Update();
private:
ContainerBox *m_Object;
G4Box *m_Solid;
};
} // namespace Geant
} // namespace uLib
#endif // SOLID_H

View File

@@ -0,0 +1,125 @@
#include "SteppingAction.hh"
#include <Geant4/G4Step.hh>
#include <Geant4/G4Track.hh>
#include <Geant4/G4Event.hh>
#include <Geant4/G4SystemOfUnits.hh>
#include <set>
#include <iostream>
#include <mutex>
#include <cmath>
namespace uLib {
namespace Geant {
SteppingAction::SteppingAction(SimulationContext *context)
: G4UserSteppingAction(),
G4UserEventAction(),
m_Context(context),
m_Verbosity(0)
{}
SteppingAction::~SteppingAction() {}
void SteppingAction::BeginOfEventAction(const G4Event *event) {
if (!event || !m_Context) return;
if (m_Context->mode == SimulationMode::DETAILED) {
m_CurrentGeant = GeantEvent();
m_CurrentGeant.m_EventID = static_cast<Id_t>(event->GetEventID());
if (event->GetNumberOfPrimaryVertex() > 0) {
G4PrimaryVertex *vtx = event->GetPrimaryVertex(0);
G4ThreeVector pos = vtx->GetPosition();
m_CurrentGeant.m_GenVector.origin = HPoint3f(pos.x(), pos.y(), pos.z());
if (vtx->GetNumberOfParticle() > 0) {
G4PrimaryParticle *prim = vtx->GetPrimary(0);
G4ThreeVector mom = prim->GetMomentumDirection();
m_CurrentGeant.m_GenVector.direction = HVector3f(mom.x(), mom.y(), mom.z());
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) {
if (!m_Context) return;
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) {
if (!step || !m_Context) return;
const G4Track *track = step->GetTrack();
if (!track || track->GetParentID() != 0) return;
if (m_Context->mode == SimulationMode::DETAILED) {
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());
if (track->GetVolume()) {
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);
}
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());
for (const auto& plane : m_Context->detectorPlanes) {
float d1 = (p1f - plane.origin).dot(plane.direction);
float d2 = (p2f - plane.origin).dot(plane.direction);
if ((d1 > 0 && d2 <= 0) || (d1 < 0 && d2 >= 0)) {
float t = d1 / (d1 - d2);
HPoint3f intersection = p1f + t * (p2f - p1f);
if (m_MuonCrossCount == 0) {
m_CurrentMuon.LineIn().origin = intersection;
m_CurrentMuon.LineIn().direction = dirf;
m_MuonCrossCount++;
} else if (m_MuonCrossCount == 1) {
m_CurrentMuon.LineOut().origin = intersection;
m_CurrentMuon.LineOut().direction = dirf;
m_MuonCrossCount++;
}
}
}
}
}
} // namespace Geant
} // namespace uLib

View File

@@ -0,0 +1,37 @@
#ifndef U_GEANT_STEPPINGACTION_HH
#define U_GEANT_STEPPINGACTION_HH
#include "G4UserSteppingAction.hh"
#include "G4UserEventAction.hh"
#include "Core/Vector.h"
#include "GeantEvent.h"
#include "HEP/Detectors/MuonEvent.h"
#include "SimulationContext.h"
namespace uLib {
namespace Geant {
/// SteppingAction collects scattering data at each Geant4 step.
class SteppingAction : public G4UserSteppingAction, public G4UserEventAction {
public:
SteppingAction(SimulationContext *context);
virtual ~SteppingAction();
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:
SimulationContext *m_Context;
GeantEvent m_CurrentGeant;
MuonEvent m_CurrentMuon;
int m_MuonCrossCount = 0;
int m_Verbosity = 0;
};
} // namespace Geant
} // namespace uLib
#endif

View File

@@ -0,0 +1,23 @@
#include "HEP/Geant/ActionInitialization.hh" // Il file appena creato
#include "G4RunManagerFactory.hh" // Per il RunManager moderno
// ... altri include (DetectorConstruction, PhysicsList, ecc.)
int main(int argc, char **argv) {
// Creazione del Run Manager
auto *runManager = G4RunManagerFactory::CreateRunManager();
// 1. Inizializzazione della Geometria
// runManager->SetUserInitialization(new DetectorConstruction());
// 2. Inizializzazione della Fisica
// runManager->SetUserInitialization(new PhysicsList());
// 3. INIZIALIZZAZIONE DELLE AZIONI (Il nostro generatore!)
runManager->SetUserInitialization(new uLib::Geant::ActionInitialization(nullptr, nullptr));
// ... Inizializzazione del kernel ( runManager->Initialize(); ), UI manager,
// vis manager, ecc.
delete runManager;
return 0;
}

View File

@@ -0,0 +1,16 @@
# TESTS
set(TESTS
SolidTest
EventTest
GeantApp
ActionInitialization
SkyPlaneEmitterTest
)
set(LIBRARIES
${PACKAGE_LIBPREFIX}Core
${PACKAGE_LIBPREFIX}Math
${PACKAGE_LIBPREFIX}Geant
Eigen3::Eigen
)
uLib_add_tests(Geant)

View File

@@ -0,0 +1,90 @@
#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/TriangleMesh.h"
#include "Math/Dense.h"
#include "Math/Units.h"
#include "testing-prototype.h"
#include <Geant4/G4Material.hh>
#include <Geant4/G4NistManager.hh>
#include <Geant4/G4LogicalVolume.hh>
#include <Geant4/G4TessellatedSolid.hh>
#include <string.h>
using namespace uLib;
int main() {
BEGIN_TESTING(Geant Event);
// Test: Scene with iron cube in air, launch muons, collect events //
{
// 1. Create world box (air, 30m x 30m x 30m)
Geant::Scene scene;
scene.ConstructWorldBox(Vector3f(30_m, 30_m, 30_m), "G4_AIR");
// 2. Create iron cube (1m x 1m x 1m) at center
ContainerBox iron_box(Vector3f(1000, 1000, 1000)); // mm
Geant::BoxSolid *iron_cube = new Geant::BoxSolid("IronCube", &iron_box);
iron_cube->SetNistMaterial("G4_Fe");
iron_cube->Update(); // apply dimensions
scene.AddSolid(iron_cube);
// 3. Set up emitter (default: mu- at 1 GeV, from z=+10m downward)
Geant::EmitterPrimary *emitter = new Geant::EmitterPrimary();
scene.SetEmitter(emitter);
// 4. Initialize Geant4
scene.Initialize();
// 5. Run simulation: 10 muons
int nEvents = 10;
Vector<Geant::GeantEvent> results;
scene.RunSimulation(nEvents, results);
// 6. Check results
printf(" Collected %zu events\n", results.size());
TEST1(results.size() > 0);
for (size_t i = 0; i < results.size(); ++i) {
const Geant::GeantEvent &ev = results[i];
bool hitIron = false;
for (const auto &d : ev.Path()) {
if (d.SolidName() == "IronCube") {
hitIron = true;
break;
}
}
printf(" Event %d: momentum=%.1f MeV, path steps=%zu, hitIron=%s\n",
ev.GetEventID(),
ev.GetMomentum(),
ev.Path().size(),
hitIron ? "YES" : "NO");
// Each event should have at least one step
TEST1(ev.Path().size() > 0);
// Print first few deltas
const auto &path = ev.Path();
for (size_t j = 0; j < path.size() && j < 5; ++j) {
const auto &d = path[j];
printf(" Delta[%zu]: solid=%s len=%.2f mm, p=%.1f MeV, "
"dir=(%.3f, %.3f, %.3f)\n",
j,
d.SolidName().c_str(),
d.GetLength(),
d.GetMomentum(),
d.Direction()(0),
d.Direction()(1),
d.Direction()(2));
}
if (path.size() > 5) {
printf(" ... (%zu more deltas)\n", path.size() - 5);
}
}
}
END_TESTING
}

View File

@@ -0,0 +1,16 @@
#include "Math/ContainerBox.h"
#include "Math/Dense.h"
#include "HEP/Geant/Scene.h"
using namespace uLib;
int main() {
uLib::Geant::Scene scene;
scene.ConstructWorldBox(Vector3f(100, 100, 100), "G4_AIR");
scene.Initialize();
return 0;
}

View 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;
}

Some files were not shown because too many files have changed in this diff Show More