2018-09-21 17:37:35 +00:00
|
|
|
#ifdef WIN32
|
|
|
|
// Why?
|
|
|
|
#define _WIN32_WINNT 0x0502
|
|
|
|
// The standard Windows includes.
|
|
|
|
#define WIN32_LEAN_AND_MEAN
|
|
|
|
#define NOMINMAX
|
|
|
|
#include <Windows.h>
|
|
|
|
#include <wchar.h>
|
2019-04-12 10:16:44 +00:00
|
|
|
#ifdef SLIC3R_GUI
|
2019-08-16 14:17:37 +00:00
|
|
|
extern "C"
|
|
|
|
{
|
2019-04-12 10:16:44 +00:00
|
|
|
// Let the NVIDIA and AMD know we want to use their graphics card
|
|
|
|
// on a dual graphics card system.
|
|
|
|
__declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001;
|
|
|
|
__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
|
2019-06-13 15:42:55 +00:00
|
|
|
}
|
2019-04-12 10:16:44 +00:00
|
|
|
#endif /* SLIC3R_GUI */
|
2018-09-21 17:37:35 +00:00
|
|
|
#endif /* WIN32 */
|
|
|
|
|
2017-08-18 11:32:35 +00:00
|
|
|
#include <cstdio>
|
|
|
|
#include <string>
|
|
|
|
#include <cstring>
|
|
|
|
#include <iostream>
|
|
|
|
#include <math.h>
|
2020-09-08 11:33:43 +00:00
|
|
|
#include <boost/algorithm/string/predicate.hpp>
|
2017-08-18 11:32:35 +00:00
|
|
|
#include <boost/filesystem.hpp>
|
|
|
|
#include <boost/nowide/args.hpp>
|
2018-09-24 09:53:05 +00:00
|
|
|
#include <boost/nowide/cenv.hpp>
|
2017-08-18 11:32:35 +00:00
|
|
|
#include <boost/nowide/iostream.hpp>
|
2019-08-08 10:59:55 +00:00
|
|
|
#include <boost/nowide/integration/filesystem.hpp>
|
2021-01-13 08:22:13 +00:00
|
|
|
#include <boost/dll/runtime_symbol_info.hpp>
|
2017-08-18 11:32:35 +00:00
|
|
|
|
2019-01-04 11:06:25 +00:00
|
|
|
#include "unix/fhs.hpp" // Generated by CMake from ../platform/unix/fhs.hpp.in
|
|
|
|
|
2018-11-26 13:41:58 +00:00
|
|
|
#include "libslic3r/libslic3r.h"
|
|
|
|
#include "libslic3r/Config.hpp"
|
|
|
|
#include "libslic3r/Geometry.hpp"
|
2020-12-07 14:21:28 +00:00
|
|
|
#include "libslic3r/GCode/PostProcessor.hpp"
|
2018-11-26 13:41:58 +00:00
|
|
|
#include "libslic3r/Model.hpp"
|
2020-04-23 16:19:03 +00:00
|
|
|
#include "libslic3r/ModelArrange.hpp"
|
2021-03-15 15:19:22 +00:00
|
|
|
#include "libslic3r/Platform.hpp"
|
2018-11-26 13:41:58 +00:00
|
|
|
#include "libslic3r/Print.hpp"
|
2018-12-11 12:16:09 +00:00
|
|
|
#include "libslic3r/SLAPrint.hpp"
|
2018-11-26 13:41:58 +00:00
|
|
|
#include "libslic3r/TriangleMesh.hpp"
|
2019-03-13 14:44:50 +00:00
|
|
|
#include "libslic3r/Format/AMF.hpp"
|
2018-11-26 13:41:58 +00:00
|
|
|
#include "libslic3r/Format/3mf.hpp"
|
2019-03-13 14:44:50 +00:00
|
|
|
#include "libslic3r/Format/STL.hpp"
|
|
|
|
#include "libslic3r/Format/OBJ.hpp"
|
2020-04-13 10:31:37 +00:00
|
|
|
#include "libslic3r/Format/SL1.hpp"
|
2018-11-26 13:41:58 +00:00
|
|
|
#include "libslic3r/Utils.hpp"
|
2020-10-22 11:53:08 +00:00
|
|
|
#include "libslic3r/Thread.hpp"
|
2021-07-30 13:52:43 +00:00
|
|
|
#include "libslic3r/BlacklistedLibraryCheck.hpp"
|
2018-11-26 13:41:58 +00:00
|
|
|
|
2019-05-13 13:22:03 +00:00
|
|
|
#include "PrusaSlicer.hpp"
|
2019-04-12 11:29:06 +00:00
|
|
|
|
|
|
|
#ifdef SLIC3R_GUI
|
2020-10-22 14:28:49 +00:00
|
|
|
#include "slic3r/GUI/GUI_Init.hpp"
|
2019-04-12 11:29:06 +00:00
|
|
|
#endif /* SLIC3R_GUI */
|
2018-09-19 09:02:24 +00:00
|
|
|
|
2017-08-18 11:32:35 +00:00
|
|
|
using namespace Slic3r;
|
|
|
|
|
2021-01-19 14:17:47 +00:00
|
|
|
static PrinterTechnology get_printer_technology(const DynamicConfig &config)
|
|
|
|
{
|
|
|
|
const ConfigOptionEnum<PrinterTechnology> *opt = config.option<ConfigOptionEnum<PrinterTechnology>>("printer_technology");
|
|
|
|
return (opt == nullptr) ? ptUnknown : opt->value;
|
|
|
|
}
|
|
|
|
|
2019-08-16 14:17:37 +00:00
|
|
|
int CLI::run(int argc, char **argv)
|
2019-03-13 14:44:50 +00:00
|
|
|
{
|
2020-10-22 11:53:08 +00:00
|
|
|
// Mark the main thread for the debugger and for runtime checks.
|
|
|
|
set_current_thread_name("slic3r_main");
|
|
|
|
|
2020-04-27 11:02:16 +00:00
|
|
|
#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);
|
|
|
|
#endif
|
|
|
|
|
2019-08-08 10:59:55 +00:00
|
|
|
// Switch boost::filesystem to utf8.
|
2019-08-08 13:17:17 +00:00
|
|
|
try {
|
|
|
|
boost::nowide::nowide_filesystem();
|
|
|
|
} catch (const std::runtime_error& ex) {
|
|
|
|
std::string caption = std::string(SLIC3R_APP_NAME) + " Error";
|
2019-08-20 14:19:30 +00:00
|
|
|
std::string text = std::string("An error occured while setting up locale.\n") + (
|
|
|
|
#if !defined(_WIN32) && !defined(__APPLE__)
|
|
|
|
// likely some linux system
|
2019-08-21 06:50:38 +00:00
|
|
|
"You may need to reconfigure the missing locales, likely by running the \"locale-gen\" and \"dpkg-reconfigure locales\" commands.\n"
|
2019-08-20 14:19:30 +00:00
|
|
|
#endif
|
|
|
|
SLIC3R_APP_NAME " will now terminate.\n\n") + ex.what();
|
2019-08-20 15:46:19 +00:00
|
|
|
#if defined(_WIN32) && defined(SLIC3R_GUI)
|
2019-08-08 13:17:17 +00:00
|
|
|
if (m_actions.empty())
|
2019-08-20 14:38:03 +00:00
|
|
|
// Empty actions means Slicer is executed in the GUI mode. Show a GUI message.
|
2019-08-08 13:17:17 +00:00
|
|
|
MessageBoxA(NULL, text.c_str(), caption.c_str(), MB_OK | MB_ICONERROR);
|
|
|
|
#endif
|
|
|
|
boost::nowide::cerr << text.c_str() << std::endl;
|
|
|
|
return 1;
|
|
|
|
}
|
2019-08-08 10:59:55 +00:00
|
|
|
|
2019-03-13 14:44:50 +00:00
|
|
|
if (! this->setup(argc, argv))
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
m_extra_config.apply(m_config, true);
|
2020-09-24 17:03:09 +00:00
|
|
|
m_extra_config.normalize_fdm();
|
2020-04-23 16:19:03 +00:00
|
|
|
|
2021-01-19 14:17:47 +00:00
|
|
|
PrinterTechnology printer_technology = get_printer_technology(m_config);
|
2019-03-13 14:44:50 +00:00
|
|
|
|
|
|
|
bool start_gui = m_actions.empty() &&
|
|
|
|
// cutting transformations are setting an "export" action.
|
2019-08-16 14:17:37 +00:00
|
|
|
std::find(m_transforms.begin(), m_transforms.end(), "cut") == m_transforms.end() &&
|
|
|
|
std::find(m_transforms.begin(), m_transforms.end(), "cut_x") == m_transforms.end() &&
|
|
|
|
std::find(m_transforms.begin(), m_transforms.end(), "cut_y") == m_transforms.end();
|
2020-09-08 11:33:43 +00:00
|
|
|
bool start_as_gcodeviewer =
|
|
|
|
#ifdef _WIN32
|
|
|
|
false;
|
|
|
|
#else
|
|
|
|
// On Unix systems, the prusa-slicer binary may be symlinked to give the application a different meaning.
|
|
|
|
boost::algorithm::iends_with(boost::filesystem::path(argv[0]).filename().string(), "gcodeviewer");
|
|
|
|
#endif // _WIN32
|
|
|
|
|
Support for forward compatibility of configurations, user and system
config bundles, project files (3MFs, AMFs). When loading these files,
the caller may decide whether to substitute some of the configuration
values the current PrusaSlicer version does not understand with
some reasonable default value, and whether to report it. If substitution
is disabled, an exception is being thrown as before this commit.
If substitution is enabled, list of substitutions is returned by the
API to be presented to the user. This allows us to introduce for example
new firmware flavor key in PrusaSlicer 2.4 while letting PrusaSlicer
2.3.2 to fall back to some default and to report it to the user.
When slicing from command line, substutions are performed by default
and reported into the console, however substitutions may be either
disabled or made silent with the new "config-compatibility" command
line option.
Substitute enums and bools only. Allow booleans to be parsed as
true: "1", "enabled", "on" case insensitive
false: "0", "disabled", "off" case insensitive
This will allow us in the future for example to switch the draft_shield
boolean to an enum with the following values: "disabled" / "enabled" / "limited".
Added "enum_bitmask.hpp" - support for type safe sets of options.
See for example PresetBundle::load_configbundle(...
LoadConfigBundleAttributes flags) for an example of intended usage.
WIP: GUI for reporting the list of config substitutions needs to be
implemented by @YuSanka.
2021-06-27 14:04:23 +00:00
|
|
|
const std::vector<std::string> &load_configs = m_config.option<ConfigOptionStrings>("load", true)->values;
|
|
|
|
const ForwardCompatibilitySubstitutionRule config_substitution_rule = m_config.option<ConfigOptionEnum<ForwardCompatibilitySubstitutionRule>>("config_compatibility", true)->value;
|
2019-08-16 14:17:37 +00:00
|
|
|
|
2019-03-13 14:44:50 +00:00
|
|
|
// load config files supplied via --load
|
2019-08-16 14:17:37 +00:00
|
|
|
for (auto const &file : load_configs) {
|
2019-03-13 14:44:50 +00:00
|
|
|
if (! boost::filesystem::exists(file)) {
|
2019-03-14 17:47:26 +00:00
|
|
|
if (m_config.opt_bool("ignore_nonexistent_config")) {
|
2019-03-13 14:44:50 +00:00
|
|
|
continue;
|
|
|
|
} else {
|
|
|
|
boost::nowide::cerr << "No such file: " << file << std::endl;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
Support for forward compatibility of configurations, user and system
config bundles, project files (3MFs, AMFs). When loading these files,
the caller may decide whether to substitute some of the configuration
values the current PrusaSlicer version does not understand with
some reasonable default value, and whether to report it. If substitution
is disabled, an exception is being thrown as before this commit.
If substitution is enabled, list of substitutions is returned by the
API to be presented to the user. This allows us to introduce for example
new firmware flavor key in PrusaSlicer 2.4 while letting PrusaSlicer
2.3.2 to fall back to some default and to report it to the user.
When slicing from command line, substutions are performed by default
and reported into the console, however substitutions may be either
disabled or made silent with the new "config-compatibility" command
line option.
Substitute enums and bools only. Allow booleans to be parsed as
true: "1", "enabled", "on" case insensitive
false: "0", "disabled", "off" case insensitive
This will allow us in the future for example to switch the draft_shield
boolean to an enum with the following values: "disabled" / "enabled" / "limited".
Added "enum_bitmask.hpp" - support for type safe sets of options.
See for example PresetBundle::load_configbundle(...
LoadConfigBundleAttributes flags) for an example of intended usage.
WIP: GUI for reporting the list of config substitutions needs to be
implemented by @YuSanka.
2021-06-27 14:04:23 +00:00
|
|
|
DynamicPrintConfig config;
|
|
|
|
ConfigSubstitutions config_substitutions;
|
2019-03-13 14:44:50 +00:00
|
|
|
try {
|
Support for forward compatibility of configurations, user and system
config bundles, project files (3MFs, AMFs). When loading these files,
the caller may decide whether to substitute some of the configuration
values the current PrusaSlicer version does not understand with
some reasonable default value, and whether to report it. If substitution
is disabled, an exception is being thrown as before this commit.
If substitution is enabled, list of substitutions is returned by the
API to be presented to the user. This allows us to introduce for example
new firmware flavor key in PrusaSlicer 2.4 while letting PrusaSlicer
2.3.2 to fall back to some default and to report it to the user.
When slicing from command line, substutions are performed by default
and reported into the console, however substitutions may be either
disabled or made silent with the new "config-compatibility" command
line option.
Substitute enums and bools only. Allow booleans to be parsed as
true: "1", "enabled", "on" case insensitive
false: "0", "disabled", "off" case insensitive
This will allow us in the future for example to switch the draft_shield
boolean to an enum with the following values: "disabled" / "enabled" / "limited".
Added "enum_bitmask.hpp" - support for type safe sets of options.
See for example PresetBundle::load_configbundle(...
LoadConfigBundleAttributes flags) for an example of intended usage.
WIP: GUI for reporting the list of config substitutions needs to be
implemented by @YuSanka.
2021-06-27 14:04:23 +00:00
|
|
|
config_substitutions = config.load(file, config_substitution_rule);
|
2019-03-13 14:44:50 +00:00
|
|
|
} catch (std::exception &ex) {
|
Support for forward compatibility of configurations, user and system
config bundles, project files (3MFs, AMFs). When loading these files,
the caller may decide whether to substitute some of the configuration
values the current PrusaSlicer version does not understand with
some reasonable default value, and whether to report it. If substitution
is disabled, an exception is being thrown as before this commit.
If substitution is enabled, list of substitutions is returned by the
API to be presented to the user. This allows us to introduce for example
new firmware flavor key in PrusaSlicer 2.4 while letting PrusaSlicer
2.3.2 to fall back to some default and to report it to the user.
When slicing from command line, substutions are performed by default
and reported into the console, however substitutions may be either
disabled or made silent with the new "config-compatibility" command
line option.
Substitute enums and bools only. Allow booleans to be parsed as
true: "1", "enabled", "on" case insensitive
false: "0", "disabled", "off" case insensitive
This will allow us in the future for example to switch the draft_shield
boolean to an enum with the following values: "disabled" / "enabled" / "limited".
Added "enum_bitmask.hpp" - support for type safe sets of options.
See for example PresetBundle::load_configbundle(...
LoadConfigBundleAttributes flags) for an example of intended usage.
WIP: GUI for reporting the list of config substitutions needs to be
implemented by @YuSanka.
2021-06-27 14:04:23 +00:00
|
|
|
boost::nowide::cerr << "Error while reading config file \"" << file << "\": " << ex.what() << std::endl;
|
2019-03-13 14:44:50 +00:00
|
|
|
return 1;
|
|
|
|
}
|
Support for forward compatibility of configurations, user and system
config bundles, project files (3MFs, AMFs). When loading these files,
the caller may decide whether to substitute some of the configuration
values the current PrusaSlicer version does not understand with
some reasonable default value, and whether to report it. If substitution
is disabled, an exception is being thrown as before this commit.
If substitution is enabled, list of substitutions is returned by the
API to be presented to the user. This allows us to introduce for example
new firmware flavor key in PrusaSlicer 2.4 while letting PrusaSlicer
2.3.2 to fall back to some default and to report it to the user.
When slicing from command line, substutions are performed by default
and reported into the console, however substitutions may be either
disabled or made silent with the new "config-compatibility" command
line option.
Substitute enums and bools only. Allow booleans to be parsed as
true: "1", "enabled", "on" case insensitive
false: "0", "disabled", "off" case insensitive
This will allow us in the future for example to switch the draft_shield
boolean to an enum with the following values: "disabled" / "enabled" / "limited".
Added "enum_bitmask.hpp" - support for type safe sets of options.
See for example PresetBundle::load_configbundle(...
LoadConfigBundleAttributes flags) for an example of intended usage.
WIP: GUI for reporting the list of config substitutions needs to be
implemented by @YuSanka.
2021-06-27 14:04:23 +00:00
|
|
|
if (! config_substitutions.empty()) {
|
|
|
|
boost::nowide::cout << "The following configuration values were substituted when loading \" << file << \":\n";
|
|
|
|
for (const ConfigSubstitution &subst : config_substitutions)
|
|
|
|
boost::nowide::cout << "\tkey = \"" << subst.opt_def->opt_key << "\"\t loaded = \"" << subst.old_value << "\tsubstituted = \"" << subst.new_value->serialize() << "\"\n";
|
|
|
|
}
|
2020-09-24 17:03:09 +00:00
|
|
|
config.normalize_fdm();
|
2021-01-19 14:17:47 +00:00
|
|
|
PrinterTechnology other_printer_technology = get_printer_technology(config);
|
2019-03-13 14:44:50 +00:00
|
|
|
if (printer_technology == ptUnknown) {
|
|
|
|
printer_technology = other_printer_technology;
|
2019-07-29 14:19:32 +00:00
|
|
|
} else if (printer_technology != other_printer_technology && other_printer_technology != ptUnknown) {
|
2019-03-13 14:44:50 +00:00
|
|
|
boost::nowide::cerr << "Mixing configurations for FFF and SLA technologies" << std::endl;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
m_print_config.apply(config);
|
|
|
|
}
|
2019-08-16 14:17:37 +00:00
|
|
|
|
2020-09-09 13:03:51 +00:00
|
|
|
// are we starting as gcodeviewer ?
|
|
|
|
for (auto it = m_actions.begin(); it != m_actions.end(); ++it) {
|
|
|
|
if (*it == "gcodeviewer") {
|
|
|
|
start_gui = true;
|
|
|
|
start_as_gcodeviewer = true;
|
|
|
|
m_actions.erase(it);
|
|
|
|
break;
|
2019-03-13 14:44:50 +00:00
|
|
|
}
|
2020-09-09 13:03:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Read input file(s) if any.
|
2021-01-05 11:26:05 +00:00
|
|
|
for (const std::string& file : m_input_files)
|
|
|
|
if (is_gcode_file(file) && boost::filesystem::exists(file)) {
|
|
|
|
start_as_gcodeviewer = true;
|
|
|
|
break;
|
2020-10-02 09:40:21 +00:00
|
|
|
}
|
2020-09-09 13:03:51 +00:00
|
|
|
if (!start_as_gcodeviewer) {
|
|
|
|
for (const std::string& file : m_input_files) {
|
|
|
|
if (!boost::filesystem::exists(file)) {
|
|
|
|
boost::nowide::cerr << "No such file: " << file << std::endl;
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
Model model;
|
|
|
|
try {
|
|
|
|
// When loading an AMF or 3MF, config is imported as well, including the printer technology.
|
|
|
|
DynamicPrintConfig config;
|
Support for forward compatibility of configurations, user and system
config bundles, project files (3MFs, AMFs). When loading these files,
the caller may decide whether to substitute some of the configuration
values the current PrusaSlicer version does not understand with
some reasonable default value, and whether to report it. If substitution
is disabled, an exception is being thrown as before this commit.
If substitution is enabled, list of substitutions is returned by the
API to be presented to the user. This allows us to introduce for example
new firmware flavor key in PrusaSlicer 2.4 while letting PrusaSlicer
2.3.2 to fall back to some default and to report it to the user.
When slicing from command line, substutions are performed by default
and reported into the console, however substitutions may be either
disabled or made silent with the new "config-compatibility" command
line option.
Substitute enums and bools only. Allow booleans to be parsed as
true: "1", "enabled", "on" case insensitive
false: "0", "disabled", "off" case insensitive
This will allow us in the future for example to switch the draft_shield
boolean to an enum with the following values: "disabled" / "enabled" / "limited".
Added "enum_bitmask.hpp" - support for type safe sets of options.
See for example PresetBundle::load_configbundle(...
LoadConfigBundleAttributes flags) for an example of intended usage.
WIP: GUI for reporting the list of config substitutions needs to be
implemented by @YuSanka.
2021-06-27 14:04:23 +00:00
|
|
|
ConfigSubstitutionContext config_substitutions(config_substitution_rule);
|
|
|
|
//FIXME should we check the version here? // | Model::LoadAttribute::CheckVersion ?
|
|
|
|
model = Model::read_from_file(file, &config, &config_substitutions, Model::LoadAttribute::AddDefaultInstances);
|
2021-01-19 14:17:47 +00:00
|
|
|
PrinterTechnology other_printer_technology = get_printer_technology(config);
|
2020-09-09 13:03:51 +00:00
|
|
|
if (printer_technology == ptUnknown) {
|
|
|
|
printer_technology = other_printer_technology;
|
|
|
|
}
|
|
|
|
else if (printer_technology != other_printer_technology && other_printer_technology != ptUnknown) {
|
|
|
|
boost::nowide::cerr << "Mixing configurations for FFF and SLA technologies" << std::endl;
|
|
|
|
return 1;
|
|
|
|
}
|
Support for forward compatibility of configurations, user and system
config bundles, project files (3MFs, AMFs). When loading these files,
the caller may decide whether to substitute some of the configuration
values the current PrusaSlicer version does not understand with
some reasonable default value, and whether to report it. If substitution
is disabled, an exception is being thrown as before this commit.
If substitution is enabled, list of substitutions is returned by the
API to be presented to the user. This allows us to introduce for example
new firmware flavor key in PrusaSlicer 2.4 while letting PrusaSlicer
2.3.2 to fall back to some default and to report it to the user.
When slicing from command line, substutions are performed by default
and reported into the console, however substitutions may be either
disabled or made silent with the new "config-compatibility" command
line option.
Substitute enums and bools only. Allow booleans to be parsed as
true: "1", "enabled", "on" case insensitive
false: "0", "disabled", "off" case insensitive
This will allow us in the future for example to switch the draft_shield
boolean to an enum with the following values: "disabled" / "enabled" / "limited".
Added "enum_bitmask.hpp" - support for type safe sets of options.
See for example PresetBundle::load_configbundle(...
LoadConfigBundleAttributes flags) for an example of intended usage.
WIP: GUI for reporting the list of config substitutions needs to be
implemented by @YuSanka.
2021-06-27 14:04:23 +00:00
|
|
|
if (! config_substitutions.substitutions.empty()) {
|
|
|
|
boost::nowide::cout << "The following configuration values were substituted when loading \" << file << \":\n";
|
|
|
|
for (const ConfigSubstitution& subst : config_substitutions.substitutions)
|
|
|
|
boost::nowide::cout << "\tkey = \"" << subst.opt_def->opt_key << "\"\t loaded = \"" << subst.old_value << "\tsubstituted = \"" << subst.new_value->serialize() << "\"\n";
|
|
|
|
}
|
2020-09-09 13:03:51 +00:00
|
|
|
// config is applied to m_print_config before the current m_config values.
|
|
|
|
config += std::move(m_print_config);
|
|
|
|
m_print_config = std::move(config);
|
|
|
|
}
|
|
|
|
catch (std::exception& e) {
|
|
|
|
boost::nowide::cerr << file << ": " << e.what() << std::endl;
|
2019-03-13 14:44:50 +00:00
|
|
|
return 1;
|
|
|
|
}
|
2020-09-09 13:03:51 +00:00
|
|
|
if (model.objects.empty()) {
|
|
|
|
boost::nowide::cerr << "Error: file is empty: " << file << std::endl;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
m_models.push_back(model);
|
2019-03-13 14:44:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Apply command line options to a more specific DynamicPrintConfig which provides normalize()
|
|
|
|
// (command line options override --load files)
|
|
|
|
m_print_config.apply(m_extra_config, true);
|
|
|
|
// Normalizing after importing the 3MFs / AMFs
|
2020-09-24 17:03:09 +00:00
|
|
|
m_print_config.normalize_fdm();
|
2019-03-13 14:44:50 +00:00
|
|
|
|
2021-01-15 17:29:19 +00:00
|
|
|
if (printer_technology == ptUnknown)
|
|
|
|
printer_technology = std::find(m_actions.begin(), m_actions.end(), "export_sla") == m_actions.end() ? ptFFF : ptSLA;
|
2021-01-19 14:35:27 +00:00
|
|
|
m_print_config.option<ConfigOptionEnum<PrinterTechnology>>("printer_technology", true)->value = printer_technology;
|
2021-01-15 17:29:19 +00:00
|
|
|
|
2019-03-13 14:44:50 +00:00
|
|
|
// Initialize full print configs for both the FFF and SLA technologies.
|
|
|
|
FullPrintConfig fff_print_config;
|
2020-02-12 17:20:12 +00:00
|
|
|
SLAFullPrintConfig sla_print_config;
|
|
|
|
|
|
|
|
// Synchronize the default parameters and the ones received on the command line.
|
|
|
|
if (printer_technology == ptFFF) {
|
|
|
|
fff_print_config.apply(m_print_config, true);
|
|
|
|
m_print_config.apply(fff_print_config, true);
|
2021-01-15 17:29:19 +00:00
|
|
|
} else {
|
|
|
|
assert(printer_technology == ptSLA);
|
2020-02-12 17:20:12 +00:00
|
|
|
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(m_print_config, true);
|
|
|
|
m_print_config.apply(sla_print_config, true);
|
|
|
|
}
|
|
|
|
|
2021-01-19 14:17:47 +00:00
|
|
|
{
|
|
|
|
std::string validity = m_print_config.validate();
|
|
|
|
if (! validity.empty()) {
|
|
|
|
boost::nowide::cerr << "Error: The composite configation is not valid: " << validity << std::endl;
|
|
|
|
return 1;
|
|
|
|
}
|
2020-04-23 16:19:03 +00:00
|
|
|
}
|
|
|
|
|
2019-03-13 14:44:50 +00:00
|
|
|
// Loop through transform options.
|
2019-11-05 10:16:55 +00:00
|
|
|
bool user_center_specified = false;
|
2020-04-23 16:19:03 +00:00
|
|
|
Points bed = get_bed_shape(m_print_config);
|
|
|
|
ArrangeParams arrange_cfg;
|
|
|
|
arrange_cfg.min_obj_distance = scaled(min_object_distance(m_print_config));
|
|
|
|
|
2019-03-13 14:44:50 +00:00
|
|
|
for (auto const &opt_key : m_transforms) {
|
|
|
|
if (opt_key == "merge") {
|
|
|
|
Model m;
|
|
|
|
for (auto &model : m_models)
|
2019-08-16 14:17:37 +00:00
|
|
|
for (ModelObject *o : model.objects)
|
|
|
|
m.add_object(*o);
|
2019-03-13 14:44:50 +00:00
|
|
|
// Rearrange instances unless --dont-arrange is supplied
|
|
|
|
if (! m_config.opt_bool("dont_arrange")) {
|
|
|
|
m.add_default_instances();
|
2020-04-23 16:19:03 +00:00
|
|
|
if (this->has_print_action())
|
|
|
|
arrange_objects(m, bed, arrange_cfg);
|
|
|
|
else
|
|
|
|
arrange_objects(m, InfiniteBed{}, arrange_cfg);
|
2019-03-13 14:44:50 +00:00
|
|
|
}
|
2019-08-16 14:17:37 +00:00
|
|
|
m_models.clear();
|
|
|
|
m_models.emplace_back(std::move(m));
|
2019-03-13 14:44:50 +00:00
|
|
|
} else if (opt_key == "duplicate") {
|
|
|
|
for (auto &model : m_models) {
|
|
|
|
const bool all_objects_have_instances = std::none_of(
|
|
|
|
model.objects.begin(), model.objects.end(),
|
|
|
|
[](ModelObject* o){ return o->instances.empty(); }
|
|
|
|
);
|
2020-04-23 16:19:03 +00:00
|
|
|
|
|
|
|
int dups = m_config.opt_int("duplicate");
|
|
|
|
if (!all_objects_have_instances) model.add_default_instances();
|
|
|
|
|
|
|
|
try {
|
|
|
|
if (dups > 1) {
|
|
|
|
// if all input objects have defined position(s) apply duplication to the whole model
|
|
|
|
duplicate(model, size_t(dups), bed, arrange_cfg);
|
|
|
|
} else {
|
|
|
|
arrange_objects(model, bed, arrange_cfg);
|
|
|
|
}
|
|
|
|
} catch (std::exception &ex) {
|
|
|
|
boost::nowide::cerr << "error: " << ex.what() << std::endl;
|
|
|
|
return 1;
|
2019-03-13 14:44:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (opt_key == "duplicate_grid") {
|
|
|
|
std::vector<int> &ints = m_config.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 = fff_print_config.duplicate_distance.value;
|
|
|
|
for (auto &model : m_models)
|
|
|
|
model.duplicate_objects_grid(x, y, (distance > 0) ? distance : 6); // TODO: this is not the right place for setting a default
|
|
|
|
} else if (opt_key == "center") {
|
2019-11-05 10:16:55 +00:00
|
|
|
user_center_specified = true;
|
2019-03-13 14:44:50 +00:00
|
|
|
for (auto &model : m_models) {
|
|
|
|
model.add_default_instances();
|
|
|
|
// this affects instances:
|
|
|
|
model.center_instances_around_point(m_config.option<ConfigOptionPoint>("center")->value);
|
|
|
|
// this affects volumes:
|
2019-08-16 14:17:37 +00:00
|
|
|
//FIXME Vojtech: Who knows why the complete model should be aligned with Z as a single rigid body?
|
2019-03-13 14:44:50 +00:00
|
|
|
//model.align_to_ground();
|
|
|
|
BoundingBoxf3 bbox;
|
|
|
|
for (ModelObject *model_object : model.objects)
|
2019-08-16 14:17:37 +00:00
|
|
|
// We are interested into the Z span only, therefore it is sufficient to measure the bounding box of the 1st instance only.
|
2019-03-13 14:44:50 +00:00
|
|
|
bbox.merge(model_object->instance_bounding_box(0, false));
|
|
|
|
for (ModelObject *model_object : model.objects)
|
|
|
|
for (ModelInstance *model_instance : model_object->instances)
|
|
|
|
model_instance->set_offset(Z, model_instance->get_offset(Z) - bbox.min.z());
|
|
|
|
}
|
|
|
|
} else if (opt_key == "align_xy") {
|
|
|
|
const Vec2d &p = m_config.option<ConfigOptionPoint>("align_xy")->value;
|
|
|
|
for (auto &model : m_models) {
|
|
|
|
BoundingBoxf3 bb = model.bounding_box();
|
|
|
|
// this affects volumes:
|
2019-08-16 14:17:37 +00:00
|
|
|
model.translate(-(bb.min.x() - p.x()), -(bb.min.y() - p.y()), -bb.min.z());
|
2019-03-13 14:44:50 +00:00
|
|
|
}
|
|
|
|
} else if (opt_key == "dont_arrange") {
|
|
|
|
// do nothing - this option alters other transform options
|
|
|
|
} else if (opt_key == "rotate") {
|
|
|
|
for (auto &model : m_models)
|
|
|
|
for (auto &o : model.objects)
|
|
|
|
// this affects volumes:
|
|
|
|
o->rotate(Geometry::deg2rad(m_config.opt_float(opt_key)), Z);
|
|
|
|
} else if (opt_key == "rotate_x") {
|
|
|
|
for (auto &model : m_models)
|
|
|
|
for (auto &o : model.objects)
|
|
|
|
// this affects volumes:
|
|
|
|
o->rotate(Geometry::deg2rad(m_config.opt_float(opt_key)), X);
|
|
|
|
} else if (opt_key == "rotate_y") {
|
|
|
|
for (auto &model : m_models)
|
|
|
|
for (auto &o : model.objects)
|
|
|
|
// this affects volumes:
|
|
|
|
o->rotate(Geometry::deg2rad(m_config.opt_float(opt_key)), Y);
|
|
|
|
} else if (opt_key == "scale") {
|
|
|
|
for (auto &model : m_models)
|
|
|
|
for (auto &o : model.objects)
|
|
|
|
// this affects volumes:
|
|
|
|
o->scale(m_config.get_abs_value(opt_key, 1));
|
|
|
|
} else if (opt_key == "scale_to_fit") {
|
|
|
|
const Vec3d &opt = m_config.opt<ConfigOptionPoint3>(opt_key)->value;
|
|
|
|
if (opt.x() <= 0 || opt.y() <= 0 || opt.z() <= 0) {
|
|
|
|
boost::nowide::cerr << "--scale-to-fit requires a positive volume" << std::endl;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
for (auto &model : m_models)
|
|
|
|
for (auto &o : model.objects)
|
|
|
|
// this affects volumes:
|
|
|
|
o->scale_to_fit(opt);
|
|
|
|
} else if (opt_key == "cut" || opt_key == "cut_x" || opt_key == "cut_y") {
|
|
|
|
std::vector<Model> new_models;
|
|
|
|
for (auto &model : m_models) {
|
2019-06-11 15:08:47 +00:00
|
|
|
model.translate(0, 0, -model.bounding_box().min.z()); // align to z = 0
|
2019-08-16 14:17:37 +00:00
|
|
|
size_t num_objects = model.objects.size();
|
|
|
|
for (size_t i = 0; i < num_objects; ++ i) {
|
2019-03-13 14:44:50 +00:00
|
|
|
|
|
|
|
#if 0
|
|
|
|
if (opt_key == "cut_x") {
|
|
|
|
o->cut(X, m_config.opt_float("cut_x"), &out);
|
|
|
|
} else if (opt_key == "cut_y") {
|
|
|
|
o->cut(Y, m_config.opt_float("cut_y"), &out);
|
|
|
|
} else if (opt_key == "cut") {
|
|
|
|
o->cut(Z, m_config.opt_float("cut"), &out);
|
|
|
|
}
|
2018-10-26 09:57:52 +00:00
|
|
|
#else
|
2021-06-27 15:36:25 +00:00
|
|
|
model.objects.front()->cut(0, m_config.opt_float("cut"), ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::FlipLower);
|
2019-03-13 14:44:50 +00:00
|
|
|
#endif
|
2019-08-16 14:17:37 +00:00
|
|
|
model.delete_object(size_t(0));
|
2019-03-13 14:44:50 +00:00
|
|
|
}
|
|
|
|
}
|
2019-08-16 14:17:37 +00:00
|
|
|
|
2019-03-13 14:44:50 +00:00
|
|
|
// TODO: copy less stuff around using pointers
|
|
|
|
m_models = new_models;
|
2019-08-16 14:17:37 +00:00
|
|
|
|
2019-03-13 14:44:50 +00:00
|
|
|
if (m_actions.empty())
|
|
|
|
m_actions.push_back("export_stl");
|
|
|
|
}
|
|
|
|
#if 0
|
|
|
|
else if (opt_key == "cut_grid") {
|
|
|
|
std::vector<Model> new_models;
|
|
|
|
for (auto &model : m_models) {
|
|
|
|
TriangleMesh mesh = model.mesh();
|
|
|
|
mesh.repair();
|
2019-08-16 14:17:37 +00:00
|
|
|
|
2019-03-13 14:44:50 +00:00
|
|
|
TriangleMeshPtrs meshes = mesh.cut_by_grid(m_config.option<ConfigOptionPoint>("cut_grid")->value);
|
|
|
|
size_t i = 0;
|
|
|
|
for (TriangleMesh* m : meshes) {
|
|
|
|
Model out;
|
|
|
|
auto o = out.add_object();
|
|
|
|
o->add_volume(*m);
|
|
|
|
o->input_file += "_" + std::to_string(i++);
|
|
|
|
delete m;
|
|
|
|
}
|
|
|
|
}
|
2019-08-16 14:17:37 +00:00
|
|
|
|
2019-03-13 14:44:50 +00:00
|
|
|
// TODO: copy less stuff around using pointers
|
|
|
|
m_models = new_models;
|
2019-08-16 14:17:37 +00:00
|
|
|
|
2019-03-13 14:44:50 +00:00
|
|
|
if (m_actions.empty())
|
|
|
|
m_actions.push_back("export_stl");
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
else if (opt_key == "split") {
|
|
|
|
for (Model &model : m_models) {
|
|
|
|
size_t num_objects = model.objects.size();
|
|
|
|
for (size_t i = 0; i < num_objects; ++ i) {
|
2021-02-07 23:41:21 +00:00
|
|
|
ModelObjectPtrs new_objects;
|
|
|
|
model.objects.front()->split(&new_objects);
|
2019-03-13 14:44:50 +00:00
|
|
|
model.delete_object(size_t(0));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (opt_key == "repair") {
|
2019-08-16 14:17:37 +00:00
|
|
|
// Models are repaired by default.
|
2019-06-11 15:08:47 +00:00
|
|
|
//for (auto &model : m_models)
|
|
|
|
// model.repair();
|
2019-03-13 14:44:50 +00:00
|
|
|
} else {
|
|
|
|
boost::nowide::cerr << "error: option not implemented yet: " << opt_key << std::endl;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
2019-08-16 14:17:37 +00:00
|
|
|
|
2019-03-13 14:44:50 +00:00
|
|
|
// loop through action options
|
|
|
|
for (auto const &opt_key : m_actions) {
|
|
|
|
if (opt_key == "help") {
|
|
|
|
this->print_help();
|
2019-03-13 18:17:26 +00:00
|
|
|
} else if (opt_key == "help_fff") {
|
|
|
|
this->print_help(true, ptFFF);
|
|
|
|
} else if (opt_key == "help_sla") {
|
|
|
|
this->print_help(true, ptSLA);
|
2019-03-13 14:44:50 +00:00
|
|
|
} else if (opt_key == "save") {
|
|
|
|
//FIXME check for mixing the FFF / SLA parameters.
|
|
|
|
// or better save fff_print_config vs. sla_print_config
|
|
|
|
m_print_config.save(m_config.opt_string("save"));
|
|
|
|
} else if (opt_key == "info") {
|
|
|
|
// --info works on unrepaired model
|
|
|
|
for (Model &model : m_models) {
|
|
|
|
model.add_default_instances();
|
|
|
|
model.print_info();
|
|
|
|
}
|
|
|
|
} else if (opt_key == "export_stl") {
|
|
|
|
for (auto &model : m_models)
|
|
|
|
model.add_default_instances();
|
|
|
|
if (! this->export_models(IO::STL))
|
|
|
|
return 1;
|
|
|
|
} else if (opt_key == "export_obj") {
|
|
|
|
for (auto &model : m_models)
|
|
|
|
model.add_default_instances();
|
|
|
|
if (! this->export_models(IO::OBJ))
|
|
|
|
return 1;
|
|
|
|
} else if (opt_key == "export_amf") {
|
|
|
|
if (! this->export_models(IO::AMF))
|
|
|
|
return 1;
|
|
|
|
} else if (opt_key == "export_3mf") {
|
|
|
|
if (! this->export_models(IO::TMF))
|
|
|
|
return 1;
|
|
|
|
} else if (opt_key == "export_gcode" || opt_key == "export_sla" || opt_key == "slice") {
|
|
|
|
if (opt_key == "export_gcode" && printer_technology == ptSLA) {
|
|
|
|
boost::nowide::cerr << "error: cannot export G-code for an FFF configuration" << std::endl;
|
|
|
|
return 1;
|
|
|
|
} else if (opt_key == "export_sla" && printer_technology == ptFFF) {
|
|
|
|
boost::nowide::cerr << "error: cannot export SLA slices for a SLA configuration" << std::endl;
|
|
|
|
return 1;
|
|
|
|
}
|
2019-08-16 14:17:37 +00:00
|
|
|
// Make a copy of the model if the current action is not the last action, as the model may be
|
|
|
|
// modified by the centering and such.
|
|
|
|
Model model_copy;
|
|
|
|
bool make_copy = &opt_key != &m_actions.back();
|
2019-03-13 14:44:50 +00:00
|
|
|
for (Model &model_in : m_models) {
|
2019-08-16 14:17:37 +00:00
|
|
|
if (make_copy)
|
|
|
|
model_copy = model_in;
|
|
|
|
Model &model = make_copy ? model_copy : model_in;
|
2019-03-13 14:44:50 +00:00
|
|
|
// If all objects have defined instances, their relative positions will be
|
|
|
|
// honored when printing (they will be only centered, unless --dont-arrange
|
|
|
|
// is supplied); if any object has no instances, it will get a default one
|
|
|
|
// and all instances will be rearranged (unless --dont-arrange is supplied).
|
|
|
|
std::string outfile = m_config.opt_string("output");
|
|
|
|
Print fff_print;
|
|
|
|
SLAPrint sla_print;
|
2020-04-13 10:31:37 +00:00
|
|
|
SL1Archive sla_archive(sla_print.printer_config());
|
|
|
|
sla_print.set_printer(&sla_archive);
|
2019-04-01 11:59:39 +00:00
|
|
|
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());
|
|
|
|
});
|
|
|
|
|
2019-03-13 14:44:50 +00:00
|
|
|
PrintBase *print = (printer_technology == ptFFF) ? static_cast<PrintBase*>(&fff_print) : static_cast<PrintBase*>(&sla_print);
|
|
|
|
if (! m_config.opt_bool("dont_arrange")) {
|
2020-04-23 16:19:03 +00:00
|
|
|
if (user_center_specified) {
|
|
|
|
Vec2d c = m_config.option<ConfigOptionPoint>("center")->value;
|
|
|
|
arrange_objects(model, InfiniteBed{scaled(c)}, arrange_cfg);
|
|
|
|
} else
|
|
|
|
arrange_objects(model, bed, arrange_cfg);
|
2019-03-13 14:44:50 +00:00
|
|
|
}
|
|
|
|
if (printer_technology == ptFFF) {
|
|
|
|
for (auto* mo : model.objects)
|
|
|
|
fff_print.auto_assign_extruders(mo);
|
|
|
|
}
|
|
|
|
print->apply(model, m_print_config);
|
|
|
|
std::string err = print->validate();
|
2019-03-13 18:49:14 +00:00
|
|
|
if (! err.empty()) {
|
|
|
|
boost::nowide::cerr << err << std::endl;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
if (print->empty())
|
|
|
|
boost::nowide::cout << "Nothing to print for " << outfile << " . Either the print is empty or no object is fully inside the print volume." << std::endl;
|
2019-08-16 14:17:37 +00:00
|
|
|
else
|
2019-03-13 14:44:50 +00:00
|
|
|
try {
|
|
|
|
std::string outfile_final;
|
2019-08-16 14:17:37 +00:00
|
|
|
print->process();
|
2019-03-13 14:44:50 +00:00
|
|
|
if (printer_technology == ptFFF) {
|
|
|
|
// The outfile is processed by a PlaceholderParser.
|
2020-03-02 14:13:23 +00:00
|
|
|
outfile = fff_print.export_gcode(outfile, nullptr, nullptr);
|
2019-03-13 14:44:50 +00:00
|
|
|
outfile_final = fff_print.print_statistics().finalize_output_path(outfile);
|
|
|
|
} else {
|
2019-08-16 14:17:37 +00:00
|
|
|
outfile = sla_print.output_filepath(outfile);
|
2019-04-04 10:31:09 +00:00
|
|
|
// 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);
|
2020-04-13 10:31:37 +00:00
|
|
|
sla_archive.export_print(outfile_final, sla_print);
|
2019-03-13 14:44:50 +00:00
|
|
|
}
|
2020-10-15 05:54:01 +00:00
|
|
|
if (outfile != outfile_final) {
|
|
|
|
if (Slic3r::rename_file(outfile, outfile_final)) {
|
|
|
|
boost::nowide::cerr << "Renaming file " << outfile << " to " << outfile_final << " failed" << std::endl;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
outfile = outfile_final;
|
2019-03-13 14:44:50 +00:00
|
|
|
}
|
2020-12-07 14:21:28 +00:00
|
|
|
// Run the post-processing scripts if defined.
|
|
|
|
run_post_process_scripts(outfile, fff_print.full_print_config());
|
2019-03-13 18:49:14 +00:00
|
|
|
boost::nowide::cout << "Slicing result exported to " << outfile << std::endl;
|
2019-03-13 14:44:50 +00:00
|
|
|
} catch (const std::exception &ex) {
|
2019-08-16 14:17:37 +00:00
|
|
|
boost::nowide::cerr << ex.what() << std::endl;
|
|
|
|
return 1;
|
2019-03-13 14:44:50 +00:00
|
|
|
}
|
|
|
|
/*
|
|
|
|
print.center = ! m_config.has("center")
|
|
|
|
&& ! m_config.has("align_xy")
|
|
|
|
&& ! m_config.opt_bool("dont_arrange");
|
|
|
|
print.set_model(model);
|
2019-08-16 14:17:37 +00:00
|
|
|
|
2019-03-13 14:44:50 +00:00
|
|
|
// start chronometer
|
|
|
|
typedef std::chrono::high_resolution_clock clock_;
|
|
|
|
typedef std::chrono::duration<double, std::ratio<1> > second_;
|
|
|
|
std::chrono::time_point<clock_> t0{ clock_::now() };
|
2019-08-16 14:17:37 +00:00
|
|
|
|
2019-03-13 14:44:50 +00:00
|
|
|
const std::string outfile = this->output_filepath(model, IO::Gcode);
|
|
|
|
try {
|
|
|
|
print.export_gcode(outfile);
|
|
|
|
} catch (std::runtime_error &e) {
|
|
|
|
boost::nowide::cerr << e.what() << std::endl;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
boost::nowide::cout << "G-code exported to " << outfile << std::endl;
|
2019-08-16 14:17:37 +00:00
|
|
|
|
2019-03-13 14:44:50 +00:00
|
|
|
// output some statistics
|
|
|
|
double duration { std::chrono::duration_cast<second_>(clock_::now() - t0).count() };
|
|
|
|
boost::nowide::cout << std::fixed << std::setprecision(0)
|
|
|
|
<< "Done. Process took " << (duration/60) << " minutes and "
|
|
|
|
<< std::setprecision(3)
|
|
|
|
<< std::fmod(duration, 60.0) << " seconds." << std::endl
|
|
|
|
<< std::setprecision(2)
|
|
|
|
<< "Filament required: " << print.total_used_filament() << "mm"
|
|
|
|
<< " (" << print.total_extruded_volume()/1000 << "cm3)" << std::endl;
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
boost::nowide::cerr << "error: option not supported yet: " << opt_key << std::endl;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
2019-08-16 14:17:37 +00:00
|
|
|
|
2021-01-27 09:39:39 +00:00
|
|
|
|
2019-08-16 14:17:37 +00:00
|
|
|
if (start_gui) {
|
2019-04-12 10:16:44 +00:00
|
|
|
#ifdef SLIC3R_GUI
|
2020-10-22 14:28:49 +00:00
|
|
|
Slic3r::GUI::GUI_InitParams params;
|
|
|
|
params.argc = argc;
|
|
|
|
params.argv = argv;
|
|
|
|
params.load_configs = load_configs;
|
|
|
|
params.extra_config = std::move(m_extra_config);
|
|
|
|
params.input_files = std::move(m_input_files);
|
|
|
|
params.start_as_gcodeviewer = start_as_gcodeviewer;
|
|
|
|
return Slic3r::GUI::GUI_Run(params);
|
|
|
|
#else // SLIC3R_GUI
|
2019-08-16 14:17:37 +00:00
|
|
|
// No GUI support. Just print out a help.
|
|
|
|
this->print_help(false);
|
|
|
|
// If started without a parameter, consider it to be OK, otherwise report an error code (no action etc).
|
|
|
|
return (argc == 0) ? 0 : 1;
|
2020-10-22 14:28:49 +00:00
|
|
|
#endif // SLIC3R_GUI
|
2019-03-13 14:44:50 +00:00
|
|
|
}
|
2019-08-16 14:17:37 +00:00
|
|
|
|
2019-03-13 14:44:50 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CLI::setup(int argc, char **argv)
|
2017-08-18 11:32:35 +00:00
|
|
|
{
|
2018-09-24 09:53:05 +00:00
|
|
|
{
|
2019-08-08 10:59:55 +00:00
|
|
|
Slic3r::set_logging_level(1);
|
2018-09-24 09:53:05 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-15 16:00:49 +00:00
|
|
|
// Detect the operating system flavor after SLIC3R_LOGLEVEL is set.
|
|
|
|
detect_platform();
|
|
|
|
|
2021-01-27 09:39:39 +00:00
|
|
|
#ifdef WIN32
|
2021-07-30 13:52:43 +00:00
|
|
|
// 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";
|
2021-01-27 09:39:39 +00:00
|
|
|
MessageBoxW(NULL, text.c_str(), L"Warning"/*L"Incopatible library found"*/, MB_OK);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2021-01-13 08:22:13 +00:00
|
|
|
// 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();
|
2018-10-11 13:19:53 +00:00
|
|
|
|
2018-09-21 14:38:42 +00:00
|
|
|
// Path from the Slic3r binary to its resources.
|
2018-09-21 15:16:44 +00:00
|
|
|
#ifdef __APPLE__
|
2018-10-11 13:19:53 +00:00
|
|
|
// The application is packed in the .dmg archive as 'Slic3r.app/Contents/MacOS/Slic3r'
|
|
|
|
// The resources are packed to 'Slic3r.app/Contents/Resources'
|
2020-10-07 11:01:48 +00:00
|
|
|
boost::filesystem::path path_resources = boost::filesystem::canonical(path_to_binary).parent_path() / "../Resources";
|
2018-10-11 13:19:53 +00:00
|
|
|
#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";
|
2019-01-04 11:06:25 +00:00
|
|
|
#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;
|
2018-09-21 09:40:32 +00:00
|
|
|
#else
|
2018-10-11 13:19:53 +00:00
|
|
|
// 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:
|
2020-10-07 11:01:48 +00:00
|
|
|
boost::filesystem::path path_resources = boost::filesystem::canonical(path_to_binary).parent_path() / "../resources";
|
2018-09-21 09:40:32 +00:00
|
|
|
#endif
|
2018-10-11 13:19:53 +00:00
|
|
|
|
2018-09-21 09:40:32 +00:00
|
|
|
set_resources_dir(path_resources.string());
|
|
|
|
set_var_dir((path_resources / "icons").string());
|
|
|
|
set_local_dir((path_resources / "localization").string());
|
2021-07-14 15:41:37 +00:00
|
|
|
set_sys_shapes_dir((path_resources / "shapes").string());
|
2018-09-21 09:40:32 +00:00
|
|
|
|
2019-03-13 14:44:50 +00:00
|
|
|
// Parse all command line options into a DynamicConfig.
|
|
|
|
// If any option is unsupported, print usage and abort immediately.
|
|
|
|
t_config_option_keys opt_order;
|
|
|
|
if (! m_config.read_cli(argc, argv, &m_input_files, &opt_order)) {
|
2019-08-16 14:17:37 +00:00
|
|
|
// Separate error message reported by the CLI parser from the help.
|
|
|
|
boost::nowide::cerr << std::endl;
|
2019-03-13 14:44:50 +00:00
|
|
|
this->print_help();
|
2019-08-16 14:17:37 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// Parse actions and transform options.
|
|
|
|
for (auto const &opt_key : opt_order) {
|
|
|
|
if (cli_actions_config_def.has(opt_key))
|
|
|
|
m_actions.emplace_back(opt_key);
|
|
|
|
else if (cli_transform_config_def.has(opt_key))
|
|
|
|
m_transforms.emplace_back(opt_key);
|
2019-03-13 14:44:50 +00:00
|
|
|
}
|
2019-01-09 10:57:59 +00:00
|
|
|
|
2019-03-13 14:44:50 +00:00
|
|
|
{
|
|
|
|
const ConfigOptionInt *opt_loglevel = m_config.opt<ConfigOptionInt>("loglevel");
|
|
|
|
if (opt_loglevel != 0)
|
|
|
|
set_logging_level(opt_loglevel->value);
|
|
|
|
}
|
2020-04-23 16:19:03 +00:00
|
|
|
|
2021-01-19 14:17:47 +00:00
|
|
|
//FIXME Validating at this stage most likely does not make sense, as the config is not fully initialized yet.
|
2020-04-23 16:19:03 +00:00
|
|
|
std::string validity = m_config.validate();
|
2018-09-21 09:40:32 +00:00
|
|
|
|
2019-03-13 14:44:50 +00:00
|
|
|
// Initialize with defaults.
|
|
|
|
for (const t_optiondef_map *options : { &cli_actions_config_def.options, &cli_transform_config_def.options, &cli_misc_config_def.options })
|
|
|
|
for (const std::pair<t_config_option_key, ConfigOptionDef> &optdef : *options)
|
2020-02-04 13:43:58 +00:00
|
|
|
m_config.option(optdef.first, true);
|
2018-09-20 14:48:13 +00:00
|
|
|
|
2019-08-16 14:17:37 +00:00
|
|
|
set_data_dir(m_config.opt_string("datadir"));
|
2020-04-23 16:19:03 +00:00
|
|
|
|
2021-01-19 14:17:47 +00:00
|
|
|
//FIXME Validating at this stage most likely does not make sense, as the config is not fully initialized yet.
|
2020-04-23 16:19:03 +00:00
|
|
|
if (!validity.empty()) {
|
|
|
|
boost::nowide::cerr << "error: " << validity << std::endl;
|
|
|
|
return false;
|
|
|
|
}
|
2019-01-09 09:43:17 +00:00
|
|
|
|
2019-08-16 14:17:37 +00:00
|
|
|
return true;
|
2019-03-13 14:44:50 +00:00
|
|
|
}
|
2019-01-09 09:43:17 +00:00
|
|
|
|
2019-08-16 14:17:37 +00:00
|
|
|
void CLI::print_help(bool include_print_options, PrinterTechnology printer_technology) const
|
2019-03-13 14:44:50 +00:00
|
|
|
{
|
|
|
|
boost::nowide::cout
|
2019-08-16 14:17:37 +00:00
|
|
|
<< SLIC3R_BUILD_ID << " " << "based on Slic3r"
|
2019-04-12 10:16:44 +00:00
|
|
|
#ifdef SLIC3R_GUI
|
|
|
|
<< " (with GUI support)"
|
|
|
|
#else /* SLIC3R_GUI */
|
|
|
|
<< " (without GUI support)"
|
|
|
|
#endif /* SLIC3R_GUI */
|
|
|
|
<< std::endl
|
2019-05-14 17:46:01 +00:00
|
|
|
<< "https://github.com/prusa3d/PrusaSlicer" << std::endl << std::endl
|
2019-06-08 20:52:03 +00:00
|
|
|
<< "Usage: prusa-slicer [ ACTIONS ] [ TRANSFORM ] [ OPTIONS ] [ file.stl ... ]" << std::endl
|
2019-03-13 14:44:50 +00:00
|
|
|
<< std::endl
|
|
|
|
<< "Actions:" << std::endl;
|
|
|
|
cli_actions_config_def.print_cli_help(boost::nowide::cout, false);
|
2019-08-16 14:17:37 +00:00
|
|
|
|
2019-03-13 14:44:50 +00:00
|
|
|
boost::nowide::cout
|
|
|
|
<< std::endl
|
|
|
|
<< "Transform options:" << std::endl;
|
|
|
|
cli_transform_config_def.print_cli_help(boost::nowide::cout, false);
|
2019-08-16 14:17:37 +00:00
|
|
|
|
2019-03-13 14:44:50 +00:00
|
|
|
boost::nowide::cout
|
|
|
|
<< std::endl
|
|
|
|
<< "Other options:" << std::endl;
|
|
|
|
cli_misc_config_def.print_cli_help(boost::nowide::cout, false);
|
2020-03-24 10:59:55 +00:00
|
|
|
|
|
|
|
boost::nowide::cout
|
|
|
|
<< std::endl
|
|
|
|
<< "Print options are processed in the following order:" << std::endl
|
|
|
|
<< "\t1) Config keys from the command line, for example --fill-pattern=stars" << std::endl
|
|
|
|
<< "\t (highest priority, overwrites everything below)" << std::endl
|
|
|
|
<< "\t2) Config files loaded with --load" << std::endl
|
|
|
|
<< "\t3) Config values loaded from amf or 3mf files" << std::endl;
|
2019-08-16 14:17:37 +00:00
|
|
|
|
2019-03-13 14:44:50 +00:00
|
|
|
if (include_print_options) {
|
|
|
|
boost::nowide::cout << std::endl;
|
2019-08-16 14:17:37 +00:00
|
|
|
print_config_def.print_cli_help(boost::nowide::cout, true, [printer_technology](const ConfigOptionDef &def)
|
2019-03-13 18:17:26 +00:00
|
|
|
{ return printer_technology == ptAny || def.printer_technology == ptAny || printer_technology == def.printer_technology; });
|
2019-03-13 14:44:50 +00:00
|
|
|
} else {
|
|
|
|
boost::nowide::cout
|
|
|
|
<< std::endl
|
2019-03-13 18:49:14 +00:00
|
|
|
<< "Run --help-fff / --help-sla to see the full listing of print options." << std::endl;
|
2018-09-25 07:55:15 +00:00
|
|
|
}
|
2019-03-13 14:44:50 +00:00
|
|
|
}
|
2018-09-25 07:55:15 +00:00
|
|
|
|
2019-03-13 14:44:50 +00:00
|
|
|
bool CLI::export_models(IO::ExportFormat format)
|
|
|
|
{
|
|
|
|
for (Model &model : m_models) {
|
|
|
|
const std::string path = this->output_filepath(model, format);
|
|
|
|
bool success = false;
|
|
|
|
switch (format) {
|
2020-01-08 10:11:38 +00:00
|
|
|
case IO::AMF: success = Slic3r::store_amf(path.c_str(), &model, nullptr, false); break;
|
2019-03-13 14:44:50 +00:00
|
|
|
case IO::OBJ: success = Slic3r::store_obj(path.c_str(), &model); break;
|
2019-08-16 14:17:37 +00:00
|
|
|
case IO::STL: success = Slic3r::store_stl(path.c_str(), &model, true); break;
|
2020-01-08 10:11:38 +00:00
|
|
|
case IO::TMF: success = Slic3r::store_3mf(path.c_str(), &model, nullptr, false); break;
|
2019-03-13 14:44:50 +00:00
|
|
|
default: assert(false); break;
|
2017-08-18 11:32:35 +00:00
|
|
|
}
|
2019-03-13 14:44:50 +00:00
|
|
|
if (success)
|
2019-08-16 14:17:37 +00:00
|
|
|
std::cout << "File exported to " << path << std::endl;
|
2019-03-13 14:44:50 +00:00
|
|
|
else {
|
2019-08-16 14:17:37 +00:00
|
|
|
std::cerr << "File export to " << path << " failed" << std::endl;
|
2019-03-13 14:44:50 +00:00
|
|
|
return false;
|
2017-08-18 11:32:35 +00:00
|
|
|
}
|
|
|
|
}
|
2019-03-13 14:44:50 +00:00
|
|
|
return true;
|
2017-08-18 11:32:35 +00:00
|
|
|
}
|
2018-09-20 14:48:13 +00:00
|
|
|
|
2019-03-13 14:44:50 +00:00
|
|
|
std::string CLI::output_filepath(const Model &model, IO::ExportFormat format) const
|
2018-09-20 14:48:13 +00:00
|
|
|
{
|
2019-03-13 14:44:50 +00:00
|
|
|
std::string ext;
|
|
|
|
switch (format) {
|
2019-05-03 09:43:48 +00:00
|
|
|
case IO::AMF: ext = ".zip.amf"; break;
|
2019-03-13 14:44:50 +00:00
|
|
|
case IO::OBJ: ext = ".obj"; break;
|
|
|
|
case IO::STL: ext = ".stl"; break;
|
2019-08-16 14:17:37 +00:00
|
|
|
case IO::TMF: ext = ".3mf"; break;
|
2019-03-13 14:44:50 +00:00
|
|
|
default: assert(false); break;
|
|
|
|
};
|
|
|
|
auto proposed_path = boost::filesystem::path(model.propose_export_file_name_and_path(ext));
|
|
|
|
// use --output when available
|
2019-08-16 14:17:37 +00:00
|
|
|
std::string cmdline_param = m_config.opt_string("output");
|
2019-03-13 14:44:50 +00:00
|
|
|
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();
|
2018-09-20 14:48:13 +00:00
|
|
|
}
|
2018-10-26 09:57:52 +00:00
|
|
|
|
2019-08-16 14:17:37 +00:00
|
|
|
#if defined(_MSC_VER) || defined(__MINGW32__)
|
2018-10-26 09:57:52 +00:00
|
|
|
extern "C" {
|
2019-08-16 14:17:37 +00:00
|
|
|
__declspec(dllexport) int __stdcall slic3r_main(int argc, wchar_t **argv)
|
|
|
|
{
|
|
|
|
// Convert wchar_t arguments to UTF8.
|
|
|
|
std::vector<std::string> argv_narrow;
|
|
|
|
std::vector<char*> argv_ptrs(argc + 1, nullptr);
|
|
|
|
for (size_t i = 0; i < argc; ++ i)
|
|
|
|
argv_narrow.emplace_back(boost::nowide::narrow(argv[i]));
|
|
|
|
for (size_t i = 0; i < argc; ++ i)
|
2020-01-03 15:32:56 +00:00
|
|
|
argv_ptrs[i] = argv_narrow[i].data();
|
2019-08-16 14:17:37 +00:00
|
|
|
// Call the UTF8 main.
|
|
|
|
return CLI().run(argc, argv_ptrs.data());
|
|
|
|
}
|
2018-10-26 09:57:52 +00:00
|
|
|
}
|
2019-03-13 14:44:50 +00:00
|
|
|
#else /* _MSC_VER */
|
|
|
|
int main(int argc, char **argv)
|
|
|
|
{
|
|
|
|
return CLI().run(argc, argv);
|
|
|
|
}
|
2018-10-26 09:57:52 +00:00
|
|
|
#endif /* _MSC_VER */
|