From d81dd2669fef9303e783903a3f29969f14c5075c Mon Sep 17 00:00:00 2001 From: Daniel Zint Date: Fri, 10 Jan 2025 16:20:05 -0500 Subject: [PATCH 1/2] Only create VTM file if there is more than one VTU file. --- src/wmtk/io/ParaviewWriter.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/wmtk/io/ParaviewWriter.cpp b/src/wmtk/io/ParaviewWriter.cpp index 38e60386a1..8f71e6c39a 100644 --- a/src/wmtk/io/ParaviewWriter.cpp +++ b/src/wmtk/io/ParaviewWriter.cpp @@ -150,13 +150,15 @@ ParaviewWriter::ParaviewWriter( m_writers[2].init(filename.string() + "_faces.vtu", vertices_name, cells[2], m_enabled[2]); m_writers[3].init(filename.string() + "_tets.vtu", vertices_name, cells[3], m_enabled[3]); - paraviewo::VTMWriter vtm; - if (m_enabled[0]) vtm.add_dataset("verts", "mesh", filename.string() + "_verts.vtu"); - if (m_enabled[1]) vtm.add_dataset("edges", "mesh", filename.string() + "_edges.vtu"); - if (m_enabled[2]) vtm.add_dataset("faces", "mesh", filename.string() + "_faces.vtu"); - if (m_enabled[3]) vtm.add_dataset("tets", "mesh", filename.string() + "_tets.vtu"); - - vtm.save(filename.string() + ".vtm"); + if (m_enabled[0] + m_enabled[1] + m_enabled[2] + m_enabled[3] > 1) { + paraviewo::VTMWriter vtm; + if (m_enabled[0]) vtm.add_dataset("verts", "mesh", filename.string() + "_verts.vtu"); + if (m_enabled[1]) vtm.add_dataset("edges", "mesh", filename.string() + "_edges.vtu"); + if (m_enabled[2]) vtm.add_dataset("faces", "mesh", filename.string() + "_faces.vtu"); + if (m_enabled[3]) vtm.add_dataset("tets", "mesh", filename.string() + "_tets.vtu"); + + vtm.save(filename.string() + ".vtm"); + } } void ParaviewWriter::write( From 55263cc57dbfdd77af665976ff7e5a72a6c4c814 Mon Sep 17 00:00:00 2001 From: Daniel Zint Date: Fri, 10 Jan 2025 16:21:56 -0500 Subject: [PATCH 2/2] Add application tetwild_msh_converter. --- applications/CMakeLists.txt | 1 + .../tetwild_msh_converter/CMakeLists.txt | 7 + applications/tetwild_msh_converter/README.md | 31 +++++ .../tetwild_msh_converter_main.cpp | 130 ++++++++++++++++++ .../tetwild_msh_converter_spec.hpp | 38 +++++ .../tetwild_msh_converter_spec.json | 30 ++++ src/wmtk/io/MshReader.cpp | 7 +- 7 files changed, 240 insertions(+), 4 deletions(-) create mode 100644 applications/tetwild_msh_converter/CMakeLists.txt create mode 100644 applications/tetwild_msh_converter/README.md create mode 100644 applications/tetwild_msh_converter/tetwild_msh_converter_main.cpp create mode 100644 applications/tetwild_msh_converter/tetwild_msh_converter_spec.hpp create mode 100644 applications/tetwild_msh_converter/tetwild_msh_converter_spec.json diff --git a/applications/CMakeLists.txt b/applications/CMakeLists.txt index 35ee362ddc..e4a06d0c4b 100644 --- a/applications/CMakeLists.txt +++ b/applications/CMakeLists.txt @@ -27,6 +27,7 @@ add_application(marching ON) add_application(procedural ON) add_application(multimesh OFF) add_application(isotropic_remeshing OFF) +add_application(tetwild_msh_converter ON) add_application(tetwild_simplification ON) add_application(triwild ON) add_application(tetwild ON) diff --git a/applications/tetwild_msh_converter/CMakeLists.txt b/applications/tetwild_msh_converter/CMakeLists.txt new file mode 100644 index 0000000000..3f95c98e6d --- /dev/null +++ b/applications/tetwild_msh_converter/CMakeLists.txt @@ -0,0 +1,7 @@ +wmtk_add_application(tetwild_msh_converter_app + tetwild_msh_converter_main.cpp + tetwild_msh_converter_spec.hpp + ) + +target_link_libraries(tetwild_msh_converter_app PRIVATE +wmtk::input) \ No newline at end of file diff --git a/applications/tetwild_msh_converter/README.md b/applications/tetwild_msh_converter/README.md new file mode 100644 index 0000000000..29db8bc2e2 --- /dev/null +++ b/applications/tetwild_msh_converter/README.md @@ -0,0 +1,31 @@ +# Tetwild MSH Converter + +This application converts an MSH file produced by [Tetwild](https://github.com/daniel-zint/TetWild) into a VTU file. +If the MSH has an `in_out` attribute, it will convert it to a `winding_number` attribute. + +#### Parameters + +As this application is extremely simple, it does not require the standard JSON input. Instead, it just requires an input file name and optionally an output file name. + +``` +-i Input MSH file +-j Output VTU file (optional, by default the same as the input) +``` + +#### Examples of usage + +``` +./tetwild_msh_converter -i sphere.msh +``` + +This will create a `sphere_tets.vtu`. + +``` +./tetwild_msh_converter -i sphere.msh -o sphere_converted.vtu +``` + +This will create a `sphere_converted_tets.vtu`. + +#### JSON parameter file + +While a JSON parameter file is not necessary, it is still possible to be used with that, too. For details, look at `tetwild_msh_converter_spec.json`. diff --git a/applications/tetwild_msh_converter/tetwild_msh_converter_main.cpp b/applications/tetwild_msh_converter/tetwild_msh_converter_main.cpp new file mode 100644 index 0000000000..3fa30dfaf6 --- /dev/null +++ b/applications/tetwild_msh_converter/tetwild_msh_converter_main.cpp @@ -0,0 +1,130 @@ +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include "tetwild_msh_converter_spec.hpp" + +using namespace wmtk; +using namespace wmtk::components; +namespace fs = std::filesystem; + +int main(int argc, char* argv[]) +{ + opt_logger().set_level(spdlog::level::off); + CLI::App app{argv[0]}; + + app.ignore_case(); + + fs::path json_input_file; + fs::path input_file = ""; + fs::path output_file; + + auto cli_group = app.add_option_group("subgroup"); + + CLI::Option* cli_json_spec_file_option = + cli_group->add_option("-j, --json", json_input_file, "json specification file") + ->check(CLI::ExistingFile); + CLI::Option* cli_input_file_option = + cli_group->add_option("-i, --input", input_file, "input MSH file") + ->check(CLI::ExistingFile); + + cli_group->require_option(1); // --j xor --i + + CLI::Option* cli_output_file_option = + app.add_option("-o, --output", output_file, "output VTU file") + ->needs(cli_input_file_option); + + CLI11_PARSE(app, argc, argv); + + // json spec file + nlohmann::json j; + if (!json_input_file.empty()) { + using wmtk::components::utils::resolve_paths; + + std::ifstream ifs(json_input_file); + j = nlohmann::json::parse(ifs); + + jse::JSE spec_engine; + bool r = spec_engine.verify_json(j, tetwild_msh_converter_spec); + if (!r) { + wmtk::logger().error("{}", spec_engine.log2str()); + return 1; + } else { + j = spec_engine.inject_defaults(j, tetwild_msh_converter_spec); + } + + input_file = resolve_paths(json_input_file, {j["input_path"], j["input"]}); + output_file = std::string(j["output"]); + } + + if (output_file.empty()) { + output_file = input_file; + } + output_file.replace_extension(); + + + std::shared_ptr mesh; + try { + mesh = wmtk::components::input::input(input_file, false, {"in_out"}); + } catch (const std::exception&) { + // try again but without in_out attribute + mesh = wmtk::components::input::input(input_file); + } + Mesh& m = *mesh; + wmtk::logger().info("mesh has {} vertices", m.get_all(PrimitiveType::Vertex).size()); + + // add winding_number attribute + if (m.has_attribute("in_out", m.top_simplex_type())) { + auto wn_handle = m.register_attribute("winding_number", m.top_simplex_type(), 1); + auto wn_acc = m.create_accessor(wn_handle); + + auto in_out_handle = m.get_attribute_handle("in_out", PrimitiveType::Tetrahedron); + auto in_out_acc = m.create_accessor(in_out_handle); + + for (const Tuple& t : m.get_all(m.top_simplex_type())) { + wn_acc.scalar_attribute(t) = in_out_acc.scalar_attribute(t); + } + + auto pos_handle = m.get_attribute_handle("vertices", PrimitiveType::Vertex); + m.clear_attributes({pos_handle, wn_handle}); + } + + // output + { + logger().info("Write mesh '{}'", output_file.string()); + + const bool edge = m.top_simplex_type() == PrimitiveType::Edge; + const bool tri = m.top_simplex_type() == PrimitiveType::Triangle; + const bool tet = m.top_simplex_type() == PrimitiveType::Tetrahedron; + + wmtk::io::ParaviewWriter writer(output_file, "vertices", m, false, edge, tri, tet); + m.serialize(writer); + } + + if (!json_input_file.empty()) { + const std::string report = j["report"]; + if (!report.empty()) { + nlohmann::json out_json; + out_json["stats"]["vertices"] = m.get_all(PrimitiveType::Vertex).size(); + out_json["stats"]["edges"] = m.get_all(PrimitiveType::Edge).size(); + out_json["stats"]["triangles"] = m.get_all(PrimitiveType::Triangle).size(); + out_json["stats"]["tets"] = m.get_all(PrimitiveType::Tetrahedron).size(); + + out_json["input"] = j; + + std::ofstream ofs(report); + ofs << std::setw(4) << out_json; + } + } + + + return 0; +} \ No newline at end of file diff --git a/applications/tetwild_msh_converter/tetwild_msh_converter_spec.hpp b/applications/tetwild_msh_converter/tetwild_msh_converter_spec.hpp new file mode 100644 index 0000000000..879d778517 --- /dev/null +++ b/applications/tetwild_msh_converter/tetwild_msh_converter_spec.hpp @@ -0,0 +1,38 @@ +#pragma once +#include +namespace { + +nlohmann::json tetwild_msh_converter_spec = R"( +[ + { + "pointer": "/", + "type": "object", + "required": ["input"], + "optional": ["output", "report", "input_path"] + }, + { + "pointer": "/input", + "type": "string", + "doc": "Input MSH file" + }, + { + "pointer": "/output", + "type": "string", + "default": "", + "doc": "Output VTU file. By default, the same as the input file name but with .vtu extension." + }, + { + "pointer": "/report", + "type": "string", + "default": "" + }, + { + "pointer": "/input_path", + "type": "string", + "default": "", + "doc": " The folder in which the input file is located. By default, this path will be set to the folder of the JSON spec file." + } +] +)"_json; + +} \ No newline at end of file diff --git a/applications/tetwild_msh_converter/tetwild_msh_converter_spec.json b/applications/tetwild_msh_converter/tetwild_msh_converter_spec.json new file mode 100644 index 0000000000..988a5218a0 --- /dev/null +++ b/applications/tetwild_msh_converter/tetwild_msh_converter_spec.json @@ -0,0 +1,30 @@ +[ + { + "pointer": "/", + "type": "object", + "required": ["input"], + "optional": ["output", "report", "input_path"] + }, + { + "pointer": "/input", + "type": "string", + "doc": "Input MSH file" + }, + { + "pointer": "/output", + "type": "string", + "default": "", + "doc": "Output VTU file. By default, the same as the input file name but with .vtu extension." + }, + { + "pointer": "/report", + "type": "string", + "default": "" + }, + { + "pointer": "/input_path", + "type": "string", + "default": "", + "doc": " The folder in which the input file is located. By default, this path will be set to the folder of the JSON spec file." + } +] diff --git a/src/wmtk/io/MshReader.cpp b/src/wmtk/io/MshReader.cpp index acd58e0e1e..a71fb62b95 100644 --- a/src/wmtk/io/MshReader.cpp +++ b/src/wmtk/io/MshReader.cpp @@ -317,9 +317,8 @@ void MshReader::extract_element_attribute( } } -auto MshReader::generate( - const std::optional>>& extra_attributes_opt) - -> std::shared_ptr +auto MshReader::generate(const std::optional>>& + extra_attributes_opt) -> std::shared_ptr { std::shared_ptr res; switch (get_mesh_dimension()) { @@ -384,7 +383,7 @@ void MshReader::validate<3>() // swap col 0 and 1 of S S.col(0).swap(S.col(1)); wmtk::logger().info( - "Input tet orientation is inverted, swapping col 0 and 1 of TV matirx."); + "Input tet orientation is inverted, swapping col 0 and 1 of TV matrix."); } }