diff --git a/include/geode/model/mixin/builder/blocks_builder.hpp b/include/geode/model/mixin/builder/blocks_builder.hpp index 605992214..ee70a2448 100644 --- a/include/geode/model/mixin/builder/blocks_builder.hpp +++ b/include/geode/model/mixin/builder/blocks_builder.hpp @@ -66,6 +66,9 @@ namespace geode void set_block_active( const uuid& id, bool active ); + [[nodiscard]] std::unique_ptr< SolidMesh< dimension > > + steal_block_mesh( const uuid& id ); + protected: explicit BlocksBuilder( Blocks< dimension >& blocks ) : blocks_( blocks ) @@ -88,9 +91,6 @@ namespace geode [[nodiscard]] SolidMesh< dimension >& modifiable_block_mesh( const uuid& id ); - [[nodiscard]] std::unique_ptr< SolidMesh< dimension > > - steal_block_mesh( const uuid& id ); - private: Blocks< dimension >& blocks_; }; diff --git a/include/geode/model/mixin/builder/corners_builder.hpp b/include/geode/model/mixin/builder/corners_builder.hpp index 383eb0714..71ffd489b 100644 --- a/include/geode/model/mixin/builder/corners_builder.hpp +++ b/include/geode/model/mixin/builder/corners_builder.hpp @@ -58,6 +58,9 @@ namespace geode void set_corner_active( const uuid& id, bool active ); + [[nodiscard]] std::unique_ptr< PointSet< dimension > > + steal_corner_mesh( const uuid& id ); + protected: explicit CornersBuilder( Corners< dimension >& corners ) : corners_( corners ) @@ -80,9 +83,6 @@ namespace geode [[nodiscard]] PointSet< dimension >& modifiable_corner_mesh( const uuid& id ); - [[nodiscard]] std::unique_ptr< PointSet< dimension > > - steal_corner_mesh( const uuid& id ); - private: Corners< dimension >& corners_; }; diff --git a/include/geode/model/mixin/builder/lines_builder.hpp b/include/geode/model/mixin/builder/lines_builder.hpp index 4e08d0fce..9133e48eb 100644 --- a/include/geode/model/mixin/builder/lines_builder.hpp +++ b/include/geode/model/mixin/builder/lines_builder.hpp @@ -58,6 +58,9 @@ namespace geode void set_line_active( const uuid& id, bool active ); + [[nodiscard]] std::unique_ptr< EdgedCurve< dimension > > + steal_line_mesh( const uuid& id ); + protected: explicit LinesBuilder( Lines< dimension >& lines ) : lines_( lines ) {} @@ -77,9 +80,6 @@ namespace geode [[nodiscard]] EdgedCurve< dimension >& modifiable_line_mesh( const uuid& id ); - [[nodiscard]] std::unique_ptr< EdgedCurve< dimension > > - steal_line_mesh( const uuid& id ); - private: Lines< dimension >& lines_; }; diff --git a/include/geode/model/mixin/builder/surfaces_builder.hpp b/include/geode/model/mixin/builder/surfaces_builder.hpp index 47c060a5c..3a0bf5b16 100644 --- a/include/geode/model/mixin/builder/surfaces_builder.hpp +++ b/include/geode/model/mixin/builder/surfaces_builder.hpp @@ -66,6 +66,9 @@ namespace geode void set_surface_active( const uuid& id, bool active ); + [[nodiscard]] std::unique_ptr< SurfaceMesh< dimension > > + steal_surface_mesh( const uuid& id ); + protected: explicit SurfacesBuilder( Surfaces< dimension >& surfaces ) : surfaces_( surfaces ) @@ -88,9 +91,6 @@ namespace geode [[nodiscard]] SurfaceMesh< dimension >& modifiable_surface_mesh( const uuid& id ); - [[nodiscard]] std::unique_ptr< SurfaceMesh< dimension > > - steal_surface_mesh( const uuid& id ); - private: Surfaces< dimension >& surfaces_; }; diff --git a/include/geode/model/representation/builder/section_builder.hpp b/include/geode/model/representation/builder/section_builder.hpp index d11c08a5e..73cab5c2e 100644 --- a/include/geode/model/representation/builder/section_builder.hpp +++ b/include/geode/model/representation/builder/section_builder.hpp @@ -99,6 +99,9 @@ namespace geode ModelCopyMapping copy( const Section& section ); + void replace_components_meshes_by_others( + Section&& other, const ModelCopyMapping& mapping ); + ModelCopyMapping copy_components( const Section& section ); void copy_components( diff --git a/include/geode/model/representation/core/detail/transfer_meshes.hpp b/include/geode/model/representation/core/detail/transfer_meshes.hpp new file mode 100644 index 000000000..ab64e051b --- /dev/null +++ b/include/geode/model/representation/core/detail/transfer_meshes.hpp @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2019 - 2026 Geode-solutions + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#pragma once + +#include +#include + +namespace geode +{ + class Section; + class SectionBuilder; + class BRep; + class BRepBuilder; +} // namespace geode + +namespace geode +{ + namespace detail + { + void opengeode_model_api transfer_brep_meshes( const BRep& brep, + BRepBuilder& brep_builder, + BRep&& other, + const ModelCopyMapping& component_mapping ); + + void opengeode_model_api transfer_section_meshes( + const Section& section, + SectionBuilder& section_builder, + Section&& other, + const ModelCopyMapping& component_mapping ); + } // namespace detail +} // namespace geode diff --git a/include/geode/model/representation/core/detail/transfer_metadata.hpp b/include/geode/model/representation/core/detail/transfer_metadata.hpp index fe99dd029..26919bf4a 100644 --- a/include/geode/model/representation/core/detail/transfer_metadata.hpp +++ b/include/geode/model/representation/core/detail/transfer_metadata.hpp @@ -53,7 +53,7 @@ namespace geode void opengeode_model_api transfer_section_metadata( const Section& old_section, - SectionBuilder& new_brep_builder, + SectionBuilder& new_section_builder, const ModelGenericMapping& component_mapping ); template < typename ModelBuilder > diff --git a/src/geode/model/CMakeLists.txt b/src/geode/model/CMakeLists.txt index a0eefe997..78f7ca248 100644 --- a/src/geode/model/CMakeLists.txt +++ b/src/geode/model/CMakeLists.txt @@ -88,6 +88,7 @@ add_geode_library( "representation/builder/section_builder.cpp" "representation/core/detail/clone.cpp" "representation/core/detail/transfer_collections.cpp" + "representation/core/detail/transfer_meshes.cpp" "representation/core/detail/transfer_metadata.cpp" "representation/core/brep.cpp" "representation/core/section.cpp" @@ -188,6 +189,7 @@ add_geode_library( "representation/core/detail/clone.hpp" "representation/core/detail/model_component.hpp" "representation/core/detail/transfer_collections.hpp" + "representation/core/detail/transfer_meshes.hpp" "representation/core/detail/transfer_metadata.hpp" INTERNAL_HEADERS "helpers/internal/simplicial_model_creator.hpp" diff --git a/src/geode/model/representation/builder/brep_builder.cpp b/src/geode/model/representation/builder/brep_builder.cpp index 963f433cf..b7a9a9762 100644 --- a/src/geode/model/representation/builder/brep_builder.cpp +++ b/src/geode/model/representation/builder/brep_builder.cpp @@ -47,71 +47,7 @@ #include #include #include - -namespace -{ - void remove_component_meshes_in_mapping( const geode::BRep& brep, - geode::BRepBuilder& builder, - const geode::ModelCopyMapping& mapping ) - { - for( const auto& in2out : - mapping.at( geode::Corner3D::component_type_static() ) - .in2out_map() ) - { - builder.update_corner_mesh( - brep.corner( in2out.first ), geode::PointSet3D::create() ); - } - for( const auto& in2out : - mapping.at( geode::Line3D::component_type_static() ).in2out_map() ) - { - builder.update_line_mesh( - brep.line( in2out.first ), geode::EdgedCurve3D::create() ); - } - for( const auto& in2out : - mapping.at( geode::Surface3D::component_type_static() ) - .in2out_map() ) - { - builder.update_surface_mesh( - brep.surface( in2out.first ), geode::SurfaceMesh3D::create() ); - } - for( const auto& in2out : - mapping.at( geode::Block3D::component_type_static() ).in2out_map() ) - { - builder.update_block_mesh( - brep.block( in2out.first ), geode::SolidMesh3D::create() ); - } - } - - template < typename Mesh > - absl::FixedArray< geode::index_t > save_mesh_unique_vertices( - const geode::BRep& model, - const Mesh& mesh, - const geode::ComponentID& component_id ) - { - const auto nb_vertices = mesh.nb_vertices(); - absl::FixedArray< geode::index_t > unique_vertices( nb_vertices ); - for( const auto v : geode::Range{ nb_vertices } ) - { - unique_vertices[v] = model.unique_vertex( { component_id, v } ); - } - return unique_vertices; - } - - void set_mesh_unique_vertices( geode::BRepBuilder& builder, - absl::Span< const geode::index_t > unique_vertices, - const geode::ComponentID& component_id, - geode::index_t first_new_unique_vertex ) - { - for( const auto v : geode::Indices{ unique_vertices } ) - { - if( unique_vertices[v] != geode::NO_ID ) - { - builder.set_unique_vertex( { component_id, v }, - first_new_unique_vertex + unique_vertices[v] ); - } - } - } -} // namespace +#include namespace geode { @@ -150,63 +86,8 @@ namespace geode void BRepBuilder::replace_components_meshes_by_others( BRep&& other, const ModelCopyMapping& mapping ) { - BRepBuilder other_builder{ other }; - remove_component_meshes_in_mapping( brep_, *this, mapping ); - this->delete_isolated_vertices(); - const auto first_new_unique_vertex_id = - create_unique_vertices( other.nb_unique_vertices() ); - for( const auto& in2out : - mapping.at( Corner3D::component_type_static() ).in2out_map() ) - { - const auto corner_unique_vertices = save_mesh_unique_vertices( - other, other.corner( in2out.second ).mesh(), - other.corner( in2out.second ).component_id() ); - this->update_corner_mesh( brep_.corner( in2out.first ), - other_builder.steal_corner_mesh( in2out.second ) ); - this->corner_mesh_builder( in2out.first )->set_id( in2out.first ); - set_mesh_unique_vertices( *this, corner_unique_vertices, - brep_.corner( in2out.first ).component_id(), - first_new_unique_vertex_id ); - } - for( const auto& in2out : - mapping.at( Line3D::component_type_static() ).in2out_map() ) - { - const auto line_unique_vertices = save_mesh_unique_vertices( other, - other.line( in2out.second ).mesh(), - other.line( in2out.second ).component_id() ); - this->update_line_mesh( brep_.line( in2out.first ), - other_builder.steal_line_mesh( in2out.second ) ); - this->line_mesh_builder( in2out.first )->set_id( in2out.first ); - set_mesh_unique_vertices( *this, line_unique_vertices, - brep_.line( in2out.first ).component_id(), - first_new_unique_vertex_id ); - } - for( const auto& in2out : - mapping.at( Surface3D::component_type_static() ).in2out_map() ) - { - const auto surface_unique_vertices = save_mesh_unique_vertices( - other, other.surface( in2out.second ).mesh(), - other.surface( in2out.second ).component_id() ); - this->update_surface_mesh( brep_.surface( in2out.first ), - other_builder.steal_surface_mesh( in2out.second ) ); - this->surface_mesh_builder( in2out.first )->set_id( in2out.first ); - set_mesh_unique_vertices( *this, surface_unique_vertices, - brep_.surface( in2out.first ).component_id(), - first_new_unique_vertex_id ); - } - for( const auto& in2out : - mapping.at( Block3D::component_type_static() ).in2out_map() ) - { - const auto block_unique_vertices = save_mesh_unique_vertices( other, - other.block( in2out.second ).mesh(), - other.block( in2out.second ).component_id() ); - this->update_block_mesh( brep_.block( in2out.first ), - other_builder.steal_block_mesh( in2out.second ) ); - this->block_mesh_builder( in2out.first )->set_id( in2out.first ); - set_mesh_unique_vertices( *this, block_unique_vertices, - brep_.block( in2out.first ).component_id(), - first_new_unique_vertex_id ); - } + detail::transfer_brep_meshes( + brep_, *this, std::move( other ), mapping ); } ModelCopyMapping BRepBuilder::copy_components( const BRep& brep ) diff --git a/src/geode/model/representation/builder/section_builder.cpp b/src/geode/model/representation/builder/section_builder.cpp index 419b5ecd9..6cf812d60 100644 --- a/src/geode/model/representation/builder/section_builder.cpp +++ b/src/geode/model/representation/builder/section_builder.cpp @@ -42,6 +42,7 @@ #include #include #include +#include #include namespace geode @@ -76,6 +77,13 @@ namespace geode return mapping; } + void SectionBuilder::replace_components_meshes_by_others( + Section&& other, const ModelCopyMapping& mapping ) + { + detail::transfer_section_meshes( + section_, *this, std::move( other ), mapping ); + } + ModelCopyMapping SectionBuilder::copy_components( const Section& section ) { ModelCopyMapping mappings; diff --git a/src/geode/model/representation/core/detail/transfer_meshes.cpp b/src/geode/model/representation/core/detail/transfer_meshes.cpp new file mode 100644 index 000000000..54334cab0 --- /dev/null +++ b/src/geode/model/representation/core/detail/transfer_meshes.cpp @@ -0,0 +1,290 @@ +/* + * Copyright (c) 2019 - 2026 Geode-solutions + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ + template < typename Model > + void remove_corner_meshes_in_mapping( const Model& model, + typename Model::Builder& builder, + const geode::ModelCopyMapping& mapping ) + { + for( const auto& [in, _] : + mapping.at( geode::Corner< Model::dim >::component_type_static() ) + .in2out_map() ) + { + builder.update_corner_mesh( + model.corner( in ), geode::PointSet< Model::dim >::create() ); + } + } + + template < typename Model > + void remove_line_meshes_in_mapping( const Model& model, + typename Model::Builder& builder, + const geode::ModelCopyMapping& mapping ) + { + for( const auto& in2out : + mapping.at( geode::Line< Model::dim >::component_type_static() ) + .in2out_map() ) + { + builder.update_line_mesh( model.line( in2out.first ), + geode::EdgedCurve< Model::dim >::create() ); + } + } + + template < typename Model > + void remove_surface_meshes_in_mapping( const Model& model, + typename Model::Builder& builder, + const geode::ModelCopyMapping& mapping ) + { + for( const auto& in2out : + mapping.at( geode::Surface< Model::dim >::component_type_static() ) + .in2out_map() ) + { + builder.update_surface_mesh( model.surface( in2out.first ), + geode::SurfaceMesh< Model::dim >::create() ); + } + } + + template < typename Model > + void remove_block_meshes_in_mapping( const Model& model, + typename Model::Builder& builder, + const geode::ModelCopyMapping& mapping ) + { + for( const auto& in2out : + mapping.at( geode::Block< Model::dim >::component_type_static() ) + .in2out_map() ) + { + builder.update_block_mesh( model.block( in2out.first ), + geode::SolidMesh< Model::dim >::create() ); + } + } + + template < typename Model > + absl::FixedArray< geode::index_t > save_mesh_unique_vertices( + const Model& model, + geode::index_t nb_vertices, + const geode::ComponentID& component_id ) + { + absl::FixedArray< geode::index_t > unique_vertices( nb_vertices ); + for( const auto v : geode::Range{ nb_vertices } ) + { + unique_vertices[v] = model.unique_vertex( { component_id, v } ); + } + return unique_vertices; + } + + template < typename ModelBuilder > + void set_mesh_unique_vertices( ModelBuilder& builder, + absl::Span< const geode::index_t > unique_vertices, + const geode::ComponentID& component_id, + geode::index_t first_new_unique_vertex ) + { + for( const auto v : geode::Indices{ unique_vertices } ) + { + if( unique_vertices[v] != geode::NO_ID ) + { + builder.set_unique_vertex( { component_id, v }, + first_new_unique_vertex + unique_vertices[v] ); + } + } + } + + template < typename Model > + void set_corner_meshes_in_mapping( const Model& model, + typename Model::Builder& builder, + const Model& other, + typename Model::Builder& other_builder, + const geode::ModelCopyMapping& mapping, + geode::index_t first_new_unique_vertex_id ) + { + for( const auto& [corner_id, other_corner_id] : + mapping.at( geode::Corner< Model::dim >::component_type_static() ) + .in2out_map() ) + { + const auto& other_corner = other.corner( other_corner_id ); + const auto corner_unique_vertices = save_mesh_unique_vertices( + other, other_corner.mesh().nb_vertices(), + other_corner.component_id() ); + const auto& corner = model.corner( corner_id ); + builder.update_corner_mesh( + corner, other_builder.steal_corner_mesh( other_corner_id ) ); + auto mesh_builder = builder.corner_mesh_builder( corner_id ); + mesh_builder->set_id( corner.id() ); + mesh_builder->set_name( corner.name() ); + set_mesh_unique_vertices( builder, corner_unique_vertices, + corner.component_id(), first_new_unique_vertex_id ); + } + } + + template < typename Model > + void set_line_meshes_in_mapping( const Model& model, + typename Model::Builder& builder, + const Model& other, + typename Model::Builder& other_builder, + const geode::ModelCopyMapping& mapping, + geode::index_t first_new_unique_vertex_id ) + { + for( const auto& [line_id, other_line_id] : + mapping.at( geode::Line< Model::dim >::component_type_static() ) + .in2out_map() ) + { + const auto& other_line = other.line( other_line_id ); + const auto line_unique_vertices = save_mesh_unique_vertices( other, + other_line.mesh().nb_vertices(), other_line.component_id() ); + const auto& line = model.line( line_id ); + builder.update_line_mesh( + line, other_builder.steal_line_mesh( other_line_id ) ); + auto mesh_builder = builder.line_mesh_builder( line_id ); + mesh_builder->set_id( line.id() ); + mesh_builder->set_name( line.name() ); + set_mesh_unique_vertices( builder, line_unique_vertices, + line.component_id(), first_new_unique_vertex_id ); + } + } + + template < typename Model > + void set_surface_meshes_in_mapping( const Model& model, + typename Model::Builder& builder, + const Model& other, + typename Model::Builder& other_builder, + const geode::ModelCopyMapping& mapping, + geode::index_t first_new_unique_vertex_id ) + { + for( const auto& [surface_id, other_surface_id] : + mapping.at( geode::Surface< Model::dim >::component_type_static() ) + .in2out_map() ) + { + const auto& other_surface = other.surface( other_surface_id ); + const auto surface_unique_vertices = save_mesh_unique_vertices( + other, other_surface.mesh().nb_vertices(), + other_surface.component_id() ); + const auto& surface = model.surface( surface_id ); + builder.update_surface_mesh( + surface, other_builder.steal_surface_mesh( other_surface_id ) ); + auto mesh_builder = builder.surface_mesh_builder( surface_id ); + mesh_builder->set_id( surface.id() ); + mesh_builder->set_name( surface.name() ); + set_mesh_unique_vertices( builder, surface_unique_vertices, + surface.component_id(), first_new_unique_vertex_id ); + } + } + + template < typename Model > + void set_block_meshes_in_mapping( const Model& model, + typename Model::Builder& builder, + const Model& other, + typename Model::Builder& other_builder, + const geode::ModelCopyMapping& mapping, + geode::index_t first_new_unique_vertex_id ) + { + for( const auto& [block_id, other_block_id] : + mapping.at( geode::Block< Model::dim >::component_type_static() ) + .in2out_map() ) + { + const auto& other_block = other.block( other_block_id ); + const auto block_unique_vertices = save_mesh_unique_vertices( other, + other_block.mesh().nb_vertices(), other_block.component_id() ); + const auto& block = model.block( block_id ); + builder.update_block_mesh( + block, other_builder.steal_block_mesh( other_block_id ) ); + auto mesh_builder = builder.block_mesh_builder( block_id ); + mesh_builder->set_id( block.id() ); + mesh_builder->set_name( block.name() ); + set_mesh_unique_vertices( builder, block_unique_vertices, + block.component_id(), first_new_unique_vertex_id ); + } + } +} // namespace + +namespace geode +{ + namespace detail + { + void transfer_brep_meshes( const BRep& brep, + BRepBuilder& brep_builder, + BRep&& other, + const ModelCopyMapping& mapping ) + { + remove_corner_meshes_in_mapping( brep, brep_builder, mapping ); + remove_line_meshes_in_mapping( brep, brep_builder, mapping ); + remove_surface_meshes_in_mapping( brep, brep_builder, mapping ); + remove_block_meshes_in_mapping( brep, brep_builder, mapping ); + brep_builder.delete_isolated_vertices(); + const auto first_new_unique_vertex_id = + brep_builder.create_unique_vertices( + other.nb_unique_vertices() ); + BRepBuilder other_builder{ other }; + set_corner_meshes_in_mapping( brep, brep_builder, other, + other_builder, mapping, first_new_unique_vertex_id ); + set_line_meshes_in_mapping( brep, brep_builder, other, + other_builder, mapping, first_new_unique_vertex_id ); + set_surface_meshes_in_mapping( brep, brep_builder, other, + other_builder, mapping, first_new_unique_vertex_id ); + set_block_meshes_in_mapping( brep, brep_builder, other, + other_builder, mapping, first_new_unique_vertex_id ); + } + + void transfer_section_meshes( const Section& section, + SectionBuilder& section_builder, + Section&& other, + const ModelCopyMapping& mapping ) + { + remove_corner_meshes_in_mapping( + section, section_builder, mapping ); + remove_line_meshes_in_mapping( section, section_builder, mapping ); + remove_surface_meshes_in_mapping( + section, section_builder, mapping ); + section_builder.delete_isolated_vertices(); + const auto first_new_unique_vertex_id = + section_builder.create_unique_vertices( + other.nb_unique_vertices() ); + SectionBuilder other_builder{ other }; + set_corner_meshes_in_mapping( section, section_builder, other, + other_builder, mapping, first_new_unique_vertex_id ); + set_line_meshes_in_mapping( section, section_builder, other, + other_builder, mapping, first_new_unique_vertex_id ); + set_surface_meshes_in_mapping( section, section_builder, other, + other_builder, mapping, first_new_unique_vertex_id ); + } + } // namespace detail +} // namespace geode