├── .gitignore ├── data ├── test_2.2_bin.msh ├── test_4.1_bin.msh ├── test_2.2_ascii.msh └── test_4.1_ascii.msh ├── python ├── mshio │ └── __init__.py └── pymshio.cpp ├── src ├── load_msh_post_process.h ├── load_msh_entities.h ├── load_msh_format.h ├── load_msh_curves.h ├── load_msh_nodes.h ├── load_msh_elements.h ├── load_msh_patches.h ├── save_msh_nodes.h ├── save_msh_curves.h ├── save_msh_patches.h ├── save_msh_elements.h ├── save_msh_format.h ├── save_msh_physical_groups.h ├── load_msh_physical_groups.h ├── save_msh_entities.h ├── io_utils.h ├── load_msh_nanospline_format.h ├── element_utils.h ├── load_msh_data.h ├── save_msh_data.h ├── save_msh_nanospline_format.h ├── io_utils.cpp ├── save_msh_format.cpp ├── load_msh_physical_groups.cpp ├── save_msh_physical_groups.cpp ├── load_msh_format.cpp ├── save_msh.cpp ├── element_utils.cpp ├── load_msh_curves.cpp ├── load_msh_patches.cpp ├── load_msh.cpp ├── validate_spec.cpp ├── save_msh_curves.cpp ├── save_msh_patches.cpp ├── load_msh_post_process.cpp ├── save_msh_data.cpp ├── save_msh_nodes.cpp ├── load_msh_data.cpp ├── save_msh_elements.cpp ├── load_msh_nodes.cpp ├── save_msh_entities.cpp ├── load_msh_entities.cpp └── load_msh_elements.cpp ├── cmake ├── sanitizer-cmake.cmake ├── python.cmake ├── nanobind.cmake ├── Catch2.cmake └── CPM.cmake ├── include └── mshio │ ├── mshio.h │ ├── MshSpecExt.h │ ├── exception.h │ └── MshSpec.h ├── pyproject.toml ├── .github └── workflows │ └── build.yml ├── CMakeLists.txt ├── examples └── msh_inspect.cpp ├── LICENSE ├── .clang-format ├── README.md └── tests └── test_io.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | build*/ 2 | _skbuild/ 3 | *.egg-info/ 4 | __pycache__/ 5 | -------------------------------------------------------------------------------- /data/test_2.2_bin.msh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qnzhou/MshIO/HEAD/data/test_2.2_bin.msh -------------------------------------------------------------------------------- /data/test_4.1_bin.msh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qnzhou/MshIO/HEAD/data/test_4.1_bin.msh -------------------------------------------------------------------------------- /python/mshio/__init__.py: -------------------------------------------------------------------------------- 1 | """ A tiny C++ library to read/write ASCII/binary MSH format files. 2 | """ 3 | from .pymshio import * 4 | __version__ = "0.1.1" 5 | -------------------------------------------------------------------------------- /src/load_msh_post_process.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace mshio { 6 | 7 | void load_msh_post_process(MshSpec& spec); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/load_msh_entities.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | namespace mshio { 6 | 7 | void load_entities(std::istream& in, MshSpec& spec); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/load_msh_format.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | namespace mshio { 6 | 7 | void load_mesh_format(std::istream& in, MshSpec& spec); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/load_msh_curves.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace mshio { 8 | 9 | void load_curves(std::istream& in, MshSpec& spec); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/load_msh_nodes.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace mshio { 8 | 9 | void load_nodes(std::istream& in, MshSpec& spec); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/load_msh_elements.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace mshio { 8 | 9 | void load_elements(std::istream& in, MshSpec& spec); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/load_msh_patches.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace mshio { 8 | 9 | void load_patches(std::istream& in, MshSpec& spec); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/save_msh_nodes.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace mshio { 8 | 9 | void save_nodes(std::ostream& out, const MshSpec& spec); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/save_msh_curves.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace mshio { 8 | 9 | void save_curves(std::ostream& out, const MshSpec& spec); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/save_msh_patches.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace mshio { 8 | 9 | void save_patches(std::ostream& out, const MshSpec& spec); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/save_msh_elements.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace mshio { 8 | 9 | void save_elements(std::ostream& out, const MshSpec& spec); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/save_msh_format.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace mshio { 8 | 9 | void save_mesh_format(std::ostream& out, const MshSpec& spec); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/save_msh_physical_groups.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | namespace mshio { 6 | 7 | void save_physical_groups(std::ostream& out, const MshSpec& spec); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/load_msh_physical_groups.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | namespace mshio { 6 | 7 | void load_physical_groups(std::istream& in, MshSpec& spec); 8 | 9 | } // namespace mshio 10 | -------------------------------------------------------------------------------- /src/save_msh_entities.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace mshio { 7 | 8 | void save_entities(std::ostream& out, const MshSpec& spec); 9 | 10 | } // namespace mshio 11 | -------------------------------------------------------------------------------- /src/io_utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace mshio { 7 | 8 | void eat_white_space(std::istream& in, size_t count = std::numeric_limits::max()); 9 | 10 | } // namespace mshio 11 | 12 | -------------------------------------------------------------------------------- /src/load_msh_nanospline_format.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace mshio 8 | { 9 | 10 | inline void load_nanospline_format(std::istream& in, MshSpec& spec) 11 | { 12 | in >> spec.nanospline_format.version; 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/element_utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace mshio { 8 | 9 | void assert_element_is_supported(int element_type); 10 | 11 | size_t nodes_per_element(int element_type); 12 | 13 | int get_element_dim(int element_type); 14 | 15 | } // namespace mshio 16 | -------------------------------------------------------------------------------- /src/load_msh_data.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace mshio { 7 | 8 | void load_node_data(std::istream& in, MshSpec& spec); 9 | 10 | void load_element_data(std::istream& in, MshSpec& spec); 11 | 12 | void load_element_node_data(std::istream& in, MshSpec& spec); 13 | 14 | } // namespace mshio 15 | -------------------------------------------------------------------------------- /src/save_msh_data.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace mshio { 7 | 8 | void save_node_data(std::ostream& out, const MshSpec& spec); 9 | 10 | void save_element_data(std::ostream& out, const MshSpec& spec); 11 | 12 | void save_element_node_data(std::ostream& out, const MshSpec& spec); 13 | 14 | } // namespace mshio 15 | -------------------------------------------------------------------------------- /src/save_msh_nanospline_format.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace mshio 8 | { 9 | 10 | inline void save_nanospline_format(std::ostream& out, const MshSpec& spec) 11 | { 12 | out << "$NanoSplineFormat" << std::endl; 13 | out << spec.nanospline_format.version << std::endl; 14 | out << "$EndNanoSplineFormat" << std::endl; 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /data/test_2.2_ascii.msh: -------------------------------------------------------------------------------- 1 | $MeshFormat 2 | 2.2 0 8 3 | $EndMeshFormat 4 | $Nodes 5 | 6 6 | 1 0.0 0.0 0.0 7 | 2 1.0 0.0 0.0 8 | 3 1.0 1.0 0.0 9 | 4 0.0 1.0 0.0 10 | 5 2.0 0.0 0.0 11 | 6 2.0 1.0 0.0 12 | $EndNodes 13 | $Elements 14 | 2 15 | 1 3 2 99 2 1 2 3 4 16 | 2 3 2 99 2 2 5 6 3 17 | $EndElements 18 | $NodeData 19 | 1 20 | "A scalar view" 21 | 1 22 | 0.0 23 | 3 24 | 0 25 | 1 26 | 6 27 | 1 0.0 28 | 2 0.1 29 | 3 0.2 30 | 4 0.0 31 | 5 0.2 32 | 6 0.4 33 | $EndNodeData 34 | -------------------------------------------------------------------------------- /cmake/sanitizer-cmake.cmake: -------------------------------------------------------------------------------- 1 | if(COMMAND add_sanitizers) 2 | return() 3 | endif() 4 | 5 | message(STATUS "Third-party (external): creating method 'add_sanitizers'") 6 | 7 | include(CPM) 8 | CPMAddPackage( 9 | NAME sanitizer 10 | GITHUB_REPOSITORY arsenm/sanitizers-cmake 11 | GIT_TAG 0573e2ea8651b9bb3083f193c41eb086497cc80a 12 | DOWNLOAD_ONLY ON 13 | ) 14 | 15 | block() 16 | set(CMAKE_MODULE_PATH "${sanitizer_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) 17 | find_package(Sanitizers REQUIRED) 18 | endblock() 19 | -------------------------------------------------------------------------------- /src/io_utils.cpp: -------------------------------------------------------------------------------- 1 | #include "io_utils.h" 2 | 3 | #include 4 | 5 | namespace mshio { 6 | 7 | void eat_white_space(std::istream& in, size_t count) 8 | { 9 | char ch = static_cast(in.peek()); 10 | while (count > 0) { 11 | if (ch == '\n' || ch == '\r' || ch == ' ' || ch == '\t') { 12 | in.get(); 13 | ch = static_cast(in.peek()); 14 | } else { 15 | break; 16 | } 17 | count--; 18 | } 19 | } 20 | 21 | } // namespace mshio 22 | 23 | -------------------------------------------------------------------------------- /include/mshio/mshio.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | namespace mshio { 9 | 10 | MshSpec load_msh(std::istream& in); 11 | MshSpec load_msh(const std::string& filename); 12 | 13 | void save_msh(std::ostream& out, const MshSpec& spec); 14 | void save_msh(const std::string& filename, const MshSpec& spec); 15 | 16 | void validate_spec(const MshSpec& spec); 17 | 18 | size_t nodes_per_element(int element_type); 19 | int get_element_dim(int element_type); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /data/test_4.1_ascii.msh: -------------------------------------------------------------------------------- 1 | $MeshFormat 2 | 4.1 0 8 3 | $EndMeshFormat 4 | $Entities 5 | 4 1 0 0 6 | 1 0.1 0.1 0.1 0 7 | 2 0.5 0.1 0.1 0 8 | 3 0.5 0.5 0.1 0 9 | 4 0.1 0.5 0.1 0 10 | 1 0. 0. 0. 1. 1. 1. 0 4 1 2 3 4 11 | $EndEntities 12 | $Nodes 13 | 1 6 1 6 14 | 2 2 0 6 15 | 1 16 | 2 17 | 3 18 | 4 19 | 5 20 | 6 21 | 0. 0. 0. 1. 0. 0. 1. 1. 0. 0. 1. 0. 2. 0. 0. 2. 1. 0. 22 | $EndNodes 23 | $Elements 24 | 1 2 1 2 25 | 2 2 3 2 26 | 1 1 2 3 4 27 | 2 2 5 6 3 28 | $EndElements 29 | $NodeData 30 | 1 31 | "A scalar view" 32 | 1 33 | 0.0 34 | 3 35 | 0 36 | 1 37 | 6 38 | 1 0.0 39 | 2 0.1 40 | 3 0.2 41 | 4 0.0 42 | 5 0.2 43 | 6 0.4 44 | $EndNodeData 45 | -------------------------------------------------------------------------------- /src/save_msh_format.cpp: -------------------------------------------------------------------------------- 1 | #include "save_msh_format.h" 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace mshio { 8 | 9 | void save_mesh_format(std::ostream& out, const MshSpec& spec) 10 | { 11 | const MeshFormat& format = spec.mesh_format; 12 | out << "$MeshFormat" << std::endl; 13 | out << format.version << " " << format.file_type << " " << format.data_size << std::endl; 14 | if (format.file_type == 1) { 15 | constexpr int one = 1; 16 | out.write(reinterpret_cast(&one), sizeof(int)); 17 | } 18 | out << "$EndMeshFormat" << std::endl; 19 | } 20 | 21 | } // namespace mshio 22 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "scikit-build-core==0.9.8", 4 | ] 5 | build-backend = "scikit_build_core.build" 6 | 7 | [project] 8 | name = "mshio" 9 | maintainers = [{name = "Qingnan Zhou", email = "qnzhou@gmail.com"}] 10 | description = "A tiny C++ library to read/write ASCII/binary MSH format files." 11 | readme = "README.md" 12 | requires-python = ">=3.9" 13 | dynamic = ["version"] 14 | license = { file = "LICENSE" } 15 | 16 | dependencies = [ 17 | "numpy>=1.25", 18 | ] 19 | urls.repository = "https://github.com/qnzhou/MshIO" 20 | 21 | [tool.scikit-build.metadata.version] 22 | provider = "scikit_build_core.metadata.regex" 23 | input = "python/mshio/__init__.py" 24 | -------------------------------------------------------------------------------- /src/load_msh_physical_groups.cpp: -------------------------------------------------------------------------------- 1 | #include "load_msh_physical_groups.h" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace mshio { 11 | 12 | void load_physical_groups(std::istream& in, MshSpec& spec) 13 | { 14 | auto& groups = spec.physical_groups; 15 | int num_groups; 16 | in >> num_groups; 17 | spec.physical_groups.resize(num_groups); 18 | for (int i = 0; i < num_groups; i++) { 19 | auto& group = groups[i]; 20 | in >> group.dim; 21 | in >> group.tag; 22 | in >> std::quoted(group.name); 23 | } 24 | assert(in.good()); 25 | } 26 | 27 | } // namespace mshio 28 | -------------------------------------------------------------------------------- /src/save_msh_physical_groups.cpp: -------------------------------------------------------------------------------- 1 | #include "save_msh_physical_groups.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | namespace mshio { 10 | 11 | void save_physical_groups(std::ostream& out, const MshSpec& spec) 12 | { 13 | out << "$PhysicalNames" << std::endl; 14 | const auto& groups = spec.physical_groups; 15 | out << groups.size() << std::endl; 16 | 17 | for (const auto& group: groups) { 18 | out << group.dim << " "; 19 | out << group.tag << " "; 20 | out << std::quoted(group.name) << std::endl; 21 | } 22 | out << "$EndPhysicalNames" << std::endl; 23 | } 24 | 25 | } // namespace mshio 26 | -------------------------------------------------------------------------------- /cmake/python.cmake: -------------------------------------------------------------------------------- 1 | if (TARGET Python::Module) 2 | return() 3 | endif() 4 | 5 | if (SKBUILD) 6 | message(STATUS "Use scikit-build python environment") 7 | message(STATUS "Python_VERSION ${PYTHON_VERSION_STRING}") 8 | message(STATUS "Python_EXECUTABLE ${PYTHON_EXECUTABLE}") 9 | message(STATUS "Python_INCLUDE_DIR ${PYTHON_INCLUDE_DIR}") 10 | message(STATUS "Python_LIBRARIES ${PYTHON_LIBRARY}") 11 | set(Python_VERSION "${PYTHON_VERSION_STRING}") 12 | set(Python_EXECUTABLE "${PYTHON_EXECUTABLE}") 13 | set(Python_INCLUDE_DIR "${PYTHON_INCLUDE_DIR}") 14 | set(Python_LIBRARIES "${PYTHON_LIBRARY}") 15 | else() 16 | set(Python_FIND_VIRTUALENV FIRST) 17 | endif() 18 | 19 | find_package(Python COMPONENTS Interpreter Development.Module REQUIRED) 20 | 21 | -------------------------------------------------------------------------------- /include/mshio/MshSpecExt.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace mshio { 7 | 8 | struct NanoSplineFormat 9 | { 10 | std::string version = "1.0"; 11 | }; 12 | 13 | struct Curve 14 | { 15 | size_t curve_tag = 0; 16 | size_t curve_type = 0; 17 | size_t curve_degree = 0; 18 | size_t num_control_points = 0; 19 | size_t num_knots = 0; 20 | size_t with_weights = 0; 21 | std::vector data; 22 | }; 23 | 24 | struct Patch 25 | { 26 | size_t patch_tag = 0; 27 | size_t patch_type = 0; 28 | size_t degree_u = 0; 29 | size_t degree_v = 0; 30 | size_t num_control_points = 0; 31 | size_t num_u_knots = 0; 32 | size_t num_v_knots = 0; 33 | size_t with_weights = 0; 34 | std::vector data; 35 | }; 36 | 37 | } 38 | -------------------------------------------------------------------------------- /cmake/nanobind.cmake: -------------------------------------------------------------------------------- 1 | if(COMMAND nanobind_add_module) 2 | return() 3 | endif() 4 | 5 | message(STATUS "Third-party (external): creating target 'nanobind::nanobind'") 6 | 7 | include(CPM) 8 | CPMAddPackage( 9 | NAME nanobind 10 | GITHUB_REPOSITORY wjakob/nanobind 11 | GIT_TAG v2.9.2 12 | DOWNLOAD_ONLY ON 13 | ) 14 | 15 | include(python) 16 | set(NB_SHARED OFF CACHE INTERNAL "") 17 | 18 | # Set this until https://github.com/wjakob/nanobind/issues/1123 is resolved. 19 | set(NB_PY_PATH "${Python_EXECUTABLE}" CACHE INTERNAL "Path to Python executable for nanobind") 20 | 21 | # Note that we do not use FetchContent_MakeAvailable here because nanobind's cmake changes 22 | # CMAKE_CXX_FLAGS and attempts to refind python, which can leads to cmake error. 23 | find_package(nanobind PATHS ${nanobind_SOURCE_DIR}/cmake NO_DEFAULT_PATH) 24 | -------------------------------------------------------------------------------- /include/mshio/exception.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace mshio { 7 | 8 | struct InvalidFormat : public std::exception 9 | { 10 | public: 11 | InvalidFormat(const std::string& message) 12 | : m_message(message) 13 | {} 14 | 15 | const char* what() const noexcept override { return m_message.c_str(); } 16 | 17 | private: 18 | std::string m_message; 19 | }; 20 | 21 | struct UnsupportedFeature : public std::exception 22 | { 23 | public: 24 | UnsupportedFeature(const std::string& message) 25 | : m_message(message) 26 | {} 27 | 28 | const char* what() const noexcept override { return m_message.c_str(); } 29 | 30 | private: 31 | std::string m_message; 32 | }; 33 | 34 | struct CorruptData : public std::exception 35 | { 36 | public: 37 | CorruptData(const std::string& message) 38 | : m_message(message) 39 | {} 40 | 41 | const char* what() const noexcept override { return m_message.c_str(); } 42 | 43 | private: 44 | std::string m_message; 45 | }; 46 | 47 | } // namespace mshio 48 | -------------------------------------------------------------------------------- /cmake/Catch2.cmake: -------------------------------------------------------------------------------- 1 | if(TARGET Catch2::Catch2) 2 | return() 3 | endif() 4 | 5 | message(STATUS "Third-party (external): creating target 'Catch2::Catch2'") 6 | 7 | option(CATCH_CONFIG_CPP17_STRING_VIEW "Enable support for std::string_view" ON) 8 | option(CATCH_INSTALL_DOCS "Install documentation alongside library" OFF) 9 | option(CATCH_INSTALL_EXTRAS "Install extras alongside library" OFF) 10 | 11 | include(CPM) 12 | CPMAddPackage( 13 | NAME catch2 14 | GITHUB_REPOSITORY catchorg/Catch2 15 | GIT_TAG v3.10.0 16 | SYSTEM On 17 | ) 18 | 19 | target_compile_features(Catch2 PUBLIC cxx_std_17) 20 | 21 | if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 22 | target_compile_options(Catch2 PUBLIC -Wno-nonnull) 23 | 24 | if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 10) 25 | # See https://github.com/catchorg/Catch2/issues/2654 26 | target_compile_options(Catch2 PUBLIC -Wno-parentheses) 27 | endif() 28 | endif() 29 | 30 | set_target_properties(Catch2 PROPERTIES FOLDER third_party) 31 | set_target_properties(Catch2WithMain PROPERTIES FOLDER third_party) 32 | include(${catch2_SOURCE_DIR}/extras/Catch.cmake) 33 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build and test 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | build-Ubuntu: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | 11 | - name: build and run 12 | run: | 13 | mkdir build 14 | cd build 15 | cmake .. -DCMAKE_BUILD_TYPE=Release -DMSHIO_BUILD_TESTS=On -DMSHIO_BUILD_EXAMPLES=On 16 | make -j 17 | ctest 18 | 19 | build-MacOS: 20 | runs-on: macos-latest 21 | steps: 22 | - uses: actions/checkout@v4 23 | 24 | - name: build and run 25 | run: | 26 | mkdir build 27 | cd build 28 | cmake .. -DCMAKE_BUILD_TYPE=Release -DMSHIO_BUILD_TESTS=On -DMSHIO_BUILD_EXAMPLES=On 29 | make -j 30 | ctest 31 | 32 | build-Windows: 33 | runs-on: windows-latest 34 | steps: 35 | - uses: actions/checkout@v4 36 | 37 | - name: build and run 38 | run: | 39 | mkdir build 40 | cd build 41 | cmake .. -DCMAKE_GENERATOR_PLATFORM=x64 -DMSHIO_BUILD_TESTS=On -DMSHIO_BUILD_EXAMPLES=On 42 | cmake --build . --config Release 43 | ctest 44 | -------------------------------------------------------------------------------- /cmake/CPM.cmake: -------------------------------------------------------------------------------- 1 | set(CPM_DOWNLOAD_VERSION 0.42.0) 2 | 3 | if(CPM_SOURCE_CACHE) 4 | set(CPM_DOWNLOAD_LOCATION "${CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") 5 | elseif(DEFINED ENV{CPM_SOURCE_CACHE}) 6 | set(CPM_DOWNLOAD_LOCATION "$ENV{CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") 7 | else() 8 | set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM_${CPM_DOWNLOAD_VERSION}.cmake") 9 | endif() 10 | 11 | # Expand relative path. This is important if the provided path contains a tilde (~) 12 | get_filename_component(CPM_DOWNLOAD_LOCATION ${CPM_DOWNLOAD_LOCATION} ABSOLUTE) 13 | 14 | function(download_cpm) 15 | message(STATUS "Downloading CPM.cmake to ${CPM_DOWNLOAD_LOCATION}") 16 | file(DOWNLOAD 17 | https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake 18 | ${CPM_DOWNLOAD_LOCATION} 19 | ) 20 | endfunction() 21 | 22 | if(NOT (EXISTS ${CPM_DOWNLOAD_LOCATION})) 23 | download_cpm() 24 | else() 25 | # resume download if it previously failed 26 | file(READ ${CPM_DOWNLOAD_LOCATION} check) 27 | if("${check}" STREQUAL "") 28 | download_cpm() 29 | endif() 30 | unset(check) 31 | endif() 32 | 33 | include(${CPM_DOWNLOAD_LOCATION}) 34 | -------------------------------------------------------------------------------- /src/load_msh_format.cpp: -------------------------------------------------------------------------------- 1 | #include "load_msh_format.h" 2 | #include "io_utils.h" 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace mshio { 13 | 14 | void load_mesh_format(std::istream& in, MshSpec& spec) 15 | { 16 | MeshFormat& format = spec.mesh_format; 17 | in >> format.version; 18 | 19 | if (format.version != "2.2" && format.version != "4.1") { 20 | std::stringstream msg; 21 | msg << "Unsupported MSH version: " << format.version; 22 | throw UnsupportedFeature(msg.str()); 23 | } 24 | 25 | in >> format.file_type; 26 | in >> format.data_size; 27 | 28 | if (format.version == "4.1" && sizeof(size_t) != format.data_size) { 29 | std::stringstream msg; 30 | msg << "MSH file (v4.1) requested data size of " << format.data_size 31 | << " bytes, which is different than `size_t` (" << sizeof(size_t) << " bytes)"; 32 | throw UnsupportedFeature(msg.str()); 33 | } 34 | 35 | if (format.file_type != 0) { 36 | int one = 0; 37 | eat_white_space(in); 38 | in.read(reinterpret_cast(&one), sizeof(int)); 39 | if (one != 1) { 40 | throw UnsupportedFeature( 41 | "MSH file (v" + format.version + ") is encoded with unsupported endianness!"); 42 | } 43 | } 44 | } 45 | 46 | } // namespace mshio 47 | -------------------------------------------------------------------------------- /src/save_msh.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "save_msh_curves.h" 4 | #include "save_msh_data.h" 5 | #include "save_msh_elements.h" 6 | #include "save_msh_entities.h" 7 | #include "save_msh_format.h" 8 | #include "save_msh_nodes.h" 9 | #include "save_msh_patches.h" 10 | #include "save_msh_physical_groups.h" 11 | #include "save_msh_nanospline_format.h" 12 | 13 | #include 14 | #include 15 | 16 | namespace mshio { 17 | 18 | void save_msh(std::ostream& out, const MshSpec& spec) 19 | { 20 | save_mesh_format(out, spec); 21 | if (spec.physical_groups.size() > 0) { 22 | save_physical_groups(out, spec); 23 | } 24 | if (!spec.entities.empty()) { 25 | save_entities(out, spec); 26 | } 27 | if (spec.nodes.num_nodes > 0) { 28 | save_nodes(out, spec); 29 | } 30 | if (spec.elements.num_elements > 0) { 31 | save_elements(out, spec); 32 | } 33 | if (spec.node_data.size() > 0) { 34 | save_node_data(out, spec); 35 | } 36 | if (spec.element_data.size() > 0) { 37 | save_element_data(out, spec); 38 | } 39 | if (spec.element_node_data.size() > 0) { 40 | save_element_node_data(out, spec); 41 | } 42 | #ifdef MSHIO_EXT_NANOSPLINE 43 | save_nanospline_format(out, spec); 44 | if (spec.curves.size() > 0) { 45 | save_curves(out, spec); 46 | } 47 | if (spec.patches.size() > 0) { 48 | save_patches(out, spec); 49 | } 50 | #endif 51 | } 52 | 53 | void save_msh(const std::string& filename, const MshSpec& spec) 54 | { 55 | std::ofstream fout(filename.c_str(), std::ios::binary); 56 | if (!fout.is_open()) { 57 | throw std::runtime_error("Unable to open output file to write!"); 58 | } 59 | save_msh(fout, spec); 60 | } 61 | 62 | } // namespace mshio 63 | -------------------------------------------------------------------------------- /src/element_utils.cpp: -------------------------------------------------------------------------------- 1 | #include "element_utils.h" 2 | 3 | namespace mshio { 4 | 5 | void assert_element_is_supported(int element_type) 6 | { 7 | if (element_type <= 0 || element_type >= 32) { 8 | std::stringstream msg; 9 | msg << "Unsupported element type: " << element_type; 10 | throw UnsupportedFeature(msg.str()); 11 | } 12 | } 13 | 14 | size_t nodes_per_element(int element_type) 15 | { 16 | assert_element_is_supported(element_type); 17 | // We support the first 15 element type so far. 18 | constexpr size_t element_sizes[] = {0, 19 | 2, 20 | 3, 21 | 4, 22 | 4, 23 | 8, // 5 24 | 6, 25 | 5, 26 | 3, 27 | 6, 28 | 9, // 10 29 | 10, 30 | 27, 31 | 18, 32 | 14, 33 | 1, // 15 34 | 8, 35 | 20, 36 | 15, 37 | 13, 38 | 9, // 20 39 | 10, 40 | 12, 41 | 15, 42 | 15, 43 | 21, // 25 44 | 4, 45 | 5, 46 | 6, 47 | 20, 48 | 35, // 30 49 | 56}; 50 | return element_sizes[element_type]; 51 | } 52 | 53 | int get_element_dim(int element_type) 54 | { 55 | assert_element_is_supported(element_type); 56 | // We support the first 15 element type so far. 57 | constexpr int element_dims[] = {0, 58 | 1, 59 | 2, 60 | 2, 61 | 3, 62 | 3, // 5 63 | 3, 64 | 3, 65 | 1, 66 | 2, 67 | 2, // 10 68 | 3, 69 | 3, 70 | 3, 71 | 3, 72 | 0, // 15 73 | 2, 74 | 3, 75 | 3, 76 | 3, 77 | 2, // 20 78 | 2, 79 | 2, 80 | 2, 81 | 2, 82 | 2, // 25 83 | 1, 84 | 1, 85 | 1, 86 | 3, 87 | 3, // 30 88 | 3}; 89 | return element_dims[element_type]; 90 | } 91 | 92 | } // namespace mshio 93 | -------------------------------------------------------------------------------- /src/load_msh_curves.cpp: -------------------------------------------------------------------------------- 1 | #include "load_msh_curves.h" 2 | #include "io_utils.h" 3 | 4 | namespace mshio { 5 | 6 | namespace { 7 | 8 | void load_curves_ascii(std::istream& in, MshSpec& spec) 9 | { 10 | #ifdef MSHIO_EXT_NANOSPLINE 11 | auto& curves = spec.curves; 12 | size_t num_curves; 13 | in >> num_curves; 14 | 15 | curves.resize(num_curves); 16 | for (size_t i = 0; i < num_curves; i++) { 17 | auto& curve = curves[i]; 18 | in >> curve.curve_tag; 19 | in >> curve.curve_type; 20 | in >> curve.curve_degree; 21 | in >> curve.num_control_points; 22 | in >> curve.num_knots; 23 | in >> curve.with_weights; 24 | 25 | size_t num_entries = 26 | curve.num_control_points * ((curve.with_weights > 0) ? 4 : 3) + curve.num_knots; 27 | curve.data.resize(num_entries); 28 | for (size_t j = 0; j < num_entries; j++) { 29 | in >> curve.data[j]; 30 | } 31 | } 32 | #endif 33 | } 34 | 35 | void load_curves_binary(std::istream& in, MshSpec& spec) 36 | { 37 | #ifdef MSHIO_EXT_NANOSPLINE 38 | auto& curves = spec.curves; 39 | size_t num_curves; 40 | in >> num_curves; 41 | 42 | curves.resize(num_curves); 43 | for (size_t i = 0; i < num_curves; i++) { 44 | auto& curve = curves[i]; 45 | in >> curve.curve_tag; 46 | in >> curve.curve_type; 47 | in >> curve.curve_degree; 48 | in >> curve.num_control_points; 49 | in >> curve.num_knots; 50 | in >> curve.with_weights; 51 | 52 | eat_white_space(in, 1); 53 | size_t num_entries = 54 | curve.num_control_points * ((curve.with_weights > 0) ? 4 : 3) + curve.num_knots; 55 | curve.data.resize(num_entries); 56 | in.read(reinterpret_cast(curve.data.data()), sizeof(double) * num_entries); 57 | } 58 | #endif 59 | } 60 | 61 | } // namespace 62 | 63 | void load_curves(std::istream& in, MshSpec& spec) 64 | { 65 | const bool is_ascii = spec.mesh_format.file_type == 0; 66 | if (is_ascii) { 67 | load_curves_ascii(in, spec); 68 | } else { 69 | load_curves_binary(in, spec); 70 | } 71 | } 72 | 73 | } // namespace mshio 74 | -------------------------------------------------------------------------------- /src/load_msh_patches.cpp: -------------------------------------------------------------------------------- 1 | #include "load_msh_patches.h" 2 | #include "io_utils.h" 3 | 4 | namespace mshio { 5 | 6 | namespace { 7 | 8 | void load_patches_ascii(std::istream& in, MshSpec& spec) 9 | { 10 | #ifdef MSHIO_EXT_NANOSPLINE 11 | auto& patches = spec.patches; 12 | size_t num_patches; 13 | in >> num_patches; 14 | patches.resize(num_patches); 15 | 16 | for (size_t i = 0; i < num_patches; i++) { 17 | auto& patch = patches[i]; 18 | in >> patch.patch_tag; 19 | in >> patch.patch_type; 20 | in >> patch.degree_u; 21 | in >> patch.degree_v; 22 | in >> patch.num_control_points; 23 | in >> patch.num_u_knots; 24 | in >> patch.num_v_knots; 25 | in >> patch.with_weights; 26 | 27 | const size_t dim = (patch.with_weights > 0) ? 4 : 3; 28 | const size_t num_entries = 29 | patch.num_control_points * dim + patch.num_u_knots + patch.num_v_knots; 30 | 31 | patch.data.resize(num_entries); 32 | for (size_t j = 0; j < num_entries; j++) { 33 | in >> patch.data[j]; 34 | } 35 | } 36 | #endif 37 | } 38 | 39 | void load_patches_binary(std::istream& in, MshSpec& spec) 40 | { 41 | #ifdef MSHIO_EXT_NANOSPLINE 42 | auto& patches = spec.patches; 43 | size_t num_patches; 44 | in >> num_patches; 45 | patches.resize(num_patches); 46 | 47 | for (size_t i = 0; i < num_patches; i++) { 48 | auto& patch = patches[i]; 49 | in >> patch.patch_tag; 50 | in >> patch.patch_type; 51 | in >> patch.degree_u; 52 | in >> patch.degree_v; 53 | in >> patch.num_control_points; 54 | in >> patch.num_u_knots; 55 | in >> patch.num_v_knots; 56 | in >> patch.with_weights; 57 | 58 | const size_t dim = (patch.with_weights > 0) ? 4 : 3; 59 | const size_t num_entries = 60 | patch.num_control_points * dim + patch.num_u_knots + patch.num_v_knots; 61 | 62 | patch.data.resize(num_entries); 63 | eat_white_space(in, 1); 64 | 65 | in.read(reinterpret_cast(patch.data.data()), sizeof(double) * num_entries); 66 | } 67 | #endif 68 | } 69 | 70 | } // namespace 71 | 72 | void load_patches(std::istream& in, MshSpec& spec) 73 | { 74 | const bool is_ascii = spec.mesh_format.file_type == 0; 75 | if (is_ascii) { 76 | load_patches_ascii(in, spec); 77 | } else { 78 | load_patches_binary(in, spec); 79 | } 80 | } 81 | 82 | } // namespace mshio 83 | -------------------------------------------------------------------------------- /src/load_msh.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "load_msh_curves.h" 4 | #include "load_msh_data.h" 5 | #include "load_msh_elements.h" 6 | #include "load_msh_entities.h" 7 | #include "load_msh_format.h" 8 | #include "load_msh_nanospline_format.h" 9 | #include "load_msh_nodes.h" 10 | #include "load_msh_patches.h" 11 | #include "load_msh_physical_groups.h" 12 | #include "load_msh_post_process.h" 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | namespace mshio { 20 | 21 | void forward_to(std::istream& in, const std::string& flag) 22 | { 23 | std::string buf; 24 | while (!in.eof() && buf != flag) { 25 | in >> buf; 26 | } 27 | } 28 | 29 | MshSpec load_msh(std::istream& in) 30 | { 31 | MshSpec spec; 32 | std::string buf, end_str; 33 | 34 | while (!in.eof()) { 35 | buf.clear(); 36 | in >> buf; 37 | if (buf.size() == 0 || buf[0] != '$') continue; 38 | end_str = "$End" + buf.substr(1); 39 | if (buf == "$MeshFormat") { 40 | load_mesh_format(in, spec); 41 | } else if (buf == "$Entities") { 42 | load_entities(in, spec); 43 | } else if (buf == "$PhysicalNames") { 44 | load_physical_groups(in, spec); 45 | } else if (buf == "$Nodes") { 46 | load_nodes(in, spec); 47 | } else if (buf == "$Elements") { 48 | load_elements(in, spec); 49 | } else if (buf == "$NodeData") { 50 | load_node_data(in, spec); 51 | } else if (buf == "$ElementData") { 52 | load_element_data(in, spec); 53 | } else if (buf == "$ElementNodeData") { 54 | load_element_node_data(in, spec); 55 | } else if (buf == "$NanoSplineFormat") { 56 | load_nanospline_format(in, spec); 57 | } else if (buf == "$Curves") { 58 | load_curves(in, spec); 59 | } else if (buf == "$Patches") { 60 | load_patches(in, spec); 61 | } else { 62 | std::cerr << "Warning: skipping section \"" << buf << "\"" << std::endl; 63 | } 64 | forward_to(in, end_str); 65 | } 66 | 67 | load_msh_post_process(spec); 68 | return spec; 69 | } 70 | 71 | MshSpec load_msh(const std::string& filename) 72 | { 73 | std::ifstream fin(filename.c_str(), std::ios::binary); 74 | if (!fin.is_open()) { 75 | throw std::runtime_error("Input file does not exist!"); 76 | } 77 | return load_msh(fin); 78 | } 79 | 80 | } // namespace mshio 81 | -------------------------------------------------------------------------------- /src/validate_spec.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | namespace mshio { 7 | 8 | void validate_spec(const MshSpec& spec) 9 | { 10 | const Nodes& nodes = spec.nodes; 11 | const Elements& elements = spec.elements; 12 | 13 | auto ASSERT = [](bool r, const std::string& msg) { 14 | if (!r) { 15 | throw CorruptData(msg); 16 | } 17 | }; 18 | 19 | ASSERT(nodes.num_entity_blocks == nodes.entity_blocks.size(), 20 | "Inconsistent entity blocks in nodes."); 21 | ASSERT(elements.num_entity_blocks == elements.entity_blocks.size(), 22 | "Inconsistent entity blocks in elements."); 23 | 24 | ASSERT(nodes.min_node_tag <= nodes.max_node_tag, "Min node tag > max node tag."); 25 | ASSERT( 26 | elements.min_element_tag <= elements.max_element_tag, "Min element tag > max element tag."); 27 | 28 | for (size_t i = 0; i < nodes.num_entity_blocks; i++) { 29 | const NodeBlock& block = nodes.entity_blocks[i]; 30 | ASSERT(block.tags.size() == block.num_nodes_in_block, "Inconsist number of node tags."); 31 | for (size_t j = 0; j < block.num_nodes_in_block; j++) { 32 | ASSERT(block.tags[j] >= nodes.min_node_tag, "Node tag < min node tag."); 33 | ASSERT(block.tags[j] <= nodes.max_node_tag, "Node tag > max node tag."); 34 | } 35 | 36 | if (block.parametric > 0) { 37 | ASSERT(block.data.size() == 38 | block.num_nodes_in_block * static_cast(3 + block.entity_dim), 39 | "Invalide node data size."); 40 | } else { 41 | ASSERT(block.data.size() == block.num_nodes_in_block * 3, "Invalid node data size."); 42 | } 43 | } 44 | 45 | for (size_t i = 0; i < elements.num_entity_blocks; i++) { 46 | const ElementBlock& block = elements.entity_blocks[i]; 47 | const size_t entries_per_element = block.data.size() / block.num_elements_in_block; 48 | ASSERT(block.data.size() % entries_per_element == 0, "Invalid element data size."); 49 | for (size_t j = 0; j < block.data.size(); j++) { 50 | if (j % entries_per_element == 0) { 51 | ASSERT(block.data[j] >= elements.min_element_tag, "Element tag < min element tag."); 52 | ASSERT(block.data[j] <= elements.max_element_tag, "Element tag > max element tag."); 53 | } else { 54 | ASSERT(block.data[j] >= nodes.min_node_tag, "Node tag in element < min node tag."); 55 | ASSERT(block.data[j] <= nodes.max_node_tag, "Node tag in element > max node tag."); 56 | } 57 | } 58 | } 59 | } 60 | 61 | } // namespace mshio 62 | -------------------------------------------------------------------------------- /src/save_msh_curves.cpp: -------------------------------------------------------------------------------- 1 | #include "save_msh_curves.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | namespace mshio { 9 | 10 | namespace { 11 | 12 | void save_curves_ascii(std::ostream& out, const MshSpec& spec) 13 | { 14 | #ifdef MSHIO_EXT_NANOSPLINE 15 | const auto& curves = spec.curves; 16 | out << curves.size() << std::endl; 17 | 18 | for (const auto& curve : curves) { 19 | out << curve.curve_tag << " "; 20 | out << curve.curve_type << " "; 21 | out << curve.curve_degree << " "; 22 | out << curve.num_control_points << " "; 23 | out << curve.num_knots << " "; 24 | out << curve.with_weights << std::endl; 25 | 26 | const size_t dim = (curve.with_weights > 0) ? 4 : 3; 27 | const size_t num_entries = curve.num_control_points * dim + curve.num_knots; 28 | assert(num_entries == curve.data.size()); 29 | 30 | for (size_t i = 0; i < curve.num_control_points; i++) { 31 | for (size_t j = 0; j < dim; j++) { 32 | out << curve.data[i * dim + j]; 33 | if (j + 1 == dim) { 34 | out << std::endl; 35 | } else { 36 | out << " "; 37 | } 38 | } 39 | } 40 | 41 | for (size_t i = 0; i < curve.num_knots; i++) { 42 | out << curve.data[curve.num_control_points * dim + i] << std::endl; 43 | } 44 | } 45 | #endif 46 | } 47 | 48 | void save_curves_binary(std::ostream& out, const MshSpec& spec) 49 | { 50 | #ifdef MSHIO_EXT_NANOSPLINE 51 | const auto& curves = spec.curves; 52 | out << curves.size() << std::endl; 53 | 54 | for (const auto& curve : curves) { 55 | out << curve.curve_tag << " "; 56 | out << curve.curve_type << " "; 57 | out << curve.curve_degree << " "; 58 | out << curve.num_control_points << " "; 59 | out << curve.num_knots << " "; 60 | out << curve.with_weights << std::endl; 61 | 62 | const size_t dim = (curve.with_weights > 0) ? 4 : 3; 63 | const size_t num_entries = curve.num_control_points * dim + curve.num_knots; 64 | assert(num_entries == curve.data.size()); 65 | 66 | out.write(reinterpret_cast(curve.data.data()), sizeof(double) * num_entries); 67 | } 68 | #endif 69 | } 70 | 71 | } // namespace 72 | 73 | void save_curves(std::ostream& out, const MshSpec& spec) 74 | { 75 | const bool is_ascii = spec.mesh_format.file_type == 0; 76 | 77 | out << "$Curves" << std::endl; 78 | if (is_ascii) { 79 | save_curves_ascii(out, spec); 80 | } else { 81 | save_curves_binary(out, spec); 82 | } 83 | out << "$EndCurves" << std::endl; 84 | } 85 | 86 | } // namespace mshio 87 | -------------------------------------------------------------------------------- /src/save_msh_patches.cpp: -------------------------------------------------------------------------------- 1 | #include "save_msh_patches.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | namespace mshio { 9 | 10 | namespace { 11 | 12 | void save_patches_ascii(std::ostream& out, const MshSpec& spec) 13 | { 14 | #ifdef MSHIO_EXT_NANOSPLINE 15 | const auto& patches = spec.patches; 16 | out << patches.size() << std::endl; 17 | for (const auto& patch : patches) { 18 | out << patch.patch_tag << " "; 19 | out << patch.patch_type << " "; 20 | out << patch.degree_u << " "; 21 | out << patch.degree_v << " "; 22 | out << patch.num_control_points << " "; 23 | out << patch.num_u_knots << " "; 24 | out << patch.num_v_knots << " "; 25 | out << patch.with_weights << std::endl; 26 | 27 | const size_t dim = (patch.with_weights > 0) ? 4 : 3; 28 | const size_t num_entries = 29 | patch.num_control_points * dim + patch.num_u_knots + patch.num_v_knots; 30 | assert(patch.data.size() == num_entries); 31 | 32 | for (size_t i = 0; i < patch.num_control_points; i++) { 33 | for (size_t j = 0; j < dim; j++) { 34 | out << patch.data[i * dim + j]; 35 | if (j + 1 == dim) { 36 | out << std::endl; 37 | } else { 38 | out << ' '; 39 | } 40 | } 41 | } 42 | 43 | for (size_t i = 0; i < patch.num_u_knots; i++) { 44 | out << patch.data[patch.num_control_points * dim + i] << std::endl; 45 | } 46 | for (size_t i = 0; i < patch.num_v_knots; i++) { 47 | out << patch.data[patch.num_control_points * dim + patch.num_u_knots + i] << std::endl; 48 | } 49 | } 50 | #endif 51 | } 52 | 53 | void save_patches_binary(std::ostream& out, const MshSpec& spec) 54 | { 55 | #ifdef MSHIO_EXT_NANOSPLINE 56 | const auto& patches = spec.patches; 57 | out << patches.size() << std::endl; 58 | for (const auto& patch : patches) { 59 | out << patch.patch_tag << " "; 60 | out << patch.patch_type << " "; 61 | out << patch.degree_u << " "; 62 | out << patch.degree_v << " "; 63 | out << patch.num_control_points << " "; 64 | out << patch.num_u_knots << " "; 65 | out << patch.num_v_knots << " "; 66 | out << patch.with_weights << std::endl; 67 | 68 | const size_t dim = (patch.with_weights > 0) ? 4 : 3; 69 | const size_t num_entries = 70 | patch.num_control_points * dim + patch.num_u_knots + patch.num_v_knots; 71 | assert(patch.data.size() == num_entries); 72 | 73 | out.write(reinterpret_cast(patch.data.data()), sizeof(double) * num_entries); 74 | } 75 | #endif 76 | } 77 | 78 | } // namespace 79 | 80 | 81 | void save_patches(std::ostream& out, const MshSpec& spec) 82 | { 83 | const bool is_ascii = spec.mesh_format.file_type == 0; 84 | 85 | out << "$Patches" << std::endl; 86 | if (is_ascii) { 87 | save_patches_ascii(out, spec); 88 | } else { 89 | save_patches_binary(out, spec); 90 | } 91 | out << "$EndPatches" << std::endl; 92 | } 93 | 94 | } // namespace mshio 95 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # ============================================================================ 2 | # This file will generate the cmake target `MshIO::MshIO`. 3 | # 4 | # To add MshIO as a dependency: 5 | # 6 | # add_subdirectory(MshIO) 7 | # target_link_libraries(your_target mshio::mshio) 8 | # 9 | # ============================================================================ 10 | message(STATUS "CMake version: ${CMAKE_VERSION}") 11 | cmake_minimum_required(VERSION 3.11) 12 | 13 | project(MshIO) 14 | 15 | option(MSHIO_BUILD_TESTS "Build unit tests" OFF) 16 | option(MSHIO_BUILD_EXAMPLES "Build examples" OFF) 17 | option(MSHIO_EXT_NANOSPLINE "Enable nanospline extension" OFF) 18 | option(MSHIO_PYTHON "Build python binding" OFF) 19 | 20 | SET(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/") 21 | 22 | file(GLOB INC_FILES "${PROJECT_SOURCE_DIR}/include/mshio/*.h") 23 | file(GLOB SRC_FILES "${PROJECT_SOURCE_DIR}/src/*.cpp") 24 | 25 | add_library(mshio STATIC ${SRC_FILES}) 26 | target_include_directories(mshio PUBLIC 27 | "$" 28 | "$") 29 | set_property(TARGET mshio PROPERTY POSITION_INDEPENDENT_CODE ON) 30 | target_compile_features(mshio PUBLIC cxx_std_14) 31 | 32 | add_library(mshio::mshio ALIAS mshio) 33 | 34 | 35 | if (MSHIO_EXT_NANOSPLINE) 36 | target_compile_definitions(mshio PUBLIC -DMSHIO_EXT_NANOSPLINE) 37 | endif() 38 | 39 | 40 | if (MSHIO_PYTHON) 41 | include(nanobind) 42 | set(PY_SRC_FILE "${PROJECT_SOURCE_DIR}/python/pymshio.cpp") 43 | nanobind_add_module(pymshio NB_STATIC ${PY_SRC_FILE}) 44 | target_link_libraries(pymshio PUBLIC mshio::mshio) 45 | install(TARGETS pymshio LIBRARY DESTINATION .) 46 | add_library(mshio::pymshio ALIAS pymshio) 47 | set_property(TARGET pymshio PROPERTY POSITION_INDEPENDENT_CODE ON) 48 | endif() 49 | 50 | 51 | if (MSHIO_BUILD_EXAMPLES) 52 | add_executable(msh_inspect ${PROJECT_SOURCE_DIR}/examples/msh_inspect.cpp) 53 | target_link_libraries(msh_inspect PRIVATE mshio::mshio) 54 | endif() 55 | 56 | 57 | if (MSHIO_BUILD_TESTS) 58 | include(CTest) 59 | enable_testing() 60 | include(Catch2) 61 | include(sanitizer-cmake) 62 | 63 | file(GLOB TEST_FILES "${PROJECT_SOURCE_DIR}/tests/*.cpp") 64 | add_executable(test_MshIO ${TEST_FILES}) 65 | target_link_libraries(test_MshIO mshio::mshio Catch2::Catch2WithMain) 66 | target_compile_definitions(test_MshIO PRIVATE 67 | MSHIO_DATA_DIR="${PROJECT_SOURCE_DIR}/data") 68 | set_property(TARGET test_MshIO PROPERTY POSITION_INDEPENDENT_CODE ON) 69 | catch_discover_tests(test_MshIO) 70 | 71 | if(NOT MSVC) 72 | target_compile_options(test_MshIO PRIVATE -Wconversion -Wall -Werror) 73 | else() 74 | target_compile_options(test_MshIO PRIVATE "/MP") 75 | endif() 76 | 77 | if (SANITIZE_ADDRESS OR 78 | SANITIZE_LINK_STATIC OR 79 | SANITIZE_MEMORY OR 80 | SANITIZE_THREAD OR 81 | SANITIZE_UNDEFINED) 82 | add_sanitizers(test_MshIO) 83 | endif() 84 | endif() 85 | 86 | 87 | if (NOT MSHIO_PYTHON) 88 | include(GNUInstallDirs) 89 | install(TARGETS mshio 90 | EXPORT mshio_target 91 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 92 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 93 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) 94 | install(DIRECTORY include/ DESTINATION include 95 | FILES_MATCHING PATTERN "*.h") 96 | install(EXPORT mshio_target DESTINATION cmake) 97 | endif() 98 | -------------------------------------------------------------------------------- /include/mshio/MshSpec.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | namespace mshio { 9 | 10 | struct MeshFormat 11 | { 12 | std::string version = "4.1"; 13 | int file_type = 0; 14 | int data_size = sizeof(size_t); 15 | }; 16 | 17 | struct NodeBlock 18 | { 19 | int entity_dim = 0; 20 | int entity_tag = 0; 21 | int parametric = 0; 22 | size_t num_nodes_in_block = 0; 23 | std::vector tags; 24 | std::vector data; 25 | }; 26 | 27 | struct Nodes 28 | { 29 | size_t num_entity_blocks = 0; 30 | size_t num_nodes = 0; 31 | size_t min_node_tag = 0; 32 | size_t max_node_tag = 0; 33 | std::vector entity_blocks; 34 | }; 35 | 36 | struct ElementBlock 37 | { 38 | int entity_dim = 0; 39 | int entity_tag = 0; 40 | int element_type = 0; 41 | size_t num_elements_in_block = 0; 42 | std::vector data; 43 | }; 44 | 45 | struct Elements 46 | { 47 | size_t num_entity_blocks = 0; 48 | size_t num_elements = 0; 49 | size_t min_element_tag = 0; 50 | size_t max_element_tag = 0; 51 | std::vector entity_blocks; 52 | }; 53 | 54 | struct DataHeader 55 | { 56 | std::vector string_tags; // [view name, ] 57 | std::vector real_tags; // [time value] 58 | std::vector int_tags; // [time step, num fields, num entries, partition id] 59 | }; 60 | 61 | struct DataEntry 62 | { 63 | size_t tag = 0; 64 | int num_nodes_per_element = 0; // Only used by ElementNodeData. 65 | std::vector data; 66 | }; 67 | 68 | struct Data 69 | { 70 | DataHeader header; 71 | std::vector entries; 72 | }; 73 | 74 | struct PointEntity { 75 | int tag = 0; 76 | double x = 0.0; 77 | double y = 0.0; 78 | double z = 0.0; 79 | std::vector physical_group_tags; 80 | }; 81 | 82 | struct CurveEntity { 83 | int tag = 0; 84 | double min_x = 0.0; 85 | double min_y = 0.0; 86 | double min_z = 0.0; 87 | double max_x = 0.0; 88 | double max_y = 0.0; 89 | double max_z = 0.0; 90 | std::vector physical_group_tags; 91 | std::vector boundary_point_tags; 92 | }; 93 | 94 | struct SurfaceEntity { 95 | int tag = 0; 96 | double min_x = 0.0; 97 | double min_y = 0.0; 98 | double min_z = 0.0; 99 | double max_x = 0.0; 100 | double max_y = 0.0; 101 | double max_z = 0.0; 102 | std::vector physical_group_tags; 103 | std::vector boundary_curve_tags; 104 | }; 105 | 106 | struct VolumeEntity { 107 | int tag = 0; 108 | double min_x = 0.0; 109 | double min_y = 0.0; 110 | double min_z = 0.0; 111 | double max_x = 0.0; 112 | double max_y = 0.0; 113 | double max_z = 0.0; 114 | std::vector physical_group_tags; 115 | std::vector boundary_surface_tags; 116 | }; 117 | 118 | struct Entities { 119 | std::vector points; 120 | std::vector curves; 121 | std::vector surfaces; 122 | std::vector volumes; 123 | 124 | bool empty() const { 125 | return points.size() == 0 && curves.size() == 0 && surfaces.size() == 0 126 | && volumes.size() == 0; 127 | } 128 | }; 129 | 130 | struct PhysicalGroup 131 | { 132 | int dim = 0; 133 | int tag = 0; 134 | std::string name; 135 | }; 136 | 137 | struct MshSpec 138 | { 139 | MeshFormat mesh_format; 140 | Nodes nodes; 141 | Elements elements; 142 | Entities entities; 143 | std::vector physical_groups; 144 | std::vector node_data; 145 | std::vector element_data; 146 | std::vector element_node_data; 147 | 148 | // Custom sections 149 | NanoSplineFormat nanospline_format; 150 | std::vector curves; 151 | std::vector patches; 152 | }; 153 | 154 | } // namespace mshio 155 | -------------------------------------------------------------------------------- /src/load_msh_post_process.cpp: -------------------------------------------------------------------------------- 1 | #include "load_msh_post_process.h" 2 | #include "element_utils.h" 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | namespace mshio { 10 | namespace v22 { 11 | 12 | void regroup_nodes_into_blocks(MshSpec& spec) 13 | { 14 | auto& nodes = spec.nodes; 15 | auto& elements = spec.elements; 16 | 17 | std::vector entity_dims(nodes.max_node_tag - nodes.min_node_tag + 1, 0); 18 | std::vector entity_tags(nodes.max_node_tag - nodes.min_node_tag + 1, 0); 19 | auto node_index = [&](size_t node_tag) { 20 | return static_cast(node_tag - nodes.min_node_tag); 21 | }; 22 | 23 | for (const auto& block : elements.entity_blocks) { 24 | const auto n = nodes_per_element(block.element_type); 25 | for (size_t i = 0; i < block.num_elements_in_block; i++) { 26 | for (size_t j = 0; j < n; j++) { 27 | size_t idx = node_index(block.data[i * (n + 1) + j + 1]); 28 | entity_dims[idx] = block.entity_dim; 29 | entity_tags[idx] = block.entity_tag; 30 | } 31 | } 32 | } 33 | 34 | std::vector node_blocks; 35 | node_blocks.reserve(16); 36 | 37 | constexpr size_t INVALID = std::numeric_limits::max(); 38 | size_t curr_dim = INVALID, curr_tag = INVALID; 39 | for (auto& block : nodes.entity_blocks) { 40 | for (size_t i = 0; i < block.num_nodes_in_block; i++) { 41 | size_t idx = node_index(block.tags[i]); 42 | size_t dim = entity_dims[idx]; 43 | size_t tag = entity_tags[idx]; 44 | 45 | if (dim != curr_dim || tag != curr_tag) { 46 | node_blocks.emplace_back(); 47 | auto& curr_block = node_blocks.back(); 48 | curr_block.tags.reserve(block.num_nodes_in_block); 49 | curr_block.data.reserve(block.num_nodes_in_block * 3); 50 | curr_block.entity_dim = static_cast(dim); 51 | curr_block.entity_tag = static_cast(tag); 52 | curr_dim = dim; 53 | curr_tag = tag; 54 | } 55 | 56 | assert(!node_blocks.empty()); 57 | auto& curr_block = node_blocks.back(); 58 | curr_block.tags.push_back(block.tags[i]); 59 | curr_block.data.push_back(block.data[i * 3]); 60 | curr_block.data.push_back(block.data[i * 3 + 1]); 61 | curr_block.data.push_back(block.data[i * 3 + 2]); 62 | } 63 | } 64 | 65 | for (auto& block : node_blocks) { 66 | block.num_nodes_in_block = block.tags.size(); 67 | } 68 | std::swap(spec.nodes.entity_blocks, node_blocks); 69 | spec.nodes.num_entity_blocks = spec.nodes.entity_blocks.size(); 70 | } 71 | 72 | void regroup_elements_into_blocks(MshSpec& spec) 73 | { 74 | auto& elements = spec.elements; 75 | 76 | std::vector element_blocks; 77 | element_blocks.reserve(elements.num_entity_blocks); 78 | 79 | size_t curr_dim = 0, curr_tag = 0, curr_element_type = 0; 80 | for (auto& block : elements.entity_blocks) { 81 | if (block.entity_dim != curr_dim || block.entity_tag != curr_tag || 82 | block.element_type != curr_element_type) { 83 | element_blocks.emplace_back(); 84 | auto& curr_block = element_blocks.back(); 85 | curr_block.entity_dim = block.entity_dim; 86 | curr_block.entity_tag = block.entity_tag; 87 | curr_block.element_type = block.element_type; 88 | 89 | curr_dim = block.entity_dim; 90 | curr_tag = block.entity_tag; 91 | curr_element_type = block.element_type; 92 | } 93 | 94 | auto& curr_block = element_blocks.back(); 95 | curr_block.num_elements_in_block += block.num_elements_in_block; 96 | curr_block.data.insert(curr_block.data.end(), block.data.begin(), block.data.end()); 97 | } 98 | 99 | std::swap(spec.elements.entity_blocks, element_blocks); 100 | spec.elements.num_entity_blocks = spec.elements.entity_blocks.size(); 101 | } 102 | 103 | } // namespace v22 104 | 105 | void load_msh_post_process(MshSpec& spec) 106 | { 107 | if (spec.mesh_format.version == "2.2") { 108 | v22::regroup_nodes_into_blocks(spec); 109 | v22::regroup_elements_into_blocks(spec); 110 | } 111 | } 112 | 113 | } // namespace mshio 114 | -------------------------------------------------------------------------------- /src/save_msh_data.cpp: -------------------------------------------------------------------------------- 1 | #include "save_msh_data.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace mshio { 12 | 13 | namespace internal { 14 | 15 | void save_data(std::ostream& out, 16 | const Data& data, 17 | const std::string& version, 18 | bool is_binary, 19 | bool is_element_node_data) 20 | { 21 | out << data.header.string_tags.size() << std::endl; 22 | for (const std::string& tag : data.header.string_tags) { 23 | out << std::quoted(tag) << std::endl; 24 | } 25 | 26 | out << data.header.real_tags.size() << std::endl; 27 | for (const double& tag : data.header.real_tags) { 28 | out << tag << std::endl; 29 | } 30 | 31 | out << data.header.int_tags.size() << std::endl; 32 | for (const int& tag : data.header.int_tags) { 33 | out << tag << std::endl; 34 | } 35 | 36 | if (is_binary) { 37 | if (version == "4.1") { 38 | int32_t tag; 39 | for (const DataEntry& entry : data.entries) { 40 | // TODO: 41 | // Based on trial and error, it seems Gmsh 4.7.1 still expect 32 42 | // bits tag, which is inconsistent with their spec. Maybe 43 | // report a bug? 44 | tag = static_cast(entry.tag); 45 | out.write(reinterpret_cast(&tag), 4); 46 | // out.write(reinterpret_cast(&entry.tag), sizeof(size_t)); 47 | if (is_element_node_data) { 48 | out.write( 49 | reinterpret_cast(&entry.num_nodes_per_element), sizeof(int)); 50 | } 51 | out.write(reinterpret_cast(entry.data.data()), 52 | static_cast(sizeof(double) * entry.data.size())); 53 | } 54 | } else if (version == "2.2") { 55 | int32_t tag, num_nodes_per_element; 56 | for (const DataEntry& entry : data.entries) { 57 | tag = static_cast(entry.tag); 58 | out.write(reinterpret_cast(&tag), 4); 59 | if (is_element_node_data) { 60 | num_nodes_per_element = static_cast(entry.num_nodes_per_element); 61 | out.write(reinterpret_cast(&num_nodes_per_element), 4); 62 | } 63 | out.write(reinterpret_cast(entry.data.data()), 64 | static_cast(sizeof(double) * entry.data.size())); 65 | } 66 | } else { 67 | throw InvalidFormat("Unsupported version " + version); 68 | } 69 | } else { 70 | for (const DataEntry& entry : data.entries) { 71 | out << entry.tag << " "; 72 | if (is_element_node_data) { 73 | out << entry.num_nodes_per_element << " "; 74 | } 75 | for (size_t i = 0; i < entry.data.size(); i++) { 76 | out << entry.data[i]; 77 | if (i == entry.data.size() - 1) { 78 | out << std::endl; 79 | } else { 80 | out << ' '; 81 | } 82 | } 83 | } 84 | } 85 | } 86 | 87 | } // namespace internal 88 | 89 | void save_node_data(std::ostream& out, const MshSpec& spec) 90 | { 91 | const std::string& version = spec.mesh_format.version; 92 | bool is_binary = spec.mesh_format.file_type > 0; 93 | 94 | for (const Data& data : spec.node_data) { 95 | out << "$NodeData" << std::endl; 96 | internal::save_data(out, data, version, is_binary, false); 97 | out << "$EndNodeData" << std::endl; 98 | } 99 | } 100 | 101 | void save_element_data(std::ostream& out, const MshSpec& spec) 102 | { 103 | const std::string& version = spec.mesh_format.version; 104 | bool is_binary = spec.mesh_format.file_type > 0; 105 | 106 | for (const Data& data : spec.element_data) { 107 | out << "$ElementData" << std::endl; 108 | internal::save_data(out, data, version, is_binary, false); 109 | out << "$EndElementData" << std::endl; 110 | } 111 | } 112 | 113 | void save_element_node_data(std::ostream& out, const MshSpec& spec) 114 | { 115 | const std::string& version = spec.mesh_format.version; 116 | bool is_binary = spec.mesh_format.file_type > 0; 117 | 118 | for (const Data& data : spec.element_node_data) { 119 | out << "$ElementNodeData" << std::endl; 120 | internal::save_data(out, data, version, is_binary, true); 121 | out << "$EndElementNodeData" << std::endl; 122 | } 123 | } 124 | 125 | } // namespace mshio 126 | -------------------------------------------------------------------------------- /src/save_msh_nodes.cpp: -------------------------------------------------------------------------------- 1 | #include "save_msh_nodes.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | namespace mshio { 11 | namespace v41 { 12 | 13 | void save_nodes_ascii(std::ostream& out, const MshSpec& spec) 14 | { 15 | const Nodes& nodes = spec.nodes; 16 | out << nodes.num_entity_blocks << " " << nodes.num_nodes << " " 17 | << nodes.min_node_tag << " " << nodes.max_node_tag << std::endl; 18 | 19 | for (size_t i = 0; i < nodes.num_entity_blocks; i++) { 20 | const NodeBlock& block = nodes.entity_blocks[i]; 21 | out << block.entity_dim << " " << block.entity_tag << " " << block.parametric << " " 22 | << block.num_nodes_in_block << std::endl; 23 | for (size_t j = 0; j < block.num_nodes_in_block; j++) { 24 | out << block.tags[j] << std::endl; 25 | } 26 | const size_t entries_per_node = 27 | static_cast(3 + ((block.parametric == 1) ? block.entity_dim : 0)); 28 | for (size_t j = 0; j < block.num_nodes_in_block; j++) { 29 | for (size_t k = 0; k < entries_per_node; k++) { 30 | out << block.data[j * entries_per_node + k]; 31 | if (k == entries_per_node - 1) { 32 | out << std::endl; 33 | } else { 34 | out << ' '; 35 | } 36 | } 37 | } 38 | } 39 | } 40 | 41 | void save_nodes_binary(std::ostream& out, const MshSpec& spec) 42 | { 43 | const Nodes& nodes = spec.nodes; 44 | out.write(reinterpret_cast(&nodes.num_entity_blocks), sizeof(size_t)); 45 | out.write(reinterpret_cast(&nodes.num_nodes), sizeof(size_t)); 46 | out.write(reinterpret_cast(&nodes.min_node_tag), sizeof(size_t)); 47 | out.write(reinterpret_cast(&nodes.max_node_tag), sizeof(size_t)); 48 | 49 | for (size_t i = 0; i < nodes.num_entity_blocks; i++) { 50 | const NodeBlock& block = nodes.entity_blocks[i]; 51 | out.write(reinterpret_cast(&block.entity_dim), sizeof(int)); 52 | out.write(reinterpret_cast(&block.entity_tag), sizeof(int)); 53 | out.write(reinterpret_cast(&block.parametric), sizeof(int)); 54 | out.write(reinterpret_cast(&block.num_nodes_in_block), sizeof(size_t)); 55 | 56 | out.write(reinterpret_cast(block.tags.data()), 57 | static_cast(sizeof(size_t) * block.num_nodes_in_block)); 58 | 59 | const size_t entries_per_node = 60 | static_cast(3 + ((block.parametric == 1) ? block.entity_dim : 0)); 61 | out.write(reinterpret_cast(block.data.data()), 62 | static_cast( 63 | sizeof(double) * block.num_nodes_in_block * entries_per_node)); 64 | } 65 | } 66 | 67 | } // namespace v41 68 | 69 | namespace v22 { 70 | 71 | void save_nodes_ascii(std::ostream& out, const MshSpec& spec) 72 | { 73 | const Nodes& nodes = spec.nodes; 74 | out << nodes.num_nodes << std::endl; 75 | 76 | for (size_t i=0; i(3 + ((block.parametric == 1) ? block.entity_dim : 0)); 80 | for (size_t j = 0; j < block.num_nodes_in_block; j++) { 81 | out << block.tags[j] << " " << block.data[j * entries_per_node] << " " 82 | << block.data[j * entries_per_node + 1] << " " 83 | << block.data[j * entries_per_node + 2] << std::endl; 84 | } 85 | } 86 | } 87 | 88 | void save_nodes_binary(std::ostream& out, const MshSpec& spec) 89 | { 90 | const Nodes& nodes = spec.nodes; 91 | out << nodes.num_nodes << std::endl; 92 | 93 | for (size_t i=0; i(3 + ((block.parametric == 1) ? block.entity_dim : 0)); 97 | for (size_t j = 0; j < block.num_nodes_in_block; j++) { 98 | const int32_t node_id = static_cast(block.tags[j]); 99 | out.write(reinterpret_cast(&node_id), 4); 100 | out.write(reinterpret_cast(block.data.data() + j * entries_per_node), 101 | static_cast(sizeof(double) * 3)); 102 | } 103 | } 104 | } 105 | 106 | } // namespace v22 107 | 108 | void save_nodes(std::ostream& out, const MshSpec& spec) 109 | { 110 | const std::string& version = spec.mesh_format.version; 111 | const bool is_ascii = spec.mesh_format.file_type == 0; 112 | 113 | out << "$Nodes" << std::endl; 114 | if (version == "4.1") { 115 | if (is_ascii) 116 | v41::save_nodes_ascii(out, spec); 117 | else 118 | v41::save_nodes_binary(out, spec); 119 | } else if (version == "2.2") { 120 | if (is_ascii) 121 | v22::save_nodes_ascii(out, spec); 122 | else 123 | v22::save_nodes_binary(out, spec); 124 | } else { 125 | std::stringstream msg; 126 | msg << "Unsupported MSH version: " << version; 127 | throw UnsupportedFeature(msg.str()); 128 | } 129 | out << "$EndNodes" << std::endl; 130 | } 131 | 132 | } // namespace mshio 133 | -------------------------------------------------------------------------------- /src/load_msh_data.cpp: -------------------------------------------------------------------------------- 1 | #include "load_msh_data.h" 2 | 3 | #include 4 | #include 5 | #include "io_utils.h" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace mshio { 13 | 14 | namespace internal { 15 | 16 | void load_data_header(std::istream& in, DataHeader& header) 17 | { 18 | size_t num_string_tags, num_real_tags, num_int_tags; 19 | 20 | in >> num_string_tags; 21 | header.string_tags.resize(num_string_tags); 22 | for (size_t i = 0; i < num_string_tags; i++) { 23 | in >> std::quoted(header.string_tags[i]); 24 | } 25 | 26 | in >> num_real_tags; 27 | header.real_tags.resize(num_real_tags); 28 | for (size_t i = 0; i < num_real_tags; i++) { 29 | in >> header.real_tags[i]; 30 | } 31 | 32 | in >> num_int_tags; 33 | header.int_tags.resize(num_int_tags); 34 | for (size_t i = 0; i < num_int_tags; i++) { 35 | in >> header.int_tags[i]; 36 | } 37 | assert(in.good()); 38 | } 39 | 40 | namespace v41 { 41 | void load_data_entry( 42 | std::istream& in, DataEntry& entry, size_t fields_per_entry, bool is_element_node_data) 43 | { 44 | // TODO: 45 | // Based on trial and error, it seems Gmsh 4.7.1 still expect 32 46 | // bits tag, which is inconsistent with their spec. Maybe 47 | // report a bug? 48 | int32_t tag_32; 49 | in.read(reinterpret_cast(&tag_32), 4); 50 | entry.tag = static_cast(tag_32); 51 | // in.read(reinterpret_cast(&entry.tag), sizeof(size_t)); 52 | if (is_element_node_data) { 53 | in.read(reinterpret_cast(&entry.num_nodes_per_element), sizeof(int)); 54 | entry.data.resize(fields_per_entry * static_cast(entry.num_nodes_per_element)); 55 | } else { 56 | entry.data.resize(fields_per_entry); 57 | } 58 | in.read(reinterpret_cast(entry.data.data()), 59 | static_cast(sizeof(double) * entry.data.size())); 60 | } 61 | } // namespace v41 62 | 63 | namespace v22 { 64 | void load_data_entry( 65 | std::istream& in, DataEntry& entry, size_t fields_per_entry, bool is_element_node_data) 66 | { 67 | int32_t tag_32; 68 | in.read(reinterpret_cast(&tag_32), 4); 69 | entry.tag = static_cast(tag_32); 70 | if (is_element_node_data) { 71 | int32_t num_nodes_per_element; 72 | in.read(reinterpret_cast(&num_nodes_per_element), 4); 73 | entry.num_nodes_per_element = static_cast(num_nodes_per_element); 74 | entry.data.resize(fields_per_entry * static_cast(entry.num_nodes_per_element)); 75 | } else { 76 | entry.data.resize(fields_per_entry); 77 | } 78 | in.read(reinterpret_cast(entry.data.data()), 79 | static_cast(sizeof(double) * entry.data.size())); 80 | } 81 | } // namespace v22 82 | 83 | void load_data(std::istream& in, 84 | Data& data, 85 | const std::string& version, 86 | bool is_binary, 87 | bool is_element_node_data = false) 88 | { 89 | load_data_header(in, data.header); 90 | 91 | if (data.header.int_tags.size() < 3) { 92 | throw InvalidFormat("Data requires at least 3 int tags."); 93 | } 94 | 95 | size_t fields_per_entry = static_cast(data.header.int_tags[1]); 96 | size_t num_entries = static_cast(data.header.int_tags[2]); 97 | data.entries.resize(num_entries); 98 | 99 | if (is_binary) { 100 | eat_white_space(in, 1); 101 | if (version == "4.1") { 102 | for (size_t i = 0; i < num_entries; i++) { 103 | v41::load_data_entry(in, data.entries[i], fields_per_entry, is_element_node_data); 104 | } 105 | } else if (version == "2.2") { 106 | for (size_t i = 0; i < num_entries; i++) { 107 | v22::load_data_entry(in, data.entries[i], fields_per_entry, is_element_node_data); 108 | } 109 | } else { 110 | throw InvalidFormat("Unsupported version " + version); 111 | } 112 | } else { 113 | for (size_t i = 0; i < num_entries; i++) { 114 | DataEntry& entry = data.entries[i]; 115 | in >> entry.tag; 116 | if (is_element_node_data) { 117 | in >> entry.num_nodes_per_element; 118 | entry.data.resize(fields_per_entry * entry.num_nodes_per_element); 119 | for (size_t j = 0; j < entry.num_nodes_per_element; j++) { 120 | for (size_t k = 0; k < fields_per_entry; k++) { 121 | in >> entry.data[j * fields_per_entry + k]; 122 | } 123 | } 124 | } else { 125 | entry.data.resize(fields_per_entry); 126 | for (size_t j = 0; j < fields_per_entry; j++) { 127 | in >> entry.data[j]; 128 | } 129 | } 130 | } 131 | } 132 | assert(in.good()); 133 | } 134 | 135 | } // namespace internal 136 | 137 | 138 | void load_node_data(std::istream& in, MshSpec& spec) 139 | { 140 | const std::string& version = spec.mesh_format.version; 141 | bool is_binary = spec.mesh_format.file_type > 0; 142 | spec.node_data.emplace_back(); 143 | internal::load_data(in, spec.node_data.back(), version, is_binary, false); 144 | } 145 | 146 | void load_element_data(std::istream& in, MshSpec& spec) 147 | { 148 | const std::string& version = spec.mesh_format.version; 149 | bool is_binary = spec.mesh_format.file_type > 0; 150 | spec.element_data.emplace_back(); 151 | internal::load_data(in, spec.element_data.back(), version, is_binary, false); 152 | } 153 | 154 | void load_element_node_data(std::istream& in, MshSpec& spec) 155 | { 156 | const std::string& version = spec.mesh_format.version; 157 | bool is_binary = spec.mesh_format.file_type > 0; 158 | spec.element_node_data.emplace_back(); 159 | internal::load_data(in, spec.element_node_data.back(), version, is_binary, true); 160 | } 161 | 162 | } // namespace mshio 163 | -------------------------------------------------------------------------------- /src/save_msh_elements.cpp: -------------------------------------------------------------------------------- 1 | #include "save_msh_elements.h" 2 | #include "element_utils.h" 3 | #include "io_utils.h" 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | 15 | namespace mshio { 16 | 17 | namespace v41 { 18 | 19 | void save_elements_ascii(std::ostream& out, const MshSpec& spec) 20 | { 21 | const Elements& elements = spec.elements; 22 | out << elements.num_entity_blocks << " " << elements.num_elements << " " 23 | << elements.min_element_tag << " " << elements.max_element_tag << std::endl; 24 | 25 | for (size_t i = 0; i < elements.num_entity_blocks; i++) { 26 | const ElementBlock& block = elements.entity_blocks[i]; 27 | 28 | out << block.entity_dim << " " << block.entity_tag << " " 29 | << block.element_type << " " << block.num_elements_in_block << std::endl; 30 | 31 | const size_t n = nodes_per_element(block.element_type); 32 | for (size_t j = 0; j < block.num_elements_in_block; j++) { 33 | for (size_t k = 0; k <= n; k++) { 34 | out << block.data[j * (n + 1) + k]; 35 | if (k == n) { 36 | out << std::endl; 37 | } else { 38 | out << ' '; 39 | } 40 | } 41 | } 42 | } 43 | } 44 | 45 | void save_elements_binary(std::ostream& out, const MshSpec& spec) 46 | { 47 | const Elements& elements = spec.elements; 48 | out.write(reinterpret_cast(&elements.num_entity_blocks), sizeof(size_t)); 49 | out.write(reinterpret_cast(&elements.num_elements), sizeof(size_t)); 50 | out.write(reinterpret_cast(&elements.min_element_tag), sizeof(size_t)); 51 | out.write(reinterpret_cast(&elements.max_element_tag), sizeof(size_t)); 52 | 53 | for (size_t i = 0; i < elements.num_entity_blocks; i++) { 54 | const ElementBlock& block = elements.entity_blocks[i]; 55 | 56 | out.write(reinterpret_cast(&block.entity_dim), sizeof(int)); 57 | out.write(reinterpret_cast(&block.entity_tag), sizeof(int)); 58 | out.write(reinterpret_cast(&block.element_type), sizeof(int)); 59 | out.write(reinterpret_cast(&block.num_elements_in_block), sizeof(size_t)); 60 | 61 | out.write(reinterpret_cast(block.data.data()), 62 | static_cast(sizeof(size_t) * block.data.size())); 63 | } 64 | } 65 | 66 | } // namespace v41 67 | 68 | namespace v22 { 69 | 70 | void save_elements_ascii(std::ostream& out, const MshSpec& spec) 71 | { 72 | const Elements& elements = spec.elements; 73 | out << elements.num_elements << std::endl; 74 | 75 | for (size_t i = 0; i < elements.num_entity_blocks; i++) { 76 | const ElementBlock& block = elements.entity_blocks[i]; 77 | int element_type = block.element_type; 78 | const size_t n = nodes_per_element(element_type); 79 | constexpr int num_tags = 1; 80 | for (size_t j = 0; j < block.num_elements_in_block; j++) { 81 | size_t element_number = block.data[j * (n + 1)]; 82 | out << element_number << " " << element_type << " " << num_tags << " " 83 | << block.entity_tag << " "; 84 | for (size_t k = 0; k < n; k++) { 85 | out << block.data[j * (n + 1) + k + 1]; 86 | if (k == n - 1) { 87 | out << std::endl; 88 | } else { 89 | out << ' '; 90 | } 91 | } 92 | } 93 | } 94 | } 95 | 96 | void save_elements_binary(std::ostream& out, const MshSpec& spec) 97 | { 98 | const Elements& elements = spec.elements; 99 | out << elements.num_elements << std::endl; 100 | for (size_t i = 0; i < elements.num_entity_blocks; i++) { 101 | const ElementBlock& block = elements.entity_blocks[i]; 102 | const int32_t element_type = block.element_type; 103 | constexpr int32_t num_tags = 1; 104 | const int32_t num_element_in_block = static_cast(block.num_elements_in_block); 105 | out.write(reinterpret_cast(&element_type), 4); 106 | out.write(reinterpret_cast(&num_element_in_block), 4); 107 | out.write(reinterpret_cast(&num_tags), 4); 108 | 109 | const size_t n = nodes_per_element(element_type); 110 | const int32_t tag = static_cast(block.entity_tag); 111 | 112 | for (size_t j = 0; j < block.num_elements_in_block; j++) { 113 | const int32_t element_number = static_cast(block.data[j * (n + 1)]); 114 | out.write(reinterpret_cast(&element_number), 4); 115 | out.write(reinterpret_cast(&tag), 4); 116 | for (size_t k = 0; k < n; k++) { 117 | const int32_t node_id = static_cast(block.data[j * (n + 1) + k + 1]); 118 | out.write(reinterpret_cast(&node_id), 4); 119 | } 120 | } 121 | } 122 | } 123 | 124 | } // namespace v22 125 | 126 | void save_elements(std::ostream& out, const MshSpec& spec) 127 | { 128 | const std::string& version = spec.mesh_format.version; 129 | const bool is_ascii = spec.mesh_format.file_type == 0; 130 | out << "$Elements" << std::endl; 131 | if (version == "4.1") { 132 | if (is_ascii) 133 | v41::save_elements_ascii(out, spec); 134 | else 135 | v41::save_elements_binary(out, spec); 136 | } else if (version == "2.2") { 137 | if (is_ascii) 138 | v22::save_elements_ascii(out, spec); 139 | else 140 | v22::save_elements_binary(out, spec); 141 | } else { 142 | std::stringstream msg; 143 | msg << "Unsupported MSH version: " << version; 144 | throw UnsupportedFeature(msg.str()); 145 | } 146 | out << "$EndElements" << std::endl; 147 | } 148 | 149 | 150 | } // namespace mshio 151 | -------------------------------------------------------------------------------- /src/load_msh_nodes.cpp: -------------------------------------------------------------------------------- 1 | #include "load_msh_nodes.h" 2 | #include "io_utils.h" 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace mshio { 15 | namespace v41 { 16 | 17 | void load_nodes_ascii(std::istream& in, MshSpec& spec) 18 | { 19 | Nodes& nodes = spec.nodes; 20 | in >> nodes.num_entity_blocks; 21 | in >> nodes.num_nodes; 22 | in >> nodes.min_node_tag; 23 | in >> nodes.max_node_tag; 24 | assert(in.good()); 25 | nodes.entity_blocks.resize(nodes.num_entity_blocks); 26 | for (size_t i = 0; i < nodes.num_entity_blocks; i++) { 27 | NodeBlock& block = nodes.entity_blocks[i]; 28 | in >> block.entity_dim; 29 | in >> block.entity_tag; 30 | in >> block.parametric; 31 | in >> block.num_nodes_in_block; 32 | assert(in.good()); 33 | 34 | block.tags.resize(block.num_nodes_in_block); 35 | for (size_t j = 0; j < block.num_nodes_in_block; j++) { 36 | in >> block.tags[j]; 37 | } 38 | assert(in.good()); 39 | 40 | assert(block.parametric >= 0 && block.parametric <= 3); 41 | const size_t entries_per_node = 42 | static_cast(3 + ((block.parametric == 1) ? block.entity_dim : 0)); 43 | block.data.resize(block.num_nodes_in_block * entries_per_node); 44 | for (size_t j = 0; j < block.num_nodes_in_block; j++) { 45 | for (size_t k = 0; k < entries_per_node; k++) { 46 | in >> block.data[j * entries_per_node + k]; 47 | } 48 | } 49 | assert(in.good()); 50 | } 51 | } 52 | 53 | void load_nodes_binary(std::istream& in, MshSpec& spec) 54 | { 55 | Nodes& nodes = spec.nodes; 56 | eat_white_space(in, 1); 57 | in.read(reinterpret_cast(&nodes.num_entity_blocks), sizeof(size_t)); 58 | in.read(reinterpret_cast(&nodes.num_nodes), sizeof(size_t)); 59 | in.read(reinterpret_cast(&nodes.min_node_tag), sizeof(size_t)); 60 | in.read(reinterpret_cast(&nodes.max_node_tag), sizeof(size_t)); 61 | assert(in.good()); 62 | nodes.entity_blocks.resize(nodes.num_entity_blocks); 63 | for (size_t i = 0; i < nodes.num_entity_blocks; i++) { 64 | NodeBlock& block = nodes.entity_blocks[i]; 65 | in.read(reinterpret_cast(&block.entity_dim), sizeof(int)); 66 | in.read(reinterpret_cast(&block.entity_tag), sizeof(int)); 67 | in.read(reinterpret_cast(&block.parametric), sizeof(int)); 68 | in.read(reinterpret_cast(&block.num_nodes_in_block), sizeof(size_t)); 69 | assert(in.good()); 70 | 71 | block.tags.resize(block.num_nodes_in_block); 72 | in.read(reinterpret_cast(block.tags.data()), 73 | static_cast(sizeof(size_t) * block.num_nodes_in_block)); 74 | assert(in.good()); 75 | 76 | const size_t entries_per_node = 77 | static_cast(3 + ((block.parametric == 1) ? block.entity_dim : 0)); 78 | block.data.resize(block.num_nodes_in_block * entries_per_node); 79 | in.read(reinterpret_cast(block.data.data()), 80 | static_cast( 81 | sizeof(double) * block.num_nodes_in_block * entries_per_node)); 82 | assert(in.good()); 83 | } 84 | } 85 | 86 | } // namespace v41 87 | 88 | namespace v22 { 89 | 90 | void load_nodes_ascii(std::istream& in, MshSpec& spec) 91 | { 92 | Nodes& nodes = spec.nodes; 93 | nodes.num_entity_blocks++; 94 | nodes.entity_blocks.emplace_back(); 95 | 96 | NodeBlock& block = nodes.entity_blocks.back(); 97 | block.entity_dim = 0; // Will be determined once elements are loaded. 98 | block.entity_tag = 0; // Same as above. 99 | block.parametric = 0; 100 | in >> block.num_nodes_in_block; 101 | assert(in.good()); 102 | nodes.num_nodes += block.num_nodes_in_block; 103 | 104 | block.tags.resize(block.num_nodes_in_block); 105 | block.data.resize(block.num_nodes_in_block * 3); 106 | for (size_t i = 0; i < block.num_nodes_in_block; i++) { 107 | in >> block.tags[i]; 108 | in >> block.data[i * 3]; 109 | in >> block.data[i * 3 + 1]; 110 | in >> block.data[i * 3 + 2]; 111 | assert(in.good()); 112 | } 113 | 114 | if (block.num_nodes_in_block > 0) { 115 | nodes.min_node_tag = 116 | std::min(nodes.min_node_tag, *std::min_element(block.tags.begin(), block.tags.end())); 117 | nodes.max_node_tag = 118 | std::max(nodes.max_node_tag, *std::max_element(block.tags.begin(), block.tags.end())); 119 | } 120 | } 121 | 122 | void load_nodes_binary(std::istream& in, MshSpec& spec) 123 | { 124 | Nodes& nodes = spec.nodes; 125 | nodes.num_entity_blocks++; 126 | nodes.entity_blocks.emplace_back(); 127 | 128 | NodeBlock& block = nodes.entity_blocks.back(); 129 | block.entity_dim = 0; // Will be determined once elements are loaded. 130 | block.entity_tag = 0; // Same as above. 131 | block.parametric = 0; 132 | in >> block.num_nodes_in_block; 133 | assert(in.good()); 134 | nodes.num_nodes += block.num_nodes_in_block; 135 | 136 | block.tags.resize(block.num_nodes_in_block); 137 | block.data.resize(block.num_nodes_in_block * 3); 138 | eat_white_space(in, 1); 139 | for (size_t i = 0; i < block.num_nodes_in_block; i++) { 140 | assert(in.good()); 141 | int tag; 142 | in.read(reinterpret_cast(&tag), sizeof(int)); 143 | block.tags[i] = static_cast(tag); 144 | in.read(reinterpret_cast(block.data.data() + i * 3), sizeof(double) * 3); 145 | } 146 | 147 | if (block.num_nodes_in_block > 0) { 148 | nodes.min_node_tag = 149 | std::min(nodes.min_node_tag, *std::min_element(block.tags.begin(), block.tags.end())); 150 | nodes.max_node_tag = 151 | std::max(nodes.max_node_tag, *std::max_element(block.tags.begin(), block.tags.end())); 152 | } 153 | } 154 | 155 | } // namespace v22 156 | 157 | void load_nodes(std::istream& in, MshSpec& spec) 158 | { 159 | if (spec.nodes.entity_blocks.size() == 0) { 160 | spec.nodes.min_node_tag = std::numeric_limits::max(); 161 | spec.nodes.max_node_tag = 0; 162 | spec.nodes.num_nodes = 0; 163 | } 164 | const std::string& version = spec.mesh_format.version; 165 | const bool is_ascii = spec.mesh_format.file_type == 0; 166 | if (version == "4.1") { 167 | if (is_ascii) 168 | v41::load_nodes_ascii(in, spec); 169 | else 170 | v41::load_nodes_binary(in, spec); 171 | } else if (version == "2.2") { 172 | if (is_ascii) 173 | v22::load_nodes_ascii(in, spec); 174 | else 175 | v22::load_nodes_binary(in, spec); 176 | } else { 177 | std::stringstream msg; 178 | msg << "Unsupported MSH version: " << version; 179 | throw UnsupportedFeature(msg.str()); 180 | } 181 | } 182 | 183 | } // namespace mshio 184 | -------------------------------------------------------------------------------- /src/save_msh_entities.cpp: -------------------------------------------------------------------------------- 1 | #include "save_msh_entities.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace mshio { 13 | 14 | namespace v41 { 15 | 16 | void save_entities_ascii(std::ostream& out, const MshSpec& spec) 17 | { 18 | const Entities& entities = spec.entities; 19 | out << entities.points.size() << " " << entities.curves.size() << " " 20 | << entities.surfaces.size() << " " << entities.volumes.size() << std::endl; 21 | 22 | for (size_t i = 0; i < entities.points.size(); i++) { 23 | const PointEntity& point = entities.points[i]; 24 | out << point.tag << " " << point.x << " " << point.y << " " << point.z << " " 25 | << point.physical_group_tags.size(); 26 | for (size_t j = 0; j < point.physical_group_tags.size(); j++) { 27 | out << " " << point.physical_group_tags[j]; 28 | } 29 | out << std::endl; 30 | } 31 | 32 | for (size_t i = 0; i < entities.curves.size(); i++) { 33 | const CurveEntity& curve = entities.curves[i]; 34 | out << curve.tag << " " << curve.min_x << " " << curve.min_y << " " << curve.min_z << " " 35 | << curve.max_x << " " << curve.max_y << " " << curve.max_z << " " 36 | << curve.physical_group_tags.size(); 37 | for (size_t j = 0; j < curve.physical_group_tags.size(); j++) { 38 | out << " " << curve.physical_group_tags[j]; 39 | } 40 | out << " " << curve.boundary_point_tags.size(); 41 | for (size_t j = 0; j < curve.boundary_point_tags.size(); j++) { 42 | out << " " << curve.boundary_point_tags[j]; 43 | } 44 | out << std::endl; 45 | } 46 | 47 | for (size_t i = 0; i < entities.surfaces.size(); i++) { 48 | const SurfaceEntity& surface = entities.surfaces[i]; 49 | out << surface.tag << " " << surface.min_x << " " << surface.min_y << " " << surface.min_z 50 | << " " << surface.max_x << " " << surface.max_y << " " << surface.max_z << " " 51 | << surface.physical_group_tags.size(); 52 | for (size_t j = 0; j < surface.physical_group_tags.size(); j++) { 53 | out << " " << surface.physical_group_tags[j]; 54 | } 55 | out << " " << surface.boundary_curve_tags.size(); 56 | for (size_t j = 0; j < surface.boundary_curve_tags.size(); j++) { 57 | out << " " << surface.boundary_curve_tags[j]; 58 | } 59 | out << std::endl; 60 | } 61 | 62 | for (size_t i = 0; i < entities.volumes.size(); i++) { 63 | const VolumeEntity& volume = entities.volumes[i]; 64 | out << volume.tag << " " << volume.min_x << " " << volume.min_y << " " << volume.min_z 65 | << " " << volume.max_x << " " << volume.max_y << " " << volume.max_z << " " 66 | << volume.physical_group_tags.size(); 67 | for (size_t j = 0; j < volume.physical_group_tags.size(); j++) { 68 | out << " " << volume.physical_group_tags[j]; 69 | } 70 | out << " " << volume.boundary_surface_tags.size(); 71 | for (size_t j = 0; j < volume.boundary_surface_tags.size(); j++) { 72 | out << " " << volume.boundary_surface_tags[j]; 73 | } 74 | out << std::endl; 75 | } 76 | } 77 | 78 | void save_entities_binary(std::ostream& out, const MshSpec& spec) 79 | { 80 | const Entities& entities = spec.entities; 81 | size_t num_points = entities.points.size(); 82 | size_t num_curves = entities.curves.size(); 83 | size_t num_surfaces = entities.surfaces.size(); 84 | size_t num_volumes = entities.volumes.size(); 85 | out.write(reinterpret_cast(&num_points), sizeof(size_t)); 86 | out.write(reinterpret_cast(&num_curves), sizeof(size_t)); 87 | out.write(reinterpret_cast(&num_surfaces), sizeof(size_t)); 88 | out.write(reinterpret_cast(&num_volumes), sizeof(size_t)); 89 | 90 | for (size_t i = 0; i < num_points; i++) { 91 | const PointEntity& point = entities.points[i]; 92 | out.write(reinterpret_cast(&point.tag), sizeof(int)); 93 | out.write(reinterpret_cast(&point.x), sizeof(double)); 94 | out.write(reinterpret_cast(&point.y), sizeof(double)); 95 | out.write(reinterpret_cast(&point.z), sizeof(double)); 96 | size_t num_physical_groups = point.physical_group_tags.size(); 97 | out.write(reinterpret_cast(&num_physical_groups), sizeof(size_t)); 98 | out.write(reinterpret_cast(point.physical_group_tags.data()), 99 | static_cast(sizeof(int) * num_physical_groups)); 100 | } 101 | 102 | for (size_t i = 0; i < num_curves; i++) { 103 | const CurveEntity& curve = entities.curves[i]; 104 | out.write(reinterpret_cast(&curve.tag), sizeof(int)); 105 | out.write(reinterpret_cast(&curve.min_x), sizeof(double)); 106 | out.write(reinterpret_cast(&curve.min_y), sizeof(double)); 107 | out.write(reinterpret_cast(&curve.min_z), sizeof(double)); 108 | out.write(reinterpret_cast(&curve.max_x), sizeof(double)); 109 | out.write(reinterpret_cast(&curve.max_y), sizeof(double)); 110 | out.write(reinterpret_cast(&curve.max_z), sizeof(double)); 111 | size_t num_physical_groups = curve.physical_group_tags.size(); 112 | out.write(reinterpret_cast(&num_physical_groups), sizeof(size_t)); 113 | out.write(reinterpret_cast(curve.physical_group_tags.data()), 114 | static_cast(sizeof(int) * num_physical_groups)); 115 | size_t num_boundary_points = curve.boundary_point_tags.size(); 116 | out.write(reinterpret_cast(&num_boundary_points), sizeof(size_t)); 117 | out.write(reinterpret_cast(curve.boundary_point_tags.data()), 118 | static_cast(sizeof(int) * num_boundary_points)); 119 | } 120 | 121 | for (size_t i = 0; i < num_surfaces; i++) { 122 | const SurfaceEntity& surface = entities.surfaces[i]; 123 | out.write(reinterpret_cast(&surface.tag), sizeof(int)); 124 | out.write(reinterpret_cast(&surface.min_x), sizeof(double)); 125 | out.write(reinterpret_cast(&surface.min_y), sizeof(double)); 126 | out.write(reinterpret_cast(&surface.min_z), sizeof(double)); 127 | out.write(reinterpret_cast(&surface.max_x), sizeof(double)); 128 | out.write(reinterpret_cast(&surface.max_y), sizeof(double)); 129 | out.write(reinterpret_cast(&surface.max_z), sizeof(double)); 130 | size_t num_physical_groups = surface.physical_group_tags.size(); 131 | out.write(reinterpret_cast(&num_physical_groups), sizeof(size_t)); 132 | out.write(reinterpret_cast(surface.physical_group_tags.data()), 133 | static_cast(sizeof(int) * num_physical_groups)); 134 | size_t num_boundary_curves = surface.boundary_curve_tags.size(); 135 | out.write(reinterpret_cast(&num_boundary_curves), sizeof(size_t)); 136 | out.write(reinterpret_cast(surface.boundary_curve_tags.data()), 137 | static_cast(sizeof(int) * num_boundary_curves)); 138 | } 139 | 140 | for (size_t i = 0; i < num_volumes; i++) { 141 | const VolumeEntity& volume = entities.volumes[i]; 142 | out.write(reinterpret_cast(&volume.tag), sizeof(int)); 143 | out.write(reinterpret_cast(&volume.min_x), sizeof(double)); 144 | out.write(reinterpret_cast(&volume.min_y), sizeof(double)); 145 | out.write(reinterpret_cast(&volume.min_z), sizeof(double)); 146 | out.write(reinterpret_cast(&volume.max_x), sizeof(double)); 147 | out.write(reinterpret_cast(&volume.max_y), sizeof(double)); 148 | out.write(reinterpret_cast(&volume.max_z), sizeof(double)); 149 | size_t num_physical_groups = volume.physical_group_tags.size(); 150 | out.write(reinterpret_cast(&num_physical_groups), sizeof(size_t)); 151 | out.write(reinterpret_cast(volume.physical_group_tags.data()), 152 | static_cast(sizeof(int) * num_physical_groups)); 153 | size_t num_boundary_surfaces = volume.boundary_surface_tags.size(); 154 | out.write(reinterpret_cast(&num_boundary_surfaces), sizeof(size_t)); 155 | out.write(reinterpret_cast(volume.boundary_surface_tags.data()), 156 | static_cast(sizeof(int) * num_boundary_surfaces)); 157 | } 158 | } 159 | 160 | } // namespace v41 161 | 162 | void save_entities(std::ostream& out, const MshSpec& spec) 163 | { 164 | const std::string& version = spec.mesh_format.version; 165 | const bool is_ascii = spec.mesh_format.file_type == 0; 166 | if (version == "2.2") { 167 | // version 2.2 has no $Entities section 168 | return; 169 | } 170 | out << "$Entities" << std::endl; 171 | if (version == "4.1") { 172 | if (is_ascii) 173 | v41::save_entities_ascii(out, spec); 174 | else 175 | v41::save_entities_binary(out, spec); 176 | } else { 177 | std::stringstream msg; 178 | msg << "Unsupported MSH version: " << version; 179 | throw UnsupportedFeature(msg.str()); 180 | } 181 | out << "$EndEntities" << std::endl; 182 | } 183 | 184 | } // namespace mshio 185 | -------------------------------------------------------------------------------- /examples/msh_inspect.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main(int argc, char** argv) 6 | { 7 | if (argc != 2) { 8 | std::cerr << "Usage: " << argv[0] << " msh_file" << std::endl; 9 | return 1; 10 | } 11 | 12 | mshio::MshSpec spec = mshio::load_msh(argv[1]); 13 | 14 | std::cout << "sizeof(int): " << sizeof(int) << std::endl; 15 | std::cout << "sizeof(size_t) " << sizeof(size_t) << std::endl; 16 | std::cout << "sizeof(double) " << sizeof(double) << std::endl; 17 | 18 | std::cout << "Num point entities: " << spec.entities.points.size() << std::endl; 19 | for (const auto& point: spec.entities.points) { 20 | std::cout << " entity tag: " << point.tag << std::endl; 21 | std::cout << " entity coordinates: " << point.x << 22 | " " << point.y << " " << point.z << std::endl; 23 | size_t num_groups = point.physical_group_tags.size(); 24 | std::cout << " Num physical groups: " << num_groups << std::endl; 25 | if (num_groups > 0) { 26 | std::cout << " "; 27 | for (size_t i = 0; i < num_groups; i++) { 28 | std::cout << point.physical_group_tags[i] << " "; 29 | } 30 | std::cout << std::endl; 31 | } 32 | } 33 | 34 | std::cout << "Num curve entities: " << spec.entities.curves.size() << std::endl; 35 | for (const auto& curve: spec.entities.curves) { 36 | std::cout << " entity tag: " << curve.tag << std::endl; 37 | std::cout << " bounding box min: " << curve.min_x << 38 | " " << curve.min_y << " " << curve.min_z << std::endl; 39 | std::cout << " bounding box max: " << curve.max_x << 40 | " " << curve.max_y << " " << curve.max_z << std::endl; 41 | size_t num_groups = curve.physical_group_tags.size(); 42 | std::cout << " Num physical groups: " << num_groups << std::endl; 43 | if (num_groups > 0) { 44 | std::cout << " "; 45 | for (size_t i = 0; i < num_groups; i++) { 46 | std::cout << curve.physical_group_tags[i] << " "; 47 | } 48 | std::cout << std::endl; 49 | } 50 | size_t num_bounds = curve.boundary_point_tags.size(); 51 | std::cout << " Num boundary points: " << num_bounds << std::endl; 52 | if (num_bounds > 0) { 53 | std::cout << " "; 54 | for (size_t i = 0; i < num_bounds; i++) { 55 | std::cout << curve.boundary_point_tags[i] << " "; 56 | } 57 | std::cout << std::endl; 58 | } 59 | } 60 | 61 | std::cout << "Num surface entities: " << spec.entities.surfaces.size() << std::endl; 62 | for (const auto& surface: spec.entities.surfaces) { 63 | std::cout << " entity tag: " << surface.tag << std::endl; 64 | std::cout << " bounding box min: " << surface.min_x << 65 | " " << surface.min_y << " " << surface.min_z << std::endl; 66 | std::cout << " bounding box max: " << surface.max_x << 67 | " " << surface.max_y << " " << surface.max_z << std::endl; 68 | size_t num_groups = surface.physical_group_tags.size(); 69 | std::cout << " Num physical groups: " << num_groups << std::endl; 70 | if (num_groups > 0) { 71 | std::cout << " "; 72 | for (size_t i = 0; i < num_groups; i++) { 73 | std::cout << surface.physical_group_tags[i] << " "; 74 | } 75 | std::cout << std::endl; 76 | } 77 | size_t num_bounds = surface.boundary_curve_tags.size(); 78 | std::cout << " Num boundary curves: " << num_bounds << std::endl; 79 | if (num_bounds > 0) { 80 | std::cout << " "; 81 | for (size_t i = 0; i < num_bounds; i++) { 82 | std::cout << surface.boundary_curve_tags[i] << " "; 83 | } 84 | std::cout << std::endl; 85 | } 86 | } 87 | 88 | std::cout << "Num volume entities: " << spec.entities.volumes.size() << std::endl; 89 | for (const auto& volume: spec.entities.volumes) { 90 | std::cout << " entity tag: " << volume.tag << std::endl; 91 | std::cout << " bounding box min: " << volume.min_x << 92 | " " << volume.min_y << " " << volume.min_z << std::endl; 93 | std::cout << " bounding box max: " << volume.max_x << 94 | " " << volume.max_y << " " << volume.max_z << std::endl; 95 | size_t num_groups = volume.physical_group_tags.size(); 96 | std::cout << " Num physical groups: " << num_groups << std::endl; 97 | if (num_groups > 0) { 98 | std::cout << " "; 99 | for (size_t i = 0; i < num_groups; i++) { 100 | std::cout << volume.physical_group_tags[i] << " "; 101 | } 102 | std::cout << std::endl; 103 | } 104 | size_t num_bounds = volume.boundary_surface_tags.size(); 105 | std::cout << " Num boundary surfaces: " << num_bounds << std::endl; 106 | if (num_bounds > 0) { 107 | std::cout << " "; 108 | for (size_t i = 0; i < num_bounds; i++) { 109 | std::cout << volume.boundary_surface_tags[i] << " "; 110 | } 111 | std::cout << std::endl; 112 | } 113 | } 114 | 115 | std::cout << "Num physical groups: " << spec.physical_groups.size() << std::endl; 116 | for (const auto& group: spec.physical_groups) { 117 | std::cout << " group dim: " << group.dim << std::endl; 118 | std::cout << " group tag: " << group.tag << std::endl; 119 | std::cout << " group name: " << std::quoted(group.name) << std::endl; 120 | } 121 | 122 | std::cout << "Num nodes: " << spec.nodes.num_nodes << std::endl; 123 | std::cout << "Num node blocks: " << spec.nodes.num_entity_blocks << std::endl; 124 | std::cout << "min node tag: " << spec.nodes.min_node_tag << std::endl; 125 | std::cout << "max node tag: " << spec.nodes.max_node_tag << std::endl; 126 | 127 | for (size_t i = 0; i < spec.nodes.num_entity_blocks; i++) { 128 | mshio::NodeBlock& block = spec.nodes.entity_blocks[i]; 129 | std::cout << " entity dim: " << block.entity_dim << std::endl; 130 | std::cout << " entity tag: " << block.entity_tag << std::endl; 131 | std::cout << " parametric: " << block.parametric << std::endl; 132 | std::cout << " num nodes in block: " << block.num_nodes_in_block << std::endl; 133 | for (size_t j = 0; j < block.num_nodes_in_block; j++) { 134 | std::cout << " " << block.tags[j] << ": "; 135 | for (size_t k = 0; k < 3 + block.parametric; k++) { 136 | std::cout << block.data[j * (3 + block.parametric) + k] << " "; 137 | } 138 | std::cout << std::endl; 139 | } 140 | } 141 | 142 | std::cout << "Num elements: " << spec.elements.num_elements << std::endl; 143 | std::cout << "Num element blocks: " << spec.elements.num_entity_blocks << std::endl; 144 | std::cout << "min element tag: " << spec.elements.min_element_tag << std::endl; 145 | std::cout << "max element tag: " << spec.elements.max_element_tag << std::endl; 146 | for (size_t i = 0; i < spec.elements.num_entity_blocks; i++) { 147 | mshio::ElementBlock& block = spec.elements.entity_blocks[i]; 148 | std::cout << " entity dim: " << block.entity_dim << std::endl; 149 | std::cout << " entity tag: " << block.entity_tag << std::endl; 150 | std::cout << " entity type: " << block.element_type << std::endl; 151 | std::cout << " num elements in block: " << block.num_elements_in_block << std::endl; 152 | 153 | const size_t n = mshio::nodes_per_element(block.element_type); 154 | for (size_t j = 0; j < block.num_elements_in_block; j++) { 155 | std::cout << " " << block.data[j * (n + 1)] << ": "; 156 | for (size_t k = 1; k <= n; k++) { 157 | std::cout << block.data[j * (n + 1) + k] << " "; 158 | } 159 | std::cout << std::endl; 160 | } 161 | } 162 | 163 | auto print_data = [](const mshio::Data& data) { 164 | std::cout << "Num string tags: " << data.header.string_tags.size() << std::endl; 165 | for (const std::string& tag : data.header.string_tags) { 166 | std::cout << std::quoted(tag) << " "; 167 | } 168 | std::cout << std::endl; 169 | 170 | std::cout << "Num real tags: " << data.header.real_tags.size() << std::endl; 171 | for (const double& tag : data.header.real_tags) { 172 | std::cout << tag << " "; 173 | } 174 | std::cout << std::endl; 175 | 176 | std::cout << "Num int tags: " << data.header.int_tags.size() << std::endl; 177 | for (const double& tag : data.header.int_tags) { 178 | std::cout << tag << " "; 179 | } 180 | std::cout << std::endl; 181 | 182 | size_t fields_per_entity = static_cast(data.header.int_tags[1]); 183 | size_t num_entities = static_cast(data.header.int_tags[2]); 184 | for (size_t i = 0; i < num_entities; i++) { 185 | const mshio::DataEntry& entry = data.entries[i]; 186 | std::cout << " " << entry.tag << ": "; 187 | for (size_t j = 0; j < fields_per_entity; j++) { 188 | std::cout << entry.data[j] << " "; 189 | } 190 | std::cout << std::endl; 191 | } 192 | }; 193 | 194 | if (spec.node_data.size() > 0) { 195 | std::cout << "Node data: " << std::endl; 196 | for (const mshio::Data& data : spec.node_data) { 197 | print_data(data); 198 | } 199 | } 200 | 201 | if (spec.element_data.size() > 0) { 202 | std::cout << "Element data: " << std::endl; 203 | for (const mshio::Data& data : spec.element_data) { 204 | print_data(data); 205 | } 206 | } 207 | 208 | mshio::save_msh("tmp.msh", spec); 209 | 210 | return 0; 211 | } 212 | -------------------------------------------------------------------------------- /src/load_msh_entities.cpp: -------------------------------------------------------------------------------- 1 | #include "io_utils.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | namespace mshio { 10 | 11 | namespace v41 { 12 | 13 | void load_entities_ascii(std::istream& in, MshSpec& spec) 14 | { 15 | size_t num_points, num_curves, num_surfaces, num_volumes; 16 | in >> num_points; 17 | in >> num_curves; 18 | in >> num_surfaces; 19 | in >> num_volumes; 20 | assert(in.good()); 21 | 22 | Entities& entities = spec.entities; 23 | entities.points.resize(num_points); 24 | entities.curves.resize(num_curves); 25 | entities.surfaces.resize(num_surfaces); 26 | entities.volumes.resize(num_volumes); 27 | 28 | for (size_t i = 0; i < num_points; i++) { 29 | PointEntity& point = entities.points[i]; 30 | in >> point.tag; 31 | in >> point.x; 32 | in >> point.y; 33 | in >> point.z; 34 | size_t num_physical_groups; 35 | in >> num_physical_groups; 36 | point.physical_group_tags.resize(num_physical_groups); 37 | for (size_t j = 0; j < num_physical_groups; j++) { 38 | in >> point.physical_group_tags[j]; 39 | } 40 | } 41 | 42 | for (size_t i = 0; i < num_curves; i++) { 43 | CurveEntity& curve = entities.curves[i]; 44 | in >> curve.tag; 45 | in >> curve.min_x; 46 | in >> curve.min_y; 47 | in >> curve.min_z; 48 | in >> curve.max_x; 49 | in >> curve.max_y; 50 | in >> curve.max_z; 51 | size_t num_physical_groups; 52 | in >> num_physical_groups; 53 | curve.physical_group_tags.resize(num_physical_groups); 54 | for (size_t j = 0; j < num_physical_groups; j++) { 55 | in >> curve.physical_group_tags[j]; 56 | } 57 | size_t num_boundary_points; 58 | in >> num_boundary_points; 59 | curve.boundary_point_tags.resize(num_boundary_points); 60 | for (size_t j = 0; j < num_boundary_points; j++) { 61 | in >> curve.boundary_point_tags[j]; 62 | } 63 | } 64 | 65 | for (size_t i = 0; i < num_surfaces; i++) { 66 | SurfaceEntity& surface = entities.surfaces[i]; 67 | in >> surface.tag; 68 | in >> surface.min_x; 69 | in >> surface.min_y; 70 | in >> surface.min_z; 71 | in >> surface.max_x; 72 | in >> surface.max_y; 73 | in >> surface.max_z; 74 | size_t num_physical_groups; 75 | in >> num_physical_groups; 76 | surface.physical_group_tags.resize(num_physical_groups); 77 | for (size_t j = 0; j < num_physical_groups; j++) { 78 | in >> surface.physical_group_tags[j]; 79 | } 80 | size_t num_boundary_curves; 81 | in >> num_boundary_curves; 82 | surface.boundary_curve_tags.resize(num_boundary_curves); 83 | for (size_t j = 0; j < num_boundary_curves; j++) { 84 | in >> surface.boundary_curve_tags[j]; 85 | } 86 | } 87 | 88 | for (size_t i = 0; i < num_volumes; i++) { 89 | VolumeEntity& volume = entities.volumes[i]; 90 | in >> volume.tag; 91 | in >> volume.min_x; 92 | in >> volume.min_y; 93 | in >> volume.min_z; 94 | in >> volume.max_x; 95 | in >> volume.max_y; 96 | in >> volume.max_z; 97 | size_t num_physical_groups; 98 | in >> num_physical_groups; 99 | volume.physical_group_tags.resize(num_physical_groups); 100 | for (size_t j = 0; j < num_physical_groups; j++) { 101 | in >> volume.physical_group_tags[j]; 102 | } 103 | size_t num_boundary_surfaces; 104 | in >> num_boundary_surfaces; 105 | volume.boundary_surface_tags.resize(num_boundary_surfaces); 106 | for (size_t j = 0; j < num_boundary_surfaces; j++) { 107 | in >> volume.boundary_surface_tags[j]; 108 | } 109 | } 110 | 111 | assert(in.good()); 112 | } 113 | 114 | void load_entities_binary(std::istream& in, MshSpec& spec) 115 | { 116 | eat_white_space(in, 1); 117 | size_t num_points, num_curves, num_surfaces, num_volumes; 118 | in.read(reinterpret_cast(&num_points), sizeof(size_t)); 119 | in.read(reinterpret_cast(&num_curves), sizeof(size_t)); 120 | in.read(reinterpret_cast(&num_surfaces), sizeof(size_t)); 121 | in.read(reinterpret_cast(&num_volumes), sizeof(size_t)); 122 | assert(in.good()); 123 | 124 | Entities& entities = spec.entities; 125 | entities.points.resize(num_points); 126 | entities.curves.resize(num_curves); 127 | entities.surfaces.resize(num_surfaces); 128 | entities.volumes.resize(num_volumes); 129 | 130 | for (size_t i = 0; i < num_points; i++) { 131 | PointEntity& point = entities.points[i]; 132 | in.read(reinterpret_cast(&point.tag), sizeof(int)); 133 | in.read(reinterpret_cast(&point.x), sizeof(double)); 134 | in.read(reinterpret_cast(&point.y), sizeof(double)); 135 | in.read(reinterpret_cast(&point.z), sizeof(double)); 136 | size_t num_physical_groups; 137 | in.read(reinterpret_cast(&num_physical_groups), sizeof(size_t)); 138 | assert(in.good()); 139 | 140 | point.physical_group_tags.resize(num_physical_groups); 141 | in.read(reinterpret_cast(point.physical_group_tags.data()), 142 | static_cast(sizeof(int) * num_physical_groups)); 143 | assert(in.good()); 144 | } 145 | 146 | for (size_t i = 0; i < num_curves; i++) { 147 | CurveEntity& curve = entities.curves[i]; 148 | in.read(reinterpret_cast(&curve.tag), sizeof(int)); 149 | in.read(reinterpret_cast(&curve.min_x), sizeof(double)); 150 | in.read(reinterpret_cast(&curve.min_y), sizeof(double)); 151 | in.read(reinterpret_cast(&curve.min_z), sizeof(double)); 152 | in.read(reinterpret_cast(&curve.max_x), sizeof(double)); 153 | in.read(reinterpret_cast(&curve.max_y), sizeof(double)); 154 | in.read(reinterpret_cast(&curve.max_z), sizeof(double)); 155 | size_t num_physical_groups; 156 | in.read(reinterpret_cast(&num_physical_groups), sizeof(size_t)); 157 | assert(in.good()); 158 | 159 | curve.physical_group_tags.resize(num_physical_groups); 160 | in.read(reinterpret_cast(curve.physical_group_tags.data()), 161 | static_cast(sizeof(int) * num_physical_groups)); 162 | assert(in.good()); 163 | 164 | size_t num_boundary_points; 165 | in.read(reinterpret_cast(&num_boundary_points), sizeof(size_t)); 166 | assert(in.good()); 167 | 168 | curve.boundary_point_tags.resize(num_boundary_points); 169 | in.read(reinterpret_cast(curve.boundary_point_tags.data()), 170 | static_cast(sizeof(int) * num_boundary_points)); 171 | assert(in.good()); 172 | } 173 | 174 | for (size_t i = 0; i < num_surfaces; i++) { 175 | SurfaceEntity& surface = entities.surfaces[i]; 176 | in.read(reinterpret_cast(&surface.tag), sizeof(int)); 177 | in.read(reinterpret_cast(&surface.min_x), sizeof(double)); 178 | in.read(reinterpret_cast(&surface.min_y), sizeof(double)); 179 | in.read(reinterpret_cast(&surface.min_z), sizeof(double)); 180 | in.read(reinterpret_cast(&surface.max_x), sizeof(double)); 181 | in.read(reinterpret_cast(&surface.max_y), sizeof(double)); 182 | in.read(reinterpret_cast(&surface.max_z), sizeof(double)); 183 | size_t num_physical_groups; 184 | in.read(reinterpret_cast(&num_physical_groups), sizeof(size_t)); 185 | assert(in.good()); 186 | 187 | surface.physical_group_tags.resize(num_physical_groups); 188 | in.read(reinterpret_cast(surface.physical_group_tags.data()), 189 | static_cast(sizeof(int) * num_physical_groups)); 190 | assert(in.good()); 191 | 192 | size_t num_boundary_curves; 193 | in.read(reinterpret_cast(&num_boundary_curves), sizeof(size_t)); 194 | assert(in.good()); 195 | 196 | surface.boundary_curve_tags.resize(num_boundary_curves); 197 | in.read(reinterpret_cast(surface.boundary_curve_tags.data()), 198 | static_cast(sizeof(int) * num_boundary_curves)); 199 | assert(in.good()); 200 | } 201 | 202 | for (size_t i = 0; i < num_volumes; i++) { 203 | VolumeEntity& volume = entities.volumes[i]; 204 | in.read(reinterpret_cast(&volume.tag), sizeof(int)); 205 | in.read(reinterpret_cast(&volume.min_x), sizeof(double)); 206 | in.read(reinterpret_cast(&volume.min_y), sizeof(double)); 207 | in.read(reinterpret_cast(&volume.min_z), sizeof(double)); 208 | in.read(reinterpret_cast(&volume.max_x), sizeof(double)); 209 | in.read(reinterpret_cast(&volume.max_y), sizeof(double)); 210 | in.read(reinterpret_cast(&volume.max_z), sizeof(double)); 211 | size_t num_physical_groups; 212 | in.read(reinterpret_cast(&num_physical_groups), sizeof(size_t)); 213 | assert(in.good()); 214 | 215 | volume.physical_group_tags.resize(num_physical_groups); 216 | in.read(reinterpret_cast(volume.physical_group_tags.data()), 217 | static_cast(sizeof(int) * num_physical_groups)); 218 | assert(in.good()); 219 | 220 | size_t num_boundary_surfaces; 221 | in.read(reinterpret_cast(&num_boundary_surfaces), sizeof(size_t)); 222 | assert(in.good()); 223 | 224 | volume.boundary_surface_tags.resize(num_boundary_surfaces); 225 | in.read(reinterpret_cast(volume.boundary_surface_tags.data()), 226 | static_cast(sizeof(int) * num_boundary_surfaces)); 227 | assert(in.good()); 228 | } 229 | } 230 | 231 | } // namespace v41 232 | 233 | void load_entities(std::istream& in, MshSpec& spec) 234 | { 235 | const std::string& version = spec.mesh_format.version; 236 | const bool is_ascii = spec.mesh_format.file_type == 0; 237 | if (version == "4.1") { 238 | if (is_ascii) 239 | v41::load_entities_ascii(in, spec); 240 | else 241 | v41::load_entities_binary(in, spec); 242 | } else if (version == "2.2") { 243 | throw InvalidFormat("$Entities section not supported by MSH version 2.2"); 244 | } 245 | } 246 | 247 | } // namespace mshio 248 | -------------------------------------------------------------------------------- /src/load_msh_elements.cpp: -------------------------------------------------------------------------------- 1 | #include "element_utils.h" 2 | #include "io_utils.h" 3 | #include "load_msh_format.h" 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | 19 | namespace mshio { 20 | 21 | namespace v41 { 22 | 23 | void load_elements_ascii(std::istream& in, MshSpec& spec) 24 | { 25 | Elements& elements = spec.elements; 26 | in >> elements.num_entity_blocks; 27 | in >> elements.num_elements; 28 | in >> elements.min_element_tag; 29 | in >> elements.max_element_tag; 30 | assert(in.good()); 31 | 32 | elements.entity_blocks.resize(elements.num_entity_blocks); 33 | for (size_t i = 0; i < elements.num_entity_blocks; i++) { 34 | ElementBlock& block = elements.entity_blocks[i]; 35 | 36 | in >> block.entity_dim; 37 | in >> block.entity_tag; 38 | in >> block.element_type; 39 | in >> block.num_elements_in_block; 40 | 41 | const size_t n = nodes_per_element(block.element_type); 42 | block.data.resize(block.num_elements_in_block * (n + 1)); 43 | for (size_t j = 0; j < block.num_elements_in_block; j++) { 44 | for (size_t k = 0; k <= n; k++) { 45 | in >> block.data[j * (n + 1) + k]; 46 | } 47 | } 48 | assert(in.good()); 49 | } 50 | } 51 | 52 | void load_elements_binary(std::istream& in, MshSpec& spec) 53 | { 54 | eat_white_space(in, 1); 55 | Elements& elements = spec.elements; 56 | in.read(reinterpret_cast(&elements.num_entity_blocks), sizeof(size_t)); 57 | in.read(reinterpret_cast(&elements.num_elements), sizeof(size_t)); 58 | in.read(reinterpret_cast(&elements.min_element_tag), sizeof(size_t)); 59 | in.read(reinterpret_cast(&elements.max_element_tag), sizeof(size_t)); 60 | assert(in.good()); 61 | 62 | elements.entity_blocks.resize(elements.num_entity_blocks); 63 | for (size_t i = 0; i < elements.num_entity_blocks; i++) { 64 | ElementBlock& block = elements.entity_blocks[i]; 65 | 66 | in.read(reinterpret_cast(&block.entity_dim), sizeof(int)); 67 | in.read(reinterpret_cast(&block.entity_tag), sizeof(int)); 68 | in.read(reinterpret_cast(&block.element_type), sizeof(int)); 69 | in.read(reinterpret_cast(&block.num_elements_in_block), sizeof(size_t)); 70 | 71 | const size_t n = nodes_per_element(block.element_type); 72 | block.data.resize(block.num_elements_in_block * (n + 1)); 73 | in.read(reinterpret_cast(block.data.data()), 74 | static_cast(sizeof(size_t) * block.data.size())); 75 | assert(in.good()); 76 | } 77 | } 78 | } // namespace v41 79 | 80 | namespace v22 { 81 | 82 | namespace { 83 | template 84 | void create_entities( 85 | const std::map>& entity_tag_to_physical_tags, std::vector& entities) 86 | { 87 | for (const auto& entry : entity_tag_to_physical_tags) { 88 | entities.emplace_back(); 89 | Entity& entity = entities.back(); 90 | entity.tag = entry.first; 91 | entity.physical_group_tags.insert( 92 | entity.physical_group_tags.end(), entry.second.begin(), entry.second.end()); 93 | } 94 | } 95 | } // namespace 96 | 97 | void load_elements_ascii(std::istream& in, MshSpec& spec) 98 | { 99 | Elements& elements = spec.elements; 100 | size_t num_elements; 101 | in >> num_elements; 102 | 103 | // Due to v2.2 constraints, each element is parsed as a separate block, and 104 | // a regrouping will happen at post-processing time. 105 | elements.num_entity_blocks += num_elements; 106 | elements.num_elements += num_elements; 107 | 108 | int element_num, element_type; 109 | int num_tags; 110 | std::vector tags; 111 | std::vector node_ids; 112 | 113 | std::array>, 4> entity_tag_to_physical_tags; 114 | 115 | for (size_t i = 0; i < num_elements; i++) { 116 | in >> element_num; 117 | in >> element_type; 118 | in >> num_tags; 119 | tags.resize(static_cast(num_tags)); 120 | for (int j = 0; j < num_tags; j++) { 121 | in >> tags[static_cast(j)]; 122 | } 123 | 124 | const size_t n = nodes_per_element(element_type); 125 | node_ids.resize(n); 126 | for (size_t j = 0; j < n; j++) { 127 | in >> node_ids[j]; 128 | } 129 | 130 | elements.min_element_tag = 131 | std::min(elements.min_element_tag, static_cast(element_num)); 132 | elements.max_element_tag = 133 | std::max(elements.max_element_tag, static_cast(element_num)); 134 | 135 | elements.entity_blocks.emplace_back(); 136 | auto& block = elements.entity_blocks.back(); 137 | block.num_elements_in_block = 1; 138 | block.entity_dim = get_element_dim(element_type); 139 | if (tags.size() > 1) { 140 | // By default, the first tag is the tag of the physical entity to which the element 141 | // belongs; the second is the tag of the elementary model entity to which the element 142 | // belongs; [...]. Gmsh and most codes using the MSH 2 format require at least the 143 | // first two tags (physical and elementary tags). 144 | block.entity_tag = tags[1]; 145 | auto it = entity_tag_to_physical_tags[block.entity_dim].find(tags[1]); 146 | if (it == entity_tag_to_physical_tags[block.entity_dim].end()) { 147 | entity_tag_to_physical_tags[block.entity_dim][tags[1]] = {tags[0]}; 148 | } else { 149 | it->second.insert(tags[0]); 150 | } 151 | } else if (tags.size() > 0) { 152 | // This is undefined 153 | block.entity_tag = tags.front(); 154 | } else { 155 | block.entity_tag = 1; 156 | } 157 | block.element_type = element_type; 158 | block.data.resize(n + 1); 159 | 160 | block.data[0] = static_cast(element_num); 161 | for (size_t j = 0; j < n; j++) { 162 | block.data[j + 1] = static_cast(node_ids[j]); 163 | } 164 | } 165 | 166 | create_entities(entity_tag_to_physical_tags[0], spec.entities.points); 167 | create_entities(entity_tag_to_physical_tags[1], spec.entities.curves); 168 | create_entities(entity_tag_to_physical_tags[2], spec.entities.surfaces); 169 | create_entities(entity_tag_to_physical_tags[3], spec.entities.volumes); 170 | } 171 | 172 | void load_elements_binary(std::istream& in, MshSpec& spec) 173 | { 174 | Elements& elements = spec.elements; 175 | in >> elements.num_elements; 176 | elements.entity_blocks.reserve(elements.num_elements); 177 | eat_white_space(in, 1); 178 | 179 | std::array>, 4> entity_tag_to_physical_tags; 180 | 181 | std::vector tags, node_ids; 182 | int32_t min_tag = std::numeric_limits::max(); 183 | int32_t max_tag = 0; 184 | size_t num_processed_elements = 0; 185 | while (num_processed_elements != elements.num_elements) { 186 | int32_t element_type, num_elements_in_block, num_tags, element_id; 187 | in.read(reinterpret_cast(&element_type), 4); 188 | in.read(reinterpret_cast(&num_elements_in_block), 4); 189 | in.read(reinterpret_cast(&num_tags), 4); 190 | 191 | tags.resize(static_cast(num_tags)); 192 | 193 | const size_t n = nodes_per_element(element_type); 194 | node_ids.resize(n); 195 | 196 | // Due to v2.2 constraints, each element is parsed as a separate block, and 197 | // a regrouping will happen at post-processing time. 198 | for (size_t i = 0; i < num_elements_in_block; i++) { 199 | in.read(reinterpret_cast(&element_id), 4); 200 | in.read(reinterpret_cast(tags.data()), 4 * num_tags); 201 | in.read(reinterpret_cast(node_ids.data()), static_cast(4 * n)); 202 | 203 | min_tag = std::min(min_tag, element_id); 204 | max_tag = std::max(max_tag, element_id); 205 | 206 | elements.entity_blocks.emplace_back(); 207 | ElementBlock& block = elements.entity_blocks.back(); 208 | block.num_elements_in_block = 1; 209 | block.entity_dim = get_element_dim(element_type); 210 | if (tags.size() > 1) { 211 | // "By default, the first tag is the tag of the physical entity to which the element 212 | // belongs; the second is the tag of the elementary model entity to which the 213 | // element belongs; [...]. Gmsh and most codes using the MSH 2 format require at 214 | // least the first two tags (physical and elementary tags). 215 | block.entity_tag = tags[1]; 216 | auto it = entity_tag_to_physical_tags[block.entity_dim].find(tags[1]); 217 | if (it == entity_tag_to_physical_tags[block.entity_dim].end()) { 218 | entity_tag_to_physical_tags[block.entity_dim][tags[1]] = {tags[0]}; 219 | } else { 220 | it->second.insert(tags[0]); 221 | } 222 | } else if (tags.size() > 0) { 223 | // This is undefined 224 | block.entity_tag = tags.front(); 225 | } else { 226 | block.entity_tag = 1; 227 | } 228 | block.element_type = static_cast(element_type); 229 | block.data.resize(n + 1); 230 | 231 | block.data[0] = static_cast(element_id); 232 | for (size_t j = 0; j < n; j++) { 233 | block.data[j + 1] = static_cast(node_ids[j]); 234 | } 235 | 236 | num_processed_elements++; 237 | if (num_processed_elements > elements.num_elements) { 238 | throw InvalidFormat("Inconsistent element count detected!"); 239 | } 240 | } 241 | } 242 | 243 | elements.num_entity_blocks = elements.entity_blocks.size(); 244 | elements.min_element_tag = std::min(elements.min_element_tag, static_cast(min_tag)); 245 | elements.max_element_tag = std::max(elements.max_element_tag, static_cast(max_tag)); 246 | 247 | create_entities(entity_tag_to_physical_tags[0], spec.entities.points); 248 | create_entities(entity_tag_to_physical_tags[1], spec.entities.curves); 249 | create_entities(entity_tag_to_physical_tags[2], spec.entities.surfaces); 250 | create_entities(entity_tag_to_physical_tags[3], spec.entities.volumes); 251 | } 252 | 253 | } // namespace v22 254 | 255 | void load_elements(std::istream& in, MshSpec& spec) 256 | { 257 | if (spec.elements.entity_blocks.size() == 0) { 258 | spec.elements.min_element_tag = std::numeric_limits::max(); 259 | spec.elements.max_element_tag = 0; 260 | } 261 | 262 | const std::string& version = spec.mesh_format.version; 263 | const bool is_ascii = spec.mesh_format.file_type == 0; 264 | if (version == "4.1") { 265 | if (is_ascii) 266 | v41::load_elements_ascii(in, spec); 267 | else 268 | v41::load_elements_binary(in, spec); 269 | } else if (version == "2.2") { 270 | if (is_ascii) 271 | v22::load_elements_ascii(in, spec); 272 | else 273 | v22::load_elements_binary(in, spec); 274 | } else { 275 | std::stringstream msg; 276 | msg << "Unsupported MSH version: " << version; 277 | throw UnsupportedFeature(msg.str()); 278 | } 279 | } 280 | 281 | } // namespace mshio 282 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | # Docs: 2 | # http://clang.llvm.org/docs/ClangFormatStyleOptions.html 3 | 4 | # The style used for all options not specifically set in the configuration. 5 | # Available options: LLVM, Google, Chromium, Mozilla, WebKit 6 | BasedOnStyle: Google 7 | 8 | # The extra indent or outdent of access modifiers, e.g. public:. 9 | AccessModifierOffset: -4 10 | 11 | # If true, horizontally aligns arguments after an open bracket. 12 | # This applies to round brackets (parentheses), angle brackets and square brackets. This will result in formattings like code someLongFunction(argument1, argument2); endcode 13 | AlignAfterOpenBracket: DontAlign 14 | 15 | # If true, aligns escaped newlines as far left as possible. Otherwise puts them into the right-most column. 16 | AlignEscapedNewlinesLeft: Left 17 | 18 | # If true, horizontally align operands of binary and ternary expressions. 19 | AlignOperands: true 20 | 21 | # If true, aligns trailing comments. 22 | AlignTrailingComments: false 23 | 24 | # Allow putting all parameters of a function declaration onto the next line even if BinPackParameters is false. 25 | AllowAllParametersOfDeclarationOnNextLine: true 26 | 27 | # Allows contracting simple braced statements to a single line. 28 | # E.g., this allows if (a) { return; } to be put on a single line. 29 | # Shayan: I set it to false. I think if you want to create a single line block, 30 | # might as well just not create the block in the first place. 31 | AllowShortBlocksOnASingleLine: false 32 | 33 | # If true, short case labels will be contracted to a single line. 34 | AllowShortCaseLabelsOnASingleLine: true 35 | 36 | # Dependent on the value, int f() { return 0; } can be put on a single line. 37 | # SFS_None (in configuration: None) Never merge functions into a single line. 38 | # SFS_Inline (in configuration: Inline) Only merge functions defined inside a class. 39 | # SFS_Empty (in configuration: Empty) Only merge empty functions. 40 | # SFS_All (in configuration: All) Merge all functions fitting on a single line. 41 | AllowShortFunctionsOnASingleLine: Inline 42 | 43 | # If true, if (a) return; can be put on a single line. 44 | # Lagrange convention: 45 | # if( condition ) do something; -> YES 46 | # if( condition ) 47 | # do something; -> DON'T 48 | AllowShortIfStatementsOnASingleLine: true 49 | 50 | # If true, while (true) continue; can be put on a single line. 51 | # Lagrange convention: 52 | # while( condition ) do something; -> YES 53 | # while( condition ) 54 | # do something; -> DON'T 55 | AllowShortLoopsOnASingleLine: true 56 | 57 | # The function definition return type breaking style to use. This option is deprecated and is 58 | # retained for backwards compatibility. 59 | # AlwaysBreakAfterDefinitionReturnType: false 60 | 61 | # If true, always break before multiline string literals. 62 | AlwaysBreakBeforeMultilineStrings: false 63 | 64 | # If true, always break after the template<...> of a template declaration. 65 | AlwaysBreakTemplateDeclarations: true 66 | 67 | # If false, a function call's arguments will either be all on the same line or will have one line each. 68 | BinPackArguments: false 69 | 70 | # If false, a function call's arguments will either be all 71 | # on the same line or will have one line each. 72 | BinPackParameters: false 73 | 74 | # The way to wrap binary operators. 75 | # BOS_None (in configuration: None) Break after operators. 76 | # BOS_NonAssignment (in configuration: NonAssignment) Break before operators that aren't assignments. 77 | # BOS_All (in configuration: All) Break before operators. 78 | #BreakBeforeBinaryOperators: None 79 | 80 | # The brace breaking style to use. 81 | # BS_Attach (in configuration: Attach) Always attach braces to surrounding context. 82 | # BS_Linux (in configuration: Linux) Like Attach, but break before braces on function, namespace and class definitions. 83 | # BS_Stroustrup (in configuration: Stroustrup) Like Attach, but break before function definitions, and `else`. 84 | # BS_Allman (in configuration: Allman) Always break before braces. 85 | # BS_GNU (in configuration: GNU) Always break before braces and add an extra level of indentation to braces of control statements, not to those of class, function or other definitions. 86 | BraceWrapping: 87 | AfterClass: true 88 | AfterControlStatement: false 89 | AfterEnum: false 90 | AfterFunction: true 91 | AfterNamespace: false 92 | AfterObjCDeclaration: false 93 | AfterStruct: true 94 | AfterUnion: false 95 | BeforeCatch: false 96 | BeforeElse: false 97 | IndentBraces: false 98 | SplitEmptyFunction: false 99 | BreakBeforeBraces: Custom 100 | 101 | # If true, ternary operators will be placed after line breaks. 102 | BreakBeforeTernaryOperators: true 103 | 104 | # Always break constructor initializers before commas and align the commas with the colon. 105 | BreakConstructorInitializersBeforeComma: true 106 | 107 | # The column limit. 108 | # A column limit of 0 means that there is no column limit. In this case, clang-format will respect the input's line breaking decisions within statements unless they contradict other rules. 109 | ColumnLimit: 100 110 | 111 | # A regular expression that describes comments with special meaning, which should not be split into lines or otherwise changed. 112 | CommentPragmas: "\/*(.*)*\/" 113 | 114 | # If the constructor initializers don't fit on a line, put each initializer on its own line. 115 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 116 | 117 | # The number of characters to use for indentation of constructor initializer lists. 118 | ConstructorInitializerIndentWidth: 4 119 | 120 | # Indent width for line continuations. 121 | # A little more than normal indent 122 | ContinuationIndentWidth: 4 123 | 124 | # If true, format braced lists as best suited for C++11 braced lists. 125 | # Important differences: - No spaces inside the braced list. - No line break before the closing brace. - Indentation with the continuation indent, not with the block indent. 126 | # Fundamentally, C++11 braced lists are formatted exactly like function calls would be formatted in their place. If the braced list follows a name (e.g. a type or variable name), clang-format formats as if the {} were the parentheses of a function call with that name. If there is no name, a zero-length name is assumed. 127 | Cpp11BracedListStyle: true 128 | 129 | # If true, analyze the formatted file for the most common alignment of & and *. Point 130 | #DerivePointerAlignment: false 131 | 132 | # Disables formatting at all. 133 | #DisableFormat: false 134 | 135 | # If true, clang-format detects whether function calls and definitions are formatted with one parameter per line. 136 | # Each call can be bin-packed, one-per-line or inconclusive. If it is inconclusive, e.g. completely on one line, but a decision needs to be made, clang-format analyzes whether there are other bin-packed cases in the input file and act accordingly. 137 | # NOTE: This is an experimental flag, that might go away or be renamed. Do not use this in config files, etc. Use at your own risk. 138 | #ExperimentalAutoDetectBinPacking: false 139 | 140 | # If true, clang-format adds missing namespace end comments and fixes invalid existing ones. 141 | FixNamespaceComments: true 142 | 143 | # A vector of macros that should be interpreted as foreach loops instead of as function calls. 144 | # These are expected to be macros of the form: code FOREACH(, ...) endcode 145 | # For example: BOOST_FOREACH. 146 | #ForEachMacros (std::vector) 147 | 148 | # Dependent on the value, multiple #include blocks can be sorted as one and divided based on category. 149 | IncludeBlocks: Preserve 150 | 151 | # Indent case labels one level from the switch statement. 152 | # When false, use the same indentation level as for the switch statement. Switch statement body is always indented one level more than case labels. 153 | IndentCaseLabels: false 154 | 155 | # The number of columns to use for indentation. 156 | IndentWidth: 4 157 | 158 | # Indent if a function definition or declaration is wrapped after the type. 159 | #IndentWrappedFunctionNames: false 160 | 161 | # If true, empty lines at the start of blocks are kept. 162 | #KeepEmptyLinesAtTheStartOfBlocks: false 163 | 164 | # Language, this format style is targeted at. 165 | # LK_None (in configuration: None) Do not use. 166 | # LK_Cpp (in configuration: Cpp) Should be used for C, C++, ObjectiveC, ObjectiveC++. 167 | # LK_Java (in configuration: Java) Should be used for Java. 168 | # LK_JavaScript (in configuration: JavaScript) Should be used for JavaScript. 169 | # LK_Proto (in configuration: Proto) Should be used for Protocol Buffers (https://developers.google.com/protocol-buffers/). 170 | Language: Cpp 171 | 172 | # The maximum number of consecutive empty lines to keep. 173 | MaxEmptyLinesToKeep: 2 174 | 175 | # The indentation used for namespaces. 176 | # NI_None (in configuration: None) Don't indent in namespaces. 177 | # NI_Inner (in configuration: Inner) Indent only in inner namespaces (nested in other namespaces). 178 | # NI_All (in configuration: All) Indent in all namespaces. 179 | NamespaceIndentation: None 180 | 181 | # The number of characters to use for indentation of ObjC blocks. 182 | #ObjCBlockIndentWidth: 4 183 | 184 | # Add a space after @property in Objective-C, i.e. use \@property (readonly) instead of \@property(readonly). 185 | #ObjCSpaceAfterProperty: false 186 | 187 | # Add a space in front of an Objective-C protocol list, i.e. use Foo instead of Foo. 188 | #ObjCSpaceBeforeProtocolList: false 189 | 190 | # The penalty for breaking a function call after `call(`. 191 | #PenaltyBreakBeforeFirstCallParameter (unsigned) 192 | 193 | # The penalty for each line break introduced inside a comment. 194 | #PenaltyBreakComment (unsigned) 195 | 196 | # The penalty for breaking before the first <<. 197 | #PenaltyBreakFirstLessLess (unsigned) 198 | 199 | # The penalty for each line break introduced inside a string literal. 200 | #PenaltyBreakString (unsigned) 201 | 202 | # The penalty for each character outside of the column limit. 203 | #PenaltyExcessCharacter (unsigned) 204 | 205 | # Penalty for putting the return type of a function onto its own line. 206 | #PenaltyReturnTypeOnItsOwnLine (unsigned) 207 | 208 | # If true, analyze the formatted file for the most common alignment of & and *. PointerAlignment is then used only as fallback. 209 | PointerAlignment: Middle 210 | 211 | # If true, clang-format will attempt to re-flow comments. 212 | ReflowComments: true 213 | 214 | # Add a space before parantheses 215 | # if (condition) -> tick 216 | # if(condition) -> cross 217 | SpaceBeforeParens: ControlStatements 218 | 219 | # If true, a space may be inserted after C style casts. 220 | SpaceAfterCStyleCast: false 221 | 222 | # If false, spaces will be removed before assignment operators. 223 | #SpaceBeforeAssignmentOperators: true 224 | 225 | # If true, spaces may be inserted into `()`. 226 | SpaceInEmptyParentheses: false 227 | 228 | # The number of spaces before trailing line comments (// - comments). 229 | # This does not affect trailing block comments (/**/ - comments) as those commonly have different usage patterns and a number of special cases. 230 | SpacesBeforeTrailingComments: 1 231 | 232 | # If true, spaces will be inserted after `<` and before `>` in template argument lists 233 | SpacesInAngles: false 234 | 235 | # If true, spaces may be inserted into C style casts. 236 | SpacesInCStyleCastParentheses: false 237 | 238 | # If true, spaces are inserted inside container literals (e.g. ObjC and Javascript array and dict literals). 239 | SpacesInContainerLiterals: false 240 | 241 | # If true, spaces will be inserted after `(` and before `)`. 242 | SpacesInParentheses: false 243 | 244 | # If true, spaces will be inserted after `[` and before `]`. 245 | SpacesInSquareBrackets: false 246 | 247 | # Format compatible with this standard, e.g. use A > instead of A> for LS_Cpp03. 248 | # LS_Cpp03 (in configuration: Cpp03) Use C++03-compatible syntax. 249 | # LS_Cpp11 (in configuration: Cpp11) Use features of C++11 (e.g. A> instead of A >). 250 | # LS_Auto (in configuration: Auto) Automatic detection based on the input. 251 | Standard: Cpp11 252 | 253 | # The number of columns used for tab stops. 254 | TabWidth: 4 255 | 256 | # The way to use tab characters in the resulting file. 257 | # UT_Never (in configuration: Never) Never use tab. 258 | # UT_ForIndentation (in configuration: ForIndentation) Use tabs only for indentation. 259 | # UT_Always (in configuration: Always) Use tabs whenever we need to fill whitespace that spans at least from one tab stop to the next one. 260 | UseTab: Never 261 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MshIO 2 | 3 | MshIO is a tiny library written with modern C++. It is created by Qingnan Zhou 4 | as a coding exercise. It supports reading and writing [MSH format] with both 5 | ASCII and binary encodings for both version 2.2 and version 4.1. 6 | 7 | ![build and test](https://github.com/qnzhou/MshIO/workflows/build%20and%20test/badge.svg) 8 | 9 | ## Build 10 | 11 | ```sh 12 | # Build C++ library 13 | mkdir build 14 | cd build 15 | cmake .. 16 | make 17 | 18 | # Build python 19 | pip install git+https://github.com/qnzhou/MshIO.git 20 | ``` 21 | 22 | ## Usage 23 | 24 | In C++: 25 | ```c++ 26 | #include 27 | 28 | mshio::MshSpec spec = mshio::load_msh("input.msh"); 29 | 30 | mshio::save_msh("output.msh", spec); 31 | ``` 32 | 33 | or in Python 34 | ```python 35 | import mshio 36 | 37 | spec = mshio.load_msh("input.msh") 38 | mshio.save_msh("output.msh", spec) 39 | ``` 40 | 41 | ## `MshSpec` data structure 42 | 43 | `MshSpec` ([code](include/mshio/MshSpec.h)) is a data structure 44 | that maps almost verbatim to the information stored in a MSH file. Currently, 45 | the following sections are supported: 46 | 47 | | Section name | Description | 48 | | ------------------: | ---------------------------------------------------------------------------- | 49 | | [Mesh format] | Format header. | 50 | | [Nodes] | 3D coordinates of nodes and (optionally) their parameterization coordinates. | 51 | | [Elements] | A list of elements grouped by blocks. | 52 | | [Entities] | The boundary representation (BRep) of the model. | 53 | | [Physical groups] | Metadata defined on elements. | 54 | | [Node data] | Scalar/vector/tensor fields defined on nodes. | 55 | | [Element data] | Scalar/vector/tensor fields defined on elements. | 56 | | [Element-node data] | Scalar/vector/tensor fields defined over each node of each element. | 57 | 58 | The follow sections are supported by MSH format, but not yet supported by MshIO 59 | (contribution welcomed): 60 | * Partitioned entities 61 | * Periodic 62 | * Ghost elements 63 | * Parametrizations 64 | * Interpolation scheme 65 | 66 | All fields will be populated by `mshio::load_msh()` method, and all fields 67 | should be set up correctly before calling `mshio::save_msh()` method. The helper 68 | method `mshio::validate_spec(spec)` can be used to check if a given `spec` is 69 | valid. 70 | 71 | ### Mesh format 72 | 73 | Mesh format section is the header of MSH file. It contains information about 74 | the MSH version used, whether the file is binary and data size. This section is 75 | required. 76 | 77 | ```c++ 78 | auto& format = spec.mesh_format; 79 | format.version = "4.1"; // Only version "2.2" and "4.1" are supported. 80 | format.file_type = 1; // 0: ASCII, 1: binary. 81 | format.data_size = sizeof(size_t); // Size of data, defined as sizeof(size_t) = 8. 82 | ``` 83 | 84 | ### Nodes 85 | 86 | Nodes are grouped into node blocks in MSH format. Each node has a unique "tag", 87 | and the tag is used for referring to this node in other sections. All tags are 88 | positive. Ideally, tags should be ordered consecutively from 1 to N (the number 89 | of nodes), but it does not have to be so. It is up to the client application to 90 | maintain a mapping from tag to nodes. 91 | 92 | ```c++ 93 | auto& nodes = spec.nodes; 94 | nodes.num_entity_blocksl = 1; // Number of node blocks. 95 | nodes.num_nodes = 3; // Total number of nodes. 96 | nodes.min_node_tag = 1; 97 | nodes.max_node_tag = 3; 98 | nodes.entity_blocks = {...}; // A std::vector of node blocks. 99 | ``` 100 | 101 | #### Node block 102 | 103 | A node block is simply a group of nodes. 104 | 105 | ```c++ 106 | auto& block = nodes.entity_blocks[k]; 107 | block.entity_dim = 2; // The dimension of the entity. 108 | block.entity_tag = 1; // The entity these nodes belongs to. 109 | block.parametric = 0; // 0: non-parametric, 1: parametric. 110 | block.num_nodes_in_block = 3; // The number of nodes in block. 111 | block.tags = {...}; // A std::vector of unique, positive node tags. 112 | block.data = {...}; // A std::vector of coordinates (x,y,z,,,,...) 113 | ``` 114 | 115 | When `block.parametric` is `1`, `block.data` contains the parametric coordinates 116 | in addition to the XYZ coordinates. The dimension of the parametric coordinates 117 | is defined by `block.entity_dim` variable. 118 | 119 | ### Elements 120 | 121 | Elements are grouped into element blocks. Each element has a unique positive 122 | "tag". Ideally, tags should be ordered consecutively from 1 to M (the number of 123 | elements), but it does not have not be so. It is up to the client application 124 | to maintain a mapping from tag to elements. 125 | 126 | ```c++ 127 | auto& elements = spec.elements; 128 | elements.num_entity_blocks = 1; // Number of element blocks. 129 | elements.num_elements = 12; // Total number of elmeents. 130 | elements.min_element_tag = 1; 131 | elements.max_element_tag = 12; 132 | elements.entity_blocks = {...}; // A std::vector of element blocks. 133 | ``` 134 | 135 | #### Element block 136 | 137 | An element block is a set of elements of the same type: 138 | 139 | ```c++ 140 | auto& block = elements.entity_blocks[k]; 141 | block.entity_dim = 2; // The dimension of the elements. 142 | block.entity_tag = 1; // The entity these elements belongs to. 143 | block.element_type = 2; // See element type table below. 144 | block.num_elements_in_block = 12; // The number of elements in this block. 145 | block.data = {...}; // See more detail below. 146 | ``` 147 | 148 | #### Element types 149 | 150 | MSH format supports a large set of finite element types. Element types 151 | determines the dimension of the element as well as the number of nodes that make 152 | up the element. 153 | 154 | ```c++ 155 | int element_type = ...; // Element type is encoded as an int. 156 | 157 | // To look up the number of nodes for this element type: 158 | size_t n = nodes_per_element(element_type); 159 | 160 | // To look up the dimension of the element: 161 | int dim = get_element_dim(element_type); 162 | ``` 163 | 164 | See all supported element types [here](#Supported-element-types). 165 | 166 | 167 | #### Element data 168 | 169 | The element data is a flattened `std::vector` of `size_t`. It follows the 170 | convention below: 171 | 172 | ``` 173 | element_tag node_tag ... node_tag 174 | element_tag node_tag ... node_tag 175 | ... 176 | ``` 177 | 178 | i.e. Each element entry consists of an element tag followed by `n` node tags, 179 | where `n` is the number of nodes corresponding determined by the element type. 180 | See the [supported element types](#Supported-element-types) table. 181 | 182 | ### Entities 183 | 184 | Entities make up the boundary representation of the mesh model. Nodes and 185 | elements belong to specific entities. The four types of entities are points, 186 | lines, surfaces and volumes. Higher order entities are bounded by lower ones 187 | (e.g. volumes are bounded by surfaces). Entities also contain bounding box and 188 | physical group information. 189 | 190 | ```c++ 191 | auto& entities = spec.entities; 192 | auto& point_entities = entities.points; 193 | auto& curve_entities = entities.curves; 194 | auto& surface_entities = entities.surfaces; 195 | auto& volume_entities = entities.volumes; 196 | 197 | // look up the physical groups of an element block 198 | auto& block = elements.entity_blocks[i]; 199 | assert(block.entity_dim == 2); // surface entity (0 for point, 1 for curve...) 200 | auto surface_entity = std::find_if(surface_entities.begin(), 201 | surface_entities.end(), [&](const mshio::SurfaceEntity& s) { 202 | return s.tag == block.entity_tag; 203 | }); 204 | assert(surface_entity != surface_entities.end()); 205 | auto& physical_groups = surface_entity->physical_group_tags; 206 | ``` 207 | 208 | ### Physical groups 209 | 210 | Physical groups are used to tag elements with arbitrary metadata (e.g. 211 | "boundary", "steel", etc.). Physical groups have an `int` tag, `int` dimension, 212 | and `string` name. The name may be empty. 213 | 214 | ```c++ 215 | auto& physical_groups = spec.physical_groups; 216 | auto& pg = physical_groups[k]; 217 | 218 | pg.dim = 0; 219 | pg.tag = 1; 220 | pg.name = "My Physical Group"; 221 | ``` 222 | 223 | ### Post-processing data 224 | 225 | One of main advantage of MSH format is its support for storing post-processing 226 | data along with the mesh. There are 3 types of post-processing data: node data, 227 | element data and element-node data. Each type of post-processing data consists 228 | of a header and a `std::vector` of entries. 229 | 230 | ```c++ 231 | auto& node_data = spec.node_data[k]; 232 | auto& element_data = spec.element_data[k]; 233 | auto& element_node_data = spec.element_node_data[k]; 234 | ``` 235 | 236 | #### Data header 237 | 238 | Data header consists of arrays of `string`, `double` and `int` tags. Some tags 239 | has pre-defined meaning: 240 | 241 | ```c++ 242 | auto& header = node_data.header; 243 | header.string_tags = {...}; // [field_name, , ...] 244 | header.real_tags = {...}; // [