diff --git a/README.md b/README.md index c792a1d..4c38196 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,28 @@ # Scaffolder Generate scaffold from STL file with implicit function (Schwarz P/ Gyroid). ``` -Usage: Scaffolder_2 [options] -Options: - -q, --quiet Disable verbose output - -f, --format Output format (OFF,PLY,STL,OBJ) [default: ply] - -i, --input Input file (STL) (Required) - -o, --output Output filename without extension [default: out] - -g, --grid Grid size [default: 100] - --thickness Thickness [default: 1.0] - --border_offset default:2 - --coff default:4*PI - --minimum_diameter used for removing small orphaned (between 0-1) [default: 0.25] +Scaffolder - generate 3D scaffold from STL file +Usage: + Scaffolder [OPTION...] [option args] + + -h, --help Print help + -q, --quiet Disable verbose output + -f, --format arg Output format (OFF,PLY,STL,OBJ) [default: + ply] + -i, --input FILE Input file (STL) + -o, --output FILENAME Output filename without extension [default: + 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] + -g, --grid_size INT Grid size [default: 100] + --grid_offset INT [default:2] + --dirty Disable autoclean + --minimum_diameter DOUBLE + used for removing small orphaned (between + 0-1) [default: 0.25] ``` ## Dependencies @@ -29,5 +40,5 @@ Options: ## Reference - [dualmc](https://github.com/dominikwodniok/dualmc) -- [argparse](https://github.com/jamolnng/argparse) +- [cxxopts](https://github.com/jarro2783/cxxopts) - [Minimal surface Blog](https://minimalsurfaces.blog/) diff --git a/Scaffolder_2.cpp b/Scaffolder_2.cpp index 9c08a84..09cb00d 100644 --- a/Scaffolder_2.cpp +++ b/Scaffolder_2.cpp @@ -4,73 +4,152 @@ #include #include #include "Implicit_function.h" -#include "argparse.h" +#include "cxxopts.hpp" #include "dualmc/dualmc.h" #include "Mesh.h" -int main(int argc, const char* argv[]) -{ - ArgumentParser parser("Scaffolder v1.0 - generate 3D scaffold from STL file"); - parser.add_argument("-q", "--quiet", "Disable verbose output", false); - parser.add_argument("-f", "--format", "Output format (OFF,PLY,STL,OBJ) [default: ply]", false); - parser.add_argument("-i", "--input", "Input file (STL)", true); - parser.add_argument("-o", "--output", "Output filename without extension [default: out]", false); - parser.add_argument("-g", "--grid", "Grid size [default: 100]", false); - parser.add_argument("--thickness", "Thickness [default: 1.0]", false); - parser.add_argument("--border_offset", "default:2", false); - parser.add_argument("--coff", "default:4*PI", false); - parser.add_argument("--minimum_diameter", "used for removing small orphaned (between 0-1) [default: 0.25]", false); - parser.add_argument("--surface", "default: schwarzp", false); - try { - parser.parse(argc, argv); +#define VERSION "v1.1" + +typedef struct index_type { + size_t x; size_t y; size_t z; +} index_type; +typedef std::map Queue_t; + +inline size_t indexFromIJK(size_t i, size_t j, size_t k, size_t grid_size) { + return i + grid_size * (j + grid_size * k); +} + +inline void indexToIJK(size_t index, size_t grid_size, index_type& r) { + r.z = index / (grid_size*grid_size); + index -= r.z * grid_size * grid_size; + r.y = index / grid_size; + r.x = index % grid_size; +} + +inline bool MarkAndSweepNeighbor(Eigen::VectorXd& W, index_type& index, Queue_t& queue, size_t grid_size, double value = 0.0, bool findAbove = false) { + bool isBorder = false; + for (int8_t di = -1; di <= 1; di++) { + for (int8_t dj = -1; dj <= 1; dj++) { + for (int8_t dk = -1; dk <= 1; dk++) { + if (di == 0 && dj == 0 && dk == 0) continue; + const size_t id = indexFromIJK(index.x + di, index.y + dj, index.z + dk, grid_size); + //std::cout << value << " " << W(id) << std::endl; + if ((findAbove && W(id) >= value - eps2) || (!findAbove && W(id) <= value + eps2)) { + isBorder = true; + break; + } + } + if (isBorder) break; + } + if (isBorder) break; } - catch (const ArgumentParser::ArgumentNotFound & ex) { - std::cout << ex.what() << std::endl; - return 0; + if (isBorder) { + for (int8_t di = -1; di <= 1; di++) { + for (int8_t dj = -1; dj <= 1; dj++) { + for (int8_t dk = -1; dk <= 1; dk++) { + if (di == 0 && dj == 0 && dk == 0) continue; + const size_t id = indexFromIJK(index.x + di, index.y + dj, index.z + dk, grid_size); + if (W(id) >= 0.5 && W(id) < 1.1) { + queue.insert({ id, true }); + } + } + } + } } - if (parser.is_help()) return 0; + return isBorder; +} + +int main(int argc, char* argv[]) +{ // Define default parameters bool verbose = true; - bool use_smooth_mesh = false; - uint16_t border_offset = 2; + bool dirty = false; + uint16_t grid_offset = 2; + uint16_t shell = 0; double thickness = 0.5; size_t grid_size = 100; double coff = pi; double minimum_diameter = 0.25; - Eigen::IOFormat CleanFmt(4, 0, ", ", "\n", "[", "]"); + Eigen::IOFormat CleanFmt(4, Eigen::DontAlignCols, ", ", "\n", "[", "]"); + Eigen::IOFormat CSVFmt(-1, Eigen::DontAlignCols, ", ", ", "); // file parameters std::string filename = "out"; std::string format = "ply"; - std::string input_file = parser.get("input"); std::string surface = "schwarzp"; + std::string input_file = ""; - // get optional parameters - if (parser.exists("quiet")) verbose = parser.get("quiet"); - if (parser.exists("file")) filename = parser.get("filename"); - if (parser.exists("format")) format = parser.get("format"); - if (parser.exists("thickness")) thickness = parser.get("thickness"); - if (parser.exists("grid_size")) grid_size = parser.get("grid_size"); - if (parser.exists("border_offset")) border_offset = parser.get("border_offset"); - if (parser.exists("coff")) coff = parser.get("coff"); - if (parser.exists("minimum_diameter")) minimum_diameter = parser.get("minimum_diameter"); - if (parser.exists("surface")) surface = parser.get("surface"); + try { + cxxopts::Options options("Scaffolder", "Scaffolder - generate 3D scaffold from STL file"); + options.positional_help("[option args]").show_positional_help(); + options.add_options() + ("h,help", "Print help") + ("q,quiet", "Disable verbose output") + ("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") + ("c,coff", "default:4*PI", cxxopts::value(), "DOUBLE") + ("s,shell", "[default:0]", cxxopts::value(), "INT") + ("n,surface", "rectlinear, schwarzp, schwarzd, gyroid, lidinoid, schoen_iwp, neovius, pwhybrid [default: schwarzp]", cxxopts::value(), "NAME") + ("t,thickness", "Thickness [default: 1.0]", cxxopts::value(), "DOUBLE") + ("g,grid_size", "Grid size [default: 100]", cxxopts::value(), "INT") + ("grid_offset", "[default:2]", cxxopts::value(), "INT") + ("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" }); + 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; + } + // get optional parameters + if (result.count("quiet")) verbose = !result["quiet"].as(); + if (result.count("dirty")) dirty = result["dirty"].as(); + if (result.count("output")) filename = result["output"].as(); + if (result.count("format")) format = result["format"].as(); + if (result.count("thickness")) thickness = result["thickness"].as(); + if (result.count("grid_size")) grid_size = result["grid_size"].as(); + if (result.count("grid_offset")) grid_offset = result["grid_offset"].as(); + if (result.count("coff")) coff = result["coff"].as(); + if (result.count("minimum_diameter")) minimum_diameter = result["minimum_diameter"].as(); + if (result.count("surface")) surface = result["surface"].as(); + if (result.count("shell")) shell = result["shell"].as(); + + to_lower(surface); + to_lower(format); + + if (format != "ply" && format != "obj" && format != "stl" && format != "off") { + std::cout << "Invalid format: " << format << std::endl; + return 1; + } - // lowercase format - to_lower(format); - if (format != "ply" && format != "obj" && format != "stl" && format != "off") { - std::cout << "Invalid format: " << format << std::endl; - return -1; + if (surface == "rectlinear") { + thickness = 0; + } + } + catch (const cxxopts::OptionException & ex) { + std::cout << "Error parsing options: " << ex.what() << std::endl; + return 1; } // Print parameters if (verbose) { - std::cout << "[Scaffolder v1.0]" << std::endl + std::cout << "[Scaffolder " << VERSION << "]" << std::endl << "-- Input file: " << input_file << std::endl << "-- Output file: " << filename << '.' << format << std::endl + << "-- Surface: " << surface << std::endl << "-- Coff: " << coff << std::endl + << "-- shell: " << shell << std::endl << "-- Thickness: " << thickness << std::endl << "-- Grid size: " << grid_size << std::endl - << "-- Border Offset: " << border_offset << std::endl + << "-- Grid offset: " << grid_offset << std::endl + << "-- Autoclean: " << (dirty ? "False" : "True") << std::endl << "-- Minimum diameter: " << 100 * minimum_diameter << "%" << std::endl; } @@ -80,7 +159,7 @@ int main(int argc, const char* argv[]) Eigen::MatrixXd V, Fxyz; Eigen::MatrixXi F; Eigen::RowVector3d V1min1, delta; - { + try { // Read FROM STL Eigen::MatrixXd V1; Eigen::MatrixXi F1; @@ -94,15 +173,12 @@ int main(int argc, const char* argv[]) const Eigen::RowVector3d L = V1max - V1min; delta = L / grid_size; // Create border offset from the original box - V1min1 = V1min - border_offset * delta; - grid_size += 2 * (size_t)(border_offset); - // Create iso cuboid condition with boundary box - //Iso_cuboid_condition condition(V1min(0), V1min(1), V1min(2), L(0), L(1), L(2)); - Function_3& isosurface = get_surface_function(surface); - Implicit_function fn(isosurface, coff, thickness); - 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 << "[Generating grid] " << grid_size * grid_size * grid_size << " (" << grid_size << '*' << grid_size << '*' << grid_size << ") "; + V1min1 = V1min - grid_offset * delta; + grid_size += 2 * (size_t)(grid_offset); + Implicit_function fn(isosurface(surface, coff), coff, thickness); + 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 << "[Generating grid] "; Eigen::MatrixXd GV(grid_size * grid_size * grid_size, 3); for (size_t k = 0; k < grid_size; k++) { const double z = V1min1(2) + k * delta(2); @@ -115,9 +191,12 @@ int main(int argc, const char* argv[]) } } } - if (verbose) std::cout << "OK" << std::endl; + if (verbose) + std::cout + << "OK" << std::endl + << "-- Grid size: " << grid_size * grid_size * grid_size << " (" << grid_size << 'x' << grid_size << 'x' << grid_size << ") " << std::endl; - if (verbose) std::cout << "[Calculating Wind number] "; + if (verbose) std::cout << "[Calculating Winding number] "; Eigen::VectorXd W; { igl::FastWindingNumberBVH bvh; @@ -125,8 +204,38 @@ int main(int argc, const char* argv[]) igl::fast_winding_number(bvh, 2, GV, W); } if (verbose) std::cout << "OK" << std::endl; - //if (verbose) std::cout << "-- Info: W>=0.5 " << (W.array() >= 0.5).count() << std::endl; + if (shell > 0) { + if (verbose) std::cout << "[Generate Shell] "; + Queue_t queue; + uint16_t shell_index = shell + 1; + for (size_t index = 0; index < grid_size * grid_size * grid_size; index++) { + if (W(index) >= 0.8) { + index_type id; + indexToIJK(index, grid_size, id); + if (MarkAndSweepNeighbor(W, id, queue, grid_size, 0.5)) { + W(index) = shell_index; + } + } + } + //if (verbose) std::cout << "-- [" << shell_index << "] " << queue.size() << std::endl; + shell_index--; + for (; shell_index > 1; shell_index--) { + Queue_t q(queue); + queue.clear(); + //if (verbose) std::cout << "-- [" << shell_index << "] " << q.size() << std::endl; + for (Queue_t::iterator it = q.begin(); it != q.end(); ++it) { + // every neighbor + index_type id; + indexToIJK(it->first, grid_size, id); + if (MarkAndSweepNeighbor(W, id, queue, grid_size, shell_index, true)) { + W(it->first) = shell_index; + } + } + } + if (verbose) std::cout << "OK" << std::endl; + } + if (verbose) std::cout << "[Generating isosurface Fxyz] "; Fxyz.resize(grid_size * grid_size * grid_size, 1); for (size_t k = 0; k < grid_size; k++) { @@ -136,15 +245,27 @@ int main(int argc, const char* argv[]) for (size_t i = 0; i < grid_size; i++) { const double x = V1min1(0) + i * delta(0); const size_t index = i + grid_size * (j + grid_size * k); - Fxyz(index) = W(index) >= 0.5 ? fn(x, y, z) : 1.0; + const double w = W(index); + // Winding field + // Fxyz(index) = W(index); + if (w < 0.8) // Outside + Fxyz(index) = 1.0; + else if (w >= 1.1) // Shell + Fxyz(index) = -1; + else // Inside + Fxyz(index) = fn(x, y, z); } } } if (verbose) std::cout << "OK" << std::endl; } + catch (const std::exception& ex) { + std::cout << "Exception: " << ex.what() << std::endl; + return 1; + } - if (verbose) std::cout << "[Marching Cube] "; { + if (verbose) std::cout << "[Marching Cube] "; dualmc::DualMC builder; std::vector mc_vertices; std::vector mc_quads; @@ -169,43 +290,47 @@ int main(int argc, const char* argv[]) fi->V(1) = vp[mc_quads[i].i3]; fi->V(2) = vp[mc_quads[i].i0]; } - vcg::tri::Clean::RemoveDuplicateFace(mesh); - vcg::tri::Clean::RemoveDuplicateVertex(mesh); - vcg::tri::UpdateTopology::FaceFace(mesh); + if (!dirty) { + vcg::tri::Clean::RemoveDuplicateFace(mesh); + vcg::tri::Clean::RemoveDuplicateVertex(mesh); + vcg::tri::Clean::RemoveUnreferencedVertex(mesh); + 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; } - if (verbose) std::cout << "OK" << std::endl; } - // Stage 2: Clearning + // Stage 2: Cleaning { - if (verbose) std::cout << "[libVCG Cleaning] "; - vcg::tri::Clean::RemoveZeroAreaFace(mesh); - vcg::tri::Clean::RemoveUnreferencedVertex(mesh); - vcg::tri::UpdateTopology::FaceFace(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); - if (mesh.fn > 0) { - vcg::tri::UpdateNormal::PerFaceNormalized(mesh); - vcg::tri::UpdateNormal::PerVertexAngleWeighted(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; + if (!dirty) { + if (verbose) std::cout << "[libVCG Cleaning] "; + 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); + if (mesh.fn > 0) { + vcg::tri::UpdateNormal::PerFaceNormalized(mesh); + vcg::tri::UpdateNormal::PerVertexAngleWeighted(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; + } } if (verbose) std::cout << "[Writing file] "; filename.append("."); filename.append(format); if (format == "ply") { - vcg::tri::io::ExporterPLY::Save(mesh, filename.c_str()); + vcg::tri::io::ExporterPLY::Save(mesh, filename.c_str(), false); } else if (format == "obj") { vcg::tri::io::ExporterOBJ::Save(mesh, filename.c_str(), 0); @@ -218,6 +343,5 @@ int main(int argc, const char* argv[]) } if (verbose) std::cout << "OK" << std::endl << "[Finished]" << std::endl; } - return 0; } \ No newline at end of file diff --git a/argparse.h b/argparse.h deleted file mode 100644 index 3e00143..0000000 --- a/argparse.h +++ /dev/null @@ -1,293 +0,0 @@ -/** - * argparse.h is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License v3 as published by - * the Free Software Foundation. - * - * argparse.h is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with argparse.h. If not, see . - * - * Author: Jesse Laning - */ - -#ifndef ARGPARSE_H -#define ARGPARSE_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -class ArgumentParser { -private: - struct Argument; - -public: - class ArgumentNotFound : public std::runtime_error { - public: - ArgumentNotFound( - ArgumentParser::Argument& arg, - std::unordered_map pairs) noexcept - : std::runtime_error(("Required argument not found: " + arg._name + - ((pairs.find(arg._name) == pairs.end()) - ? "" - : " or " + pairs.find(arg._name)->second)) - .c_str()) {} - }; - - ArgumentParser(const std::string& desc) : _desc(desc), _help(false) {} - ArgumentParser(const std::string desc, int argc, const char* argv[]) - : ArgumentParser(desc) { - parse(argc, argv); - } - - void add_argument(const std::string& name, const std::string& long_name, - const std::string& desc, const bool required = false) { - _arguments.push_back({ name, desc, required }); - _pairs[name] = long_name; - } - - void add_argument(const std::string& name, const std::string& desc, - const bool required = false) { - _arguments.push_back({ name, desc, required }); - } - - bool is_help() { return _help; } - - void print_help() { - std::cout << "Usage: " << _bin << " [options]" << std::endl; - std::cout << "Options:" << std::endl; - for (auto& a : _arguments) { - std::string name = a._name; - auto i = _pairs.find(name); - if (i != _pairs.end()) name.append(", " + i->second); - std::cout << " " << std::setw(23) << std::left << name << std::setw(23) - << a._desc + (a._required ? " (Required)" : "") << std::endl; - } - } - - void parse(int argc, const char* argv[]) { - _bin = argv[0]; - if (argc > 1) { - std::string name; - std::vector arg_parts; - std::vector free_args; - auto push_arg = [&name, &arg_parts, this]() { - if (!name.empty()) { - if (name[0] == '-') { - _add_variable(name, arg_parts); - } - else { - for (char c : name) { - _add_variable(std::string(1, c), arg_parts); - } - } - arg_parts.clear(); - } - }; - for (int i = 1; i < argc; i++) { - size_t slen = std::strlen(argv[i]); - if (slen == 0) { - continue; - } - else if (slen >= 2 && argv[i][0] == '-' && !_is_number(argv[i])) { - push_arg(); - if (i == argc - 1) { - name = &(argv[i][1]); - push_arg(); - } - else { - name = &(argv[i][1]); - } - } - else if (name.empty()) { - free_args.push_back(argv[i]); - } - else { - arg_parts.push_back(argv[i]); - if (i == argc - 1) { - push_arg(); - } - } - } - _add_variable("", free_args); - } - if (!_help) { - for (auto& a : _arguments) { - if (a._required) { - if (_variables.find(a._name) == _variables.end()) { - // Check if a pair name does not exist. If it does exist check that - // there is not a variable with that name - if (_pairs.find(a._name) == _pairs.end() || - _variables.find(_pairs.find(a._name)->second) == - _variables.end()) { - throw ArgumentNotFound(a, _pairs); - } - } - } - } - } - } - - bool exists(const std::string& name) { - std::string t = _delimit(name); - if (_pairs.find(t) != _pairs.end()) t = _pairs[t]; - return _variables.find(t) != _variables.end(); - } - - template - std::vector getv(const std::string& name) { - std::vector argstr = getv(name); - std::vector v; - for (auto& s : argstr) { - std::istringstream in(s); - T t; - in >> t; - v.push_back(t); - } - return v; - } - - template - T get(const std::string& name) { - std::istringstream in(get(name)); - T t; - in >> t >> std::ws; - return t; - } - -private: - friend class ArgumentNotFound; - struct Argument { - public: - Argument(const std::string& name, const std::string& desc, - bool required = false) - : _name(name), _desc(desc), _required(required) {} - - std::string _name; - std::string _desc; - bool _required; - }; - inline void _add_variable(std::string name, - std::vector& arg_parts) { - if (name == "h" || name == "-help") { - _help = true; - print_help(); - } - _ltrim(name, [](int c) { return c != static_cast('-'); }); - name = _delimit(name); - if (_pairs.find(name) != _pairs.end()) name = _pairs[name]; - _variables[name] = arg_parts; - } - static std::string _delimit(const std::string& name) { - return std::string(std::min(name.size(), static_cast(2)), '-') - .append(name); - } - static std::string _strip(const std::string& name) { - size_t begin = 0; - begin += name.size() > 0 ? name[0] == '-' : 0u; - begin += name.size() > 3 ? name[1] == '-' : 0u; - return name.substr(begin); - } - static std::string _upper(const std::string& in) { - std::string out(in); - std::transform(out.begin(), out.end(), out.begin(), ::toupper); - return out; - } - static std::string _escape(const std::string& in) { - std::string out(in); - if (in.find(' ') != std::string::npos) - out = std::string("\"").append(out).append("\""); - return out; - } - static bool _not_space(int ch) { return !std::isspace(ch); } - static inline void _ltrim(std::string& s, bool (*f)(int) = _not_space) { - s.erase(s.begin(), std::find_if(s.begin(), s.end(), f)); - } - static inline void _rtrim(std::string& s, bool (*f)(int) = _not_space) { - s.erase(std::find_if(s.rbegin(), s.rend(), f).base(), s.end()); - } - static inline void _trim(std::string& s, bool (*f)(int) = _not_space) { - _ltrim(s, f); - _rtrim(s, f); - } - static inline std::string _ltrim_copy(std::string s, - bool (*f)(int) = _not_space) { - _ltrim(s, f); - return s; - } - static inline std::string _rtrim_copy(std::string s, - bool (*f)(int) = _not_space) { - _rtrim(s, f); - return s; - } - static inline std::string _trim_copy(std::string s, - bool (*f)(int) = _not_space) { - _trim(s, f); - return s; - } - template - inline std::string _join(InputIt begin, InputIt end, - const std::string& separator = " ") { - std::ostringstream ss; - if (begin != end) { - ss << *begin++; - } - while (begin != end) { - ss << separator; - ss << *begin++; - } - return ss.str(); - } - static inline bool _is_number(const char* arg) { - std::istringstream iss{ std::string(arg) }; - float f; - iss >> std::noskipws >> f; - return iss.eof() && !iss.fail(); - } - - std::string _desc; - std::string _bin{}; - bool _help{ false }; - std::vector _arguments{}; - std::unordered_map> _variables{}; - std::unordered_map _pairs{}; -}; -template <> -inline std::string ArgumentParser::get(const std::string& name) { - std::string t = _delimit(name); - if (_pairs.find(t) != _pairs.end()) t = _pairs[t]; - auto v = _variables.find(t); - if (v != _variables.end()) { - return _join(v->second.begin(), v->second.end()); - } - return ""; -} -template <> -inline bool ArgumentParser::get(const std::string& name) { - return exists(name); -} -template <> -inline std::vector ArgumentParser::getv( - const std::string& name) { - std::string t = _delimit(name); - if (_pairs.find(t) != _pairs.end()) t = _pairs[t]; - auto v = _variables.find(t); - if (v != _variables.end()) { - return v->second; - } - return std::vector(); -} -#endif \ No newline at end of file diff --git a/cxxopts.hpp b/cxxopts.hpp new file mode 100644 index 0000000..93c69d3 --- /dev/null +++ b/cxxopts.hpp @@ -0,0 +1,2214 @@ +/* + +Copyright (c) 2014, 2015, 2016, 2017 Jarryd Beck + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + +#ifndef CXXOPTS_HPP_INCLUDED +#define CXXOPTS_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cpp_lib_optional +#include +#define CXXOPTS_HAS_OPTIONAL +#endif + +#ifndef CXXOPTS_VECTOR_DELIMITER +#define CXXOPTS_VECTOR_DELIMITER ',' +#endif + +#define CXXOPTS__VERSION_MAJOR 2 +#define CXXOPTS__VERSION_MINOR 2 +#define CXXOPTS__VERSION_PATCH 0 + +namespace cxxopts +{ + 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, +//which results in the correct lengths being computed for strings when they +//are formatted for the help output +//it is necessary to make sure that can be found by the +//compiler, and that icu-uc is linked in to the binary. + +#ifdef CXXOPTS_USE_UNICODE +#include + +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: + + 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; + }; + + 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); + } + + return s; + } + + template + String& + stringAppend(String& s, Iterator begin, Iterator end) + { + while (begin != end) + { + s.append(*begin); + ++begin; + } + + 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()); + } +} + +//ifdef CXXOPTS_USE_UNICODE +#else + +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(); + } +} + +//ifdef CXXOPTS_USE_UNICODE +#endif + +namespace cxxopts +{ + namespace + { +#ifdef _WIN32 + const std::string LQUOTE("\'"); + const std::string RQUOTE("\'"); +#else + const std::string LQUOTE("‘"); + const std::string RQUOTE("’"); +#endif + } + + class Value : public std::enable_shared_from_this + { + public: + + virtual ~Value() = default; + + virtual + std::shared_ptr + clone() const = 0; + + virtual void + parse(const std::string& text) const = 0; + + virtual void + parse() const = 0; + + virtual bool + has_default() const = 0; + + virtual bool + is_container() const = 0; + + virtual bool + has_implicit() const = 0; + + virtual std::string + get_default_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 + implicit_value(const std::string& value) = 0; + + virtual std::shared_ptr + no_implicit_value() = 0; + + virtual bool + is_boolean() const = 0; + }; + + 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(); + } + + private: + std::string m_message; + }; + + class OptionSpecException : public OptionException + { + public: + + OptionSpecException(const std::string& message) + : OptionException(message) + { + } + }; + + class OptionParseException : public OptionException + { + public: + OptionParseException(const std::string& message) + : OptionException(message) + { + } + }; + + class option_exists_error : public OptionSpecException + { + public: + option_exists_error(const std::string& option) + : OptionSpecException("Option " + LQUOTE + option + RQUOTE + " already exists") + { + } + }; + + 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_syntax_exception : public OptionParseException { + public: + option_syntax_exception(const std::string& text) + : OptionParseException("Argument " + LQUOTE + text + RQUOTE + + " starts with a - but has incorrect syntax") + { + } + }; + + 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 missing_argument_exception : public OptionParseException + { + public: + 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 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_present_exception : public OptionParseException + { + public: + option_not_present_exception(const std::string& option) + : OptionParseException("Option " + LQUOTE + option + RQUOTE + " not present") + { + } + }; + + class argument_incorrect_type : public OptionParseException + { + public: + 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"); + +#ifndef CXXOPTS_NO_EXCEPTIONS + // 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(); +#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 + { + template + struct SignedCheck; + + template + struct SignedCheck + { + 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) + { + SignedCheck::is_signed>()(negative, value, text); + } + } + + 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 + integer_parser(const std::string& text, T& value) + { + std::smatch match; + std::regex_match(text, match, integer_pattern); + + if (match.length() == 0) + { + throw_or_mimic(text); + } + + if (match.length(4) > 0) + { + value = 0; + return; + } + + using US = typename std::make_unsigned::type; + + 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; + + auto value_match = match[3]; + + US result = 0; + + 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; + } + + detail::check_signed_range(negative, result, text); + + if (negative) + { + value = checked_negate(result, + text, + std::integral_constant()); + } + else + { + value = static_cast(result); + } + } + + 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, uint8_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, int8_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, uint16_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, int16_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, uint32_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, int32_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, uint64_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, int64_t& value) + { + integer_parser(text, value); + } + + 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); + } + + inline + void + parse_value(const std::string& text, std::string& value) + { + value = text; + } + + // 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 + 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)); + } + } + +#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, char& c) + { + if (text.length() != 1) + { + throw_or_mimic(text); + } + + c = text[0]; + } + + template + struct type_is_container + { + static constexpr bool value = false; + }; + + 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 + 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) + { + } + + OptionDetails(const OptionDetails& rhs) + : m_desc(rhs.m_desc) + , m_count(rhs.m_count) + { + m_value = rhs.m_value->clone(); + } + + 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; + }; + + 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(); + } + + size_t + count() const noexcept + { + return m_count; + } + + // TODO: maybe default options should count towards the number of arguments + bool + has_default() const noexcept + { + return m_default; + } + + template + const T& + as() const + { + 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 + } + + private: + void + ensure_value(std::shared_ptr details) + { + if (m_value == nullptr) + { + m_value = details->make_storage(); + } + } + + 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_)) + { + } + + const + std::string& + key() const + { + return m_key; + } + + const + std::string& + value() const + { + return m_value; + } + + template + T + as() const + { + T result; + values::parse_value(m_value, result); + return result; + } + + private: + std::string m_key; + std::string m_value; + }; + + class ParseResult + { + public: + + ParseResult( + const std::shared_ptr< + std::unordered_map> + >, + std::vector, + bool allow_unrecognised, + int&, char**&); + + size_t + count(const std::string& o) const + { + auto iter = m_options->find(o); + if (iter == m_options->end()) + { + return 0; + } + + auto riter = m_results.find(iter->second); + + return riter->second.count(); + } + + const OptionValue& + operator[](const std::string& option) const + { + auto iter = m_options->find(option); + + if (iter == m_options->end()) + { + throw_or_mimic(option); + } + + auto riter = m_results.find(iter->second); + + return riter->second; + } + + const std::vector& + arguments() const + { + return m_sequential; + } + + private: + + void + parse(int& argc, char**& argv); + + void + add_to_option(const std::string& option, const std::string& arg); + + bool + consume_positional(std::string a); + + void + parse_option + ( + std::shared_ptr value, + const std::string& name, + const std::string& arg = "" + ); + + void + parse_default(std::shared_ptr details); + + 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::string opts_; + std::string desc_; + std::shared_ptr value_; + std::string arg_help_; + }; + + class Options + { + typedef std::unordered_map> + OptionMap; + public: + + 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()) + { + } + + Options& + positional_help(std::string help_text) + { + m_positional_help = std::move(help_text); + return *this; + } + + Options& + custom_help(std::string help_text) + { + m_custom_help = std::move(help_text); + return *this; + } + + Options& + show_positional_help() + { + m_show_positional = true; + return *this; + } + + Options& + allow_unrecognised_options() + { + m_allow_unrecognised = true; + return *this; + } + + ParseResult + parse(int& argc, char**& argv); + + OptionAdder + add_options(std::string group = ""); + + void + add_options + ( + const std::string& group, + std::initializer_list