diff --git a/core/opengate_core/g4_bindings/pyG4Material.cpp b/core/opengate_core/g4_bindings/pyG4Material.cpp index 36b1f66942..308d1a228c 100644 --- a/core/opengate_core/g4_bindings/pyG4Material.cpp +++ b/core/opengate_core/g4_bindings/pyG4Material.cpp @@ -33,6 +33,18 @@ void init_G4Material(py::module &m) { .def("AddElementByNumberOfAtoms", &G4Material::AddElementByNumberOfAtoms) .def("AddElementByMassFraction", &G4Material::AddElementByMassFraction) + // Expose both native G4Material::AddElement overloads. The G4double + // overload interprets its second argument as a mass fraction, whereas + // the G4int overload interprets it as the number of atoms. Explicit + // overload selection is required because both methods have the same + // C++ name and must remain distinguishable to pybind11. + .def("AddElement", + py::overload_cast(&G4Material::AddElement), + py::arg("element"), py::arg("fraction")) + .def("AddElement", + py::overload_cast(&G4Material::AddElement), + py::arg("element"), py::arg("natoms")) + .def("GetElementFraction", [](G4Material &ma, int i) -> double { return ma.GetFractionVector()[i]; diff --git a/core/opengate_core/g4_bindings/pyG4Tet.cpp b/core/opengate_core/g4_bindings/pyG4Tet.cpp new file mode 100644 index 0000000000..bd8b20a44d --- /dev/null +++ b/core/opengate_core/g4_bindings/pyG4Tet.cpp @@ -0,0 +1,74 @@ +// pyG4Tet.cpp (FIXED v2 for Geant4 11.1.x) +// - Avoids calling G4Tet::CheckDegeneracy (non-static in this Geant4) +// - Provides a simple geometric degeneracy test based on tetra volume +// +// Place this file into: opengate_core/g4_bindings/pyG4Tet.cpp + +#include +#include + +#include +#include +#include +#include + +#include "G4String.hh" +#include "G4Tet.hh" +#include "G4ThreeVector.hh" + +namespace py = pybind11; + +// Simple degeneracy check: |6V| = | dot( (b-a), cross((c-a),(d-a)) ) | +static bool tet_is_degenerate(const G4ThreeVector &a, const G4ThreeVector &b, + const G4ThreeVector &c, const G4ThreeVector &d, + double eps = 1e-12) { + const auto ab = b - a; + const auto ac = c - a; + const auto ad = d - a; + const double sixV = ab.dot(ac.cross(ad)); + return std::abs(sixV) <= eps; +} + +void init_G4Tet(py::module &m) { + + py::class_>(m, "G4Tet") + // NOTE: Geant4 11.1.x constructor is: + // G4Tet(name, p1,p2,p3,p4, G4bool* degeneracyFlag = nullptr) + .def( + py::init([](const std::string &name, const std::array &p1, + const std::array &p2, + const std::array &p3, + const std::array &p4) { + G4bool deg = false; + auto *t = new G4Tet(G4String(name.c_str()), + G4ThreeVector(p1[0], p1[1], p1[2]), + G4ThreeVector(p2[0], p2[1], p2[2]), + G4ThreeVector(p3[0], p3[1], p3[2]), + G4ThreeVector(p4[0], p4[1], p4[2]), °); + // If Geant4 says degenerate, delete and throw + if (deg) { + delete t; + throw std::runtime_error( + "G4Tet: degenerate tetrahedron (Geant4 degeneracyFlag=true)"); + } + return t; + }), + py::arg("name"), py::arg("p1"), py::arg("p2"), py::arg("p3"), + py::arg("p4")) + .def("GetCubicVolume", &G4Tet::GetCubicVolume) + .def("Inside", &G4Tet::Inside); + + // Standalone helper: purely geometric degeneracy test + m.def( + "tet_is_degenerate", + [](const std::array &p1, const std::array &p2, + const std::array &p3, const std::array &p4, + double eps) { + return tet_is_degenerate(G4ThreeVector(p1[0], p1[1], p1[2]), + G4ThreeVector(p2[0], p2[1], p2[2]), + G4ThreeVector(p3[0], p3[1], p3[2]), + G4ThreeVector(p4[0], p4[1], p4[2]), eps); + }, + py::arg("p1"), py::arg("p2"), py::arg("p3"), py::arg("p4"), + py::arg("eps") = 1e-12); +} diff --git a/core/opengate_core/opengate_core.cpp b/core/opengate_core/opengate_core.cpp index e317eedc17..46cafe78b1 100644 --- a/core/opengate_core/opengate_core.cpp +++ b/core/opengate_core/opengate_core.cpp @@ -296,6 +296,11 @@ void init_G4TriangularFacet(py::module &m); void init_G4QuadrangularFacet(py::module &m); +// Added binding declaration: expose Geant4's single tetrahedron solid +// (G4Tet) to Python. This is distinct from the OpenGATE tetrahedral mesh +// builder declared below. +void init_G4Tet(py::module &); + // geometry/volume void init_G4PVPlacement(py::module &); @@ -400,6 +405,14 @@ void init_GateBooleanFilter(py::module &); void init_GateAttributeComparisonFilter(py::module &); +// Added filter binding declaration: select steps by the copy number of their +// pre-step touchable. +void init_GateCopyNumberFilter(py::module &); + +// Added OpenGATE geometry binding declaration: build a parameterised +// tetrahedral mesh from TetGen .node and .ele files. +void init_GateTetrahedralMesh(py::module &); + // Gate actors void init_GateDoseActor(py::module &m); @@ -714,6 +727,10 @@ PYBIND11_MODULE(opengate_core, m) { init_G4TriangularFacet(m); init_G4QuadrangularFacet(m); + // Added module registration: make g4.G4Tet and its helper binding available + // after importing opengate_core. + init_G4Tet(m); + init_G4PVPlacement(m); init_G4TouchableHistory(m); init_G4NavigationHistory(m); @@ -782,6 +799,15 @@ PYBIND11_MODULE(opengate_core, m) { init_GateTrackCreatorProcessFilter(m); init_GateBooleanFilter(m); init_GateAttributeComparisonFilter(m); + + // Added module registration: expose g4.GateCopyNumberFilter to the Python + // CopyNumberFilter wrapper in opengate/actors/filters.py. + init_GateCopyNumberFilter(m); + + // Added module registration: expose the TetGen-based parameterised mesh + // builders implemented in opengate_lib. + init_GateTetrahedralMesh(m); + init_itk_image(m); init_GateImageNestedParameterisation(m); init_GateRepeatParameterisation(m); diff --git a/core/opengate_core/opengate_lib/GateTetrahedralMeshParameterisation.cpp b/core/opengate_core/opengate_lib/GateTetrahedralMeshParameterisation.cpp new file mode 100644 index 0000000000..4d4c494aab --- /dev/null +++ b/core/opengate_core/opengate_lib/GateTetrahedralMeshParameterisation.cpp @@ -0,0 +1,509 @@ +/* -------------------------------------------------- + G4Tet-based tetrahedral mesh implementation for OpenGATE + - Builds ONE parameterised volume (G4PVParameterised + + G4VNestedParameterisation) + - IMPORTANT (GeomVol0002): the parameterised PV MUST be the ONLY daughter of + mother_lv. + -------------------------------------------------- */ + +#include "GateTetrahedralMeshParameterisation.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "G4Box.hh" +#include "G4Colour.hh" +#include "G4LogicalVolume.hh" +#include "G4Material.hh" +#include "G4NistManager.hh" +#include "G4PVParameterised.hh" +#include "G4SystemOfUnits.hh" +#include "G4Tet.hh" +#include "G4Threading.hh" +#include "G4ThreeVector.hh" +#include "G4VNestedParameterisation.hh" +#include "G4VPhysicalVolume.hh" +#include "G4VTouchable.hh" +#include "G4VisAttributes.hh" +#include "globals.hh" + +// -------------------------------------------------------------------------------------- +// TetGen .node/.ele parsing +// -------------------------------------------------------------------------------------- + +struct NodeRec { + G4double x{0}, y{0}, z{0}; +}; + +struct NodeTable { + std::vector nodes; // stored 0..N-1 + int index_base{1}; // 0 if input uses 0-based IDs, 1 if input uses 1-based IDs + int n_points{0}; +}; + +struct IndexedNodeRec { + int id{0}; + NodeRec node; +}; + +struct EleRec { + int id{0}; + int n1{0}, n2{0}, n3{0}, n4{0}; + int region{ + 0}; // first attribute interpreted as region/material id (if present) +}; + +static inline std::string strip_comment(const std::string &line) { + auto pos = line.find('#'); + return (pos == std::string::npos) ? line : line.substr(0, pos); +} + +static inline bool is_blank(const std::string &s) { + for (char c : s) { + if (!std::isspace(static_cast(c))) + return false; + } + return true; +} + +static NodeTable read_node_file(const std::string &path, + G4double input_length_unit) { + std::ifstream fin(path); + if (!fin) + throw std::runtime_error("Cannot open .node file: " + path); + + std::string line; + int n_points = 0, dim = 0, n_attr = 0, has_bm = 0; + + // Header: <#points> <#attr> <#bm> + while (std::getline(fin, line)) { + line = strip_comment(line); + if (is_blank(line)) + continue; + std::istringstream iss(line); + if (!(iss >> n_points >> dim >> n_attr >> has_bm)) + throw std::runtime_error("Invalid .node header in: " + path); + break; + } + if (n_points <= 0 || dim < 3) + throw std::runtime_error("Unexpected .node header values in: " + path); + + // Read all indexed records first. The index base must be known before a + // record can be placed safely in the zero-based storage vector. + std::vector indexed_nodes; + indexed_nodes.reserve(static_cast(n_points)); + int min_idx = std::numeric_limits::max(); + int max_idx = std::numeric_limits::min(); + + // Read node lines: ... + // We accept either 0-based (0..N-1) or 1-based (1..N) indices. + while (std::getline(fin, line)) { + line = strip_comment(line); + if (is_blank(line)) + continue; + std::istringstream iss(line); + int idx = 0; + G4double x = 0, y = 0, z = 0; + if (!(iss >> idx >> x >> y >> z)) + continue; + + min_idx = std::min(min_idx, idx); + max_idx = std::max(max_idx, idx); + + indexed_nodes.push_back(IndexedNodeRec{ + idx, NodeRec{x * input_length_unit, y * input_length_unit, + z * input_length_unit}}); + } + + if (indexed_nodes.size() != static_cast(n_points)) + throw std::runtime_error("Node count does not match .node header in: " + + path); + + int index_base = 0; + if (min_idx == 0 && max_idx == n_points - 1) { + index_base = 0; + } else if (min_idx == 1 && max_idx == n_points) { + index_base = 1; + } else { + throw std::runtime_error( + "Node indices must span either 0..N-1 or 1..N in: " + path); + } + + std::vector nodes(static_cast(n_points)); + std::vector assigned(static_cast(n_points), false); + for (const auto &record : indexed_nodes) { + const int slot = record.id - index_base; + if (slot < 0 || slot >= n_points) + throw std::runtime_error("Node index out of range in: " + path); + if (assigned[static_cast(slot)]) + throw std::runtime_error("Duplicate node index in: " + path); + nodes[static_cast(slot)] = record.node; + assigned[static_cast(slot)] = true; + } + + NodeTable t; + t.nodes = std::move(nodes); + t.index_base = index_base; + t.n_points = n_points; + return t; +} + +static std::vector read_ele_file(const std::string &path) { + std::ifstream fin(path); + if (!fin) + throw std::runtime_error("Cannot open .ele file: " + path); + + std::string line; + int n_tets = 0, nodes_per_tet = 0, n_attr = 0; + + while (std::getline(fin, line)) { + line = strip_comment(line); + if (is_blank(line)) + continue; + std::istringstream iss(line); + if (!(iss >> n_tets >> nodes_per_tet >> n_attr)) + throw std::runtime_error("Invalid .ele header in: " + path); + break; + } + if (n_tets <= 0 || nodes_per_tet != 4) + throw std::runtime_error("Unexpected .ele header values in: " + path); + + std::vector tets; + tets.reserve(static_cast(n_tets)); + + while (std::getline(fin, line)) { + line = strip_comment(line); + if (is_blank(line)) + continue; + std::istringstream iss(line); + EleRec e{}; + if (!(iss >> e.id >> e.n1 >> e.n2 >> e.n3 >> e.n4)) + continue; + + if (n_attr > 0) { + double first_attr = 0.0; + if (iss >> first_attr) + e.region = static_cast(first_attr); + } + tets.push_back(e); + } + + if (tets.empty()) + throw std::runtime_error("No tetrahedra were read from: " + path); + + return tets; +} + +// -------------------------------------------------------------------------------------- +// Nested parameterisation +// -------------------------------------------------------------------------------------- + +GateTetrahedralMeshParameterisation::GateTetrahedralMeshParameterisation( + std::vector solids, std::vector unique_materials, + std::vector material_index_per_copy, + std::vector region_per_copy, + std::unordered_map region_to_colour, + std::unordered_map region_visible) + : fSolids(std::move(solids)), fUniqueMaterials(std::move(unique_materials)), + fMaterialIndexPerCopy(std::move(material_index_per_copy)), + fRegionPerCopy(std::move(region_per_copy)), + fRegionToColour(std::move(region_to_colour)), + fRegionVisible(std::move(region_visible)) { + if (fSolids.empty()) + throw std::runtime_error("GateTetrahedralMeshParameterisation: no solids"); + if (fUniqueMaterials.empty()) + throw std::runtime_error( + "GateTetrahedralMeshParameterisation: no materials"); + if (fMaterialIndexPerCopy.size() != fSolids.size()) + throw std::runtime_error( + "GateTetrahedralMeshParameterisation: material index size mismatch"); + if (fRegionPerCopy.size() != fSolids.size()) + throw std::runtime_error( + "GateTetrahedralMeshParameterisation: region index size mismatch"); +} + +void GateTetrahedralMeshParameterisation::ComputeTransformation( + G4int /*copy_number*/, G4VPhysicalVolume *physical_volume) const { + physical_volume->SetTranslation(G4ThreeVector(0, 0, 0)); + physical_volume->SetRotation(nullptr); +} + +G4Material *GateTetrahedralMeshParameterisation::ComputeMaterial( + G4VPhysicalVolume *current_volume, G4int copy_number, + const G4VTouchable *parent_touch) { + const auto index = fMaterialIndexPerCopy.at(static_cast(copy_number)); + auto *material = fUniqueMaterials.at(static_cast(index)); + + // Worker threads may navigate concurrently. Return the material there, but + // only mutate the shared logical volume from the master thread. + if (current_volume != nullptr && !G4Threading::IsWorkerThread()) { + const auto region = fRegionPerCopy.at(static_cast(copy_number)); + auto *logical_volume = current_volume->GetLogicalVolume(); + if (logical_volume != nullptr) { + logical_volume->SetVisAttributes(get_or_create_vis(region)); + logical_volume->SetMaterial(material); + } + } + (void)parent_touch; + return material; +} + +G4VSolid *GateTetrahedralMeshParameterisation::ComputeSolid( + G4int copy_number, G4VPhysicalVolume * /*physical_volume*/) { + return fSolids.at(static_cast(copy_number)); +} + +G4int GateTetrahedralMeshParameterisation::GetNumberOfMaterials() const { + return static_cast(fUniqueMaterials.size()); +} + +G4Material * +GateTetrahedralMeshParameterisation::GetMaterial(G4int index) const { + return fUniqueMaterials.at(static_cast(index)); +} + +G4VisAttributes * +GateTetrahedralMeshParameterisation::get_or_create_vis(int region) { + auto existing = fVisCache.find(region); + if (existing != fVisCache.end()) + return existing->second; + + G4Colour colour(0.8, 0.8, 0.8, 1.0); + auto configured_colour = fRegionToColour.find(region); + if (configured_colour != fRegionToColour.end()) + colour = configured_colour->second; + + const bool visible = + fRegionVisible.count(region) == 0 ? true : fRegionVisible.at(region); + auto *attributes = new G4VisAttributes(colour); + attributes->SetVisibility(visible); + attributes->SetForceSolid(true); + fVisCache.emplace(region, attributes); + return attributes; +} + +// Keep parameterisations alive for the whole run +static std::vector> + g_tetrahedral_mesh_parameterisations; + +// -------------------------------------------------------------------------------------- +// Forward declaration (avoids "undeclared identifier" errors) +// -------------------------------------------------------------------------------------- +static G4VPhysicalVolume *build_tetrahedral_mesh_impl( + const std::string &node_path, const std::string &ele_path, + G4LogicalVolume *mother_lv, + const std::map ®ion_to_material, + const std::unordered_map ®ion_to_colour, + const std::unordered_map ®ion_visible, + G4Material *default_material, const std::string &pv_name, + G4bool check_overlaps, G4double node_coordinate_unit); + +// -------------------------------------------------------------------------------------- +// Material resolver for compat API +// -------------------------------------------------------------------------------------- +static G4Material *resolve_material_by_name(const std::string &name) { + if (name.empty()) + return nullptr; + if (auto *m = G4Material::GetMaterial(name, false)) + return m; + + if (name.rfind("G4_", 0) == 0) { + auto *nist = G4NistManager::Instance(); + if (auto *m = nist->FindOrBuildMaterial(name, false)) + return m; + } + return nullptr; +} + +// -------------------------------------------------------------------------------------- +// New API wrapper (material pointers) +// -------------------------------------------------------------------------------------- +G4VPhysicalVolume *build_tetrahedral_mesh_from_tetgen( + const std::string &node_path, const std::string &ele_path, + G4LogicalVolume *mother_lv, + const std::map ®ion_to_material, + const std::unordered_map ®ion_to_colour, + const std::unordered_map ®ion_visible, + G4Material *default_material, const std::string &pv_name, + G4bool check_overlaps, G4double scale) { + return build_tetrahedral_mesh_impl( + node_path, ele_path, mother_lv, region_to_material, region_to_colour, + region_visible, default_material, pv_name, check_overlaps, scale * mm); +} + +// -------------------------------------------------------------------------------------- +// MRCP API wrapper (MRCP .node coordinates are stored in centimetres) +// -------------------------------------------------------------------------------------- +G4VPhysicalVolume *build_mrcp_tetrahedral_mesh_from_tetgen( + const std::string &node_path, const std::string &ele_path, + G4LogicalVolume *mother_lv, + const std::map ®ion_to_material, + const std::unordered_map ®ion_to_colour, + const std::unordered_map ®ion_visible, + G4Material *default_material, const std::string &pv_name, + G4bool check_overlaps) { + // Convert MRCP coordinates to Geant4 internal length units while parsing. + return build_tetrahedral_mesh_impl( + node_path, ele_path, mother_lv, region_to_material, region_to_colour, + region_visible, default_material, pv_name, check_overlaps, cm); +} + +// -------------------------------------------------------------------------------------- +// Compat API wrapper (material names + scale, legacy argument order) +// -------------------------------------------------------------------------------------- +G4VPhysicalVolume *build_tetrahedral_mesh_from_tetgen_material_names( + G4LogicalVolume *mother_lv, const std::string &pv_name, + const std::string &node_path, const std::string &ele_path, + const std::map ®ion_id_to_mat_name, G4double scale, + G4bool check_overlaps) { + + std::map region_to_material_ptr; + for (const auto &kv : region_id_to_mat_name) { + auto *m = resolve_material_by_name(kv.second); + if (m != nullptr) + region_to_material_ptr.emplace(kv.first, m); + } + + return build_tetrahedral_mesh_impl( + node_path, ele_path, mother_lv, region_to_material_ptr, + /*region_to_colour=*/{}, /*region_visible=*/{}, + /*default_material=*/nullptr, pv_name, check_overlaps, scale * mm); +} + +// -------------------------------------------------------------------------------------- +// Internal implementation +// -------------------------------------------------------------------------------------- +static G4VPhysicalVolume *build_tetrahedral_mesh_impl( + const std::string &node_path, const std::string &ele_path, + G4LogicalVolume *mother_lv, + const std::map ®ion_to_material, + const std::unordered_map ®ion_to_colour, + const std::unordered_map ®ion_visible, + G4Material *default_material, const std::string &pv_name, + G4bool check_overlaps, G4double node_coordinate_unit) { + if (mother_lv == nullptr) + throw std::runtime_error("mother_lv is null"); + + if (default_material == nullptr) { + default_material = G4NistManager::Instance()->FindOrBuildMaterial("G4_AIR"); + if (default_material == nullptr) + throw std::runtime_error("Cannot build default material G4_AIR"); + } + + const auto node_table = read_node_file(node_path, node_coordinate_unit); + const auto elems = read_ele_file(ele_path); + + // Keep tetrahedra centered inside the already-centered mother container LV. + // The Python example translates the outer phantom volume by the global bbox + // center, so the parameterised tets must be shifted by -center here, matching + // the legacy per-tet placement builder behavior. + G4double min_x = std::numeric_limits::max(); + G4double min_y = std::numeric_limits::max(); + G4double min_z = std::numeric_limits::max(); + G4double max_x = std::numeric_limits::lowest(); + G4double max_y = std::numeric_limits::lowest(); + G4double max_z = std::numeric_limits::lowest(); + for (const auto &n : node_table.nodes) { + min_x = std::min(min_x, n.x); + min_y = std::min(min_y, n.y); + min_z = std::min(min_z, n.z); + max_x = std::max(max_x, n.x); + max_y = std::max(max_y, n.y); + max_z = std::max(max_z, n.z); + } + const G4ThreeVector mesh_center(0.5 * (min_x + max_x), 0.5 * (min_y + max_y), + 0.5 * (min_z + max_z)); + + std::vector solids; + solids.reserve(elems.size()); + + std::vector unique_mats; + unique_mats.reserve(std::min(64, elems.size())); + + std::unordered_map mat_ptr_to_index; + std::vector mat_index_per_copy; + mat_index_per_copy.reserve(elems.size()); + std::vector region_per_copy; + region_per_copy.reserve(elems.size()); + + auto get_or_add_material_index = [&](G4Material *m) -> unsigned int { + auto it = mat_ptr_to_index.find(m); + if (it != mat_ptr_to_index.end()) + return it->second; + const unsigned int idx = static_cast(unique_mats.size()); + unique_mats.push_back(m); + mat_ptr_to_index.emplace(m, idx); + return idx; + }; + + auto node_at = [&](int id) -> const NodeRec & { + // Accept both 0-based and 1-based .node indices + if (node_table.index_base == 0) { + if (id < 0 || id >= node_table.n_points) + throw std::runtime_error( + "Invalid node index in .ele (0-based expected): " + + std::to_string(id)); + return node_table.nodes[static_cast(id)]; + } else { + if (id <= 0 || id > node_table.n_points) + throw std::runtime_error( + "Invalid node index in .ele (1-based expected): " + + std::to_string(id)); + return node_table.nodes[static_cast(id - 1)]; + } + }; + + for (const auto &e : elems) { + const auto &a = node_at(e.n1); + const auto &b = node_at(e.n2); + const auto &c = node_at(e.n3); + const auto &d = node_at(e.n4); + + const G4ThreeVector p1(a.x, a.y, a.z); + const G4ThreeVector p2(b.x, b.y, b.z); + const G4ThreeVector p3(c.x, c.y, c.z); + const G4ThreeVector p4(d.x, d.y, d.z); + + const auto solid_name = + pv_name + std::string("_tet_") + std::to_string(e.id); + solids.push_back(new G4Tet(solid_name, p1 - mesh_center, p2 - mesh_center, + p3 - mesh_center, p4 - mesh_center)); + + G4Material *mat = default_material; + auto mit = region_to_material.find(e.region); + if (mit != region_to_material.end() && mit->second != nullptr) + mat = mit->second; + + mat_index_per_copy.push_back(get_or_add_material_index(mat)); + region_per_copy.push_back(e.region); + } + + // Dummy LV (required by G4PVParameterised). Actual solids come from + // ComputeSolid(). + auto *dummy_solid = + new G4Box((pv_name + "_dummy_box").c_str(), 0.5 * mm, 0.5 * mm, 0.5 * mm); + auto *tet_lv = new G4LogicalVolume(dummy_solid, default_material, + (pv_name + "_lv").c_str()); + + auto param = std::make_unique( + std::move(solids), unique_mats, mat_index_per_copy, region_per_copy, + region_to_colour, region_visible); + auto *param_ptr = param.get(); + g_tetrahedral_mesh_parameterisations.emplace_back(std::move(param)); + + // ONLY DAUGHTER RULE: mother_lv must have no other daughters. + auto *pv = new G4PVParameterised( + pv_name.c_str(), tet_lv, mother_lv, kUndefined, + static_cast(mat_index_per_copy.size()), param_ptr, check_overlaps); + return pv; +} diff --git a/core/opengate_core/opengate_lib/GateTetrahedralMeshParameterisation.h b/core/opengate_core/opengate_lib/GateTetrahedralMeshParameterisation.h new file mode 100644 index 0000000000..b6c97bb7de --- /dev/null +++ b/core/opengate_core/opengate_lib/GateTetrahedralMeshParameterisation.h @@ -0,0 +1,88 @@ +/* -------------------------------------------------- + Copyright (C): OpenGATE Collaboration + This software is distributed under the terms + of the GNU Lesser General Public Licence (LGPL) + See LICENSE.md for further details + -------------------------------------------------- */ + +#ifndef GateTetrahedralMeshParameterisation_h +#define GateTetrahedralMeshParameterisation_h + +#include +#include +#include +#include + +#include "G4Colour.hh" +#include "G4VNestedParameterisation.hh" +#include "globals.hh" + +class G4LogicalVolume; +class G4Material; +class G4Tet; +class G4VisAttributes; +class G4VPhysicalVolume; +class G4VSolid; + +class GateTetrahedralMeshParameterisation : public G4VNestedParameterisation { +public: + GateTetrahedralMeshParameterisation( + std::vector solids, std::vector unique_materials, + std::vector material_index_per_copy, + std::vector region_per_copy, + std::unordered_map region_to_colour, + std::unordered_map region_visible); + + ~GateTetrahedralMeshParameterisation() override = default; + + void ComputeTransformation(G4int copy_number, + G4VPhysicalVolume *physical_volume) const override; + + G4Material * + ComputeMaterial(G4VPhysicalVolume *current_volume, G4int copy_number, + const G4VTouchable *parent_touch = nullptr) override; + + G4VSolid *ComputeSolid(G4int copy_number, + G4VPhysicalVolume *physical_volume) override; + + G4int GetNumberOfMaterials() const override; + + G4Material *GetMaterial(G4int index) const override; + +private: + G4VisAttributes *get_or_create_vis(int region); + + std::vector fSolids; + std::vector fUniqueMaterials; + std::vector fMaterialIndexPerCopy; + std::vector fRegionPerCopy; + std::unordered_map fRegionToColour; + std::unordered_map fRegionVisible; + std::unordered_map fVisCache; +}; + +G4VPhysicalVolume *build_tetrahedral_mesh_from_tetgen( + const std::string &node_path, const std::string &ele_path, + G4LogicalVolume *mother_lv, + const std::map ®ion_to_material, + const std::unordered_map ®ion_to_colour, + const std::unordered_map ®ion_visible, + G4Material *default_material, const std::string &pv_name, + G4bool check_overlaps, G4double scale); + +G4VPhysicalVolume *build_mrcp_tetrahedral_mesh_from_tetgen( + const std::string &node_path, const std::string &ele_path, + G4LogicalVolume *mother_lv, + const std::map ®ion_to_material, + const std::unordered_map ®ion_to_colour, + const std::unordered_map ®ion_visible, + G4Material *default_material, const std::string &pv_name, + G4bool check_overlaps); + +G4VPhysicalVolume *build_tetrahedral_mesh_from_tetgen_material_names( + G4LogicalVolume *mother_lv, const std::string &pv_name, + const std::string &node_path, const std::string &ele_path, + const std::map ®ion_id_to_mat_name, G4double scale, + G4bool check_overlaps); + +#endif // GateTetrahedralMeshParameterisation_h diff --git a/core/opengate_core/opengate_lib/filters/GateCopyNumberFilter.cpp b/core/opengate_core/opengate_lib/filters/GateCopyNumberFilter.cpp new file mode 100644 index 0000000000..2ee8b6a7dd --- /dev/null +++ b/core/opengate_core/opengate_lib/filters/GateCopyNumberFilter.cpp @@ -0,0 +1,60 @@ +/* -------------------------------------------------- + Copyright (C): OpenGATE Collaboration + This software is distributed under the terms + of the GNU Lesser General Public Licence (LGPL) + See LICENSE.md for further details + -------------------------------------------------- */ + +#include "GateCopyNumberFilter.h" + +#include "G4Step.hh" +#include "G4StepPoint.hh" +#include "G4TouchableHandle.hh" +#include "G4VTouchable.hh" + +#include + +void GateCopyNumberFilter::InitializeUserInfo(py::dict &user_info) { + // Initialize the common GateVFilter state before reading filter-specific + // configuration. + GateVFilter::InitializeUserInfo(user_info); + + // Reinitialization must replace, rather than append to, the previous set. + fCopyNumbers.clear(); + + if (user_info.contains("copy_numbers")) { + try { + auto values = user_info["copy_numbers"].cast>(); + // A set provides fast membership tests and removes duplicate values. + for (auto v : values) { + fCopyNumbers.insert(v); + } + } catch (...) { + // Keep the filter empty when Python input cannot be converted to a list + // of integers. By design, an empty set accepts all steps. + } + } +} + +bool GateCopyNumberFilter::Evaluate(G4Step *step) const { + // An empty selection means that copy-number filtering is disabled. + if (fCopyNumbers.empty()) + return true; + + // A configured filter cannot evaluate an invalid step or touchable. + if (!step) + return false; + + auto *pre = step->GetPreStepPoint(); + if (!pre) + return false; + + auto touchable = pre->GetTouchableHandle(); + if (!touchable) + return false; + + // GetCopyNumber() without an explicit depth reads the current volume + // (touchable history depth 0), which is the parameterised tetrahedron here. + const int copy_no = touchable->GetCopyNumber(); + return (fCopyNumbers.count(copy_no) > 0); +} diff --git a/core/opengate_core/opengate_lib/filters/GateCopyNumberFilter.h b/core/opengate_core/opengate_lib/filters/GateCopyNumberFilter.h new file mode 100644 index 0000000000..83a6a20aca --- /dev/null +++ b/core/opengate_core/opengate_lib/filters/GateCopyNumberFilter.h @@ -0,0 +1,38 @@ +/* -------------------------------------------------- + Copyright (C): OpenGATE Collaboration + This software is distributed under the terms + of the GNU Lesser General Public Licence (LGPL) + See LICENSE.md for further details + -------------------------------------------------- */ + +#ifndef GateCopyNumberFilter_h +#define GateCopyNumberFilter_h + +#include "GateVFilter.h" + +#include +#include + +/** + * Filter Geant4 steps by the copy number of the pre-step touchable at depth 0. + * + * The accepted copy numbers are supplied through the Python user-info field + * "copy_numbers". An empty set disables copy-number filtering and accepts all + * valid steps. + */ +class GateCopyNumberFilter : public GateVFilter { +public: + GateCopyNumberFilter() : GateVFilter() {} + + /// Read "copy_numbers" from the Python filter configuration. + void InitializeUserInfo(py::dict &user_info) override; + + /// Return true when the pre-step touchable copy number is accepted. + bool Evaluate(G4Step *step) const override; + +private: + /// Unique copy numbers accepted by this filter. + std::set fCopyNumbers; +}; + +#endif // GateCopyNumberFilter_h diff --git a/core/opengate_core/opengate_lib/filters/pyGateCopyNumberFilter.cpp b/core/opengate_core/opengate_lib/filters/pyGateCopyNumberFilter.cpp new file mode 100644 index 0000000000..a91df19acd --- /dev/null +++ b/core/opengate_core/opengate_lib/filters/pyGateCopyNumberFilter.cpp @@ -0,0 +1,20 @@ +/* -------------------------------------------------- + Copyright (C): OpenGATE Collaboration + This software is distributed under the terms + of the GNU Lesser General Public Licence (LGPL) + See LICENSE.md for further details + -------------------------------------------------- */ + +#include + +#include "GateCopyNumberFilter.h" + +namespace py = pybind11; + +void init_GateCopyNumberFilter(py::module &m) { + // Expose the C++ filter used by the Python CopyNumberFilter wrapper in + // opengate/actors/filters.py. + py::class_(m, "GateCopyNumberFilter") + .def(py::init<>()) + .def("InitializeUserInfo", &GateCopyNumberFilter::InitializeUserInfo); +} diff --git a/core/opengate_core/opengate_lib/pyGateTetrahedralMesh.cpp b/core/opengate_core/opengate_lib/pyGateTetrahedralMesh.cpp new file mode 100644 index 0000000000..652893df76 --- /dev/null +++ b/core/opengate_core/opengate_lib/pyGateTetrahedralMesh.cpp @@ -0,0 +1,99 @@ +/* -------------------------------------------------- + Copyright (C): OpenGATE Collaboration + This software is distributed under the terms + of the GNU Lesser General Public Licence (LGPL) + See LICENSE.md for further details + -------------------------------------------------- */ + +#include +#include + +#include +#include +#include +#include + +#include "G4Colour.hh" +#include "G4LogicalVolume.hh" +#include "G4Material.hh" +#include "GateTetrahedralMeshParameterisation.h" + +namespace py = pybind11; + +static std::unordered_map rgba_to_colour_map( + const std::unordered_map> &rgba_by_region) { + std::unordered_map colours; + colours.reserve(rgba_by_region.size()); + for (const auto &entry : rgba_by_region) { + const auto &rgba = entry.second; + double red = 0.8; + double green = 0.8; + double blue = 0.8; + double alpha = 1.0; + if (rgba.size() >= 3) { + red = rgba[0]; + green = rgba[1]; + blue = rgba[2]; + } + if (rgba.size() >= 4) { + alpha = rgba[3]; + } + colours.emplace(entry.first, G4Colour(red, green, blue, alpha)); + } + return colours; +} + +void init_GateTetrahedralMesh(py::module &m) { + m.def( + "build_tetrahedral_mesh_from_tetgen", + [](const std::string &node_path, const std::string &ele_path, + G4LogicalVolume *mother_lv, + const std::map ®ion_to_material, + const std::unordered_map> ®ion_to_rgba, + const std::unordered_map ®ion_visible, + G4Material *default_material, const std::string &pv_name, + G4bool check_overlaps, G4double scale) { + return build_tetrahedral_mesh_from_tetgen( + node_path, ele_path, mother_lv, region_to_material, + rgba_to_colour_map(region_to_rgba), region_visible, + default_material, pv_name, check_overlaps, scale); + }, + py::return_value_policy::reference, py::arg("node_path"), + py::arg("ele_path"), py::arg("mother_lv"), py::arg("region_to_material"), + py::arg("region_to_rgba") = + std::unordered_map>{}, + py::arg("region_visible") = std::unordered_map{}, + py::arg("default_material") = nullptr, + py::arg("pv_name") = std::string("phantom_tetmesh"), + py::arg("check_overlaps") = false, py::arg("scale") = 1.0); + + m.def( + "build_mrcp_tetrahedral_mesh_from_tetgen", + [](const std::string &node_path, const std::string &ele_path, + G4LogicalVolume *mother_lv, + const std::map ®ion_to_material, + const std::unordered_map> ®ion_to_rgba, + const std::unordered_map ®ion_visible, + G4Material *default_material, const std::string &pv_name, + G4bool check_overlaps) { + return build_mrcp_tetrahedral_mesh_from_tetgen( + node_path, ele_path, mother_lv, region_to_material, + rgba_to_colour_map(region_to_rgba), region_visible, + default_material, pv_name, check_overlaps); + }, + py::return_value_policy::reference, py::arg("node_path"), + py::arg("ele_path"), py::arg("mother_lv"), py::arg("region_to_material"), + py::arg("region_to_rgba") = + std::unordered_map>{}, + py::arg("region_visible") = std::unordered_map{}, + py::arg("default_material") = nullptr, + py::arg("pv_name") = std::string("phantom_tetmesh"), + py::arg("check_overlaps") = false); + + m.def("build_tetrahedral_mesh_from_tetgen_material_names", + &build_tetrahedral_mesh_from_tetgen_material_names, + py::return_value_policy::reference, py::arg("mother_lv"), + py::arg("pv_name"), py::arg("node_path"), py::arg("ele_path"), + py::arg("region_id_to_mat_name"), py::arg("scale") = 1.0, + py::arg("check_overlaps") = false); +} diff --git a/opengate/actors/filters.py b/opengate/actors/filters.py index 100ce0926c..e39b6548c4 100644 --- a/opengate/actors/filters.py +++ b/opengate/actors/filters.py @@ -432,13 +432,49 @@ def initialize(self): super().initialize() -# Registry update +# Added filter: Python interface to the C++ GateCopyNumberFilter binding. +class CopyNumberFilter(FilterBase, g4.GateCopyNumberFilter): + """Filter steps using the current volume's Geant4 copy number. + + The copy number is read from the pre-step touchable at history depth 0. + A step is accepted when that number occurs in ``copy_numbers``. An empty + list disables copy-number filtering and therefore accepts every step. + """ + + copy_numbers: list + + user_info_defaults = { + "copy_numbers": ( + [], + { + "doc": ( + "Geant4 copy numbers accepted by this filter. " + "An empty list accepts all copy numbers." + ) + }, + ), + } + + def __init__(self, *args, **kwargs): + # Initialize OpenGATE's Python-side filter state first. + FilterBase.__init__(self, *args, **kwargs) + self.__initcpp__() + + def __initcpp__(self): + # Construct the matching pybind11 C++ base. InitializeUserInfo later + # transfers copy_numbers to GateCopyNumberFilter. + g4.GateCopyNumberFilter.__init__(self) + + +# Added registration: allow get_filter_class(), filter deserialization, and +# manager-side lookup to resolve the "CopyNumberFilter" type name. filter_classes = { "UnscatteredPrimaryFilter": UnscatteredPrimaryFilter, "AttributeFilterDouble": AttributeFilterDouble, "AttributeFilterInt": AttributeFilterInt, "AttributeFilterString": AttributeFilterString, "BooleanFilter": BooleanFilter, + "CopyNumberFilter": CopyNumberFilter, } @@ -486,3 +522,7 @@ def get_filter_class(f): process_cls(AttributeFilterDouble) process_cls(AttributeFilterInt) process_cls(AttributeFilterString) + +# Added class processing: install OpenGATE's generated properties and +# serialization hooks for CopyNumberFilter.user_info_defaults. +process_cls(CopyNumberFilter) diff --git a/opengate/contrib/__init__.py b/opengate/contrib/__init__.py index fa4ffe7389..7f026dfb13 100644 --- a/opengate/contrib/__init__.py +++ b/opengate/contrib/__init__.py @@ -1 +1 @@ -from . import dose, linacs, pet, phantoms, spect, tps, beamlines, optical +from . import dose, linacs, mrcp, pet, phantoms, spect, tps, beamlines, optical diff --git a/opengate/contrib/mrcp/README.md b/opengate/contrib/mrcp/README.md new file mode 100644 index 0000000000..ace7bd9e83 --- /dev/null +++ b/opengate/contrib/mrcp/README.md @@ -0,0 +1,78 @@ +# Tetrahedral MRCP phantom support + +This contribution demonstrates how to construct an OpenGATE geometry from +TetGen tetrahedral-mesh files used by MRCP-style computational phantoms. +The mesh is represented by one parameterised Geant4 daughter volume whose +copies use individual `G4Tet` solids. + +## Input files + +`TetrahedralMeshVolume` reads four files: + +- `*.node`: TetGen node coordinates. MRCP node coordinates are interpreted in + centimetres and converted to Geant4 length units while they are read. +- `*.ele`: four node indices and one region ID for each tetrahedron. +- `*.material`: material density and elemental mass fractions, together with + the region-to-material mapping. +- `*_colour.dat`: optional region ID followed by red, green, blue, and alpha + values in the range `[0, 1]`. + +The node reader accepts complete zero-based (`0..N-1`) or one-based (`1..N`) +indices. It rejects duplicate, incomplete, mixed, and out-of-range indices. + +## Minimal example + +```python +from pathlib import Path + +import opengate as gate + +data_dir = Path("opengate/contrib/mrcp") + +sim = gate.Simulation() +sim.world.size = [2 * gate.g4_units.m] * 3 +sim.world.material = "G4_Galactic" + +phantom = sim.add_volume("TetrahedralMesh", "phantom_tetmesh") +phantom.mother = "world" +phantom.material = "G4_Galactic" +phantom.node_file = str(data_dir / "simple.node") +phantom.ele_file = str(data_dir / "simple.ele") +phantom.material_file = str(data_dir / "simple.material") +phantom.color_file = str(data_dir / "simple_colour.dat") +phantom.default_material = "G4_Galactic" +phantom.check_overlaps = True + +sim.run() +``` + +Run `mrcp_simple.py` to initialize the example with Qt visualization enabled. +No particle source is configured because the script is a geometry example. + +## Synthetic datasets + +The directory contains synthetic, non-anatomical data so the implementation +can be demonstrated without redistributing a human phantom: + +- `simple.*`: a separated sphere approximation, cube, and regular + tetrahedron using region IDs 100, 200, and 300. +- `letter.*`: a separated three-dimensional `GATE` label using region IDs + 400, 500, 600, and 700. + +`keep_regions` may be set to a list of region IDs to retain selected regions. +Unmapped tetrahedra use `default_material`. Visualization colors and +visibility are taken from the color file when provided. + +## Copy-number filtering + +Each parameterised tetrahedron has its own Geant4 copy number. Actors can use +`CopyNumberFilter` when scoring must be restricted to selected tetrahedron +copies: + +```python +copy_filter = sim.add_filter("CopyNumberFilter", "selected_tetrahedra") +copy_filter.copy_numbers = [0, 1, 2] +``` + +An empty `copy_numbers` list disables copy-number selection and accepts all +valid steps. diff --git a/opengate/contrib/mrcp/letter.ele b/opengate/contrib/mrcp/letter.ele new file mode 100644 index 0000000000..5e3f7cf463 --- /dev/null +++ b/opengate/contrib/mrcp/letter.ele @@ -0,0 +1,901 @@ +900 4 1 +0 0 1 3 7 400 +1 0 3 2 7 400 +2 0 2 6 7 400 +3 0 6 4 7 400 +4 0 4 5 7 400 +5 0 5 1 7 400 +6 8 9 11 15 400 +7 8 11 10 15 400 +8 8 10 14 15 400 +9 8 14 12 15 400 +10 8 12 13 15 400 +11 8 13 9 15 400 +12 16 17 19 23 400 +13 16 19 18 23 400 +14 16 18 22 23 400 +15 16 22 20 23 400 +16 16 20 21 23 400 +17 16 21 17 23 400 +18 24 25 27 31 400 +19 24 27 26 31 400 +20 24 26 30 31 400 +21 24 30 28 31 400 +22 24 28 29 31 400 +23 24 29 25 31 400 +24 32 33 35 39 400 +25 32 35 34 39 400 +26 32 34 38 39 400 +27 32 38 36 39 400 +28 32 36 37 39 400 +29 32 37 33 39 400 +30 40 41 43 47 400 +31 40 43 42 47 400 +32 40 42 46 47 400 +33 40 46 44 47 400 +34 40 44 45 47 400 +35 40 45 41 47 400 +36 48 49 51 55 400 +37 48 51 50 55 400 +38 48 50 54 55 400 +39 48 54 52 55 400 +40 48 52 53 55 400 +41 48 53 49 55 400 +42 56 57 59 63 400 +43 56 59 58 63 400 +44 56 58 62 63 400 +45 56 62 60 63 400 +46 56 60 61 63 400 +47 56 61 57 63 400 +48 64 65 67 71 400 +49 64 67 66 71 400 +50 64 66 70 71 400 +51 64 70 68 71 400 +52 64 68 69 71 400 +53 64 69 65 71 400 +54 72 73 75 79 400 +55 72 75 74 79 400 +56 72 74 78 79 400 +57 72 78 76 79 400 +58 72 76 77 79 400 +59 72 77 73 79 400 +60 80 81 83 87 400 +61 80 83 82 87 400 +62 80 82 86 87 400 +63 80 86 84 87 400 +64 80 84 85 87 400 +65 80 85 81 87 400 +66 88 89 91 95 400 +67 88 91 90 95 400 +68 88 90 94 95 400 +69 88 94 92 95 400 +70 88 92 93 95 400 +71 88 93 89 95 400 +72 96 97 99 103 400 +73 96 99 98 103 400 +74 96 98 102 103 400 +75 96 102 100 103 400 +76 96 100 101 103 400 +77 96 101 97 103 400 +78 104 105 107 111 400 +79 104 107 106 111 400 +80 104 106 110 111 400 +81 104 110 108 111 400 +82 104 108 109 111 400 +83 104 109 105 111 400 +84 112 113 115 119 400 +85 112 115 114 119 400 +86 112 114 118 119 400 +87 112 118 116 119 400 +88 112 116 117 119 400 +89 112 117 113 119 400 +90 120 121 123 127 400 +91 120 123 122 127 400 +92 120 122 126 127 400 +93 120 126 124 127 400 +94 120 124 125 127 400 +95 120 125 121 127 400 +96 128 129 131 135 400 +97 128 131 130 135 400 +98 128 130 134 135 400 +99 128 134 132 135 400 +100 128 132 133 135 400 +101 128 133 129 135 400 +102 136 137 139 143 400 +103 136 139 138 143 400 +104 136 138 142 143 400 +105 136 142 140 143 400 +106 136 140 141 143 400 +107 136 141 137 143 400 +108 144 145 147 151 400 +109 144 147 146 151 400 +110 144 146 150 151 400 +111 144 150 148 151 400 +112 144 148 149 151 400 +113 144 149 145 151 400 +114 152 153 155 159 400 +115 152 155 154 159 400 +116 152 154 158 159 400 +117 152 158 156 159 400 +118 152 156 157 159 400 +119 152 157 153 159 400 +120 160 161 163 167 400 +121 160 163 162 167 400 +122 160 162 166 167 400 +123 160 166 164 167 400 +124 160 164 165 167 400 +125 160 165 161 167 400 +126 168 169 171 175 400 +127 168 171 170 175 400 +128 168 170 174 175 400 +129 168 174 172 175 400 +130 168 172 173 175 400 +131 168 173 169 175 400 +132 176 177 179 183 400 +133 176 179 178 183 400 +134 176 178 182 183 400 +135 176 182 180 183 400 +136 176 180 181 183 400 +137 176 181 177 183 400 +138 184 185 187 191 400 +139 184 187 186 191 400 +140 184 186 190 191 400 +141 184 190 188 191 400 +142 184 188 189 191 400 +143 184 189 185 191 400 +144 192 193 195 199 400 +145 192 195 194 199 400 +146 192 194 198 199 400 +147 192 198 196 199 400 +148 192 196 197 199 400 +149 192 197 193 199 400 +150 200 201 203 207 400 +151 200 203 202 207 400 +152 200 202 206 207 400 +153 200 206 204 207 400 +154 200 204 205 207 400 +155 200 205 201 207 400 +156 208 209 211 215 400 +157 208 211 210 215 400 +158 208 210 214 215 400 +159 208 214 212 215 400 +160 208 212 213 215 400 +161 208 213 209 215 400 +162 216 217 219 223 400 +163 216 219 218 223 400 +164 216 218 222 223 400 +165 216 222 220 223 400 +166 216 220 221 223 400 +167 216 221 217 223 400 +168 224 225 227 231 400 +169 224 227 226 231 400 +170 224 226 230 231 400 +171 224 230 228 231 400 +172 224 228 229 231 400 +173 224 229 225 231 400 +174 232 233 235 239 400 +175 232 235 234 239 400 +176 232 234 238 239 400 +177 232 238 236 239 400 +178 232 236 237 239 400 +179 232 237 233 239 400 +180 240 241 243 247 400 +181 240 243 242 247 400 +182 240 242 246 247 400 +183 240 246 244 247 400 +184 240 244 245 247 400 +185 240 245 241 247 400 +186 248 249 251 255 400 +187 248 251 250 255 400 +188 248 250 254 255 400 +189 248 254 252 255 400 +190 248 252 253 255 400 +191 248 253 249 255 400 +192 256 257 259 263 400 +193 256 259 258 263 400 +194 256 258 262 263 400 +195 256 262 260 263 400 +196 256 260 261 263 400 +197 256 261 257 263 400 +198 264 265 267 271 400 +199 264 267 266 271 400 +200 264 266 270 271 400 +201 264 270 268 271 400 +202 264 268 269 271 400 +203 264 269 265 271 400 +204 272 273 275 279 400 +205 272 275 274 279 400 +206 272 274 278 279 400 +207 272 278 276 279 400 +208 272 276 277 279 400 +209 272 277 273 279 400 +210 280 281 283 287 400 +211 280 283 282 287 400 +212 280 282 286 287 400 +213 280 286 284 287 400 +214 280 284 285 287 400 +215 280 285 281 287 400 +216 288 289 291 295 400 +217 288 291 290 295 400 +218 288 290 294 295 400 +219 288 294 292 295 400 +220 288 292 293 295 400 +221 288 293 289 295 400 +222 296 297 299 303 400 +223 296 299 298 303 400 +224 296 298 302 303 400 +225 296 302 300 303 400 +226 296 300 301 303 400 +227 296 301 297 303 400 +228 304 305 307 311 400 +229 304 307 306 311 400 +230 304 306 310 311 400 +231 304 310 308 311 400 +232 304 308 309 311 400 +233 304 309 305 311 400 +234 312 313 315 319 400 +235 312 315 314 319 400 +236 312 314 318 319 400 +237 312 318 316 319 400 +238 312 316 317 319 400 +239 312 317 313 319 400 +240 320 321 323 327 400 +241 320 323 322 327 400 +242 320 322 326 327 400 +243 320 326 324 327 400 +244 320 324 325 327 400 +245 320 325 321 327 400 +246 328 329 331 335 400 +247 328 331 330 335 400 +248 328 330 334 335 400 +249 328 334 332 335 400 +250 328 332 333 335 400 +251 328 333 329 335 400 +252 336 337 339 343 500 +253 336 339 338 343 500 +254 336 338 342 343 500 +255 336 342 340 343 500 +256 336 340 341 343 500 +257 336 341 337 343 500 +258 344 345 347 351 500 +259 344 347 346 351 500 +260 344 346 350 351 500 +261 344 350 348 351 500 +262 344 348 349 351 500 +263 344 349 345 351 500 +264 352 353 355 359 500 +265 352 355 354 359 500 +266 352 354 358 359 500 +267 352 358 356 359 500 +268 352 356 357 359 500 +269 352 357 353 359 500 +270 360 361 363 367 500 +271 360 363 362 367 500 +272 360 362 366 367 500 +273 360 366 364 367 500 +274 360 364 365 367 500 +275 360 365 361 367 500 +276 368 369 371 375 500 +277 368 371 370 375 500 +278 368 370 374 375 500 +279 368 374 372 375 500 +280 368 372 373 375 500 +281 368 373 369 375 500 +282 376 377 379 383 500 +283 376 379 378 383 500 +284 376 378 382 383 500 +285 376 382 380 383 500 +286 376 380 381 383 500 +287 376 381 377 383 500 +288 384 385 387 391 500 +289 384 387 386 391 500 +290 384 386 390 391 500 +291 384 390 388 391 500 +292 384 388 389 391 500 +293 384 389 385 391 500 +294 392 393 395 399 500 +295 392 395 394 399 500 +296 392 394 398 399 500 +297 392 398 396 399 500 +298 392 396 397 399 500 +299 392 397 393 399 500 +300 400 401 403 407 500 +301 400 403 402 407 500 +302 400 402 406 407 500 +303 400 406 404 407 500 +304 400 404 405 407 500 +305 400 405 401 407 500 +306 408 409 411 415 500 +307 408 411 410 415 500 +308 408 410 414 415 500 +309 408 414 412 415 500 +310 408 412 413 415 500 +311 408 413 409 415 500 +312 416 417 419 423 500 +313 416 419 418 423 500 +314 416 418 422 423 500 +315 416 422 420 423 500 +316 416 420 421 423 500 +317 416 421 417 423 500 +318 424 425 427 431 500 +319 424 427 426 431 500 +320 424 426 430 431 500 +321 424 430 428 431 500 +322 424 428 429 431 500 +323 424 429 425 431 500 +324 432 433 435 439 500 +325 432 435 434 439 500 +326 432 434 438 439 500 +327 432 438 436 439 500 +328 432 436 437 439 500 +329 432 437 433 439 500 +330 440 441 443 447 500 +331 440 443 442 447 500 +332 440 442 446 447 500 +333 440 446 444 447 500 +334 440 444 445 447 500 +335 440 445 441 447 500 +336 448 449 451 455 500 +337 448 451 450 455 500 +338 448 450 454 455 500 +339 448 454 452 455 500 +340 448 452 453 455 500 +341 448 453 449 455 500 +342 456 457 459 463 500 +343 456 459 458 463 500 +344 456 458 462 463 500 +345 456 462 460 463 500 +346 456 460 461 463 500 +347 456 461 457 463 500 +348 464 465 467 471 500 +349 464 467 466 471 500 +350 464 466 470 471 500 +351 464 470 468 471 500 +352 464 468 469 471 500 +353 464 469 465 471 500 +354 472 473 475 479 500 +355 472 475 474 479 500 +356 472 474 478 479 500 +357 472 478 476 479 500 +358 472 476 477 479 500 +359 472 477 473 479 500 +360 480 481 483 487 500 +361 480 483 482 487 500 +362 480 482 486 487 500 +363 480 486 484 487 500 +364 480 484 485 487 500 +365 480 485 481 487 500 +366 488 489 491 495 500 +367 488 491 490 495 500 +368 488 490 494 495 500 +369 488 494 492 495 500 +370 488 492 493 495 500 +371 488 493 489 495 500 +372 496 497 499 503 500 +373 496 499 498 503 500 +374 496 498 502 503 500 +375 496 502 500 503 500 +376 496 500 501 503 500 +377 496 501 497 503 500 +378 504 505 507 511 500 +379 504 507 506 511 500 +380 504 506 510 511 500 +381 504 510 508 511 500 +382 504 508 509 511 500 +383 504 509 505 511 500 +384 512 513 515 519 500 +385 512 515 514 519 500 +386 512 514 518 519 500 +387 512 518 516 519 500 +388 512 516 517 519 500 +389 512 517 513 519 500 +390 520 521 523 527 500 +391 520 523 522 527 500 +392 520 522 526 527 500 +393 520 526 524 527 500 +394 520 524 525 527 500 +395 520 525 521 527 500 +396 528 529 531 535 500 +397 528 531 530 535 500 +398 528 530 534 535 500 +399 528 534 532 535 500 +400 528 532 533 535 500 +401 528 533 529 535 500 +402 536 537 539 543 500 +403 536 539 538 543 500 +404 536 538 542 543 500 +405 536 542 540 543 500 +406 536 540 541 543 500 +407 536 541 537 543 500 +408 544 545 547 551 500 +409 544 547 546 551 500 +410 544 546 550 551 500 +411 544 550 548 551 500 +412 544 548 549 551 500 +413 544 549 545 551 500 +414 552 553 555 559 500 +415 552 555 554 559 500 +416 552 554 558 559 500 +417 552 558 556 559 500 +418 552 556 557 559 500 +419 552 557 553 559 500 +420 560 561 563 567 500 +421 560 563 562 567 500 +422 560 562 566 567 500 +423 560 566 564 567 500 +424 560 564 565 567 500 +425 560 565 561 567 500 +426 568 569 571 575 500 +427 568 571 570 575 500 +428 568 570 574 575 500 +429 568 574 572 575 500 +430 568 572 573 575 500 +431 568 573 569 575 500 +432 576 577 579 583 500 +433 576 579 578 583 500 +434 576 578 582 583 500 +435 576 582 580 583 500 +436 576 580 581 583 500 +437 576 581 577 583 500 +438 584 585 587 591 500 +439 584 587 586 591 500 +440 584 586 590 591 500 +441 584 590 588 591 500 +442 584 588 589 591 500 +443 584 589 585 591 500 +444 592 593 595 599 500 +445 592 595 594 599 500 +446 592 594 598 599 500 +447 592 598 596 599 500 +448 592 596 597 599 500 +449 592 597 593 599 500 +450 600 601 603 607 500 +451 600 603 602 607 500 +452 600 602 606 607 500 +453 600 606 604 607 500 +454 600 604 605 607 500 +455 600 605 601 607 500 +456 608 609 611 615 500 +457 608 611 610 615 500 +458 608 610 614 615 500 +459 608 614 612 615 500 +460 608 612 613 615 500 +461 608 613 609 615 500 +462 616 617 619 623 500 +463 616 619 618 623 500 +464 616 618 622 623 500 +465 616 622 620 623 500 +466 616 620 621 623 500 +467 616 621 617 623 500 +468 624 625 627 631 500 +469 624 627 626 631 500 +470 624 626 630 631 500 +471 624 630 628 631 500 +472 624 628 629 631 500 +473 624 629 625 631 500 +474 632 633 635 639 500 +475 632 635 634 639 500 +476 632 634 638 639 500 +477 632 638 636 639 500 +478 632 636 637 639 500 +479 632 637 633 639 500 +480 640 641 643 647 500 +481 640 643 642 647 500 +482 640 642 646 647 500 +483 640 646 644 647 500 +484 640 644 645 647 500 +485 640 645 641 647 500 +486 648 649 651 655 500 +487 648 651 650 655 500 +488 648 650 654 655 500 +489 648 654 652 655 500 +490 648 652 653 655 500 +491 648 653 649 655 500 +492 656 657 659 663 500 +493 656 659 658 663 500 +494 656 658 662 663 500 +495 656 662 660 663 500 +496 656 660 661 663 500 +497 656 661 657 663 500 +498 664 665 667 671 600 +499 664 667 666 671 600 +500 664 666 670 671 600 +501 664 670 668 671 600 +502 664 668 669 671 600 +503 664 669 665 671 600 +504 672 673 675 679 600 +505 672 675 674 679 600 +506 672 674 678 679 600 +507 672 678 676 679 600 +508 672 676 677 679 600 +509 672 677 673 679 600 +510 680 681 683 687 600 +511 680 683 682 687 600 +512 680 682 686 687 600 +513 680 686 684 687 600 +514 680 684 685 687 600 +515 680 685 681 687 600 +516 688 689 691 695 600 +517 688 691 690 695 600 +518 688 690 694 695 600 +519 688 694 692 695 600 +520 688 692 693 695 600 +521 688 693 689 695 600 +522 696 697 699 703 600 +523 696 699 698 703 600 +524 696 698 702 703 600 +525 696 702 700 703 600 +526 696 700 701 703 600 +527 696 701 697 703 600 +528 704 705 707 711 600 +529 704 707 706 711 600 +530 704 706 710 711 600 +531 704 710 708 711 600 +532 704 708 709 711 600 +533 704 709 705 711 600 +534 712 713 715 719 600 +535 712 715 714 719 600 +536 712 714 718 719 600 +537 712 718 716 719 600 +538 712 716 717 719 600 +539 712 717 713 719 600 +540 720 721 723 727 600 +541 720 723 722 727 600 +542 720 722 726 727 600 +543 720 726 724 727 600 +544 720 724 725 727 600 +545 720 725 721 727 600 +546 728 729 731 735 600 +547 728 731 730 735 600 +548 728 730 734 735 600 +549 728 734 732 735 600 +550 728 732 733 735 600 +551 728 733 729 735 600 +552 736 737 739 743 600 +553 736 739 738 743 600 +554 736 738 742 743 600 +555 736 742 740 743 600 +556 736 740 741 743 600 +557 736 741 737 743 600 +558 744 745 747 751 600 +559 744 747 746 751 600 +560 744 746 750 751 600 +561 744 750 748 751 600 +562 744 748 749 751 600 +563 744 749 745 751 600 +564 752 753 755 759 600 +565 752 755 754 759 600 +566 752 754 758 759 600 +567 752 758 756 759 600 +568 752 756 757 759 600 +569 752 757 753 759 600 +570 760 761 763 767 600 +571 760 763 762 767 600 +572 760 762 766 767 600 +573 760 766 764 767 600 +574 760 764 765 767 600 +575 760 765 761 767 600 +576 768 769 771 775 600 +577 768 771 770 775 600 +578 768 770 774 775 600 +579 768 774 772 775 600 +580 768 772 773 775 600 +581 768 773 769 775 600 +582 776 777 779 783 600 +583 776 779 778 783 600 +584 776 778 782 783 600 +585 776 782 780 783 600 +586 776 780 781 783 600 +587 776 781 777 783 600 +588 784 785 787 791 600 +589 784 787 786 791 600 +590 784 786 790 791 600 +591 784 790 788 791 600 +592 784 788 789 791 600 +593 784 789 785 791 600 +594 792 793 795 799 600 +595 792 795 794 799 600 +596 792 794 798 799 600 +597 792 798 796 799 600 +598 792 796 797 799 600 +599 792 797 793 799 600 +600 800 801 803 807 600 +601 800 803 802 807 600 +602 800 802 806 807 600 +603 800 806 804 807 600 +604 800 804 805 807 600 +605 800 805 801 807 600 +606 808 809 811 815 600 +607 808 811 810 815 600 +608 808 810 814 815 600 +609 808 814 812 815 600 +610 808 812 813 815 600 +611 808 813 809 815 600 +612 816 817 819 823 600 +613 816 819 818 823 600 +614 816 818 822 823 600 +615 816 822 820 823 600 +616 816 820 821 823 600 +617 816 821 817 823 600 +618 824 825 827 831 600 +619 824 827 826 831 600 +620 824 826 830 831 600 +621 824 830 828 831 600 +622 824 828 829 831 600 +623 824 829 825 831 600 +624 832 833 835 839 600 +625 832 835 834 839 600 +626 832 834 838 839 600 +627 832 838 836 839 600 +628 832 836 837 839 600 +629 832 837 833 839 600 +630 840 841 843 847 600 +631 840 843 842 847 600 +632 840 842 846 847 600 +633 840 846 844 847 600 +634 840 844 845 847 600 +635 840 845 841 847 600 +636 848 849 851 855 600 +637 848 851 850 855 600 +638 848 850 854 855 600 +639 848 854 852 855 600 +640 848 852 853 855 600 +641 848 853 849 855 600 +642 856 857 859 863 600 +643 856 859 858 863 600 +644 856 858 862 863 600 +645 856 862 860 863 600 +646 856 860 861 863 600 +647 856 861 857 863 600 +648 864 865 867 871 600 +649 864 867 866 871 600 +650 864 866 870 871 600 +651 864 870 868 871 600 +652 864 868 869 871 600 +653 864 869 865 871 600 +654 872 873 875 879 600 +655 872 875 874 879 600 +656 872 874 878 879 600 +657 872 878 876 879 600 +658 872 876 877 879 600 +659 872 877 873 879 600 +660 880 881 883 887 600 +661 880 883 882 887 600 +662 880 882 886 887 600 +663 880 886 884 887 600 +664 880 884 885 887 600 +665 880 885 881 887 600 +666 888 889 891 895 600 +667 888 891 890 895 600 +668 888 890 894 895 600 +669 888 894 892 895 600 +670 888 892 893 895 600 +671 888 893 889 895 600 +672 896 897 899 903 600 +673 896 899 898 903 600 +674 896 898 902 903 600 +675 896 902 900 903 600 +676 896 900 901 903 600 +677 896 901 897 903 600 +678 904 905 907 911 600 +679 904 907 906 911 600 +680 904 906 910 911 600 +681 904 910 908 911 600 +682 904 908 909 911 600 +683 904 909 905 911 600 +684 912 913 915 919 600 +685 912 915 914 919 600 +686 912 914 918 919 600 +687 912 918 916 919 600 +688 912 916 917 919 600 +689 912 917 913 919 600 +690 920 921 923 927 600 +691 920 923 922 927 600 +692 920 922 926 927 600 +693 920 926 924 927 600 +694 920 924 925 927 600 +695 920 925 921 927 600 +696 928 929 931 935 600 +697 928 931 930 935 600 +698 928 930 934 935 600 +699 928 934 932 935 600 +700 928 932 933 935 600 +701 928 933 929 935 600 +702 936 937 939 943 600 +703 936 939 938 943 600 +704 936 938 942 943 600 +705 936 942 940 943 600 +706 936 940 941 943 600 +707 936 941 937 943 600 +708 944 945 947 951 600 +709 944 947 946 951 600 +710 944 946 950 951 600 +711 944 950 948 951 600 +712 944 948 949 951 600 +713 944 949 945 951 600 +714 952 953 955 959 700 +715 952 955 954 959 700 +716 952 954 958 959 700 +717 952 958 956 959 700 +718 952 956 957 959 700 +719 952 957 953 959 700 +720 960 961 963 967 700 +721 960 963 962 967 700 +722 960 962 966 967 700 +723 960 966 964 967 700 +724 960 964 965 967 700 +725 960 965 961 967 700 +726 968 969 971 975 700 +727 968 971 970 975 700 +728 968 970 974 975 700 +729 968 974 972 975 700 +730 968 972 973 975 700 +731 968 973 969 975 700 +732 976 977 979 983 700 +733 976 979 978 983 700 +734 976 978 982 983 700 +735 976 982 980 983 700 +736 976 980 981 983 700 +737 976 981 977 983 700 +738 984 985 987 991 700 +739 984 987 986 991 700 +740 984 986 990 991 700 +741 984 990 988 991 700 +742 984 988 989 991 700 +743 984 989 985 991 700 +744 992 993 995 999 700 +745 992 995 994 999 700 +746 992 994 998 999 700 +747 992 998 996 999 700 +748 992 996 997 999 700 +749 992 997 993 999 700 +750 1000 1001 1003 1007 700 +751 1000 1003 1002 1007 700 +752 1000 1002 1006 1007 700 +753 1000 1006 1004 1007 700 +754 1000 1004 1005 1007 700 +755 1000 1005 1001 1007 700 +756 1008 1009 1011 1015 700 +757 1008 1011 1010 1015 700 +758 1008 1010 1014 1015 700 +759 1008 1014 1012 1015 700 +760 1008 1012 1013 1015 700 +761 1008 1013 1009 1015 700 +762 1016 1017 1019 1023 700 +763 1016 1019 1018 1023 700 +764 1016 1018 1022 1023 700 +765 1016 1022 1020 1023 700 +766 1016 1020 1021 1023 700 +767 1016 1021 1017 1023 700 +768 1024 1025 1027 1031 700 +769 1024 1027 1026 1031 700 +770 1024 1026 1030 1031 700 +771 1024 1030 1028 1031 700 +772 1024 1028 1029 1031 700 +773 1024 1029 1025 1031 700 +774 1032 1033 1035 1039 700 +775 1032 1035 1034 1039 700 +776 1032 1034 1038 1039 700 +777 1032 1038 1036 1039 700 +778 1032 1036 1037 1039 700 +779 1032 1037 1033 1039 700 +780 1040 1041 1043 1047 700 +781 1040 1043 1042 1047 700 +782 1040 1042 1046 1047 700 +783 1040 1046 1044 1047 700 +784 1040 1044 1045 1047 700 +785 1040 1045 1041 1047 700 +786 1048 1049 1051 1055 700 +787 1048 1051 1050 1055 700 +788 1048 1050 1054 1055 700 +789 1048 1054 1052 1055 700 +790 1048 1052 1053 1055 700 +791 1048 1053 1049 1055 700 +792 1056 1057 1059 1063 700 +793 1056 1059 1058 1063 700 +794 1056 1058 1062 1063 700 +795 1056 1062 1060 1063 700 +796 1056 1060 1061 1063 700 +797 1056 1061 1057 1063 700 +798 1064 1065 1067 1071 700 +799 1064 1067 1066 1071 700 +800 1064 1066 1070 1071 700 +801 1064 1070 1068 1071 700 +802 1064 1068 1069 1071 700 +803 1064 1069 1065 1071 700 +804 1072 1073 1075 1079 700 +805 1072 1075 1074 1079 700 +806 1072 1074 1078 1079 700 +807 1072 1078 1076 1079 700 +808 1072 1076 1077 1079 700 +809 1072 1077 1073 1079 700 +810 1080 1081 1083 1087 700 +811 1080 1083 1082 1087 700 +812 1080 1082 1086 1087 700 +813 1080 1086 1084 1087 700 +814 1080 1084 1085 1087 700 +815 1080 1085 1081 1087 700 +816 1088 1089 1091 1095 700 +817 1088 1091 1090 1095 700 +818 1088 1090 1094 1095 700 +819 1088 1094 1092 1095 700 +820 1088 1092 1093 1095 700 +821 1088 1093 1089 1095 700 +822 1096 1097 1099 1103 700 +823 1096 1099 1098 1103 700 +824 1096 1098 1102 1103 700 +825 1096 1102 1100 1103 700 +826 1096 1100 1101 1103 700 +827 1096 1101 1097 1103 700 +828 1104 1105 1107 1111 700 +829 1104 1107 1106 1111 700 +830 1104 1106 1110 1111 700 +831 1104 1110 1108 1111 700 +832 1104 1108 1109 1111 700 +833 1104 1109 1105 1111 700 +834 1112 1113 1115 1119 700 +835 1112 1115 1114 1119 700 +836 1112 1114 1118 1119 700 +837 1112 1118 1116 1119 700 +838 1112 1116 1117 1119 700 +839 1112 1117 1113 1119 700 +840 1120 1121 1123 1127 700 +841 1120 1123 1122 1127 700 +842 1120 1122 1126 1127 700 +843 1120 1126 1124 1127 700 +844 1120 1124 1125 1127 700 +845 1120 1125 1121 1127 700 +846 1128 1129 1131 1135 700 +847 1128 1131 1130 1135 700 +848 1128 1130 1134 1135 700 +849 1128 1134 1132 1135 700 +850 1128 1132 1133 1135 700 +851 1128 1133 1129 1135 700 +852 1136 1137 1139 1143 700 +853 1136 1139 1138 1143 700 +854 1136 1138 1142 1143 700 +855 1136 1142 1140 1143 700 +856 1136 1140 1141 1143 700 +857 1136 1141 1137 1143 700 +858 1144 1145 1147 1151 700 +859 1144 1147 1146 1151 700 +860 1144 1146 1150 1151 700 +861 1144 1150 1148 1151 700 +862 1144 1148 1149 1151 700 +863 1144 1149 1145 1151 700 +864 1152 1153 1155 1159 700 +865 1152 1155 1154 1159 700 +866 1152 1154 1158 1159 700 +867 1152 1158 1156 1159 700 +868 1152 1156 1157 1159 700 +869 1152 1157 1153 1159 700 +870 1160 1161 1163 1167 700 +871 1160 1163 1162 1167 700 +872 1160 1162 1166 1167 700 +873 1160 1166 1164 1167 700 +874 1160 1164 1165 1167 700 +875 1160 1165 1161 1167 700 +876 1168 1169 1171 1175 700 +877 1168 1171 1170 1175 700 +878 1168 1170 1174 1175 700 +879 1168 1174 1172 1175 700 +880 1168 1172 1173 1175 700 +881 1168 1173 1169 1175 700 +882 1176 1177 1179 1183 700 +883 1176 1179 1178 1183 700 +884 1176 1178 1182 1183 700 +885 1176 1182 1180 1183 700 +886 1176 1180 1181 1183 700 +887 1176 1181 1177 1183 700 +888 1184 1185 1187 1191 700 +889 1184 1187 1186 1191 700 +890 1184 1186 1190 1191 700 +891 1184 1190 1188 1191 700 +892 1184 1188 1189 1191 700 +893 1184 1189 1185 1191 700 +894 1192 1193 1195 1199 700 +895 1192 1195 1194 1199 700 +896 1192 1194 1198 1199 700 +897 1192 1198 1196 1199 700 +898 1192 1196 1197 1199 700 +899 1192 1197 1193 1199 700 diff --git a/opengate/contrib/mrcp/letter.material b/opengate/contrib/mrcp/letter.material new file mode 100644 index 0000000000..b7ef71983c --- /dev/null +++ b/opengate/contrib/mrcp/letter.material @@ -0,0 +1,16 @@ +C LetterG 1.100 g/cm3 +m400 6000 -0.500 + 8000 -0.500 +C +C LetterA 1.100 g/cm3 +m500 6000 -0.500 + 8000 -0.500 +C +C LetterT 1.100 g/cm3 +m600 6000 -0.500 + 8000 -0.500 +C +C LetterE 1.100 g/cm3 +m700 6000 -0.500 + 8000 -0.500 +C diff --git a/opengate/contrib/mrcp/letter.node b/opengate/contrib/mrcp/letter.node new file mode 100644 index 0000000000..818186ba46 --- /dev/null +++ b/opengate/contrib/mrcp/letter.node @@ -0,0 +1,1201 @@ +1200 3 0 0 +0 -16.710938 -0.202188 -1.000000 +1 -15.930938 -0.202188 -1.000000 +2 -16.710938 0.577813 -1.000000 +3 -15.930938 0.577813 -1.000000 +4 -16.710938 -0.202188 1.000000 +5 -15.930938 -0.202188 1.000000 +6 -16.710938 0.577813 1.000000 +7 -15.930938 0.577813 1.000000 +8 -15.910938 -0.202188 -1.000000 +9 -15.130938 -0.202188 -1.000000 +10 -15.910938 0.577813 -1.000000 +11 -15.130938 0.577813 -1.000000 +12 -15.910938 -0.202188 1.000000 +13 -15.130938 -0.202188 1.000000 +14 -15.910938 0.577813 1.000000 +15 -15.130938 0.577813 1.000000 +16 -15.110938 -0.202188 -1.000000 +17 -14.330937 -0.202188 -1.000000 +18 -15.110938 0.577813 -1.000000 +19 -14.330937 0.577813 -1.000000 +20 -15.110938 -0.202188 1.000000 +21 -14.330937 -0.202188 1.000000 +22 -15.110938 0.577813 1.000000 +23 -14.330937 0.577813 1.000000 +24 -14.310937 -0.202188 -1.000000 +25 -13.530937 -0.202188 -1.000000 +26 -14.310937 0.577813 -1.000000 +27 -13.530937 0.577813 -1.000000 +28 -14.310937 -0.202188 1.000000 +29 -13.530937 -0.202188 1.000000 +30 -14.310937 0.577813 1.000000 +31 -13.530937 0.577813 1.000000 +32 -13.510938 -0.202188 -1.000000 +33 -12.730937 -0.202188 -1.000000 +34 -13.510938 0.577813 -1.000000 +35 -12.730937 0.577813 -1.000000 +36 -13.510938 -0.202188 1.000000 +37 -12.730937 -0.202188 1.000000 +38 -13.510938 0.577813 1.000000 +39 -12.730937 0.577813 1.000000 +40 -18.310937 0.597813 -1.000000 +41 -17.530938 0.597813 -1.000000 +42 -18.310937 1.377813 -1.000000 +43 -17.530938 1.377813 -1.000000 +44 -18.310937 0.597813 1.000000 +45 -17.530938 0.597813 1.000000 +46 -18.310937 1.377813 1.000000 +47 -17.530938 1.377813 1.000000 +48 -17.510938 0.597813 -1.000000 +49 -16.730938 0.597813 -1.000000 +50 -17.510938 1.377813 -1.000000 +51 -16.730938 1.377813 -1.000000 +52 -17.510938 0.597813 1.000000 +53 -16.730938 0.597813 1.000000 +54 -17.510938 1.377813 1.000000 +55 -16.730938 1.377813 1.000000 +56 -12.710938 0.597813 -1.000000 +57 -11.930938 0.597813 -1.000000 +58 -12.710938 1.377813 -1.000000 +59 -11.930938 1.377813 -1.000000 +60 -12.710938 0.597813 1.000000 +61 -11.930938 0.597813 1.000000 +62 -12.710938 1.377813 1.000000 +63 -11.930938 1.377813 1.000000 +64 -19.110938 1.397813 -1.000000 +65 -18.330937 1.397813 -1.000000 +66 -19.110938 2.177813 -1.000000 +67 -18.330937 2.177813 -1.000000 +68 -19.110938 1.397813 1.000000 +69 -18.330937 1.397813 1.000000 +70 -19.110938 2.177813 1.000000 +71 -18.330937 2.177813 1.000000 +72 -18.310937 1.397813 -1.000000 +73 -17.530938 1.397813 -1.000000 +74 -18.310937 2.177813 -1.000000 +75 -17.530938 2.177813 -1.000000 +76 -18.310937 1.397813 1.000000 +77 -17.530938 1.397813 1.000000 +78 -18.310937 2.177813 1.000000 +79 -17.530938 2.177813 1.000000 +80 -12.710938 1.397813 -1.000000 +81 -11.930938 1.397813 -1.000000 +82 -12.710938 2.177813 -1.000000 +83 -11.930938 2.177813 -1.000000 +84 -12.710938 1.397813 1.000000 +85 -11.930938 1.397813 1.000000 +86 -12.710938 2.177813 1.000000 +87 -11.930938 2.177813 1.000000 +88 -19.110938 2.197813 -1.000000 +89 -18.330937 2.197813 -1.000000 +90 -19.110938 2.977812 -1.000000 +91 -18.330937 2.977812 -1.000000 +92 -19.110938 2.197813 1.000000 +93 -18.330937 2.197813 1.000000 +94 -19.110938 2.977812 1.000000 +95 -18.330937 2.977812 1.000000 +96 -18.310937 2.197813 -1.000000 +97 -17.530938 2.197813 -1.000000 +98 -18.310937 2.977812 -1.000000 +99 -17.530938 2.977812 -1.000000 +100 -18.310937 2.197813 1.000000 +101 -17.530938 2.197813 1.000000 +102 -18.310937 2.977812 1.000000 +103 -17.530938 2.977812 1.000000 +104 -12.710938 2.197813 -1.000000 +105 -11.930938 2.197813 -1.000000 +106 -12.710938 2.977812 -1.000000 +107 -11.930938 2.977812 -1.000000 +108 -12.710938 2.197813 1.000000 +109 -11.930938 2.197813 1.000000 +110 -12.710938 2.977812 1.000000 +111 -11.930938 2.977812 1.000000 +112 -19.910938 2.997812 -1.000000 +113 -19.130938 2.997812 -1.000000 +114 -19.910938 3.777812 -1.000000 +115 -19.130938 3.777812 -1.000000 +116 -19.910938 2.997812 1.000000 +117 -19.130938 2.997812 1.000000 +118 -19.910938 3.777812 1.000000 +119 -19.130938 3.777812 1.000000 +120 -19.110938 2.997812 -1.000000 +121 -18.330937 2.997812 -1.000000 +122 -19.110938 3.777812 -1.000000 +123 -18.330937 3.777812 -1.000000 +124 -19.110938 2.997812 1.000000 +125 -18.330937 2.997812 1.000000 +126 -19.110938 3.777812 1.000000 +127 -18.330937 3.777812 1.000000 +128 -12.710938 2.997812 -1.000000 +129 -11.930938 2.997812 -1.000000 +130 -12.710938 3.777812 -1.000000 +131 -11.930938 3.777812 -1.000000 +132 -12.710938 2.997812 1.000000 +133 -11.930938 2.997812 1.000000 +134 -12.710938 3.777812 1.000000 +135 -11.930938 3.777812 1.000000 +136 -19.910938 3.797813 -1.000000 +137 -19.130938 3.797813 -1.000000 +138 -19.910938 4.577813 -1.000000 +139 -19.130938 4.577813 -1.000000 +140 -19.910938 3.797813 1.000000 +141 -19.130938 3.797813 1.000000 +142 -19.910938 4.577813 1.000000 +143 -19.130938 4.577813 1.000000 +144 -19.110938 3.797813 -1.000000 +145 -18.330937 3.797813 -1.000000 +146 -19.110938 4.577813 -1.000000 +147 -18.330937 4.577813 -1.000000 +148 -19.110938 3.797813 1.000000 +149 -18.330937 3.797813 1.000000 +150 -19.110938 4.577813 1.000000 +151 -18.330937 4.577813 1.000000 +152 -12.710938 3.797813 -1.000000 +153 -11.930938 3.797813 -1.000000 +154 -12.710938 4.577813 -1.000000 +155 -11.930938 4.577813 -1.000000 +156 -12.710938 3.797813 1.000000 +157 -11.930938 3.797813 1.000000 +158 -12.710938 4.577813 1.000000 +159 -11.930938 4.577813 1.000000 +160 -11.910938 3.797813 -1.000000 +161 -11.130938 3.797813 -1.000000 +162 -11.910938 4.577813 -1.000000 +163 -11.130938 4.577813 -1.000000 +164 -11.910938 3.797813 1.000000 +165 -11.130938 3.797813 1.000000 +166 -11.910938 4.577813 1.000000 +167 -11.130938 4.577813 1.000000 +168 -19.910938 4.597813 -1.000000 +169 -19.130938 4.597813 -1.000000 +170 -19.910938 5.377813 -1.000000 +171 -19.130938 5.377813 -1.000000 +172 -19.910938 4.597813 1.000000 +173 -19.130938 4.597813 1.000000 +174 -19.910938 5.377813 1.000000 +175 -19.130938 5.377813 1.000000 +176 -19.110938 4.597813 -1.000000 +177 -18.330937 4.597813 -1.000000 +178 -19.110938 5.377813 -1.000000 +179 -18.330937 5.377813 -1.000000 +180 -19.110938 4.597813 1.000000 +181 -18.330937 4.597813 1.000000 +182 -19.110938 5.377813 1.000000 +183 -18.330937 5.377813 1.000000 +184 -14.310937 4.597813 -1.000000 +185 -13.530937 4.597813 -1.000000 +186 -14.310937 5.377813 -1.000000 +187 -13.530937 5.377813 -1.000000 +188 -14.310937 4.597813 1.000000 +189 -13.530937 4.597813 1.000000 +190 -14.310937 5.377813 1.000000 +191 -13.530937 5.377813 1.000000 +192 -13.510938 4.597813 -1.000000 +193 -12.730937 4.597813 -1.000000 +194 -13.510938 5.377813 -1.000000 +195 -12.730937 5.377813 -1.000000 +196 -13.510938 4.597813 1.000000 +197 -12.730937 4.597813 1.000000 +198 -13.510938 5.377813 1.000000 +199 -12.730937 5.377813 1.000000 +200 -12.710938 4.597813 -1.000000 +201 -11.930938 4.597813 -1.000000 +202 -12.710938 5.377813 -1.000000 +203 -11.930938 5.377813 -1.000000 +204 -12.710938 4.597813 1.000000 +205 -11.930938 4.597813 1.000000 +206 -12.710938 5.377813 1.000000 +207 -11.930938 5.377813 1.000000 +208 -11.910938 4.597813 -1.000000 +209 -11.130938 4.597813 -1.000000 +210 -11.910938 5.377813 -1.000000 +211 -11.130938 5.377813 -1.000000 +212 -11.910938 4.597813 1.000000 +213 -11.130938 4.597813 1.000000 +214 -11.910938 5.377813 1.000000 +215 -11.130938 5.377813 1.000000 +216 -11.110938 4.597813 -1.000000 +217 -10.330937 4.597813 -1.000000 +218 -11.110938 5.377813 -1.000000 +219 -10.330937 5.377813 -1.000000 +220 -11.110938 4.597813 1.000000 +221 -10.330937 4.597813 1.000000 +222 -11.110938 5.377813 1.000000 +223 -10.330937 5.377813 1.000000 +224 -19.910938 5.397812 -1.000000 +225 -19.130938 5.397812 -1.000000 +226 -19.910938 6.177813 -1.000000 +227 -19.130938 6.177813 -1.000000 +228 -19.910938 5.397812 1.000000 +229 -19.130938 5.397812 1.000000 +230 -19.910938 6.177813 1.000000 +231 -19.130938 6.177813 1.000000 +232 -19.110938 5.397812 -1.000000 +233 -18.330937 5.397812 -1.000000 +234 -19.110938 6.177813 -1.000000 +235 -18.330937 6.177813 -1.000000 +236 -19.110938 5.397812 1.000000 +237 -18.330937 5.397812 1.000000 +238 -19.110938 6.177813 1.000000 +239 -18.330937 6.177813 1.000000 +240 -19.110938 6.197813 -1.000000 +241 -18.330937 6.197813 -1.000000 +242 -19.110938 6.977813 -1.000000 +243 -18.330937 6.977813 -1.000000 +244 -19.110938 6.197813 1.000000 +245 -18.330937 6.197813 1.000000 +246 -19.110938 6.977813 1.000000 +247 -18.330937 6.977813 1.000000 +248 -19.110938 6.997813 -1.000000 +249 -18.330937 6.997813 -1.000000 +250 -19.110938 7.777813 -1.000000 +251 -18.330937 7.777813 -1.000000 +252 -19.110938 6.997813 1.000000 +253 -18.330937 6.997813 1.000000 +254 -19.110938 7.777813 1.000000 +255 -18.330937 7.777813 1.000000 +256 -18.310937 6.997813 -1.000000 +257 -17.530938 6.997813 -1.000000 +258 -18.310937 7.777813 -1.000000 +259 -17.530938 7.777813 -1.000000 +260 -18.310937 6.997813 1.000000 +261 -17.530938 6.997813 1.000000 +262 -18.310937 7.777813 1.000000 +263 -17.530938 7.777813 1.000000 +264 -18.310937 7.797812 -1.000000 +265 -17.530938 7.797812 -1.000000 +266 -18.310937 8.577813 -1.000000 +267 -17.530938 8.577813 -1.000000 +268 -18.310937 7.797812 1.000000 +269 -17.530938 7.797812 1.000000 +270 -18.310937 8.577813 1.000000 +271 -17.530938 8.577813 1.000000 +272 -17.510938 7.797812 -1.000000 +273 -16.730938 7.797812 -1.000000 +274 -17.510938 8.577813 -1.000000 +275 -16.730938 8.577813 -1.000000 +276 -17.510938 7.797812 1.000000 +277 -16.730938 7.797812 1.000000 +278 -17.510938 8.577813 1.000000 +279 -16.730938 8.577813 1.000000 +280 -12.710938 7.797812 -1.000000 +281 -11.930938 7.797812 -1.000000 +282 -12.710938 8.577813 -1.000000 +283 -11.930938 8.577813 -1.000000 +284 -12.710938 7.797812 1.000000 +285 -11.930938 7.797812 1.000000 +286 -12.710938 8.577813 1.000000 +287 -11.930938 8.577813 1.000000 +288 -17.510938 8.597813 -1.000000 +289 -16.730938 8.597813 -1.000000 +290 -17.510938 9.377813 -1.000000 +291 -16.730938 9.377813 -1.000000 +292 -17.510938 8.597813 1.000000 +293 -16.730938 8.597813 1.000000 +294 -17.510938 9.377813 1.000000 +295 -16.730938 9.377813 1.000000 +296 -16.710938 8.597813 -1.000000 +297 -15.930938 8.597813 -1.000000 +298 -16.710938 9.377813 -1.000000 +299 -15.930938 9.377813 -1.000000 +300 -16.710938 8.597813 1.000000 +301 -15.930938 8.597813 1.000000 +302 -16.710938 9.377813 1.000000 +303 -15.930938 9.377813 1.000000 +304 -15.910938 8.597813 -1.000000 +305 -15.130938 8.597813 -1.000000 +306 -15.910938 9.377813 -1.000000 +307 -15.130938 9.377813 -1.000000 +308 -15.910938 8.597813 1.000000 +309 -15.130938 8.597813 1.000000 +310 -15.910938 9.377813 1.000000 +311 -15.130938 9.377813 1.000000 +312 -15.110938 8.597813 -1.000000 +313 -14.330937 8.597813 -1.000000 +314 -15.110938 9.377813 -1.000000 +315 -14.330937 9.377813 -1.000000 +316 -15.110938 8.597813 1.000000 +317 -14.330937 8.597813 1.000000 +318 -15.110938 9.377813 1.000000 +319 -14.330937 9.377813 1.000000 +320 -14.310937 8.597813 -1.000000 +321 -13.530937 8.597813 -1.000000 +322 -14.310937 9.377813 -1.000000 +323 -13.530937 9.377813 -1.000000 +324 -14.310937 8.597813 1.000000 +325 -13.530937 8.597813 1.000000 +326 -14.310937 9.377813 1.000000 +327 -13.530937 9.377813 1.000000 +328 -13.510938 8.597813 -1.000000 +329 -12.730937 8.597813 -1.000000 +330 -13.510938 9.377813 -1.000000 +331 -12.730937 9.377813 -1.000000 +332 -13.510938 8.597813 1.000000 +333 -12.730937 8.597813 1.000000 +334 -13.510938 9.377813 1.000000 +335 -12.730937 9.377813 1.000000 +336 -8.091563 0.010000 -1.000000 +337 -7.311563 0.010000 -1.000000 +338 -8.091563 0.790000 -1.000000 +339 -7.311563 0.790000 -1.000000 +340 -8.091563 0.010000 1.000000 +341 -7.311563 0.010000 1.000000 +342 -8.091563 0.790000 1.000000 +343 -7.311563 0.790000 1.000000 +344 -7.291563 0.010000 -1.000000 +345 -6.511563 0.010000 -1.000000 +346 -7.291563 0.790000 -1.000000 +347 -6.511563 0.790000 -1.000000 +348 -7.291563 0.010000 1.000000 +349 -6.511563 0.010000 1.000000 +350 -7.291563 0.790000 1.000000 +351 -6.511563 0.790000 1.000000 +352 -1.691562 0.010000 -1.000000 +353 -0.911563 0.010000 -1.000000 +354 -1.691562 0.790000 -1.000000 +355 -0.911563 0.790000 -1.000000 +356 -1.691562 0.010000 1.000000 +357 -0.911563 0.010000 1.000000 +358 -1.691562 0.790000 1.000000 +359 -0.911563 0.790000 1.000000 +360 -0.891562 0.010000 -1.000000 +361 -0.111562 0.010000 -1.000000 +362 -0.891562 0.790000 -1.000000 +363 -0.111562 0.790000 -1.000000 +364 -0.891562 0.010000 1.000000 +365 -0.111562 0.010000 1.000000 +366 -0.891562 0.790000 1.000000 +367 -0.111562 0.790000 1.000000 +368 -0.091562 0.010000 -1.000000 +369 0.688437 0.010000 -1.000000 +370 -0.091562 0.790000 -1.000000 +371 0.688437 0.790000 -1.000000 +372 -0.091562 0.010000 1.000000 +373 0.688437 0.010000 1.000000 +374 -0.091562 0.790000 1.000000 +375 0.688437 0.790000 1.000000 +376 -1.691562 0.810000 -1.000000 +377 -0.911563 0.810000 -1.000000 +378 -1.691562 1.590000 -1.000000 +379 -0.911563 1.590000 -1.000000 +380 -1.691562 0.810000 1.000000 +381 -0.911563 0.810000 1.000000 +382 -1.691562 1.590000 1.000000 +383 -0.911563 1.590000 1.000000 +384 -0.891562 0.810000 -1.000000 +385 -0.111562 0.810000 -1.000000 +386 -0.891562 1.590000 -1.000000 +387 -0.111562 1.590000 -1.000000 +388 -0.891562 0.810000 1.000000 +389 -0.111562 0.810000 1.000000 +390 -0.891562 1.590000 1.000000 +391 -0.111562 1.590000 1.000000 +392 -7.291563 1.610000 -1.000000 +393 -6.511563 1.610000 -1.000000 +394 -7.291563 2.390000 -1.000000 +395 -6.511563 2.390000 -1.000000 +396 -7.291563 1.610000 1.000000 +397 -6.511563 1.610000 1.000000 +398 -7.291563 2.390000 1.000000 +399 -6.511563 2.390000 1.000000 +400 -2.491562 1.610000 -1.000000 +401 -1.711563 1.610000 -1.000000 +402 -2.491562 2.390000 -1.000000 +403 -1.711563 2.390000 -1.000000 +404 -2.491562 1.610000 1.000000 +405 -1.711563 1.610000 1.000000 +406 -2.491562 2.390000 1.000000 +407 -1.711563 2.390000 1.000000 +408 -1.691562 1.610000 -1.000000 +409 -0.911563 1.610000 -1.000000 +410 -1.691562 2.390000 -1.000000 +411 -0.911563 2.390000 -1.000000 +412 -1.691562 1.610000 1.000000 +413 -0.911563 1.610000 1.000000 +414 -1.691562 2.390000 1.000000 +415 -0.911563 2.390000 1.000000 +416 -7.291563 2.410000 -1.000000 +417 -6.511563 2.410000 -1.000000 +418 -7.291563 3.190000 -1.000000 +419 -6.511563 3.190000 -1.000000 +420 -7.291563 2.410000 1.000000 +421 -6.511563 2.410000 1.000000 +422 -7.291563 3.190000 1.000000 +423 -6.511563 3.190000 1.000000 +424 -2.491562 2.410000 -1.000000 +425 -1.711563 2.410000 -1.000000 +426 -2.491562 3.190000 -1.000000 +427 -1.711563 3.190000 -1.000000 +428 -2.491562 2.410000 1.000000 +429 -1.711563 2.410000 1.000000 +430 -2.491562 3.190000 1.000000 +431 -1.711563 3.190000 1.000000 +432 -1.691562 2.410000 -1.000000 +433 -0.911563 2.410000 -1.000000 +434 -1.691562 3.190000 -1.000000 +435 -0.911563 3.190000 -1.000000 +436 -1.691562 2.410000 1.000000 +437 -0.911563 2.410000 1.000000 +438 -1.691562 3.190000 1.000000 +439 -0.911563 3.190000 1.000000 +440 -6.491563 3.210000 -1.000000 +441 -5.711562 3.210000 -1.000000 +442 -6.491563 3.990000 -1.000000 +443 -5.711562 3.990000 -1.000000 +444 -6.491563 3.210000 1.000000 +445 -5.711562 3.210000 1.000000 +446 -6.491563 3.990000 1.000000 +447 -5.711562 3.990000 1.000000 +448 -5.691563 3.210000 -1.000000 +449 -4.911563 3.210000 -1.000000 +450 -5.691563 3.990000 -1.000000 +451 -4.911563 3.990000 -1.000000 +452 -5.691563 3.210000 1.000000 +453 -4.911563 3.210000 1.000000 +454 -5.691563 3.990000 1.000000 +455 -4.911563 3.990000 1.000000 +456 -4.891563 3.210000 -1.000000 +457 -4.111562 3.210000 -1.000000 +458 -4.891563 3.990000 -1.000000 +459 -4.111562 3.990000 -1.000000 +460 -4.891563 3.210000 1.000000 +461 -4.111562 3.210000 1.000000 +462 -4.891563 3.990000 1.000000 +463 -4.111562 3.990000 1.000000 +464 -4.091563 3.210000 -1.000000 +465 -3.311563 3.210000 -1.000000 +466 -4.091563 3.990000 -1.000000 +467 -3.311563 3.990000 -1.000000 +468 -4.091563 3.210000 1.000000 +469 -3.311563 3.210000 1.000000 +470 -4.091563 3.990000 1.000000 +471 -3.311563 3.990000 1.000000 +472 -3.291562 3.210000 -1.000000 +473 -2.511563 3.210000 -1.000000 +474 -3.291562 3.990000 -1.000000 +475 -2.511563 3.990000 -1.000000 +476 -3.291562 3.210000 1.000000 +477 -2.511563 3.210000 1.000000 +478 -3.291562 3.990000 1.000000 +479 -2.511563 3.990000 1.000000 +480 -2.491562 3.210000 -1.000000 +481 -1.711563 3.210000 -1.000000 +482 -2.491562 3.990000 -1.000000 +483 -1.711563 3.990000 -1.000000 +484 -2.491562 3.210000 1.000000 +485 -1.711563 3.210000 1.000000 +486 -2.491562 3.990000 1.000000 +487 -1.711563 3.990000 1.000000 +488 -6.491563 4.010000 -1.000000 +489 -5.711562 4.010000 -1.000000 +490 -6.491563 4.790000 -1.000000 +491 -5.711562 4.790000 -1.000000 +492 -6.491563 4.010000 1.000000 +493 -5.711562 4.010000 1.000000 +494 -6.491563 4.790000 1.000000 +495 -5.711562 4.790000 1.000000 +496 -5.691563 4.010000 -1.000000 +497 -4.911563 4.010000 -1.000000 +498 -5.691563 4.790000 -1.000000 +499 -4.911563 4.790000 -1.000000 +500 -5.691563 4.010000 1.000000 +501 -4.911563 4.010000 1.000000 +502 -5.691563 4.790000 1.000000 +503 -4.911563 4.790000 1.000000 +504 -4.891563 4.010000 -1.000000 +505 -4.111562 4.010000 -1.000000 +506 -4.891563 4.790000 -1.000000 +507 -4.111562 4.790000 -1.000000 +508 -4.891563 4.010000 1.000000 +509 -4.111562 4.010000 1.000000 +510 -4.891563 4.790000 1.000000 +511 -4.111562 4.790000 1.000000 +512 -4.091563 4.010000 -1.000000 +513 -3.311563 4.010000 -1.000000 +514 -4.091563 4.790000 -1.000000 +515 -3.311563 4.790000 -1.000000 +516 -4.091563 4.010000 1.000000 +517 -3.311563 4.010000 1.000000 +518 -4.091563 4.790000 1.000000 +519 -3.311563 4.790000 1.000000 +520 -3.291562 4.010000 -1.000000 +521 -2.511563 4.010000 -1.000000 +522 -3.291562 4.790000 -1.000000 +523 -2.511563 4.790000 -1.000000 +524 -3.291562 4.010000 1.000000 +525 -2.511563 4.010000 1.000000 +526 -3.291562 4.790000 1.000000 +527 -2.511563 4.790000 1.000000 +528 -2.491562 4.010000 -1.000000 +529 -1.711563 4.010000 -1.000000 +530 -2.491562 4.790000 -1.000000 +531 -1.711563 4.790000 -1.000000 +532 -2.491562 4.010000 1.000000 +533 -1.711563 4.010000 1.000000 +534 -2.491562 4.790000 1.000000 +535 -1.711563 4.790000 1.000000 +536 -5.691563 4.810000 -1.000000 +537 -4.911563 4.810000 -1.000000 +538 -5.691563 5.590000 -1.000000 +539 -4.911563 5.590000 -1.000000 +540 -5.691563 4.810000 1.000000 +541 -4.911563 4.810000 1.000000 +542 -5.691563 5.590000 1.000000 +543 -4.911563 5.590000 1.000000 +544 -4.891563 4.810000 -1.000000 +545 -4.111562 4.810000 -1.000000 +546 -4.891563 5.590000 -1.000000 +547 -4.111562 5.590000 -1.000000 +548 -4.891563 4.810000 1.000000 +549 -4.111562 4.810000 1.000000 +550 -4.891563 5.590000 1.000000 +551 -4.111562 5.590000 1.000000 +552 -4.091563 4.810000 -1.000000 +553 -3.311563 4.810000 -1.000000 +554 -4.091563 5.590000 -1.000000 +555 -3.311563 5.590000 -1.000000 +556 -4.091563 4.810000 1.000000 +557 -3.311563 4.810000 1.000000 +558 -4.091563 5.590000 1.000000 +559 -3.311563 5.590000 1.000000 +560 -3.291562 4.810000 -1.000000 +561 -2.511563 4.810000 -1.000000 +562 -3.291562 5.590000 -1.000000 +563 -2.511563 5.590000 -1.000000 +564 -3.291562 4.810000 1.000000 +565 -2.511563 4.810000 1.000000 +566 -3.291562 5.590000 1.000000 +567 -2.511563 5.590000 1.000000 +568 -2.491562 4.810000 -1.000000 +569 -1.711563 4.810000 -1.000000 +570 -2.491562 5.590000 -1.000000 +571 -1.711563 5.590000 -1.000000 +572 -2.491562 4.810000 1.000000 +573 -1.711563 4.810000 1.000000 +574 -2.491562 5.590000 1.000000 +575 -1.711563 5.590000 1.000000 +576 -5.691563 5.610000 -1.000000 +577 -4.911563 5.610000 -1.000000 +578 -5.691563 6.390000 -1.000000 +579 -4.911563 6.390000 -1.000000 +580 -5.691563 5.610000 1.000000 +581 -4.911563 5.610000 1.000000 +582 -5.691563 6.390000 1.000000 +583 -4.911563 6.390000 1.000000 +584 -4.891563 5.610000 -1.000000 +585 -4.111562 5.610000 -1.000000 +586 -4.891563 6.390000 -1.000000 +587 -4.111562 6.390000 -1.000000 +588 -4.891563 5.610000 1.000000 +589 -4.111562 5.610000 1.000000 +590 -4.891563 6.390000 1.000000 +591 -4.111562 6.390000 1.000000 +592 -4.091563 5.610000 -1.000000 +593 -3.311563 5.610000 -1.000000 +594 -4.091563 6.390000 -1.000000 +595 -3.311563 6.390000 -1.000000 +596 -4.091563 5.610000 1.000000 +597 -3.311563 5.610000 1.000000 +598 -4.091563 6.390000 1.000000 +599 -3.311563 6.390000 1.000000 +600 -3.291562 5.610000 -1.000000 +601 -2.511563 5.610000 -1.000000 +602 -3.291562 6.390000 -1.000000 +603 -2.511563 6.390000 -1.000000 +604 -3.291562 5.610000 1.000000 +605 -2.511563 5.610000 1.000000 +606 -3.291562 6.390000 1.000000 +607 -2.511563 6.390000 1.000000 +608 -4.891563 6.410000 -1.000000 +609 -4.111562 6.410000 -1.000000 +610 -4.891563 7.190000 -1.000000 +611 -4.111562 7.190000 -1.000000 +612 -4.891563 6.410000 1.000000 +613 -4.111562 6.410000 1.000000 +614 -4.891563 7.190000 1.000000 +615 -4.111562 7.190000 1.000000 +616 -4.091563 6.410000 -1.000000 +617 -3.311563 6.410000 -1.000000 +618 -4.091563 7.190000 -1.000000 +619 -3.311563 7.190000 -1.000000 +620 -4.091563 6.410000 1.000000 +621 -3.311563 6.410000 1.000000 +622 -4.091563 7.190000 1.000000 +623 -3.311563 7.190000 1.000000 +624 -3.291562 6.410000 -1.000000 +625 -2.511563 6.410000 -1.000000 +626 -3.291562 7.190000 -1.000000 +627 -2.511563 7.190000 -1.000000 +628 -3.291562 6.410000 1.000000 +629 -2.511563 6.410000 1.000000 +630 -3.291562 7.190000 1.000000 +631 -2.511563 7.190000 1.000000 +632 -4.891563 7.210000 -1.000000 +633 -4.111562 7.210000 -1.000000 +634 -4.891563 7.990000 -1.000000 +635 -4.111562 7.990000 -1.000000 +636 -4.891563 7.210000 1.000000 +637 -4.111562 7.210000 1.000000 +638 -4.891563 7.990000 1.000000 +639 -4.111562 7.990000 1.000000 +640 -4.091563 7.210000 -1.000000 +641 -3.311563 7.210000 -1.000000 +642 -4.091563 7.990000 -1.000000 +643 -3.311563 7.990000 -1.000000 +644 -4.091563 7.210000 1.000000 +645 -3.311563 7.210000 1.000000 +646 -4.091563 7.990000 1.000000 +647 -3.311563 7.990000 1.000000 +648 -4.891563 8.010000 -1.000000 +649 -4.111562 8.010000 -1.000000 +650 -4.891563 8.790000 -1.000000 +651 -4.111562 8.790000 -1.000000 +652 -4.891563 8.010000 1.000000 +653 -4.111562 8.010000 1.000000 +654 -4.891563 8.790000 1.000000 +655 -4.111562 8.790000 1.000000 +656 -4.091563 8.010000 -1.000000 +657 -3.311563 8.010000 -1.000000 +658 -4.091563 8.790000 -1.000000 +659 -3.311563 8.790000 -1.000000 +660 -4.091563 8.010000 1.000000 +661 -3.311563 8.010000 1.000000 +662 -4.091563 8.790000 1.000000 +663 -3.311563 8.790000 1.000000 +664 4.952188 0.010000 -1.000000 +665 5.732188 0.010000 -1.000000 +666 4.952188 0.790000 -1.000000 +667 5.732188 0.790000 -1.000000 +668 4.952188 0.010000 1.000000 +669 5.732188 0.010000 1.000000 +670 4.952188 0.790000 1.000000 +671 5.732188 0.790000 1.000000 +672 5.752188 0.010000 -1.000000 +673 6.532188 0.010000 -1.000000 +674 5.752188 0.790000 -1.000000 +675 6.532188 0.790000 -1.000000 +676 5.752188 0.010000 1.000000 +677 6.532188 0.010000 1.000000 +678 5.752188 0.790000 1.000000 +679 6.532188 0.790000 1.000000 +680 6.552188 0.010000 -1.000000 +681 7.332187 0.010000 -1.000000 +682 6.552188 0.790000 -1.000000 +683 7.332187 0.790000 -1.000000 +684 6.552188 0.010000 1.000000 +685 7.332187 0.010000 1.000000 +686 6.552188 0.790000 1.000000 +687 7.332187 0.790000 1.000000 +688 5.752188 0.810000 -1.000000 +689 6.532188 0.810000 -1.000000 +690 5.752188 1.590000 -1.000000 +691 6.532188 1.590000 -1.000000 +692 5.752188 0.810000 1.000000 +693 6.532188 0.810000 1.000000 +694 5.752188 1.590000 1.000000 +695 6.532188 1.590000 1.000000 +696 6.552188 0.810000 -1.000000 +697 7.332187 0.810000 -1.000000 +698 6.552188 1.590000 -1.000000 +699 7.332187 1.590000 -1.000000 +700 6.552188 0.810000 1.000000 +701 7.332187 0.810000 1.000000 +702 6.552188 1.590000 1.000000 +703 7.332187 1.590000 1.000000 +704 5.752188 1.610000 -1.000000 +705 6.532188 1.610000 -1.000000 +706 5.752188 2.390000 -1.000000 +707 6.532188 2.390000 -1.000000 +708 5.752188 1.610000 1.000000 +709 6.532188 1.610000 1.000000 +710 5.752188 2.390000 1.000000 +711 6.532188 2.390000 1.000000 +712 6.552188 1.610000 -1.000000 +713 7.332187 1.610000 -1.000000 +714 6.552188 2.390000 -1.000000 +715 7.332187 2.390000 -1.000000 +716 6.552188 1.610000 1.000000 +717 7.332187 1.610000 1.000000 +718 6.552188 2.390000 1.000000 +719 7.332187 2.390000 1.000000 +720 5.752188 2.410000 -1.000000 +721 6.532188 2.410000 -1.000000 +722 5.752188 3.190000 -1.000000 +723 6.532188 3.190000 -1.000000 +724 5.752188 2.410000 1.000000 +725 6.532188 2.410000 1.000000 +726 5.752188 3.190000 1.000000 +727 6.532188 3.190000 1.000000 +728 6.552188 2.410000 -1.000000 +729 7.332187 2.410000 -1.000000 +730 6.552188 3.190000 -1.000000 +731 7.332187 3.190000 -1.000000 +732 6.552188 2.410000 1.000000 +733 7.332187 2.410000 1.000000 +734 6.552188 3.190000 1.000000 +735 7.332187 3.190000 1.000000 +736 5.752188 3.210000 -1.000000 +737 6.532188 3.210000 -1.000000 +738 5.752188 3.990000 -1.000000 +739 6.532188 3.990000 -1.000000 +740 5.752188 3.210000 1.000000 +741 6.532188 3.210000 1.000000 +742 5.752188 3.990000 1.000000 +743 6.532188 3.990000 1.000000 +744 6.552188 3.210000 -1.000000 +745 7.332187 3.210000 -1.000000 +746 6.552188 3.990000 -1.000000 +747 7.332187 3.990000 -1.000000 +748 6.552188 3.210000 1.000000 +749 7.332187 3.210000 1.000000 +750 6.552188 3.990000 1.000000 +751 7.332187 3.990000 1.000000 +752 5.752188 4.010000 -1.000000 +753 6.532188 4.010000 -1.000000 +754 5.752188 4.790000 -1.000000 +755 6.532188 4.790000 -1.000000 +756 5.752188 4.010000 1.000000 +757 6.532188 4.010000 1.000000 +758 5.752188 4.790000 1.000000 +759 6.532188 4.790000 1.000000 +760 6.552188 4.010000 -1.000000 +761 7.332187 4.010000 -1.000000 +762 6.552188 4.790000 -1.000000 +763 7.332187 4.790000 -1.000000 +764 6.552188 4.010000 1.000000 +765 7.332187 4.010000 1.000000 +766 6.552188 4.790000 1.000000 +767 7.332187 4.790000 1.000000 +768 5.752188 4.810000 -1.000000 +769 6.532188 4.810000 -1.000000 +770 5.752188 5.590000 -1.000000 +771 6.532188 5.590000 -1.000000 +772 5.752188 4.810000 1.000000 +773 6.532188 4.810000 1.000000 +774 5.752188 5.590000 1.000000 +775 6.532188 5.590000 1.000000 +776 6.552188 4.810000 -1.000000 +777 7.332187 4.810000 -1.000000 +778 6.552188 5.590000 -1.000000 +779 7.332187 5.590000 -1.000000 +780 6.552188 4.810000 1.000000 +781 7.332187 4.810000 1.000000 +782 6.552188 5.590000 1.000000 +783 7.332187 5.590000 1.000000 +784 5.752188 5.610000 -1.000000 +785 6.532188 5.610000 -1.000000 +786 5.752188 6.390000 -1.000000 +787 6.532188 6.390000 -1.000000 +788 5.752188 5.610000 1.000000 +789 6.532188 5.610000 1.000000 +790 5.752188 6.390000 1.000000 +791 6.532188 6.390000 1.000000 +792 6.552188 5.610000 -1.000000 +793 7.332187 5.610000 -1.000000 +794 6.552188 6.390000 -1.000000 +795 7.332187 6.390000 -1.000000 +796 6.552188 5.610000 1.000000 +797 7.332187 5.610000 1.000000 +798 6.552188 6.390000 1.000000 +799 7.332187 6.390000 1.000000 +800 5.752188 6.410000 -1.000000 +801 6.532188 6.410000 -1.000000 +802 5.752188 7.190000 -1.000000 +803 6.532188 7.190000 -1.000000 +804 5.752188 6.410000 1.000000 +805 6.532188 6.410000 1.000000 +806 5.752188 7.190000 1.000000 +807 6.532188 7.190000 1.000000 +808 6.552188 6.410000 -1.000000 +809 7.332187 6.410000 -1.000000 +810 6.552188 7.190000 -1.000000 +811 7.332187 7.190000 -1.000000 +812 6.552188 6.410000 1.000000 +813 7.332187 6.410000 1.000000 +814 6.552188 7.190000 1.000000 +815 7.332187 7.190000 1.000000 +816 5.752188 7.210000 -1.000000 +817 6.532188 7.210000 -1.000000 +818 5.752188 7.990000 -1.000000 +819 6.532188 7.990000 -1.000000 +820 5.752188 7.210000 1.000000 +821 6.532188 7.210000 1.000000 +822 5.752188 7.990000 1.000000 +823 6.532188 7.990000 1.000000 +824 6.552188 7.210000 -1.000000 +825 7.332187 7.210000 -1.000000 +826 6.552188 7.990000 -1.000000 +827 7.332187 7.990000 -1.000000 +828 6.552188 7.210000 1.000000 +829 7.332187 7.210000 1.000000 +830 6.552188 7.990000 1.000000 +831 7.332187 7.990000 1.000000 +832 9.752188 7.210000 -1.000000 +833 10.532188 7.210000 -1.000000 +834 9.752188 7.990000 -1.000000 +835 10.532188 7.990000 -1.000000 +836 9.752188 7.210000 1.000000 +837 10.532188 7.210000 1.000000 +838 9.752188 7.990000 1.000000 +839 10.532188 7.990000 1.000000 +840 2.552188 8.010000 -1.000000 +841 3.332187 8.010000 -1.000000 +842 2.552188 8.790000 -1.000000 +843 3.332187 8.790000 -1.000000 +844 2.552188 8.010000 1.000000 +845 3.332187 8.010000 1.000000 +846 2.552188 8.790000 1.000000 +847 3.332187 8.790000 1.000000 +848 5.752188 8.010000 -1.000000 +849 6.532188 8.010000 -1.000000 +850 5.752188 8.790000 -1.000000 +851 6.532188 8.790000 -1.000000 +852 5.752188 8.010000 1.000000 +853 6.532188 8.010000 1.000000 +854 5.752188 8.790000 1.000000 +855 6.532188 8.790000 1.000000 +856 6.552188 8.010000 -1.000000 +857 7.332187 8.010000 -1.000000 +858 6.552188 8.790000 -1.000000 +859 7.332187 8.790000 -1.000000 +860 6.552188 8.010000 1.000000 +861 7.332187 8.010000 1.000000 +862 6.552188 8.790000 1.000000 +863 7.332187 8.790000 1.000000 +864 9.752188 8.010000 -1.000000 +865 10.532188 8.010000 -1.000000 +866 9.752188 8.790000 -1.000000 +867 10.532188 8.790000 -1.000000 +868 9.752188 8.010000 1.000000 +869 10.532188 8.010000 1.000000 +870 9.752188 8.790000 1.000000 +871 10.532188 8.790000 1.000000 +872 2.552188 8.810000 -1.000000 +873 3.332187 8.810000 -1.000000 +874 2.552188 9.590000 -1.000000 +875 3.332187 9.590000 -1.000000 +876 2.552188 8.810000 1.000000 +877 3.332187 8.810000 1.000000 +878 2.552188 9.590000 1.000000 +879 3.332187 9.590000 1.000000 +880 3.352188 8.810000 -1.000000 +881 4.132187 8.810000 -1.000000 +882 3.352188 9.590000 -1.000000 +883 4.132187 9.590000 -1.000000 +884 3.352188 8.810000 1.000000 +885 4.132187 8.810000 1.000000 +886 3.352188 9.590000 1.000000 +887 4.132187 9.590000 1.000000 +888 4.152188 8.810000 -1.000000 +889 4.932187 8.810000 -1.000000 +890 4.152188 9.590000 -1.000000 +891 4.932187 9.590000 -1.000000 +892 4.152188 8.810000 1.000000 +893 4.932187 8.810000 1.000000 +894 4.152188 9.590000 1.000000 +895 4.932187 9.590000 1.000000 +896 4.952188 8.810000 -1.000000 +897 5.732188 8.810000 -1.000000 +898 4.952188 9.590000 -1.000000 +899 5.732188 9.590000 -1.000000 +900 4.952188 8.810000 1.000000 +901 5.732188 8.810000 1.000000 +902 4.952188 9.590000 1.000000 +903 5.732188 9.590000 1.000000 +904 5.752188 8.810000 -1.000000 +905 6.532188 8.810000 -1.000000 +906 5.752188 9.590000 -1.000000 +907 6.532188 9.590000 -1.000000 +908 5.752188 8.810000 1.000000 +909 6.532188 8.810000 1.000000 +910 5.752188 9.590000 1.000000 +911 6.532188 9.590000 1.000000 +912 6.552188 8.810000 -1.000000 +913 7.332187 8.810000 -1.000000 +914 6.552188 9.590000 -1.000000 +915 7.332187 9.590000 -1.000000 +916 6.552188 8.810000 1.000000 +917 7.332187 8.810000 1.000000 +918 6.552188 9.590000 1.000000 +919 7.332187 9.590000 1.000000 +920 7.352188 8.810000 -1.000000 +921 8.132187 8.810000 -1.000000 +922 7.352188 9.590000 -1.000000 +923 8.132187 9.590000 -1.000000 +924 7.352188 8.810000 1.000000 +925 8.132187 8.810000 1.000000 +926 7.352188 9.590000 1.000000 +927 8.132187 9.590000 1.000000 +928 8.152188 8.810000 -1.000000 +929 8.932187 8.810000 -1.000000 +930 8.152188 9.590000 -1.000000 +931 8.932187 9.590000 -1.000000 +932 8.152188 8.810000 1.000000 +933 8.932187 8.810000 1.000000 +934 8.152188 9.590000 1.000000 +935 8.932187 9.590000 1.000000 +936 8.952188 8.810000 -1.000000 +937 9.732188 8.810000 -1.000000 +938 8.952188 9.590000 -1.000000 +939 9.732188 9.590000 -1.000000 +940 8.952188 8.810000 1.000000 +941 9.732188 8.810000 1.000000 +942 8.952188 9.590000 1.000000 +943 9.732188 9.590000 1.000000 +944 9.752188 8.810000 -1.000000 +945 10.532188 8.810000 -1.000000 +946 9.752188 9.590000 -1.000000 +947 10.532188 9.590000 -1.000000 +948 9.752188 8.810000 1.000000 +949 10.532188 8.810000 1.000000 +950 9.752188 9.590000 1.000000 +951 10.532188 9.590000 1.000000 +952 12.730937 0.010000 -1.000000 +953 13.510937 0.010000 -1.000000 +954 12.730937 0.790000 -1.000000 +955 13.510937 0.790000 -1.000000 +956 12.730937 0.010000 1.000000 +957 13.510937 0.010000 1.000000 +958 12.730937 0.790000 1.000000 +959 13.510937 0.790000 1.000000 +960 13.530938 0.010000 -1.000000 +961 14.310937 0.010000 -1.000000 +962 13.530938 0.790000 -1.000000 +963 14.310937 0.790000 -1.000000 +964 13.530938 0.010000 1.000000 +965 14.310937 0.010000 1.000000 +966 13.530938 0.790000 1.000000 +967 14.310937 0.790000 1.000000 +968 14.330937 0.010000 -1.000000 +969 15.110937 0.010000 -1.000000 +970 14.330937 0.790000 -1.000000 +971 15.110937 0.790000 -1.000000 +972 14.330937 0.010000 1.000000 +973 15.110937 0.010000 1.000000 +974 14.330937 0.790000 1.000000 +975 15.110937 0.790000 1.000000 +976 15.130937 0.010000 -1.000000 +977 15.910937 0.010000 -1.000000 +978 15.130937 0.790000 -1.000000 +979 15.910937 0.790000 -1.000000 +980 15.130937 0.010000 1.000000 +981 15.910937 0.010000 1.000000 +982 15.130937 0.790000 1.000000 +983 15.910937 0.790000 1.000000 +984 15.930938 0.010000 -1.000000 +985 16.710937 0.010000 -1.000000 +986 15.930938 0.790000 -1.000000 +987 16.710937 0.790000 -1.000000 +988 15.930938 0.010000 1.000000 +989 16.710937 0.010000 1.000000 +990 15.930938 0.790000 1.000000 +991 16.710937 0.790000 1.000000 +992 16.730937 0.010000 -1.000000 +993 17.510937 0.010000 -1.000000 +994 16.730937 0.790000 -1.000000 +995 17.510937 0.790000 -1.000000 +996 16.730937 0.010000 1.000000 +997 17.510937 0.010000 1.000000 +998 16.730937 0.790000 1.000000 +999 17.510937 0.790000 1.000000 +1000 17.530938 0.010000 -1.000000 +1001 18.310937 0.010000 -1.000000 +1002 17.530938 0.790000 -1.000000 +1003 18.310937 0.790000 -1.000000 +1004 17.530938 0.010000 1.000000 +1005 18.310937 0.010000 1.000000 +1006 17.530938 0.790000 1.000000 +1007 18.310937 0.790000 1.000000 +1008 18.330937 0.010000 -1.000000 +1009 19.110937 0.010000 -1.000000 +1010 18.330937 0.790000 -1.000000 +1011 19.110937 0.790000 -1.000000 +1012 18.330937 0.010000 1.000000 +1013 19.110937 0.010000 1.000000 +1014 18.330937 0.790000 1.000000 +1015 19.110937 0.790000 1.000000 +1016 13.530938 0.810000 -1.000000 +1017 14.310937 0.810000 -1.000000 +1018 13.530938 1.590000 -1.000000 +1019 14.310937 1.590000 -1.000000 +1020 13.530938 0.810000 1.000000 +1021 14.310937 0.810000 1.000000 +1022 13.530938 1.590000 1.000000 +1023 14.310937 1.590000 1.000000 +1024 13.530938 1.610000 -1.000000 +1025 14.310937 1.610000 -1.000000 +1026 13.530938 2.390000 -1.000000 +1027 14.310937 2.390000 -1.000000 +1028 13.530938 1.610000 1.000000 +1029 14.310937 1.610000 1.000000 +1030 13.530938 2.390000 1.000000 +1031 14.310937 2.390000 1.000000 +1032 19.130938 1.610000 -1.000000 +1033 19.910938 1.610000 -1.000000 +1034 19.130938 2.390000 -1.000000 +1035 19.910938 2.390000 -1.000000 +1036 19.130938 1.610000 1.000000 +1037 19.910938 1.610000 1.000000 +1038 19.130938 2.390000 1.000000 +1039 19.910938 2.390000 1.000000 +1040 13.530938 2.410000 -1.000000 +1041 14.310937 2.410000 -1.000000 +1042 13.530938 3.190000 -1.000000 +1043 14.310937 3.190000 -1.000000 +1044 13.530938 2.410000 1.000000 +1045 14.310937 2.410000 1.000000 +1046 13.530938 3.190000 1.000000 +1047 14.310937 3.190000 1.000000 +1048 13.530938 3.210000 -1.000000 +1049 14.310937 3.210000 -1.000000 +1050 13.530938 3.990000 -1.000000 +1051 14.310937 3.990000 -1.000000 +1052 13.530938 3.210000 1.000000 +1053 14.310937 3.210000 1.000000 +1054 13.530938 3.990000 1.000000 +1055 14.310937 3.990000 1.000000 +1056 13.530938 4.010000 -1.000000 +1057 14.310937 4.010000 -1.000000 +1058 13.530938 4.790000 -1.000000 +1059 14.310937 4.790000 -1.000000 +1060 13.530938 4.010000 1.000000 +1061 14.310937 4.010000 1.000000 +1062 13.530938 4.790000 1.000000 +1063 14.310937 4.790000 1.000000 +1064 17.530938 4.010000 -1.000000 +1065 18.310937 4.010000 -1.000000 +1066 17.530938 4.790000 -1.000000 +1067 18.310937 4.790000 -1.000000 +1068 17.530938 4.010000 1.000000 +1069 18.310937 4.010000 1.000000 +1070 17.530938 4.790000 1.000000 +1071 18.310937 4.790000 1.000000 +1072 13.530938 4.810000 -1.000000 +1073 14.310937 4.810000 -1.000000 +1074 13.530938 5.590000 -1.000000 +1075 14.310937 5.590000 -1.000000 +1076 13.530938 4.810000 1.000000 +1077 14.310937 4.810000 1.000000 +1078 13.530938 5.590000 1.000000 +1079 14.310937 5.590000 1.000000 +1080 17.530938 4.810000 -1.000000 +1081 18.310937 4.810000 -1.000000 +1082 17.530938 5.590000 -1.000000 +1083 18.310937 5.590000 -1.000000 +1084 17.530938 4.810000 1.000000 +1085 18.310937 4.810000 1.000000 +1086 17.530938 5.590000 1.000000 +1087 18.310937 5.590000 1.000000 +1088 13.530938 5.610000 -1.000000 +1089 14.310937 5.610000 -1.000000 +1090 13.530938 6.390000 -1.000000 +1091 14.310937 6.390000 -1.000000 +1092 13.530938 5.610000 1.000000 +1093 14.310937 5.610000 1.000000 +1094 13.530938 6.390000 1.000000 +1095 14.310937 6.390000 1.000000 +1096 13.530938 6.410000 -1.000000 +1097 14.310937 6.410000 -1.000000 +1098 13.530938 7.190000 -1.000000 +1099 14.310937 7.190000 -1.000000 +1100 13.530938 6.410000 1.000000 +1101 14.310937 6.410000 1.000000 +1102 13.530938 7.190000 1.000000 +1103 14.310937 7.190000 1.000000 +1104 13.530938 7.210000 -1.000000 +1105 14.310937 7.210000 -1.000000 +1106 13.530938 7.990000 -1.000000 +1107 14.310937 7.990000 -1.000000 +1108 13.530938 7.210000 1.000000 +1109 14.310937 7.210000 1.000000 +1110 13.530938 7.990000 1.000000 +1111 14.310937 7.990000 1.000000 +1112 13.530938 8.010000 -1.000000 +1113 14.310937 8.010000 -1.000000 +1114 13.530938 8.790000 -1.000000 +1115 14.310937 8.790000 -1.000000 +1116 13.530938 8.010000 1.000000 +1117 14.310937 8.010000 1.000000 +1118 13.530938 8.790000 1.000000 +1119 14.310937 8.790000 1.000000 +1120 18.330937 8.010000 -1.000000 +1121 19.110937 8.010000 -1.000000 +1122 18.330937 8.790000 -1.000000 +1123 19.110937 8.790000 -1.000000 +1124 18.330937 8.010000 1.000000 +1125 19.110937 8.010000 1.000000 +1126 18.330937 8.790000 1.000000 +1127 19.110937 8.790000 1.000000 +1128 11.930938 8.810000 -1.000000 +1129 12.710937 8.810000 -1.000000 +1130 11.930938 9.590000 -1.000000 +1131 12.710937 9.590000 -1.000000 +1132 11.930938 8.810000 1.000000 +1133 12.710937 8.810000 1.000000 +1134 11.930938 9.590000 1.000000 +1135 12.710937 9.590000 1.000000 +1136 12.730937 8.810000 -1.000000 +1137 13.510937 8.810000 -1.000000 +1138 12.730937 9.590000 -1.000000 +1139 13.510937 9.590000 -1.000000 +1140 12.730937 8.810000 1.000000 +1141 13.510937 8.810000 1.000000 +1142 12.730937 9.590000 1.000000 +1143 13.510937 9.590000 1.000000 +1144 13.530938 8.810000 -1.000000 +1145 14.310937 8.810000 -1.000000 +1146 13.530938 9.590000 -1.000000 +1147 14.310937 9.590000 -1.000000 +1148 13.530938 8.810000 1.000000 +1149 14.310937 8.810000 1.000000 +1150 13.530938 9.590000 1.000000 +1151 14.310937 9.590000 1.000000 +1152 14.330937 8.810000 -1.000000 +1153 15.110937 8.810000 -1.000000 +1154 14.330937 9.590000 -1.000000 +1155 15.110937 9.590000 -1.000000 +1156 14.330937 8.810000 1.000000 +1157 15.110937 8.810000 1.000000 +1158 14.330937 9.590000 1.000000 +1159 15.110937 9.590000 1.000000 +1160 15.130937 8.810000 -1.000000 +1161 15.910937 8.810000 -1.000000 +1162 15.130937 9.590000 -1.000000 +1163 15.910937 9.590000 -1.000000 +1164 15.130937 8.810000 1.000000 +1165 15.910937 8.810000 1.000000 +1166 15.130937 9.590000 1.000000 +1167 15.910937 9.590000 1.000000 +1168 15.930938 8.810000 -1.000000 +1169 16.710937 8.810000 -1.000000 +1170 15.930938 9.590000 -1.000000 +1171 16.710937 9.590000 -1.000000 +1172 15.930938 8.810000 1.000000 +1173 16.710937 8.810000 1.000000 +1174 15.930938 9.590000 1.000000 +1175 16.710937 9.590000 1.000000 +1176 16.730937 8.810000 -1.000000 +1177 17.510937 8.810000 -1.000000 +1178 16.730937 9.590000 -1.000000 +1179 17.510937 9.590000 -1.000000 +1180 16.730937 8.810000 1.000000 +1181 17.510937 8.810000 1.000000 +1182 16.730937 9.590000 1.000000 +1183 17.510937 9.590000 1.000000 +1184 17.530938 8.810000 -1.000000 +1185 18.310937 8.810000 -1.000000 +1186 17.530938 9.590000 -1.000000 +1187 18.310937 9.590000 -1.000000 +1188 17.530938 8.810000 1.000000 +1189 18.310937 8.810000 1.000000 +1190 17.530938 9.590000 1.000000 +1191 18.310937 9.590000 1.000000 +1192 18.330937 8.810000 -1.000000 +1193 19.110937 8.810000 -1.000000 +1194 18.330937 9.590000 -1.000000 +1195 19.110937 9.590000 -1.000000 +1196 18.330937 8.810000 1.000000 +1197 19.110937 8.810000 1.000000 +1198 18.330937 9.590000 1.000000 +1199 19.110937 9.590000 1.000000 diff --git a/opengate/contrib/mrcp/letter_colour.dat b/opengate/contrib/mrcp/letter_colour.dat new file mode 100644 index 0000000000..d5375e6cf6 --- /dev/null +++ b/opengate/contrib/mrcp/letter_colour.dat @@ -0,0 +1,4 @@ +400 0.850000 0.100000 0.100000 1.000000 +500 0.100000 0.550000 0.900000 1.000000 +600 0.150000 0.750000 0.300000 1.000000 +700 0.900000 0.650000 0.050000 1.000000 diff --git a/opengate/contrib/mrcp/mrcp_simple.py b/opengate/contrib/mrcp/mrcp_simple.py new file mode 100644 index 0000000000..a442240027 --- /dev/null +++ b/opengate/contrib/mrcp/mrcp_simple.py @@ -0,0 +1,40 @@ +"""Minimal MRCP tetrahedral-phantom geometry example.""" + +from pathlib import Path + +import opengate as gate + +# All MRCP input files are expected to be in the same directory as this script. +data_dir = Path(__file__).resolve().parent + +# Create a single-threaded simulation and a vacuum world large enough for MRCP. +sim = gate.Simulation() +sim.number_of_threads = 1 + +sim.world.size = [2 * gate.g4_units.m] * 3 +sim.world.material = "G4_Galactic" + +# Open the Geant4 Qt viewer when the simulation starts. +sim.visu = True +sim.visu_type = "qt" + +# Create the MRCP tetrahedral mesh directly from the TetGen input files. +# MRCP .node coordinates are stored in cm; the MRCP C++ reader converts them +# to Geant4's mm unit with a fixed factor of 10, so no Python scale is needed. +phantom = sim.add_volume("TetrahedralMesh", "phantom_tetmesh") +phantom.mother = "world" +phantom.material = "G4_Galactic" +phantom.node_file = str(data_dir / "letter.node") +phantom.ele_file = str(data_dir / "letter.ele") +phantom.material_file = str(data_dir / "letter.material") + +# Assign visualization colors to the material/organ indices. +phantom.color_file = str(data_dir / "letter_colour.dat") + +# Use vacuum for any tetrahedron whose material index is not defined. +phantom.default_material = "G4_Galactic" +phantom.pv_name = "phantom_tetmesh_env" +phantom.check_overlaps = True + +# Initialize the geometry and open the Qt visualization window. +sim.run() diff --git a/opengate/contrib/mrcp/simple.ele b/opengate/contrib/mrcp/simple.ele new file mode 100644 index 0000000000..784f5decc9 --- /dev/null +++ b/opengate/contrib/mrcp/simple.ele @@ -0,0 +1,28 @@ +27 4 1 +0 0 1 3 9 100 +1 0 1 3 10 100 +2 0 1 5 7 100 +3 0 1 5 9 100 +4 0 1 7 10 100 +5 0 2 4 11 100 +6 0 2 4 12 100 +7 0 2 5 7 100 +8 0 2 5 11 100 +9 0 2 7 12 100 +10 0 3 6 8 100 +11 0 3 6 9 100 +12 0 3 8 10 100 +13 0 4 6 8 100 +14 0 4 6 11 100 +15 0 4 8 12 100 +16 0 5 9 11 100 +17 0 6 9 11 100 +18 0 7 10 12 100 +19 0 8 10 12 100 +20 13 14 16 20 200 +21 13 16 15 20 200 +22 13 15 19 20 200 +23 13 19 17 20 200 +24 13 17 18 20 200 +25 13 18 14 20 200 +26 21 22 23 24 300 diff --git a/opengate/contrib/mrcp/simple.material b/opengate/contrib/mrcp/simple.material new file mode 100644 index 0000000000..50fc50d6bb --- /dev/null +++ b/opengate/contrib/mrcp/simple.material @@ -0,0 +1,14 @@ +C SyntheticSphere 1.000 g/cm3 +m100 1000 -0.1119 + 8000 -0.8881 +C +C SyntheticCube 2.200 g/cm3 +m200 14000 -0.4674 + 8000 -0.5326 +C +C SyntheticTetrahedron 0.300 g/cm3 +m300 1000 -0.103 + 6000 -0.105 + 7000 -0.031 + 8000 -0.761 +C diff --git a/opengate/contrib/mrcp/simple.node b/opengate/contrib/mrcp/simple.node new file mode 100644 index 0000000000..cf15575d9b --- /dev/null +++ b/opengate/contrib/mrcp/simple.node @@ -0,0 +1,26 @@ +25 3 0 0 +0 -12.000000 0.000000 0.000000 +1 -12.000000 -1.840059 -2.977278 +2 -12.000000 -1.840059 2.977278 +3 -12.000000 1.840059 -2.977278 +4 -12.000000 1.840059 2.977278 +5 -13.840059 -2.977278 0.000000 +6 -13.840059 2.977278 0.000000 +7 -10.159941 -2.977278 0.000000 +8 -10.159941 2.977278 0.000000 +9 -14.977278 0.000000 -1.840059 +10 -9.022722 0.000000 -1.840059 +11 -14.977278 0.000000 1.840059 +12 -9.022722 0.000000 1.840059 +13 -3.000000 -3.000000 -3.000000 +14 3.000000 -3.000000 -3.000000 +15 -3.000000 3.000000 -3.000000 +16 3.000000 3.000000 -3.000000 +17 -3.000000 -3.000000 3.000000 +18 3.000000 -3.000000 3.000000 +19 -3.000000 3.000000 3.000000 +20 3.000000 3.000000 3.000000 +21 14.500000 2.500000 2.500000 +22 9.500000 -2.500000 2.500000 +23 9.500000 2.500000 -2.500000 +24 14.500000 -2.500000 -2.500000 diff --git a/opengate/contrib/mrcp/simple_colour.dat b/opengate/contrib/mrcp/simple_colour.dat new file mode 100644 index 0000000000..c1745c12f0 --- /dev/null +++ b/opengate/contrib/mrcp/simple_colour.dat @@ -0,0 +1,3 @@ +100 1.000000 0.200000 0.200000 1.000000 +200 0.200000 0.850000 0.200000 1.000000 +300 0.200000 0.400000 1.000000 1.000000 diff --git a/opengate/geometry/solids.py b/opengate/geometry/solids.py index 6ac94be2b1..aa82d359e6 100644 --- a/opengate/geometry/solids.py +++ b/opengate/geometry/solids.py @@ -639,6 +639,106 @@ def construct_solid(self): self.g4_solid = g4.GateImageBox(image_dict) +# ----------------------------------------------------------------------------- +# Added TetGen helpers and envelope solid for TetrahedralMeshVolume. +# The tetrahedra themselves are created by the C++ parameterised-mesh builder; +# this module only reads auxiliary files and constructs the enclosing G4Box. +# ----------------------------------------------------------------------------- +class TetrahedralMeshEnvelopeSolid(SolidBase): + """Build the bounding-box solid that encloses a TetGen tetrahedral mesh. + + This class does not create individual tetrahedra. It derives the envelope + size from ``node_file``; the C++ mesh builder later creates the + parameterised ``G4Tet`` daughters inside this box. + """ + + user_info_defaults = { + "node_file": ("", {"doc": "TetGen .node file", "is_input_file": True}), + "ele_file": ("", {"doc": "TetGen .ele file", "is_input_file": True}), + "material_file": ("", {"doc": "TetGen .material file", "is_input_file": True}), + "color_file": ("", {"doc": "colour.dat", "is_input_file": True}), + "container_margin_mm": (20.0, {"doc": "Extra margin around node bounds (mm)"}), + } + + @staticmethod + def _read_node_bounds(node_file): + """Return the coordinate bounds found in a TetGen .node file.""" + + minx = miny = minz = 1e30 + maxx = maxy = maxz = -1e30 + with open(node_file, "r") as node_stream: + # The first non-comment line is the TetGen header + # ( ), + # not a node coordinate. Read and validate it before parsing nodes. + for line in node_stream: + stripped = line.strip() + if not stripped or stripped.startswith("#"): + continue + parts = stripped.split() + if len(parts) < 4: + fatal(f"Invalid TetGen node header in {node_file}") + try: + number_of_points = int(parts[0]) + dimension = int(parts[1]) + except ValueError: + fatal(f"Invalid TetGen node header in {node_file}") + if number_of_points <= 0 or dimension < 3: + fatal(f"Invalid TetGen node header values in {node_file}") + break + else: + fatal(f"Cannot find TetGen node header in {node_file}") + + read_count = 0 + for line in node_stream: + stripped = line.strip() + if not stripped or stripped.startswith("#"): + continue + parts = stripped.split() + if len(parts) < 4: + continue + try: + x, y, z = float(parts[1]), float(parts[2]), float(parts[3]) + except (TypeError, ValueError): + continue + minx, miny, minz = min(minx, x), min(miny, y), min(minz, z) + maxx, maxy, maxz = max(maxx, x), max(maxy, y), max(maxz, z) + read_count += 1 + + if read_count != number_of_points: + fatal( + f"TetGen node count mismatch in {node_file}: " + f"header declares {number_of_points}, read {read_count}" + ) + return (minx, miny, minz), (maxx, maxy, maxz) + + def get_bbox_size_and_center_mm(self): + """Calculate the MRCP envelope size and mesh center in millimetres.""" + + (minx, miny, minz), (maxx, maxy, maxz) = self._read_node_bounds(self.node_file) + # MRCP .node coordinates are centimetres. The envelope is expressed + # in millimetres to match the C++ reader's use of Geant4's `cm` unit. + cm_in_mm = g4_units.cm / g4_units.mm + cx = 0.5 * (minx + maxx) * cm_in_mm + cy = 0.5 * (miny + maxy) * cm_in_mm + cz = 0.5 * (minz + maxz) * cm_in_mm + sx = (maxx - minx) * cm_in_mm + 2.0 * float(self.container_margin_mm) + sy = (maxy - miny) * cm_in_mm + 2.0 * float(self.container_margin_mm) + sz = (maxz - minz) * cm_in_mm + 2.0 * float(self.container_margin_mm) + return (sx, sy, sz), (cx, cy, cz) + + def build_solid(self): + """Create the enclosing G4Box, including the configured margin.""" + + size_mm, _ = self.get_bbox_size_and_center_mm() + sx, sy, sz = size_mm + return g4.G4Box( + self.name, + 0.5 * sx * g4_units.mm, + 0.5 * sy * g4_units.mm, + 0.5 * sz * g4_units.mm, + ) + + process_cls(SolidBase) process_cls(BooleanSolid) process_cls(BoxSolid) @@ -652,3 +752,6 @@ def construct_solid(self): process_cls(TubsSolid) process_cls(ImageSolid) process_cls(TesselatedSolid) + +# Added class processing for TetrahedralMeshEnvelopeSolid user properties. +process_cls(TetrahedralMeshEnvelopeSolid) diff --git a/opengate/geometry/volumes.py b/opengate/geometry/volumes.py index 537f0a355d..b16e3bfbe3 100644 --- a/opengate/geometry/volumes.py +++ b/opengate/geometry/volumes.py @@ -1346,6 +1346,373 @@ def read_label_to_material(self, labels_filename): self.voxel_materials = labels +# ----------------------------------------------------------------------------- +# Added TetrahedralMesh volume support. +# +# MRCP-specific input parsing is kept private to this volume class, while mesh +# construction is delegated to opengate_core. +# ----------------------------------------------------------------------------- +class TetrahedralMeshVolume(VolumeBase, solids.TetrahedralMeshEnvelopeSolid): + """OpenGATE volume backed by a parameterised Geant4 tetrahedral mesh. + + The outer logical volume is a bounding box derived from the TetGen node + coordinates. During construction, the C++ builder fills that logical + volume with one parameterised daughter whose copies use individual + ``G4Tet`` solids and region-specific materials. + """ + + user_info_defaults = { + "pv_name": ("phantom_tetmesh", {"doc": "PV name used by builder"}), + "check_overlaps": (False, {"doc": "Enable overlap checking"}), + "default_material": ("G4_WATER", {"doc": "Fallback mesh material"}), + "keep_regions": ([], {"doc": "Region IDs to keep"}), + "verbose": (False, {"doc": "Print tetrahedral mesh construction details"}), + } + + _element_symbols = ( + "", + "H", + "He", + "Li", + "Be", + "B", + "C", + "N", + "O", + "F", + "Ne", + "Na", + "Mg", + "Al", + "Si", + "P", + "S", + "Cl", + "Ar", + "K", + "Ca", + "Sc", + "Ti", + "V", + "Cr", + "Mn", + "Fe", + "Co", + "Ni", + "Cu", + "Zn", + "Ga", + "Ge", + "As", + "Se", + "Br", + "Kr", + "Rb", + "Sr", + "Y", + "Zr", + "Nb", + "Mo", + "Tc", + "Ru", + "Rh", + "Pd", + "Ag", + "Cd", + "In", + "Sn", + "Sb", + "Te", + "I", + "Xe", + "Cs", + "Ba", + "La", + "Ce", + "Pr", + "Nd", + "Pm", + "Sm", + "Eu", + "Gd", + "Tb", + "Dy", + "Ho", + "Er", + "Tm", + "Yb", + "Lu", + "Hf", + "Ta", + "W", + "Re", + "Os", + "Ir", + "Pt", + "Au", + "Hg", + "Tl", + "Pb", + "Bi", + "Po", + "At", + "Rn", + "Fr", + "Ra", + "Ac", + "Th", + "Pa", + "U", + "Np", + "Pu", + "Am", + "Cm", + "Bk", + "Cf", + "Es", + "Fm", + "Md", + "No", + "Lr", + "Rf", + "Db", + "Sg", + "Bh", + "Hs", + "Mt", + "Ds", + "Rg", + "Cn", + "Nh", + "Fl", + "Mc", + "Lv", + "Ts", + "Og", + ) + _material_cache = {} + + @staticmethod + def _parse_mrcp_material_file(material_file): + """Parse MRCP material blocks and their region IDs.""" + definitions = {} + region_to_name = {} + current_name = None + current_density = None + current_fractions = {} + + def flush_material(): + nonlocal current_name, current_density, current_fractions + if current_name is None: + return + total = sum(current_fractions.values()) + if current_density is None or current_density <= 0 or total <= 0: + fatal(f"Invalid MRCP material '{current_name}' in {material_file}") + definitions[current_name] = { + "density_g_cm3": float(current_density), + "zfrac": { + z: fraction / total for z, fraction in current_fractions.items() + }, + } + current_name = None + current_density = None + current_fractions = {} + + with open(material_file, "r", encoding="utf-8") as stream: + for line_number, line in enumerate(stream, start=1): + stripped = line.strip() + if not stripped: + continue + if stripped.startswith("C"): + tokens = stripped.split() + flush_material() + if len(tokens) >= 3: + current_name = tokens[1] + try: + current_density = float(tokens[2]) + except ValueError: + fatal(f"Invalid density at {material_file}:{line_number}") + continue + if current_name is None: + fatal( + f"Material data before a header at {material_file}:{line_number}" + ) + tokens = stripped.split() + if tokens[0].startswith("m") and tokens[0][1:].isdigit(): + region_to_name[int(tokens[0][1:])] = current_name + if len(tokens) < 3: + continue + element_code, fraction_text = tokens[1], tokens[2] + elif len(tokens) >= 2: + element_code, fraction_text = tokens[0], tokens[1] + else: + fatal(f"Invalid composition at {material_file}:{line_number}") + try: + atomic_number = int(element_code) // 1000 + fraction = abs(float(fraction_text)) + except ValueError: + fatal(f"Invalid composition at {material_file}:{line_number}") + if not 0 < atomic_number < len(TetrahedralMeshVolume._element_symbols): + fatal(f"Unsupported atomic number Z={atomic_number}") + current_fractions[atomic_number] = ( + current_fractions.get(atomic_number, 0.0) + fraction + ) + flush_material() + return definitions, region_to_name + + @classmethod + def _ensure_custom_material_from_zfrac(cls, name, density_g_cm3, zfrac): + """Build once and reuse a material from elemental mass fractions.""" + if name in cls._material_cache: + return cls._material_cache[name] + if density_g_cm3 <= 0 or density_g_cm3 > 30: + fatal(f"Unreasonable density for '{name}': {density_g_cm3} g/cm3") + material = g4.G4Material( + name, + float(density_g_cm3) * g4_units.g_cm3, + len(zfrac), + g4.kStateSolid, + 293.15 * g4_units.kelvin, + g4_units.atmosphere, + ) + nist = g4.G4NistManager.Instance() + for atomic_number, fraction in sorted(zfrac.items()): + element = nist.FindOrBuildElement(cls._element_symbols[int(atomic_number)]) + material.AddElement(element, float(fraction)) + cls._material_cache[name] = material + return material + + @staticmethod + def _parse_colour_dat(color_file): + """Parse region RGBA entries used by tetrahedral visualization.""" + colors = {} + if not color_file: + return colors + if not os.path.isfile(color_file): + fatal(f"MRCP color file does not exist: {color_file}") + with open(color_file, "r", encoding="utf-8") as stream: + for line_number, line in enumerate(stream, start=1): + stripped = line.strip() + if not stripped or stripped.startswith("#"): + continue + tokens = stripped.replace(",", " ").split() + if len(tokens) < 4: + fatal(f"Invalid color entry at {color_file}:{line_number}") + try: + key = int(tokens[0]) + rgba = [float(value) for value in tokens[1:5]] + except ValueError: + fatal(f"Invalid color entry at {color_file}:{line_number}") + if len(rgba) == 3: + rgba.append(1.0) + if any(value < 0.0 or value > 1.0 for value in rgba): + fatal( + f"RGBA values must be between 0 and 1 at {color_file}:{line_number}" + ) + colors[key] = (rgba, rgba[3] > 0.0) + return colors + + def _build_region_dicts(self): + """Build the region-to-material, RGBA, and visibility dictionaries.""" + + material_definitions, region_to_name = self._parse_mrcp_material_file( + self.material_file + ) + colour_table = self._parse_colour_dat(self.color_file) + + # 3) Select the regions used by this volume. + if self.keep_regions and len(self.keep_regions) > 0: + selected_rids = [int(r) for r in self.keep_regions] + else: + selected_rids = sorted(region_to_name.keys()) + + # 4) Build region-to-material, RGBA, and visibility mappings. + region_to_material = {} + region_to_rgba = {} + region_visible = {} + + for rid in selected_rids: + if rid not in region_to_name: + # Missing regions are omitted and handled by the default + # material in the C++ builder. + continue + + material_name = region_to_name[rid] + if material_name not in material_definitions: + continue + + definition = material_definitions[material_name] + + region_to_material[int(rid)] = self._ensure_custom_material_from_zfrac( + material_name, + definition["density_g_cm3"], + definition["zfrac"], + ) + + # Prefer a region-ID colour entry over a material-name entry. + rgba_vis = None + if int(rid) in colour_table: + rgba_vis = colour_table[int(rid)] + elif material_name in colour_table: + rgba_vis = colour_table[material_name] + + if rgba_vis is None: + rgba, vis = ([0.8, 0.8, 0.8, 1.0], True) + else: + rgba, vis = rgba_vis + + region_to_rgba[int(rid)] = list(rgba) + region_visible[int(rid)] = bool(vis) + + return region_to_material, region_to_rgba, region_visible + + def construct(self): + """Construct the envelope first, then populate it with the TetGen mesh.""" + + if self._is_constructed: + return + + # 1) Construct the outer bounding-box volume first. + self.construct_material() + self.construct_solid() + self.construct_logical_volume() + if self.build_physical_volume is True: + self.construct_physical_volume() + + # 2) Build region-specific material and visualization dictionaries. + region_to_material, region_to_rgba, region_visible = self._build_region_dicts() + + default_material = g4.G4NistManager.Instance().FindOrBuildMaterial( + str(self.default_material), False + ) + if default_material is None: + fatal(f"Cannot build default material {self.default_material}") + + if self.verbose: + print( + f"[TetrahedralMeshVolume] name={self.name}, " + f"envelope_material={self.material}, " + f"default_material={default_material.GetName()}" + ) + + g4.build_mrcp_tetrahedral_mesh_from_tetgen( + self.node_file, + self.ele_file, + self.g4_logical_volume, # The empty envelope logical volume. + region_to_material, + region_to_rgba, + region_visible, + default_material, + self.pv_name, + bool(self.check_overlaps), + ) + + self._is_constructed = True + + +# End of added TetrahedralMesh volume support. + + class ParallelWorldVolume(NodeMixin): def __init__(self, name, volume_manager): # VolumeBase.__init__(self, name) @@ -1441,3 +1808,6 @@ def __getstate__(self): process_cls(RepeatParametrisedVolume) process_cls(ImageVolume) process_cls(TesselatedVolume) + +# Added class processing for TetrahedralMeshVolume user properties. +process_cls(TetrahedralMeshVolume) diff --git a/opengate/managers.py b/opengate/managers.py index 3232ba197c..3f42b630e1 100644 --- a/opengate/managers.py +++ b/opengate/managers.py @@ -149,6 +149,7 @@ PolyhedraVolume, RepeatParametrisedVolume, SphereVolume, + TetrahedralMeshVolume, TesselatedVolume, TrapVolume, TrdVolume, @@ -1452,6 +1453,7 @@ class VolumeManager(GateObject): "BooleanVolume": BooleanVolume, "RepeatParametrisedVolume": RepeatParametrisedVolume, "TesselatedVolume": TesselatedVolume, + "TetrahedralMeshVolume": TetrahedralMeshVolume, } def __init__(self, simulation, *args, **kwargs) -> None: diff --git a/opengate/tests/src/geometry/test115_tetrahedral_mesh.py b/opengate/tests/src/geometry/test115_tetrahedral_mesh.py new file mode 100644 index 0000000000..4abcbeb626 --- /dev/null +++ b/opengate/tests/src/geometry/test115_tetrahedral_mesh.py @@ -0,0 +1,58 @@ +"""CI smoke test for the synthetic MRCP tetrahedral mesh.""" + +from pathlib import Path + +import opengate as gate + +from opengate.tests import utility + + +def test_tetrahedral_mesh(): + # Locate packaged mesh data independently of the current working directory. + opengate_dir = Path(__file__).resolve().parents[3] + data_dir = opengate_dir / "contrib" / "mrcp" + for filename in ( + "simple.node", + "simple.ele", + "simple.material", + "simple_colour.dat", + ): + assert (data_dir / filename).is_file(), f"Missing MRCP fixture: {filename}" + + sim = gate.Simulation() + sim.number_of_threads = 1 + sim.visu = False + sim.world.size = [2 * gate.g4_units.m] * 3 + sim.world.material = "G4_Galactic" + + phantom = sim.add_volume("TetrahedralMesh", "phantom_tetmesh") + phantom.mother = "world" + phantom.material = "G4_Galactic" + phantom.node_file = str(data_dir / "simple.node") + phantom.ele_file = str(data_dir / "simple.ele") + phantom.material_file = str(data_dir / "simple.material") + phantom.color_file = str(data_dir / "simple_colour.dat") + phantom.default_material = "G4_Galactic" + phantom.pv_name = "phantom_tetmesh_env" + + # The mesh has a region centered near x=-12 cm and another at the origin. + # Fire along +x so primaries cross both regions rather than missing the mesh. + source = sim.add_source("GenericSource", "mesh_source") + source.particle = "gamma" + source.number_of_primaries = 100 + source.energy.mono = 1 * gate.g4_units.MeV + source.position.translation = [-18 * gate.g4_units.cm, 0, 0] + source.direction.type = "momentum" + source.direction.momentum = [1, 0, 0] + + stats = sim.add_actor("SimulationStatisticsActor", "stats") + sim.run() + + assert stats.counts.events == 100 + assert stats.counts.tracks >= 100 + assert stats.counts.steps > 0 + + +if __name__ == "__main__": + test_tetrahedral_mesh() + utility.test_ok(True)