Loading CMakeLists.txt +7 −0 Original line number Diff line number Diff line Loading @@ -56,3 +56,10 @@ if( ZLIB_FOUND ) target_include_directories(cgal-mesher PUBLIC ${ZLIB_INCLUDE_DIRS}) target_link_libraries(cgal-mesher PUBLIC ${ZLIB_LIBRARIES}) endif() # polyhedral-generator (based on cinolib; needs VTK for input) set(cinolib_DIR "${CMAKE_SOURCE_DIR}/libs/cinolib") set(CINOLIB_USES_VTK ON) find_package(cinolib REQUIRED) add_executable(polyhedral-generator polyhedral-generator.cpp) target_link_libraries(polyhedral-generator cinolib) polyhedral-generator.cpp 0 → 100644 +223 −0 Original line number Diff line number Diff line #include <filesystem> #include <string> #include <sstream> #include <cinolib/meshes/tetmesh.h> #include <cinolib/dual_mesh.h> #include "lyra/lyra.hpp" template <typename T> std::string fmt_default(T value) { std::stringstream str; str << " Default value: " << value << "."; return str.str(); } struct Parameters { // pipeline options bool detect_features = false; double features_angle_bound = 60; void set_cli_options(lyra::cli & cli) { // pipeline options cli |= lyra::opt(detect_features) ["-f"]["--detect-features"] ("Before dual mesh generation, detect sharp features on the primal " "mesh surface and preserve them on the dual mesh." + fmt_default(detect_features)); cli |= lyra::opt(features_angle_bound, "SCALAR") ["--features-angle-bound"] ("The maximum angle (in degrees) between the two normal vectors " "of adjacent triangles. For an edge of the mesh, if the angle " "between the two normal vectors of its incident facets is bigger " "than the given bound, then the edge is considered as a feature " "edge." + fmt_default(features_angle_bound)); } void print_options(std::ostream & str) { str << "Pipeline parameters:\n"; str << "- Detect features: " << detect_features << "\n"; if (detect_features) str << "- Features angle bound: " << features_angle_bound << "\n"; str.flush(); } }; void write_FPMA(const char * filename, const std::vector<cinolib::vec3d> & verts, const std::vector<std::vector<uint>> & faces, const std::vector<std::vector<uint>> & polys, const std::vector<std::vector<bool>> & polys_winding) { setlocale(LC_NUMERIC, "en_US.UTF-8"); // makes sure "." is the decimal separator FILE *fp = fopen(filename, "w"); if(!fp) { std::cerr << "ERROR : " << __FILE__ << ", line " << __LINE__ << " : save_HEDRA() : couldn't write output file " << filename << std::endl; exit(-1); } uint nv = verts.size(); uint nf = faces.size(); uint np = polys.size(); // write vertices fprintf(fp, "%d\n", nv); // http://stackoverflow.com/questions/16839658/printf-width-specifier-to-maintain-precision-of-floating-point-value for(const cinolib::vec3d & v : verts) fprintf(fp, "%.17g %.17g %.17g\n", v.x(), v.y(), v.z()); fprintf(fp, "\n"); // write faces fprintf(fp, "%d\n", nf); for(const std::vector<uint> & f : faces) { fprintf(fp, "%d ", static_cast<int>(f.size())); for(uint vid : f) fprintf(fp, "%d ", vid); fprintf(fp, "\n"); } fprintf(fp, "\n"); // write cells fprintf(fp, "%d\n", np); for(uint pid=0; pid<np; ++pid) { fprintf(fp, "%d ", static_cast<int>(polys.at(pid).size())); for(uint off=0; off<polys.at(pid).size(); ++off) fprintf(fp, "%d ", polys.at(pid).at(off)); fprintf(fp, "\n"); } fprintf(fp, "\n"); fclose(fp); } class MyPolyhedralmesh : public cinolib::Polyhedralmesh<> { public: // hack to expose attributes that we need for FPMA export auto my_get_verts() { return this->verts; } auto my_get_faces() { return this->faces; } auto my_get_polys() { return this->polys; } auto my_get_polys_face_winding() { return this->polys_face_winding; } }; void generateDual( const std::filesystem::path & input_file, const std::filesystem::path & output_file, const Parameters & args) { // load the input mesh cinolib::Tetmesh<> m_tet; m_tet.load(input_file.string().c_str()); // detect feature edges if (args.detect_features) { // convert degrees to radians const double angle_threshold = args.features_angle_bound * 3.1415926535897932384626433 / 180.; m_tet.edge_mark_sharp_creases(angle_threshold); } // make polyhedral (voronoi) mesh //cinolib::Polyhedralmesh<> m_poly; MyPolyhedralmesh m_poly; cinolib::dual_mesh(m_tet, m_poly, args.detect_features); // write the polyhedral mesh //m_poly.save("output.hedra"); // only .hedra is supported for output write_FPMA(output_file.string().c_str(), m_poly.my_get_verts(), m_poly.my_get_faces(), m_poly.my_get_polys(), m_poly.my_get_polys_face_winding()); } int main(int argc, char * argv[]) { Parameters args; std::filesystem::path input_file; std::filesystem::path output_path = "."; bool show_help = false; auto cli = lyra::cli(); cli |= lyra::help(show_help).description("polyhedral mesh generator based on the cinolib library"); args.set_cli_options(cli); // positional arguments (should be generally defined after optional arguments) cli |= lyra::arg(input_file, "input_file") ("Input file of a tetrahedral mesh (supported formats: VTK, VTU, TET, MESH).").required(); cli |= lyra::arg(output_path, "output_path") ("Output path. If it is an existing directory, the stem of " "input_file (i.e., base file name without suffix) is appended to " "output_path. Otherwise, output_path denotes the output file and " "it must end with the \".fpma\" suffix." + fmt_default(output_path)); auto result = cli.parse({ argc, argv }); // check that the arguments where valid if (!result) { std::cerr << "Error in command line: " << result.errorMessage() << std::endl; std::cerr << cli << std::endl; return EXIT_FAILURE; } // show the help when asked for. if (show_help) { std::cout << cli << std::endl; return EXIT_SUCCESS; } // check if the input file exists if (!std::filesystem::exists(input_file)) { std::cerr << "Error: input_file \"" + input_file.string() + "\" does not exist." << std::endl; return EXIT_FAILURE; } // check if output_path is an existing directory if (std::filesystem::is_directory(output_path)) output_path /= (input_file.stem().string() + ".fpma"); // ensure that output_path ends with ".fpma" else if (output_path.extension() != ".fpma") { std::cerr << "Error: output_path \"" + output_path.string() + "\" does not end with \".fpma\"." << std::endl; return EXIT_FAILURE; } std::cout << "Input file: " << input_file << "\n"; std::cout << "Output file: " << output_path << "\n"; args.print_options(std::cout); try { generateDual(input_file, output_path, args); } catch (std::logic_error & e) { std::cerr << "Logic error: " << e.what() << std::endl; return EXIT_FAILURE; } catch (std::runtime_error & e) { std::cerr << "Runtime error: " << e.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; } Loading
CMakeLists.txt +7 −0 Original line number Diff line number Diff line Loading @@ -56,3 +56,10 @@ if( ZLIB_FOUND ) target_include_directories(cgal-mesher PUBLIC ${ZLIB_INCLUDE_DIRS}) target_link_libraries(cgal-mesher PUBLIC ${ZLIB_LIBRARIES}) endif() # polyhedral-generator (based on cinolib; needs VTK for input) set(cinolib_DIR "${CMAKE_SOURCE_DIR}/libs/cinolib") set(CINOLIB_USES_VTK ON) find_package(cinolib REQUIRED) add_executable(polyhedral-generator polyhedral-generator.cpp) target_link_libraries(polyhedral-generator cinolib)
polyhedral-generator.cpp 0 → 100644 +223 −0 Original line number Diff line number Diff line #include <filesystem> #include <string> #include <sstream> #include <cinolib/meshes/tetmesh.h> #include <cinolib/dual_mesh.h> #include "lyra/lyra.hpp" template <typename T> std::string fmt_default(T value) { std::stringstream str; str << " Default value: " << value << "."; return str.str(); } struct Parameters { // pipeline options bool detect_features = false; double features_angle_bound = 60; void set_cli_options(lyra::cli & cli) { // pipeline options cli |= lyra::opt(detect_features) ["-f"]["--detect-features"] ("Before dual mesh generation, detect sharp features on the primal " "mesh surface and preserve them on the dual mesh." + fmt_default(detect_features)); cli |= lyra::opt(features_angle_bound, "SCALAR") ["--features-angle-bound"] ("The maximum angle (in degrees) between the two normal vectors " "of adjacent triangles. For an edge of the mesh, if the angle " "between the two normal vectors of its incident facets is bigger " "than the given bound, then the edge is considered as a feature " "edge." + fmt_default(features_angle_bound)); } void print_options(std::ostream & str) { str << "Pipeline parameters:\n"; str << "- Detect features: " << detect_features << "\n"; if (detect_features) str << "- Features angle bound: " << features_angle_bound << "\n"; str.flush(); } }; void write_FPMA(const char * filename, const std::vector<cinolib::vec3d> & verts, const std::vector<std::vector<uint>> & faces, const std::vector<std::vector<uint>> & polys, const std::vector<std::vector<bool>> & polys_winding) { setlocale(LC_NUMERIC, "en_US.UTF-8"); // makes sure "." is the decimal separator FILE *fp = fopen(filename, "w"); if(!fp) { std::cerr << "ERROR : " << __FILE__ << ", line " << __LINE__ << " : save_HEDRA() : couldn't write output file " << filename << std::endl; exit(-1); } uint nv = verts.size(); uint nf = faces.size(); uint np = polys.size(); // write vertices fprintf(fp, "%d\n", nv); // http://stackoverflow.com/questions/16839658/printf-width-specifier-to-maintain-precision-of-floating-point-value for(const cinolib::vec3d & v : verts) fprintf(fp, "%.17g %.17g %.17g\n", v.x(), v.y(), v.z()); fprintf(fp, "\n"); // write faces fprintf(fp, "%d\n", nf); for(const std::vector<uint> & f : faces) { fprintf(fp, "%d ", static_cast<int>(f.size())); for(uint vid : f) fprintf(fp, "%d ", vid); fprintf(fp, "\n"); } fprintf(fp, "\n"); // write cells fprintf(fp, "%d\n", np); for(uint pid=0; pid<np; ++pid) { fprintf(fp, "%d ", static_cast<int>(polys.at(pid).size())); for(uint off=0; off<polys.at(pid).size(); ++off) fprintf(fp, "%d ", polys.at(pid).at(off)); fprintf(fp, "\n"); } fprintf(fp, "\n"); fclose(fp); } class MyPolyhedralmesh : public cinolib::Polyhedralmesh<> { public: // hack to expose attributes that we need for FPMA export auto my_get_verts() { return this->verts; } auto my_get_faces() { return this->faces; } auto my_get_polys() { return this->polys; } auto my_get_polys_face_winding() { return this->polys_face_winding; } }; void generateDual( const std::filesystem::path & input_file, const std::filesystem::path & output_file, const Parameters & args) { // load the input mesh cinolib::Tetmesh<> m_tet; m_tet.load(input_file.string().c_str()); // detect feature edges if (args.detect_features) { // convert degrees to radians const double angle_threshold = args.features_angle_bound * 3.1415926535897932384626433 / 180.; m_tet.edge_mark_sharp_creases(angle_threshold); } // make polyhedral (voronoi) mesh //cinolib::Polyhedralmesh<> m_poly; MyPolyhedralmesh m_poly; cinolib::dual_mesh(m_tet, m_poly, args.detect_features); // write the polyhedral mesh //m_poly.save("output.hedra"); // only .hedra is supported for output write_FPMA(output_file.string().c_str(), m_poly.my_get_verts(), m_poly.my_get_faces(), m_poly.my_get_polys(), m_poly.my_get_polys_face_winding()); } int main(int argc, char * argv[]) { Parameters args; std::filesystem::path input_file; std::filesystem::path output_path = "."; bool show_help = false; auto cli = lyra::cli(); cli |= lyra::help(show_help).description("polyhedral mesh generator based on the cinolib library"); args.set_cli_options(cli); // positional arguments (should be generally defined after optional arguments) cli |= lyra::arg(input_file, "input_file") ("Input file of a tetrahedral mesh (supported formats: VTK, VTU, TET, MESH).").required(); cli |= lyra::arg(output_path, "output_path") ("Output path. If it is an existing directory, the stem of " "input_file (i.e., base file name without suffix) is appended to " "output_path. Otherwise, output_path denotes the output file and " "it must end with the \".fpma\" suffix." + fmt_default(output_path)); auto result = cli.parse({ argc, argv }); // check that the arguments where valid if (!result) { std::cerr << "Error in command line: " << result.errorMessage() << std::endl; std::cerr << cli << std::endl; return EXIT_FAILURE; } // show the help when asked for. if (show_help) { std::cout << cli << std::endl; return EXIT_SUCCESS; } // check if the input file exists if (!std::filesystem::exists(input_file)) { std::cerr << "Error: input_file \"" + input_file.string() + "\" does not exist." << std::endl; return EXIT_FAILURE; } // check if output_path is an existing directory if (std::filesystem::is_directory(output_path)) output_path /= (input_file.stem().string() + ".fpma"); // ensure that output_path ends with ".fpma" else if (output_path.extension() != ".fpma") { std::cerr << "Error: output_path \"" + output_path.string() + "\" does not end with \".fpma\"." << std::endl; return EXIT_FAILURE; } std::cout << "Input file: " << input_file << "\n"; std::cout << "Output file: " << output_path << "\n"; args.print_options(std::cout); try { generateDual(input_file, output_path, args); } catch (std::logic_error & e) { std::cerr << "Logic error: " << e.what() << std::endl; return EXIT_FAILURE; } catch (std::runtime_error & e) { std::cerr << "Runtime error: " << e.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; }