From c8c85d38400a69afcb07249d456ed965aa385e0f Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 14 Jan 2025 09:18:11 +0100 Subject: [PATCH 1/9] PrusaSlicer reorganization: WIP * Deleted old unused code. * Functions related to profiles sharing are removed from CLI and used as a free functions now. * Code blocks related to transformation and actions are extracted into separate free functions. * Added CLIParams struct to hold current parameters needed to process cli options --- src/PrusaSlicer.cpp | 1467 ++++++++++++++++++++++--------------------- src/PrusaSlicer.hpp | 43 +- 2 files changed, 755 insertions(+), 755 deletions(-) diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index ba41e05fa0..87a5553386 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -78,12 +78,623 @@ using namespace Slic3r; +static void print_help(bool include_print_options = false, PrinterTechnology printer_technology = ptAny) +{ + boost::nowide::cout + << SLIC3R_BUILD_ID << " " << "based on Slic3r" +#ifdef SLIC3R_GUI + << " (with GUI support)" +#else /* SLIC3R_GUI */ + << " (without GUI support)" +#endif /* SLIC3R_GUI */ + << std::endl + << "https://github.com/prusa3d/PrusaSlicer" << std::endl << std::endl + << "Usage: prusa-slicer [ ACTIONS ] [ TRANSFORM ] [ OPTIONS ] [ file.stl ... ]" << std::endl + << std::endl + << "Actions:" << std::endl; + cli_actions_config_def.print_cli_help(boost::nowide::cout, false); + // << std::endl + // << "Profiles sharing options:" << std::endl; + cli_profiles_sharing_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); + + boost::nowide::cout + << std::endl + << "Print options are processed in the following order:" << std::endl + << "\t1) Config keys from the command line, for example --fill-pattern=stars" << std::endl + << "\t (highest priority, overwrites everything below)" << std::endl + << "\t2) Config files loaded with --load" << std::endl + << "\t3) Config values loaded from amf or 3mf files" << std::endl; + + if (include_print_options) { + boost::nowide::cout << std::endl; + print_config_def.print_cli_help(boost::nowide::cout, true, [printer_technology](const ConfigOptionDef& def) + { return printer_technology == ptAny || def.printer_technology == ptAny || printer_technology == def.printer_technology; }); + } + else { + boost::nowide::cout + << std::endl + << "Run --help-fff / --help-sla to see the full listing of print options." << std::endl; + } +} + static PrinterTechnology get_printer_technology(const DynamicConfig &config) { const ConfigOptionEnum *opt = config.option>("printer_technology"); return (opt == nullptr) ? ptUnknown : opt->value; } +// may be "validate_and_apply_printer_technology" will be better? +static bool can_apply_printer_technology(PrinterTechnology& printer_technology, const PrinterTechnology& other_printer_technology) +{ + if (printer_technology == ptUnknown) { + printer_technology = other_printer_technology; + return true; + } + + bool invalid_other_pt = printer_technology != other_printer_technology && other_printer_technology != ptUnknown; + + if (invalid_other_pt) + boost::nowide::cerr << "Mixing configurations for FFF and SLA technologies" << std::endl; + + return !invalid_other_pt; +} + +static bool process_profiles_sharing(const CliInParams& in) +{ + if (in.profiles_sharing.empty()) + return false; + + std::string ret; + for (auto const& opt_key : in.profiles_sharing) { + if (opt_key == "query-printer-models") { + ret = Slic3r::get_json_printer_models(get_printer_technology(in.config)); + break; + } + else + if (opt_key == "query-print-filament-profiles") { + const std::string printer_profile = in.config.opt_string("printer-profile"); + if (printer_profile.empty()) { + boost::nowide::cerr << opt_key << " error: This action requires set 'printer-profile' option" << std::endl; + return true; + } + + ret = Slic3r::get_json_print_filament_profiles(printer_profile); + if (ret.empty()) { + boost::nowide::cerr << opt_key << " error: Printer profile '" << printer_profile << + "' wasn't found among installed printers." << std::endl << + "Or the request can be wrong." << std::endl; + return true; + } + break; + } + else { + boost::nowide::cerr << "error: option not implemented yet: " << opt_key << std::endl; + break; + } + } + + if (ret.empty()) { + boost::nowide::cerr << "Wrong request" << std::endl; + return true; + } + + // use --output when available + + std::string cmdline_param = in.config.opt_string("output"); + if (cmdline_param.empty()) + printf("%s", ret.c_str()); + else { + // if we were supplied a directory, use it and append our automatically generated filename + boost::filesystem::path cmdline_path(cmdline_param); + boost::filesystem::path proposed_path = boost::filesystem::path(Slic3r::resources_dir()) / "out.json"; + if (boost::filesystem::is_directory(cmdline_path)) + proposed_path = (cmdline_path / proposed_path.filename()); + else if (cmdline_path.extension().empty()) + proposed_path = cmdline_path.replace_extension("json"); + else + proposed_path = cmdline_path; + const std::string file = proposed_path.string(); + + boost::nowide::ofstream c; + c.open(file, std::ios::out | std::ios::trunc); + c << ret << std::endl; + c.close(); + + boost::nowide::cout << "Output for your request is written into " << file << std::endl; + } + + return true; +} + +static void print_config_substitutions(const ConfigSubstitutions& config_substitutions, const std::string& file) +{ + if (config_substitutions.empty()) + return; + boost::nowide::cout << "The following configuration values were substituted when loading \" << file << \":\n"; + for (const ConfigSubstitution& subst : config_substitutions) + boost::nowide::cout << "\tkey = \"" << subst.opt_def->opt_key << "\"\t loaded = \"" << subst.old_value << "\tsubstituted = \"" << subst.new_value->serialize() << "\"\n"; +} + +static bool apply_model_and_print_config_from_file(const std::string& file, Model& model, CliOutParams& cli) +{ + // When loading an AMF or 3MF, config is imported as well, including the printer technology. + DynamicPrintConfig config; + ConfigSubstitutionContext config_substitutions_ctxt(cli.config_substitution_rule); + //FIXME should we check the version here? // | Model::LoadAttribute::CheckVersion ? + model = FileReader::load_model_with_config(file, &config, &config_substitutions, prusaslicer_generator_version, FileReader::LoadAttribute::AddDefaultInstances); + if (!can_apply_printer_technology(cli.printer_technology, get_printer_technology(config))) + return false; + + print_config_substitutions(config_substitutions_ctxt.substitutions, file); + + // config is applied to cli.print_config before the current m_config values. + config += std::move(cli.print_config); + cli.print_config = std::move(config); + + return true; +} + +static bool has_print_action(const DynamicPrintAndCLIConfig& config) +{ + return config.opt_bool("export_gcode") || config.opt_bool("export_sla"); +} + +static bool process_transform( CliInParams& in, + CliOutParams& out, + const arr2::ArrangeBed& bed, + const arr2::ArrangeSettingsView& arrange_cfg) +{ + for (auto const& opt_key : in.transforms) { + if (opt_key == "merge") { + Model m; + for (auto& model : out.models) + for (ModelObject* o : model.objects) + m.add_object(*o); + // Rearrange instances unless --dont-arrange is supplied + if (!in.config.opt_bool("dont_arrange")) { + m.add_default_instances(); + if (has_print_action(in.config)) + arrange_objects(m, bed, arrange_cfg); + else + arrange_objects(m, arr2::InfiniteBed{}, arrange_cfg); + } + out.models.clear(); + out.models.emplace_back(std::move(m)); + } + else if (opt_key == "duplicate") { + for (auto& model : out.models) { + const bool all_objects_have_instances = std::none_of( + model.objects.begin(), model.objects.end(), + [](ModelObject* o) { return o->instances.empty(); } + ); + + int dups = in.config.opt_int("duplicate"); + if (!all_objects_have_instances) model.add_default_instances(); + + try { + if (dups > 1) { + // if all input objects have defined position(s) apply duplication to the whole model + duplicate(model, size_t(dups), bed, arrange_cfg); + } + else { + arrange_objects(model, bed, arrange_cfg); + } + } + catch (std::exception& ex) { + boost::nowide::cerr << "error: " << ex.what() << std::endl; + return false; + } + } + } + else if (opt_key == "duplicate_grid") { + std::vector& ints = in.config.option("duplicate_grid")->values; + const int x = ints.size() > 0 ? ints.at(0) : 1; + const int y = ints.size() > 1 ? ints.at(1) : 1; + FullPrintConfig fff_print_config; + const double distance = fff_print_config.duplicate_distance.value; + for (auto& model : out.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") { + out.user_center_specified = true; + for (auto& model : out.models) { + model.add_default_instances(); + // this affects instances: + model.center_instances_around_point(in.config.option("center")->value); + // this affects volumes: + //FIXME Vojtech: Who knows why the complete model should be aligned with Z as a single rigid body? + //model.align_to_ground(); + BoundingBoxf3 bbox; + for (ModelObject* model_object : model.objects) + // We are interested into the Z span only, therefore it is sufficient to measure the bounding box of the 1st instance only. + bbox.merge(model_object->instance_bounding_box(0, false)); + for (ModelObject* model_object : model.objects) + for (ModelInstance* model_instance : model_object->instances) + model_instance->set_offset(Z, model_instance->get_offset(Z) - bbox.min.z()); + } + } + else if (opt_key == "align_xy") { + const Vec2d& p = in.config.option("align_xy")->value; + for (auto& model : out.models) { + BoundingBoxf3 bb = model.bounding_box_exact(); + // 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 == "ensure_on_bed") { + // do nothing, the value is used later + } + else if (opt_key == "rotate") { + for (auto& model : out.models) + for (auto& o : model.objects) + // this affects volumes: + o->rotate(Geometry::deg2rad(in.config.opt_float(opt_key)), Z); + } + else if (opt_key == "rotate_x") { + for (auto& model : out.models) + for (auto& o : model.objects) + // this affects volumes: + o->rotate(Geometry::deg2rad(in.config.opt_float(opt_key)), X); + } + else if (opt_key == "rotate_y") { + for (auto& model : out.models) + for (auto& o : model.objects) + // this affects volumes: + o->rotate(Geometry::deg2rad(in.config.opt_float(opt_key)), Y); + } + else if (opt_key == "scale") { + for (auto& model : out.models) + for (auto& o : model.objects) + // this affects volumes: + o->scale(in.config.get_abs_value(opt_key, 1)); + } + else if (opt_key == "scale_to_fit") { + const Vec3d& opt = in.config.opt(opt_key)->value; + if (opt.x() <= 0 || opt.y() <= 0 || opt.z() <= 0) { + boost::nowide::cerr << "--scale-to-fit requires a positive volume" << std::endl; + return false; + } + for (auto& model : out.models) + for (auto& o : model.objects) + // this affects volumes: + o->scale_to_fit(opt); + } + else if (opt_key == "cut") { + std::vector new_models; + for (auto& model : out.models) { + model.translate(0, 0, -model.bounding_box_exact().min.z()); // align to z = 0 + size_t num_objects = model.objects.size(); + for (size_t i = 0; i < num_objects; ++i) { + Cut cut(model.objects.front(), 0, Geometry::translation_transform(in.config.opt_float("cut") * Vec3d::UnitZ()), + ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::PlaceOnCutUpper); + auto cut_objects = cut.perform_with_plane(); + for (ModelObject* obj : cut_objects) + model.add_object(*obj); + model.delete_object(size_t(0)); + } + } + + // TODO: copy less stuff around using pointers + out.models = new_models; + + if (in.actions.empty()) + in.actions.push_back("export_stl"); + } + else if (opt_key == "split") { + for (Model& model : out.models) { + size_t num_objects = model.objects.size(); + for (size_t i = 0; i < num_objects; ++i) { + ModelObjectPtrs new_objects; + model.objects.front()->split(&new_objects); + model.delete_object(size_t(0)); + } + } + } + else if (opt_key == "delete-after-load") { + out.delete_after_load = true; + } + else { + boost::nowide::cerr << "error: option not implemented yet: " << opt_key << std::endl; + return false; + } + } + + // All transforms have been dealt with. Now ensure that the objects are on bed. + // (Unless the user said otherwise.) + if (in.config.opt_bool("ensure_on_bed")) + for (auto& model : out.models) + for (auto& o : model.objects) + o->ensure_on_bed(); + + return true; +} + + +static std::string output_filepath(const Model& model, IO::ExportFormat format, const std::string cmdline_param) +{ + std::string ext; + switch (format) { + case IO::OBJ: ext = ".obj"; break; + case IO::STL: ext = ".stl"; break; + case IO::TMF: ext = ".3mf"; break; + default: assert(false); break; + }; + auto proposed_path = boost::filesystem::path(model.propose_export_file_name_and_path(ext)); + // use --output when available + if (!cmdline_param.empty()) { + // if we were supplied a directory, use it and append our automatically generated filename + boost::filesystem::path cmdline_path(cmdline_param); + if (boost::filesystem::is_directory(cmdline_path)) + proposed_path = cmdline_path / proposed_path.filename(); + else + proposed_path = cmdline_path; + } + return proposed_path.string(); +} + +static bool export_models(std::vector& models, IO::ExportFormat format, const std::string cmdline_param) +{ + for (Model& model : models) { + const std::string path = output_filepath(model, format, cmdline_param); + bool success = false; + switch (format) { + case IO::OBJ: success = Slic3r::store_obj(path.c_str(), &model); break; + case IO::STL: success = Slic3r::store_stl(path.c_str(), &model, true); break; + case IO::TMF: success = Slic3r::store_3mf(path.c_str(), &model, nullptr, false); break; + default: assert(false); break; + } + if (success) + std::cout << "File exported to " << path << std::endl; + else { + std::cerr << "File export to " << path << " failed" << std::endl; + return false; + } + } + return true; +} + +static bool process_actions( CliInParams& in, + CliOutParams& out, + const arr2::ArrangeBed& bed, + const arr2::ArrangeSettingsView& arrange_cfg) +{ + + for (auto const& opt_key : in.actions) { + if (opt_key == "help") { + print_help(); + } + else if (opt_key == "help_fff") { + print_help(true, ptFFF); + } + else if (opt_key == "help_sla") { + print_help(true, ptSLA); + } + else if (opt_key == "save") { + //FIXME check for mixing the FFF / SLA parameters. + // or better save fff_print_config vs. sla_print_config + out.print_config.save(in.config.opt_string("save")); + } + else if (opt_key == "info") { + // --info works on unrepaired model + for (Model& model : out.models) { + model.add_default_instances(); + model.print_info(); + } + } + else if (opt_key == "export_stl") { + for (auto& model : out.models) + model.add_default_instances(); + if (!export_models(out.models, IO::STL, in.config.opt_string("output"))) + return 1; + } + else if (opt_key == "export_obj") { + for (auto& model : out.models) + model.add_default_instances(); + if (!export_models(out.models, IO::OBJ, in.config.opt_string("output"))) + return 1; + } + else if (opt_key == "export_3mf") { + if (!export_models(out.models, IO::TMF, in.config.opt_string("output"))) + return 1; + } + else if (opt_key == "export_gcode" || opt_key == "export_sla" || opt_key == "slice") { + if (opt_key == "export_gcode" && out.printer_technology == ptSLA) { + boost::nowide::cerr << "error: cannot export G-code for an FFF configuration" << std::endl; + return 1; + } + else if (opt_key == "export_sla" && out.printer_technology == ptFFF) { + boost::nowide::cerr << "error: cannot export SLA slices for a SLA configuration" << std::endl; + return 1; + } + // Make a copy of the model if the current action is not the last action, as the model may be + // modified by the centering and such. + Model model_copy; + bool make_copy = &opt_key != &in.actions.back(); + for (Model& model_in : out.models) { + if (make_copy) + model_copy = model_in; + Model& model = make_copy ? model_copy : model_in; + // 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). + std::string outfile = in.config.opt_string("output"); + Print fff_print; + SLAPrint sla_print; + sla_print.set_status_callback( + [](const PrintBase::SlicingStatus& s) + { + if (s.percent >= 0) { // FIXME: is this sufficient? + printf("%3d%s %s\n", s.percent, "% =>", s.text.c_str()); + std::fflush(stdout); + } + }); + + PrintBase* print = (out.printer_technology == ptFFF) ? static_cast(&fff_print) : static_cast(&sla_print); + if (!in.config.opt_bool("dont_arrange")) { + if (out.user_center_specified) { + Vec2d c = in.config.option("center")->value; + arrange_objects(model, arr2::InfiniteBed{ scaled(c) }, arrange_cfg); + } + else + arrange_objects(model, bed, arrange_cfg); + } + if (out.printer_technology == ptFFF) { + for (auto* mo : model.objects) + fff_print.auto_assign_extruders(mo); + } + print->apply(model, out.print_config); + std::string err = print->validate(); + if (!err.empty()) { + boost::nowide::cerr << err << std::endl; + return 1; + } + if (print->empty()) + boost::nowide::cout << "Nothing to print for " << outfile << " . Either the print is empty or no object is fully inside the print volume." << std::endl; + else + try { + std::string outfile_final; + print->process(); + if (out.printer_technology == ptFFF) { + + + + + + std::function thumbnail_generator_cli; + if (!fff_print.model().objects.empty() && boost::iends_with(fff_print.model().objects.front()->input_file, ".3mf")) { + std::string filename = fff_print.model().objects.front()->input_file; + thumbnail_generator_cli = [filename](const ThumbnailsParams&) { + ThumbnailsList list_out; + + mz_zip_archive archive; + mz_zip_zero_struct(&archive); + + if (!open_zip_reader(&archive, filename)) + return list_out; + mz_uint num_entries = mz_zip_reader_get_num_files(&archive); + mz_zip_archive_file_stat stat; + + int index = mz_zip_reader_locate_file(&archive, "Metadata/thumbnail.png", nullptr, 0); + if (index < 0 || !mz_zip_reader_file_stat(&archive, index, &stat)) + return list_out; + std::string buffer; + buffer.resize(int(stat.m_uncomp_size)); + mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, buffer.data(), (size_t)stat.m_uncomp_size, 0); + if (res == 0) + return list_out; + close_zip_reader(&archive); + + std::vector data; + unsigned width = 0; + unsigned height = 0; + png::decode_png(buffer, data, width, height); + + { + // Flip the image vertically so it matches the convention in Thumbnails generator. + const int row_size = width * 4; // Each pixel is 4 bytes (RGBA) + std::vector temp_row(row_size); + for (int i = 0; i < height / 2; ++i) { + unsigned char* top_row = &data[i * row_size]; + unsigned char* bottom_row = &data[(height - i - 1) * row_size]; + std::copy(bottom_row, bottom_row + row_size, temp_row.begin()); + std::copy(top_row, top_row + row_size, bottom_row); + std::copy(temp_row.begin(), temp_row.end(), top_row); + } + } + + ThumbnailData th; + th.set(width, height); + th.pixels = data; + list_out.push_back(th); + return list_out; + }; + } + + + + + + // The outfile is processed by a PlaceholderParser. + outfile = fff_print.export_gcode(outfile, nullptr, thumbnail_generator_cli); + outfile_final = fff_print.print_statistics().finalize_output_path(outfile); + } + else { + outfile = sla_print.output_filepath(outfile); + // We need to finalize the filename beforehand because the export function sets the filename inside the zip metadata + outfile_final = sla_print.print_statistics().finalize_output_path(outfile); + sla_print.export_print(outfile_final); + } + if (outfile != outfile_final) { + if (Slic3r::rename_file(outfile, outfile_final)) { + boost::nowide::cerr << "Renaming file " << outfile << " to " << outfile_final << " failed" << std::endl; + return false; + } + outfile = outfile_final; + } + // Run the post-processing scripts if defined. + run_post_process_scripts(outfile, fff_print.full_print_config()); + boost::nowide::cout << "Slicing result exported to " << outfile << std::endl; + } + catch (const std::exception& ex) { + boost::nowide::cerr << ex.what() << std::endl; + return false; + } +/* + print.center = ! in.config.has("center") + && ! in.config.has("align_xy") + && ! in.config.opt_bool("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 false; + } + } + + return true; +} + + int CLI::run(int argc, char **argv) { // Mark the main thread for the debugger and for runtime checks. @@ -113,30 +724,35 @@ int CLI::run(int argc, char **argv) "You may need to reconfigure the missing locales, likely by running the \"locale-gen\" and \"dpkg-reconfigure locales\" commands.\n" #endif SLIC3R_APP_NAME " will now terminate.\n\n") + ex.what(); - #if defined(_WIN32) && defined(SLIC3R_GUI) - if (m_actions.empty()) - // Empty actions means Slicer is executed in the GUI mode. Show a GUI message. - MessageBoxA(NULL, text.c_str(), caption.c_str(), MB_OK | MB_ICONERROR); - #endif +#if defined(_WIN32) && defined(SLIC3R_GUI) + MessageBoxA(NULL, text.c_str(), caption.c_str(), MB_OK | MB_ICONERROR); +#endif boost::nowide::cerr << text.c_str() << std::endl; return 1; } - if (! this->setup(argc, argv)) + CliInParams cli_in; + if (! this->setup(argc, argv, cli_in)) return 1; + if (process_profiles_sharing(cli_in)) + return 1; + + CliOutParams cli_out; + + /* ysFIXME it looks like m_extra_config is a redundant parametr + * because of m_extra_config is empty from the very beginning and is applied with ignorring of the non-existing options + * m_extra_config.apply(m_config, true); m_extra_config.normalize_fdm(); - - PrinterTechnology printer_technology = get_printer_technology(m_config); + */ - bool start_gui = m_actions.empty() && + cli_out.printer_technology = get_printer_technology(cli_in.config); + + bool start_gui = cli_in.actions.empty() && // cutting transformations are setting an "export" action. - std::find(m_transforms.begin(), m_transforms.end(), "cut") == m_transforms.end() && - std::find(m_transforms.begin(), m_transforms.end(), "cut_x") == m_transforms.end() && - std::find(m_transforms.begin(), m_transforms.end(), "cut_y") == m_transforms.end(); + std::find(cli_in.transforms.begin(), cli_in.transforms.end(), "cut") == cli_in.transforms.end(); bool start_downloader = false; - bool delete_after_load = false; std::string download_url; bool start_as_gcodeviewer = #ifdef _WIN32 @@ -146,13 +762,13 @@ int CLI::run(int argc, char **argv) boost::algorithm::iends_with(boost::filesystem::path(argv[0]).filename().string(), "gcodeviewer"); #endif // _WIN32 - const std::vector &load_configs = m_config.option("load", true)->values; - const ForwardCompatibilitySubstitutionRule config_substitution_rule = m_config.option>("config_compatibility", true)->value; + const std::vector &load_configs = cli_in.config.option("load", true)->values; + cli_out.config_substitution_rule = cli_in.config.option>("config_compatibility", true)->value; // load config files supplied via --load for (auto const &file : load_configs) { if (! boost::filesystem::exists(file)) { - if (m_config.opt_bool("ignore_nonexistent_config")) { + if (cli_in.config.opt_bool("ignore_nonexistent_config")) { continue; } else { boost::nowide::cerr << "No such file: " << file << std::endl; @@ -162,53 +778,64 @@ int CLI::run(int argc, char **argv) DynamicPrintConfig config; ConfigSubstitutions config_substitutions; try { - config_substitutions = config.load(file, config_substitution_rule); + config_substitutions = config.load(file, cli_out.config_substitution_rule); } catch (std::exception &ex) { boost::nowide::cerr << "Error while reading config file \"" << file << "\": " << ex.what() << std::endl; return 1; } - if (! config_substitutions.empty()) { - boost::nowide::cout << "The following configuration values were substituted when loading \" << file << \":\n"; - for (const ConfigSubstitution &subst : config_substitutions) - boost::nowide::cout << "\tkey = \"" << subst.opt_def->opt_key << "\"\t loaded = \"" << subst.old_value << "\tsubstituted = \"" << subst.new_value->serialize() << "\"\n"; - } - config.normalize_fdm(); - PrinterTechnology other_printer_technology = get_printer_technology(config); - if (printer_technology == ptUnknown) { - printer_technology = other_printer_technology; - } else if (printer_technology != other_printer_technology && other_printer_technology != ptUnknown) { - boost::nowide::cerr << "Mixing configurations for FFF and SLA technologies" << std::endl; + + if (!can_apply_printer_technology(cli_out.printer_technology, get_printer_technology(config))) return 1; - } - m_print_config.apply(config); + + print_config_substitutions(config_substitutions, file); + + config.normalize_fdm(); + cli_out.print_config.apply(config); } - bool has_config_from_profiles = m_profiles_sharing.empty() && - (!m_config.opt_string("print-profile").empty() || - !m_config.option("material-profile")->values.empty() || - !m_config.opt_string("printer-profile").empty() ); - if (has_config_from_profiles && !check_and_load_input_profiles(printer_technology)) - return 1; + bool has_config_from_profiles = cli_in.profiles_sharing.empty() && + (!cli_in.config.opt_string("print-profile").empty() || + !cli_in.config.option("material-profile")->values.empty() || + !cli_in.config.opt_string("printer-profile").empty() ); + + // load config from profiles set + if (has_config_from_profiles) { + Slic3r::DynamicPrintConfig config = {}; + std::string errors = Slic3r::load_full_print_config(cli_in.config.opt_string("print-profile"), + cli_in.config.option("material-profile")->values, + cli_in.config.opt_string("printer-profile"), + config, cli_out.printer_technology); + if (!errors.empty()) { + boost::nowide::cerr << errors << std::endl; + return 1; + } + + if (!can_apply_printer_technology(cli_out.printer_technology, get_printer_technology(config))) + return 1; + + config.normalize_fdm(); + cli_out.print_config.apply(config); + } #ifdef SLIC3R_GUI - if (m_config.has("webdev")) { - Utils::ServiceConfig::instance().set_webdev_enabled(m_config.opt_bool("webdev")); + if (cli_in.config.has("webdev")) { + Utils::ServiceConfig::instance().set_webdev_enabled(cli_in.config.opt_bool("webdev")); } std::vector::iterator it; bool opengl_aa = false; - it = std::find(m_actions.begin(), m_actions.end(), "opengl-aa"); - if (it != m_actions.end()) { + it = std::find(cli_in.actions.begin(), cli_in.actions.end(), "opengl-aa"); + if (it != cli_in.actions.end()) { start_gui = true; opengl_aa = true; - m_actions.erase(it); + cli_in.actions.erase(it); } #if SLIC3R_OPENGL_ES // are we starting as gcodeviewer ? - for (auto it = m_actions.begin(); it != m_actions.end(); ++it) { + for (auto it = cli_in.actions.begin(); it != cli_in.actions.end(); ++it) { if (*it == "gcodeviewer") { start_gui = true; start_as_gcodeviewer = true; - m_actions.erase(it); + cli_in.actions.erase(it); break; } } @@ -218,17 +845,17 @@ int CLI::run(int argc, char **argv) bool opengl_compatibility_profile = false; // search for special keys into command line parameters - it = std::find(m_actions.begin(), m_actions.end(), "gcodeviewer"); - if (it != m_actions.end()) { + it = std::find(cli_in.actions.begin(), cli_in.actions.end(), "gcodeviewer"); + if (it != cli_in.actions.end()) { start_gui = true; start_as_gcodeviewer = true; - m_actions.erase(it); + cli_in.actions.erase(it); } - it = std::find(m_actions.begin(), m_actions.end(), "opengl-version"); - if (it != m_actions.end()) { + it = std::find(cli_in.actions.begin(), cli_in.actions.end(), "opengl-version"); + if (it != cli_in.actions.end()) { const Semver opengl_minimum = Semver(3,2,0); - const std::string opengl_version_str = m_config.opt_string("opengl-version"); + const std::string opengl_version_str = cli_in.config.opt_string("opengl-version"); boost::optional semver = Semver::parse(opengl_version_str); if (semver.has_value() && (*semver) >= opengl_minimum ) { opengl_version.first = semver->maj(); @@ -241,46 +868,47 @@ int CLI::run(int argc, char **argv) boost::nowide::cerr << "Required OpenGL version " << opengl_version_str << " is invalid. Must be greater than or equal to " << opengl_minimum.to_string() << "\n Option 'opengl-version' ignored." << std::endl; start_gui = true; - m_actions.erase(it); + cli_in.actions.erase(it); } - it = std::find(m_actions.begin(), m_actions.end(), "opengl-compatibility"); - if (it != m_actions.end()) { + it = std::find(cli_in.actions.begin(), cli_in.actions.end(), "opengl-compatibility"); + if (it != cli_in.actions.end()) { start_gui = true; opengl_compatibility_profile = true; // reset version as compatibility profile always take the highest version // supported by the graphic card opengl_version = std::make_pair(0, 0); - m_actions.erase(it); + cli_in.actions.erase(it); } - it = std::find(m_actions.begin(), m_actions.end(), "opengl-debug"); - if (it != m_actions.end()) { + it = std::find(cli_in.actions.begin(), cli_in.actions.end(), "opengl-debug"); + if (it != cli_in.actions.end()) { start_gui = true; opengl_debug = true; - m_actions.erase(it); + cli_in.actions.erase(it); } #endif // SLIC3R_OPENGL_ES #else // SLIC3R_GUI // If there is no GUI, we shall ignore the parameters. Remove them from the list. for (const std::string& s : { "opengl-version", "opengl-compatibility", "opengl-debug", "opengl-aa", "gcodeviewer" }) { - auto it = std::find(m_actions.cbegin(), m_actions.cend(), s); - if (it != m_actions.end()) { + auto it = std::find(cli_in.actions.cbegin(), cli_in.actions.cend(), s); + if (it != cli_in.actions.end()) { boost::nowide::cerr << "Parameter '" << s << "' is ignored, this PrusaSlicer build is CLI only." << std::endl; - m_actions.erase(it); + cli_in.actions.erase(it); } } #endif // SLIC3R_GUI // Read input file(s) if any. - for (const std::string& file : m_input_files) + for (const std::string& file : cli_in.input_files) if (is_gcode_file(file) && boost::filesystem::exists(file)) { start_as_gcodeviewer = true; break; } + if (!start_as_gcodeviewer) { - for (const std::string& file : m_input_files) { + for (const std::string& file : cli_in.input_files) { if (boost::starts_with(file, "prusaslicer://")) { start_downloader = true; download_url = file; @@ -294,35 +922,13 @@ int CLI::run(int argc, char **argv) try { if (has_config_from_profiles) model = FileReader::load_model(file); - else { - // When loading an AMF or 3MF, config is imported as well, including the printer technology. - DynamicPrintConfig config; - ConfigSubstitutionContext config_substitutions(config_substitution_rule); - boost::optional prusaslicer_generator_version; - //FIXME should we check the version here? // | Model::LoadAttribute::CheckVersion ? - model = FileReader::load_model_with_config(file, &config, &config_substitutions, prusaslicer_generator_version, FileReader::LoadAttribute::AddDefaultInstances); - PrinterTechnology other_printer_technology = get_printer_technology(config); - if (printer_technology == ptUnknown) { - printer_technology = other_printer_technology; - } - else if (printer_technology != other_printer_technology && other_printer_technology != ptUnknown) { - boost::nowide::cerr << "Mixing configurations for FFF and SLA technologies" << std::endl; + else if (!apply_model_and_print_config_from_file(file, model, cli_out)) return 1; - } - if (! config_substitutions.substitutions.empty()) { - boost::nowide::cout << "The following configuration values were substituted when loading \" << file << \":\n"; - for (const ConfigSubstitution& subst : config_substitutions.substitutions) - boost::nowide::cout << "\tkey = \"" << subst.opt_def->opt_key << "\"\t loaded = \"" << subst.old_value << "\tsubstituted = \"" << subst.new_value->serialize() << "\"\n"; - } - // config is applied to m_print_config before the current m_config values. - config += std::move(m_print_config); - m_print_config = std::move(config); - } // If model for slicing is loaded from 3mf file, then its geometry has to be used and arrange couldn't be apply for this model. - if ((boost::algorithm::iends_with(file, ".3mf") || boost::algorithm::iends_with(file, ".zip")) && !m_config.opt_bool("dont_arrange")) { + if ((boost::algorithm::iends_with(file, ".3mf") || boost::algorithm::iends_with(file, ".zip")) && !cli_in.config.opt_bool("dont_arrange")) { //So, check a state of "dont_arrange" parameter and set it to true, if its value is false. - m_config.set_key_value("dont_arrange", new ConfigOptionBool(true)); + cli_in.config.set_key_value("dont_arrange", new ConfigOptionBool(true)); } } catch (std::exception& e) { @@ -333,12 +939,12 @@ int CLI::run(int argc, char **argv) boost::nowide::cerr << "Error: file is empty: " << file << std::endl; continue; } - m_models.push_back(model); + cli_out.models.push_back(model); } } if (!start_gui) { - const auto* post_process = m_print_config.opt("post_process"); + const auto* post_process = cli_out.print_config.opt("post_process"); if (post_process != nullptr && !post_process->values.empty()) { boost::nowide::cout << "\nA post-processing script has been detected in the config data:\n\n"; for (const auto& s : post_process->values) { @@ -352,26 +958,28 @@ int CLI::run(int argc, char **argv) } } + /* ysFIXME // Apply command line options to a more specific DynamicPrintConfig which provides normalize() // (command line options override --load files) - m_print_config.apply(m_extra_config, true); + cli.print_config.apply(cli_in.extra_config, true); // Normalizing after importing the 3MFs / AMFs - m_print_config.normalize_fdm(); + cli.print_config.normalize_fdm(); + */ - if (printer_technology == ptUnknown) - printer_technology = std::find(m_actions.begin(), m_actions.end(), "export_sla") == m_actions.end() ? ptFFF : ptSLA; - m_print_config.option>("printer_technology", true)->value = printer_technology; + if (cli_out.printer_technology == ptUnknown) + cli_out.printer_technology = std::find(cli_in.actions.begin(), cli_in.actions.end(), "export_sla") == cli_in.actions.end() ? ptFFF : ptSLA; + cli_out.print_config.option>("printer_technology", true)->value = cli_out.printer_technology; // Initialize full print configs for both the FFF and SLA technologies. FullPrintConfig fff_print_config; SLAFullPrintConfig sla_print_config; // Synchronize the default parameters and the ones received on the command line. - if (printer_technology == ptFFF) { - fff_print_config.apply(m_print_config, true); - m_print_config.apply(fff_print_config, true); + if (cli_out.printer_technology == ptFFF) { + fff_print_config.apply(cli_out.print_config, true); + cli_out.print_config.apply(fff_print_config, true); } else { - assert(printer_technology == ptSLA); + assert(cli_out.printer_technology == ptSLA); sla_print_config.output_filename_format.value = "[input_filename_base].sl1"; // The default bed shape should reflect the default display parameters @@ -380,424 +988,29 @@ int CLI::run(int argc, char **argv) double h = sla_print_config.display_height.getFloat(); sla_print_config.bed_shape.values = { Vec2d(0, 0), Vec2d(w, 0), Vec2d(w, h), Vec2d(0, h) }; - sla_print_config.apply(m_print_config, true); - m_print_config.apply(sla_print_config, true); + sla_print_config.apply(cli_out.print_config, true); + cli_out.print_config.apply(sla_print_config, true); } { - std::string validity = m_print_config.validate(); + std::string validity = cli_out.print_config.validate(); if (! validity.empty()) { boost::nowide::cerr << "Error: The composite configation is not valid: " << validity << std::endl; return 1; } } - - // Loop through transform options. - bool user_center_specified = false; const Vec2crd gap{s_multiple_beds.get_bed_gap()}; - arr2::ArrangeBed bed = arr2::to_arrange_bed(get_bed_shape(m_print_config), gap); + arr2::ArrangeBed bed = arr2::to_arrange_bed(get_bed_shape(cli_out.print_config), gap); arr2::ArrangeSettings arrange_cfg; - arrange_cfg.set_distance_from_objects(min_object_distance(m_print_config)); + arrange_cfg.set_distance_from_objects(min_object_distance(cli_out.print_config)); - for (auto const &opt_key : m_transforms) { - if (opt_key == "merge") { - Model m; - for (auto &model : m_models) - for (ModelObject *o : model.objects) - m.add_object(*o); - // Rearrange instances unless --dont-arrange is supplied - if (! m_config.opt_bool("dont_arrange")) { - m.add_default_instances(); - if (this->has_print_action()) - arrange_objects(m, bed, arrange_cfg); - else - arrange_objects(m, arr2::InfiniteBed{}, arrange_cfg); - } - m_models.clear(); - m_models.emplace_back(std::move(m)); - } else if (opt_key == "duplicate") { - for (auto &model : m_models) { - const bool all_objects_have_instances = std::none_of( - model.objects.begin(), model.objects.end(), - [](ModelObject* o){ return o->instances.empty(); } - ); - - int dups = m_config.opt_int("duplicate"); - if (!all_objects_have_instances) model.add_default_instances(); - - try { - if (dups > 1) { - // if all input objects have defined position(s) apply duplication to the whole model - duplicate(model, size_t(dups), bed, arrange_cfg); - } else { - arrange_objects(model, bed, arrange_cfg); - } - } catch (std::exception &ex) { - boost::nowide::cerr << "error: " << ex.what() << std::endl; - return 1; - } - } - } else if (opt_key == "duplicate_grid") { - std::vector &ints = m_config.option("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 = fff_print_config.duplicate_distance.value; - for (auto &model : m_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") { - user_center_specified = true; - for (auto &model : m_models) { - model.add_default_instances(); - // this affects instances: - model.center_instances_around_point(m_config.option("center")->value); - // this affects volumes: - //FIXME Vojtech: Who knows why the complete model should be aligned with Z as a single rigid body? - //model.align_to_ground(); - BoundingBoxf3 bbox; - for (ModelObject *model_object : model.objects) - // We are interested into the Z span only, therefore it is sufficient to measure the bounding box of the 1st instance only. - bbox.merge(model_object->instance_bounding_box(0, false)); - for (ModelObject *model_object : model.objects) - for (ModelInstance *model_instance : model_object->instances) - model_instance->set_offset(Z, model_instance->get_offset(Z) - bbox.min.z()); - } - } else if (opt_key == "align_xy") { - const Vec2d &p = m_config.option("align_xy")->value; - for (auto &model : m_models) { - BoundingBoxf3 bb = model.bounding_box_exact(); - // 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 == "ensure_on_bed") { - // do nothing, the value is used later - } else if (opt_key == "rotate") { - for (auto &model : m_models) - for (auto &o : model.objects) - // this affects volumes: - o->rotate(Geometry::deg2rad(m_config.opt_float(opt_key)), Z); - } else if (opt_key == "rotate_x") { - for (auto &model : m_models) - for (auto &o : model.objects) - // this affects volumes: - o->rotate(Geometry::deg2rad(m_config.opt_float(opt_key)), X); - } else if (opt_key == "rotate_y") { - for (auto &model : m_models) - for (auto &o : model.objects) - // this affects volumes: - o->rotate(Geometry::deg2rad(m_config.opt_float(opt_key)), Y); - } else if (opt_key == "scale") { - for (auto &model : m_models) - for (auto &o : model.objects) - // this affects volumes: - o->scale(m_config.get_abs_value(opt_key, 1)); - } else if (opt_key == "scale_to_fit") { - const Vec3d &opt = m_config.opt(opt_key)->value; - if (opt.x() <= 0 || opt.y() <= 0 || opt.z() <= 0) { - boost::nowide::cerr << "--scale-to-fit requires a positive volume" << std::endl; - return 1; - } - for (auto &model : m_models) - for (auto &o : model.objects) - // this affects volumes: - o->scale_to_fit(opt); - } else if (opt_key == "cut" || opt_key == "cut_x" || opt_key == "cut_y") { - std::vector new_models; - for (auto &model : m_models) { - model.translate(0, 0, -model.bounding_box_exact().min.z()); // align to z = 0 - size_t num_objects = model.objects.size(); - for (size_t i = 0; i < num_objects; ++ i) { - -#if 0 - if (opt_key == "cut_x") { - o->cut(X, m_config.opt_float("cut_x"), &out); - } else if (opt_key == "cut_y") { - o->cut(Y, m_config.opt_float("cut_y"), &out); - } else if (opt_key == "cut") { - o->cut(Z, m_config.opt_float("cut"), &out); - } -#else -// model.objects.front()->cut(0, m_config.opt_float("cut"), ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::FlipLower); - Cut cut(model.objects.front(), 0, Geometry::translation_transform(m_config.opt_float("cut") * Vec3d::UnitZ()), - ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::PlaceOnCutUpper); - auto cut_objects = cut.perform_with_plane(); - for (ModelObject* obj : cut_objects) - model.add_object(*obj); -#endif - model.delete_object(size_t(0)); - } - } - - // TODO: copy less stuff around using pointers - m_models = new_models; - - if (m_actions.empty()) - m_actions.push_back("export_stl"); - } -#if 0 - else if (opt_key == "cut_grid") { - std::vector new_models; - for (auto &model : m_models) { - TriangleMesh mesh = model.mesh(); - mesh.repair(); - - std::vector meshes = mesh.cut_by_grid(m_config.option("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 - m_models = new_models; - - if (m_actions.empty()) - m_actions.push_back("export_stl"); - } -#endif - else if (opt_key == "split") { - for (Model &model : m_models) { - size_t num_objects = model.objects.size(); - for (size_t i = 0; i < num_objects; ++ i) { - ModelObjectPtrs new_objects; - ModelProcessing::split(model.objects.front(), &new_objects); - model.delete_object(size_t(0)); - } - } - } else if (opt_key == "repair") { - // Models are repaired by default. - //for (auto &model : m_models) - // model.repair(); - - } else if (opt_key == "delete-after-load") { - delete_after_load = true; - } else { - boost::nowide::cerr << "error: option not implemented yet: " << opt_key << std::endl; - return 1; - } - } - - // All transforms have been dealt with. Now ensure that the objects are on bed. - // (Unless the user said otherwise.) - if (m_config.opt_bool("ensure_on_bed")) - for (auto &model : m_models) - for (auto &o : model.objects) - o->ensure_on_bed(); + // Loop through transform options. + if (!process_transform(cli_in, cli_out, bed, arrange_cfg)) + return 1; // loop through action options - for (auto const &opt_key : m_actions) { - if (opt_key == "help") { - this->print_help(); - } else if (opt_key == "help_fff") { - this->print_help(true, ptFFF); - } else if (opt_key == "help_sla") { - this->print_help(true, ptSLA); - } else if (opt_key == "save") { - //FIXME check for mixing the FFF / SLA parameters. - // or better save fff_print_config vs. sla_print_config - m_print_config.save(m_config.opt_string("save")); - } else if (opt_key == "info") { - // --info works on unrepaired model - for (Model &model : m_models) { - model.add_default_instances(); - model.print_info(); - } - } else if (opt_key == "export_stl") { - for (auto &model : m_models) - model.add_default_instances(); - if (! this->export_models(IO::STL)) - return 1; - } else if (opt_key == "export_obj") { - for (auto &model : m_models) - model.add_default_instances(); - if (! this->export_models(IO::OBJ)) - return 1; - } else if (opt_key == "export_3mf") { - if (! this->export_models(IO::TMF)) - return 1; - } else if (opt_key == "export_gcode" || opt_key == "export_sla" || opt_key == "slice") { - if (opt_key == "export_gcode" && printer_technology == ptSLA) { - boost::nowide::cerr << "error: cannot export G-code for an FFF configuration" << std::endl; - return 1; - } else if (opt_key == "export_sla" && printer_technology == ptFFF) { - boost::nowide::cerr << "error: cannot export SLA slices for a SLA configuration" << std::endl; - return 1; - } - // Make a copy of the model if the current action is not the last action, as the model may be - // modified by the centering and such. - Model model_copy; - bool make_copy = &opt_key != &m_actions.back(); - for (Model &model_in : m_models) { - if (make_copy) - model_copy = model_in; - Model &model = make_copy ? model_copy : model_in; - // 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). - std::string outfile = m_config.opt_string("output"); - Print fff_print; - SLAPrint sla_print; - sla_print.set_status_callback( - [](const PrintBase::SlicingStatus& s) - { - if(s.percent >= 0) { // FIXME: is this sufficient? - printf("%3d%s %s\n", s.percent, "% =>", s.text.c_str()); - std::fflush(stdout); - } - }); - - PrintBase *print = (printer_technology == ptFFF) ? static_cast(&fff_print) : static_cast(&sla_print); - if (! m_config.opt_bool("dont_arrange")) { - if (user_center_specified) { - Vec2d c = m_config.option("center")->value; - arrange_objects(model, arr2::InfiniteBed{scaled(c)}, arrange_cfg); - } else - arrange_objects(model, bed, arrange_cfg); - } - if (printer_technology == ptFFF) { - for (auto* mo : model.objects) - fff_print.auto_assign_extruders(mo); - } - print->apply(model, m_print_config); - std::string err = print->validate(); - if (! err.empty()) { - boost::nowide::cerr << err << std::endl; - return 1; - } - if (print->empty()) - boost::nowide::cout << "Nothing to print for " << outfile << " . Either the print is empty or no object is fully inside the print volume." << std::endl; - else - try { - std::string outfile_final; - print->process(); - if (printer_technology == ptFFF) { - - - - - - std::function thumbnail_generator_cli; - if (!fff_print.model().objects.empty() && boost::iends_with(fff_print.model().objects.front()->input_file, ".3mf")) { - std::string filename = fff_print.model().objects.front()->input_file; - thumbnail_generator_cli = [filename](const ThumbnailsParams&) { - ThumbnailsList list_out; - - mz_zip_archive archive; - mz_zip_zero_struct(&archive); - - if (!open_zip_reader(&archive, filename)) - return list_out; - mz_uint num_entries = mz_zip_reader_get_num_files(&archive); - mz_zip_archive_file_stat stat; - - int index = mz_zip_reader_locate_file(&archive, "Metadata/thumbnail.png", nullptr, 0); - if (index < 0 || !mz_zip_reader_file_stat(&archive, index, &stat)) - return list_out; - std::string buffer; - buffer.resize(int(stat.m_uncomp_size)); - mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, buffer.data(), (size_t)stat.m_uncomp_size, 0); - if (res == 0) - return list_out; - close_zip_reader(&archive); - - std::vector data; - unsigned width = 0; - unsigned height = 0; - png::decode_png(buffer, data, width, height); - - { - // Flip the image vertically so it matches the convention in Thumbnails generator. - const int row_size = width * 4; // Each pixel is 4 bytes (RGBA) - std::vector temp_row(row_size); - for (int i = 0; i < height / 2; ++i) { - unsigned char* top_row = &data[i * row_size]; - unsigned char* bottom_row = &data[(height - i - 1) * row_size]; - std::copy(bottom_row, bottom_row + row_size, temp_row.begin()); - std::copy(top_row, top_row + row_size, bottom_row); - std::copy(temp_row.begin(), temp_row.end(), top_row); - } - } - - ThumbnailData th; - th.set(width, height); - th.pixels = data; - list_out.push_back(th); - return list_out; - }; - } - - - - - - // The outfile is processed by a PlaceholderParser. - outfile = fff_print.export_gcode(outfile, nullptr, thumbnail_generator_cli); - outfile_final = fff_print.print_statistics().finalize_output_path(outfile); - } else { - outfile = sla_print.output_filepath(outfile); - // We need to finalize the filename beforehand because the export function sets the filename inside the zip metadata - outfile_final = sla_print.print_statistics().finalize_output_path(outfile); - sla_print.export_print(outfile_final); - } - if (outfile != outfile_final) { - if (Slic3r::rename_file(outfile, outfile_final)) { - boost::nowide::cerr << "Renaming file " << outfile << " to " << outfile_final << " failed" << std::endl; - return 1; - } - outfile = outfile_final; - } - // Run the post-processing scripts if defined. - run_post_process_scripts(outfile, fff_print.full_print_config()); - boost::nowide::cout << "Slicing result exported to " << outfile << std::endl; - } catch (const std::exception &ex) { - boost::nowide::cerr << ex.what() << std::endl; - return 1; - } -/* - print.center = ! m_config.has("center") - && ! m_config.has("align_xy") - && ! m_config.opt_bool("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 (processed_profiles_sharing()) + if (!process_actions(cli_in, cli_out, bed, arrange_cfg)) return 1; if (start_gui) { @@ -819,17 +1032,17 @@ int CLI::run(int argc, char **argv) params.argc = argc; params.argv = argv; params.load_configs = load_configs; - params.extra_config = std::move(m_extra_config); - params.input_files = std::move(m_input_files); +// params.extra_config = std::move(m_extra_config); + params.input_files = std::move(cli_in.input_files); if (has_config_from_profiles && params.input_files.empty()) { - params.selected_presets = Slic3r::GUI::CLISelectedProfiles{ m_config.opt_string("print-profile"), - m_config.opt_string("printer-profile") , - m_config.option("material-profile")->values }; + params.selected_presets = Slic3r::GUI::CLISelectedProfiles{ cli_in.config.opt_string("print-profile"), + cli_in.config.opt_string("printer-profile") , + cli_in.config.option("material-profile")->values }; } params.start_as_gcodeviewer = start_as_gcodeviewer; params.start_downloader = start_downloader; params.download_url = download_url; - params.delete_after_load = delete_after_load; + params.delete_after_load = cli_out.delete_after_load; params.opengl_aa = opengl_aa; #if !SLIC3R_OPENGL_ES params.opengl_version = opengl_version; @@ -848,7 +1061,7 @@ int CLI::run(int argc, char **argv) return 0; } -bool CLI::setup(int argc, char **argv) +bool CLI::setup(int argc, char **argv, CliInParams& in) { { Slic3r::set_logging_level(1); @@ -915,50 +1128,49 @@ bool CLI::setup(int argc, char **argv) // Parse all command line options into a DynamicConfig. // If any option is unsupported, print usage and abort immediately. t_config_option_keys opt_order; - if (! m_config.read_cli(argc, argv, &m_input_files, &opt_order)) { + if (! in.config.read_cli(argc, argv, &in.input_files, &opt_order)) { // Separate error message reported by the CLI parser from the help. boost::nowide::cerr << std::endl; - this->print_help(); + print_help(); return false; } // Parse actions and transform options. for (auto const &opt_key : opt_order) { if (cli_actions_config_def.has(opt_key)) - m_actions.emplace_back(opt_key); + in.actions.emplace_back(opt_key); else if (cli_transform_config_def.has(opt_key)) - m_transforms.emplace_back(opt_key); + in.transforms.emplace_back(opt_key); else if (cli_profiles_sharing_config_def.has(opt_key)) - m_profiles_sharing.emplace_back(opt_key); + in.profiles_sharing.emplace_back(opt_key); } { - const ConfigOptionInt *opt_loglevel = m_config.opt("loglevel"); + const ConfigOptionInt *opt_loglevel = in.config.opt("loglevel"); if (opt_loglevel != 0) set_logging_level(opt_loglevel->value); } { - const ConfigOptionInt *opt_threads = m_config.opt("threads"); + const ConfigOptionInt *opt_threads = in.config.opt("threads"); if (opt_threads != nullptr) thread_count = opt_threads->value; } //FIXME Validating at this stage most likely does not make sense, as the config is not fully initialized yet. - std::string validity = m_config.validate(); + std::string validity = in.config.validate(); // Initialize with defaults. - for (const t_optiondef_map *options : { &cli_actions_config_def.options + // Some of those values will used later + for (const t_optiondef_map* options : { &cli_actions_config_def.options , &cli_transform_config_def.options , &cli_profiles_sharing_config_def.options , &cli_misc_config_def.options }) for (const t_optiondef_map::value_type &optdef : *options) - m_config.option(optdef.first, true); + in.config.option(optdef.first, true); + + const std::string provided_datadir = in.config.opt_string("datadir"); + set_data_dir(provided_datadir.empty() ? get_default_datadir() : provided_datadir); - if (std::string provided_datadir = m_config.opt_string("datadir"); provided_datadir.empty()) { - set_data_dir(get_default_datadir()); - } else - set_data_dir(provided_datadir); - //FIXME Validating at this stage most likely does not make sense, as the config is not fully initialized yet. if (!validity.empty()) { boost::nowide::cerr << "error: " << validity << std::endl; @@ -968,215 +1180,6 @@ bool CLI::setup(int argc, char **argv) return true; } -void CLI::print_help(bool include_print_options, PrinterTechnology printer_technology) const -{ - boost::nowide::cout - << SLIC3R_BUILD_ID << " " << "based on Slic3r" -#ifdef SLIC3R_GUI - << " (with GUI support)" -#else /* SLIC3R_GUI */ - << " (without GUI support)" -#endif /* SLIC3R_GUI */ - << std::endl - << "https://github.com/prusa3d/PrusaSlicer" << std::endl << std::endl - << "Usage: prusa-slicer [ ACTIONS ] [ TRANSFORM ] [ OPTIONS ] [ file.stl ... ]" << std::endl - << std::endl - << "Actions:" << std::endl; - cli_actions_config_def.print_cli_help(boost::nowide::cout, false); - cli_profiles_sharing_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); - - //boost::nowide::cout - // << std::endl - // << "Profiles sharing options:" << std::endl; - // cli_profiles_sharing_config_def.print_cli_help(boost::nowide::cout, false); - - boost::nowide::cout - << std::endl - << "Print options are processed in the following order:" << std::endl - << "\t1) Config keys from the command line, for example --fill-pattern=stars" << std::endl - << "\t (highest priority, overwrites everything below)" << std::endl - << "\t2) Config files loaded with --load" << std::endl - << "\t3) Config values loaded from amf or 3mf files" << std::endl; - - if (include_print_options) { - boost::nowide::cout << std::endl; - print_config_def.print_cli_help(boost::nowide::cout, true, [printer_technology](const ConfigOptionDef &def) - { return printer_technology == ptAny || def.printer_technology == ptAny || printer_technology == def.printer_technology; }); - } else { - boost::nowide::cout - << std::endl - << "Run --help-fff / --help-sla to see the full listing of print options." << std::endl; - } -} - -bool CLI::export_models(IO::ExportFormat format) -{ - for (Model &model : m_models) { - const std::string path = this->output_filepath(model, format); - bool success = false; - switch (format) { - case IO::OBJ: success = Slic3r::store_obj(path.c_str(), &model); break; - case IO::STL: success = Slic3r::store_stl(path.c_str(), &model, true); break; - case IO::TMF: success = Slic3r::store_3mf(path.c_str(), &model, nullptr, false); break; - default: assert(false); break; - } - if (success) - std::cout << "File exported to " << path << std::endl; - else { - std::cerr << "File export to " << path << " failed" << std::endl; - return false; - } - } - return true; -} - -std::string CLI::output_filepath(const Model &model, IO::ExportFormat format) const -{ - std::string ext; - switch (format) { - case IO::OBJ: ext = ".obj"; break; - case IO::STL: ext = ".stl"; break; - case IO::TMF: ext = ".3mf"; break; - default: assert(false); break; - }; - auto proposed_path = boost::filesystem::path(model.propose_export_file_name_and_path(ext)); - // use --output when available - std::string cmdline_param = m_config.opt_string("output"); - if (! cmdline_param.empty()) { - // if we were supplied a directory, use it and append our automatically generated filename - boost::filesystem::path cmdline_path(cmdline_param); - if (boost::filesystem::is_directory(cmdline_path)) - proposed_path = cmdline_path / proposed_path.filename(); - else - proposed_path = cmdline_path; - } - return proposed_path.string(); -} - -std::set query_options = { - "printer-profile" -}; - -bool CLI::processed_profiles_sharing() -{ - if (m_profiles_sharing.empty()) { -#if 0 // fsFIXME !!! just for the test - Slic3r::DynamicPrintConfig config = {}; - bool was_loaded = Slic3r::load_full_print_config("0.20mm QUALITY @MINI", "Prusament PLA", "Original Prusa MINI & MINI+", config); - return true; -#else - return false; -#endif - } - - std::string ret; - for (auto const& opt_key : m_profiles_sharing) { - if (query_options.find(opt_key) != query_options.end()) - continue; - if (opt_key == "query-printer-models") { - ret = Slic3r::get_json_printer_models(get_printer_technology(m_config)); - } -/* - else if (opt_key == "query-printer-profiles") { - if (!m_config.has("printer_model") || !m_config.has("printer_variant")) { - boost::nowide::cerr << "error in '" << opt_key << "' : this action requires set 'printer-model' and 'printer-variant' options" << std::endl; - break; - } - ret = Slic3r::get_json_printer_profiles(m_config.opt_string("printer_model"), m_config.opt_string("printer_variant")); - - if (ret.empty()) - boost::nowide::cerr << "Printer_model '" << m_config.opt_string("printer_model") << - "' with printer_variant '" << m_config.opt_string("printer_variant") << - "' wasn't found among installed printers." << std::endl << - "Or the request can be wrong." << std::endl; - } -*/ - else if (opt_key == "query-print-filament-profiles") { - if (!m_config.has("printer-profile")) { - boost::nowide::cerr << "error: this action requires set printer-preset option" << opt_key << std::endl; - break; - } - ret = Slic3r::get_json_print_filament_profiles(m_config.opt_string("printer-profile")); - - if (ret.empty()) - boost::nowide::cerr << "Printer profile '" << m_config.opt_string("printer-profile") << - "' wasn't found among installed printers." << std::endl << - "Or the request can be wrong." << std::endl; - } - else { - boost::nowide::cerr << "error: option not implemented yet: " << opt_key << std::endl; - break; - } - } - - // use --output when available - - std::string cmdline_param = m_config.opt_string("output"); - if (cmdline_param.empty()) { - if (ret.empty()) - boost::nowide::cerr << "Wrong request" << std::endl; - else - printf("%s", ret.c_str()); - } - else { - // if we were supplied a directory, use it and append our automatically generated filename - boost::filesystem::path cmdline_path(cmdline_param); - boost::filesystem::path proposed_path = boost::filesystem::path(Slic3r::resources_dir()) / "out.json"; - if (boost::filesystem::is_directory(cmdline_path)) - proposed_path = (cmdline_path / proposed_path.filename()); - else if (cmdline_path.extension().empty()) - proposed_path = cmdline_path.replace_extension("json"); - else - proposed_path = cmdline_path; - const std::string file = proposed_path.string(); - - boost::nowide::ofstream c; - c.open(file, std::ios::out | std::ios::trunc); - c << ret << std::endl; - c.close(); - - boost::nowide::cout << "Output for your request is written into " << file << std::endl; - } - - return true; -} - -bool CLI::check_and_load_input_profiles(PrinterTechnology& printer_technology) -{ - Slic3r::DynamicPrintConfig config = {}; - std::string ret = Slic3r::load_full_print_config(m_config.opt_string("print-profile"), - m_config.option("material-profile")->values, - m_config.opt_string("printer-profile"), - config, printer_technology); - if (!ret.empty()) { - boost::nowide::cerr << ret << std::endl; - return false; - } - - config.normalize_fdm(); - - PrinterTechnology other_printer_technology = get_printer_technology(config); - if (printer_technology == ptUnknown) - printer_technology = other_printer_technology; - else if (printer_technology != other_printer_technology && other_printer_technology != ptUnknown) { - boost::nowide::cerr << "Mixing configurations for FFF and SLA technologies" << std::endl; - return false; - } - - m_print_config.apply(config); - - return true; -} // __has_feature() is used later for Clang, this is for compatibility with other compilers (such as GCC and MSVC) #ifndef __has_feature diff --git a/src/PrusaSlicer.hpp b/src/PrusaSlicer.hpp index b26000a320..2f9d9e5ada 100644 --- a/src/PrusaSlicer.hpp +++ b/src/PrusaSlicer.hpp @@ -16,35 +16,32 @@ namespace IO { }; } +struct CliInParams +{ + DynamicPrintAndCLIConfig config; + std::vector input_files; + std::vector actions; + std::vector transforms; + std::vector profiles_sharing; +}; + +struct CliOutParams +{ + DynamicPrintConfig print_config; + PrinterTechnology printer_technology; + ForwardCompatibilitySubstitutionRule config_substitution_rule; + std::vector models; + bool user_center_specified { false }; + bool delete_after_load { false }; +}; + class CLI { public: int run(int argc, char **argv); private: - DynamicPrintAndCLIConfig m_config; - DynamicPrintConfig m_print_config; - DynamicPrintConfig m_extra_config; - std::vector m_input_files; - std::vector m_actions; - std::vector m_transforms; - std::vector m_profiles_sharing; - std::vector m_models; - bool setup(int argc, char **argv); - - /// Prints usage of the CLI. - void print_help(bool include_print_options = false, PrinterTechnology printer_technology = ptAny) const; - - /// Exports loaded models to a file of the specified format, according to the options affecting output filename. - bool export_models(IO::ExportFormat format); - - bool has_print_action() const { return m_config.opt_bool("export_gcode") || m_config.opt_bool("export_sla"); } - - bool processed_profiles_sharing(); - - bool check_and_load_input_profiles(PrinterTechnology& printer_technology); - - std::string output_filepath(const Model &model, IO::ExportFormat format) const; + bool setup(int argc, char **argv, CliInParams& in); }; } From 79ae91114004aed2e588bfbeeb572324cc094338 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 12 Dec 2024 15:35:22 +0100 Subject: [PATCH 2/9] GUI_InitParams: Set initial values for members and fixed a typo in opengl_compatibiity_profile --- src/slic3r/GUI/GUI_App.cpp | 2 +- src/slic3r/GUI/GUI_Init.hpp | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 27315f8078..273e030b24 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -907,7 +907,7 @@ wxGLContext* GUI_App::init_glcontext(wxGLCanvas& canvas) return m_opengl_mgr.init_glcontext(canvas); #else return m_opengl_mgr.init_glcontext(canvas, init_params != nullptr ? init_params->opengl_version : std::make_pair(0, 0), - init_params != nullptr ? init_params->opengl_compatibiity_profile : false, init_params != nullptr ? init_params->opengl_debug : false); + init_params != nullptr ? init_params->opengl_compatibility_profile : false, init_params != nullptr ? init_params->opengl_debug : false); #endif // SLIC3R_OPENGL_ES } diff --git a/src/slic3r/GUI/GUI_Init.hpp b/src/slic3r/GUI/GUI_Init.hpp index 2a8e210e65..acb66895c7 100644 --- a/src/slic3r/GUI/GUI_Init.hpp +++ b/src/slic3r/GUI/GUI_Init.hpp @@ -39,16 +39,16 @@ struct GUI_InitParams std::vector input_files; CLISelectedProfiles selected_presets; - bool start_as_gcodeviewer; - bool start_downloader; - bool delete_after_load; + bool start_as_gcodeviewer { false }; + bool start_downloader { false }; + bool delete_after_load { false }; std::string download_url; #if !SLIC3R_OPENGL_ES - std::pair opengl_version; - bool opengl_debug; - bool opengl_compatibiity_profile; + std::pair opengl_version { 0, 0 }; + bool opengl_debug { false }; + bool opengl_compatibility_profile { false }; #endif // !SLIC3R_OPENGL_ES - bool opengl_aa; + bool opengl_aa { false }; }; int GUI_Run(GUI_InitParams ¶ms); From 42d4f5c7759f229348ad375274e1bc6721b80d48 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 12 Dec 2024 15:29:20 +0100 Subject: [PATCH 3/9] PrintConfig WIP: Changes related to CLI * change a little bit a list of options inside CLI*ConfigDefs * --load and --*-profiles are extracted into separate CLIInputConfigDef + removed CLIProfilesSharingConfigDef and DynamicPrintAndCLIConfig as no needed anymore --- src/libslic3r/PrintConfig.cpp | 338 ++++++++++++++++------------------ src/libslic3r/PrintConfig.hpp | 44 +---- 2 files changed, 159 insertions(+), 223 deletions(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index c85303c949..5acc2cf8d3 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -5586,82 +5586,36 @@ PRINT_CONFIG_CACHE_INITIALIZE(( SLAMaterialConfig, SLAPrintConfig, SLAPrintObjectConfig, SLAPrinterConfig, SLAFullPrintConfig)) static int print_config_static_initialized = print_config_static_initializer(); +CLIInputConfigDef::CLIInputConfigDef() +{ + ConfigOptionDef* def; + + def = this->add("load", coStrings); + def->label = L("Load config file"); + def->tooltip = L("Load configuration from the specified file. It can be used more than once to load options from multiple files."); + + def = this->add("printer-profile", coString); + def->label = ("Printer preset name"); + def->tooltip = ("Name of the printer preset used for slicing."); + def->set_default_value(new ConfigOptionString()); + + def = this->add("print-profile", coString); + def->label = ("Print preset name"); + def->tooltip = ("Name of the print preset used for slicing."); + def->set_default_value(new ConfigOptionString()); + + def = this->add("material-profile", coStrings); + def->label = ("Material preset name(s)"); + def->tooltip = ("Name(s) of the material preset(s) used for slicing.\n" + "Could be filaments or sla_material preset name(s) depending on printer tochnology"); + def->set_default_value(new ConfigOptionStrings()); +} + CLIActionsConfigDef::CLIActionsConfigDef() { ConfigOptionDef* def; - // Actions: - def = this->add("export_obj", coBool); - def->label = L("Export OBJ"); - def->tooltip = L("Export the model(s) as OBJ."); - def->set_default_value(new ConfigOptionBool(false)); - -/* - def = this->add("export_svg", coBool); - def->label = L("Export SVG"); - def->tooltip = L("Slice the model and export solid slices as SVG."); - def->set_default_value(new ConfigOptionBool(false)); -*/ - - def = this->add("export_sla", coBool); - def->label = L("Export SLA"); - def->tooltip = L("Slice the model and export SLA printing layers as PNG."); - def->cli = "export-sla|sla"; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("export_3mf", coBool); - def->label = L("Export 3MF"); - def->tooltip = L("Export the model(s) as 3MF."); - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("export_stl", coBool); - def->label = L("Export STL"); - def->tooltip = L("Export the model(s) as STL."); - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("export_gcode", coBool); - def->label = L("Export G-code"); - def->tooltip = L("Slice the model and export toolpaths as G-code."); - def->cli = "export-gcode|gcode|g"; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("gcodeviewer", coBool); - def->label = L("G-code viewer"); - def->tooltip = L("Visualize an already sliced and saved G-code"); - def->cli = "gcodeviewer"; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("opengl-aa", coBool); - def->label = L("Automatic OpenGL antialiasing samples number selection"); - def->tooltip = L("Automatically select the highest number of samples for OpenGL antialiasing."); - def->cli = "opengl-aa"; - def->set_default_value(new ConfigOptionBool(false)); - -#if !SLIC3R_OPENGL_ES - def = this->add("opengl-version", coString); - def->label = L("OpenGL version"); - def->tooltip = L("Select a specific version of OpenGL"); - def->cli = "opengl-version"; - def->set_default_value(new ConfigOptionString()); - - def = this->add("opengl-compatibility", coBool); - def->label = L("OpenGL compatibility profile"); - def->tooltip = L("Enable OpenGL compatibility profile"); - def->cli = "opengl-compatibility"; - def->set_default_value(new ConfigOptionBool(false)); - - def = this->add("opengl-debug", coBool); - def->label = L("OpenGL debug output"); - def->tooltip = L("Activate OpenGL debug output on graphic cards which support it (OpenGL 4.3 or higher)"); - def->cli = "opengl-debug"; - def->set_default_value(new ConfigOptionBool(false)); -#endif // !SLIC3R_OPENGL_ES - - def = this->add("slice", coBool); - def->label = L("Slice"); - def->tooltip = L("Slice the model as FFF or SLA based on the printer_technology configuration value."); - def->cli = "slice|s"; - def->set_default_value(new ConfigOptionBool(false)); + // doesn't need any aditional input def = this->add("help", coBool); def->label = L("Help"); @@ -5679,15 +5633,86 @@ CLIActionsConfigDef::CLIActionsConfigDef() def->tooltip = L("Show the full list of SLA print configuration options."); def->set_default_value(new ConfigOptionBool(false)); - def = this->add("info", coBool); - def->label = L("Output Model Info"); - def->tooltip = L("Write information about the model to the console."); + def = this->add("query-printer-models", coBool); + def->label = ("Get list of printer models"); + def->tooltip = ("Get list of installed printer models into JSON.\n" + "Note:\n" + "To print printer models for required technology use 'printer-technology' option with value FFF or SLA. By default printer_technology is FFF.\n" + "To print out JSON into file use 'output' option.\n" + "To specify configuration folder use 'datadir' option."); + + // needs a --printer-profile input + + def = this->add("query-print-filament-profiles", coBool); + def->label = ("Get list of print profiles and filament profiles for the selected printer profile"); + def->tooltip = ("Get list of print profiles and filament profiles for the selected 'printer-profile' into JSON.\n" + "Note:\n" + "To print out JSON into file use 'output' option.\n" + "To specify configuration folder use 'datadir' option."); + + // needs nothing or input just one *.gcode file + + def = this->add("gcodeviewer", coBool); + def->label = L("G-code viewer"); + def->tooltip = L("Visualize an already sliced and saved G-code"); + def->cli = "gcodeviewer"; def->set_default_value(new ConfigOptionBool(false)); + // needs a configuration input + def = this->add("save", coString); def->label = L("Save config file"); def->tooltip = L("Save configuration to the specified file."); def->set_default_value(new ConfigOptionString()); + + // needs a model to process this actions + + def = this->add("info", coBool); + def->label = L("Output Model Info"); + def->tooltip = L("Write information about the model to the console."); + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("export_obj", coBool); + def->label = L("Export OBJ"); + def->tooltip = L("Export the model(s) as OBJ."); + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("export_stl", coBool); + def->label = L("Export STL"); + def->tooltip = L("Export the model(s) as STL."); + def->set_default_value(new ConfigOptionBool(false)); + + // needs model and configuration + + def = this->add("export_3mf", coBool); + def->label = L("Export 3MF"); + def->tooltip = L("Export the model(s) as 3MF."); + def->set_default_value(new ConfigOptionBool(false)); + + //! slice/export_sla/export_gcode is the same action + //! May be merged into one action "slice_and_export" + + def = this->add("slice", coBool); + def->label = L("Slice"); +// def->tooltip = L("Slice the model as FFF or SLA based on the printer_technology configuration value."); + def->tooltip = L("Slice the model as FFF or SLA based on the printer_technology configuration value " + "and export FFF printing toolpaths as G-code or SLA printing layers as PNG."); + def->cli = "slice|s"; + def->set_default_value(new ConfigOptionBool(false)); + + /* looks like redundant actions. "slice" is complitely enough + def = this->add("export_sla", coBool); + def->label = L("Export SLA"); + def->tooltip = L("Slice the model and export SLA printing layers as PNG."); + def->cli = "export-sla|sla"; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("export_gcode", coBool); + def->label = L("Export G-code"); + def->tooltip = L("Slice the model and export toolpaths as G-code."); + def->cli = "export-gcode|gcode|g"; + def->set_default_value(new ConfigOptionBool(false)); +*/ } CLITransformConfigDef::CLITransformConfigDef() @@ -5705,23 +5730,6 @@ CLITransformConfigDef::CLITransformConfigDef() def->tooltip = L("Cut model at the given Z."); def->set_default_value(new ConfigOptionFloat(0)); -/* - def = this->add("cut_grid", coFloat); - def->label = L("Cut"); - def->tooltip = L("Cut model in the XY plane into tiles of the specified max size."); - def->set_default_value(new ConfigOptionPoint()); - - def = this->add("cut_x", coFloat); - def->label = L("Cut"); - def->tooltip = L("Cut model at the given X."); - def->set_default_value(new ConfigOptionFloat(0)); - - def = this->add("cut_y", coFloat); - def->label = L("Cut"); - def->tooltip = L("Cut model at the given Y."); - def->set_default_value(new ConfigOptionFloat(0)); -*/ - def = this->add("center", coPoint); def->label = L("Center"); def->tooltip = L("Center the print around the given center."); @@ -5750,10 +5758,6 @@ CLITransformConfigDef::CLITransformConfigDef() def->tooltip = L("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 = L("Repair"); - def->tooltip = L("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 = this->add("rotate", coFloat); def->label = L("Rotate"); def->tooltip = L("Rotation angle around the Z axis in degrees."); @@ -5781,11 +5785,7 @@ CLITransformConfigDef::CLITransformConfigDef() def = this->add("scale_to_fit", coPoint3); def->label = L("Scale to Fit"); def->tooltip = L("Scale to fit the given volume."); - def->set_default_value(new ConfigOptionPoint3(Vec3d(0,0,0))); - - def = this->add("delete-after-load", coString); - def->label = L("Delete files after loading"); - def->tooltip = L("Delete files after loading."); + def->set_default_value(new ConfigOptionPoint3(Vec3d(0, 0, 0))); } CLIMiscConfigDef::CLIMiscConfigDef() @@ -5799,34 +5799,20 @@ CLIMiscConfigDef::CLIMiscConfigDef() def = this->add("config_compatibility", coEnum); def->label = L("Forward-compatibility rule when loading configurations from config files and project files (3MF, AMF)."); def->tooltip = L("This version of PrusaSlicer may not understand configurations produced by the newest PrusaSlicer versions. " - "For example, newer PrusaSlicer may extend the list of supported firmware flavors. One may decide to " - "bail out or to substitute an unknown value with a default silently or verbosely."); + "For example, newer PrusaSlicer may extend the list of supported firmware flavors. One may decide to " + "bail out or to substitute an unknown value with a default silently or verbosely."); def->set_enum({ { "disable", L("Bail out on unknown configuration values") }, { "enable", L("Enable reading unknown configuration values by verbosely substituting them with defaults.") }, { "enable_silent", L("Enable reading unknown configuration values by silently substituting them with defaults.") } - }); + }); def->set_default_value(new ConfigOptionEnum(ForwardCompatibilitySubstitutionRule::Enable)); - def = this->add("load", coStrings); - def->label = L("Load config file"); - def->tooltip = L("Load configuration from the specified file. It can be used more than once to load options from multiple files."); - def = this->add("output", coString); def->label = L("Output File"); def->tooltip = L("The file where the output will be written (if not specified, it will be based on the input file)."); def->cli = "output|o"; - def = this->add("single_instance", coBool); - def->label = L("Single instance mode"); - def->tooltip = L("If enabled, the command line arguments are sent to an existing instance of GUI PrusaSlicer, " - "or an existing PrusaSlicer window is activated. " - "Overrides the \"single_instance\" configuration value from application preferences."); - - def = this->add("single_instance_on_url", coBool); - def->label = "Single instance mode for prusaslicer url"; // Not translated on purpose - for internal use only. - def->tooltip = "Works as single_instance but only if prusaslicer url is present."; - def = this->add("datadir", coString); def->label = L("Data directory"); def->tooltip = L("Load and store settings at the given directory. This is useful for maintaining different profiles or including configurations from a network storage."); @@ -5839,85 +5825,69 @@ CLIMiscConfigDef::CLIMiscConfigDef() def = this->add("loglevel", coInt); def->label = L("Logging level"); def->tooltip = L("Sets logging sensitivity. 0:fatal, 1:error, 2:warning, 3:info, 4:debug, 5:trace\n" - "For example. loglevel=2 logs fatal, error and warning level messages."); + "For example. loglevel=2 logs fatal, error and warning level messages."); def->min = 0; - def = this->add("webdev", coBool); - def->label = "Enable webdev tools"; // Not translated on purpose - for internal use only. - def->tooltip = "Enable webdev tools"; +#ifdef SLIC3R_GUI + def = this->add("opengl-aa", coBool); + def->label = L("Automatic OpenGL antialiasing samples number selection"); + def->tooltip = L("Automatically select the highest number of samples for OpenGL antialiasing."); + def->cli = "opengl-aa"; + def->set_default_value(new ConfigOptionBool(false)); + +#if !SLIC3R_OPENGL_ES + def = this->add("opengl-version", coString); + def->label = L("OpenGL version"); + def->tooltip = L("Select a specific version of OpenGL"); + def->cli = "opengl-version"; + def->set_default_value(new ConfigOptionString()); + + def = this->add("opengl-compatibility", coBool); + def->label = L("OpenGL compatibility profile"); + def->tooltip = L("Enable OpenGL compatibility profile"); + def->cli = "opengl-compatibility"; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("opengl-debug", coBool); + def->label = L("OpenGL debug output"); + def->tooltip = L("Activate OpenGL debug output on graphic cards which support it (OpenGL 4.3 or higher)"); + def->cli = "opengl-debug"; + def->set_default_value(new ConfigOptionBool(false)); +#endif // !SLIC3R_OPENGL_ES + + def = this->add("single_instance", coBool); + def->label = L("Single instance mode"); + def->tooltip = L("If enabled, the command line arguments are sent to an existing instance of GUI PrusaSlicer, " + "or an existing PrusaSlicer window is activated. " + "Overrides the \"single_instance\" configuration value from application preferences."); + + def = this->add("delete-after-load", coString); + def->label = L("Delete files after loading"); + def->tooltip = L("Delete files after loading."); #if (defined(_MSC_VER) || defined(__MINGW32__)) && defined(SLIC3R_GUI) def = this->add("sw_renderer", coBool); def->label = L("Render with a software renderer"); def->tooltip = L("Render with a software renderer. The bundled MESA software renderer is loaded instead of the default OpenGL driver."); def->min = 0; -#endif /* _MSC_VER */ +#endif // _MSC_VER - def = this->add("printer-profile", coString); - def->label = ("Printer preset name"); - def->tooltip = ("Name of the printer preset used for slicing."); - def->set_default_value(new ConfigOptionString()); + // for internal use only + def = this->add("webdev", coBool); + def->label = "Enable webdev tools"; // Not translated on purpose - for internal use only. + def->tooltip = "Enable webdev tools"; - def = this->add("print-profile", coString); - def->label = ("Print preset name"); - def->tooltip = ("Name of the print preset used for slicing."); - def->set_default_value(new ConfigOptionString()); + def = this->add("single_instance_on_url", coBool); + def->label = "Single instance mode for prusaslicer url"; // Not translated on purpose - for internal use only. + def->tooltip = "Works as single_instance but only if prusaslicer url is present."; - def = this->add("material-profile", coStrings); - def->label = ("Material preset name(s)"); - def->tooltip = ("Name(s) of the material preset(s) used for slicing.\n" - "Could be filaments or sla_material preset name(s) depending on printer tochnology"); - def->set_default_value(new ConfigOptionStrings()); +#endif // SLIC3R_GUI } -CLIProfilesSharingConfigDef::CLIProfilesSharingConfigDef() -{ - ConfigOptionDef* def; - - // Information from this def will be used just for console output. - // So, don't use L marker to label and tooltips values to avoid extract those phrases to translation. - - def = this->add("query-printer-models", coBool); - def->label = ("Get list of printer models"); - def->tooltip = ("Get list of installed printer models into JSON.\n" - "Note:\n" - "To print printer models for required technology use 'printer-technology' option with value FFF or SLA. By default printer_technology is FFF.\n" - "To print out JSON into file use 'output' option.\n" - "To specify configuration folder use 'datadir' option."); - -/* - def = this->add("query-printer-profiles", coBool); - def->label = ("Get list of printer profiles for the selected printer model and printer variant"); - def->tooltip = ("Get list of printer profiles for the selected 'printer-model' and 'printer-variant' into JSON.\n" - "Note:\n" - "To print out JSON into file use 'output' option.\n" - "To specify configuration folder use 'datadir' option."); -*/ - - def = this->add("query-print-filament-profiles", coBool); - def->label = ("Get list of print profiles and filament profiles for the selected printer profile"); - def->tooltip = ("Get list of print profiles and filament profiles for the selected 'printer-profile' into JSON.\n" - "Note:\n" - "To print out JSON into file use 'output' option.\n" - "To specify configuration folder use 'datadir' option."); -} - -const CLIActionsConfigDef cli_actions_config_def; -const CLITransformConfigDef cli_transform_config_def; -const CLIMiscConfigDef cli_misc_config_def; -const CLIProfilesSharingConfigDef cli_profiles_sharing_config_def; - -DynamicPrintAndCLIConfig::PrintAndCLIConfigDef DynamicPrintAndCLIConfig::s_def; - -void DynamicPrintAndCLIConfig::handle_legacy(t_config_option_key &opt_key, std::string &value) const -{ - if (cli_actions_config_def .options.find(opt_key) == cli_actions_config_def .options.end() && - cli_profiles_sharing_config_def.options.find(opt_key) == cli_profiles_sharing_config_def.options.end() && - cli_transform_config_def.options.find(opt_key) == cli_transform_config_def.options.end() && - cli_misc_config_def .options.find(opt_key) == cli_misc_config_def .options.end()) { - PrintConfigDef::handle_legacy(opt_key, value); - } -} +const CLIInputConfigDef cli_input_config_def; +const CLIActionsConfigDef cli_actions_config_def; +const CLITransformConfigDef cli_transform_config_def; +const CLIMiscConfigDef cli_misc_config_def; // SlicingStatesConfigDefs diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index dac7561bdd..a7dfe1a460 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1341,10 +1341,10 @@ public: CLIMiscConfigDef(); }; -class CLIProfilesSharingConfigDef : public ConfigDef +class CLIInputConfigDef : public ConfigDef { public: - CLIProfilesSharingConfigDef(); + CLIInputConfigDef(); }; typedef std::string t_custom_gcode_key; @@ -1409,7 +1409,7 @@ public: }; extern const CustomGcodeSpecificConfigDef custom_gcode_specific_config_def; -// This class defines the command line options representing actions. +// This class defines the command line options representing actions including options representing profiles sharing commands. extern const CLIActionsConfigDef cli_actions_config_def; // This class defines the command line options representing transforms. @@ -1418,42 +1418,8 @@ extern const CLITransformConfigDef cli_transform_config_def; // This class defines all command line options that are not actions or transforms. extern const CLIMiscConfigDef cli_misc_config_def; -// This class defines the command line options representing profiles sharing commands. -extern const CLIProfilesSharingConfigDef cli_profiles_sharing_config_def; - -class DynamicPrintAndCLIConfig : public DynamicPrintConfig -{ -public: - DynamicPrintAndCLIConfig() {} - DynamicPrintAndCLIConfig(const DynamicPrintAndCLIConfig &other) : DynamicPrintConfig(other) {} - - // Overrides ConfigBase::def(). Static configuration definition. Any value stored into this ConfigBase shall have its definition here. - const ConfigDef* def() const override { return &s_def; } - - // Verify whether the opt_key has not been obsoleted or renamed. - // Both opt_key and value may be modified by handle_legacy(). - // If the opt_key is no more valid in this version of Slic3r, opt_key is cleared by handle_legacy(). - // handle_legacy() is called internally by set_deserialize(). - void handle_legacy(t_config_option_key &opt_key, std::string &value) const override; - -private: - class PrintAndCLIConfigDef : public ConfigDef - { - public: - PrintAndCLIConfigDef() { - this->options.insert(print_config_def.options.begin(), print_config_def.options.end()); - this->options.insert(cli_actions_config_def.options.begin(), cli_actions_config_def.options.end()); - this->options.insert(cli_transform_config_def.options.begin(), cli_transform_config_def.options.end()); - this->options.insert(cli_misc_config_def.options.begin(), cli_misc_config_def.options.end()); - this->options.insert(cli_profiles_sharing_config_def.options.begin(), cli_profiles_sharing_config_def.options.end()); - for (const auto &kvp : this->options) - this->by_serialization_key_ordinal[kvp.second.serialization_key_ordinal] = &kvp.second; - } - // Do not release the default values, they are handled by print_config_def & cli_actions_config_def / cli_transform_config_def / cli_misc_config_def. - ~PrintAndCLIConfigDef() { this->options.clear(); } - }; - static PrintAndCLIConfigDef s_def; -}; +// This class defines the command line options representing commands for loading configuration from CLI +extern const CLIInputConfigDef cli_input_config_def; bool is_XL_printer(const DynamicPrintConfig &cfg); bool is_XL_printer(const PrintConfig &cfg); From 253abbfa78d2741d1579864d4158e24495a6ad5c Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 14 Jan 2025 09:58:07 +0100 Subject: [PATCH 4/9] PrusaSlicer completely rework : WIP * read cli arguments into separate DynamicPrintConfigs * CLI::run : All separate code blocks are extracted into free functions --- src/PrusaSlicer.cpp | 1811 ++++++++++++++++++++++++------------------- src/PrusaSlicer.hpp | 42 +- 2 files changed, 1016 insertions(+), 837 deletions(-) diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index 87a5553386..cd701a7b96 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -78,6 +78,214 @@ using namespace Slic3r; + +// struct which is filled from comand line input +struct Cli +{ + DynamicPrintConfig input_config; + DynamicPrintConfig overrides_config; + DynamicPrintConfig transform_config; + DynamicPrintConfig misc_config; + DynamicPrintConfig actions_config; + + std::vector input_files; + + bool read(int argc, const char* const argv[]); + + bool empty() { + return input_files.empty() + && input_config.empty() + && overrides_config.empty() + && transform_config.empty() + && actions_config.empty(); + } + +}; + + +enum class CliType +{ + Input, + Overrides, + Transformations, + Misc, + Actions +}; + + +bool Cli::read(int argc, const char* const argv[]) +{ + // cache the CLI option => opt_key mapping + std::map > opts; + + std::initializer_list> list = { + { cli_input_config_def, CliType::Input}, + { print_config_def, CliType::Overrides}, + { cli_transform_config_def, CliType::Transformations}, + { cli_misc_config_def, CliType::Misc}, + { cli_actions_config_def, CliType::Actions} + }; + + for (const auto& [config_def, type] : list) { + for (const auto& oit : config_def.options) + for (const std::string& t : oit.second.cli_args(oit.first)) + opts[t] = { oit.first , type }; + } + + bool parse_options = true; + for (int i = 1; i < argc; ++i) { + std::string token = argv[i]; + // Store non-option arguments in the provided vector. + if (!parse_options || !boost::starts_with(token, "-")) { + input_files.push_back(token); + continue; + } +#ifdef __APPLE__ + if (boost::starts_with(token, "-psn_")) + // OSX launcher may add a "process serial number", for example "-psn_0_989382" to the command line. + // While it is supposed to be dropped since OSX 10.9, we will rather ignore it. + continue; +#endif /* __APPLE__ */ + // Stop parsing tokens as options when -- is supplied. + if (token == "--") { + parse_options = false; + continue; + } + // Remove leading dashes (one or two). + token.erase(token.begin(), token.begin() + (boost::starts_with(token, "--") ? 2 : 1)); + // Read value when supplied in the --key=value form. + std::string value; + { + size_t equals_pos = token.find("="); + if (equals_pos != std::string::npos) { + value = token.substr(equals_pos + 1); + token.erase(equals_pos); + } + } + // Look for the cli -> option mapping. + auto it = opts.find(token); + bool no = false; + if (it == opts.end()) { + // Remove the "no-" prefix used to negate boolean options. + std::string yes_token; + if (boost::starts_with(token, "no-")) { + yes_token = token.substr(3); + it = opts.find(yes_token); + no = true; + } + if (it == opts.end()) { + boost::nowide::cerr << "Unknown option --" << token.c_str() << std::endl; + return false; + } + if (no) + token = yes_token; + } + + //const t_config_option_key& opt_key = it->second.first; + //const ConfigOptionDef& optdef = *this->option_def(opt_key); + const auto& [opt_key, type] = it->second; + const ConfigDef* config_def; + DynamicPrintConfig* config; + if (type == CliType::Input) { + config_def = &cli_input_config_def; + config = &input_config; + } + else if (type == CliType::Transformations) { + config_def = &cli_transform_config_def; + config = &transform_config; + } + else if(type == CliType::Misc) { + config_def = &cli_misc_config_def; + config = &misc_config; + } + else if(type == CliType::Actions) { + config_def = &cli_actions_config_def; + config = &actions_config; + } + else { + config_def = &print_config_def; + config = &overrides_config; + } + + const ConfigOptionDef* optdef = config_def->get(opt_key); + assert(optdef); + + // If the option type expects a value and it was not already provided, + // look for it in the next token. + if (value.empty() && optdef->type != coBool && optdef->type != coBools) { + if (i == argc - 1) { + boost::nowide::cerr << "No value supplied for --" << token.c_str() << std::endl; + return false; + } + value = argv[++i]; + } + + if (no) { + assert(optdef->type == coBool || optdef->type == coBools); + if (!value.empty()) { + boost::nowide::cerr << "Boolean options negated by the --no- prefix cannot have a value." << std::endl; + return false; + } + } + + // Store the option value. + const bool existing = config->has(opt_key); + ConfigOption* opt_base = existing ? config->option(opt_key) : optdef->create_default_option(); + if (!existing) + config->set_key_value(opt_key, opt_base); + ConfigOptionVectorBase* opt_vector = opt_base->is_vector() ? static_cast(opt_base) : nullptr; + if (opt_vector) { + if (!existing) + // remove the default values + opt_vector->clear(); + // Vector values will be chained. Repeated use of a parameter will append the parameter or parameters + // to the end of the value. + if (opt_base->type() == coBools && value.empty()) + static_cast(opt_base)->values.push_back(!no); + else + // Deserialize any other vector value (ConfigOptionInts, Floats, Percents, Points) the same way + // they get deserialized from an .ini file. For ConfigOptionStrings, that means that the C-style unescape + // will be applied for values enclosed in quotes, while values non-enclosed in quotes are left to be + // unescaped by the calling shell. + opt_vector->deserialize(value, true); + } + else if (opt_base->type() == coBool) { + if (value.empty()) + static_cast(opt_base)->value = !no; + else + opt_base->deserialize(value); + } + else if (opt_base->type() == coString) { + // Do not unescape single string values, the unescaping is left to the calling shell. + static_cast(opt_base)->value = value; + } + else { + // Just bail out if the configuration value is not understood. + ConfigSubstitutionContext context(ForwardCompatibilitySubstitutionRule::Disable); + // Any scalar value of a type different from Bool and String. + if (!config->set_deserialize_nothrow(opt_key, value, context, false)) { + boost::nowide::cerr << "Invalid value supplied for --" << token.c_str() << std::endl; + return false; + } + } + } + + // normalize override options + if (!overrides_config.empty()) + overrides_config.normalize_fdm(); + + if (!misc_config.has("config_compatibility")) { + // "config_compatibility" can be used during the loading configuration + // So, if this option wasn't set, then initialise it from default value + const ConfigOptionDef* optdef = cli_misc_config_def.get("config_compatibility"); + ConfigOption* opt_with_def_value = optdef->create_default_option(); + if (opt_with_def_value) + misc_config.set_key_value("config_compatibility", opt_with_def_value); + } + + return true; +} + static void print_help(bool include_print_options = false, PrinterTechnology printer_technology = ptAny) { boost::nowide::cout @@ -89,12 +297,19 @@ static void print_help(bool include_print_options = false, PrinterTechnology pri #endif /* SLIC3R_GUI */ << std::endl << "https://github.com/prusa3d/PrusaSlicer" << std::endl << std::endl - << "Usage: prusa-slicer [ ACTIONS ] [ TRANSFORM ] [ OPTIONS ] [ file.stl ... ]" << std::endl + << "Usage: prusa-slicer [ INPUT ] [ OPTIONS ] [ ACTIONS ] [ TRANSFORM ] [ file.stl ... ]" << std::endl; + + boost::nowide::cout + << std::endl + << "Input:" << std::endl; + cli_input_config_def.print_cli_help(boost::nowide::cout, false); + + boost::nowide::cout << std::endl << "Actions:" << std::endl; - cli_actions_config_def.print_cli_help(boost::nowide::cout, false); - // << std::endl - // << "Profiles sharing options:" << std::endl; + cli_actions_config_def.print_cli_help(boost::nowide::cout, false)//; + << std::endl + << "Profiles sharing options:" << std::endl; cli_profiles_sharing_config_def.print_cli_help(boost::nowide::cout, false); boost::nowide::cout @@ -113,7 +328,7 @@ static void print_help(bool include_print_options = false, PrinterTechnology pri << "\t1) Config keys from the command line, for example --fill-pattern=stars" << std::endl << "\t (highest priority, overwrites everything below)" << std::endl << "\t2) Config files loaded with --load" << std::endl - << "\t3) Config values loaded from amf or 3mf files" << std::endl; + << "\t3) Config values loaded from 3mf files" << std::endl; if (include_print_options) { boost::nowide::cout << std::endl; @@ -149,37 +364,44 @@ static bool can_apply_printer_technology(PrinterTechnology& printer_technology, return !invalid_other_pt; } -static bool process_profiles_sharing(const CliInParams& in) +static bool has_profile_sharing_action(const Cli& cli) { - if (in.profiles_sharing.empty()) + return cli.actions_config.has("query-printer-models") || cli.actions_config.has("query-print-filament-profiles"); +} + +static bool has_full_config_from_profiles(const Cli& cli) +{ + const DynamicPrintConfig& input = cli.input_config; + return !has_profile_sharing_action(cli) && + (input.has("print-profile") && !input.opt_string("print-profile").empty() || + input.has("material-profile") && !input.option("material-profile")->values.empty() || + input.has("printer-profile") && !input.opt_string("printer-profile").empty()); +} + +static bool process_profiles_sharing(const Cli& cli) +{ + if (!has_profile_sharing_action(cli)) return false; std::string ret; - for (auto const& opt_key : in.profiles_sharing) { - if (opt_key == "query-printer-models") { - ret = Slic3r::get_json_printer_models(get_printer_technology(in.config)); - break; - } - else - if (opt_key == "query-print-filament-profiles") { - const std::string printer_profile = in.config.opt_string("printer-profile"); - if (printer_profile.empty()) { - boost::nowide::cerr << opt_key << " error: This action requires set 'printer-profile' option" << std::endl; - return true; - } + if (cli.actions_config.has("query-printer-models")) { + ret = Slic3r::get_json_printer_models(get_printer_technology(cli.overrides_config)); + } + else if (cli.actions_config.has("query-print-filament-profiles")) { + if (cli.input_config.has("printer-profile") && !cli.input_config.opt_string("printer-profile").empty()) { + const std::string printer_profile = cli.input_config.opt_string("printer-profile"); ret = Slic3r::get_json_print_filament_profiles(printer_profile); if (ret.empty()) { - boost::nowide::cerr << opt_key << " error: Printer profile '" << printer_profile << + boost::nowide::cerr << "query-print-filament-profiles error: Printer profile '" << printer_profile << "' wasn't found among installed printers." << std::endl << "Or the request can be wrong." << std::endl; return true; } - break; } else { - boost::nowide::cerr << "error: option not implemented yet: " << opt_key << std::endl; - break; + boost::nowide::cerr << "query-print-filament-profiles error: This action requires set 'printer-profile' option" << std::endl; + return true; } } @@ -190,10 +412,8 @@ static bool process_profiles_sharing(const CliInParams& in) // use --output when available - std::string cmdline_param = in.config.opt_string("output"); - if (cmdline_param.empty()) - printf("%s", ret.c_str()); - else { + if (cli.misc_config.has("output")) { + std::string cmdline_param = cli.misc_config.opt_string("output"); // if we were supplied a directory, use it and append our automatically generated filename boost::filesystem::path cmdline_path(cmdline_param); boost::filesystem::path proposed_path = boost::filesystem::path(Slic3r::resources_dir()) / "out.json"; @@ -212,6 +432,8 @@ static bool process_profiles_sharing(const CliInParams& in) boost::nowide::cout << "Output for your request is written into " << file << std::endl; } + else + printf("%s", ret.c_str()); return true; } @@ -225,197 +447,171 @@ static void print_config_substitutions(const ConfigSubstitutions& config_substit boost::nowide::cout << "\tkey = \"" << subst.opt_def->opt_key << "\"\t loaded = \"" << subst.old_value << "\tsubstituted = \"" << subst.new_value->serialize() << "\"\n"; } -static bool apply_model_and_print_config_from_file(const std::string& file, Model& model, CliOutParams& cli) +static bool process_transform(Cli& cli, const DynamicPrintConfig& print_config, std::vector& models) { - // When loading an AMF or 3MF, config is imported as well, including the printer technology. - DynamicPrintConfig config; - ConfigSubstitutionContext config_substitutions_ctxt(cli.config_substitution_rule); - //FIXME should we check the version here? // | Model::LoadAttribute::CheckVersion ? - model = FileReader::load_model_with_config(file, &config, &config_substitutions, prusaslicer_generator_version, FileReader::LoadAttribute::AddDefaultInstances); - if (!can_apply_printer_technology(cli.printer_technology, get_printer_technology(config))) - return false; + DynamicPrintConfig& transform = cli.transform_config; + DynamicPrintConfig& actions = cli.actions_config; - print_config_substitutions(config_substitutions_ctxt.substitutions, file); + const Vec2crd gap{ s_multiple_beds.get_bed_gap() }; + arr2::ArrangeBed bed = arr2::to_arrange_bed(get_bed_shape(print_config), gap); + arr2::ArrangeSettings arrange_cfg; + if (transform.has("merge") || transform.has("duplicate")) + arrange_cfg.set_distance_from_objects(min_object_distance(print_config)); - // config is applied to cli.print_config before the current m_config values. - config += std::move(cli.print_config); - cli.print_config = std::move(config); - - return true; -} - -static bool has_print_action(const DynamicPrintAndCLIConfig& config) -{ - return config.opt_bool("export_gcode") || config.opt_bool("export_sla"); -} - -static bool process_transform( CliInParams& in, - CliOutParams& out, - const arr2::ArrangeBed& bed, - const arr2::ArrangeSettingsView& arrange_cfg) -{ - for (auto const& opt_key : in.transforms) { - if (opt_key == "merge") { - Model m; - for (auto& model : out.models) - for (ModelObject* o : model.objects) - m.add_object(*o); - // Rearrange instances unless --dont-arrange is supplied - if (!in.config.opt_bool("dont_arrange")) { - m.add_default_instances(); - if (has_print_action(in.config)) - arrange_objects(m, bed, arrange_cfg); - else - arrange_objects(m, arr2::InfiniteBed{}, arrange_cfg); - } - out.models.clear(); - out.models.emplace_back(std::move(m)); + if (transform.has("merge")) { + Model m; + for (auto& model : models) + for (ModelObject* o : model.objects) + m.add_object(*o); + // Rearrange instances unless --dont-arrange is supplied + if (!transform.has("dont_arrange") && !transform.opt_bool("dont_arrange")) { + m.add_default_instances(); + if (actions.has("slice")) + arrange_objects(m, bed, arrange_cfg); + else + arrange_objects(m, arr2::InfiniteBed{}, arrange_cfg);//?????? } - else if (opt_key == "duplicate") { - for (auto& model : out.models) { - const bool all_objects_have_instances = std::none_of( - model.objects.begin(), model.objects.end(), - [](ModelObject* o) { return o->instances.empty(); } - ); + models.clear(); + models.emplace_back(std::move(m)); + } - int dups = in.config.opt_int("duplicate"); - if (!all_objects_have_instances) model.add_default_instances(); + if (transform.has("duplicate")) { + for (auto& model : models) { + const bool all_objects_have_instances = std::none_of( + model.objects.begin(), model.objects.end(), + [](ModelObject* o) { return o->instances.empty(); } + ); - try { - if (dups > 1) { - // if all input objects have defined position(s) apply duplication to the whole model - duplicate(model, size_t(dups), bed, arrange_cfg); - } - else { - arrange_objects(model, bed, arrange_cfg); - } + int dups = transform.opt_int("duplicate"); + if (!all_objects_have_instances) model.add_default_instances(); + + try { + if (dups > 1) { + // if all input objects have defined position(s) apply duplication to the whole model + duplicate(model, size_t(dups), bed, arrange_cfg); } - catch (std::exception& ex) { - boost::nowide::cerr << "error: " << ex.what() << std::endl; - return false; + else { + arrange_objects(model, bed, arrange_cfg); } } - } - else if (opt_key == "duplicate_grid") { - std::vector& ints = in.config.option("duplicate_grid")->values; - const int x = ints.size() > 0 ? ints.at(0) : 1; - const int y = ints.size() > 1 ? ints.at(1) : 1; - FullPrintConfig fff_print_config; - const double distance = fff_print_config.duplicate_distance.value; - for (auto& model : out.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") { - out.user_center_specified = true; - for (auto& model : out.models) { - model.add_default_instances(); - // this affects instances: - model.center_instances_around_point(in.config.option("center")->value); - // this affects volumes: - //FIXME Vojtech: Who knows why the complete model should be aligned with Z as a single rigid body? - //model.align_to_ground(); - BoundingBoxf3 bbox; - for (ModelObject* model_object : model.objects) - // We are interested into the Z span only, therefore it is sufficient to measure the bounding box of the 1st instance only. - bbox.merge(model_object->instance_bounding_box(0, false)); - for (ModelObject* model_object : model.objects) - for (ModelInstance* model_instance : model_object->instances) - model_instance->set_offset(Z, model_instance->get_offset(Z) - bbox.min.z()); - } - } - else if (opt_key == "align_xy") { - const Vec2d& p = in.config.option("align_xy")->value; - for (auto& model : out.models) { - BoundingBoxf3 bb = model.bounding_box_exact(); - // 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 == "ensure_on_bed") { - // do nothing, the value is used later - } - else if (opt_key == "rotate") { - for (auto& model : out.models) - for (auto& o : model.objects) - // this affects volumes: - o->rotate(Geometry::deg2rad(in.config.opt_float(opt_key)), Z); - } - else if (opt_key == "rotate_x") { - for (auto& model : out.models) - for (auto& o : model.objects) - // this affects volumes: - o->rotate(Geometry::deg2rad(in.config.opt_float(opt_key)), X); - } - else if (opt_key == "rotate_y") { - for (auto& model : out.models) - for (auto& o : model.objects) - // this affects volumes: - o->rotate(Geometry::deg2rad(in.config.opt_float(opt_key)), Y); - } - else if (opt_key == "scale") { - for (auto& model : out.models) - for (auto& o : model.objects) - // this affects volumes: - o->scale(in.config.get_abs_value(opt_key, 1)); - } - else if (opt_key == "scale_to_fit") { - const Vec3d& opt = in.config.opt(opt_key)->value; - if (opt.x() <= 0 || opt.y() <= 0 || opt.z() <= 0) { - boost::nowide::cerr << "--scale-to-fit requires a positive volume" << std::endl; + catch (std::exception& ex) { + boost::nowide::cerr << "error: " << ex.what() << std::endl; return false; } - for (auto& model : out.models) - for (auto& o : model.objects) - // this affects volumes: - o->scale_to_fit(opt); } - else if (opt_key == "cut") { - std::vector new_models; - for (auto& model : out.models) { - model.translate(0, 0, -model.bounding_box_exact().min.z()); // align to z = 0 - size_t num_objects = model.objects.size(); - for (size_t i = 0; i < num_objects; ++i) { - Cut cut(model.objects.front(), 0, Geometry::translation_transform(in.config.opt_float("cut") * Vec3d::UnitZ()), - ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::PlaceOnCutUpper); - auto cut_objects = cut.perform_with_plane(); - for (ModelObject* obj : cut_objects) - model.add_object(*obj); - model.delete_object(size_t(0)); - } - } + } + if (transform.has("duplicate_grid")) { + std::vector& ints = transform.option("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 = print_config.opt_float("duplicate_distance"); + for (auto& model : models) + model.duplicate_objects_grid(x, y, (distance > 0) ? distance : 6); // TODO: this is not the right place for setting a default + } + + if (transform.has("center")) { + for (auto& model : models) { + model.add_default_instances(); + // this affects instances: + model.center_instances_around_point(transform.option("center")->value); + // this affects volumes: + //FIXME Vojtech: Who knows why the complete model should be aligned with Z as a single rigid body? + //model.align_to_ground(); + BoundingBoxf3 bbox; + for (ModelObject* model_object : model.objects) + // We are interested into the Z span only, therefore it is sufficient to measure the bounding box of the 1st instance only. + bbox.merge(model_object->instance_bounding_box(0, false)); + for (ModelObject* model_object : model.objects) + for (ModelInstance* model_instance : model_object->instances) + model_instance->set_offset(Z, model_instance->get_offset(Z) - bbox.min.z()); + } + } - // TODO: copy less stuff around using pointers - out.models = new_models; + if (transform.has("align_xy")) { + const Vec2d& p = transform.option("align_xy")->value; + for (auto& model : models) { + BoundingBoxf3 bb = model.bounding_box_exact(); + // this affects volumes: + model.translate(-(bb.min.x() - p.x()), -(bb.min.y() - p.y()), -bb.min.z()); + } + } - if (in.actions.empty()) - in.actions.push_back("export_stl"); - } - else if (opt_key == "split") { - for (Model& model : out.models) { - size_t num_objects = model.objects.size(); - for (size_t i = 0; i < num_objects; ++i) { - ModelObjectPtrs new_objects; - model.objects.front()->split(&new_objects); - model.delete_object(size_t(0)); - } - } - } - else if (opt_key == "delete-after-load") { - out.delete_after_load = true; - } - else { - boost::nowide::cerr << "error: option not implemented yet: " << opt_key << std::endl; + if (transform.has("rotate")) { + for (auto& model : models) + for (auto& o : model.objects) + // this affects volumes: + o->rotate(Geometry::deg2rad(transform.opt_float("rotate")), Z); + } + if (transform.has("rotate_x")) { + for (auto& model : models) + for (auto& o : model.objects) + // this affects volumes: + o->rotate(Geometry::deg2rad(transform.opt_float("rotate_x")), X); + } + if (transform.has("rotate_y")) { + for (auto& model : models) + for (auto& o : model.objects) + // this affects volumes: + o->rotate(Geometry::deg2rad(transform.opt_float("rotate_y")), Y); + } + + if (transform.has("scale")) { + for (auto& model : models) + for (auto& o : model.objects) + // this affects volumes: + o->scale(transform.get_abs_value("scale", 1)); + } + if (transform.has("scale_to_fit")) { + const Vec3d& opt = transform.opt("scale_to_fit")->value; + if (opt.x() <= 0 || opt.y() <= 0 || opt.z() <= 0) { + boost::nowide::cerr << "--scale-to-fit requires a positive volume" << std::endl; return false; } + for (auto& model : models) + for (auto& o : model.objects) + // this affects volumes: + o->scale_to_fit(opt); + } + + if (transform.has("cut")) { + std::vector new_models; + for (auto& model : models) { + model.translate(0, 0, -model.bounding_box_exact().min.z()); // align to z = 0 + size_t num_objects = model.objects.size(); + for (size_t i = 0; i < num_objects; ++i) { + Cut cut(model.objects.front(), 0, Geometry::translation_transform(transform.opt_float("cut") * Vec3d::UnitZ()), + ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::PlaceOnCutUpper); + auto cut_objects = cut.perform_with_plane(); + for (ModelObject* obj : cut_objects) + model.add_object(*obj); + model.delete_object(size_t(0)); + } + } + + // TODO: copy less stuff around using pointers + models = new_models; + + if (actions.empty()) { + // cutting transformations are setting an "export" action. + actions.set_key_value("export_stl", new ConfigOptionBool(true)); + } + } + + if (transform.has("split")) { + for (Model& model : models) { + size_t num_objects = model.objects.size(); + for (size_t i = 0; i < num_objects; ++i) { + ModelObjectPtrs new_objects; + model.objects.front()->split(&new_objects); + model.delete_object(size_t(0)); + } + } } // All transforms have been dealt with. Now ensure that the objects are on bed. // (Unless the user said otherwise.) - if (in.config.opt_bool("ensure_on_bed")) - for (auto& model : out.models) + if (!transform.has("ensure_on_bed") || transform.opt_bool("ensure_on_bed")) + for (auto& model : models) for (auto& o : model.objects) o->ensure_on_bed(); @@ -423,7 +619,18 @@ static bool process_transform( CliInParams& in, } -static std::string output_filepath(const Model& model, IO::ExportFormat format, const std::string cmdline_param) + +namespace IO { + enum ExportFormat : int { + OBJ, + STL, + // SVG, + TMF, + Gcode + }; +} + +static std::string output_filepath(const Model& model, IO::ExportFormat format, const std::string& cmdline_param) { std::string ext; switch (format) { @@ -445,7 +652,7 @@ static std::string output_filepath(const Model& model, IO::ExportFormat format, return proposed_path.string(); } -static bool export_models(std::vector& models, IO::ExportFormat format, const std::string cmdline_param) +static bool export_models(std::vector& models, IO::ExportFormat format, const std::string& cmdline_param) { for (Model& model : models) { const std::string path = output_filepath(model, format, cmdline_param); @@ -466,236 +673,241 @@ static bool export_models(std::vector& models, IO::ExportFormat format, c return true; } -static bool process_actions( CliInParams& in, - CliOutParams& out, - const arr2::ArrangeBed& bed, - const arr2::ArrangeSettingsView& arrange_cfg) +static std::function get_thumbnail_generator_cli(Print& fff_print) { + if (!fff_print.model().objects.empty() && boost::iends_with(fff_print.model().objects.front()->input_file, ".3mf")) { + std::string filename = fff_print.model().objects.front()->input_file; + return [filename](const ThumbnailsParams&) { + ThumbnailsList list_out; - for (auto const& opt_key : in.actions) { - if (opt_key == "help") { - print_help(); - } - else if (opt_key == "help_fff") { - print_help(true, ptFFF); - } - else if (opt_key == "help_sla") { - print_help(true, ptSLA); - } - else if (opt_key == "save") { - //FIXME check for mixing the FFF / SLA parameters. - // or better save fff_print_config vs. sla_print_config - out.print_config.save(in.config.opt_string("save")); - } - else if (opt_key == "info") { - // --info works on unrepaired model - for (Model& model : out.models) { - model.add_default_instances(); - model.print_info(); - } - } - else if (opt_key == "export_stl") { - for (auto& model : out.models) - model.add_default_instances(); - if (!export_models(out.models, IO::STL, in.config.opt_string("output"))) - return 1; - } - else if (opt_key == "export_obj") { - for (auto& model : out.models) - model.add_default_instances(); - if (!export_models(out.models, IO::OBJ, in.config.opt_string("output"))) - return 1; - } - else if (opt_key == "export_3mf") { - if (!export_models(out.models, IO::TMF, in.config.opt_string("output"))) - return 1; - } - else if (opt_key == "export_gcode" || opt_key == "export_sla" || opt_key == "slice") { - if (opt_key == "export_gcode" && out.printer_technology == ptSLA) { - boost::nowide::cerr << "error: cannot export G-code for an FFF configuration" << std::endl; - return 1; - } - else if (opt_key == "export_sla" && out.printer_technology == ptFFF) { - boost::nowide::cerr << "error: cannot export SLA slices for a SLA configuration" << std::endl; - return 1; - } - // Make a copy of the model if the current action is not the last action, as the model may be - // modified by the centering and such. - Model model_copy; - bool make_copy = &opt_key != &in.actions.back(); - for (Model& model_in : out.models) { - if (make_copy) - model_copy = model_in; - Model& model = make_copy ? model_copy : model_in; - // 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). - std::string outfile = in.config.opt_string("output"); - Print fff_print; - SLAPrint sla_print; - sla_print.set_status_callback( - [](const PrintBase::SlicingStatus& s) - { - if (s.percent >= 0) { // FIXME: is this sufficient? - printf("%3d%s %s\n", s.percent, "% =>", s.text.c_str()); - std::fflush(stdout); - } - }); + mz_zip_archive archive; + mz_zip_zero_struct(&archive); - PrintBase* print = (out.printer_technology == ptFFF) ? static_cast(&fff_print) : static_cast(&sla_print); - if (!in.config.opt_bool("dont_arrange")) { - if (out.user_center_specified) { - Vec2d c = in.config.option("center")->value; - arrange_objects(model, arr2::InfiniteBed{ scaled(c) }, arrange_cfg); - } - else - arrange_objects(model, bed, arrange_cfg); + if (!open_zip_reader(&archive, filename)) + return list_out; + mz_uint num_entries = mz_zip_reader_get_num_files(&archive); + mz_zip_archive_file_stat stat; + + int index = mz_zip_reader_locate_file(&archive, "Metadata/thumbnail.png", nullptr, 0); + if (index < 0 || !mz_zip_reader_file_stat(&archive, index, &stat)) + return list_out; + std::string buffer; + buffer.resize(int(stat.m_uncomp_size)); + mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, buffer.data(), (size_t)stat.m_uncomp_size, 0); + if (res == 0) + return list_out; + close_zip_reader(&archive); + + std::vector data; + unsigned width = 0; + unsigned height = 0; + png::decode_png(buffer, data, width, height); + + { + // Flip the image vertically so it matches the convention in Thumbnails generator. + const int row_size = width * 4; // Each pixel is 4 bytes (RGBA) + std::vector temp_row(row_size); + for (int i = 0; i < height / 2; ++i) { + unsigned char* top_row = &data[i * row_size]; + unsigned char* bottom_row = &data[(height - i - 1) * row_size]; + std::copy(bottom_row, bottom_row + row_size, temp_row.begin()); + std::copy(top_row, top_row + row_size, bottom_row); + std::copy(temp_row.begin(), temp_row.end(), top_row); } - if (out.printer_technology == ptFFF) { - for (auto* mo : model.objects) - fff_print.auto_assign_extruders(mo); + } + + ThumbnailData th; + th.set(width, height); + th.pixels = data; + list_out.push_back(th); + return list_out; + }; + } + + return [](const ThumbnailsParams&) ->ThumbnailsList { return {}; }; +} + +static bool process_actions(Cli& cli, const DynamicPrintConfig& print_config, std::vector& models) +{ + DynamicPrintConfig& actions = cli.actions_config; + DynamicPrintConfig& transform = cli.transform_config; + + // doesn't need any aditional input + + if (actions.has("help")) { + print_help(); + } + if (actions.has("help_fff")) { + print_help(true, ptFFF); + } + if (actions.has("help_sla")) { + print_help(true, ptSLA); + } + + if (actions.has("info")) { + if (models.empty()) { + boost::nowide::cerr << "error: cannot show info for empty models." << std::endl; + return 1; + } + // --info works on unrepaired model + for (Model& model : models) { + model.add_default_instances(); + model.print_info(); + } + } + + if (actions.has("save")) { + //FIXME check for mixing the FFF / SLA parameters. + // or better save fff_print_config vs. sla_print_config + print_config.save(actions.opt_string("save")); + } + + if (models.empty() && (actions.has("export_stl") || actions.has("export_obj") || actions.has("export_3mf"))) { + boost::nowide::cerr << "error: cannot export empty models." << std::endl; + return 1; + } + + const std::string output = cli.misc_config.has("output") ? cli.misc_config.opt_string("output") : ""; + + if (actions.has("export_stl")) { + for (auto& model : models) + model.add_default_instances(); + if (!export_models(models, IO::STL, output)) + return 1; + } + if (actions.has("export_obj")) { + for (auto& model : models) + model.add_default_instances(); + if (!export_models(models, IO::OBJ, output)) + return 1; + } + if (actions.has("export_3mf")) { + if (!export_models(models, IO::TMF, output)) + return 1; + } + + if (actions.has("slice")) { + PrinterTechnology printer_technology = Preset::printer_technology(print_config); + + const Vec2crd gap{ s_multiple_beds.get_bed_gap() }; + arr2::ArrangeBed bed = arr2::to_arrange_bed(get_bed_shape(print_config), gap); + arr2::ArrangeSettings arrange_cfg; + arrange_cfg.set_distance_from_objects(min_object_distance(print_config)); + + for (Model& model : 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). + if (!transform.has("dont_arrange") || !transform.opt_bool("dont_arrange")) { + if (transform.has("center")) { + Vec2d c = transform.option("center")->value; + arrange_objects(model, arr2::InfiniteBed{ scaled(c) }, arrange_cfg); } - print->apply(model, out.print_config); - std::string err = print->validate(); - if (!err.empty()) { - boost::nowide::cerr << err << std::endl; - return 1; - } - if (print->empty()) - boost::nowide::cout << "Nothing to print for " << outfile << " . Either the print is empty or no object is fully inside the print volume." << std::endl; else - try { - std::string outfile_final; - print->process(); - if (out.printer_technology == ptFFF) { - - - - - - std::function thumbnail_generator_cli; - if (!fff_print.model().objects.empty() && boost::iends_with(fff_print.model().objects.front()->input_file, ".3mf")) { - std::string filename = fff_print.model().objects.front()->input_file; - thumbnail_generator_cli = [filename](const ThumbnailsParams&) { - ThumbnailsList list_out; - - mz_zip_archive archive; - mz_zip_zero_struct(&archive); - - if (!open_zip_reader(&archive, filename)) - return list_out; - mz_uint num_entries = mz_zip_reader_get_num_files(&archive); - mz_zip_archive_file_stat stat; - - int index = mz_zip_reader_locate_file(&archive, "Metadata/thumbnail.png", nullptr, 0); - if (index < 0 || !mz_zip_reader_file_stat(&archive, index, &stat)) - return list_out; - std::string buffer; - buffer.resize(int(stat.m_uncomp_size)); - mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, buffer.data(), (size_t)stat.m_uncomp_size, 0); - if (res == 0) - return list_out; - close_zip_reader(&archive); - - std::vector data; - unsigned width = 0; - unsigned height = 0; - png::decode_png(buffer, data, width, height); - - { - // Flip the image vertically so it matches the convention in Thumbnails generator. - const int row_size = width * 4; // Each pixel is 4 bytes (RGBA) - std::vector temp_row(row_size); - for (int i = 0; i < height / 2; ++i) { - unsigned char* top_row = &data[i * row_size]; - unsigned char* bottom_row = &data[(height - i - 1) * row_size]; - std::copy(bottom_row, bottom_row + row_size, temp_row.begin()); - std::copy(top_row, top_row + row_size, bottom_row); - std::copy(temp_row.begin(), temp_row.end(), top_row); - } - } - - ThumbnailData th; - th.set(width, height); - th.pixels = data; - list_out.push_back(th); - return list_out; - }; - } - - - - - - // The outfile is processed by a PlaceholderParser. - outfile = fff_print.export_gcode(outfile, nullptr, thumbnail_generator_cli); - outfile_final = fff_print.print_statistics().finalize_output_path(outfile); - } - else { - outfile = sla_print.output_filepath(outfile); - // We need to finalize the filename beforehand because the export function sets the filename inside the zip metadata - outfile_final = sla_print.print_statistics().finalize_output_path(outfile); - sla_print.export_print(outfile_final); - } - if (outfile != outfile_final) { - if (Slic3r::rename_file(outfile, outfile_final)) { - boost::nowide::cerr << "Renaming file " << outfile << " to " << outfile_final << " failed" << std::endl; - return false; - } - outfile = outfile_final; - } - // Run the post-processing scripts if defined. - run_post_process_scripts(outfile, fff_print.full_print_config()); - boost::nowide::cout << "Slicing result exported to " << outfile << std::endl; - } - catch (const std::exception& ex) { - boost::nowide::cerr << ex.what() << std::endl; - return false; - } -/* - print.center = ! in.config.has("center") - && ! in.config.has("align_xy") - && ! in.config.opt_bool("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; -*/ + arrange_objects(model, bed, arrange_cfg); } - } - else { - boost::nowide::cerr << "error: option not supported yet: " << opt_key << std::endl; - return false; + + Print fff_print; + SLAPrint sla_print; + sla_print.set_status_callback( [](const PrintBase::SlicingStatus& s) { + if (s.percent >= 0) { // FIXME: is this sufficient? + printf("%3d%s %s\n", s.percent, "% =>", s.text.c_str()); + std::fflush(stdout); + } + }); + + PrintBase* print = (printer_technology == ptFFF) ? static_cast(&fff_print) : static_cast(&sla_print); + if (printer_technology == ptFFF) { + for (auto* mo : model.objects) + fff_print.auto_assign_extruders(mo); + } + print->apply(model, print_config); + std::string err = print->validate(); + if (!err.empty()) { + boost::nowide::cerr << err << std::endl; + return 1; + } + + std::string outfile = output; + + if (print->empty()) + boost::nowide::cout << "Nothing to print for " << outfile << " . Either the print is empty or no object is fully inside the print volume." << std::endl; + else + try { + std::string outfile_final; + print->process(); + if (printer_technology == ptFFF) { + // The outfile is processed by a PlaceholderParser. + outfile = fff_print.export_gcode(outfile, nullptr, get_thumbnail_generator_cli(fff_print)); + outfile_final = fff_print.print_statistics().finalize_output_path(outfile); + } + else { + outfile = sla_print.output_filepath(outfile); + // We need to finalize the filename beforehand because the export function sets the filename inside the zip metadata + outfile_final = sla_print.print_statistics().finalize_output_path(outfile); + sla_print.export_print(outfile_final); + } + if (outfile != outfile_final) { + if (Slic3r::rename_file(outfile, outfile_final)) { + boost::nowide::cerr << "Renaming file " << outfile << " to " << outfile_final << " failed" << std::endl; + return false; + } + outfile = outfile_final; + } + // Run the post-processing scripts if defined. + run_post_process_scripts(outfile, fff_print.full_print_config()); + boost::nowide::cout << "Slicing result exported to " << outfile << std::endl; + } + catch (const std::exception& ex) { + boost::nowide::cerr << ex.what() << std::endl; + return false; + } + } } return true; } +#ifdef SLIC3R_GUI +static int start_gui_with_params(GUI::GUI_InitParams& params) +{ + #if !defined(_WIN32) && !defined(__APPLE__) + // likely some linux / unix system + const char* display = boost::nowide::getenv("DISPLAY"); + // const char *wayland_display = boost::nowide::getenv("WAYLAND_DISPLAY"); + //if (! ((display && *display) || (wayland_display && *wayland_display))) { + if (!(display && *display)) { + // DISPLAY not set. + boost::nowide::cerr << "DISPLAY not set, GUI mode not available." << std::endl << std::endl; + this->print_help(false); + // Indicate an error. + return 1; + } + #endif // some linux / unix system + return Slic3r::GUI::GUI_Run(params); +} -int CLI::run(int argc, char **argv) +static int start_as_gcode_viewer(const Cli& cli, GUI::GUI_InitParams& gui_params) +{ + if (cli.input_files.size() > 1) { + boost::nowide::cerr << "You can open only one .gcode file at a time in GCodeViewer" << std::endl; + return 1; + } + + if (cli.input_files.size() == 1) { + if (!is_gcode_file(cli.input_files[0]) || !boost::filesystem::exists(cli.input_files[0])) { + boost::nowide::cerr << "Input file isn't a .gcode file or doesn't exist. GCodeViewer can't be start." << std::endl; + return 1; + } + } + + return start_gui_with_params(gui_params); +} +#endif // SLIC3R_GUI + +static bool setup_general() { // Mark the main thread for the debugger and for runtime checks. set_current_thread_name("slic3r_main"); @@ -713,359 +925,28 @@ int CLI::run(int argc, char **argv) ::setenv("WEBKIT_DISABLE_DMABUF_RENDERER", "1", /* replace */ false); #endif - // Switch boost::filesystem to utf8. + // Switch boost::filesystem to utf8. try { boost::nowide::nowide_filesystem(); - } catch (const std::runtime_error& ex) { + } + catch (const std::runtime_error& ex) { std::string caption = std::string(SLIC3R_APP_NAME) + " Error"; std::string text = std::string("An error occured while setting up locale.\n") + ( #if !defined(_WIN32) && !defined(__APPLE__) - // likely some linux system - "You may need to reconfigure the missing locales, likely by running the \"locale-gen\" and \"dpkg-reconfigure locales\" commands.\n" + // likely some linux system + "You may need to reconfigure the missing locales, likely by running the \"locale-gen\" and \"dpkg-reconfigure locales\" commands.\n" #endif - SLIC3R_APP_NAME " will now terminate.\n\n") + ex.what(); + SLIC3R_APP_NAME " will now terminate.\n\n") + ex.what(); #if defined(_WIN32) && defined(SLIC3R_GUI) MessageBoxA(NULL, text.c_str(), caption.c_str(), MB_OK | MB_ICONERROR); #endif boost::nowide::cerr << text.c_str() << std::endl; - return 1; + return false; } - CliInParams cli_in; - if (! this->setup(argc, argv, cli_in)) - return 1; - - if (process_profiles_sharing(cli_in)) - return 1; - - CliOutParams cli_out; - - /* ysFIXME it looks like m_extra_config is a redundant parametr - * because of m_extra_config is empty from the very beginning and is applied with ignorring of the non-existing options - * - m_extra_config.apply(m_config, true); - m_extra_config.normalize_fdm(); - */ - - cli_out.printer_technology = get_printer_technology(cli_in.config); - - bool start_gui = cli_in.actions.empty() && - // cutting transformations are setting an "export" action. - std::find(cli_in.transforms.begin(), cli_in.transforms.end(), "cut") == cli_in.transforms.end(); - bool start_downloader = false; - std::string download_url; - bool start_as_gcodeviewer = -#ifdef _WIN32 - false; -#else - // On Unix systems, the prusa-slicer binary may be symlinked to give the application a different meaning. - boost::algorithm::iends_with(boost::filesystem::path(argv[0]).filename().string(), "gcodeviewer"); -#endif // _WIN32 - - const std::vector &load_configs = cli_in.config.option("load", true)->values; - cli_out.config_substitution_rule = cli_in.config.option>("config_compatibility", true)->value; - - // load config files supplied via --load - for (auto const &file : load_configs) { - if (! boost::filesystem::exists(file)) { - if (cli_in.config.opt_bool("ignore_nonexistent_config")) { - continue; - } else { - boost::nowide::cerr << "No such file: " << file << std::endl; - return 1; - } - } - DynamicPrintConfig config; - ConfigSubstitutions config_substitutions; - try { - config_substitutions = config.load(file, cli_out.config_substitution_rule); - } catch (std::exception &ex) { - boost::nowide::cerr << "Error while reading config file \"" << file << "\": " << ex.what() << std::endl; - return 1; - } - - if (!can_apply_printer_technology(cli_out.printer_technology, get_printer_technology(config))) - return 1; - - print_config_substitutions(config_substitutions, file); - - config.normalize_fdm(); - cli_out.print_config.apply(config); - } - - bool has_config_from_profiles = cli_in.profiles_sharing.empty() && - (!cli_in.config.opt_string("print-profile").empty() || - !cli_in.config.option("material-profile")->values.empty() || - !cli_in.config.opt_string("printer-profile").empty() ); - - // load config from profiles set - if (has_config_from_profiles) { - Slic3r::DynamicPrintConfig config = {}; - std::string errors = Slic3r::load_full_print_config(cli_in.config.opt_string("print-profile"), - cli_in.config.option("material-profile")->values, - cli_in.config.opt_string("printer-profile"), - config, cli_out.printer_technology); - if (!errors.empty()) { - boost::nowide::cerr << errors << std::endl; - return 1; - } - - if (!can_apply_printer_technology(cli_out.printer_technology, get_printer_technology(config))) - return 1; - - config.normalize_fdm(); - cli_out.print_config.apply(config); - } - -#ifdef SLIC3R_GUI - if (cli_in.config.has("webdev")) { - Utils::ServiceConfig::instance().set_webdev_enabled(cli_in.config.opt_bool("webdev")); - } - std::vector::iterator it; - bool opengl_aa = false; - it = std::find(cli_in.actions.begin(), cli_in.actions.end(), "opengl-aa"); - if (it != cli_in.actions.end()) { - start_gui = true; - opengl_aa = true; - cli_in.actions.erase(it); - } -#if SLIC3R_OPENGL_ES - // are we starting as gcodeviewer ? - for (auto it = cli_in.actions.begin(); it != cli_in.actions.end(); ++it) { - if (*it == "gcodeviewer") { - start_gui = true; - start_as_gcodeviewer = true; - cli_in.actions.erase(it); - break; - } - } -#else - std::pair opengl_version = { 0, 0 }; - bool opengl_debug = false; - bool opengl_compatibility_profile = false; - - // search for special keys into command line parameters - it = std::find(cli_in.actions.begin(), cli_in.actions.end(), "gcodeviewer"); - if (it != cli_in.actions.end()) { - start_gui = true; - start_as_gcodeviewer = true; - cli_in.actions.erase(it); - } - - it = std::find(cli_in.actions.begin(), cli_in.actions.end(), "opengl-version"); - if (it != cli_in.actions.end()) { - const Semver opengl_minimum = Semver(3,2,0); - const std::string opengl_version_str = cli_in.config.opt_string("opengl-version"); - boost::optional semver = Semver::parse(opengl_version_str); - if (semver.has_value() && (*semver) >= opengl_minimum ) { - opengl_version.first = semver->maj(); - opengl_version.second = semver->min(); - if (std::find(Slic3r::GUI::OpenGLVersions::core.begin(), Slic3r::GUI::OpenGLVersions::core.end(), std::make_pair(opengl_version.first, opengl_version.second)) == Slic3r::GUI::OpenGLVersions::core.end()) { - opengl_version = { 0, 0 }; - boost::nowide::cerr << "Required OpenGL version " << opengl_version_str << " not recognized.\n Option 'opengl-version' ignored." << std::endl; - } - } else - boost::nowide::cerr << "Required OpenGL version " << opengl_version_str << " is invalid. Must be greater than or equal to " << - opengl_minimum.to_string() << "\n Option 'opengl-version' ignored." << std::endl; - start_gui = true; - cli_in.actions.erase(it); - } - - it = std::find(cli_in.actions.begin(), cli_in.actions.end(), "opengl-compatibility"); - if (it != cli_in.actions.end()) { - start_gui = true; - opengl_compatibility_profile = true; - // reset version as compatibility profile always take the highest version - // supported by the graphic card - opengl_version = std::make_pair(0, 0); - cli_in.actions.erase(it); - } - - it = std::find(cli_in.actions.begin(), cli_in.actions.end(), "opengl-debug"); - if (it != cli_in.actions.end()) { - start_gui = true; - opengl_debug = true; - cli_in.actions.erase(it); - } -#endif // SLIC3R_OPENGL_ES -#else // SLIC3R_GUI - // If there is no GUI, we shall ignore the parameters. Remove them from the list. - for (const std::string& s : { "opengl-version", "opengl-compatibility", "opengl-debug", "opengl-aa", "gcodeviewer" }) { - auto it = std::find(cli_in.actions.cbegin(), cli_in.actions.cend(), s); - if (it != cli_in.actions.end()) { - boost::nowide::cerr << "Parameter '" << s << "' is ignored, this PrusaSlicer build is CLI only." << std::endl; - cli_in.actions.erase(it); - } - } -#endif // SLIC3R_GUI - - - // Read input file(s) if any. - for (const std::string& file : cli_in.input_files) - if (is_gcode_file(file) && boost::filesystem::exists(file)) { - start_as_gcodeviewer = true; - break; - } - - if (!start_as_gcodeviewer) { - for (const std::string& file : cli_in.input_files) { - if (boost::starts_with(file, "prusaslicer://")) { - start_downloader = true; - download_url = file; - continue; - } - if (!boost::filesystem::exists(file)) { - boost::nowide::cerr << "No such file: " << file << std::endl; - exit(1); - } - Model model; - try { - if (has_config_from_profiles) - model = FileReader::load_model(file); - else if (!apply_model_and_print_config_from_file(file, model, cli_out)) - return 1; - - // If model for slicing is loaded from 3mf file, then its geometry has to be used and arrange couldn't be apply for this model. - if ((boost::algorithm::iends_with(file, ".3mf") || boost::algorithm::iends_with(file, ".zip")) && !cli_in.config.opt_bool("dont_arrange")) { - //So, check a state of "dont_arrange" parameter and set it to true, if its value is false. - cli_in.config.set_key_value("dont_arrange", new ConfigOptionBool(true)); - } - } - catch (std::exception& e) { - boost::nowide::cerr << file << ": " << e.what() << std::endl; - return 1; - } - if (model.objects.empty()) { - boost::nowide::cerr << "Error: file is empty: " << file << std::endl; - continue; - } - cli_out.models.push_back(model); - } - } - - if (!start_gui) { - const auto* post_process = cli_out.print_config.opt("post_process"); - if (post_process != nullptr && !post_process->values.empty()) { - boost::nowide::cout << "\nA post-processing script has been detected in the config data:\n\n"; - for (const auto& s : post_process->values) { - boost::nowide::cout << "> " << s << "\n"; - } - boost::nowide::cout << "\nContinue(Y/N) ? "; - char in; - boost::nowide::cin >> in; - if (in != 'Y' && in != 'y') - return 0; - } - } - - /* ysFIXME - // Apply command line options to a more specific DynamicPrintConfig which provides normalize() - // (command line options override --load files) - cli.print_config.apply(cli_in.extra_config, true); - // Normalizing after importing the 3MFs / AMFs - cli.print_config.normalize_fdm(); - */ - - if (cli_out.printer_technology == ptUnknown) - cli_out.printer_technology = std::find(cli_in.actions.begin(), cli_in.actions.end(), "export_sla") == cli_in.actions.end() ? ptFFF : ptSLA; - cli_out.print_config.option>("printer_technology", true)->value = cli_out.printer_technology; - - // Initialize full print configs for both the FFF and SLA technologies. - FullPrintConfig fff_print_config; - SLAFullPrintConfig sla_print_config; - - // Synchronize the default parameters and the ones received on the command line. - if (cli_out.printer_technology == ptFFF) { - fff_print_config.apply(cli_out.print_config, true); - cli_out.print_config.apply(fff_print_config, true); - } else { - assert(cli_out.printer_technology == ptSLA); - sla_print_config.output_filename_format.value = "[input_filename_base].sl1"; - - // The default bed shape should reflect the default display parameters - // and not the fff defaults. - double w = sla_print_config.display_width.getFloat(); - double h = sla_print_config.display_height.getFloat(); - sla_print_config.bed_shape.values = { Vec2d(0, 0), Vec2d(w, 0), Vec2d(w, h), Vec2d(0, h) }; - - sla_print_config.apply(cli_out.print_config, true); - cli_out.print_config.apply(sla_print_config, true); - } - { - std::string validity = cli_out.print_config.validate(); - if (! validity.empty()) { - boost::nowide::cerr << "Error: The composite configation is not valid: " << validity << std::endl; - return 1; - } - } - - const Vec2crd gap{s_multiple_beds.get_bed_gap()}; - arr2::ArrangeBed bed = arr2::to_arrange_bed(get_bed_shape(cli_out.print_config), gap); - arr2::ArrangeSettings arrange_cfg; - arrange_cfg.set_distance_from_objects(min_object_distance(cli_out.print_config)); - - // Loop through transform options. - if (!process_transform(cli_in, cli_out, bed, arrange_cfg)) - return 1; - - // loop through action options - if (!process_actions(cli_in, cli_out, bed, arrange_cfg)) - return 1; - - if (start_gui) { -#ifdef SLIC3R_GUI - #if !defined(_WIN32) && !defined(__APPLE__) - // likely some linux / unix system - const char *display = boost::nowide::getenv("DISPLAY"); - // const char *wayland_display = boost::nowide::getenv("WAYLAND_DISPLAY"); - //if (! ((display && *display) || (wayland_display && *wayland_display))) { - if (! (display && *display)) { - // DISPLAY not set. - boost::nowide::cerr << "DISPLAY not set, GUI mode not available." << std::endl << std::endl; - this->print_help(false); - // Indicate an error. - return 1; - } - #endif // some linux / unix system - Slic3r::GUI::GUI_InitParams params; - params.argc = argc; - params.argv = argv; - params.load_configs = load_configs; -// params.extra_config = std::move(m_extra_config); - params.input_files = std::move(cli_in.input_files); - if (has_config_from_profiles && params.input_files.empty()) { - params.selected_presets = Slic3r::GUI::CLISelectedProfiles{ cli_in.config.opt_string("print-profile"), - cli_in.config.opt_string("printer-profile") , - cli_in.config.option("material-profile")->values }; - } - params.start_as_gcodeviewer = start_as_gcodeviewer; - params.start_downloader = start_downloader; - params.download_url = download_url; - params.delete_after_load = cli_out.delete_after_load; - params.opengl_aa = opengl_aa; -#if !SLIC3R_OPENGL_ES - params.opengl_version = opengl_version; - params.opengl_debug = opengl_debug; - params.opengl_compatibiity_profile = opengl_compatibility_profile; -#endif // !SLIC3R_OPENGL_ES - return Slic3r::GUI::GUI_Run(params); -#else // SLIC3R_GUI - // No GUI support. Just print out a help. - this->print_help(false); - // If started without a parameter, consider it to be OK, otherwise report an error code (no action etc). - return (argc == 0) ? 0 : 1; -#endif // SLIC3R_GUI - } - - return 0; -} - -bool CLI::setup(int argc, char **argv, CliInParams& in) -{ - { - Slic3r::set_logging_level(1); - const char *loglevel = boost::nowide::getenv("SLIC3R_LOGLEVEL"); + Slic3r::set_logging_level(1); + const char* loglevel = boost::nowide::getenv("SLIC3R_LOGLEVEL"); if (loglevel != nullptr) { if (loglevel[0] >= '0' && loglevel[0] <= '9' && loglevel[1] == 0) set_logging_level(loglevel[0] - '0'); @@ -1125,61 +1006,399 @@ bool CLI::setup(int argc, char **argv, CliInParams& in) set_sys_shapes_dir((path_resources / "shapes").string()); set_custom_gcodes_dir((path_resources / "custom_gcodes").string()); - // Parse all command line options into a DynamicConfig. - // If any option is unsupported, print usage and abort immediately. - t_config_option_keys opt_order; - if (! in.config.read_cli(argc, argv, &in.input_files, &opt_order)) { - // Separate error message reported by the CLI parser from the help. - boost::nowide::cerr << std::endl; - print_help(); - return false; - } - // Parse actions and transform options. - for (auto const &opt_key : opt_order) { - if (cli_actions_config_def.has(opt_key)) - in.actions.emplace_back(opt_key); - else if (cli_transform_config_def.has(opt_key)) - in.transforms.emplace_back(opt_key); - else if (cli_profiles_sharing_config_def.has(opt_key)) - in.profiles_sharing.emplace_back(opt_key); - } + return true; +} +static bool load_cli(Cli& cli, int argc, char** argv) +{ + if (cli.read(argc, argv)) + return true; + + // Separate error message reported by the CLI parser from the help. + boost::nowide::cerr << std::endl; + print_help(); + return false; +} + +static void setup_from_cli(Cli& cli) +{ + if (cli.misc_config.has("loglevel")) { - const ConfigOptionInt *opt_loglevel = in.config.opt("loglevel"); - if (opt_loglevel != 0) - set_logging_level(opt_loglevel->value); + int loglevel = cli.misc_config.opt_int("loglevel"); + if (loglevel != 0) + set_logging_level(loglevel); } - { - const ConfigOptionInt *opt_threads = in.config.opt("threads"); - if (opt_threads != nullptr) - thread_count = opt_threads->value; + if (cli.misc_config.has("threads")) + thread_count = cli.misc_config.opt_int("threads"); + + set_data_dir(cli.misc_config.has("datadir") ? cli.misc_config.opt_string("datadir") : get_default_datadir()); + +#ifdef SLIC3R_GUI + if (cli.misc_config.has("webdev")) { + Utils::ServiceConfig::instance().set_webdev_enabled(cli.misc_config.opt_bool("webdev")); + } +#endif +} + +#ifdef SLIC3R_GUI +// set data for init GUI parameters +// and return state of start_gui +static bool init_gui_params(GUI::GUI_InitParams& gui_params, int argc, char** argv, Cli& cli) +{ + bool start_gui = false; + + gui_params.argc = argc; + gui_params.argv = argv; + gui_params.input_files = cli.input_files; + + if (cli.misc_config.has("opengl-aa")) { + start_gui = true; + gui_params.opengl_aa = true; + } +#if SLIC3R_OPENGL_ES + // are we starting as gcodeviewer ? + if (cli.misc_config.has("gcodeviewer")) { + cli.start_gui = true; + cli.start_as_gcodeviewer = true; + } +#else + + // search for special keys into command line parameters + if (cli.misc_config.has("gcodeviewer")) { + start_gui = true; + gui_params.start_as_gcodeviewer = true; + } + else { +#ifndef _WIN32 + // On Unix systems, the prusa-slicer binary may be symlinked to give the application a different meaning. + gui_params.start_as_gcodeviewer = boost::algorithm::iends_with(boost::filesystem::path(argv[0]).filename().string(), "gcodeviewer"); +#endif // _WIN32 } - //FIXME Validating at this stage most likely does not make sense, as the config is not fully initialized yet. - std::string validity = in.config.validate(); + if (cli.misc_config.has("opengl-version")) { + const Semver opengl_minimum = Semver(3, 2, 0); + const std::string opengl_version_str = cli.misc_config.opt_string("opengl-version"); + boost::optional semver = Semver::parse(opengl_version_str); + if (semver.has_value() && (*semver) >= opengl_minimum) { + std::pair& version = gui_params.opengl_version; + version.first = semver->maj(); + version.second = semver->min(); + if (std::find(Slic3r::GUI::OpenGLVersions::core.begin(), Slic3r::GUI::OpenGLVersions::core.end(), std::make_pair(version.first, version.second)) == Slic3r::GUI::OpenGLVersions::core.end()) { + version = { 0, 0 }; + boost::nowide::cerr << "Required OpenGL version " << opengl_version_str << " not recognized.\n Option 'opengl-version' ignored." << std::endl; + } + } + else + boost::nowide::cerr << "Required OpenGL version " << opengl_version_str << " is invalid. Must be greater than or equal to " << + opengl_minimum.to_string() << "\n Option 'opengl-version' ignored." << std::endl; + start_gui = true; + } - // Initialize with defaults. - // Some of those values will used later - for (const t_optiondef_map* options : { &cli_actions_config_def.options - , &cli_transform_config_def.options - , &cli_profiles_sharing_config_def.options - , &cli_misc_config_def.options }) - for (const t_optiondef_map::value_type &optdef : *options) - in.config.option(optdef.first, true); + if (cli.misc_config.has("opengl-compatibility")) { + start_gui = true; + gui_params.opengl_compatibility_profile = true; + // reset version as compatibility profile always take the highest version + // supported by the graphic card + gui_params.opengl_version = std::make_pair(0, 0); + } - const std::string provided_datadir = in.config.opt_string("datadir"); - set_data_dir(provided_datadir.empty() ? get_default_datadir() : provided_datadir); + if (cli.misc_config.has("opengl-debug")) { + start_gui = true; + gui_params.opengl_debug = true; + } +#endif // SLIC3R_OPENGL_ES - //FIXME Validating at this stage most likely does not make sense, as the config is not fully initialized yet. + if (cli.misc_config.has("delete-after-load")) { + gui_params.delete_after_load = true; + } + + if (!gui_params.start_as_gcodeviewer && !cli.input_config.has("load")) { + // Read input file(s) if any and check if can start GcodeViewer + if (cli.input_files.size() == 1 && is_gcode_file(cli.input_files[0]) && boost::filesystem::exists(cli.input_files[0])) + gui_params.start_as_gcodeviewer = true; + } + + if (has_full_config_from_profiles(cli)) { + gui_params.selected_presets = Slic3r::GUI::CLISelectedProfiles{ cli.input_config.opt_string("print-profile"), + cli.input_config.opt_string("printer-profile") , + cli.input_config.option("material-profile")->values }; + } + + if (!cli.overrides_config.empty()) + gui_params.extra_config = cli.overrides_config; + + if (cli.input_config.has("load")) { + gui_params.load_configs = cli.input_config.option("load")->values; + start_gui = true; + } + + for (const std::string& file : cli.input_files) { + if (boost::starts_with(file, "prusaslicer://")) { + gui_params.start_downloader = true; + gui_params.download_url = file; + break; + } + } + + return start_gui; +} +#else // SLIC3R_GUI + // If there is no GUI, we shall ignore the parameters. Remove them from the list. +#endif // SLIC3R_GUI + +static bool load_print_config(DynamicPrintConfig &print_config, PrinterTechnology& printer_technology, const Cli& cli) +{ + // first of all load configuration from "--load" if any + + if (cli.input_config.has("load")) { + + const std::vector& load_configs = cli.input_config.option("load")->values; + ForwardCompatibilitySubstitutionRule config_substitution_rule = cli.misc_config.option>("config_compatibility")->value; + + // load config files supplied via --load + for (auto const& file : load_configs) { + if (!boost::filesystem::exists(file)) { + if (cli.misc_config.has("ignore_nonexistent_config") && cli.misc_config.opt_bool("ignore_nonexistent_config")) { + continue; + } + else { + boost::nowide::cerr << "No such file: " << file << std::endl; + return false; + } + } + DynamicPrintConfig config; + ConfigSubstitutions config_substitutions; + try { + config_substitutions = config.load(file, config_substitution_rule); + } + catch (std::exception& ex) { + boost::nowide::cerr << "Error while reading config file \"" << file << "\": " << ex.what() << std::endl; + return false; + } + + if (!can_apply_printer_technology(printer_technology, get_printer_technology(config))) + return false; + + print_config_substitutions(config_substitutions, file); + + config.normalize_fdm(); + print_config.apply(config); + } + } + + // than apply other options from full print config if any is provided by prifiles set + + if (has_full_config_from_profiles(cli)) { + DynamicPrintConfig config; + // load config from profiles set + std::string errors = Slic3r::load_full_print_config(cli.input_config.opt_string("print-profile"), + cli.input_config.option("material-profile")->values, + cli.input_config.opt_string("printer-profile"), + config, printer_technology); + if (!errors.empty()) { + boost::nowide::cerr << "Error while loading config from profiles: " << errors << std::endl; + return false; + } + + if (!can_apply_printer_technology(printer_technology, get_printer_technology(config))) + return false; + + config.normalize_fdm(); + + // config is applied with print_config loaded before + config += std::move(print_config); + print_config = std::move(config); + } + + return true; +} + +static bool process_input_files(std::vector& models, DynamicPrintConfig& print_config, PrinterTechnology& printer_technology, Cli& cli) +{ + for (const std::string& file : cli.input_files) { + if (boost::starts_with(file, "prusaslicer://")) { + continue; + } + if (!boost::filesystem::exists(file)) { + boost::nowide::cerr << "No such file: " << file << std::endl; + return false; + } + + Model model; + try { + if (has_full_config_from_profiles(cli)) { + // we have full banch of options from profiles set + // so, just load a geometry + model = FileReader::load_model(file); + } + else { + // load model and configuration from the file + DynamicPrintConfig config; + ConfigSubstitutionContext config_substitutions_ctxt(cli.misc_config.option>("config_compatibility")->value); + + boost::optional prusaslicer_generator_version; + //FIXME should we check the version here? // | Model::LoadAttribute::CheckVersion ? + model = FileReader::load_model_with_config(file, &config, &config_substitutions, prusaslicer_generator_version, FileReader::LoadAttribute::AddDefaultInstances); + + if (!can_apply_printer_technology(printer_technology, get_printer_technology(config))) + return false; + + print_config_substitutions(config_substitutions_ctxt.substitutions, file); + + // config is applied with print_config loaded before + config += std::move(print_config); + print_config = std::move(config); + } + + // If model for slicing is loaded from 3mf file, then its geometry has to be used and arrange couldn't be apply for this model. + if ((boost::algorithm::iends_with(file, ".3mf") || boost::algorithm::iends_with(file, ".zip")) && + (!cli.transform_config.has("dont_arrange") || !cli.transform_config.opt_bool("dont_arrange"))) { + //So, check a state of "dont_arrange" parameter and set it to true, if its value is false. + cli.transform_config.set_key_value("dont_arrange", new ConfigOptionBool(true)); + } + } + catch (std::exception& e) { + boost::nowide::cerr << file << ": " << e.what() << std::endl; + return false; + } + if (model.objects.empty()) { + boost::nowide::cerr << "Error: file is empty: " << file << std::endl; + continue; + } + models.push_back(model); + } + + return true; +} + +static bool is_needed_post_processing(const DynamicPrintConfig& print_config, const Cli& cli) +{ + if (print_config.has("post_process")) { + const std::vector& post_process = print_config.opt("post_process")->values; + if (!post_process.empty()) { + boost::nowide::cout << "\nA post-processing script has been detected in the config data:\n\n"; + for (const std::string& s : post_process) { + boost::nowide::cout << "> " << s << "\n"; + } + boost::nowide::cout << "\nContinue(Y/N) ? "; + char in; + boost::nowide::cin >> in; + if (in != 'Y' && in != 'y') + return true; + } + } + + return false; +} + +static bool finalize_print_config(DynamicPrintConfig& print_config, PrinterTechnology& printer_technology, const Cli& cli) +{ + // Apply command line options to a more specific DynamicPrintConfig which provides normalize() + // (command line options override --load files or congiguration which is loaded prom profiles) + print_config.apply(cli.overrides_config, true); + // Normalizing after importing the 3MFs / AMFs + print_config.normalize_fdm(); + + if (printer_technology == ptUnknown) + printer_technology = cli.actions_config.has("export_sla") ? ptSLA : ptFFF; + print_config.option>("printer_technology", true)->value = printer_technology; + + // Initialize full print configs for both the FFF and SLA technologies. + FullPrintConfig fff_print_config; + SLAFullPrintConfig sla_print_config; + + // Synchronize the default parameters and the ones received on the command line. + if (printer_technology == ptFFF) { + fff_print_config.apply(print_config, true); + print_config.apply(fff_print_config, true); + } + else { + assert(printer_technology == ptSLA); + sla_print_config.output_filename_format.value = "[input_filename_base].sl1"; + + // The default bed shape should reflect the default display parameters + // and not the fff defaults. + double w = sla_print_config.display_width.getFloat(); + double h = sla_print_config.display_height.getFloat(); + sla_print_config.bed_shape.values = { Vec2d(0, 0), Vec2d(w, 0), Vec2d(w, h), Vec2d(0, h) }; + + sla_print_config.apply(print_config, true); + print_config.apply(sla_print_config, true); + } + + // validate print configuration + std::string validity = print_config.validate(); if (!validity.empty()) { - boost::nowide::cerr << "error: " << validity << std::endl; + boost::nowide::cerr << "Error: The composite configation is not valid: " << validity << std::endl; return false; } return true; } +int Slic3r::CLI::run(int argc, char** argv) +{ + if (!setup_general()) + return 1; + + Cli cli; + if (!load_cli(cli, argc, argv)) + return 1; + + setup_from_cli(cli); + + if (process_profiles_sharing(cli)) + return 1; + + bool start_gui = cli.empty() || (has_full_config_from_profiles(cli) && cli.actions_config.empty()); + PrinterTechnology printer_technology = cli.overrides_config.has("printer_technology") ? Preset::printer_technology(cli.overrides_config) : ptUnknown; + DynamicPrintConfig print_config = {}; + std::vector models; + +#ifdef SLIC3R_GUI + GUI::GUI_InitParams gui_params; + start_gui |= init_gui_params(gui_params, argc, argv, cli); + + if (gui_params.start_as_gcodeviewer) + return start_as_gcode_viewer(cli, gui_params); +#endif + + if (!load_print_config(print_config, printer_technology, cli)) + return 1; + + if (!process_input_files(models, print_config, printer_technology, cli)) + return 1; + + if (!start_gui && is_needed_post_processing(print_config, cli)) + return 0; + + if (!finalize_print_config(print_config, printer_technology, cli)) + return 1; + + if (!process_transform(cli, print_config, models)) + return 1; + + if (!process_actions(cli, print_config, models)) + return 1; + + // this need to be check AFTER process transformations, where this option can be added + start_gui |= cli.actions_config.has("export_stl"); + + if (start_gui) { +#ifdef SLIC3R_GUI + return start_gui_with_params(gui_params); +#else + // No GUI support. Just print out a help. + print_help(false); + // If started without a parameter, consider it to be OK, otherwise report an error code (no action etc). + return (argc == 0) ? 0 : 1; +#endif + } + + return 0; +} + // __has_feature() is used later for Clang, this is for compatibility with other compilers (such as GCC and MSVC) #ifndef __has_feature @@ -1226,7 +1445,7 @@ extern "C" { for (size_t i = 0; i < argc; ++ i) argv_ptrs[i] = argv_narrow[i].data(); // Call the UTF8 main. - return CLI().run(argc, argv_ptrs.data()); + return Slic3r::CLI::run(argc, argv_ptrs.data()); } } #else /* _MSC_VER */ diff --git a/src/PrusaSlicer.hpp b/src/PrusaSlicer.hpp index 2f9d9e5ada..302666a4d1 100644 --- a/src/PrusaSlicer.hpp +++ b/src/PrusaSlicer.hpp @@ -1,49 +1,9 @@ #ifndef SLIC3R_HPP #define SLIC3R_HPP -#include "libslic3r/Config.hpp" -#include "libslic3r/Model.hpp" - -namespace Slic3r { - -namespace IO { - enum ExportFormat : int { - OBJ, - STL, - // SVG, - TMF, - Gcode - }; -} - -struct CliInParams +namespace Slic3r::CLI { - DynamicPrintAndCLIConfig config; - std::vector input_files; - std::vector actions; - std::vector transforms; - std::vector profiles_sharing; -}; - -struct CliOutParams -{ - DynamicPrintConfig print_config; - PrinterTechnology printer_technology; - ForwardCompatibilitySubstitutionRule config_substitution_rule; - std::vector models; - bool user_center_specified { false }; - bool delete_after_load { false }; -}; - -class CLI { -public: int run(int argc, char **argv); - -private: - - bool setup(int argc, char **argv, CliInParams& in); -}; - } #endif From 4be7eeabb460a6c014a072bd9c60457c6341ce5b Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 14 Jan 2025 10:01:47 +0100 Subject: [PATCH 5/9] CLI: Code cleaning and reorganization +Remove configuration of fhs.hpp on UNIX. Use target_compile_definitions() instead. --- CMakeLists.txt | 4 +- src/CLI/CLI.hpp | 71 ++ src/CLI/GuiParams.cpp | 150 ++++ src/CLI/LoadPrintData.cpp | 259 +++++++ src/CLI/PrintHelp.cpp | 180 +++++ src/CLI/ProcessActions.cpp | 367 +++++++++ src/CLI/ProcessTransform.cpp | 205 +++++ src/CLI/Run.cpp | 54 ++ src/CLI/Setup.cpp | 342 +++++++++ src/CMakeLists.txt | 31 +- src/PrusaSlicer.cpp | 1373 +--------------------------------- 11 files changed, 1659 insertions(+), 1377 deletions(-) create mode 100644 src/CLI/CLI.hpp create mode 100644 src/CLI/GuiParams.cpp create mode 100644 src/CLI/LoadPrintData.cpp create mode 100644 src/CLI/PrintHelp.cpp create mode 100644 src/CLI/ProcessActions.cpp create mode 100644 src/CLI/ProcessTransform.cpp create mode 100644 src/CLI/Run.cpp create mode 100644 src/CLI/Setup.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 25bf56b3fd..57b60ea620 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -626,7 +626,6 @@ if(SLIC3R_BUILD_TESTS) endif() -# Resources install target, configure fhs.hpp on UNIX if (WIN32) install(DIRECTORY "${SLIC3R_RESOURCES_DIR}/" DESTINATION "${CMAKE_INSTALL_PREFIX}/resources") elseif (SLIC3R_FHS) @@ -646,10 +645,9 @@ elseif (SLIC3R_FHS) ) endforeach() install(DIRECTORY ${SLIC3R_RESOURCES_DIR}/udev/ DESTINATION lib/udev/rules.d) + target_compile_definitions(PrusaSlicer PUBLIC SLIC3R_FHS SLIC3R_FHS_RESOURCES="${SLIC3R_FHS_RESOURCES}") else () install(FILES src/platform/unix/PrusaSlicer.desktop DESTINATION ${CMAKE_INSTALL_PREFIX}/resources/applications) install(FILES src/platform/unix/PrusaGcodeviewer.desktop DESTINATION ${CMAKE_INSTALL_PREFIX}/resources/applications) install(DIRECTORY "${SLIC3R_RESOURCES_DIR}/" DESTINATION "${CMAKE_INSTALL_PREFIX}/resources") endif () - -configure_file(src/platform/unix/fhs.hpp.in ${LIBDIR_BIN}/platform/unix/fhs.hpp) diff --git a/src/CLI/CLI.hpp b/src/CLI/CLI.hpp new file mode 100644 index 0000000000..4cd487b363 --- /dev/null +++ b/src/CLI/CLI.hpp @@ -0,0 +1,71 @@ +#pragma once + +#include +#include + +#include "libslic3r/Config.hpp" +#include "libslic3r/Model.hpp" + +#ifdef SLIC3R_GUI +#include "slic3r/GUI/GUI_Init.hpp" +#endif + +namespace Slic3r::CLI +{ + // struct which is filled from comand line input + struct Data + { + DynamicPrintConfig input_config; + DynamicPrintConfig overrides_config; + DynamicPrintConfig transform_config; + DynamicPrintConfig misc_config; + DynamicPrintConfig actions_config; + + std::vector input_files; + + bool empty() { + return input_files.empty() + && input_config.empty() + && overrides_config.empty() + && transform_config.empty() + && actions_config.empty(); + } + }; + + // Implemented in PrintHelp.cpp + + void print_help(bool include_print_options = false, PrinterTechnology printer_technology = ptAny); + + // Implemented in Setup.cpp + + bool setup(Data& cli, int argc, char** argv); + + // Implemented in LoadPrintData.cpp + + PrinterTechnology get_printer_technology(const DynamicConfig& config); + bool load_print_data(std::vector& models, + DynamicPrintConfig& print_config, + PrinterTechnology& printer_technology, + Data& cli); + bool is_needed_post_processing(const DynamicPrintConfig& print_config); + + // Implemented in ProcessTransform.cpp + + bool process_transform(Data& cli, const DynamicPrintConfig& print_config, std::vector& models); + + // Implemented in ProcessActions.cpp + + bool has_full_config_from_profiles(const Data& cli); + bool process_profiles_sharing(const Data& cli); + bool process_actions(Data& cli, const DynamicPrintConfig& print_config, std::vector& models); + + // Implemented in GuiParams.cpp +#ifdef SLIC3R_GUI + // set data for init GUI parameters + // and return state of start_gui + bool init_gui_params(GUI::GUI_InitParams& gui_params, int argc, char** argv, Data& cli); + int start_gui_with_params(GUI::GUI_InitParams& params); + int start_as_gcode_viewer(GUI::GUI_InitParams& gui_params); +#endif + +} diff --git a/src/CLI/GuiParams.cpp b/src/CLI/GuiParams.cpp new file mode 100644 index 0000000000..ed70aa4018 --- /dev/null +++ b/src/CLI/GuiParams.cpp @@ -0,0 +1,150 @@ +#include + +#include +#include +#include + +#include "CLI.hpp" + +#ifdef SLIC3R_GUI + +namespace Slic3r::CLI { + +bool init_gui_params(GUI::GUI_InitParams& gui_params, int argc, char** argv, Data& cli) +{ + bool start_gui = false; + + gui_params.argc = argc; + gui_params.argv = argv; + gui_params.input_files = cli.input_files; + + if (cli.misc_config.has("opengl-aa")) { + start_gui = true; + gui_params.opengl_aa = true; + } +#if SLIC3R_OPENGL_ES + // are we starting as gcodeviewer ? + if (cli.misc_config.has("gcodeviewer")) { + cli.start_gui = true; + cli.start_as_gcodeviewer = true; + } +#else + + // search for special keys into command line parameters + if (cli.misc_config.has("gcodeviewer")) { + start_gui = true; + gui_params.start_as_gcodeviewer = true; + } + else { +#ifndef _WIN32 + // On Unix systems, the prusa-slicer binary may be symlinked to give the application a different meaning. + gui_params.start_as_gcodeviewer = boost::algorithm::iends_with(boost::filesystem::path(argv[0]).filename().string(), "gcodeviewer"); +#endif // _WIN32 + } + + if (cli.misc_config.has("opengl-version")) { + const Semver opengl_minimum = Semver(3, 2, 0); + const std::string opengl_version_str = cli.misc_config.opt_string("opengl-version"); + boost::optional semver = Semver::parse(opengl_version_str); + if (semver.has_value() && (*semver) >= opengl_minimum) { + std::pair& version = gui_params.opengl_version; + version.first = semver->maj(); + version.second = semver->min(); + if (std::find(Slic3r::GUI::OpenGLVersions::core.begin(), Slic3r::GUI::OpenGLVersions::core.end(), std::make_pair(version.first, version.second)) == Slic3r::GUI::OpenGLVersions::core.end()) { + version = { 0, 0 }; + boost::nowide::cerr << "Required OpenGL version " << opengl_version_str << " not recognized.\n Option 'opengl-version' ignored." << std::endl; + } + } + else + boost::nowide::cerr << "Required OpenGL version " << opengl_version_str << " is invalid. Must be greater than or equal to " << + opengl_minimum.to_string() << "\n Option 'opengl-version' ignored." << std::endl; + start_gui = true; + } + + if (cli.misc_config.has("opengl-compatibility")) { + start_gui = true; + gui_params.opengl_compatibility_profile = true; + // reset version as compatibility profile always take the highest version + // supported by the graphic card + gui_params.opengl_version = std::make_pair(0, 0); + } + + if (cli.misc_config.has("opengl-debug")) { + start_gui = true; + gui_params.opengl_debug = true; + } +#endif // SLIC3R_OPENGL_ES + + if (cli.misc_config.has("delete-after-load")) { + gui_params.delete_after_load = true; + } + + if (!gui_params.start_as_gcodeviewer && !cli.input_config.has("load")) { + // Read input file(s) if any and check if can start GcodeViewer + if (cli.input_files.size() == 1 && is_gcode_file(cli.input_files[0]) && boost::filesystem::exists(cli.input_files[0])) + gui_params.start_as_gcodeviewer = true; + } + + if (has_full_config_from_profiles(cli)) { + gui_params.selected_presets = Slic3r::GUI::CLISelectedProfiles{ cli.input_config.opt_string("print-profile"), + cli.input_config.opt_string("printer-profile") , + cli.input_config.option("material-profile")->values }; + } + + if (!cli.overrides_config.empty()) + gui_params.extra_config = cli.overrides_config; + + if (cli.input_config.has("load")) + gui_params.load_configs = cli.input_config.option("load")->values; + + for (const std::string& file : cli.input_files) { + if (boost::starts_with(file, "prusaslicer://")) { + gui_params.start_downloader = true; + gui_params.download_url = file; + break; + } + } + + return start_gui; +} + +int start_gui_with_params(GUI::GUI_InitParams& params) +{ +#if !defined(_WIN32) && !defined(__APPLE__) + // likely some linux / unix system + const char* display = boost::nowide::getenv("DISPLAY"); + // const char *wayland_display = boost::nowide::getenv("WAYLAND_DISPLAY"); + //if (! ((display && *display) || (wayland_display && *wayland_display))) { + if (!(display && *display)) { + // DISPLAY not set. + boost::nowide::cerr << "DISPLAY not set, GUI mode not available." << std::endl << std::endl; + print_help(false); + // Indicate an error. + return 1; + } +#endif // some linux / unix system + return Slic3r::GUI::GUI_Run(params); +} + +int start_as_gcode_viewer(GUI::GUI_InitParams& gui_params) +{ + if (gui_params.input_files.size() > 1) { + boost::nowide::cerr << "You can open only one .gcode file at a time in GCodeViewer" << std::endl; + return 1; + } + + if (!gui_params.input_files.empty()) { + const std::string& file = gui_params.input_files[0]; + if (!is_gcode_file(file) || !boost::filesystem::exists(file)) { + boost::nowide::cerr << "Input file isn't a .gcode file or doesn't exist. GCodeViewer can't be start." << std::endl; + return 1; + } + } + + return start_gui_with_params(gui_params); +} + +} +#else // SLIC3R_GUI + // If there is no GUI, we shall ignore the parameters. Remove them from the list. +#endif // SLIC3R_GUI \ No newline at end of file diff --git a/src/CLI/LoadPrintData.cpp b/src/CLI/LoadPrintData.cpp new file mode 100644 index 0000000000..2418527c1c --- /dev/null +++ b/src/CLI/LoadPrintData.cpp @@ -0,0 +1,259 @@ +#include +#include +#include +#include + +#include +#include +#include + +#include "libslic3r/libslic3r.h" +#include "libslic3r/Config.hpp" +#include "libslic3r/GCode/PostProcessor.hpp" +#include "libslic3r/Model.hpp" +#include "libslic3r/ProfilesSharingUtils.hpp" +#include "libslic3r/FileReader.hpp" + +#include "CLI.hpp" + +namespace Slic3r::CLI { + +PrinterTechnology get_printer_technology(const DynamicConfig &config) +{ + const ConfigOptionEnum *opt = config.option>("printer_technology"); + return (opt == nullptr) ? ptUnknown : opt->value; +} + +// may be "validate_and_apply_printer_technology" will be better? +static bool can_apply_printer_technology(PrinterTechnology& printer_technology, const PrinterTechnology& other_printer_technology) +{ + if (printer_technology == ptUnknown) { + printer_technology = other_printer_technology; + return true; + } + + bool invalid_other_pt = printer_technology != other_printer_technology && other_printer_technology != ptUnknown; + + if (invalid_other_pt) + boost::nowide::cerr << "Mixing configurations for FFF and SLA technologies" << std::endl; + + return !invalid_other_pt; +} + +static void print_config_substitutions(const ConfigSubstitutions& config_substitutions, const std::string& file) +{ + if (config_substitutions.empty()) + return; + boost::nowide::cout << "The following configuration values were substituted when loading \" << file << \":\n"; + for (const ConfigSubstitution& subst : config_substitutions) + boost::nowide::cout << "\tkey = \"" << subst.opt_def->opt_key << "\"\t loaded = \"" << subst.old_value << "\tsubstituted = \"" << subst.new_value->serialize() << "\"\n"; +} + +static bool load_print_config(DynamicPrintConfig &print_config, PrinterTechnology& printer_technology, const Data& cli) +{ + // first of all load configuration from "--load" if any + + if (cli.input_config.has("load")) { + + const std::vector& load_configs = cli.input_config.option("load")->values; + ForwardCompatibilitySubstitutionRule config_substitution_rule = cli.misc_config.option>("config_compatibility")->value; + + // load config files supplied via --load + for (auto const& file : load_configs) { + if (!boost::filesystem::exists(file)) { + if (cli.misc_config.has("ignore_nonexistent_config") && cli.misc_config.opt_bool("ignore_nonexistent_config")) { + continue; + } + else { + boost::nowide::cerr << "No such file: " << file << std::endl; + return false; + } + } + DynamicPrintConfig config; + ConfigSubstitutions config_substitutions; + try { + config_substitutions = config.load(file, config_substitution_rule); + } + catch (std::exception& ex) { + boost::nowide::cerr << "Error while reading config file \"" << file << "\": " << ex.what() << std::endl; + return false; + } + + if (!can_apply_printer_technology(printer_technology, get_printer_technology(config))) + return false; + + print_config_substitutions(config_substitutions, file); + + config.normalize_fdm(); + print_config.apply(config); + } + } + + // than apply other options from full print config if any is provided by prifiles set + + if (has_full_config_from_profiles(cli)) { + DynamicPrintConfig config; + // load config from profiles set + std::string errors = Slic3r::load_full_print_config(cli.input_config.opt_string("print-profile"), + cli.input_config.option("material-profile")->values, + cli.input_config.opt_string("printer-profile"), + config, printer_technology); + if (!errors.empty()) { + boost::nowide::cerr << "Error while loading config from profiles: " << errors << std::endl; + return false; + } + + if (!can_apply_printer_technology(printer_technology, get_printer_technology(config))) + return false; + + config.normalize_fdm(); + + // config is applied with print_config loaded before + config += std::move(print_config); + print_config = std::move(config); + } + + return true; +} + +static bool process_input_files(std::vector& models, DynamicPrintConfig& print_config, PrinterTechnology& printer_technology, Data& cli) +{ + for (const std::string& file : cli.input_files) { + if (boost::starts_with(file, "prusaslicer://")) { + continue; + } + if (!boost::filesystem::exists(file)) { + boost::nowide::cerr << "No such file: " << file << std::endl; + return false; + } + + Model model; + try { + if (has_full_config_from_profiles(cli)) { + // we have full banch of options from profiles set + // so, just load a geometry + model = FileReader::load_model(file); + } + else { + // load model and configuration from the file + DynamicPrintConfig config; + ConfigSubstitutionContext config_substitutions_ctxt(cli.misc_config.option>("config_compatibility")->value); + boost::optional prusaslicer_generator_version; + + //FIXME should we check the version here? // | Model::LoadAttribute::CheckVersion ? + model = FileReader::load_model_with_config(file, &config, &config_substitutions_ctxt, prusaslicer_generator_version, FileReader::LoadAttribute::AddDefaultInstances); + + if (!can_apply_printer_technology(printer_technology, get_printer_technology(config))) + return false; + + print_config_substitutions(config_substitutions_ctxt.substitutions, file); + + // config is applied with print_config loaded before + config += std::move(print_config); + print_config = std::move(config); + } + + // If model for slicing is loaded from 3mf file, then its geometry has to be used and arrange couldn't be apply for this model. + if ((boost::algorithm::iends_with(file, ".3mf") || boost::algorithm::iends_with(file, ".zip")) && + (!cli.transform_config.has("dont_arrange") || !cli.transform_config.opt_bool("dont_arrange"))) { + //So, check a state of "dont_arrange" parameter and set it to true, if its value is false. + cli.transform_config.set_key_value("dont_arrange", new ConfigOptionBool(true)); + } + } + catch (std::exception& e) { + boost::nowide::cerr << file << ": " << e.what() << std::endl; + return false; + } + if (model.objects.empty()) { + boost::nowide::cerr << "Error: file is empty: " << file << std::endl; + continue; + } + models.push_back(model); + } + + return true; +} + +static bool finalize_print_config(DynamicPrintConfig& print_config, PrinterTechnology& printer_technology, const Data& cli) +{ + // Apply command line options to a more specific DynamicPrintConfig which provides normalize() + // (command line options override --load files or congiguration which is loaded prom profiles) + print_config.apply(cli.overrides_config, true); + // Normalizing after importing the 3MFs / AMFs + print_config.normalize_fdm(); + + if (printer_technology == ptUnknown) + printer_technology = cli.actions_config.has("export_sla") ? ptSLA : ptFFF; + print_config.option>("printer_technology", true)->value = printer_technology; + + // Initialize full print configs for both the FFF and SLA technologies. + FullPrintConfig fff_print_config; + SLAFullPrintConfig sla_print_config; + + // Synchronize the default parameters and the ones received on the command line. + if (printer_technology == ptFFF) { + fff_print_config.apply(print_config, true); + print_config.apply(fff_print_config, true); + } + else { + assert(printer_technology == ptSLA); + sla_print_config.output_filename_format.value = "[input_filename_base].sl1"; + + // The default bed shape should reflect the default display parameters + // and not the fff defaults. + double w = sla_print_config.display_width.getFloat(); + double h = sla_print_config.display_height.getFloat(); + sla_print_config.bed_shape.values = { Vec2d(0, 0), Vec2d(w, 0), Vec2d(w, h), Vec2d(0, h) }; + + sla_print_config.apply(print_config, true); + print_config.apply(sla_print_config, true); + } + + // validate print configuration + std::string validity = print_config.validate(); + if (!validity.empty()) { + boost::nowide::cerr << "Error: The composite configation is not valid: " << validity << std::endl; + return false; + } + + return true; +} + +bool load_print_data(std::vector& models, + DynamicPrintConfig& print_config, + PrinterTechnology& printer_technology, + Data& cli) +{ + if (!load_print_config(print_config, printer_technology, cli)) + return false; + + if (!process_input_files(models, print_config, printer_technology, cli)) + return false; + + if (!finalize_print_config(print_config, printer_technology, cli)) + return false; + + return true; +} + +bool is_needed_post_processing(const DynamicPrintConfig& print_config) +{ + if (print_config.has("post_process")) { + const std::vector& post_process = print_config.opt("post_process")->values; + if (!post_process.empty()) { + boost::nowide::cout << "\nA post-processing script has been detected in the config data:\n\n"; + for (const std::string& s : post_process) { + boost::nowide::cout << "> " << s << "\n"; + } + boost::nowide::cout << "\nContinue(Y/N) ? "; + char in; + boost::nowide::cin >> in; + if (in != 'Y' && in != 'y') + return true; + } + } + + return false; +} + +} \ No newline at end of file diff --git a/src/CLI/PrintHelp.cpp b/src/CLI/PrintHelp.cpp new file mode 100644 index 0000000000..9295a4314c --- /dev/null +++ b/src/CLI/PrintHelp.cpp @@ -0,0 +1,180 @@ +#include + +#include +#include +#include +#include + +#include "CLI.hpp" + +namespace Slic3r::CLI { + +static void print_help(const ConfigDef& config_def, bool show_defaults, std::function filter = [](const ConfigOptionDef &){ return true; }) +{ + + // prepare a function for wrapping text + auto wrap = [](const std::string& text, size_t line_length) -> std::string { + std::istringstream words(text); + std::ostringstream wrapped; + std::string word; + + if (words >> word) { + wrapped << word; + size_t space_left = line_length - word.length(); + while (words >> word) { + if (space_left < word.length() + 1) { + wrapped << '\n' << word; + space_left = line_length - word.length(); + } + else { + wrapped << ' ' << word; + space_left -= word.length() + 1; + } + } + } + return wrapped.str(); + }; + + // List of opt_keys that should be hidden from the CLI help. + const std::vector silent_options = { "webdev", "single_instance_on_url" }; + + // get the unique categories + std::set categories; + for (const auto& opt : config_def.options) { + const ConfigOptionDef& def = opt.second; + if (filter(def)) + categories.insert(def.category); + } + + for (const std::string& category : categories) { + if (category != "") { + boost::nowide::cout << category << ":" << std::endl; + } + else if (categories.size() > 1) { + boost::nowide::cout << "Misc options:" << std::endl; + } + + for (const auto& opt : config_def.options) { + const ConfigOptionDef& def = opt.second; + if (def.category != category || def.cli == ConfigOptionDef::nocli || !filter(def)) + continue; + + if (std::find(silent_options.begin(), silent_options.end(), opt.second.opt_key) != silent_options.end()) + continue; + + // get all possible variations: --foo, --foobar, -f... + std::vector cli_args = def.cli_args(opt.first); + if (cli_args.empty()) + continue; + + for (auto& arg : cli_args) { + arg.insert(0, (arg.size() == 1) ? "-" : "--"); + if (def.type == coFloat || def.type == coInt || def.type == coFloatOrPercent + || def.type == coFloats || def.type == coInts) { + arg += " N"; + } + else if (def.type == coPoint) { + arg += " X,Y"; + } + else if (def.type == coPoint3) { + arg += " X,Y,Z"; + } + else if (def.type == coString || def.type == coStrings) { + arg += " ABCD"; + } + } + + // left: command line options + const std::string cli = boost::algorithm::join(cli_args, ", "); + boost::nowide::cout << " " << std::left << std::setw(20) << cli; + + // right: option description + std::string descr = def.tooltip; + bool show_defaults_this = show_defaults || def.opt_key == "config_compatibility"; + if (show_defaults_this && def.default_value && def.type != coBool + && (def.type != coString || !def.default_value->serialize().empty())) { + descr += " ("; + if (!def.sidetext.empty()) { + descr += def.sidetext + ", "; + } + else if (def.enum_def && def.enum_def->has_values()) { + descr += boost::algorithm::join(def.enum_def->values(), ", ") + "; "; + } + descr += "default: " + def.default_value->serialize() + ")"; + } + + // wrap lines of description + descr = wrap(descr, 80); + std::vector lines; + boost::split(lines, descr, boost::is_any_of("\n")); + + // if command line options are too long, print description in new line + for (size_t i = 0; i < lines.size(); ++i) { + if (i == 0 && cli.size() > 19) + boost::nowide::cout << std::endl; + if (i > 0 || cli.size() > 19) + boost::nowide::cout << std::string(21, ' '); + boost::nowide::cout << lines[i] << std::endl; + } + } + } +} + +void print_help(bool include_print_options/* = false*/, PrinterTechnology printer_technology/* = ptAny*/) +{ + boost::nowide::cout + << SLIC3R_BUILD_ID << " " << "based on Slic3r" +#ifdef SLIC3R_GUI + << " (with GUI support)" +#else /* SLIC3R_GUI */ + << " (without GUI support)" +#endif /* SLIC3R_GUI */ + << std::endl + << "https://github.com/prusa3d/PrusaSlicer" << std::endl << std::endl + << "Usage: prusa-slicer [ INPUT ] [ OPTIONS ] [ ACTIONS ] [ TRANSFORM ] [ file.stl ... ]" << std::endl; + + boost::nowide::cout + << std::endl + << "Input:" << std::endl; + print_help(cli_input_config_def, false); + + boost::nowide::cout + << std::endl + << "Note: To load configuration from profiles, you need to set whole banch of presets" << std::endl; + + boost::nowide::cout + << std::endl + << "Actions:" << std::endl; + print_help(cli_actions_config_def, false); + + boost::nowide::cout + << std::endl + << "Transform options:" << std::endl; + print_help(cli_transform_config_def, false); + + boost::nowide::cout + << std::endl + << "Other options:" << std::endl; + print_help(cli_misc_config_def, false); + + boost::nowide::cout + << std::endl + << "Print options are processed in the following order:" << std::endl + << "\t1) Config keys from the command line, for example --fill-pattern=stars" << std::endl + << "\t (highest priority, overwrites everything below)" << std::endl + << "\t2) Config files loaded with --load" << std::endl + << "\t3) Config values loaded from 3mf files" << std::endl; + + if (include_print_options) { + boost::nowide::cout << std::endl; + print_help(print_config_def, true, [printer_technology](const ConfigOptionDef& def) + { return printer_technology == ptAny || def.printer_technology == ptAny || printer_technology == def.printer_technology; }); + } + else { + boost::nowide::cout + << std::endl + << "Run --help-fff / --help-sla to see the full listing of print options." << std::endl; + } +} + +} \ No newline at end of file diff --git a/src/CLI/ProcessActions.cpp b/src/CLI/ProcessActions.cpp new file mode 100644 index 0000000000..b16f3036ad --- /dev/null +++ b/src/CLI/ProcessActions.cpp @@ -0,0 +1,367 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libslic3r/libslic3r.h" +#if !SLIC3R_OPENGL_ES +#include +#endif // !SLIC3R_OPENGL_ES +#include "libslic3r/Config.hpp" +#include "libslic3r/Geometry.hpp" +#include "libslic3r/GCode/PostProcessor.hpp" +#include "libslic3r/Model.hpp" +#include "libslic3r/Preset.hpp" +#include "libslic3r/ProfilesSharingUtils.hpp" +#include +#include "libslic3r/Print.hpp" +#include "libslic3r/SLAPrint.hpp" +#include "libslic3r/Format/AMF.hpp" +#include "libslic3r/Format/3mf.hpp" +#include "libslic3r/Format/STL.hpp" +#include "libslic3r/Format/OBJ.hpp" +#include "libslic3r/Format/SL1.hpp" +#include "libslic3r/miniz_extension.hpp" +#include "libslic3r/PNGReadWrite.hpp" +#include "libslic3r/MultipleBeds.hpp" + +#include "CLI.hpp" + +namespace Slic3r::CLI { + +static bool has_profile_sharing_action(const Data& cli) +{ + return cli.actions_config.has("query-printer-models") || cli.actions_config.has("query-print-filament-profiles"); +} + +bool has_full_config_from_profiles(const Data& cli) +{ + const DynamicPrintConfig& input = cli.input_config; + return !has_profile_sharing_action(cli) && + (input.has("print-profile") && !input.opt_string("print-profile").empty() || + input.has("material-profile") && !input.option("material-profile")->values.empty() || + input.has("printer-profile") && !input.opt_string("printer-profile").empty()); +} + +bool process_profiles_sharing(const Data& cli) +{ + if (!has_profile_sharing_action(cli)) + return false; + + std::string ret; + + if (cli.actions_config.has("query-printer-models")) { + ret = Slic3r::get_json_printer_models(get_printer_technology(cli.overrides_config)); + } + else if (cli.actions_config.has("query-print-filament-profiles")) { + if (cli.input_config.has("printer-profile") && !cli.input_config.opt_string("printer-profile").empty()) { + const std::string printer_profile = cli.input_config.opt_string("printer-profile"); + ret = Slic3r::get_json_print_filament_profiles(printer_profile); + if (ret.empty()) { + boost::nowide::cerr << "query-print-filament-profiles error: Printer profile '" << printer_profile << + "' wasn't found among installed printers." << std::endl << + "Or the request can be wrong." << std::endl; + return true; + } + } + else { + boost::nowide::cerr << "query-print-filament-profiles error: This action requires set 'printer-profile' option" << std::endl; + return true; + } + } + + if (ret.empty()) { + boost::nowide::cerr << "Wrong request" << std::endl; + return true; + } + + // use --output when available + + if (cli.misc_config.has("output")) { + std::string cmdline_param = cli.misc_config.opt_string("output"); + // if we were supplied a directory, use it and append our automatically generated filename + boost::filesystem::path cmdline_path(cmdline_param); + boost::filesystem::path proposed_path = boost::filesystem::path(Slic3r::resources_dir()) / "out.json"; + if (boost::filesystem::is_directory(cmdline_path)) + proposed_path = (cmdline_path / proposed_path.filename()); + else if (cmdline_path.extension().empty()) + proposed_path = cmdline_path.replace_extension("json"); + else + proposed_path = cmdline_path; + const std::string file = proposed_path.string(); + + boost::nowide::ofstream c; + c.open(file, std::ios::out | std::ios::trunc); + c << ret << std::endl; + c.close(); + + boost::nowide::cout << "Output for your request is written into " << file << std::endl; + } + else + printf("%s", ret.c_str()); + + return true; +} + +namespace IO { + enum ExportFormat : int { + OBJ, + STL, + // SVG, + TMF, + Gcode + }; +} + +static std::string output_filepath(const Model& model, IO::ExportFormat format, const std::string& cmdline_param) +{ + std::string ext; + switch (format) { + case IO::OBJ: ext = ".obj"; break; + case IO::STL: ext = ".stl"; break; + case IO::TMF: ext = ".3mf"; break; + default: assert(false); break; + }; + auto proposed_path = boost::filesystem::path(model.propose_export_file_name_and_path(ext)); + // use --output when available + if (!cmdline_param.empty()) { + // if we were supplied a directory, use it and append our automatically generated filename + boost::filesystem::path cmdline_path(cmdline_param); + if (boost::filesystem::is_directory(cmdline_path)) + proposed_path = cmdline_path / proposed_path.filename(); + else + proposed_path = cmdline_path; + } + return proposed_path.string(); +} + +static bool export_models(std::vector& models, IO::ExportFormat format, const std::string& cmdline_param) +{ + for (Model& model : models) { + const std::string path = output_filepath(model, format, cmdline_param); + bool success = false; + switch (format) { + case IO::OBJ: success = Slic3r::store_obj(path.c_str(), &model); break; + case IO::STL: success = Slic3r::store_stl(path.c_str(), &model, true); break; + case IO::TMF: success = Slic3r::store_3mf(path.c_str(), &model, nullptr, false); break; + default: assert(false); break; + } + if (success) + std::cout << "File exported to " << path << std::endl; + else { + std::cerr << "File export to " << path << " failed" << std::endl; + return false; + } + } + return true; +} + +static std::function get_thumbnail_generator_cli(Print& fff_print) +{ + if (!fff_print.model().objects.empty() && boost::iends_with(fff_print.model().objects.front()->input_file, ".3mf")) { + std::string filename = fff_print.model().objects.front()->input_file; + return [filename](const ThumbnailsParams&) { + ThumbnailsList list_out; + + mz_zip_archive archive; + mz_zip_zero_struct(&archive); + + if (!open_zip_reader(&archive, filename)) + return list_out; + mz_uint num_entries = mz_zip_reader_get_num_files(&archive); + mz_zip_archive_file_stat stat; + + int index = mz_zip_reader_locate_file(&archive, "Metadata/thumbnail.png", nullptr, 0); + if (index < 0 || !mz_zip_reader_file_stat(&archive, index, &stat)) + return list_out; + std::string buffer; + buffer.resize(int(stat.m_uncomp_size)); + mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, buffer.data(), (size_t)stat.m_uncomp_size, 0); + if (res == 0) + return list_out; + close_zip_reader(&archive); + + std::vector data; + unsigned width = 0; + unsigned height = 0; + png::decode_png(buffer, data, width, height); + + { + // Flip the image vertically so it matches the convention in Thumbnails generator. + const int row_size = width * 4; // Each pixel is 4 bytes (RGBA) + std::vector temp_row(row_size); + for (int i = 0; i < height / 2; ++i) { + unsigned char* top_row = &data[i * row_size]; + unsigned char* bottom_row = &data[(height - i - 1) * row_size]; + std::copy(bottom_row, bottom_row + row_size, temp_row.begin()); + std::copy(top_row, top_row + row_size, bottom_row); + std::copy(temp_row.begin(), temp_row.end(), top_row); + } + } + + ThumbnailData th; + th.set(width, height); + th.pixels = data; + list_out.push_back(th); + return list_out; + }; + } + + return [](const ThumbnailsParams&) ->ThumbnailsList { return {}; }; +} + +bool process_actions(Data& cli, const DynamicPrintConfig& print_config, std::vector& models) +{ + DynamicPrintConfig& actions = cli.actions_config; + DynamicPrintConfig& transform = cli.transform_config; + + // doesn't need any aditional input + + if (actions.has("help")) { + print_help(); + } + if (actions.has("help_fff")) { + print_help(true, ptFFF); + } + if (actions.has("help_sla")) { + print_help(true, ptSLA); + } + + if (actions.has("info")) { + if (models.empty()) { + boost::nowide::cerr << "error: cannot show info for empty models." << std::endl; + return 1; + } + // --info works on unrepaired model + for (Model& model : models) { + model.add_default_instances(); + model.print_info(); + } + } + + if (actions.has("save")) { + //FIXME check for mixing the FFF / SLA parameters. + // or better save fff_print_config vs. sla_print_config + print_config.save(actions.opt_string("save")); + } + + if (models.empty() && (actions.has("export_stl") || actions.has("export_obj") || actions.has("export_3mf"))) { + boost::nowide::cerr << "error: cannot export empty models." << std::endl; + return 1; + } + + const std::string output = cli.misc_config.has("output") ? cli.misc_config.opt_string("output") : ""; + + if (actions.has("export_stl")) { + for (auto& model : models) + model.add_default_instances(); + if (!export_models(models, IO::STL, output)) + return 1; + } + if (actions.has("export_obj")) { + for (auto& model : models) + model.add_default_instances(); + if (!export_models(models, IO::OBJ, output)) + return 1; + } + if (actions.has("export_3mf")) { + if (!export_models(models, IO::TMF, output)) + return 1; + } + + if (actions.has("slice")) { + PrinterTechnology printer_technology = Preset::printer_technology(print_config); + + const Vec2crd gap{ s_multiple_beds.get_bed_gap() }; + arr2::ArrangeBed bed = arr2::to_arrange_bed(get_bed_shape(print_config), gap); + arr2::ArrangeSettings arrange_cfg; + arrange_cfg.set_distance_from_objects(min_object_distance(print_config)); + + for (Model& model : 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). + if (!transform.has("dont_arrange") || !transform.opt_bool("dont_arrange")) { + if (transform.has("center")) { + Vec2d c = transform.option("center")->value; + arrange_objects(model, arr2::InfiniteBed{ scaled(c) }, arrange_cfg); + } + else + arrange_objects(model, bed, arrange_cfg); + } + + Print fff_print; + SLAPrint sla_print; + sla_print.set_status_callback( [](const PrintBase::SlicingStatus& s) { + if (s.percent >= 0) { // FIXME: is this sufficient? + printf("%3d%s %s\n", s.percent, "% =>", s.text.c_str()); + std::fflush(stdout); + } + }); + + PrintBase* print = (printer_technology == ptFFF) ? static_cast(&fff_print) : static_cast(&sla_print); + if (printer_technology == ptFFF) { + for (auto* mo : model.objects) + fff_print.auto_assign_extruders(mo); + } + print->apply(model, print_config); + std::string err = print->validate(); + if (!err.empty()) { + boost::nowide::cerr << err << std::endl; + return 1; + } + + std::string outfile = output; + + if (print->empty()) + boost::nowide::cout << "Nothing to print for " << outfile << " . Either the print is empty or no object is fully inside the print volume." << std::endl; + else + try { + std::string outfile_final; + print->process(); + if (printer_technology == ptFFF) { + // The outfile is processed by a PlaceholderParser. + outfile = fff_print.export_gcode(outfile, nullptr, get_thumbnail_generator_cli(fff_print)); + outfile_final = fff_print.print_statistics().finalize_output_path(outfile); + } + else { + outfile = sla_print.output_filepath(outfile); + // We need to finalize the filename beforehand because the export function sets the filename inside the zip metadata + outfile_final = sla_print.print_statistics().finalize_output_path(outfile); + sla_print.export_print(outfile_final); + } + if (outfile != outfile_final) { + if (Slic3r::rename_file(outfile, outfile_final)) { + boost::nowide::cerr << "Renaming file " << outfile << " to " << outfile_final << " failed" << std::endl; + return false; + } + outfile = outfile_final; + } + // Run the post-processing scripts if defined. + run_post_process_scripts(outfile, fff_print.full_print_config()); + boost::nowide::cout << "Slicing result exported to " << outfile << std::endl; + } + catch (const std::exception& ex) { + boost::nowide::cerr << ex.what() << std::endl; + return false; + } + + } + } + + return true; +} + +} \ No newline at end of file diff --git a/src/CLI/ProcessTransform.cpp b/src/CLI/ProcessTransform.cpp new file mode 100644 index 0000000000..2641e338d0 --- /dev/null +++ b/src/CLI/ProcessTransform.cpp @@ -0,0 +1,205 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libslic3r/libslic3r.h" +#if !SLIC3R_OPENGL_ES +#include +#endif // !SLIC3R_OPENGL_ES +#include "libslic3r/Config.hpp" +#include "libslic3r/Geometry.hpp" +#include "libslic3r/Model.hpp" +#include "libslic3r/ModelProcessing.hpp" +#include "libslic3r/CutUtils.hpp" +#include +#include "libslic3r/MultipleBeds.hpp" + +#include "CLI.hpp" + +namespace Slic3r::CLI { + +bool process_transform(Data& cli, const DynamicPrintConfig& print_config, std::vector& models) +{ + DynamicPrintConfig& transform = cli.transform_config; + DynamicPrintConfig& actions = cli.actions_config; + + const Vec2crd gap{ s_multiple_beds.get_bed_gap() }; + arr2::ArrangeBed bed = arr2::to_arrange_bed(get_bed_shape(print_config), gap); + arr2::ArrangeSettings arrange_cfg; + if (transform.has("merge") || transform.has("duplicate")) + arrange_cfg.set_distance_from_objects(min_object_distance(print_config)); + + if (transform.has("merge")) { + Model m; + + for (auto& model : models) + for (ModelObject* o : model.objects) + m.add_object(*o); + // Rearrange instances unless --dont-arrange is supplied + if (!transform.has("dont_arrange") && !transform.opt_bool("dont_arrange")) { + m.add_default_instances(); + if (actions.has("slice")) + arrange_objects(m, bed, arrange_cfg); + else + arrange_objects(m, arr2::InfiniteBed{}, arrange_cfg);//?????? + } + models.clear(); + models.emplace_back(std::move(m)); + } + + if (transform.has("duplicate")) { + for (auto& model : models) { + const bool all_objects_have_instances = std::none_of( + model.objects.begin(), model.objects.end(), + [](ModelObject* o) { return o->instances.empty(); } + ); + + int dups = transform.opt_int("duplicate"); + if (!all_objects_have_instances) model.add_default_instances(); + + try { + if (dups > 1) { + // if all input objects have defined position(s) apply duplication to the whole model + duplicate(model, size_t(dups), bed, arrange_cfg); + } + else { + arrange_objects(model, bed, arrange_cfg); + } + } + catch (std::exception& ex) { + boost::nowide::cerr << "error: " << ex.what() << std::endl; + return false; + } + } + } + if (transform.has("duplicate_grid")) { + std::vector& ints = transform.option("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 = print_config.opt_float("duplicate_distance"); + for (auto& model : models) + model.duplicate_objects_grid(x, y, (distance > 0) ? distance : 6); // TODO: this is not the right place for setting a default + } + + if (transform.has("center")) { + for (auto& model : models) { + model.add_default_instances(); + // this affects instances: + model.center_instances_around_point(transform.option("center")->value); + // this affects volumes: + //FIXME Vojtech: Who knows why the complete model should be aligned with Z as a single rigid body? + //model.align_to_ground(); + BoundingBoxf3 bbox; + for (ModelObject* model_object : model.objects) + // We are interested into the Z span only, therefore it is sufficient to measure the bounding box of the 1st instance only. + bbox.merge(model_object->instance_bounding_box(0, false)); + for (ModelObject* model_object : model.objects) + for (ModelInstance* model_instance : model_object->instances) + model_instance->set_offset(Z, model_instance->get_offset(Z) - bbox.min.z()); + } + } + + if (transform.has("align_xy")) { + const Vec2d& p = transform.option("align_xy")->value; + for (auto& model : models) { + BoundingBoxf3 bb = model.bounding_box_exact(); + // this affects volumes: + model.translate(-(bb.min.x() - p.x()), -(bb.min.y() - p.y()), -bb.min.z()); + } + } + + if (transform.has("rotate")) { + for (auto& model : models) + for (auto& o : model.objects) + // this affects volumes: + o->rotate(Geometry::deg2rad(transform.opt_float("rotate")), Z); + } + if (transform.has("rotate_x")) { + for (auto& model : models) + for (auto& o : model.objects) + // this affects volumes: + o->rotate(Geometry::deg2rad(transform.opt_float("rotate_x")), X); + } + if (transform.has("rotate_y")) { + for (auto& model : models) + for (auto& o : model.objects) + // this affects volumes: + o->rotate(Geometry::deg2rad(transform.opt_float("rotate_y")), Y); + } + + if (transform.has("scale")) { + for (auto& model : models) + for (auto& o : model.objects) + // this affects volumes: + o->scale(transform.get_abs_value("scale", 1)); + } + if (transform.has("scale_to_fit")) { + const Vec3d& opt = transform.opt("scale_to_fit")->value; + if (opt.x() <= 0 || opt.y() <= 0 || opt.z() <= 0) { + boost::nowide::cerr << "--scale-to-fit requires a positive volume" << std::endl; + return false; + } + for (auto& model : models) + for (auto& o : model.objects) + // this affects volumes: + o->scale_to_fit(opt); + } + + if (transform.has("cut")) { + std::vector new_models; + for (auto& model : models) { + model.translate(0, 0, -model.bounding_box_exact().min.z()); // align to z = 0 + size_t num_objects = model.objects.size(); + for (size_t i = 0; i < num_objects; ++i) { + Cut cut(model.objects.front(), 0, Geometry::translation_transform(transform.opt_float("cut") * Vec3d::UnitZ()), + ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::PlaceOnCutUpper); + auto cut_objects = cut.perform_with_plane(); + for (ModelObject* obj : cut_objects) + model.add_object(*obj); + model.delete_object(size_t(0)); + } + } + + // TODO: copy less stuff around using pointers + models = new_models; + + if (actions.empty()) { + // cutting transformations are setting an "export" action. + actions.set_key_value("export_stl", new ConfigOptionBool(true)); + } + } + + if (transform.has("split")) { + for (Model& model : models) { + size_t num_objects = model.objects.size(); + for (size_t i = 0; i < num_objects; ++i) { + ModelObjectPtrs new_objects; + ModelProcessing::split(model.objects.front(), &new_objects); + model.delete_object(size_t(0)); + } + } + } + + // All transforms have been dealt with. Now ensure that the objects are on bed. + // (Unless the user said otherwise.) + if (!transform.has("ensure_on_bed") || transform.opt_bool("ensure_on_bed")) + for (auto& model : models) + for (auto& o : model.objects) + o->ensure_on_bed(); + + return true; +} + +} \ No newline at end of file diff --git a/src/CLI/Run.cpp b/src/CLI/Run.cpp new file mode 100644 index 0000000000..bbbdd44326 --- /dev/null +++ b/src/CLI/Run.cpp @@ -0,0 +1,54 @@ +#include "../PrusaSlicer.hpp" +#include "CLI.hpp" + +namespace Slic3r::CLI { + +int run(int argc, char** argv) +{ + Data cli; + if (!setup(cli, argc, argv)) + return 1; + + if (process_profiles_sharing(cli)) + return 1; + + bool start_gui = cli.empty() || (cli.actions_config.empty() && !cli.transform_config.has("cut")); + PrinterTechnology printer_technology = get_printer_technology(cli.overrides_config); + DynamicPrintConfig print_config = {}; + std::vector models; + +#ifdef SLIC3R_GUI + GUI::GUI_InitParams gui_params; + start_gui |= init_gui_params(gui_params, argc, argv, cli); + + if (gui_params.start_as_gcodeviewer) + return start_as_gcode_viewer(gui_params); +#endif + + if (!load_print_data(models, print_config, printer_technology, cli)) + return 1; + + if (!start_gui && is_needed_post_processing(print_config)) + return 0; + + if (!process_transform(cli, print_config, models)) + return 1; + + if (!process_actions(cli, print_config, models)) + return 1; + + if (start_gui) { +#ifdef SLIC3R_GUI + return start_gui_with_params(gui_params); +#else + // No GUI support. Just print out a help. + print_help(false); + // If started without a parameter, consider it to be OK, otherwise report an error code (no action etc). + return (argc == 0) ? 0 : 1; +#endif + } + + return 0; +} + +} \ No newline at end of file diff --git a/src/CLI/Setup.cpp b/src/CLI/Setup.cpp new file mode 100644 index 0000000000..0c91cbc38a --- /dev/null +++ b/src/CLI/Setup.cpp @@ -0,0 +1,342 @@ +#include +#include +#include +#include +#include +#include + +#include "libslic3r/libslic3r.h" +#include "libslic3r/Config.hpp" +#include "libslic3r/PrintConfig.hpp" +#include "libslic3r/Platform.hpp" +#include "libslic3r/Utils.hpp" +#include "libslic3r/Thread.hpp" +#include "libslic3r/BlacklistedLibraryCheck.hpp" +#include "libslic3r/Utils/DirectoriesUtils.hpp" + +#include "CLI.hpp" + +#ifdef SLIC3R_GUI +#include "slic3r/Utils/ServiceConfig.hpp" +#endif /* SLIC3R_GUI */ + + +namespace Slic3r::CLI { + +enum class Type +{ + Input, + Overrides, + Transformations, + Misc, + Actions +}; + +static bool read(Data& data, int argc, const char* const argv[]) +{ + // cache the CLI option => opt_key mapping + std::map > opts; + + std::initializer_list> list = { + { cli_input_config_def, Type::Input}, + { print_config_def, Type::Overrides}, + { cli_transform_config_def, Type::Transformations}, + { cli_misc_config_def, Type::Misc}, + { cli_actions_config_def, Type::Actions} + }; + + for (const auto& [config_def, type] : list) { + for (const auto& oit : config_def.options) + for (const std::string& t : oit.second.cli_args(oit.first)) + opts[t] = { oit.first , type }; + } + + bool parse_options = true; + for (int i = 1; i < argc; ++i) { + std::string token = argv[i]; + // Store non-option arguments in the provided vector. + if (!parse_options || !boost::starts_with(token, "-")) { + data.input_files.push_back(token); + continue; + } +#ifdef __APPLE__ + if (boost::starts_with(token, "-psn_")) + // OSX launcher may add a "process serial number", for example "-psn_0_989382" to the command line. + // While it is supposed to be dropped since OSX 10.9, we will rather ignore it. + continue; +#endif /* __APPLE__ */ + // Stop parsing tokens as options when -- is supplied. + if (token == "--") { + parse_options = false; + continue; + } + // Remove leading dashes (one or two). + token.erase(token.begin(), token.begin() + (boost::starts_with(token, "--") ? 2 : 1)); + // Read value when supplied in the --key=value form. + std::string value; + { + size_t equals_pos = token.find("="); + if (equals_pos != std::string::npos) { + value = token.substr(equals_pos + 1); + token.erase(equals_pos); + } + } + // Look for the cli -> option mapping. + auto it = opts.find(token); + bool no = false; + if (it == opts.end()) { + // Remove the "no-" prefix used to negate boolean options. + std::string yes_token; + if (boost::starts_with(token, "no-")) { + yes_token = token.substr(3); + it = opts.find(yes_token); + no = true; + } + if (it == opts.end()) { + boost::nowide::cerr << "Unknown option --" << token.c_str() << std::endl; + return false; + } + if (no) + token = yes_token; + } + + //const t_config_option_key& opt_key = it->second.first; + //const ConfigOptionDef& optdef = *this->option_def(opt_key); + const auto& [opt_key, type] = it->second; + const ConfigDef* config_def; + DynamicPrintConfig* config; + if (type == Type::Input) { + config_def = &cli_input_config_def; + config = &data.input_config; + } + else if (type == Type::Transformations) { + config_def = &cli_transform_config_def; + config = &data.transform_config; + } + else if(type == Type::Misc) { + config_def = &cli_misc_config_def; + config = &data.misc_config; + } + else if(type == Type::Actions) { + config_def = &cli_actions_config_def; + config = &data.actions_config; + } + else { + config_def = &print_config_def; + config = &data.overrides_config; + } + + const ConfigOptionDef* optdef = config_def->get(opt_key); + assert(optdef); + + // If the option type expects a value and it was not already provided, + // look for it in the next token. + if (value.empty() && optdef->type != coBool && optdef->type != coBools) { + if (i == argc - 1) { + boost::nowide::cerr << "No value supplied for --" << token.c_str() << std::endl; + return false; + } + value = argv[++i]; + } + + if (no) { + assert(optdef->type == coBool || optdef->type == coBools); + if (!value.empty()) { + boost::nowide::cerr << "Boolean options negated by the --no- prefix cannot have a value." << std::endl; + return false; + } + } + + // Store the option value. + const bool existing = config->has(opt_key); + ConfigOption* opt_base = existing ? config->option(opt_key) : optdef->create_default_option(); + if (!existing) + config->set_key_value(opt_key, opt_base); + ConfigOptionVectorBase* opt_vector = opt_base->is_vector() ? static_cast(opt_base) : nullptr; + if (opt_vector) { + if (!existing) + // remove the default values + opt_vector->clear(); + // Vector values will be chained. Repeated use of a parameter will append the parameter or parameters + // to the end of the value. + if (opt_base->type() == coBools && value.empty()) + static_cast(opt_base)->values.push_back(!no); + else + // Deserialize any other vector value (ConfigOptionInts, Floats, Percents, Points) the same way + // they get deserialized from an .ini file. For ConfigOptionStrings, that means that the C-style unescape + // will be applied for values enclosed in quotes, while values non-enclosed in quotes are left to be + // unescaped by the calling shell. + opt_vector->deserialize(value, true); + } + else if (opt_base->type() == coBool) { + if (value.empty()) + static_cast(opt_base)->value = !no; + else + opt_base->deserialize(value); + } + else if (opt_base->type() == coString) { + // Do not unescape single string values, the unescaping is left to the calling shell. + static_cast(opt_base)->value = value; + } + else { + // Just bail out if the configuration value is not understood. + ConfigSubstitutionContext context(ForwardCompatibilitySubstitutionRule::Disable); + // Any scalar value of a type different from Bool and String. + if (!config->set_deserialize_nothrow(opt_key, value, context, false)) { + boost::nowide::cerr << "Invalid value supplied for --" << token.c_str() << std::endl; + return false; + } + } + } + + // normalize override options + if (!data.overrides_config.empty()) + data.overrides_config.normalize_fdm(); + + if (!data.misc_config.has("config_compatibility")) { + // "config_compatibility" can be used during the loading configuration + // So, if this option wasn't set, then initialise it from default value + const ConfigOptionDef* optdef = cli_misc_config_def.get("config_compatibility"); + ConfigOption* opt_with_def_value = optdef->create_default_option(); + if (opt_with_def_value) + data.misc_config.set_key_value("config_compatibility", opt_with_def_value); + } + + return true; +} + +static bool setup_common() +{ + // Mark the main thread for the debugger and for runtime checks. + set_current_thread_name("slic3r_main"); + // Save the thread ID of the main thread. + save_main_thread_id(); + +#ifdef __WXGTK__ + // On Linux, wxGTK has no support for Wayland, and the app crashes on + // startup if gtk3 is used. This env var has to be set explicitly to + // instruct the window manager to fall back to X server mode. + ::setenv("GDK_BACKEND", "x11", /* replace */ true); + + // https://github.com/prusa3d/PrusaSlicer/issues/12969 + ::setenv("WEBKIT_DISABLE_COMPOSITING_MODE", "1", /* replace */ false); + ::setenv("WEBKIT_DISABLE_DMABUF_RENDERER", "1", /* replace */ false); +#endif + + // Switch boost::filesystem to utf8. + try { + boost::nowide::nowide_filesystem(); + } + catch (const std::runtime_error& ex) { + std::string caption = std::string(SLIC3R_APP_NAME) + " Error"; + std::string text = std::string("An error occured while setting up locale.\n") + ( +#if !defined(_WIN32) && !defined(__APPLE__) + // likely some linux system + "You may need to reconfigure the missing locales, likely by running the \"locale-gen\" and \"dpkg-reconfigure locales\" commands.\n" +#endif + SLIC3R_APP_NAME " will now terminate.\n\n") + ex.what(); +#if defined(_WIN32) && defined(SLIC3R_GUI) + MessageBoxA(NULL, text.c_str(), caption.c_str(), MB_OK | MB_ICONERROR); +#endif + boost::nowide::cerr << text.c_str() << std::endl; + return false; + } + + { + Slic3r::set_logging_level(1); + const char* loglevel = boost::nowide::getenv("SLIC3R_LOGLEVEL"); + if (loglevel != nullptr) { + if (loglevel[0] >= '0' && loglevel[0] <= '9' && loglevel[1] == 0) + set_logging_level(loglevel[0] - '0'); + else + boost::nowide::cerr << "Invalid SLIC3R_LOGLEVEL environment variable: " << loglevel << std::endl; + } + } + + // Detect the operating system flavor after SLIC3R_LOGLEVEL is set. + detect_platform(); + +#ifdef WIN32 + // Notify user that a blacklisted DLL was injected into PrusaSlicer process (for example Nahimic, see GH #5573). + // We hope that if a DLL is being injected into a PrusaSlicer process, it happens at the very start of the application, + // thus we shall detect them now. + if (BlacklistedLibraryCheck::get_instance().perform_check()) { + std::wstring text = L"Following DLLs have been injected into the PrusaSlicer process:\n\n"; + text += BlacklistedLibraryCheck::get_instance().get_blacklisted_string(); + text += L"\n\n" + L"PrusaSlicer is known to not run correctly with these DLLs injected. " + L"We suggest stopping or uninstalling these services if you experience " + L"crashes or unexpected behaviour while using PrusaSlicer.\n" + L"For example, ASUS Sonic Studio injects a Nahimic driver, which makes PrusaSlicer " + L"to crash on a secondary monitor, see PrusaSlicer github issue #5573"; + MessageBoxW(NULL, text.c_str(), L"Warning"/*L"Incopatible library found"*/, MB_OK); + } +#endif + + // See Invoking prusa-slicer from $PATH environment variable crashes #5542 + // boost::filesystem::path path_to_binary = boost::filesystem::system_complete(argv[0]); + boost::filesystem::path path_to_binary = boost::dll::program_location(); + + // Path from the Slic3r binary to its resources. +#ifdef __APPLE__ + // The application is packed in the .dmg archive as 'Slic3r.app/Contents/MacOS/Slic3r' + // The resources are packed to 'Slic3r.app/Contents/Resources' + boost::filesystem::path path_resources = boost::filesystem::canonical(path_to_binary).parent_path() / "../Resources"; +#elif defined _WIN32 + // The application is packed in the .zip archive in the root, + // The resources are packed to 'resources' + // Path from Slic3r binary to resources: + boost::filesystem::path path_resources = path_to_binary.parent_path() / "resources"; +#elif defined SLIC3R_FHS + // The application is packaged according to the Linux Filesystem Hierarchy Standard + // Resources are set to the 'Architecture-independent (shared) data', typically /usr/share or /usr/local/share + boost::filesystem::path path_resources = SLIC3R_FHS_RESOURCES; +#else + // The application is packed in the .tar.bz archive (or in AppImage) as 'bin/slic3r', + // The resources are packed to 'resources' + // Path from Slic3r binary to resources: + boost::filesystem::path path_resources = boost::filesystem::canonical(path_to_binary).parent_path() / "../resources"; +#endif + + set_resources_dir(path_resources.string()); + set_var_dir((path_resources / "icons").string()); + set_local_dir((path_resources / "localization").string()); + set_sys_shapes_dir((path_resources / "shapes").string()); + set_custom_gcodes_dir((path_resources / "custom_gcodes").string()); + + return true; +} + +bool setup(Data& cli, int argc, char** argv) +{ + if (!setup_common()) + return false; + + if (!read(cli, argc, argv)) { + // Separate error message reported by the CLI parser from the help. + boost::nowide::cerr << std::endl; + print_help(); + return false; + } + + if (cli.misc_config.has("loglevel")) + { + int loglevel = cli.misc_config.opt_int("loglevel"); + if (loglevel != 0) + set_logging_level(loglevel); + } + + if (cli.misc_config.has("threads")) + thread_count = cli.misc_config.opt_int("threads"); + + set_data_dir(cli.misc_config.has("datadir") ? cli.misc_config.opt_string("datadir") : get_default_datadir()); + +#ifdef SLIC3R_GUI + if (cli.misc_config.has("webdev")) { + Utils::ServiceConfig::instance().set_webdev_enabled(cli.misc_config.opt_bool("webdev")); + } +#endif + return true; +} + +} \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b2d78e5b2d..917ecc9d40 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -95,12 +95,35 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/platform/msw/PrusaSlicer.rc.in ${CMAK configure_file(${CMAKE_CURRENT_SOURCE_DIR}/platform/msw/PrusaSlicer-gcodeviewer.rc.in ${CMAKE_CURRENT_BINARY_DIR}/PrusaSlicer-gcodeviewer.rc @ONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/platform/msw/PrusaSlicer.manifest.in ${CMAKE_CURRENT_BINARY_DIR}/PrusaSlicer.manifest @ONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/platform/osx/Info.plist.in ${CMAKE_CURRENT_BINARY_DIR}/Info.plist @ONLY) -if (WIN32) - add_library(PrusaSlicer SHARED PrusaSlicer.cpp PrusaSlicer.hpp) -else () - add_executable(PrusaSlicer PrusaSlicer.cpp PrusaSlicer.hpp) + +set(SLIC3R_CLI_SOURCES + PrusaSlicer.hpp + CLI/CLI.hpp + CLI/PrintHelp.cpp + CLI/Setup.cpp + CLI/LoadPrintData.cpp + CLI/ProcessTransform.cpp + CLI/ProcessActions.cpp + CLI/Run.cpp +) +if (SLIC3R_GUI) + list(APPEND SLIC3R_CLI_SOURCES + CLI/GuiParams.cpp + ) endif () +if (WIN32) + add_library(PrusaSlicer SHARED PrusaSlicer.cpp ${SLIC3R_CLI_SOURCES}) +else () + add_executable(PrusaSlicer PrusaSlicer.cpp ${SLIC3R_CLI_SOURCES}) +endif () + +foreach(_source IN ITEMS ${SLIC3R_CLI_SOURCES}) + get_filename_component(_source_path "${_source}" PATH) + string(REPLACE "/" "\\" _group_path "${_source_path}") + source_group("${_group_path}" FILES "${_source}") +endforeach() + if (MINGW) target_link_options(PrusaSlicer PUBLIC "-Wl,-allow-multiple-definition") set_target_properties(PrusaSlicer PROPERTIES PREFIX "") diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index cd701a7b96..ce287b7b29 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -25,1381 +25,14 @@ #include #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include -#include "unix/fhs.hpp" // Generated by CMake from ../platform/unix/fhs.hpp.in +#include #include "libslic3r/libslic3r.h" -#if !SLIC3R_OPENGL_ES -#include -#endif // !SLIC3R_OPENGL_ES -#include "libslic3r/Config.hpp" -#include "libslic3r/Geometry.hpp" -#include "libslic3r/GCode/PostProcessor.hpp" -#include "libslic3r/Model.hpp" -#include "libslic3r/ModelProcessing.hpp" -#include "libslic3r/FileReader.hpp" -#include "libslic3r/CutUtils.hpp" -#include -#include "libslic3r/Platform.hpp" -#include "libslic3r/Print.hpp" -#include "libslic3r/SLAPrint.hpp" -#include "libslic3r/TriangleMesh.hpp" -#include "libslic3r/Format/AMF.hpp" -#include "libslic3r/Format/3mf.hpp" -#include "libslic3r/Format/STL.hpp" -#include "libslic3r/Format/OBJ.hpp" -#include "libslic3r/Format/SL1.hpp" -#include "libslic3r/miniz_extension.hpp" -#include "libslic3r/PNGReadWrite.hpp" -#include "libslic3r/Utils.hpp" -#include "libslic3r/Thread.hpp" -#include "libslic3r/BlacklistedLibraryCheck.hpp" -#include "libslic3r/ProfilesSharingUtils.hpp" -#include "libslic3r/Utils/DirectoriesUtils.hpp" -#include "libslic3r/MultipleBeds.hpp" #include "PrusaSlicer.hpp" -#ifdef SLIC3R_GUI - #include "slic3r/GUI/GUI_Init.hpp" - #include "slic3r/Utils/ServiceConfig.hpp" -#endif /* SLIC3R_GUI */ - -using namespace Slic3r; - - -// struct which is filled from comand line input -struct Cli -{ - DynamicPrintConfig input_config; - DynamicPrintConfig overrides_config; - DynamicPrintConfig transform_config; - DynamicPrintConfig misc_config; - DynamicPrintConfig actions_config; - - std::vector input_files; - - bool read(int argc, const char* const argv[]); - - bool empty() { - return input_files.empty() - && input_config.empty() - && overrides_config.empty() - && transform_config.empty() - && actions_config.empty(); - } - -}; - - -enum class CliType -{ - Input, - Overrides, - Transformations, - Misc, - Actions -}; - - -bool Cli::read(int argc, const char* const argv[]) -{ - // cache the CLI option => opt_key mapping - std::map > opts; - - std::initializer_list> list = { - { cli_input_config_def, CliType::Input}, - { print_config_def, CliType::Overrides}, - { cli_transform_config_def, CliType::Transformations}, - { cli_misc_config_def, CliType::Misc}, - { cli_actions_config_def, CliType::Actions} - }; - - for (const auto& [config_def, type] : list) { - for (const auto& oit : config_def.options) - for (const std::string& t : oit.second.cli_args(oit.first)) - opts[t] = { oit.first , type }; - } - - bool parse_options = true; - for (int i = 1; i < argc; ++i) { - std::string token = argv[i]; - // Store non-option arguments in the provided vector. - if (!parse_options || !boost::starts_with(token, "-")) { - input_files.push_back(token); - continue; - } -#ifdef __APPLE__ - if (boost::starts_with(token, "-psn_")) - // OSX launcher may add a "process serial number", for example "-psn_0_989382" to the command line. - // While it is supposed to be dropped since OSX 10.9, we will rather ignore it. - continue; -#endif /* __APPLE__ */ - // Stop parsing tokens as options when -- is supplied. - if (token == "--") { - parse_options = false; - continue; - } - // Remove leading dashes (one or two). - token.erase(token.begin(), token.begin() + (boost::starts_with(token, "--") ? 2 : 1)); - // Read value when supplied in the --key=value form. - std::string value; - { - size_t equals_pos = token.find("="); - if (equals_pos != std::string::npos) { - value = token.substr(equals_pos + 1); - token.erase(equals_pos); - } - } - // Look for the cli -> option mapping. - auto it = opts.find(token); - bool no = false; - if (it == opts.end()) { - // Remove the "no-" prefix used to negate boolean options. - std::string yes_token; - if (boost::starts_with(token, "no-")) { - yes_token = token.substr(3); - it = opts.find(yes_token); - no = true; - } - if (it == opts.end()) { - boost::nowide::cerr << "Unknown option --" << token.c_str() << std::endl; - return false; - } - if (no) - token = yes_token; - } - - //const t_config_option_key& opt_key = it->second.first; - //const ConfigOptionDef& optdef = *this->option_def(opt_key); - const auto& [opt_key, type] = it->second; - const ConfigDef* config_def; - DynamicPrintConfig* config; - if (type == CliType::Input) { - config_def = &cli_input_config_def; - config = &input_config; - } - else if (type == CliType::Transformations) { - config_def = &cli_transform_config_def; - config = &transform_config; - } - else if(type == CliType::Misc) { - config_def = &cli_misc_config_def; - config = &misc_config; - } - else if(type == CliType::Actions) { - config_def = &cli_actions_config_def; - config = &actions_config; - } - else { - config_def = &print_config_def; - config = &overrides_config; - } - - const ConfigOptionDef* optdef = config_def->get(opt_key); - assert(optdef); - - // If the option type expects a value and it was not already provided, - // look for it in the next token. - if (value.empty() && optdef->type != coBool && optdef->type != coBools) { - if (i == argc - 1) { - boost::nowide::cerr << "No value supplied for --" << token.c_str() << std::endl; - return false; - } - value = argv[++i]; - } - - if (no) { - assert(optdef->type == coBool || optdef->type == coBools); - if (!value.empty()) { - boost::nowide::cerr << "Boolean options negated by the --no- prefix cannot have a value." << std::endl; - return false; - } - } - - // Store the option value. - const bool existing = config->has(opt_key); - ConfigOption* opt_base = existing ? config->option(opt_key) : optdef->create_default_option(); - if (!existing) - config->set_key_value(opt_key, opt_base); - ConfigOptionVectorBase* opt_vector = opt_base->is_vector() ? static_cast(opt_base) : nullptr; - if (opt_vector) { - if (!existing) - // remove the default values - opt_vector->clear(); - // Vector values will be chained. Repeated use of a parameter will append the parameter or parameters - // to the end of the value. - if (opt_base->type() == coBools && value.empty()) - static_cast(opt_base)->values.push_back(!no); - else - // Deserialize any other vector value (ConfigOptionInts, Floats, Percents, Points) the same way - // they get deserialized from an .ini file. For ConfigOptionStrings, that means that the C-style unescape - // will be applied for values enclosed in quotes, while values non-enclosed in quotes are left to be - // unescaped by the calling shell. - opt_vector->deserialize(value, true); - } - else if (opt_base->type() == coBool) { - if (value.empty()) - static_cast(opt_base)->value = !no; - else - opt_base->deserialize(value); - } - else if (opt_base->type() == coString) { - // Do not unescape single string values, the unescaping is left to the calling shell. - static_cast(opt_base)->value = value; - } - else { - // Just bail out if the configuration value is not understood. - ConfigSubstitutionContext context(ForwardCompatibilitySubstitutionRule::Disable); - // Any scalar value of a type different from Bool and String. - if (!config->set_deserialize_nothrow(opt_key, value, context, false)) { - boost::nowide::cerr << "Invalid value supplied for --" << token.c_str() << std::endl; - return false; - } - } - } - - // normalize override options - if (!overrides_config.empty()) - overrides_config.normalize_fdm(); - - if (!misc_config.has("config_compatibility")) { - // "config_compatibility" can be used during the loading configuration - // So, if this option wasn't set, then initialise it from default value - const ConfigOptionDef* optdef = cli_misc_config_def.get("config_compatibility"); - ConfigOption* opt_with_def_value = optdef->create_default_option(); - if (opt_with_def_value) - misc_config.set_key_value("config_compatibility", opt_with_def_value); - } - - return true; -} - -static void print_help(bool include_print_options = false, PrinterTechnology printer_technology = ptAny) -{ - boost::nowide::cout - << SLIC3R_BUILD_ID << " " << "based on Slic3r" -#ifdef SLIC3R_GUI - << " (with GUI support)" -#else /* SLIC3R_GUI */ - << " (without GUI support)" -#endif /* SLIC3R_GUI */ - << std::endl - << "https://github.com/prusa3d/PrusaSlicer" << std::endl << std::endl - << "Usage: prusa-slicer [ INPUT ] [ OPTIONS ] [ ACTIONS ] [ TRANSFORM ] [ file.stl ... ]" << std::endl; - - boost::nowide::cout - << std::endl - << "Input:" << std::endl; - cli_input_config_def.print_cli_help(boost::nowide::cout, false); - - boost::nowide::cout - << std::endl - << "Actions:" << std::endl; - cli_actions_config_def.print_cli_help(boost::nowide::cout, false)//; - << std::endl - << "Profiles sharing options:" << std::endl; - cli_profiles_sharing_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); - - boost::nowide::cout - << std::endl - << "Print options are processed in the following order:" << std::endl - << "\t1) Config keys from the command line, for example --fill-pattern=stars" << std::endl - << "\t (highest priority, overwrites everything below)" << std::endl - << "\t2) Config files loaded with --load" << std::endl - << "\t3) Config values loaded from 3mf files" << std::endl; - - if (include_print_options) { - boost::nowide::cout << std::endl; - print_config_def.print_cli_help(boost::nowide::cout, true, [printer_technology](const ConfigOptionDef& def) - { return printer_technology == ptAny || def.printer_technology == ptAny || printer_technology == def.printer_technology; }); - } - else { - boost::nowide::cout - << std::endl - << "Run --help-fff / --help-sla to see the full listing of print options." << std::endl; - } -} - -static PrinterTechnology get_printer_technology(const DynamicConfig &config) -{ - const ConfigOptionEnum *opt = config.option>("printer_technology"); - return (opt == nullptr) ? ptUnknown : opt->value; -} - -// may be "validate_and_apply_printer_technology" will be better? -static bool can_apply_printer_technology(PrinterTechnology& printer_technology, const PrinterTechnology& other_printer_technology) -{ - if (printer_technology == ptUnknown) { - printer_technology = other_printer_technology; - return true; - } - - bool invalid_other_pt = printer_technology != other_printer_technology && other_printer_technology != ptUnknown; - - if (invalid_other_pt) - boost::nowide::cerr << "Mixing configurations for FFF and SLA technologies" << std::endl; - - return !invalid_other_pt; -} - -static bool has_profile_sharing_action(const Cli& cli) -{ - return cli.actions_config.has("query-printer-models") || cli.actions_config.has("query-print-filament-profiles"); -} - -static bool has_full_config_from_profiles(const Cli& cli) -{ - const DynamicPrintConfig& input = cli.input_config; - return !has_profile_sharing_action(cli) && - (input.has("print-profile") && !input.opt_string("print-profile").empty() || - input.has("material-profile") && !input.option("material-profile")->values.empty() || - input.has("printer-profile") && !input.opt_string("printer-profile").empty()); -} - -static bool process_profiles_sharing(const Cli& cli) -{ - if (!has_profile_sharing_action(cli)) - return false; - - std::string ret; - - if (cli.actions_config.has("query-printer-models")) { - ret = Slic3r::get_json_printer_models(get_printer_technology(cli.overrides_config)); - } - else if (cli.actions_config.has("query-print-filament-profiles")) { - if (cli.input_config.has("printer-profile") && !cli.input_config.opt_string("printer-profile").empty()) { - const std::string printer_profile = cli.input_config.opt_string("printer-profile"); - ret = Slic3r::get_json_print_filament_profiles(printer_profile); - if (ret.empty()) { - boost::nowide::cerr << "query-print-filament-profiles error: Printer profile '" << printer_profile << - "' wasn't found among installed printers." << std::endl << - "Or the request can be wrong." << std::endl; - return true; - } - } - else { - boost::nowide::cerr << "query-print-filament-profiles error: This action requires set 'printer-profile' option" << std::endl; - return true; - } - } - - if (ret.empty()) { - boost::nowide::cerr << "Wrong request" << std::endl; - return true; - } - - // use --output when available - - if (cli.misc_config.has("output")) { - std::string cmdline_param = cli.misc_config.opt_string("output"); - // if we were supplied a directory, use it and append our automatically generated filename - boost::filesystem::path cmdline_path(cmdline_param); - boost::filesystem::path proposed_path = boost::filesystem::path(Slic3r::resources_dir()) / "out.json"; - if (boost::filesystem::is_directory(cmdline_path)) - proposed_path = (cmdline_path / proposed_path.filename()); - else if (cmdline_path.extension().empty()) - proposed_path = cmdline_path.replace_extension("json"); - else - proposed_path = cmdline_path; - const std::string file = proposed_path.string(); - - boost::nowide::ofstream c; - c.open(file, std::ios::out | std::ios::trunc); - c << ret << std::endl; - c.close(); - - boost::nowide::cout << "Output for your request is written into " << file << std::endl; - } - else - printf("%s", ret.c_str()); - - return true; -} - -static void print_config_substitutions(const ConfigSubstitutions& config_substitutions, const std::string& file) -{ - if (config_substitutions.empty()) - return; - boost::nowide::cout << "The following configuration values were substituted when loading \" << file << \":\n"; - for (const ConfigSubstitution& subst : config_substitutions) - boost::nowide::cout << "\tkey = \"" << subst.opt_def->opt_key << "\"\t loaded = \"" << subst.old_value << "\tsubstituted = \"" << subst.new_value->serialize() << "\"\n"; -} - -static bool process_transform(Cli& cli, const DynamicPrintConfig& print_config, std::vector& models) -{ - DynamicPrintConfig& transform = cli.transform_config; - DynamicPrintConfig& actions = cli.actions_config; - - const Vec2crd gap{ s_multiple_beds.get_bed_gap() }; - arr2::ArrangeBed bed = arr2::to_arrange_bed(get_bed_shape(print_config), gap); - arr2::ArrangeSettings arrange_cfg; - if (transform.has("merge") || transform.has("duplicate")) - arrange_cfg.set_distance_from_objects(min_object_distance(print_config)); - - if (transform.has("merge")) { - Model m; - for (auto& model : models) - for (ModelObject* o : model.objects) - m.add_object(*o); - // Rearrange instances unless --dont-arrange is supplied - if (!transform.has("dont_arrange") && !transform.opt_bool("dont_arrange")) { - m.add_default_instances(); - if (actions.has("slice")) - arrange_objects(m, bed, arrange_cfg); - else - arrange_objects(m, arr2::InfiniteBed{}, arrange_cfg);//?????? - } - models.clear(); - models.emplace_back(std::move(m)); - } - - if (transform.has("duplicate")) { - for (auto& model : models) { - const bool all_objects_have_instances = std::none_of( - model.objects.begin(), model.objects.end(), - [](ModelObject* o) { return o->instances.empty(); } - ); - - int dups = transform.opt_int("duplicate"); - if (!all_objects_have_instances) model.add_default_instances(); - - try { - if (dups > 1) { - // if all input objects have defined position(s) apply duplication to the whole model - duplicate(model, size_t(dups), bed, arrange_cfg); - } - else { - arrange_objects(model, bed, arrange_cfg); - } - } - catch (std::exception& ex) { - boost::nowide::cerr << "error: " << ex.what() << std::endl; - return false; - } - } - } - if (transform.has("duplicate_grid")) { - std::vector& ints = transform.option("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 = print_config.opt_float("duplicate_distance"); - for (auto& model : models) - model.duplicate_objects_grid(x, y, (distance > 0) ? distance : 6); // TODO: this is not the right place for setting a default - } - - if (transform.has("center")) { - for (auto& model : models) { - model.add_default_instances(); - // this affects instances: - model.center_instances_around_point(transform.option("center")->value); - // this affects volumes: - //FIXME Vojtech: Who knows why the complete model should be aligned with Z as a single rigid body? - //model.align_to_ground(); - BoundingBoxf3 bbox; - for (ModelObject* model_object : model.objects) - // We are interested into the Z span only, therefore it is sufficient to measure the bounding box of the 1st instance only. - bbox.merge(model_object->instance_bounding_box(0, false)); - for (ModelObject* model_object : model.objects) - for (ModelInstance* model_instance : model_object->instances) - model_instance->set_offset(Z, model_instance->get_offset(Z) - bbox.min.z()); - } - } - - if (transform.has("align_xy")) { - const Vec2d& p = transform.option("align_xy")->value; - for (auto& model : models) { - BoundingBoxf3 bb = model.bounding_box_exact(); - // this affects volumes: - model.translate(-(bb.min.x() - p.x()), -(bb.min.y() - p.y()), -bb.min.z()); - } - } - - if (transform.has("rotate")) { - for (auto& model : models) - for (auto& o : model.objects) - // this affects volumes: - o->rotate(Geometry::deg2rad(transform.opt_float("rotate")), Z); - } - if (transform.has("rotate_x")) { - for (auto& model : models) - for (auto& o : model.objects) - // this affects volumes: - o->rotate(Geometry::deg2rad(transform.opt_float("rotate_x")), X); - } - if (transform.has("rotate_y")) { - for (auto& model : models) - for (auto& o : model.objects) - // this affects volumes: - o->rotate(Geometry::deg2rad(transform.opt_float("rotate_y")), Y); - } - - if (transform.has("scale")) { - for (auto& model : models) - for (auto& o : model.objects) - // this affects volumes: - o->scale(transform.get_abs_value("scale", 1)); - } - if (transform.has("scale_to_fit")) { - const Vec3d& opt = transform.opt("scale_to_fit")->value; - if (opt.x() <= 0 || opt.y() <= 0 || opt.z() <= 0) { - boost::nowide::cerr << "--scale-to-fit requires a positive volume" << std::endl; - return false; - } - for (auto& model : models) - for (auto& o : model.objects) - // this affects volumes: - o->scale_to_fit(opt); - } - - if (transform.has("cut")) { - std::vector new_models; - for (auto& model : models) { - model.translate(0, 0, -model.bounding_box_exact().min.z()); // align to z = 0 - size_t num_objects = model.objects.size(); - for (size_t i = 0; i < num_objects; ++i) { - Cut cut(model.objects.front(), 0, Geometry::translation_transform(transform.opt_float("cut") * Vec3d::UnitZ()), - ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::PlaceOnCutUpper); - auto cut_objects = cut.perform_with_plane(); - for (ModelObject* obj : cut_objects) - model.add_object(*obj); - model.delete_object(size_t(0)); - } - } - - // TODO: copy less stuff around using pointers - models = new_models; - - if (actions.empty()) { - // cutting transformations are setting an "export" action. - actions.set_key_value("export_stl", new ConfigOptionBool(true)); - } - } - - if (transform.has("split")) { - for (Model& model : models) { - size_t num_objects = model.objects.size(); - for (size_t i = 0; i < num_objects; ++i) { - ModelObjectPtrs new_objects; - model.objects.front()->split(&new_objects); - model.delete_object(size_t(0)); - } - } - } - - // All transforms have been dealt with. Now ensure that the objects are on bed. - // (Unless the user said otherwise.) - if (!transform.has("ensure_on_bed") || transform.opt_bool("ensure_on_bed")) - for (auto& model : models) - for (auto& o : model.objects) - o->ensure_on_bed(); - - return true; -} - - - -namespace IO { - enum ExportFormat : int { - OBJ, - STL, - // SVG, - TMF, - Gcode - }; -} - -static std::string output_filepath(const Model& model, IO::ExportFormat format, const std::string& cmdline_param) -{ - std::string ext; - switch (format) { - case IO::OBJ: ext = ".obj"; break; - case IO::STL: ext = ".stl"; break; - case IO::TMF: ext = ".3mf"; break; - default: assert(false); break; - }; - auto proposed_path = boost::filesystem::path(model.propose_export_file_name_and_path(ext)); - // use --output when available - if (!cmdline_param.empty()) { - // if we were supplied a directory, use it and append our automatically generated filename - boost::filesystem::path cmdline_path(cmdline_param); - if (boost::filesystem::is_directory(cmdline_path)) - proposed_path = cmdline_path / proposed_path.filename(); - else - proposed_path = cmdline_path; - } - return proposed_path.string(); -} - -static bool export_models(std::vector& models, IO::ExportFormat format, const std::string& cmdline_param) -{ - for (Model& model : models) { - const std::string path = output_filepath(model, format, cmdline_param); - bool success = false; - switch (format) { - case IO::OBJ: success = Slic3r::store_obj(path.c_str(), &model); break; - case IO::STL: success = Slic3r::store_stl(path.c_str(), &model, true); break; - case IO::TMF: success = Slic3r::store_3mf(path.c_str(), &model, nullptr, false); break; - default: assert(false); break; - } - if (success) - std::cout << "File exported to " << path << std::endl; - else { - std::cerr << "File export to " << path << " failed" << std::endl; - return false; - } - } - return true; -} - -static std::function get_thumbnail_generator_cli(Print& fff_print) -{ - if (!fff_print.model().objects.empty() && boost::iends_with(fff_print.model().objects.front()->input_file, ".3mf")) { - std::string filename = fff_print.model().objects.front()->input_file; - return [filename](const ThumbnailsParams&) { - ThumbnailsList list_out; - - mz_zip_archive archive; - mz_zip_zero_struct(&archive); - - if (!open_zip_reader(&archive, filename)) - return list_out; - mz_uint num_entries = mz_zip_reader_get_num_files(&archive); - mz_zip_archive_file_stat stat; - - int index = mz_zip_reader_locate_file(&archive, "Metadata/thumbnail.png", nullptr, 0); - if (index < 0 || !mz_zip_reader_file_stat(&archive, index, &stat)) - return list_out; - std::string buffer; - buffer.resize(int(stat.m_uncomp_size)); - mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, buffer.data(), (size_t)stat.m_uncomp_size, 0); - if (res == 0) - return list_out; - close_zip_reader(&archive); - - std::vector data; - unsigned width = 0; - unsigned height = 0; - png::decode_png(buffer, data, width, height); - - { - // Flip the image vertically so it matches the convention in Thumbnails generator. - const int row_size = width * 4; // Each pixel is 4 bytes (RGBA) - std::vector temp_row(row_size); - for (int i = 0; i < height / 2; ++i) { - unsigned char* top_row = &data[i * row_size]; - unsigned char* bottom_row = &data[(height - i - 1) * row_size]; - std::copy(bottom_row, bottom_row + row_size, temp_row.begin()); - std::copy(top_row, top_row + row_size, bottom_row); - std::copy(temp_row.begin(), temp_row.end(), top_row); - } - } - - ThumbnailData th; - th.set(width, height); - th.pixels = data; - list_out.push_back(th); - return list_out; - }; - } - - return [](const ThumbnailsParams&) ->ThumbnailsList { return {}; }; -} - -static bool process_actions(Cli& cli, const DynamicPrintConfig& print_config, std::vector& models) -{ - DynamicPrintConfig& actions = cli.actions_config; - DynamicPrintConfig& transform = cli.transform_config; - - // doesn't need any aditional input - - if (actions.has("help")) { - print_help(); - } - if (actions.has("help_fff")) { - print_help(true, ptFFF); - } - if (actions.has("help_sla")) { - print_help(true, ptSLA); - } - - if (actions.has("info")) { - if (models.empty()) { - boost::nowide::cerr << "error: cannot show info for empty models." << std::endl; - return 1; - } - // --info works on unrepaired model - for (Model& model : models) { - model.add_default_instances(); - model.print_info(); - } - } - - if (actions.has("save")) { - //FIXME check for mixing the FFF / SLA parameters. - // or better save fff_print_config vs. sla_print_config - print_config.save(actions.opt_string("save")); - } - - if (models.empty() && (actions.has("export_stl") || actions.has("export_obj") || actions.has("export_3mf"))) { - boost::nowide::cerr << "error: cannot export empty models." << std::endl; - return 1; - } - - const std::string output = cli.misc_config.has("output") ? cli.misc_config.opt_string("output") : ""; - - if (actions.has("export_stl")) { - for (auto& model : models) - model.add_default_instances(); - if (!export_models(models, IO::STL, output)) - return 1; - } - if (actions.has("export_obj")) { - for (auto& model : models) - model.add_default_instances(); - if (!export_models(models, IO::OBJ, output)) - return 1; - } - if (actions.has("export_3mf")) { - if (!export_models(models, IO::TMF, output)) - return 1; - } - - if (actions.has("slice")) { - PrinterTechnology printer_technology = Preset::printer_technology(print_config); - - const Vec2crd gap{ s_multiple_beds.get_bed_gap() }; - arr2::ArrangeBed bed = arr2::to_arrange_bed(get_bed_shape(print_config), gap); - arr2::ArrangeSettings arrange_cfg; - arrange_cfg.set_distance_from_objects(min_object_distance(print_config)); - - for (Model& model : 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). - if (!transform.has("dont_arrange") || !transform.opt_bool("dont_arrange")) { - if (transform.has("center")) { - Vec2d c = transform.option("center")->value; - arrange_objects(model, arr2::InfiniteBed{ scaled(c) }, arrange_cfg); - } - else - arrange_objects(model, bed, arrange_cfg); - } - - Print fff_print; - SLAPrint sla_print; - sla_print.set_status_callback( [](const PrintBase::SlicingStatus& s) { - if (s.percent >= 0) { // FIXME: is this sufficient? - printf("%3d%s %s\n", s.percent, "% =>", s.text.c_str()); - std::fflush(stdout); - } - }); - - PrintBase* print = (printer_technology == ptFFF) ? static_cast(&fff_print) : static_cast(&sla_print); - if (printer_technology == ptFFF) { - for (auto* mo : model.objects) - fff_print.auto_assign_extruders(mo); - } - print->apply(model, print_config); - std::string err = print->validate(); - if (!err.empty()) { - boost::nowide::cerr << err << std::endl; - return 1; - } - - std::string outfile = output; - - if (print->empty()) - boost::nowide::cout << "Nothing to print for " << outfile << " . Either the print is empty or no object is fully inside the print volume." << std::endl; - else - try { - std::string outfile_final; - print->process(); - if (printer_technology == ptFFF) { - // The outfile is processed by a PlaceholderParser. - outfile = fff_print.export_gcode(outfile, nullptr, get_thumbnail_generator_cli(fff_print)); - outfile_final = fff_print.print_statistics().finalize_output_path(outfile); - } - else { - outfile = sla_print.output_filepath(outfile); - // We need to finalize the filename beforehand because the export function sets the filename inside the zip metadata - outfile_final = sla_print.print_statistics().finalize_output_path(outfile); - sla_print.export_print(outfile_final); - } - if (outfile != outfile_final) { - if (Slic3r::rename_file(outfile, outfile_final)) { - boost::nowide::cerr << "Renaming file " << outfile << " to " << outfile_final << " failed" << std::endl; - return false; - } - outfile = outfile_final; - } - // Run the post-processing scripts if defined. - run_post_process_scripts(outfile, fff_print.full_print_config()); - boost::nowide::cout << "Slicing result exported to " << outfile << std::endl; - } - catch (const std::exception& ex) { - boost::nowide::cerr << ex.what() << std::endl; - return false; - } - - } - } - - return true; -} - -#ifdef SLIC3R_GUI -static int start_gui_with_params(GUI::GUI_InitParams& params) -{ - #if !defined(_WIN32) && !defined(__APPLE__) - // likely some linux / unix system - const char* display = boost::nowide::getenv("DISPLAY"); - // const char *wayland_display = boost::nowide::getenv("WAYLAND_DISPLAY"); - //if (! ((display && *display) || (wayland_display && *wayland_display))) { - if (!(display && *display)) { - // DISPLAY not set. - boost::nowide::cerr << "DISPLAY not set, GUI mode not available." << std::endl << std::endl; - this->print_help(false); - // Indicate an error. - return 1; - } - #endif // some linux / unix system - return Slic3r::GUI::GUI_Run(params); -} - -static int start_as_gcode_viewer(const Cli& cli, GUI::GUI_InitParams& gui_params) -{ - if (cli.input_files.size() > 1) { - boost::nowide::cerr << "You can open only one .gcode file at a time in GCodeViewer" << std::endl; - return 1; - } - - if (cli.input_files.size() == 1) { - if (!is_gcode_file(cli.input_files[0]) || !boost::filesystem::exists(cli.input_files[0])) { - boost::nowide::cerr << "Input file isn't a .gcode file or doesn't exist. GCodeViewer can't be start." << std::endl; - return 1; - } - } - - return start_gui_with_params(gui_params); -} -#endif // SLIC3R_GUI - -static bool setup_general() -{ - // Mark the main thread for the debugger and for runtime checks. - set_current_thread_name("slic3r_main"); - // Save the thread ID of the main thread. - save_main_thread_id(); - -#ifdef __WXGTK__ - // On Linux, wxGTK has no support for Wayland, and the app crashes on - // startup if gtk3 is used. This env var has to be set explicitly to - // instruct the window manager to fall back to X server mode. - ::setenv("GDK_BACKEND", "x11", /* replace */ true); - - // https://github.com/prusa3d/PrusaSlicer/issues/12969 - ::setenv("WEBKIT_DISABLE_COMPOSITING_MODE", "1", /* replace */ false); - ::setenv("WEBKIT_DISABLE_DMABUF_RENDERER", "1", /* replace */ false); -#endif - - // Switch boost::filesystem to utf8. - try { - boost::nowide::nowide_filesystem(); - } - catch (const std::runtime_error& ex) { - std::string caption = std::string(SLIC3R_APP_NAME) + " Error"; - std::string text = std::string("An error occured while setting up locale.\n") + ( -#if !defined(_WIN32) && !defined(__APPLE__) - // likely some linux system - "You may need to reconfigure the missing locales, likely by running the \"locale-gen\" and \"dpkg-reconfigure locales\" commands.\n" -#endif - SLIC3R_APP_NAME " will now terminate.\n\n") + ex.what(); -#if defined(_WIN32) && defined(SLIC3R_GUI) - MessageBoxA(NULL, text.c_str(), caption.c_str(), MB_OK | MB_ICONERROR); -#endif - boost::nowide::cerr << text.c_str() << std::endl; - return false; - } - - { - Slic3r::set_logging_level(1); - const char* loglevel = boost::nowide::getenv("SLIC3R_LOGLEVEL"); - if (loglevel != nullptr) { - if (loglevel[0] >= '0' && loglevel[0] <= '9' && loglevel[1] == 0) - set_logging_level(loglevel[0] - '0'); - else - boost::nowide::cerr << "Invalid SLIC3R_LOGLEVEL environment variable: " << loglevel << std::endl; - } - } - - // Detect the operating system flavor after SLIC3R_LOGLEVEL is set. - detect_platform(); - -#ifdef WIN32 - // Notify user that a blacklisted DLL was injected into PrusaSlicer process (for example Nahimic, see GH #5573). - // We hope that if a DLL is being injected into a PrusaSlicer process, it happens at the very start of the application, - // thus we shall detect them now. - if (BlacklistedLibraryCheck::get_instance().perform_check()) { - std::wstring text = L"Following DLLs have been injected into the PrusaSlicer process:\n\n"; - text += BlacklistedLibraryCheck::get_instance().get_blacklisted_string(); - text += L"\n\n" - L"PrusaSlicer is known to not run correctly with these DLLs injected. " - L"We suggest stopping or uninstalling these services if you experience " - L"crashes or unexpected behaviour while using PrusaSlicer.\n" - L"For example, ASUS Sonic Studio injects a Nahimic driver, which makes PrusaSlicer " - L"to crash on a secondary monitor, see PrusaSlicer github issue #5573"; - MessageBoxW(NULL, text.c_str(), L"Warning"/*L"Incopatible library found"*/, MB_OK); - } -#endif - - // See Invoking prusa-slicer from $PATH environment variable crashes #5542 - // boost::filesystem::path path_to_binary = boost::filesystem::system_complete(argv[0]); - boost::filesystem::path path_to_binary = boost::dll::program_location(); - - // Path from the Slic3r binary to its resources. -#ifdef __APPLE__ - // The application is packed in the .dmg archive as 'Slic3r.app/Contents/MacOS/Slic3r' - // The resources are packed to 'Slic3r.app/Contents/Resources' - boost::filesystem::path path_resources = boost::filesystem::canonical(path_to_binary).parent_path() / "../Resources"; -#elif defined _WIN32 - // The application is packed in the .zip archive in the root, - // The resources are packed to 'resources' - // Path from Slic3r binary to resources: - boost::filesystem::path path_resources = path_to_binary.parent_path() / "resources"; -#elif defined SLIC3R_FHS - // The application is packaged according to the Linux Filesystem Hierarchy Standard - // Resources are set to the 'Architecture-independent (shared) data', typically /usr/share or /usr/local/share - boost::filesystem::path path_resources = SLIC3R_FHS_RESOURCES; -#else - // The application is packed in the .tar.bz archive (or in AppImage) as 'bin/slic3r', - // The resources are packed to 'resources' - // Path from Slic3r binary to resources: - boost::filesystem::path path_resources = boost::filesystem::canonical(path_to_binary).parent_path() / "../resources"; -#endif - - set_resources_dir(path_resources.string()); - set_var_dir((path_resources / "icons").string()); - set_local_dir((path_resources / "localization").string()); - set_sys_shapes_dir((path_resources / "shapes").string()); - set_custom_gcodes_dir((path_resources / "custom_gcodes").string()); - - return true; -} - -static bool load_cli(Cli& cli, int argc, char** argv) -{ - if (cli.read(argc, argv)) - return true; - - // Separate error message reported by the CLI parser from the help. - boost::nowide::cerr << std::endl; - print_help(); - return false; -} - -static void setup_from_cli(Cli& cli) -{ - if (cli.misc_config.has("loglevel")) - { - int loglevel = cli.misc_config.opt_int("loglevel"); - if (loglevel != 0) - set_logging_level(loglevel); - } - - if (cli.misc_config.has("threads")) - thread_count = cli.misc_config.opt_int("threads"); - - set_data_dir(cli.misc_config.has("datadir") ? cli.misc_config.opt_string("datadir") : get_default_datadir()); - -#ifdef SLIC3R_GUI - if (cli.misc_config.has("webdev")) { - Utils::ServiceConfig::instance().set_webdev_enabled(cli.misc_config.opt_bool("webdev")); - } -#endif -} - -#ifdef SLIC3R_GUI -// set data for init GUI parameters -// and return state of start_gui -static bool init_gui_params(GUI::GUI_InitParams& gui_params, int argc, char** argv, Cli& cli) -{ - bool start_gui = false; - - gui_params.argc = argc; - gui_params.argv = argv; - gui_params.input_files = cli.input_files; - - if (cli.misc_config.has("opengl-aa")) { - start_gui = true; - gui_params.opengl_aa = true; - } -#if SLIC3R_OPENGL_ES - // are we starting as gcodeviewer ? - if (cli.misc_config.has("gcodeviewer")) { - cli.start_gui = true; - cli.start_as_gcodeviewer = true; - } -#else - - // search for special keys into command line parameters - if (cli.misc_config.has("gcodeviewer")) { - start_gui = true; - gui_params.start_as_gcodeviewer = true; - } - else { -#ifndef _WIN32 - // On Unix systems, the prusa-slicer binary may be symlinked to give the application a different meaning. - gui_params.start_as_gcodeviewer = boost::algorithm::iends_with(boost::filesystem::path(argv[0]).filename().string(), "gcodeviewer"); -#endif // _WIN32 - } - - if (cli.misc_config.has("opengl-version")) { - const Semver opengl_minimum = Semver(3, 2, 0); - const std::string opengl_version_str = cli.misc_config.opt_string("opengl-version"); - boost::optional semver = Semver::parse(opengl_version_str); - if (semver.has_value() && (*semver) >= opengl_minimum) { - std::pair& version = gui_params.opengl_version; - version.first = semver->maj(); - version.second = semver->min(); - if (std::find(Slic3r::GUI::OpenGLVersions::core.begin(), Slic3r::GUI::OpenGLVersions::core.end(), std::make_pair(version.first, version.second)) == Slic3r::GUI::OpenGLVersions::core.end()) { - version = { 0, 0 }; - boost::nowide::cerr << "Required OpenGL version " << opengl_version_str << " not recognized.\n Option 'opengl-version' ignored." << std::endl; - } - } - else - boost::nowide::cerr << "Required OpenGL version " << opengl_version_str << " is invalid. Must be greater than or equal to " << - opengl_minimum.to_string() << "\n Option 'opengl-version' ignored." << std::endl; - start_gui = true; - } - - if (cli.misc_config.has("opengl-compatibility")) { - start_gui = true; - gui_params.opengl_compatibility_profile = true; - // reset version as compatibility profile always take the highest version - // supported by the graphic card - gui_params.opengl_version = std::make_pair(0, 0); - } - - if (cli.misc_config.has("opengl-debug")) { - start_gui = true; - gui_params.opengl_debug = true; - } -#endif // SLIC3R_OPENGL_ES - - if (cli.misc_config.has("delete-after-load")) { - gui_params.delete_after_load = true; - } - - if (!gui_params.start_as_gcodeviewer && !cli.input_config.has("load")) { - // Read input file(s) if any and check if can start GcodeViewer - if (cli.input_files.size() == 1 && is_gcode_file(cli.input_files[0]) && boost::filesystem::exists(cli.input_files[0])) - gui_params.start_as_gcodeviewer = true; - } - - if (has_full_config_from_profiles(cli)) { - gui_params.selected_presets = Slic3r::GUI::CLISelectedProfiles{ cli.input_config.opt_string("print-profile"), - cli.input_config.opt_string("printer-profile") , - cli.input_config.option("material-profile")->values }; - } - - if (!cli.overrides_config.empty()) - gui_params.extra_config = cli.overrides_config; - - if (cli.input_config.has("load")) { - gui_params.load_configs = cli.input_config.option("load")->values; - start_gui = true; - } - - for (const std::string& file : cli.input_files) { - if (boost::starts_with(file, "prusaslicer://")) { - gui_params.start_downloader = true; - gui_params.download_url = file; - break; - } - } - - return start_gui; -} -#else // SLIC3R_GUI - // If there is no GUI, we shall ignore the parameters. Remove them from the list. -#endif // SLIC3R_GUI - -static bool load_print_config(DynamicPrintConfig &print_config, PrinterTechnology& printer_technology, const Cli& cli) -{ - // first of all load configuration from "--load" if any - - if (cli.input_config.has("load")) { - - const std::vector& load_configs = cli.input_config.option("load")->values; - ForwardCompatibilitySubstitutionRule config_substitution_rule = cli.misc_config.option>("config_compatibility")->value; - - // load config files supplied via --load - for (auto const& file : load_configs) { - if (!boost::filesystem::exists(file)) { - if (cli.misc_config.has("ignore_nonexistent_config") && cli.misc_config.opt_bool("ignore_nonexistent_config")) { - continue; - } - else { - boost::nowide::cerr << "No such file: " << file << std::endl; - return false; - } - } - DynamicPrintConfig config; - ConfigSubstitutions config_substitutions; - try { - config_substitutions = config.load(file, config_substitution_rule); - } - catch (std::exception& ex) { - boost::nowide::cerr << "Error while reading config file \"" << file << "\": " << ex.what() << std::endl; - return false; - } - - if (!can_apply_printer_technology(printer_technology, get_printer_technology(config))) - return false; - - print_config_substitutions(config_substitutions, file); - - config.normalize_fdm(); - print_config.apply(config); - } - } - - // than apply other options from full print config if any is provided by prifiles set - - if (has_full_config_from_profiles(cli)) { - DynamicPrintConfig config; - // load config from profiles set - std::string errors = Slic3r::load_full_print_config(cli.input_config.opt_string("print-profile"), - cli.input_config.option("material-profile")->values, - cli.input_config.opt_string("printer-profile"), - config, printer_technology); - if (!errors.empty()) { - boost::nowide::cerr << "Error while loading config from profiles: " << errors << std::endl; - return false; - } - - if (!can_apply_printer_technology(printer_technology, get_printer_technology(config))) - return false; - - config.normalize_fdm(); - - // config is applied with print_config loaded before - config += std::move(print_config); - print_config = std::move(config); - } - - return true; -} - -static bool process_input_files(std::vector& models, DynamicPrintConfig& print_config, PrinterTechnology& printer_technology, Cli& cli) -{ - for (const std::string& file : cli.input_files) { - if (boost::starts_with(file, "prusaslicer://")) { - continue; - } - if (!boost::filesystem::exists(file)) { - boost::nowide::cerr << "No such file: " << file << std::endl; - return false; - } - - Model model; - try { - if (has_full_config_from_profiles(cli)) { - // we have full banch of options from profiles set - // so, just load a geometry - model = FileReader::load_model(file); - } - else { - // load model and configuration from the file - DynamicPrintConfig config; - ConfigSubstitutionContext config_substitutions_ctxt(cli.misc_config.option>("config_compatibility")->value); - - boost::optional prusaslicer_generator_version; - //FIXME should we check the version here? // | Model::LoadAttribute::CheckVersion ? - model = FileReader::load_model_with_config(file, &config, &config_substitutions, prusaslicer_generator_version, FileReader::LoadAttribute::AddDefaultInstances); - - if (!can_apply_printer_technology(printer_technology, get_printer_technology(config))) - return false; - - print_config_substitutions(config_substitutions_ctxt.substitutions, file); - - // config is applied with print_config loaded before - config += std::move(print_config); - print_config = std::move(config); - } - - // If model for slicing is loaded from 3mf file, then its geometry has to be used and arrange couldn't be apply for this model. - if ((boost::algorithm::iends_with(file, ".3mf") || boost::algorithm::iends_with(file, ".zip")) && - (!cli.transform_config.has("dont_arrange") || !cli.transform_config.opt_bool("dont_arrange"))) { - //So, check a state of "dont_arrange" parameter and set it to true, if its value is false. - cli.transform_config.set_key_value("dont_arrange", new ConfigOptionBool(true)); - } - } - catch (std::exception& e) { - boost::nowide::cerr << file << ": " << e.what() << std::endl; - return false; - } - if (model.objects.empty()) { - boost::nowide::cerr << "Error: file is empty: " << file << std::endl; - continue; - } - models.push_back(model); - } - - return true; -} - -static bool is_needed_post_processing(const DynamicPrintConfig& print_config, const Cli& cli) -{ - if (print_config.has("post_process")) { - const std::vector& post_process = print_config.opt("post_process")->values; - if (!post_process.empty()) { - boost::nowide::cout << "\nA post-processing script has been detected in the config data:\n\n"; - for (const std::string& s : post_process) { - boost::nowide::cout << "> " << s << "\n"; - } - boost::nowide::cout << "\nContinue(Y/N) ? "; - char in; - boost::nowide::cin >> in; - if (in != 'Y' && in != 'y') - return true; - } - } - - return false; -} - -static bool finalize_print_config(DynamicPrintConfig& print_config, PrinterTechnology& printer_technology, const Cli& cli) -{ - // Apply command line options to a more specific DynamicPrintConfig which provides normalize() - // (command line options override --load files or congiguration which is loaded prom profiles) - print_config.apply(cli.overrides_config, true); - // Normalizing after importing the 3MFs / AMFs - print_config.normalize_fdm(); - - if (printer_technology == ptUnknown) - printer_technology = cli.actions_config.has("export_sla") ? ptSLA : ptFFF; - print_config.option>("printer_technology", true)->value = printer_technology; - - // Initialize full print configs for both the FFF and SLA technologies. - FullPrintConfig fff_print_config; - SLAFullPrintConfig sla_print_config; - - // Synchronize the default parameters and the ones received on the command line. - if (printer_technology == ptFFF) { - fff_print_config.apply(print_config, true); - print_config.apply(fff_print_config, true); - } - else { - assert(printer_technology == ptSLA); - sla_print_config.output_filename_format.value = "[input_filename_base].sl1"; - - // The default bed shape should reflect the default display parameters - // and not the fff defaults. - double w = sla_print_config.display_width.getFloat(); - double h = sla_print_config.display_height.getFloat(); - sla_print_config.bed_shape.values = { Vec2d(0, 0), Vec2d(w, 0), Vec2d(w, h), Vec2d(0, h) }; - - sla_print_config.apply(print_config, true); - print_config.apply(sla_print_config, true); - } - - // validate print configuration - std::string validity = print_config.validate(); - if (!validity.empty()) { - boost::nowide::cerr << "Error: The composite configation is not valid: " << validity << std::endl; - return false; - } - - return true; -} - -int Slic3r::CLI::run(int argc, char** argv) -{ - if (!setup_general()) - return 1; - - Cli cli; - if (!load_cli(cli, argc, argv)) - return 1; - - setup_from_cli(cli); - - if (process_profiles_sharing(cli)) - return 1; - - bool start_gui = cli.empty() || (has_full_config_from_profiles(cli) && cli.actions_config.empty()); - PrinterTechnology printer_technology = cli.overrides_config.has("printer_technology") ? Preset::printer_technology(cli.overrides_config) : ptUnknown; - DynamicPrintConfig print_config = {}; - std::vector models; - -#ifdef SLIC3R_GUI - GUI::GUI_InitParams gui_params; - start_gui |= init_gui_params(gui_params, argc, argv, cli); - - if (gui_params.start_as_gcodeviewer) - return start_as_gcode_viewer(cli, gui_params); -#endif - - if (!load_print_config(print_config, printer_technology, cli)) - return 1; - - if (!process_input_files(models, print_config, printer_technology, cli)) - return 1; - - if (!start_gui && is_needed_post_processing(print_config, cli)) - return 0; - - if (!finalize_print_config(print_config, printer_technology, cli)) - return 1; - - if (!process_transform(cli, print_config, models)) - return 1; - - if (!process_actions(cli, print_config, models)) - return 1; - - // this need to be check AFTER process transformations, where this option can be added - start_gui |= cli.actions_config.has("export_stl"); - - if (start_gui) { -#ifdef SLIC3R_GUI - return start_gui_with_params(gui_params); -#else - // No GUI support. Just print out a help. - print_help(false); - // If started without a parameter, consider it to be OK, otherwise report an error code (no action etc). - return (argc == 0) ? 0 : 1; -#endif - } - - return 0; -} - - // __has_feature() is used later for Clang, this is for compatibility with other compilers (such as GCC and MSVC) #ifndef __has_feature # define __has_feature(x) 0 @@ -1451,6 +84,6 @@ extern "C" { #else /* _MSC_VER */ int main(int argc, char **argv) { - return CLI().run(argc, argv); + return Slic3r::CLI::run(argc, argv); } #endif /* _MSC_VER */ From e88ec2d62d52c895dd37c0203289ca9ba40b8ceb Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 16 Dec 2024 14:31:11 +0100 Subject: [PATCH 6/9] ConfigDef & DynamicConfig: Removed CLI-related functions --- src/libslic3r/Config.cpp | 226 --------------------------------------- src/libslic3r/Config.hpp | 8 -- 2 files changed, 234 deletions(-) diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp index 102236f8b8..18bac9d82e 100644 --- a/src/libslic3r/Config.cpp +++ b/src/libslic3r/Config.cpp @@ -352,111 +352,6 @@ void ConfigDef::finalize() } } -std::ostream& ConfigDef::print_cli_help(std::ostream& out, bool show_defaults, std::function filter) const -{ - // prepare a function for wrapping text - auto wrap = [](const std::string& text, size_t line_length) -> std::string { - std::istringstream words(text); - std::ostringstream wrapped; - std::string word; - - if (words >> word) { - wrapped << word; - size_t space_left = line_length - word.length(); - while (words >> word) { - if (space_left < word.length() + 1) { - wrapped << '\n' << word; - space_left = line_length - word.length(); - } else { - wrapped << ' ' << word; - space_left -= word.length() + 1; - } - } - } - return wrapped.str(); - }; - - // List of opt_keys that should be hidden from the CLI help. - const std::vector silent_options = { "webdev", "single_instance_on_url" }; - - // get the unique categories - std::set categories; - for (const auto& opt : this->options) { - const ConfigOptionDef& def = opt.second; - if (filter(def)) - categories.insert(def.category); - } - - for (const std::string& category : categories) { - if (category != "") { - out << category << ":" << std::endl; - } else if (categories.size() > 1) { - out << "Misc options:" << std::endl; - } - - for (const auto& opt : this->options) { - const ConfigOptionDef& def = opt.second; - if (def.category != category || def.cli == ConfigOptionDef::nocli || !filter(def)) - continue; - - if (std::find(silent_options.begin(), silent_options.end(), opt.second.opt_key) != silent_options.end()) - continue; - - // get all possible variations: --foo, --foobar, -f... - std::vector cli_args = def.cli_args(opt.first); - if (cli_args.empty()) - continue; - - for (auto& arg : cli_args) { - arg.insert(0, (arg.size() == 1) ? "-" : "--"); - if (def.type == coFloat || def.type == coInt || def.type == coFloatOrPercent - || def.type == coFloats || def.type == coInts) { - arg += " N"; - } else if (def.type == coPoint) { - arg += " X,Y"; - } else if (def.type == coPoint3) { - arg += " X,Y,Z"; - } else if (def.type == coString || def.type == coStrings) { - arg += " ABCD"; - } - } - - // left: command line options - const std::string cli = boost::algorithm::join(cli_args, ", "); - out << " " << std::left << std::setw(20) << cli; - - // right: option description - std::string descr = def.tooltip; - bool show_defaults_this = show_defaults || def.opt_key == "config_compatibility"; - if (show_defaults_this && def.default_value && def.type != coBool - && (def.type != coString || !def.default_value->serialize().empty())) { - descr += " ("; - if (!def.sidetext.empty()) { - descr += def.sidetext + ", "; - } else if (def.enum_def && def.enum_def->has_values()) { - descr += boost::algorithm::join(def.enum_def->values(), ", ") + "; "; - } - descr += "default: " + def.default_value->serialize() + ")"; - } - - // wrap lines of description - descr = wrap(descr, 80); - std::vector lines; - boost::split(lines, descr, boost::is_any_of("\n")); - - // if command line options are too long, print description in new line - for (size_t i = 0; i < lines.size(); ++i) { - if (i == 0 && cli.size() > 19) - out << std::endl; - if (i > 0 || cli.size() > 19) - out << std::string(21, ' '); - out << lines[i] << std::endl; - } - } - } - return out; -} - std::string ConfigBase::SetDeserializeItem::format(std::initializer_list values) { std::string out; @@ -1220,127 +1115,6 @@ const ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key) co return (it == options.end()) ? nullptr : it->second.get(); } -bool DynamicConfig::read_cli(int argc, const char* const argv[], t_config_option_keys* extra, t_config_option_keys* keys) -{ - // cache the CLI option => opt_key mapping - std::map opts; - for (const auto &oit : this->def()->options) - for (const std::string &t : oit.second.cli_args(oit.first)) - opts[t] = oit.first; - - bool parse_options = true; - for (int i = 1; i < argc; ++ i) { - std::string token = argv[i]; - // Store non-option arguments in the provided vector. - if (! parse_options || ! boost::starts_with(token, "-")) { - extra->push_back(token); - continue; - } -#ifdef __APPLE__ - if (boost::starts_with(token, "-psn_")) - // OSX launcher may add a "process serial number", for example "-psn_0_989382" to the command line. - // While it is supposed to be dropped since OSX 10.9, we will rather ignore it. - continue; -#endif /* __APPLE__ */ - // Stop parsing tokens as options when -- is supplied. - if (token == "--") { - parse_options = false; - continue; - } - // Remove leading dashes (one or two). - token.erase(token.begin(), token.begin() + (boost::starts_with(token, "--") ? 2 : 1)); - // Read value when supplied in the --key=value form. - std::string value; - { - size_t equals_pos = token.find("="); - if (equals_pos != std::string::npos) { - value = token.substr(equals_pos+1); - token.erase(equals_pos); - } - } - // Look for the cli -> option mapping. - auto it = opts.find(token); - bool no = false; - if (it == opts.end()) { - // Remove the "no-" prefix used to negate boolean options. - std::string yes_token; - if (boost::starts_with(token, "no-")) { - yes_token = token.substr(3); - it = opts.find(yes_token); - no = true; - } - if (it == opts.end()) { - boost::nowide::cerr << "Unknown option --" << token.c_str() << std::endl; - return false; - } - if (no) - token = yes_token; - } - - const t_config_option_key &opt_key = it->second; - const ConfigOptionDef &optdef = *this->option_def(opt_key); - - // If the option type expects a value and it was not already provided, - // look for it in the next token. - if (value.empty() && optdef.type != coBool && optdef.type != coBools) { - if (i == argc-1) { - boost::nowide::cerr << "No value supplied for --" << token.c_str() << std::endl; - return false; - } - value = argv[++ i]; - } - - if (no) { - assert(optdef.type == coBool || optdef.type == coBools); - if (! value.empty()) { - boost::nowide::cerr << "Boolean options negated by the --no- prefix cannot have a value." << std::endl; - return false; - } - } - - // 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); - } - ConfigOption *opt_base = this->option(opt_key, true); - ConfigOptionVectorBase *opt_vector = opt_base->is_vector() ? static_cast(opt_base) : nullptr; - if (opt_vector) { - if (! existing) - // remove the default values - opt_vector->clear(); - // Vector values will be chained. Repeated use of a parameter will append the parameter or parameters - // to the end of the value. - if (opt_base->type() == coBools && value.empty()) - static_cast(opt_base)->values.push_back(!no); - else - // Deserialize any other vector value (ConfigOptionInts, Floats, Percents, Points) the same way - // they get deserialized from an .ini file. For ConfigOptionStrings, that means that the C-style unescape - // will be applied for values enclosed in quotes, while values non-enclosed in quotes are left to be - // unescaped by the calling shell. - opt_vector->deserialize(value, true); - } else if (opt_base->type() == coBool) { - if (value.empty()) - static_cast(opt_base)->value = !no; - else - opt_base->deserialize(value); - } else if (opt_base->type() == coString) { - // Do not unescape single string values, the unescaping is left to the calling shell. - static_cast(opt_base)->value = value; - } else { - // Just bail out if the configuration value is not understood. - ConfigSubstitutionContext context(ForwardCompatibilitySubstitutionRule::Disable); - // Any scalar value of a type different from Bool and String. - if (! this->set_deserialize_nothrow(opt_key, value, context, false)) { - boost::nowide::cerr << "Invalid value supplied for --" << token.c_str() << std::endl; - return false; - } - } - } - return true; -} - t_config_option_keys DynamicConfig::keys() const { t_config_option_keys keys; diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index 13408b50f6..cc7eabe336 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -2522,11 +2522,6 @@ public: } bool empty() const { return options.empty(); } - // Iterate through all of the CLI options and write them to a stream. - std::ostream& print_cli_help( - std::ostream& out, bool show_defaults, - std::function filter = [](const ConfigOptionDef &){ return true; }) const; - protected: ConfigOptionDef* add(const t_config_option_key &opt_key, ConfigOptionType type); ConfigOptionDef* add_nullable(const t_config_option_key &opt_key, ConfigOptionType type); @@ -2898,9 +2893,6 @@ public: // Returns options being equal in the two configs, ignoring options not present in both configs. t_config_option_keys equal(const DynamicConfig &other) const; - // Command line processing - bool read_cli(int argc, const char* const argv[], t_config_option_keys* extra, t_config_option_keys* keys = nullptr); - std::map>::const_iterator cbegin() const { return options.cbegin(); } std::map>::const_iterator cend() const { return options.cend(); } size_t size() const { return options.size(); } From 1f55b1ad7945686e8943a704906a66c21edfe12f Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 6 Jan 2025 23:21:40 +0100 Subject: [PATCH 7/9] CLI: Improved read of data + implemented CLI_DynamicPrintConfig as DynamicPrintConfig with info about type of cli config and related config_def and overridden handle_legacy(). --- src/CLI/CLI.hpp | 16 ++--- src/CLI/CLI_DynamicPrintConfig.hpp | 47 +++++++++++++++ src/CLI/Setup.cpp | 93 +++++++++++++++--------------- src/CMakeLists.txt | 1 + 4 files changed, 103 insertions(+), 54 deletions(-) create mode 100644 src/CLI/CLI_DynamicPrintConfig.hpp diff --git a/src/CLI/CLI.hpp b/src/CLI/CLI.hpp index 4cd487b363..f5a7f4b775 100644 --- a/src/CLI/CLI.hpp +++ b/src/CLI/CLI.hpp @@ -3,8 +3,8 @@ #include #include -#include "libslic3r/Config.hpp" #include "libslic3r/Model.hpp" +#include "CLI_DynamicPrintConfig.hpp" #ifdef SLIC3R_GUI #include "slic3r/GUI/GUI_Init.hpp" @@ -15,16 +15,18 @@ namespace Slic3r::CLI // struct which is filled from comand line input struct Data { - DynamicPrintConfig input_config; - DynamicPrintConfig overrides_config; - DynamicPrintConfig transform_config; - DynamicPrintConfig misc_config; - DynamicPrintConfig actions_config; + Data(); + + CLI_DynamicPrintConfig input_config; + CLI_DynamicPrintConfig overrides_config; + CLI_DynamicPrintConfig transform_config; + CLI_DynamicPrintConfig misc_config; + CLI_DynamicPrintConfig actions_config; std::vector input_files; bool empty() { - return input_files.empty() + return input_files.empty() && input_config.empty() && overrides_config.empty() && transform_config.empty() diff --git a/src/CLI/CLI_DynamicPrintConfig.hpp b/src/CLI/CLI_DynamicPrintConfig.hpp new file mode 100644 index 0000000000..48aa947e28 --- /dev/null +++ b/src/CLI/CLI_DynamicPrintConfig.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include "libslic3r/Config.hpp" + +namespace Slic3r::CLI +{ +enum class Type +{ + Input, + Overrides, + Transformations, + Misc, + Actions, + Undef +}; + +class CLI_DynamicPrintConfig : public DynamicPrintConfig +{ +public: + CLI_DynamicPrintConfig() {} + CLI_DynamicPrintConfig(Type type, const ConfigDef* config_def) : + m_type (type), + m_config_def (config_def) {} + CLI_DynamicPrintConfig(const CLI_DynamicPrintConfig& other) : + DynamicPrintConfig(other), + m_type (other.type()), + m_config_def (other.def()) {} + + // Overrides ConfigBase::def(). Static configuration definition. Any value stored into this ConfigBase shall have its definition here. + const ConfigDef* def() const override { return m_config_def; } + + // Verify whether the opt_key has not been obsoleted or renamed. + // Both opt_key and value may be modified by handle_legacy(). + // If the opt_key is no more valid in this version of Slic3r, opt_key is cleared by handle_legacy(). + // handle_legacy() is called internally by set_deserialize(). + void handle_legacy(t_config_option_key& opt_key, std::string& value) const override { + if (m_type == Type::Overrides) + DynamicPrintConfig::handle_legacy(opt_key, value); + } + + Type type() const { return m_type; } + +private: + Type m_type { Type::Undef }; + const ConfigDef* m_config_def { nullptr }; +}; +} diff --git a/src/CLI/Setup.cpp b/src/CLI/Setup.cpp index 0c91cbc38a..82e03d466d 100644 --- a/src/CLI/Setup.cpp +++ b/src/CLI/Setup.cpp @@ -23,33 +23,55 @@ namespace Slic3r::CLI { -enum class Type +Data::Data() { - Input, - Overrides, - Transformations, - Misc, - Actions -}; + input_config = CLI_DynamicPrintConfig(Type::Input, &cli_input_config_def); + overrides_config = CLI_DynamicPrintConfig(Type::Overrides, &print_config_def); + transform_config = CLI_DynamicPrintConfig(Type::Transformations, &cli_transform_config_def); + misc_config = CLI_DynamicPrintConfig(Type::Misc, &cli_misc_config_def); + actions_config = CLI_DynamicPrintConfig(Type::Actions, &cli_actions_config_def); +} + +using opts_map = std::map >; + +static opts_map get_opts_map(const Data& data) +{ + opts_map ret; + + for (const CLI_DynamicPrintConfig* config : { &data.input_config , + &data.overrides_config, + &data.transform_config, + &data.misc_config , + &data.actions_config }) + { + for (const auto& oit : config->def()->options) + for (const std::string& t : oit.second.cli_args(oit.first)) + ret[t] = { oit.first , config->type()}; + } + + return ret; +} + +static CLI_DynamicPrintConfig* get_config(Data& data, Type type) +{ + for (CLI_DynamicPrintConfig* config : { &data.input_config , + &data.overrides_config, + &data.transform_config, + &data.misc_config , + &data.actions_config }) + { + if (type == config->type()) + return config; + } + + assert(false); + return nullptr; +} static bool read(Data& data, int argc, const char* const argv[]) { // cache the CLI option => opt_key mapping - std::map > opts; - - std::initializer_list> list = { - { cli_input_config_def, Type::Input}, - { print_config_def, Type::Overrides}, - { cli_transform_config_def, Type::Transformations}, - { cli_misc_config_def, Type::Misc}, - { cli_actions_config_def, Type::Actions} - }; - - for (const auto& [config_def, type] : list) { - for (const auto& oit : config_def.options) - for (const std::string& t : oit.second.cli_args(oit.first)) - opts[t] = { oit.first , type }; - } + opts_map opts = get_opts_map(data); bool parse_options = true; for (int i = 1; i < argc; ++i) { @@ -100,33 +122,10 @@ static bool read(Data& data, int argc, const char* const argv[]) token = yes_token; } - //const t_config_option_key& opt_key = it->second.first; - //const ConfigOptionDef& optdef = *this->option_def(opt_key); const auto& [opt_key, type] = it->second; - const ConfigDef* config_def; - DynamicPrintConfig* config; - if (type == Type::Input) { - config_def = &cli_input_config_def; - config = &data.input_config; - } - else if (type == Type::Transformations) { - config_def = &cli_transform_config_def; - config = &data.transform_config; - } - else if(type == Type::Misc) { - config_def = &cli_misc_config_def; - config = &data.misc_config; - } - else if(type == Type::Actions) { - config_def = &cli_actions_config_def; - config = &data.actions_config; - } - else { - config_def = &print_config_def; - config = &data.overrides_config; - } - const ConfigOptionDef* optdef = config_def->get(opt_key); + CLI_DynamicPrintConfig* config = get_config(data, type); + const ConfigOptionDef* optdef = config->option_def(opt_key); assert(optdef); // If the option type expects a value and it was not already provided, diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 917ecc9d40..10fee49467 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -99,6 +99,7 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/platform/osx/Info.plist.in ${CMAKE_CU set(SLIC3R_CLI_SOURCES PrusaSlicer.hpp CLI/CLI.hpp + CLI/CLI_DynamicPrintConfig.hpp CLI/PrintHelp.cpp CLI/Setup.cpp CLI/LoadPrintData.cpp From c264a216c06a8d6f27bcc5bd082b306f5af27f0a Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 6 Jan 2025 23:08:46 +0100 Subject: [PATCH 8/9] CLI: Fixed a cut function --- src/CLI/ProcessTransform.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/CLI/ProcessTransform.cpp b/src/CLI/ProcessTransform.cpp index 2641e338d0..8029a74f24 100644 --- a/src/CLI/ProcessTransform.cpp +++ b/src/CLI/ProcessTransform.cpp @@ -159,17 +159,22 @@ bool process_transform(Data& cli, const DynamicPrintConfig& print_config, std::v if (transform.has("cut")) { std::vector new_models; + const Vec3d plane_center = transform.opt_float("cut") * Vec3d::UnitZ(); for (auto& model : models) { + Model new_model; model.translate(0, 0, -model.bounding_box_exact().min.z()); // align to z = 0 size_t num_objects = model.objects.size(); for (size_t i = 0; i < num_objects; ++i) { - Cut cut(model.objects.front(), 0, Geometry::translation_transform(transform.opt_float("cut") * Vec3d::UnitZ()), + ModelObject* mo = model.objects.front(); + const Vec3d cut_center_offset = plane_center - mo->instances[0]->get_offset(); + Cut cut(mo, 0, Geometry::translation_transform(cut_center_offset), ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::PlaceOnCutUpper); auto cut_objects = cut.perform_with_plane(); for (ModelObject* obj : cut_objects) - model.add_object(*obj); + new_model.add_object(*obj); model.delete_object(size_t(0)); } + new_models.push_back(new_model); } // TODO: copy less stuff around using pointers From f51611c83e069a1342207e4c8f3f2fadf0f9a56d Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 15 Jan 2025 13:47:40 +0100 Subject: [PATCH 9/9] CLI: Fixed load of the model, when input file isn't project file --- src/CLI/LoadPrintData.cpp | 4 ++-- src/libslic3r/FileReader.cpp | 11 ++++++++--- src/libslic3r/FileReader.hpp | 2 ++ 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/CLI/LoadPrintData.cpp b/src/CLI/LoadPrintData.cpp index 2418527c1c..fa64c43dc8 100644 --- a/src/CLI/LoadPrintData.cpp +++ b/src/CLI/LoadPrintData.cpp @@ -129,7 +129,7 @@ static bool process_input_files(std::vector& models, DynamicPrintConfig& Model model; try { - if (has_full_config_from_profiles(cli)) { + if (has_full_config_from_profiles(cli) || !FileReader::is_project_file(file)) { // we have full banch of options from profiles set // so, just load a geometry model = FileReader::load_model(file); @@ -154,7 +154,7 @@ static bool process_input_files(std::vector& models, DynamicPrintConfig& } // If model for slicing is loaded from 3mf file, then its geometry has to be used and arrange couldn't be apply for this model. - if ((boost::algorithm::iends_with(file, ".3mf") || boost::algorithm::iends_with(file, ".zip")) && + if (FileReader::is_project_file(file) && (!cli.transform_config.has("dont_arrange") || !cli.transform_config.opt_bool("dont_arrange"))) { //So, check a state of "dont_arrange" parameter and set it to true, if its value is false. cli.transform_config.set_key_value("dont_arrange", new ConfigOptionBool(true)); diff --git a/src/libslic3r/FileReader.cpp b/src/libslic3r/FileReader.cpp index cf05144e56..71c87dd1df 100644 --- a/src/libslic3r/FileReader.cpp +++ b/src/libslic3r/FileReader.cpp @@ -30,6 +30,12 @@ namespace Slic3r::FileReader { + +bool is_project_file(const std::string& input_file) +{ + return boost::algorithm::iends_with(input_file, ".3mf") || boost::algorithm::iends_with(input_file, ".zip"); +} + // Loading model from a file, it may be a simple geometry file as STL or OBJ, however it may be a project file as well. static Model read_model_from_file(const std::string& input_file, LoadAttributes options) { @@ -83,15 +89,14 @@ static Model read_all_from_file(const std::string& input_file, boost::optional &prusaslicer_generator_version, LoadAttributes options) { - const bool is_project_file = boost::algorithm::iends_with(input_file, ".3mf") || boost::algorithm::iends_with(input_file, ".zip"); - assert(is_project_file); + assert(is_project_file(input_file)); assert(config != nullptr); assert(config_substitutions != nullptr); Model model; bool result = false; - if (is_project_file) + if (is_project_file(input_file)) result = load_3mf(input_file.c_str(), *config, *config_substitutions, &model, options & LoadAttribute::CheckVersion, prusaslicer_generator_version); else throw Slic3r::RuntimeError(L("Unknown file format. Input file must have .3mf extension.")); diff --git a/src/libslic3r/FileReader.hpp b/src/libslic3r/FileReader.hpp index ab3cb63132..661328b580 100644 --- a/src/libslic3r/FileReader.hpp +++ b/src/libslic3r/FileReader.hpp @@ -35,6 +35,8 @@ namespace FileReader bool looks_like_multipart_object { false }; }; + bool is_project_file(const std::string& input_file); + // Load model from input file and return the its mesh. // Throw RuntimeError if some problem was detected during model loading TriangleMesh load_mesh(const std::string& input_file);