mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-04-19 12:39:37 +08:00
CLI: Code cleaning and reorganization
+Remove configuration of fhs.hpp on UNIX. Use target_compile_definitions() instead.
This commit is contained in:
parent
253abbfa78
commit
4be7eeabb4
@ -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
71
src/CLI/CLI.hpp
Normal 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
150
src/CLI/GuiParams.cpp
Normal 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
259
src/CLI/LoadPrintData.cpp
Normal 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
180
src/CLI/PrintHelp.cpp
Normal 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
367
src/CLI/ProcessActions.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
205
src/CLI/ProcessTransform.cpp
Normal file
205
src/CLI/ProcessTransform.cpp
Normal 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
54
src/CLI/Run.cpp
Normal 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
342
src/CLI/Setup.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
@ -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 "")
|
||||
|
1373
src/PrusaSlicer.cpp
1373
src/PrusaSlicer.cpp
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user