From 4be7eeabb460a6c014a072bd9c60457c6341ce5b Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 14 Jan 2025 10:01:47 +0100 Subject: [PATCH] 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 */