update src and test

This commit is contained in:
QIDI TECH
2025-03-22 09:44:19 +08:00
parent b15deeb656
commit 7e7d699e43
151 changed files with 36981 additions and 1531 deletions

View File

@@ -95,6 +95,7 @@ PenaltyBreakString: 600
PenaltyBreakTemplateDeclaration: 10 PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 50 PenaltyExcessCharacter: 50
PenaltyReturnTypeOnItsOwnLine: 300 PenaltyReturnTypeOnItsOwnLine: 300
PenaltyIndentedWhitespace: 10
PointerAlignment: Right PointerAlignment: Right
ReflowComments: true ReflowComments: true
SortIncludes: false SortIncludes: false

View File

@@ -616,7 +616,6 @@ if(SLIC3R_BUILD_TESTS)
endif() endif()
# Resources install target, configure fhs.hpp on UNIX
if (WIN32) if (WIN32)
install(DIRECTORY "${SLIC3R_RESOURCES_DIR}/" DESTINATION "${CMAKE_INSTALL_PREFIX}/resources") install(DIRECTORY "${SLIC3R_RESOURCES_DIR}/" DESTINATION "${CMAKE_INSTALL_PREFIX}/resources")
elseif (SLIC3R_FHS) elseif (SLIC3R_FHS)
@@ -636,10 +635,10 @@ elseif (SLIC3R_FHS)
) )
endforeach() endforeach()
install(DIRECTORY ${SLIC3R_RESOURCES_DIR}/udev/ DESTINATION lib/udev/rules.d) install(DIRECTORY ${SLIC3R_RESOURCES_DIR}/udev/ DESTINATION lib/udev/rules.d)
target_compile_definitions(QIDISlicer PUBLIC SLIC3R_FHS SLIC3R_FHS_RESOURCES="${SLIC3R_FHS_RESOURCES}")
else () else ()
install(FILES src/platform/unix/QIDISlicer.desktop DESTINATION ${CMAKE_INSTALL_PREFIX}/resources/applications) install(FILES src/platform/unix/QIDISlicer.desktop DESTINATION ${CMAKE_INSTALL_PREFIX}/resources/applications)
install(FILES src/platform/unix/QIDIGcodeviewer.desktop DESTINATION ${CMAKE_INSTALL_PREFIX}/resources/applications) install(FILES src/platform/unix/QIDIGcodeviewer.desktop DESTINATION ${CMAKE_INSTALL_PREFIX}/resources/applications)
install(DIRECTORY "${SLIC3R_RESOURCES_DIR}/" DESTINATION "${CMAKE_INSTALL_PREFIX}/resources") install(DIRECTORY "${SLIC3R_RESOURCES_DIR}/" DESTINATION "${CMAKE_INSTALL_PREFIX}/resources")
endif () endif ()
configure_file(src/platform/unix/fhs.hpp.in ${LIBDIR_BIN}/platform/unix/fhs.hpp)

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

@@ -0,0 +1,73 @@
#pragma once
#include <string>
#include <vector>
#include "libslic3r/Model.hpp"
#include "CLI_DynamicPrintConfig.hpp"
#ifdef SLIC3R_GUI
#include "slic3r/GUI/GUI_Init.hpp"
#endif
namespace Slic3r::CLI
{
// struct which is filled from comand line input
struct Data
{
Data();
CLI_DynamicPrintConfig input_config;
CLI_DynamicPrintConfig overrides_config;
CLI_DynamicPrintConfig transform_config;
CLI_DynamicPrintConfig misc_config;
CLI_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
}

View File

@@ -0,0 +1,47 @@
#pragma once
#include "libslic3r/Config.hpp"
namespace Slic3r::CLI
{
enum class Type
{
Input,
Overrides,
Transformations,
Misc,
Actions,
Undef
};
class CLI_DynamicPrintConfig : public DynamicPrintConfig
{
public:
CLI_DynamicPrintConfig() {}
CLI_DynamicPrintConfig(Type type, const ConfigDef* config_def) :
m_type (type),
m_config_def (config_def) {}
CLI_DynamicPrintConfig(const CLI_DynamicPrintConfig& other) :
DynamicPrintConfig(other),
m_type (other.type()),
m_config_def (other.def()) {}
// Overrides ConfigBase::def(). Static configuration definition. Any value stored into this ConfigBase shall have its definition here.
const ConfigDef* def() const override { return m_config_def; }
// Verify whether the opt_key has not been obsoleted or renamed.
// Both opt_key and value may be modified by handle_legacy().
// If the opt_key is no more valid in this version of Slic3r, opt_key is cleared by handle_legacy().
// handle_legacy() is called internally by set_deserialize().
void handle_legacy(t_config_option_key& opt_key, std::string& value) const override {
if (m_type == Type::Overrides)
DynamicPrintConfig::handle_legacy(opt_key, value);
}
Type type() const { return m_type; }
private:
Type m_type { Type::Undef };
const ConfigDef* m_config_def { nullptr };
};
}

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

@@ -0,0 +1,144 @@
#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;
}
// are we starting as gcodeviewer ?
if (cli.actions_config.has("gcodeviewer")) {
start_gui = true;
gui_params.start_as_gcodeviewer = true;
}
#ifndef _WIN32
else {
// On Unix systems, the qidi-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 !SLIC3R_OPENGL_ES
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, "qidislicer://")) {
gui_params.start_downloader = true;
gui_params.download_url = file;
break;
}
}
return start_gui;
}
int start_gui_with_params(GUI::GUI_InitParams& params)
{
#if !defined(_WIN32) && !defined(__APPLE__)
// likely some linux / unix system
const char* display = boost::nowide::getenv("DISPLAY");
// const char *wayland_display = boost::nowide::getenv("WAYLAND_DISPLAY");
//if (! ((display && *display) || (wayland_display && *wayland_display))) {
if (!(display && *display)) {
// DISPLAY not set.
boost::nowide::cerr << "DISPLAY not set, GUI mode not available." << std::endl << std::endl;
print_help(false);
// Indicate an error.
return 1;
}
#endif // some linux / unix system
return Slic3r::GUI::GUI_Run(params);
}
int start_as_gcode_viewer(GUI::GUI_InitParams& gui_params)
{
if (gui_params.input_files.size() > 1) {
boost::nowide::cerr << "You can open only one .gcode file at a time in GCodeViewer" << std::endl;
return 1;
}
if (!gui_params.input_files.empty()) {
const std::string& file = gui_params.input_files[0];
if (!is_gcode_file(file) || !boost::filesystem::exists(file)) {
boost::nowide::cerr << "Input file isn't a .gcode file or doesn't exist. GCodeViewer can't be start." << std::endl;
return 1;
}
}
return start_gui_with_params(gui_params);
}
}
#else // SLIC3R_GUI
// If there is no GUI, we shall ignore the parameters. Remove them from the list.
#endif // SLIC3R_GUI

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

@@ -0,0 +1,259 @@
#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <boost/filesystem.hpp>
#include <boost/nowide/args.hpp>
#include <boost/nowide/iostream.hpp>
#include "libslic3r/libslic3r.h"
#include "libslic3r/Config.hpp"
#include "libslic3r/GCode/PostProcessor.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/FileReader.hpp"
#include "CLI/CLI.hpp"
#include "CLI/ProfilesSharingUtils.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, "qidislicer://")) {
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) || !FileReader::is_project_file(file)) {
// 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> qidislicer_generator_version;
//FIXME should we check the version here? // | Model::LoadAttribute::CheckVersion ?
model = FileReader::load_model_with_config(file, &config, &config_substitutions_ctxt, qidislicer_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 (FileReader::is_project_file(file) &&
(!cli.transform_config.has("dont_arrange") || !cli.transform_config.opt_bool("dont_arrange"))) {
//So, check a state of "dont_arrange" parameter and set it to true, if its value is false.
cli.transform_config.set_key_value("dont_arrange", new ConfigOptionBool(true));
}
}
catch (std::exception& e) {
boost::nowide::cerr << file << ": " << e.what() << std::endl;
return false;
}
if (model.objects.empty()) {
boost::nowide::cerr << "Error: file is empty: " << file << std::endl;
continue;
}
models.push_back(model);
}
return true;
}
static bool finalize_print_config(DynamicPrintConfig& print_config, PrinterTechnology& printer_technology, const Data& cli)
{
// Apply command line options to a more specific DynamicPrintConfig which provides normalize()
// (command line options override --load files or congiguration which is loaded prom profiles)
print_config.apply(cli.overrides_config, true);
// Normalizing after importing the 3MFs / AMFs
print_config.normalize_fdm();
if (printer_technology == ptUnknown)
printer_technology = cli.actions_config.has("export_sla") ? ptSLA : ptFFF;
print_config.option<ConfigOptionEnum<PrinterTechnology>>("printer_technology", true)->value = printer_technology;
// Initialize full print configs for both the FFF and SLA technologies.
FullPrintConfig fff_print_config;
SLAFullPrintConfig sla_print_config;
// Synchronize the default parameters and the ones received on the command line.
if (printer_technology == ptFFF) {
fff_print_config.apply(print_config, true);
print_config.apply(fff_print_config, true);
}
else {
assert(printer_technology == ptSLA);
sla_print_config.output_filename_format.value = "[input_filename_base].sl1";
// The default bed shape should reflect the default display parameters
// and not the fff defaults.
double w = sla_print_config.display_width.getFloat();
double h = sla_print_config.display_height.getFloat();
sla_print_config.bed_shape.values = { Vec2d(0, 0), Vec2d(w, 0), Vec2d(w, h), Vec2d(0, h) };
sla_print_config.apply(print_config, true);
print_config.apply(sla_print_config, true);
}
// validate print configuration
std::string validity = print_config.validate();
if (!validity.empty()) {
boost::nowide::cerr << "Error: The composite configation is not valid: " << validity << std::endl;
return false;
}
return true;
}
bool load_print_data(std::vector<Model>& models,
DynamicPrintConfig& print_config,
PrinterTechnology& printer_technology,
Data& cli)
{
if (!load_print_config(print_config, printer_technology, cli))
return false;
if (!process_input_files(models, print_config, printer_technology, cli))
return false;
if (!finalize_print_config(print_config, printer_technology, cli))
return false;
return true;
}
bool is_needed_post_processing(const DynamicPrintConfig& print_config)
{
if (print_config.has("post_process")) {
const std::vector<std::string>& post_process = print_config.opt<ConfigOptionStrings>("post_process")->values;
if (!post_process.empty()) {
boost::nowide::cout << "\nA post-processing script has been detected in the config data:\n\n";
for (const std::string& s : post_process) {
boost::nowide::cout << "> " << s << "\n";
}
boost::nowide::cout << "\nContinue(Y/N) ? ";
char in;
boost::nowide::cin >> in;
if (in != 'Y' && in != 'y')
return true;
}
}
return false;
}
}

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

@@ -0,0 +1,180 @@
#include <string>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/join.hpp>
#include <boost/nowide/iostream.hpp>
#include "CLI.hpp"
namespace Slic3r::CLI {
static void print_help(const ConfigDef& config_def, bool show_defaults, std::function<bool(const ConfigOptionDef&)> filter = [](const ConfigOptionDef &){ return true; })
{
// prepare a function for wrapping text
auto wrap = [](const std::string& text, size_t line_length) -> std::string {
std::istringstream words(text);
std::ostringstream wrapped;
std::string word;
if (words >> word) {
wrapped << word;
size_t space_left = line_length - word.length();
while (words >> word) {
if (space_left < word.length() + 1) {
wrapped << '\n' << word;
space_left = line_length - word.length();
}
else {
wrapped << ' ' << word;
space_left -= word.length() + 1;
}
}
}
return wrapped.str();
};
// List of opt_keys that should be hidden from the CLI help.
const std::vector<std::string> silent_options = { "webdev", "single_instance_on_url" };
// get the unique categories
std::set<std::string> categories;
for (const auto& opt : config_def.options) {
const ConfigOptionDef& def = opt.second;
if (filter(def))
categories.insert(def.category);
}
for (const std::string& category : categories) {
if (category != "") {
boost::nowide::cout << category << ":" << std::endl;
}
else if (categories.size() > 1) {
boost::nowide::cout << "Misc options:" << std::endl;
}
for (const auto& opt : config_def.options) {
const ConfigOptionDef& def = opt.second;
if (def.category != category || def.cli == ConfigOptionDef::nocli || !filter(def))
continue;
if (std::find(silent_options.begin(), silent_options.end(), opt.second.opt_key) != silent_options.end())
continue;
// get all possible variations: --foo, --foobar, -f...
std::vector<std::string> cli_args = def.cli_args(opt.first);
if (cli_args.empty())
continue;
for (auto& arg : cli_args) {
arg.insert(0, (arg.size() == 1) ? "-" : "--");
if (def.type == coFloat || def.type == coInt || def.type == coFloatOrPercent
|| def.type == coFloats || def.type == coInts) {
arg += " N";
}
else if (def.type == coPoint) {
arg += " X,Y";
}
else if (def.type == coPoint3) {
arg += " X,Y,Z";
}
else if (def.type == coString || def.type == coStrings) {
arg += " ABCD";
}
}
// left: command line options
const std::string cli = boost::algorithm::join(cli_args, ", ");
boost::nowide::cout << " " << std::left << std::setw(20) << cli;
// right: option description
std::string descr = def.tooltip;
bool show_defaults_this = show_defaults || def.opt_key == "config_compatibility";
if (show_defaults_this && def.default_value && def.type != coBool
&& (def.type != coString || !def.default_value->serialize().empty())) {
descr += " (";
if (!def.sidetext.empty()) {
descr += def.sidetext + ", ";
}
else if (def.enum_def && def.enum_def->has_values()) {
descr += boost::algorithm::join(def.enum_def->values(), ", ") + "; ";
}
descr += "default: " + def.default_value->serialize() + ")";
}
// wrap lines of description
descr = wrap(descr, 80);
std::vector<std::string> lines;
boost::split(lines, descr, boost::is_any_of("\n"));
// if command line options are too long, print description in new line
for (size_t i = 0; i < lines.size(); ++i) {
if (i == 0 && cli.size() > 19)
boost::nowide::cout << std::endl;
if (i > 0 || cli.size() > 19)
boost::nowide::cout << std::string(21, ' ');
boost::nowide::cout << lines[i] << std::endl;
}
}
}
}
void print_help(bool include_print_options/* = false*/, PrinterTechnology printer_technology/* = ptAny*/)
{
boost::nowide::cout
<< SLIC3R_BUILD_ID << " " << "based on Slic3r"
#ifdef SLIC3R_GUI
<< " (with GUI support)"
#else /* SLIC3R_GUI */
<< " (without GUI support)"
#endif /* SLIC3R_GUI */
<< std::endl
<< "https://github.com/qiditech/QIDISlicer" << std::endl << std::endl
<< "Usage: qidi-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;
}
}
}

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

@@ -0,0 +1,421 @@
#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 <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 "libslic3r/BuildVolume.hpp"
#include "CLI/CLI.hpp"
#include "CLI/ProfilesSharingUtils.hpp"
#define STB_IMAGE_RESIZE_IMPLEMENTATION
#include "stb_image_resize2.h"
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 ThumbnailData resize_and_crop(const std::vector<unsigned char>& data, int width, int height, int width_new, int height_new) {
ThumbnailData th;
float scale_x = float(width_new) / width;
float scale_y = float(height_new) / height;
float scale = std::max(scale_x, scale_y); // Choose the larger scale to fill the box
int resized_width = int(width * scale);
int resized_height = int(height * scale);
std::vector<unsigned char> resized_rgba(resized_width * resized_height * 4);
stbir_resize_uint8_linear(data.data(), width, height, 4 * width,
resized_rgba.data(), resized_width, resized_height, 4 * resized_width,
STBIR_RGBA);
th.set(width_new, height_new);
int crop_x = (resized_width - width_new) / 2;
int crop_y = (resized_height - height_new) / 2;
for (int y = 0; y < height_new; ++y) {
std::memcpy(th.pixels.data() + y * width_new * 4,
resized_rgba.data() + ((y + crop_y) * resized_width + crop_x) * 4,
width_new * 4);
}
return th;
}
static std::function<ThumbnailsList(const ThumbnailsParams&)> get_thumbnail_generator_cli(const std::string& filename)
{
if (boost::iends_with(filename, ".3mf")) {
return [filename](const ThumbnailsParams& params) {
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);
}
}
for (const Vec2d& size : params.sizes) {
Point isize(size);
list_out.push_back(resize_and_crop(data, width, height, isize.x(), isize.y()));
}
return list_out;
};
}
return [](const ThumbnailsParams&) ->ThumbnailsList { return {}; };
}
static void update_instances_outside_state(Model& model, const DynamicPrintConfig& config)
{
Pointfs bed_shape = dynamic_cast<const ConfigOptionPoints*>(config.option("bed_shape"))->values;
BuildVolume build_volume(bed_shape, config.opt_float("max_print_height"));
s_multiple_beds.update_build_volume(BoundingBoxf(bed_shape));
model.update_print_volume_state(build_volume);
}
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") || actions.has("export_gcode") || actions.has("export_sla")) {
PrinterTechnology printer_technology = Preset::printer_technology(print_config);
if (actions.has("export_gcode") && printer_technology == ptSLA) {
boost::nowide::cerr << "error: cannot export G-code for an FFF configuration" << std::endl;
return 1;
}
else if (actions.has("export_sla") && printer_technology == ptFFF) {
boost::nowide::cerr << "error: cannot export SLA slices for a SLA configuration" << std::endl;
return 1;
}
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);
}
update_instances_outside_state(model, print_config);
MultipleBedsUtils::with_single_bed_model_fff(model, 0, [&print, &model, &print_config]()
{
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.
const std::string input_file = fff_print.model().objects.empty() ? "" : fff_print.model().objects.front()->input_file;
outfile = fff_print.export_gcode(outfile, nullptr, get_thumbnail_generator_cli(input_file));
outfile_final = fff_print.print_statistics().finalize_output_path(outfile);
}
else {
outfile = sla_print.output_filepath(outfile);
// We need to finalize the filename beforehand because the export function sets the filename inside the zip metadata
outfile_final = sla_print.print_statistics().finalize_output_path(outfile);
sla_print.export_print(outfile_final);
}
if (outfile != outfile_final) {
if (Slic3r::rename_file(outfile, outfile_final)) {
boost::nowide::cerr << "Renaming file " << outfile << " to " << outfile_final << " failed" << std::endl;
return false;
}
outfile = outfile_final;
}
// Run the post-processing scripts if defined.
run_post_process_scripts(outfile, fff_print.full_print_config());
boost::nowide::cout << "Slicing result exported to " << outfile << std::endl;
}
catch (const std::exception& ex) {
boost::nowide::cerr << ex.what() << std::endl;
return false;
}
}
}
return true;
}
}

View File

@@ -0,0 +1,210 @@
#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;
const Vec3d plane_center = transform.opt_float("cut") * Vec3d::UnitZ();
for (auto& model : models) {
Model new_model;
model.translate(0, 0, -model.bounding_box_exact().min.z()); // align to z = 0
size_t num_objects = model.objects.size();
for (size_t i = 0; i < num_objects; ++i) {
ModelObject* mo = model.objects.front();
const Vec3d cut_center_offset = plane_center - mo->instances[0]->get_offset();
Cut cut(mo, 0, Geometry::translation_transform(cut_center_offset),
ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::PlaceOnCutUpper);
auto cut_objects = cut.perform_with_plane();
for (ModelObject* obj : cut_objects)
new_model.add_object(*obj);
model.delete_object(size_t(0));
}
new_models.push_back(new_model);
}
// 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;
}
}

View File

@@ -0,0 +1,584 @@
#include "ProfilesSharingUtils.hpp"
#include "libslic3r/Utils.hpp"
#include "libslic3r/format.hpp"
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/PresetBundle.hpp"
#include "libslic3r/Utils/DirectoriesUtils.hpp"
#include "libslic3r/Utils/JsonUtils.hpp"
#include "libslic3r/BuildVolume.hpp"
#include <boost/log/trivial.hpp>
namespace Slic3r {
static bool load_preset_bundle_from_datadir(PresetBundle& preset_bundle)
{
AppConfig app_config = AppConfig(AppConfig::EAppMode::Editor);
if (!app_config.exists()) {
BOOST_LOG_TRIVIAL(error) << "Configuration wasn't found. Check your 'datadir' value.";
return false;
}
if (std::string error = app_config.load(); !error.empty()) {
BOOST_LOG_TRIVIAL(error) << Slic3r::format("Error parsing QIDISlicer config file, it is probably corrupted. "
"Try to manually delete the file to recover from the error. Your user profiles will not be affected."
"\n%1%\n%2%", app_config.config_path(), error);
return false;
}
// just checking for existence of Slic3r::data_dir is not enough : it may be an empty directory
// supplied as argument to --datadir; in that case we should still run the wizard
preset_bundle.setup_directories();
std::string delayed_error_load_presets;
// Suppress the '- default -' presets.
preset_bundle.set_default_suppressed(app_config.get_bool("no_defaults"));
try {
auto preset_substitutions = preset_bundle.load_presets(app_config, ForwardCompatibilitySubstitutionRule::EnableSystemSilent);
if (!preset_substitutions.empty()) {
BOOST_LOG_TRIVIAL(error) << "Some substitutions are found during loading presets.";
return false;
}
// Post-process vendor map to delete non-installed models/varians
VendorMap& vendors = preset_bundle.vendors;
for (auto& [vendor_id, vendor_profile] : vendors) {
std::vector<VendorProfile::PrinterModel> models;
for (auto& printer_model : vendor_profile.models) {
std::vector<VendorProfile::PrinterVariant> variants;
for (const auto& variant : printer_model.variants) {
// check if printer model with variant is intalled
if (app_config.get_variant(vendor_id, printer_model.id, variant.name))
variants.push_back(variant);
}
if (!variants.empty()) {
if (printer_model.variants.size() != variants.size())
printer_model.variants = variants;
models.push_back(printer_model);
}
}
if (!models.empty()) {
if (vendor_profile.models.size() != models.size())
vendor_profile.models = models;
}
}
}
catch (const std::exception& ex) {
BOOST_LOG_TRIVIAL(error) << ex.what();
return false;
}
return true;
}
namespace pt = boost::property_tree;
/*
struct PrinterAttr_
{
std::string model_name;
std::string variant;
};
static std::string get_printer_profiles(const VendorProfile* vendor_profile,
const PresetBundle* preset_bundle,
const PrinterAttr_& printer_attr)
{
for (const auto& printer_model : vendor_profile->models) {
if (printer_model.name != printer_attr.model_name)
continue;
for (const auto& variant : printer_model.variants)
if (variant.name == printer_attr.variant)
{
pt::ptree data_node;
data_node.put("printer_model", printer_model.name);
data_node.put("printer_variant", printer_attr.variant);
pt::ptree printer_profiles_node;
for (const Preset& printer_preset : preset_bundle->printers) {
if (printer_preset.vendor->id == vendor_profile->id &&
printer_preset.is_visible && // ???
printer_preset.config.opt_string("printer_model") == printer_model.id &&
printer_preset.config.opt_string("printer_variant") == printer_attr.variant) {
pt::ptree profile_node;
profile_node.put("", printer_preset.name);
printer_profiles_node.push_back(std::make_pair("", profile_node));
}
}
data_node.add_child("printer_profiles", printer_profiles_node);
// Serialize the tree into JSON and return it.
return write_json_with_post_process(data_node);
}
}
return "";
}
std::string get_json_printer_profiles(const std::string& printer_model_name, const std::string& printer_variant)
{
if (!is_datadir())
return "";
PrinterAttr_ printer_attr({printer_model_name, printer_variant});
PresetBundle preset_bundle;
if (!load_preset_bundle_from_datadir(preset_bundle))
return "";
const VendorMap& vendors = preset_bundle.vendors;
for (const auto& [vendor_id, vendor] : vendors) {
std::string out = get_printer_profiles(&vendor, &preset_bundle, printer_attr);
if (!out.empty())
return out;
}
return "";
}
*/
struct PrinterAttr
{
std::string vendor_id;
std::string model_id;
std::string variant_name;
};
static bool is_compatible_preset(const Preset& printer_preset, const PrinterAttr& attr)
{
return printer_preset.vendor->id == attr.vendor_id &&
printer_preset.config.opt_string("printer_model") == attr.model_id &&
printer_preset.config.opt_string("printer_variant") == attr.variant_name;
}
static void add_profile_node(pt::ptree& printer_profiles_node, const Preset& printer_preset)
{
pt::ptree profile_node;
const DynamicPrintConfig& config = printer_preset.config;
int extruders_cnt = printer_preset.printer_technology() == ptSLA ? 0 :
config.option<ConfigOptionFloats>("nozzle_diameter")->values.size();
profile_node.put("name", printer_preset.name);
if (extruders_cnt > 0)
profile_node.put("extruders_cnt", extruders_cnt);
const double max_print_height = config.opt_float("max_print_height");
const ConfigOptionPoints& bed_shape = *config.option<ConfigOptionPoints>("bed_shape");
BuildVolume build_volume = BuildVolume { bed_shape.values, max_print_height };
BoundingBoxf bb = build_volume.bounding_volume2d();
Vec2d origin_pt;
if (build_volume.type() == BuildVolume::Type::Circle) {
origin_pt = build_volume.bed_center();
}
else {
origin_pt = to_2d(-1 * build_volume.bounding_volume().min);
}
std::string origin = Slic3r::format("[%1%, %2%]", is_approx(origin_pt.x(), 0.) ? 0 : origin_pt.x(),
is_approx(origin_pt.y(), 0.) ? 0 : origin_pt.y());
pt::ptree bed_node;
bed_node.put("type", build_volume.type_name());
bed_node.put("width", bb.max.x() - bb.min.x());
bed_node.put("height", bb.max.y() - bb.min.y());
bed_node.put("origin", origin);
bed_node.put("max_print_height", max_print_height);
profile_node.add_child("bed", bed_node);
printer_profiles_node.push_back(std::make_pair("", profile_node));
}
static void get_printer_profiles_node(pt::ptree& printer_profiles_node,
pt::ptree& user_printer_profiles_node,
const PrinterPresetCollection& printer_presets,
const PrinterAttr& attr)
{
printer_profiles_node.clear();
user_printer_profiles_node.clear();
for (const Preset& printer_preset : printer_presets) {
if (!printer_preset.is_visible)
continue;
if (printer_preset.is_user()) {
const Preset* parent_preset = printer_presets.get_preset_parent(printer_preset);
if (parent_preset && is_compatible_preset(*parent_preset, attr))
add_profile_node(user_printer_profiles_node, printer_preset);
}
else if (is_compatible_preset(printer_preset, attr))
add_profile_node(printer_profiles_node, printer_preset);
}
}
static void add_printer_models(pt::ptree& vendor_node,
const VendorProfile* vendor_profile,
PrinterTechnology printer_technology,
const PrinterPresetCollection& printer_presets)
{
for (const auto& printer_model : vendor_profile->models) {
if (printer_technology != ptUnknown && printer_model.technology != printer_technology)
continue;
pt::ptree variants_node;
pt::ptree printer_profiles_node;
pt::ptree user_printer_profiles_node;
if (printer_model.technology == ptSLA) {
PrinterAttr attr({ vendor_profile->id, printer_model.id, "default" });
get_printer_profiles_node(printer_profiles_node, user_printer_profiles_node, printer_presets, attr);
if (printer_profiles_node.empty() && user_printer_profiles_node.empty())
continue;
}
else {
for (const auto& variant : printer_model.variants) {
PrinterAttr attr({ vendor_profile->id, printer_model.id, variant.name });
get_printer_profiles_node(printer_profiles_node, user_printer_profiles_node, printer_presets, attr);
if (printer_profiles_node.empty() && user_printer_profiles_node.empty())
continue;
pt::ptree variant_node;
variant_node.put("name", variant.name);
variant_node.add_child("printer_profiles", printer_profiles_node);
if (!user_printer_profiles_node.empty())
variant_node.add_child("user_printer_profiles", user_printer_profiles_node);
variants_node.push_back(std::make_pair("", variant_node));
}
if (variants_node.empty())
continue;
}
pt::ptree data_node;
data_node.put("id", printer_model.id);
data_node.put("name", printer_model.name);
data_node.put("technology", printer_model.technology == ptFFF ? "FFF" : "SLA");
if (!variants_node.empty())
data_node.add_child("variants", variants_node);
else {
data_node.add_child("printer_profiles", printer_profiles_node);
if (!user_printer_profiles_node.empty())
data_node.add_child("user_printer_profiles", user_printer_profiles_node);
}
data_node.put("vendor_name", vendor_profile->name);
data_node.put("vendor_id", vendor_profile->id);
vendor_node.push_back(std::make_pair("", data_node));
}
}
static void add_undef_printer_models(pt::ptree& vendor_node,
PrinterTechnology printer_technology,
const PrinterPresetCollection& printer_presets)
{
for (auto pt : { ptFFF, ptSLA }) {
if (printer_technology != ptUnknown && printer_technology != pt)
continue;
pt::ptree printer_profiles_node;
for (const Preset& preset : printer_presets) {
if (!preset.is_visible || preset.printer_technology() != pt ||
preset.vendor || printer_presets.get_preset_parent(preset))
continue;
add_profile_node(printer_profiles_node, preset);
}
if (!printer_profiles_node.empty()) {
pt::ptree data_node;
data_node.put("id", "");
data_node.put("technology", pt == ptFFF ? "FFF" : "SLA");
data_node.add_child("printer_profiles", printer_profiles_node);
data_node.put("vendor_name", "");
data_node.put("vendor_id", "");
vendor_node.push_back(std::make_pair("", data_node));
}
}
}
std::string get_json_printer_models(PrinterTechnology printer_technology)
{
PresetBundle preset_bundle;
if (!load_preset_bundle_from_datadir(preset_bundle))
return "";
pt::ptree vendor_node;
const VendorMap& vendors_map = preset_bundle.vendors;
for (const auto& [vendor_id, vendor] : vendors_map)
add_printer_models(vendor_node, &vendor, printer_technology, preset_bundle.printers);
// add printers with no vendor information
add_undef_printer_models(vendor_node, printer_technology, preset_bundle.printers);
pt::ptree root;
root.add_child("printer_models", vendor_node);
// Serialize the tree into JSON and return it.
return write_json_with_post_process(root);
}
static std::string get_installed_print_and_filament_profiles(const PresetBundle* preset_bundle, const Preset* printer_preset)
{
PrinterTechnology printer_technology = printer_preset->printer_technology();
pt::ptree print_profiles;
pt::ptree user_print_profiles;
const PresetWithVendorProfile printer_preset_with_vendor_profile = preset_bundle->printers.get_preset_with_vendor_profile(*printer_preset);
const PresetCollection& print_presets = printer_technology == ptFFF ? preset_bundle->prints : preset_bundle->sla_prints;
const PresetCollection& material_presets = printer_technology == ptFFF ? preset_bundle->filaments : preset_bundle->sla_materials;
const std::string material_node_name = printer_technology == ptFFF ? "filament_profiles" : "sla_material_profiles";
for (auto print_preset : print_presets) {
const PresetWithVendorProfile print_preset_with_vendor_profile = print_presets.get_preset_with_vendor_profile(print_preset);
if (is_compatible_with_printer(print_preset_with_vendor_profile, printer_preset_with_vendor_profile))
{
pt::ptree materials_profile_node;
pt::ptree user_materials_profile_node;
for (auto material_preset : material_presets) {
// ?! check visible and no-template presets only
if (!material_preset.is_visible || (material_preset.vendor && material_preset.vendor->templates_profile))
continue;
const PresetWithVendorProfile material_preset_with_vendor_profile = material_presets.get_preset_with_vendor_profile(material_preset);
if (is_compatible_with_printer(material_preset_with_vendor_profile, printer_preset_with_vendor_profile) &&
is_compatible_with_print(material_preset_with_vendor_profile, print_preset_with_vendor_profile, printer_preset_with_vendor_profile)) {
pt::ptree material_node;
material_node.put("", material_preset.name);
if (material_preset.is_user())
user_materials_profile_node.push_back(std::make_pair("", material_node));
else
materials_profile_node.push_back(std::make_pair("", material_node));
}
}
pt::ptree print_profile_node;
print_profile_node.put("name", print_preset.name);
print_profile_node.add_child(material_node_name, materials_profile_node);
if (!user_materials_profile_node.empty())
print_profile_node.add_child("user_" + material_node_name, user_materials_profile_node);
if (print_preset.is_user())
user_print_profiles.push_back(std::make_pair("", print_profile_node));
else
print_profiles.push_back(std::make_pair("", print_profile_node));
}
}
if (print_profiles.empty() && user_print_profiles.empty())
return "";
pt::ptree tree;
tree.put("printer_profile", printer_preset->name);
tree.add_child("print_profiles", print_profiles);
if (!user_print_profiles.empty())
tree.add_child("user_print_profiles", user_print_profiles);
// Serialize the tree into JSON and return it.
return write_json_with_post_process(tree);
}
std::string get_json_print_filament_profiles(const std::string& printer_profile)
{
PresetBundle preset_bundle;
if (load_preset_bundle_from_datadir(preset_bundle)) {
const Preset* preset = preset_bundle.printers.find_preset(printer_profile, false, false);
if (preset)
return get_installed_print_and_filament_profiles(&preset_bundle, preset);
}
return "";
}
// Helper function for FS
bool load_full_print_config(const std::string& print_preset_name, const std::string& filament_preset_name, const std::string& printer_preset_name, DynamicPrintConfig& config)
{
PresetBundle preset_bundle;
if (!load_preset_bundle_from_datadir(preset_bundle)){
BOOST_LOG_TRIVIAL(error) << Slic3r::format("Failed to load data from the datadir '%1%'.", data_dir());
return false;
}
config = {};
config.apply(FullPrintConfig::defaults());
bool is_failed{ false };
if (const Preset* print_preset = preset_bundle.prints.find_preset(print_preset_name))
config.apply_only(print_preset->config, print_preset->config.keys());
else {
BOOST_LOG_TRIVIAL(warning) << Slic3r::format("Print profile '%1%' wasn't found.", print_preset_name);
is_failed |= true;
}
if (const Preset* filament_preset = preset_bundle.filaments.find_preset(filament_preset_name))
config.apply_only(filament_preset->config, filament_preset->config.keys());
else {
BOOST_LOG_TRIVIAL(warning) << Slic3r::format("Filament profile '%1%' wasn't found.", filament_preset_name);
is_failed |= true;
}
if (const Preset* printer_preset = preset_bundle.printers.find_preset(printer_preset_name))
config.apply_only(printer_preset->config, printer_preset->config.keys());
else {
BOOST_LOG_TRIVIAL(warning) << Slic3r::format("Printer profile '%1%' wasn't found.", printer_preset_name);
is_failed |= true;
}
return !is_failed;
}
// Helper function for load full config from installed presets by profile names
std::string load_full_print_config(const std::string& print_preset_name,
const std::vector<std::string>& material_preset_names_in,
const std::string& printer_preset_name,
DynamicPrintConfig& config,
PrinterTechnology printer_technology /*= ptUnknown*/)
{
// check entered profile names
if (print_preset_name.empty() ||
material_preset_names_in.empty() ||
printer_preset_name.empty())
return "Request is not completed. All of Print/Material/Printer profiles have to be entered";
// check preset bundle
PresetBundle preset_bundle;
if (!load_preset_bundle_from_datadir(preset_bundle))
return Slic3r::format("Failed to load data from the datadir '%1%'.", data_dir());
// check existance of required profiles
std::string errors;
const Preset* printer_preset = preset_bundle.printers.find_preset(printer_preset_name);
if (!printer_preset)
errors += "\n" + Slic3r::format("Printer profile '%1%' wasn't found.", printer_preset_name);
else if (printer_technology == ptUnknown)
printer_technology = printer_preset->printer_technology();
else if (printer_technology != printer_preset->printer_technology())
errors += "\n" + std::string("Printer technology of the selected printer preset is differs with required printer technology");
PresetCollection& print_presets = printer_technology == ptFFF ? preset_bundle.prints : preset_bundle.sla_prints;
const Preset* print_preset = print_presets.find_preset(print_preset_name);
if (!print_preset)
errors += "\n" + Slic3r::format("Print profile '%1%' wasn't found.", print_preset_name);
PresetCollection& material_presets = printer_technology == ptFFF ? preset_bundle.filaments : preset_bundle.sla_materials;
auto check_material = [&material_presets] (const std::string& name, std::string& errors) -> void {
const Preset* material_preset = material_presets.find_preset(name);
if (!material_preset)
errors += "\n" + Slic3r::format("Material profile '%1%' wasn't found.", name);
};
check_material(material_preset_names_in.front(), errors);
if (material_preset_names_in.size() > 1) {
for (size_t idx = 1; idx < material_preset_names_in.size(); idx++) {
if (material_preset_names_in[idx] != material_preset_names_in.front())
check_material(material_preset_names_in[idx], errors);
}
}
if (!errors.empty())
return errors;
// check and update list of material presets
std::vector<std::string> material_preset_names = material_preset_names_in;
if (printer_technology == ptSLA && material_preset_names.size() > 1) {
BOOST_LOG_TRIVIAL(warning) << "Note: More than one sla material profiles were entered. Extras material profiles will be ignored.";
material_preset_names.resize(1);
}
if (printer_technology == ptFFF) {
const int extruders_count = int(static_cast<const ConfigOptionFloats*>(printer_preset->config.option("nozzle_diameter"))->values.size());
if (extruders_count > int(material_preset_names.size())) {
BOOST_LOG_TRIVIAL(warning) << "Note: Less than needed filament profiles were entered. Missed filament profiles will be filled with first material.";
material_preset_names.reserve(extruders_count);
for (int i = extruders_count - material_preset_names.size(); i > 0; i--)
material_preset_names.push_back(material_preset_names.front());
}
else if (extruders_count < int(material_preset_names.size())) {
BOOST_LOG_TRIVIAL(warning) << "Note: More than needed filament profiles were entered. Extras filament profiles will be ignored.";
material_preset_names.resize(extruders_count);
}
}
// check profiles compatibility
const PresetWithVendorProfile printer_preset_with_vendor_profile = preset_bundle.printers.get_preset_with_vendor_profile(*printer_preset);
const PresetWithVendorProfile print_preset_with_vendor_profile = print_presets.get_preset_with_vendor_profile(*print_preset);
if (!is_compatible_with_printer(print_preset_with_vendor_profile, printer_preset_with_vendor_profile))
errors += "\n" + Slic3r::format("Print profile '%1%' is not compatible with printer profile %2%.", print_preset_name, printer_preset_name);
auto check_material_preset_compatibility = [&material_presets, printer_preset_name, print_preset_name, printer_preset_with_vendor_profile, print_preset_with_vendor_profile]
(const std::string& name, std::string& errors) -> void {
const Preset* material_preset = material_presets.find_preset(name);
const PresetWithVendorProfile material_preset_with_vendor_profile = material_presets.get_preset_with_vendor_profile(*material_preset);
if (!is_compatible_with_printer(material_preset_with_vendor_profile, printer_preset_with_vendor_profile))
errors += "\n" + Slic3r::format("Material profile '%1%' is not compatible with printer profile %2%.", name, printer_preset_name);
if (!is_compatible_with_print(material_preset_with_vendor_profile, print_preset_with_vendor_profile, printer_preset_with_vendor_profile))
errors += "\n" + Slic3r::format("Material profile '%1%' is not compatible with print profile %2%.", name, print_preset_name);
};
check_material_preset_compatibility(material_preset_names.front(), errors);
if (material_preset_names.size() > 1) {
for (size_t idx = 1; idx < material_preset_names.size(); idx++) {
if (material_preset_names[idx] != material_preset_names.front())
check_material_preset_compatibility(material_preset_names[idx], errors);
}
}
if (!errors.empty())
return errors;
// get full print configuration
preset_bundle.printers.select_preset_by_name(printer_preset_name, true);
print_presets.select_preset_by_name(print_preset_name, true);
if (printer_technology == ptSLA)
material_presets.select_preset_by_name(material_preset_names.front(), true);
else if (printer_technology == ptFFF) {
auto& extruders_filaments = preset_bundle.extruders_filaments;
extruders_filaments.clear();
for (size_t i = 0; i < material_preset_names.size(); ++i)
extruders_filaments.emplace_back(ExtruderFilaments(&preset_bundle.filaments, i, material_preset_names[i]));
if (extruders_filaments.size() == 1)
preset_bundle.filaments.select_preset_by_name(material_preset_names[0], false);
}
config = preset_bundle.full_config();
return "";
}
} // namespace Slic3r

View File

@@ -0,0 +1,29 @@
#ifndef slic3r_ProfilesSharingUtils_hpp_
#define slic3r_ProfilesSharingUtils_hpp_
#include <string>
#include "libslic3r/Config.hpp"
namespace Slic3r {
std::string get_json_printer_models(PrinterTechnology printer_technology);
//std::string get_json_printer_profiles(const std::string& printer_model, const std::string& printer_variant);
std::string get_json_print_filament_profiles(const std::string& printer_profile);
class DynamicPrintConfig;
bool load_full_print_config(const std::string& print_preset, const std::string& filament_preset, const std::string& printer_preset, DynamicPrintConfig& out_config);
// Load full print config into config
// Return value is always error string if any exists
// Note, that all appearing warnings are added into BOOST_LOG
// When printer_technology is set, then it will be compared with printer technology of the printer_profile and return the error, when they aren't the same
std::string load_full_print_config( const std::string& print_preset_name,
const std::vector<std::string>& material_preset_names,
const std::string& printer_preset_name,
DynamicPrintConfig& config,
PrinterTechnology printer_technology = ptUnknown);
} // namespace Slic3r
#endif // slic3r_ProfilesSharingUtils_hpp_

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

@@ -0,0 +1,54 @@
#include "../QIDISlicer.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;
}
}

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

@@ -0,0 +1,337 @@
#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 {
Data::Data()
{
input_config = CLI_DynamicPrintConfig(Type::Input, &cli_input_config_def);
overrides_config = CLI_DynamicPrintConfig(Type::Overrides, &print_config_def);
transform_config = CLI_DynamicPrintConfig(Type::Transformations, &cli_transform_config_def);
misc_config = CLI_DynamicPrintConfig(Type::Misc, &cli_misc_config_def);
actions_config = CLI_DynamicPrintConfig(Type::Actions, &cli_actions_config_def);
}
using opts_map = std::map<std::string, std::pair<std::string, Type> >;
static opts_map get_opts_map(const Data& data)
{
opts_map ret;
for (const CLI_DynamicPrintConfig* config : { &data.input_config ,
&data.overrides_config,
&data.transform_config,
&data.misc_config ,
&data.actions_config })
{
for (const auto& oit : config->def()->options)
for (const std::string& t : oit.second.cli_args(oit.first))
ret[t] = { oit.first , config->type()};
}
return ret;
}
static CLI_DynamicPrintConfig* get_config(Data& data, Type type)
{
for (CLI_DynamicPrintConfig* config : { &data.input_config ,
&data.overrides_config,
&data.transform_config,
&data.misc_config ,
&data.actions_config })
{
if (type == config->type())
return config;
}
assert(false);
return nullptr;
}
static bool read(Data& data, int argc, const char* const argv[])
{
// cache the CLI option => opt_key mapping
opts_map opts = get_opts_map(data);
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 auto& [opt_key, type] = it->second;
CLI_DynamicPrintConfig* config = get_config(data, type);
const ConfigOptionDef* optdef = config->option_def(opt_key);
assert(optdef);
// If the option type expects a value and it was not already provided,
// 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);
::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
if (BlacklistedLibraryCheck::get_instance().perform_check()) {
std::wstring text = L"Following DLLs have been injected into the QIDISlicer process:\n\n";
text += BlacklistedLibraryCheck::get_instance().get_blacklisted_string();
text += L"\n\n"
L"QIDISlicer 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 QIDISlicer.\n"
L"For example, ASUS Sonic Studio injects a Nahimic driver, which makes QIDISlicer "
L"to crash on a secondary monitor, see QIDISlicer github issue #5573";
MessageBoxW(NULL, text.c_str(), L"Warning"/*L"Incopatible library found"*/, MB_OK);
}
#endif
// See Invoking qidi-slicer from $PATH environment variable crashes #5542
// boost::filesystem::path path_to_binary = boost::filesystem::system_complete(argv[0]);
boost::filesystem::path path_to_binary = boost::dll::program_location();
// Path from the Slic3r binary to its resources.
#ifdef __APPLE__
// The application is packed in the .dmg archive as 'Slic3r.app/Contents/MacOS/Slic3r'
// The resources are packed to 'Slic3r.app/Contents/Resources'
boost::filesystem::path path_resources = boost::filesystem::canonical(path_to_binary).parent_path() / "../Resources";
#elif defined _WIN32
// The application is packed in the .zip archive in the root,
// The resources are packed to 'resources'
// Path from Slic3r binary to resources:
boost::filesystem::path path_resources = path_to_binary.parent_path() / "resources";
#elif defined SLIC3R_FHS
// The application is packaged according to the Linux Filesystem Hierarchy Standard
// Resources are set to the 'Architecture-independent (shared) data', typically /usr/share or /usr/local/share
boost::filesystem::path path_resources = SLIC3R_FHS_RESOURCES;
#else
// The application is packed in the .tar.bz archive (or in AppImage) as 'bin/slic3r',
// The resources are packed to 'resources'
// Path from Slic3r binary to resources:
boost::filesystem::path path_resources = boost::filesystem::canonical(path_to_binary).parent_path() / "../resources";
#endif
set_resources_dir(path_resources.string());
set_var_dir((path_resources / "icons").string());
set_local_dir((path_resources / "localization").string());
set_sys_shapes_dir((path_resources / "shapes").string());
set_custom_gcodes_dir((path_resources / "custom_gcodes").string());
return true;
}
bool setup(Data& cli, int argc, char** argv)
{
if (!setup_common())
return false;
if (!read(cli, argc, argv)) {
// Separate error message reported by the CLI parser from the help.
boost::nowide::cerr << std::endl;
print_help();
return false;
}
if (cli.misc_config.has("loglevel"))
{
int loglevel = cli.misc_config.opt_int("loglevel");
if (loglevel != 0)
set_logging_level(loglevel);
}
if (cli.misc_config.has("threads"))
thread_count = cli.misc_config.opt_int("threads");
set_data_dir(cli.misc_config.has("datadir") ? cli.misc_config.opt_string("datadir") : get_default_datadir());
#ifdef SLIC3R_GUI
if (cli.misc_config.has("webdev")) {
Utils::ServiceConfig::instance().set_webdev_enabled(cli.misc_config.opt_bool("webdev"));
}
#endif
return true;
}
}

View File

@@ -18,10 +18,12 @@ if (SLIC3R_ENABLE_FORMAT_STEP)
add_subdirectory(occt_wrapper) add_subdirectory(occt_wrapper)
endif () endif ()
add_subdirectory(slic3r-arrange)
add_subdirectory(slic3r-arrange-wrapper)
add_subdirectory(libseqarrange)
if (SLIC3R_GUI) if (SLIC3R_GUI)
add_subdirectory(libvgcode) add_subdirectory(libvgcode)
add_subdirectory(slic3r-arrange)
add_subdirectory(slic3r-arrange-wrapper)
if(WIN32) if(WIN32)
message(STATUS "WXWIN environment set to: $ENV{WXWIN}") message(STATUS "WXWIN environment set to: $ENV{WXWIN}")
@@ -95,12 +97,38 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/platform/msw/QIDISlicer.rc.in ${CMAKE
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/platform/msw/QIDISlicer-gcodeviewer.rc.in ${CMAKE_CURRENT_BINARY_DIR}/QIDISlicer-gcodeviewer.rc @ONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/platform/msw/QIDISlicer-gcodeviewer.rc.in ${CMAKE_CURRENT_BINARY_DIR}/QIDISlicer-gcodeviewer.rc @ONLY)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/platform/msw/QIDISlicer.manifest.in ${CMAKE_CURRENT_BINARY_DIR}/QIDISlicer.manifest @ONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/platform/msw/QIDISlicer.manifest.in ${CMAKE_CURRENT_BINARY_DIR}/QIDISlicer.manifest @ONLY)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/platform/osx/Info.plist.in ${CMAKE_CURRENT_BINARY_DIR}/Info.plist @ONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/platform/osx/Info.plist.in ${CMAKE_CURRENT_BINARY_DIR}/Info.plist @ONLY)
if (WIN32)
add_library(QIDISlicer SHARED QIDISlicer.cpp QIDISlicer.hpp) set(SLIC3R_CLI_SOURCES
else () QIDISlicer.hpp
add_executable(QIDISlicer QIDISlicer.cpp QIDISlicer.hpp) CLI/CLI.hpp
CLI/CLI_DynamicPrintConfig.hpp
CLI/PrintHelp.cpp
CLI/Setup.cpp
CLI/LoadPrintData.cpp
CLI/ProcessTransform.cpp
CLI/ProcessActions.cpp
CLI/Run.cpp
CLI/ProfilesSharingUtils.cpp
CLI/ProfilesSharingUtils.hpp
)
if (SLIC3R_GUI)
list(APPEND SLIC3R_CLI_SOURCES
CLI/GuiParams.cpp
)
endif () endif ()
if (WIN32)
add_library(QIDISlicer SHARED QIDISlicer.cpp ${SLIC3R_CLI_SOURCES})
else ()
add_executable(QIDISlicer QIDISlicer.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) if (MINGW)
target_link_options(QIDISlicer PUBLIC "-Wl,-allow-multiple-definition") target_link_options(QIDISlicer PUBLIC "-Wl,-allow-multiple-definition")
set_target_properties(QIDISlicer PROPERTIES PREFIX "") set_target_properties(QIDISlicer PROPERTIES PREFIX "")
@@ -111,39 +139,38 @@ if (NOT WIN32 AND NOT APPLE)
set_target_properties(QIDISlicer PROPERTIES OUTPUT_NAME "qidi-slicer") set_target_properties(QIDISlicer PROPERTIES OUTPUT_NAME "qidi-slicer")
endif () endif ()
target_link_libraries(QIDISlicer libslic3r libcereal slic3r-arrange-wrapper) target_link_libraries(QIDISlicer PRIVATE libslic3r libcereal slic3r-arrange-wrapper libseqarrange stb_image)
if (APPLE) if (APPLE)
# add_compile_options(-stdlib=libc++) # add_compile_options(-stdlib=libc++)
# add_definitions(-DBOOST_THREAD_DONT_USE_CHRONO -DBOOST_NO_CXX11_RVALUE_REFERENCES -DBOOST_THREAD_USES_MOVE) # add_definitions(-DBOOST_THREAD_DONT_USE_CHRONO -DBOOST_NO_CXX11_RVALUE_REFERENCES -DBOOST_THREAD_USES_MOVE)
# -liconv: boost links to libiconv by default # -liconv: boost links to libiconv by default
target_link_libraries(QIDISlicer "-liconv -framework IOKit" "-framework CoreFoundation" -lc++) target_link_libraries(QIDISlicer PRIVATE "-liconv -framework IOKit" "-framework CoreFoundation" -lc++)
elseif (MSVC) elseif (MSVC)
# Manifest is provided through QIDISlicer.rc, don't generate your own. # Manifest is provided through QIDISlicer.rc, don't generate your own.
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /MANIFEST:NO") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /MANIFEST:NO")
else () else ()
# Boost on Raspberry-Pi does not link to pthreads explicitely. # Boost on Raspberry-Pi does not link to pthreads explicitely.
target_link_libraries(QIDISlicer ${CMAKE_DL_LIBS} -lstdc++ Threads::Threads) target_link_libraries(QIDISlicer PRIVATE ${CMAKE_DL_LIBS} -lstdc++ Threads::Threads)
endif () endif ()
# Add the Slic3r GUI library, libcurl, OpenGL and GLU libraries. # Add the Slic3r GUI library, libcurl, OpenGL and GLU libraries.
if (SLIC3R_GUI) if (SLIC3R_GUI)
# target_link_libraries(QIDISlicer ws2_32 uxtheme setupapi libslic3r_gui ${wxWidgets_LIBRARIES}) target_link_libraries(QIDISlicer PRIVATE libslic3r_gui)
target_link_libraries(QIDISlicer libslic3r_gui)
if (MSVC) if (MSVC)
# Generate debug symbols even in release mode. # Generate debug symbols even in release mode.
target_link_options(QIDISlicer PUBLIC "$<$<CONFIG:RELEASE>:/DEBUG>") target_link_options(QIDISlicer PUBLIC "$<$<CONFIG:RELEASE>:/DEBUG>")
target_link_libraries(QIDISlicer user32.lib Setupapi.lib) target_link_libraries(QIDISlicer PRIVATE user32.lib Setupapi.lib)
elseif (MINGW) elseif (MINGW)
target_link_libraries(QIDISlicer ws2_32 uxtheme setupapi) target_link_libraries(QIDISlicer PRIVATE ws2_32 uxtheme setupapi)
elseif (APPLE) elseif (APPLE)
target_link_libraries(QIDISlicer "-framework OpenGL") target_link_libraries(QIDISlicer PRIVATE "-framework OpenGL")
else () else ()
target_link_libraries(QIDISlicer -ldl) target_link_libraries(QIDISlicer PRIVATE -ldl)
endif () endif ()
if (WIN32) if (WIN32)
find_library(PSAPI_LIB NAMES Psapi) find_library(PSAPI_LIB NAMES Psapi)
target_link_libraries(QIDISlicer ${PSAPI_LIB}) target_link_libraries(QIDISlicer PRIVATE ${PSAPI_LIB})
endif () endif ()
endif () endif ()

File diff suppressed because it is too large Load Diff

View File

@@ -1,51 +1,9 @@
#ifndef SLIC3R_HPP #ifndef SLIC3R_HPP
#define SLIC3R_HPP #define SLIC3R_HPP
#include "libslic3r/Config.hpp" namespace Slic3r::CLI
#include "libslic3r/Model.hpp" {
namespace Slic3r {
namespace IO {
enum ExportFormat : int {
OBJ,
STL,
// SVG,
TMF,
Gcode
};
}
class CLI {
public:
int run(int argc, char **argv); int run(int argc, char **argv);
private:
DynamicPrintAndCLIConfig m_config;
DynamicPrintConfig m_print_config;
DynamicPrintConfig m_extra_config;
std::vector<std::string> m_input_files;
std::vector<std::string> m_actions;
std::vector<std::string> m_transforms;
std::vector<std::string> m_profiles_sharing;
std::vector<Model> m_models;
bool setup(int argc, char **argv);
/// Prints usage of the CLI.
void print_help(bool include_print_options = false, PrinterTechnology printer_technology = ptAny) const;
/// Exports loaded models to a file of the specified format, according to the options affecting output filename.
bool export_models(IO::ExportFormat format);
bool has_print_action() const { return m_config.opt_bool("export_gcode") || m_config.opt_bool("export_sla"); }
bool processed_profiles_sharing();
bool check_and_load_input_profiles(PrinterTechnology& printer_technology);
std::string output_filepath(const Model &model, IO::ExportFormat format) const;
};
} }
#endif #endif

View File

@@ -0,0 +1,35 @@
find_package(Z3 REQUIRED)
slic3r_remap_configs("z3::libz3" RelWithDebInfo Release)
add_library(libseqarrange STATIC src/seq_interface.cpp src/seq_preprocess.cpp src/seq_sequential.cpp src/seq_utilities.cpp)
target_include_directories(libseqarrange PUBLIC include PRIVATE src )
target_link_libraries(libseqarrange PUBLIC libslic3r PRIVATE z3::libz3)
add_executable(sequential_decimator src/sequential_decimator.cpp)
target_include_directories(sequential_decimator PRIVATE include)
target_link_libraries(sequential_decimator PRIVATE libseqarrange)
if (SLIC3R_BUILD_TESTS)
find_package(Catch2 3.8 REQUIRED)
add_executable(libseqarrange_tests test/qidiparts.cpp test/seq_test_polygon.cpp test/seq_test_sequential.cpp test/seq_test_preprocess.cpp test/seq_test_interface.cpp)
target_include_directories(libseqarrange_tests PRIVATE src )
target_link_libraries(libseqarrange_tests PRIVATE Catch2::Catch2WithMain libseqarrange)
set(_catch_args "exclude:[NotWorking] exclude:[Slow]")
list(APPEND _catch_args "${CATCH_EXTRA_ARGS}")
add_test(NAME libseqarrange_tests
COMMAND libseqarrange_tests ${_catch_args}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
endif()

View File

@@ -0,0 +1,204 @@
#ifndef __SEQ_INTERFACE_HPP__
#define __SEQ_INTERFACE_HPP__
/*----------------------------------------------------------------*/
#include "libslic3r/Polygon.hpp"
#include "libslic3r/Geometry/ConvexHull.hpp"
/*----------------------------------------------------------------*/
using namespace Slic3r;
/*----------------------------------------------------------------*/
namespace Sequential
{
class ObjectTooLargeException : public std::runtime_error { public: explicit ObjectTooLargeException(const std::string& msg) : std::runtime_error(msg) {}};
/*----------------------------------------------------------------*/
struct PrinterGeometry
{
// must be convex; for best performance a rectangle is recommended
Slic3r::Polygon plate;
// at least height 0 (corresponding to nozzle) must be present in convex_heights
std::set<coord_t> convex_heights;
std::set<coord_t> box_heights;
// <height, polygons at given height>, at least one polygon must be present for height 0
std::map<coord_t, std::vector<Slic3r::Polygon> > extruder_slices;
bool convert_Geometry2PlateBounds(Slic3r::BoundingBox &plate_bounding_box, Slic3r::Polygon &plate_bounding_polygon) const;
};
/*----------------------------------------------------------------*/
enum DecimationPrecision
{
SEQ_DECIMATION_PRECISION_UNDEFINED,
SEQ_DECIMATION_PRECISION_LOW,
SEQ_DECIMATION_PRECISION_HIGH
};
/*----------------------------------------------------------------*/
struct SolverConfiguration
{
SolverConfiguration();
SolverConfiguration(const PrinterGeometry &printer_geometry);
void set_DecimationPrecision(DecimationPrecision decimation_precision);
void set_ObjectGroupSize(int object_group_size);
void setup(const PrinterGeometry &printer_geometry);
static double convert_DecimationPrecision2Tolerance(DecimationPrecision decimation_precision);
int bounding_box_size_optimization_step;
int minimum_bounding_box_size;
Slic3r::BoundingBox plate_bounding_box;
Slic3r::Polygon plate_bounding_polygon;
int max_refines;
int object_group_size;
int fixed_object_grouping_limit;
int temporal_spread;
DecimationPrecision decimation_precision;
std::string optimization_timeout;
};
/*----------------------------------------------------------------*/
struct ObjectToPrint
{
int id = 0;
bool glued_to_next = false; /* the next object must be scheduled right after this object */
coord_t total_height = 0;
std::vector<std::pair<coord_t, Slic3r::Polygon>> pgns_at_height;
};
struct ScheduledObject {
ScheduledObject(int _id, coord_t _x, coord_t _y)
: id(_id)
, x(_x)
, y(_y) { /* */ }
int id = 0;
coord_t x, y;
};
struct ScheduledPlate {
std::vector<ScheduledObject> scheduled_objects;
};
/*----------------------------------------------------------------*/
/*
This is the recommended interface for checking sequential printability.
Returns true if objects are sequentially printable according to their
ordering in the input vector and the arrangement on the plate specified
by the schedule. Printable means that the extruder never hits printed
objects during printing. Otherwise returns false.
Please see the corresponding example of usage (seq_test_interface.cpp)
Note: The function always succeeds, does not throw any exception.
*/
bool check_ScheduledObjectsForSequentialPrintability(const SolverConfiguration &solver_configuration,
const PrinterGeometry &printer_geometry,
const std::vector<ObjectToPrint> &objects_to_print,
const std::vector<ScheduledPlate> &scheduled_plates);
/*
This is a variant of the interface for checking sequential printability.
If not sequentially printable returns a pair of object IDs that are in conflict,
that is, when the second object is printed the extruder will collide with the
first object. The returned conflict is not necessarily the first collision to
occur when printing the object according to the given input schedule.
Note: The function always succeeds, does not throw any exception.
*/
std::optional<std::pair<int, int> > check_ScheduledObjectsForSequentialConflict(const SolverConfiguration &solver_configuration,
const PrinterGeometry &printer_geometry,
const std::vector<ObjectToPrint> &objects_to_print,
const std::vector<ScheduledPlate> &scheduled_plates);
/*----------------------------------------------------------------*/
/*
This is the recommended interface for sequential scheduling/arranging.
Please see the corresponding example of usage (seq_test_interface.cpp)
Note: The function should succeed except the case when there is an
object that does not fit on the plate and in the case when the solver
is unable to scedule even single object on the plate. The latter case
is detected by timeout and should not normally happen. These failures
are reported via exceptions.
The trans_bed_glue parameter should be set to false when scheduling
all objects. If only objects on a separate bed are scheduled, then
trans_bed_glue should be set to true when there is an object on the
previous bed that is temporally glued to the first scheduled object.
In such a case, the first object will be scheduled as first temporally.
*/
std::vector<ScheduledPlate> schedule_ObjectsForSequentialPrint(const SolverConfiguration &solver_configuration,
const PrinterGeometry &printer_geometry,
const std::vector<ObjectToPrint> &objects_to_print,
std::function<void(int)> progress_callback = [](int progress){});
void schedule_ObjectsForSequentialPrint(const SolverConfiguration &solver_configuration,
const PrinterGeometry &printer_geometry,
const std::vector<ObjectToPrint> &objects_to_print,
std::vector<ScheduledPlate> &scheduled_plates,
std::function<void(int)> progress_callback = [](int progress){});
/*----------------------------------------------------------------*/
/*
The following interface is for more internal use.
*/
int schedule_ObjectsForSequentialPrint(const SolverConfiguration &solver_configuration,
const std::vector<ObjectToPrint> &objects_to_print,
std::vector<ScheduledPlate> &scheduled_plates,
std::function<void(int)> progress_callback = [](int progress){});
void setup_ExtruderUnreachableZones(const SolverConfiguration &solver_configuration,
std::vector<std::vector<Slic3r::Polygon> > &convex_unreachable_zones,
std::vector<std::vector<Slic3r::Polygon> > &box_unreachable_zones);
int schedule_ObjectsForSequentialPrint(const SolverConfiguration &solver_configuration,
const std::vector<ObjectToPrint> &objects_to_print,
const std::vector<std::vector<Slic3r::Polygon> > &convex_unreachable_zones,
const std::vector<std::vector<Slic3r::Polygon> > &box_unreachable_zones,
std::vector<ScheduledPlate> &scheduled_plates,
std::function<void(int)> progress_callback = [](int progress){});
/*----------------------------------------------------------------*/
} // namespace Sequential
/*----------------------------------------------------------------*/
#endif /* __SEQ_INTERFACE_HPP__ */

View File

@@ -0,0 +1,73 @@
/*================================================================*/
/*
* Author: Pavel Surynek, 2023 - 2025
*
* File: seq_defs.h
*
* Definitions of useful macros.
*/
/*================================================================*/
#ifndef __SEQ_DEFS_HPP__
#define __SEQ_DEFS_HPP__
/*----------------------------------------------------------------*/
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <vector>
#include <set>
/*----------------------------------------------------------------*/
using namespace std;
#define SEQ_UNUSED(x)
//#define DEBUG
//#define PROFILE
typedef wchar_t wchar;
typedef std::basic_string<char> string;
typedef std::vector<string> strings_vector;
typedef std::set<string> strings_set;
/*----------------------------------------------------------------*/
extern const string INDENT;
/*----------------------------------------------------------------*/
#define MIN(x,y) (((x) < (y)) ? (x) : (y))
#define MAX(x,y) (((x) > (y)) ? (x) : (y))
#define DFR(x,y) (((x) < (y)) ? ((y) - (x)) : ((x) - (y)))
#define ABS(x) (((x) < 0) ? -(x) : (x))
#define SGN(x) (((x) < 0) ? -(-1) : ((x) > 0) ? 1 : 0)
/*----------------------------------------------------------------*/
#ifdef DEBUG
#define ASSERT(condition) \
{ \
if (!(condition)) \
{ \
printf("ASSERT: assertion failed (file: %s, line:%d).\n", __FILE__, __LINE__); \
fflush(NULL); \
exit(-1); \
} \
}
#else
#define ASSERT(condition)
#endif /* DEBUG */
/*----------------------------------------------------------------*/
#endif /* __SEQ_DEFS_HPP__ */

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,223 @@
#ifndef __SEQ_PREPROCESS_HPP__
#define __SEQ_PREPROCESS_HPP__
/*----------------------------------------------------------------*/
#include "seq_sequential.hpp"
/*----------------------------------------------------------------*/
namespace Sequential
{
/*----------------------------------------------------------------*/
const coord_t SEQ_SLICER_SCALE_FACTOR = 100000;
const double SEQ_POLYGON_DECIMATION_GROW_FACTOR = 1.005;
/*----------------------------------------------------------------*/
struct ObjectToPrint;
/*----------------------------------------------------------------*/
extern const std::vector<Slic3r::Polygon> SEQ_UNREACHABLE_POLYGON_NOZZLE_LEVEL_MK3S;
extern const std::vector<Slic3r::Polygon> SEQ_UNREACHABLE_POLYGON_EXTRUDER_LEVEL_MK3S;
extern const std::vector<Slic3r::Polygon> SEQ_UNREACHABLE_POLYGON_HOSE_LEVEL_MK3S;
extern const std::vector<Slic3r::Polygon> SEQ_UNREACHABLE_POLYGON_GANTRY_LEVEL_MK3S;
extern const std::vector<std::vector<Slic3r::Polygon> > SEQ_UNREACHABLE_POLYGON_ALL_LEVELS_MK3S;
extern const std::vector<std::vector<Slic3r::Polygon> > SEQ_UNREACHABLE_POLYGON_CONVEX_LEVELS_MK3S;
extern const std::vector<std::vector<Slic3r::Polygon> > SEQ_UNREACHABLE_POLYGON_BOX_LEVELS_MK3S;
extern const std::vector<Slic3r::Polygon> SEQ_UNREACHABLE_POLYGON_NOZZLE_LEVEL_MK4;
extern const std::vector<Slic3r::Polygon> SEQ_UNREACHABLE_POLYGON_EXTRUDER_LEVEL_MK4;
extern const std::vector<Slic3r::Polygon> SEQ_UNREACHABLE_POLYGON_HOSE_LEVEL_MK4;
extern const std::vector<Slic3r::Polygon> SEQ_UNREACHABLE_POLYGON_GANTRY_LEVEL_MK4;
extern const std::vector<std::vector<Slic3r::Polygon> > SEQ_UNREACHABLE_POLYGON_ALL_LEVELS_MK4;
extern const std::vector<std::vector<Slic3r::Polygon> > SEQ_UNREACHABLE_POLYGON_CONVEX_LEVELS_MK4;
extern const std::vector<std::vector<Slic3r::Polygon> > SEQ_UNREACHABLE_POLYGON_BOX_LEVELS_MK4;
extern const std::vector<Slic3r::Polygon> SEQ_UNREACHABLE_POLYGON_NOZZLE_LEVEL_XL;
extern const std::vector<Slic3r::Polygon> SEQ_UNREACHABLE_POLYGON_EXTRUDER_LEVEL_XL;
extern const std::vector<Slic3r::Polygon> SEQ_UNREACHABLE_POLYGON_HOSE_LEVEL_XL;
extern const std::vector<Slic3r::Polygon> SEQ_UNREACHABLE_POLYGON_GANTRY_LEVEL_XL;
extern const std::vector<std::vector<Slic3r::Polygon> > SEQ_UNREACHABLE_POLYGON_ALL_LEVELS_XL;
extern const std::vector<std::vector<Slic3r::Polygon> > SEQ_UNREACHABLE_POLYGON_CONVEX_LEVELS_XL;
extern const std::vector<std::vector<Slic3r::Polygon> > SEQ_UNREACHABLE_POLYGON_BOX_LEVELS_XL;
/*----------------------------------------------------------------*/
Rational scaleDown_CoordinateForSequentialSolver(coord_t x);
void scaleDown_PolygonForSequentialSolver(const Slic3r::Polygon &polygon,
Slic3r::Polygon &scaled_polygon);
void scaleDown_PolygonForSequentialSolver(coord_t scale_factor,
const Slic3r::Polygon &polygon,
Slic3r::Polygon &scaled_polygon);
Slic3r::Polygon scaleDown_PolygonForSequentialSolver(coord_t scale_factor, const Slic3r::Polygon &polygon);
void scaleUp_PositionForSlicer(const Rational &position_X,
const Rational &position_Y,
coord_t &scaled_position_X,
coord_t &scaled_position_Y);
void scaleUp_PositionForSlicer(coord_t scale_factor,
const Rational &position_X,
const Rational &position_Y,
coord_t &scaled_position_X,
coord_t &scaled_position_Y);
void scaleUp_PositionForSlicer(double position_X,
double position_Y,
coord_t &scaled_position_X,
coord_t &scaled_position_Y);
void scaleUp_PositionForSlicer(coord_t scale_factor,
double position_X,
double position_Y,
coord_t &scaled_position_X,
coord_t &scaled_position_Y);
Slic3r::Polygon scaleUp_PolygonForSlicer(const Slic3r::Polygon &polygon);
Slic3r::Polygon scaleUp_PolygonForSlicer(coord_t scale_factor, const Slic3r::Polygon &polygon);
Slic3r::Polygon scaleUp_PolygonForSlicer(const Slic3r::Polygon &polygon, double x_pos, double y_pos);
Slic3r::Polygon scaleUp_PolygonForSlicer(coord_t scale_factor, const Slic3r::Polygon &polygon, double x_pos, double y_pos);
void ground_PolygonByBoundingBox(Slic3r::Polygon &polygon);
void ground_PolygonByFirstPoint(Slic3r::Polygon &polygon);
void shift_Polygon(Slic3r::Polygon &polygon, coord_t x_offset, coord_t y_offset);
void shift_Polygon(Slic3r::Polygon &polygon, const Slic3r::Point &offset);
/*----------------------------------------------------------------*/
Slic3r::Polygon transform_UpsideDown(const SolverConfiguration &solver_configuration, const Slic3r::Polygon &polygon);
Slic3r::Polygon transform_UpsideDown(const SolverConfiguration &solver_configuration, coord_t scale_factor, const Slic3r::Polygon &polygon);
void transform_UpsideDown(const SolverConfiguration &solver_configuration,
const coord_t &scaled_x_pos,
const coord_t &scaled_y_pos,
coord_t &transformed_x_pos,
coord_t &transformed_y_pos);
void transform_UpsideDown(const SolverConfiguration &solver_configuration,
coord_t scale_factor,
const coord_t &scaled_x_pos,
const coord_t &scaled_y_pos,
coord_t &transformed_x_pos,
coord_t &transformed_y_pos);
/*----------------------------------------------------------------*/
void grow_PolygonForContainedness(coord_t center_x, coord_t center_y, Slic3r::Polygon &polygon);
void decimate_PolygonForSequentialSolver(const SolverConfiguration &solver_configuration,
const Slic3r::Polygon &polygon,
Slic3r::Polygon &scale_down_polygon,
bool extra_safety);
void decimate_PolygonForSequentialSolver(double DP_tolerance,
const Slic3r::Polygon &polygon,
Slic3r::Polygon &decimated_polygon,
bool extra_safety);
void extend_PolygonConvexUnreachableZone(const SolverConfiguration &solver_configuration,
const Slic3r::Polygon &polygon,
const std::vector<Slic3r::Polygon> &extruder_polygons,
std::vector<Slic3r::Polygon> &unreachable_polygons);
void extend_PolygonBoxUnreachableZone(const SolverConfiguration &solver_configuration,
const Slic3r::Polygon &polygon,
const std::vector<Slic3r::Polygon> &extruder_polygons,
std::vector<Slic3r::Polygon> &unreachable_polygons);
void extend_PolygonBoxUnreachableZone(const SolverConfiguration &solver_configuration,
const Slic3r::Polygon &polygon,
const std::vector<Slic3r::Polygon> &extruder_polygons,
std::vector<Slic3r::Polygon> &unreachable_polygons);
void prepare_ExtruderPolygons(const SolverConfiguration &solver_configuration,
const PrinterGeometry &printer_geometry,
const ObjectToPrint &object_to_print,
std::vector<Slic3r::Polygon> &convex_level_polygons,
std::vector<Slic3r::Polygon> &box_level_polygons,
std::vector<std::vector<Slic3r::Polygon> > &extruder_convex_level_polygons,
std::vector<std::vector<Slic3r::Polygon> > &extruder_box_level_polygons,
bool extra_safety);
void prepare_ObjectPolygons(const SolverConfiguration &solver_configuration,
const std::vector<Slic3r::Polygon> &convex_level_polygons,
const std::vector<Slic3r::Polygon> &box_level_polygons,
const std::vector<std::vector<Slic3r::Polygon> > &extruder_convex_level_polygons,
const std::vector<std::vector<Slic3r::Polygon> > &extruder_box_level_polygons,
Slic3r::Polygon &object_polygon,
std::vector<Slic3r::Polygon> &unreachable_polygons);
void prepare_UnreachableZonePolygons(const SolverConfiguration &solver_configuration,
const Slic3r::Polygon &polygon,
const std::vector<std::vector<Slic3r::Polygon> > &extruder_convex_level_polygons,
const std::vector<std::vector<Slic3r::Polygon> > &extruder_box_level_polygons,
std::vector<Slic3r::Polygon> &unreachable_polygons);
void prepare_UnreachableZonePolygons(const SolverConfiguration &solver_configuration,
const std::vector<Slic3r::Polygon> &convex_level_polygons,
const std::vector<Slic3r::Polygon> &box_level_polygons,
const std::vector<std::vector<Slic3r::Polygon> > &extruder_convex_level_polygons,
const std::vector<std::vector<Slic3r::Polygon> > &extruder_box_level_polygons,
std::vector<Slic3r::Polygon> &unreachable_polygons);
bool check_PolygonSizeFitToPlate(const SolverConfiguration &solver_configuration, const Slic3r::Polygon &polygon);
bool check_PolygonPositionWithinPlate(const SolverConfiguration &solver_configuration, coord_t x, coord_t y, const Slic3r::Polygon &polygon);
bool check_PolygonSizeFitToPlate(const SolverConfiguration &solver_configuration, coord_t scale_factor, const Slic3r::Polygon &polygon);
bool check_PolygonPositionWithinPlate(const SolverConfiguration &solver_configuration, coord_t scale_factor, coord_t x, coord_t y, const Slic3r::Polygon &polygon);
/*----------------------------------------------------------------*/
bool check_PolygonConsumation(const std::vector<Slic3r::Polygon> &polygons, const std::vector<Slic3r::Polygon> &consumer_polygons);
std::vector<std::vector<Slic3r::Polygon> > simplify_UnreachableZonePolygons(const std::vector<std::vector<Slic3r::Polygon> > &unreachable_polygons);
void glue_LowObjects(std::vector<SolvableObject> &solvable_ojects);
/*----------------------------------------------------------------*/
double calc_PolygonArea(const Slic3r::Polygon &polygon);
double calc_PolygonUnreachableZoneArea(const std::vector<Slic3r::Polygon> &unreachable_polygons);
double calc_PolygonUnreachableZoneArea(const Slic3r::Polygon &polygon,
const std::vector<Slic3r::Polygon> &unreachable_polygons);
double calc_PolygonArea(const std::vector<Slic3r::Polygon> &polygons);
double calc_PolygonArea(const std::vector<int> &fixed,
const std::vector<int> &undecided,
const std::vector<Slic3r::Polygon> &polygons);
double calc_PolygonUnreachableZoneArea(const std::vector<Slic3r::Polygon> &polygons,
const std::vector<std::vector<Slic3r::Polygon> > &unreachable_polygons);
/*----------------------------------------------------------------*/
} // namespace Sequential
/*----------------------------------------------------------------*/
#endif /* __SEQ_PREPROCESS_HPP__ */

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,235 @@
#include <sstream>
#include <iostream>
#include <fstream>
#include "seq_defs.hpp"
#include "libslic3r/Geometry.hpp"
#include "libslic3r/ClipperUtils.hpp"
#include "seq_utilities.hpp"
#include "seq_preprocess.hpp"
/*----------------------------------------------------------------*/
using namespace std;
using namespace Slic3r;
/*----------------------------------------------------------------*/
namespace Sequential
{
/*----------------------------------------------------------------*/
bool find_and_remove(std::string &src, const std::string &key)
{
size_t pos = src.find(key);
if (pos != std::string::npos)
{
src.erase(pos, key.length());
return true;
}
return false;
}
std::vector<ObjectToPrint> load_exported_data_from_file(const std::string &filename)
{
std::ifstream in(filename);
if (!in)
{
throw std::runtime_error("NO EXPORTED FILE WAS FOUND");
}
return load_exported_data_from_stream(in);
}
std::vector<ObjectToPrint> load_exported_data_from_text(const std::string &data_text)
{
std::istringstream iss(data_text);
return load_exported_data_from_stream(iss);
}
std::vector<ObjectToPrint> load_exported_data_from_stream(std::istream &data_stream)
{
std::vector<ObjectToPrint> objects_to_print;
std::string line;
while (data_stream)
{
std::getline(data_stream, line);
if (find_and_remove(line, "OBJECT_ID")) {
objects_to_print.push_back(ObjectToPrint());
objects_to_print.back().id = std::stoi(line);
}
if (find_and_remove(line, "TOTAL_HEIGHT"))
{
objects_to_print.back().total_height = std::stoi(line);
}
if (find_and_remove(line, "POLYGON_AT_HEIGHT"))
{
objects_to_print.back().pgns_at_height.emplace_back(std::make_pair(std::stoi(line), Polygon()));
}
if (find_and_remove(line, "POINT"))
{
std::stringstream ss(line);
std::string val;
ss >> val;
Point pt(std::stoi(val), 0);
ss >> val;
pt.y() = std::stoi(val);
objects_to_print.back().pgns_at_height.back().second.append(pt);
}
}
return objects_to_print;
}
int load_printer_geometry_from_file(const std::string &filename, PrinterGeometry &printer_geometry)
{
std::ifstream in(filename);
if (!in)
{
throw std::runtime_error("NO PRINTER GEOMETRY FILE WAS FOUND");
}
return load_printer_geometry_from_stream(in, printer_geometry);
}
int load_printer_geometry_from_text(const std::string &geometry_text, PrinterGeometry &printer_geometry)
{
std::istringstream iss(geometry_text);
return load_printer_geometry_from_stream(iss, printer_geometry);
}
int load_printer_geometry_from_stream(std::istream &geometry_stream, PrinterGeometry &printer_geometry)
{
Polygon *current_polygon = NULL;
std::string line;
coord_t x_size = -1;
coord_t y_size = -1;
while (geometry_stream)
{
std::getline(geometry_stream, line);
if (find_and_remove(line, "POLYGON_AT_HEIGHT"))
{
coord_t height = std::stoi(line);
std::map<coord_t, std::vector<Polygon> >::iterator extruder_slice = printer_geometry.extruder_slices.find(height);
if (extruder_slice != printer_geometry.extruder_slices.end())
{
extruder_slice->second.push_back(Polygon());
current_polygon = &extruder_slice->second.back();
}
else
{
vector<Polygon> polygons;
polygons.push_back(Polygon());
current_polygon = &printer_geometry.extruder_slices.insert(std::pair(height, polygons)).first->second.back();
}
}
else if (find_and_remove(line, "POINT"))
{
std::stringstream ss(line);
std::string val;
ss >> val;
Point pt(std::stoi(val), 0);
ss >> val;
pt.y() = std::stoi(val);
assert(current_polygon != NULL);
current_polygon->append(pt);
}
else if (find_and_remove(line, "CONVEX_HEIGHT"))
{
std::stringstream ss(line);
std::string val;
ss >> val;
coord_t height = std::stoi(val);
printer_geometry.convex_heights.insert(height);
}
else if (find_and_remove(line, "BOX_HEIGHT"))
{
std::stringstream ss(line);
std::string val;
ss >> val;
coord_t height = std::stoi(val);
printer_geometry.box_heights.insert(height);
}
else if (find_and_remove(line, "X_SIZE"))
{
std::stringstream ss(line);
std::string val;
ss >> val;
x_size = std::stoi(val);
}
else if (find_and_remove(line, "Y_SIZE"))
{
std::stringstream ss(line);
std::string val;
ss >> val;
y_size = std::stoi(val);
}
}
assert(x_size > 0 && y_size > 0);
printer_geometry.plate = { {0, 0}, {x_size, 0}, {x_size, y_size}, {0, y_size} };
return 0;
}
void save_import_data_to_file(const std::string &filename,
const std::map<double, int> &scheduled_polygons,
const map<int, int> &original_index_map,
const vector<Rational> &poly_positions_X,
const vector<Rational> &poly_positions_Y)
{
std::ofstream out(filename);
if (!out)
{
throw std::runtime_error("CANNOT CREATE IMPORT FILE");
}
for (const auto& scheduled_polygon: scheduled_polygons)
{
coord_t X, Y;
scaleUp_PositionForSlicer(poly_positions_X[scheduled_polygon.second],
poly_positions_Y[scheduled_polygon.second],
X,
Y);
const auto& original_index = original_index_map.find(scheduled_polygon.second);
out << original_index->second << " " << X << " " << Y << endl;
}
}
/*----------------------------------------------------------------*/
} // namespace Sequential

View File

@@ -0,0 +1,41 @@
#ifndef __SEQ_UTILITIES_HPP__
#define __SEQ_UTILITIES_HPP__
/*----------------------------------------------------------------*/
#include "seq_sequential.hpp"
#include "libseqarrange/seq_interface.hpp"
/*----------------------------------------------------------------*/
namespace Sequential
{
bool find_and_remove(std::string &src, const std::string &key);
std::vector<ObjectToPrint> load_exported_data_from_file(const std::string &filename);
std::vector<ObjectToPrint> load_exported_data_from_text(const std::string &data_text);
std::vector<ObjectToPrint> load_exported_data_from_stream(std::istream &data_stream);
int load_printer_geometry_from_file(const std::string& filename, PrinterGeometry &printer_geometry);
int load_printer_geometry_from_text(const std::string& geometry_text, PrinterGeometry &printer_geometry);
int load_printer_geometry_from_stream(std::istream& geometry_stream, PrinterGeometry &printer_geometry);
void save_import_data_to_file(const std::string &filename,
const std::map<double, int> &scheduled_polygons,
const map<int, int> &original_index_map,
const vector<Rational> &poly_positions_X,
const vector<Rational> &poly_positions_Y);
/*----------------------------------------------------------------*/
} // namespace Sequential
/*----------------------------------------------------------------*/
#endif /* __SEQ_UTILITIES_HPP__ */

View File

@@ -0,0 +1,415 @@
#include <sstream>
#include <iostream>
#include <fstream>
#include "libslic3r/Polygon.hpp"
#include "libslic3r/Geometry/ConvexHull.hpp"
#include "libslic3r/SVG.hpp"
#include "seq_utilities.hpp"
#include "sequential_decimator.hpp"
/*----------------------------------------------------------------*/
using namespace Slic3r;
using namespace Sequential;
/*----------------------------------------------------------------*/
#define SCALE_FACTOR 50000.0
/*----------------------------------------------------------------*/
//const Point polygon_offset_0(28000000, -16000000); // body
//const Point polygon_offset_1(-3000000, -60000000); // hose
//const Point polygon_offset_2(28000000, -16000000); // fan
//const Point polygon_offset_3(0,-24000000); // gantry
//const Point polygon_offset_4(0,0); // nozzle
const int SEQ_QIDI_MK3S_X_SIZE = 2500;
const int SEQ_QIDI_MK3S_Y_SIZE = 2100;
/*----------------------------------------------------------------*/
void print_IntroductoryMessage(void)
{
printf("----------------------------------------------------------------\n");
printf("Polygon decimation utility\n");
printf("(C) 2024 QIDI Tech \n");
printf("================================================================\n");
}
void print_ConcludingMessage(void)
{
printf("----------------------------------------------------------------\n");
}
void print_Help(void)
{
printf("Usage:\n");
printf("sequential_decimator [--input-file=<string>]\n");
printf(" [--output-file=<string>]\n");
printf(" [--tolerance=<double>]\n");
printf(" [--x-pos=<double> (in mm)]\n");
printf(" [--y-pos=<double> (in mm)]\n");
printf(" [--x-nozzle=<int> (in coord_t)]\n");
printf(" [--y-nozzle=<int> (in coord_t)]\n");
printf(" [--help]\n");
printf("\n");
printf("\n");
printf("Defaults: --input-file=arrange_data_export.txt\n");
printf(" --output-file=arrange_data_import.txt\n");
printf(" --x-pos='random'\n");
printf(" --y-pos='random'\n");
printf(" --x-nozzle=0\n");
printf(" --y-nozzle=0\n");
printf(" --tolerance=400000 \n");
printf("\n");
}
int parse_CommandLineParameter(const string &parameter, CommandParameters &command_parameters)
{
if (parameter.find("--input-file=") == 0)
{
command_parameters.input_filename = parameter.substr(13, parameter.size());
}
else if (parameter.find("--output-file=") == 0)
{
command_parameters.output_filename = parameter.substr(14, parameter.size());
}
else if (parameter.find("--tolerance=") == 0)
{
command_parameters.tolerance = std::atof(parameter.substr(12, parameter.size()).c_str());
}
else if (parameter.find("--x-pos=") == 0)
{
command_parameters.x_position = std::atof(parameter.substr(8, parameter.size()).c_str());
command_parameters.random_position = false;
}
else if (parameter.find("--y-pos=") == 0)
{
command_parameters.y_position = std::atof(parameter.substr(8, parameter.size()).c_str());
command_parameters.random_position = false;
}
else if (parameter.find("--x-nozzle=") == 0)
{
command_parameters.x_nozzle = std::atoi(parameter.substr(11, parameter.size()).c_str());
}
else if (parameter.find("--y-nozzle=") == 0)
{
command_parameters.y_nozzle = std::atoi(parameter.substr(11, parameter.size()).c_str());
}
else if (parameter.find("--help") == 0)
{
command_parameters.help = true;
}
else
{
return -1;
}
return 0;
}
void save_DecimatedPolygons(const CommandParameters &command_parameters,
const std::vector<Slic3r::Polygon> &decimated_polygons)
{
std::ofstream out(command_parameters.output_filename);
if (!out)
throw std::runtime_error("CANNOT CREATE OUTPUT FILE");
Point nozzle_offset(-command_parameters.x_nozzle, -command_parameters.y_nozzle);
for (unsigned int i = 0; i < decimated_polygons.size(); ++i)
{
out << "[" << i << "]" << endl;
out << "{" << endl;
Slic3r::Polygon shift_polygon = scaleUp_PolygonForSlicer(1,
decimated_polygons[i],
(command_parameters.x_position * SEQ_SLICER_SCALE_FACTOR) * 10,
(command_parameters.y_position * SEQ_SLICER_SCALE_FACTOR) * 10);
shift_Polygon(shift_polygon, nozzle_offset);
for (const auto& point: shift_polygon.points)
{
out << " { " << point.x() << ", " << point.y() << "}," << endl;
}
out << "}" << endl;
}
}
int decimate_Polygons(const CommandParameters &command_parameters)
{
clock_t start, finish;
printf("Decimation ...\n");
start = clock();
SolverConfiguration solver_configuration;
std::vector<ObjectToPrint> objects_to_print = load_exported_data_from_file(command_parameters.input_filename);
std::vector<Slic3r::Polygon> decimated_polygons;
std::vector<std::vector<Slic3r::Polygon> > unreachable_polygons;
printf(" Decimating objects (polygons) ...\n");
for (unsigned int i = 0; i < objects_to_print.size(); ++i)
{
for (unsigned int j = 0; j < objects_to_print[i].pgns_at_height.size(); ++j)
{
//coord_t height = objects_to_print[i].pgns_at_height[j].first;
if (!objects_to_print[i].pgns_at_height[j].second.points.empty())
{
Polygon decimated_polygon;
//ground_PolygonByFirstPoint(objects_to_print[i].pgns_at_height[j].second);
decimate_PolygonForSequentialSolver(command_parameters.tolerance,
objects_to_print[i].pgns_at_height[j].second,
decimated_polygon,
false);
decimated_polygons.push_back(decimated_polygon);
}
}
}
printf(" Decimating objects (polygons) ... finished\n");
Point nozzle_offset(-command_parameters.x_nozzle, -command_parameters.y_nozzle);
for (unsigned int i = 0; i < decimated_polygons.size(); ++i)
{
printf(" [%d]\n", i);
Slic3r::Polygon shift_polygon = decimated_polygons[i];
shift_Polygon(shift_polygon, nozzle_offset);
shift_polygon = scaleUp_PolygonForSlicer(1,
shift_polygon,
(command_parameters.x_position * SEQ_SLICER_SCALE_FACTOR) * 10,
(command_parameters.y_position * SEQ_SLICER_SCALE_FACTOR) * 10);
for (const auto &point: shift_polygon.points)
{
cout << " " << point.x() << " " << point.y() << endl;
}
BoundingBox bounding_box = get_extents(shift_polygon);
cout << " BB" << endl;
cout << " " << bounding_box.min.x() << " " << bounding_box.min.y() << endl;
cout << " " << bounding_box.max.x() << " " << bounding_box.max.y() << endl;
cout << endl;
}
if (command_parameters.output_filename != "")
{
save_DecimatedPolygons(command_parameters, decimated_polygons);
}
string svg_filename = "sequential_decimator.svg";
SVG preview_svg(svg_filename);
solver_configuration.plate_bounding_box = BoundingBox({0,0}, {SEQ_QIDI_MK3S_X_SIZE, SEQ_QIDI_MK3S_Y_SIZE});
printf(" Generating output SVG ...\n");
for (unsigned int i = 0; i < decimated_polygons.size(); ++i)
{
Polygon transformed_polygon;
Polygon shift_polygon = decimated_polygons[i];
shift_Polygon(shift_polygon, nozzle_offset);
if (command_parameters.random_position)
{
transformed_polygon = transform_UpsideDown(solver_configuration,
scaleUp_PolygonForSlicer(1,
shift_polygon,
(solver_configuration.plate_bounding_box.min.x() + rand() % (solver_configuration.plate_bounding_box.max.x() - solver_configuration.plate_bounding_box.min.x())) * SEQ_SLICER_SCALE_FACTOR,
(solver_configuration.plate_bounding_box.min.y() + rand() % (solver_configuration.plate_bounding_box.max.y() - solver_configuration.plate_bounding_box.min.y()) * SEQ_SLICER_SCALE_FACTOR)));
}
else
{
transformed_polygon = transform_UpsideDown(solver_configuration,
scaleUp_PolygonForSlicer(1,
shift_polygon,
(command_parameters.x_position * SEQ_SLICER_SCALE_FACTOR) * 10,
(command_parameters.y_position * SEQ_SLICER_SCALE_FACTOR) * 10));
}
Polygon display_polygon = scaleDown_PolygonForSequentialSolver(2, transformed_polygon);
string color;
switch(i % 16)
{
case 0:
{
color = "green";
break;
}
case 1:
{
color = "blue";
break;
}
case 2:
{
color = "red";
break;
}
case 3:
{
color = "grey";
break;
}
case 4:
{
color = "cyan";
break;
}
case 5:
{
color = "magenta";
break;
}
case 6:
{
color = "yellow";
break;
}
case 7:
{
color = "black";
break;
}
case 8:
{
color = "indigo";
break;
}
case 9:
{
color = "olive";
break;
}
case 10:
{
color = "firebrick";
break;
}
case 11:
{
color = "violet";
break;
}
case 12:
{
color = "midnightblue";
break;
}
case 13:
{
color = "khaki";
break;
}
case 14:
{
color = "darkslategrey";
break;
}
case 15:
{
color = "hotpink";
break;
}
default:
{
break;
}
}
preview_svg.draw(display_polygon, color);
}
// general plate polygons are currently not supported
assert(solver_configuration.plate_bounding_polygon.points.size() == 0);
Polygon bed_polygon({ { solver_configuration.plate_bounding_box.min.x(), solver_configuration.plate_bounding_box.min.y() },
{ solver_configuration.plate_bounding_box.max.x(), solver_configuration.plate_bounding_box.min.y() },
{ solver_configuration.plate_bounding_box.max.x(), solver_configuration.plate_bounding_box.max.y() },
{ solver_configuration.plate_bounding_box.min.x(), solver_configuration.plate_bounding_box.max.y() } });
Polygon display_bed_polygon = scaleUp_PolygonForSlicer(SEQ_SVG_SCALE_FACTOR,
bed_polygon,
0,
0);
preview_svg.draw_outline(display_bed_polygon, "black");
preview_svg.Close();
printf(" Generating output SVG ... finised\n");
finish = clock();
printf("Decimation ... finished\n");
printf("Total CPU time: %.3f\n", (finish - start) / (double)CLOCKS_PER_SEC);
return 0;
}
/*----------------------------------------------------------------------------*/
// main program
int main(int argc, char **argv)
{
int result;
CommandParameters command_parameters;
print_IntroductoryMessage();
if (argc >= 1 && argc <= 10)
{
for (int i = 1; i < argc; ++i)
{
result = parse_CommandLineParameter(argv[i], command_parameters);
if (result < 0)
{
printf("Error: Cannot parse command line parameters (code = %d).\n", result);
print_Help();
return result;
}
}
if (command_parameters.help)
{
print_Help();
}
else
{
result = decimate_Polygons(command_parameters);
if (result < 0)
{
return result;
}
}
}
else
{
print_Help();
}
print_ConcludingMessage();
return 0;
}

View File

@@ -0,0 +1,62 @@
#ifndef __SEQUENTIAL_DECIMATOR_HPP__
#define __SEQUENTIAL_DECIMATOR_HPP__
/*----------------------------------------------------------------*/
#include "seq_sequential.hpp"
#include "seq_preprocess.hpp"
#include "libseqarrange/seq_interface.hpp"
/*----------------------------------------------------------------*/
const double SEQ_DECIMATION_TOLERANCE = 400000.0;
/*----------------------------------------------------------------*/
struct CommandParameters
{
CommandParameters()
: tolerance(SEQ_DECIMATION_TOLERANCE)
, input_filename("arrange_data_export.txt")
, output_filename("arrange_data_import.txt")
, x_position(0)
, y_position(0)
, random_position(true)
, help(false)
, x_nozzle(0)
, y_nozzle(0)
{
/* nothing */
}
double tolerance;
string input_filename;
string output_filename;
double x_position;
double y_position;
bool random_position;
coord_t x_nozzle;
coord_t y_nozzle;
bool help;
};
/*----------------------------------------------------------------------------*/
void print_IntroductoryMessage(void);
void print_ConcludingMessage(void);
void print_Help(void);
int parse_CommandLineParameter(const string &parameter, CommandParameters &parameters);
int decimate_Polygons(const CommandParameters &command_parameters);
/*----------------------------------------------------------------*/
#endif /* __SEQUENTIAL_DECIMATOR_HPP__ */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,14 @@
#ifndef QIDIPARTS_H
#define QIDIPARTS_H
#include <vector>
#include "libslic3r/ExPolygon.hpp"
using TestData = std::vector<Slic3r::Polygon>;
using TestDataEx = std::vector<Slic3r::ExPolygons>;
extern const TestData QIDI_PART_POLYGONS;
extern const TestData QIDI_STEGOSAUR_POLYGONS;
extern const TestDataEx QIDI_PART_POLYGONS_EX;
#endif // QIDIPARTS_H

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,7 @@
#ifndef __SEQ_TEST_INTERFACE_HPP__
#define __SEQ_TEST_INTERFACE_HPP__
/*----------------------------------------------------------------*/
#endif /* __SEQ_TEST_PREPROCESS_HPP__ */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,7 @@
#ifndef __SEQ_TEST_POLYGON_HPP__
#define __SEQ_TEST_POLYGON_HPP__
/*----------------------------------------------------------------*/
#endif /* __SEQ_TEST_POLYGON_HPP__ */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,7 @@
#ifndef __SEQ_TEST_PREPROCESS_HPP__
#define __SEQ_TEST_PREPROCESS_HPP__
/*----------------------------------------------------------------*/
#endif /* __SEQ_TEST_PREPROCESS_HPP__ */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,7 @@
#ifndef __SEQ_TEST_SEQUENTIAL_HPP__
#define __SEQ_TEST_SEQUENTIAL_HPP__
/*----------------------------------------------------------------*/
#endif /* __SEQ_TEST_SEQUENTIAL_HPP__ */

View File

@@ -1,7 +1,7 @@
project(slic3r-arrange-wrapper) project(slic3r-arrange-wrapper)
cmake_minimum_required(VERSION 3.13) cmake_minimum_required(VERSION 3.13)
add_library(slic3r-arrange-wrapper add_library(slic3r-arrange-wrapper STATIC
include/arrange-wrapper/Arrange.hpp include/arrange-wrapper/Arrange.hpp
include/arrange-wrapper/ArrangeSettingsDb_AppCfg.hpp include/arrange-wrapper/ArrangeSettingsDb_AppCfg.hpp
include/arrange-wrapper/ArrangeSettingsView.hpp include/arrange-wrapper/ArrangeSettingsView.hpp

View File

@@ -1,7 +1,7 @@
project(slic3r-arrange) project(slic3r-arrange)
cmake_minimum_required(VERSION 3.13) cmake_minimum_required(VERSION 3.13)
add_library(slic3r-arrange add_library(slic3r-arrange STATIC
include/arrange/Beds.hpp include/arrange/Beds.hpp
include/arrange/ArrangeItemTraits.hpp include/arrange/ArrangeItemTraits.hpp
include/arrange/PackingContext.hpp include/arrange/PackingContext.hpp

View File

@@ -67,7 +67,7 @@ template<class T, class TT = T> using WritableDataStoreOnly = std::enable_if_t<I
template<class T, class ArrItem> template<class T, class ArrItem>
void set_data(ArrItem &itm, const std::string &key, T &&data) void set_data(ArrItem &itm, const std::string &key, T &&data)
{ {
WritableDataStoreTraits<ArrItem>::template set(itm, key, std::forward<T>(data)); WritableDataStoreTraits<ArrItem>::template set<>(itm, key, std::forward<T>(data));
} }
template<class T> constexpr bool IsReadWritableDataStore = IsDataStore<T> && IsWritableDataStore<T>; template<class T> constexpr bool IsReadWritableDataStore = IsDataStore<T> && IsWritableDataStore<T>;

View File

@@ -1,7 +1,9 @@
# TODO Add individual tests as executables in separate directories # TODO Add individual tests as executables in separate directories
# add_subirectory(<testcase>) # add_subirectory(<testcase>)
find_package(Catch2 2.9 REQUIRED) find_package(Catch2 3.8 REQUIRED)
slic3r_remap_configs(Catch2::Catch2 RelWithDebInfo Release)
slic3r_remap_configs(Catch2::Catch2WithMain RelWithDebInfo Release)
include(Catch) include(Catch)
@@ -13,7 +15,7 @@ set(CATCH_EXTRA_ARGS "" CACHE STRING "Extra arguments for catch2 test suites.")
add_library(test_common INTERFACE) add_library(test_common INTERFACE)
target_include_directories(test_common INTERFACE ${CMAKE_CURRENT_LIST_DIR}) target_include_directories(test_common INTERFACE ${CMAKE_CURRENT_LIST_DIR})
target_compile_definitions(test_common INTERFACE TEST_DATA_DIR=R"\(${TEST_DATA_DIR}\)" CATCH_CONFIG_FAST_COMPILE) target_compile_definitions(test_common INTERFACE TEST_DATA_DIR=R"\(${TEST_DATA_DIR}\)" CATCH_CONFIG_FAST_COMPILE)
target_link_libraries(test_common INTERFACE Catch2::Catch2) target_link_libraries(test_common INTERFACE Catch2::Catch2WithMain)
if (APPLE) if (APPLE)
target_link_libraries(test_common INTERFACE "-liconv -framework IOKit" "-framework CoreFoundation" -lc++) target_link_libraries(test_common INTERFACE "-liconv -framework IOKit" "-framework CoreFoundation" -lc++)

View File

@@ -1,4 +1,7 @@
#include <catch2/catch.hpp> #include <catch2/catch_test_macros.hpp>
#include <catch2/catch_template_test_macros.hpp>
#include <catch2/matchers/catch_matchers.hpp>
#include <catch2/catch_approx.hpp>
#include "test_utils.hpp" #include "test_utils.hpp"
#include <libslic3r/Execution/ExecutionSeq.hpp> #include <libslic3r/Execution/ExecutionSeq.hpp>
@@ -36,6 +39,8 @@
#include <random> #include <random>
using namespace Catch;
template<class ArrItem = Slic3r::arr2::ArrangeItem> template<class ArrItem = Slic3r::arr2::ArrangeItem>
static std::vector<ArrItem> qidi_parts(double infl = 0.) { static std::vector<ArrItem> qidi_parts(double infl = 0.) {
using namespace Slic3r; using namespace Slic3r;

View File

@@ -1,4 +1,11 @@
#include <catch2/catch.hpp> #include <catch2/catch_test_macros.hpp>
#include <catch2/catch_template_test_macros.hpp>
#include <catch2/generators/catch_generators.hpp>
#include <catch2/generators/catch_generators_range.hpp>
#include <catch2/generators/catch_generators_all.hpp>
#include <catch2/matchers/catch_matchers.hpp>
#include <catch2/catch_approx.hpp>
#include "test_utils.hpp" #include "test_utils.hpp"
#include <arrange-wrapper/Arrange.hpp> #include <arrange-wrapper/Arrange.hpp>
@@ -11,6 +18,8 @@
#include "libslic3r/Geometry/ConvexHull.hpp" #include "libslic3r/Geometry/ConvexHull.hpp"
#include "libslic3r/Format/3mf.hpp" #include "libslic3r/Format/3mf.hpp"
using namespace Catch;
static Slic3r::Model get_example_model_with_20mm_cube() static Slic3r::Model get_example_model_with_20mm_cube()
{ {
using namespace Slic3r; using namespace Slic3r;

View File

@@ -4,46 +4,53 @@
#define CATCH_CONFIG_EXTERNAL_INTERFACES #define CATCH_CONFIG_EXTERNAL_INTERFACES
#define CATCH_CONFIG_MAIN #define CATCH_CONFIG_MAIN
// #define CATCH_CONFIG_DEFAULT_REPORTER "verboseconsole" // #define CATCH_CONFIG_DEFAULT_REPORTER "verboseconsole"
#include <catch2/catch.hpp> #include <catch2/reporters/catch_reporter_streaming_base.hpp>
#include <catch2/catch_all.hpp>
namespace Catch { namespace Catch {
struct VerboseConsoleReporter : public ConsoleReporter { struct VerboseConsoleReporter : public StreamingReporterBase {
double duration = 0.; double duration = 0.;
using ConsoleReporter::ConsoleReporter; using StreamingReporterBase::StreamingReporterBase;
static std::string getDescription() {
return "Verbose Console Reporter";
}
void testCaseStarting(TestCaseInfo const& _testInfo) override void testCaseStarting(TestCaseInfo const& _testInfo) override
{ {
Colour::use(Colour::Cyan); //Colour::use(Colour::Cyan);
stream << "Testing "; m_stream << "Testing ";
Colour::use(Colour::None); //Colour::use(Colour::None);
stream << _testInfo.name << std::endl; m_stream << _testInfo.name << std::endl;
ConsoleReporter::testCaseStarting(_testInfo); StreamingReporterBase::testCaseStarting(_testInfo);
} }
void sectionStarting(const SectionInfo &_sectionInfo) override void sectionStarting(const SectionInfo &_sectionInfo) override
{ {
if (_sectionInfo.name != currentTestCaseInfo->name) if (_sectionInfo.name != currentTestCaseInfo->name)
stream << _sectionInfo.name << std::endl; m_stream << _sectionInfo.name << std::endl;
ConsoleReporter::sectionStarting(_sectionInfo); StreamingReporterBase::sectionStarting(_sectionInfo);
} }
void sectionEnded(const SectionStats &_sectionStats) override { void sectionEnded(const SectionStats &_sectionStats) override {
duration += _sectionStats.durationInSeconds; duration += _sectionStats.durationInSeconds;
ConsoleReporter::sectionEnded(_sectionStats); StreamingReporterBase::sectionEnded(_sectionStats);
} }
void testCaseEnded(TestCaseStats const& stats) override void testCaseEnded(TestCaseStats const& stats) override
{ {
if (stats.totals.assertions.allOk()) { if (stats.totals.assertions.allOk()) {
Colour::use(Colour::BrightGreen); //Colour::use(Colour::BrightGreen);
stream << "Passed"; m_stream << "Passed";
Colour::use(Colour::None); //Colour::use(Colour::None);
stream << " in " << duration << " [seconds]\n" << std::endl; m_stream << " in " << duration << " [seconds]\n" << std::endl;
} }
duration = 0.; duration = 0.;
ConsoleReporter::testCaseEnded(stats); StreamingReporterBase::testCaseEnded(stats);
} }
}; };

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg height="20.860270" width="22.311550" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<marker id="endArrow" markerHeight="8" markerUnits="strokeWidth" markerWidth="10" orient="auto" refX="1" refY="5" viewBox="0 0 10 10">
<polyline fill="darkblue" points="0,0 10,5 0,10 1,5" />
</marker>
<rect fill='white' stroke='none' x='0' y='0' width='22.311550' height='20.860270'/>
<path d="M 1716256 -11015182 1795391 -11035439 1568498 -10955916 1631011 -10984708 1564236 -10960836 1564236 -10960837 1674546 -11011825 1791112 -11041943 z " style="fill: none; stroke: black; stroke-width: 0.000010; fill-type: evenodd" fill-opacity="0.000000" />
<path d="M 11.5202 10.5927 12.3116 10.7952 10.0426 10 10.6677 10.2879 10 10.0492 10 10.0492 11.1031 10.5591 12.2688 10.8603 z " style="fill: lightgray; stroke: black; stroke-width: 0.000000; fill-type: evenodd" fill-opacity="1.000000" />
<path d="M 10 10.0492 12.0868 10.7952 11.5202 10.5927 12.3116 10.7952 z " style="fill: gray; stroke: black; stroke-width: 0.000000; fill-type: evenodd" fill-opacity="1.000000" />
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 189 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 50 KiB

View File

@@ -14,6 +14,7 @@ add_executable(${_TEST_NAME}_tests
test_gaps.cpp test_gaps.cpp
test_gcode.cpp test_gcode.cpp
test_gcode_travels.cpp test_gcode_travels.cpp
test_infill_above_bridges.cpp
test_seam_perimeters.cpp test_seam_perimeters.cpp
test_seam_shells.cpp test_seam_shells.cpp
test_seam_geometry.cpp test_seam_geometry.cpp

View File

@@ -1,4 +1,5 @@
#include <catch2/catch.hpp> #include <catch2/catch_test_macros.hpp>
#include <catch2/benchmark/catch_benchmark_all.hpp>
#include "test_data.hpp" #include "test_data.hpp"
#include "libslic3r/GCode/SeamGeometry.hpp" #include "libslic3r/GCode/SeamGeometry.hpp"

View File

@@ -1,4 +1,4 @@
#include <catch2/catch.hpp> #include <catch2/catch_test_macros.hpp>
#include "test_data.hpp" #include "test_data.hpp"

View File

@@ -1,4 +1,4 @@
#include <catch2/catch.hpp> #include <catch2/catch_test_macros.hpp>
#include <libslic3r/BridgeDetector.hpp> #include <libslic3r/BridgeDetector.hpp>
#include <libslic3r/Geometry.hpp> #include <libslic3r/Geometry.hpp>

View File

@@ -1,4 +1,5 @@
#include <catch2/catch.hpp> #include <catch2/catch_test_macros.hpp>
#include <catch2/catch_approx.hpp>
#include <sstream> #include <sstream>
#include <fstream> #include <fstream>
@@ -7,6 +8,7 @@
using namespace Slic3r; using namespace Slic3r;
using namespace Test; using namespace Test;
using namespace Catch;
constexpr bool debug_files{false}; constexpr bool debug_files{false};
@@ -168,7 +170,7 @@ TEST_CASE_METHOD(CancelObjectFixture, "Single extruder", "[CancelObject]") {
} }
TEST_CASE_METHOD(CancelObjectFixture, "Sequential print", "[CancelObject]") { TEST_CASE_METHOD(CancelObjectFixture, "Sequential print", "[CancelObject]") {
config.set_deserialize_strict({{"complete_objects", 1}}); config.set_deserialize_strict({{"complete_objects", 1} });
Print print; Print print;
print.apply(two_cubes, config); print.apply(two_cubes, config);

View File

@@ -1,4 +1,4 @@
#include <catch2/catch.hpp> #include <catch2/catch_test_macros.hpp>
#include "test_data.hpp" #include "test_data.hpp"
#include "libslic3r/ClipperZUtils.hpp" #include "libslic3r/ClipperZUtils.hpp"

View File

@@ -1,4 +1,4 @@
#include <catch2/catch.hpp> #include <catch2/catch_test_macros.hpp>
#include <numeric> #include <numeric>
#include <sstream> #include <sstream>

View File

@@ -1,4 +1,4 @@
#include <catch2/catch.hpp> #include <catch2/catch_test_macros.hpp>
#include <exception> #include <exception>
#include <numeric> #include <numeric>

View File

@@ -1,3 +1,4 @@
#include <catch2/catch_test_macros.hpp>
#include "test_data.hpp" #include "test_data.hpp"
#include "libslic3r/TriangleMesh.hpp" #include "libslic3r/TriangleMesh.hpp"
@@ -399,7 +400,6 @@ bool contains_regex(const std::string &data, const std::string &pattern)
} } // namespace Slic3r::Test } } // namespace Slic3r::Test
#include <catch2/catch.hpp>
SCENARIO("init_print functionality", "[test_data]") { SCENARIO("init_print functionality", "[test_data]") {
GIVEN("A default config") { GIVEN("A default config") {

View File

@@ -14,7 +14,7 @@
#include "libslic3r/GCode/SeamPlacer.hpp" #include "libslic3r/GCode/SeamPlacer.hpp"
#include "libslic3r/GCode/SeamAligned.hpp" #include "libslic3r/GCode/SeamAligned.hpp"
#include <filesystem> #include <boost/filesystem.hpp>
#include <unordered_map> #include <unordered_map>
namespace Slic3r { namespace Test { namespace Slic3r { namespace Test {
@@ -159,7 +159,7 @@ std::string slice(
bool contains(const std::string &data, const std::string &pattern); bool contains(const std::string &data, const std::string &pattern);
bool contains_regex(const std::string &data, const std::string &pattern); bool contains_regex(const std::string &data, const std::string &pattern);
inline std::unique_ptr<Print> process_3mf(const std::filesystem::path &path) { inline std::unique_ptr<Print> process_3mf(const boost::filesystem::path &path) {
DynamicPrintConfig config; DynamicPrintConfig config;
auto print{std::make_unique<Print>()}; auto print{std::make_unique<Print>()};
Model model; Model model;
@@ -176,7 +176,7 @@ inline std::unique_ptr<Print> process_3mf(const std::filesystem::path &path) {
static std::map<std::string, std::unique_ptr<Print>> prints_3mfs; static std::map<std::string, std::unique_ptr<Print>> prints_3mfs;
// Lazy getter, to avoid processing the 3mf multiple times, it already takes ages. // Lazy getter, to avoid processing the 3mf multiple times, it already takes ages.
inline Print *get_print(const std::filesystem::path &file_path) { inline Print *get_print(const boost::filesystem::path &file_path) {
if (!prints_3mfs.count(file_path.string())) { if (!prints_3mfs.count(file_path.string())) {
prints_3mfs[file_path.string()] = process_3mf(file_path.string()); prints_3mfs[file_path.string()] = process_3mf(file_path.string());
} }
@@ -204,8 +204,8 @@ inline void serialize_seam(std::ostream &output, const std::vector<std::vector<S
struct SeamsFixture struct SeamsFixture
{ {
const std::filesystem::path file_3mf{ const boost::filesystem::path file_3mf{
std::filesystem::path{TEST_DATA_DIR} / std::filesystem::path{"seam_test_object.3mf"}}; boost::filesystem::path{TEST_DATA_DIR} / boost::filesystem::path{"seam_test_object.3mf"}};
const Print *print{Test::get_print(file_3mf)}; const Print *print{Test::get_print(file_3mf)};
const PrintObject *print_object{print->objects()[0]}; const PrintObject *print_object{print->objects()[0]};

View File

@@ -1,4 +1,5 @@
#include <catch2/catch.hpp> #include <catch2/catch_test_macros.hpp>
#include <catch2/catch_approx.hpp>
#include <cstdlib> #include <cstdlib>
@@ -11,6 +12,7 @@
#include "test_data.hpp" #include "test_data.hpp"
using namespace Slic3r; using namespace Slic3r;
using namespace Catch;
static inline Slic3r::Point random_point(float LO=-50, float HI=50) static inline Slic3r::Point random_point(float LO=-50, float HI=50)
{ {

View File

@@ -1,4 +1,4 @@
#include <catch2/catch.hpp> #include <catch2/catch_test_macros.hpp>
#include <numeric> #include <numeric>
#include <sstream> #include <sstream>

View File

@@ -1,4 +1,5 @@
#include <catch2/catch.hpp> #include <catch2/catch_test_macros.hpp>
#include <catch2/catch_approx.hpp>
#include <numeric> #include <numeric>
#include <sstream> #include <sstream>
@@ -12,6 +13,7 @@
using namespace Slic3r::Test; using namespace Slic3r::Test;
using namespace Slic3r; using namespace Slic3r;
using namespace Catch;
SCENARIO("Extrusion width specifics", "[Flow]") { SCENARIO("Extrusion width specifics", "[Flow]") {

View File

@@ -1,4 +1,4 @@
#include <catch2/catch.hpp> #include <catch2/catch_test_macros.hpp>
#include "libslic3r/GCodeReader.hpp" #include "libslic3r/GCodeReader.hpp"
#include "libslic3r/Geometry/ConvexHull.hpp" #include "libslic3r/Geometry/ConvexHull.hpp"

View File

@@ -1,4 +1,5 @@
#include <catch2/catch.hpp> #include <catch2/catch_test_macros.hpp>
#include <catch2/catch_approx.hpp>
#include <memory> #include <memory>
#include <regex> #include <regex>
@@ -10,6 +11,7 @@
using namespace Slic3r; using namespace Slic3r;
using namespace Test; using namespace Test;
using namespace Catch;
constexpr bool debug_files = false; constexpr bool debug_files = false;
@@ -61,7 +63,7 @@ TEST_CASE("Wiping speeds", "[GCode]") {
INFO("Wipe moves don\'t retract faster than configured speed"); INFO("Wipe moves don\'t retract faster than configured speed");
CHECK(retract_speed < expected_retract_speed); CHECK(retract_speed < expected_retract_speed);
} }
INFO("No wiping after layer change") INFO("No wiping after layer change");
CHECK(!wiping_on_new_layer); CHECK(!wiping_on_new_layer);
} }
@@ -115,12 +117,12 @@ std::optional<double> parse_axis(const std::string& line, const std::string& axi
* - no travel moves go outside skirt * - no travel moves go outside skirt
* - temperatures are set correctly * - temperatures are set correctly
*/ */
TEST_CASE("Extrusion, travels, temeperatures", "[GCode]") { TEST_CASE("Extrusion, travels, temperatures", "[GCode]") {
DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
config.set_deserialize_strict({ config.set_deserialize_strict({
{ "gcode_comments", 1 }, { "gcode_comments", 1 },
{ "complete_objects", 1 }, { "complete_objects", 1 },
{ "extrusion_axis", 'A' }, { "extrusion_axis", "A" },
{ "start_gcode", "" }, // prevent any default extra Z move { "start_gcode", "" }, // prevent any default extra Z move
{ "layer_height", 0.4 }, { "layer_height", 0.4 },
{ "first_layer_height", 0.4 }, { "first_layer_height", 0.4 },
@@ -170,6 +172,11 @@ TEST_CASE("Extrusion, travels, temeperatures", "[GCode]") {
} }
}); });
// Remove last travel_moves returning to origin
if (travel_moves.back().x() == 0 && travel_moves.back().y() == 0) {
travel_moves.pop_back();
}
const unsigned layer_count = 20 / 0.4; const unsigned layer_count = 20 / 0.4;
INFO("Complete_objects generates the correct number of Z moves."); INFO("Complete_objects generates the correct number of Z moves.");
CHECK(z_moves.size() == layer_count * 2); CHECK(z_moves.size() == layer_count * 2);
@@ -188,20 +195,30 @@ TEST_CASE("Extrusion, travels, temeperatures", "[GCode]") {
TEST_CASE("Used filament", "[GCode]") { TEST_CASE("Used filament", "[GCode]") {
DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); DynamicPrintConfig config1 = Slic3r::DynamicPrintConfig::full_print_config();
config.set_deserialize_strict({ config1.set_deserialize_strict({
{ "retract_length", "1000000" }, { "retract_length", "0" },
{ "use_relative_e_distances", 1 }, { "use_relative_e_distances", 1 },
{ "layer_gcode", "G92 E0\n" }, { "layer_gcode", "G92 E0\n" },
}); });
GCodeReader parser; Print print1;
Print print; Model model1;
Model model; Test::init_print({TestMesh::cube_20x20x20}, print1, model1, config1);
Test::init_print({TestMesh::cube_20x20x20}, print, model, config); Test::gcode(print1);
Test::gcode(print);
DynamicPrintConfig config2 = Slic3r::DynamicPrintConfig::full_print_config();
config2.set_deserialize_strict({
{ "retract_length", "999" },
{ "use_relative_e_distances", 1 },
{ "layer_gcode", "G92 E0\n" },
});
Print print2;
Model model2;
Test::init_print({TestMesh::cube_20x20x20}, print2, model2, config2);
Test::gcode(print2);
INFO("Final retraction is not considered in total used filament"); INFO("Final retraction is not considered in total used filament");
CHECK(print.print_statistics().total_used_filament > 0); CHECK(print1.print_statistics().total_used_filament == print2.print_statistics().total_used_filament);
} }
void check_m73s(Print& print){ void check_m73s(Print& print){

View File

@@ -1,4 +1,6 @@
#include <catch2/catch.hpp> #include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_all.hpp>
#include <catch2/catch_approx.hpp>
#include <libslic3r/GCode/Travels.hpp> #include <libslic3r/GCode/Travels.hpp>
#include <libslic3r/ExPolygon.hpp> #include <libslic3r/ExPolygon.hpp>
#include <libslic3r/GCode.hpp> #include <libslic3r/GCode.hpp>
@@ -6,8 +8,9 @@
using namespace Slic3r; using namespace Slic3r;
using namespace Slic3r::GCode::Impl::Travels; using namespace Slic3r::GCode::Impl::Travels;
using namespace Catch;
struct ApproxEqualsPoints : public Catch::MatcherBase<Points> { struct ApproxEqualsPoints : public Catch::Matchers::MatcherBase<Points> {
ApproxEqualsPoints(const Points& expected, unsigned tolerance): expected(expected), tolerance(tolerance) {} ApproxEqualsPoints(const Points& expected, unsigned tolerance): expected(expected), tolerance(tolerance) {}
bool match(const Points& points) const override { bool match(const Points& points) const override {
if (points.size() != expected.size()) { if (points.size() != expected.size()) {

View File

@@ -1,4 +1,4 @@
#include <catch2/catch.hpp> #include <catch2/catch_test_macros.hpp>
#include <memory> #include <memory>

View File

@@ -1,10 +1,15 @@
#include <catch2/catch.hpp> #include <catch2/catch_approx.hpp>
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers.hpp>
#include <catch2/matchers/catch_matchers_string.hpp>
#include <memory> #include <memory>
#include "libslic3r/GCode/GCodeWriter.hpp" #include "libslic3r/GCode/GCodeWriter.hpp"
#include "libslic3r/GCodeReader.hpp"
using namespace Slic3r; using namespace Slic3r;
using Catch::Approx;
SCENARIO("set_speed emits values with fixed-point output.", "[GCodeWriter]") { SCENARIO("set_speed emits values with fixed-point output.", "[GCodeWriter]") {
@@ -12,23 +17,523 @@ SCENARIO("set_speed emits values with fixed-point output.", "[GCodeWriter]") {
GCodeWriter writer; GCodeWriter writer;
WHEN("set_speed is called to set speed to 99999.123") { WHEN("set_speed is called to set speed to 99999.123") {
THEN("Output string is G1 F99999.123") { THEN("Output string is G1 F99999.123") {
REQUIRE_THAT(writer.set_speed(99999.123), Catch::Equals("G1 F99999.123\n")); REQUIRE_THAT(writer.set_speed(99999.123), Catch::Matchers::Equals("G1 F99999.123\n"));
} }
} }
WHEN("set_speed is called to set speed to 1") { WHEN("set_speed is called to set speed to 1") {
THEN("Output string is G1 F1") { THEN("Output string is G1 F1") {
REQUIRE_THAT(writer.set_speed(1.0), Catch::Equals("G1 F1\n")); REQUIRE_THAT(writer.set_speed(1.0), Catch::Matchers::Equals("G1 F1\n"));
} }
} }
WHEN("set_speed is called to set speed to 203.200022") { WHEN("set_speed is called to set speed to 203.200022") {
THEN("Output string is G1 F203.2") { THEN("Output string is G1 F203.2") {
REQUIRE_THAT(writer.set_speed(203.200022), Catch::Equals("G1 F203.2\n")); REQUIRE_THAT(writer.set_speed(203.200022), Catch::Matchers::Equals("G1 F203.2\n"));
} }
} }
WHEN("set_speed is called to set speed to 203.200522") { WHEN("set_speed is called to set speed to 203.200522") {
THEN("Output string is G1 F203.201") { THEN("Output string is G1 F203.201") {
REQUIRE_THAT(writer.set_speed(203.200522), Catch::Equals("G1 F203.201\n")); REQUIRE_THAT(writer.set_speed(203.200522), Catch::Matchers::Equals("G1 F203.201\n"));
} }
} }
} }
} }
void check_gcode_feedrate(const std::string& gcode, const GCodeConfig& config, double expected_speed) {
GCodeReader parser;
parser.parse_buffer(gcode, [&] (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) {
const double travel_speed = config.opt_float("travel_speed");
const double feedrate = line.has_f() ? line.f() : self.f();
CHECK(feedrate == Approx(expected_speed * 60).epsilon(GCodeFormatter::XYZ_EPSILON));
if (line.dist_Z(self) != 0) {
// lift move or lift + change layer
const double travel_speed_z = config.opt_float("travel_speed_z");
if (travel_speed_z) {
Vec3d move{line.dist_X(self), line.dist_Y(self), line.dist_Z(self)};
double move_u_z = move.z() / move.norm();
double travel_speed_ = std::abs(travel_speed_z / move_u_z);
INFO("move Z feedrate Z component is less than or equal to travel_speed_z");
CHECK(feedrate * std::abs(move_u_z) <= Approx(travel_speed_z * 60).epsilon(GCodeFormatter::XYZ_EPSILON));
if (travel_speed_ < travel_speed) {
INFO("move Z at travel speed Z");
CHECK(feedrate == Approx(travel_speed_ * 60).epsilon(GCodeFormatter::XYZ_EPSILON));
INFO("move Z feedrate Z component is equal to travel_speed_z");
CHECK(feedrate * std::abs(move_u_z) == Approx(travel_speed_z * 60).epsilon(GCodeFormatter::XYZ_EPSILON));
} else {
INFO("move Z at travel speed");
CHECK(feedrate == Approx(travel_speed * 60).epsilon(GCodeFormatter::XYZ_EPSILON));
}
} else {
INFO("move Z at travel speed");
CHECK(feedrate == Approx(travel_speed * 60).epsilon(GCodeFormatter::XYZ_EPSILON));
}
} else if (not line.extruding(self)) {
// normal move
INFO("move XY at travel speed");
CHECK(feedrate == Approx(travel_speed * 60));
}
});
}
SCENARIO("travel_speed_z is zero should use travel_speed.", "[GCodeWriter]") {
GIVEN("GCodeWriter instance") {
GCodeWriter writer;
WHEN("travel_speed_z is set to 0") {
writer.config.travel_speed.value = 1000;
writer.config.travel_speed_z.value = 0;
THEN("XYZ move feed rate should be equal to travel_speed") {
const Vec3d move{10, 10, 10};
const double speed = writer.config.travel_speed.value;
const Vec3d p1 = writer.get_position();
const Vec3d p2 = p1 + move;
const std::string result = writer.travel_to_xyz(p2);
check_gcode_feedrate(result, writer.config, speed);
}
}
}
}
SCENARIO("travel_speed_z is respected in Z speed component.", "[GCodeWriter]") {
GIVEN("GCodeWriter instance") {
GCodeWriter writer;
WHEN("travel_speed_z is set to 10") {
writer.config.travel_speed.value = 1000;
writer.config.travel_speed_z.value = 10;
THEN("Z move feed rate should be equal to travel_speed_z") {
const Vec3d move{0, 0, 10};
const double speed = writer.config.travel_speed_z.value;
const Vec3d p1 = writer.get_position();
const Vec3d p2 = p1 + move;
const std::string result = writer.travel_to_xyz(p2);
check_gcode_feedrate(result, writer.config, speed);
}
THEN("-Z move feed rate should be equal to travel_speed_z") {
const Vec3d move{0, 0, -10};
const double speed = writer.config.travel_speed_z.value;
const Vec3d p1 = writer.get_position();
const Vec3d p2 = p1 + move;
const std::string result = writer.travel_to_xyz(p2);
check_gcode_feedrate(result, writer.config, speed);
}
THEN("XY move feed rate should be equal to travel_speed") {
const Vec3d move{10, 10, 0};
const double speed = writer.config.travel_speed.value;
const Vec3d p1 = writer.get_position();
const Vec3d p2 = p1 + move;
const std::string result = writer.travel_to_xyz(p2);
check_gcode_feedrate(result, writer.config, speed);
}
THEN("-XY move feed rate should be equal to travel_speed") {
const Vec3d move{-10, 10, 0};
const double speed = writer.config.travel_speed.value;
const Vec3d p1 = writer.get_position();
const Vec3d p2 = p1 + move;
const std::string result = writer.travel_to_xyz(p2);
check_gcode_feedrate(result, writer.config, speed);
}
THEN("X-Y move feed rate should be equal to travel_speed") {
const Vec3d move{10, -10, 0};
const double speed = writer.config.travel_speed.value;
const Vec3d p1 = writer.get_position();
const Vec3d p2 = p1 + move;
const std::string result = writer.travel_to_xyz(p2);
check_gcode_feedrate(result, writer.config, speed);
}
THEN("-X-Y move feed rate should be equal to travel_speed") {
const Vec3d move{-10, -10, 0};
const double speed = writer.config.travel_speed.value;
const Vec3d p1 = writer.get_position();
const Vec3d p2 = p1 + move;
const std::string result = writer.travel_to_xyz(p2);
check_gcode_feedrate(result, writer.config, speed);
}
THEN("XZ move feed rate Z component should be equal to travel_speed_z") {
const Vec3d move{10, 0, 10};
const Vec3d move_u = move / move.norm();
const double speed = std::abs(writer.config.travel_speed_z.value / move_u.z());
Vec3d p1 = writer.get_position();
Vec3d p2 = p1 + move;
std::string result = writer.travel_to_xyz(p2);
check_gcode_feedrate(result, writer.config, speed);
}
THEN("-XZ move feed rate Z component should be equal to travel_speed_z") {
const Vec3d move{-10, 0, 10};
const Vec3d move_u = move / move.norm();
const double speed = std::abs(writer.config.travel_speed_z.value / move_u.z());
const Vec3d p1 = writer.get_position();
const Vec3d p2 = p1 + move;
const std::string result = writer.travel_to_xyz(p2);
check_gcode_feedrate(result, writer.config, speed);
}
THEN("X-Z move feed rate Z component should be equal to travel_speed_z") {
const Vec3d move{10, 0, -10};
const Vec3d move_u = move / move.norm();
const double speed = std::abs(writer.config.travel_speed_z.value / move_u.z());
const Vec3d p1 = writer.get_position();
const Vec3d p2 = p1 + move;
const std::string result = writer.travel_to_xyz(p2);
check_gcode_feedrate(result, writer.config, speed);
}
THEN("-X-Z move feed rate Z component should be equal to travel_speed_z") {
const Vec3d move{-10, 0, -10};
const Vec3d move_u = move / move.norm();
const double speed = std::abs(writer.config.travel_speed_z.value / move_u.z());
const Vec3d p1 = writer.get_position();
const Vec3d p2 = p1 + move;
const std::string result = writer.travel_to_xyz(p2);
check_gcode_feedrate(result, writer.config, speed);
}
THEN("YZ move feed rate Z component should be equal to travel_speed_z") {
const Vec3d move{0, 10, 10};
const Vec3d move_u = move / move.norm();
const double speed = std::abs(writer.config.travel_speed_z.value / move_u.z());
const Vec3d p1 = writer.get_position();
const Vec3d p2 = p1 + move;
const std::string result = writer.travel_to_xyz(p2);
check_gcode_feedrate(result, writer.config, speed);
}
THEN("-YZ move feed rate Z component should be equal to travel_speed_z") {
const Vec3d move{0, -10, 10};
const Vec3d move_u = move / move.norm();
const double speed = std::abs(writer.config.travel_speed_z.value / move_u.z());
const Vec3d p1 = writer.get_position();
const Vec3d p2 = p1 + move;
const std::string result = writer.travel_to_xyz(p2);
check_gcode_feedrate(result, writer.config, speed);
}
THEN("Y-Z move feed rate Z component should be equal to travel_speed_z") {
const Vec3d move{0, 10, -10};
const Vec3d move_u = move / move.norm();
const double speed = std::abs(writer.config.travel_speed_z.value / move_u.z());
const Vec3d p1 = writer.get_position();
const Vec3d p2 = p1 + move;
const std::string result = writer.travel_to_xyz(p2);
check_gcode_feedrate(result, writer.config, speed);
}
THEN("-Y-Z move feed rate Z component should be equal to travel_speed_z") {
const Vec3d move{0, -10, -10};
const Vec3d move_u = move / move.norm();
const double speed = std::abs(writer.config.travel_speed_z.value / move_u.z());
const Vec3d p1 = writer.get_position();
const Vec3d p2 = p1 + move;
const std::string result = writer.travel_to_xyz(p2);
check_gcode_feedrate(result, writer.config, speed);
}
THEN("XYZ move feed rate Z component should be equal to travel_speed_z") {
const Vec3d move{10, 10, 10};
const Vec3d move_u = move / move.norm();
const double speed = std::abs(writer.config.travel_speed_z.value / move_u.z());
const Vec3d p1 = writer.get_position();
const Vec3d p2 = p1 + move;
const std::string result = writer.travel_to_xyz(p2);
check_gcode_feedrate(result, writer.config, speed);
}
THEN("-XYZ move feed rate Z component should be equal to travel_speed_z") {
const Vec3d move{-10, 10, 10};
const Vec3d move_u = move / move.norm();
const double speed = std::abs(writer.config.travel_speed_z.value / move_u.z());
const Vec3d p1 = writer.get_position();
const Vec3d p2 = p1 + move;
const std::string result = writer.travel_to_xyz(p2);
check_gcode_feedrate(result, writer.config, speed);
}
THEN("X-YZ move feed rate Z component should be equal to travel_speed_z") {
const Vec3d move{10, -10, 10};
const Vec3d move_u = move / move.norm();
const double speed = std::abs(writer.config.travel_speed_z.value / move_u.z());
const Vec3d p1 = writer.get_position();
const Vec3d p2 = p1 + move;
const std::string result = writer.travel_to_xyz(p2);
check_gcode_feedrate(result, writer.config, speed);
}
THEN("-X-YZ move feed rate Z component should be equal to travel_speed_z") {
const Vec3d move{-10, -10, 10};
const Vec3d move_u = move / move.norm();
const double speed = std::abs(writer.config.travel_speed_z.value / move_u.z());
const Vec3d p1 = writer.get_position();
const Vec3d p2 = p1 + move;
const std::string result = writer.travel_to_xyz(p2);
check_gcode_feedrate(result, writer.config, speed);
}
THEN("XY-Z move feed rate Z component should be equal to travel_speed_z") {
const Vec3d move{10, 10, -10};
const Vec3d move_u = move / move.norm();
const double speed = std::abs(writer.config.travel_speed_z.value / move_u.z());
const Vec3d p1 = writer.get_position();
const Vec3d p2 = p1 + move;
const std::string result = writer.travel_to_xyz(p2);
check_gcode_feedrate(result, writer.config, speed);
}
THEN("-XY-Z move feed rate Z component should be equal to travel_speed_z") {
const Vec3d move{-10, 10, -10};
const Vec3d move_u = move / move.norm();
const double speed = std::abs(writer.config.travel_speed_z.value / move_u.z());
const Vec3d p1 = writer.get_position();
const Vec3d p2 = p1 + move;
const std::string result = writer.travel_to_xyz(p2);
check_gcode_feedrate(result, writer.config, speed);
}
THEN("X-Y-Z move feed rate Z component should be equal to travel_speed_z") {
const Vec3d move{10, -10, -10};
const Vec3d move_u = move / move.norm();
const double speed = std::abs(writer.config.travel_speed_z.value / move_u.z());
const Vec3d p1 = writer.get_position();
const Vec3d p2 = p1 + move;
const std::string result = writer.travel_to_xyz(p2);
check_gcode_feedrate(result, writer.config, speed);
}
THEN("-X-Y-Z move feed rate Z component should be equal to travel_speed_z") {
const Vec3d move{-10, -10, -10};
const Vec3d move_u = move / move.norm();
const double speed = std::abs(writer.config.travel_speed_z.value / move_u.z());
const Vec3d p1 = writer.get_position();
const Vec3d p2 = p1 + move;
const std::string result = writer.travel_to_xyz(p2);
check_gcode_feedrate(result, writer.config, speed);
}
}
}
}
TEST_CASE("GCodeWriter emits G1 code correctly according to XYZF_EXPORT_DIGITS", "[GCodeWriter]") {
GCodeWriter writer;
SECTION("Check quantize") {
CHECK(GCodeFormatter::quantize(1.0,0) == 1.);
CHECK(GCodeFormatter::quantize(0.0,0) == 0.);
CHECK(GCodeFormatter::quantize(0.1,0) == 0);
CHECK(GCodeFormatter::quantize(1.0,1) == 1.);
CHECK(GCodeFormatter::quantize(0.0,1) == 0.);
CHECK(GCodeFormatter::quantize(0.1,1) == Approx(0.1));
CHECK(GCodeFormatter::quantize(0.01,1) == 0.);
CHECK(GCodeFormatter::quantize(1.0,2) == 1.);
CHECK(GCodeFormatter::quantize(0.0,2) == 0.);
CHECK(GCodeFormatter::quantize(0.1,2) == Approx(0.1));
CHECK(GCodeFormatter::quantize(0.01,2) == Approx(0.01));
CHECK(GCodeFormatter::quantize(0.001,2) == 0.);
CHECK(GCodeFormatter::quantize(1.0,3) == 1.);
CHECK(GCodeFormatter::quantize(0.0,3) == 0.);
CHECK(GCodeFormatter::quantize(0.1,3) == Approx(0.1));
CHECK(GCodeFormatter::quantize(0.01,3) == Approx(0.01));
CHECK(GCodeFormatter::quantize(0.001,3) == Approx(0.001));
CHECK(GCodeFormatter::quantize(0.0001,3) == 0.);
CHECK(GCodeFormatter::quantize(1.0,4) == 1.);
CHECK(GCodeFormatter::quantize(0.0,4) == 0.);
CHECK(GCodeFormatter::quantize(0.1,4) == Approx(0.1));
CHECK(GCodeFormatter::quantize(0.01,4) == Approx(0.01));
CHECK(GCodeFormatter::quantize(0.001,4) == Approx(0.001));
CHECK(GCodeFormatter::quantize(0.0001,4) == Approx(0.0001));
CHECK(GCodeFormatter::quantize(0.00001,4) == 0.);
CHECK(GCodeFormatter::quantize(1.0,5) == 1.);
CHECK(GCodeFormatter::quantize(0.0,5) == 0.);
CHECK(GCodeFormatter::quantize(0.1,5) == Approx(0.1));
CHECK(GCodeFormatter::quantize(0.01,5) == Approx(0.01));
CHECK(GCodeFormatter::quantize(0.001,5) == Approx(0.001));
CHECK(GCodeFormatter::quantize(0.0001,5) == Approx(0.0001));
CHECK(GCodeFormatter::quantize(0.00001,5) == Approx(0.00001));
CHECK(GCodeFormatter::quantize(0.000001,5) == 0.);
CHECK(GCodeFormatter::quantize(1.0,6) == 1.);
CHECK(GCodeFormatter::quantize(0.0,6) == 0.);
CHECK(GCodeFormatter::quantize(0.1,6) == Approx(0.1));
CHECK(GCodeFormatter::quantize(0.01,6) == Approx(0.01));
CHECK(GCodeFormatter::quantize(0.001,6) == Approx(0.001));
CHECK(GCodeFormatter::quantize(0.0001,6) == Approx(0.0001));
CHECK(GCodeFormatter::quantize(0.00001,6) == Approx(0.00001));
CHECK(GCodeFormatter::quantize(0.000001,6) == Approx(0.000001));
CHECK(GCodeFormatter::quantize(0.0000001,6) == 0.);
}
SECTION("Check pow_10") {
// IEEE 754 floating point numbers can represent these numbers EXACTLY.
CHECK(GCodeFormatter::pow_10[0] == 1.);
CHECK(GCodeFormatter::pow_10[1] == 10.);
CHECK(GCodeFormatter::pow_10[2] == 100.);
CHECK(GCodeFormatter::pow_10[3] == 1000.);
CHECK(GCodeFormatter::pow_10[4] == 10000.);
CHECK(GCodeFormatter::pow_10[5] == 100000.);
CHECK(GCodeFormatter::pow_10[6] == 1000000.);
CHECK(GCodeFormatter::pow_10[7] == 10000000.);
CHECK(GCodeFormatter::pow_10[8] == 100000000.);
CHECK(GCodeFormatter::pow_10[9] == 1000000000.);
}
SECTION("Check pow_10_inv") {
// IEEE 754 floating point numbers can NOT represent these numbers exactly.
CHECK(GCodeFormatter::pow_10_inv[0] == 1.);
CHECK(GCodeFormatter::pow_10_inv[1] == 0.1);
CHECK(GCodeFormatter::pow_10_inv[2] == 0.01);
CHECK(GCodeFormatter::pow_10_inv[3] == 0.001);
CHECK(GCodeFormatter::pow_10_inv[4] == 0.0001);
CHECK(GCodeFormatter::pow_10_inv[5] == 0.00001);
CHECK(GCodeFormatter::pow_10_inv[6] == 0.000001);
CHECK(GCodeFormatter::pow_10_inv[7] == 0.0000001);
CHECK(GCodeFormatter::pow_10_inv[8] == 0.00000001);
CHECK(GCodeFormatter::pow_10_inv[9] == 0.000000001);
}
SECTION("travel_to_z Emit G1 code for very significant movement") {
double z1 = 10.0;
std::string result1{ writer.travel_to_z(z1) };
CHECK(result1 == "G1 Z10 F7800\n");
double z2 = z1 * 2;
std::string result2{ writer.travel_to_z(z2) };
CHECK(result2 == "G1 Z20 F7800\n");
}
SECTION("travel_to_z Emit G1 code for significant movement") {
double z1 = 10.0;
std::string result1{ writer.travel_to_z(z1) };
CHECK(result1 == "G1 Z10 F7800\n");
// This should test with XYZ_EPSILON exactly,
// but IEEE 754 floating point numbers cannot pass the test.
double z2 = z1 + GCodeFormatter::XYZ_EPSILON * 1.001;
std::string result2{ writer.travel_to_z(z2) };
std::ostringstream oss;
oss << "G1 Z"
<< GCodeFormatter::quantize_xyzf(z2)
<< " F7800\n";
CHECK(result2 == oss.str());
}
SECTION("travel_to_z Do not emit G1 code for insignificant movement") {
double z1 = 10.0;
std::string result1{ writer.travel_to_z(z1) };
CHECK(result1 == "G1 Z10 F7800\n");
// Movement smaller than XYZ_EPSILON
double z2 = z1 + (GCodeFormatter::XYZ_EPSILON * 0.999);
std::string result2{ writer.travel_to_z(z2) };
CHECK(result2 == "");
double z3 = z1 + (GCodeFormatter::XYZ_EPSILON * 0.1);
std::string result3{ writer.travel_to_z(z3) };
CHECK(result3 == "");
}
SECTION("travel_to_xyz Emit G1 code for very significant movement") {
Vec3d v1{10.0, 10.0, 10.0};
std::string result1{ writer.travel_to_xyz(v1) };
CHECK(result1 == "G1 X10 Y10 Z10 F7800\n");
Vec3d v2 = v1 * 2;
std::string result2{ writer.travel_to_xyz(v2) };
CHECK(result2 == "G1 X20 Y20 Z20 F7800\n");
}
SECTION("travel_to_xyz Emit G1 code for significant XYZ movement") {
Vec3d v1{10.0, 10.0, 10.0};
std::string result1{ writer.travel_to_xyz(v1) };
CHECK(result1 == "G1 X10 Y10 Z10 F7800\n");
Vec3d v2 = v1;
// This should test with XYZ_EPSILON exactly,
// but IEEE 754 floating point numbers cannot pass the test.
v2.array() += GCodeFormatter::XYZ_EPSILON * 1.001;
std::string result2{ writer.travel_to_xyz(v2) };
std::ostringstream oss;
oss << "G1 X"
<< GCodeFormatter::quantize_xyzf(v2.x())
<< " Y"
<< GCodeFormatter::quantize_xyzf(v2.y())
<< " Z"
<< GCodeFormatter::quantize_xyzf(v2.z())
<< " F7800\n";
CHECK(result2 == oss.str());
}
SECTION("travel_to_xyz Emit G1 code for significant X movement") {
Vec3d v1{10.0, 10.0, 10.0};
std::string result1{ writer.travel_to_xyz(v1) };
CHECK(result1 == "G1 X10 Y10 Z10 F7800\n");
Vec3d v2 = v1;
// This should test with XYZ_EPSILON exactly,
// but IEEE 754 floating point numbers cannot pass the test.
v2.x() += GCodeFormatter::XYZ_EPSILON * 1.001;
std::string result2{ writer.travel_to_xyz(v2) };
std::ostringstream oss;
// Only X needs to be emitted in this case,
// but this is how the code currently works.
oss << "G1 X"
<< GCodeFormatter::quantize_xyzf(v2.x())
<< " Y"
<< GCodeFormatter::quantize_xyzf(v2.y())
<< " F7800\n";
CHECK(result2 == oss.str());
}
SECTION("travel_to_xyz Emit G1 code for significant Y movement") {
Vec3d v1{10.0, 10.0, 10.0};
std::string result1{ writer.travel_to_xyz(v1) };
CHECK(result1 == "G1 X10 Y10 Z10 F7800\n");
Vec3d v2 = v1;
// This should test with XYZ_EPSILON exactly,
// but IEEE 754 floating point numbers cannot pass the test.
v2.y() += GCodeFormatter::XYZ_EPSILON * 1.001;
std::string result2{ writer.travel_to_xyz(v2) };
std::ostringstream oss;
// Only Y needs to be emitted in this case,
// but this is how the code currently works.
oss << "G1 X"
<< GCodeFormatter::quantize_xyzf(v2.x())
<< " Y"
<< GCodeFormatter::quantize_xyzf(v2.y())
<< " F7800\n";
CHECK(result2 == oss.str());
}
SECTION("travel_to_xyz Emit G1 code for significant Z movement") {
Vec3d v1{10.0, 10.0, 10.0};
std::string result1{ writer.travel_to_xyz(v1) };
CHECK(result1 == "G1 X10 Y10 Z10 F7800\n");
Vec3d v2 = v1;
// This should test with XYZ_EPSILON exactly,
// but IEEE 754 floating point numbers cannot pass the test.
v2.z() += GCodeFormatter::XYZ_EPSILON * 1.001;
std::string result2{ writer.travel_to_xyz(v2) };
std::ostringstream oss;
oss << "G1 Z"
<< GCodeFormatter::quantize_xyzf(v2.z())
<< " F7800\n";
CHECK(result2 == oss.str());
}
SECTION("travel_to_xyz Do not emit G1 code for insignificant movement") {
Vec3d v1{10.0, 10.0, 10.0};
std::string result1{ writer.travel_to_xyz(v1) };
CHECK(result1 == "G1 X10 Y10 Z10 F7800\n");
// Movement smaller than XYZ_EPSILON
Vec3d v2 = v1;
v2.array() += GCodeFormatter::XYZ_EPSILON * 0.999;
std::string result2{ writer.travel_to_xyz(v2) };
CHECK(result2 == "");
Vec3d v3 = v1;
v3.array() += GCodeFormatter::XYZ_EPSILON * 0.1;
std::string result3{ writer.travel_to_xyz(v3) };
CHECK(result3 == "");
}
}

View File

@@ -0,0 +1,83 @@
#include <catch2/catch_test_macros.hpp>
#include <catch2/catch_approx.hpp>
#include <libslic3r/InfillAboveBridges.hpp>
using namespace Slic3r;
using Catch::Approx;
const ExPolygon square{
Point::new_scale(0, 0),
Point::new_scale(10, 0),
Point::new_scale(10, 10),
Point::new_scale(0, 10)
};
ExPolygon translate(const ExPolygon &polygon, const Point &offset) {
ExPolygons result{polygon};
translate(result, offset);
return result.front();
}
constexpr bool debug_files{false};
void draw_surfaces(const PrepareInfill::SurfaceRefsByRegion &surfaces, std::string_view file_name) {
using PrepareInfill::SurfaceCollectionRef;
SurfaceCollection to_display;
for (const SurfaceCollectionRef &surface_collection : surfaces) {
to_display.append(surface_collection.get());
}
to_display.export_to_svg(file_name.data(), false);
}
TEST_CASE("Separate infill above bridges", "[PrepareInfill]") {
ExPolygons layer_0_region_0_bridge{
square
};
ExPolygons layer_0_region_0_internal{
translate(square, Point::new_scale(10, 0))
};
ExPolygons layer_0_region_1_internal{
translate(square, Point::new_scale(0, 10))
};
ExPolygons layer_0_region_1_bridge{
translate(square, Point::new_scale(10, 10))
};
SurfaceCollection layer_0_region_0;
layer_0_region_0.append(layer_0_region_0_bridge, stBottomBridge);
layer_0_region_0.append(layer_0_region_0_internal, stInternal);
SurfaceCollection layer_0_region_1;
layer_0_region_1.append(layer_0_region_1_bridge, stBottomBridge);
layer_0_region_1.append(layer_0_region_1_internal, stInternal);
PrepareInfill::SurfaceRefsByRegion layer_0{layer_0_region_0, layer_0_region_1};
ExPolygons layer_1_region_0_solid{
translate(square, Point::new_scale(5, 5))
};
SurfaceCollection layer_1_region_0;
layer_1_region_0.append(layer_1_region_0_solid, stInternalSolid);
PrepareInfill::SurfaceRefsByRegion layer_1{layer_1_region_0};
if constexpr (debug_files) {
draw_surfaces(layer_0, "layer_0.svg");
}
PrepareInfill::separate_infill_above_bridges({layer_0, layer_1}, 0);
if constexpr (debug_files) {
draw_surfaces(layer_1, "layer_1.svg");
}
const Surfaces &result{layer_1.front().get().surfaces};
REQUIRE(result.size() == 4);
const double expected_area{scale_(5.0) * scale_(5.0)};
CHECK(result[0].expolygon.contour.area() == Approx(expected_area));
CHECK(result[0].surface_type == stInternalSolid);
CHECK(result[1].expolygon.contour.area() == Approx(expected_area));
CHECK(result[1].surface_type == stInternalSolid);
CHECK(result[2].expolygon.contour.area() == Approx(expected_area));
CHECK(result[2].surface_type == stSolidOverBridge);
CHECK(result[3].expolygon.contour.area() == Approx(expected_area));
CHECK(result[3].surface_type == stSolidOverBridge);
}

View File

@@ -2,11 +2,13 @@
* Ported from t/layers.t * Ported from t/layers.t
*/ */
#include <catch2/catch.hpp> #include <catch2/catch_test_macros.hpp>
#include <catch2/catch_approx.hpp>
#include "test_data.hpp" #include "test_data.hpp"
using namespace Slic3r; using namespace Slic3r;
using namespace Slic3r::Test; using namespace Slic3r::Test;
using namespace Catch;
void check_layers(const DynamicPrintConfig& config) { void check_layers(const DynamicPrintConfig& config) {
GCodeReader parser; GCodeReader parser;
@@ -27,10 +29,10 @@ void check_layers(const DynamicPrintConfig& config) {
const double layer_height = config.opt_float("layer_height"); const double layer_height = config.opt_float("layer_height");
INFO("Correct first layer height."); INFO("Correct first layer height.");
CHECK(z.at(0) == Approx(first_layer_height + z_offset)); CHECK(z.at(0) == Approx(first_layer_height + z_offset));
INFO("Correct second layer height") INFO("Correct second layer height");
CHECK(z.at(1) == Approx(first_layer_height + layer_height + z_offset)); CHECK(z.at(1) == Approx(first_layer_height + layer_height + z_offset));
INFO("Correct layer height") INFO("Correct layer height");
for (const double increment : tcb::span{increments}.subspan(1)) { for (const double increment : tcb::span{increments}.subspan(1)) {
CHECK(increment == Approx(layer_height)); CHECK(increment == Approx(layer_height));
} }

View File

@@ -1,4 +1,4 @@
#include <catch2/catch.hpp> #include <catch2/catch_test_macros.hpp>
#include "libslic3r/libslic3r.h" #include "libslic3r/libslic3r.h"
#include "libslic3r/Model.hpp" #include "libslic3r/Model.hpp"

View File

@@ -1,4 +1,4 @@
#include <catch2/catch.hpp> #include <catch2/catch_test_macros.hpp>
#include <numeric> #include <numeric>
#include <sstream> #include <sstream>

View File

@@ -1,4 +1,4 @@
#include <catch2/catch.hpp> #include <catch2/catch_test_macros.hpp>
#include <numeric> #include <numeric>
#include <sstream> #include <sstream>

View File

@@ -1,4 +1,4 @@
#include <catch2/catch.hpp> #include <catch2/catch_test_macros.hpp>
#include "libslic3r/libslic3r.h" #include "libslic3r/libslic3r.h"
#include "libslic3r/Print.hpp" #include "libslic3r/Print.hpp"

View File

@@ -1,4 +1,5 @@
#include <catch2/catch.hpp> #include <catch2/catch_test_macros.hpp>
#include <catch2/catch_approx.hpp>
#include "libslic3r/libslic3r.h" #include "libslic3r/libslic3r.h"
#include "libslic3r/GCodeReader.hpp" #include "libslic3r/GCodeReader.hpp"
@@ -10,6 +11,7 @@
using namespace Slic3r; using namespace Slic3r;
using namespace Slic3r::Test; using namespace Slic3r::Test;
using namespace Catch;
boost::regex perimeters_regex("G1 X[-0-9.]* Y[-0-9.]* E[-0-9.]* ; perimeter"); boost::regex perimeters_regex("G1 X[-0-9.]* Y[-0-9.]* E[-0-9.]* ; perimeter");
boost::regex infill_regex("G1 X[-0-9.]* Y[-0-9.]* E[-0-9.]* ; infill"); boost::regex infill_regex("G1 X[-0-9.]* Y[-0-9.]* E[-0-9.]* ; infill");

View File

@@ -1,4 +1,5 @@
#include <catch2/catch.hpp> #include <catch2/catch_test_macros.hpp>
#include <catch2/catch_approx.hpp>
#include "libslic3r/libslic3r.h" #include "libslic3r/libslic3r.h"
#include "libslic3r/Print.hpp" #include "libslic3r/Print.hpp"
@@ -8,6 +9,7 @@
using namespace Slic3r; using namespace Slic3r;
using namespace Slic3r::Test; using namespace Slic3r::Test;
using namespace Catch;
SCENARIO("PrintObject: object layer heights", "[PrintObject]") { SCENARIO("PrintObject: object layer heights", "[PrintObject]") {
GIVEN("20mm cube and default initial config, initial layer height of 2mm") { GIVEN("20mm cube and default initial config, initial layer height of 2mm") {

View File

@@ -2,10 +2,12 @@
* Ported from t/retraction.t * Ported from t/retraction.t
*/ */
#include <catch2/catch.hpp> #include <catch2/catch_test_macros.hpp>
#include <catch2/catch_approx.hpp>
#include <libslic3r/GCodeReader.hpp> #include "libslic3r/GCodeReader.hpp"
#include <libslic3r/Config.hpp> #include "libslic3r/GCode/GCodeWriter.hpp"
#include "libslic3r/Config.hpp"
#include "test_data.hpp" #include "test_data.hpp"
#include <regex> #include <regex>
@@ -13,6 +15,7 @@
using namespace Slic3r; using namespace Slic3r;
using namespace Test; using namespace Test;
using namespace Catch;
constexpr bool debug_files {false}; constexpr bool debug_files {false};
@@ -59,6 +62,10 @@ void check_gcode(std::initializer_list<TestMesh> meshes, const DynamicPrintConfi
const double retract_restart_extra = config.option<ConfigOptionFloats>("retract_restart_extra")->get_at(tool); const double retract_restart_extra = config.option<ConfigOptionFloats>("retract_restart_extra")->get_at(tool);
const double retract_restart_extra_toolchange = config.option<ConfigOptionFloats>("retract_restart_extra_toolchange")->get_at(tool); const double retract_restart_extra_toolchange = config.option<ConfigOptionFloats>("retract_restart_extra_toolchange")->get_at(tool);
const double travel_speed = config.opt_float("travel_speed");
const double feedrate = line.has_f() ? line.f() : self.f();
if (line.dist_Z(self) != 0) { if (line.dist_Z(self) != 0) {
// lift move or lift + change layer // lift move or lift + change layer
const double retract_lift = config.option<ConfigOptionFloats>("retract_lift")->get_at(tool); const double retract_lift = config.option<ConfigOptionFloats>("retract_lift")->get_at(tool);
@@ -77,7 +84,7 @@ void check_gcode(std::initializer_list<TestMesh> meshes, const DynamicPrintConfi
lift_dist = line.dist_Z(self); lift_dist = line.dist_Z(self);
} }
if (line.dist_Z(self) < 0) { if (line.dist_Z(self) < 0) {
INFO("Must be lifted before going down.") INFO("Must be lifted before going down.");
CHECK(lifted); CHECK(lifted);
INFO("Going down by the same amount of the lift or by the amount needed to get to next layer"); INFO("Going down by the same amount of the lift or by the amount needed to get to next layer");
CHECK(( CHECK((
@@ -87,9 +94,26 @@ void check_gcode(std::initializer_list<TestMesh> meshes, const DynamicPrintConfi
lift_dist = 0; lift_dist = 0;
lifted = false; lifted = false;
} }
const double feedrate = line.has_f() ? line.f() : self.f(); const double travel_speed_z = config.opt_float("travel_speed_z");
INFO("move Z at travel speed"); if (travel_speed_z) {
CHECK(feedrate == Approx(config.opt_float("travel_speed") * 60)); Vec3d move{line.dist_X(self), line.dist_Y(self), line.dist_Z(self)};
const double move_u_z = move.z() / move.norm();
const double travel_speed_ = std::abs(travel_speed_z / move_u_z);
INFO("move Z feedrate Z component is less than or equal to travel_speed_z");
CHECK(feedrate * std::abs(move_u_z) <= Approx(travel_speed_z * 60).epsilon(GCodeFormatter::XYZ_EPSILON));
if (travel_speed_ < travel_speed) {
INFO("move Z at travel speed Z");
CHECK(feedrate == Approx(travel_speed_ * 60).epsilon(GCodeFormatter::XYZ_EPSILON));
INFO("move Z feedrate Z component is equal to travel_speed_z");
CHECK(feedrate * std::abs(move_u_z) == Approx(travel_speed_z * 60).epsilon(GCodeFormatter::XYZ_EPSILON));
} else {
INFO("move Z at travel speed");
CHECK(feedrate == Approx(travel_speed * 60).epsilon(GCodeFormatter::XYZ_EPSILON));
}
} else {
INFO("move Z at travel speed");
CHECK(feedrate == Approx(travel_speed * 60).epsilon(GCodeFormatter::XYZ_EPSILON));
}
} }
if (line.retracting(self)) { if (line.retracting(self)) {
retracted[tool] = true; retracted[tool] = true;
@@ -135,11 +159,6 @@ void test_slicing(std::initializer_list<TestMesh> meshes, DynamicPrintConfig& co
check_gcode(meshes, config, duplicate); check_gcode(meshes, config, duplicate);
} }
SECTION("Negative restart extra length") {
config.set_deserialize_strict({{ "retract_restart_extra", "-1" }});
check_gcode(meshes, config, duplicate);
}
SECTION("Retract_lift") { SECTION("Retract_lift") {
config.set_deserialize_strict({{ "retract_lift", "1,2" }}); config.set_deserialize_strict({{ "retract_lift", "1,2" }});
check_gcode(meshes, config, duplicate); check_gcode(meshes, config, duplicate);
@@ -147,7 +166,7 @@ void test_slicing(std::initializer_list<TestMesh> meshes, DynamicPrintConfig& co
} }
TEST_CASE("Slicing with retraction and lifing", "[retraction]") { TEST_CASE("Slicing with retraction and lifting", "[retraction]") {
DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
config.set_deserialize_strict({ config.set_deserialize_strict({
{ "nozzle_diameter", "0.6,0.6,0.6,0.6" }, { "nozzle_diameter", "0.6,0.6,0.6,0.6" },
@@ -176,6 +195,37 @@ TEST_CASE("Slicing with retraction and lifing", "[retraction]") {
} }
} }
TEST_CASE("Slicing with retraction and lifting with travel_speed_z=10", "[retraction]") {
DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
config.set_deserialize_strict({
{ "nozzle_diameter", "0.6,0.6,0.6,0.6" },
{ "first_layer_height", config.opt_float("layer_height") },
{ "first_layer_speed", "100%" },
{ "start_gcode", "" }, // To avoid dealing with the nozzle lift in start G-code
{ "retract_length", "1.5" },
{ "retract_before_travel", "3" },
{ "retract_layer_change", "1" },
{ "only_retract_when_crossing_perimeters", 0 },
{ "travel_speed", "600" },
{ "travel_speed_z", "10" },
});
SECTION("Standard run") {
test_slicing({TestMesh::cube_20x20x20}, config);
}
SECTION("With duplicate cube") {
test_slicing({TestMesh::cube_20x20x20}, config, 2);
}
SECTION("Dual extruder with multiple skirt layers") {
config.set_deserialize_strict({
{"infill_extruder", 2},
{"skirts", 4},
{"skirt_height", 3},
});
test_slicing({TestMesh::cube_20x20x20}, config);
}
}
TEST_CASE("Z moves", "[retraction]") { TEST_CASE("Z moves", "[retraction]") {
DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();

View File

@@ -1,11 +1,13 @@
#include <libslic3r/Point.hpp> #include <libslic3r/Point.hpp>
#include <catch2/catch.hpp> #include <catch2/catch_test_macros.hpp>
#include <catch2/catch_approx.hpp>
#include <libslic3r/GCode/SeamAligned.hpp> #include <libslic3r/GCode/SeamAligned.hpp>
#include "test_data.hpp" #include "test_data.hpp"
#include <fstream> #include <fstream>
using namespace Slic3r; using namespace Slic3r;
using namespace Slic3r::Seams; using namespace Slic3r::Seams;
using namespace Catch;
constexpr bool debug_files{false}; constexpr bool debug_files{false};

View File

@@ -1,9 +1,12 @@
#include <libslic3r/Point.hpp> #include <libslic3r/Point.hpp>
#include <catch2/catch.hpp> #include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_vector.hpp>
#include <catch2/catch_approx.hpp>
#include <libslic3r/GCode/SeamGeometry.hpp> #include <libslic3r/GCode/SeamGeometry.hpp>
#include <libslic3r/Geometry.hpp> #include <libslic3r/Geometry.hpp>
using namespace Slic3r; using namespace Slic3r;
using namespace Catch;
TEST_CASE("Lists mapping", "[Seams][SeamGeometry]") { TEST_CASE("Lists mapping", "[Seams][SeamGeometry]") {
// clang-format off // clang-format off
@@ -123,50 +126,3 @@ TEST_CASE("Vertex angle is rotation agnostic", "[Seams][SeamGeometry]") {
std::vector<double> rotated_angles = Seams::Geometry::get_vertex_angles(points, 0.1); std::vector<double> rotated_angles = Seams::Geometry::get_vertex_angles(points, 0.1);
CHECK(rotated_angles[1] == Approx(angles[1])); CHECK(rotated_angles[1] == Approx(angles[1]));
} }
TEST_CASE("Calculate overhangs", "[Seams][SeamGeometry]") {
const ExPolygon square{
scaled(Vec2d{0.0, 0.0}),
scaled(Vec2d{1.0, 0.0}),
scaled(Vec2d{1.0, 1.0}),
scaled(Vec2d{0.0, 1.0})
};
const std::vector<Vec2d> points{Seams::Geometry::unscaled(square.contour.points)};
ExPolygon previous_layer{square};
previous_layer.translate(scaled(Vec2d{-0.5, 0}));
AABBTreeLines::LinesDistancer<Linef> previous_layer_distancer{
to_unscaled_linesf({previous_layer})};
const std::vector<double> overhangs{
Seams::Geometry::get_overhangs(points, previous_layer_distancer, 0.5)};
REQUIRE(overhangs.size() == points.size());
CHECK_THAT(overhangs, Catch::Matchers::Approx(std::vector<double>{
0.0, M_PI / 4.0, M_PI / 4.0, 0.0
}));
}
const Linesf lines{to_unscaled_linesf({ExPolygon{
scaled(Vec2d{0.0, 0.0}),
scaled(Vec2d{1.0, 0.0}),
scaled(Vec2d{1.0, 1.0}),
scaled(Vec2d{0.0, 1.0})
}})};
TEST_CASE("Offset along loop lines forward", "[Seams][SeamGeometry]") {
const std::optional<Seams::Geometry::PointOnLine> result{Seams::Geometry::offset_along_lines(
{0.5, 0.0}, 0, lines, 3.9, Seams::Geometry::Direction1D::forward
)};
REQUIRE(result);
const auto &[point, line_index] = *result;
CHECK((scaled(point) - Point::new_scale(0.4, 0.0)).norm() < scaled(EPSILON));
CHECK(line_index == 0);
}
TEST_CASE("Offset along loop lines backward", "[Seams][SeamGeometry]") {
const std::optional<Seams::Geometry::PointOnLine> result{Seams::Geometry::offset_along_lines(
{1.0, 0.5}, 1, lines, 1.8, Seams::Geometry::Direction1D::backward
)};
REQUIRE(result);
const auto &[point, line_index] = *result;
CHECK((scaled(point) - Point::new_scale(0.0, 0.3)).norm() < scaled(EPSILON));
CHECK(line_index == 3);
}

View File

@@ -2,7 +2,8 @@
#include "libslic3r/GCode/SeamPerimeters.hpp" #include "libslic3r/GCode/SeamPerimeters.hpp"
#include "libslic3r/Layer.hpp" #include "libslic3r/Layer.hpp"
#include "libslic3r/Point.hpp" #include "libslic3r/Point.hpp"
#include <catch2/catch.hpp> #include <catch2/catch_test_macros.hpp>
#include <catch2/catch_approx.hpp>
#include <libslic3r/GCode/SeamGeometry.hpp> #include <libslic3r/GCode/SeamGeometry.hpp>
#include <libslic3r/Geometry.hpp> #include <libslic3r/Geometry.hpp>
#include <fstream> #include <fstream>
@@ -11,26 +12,29 @@
using namespace Slic3r; using namespace Slic3r;
using namespace Slic3r::Seams; using namespace Slic3r::Seams;
using namespace Catch;
constexpr bool debug_files{false}; constexpr bool debug_files{false};
const ExPolygon square{
scaled(Vec2d{0.0, 0.0}), scaled(Vec2d{1.0, 0.0}), scaled(Vec2d{1.0, 1.0}),
scaled(Vec2d{0.0, 1.0})};
TEST_CASE("Oversample painted", "[Seams][SeamPerimeters]") { TEST_CASE("Oversample painted", "[Seams][SeamPerimeters]") {
Perimeters::PerimeterPoints square(4);
square[0].position = Vec2d{0.0, 0.0};
square[1].position = Vec2d{1.0, 0.0};
square[2].position = Vec2d{1.0, 1.0};
square[3].position = Vec2d{0.0, 1.0};
auto is_painted{[](const Vec3f &position, float radius) { auto is_painted{[](const Vec3f &position, float radius) {
return (position - Vec3f{0.5, 0.0, 1.0}).norm() < radius; return (position - Vec3f{0.5, 0.0, 1.0}).norm() < radius;
}}; }};
std::vector<Vec2d> points{Perimeters::Impl::oversample_painted( Perimeters::PerimeterPoints points{Perimeters::Impl::oversample_painted(
Seams::Geometry::unscaled(square.contour.points), is_painted, 1.0, 0.2 square, is_painted, 1.0, 0.2
)}; )};
REQUIRE(points.size() == 8); REQUIRE(points.size() == 8);
CHECK((points[1] - Vec2d{0.2, 0.0}).norm() == Approx(0.0)); CHECK((points[1].position - Vec2d{0.2, 0.0}).norm() == Approx(0.0));
points = Perimeters::Impl::oversample_painted( points = Perimeters::Impl::oversample_painted(
Seams::Geometry::unscaled(square.contour.points), is_painted, 1.0, 0.199 square, is_painted, 1.0, 0.199
); );
CHECK(points.size() == 9); CHECK(points.size() == 9);
} }
@@ -39,24 +43,35 @@ TEST_CASE("Remove redundant points", "[Seams][SeamPerimeters]") {
using Perimeters::PointType; using Perimeters::PointType;
using Perimeters::PointClassification; using Perimeters::PointClassification;
std::vector<Vec2d> points{{0.0, 0.0}, {1.0, 0.0}, {2.0, 0.0}, {3.0, 0.0}, Perimeters::PerimeterPoints points(9);
{3.0, 1.0}, {3.0, 2.0}, {0.0, 2.0}}; points[0].position = {0.0, 0.0};
std::vector<PointType> point_types{PointType::common, points[0].type = PointType::common;
PointType::enforcer, // Should keep this. points[1].position = {1.0, 0.0};
PointType::enforcer, // Should keep this. points[1].type = PointType::enforcer; // Should keep
PointType::blocker, points[2].position = {2.0, 0.0};
PointType::blocker, // Should remove this. points[2].type = PointType::enforcer; // Should keep
PointType::blocker, PointType::common}; points[3].position = {3.0, 0.0};
points[3].type = PointType::blocker;
points[4].position = {3.0, 1.0};
points[4].type = PointType::blocker; // Should remove
points[5].position = {3.0, 1.1};
points[5].type = PointType::blocker;
points[6].position = {3.0, 1.2};
points[6].type = PointType::blocker;
points[6].classification = PointClassification::overhang; // Should keep
points[7].position = {3.0, 2.0};
points[7].type = PointType::blocker;
points[8].position = {0.0, 2.0};
points[8].type = PointType::common;
const auto [resulting_points, resulting_point_types]{ Perimeters::PerimeterPoints result{
Perimeters::Impl::remove_redundant_points(points, point_types, 0.1)}; Perimeters::Impl::remove_redundant_points(points, 0.1)};
REQUIRE(resulting_points.size() == 6); REQUIRE(result.size() == 8);
REQUIRE(resulting_point_types.size() == 6); CHECK((result[3].position - Vec2d{3.0, 0.0}).norm() == Approx(0.0));
CHECK((resulting_points[3] - Vec2d{3.0, 0.0}).norm() == Approx(0.0)); CHECK((result[4].position - Vec2d{3.0, 1.1}).norm() == Approx(0.0));
CHECK((resulting_points[4] - Vec2d{3.0, 2.0}).norm() == Approx(0.0)); CHECK(result[3].type == PointType::blocker);
CHECK(resulting_point_types[3] == PointType::blocker); CHECK(result[4].type == PointType::blocker);
CHECK(resulting_point_types[4] == PointType::blocker);
} }
TEST_CASE("Perimeter constructs KD trees", "[Seams][SeamPerimeters]") { TEST_CASE("Perimeter constructs KD trees", "[Seams][SeamPerimeters]") {
@@ -89,8 +104,6 @@ TEST_CASE("Perimeter constructs KD trees", "[Seams][SeamPerimeters]") {
CHECK(perimeter.common_points.embedded_points); CHECK(perimeter.common_points.embedded_points);
} }
using std::filesystem::path;
constexpr const char *to_string(Perimeters::PointType point_type) { constexpr const char *to_string(Perimeters::PointType point_type) {
using Perimeters::PointType; using Perimeters::PointType;
@@ -182,3 +195,57 @@ TEST_CASE_METHOD(Test::SeamsFixture, "Create perimeters", "[Seams][SeamPerimeter
serialize_shells(csv, shells); serialize_shells(csv, shells);
} }
} }
using Dir = Seams::Geometry::Direction1D;
Perimeters::Perimeter get_perimeter(){
Perimeters::Perimeter perimeter;
perimeter.positions = {
Vec2d{0.0, 0.0},
Vec2d{1.0, 0.0},
Vec2d{1.0, 1.0},
Vec2d{0.0, 1.0}
};
return perimeter;
}
TEST_CASE("Offset along perimeter forward", "[Seams][SeamPerimeters]") {
const std::optional<Perimeters::PointOnPerimeter> result{Perimeters::offset_along_perimeter(
{0, 1, {0.5, 0.0}}, get_perimeter(), 3.9, Dir::forward,
[](const Perimeters::Perimeter &, const std::size_t) { return false; }
)};
REQUIRE(result);
const auto &[previous_index, next_index, point] = *result;
CHECK((scaled(point) - Point::new_scale(0.4, 0.0)).norm() < scaled(EPSILON));
CHECK(previous_index == 0);
CHECK(next_index == 1);
}
TEST_CASE("Offset along perimeter backward", "[Seams][SeamPerimeters]") {
const std::optional<Perimeters::PointOnPerimeter> result{Perimeters::offset_along_perimeter(
{1, 2, {1.0, 0.5}}, get_perimeter(), 1.8, Dir::backward,
[](const Perimeters::Perimeter &, const std::size_t) { return false; }
)};
REQUIRE(result);
const auto &[previous_index, next_index, point] = *result;
CHECK((scaled(point) - Point::new_scale(0.0, 0.3)).norm() < scaled(EPSILON));
CHECK(previous_index == 3);
CHECK(next_index == 0);
}
TEST_CASE("Offset along perimeter forward respects stop condition", "[Seams][SeamPerimeters]") {
Perimeters::Perimeter perimeter{get_perimeter()};
perimeter.point_types = std::vector<Perimeters::PointType>(perimeter.positions.size(), Perimeters::PointType::common);
perimeter.point_types[2] = Perimeters::PointType::blocker;
const std::optional<Perimeters::PointOnPerimeter> result{Perimeters::offset_along_perimeter(
{0, 1, {0.5, 0.0}}, perimeter, 3.9, Dir::forward,
[](const Perimeters::Perimeter &perimeter, const std::size_t index) {
return perimeter.point_types[index] == Perimeters::PointType::blocker;
}
)};
REQUIRE(result);
const auto &[previous_index, next_index, point] = *result;
CHECK((scaled(point) - Point::new_scale(1.0, 0.0)).norm() < scaled(EPSILON));
CHECK(previous_index == 1);
CHECK(next_index == 1);
}

View File

@@ -1,5 +1,5 @@
#include <libslic3r/Point.hpp> #include <libslic3r/Point.hpp>
#include <catch2/catch.hpp> #include <catch2/catch_test_macros.hpp>
#include <libslic3r/GCode/SeamRandom.hpp> #include <libslic3r/GCode/SeamRandom.hpp>
#include "test_data.hpp" #include "test_data.hpp"
#include <fstream> #include <fstream>
@@ -32,7 +32,7 @@ Perimeters::Perimeter get_perimeter() {
} }
} // namespace RandomTest } // namespace RandomTest
double get_chi2_uniform(const std::vector<double> &data, double min, double max, const std::size_t bin_count) { double get_chi2_uniform(const std::vector<double> &data, const double min, const double max, const std::size_t bin_count) {
std::vector<std::size_t> bins(bin_count); std::vector<std::size_t> bins(bin_count);
const double bin_size{(max - min) / bin_count}; const double bin_size{(max - min) / bin_count};
const double expected_frequncy{static_cast<double>(data.size()) / bin_count}; const double expected_frequncy{static_cast<double>(data.size()) / bin_count};
@@ -62,7 +62,7 @@ TEST_CASE("Random is uniform", "[Seams][SeamRandom]") {
return choice->position.x(); return choice->position.x();
}); });
const std::size_t degrees_of_freedom{10}; const std::size_t degrees_of_freedom{10};
const double critical{18.307}; // dof 10, significance 0.05 const double critical{29.588}; // dof 10, significance 0.001
CHECK(get_chi2_uniform(x_positions, 0.0, 1.0, degrees_of_freedom + 1) < critical); CHECK(get_chi2_uniform(x_positions, 0.0, 1.0, degrees_of_freedom + 1) < critical);
} }

View File

@@ -1,5 +1,5 @@
#include <libslic3r/Point.hpp> #include <libslic3r/Point.hpp>
#include <catch2/catch.hpp> #include <catch2/catch_test_macros.hpp>
#include <libslic3r/GCode/SeamRear.hpp> #include <libslic3r/GCode/SeamRear.hpp>
#include "test_data.hpp" #include "test_data.hpp"
#include <fstream> #include <fstream>

View File

@@ -1,9 +1,11 @@
#include <catch2/catch.hpp> #include <catch2/catch_test_macros.hpp>
#include <catch2/catch_approx.hpp>
#include <libslic3r/GCode/SeamScarf.hpp> #include <libslic3r/GCode/SeamScarf.hpp>
#include <libslic3r/GCode/SmoothPath.hpp> #include <libslic3r/GCode/SmoothPath.hpp>
using namespace Slic3r; using namespace Slic3r;
using Seams::Scarf::Impl::PathPoint; using Seams::Scarf::Impl::PathPoint;
using namespace Catch;
TEST_CASE("Get path point", "[Seams][Scarf]") { TEST_CASE("Get path point", "[Seams][Scarf]") {
using Seams::Scarf::Impl::get_path_point; using Seams::Scarf::Impl::get_path_point;

View File

@@ -1,4 +1,5 @@
#include <catch2/catch.hpp> #include <catch2/catch_test_macros.hpp>
#include <catch2/catch_approx.hpp>
#include <filesystem> #include <filesystem>
#include <fstream> #include <fstream>
#include "libslic3r/ClipperUtils.hpp" #include "libslic3r/ClipperUtils.hpp"
@@ -9,6 +10,7 @@
using namespace Slic3r; using namespace Slic3r;
using namespace Slic3r::Seams; using namespace Slic3r::Seams;
using namespace Catch;
struct ProjectionFixture struct ProjectionFixture
{ {
@@ -21,7 +23,12 @@ struct ProjectionFixture
double extrusion_width{0.2}; double extrusion_width{0.2};
ProjectionFixture() { ProjectionFixture() {
extrusions.emplace_back(Polygon{extrusion_path}, extrusion_path.bounding_box(), extrusion_width, island_boundary); extrusions.emplace_back(
Polygon{extrusion_path},
extrusion_path.bounding_box(),
extrusion_width, island_boundary,
Seams::Geometry::Overhangs{}
);
} }
}; };

View File

@@ -1,4 +1,4 @@
#include <catch2/catch.hpp> #include <catch2/catch_test_macros.hpp>
#include "libslic3r/GCodeReader.hpp" #include "libslic3r/GCodeReader.hpp"

View File

@@ -1,4 +1,5 @@
#include <catch2/catch.hpp> #include <catch2/catch_test_macros.hpp>
#include <catch2/catch_approx.hpp>
#include "libslic3r/GCodeReader.hpp" #include "libslic3r/GCodeReader.hpp"
#include "libslic3r/Config.hpp" #include "libslic3r/Config.hpp"
@@ -10,6 +11,7 @@
using namespace Slic3r::Test; using namespace Slic3r::Test;
using namespace Slic3r; using namespace Slic3r;
using namespace Catch;
/// Helper method to find the tool used for the brim (always the first extrusion) /// Helper method to find the tool used for the brim (always the first extrusion)
static int get_brim_tool(const std::string &gcode) static int get_brim_tool(const std::string &gcode)

View File

@@ -1,4 +1,4 @@
#include <catch2/catch.hpp> #include <catch2/catch_test_macros.hpp>
#include "libslic3r/GCodeReader.hpp" #include "libslic3r/GCodeReader.hpp"
#include "libslic3r/Layer.hpp" #include "libslic3r/Layer.hpp"

View File

@@ -1,7 +1,8 @@
#include <catch2/catch.hpp> #include <catch2/catch_test_macros.hpp>
#include <numeric> #include <numeric>
#include <sstream> #include <sstream>
#include <algorithm>
#include "test_data.hpp" // get access to init_print, etc #include "test_data.hpp" // get access to init_print, etc

View File

@@ -1,4 +1,4 @@
#include <catch2/catch.hpp> #include <catch2/catch_test_macros.hpp>
#include "libslic3r/TriangleMesh.hpp" #include "libslic3r/TriangleMesh.hpp"
#include "libslic3r/TriangleMeshSlicer.hpp" #include "libslic3r/TriangleMeshSlicer.hpp"

View File

@@ -1,4 +1,4 @@
#include <catch2/catch.hpp> #include <catch2/catch_test_macros.hpp>
#include "libslic3r/Model.hpp" #include "libslic3r/Model.hpp"
#include "libslic3r/Format/3mf.hpp" #include "libslic3r/Format/3mf.hpp"

View File

@@ -1,5 +1,6 @@
#include <algorithm> #include <algorithm>
#include <catch2/catch.hpp> #include <catch2/catch_test_macros.hpp>
#include <catch2/catch_approx.hpp>
#include <test_utils.hpp> #include <test_utils.hpp>
#include <libslic3r/TriangleMesh.hpp> #include <libslic3r/TriangleMesh.hpp>
@@ -7,6 +8,7 @@
#include <libslic3r/AABBTreeLines.hpp> #include <libslic3r/AABBTreeLines.hpp>
using namespace Slic3r; using namespace Slic3r;
using namespace Catch;
TEST_CASE("Building a tree over a box, ray caster and closest query", "[AABBIndirect]") TEST_CASE("Building a tree over a box, ray caster and closest query", "[AABBIndirect]")
{ {

View File

@@ -1,4 +1,4 @@
#include <catch2/catch.hpp> #include <catch2/catch_test_macros.hpp>
#include <libslic3r/libslic3r.h> #include <libslic3r/libslic3r.h>
#include <libslic3r/AnyPtr.hpp> #include <libslic3r/AnyPtr.hpp>

View File

@@ -1,4 +1,4 @@
#include <catch2/catch.hpp> #include <catch2/catch_test_macros.hpp>
#include "libslic3r/Arachne/WallToolPaths.hpp" #include "libslic3r/Arachne/WallToolPaths.hpp"
#include "libslic3r/ClipperUtils.hpp" #include "libslic3r/ClipperUtils.hpp"

View File

@@ -1,4 +1,5 @@
#include <catch2/catch.hpp> #include <catch2/catch_test_macros.hpp>
#include <catch2/catch_approx.hpp>
#include <test_utils.hpp> #include <test_utils.hpp>
#include <random> #include <random>
@@ -12,6 +13,7 @@
#include <libslic3r/libslic3r.h> #include <libslic3r/libslic3r.h>
using namespace Slic3r; using namespace Slic3r;
using namespace Catch;
TEST_CASE("arc basics", "[ArcWelder]") { TEST_CASE("arc basics", "[ArcWelder]") {
using namespace Slic3r::Geometry; using namespace Slic3r::Geometry;

View File

@@ -1,4 +1,8 @@
#include <catch2/catch.hpp> #include <catch2/catch_test_macros.hpp>
#include <catch2/catch_template_test_macros.hpp>
#include <catch2/catch_approx.hpp>
#include <algorithm>
#include "libslic3r/BoundingBox.hpp" #include "libslic3r/BoundingBox.hpp"
#include "libslic3r/AStar.hpp" #include "libslic3r/AStar.hpp"
@@ -6,6 +10,7 @@
#include "libslic3r/PointGrid.hpp" #include "libslic3r/PointGrid.hpp"
using namespace Slic3r; using namespace Slic3r;
using namespace Catch;
TEST_CASE("Testing basic invariants of AStar", "[AStar]") { TEST_CASE("Testing basic invariants of AStar", "[AStar]") {
struct DummyTracer { struct DummyTracer {

View File

@@ -1,4 +1,5 @@
#include <catch2/catch.hpp> #include <catch2/catch_test_macros.hpp>
#include <catch2/catch_approx.hpp>
#include <iostream> #include <iostream>
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
@@ -8,6 +9,7 @@
#include "libslic3r/SVG.hpp" #include "libslic3r/SVG.hpp"
using namespace Slic3r; using namespace Slic3r;
using namespace Catch;
// #define TESTS_EXPORT_SVGS // #define TESTS_EXPORT_SVGS

View File

@@ -1,4 +1,5 @@
#include <catch2/catch.hpp> #include <catch2/catch_test_macros.hpp>
#include <catch2/catch_approx.hpp>
#include <numeric> #include <numeric>
#include <iostream> #include <iostream>
@@ -9,6 +10,7 @@
#include "libslic3r/SVG.hpp" #include "libslic3r/SVG.hpp"
using namespace Slic3r; using namespace Slic3r;
using namespace Catch;
SCENARIO("Various Clipper operations - xs/t/11_clipper.t", "[ClipperUtils]") { SCENARIO("Various Clipper operations - xs/t/11_clipper.t", "[ClipperUtils]") {
// CCW oriented contour // CCW oriented contour

View File

@@ -1,4 +1,4 @@
#include <catch2/catch.hpp> #include <catch2/catch_test_macros.hpp>
#include "libslic3r/libslic3r.h" #include "libslic3r/libslic3r.h"
#include "libslic3r/Color.hpp" #include "libslic3r/Color.hpp"

View File

@@ -1,4 +1,6 @@
#include <catch2/catch.hpp> #include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers.hpp>
#include <catch2/matchers/catch_matchers_vector.hpp>
#include "libslic3r/Config.hpp" #include "libslic3r/Config.hpp"
#include "libslic3r/PrintConfig.hpp" #include "libslic3r/PrintConfig.hpp"

View File

@@ -1,10 +1,13 @@
#include <catch2/catch.hpp> #include <catch2/catch_test_macros.hpp>
#include <catch2/catch_approx.hpp>
#include <test_utils.hpp> #include <test_utils.hpp>
#include <libslic3r/Geometry/Curves.hpp> #include <libslic3r/Geometry/Curves.hpp>
#include <libslic3r/Utils.hpp> #include <libslic3r/Utils.hpp>
#include <libslic3r/SVG.hpp> #include <libslic3r/SVG.hpp>
using namespace Catch;
TEST_CASE("Curves: cubic b spline fit test", "[Curves]") { TEST_CASE("Curves: cubic b spline fit test", "[Curves]") {
using namespace Slic3r; using namespace Slic3r;
using namespace Slic3r::Geometry; using namespace Slic3r::Geometry;

View File

@@ -1,4 +1,4 @@
#include <catch2/catch.hpp> #include <catch2/catch_test_macros.hpp>
#include <libslic3r/CutSurface.hpp> #include <libslic3r/CutSurface.hpp>
#include <libslic3r/TriangleMesh.hpp> // its_make_cube + its_merge #include <libslic3r/TriangleMesh.hpp> // its_make_cube + its_merge

Some files were not shown because too many files have changed in this diff Show More