CLI: Code cleaning and reorganization

+Remove configuration of fhs.hpp on UNIX. Use target_compile_definitions() instead.
This commit is contained in:
YuSanka 2025-01-14 10:01:47 +01:00 committed by Lukas Matena
parent 253abbfa78
commit 4be7eeabb4
11 changed files with 1659 additions and 1377 deletions

View File

@ -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)

71
src/CLI/CLI.hpp Normal file
View File

@ -0,0 +1,71 @@
#pragma once
#include <string>
#include <vector>
#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<std::string> 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<Model>& 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<Model>& 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<Model>& 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
}

150
src/CLI/GuiParams.cpp Normal file
View File

@ -0,0 +1,150 @@
#include <string>
#include <boost/filesystem.hpp>
#include <boost/nowide/iostream.hpp>
#include <boost/nowide/cstdlib.hpp>
#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 = Semver::parse(opengl_version_str);
if (semver.has_value() && (*semver) >= opengl_minimum) {
std::pair<int, int>& 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<ConfigOptionStrings>("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<ConfigOptionStrings>("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

259
src/CLI/LoadPrintData.cpp Normal file
View File

@ -0,0 +1,259 @@
#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <boost/filesystem.hpp>
#include <boost/nowide/args.hpp>
#include <boost/nowide/iostream.hpp>
#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<PrinterTechnology> *opt = config.option<ConfigOptionEnum<PrinterTechnology>>("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<std::string>& load_configs = cli.input_config.option<ConfigOptionStrings>("load")->values;
ForwardCompatibilitySubstitutionRule config_substitution_rule = cli.misc_config.option<ConfigOptionEnum<ForwardCompatibilitySubstitutionRule>>("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<ConfigOptionStrings>("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<Model>& 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<ConfigOptionEnum<ForwardCompatibilitySubstitutionRule>>("config_compatibility")->value);
boost::optional<Semver> 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<ConfigOptionEnum<PrinterTechnology>>("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<Model>& 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<std::string>& post_process = print_config.opt<ConfigOptionStrings>("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;
}
}

180
src/CLI/PrintHelp.cpp Normal file
View File

@ -0,0 +1,180 @@
#include <string>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/join.hpp>
#include <boost/nowide/iostream.hpp>
#include "CLI.hpp"
namespace Slic3r::CLI {
static void print_help(const ConfigDef& config_def, bool show_defaults, std::function<bool(const ConfigOptionDef&)> 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<std::string> silent_options = { "webdev", "single_instance_on_url" };
// get the unique categories
std::set<std::string> 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<std::string> 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<std::string> 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;
}
}
}

367
src/CLI/ProcessActions.cpp Normal file
View File

@ -0,0 +1,367 @@
#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <math.h>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/filesystem.hpp>
#include <boost/nowide/args.hpp>
#include <boost/nowide/cstdlib.hpp>
#include <boost/nowide/iostream.hpp>
#include <boost/nowide/filesystem.hpp>
#include <boost/nowide/cstdio.hpp>
#include <boost/nowide/iostream.hpp>
#include <boost/nowide/fstream.hpp>
#include <boost/dll/runtime_symbol_info.hpp>
#include "libslic3r/libslic3r.h"
#if !SLIC3R_OPENGL_ES
#include <boost/algorithm/string/split.hpp>
#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 <arrange-wrapper/ModelArrange.hpp>
#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<ConfigOptionStrings>("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<Model>& 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<ThumbnailsList(const ThumbnailsParams&)> 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<unsigned char> 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<unsigned char> 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<Model>& 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<ConfigOptionPoint>("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<PrintBase*>(&fff_print) : static_cast<PrintBase*>(&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;
}
}

View File

@ -0,0 +1,205 @@
#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <math.h>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/filesystem.hpp>
#include <boost/nowide/args.hpp>
#include <boost/nowide/cstdlib.hpp>
#include <boost/nowide/iostream.hpp>
#include <boost/nowide/filesystem.hpp>
#include <boost/nowide/cstdio.hpp>
#include <boost/nowide/iostream.hpp>
#include <boost/nowide/fstream.hpp>
#include <boost/dll/runtime_symbol_info.hpp>
#include "libslic3r/libslic3r.h"
#if !SLIC3R_OPENGL_ES
#include <boost/algorithm/string/split.hpp>
#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 <arrange-wrapper/ModelArrange.hpp>
#include "libslic3r/MultipleBeds.hpp"
#include "CLI.hpp"
namespace Slic3r::CLI {
bool process_transform(Data& cli, const DynamicPrintConfig& print_config, std::vector<Model>& 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<int>& ints = transform.option<ConfigOptionInts>("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<ConfigOptionPoint>("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<ConfigOptionPoint>("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<ConfigOptionPoint3>("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<Model> 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;
}
}

54
src/CLI/Run.cpp Normal file
View File

@ -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<Model> 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;
}
}

342
src/CLI/Setup.cpp Normal file
View File

@ -0,0 +1,342 @@
#include <boost/nowide/cstdlib.hpp>
#include <boost/nowide/filesystem.hpp>
#include <boost/nowide/cstdio.hpp>
#include <boost/nowide/iostream.hpp>
#include <boost/nowide/fstream.hpp>
#include <boost/dll/runtime_symbol_info.hpp>
#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<std::string, std::pair<std::string, Type> > opts;
std::initializer_list<std::pair<const ConfigDef&, Type>> 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<ConfigOptionVectorBase*>(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<ConfigOptionBools*>(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<ConfigOptionBool*>(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<ConfigOptionString*>(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;
}
}

View File

@ -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 "")

File diff suppressed because it is too large Load Diff