From 6339e08b6268a973af545e56fa17e6f62f0b8161 Mon Sep 17 00:00:00 2001 From: Jirawat I Date: Mon, 17 Feb 2020 00:46:25 +0700 Subject: [PATCH 01/20] improve quiet mode --- README.md | 3 +- include/Scaffolder_2.h | 48 ++++++++++++- include/dualmc/dualmc.h | 4 ++ include/dualmc/dualmc.tpp | 19 ++++- include/implicit_function.h | 13 +++- src/Main.cpp | 138 +++++++++++++++++++++++++++--------- 6 files changed, 187 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 4127d33..9b939b2 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ -# Scaffolder [![Build Status](https://travis-ci.org/nodtem66/Scaffolder.svg?branch=master)](https://travis-ci.org/nodtem66/Scaffolder) +# Scaffolder +[![Build Status](https://travis-ci.org/nodtem66/Scaffolder.svg?branch=master)](https://travis-ci.org/nodtem66/Scaffolder) [![Build Status](https://travis-ci.org/nodtem66/Scaffolder.svg?branch=dev)](https://travis-ci.org/nodtem66/Scaffolder) Generate scaffold from STL file with implicit function (Schwarz P/ Gyroid). ``` Scaffolder - generate 3D scaffold from STL file diff --git a/include/Scaffolder_2.h b/include/Scaffolder_2.h index b8ce9be..30fc370 100644 --- a/include/Scaffolder_2.h +++ b/include/Scaffolder_2.h @@ -5,6 +5,7 @@ #include #include #include +#include #include "cxxopts.hpp" #include "diplib.h" @@ -18,7 +19,11 @@ #include "implicit_function.h" #include "Mesh.h" -#ifndef _WIN32 +#ifdef _WIN32 +#include +#else +#include +#include #include #include #include @@ -154,4 +159,45 @@ int make_dir(std::string& str) { #else return mkdir(str.c_str(), 0733); #endif +} + +/* Remove if already defined */ +typedef long long int64; typedef unsigned long long uint64; + +/* Returns the amount of milliseconds elapsed since the UNIX epoch. Works on both + * windows and linux. */ + +uint64 GetTimeMs64() +{ +#ifdef _WIN32 + /* Windows */ + FILETIME ft; + LARGE_INTEGER li; + + /* Get the amount of 100 nano seconds intervals elapsed since January 1, 1601 (UTC) and copy it + * to a LARGE_INTEGER structure. */ + GetSystemTimeAsFileTime(&ft); + li.LowPart = ft.dwLowDateTime; + li.HighPart = ft.dwHighDateTime; + + uint64 ret = li.QuadPart; + ret -= 116444736000000000LL; /* Convert from file time to UNIX epoch time. */ + ret /= 10000; /* From 100 nano seconds (10^-7) to 1 millisecond (10^-3) intervals */ + + return ret; +#else + /* Linux */ + struct timeval tv; + + gettimeofday(&tv, NULL); + + uint64 ret = tv.tv_usec; + /* Convert from micro seconds (10^-6) to milliseconds (10^-3) */ + ret /= 1000; + + /* Adds the seconds (10^0) after converting them to milliseconds (10^-3) */ + ret += (tv.tv_sec * 1000); + + return ret; +#endif } \ No newline at end of file diff --git a/include/dualmc/dualmc.h b/include/dualmc/dualmc.h index 7a076f8..74b653c 100644 --- a/include/dualmc/dualmc.h +++ b/include/dualmc/dualmc.h @@ -16,6 +16,10 @@ // stl includes #include #include +#include "ProgressBar.hpp" +#ifndef PROGRESS_BAR_COLUMN +#define PROGRESS_BAR_COLUMN 40 +#endif namespace dualmc { diff --git a/include/dualmc/dualmc.tpp b/include/dualmc/dualmc.tpp index 2df0aa8..a6c614d 100644 --- a/include/dualmc/dualmc.tpp +++ b/include/dualmc/dualmc.tpp @@ -267,8 +267,10 @@ void DualMC::buildQuadSoup( Vertex vertex3; int pointCode; + ProgressBar progress(reducedX*reducedY*reducedZ, PROGRESS_BAR_COLUMN); + // iterate voxels - for(int32_t z = 0; z < reducedZ; ++z) + for(int32_t z = 0; z < reducedZ; ++z) { for(int32_t y = 0; y < reducedY; ++y) for(int32_t x = 0; x < reducedX; ++x) { // construct quad for x edge @@ -369,7 +371,10 @@ void DualMC::buildQuadSoup( } } } + ++progress; } + progress.display(); + } // generate triangle soup quads size_t const numQuads = vertices.size() / 4; @@ -377,6 +382,7 @@ void DualMC::buildQuadSoup( for (size_t i = 0; i < numQuads; ++i) { quads.emplace_back(i * 4, i * 4 + 1, i * 4 + 2, i * 4 + 3); } + progress.done(); } //------------------------------------------------------------------------------ @@ -397,8 +403,10 @@ void DualMC::buildSharedVerticesQuads( pointToIndex.clear(); + ProgressBar progress(reducedX*reducedY*reducedZ, PROGRESS_BAR_COLUMN); + // iterate voxels - for(int32_t z = 0; z < reducedZ; ++z) + for(int32_t z = 0; z < reducedZ; ++z) { for(int32_t y = 0; y < reducedY; ++y) for(int32_t x = 0; x < reducedX; ++x) { // construct quads for x edge @@ -456,6 +464,11 @@ void DualMC::buildSharedVerticesQuads( quads.emplace_back(i0,i3,i2,i1); } } - } + } + // progress bar + ++progress; } + progress.display(); + } + progress.done(); } \ No newline at end of file diff --git a/include/implicit_function.h b/include/implicit_function.h index 2c5a10e..9adf268 100644 --- a/include/implicit_function.h +++ b/include/implicit_function.h @@ -22,6 +22,14 @@ class Function { virtual FT operator()(FT, FT, FT) = 0; }; +class Fixed : public Function { +public: + const FT val; + Fixed(FT val) : val(val) {} + FT operator()(FT x, FT y, FT z) { + return val; + } +}; /* Function return rectlinear style from slicer * where coff is required to adjust the diameter filament */ @@ -232,7 +240,10 @@ inline void to_lower(std::string& s) { } Function* isosurface(std::string name, FT t) { - if (name == "rectlinear") { + if (name == "empty") { + return new Fixed(1); + } + else if (name == "rectlinear") { return new Rectlinear(); } else if (name == "schwarzp") { diff --git a/src/Main.cpp b/src/Main.cpp index 48f9a1e..6df75f3 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -8,7 +8,8 @@ int main(int argc, char* argv[]) bool is_analysis_microstructure = false; bool is_export_microstructure = false; bool is_export_feret = false; - bool is_export_hdf5 = false; + bool is_export_inverse = false; + bool is_build_inverse = true; bool no_output = false; uint16_t grid_offset = 2; uint16_t shell = 0; @@ -44,7 +45,8 @@ int main(int argc, char* argv[]) ("g,grid_size", "Grid size [default: 100]", cxxopts::value(), "INT") ("grid_offset", "[default:2]", cxxopts::value(), "INT") ("smooth_step", "Smooth with laplacian (default: 5)", cxxopts::value(), "INT") - ("hdf5", "export the result in hdf5 format [default: false]") + ("output_inverse", "additional output inverse scaffold") + ("inverse", "Enable build inverse 3D scaffold (for pore connectivity analysis)") ("dirty", "Disable autoclean") ("minimum_diameter", "used for removing small orphaned (between 0-1) [default: 0.25]", cxxopts::value(), "DOUBLE"); options.parse_positional({ "input", "output", "format" }); @@ -76,8 +78,8 @@ int main(int argc, char* argv[]) is_export_feret = result["m3"].as(); is_analysis_microstructure = is_export_feret; } - if (result.count("hdf5")) { - is_export_hdf5 = result["hdf5"].as(); + if (result.count("output_inverse")) { + is_export_inverse = result["output_inverse"].as(); } if (result.count("output")) { filename = result["output"].as(); @@ -92,6 +94,7 @@ int main(int argc, char* argv[]) if (result.count("surface")) surface = result["surface"].as(); if (result.count("shell")) shell = result["shell"].as(); if (result.count("smooth_step")) smooth_step = result["smooth_step"].as(); + if (result.count("inverse")) is_build_inverse = result["inverse"].as(); to_lower(surface); to_lower(format); @@ -111,6 +114,7 @@ int main(int argc, char* argv[]) } // Print parameters + std::ofstream result; if (verbose) { std::cout << "[Scaffolder " << VERSION << "]" << std::endl << "-- Input file: " << input_file << std::endl @@ -125,11 +129,30 @@ int main(int argc, char* argv[]) << "-- Autoclean: " << (dirty ? "False" : "True") << std::endl << "-- Minimum diameter: " << 100 * minimum_diameter << "%" << std::endl << "-- Analysis microstructure: " << (is_analysis_microstructure || is_export_microstructure ? "True" : "False") << std::endl - << "-- Export HDF5: " << (is_export_hdf5 ? "True" : "False") << std::endl; + << "-- Build Inverse: " << (is_build_inverse ? "True" : "False") << std::endl + << "-- Export Inverse: " << (is_export_inverse ? "True" : "False") << std::endl; + } + else { + std::string _name(filename); + _name.append(".txt"); + result.open(_name.c_str(), std::ofstream::out); + // Print header + result << "Surface,coff,shell,thickness,grid_size,grid_offset,smooth_step,input_file,avg_min_feret,avg_max_feret," + << "min_min_feret,q1_min_feret,q2_min_feret,q3_min_feret,max_min_feret," + << "min_max_feret,q1_max_feret,q2_max_feret,q3_max_feret,max_max_feret," + << "vol,surface_area,porosity,surface_area_ratio,#vertices,#faces," + << "min_square,q1_square,q2_square,q3_square,max_square," + << "min_circle,q1_circle,q2_circle,q3_circle,max_circle," + << "min_triangle,q1_triangle,q2_triangle,q3_triangle,max_triangle," + << "min_ellipse,q1_ellipse,q2_ellipse,q3_ellipse,max_ellipse," + << "min_elongation,q1_elongation,q2_elongation,q3_elongation,max_elongation" << std::endl; + result << surface << ',' << coff << ',' << shell << ',' << thickness << ',' << minimum_grid_size << ',' << grid_offset << ',' << smooth_step << ',' + << input_file << ','; } // Stage 1: TMesh mesh, inverse_mesh; + uint64_t starttime, endtime; double volume1 = eps, volume2 = eps; double area1 = eps, area2 = eps; { @@ -205,8 +228,11 @@ int main(int argc, char* argv[]) // Create border offset from the original box V1min1 = V1min - grid_offset * grid_delta * Eigen::RowVector3d::Ones(); grid_size = (L / grid_delta).cast() + 2 * grid_offset * Eigen::RowVector3i::Ones(); - if (verbose) std::cout << "-- Bounding Box: " << V1min.format(CleanFmt) << ' ' << V1max.format(CleanFmt) << std::endl; - if (verbose) std::cout << "-- Length: " << L << std::endl; + if (verbose) + std::cout << "-- Bounding Box: " + << V1min.format(CleanFmt) << ' ' << V1max.format(CleanFmt) << std::endl + << "-- Length: " << L.format(CleanFmt) << std::endl + << "-- Grid delta: " << grid_delta << std::endl; } Implicit_function fn(isosurface(surface, thickness), coff); if (verbose) std::cout << "[Generating grid] "; @@ -225,7 +251,7 @@ int main(int argc, char* argv[]) if (verbose) std::cout << "OK" << std::endl - << "-- Grid size: " << grid_size.prod() << grid_size.format(CleanFmt) << std::endl; + << "-- Grid size: " << grid_size.prod() << " " << grid_size.format(CleanFmt) << std::endl; if (verbose) std::cout << "[Calculating Winding number] "; Eigen::VectorXd W; @@ -304,6 +330,7 @@ int main(int argc, char* argv[]) std::stringstream filename; std::vector minFeret; std::vector maxFeret; + std::vector podczeckShapes[5]; if (is_export_microstructure) { size_t firstindex = input_file.find_last_of("/\\"); firstindex = firstindex == string::npos ? 0 : firstindex + 1; @@ -324,7 +351,7 @@ int main(int argc, char* argv[]) uint8_t axis2 = (main_axis + 1) % 3; uint8_t axis3 = (main_axis + 2) % 3; for (size_t k = 0; k < grid_size(main_axis); k++) { - img2d.Fill(255); + img2d.Fill(200); for (size_t j = 0; j < grid_size(axis2); j++) { for (size_t i = 0; i < grid_size(axis3); i++) { if (main_axis == 0) @@ -340,15 +367,26 @@ int main(int argc, char* argv[]) } } // Measurement Feret diameter - dip::Image label = dip::Label(img2d < 150, 2); - dip::Measurement msr = tool.Measure(label, img2d, { "Feret" }, {}, 2); + dip::Image label = dip::Label(img2d < 50, 2); + dip::Measurement msr = tool.Measure(label, img2d, { "Feret", "PodczeckShapes" }, {}, 2); dip::Measurement::IteratorFeature it = msr["Feret"]; dip::Measurement::IteratorFeature::Iterator feret = it.FirstObject(); while (feret) { - maxFeret.push_back(feret[0]*grid_delta); + // From ref: https://diplib.github.io/diplib-docs/features.html#size_features_Feret + maxFeret.push_back(feret[2]*grid_delta); minFeret.push_back(feret[1]*grid_delta); ++feret; } + it = msr["PodczeckShapes"]; + dip::Measurement::IteratorFeature::Iterator shape = it.FirstObject(); + while (shape) { + podczeckShapes[0].push_back(shape[0]); + podczeckShapes[1].push_back(shape[1]); + podczeckShapes[2].push_back(shape[2]); + podczeckShapes[3].push_back(shape[3]); + podczeckShapes[4].push_back(shape[4]); + ++shape; + } if (is_export_microstructure) { filename.str(std::string()); filename << dir << '/' << axis << '_' << k << ".jpg"; @@ -375,10 +413,37 @@ int main(int argc, char* argv[]) fs.close(); } progress.done(); - if (verbose) { - std::cout << "[Microstructure] " << std::endl - << "-- Avg Min Feret: " << std::accumulate(minFeret.begin(), minFeret.end(), 0.0) / minFeret.size() << std::endl - << "-- Avg Max Feret: " << std::accumulate(maxFeret.begin(), maxFeret.end(), 0.0) / maxFeret.size() << std::endl; + if (minFeret.size() > 0 && maxFeret.size() > 0) { + if (verbose) { + std::sort(minFeret.begin(), minFeret.end()); + std::sort(maxFeret.begin(), maxFeret.end()); + std::sort(podczeckShapes[0].begin(), podczeckShapes[0].end()); + std::sort(podczeckShapes[1].begin(), podczeckShapes[1].end()); + std::sort(podczeckShapes[2].begin(), podczeckShapes[2].end()); + std::sort(podczeckShapes[3].begin(), podczeckShapes[3].end()); + std::sort(podczeckShapes[4].begin(), podczeckShapes[4].end()); + std::cout << "[Microstructure] " << std::endl + << "-- Avg Min Feret: " << std::accumulate(minFeret.begin(), minFeret.end(), 0.0) / minFeret.size() << std::endl + << "-- Avg Max Feret: " << std::accumulate(maxFeret.begin(), maxFeret.end(), 0.0) / maxFeret.size() << std::endl + << "-- Min Feret: [" << minFeret.at(0) << " " << minFeret.at(minFeret.size() * 0.25) << " " << minFeret.at(minFeret.size() / 2) << " " << minFeret.at(minFeret.size() * 0.75) << " " << minFeret.at(minFeret.size() - 1) << "]" << std::endl + << "-- Max Feret: [" << maxFeret.at(0) << " " << maxFeret.at(maxFeret.size() * 0.25) << " " << maxFeret.at(maxFeret.size() / 2) << " " << maxFeret.at(maxFeret.size() * 0.75) << " " << maxFeret.at(maxFeret.size() - 1) << "]" << std::endl + << "-- Square Similarity: [" << podczeckShapes[0].at(0) << " " << podczeckShapes[0].at(podczeckShapes[0].size() * 0.25) << " " << podczeckShapes[0].at(podczeckShapes[0].size() / 2) << " " << podczeckShapes[0].at(podczeckShapes[0].size() * 0.75) << " " << podczeckShapes[0].at(podczeckShapes[0].size() - 1) << "]" << std::endl + << "-- Circle Similarity: [" << podczeckShapes[1].at(0) << " " << podczeckShapes[1].at(podczeckShapes[1].size() * 0.25) << " " << podczeckShapes[1].at(podczeckShapes[1].size() / 2) << " " << podczeckShapes[1].at(podczeckShapes[1].size() * 0.75) << " " << podczeckShapes[1].at(podczeckShapes[1].size() - 1) << "]" << std::endl + << "-- Triangle Similarity: [" << podczeckShapes[2].at(0) << " " << podczeckShapes[2].at(podczeckShapes[2].size() * 0.25) << " " << podczeckShapes[2].at(podczeckShapes[2].size() / 2) << " " << podczeckShapes[2].at(podczeckShapes[2].size() * 0.75) << " " << podczeckShapes[2].at(podczeckShapes[2].size() - 1) << "]" << std::endl + << "-- Ellipse Similarity: [" << podczeckShapes[3].at(0) << " " << podczeckShapes[3].at(podczeckShapes[3].size() * 0.25) << " " << podczeckShapes[3].at(podczeckShapes[3].size() / 2) << " " << podczeckShapes[3].at(podczeckShapes[3].size() * 0.75) << " " << podczeckShapes[3].at(podczeckShapes[3].size() - 1) << "]" << std::endl + << "-- Elongation Similarity: [" << podczeckShapes[4].at(0) << " " << podczeckShapes[4].at(podczeckShapes[4].size() * 0.25) << " " << podczeckShapes[4].at(podczeckShapes[4].size() / 2) << " " << podczeckShapes[4].at(podczeckShapes[4].size() * 0.75) << " " << podczeckShapes[4].at(podczeckShapes[4].size() - 1) << "]" << std::endl; + } + else { + result << std::accumulate(minFeret.begin(), minFeret.end(), 0.0) / minFeret.size() << ',' + << std::accumulate(maxFeret.begin(), maxFeret.end(), 0.0) / maxFeret.size() << ',' + << minFeret.at(0) << ',' << minFeret.at(minFeret.size() * 0.25) << ',' << minFeret.at(minFeret.size() / 2) << ',' << minFeret.at(minFeret.size() * 0.75) << ',' << minFeret.at(minFeret.size() - 1) << ',' + << maxFeret.at(0) << ',' << maxFeret.at(maxFeret.size() * 0.25) << ',' << maxFeret.at(maxFeret.size() / 2) << ',' << maxFeret.at(maxFeret.size() * 0.75) << ',' << maxFeret.at(maxFeret.size() - 1) << ',' + << podczeckShapes[0].at(0) << ',' << podczeckShapes[0].at(podczeckShapes[0].size() * 0.25) << ',' << podczeckShapes[0].at(podczeckShapes[0].size() / 2) << ',' << podczeckShapes[0].at(podczeckShapes[0].size() * 0.75) << ',' << podczeckShapes[0].at(podczeckShapes[0].size() - 1) << ',' + << podczeckShapes[1].at(0) << ',' << podczeckShapes[1].at(podczeckShapes[1].size() * 0.25) << ',' << podczeckShapes[1].at(podczeckShapes[1].size() / 2) << ',' << podczeckShapes[1].at(podczeckShapes[1].size() * 0.75) << ',' << podczeckShapes[1].at(podczeckShapes[1].size() - 1) << ',' + << podczeckShapes[2].at(0) << ',' << podczeckShapes[2].at(podczeckShapes[2].size() * 0.25) << ',' << podczeckShapes[2].at(podczeckShapes[2].size() / 2) << ',' << podczeckShapes[2].at(podczeckShapes[2].size() * 0.75) << ',' << podczeckShapes[2].at(podczeckShapes[2].size() - 1) << ',' + << podczeckShapes[3].at(0) << ',' << podczeckShapes[3].at(podczeckShapes[3].size() * 0.25) << ',' << podczeckShapes[3].at(podczeckShapes[3].size() / 2) << ',' << podczeckShapes[3].at(podczeckShapes[3].size() * 0.75) << ',' << podczeckShapes[3].at(podczeckShapes[3].size() - 1) << ',' + << podczeckShapes[4].at(0) << ',' << podczeckShapes[4].at(podczeckShapes[4].size() * 0.25) << ',' << podczeckShapes[4].at(podczeckShapes[4].size() / 2) << ',' << podczeckShapes[4].at(podczeckShapes[4].size() * 0.75) << ',' << podczeckShapes[4].at(podczeckShapes[4].size() - 1) << ','; + } } } } @@ -389,24 +454,28 @@ int main(int argc, char* argv[]) } marching_cube(mesh, Fxyz, grid_size, V1min1, grid_delta, verbose, dirty); - marching_cube(inverse_mesh, IFxyz, grid_size, V1min1, grid_delta, false, false); + if(is_build_inverse) marching_cube(inverse_mesh, IFxyz, grid_size, V1min1, grid_delta, false, false); } // Stage 2: Cleaning { if (!dirty) { clean_mesh(mesh, minimum_diameter, smooth_step, verbose); - clean_mesh(inverse_mesh, minimum_diameter, smooth_step, false); + if (is_build_inverse) clean_mesh(inverse_mesh, minimum_diameter, smooth_step, false); } // Report Volume and surface area int edgeNum = 0, edgeBorderNum = 0, edgeNonManifNum = 0; - vcg::tri::Clean::CountEdgeNum(inverse_mesh, edgeNum, edgeBorderNum, edgeNonManifNum); - - if (verbose) - std::cout << "[Pore connectivity]" << std::endl - << "-- #Edge Border: " << edgeBorderNum << std::endl - << "-- #Edge Non-manifold: " << edgeNonManifNum << std::endl; + if (is_build_inverse) { + vcg::tri::Clean::CountEdgeNum(inverse_mesh, edgeNum, edgeBorderNum, edgeNonManifNum); + if (verbose) + std::cout << "[Pore connectivity]" << std::endl + << "-- #Edge Border: " << edgeBorderNum << std::endl + << "-- #Edge Non-manifold: " << edgeNonManifNum << std::endl; + bool watertight = (edgeBorderNum == 0) && (edgeNonManifNum == 0); + bool pointcloud = (mesh.fn == 0 && mesh.vn != 0); + if (!watertight || pointcloud) std::cout << "[Warning] Pore isn't conencted" << std::endl; + } vcg::tri::Clean::CountEdgeNum(mesh, edgeNum, edgeBorderNum, edgeNonManifNum); bool watertight = (edgeBorderNum == 0) && (edgeNonManifNum == 0); @@ -416,11 +485,15 @@ int main(int argc, char* argv[]) area2 = vcg::tri::Stat::ComputeMeshArea(mesh); volume2 = vcg::tri::Stat::ComputeMeshVolume(mesh); if (verbose) - std::cout - << "-- Volume: " << volume2 << std::endl + std::cout + << "-- Volume: " << abs(volume2) << std::endl << "-- Surface Area: " << area2 << std::endl - << "-- Porosity: " << volume2 / volume1 << std::endl + << "-- Porosity: " << abs(volume2 / volume1) << std::endl << "-- Surface Area ratio: " << area2 / area1 << std::endl; + else + result + << abs(volume2) << ',' << area2 << ',' << abs(volume2 / volume1) << ',' << area2 / area1 << ',' + << mesh.VN() << ',' << mesh.FN() << std::endl; } if (!no_output) { @@ -430,22 +503,23 @@ int main(int argc, char* argv[]) filename2.append("_inverse." + format); if (format == "ply") { vcg::tri::io::ExporterPLY::Save(mesh, filename.c_str(), false); - vcg::tri::io::ExporterPLY::Save(inverse_mesh, filename2.c_str(), false); + if (is_build_inverse && is_export_inverse) vcg::tri::io::ExporterPLY::Save(inverse_mesh, filename2.c_str(), false); } else if (format == "obj") { vcg::tri::io::ExporterOBJ::Save(mesh, filename.c_str(), 0); - vcg::tri::io::ExporterOBJ::Save(inverse_mesh, filename2.c_str(), 0); + if (is_build_inverse && is_export_inverse) vcg::tri::io::ExporterOBJ::Save(inverse_mesh, filename2.c_str(), 0); } else if (format == "off") { vcg::tri::io::ExporterOFF::Save(mesh, filename.c_str(), 0); - vcg::tri::io::ExporterOFF::Save(inverse_mesh, filename2.c_str(), 0); + if (is_build_inverse && is_export_inverse) vcg::tri::io::ExporterOFF::Save(inverse_mesh, filename2.c_str(), 0); } else if (format == "stl") { vcg::tri::io::ExporterSTL::Save(mesh, filename.c_str(), 0); - vcg::tri::io::ExporterSTL::Save(inverse_mesh, filename2.c_str(), 0); + if (is_build_inverse && is_export_inverse) vcg::tri::io::ExporterSTL::Save(inverse_mesh, filename2.c_str(), 0); } } - if (verbose) std::cout << "OK" << std::endl << "[Finished]" << std::endl; + if (verbose) std::cout << "[Finished]" << std::endl; + result.close(); } return 0; } \ No newline at end of file From a4d7e4acc9237b7d411cad32ad55714af904e5bd Mon Sep 17 00:00:00 2001 From: Jirawat I Date: Tue, 25 Feb 2020 15:59:32 +0700 Subject: [PATCH 02/20] minor fixed in quiet mode --- include/Scaffolder_2.h | 6 ++--- include/implicit_function.h | 2 +- src/Main.cpp | 53 +++++++++++++++++++++++++------------ 3 files changed, 40 insertions(+), 21 deletions(-) diff --git a/include/Scaffolder_2.h b/include/Scaffolder_2.h index 30fc370..bcb36f8 100644 --- a/include/Scaffolder_2.h +++ b/include/Scaffolder_2.h @@ -6,6 +6,7 @@ #include #include #include +#include #include "cxxopts.hpp" #include "diplib.h" @@ -21,6 +22,7 @@ #ifdef _WIN32 #include +#include #else #include #include @@ -153,9 +155,7 @@ int make_dir(std::string& str) { if (str.empty()) return 0; #ifdef _WIN32 - wchar_t* wc = new wchar_t[str.size() + 1]; - mbstowcs(wc, str.c_str(), str.size()); - return _wmkdir(wc); + return _mkdir(str.c_str()); #else return mkdir(str.c_str(), 0733); #endif diff --git a/include/implicit_function.h b/include/implicit_function.h index 9adf268..dd71f48 100644 --- a/include/implicit_function.h +++ b/include/implicit_function.h @@ -265,7 +265,7 @@ Function* isosurface(std::string name, FT t) { return new DoubleGyroid(t); } else if (name == "lidinoid") { - return new Lidinoid(); + return new Lidinoid(t); } else if (name == "neovius") { return new Neovius(t); diff --git a/src/Main.cpp b/src/Main.cpp index 6df75f3..c5c3cda 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -11,10 +11,10 @@ int main(int argc, char* argv[]) bool is_export_inverse = false; bool is_build_inverse = true; bool no_output = false; - uint16_t grid_offset = 2; + uint16_t grid_offset = 3; uint16_t shell = 0; uint16_t smooth_step = 5; - double thickness = 0.5; + double thickness = 0.0; size_t minimum_grid_size = 100; double coff = pi; double minimum_diameter = 0.25; @@ -41,9 +41,9 @@ int main(int argc, char* argv[]) ("c,coff", "default:4*PI", cxxopts::value(), "DOUBLE") ("s,shell", "[default:0]", cxxopts::value(), "INT") ("n,surface", "rectlinear, schwarzp, schwarzd, gyroid, double-p, double-d, double-gyroiod, lidinoid, schoen_iwp, neovius, bcc, tubular_g_ab, tubular_g_c [default: schwarzp]", cxxopts::value(), "NAME") - ("t,thickness", "Thickness [default: 1.0]", cxxopts::value(), "DOUBLE") + ("t,thickness", "Thickness [default: 0]", cxxopts::value(), "DOUBLE") ("g,grid_size", "Grid size [default: 100]", cxxopts::value(), "INT") - ("grid_offset", "[default:2]", cxxopts::value(), "INT") + ("grid_offset", "[default:3]", cxxopts::value(), "INT") ("smooth_step", "Smooth with laplacian (default: 5)", cxxopts::value(), "INT") ("output_inverse", "additional output inverse scaffold") ("inverse", "Enable build inverse 3D scaffold (for pore connectivity analysis)") @@ -133,9 +133,9 @@ int main(int argc, char* argv[]) << "-- Export Inverse: " << (is_export_inverse ? "True" : "False") << std::endl; } else { - std::string _name(filename); - _name.append(".txt"); - result.open(_name.c_str(), std::ofstream::out); + std::stringstream _name; + _name << filename << '_' << surface << std::time(nullptr) << ".txt"; + result.open(_name.str(), std::ofstream::out); // Print header result << "Surface,coff,shell,thickness,grid_size,grid_offset,smooth_step,input_file,avg_min_feret,avg_max_feret," << "min_min_feret,q1_min_feret,q2_min_feret,q3_min_feret,max_min_feret," @@ -145,7 +145,8 @@ int main(int argc, char* argv[]) << "min_circle,q1_circle,q2_circle,q3_circle,max_circle," << "min_triangle,q1_triangle,q2_triangle,q3_triangle,max_triangle," << "min_ellipse,q1_ellipse,q2_ellipse,q3_ellipse,max_ellipse," - << "min_elongation,q1_elongation,q2_elongation,q3_elongation,max_elongation" << std::endl; + << "min_elongation,q1_elongation,q2_elongation,q3_elongation,max_elongation," + << "volumn,surface_area,porosity,surface_area_ratio,vertices,faces" << std::endl; result << surface << ',' << coff << ',' << shell << ',' << thickness << ',' << minimum_grid_size << ',' << grid_offset << ',' << smooth_step << ',' << input_file << ','; } @@ -414,14 +415,14 @@ int main(int argc, char* argv[]) } progress.done(); if (minFeret.size() > 0 && maxFeret.size() > 0) { + std::sort(minFeret.begin(), minFeret.end()); + std::sort(maxFeret.begin(), maxFeret.end()); + std::sort(podczeckShapes[0].begin(), podczeckShapes[0].end()); + std::sort(podczeckShapes[1].begin(), podczeckShapes[1].end()); + std::sort(podczeckShapes[2].begin(), podczeckShapes[2].end()); + std::sort(podczeckShapes[3].begin(), podczeckShapes[3].end()); + std::sort(podczeckShapes[4].begin(), podczeckShapes[4].end()); if (verbose) { - std::sort(minFeret.begin(), minFeret.end()); - std::sort(maxFeret.begin(), maxFeret.end()); - std::sort(podczeckShapes[0].begin(), podczeckShapes[0].end()); - std::sort(podczeckShapes[1].begin(), podczeckShapes[1].end()); - std::sort(podczeckShapes[2].begin(), podczeckShapes[2].end()); - std::sort(podczeckShapes[3].begin(), podczeckShapes[3].end()); - std::sort(podczeckShapes[4].begin(), podczeckShapes[4].end()); std::cout << "[Microstructure] " << std::endl << "-- Avg Min Feret: " << std::accumulate(minFeret.begin(), minFeret.end(), 0.0) / minFeret.size() << std::endl << "-- Avg Max Feret: " << std::accumulate(maxFeret.begin(), maxFeret.end(), 0.0) / maxFeret.size() << std::endl @@ -445,6 +446,16 @@ int main(int argc, char* argv[]) << podczeckShapes[4].at(0) << ',' << podczeckShapes[4].at(podczeckShapes[4].size() * 0.25) << ',' << podczeckShapes[4].at(podczeckShapes[4].size() / 2) << ',' << podczeckShapes[4].at(podczeckShapes[4].size() * 0.75) << ',' << podczeckShapes[4].at(podczeckShapes[4].size() - 1) << ','; } } + else { + result << "0,0," + << "0,0,0,0,0," + << "0,0,0,0,0," + << "0,0,0,0,0," + << "0,0,0,0,0," + << "0,0,0,0,0," + << "0,0,0,0,0," + << "0,0,0,0,0,"; + } } } catch (const std::exception& ex) { @@ -471,7 +482,9 @@ int main(int argc, char* argv[]) if (verbose) std::cout << "[Pore connectivity]" << std::endl << "-- #Edge Border: " << edgeBorderNum << std::endl - << "-- #Edge Non-manifold: " << edgeNonManifNum << std::endl; + << "-- #Edge Non-manifold: " << edgeNonManifNum << std::endl + << "-- Vertices: " << inverse_mesh.VN() << std::endl + << "-- Faces: " << inverse_mesh.FN() << std::endl; bool watertight = (edgeBorderNum == 0) && (edgeNonManifNum == 0); bool pointcloud = (mesh.fn == 0 && mesh.vn != 0); if (!watertight || pointcloud) std::cout << "[Warning] Pore isn't conencted" << std::endl; @@ -485,7 +498,7 @@ int main(int argc, char* argv[]) area2 = vcg::tri::Stat::ComputeMeshArea(mesh); volume2 = vcg::tri::Stat::ComputeMeshVolume(mesh); if (verbose) - std::cout + std::cout << "[Scaffold properties]" << "-- Volume: " << abs(volume2) << std::endl << "-- Surface Area: " << area2 << std::endl << "-- Porosity: " << abs(volume2 / volume1) << std::endl @@ -495,6 +508,12 @@ int main(int argc, char* argv[]) << abs(volume2) << ',' << area2 << ',' << abs(volume2 / volume1) << ',' << area2 / area1 << ',' << mesh.VN() << ',' << mesh.FN() << std::endl; } + else { + if (!verbose) { + result << ",,,," << mesh.VN() << ',' << mesh.FN() << std::endl; + } + std::cout << "[Warning] The scaffolder isn't a manifold. The grid_offset should have been increased" << std::endl; + } if (!no_output) { if (verbose) std::cout << "[Writing file] "; From fb926a9228e0d46d8fb5800aebcf8d2f35741a83 Mon Sep 17 00:00:00 2001 From: Jirawat I Date: Sun, 1 Mar 2020 02:34:36 +0700 Subject: [PATCH 03/20] add slice algorithm --- .gitmodules | 3 + CMakeLists.txt | 28 +- cmake/FindTBB.cmake | 32 ++ cmake/update_deps_file.cmake | 27 ++ external/diplib | 2 +- external/libigl | 2 +- external/tbb | 1 + include/Mesh.h | 29 +- include/OptimalSlice.hpp | 576 +++++++++++++++++++++++++++++++++++ include/Scaffolder_2.h | 68 +---- include/implicit_function.h | 2 +- include/utils.h | 63 ++++ src/Main.cpp | 40 +-- src/SliceTest.cpp | 119 ++++++++ 14 files changed, 887 insertions(+), 105 deletions(-) create mode 100644 cmake/FindTBB.cmake create mode 100644 cmake/update_deps_file.cmake create mode 160000 external/tbb create mode 100644 include/OptimalSlice.hpp create mode 100644 include/utils.h create mode 100644 src/SliceTest.cpp diff --git a/.gitmodules b/.gitmodules index c53f5d5..b3f829e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -9,3 +9,6 @@ path = external/vcglib url = https://github.com/cnr-isti-vclab/vcglib branch = master +[submodule "external/tbb"] + path = external/tbb + url = https://github.com/wjakob/tbb diff --git a/CMakeLists.txt b/CMakeLists.txt index fbb4af0..5ed19ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,18 +6,27 @@ set(CGAL_DO_NOT_WARN_ABOUT_CMAKE_BUILD_TYPE ON) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) # libigl -option(LIBIGL_WITH_OPENGL "Use OpenGL" ON) -option(LIBIGL_WITH_OPENGL_GLFW "Use GLFW" ON) +option(LIBIGL_WITH_OPENGL "Use OpenGL" OFF) +option(LIBIGL_WITH_OPENGL_GLFW "Use GLFW" OFF) find_package(LIBIGL REQUIRED) find_package(VCG REQUIRED) -find_package(DIPlib REQUIRED) +#find_package(DIPlib REQUIRED) +find_package(TBB REQUIRED) #find_package(HDF5 CONFIG REQUIRED) #find_package(HighFive REQUIRED) +#find_package(OpenMP) +#if (OpenMP_FOUND) +# set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") +# set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") +# set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}") +# if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") +# string(REPLACE "-openmp" "-openmp:experimental" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) +# endif() +#endif() # Add your project files file(GLOB MAIN_SOURCES - "${PROJECT_SOURCE_DIR}/src/**.cpp" "${PROJECT_SOURCE_DIR}/include/*.c" "${PROJECT_SOURCE_DIR}/include/*.cpp" "${PROJECT_SOURCE_DIR}/include/*.tpp" @@ -29,9 +38,14 @@ file(GLOB MAIN_SOURCES "${PROJECT_SOURCE_DIR}/include/*/*.h" "${PROJECT_SOURCE_DIR}/include/*/*.hpp" ) +include(update_deps_file) update_deps_file("main_sources" "${MAIN_SOURCES}") add_definitions(-DNOMINMAX -D_USE_MATH_DEFINES -DDIP__IS_STATIC -DDIP__ENABLE_ASSERT -DDIP__HAS_JPEG -DDIP__EXCEPTIONS_RECORD_STACK_TRACE) -add_executable(${PROJECT_NAME} ${MAIN_SOURCES} ${VCG_INCLUDE_DIR}/wrap/ply/plylib.cpp) -target_include_directories(${PROJECT_NAME} PRIVATE "${PROJECT_SOURCE_DIR}/include" ${VCG_INCLUDE_DIR} ${DIPlib_INCLUDE_DIR}) -target_link_libraries(${PROJECT_NAME} PRIVATE igl::core DIP) +#add_executable(${PROJECT_NAME} ${MAIN_SOURCES} ${PROJECT_SOURCE_DIR}/src/Main.cpp ${VCG_INCLUDE_DIR}/wrap/ply/plylib.cpp) +#target_include_directories(${PROJECT_NAME} PRIVATE "${PROJECT_SOURCE_DIR}/include" ${VCG_INCLUDE_DIR} ${DIPlib_INCLUDE_DIR}) +#target_link_libraries(${PROJECT_NAME} PRIVATE igl::core DIP) + +add_executable(SliceTest ${MAIN_SOURCES} ${PROJECT_SOURCE_DIR}/src/SliceTest.cpp) +target_include_directories(SliceTest PRIVATE "${PROJECT_SOURCE_DIR}/include" ${VCG_INCLUDE_DIR} ${TBB_INCLUDE_DIR}) +target_link_libraries(SliceTest PRIVATE igl::core tbb_static) diff --git a/cmake/FindTBB.cmake b/cmake/FindTBB.cmake new file mode 100644 index 0000000..0c67a92 --- /dev/null +++ b/cmake/FindTBB.cmake @@ -0,0 +1,32 @@ +# - Try to find the TBB library +# Once done this will define +# +# TBB_FOUND - system has TBB +# TBB_INCLUDE_DIR - **the** TBB include directory +if(TBB_FOUND) + return() +endif() + +find_path(TBB_INCLUDE_DIR tbb/tbb.h + HINTS + ${TBB_DIR} + ENV TBB_DIR + PATHS + ${CMAKE_SOURCE_DIR}/external/tbb + PATH_SUFFIXES include +) +set(TBB_ROOT_DIR ${TBB_INCLUDE_DIR}/../) + +set(TBB_BUILD_SHARED OFF) +set(TBB_BUILD_STATIC ON) +set(TBB_BUILD_TESTS OFF) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(TBB + "\nTBB not found --- You can download it using:\n\tgit clone https://github.com/TBB/TBB ${CMAKE_SOURCE_DIR}/../TBB" + TBB_INCLUDE_DIR) +mark_as_advanced(TBB_INCLUDE_DIR) + +list(APPEND CMAKE_MODULE_PATH "${TBB_ROOT_DIR}") +message(STATUS "USE DIR: ${TBB_ROOT_DIR}") +add_subdirectory(${TBB_ROOT_DIR}) \ No newline at end of file diff --git a/cmake/update_deps_file.cmake b/cmake/update_deps_file.cmake new file mode 100644 index 0000000..3d40d02 --- /dev/null +++ b/cmake/update_deps_file.cmake @@ -0,0 +1,27 @@ +# Utility for automatically updating targets when a file is added to the source directories +# from: https://stackoverflow.com/a/39971448/7328782, but modified +# Creates a file called ${name} with the list of dependencies in it. +# The file is updated when the list of dependencies changes. +# If the file is updated, cmake will automatically reload. +function(update_deps_file name deps) + # Normalize the list so it's the same on every machine + list(REMOVE_DUPLICATES deps) + foreach(dep IN LISTS deps) + file(RELATIVE_PATH rel_dep "${CMAKE_CURRENT_SOURCE_DIR}" ${dep}) + list(APPEND rel_deps ${rel_dep}) + endforeach(dep) + list(SORT rel_deps) + # Split the list into lines, and add some CMake-valid syntax so it's ignored + string(REPLACE ";" "\n" new_deps "${rel_deps}") + set(new_deps "# Automatically generated, don't edit!\nset(${name}_bogus\n${new_deps}\n)\n") + # Compare with the old file + set(old_deps "") + if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${name}.cmake") + file(READ "${CMAKE_CURRENT_SOURCE_DIR}/${name}.cmake" old_deps) + endif() + if(NOT old_deps STREQUAL new_deps) + file(WRITE "${CMAKE_CURRENT_SOURCE_DIR}/${name}.cmake" "${new_deps}") + endif() + # Include the file so it's tracked as a generation dependency (we don't need the content). + include("${CMAKE_CURRENT_SOURCE_DIR}/${name}.cmake") +endfunction(update_deps_file) diff --git a/external/diplib b/external/diplib index 1918cbc..51be593 160000 --- a/external/diplib +++ b/external/diplib @@ -1 +1 @@ -Subproject commit 1918cbcb89feac292689d02ecc2ab99326771851 +Subproject commit 51be5930cb7818c5d0f8ad18db815f9649fc6ae0 diff --git a/external/libigl b/external/libigl index 56f129e..26053f6 160000 --- a/external/libigl +++ b/external/libigl @@ -1 +1 @@ -Subproject commit 56f129e4403d3b7b04ddd786745fd3d574e95e04 +Subproject commit 26053f696cd2ec01f819ff353392e4201742fc97 diff --git a/external/tbb b/external/tbb new file mode 160000 index 0000000..20357d8 --- /dev/null +++ b/external/tbb @@ -0,0 +1 @@ +Subproject commit 20357d83871e4cb93b2c724fe0c337cd999fd14f diff --git a/include/Mesh.h b/include/Mesh.h index eebcf81..9e8bf73 100644 --- a/include/Mesh.h +++ b/include/Mesh.h @@ -1,8 +1,11 @@ +#ifndef SCAFFOLD_TMESH_H +#define SCAFFOLD_TMESH_H #include #include #include #include #include +#include class TFace; class TVertex; @@ -24,4 +27,28 @@ class TFace : public vcg::Face {}; /* the mesh is a container of vertices and a container of faces */ -class TMesh : public vcg::tri::TriMesh< vector, vector > {}; \ No newline at end of file +class TMesh : public vcg::tri::TriMesh< vector, vector > {}; + +inline void mesh_to_eigen_vector(TMesh& mesh, Eigen::MatrixXd& V, Eigen::MatrixXi& F) { + V.resize(mesh.VN(), 3); + size_t i = 0; + std::vector vertexId(mesh.vert.size()); + for (TMesh::VertexIterator it = mesh.vert.begin(); it != mesh.vert.end(); ++it) if (!it->IsD()) { + vertexId[it - mesh.vert.begin()] = i; + vcg::Point3d point = it->P(); + V(i, 0) = point[0]; + V(i, 1) = point[1]; + V(i, 2) = point[2]; + i++; + } + // Faces to Eigen matrixXi F1 + i = 0; + F.resize(mesh.FN(), mesh.face.begin()->VN()); + for (TMesh::FaceIterator it = mesh.face.begin(); it != mesh.face.end(); ++it) if (!it->IsD()) { + for (int k = 0; k < it->VN(); k++) { + F(i, k) = vertexId[vcg::tri::Index(mesh, it->V(k))]; + } + i++; + } +} +#endif \ No newline at end of file diff --git a/include/OptimalSlice.hpp b/include/OptimalSlice.hpp new file mode 100644 index 0000000..0d94ed0 --- /dev/null +++ b/include/OptimalSlice.hpp @@ -0,0 +1,576 @@ +/* + * This is the implement slice algorithm from Rodrigo, 2017 (An Optimal Algorithm for 3D Triangle Mesh Slicing) + * which is claimed to be faster than slic3r and CGAL method. + */ +#ifndef OPTIMAL_SLICE_HPP +#define OPTIMAL_SLICE_HPP + +#ifndef SLICE_PRECISION +// There's a problem of double hashing with the precision less than 1e-8 (e.g. 1e-10) +// when performed contour constructing +#define SLICE_PRECISION 1e-8 +#endif +#define DOUBLE_EQ(x,y) (abs(x - y) < SLICE_PRECISION) +#define DOUBLE_GT(x,y) ((x - y) > SLICE_PRECISION) +#define DOUBLE_LT(x,y) ((y - x) > SLICE_PRECISION) +#define USE_PARALLEL + +#include "Mesh.h" +#include "tbb/tbb.h" + +namespace slice { + + enum Direction {X = 0,Y,Z}; + enum ContourPosition {OUTSIDE = 0, INSIDE}; + + struct Point2d { + double x; + double y; + bool operator==(const Point2d& ls) const { + return (DOUBLE_EQ(x, ls.x) && DOUBLE_EQ(y, ls.y)); + } + bool operator< (const Point2d& ls) const { + if (x < ls.x) return true; + return y < ls.y; + } + }; + + struct Point3d { + double x; + double y; + double z; + bool operator==(const Point3d& ls) const { + return (DOUBLE_EQ(x, ls.x) && DOUBLE_EQ(y, ls.y) && DOUBLE_EQ(z, ls.z)); + } + bool operator< (const Point3d& ls) const { + if (x < ls.x) return true; + else if DOUBLE_EQ(x, ls.x) + if (y < ls.y) return true; + else if DOUBLE_EQ(y, ls.y) return (z < ls.z); + return false; + } + }; + + Point2d make_point2d(Point3d p) { + return Point2d{ p.x, p.y }; + } + + class Triangle { + public: + Point3d v[3]; + double min[3], max[3]; + Triangle(TMesh::FaceType face) { + assert(face.VN() == 3); + vcg::Point3d point = face.P(0); + v[0].x = point[0]; + v[0].y = point[1]; + v[0].z = point[2]; + point = face.P(1); + v[1].x = point[0]; + v[1].y = point[1]; + v[1].z = point[2]; + point = face.P(2); + v[2].x = point[0]; + v[2].y = point[1]; + v[2].z = point[2]; + updateMinMax(); + } + void updateMinMax() { + min[0] = std::min({ v[0].x, v[1].x, v[2].x }); + min[1] = std::min({ v[0].y, v[1].y, v[2].y }); + min[2] = std::min({ v[0].z, v[1].z, v[2].z }); + max[0] = std::max({ v[0].x, v[1].x, v[2].x }); + max[1] = std::max({ v[0].y, v[1].y, v[2].y }); + max[2] = std::max({ v[0].z, v[1].z, v[2].z }); + } + + double minOf(int i, int j, int direction) { + assert(direction >= 0 && direction <= 2); + assert(i >= 0 && i <= 2); + assert(j >= 0 && j <= 2); + if (direction == 0) + return v[i].x < v[j].x ? v[i].x : v[j].x; + else if (direction == 1) + return v[i].y < v[j].y ? v[i].y : v[j].y; + else + return v[i].z < v[j].z ? v[i].z : v[j].z; + } + double maxOf(int i, int j, int direction) { + assert(direction >= 0 && direction <= 2); + assert(i >= 0 && i <= 2); + assert(j >= 0 && j <= 2); + if (direction == 0) + return v[i].x < v[j].x ? v[j].x : v[i].x; + else if (direction == 1) + return v[i].y < v[j].y ? v[j].y : v[i].y; + else + return v[i].z < v[j].z ? v[j].z : v[i].z; + } + + bool operator== (const Triangle& ls) const { + return (v[0] == ls.v[0]) && (v[1] == ls.v[1]) && (v[2] == ls.v[2]); + } + + bool operator< (const Triangle& ls) const { + if (v[0] < ls.v[0]) return true; + else if (v[0] == ls.v[0]) + if (v[1] < ls.v[1]) return true; + else if (v[1] == ls.v[1]) return (v[2] < ls.v[2]); + return false; + } + }; + + class Line { + public: + size_t index; + Point3d v[2]; + + Line() {} + + Line(Point3d v0, Point3d v1, size_t index) : index(index) { + v[0] = v0; + v[1] = v1; + sort(); + } + + void sort() { + if (v[1] < v[0]) std::swap(v[0], v[1]); + } + + bool operator== (const Line& ls) const { + return (v[0] == ls.v[0]) && (v[1] == ls.v[1]); + } + + bool operator< (const Line& ls) const { + if (v[0] < ls.v[0]) return true; + else if (v[0] == ls.v[0]) return (v[1] < ls.v[1]); + return false; + } + }; + + typedef std::vector Plane; + typedef std::vector Triangles; + typedef std::vector Layer; + typedef std::vector Lines; + typedef std::vector Slice; + typedef std::vector Contour; + typedef std::vector Contours; + typedef std::vector ContourSlice; + typedef std::pair PairPoint2d; + typedef std::unordered_map ContourHash; + typedef std::vector ContourPositions; + + std::ostream& operator<< (std::ostream& out, Point2d const& data) { + out << "[" << data.x << "," << data.y << "]"; + return out; + } + + std::ostream& operator<< (std::ostream& out, PairPoint2d const& data) { + out << "(" << data.first << " " << data.second << ")"; + return out; + } + + std::ostream& operator<< (std::ostream& out, Point3d const& data) { + out << "[" << data.x << "," << data.y << "," << data.z << "]"; + return out; + } + + std::ostream& operator<< (std::ostream& out, Triangle const& data) { + out << "slice::Triangle(" << data.v[0] << " " << data.v[1] << " " << data.v[2] << ")"; + return out; + } + + std::ostream& operator<< (std::ostream& out, Line const& data) { + out << "slice::Line(" << data.v[0] << " " << data.v[1] << ")"; + return out; + } +} + +namespace std { + template<> struct hash { + size_t operator()(const slice::Point2d& p) const noexcept { + size_t x = hash()(llround(p.x/SLICE_PRECISION)); + size_t y = hash()(llround(p.y/SLICE_PRECISION)); + return x ^ (y << 1); + } + }; +} + +namespace slice { + + inline void build_triangle_list(TMesh& mesh, size_t grid_size, Plane& P, Layer& L, int direction = Direction::Z) { + // Uniform slicing with delta > 0 + // in this case, grid_size = k from Rodrigo paper + assert(grid_size > 1 && direction <= 2 && direction >= 0); + vcg::tri::UpdateBounding::Box(mesh); + vcg::Box3d bbox = mesh.bbox; + double minBBox = bbox.min[direction]; + double maxBBox = bbox.max[direction]; + vcg::Point3d dim = bbox.Dim(); + double delta = dim[direction] / (grid_size - 1); + // build Plane vector P[0...k+1] + P.resize(grid_size + 2); + P[0] = minBBox - 10 * delta; + P[1] = minBBox; + P[grid_size + 1] = maxBBox + 10 * delta; + for (size_t i = 2; i <= grid_size; i++) + P[i] = P[i - 1] + delta; + // initialize layer L[0...k+1] + L.resize(grid_size + 2); + for (size_t i = 0; i <= grid_size + 1; i++) L[i].clear(); + // foreach triangle in mesh + // TODO: implement parallel version with tbb + for (TMesh::FaceIterator it = mesh.face.begin(); it != mesh.face.end(); it++) { + if (!it->IsD()) + { + Triangle triangle(*it); + size_t i = 0; + i = size_t(ceil((triangle.min[direction] - P[1]) / delta) + 1); + assert(i > 0 && i <= grid_size + 1); + L[i].push_back(triangle); + } + } + } + + inline Point3d compute_point_at_plane(Point3d v0, Point3d v1, double position, int direction = Direction::Z) { + double dx = v1.x - v0.x; + double dy = v1.y - v0.y; + double dz = v1.z - v0.z; + if (direction == 2) { + assert(dz != 0); + double frac = (position - v0.z) / dz; + double x = frac * dx + v0.x; + double y = frac * dy + v0.y; + return Point3d{ x, y, position }; + } + else if (direction == 1) { + assert(dy != 0); + double frac = (position - v0.y) / dy; + double x = frac * dx + v0.x; + double z = frac * dz + v0.z; + return Point3d{ x, position, z }; + } + else { + assert(dx != 0); + double frac = (position - v0.x) / dx; + double y = frac * dy + v0.y; + double z = frac * dz + v0.z; + return Point3d{ position, y, z }; + } + } + + // Modified version of Rodrigo (2017) and Adnan's slicing algorithm (2018) + // (Real-time slicing algorithm for Stereolithography (STL) CAD model applied in additive manufacturing industry) + // The ill-conditioned case will be cured + bool compute_intersection(Triangle t, double position, Line& L, int direction = Direction::Z) { + assert(direction >= 0 && direction <= 2); + assert(t.min[direction] <= position && t.max[direction] >= position); + int np = 0; // number of endpoints on the plane + std::vector found_indexs; + found_indexs.reserve(3); + for (int i = 0; i < 3; i++) { + if ((direction == Direction::X && DOUBLE_EQ(t.v[i].x, position)) || + (direction == Direction::Y && DOUBLE_EQ(t.v[i].y, position)) || + (direction == Direction::Z && DOUBLE_EQ(t.v[i].z, position))) { + np++; + found_indexs.push_back(i); + } + } + if (np == 0) { + int k = 0; + for (int i = 0; i < 3; i++) { + int next_i = (i == 2) ? 0 : i + 1; + double min = t.minOf(i, next_i, direction); + double max = t.maxOf(i, next_i, direction); + if (min <= position && max >= position) { + assert(k < 2); + L.v[k] = compute_point_at_plane(t.v[i], t.v[next_i], position, direction); + k++; + } + } + assert(k == 2); + L.sort(); + return true; + } + else if (np == 1 && DOUBLE_GT(t.max[direction], position) && DOUBLE_LT(t.min[direction], position)) { + assert(found_indexs.size() == 1); + int i = (found_indexs[0] + 1) % 3; + int next_i = (i + 1) % 3; + L.v[0] = t.v[found_indexs[0]]; + L.v[1] = compute_point_at_plane(t.v[i], t.v[next_i], position, direction); + L.sort(); + return true; + } + else if (np == 2) { + assert(found_indexs.size() == 2); + L.v[0] = t.v[found_indexs[0]]; + L.v[1] = t.v[found_indexs[1]]; + L.sort(); + return true; + } + return false; + } + + Slice incremental_slicing(TMesh& mesh, size_t grid_size, int direction = Direction::Z) { + slice::Plane P; + slice::Layer L; + slice::build_triangle_list(mesh, grid_size, P, L); + Slice S(grid_size); + + Triangles A; + for (size_t i = 1; i <= grid_size; i++) { + if (L[i].size() > 0) { + A.reserve(A.size() + L[i].size()); + A.insert(A.end(), L[i].begin(), L[i].end()); + } + S[i - 1].clear(); +#ifndef USE_PARALLEL + for (Triangles::iterator t = A.begin(); t != A.end();) { + if (t->max[direction] < P[i]) { + t = A.erase(t); + } + else { + Line line; + if (t->max[direction] >= P[i] && t->min[direction] <= P[i]) { + if (compute_intersection(*t, P[i], line, direction)) { + S[i - 1].push_back(line); + } + } + t++; + } + } +#else + tbb::spin_mutex concatMutex; + tbb::spin_mutex writeMutex; + static tbb::affinity_partitioner ap; + std::vector deleteIndex; + deleteIndex.clear(); + + Triangles new_A; + new_A.reserve(A.size()); + tbb::parallel_for( + tbb::blocked_range(0, A.size()), + [&](const tbb::blocked_range &r) + { + Triangles local_A; + std::copy(A.begin() + r.begin(), A.begin() + r.end(), std::back_inserter(local_A)); + for (Triangles::iterator t = local_A.begin(); t != local_A.end();) { + if (t->max[direction] < P[i]) { + t = local_A.erase(t); + } + else { + Line line; + if (t->max[direction] >= P[i] && t->min[direction] <= P[i]) { + if (compute_intersection(*t, P[i], line, direction)) { + tbb::spin_mutex::scoped_lock lock(writeMutex); + S[i - 1].push_back(line); + } + } + t++; + } + } + if (local_A.size() > 0) { + tbb::spin_mutex::scoped_lock lock(concatMutex); + new_A.insert(new_A.end(), local_A.begin(), local_A.end()); + } + }, + ap + ); + A = new_A; +#endif + } + return S; + } + +#ifndef USE_PARALLEL + ContourSlice contour_construct(Slice const& S) { + ContourSlice CS(S.size()); + ContourHash hash; + for (size_t i = 0, len = S.size(); i < len; i++) { + CS[i].clear(); + hash.clear(); + hash.reserve(S[i].size() + 1); + for (Lines::const_iterator l = S[i].begin(); l != S[i].end(); l++) { + Point2d u = make_point2d(l->v[0]); + Point2d v = make_point2d(l->v[1]); + ContourHash::iterator item = hash.find(u); + if (item == hash.end()) + hash.emplace(u, make_pair(v, v)); + else { + Point2d w = item->second.first; + Point2d target = item->second.second; + if (w == target) { + item->second = make_pair(w, v); + } + } + item = hash.find(v); + if (item == hash.end()) + hash.emplace(v, make_pair(u, u)); + else { + Point2d w = item->second.first; + Point2d target = item->second.second; + if (w == target) { + item->second = make_pair(w, u); + } + } + } + //std::cout << " [Hash OK: " << hash.size() << "]"; + /* TODO: remove this debug message + std::cout << "Hash Slice " << i << std::endl; + for (ContourHash::const_iterator item = hash.begin(); item != hash.end(); item++) { + std::cout << " " << item->first << ": " << item->second << (item->second.first == item->second.second ? " [***]" : "") << std::endl; + //std::cout << " " << item->first << ": " << std::hash()(item->first) << std::endl; + } + //*/ + while (!hash.empty()) { + ContourHash::const_iterator item = hash.begin(); + assert(item != hash.end()); + Contour C; + C.push_back(item->first); + C.push_back(item->second.first); + Point2d last = item->second.second; + hash.erase(item); + for (size_t j = 1;; j++) { + item = hash.find(C[j]); + //if (item == hash.end()) break; + assert(item != hash.end()); + if (!(C[j] == last)) { + if (item->second.first == C[j - 1]) + C.push_back(item->second.second); + else + C.push_back(item->second.first); + } + hash.erase(item); + if (C[j] == last) break; + } + CS[i].push_back(C); + } + } + return CS; + } +#else + ContourSlice contour_construct(Slice const& S) { + ContourSlice CS(S.size()); + static tbb::affinity_partitioner ap; + tbb::spin_mutex printMutex; + tbb::parallel_for( + tbb::blocked_range(0, S.size()), + [&](const tbb::blocked_range& r) { + for (size_t i = r.begin(); i < r.end(); i++) { + CS[i].clear(); + ContourHash hash; + hash.clear(); + hash.reserve(S[i].size() + 1); + for (Lines::const_iterator l = S[i].begin(); l != S[i].end(); l++) { + Point2d u = make_point2d(l->v[0]); + Point2d v = make_point2d(l->v[1]); + ContourHash::iterator item = hash.find(u); + if (item == hash.end()) + hash.emplace(u, make_pair(v, v)); + else { + Point2d w = item->second.first; + Point2d target = item->second.second; + if (w == target) { + item->second = make_pair(w, v); + } + } + item = hash.find(v); + if (item == hash.end()) + hash.emplace(v, make_pair(u, u)); + else { + Point2d w = item->second.first; + Point2d target = item->second.second; + if (w == target) { + item->second = make_pair(w, u); + } + } + } + while (!hash.empty()) { + ContourHash::const_iterator item = hash.begin(); + assert(item != hash.end()); + Contour C; + C.push_back(item->first); + C.push_back(item->second.first); + Point2d last = item->second.second; + hash.erase(item); + for (size_t j = 1;; j++) { + item = hash.find(C[j]); + assert(item != hash.end()); + if (!(C[j] == last)) { + if (item->second.first == C[j - 1]) + C.push_back(item->second.second); + else + C.push_back(item->second.first); + } + hash.erase(item); + if (C[j] == last) break; + } + CS[i].push_back(C); + } + } + }, + ap + ); + return CS; + } +#endif + // Finley DR (2007) Point-in-polygon algorithm + // — determining whether a point is inside a complex polygon. + // Available at: http://alienryderflex.com/polygon/. + // return true = inside, false = outside + bool is_point_inside_polygon(const Point2d &p, const Contour &C) { + bool oddNodes = 0; + size_t size = C.size(); + for (size_t i = 0; i < size; i++) { + size_t j = (i == size - 1) ? 0 : i + 1; + if ((C[i].y < p.y && C[j].y >= p.y) || (C[j].y < p.y && C[i].y >= p.y) && (C[i].x <= p.x || C[j].x <= p.x)) { + oddNodes ^= (((p.y - C[i].y) / (C[j].y - C[i].y) * (C[j].x - C[i].x) + C[i].x) < p.x); + } + } + return oddNodes; + } + + ContourPositions contour_inside_test(const Contours& C) { + ContourPositions position(C.size()); + for (size_t i = 0, size = C.size(); i < size; i++) { + ContourPosition pos = ContourPosition::OUTSIDE; + for (size_t j = 0, size = C.size(); j < size; j++) { + if (i == j) continue; + if (is_point_inside_polygon(C[i][0], C[j])) { + pos = ContourPosition::INSIDE; + break; + } + } + position[i] = pos; + } + return position; + } +} + +namespace slice { + bool write_svg(std::string filename, const Contours &C, const int width, const int height, const int min_x, const int min_y) { + if (C.empty()) return false; + ContourPositions p = contour_inside_test(C); + assert(p.size() == C.size()); + std::ofstream svg(filename, 'r'); + svg << "" << std::endl; + svg << "" << std::endl; + svg << "" << std::endl; + for (Contours::const_iterator t = C.begin(); t != C.end(); t++) { + if (t->size() < 2) continue; + size_t index = (size_t)(t - C.begin()); + bool is_inside = (p[index] == ContourPosition::INSIDE); + Contour::const_iterator c = t->begin(); + svg << "x << "," << c->y << " L"; + c++; + for (; c != t->end(); c++) { + svg << c->x << "," << c->y << " "; + } + svg << "z\" fill=\"transparent\" stroke=\"#" << (is_inside ? "f00" : "000") << "\" stroke-width=\"0.1\" stroke-linejoin=\"round\"/>" << std::endl; + } + svg << "" << std::endl; + svg.close(); + return true; + } +} +#endif \ No newline at end of file diff --git a/include/Scaffolder_2.h b/include/Scaffolder_2.h index bcb36f8..c53e1bb 100644 --- a/include/Scaffolder_2.h +++ b/include/Scaffolder_2.h @@ -1,3 +1,4 @@ +#pragma once #include #include #include @@ -19,17 +20,7 @@ #include "implicit_function.h" #include "Mesh.h" - -#ifdef _WIN32 -#include -#include -#else -#include -#include -#include -#include -#include -#endif +#include "utils.h" #define VERSION "v1.2" #define PROGRESS_BAR_COLUMN 40 @@ -134,10 +125,6 @@ inline void clean_mesh(TMesh& mesh, double minimum_diameter, uint16_t smooth_ste vcg::tri::Clean::RemoveSmallConnectedComponentsDiameter(mesh, minimum_diameter * mesh.bbox.Diag()); vcg::tri::Clean::RemoveUnreferencedVertex(mesh); vcg::tri::UpdateTopology::FaceFace(mesh); - if (mesh.fn > 0) { - vcg::tri::UpdateNormal::PerFaceNormalized(mesh); - vcg::tri::UpdateNormal::PerVertexAngleWeighted(mesh); - } if (verbose) { std::cout << "OK" << std::endl @@ -149,55 +136,4 @@ inline void clean_mesh(TMesh& mesh, double minimum_diameter, uint16_t smooth_ste vcg::tri::Smooth::VertexCoordLaplacian(mesh, smooth_step, false, true); if (verbose) std::cout << "OK" << std::endl; } -} - -int make_dir(std::string& str) { - if (str.empty()) - return 0; -#ifdef _WIN32 - return _mkdir(str.c_str()); -#else - return mkdir(str.c_str(), 0733); -#endif -} - -/* Remove if already defined */ -typedef long long int64; typedef unsigned long long uint64; - -/* Returns the amount of milliseconds elapsed since the UNIX epoch. Works on both - * windows and linux. */ - -uint64 GetTimeMs64() -{ -#ifdef _WIN32 - /* Windows */ - FILETIME ft; - LARGE_INTEGER li; - - /* Get the amount of 100 nano seconds intervals elapsed since January 1, 1601 (UTC) and copy it - * to a LARGE_INTEGER structure. */ - GetSystemTimeAsFileTime(&ft); - li.LowPart = ft.dwLowDateTime; - li.HighPart = ft.dwHighDateTime; - - uint64 ret = li.QuadPart; - ret -= 116444736000000000LL; /* Convert from file time to UNIX epoch time. */ - ret /= 10000; /* From 100 nano seconds (10^-7) to 1 millisecond (10^-3) intervals */ - - return ret; -#else - /* Linux */ - struct timeval tv; - - gettimeofday(&tv, NULL); - - uint64 ret = tv.tv_usec; - /* Convert from micro seconds (10^-6) to milliseconds (10^-3) */ - ret /= 1000; - - /* Adds the seconds (10^0) after converting them to milliseconds (10^-3) */ - ret += (tv.tv_sec * 1000); - - return ret; -#endif } \ No newline at end of file diff --git a/include/implicit_function.h b/include/implicit_function.h index dd71f48..70cca28 100644 --- a/include/implicit_function.h +++ b/include/implicit_function.h @@ -154,7 +154,7 @@ class TGc : public Function { public: TGc() {} FT operator ()(FT x, FT y, FT z) { - return 10 * (cos(x) * sin(y) + cos(y) * sin(z) + cos(z) * sin(x)) - 2 * (cos(2 * x) * cos(2 * y) + cos(2 * y) * cos(2 * z) + cos(2 * z) * cos(2 * x)) - 12; + return -(10 * (cos(x) * sin(y) + cos(y) * sin(z) + cos(z) * sin(x)) - 2 * (cos(2 * x) * cos(2 * y) + cos(2 * y) * cos(2 * z) + cos(2 * z) * cos(2 * x)) - 12); } }; diff --git a/include/utils.h b/include/utils.h new file mode 100644 index 0000000..68bda96 --- /dev/null +++ b/include/utils.h @@ -0,0 +1,63 @@ +#pragma once + +#ifdef _WIN32 +#include +#include +#else +#include +#include +#include +#include +#include +#endif + +int make_dir(std::string& str) { + if (str.empty()) + return 0; +#ifdef _WIN32 + return _mkdir(str.c_str()); +#else + return mkdir(str.c_str(), 0733); +#endif +} + +/* Remove if already defined */ +typedef long long int64; typedef unsigned long long uint64; + +/* Returns the amount of milliseconds elapsed since the UNIX epoch. Works on both + * windows and linux. */ + +uint64 GetTimeMs64() +{ +#ifdef _WIN32 + /* Windows */ + FILETIME ft; + LARGE_INTEGER li; + + /* Get the amount of 100 nano seconds intervals elapsed since January 1, 1601 (UTC) and copy it + * to a LARGE_INTEGER structure. */ + GetSystemTimeAsFileTime(&ft); + li.LowPart = ft.dwLowDateTime; + li.HighPart = ft.dwHighDateTime; + + uint64 ret = li.QuadPart; + ret -= 116444736000000000LL; /* Convert from file time to UNIX epoch time. */ + ret /= 10000; /* From 100 nano seconds (10^-7) to 1 millisecond (10^-3) intervals */ + + return ret; +#else + /* Linux */ + struct timeval tv; + + gettimeofday(&tv, NULL); + + uint64 ret = tv.tv_usec; + /* Convert from micro seconds (10^-6) to milliseconds (10^-3) */ + ret /= 1000; + + /* Adds the seconds (10^0) after converting them to milliseconds (10^-3) */ + ret += (tv.tv_sec * 1000); + + return ret; +#endif +} \ No newline at end of file diff --git a/src/Main.cpp b/src/Main.cpp index c5c3cda..f6506d9 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -195,28 +195,8 @@ int main(int argc, char* argv[]) << "-- Point cloud: " << pointcloud << (!pointcloud ? "[Valid]" : "[Invalid]") << std::endl; return 1; } - // Convert STL VCG::TMesh - // Vertices to Eigen matrixXd V1 - V1.resize(stl.VN(), 3); - size_t i = 0; - std::vector vertexId(stl.vert.size()); - for (TMesh::VertexIterator it = stl.vert.begin(); it != stl.vert.end(); ++it) if (!it->IsD()) { - vertexId[it - stl.vert.begin()] = i; - vcg::Point3d point = it->P(); - V1(i, 0) = point[0]; - V1(i, 1) = point[1]; - V1(i, 2) = point[2]; - i++; - } - // Faces to Eigen matrixXi F1 - i = 0; - F1.resize(stl.FN(), stl.face.begin()->VN()); - for (TMesh::FaceIterator it = stl.face.begin(); it != stl.face.end(); ++it) if (!it->IsD()) { - for (int k = 0; k < it->VN(); k++) { - F1(i, k) = vertexId[vcg::tri::Index(stl, it->V(k))]; - } - i++; - } + // Convert input STL from VCG::TMesh to Eigen matrixXd V1 + mesh_to_eigen_vector(stl, V1, F1); } { @@ -322,11 +302,17 @@ int main(int argc, char* argv[]) } if (verbose) std::cout << "OK" << std::endl; - // Evaluating pore size by create 2D slice 8-bit image (0-blacks're pores and 255-whites're grains) - // Then an 8-bit image become a binary image by image thresholding (value of 150) - // The binary imaege'll be labeled and finally evaluated the feret diameter by chain coding + // Create scaffolder and inverse mesh by dual marching cube + marching_cube(mesh, Fxyz, grid_size, V1min1, grid_delta, verbose, dirty); + if (is_build_inverse) marching_cube(inverse_mesh, IFxyz, grid_size, V1min1, grid_delta, false, false); + if (is_analysis_microstructure) { + // Evaluating pore size by create 2D slice 8-bit image (0-blacks're pores and 255-whites're grains) + // Then an 8-bit image become a binary image by image thresholding (value of 150) + // The binary imaege'll be labeled and finally evaluated the feret diameter by chain coding + // init filename + /* std::string dir; std::stringstream filename; std::vector minFeret; @@ -456,6 +442,7 @@ int main(int argc, char* argv[]) << "0,0,0,0,0," << "0,0,0,0,0,"; } + */ } } catch (const std::exception& ex) { @@ -463,9 +450,6 @@ int main(int argc, char* argv[]) std::cout << strerror(errno) << endl; return errno; } - - marching_cube(mesh, Fxyz, grid_size, V1min1, grid_delta, verbose, dirty); - if(is_build_inverse) marching_cube(inverse_mesh, IFxyz, grid_size, V1min1, grid_delta, false, false); } // Stage 2: Cleaning diff --git a/src/SliceTest.cpp b/src/SliceTest.cpp new file mode 100644 index 0000000..c8e80a4 --- /dev/null +++ b/src/SliceTest.cpp @@ -0,0 +1,119 @@ +#include +#include +#include +#include +#include + +#include "cxxopts.hpp" +#include "ProgressBar.hpp" +#include "OptimalSlice.hpp" +#include "utils.h" + +int main(int argc, char* argv[]) +{ + // Define default parameters + Eigen::IOFormat CleanFmt(4, Eigen::DontAlignCols, ", ", "\n", "[", "]"); + Eigen::IOFormat CSVFmt(-1, Eigen::DontAlignCols, ", ", ", "); + // file parameters + std::string input_file = ""; + size_t slice = 10; + int direction = 2; + try { + cxxopts::Options options("SliceTest", "Test"); + options.positional_help("[option args]").show_positional_help(); + options.add_options() + ("h,help", "Print help") + ("i,input", "Input file (STL)", cxxopts::value(), "FILE") + ("s,slice", "Number of slice > 1 (Default: 10)", cxxopts::value(), "POSITVE INTEGER MORE THAN 1") + ("d,direction", "Direction 0=X, 1=Y, 2=Z default: 2)", cxxopts::value(), "{0,1,2}"); + options.parse_positional({ "input", "slice", "direction" }); + bool isEmptyOption = (argc == 1); + cxxopts::ParseResult result = options.parse(argc, argv); + if (isEmptyOption || result.count("help")) { + std::cout << options.help() << std::endl; + return 0; + } + // Requirment + if (result.count("input")) input_file = result["input"].as(); + else { + std::cout << "Missing Input file" << std::endl; + return 1; + } + if (result.count("slice")) slice = result["slice"].as(); + if (slice < 2) { + std::cout << "Invalid slice: " << slice << std::endl; + } + if (result.count("direction")) direction = result["direction"].as(); + if (direction < 0 && direction > 2) { + std::cout << "Invalid direction: " << direction << std::endl; + } + } + catch (const cxxopts::OptionException & ex) { + std::cout << "Error parsing options: " << ex.what() << std::endl; + return 1; + } + + // Stage 1: + { + try { + // Read FROM STL + TMesh stl; + int loadmark = 0; + vcg::tri::io::ImporterSTL::Open(stl, input_file.c_str(), loadmark); + vcg::tri::Clean::RemoveDuplicateFace(stl); + vcg::tri::Clean::RemoveDuplicateVertex(stl); + vcg::tri::Clean::RemoveUnreferencedVertex(stl); + vcg::tri::UpdateTopology::FaceFace(stl); + vcg::tri::Clean::RemoveZeroAreaFace(stl); + vcg::tri::Clean::RemoveNonManifoldFace(stl); + vcg::tri::Clean::RemoveUnreferencedVertex(stl); + vcg::tri::UpdateTopology::FaceFace(stl); + + vcg::tri::UpdateBounding::Box(stl); + vcg::Box3d bbox = stl.bbox; + vcg::Point3d dim = bbox.Dim(); + + int connectedComponentsNum = vcg::tri::Clean::CountConnectedComponents(stl); + std::cout << "Mesh is composed by " << connectedComponentsNum << " connected component(s)" << std::endl; + + int edgeNum = 0, edgeBorderNum = 0, edgeNonManifoldNum = 0; + vcg::tri::Clean::CountEdgeNum(stl, edgeNum, edgeBorderNum, edgeNonManifoldNum); + int vertManifNum = vcg::tri::Clean::CountNonManifoldVertexFF(stl, true); + if (edgeNonManifoldNum == 0 && vertManifNum == 0) { + std::cout << "Mesh is two-manifold " << std::endl; + int holeNum = vcg::tri::Clean::CountHoles(stl); + std::cout << "Mesh has " << holeNum << " holes" << std::endl; + int genus = vcg::tri::Clean::MeshGenus(stl.vn, edgeNum, stl.fn, holeNum, connectedComponentsNum); + std::cout << "Genus is " << genus << std::endl; + } + + slice::Slice s = slice::incremental_slicing(stl, slice, direction); + slice::ContourSlice C = slice::contour_construct(s); + + // create output directory + if (!C.empty()) { + size_t firstindex = input_file.find_last_of("/\\"); + firstindex = firstindex == string::npos ? 0 : firstindex + 1; + size_t lastindex = input_file.find_last_of("."); + std::string dir = input_file.substr(firstindex, lastindex - firstindex) + "_svg"; + std::cout << "Creating direction: " << dir << std::endl; + make_dir(dir); + + for (slice::ContourSlice::const_iterator cs = C.begin(); cs != C.end(); cs++) { + size_t index = (size_t)(cs - C.begin() + 1); + std::cout << "Contour slice " << index << " size: " << cs->size() << " contour(s)" << std::endl; + std::stringstream name(dir); + name << dir << "/" << index << ".svg"; + slice::write_svg(name.str(), *cs, dim[0], dim[1], bbox.min[0], bbox.min[1]); + } + } + + } + catch (const std::exception & ex) { + std::cout << "Exception: " << ex.what() << std::endl; + std::cout << strerror(errno) << endl; + return errno; + } + } + return 0; +} \ No newline at end of file From 275b2a1a53f3a4916d46daeb194d044eb7a0d230 Mon Sep 17 00:00:00 2001 From: Jirawat I Date: Sun, 1 Mar 2020 21:32:13 +0700 Subject: [PATCH 04/20] fixed vcglib old eigen --- .travis.yml | 9 +++++++++ CMakeLists.txt | 8 ++++---- cmake/FindDIPlib.cmake | 13 +++++++------ cmake/FindLIBIGL.cmake | 4 ++-- cmake/FindTBB.cmake | 6 +++--- include/OptimalSlice.hpp | 2 +- src/Main.cpp | 2 -- src/SliceTest.cpp | 22 ++++++++++++---------- 8 files changed, 38 insertions(+), 28 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1148f47..a8b6051 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,17 +8,26 @@ jobs: include: - os: linux script: + - rm -rf ./external/vcglib/eigenlib/Eigen/* && rm -rf ./external/vcglib/eigenlib/unsupported/* + - cp -rf ./external/libigl/external/eigen/Eigen/* ./external/vcglib/eigenlib/Eigen + - cp -rf ./external/libigl/external/eigen/unsupported/* ./external/vcglib/eigenlib/unsupported - mkdir build && cd build - cmake .. - make - os: osx osx_image: xcode10 script: + - rm -rf ./external/vcglib/eigenlib/Eigen/* && rm -rf ./external/vcglib/eigenlib/unsupported/* + - cp -rf ./external/libigl/external/eigen/Eigen/* ./external/vcglib/eigenlib/Eigen + - cp -rf ./external/libigl/external/eigen/unsupported/* ./external/vcglib/eigenlib/unsupported - mkdir build && cd build - cmake .. - make - os: windows script: + - rd /S /Q ./external/vcglib/eigenlib/Eigen/* && rd /S /Q ./external/vcglib/eigenlib/unsupported/* + - copy /y ./external/libigl/external/eigen/Eigen/* ./external/vcglib/eigenlib/Eigen + - copy /y ./external/libigl/external/eigen/unsupported/* ./external/vcglib/eigenlib/unsupported - mkdir build && cd build - cmake .. - cmake --build . \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 5ed19ec..451db83 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,7 +11,7 @@ option(LIBIGL_WITH_OPENGL_GLFW "Use GLFW" OFF) find_package(LIBIGL REQUIRED) find_package(VCG REQUIRED) -#find_package(DIPlib REQUIRED) +find_package(DIPlib REQUIRED) find_package(TBB REQUIRED) #find_package(HDF5 CONFIG REQUIRED) #find_package(HighFive REQUIRED) @@ -42,9 +42,9 @@ include(update_deps_file) update_deps_file("main_sources" "${MAIN_SOURCES}") add_definitions(-DNOMINMAX -D_USE_MATH_DEFINES -DDIP__IS_STATIC -DDIP__ENABLE_ASSERT -DDIP__HAS_JPEG -DDIP__EXCEPTIONS_RECORD_STACK_TRACE) -#add_executable(${PROJECT_NAME} ${MAIN_SOURCES} ${PROJECT_SOURCE_DIR}/src/Main.cpp ${VCG_INCLUDE_DIR}/wrap/ply/plylib.cpp) -#target_include_directories(${PROJECT_NAME} PRIVATE "${PROJECT_SOURCE_DIR}/include" ${VCG_INCLUDE_DIR} ${DIPlib_INCLUDE_DIR}) -#target_link_libraries(${PROJECT_NAME} PRIVATE igl::core DIP) +add_executable(${PROJECT_NAME} ${MAIN_SOURCES} ${PROJECT_SOURCE_DIR}/src/Main.cpp ${VCG_INCLUDE_DIR}/wrap/ply/plylib.cpp) +target_include_directories(${PROJECT_NAME} PRIVATE "${PROJECT_SOURCE_DIR}/include" ${VCG_INCLUDE_DIR} ${DIPlib_INCLUDE_DIR}) +target_link_libraries(${PROJECT_NAME} PRIVATE igl::core DIP) add_executable(SliceTest ${MAIN_SOURCES} ${PROJECT_SOURCE_DIR}/src/SliceTest.cpp) target_include_directories(SliceTest PRIVATE "${PROJECT_SOURCE_DIR}/include" ${VCG_INCLUDE_DIR} ${TBB_INCLUDE_DIR}) diff --git a/cmake/FindDIPlib.cmake b/cmake/FindDIPlib.cmake index aef5a33..a82e990 100644 --- a/cmake/FindDIPlib.cmake +++ b/cmake/FindDIPlib.cmake @@ -25,12 +25,13 @@ find_package_handle_standard_args(DIPlib DIPlib_INCLUDE_DIR) mark_as_advanced(DIPlib_INCLUDE_DIR) -set(DIP_BUILD_DIPIMAGE FALSE) -set(DIP_BUILD_JAVAIO FALSE) -set(DIP_BUILD_PYDIP FALSE) -set(DIP_SHARED_LIBRARY FALSE) -set(DIP_ENABLE_DOCTEST FALSE) -set(DIP_ENABLE_STACK_TRACE FALSE) +set(DIP_BUILD_DIPIMAGE OFF CACHE INTERNAL "") +set(DIP_BUILD_JAVAIO OFF CACHE INTERNAL "") +set(DIP_BUILD_PYDIP OFF CACHE INTERNAL "") +set(DIP_SHARED_LIBRARY OFF CACHE INTERNAL "") +set(DIP_ENABLE_DOCTEST OFF CACHE INTERNAL "") +set(DIP_ENABLE_STACK_TRACE OFF CACHE INTERNAL "") +set(DIP_ENABLE_MULTITHREADING ON CACHE INTERNAL "") list(APPEND CMAKE_MODULE_PATH "${DIPlib_ROOT_DIR}") message(STATUS "USE DIR: ${DIPlib_ROOT_DIR}") diff --git a/cmake/FindLIBIGL.cmake b/cmake/FindLIBIGL.cmake index 2596361..9a62837 100644 --- a/cmake/FindLIBIGL.cmake +++ b/cmake/FindLIBIGL.cmake @@ -21,8 +21,8 @@ find_package_handle_standard_args(LIBIGL LIBIGL_INCLUDE_DIR) mark_as_advanced(LIBIGL_INCLUDE_DIR) -set(LIBIGL_WITH_OPENGL FALSE) -set(LIBIGL_WITH_OPENGL_GLFW FALSE) +set(LIBIGL_WITH_OPENGL OFF CACHE INTERNAL "") +set(LIBIGL_WITH_OPENGL_GLFW OFF CACHE INTERNAL "") list(APPEND CMAKE_MODULE_PATH "${LIBIGL_INCLUDE_DIR}/../cmake") message(STATUS "USE DIR: ${LIBIGL_INCLUDE_DIR}/../cmake") diff --git a/cmake/FindTBB.cmake b/cmake/FindTBB.cmake index 0c67a92..632d5fe 100644 --- a/cmake/FindTBB.cmake +++ b/cmake/FindTBB.cmake @@ -17,9 +17,9 @@ find_path(TBB_INCLUDE_DIR tbb/tbb.h ) set(TBB_ROOT_DIR ${TBB_INCLUDE_DIR}/../) -set(TBB_BUILD_SHARED OFF) -set(TBB_BUILD_STATIC ON) -set(TBB_BUILD_TESTS OFF) +set(TBB_BUILD_SHARED OFF CACHE INTERNAL "") +set(TBB_BUILD_STATIC ON CACHE INTERNAL "") +set(TBB_BUILD_TESTS OFF CACHE INTERNAL "") include(FindPackageHandleStandardArgs) find_package_handle_standard_args(TBB diff --git a/include/OptimalSlice.hpp b/include/OptimalSlice.hpp index 0d94ed0..faf4be2 100644 --- a/include/OptimalSlice.hpp +++ b/include/OptimalSlice.hpp @@ -555,7 +555,7 @@ namespace slice { std::ofstream svg(filename, 'r'); svg << "" << std::endl; svg << "" << std::endl; - svg << "" << std::endl; + svg << "" << std::endl; for (Contours::const_iterator t = C.begin(); t != C.end(); t++) { if (t->size() < 2) continue; size_t index = (size_t)(t - C.begin()); diff --git a/src/Main.cpp b/src/Main.cpp index f6506d9..185d952 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -312,7 +312,6 @@ int main(int argc, char* argv[]) // The binary imaege'll be labeled and finally evaluated the feret diameter by chain coding // init filename - /* std::string dir; std::stringstream filename; std::vector minFeret; @@ -442,7 +441,6 @@ int main(int argc, char* argv[]) << "0,0,0,0,0," << "0,0,0,0,0,"; } - */ } } catch (const std::exception& ex) { diff --git a/src/SliceTest.cpp b/src/SliceTest.cpp index c8e80a4..6187559 100644 --- a/src/SliceTest.cpp +++ b/src/SliceTest.cpp @@ -60,22 +60,24 @@ int main(int argc, char* argv[]) TMesh stl; int loadmark = 0; vcg::tri::io::ImporterSTL::Open(stl, input_file.c_str(), loadmark); - vcg::tri::Clean::RemoveDuplicateFace(stl); - vcg::tri::Clean::RemoveDuplicateVertex(stl); - vcg::tri::Clean::RemoveUnreferencedVertex(stl); - vcg::tri::UpdateTopology::FaceFace(stl); - vcg::tri::Clean::RemoveZeroAreaFace(stl); - vcg::tri::Clean::RemoveNonManifoldFace(stl); - vcg::tri::Clean::RemoveUnreferencedVertex(stl); - vcg::tri::UpdateTopology::FaceFace(stl); - + std::cout << "STL Imported" << std::endl; vcg::tri::UpdateBounding::Box(stl); vcg::Box3d bbox = stl.bbox; vcg::Point3d dim = bbox.Dim(); + + vcg::tri::Clean::RemoveDuplicateVertex(stl); + vcg::tri::Allocator::CompactEveryVector(stl); + std::cout << "Removed duplicate face and vertex" << std::endl; + vcg::tri::UpdateTopology::FaceFace(stl); + std::cout << "Update topology" << std::endl; + vcg::tri::Clean::MergeCloseVertex(stl, SLICE_PRECISION*100); + std::cout << "Merge close vertex" << std::endl; + vcg::tri::UpdateTopology::FaceFace(stl); + std::cout << "Update topology" << std::endl; int connectedComponentsNum = vcg::tri::Clean::CountConnectedComponents(stl); std::cout << "Mesh is composed by " << connectedComponentsNum << " connected component(s)" << std::endl; - + int edgeNum = 0, edgeBorderNum = 0, edgeNonManifoldNum = 0; vcg::tri::Clean::CountEdgeNum(stl, edgeNum, edgeBorderNum, edgeNonManifoldNum); int vertManifNum = vcg::tri::Clean::CountNonManifoldVertexFF(stl, true); From 127a48152b4b766dd483f6092068521402431f74 Mon Sep 17 00:00:00 2001 From: Jirawat I Date: Mon, 2 Mar 2020 09:43:10 +0700 Subject: [PATCH 05/20] fixed eigenlib missing --- .travis.yml | 9 --------- CMakeLists.txt | 23 +++++++++-------------- cmake/FindLIBIGL.cmake | 13 +++++++++++-- 3 files changed, 20 insertions(+), 25 deletions(-) diff --git a/.travis.yml b/.travis.yml index a8b6051..1148f47 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,26 +8,17 @@ jobs: include: - os: linux script: - - rm -rf ./external/vcglib/eigenlib/Eigen/* && rm -rf ./external/vcglib/eigenlib/unsupported/* - - cp -rf ./external/libigl/external/eigen/Eigen/* ./external/vcglib/eigenlib/Eigen - - cp -rf ./external/libigl/external/eigen/unsupported/* ./external/vcglib/eigenlib/unsupported - mkdir build && cd build - cmake .. - make - os: osx osx_image: xcode10 script: - - rm -rf ./external/vcglib/eigenlib/Eigen/* && rm -rf ./external/vcglib/eigenlib/unsupported/* - - cp -rf ./external/libigl/external/eigen/Eigen/* ./external/vcglib/eigenlib/Eigen - - cp -rf ./external/libigl/external/eigen/unsupported/* ./external/vcglib/eigenlib/unsupported - mkdir build && cd build - cmake .. - make - os: windows script: - - rd /S /Q ./external/vcglib/eigenlib/Eigen/* && rd /S /Q ./external/vcglib/eigenlib/unsupported/* - - copy /y ./external/libigl/external/eigen/Eigen/* ./external/vcglib/eigenlib/Eigen - - copy /y ./external/libigl/external/eigen/unsupported/* ./external/vcglib/eigenlib/unsupported - mkdir build && cd build - cmake .. - cmake --build . \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 451db83..b437715 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,21 +9,10 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) option(LIBIGL_WITH_OPENGL "Use OpenGL" OFF) option(LIBIGL_WITH_OPENGL_GLFW "Use GLFW" OFF) -find_package(LIBIGL REQUIRED) +find_package(TBB REQUIRED) find_package(VCG REQUIRED) find_package(DIPlib REQUIRED) -find_package(TBB REQUIRED) -#find_package(HDF5 CONFIG REQUIRED) -#find_package(HighFive REQUIRED) -#find_package(OpenMP) -#if (OpenMP_FOUND) -# set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") -# set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") -# set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}") -# if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") -# string(REPLACE "-openmp" "-openmp:experimental" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) -# endif() -#endif() +find_package(LIBIGL REQUIRED) # Add your project files file(GLOB MAIN_SOURCES @@ -41,7 +30,13 @@ file(GLOB MAIN_SOURCES include(update_deps_file) update_deps_file("main_sources" "${MAIN_SOURCES}") -add_definitions(-DNOMINMAX -D_USE_MATH_DEFINES -DDIP__IS_STATIC -DDIP__ENABLE_ASSERT -DDIP__HAS_JPEG -DDIP__EXCEPTIONS_RECORD_STACK_TRACE) +if(MSVC) + # Enable parallel compilation for Visual Studio + add_compile_options(/MP /bigobj) + add_definitions(-DNOMINMAX -D_USE_MATH_DEFINES) +endif() + +add_definitions(-DDIP__IS_STATIC -DDIP__ENABLE_ASSERT -DDIP__HAS_JPEG -DDIP__EXCEPTIONS_RECORD_STACK_TRACE) add_executable(${PROJECT_NAME} ${MAIN_SOURCES} ${PROJECT_SOURCE_DIR}/src/Main.cpp ${VCG_INCLUDE_DIR}/wrap/ply/plylib.cpp) target_include_directories(${PROJECT_NAME} PRIVATE "${PROJECT_SOURCE_DIR}/include" ${VCG_INCLUDE_DIR} ${DIPlib_INCLUDE_DIR}) target_link_libraries(${PROJECT_NAME} PRIVATE igl::core DIP) diff --git a/cmake/FindLIBIGL.cmake b/cmake/FindLIBIGL.cmake index 9a62837..7598a4f 100644 --- a/cmake/FindLIBIGL.cmake +++ b/cmake/FindLIBIGL.cmake @@ -25,5 +25,14 @@ set(LIBIGL_WITH_OPENGL OFF CACHE INTERNAL "") set(LIBIGL_WITH_OPENGL_GLFW OFF CACHE INTERNAL "") list(APPEND CMAKE_MODULE_PATH "${LIBIGL_INCLUDE_DIR}/../cmake") -message(STATUS "USE DIR: ${LIBIGL_INCLUDE_DIR}/../cmake") -include(libigl) \ No newline at end of file +include(libigl) +if (VCG_INCLUDE_DIR) + set(eigen_version "") + if (EXISTS "${VCG_INCLUDE_DIR}/eigenlib/eigen_version.txt") + file(READ "${VCG_INCLUDE_DIR}/eigenlib/eigen_version.txt" eigen_version) + endif() + if (NOT eigen_version STREQUAL LIBIGL_EIGEN_VERSION) + file(COPY "${LIBIGL_EXTERNAL}/eigen/Eigen" "${LIBIGL_EXTERNAL}/eigen/unsupported" DESTINATION "${VCG_INCLUDE_DIR}/eigenlib/") + file(WRITE "${VCG_INCLUDE_DIR}/eigenlib/eigen_version.txt" "${LIBIGL_EIGEN_VERSION}") + endif() +endif() \ No newline at end of file From 03315a18dfee515fce8ad155140dc611c48f325b Mon Sep 17 00:00:00 2001 From: Jirawat I Date: Mon, 2 Mar 2020 12:20:17 +0700 Subject: [PATCH 06/20] fixed incompatible /MTd /MDd --- CMakeLists.txt | 2 +- cmake/FindLIBIGL.cmake | 5 +++-- include/OptimalSlice.hpp | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b437715..bb360e0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,10 +9,10 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) option(LIBIGL_WITH_OPENGL "Use OpenGL" OFF) option(LIBIGL_WITH_OPENGL_GLFW "Use GLFW" OFF) +find_package(LIBIGL REQUIRED) find_package(TBB REQUIRED) find_package(VCG REQUIRED) find_package(DIPlib REQUIRED) -find_package(LIBIGL REQUIRED) # Add your project files file(GLOB MAIN_SOURCES diff --git a/cmake/FindLIBIGL.cmake b/cmake/FindLIBIGL.cmake index 7598a4f..cb624f8 100644 --- a/cmake/FindLIBIGL.cmake +++ b/cmake/FindLIBIGL.cmake @@ -21,8 +21,9 @@ find_package_handle_standard_args(LIBIGL LIBIGL_INCLUDE_DIR) mark_as_advanced(LIBIGL_INCLUDE_DIR) -set(LIBIGL_WITH_OPENGL OFF CACHE INTERNAL "") -set(LIBIGL_WITH_OPENGL_GLFW OFF CACHE INTERNAL "") +set(LIBIGL_WITH_OPENGL OFF CACHE INTERNAL "turn off OPENGL in LIBIGL") +set(LIBIGL_WITH_OPENGL_GLFW OFF CACHE INTERNAL "turn off OPENGL GLFW in LIBIGL") +set(LIBIGL_USE_STATIC_LIBRARY OFF CACHE INTERNAL "prefer STATIC build") list(APPEND CMAKE_MODULE_PATH "${LIBIGL_INCLUDE_DIR}/../cmake") include(libigl) diff --git a/include/OptimalSlice.hpp b/include/OptimalSlice.hpp index faf4be2..adc2446 100644 --- a/include/OptimalSlice.hpp +++ b/include/OptimalSlice.hpp @@ -552,7 +552,7 @@ namespace slice { if (C.empty()) return false; ContourPositions p = contour_inside_test(C); assert(p.size() == C.size()); - std::ofstream svg(filename, 'r'); + std::ofstream svg(filename, std::ofstream::out); svg << "" << std::endl; svg << "" << std::endl; svg << "" << std::endl; From f72466b0575e2fa7cda7a6a9f785c6c472d21e43 Mon Sep 17 00:00:00 2001 From: Jirawat I Date: Mon, 2 Mar 2020 12:50:41 +0700 Subject: [PATCH 07/20] correct the order of find_package in cmake --- CMakeLists.txt | 2 +- cmake/FindLIBIGL.cmake | 12 +----------- cmake/FindVCG.cmake | 11 ++++++++++- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index bb360e0..948f8a4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,8 +10,8 @@ option(LIBIGL_WITH_OPENGL "Use OpenGL" OFF) option(LIBIGL_WITH_OPENGL_GLFW "Use GLFW" OFF) find_package(LIBIGL REQUIRED) -find_package(TBB REQUIRED) find_package(VCG REQUIRED) +find_package(TBB REQUIRED) find_package(DIPlib REQUIRED) # Add your project files diff --git a/cmake/FindLIBIGL.cmake b/cmake/FindLIBIGL.cmake index cb624f8..22232dc 100644 --- a/cmake/FindLIBIGL.cmake +++ b/cmake/FindLIBIGL.cmake @@ -26,14 +26,4 @@ set(LIBIGL_WITH_OPENGL_GLFW OFF CACHE INTERNAL "turn off OPENGL GLFW in LIBIGL") set(LIBIGL_USE_STATIC_LIBRARY OFF CACHE INTERNAL "prefer STATIC build") list(APPEND CMAKE_MODULE_PATH "${LIBIGL_INCLUDE_DIR}/../cmake") -include(libigl) -if (VCG_INCLUDE_DIR) - set(eigen_version "") - if (EXISTS "${VCG_INCLUDE_DIR}/eigenlib/eigen_version.txt") - file(READ "${VCG_INCLUDE_DIR}/eigenlib/eigen_version.txt" eigen_version) - endif() - if (NOT eigen_version STREQUAL LIBIGL_EIGEN_VERSION) - file(COPY "${LIBIGL_EXTERNAL}/eigen/Eigen" "${LIBIGL_EXTERNAL}/eigen/unsupported" DESTINATION "${VCG_INCLUDE_DIR}/eigenlib/") - file(WRITE "${VCG_INCLUDE_DIR}/eigenlib/eigen_version.txt" "${LIBIGL_EIGEN_VERSION}") - endif() -endif() \ No newline at end of file +include(libigl) \ No newline at end of file diff --git a/cmake/FindVCG.cmake b/cmake/FindVCG.cmake index 59314b7..76c24c9 100644 --- a/cmake/FindVCG.cmake +++ b/cmake/FindVCG.cmake @@ -23,4 +23,13 @@ find_package_handle_standard_args(VCG mark_as_advanced(VCG_INCLUDE_DIR) message(STATUS "USE DIR: ${VCG_INCLUDE_DIR}/") -# include(VCG) \ No newline at end of file +if (VCG_INCLUDE_DIR AND LIBIGL_INCLUDE_DIR) + set(eigen_version "") + if (EXISTS "${VCG_INCLUDE_DIR}/eigenlib/eigen_version.txt") + file(READ "${VCG_INCLUDE_DIR}/eigenlib/eigen_version.txt" eigen_version) + endif() + if (NOT eigen_version STREQUAL LIBIGL_EIGEN_VERSION) + file(COPY "${LIBIGL_EXTERNAL}/eigen/Eigen" "${LIBIGL_EXTERNAL}/eigen/unsupported" DESTINATION "${VCG_INCLUDE_DIR}/eigenlib/") + file(WRITE "${VCG_INCLUDE_DIR}/eigenlib/eigen_version.txt" "${LIBIGL_EIGEN_VERSION}") + endif() +endif() \ No newline at end of file From ade6ba15e7eef3f27f52b841df2abebf77a282bf Mon Sep 17 00:00:00 2001 From: Jirawat I Date: Fri, 6 Mar 2020 00:31:06 +0700 Subject: [PATCH 08/20] fixed a lot of bugs in feret and convexhull --- CMakeLists.txt | 8 +- cmake/FindDIPlib.cmake | 2 + include/OptimalSlice.hpp | 489 ++++++++++++++++++++++++++++++++++++--- include/Scaffolder_2.h | 48 +++- src/Main.cpp | 174 ++++++++------ src/SliceTest.cpp | 39 +++- 6 files changed, 625 insertions(+), 135 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 948f8a4..5e2acbd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,8 +10,8 @@ option(LIBIGL_WITH_OPENGL "Use OpenGL" OFF) option(LIBIGL_WITH_OPENGL_GLFW "Use GLFW" OFF) find_package(LIBIGL REQUIRED) -find_package(VCG REQUIRED) find_package(TBB REQUIRED) +find_package(VCG REQUIRED) find_package(DIPlib REQUIRED) # Add your project files @@ -38,9 +38,9 @@ endif() add_definitions(-DDIP__IS_STATIC -DDIP__ENABLE_ASSERT -DDIP__HAS_JPEG -DDIP__EXCEPTIONS_RECORD_STACK_TRACE) add_executable(${PROJECT_NAME} ${MAIN_SOURCES} ${PROJECT_SOURCE_DIR}/src/Main.cpp ${VCG_INCLUDE_DIR}/wrap/ply/plylib.cpp) -target_include_directories(${PROJECT_NAME} PRIVATE "${PROJECT_SOURCE_DIR}/include" ${VCG_INCLUDE_DIR} ${DIPlib_INCLUDE_DIR}) -target_link_libraries(${PROJECT_NAME} PRIVATE igl::core DIP) +target_include_directories(${PROJECT_NAME} PRIVATE "${PROJECT_SOURCE_DIR}/include" ${TBB_INCLUDE_DIR} ${VCG_INCLUDE_DIR} ${DIPlib_INCLUDE_DIR}) +target_link_libraries(${PROJECT_NAME} PRIVATE igl::core DIP tbb_static) add_executable(SliceTest ${MAIN_SOURCES} ${PROJECT_SOURCE_DIR}/src/SliceTest.cpp) -target_include_directories(SliceTest PRIVATE "${PROJECT_SOURCE_DIR}/include" ${VCG_INCLUDE_DIR} ${TBB_INCLUDE_DIR}) +target_include_directories(SliceTest PRIVATE "${PROJECT_SOURCE_DIR}/include" ${TBB_INCLUDE_DIR} ${VCG_INCLUDE_DIR}) target_link_libraries(SliceTest PRIVATE igl::core tbb_static) diff --git a/cmake/FindDIPlib.cmake b/cmake/FindDIPlib.cmake index a82e990..4da73e8 100644 --- a/cmake/FindDIPlib.cmake +++ b/cmake/FindDIPlib.cmake @@ -29,6 +29,8 @@ set(DIP_BUILD_DIPIMAGE OFF CACHE INTERNAL "") set(DIP_BUILD_JAVAIO OFF CACHE INTERNAL "") set(DIP_BUILD_PYDIP OFF CACHE INTERNAL "") set(DIP_SHARED_LIBRARY OFF CACHE INTERNAL "") +set(DIP_ENABLE_ICS OFF CACHE INTERNAL "") +set(DIP_ENABLE_UNICODE OFF CACHE INTERNAL "") set(DIP_ENABLE_DOCTEST OFF CACHE INTERNAL "") set(DIP_ENABLE_STACK_TRACE OFF CACHE INTERNAL "") set(DIP_ENABLE_MULTITHREADING ON CACHE INTERNAL "") diff --git a/include/OptimalSlice.hpp b/include/OptimalSlice.hpp index adc2446..deba4ce 100644 --- a/include/OptimalSlice.hpp +++ b/include/OptimalSlice.hpp @@ -2,9 +2,7 @@ * This is the implement slice algorithm from Rodrigo, 2017 (An Optimal Algorithm for 3D Triangle Mesh Slicing) * which is claimed to be faster than slic3r and CGAL method. */ -#ifndef OPTIMAL_SLICE_HPP -#define OPTIMAL_SLICE_HPP - +#pragma once #ifndef SLICE_PRECISION // There's a problem of double hashing with the precision less than 1e-8 (e.g. 1e-10) // when performed contour constructing @@ -13,6 +11,8 @@ #define DOUBLE_EQ(x,y) (abs(x - y) < SLICE_PRECISION) #define DOUBLE_GT(x,y) ((x - y) > SLICE_PRECISION) #define DOUBLE_LT(x,y) ((y - x) > SLICE_PRECISION) +#define DOUBLE_GT_EQ(x,y) (DOUBLE_EQ(x,y) || DOUBLE_GT(x,y)) +#define DOUBLE_LT_EQ(x,y) (DOUBLE_EQ(x,y) || DOUBLE_LT(x,y)) #define USE_PARALLEL #include "Mesh.h" @@ -21,7 +21,7 @@ namespace slice { enum Direction {X = 0,Y,Z}; - enum ContourPosition {OUTSIDE = 0, INSIDE}; + enum PolygonSide {OUTSIDE = 0, INSIDE}; struct Point2d { double x; @@ -46,12 +46,16 @@ namespace slice { if (x < ls.x) return true; else if DOUBLE_EQ(x, ls.x) if (y < ls.y) return true; - else if DOUBLE_EQ(y, ls.y) return (z < ls.z); + else if (DOUBLE_EQ(y, ls.y)) { + return (z < ls.z); + } return false; } }; - Point2d make_point2d(Point3d p) { + Point2d make_point2d(Point3d p, int direction = Direction::Z) { + if (direction == Direction::X) return Point2d{ p.y, p.z }; + else if (direction == Direction::Y) return Point2d{ p.x, p.z }; return Point2d{ p.x, p.y }; } @@ -115,19 +119,20 @@ namespace slice { if (v[0] < ls.v[0]) return true; else if (v[0] == ls.v[0]) if (v[1] < ls.v[1]) return true; - else if (v[1] == ls.v[1]) return (v[2] < ls.v[2]); + else if (v[1] == ls.v[1]) { + return (v[2] < ls.v[2]); + } return false; } }; class Line { public: - size_t index; Point3d v[2]; Line() {} - Line(Point3d v0, Point3d v1, size_t index) : index(index) { + Line(Point3d v0, Point3d v1, size_t index) { v[0] = v0; v[1] = v1; sort(); @@ -148,17 +153,127 @@ namespace slice { } }; + class SupportLine2d { + public: + double x, y; + double m, theta, c; + bool is_vertical = false; + SupportLine2d(double _x, double _y, double _theta) : x(_x), y(_y), theta(_theta) { + update(); + } + SupportLine2d(Point2d p, double _theta) : x(p.x), y(p.y), theta(_theta) { + update(); + } + void update() { + // adjust theta to be in range [-90, 90] + if (theta > 90) theta -= 180; + else if (theta < -90) theta += 180; + if (DOUBLE_EQ(abs(theta), 90)) { + is_vertical = true; + m = 0; + c = x; + } + else { + is_vertical = false; + m = tan(theta * M_PI / 180); + c = y - m * x; + } + } + void update(double _x, double _y) { + x = _x; + y = _y; + update(); + } + + void update(const Point2d& p) { + update(p.x, p.y); + } + + SupportLine2d& operator+= (const double theta) { + this->theta += theta; + update(); + return *this; + } + }; + + double two_line_distance(const SupportLine2d& line1, const SupportLine2d &line2) { + if (DOUBLE_EQ(line1.theta, line2.theta) && DOUBLE_EQ(line1.m, line2.m) && line1.is_vertical == line2.is_vertical) { + return abs(line2.c - line1.c) / sqrt(line1.m * line1.m + 1); + } + return 0; + } + + double line_point_angle(const SupportLine2d& line, const Point2d& p) { + double angle; + if (DOUBLE_EQ(p.x, line.x)) angle = 90; + else if (DOUBLE_EQ(p.y, line.y)) angle = 0; + else angle = atan((p.y - line.y) / (p.x - line.x)) * 180 / M_PI; + // calculate delta + if (DOUBLE_GT_EQ(line.theta, angle)) angle = line.theta - angle; + else angle = 180 + line.theta - angle; + // normalized angle + if (angle > 90) angle -= 180; + return angle; + } + + class FeretDiameter { + public: + double min; + double max; + double perpendicularMax; + double perimeter; + double angleMin; + double angleMax; + FeretDiameter() : min(0), max(0), perpendicularMax(0), perimeter(0), angleMin(0), angleMax(0) {} + FeretDiameter(const SupportLine2d (&lines)[4]) { + double d1 = two_line_distance(lines[0], lines[2]); + double d2 = two_line_distance(lines[1], lines[3]); + if (d1 > d2) { + min = d2; angleMin = lines[1].theta; + max = d1; angleMax = lines[0].theta; + } + else { + min = d1; angleMin = lines[0].theta; + max = d2; angleMax = lines[1].theta; + } + perpendicularMax = max; + } + void update(const SupportLine2d (&lines)[4]) { + double d1 = two_line_distance(lines[0], lines[2]); + double d2 = two_line_distance(lines[1], lines[3]); + double angle1 = lines[0].theta; + double angle2 = lines[1].theta; + if (d1 > d2) { + std::swap(d1, d2); + std::swap(angle1, angle2); + } + if (min > d1) { + min = d1; + angleMin = angle1; + perpendicularMax = d2; + } + if (max < d1) { + max = d1; + angleMax = angle1; + } + if (max < d2) { + max = d2; + angleMax = angle2; + } + } + }; + typedef std::vector Plane; typedef std::vector Triangles; typedef std::vector Layer; typedef std::vector Lines; typedef std::vector Slice; - typedef std::vector Contour; - typedef std::vector Contours; - typedef std::vector ContourSlice; + typedef std::vector Polygon; + typedef std::vector Polygons; + typedef std::vector ContourSlice; typedef std::pair PairPoint2d; typedef std::unordered_map ContourHash; - typedef std::vector ContourPositions; + typedef std::vector PolygonSides; std::ostream& operator<< (std::ostream& out, Point2d const& data) { out << "[" << data.x << "," << data.y << "]"; @@ -184,6 +299,16 @@ namespace slice { out << "slice::Line(" << data.v[0] << " " << data.v[1] << ")"; return out; } + + std::ostream& operator<< (std::ostream& out, FeretDiameter const& data) { + out << "slice::FeretDiameter(min:" << data.min << ", max:" << data.max << ", pmax:" << data.perpendicularMax << ")"; + return out; + } + + std::ostream& operator<< (std::ostream& out, SupportLine2d const& data) { + out << "slice::SupportLine2d(x:" << data.x << ", y:" << data.y << ", m:" << data.m << ", c:" << data.c << ", theta:" << data.theta << "[" << data.is_vertical <<"])"; + return out; + } } namespace std { @@ -314,7 +439,7 @@ namespace slice { Slice incremental_slicing(TMesh& mesh, size_t grid_size, int direction = Direction::Z) { slice::Plane P; slice::Layer L; - slice::build_triangle_list(mesh, grid_size, P, L); + slice::build_triangle_list(mesh, grid_size, P, L, direction); Slice S(grid_size); Triangles A; @@ -383,7 +508,7 @@ namespace slice { } #ifndef USE_PARALLEL - ContourSlice contour_construct(Slice const& S) { + ContourSlice contour_construct(Slice const& S, int direction = Direction::Z) { ContourSlice CS(S.size()); ContourHash hash; for (size_t i = 0, len = S.size(); i < len; i++) { @@ -391,8 +516,8 @@ namespace slice { hash.clear(); hash.reserve(S[i].size() + 1); for (Lines::const_iterator l = S[i].begin(); l != S[i].end(); l++) { - Point2d u = make_point2d(l->v[0]); - Point2d v = make_point2d(l->v[1]); + Point2d u = make_point2d(l->v[0], direction); + Point2d v = make_point2d(l->v[1], direction); ContourHash::iterator item = hash.find(u); if (item == hash.end()) hash.emplace(u, make_pair(v, v)); @@ -414,9 +539,10 @@ namespace slice { } } } - //std::cout << " [Hash OK: " << hash.size() << "]"; + //std::cout << " [Hash OK: " << hash.size() << "] from S[i]: " << S[i].size() << std::endl; + //std::copy(S[i].begin(), S[i].end(), std::ostream_iterator(std::cout, " ")); + //std::cout << std::endl; /* TODO: remove this debug message - std::cout << "Hash Slice " << i << std::endl; for (ContourHash::const_iterator item = hash.begin(); item != hash.end(); item++) { std::cout << " " << item->first << ": " << item->second << (item->second.first == item->second.second ? " [***]" : "") << std::endl; //std::cout << " " << item->first << ": " << std::hash()(item->first) << std::endl; @@ -425,7 +551,7 @@ namespace slice { while (!hash.empty()) { ContourHash::const_iterator item = hash.begin(); assert(item != hash.end()); - Contour C; + Polygon C; C.push_back(item->first); C.push_back(item->second.first); Point2d last = item->second.second; @@ -449,7 +575,7 @@ namespace slice { return CS; } #else - ContourSlice contour_construct(Slice const& S) { + ContourSlice contour_construct(Slice const& S, int direction = Direction::Z) { ContourSlice CS(S.size()); static tbb::affinity_partitioner ap; tbb::spin_mutex printMutex; @@ -462,8 +588,8 @@ namespace slice { hash.clear(); hash.reserve(S[i].size() + 1); for (Lines::const_iterator l = S[i].begin(); l != S[i].end(); l++) { - Point2d u = make_point2d(l->v[0]); - Point2d v = make_point2d(l->v[1]); + Point2d u = make_point2d(l->v[0], direction); + Point2d v = make_point2d(l->v[1], direction); ContourHash::iterator item = hash.find(u); if (item == hash.end()) hash.emplace(u, make_pair(v, v)); @@ -488,7 +614,7 @@ namespace slice { while (!hash.empty()) { ContourHash::const_iterator item = hash.begin(); assert(item != hash.end()); - Contour C; + Polygon C; C.push_back(item->first); C.push_back(item->second.first); Point2d last = item->second.second; @@ -510,7 +636,7 @@ namespace slice { } }, ap - ); + ); return CS; } #endif @@ -518,26 +644,26 @@ namespace slice { // — determining whether a point is inside a complex polygon. // Available at: http://alienryderflex.com/polygon/. // return true = inside, false = outside - bool is_point_inside_polygon(const Point2d &p, const Contour &C) { + bool is_point_inside_polygon(const Point2d &p, const Polygon &C) { bool oddNodes = 0; size_t size = C.size(); for (size_t i = 0; i < size; i++) { size_t j = (i == size - 1) ? 0 : i + 1; - if ((C[i].y < p.y && C[j].y >= p.y) || (C[j].y < p.y && C[i].y >= p.y) && (C[i].x <= p.x || C[j].x <= p.x)) { + if (((C[i].y < p.y && C[j].y >= p.y) || (C[j].y < p.y && C[i].y >= p.y)) && (C[i].x <= p.x || C[j].x <= p.x)) { oddNodes ^= (((p.y - C[i].y) / (C[j].y - C[i].y) * (C[j].x - C[i].x) + C[i].x) < p.x); } } return oddNodes; } - ContourPositions contour_inside_test(const Contours& C) { - ContourPositions position(C.size()); + PolygonSides contour_inside_test(const Polygons& C) { + PolygonSides position(C.size()); for (size_t i = 0, size = C.size(); i < size; i++) { - ContourPosition pos = ContourPosition::OUTSIDE; + PolygonSide pos = PolygonSide::OUTSIDE; for (size_t j = 0, size = C.size(); j < size; j++) { if (i == j) continue; if (is_point_inside_polygon(C[i][0], C[j])) { - pos = ContourPosition::INSIDE; + pos = PolygonSide::INSIDE; break; } } @@ -545,32 +671,319 @@ namespace slice { } return position; } + + // This formular came from the cross product of two vectors that is the area of PARALLELOGRAM + // Then the area of polygon is 1/2 * sum of all parallelogram + // ref: http://geomalgorithms.com/a01-_area.html + double measure_polygon_area(const Polygon& C) { + double A2 = 0; + for (size_t i = 0, s = C.size(); i < s; i++) { + size_t i_prev = (i == 0) ? s - 1 : i - 1; + size_t i_next = (i == s - 1) ? 0 : i + 1; + A2 += C[i].x * (C[i_next].y - C[i_prev].y); + } + return A2 * 0.5; + } + + double measure_point_square_distance(const Point2d& p1, const Point2d& p2) { + return (p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y); + } + + // To find orientation of ordered triplet (p, q, r). + // The function returns following values + // 0 --> p, q and r are colinear + // 1 --> Clockwise (Turn Right) + // -1 --> Counterclockwise (Turn Left) + int orientation(const Point2d& p, const Point2d& q, const Point2d& r) { + double val = (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y); + if (DOUBLE_EQ(val, 0)) return 0; + else if (val > 0) return 1; + else if (val < 0) return -1; + } + + struct CompareOrientation { + Point2d origin_; + CompareOrientation(const Point2d& origin) : origin_(origin) {} + bool operator() (const Point2d& q, const Point2d& r) { + int o = orientation(origin_, q, r); + if (o == 0) + return (measure_point_square_distance(origin_, r) >= measure_point_square_distance(origin_, q)); + return (o < 0); + } + }; + + // Graham scan algorithm for constructing convex hull + // https://www.geeksforgeeks.org/convex-hull-set-2-graham-scan/ + Polygon convexhull(Polygon points) { + Polygon p; + if (points.size() < 3) return p; + double ymin = points[0].y; + size_t index = 0; + // Find the bottom-left point + for (size_t i = 1, size = points.size(); i < size; i++) { + if (points[i].y < ymin || (DOUBLE_EQ(points[i].y, ymin) && points[i].x < points[index].x)) { + ymin = points[i].y; + index = i; + } + } + assert(points.size() > 3 && index >= 0 && index < points.size()); + std::swap(points[0], points[index]); + Point2d origin = points.front(); + std::sort(points.begin() + 1, points.end(), CompareOrientation(origin)); + // delete the colinear points + for (Polygon::iterator p = points.begin() + 1; p != points.end();) { + Polygon::iterator next_p = (p + 1); + if (next_p != points.end()) { + if (orientation(points.front(), *p, *next_p) == 0) { + p = points.erase(p); + } + else { + p++; + } + } + else { + break; + } + } + if (points.size() < 3) return p; + // Create convexhull polygon with points[0..2] + p.reserve(points.size()); + p.push_back(points[0]); + p.push_back(points[1]); + p.push_back(points[2]); + for (size_t i = 3, size = points.size(); i < size; i++) { + Point2d prev = *(p.end() - 2); + Point2d current = p.back(); + Point2d next = points[i]; + // check if clockwise (Turn right), then remove current point from convexhull polygon + while (slice::orientation(prev, current, next) > 0) { + p.pop_back(); + if (p.size() < 2) break; + current = p.back(); + prev = *(p.end() - 2); + } + p.push_back(next); + } + return p; + } + + FeretDiameter measure_polygon_feret_diameter(const Polygon& p) { + // find max min in convexhull + Polygon convex = convexhull(p); + if (convex.size() < 3) return FeretDiameter(); + size_t size = size = convex.size(); + size_t index_point[4] = { 0,0,0,0 }; // store top-most, right-most, bottom-most, left-most index + double minX = convex[0].x, minY = convex[0].y, maxX = convex[0].x, maxY = convex[0].y; + double perimeter = 0; + for (size_t i = 0; i < size; i++) { + double x = convex[i].x, y = convex[i].y; + size_t next_i = (i == size - 1) ? 0 : i + 1; + perimeter += sqrt(measure_point_square_distance(convex[next_i], convex[i])); + if (maxY < y || (DOUBLE_EQ(maxY, y) && x < convex[index_point[0]].x)) { + maxY = y; + index_point[0] = i; + } + if (maxX < x || (DOUBLE_EQ(maxX, x) && y > convex[index_point[1]].y)) { + maxX = x; + index_point[1] = i; + } + if (minY > y || (DOUBLE_EQ(minY, y) && x > convex[index_point[2]].x)) { + minY = y; + index_point[2] = i; + } + if (minX > x || (DOUBLE_EQ(minX, x) && y < convex[index_point[3]].y)) { + minX = x; + index_point[3] = i; + } + } + SupportLine2d line[4] = { + SupportLine2d(convex[index_point[0]], 0), + SupportLine2d(convex[index_point[1]], 90), + SupportLine2d(convex[index_point[2]], 0), + SupportLine2d(convex[index_point[3]], 90) + }; + // rotating caliber with perpendicular set of 4 supporting lines + // the conhexhull's direction is counterclockwise and this procedure direction will be clockwise + // the loop will finish when the initial upper-most point reached the bottom-most point + size_t last_index = index_point[1] == 0 ? size - 1 : index_point[1] - 1; + FeretDiameter feret(line); + feret.perimeter = perimeter; + while (index_point[0] != last_index) { + // measure the next minimal angle (min_theta) of 4 lines + double min_angle = 359; + size_t min_index = 0; + double angle[4]; + for (int i = 0; i < 4; i++) { + size_t next_index = index_point[i] == 0 ? size - 1 : (index_point[i] - 1); + angle[i] = line_point_angle(line[i], convex[next_index]); + if (min_angle > angle[i]) { + min_angle = angle[i]; + min_index = i; + } + } + //std::cout << " -- min_angle: " << min_angle << " min_index:" << min_index << std::endl; + //for (int i = 0; i < 4; i++) std::cout << angle[i] << " "; + //std::cout << std::endl; + for (int i = 0; i < 4; i++) { + if (DOUBLE_EQ(min_angle, angle[i])) { + size_t next_index = index_point[i] == 0 ? size - 1 : (index_point[i] - 1); + line[i].update(convex[next_index]); + index_point[i] = next_index; + } + assert(DOUBLE_GT_EQ(min_angle, -1) && DOUBLE_LT_EQ(min_angle, 180)); + if (min_angle < 90) line[i] += -min_angle; + else line[i] += 180 - min_angle; + } + //for (int i = 0; i < 4; i++) std::cout << " " << line[i] << std::endl; + feret.update(line); + //std::cout << feret << std::endl; + } + return feret; + } } namespace slice { - bool write_svg(std::string filename, const Contours &C, const int width, const int height, const int min_x, const int min_y) { + // Export the contour to SVG + bool write_svg(std::string filename, const Polygons &C, const int width, const int height, const int min_x, const int min_y, bool show_convexhull = false) { if (C.empty()) return false; - ContourPositions p = contour_inside_test(C); + PolygonSides p = contour_inside_test(C); assert(p.size() == C.size()); std::ofstream svg(filename, std::ofstream::out); svg << "" << std::endl; svg << "" << std::endl; svg << "" << std::endl; - for (Contours::const_iterator t = C.begin(); t != C.end(); t++) { + for (Polygons::const_iterator t = C.begin(); t != C.end(); t++) { if (t->size() < 2) continue; size_t index = (size_t)(t - C.begin()); - bool is_inside = (p[index] == ContourPosition::INSIDE); - Contour::const_iterator c = t->begin(); + bool is_inside = (p[index] == PolygonSide::INSIDE); + // Write origin polygon + Polygon::const_iterator c = t->begin(); svg << "x << "," << c->y << " L"; c++; for (; c != t->end(); c++) { svg << c->x << "," << c->y << " "; } svg << "z\" fill=\"transparent\" stroke=\"#" << (is_inside ? "f00" : "000") << "\" stroke-width=\"0.1\" stroke-linejoin=\"round\"/>" << std::endl; + // Write a convex-hull polygon + if (show_convexhull) { + Polygon convex = convexhull(*t); + if (convex.size() >= 3) { + c = convex.begin(); + svg << "x << "," << c->y << " L"; + c++; + for (; c != convex.end(); c++) { + svg << c->x << "," << c->y << " "; + } + svg << "z\" stroke-dasharray=\"1,1\" fill=\"transparent\" stroke=\"#" << (is_inside ? "d33" : "666") << "\" stroke-width=\"0.1\" stroke-linejoin=\"round\"/>" << std::endl; + } + } } svg << "" << std::endl; svg.close(); return true; } -} -#endif \ No newline at end of file + + // Measure feret diameter + void measure_feret_and_shape(const slice::ContourSlice& CS, std::vector& minFeret, std::vector& maxFeret, std::vector (&shapes)[5]) { + // Foreach slice in 3D + for (slice::ContourSlice::const_iterator cs = CS.begin(); cs != CS.end(); cs++) { + if (cs->empty()) continue; + // Inside or outside testing + slice::PolygonSides p = contour_inside_test(*cs); + assert(p.size() == cs->size()); + // For each contour in slice, calculate min max of contour + std::vector minX, minY, maxX, maxY; + std::vector sortedMinX, sortedMinY, sortedMaxX, sortedMaxY; + size_t n_outside = 0; + for (slice::Polygons::const_iterator c = cs->begin(); c != cs->end(); c++) { + // if polygon must have more-than 2 lines + if (c->size() <= 2) continue; + size_t index_polygon = (size_t)(c - cs->begin()); + + // Loop for each point to find max min in polygon + slice::Polygon::const_iterator l = c->begin(); + double _minX = l->x, _minY = l->y, _maxX = l->x, _maxY = l->y; + for (l++; l != c->end(); l++) { + if (l->x > _maxX) { + _maxX = l->x; + } + if (_minX > l->x) { + _minX = l->x; + } + if (l->y > _maxY) { + _maxY = l->y; + } + if (_minY > l->y) { + _minY = l->y; + } + } + minX.push_back(_minX); + maxX.push_back(_maxX); + minY.push_back(_minY); + maxY.push_back(_maxY); + + /* + if (DOUBLE_GT(h, 0) && DOUBLE_GT(w, 0)) { + double shape[5] = { area, 4 * area / (M_PI * h * h), 2 * area / (w * h), 4 * area / (M_PI * w * h), P / l }; + }*/ + // count the outside polygon + if (p[index_polygon] == slice::PolygonSide::OUTSIDE) { + // the polygon is the boundary of solid; + n_outside++; + sortedMinX.push_back(_minX); + sortedMaxX.push_back(_maxX); + sortedMinY.push_back(_minY); + sortedMaxY.push_back(_maxY); + } + else { + double area = measure_polygon_area(*c); + FeretDiameter feret = measure_polygon_feret_diameter(*c); + double w = feret.min, h = feret.perpendicularMax; + // calculate Podczeck's shape description + // from: https://diplib.github.io/diplib-docs/features.html#shape_features_PodczeckShapes + // the polygon is the boundary of a hole; push the feret diameter to the result + minFeret.push_back(feret.min); + maxFeret.push_back(feret.perpendicularMax); + shapes[0].push_back(area / (w * h)); + shapes[1].push_back(4 * area / (M_PI * h * h)); + shapes[2].push_back(2 * area / (w * h)); + shapes[3].push_back(4 * area / (M_PI * w * h)); + shapes[4].push_back(feret.perimeter / feret.max); + } + } + + // sorting for searching performances + std::sort(sortedMinX.begin(), sortedMinX.end()); + std::sort(sortedMaxX.begin(), sortedMaxX.end()); + std::sort(sortedMinY.begin(), sortedMinY.end()); + std::sort(sortedMaxY.begin(), sortedMaxY.end()); + + // For each contour in slice, calculate feret + if (n_outside > 1) { + std::vector feret; + for (slice::Polygons::const_iterator c = cs->begin(); c != cs->end(); c++) { + size_t index = (size_t)(c - cs->begin()); + if (p[index] == slice::PolygonSide::OUTSIDE) { + // Left direction: find previous maxX of left neighbor < minX + auto lower = std::lower_bound(sortedMaxX.begin(), sortedMaxX.end(), minX[index]); + if (lower != sortedMaxX.end() && lower != sortedMaxX.begin()) feret.push_back(minX[index] - *(lower - 1)); + // Right direction: find next minX of right neightbor > maxX + auto upper = std::upper_bound(sortedMinX.begin(), sortedMinX.end(), maxX[index]); + if (upper != sortedMinX.end()) feret.push_back(*upper - maxX[index]); + // Upper direction; find previous maxY of upper neighbor < minY + lower = std::lower_bound(sortedMaxY.begin(), sortedMaxY.end(), minY[index]); + if (lower != sortedMaxY.end() && lower != sortedMaxY.begin()) feret.push_back(minY[index] - *(lower - 1)); + // lower direction; find next minY of lower neightbor > maxY + upper = std::upper_bound(sortedMinY.begin(), sortedMinY.end(), maxY[index]); + if (upper != sortedMinY.end()) feret.push_back(*upper - maxY[index]); + } + } + std::sort(feret.begin(), feret.end()); + if (feret.size() > 0) { + minFeret.push_back(feret.front()); + maxFeret.push_back(feret.back()); + } + } + } + } +} \ No newline at end of file diff --git a/include/Scaffolder_2.h b/include/Scaffolder_2.h index c53e1bb..33ef16f 100644 --- a/include/Scaffolder_2.h +++ b/include/Scaffolder_2.h @@ -1,13 +1,14 @@ #pragma once +#include +#include +#include +#include + #include #include #include #include #include -#include -#include -#include -#include #include "cxxopts.hpp" #include "diplib.h" @@ -15,8 +16,8 @@ #include "diplib/regions.h" #include "diplib/measurement.h" #include "dualmc/dualmc.h" -//#include "H5Easy.hpp" #include "ProgressBar.hpp" +#include "OptimalSlice.hpp" #include "implicit_function.h" #include "Mesh.h" @@ -25,6 +26,9 @@ #define VERSION "v1.2" #define PROGRESS_BAR_COLUMN 40 +#define METHOD_IMAGE_PROCESS 0 +#define METHOD_SLICE_CONTOUR 1 + typedef struct index_type { size_t x; size_t y; size_t z; } index_type; @@ -125,15 +129,37 @@ inline void clean_mesh(TMesh& mesh, double minimum_diameter, uint16_t smooth_ste vcg::tri::Clean::RemoveSmallConnectedComponentsDiameter(mesh, minimum_diameter * mesh.bbox.Diag()); vcg::tri::Clean::RemoveUnreferencedVertex(mesh); vcg::tri::UpdateTopology::FaceFace(mesh); - if (verbose) { - std::cout - << "OK" << std::endl - << "-- IsWaterTight: " << vcg::tri::Clean::IsWaterTight(mesh) << std::endl - << "-- Minimum_diameter: " << minimum_diameter * mesh.bbox.Diag() << std::endl; - } + vcg::tri::Clean::RemoveDuplicateVertex(mesh); + vcg::tri::Allocator::CompactEveryVector(mesh); + vcg::tri::UpdateTopology::FaceFace(mesh); + vcg::tri::UpdateBounding::Box(mesh); + vcg::tri::Clean::MergeCloseVertex(mesh, mesh.bbox.Diag()/10000); + vcg::tri::UpdateTopology::FaceFace(mesh); + if (verbose) std::cout << "OK" << std::endl; if (smooth_step > 0) { if (verbose) std::cout << "[Laplacian smoothing] "; vcg::tri::Smooth::VertexCoordLaplacian(mesh, smooth_step, false, true); if (verbose) std::cout << "OK" << std::endl; } +} + +inline void report_mesh(TMesh& mesh) { + int connectedComponentsNum = vcg::tri::Clean::CountConnectedComponents(mesh); + std::cout + << "[Topology Measurement] " << std::endl + << "-- Mesh is composed by " << connectedComponentsNum << " connected component(s)" << std::endl; + + int edgeNum = 0, edgeBorderNum = 0, edgeNonManifoldNum = 0; + vcg::tri::Clean::CountEdgeNum(mesh, edgeNum, edgeBorderNum, edgeNonManifoldNum); + int vertManifNum = vcg::tri::Clean::CountNonManifoldVertexFF(mesh, true); + + if (edgeNonManifoldNum == 0 && vertManifNum == 0) { + int holeNum = vcg::tri::Clean::CountHoles(mesh); + int genus = vcg::tri::Clean::MeshGenus(mesh.vn, edgeNum, mesh.fn, holeNum, connectedComponentsNum); + + std::cout + << "-- Mesh is two-manifold " << std::endl + << "-- Mesh has " << holeNum << " holes" << std::endl + << "-- Genus is " << genus << std::endl; + } } \ No newline at end of file diff --git a/src/Main.cpp b/src/Main.cpp index 185d952..01759f7 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -14,6 +14,7 @@ int main(int argc, char* argv[]) uint16_t grid_offset = 3; uint16_t shell = 0; uint16_t smooth_step = 5; + uint8_t method = METHOD_IMAGE_PROCESS; double thickness = 0.0; size_t minimum_grid_size = 100; double coff = pi; @@ -32,9 +33,9 @@ int main(int argc, char* argv[]) options.add_options() ("h,help", "Print help") ("q,quiet", "Disable verbose output [default: false]") - ("m,microstructure", "Analysis microstructure [default: false]") - ("m2", "Export and analysis microstructure [default: false]") - ("m3", "Export feret data") + ("m,microstructure", "Analysis microstructure ( [default: false]") + ("m1", "Export and analysis microstructure 1 (Image processing technique) [default: false]") + ("m2", "Export and analysis microstructure 2 (Slice coutour technique) [default: false]") ("f,format", "Output format (OFF,PLY,STL,OBJ) [default: ply]", cxxopts::value()) ("i,input", "Input file (STL)", cxxopts::value(), "FILE") ("o,output", "Output filename without extension [default: out]", cxxopts::value(), "FILENAME") @@ -45,6 +46,7 @@ int main(int argc, char* argv[]) ("g,grid_size", "Grid size [default: 100]", cxxopts::value(), "INT") ("grid_offset", "[default:3]", cxxopts::value(), "INT") ("smooth_step", "Smooth with laplacian (default: 5)", cxxopts::value(), "INT") + ("method", "Method of microstructure analysis: 0 (Image processing technique) or 1 (Slice contour technique) [default: 0]", cxxopts::value(), "0,1") ("output_inverse", "additional output inverse scaffold") ("inverse", "Enable build inverse 3D scaffold (for pore connectivity analysis)") ("dirty", "Disable autoclean") @@ -69,15 +71,19 @@ int main(int argc, char* argv[]) is_analysis_microstructure = result["microstructure"].as(); no_output = true; } + if (result.count("method")) method = result["method"].as(); + if (result.count("m1")) { + is_analysis_microstructure = result["m1"].as(); + is_export_microstructure = result["m1"].as(); + method = METHOD_IMAGE_PROCESS; + no_output = true; + } if (result.count("m2")) { is_analysis_microstructure = result["m2"].as(); is_export_microstructure = result["m2"].as(); + method = METHOD_SLICE_CONTOUR; no_output = true; } - if (result.count("m3")) { - is_export_feret = result["m3"].as(); - is_analysis_microstructure = is_export_feret; - } if (result.count("output_inverse")) { is_export_inverse = result["output_inverse"].as(); } @@ -306,6 +312,12 @@ int main(int argc, char* argv[]) marching_cube(mesh, Fxyz, grid_size, V1min1, grid_delta, verbose, dirty); if (is_build_inverse) marching_cube(inverse_mesh, IFxyz, grid_size, V1min1, grid_delta, false, false); + if (!dirty) { + clean_mesh(mesh, minimum_diameter, smooth_step, verbose); + if (is_build_inverse) clean_mesh(inverse_mesh, minimum_diameter, smooth_step, false); + } + if (verbose) report_mesh(mesh); + if (is_analysis_microstructure) { // Evaluating pore size by create 2D slice 8-bit image (0-blacks're pores and 255-whites're grains) // Then an 8-bit image become a binary image by image thresholding (value of 150) @@ -326,78 +338,93 @@ int main(int argc, char* argv[]) } // init progress bar ProgressBar progress(grid_size.sum(), PROGRESS_BAR_COLUMN); - // init DIP MeasurementTool and DIP image - dip::MeasurementTool tool; - dip::Image img2d({ (dip::uint64)grid_size.maxCoeff(), (dip::uint64)grid_size.maxCoeff() }, 1, dip::DT_UINT8); - dip::uint8* data = static_cast(img2d.Origin()); - char axis = 'x'; - for (uint8_t main_axis = 0; main_axis < 3; main_axis++) { - // main_axis: 0 = x, 1 = y, 2 = z - size_t index; - uint8_t axis2 = (main_axis + 1) % 3; - uint8_t axis3 = (main_axis + 2) % 3; - for (size_t k = 0; k < grid_size(main_axis); k++) { - img2d.Fill(200); - for (size_t j = 0; j < grid_size(axis2); j++) { - for (size_t i = 0; i < grid_size(axis3); i++) { - if (main_axis == 0) - index = k + grid_size(0) * (j + grid_size(1) * i); - else if (main_axis == 1) - index = i + grid_size(0) * (k + grid_size(1) * j); - else - index = j + grid_size(0) * (i + grid_size(1) * k); - if (W(index) >= 0.8) {// inside STL mesh - if (Fxyz(index) > eps2) - data[i + grid_size(axis3) * j] = 0; + + if (method == METHOD_IMAGE_PROCESS) { + // init DIP MeasurementTool and DIP image + dip::MeasurementTool tool; + dip::Image img2d({ (dip::uint64)grid_size.maxCoeff(), (dip::uint64)grid_size.maxCoeff() }, 1, dip::DT_UINT8); + dip::uint8* data = static_cast(img2d.Origin()); + char axis = 'x'; + for (uint8_t main_axis = 0; main_axis < 3; main_axis++) { + // main_axis: 0 = x, 1 = y, 2 = z + size_t index; + uint8_t axis2 = (main_axis + 1) % 3; + uint8_t axis3 = (main_axis + 2) % 3; + for (size_t k = 0; k < grid_size(main_axis); k++) { + img2d.Fill(200); + for (size_t j = 0; j < grid_size(axis2); j++) { + for (size_t i = 0; i < grid_size(axis3); i++) { + if (main_axis == 0) + index = k + grid_size(0) * (j + grid_size(1) * i); + else if (main_axis == 1) + index = i + grid_size(0) * (k + grid_size(1) * j); + else + index = j + grid_size(0) * (i + grid_size(1) * k); + if (W(index) >= 0.8) {// inside STL mesh + if (Fxyz(index) > eps2) + data[i + grid_size(axis3) * j] = 0; + } } } + // Measurement Feret diameter + dip::Image label = dip::Label(img2d < 50, 2); + dip::Measurement msr = tool.Measure(label, img2d, { "Feret", "PodczeckShapes" }, {}, 2); + dip::Measurement::IteratorFeature it = msr["Feret"]; + dip::Measurement::IteratorFeature::Iterator feret = it.FirstObject(); + while (feret) { + // From ref: https://diplib.github.io/diplib-docs/features.html#size_features_Feret + maxFeret.push_back(feret[2] * grid_delta); + minFeret.push_back(feret[1] * grid_delta); + ++feret; + } + it = msr["PodczeckShapes"]; + dip::Measurement::IteratorFeature::Iterator shape = it.FirstObject(); + while (shape) { + podczeckShapes[0].push_back(shape[0]); + podczeckShapes[1].push_back(shape[1]); + podczeckShapes[2].push_back(shape[2]); + podczeckShapes[3].push_back(shape[3]); + podczeckShapes[4].push_back(shape[4]); + ++shape; + } + if (is_export_microstructure) { + filename.str(std::string()); + filename << dir << '/' << axis << '_' << k << ".jpg"; + dip::ImageWriteJPEG(img2d, filename.str()); + } + ++progress; + progress.display(); } - // Measurement Feret diameter - dip::Image label = dip::Label(img2d < 50, 2); - dip::Measurement msr = tool.Measure(label, img2d, { "Feret", "PodczeckShapes" }, {}, 2); - dip::Measurement::IteratorFeature it = msr["Feret"]; - dip::Measurement::IteratorFeature::Iterator feret = it.FirstObject(); - while (feret) { - // From ref: https://diplib.github.io/diplib-docs/features.html#size_features_Feret - maxFeret.push_back(feret[2]*grid_delta); - minFeret.push_back(feret[1]*grid_delta); - ++feret; - } - it = msr["PodczeckShapes"]; - dip::Measurement::IteratorFeature::Iterator shape = it.FirstObject(); - while (shape) { - podczeckShapes[0].push_back(shape[0]); - podczeckShapes[1].push_back(shape[1]); - podczeckShapes[2].push_back(shape[2]); - podczeckShapes[3].push_back(shape[3]); - podczeckShapes[4].push_back(shape[4]); - ++shape; - } + axis++; + } + } // End Image processing + else if (method == METHOD_SLICE_CONTOUR) { + vcg::tri::UpdateBounding::Box(mesh); + vcg::Box3d bbox = mesh.bbox; + vcg::Point3d dim = bbox.Dim(); + + char axis = 'x'; + for (uint8_t main_axis = 0; main_axis < 3; main_axis++) { + // main_axis: 0 = x, 1 = y, 2 = z + size_t index; + slice::Slice s = slice::incremental_slicing(mesh, grid_size(main_axis), main_axis); + slice::ContourSlice C = slice::contour_construct(s); + measure_feret_and_shape(C, minFeret, maxFeret, podczeckShapes); if (is_export_microstructure) { filename.str(std::string()); - filename << dir << '/' << axis << '_' << k << ".jpg"; - dip::ImageWriteJPEG(img2d, filename.str()); + for (slice::ContourSlice::const_iterator cs = C.begin(); cs != C.end(); cs++) { + size_t index = (size_t)(cs - C.begin() + 1); + std::cout << "Contour slice " << index << " size: " << cs->size() << " contour(s)" << std::endl; + std::stringstream name(dir); + name << dir << "/" << axis << '_' << index << ".svg"; + slice::write_svg(name.str(), *cs, dim[0], dim[1], bbox.min[0], bbox.min[1]); + } } ++progress; progress.display(); + axis++; } - axis++; - } - if (is_export_feret) { - std::string f = filename.str() + ".csv"; - std::ofstream fs(f.c_str(), std::ofstream::out); - fs << "MIN"; - for (std::vector::iterator it = minFeret.begin(); it != minFeret.end(); it++) { - fs << "," << *it; - } - fs << std::endl; - fs << "MAX"; - for (std::vector::iterator it = maxFeret.begin(); it != maxFeret.end(); it++) { - fs << "," << *it; - } - fs << std::endl; - fs.close(); - } + } // End Slice contour progress.done(); if (minFeret.size() > 0 && maxFeret.size() > 0) { std::sort(minFeret.begin(), minFeret.end()); @@ -450,13 +477,8 @@ int main(int argc, char* argv[]) } } - // Stage 2: Cleaning + // Stage 2: Result { - if (!dirty) { - clean_mesh(mesh, minimum_diameter, smooth_step, verbose); - if (is_build_inverse) clean_mesh(inverse_mesh, minimum_diameter, smooth_step, false); - } - // Report Volume and surface area int edgeNum = 0, edgeBorderNum = 0, edgeNonManifNum = 0; if (is_build_inverse) { diff --git a/src/SliceTest.cpp b/src/SliceTest.cpp index 6187559..cd5fc40 100644 --- a/src/SliceTest.cpp +++ b/src/SliceTest.cpp @@ -18,6 +18,7 @@ int main(int argc, char* argv[]) std::string input_file = ""; size_t slice = 10; int direction = 2; + bool is_export_convexhull = false; try { cxxopts::Options options("SliceTest", "Test"); options.positional_help("[option args]").show_positional_help(); @@ -25,7 +26,8 @@ int main(int argc, char* argv[]) ("h,help", "Print help") ("i,input", "Input file (STL)", cxxopts::value(), "FILE") ("s,slice", "Number of slice > 1 (Default: 10)", cxxopts::value(), "POSITVE INTEGER MORE THAN 1") - ("d,direction", "Direction 0=X, 1=Y, 2=Z default: 2)", cxxopts::value(), "{0,1,2}"); + ("d,direction", "Direction 0=X, 1=Y, 2=Z default: 2)", cxxopts::value(), "{0,1,2}") + ("x,convexhull", "Export convexhull to SVG", cxxopts::value()); options.parse_positional({ "input", "slice", "direction" }); bool isEmptyOption = (argc == 1); cxxopts::ParseResult result = options.parse(argc, argv); @@ -47,12 +49,15 @@ int main(int argc, char* argv[]) if (direction < 0 && direction > 2) { std::cout << "Invalid direction: " << direction << std::endl; } + if (result.count("convexhull")) is_export_convexhull = result["convexhull"].as(); } catch (const cxxopts::OptionException & ex) { std::cout << "Error parsing options: " << ex.what() << std::endl; return 1; } - + //slice::Polygon p = { {0,0}, {10,0}, {20,10}, {21, 20}, {10, 30}, {4, 31}, {-10, 22}, {-1, 20}, {8,18}, {2,6}, {-5,5} }; + //slice::Polygon p2 = { {-20,0}, {0,-20}, {20,0}, {0,20} }; + // Stage 1: { try { @@ -64,6 +69,8 @@ int main(int argc, char* argv[]) vcg::tri::UpdateBounding::Box(stl); vcg::Box3d bbox = stl.bbox; vcg::Point3d dim = bbox.Dim(); + int next_direction = ((direction + 1) % 3); + int prev_direction = ((direction + 2) % 3); vcg::tri::Clean::RemoveDuplicateVertex(stl); vcg::tri::Allocator::CompactEveryVector(stl); @@ -73,6 +80,8 @@ int main(int argc, char* argv[]) vcg::tri::Clean::MergeCloseVertex(stl, SLICE_PRECISION*100); std::cout << "Merge close vertex" << std::endl; vcg::tri::UpdateTopology::FaceFace(stl); + vcg::tri::Clean::RemoveDuplicateFace(stl); + vcg::tri::UpdateTopology::FaceFace(stl); std::cout << "Update topology" << std::endl; int connectedComponentsNum = vcg::tri::Clean::CountConnectedComponents(stl); @@ -90,7 +99,7 @@ int main(int argc, char* argv[]) } slice::Slice s = slice::incremental_slicing(stl, slice, direction); - slice::ContourSlice C = slice::contour_construct(s); + slice::ContourSlice C = slice::contour_construct(s, direction); // create output directory if (!C.empty()) { @@ -99,15 +108,33 @@ int main(int argc, char* argv[]) size_t lastindex = input_file.find_last_of("."); std::string dir = input_file.substr(firstindex, lastindex - firstindex) + "_svg"; std::cout << "Creating direction: " << dir << std::endl; - make_dir(dir); + //make_dir(dir); +#ifndef USE_PARALLEL for (slice::ContourSlice::const_iterator cs = C.begin(); cs != C.end(); cs++) { size_t index = (size_t)(cs - C.begin() + 1); - std::cout << "Contour slice " << index << " size: " << cs->size() << " contour(s)" << std::endl; std::stringstream name(dir); name << dir << "/" << index << ".svg"; - slice::write_svg(name.str(), *cs, dim[0], dim[1], bbox.min[0], bbox.min[1]); + slice::write_svg(name.str(), *cs, dim[next_direction], dim[prev_direction], bbox.min[next_direction], bbox.min[prev_direction], is_export_convexhull); } +#else + tbb::parallel_for( + tbb::blocked_range(0, C.size()), + [&](const tbb::blocked_range &r) { + for (size_t index = r.begin(); index < r.end(); index++) { + std::stringstream name(dir); + name << dir << "/" << (index) << ".svg"; + slice::write_svg(name.str(), C[index], dim[next_direction], dim[prev_direction], bbox.min[next_direction], bbox.min[prev_direction], is_export_convexhull); + } + } + ); +#endif + std::vector minFeret, maxFeret, shape[5]; + slice::measure_feret_and_shape(C, minFeret, maxFeret, shape); + std::copy(minFeret.begin(), minFeret.end(), std::ostream_iterator(std::cout, " ")); + std::cout << std::endl; + std::copy(maxFeret.begin(), maxFeret.end(), std::ostream_iterator(std::cout, " ")); + std::cout << std::endl; } } From 024708f8f1c64463b9180a6c65ee86ce57f5bb35 Mon Sep 17 00:00:00 2001 From: Jirawat I Date: Fri, 6 Mar 2020 01:55:43 +0700 Subject: [PATCH 09/20] fixed macos build --- README.md | 24 +++++++++++++---- cmake/FindDIPlib.cmake | 3 +-- include/OptimalSlice.hpp | 32 +++++++++++++--------- include/ProgressBar.hpp | 1 + include/Scaffolder_2.h | 7 +++-- src/Main.cpp | 57 +++++++++++++++++++++++++++++----------- 6 files changed, 85 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 9b939b2..36a9895 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,12 @@ Usage: Scaffolder [OPTION...] [option args] -h, --help Print help - -q, --quiet Disable verbose output + -q, --quiet Disable verbose output [default: false] + -m, --microstructure Analysis microstructure ( [default: false] + --m1 Export and analysis microstructure 1 (Image + processing technique) [default: false] + --m2 Export and analysis microstructure 2 (Slice + coutour technique) [default: false] -f, --format arg Output format (OFF,PLY,STL,OBJ) [default: ply] -i, --input FILE Input file (STL) @@ -15,11 +20,20 @@ Usage: out] -c, --coff DOUBLE default:4*PI -s, --shell INT [default:0] - -n, --surface NAME schwarzp, schwarzd, gyroid, lidinoid, - schoen_iwp, neovius, pwhybrid [default: schwarzp] - -t, --thickness DOUBLE Thickness [default: 1.0] + -n, --surface NAME rectlinear, schwarzp, schwarzd, gyroid, + double-p, double-d, double-gyroiod, lidinoid, + schoen_iwp, neovius, bcc, tubular_g_ab, tubular_g_c + [default: schwarzp] + -t, --thickness DOUBLE Thickness [default: 0] -g, --grid_size INT Grid size [default: 100] - --grid_offset INT [default:2] + --grid_offset INT [default:3] + --smooth_step INT Smooth with laplacian (default: 5) + --method 0,1 Method of microstructure analysis: 0 (Image + processing technique) or 1 (Slice contour + technique) [default: 0] + --output_inverse additional output inverse scaffold + --inverse Enable build inverse 3D scaffold (for pore + connectivity analysis) --dirty Disable autoclean --minimum_diameter DOUBLE used for removing small orphaned (between diff --git a/cmake/FindDIPlib.cmake b/cmake/FindDIPlib.cmake index 4da73e8..e55c58a 100644 --- a/cmake/FindDIPlib.cmake +++ b/cmake/FindDIPlib.cmake @@ -29,11 +29,10 @@ set(DIP_BUILD_DIPIMAGE OFF CACHE INTERNAL "") set(DIP_BUILD_JAVAIO OFF CACHE INTERNAL "") set(DIP_BUILD_PYDIP OFF CACHE INTERNAL "") set(DIP_SHARED_LIBRARY OFF CACHE INTERNAL "") -set(DIP_ENABLE_ICS OFF CACHE INTERNAL "") set(DIP_ENABLE_UNICODE OFF CACHE INTERNAL "") set(DIP_ENABLE_DOCTEST OFF CACHE INTERNAL "") set(DIP_ENABLE_STACK_TRACE OFF CACHE INTERNAL "") -set(DIP_ENABLE_MULTITHREADING ON CACHE INTERNAL "") +#set(DIP_ENABLE_MULTITHREADING ON CACHE INTERNAL "") list(APPEND CMAKE_MODULE_PATH "${DIPlib_ROOT_DIR}") message(STATUS "USE DIR: ${DIPlib_ROOT_DIR}") diff --git a/include/OptimalSlice.hpp b/include/OptimalSlice.hpp index deba4ce..cf5aaad 100644 --- a/include/OptimalSlice.hpp +++ b/include/OptimalSlice.hpp @@ -218,6 +218,7 @@ namespace slice { class FeretDiameter { public: + bool empty = true; double min; double max; double perpendicularMax; @@ -237,6 +238,7 @@ namespace slice { max = d2; angleMax = lines[1].theta; } perpendicularMax = max; + empty = false; } void update(const SupportLine2d (&lines)[4]) { double d1 = two_line_distance(lines[0], lines[2]); @@ -682,7 +684,7 @@ namespace slice { size_t i_next = (i == s - 1) ? 0 : i + 1; A2 += C[i].x * (C[i_next].y - C[i_prev].y); } - return A2 * 0.5; + return abs(A2) * 0.5; } double measure_point_square_distance(const Point2d& p1, const Point2d& p2) { @@ -894,7 +896,7 @@ namespace slice { // For each contour in slice, calculate min max of contour std::vector minX, minY, maxX, maxY; std::vector sortedMinX, sortedMinY, sortedMaxX, sortedMaxY; - size_t n_outside = 0; + size_t n_outside = 0, n_inside = 0; for (slice::Polygons::const_iterator c = cs->begin(); c != cs->end(); c++) { // if polygon must have more-than 2 lines if (c->size() <= 2) continue; @@ -936,19 +938,23 @@ namespace slice { sortedMaxY.push_back(_maxY); } else { + n_inside++; double area = measure_polygon_area(*c); FeretDiameter feret = measure_polygon_feret_diameter(*c); - double w = feret.min, h = feret.perpendicularMax; - // calculate Podczeck's shape description - // from: https://diplib.github.io/diplib-docs/features.html#shape_features_PodczeckShapes - // the polygon is the boundary of a hole; push the feret diameter to the result - minFeret.push_back(feret.min); - maxFeret.push_back(feret.perpendicularMax); - shapes[0].push_back(area / (w * h)); - shapes[1].push_back(4 * area / (M_PI * h * h)); - shapes[2].push_back(2 * area / (w * h)); - shapes[3].push_back(4 * area / (M_PI * w * h)); - shapes[4].push_back(feret.perimeter / feret.max); + if (!feret.empty && feret.min > 0) { + double w = feret.min, h = feret.perpendicularMax; + assert(w > 0 && h > 0 && area > 0); + // calculate Podczeck's shape description + // from: https://diplib.github.io/diplib-docs/features.html#shape_features_PodczeckShapes + // the polygon is the boundary of a hole; push the feret diameter to the result + minFeret.push_back(feret.min); + maxFeret.push_back(feret.perpendicularMax); + shapes[0].push_back(area / (w * h)); + shapes[1].push_back(4 * area / (M_PI * h * h)); + shapes[2].push_back(2 * area / (w * h)); + shapes[3].push_back(4 * area / (M_PI * w * h)); + shapes[4].push_back(feret.perimeter / feret.max); + } } } diff --git a/include/ProgressBar.hpp b/include/ProgressBar.hpp index 79c01fe..608ff2a 100644 --- a/include/ProgressBar.hpp +++ b/include/ProgressBar.hpp @@ -21,6 +21,7 @@ class ProgressBar { ProgressBar(unsigned int total, unsigned int width) : total_ticks{ total }, bar_width{ width } {} unsigned int operator++() { return ++ticks; } + ProgressBar& operator+=(const unsigned int tick) { ticks += tick; return *this; } void display() const { diff --git a/include/Scaffolder_2.h b/include/Scaffolder_2.h index 33ef16f..f2eca64 100644 --- a/include/Scaffolder_2.h +++ b/include/Scaffolder_2.h @@ -82,7 +82,7 @@ inline bool MarkAndSweepNeighbor(Eigen::VectorXd& W, index_type& index, Queue_t& inline void marching_cube(TMesh &mesh, Eigen::MatrixXd &Fxyz, Eigen::RowVector3i grid_size, Eigen::RowVector3d &Vmin, double delta, bool verbose = true, bool dirty = false) { { - if (verbose) std::cout << "[Marching Cube] "; + if (verbose) std::cout << "[Marching Cube] " << std::endl; dualmc::DualMC builder; std::vector mc_vertices; std::vector mc_quads; @@ -114,7 +114,6 @@ inline void marching_cube(TMesh &mesh, Eigen::MatrixXd &Fxyz, Eigen::RowVector3i vcg::tri::UpdateTopology::FaceFace(mesh); } - if (verbose) std::cout << "OK" << std::endl; if (verbose) std::cout << "-- Info: " << mc_vertices.size() << " vertices " << mc_quads.size() << " faces" << std::endl; } } @@ -133,14 +132,14 @@ inline void clean_mesh(TMesh& mesh, double minimum_diameter, uint16_t smooth_ste vcg::tri::Allocator::CompactEveryVector(mesh); vcg::tri::UpdateTopology::FaceFace(mesh); vcg::tri::UpdateBounding::Box(mesh); - vcg::tri::Clean::MergeCloseVertex(mesh, mesh.bbox.Diag()/10000); - vcg::tri::UpdateTopology::FaceFace(mesh); if (verbose) std::cout << "OK" << std::endl; if (smooth_step > 0) { if (verbose) std::cout << "[Laplacian smoothing] "; vcg::tri::Smooth::VertexCoordLaplacian(mesh, smooth_step, false, true); if (verbose) std::cout << "OK" << std::endl; } + vcg::tri::Clean::MergeCloseVertex(mesh, SLICE_PRECISION*100); //mesh.bbox.Diag() / 10000 + vcg::tri::UpdateTopology::FaceFace(mesh); } inline void report_mesh(TMesh& mesh) { diff --git a/src/Main.cpp b/src/Main.cpp index 01759f7..1f42134 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -14,7 +14,7 @@ int main(int argc, char* argv[]) uint16_t grid_offset = 3; uint16_t shell = 0; uint16_t smooth_step = 5; - uint8_t method = METHOD_IMAGE_PROCESS; + uint8_t method = METHOD_SLICE_CONTOUR; double thickness = 0.0; size_t minimum_grid_size = 100; double coff = pi; @@ -46,7 +46,7 @@ int main(int argc, char* argv[]) ("g,grid_size", "Grid size [default: 100]", cxxopts::value(), "INT") ("grid_offset", "[default:3]", cxxopts::value(), "INT") ("smooth_step", "Smooth with laplacian (default: 5)", cxxopts::value(), "INT") - ("method", "Method of microstructure analysis: 0 (Image processing technique) or 1 (Slice contour technique) [default: 0]", cxxopts::value(), "0,1") + ("method", "Method of microstructure analysis: 0 (Image processing technique) or 1 (Slice contour technique) [default: 1]", cxxopts::value(), "0,1") ("output_inverse", "additional output inverse scaffold") ("inverse", "Enable build inverse 3D scaffold (for pore connectivity analysis)") ("dirty", "Disable autoclean") @@ -72,6 +72,10 @@ int main(int argc, char* argv[]) no_output = true; } if (result.count("method")) method = result["method"].as(); + if (method < 0 && method > 1) { + std::cout << "Invalid method option: " << method << ". The method option must be only 0 or 1, see --help"; + return 1; + } if (result.count("m1")) { is_analysis_microstructure = result["m1"].as(); is_export_microstructure = result["m1"].as(); @@ -134,7 +138,9 @@ int main(int argc, char* argv[]) << "-- Smooth step: " << smooth_step << std::endl << "-- Autoclean: " << (dirty ? "False" : "True") << std::endl << "-- Minimum diameter: " << 100 * minimum_diameter << "%" << std::endl - << "-- Analysis microstructure: " << (is_analysis_microstructure || is_export_microstructure ? "True" : "False") << std::endl + << "-- Analysis microstructure: " << (is_analysis_microstructure ? "True" : "False") + << " Method=" << (method == METHOD_IMAGE_PROCESS ? "Image Processing" : "Contour Slicing") << std::endl + << "-- Export microstructure: " << (is_analysis_microstructure ? "True" : "False") << std::endl << "-- Build Inverse: " << (is_build_inverse ? "True" : "False") << std::endl << "-- Export Inverse: " << (is_export_inverse ? "True" : "False") << std::endl; } @@ -404,24 +410,42 @@ int main(int argc, char* argv[]) vcg::Point3d dim = bbox.Dim(); char axis = 'x'; + progress.display(); for (uint8_t main_axis = 0; main_axis < 3; main_axis++) { // main_axis: 0 = x, 1 = y, 2 = z size_t index; + int next_axis = ((main_axis + 1) % 3); + int prev_axis = ((main_axis + 2) % 3); + unsigned int progress_major_step = grid_size[main_axis] / 4; slice::Slice s = slice::incremental_slicing(mesh, grid_size(main_axis), main_axis); - slice::ContourSlice C = slice::contour_construct(s); - measure_feret_and_shape(C, minFeret, maxFeret, podczeckShapes); + progress += progress_major_step; + progress.display(); + slice::ContourSlice C = slice::contour_construct(s, main_axis); + progress += progress_major_step; + progress.display(); + slice::measure_feret_and_shape(C, minFeret, maxFeret, podczeckShapes); + progress += progress_major_step; + progress.display(); if (is_export_microstructure) { filename.str(std::string()); + unsigned int minor_step = C.size() / progress_major_step, step = minor_step; for (slice::ContourSlice::const_iterator cs = C.begin(); cs != C.end(); cs++) { size_t index = (size_t)(cs - C.begin() + 1); - std::cout << "Contour slice " << index << " size: " << cs->size() << " contour(s)" << std::endl; std::stringstream name(dir); name << dir << "/" << axis << '_' << index << ".svg"; - slice::write_svg(name.str(), *cs, dim[0], dim[1], bbox.min[0], bbox.min[1]); + slice::write_svg(name.str(), *cs, dim[next_axis], dim[prev_axis], bbox.min[next_axis], bbox.min[prev_axis]); + step--; + if (step == 0) { + step = minor_step; + ++progress; + progress.display(); + } } } - ++progress; - progress.display(); + else { + progress += progress_major_step; + progress.display(); + } axis++; } } // End Slice contour @@ -429,11 +453,14 @@ int main(int argc, char* argv[]) if (minFeret.size() > 0 && maxFeret.size() > 0) { std::sort(minFeret.begin(), minFeret.end()); std::sort(maxFeret.begin(), maxFeret.end()); - std::sort(podczeckShapes[0].begin(), podczeckShapes[0].end()); - std::sort(podczeckShapes[1].begin(), podczeckShapes[1].end()); - std::sort(podczeckShapes[2].begin(), podczeckShapes[2].end()); - std::sort(podczeckShapes[3].begin(), podczeckShapes[3].end()); - std::sort(podczeckShapes[4].begin(), podczeckShapes[4].end()); + for (int i = 0; i < 5; i++) { + if (podczeckShapes[i].empty()) { + podczeckShapes[i].push_back(0); + } + else { + std::sort(podczeckShapes[i].begin(), podczeckShapes[i].end()); + } + } if (verbose) { std::cout << "[Microstructure] " << std::endl << "-- Avg Min Feret: " << std::accumulate(minFeret.begin(), minFeret.end(), 0.0) / minFeret.size() << std::endl @@ -502,7 +529,7 @@ int main(int argc, char* argv[]) area2 = vcg::tri::Stat::ComputeMeshArea(mesh); volume2 = vcg::tri::Stat::ComputeMeshVolume(mesh); if (verbose) - std::cout << "[Scaffold properties]" + std::cout << "[Scaffold properties]" << std::endl << "-- Volume: " << abs(volume2) << std::endl << "-- Surface Area: " << area2 << std::endl << "-- Porosity: " << abs(volume2 / volume1) << std::endl From 3f3d91efcab915e1b28c6f0140d05594fd9e24af Mon Sep 17 00:00:00 2001 From: Jirawat I Date: Sun, 8 Mar 2020 18:48:00 +0700 Subject: [PATCH 10/20] fixed #3 --- include/OptimalSlice.hpp | 4 ++- include/Scaffolder_2.h | 28 ++++++++++++++++----- include/implicit_function.h | 4 +-- src/Main.cpp | 49 ++++++++++++++++++++----------------- src/SliceTest.cpp | 12 +++++---- 5 files changed, 60 insertions(+), 37 deletions(-) diff --git a/include/OptimalSlice.hpp b/include/OptimalSlice.hpp index cf5aaad..8441856 100644 --- a/include/OptimalSlice.hpp +++ b/include/OptimalSlice.hpp @@ -808,9 +808,10 @@ namespace slice { // the conhexhull's direction is counterclockwise and this procedure direction will be clockwise // the loop will finish when the initial upper-most point reached the bottom-most point size_t last_index = index_point[1] == 0 ? size - 1 : index_point[1] - 1; + size_t _count = 0; FeretDiameter feret(line); feret.perimeter = perimeter; - while (index_point[0] != last_index) { + while (index_point[0] != last_index && _count < size) { // measure the next minimal angle (min_theta) of 4 lines double min_angle = 359; size_t min_index = 0; @@ -839,6 +840,7 @@ namespace slice { //for (int i = 0; i < 4; i++) std::cout << " " << line[i] << std::endl; feret.update(line); //std::cout << feret << std::endl; + _count++; } return feret; } diff --git a/include/Scaffolder_2.h b/include/Scaffolder_2.h index f2eca64..a9e4bc6 100644 --- a/include/Scaffolder_2.h +++ b/include/Scaffolder_2.h @@ -23,7 +23,7 @@ #include "Mesh.h" #include "utils.h" -#define VERSION "v1.2" +#define VERSION "v1.3" #define PROGRESS_BAR_COLUMN 40 #define METHOD_IMAGE_PROCESS 0 @@ -120,17 +120,18 @@ inline void marching_cube(TMesh &mesh, Eigen::MatrixXd &Fxyz, Eigen::RowVector3i inline void clean_mesh(TMesh& mesh, double minimum_diameter, uint16_t smooth_step, bool verbose = true) { if (verbose) std::cout << "[libVCG Cleaning] "; + vcg::tri::Clean::RemoveDuplicateVertex(mesh); + vcg::tri::Allocator::CompactEveryVector(mesh); + vcg::tri::UpdateTopology::FaceFace(mesh); + vcg::tri::Clean::RemoveDuplicateFace(mesh); + vcg::tri::UpdateTopology::FaceFace(mesh); vcg::tri::Clean::RemoveZeroAreaFace(mesh); - vcg::tri::Clean::RemoveNonManifoldFace(mesh); vcg::tri::Clean::RemoveUnreferencedVertex(mesh); vcg::tri::UpdateTopology::FaceFace(mesh); vcg::tri::UpdateBounding::Box(mesh); vcg::tri::Clean::RemoveSmallConnectedComponentsDiameter(mesh, minimum_diameter * mesh.bbox.Diag()); vcg::tri::Clean::RemoveUnreferencedVertex(mesh); vcg::tri::UpdateTopology::FaceFace(mesh); - vcg::tri::Clean::RemoveDuplicateVertex(mesh); - vcg::tri::Allocator::CompactEveryVector(mesh); - vcg::tri::UpdateTopology::FaceFace(mesh); vcg::tri::UpdateBounding::Box(mesh); if (verbose) std::cout << "OK" << std::endl; if (smooth_step > 0) { @@ -138,8 +139,16 @@ inline void clean_mesh(TMesh& mesh, double minimum_diameter, uint16_t smooth_ste vcg::tri::Smooth::VertexCoordLaplacian(mesh, smooth_step, false, true); if (verbose) std::cout << "OK" << std::endl; } - vcg::tri::Clean::MergeCloseVertex(mesh, SLICE_PRECISION*100); //mesh.bbox.Diag() / 10000 + vcg::tri::Clean::MergeCloseVertex(mesh, SLICE_PRECISION*100); + vcg::tri::Clean::RemoveUnreferencedVertex(mesh); vcg::tri::UpdateTopology::FaceFace(mesh); + //vcg::tri::Clean::SelfIntersections(mesh) +} + +inline void fix_self_intersect_mesh(TMesh& mesh, uint16_t max_iteration = 10) { + std::vector faces; + vcg::tri::Clean::SelfIntersections(mesh, faces); + } inline void report_mesh(TMesh& mesh) { @@ -161,4 +170,11 @@ inline void report_mesh(TMesh& mesh) { << "-- Mesh has " << holeNum << " holes" << std::endl << "-- Genus is " << genus << std::endl; } +} + +inline bool is_mesh_manifold(TMesh &mesh) { + int edgeNum = 0, edgeBorderNum = 0, edgeNonManifoldNum = 0; + vcg::tri::Clean::CountEdgeNum(mesh, edgeNum, edgeBorderNum, edgeNonManifoldNum); + int vertManifNum = vcg::tri::Clean::CountNonManifoldVertexFF(mesh, true); + return edgeNonManifoldNum == 0 && vertManifNum == 0; } \ No newline at end of file diff --git a/include/implicit_function.h b/include/implicit_function.h index 70cca28..17649aa 100644 --- a/include/implicit_function.h +++ b/include/implicit_function.h @@ -78,7 +78,7 @@ class DoubleD : public Function { const FT t; DoubleD(FT t = 0) : t(t) {} FT operator ()(FT x, FT y, FT z) { - return -1 * (cos(x) * cos(y) + cos(y) * cos(z) + cos(x) * cos(z)) - 1 * (sin(x) * sin(y) * sin(z)) - t; + return -1 * (cos(x) * cos(y) + cos(y) * cos(z) + cos(x) * cos(z)) - 1 * (sin(x) * sin(y) * sin(z)) + t; } }; @@ -108,7 +108,7 @@ class Lidinoid : public Function { const FT t = 0; Lidinoid(FT t = 0) : t(t) {} FT operator ()(FT x, FT y, FT z) { - return -1 * ( + return ( (sin(2 * x) * cos(y) * sin(z) + sin(2 * y) * cos(z) * sin(x) + sin(2 * z) * cos(x) * sin(y)) + (cos(2 * x) * cos(2 * y) + cos(2 * y) * cos(2 * z) + cos(2 * z) * cos(2 * x)) ) + t; diff --git a/src/Main.cpp b/src/Main.cpp index 1f42134..26870a3 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -131,18 +131,18 @@ int main(int argc, char* argv[]) << "-- Output file: " << filename << '.' << format << std::endl << "-- Surface: " << surface << std::endl << "-- Coff: " << coff << std::endl - << "-- shell: " << shell << std::endl << "-- Thickness: " << thickness << std::endl << "-- Grid size: " << minimum_grid_size << std::endl - << "-- Grid offset: " << grid_offset << std::endl - << "-- Smooth step: " << smooth_step << std::endl + << "-- Grid offset: " << grid_offset << std::endl + << "-- Shell: " << shell << std::endl << "-- Autoclean: " << (dirty ? "False" : "True") << std::endl - << "-- Minimum diameter: " << 100 * minimum_diameter << "%" << std::endl - << "-- Analysis microstructure: " << (is_analysis_microstructure ? "True" : "False") - << " Method=" << (method == METHOD_IMAGE_PROCESS ? "Image Processing" : "Contour Slicing") << std::endl - << "-- Export microstructure: " << (is_analysis_microstructure ? "True" : "False") << std::endl + << "-- Minimum diameter: " << 100 * minimum_diameter << "%" << std::endl + << "-- Smooth step: " << smooth_step << std::endl + << "-- Analysis microstructure: " << (is_analysis_microstructure ? "True" : "False") << std::endl + << "-- Method :" << (method == METHOD_IMAGE_PROCESS ? "Image Processing" : "Contour Slicing") << std::endl + << "-- Export microstructure: " << (is_analysis_microstructure ? "True" : "False") << std::endl << "-- Build Inverse: " << (is_build_inverse ? "True" : "False") << std::endl - << "-- Export Inverse: " << (is_export_inverse ? "True" : "False") << std::endl; + << "-- Export Inverse: " << (is_export_inverse ? "True" : "False") << std::endl; } else { std::stringstream _name; @@ -182,16 +182,14 @@ int main(int argc, char* argv[]) TMesh stl; int loadmark = 0; vcg::tri::io::ImporterSTL::Open(stl, input_file.c_str(), loadmark); - if (!dirty) { - vcg::tri::Clean::RemoveDuplicateFace(stl); - vcg::tri::Clean::RemoveDuplicateVertex(stl); - vcg::tri::Clean::RemoveUnreferencedVertex(stl); - vcg::tri::UpdateTopology::FaceFace(stl); - vcg::tri::Clean::RemoveZeroAreaFace(stl); - vcg::tri::Clean::RemoveNonManifoldFace(stl); - vcg::tri::Clean::RemoveUnreferencedVertex(stl); - vcg::tri::UpdateTopology::FaceFace(stl); - } + vcg::tri::Clean::RemoveDuplicateFace(stl); + vcg::tri::Clean::RemoveDuplicateVertex(stl); + vcg::tri::Clean::RemoveUnreferencedVertex(stl); + vcg::tri::UpdateTopology::FaceFace(stl); + vcg::tri::Clean::RemoveZeroAreaFace(stl); + vcg::tri::Clean::RemoveNonManifoldFace(stl); + vcg::tri::Clean::RemoveUnreferencedVertex(stl); + vcg::tri::UpdateTopology::FaceFace(stl); // Report Volume and surface area int edgeNum = 0, edgeBorderNum = 0, edgeNonManifNum = 0; vcg::tri::Clean::CountEdgeNum(stl, edgeNum, edgeBorderNum, edgeNonManifNum); @@ -320,11 +318,15 @@ int main(int argc, char* argv[]) if (!dirty) { clean_mesh(mesh, minimum_diameter, smooth_step, verbose); - if (is_build_inverse) clean_mesh(inverse_mesh, minimum_diameter, smooth_step, false); + if (is_build_inverse) clean_mesh(inverse_mesh, minimum_diameter, 0, false); + } + bool is_manifold = is_mesh_manifold(mesh); + if (!is_manifold && !verbose) { + std::cout << "[Warning] Mesh is not two manifold" << std::endl; } if (verbose) report_mesh(mesh); - if (is_analysis_microstructure) { + if (is_analysis_microstructure && is_manifold) { // Evaluating pore size by create 2D slice 8-bit image (0-blacks're pores and 255-whites're grains) // Then an 8-bit image become a binary image by image thresholding (value of 150) // The binary imaege'll be labeled and finally evaluated the feret diameter by chain coding @@ -356,10 +358,10 @@ int main(int argc, char* argv[]) size_t index; uint8_t axis2 = (main_axis + 1) % 3; uint8_t axis3 = (main_axis + 2) % 3; - for (size_t k = 0; k < grid_size(main_axis); k++) { + for (size_t k = grid_offset; k < grid_size(main_axis) - (size_t)(grid_offset); k++) { img2d.Fill(200); - for (size_t j = 0; j < grid_size(axis2); j++) { - for (size_t i = 0; i < grid_size(axis3); i++) { + for (size_t j = grid_offset; j < grid_size(axis2) - (size_t)(grid_offset); j++) { + for (size_t i = grid_offset; i < grid_size(axis3) - (size_t)(grid_offset); i++) { if (main_axis == 0) index = k + grid_size(0) * (j + grid_size(1) * i); else if (main_axis == 1) @@ -567,6 +569,7 @@ int main(int argc, char* argv[]) vcg::tri::io::ExporterSTL::Save(mesh, filename.c_str(), 0); if (is_build_inverse && is_export_inverse) vcg::tri::io::ExporterSTL::Save(inverse_mesh, filename2.c_str(), 0); } + if (verbose) std::cout << "OK" << std::endl; } if (verbose) std::cout << "[Finished]" << std::endl; result.close(); diff --git a/src/SliceTest.cpp b/src/SliceTest.cpp index cd5fc40..ebf94b0 100644 --- a/src/SliceTest.cpp +++ b/src/SliceTest.cpp @@ -74,14 +74,16 @@ int main(int argc, char* argv[]) vcg::tri::Clean::RemoveDuplicateVertex(stl); vcg::tri::Allocator::CompactEveryVector(stl); - std::cout << "Removed duplicate face and vertex" << std::endl; vcg::tri::UpdateTopology::FaceFace(stl); + vcg::tri::Clean::RemoveDuplicateFace(stl); + vcg::tri::UpdateTopology::FaceFace(stl); + vcg::tri::Clean::RemoveZeroAreaFace(stl); + vcg::tri::UpdateTopology::FaceFace(stl); + std::cout << "Removed duplicate face and vertex" << std::endl; std::cout << "Update topology" << std::endl; - vcg::tri::Clean::MergeCloseVertex(stl, SLICE_PRECISION*100); + vcg::tri::Clean::MergeCloseVertex(stl, SLICE_PRECISION*10); std::cout << "Merge close vertex" << std::endl; vcg::tri::UpdateTopology::FaceFace(stl); - vcg::tri::Clean::RemoveDuplicateFace(stl); - vcg::tri::UpdateTopology::FaceFace(stl); std::cout << "Update topology" << std::endl; int connectedComponentsNum = vcg::tri::Clean::CountConnectedComponents(stl); @@ -108,7 +110,7 @@ int main(int argc, char* argv[]) size_t lastindex = input_file.find_last_of("."); std::string dir = input_file.substr(firstindex, lastindex - firstindex) + "_svg"; std::cout << "Creating direction: " << dir << std::endl; - //make_dir(dir); + make_dir(dir); #ifndef USE_PARALLEL for (slice::ContourSlice::const_iterator cs = C.begin(); cs != C.end(); cs++) { From 3f3d530ce55c953b4e6e56c8f73e0385db6a44ca Mon Sep 17 00:00:00 2001 From: Jirawat I Date: Wed, 11 Mar 2020 00:22:05 +0700 Subject: [PATCH 11/20] major fixed with slice analysis --- include/MaxHeap.hpp | 43 ++++ include/Mesh.h | 1 + include/OptimalSlice.hpp | 411 ++++++++++++++++++++++++++++++--------- include/Scaffolder_2.h | 88 ++++++++- src/Main.cpp | 25 ++- src/SliceTest.cpp | 76 +++++++- 6 files changed, 527 insertions(+), 117 deletions(-) create mode 100644 include/MaxHeap.hpp diff --git a/include/MaxHeap.hpp b/include/MaxHeap.hpp new file mode 100644 index 0000000..19843d1 --- /dev/null +++ b/include/MaxHeap.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include +#include + +template +class MaxHeap { + size_t size; + std::vector *arr; +public: + MaxHeap(size_t size, std::vector *arr) { + this->size = size; + this->arr = arr; + build(); + } + void heapify(size_t i) { + while (i < size / 2) { + size_t left = 2 * i + 1; + size_t right = 2 * i + 2; + size_t root = (*arr)[i] < (*arr)[left] ? left : i; + if (right < size) + root = (*arr)[root] < (*arr)[right] ? right : root; + if (root != i) { + std::swap((*arr)[root], (*arr)[i]); + i = root; + } + else { + return; + } + } + } + void build() { + for (size_t i = (size_t)(size / 2) - 1;; i--) { + heapify(i); + if (i == 0) return; + } + } + void update(DataType d) { + if ((*arr)[0] < d) return; + (*arr)[0] = d; + heapify(0); + } +}; \ No newline at end of file diff --git a/include/Mesh.h b/include/Mesh.h index 9e8bf73..ab1b9bb 100644 --- a/include/Mesh.h +++ b/include/Mesh.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include diff --git a/include/OptimalSlice.hpp b/include/OptimalSlice.hpp index 8441856..7c2e926 100644 --- a/include/OptimalSlice.hpp +++ b/include/OptimalSlice.hpp @@ -16,6 +16,7 @@ #define USE_PARALLEL #include "Mesh.h" +#include "MaxHeap.hpp" #include "tbb/tbb.h" namespace slice { @@ -33,6 +34,15 @@ namespace slice { if (x < ls.x) return true; return y < ls.y; } + Point2d operator-(const Point2d &rh) const { + return {x - rh.x, y - rh.y}; + } + Point2d operator+(const Point2d& rh) const { + return { x + rh.x, y + rh.y }; + } + Point2d operator-() const { + return { -x, -y }; + } }; struct Point3d { @@ -128,7 +138,7 @@ namespace slice { class Line { public: - Point3d v[2]; + Point3d v[2] = { 0,0 }; Line() {} @@ -203,6 +213,7 @@ namespace slice { return 0; } + // return the 0 < angle <= 90 degree between line and vertex P double line_point_angle(const SupportLine2d& line, const Point2d& p) { double angle; if (DOUBLE_EQ(p.x, line.x)) angle = 90; @@ -216,6 +227,21 @@ namespace slice { return angle; } + // return the 0 <= angle < 180 degree between line and vertex P + double line_edge_angle(const SupportLine2d& line, const Point2d& delta, bool direction_ccw = false) { + double angle; + if (DOUBLE_EQ(delta.x, 0)) angle = 90; + else if (DOUBLE_EQ(delta.y, 0)) angle = 0; + else angle = atan(delta.y / delta.x) * 180 / M_PI; + // calculate delta + if (DOUBLE_GT_EQ(line.theta, angle)) angle = line.theta - angle; + else angle = 180 + line.theta - angle; + if (direction_ccw && angle > 0) angle = 180 - angle; + // normalized angle + if (angle < 0) angle += 180; + return angle; + } + class FeretDiameter { public: bool empty = true; @@ -237,6 +263,7 @@ namespace slice { min = d1; angleMin = lines[0].theta; max = d2; angleMax = lines[1].theta; } + perimeter = 0; perpendicularMax = max; empty = false; } @@ -276,6 +303,7 @@ namespace slice { typedef std::pair PairPoint2d; typedef std::unordered_map ContourHash; typedef std::vector PolygonSides; + typedef std::pair DistanceIndexPair; std::ostream& operator<< (std::ostream& out, Point2d const& data) { out << "[" << data.x << "," << data.y << "]"; @@ -311,6 +339,15 @@ namespace slice { out << "slice::SupportLine2d(x:" << data.x << ", y:" << data.y << ", m:" << data.m << ", c:" << data.c << ", theta:" << data.theta << "[" << data.is_vertical <<"])"; return out; } + + std::ostream& operator<< (std::ostream& out, Polygon const& data) { + out << "slice::Polygon{" << std::endl; + for (auto d = data.begin(); d != data.end(); ++d) { + out << " -- " << (*d) << std::endl; + } + out << "}" << std::endl; + return out; + } } namespace std { @@ -623,6 +660,11 @@ namespace slice { hash.erase(item); for (size_t j = 1;; j++) { item = hash.find(C[j]); + // TODO: fixed this bugs + if (item == hash.end()) { + CS[i].clear(); + break; + } assert(item != hash.end()); if (!(C[j] == last)) { if (item->second.first == C[j - 1]) @@ -691,6 +733,14 @@ namespace slice { return (p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y); } + double measure_point_distance(const Point2d& p1, const Point2d& p2) { + return sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y)); + } + + double measure_point_magnitude(const Point2d& p) { + return sqrt(p.x * p.x + p.y * p.y); + } + // To find orientation of ordered triplet (p, q, r). // The function returns following values // 0 --> p, q and r are colinear @@ -715,6 +765,7 @@ namespace slice { }; // Graham scan algorithm for constructing convex hull + // the direction is counterclockwise // https://www.geeksforgeeks.org/convex-hull-set-2-graham-scan/ Polygon convexhull(Polygon points) { Polygon p; @@ -769,6 +820,165 @@ namespace slice { return p; } + // Support function generate the farthest point in direction d + // Complexity O(m+n) + Point2d gjk_support(const Polygon& P, const Polygon& Q, const Point2d& d) { + assert(P.size() > 0 && Q.size() > 0); + Point2d p = P[0]; + double max_dot = p.x * d.x + p.y * d.y; + for (auto it = P.begin(); it != P.end(); ++it) { + double dot = it->x * d.x + it->y * d.y; + if (dot > max_dot) { + max_dot = dot; + p = (*it); + } + } + Point2d q = Q[0]; + max_dot = q.x * -d.x + q.y * -d.y; + for (auto it = Q.begin(); it != Q.end(); ++it) { + double dot = it->x * -d.x + it->y * -d.y; + if (dot > max_dot) { + max_dot = dot; + q = (*it); + } + } + return p - q; + } + + // return the origin-closest point on simplex A,B + // complexity: O(1) + Point2d gjk_closest_point_to_origin(const Point2d& A, const Point2d& B) { + Point2d AB = B - A; + double norm_AB = (AB.x * AB.x + AB.y * AB.y); + if (DOUBLE_EQ(norm_AB, 0)) return A; + double lambda_2 = (-AB.x * A.x + -AB.y * A.y) / norm_AB; + if (lambda_2 > 1) return B; + else if (lambda_2 < -1) return A; + double lambda_1 = 1 - lambda_2; + return { lambda_1 * A.x + lambda_2 * B.x, lambda_1 * A.y + lambda_2 * B.y }; + } + + Point2d center_point_distance(const Polygon& P, const Polygon& Q) { + Point2d d; + double x = 0, y = 0; + for (auto it = P.begin(); it != P.end(); ++it) { + x += it->x; + y += it->y; + } + d.x = x / P.size(); + d.y = y / P.size(); + x = 0; + y = 0; + for (auto it = Q.begin(); it != Q.end(); ++it) { + x += it->x; + y += it->y; + } + d.x -= x / Q.size(); + d.y -= y / Q.size(); + return d; + } + + // Minkowski difference of two convex: P and Q + // return S = P - Q, Complexity O(m+n) + Polygon minkwoski_difference(const Polygon& P, Polygon Q) { + Polygon S; + if (P.size() >= 3 && Q.size() >= 3) { + // compute -Q and find the bottom-left vertex of Q + size_t index_q = 0; + double min_y = -Q[0].y; + for (Polygon::iterator it = Q.begin(); it != Q.end(); it++) { + it->x *= -1; + it->y *= -1; + if (min_y > it->y || (DOUBLE_EQ(min_y, it->y) && it->x < Q[index_q].x )) { + min_y = it->y; + index_q = (size_t)(it - Q.begin()); + } + } + size_t index_p = 0; + min_y = P[0].y; + // find the bottom-left vertex of P + for (Polygon::const_iterator it = P.begin(); it != P.end(); it++) { + if (min_y > it->y || (DOUBLE_EQ(min_y, it->y) && it->x < P[index_p].x)) { + min_y = it->y; + index_p = (size_t)(it - P.begin()); + } + } + SupportLine2d line(P[index_p] + Q[index_q], 0); + S.push_back({ line.x, line.y }); + size_t last_p = index_p; + size_t _count = 0; + Point2d first_point = S.front(); + //std::cout << line << std::endl; + do { + size_t next_p = (index_p == P.size() - 1) ? 0 : index_p + 1; + size_t next_q = (index_q == Q.size() - 1) ? 0 : index_q + 1; + Point2d edge_q = Q[next_q] - Q[index_q]; + Point2d edge_p = P[next_p] - P[index_p]; + double angle_p = line_edge_angle(line, edge_p, true); + double angle_q = line_edge_angle(line, edge_q, true); + //std::cout << "Edge P:" << edge_p << " " << angle_p << " Q:" << edge_q << " " << angle_q << std::endl; + if (angle_p > angle_q) { + // insert edge q into S and update index_q + line.update(line.x + edge_q.x, line.y + edge_q.y); + line += angle_q; + index_q = next_q; + } + else { + line.update(line.x + edge_p.x, line.y + edge_p.y); + line += angle_p; + index_p = next_p; + _count++; + } + //std::cout << line << std::endl; + if (index_p != last_p || _count == 0) { + S.push_back({ line.x,line.y }); + } + } while (index_p != last_p || _count == 0); + } + return S; + } + + // Implement from JAVA lib + // ref: http://www.dyn4j.org/2010/04/gjk-distance-closest-points/#gjk-closest + // return -1 if the polygon is intersecting + double gjk_minimal_distance(const Polygon& P, const Polygon& Q, double tolerance = 1e-4) { + if (P.size() < 3 || Q.size() < 3) return -1; + Polygon P1 = convexhull(P); + Polygon Q1 = convexhull(Q); + //Polygon Diff = minkwoski_difference(P1, Q1); + //assert(Diff.size() > 3); + //std::copy(Diff.begin(), Diff.end(), std::ostream_iterator(std::cout, " ")); + Point2d d = center_point_distance(P1, Q1); + Point2d simplex_a = gjk_support(P1, Q1, d); + Point2d simplex_b = gjk_support(P1, Q1, -d); + d = -gjk_closest_point_to_origin(simplex_a, simplex_b); + //std::cout << std::endl << "Pre: a:" << simplex_a << " b:" << simplex_b << " d:" << d << std::endl; + size_t _count = P1.size() + Q1.size(); + while (_count > 0) { + if (DOUBLE_EQ(d.x * d.x + d.y * d.y, 0)) return 0; + Point2d c = gjk_support(P1, Q1, d); + //std::cout << " -- c:" << c << std::endl; + // check new point c is better than simplex a, b + if (( d.x * c.x + d.y * c.y ) - (simplex_a.x * d.x + simplex_a.y * d.y) < tolerance) { + return measure_point_magnitude(d); + } + Point2d p1 = gjk_closest_point_to_origin(simplex_a, c); + Point2d p2 = gjk_closest_point_to_origin(c, simplex_b); + if (measure_point_magnitude(p1) < measure_point_magnitude(p2)) { + simplex_b = c; + d = -p1; + } + else { + simplex_a = c; + d = -p2; + } + //std::cout << " -- a:" << simplex_a << " b:" << simplex_b << " d:" << d << std::endl; + _count--; + } + //std::cout << "[Warning] GJK Iteration timeout: Found the intersect polygon" << std::endl; + return -1; + } + FeretDiameter measure_polygon_feret_diameter(const Polygon& p) { // find max min in convexhull Polygon convex = convexhull(p); @@ -890,108 +1100,117 @@ namespace slice { // Measure feret diameter void measure_feret_and_shape(const slice::ContourSlice& CS, std::vector& minFeret, std::vector& maxFeret, std::vector (&shapes)[5]) { // Foreach slice in 3D - for (slice::ContourSlice::const_iterator cs = CS.begin(); cs != CS.end(); cs++) { - if (cs->empty()) continue; - // Inside or outside testing - slice::PolygonSides p = contour_inside_test(*cs); - assert(p.size() == cs->size()); - // For each contour in slice, calculate min max of contour - std::vector minX, minY, maxX, maxY; - std::vector sortedMinX, sortedMinY, sortedMaxX, sortedMaxY; - size_t n_outside = 0, n_inside = 0; - for (slice::Polygons::const_iterator c = cs->begin(); c != cs->end(); c++) { - // if polygon must have more-than 2 lines - if (c->size() <= 2) continue; - size_t index_polygon = (size_t)(c - cs->begin()); - - // Loop for each point to find max min in polygon - slice::Polygon::const_iterator l = c->begin(); - double _minX = l->x, _minY = l->y, _maxX = l->x, _maxY = l->y; - for (l++; l != c->end(); l++) { - if (l->x > _maxX) { - _maxX = l->x; - } - if (_minX > l->x) { - _minX = l->x; - } - if (l->y > _maxY) { - _maxY = l->y; + // TODO: change to TBB parallel for +#ifndef USE_PARALLEL + for (size_t cs_index = 0, cs_size = CS.size(); cs_index < cs_size; cs_index++) { +#else + static tbb::affinity_partitioner ap; + tbb::spin_mutex feretMutex, shapeMutex; + tbb::parallel_for(tbb::blocked_range(0, CS.size()), [&](tbb::blocked_range r) { + for (size_t cs_index = r.begin(); cs_index < r.end(); cs_index++) { +#endif + if (CS[cs_index].empty()) continue; + // Inside or outside testing + slice::PolygonSides p = contour_inside_test(CS[cs_index]); + assert(p.size() == CS[cs_index].size()); + // For each contour in slice, calculate min max of contour + std::vector< Point2d > polygonCenters; + std::vector< std::pair > centerPolygonMap; + size_t n_outside = 0, n_inside = 0; + for (slice::Polygons::const_iterator c = CS[cs_index].begin(); c != CS[cs_index].end(); c++) { + // if polygon must have more-than 2 lines + if (c->size() <= 2) continue; + size_t index_polygon = (size_t)(c - CS[cs_index].begin()); + + // Loop for each point to find max min in polygon + slice::Polygon::const_iterator l = c->begin(); + double _x = 0, _y = 0; + for (l++; l != c->end(); l++) { + _x += l->x; + _y += l->y; } - if (_minY > l->y) { - _minY = l->y; + _x /= c->size(); + _y /= c->size(); + polygonCenters.push_back({ _x, _y }); + + if (p[index_polygon] == slice::PolygonSide::OUTSIDE) { + // the polygon is the boundary of solid; + // count the outside polygon + n_outside++; + centerPolygonMap.push_back(std::make_pair(Point2d{ _x, _y }, index_polygon)); } - } - minX.push_back(_minX); - maxX.push_back(_maxX); - minY.push_back(_minY); - maxY.push_back(_maxY); - - /* - if (DOUBLE_GT(h, 0) && DOUBLE_GT(w, 0)) { - double shape[5] = { area, 4 * area / (M_PI * h * h), 2 * area / (w * h), 4 * area / (M_PI * w * h), P / l }; - }*/ - // count the outside polygon - if (p[index_polygon] == slice::PolygonSide::OUTSIDE) { - // the polygon is the boundary of solid; - n_outside++; - sortedMinX.push_back(_minX); - sortedMaxX.push_back(_maxX); - sortedMinY.push_back(_minY); - sortedMaxY.push_back(_maxY); - } - else { - n_inside++; - double area = measure_polygon_area(*c); - FeretDiameter feret = measure_polygon_feret_diameter(*c); - if (!feret.empty && feret.min > 0) { - double w = feret.min, h = feret.perpendicularMax; - assert(w > 0 && h > 0 && area > 0); - // calculate Podczeck's shape description - // from: https://diplib.github.io/diplib-docs/features.html#shape_features_PodczeckShapes - // the polygon is the boundary of a hole; push the feret diameter to the result - minFeret.push_back(feret.min); - maxFeret.push_back(feret.perpendicularMax); - shapes[0].push_back(area / (w * h)); - shapes[1].push_back(4 * area / (M_PI * h * h)); - shapes[2].push_back(2 * area / (w * h)); - shapes[3].push_back(4 * area / (M_PI * w * h)); - shapes[4].push_back(feret.perimeter / feret.max); + else { + n_inside++; + double area = measure_polygon_area(*c); + FeretDiameter feret = measure_polygon_feret_diameter(*c); + if (!feret.empty && feret.min > 0) { + double w = feret.min, h = feret.perpendicularMax; + assert(w > 0 && h > 0 && area > 0); + // calculate Podczeck's shape description + // from: https://diplib.github.io/diplib-docs/features.html#shape_features_PodczeckShapes + // the polygon is the boundary of a hole; push the feret diameter to the result + { +#ifdef USE_PARALLEL + tbb::spin_mutex::scoped_lock lock(feretMutex); +#endif + minFeret.push_back(feret.min); + maxFeret.push_back(feret.perpendicularMax); + } + { +#ifdef USE_PARALLEL + tbb::spin_mutex::scoped_lock lock(shapeMutex); +#endif + shapes[0].push_back(area / (w * h)); + shapes[1].push_back(4 * area / (M_PI * h * h)); + shapes[2].push_back(2 * area / (w * h)); + shapes[3].push_back(4 * area / (M_PI * w * h)); + shapes[4].push_back(feret.perimeter / feret.max); + } + } } } - } - // sorting for searching performances - std::sort(sortedMinX.begin(), sortedMinX.end()); - std::sort(sortedMaxX.begin(), sortedMaxX.end()); - std::sort(sortedMinY.begin(), sortedMinY.end()); - std::sort(sortedMaxY.begin(), sortedMaxY.end()); - - // For each contour in slice, calculate feret - if (n_outside > 1) { - std::vector feret; - for (slice::Polygons::const_iterator c = cs->begin(); c != cs->end(); c++) { - size_t index = (size_t)(c - cs->begin()); - if (p[index] == slice::PolygonSide::OUTSIDE) { - // Left direction: find previous maxX of left neighbor < minX - auto lower = std::lower_bound(sortedMaxX.begin(), sortedMaxX.end(), minX[index]); - if (lower != sortedMaxX.end() && lower != sortedMaxX.begin()) feret.push_back(minX[index] - *(lower - 1)); - // Right direction: find next minX of right neightbor > maxX - auto upper = std::upper_bound(sortedMinX.begin(), sortedMinX.end(), maxX[index]); - if (upper != sortedMinX.end()) feret.push_back(*upper - maxX[index]); - // Upper direction; find previous maxY of upper neighbor < minY - lower = std::lower_bound(sortedMaxY.begin(), sortedMaxY.end(), minY[index]); - if (lower != sortedMaxY.end() && lower != sortedMaxY.begin()) feret.push_back(minY[index] - *(lower - 1)); - // lower direction; find next minY of lower neightbor > maxY - upper = std::upper_bound(sortedMinY.begin(), sortedMinY.end(), maxY[index]); - if (upper != sortedMinY.end()) feret.push_back(*upper - maxY[index]); + if (n_outside > 1) { + // For each Polygon in slice layer, find 4 minimum-distance polygon + for (slice::Polygons::const_iterator c = CS[cs_index].begin(); c != CS[cs_index].end(); c++) { + size_t index = (size_t)(c - CS[cs_index].begin()); + std::vector feret; + double _feret; + std::vector pairs; + int k = std::min(centerPolygonMap.size() - 1, (size_t)4); + if (p[index] == slice::PolygonSide::OUTSIDE) { + size_t i = 0; + for (size_t j = 0; j < k; i++) { + if (i != index && i < centerPolygonMap.size()) { + double distance = measure_point_distance(polygonCenters[index], centerPolygonMap[i].first); + pairs.push_back(std::make_pair(distance, centerPolygonMap[i].second)); + j++; + } + } + MaxHeap minimum(k, &pairs); + for (; i < centerPolygonMap.size(); i++) { + double distance = measure_point_distance(polygonCenters[index], centerPolygonMap[i].first); + minimum.update(std::make_pair(distance, centerPolygonMap[i].second)); + } + for (i = 0; i < k; i++) { + size_t next_index = pairs[i].second; + _feret = gjk_minimal_distance(*c, CS[cs_index].at(next_index)); + if (_feret > 0) feret.push_back(_feret); + } + } + if (feret.size() > 0) { + std::sort(feret.begin(), feret.end()); +#ifdef USE_PARALLEL + tbb::spin_mutex::scoped_lock lock(feretMutex); +#endif + minFeret.push_back(feret.front()); + maxFeret.push_back(feret.back()); + } } } - std::sort(feret.begin(), feret.end()); - if (feret.size() > 0) { - minFeret.push_back(feret.front()); - maxFeret.push_back(feret.back()); - } } - } +#ifdef USE_PARALLEL + }, ap); +#endif } } \ No newline at end of file diff --git a/include/Scaffolder_2.h b/include/Scaffolder_2.h index a9e4bc6..48e96a6 100644 --- a/include/Scaffolder_2.h +++ b/include/Scaffolder_2.h @@ -139,16 +139,98 @@ inline void clean_mesh(TMesh& mesh, double minimum_diameter, uint16_t smooth_ste vcg::tri::Smooth::VertexCoordLaplacian(mesh, smooth_step, false, true); if (verbose) std::cout << "OK" << std::endl; } - vcg::tri::Clean::MergeCloseVertex(mesh, SLICE_PRECISION*100); + vcg::tri::Clean::MergeCloseVertex(mesh, SLICE_PRECISION*1000); vcg::tri::Clean::RemoveUnreferencedVertex(mesh); + vcg::tri::Allocator::CompactEveryVector(mesh); vcg::tri::UpdateTopology::FaceFace(mesh); - //vcg::tri::Clean::SelfIntersections(mesh) } -inline void fix_self_intersect_mesh(TMesh& mesh, uint16_t max_iteration = 10) { +inline void fix_self_intersect_mesh(TMesh& mesh, double minimum_diameter, uint16_t max_iteration = 10, bool verbose = false) { + std::vector faces; vcg::tri::Clean::SelfIntersections(mesh, faces); + uint16_t iteration = 0; + int maxSize = mesh.bbox.SquaredDiag(); + size_t nf = 0; + while (iteration < max_iteration && (faces.size() > 0 || nf > 0)) { + + if (verbose) std::cout << "[Fix Self-intersect face]" << std::endl << " -- Iteration " << iteration + 1 << std::endl; + + if (faces.size() > 0) { + for (size_t i = 0; i < faces.size(); i++) { + if (!faces[i]->IsD()) + vcg::tri::Allocator::DeleteFace(mesh, *(faces[i])); + } + if (verbose) std::cout << " -- self-intersect faces: " << faces.size() << std::endl; + vcg::tri::Clean::RemoveUnreferencedVertex(mesh); + vcg::tri::Allocator::CompactEveryVector(mesh); + vcg::tri::UpdateTopology::FaceFace(mesh); + vcg::tri::UpdateBounding::Box(mesh); + if (verbose) std::cout << " -- Remove faces [OK]" << std::endl; + vcg::tri::Clean::RemoveSmallConnectedComponentsDiameter(mesh, minimum_diameter * mesh.bbox.Diag()); + vcg::tri::Clean::RemoveUnreferencedVertex(mesh); + if (verbose) std::cout << " -- Remove small components " << minimum_diameter * mesh.bbox.Diag() << " [OK]" << std::endl; + vcg::tri::Clean::RemoveNonManifoldFace(mesh); + vcg::tri::UpdateTopology::FaceFace(mesh); + if (verbose) std::cout << " -- Remove non-manifold edges [OK]" << std::endl; + if (vcg::tri::Clean::CountNonManifoldEdgeFF(mesh) > 0) { + std::cout << "[Warning]: Fixed Self-intersecting failed: Mesh has some not 2-manifold edges" << std::endl; + return; + } + vcg::tri::Hole::EarCuttingIntersectionFill>(mesh, maxSize, false); + if (verbose) std::cout << " -- Close holes [OK]" << std::endl; + } + + vcg::tri::UpdateFlags::FaceBorderFromNone(mesh); + vcg::tri::UpdateFlags::VertexBorderFromFaceBorder(mesh); + vcg::tri::UpdateSelection::FaceFromBorderFlag(mesh); + vcg::tri::UpdateSelection::VertexFromBorderFlag(mesh); + nf = vcg::tri::UpdateSelection::FaceCount(mesh); + if (nf > 0) { + vcg::tri::UpdateSelection::VertexFromFaceLoose(mesh); + vcg::tri::UpdateSelection::FaceFromVertexLoose(mesh); + vcg::tri::UpdateSelection::VertexClear(mesh); + vcg::tri::UpdateSelection::VertexFromFaceStrict(mesh); + for (TMesh::FaceIterator it = mesh.face.begin(); it != mesh.face.end(); ++it) { + if (!it->IsD() && it->IsS()) + vcg::tri::Allocator::DeleteFace(mesh, *it); + } + for (TMesh::VertexIterator it = mesh.vert.begin(); it != mesh.vert.end(); ++it) { + if (!it->IsD() && it->IsS()) + vcg::tri::Allocator::DeleteVertex(mesh, *it); + } + vcg::tri::Allocator::CompactEveryVector(mesh); + vcg::tri::UpdateTopology::FaceFace(mesh); + if (verbose) std::cout << " -- Remove Border faces [OK]" << std::endl; + vcg::tri::Clean::RemoveNonManifoldFace(mesh); + vcg::tri::UpdateBounding::Box(mesh); + vcg::tri::UpdateTopology::FaceFace(mesh); + if (verbose) std::cout << " -- Remove non-manifold edges [OK]" << std::endl; + vcg::tri::Hole::EarCuttingIntersectionFill>(mesh, maxSize, false); + if (verbose) std::cout << " -- Close holes [OK]" << std::endl; + + vcg::tri::UpdateFlags::FaceBorderFromNone(mesh); + vcg::tri::UpdateFlags::VertexBorderFromFaceBorder(mesh); + vcg::tri::UpdateSelection::FaceFromBorderFlag(mesh); + vcg::tri::UpdateSelection::VertexFromBorderFlag(mesh); + nf = vcg::tri::UpdateSelection::FaceCount(mesh); + } + + vcg::tri::Clean::SelfIntersections(mesh, faces); + iteration++; + } + vcg::tri::Allocator::CompactEveryVector(mesh); + + while (iteration < max_iteration && nf > 0) { + if (verbose) std::cout << "[Fix Border Edges and holes]" << std::endl << " -- Iteration " << iteration + 1 << std::endl; + + + + iteration++; + } + vcg::tri::UpdateBounding::Box(mesh); + vcg::tri::UpdateTopology::FaceFace(mesh); } inline void report_mesh(TMesh& mesh) { diff --git a/src/Main.cpp b/src/Main.cpp index 26870a3..6000570 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -10,11 +10,12 @@ int main(int argc, char* argv[]) bool is_export_feret = false; bool is_export_inverse = false; bool is_build_inverse = true; + bool is_fix_self_intersect = false; bool no_output = false; uint16_t grid_offset = 3; uint16_t shell = 0; uint16_t smooth_step = 5; - uint8_t method = METHOD_SLICE_CONTOUR; + uint8_t method = METHOD_IMAGE_PROCESS; double thickness = 0.0; size_t minimum_grid_size = 100; double coff = pi; @@ -46,10 +47,11 @@ int main(int argc, char* argv[]) ("g,grid_size", "Grid size [default: 100]", cxxopts::value(), "INT") ("grid_offset", "[default:3]", cxxopts::value(), "INT") ("smooth_step", "Smooth with laplacian (default: 5)", cxxopts::value(), "INT") - ("method", "Method of microstructure analysis: 0 (Image processing technique) or 1 (Slice contour technique) [default: 1]", cxxopts::value(), "0,1") - ("output_inverse", "additional output inverse scaffold") - ("inverse", "Enable build inverse 3D scaffold (for pore connectivity analysis)") - ("dirty", "Disable autoclean") + ("method", "Method of microstructure analysis: 0 (Image processing technique) or 1 (Slice contour technique) [default: 0]", cxxopts::value(), "0,1") + ("output_inverse", "additional output inverse scaffold [default: false]") + ("inverse", "Enable build inverse 3D scaffold (for pore connectivity analysis) [default: true]") + ("dirty", "Disable autoclean [default false]") + ("fix_self_intersect", "Experimental fix self-intersect faces [default: false]") ("minimum_diameter", "used for removing small orphaned (between 0-1) [default: 0.25]", cxxopts::value(), "DOUBLE"); options.parse_positional({ "input", "output", "format" }); bool isEmptyOption = (argc == 1); @@ -105,6 +107,7 @@ int main(int argc, char* argv[]) if (result.count("shell")) shell = result["shell"].as(); if (result.count("smooth_step")) smooth_step = result["smooth_step"].as(); if (result.count("inverse")) is_build_inverse = result["inverse"].as(); + if (result.count("fix_self_intersect")) is_fix_self_intersect = result["fix_self_intersect"].as(); to_lower(surface); to_lower(format); @@ -138,9 +141,10 @@ int main(int argc, char* argv[]) << "-- Autoclean: " << (dirty ? "False" : "True") << std::endl << "-- Minimum diameter: " << 100 * minimum_diameter << "%" << std::endl << "-- Smooth step: " << smooth_step << std::endl + << "-- Fix self-intersect: " << (is_fix_self_intersect ? "True" : "False") << std::endl << "-- Analysis microstructure: " << (is_analysis_microstructure ? "True" : "False") << std::endl - << "-- Method :" << (method == METHOD_IMAGE_PROCESS ? "Image Processing" : "Contour Slicing") << std::endl - << "-- Export microstructure: " << (is_analysis_microstructure ? "True" : "False") << std::endl + << "-- Method: " << (method == METHOD_IMAGE_PROCESS ? "Image Processing" : "Contour Slicing") << std::endl + << "-- Export microstructure: " << (is_export_microstructure ? "True" : "False") << std::endl << "-- Build Inverse: " << (is_build_inverse ? "True" : "False") << std::endl << "-- Export Inverse: " << (is_export_inverse ? "True" : "False") << std::endl; } @@ -318,6 +322,7 @@ int main(int argc, char* argv[]) if (!dirty) { clean_mesh(mesh, minimum_diameter, smooth_step, verbose); + if (is_fix_self_intersect) fix_self_intersect_mesh(mesh, minimum_diameter, 5, verbose); if (is_build_inverse) clean_mesh(inverse_mesh, minimum_diameter, 0, false); } bool is_manifold = is_mesh_manifold(mesh); @@ -519,13 +524,13 @@ int main(int argc, char* argv[]) << "-- Vertices: " << inverse_mesh.VN() << std::endl << "-- Faces: " << inverse_mesh.FN() << std::endl; bool watertight = (edgeBorderNum == 0) && (edgeNonManifNum == 0); - bool pointcloud = (mesh.fn == 0 && mesh.vn != 0); + bool pointcloud = (mesh.FN() == 0 && mesh.VN() != 0); if (!watertight || pointcloud) std::cout << "[Warning] Pore isn't conencted" << std::endl; } vcg::tri::Clean::CountEdgeNum(mesh, edgeNum, edgeBorderNum, edgeNonManifNum); bool watertight = (edgeBorderNum == 0) && (edgeNonManifNum == 0); - bool pointcloud = (mesh.fn == 0 && mesh.vn != 0); + bool pointcloud = (mesh.FN() == 0 && mesh.VN() != 0); if (!pointcloud && watertight) { area2 = vcg::tri::Stat::ComputeMeshArea(mesh); @@ -567,7 +572,7 @@ int main(int argc, char* argv[]) } else if (format == "stl") { vcg::tri::io::ExporterSTL::Save(mesh, filename.c_str(), 0); - if (is_build_inverse && is_export_inverse) vcg::tri::io::ExporterSTL::Save(inverse_mesh, filename2.c_str(), 0); + if (is_build_inverse && is_export_inverse) vcg::tri::io::ExporterSTL::Save(inverse_mesh, filename2.c_str(), true, 0); } if (verbose) std::cout << "OK" << std::endl; } diff --git a/src/SliceTest.cpp b/src/SliceTest.cpp index ebf94b0..e4aee76 100644 --- a/src/SliceTest.cpp +++ b/src/SliceTest.cpp @@ -55,10 +55,69 @@ int main(int argc, char* argv[]) std::cout << "Error parsing options: " << ex.what() << std::endl; return 1; } + // Test case for feret //slice::Polygon p = { {0,0}, {10,0}, {20,10}, {21, 20}, {10, 30}, {4, 31}, {-10, 22}, {-1, 20}, {8,18}, {2,6}, {-5,5} }; - //slice::Polygon p2 = { {-20,0}, {0,-20}, {20,0}, {0,20} }; - - // Stage 1: + /* + slice::Polygons c = { + {{0,0}, {1,0}, {1,1}, {0,1}}, + {{2,0}, {3,0}, {3,1}, {2,1}}, + {{0,2}, {1,2}, {1,3}, {0,3}}, + {{2,2}, {3,2}, {3,3}, {2,3}} + }; + slice::ContourSlice s = { c }; + std::vector minFeret, maxFeret, shape[5]; + slice::measure_feret_and_shape(s, minFeret, maxFeret, shape); + std::copy(minFeret.begin(), minFeret.end(), std::ostream_iterator(std::cout, " ")); + std::cout << std::endl; + std::copy(maxFeret.begin(), maxFeret.end(), std::ostream_iterator(std::cout, " ")); + std::cout << std::endl; + */ + + // Test case for GJK Minimal distance + //slice::Polygon p = { {4,11}, {4,5}, {9,9} }; + //slice::Polygon q = { {8,6}, {10,2}, {13,1}, {15,6} }; + //slice::Polygon r = { {5,7}, {7,3}, {10,2}, {12,7} }; + //slice::Polygon s = slice::minkwoski_difference(p, q); + //std::copy(s.begin(), s.end(), std::ostream_iterator(std::cout, " ")); + //std::cout << slice::gjk_closest_point_to_origin({ -4, -1 }, { 1, 3 }) << std::endl; + //std::cout << slice::gjk_support(p, q, { 1, 0 }) << std::endl; + /*slice::Polygon p = { + {2.80414,-10.5846}, + {2.80468,-10.5841}, + {2.83126,-10.5382}, + {2.81928,-10.5061}, + {2.82824,-10.4688}, + {2.80345,-10.4289}, + {2.79971,-10.428}, + {2.69903,-10.3713}, + {2.6855,-10.3905}, + {2.65292,-10.414}, + {2.65199,-10.4561}, + {2.66653,-10.5019}, + {2.64892,-10.5492}, + {2.65068,-10.5939}, + {2.66574,-10.623}, + {2.69398,-10.6438}, + {2.7074,-10.6399}, + {2.80178,-10.586} + }; + slice::Polygon r = { + {2.31131,-9.39875}, + {2.32335,-9.42967}, + {2.31047,-9.49915}, + {2.32325,-9.56584}, + {2.31216,-9.57226}, + {2.30057,-9.59902}, + {2.23808,-9.54045}, + {2.22256,-9.53846}, + {2.20136,-9.50325}, + {2.22015,-9.44737}, + {2.28571,-9.38959}, + {2.2985,-9.38883} + }; + std::cout << slice::gjk_minimal_distance(p, r) << std::endl; + return 0; + */ { try { // Read FROM STL @@ -108,7 +167,8 @@ int main(int argc, char* argv[]) size_t firstindex = input_file.find_last_of("/\\"); firstindex = firstindex == string::npos ? 0 : firstindex + 1; size_t lastindex = input_file.find_last_of("."); - std::string dir = input_file.substr(firstindex, lastindex - firstindex) + "_svg"; + std::string dir = input_file.substr(firstindex, lastindex - firstindex); + dir.append("_svg"); std::cout << "Creating direction: " << dir << std::endl; make_dir(dir); @@ -133,10 +193,10 @@ int main(int argc, char* argv[]) #endif std::vector minFeret, maxFeret, shape[5]; slice::measure_feret_and_shape(C, minFeret, maxFeret, shape); - std::copy(minFeret.begin(), minFeret.end(), std::ostream_iterator(std::cout, " ")); - std::cout << std::endl; - std::copy(maxFeret.begin(), maxFeret.end(), std::ostream_iterator(std::cout, " ")); - std::cout << std::endl; + std::sort(minFeret.begin(), minFeret.end()); + std::sort(maxFeret.begin(), maxFeret.end()); + std::cout << minFeret.at(0) << ',' << minFeret.at(minFeret.size() * 0.25) << ',' << minFeret.at(minFeret.size() / 2) << ',' << minFeret.at(minFeret.size() * 0.75) << ',' << minFeret.at(minFeret.size() - 1) << ',' << std::endl; + std::cout << maxFeret.at(0) << ',' << maxFeret.at(maxFeret.size() * 0.25) << ',' << maxFeret.at(maxFeret.size() / 2) << ',' << maxFeret.at(maxFeret.size() * 0.75) << ',' << maxFeret.at(maxFeret.size() - 1) << ',' << std::endl; } } From bd093a51e6910e81311229c987621d0309ea23e1 Mon Sep 17 00:00:00 2001 From: Jirawat I Date: Wed, 11 Mar 2020 02:26:55 +0700 Subject: [PATCH 12/20] fixed point3d init --- include/MaxHeap.hpp | 8 ++-- include/OptimalSlice.hpp | 28 ++++++++---- include/utils.h | 92 ++++++++++++++++++++++++++-------------- src/Main.cpp | 24 ++++++++--- src/SliceTest.cpp | 15 ++++--- 5 files changed, 110 insertions(+), 57 deletions(-) diff --git a/include/MaxHeap.hpp b/include/MaxHeap.hpp index 19843d1..7e44eb8 100644 --- a/include/MaxHeap.hpp +++ b/include/MaxHeap.hpp @@ -30,9 +30,11 @@ class MaxHeap { } } void build() { - for (size_t i = (size_t)(size / 2) - 1;; i--) { - heapify(i); - if (i == 0) return; + if (size >= 2) { + for (size_t i = (size_t)(size / 2) - 1;; i--) { + heapify(i); + if (i == 0) return; + } } } void update(DataType d) { diff --git a/include/OptimalSlice.hpp b/include/OptimalSlice.hpp index 7c2e926..fa76c3c 100644 --- a/include/OptimalSlice.hpp +++ b/include/OptimalSlice.hpp @@ -138,9 +138,12 @@ namespace slice { class Line { public: - Point3d v[2] = { 0,0 }; + Point3d v[2]; - Line() {} + Line() { + v[0] = Point3d{ 0, 0, 0 }; + v[1] = Point3d{ 0, 0, 0 }; + } Line(Point3d v0, Point3d v1, size_t index) { v[0] = v0; @@ -768,8 +771,7 @@ namespace slice { // the direction is counterclockwise // https://www.geeksforgeeks.org/convex-hull-set-2-graham-scan/ Polygon convexhull(Polygon points) { - Polygon p; - if (points.size() < 3) return p; + if (points.size() < 3) return points; double ymin = points[0].y; size_t index = 0; // Find the bottom-left point @@ -784,6 +786,7 @@ namespace slice { Point2d origin = points.front(); std::sort(points.begin() + 1, points.end(), CompareOrientation(origin)); // delete the colinear points + Polygon p; for (Polygon::iterator p = points.begin() + 1; p != points.end();) { Polygon::iterator next_p = (p + 1); if (next_p != points.end()) { @@ -798,7 +801,7 @@ namespace slice { break; } } - if (points.size() < 3) return p; + if (points.size() < 3) return points; // Create convexhull polygon with points[0..2] p.reserve(points.size()); p.push_back(points[0]); @@ -945,6 +948,7 @@ namespace slice { if (P.size() < 3 || Q.size() < 3) return -1; Polygon P1 = convexhull(P); Polygon Q1 = convexhull(Q); + if (P1.size() < 3 || Q1.size() < 3) return -1; //Polygon Diff = minkwoski_difference(P1, Q1); //assert(Diff.size() > 3); //std::copy(Diff.begin(), Diff.end(), std::ostream_iterator(std::cout, " ")); @@ -1119,7 +1123,10 @@ namespace slice { size_t n_outside = 0, n_inside = 0; for (slice::Polygons::const_iterator c = CS[cs_index].begin(); c != CS[cs_index].end(); c++) { // if polygon must have more-than 2 lines - if (c->size() <= 2) continue; + if (c->size() <= 2) { + polygonCenters.push_back({ 0, 0 }); + continue; + } size_t index_polygon = (size_t)(c - CS[cs_index].begin()); // Loop for each point to find max min in polygon @@ -1170,9 +1177,10 @@ namespace slice { } } - if (n_outside > 1) { + if (n_outside > 1 && centerPolygonMap.size() > 1) { // For each Polygon in slice layer, find 4 minimum-distance polygon for (slice::Polygons::const_iterator c = CS[cs_index].begin(); c != CS[cs_index].end(); c++) { + if (c->size() <= 2) continue; size_t index = (size_t)(c - CS[cs_index].begin()); std::vector feret; double _feret; @@ -1194,8 +1202,10 @@ namespace slice { } for (i = 0; i < k; i++) { size_t next_index = pairs[i].second; - _feret = gjk_minimal_distance(*c, CS[cs_index].at(next_index)); - if (_feret > 0) feret.push_back(_feret); + if (next_index < CS[cs_index].size()) { + _feret = gjk_minimal_distance(*c, CS[cs_index].at(next_index)); + if (_feret > 0) feret.push_back(_feret); + } } } if (feret.size() > 0) { diff --git a/include/utils.h b/include/utils.h index 68bda96..8ca84e0 100644 --- a/include/utils.h +++ b/include/utils.h @@ -11,53 +11,79 @@ #include #endif -int make_dir(std::string& str) { - if (str.empty()) - return 0; +namespace util { + int make_dir(std::string& str) { + if (str.empty()) + return 0; #ifdef _WIN32 - return _mkdir(str.c_str()); + return _mkdir(str.c_str()); #else - return mkdir(str.c_str(), 0733); + return mkdir(str.c_str(), 0733); #endif -} + } -/* Remove if already defined */ -typedef long long int64; typedef unsigned long long uint64; + /* Remove if already defined */ + typedef long long int64; typedef unsigned long long uint64; -/* Returns the amount of milliseconds elapsed since the UNIX epoch. Works on both - * windows and linux. */ + /* Returns the amount of milliseconds elapsed since the UNIX epoch. Works on both + * windows and linux. */ -uint64 GetTimeMs64() -{ + uint64 GetTimeMs64() + { #ifdef _WIN32 - /* Windows */ - FILETIME ft; - LARGE_INTEGER li; + /* Windows */ + FILETIME ft; + LARGE_INTEGER li; - /* Get the amount of 100 nano seconds intervals elapsed since January 1, 1601 (UTC) and copy it - * to a LARGE_INTEGER structure. */ - GetSystemTimeAsFileTime(&ft); - li.LowPart = ft.dwLowDateTime; - li.HighPart = ft.dwHighDateTime; + /* Get the amount of 100 nano seconds intervals elapsed since January 1, 1601 (UTC) and copy it + * to a LARGE_INTEGER structure. */ + GetSystemTimeAsFileTime(&ft); + li.LowPart = ft.dwLowDateTime; + li.HighPart = ft.dwHighDateTime; - uint64 ret = li.QuadPart; - ret -= 116444736000000000LL; /* Convert from file time to UNIX epoch time. */ - ret /= 10000; /* From 100 nano seconds (10^-7) to 1 millisecond (10^-3) intervals */ + uint64 ret = li.QuadPart; + ret -= 116444736000000000LL; /* Convert from file time to UNIX epoch time. */ + ret /= 10000; /* From 100 nano seconds (10^-7) to 1 millisecond (10^-3) intervals */ - return ret; + return ret; #else - /* Linux */ - struct timeval tv; + /* Linux */ + struct timeval tv; - gettimeofday(&tv, NULL); + gettimeofday(&tv, NULL); - uint64 ret = tv.tv_usec; - /* Convert from micro seconds (10^-6) to milliseconds (10^-3) */ - ret /= 1000; + uint64 ret = tv.tv_usec; + /* Convert from micro seconds (10^-6) to milliseconds (10^-3) */ + ret /= 1000; - /* Adds the seconds (10^0) after converting them to milliseconds (10^-3) */ - ret += (tv.tv_sec * 1000); + /* Adds the seconds (10^0) after converting them to milliseconds (10^-3) */ + ret += (tv.tv_sec * 1000); - return ret; + return ret; #endif + } + + std::string PathGetExtension(std::string const& path) + { + std::string ext; + + // Find the last dot, if any. + size_t dotIdx = path.find_last_of("."); + if (dotIdx != string::npos) + { + // Find the last directory separator, if any. + size_t dirSepIdx = path.find_last_of("/\\"); + + // If the dot is at the beginning of the file name, do not treat it as a file extension. + // e.g., a hidden file: ".alpha". + // This test also incidentally avoids a dot that is really a current directory indicator. + // e.g.: "alpha/./bravo" + if (dotIdx > dirSepIdx + 1) + { + ext = path.substr(dotIdx); + } + } + + return ext; + } } \ No newline at end of file diff --git a/src/Main.cpp b/src/Main.cpp index 6000570..83a1f91 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -15,6 +15,7 @@ int main(int argc, char* argv[]) uint16_t grid_offset = 3; uint16_t shell = 0; uint16_t smooth_step = 5; + uint16_t slice_grid = 100; uint8_t method = METHOD_IMAGE_PROCESS; double thickness = 0.0; size_t minimum_grid_size = 100; @@ -30,16 +31,13 @@ int main(int argc, char* argv[]) try { cxxopts::Options options("Scaffolder", "Scaffolder - generate 3D scaffold from STL file"); - options.positional_help("[option args]").show_positional_help(); + options.positional_help("INPUT OUTPUT FORMAT").show_positional_help(); options.add_options() ("h,help", "Print help") ("q,quiet", "Disable verbose output [default: false]") - ("m,microstructure", "Analysis microstructure ( [default: false]") - ("m1", "Export and analysis microstructure 1 (Image processing technique) [default: false]") - ("m2", "Export and analysis microstructure 2 (Slice coutour technique) [default: false]") - ("f,format", "Output format (OFF,PLY,STL,OBJ) [default: ply]", cxxopts::value()) ("i,input", "Input file (STL)", cxxopts::value(), "FILE") ("o,output", "Output filename without extension [default: out]", cxxopts::value(), "FILENAME") + ("f,format", "Output format (OFF,PLY,STL,OBJ) [default: ply]", cxxopts::value()) ("c,coff", "default:4*PI", cxxopts::value(), "DOUBLE") ("s,shell", "[default:0]", cxxopts::value(), "INT") ("n,surface", "rectlinear, schwarzp, schwarzd, gyroid, double-p, double-d, double-gyroiod, lidinoid, schoen_iwp, neovius, bcc, tubular_g_ab, tubular_g_c [default: schwarzp]", cxxopts::value(), "NAME") @@ -47,7 +45,11 @@ int main(int argc, char* argv[]) ("g,grid_size", "Grid size [default: 100]", cxxopts::value(), "INT") ("grid_offset", "[default:3]", cxxopts::value(), "INT") ("smooth_step", "Smooth with laplacian (default: 5)", cxxopts::value(), "INT") + ("m,microstructure", "Analysis microstructure ( [default: false]") + ("m1", "Export and analysis microstructure 1 (Image processing technique) [default: false]") + ("m2", "Export and analysis microstructure 2 (Slice coutour technique) [default: false]") ("method", "Method of microstructure analysis: 0 (Image processing technique) or 1 (Slice contour technique) [default: 0]", cxxopts::value(), "0,1") + ("slice_grid", "Slice Grid used in microstructure analysis [default: 100]", cxxopts::value(), "INT") ("output_inverse", "additional output inverse scaffold [default: false]") ("inverse", "Enable build inverse 3D scaffold (for pore connectivity analysis) [default: true]") ("dirty", "Disable autoclean [default false]") @@ -95,6 +97,12 @@ int main(int argc, char* argv[]) } if (result.count("output")) { filename = result["output"].as(); + to_lower(filename); + std::string ext = util::PathGetExtension(filename); + if (!ext.empty()) { + filename = filename.substr(0, filename.size()-ext.size()); + format = ext.substr(1); + } no_output = false; } if (result.count("format")) format = result["format"].as(); @@ -108,6 +116,7 @@ int main(int argc, char* argv[]) if (result.count("smooth_step")) smooth_step = result["smooth_step"].as(); if (result.count("inverse")) is_build_inverse = result["inverse"].as(); if (result.count("fix_self_intersect")) is_fix_self_intersect = result["fix_self_intersect"].as(); + if (result.count("slice_grid")) slice_grid = result["slice_grid"].as(); to_lower(surface); to_lower(format); @@ -144,6 +153,7 @@ int main(int argc, char* argv[]) << "-- Fix self-intersect: " << (is_fix_self_intersect ? "True" : "False") << std::endl << "-- Analysis microstructure: " << (is_analysis_microstructure ? "True" : "False") << std::endl << "-- Method: " << (method == METHOD_IMAGE_PROCESS ? "Image Processing" : "Contour Slicing") << std::endl + << "-- Slice grid: " << slice_grid << std::endl << "-- Export microstructure: " << (is_export_microstructure ? "True" : "False") << std::endl << "-- Build Inverse: " << (is_build_inverse ? "True" : "False") << std::endl << "-- Export Inverse: " << (is_export_inverse ? "True" : "False") << std::endl; @@ -347,7 +357,7 @@ int main(int argc, char* argv[]) firstindex = firstindex == string::npos ? 0 : firstindex + 1; size_t lastindex = input_file.find_last_of("."); dir = input_file.substr(firstindex, lastindex - firstindex) + "_slice"; - make_dir(dir); + util::make_dir(dir); } // init progress bar ProgressBar progress(grid_size.sum(), PROGRESS_BAR_COLUMN); @@ -424,7 +434,7 @@ int main(int argc, char* argv[]) int next_axis = ((main_axis + 1) % 3); int prev_axis = ((main_axis + 2) % 3); unsigned int progress_major_step = grid_size[main_axis] / 4; - slice::Slice s = slice::incremental_slicing(mesh, grid_size(main_axis), main_axis); + slice::Slice s = slice::incremental_slicing(mesh, slice_grid, main_axis); progress += progress_major_step; progress.display(); slice::ContourSlice C = slice::contour_construct(s, main_axis); diff --git a/src/SliceTest.cpp b/src/SliceTest.cpp index e4aee76..c4a7423 100644 --- a/src/SliceTest.cpp +++ b/src/SliceTest.cpp @@ -170,7 +170,7 @@ int main(int argc, char* argv[]) std::string dir = input_file.substr(firstindex, lastindex - firstindex); dir.append("_svg"); std::cout << "Creating direction: " << dir << std::endl; - make_dir(dir); + util::make_dir(dir); #ifndef USE_PARALLEL for (slice::ContourSlice::const_iterator cs = C.begin(); cs != C.end(); cs++) { @@ -193,10 +193,15 @@ int main(int argc, char* argv[]) #endif std::vector minFeret, maxFeret, shape[5]; slice::measure_feret_and_shape(C, minFeret, maxFeret, shape); - std::sort(minFeret.begin(), minFeret.end()); - std::sort(maxFeret.begin(), maxFeret.end()); - std::cout << minFeret.at(0) << ',' << minFeret.at(minFeret.size() * 0.25) << ',' << minFeret.at(minFeret.size() / 2) << ',' << minFeret.at(minFeret.size() * 0.75) << ',' << minFeret.at(minFeret.size() - 1) << ',' << std::endl; - std::cout << maxFeret.at(0) << ',' << maxFeret.at(maxFeret.size() * 0.25) << ',' << maxFeret.at(maxFeret.size() / 2) << ',' << maxFeret.at(maxFeret.size() * 0.75) << ',' << maxFeret.at(maxFeret.size() - 1) << ',' << std::endl; + if (minFeret.size() > 0) { + std::sort(minFeret.begin(), minFeret.end()); + std::cout << minFeret.at(0) << ',' << minFeret.at(minFeret.size() * 0.25) << ',' << minFeret.at(minFeret.size() / 2) << ',' << minFeret.at(minFeret.size() * 0.75) << ',' << minFeret.at(minFeret.size() - 1) << ',' << std::endl; + } + if (maxFeret.size() > 0) { + std::sort(maxFeret.begin(), maxFeret.end()); + std::cout << maxFeret.at(0) << ',' << maxFeret.at(maxFeret.size() * 0.25) << ',' << maxFeret.at(maxFeret.size() / 2) << ',' << maxFeret.at(maxFeret.size() * 0.75) << ',' << maxFeret.at(maxFeret.size() - 1) << ',' << std::endl; + } + } } From 31610ab39db018c7dad5ed74f0587d968ea59a28 Mon Sep 17 00:00:00 2001 From: Jirawat I Date: Sat, 14 Mar 2020 03:21:51 +0700 Subject: [PATCH 13/20] add new program to fix self-intersect mesh --- CMakeLists.txt | 4 + include/Mesh.h | 271 +++++++++++++++++++++++++++++++++--- include/OptimalSlice.hpp | 30 +++- include/ProgressBar.hpp | 2 + include/Scaffolder_2.h | 156 +++------------------ include/implicit_function.h | 6 - include/utils.h | 10 +- src/FixSelfIntersect.cpp | 142 +++++++++++++++++++ src/Main.cpp | 16 ++- 9 files changed, 462 insertions(+), 175 deletions(-) create mode 100644 src/FixSelfIntersect.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 5e2acbd..a659c48 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,3 +44,7 @@ target_link_libraries(${PROJECT_NAME} PRIVATE igl::core DIP tbb_static) add_executable(SliceTest ${MAIN_SOURCES} ${PROJECT_SOURCE_DIR}/src/SliceTest.cpp) target_include_directories(SliceTest PRIVATE "${PROJECT_SOURCE_DIR}/include" ${TBB_INCLUDE_DIR} ${VCG_INCLUDE_DIR}) target_link_libraries(SliceTest PRIVATE igl::core tbb_static) + +add_executable(Fixer ${MAIN_SOURCES} ${PROJECT_SOURCE_DIR}/src/FixSelfIntersect.cpp ${VCG_INCLUDE_DIR}/wrap/ply/plylib.cpp) +target_include_directories(Fixer PRIVATE "${PROJECT_SOURCE_DIR}/include" ${TBB_INCLUDE_DIR} ${VCG_INCLUDE_DIR}) +target_link_libraries(Fixer PRIVATE) diff --git a/include/Mesh.h b/include/Mesh.h index ab1b9bb..b13adde 100644 --- a/include/Mesh.h +++ b/include/Mesh.h @@ -1,12 +1,13 @@ -#ifndef SCAFFOLD_TMESH_H -#define SCAFFOLD_TMESH_H +#pragma once #include #include #include #include #include #include -#include +#ifndef SLICE_PRECISION +#define SLICE_PRECISION 1e-8 +#endif class TFace; class TVertex; @@ -30,26 +31,252 @@ class TFace : public vcg::Face, vector > {}; -inline void mesh_to_eigen_vector(TMesh& mesh, Eigen::MatrixXd& V, Eigen::MatrixXi& F) { - V.resize(mesh.VN(), 3); - size_t i = 0; - std::vector vertexId(mesh.vert.size()); - for (TMesh::VertexIterator it = mesh.vert.begin(); it != mesh.vert.end(); ++it) if (!it->IsD()) { - vertexId[it - mesh.vert.begin()] = i; - vcg::Point3d point = it->P(); - V(i, 0) = point[0]; - V(i, 1) = point[1]; - V(i, 2) = point[2]; - i++; +inline void clean_mesh(TMesh& mesh, double minimum_diameter, uint16_t smooth_step, bool verbose = true) { + if (verbose) std::cout << "[libVCG Cleaning] "; + vcg::tri::Clean::RemoveDuplicateVertex(mesh); + vcg::tri::Allocator::CompactEveryVector(mesh); + vcg::tri::UpdateTopology::FaceFace(mesh); + vcg::tri::Clean::RemoveDuplicateFace(mesh); + vcg::tri::UpdateTopology::FaceFace(mesh); + vcg::tri::Clean::RemoveZeroAreaFace(mesh); + vcg::tri::Clean::RemoveUnreferencedVertex(mesh); + vcg::tri::UpdateTopology::FaceFace(mesh); + vcg::tri::UpdateBounding::Box(mesh); + vcg::tri::Clean::RemoveSmallConnectedComponentsDiameter(mesh, minimum_diameter * mesh.bbox.Diag()); + vcg::tri::Clean::RemoveUnreferencedVertex(mesh); + vcg::tri::UpdateTopology::FaceFace(mesh); + vcg::tri::UpdateBounding::Box(mesh); + if (verbose) std::cout << "OK" << std::endl; + if (smooth_step > 0) { + if (verbose) std::cout << "[Laplacian smoothing] "; + vcg::tri::Smooth::VertexCoordLaplacian(mesh, smooth_step, false, true); + if (verbose) std::cout << "OK" << std::endl; } - // Faces to Eigen matrixXi F1 - i = 0; - F.resize(mesh.FN(), mesh.face.begin()->VN()); - for (TMesh::FaceIterator it = mesh.face.begin(); it != mesh.face.end(); ++it) if (!it->IsD()) { - for (int k = 0; k < it->VN(); k++) { - F(i, k) = vertexId[vcg::tri::Index(mesh, it->V(k))]; + vcg::tri::Clean::MergeCloseVertex(mesh, SLICE_PRECISION * 1000); + vcg::tri::Clean::RemoveUnreferencedVertex(mesh); + vcg::tri::Allocator::CompactEveryVector(mesh); + vcg::tri::UpdateTopology::FaceFace(mesh); +} + +inline bool fix_non_manifold(TMesh& mesh, double minimum_diameter, uint16_t max_iteration = 5, bool verbose = false) { + size_t nf = 1; + int maxSize = mesh.bbox.SquaredDiag(); + + int edgeNum = 0, edgeBorderNum = 0, edgeNonManifoldNum = 0; + vcg::tri::Clean::CountEdgeNum(mesh, edgeNum, edgeBorderNum, edgeNonManifoldNum); + + for (uint16_t iteration = 0; (edgeBorderNum > 0 || edgeNonManifoldNum > 0) && iteration < max_iteration; iteration++) { + if (verbose) std::cout << "[Fix non-manifold]" << std::endl << " -- Iteration " << iteration + 1 << std::endl; + if (verbose) std::cout << " -- non-manifold edges: " << edgeNonManifoldNum << std::endl; + if (edgeNonManifoldNum > 0) { + // fix floating faces + vcg::tri::Clean::RemoveSmallConnectedComponentsDiameter(mesh, minimum_diameter * mesh.bbox.Diag()); + vcg::tri::Clean::RemoveUnreferencedVertex(mesh); + if (verbose) std::cout << " -- Remove small components " << minimum_diameter * mesh.bbox.Diag() << " [OK]" << std::endl; + // fix non-manifold edges + vcg::tri::Clean::RemoveNonManifoldFace(mesh); + vcg::tri::UpdateTopology::FaceFace(mesh); + if (verbose) std::cout << " -- Remove non-manifold edges [OK]" << std::endl; + // fix holes + if (vcg::tri::Clean::CountNonManifoldEdgeFF(mesh) > 0) { + std::cout << "[Warning]: Fixed Self-intersecting failed: Mesh has some not 2-manifold edges" << std::endl; + return false; + } + vcg::tri::Hole::EarCuttingIntersectionFill>(mesh, maxSize, false); + if (verbose) std::cout << " -- Close holes [OK]" << std::endl; + } + if (verbose) std::cout << " -- border edges: " << edgeBorderNum << std::endl; + if (edgeBorderNum > 0) { + // select border vertices and faces + vcg::tri::UpdateFlags::FaceBorderFromNone(mesh); + vcg::tri::UpdateFlags::VertexBorderFromFaceBorder(mesh); + vcg::tri::UpdateSelection::FaceFromBorderFlag(mesh); + vcg::tri::UpdateSelection::VertexFromBorderFlag(mesh); + // Dilate selection + vcg::tri::UpdateSelection::VertexFromFaceLoose(mesh); + vcg::tri::UpdateSelection::FaceFromVertexLoose(mesh); + vcg::tri::UpdateSelection::VertexClear(mesh); + vcg::tri::UpdateSelection::VertexFromFaceStrict(mesh); + // delete all selected + for (TMesh::FaceIterator it = mesh.face.begin(); it != mesh.face.end(); ++it) { + if (!it->IsD() && it->IsS()) + vcg::tri::Allocator::DeleteFace(mesh, *it); + } + for (TMesh::VertexIterator it = mesh.vert.begin(); it != mesh.vert.end(); ++it) { + if (!it->IsD() && it->IsS()) + vcg::tri::Allocator::DeleteVertex(mesh, *it); + } + vcg::tri::Allocator::CompactEveryVector(mesh); + vcg::tri::UpdateTopology::FaceFace(mesh); + if (verbose) std::cout << " -- Remove Border faces [OK]" << std::endl; + // fix floating faces + vcg::tri::Clean::RemoveSmallConnectedComponentsDiameter(mesh, minimum_diameter * mesh.bbox.Diag()); + vcg::tri::Clean::RemoveUnreferencedVertex(mesh); + if (verbose) std::cout << " -- Remove small components " << minimum_diameter * mesh.bbox.Diag() << " [OK]" << std::endl; + // fix non-manifold edges + vcg::tri::Clean::RemoveNonManifoldFace(mesh); + vcg::tri::UpdateTopology::FaceFace(mesh); + if (verbose) std::cout << " -- Remove non-manifold edges [OK]" << std::endl; + // fix holes + if (vcg::tri::Clean::CountNonManifoldEdgeFF(mesh) > 0) { + std::cout << "[Warning]: Fixed Self-intersecting failed: Mesh has some not 2-manifold edges" << std::endl; + return false; + } + vcg::tri::Hole::EarCuttingIntersectionFill>(mesh, maxSize, false); + if (verbose) std::cout << " -- Close holes [OK]" << std::endl; + } + + vcg::tri::Clean::CountEdgeNum(mesh, edgeNum, edgeBorderNum, edgeNonManifoldNum); + } + + if (edgeBorderNum > 0 || edgeNonManifoldNum > 0) return false; + return true; +} + +inline bool fix_self_intersect_mesh(TMesh& mesh, double minimum_diameter, uint16_t max_iteration = 10, bool verbose = false) { + + std::vector faces; + std::vector::iterator fc; + vcg::tri::Clean::SelfIntersections(mesh, faces); + + uint16_t iteration = 0; + int maxSize = mesh.bbox.SquaredDiag(); + size_t nf = 0, nv = 0; + while (iteration < max_iteration && (faces.size() > 0 || nf > 0)) { + + if (verbose) std::cout << "[Fix Self-intersect face]" << std::endl << " -- Iteration " << iteration + 1 << std::endl; + vcg::tri::UpdateSelection::FaceClear(mesh); + if (faces.size() > 0) { + // Select self-intersect faces + for (fc = faces.begin(); fc != faces.end(); fc++) { + (*fc)->SetS(); + } + // Dilate the faces and vertices + for (uint16_t dilate_step = 0; dilate_step < iteration + 1; dilate_step++) { + vcg::tri::UpdateSelection::VertexFromFaceLoose(mesh); + vcg::tri::UpdateSelection::FaceFromVertexLoose(mesh); + } + // Select vertices from current faces and remove all selected + vcg::tri::UpdateSelection::VertexClear(mesh); + vcg::tri::UpdateSelection::VertexFromFaceStrict(mesh); + for (TMesh::FaceIterator it = mesh.face.begin(); it != mesh.face.end(); ++it) { + if (!it->IsD() && it->IsS()) + vcg::tri::Allocator::DeleteFace(mesh, *it); + } + for (TMesh::VertexIterator it = mesh.vert.begin(); it != mesh.vert.end(); ++it) { + if (!it->IsD() && it->IsS()) + vcg::tri::Allocator::DeleteVertex(mesh, *it); + } + if (verbose) std::cout << " -- self-intersect faces: " << faces.size() << std::endl; + vcg::tri::Clean::RemoveUnreferencedVertex(mesh); + vcg::tri::Allocator::CompactEveryVector(mesh); + vcg::tri::UpdateTopology::FaceFace(mesh); + if (verbose) std::cout << " -- Remove faces [OK]" << std::endl; + vcg::tri::Clean::RemoveSmallConnectedComponentsDiameter(mesh, minimum_diameter * mesh.bbox.Diag()); + vcg::tri::Clean::RemoveUnreferencedVertex(mesh); + if (verbose) std::cout << " -- Remove small components " << minimum_diameter * mesh.bbox.Diag() << " [OK]" << std::endl; + vcg::tri::Clean::RemoveNonManifoldFace(mesh); + vcg::tri::UpdateTopology::FaceFace(mesh); + if (verbose) std::cout << " -- Remove non-manifold edges [OK]" << std::endl; + if (vcg::tri::Clean::CountNonManifoldEdgeFF(mesh) > 0) { + std::cout << "[Warning]: Fixed Self-intersecting failed: Mesh has some not 2-manifold edges" << std::endl; + return false; + } + vcg::tri::Hole::EarCuttingIntersectionFill>(mesh, maxSize, false); + if (verbose) std::cout << " -- Close holes [OK]" << std::endl; + vcg::tri::UpdateSelection::FaceClear(mesh); + vcg::tri::UpdateSelection::VertexClear(mesh); + } + + vcg::tri::UpdateSelection::FaceClear(mesh); + vcg::tri::UpdateSelection::VertexClear(mesh); + vcg::tri::UpdateFlags::FaceBorderFromNone(mesh); + vcg::tri::UpdateFlags::VertexBorderFromFaceBorder(mesh); + vcg::tri::UpdateSelection::FaceFromBorderFlag(mesh); + vcg::tri::UpdateSelection::VertexFromBorderFlag(mesh); + nf = vcg::tri::UpdateSelection::FaceCount(mesh); + nv = vcg::tri::UpdateSelection::VertexCount(mesh); + if (verbose) std::cout << " -- Count border edge v:" << nv << " f:" << nf << std::endl; + if (nf > 0) { + // Dilate the faces and vertices + //for (uint16_t dilate_step = 0; dilate_step < iteration + 1; dilate_step++) { + vcg::tri::UpdateSelection::VertexFromFaceLoose(mesh); + vcg::tri::UpdateSelection::FaceFromVertexLoose(mesh); + //} + vcg::tri::UpdateSelection::VertexClear(mesh); + vcg::tri::UpdateSelection::VertexFromFaceStrict(mesh); + for (TMesh::FaceIterator it = mesh.face.begin(); it != mesh.face.end(); ++it) { + if (!it->IsD() && it->IsS()) + vcg::tri::Allocator::DeleteFace(mesh, *it); + } + for (TMesh::VertexIterator it = mesh.vert.begin(); it != mesh.vert.end(); ++it) { + if (!it->IsD() && it->IsS()) + vcg::tri::Allocator::DeleteVertex(mesh, *it); + } + vcg::tri::Allocator::CompactEveryVector(mesh); + vcg::tri::UpdateTopology::FaceFace(mesh); + if (verbose) std::cout << " -- Remove Border faces [OK]" << std::endl; + vcg::tri::Clean::RemoveSmallConnectedComponentsDiameter(mesh, minimum_diameter * mesh.bbox.Diag()); + vcg::tri::Clean::RemoveUnreferencedVertex(mesh); + if (verbose) std::cout << " -- Remove small components " << minimum_diameter * mesh.bbox.Diag() << " [OK]" << std::endl; + vcg::tri::Clean::RemoveNonManifoldFace(mesh); + vcg::tri::UpdateTopology::FaceFace(mesh); + if (verbose) std::cout << " -- Remove non-manifold edges [OK]" << std::endl; + if (vcg::tri::Clean::CountNonManifoldEdgeFF(mesh) > 0) { + std::cout << "[Warning]: Fixed Self-intersecting failed: Mesh has some not 2-manifold edges" << std::endl; + return false; + } + vcg::tri::Hole::EarCuttingIntersectionFill>(mesh, maxSize, false); + if (verbose) std::cout << " -- Close holes [OK]" << std::endl; + + vcg::tri::UpdateSelection::FaceClear(mesh); + vcg::tri::UpdateFlags::FaceBorderFromNone(mesh); + vcg::tri::UpdateFlags::VertexBorderFromFaceBorder(mesh); + vcg::tri::UpdateSelection::FaceFromBorderFlag(mesh); + vcg::tri::UpdateSelection::VertexFromBorderFlag(mesh); + nf = vcg::tri::UpdateSelection::FaceCount(mesh); } - i++; + + vcg::tri::Clean::SelfIntersections(mesh, faces); + iteration++; } + + vcg::tri::Allocator::CompactEveryVector(mesh); + vcg::tri::UpdateTopology::FaceFace(mesh); + + if (faces.size() > 0 && nf > 0) return false; + return true; } -#endif \ No newline at end of file + +inline void report_mesh(TMesh& mesh) { + int connectedComponentsNum = vcg::tri::Clean::CountConnectedComponents(mesh); + std::cout + << "[Topology Measurement] " << std::endl + << "-- Mesh is composed by " << connectedComponentsNum << " connected component(s)" << std::endl; + + int edgeNum = 0, edgeBorderNum = 0, edgeNonManifoldNum = 0; + vcg::tri::Clean::CountEdgeNum(mesh, edgeNum, edgeBorderNum, edgeNonManifoldNum); + int vertManifNum = vcg::tri::Clean::CountNonManifoldVertexFF(mesh, false); + + std::cout + << "-- border edge: " << edgeBorderNum << std::endl + << "-- non-manifold edge: " << edgeNonManifoldNum << std::endl + << "-- non-manifold vertex: " << vertManifNum << std::endl; + + if (edgeNonManifoldNum == 0 && vertManifNum == 0) { + int holeNum = vcg::tri::Clean::CountHoles(mesh); + int genus = vcg::tri::Clean::MeshGenus(mesh.vn, edgeNum, mesh.fn, holeNum, connectedComponentsNum); + + std::cout + << "-- Mesh is two-manifold " << std::endl + << "-- Mesh has " << holeNum << " holes" << std::endl + << "-- Genus is " << genus << std::endl; + } +} + +inline bool is_mesh_manifold(TMesh& mesh) { + int edgeNum = 0, edgeBorderNum = 0, edgeNonManifoldNum = 0; + vcg::tri::Clean::CountEdgeNum(mesh, edgeNum, edgeBorderNum, edgeNonManifoldNum); + int vertManifNum = vcg::tri::Clean::CountNonManifoldVertexFF(mesh, false); + return (edgeNonManifoldNum == 0 && vertManifNum == 0); +} \ No newline at end of file diff --git a/include/OptimalSlice.hpp b/include/OptimalSlice.hpp index fa76c3c..78b34f9 100644 --- a/include/OptimalSlice.hpp +++ b/include/OptimalSlice.hpp @@ -386,7 +386,7 @@ namespace slice { L.resize(grid_size + 2); for (size_t i = 0; i <= grid_size + 1; i++) L[i].clear(); // foreach triangle in mesh - // TODO: implement parallel version with tbb +#ifndef USE_PARALLEL for (TMesh::FaceIterator it = mesh.face.begin(); it != mesh.face.end(); it++) { if (!it->IsD()) { @@ -397,6 +397,34 @@ namespace slice { L[i].push_back(triangle); } } +#else + tbb::spin_mutex writeMutex; + static tbb::affinity_partitioner ap; + tbb::parallel_for( + tbb::blocked_range(0, mesh.face.size()), + [&](const tbb::blocked_range r) { + // Prepare local_L + Layer _L(grid_size + 2); + _L.resize(grid_size + 2); + for (size_t i = 0; i <= grid_size + 1; i++) _L[i].clear(); + for (size_t i = r.begin(); i < r.end(); i++) { + if (!mesh.face[i].IsD()) { + Triangle triangle(mesh.face[i]); + size_t level = size_t(ceil((triangle.min[direction] - P[1]) / delta) + 1); + assert(level > 0 && level <= grid_size + 1); + _L[level].push_back(triangle); + } + } + { + tbb::spin_mutex::scoped_lock lock(writeMutex); + for (size_t i = 0; i <= grid_size + 1; i++) { + L[i].reserve(L[i].size() + _L[i].size()); + L[i].insert(L[i].end(), _L[i].begin(), _L[i].end()); + } + } + }, ap + ); +#endif } inline Point3d compute_point_at_plane(Point3d v0, Point3d v1, double position, int direction = Direction::Z) { diff --git a/include/ProgressBar.hpp b/include/ProgressBar.hpp index 608ff2a..ccb6a2b 100644 --- a/include/ProgressBar.hpp +++ b/include/ProgressBar.hpp @@ -23,6 +23,8 @@ class ProgressBar { unsigned int operator++() { return ++ticks; } ProgressBar& operator+=(const unsigned int tick) { ticks += tick; return *this; } + void update(const unsigned int tick) { ticks = tick; } + void display() const { float progress = (float)ticks / total_ticks; diff --git a/include/Scaffolder_2.h b/include/Scaffolder_2.h index 48e96a6..973542e 100644 --- a/include/Scaffolder_2.h +++ b/include/Scaffolder_2.h @@ -118,145 +118,25 @@ inline void marching_cube(TMesh &mesh, Eigen::MatrixXd &Fxyz, Eigen::RowVector3i } } -inline void clean_mesh(TMesh& mesh, double minimum_diameter, uint16_t smooth_step, bool verbose = true) { - if (verbose) std::cout << "[libVCG Cleaning] "; - vcg::tri::Clean::RemoveDuplicateVertex(mesh); - vcg::tri::Allocator::CompactEveryVector(mesh); - vcg::tri::UpdateTopology::FaceFace(mesh); - vcg::tri::Clean::RemoveDuplicateFace(mesh); - vcg::tri::UpdateTopology::FaceFace(mesh); - vcg::tri::Clean::RemoveZeroAreaFace(mesh); - vcg::tri::Clean::RemoveUnreferencedVertex(mesh); - vcg::tri::UpdateTopology::FaceFace(mesh); - vcg::tri::UpdateBounding::Box(mesh); - vcg::tri::Clean::RemoveSmallConnectedComponentsDiameter(mesh, minimum_diameter * mesh.bbox.Diag()); - vcg::tri::Clean::RemoveUnreferencedVertex(mesh); - vcg::tri::UpdateTopology::FaceFace(mesh); - vcg::tri::UpdateBounding::Box(mesh); - if (verbose) std::cout << "OK" << std::endl; - if (smooth_step > 0) { - if (verbose) std::cout << "[Laplacian smoothing] "; - vcg::tri::Smooth::VertexCoordLaplacian(mesh, smooth_step, false, true); - if (verbose) std::cout << "OK" << std::endl; +inline void mesh_to_eigen_vector(TMesh& mesh, Eigen::MatrixXd& V, Eigen::MatrixXi& F) { + V.resize(mesh.VN(), 3); + size_t i = 0; + std::vector vertexId(mesh.vert.size()); + for (TMesh::VertexIterator it = mesh.vert.begin(); it != mesh.vert.end(); ++it) if (!it->IsD()) { + vertexId[it - mesh.vert.begin()] = i; + vcg::Point3d point = it->P(); + V(i, 0) = point[0]; + V(i, 1) = point[1]; + V(i, 2) = point[2]; + i++; } - vcg::tri::Clean::MergeCloseVertex(mesh, SLICE_PRECISION*1000); - vcg::tri::Clean::RemoveUnreferencedVertex(mesh); - vcg::tri::Allocator::CompactEveryVector(mesh); - vcg::tri::UpdateTopology::FaceFace(mesh); -} - -inline void fix_self_intersect_mesh(TMesh& mesh, double minimum_diameter, uint16_t max_iteration = 10, bool verbose = false) { - - std::vector faces; - vcg::tri::Clean::SelfIntersections(mesh, faces); - - uint16_t iteration = 0; - int maxSize = mesh.bbox.SquaredDiag(); - size_t nf = 0; - while (iteration < max_iteration && (faces.size() > 0 || nf > 0)) { - - if (verbose) std::cout << "[Fix Self-intersect face]" << std::endl << " -- Iteration " << iteration + 1 << std::endl; - - if (faces.size() > 0) { - for (size_t i = 0; i < faces.size(); i++) { - if (!faces[i]->IsD()) - vcg::tri::Allocator::DeleteFace(mesh, *(faces[i])); - } - if (verbose) std::cout << " -- self-intersect faces: " << faces.size() << std::endl; - vcg::tri::Clean::RemoveUnreferencedVertex(mesh); - vcg::tri::Allocator::CompactEveryVector(mesh); - vcg::tri::UpdateTopology::FaceFace(mesh); - vcg::tri::UpdateBounding::Box(mesh); - if (verbose) std::cout << " -- Remove faces [OK]" << std::endl; - vcg::tri::Clean::RemoveSmallConnectedComponentsDiameter(mesh, minimum_diameter * mesh.bbox.Diag()); - vcg::tri::Clean::RemoveUnreferencedVertex(mesh); - if (verbose) std::cout << " -- Remove small components " << minimum_diameter * mesh.bbox.Diag() << " [OK]" << std::endl; - vcg::tri::Clean::RemoveNonManifoldFace(mesh); - vcg::tri::UpdateTopology::FaceFace(mesh); - if (verbose) std::cout << " -- Remove non-manifold edges [OK]" << std::endl; - if (vcg::tri::Clean::CountNonManifoldEdgeFF(mesh) > 0) { - std::cout << "[Warning]: Fixed Self-intersecting failed: Mesh has some not 2-manifold edges" << std::endl; - return; - } - vcg::tri::Hole::EarCuttingIntersectionFill>(mesh, maxSize, false); - if (verbose) std::cout << " -- Close holes [OK]" << std::endl; - } - - vcg::tri::UpdateFlags::FaceBorderFromNone(mesh); - vcg::tri::UpdateFlags::VertexBorderFromFaceBorder(mesh); - vcg::tri::UpdateSelection::FaceFromBorderFlag(mesh); - vcg::tri::UpdateSelection::VertexFromBorderFlag(mesh); - nf = vcg::tri::UpdateSelection::FaceCount(mesh); - if (nf > 0) { - vcg::tri::UpdateSelection::VertexFromFaceLoose(mesh); - vcg::tri::UpdateSelection::FaceFromVertexLoose(mesh); - vcg::tri::UpdateSelection::VertexClear(mesh); - vcg::tri::UpdateSelection::VertexFromFaceStrict(mesh); - for (TMesh::FaceIterator it = mesh.face.begin(); it != mesh.face.end(); ++it) { - if (!it->IsD() && it->IsS()) - vcg::tri::Allocator::DeleteFace(mesh, *it); - } - for (TMesh::VertexIterator it = mesh.vert.begin(); it != mesh.vert.end(); ++it) { - if (!it->IsD() && it->IsS()) - vcg::tri::Allocator::DeleteVertex(mesh, *it); - } - vcg::tri::Allocator::CompactEveryVector(mesh); - vcg::tri::UpdateTopology::FaceFace(mesh); - if (verbose) std::cout << " -- Remove Border faces [OK]" << std::endl; - vcg::tri::Clean::RemoveNonManifoldFace(mesh); - vcg::tri::UpdateBounding::Box(mesh); - vcg::tri::UpdateTopology::FaceFace(mesh); - if (verbose) std::cout << " -- Remove non-manifold edges [OK]" << std::endl; - vcg::tri::Hole::EarCuttingIntersectionFill>(mesh, maxSize, false); - if (verbose) std::cout << " -- Close holes [OK]" << std::endl; - - vcg::tri::UpdateFlags::FaceBorderFromNone(mesh); - vcg::tri::UpdateFlags::VertexBorderFromFaceBorder(mesh); - vcg::tri::UpdateSelection::FaceFromBorderFlag(mesh); - vcg::tri::UpdateSelection::VertexFromBorderFlag(mesh); - nf = vcg::tri::UpdateSelection::FaceCount(mesh); + // Faces to Eigen matrixXi F1 + i = 0; + F.resize(mesh.FN(), mesh.face.begin()->VN()); + for (TMesh::FaceIterator it = mesh.face.begin(); it != mesh.face.end(); ++it) if (!it->IsD()) { + for (int k = 0; k < it->VN(); k++) { + F(i, k) = vertexId[vcg::tri::Index(mesh, it->V(k))]; } - - vcg::tri::Clean::SelfIntersections(mesh, faces); - iteration++; - } - vcg::tri::Allocator::CompactEveryVector(mesh); - - while (iteration < max_iteration && nf > 0) { - if (verbose) std::cout << "[Fix Border Edges and holes]" << std::endl << " -- Iteration " << iteration + 1 << std::endl; - - - - iteration++; - } - vcg::tri::UpdateBounding::Box(mesh); - vcg::tri::UpdateTopology::FaceFace(mesh); -} - -inline void report_mesh(TMesh& mesh) { - int connectedComponentsNum = vcg::tri::Clean::CountConnectedComponents(mesh); - std::cout - << "[Topology Measurement] " << std::endl - << "-- Mesh is composed by " << connectedComponentsNum << " connected component(s)" << std::endl; - - int edgeNum = 0, edgeBorderNum = 0, edgeNonManifoldNum = 0; - vcg::tri::Clean::CountEdgeNum(mesh, edgeNum, edgeBorderNum, edgeNonManifoldNum); - int vertManifNum = vcg::tri::Clean::CountNonManifoldVertexFF(mesh, true); - - if (edgeNonManifoldNum == 0 && vertManifNum == 0) { - int holeNum = vcg::tri::Clean::CountHoles(mesh); - int genus = vcg::tri::Clean::MeshGenus(mesh.vn, edgeNum, mesh.fn, holeNum, connectedComponentsNum); - - std::cout - << "-- Mesh is two-manifold " << std::endl - << "-- Mesh has " << holeNum << " holes" << std::endl - << "-- Genus is " << genus << std::endl; + i++; } -} - -inline bool is_mesh_manifold(TMesh &mesh) { - int edgeNum = 0, edgeBorderNum = 0, edgeNonManifoldNum = 0; - vcg::tri::Clean::CountEdgeNum(mesh, edgeNum, edgeBorderNum, edgeNonManifoldNum); - int vertManifNum = vcg::tri::Clean::CountNonManifoldVertexFF(mesh, true); - return edgeNonManifoldNum == 0 && vertManifNum == 0; } \ No newline at end of file diff --git a/include/implicit_function.h b/include/implicit_function.h index 17649aa..e881a12 100644 --- a/include/implicit_function.h +++ b/include/implicit_function.h @@ -233,12 +233,6 @@ class Implicit_function : public Function { } }; -inline void to_lower(std::string& s) { - std::locale loc; - for (std::string::size_type i = 0; i < s.length(); ++i) - s[i] = std::tolower(s[i], loc); -} - Function* isosurface(std::string name, FT t) { if (name == "empty") { return new Fixed(1); diff --git a/include/utils.h b/include/utils.h index 8ca84e0..ec54486 100644 --- a/include/utils.h +++ b/include/utils.h @@ -1,5 +1,7 @@ #pragma once +#include + #ifdef _WIN32 #include #include @@ -69,7 +71,7 @@ namespace util { // Find the last dot, if any. size_t dotIdx = path.find_last_of("."); - if (dotIdx != string::npos) + if (dotIdx != std::string::npos) { // Find the last directory separator, if any. size_t dirSepIdx = path.find_last_of("/\\"); @@ -86,4 +88,10 @@ namespace util { return ext; } + + inline void to_lower(std::string& s) { + std::locale loc; + for (std::string::size_type i = 0; i < s.length(); ++i) + s[i] = std::tolower(s[i], loc); + } } \ No newline at end of file diff --git a/src/FixSelfIntersect.cpp b/src/FixSelfIntersect.cpp new file mode 100644 index 0000000..1127432 --- /dev/null +++ b/src/FixSelfIntersect.cpp @@ -0,0 +1,142 @@ +#include +#include +#include + +#include "cxxopts.hpp" +#include "ProgressBar.hpp" +#include "utils.h" +#include "Mesh.h" + +ProgressBar import_progress(100, 40); + +bool import_callback(int pos, const char* str) { + if (pos % 10 == 0) { + import_progress.update(pos); + import_progress.display(); + } + if (pos >= 100) import_progress.done(); + return true; +} + +int main(int argc, char* argv[]) +{ + bool verbose = true; + double minimum_diameter = 0.25; + uint16_t max_iteration = 5; + + std::string input_file = ""; + std::string format = ""; + std::string output_file = ""; + std::string output_format = ""; + + try { + cxxopts::Options options("Fix Self Intersect", "Automatic fix self-intersect"); + options.positional_help("INPUT OUTPUT MAX_ITERATION").show_positional_help(); + options.add_options() + ("h,help", "Print help") + ("i,input", "Input file (STL,PLY,OFF,OBJ)", cxxopts::value(), "FILE") + ("o,output", "Output file (STL,PLY,OFF,OBJ)", cxxopts::value(), "FILE") + ("n,max_iteration", "Max iteration [default: 5]", cxxopts::value(), "UINT16"); + options.parse_positional({ "input", "output", "max_iteration" }); + bool isEmptyOption = (argc == 1); + cxxopts::ParseResult result = options.parse(argc, argv); + if (isEmptyOption || result.count("help")) { + std::cout << options.help() << std::endl; + return 0; + } + // Requirment + if (result.count("input")) { + input_file = result["input"].as(); + std::string ext = util::PathGetExtension(input_file); + if (!ext.empty()) { + format = ext.substr(1); + util::to_lower(format); + } + } + else { + std::cout << "Missing Input file" << std::endl; + return 1; + } + + // Optional parameter: output_file default to input_file + if (result.count("output")) { + output_file = result["output"].as(); + std::string ext = util::PathGetExtension(output_file); + if (!ext.empty()) { + output_format = ext.substr(1); + util::to_lower(output_format); + } + } + else { + output_file = input_file; + output_format = format; + } + + if (format != "ply" && format != "obj" && format != "stl" && format != "off") { + std::cout << "Invalid input extension: " << format << std::endl; + return 1; + } + + if (output_format != "ply" && output_format != "obj" && output_format != "stl" && output_format != "off") { + std::cout << "Invalid output extension: " << output_format << std::endl; + return 1; + } + + if (result.count("max_iteration")) max_iteration = result["max_iteration"].as(); + } + catch (const cxxopts::OptionException & ex) { + std::cout << "Error parsing options: " << ex.what() << std::endl; + return 1; + } + + { + TMesh mesh; + { + if (verbose) std::cout << "Import " << format << std::endl; + int loadmark = 0; + if (format == "stl") { + vcg::tri::io::ImporterSTL::Open(mesh, input_file.c_str(), loadmark, import_callback); + } + else if (format == "ply") { + vcg::tri::io::ImporterPLY::Open(mesh, input_file.c_str(), import_callback); + } + else if (format == "off") { + vcg::tri::io::ImporterOFF::Open(mesh, input_file.c_str(), import_callback); + } + else if (format == "obj") { + vcg::tri::io::ImporterOBJ::Open(mesh, input_file.c_str(), loadmark, import_callback); + } + if (verbose) std::cout << std::endl; + } + vcg::tri::UpdateBounding::Box(mesh); + + bool is_success = fix_self_intersect_mesh(mesh, minimum_diameter, max_iteration, verbose); + + bool is_manifold = is_mesh_manifold(mesh); + if (!is_manifold) { + is_success = fix_non_manifold(mesh, minimum_diameter, max_iteration, verbose); + } + if(verbose) report_mesh(mesh); + + if (is_success) { + if (verbose) std::cout << "Export " << output_file; + if (output_format == "stl") { + vcg::tri::io::ExporterSTL::Save(mesh, output_file.c_str(), true); + } + else if (output_format == "ply") { + vcg::tri::io::ExporterPLY::Save(mesh, output_file.c_str(), false); + } + else if (output_format == "off") { + vcg::tri::io::ExporterOFF::Save(mesh, output_file.c_str()); + } + else if (output_format == "obj") { + vcg::tri::io::ExporterOBJ::Save(mesh, output_file.c_str(), 0); + } + if (verbose) std::cout << " [OK]" << std::endl; + } + else { + if (verbose) std::cout << "Sorry, fail to fix" << std::endl; + } + } + return 0; +} \ No newline at end of file diff --git a/src/Main.cpp b/src/Main.cpp index 83a1f91..80d5723 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -9,7 +9,7 @@ int main(int argc, char* argv[]) bool is_export_microstructure = false; bool is_export_feret = false; bool is_export_inverse = false; - bool is_build_inverse = true; + bool is_build_inverse = false; bool is_fix_self_intersect = false; bool no_output = false; uint16_t grid_offset = 3; @@ -51,7 +51,7 @@ int main(int argc, char* argv[]) ("method", "Method of microstructure analysis: 0 (Image processing technique) or 1 (Slice contour technique) [default: 0]", cxxopts::value(), "0,1") ("slice_grid", "Slice Grid used in microstructure analysis [default: 100]", cxxopts::value(), "INT") ("output_inverse", "additional output inverse scaffold [default: false]") - ("inverse", "Enable build inverse 3D scaffold (for pore connectivity analysis) [default: true]") + ("inverse", "Enable build inverse 3D scaffold (for pore connectivity analysis) [default: false]") ("dirty", "Disable autoclean [default false]") ("fix_self_intersect", "Experimental fix self-intersect faces [default: false]") ("minimum_diameter", "used for removing small orphaned (between 0-1) [default: 0.25]", cxxopts::value(), "DOUBLE"); @@ -97,7 +97,7 @@ int main(int argc, char* argv[]) } if (result.count("output")) { filename = result["output"].as(); - to_lower(filename); + util::to_lower(filename); std::string ext = util::PathGetExtension(filename); if (!ext.empty()) { filename = filename.substr(0, filename.size()-ext.size()); @@ -118,8 +118,8 @@ int main(int argc, char* argv[]) if (result.count("fix_self_intersect")) is_fix_self_intersect = result["fix_self_intersect"].as(); if (result.count("slice_grid")) slice_grid = result["slice_grid"].as(); - to_lower(surface); - to_lower(format); + util::to_lower(surface); + util::to_lower(format); if (format != "ply" && format != "obj" && format != "stl" && format != "off") { std::cout << "Invalid format: " << format << std::endl; @@ -166,7 +166,6 @@ int main(int argc, char* argv[]) result << "Surface,coff,shell,thickness,grid_size,grid_offset,smooth_step,input_file,avg_min_feret,avg_max_feret," << "min_min_feret,q1_min_feret,q2_min_feret,q3_min_feret,max_min_feret," << "min_max_feret,q1_max_feret,q2_max_feret,q3_max_feret,max_max_feret," - << "vol,surface_area,porosity,surface_area_ratio,#vertices,#faces," << "min_square,q1_square,q2_square,q3_square,max_square," << "min_circle,q1_circle,q2_circle,q3_circle,max_circle," << "min_triangle,q1_triangle,q2_triangle,q3_triangle,max_triangle," @@ -336,6 +335,9 @@ int main(int argc, char* argv[]) if (is_build_inverse) clean_mesh(inverse_mesh, minimum_diameter, 0, false); } bool is_manifold = is_mesh_manifold(mesh); + if (!is_manifold) { + is_manifold = fix_non_manifold(mesh, minimum_diameter, 3, verbose); + } if (!is_manifold && !verbose) { std::cout << "[Warning] Mesh is not two manifold" << std::endl; } @@ -560,7 +562,7 @@ int main(int argc, char* argv[]) if (!verbose) { result << ",,,," << mesh.VN() << ',' << mesh.FN() << std::endl; } - std::cout << "[Warning] The scaffolder isn't a manifold. The grid_offset should have been increased" << std::endl; + std::cout << "[Warning] The scaffold isn't a manifold. The grid_offset should be increased or enable --fix_self_intersect option" << std::endl; } if (!no_output) { From 7612096782bd768b568ee55e55b007e3c721a804 Mon Sep 17 00:00:00 2001 From: Jirawat I Date: Sat, 14 Mar 2020 03:43:36 +0700 Subject: [PATCH 14/20] fixed cmake and utils.h --- CMakeLists.txt | 3 +-- include/utils.h | 1 + src/FixSelfIntersect.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a659c48..11abbb7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,5 +46,4 @@ target_include_directories(SliceTest PRIVATE "${PROJECT_SOURCE_DIR}/include" ${T target_link_libraries(SliceTest PRIVATE igl::core tbb_static) add_executable(Fixer ${MAIN_SOURCES} ${PROJECT_SOURCE_DIR}/src/FixSelfIntersect.cpp ${VCG_INCLUDE_DIR}/wrap/ply/plylib.cpp) -target_include_directories(Fixer PRIVATE "${PROJECT_SOURCE_DIR}/include" ${TBB_INCLUDE_DIR} ${VCG_INCLUDE_DIR}) -target_link_libraries(Fixer PRIVATE) +target_include_directories(Fixer PRIVATE "${PROJECT_SOURCE_DIR}/include" ${VCG_INCLUDE_DIR}) diff --git a/include/utils.h b/include/utils.h index ec54486..e9283cd 100644 --- a/include/utils.h +++ b/include/utils.h @@ -1,6 +1,7 @@ #pragma once #include +#include #ifdef _WIN32 #include diff --git a/src/FixSelfIntersect.cpp b/src/FixSelfIntersect.cpp index 1127432..bffa5b7 100644 --- a/src/FixSelfIntersect.cpp +++ b/src/FixSelfIntersect.cpp @@ -2,10 +2,10 @@ #include #include -#include "cxxopts.hpp" #include "ProgressBar.hpp" #include "utils.h" #include "Mesh.h" +#include "cxxopts.hpp" ProgressBar import_progress(100, 40); From 9162756d240227dc450497ae7e23f5b1cd846740 Mon Sep 17 00:00:00 2001 From: Jirawat I Date: Sat, 14 Mar 2020 04:20:09 +0700 Subject: [PATCH 15/20] fixed #7 --- .travis.yml | 70 +- include/cxxopts.hpp | 3588 ++++++++++++++++++++++--------------------- 2 files changed, 1854 insertions(+), 1804 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1148f47..b6cfe37 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,7 @@ language: cpp os: linux +dist: xenial +sudo: false branches: only: - master @@ -7,18 +9,64 @@ branches: jobs: include: - os: linux - script: - - mkdir build && cd build - - cmake .. - - make + addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - g++-7 + env: COMPILER=g++-7 CPP_VER=11 - os: osx osx_image: xcode10 - script: - - mkdir build && cd build - - cmake .. - - make + env: COMPILER=clang++ CPP_VER=11 - os: windows script: - - mkdir build && cd build - - cmake .. - - cmake --build . \ No newline at end of file + +install: + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update; fi + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install icu4c; fi + - if [[ "${COMPILER}" != "" ]]; then export CXX=${COMPILER}; fi + - ${CXX} --version + ############################################################################ + # Install libc++ and libc++abi if needed + # Taken from https://github.com/boostorg/hana/blob/master/.travis.yml + ############################################################################ + - | + if [[ "$CPP_VER" == "17" && "$TRAVIS_OS_NAME" == "linux" && "${CXX%%+*}" == "clang" ]]; then + if [[ "${CXX}" == "clang++-3.5" ]]; then LLVM_VERSION="3.5.2"; + elif [[ "${CXX}" == "clang++-3.6" ]]; then LLVM_VERSION="3.6.2"; + elif [[ "${CXX}" == "clang++-3.7" ]]; then LLVM_VERSION="3.7.1"; + elif [[ "${CXX}" == "clang++-3.8" ]]; then LLVM_VERSION="3.8.1"; + elif [[ "${CXX}" == "clang++-3.9" ]]; then LLVM_VERSION="3.9.1"; + elif [[ "${CXX}" == "clang++-4.0" ]]; then LLVM_VERSION="4.0.1"; + elif [[ "${CXX}" == "clang++-5.0" ]]; then LLVM_VERSION="5.0.2"; + elif [[ "${CXX}" == "clang++-6.0" ]]; then LLVM_VERSION="6.0.0"; + elif [[ "${CXX}" == "clang++-7" ]]; then LLVM_VERSION="7.0.0"; + elif [[ "${CXX}" == "clang++-8" ]]; then LLVM_VERSION="8.0.0"; + fi + LLVM_URL="http://llvm.org/releases/${LLVM_VERSION}/llvm-${LLVM_VERSION}.src.tar.xz" + LIBCXX_URL="http://llvm.org/releases/${LLVM_VERSION}/libcxx-${LLVM_VERSION}.src.tar.xz" + LIBCXXABI_URL="http://llvm.org/releases/${LLVM_VERSION}/libcxxabi-${LLVM_VERSION}.src.tar.xz" + mkdir -p llvm llvm/build llvm/projects/libcxx llvm/projects/libcxxabi + travis_retry wget -O - ${LLVM_URL} | tar --strip-components=1 -xJ -C llvm || exit 1 + travis_retry wget -O - ${LIBCXX_URL} | tar --strip-components=1 -xJ -C llvm/projects/libcxx || exit 1 + travis_retry wget -O - ${LIBCXXABI_URL} | tar --strip-components=1 -xJ -C llvm/projects/libcxxabi || exit 1 + (cd llvm/build && cmake .. -DCMAKE_INSTALL_PREFIX=${DEPS_DIR}/llvm/install) || exit 1 + (cd llvm/build/projects/libcxx && make install -j2) || exit 1 + (cd llvm/build/projects/libcxxabi && make install -j2) || exit 1 + export CXXFLAGS="-isystem ${DEPS_DIR}/llvm/install/include/c++/v1" + export LDFLAGS="-L ${DEPS_DIR}/llvm/install/lib -l c++ -l c++abi" + export LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${DEPS_DIR}/llvm/install/lib" + fi + +before_script: + - cd $TRAVIS_BUILD_DIR + - mkdir -p -v build + - cd build + - cmake .. + +script: + - cmake --build . -- -j2 + +notifications: + email: off \ No newline at end of file diff --git a/include/cxxopts.hpp b/include/cxxopts.hpp index 93c69d3..4c21c1b 100644 --- a/include/cxxopts.hpp +++ b/include/cxxopts.hpp @@ -54,13 +54,13 @@ THE SOFTWARE. namespace cxxopts { - static constexpr struct { - uint8_t major, minor, patch; - } version = { - CXXOPTS__VERSION_MAJOR, - CXXOPTS__VERSION_MINOR, - CXXOPTS__VERSION_PATCH - }; + static constexpr struct { + uint8_t major, minor, patch; + } version = { + CXXOPTS__VERSION_MAJOR, + CXXOPTS__VERSION_MINOR, + CXXOPTS__VERSION_PATCH + }; } //when we ask cxxopts to use Unicode, help strings are processed using ICU, @@ -74,134 +74,134 @@ namespace cxxopts namespace cxxopts { - typedef icu::UnicodeString String; - - inline - String - toLocalString(std::string s) - { - return icu::UnicodeString::fromUTF8(std::move(s)); - } - - class UnicodeStringIterator : public - std::iterator - { - public: + typedef icu::UnicodeString String; - UnicodeStringIterator(const icu::UnicodeString* string, int32_t pos) - : s(string) - , i(pos) + inline + String + toLocalString(std::string s) { + return icu::UnicodeString::fromUTF8(std::move(s)); } - value_type - operator*() const + class UnicodeStringIterator : public + std::iterator { - return s->char32At(i); - } + public: + + UnicodeStringIterator(const icu::UnicodeString* string, int32_t pos) + : s(string) + , i(pos) + { + } + + value_type + operator*() const + { + return s->char32At(i); + } + + bool + operator==(const UnicodeStringIterator& rhs) const + { + return s == rhs.s && i == rhs.i; + } + + bool + operator!=(const UnicodeStringIterator& rhs) const + { + return !(*this == rhs); + } + + UnicodeStringIterator& + operator++() + { + ++i; + return *this; + } + + UnicodeStringIterator + operator+(int32_t v) + { + return UnicodeStringIterator(s, i + v); + } + + private: + const icu::UnicodeString* s; + int32_t i; + }; - bool - operator==(const UnicodeStringIterator& rhs) const + inline + String& + stringAppend(String& s, String a) { - return s == rhs.s && i == rhs.i; + return s.append(std::move(a)); } - bool - operator!=(const UnicodeStringIterator& rhs) const + inline + String& + stringAppend(String& s, int n, UChar32 c) { - return !(*this == rhs); + for (int i = 0; i != n; ++i) + { + s.append(c); + } + + return s; } - UnicodeStringIterator& - operator++() + template + String& + stringAppend(String& s, Iterator begin, Iterator end) { - ++i; - return *this; + while (begin != end) + { + s.append(*begin); + ++begin; + } + + return s; } - UnicodeStringIterator - operator+(int32_t v) + inline + size_t + stringLength(const String& s) { - return UnicodeStringIterator(s, i + v); + return s.length(); } - private: - const icu::UnicodeString* s; - int32_t i; - }; - - inline - String& - stringAppend(String&s, String a) - { - return s.append(std::move(a)); - } - - inline - String& - stringAppend(String& s, int n, UChar32 c) - { - for (int i = 0; i != n; ++i) - { - s.append(c); - } + inline + std::string + toUTF8String(const String& s) + { + std::string result; + s.toUTF8String(result); - return s; - } + return result; + } - template - String& - stringAppend(String& s, Iterator begin, Iterator end) - { - while (begin != end) + inline + bool + empty(const String& s) { - s.append(*begin); - ++begin; + return s.isEmpty(); } - - return s; - } - - inline - size_t - stringLength(const String& s) - { - return s.length(); - } - - inline - std::string - toUTF8String(const String& s) - { - std::string result; - s.toUTF8String(result); - - return result; - } - - inline - bool - empty(const String& s) - { - return s.isEmpty(); - } } namespace std { - inline - cxxopts::UnicodeStringIterator - begin(const icu::UnicodeString& s) - { - return cxxopts::UnicodeStringIterator(&s, 0); - } - - inline - cxxopts::UnicodeStringIterator - end(const icu::UnicodeString& s) - { - return cxxopts::UnicodeStringIterator(&s, s.length()); - } + inline + cxxopts::UnicodeStringIterator + begin(const icu::UnicodeString& s) + { + return cxxopts::UnicodeStringIterator(&s, 0); + } + + inline + cxxopts::UnicodeStringIterator + end(const icu::UnicodeString& s) + { + return cxxopts::UnicodeStringIterator(&s, s.length()); + } } //ifdef CXXOPTS_USE_UNICODE @@ -209,56 +209,56 @@ namespace std namespace cxxopts { - typedef std::string String; - - template - T - toLocalString(T&& t) - { - return std::forward(t); - } - - inline - size_t - stringLength(const String& s) - { - return s.length(); - } - - inline - String& - stringAppend(String&s, String a) - { - return s.append(std::move(a)); - } - - inline - String& - stringAppend(String& s, size_t n, char c) - { - return s.append(n, c); - } - - template - String& - stringAppend(String& s, Iterator begin, Iterator end) - { - return s.append(begin, end); - } - - template - std::string - toUTF8String(T&& t) - { - return std::forward(t); - } - - inline - bool - empty(const std::string& s) - { - return s.empty(); - } + typedef std::string String; + + template + T + toLocalString(T&& t) + { + return std::forward(t); + } + + inline + size_t + stringLength(const String& s) + { + return s.length(); + } + + inline + String& + stringAppend(String& s, String a) + { + return s.append(std::move(a)); + } + + inline + String& + stringAppend(String& s, size_t n, char c) + { + return s.append(n, c); + } + + template + String& + stringAppend(String& s, Iterator begin, Iterator end) + { + return s.append(begin, end); + } + + template + std::string + toUTF8String(T&& t) + { + return std::forward(t); + } + + inline + bool + empty(const std::string& s) + { + return s.empty(); + } } //ifdef CXXOPTS_USE_UNICODE @@ -266,1949 +266,1951 @@ namespace cxxopts namespace cxxopts { - namespace - { + namespace + { #ifdef _WIN32 - const std::string LQUOTE("\'"); - const std::string RQUOTE("\'"); + const std::string LQUOTE("\'"); + const std::string RQUOTE("\'"); #else - const std::string LQUOTE("‘"); - const std::string RQUOTE("’"); + const std::string LQUOTE("‘"); + const std::string RQUOTE("’"); #endif - } + } - class Value : public std::enable_shared_from_this - { + class Value : public std::enable_shared_from_this + { public: - virtual ~Value() = default; + virtual ~Value() = default; - virtual - std::shared_ptr - clone() const = 0; + virtual + std::shared_ptr + clone() const = 0; - virtual void - parse(const std::string& text) const = 0; + virtual void + parse(const std::string& text) const = 0; - virtual void - parse() const = 0; + virtual void + parse() const = 0; - virtual bool - has_default() const = 0; + virtual bool + has_default() const = 0; - virtual bool - is_container() const = 0; + virtual bool + is_container() const = 0; - virtual bool - has_implicit() const = 0; + virtual bool + has_implicit() const = 0; - virtual std::string - get_default_value() const = 0; + virtual std::string + get_default_value() const = 0; - virtual std::string - get_implicit_value() const = 0; + virtual std::string + get_implicit_value() const = 0; - virtual std::shared_ptr - default_value(const std::string& value) = 0; + virtual std::shared_ptr + default_value(const std::string& value) = 0; - virtual std::shared_ptr - implicit_value(const std::string& value) = 0; + virtual std::shared_ptr + implicit_value(const std::string& value) = 0; - virtual std::shared_ptr - no_implicit_value() = 0; + virtual std::shared_ptr + no_implicit_value() = 0; - virtual bool - is_boolean() const = 0; - }; + virtual bool + is_boolean() const = 0; + }; - class OptionException : public std::exception - { - public: - OptionException(const std::string& message) - : m_message(message) + class OptionException : public std::exception { - } + public: + OptionException(const std::string& message) + : m_message(message) + { + } - virtual const char* - what() const noexcept - { - return m_message.c_str(); - } + virtual const char* + what() const noexcept + { + return m_message.c_str(); + } private: - std::string m_message; - }; + std::string m_message; + }; - class OptionSpecException : public OptionException - { + class OptionSpecException : public OptionException + { public: - OptionSpecException(const std::string& message) - : OptionException(message) - { - } - }; + OptionSpecException(const std::string& message) + : OptionException(message) + { + } + }; - class OptionParseException : public OptionException - { - public: - OptionParseException(const std::string& message) - : OptionException(message) + class OptionParseException : public OptionException { - } - }; - - class option_exists_error : public OptionSpecException - { public: - option_exists_error(const std::string& option) - : OptionSpecException("Option " + LQUOTE + option + RQUOTE + " already exists") - { - } - }; + OptionParseException(const std::string& message) + : OptionException(message) + { + } + }; - class invalid_option_format_error : public OptionSpecException - { - public: - invalid_option_format_error(const std::string& format) - : OptionSpecException("Invalid option format " + LQUOTE + format + RQUOTE) + class option_exists_error : public OptionSpecException { - } - }; - - class option_syntax_exception : public OptionParseException { public: - option_syntax_exception(const std::string& text) - : OptionParseException("Argument " + LQUOTE + text + RQUOTE + - " starts with a - but has incorrect syntax") - { - } - }; + option_exists_error(const std::string& option) + : OptionSpecException("Option " + LQUOTE + option + RQUOTE + " already exists") + { + } + }; - class option_not_exists_exception : public OptionParseException - { - public: - option_not_exists_exception(const std::string& option) - : OptionParseException("Option " + LQUOTE + option + RQUOTE + " does not exist") + class invalid_option_format_error : public OptionSpecException { - } - }; - - class missing_argument_exception : public OptionParseException - { public: - missing_argument_exception(const std::string& option) - : OptionParseException( - "Option " + LQUOTE + option + RQUOTE + " is missing an argument" - ) - { - } - }; + invalid_option_format_error(const std::string& format) + : OptionSpecException("Invalid option format " + LQUOTE + format + RQUOTE) + { + } + }; - class option_requires_argument_exception : public OptionParseException - { + class option_syntax_exception : public OptionParseException { public: - option_requires_argument_exception(const std::string& option) - : OptionParseException( - "Option " + LQUOTE + option + RQUOTE + " requires an argument" - ) - { - } - }; + option_syntax_exception(const std::string& text) + : OptionParseException("Argument " + LQUOTE + text + RQUOTE + + " starts with a - but has incorrect syntax") + { + } + }; - class option_not_has_argument_exception : public OptionParseException - { - public: - option_not_has_argument_exception - ( - const std::string& option, - const std::string& arg - ) - : OptionParseException( - "Option " + LQUOTE + option + RQUOTE + - " does not take an argument, but argument " + - LQUOTE + arg + RQUOTE + " given" - ) + class option_not_exists_exception : public OptionParseException { - } - }; + public: + option_not_exists_exception(const std::string& option) + : OptionParseException("Option " + LQUOTE + option + RQUOTE + " does not exist") + { + } + }; - class option_not_present_exception : public OptionParseException - { + class missing_argument_exception : public OptionParseException + { public: - option_not_present_exception(const std::string& option) - : OptionParseException("Option " + LQUOTE + option + RQUOTE + " not present") + missing_argument_exception(const std::string& option) + : OptionParseException( + "Option " + LQUOTE + option + RQUOTE + " is missing an argument" + ) + { + } + }; + + class option_requires_argument_exception : public OptionParseException { - } - }; + public: + option_requires_argument_exception(const std::string& option) + : OptionParseException( + "Option " + LQUOTE + option + RQUOTE + " requires an argument" + ) + { + } + }; - class argument_incorrect_type : public OptionParseException - { + class option_not_has_argument_exception : public OptionParseException + { public: - argument_incorrect_type - ( - const std::string& arg - ) - : OptionParseException( - "Argument " + LQUOTE + arg + RQUOTE + " failed to parse" - ) + option_not_has_argument_exception + ( + const std::string& option, + const std::string& arg + ) + : OptionParseException( + "Option " + LQUOTE + option + RQUOTE + + " does not take an argument, but argument " + + LQUOTE + arg + RQUOTE + " given" + ) + { + } + }; + + class option_not_present_exception : public OptionParseException { - } - }; + public: + option_not_present_exception(const std::string& option) + : OptionParseException("Option " + LQUOTE + option + RQUOTE + " not present") + { + } + }; - class option_required_exception : public OptionParseException - { + class argument_incorrect_type : public OptionParseException + { public: - option_required_exception(const std::string& option) - : OptionParseException( - "Option " + LQUOTE + option + RQUOTE + " is required but not present" - ) + argument_incorrect_type + ( + const std::string& arg + ) + : OptionParseException( + "Argument " + LQUOTE + arg + RQUOTE + " failed to parse" + ) + { + } + }; + + class option_required_exception : public OptionParseException { - } - }; + public: + option_required_exception(const std::string& option) + : OptionParseException( + "Option " + LQUOTE + option + RQUOTE + " is required but not present" + ) + { + } + }; - template - void throw_or_mimic(const std::string& text) - { - static_assert(std::is_base_of::value, - "throw_or_mimic only works on std::exception and " - "deriving classes"); + template + void throw_or_mimic(const std::string& text) + { + static_assert(std::is_base_of::value, + "throw_or_mimic only works on std::exception and " + "deriving classes"); #ifndef CXXOPTS_NO_EXCEPTIONS - // If CXXOPTS_NO_EXCEPTIONS is not defined, just throw - throw T{text}; + // If CXXOPTS_NO_EXCEPTIONS is not defined, just throw + throw T{ text }; #else - // Otherwise manually instantiate the exception, print what() to stderr, - // and abort - T exception{text}; - std::cerr << exception.what() << std::endl; - std::cerr << "Aborting (exceptions disabled)..." << std::endl; - std::abort(); + // Otherwise manually instantiate the exception, print what() to stderr, + // and abort + T exception{ text }; + std::cerr << exception.what() << std::endl; + std::cerr << "Aborting (exceptions disabled)..." << std::endl; + std::abort(); #endif - } - - namespace values - { - namespace - { - std::basic_regex integer_pattern - ("(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)"); - std::basic_regex truthy_pattern - ("(t|T)(rue)?|1"); - std::basic_regex falsy_pattern - ("(f|F)(alse)?|0"); } - namespace detail + namespace values { - template - struct SignedCheck; + namespace + { + std::basic_regex integer_pattern + ("(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)"); + std::basic_regex truthy_pattern + ("(t|T)(rue)?|1"); + std::basic_regex falsy_pattern + ("(f|F)(alse)?|0"); + } - template - struct SignedCheck - { - template - void - operator()(bool negative, U u, const std::string& text) + namespace detail { - if (negative) - { - if (u > static_cast((std::numeric_limits::min)())) + template + struct SignedCheck; + + template + struct SignedCheck { - throw_or_mimic(text); - } - } - else - { - if (u > static_cast((std::numeric_limits::max)())) + template + void + operator()(bool negative, U u, const std::string& text) + { + if (negative) + { + if (u > static_cast((std::numeric_limits::min)())) + { + throw_or_mimic(text); + } + } + else + { + if (u > static_cast((std::numeric_limits::max)())) + { + throw_or_mimic(text); + } + } + } + }; + + template + struct SignedCheck + { + template + void + operator()(bool, U, const std::string&) {} + }; + + template + void + check_signed_range(bool negative, U value, const std::string& text) { - throw_or_mimic(text); + SignedCheck::is_signed>()(negative, value, text); } - } } - }; - template - struct SignedCheck - { - template + template + R + checked_negate(T&& t, const std::string&, std::true_type) + { + // if we got to here, then `t` is a positive number that fits into + // `R`. So to avoid MSVC C4146, we first cast it to `R`. + // See https://github.com/jarro2783/cxxopts/issues/62 for more details. + return static_cast(-static_cast(t - 1) - 1); + } + + template + T + checked_negate(T&& t, const std::string& text, std::false_type) + { + throw_or_mimic(text); + return t; + } + + template void - operator()(bool, U, const std::string&) {} - }; - - template - void - check_signed_range(bool negative, U value, const std::string& text) - { - SignedCheck::is_signed>()(negative, value, text); - } - } + integer_parser(const std::string& text, T& value) + { + std::smatch match; + std::regex_match(text, match, integer_pattern); - template - R - checked_negate(T&& t, const std::string&, std::true_type) - { - // if we got to here, then `t` is a positive number that fits into - // `R`. So to avoid MSVC C4146, we first cast it to `R`. - // See https://github.com/jarro2783/cxxopts/issues/62 for more details. - return static_cast(-static_cast(t-1)-1); - } + if (match.length() == 0) + { + throw_or_mimic(text); + } - template - T - checked_negate(T&& t, const std::string& text, std::false_type) - { - throw_or_mimic(text); - return t; - } + if (match.length(4) > 0) + { + value = 0; + return; + } - template - void - integer_parser(const std::string& text, T& value) - { - std::smatch match; - std::regex_match(text, match, integer_pattern); + using US = typename std::make_unsigned::type; - if (match.length() == 0) - { - throw_or_mimic(text); - } + constexpr bool is_signed = std::numeric_limits::is_signed; + const bool negative = match.length(1) > 0; + const uint8_t base = match.length(2) > 0 ? 16 : 10; - if (match.length(4) > 0) - { - value = 0; - return; - } + auto value_match = match[3]; - using US = typename std::make_unsigned::type; + US result = 0; - constexpr bool is_signed = std::numeric_limits::is_signed; - const bool negative = match.length(1) > 0; - const uint8_t base = match.length(2) > 0 ? 16 : 10; + for (auto iter = value_match.first; iter != value_match.second; ++iter) + { + US digit = 0; + + if (*iter >= '0' && *iter <= '9') + { + digit = static_cast(*iter - '0'); + } + else if (base == 16 && *iter >= 'a' && *iter <= 'f') + { + digit = static_cast(*iter - 'a' + 10); + } + else if (base == 16 && *iter >= 'A' && *iter <= 'F') + { + digit = static_cast(*iter - 'A' + 10); + } + else + { + throw_or_mimic(text); + } + + const US next = static_cast(result * base + digit); + if (result > next) + { + throw_or_mimic(text); + } + + result = next; + } - auto value_match = match[3]; + detail::check_signed_range(negative, result, text); - US result = 0; + if (negative) + { + value = checked_negate(result, + text, + std::integral_constant()); + } + else + { + value = static_cast(result); + } + } - for (auto iter = value_match.first; iter != value_match.second; ++iter) - { - US digit = 0; + template + void stringstream_parser(const std::string& text, T& value) + { + std::stringstream in(text); + in >> value; + if (!in) { + throw_or_mimic(text); + } + } - if (*iter >= '0' && *iter <= '9') + inline + void + parse_value(const std::string& text, uint8_t& value) { - digit = static_cast(*iter - '0'); + integer_parser(text, value); } - else if (base == 16 && *iter >= 'a' && *iter <= 'f') + + inline + void + parse_value(const std::string& text, int8_t& value) { - digit = static_cast(*iter - 'a' + 10); + integer_parser(text, value); } - else if (base == 16 && *iter >= 'A' && *iter <= 'F') + + inline + void + parse_value(const std::string& text, uint16_t& value) { - digit = static_cast(*iter - 'A' + 10); + integer_parser(text, value); } - else + + inline + void + parse_value(const std::string& text, int16_t& value) { - throw_or_mimic(text); + integer_parser(text, value); } - const US next = static_cast(result * base + digit); - if (result > next) + inline + void + parse_value(const std::string& text, uint32_t& value) { - throw_or_mimic(text); + integer_parser(text, value); } - result = next; - } + inline + void + parse_value(const std::string& text, int32_t& value) + { + integer_parser(text, value); + } - detail::check_signed_range(negative, result, text); + inline + void + parse_value(const std::string& text, uint64_t& value) + { + integer_parser(text, value); + } - if (negative) - { - value = checked_negate(result, - text, - std::integral_constant()); - } - else - { - value = static_cast(result); - } - } + inline + void + parse_value(const std::string& text, int64_t& value) + { + integer_parser(text, value); + } - template - void stringstream_parser(const std::string& text, T& value) - { - std::stringstream in(text); - in >> value; - if (!in) { - throw_or_mimic(text); - } - } + inline + void + parse_value(const std::string& text, bool& value) + { + std::smatch result; + std::regex_match(text, result, truthy_pattern); - inline - void - parse_value(const std::string& text, uint8_t& value) - { - integer_parser(text, value); - } + if (!result.empty()) + { + value = true; + return; + } - inline - void - parse_value(const std::string& text, int8_t& value) - { - integer_parser(text, value); - } + std::regex_match(text, result, falsy_pattern); + if (!result.empty()) + { + value = false; + return; + } - inline - void - parse_value(const std::string& text, uint16_t& value) - { - integer_parser(text, value); - } + throw_or_mimic(text); + } - inline - void - parse_value(const std::string& text, int16_t& value) - { - integer_parser(text, value); - } + inline + void + parse_value(const std::string& text, std::string& value) + { + value = text; + } - inline - void - parse_value(const std::string& text, uint32_t& value) - { - integer_parser(text, value); - } + // The fallback parser. It uses the stringstream parser to parse all types + // that have not been overloaded explicitly. It has to be placed in the + // source code before all other more specialized templates. + template + void + parse_value(const std::string& text, T& value) { + stringstream_parser(text, value); + } - inline - void - parse_value(const std::string& text, int32_t& value) - { - integer_parser(text, value); - } + template + void + parse_value(const std::string& text, std::vector& value) + { + std::stringstream in(text); + std::string token; + while (in.eof() == false && std::getline(in, token, CXXOPTS_VECTOR_DELIMITER)) { + T v; + parse_value(token, v); + value.emplace_back(std::move(v)); + } + } - inline - void - parse_value(const std::string& text, uint64_t& value) - { - integer_parser(text, value); - } +#ifdef CXXOPTS_HAS_OPTIONAL + template + void + parse_value(const std::string& text, std::optional& value) + { + T result; + parse_value(text, result); + value = std::move(result); + } +#endif - inline - void - parse_value(const std::string& text, int64_t& value) - { - integer_parser(text, value); - } + inline + void parse_value(const std::string& text, char& c) + { + if (text.length() != 1) + { + throw_or_mimic(text); + } - inline - void - parse_value(const std::string& text, bool& value) - { - std::smatch result; - std::regex_match(text, result, truthy_pattern); - - if (!result.empty()) - { - value = true; - return; - } - - std::regex_match(text, result, falsy_pattern); - if (!result.empty()) - { - value = false; - return; - } - - throw_or_mimic(text); - } + c = text[0]; + } - inline - void - parse_value(const std::string& text, std::string& value) - { - value = text; - } + template + struct type_is_container + { + static constexpr bool value = false; + }; - // The fallback parser. It uses the stringstream parser to parse all types - // that have not been overloaded explicitly. It has to be placed in the - // source code before all other more specialized templates. - template - void - parse_value(const std::string& text, T& value) { - stringstream_parser(text, value); + template + struct type_is_container> + { + static constexpr bool value = true; + }; + + template + class abstract_value : public Value + { + using Self = abstract_value; + + public: + abstract_value() + : m_result(std::make_shared()) + , m_store(m_result.get()) + { + } + + abstract_value(T* t) + : m_store(t) + { + } + + virtual ~abstract_value() = default; + + abstract_value(const abstract_value& rhs) + { + if (rhs.m_result) + { + m_result = std::make_shared(); + m_store = m_result.get(); + } + else + { + m_store = rhs.m_store; + } + + m_default = rhs.m_default; + m_implicit = rhs.m_implicit; + m_default_value = rhs.m_default_value; + m_implicit_value = rhs.m_implicit_value; + } + + void + parse(const std::string& text) const + { + parse_value(text, *m_store); + } + + bool + is_container() const + { + return type_is_container::value; + } + + void + parse() const + { + parse_value(m_default_value, *m_store); + } + + bool + has_default() const + { + return m_default; + } + + bool + has_implicit() const + { + return m_implicit; + } + + std::shared_ptr + default_value(const std::string& value) + { + m_default = true; + m_default_value = value; + return shared_from_this(); + } + + std::shared_ptr + implicit_value(const std::string& value) + { + m_implicit = true; + m_implicit_value = value; + return shared_from_this(); + } + + std::shared_ptr + no_implicit_value() + { + m_implicit = false; + return shared_from_this(); + } + + std::string + get_default_value() const + { + return m_default_value; + } + + std::string + get_implicit_value() const + { + return m_implicit_value; + } + + bool + is_boolean() const + { + return std::is_same::value; + } + + const T& + get() const + { + if (m_store == nullptr) + { + return *m_result; + } + else + { + return *m_store; + } + } + + protected: + std::shared_ptr m_result; + T* m_store; + + bool m_default = false; + bool m_implicit = false; + + std::string m_default_value; + std::string m_implicit_value; + }; + + template + class standard_value : public abstract_value + { + public: + using abstract_value::abstract_value; + + std::shared_ptr + clone() const + { + return std::make_shared>(*this); + } + }; + + template <> + class standard_value : public abstract_value + { + public: + ~standard_value() = default; + + standard_value() + { + set_default_and_implicit(); + } + + standard_value(bool* b) + : abstract_value(b) + { + set_default_and_implicit(); + } + + std::shared_ptr + clone() const + { + return std::make_shared>(*this); + } + + private: + + void + set_default_and_implicit() + { + m_default = true; + m_default_value = "false"; + m_implicit = true; + m_implicit_value = "true"; + } + }; } template - void - parse_value(const std::string& text, std::vector& value) - { - std::stringstream in(text); - std::string token; - while(in.eof() == false && std::getline(in, token, CXXOPTS_VECTOR_DELIMITER)) { - T v; - parse_value(token, v); - value.emplace_back(std::move(v)); - } + std::shared_ptr + value() + { + return std::make_shared>(); } -#ifdef CXXOPTS_HAS_OPTIONAL template - void - parse_value(const std::string& text, std::optional& value) + std::shared_ptr + value(T& t) { - T result; - parse_value(text, result); - value = std::move(result); + return std::make_shared>(&t); } -#endif - inline - void parse_value(const std::string& text, char& c) + class OptionAdder; + + class OptionDetails { - if (text.length() != 1) - { - throw_or_mimic(text); - } + public: + OptionDetails + ( + const std::string& short_, + const std::string& long_, + const String& desc, + std::shared_ptr val + ) + : m_short(short_) + , m_long(long_) + , m_desc(desc) + , m_value(val) + , m_count(0) + { + } - c = text[0]; - } + OptionDetails(const OptionDetails& rhs) + : m_desc(rhs.m_desc) + , m_count(rhs.m_count) + { + m_value = rhs.m_value->clone(); + } - template - struct type_is_container - { - static constexpr bool value = false; + OptionDetails(OptionDetails&& rhs) = default; + + const String& + description() const + { + return m_desc; + } + + const Value& value() const { + return *m_value; + } + + std::shared_ptr + make_storage() const + { + return m_value->clone(); + } + + const std::string& + short_name() const + { + return m_short; + } + + const std::string& + long_name() const + { + return m_long; + } + + private: + std::string m_short; + std::string m_long; + String m_desc; + std::shared_ptr m_value; + int m_count; }; - template - struct type_is_container> + struct HelpOptionDetails + { + std::string s; + std::string l; + String desc; + bool has_default; + std::string default_value; + bool has_implicit; + std::string implicit_value; + std::string arg_help; + bool is_container; + bool is_boolean; + }; + + struct HelpGroupDetails { - static constexpr bool value = true; + std::string name; + std::string description; + std::vector options; }; - template - class abstract_value : public Value + class OptionValue { - using Self = abstract_value; + public: + void + parse + ( + std::shared_ptr details, + const std::string& text + ) + { + ensure_value(details); + ++m_count; + m_value->parse(text); + } - public: - abstract_value() - : m_result(std::make_shared()) - , m_store(m_result.get()) - { - } + void + parse_default(std::shared_ptr details) + { + ensure_value(details); + m_default = true; + m_value->parse(); + } - abstract_value(T* t) - : m_store(t) - { - } + size_t + count() const noexcept + { + return m_count; + } - virtual ~abstract_value() = default; + // TODO: maybe default options should count towards the number of arguments + bool + has_default() const noexcept + { + return m_default; + } - abstract_value(const abstract_value& rhs) - { - if (rhs.m_result) + template + const T& + as() const { - m_result = std::make_shared(); - m_store = m_result.get(); + if (m_value == nullptr) { + throw_or_mimic("No value"); + } + +#ifdef CXXOPTS_NO_RTTI + return static_cast&>(*m_value).get(); +#else + return dynamic_cast&>(*m_value).get(); +#endif } - else + + private: + void + ensure_value(std::shared_ptr details) { - m_store = rhs.m_store; - } - - m_default = rhs.m_default; - m_implicit = rhs.m_implicit; - m_default_value = rhs.m_default_value; - m_implicit_value = rhs.m_implicit_value; - } - - void - parse(const std::string& text) const - { - parse_value(text, *m_store); - } - - bool - is_container() const - { - return type_is_container::value; - } - - void - parse() const - { - parse_value(m_default_value, *m_store); - } - - bool - has_default() const - { - return m_default; - } - - bool - has_implicit() const - { - return m_implicit; - } - - std::shared_ptr - default_value(const std::string& value) - { - m_default = true; - m_default_value = value; - return shared_from_this(); - } - - std::shared_ptr - implicit_value(const std::string& value) - { - m_implicit = true; - m_implicit_value = value; - return shared_from_this(); - } - - std::shared_ptr - no_implicit_value() - { - m_implicit = false; - return shared_from_this(); - } - - std::string - get_default_value() const - { - return m_default_value; - } - - std::string - get_implicit_value() const - { - return m_implicit_value; - } - - bool - is_boolean() const - { - return std::is_same::value; - } - - const T& - get() const - { - if (m_store == nullptr) - { - return *m_result; + if (m_value == nullptr) + { + m_value = details->make_storage(); + } } - else + + std::shared_ptr m_value; + size_t m_count = 0; + bool m_default = false; + }; + + class KeyValue + { + public: + KeyValue(std::string key_, std::string value_) + : m_key(std::move(key_)) + , m_value(std::move(value_)) { - return *m_store; } - } - protected: - std::shared_ptr m_result; - T* m_store; + const + std::string& + key() const + { + return m_key; + } - bool m_default = false; - bool m_implicit = false; + const + std::string& + value() const + { + return m_value; + } - std::string m_default_value; - std::string m_implicit_value; + template + T + as() const + { + T result; + values::parse_value(m_value, result); + return result; + } + + private: + std::string m_key; + std::string m_value; }; - template - class standard_value : public abstract_value + class ParseResult { - public: - using abstract_value::abstract_value; + public: - std::shared_ptr - clone() const - { - return std::make_shared>(*this); - } - }; + ParseResult( + const std::shared_ptr< + std::unordered_map> + >, + std::vector, + bool allow_unrecognised, + int&, char**&); - template <> - class standard_value : public abstract_value - { - public: - ~standard_value() = default; - - standard_value() - { - set_default_and_implicit(); - } - - standard_value(bool* b) - : abstract_value(b) - { - set_default_and_implicit(); - } - - std::shared_ptr - clone() const - { - return std::make_shared>(*this); - } - - private: - - void - set_default_and_implicit() - { - m_default = true; - m_default_value = "false"; - m_implicit = true; - m_implicit_value = "true"; - } - }; - } - - template - std::shared_ptr - value() - { - return std::make_shared>(); - } - - template - std::shared_ptr - value(T& t) - { - return std::make_shared>(&t); - } - - class OptionAdder; - - class OptionDetails - { - public: - OptionDetails - ( - const std::string& short_, - const std::string& long_, - const String& desc, - std::shared_ptr val - ) - : m_short(short_) - , m_long(long_) - , m_desc(desc) - , m_value(val) - , m_count(0) - { - } + size_t + count(const std::string& o) const + { + auto iter = m_options->find(o); + if (iter == m_options->end()) + { + return 0; + } - OptionDetails(const OptionDetails& rhs) - : m_desc(rhs.m_desc) - , m_count(rhs.m_count) - { - m_value = rhs.m_value->clone(); - } + auto riter = m_results.find(iter->second); - OptionDetails(OptionDetails&& rhs) = default; + return riter->second.count(); + } - const String& - description() const - { - return m_desc; - } + const OptionValue& + operator[](const std::string& option) const + { + auto iter = m_options->find(option); - const Value& value() const { - return *m_value; - } + if (iter == m_options->end()) + { + throw_or_mimic(option); + } - std::shared_ptr - make_storage() const - { - return m_value->clone(); - } + auto riter = m_results.find(iter->second); - const std::string& - short_name() const - { - return m_short; - } + return riter->second; + } - const std::string& - long_name() const - { - return m_long; - } + const std::vector& + arguments() const + { + return m_sequential; + } private: - std::string m_short; - std::string m_long; - String m_desc; - std::shared_ptr m_value; - int m_count; - }; - - struct HelpOptionDetails - { - std::string s; - std::string l; - String desc; - bool has_default; - std::string default_value; - bool has_implicit; - std::string implicit_value; - std::string arg_help; - bool is_container; - bool is_boolean; - }; - - struct HelpGroupDetails - { - std::string name; - std::string description; - std::vector options; - }; - - class OptionValue - { - public: - void - parse - ( - std::shared_ptr details, - const std::string& text - ) - { - ensure_value(details); - ++m_count; - m_value->parse(text); - } - void - parse_default(std::shared_ptr details) - { - ensure_value(details); - m_default = true; - m_value->parse(); - } + void + parse(int& argc, char**& argv); - size_t - count() const noexcept - { - return m_count; - } + void + add_to_option(const std::string& option, const std::string& arg); - // TODO: maybe default options should count towards the number of arguments - bool - has_default() const noexcept - { - return m_default; - } + bool + consume_positional(std::string a); - template - const T& - as() const - { - if (m_value == nullptr) { - throw_or_mimic("No value"); - } + void + parse_option + ( + std::shared_ptr value, + const std::string& name, + const std::string& arg = "" + ); -#ifdef CXXOPTS_NO_RTTI - return static_cast&>(*m_value).get(); -#else - return dynamic_cast&>(*m_value).get(); -#endif - } + void + parse_default(std::shared_ptr details); - private: - void - ensure_value(std::shared_ptr details) - { - if (m_value == nullptr) - { - m_value = details->make_storage(); - } - } + void + checked_parse_arg + ( + int argc, + char* argv[], + int& current, + std::shared_ptr value, + const std::string& name + ); + + const std::shared_ptr< + std::unordered_map> + > m_options; + std::vector m_positional; + std::vector::iterator m_next_positional; + std::unordered_set m_positional_set; + std::unordered_map, OptionValue> m_results; + + bool m_allow_unrecognised; + + std::vector m_sequential; + }; + + struct Option + { + Option + ( + const std::string& opts, + const std::string& desc, + const std::shared_ptr& value = ::cxxopts::value(), + const std::string& arg_help = "" + ) + : opts_(opts) + , desc_(desc) + , value_(value) + , arg_help_(arg_help) + { + } - std::shared_ptr m_value; - size_t m_count = 0; - bool m_default = false; - }; + std::string opts_; + std::string desc_; + std::shared_ptr value_; + std::string arg_help_; + }; - class KeyValue - { - public: - KeyValue(std::string key_, std::string value_) - : m_key(std::move(key_)) - , m_value(std::move(value_)) + class Options { - } + typedef std::unordered_map> + OptionMap; + public: - const - std::string& - key() const - { - return m_key; - } + Options(std::string program, std::string help_string = "") + : m_program(std::move(program)) + , m_help_string(toLocalString(std::move(help_string))) + , m_custom_help("[OPTION...]") + , m_positional_help("positional parameters") + , m_show_positional(false) + , m_allow_unrecognised(false) + , m_options(std::make_shared()) + , m_next_positional(m_positional.end()) + { + } - const - std::string& - value() const - { - return m_value; - } + Options& + positional_help(std::string help_text) + { + m_positional_help = std::move(help_text); + return *this; + } - template - T - as() const - { - T result; - values::parse_value(m_value, result); - return result; - } + Options& + custom_help(std::string help_text) + { + m_custom_help = std::move(help_text); + return *this; + } - private: - std::string m_key; - std::string m_value; - }; + Options& + show_positional_help() + { + m_show_positional = true; + return *this; + } - class ParseResult - { - public: + Options& + allow_unrecognised_options() + { + m_allow_unrecognised = true; + return *this; + } - ParseResult( - const std::shared_ptr< - std::unordered_map> - >, - std::vector, - bool allow_unrecognised, - int&, char**&); + ParseResult + parse(int& argc, char**& argv); - size_t - count(const std::string& o) const - { - auto iter = m_options->find(o); - if (iter == m_options->end()) - { - return 0; - } + OptionAdder + add_options(std::string group = ""); - auto riter = m_results.find(iter->second); + void + add_options + ( + const std::string& group, + std::initializer_list