Compare commits
17 Commits
serializat
...
8a01ee7f40
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a01ee7f40 | ||
|
|
03a3130855 | ||
|
|
3af983a955 | ||
|
|
e0fb2f4dae | ||
|
|
74ba67f072 | ||
|
|
7d72f825ae | ||
|
|
148c046a02 | ||
|
|
bb24f13fba | ||
|
|
9d6301319b | ||
|
|
ea1aec04bd | ||
|
|
7f558f4f30 | ||
|
|
a6a1539663 | ||
|
|
6396bdfebf | ||
|
|
96ab3b0930 | ||
|
|
5c04d00d4c | ||
|
|
72e69cfca5 | ||
|
|
59a9e829fc |
9
.agents/rules/context_exclude_vtk.md
Normal file
9
.agents/rules/context_exclude_vtk.md
Normal file
@@ -0,0 +1,9 @@
|
||||
---
|
||||
trigger: manual
|
||||
---
|
||||
# Context Restriction: No VTK
|
||||
When this rule is active, restrict the operational context to libraries excluding the VTK layer.
|
||||
- **Exclude Path**: `src/Vtk`
|
||||
- **Include Paths**: `src/Core`, `src/Math`, `src/HEP`, `src/Root`, `src/Python`, `src/utils`
|
||||
- **Focus**: Tomographic reconstruction algorithms, data structures in `Core`, and physical modeling in `HEP`.
|
||||
- **Constraint**: Avoid referencing `Puppet`, `Viewport`, or any VTK-specific headers unless the user overrides this restriction.
|
||||
9
.agents/rules/context_gcompose.md
Normal file
9
.agents/rules/context_gcompose.md
Normal file
@@ -0,0 +1,9 @@
|
||||
---
|
||||
trigger: manual
|
||||
---
|
||||
# Context Focus: gcompose Application
|
||||
When this rule is active, prioritize the `gcompose` GUI application.
|
||||
- **Primary Path**: `app/gcompose`
|
||||
- **Focus**: `MainPanel`, `ContextPanel`, `PropertiesPanel`, and `ViewportPane`.
|
||||
- **Integration**: Wiring of Qt signals/slots between the `uLib` core model and the GUI widgets.
|
||||
- **Dependency**: Reference `src/Vtk` and `src/Core` as the underlying framework for the application.
|
||||
9
.agents/rules/context_vtk.md
Normal file
9
.agents/rules/context_vtk.md
Normal file
@@ -0,0 +1,9 @@
|
||||
---
|
||||
trigger: always_on
|
||||
---
|
||||
# Context Inclusion: VTK
|
||||
When this rule is active, include the VTK visualization layer in the operational context.
|
||||
- **Priority Path**: `src/Vtk`
|
||||
- **Focus**: `Puppet` hierarchy, `Viewport` management, and the synchronization between domain objects and VTK props.
|
||||
- **Key Classes**: `vtkViewport`, `vtkQViewport`, `vtkObjectsContext`, and all classes in `src/Vtk/HEP/Geant`.
|
||||
- **Logic**: Ensure transformations (TRS) applied to domain objects are correctly mirrored in the visualization layer and vice versa.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
trigger: always_on
|
||||
---
|
||||
|
||||
build in build directory using always micromamba "mutom" env.
|
||||
build with make flag -j$(nproc).
|
||||
|
||||
39
.agents/skills/micromamba_build.md
Normal file
39
.agents/skills/micromamba_build.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# Skill: Build uLib with Micromamba
|
||||
|
||||
This skill provides instructions for building the uLib project using the micromamba environment.
|
||||
|
||||
## Context
|
||||
- **Environment**: micromamba `uLib`
|
||||
- **Output Directory**: `build`
|
||||
- **CPU Usage**: All available cores
|
||||
|
||||
## Instructions
|
||||
|
||||
1. **Environment Setup**:
|
||||
Ensure micromamba is properly initialized and the `uLib` environment is active.
|
||||
```bash
|
||||
export MAMBA_EXE="/home/share/micromamba/bin/micromamba"
|
||||
export MAMBA_ROOT_PREFIX="/home/share/micromamba"
|
||||
eval "$(/home/share/micromamba/bin/micromamba shell hook --shell bash)"
|
||||
micromamba activate uLib
|
||||
```
|
||||
|
||||
2. **Full Rebuild (if needed)**:
|
||||
If the `build` directory does not exist or a full reconfiguration is required:
|
||||
```bash
|
||||
conan profile detect --force
|
||||
conan install . --output-folder=build --build=missing
|
||||
cmake --preset conan-release
|
||||
```
|
||||
|
||||
3. **Incremental Build**:
|
||||
Run the build command from the root directory, pointing to the `build` folder and using all cores.
|
||||
```bash
|
||||
cmake --build build -j$(nproc)
|
||||
```
|
||||
|
||||
4. **Specific Target Build**:
|
||||
To build a specific target (e.g., gcompose):
|
||||
```bash
|
||||
cmake --build build --target gcompose -j$(nproc)
|
||||
```
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -1,6 +1,7 @@
|
||||
CMakeFiles/
|
||||
build/
|
||||
build*/
|
||||
.cache/
|
||||
CMakeUserPresets.json
|
||||
build_warnings*.log
|
||||
final_build.log
|
||||
cmake_configure.log
|
||||
@@ -16,3 +17,6 @@ src/Python/uLib/.nfs*
|
||||
test_props.xml
|
||||
test_props2.xml
|
||||
test_boost.cpp
|
||||
.claude/settings.json
|
||||
build_output.log
|
||||
configure_output.log
|
||||
|
||||
3
.vscode/gdb_wrapper.sh
vendored
Executable file
3
.vscode/gdb_wrapper.sh
vendored
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
export DISPLAY=:1001.0
|
||||
/home/share/micromamba/bin/micromamba run -n uLib /usr/bin/gdb "$@"
|
||||
31
.vscode/launch.json
vendored
Normal file
31
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "gcompose",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/build/app/gcompose/gcompose",
|
||||
"args": [],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}",
|
||||
"environment": [
|
||||
{ "name": "DISPLAY", "value": ":1001.0" }
|
||||
],
|
||||
"externalConsole": false,
|
||||
"MIMode": "gdb",
|
||||
"setupCommands": [
|
||||
{
|
||||
"description": "Enable pretty-printing for gdb",
|
||||
"text": "-enable-pretty-printing",
|
||||
"ignoreFailures": true
|
||||
}
|
||||
],
|
||||
"preLaunchTask": "Build gcompose",
|
||||
"miDebuggerPath": "${workspaceFolder}/.vscode/gdb_wrapper.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
17
.vscode/tasks.json
vendored
Normal file
17
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "Build gcompose",
|
||||
"type": "shell",
|
||||
"command": "/home/share/micromamba/bin/micromamba run -n uLib cmake --build build --target gcompose -j$(nproc)",
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"problemMatcher": [
|
||||
"$gcc"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
16
CLAUDE.md
16
CLAUDE.md
@@ -10,9 +10,9 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
||||
export MAMBA_EXE="/home/share/micromamba/bin/micromamba"
|
||||
export MAMBA_ROOT_PREFIX="/home/share/micromamba"
|
||||
eval "$(/home/share/micromamba/bin/micromamba shell hook --shell bash)"
|
||||
micromamba activate mutom
|
||||
micromamba activate uLib
|
||||
|
||||
# Configure (from repo root, using Conan preset)
|
||||
# Configure (from repo root, using Conan preset — uses Ninja + ccache)
|
||||
cmake --preset conan-release
|
||||
|
||||
# Build everything
|
||||
@@ -40,6 +40,18 @@ conan install . --output-folder=build --build=missing
|
||||
cmake --preset conan-release
|
||||
```
|
||||
|
||||
### Build acceleration (already configured)
|
||||
- **Ninja** generator — used automatically via the conan default profile (`~/.conan2/profiles/default`)
|
||||
- **ccache** — enabled via `CMAKE_CXX_COMPILER_LAUNCHER=ccache`; cached rebuilds are nearly instant (~0.3s vs ~25s cold)
|
||||
- **Clang 22 + lld** profile available (`~/.conan2/profiles/fast`) but blocked by template overload ambiguities in `src/Core/Archives.h` that need fixing for full compatibility
|
||||
|
||||
To reconfigure with the fast profile once Archives.h is fixed:
|
||||
```bash
|
||||
conan install . --output-folder=build --build=missing --profile=fast
|
||||
cmake -B build -G Ninja -DCMAKE_TOOLCHAIN_FILE=build/conan_toolchain.cmake -DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build build -j$(nproc)
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
**uLib** is a C++ framework for Cosmic Muon Tomography (CMT), structured as layered shared libraries:
|
||||
|
||||
@@ -15,11 +15,20 @@ if(POLICY CMP0167)
|
||||
cmake_policy(SET CMP0167 NEW)
|
||||
endif()
|
||||
|
||||
|
||||
## -------------------------------------------------------------------------- ##
|
||||
|
||||
project(uLib)
|
||||
|
||||
# Applica la flag SOLO se il compilatore è GCC
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
add_compile_options(-fno-merge-constants)
|
||||
endif()
|
||||
|
||||
# Disabilita il warning se il compilatore è Clang (o AppleClang)
|
||||
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
add_compile_options(-Wno-ignored-optimization-argument)
|
||||
endif()
|
||||
|
||||
# CUDA Toolkit seems to be missing locally. Toggle ON if nvcc is made available.
|
||||
option(USE_CUDA "Enable CUDA support" OFF)
|
||||
if(USE_CUDA)
|
||||
@@ -124,6 +133,8 @@ find_package(Eigen3 CONFIG REQUIRED)
|
||||
get_target_property(EIGEN3_INCLUDE_DIRS Eigen3::Eigen INTERFACE_INCLUDE_DIRECTORIES)
|
||||
include_directories(${EIGEN3_INCLUDE_DIRS})
|
||||
|
||||
find_package(OpenMP)
|
||||
|
||||
find_package(ROOT CONFIG REQUIRED)
|
||||
include(${ROOT_USE_FILE})
|
||||
|
||||
@@ -145,6 +156,8 @@ else()
|
||||
IOXML
|
||||
IOXMLParser
|
||||
ImagingCore
|
||||
ImagingHybrid
|
||||
ImagingSources
|
||||
InteractionStyle
|
||||
InteractionWidgets
|
||||
RenderingAnnotation
|
||||
@@ -169,6 +182,26 @@ if(Geant4_FOUND)
|
||||
add_compile_definitions(HAVE_GEANT4)
|
||||
set(HAVE_GEANT4 1)
|
||||
|
||||
# Workaround: Geant4's G4EXPATShim creates EXPAT::EXPAT (uppercase) with
|
||||
# IMPORTED_LOCATION "${EXPAT_LIBRARY}", but EXPAT_LIBRARY is empty when using
|
||||
# conda's config-mode expat package (which installs as expat::expat lowercase).
|
||||
# Resolve the actual library path from expat::expat or via find_library.
|
||||
if(TARGET EXPAT::EXPAT)
|
||||
get_target_property(_expat_loc EXPAT::EXPAT IMPORTED_LOCATION)
|
||||
if(NOT _expat_loc OR _expat_loc MATCHES "NOTFOUND|^$")
|
||||
if(TARGET expat::expat)
|
||||
get_target_property(_expat_loc expat::expat IMPORTED_LOCATION_NOCONFIG)
|
||||
endif()
|
||||
if(NOT _expat_loc OR _expat_loc MATCHES "NOTFOUND|^$")
|
||||
find_library(_expat_loc NAMES expat)
|
||||
endif()
|
||||
if(_expat_loc)
|
||||
set_target_properties(EXPAT::EXPAT PROPERTIES IMPORTED_LOCATION "${_expat_loc}")
|
||||
endif()
|
||||
endif()
|
||||
unset(_expat_loc)
|
||||
endif()
|
||||
|
||||
# Sanitize Geant4 targets to remove Qt5 dependencies that conflict with VTK/Qt6
|
||||
if(TARGET Geant4::G4interfaces)
|
||||
set_target_properties(Geant4::G4interfaces PROPERTIES
|
||||
|
||||
@@ -12,6 +12,22 @@
|
||||
"CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "fast",
|
||||
"displayName": "Fast build: Ninja + clang + ccache",
|
||||
"description": "Uses Ninja generator, clang/lld compiler, and ccache",
|
||||
"generator": "Ninja",
|
||||
"binaryDir": "${sourceDir}/build",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Release",
|
||||
"CMAKE_C_COMPILER": "clang",
|
||||
"CMAKE_CXX_COMPILER": "clang++",
|
||||
"CMAKE_EXE_LINKER_FLAGS": "-fuse-ld=lld",
|
||||
"CMAKE_SHARED_LINKER_FLAGS": "-fuse-ld=lld",
|
||||
"CMAKE_CXX_COMPILER_LAUNCHER": "ccache",
|
||||
"CMAKE_C_COMPILER_LAUNCHER": "ccache"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "mutom",
|
||||
"description": "",
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"version": 4,
|
||||
"vendor": {
|
||||
"conan": {}
|
||||
},
|
||||
"include": [
|
||||
"build/CMakePresets.json"
|
||||
]
|
||||
}
|
||||
54
README.md
54
README.md
@@ -30,18 +30,22 @@ You can create and activate the environment using either `micromamba` or `conda`
|
||||
**Using Micromamba:**
|
||||
```bash
|
||||
micromamba env create -f condaenv.yml
|
||||
micromamba activate mutom
|
||||
micromamba activate uLib
|
||||
```
|
||||
|
||||
**Using Conda:**
|
||||
```bash
|
||||
conda env create -f condaenv.yml
|
||||
conda activate mutom
|
||||
conda activate uLib
|
||||
```
|
||||
|
||||
### Configure and Build
|
||||
|
||||
1. **Configure Conan profile (if you haven't yet on your machine):**
|
||||
#### Standard build (GCC + Ninja + ccache)
|
||||
|
||||
The default conan profile uses **Ninja** as the generator and **ccache** for compiler caching, dramatically speeding up incremental rebuilds.
|
||||
|
||||
1. **Configure Conan profile (first time only):**
|
||||
```bash
|
||||
conan profile detect
|
||||
```
|
||||
@@ -51,20 +55,54 @@ conan profile detect
|
||||
conan install . --output-folder=build --build=missing
|
||||
```
|
||||
|
||||
3. **Configure the project with CMake:**
|
||||
3. **Configure with CMake:**
|
||||
```bash
|
||||
cmake --preset conan-release
|
||||
```
|
||||
*(Alternatively: `cd build && cmake .. -DCMAKE_TOOLCHAIN_FILE=conan_toolchain.cmake -DCMAKE_BUILD_TYPE=Release`)*
|
||||
|
||||
4. **Build the project:**
|
||||
4. **Build:**
|
||||
```bash
|
||||
cmake --build build -j10
|
||||
cmake --build build -j$(nproc)
|
||||
```
|
||||
|
||||
5. **Clean build (wipe and rebuild everything):**
|
||||
```bash
|
||||
cmake --build build --clean-first -j$(nproc)
|
||||
```
|
||||
|
||||
6. **Run tests:**
|
||||
```bash
|
||||
cmake --build build --target test -j$(nproc)
|
||||
# or equivalently:
|
||||
ctest --test-dir build --output-on-failure -j$(nproc)
|
||||
```
|
||||
|
||||
#### LLVM/Clang build (clang + lld + ccache — fastest)
|
||||
|
||||
A `fast` conan profile is provided that uses **clang**, **lld** (LLVM linker), and **ccache**. Install them into your environment first:
|
||||
|
||||
```bash
|
||||
micromamba install -n uLib -y clang clangxx lld -c conda-forge
|
||||
```
|
||||
|
||||
Then build using the `fast` profile:
|
||||
|
||||
```bash
|
||||
conan install . --output-folder=build --build=missing --profile=fast
|
||||
cmake -B build -G Ninja \
|
||||
-DCMAKE_TOOLCHAIN_FILE=build/conan_toolchain.cmake \
|
||||
-DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build build -j$(nproc)
|
||||
```
|
||||
|
||||
The `fast` profile is defined at `~/.conan2/profiles/fast` and sets:
|
||||
- `CMAKE_C_COMPILER=clang` / `CMAKE_CXX_COMPILER=clang++`
|
||||
- `CMAKE_EXE_LINKER_FLAGS=-fuse-ld=lld`
|
||||
- `CMAKE_CXX_COMPILER_LAUNCHER=ccache`
|
||||
|
||||
### Make python package
|
||||
|
||||
```bash
|
||||
micromamba run -n mutom env USE_CUDA=ON poetry install
|
||||
micromamba run -n uLib env USE_CUDA=ON poetry install
|
||||
```
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[requires]
|
||||
eigen/3.4.0
|
||||
boost/1.83.0
|
||||
boost/1.86.0
|
||||
# pybind11/3.0.2
|
||||
hdf5/1.14.3
|
||||
|
||||
|
||||
15
condaenv.yml
15
condaenv.yml
@@ -1,4 +1,4 @@
|
||||
name: mutom
|
||||
name: uLib
|
||||
channels:
|
||||
- conda-forge
|
||||
dependencies:
|
||||
@@ -7,4 +7,15 @@ dependencies:
|
||||
- cmake
|
||||
- conan
|
||||
- root
|
||||
- vtk
|
||||
- vtk=9.4 # VTK 9.4
|
||||
- pybind11
|
||||
# - boost=1.86.0 # requested by VTK 9.4
|
||||
- ninja
|
||||
- clang
|
||||
- clangxx
|
||||
- lld
|
||||
- ccache
|
||||
- OpenMP
|
||||
- Geant4
|
||||
- gdb
|
||||
- valgrind
|
||||
|
||||
@@ -80,6 +80,9 @@ template <class ArchiveImplementation> class polymorphic_iarchive_route;
|
||||
namespace boost {
|
||||
namespace serialization {
|
||||
template <typename T> struct hrp;
|
||||
template <typename T> struct hrp_val;
|
||||
template <typename T> struct hrp_enum;
|
||||
template <typename T> struct hrp_enum_val;
|
||||
}
|
||||
} // namespace boost
|
||||
|
||||
@@ -177,6 +180,24 @@ public:
|
||||
return *this->This();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
Archive &operator>>(const boost::serialization::hrp_val<T> &t) {
|
||||
this->This()->load_override(const_cast<boost::serialization::hrp_val<T> &>(t));
|
||||
return *this->This();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
Archive &operator>>(const boost::serialization::hrp_enum<T> &t) {
|
||||
this->This()->load_override(const_cast<boost::serialization::hrp_enum<T> &>(t));
|
||||
return *this->This();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
Archive &operator>>(const boost::serialization::hrp_enum_val<T> &t) {
|
||||
this->This()->load_override(const_cast<boost::serialization::hrp_enum_val<T> &>(t));
|
||||
return *this->This();
|
||||
}
|
||||
|
||||
// the & operator
|
||||
template <class T> Archive &operator&(T &t) { return *(this->This()) >> t; }
|
||||
|
||||
@@ -190,6 +211,21 @@ public:
|
||||
return *(this->This()) >> t;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
Archive &operator&(const boost::serialization::hrp_val<T> &t) {
|
||||
return *(this->This()) >> t;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
Archive &operator&(const boost::serialization::hrp_enum<T> &t) {
|
||||
return *(this->This()) >> t;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
Archive &operator&(const boost::serialization::hrp_enum_val<T> &t) {
|
||||
return *(this->This()) >> t;
|
||||
}
|
||||
|
||||
// the == operator
|
||||
template <class T> Archive &operator==(T &t) { return this->operator&(t); }
|
||||
|
||||
@@ -230,11 +266,61 @@ public:
|
||||
return *this->This();
|
||||
}
|
||||
|
||||
template <class T> Archive &operator<<(const boost::serialization::hrp<T> &t) {
|
||||
this->This()->save_override(t);
|
||||
return *this->This();
|
||||
}
|
||||
|
||||
template <class T> Archive &operator<<(const boost::serialization::hrp_val<T> &t) {
|
||||
this->This()->save_override(t);
|
||||
return *this->This();
|
||||
}
|
||||
|
||||
template <class T> Archive &operator<<(const boost::serialization::hrp_enum<T> &t) {
|
||||
this->This()->save_override(t);
|
||||
return *this->This();
|
||||
}
|
||||
|
||||
template <class T> Archive &operator<<(const boost::serialization::hrp_enum_val<T> &t) {
|
||||
this->This()->save_override(t);
|
||||
return *this->This();
|
||||
}
|
||||
|
||||
template <class T> Archive &operator<<(const boost::serialization::nvp<T> &t) {
|
||||
this->This()->save_override(t);
|
||||
return *this->This();
|
||||
}
|
||||
|
||||
// the & operator
|
||||
template <class T> Archive &operator&(const T &t) {
|
||||
return *this->This() << t;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
Archive &operator&(const boost::serialization::hrp<T> &t) {
|
||||
return *this->This() << t;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
Archive &operator&(const boost::serialization::hrp_val<T> &t) {
|
||||
return *this->This() << t;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
Archive &operator&(const boost::serialization::hrp_enum<T> &t) {
|
||||
return *this->This() << t;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
Archive &operator&(const boost::serialization::hrp_enum_val<T> &t) {
|
||||
return *this->This() << t;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
Archive &operator&(const boost::serialization::nvp<T> &t) {
|
||||
return *this->This() << t;
|
||||
}
|
||||
|
||||
// the == operator
|
||||
template <class T> Archive &operator==(T &t) { return this->operator&(t); }
|
||||
|
||||
@@ -338,8 +424,6 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
using basic_xml_iarchive::load_override;
|
||||
|
||||
// Anything not an attribute should be a name value pair as nvp or hrp
|
||||
typedef boost::archive::detail::common_iarchive<Archive>
|
||||
detail_common_iarchive;
|
||||
@@ -357,6 +441,9 @@ public:
|
||||
// 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 ..
|
||||
// Note: using base::load_override covers all basic_xml_iarchive overloads
|
||||
// transitively, so a separate 'using basic_xml_iarchive::load_override'
|
||||
// is redundant and creates ambiguity with clang.
|
||||
using base::load_override;
|
||||
|
||||
void load_override(const char *str) {
|
||||
|
||||
@@ -21,6 +21,7 @@ set(HEADERS
|
||||
StringReader.h
|
||||
Threads.h
|
||||
Monitor.h
|
||||
Property.h
|
||||
Types.h
|
||||
Uuid.h
|
||||
Vector.h
|
||||
|
||||
@@ -65,10 +65,19 @@ public:
|
||||
std::vector<Slot> slov;
|
||||
std::vector<PropertyBase*> m_Properties;
|
||||
std::vector<PropertyBase*> m_DynamicProperties;
|
||||
std::vector<PropertyBase*> m_DisplayProperties;
|
||||
bool m_SignalsBlocked;
|
||||
};
|
||||
|
||||
// Implementations of Property methods
|
||||
void Object::RegisterDisplayProperty(PropertyBase* prop) {
|
||||
if (prop) d->m_DisplayProperties.push_back(prop);
|
||||
}
|
||||
|
||||
const std::vector<PropertyBase*>& Object::GetDisplayProperties() const {
|
||||
return d->m_DisplayProperties;
|
||||
}
|
||||
|
||||
void Object::RegisterProperty(PropertyBase* prop) {
|
||||
if (prop) {
|
||||
d->m_Properties.push_back(prop);
|
||||
@@ -104,32 +113,9 @@ void Object::NotifyPropertiesUpdated() {
|
||||
for (auto* p : d->m_DynamicProperties) p->Updated();
|
||||
}
|
||||
|
||||
// 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); }
|
||||
void Object::PropertyUpdated() { ULIB_SIGNAL_EMIT(Object::PropertyUpdated); }
|
||||
|
||||
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);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -92,9 +92,11 @@ public:
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// PROPERTIES //
|
||||
void RegisterProperty(PropertyBase* prop);
|
||||
void RegisterDynamicProperty(PropertyBase* prop);
|
||||
const std::vector<PropertyBase*>& GetProperties() const;
|
||||
virtual void RegisterProperty(PropertyBase* property);
|
||||
virtual void RegisterDynamicProperty(PropertyBase* property);
|
||||
virtual void RegisterDisplayProperty(PropertyBase* property);
|
||||
virtual const std::vector<PropertyBase*>& GetProperties() const;
|
||||
virtual const std::vector<PropertyBase*>& GetDisplayProperties() const;
|
||||
PropertyBase* GetProperty(const std::string& name) const;
|
||||
|
||||
/** @brief Sends an Updated signal for all properties of this object. useful for real-time UI refresh. */
|
||||
@@ -124,7 +126,7 @@ public:
|
||||
virtual void serialize(Archive::log_archive & ar, const unsigned int version) {}
|
||||
|
||||
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 LoadConfig(std::istream &is, int version = 0);
|
||||
|
||||
@@ -5,19 +5,23 @@
|
||||
#include <vector>
|
||||
#include <sstream>
|
||||
#include <typeinfo>
|
||||
#include <typeindex> // Added
|
||||
#include <typeindex>
|
||||
#include <boost/serialization/nvp.hpp>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <vector>
|
||||
#include <boost/type_traits/is_class.hpp>
|
||||
#include <boost/mpl/bool.hpp>
|
||||
#include <boost/serialization/serialization.hpp>
|
||||
#include <set>
|
||||
#include <boost/type_traits/is_base_of.hpp>
|
||||
#include "Core/Archives.h"
|
||||
#include "Core/Signal.h"
|
||||
#include "Core/Object.h"
|
||||
|
||||
namespace uLib {
|
||||
|
||||
namespace Archive {
|
||||
class property_register_archive;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Base class for properties to allow runtime listing and identification.
|
||||
*/
|
||||
@@ -27,7 +31,7 @@ public:
|
||||
virtual const std::string& GetName() const = 0;
|
||||
virtual const char* GetTypeName() const = 0;
|
||||
virtual std::string GetValueAsString() const = 0;
|
||||
virtual std::type_index GetTypeIndex() const = 0; // Added
|
||||
virtual std::type_index GetTypeIndex() const = 0;
|
||||
virtual const std::string& GetUnits() const = 0;
|
||||
virtual void SetUnits(const std::string& units) = 0;
|
||||
virtual const std::vector<std::string>& GetEnumLabels() const {
|
||||
@@ -61,42 +65,29 @@ public:
|
||||
virtual void serialize(Archive::hrt_oarchive & ar, const unsigned int version) override = 0;
|
||||
virtual void serialize(Archive::hrt_iarchive & ar, const unsigned int version) override = 0;
|
||||
virtual void serialize(Archive::log_archive & ar, const unsigned int version) override = 0;
|
||||
|
||||
virtual void serialize(Archive::property_register_archive & ar, const unsigned int v) = 0;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief Template class for typed properties.
|
||||
*/
|
||||
template <typename T>
|
||||
class Property : public PropertyBase {
|
||||
public:
|
||||
// PROXY: Use an existing variable as back-end storage
|
||||
Property(Object* owner, const std::string& name, T* valuePtr, const std::string& units = "", const std::string& group = "")
|
||||
: m_owner(owner), m_name(name), m_units(units), m_group(group), m_value(valuePtr), m_own(false),
|
||||
m_HasRange(false), m_HasDefault(false), m_ReadOnly(false) {
|
||||
if (m_owner) {
|
||||
m_owner->RegisterProperty(this);
|
||||
}
|
||||
if (m_owner) m_owner->RegisterProperty(this);
|
||||
}
|
||||
|
||||
// MANAGED: Create and own internal storage
|
||||
Property(Object* owner, const std::string& name, const T& defaultValue = T(), const std::string& units = "", const std::string& group = "")
|
||||
: m_owner(owner), m_name(name), m_units(units), m_group(group), m_value(new T(defaultValue)), m_own(true),
|
||||
m_HasRange(false), m_HasDefault(true), m_Default(defaultValue), m_ReadOnly(false) {
|
||||
if (m_owner) {
|
||||
m_owner->RegisterProperty(this);
|
||||
}
|
||||
if (m_owner) m_owner->RegisterProperty(this);
|
||||
}
|
||||
|
||||
virtual ~Property() {
|
||||
if (m_own) delete m_value;
|
||||
}
|
||||
virtual ~Property() { if (m_own) delete m_value; }
|
||||
|
||||
// Identification
|
||||
virtual const std::string& GetName() const override { return m_name; }
|
||||
@@ -107,36 +98,18 @@ public:
|
||||
virtual const std::string& GetGroup() const override { return m_group; }
|
||||
virtual void SetGroup(const std::string& group) override { m_group = group; }
|
||||
|
||||
|
||||
std::string GetValueAsString() const override {
|
||||
try {
|
||||
return boost::lexical_cast<std::string>(*m_value);
|
||||
} catch (const boost::bad_lexical_cast&) {
|
||||
std::stringstream ss;
|
||||
ss << *m_value;
|
||||
return ss.str();
|
||||
}
|
||||
try { return boost::lexical_cast<std::string>(*m_value); }
|
||||
catch (...) { std::stringstream ss; ss << *m_value; return ss.str(); }
|
||||
}
|
||||
|
||||
// Accessors
|
||||
const T& Get() const { return *m_value; }
|
||||
template<typename U = T>
|
||||
typename std::enable_if<std::is_arithmetic<U>::value, void>::type
|
||||
ValidateT(T& val) {
|
||||
if (m_HasRange) {
|
||||
if (val < m_Min) val = m_Min;
|
||||
if (val > m_Max) val = m_Max;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename U = T>
|
||||
typename std::enable_if<!std::is_arithmetic<U>::value, void>::type
|
||||
ValidateT(T& val) {
|
||||
}
|
||||
|
||||
void Set(const T& value) {
|
||||
T val = value;
|
||||
ValidateT<T>(val);
|
||||
if constexpr (std::is_arithmetic<T>::value) {
|
||||
if (m_HasRange) { if (val < m_Min) val = m_Min; if (val > m_Max) val = m_Max; }
|
||||
}
|
||||
if (*m_value != val) {
|
||||
*m_value = val;
|
||||
ULIB_SIGNAL_EMIT(Property<T>::PropertyChanged);
|
||||
@@ -150,65 +123,55 @@ public:
|
||||
void SetReadOnly(bool ro) { m_ReadOnly = ro; }
|
||||
|
||||
virtual bool IsReadOnly() const override { return m_ReadOnly; }
|
||||
|
||||
virtual bool HasRange() const override { return m_HasRange; }
|
||||
virtual double GetMin() const override { return m_HasRange ? convert_to_double(m_Min) : 0.0; }
|
||||
virtual double GetMax() const override { return m_HasRange ? convert_to_double(m_Max) : 0.0; }
|
||||
|
||||
template<typename U = T>
|
||||
typename std::enable_if<std::is_arithmetic<U>::value, double>::type
|
||||
GetMinT() const { return (double)m_Min; }
|
||||
|
||||
template<typename U = T>
|
||||
typename std::enable_if<!std::is_arithmetic<U>::value, double>::type
|
||||
GetMinT() const { return 0.0; }
|
||||
|
||||
template<typename U = T>
|
||||
typename std::enable_if<std::is_arithmetic<U>::value, double>::type
|
||||
GetMaxT() const { return (double)m_Max; }
|
||||
|
||||
template<typename U = T>
|
||||
typename std::enable_if<!std::is_arithmetic<U>::value, double>::type
|
||||
GetMaxT() const { return 0.0; }
|
||||
|
||||
virtual double GetMin() const override { return GetMinT<T>(); }
|
||||
virtual double GetMax() const override { return GetMaxT<T>(); }
|
||||
const T& GetMinTyped() const { return m_Min; }
|
||||
const T& GetMaxTyped() const { return m_Max; }
|
||||
|
||||
virtual bool HasDefault() const override { return m_HasDefault; }
|
||||
virtual std::string GetDefaultValueAsString() const override {
|
||||
try { return boost::lexical_cast<std::string>(m_Default); }
|
||||
catch (...) { return ""; }
|
||||
try { return boost::lexical_cast<std::string>(m_Default); } catch (...) { return ""; }
|
||||
}
|
||||
|
||||
// Operators for seamless usage
|
||||
// Operators
|
||||
operator const T&() const { return *m_value; }
|
||||
Property& operator=(const T& value) {
|
||||
Set(value);
|
||||
return *this;
|
||||
}
|
||||
Property& operator=(const T& value) { Set(value); return *this; }
|
||||
|
||||
// Signals
|
||||
signals:
|
||||
virtual void PropertyChanged() { ULIB_SIGNAL_EMIT(Property<T>::PropertyChanged); }
|
||||
|
||||
private:
|
||||
template <typename U>
|
||||
static double convert_to_double(const U& val) {
|
||||
return convert_to_double_impl(val, typename std::is_arithmetic<U>::type());
|
||||
}
|
||||
template <typename U>
|
||||
static double convert_to_double_impl(const U& val, std::true_type) { return (double)val; }
|
||||
template <typename U>
|
||||
static double convert_to_double_impl(const U& val, std::false_type) { return 0.0; }
|
||||
|
||||
public:
|
||||
// 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_helper(ArchiveT & ar, const unsigned int version) {
|
||||
ar & boost::serialization::make_hrp(m_name.c_str(), *m_value, m_units.c_str());
|
||||
}
|
||||
|
||||
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); }
|
||||
virtual void serialize(Archive::xml_oarchive & ar, const unsigned int v) override { serialize_helper(ar, v); }
|
||||
virtual void serialize(Archive::xml_iarchive & ar, const unsigned int v) override { serialize_helper(ar, v); }
|
||||
virtual void serialize(Archive::text_oarchive & ar, const unsigned int v) override { serialize_helper(ar, v); }
|
||||
virtual void serialize(Archive::text_iarchive & ar, const unsigned int v) override { serialize_helper(ar, v); }
|
||||
virtual void serialize(Archive::hrt_oarchive & ar, const unsigned int v) override { serialize_helper(ar, v); }
|
||||
virtual void serialize(Archive::hrt_iarchive & ar, const unsigned int v) override { serialize_helper(ar, v); }
|
||||
virtual void serialize(Archive::log_archive & ar, const unsigned int v) override { serialize_helper(ar, v); }
|
||||
|
||||
virtual void Updated() override {
|
||||
PropertyBase::Updated();
|
||||
this->PropertyChanged();
|
||||
}
|
||||
virtual void serialize(Archive::property_register_archive & ar, const unsigned int v) override;
|
||||
|
||||
private:
|
||||
virtual void Updated() override { PropertyBase::Updated(); this->PropertyChanged(); }
|
||||
|
||||
protected:
|
||||
std::string m_name;
|
||||
std::string m_units;
|
||||
std::string m_group;
|
||||
@@ -224,20 +187,7 @@ private:
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Conveninent typedefs for common property types.
|
||||
*/
|
||||
typedef Property<std::string> StringProperty;
|
||||
typedef Property<int> IntProperty;
|
||||
typedef Property<unsigned int> UIntProperty;
|
||||
typedef Property<long> LongProperty;
|
||||
typedef Property<unsigned long> ULongProperty;
|
||||
typedef Property<float> FloatProperty;
|
||||
typedef Property<double> DoubleProperty;
|
||||
typedef Property<Bool_t> BoolProperty;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Property specialized for enumerations, providing labels for GUI representations.
|
||||
* @brief Property specialized for enumerations.
|
||||
*/
|
||||
class EnumProperty : public Property<int> {
|
||||
public:
|
||||
@@ -248,68 +198,49 @@ public:
|
||||
const char* GetTypeName() const override { return "Enum"; }
|
||||
virtual std::type_index GetTypeIndex() const override { return std::type_index(typeid(EnumProperty)); }
|
||||
|
||||
template <class ArchiveT>
|
||||
void serialize_enum_helper(ArchiveT & ar, const unsigned int version) {
|
||||
ar & boost::serialization::make_hrp_enum(m_name.c_str(), *m_value, m_Labels, m_units.c_str());
|
||||
}
|
||||
|
||||
virtual void serialize(Archive::xml_oarchive & ar, const unsigned int v) override { serialize_enum_helper(ar, v); }
|
||||
virtual void serialize(Archive::xml_iarchive & ar, const unsigned int v) override { serialize_enum_helper(ar, v); }
|
||||
virtual void serialize(Archive::text_oarchive & ar, const unsigned int v) override { serialize_enum_helper(ar, v); }
|
||||
virtual void serialize(Archive::text_iarchive & ar, const unsigned int v) override { serialize_enum_helper(ar, v); }
|
||||
virtual void serialize(Archive::hrt_oarchive & ar, const unsigned int v) override { serialize_enum_helper(ar, v); }
|
||||
virtual void serialize(Archive::hrt_iarchive & ar, const unsigned int v) override { serialize_enum_helper(ar, v); }
|
||||
virtual void serialize(Archive::log_archive & ar, const unsigned int v) override { serialize_enum_helper(ar, v); }
|
||||
|
||||
virtual void serialize(Archive::property_register_archive & ar, const unsigned int v) override;
|
||||
|
||||
private:
|
||||
std::vector<std::string> m_Labels;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief Macro to simplify property declaration within a class.
|
||||
* Usage: ULIB_PROPERTY(float, Width, 1.0f)
|
||||
* It creates a raw member m_Width and a Property proxy Width.
|
||||
*/
|
||||
#define ULIB_PROPERTY(type, name, defaultValue) \
|
||||
type m_##name = defaultValue; \
|
||||
Property<type> name = Property<type>(this, #name, &m_##name);
|
||||
|
||||
|
||||
} // namespace uLib
|
||||
|
||||
namespace uLib {
|
||||
namespace Archive {
|
||||
class property_register_archive;
|
||||
} // namespace Archive
|
||||
} // namespace uLib
|
||||
|
||||
namespace boost {
|
||||
namespace archive {
|
||||
namespace detail {
|
||||
template <>
|
||||
class interface_oarchive<uLib::Archive::property_register_archive>
|
||||
: public uLib_interface_oarchive<uLib::Archive::property_register_archive> {};
|
||||
} // namespace detail
|
||||
} // namespace archive
|
||||
} // namespace boost
|
||||
|
||||
namespace uLib {
|
||||
namespace Archive {
|
||||
|
||||
/**
|
||||
* @brief A special archive that creates and registers Property proxies
|
||||
* for any member it encounters wrapped in HRP().
|
||||
*/
|
||||
class property_register_archive :
|
||||
public boost::archive::detail::common_oarchive<property_register_archive>
|
||||
{
|
||||
class property_register_archive
|
||||
: public boost::archive::detail::common_oarchive<property_register_archive> {
|
||||
protected:
|
||||
Object* m_Object;
|
||||
bool m_DisplayOnly;
|
||||
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;
|
||||
using boost::archive::detail::common_oarchive<property_register_archive>::save_override;
|
||||
|
||||
property_register_archive(Object* obj) :
|
||||
property_register_archive(Object* obj, bool displayOnly = false) :
|
||||
boost::archive::detail::common_oarchive<property_register_archive>(boost::archive::no_header),
|
||||
m_Object(obj) {}
|
||||
m_Object(obj), m_DisplayOnly(displayOnly) {
|
||||
if (obj) m_Visited.insert(dynamic_cast<const void*>(obj));
|
||||
}
|
||||
|
||||
template<class T> property_register_archive &operator&(const T &t) { this->save_override(t); return *this; }
|
||||
template<class T> property_register_archive &operator<<(const T &t) { this->save_override(t); return *this; }
|
||||
|
||||
std::string GetCurrentGroup() const {
|
||||
std::string group;
|
||||
@@ -320,77 +251,91 @@ public:
|
||||
return group;
|
||||
}
|
||||
|
||||
// Core logic: encounter HRP -> Create Dynamic Property
|
||||
template<class T>
|
||||
void save_override(const boost::serialization::hrp<T> &t) {
|
||||
if (m_Object) {
|
||||
Property<T>* p = new Property<T>(m_Object, t.name(), &const_cast<boost::serialization::hrp<T>&>(t).value(), t.units() ? t.units() : "", GetCurrentGroup());
|
||||
if (t.has_range()) p->SetRange(t.min_val(), t.max_val());
|
||||
if (t.has_default()) p->SetDefault(t.default_val());
|
||||
p->SetReadOnly(t.is_read_only());
|
||||
m_Object->RegisterDynamicProperty(p);
|
||||
template<class T> void register_property(Property<T>& p) {
|
||||
save_property_impl(p.GetName().c_str(), const_cast<T&>(p.Get()), p.GetUnits().c_str(),
|
||||
p.HasRange(), p.GetMinTyped(), p.GetMaxTyped(), p.IsReadOnly());
|
||||
}
|
||||
|
||||
void register_enum_property(EnumProperty& p) {
|
||||
if (!m_Object) return;
|
||||
EnumProperty* newP = new EnumProperty(m_Object, p.GetName(), const_cast<int*>(&p.Get()), p.GetEnumLabels(), p.GetUnits(), GetCurrentGroup());
|
||||
newP->SetReadOnly(p.IsReadOnly());
|
||||
if (m_DisplayOnly) {
|
||||
m_Object->RegisterDisplayProperty(newP);
|
||||
Object* obj = m_Object;
|
||||
Object::connect(newP, &PropertyBase::Updated, [obj]() { obj->Updated(); });
|
||||
} else {
|
||||
m_Object->RegisterDynamicProperty(newP);
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void save_override(const boost::serialization::hrp_val<T> &t) {
|
||||
template<class T> void save_property_impl(const char* name, T& val, const char* units, bool hasRange, const T& minVal, const T& maxVal, bool isReadOnly) {
|
||||
if (m_Object) {
|
||||
// Note: hrp_val stores by value. Property usually points to existing data.
|
||||
// But here we are registering properties from HRP wrappers.
|
||||
// If it's hrp_val, it means it's an rvalue from a getter.
|
||||
// The hrp_val wrapper itself owns the value.
|
||||
// However, the property_register_archive is temporary.
|
||||
// This is a bit tricky. Usually HRP(rvalue) is meant for read-only display.
|
||||
// Let's use the address of the value in the wrapper, but mark it read-only.
|
||||
Property<T>* p = new Property<T>(m_Object, t.name(), &const_cast<boost::serialization::hrp_val<T>&>(t).value(), t.units() ? t.units() : "", GetCurrentGroup());
|
||||
if (t.has_range()) p->SetRange(t.min_val(), t.max_val());
|
||||
if (t.has_default()) p->SetDefault(t.default_val());
|
||||
p->SetReadOnly(t.is_read_only());
|
||||
Property<T>* p = new Property<T>(m_Object, name, &val, units ? units : "", GetCurrentGroup());
|
||||
set_range_helper(p, hasRange, minVal, maxVal, typename std::is_arithmetic<T>::type());
|
||||
p->SetReadOnly(isReadOnly);
|
||||
if (m_DisplayOnly) {
|
||||
m_Object->RegisterDisplayProperty(p);
|
||||
Object* obj = m_Object;
|
||||
Object::connect(p, &PropertyBase::Updated, [obj]() { obj->Updated(); });
|
||||
} else {
|
||||
m_Object->RegisterDynamicProperty(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void save_override(const boost::serialization::hrp_enum<T> &t) {
|
||||
template<class U> static void set_range_helper(Property<U>* p, bool hasRange, const U& minVal, const U& maxVal, std::true_type) { if (hasRange) p->SetRange(minVal, maxVal); }
|
||||
template<class U> static void set_range_helper(Property<U>* p, bool hasRange, const U& minVal, const U& maxVal, std::false_type) {}
|
||||
|
||||
template<class T> void save_override(const boost::serialization::hrp<T> &t) {
|
||||
// To handle T correctly without deduction issues, we assume T can be passed to save_property_impl
|
||||
T dummy = T(); // Ensure we can construct T
|
||||
save_property_impl(t.name(), const_cast<boost::serialization::hrp<T>&>(t).value(), t.units(), t.has_range(), t.has_range() ? t.min_val() : dummy, t.has_range() ? t.max_val() : dummy, t.is_read_only());
|
||||
}
|
||||
template<class T> void save_override(const boost::serialization::hrp_val<T> &t) {
|
||||
T dummy = T();
|
||||
save_property_impl(t.name(), const_cast<boost::serialization::hrp_val<T>&>(t).value(), t.units(), t.has_range(), t.has_range() ? t.min_val() : dummy, t.has_range() ? t.max_val() : dummy, t.is_read_only());
|
||||
}
|
||||
template<class T> void save_override(const boost::serialization::hrp_enum<T> &t) {
|
||||
if (m_Object) {
|
||||
EnumProperty* p = new EnumProperty(m_Object, t.name(), (int*)&const_cast<boost::serialization::hrp_enum<T>&>(t).value(), t.labels(), t.units() ? t.units() : "", GetCurrentGroup());
|
||||
p->SetReadOnly(t.is_read_only());
|
||||
m_Object->RegisterDynamicProperty(p);
|
||||
if (m_DisplayOnly) { m_Object->RegisterDisplayProperty(p); Object* obj = m_Object; Object::connect(p, &PropertyBase::Updated, [obj]() { obj->Updated(); }); }
|
||||
else { m_Object->RegisterDynamicProperty(p); }
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void save_override(const boost::serialization::hrp_enum_val<T> &t) {
|
||||
template<class T> void save_override(const boost::serialization::hrp_enum_val<T> &t) {
|
||||
if (m_Object) {
|
||||
EnumProperty* p = new EnumProperty(m_Object, t.name(), (int*)&const_cast<boost::serialization::hrp_enum_val<T>&>(t).value(), t.labels(), t.units() ? t.units() : "", GetCurrentGroup());
|
||||
p->SetReadOnly(t.is_read_only());
|
||||
m_Object->RegisterDynamicProperty(p);
|
||||
if (m_DisplayOnly) { m_Object->RegisterDisplayProperty(p); Object* obj = m_Object; Object::connect(p, &PropertyBase::Updated, [obj]() { obj->Updated(); }); }
|
||||
else { m_Object->RegisterDynamicProperty(p); }
|
||||
}
|
||||
}
|
||||
|
||||
// Handle standard NVPs by recursing (important for base classes)
|
||||
template<class T>
|
||||
void save_override(const boost::serialization::nvp<T> &t) {
|
||||
template<class T> void save_override(const boost::serialization::nvp<T> &t) {
|
||||
if (t.name()) m_GroupStack.push_back(t.name());
|
||||
this->save_helper(t.const_value(), typename boost::is_class<T>::type());
|
||||
if (t.name()) m_GroupStack.pop_back();
|
||||
}
|
||||
|
||||
// Recursion for nested classes, ignore primitives
|
||||
template<class T>
|
||||
void save_override(const T &t) {
|
||||
this->save_helper(t, typename boost::is_class<T>::type());
|
||||
void save_override(const std::string &t) {}
|
||||
template<class T> void save_override(T * const & t) {
|
||||
if (!t) return;
|
||||
this->save_pointer_helper(t, typename boost::is_base_of<Object, T>::type());
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void save_helper(const T &t, boost::mpl::true_) {
|
||||
boost::serialization::serialize_adl(*this, const_cast<T&>(t), 0);
|
||||
template<class T> void save_pointer_helper(T* t, boost::mpl::true_) {
|
||||
const void* ptr = dynamic_cast<const void*>(t);
|
||||
if (m_Visited.find(ptr) != m_Visited.end()) return;
|
||||
m_Visited.insert(ptr);
|
||||
this->save_override(*t);
|
||||
}
|
||||
template<class T> void save_pointer_helper(T* t, boost::mpl::false_) {}
|
||||
template<class T> void save_override(const T &t) { this->save_helper(t, typename boost::is_class<T>::type()); }
|
||||
template<class T> void save_helper(const T &t, boost::mpl::true_) { boost::serialization::serialize_adl(*this, const_cast<T&>(t), 0); }
|
||||
void save_helper(const std::string &t, boost::mpl::true_) {}
|
||||
template<class T> void save_helper(const T &t, boost::mpl::false_) {}
|
||||
|
||||
template<class T>
|
||||
void save_helper(const T &t, boost::mpl::false_) {}
|
||||
|
||||
// Required attribute overrides for common_oarchive
|
||||
void save_override(const boost::archive::object_id_type & t) {}
|
||||
void save_override(const boost::archive::object_reference_type & t) {}
|
||||
void save_override(const boost::archive::version_type & t) {}
|
||||
@@ -400,30 +345,30 @@ public:
|
||||
void save_override(const boost::archive::class_name_type & t) {}
|
||||
void save_override(const boost::archive::tracking_type & t) {}
|
||||
|
||||
private:
|
||||
protected:
|
||||
std::vector<std::string> m_GroupStack;
|
||||
std::set<const void*> m_Visited;
|
||||
};
|
||||
|
||||
} // namespace Archive
|
||||
} // namespace uLib
|
||||
|
||||
namespace uLib {
|
||||
|
||||
template <typename T>
|
||||
inline void Property<T>::serialize(Archive::property_register_archive & ar, const unsigned int v) {
|
||||
ar.register_property(*this);
|
||||
}
|
||||
|
||||
inline void EnumProperty::serialize(Archive::property_register_archive & ar, const unsigned int v) {
|
||||
ar.register_enum_property(*this);
|
||||
}
|
||||
|
||||
namespace Archive {
|
||||
|
||||
/**
|
||||
* @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)); _ar_tmp & (obj); }
|
||||
|
||||
/**
|
||||
* @brief Declares a private member that automatically calls ULIB_ACTIVATE_PROPERTIES
|
||||
* in every constructor of the class. Place this macro as the last declaration
|
||||
* inside the class body (before the closing brace).
|
||||
*
|
||||
* Usage: ULIB_DECLARE_PROPERTIES(ClassName)
|
||||
*
|
||||
* This replaces per-constructor ULIB_ACTIVATE_PROPERTIES(*this) calls.
|
||||
* RegisterDynamicProperty deduplicates by qualified name, so re-registration
|
||||
* from inherited activators in a hierarchy is safe.
|
||||
*/
|
||||
#define ULIB_DECLARE_PROPERTIES(SelfType) \
|
||||
private: \
|
||||
struct _PropActivator { \
|
||||
@@ -434,6 +379,31 @@ private: \
|
||||
} _prop_activator{this};
|
||||
|
||||
} // namespace Archive
|
||||
|
||||
// Convenience macro: declares a named Property<T> member with a default value.
|
||||
// Usage inside a class body (requires 'this' to be available, so use in-class initializer):
|
||||
// ULIB_PROPERTY(int, MyProp, 42)
|
||||
#define ULIB_PROPERTY(type, name, defaultVal) \
|
||||
::uLib::Property<type> name{this, #name, (type)(defaultVal)};
|
||||
|
||||
// Common property type aliases
|
||||
typedef Property<bool> BoolProperty;
|
||||
typedef Property<int> IntProperty;
|
||||
typedef Property<float> FloatProperty;
|
||||
typedef Property<double> DoubleProperty;
|
||||
typedef Property<std::string> StringProperty;
|
||||
|
||||
template <class ArchiveT>
|
||||
void serialize_properties_helper(ArchiveT &ar, const std::vector<PropertyBase*> &props, unsigned int version) {
|
||||
for (auto* prop : props) prop->serialize(ar, version);
|
||||
}
|
||||
|
||||
template <class ArchiveT>
|
||||
void Object::serialize(ArchiveT &ar, const unsigned int version) {
|
||||
ar & boost::serialization::make_nvp("InstanceName", this->GetInstanceName());
|
||||
serialize_properties_helper(ar, this->GetProperties(), version);
|
||||
}
|
||||
|
||||
} // namespace uLib
|
||||
|
||||
#endif // U_CORE_PROPERTY_H
|
||||
|
||||
@@ -182,7 +182,7 @@ typedef bool Bool_t; // Boolean (0=false, 1=true) (bool)
|
||||
\
|
||||
public: \
|
||||
typedef type_info::BaseClass BaseClass; \
|
||||
virtual const char *type_name() const { return type_info::name; } \
|
||||
virtual const char *type_name() const override { return type_info::name; } \
|
||||
/**/
|
||||
|
||||
/**
|
||||
|
||||
@@ -38,7 +38,7 @@ inline const unsigned long VectorSplice(const _Tp &_it, const _Tp &_end,
|
||||
|
||||
_Tp it = _it;
|
||||
_Tp end = _end - 1;
|
||||
for (it; it != end;) {
|
||||
for (; it != end;) {
|
||||
if (_comp(*it, value))
|
||||
it++;
|
||||
else if (_comp(*end, value)) {
|
||||
|
||||
@@ -32,7 +32,7 @@ class EmitterPrimary : public G4VUserPrimaryGeneratorAction, public AffineTransf
|
||||
virtual ~EmitterPrimary();
|
||||
|
||||
// Metodo principale chiamato all'inizio di ogni evento
|
||||
virtual void GeneratePrimaries(G4Event*);
|
||||
virtual void GeneratePrimaries(G4Event*) override;
|
||||
|
||||
virtual void Updated() override { ULIB_SIGNAL_EMIT(EmitterPrimary::Updated); }
|
||||
|
||||
@@ -51,7 +51,7 @@ class SkyPlaneEmitterPrimary : public EmitterPrimary
|
||||
SkyPlaneEmitterPrimary();
|
||||
virtual ~SkyPlaneEmitterPrimary();
|
||||
|
||||
virtual void GeneratePrimaries(G4Event*);
|
||||
virtual void GeneratePrimaries(G4Event*) override;
|
||||
|
||||
void SetPlane(const uLib::Vector3f& p0, const uLib::Vector3f& normal);
|
||||
void SetSkySize(const uLib::Vector2f& size);
|
||||
@@ -72,7 +72,7 @@ class CylinderEmitterPrimary : public EmitterPrimary
|
||||
CylinderEmitterPrimary();
|
||||
virtual ~CylinderEmitterPrimary();
|
||||
|
||||
virtual void GeneratePrimaries(G4Event*);
|
||||
virtual void GeneratePrimaries(G4Event*) override;
|
||||
|
||||
void SetRadius(float r);
|
||||
float GetRadius() const { return m_Radius; }
|
||||
@@ -101,7 +101,7 @@ class QuadMeshEmitterPrimary : public EmitterPrimary
|
||||
virtual ~QuadMeshEmitterPrimary();
|
||||
|
||||
// Metodo principale chiamato all'inizio di ogni evento
|
||||
virtual void GeneratePrimaries(G4Event*);
|
||||
virtual void GeneratePrimaries(G4Event*) override;
|
||||
|
||||
void SetMesh(uLib::QuadMesh* mesh);
|
||||
|
||||
|
||||
@@ -105,6 +105,8 @@ void Solid::SetTransform(Matrix4f transform) {
|
||||
m_Physical->SetTranslation(*m_Position);
|
||||
m_Physical->SetRotation(m_Rotation);
|
||||
}
|
||||
|
||||
std::cout << "Solid " << GetName() << " position: " << pos << " rotation: " << m << std::endl;
|
||||
}
|
||||
|
||||
void Solid::SetParent(Solid *parent) {
|
||||
|
||||
@@ -13,41 +13,41 @@ namespace uLib {
|
||||
using namespace CLHEP;
|
||||
|
||||
inline namespace literals {
|
||||
constexpr double operator"" _m(long double v) { return static_cast<double>(v) * CLHEP::meter; }
|
||||
constexpr double operator"" _cm(long double v) { return static_cast<double>(v) * CLHEP::centimeter; }
|
||||
constexpr double operator"" _mm(long double v) { return static_cast<double>(v) * CLHEP::millimeter; }
|
||||
constexpr double operator"" _um(long double v) { return static_cast<double>(v) * CLHEP::micrometer; }
|
||||
constexpr double operator"" _nm(long double v) { return static_cast<double>(v) * CLHEP::nanometer; }
|
||||
constexpr double operator"" _km(long double v) { return static_cast<double>(v) * CLHEP::kilometer; }
|
||||
constexpr double operator""_m(long double v) { return static_cast<double>(v) * CLHEP::meter; }
|
||||
constexpr double operator""_cm(long double v) { return static_cast<double>(v) * CLHEP::centimeter; }
|
||||
constexpr double operator""_mm(long double v) { return static_cast<double>(v) * CLHEP::millimeter; }
|
||||
constexpr double operator""_um(long double v) { return static_cast<double>(v) * CLHEP::micrometer; }
|
||||
constexpr double operator""_nm(long double v) { return static_cast<double>(v) * CLHEP::nanometer; }
|
||||
constexpr double operator""_km(long double v) { return static_cast<double>(v) * CLHEP::kilometer; }
|
||||
|
||||
constexpr double operator"" _m(unsigned long long v) { return static_cast<double>(v) * CLHEP::meter; }
|
||||
constexpr double operator"" _cm(unsigned long long v) { return static_cast<double>(v) * CLHEP::centimeter; }
|
||||
constexpr double operator"" _mm(unsigned long long v) { return static_cast<double>(v) * CLHEP::millimeter; }
|
||||
constexpr double operator"" _um(unsigned long long v) { return static_cast<double>(v) * CLHEP::micrometer; }
|
||||
constexpr double operator"" _nm(unsigned long long v) { return static_cast<double>(v) * CLHEP::nanometer; }
|
||||
constexpr double operator"" _km(unsigned long long v) { return static_cast<double>(v) * CLHEP::kilometer; }
|
||||
constexpr double operator""_m(unsigned long long v) { return static_cast<double>(v) * CLHEP::meter; }
|
||||
constexpr double operator""_cm(unsigned long long v) { return static_cast<double>(v) * CLHEP::centimeter; }
|
||||
constexpr double operator""_mm(unsigned long long v) { return static_cast<double>(v) * CLHEP::millimeter; }
|
||||
constexpr double operator""_um(unsigned long long v) { return static_cast<double>(v) * CLHEP::micrometer; }
|
||||
constexpr double operator""_nm(unsigned long long v) { return static_cast<double>(v) * CLHEP::nanometer; }
|
||||
constexpr double operator""_km(unsigned long long v) { return static_cast<double>(v) * CLHEP::kilometer; }
|
||||
|
||||
constexpr double operator"" _deg(long double v) { return static_cast<double>(v) * CLHEP::degree; }
|
||||
constexpr double operator"" _rad(long double v) { return static_cast<double>(v) * CLHEP::radian; }
|
||||
constexpr double operator"" _deg(unsigned long long v) { return static_cast<double>(v) * CLHEP::degree; }
|
||||
constexpr double operator"" _rad(unsigned long long v) { return static_cast<double>(v) * CLHEP::radian; }
|
||||
constexpr double operator""_deg(long double v) { return static_cast<double>(v) * CLHEP::degree; }
|
||||
constexpr double operator""_rad(long double v) { return static_cast<double>(v) * CLHEP::radian; }
|
||||
constexpr double operator""_deg(unsigned long long v) { return static_cast<double>(v) * CLHEP::degree; }
|
||||
constexpr double operator""_rad(unsigned long long v) { return static_cast<double>(v) * CLHEP::radian; }
|
||||
|
||||
constexpr double operator"" _ns(long double v) { return static_cast<double>(v) * CLHEP::nanosecond; }
|
||||
constexpr double operator"" _s(long double v) { return static_cast<double>(v) * CLHEP::second; }
|
||||
constexpr double operator"" _ms(long double v) { return static_cast<double>(v) * CLHEP::millisecond; }
|
||||
constexpr double operator"" _ns(unsigned long long v) { return static_cast<double>(v) * CLHEP::nanosecond; }
|
||||
constexpr double operator"" _s(unsigned long long v) { return static_cast<double>(v) * CLHEP::second; }
|
||||
constexpr double operator"" _ms(unsigned long long v) { return static_cast<double>(v) * CLHEP::millisecond; }
|
||||
constexpr double operator""_ns(long double v) { return static_cast<double>(v) * CLHEP::nanosecond; }
|
||||
constexpr double operator""_s(long double v) { return static_cast<double>(v) * CLHEP::second; }
|
||||
constexpr double operator""_ms(long double v) { return static_cast<double>(v) * CLHEP::millisecond; }
|
||||
constexpr double operator""_ns(unsigned long long v) { return static_cast<double>(v) * CLHEP::nanosecond; }
|
||||
constexpr double operator""_s(unsigned long long v) { return static_cast<double>(v) * CLHEP::second; }
|
||||
constexpr double operator""_ms(unsigned long long v) { return static_cast<double>(v) * CLHEP::millisecond; }
|
||||
|
||||
constexpr double operator"" _MeV(long double v) { return static_cast<double>(v) * CLHEP::megaelectronvolt; }
|
||||
constexpr double operator"" _eV(long double v) { return static_cast<double>(v) * CLHEP::electronvolt; }
|
||||
constexpr double operator"" _keV(long double v) { return static_cast<double>(v) * CLHEP::kiloelectronvolt; }
|
||||
constexpr double operator"" _GeV(long double v) { return static_cast<double>(v) * CLHEP::gigaelectronvolt; }
|
||||
constexpr double operator"" _TeV(long double v) { return static_cast<double>(v) * CLHEP::teraelectronvolt; }
|
||||
constexpr double operator"" _MeV(unsigned long long v) { return static_cast<double>(v) * CLHEP::megaelectronvolt; }
|
||||
constexpr double operator"" _eV(unsigned long long v) { return static_cast<double>(v) * CLHEP::electronvolt; }
|
||||
constexpr double operator"" _keV(unsigned long long v) { return static_cast<double>(v) * CLHEP::kiloelectronvolt; }
|
||||
constexpr double operator"" _GeV(unsigned long long v) { return static_cast<double>(v) * CLHEP::gigaelectronvolt; }
|
||||
constexpr double operator""_MeV(long double v) { return static_cast<double>(v) * CLHEP::megaelectronvolt; }
|
||||
constexpr double operator""_eV(long double v) { return static_cast<double>(v) * CLHEP::electronvolt; }
|
||||
constexpr double operator""_keV(long double v) { return static_cast<double>(v) * CLHEP::kiloelectronvolt; }
|
||||
constexpr double operator""_GeV(long double v) { return static_cast<double>(v) * CLHEP::gigaelectronvolt; }
|
||||
constexpr double operator""_TeV(long double v) { return static_cast<double>(v) * CLHEP::teraelectronvolt; }
|
||||
constexpr double operator""_MeV(unsigned long long v) { return static_cast<double>(v) * CLHEP::megaelectronvolt; }
|
||||
constexpr double operator""_eV(unsigned long long v) { return static_cast<double>(v) * CLHEP::electronvolt; }
|
||||
constexpr double operator""_keV(unsigned long long v) { return static_cast<double>(v) * CLHEP::kiloelectronvolt; }
|
||||
constexpr double operator""_GeV(unsigned long long v) { return static_cast<double>(v) * CLHEP::gigaelectronvolt; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,8 @@ list(APPEND HEADERS ${HEP_GEANT_HEADERS})
|
||||
set(LIBRARIES Eigen3::Eigen
|
||||
${ROOT_LIBRARIES}
|
||||
${VTK_LIBRARIES}
|
||||
VTK::ImagingHybrid
|
||||
VTK::ImagingSources
|
||||
${PACKAGE_LIBPREFIX}Math
|
||||
${PACKAGE_LIBPREFIX}Detectors
|
||||
${PACKAGE_LIBPREFIX}Geant)
|
||||
@@ -56,7 +58,7 @@ set_target_properties(${libname} PROPERTIES
|
||||
AUTOMOC ON
|
||||
AUTOUIC ON
|
||||
AUTORCC ON)
|
||||
target_link_libraries(${libname} ${LIBRARIES} Qt6::Widgets)
|
||||
target_link_libraries(${libname} PUBLIC ${LIBRARIES} Qt6::Widgets)
|
||||
|
||||
install(TARGETS ${libname}
|
||||
EXPORT "uLibTargets"
|
||||
|
||||
@@ -61,8 +61,8 @@ int main(int argc, char** argv) {
|
||||
vtkTess.AddToViewer(viewer);
|
||||
|
||||
// Color them differently
|
||||
vtkActor::SafeDownCast(vtkBox.GetProp())->GetProperty()->SetColor(0.8, 0.2, 0.2); // Redish box
|
||||
vtkActor::SafeDownCast(vtkTess.GetProp())->GetProperty()->SetColor(0.2, 0.8, 0.2); // Greenish tess
|
||||
vtkBox.SetColor(0.8, 0.2, 0.2); // Redish box
|
||||
vtkTess.SetColor(0.2, 0.8, 0.2); // Greenish tess
|
||||
|
||||
// Position tessellated solid away from box
|
||||
Matrix4f trans = Matrix4f::Identity();
|
||||
|
||||
@@ -10,48 +10,75 @@
|
||||
//////////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
#include "vtkBoxSolid.h"
|
||||
#include "Core/Monitor.h"
|
||||
#include <vtkCubeSource.h>
|
||||
#include <vtkPolyDataMapper.h>
|
||||
#include <vtkActor.h>
|
||||
#include <vtkProperty.h>
|
||||
#include <vtkAssembly.h>
|
||||
#include <vtkTransform.h>
|
||||
#include <vtkMatrix4x4.h>
|
||||
#include <Geant4/G4VPhysicalVolume.hh>
|
||||
#include "Vtk/Math/vtkDense.h"
|
||||
|
||||
namespace uLib {
|
||||
namespace Vtk {
|
||||
|
||||
vtkBoxSolid::vtkBoxSolid(Geant::BoxSolid *content)
|
||||
: vtkGeantSolid(content), m_BoxContent(content) {
|
||||
// Re-run Update for box-specific pipe
|
||||
this->InstallPipe();
|
||||
|
||||
// Connect the model's Updated event to updateTransform to ensure VTK sync
|
||||
m_UpdateConnection = Object::connect(m_BoxContent, &uLib::Object::Updated, this, &vtkBoxSolid::Update);
|
||||
|
||||
// Initial sync
|
||||
this->Update();
|
||||
}
|
||||
|
||||
vtkBoxSolid::~vtkBoxSolid() {}
|
||||
vtkBoxSolid::~vtkBoxSolid() {
|
||||
}
|
||||
|
||||
void vtkBoxSolid::Update() {
|
||||
ConnectionBlock blocker(m_UpdateConnection);
|
||||
this->UpdateGeometry();
|
||||
this->UpdateTransform();
|
||||
// Ensure base Puppet properties (color, opacity, etc) and transform are applied
|
||||
this->Puppet::Update();
|
||||
}
|
||||
|
||||
void vtkBoxSolid::SyncFromVtk() {
|
||||
this->Puppet::SyncFromVtk();
|
||||
if (auto* proxy = vtkProp3D::SafeDownCast(this->GetProxyProp())) {
|
||||
if (vtkMatrix4x4* mat = proxy->GetUserMatrix()) {
|
||||
m_BoxContent->SetTransform(VtkToMatrix4f(mat));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void vtkBoxSolid::UpdateGeometry() {
|
||||
if (!m_BoxContent || !m_BoxContent->GetObject()) {
|
||||
// Fallback to base tessellation if no model object
|
||||
// Sync geometry from G4VSolid provided by vtkGeantSolid (tessellation)
|
||||
vtkGeantSolid::UpdateGeometry();
|
||||
return;
|
||||
}
|
||||
|
||||
void vtkBoxSolid::UpdateTransform() {
|
||||
// Take transform from Puppet base (which uses GetContent() -> ContainerBox TRS)
|
||||
this->Puppet::Update();
|
||||
}
|
||||
|
||||
void vtkBoxSolid::serialize_display(uLib::Archive::display_properties_archive &ar,
|
||||
const unsigned int version) {
|
||||
// Expose Geant solid properties and underlying Box/TRS properties
|
||||
this->Puppet::serialize_display(ar, version);
|
||||
if (m_BoxContent) {
|
||||
ar & NVP("Box", *m_BoxContent);
|
||||
if (m_BoxContent->GetObject()) {
|
||||
ar & NVP("Container", *m_BoxContent->GetObject());
|
||||
}
|
||||
|
||||
// Use the underlying ContainerBox for precise geometry
|
||||
Vector3f size = m_BoxContent->GetObject()->GetSize();
|
||||
|
||||
vtkNew<vtkCubeSource> cube;
|
||||
cube->SetXLength(size(0));
|
||||
cube->SetYLength(size(1));
|
||||
cube->SetZLength(size(2));
|
||||
cube->Update();
|
||||
|
||||
vtkPolyData *poly = GetPolyData();
|
||||
if (poly) {
|
||||
poly->ShallowCopy(cube->GetOutput());
|
||||
poly->Modified();
|
||||
}
|
||||
}
|
||||
|
||||
void vtkBoxSolid::InstallPipe() {
|
||||
vtkGeantSolid::InstallPipe();
|
||||
}
|
||||
|
||||
} // namespace Vtk
|
||||
} // namespace uLib
|
||||
|
||||
@@ -26,8 +26,15 @@
|
||||
#ifndef U_VTKBOXSOLID_H
|
||||
#define U_VTKBOXSOLID_H
|
||||
|
||||
#include "Core/Types.h"
|
||||
#include "Core/Property.h"
|
||||
#include "Core/Serializable.h"
|
||||
|
||||
#include "vtkGeantSolid.h"
|
||||
|
||||
class vtkCubeSource;
|
||||
class vtkActor;
|
||||
|
||||
namespace uLib {
|
||||
namespace Vtk {
|
||||
|
||||
@@ -35,15 +42,36 @@ namespace Vtk {
|
||||
* @brief VTK Puppet for visualizing a Geant::BoxSolid.
|
||||
*/
|
||||
class vtkBoxSolid : public vtkGeantSolid {
|
||||
uLibTypeMacro(vtkBoxSolid, uLib::Vtk::vtkGeantSolid)
|
||||
|
||||
public:
|
||||
vtkBoxSolid(Geant::BoxSolid *content);
|
||||
virtual ~vtkBoxSolid();
|
||||
|
||||
virtual void Update() override;
|
||||
virtual void UpdateGeometry() override;
|
||||
virtual void UpdateTransform() override;
|
||||
virtual void SyncFromVtk() override;
|
||||
|
||||
virtual uLib::Object *GetContent() const override {
|
||||
return m_BoxContent ? (::uLib::Object*)m_BoxContent->GetObject() : nullptr;
|
||||
}
|
||||
|
||||
virtual void serialize_display(uLib::Archive::display_properties_archive &ar,
|
||||
const unsigned int version = 0) override;
|
||||
|
||||
template <typename Ar>
|
||||
void serialize(Ar &ar, const unsigned int version) {
|
||||
ar & NVP("BoxSolid", *m_BoxContent);
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void InstallPipe() override;
|
||||
|
||||
Geant::BoxSolid *m_BoxContent;
|
||||
uLib::Connection m_UpdateConnection;
|
||||
|
||||
ULIB_DECLARE_PROPERTIES(vtkBoxSolid)
|
||||
};
|
||||
|
||||
} // namespace Vtk
|
||||
|
||||
@@ -43,7 +43,7 @@ public:
|
||||
|
||||
virtual class vtkPolyData *GetPolyData() const override;
|
||||
|
||||
virtual void Update();
|
||||
virtual void Update() override;
|
||||
|
||||
protected:
|
||||
virtual void InstallPipe();
|
||||
|
||||
@@ -82,6 +82,7 @@ void vtkGeantSolid::UpdateGeometry() {
|
||||
int nVertices = polyhedron->GetNoVertices();
|
||||
for (int i = 1; i <= nVertices; ++i) {
|
||||
G4Point3D vtx = polyhedron->GetVertex(i);
|
||||
|
||||
points->InsertNextPoint(vtx.x(), vtx.y(), vtx.z());
|
||||
}
|
||||
|
||||
|
||||
@@ -37,14 +37,17 @@ int main() {
|
||||
BEGIN_TESTING(vtk ContainerBox Test);
|
||||
|
||||
ContainerBox box;
|
||||
box.Scale(Vector3f(1_m,5_m,1_m));
|
||||
box.SetPosition(Vector3f(0,1_m,0));
|
||||
Vtk::vtkContainerBox v_box(&box);
|
||||
v_box.SetRepresentation(Vtk::Puppet::Surface);
|
||||
v_box.SetOpacity(0.5);
|
||||
v_box.SetSelectable(true);
|
||||
box.Scale(Vector3f(1_m,2_m,1_m));
|
||||
box.SetPosition(Vector3f(0,0,0));
|
||||
|
||||
box.findOrAddSignal(&ContainerBox::Updated)->connect([&box](){
|
||||
Vtk::vtkContainerBox v_box(&box);
|
||||
v_box.Update();
|
||||
|
||||
// v_box.SetRepresentation(Vtk::Puppet::Surface);
|
||||
// v_box.SetOpacity(0.5);
|
||||
// v_box.SetSelectable(true);
|
||||
|
||||
box.findOrAddSignal(&Object::Updated)->connect([&box](){
|
||||
std::cout << "box updated: " << box.GetWorldPoint(HPoint3f(1,1,1)) << std::endl;
|
||||
});
|
||||
|
||||
|
||||
@@ -51,13 +51,12 @@ struct ContainerBoxData {
|
||||
vtkSmartPointer<vtkActor> m_Cube;
|
||||
vtkSmartPointer<vtkActor> m_Axes;
|
||||
vtkSmartPointer<vtkAssembly> m_VtkAsm;
|
||||
vtkSmartPointer<vtkMatrix4x4> m_Affine;
|
||||
uLib::Connection m_UpdateSignal;
|
||||
|
||||
|
||||
ContainerBoxData() : m_Cube(vtkSmartPointer<vtkActor>::New()),
|
||||
m_Axes(vtkSmartPointer<vtkActor>::New()),
|
||||
m_VtkAsm(vtkSmartPointer<vtkAssembly>::New()),
|
||||
m_Affine(vtkSmartPointer<vtkMatrix4x4>::New()) {}
|
||||
m_VtkAsm(vtkSmartPointer<vtkAssembly>::New()) {}
|
||||
~ContainerBoxData() {
|
||||
}
|
||||
};
|
||||
@@ -86,14 +85,13 @@ void vtkContainerBox::Update() {
|
||||
RecursiveMutex::ScopedLock lock(this->m_UpdateMutex);
|
||||
if (!m_Content) return;
|
||||
|
||||
vtkProp3D* root = vtkProp3D::SafeDownCast(this->GetProp());
|
||||
if (root) {
|
||||
// Apply local full matrix (TRS * LocalBox) so that nested assemblies work correctly
|
||||
Matrix4f fullLocal = m_Content->GetMatrix() * m_Content->GetLocalMatrix();
|
||||
vtkProp3D* prop = vtkProp3D::SafeDownCast(this->GetProp());
|
||||
if (prop) {
|
||||
// Apply the full volume matrix (TRS * m_LocalT)
|
||||
vtkNew<vtkMatrix4x4> m;
|
||||
Matrix4fToVtk(fullLocal, m);
|
||||
root->SetUserMatrix(m);
|
||||
root->Modified();
|
||||
Matrix4fToVtk(m_Content->GetMatrix(), m);
|
||||
prop->SetUserMatrix(m);
|
||||
prop->Modified();
|
||||
}
|
||||
|
||||
// Delegate rest of update (appearance, render, etc)
|
||||
@@ -102,6 +100,8 @@ void vtkContainerBox::Update() {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void vtkContainerBox::SyncFromVtk() {
|
||||
RecursiveMutex::ScopedLock lock(this->m_UpdateMutex);
|
||||
if (!m_Content) return;
|
||||
@@ -111,18 +111,11 @@ void vtkContainerBox::SyncFromVtk() {
|
||||
|
||||
// VTK -> Model: Extract new world TRS from proxy, which matches the model's TRS center
|
||||
vtkMatrix4x4* rootMat = root->GetUserMatrix();
|
||||
if (rootMat) {
|
||||
std::cout << "[vtkContainerBox::SyncFromVtk] Read Proxy UserMatrix:" << std::endl;
|
||||
rootMat->Print(std::cout);
|
||||
}
|
||||
|
||||
Matrix4f vtkWorld = VtkToMatrix4f(rootMat);
|
||||
|
||||
// Synchronize TRS property members from the updated local matrix
|
||||
m_Content->FromMatrix(vtkWorld);
|
||||
|
||||
std::cout << "[vtkContainerBox::SyncFromVtk] New Model WorldMatrix:" << std::endl << m_Content->GetWorldMatrix() << std::endl;
|
||||
|
||||
// Since we modified the model, notify observers, but block the loop back to VTK
|
||||
// ConnectionBlock blocker(d->m_UpdateSignal);
|
||||
m_Content->Updated();
|
||||
@@ -171,11 +164,10 @@ void vtkContainerBox::InstallPipe() {
|
||||
d->m_VtkAsm->AddPart(d->m_Axes);
|
||||
this->SetProp(d->m_VtkAsm);
|
||||
|
||||
vtkProp3D* root = d->m_VtkAsm;
|
||||
if (root) {
|
||||
d->m_Affine = Matrix4fToVtk(m_Content->GetMatrix());
|
||||
root->SetUserMatrix(d->m_Affine);
|
||||
}
|
||||
// vtkProp3D* root = d->m_VtkAsm;
|
||||
// if (root) {
|
||||
// this->ApplyPuppetTransform(root);
|
||||
// }
|
||||
this->Update();
|
||||
}
|
||||
|
||||
|
||||
@@ -38,13 +38,15 @@ namespace Vtk {
|
||||
struct ContainerBoxData;
|
||||
|
||||
class vtkContainerBox : public Puppet, public Polydata {
|
||||
|
||||
uLibTypeMacro(vtkContainerBox, Puppet, Polydata)
|
||||
typedef ContainerBox Content;
|
||||
|
||||
public:
|
||||
vtkContainerBox(Content *content);
|
||||
~vtkContainerBox();
|
||||
|
||||
virtual class vtkPolyData *GetPolyData() const;
|
||||
virtual class vtkPolyData *GetPolyData() const override;
|
||||
|
||||
/**
|
||||
* @brief Updates the VTK representation from the internal state.
|
||||
@@ -62,9 +64,9 @@ protected:
|
||||
virtual void InstallPipe();
|
||||
|
||||
struct ContainerBoxData *d;
|
||||
Content *m_Content;
|
||||
bool m_BlockUpdate = false;
|
||||
ContainerBox *m_Content;
|
||||
|
||||
ULIB_DECLARE_PROPERTIES(vtkContainerBox)
|
||||
};
|
||||
|
||||
} // namespace Vtk
|
||||
|
||||
@@ -93,18 +93,10 @@ void vtkCylinder::SyncFromVtk() {
|
||||
|
||||
// VTK -> Model: Extract new world TRS from proxy
|
||||
vtkMatrix4x4* rootMat = root->GetUserMatrix();
|
||||
if (rootMat) {
|
||||
std::cout << "[vtkCylinder::SyncFromVtk] Read Proxy UserMatrix:" << std::endl;
|
||||
rootMat->Print(std::cout);
|
||||
}
|
||||
|
||||
Matrix4f vtkWorld = VtkToMatrix4f(rootMat);
|
||||
|
||||
// Directly sync model from the world matrix
|
||||
m_Content->FromMatrix(vtkWorld);
|
||||
|
||||
std::cout << "[vtkCylinder::SyncFromVtk] New Model WorldMatrix:" << std::endl << m_Content->GetWorldMatrix() << std::endl;
|
||||
|
||||
m_Content->Updated();
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
#include <vtkPiecewiseFunction.h>
|
||||
#include <vtkSmartVolumeMapper.h>
|
||||
#include <vtkVolumeProperty.h>
|
||||
#include <vtkMatrix4x4.h>
|
||||
|
||||
#include <vtkActor.h>
|
||||
#include <vtkPolyDataMapper.h>
|
||||
@@ -49,6 +50,7 @@
|
||||
#include <Math/VoxImage.h>
|
||||
|
||||
#include "Vtk/Math/vtkVoxImage.h"
|
||||
#include "Vtk/Math/vtkDense.h"
|
||||
|
||||
#include <vtkAutoInit.h>
|
||||
VTK_MODULE_INIT(vtkRenderingVolumeOpenGL2);
|
||||
@@ -128,9 +130,15 @@ vtkVoxImage::vtkVoxImage(Content &content)
|
||||
m_Image(vtkImageData::New()), m_Outline(vtkCubeSource::New()),
|
||||
m_OutlineActor(vtkActor::New()),
|
||||
m_Reader(NULL), m_Writer(NULL), writer_factor(1.E6),
|
||||
m_Window(40/1.E6), m_Level(20/1.E6), m_ShadingPreset(0) {
|
||||
m_Window(1.0), m_Level(0.5), m_ShadingPreset(0) {
|
||||
// Transfer functions
|
||||
m_ColorFun = vtkColorTransferFunction::New();
|
||||
m_OpacityFun = vtkPiecewiseFunction::New();
|
||||
m_UpdateConnection = Object::connect(&m_Content, &uLib::Object::Updated, this, &vtkVoxImage::Update);
|
||||
|
||||
GetContent();
|
||||
InstallPipe();
|
||||
RescaleShaderRange();
|
||||
ULIB_ACTIVATE_DISPLAY_PROPERTIES;
|
||||
}
|
||||
|
||||
@@ -140,6 +148,8 @@ vtkVoxImage::~vtkVoxImage() {
|
||||
m_Asm->Delete();
|
||||
m_Outline->Delete();
|
||||
m_OutlineActor->Delete();
|
||||
m_ColorFun->Delete();
|
||||
m_OpacityFun->Delete();
|
||||
}
|
||||
|
||||
vtkImageData *vtkVoxImage::GetImageData() {
|
||||
@@ -181,6 +191,7 @@ void vtkVoxImage::ReadFromVKTFile(const char *fname) {
|
||||
|
||||
m_Image->DeepCopy(vtkscale->GetOutput());
|
||||
SetContent();
|
||||
RescaleShaderRange();
|
||||
} else {
|
||||
std::cerr << "Error: file does not contain structured points\n";
|
||||
}
|
||||
@@ -200,115 +211,134 @@ void vtkVoxImage::ReadFromXMLFile(const char *fname) {
|
||||
|
||||
m_Image->DeepCopy(vtkscale->GetOutput());
|
||||
SetContent();
|
||||
RescaleShaderRange();
|
||||
}
|
||||
|
||||
void vtkVoxImage::setShadingPreset(int blendType) {
|
||||
m_ShadingPreset = blendType;
|
||||
vtkSmartVolumeMapper *mapper = (vtkSmartVolumeMapper *)m_Actor->GetMapper();
|
||||
if (!mapper) return;
|
||||
vtkVolumeProperty *property = m_Actor->GetProperty();
|
||||
|
||||
static vtkColorTransferFunction *colorFun = vtkColorTransferFunction::New();
|
||||
static vtkPiecewiseFunction *opacityFun = vtkPiecewiseFunction::New();
|
||||
|
||||
float window = m_Window;
|
||||
float level = m_Level;
|
||||
|
||||
property->SetColor(colorFun);
|
||||
property->SetScalarOpacity(opacityFun);
|
||||
property->SetColor(m_ColorFun);
|
||||
property->SetScalarOpacity(m_OpacityFun);
|
||||
property->SetInterpolationTypeToLinear();
|
||||
|
||||
if (blendType != 6) {
|
||||
colorFun->RemoveAllPoints();
|
||||
opacityFun->RemoveAllPoints();
|
||||
}
|
||||
m_ColorFun->RemoveAllPoints();
|
||||
m_OpacityFun->RemoveAllPoints();
|
||||
|
||||
switch (blendType) {
|
||||
case 0:
|
||||
colorFun->AddRGBSegment(0.0, 1.0, 1.0, 1.0, 255.0, 1.0, 1.0, 1.0);
|
||||
opacityFun->AddSegment(level - 0.5 * window, 0.0, level + 0.5 * window,
|
||||
1.0);
|
||||
case 0: // MIP
|
||||
m_ColorFun->AddRGBPoint(level - 0.5 * window, 0, 0, 0);
|
||||
m_ColorFun->AddRGBPoint(level + 0.5 * window, 1, 1, 1);
|
||||
m_OpacityFun->AddSegment(level - 0.5 * window, 0.0, level + 0.5 * window, 1.0);
|
||||
mapper->SetBlendModeToMaximumIntensity();
|
||||
break;
|
||||
case 1:
|
||||
colorFun->AddRGBSegment(level - 0.5 * window, 0.0, 0.0, 0.0,
|
||||
level + 0.5 * window, 1.0, 1.0, 1.0);
|
||||
opacityFun->AddSegment(level - 0.5 * window, 0.0, level + 0.5 * window,
|
||||
1.0);
|
||||
case 1: // Composite
|
||||
m_ColorFun->AddRGBPoint(level - 0.5 * window, 0, 0, 0);
|
||||
m_ColorFun->AddRGBPoint(level + 0.5 * window, 1, 1, 1);
|
||||
m_OpacityFun->AddSegment(level - 0.5 * window, 0.0, level + 0.5 * window, 1.0);
|
||||
mapper->SetBlendModeToComposite();
|
||||
property->ShadeOff();
|
||||
break;
|
||||
case 2:
|
||||
colorFun->AddRGBSegment(0.0, 1.0, 1.0, 1.0, 255.0, 1.0, 1.0, 1.0);
|
||||
opacityFun->AddSegment(level - 0.5 * window, 0.0, level + 0.5 * window,
|
||||
1.0);
|
||||
case 2: // Composite Shaded
|
||||
m_ColorFun->AddRGBPoint(level - 0.5 * window, 0, 0, 0);
|
||||
m_ColorFun->AddRGBPoint(level + 0.5 * window, 1, 1, 1);
|
||||
m_OpacityFun->AddSegment(level - 0.5 * window, 0.0, level + 0.5 * window, 1.0);
|
||||
mapper->SetBlendModeToComposite();
|
||||
property->ShadeOn();
|
||||
break;
|
||||
case 3:
|
||||
colorFun->AddRGBPoint(-3024, 0, 0, 0, 0.5, 0.0);
|
||||
colorFun->AddRGBPoint(-1000, .62, .36, .18, 0.5, 0.0);
|
||||
colorFun->AddRGBPoint(-500, .88, .60, .29, 0.33, 0.45);
|
||||
colorFun->AddRGBPoint(3071, .83, .66, 1, 0.5, 0.0);
|
||||
opacityFun->AddPoint(-3024, 0, 0.5, 0.0);
|
||||
opacityFun->AddPoint(-1000, 0, 0.5, 0.0);
|
||||
opacityFun->AddPoint(-500, 1.0, 0.33, 0.45);
|
||||
opacityFun->AddPoint(3071, 1.0, 0.5, 0.0);
|
||||
mapper->SetBlendModeToComposite();
|
||||
property->ShadeOn();
|
||||
property->SetAmbient(0.1);
|
||||
property->SetDiffuse(0.9);
|
||||
property->SetSpecular(0.2);
|
||||
property->SetSpecularPower(10.0);
|
||||
property->SetScalarOpacityUnitDistance(0.8919);
|
||||
break;
|
||||
case 4:
|
||||
colorFun->AddRGBPoint(0.0, 0, 0, 1); // Blue
|
||||
colorFun->AddRGBPoint(level, 0, 1, 0); // Green
|
||||
colorFun->AddRGBPoint(level + 0.5*window, 1, 1, 0); // Yellow
|
||||
colorFun->AddRGBPoint(level + window, 1, 0, 0); // Red
|
||||
opacityFun->AddSegment(level - 0.5 * window, 0.0, level + 0.5 * window, 1.0);
|
||||
case 3: // Rainbow MIP
|
||||
m_ColorFun->AddRGBPoint(level - 0.5 * window, 0, 0, 1);
|
||||
m_ColorFun->AddRGBPoint(level, 0, 1, 0);
|
||||
m_ColorFun->AddRGBPoint(level + 0.5 * window, 1, 1, 0);
|
||||
m_ColorFun->AddRGBPoint(level + window, 1, 0, 0);
|
||||
m_OpacityFun->AddSegment(level - 0.5 * window, 0.0, level + 0.5 * window, 1.0);
|
||||
mapper->SetBlendModeToMaximumIntensity();
|
||||
break;
|
||||
case 5:
|
||||
colorFun->AddRGBSegment(0.0, 1.0, 1.0, 1.0, 255.0, 1.0, 1.0, 1.0);
|
||||
opacityFun->AddSegment(level - 0.5 * window, 0.0, level + 0.5 * window, 1.0);
|
||||
case 4: // Additive
|
||||
m_ColorFun->AddRGBPoint(level - 0.5 * window, 0, 0, 0);
|
||||
m_ColorFun->AddRGBPoint(level + 0.5 * window, 1, 1, 1);
|
||||
m_OpacityFun->AddSegment(level - 0.5 * window, 0.0, level + 0.5 * window, 1.0);
|
||||
mapper->SetBlendModeToAdditive();
|
||||
break;
|
||||
default:
|
||||
vtkGenericWarningMacro("Unknown blend type.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void vtkVoxImage::RescaleShaderRange() {
|
||||
double range[2];
|
||||
m_Image->GetScalarRange(range);
|
||||
m_Level = (range[0] + range[1]) / 2.0;
|
||||
m_Window = range[1] - range[0];
|
||||
if (m_Window <= 1e-9)
|
||||
m_Window = 1.0;
|
||||
setShadingPreset(m_ShadingPreset);
|
||||
}
|
||||
|
||||
void vtkVoxImage::SetRepresentation(Representation mode) {
|
||||
Puppet::SetRepresentation(mode); // Ensure base class data state is updated
|
||||
|
||||
if (mode == Wireframe) {
|
||||
m_Actor->SetVisibility(0);
|
||||
m_OutlineActor->SetVisibility(1);
|
||||
} else if (mode == Surface) {
|
||||
m_OutlineActor->GetProperty()->SetRepresentationToWireframe();
|
||||
} else if (mode == Volume) {
|
||||
m_Actor->SetVisibility(1);
|
||||
m_OutlineActor->SetVisibility(1); // Keep outline visible as boundary
|
||||
m_OutlineActor->SetVisibility(1);
|
||||
m_OutlineActor->GetProperty()->SetRepresentationToWireframe();
|
||||
} else {
|
||||
Puppet::SetRepresentation(mode);
|
||||
// Other representations (Points, Surface, etc) are handled by basic Puppet
|
||||
// behavior which affects the m_Asm parts.
|
||||
}
|
||||
}
|
||||
|
||||
void vtkVoxImage::serialize_display(uLib::Archive::display_properties_archive & ar, const unsigned int version) {
|
||||
// Call base class if it has display properties
|
||||
Puppet::serialize_display(ar, version);
|
||||
// Call base class to show Transform and Appearance properties
|
||||
// Puppet::serialize_display(ar, version);
|
||||
|
||||
// Use the member variables if they are available
|
||||
// Use the member variables for volume rendering parameters
|
||||
ar & boost::serialization::make_hrp("Window", m_Window);
|
||||
ar & boost::serialization::make_hrp("Level", m_Level);
|
||||
ar & boost::serialization::make_hrp_enum("Shading", m_ShadingPreset, {"MIP", "Composite", "Composite Shaded", "MIP Bone", "MIP Hot", "Additive"});
|
||||
ar & boost::serialization::make_hrp_enum("Shading", m_ShadingPreset,
|
||||
{"MIP", "Composite", "Composite Shaded", "MIP Bone", "MIP Hot", "Additive"});
|
||||
}
|
||||
|
||||
void vtkVoxImage::SyncFromVtk() {
|
||||
if (auto *root = this->GetProxyProp()) {
|
||||
vtkMatrix4x4 *rootMat = root->GetUserMatrix();
|
||||
if (rootMat) {
|
||||
Matrix4f vtkLocal = VtkToMatrix4f(rootMat);
|
||||
// Synchronize TRS from VTK, compensating for local volume offset
|
||||
m_Content.FromMatrix(vtkLocal); // * m_Content.GetLocalMatrix().inverse());
|
||||
m_Content.Updated();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void vtkVoxImage::Update() {
|
||||
if (auto *root = vtkProp3D::SafeDownCast(this->GetProp())) {
|
||||
vtkNew<vtkMatrix4x4> m;
|
||||
Matrix4fToVtk(m_Content.GetMatrix(), m); // * m_Content.GetLocalMatrix(), m);
|
||||
root->SetUserMatrix(m);
|
||||
root->Modified();
|
||||
// std::cout << "[vtkVoxImage::Update] Set Proxy UserMatrix:" << std::endl;
|
||||
// std::cout << m_Content.GetMatrix() << std::endl;
|
||||
}
|
||||
setShadingPreset(m_ShadingPreset);
|
||||
m_Actor->Update();
|
||||
m_Outline->SetBounds(m_Image->GetBounds());
|
||||
m_Outline->Update();
|
||||
|
||||
ConnectionBlock blocker(m_UpdateConnection);
|
||||
this->Puppet::Update();
|
||||
}
|
||||
|
||||
|
||||
void vtkVoxImage::InstallPipe() {
|
||||
vtkSmartPointer<vtkSmartVolumeMapper> mapper =
|
||||
vtkSmartPointer<vtkSmartVolumeMapper>::New();
|
||||
@@ -320,7 +350,7 @@ void vtkVoxImage::InstallPipe() {
|
||||
mapper->Update();
|
||||
|
||||
m_Actor->SetMapper(mapper);
|
||||
this->setShadingPreset(0);
|
||||
this->setShadingPreset(m_ShadingPreset);
|
||||
mapper->Update();
|
||||
|
||||
m_Outline->SetBounds(m_Image->GetBounds());
|
||||
@@ -337,7 +367,7 @@ void vtkVoxImage::InstallPipe() {
|
||||
this->SetProp(m_Asm);
|
||||
|
||||
// Default look
|
||||
this->SetRepresentation(Surface);
|
||||
this->SetRepresentation(Volume);
|
||||
}
|
||||
|
||||
} // namespace Vtk
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
|
||||
class vtkImageData;
|
||||
class vtkActor;
|
||||
class vtkColorTransferFunction;
|
||||
class vtkPiecewiseFunction;
|
||||
|
||||
namespace uLib {
|
||||
namespace Vtk {
|
||||
@@ -55,6 +57,8 @@ public:
|
||||
|
||||
void SetContent();
|
||||
|
||||
vtkProp3D *GetProp() override { return m_Asm; }
|
||||
|
||||
vtkImageData *GetImageData();
|
||||
|
||||
void SaveToXMLFile(const char *fname);
|
||||
@@ -65,8 +69,10 @@ public:
|
||||
|
||||
void setShadingPreset(int blendType = 2);
|
||||
void SetRepresentation(Representation mode);
|
||||
void RescaleShaderRange();
|
||||
|
||||
void Update() override;
|
||||
void SyncFromVtk() override;
|
||||
void serialize_display(uLib::Archive::display_properties_archive & ar, const unsigned int version = 0) override;
|
||||
|
||||
protected:
|
||||
@@ -88,6 +94,11 @@ private:
|
||||
float m_Window;
|
||||
float m_Level;
|
||||
int m_ShadingPreset;
|
||||
|
||||
Connection m_UpdateConnection;
|
||||
|
||||
class vtkColorTransferFunction *m_ColorFun;
|
||||
class vtkPiecewiseFunction *m_OpacityFun;
|
||||
};
|
||||
|
||||
} // namespace Vtk
|
||||
|
||||
@@ -4,6 +4,7 @@ set(TESTS
|
||||
vtkHandlerWidget
|
||||
PuppetPropertyTest
|
||||
PuppetParentingTest
|
||||
vtkQViewportTest
|
||||
# vtkVoxImageTest
|
||||
# vtkTriangleMeshTest
|
||||
)
|
||||
|
||||
87
src/Vtk/testing/vtkQViewportTest.cpp
Normal file
87
src/Vtk/testing/vtkQViewportTest.cpp
Normal file
@@ -0,0 +1,87 @@
|
||||
/*//////////////////////////////////////////////////////////////////////////////
|
||||
// CMT Cosmic Muon Tomography project //////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Copyright (c) 2014, Universita' degli Studi di Padova, INFN sez. di Padova
|
||||
All rights reserved
|
||||
|
||||
Authors: Andrea Rigoni Garola < andrea.rigoni@pd.infn.it >
|
||||
|
||||
------------------------------------------------------------------
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 3.0 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library.
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <QApplication>
|
||||
#include <QtGlobal>
|
||||
#include <Vtk/vtkQViewport.h>
|
||||
|
||||
#include <vtkSmartPointer.h>
|
||||
#include <vtkCubeSource.h>
|
||||
#include <vtkPolyDataMapper.h>
|
||||
#include <vtkActor.h>
|
||||
#include <vtkProperty.h>
|
||||
#include <vtkRenderer.h>
|
||||
|
||||
#include "testing-prototype.h"
|
||||
|
||||
using namespace uLib;
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
// Force X11 on Linux to avoid Wayland connection issues in headless/X11 environments
|
||||
#if defined(Q_OS_LINUX)
|
||||
qputenv("QT_QPA_PLATFORM", "xcb");
|
||||
#endif
|
||||
|
||||
BEGIN_TESTING(vtk QViewport Test);
|
||||
|
||||
QApplication app(argc, argv);
|
||||
app.processEvents();
|
||||
|
||||
Vtk::QViewport viewport;
|
||||
viewport.resize(800, 600);
|
||||
viewport.show();
|
||||
|
||||
vtkSmartPointer<vtkCubeSource> cube = vtkSmartPointer<vtkCubeSource>::New();
|
||||
cube->SetXLength(10);
|
||||
cube->SetYLength(10);
|
||||
cube->SetZLength(10);
|
||||
cube->Update();
|
||||
|
||||
vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
|
||||
mapper->SetInputConnection(cube->GetOutputPort());
|
||||
|
||||
vtkSmartPointer<vtkActor> actor = vtkSmartPointer<vtkActor>::New();
|
||||
actor->SetMapper(mapper);
|
||||
actor->GetProperty()->SetColor(1, 0, 0);
|
||||
|
||||
viewport.addProp(actor);
|
||||
viewport.Render();
|
||||
|
||||
ASSERT_NOT_NULL(viewport.GetRenderWindow());
|
||||
ASSERT_NOT_NULL(viewport.GetRenderer());
|
||||
|
||||
if (std::getenv("CTEST_PROJECT_NAME") == nullptr) {
|
||||
// Run application for a while to see the result
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
|
||||
END_TESTING;
|
||||
}
|
||||
@@ -100,6 +100,7 @@ public:
|
||||
|
||||
~PuppetData() {
|
||||
// No manual Delete needed for smart pointers
|
||||
|
||||
}
|
||||
|
||||
Puppet *m_Puppet;
|
||||
@@ -112,9 +113,10 @@ public:
|
||||
vtkSmartPointer<vtkCubeAxesActor> m_CubeAxesActor;
|
||||
vtkSmartPointer<vtkActor> m_HighlightActor;
|
||||
|
||||
// Display properties
|
||||
bool m_ShowBoundingBox;
|
||||
bool m_ShowScaleMeasures;
|
||||
int m_Representation;
|
||||
int m_Representation; // 0: Points, 1: Wireframe, 2: Surface, 3: SurfaceWithEdges, 4: Volume, 5: Outline, 6: Slice
|
||||
Vector3d m_Color;
|
||||
double m_Opacity;
|
||||
|
||||
@@ -134,11 +136,11 @@ public:
|
||||
|
||||
vtkActor *actor = vtkActor::SafeDownCast(p);
|
||||
if (actor) {
|
||||
if (m_Representation != -1) {
|
||||
if (m_Representation != -1 && m_Representation != Puppet::Volume) {
|
||||
if (m_Representation == Puppet::SurfaceWithEdges) {
|
||||
actor->GetProperty()->SetRepresentation(VTK_SURFACE);
|
||||
actor->GetProperty()->SetEdgeVisibility(1);
|
||||
} else {
|
||||
} else if (m_Representation != Puppet::Outline && m_Representation != Puppet::Slice) {
|
||||
actor->GetProperty()->SetRepresentation(m_Representation);
|
||||
actor->GetProperty()->SetEdgeVisibility(0);
|
||||
}
|
||||
@@ -179,11 +181,10 @@ public:
|
||||
|
||||
void UpdateHighlight() {
|
||||
if (m_Selected) {
|
||||
// Find first polydata in assembly to highlight
|
||||
vtkPolyData* polydata = nullptr;
|
||||
if (vtkActor *actor = vtkActor::SafeDownCast(m_Prop)) {
|
||||
if (actor->GetMapper()) {
|
||||
polydata = vtkPolyData::SafeDownCast(actor->GetMapper()->GetDataSetInput());
|
||||
polydata = vtkPolyData::SafeDownCast(actor->GetMapper()->GetInput());
|
||||
}
|
||||
} else if (vtkAssembly *asm_p = vtkAssembly::SafeDownCast(m_Prop)) {
|
||||
vtkPropCollection *parts = asm_p->GetParts();
|
||||
@@ -192,7 +193,7 @@ public:
|
||||
for (int i = 0; i < parts->GetNumberOfItems(); ++i) {
|
||||
vtkActor *a = vtkActor::SafeDownCast(parts->GetNextProp());
|
||||
if (a && a->GetMapper()) {
|
||||
polydata = vtkPolyData::SafeDownCast(a->GetMapper()->GetDataSetInput());
|
||||
polydata = vtkPolyData::SafeDownCast(a->GetMapper()->GetInput());
|
||||
if (polydata) break;
|
||||
}
|
||||
}
|
||||
@@ -560,28 +561,55 @@ bool Puppet::IsSelected() const
|
||||
return pd->m_Selected;
|
||||
}
|
||||
|
||||
void Puppet::ApplyPuppetTransform(vtkProp3D* prop)
|
||||
{
|
||||
if (!prop) return;
|
||||
if (auto* content = this->GetContent()) {
|
||||
if (auto* tr = dynamic_cast<uLib::TRS*>(content)) {
|
||||
vtkNew<vtkMatrix4x4> m;
|
||||
Matrix4fToVtk(tr->GetMatrix(), m);
|
||||
prop->SetUserMatrix(m);
|
||||
prop->Modified();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Puppet::SyncFromVtk()
|
||||
{
|
||||
if (auto* content = this->GetContent()) {
|
||||
if (auto* tr = dynamic_cast<uLib::TRS*>(content)) {
|
||||
if (auto* proxy = this->GetProxyProp()) {
|
||||
if (vtkMatrix4x4* mat = proxy->GetUserMatrix()) {
|
||||
tr->FromMatrix(VtkToMatrix4f(mat));
|
||||
content->Updated();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Puppet::Update()
|
||||
{
|
||||
// Derived classes should have updated the transform if they override Update()
|
||||
// or we can apply base transform if it's default:
|
||||
// pd->ApplyTransform(pd->m_Prop);
|
||||
// Apply content transform via virtual GetProp() / ApplyPuppetTransform(),
|
||||
// so all derived classes benefit without duplicating the matrix code.
|
||||
this->ApplyPuppetTransform(vtkProp3D::SafeDownCast(this->GetProp()));
|
||||
|
||||
pd->ApplyAppearance(pd->m_Prop);
|
||||
// Use virtual GetProp() for appearance so overriders (e.g. vtkVoxImage)
|
||||
// that never call SetProp() are handled correctly.
|
||||
pd->ApplyAppearance(this->GetProp());
|
||||
|
||||
if (pd->m_Selected) {
|
||||
pd->UpdateHighlight();
|
||||
}
|
||||
|
||||
if (pd->m_Prop) {
|
||||
if (pd->m_ShowBoundingBox) {
|
||||
double* bounds = pd->m_Prop->GetBounds();
|
||||
if (auto* prop = this->GetProp()) {
|
||||
if (pd->m_ShowBoundingBox && pd->m_OutlineSource) {
|
||||
double* bounds = prop->GetBounds();
|
||||
pd->m_OutlineSource->SetBounds(bounds);
|
||||
pd->m_OutlineSource->Update();
|
||||
}
|
||||
|
||||
if (pd->m_ShowScaleMeasures) {
|
||||
double* bounds = pd->m_Prop->GetBounds();
|
||||
pd->m_CubeAxesActor->SetBounds(bounds);
|
||||
if (pd->m_ShowScaleMeasures && pd->m_CubeAxesActor) {
|
||||
pd->m_CubeAxesActor->SetBounds(prop->GetBounds());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -598,6 +626,7 @@ void Puppet::Update()
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Puppet::ConnectInteractor(vtkRenderWindowInteractor *interactor)
|
||||
{
|
||||
}
|
||||
@@ -628,6 +657,8 @@ struct AppearanceProxy {
|
||||
ar & boost::serialization::make_hrp("Visibility", pd->m_Visibility);
|
||||
ar & boost::serialization::make_hrp("Pickable", pd->m_Selectable);
|
||||
ar & boost::serialization::make_hrp("Dragable", pd->m_Dragable);
|
||||
ar & boost::serialization::make_hrp("ShowBoundingBox", pd->m_ShowBoundingBox);
|
||||
ar & boost::serialization::make_hrp("ShowScaleMeasures", pd->m_ShowScaleMeasures);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -35,6 +35,8 @@
|
||||
#include <iomanip>
|
||||
#include <ostream>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <boost/type_traits/is_base_of.hpp>
|
||||
|
||||
// vtk classes forward declaration //
|
||||
class vtkProp;
|
||||
@@ -62,7 +64,8 @@ class Puppet : public uLib::Object {
|
||||
|
||||
uLibTypeMacro(Puppet, uLib::Object)
|
||||
|
||||
public : Puppet();
|
||||
public:
|
||||
Puppet();
|
||||
virtual ~Puppet();
|
||||
|
||||
virtual vtkProp *GetProp();
|
||||
@@ -106,7 +109,7 @@ uLibTypeMacro(Puppet, uLib::Object)
|
||||
* This method should be called when the VTK representation has been modified
|
||||
* (e.g., via a gizmo) and the changes need to be pushed back to the model.
|
||||
*/
|
||||
virtual void SyncFromVtk() {}
|
||||
virtual void SyncFromVtk();
|
||||
|
||||
enum Representation {
|
||||
Points = 0,
|
||||
@@ -127,10 +130,10 @@ uLibTypeMacro(Puppet, uLib::Object)
|
||||
|
||||
vtkRendererCollection *GetRenderers() const;
|
||||
|
||||
const std::vector<uLib::PropertyBase *> &GetDisplayProperties() const {
|
||||
const std::vector<uLib::PropertyBase *> &GetDisplayProperties() const override {
|
||||
return m_DisplayProperties;
|
||||
}
|
||||
void RegisterDisplayProperty(uLib::PropertyBase *prop) {
|
||||
void RegisterDisplayProperty(uLib::PropertyBase *prop) override {
|
||||
m_DisplayProperties.push_back(prop);
|
||||
}
|
||||
|
||||
@@ -147,8 +150,9 @@ protected:
|
||||
|
||||
void RemoveProp(vtkProp *prop);
|
||||
|
||||
void ApplyAppearance(vtkProp *prop);
|
||||
void ApplyTransform(vtkProp3D *p3d);
|
||||
virtual void ApplyAppearance(vtkProp *prop);
|
||||
virtual void ApplyTransform(vtkProp3D *p3d);
|
||||
virtual void ApplyPuppetTransform(vtkProp3D *p3d);
|
||||
|
||||
std::vector<uLib::PropertyBase *> m_DisplayProperties;
|
||||
mutable uLib::RecursiveMutex m_UpdateMutex;
|
||||
@@ -164,6 +168,10 @@ private:
|
||||
} // namespace Vtk
|
||||
} // namespace uLib
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
// DISPLAY PROPERTIES SERIALIZE
|
||||
// -------------------------------------------------------------------------- //
|
||||
@@ -179,10 +187,17 @@ class display_properties_archive
|
||||
: public boost::archive::detail::common_oarchive<
|
||||
display_properties_archive> {
|
||||
public:
|
||||
display_properties_archive(Vtk::Puppet *puppet)
|
||||
friend class boost::archive::detail::interface_oarchive<display_properties_archive>;
|
||||
friend class boost::archive::save_access;
|
||||
|
||||
using boost::archive::detail::common_oarchive<display_properties_archive>::save_override;
|
||||
display_properties_archive(Vtk::Puppet *p)
|
||||
: boost::archive::detail::common_oarchive<display_properties_archive>(
|
||||
boost::archive::no_header),
|
||||
m_Puppet(puppet) {}
|
||||
m_Puppet(p) {
|
||||
if (p)
|
||||
m_Visited.insert(dynamic_cast<const void *>(p));
|
||||
}
|
||||
|
||||
std::string GetCurrentGroup() const {
|
||||
std::string group;
|
||||
@@ -234,6 +249,24 @@ public:
|
||||
m_GroupStack.pop_back();
|
||||
}
|
||||
|
||||
// Follow pointers to discover properties in child objects
|
||||
template<class T>
|
||||
void save_override(T * const & t) {
|
||||
if (!t) return;
|
||||
this->save_pointer_helper(t, typename boost::is_base_of<uLib::Object, T>::type());
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void save_pointer_helper(T* t, boost::mpl::true_) {
|
||||
const void* ptr = dynamic_cast<const void*>(t);
|
||||
if (m_Visited.find(ptr) != m_Visited.end()) return;
|
||||
m_Visited.insert(ptr);
|
||||
this->save_override(*t);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void save_pointer_helper(T* t, boost::mpl::false_) {}
|
||||
|
||||
// Recursion for nested classes, ignore primitives
|
||||
template <class T> void save_override(const T &t) {
|
||||
this->save_helper(t, typename boost::is_class<T>::type());
|
||||
@@ -243,6 +276,8 @@ public:
|
||||
boost::serialization::serialize_adl(*this, const_cast<T &>(t), 0);
|
||||
}
|
||||
|
||||
void save_helper(const std::string &t, boost::mpl::true_) {}
|
||||
|
||||
template <class T> void save_helper(const T &t, boost::mpl::false_) {}
|
||||
|
||||
void save_override(const boost::archive::object_id_type &t) {}
|
||||
@@ -254,9 +289,25 @@ public:
|
||||
void save_override(const boost::archive::class_name_type &t) {}
|
||||
void save_override(const boost::archive::tracking_type &t) {}
|
||||
|
||||
// Called by Property<T>::serialize() and EnumProperty::serialize() to
|
||||
// directly register an existing property object as a display property.
|
||||
void register_property(uLib::PropertyBase &p) {
|
||||
if (m_Puppet) {
|
||||
m_Puppet->RegisterDisplayProperty(&p);
|
||||
Vtk::Puppet *puppet = m_Puppet;
|
||||
uLib::Object::connect(&p, &uLib::PropertyBase::Updated,
|
||||
[puppet]() { puppet->Update(); });
|
||||
}
|
||||
}
|
||||
|
||||
void register_enum_property(uLib::EnumProperty &p) {
|
||||
register_property(p);
|
||||
}
|
||||
|
||||
private:
|
||||
Vtk::Puppet *m_Puppet;
|
||||
std::vector<std::string> m_GroupStack;
|
||||
std::set<const void *> m_Visited;
|
||||
};
|
||||
|
||||
} // namespace Archive
|
||||
|
||||
@@ -72,6 +72,7 @@ struct ViewerData {
|
||||
vtkRenderWindow *m_RenderWindow;
|
||||
vtkSmartPointer<vtkRenderWindowInteractor> m_Interactor;
|
||||
vtkSmartPointer<vtkButtonWidget> m_GridButton;
|
||||
vtkSmartPointer<vtkButtonWidget> m_ProjButton;
|
||||
|
||||
ViewerData() : m_RenderWindow(vtkRenderWindow::New()) {}
|
||||
~ViewerData() {
|
||||
@@ -97,6 +98,11 @@ Viewer::~Viewer() {
|
||||
dv->m_GridButton->SetInteractor(nullptr);
|
||||
dv->m_GridButton = nullptr;
|
||||
}
|
||||
if (dv->m_ProjButton) {
|
||||
dv->m_ProjButton->Off();
|
||||
dv->m_ProjButton->SetInteractor(nullptr);
|
||||
dv->m_ProjButton = nullptr;
|
||||
}
|
||||
if (this->GetRenderWindow()) {
|
||||
this->GetRenderWindow()->RemoveAllObservers();
|
||||
}
|
||||
@@ -123,6 +129,7 @@ void Viewer::InstallPipe() {
|
||||
// Setup native grid button
|
||||
if (!std::getenv("CTEST_PROJECT_NAME")) {
|
||||
SetupGridButton();
|
||||
SetupProjButton();
|
||||
}
|
||||
|
||||
// BUT we want to override the style with our custom NoSpin version
|
||||
@@ -238,6 +245,88 @@ void Viewer::UpdateGridButtonPosition() {
|
||||
rep->PlaceWidget(bds);
|
||||
}
|
||||
|
||||
void Viewer::SetupProjButton() {
|
||||
if (!dv->m_RenderWindow || !dv->m_RenderWindow->GetInteractor()) return;
|
||||
|
||||
vtkNew<vtkImageCanvasSource2D> canvas;
|
||||
canvas->SetScalarTypeToUnsignedChar();
|
||||
canvas->SetNumberOfScalarComponents(4);
|
||||
canvas->SetExtent(0, 63, 0, 63, 0, 0);
|
||||
|
||||
// State 0: Perspective (gray trapezoid-like lines)
|
||||
canvas->SetDrawColor(0, 0, 0, 0);
|
||||
canvas->FillBox(0, 63, 0, 63);
|
||||
canvas->SetDrawColor(120, 120, 120, 255);
|
||||
canvas->DrawSegment(16, 16, 48, 16);
|
||||
canvas->DrawSegment(48, 16, 56, 48);
|
||||
canvas->DrawSegment(56, 48, 8, 48);
|
||||
canvas->DrawSegment(8, 48, 16, 16);
|
||||
canvas->Update();
|
||||
|
||||
vtkNew<vtkImageData> imgPersp;
|
||||
imgPersp->DeepCopy(canvas->GetOutput());
|
||||
|
||||
// State 1: Orthographic (white rectangle)
|
||||
canvas->SetDrawColor(0, 0, 0, 0);
|
||||
canvas->FillBox(0, 63, 0, 63);
|
||||
canvas->SetDrawColor(255, 255, 255, 255);
|
||||
canvas->DrawSegment(12, 16, 52, 16);
|
||||
canvas->DrawSegment(52, 16, 52, 48);
|
||||
canvas->DrawSegment(52, 48, 12, 48);
|
||||
canvas->DrawSegment(12, 48, 12, 16);
|
||||
canvas->Update();
|
||||
|
||||
vtkNew<vtkImageData> imgOrtho;
|
||||
imgOrtho->DeepCopy(canvas->GetOutput());
|
||||
|
||||
vtkNew<vtkTexturedButtonRepresentation2D> rep;
|
||||
rep->SetNumberOfStates(2);
|
||||
rep->SetButtonTexture(0, imgPersp);
|
||||
rep->SetButtonTexture(1, imgOrtho);
|
||||
|
||||
dv->m_ProjButton = vtkSmartPointer<vtkButtonWidget>::New();
|
||||
dv->m_ProjButton->SetInteractor(dv->m_RenderWindow->GetInteractor());
|
||||
dv->m_ProjButton->SetRepresentation(rep);
|
||||
|
||||
UpdateProjButtonPosition();
|
||||
|
||||
vtkNew<vtkCallbackCommand> resizeCallback;
|
||||
resizeCallback->SetClientData(this);
|
||||
resizeCallback->SetCallback([](vtkObject*, unsigned long, void* clientdata, void*){
|
||||
static_cast<Viewer*>(clientdata)->UpdateProjButtonPosition();
|
||||
});
|
||||
dv->m_RenderWindow->AddObserver(vtkCommand::ModifiedEvent, resizeCallback);
|
||||
|
||||
vtkNew<vtkCallbackCommand> stateCallback;
|
||||
stateCallback->SetClientData(this);
|
||||
stateCallback->SetCallback([](vtkObject* caller, unsigned long, void* clientdata, void*){
|
||||
auto* btn = vtkButtonWidget::SafeDownCast(caller);
|
||||
auto* v = static_cast<Viewer*>(clientdata);
|
||||
auto* r = vtkTexturedButtonRepresentation2D::SafeDownCast(btn->GetRepresentation());
|
||||
v->SetParallelProjection(r->GetState() == 1);
|
||||
});
|
||||
dv->m_ProjButton->AddObserver(vtkCommand::StateChangedEvent, stateCallback);
|
||||
dv->m_ProjButton->On();
|
||||
|
||||
rep->SetState(GetParallelProjection() ? 1 : 0);
|
||||
}
|
||||
|
||||
void Viewer::UpdateProjButtonPosition() {
|
||||
if (!dv->m_ProjButton || !dv->m_RenderWindow) return;
|
||||
auto* rep = vtkTexturedButtonRepresentation2D::SafeDownCast(dv->m_ProjButton->GetRepresentation());
|
||||
if (!rep) return;
|
||||
|
||||
int *sz = dv->m_RenderWindow->GetSize();
|
||||
if (sz[0] == 0 || sz[1] == 0) return;
|
||||
|
||||
int margin_right = 23;
|
||||
int margin_top = 220; // below the grid button (170 + 50)
|
||||
int btnSz = 100;
|
||||
double bds[6] = { (double)sz[0] - btnSz - margin_right, (double)sz[0] - margin_right,
|
||||
(double)sz[1] - margin_top - btnSz/2.0, (double)sz[1] - margin_top + btnSz/2.0, 0, 0 };
|
||||
rep->PlaceWidget(bds);
|
||||
}
|
||||
|
||||
void Viewer::Start() {
|
||||
if (std::getenv("CTEST_PROJECT_NAME")) return;
|
||||
dv->m_RenderWindow->GetInteractor()->Start();
|
||||
|
||||
@@ -38,6 +38,9 @@ private:
|
||||
void SetupGridButton();
|
||||
void UpdateGridButtonPosition();
|
||||
|
||||
void SetupProjButton();
|
||||
void UpdateProjButtonPosition();
|
||||
|
||||
struct ViewerData *dv;
|
||||
};
|
||||
|
||||
|
||||
@@ -118,18 +118,17 @@ void vtkObjectsContext::SyncFromVtk() {
|
||||
Puppet* vtkObjectsContext::CreatePuppet(uLib::Object* obj) {
|
||||
if (!obj) return nullptr;
|
||||
|
||||
if (auto* box = dynamic_cast<uLib::ContainerBox*>(obj)) {
|
||||
if (auto* vox = dynamic_cast<uLib::Abstract::VoxImage*>(obj)) {
|
||||
return new vtkVoxImage(*vox);
|
||||
} else if (auto* box = dynamic_cast<uLib::ContainerBox*>(obj)) {
|
||||
return new vtkContainerBox(box);
|
||||
} else if (auto* chamber = dynamic_cast<uLib::DetectorChamber*>(obj)) {
|
||||
return new vtkDetectorChamber(chamber);
|
||||
} else if (auto* cylinder = dynamic_cast<uLib::Cylinder*>(obj)) {
|
||||
return new vtkCylinder(cylinder);
|
||||
} else if (auto* vox = dynamic_cast<uLib::Abstract::VoxImage*>(obj)) {
|
||||
return new vtkVoxImage(*vox);
|
||||
} else if (auto* assembly = dynamic_cast<uLib::Assembly*>(obj)) {
|
||||
return new Assembly(assembly);
|
||||
}
|
||||
else if (auto* box = dynamic_cast<uLib::Geant::BoxSolid*>(obj)) {
|
||||
} else if (auto* box = dynamic_cast<uLib::Geant::BoxSolid*>(obj)) {
|
||||
return new vtkBoxSolid(box);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ QViewport::QViewport(QWidget* parent)
|
||||
, Viewport()
|
||||
, m_VtkWidget(nullptr)
|
||||
, m_GridButton(nullptr)
|
||||
, m_ProjButton(nullptr)
|
||||
{
|
||||
// Build the layout – zero margins so VTK fills the entire widget
|
||||
auto* layout = new QVBoxLayout(this);
|
||||
@@ -58,6 +59,36 @@ QViewport::QViewport(QWidget* parent)
|
||||
m_GridButton->setChecked(true); // Grid is on by default
|
||||
connect(m_GridButton, &QPushButton::clicked, this, &QViewport::onGridButtonClicked);
|
||||
|
||||
// Projection Toggle Button (below grid button)
|
||||
m_ProjButton = new QPushButton(m_VtkWidget);
|
||||
m_ProjButton->setText("P");
|
||||
m_ProjButton->setFixedSize(40, 40);
|
||||
m_ProjButton->setToolTip("Toggle Perspective / Orthographic");
|
||||
m_ProjButton->setStyleSheet(
|
||||
"QPushButton {"
|
||||
" border-radius: 20px;"
|
||||
" background-color: rgba(40, 40, 40, 180);"
|
||||
" color: white;"
|
||||
" font-size: 22px;"
|
||||
" border: 1.5px solid rgba(255, 255, 255, 60);"
|
||||
"}"
|
||||
"QPushButton:hover {"
|
||||
" background-color: rgba(70, 70, 70, 200);"
|
||||
" border: 1.5px solid rgba(255, 255, 255, 100);"
|
||||
"}"
|
||||
"QPushButton:checked {"
|
||||
" background-color: rgba(0, 120, 215, 200);"
|
||||
" color: white;"
|
||||
" border: 1.5px solid rgba(255, 255, 255, 120);"
|
||||
"}"
|
||||
"QPushButton:pressed {"
|
||||
" background-color: rgba(0, 90, 160, 220);"
|
||||
"}"
|
||||
);
|
||||
m_ProjButton->setCheckable(true);
|
||||
m_ProjButton->setChecked(false); // Perspective by default
|
||||
connect(m_ProjButton, &QPushButton::clicked, this, &QViewport::onProjButtonClicked);
|
||||
|
||||
// After the Qt widget exists but before the first paint,
|
||||
// attach the renderer and configure the pipeline.
|
||||
SetupPipeline();
|
||||
@@ -81,7 +112,6 @@ void QViewport::SetupPipeline()
|
||||
|
||||
void QViewport::Render()
|
||||
{
|
||||
UpdateGrid();
|
||||
if (m_VtkWidget && m_VtkWidget->renderWindow())
|
||||
m_VtkWidget->renderWindow()->Render();
|
||||
}
|
||||
@@ -101,6 +131,11 @@ void QViewport::onGridButtonClicked()
|
||||
SetGridVisible(m_GridButton->isChecked());
|
||||
}
|
||||
|
||||
void QViewport::onProjButtonClicked()
|
||||
{
|
||||
SetParallelProjection(m_ProjButton->isChecked());
|
||||
}
|
||||
|
||||
void QViewport::OnSelectionChanged(Puppet* p)
|
||||
{
|
||||
emit puppetSelected(p);
|
||||
@@ -116,6 +151,11 @@ void QViewport::resizeEvent(QResizeEvent* event)
|
||||
int y = 160;
|
||||
m_GridButton->move(x, y);
|
||||
}
|
||||
if (m_ProjButton) {
|
||||
int x = width() - m_ProjButton->width() - 10;
|
||||
int y = 210;
|
||||
m_ProjButton->move(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Vtk
|
||||
|
||||
@@ -51,12 +51,14 @@ protected:
|
||||
|
||||
private slots:
|
||||
void onGridButtonClicked();
|
||||
void onProjButtonClicked();
|
||||
|
||||
private:
|
||||
void SetupPipeline();
|
||||
|
||||
QVTKOpenGLNativeWidget* m_VtkWidget;
|
||||
QPushButton* m_GridButton;
|
||||
QPushButton* m_ProjButton;
|
||||
};
|
||||
|
||||
} // namespace Vtk
|
||||
|
||||
@@ -537,6 +537,21 @@ bool Viewport::GetGridVisible() const
|
||||
return false;
|
||||
}
|
||||
|
||||
void Viewport::SetParallelProjection(bool parallel)
|
||||
{
|
||||
if (pv->m_Renderer && pv->m_Renderer->GetActiveCamera()) {
|
||||
pv->m_Renderer->GetActiveCamera()->SetParallelProjection(parallel ? 1 : 0);
|
||||
Render();
|
||||
}
|
||||
}
|
||||
|
||||
bool Viewport::GetParallelProjection() const
|
||||
{
|
||||
if (pv->m_Renderer && pv->m_Renderer->GetActiveCamera())
|
||||
return pv->m_Renderer->GetActiveCamera()->GetParallelProjection() != 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
void Viewport::SetGridAxis(Axis axis)
|
||||
{
|
||||
m_GridAxis = axis;
|
||||
|
||||
@@ -73,6 +73,10 @@ public:
|
||||
void SetGridAxis(Axis axis);
|
||||
Axis GetGridAxis() const { return m_GridAxis; }
|
||||
|
||||
// Projection control
|
||||
void SetParallelProjection(bool parallel);
|
||||
bool GetParallelProjection() const;
|
||||
|
||||
protected:
|
||||
void SetupPipeline(vtkRenderWindowInteractor* iren);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user