11 Commits

Author SHA1 Message Date
AndreaRigoni
e09a614fa5 refactor: add override specifier to type_name method in Core/Types.h 2026-04-03 10:14:20 +00:00
AndreaRigoni
7f6323403d merge andrea-geo: geometry/material/property system features
Merges all work from andrea-geo branch:
- Geant material management classes
- Serialization enhancements (read-only, NVP/HRP macros)
- Transform/assembly system improvements
- VTK puppet/viewport updates (orthographic toggle, voxel rendering)
- Property grouping, dynamic properties, NotifyPropertiesUpdated
- Object type identification via uLibTypeMacro
- New tests (PropertyGrouping, ReadOnly, vtkQViewport, PuppetParenting)
- Various gcompose UI fixes

Conflict resolved in CMakePresets.json: kept both 'fast' (clang/lld)
and 'mutom' (stub) presets.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 08:47:32 +00:00
AndreaRigoni
a53b3051de fix EXPAT::EXPAT-NOTFOUND when building with Geant4 on conda
Geant4's G4EXPATShim creates EXPAT::EXPAT (uppercase) with
IMPORTED_LOCATION set to ${EXPAT_LIBRARY}, which is empty when EXPAT
is found via conda's config-mode package (expat::expat, lowercase).

After find_package(Geant4), patch EXPAT::EXPAT with the real library
path taken from expat::expat IMPORTED_LOCATION_NOCONFIG, falling back
to find_library if needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 08:41:00 +00:00
AndreaRigoni
c53570192f switch to Ninja+ccache, add clang/lld fast build profile
- CMakePresets.json: add 'fast' preset (clang+lld+ccache)
- .gitignore: generalize build/ to build*/, add CMakeUserPresets.json
- CMakeUserPresets.json: untrack (conan-generated, now gitignored)
- src/Core/Archives.h: remove redundant 'using basic_xml_iarchive::load_override'
  in xml_iarchive; caused ambiguous overload with clang (diamond inheritance)
- src/Core/Object.cpp: remove invalid explicit instantiations of non-template
  virtual Object::serialize (GCC extension, clang rejects)
- README.md, CLAUDE.md: document GCC and LLVM/clang build workflows

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 08:24:50 +00:00
AndreaRigoni
6396bdfebf feat: add projection toggle button to switch between perspective and orthographic views 2026-04-02 14:42:38 +00:00
AndreaRigoni
96ab3b0930 fix: restore ULIB_ACTIVATE_DISPLAY_PROPERTIES to vtkVoxImage constructor 2026-04-02 14:32:39 +00:00
AndreaRigoni
5c04d00d4c refactor: remove redundant UpdateGrid call from QViewport::Render and add Claude configuration settings 2026-04-02 14:30:31 +00:00
AndreaRigoni
72e69cfca5 test: add unit test for vtkQViewport and register in CMakeLists.txt 2026-04-02 14:27:49 +00:00
AndreaRigoni
59a9e829fc refactor: enhance vtkVoxImage volume rendering with dynamic shader range scaling, improved transfer function management, and synchronized VTK property updates. 2026-04-02 14:08:32 +00:00
AndreaRigoni
6068b62e39 refactor: replace HRP with NVP in Cylinder serialization and add stream operators for TRS 2026-04-02 11:56:25 +00:00
AndreaRigoni
4435776484 refactor: standardize object type identification using uLibTypeMacro and update serialization macros 2026-04-02 10:33:14 +00:00
51 changed files with 693 additions and 209 deletions

7
.gitignore vendored
View File

@@ -1,6 +1,7 @@
CMakeFiles/ CMakeFiles/
build/ build*/
.cache/ .cache/
CMakeUserPresets.json
build_warnings*.log build_warnings*.log
final_build.log final_build.log
cmake_configure.log cmake_configure.log
@@ -13,3 +14,7 @@ src/Python/uLib/*.pyd
src/Python/uLib/*.pyc src/Python/uLib/*.pyc
src/Python/uLib/__pycache__ src/Python/uLib/__pycache__
src/Python/uLib/.nfs* src/Python/uLib/.nfs*
test_props.xml
test_props2.xml
test_boost.cpp
.claude/settings.json

View File

@@ -12,7 +12,7 @@ export MAMBA_ROOT_PREFIX="/home/share/micromamba"
eval "$(/home/share/micromamba/bin/micromamba shell hook --shell bash)" eval "$(/home/share/micromamba/bin/micromamba shell hook --shell bash)"
micromamba activate mutom micromamba activate mutom
# Configure (from repo root, using Conan preset) # Configure (from repo root, using Conan preset — uses Ninja + ccache)
cmake --preset conan-release cmake --preset conan-release
# Build everything # Build everything
@@ -40,6 +40,18 @@ conan install . --output-folder=build --build=missing
cmake --preset conan-release 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 ## Architecture
**uLib** is a C++ framework for Cosmic Muon Tomography (CMT), structured as layered shared libraries: **uLib** is a C++ framework for Cosmic Muon Tomography (CMT), structured as layered shared libraries:

View File

@@ -38,7 +38,7 @@ endif()
# The version number. # The version number.
set(PROJECT_VERSION_MAJOR 0) set(PROJECT_VERSION_MAJOR 0)
set(PROJECT_VERSION_MINOR 6) set(PROJECT_VERSION_MINOR 7)
set(PROJECT_VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}") set(PROJECT_VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}")
set(PROJECT_SOVERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}") set(PROJECT_SOVERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}")
@@ -169,6 +169,26 @@ if(Geant4_FOUND)
add_compile_definitions(HAVE_GEANT4) add_compile_definitions(HAVE_GEANT4)
set(HAVE_GEANT4 1) 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 # Sanitize Geant4 targets to remove Qt5 dependencies that conflict with VTK/Qt6
if(TARGET Geant4::G4interfaces) if(TARGET Geant4::G4interfaces)
set_target_properties(Geant4::G4interfaces PROPERTIES set_target_properties(Geant4::G4interfaces PROPERTIES

View File

@@ -12,6 +12,22 @@
"CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}" "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", "name": "mutom",
"description": "", "description": "",

View File

@@ -1,9 +0,0 @@
{
"version": 4,
"vendor": {
"conan": {}
},
"include": [
"build/CMakePresets.json"
]
}

View File

@@ -41,7 +41,11 @@ conda activate mutom
### Configure and Build ### 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 ```bash
conan profile detect conan profile detect
``` ```
@@ -51,17 +55,39 @@ conan profile detect
conan install . --output-folder=build --build=missing conan install . --output-folder=build --build=missing
``` ```
3. **Configure the project with CMake:** 3. **Configure with CMake:**
```bash ```bash
cmake --preset conan-release 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 ```bash
cmake --build build -j10 cmake --build build -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 mutom -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 ### Make python package
```bash ```bash

View File

@@ -448,17 +448,17 @@ void PropertyEditor::setObject(::uLib::Object* obj, bool displayOnly) {
} }
} else { } else {
// Priority 2: Standard factory lookup // Priority 2: Standard factory lookup
auto it = m_Factories.find(prop->GetTypeIndex()); auto it = m_Factories.find(prop->GetTypeIndex());
if (it != m_Factories.end()) { if (it != m_Factories.end()) {
widget = it->second(prop, m_Container); widget = it->second(prop, m_Container);
} else { } else {
// Debug info for unknown types // Debug info for unknown types
std::cout << "PropertyEditor: No factory for " << prop->GetQualifiedName() std::cout << "PropertyEditor: No factory for " << prop->GetQualifiedName()
<< " (Type: " << prop->GetTypeName() << ")" << std::endl; << " (Type: " << prop->GetTypeName() << ")" << std::endl;
widget = new PropertyWidgetBase(prop, m_Container); widget = new PropertyWidgetBase(prop, m_Container);
widget->layout()->addWidget(new QLabel("(Read-only: " + QString::fromStdString(prop->GetValueAsString()) + ")")); widget->layout()->addWidget(new QLabel("(Read-only: " + QString::fromStdString(prop->GetValueAsString()) + ")"));
} }
} }
if (widget) { if (widget) {

View File

@@ -19,3 +19,76 @@ The vtkHandlerWidget should handle the transformation of the puppet internal Con
## ACTIVATE PROPERTIES
ULIB_ACTIVATE_PROPERTIES must run after all member initialization, with the vtable pointing to the most-derived type. This is why it has to be in each constructor — in C++, virtual dispatch only works correctly after a class's vtable is installed, which happens at the start of each level's constructor body.
### Option 1 — End-of-class macro (no constructor boilerplate)
Declare a private member activator as the last member of the class. Its constructor runs after all other members, and at that point the vtable is already Derived's:
// In Property.h, add alongside ULIB_ACTIVATE_PROPERTIES:
#define ULIB_DECLARE_PROPERTIES(SelfType) \
private: \
struct _PropActivator { \
_PropActivator(SelfType* self) { \
uLib::Archive::property_register_archive ar(self); \
ar & *self; \
} \
} _prop_activator{this};
Usage in ContainerBox.h — place it just before the closing brace:
class ContainerBox : public TRS {
public:
// ... all constructors, no more ULIB_ACTIVATE_PROPERTIES(*this)
ULIB_DECLARE_PROPERTIES(ContainerBox) // ← replaces all 3 constructor calls
};
Tradeoff: Works perfectly for single-level classes. For hierarchies where multiple levels use the macro, RegisterDynamicProperty must deduplicate by name (skip if already registered). Requires one line per class in the class body, but zero lines in constructors.
### Option 2 — Lazy init via virtual InitProperties() in Object
Modify Object to call a virtual hook on first GetProperties():
// In Object.h:
class Object {
protected:
virtual void InitProperties() {} // override in derived
public:
const std::vector<PropertyBase*>& GetProperties() const {
if (!m_propertiesInitialized) {
const_cast<Object*>(this)->m_propertiesInitialized = true;
const_cast<Object*>(this)->InitProperties();
}
return m_properties;
}
};
Then a CRTP base handles the rest without any macro:
template<typename Derived>
class PropertyObject : public Object {
protected:
void InitProperties() override {
uLib::Archive::property_register_archive ar(this);
ar & *static_cast<Derived*>(this);
}
};
Usage — just change the base class:
class ContainerBox : public PropertyObject<ContainerBox>, public TRS { ... };
// Nothing else needed — properties activated on first GetProperties() call
Tradeoff: Most "automatic" — pure inheritance, no constructor or class-body macros. But requires modifying Object (adding m_propertiesInitialized flag + virtual hook), and lazy init means properties aren't available until first access. Also doesn't work well with multiple inheritance (which TRS likely involves).
Option 3 — CRTP doesn't work from the base constructor
Just to be explicit: a CRTP base that calls ULIB_ACTIVATE_PROPERTIES in its own constructor won't work, because when PropertyObject<Derived>'s constructor runs, the vtable is PropertyObject<Derived>'s — Derived::serialize() hasn't been installed yet. So ar & *self calls Object::serialize() (a no-op).
Recommendation
Option 1 is the least invasive and safest. Add deduplication to RegisterDynamicProperty in Object.cpp to guard against re-registration when hierarchies stack activators, then replace every ULIB_ACTIVATE_PROPERTIES(*this) in constructors with a single ULIB_DECLARE_PROPERTIES(ClassName) at the end of the class body.
Option 2 is cleaner to use but requires changing the Object interface and has the lazy-init semantic change — only worth it if you want zero-touch activation across the entire framework.

View File

@@ -28,6 +28,8 @@
#include <boost/archive/detail/basic_pointer_iserializer.hpp> #include <boost/archive/detail/basic_pointer_iserializer.hpp>
#include <boost/archive/detail/basic_pointer_oserializer.hpp> #include <boost/archive/detail/basic_pointer_oserializer.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <cstring>
#include <iostream> #include <iostream>
#include <boost/archive/text_iarchive.hpp> #include <boost/archive/text_iarchive.hpp>
@@ -309,20 +311,32 @@ namespace Archive {
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// XML // // XML //
// ULIB_SERIALIZATION_VERSION should be get from the build system
#ifndef ULIB_SERIALIZATION_VERSION
#define ULIB_SERIALIZATION_VERSION "0.0"
#endif
class xml_iarchive : public boost::archive::xml_iarchive_impl<xml_iarchive> { class xml_iarchive : public boost::archive::xml_iarchive_impl<xml_iarchive> {
typedef xml_iarchive Archive; typedef xml_iarchive Archive;
typedef boost::archive::xml_iarchive_impl<Archive> base; typedef boost::archive::xml_iarchive_impl<Archive> base;
unsigned int m_flags;
// give serialization implementation access to this class // give serialization implementation access to this class
friend class boost::archive::detail::interface_iarchive<Archive>; friend class boost::archive::detail::interface_iarchive<Archive>;
friend class boost::archive::basic_xml_iarchive<Archive>; friend class boost::archive::basic_xml_iarchive<Archive>;
friend class boost::archive::load_access; friend class boost::archive::load_access;
public: public:
xml_iarchive(std::istream &is, unsigned int flags = 0) xml_iarchive(std::istream &is, unsigned int flags = 0)
: xml_iarchive_impl<xml_iarchive>(is, flags) {} : boost::archive::xml_iarchive_impl<xml_iarchive>(
is, flags | boost::archive::no_header), m_flags(flags) {
using basic_xml_iarchive::load_override; if (0 == (flags & boost::archive::no_header)) {
std::string line;
std::getline(is, line); // <?xml ... ?>
std::getline(is, line); // <!DOCTYPE ...>
std::getline(is, line); // <ulib_serialization ...>
}
}
// Anything not an attribute should be a name value pair as nvp or hrp // Anything not an attribute should be a name value pair as nvp or hrp
typedef boost::archive::detail::common_iarchive<Archive> typedef boost::archive::detail::common_iarchive<Archive>
@@ -341,6 +355,9 @@ public:
// class_name_type can't be handled here as it depends upon the // class_name_type can't be handled here as it depends upon the
// char type used by the stream. So require the derived implementation. // char type used by the stream. So require the derived implementation.
// derived in this case is xml_iarchive_impl or base .. // 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; using base::load_override;
void load_override(const char *str) { void load_override(const char *str) {
@@ -368,14 +385,31 @@ class xml_oarchive : public boost::archive::xml_oarchive_impl<xml_oarchive> {
typedef xml_oarchive Archive; typedef xml_oarchive Archive;
typedef boost::archive::xml_oarchive_impl<Archive> base; typedef boost::archive::xml_oarchive_impl<Archive> base;
unsigned int m_flags;
// give serialization implementation access to this class // give serialization implementation access to this class
friend class boost::archive::detail::interface_oarchive<Archive>; friend class boost::archive::detail::interface_oarchive<Archive>;
friend class boost::archive::basic_xml_oarchive<Archive>; friend class boost::archive::basic_xml_oarchive<Archive>;
friend class boost::archive::save_access; friend class boost::archive::save_access;
public: public:
xml_oarchive(std::ostream &os, unsigned int flags = 0) xml_oarchive(std::ostream &os, unsigned int flags = 0)
: boost::archive::xml_oarchive_impl<xml_oarchive>(os, flags) {} : boost::archive::xml_oarchive_impl<xml_oarchive>(
os, flags | boost::archive::no_header), m_flags(flags) {
if (0 == (flags & boost::archive::no_header)) {
this->This()->put(
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\" ?>\n");
this->This()->put("<!DOCTYPE ulib_serialization>\n");
this->This()->put("<ulib_serialization signature=\"serialization::archive\" ");
this->write_attribute("version", (const char *)ULIB_SERIALIZATION_VERSION);
this->This()->put(">\n");
}
}
virtual ~xml_oarchive() {
if (0 == (m_flags & boost::archive::no_header)) {
this->This()->put("</ulib_serialization>\n");
}
}
using basic_xml_oarchive::save_override; using basic_xml_oarchive::save_override;
@@ -397,8 +431,6 @@ public:
// Do not save any human decoration string // // Do not save any human decoration string //
// basic_text_oprimitive::save(str); // basic_text_oprimitive::save(str);
} }
virtual ~xml_oarchive() {}
}; };
// typedef boost::archive::detail::polymorphic_oarchive_route< // typedef boost::archive::detail::polymorphic_oarchive_route<

View File

@@ -58,6 +58,7 @@ if(USE_CUDA)
endif() endif()
target_link_libraries(${libname} ${LIBRARIES}) target_link_libraries(${libname} ${LIBRARIES})
target_compile_definitions(${libname} PUBLIC ULIB_SERIALIZATION_VERSION="${PROJECT_VERSION}")
install(TARGETS ${libname} install(TARGETS ${libname}
EXPORT "uLibTargets" EXPORT "uLibTargets"

View File

@@ -79,6 +79,7 @@ void Object::RegisterDynamicProperty(PropertyBase* prop) {
if (prop) { if (prop) {
for (auto* existing : d->m_DynamicProperties) { for (auto* existing : d->m_DynamicProperties) {
if (existing == prop) return; if (existing == prop) return;
if (existing->GetQualifiedName() == prop->GetQualifiedName()) return;
} }
d->m_DynamicProperties.push_back(prop); d->m_DynamicProperties.push_back(prop);
} }
@@ -120,14 +121,6 @@ void Object::PropertyUpdated() { ULIB_SIGNAL_EMIT(Object::PropertyUpdated); }
template <class ArchiveT> template <class ArchiveT>
void Object::save_override(ArchiveT &ar, const unsigned int version) {} 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);
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////

View File

@@ -78,7 +78,8 @@ public:
Object(const Object &copy); Object(const Object &copy);
virtual ~Object(); virtual ~Object();
virtual const char * GetClassName() const { return "Object"; } virtual const char * GetClassName() const { return type_name(); }
virtual const char * type_name() const { return "Object"; }
const std::string& GetInstanceName() const; const std::string& GetInstanceName() const;
void SetInstanceName(const std::string& name); void SetInstanceName(const std::string& name);

View File

@@ -14,7 +14,7 @@ public:
ObjectsContext(); ObjectsContext();
virtual ~ObjectsContext(); virtual ~ObjectsContext();
virtual const char * GetClassName() const { return "ObjectsContext"; } uLibTypeMacro(ObjectsContext, Object)
virtual ObjectsContext* GetChildren() override { return this; } virtual ObjectsContext* GetChildren() override { return this; }
/** /**

View File

@@ -54,13 +54,13 @@ public:
virtual void Updated() override { ULIB_SIGNAL_EMIT(PropertyBase::Updated); } virtual void Updated() override { ULIB_SIGNAL_EMIT(PropertyBase::Updated); }
// Serialization support for different uLib archives // Serialization support for different uLib archives
virtual void serialize(Archive::xml_oarchive & ar, const unsigned int version) = 0; virtual void serialize(Archive::xml_oarchive & ar, const unsigned int version) override = 0;
virtual void serialize(Archive::xml_iarchive & ar, const unsigned int version) = 0; virtual void serialize(Archive::xml_iarchive & ar, const unsigned int version) override = 0;
virtual void serialize(Archive::text_oarchive & ar, const unsigned int version) = 0; virtual void serialize(Archive::text_oarchive & ar, const unsigned int version) override = 0;
virtual void serialize(Archive::text_iarchive & ar, const unsigned int version) = 0; virtual void serialize(Archive::text_iarchive & ar, const unsigned int version) override = 0;
virtual void serialize(Archive::hrt_oarchive & ar, const unsigned int version) = 0; virtual void serialize(Archive::hrt_oarchive & ar, const unsigned int version) override = 0;
virtual void serialize(Archive::hrt_iarchive & ar, const unsigned int version) = 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) = 0; virtual void serialize(Archive::log_archive & ar, const unsigned int version) override = 0;
}; };
@@ -413,6 +413,26 @@ private:
#define ULIB_ACTIVATE_PROPERTIES(obj) \ #define ULIB_ACTIVATE_PROPERTIES(obj) \
{ uLib::Archive::property_register_archive _ar_tmp(&(obj)); _ar_tmp & (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 { \
_PropActivator(SelfType* self) { \
uLib::Archive::property_register_archive _ar(self); \
_ar & *self; \
} \
} _prop_activator{this};
} // namespace Archive } // namespace Archive
} // namespace uLib } // namespace uLib

View File

@@ -309,6 +309,8 @@ namespace uLib {
#define HRP5(name, data, units, min, max) boost::serialization::make_hrp(name, data, units).range(min, max) #define HRP5(name, data, units, min, max) boost::serialization::make_hrp(name, data, units).range(min, max)
#define HRP6(name, data, units, default, min, max) boost::serialization::make_hrp(name, data, units).set_default(default).range(min, max) #define HRP6(name, data, units, default, min, max) boost::serialization::make_hrp(name, data, units).set_default(default).range(min, max)
#define HRPE(name, data, labels) boost::serialization::make_hrp_enum(name, data, labels)
// LEFT FOR BACKWARD COMPATIBILITY // LEFT FOR BACKWARD COMPATIBILITY
#define HRPU(name, units) boost::serialization::make_hrp(BOOST_PP_STRINGIZE(name), name, units) #define HRPU(name, units) boost::serialization::make_hrp(BOOST_PP_STRINGIZE(name), name, units)
@@ -349,7 +351,7 @@ using boost::serialization::make_hrp_enum;
#define ULIB_SERIALIZE_OBJECT(_Ob, ...) \ #define ULIB_SERIALIZE_OBJECT(_Ob, ...) \
_ULIB_DETAIL_UNINTRUSIVE_SERIALIZE_OBJECT(_Ob, __VA_ARGS__) _ULIB_DETAIL_UNINTRUSIVE_SERIALIZE_OBJECT(_Ob, __VA_ARGS__)
#define AR(_name) _ULIB_DETAIL_UNINTRUSIVE_AR_(_name) #define AR(_name) _ULIB_DETAIL_UNINTRUSIVE_AR_(_name)
#define HR(_name) _ULIB_DETAIL_UNINTRUSIVE_AR_(_name) #define HR(_name) _ULIB_DETAIL_UNINTRUSIVE_HR_(_name)
#endif #endif
#define ULIB_SERIALIZE_ACCESS \ #define ULIB_SERIALIZE_ACCESS \
@@ -362,14 +364,14 @@ using boost::serialization::make_hrp_enum;
#define ULIB_CLASS_EXPORT_OBJECT_KEY(_FullNamespaceClass) \ #define ULIB_CLASS_EXPORT_OBJECT_KEY(_FullNamespaceClass) \
BOOST_CLASS_EXPORT_KEY(_FullNamespaceClass) BOOST_CLASS_EXPORT_KEY(_FullNamespaceClass)
#define _SERIALIZE_IMPL_SEQ \ #define _SERIALIZE_IMPL_SEQ \
(uLib::Archive::text_iarchive)(uLib::Archive::text_oarchive)( \ (uLib::Archive::text_iarchive) \
uLib::Archive:: \ (uLib::Archive::text_oarchive) \
hrt_iarchive)(uLib::Archive:: \ (uLib::Archive::hrt_iarchive) \
hrt_oarchive)(uLib::Archive:: \ (uLib::Archive::hrt_oarchive) \
xml_iarchive)(uLib::Archive:: \ (uLib::Archive::xml_iarchive) \
xml_oarchive)(uLib::Archive:: \ (uLib::Archive::xml_oarchive) \
log_archive) (uLib::Archive::log_archive)
/** Solving virtual class check problem */ /** Solving virtual class check problem */
#define _ULIB_DETAIL_SPECIALIZE_IS_VIRTUAL_BASE(_Base, _Derived) \ #define _ULIB_DETAIL_SPECIALIZE_IS_VIRTUAL_BASE(_Base, _Derived) \
@@ -549,7 +551,8 @@ using boost::serialization::make_hrp_enum;
void serialize_parents(ArchiveT &ar, _Ob &ob, const unsigned int v) { \ void serialize_parents(ArchiveT &ar, _Ob &ob, const unsigned int v) { \
/* PP serialize */ BOOST_PP_SEQ_FOR_EACH( \ /* PP serialize */ BOOST_PP_SEQ_FOR_EACH( \
_UNAR_OP, ob, BOOST_PP_TUPLE_TO_SEQ((__VA_ARGS__))); \ _UNAR_OP, ob, BOOST_PP_TUPLE_TO_SEQ((__VA_ARGS__))); \
/* MPL serialize */ /*uLib::mpl::for_each<_Ob::BaseList>(uLib::detail::Serializable::serialize_baseobject<_Ob,ArchiveT>(ob,ar) );*/ } \ /* MPL serialize */ /*uLib::mpl::for_each<_Ob::BaseList> \
(uLib::detail::Serializable::serialize_baseobject<_Ob,ArchiveT>(ob,ar) );*/ }\
template <class ArchiveT> \ template <class ArchiveT> \
inline void load_construct_data(ArchiveT &ar, _Ob *ob, \ inline void load_construct_data(ArchiveT &ar, _Ob *ob, \
const unsigned int file_version) { \ const unsigned int file_version) { \
@@ -574,8 +577,16 @@ using boost::serialization::make_hrp_enum;
void boost::serialization::access2<_Ob>::save_override( \ void boost::serialization::access2<_Ob>::save_override( \
ArchiveT &ar, _Ob &ob, const unsigned int version) ArchiveT &ar, _Ob &ob, const unsigned int version)
#define _ULIB_DETAIL_UNINTRUSIVE_AR_(name) \ #define _ULIB_DETAIL_UNINTRUSIVE_AR_(name) \
boost::serialization::make_nvp(BOOST_PP_STRINGIZE(name), ob.name) boost::serialization::make_nvp(BOOST_PP_STRINGIZE(name), ob.name)
#define _ULIB_DETAIL_UNINTRUSIVE_HR_(name) \
boost::serialization::make_hrp(BOOST_PP_STRINGIZE(name), ob.name)
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////

View File

@@ -182,7 +182,7 @@ typedef bool Bool_t; // Boolean (0=false, 1=true) (bool)
\ \
public: \ public: \
typedef type_info::BaseClass BaseClass; \ 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; } \
/**/ /**/
/** /**

View File

@@ -76,8 +76,9 @@ public:
ULIB_SERIALIZABLE_OBJECT(TestObject2) ULIB_SERIALIZABLE_OBJECT(TestObject2)
ULIB_SERIALIZE_OBJECT(TestObject2, TestObject) { ULIB_SERIALIZE_OBJECT(TestObject2, TestObject) {
// std::cout << "Serializing TestObject2" << std::endl; std::cout << "Serializing TestObject2" << std::endl;
ar & boost::serialization::make_hrp("value2", ob.m_Value2, "mm").set_default(1.); // ar & boost::serialization::make_hrp("value2", ob.m_Value2, "mm").set_default(1.);
ar & HRP("value2", ob.m_Value2, "mm").set_default(1.);
} }

View File

@@ -8,13 +8,12 @@ using namespace uLib;
class TestObject : public Object { class TestObject : public Object {
public: public:
uLibTypeMacro(TestObject, Object)
TestObject() : Object(), TestObject() : Object(),
IntProp(this, "IntProp", 10), IntProp(this, "IntProp", 10),
StringProp(this, "StringProp", "Initial") StringProp(this, "StringProp", "Initial")
{} {}
virtual const char* GetClassName() const override { return "TestObject"; }
Property<int> IntProp; Property<int> IntProp;
Property<std::string> StringProp; Property<std::string> StringProp;
}; };

View File

@@ -9,10 +9,9 @@ using namespace uLib;
class TestObject : public Object { class TestObject : public Object {
public: public:
uLibTypeMacro(TestObject, Object)
TestObject() : Object() {} TestObject() : Object() {}
virtual const char* GetClassName() const override { return "TestObject"; }
// Use new typedefs // Use new typedefs
StringProperty StringProp = StringProperty(this, "StringProp", "Initial"); StringProperty StringProp = StringProperty(this, "StringProp", "Initial");
IntProperty IntProp = IntProperty(this, "IntProp", 42); IntProperty IntProp = IntProperty(this, "IntProp", 42);

View File

@@ -40,12 +40,10 @@ namespace uLib {
class DetectorChamber : public ContainerBox { class DetectorChamber : public ContainerBox {
typedef ContainerBox BaseClass;
public: public:
uLibTypeMacro(DetectorChamber, ContainerBox)
virtual const char * GetClassName() const { return "DetectorChamber"; }
DetectorChamber() : BaseClass() { DetectorChamber() : BaseClass() {
m_ProjectionPlane.origin = HPoint3f(0, 0, 0); m_ProjectionPlane.origin = HPoint3f(0, 0, 0);

View File

@@ -26,8 +26,7 @@ namespace Geant {
class EmitterPrimary : public G4VUserPrimaryGeneratorAction, public AffineTransform class EmitterPrimary : public G4VUserPrimaryGeneratorAction, public AffineTransform
{ {
public: public:
uLibTypeMacro(EmitterPrimary, Object)
virtual const char* GetClassName() const override { return "Geant.EmitterPrimary"; }
EmitterPrimary(); EmitterPrimary();
virtual ~EmitterPrimary(); virtual ~EmitterPrimary();
@@ -47,8 +46,7 @@ class EmitterPrimary : public G4VUserPrimaryGeneratorAction, public AffineTransf
class SkyPlaneEmitterPrimary : public EmitterPrimary class SkyPlaneEmitterPrimary : public EmitterPrimary
{ {
public: public:
uLibTypeMacro(SkyPlaneEmitterPrimary, EmitterPrimary)
virtual const char* GetClassName() const override { return "Geant.SkyPlaneEmitterPrimary"; }
SkyPlaneEmitterPrimary(); SkyPlaneEmitterPrimary();
virtual ~SkyPlaneEmitterPrimary(); virtual ~SkyPlaneEmitterPrimary();
@@ -69,8 +67,7 @@ class SkyPlaneEmitterPrimary : public EmitterPrimary
class CylinderEmitterPrimary : public EmitterPrimary class CylinderEmitterPrimary : public EmitterPrimary
{ {
public: public:
uLibTypeMacro(CylinderEmitterPrimary, EmitterPrimary)
virtual const char* GetClassName() const override { return "Geant.CylinderEmitterPrimary"; }
CylinderEmitterPrimary(); CylinderEmitterPrimary();
virtual ~CylinderEmitterPrimary(); virtual ~CylinderEmitterPrimary();
@@ -98,8 +95,7 @@ class CylinderEmitterPrimary : public EmitterPrimary
class QuadMeshEmitterPrimary : public EmitterPrimary class QuadMeshEmitterPrimary : public EmitterPrimary
{ {
public: public:
uLibTypeMacro(QuadMeshEmitterPrimary, EmitterPrimary)
virtual const char* GetClassName() const override { return "Geant.QuadMeshEmitterPrimary"; }
QuadMeshEmitterPrimary(); QuadMeshEmitterPrimary();
virtual ~QuadMeshEmitterPrimary(); virtual ~QuadMeshEmitterPrimary();

View File

@@ -50,8 +50,7 @@ class SteppingAction;
class GeantEvent : public Object { class GeantEvent : public Object {
public: public:
uLibTypeMacro(GeantEvent, Object)
virtual const char* GetClassName() const override { return "Geant.GeantEvent"; }
/// A single interaction step along the muon path. /// A single interaction step along the muon path.
struct Delta { struct Delta {

View File

@@ -60,6 +60,7 @@ private:
class Material : public Object { class Material : public Object {
public: public:
uLibTypeMacro(Material, Object)
enum State { enum State {
Undefined = 0, Undefined = 0,
@@ -68,8 +69,6 @@ public:
Gas Gas
}; };
virtual const char* GetClassName() const override { return "Geant.Material"; }
Material(); Material();
Material(const char *name); Material(const char *name);
~Material(); ~Material();

View File

@@ -43,8 +43,7 @@ class EmitterPrimary;
class Scene : public Object { class Scene : public Object {
public: public:
uLibTypeMacro(Scene, Object)
virtual const char* GetClassName() const override { return "Geant.Scene"; }
Scene(); Scene();
~Scene(); ~Scene();

View File

@@ -43,8 +43,7 @@ namespace Geant {
class Solid : public Object { class Solid : public Object {
public: public:
uLibTypeMacro(Solid, Object)
virtual const char* GetClassName() const override { return "Geant.Solid"; }
Solid(); Solid();
Solid(const char *name); Solid(const char *name);
@@ -93,10 +92,8 @@ protected:
class TessellatedSolid : public Solid { class TessellatedSolid : public Solid {
typedef Solid BaseClass;
public: public:
uLibTypeMacro(TessellatedSolid, Solid)
virtual const char* GetClassName() const override { return "Geant.TessellatedSolid"; }
TessellatedSolid(); TessellatedSolid();
TessellatedSolid(const char *name); TessellatedSolid(const char *name);
@@ -120,11 +117,9 @@ private :
class BoxSolid : public Solid { class BoxSolid : public Solid {
typedef Solid BaseClass;
public: public:
uLibTypeMacro(BoxSolid, Solid)
virtual const char* GetClassName() const override { return "Geant.BoxSolid"; }
BoxSolid(const char *name = ""); BoxSolid(const char *name = "");
BoxSolid(const char *name, ContainerBox *box); BoxSolid(const char *name, ContainerBox *box);

View File

@@ -26,7 +26,6 @@ Assembly::Assembly()
m_BBoxMax(Vector3f::Zero()), m_BBoxMax(Vector3f::Zero()),
m_ShowBoundingBox(false), m_ShowBoundingBox(false),
m_GroupSelection(true) { m_GroupSelection(true) {
ULIB_ACTIVATE_PROPERTIES(*this);
} }
Assembly::Assembly(const Assembly &copy) Assembly::Assembly(const Assembly &copy)

View File

@@ -46,7 +46,7 @@ namespace uLib {
class Assembly : public ObjectsContext, public TRS { class Assembly : public ObjectsContext, public TRS {
public: public:
uLibTypeMacro(Assembly, ObjectsContext, TRS) uLibTypeMacro(Assembly, ObjectsContext, TRS)
virtual const char *GetClassName() const override { return "Assembly"; }
Assembly(); Assembly();
Assembly(const Assembly &copy); Assembly(const Assembly &copy);
@@ -113,6 +113,8 @@ private:
bool m_GroupSelection; bool m_GroupSelection;
bool m_InUpdated = false; bool m_InUpdated = false;
std::map<Object*, Connection> m_ChildConnections; std::map<Object*, Connection> m_ChildConnections;
ULIB_DECLARE_PROPERTIES(Assembly)
}; };
} // namespace uLib } // namespace uLib

View File

@@ -29,6 +29,7 @@
#include "Geometry.h" #include "Geometry.h"
#include "Core/Object.h" #include "Core/Object.h"
#include "Core/Property.h" #include "Core/Property.h"
#include "Core/Serializable.h"
#include "Math/Dense.h" #include "Math/Dense.h"
#include "Math/Transform.h" #include "Math/Transform.h"
#include <utility> #include <utility>
@@ -48,16 +49,11 @@ namespace uLib {
*/ */
class ContainerBox : public TRS { class ContainerBox : public TRS {
public:
uLibTypeMacro(ContainerBox, TRS) uLibTypeMacro(ContainerBox, TRS)
ULIB_SERIALIZE_ACCESS
ULIB_DECLARE_PROPERTIES(ContainerBox)
virtual const char * GetClassName() const override { return "ContainerBox"; } public:
////////////////////////////////////////////////////////////////////////////
// PROPERTIES //
Vector3f Size;
Vector3f Origin;
/** /**
* @brief Default constructor. * @brief Default constructor.
@@ -67,7 +63,6 @@ public:
: m_LocalT(this), // BaseClass is Parent of m_LocalTransform : m_LocalT(this), // BaseClass is Parent of m_LocalTransform
Size(1.0f, 1.0f, 1.0f), Size(1.0f, 1.0f, 1.0f),
Origin(0.0f, 0.0f, 0.0f) { Origin(0.0f, 0.0f, 0.0f) {
ULIB_ACTIVATE_PROPERTIES(*this);
this->Sync(); this->Sync();
} }
@@ -79,7 +74,6 @@ public:
: m_LocalT(this), : m_LocalT(this),
Size(size), Size(size),
Origin(0.0f, 0.0f, 0.0f) { Origin(0.0f, 0.0f, 0.0f) {
ULIB_ACTIVATE_PROPERTIES(*this);
this->Sync(); this->Sync();
} }
@@ -92,13 +86,12 @@ public:
TRS(copy), TRS(copy),
Size(copy.Size), Size(copy.Size),
Origin(copy.Origin) { Origin(copy.Origin) {
ULIB_ACTIVATE_PROPERTIES(*this);
this->Sync(); this->Sync();
} }
/** // /**
* @brief Serialization template for property registration and persistence. // * @brief Serialization template for property registration and persistence.
*/ // */
template <class ArchiveT> template <class ArchiveT>
void serialize(ArchiveT & ar, const unsigned int version) { void serialize(ArchiveT & ar, const unsigned int version) {
ar & HRP(Size); ar & HRP(Size);
@@ -236,9 +229,13 @@ private:
private: private:
Vector3f Size;
Vector3f Origin;
AffineTransform m_LocalT; AffineTransform m_LocalT;
}; };
} // namespace uLib } // namespace uLib
#endif // CONTAINERBOX_H #endif // CONTAINERBOX_H

View File

@@ -41,8 +41,10 @@ namespace uLib {
*/ */
class Cylinder : public TRS { class Cylinder : public TRS {
public:
uLibTypeMacro(Cylinder, TRS) uLibTypeMacro(Cylinder, TRS)
ULIB_DECLARE_PROPERTIES(Cylinder)
public:
/** /**
* @brief PROPERTIES * @brief PROPERTIES
@@ -51,13 +53,12 @@ public:
float Height; float Height;
int Axis; int Axis;
virtual const char * GetClassName() const override { return "Cylinder"; }
/** /**
* @brief Default constructor. Aligns with Y by default. * @brief Default constructor. Aligns with Y by default.
*/ */
Cylinder() : m_LocalT(this), Radius(1.0), Height(1.0), Axis(1) { Cylinder() : m_LocalT(this), Radius(1.0), Height(1.0), Axis(1) {
ULIB_ACTIVATE_PROPERTIES(*this);
this->Sync(); this->Sync();
} }
@@ -66,7 +67,6 @@ public:
*/ */
Cylinder(float radius, float height, int axis = 1) Cylinder(float radius, float height, int axis = 1)
: m_LocalT(this), Radius(radius), Height(height), Axis(axis) { : m_LocalT(this), Radius(radius), Height(height), Axis(axis) {
ULIB_ACTIVATE_PROPERTIES(*this);
this->Sync(); this->Sync();
} }
@@ -75,7 +75,6 @@ public:
*/ */
Cylinder(const Cylinder &copy) Cylinder(const Cylinder &copy)
: m_LocalT(this), TRS(copy), Radius(copy.Radius), Height(copy.Height), Axis(copy.Axis) { : m_LocalT(this), TRS(copy), Radius(copy.Radius), Height(copy.Height), Axis(copy.Axis) {
ULIB_ACTIVATE_PROPERTIES(*this);
this->Sync(); this->Sync();
} }
@@ -84,10 +83,10 @@ public:
*/ */
template <class ArchiveT> template <class ArchiveT>
void serialize(ArchiveT & ar, const unsigned int version) { void serialize(ArchiveT & ar, const unsigned int version) {
ar & boost::serialization::make_nvp("TRS", boost::serialization::base_object<TRS>(*this));
ar & HRP(Radius); ar & HRP(Radius);
ar & HRP(Height); ar & HRP(Height);
ar & HRP(Axis); ar & boost::serialization::make_hrp_enum("Axis", Axis, {"X", "Y", "Z"});
ar & NVP("TRS", boost::serialization::base_object<TRS>(*this));
} }
/** Sets the radius of the cylinder */ /** Sets the radius of the cylinder */

View File

@@ -43,7 +43,7 @@ protected:
public: public:
uLibTypeMacro(Geometry, Object) uLibTypeMacro(Geometry, Object)
virtual const char * GetClassName() const override { return "Geometry"; }
virtual void SetParent(Geometry* p) { m_Parent = p; } virtual void SetParent(Geometry* p) { m_Parent = p; }
virtual Geometry* GetParent() const { return m_Parent; } virtual Geometry* GetParent() const { return m_Parent; }
@@ -93,7 +93,7 @@ protected:
public: public:
uLibTypeMacro(LinearGeometry, Geometry) uLibTypeMacro(LinearGeometry, Geometry)
virtual const char * GetClassName() const override { return "LinearGeometry"; }
virtual bool IsLinear() const override { return true; } virtual bool IsLinear() const override { return true; }
virtual bool IsPure() const override { return true; } virtual bool IsPure() const override { return true; }
@@ -162,7 +162,7 @@ public:
uLibTypeMacro(CylindricalGeometry, LinearGeometry) uLibTypeMacro(CylindricalGeometry, LinearGeometry)
CylindricalGeometry() {} CylindricalGeometry() {}
virtual const char * GetClassName() const override { return "CylindricalGeometry"; }
virtual bool IsPure() const override { return false; } virtual bool IsPure() const override { return false; }
@@ -185,7 +185,7 @@ public:
uLibTypeMacro(SphericalGeometry, LinearGeometry) uLibTypeMacro(SphericalGeometry, LinearGeometry)
SphericalGeometry() {} SphericalGeometry() {}
virtual const char * GetClassName() const override { return "SphericalGeometry"; }
virtual bool IsPure() const override { return false; } virtual bool IsPure() const override { return false; }
@@ -212,7 +212,7 @@ public:
uLibTypeMacro(ToroidalGeometry, LinearGeometry) uLibTypeMacro(ToroidalGeometry, LinearGeometry)
ToroidalGeometry(float Rtor) : m_Rtor(Rtor) {} ToroidalGeometry(float Rtor) : m_Rtor(Rtor) {}
virtual const char * GetClassName() const override { return "ToroidalGeometry"; }
virtual bool IsPure() const override { return false; } virtual bool IsPure() const override { return false; }

View File

@@ -36,7 +36,7 @@ class Polydata : public Object {
public: public:
virtual const char * GetClassName() const { return "Polydata"; }

View File

@@ -39,7 +39,7 @@ class QuadMesh : public TRS
public: public:
uLibTypeMacro(QuadMesh, TRS) uLibTypeMacro(QuadMesh, TRS)
virtual const char * GetClassName() const override { return "QuadMesh"; }
void PrintSelf(std::ostream &o); void PrintSelf(std::ostream &o);

View File

@@ -189,8 +189,11 @@ typedef Eigen::Affine3f AffineMatrix;
class TRS : public AffineTransform { class TRS : public AffineTransform {
public:
uLibTypeMacro(TRS, AffineTransform) uLibTypeMacro(TRS, AffineTransform)
ULIB_SERIALIZE_ACCESS
// ULIB_DECLARE_PROPERTIES(TRS)
public:
Vector3f position = Vector3f::Zero(); Vector3f position = Vector3f::Zero();
Vector3f rotation = Vector3f::Zero(); Vector3f rotation = Vector3f::Zero();
@@ -260,6 +263,7 @@ public:
ar & HRP(scaling); ar & HRP(scaling);
} }
AffineMatrix GetAffineMatrix() const { AffineMatrix GetAffineMatrix() const {
AffineMatrix m = AffineMatrix::Identity(); AffineMatrix m = AffineMatrix::Identity();
m.translate(position); m.translate(position);
@@ -273,12 +277,26 @@ public:
Matrix4f GetMatrix() const { Matrix4f GetMatrix() const {
return this->GetAffineMatrix().matrix(); return this->GetAffineMatrix().matrix();
} }
}; };
inline std::ostream& operator<<(std::ostream& os, const TRS& trs) {
os << trs.position << " " << trs.rotation << " " << trs.scaling;
return os;
}
inline std::istream& operator>>(std::istream& is, TRS& trs) {
is >> trs.position >> trs.rotation >> trs.scaling;
return is;
}
} // uLib } // uLib

View File

@@ -42,7 +42,7 @@ class TriangleMesh : public TRS
public: public:
uLibTypeMacro(TriangleMesh, TRS) uLibTypeMacro(TriangleMesh, TRS)
virtual const char * GetClassName() const override { return "TriangleMesh"; }
void PrintSelf(std::ostream &o); void PrintSelf(std::ostream &o);

View File

@@ -47,7 +47,7 @@ namespace Abstract {
class VoxImage : public uLib::StructuredGrid { class VoxImage : public uLib::StructuredGrid {
public: public:
virtual const char * GetClassName() const { return "VoxImage"; }
typedef uLib::StructuredGrid BaseClass; typedef uLib::StructuredGrid BaseClass;

View File

@@ -61,7 +61,7 @@ class VoxImageFilter : public Abstract::VoxImageFilter, public Object {
public: public:
virtual const char * GetClassName() const { return "VoxImageFilter"; }
VoxImageFilter(const Vector3i &size); VoxImageFilter(const Vector3i &size);

View File

@@ -36,7 +36,7 @@ class vtkObjectsContext; // forward
*/ */
class Assembly : public Puppet { class Assembly : public Puppet {
public: public:
virtual const char *GetClassName() const override { return "Vtk.Assembly"; } uLibTypeMacro(Assembly, Puppet)
Assembly(uLib::Assembly *content); Assembly(uLib::Assembly *content);
virtual ~Assembly(); virtual ~Assembly();

View File

@@ -111,17 +111,17 @@ void vtkContainerBox::SyncFromVtk() {
// VTK -> Model: Extract new world TRS from proxy, which matches the model's TRS center // VTK -> Model: Extract new world TRS from proxy, which matches the model's TRS center
vtkMatrix4x4* rootMat = root->GetUserMatrix(); vtkMatrix4x4* rootMat = root->GetUserMatrix();
if (rootMat) { // if (rootMat) {
std::cout << "[vtkContainerBox::SyncFromVtk] Read Proxy UserMatrix:" << std::endl; // std::cout << "[vtkContainerBox::SyncFromVtk] Read Proxy UserMatrix:" << std::endl;
rootMat->Print(std::cout); // rootMat->Print(std::cout);
} // }
Matrix4f vtkWorld = VtkToMatrix4f(rootMat); Matrix4f vtkWorld = VtkToMatrix4f(rootMat);
// Synchronize TRS property members from the updated local matrix // Synchronize TRS property members from the updated local matrix
m_Content->FromMatrix(vtkWorld); m_Content->FromMatrix(vtkWorld);
std::cout << "[vtkContainerBox::SyncFromVtk] New Model WorldMatrix:" << std::endl << m_Content->GetWorldMatrix() << std::endl; // 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 // Since we modified the model, notify observers, but block the loop back to VTK
// ConnectionBlock blocker(d->m_UpdateSignal); // ConnectionBlock blocker(d->m_UpdateSignal);

View File

@@ -41,6 +41,7 @@
#include <vtkPiecewiseFunction.h> #include <vtkPiecewiseFunction.h>
#include <vtkSmartVolumeMapper.h> #include <vtkSmartVolumeMapper.h>
#include <vtkVolumeProperty.h> #include <vtkVolumeProperty.h>
#include <vtkMatrix4x4.h>
#include <vtkActor.h> #include <vtkActor.h>
#include <vtkPolyDataMapper.h> #include <vtkPolyDataMapper.h>
@@ -49,6 +50,7 @@
#include <Math/VoxImage.h> #include <Math/VoxImage.h>
#include "Vtk/Math/vtkVoxImage.h" #include "Vtk/Math/vtkVoxImage.h"
#include "Vtk/Math/vtkDense.h"
#include <vtkAutoInit.h> #include <vtkAutoInit.h>
VTK_MODULE_INIT(vtkRenderingVolumeOpenGL2); VTK_MODULE_INIT(vtkRenderingVolumeOpenGL2);
@@ -128,9 +130,15 @@ vtkVoxImage::vtkVoxImage(Content &content)
m_Image(vtkImageData::New()), m_Outline(vtkCubeSource::New()), m_Image(vtkImageData::New()), m_Outline(vtkCubeSource::New()),
m_OutlineActor(vtkActor::New()), m_OutlineActor(vtkActor::New()),
m_Reader(NULL), m_Writer(NULL), writer_factor(1.E6), m_Reader(NULL), m_Writer(NULL), writer_factor(1.E6),
m_Window(40/1.E6), m_Level(20/1.E6), m_ShadingPreset(0) { 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(); GetContent();
InstallPipe(); InstallPipe();
RescaleShaderRange();
ULIB_ACTIVATE_DISPLAY_PROPERTIES; ULIB_ACTIVATE_DISPLAY_PROPERTIES;
} }
@@ -140,6 +148,8 @@ vtkVoxImage::~vtkVoxImage() {
m_Asm->Delete(); m_Asm->Delete();
m_Outline->Delete(); m_Outline->Delete();
m_OutlineActor->Delete(); m_OutlineActor->Delete();
m_ColorFun->Delete();
m_OpacityFun->Delete();
} }
vtkImageData *vtkVoxImage::GetImageData() { vtkImageData *vtkVoxImage::GetImageData() {
@@ -181,6 +191,7 @@ void vtkVoxImage::ReadFromVKTFile(const char *fname) {
m_Image->DeepCopy(vtkscale->GetOutput()); m_Image->DeepCopy(vtkscale->GetOutput());
SetContent(); SetContent();
RescaleShaderRange();
} else { } else {
std::cerr << "Error: file does not contain structured points\n"; 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()); m_Image->DeepCopy(vtkscale->GetOutput());
SetContent(); SetContent();
RescaleShaderRange();
} }
void vtkVoxImage::setShadingPreset(int blendType) { void vtkVoxImage::setShadingPreset(int blendType) {
m_ShadingPreset = blendType; m_ShadingPreset = blendType;
vtkSmartVolumeMapper *mapper = (vtkSmartVolumeMapper *)m_Actor->GetMapper(); vtkSmartVolumeMapper *mapper = (vtkSmartVolumeMapper *)m_Actor->GetMapper();
if (!mapper) return;
vtkVolumeProperty *property = m_Actor->GetProperty(); vtkVolumeProperty *property = m_Actor->GetProperty();
static vtkColorTransferFunction *colorFun = vtkColorTransferFunction::New();
static vtkPiecewiseFunction *opacityFun = vtkPiecewiseFunction::New();
float window = m_Window; float window = m_Window;
float level = m_Level; float level = m_Level;
property->SetColor(colorFun); property->SetColor(m_ColorFun);
property->SetScalarOpacity(opacityFun); property->SetScalarOpacity(m_OpacityFun);
property->SetInterpolationTypeToLinear(); property->SetInterpolationTypeToLinear();
if (blendType != 6) { m_ColorFun->RemoveAllPoints();
colorFun->RemoveAllPoints(); m_OpacityFun->RemoveAllPoints();
opacityFun->RemoveAllPoints();
}
switch (blendType) { switch (blendType) {
case 0: case 0: // MIP
colorFun->AddRGBSegment(0.0, 1.0, 1.0, 1.0, 255.0, 1.0, 1.0, 1.0); m_ColorFun->AddRGBPoint(level - 0.5 * window, 0, 0, 0);
opacityFun->AddSegment(level - 0.5 * window, 0.0, level + 0.5 * window, m_ColorFun->AddRGBPoint(level + 0.5 * window, 1, 1, 1);
1.0); m_OpacityFun->AddSegment(level - 0.5 * window, 0.0, level + 0.5 * window, 1.0);
mapper->SetBlendModeToMaximumIntensity(); mapper->SetBlendModeToMaximumIntensity();
break; break;
case 1: case 1: // Composite
colorFun->AddRGBSegment(level - 0.5 * window, 0.0, 0.0, 0.0, m_ColorFun->AddRGBPoint(level - 0.5 * window, 0, 0, 0);
level + 0.5 * window, 1.0, 1.0, 1.0); m_ColorFun->AddRGBPoint(level + 0.5 * window, 1, 1, 1);
opacityFun->AddSegment(level - 0.5 * window, 0.0, level + 0.5 * window, m_OpacityFun->AddSegment(level - 0.5 * window, 0.0, level + 0.5 * window, 1.0);
1.0);
mapper->SetBlendModeToComposite(); mapper->SetBlendModeToComposite();
property->ShadeOff(); property->ShadeOff();
break; break;
case 2: case 2: // Composite Shaded
colorFun->AddRGBSegment(0.0, 1.0, 1.0, 1.0, 255.0, 1.0, 1.0, 1.0); m_ColorFun->AddRGBPoint(level - 0.5 * window, 0, 0, 0);
opacityFun->AddSegment(level - 0.5 * window, 0.0, level + 0.5 * window, m_ColorFun->AddRGBPoint(level + 0.5 * window, 1, 1, 1);
1.0); m_OpacityFun->AddSegment(level - 0.5 * window, 0.0, level + 0.5 * window, 1.0);
mapper->SetBlendModeToComposite(); mapper->SetBlendModeToComposite();
property->ShadeOn(); property->ShadeOn();
break; break;
case 3: case 3: // Rainbow MIP
colorFun->AddRGBPoint(-3024, 0, 0, 0, 0.5, 0.0); m_ColorFun->AddRGBPoint(level - 0.5 * window, 0, 0, 1);
colorFun->AddRGBPoint(-1000, .62, .36, .18, 0.5, 0.0); m_ColorFun->AddRGBPoint(level, 0, 1, 0);
colorFun->AddRGBPoint(-500, .88, .60, .29, 0.33, 0.45); m_ColorFun->AddRGBPoint(level + 0.5 * window, 1, 1, 0);
colorFun->AddRGBPoint(3071, .83, .66, 1, 0.5, 0.0); m_ColorFun->AddRGBPoint(level + window, 1, 0, 0);
opacityFun->AddPoint(-3024, 0, 0.5, 0.0); m_OpacityFun->AddSegment(level - 0.5 * window, 0.0, level + 0.5 * window, 1.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);
mapper->SetBlendModeToMaximumIntensity(); mapper->SetBlendModeToMaximumIntensity();
break; break;
case 5: case 4: // Additive
colorFun->AddRGBSegment(0.0, 1.0, 1.0, 1.0, 255.0, 1.0, 1.0, 1.0); m_ColorFun->AddRGBPoint(level - 0.5 * window, 0, 0, 0);
opacityFun->AddSegment(level - 0.5 * window, 0.0, level + 0.5 * window, 1.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(); mapper->SetBlendModeToAdditive();
break; break;
default: default:
vtkGenericWarningMacro("Unknown blend type.");
break; 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) { void vtkVoxImage::SetRepresentation(Representation mode) {
Puppet::SetRepresentation(mode); // Ensure base class data state is updated
if (mode == Wireframe) { if (mode == Wireframe) {
m_Actor->SetVisibility(0); m_Actor->SetVisibility(0);
m_OutlineActor->SetVisibility(1); m_OutlineActor->SetVisibility(1);
} else if (mode == Surface) { m_OutlineActor->GetProperty()->SetRepresentationToWireframe();
} else if (mode == Volume) {
m_Actor->SetVisibility(1); m_Actor->SetVisibility(1);
m_OutlineActor->SetVisibility(1); // Keep outline visible as boundary m_OutlineActor->SetVisibility(1);
m_OutlineActor->GetProperty()->SetRepresentationToWireframe();
} else { } 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) { void vtkVoxImage::serialize_display(uLib::Archive::display_properties_archive & ar, const unsigned int version) {
// Call base class if it has display properties // Call base class to show Transform and Appearance properties
Puppet::serialize_display(ar, version); // 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("Window", m_Window);
ar & boost::serialization::make_hrp("Level", m_Level); 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() { 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); setShadingPreset(m_ShadingPreset);
m_Actor->Update(); m_Actor->Update();
m_Outline->SetBounds(m_Image->GetBounds()); m_Outline->SetBounds(m_Image->GetBounds());
m_Outline->Update(); m_Outline->Update();
ConnectionBlock blocker(m_UpdateConnection);
this->Puppet::Update();
} }
void vtkVoxImage::InstallPipe() { void vtkVoxImage::InstallPipe() {
vtkSmartPointer<vtkSmartVolumeMapper> mapper = vtkSmartPointer<vtkSmartVolumeMapper> mapper =
vtkSmartPointer<vtkSmartVolumeMapper>::New(); vtkSmartPointer<vtkSmartVolumeMapper>::New();
@@ -320,7 +350,7 @@ void vtkVoxImage::InstallPipe() {
mapper->Update(); mapper->Update();
m_Actor->SetMapper(mapper); m_Actor->SetMapper(mapper);
this->setShadingPreset(0); this->setShadingPreset(m_ShadingPreset);
mapper->Update(); mapper->Update();
m_Outline->SetBounds(m_Image->GetBounds()); m_Outline->SetBounds(m_Image->GetBounds());
@@ -337,7 +367,7 @@ void vtkVoxImage::InstallPipe() {
this->SetProp(m_Asm); this->SetProp(m_Asm);
// Default look // Default look
this->SetRepresentation(Surface); this->SetRepresentation(Volume);
} }
} // namespace Vtk } // namespace Vtk

View File

@@ -39,6 +39,8 @@
class vtkImageData; class vtkImageData;
class vtkActor; class vtkActor;
class vtkColorTransferFunction;
class vtkPiecewiseFunction;
namespace uLib { namespace uLib {
namespace Vtk { namespace Vtk {
@@ -55,6 +57,8 @@ public:
void SetContent(); void SetContent();
vtkProp3D *GetProp() override { return m_Asm; }
vtkImageData *GetImageData(); vtkImageData *GetImageData();
void SaveToXMLFile(const char *fname); void SaveToXMLFile(const char *fname);
@@ -65,8 +69,10 @@ public:
void setShadingPreset(int blendType = 2); void setShadingPreset(int blendType = 2);
void SetRepresentation(Representation mode); void SetRepresentation(Representation mode);
void RescaleShaderRange();
void Update() override; void Update() override;
void SyncFromVtk() override;
void serialize_display(uLib::Archive::display_properties_archive & ar, const unsigned int version = 0) override; void serialize_display(uLib::Archive::display_properties_archive & ar, const unsigned int version = 0) override;
protected: protected:
@@ -88,6 +94,11 @@ private:
float m_Window; float m_Window;
float m_Level; float m_Level;
int m_ShadingPreset; int m_ShadingPreset;
Connection m_UpdateConnection;
class vtkColorTransferFunction *m_ColorFun;
class vtkPiecewiseFunction *m_OpacityFun;
}; };
} // namespace Vtk } // namespace Vtk

View File

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

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

View File

@@ -134,11 +134,11 @@ public:
vtkActor *actor = vtkActor::SafeDownCast(p); vtkActor *actor = vtkActor::SafeDownCast(p);
if (actor) { if (actor) {
if (m_Representation != -1) { if (m_Representation != -1 && m_Representation != Puppet::Volume) {
if (m_Representation == Puppet::SurfaceWithEdges) { if (m_Representation == Puppet::SurfaceWithEdges) {
actor->GetProperty()->SetRepresentation(VTK_SURFACE); actor->GetProperty()->SetRepresentation(VTK_SURFACE);
actor->GetProperty()->SetEdgeVisibility(1); actor->GetProperty()->SetEdgeVisibility(1);
} else { } else if (m_Representation != Puppet::Outline && m_Representation != Puppet::Slice) {
actor->GetProperty()->SetRepresentation(m_Representation); actor->GetProperty()->SetRepresentation(m_Representation);
actor->GetProperty()->SetEdgeVisibility(0); actor->GetProperty()->SetEdgeVisibility(0);
} }
@@ -628,6 +628,8 @@ struct AppearanceProxy {
ar & boost::serialization::make_hrp("Visibility", pd->m_Visibility); ar & boost::serialization::make_hrp("Visibility", pd->m_Visibility);
ar & boost::serialization::make_hrp("Pickable", pd->m_Selectable); ar & boost::serialization::make_hrp("Pickable", pd->m_Selectable);
ar & boost::serialization::make_hrp("Dragable", pd->m_Dragable); 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);
} }
}; };

View File

@@ -72,6 +72,7 @@ struct ViewerData {
vtkRenderWindow *m_RenderWindow; vtkRenderWindow *m_RenderWindow;
vtkSmartPointer<vtkRenderWindowInteractor> m_Interactor; vtkSmartPointer<vtkRenderWindowInteractor> m_Interactor;
vtkSmartPointer<vtkButtonWidget> m_GridButton; vtkSmartPointer<vtkButtonWidget> m_GridButton;
vtkSmartPointer<vtkButtonWidget> m_ProjButton;
ViewerData() : m_RenderWindow(vtkRenderWindow::New()) {} ViewerData() : m_RenderWindow(vtkRenderWindow::New()) {}
~ViewerData() { ~ViewerData() {
@@ -97,6 +98,11 @@ Viewer::~Viewer() {
dv->m_GridButton->SetInteractor(nullptr); dv->m_GridButton->SetInteractor(nullptr);
dv->m_GridButton = 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()) { if (this->GetRenderWindow()) {
this->GetRenderWindow()->RemoveAllObservers(); this->GetRenderWindow()->RemoveAllObservers();
} }
@@ -123,6 +129,7 @@ void Viewer::InstallPipe() {
// Setup native grid button // Setup native grid button
if (!std::getenv("CTEST_PROJECT_NAME")) { if (!std::getenv("CTEST_PROJECT_NAME")) {
SetupGridButton(); SetupGridButton();
SetupProjButton();
} }
// BUT we want to override the style with our custom NoSpin version // BUT we want to override the style with our custom NoSpin version
@@ -238,6 +245,88 @@ void Viewer::UpdateGridButtonPosition() {
rep->PlaceWidget(bds); 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() { void Viewer::Start() {
if (std::getenv("CTEST_PROJECT_NAME")) return; if (std::getenv("CTEST_PROJECT_NAME")) return;
dv->m_RenderWindow->GetInteractor()->Start(); dv->m_RenderWindow->GetInteractor()->Start();

View File

@@ -38,6 +38,9 @@ private:
void SetupGridButton(); void SetupGridButton();
void UpdateGridButtonPosition(); void UpdateGridButtonPosition();
void SetupProjButton();
void UpdateProjButtonPosition();
struct ViewerData *dv; struct ViewerData *dv;
}; };

View File

@@ -118,18 +118,17 @@ void vtkObjectsContext::SyncFromVtk() {
Puppet* vtkObjectsContext::CreatePuppet(uLib::Object* obj) { Puppet* vtkObjectsContext::CreatePuppet(uLib::Object* obj) {
if (!obj) return nullptr; 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); return new vtkContainerBox(box);
} else if (auto* chamber = dynamic_cast<uLib::DetectorChamber*>(obj)) { } else if (auto* chamber = dynamic_cast<uLib::DetectorChamber*>(obj)) {
return new vtkDetectorChamber(chamber); return new vtkDetectorChamber(chamber);
} else if (auto* cylinder = dynamic_cast<uLib::Cylinder*>(obj)) { } else if (auto* cylinder = dynamic_cast<uLib::Cylinder*>(obj)) {
return new vtkCylinder(cylinder); 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)) { } else if (auto* assembly = dynamic_cast<uLib::Assembly*>(obj)) {
return new Assembly(assembly); 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); return new vtkBoxSolid(box);
} }

View File

@@ -15,7 +15,7 @@ namespace Vtk {
*/ */
class vtkObjectsContext : public Puppet { class vtkObjectsContext : public Puppet {
public: public:
virtual const char* GetClassName() const override { return "vtkObjectsContext"; } uLibTypeMacro(vtkObjectsContext, Puppet)
vtkObjectsContext(uLib::ObjectsContext *context); vtkObjectsContext(uLib::ObjectsContext *context);
virtual ~vtkObjectsContext(); virtual ~vtkObjectsContext();

View File

@@ -19,6 +19,7 @@ QViewport::QViewport(QWidget* parent)
, Viewport() , Viewport()
, m_VtkWidget(nullptr) , m_VtkWidget(nullptr)
, m_GridButton(nullptr) , m_GridButton(nullptr)
, m_ProjButton(nullptr)
{ {
// Build the layout zero margins so VTK fills the entire widget // Build the layout zero margins so VTK fills the entire widget
auto* layout = new QVBoxLayout(this); auto* layout = new QVBoxLayout(this);
@@ -58,6 +59,36 @@ QViewport::QViewport(QWidget* parent)
m_GridButton->setChecked(true); // Grid is on by default m_GridButton->setChecked(true); // Grid is on by default
connect(m_GridButton, &QPushButton::clicked, this, &QViewport::onGridButtonClicked); 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, // After the Qt widget exists but before the first paint,
// attach the renderer and configure the pipeline. // attach the renderer and configure the pipeline.
SetupPipeline(); SetupPipeline();
@@ -81,7 +112,6 @@ void QViewport::SetupPipeline()
void QViewport::Render() void QViewport::Render()
{ {
UpdateGrid();
if (m_VtkWidget && m_VtkWidget->renderWindow()) if (m_VtkWidget && m_VtkWidget->renderWindow())
m_VtkWidget->renderWindow()->Render(); m_VtkWidget->renderWindow()->Render();
} }
@@ -101,6 +131,11 @@ void QViewport::onGridButtonClicked()
SetGridVisible(m_GridButton->isChecked()); SetGridVisible(m_GridButton->isChecked());
} }
void QViewport::onProjButtonClicked()
{
SetParallelProjection(m_ProjButton->isChecked());
}
void QViewport::OnSelectionChanged(Puppet* p) void QViewport::OnSelectionChanged(Puppet* p)
{ {
emit puppetSelected(p); emit puppetSelected(p);
@@ -116,6 +151,11 @@ void QViewport::resizeEvent(QResizeEvent* event)
int y = 160; int y = 160;
m_GridButton->move(x, y); 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 } // namespace Vtk

View File

@@ -51,12 +51,14 @@ protected:
private slots: private slots:
void onGridButtonClicked(); void onGridButtonClicked();
void onProjButtonClicked();
private: private:
void SetupPipeline(); void SetupPipeline();
QVTKOpenGLNativeWidget* m_VtkWidget; QVTKOpenGLNativeWidget* m_VtkWidget;
QPushButton* m_GridButton; QPushButton* m_GridButton;
QPushButton* m_ProjButton;
}; };
} // namespace Vtk } // namespace Vtk

View File

@@ -537,6 +537,21 @@ bool Viewport::GetGridVisible() const
return false; 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) void Viewport::SetGridAxis(Axis axis)
{ {
m_GridAxis = axis; m_GridAxis = axis;

View File

@@ -73,6 +73,10 @@ public:
void SetGridAxis(Axis axis); void SetGridAxis(Axis axis);
Axis GetGridAxis() const { return m_GridAxis; } Axis GetGridAxis() const { return m_GridAxis; }
// Projection control
void SetParallelProjection(bool parallel);
bool GetParallelProjection() const;
protected: protected:
void SetupPipeline(vtkRenderWindowInteractor* iren); void SetupPipeline(vtkRenderWindowInteractor* iren);