From bb29661a909811ce619304e8eb6ee510fdcc173a Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 17 Nov 2018 16:43:29 +0100 Subject: [PATCH] Redesigned C++ CLI --- src/CMakeLists.txt | 1 + src/slic3r.cpp | 485 ++++++++++++++++--------- src/slic3r.hpp | 29 ++ xs/src/libslic3r/Config.hpp | 1 - xs/src/libslic3r/ConfigBase.cpp | 41 ++- xs/src/libslic3r/ConfigBase.hpp | 14 +- xs/src/libslic3r/IO.cpp | 36 +- xs/src/libslic3r/IO.hpp | 24 +- xs/src/libslic3r/IO/AMF.cpp | 2 +- xs/src/libslic3r/IO/TMF.cpp | 5 +- xs/src/libslic3r/Model.cpp | 63 +++- xs/src/libslic3r/Model.hpp | 10 +- xs/src/libslic3r/PlaceholderParser.cpp | 2 +- xs/src/libslic3r/PlaceholderParser.hpp | 2 +- xs/src/libslic3r/Print.cpp | 10 +- xs/src/libslic3r/Print.hpp | 3 +- xs/src/libslic3r/PrintConfig.cpp | 213 +++++++---- xs/src/libslic3r/PrintConfig.hpp | 77 +--- xs/src/libslic3r/SLAPrint.hpp | 4 +- xs/src/libslic3r/SimplePrint.cpp | 49 +++ xs/src/libslic3r/SimplePrint.hpp | 29 ++ xs/src/libslic3r/TriangleMesh.cpp | 20 +- xs/src/libslic3r/TriangleMesh.hpp | 6 +- 23 files changed, 755 insertions(+), 371 deletions(-) create mode 100644 src/slic3r.hpp create mode 100644 xs/src/libslic3r/SimplePrint.cpp create mode 100644 xs/src/libslic3r/SimplePrint.hpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 16727351c..a1f389c0c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -212,6 +212,7 @@ add_library(libslic3r STATIC ${LIBDIR}/libslic3r/PrintConfig.cpp ${LIBDIR}/libslic3r/PrintObject.cpp ${LIBDIR}/libslic3r/PrintRegion.cpp + ${LIBDIR}/libslic3r/SimplePrint.cpp ${LIBDIR}/libslic3r/SLAPrint.cpp ${LIBDIR}/libslic3r/SlicingAdaptive.cpp ${LIBDIR}/libslic3r/Surface.cpp diff --git a/src/slic3r.cpp b/src/slic3r.cpp index b7556eae4..7533445e5 100644 --- a/src/slic3r.cpp +++ b/src/slic3r.cpp @@ -1,102 +1,93 @@ -#include "ConfigBase.hpp" +#include "slic3r.hpp" #include "Geometry.hpp" #include "IO.hpp" -#include "Model.hpp" #include "SLAPrint.hpp" #include "Print.hpp" +#include "SimplePrint.hpp" #include "TriangleMesh.hpp" #include "libslic3r.h" +#include +#include #include #include #include +#include #include #include #include #include #include - #ifdef USE_WX #include "GUI/GUI.hpp" #endif -/// utility function for displaying CLI usage -void printUsage(); - using namespace Slic3r; int -main(int argc, char **argv) -{ +main(int argc, char **argv) { + return CLI().run(argc, argv); +} + +int CLI::run(int argc, char **argv) { // Convert arguments to UTF-8 (needed on Windows). // argv then points to memory owned by a. boost::nowide::args a(argc, argv); // parse all command line options into a DynamicConfig - ConfigDef config_def; - config_def.merge(cli_config_def); - config_def.merge(print_config_def); - DynamicConfig config(&config_def); - t_config_option_keys input_files; + t_config_option_keys opt_order; + this->config_def.merge(cli_actions_config_def); + this->config_def.merge(cli_transform_config_def); + this->config_def.merge(cli_misc_config_def); + this->config_def.merge(print_config_def); + this->config.def = &this->config_def; + // if any option is unsupported, print usage and abort immediately - if ( !config.read_cli(argc, argv, &input_files) ) - { - printUsage(); - return 0; + if (!this->config.read_cli(argc, argv, &this->input_files, &opt_order)) { + this->print_help(); + return 1; } - // apply command line options to a more handy CLIConfig - CLIConfig cli_config; - cli_config.apply(config, true); + // TODO: validate this->config (min, max etc.) - DynamicPrintConfig print_config; - -#ifdef USE_WX - if (cli_config.gui) { - GUI::App *gui = new GUI::App(); - - GUI::App::SetInstance(gui); - wxEntry(argc, argv); + // parse actions and transform options + for (auto const &opt_key : opt_order) { + if (cli_actions_config_def.has(opt_key)) this->actions.push_back(opt_key); + if (cli_transform_config_def.has(opt_key)) this->transforms.push_back(opt_key); } -#else - if (cli_config.gui) { - std::cout << "GUI support has not been built." << "\n"; - } -#endif + // load config files supplied via --load - for (const std::string &file : cli_config.load.values) { + for (auto const &file : config.getStrings("load")) { if (!boost::filesystem::exists(file)) { - boost::nowide::cout << "No such file: " << file << std::endl; - exit(1); + if (config.getBool("ignore_nonexistent_file", false)) { + continue; + } else { + boost::nowide::cerr << "No such file: " << file << std::endl; + exit(1); + } } DynamicPrintConfig c; try { c.load(file); } catch (std::exception &e) { - boost::nowide::cout << "Error while reading config file: " << e.what() << std::endl; + boost::nowide::cerr << "Error while reading config file: " << e.what() << std::endl; exit(1); } c.normalize(); - print_config.apply(c); + this->print_config.apply(c); } // apply command line options to a more specific DynamicPrintConfig which provides normalize() // (command line options override --load files) - print_config.apply(config, true); - print_config.normalize(); + this->print_config.apply(config, true); + this->print_config.normalize(); - // write config if requested - if (!cli_config.save.value.empty()) print_config.save(cli_config.save.value); + // create a static (full) print config to be used in our logic + this->full_print_config.apply(this->print_config); // read input file(s) if any - std::vector models; - for (const t_config_option_key &file : input_files) { - if (!boost::filesystem::exists(file)) { - boost::nowide::cerr << "No such file: " << file << std::endl; - exit(1); - } - + for (auto const &file : input_files) { Model model; try { model = Model::read_from_file(file); @@ -110,151 +101,285 @@ main(int argc, char **argv) continue; } - model.add_default_instances(); - - // apply command line transform options - for (ModelObject* o : model.objects) { - if (cli_config.scale_to_fit.is_positive_volume()) - o->scale_to_fit(cli_config.scale_to_fit.value); - - // TODO: honor option order? - o->scale(cli_config.scale.value); - o->rotate(Geometry::deg2rad(cli_config.rotate_x.value), X); - o->rotate(Geometry::deg2rad(cli_config.rotate_y.value), Y); - o->rotate(Geometry::deg2rad(cli_config.rotate.value), Z); - } - - // TODO: handle --merge - models.push_back(model); - } - if (cli_config.help) { - printUsage(); - return 0; + this->models.push_back(model); } - for (Model &model : models) { - if (cli_config.info) { - // --info works on unrepaired model - model.print_info(); - } else if (cli_config.export_obj) { - std::string outfile = cli_config.output.value; - if (outfile.empty()) outfile = model.objects.front()->input_file + ".obj"; - - TriangleMesh mesh = model.mesh(); - mesh.repair(); - IO::OBJ::write(mesh, outfile); - boost::nowide::cout << "File exported to " << outfile << std::endl; - } else if (cli_config.export_pov) { - std::string outfile = cli_config.output.value; - if (outfile.empty()) outfile = model.objects.front()->input_file + ".pov"; - - TriangleMesh mesh = model.mesh(); - mesh.repair(); - IO::POV::write(mesh, outfile); - boost::nowide::cout << "File exported to " << outfile << std::endl; - } else if (cli_config.export_svg) { - std::string outfile = cli_config.output.value; - if (outfile.empty()) - outfile = model.objects.front()->input_file + ".svg"; - - SLAPrint print(&model); // initialize print with model - print.config.apply(print_config, true); // apply configuration - print.slice(); // slice file - print.write_svg(outfile); // write SVG - boost::nowide::cout << "SVG file exported to " << outfile << std::endl; - } else if (cli_config.export_3mf) { - std::string outfile = cli_config.output.value; - if (outfile.empty()) outfile = model.objects.front()->input_file; - // Check if the file is already a 3mf. - if(outfile.substr(outfile.find_last_of('.'), outfile.length()) == ".3mf") - outfile = outfile.substr(0, outfile.find_last_of('.')) + "_2" + ".3mf"; - else - // Remove the previous extension and add .3mf extention. - outfile = outfile.substr(0, outfile.find_last_of('.')) + ".3mf"; - IO::TMF::write(model, outfile); - boost::nowide::cout << "File file exported to " << outfile << std::endl; - } else if (cli_config.cut_x > 0 || cli_config.cut_y > 0 || cli_config.cut > 0) { - model.repair(); - model.translate(0, 0, -model.bounding_box().min.z); - - if (!model.objects.empty()) { - // FIXME: cut all objects - Model out; - if (cli_config.cut_x > 0) { - model.objects.front()->cut(X, cli_config.cut_x, &out); - } else if (cli_config.cut_y > 0) { - model.objects.front()->cut(Y, cli_config.cut_y, &out); + // loop through transform options + for (auto const &opt_key : this->transforms) { + if (opt_key == "merge") { + Model m; + for (auto &model : this->models) + m.merge(model); + this->models = {m}; + } else if (opt_key == "duplicate") { + const BoundingBoxf bb{ this->full_print_config.bed_shape.values }; + for (auto &model : this->models) { + const bool all_objects_have_instances = std::none_of( + model.objects.begin(), model.objects.end(), + [](ModelObject* o){ return o->instances.empty(); } + ); + if (all_objects_have_instances) { + // if all input objects have defined position(s) apply duplication to the whole model + model.duplicate(this->config.getInt("duplicate"), this->full_print_config.min_object_distance(), &bb); } else { - model.objects.front()->cut(Z, cli_config.cut, &out); + model.add_default_instances(); + model.duplicate_objects(this->config.getInt("duplicate"), this->full_print_config.min_object_distance(), &bb); + } + } + } else if (opt_key == "duplicate_grid") { + auto &ints = this->config.opt("duplicate_grid")->values; + const int x = ints.size() > 0 ? ints.at(0) : 1; + const int y = ints.size() > 1 ? ints.at(1) : 1; + const double distance = this->full_print_config.duplicate_distance.value; + for (auto &model : this->models) + model.duplicate_objects_grid(x, y, (distance > 0) ? distance : 6); // TODO: this is not the right place for setting a default + } else if (opt_key == "center") { + for (auto &model : this->models) { + model.center_instances_around_point(config.opt("center")->value); + model.align_to_ground(); + } + } else if (opt_key == "align_xy") { + const Pointf p{ this->config.opt("align_xy")->value }; + for (auto &model : this->models) { + BoundingBoxf3 bb{ model.bounding_box() }; + model.translate(-(bb.min.x - p.x), -(bb.min.y - p.y), -bb.min.z); + } + } else if (opt_key == "rotate") { + for (auto &model : this->models) + for (auto &o : model.objects) + o->rotate(Geometry::deg2rad(config.getFloat(opt_key)), Z); + } else if (opt_key == "rotate_x") { + for (auto &model : this->models) + for (auto &o : model.objects) + o->rotate(Geometry::deg2rad(config.getFloat(opt_key)), X); + } else if (opt_key == "rotate_y") { + for (auto &model : this->models) + for (auto &o : model.objects) + o->rotate(Geometry::deg2rad(config.getFloat(opt_key)), Y); + } else if (opt_key == "scale") { + for (auto &model : this->models) + for (auto &o : model.objects) + o->scale(config.get_abs_value(opt_key, 1)); + } else if (opt_key == "scale_to_fit") { + const auto opt = config.opt(opt_key); + if (!opt->is_positive_volume()) { + boost::nowide::cerr << "--scale-to-fit requires a positive volume" << std::endl; + return 1; + } + for (auto &model : this->models) + for (auto &o : model.objects) + o->scale_to_fit(opt->value); + } else if (opt_key == "cut" || opt_key == "cut_x" || opt_key == "cut_y") { + std::vector new_models; + for (auto &model : this->models) { + model.repair(); + model.translate(0, 0, -model.bounding_box().min.z); // align to z = 0 + + Model out; + for (auto &o : model.objects) { + if (opt_key == "cut_x") { + o->cut(X, config.getFloat("cut_x"), &out); + } else if (opt_key == "cut_y") { + o->cut(Y, config.getFloat("cut_y"), &out); + } else if (opt_key == "cut") { + o->cut(Z, config.getFloat("cut"), &out); + } } - ModelObject &upper = *out.objects[0]; - ModelObject &lower = *out.objects[1]; - - // Use the input name and trim off the extension. - std::string outfile = cli_config.output.value; - if (outfile.empty()) outfile = model.objects.front()->input_file; - outfile = outfile.substr(0, outfile.find_last_of('.')); - std::cerr << outfile << "\n"; + // add each resulting object as a distinct model + Model upper, lower; + auto upper_obj = upper.add_object(*out.objects[0]); + auto lower_obj = lower.add_object(*out.objects[1]); + + if (upper_obj->facets_count() > 0) new_models.push_back(upper); + if (lower_obj->facets_count() > 0) new_models.push_back(lower); + } - if (upper.facets_count() > 0) { - TriangleMesh m = upper.mesh(); - IO::STL::write(m, outfile + "_upper.stl"); - } - if (lower.facets_count() > 0) { - TriangleMesh m = lower.mesh(); - IO::STL::write(m, outfile + "_lower.stl"); + // TODO: copy less stuff around using pointers + this->models = new_models; + + if (this->actions.empty()) + this->actions.push_back("export_stl"); + } else if (opt_key == "cut_grid") { + std::vector new_models; + for (auto &model : this->models) { + TriangleMesh mesh = model.mesh(); + mesh.repair(); + + TriangleMeshPtrs meshes = mesh.cut_by_grid(config.opt("cut_grid")->value); + size_t i = 0; + for (TriangleMesh* m : meshes) { + Model out; + auto o = out.add_object(); + o->add_volume(*m); + o->input_file += "_" + std::to_string(i++); + delete m; } } - } else if (cli_config.cut_grid.value.x > 0 && cli_config.cut_grid.value.y > 0) { - TriangleMesh mesh = model.mesh(); - mesh.repair(); - TriangleMeshPtrs meshes = mesh.cut_by_grid(cli_config.cut_grid.value); - size_t i = 0; - for (TriangleMesh* m : meshes) { - std::ostringstream ss; - ss << model.objects.front()->input_file << "_" << i++ << ".stl"; - IO::STL::write(*m, ss.str()); - delete m; - } - } else if (cli_config.slice) { - std::string outfile = cli_config.output.value; - Print print; - - model.arrange_objects(print.config.min_object_distance()); - model.center_instances_around_point(cli_config.center); - if (outfile.empty()) outfile = model.objects.front()->input_file + ".gcode"; - print.apply_config(print_config); - - for (auto* mo : model.objects) { - print.auto_assign_extruders(mo); - print.add_model_object(mo); - } - print.validate(); - - print.export_gcode(outfile); - + // TODO: copy less stuff around using pointers + this->models = new_models; + + if (this->actions.empty()) + this->actions.push_back("export_stl"); + } else if (opt_key == "split") { + for (auto &model : this->models) + model.split(); + } else if (opt_key == "repair") { + for (auto &model : this->models) + model.repair(); } else { - boost::nowide::cerr << "error: command not supported" << std::endl; + boost::nowide::cerr << "error: option not implemented yet: " << opt_key << std::endl; return 1; } } + // loop through action options + for (auto const &opt_key : this->actions) { + if (opt_key == "help") { + this->print_help(); + } else if (opt_key == "save") { + this->print_config.save(config.getString("save")); + } else if (opt_key == "info") { + // --info works on unrepaired model + for (const Model &model : this->models) + model.print_info(); + } else if (opt_key == "export_stl") { + this->export_models(IO::STL); + } else if (opt_key == "export_obj") { + this->export_models(IO::OBJ); + } else if (opt_key == "export_pov") { + this->export_models(IO::POV); + } else if (opt_key == "export_amf") { + this->export_models(IO::AMF); + } else if (opt_key == "export_3mf") { + this->export_models(IO::TMF); + } else if (opt_key == "export_sla") { + boost::nowide::cerr << "--export-sla is not implemented yet" << std::endl; + } else if (opt_key == "export_sla_svg") { + for (const Model &model : this->models) { + SLAPrint print(&model); // initialize print with model + print.config.apply(this->print_config, true); // apply configuration + print.slice(); // slice file + const std::string outfile = this->output_filepath(model, IO::SVG); + print.write_svg(outfile); // write SVG + boost::nowide::cout << "SVG file exported to " << outfile << std::endl; + } + } else if (opt_key == "export_gcode") { + for (const Model &model : this->models) { + SimplePrint print; + print.status_cb = [](int ln, const std::string& msg) { + boost::nowide::cout << msg << std::endl; + }; + print.center = !this->config.has("center") + && !this->config.has("align_xy") + && !this->config.getBool("dont_arrange"); + print.set_model(model); + + // start chronometer + typedef std::chrono::high_resolution_clock clock_; + typedef std::chrono::duration > second_; + std::chrono::time_point t0{ clock_::now() }; + + const std::string outfile = this->output_filepath(model, IO::Gcode); + print.export_gcode(outfile); + boost::nowide::cout << "G-code exported to " << outfile << std::endl; + + // output some statistics + double duration { std::chrono::duration_cast(clock_::now() - t0).count() }; + boost::nowide::cout << std::fixed << std::setprecision(0) + << "Done. Process took " << (duration/60) << " minutes and " + << std::setprecision(3) + << std::fmod(duration, 60.0) << " seconds." << std::endl + << std::setprecision(2) + << "Filament required: " << print.total_used_filament() << "mm" + << " (" << print.total_extruded_volume()/1000 << "cm3)" << std::endl; + } + } else { + boost::nowide::cerr << "error: option not supported yet: " << opt_key << std::endl; + return 1; + } + } + + if (actions.empty()) { +#ifdef USE_WX + GUI::App *gui = new GUI::App(); + + GUI::App::SetInstance(gui); + wxEntry(argc, argv); +#else + std::cout << "GUI support has not been built." << "\n"; +#endif + } + return 0; } -void printUsage() -{ - std::cout << "Slic3r " << SLIC3R_VERSION << " is a STL-to-GCODE translator for RepRap 3D printers" << "\n" - << "written by Alessandro Ranellucci - http://slic3r.org/ - https://github.com/slic3r/Slic3r" << "\n" - << "Git Version " << BUILD_COMMIT << "\n\n" - << "Usage (C++ only): ./slic3r [ OPTIONS ] [ file.stl ] [ file2.stl ] ..." << "\n"; - // CLI Options - std::cout << "** CLI OPTIONS **\n"; - print_cli_options(boost::nowide::cout); - std::cout << "****\n"; - // Print options - std::cout << "** PRINT OPTIONS **\n"; - print_print_options(boost::nowide::cout); - std::cout << "****\n"; + +void +CLI::print_help() const { + std::cout << "Slic3r " << SLIC3R_VERSION << " is a STL-to-GCODE translator for RepRap 3D printers" << "\n" + << "written by Alessandro Ranellucci & the Slic3r community - https://slic3r.org/ - https://github.com/slic3r/Slic3r" << "\n" + << "Git Version " << BUILD_COMMIT << "\n\n" + << "Usage (C++ only): ./slic3r [ OPTIONS ] [ file.stl ] [ file2.stl ] ..." << "\n"; + // CLI Options + std::cout << "** CLI OPTIONS **\n"; + print_cli_options(boost::nowide::cout); + std::cout << "****\n"; + // Print options + std::cout << "** PRINT OPTIONS **\n"; + print_print_options(boost::nowide::cout); + std::cout << "****\n"; +} + +void +CLI::export_models(IO::ExportFormat format) { + for (size_t i = 0; i < this->models.size(); ++i) { + Model &model = this->models[i]; + const std::string outfile = this->output_filepath(model, format); + + IO::write_model.at(format)(model, outfile); + std::cout << "File exported to " << outfile << std::endl; + } +} + +std::string +CLI::output_filepath(const Model &model, IO::ExportFormat format) const { + // get the --output-filename-format option + std::string filename_format = this->print_config.getString("output_filename_format", "[input_filename_base]"); + + // strip the file extension and add the correct one + filename_format = filename_format.substr(0, filename_format.find_last_of(".")); + filename_format += "." + IO::extensions.at(format); + + // this is the same logic used in Print::output_filepath() + // TODO: factor it out to a single place? + + // find the first input_file of the model + boost::filesystem::path input_file; + for (auto o : model.objects) { + if (!o->input_file.empty()) { + input_file = o->input_file; + break; + } + } + + // compute the automatic filename + PlaceholderParser pp; + pp.set("input_filename", input_file.filename().string()); + pp.set("input_filename_base", input_file.stem().string()); + pp.apply_config(this->config); + const std::string filename = pp.process(filename_format); + + // use --output when available + std::string outfile{ this->config.getString("output") }; + if (!outfile.empty()) { + // if we were supplied a directory, use it and append our automatically generated filename + const boost::filesystem::path out(outfile); + if (boost::filesystem::is_directory(out)) + outfile = (out / filename).string(); + } else { + outfile = (input_file.parent_path() / filename).string(); + } + + return outfile; } diff --git a/src/slic3r.hpp b/src/slic3r.hpp new file mode 100644 index 000000000..6787710ab --- /dev/null +++ b/src/slic3r.hpp @@ -0,0 +1,29 @@ +#ifndef SLIC3R_HPP +#define SLIC3R_HPP + +#include "ConfigBase.hpp" +#include "IO.hpp" +#include "Model.hpp" + +namespace Slic3r { + +class CLI { + public: + int run(int argc, char **argv); + + private: + ConfigDef config_def; + DynamicConfig config; + DynamicPrintConfig print_config; + FullPrintConfig full_print_config; + t_config_option_keys input_files, actions, transforms; + std::vector models; + + void print_help() const; + void export_models(IO::ExportFormat format); + std::string output_filepath(const Model &model, IO::ExportFormat format) const; +}; + +} + +#endif diff --git a/xs/src/libslic3r/Config.hpp b/xs/src/libslic3r/Config.hpp index bed98e911..703de8b49 100644 --- a/xs/src/libslic3r/Config.hpp +++ b/xs/src/libslic3r/Config.hpp @@ -64,7 +64,6 @@ public: if (print_config_def.options.count(opt_key) == 0) throw InvalidOptionType(opt_key + std::string(" is an invalid option.")); return (dynamic_cast(this->_config.optptr(opt_key, create)))->getFloat(); } - int getInt(const t_config_option_key& opt_key, bool create=true) { if (print_config_def.options.count(opt_key) == 0) throw InvalidOptionType(opt_key + std::string(" is an invalid option.")); return (dynamic_cast(this->_config.optptr(opt_key, create)))->getInt(); diff --git a/xs/src/libslic3r/ConfigBase.cpp b/xs/src/libslic3r/ConfigBase.cpp index e34e8557d..b63f8aab6 100644 --- a/xs/src/libslic3r/ConfigBase.cpp +++ b/xs/src/libslic3r/ConfigBase.cpp @@ -374,6 +374,36 @@ ConfigBase::get_abs_value(const t_config_option_key &opt_key, double ratio_over) return opt->get_abs_value(ratio_over); } +bool +ConfigBase::getBool(const t_config_option_key &opt_key, bool default_value) const { + auto opt = this->opt(opt_key); + return opt == nullptr ? default_value : opt->value; +} + +double +ConfigBase::getFloat(const t_config_option_key &opt_key, double default_value) const { + auto opt = this->opt(opt_key); + return opt == nullptr ? default_value : opt->value; +} + +int +ConfigBase::getInt(const t_config_option_key &opt_key, double default_value) const { + auto opt = this->opt(opt_key); + return opt == nullptr ? default_value : opt->value; +} + +std::string +ConfigBase::getString(const t_config_option_key &opt_key, std::string default_value) const { + auto opt = this->opt(opt_key); + return opt == nullptr ? default_value : opt->value; +} + +std::vector +ConfigBase::getStrings(const t_config_option_key &opt_key, std::vector default_value) const { + auto opt = this->opt(opt_key); + return opt == nullptr ? default_value : opt->values; +} + void ConfigBase::setenv_() { @@ -451,6 +481,7 @@ DynamicConfig& DynamicConfig::operator= (DynamicConfig other) void DynamicConfig::swap(DynamicConfig &other) { + std::swap(this->def, other.def); std::swap(this->options, other.options); } @@ -541,7 +572,7 @@ DynamicConfig::empty() const { } void -DynamicConfig::read_cli(const std::vector &tokens, t_config_option_keys* extra) +DynamicConfig::read_cli(const std::vector &tokens, t_config_option_keys* extra, t_config_option_keys* keys) { std::vector _argv; @@ -551,11 +582,11 @@ DynamicConfig::read_cli(const std::vector &tokens, t_config_option_ for (size_t i = 0; i < tokens.size(); ++i) _argv.push_back(const_cast(tokens[i].c_str())); - this->read_cli(_argv.size(), &_argv[0], extra); + this->read_cli(_argv.size(), &_argv[0], extra, keys); } bool -DynamicConfig::read_cli(int argc, char** argv, t_config_option_keys* extra) +DynamicConfig::read_cli(int argc, char** argv, t_config_option_keys* extra, t_config_option_keys* keys) { // cache the CLI option => opt_key mapping std::map opts; @@ -630,6 +661,10 @@ DynamicConfig::read_cli(int argc, char** argv, t_config_option_keys* extra) // Store the option value. const bool existing = this->has(opt_key); + if (keys != nullptr && !existing) { + // save the order of detected keys + keys->push_back(opt_key); + } if (ConfigOptionBool* opt = this->opt(opt_key, true)) { opt->value = !no; } else if (ConfigOptionBools* opt = this->opt(opt_key, true)) { diff --git a/xs/src/libslic3r/ConfigBase.hpp b/xs/src/libslic3r/ConfigBase.hpp index 2be8cefba..e8e9caeeb 100644 --- a/xs/src/libslic3r/ConfigBase.hpp +++ b/xs/src/libslic3r/ConfigBase.hpp @@ -45,6 +45,7 @@ class ConfigOption { virtual void setFloat(double val) {}; virtual void setString(std::string val) {}; virtual std::string getString() const { return ""; }; + virtual std::vector getStrings() const { return std::vector(); }; friend bool operator== (const ConfigOption &a, const ConfigOption &b); friend bool operator!= (const ConfigOption &a, const ConfigOption &b); }; @@ -259,6 +260,8 @@ class ConfigOptionStrings : public ConfigOptionVector ConfigOptionStrings(const std::vector _values) : ConfigOptionVector(_values) {}; ConfigOptionStrings* clone() const { return new ConfigOptionStrings(this->values); }; + std::vector getStrings() const { return this->values; }; + std::string serialize() const { return escape_strings_cstyle(this->values); }; @@ -387,7 +390,7 @@ class ConfigOptionPoint3 : public ConfigOptionSingle bool deserialize(std::string str, bool append = false); - bool is_positive_volume () { + bool is_positive_volume() const { return this->value.x > 0 && this->value.y > 0 && this->value.z > 0; }; }; @@ -721,6 +724,11 @@ class ConfigBase virtual bool set_deserialize(t_config_option_key opt_key, std::string str, bool append = false); double get_abs_value(const t_config_option_key &opt_key) const; double get_abs_value(const t_config_option_key &opt_key, double ratio_over) const; + bool getBool(const t_config_option_key &opt_key, bool default_value = false) const; + double getFloat(const t_config_option_key &opt_key, double default_value = 0.0) const; + int getInt(const t_config_option_key &opt_key, double default_value = 0) const; + std::string getString(const t_config_option_key &opt_key, std::string default_value = "") const; + std::vector getStrings(const t_config_option_key &opt_key, std::vector default_value = std::vector()) const; void setenv_(); void load(const std::string &file); void save(const std::string &file) const; @@ -742,8 +750,8 @@ class DynamicConfig : public virtual ConfigBase void erase(const t_config_option_key &opt_key); void clear(); bool empty() const; - void read_cli(const std::vector &tokens, t_config_option_keys* extra); - bool read_cli(int argc, char** argv, t_config_option_keys* extra); + void read_cli(const std::vector &tokens, t_config_option_keys* extra, t_config_option_keys* keys = nullptr); + bool read_cli(int argc, char** argv, t_config_option_keys* extra, t_config_option_keys* keys = nullptr); private: typedef std::map t_options_map; diff --git a/xs/src/libslic3r/IO.cpp b/xs/src/libslic3r/IO.cpp index 5b7c77d2d..68b977ee2 100644 --- a/xs/src/libslic3r/IO.cpp +++ b/xs/src/libslic3r/IO.cpp @@ -1,6 +1,7 @@ #include "IO.hpp" #include #include +#include #include #include #include @@ -10,6 +11,24 @@ namespace Slic3r { namespace IO { +const std::map extensions{ + {STL, "stl"}, + {OBJ, "obj"}, + {POV, "pov"}, + {AMF, "amf"}, + {TMF, "3mf"}, + {SVG, "svg"}, + {Gcode, "gcode"}, +}; + +const std::map write_model{ + {STL, &STL::write}, + {OBJ, &OBJ::write}, + {POV, &POV::write}, + {AMF, &AMF::write}, + {TMF, &TMF::write}, +}; + bool STL::read(std::string input_file, TriangleMesh* mesh) { @@ -44,14 +63,14 @@ STL::read(std::string input_file, Model* model) } bool -STL::write(Model& model, std::string output_file, bool binary) +STL::write(const Model &model, std::string output_file, bool binary) { TriangleMesh mesh = model.mesh(); return STL::write(mesh, output_file, binary); } bool -STL::write(TriangleMesh& mesh, std::string output_file, bool binary) +STL::write(const TriangleMesh &mesh, std::string output_file, bool binary) { if (binary) { mesh.write_binary(output_file); @@ -134,21 +153,28 @@ OBJ::read(std::string input_file, Model* model) } bool -OBJ::write(Model& model, std::string output_file) +OBJ::write(const Model& model, std::string output_file) { TriangleMesh mesh = model.mesh(); return OBJ::write(mesh, output_file); } bool -OBJ::write(TriangleMesh& mesh, std::string output_file) +OBJ::write(const TriangleMesh& mesh, std::string output_file) { mesh.WriteOBJFile(output_file); return true; } bool -POV::write(TriangleMesh& mesh, std::string output_file) +POV::write(const Model &model, std::string output_file) +{ + TriangleMesh mesh{ model.mesh() }; + return STL::write(mesh, output_file); +} + +bool +POV::write(const TriangleMesh& mesh, std::string output_file) { TriangleMesh mesh2 = mesh; mesh2.center_around_origin(); diff --git a/xs/src/libslic3r/IO.hpp b/xs/src/libslic3r/IO.hpp index 9a5c527ca..83eb2a293 100644 --- a/xs/src/libslic3r/IO.hpp +++ b/xs/src/libslic3r/IO.hpp @@ -4,17 +4,26 @@ #include "libslic3r.h" #include "Model.hpp" #include "TriangleMesh.hpp" +#include #include namespace Slic3r { namespace IO { +enum ExportFormat { AMF, OBJ, POV, STL, SVG, TMF, Gcode }; + +extern const std::map extensions; +extern const std::map write_model; + class STL { public: static bool read(std::string input_file, TriangleMesh* mesh); static bool read(std::string input_file, Model* model); - static bool write(Model& model, std::string output_file, bool binary = true); - static bool write(TriangleMesh& mesh, std::string output_file, bool binary = true); + static bool write(const Model &model, std::string output_file) { + return STL::write(model, output_file, true); + }; + static bool write(const Model &model, std::string output_file, bool binary); + static bool write(const TriangleMesh &mesh, std::string output_file, bool binary = true); }; class OBJ @@ -22,28 +31,29 @@ class OBJ public: static bool read(std::string input_file, TriangleMesh* mesh); static bool read(std::string input_file, Model* model); - static bool write(Model& model, std::string output_file); - static bool write(TriangleMesh& mesh, std::string output_file); + static bool write(const Model& model, std::string output_file); + static bool write(const TriangleMesh& mesh, std::string output_file); }; class AMF { public: static bool read(std::string input_file, Model* model); - static bool write(Model& model, std::string output_file); + static bool write(const Model& model, std::string output_file); }; class POV { public: - static bool write(TriangleMesh& mesh, std::string output_file); + static bool write(const Model& model, std::string output_file); + static bool write(const TriangleMesh& mesh, std::string output_file); }; class TMF { public: static bool read(std::string input_file, Model* model); - static bool write(Model& model, std::string output_file); + static bool write(const Model& model, std::string output_file); }; } } diff --git a/xs/src/libslic3r/IO/AMF.cpp b/xs/src/libslic3r/IO/AMF.cpp index 7433762ee..6e5c342f6 100644 --- a/xs/src/libslic3r/IO/AMF.cpp +++ b/xs/src/libslic3r/IO/AMF.cpp @@ -496,7 +496,7 @@ AMF::read(std::string input_file, Model* model) } bool -AMF::write(Model& model, std::string output_file) +AMF::write(const Model& model, std::string output_file) { using namespace std; diff --git a/xs/src/libslic3r/IO/TMF.cpp b/xs/src/libslic3r/IO/TMF.cpp index 35d0d684c..a8a2a7e8c 100644 --- a/xs/src/libslic3r/IO/TMF.cpp +++ b/xs/src/libslic3r/IO/TMF.cpp @@ -388,9 +388,10 @@ TMFEditor::~TMFEditor(){ } bool -TMF::write(Model& model, std::string output_file) +TMF::write(const Model& model, std::string output_file) { - TMFEditor tmf_writer(std::move(output_file), &model); + Model m2{model}; + TMFEditor tmf_writer(std::move(output_file), &m2); return tmf_writer.produce_TMF(); } diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp index f6f49e391..a867a663f 100644 --- a/xs/src/libslic3r/Model.cpp +++ b/xs/src/libslic3r/Model.cpp @@ -48,8 +48,11 @@ Model::~Model() Model Model::read_from_file(std::string input_file) { - Model model; + if (!boost::filesystem::exists(input_file)) { + throw std::runtime_error("No such file"); + } + Model model; if (boost::algorithm::iends_with(input_file, ".stl")) { IO::STL::read(input_file, &model); } else if (boost::algorithm::iends_with(input_file, ".obj")) { @@ -72,6 +75,13 @@ Model::read_from_file(std::string input_file) return model; } +void +Model::merge(const Model &other) +{ + for (ModelObject* o : other.objects) + this->add_object(*o, true); +} + ModelObject* Model::add_object() { @@ -201,6 +211,18 @@ Model::repair() (*o)->repair(); } +void +Model::split() +{ + Model new_model; + for (ModelObject* o : this->objects) + o->split(&new_model.objects); + + this->clear_objects(); + for (ModelObject* o : new_model.objects) + this->add_object(*o); +} + void Model::center_instances_around_point(const Pointf &point) { @@ -281,16 +303,14 @@ Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb) // get the (transformed) size of each instance so that we take // into account their different transformations when packing Pointfs instance_sizes; - for (ModelObjectPtrs::const_iterator o = this->objects.begin(); o != this->objects.end(); ++o) { - for (size_t i = 0; i < (*o)->instances.size(); ++i) { - instance_sizes.push_back((*o)->instance_bounding_box(i).size()); - } - } + for (const ModelObject* o : this->objects) + for (size_t i = 0; i < o->instances.size(); ++i) + instance_sizes.push_back(o->instance_bounding_box(i).size()); Pointfs positions; if (! this->_arrange(instance_sizes, dist, bb, positions)) return false; - + for (ModelObjectPtrs::const_iterator o = this->objects.begin(); o != this->objects.end(); ++o) { for (ModelInstancePtrs::const_iterator i = (*o)->instances.begin(); i != (*o)->instances.end(); ++i) { (*i)->offset = positions.back(); @@ -330,13 +350,12 @@ Model::duplicate(size_t copies_num, coordf_t dist, const BoundingBoxf* bb) void Model::duplicate_objects(size_t copies_num, coordf_t dist, const BoundingBoxf* bb) { - for (ModelObjectPtrs::const_iterator o = this->objects.begin(); o != this->objects.end(); ++o) { + for (ModelObject* o : this->objects) { // make a copy of the pointers in order to avoid recursion when appending their copies - ModelInstancePtrs instances = (*o)->instances; - for (ModelInstancePtrs::const_iterator i = instances.begin(); i != instances.end(); ++i) { + const auto instances = o->instances; + for (const ModelInstance* i : instances) for (size_t k = 2; k <= copies_num; ++k) - (*o)->add_instance(**i); - } + o->add_instance(*i); } this->arrange_objects(dist, bb); @@ -642,6 +661,14 @@ Model::align_instances_to_origin() new_center.translate(-new_center.x/2, -new_center.y/2); this->center_instances_around_point(new_center); } + +void +Model::align_to_ground() +{ + BoundingBoxf3 bb = this->bounding_box(); + for (ModelObject* o : this->objects) + o->translate(0, 0, -bb.min.z); +} void ModelObject::align_to_ground() @@ -819,8 +846,16 @@ ModelObject::cut(Axis axis, coordf_t z, Model* model) const ModelObject* lower = model->add_object(*this); upper->clear_volumes(); lower->clear_volumes(); - upper->input_file = ""; - lower->input_file = ""; + + // remove extension from filename and add suffix + if (this->input_file.empty()) { + upper->input_file = "upper"; + lower->input_file = "lower"; + } else { + const boost::filesystem::path p{this->input_file}; + upper->input_file = (p.parent_path() / p.stem()).string() + "_upper"; + lower->input_file = (p.parent_path() / p.stem()).string() + "_lower"; + } for (ModelVolumePtrs::const_iterator v = this->volumes.begin(); v != this->volumes.end(); ++v) { ModelVolume* volume = *v; diff --git a/xs/src/libslic3r/Model.hpp b/xs/src/libslic3r/Model.hpp index 30cbf5e0d..05ea578eb 100644 --- a/xs/src/libslic3r/Model.hpp +++ b/xs/src/libslic3r/Model.hpp @@ -73,7 +73,9 @@ class Model /// \param input_file std::string the file path expressed in UTF-8 /// \return Model the read Model static Model read_from_file(std::string input_file); - + + void merge(const Model &other); + /// Create a new object and add it to the current Model. /// \return ModelObject* a pointer to the new Model ModelObject* add_object(); @@ -133,13 +135,17 @@ class Model /// Repair the ModelObjects of the current Model. /// This function calls repair function on each TriangleMesh of each model object volume void repair(); - + + /// Split the meshes of the ModelObjects into several distinct ModelObjects. + void split(); + /// Center the total bounding box of the instances around a point. /// This transformation works in the XY plane only and no transformation in Z is performed. /// \param point pointf object to center the model instances of model objects around void center_instances_around_point(const Pointf &point); void align_instances_to_origin(); + void align_to_ground(); /// Translate each ModelObject with x, y, z units. /// \param x coordf_t units in the x direction diff --git a/xs/src/libslic3r/PlaceholderParser.cpp b/xs/src/libslic3r/PlaceholderParser.cpp index 675bd1fad..f119df888 100644 --- a/xs/src/libslic3r/PlaceholderParser.cpp +++ b/xs/src/libslic3r/PlaceholderParser.cpp @@ -76,7 +76,7 @@ PlaceholderParser::update_timestamp() } } -void PlaceholderParser::apply_config(const DynamicPrintConfig &config) +void PlaceholderParser::apply_config(const DynamicConfig &config) { t_config_option_keys opt_keys = config.keys(); for (t_config_option_keys::const_iterator i = opt_keys.begin(); i != opt_keys.end(); ++i) { diff --git a/xs/src/libslic3r/PlaceholderParser.hpp b/xs/src/libslic3r/PlaceholderParser.hpp index 0cdf809d5..d41316dca 100644 --- a/xs/src/libslic3r/PlaceholderParser.hpp +++ b/xs/src/libslic3r/PlaceholderParser.hpp @@ -21,7 +21,7 @@ class PlaceholderParser PlaceholderParser(); void update_timestamp(); - void apply_config(const DynamicPrintConfig &config); + void apply_config(const DynamicConfig &config); void apply_env_variables(); void set(const std::string &key, const std::string &value); void set(const std::string &key, int value); diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp index 58623c18c..b40f02e53 100644 --- a/xs/src/libslic3r/Print.cpp +++ b/xs/src/libslic3r/Print.cpp @@ -705,10 +705,13 @@ Print::add_model_object(ModelObject* model_object, int idx) } #ifndef SLIC3RXS + void Print::export_gcode(std::ostream& output, bool quiet) { + // prerequisites this->process(); + if (this->status_cb != nullptr) this->status_cb(90, "Exporting G-Code..."); @@ -718,10 +721,15 @@ Print::export_gcode(std::ostream& output, bool quiet) } void -Print::export_gcode(const std::string& outfile, bool quiet) +Print::export_gcode(std::string outfile, bool quiet) { + // compute the actual output filepath + outfile = this->output_filepath(outfile); + std::ofstream outstream(outfile); this->export_gcode(outstream); + + // TODO: export_gcode() is not ported completely from Perl } diff --git a/xs/src/libslic3r/Print.hpp b/xs/src/libslic3r/Print.hpp index 1c716eaa5..accb67d35 100644 --- a/xs/src/libslic3r/Print.hpp +++ b/xs/src/libslic3r/Print.hpp @@ -233,7 +233,6 @@ class Print PrintObjectPtrs objects; PrintRegionPtrs regions; PlaceholderParser placeholder_parser; - // TODO: status_cb #ifndef SLIC3RXS std::function status_cb {nullptr}; @@ -272,7 +271,7 @@ class Print void export_gcode(std::ostream& output, bool quiet = false); /// Performs a gcode export and then runs post-processing scripts (if any) - void export_gcode(const std::string& filename, bool quiet = false); + void export_gcode(std::string filename, bool quiet = false); /// commands a gcode export to a temporary file and return its name std::string export_gcode(bool quiet = false); diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index 48d43fba5..75cc75dc6 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -1892,10 +1892,89 @@ PrintConfigBase::_handle_legacy(t_config_option_key &opt_key, std::string &value } } -CLIConfigDef::CLIConfigDef() +CLIActionsConfigDef::CLIActionsConfigDef() { ConfigOptionDef* def; + // Actions: + def = this->add("export_obj", coBool); + def->label = __TRANS("Export SVG"); + def->tooltip = __TRANS("Export the model(s) as OBJ."); + def->cli = "export-obj"; + def->default_value = new ConfigOptionBool(false); + + def = this->add("export_pov", coBool); + def->label = __TRANS("Export POV"); + def->tooltip = __TRANS("Export the model as POV-Ray definition."); + def->cli = "export-pov"; + def->default_value = new ConfigOptionBool(false); + + def = this->add("export_svg", coBool); + def->label = __TRANS("Export SVG"); + def->tooltip = __TRANS("Slice the model and export solid slices as SVG."); + def->cli = "export-svg"; + def->default_value = new ConfigOptionBool(false); + + def = this->add("export_sla_svg", coBool); + def->label = __TRANS("Export SVG for SLA"); + def->tooltip = __TRANS("Slice the model and export SLA printing layers as SVG."); + def->cli = "export-sla-svg|sla"; + def->default_value = new ConfigOptionBool(false); + + def = this->add("export_3mf", coBool); + def->label = __TRANS("Export 3MF"); + def->tooltip = __TRANS("Export the model(s) as 3MF."); + def->cli = "export-3mf"; + def->default_value = new ConfigOptionBool(false); + + def = this->add("export_stl", coBool); + def->label = __TRANS("Export STL"); + def->tooltip = __TRANS("Export the model(s) as STL."); + def->cli = "export-stl"; + def->default_value = new ConfigOptionBool(false); + + def = this->add("export_gcode", coBool); + def->label = __TRANS("Export G-code"); + def->tooltip = __TRANS("Slice the model and export toolpaths as G-code."); + def->cli = "export-gcode|gcode|g"; + def->default_value = new ConfigOptionBool(false); + + def = this->add("help", coBool); + def->label = __TRANS("Help"); + def->tooltip = __TRANS("Show this help."); + def->cli = "help"; + def->default_value = new ConfigOptionBool(false); + + def = this->add("help_options", coBool); + def->label = __TRANS("Help (options)"); + def->tooltip = __TRANS("Show the list of configuration options."); + def->cli = "help-options"; + def->default_value = new ConfigOptionBool(false); + + def = this->add("info", coBool); + def->label = __TRANS("Output Model Info"); + def->tooltip = __TRANS("Write information about the model to the console."); + def->cli = "info"; + def->default_value = new ConfigOptionBool(false); + + def = this->add("save", coString); + def->label = __TRANS("Save config file"); + def->tooltip = __TRANS("Save configuration to the specified file."); + def->cli = "save"; + def->default_value = new ConfigOptionString(); +} + +CLITransformConfigDef::CLITransformConfigDef() +{ + ConfigOptionDef* def; + + // Transform options: + def = this->add("align_xy", coPoint); + def->label = __TRANS("Align XY"); + def->tooltip = __TRANS("Align the model to the given point."); + def->cli = "align-xy"; + def->default_value = new ConfigOptionPoint(Pointf(100,100)); + def = this->add("cut", coFloat); def->label = __TRANS("Cut"); def->tooltip = __TRANS("Cut model at the given Z."); @@ -1920,113 +1999,102 @@ CLIConfigDef::CLIConfigDef() def->cli = "cut-y"; def->default_value = new ConfigOptionFloat(0); - def = this->add("export_obj", coBool); - def->label = __TRANS("Export SVG"); - def->tooltip = __TRANS("Export the model as OBJ."); - def->cli = "export-obj"; - def->default_value = new ConfigOptionBool(false); + def = this->add("center", coPoint); + def->label = __TRANS("Center"); + def->tooltip = __TRANS("Center the print around the given center."); + def->cli = "center"; + def->default_value = new ConfigOptionPoint(Pointf(100,100)); + + def = this->add("dont_arrange", coBool); + def->label = __TRANS("Don't arrange"); + def->tooltip = __TRANS("Do not rearrange the given models before merging and keep their original XY coordinates."); + def->cli = "dont-arrange"; - def = this->add("export_pov", coBool); - def->label = __TRANS("Export POV"); - def->tooltip = __TRANS("Export the model as POV-Ray definition."); - def->cli = "export-pov"; - def->default_value = new ConfigOptionBool(false); + def = this->add("duplicate", coInt); + def->label = __TRANS("Duplicate"); + def->tooltip =__TRANS("Multiply copies by this factor."); + def->cli = "duplicate=i"; + def->min = 1; - def = this->add("export_svg", coBool); - def->label = __TRANS("Export SVG"); - def->tooltip = __TRANS("Slice the model and export slices as SVG."); - def->cli = "export-svg"; - def->default_value = new ConfigOptionBool(false); - - def = this->add("export_3mf", coBool); - def->label = __TRANS("Export 3MF"); - def->tooltip = __TRANS("Slice the model and export slices as 3MF."); - def->cli = "export-3mf"; - def->default_value = new ConfigOptionBool(false); - - def = this->add("slice", coBool); - def->label = __TRANS("Slice"); - def->tooltip = __TRANS("Slice the model and export gcode."); - def->cli = "slice"; - def->default_value = new ConfigOptionBool(false); - - def = this->add("help", coBool); - def->label = __TRANS("Help"); - def->tooltip = __TRANS("Show this help."); - def->cli = "help"; - def->default_value = new ConfigOptionBool(false); - - def = this->add("gui", coBool); - def->label = __TRANS("Use GUI"); - def->tooltip = __TRANS("Start the Slic3r GUI."); - def->cli = "gui"; - def->default_value = new ConfigOptionBool(false); - - def = this->add("info", coBool); - def->label = __TRANS("Output Model Info"); - def->tooltip = __TRANS("Write information about the model to the console."); - def->cli = "info"; - def->default_value = new ConfigOptionBool(false); + def = this->add("duplicate_grid", coInts); + def->label = __TRANS("Duplicate by grid"); + def->tooltip = __TRANS("Multiply copies by creating a grid."); + def->cli = "duplicate-grid=i@"; def = this->add("load", coStrings); def->label = __TRANS("Load config file"); def->tooltip = __TRANS("Load configuration from the specified file. It can be used more than once to load options from multiple files."); def->cli = "load"; - def->default_value = new ConfigOptionStrings(); - - def = this->add("output", coString); - def->label = __TRANS("Output File"); - def->tooltip = __TRANS("The file where the output will be written (if not specified, it will be based on the input file)."); - def->cli = "output"; - def->default_value = new ConfigOptionString(""); + + def = this->add("merge", coBool); + def->label = __TRANS("Merge"); + def->tooltip = __TRANS("Arrange the supplied models in a plate and merge them in a single model in order to perform actions once."); + def->cli = "merge|m"; + + def = this->add("repair", coBool); + def->label = __TRANS("Repair"); + def->tooltip = __TRANS("Try to repair any non-manifold meshes (this option is implicitly added whenever we need to slice the model to perform the requested action."); + def->cli = "repair"; def = this->add("rotate", coFloat); def->label = __TRANS("Rotate"); - def->tooltip = __TRANS("Rotation angle around the Z axis in degrees (0-360, default: 0)."); + def->tooltip = __TRANS("Rotation angle around the Z axis in degrees."); def->cli = "rotate"; def->default_value = new ConfigOptionFloat(0); def = this->add("rotate_x", coFloat); def->label = __TRANS("Rotate around X"); - def->tooltip = __TRANS("Rotation angle around the X axis in degrees (0-360, default: 0)."); + def->tooltip = __TRANS("Rotation angle around the X axis in degrees."); def->cli = "rotate-x"; def->default_value = new ConfigOptionFloat(0); def = this->add("rotate_y", coFloat); def->label = __TRANS("Rotate around Y"); - def->tooltip = __TRANS("Rotation angle around the Y axis in degrees (0-360, default: 0)."); + def->tooltip = __TRANS("Rotation angle around the Y axis in degrees."); def->cli = "rotate-y"; def->default_value = new ConfigOptionFloat(0); - def = this->add("save", coString); - def->label = __TRANS("Save config file"); - def->tooltip = __TRANS("Save configuration to the specified file."); - def->cli = "save"; - def->default_value = new ConfigOptionString(); - - def = this->add("scale", coFloat); + def = this->add("scale", coFloatOrPercent); def->label = __TRANS("Scale"); - def->tooltip = __TRANS("Scaling factor (default: 1)."); + def->tooltip = __TRANS("Scaling factor or percentage."); def->cli = "scale"; - def->default_value = new ConfigOptionFloat(1); + def->default_value = new ConfigOptionFloatOrPercent(1, false); + + def = this->add("split", coBool); + def->label = __TRANS("Split"); + def->tooltip = __TRANS("Detect unconnected parts in the given model(s) and split them into separate objects."); + def->cli = "split"; def = this->add("scale_to_fit", coPoint3); def->label = __TRANS("Scale to Fit"); def->tooltip = __TRANS("Scale to fit the given volume."); def->cli = "scale-to-fit"; def->default_value = new ConfigOptionPoint3(Pointf3(0,0,0)); - - def = this->add("center", coPoint3); - def->label = __TRANS("Center"); - def->tooltip = __TRANS("Center the print around the given center (default: 100, 100)."); - def->cli = "center"; - def->default_value = new ConfigOptionPoint(Pointf(100,100)); } -const CLIConfigDef cli_config_def; + +CLIMiscConfigDef::CLIMiscConfigDef() +{ + ConfigOptionDef* def; + + def = this->add("ignore_nonexistent_config", coBool); + def->label = __TRANS("Ignore non-existent config files"); + def->tooltip = __TRANS("Do not fail if a file supplied to --load does not exist."); + def->cli = "ignore-nonexistent-config"; + + def = this->add("output", coString); + def->label = __TRANS("Output File"); + def->tooltip = __TRANS("The file where the output will be written (if not specified, it will be based on the input file)."); + def->cli = "output"; +} + +const CLIActionsConfigDef cli_actions_config_def; +const CLITransformConfigDef cli_transform_config_def; +const CLIMiscConfigDef cli_misc_config_def; std::ostream& print_cli_options(std::ostream& out) { + /* for (const auto& opt : cli_config_def.options) { if (opt.second.cli.size() != 0) { out << "\t" << std::left << std::setw(40) << std::string("--") + opt.second.cli; @@ -2037,6 +2105,7 @@ print_cli_options(std::ostream& out) { } } std::cerr << std::endl; + */ return out; } diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp index 17f629541..0a56d8714 100644 --- a/xs/src/libslic3r/PrintConfig.hpp +++ b/xs/src/libslic3r/PrintConfig.hpp @@ -639,78 +639,33 @@ class SLAPrintConfig }; }; -class CLIConfigDef : public ConfigDef +class CLIActionsConfigDef : public ConfigDef { public: - CLIConfigDef(); + CLIActionsConfigDef(); }; -extern const CLIConfigDef cli_config_def; - -class CLIConfig - : public virtual ConfigBase, public StaticConfig +class CLITransformConfigDef : public ConfigDef { public: - ConfigOptionFloat cut; - ConfigOptionPoint cut_grid; - ConfigOptionFloat cut_x; - ConfigOptionFloat cut_y; - ConfigOptionBool export_obj; - ConfigOptionBool export_pov; - ConfigOptionBool export_svg; - ConfigOptionBool export_3mf; - ConfigOptionBool gui; - ConfigOptionBool info; - ConfigOptionBool help; - ConfigOptionStrings load; - ConfigOptionString output; - ConfigOptionFloat rotate; - ConfigOptionFloat rotate_x; - ConfigOptionFloat rotate_y; - ConfigOptionString save; - ConfigOptionFloat scale; - ConfigOptionPoint3 scale_to_fit; - ConfigOptionPoint center; - ConfigOptionBool slice; - ConfigOptionBool threads; - - CLIConfig() : ConfigBase(), StaticConfig() { - this->def = &cli_config_def; - this->set_defaults(); - }; - - virtual ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false) { - OPT_PTR(cut); - OPT_PTR(cut_grid); - OPT_PTR(cut_x); - OPT_PTR(cut_y); - OPT_PTR(export_obj); - OPT_PTR(export_pov); - OPT_PTR(export_svg); - OPT_PTR(export_3mf); - OPT_PTR(gui); - OPT_PTR(help); - OPT_PTR(info); - OPT_PTR(load); - OPT_PTR(output); - OPT_PTR(rotate); - OPT_PTR(rotate_x); - OPT_PTR(rotate_y); - OPT_PTR(save); - OPT_PTR(scale); - OPT_PTR(scale_to_fit); - OPT_PTR(slice); - OPT_PTR(threads); - - return NULL; - }; + CLITransformConfigDef(); }; +class CLIMiscConfigDef : public ConfigDef +{ + public: + CLIMiscConfigDef(); +}; + +extern const CLIActionsConfigDef cli_actions_config_def; +extern const CLITransformConfigDef cli_transform_config_def; +extern const CLIMiscConfigDef cli_misc_config_def; + /// Iterate through all of the print options and write them to a stream. std::ostream& print_print_options(std::ostream& out); /// Iterate through all of the CLI options and write them to a stream. -std::ostream& -print_cli_options(std::ostream& out); +std::ostream& print_cli_options(std::ostream& out); + } #endif diff --git a/xs/src/libslic3r/SLAPrint.hpp b/xs/src/libslic3r/SLAPrint.hpp index 6c6028abb..d35b38c9a 100644 --- a/xs/src/libslic3r/SLAPrint.hpp +++ b/xs/src/libslic3r/SLAPrint.hpp @@ -38,12 +38,12 @@ class SLAPrint }; std::vector sm_pillars; - SLAPrint(Model* _model) : model(_model) {}; + SLAPrint(const Model* _model) : model(_model) {}; void slice(); void write_svg(const std::string &outputfile) const; private: - Model* model; + const Model* model; BoundingBoxf3 bb; void _infill_layer(size_t i, const Fill* fill); diff --git a/xs/src/libslic3r/SimplePrint.cpp b/xs/src/libslic3r/SimplePrint.cpp new file mode 100644 index 000000000..d305ff523 --- /dev/null +++ b/xs/src/libslic3r/SimplePrint.cpp @@ -0,0 +1,49 @@ +#include "SimplePrint.hpp" +#include "ClipperUtils.hpp" +#include + +namespace Slic3r { + +void +SimplePrint::set_model(const Model &model) { + this->_model = model; + + // make method idempotent so that the object is reusable + this->_print.clear_objects(); + + // make sure all objects have at least one defined instance + this->_model.add_default_instances(); + + // align to z = 0 + for (ModelObject* o : this->_model.objects) + o->translate(0, 0, -o->bounding_box().min.z); + + if (this->center) { + Polygon bed_polygon{ scale(this->_print.config.bed_shape.values) }; + this->_model.center_instances_around_point(Slic3r::Pointf::new_unscale(bed_polygon.centroid())); + } + + for (ModelObject* o : this->_model.objects) { + this->_print.auto_assign_extruders(o); + this->_print.add_model_object(o); + } +} + +void +SimplePrint::export_gcode(std::string outfile) { + this->_print.status_cb = this->status_cb; + this->_print.validate(); + this->_print.export_gcode(outfile); + + // check that all parts fit in bed shape, and warn if they don't + // TODO: use actual toolpaths instead of total bounding box + Polygon bed_polygon{ scale(this->_print.config.bed_shape.values) }; + if (!diff(this->_print.bounding_box().polygon(), bed_polygon).empty()) { + std::cout << "Warning: the supplied parts might not fit in the configured bed shape. " + << "You might want to review the result before printing." << std::endl; + } + + this->_print.status_cb = nullptr; +} + +} diff --git a/xs/src/libslic3r/SimplePrint.hpp b/xs/src/libslic3r/SimplePrint.hpp new file mode 100644 index 000000000..313718da9 --- /dev/null +++ b/xs/src/libslic3r/SimplePrint.hpp @@ -0,0 +1,29 @@ +#ifndef slic3r_SimplePrint_hpp_ +#define slic3r_SimplePrint_hpp_ + +#include "libslic3r.h" +#include "Point.hpp" +#include "Print.hpp" + +namespace Slic3r { + +class SimplePrint { + public: + bool center{true}; + std::function status_cb {nullptr}; + + bool apply_config(config_ptr config) { return this->_print.apply_config(config); } + bool apply_config(DynamicPrintConfig config) { return this->_print.apply_config(config); } + double total_used_filament() const { return this->_print.total_used_filament; } + double total_extruded_volume() const { return this->_print.total_extruded_volume; } + void set_model(const Model &model); + void export_gcode(std::string outfile); + + private: + Model _model; + Print _print; +}; + +} + +#endif diff --git a/xs/src/libslic3r/TriangleMesh.cpp b/xs/src/libslic3r/TriangleMesh.cpp index ed2c9c995..5950c5382 100644 --- a/xs/src/libslic3r/TriangleMesh.cpp +++ b/xs/src/libslic3r/TriangleMesh.cpp @@ -147,22 +147,22 @@ TriangleMesh::ReadSTLFile(const std::string &input_file) { } void -TriangleMesh::write_ascii(const std::string &output_file) +TriangleMesh::write_ascii(const std::string &output_file) const { #ifdef BOOST_WINDOWS - stl_write_ascii(&this->stl, boost::nowide::widen(output_file).c_str(), ""); + stl_write_ascii(const_cast(&this->stl), boost::nowide::widen(output_file).c_str(), ""); #else - stl_write_ascii(&this->stl, output_file.c_str(), ""); + stl_write_ascii(const_cast(&this->stl), output_file.c_str(), ""); #endif } void -TriangleMesh::write_binary(const std::string &output_file) +TriangleMesh::write_binary(const std::string &output_file) const { #ifdef BOOST_WINDOWS - stl_write_binary(&this->stl, boost::nowide::widen(output_file).c_str(), ""); + stl_write_binary(const_cast(&this->stl), boost::nowide::widen(output_file).c_str(), ""); #else - stl_write_binary(&this->stl, output_file.c_str(), ""); + stl_write_binary(const_cast(&this->stl), output_file.c_str(), ""); #endif } @@ -272,13 +272,13 @@ TriangleMesh::facets_count() const } void -TriangleMesh::WriteOBJFile(const std::string &output_file) { - stl_generate_shared_vertices(&stl); +TriangleMesh::WriteOBJFile(const std::string &output_file) const { + stl_generate_shared_vertices(const_cast(&this->stl)); #ifdef BOOST_WINDOWS - stl_write_obj(&stl, boost::nowide::widen(output_file).c_str()); + stl_write_obj(const_cast(&this->stl), boost::nowide::widen(output_file).c_str()); #else - stl_write_obj(&stl, output_file.c_str()); + stl_write_obj(const_cast(&this->stl), output_file.c_str()); #endif } diff --git a/xs/src/libslic3r/TriangleMesh.hpp b/xs/src/libslic3r/TriangleMesh.hpp index 84104e5b8..96728911c 100644 --- a/xs/src/libslic3r/TriangleMesh.hpp +++ b/xs/src/libslic3r/TriangleMesh.hpp @@ -55,13 +55,13 @@ class TriangleMesh void swap(TriangleMesh &other); ~TriangleMesh(); void ReadSTLFile(const std::string &input_file); - void write_ascii(const std::string &output_file); - void write_binary(const std::string &output_file); + void write_ascii(const std::string &output_file) const; + void write_binary(const std::string &output_file) const; void repair(); void check_topology(); float volume(); bool is_manifold() const; - void WriteOBJFile(const std::string &output_file); + void WriteOBJFile(const std::string &output_file) const; void scale(float factor); void scale(const Pointf3 &versor);