Skip to content

Commit

Permalink
Merge pull request #851 from wildmeshing/dzint/tetwild_msh_converter
Browse files Browse the repository at this point in the history
Dzint/tetwild msh converter
  • Loading branch information
daniel-zint authored Jan 11, 2025
2 parents 1107b6b + 55263cc commit 958ba69
Show file tree
Hide file tree
Showing 8 changed files with 249 additions and 11 deletions.
1 change: 1 addition & 0 deletions applications/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
7 changes: 7 additions & 0 deletions applications/tetwild_msh_converter/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
31 changes: 31 additions & 0 deletions applications/tetwild_msh_converter/README.md
Original file line number Diff line number Diff line change
@@ -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`.
130 changes: 130 additions & 0 deletions applications/tetwild_msh_converter/tetwild_msh_converter_main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
#include <jse/jse.h>
#include <CLI/CLI.hpp>
#include <filesystem>
#include <nlohmann/json.hpp>

#include <wmtk/io/ParaviewWriter.hpp>
#include <wmtk/utils/Logger.hpp>

#include <wmtk/components/input/input.hpp>
#include <wmtk/components/utils/json_utils.hpp>
#include <wmtk/components/utils/resolve_path.hpp>

#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> 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<double>("in_out", m.top_simplex_type())) {
auto wn_handle = m.register_attribute<double>("winding_number", m.top_simplex_type(), 1);
auto wn_acc = m.create_accessor<double>(wn_handle);

auto in_out_handle = m.get_attribute_handle<double>("in_out", PrimitiveType::Tetrahedron);
auto in_out_acc = m.create_accessor<double>(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<double>("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;
}
38 changes: 38 additions & 0 deletions applications/tetwild_msh_converter/tetwild_msh_converter_spec.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#pragma once
#include <nlohmann/json.hpp>
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;

}
30 changes: 30 additions & 0 deletions applications/tetwild_msh_converter/tetwild_msh_converter_spec.json
Original file line number Diff line number Diff line change
@@ -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."
}
]
7 changes: 3 additions & 4 deletions src/wmtk/io/MshReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -317,9 +317,8 @@ void MshReader::extract_element_attribute(
}
}

auto MshReader::generate(
const std::optional<std::vector<std::vector<std::string>>>& extra_attributes_opt)
-> std::shared_ptr<Mesh>
auto MshReader::generate(const std::optional<std::vector<std::vector<std::string>>>&
extra_attributes_opt) -> std::shared_ptr<Mesh>
{
std::shared_ptr<Mesh> res;
switch (get_mesh_dimension()) {
Expand Down Expand Up @@ -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.");
}
}

Expand Down
16 changes: 9 additions & 7 deletions src/wmtk/io/ParaviewWriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down

0 comments on commit 958ba69

Please sign in to comment.