#include "slic3r.hpp" #include "Geometry.hpp" #include "IO.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 #include #ifdef USE_WX #include "GUI/GUI.hpp" #endif using namespace Slic3r; #ifndef BUILD_TEST int main(int argc, char **argv) { return CLI().run(argc, argv); } #endif // BUILD_TEST 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 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 (!this->config.read_cli(argc, argv, &this->input_files, &opt_order)) { this->print_help(); return 1; } // 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); } // load config files supplied via --load for (auto const &file : config.getStrings("load")) { if (!boost::filesystem::exists(file)) { 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::cerr << "Error while reading config file: " << e.what() << std::endl; exit(1); } c.normalize(); this->print_config.apply(c); } // apply command line options to a more specific DynamicPrintConfig which provides normalize() // (command line options override --load files) this->print_config.apply(config, true); this->print_config.normalize(); // create a static (full) print config to be used in our logic this->full_print_config.apply(this->print_config); // validate config try { this->full_print_config.validate(); } catch (InvalidOptionException &e) { boost::nowide::cerr << e.what() << std::endl; return 1; } // read input file(s) if any for (auto const &file : input_files) { Model model; try { model = Model::read_from_file(file); } catch (std::exception &e) { boost::nowide::cerr << file << ": " << e.what() << std::endl; exit(1); } if (model.objects.empty()) { boost::nowide::cerr << "Error: file is empty: " << file << std::endl; continue; } this->models.push_back(model); } // 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); // Rearrange instances unless --dont-arrange is supplied if (!this->config.getBool("dont_arrange")) { m.add_default_instances(); const BoundingBoxf bb{ this->full_print_config.bed_shape.values }; m.arrange_objects( this->full_print_config.min_object_distance(), // if we are going to use the merged model for printing, honor // the configured print bed for arranging, otherwise do it freely this->has_print_action() ? &bb : nullptr ); } 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.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.add_default_instances(); // this affects instances: model.center_instances_around_point(config.opt("center")->value); // this affects volumes: 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() }; // this affects volumes: model.translate(-(bb.min.x - p.x), -(bb.min.y - p.y), -bb.min.z); } } else if (opt_key == "dont_arrange") { // do nothing - this option alters other transform options } else if (opt_key == "rotate") { for (auto &model : this->models) for (auto &o : model.objects) // this affects volumes: 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) // this affects volumes: 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) // this affects volumes: o->rotate(Geometry::deg2rad(config.getFloat(opt_key)), Y); } else if (opt_key == "scale") { for (auto &model : this->models) for (auto &o : model.objects) // this affects volumes: 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) // this affects volumes: 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); } } // 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); } // 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; } } // 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: 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 == "help_options") { this->print_help(true); } else if (opt_key == "save") { this->print_config.save(config.getString("save")); } else if (opt_key == "info") { // --info works on unrepaired model for (Model &model : this->models) { model.add_default_instances(); model.print_info(); } } else if (opt_key == "export_stl") { for (auto &model : this->models) model.add_default_instances(); this->export_models(IO::STL); } else if (opt_key == "export_obj") { for (auto &model : this->models) model.add_default_instances(); this->export_models(IO::OBJ); } else if (opt_key == "export_pov") { for (auto &model : this->models) model.add_default_instances(); 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) { // If all objects have defined instances, their relative positions will be // honored when printing (they will be only centered, unless --dont-arrange // is supplied); if any object has no instances, it will get a default one // and all instances will be rearranged (unless --dont-arrange is supplied). SimplePrint print; print.status_cb = [](int ln, const std::string& msg) { boost::nowide::cout << msg << std::endl; }; print.apply_config(this->print_config); print.arrange = !this->config.getBool("dont_arrange"); 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); try { print.export_gcode(outfile); } catch (std::runtime_error &e) { boost::nowide::cerr << e.what() << std::endl; return 1; } 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->autosave = this->config.getString("autosave"); gui->datadir = this->config.getString("datadir"); GUI::App::SetInstance(gui); wxEntry(argc, argv); #else std::cout << "GUI support has not been built." << "\n"; #endif } return 0; } void CLI::print_help(bool include_print_options) const { boost::nowide::cout << "Slic3r " << SLIC3R_VERSION << " (build commit: " << BUILD_COMMIT << ")" << std::endl << "https://slic3r.org/ - https://github.com/slic3r/Slic3r" << std::endl << std::endl << "Usage: slic3r [ ACTIONS ] [ TRANSFORM ] [ OPTIONS ] [ file.stl ... ]" << std::endl << std::endl << "Actions:" << std::endl; cli_actions_config_def.print_cli_help(boost::nowide::cout, false); boost::nowide::cout << std::endl << "Transform options:" << std::endl; cli_transform_config_def.print_cli_help(boost::nowide::cout, false); boost::nowide::cout << std::endl << "Other options:" << std::endl; cli_misc_config_def.print_cli_help(boost::nowide::cout, false); if (include_print_options) { boost::nowide::cout << std::endl; print_config_def.print_cli_help(boost::nowide::cout, true); } else { boost::nowide::cout << std::endl << "Run --help-options to see the full listing of print/G-code options." << std::endl; } } void CLI::export_models(IO::ExportFormat format) { for (const Model& model : this->models) { 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; }