Compare commits

..
Author SHA1 Message Date
Hanif Koh 5704fa348f Stop Format/STEP.hpp Defining a Global fs Alias
Every file that included STEP.hpp, directly or not, got namespace fs = boost::filesystem at global scope, and 29 sources and three headers relied on it without saying so. Headers now spell out boost::filesystem, and each source that uses fs declares the alias itself.
2026-10-03 22:45:13 +08:00
63 changed files with 576 additions and 1860 deletions
+171 -61
View File
@@ -141,6 +141,8 @@ using namespace nlohmann;
#include "slic3r/GUI/GuiColor.hpp"
#include <GLFW/glfw3.h>
namespace fs = boost::filesystem;
#ifdef __WXGTK__
#if __has_include(<X11/Xlib.h>)
#include <X11/Xlib.h>
@@ -685,6 +687,162 @@ static void load_default_gcodes_to_config(DynamicPrintConfig& config, Preset::Ty
}
}
static int load_assemble_plate_list(std::string config_file, std::vector<assemble_plate_info_t> &assemble_plate_info_list)
{
int ret = 0;
boost::filesystem::path directory_path(config_file);
BOOST_LOG_TRIVIAL(info) << boost::format("%1% enter, file %2%")%__FUNCTION__ % config_file;
if (!fs::exists(directory_path)) {
BOOST_LOG_TRIVIAL(error) << boost::format("directory %1% not exist.")%config_file;
return CLI_FILE_NOTFOUND;
}
try {
json root_json;
boost::nowide::ifstream ifs(config_file);
ifs >> root_json;
ifs.close();
int plate_count = root_json[JSON_ASSEMPLE_PLATES].size();
if ((plate_count <= 0) || (plate_count > MAX_PLATE_COUNT)) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__<< boost::format(": invalid plate count %1%")%plate_count;
return CLI_CONFIG_FILE_ERROR;
}
assemble_plate_info_list.resize(plate_count);
for (int plate_index = 0; plate_index < plate_count; plate_index++)
{
assemble_plate_info_t &assemble_plate = assemble_plate_info_list[plate_index];
const json& plate_json = root_json[JSON_ASSEMPLE_PLATES][plate_index];
assemble_plate.plate_name = plate_json[JSON_ASSEMPLE_PLATE_NAME];
assemble_plate.need_arrange = plate_json[JSON_ASSEMPLE_PLATE_NEED_ARRANGE];
if (plate_json.contains(JSON_ASSEMPLE_PLATE_PARAMS)) {
assemble_plate.plate_params = plate_json[JSON_ASSEMPLE_PLATE_PARAMS].get<std::map<std::string, std::string>>();
BOOST_LOG_TRIVIAL(debug) << boost::format("Plate %1%, has %2% plate params") % (plate_index + 1) % assemble_plate.plate_params.size();
}
int object_count = plate_json[JSON_ASSEMPLE_OBJECTS].size();
if (object_count <= 0) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__<< boost::format(": invalid object count %1% in plate %2%")%object_count %(plate_index+1);
return CLI_CONFIG_FILE_ERROR;
}
assemble_plate.assemble_obj_list.resize(object_count);
for (int object_index = 0; object_index < object_count; object_index++)
{
assemble_object_info_t& assemble_object = assemble_plate.assemble_obj_list[object_index];
const json& object_json = plate_json[JSON_ASSEMPLE_OBJECTS][object_index];
assemble_object.path = object_json[JSON_ASSEMPLE_OBJECT_PATH];
assemble_object.count = object_json[JSON_ASSEMPLE_OBJECT_COUNT];
if (assemble_object.count <= 0) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": invalid object clone count %1% in plate %2% Object %3%") % assemble_object.count % (plate_index + 1) % assemble_object.path;
return CLI_CONFIG_FILE_ERROR;
}
assemble_object.filaments = object_json.at(JSON_ASSEMPLE_OBJECT_FILAMENTS).get<std::vector<int>>();
if ((assemble_object.filaments.size() > 0) && (assemble_object.filaments.size() != assemble_object.count) && (assemble_object.filaments.size() != 1))
{
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": object %1%'s filaments count %2% not equal to clone count %3%, also not equal to 1") % assemble_object.path % assemble_object.filaments.size() % assemble_object.count;
return CLI_CONFIG_FILE_ERROR;
}
if (object_json.contains(JSON_ASSEMPLE_OBJECT_ASSEMBLE_INDEX)) {
assemble_object.assemble_index = object_json[JSON_ASSEMPLE_OBJECT_ASSEMBLE_INDEX].get<std::vector<int>>();
if ((assemble_object.assemble_index.size() > 0) && (assemble_object.assemble_index.size() != assemble_object.count) && (assemble_object.assemble_index.size() != 1))
{
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": object %1%'s assemble_index count %2% not equal to clone count %3%, also not equal to 1") % assemble_object.path % assemble_object.assemble_index.size() % assemble_object.count;
return CLI_CONFIG_FILE_ERROR;
}
}
if (object_json.contains(JSON_ASSEMPLE_OBJECT_POS_X)) {
assemble_object.pos_x = object_json[JSON_ASSEMPLE_OBJECT_POS_X].get<std::vector<float>>();
if ((assemble_object.pos_x.size() > 0) && (assemble_object.pos_x.size() != assemble_object.count) && (assemble_object.pos_x.size() != 1))
{
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": object %1%'s pos_x count %2% not equal to clone count %3%, also not equal to 1") % assemble_object.path % assemble_object.pos_x.size() % assemble_object.count;
return CLI_CONFIG_FILE_ERROR;
}
}
if (object_json.contains(JSON_ASSEMPLE_OBJECT_POS_Y)) {
assemble_object.pos_y = object_json[JSON_ASSEMPLE_OBJECT_POS_Y].get<std::vector<float>>();
if ((assemble_object.pos_y.size() > 0) && (assemble_object.pos_y.size() != assemble_object.count) && (assemble_object.pos_y.size() != 1))
{
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": object %1%'s pos_y count %2% not equal to clone count %3%, also not equal to 1") % assemble_object.path % assemble_object.pos_y.size() % assemble_object.count;
return CLI_CONFIG_FILE_ERROR;
}
}
if (object_json.contains(JSON_ASSEMPLE_OBJECT_POS_Z)) {
assemble_object.pos_z = object_json[JSON_ASSEMPLE_OBJECT_POS_Z].get<std::vector<float>>();
if ((assemble_object.pos_z.size() > 0) && (assemble_object.pos_z.size() != assemble_object.count) && (assemble_object.pos_z.size() != 1))
{
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": object %1%'s pos_z count %2% not equal to clone count %3%, also not equal to 1") % assemble_object.path % assemble_object.pos_z.size() % assemble_object.count;
return CLI_CONFIG_FILE_ERROR;
}
}
if (object_json.contains(JSON_ASSEMPLE_OBJECT_PRINT_PARAMS)) {
assemble_object.print_params = object_json[JSON_ASSEMPLE_OBJECT_PRINT_PARAMS].get<std::map<std::string, std::string>>();
BOOST_LOG_TRIVIAL(debug) << boost::format("Plate %1%, object %2% has %3% print params") % (plate_index + 1) %assemble_object.path % assemble_object.print_params.size();
}
if (object_json.contains(JSON_ASSEMPLE_OBJECT_HEIGHT_RANGES)) {
json height_range_json = object_json[JSON_ASSEMPLE_OBJECT_HEIGHT_RANGES];
int range_count = height_range_json.size();
BOOST_LOG_TRIVIAL(debug) << boost::format("Plate %1%, object %2% has %3% height ranges") % (plate_index + 1) %assemble_object.path % range_count;
assemble_object.height_ranges.resize(range_count);
for (int range_index = 0; range_index < range_count; range_index++)
{
height_range_info_t& height_range = assemble_object.height_ranges[range_index];
height_range.min_z = height_range_json[range_index][JSON_ASSEMPLE_OBJECT_MIN_Z];
height_range.max_z = height_range_json[range_index][JSON_ASSEMPLE_OBJECT_MAX_Z];
height_range.range_params = height_range_json[range_index][JSON_ASSEMPLE_OBJECT_RANGE_PARAMS].get<std::map<std::string, std::string>>();
}
}
}
if (plate_json.contains(JSON_ASSEMPLE_ASSEMBLE_PARAMS)) {
json assemble_params_json = plate_json[JSON_ASSEMPLE_ASSEMBLE_PARAMS];
int assemble_count = assemble_params_json.size();
for (int i = 0; i < assemble_count; i++)
{
assembled_param_info_t assembled_param;
int assemble_index = assemble_params_json[i][JSON_ASSEMPLE_OBJECT_ASSEMBLE_INDEX];
if (assemble_params_json[i].contains(JSON_ASSEMPLE_OBJECT_PRINT_PARAMS)) {
assembled_param.print_params = assemble_params_json[i][JSON_ASSEMPLE_OBJECT_PRINT_PARAMS].get<std::map<std::string, std::string>>();
BOOST_LOG_TRIVIAL(debug) << boost::format("Plate %1%, assemble object %2% has %3% print params") % (plate_index + 1) %i % assembled_param.print_params.size();
}
if (assemble_params_json[i].contains(JSON_ASSEMPLE_OBJECT_HEIGHT_RANGES)) {
json height_range_json = assemble_params_json[i][JSON_ASSEMPLE_OBJECT_HEIGHT_RANGES];
int range_count = height_range_json.size();
BOOST_LOG_TRIVIAL(debug) << boost::format("Plate %1%, assemble object %2% has %3% height ranges") % (plate_index + 1) %i % range_count;
assembled_param.height_ranges.resize(range_count);
for (int range_index = 0; range_index < range_count; range_index++)
{
height_range_info_t& height_range = assembled_param.height_ranges[range_index];
height_range.min_z = height_range_json[range_index][JSON_ASSEMPLE_OBJECT_MIN_Z];
height_range.max_z = height_range_json[range_index][JSON_ASSEMPLE_OBJECT_MAX_Z];
height_range.range_params = height_range_json[range_index][JSON_ASSEMPLE_OBJECT_RANGE_PARAMS].get<std::map<std::string, std::string>>();
}
}
assemble_plate.assembled_param_list.emplace(assemble_index, std::move(assembled_param));
}
BOOST_LOG_TRIVIAL(debug) << boost::format("Plate %1%, has %2% plate params") % (plate_index + 1) % assemble_plate.plate_params.size();
}
}
}
catch(std::exception &err) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__<< ": parse file "<<config_file<<" got a generic exception, reason = " << err.what();
ret = CLI_CONFIG_FILE_ERROR;
}
return ret;
}
void merge_or_add_object(assemble_plate_info_t& assemble_plate_info, Model &model, int assemble_index, std::map<int, ModelObject*> &merged_objects, ModelObject *ori_object)
{
if (assemble_index > 0) {
@@ -1750,14 +1908,6 @@ int CLI::run(int argc, char **argv)
BOOST_LOG_TRIVIAL(info) << boost::format("current_printer_name %1%, current_process_name %2%")%current_printer_name %current_process_name;
ConfigOptionStrings* option_strings = config.option<ConfigOptionStrings>("inherits_group");
// One entry for the process, one per filament and one for the printer. A group of another
// length still has the process first and the printer last; one too short for that is ignored.
if (option_strings && option_strings->values.size() != current_filaments_name.size() + 2) {
boost::nowide::cerr << "Warning: inherits_group has " << option_strings->values.size() << " entries, expected "
<< current_filaments_name.size() + 2 << " for " << current_filaments_name.size() << " filaments" << std::endl;
if (option_strings->values.size() < 2)
option_strings = nullptr;
}
if (option_strings) {
current_inherits_group = option_strings->values;
size_t size = current_inherits_group.size();
@@ -1779,11 +1929,14 @@ int CLI::run(int argc, char **argv)
BOOST_LOG_TRIVIAL(info) << boost::format("inherits of process valid, current_process_system_name is %1%") %current_process_system_name;
}
// A filament without an entry of its own counts as a system preset.
current_filaments_system_name = current_filaments_name;
for (size_t index = 1; index < size - 1 && index <= current_filaments_name.size(); index++) {
if (!current_inherits_group[index].empty())
current_filaments_system_name.resize(size - 2);
for (int index = 1; index < (size - 1); index++) {
if (current_inherits_group[index].empty()) {
current_filaments_system_name[index-1] = current_filaments_name[index-1];
}
else {
current_filaments_system_name[index-1] = current_inherits_group[index];
}
}
}
else {
@@ -1916,12 +2069,7 @@ int CLI::run(int argc, char **argv)
//parse the json and assemble object here
Model model;
AssembleListResult list_result = load_assemble_plate_list(load_assemble_list, assemble_plate_info_list, MAX_PLATE_COUNT);
int ret = CLI_SUCCESS;
if (list_result == AssembleListResult::FileNotFound)
ret = CLI_FILE_NOTFOUND;
else if (list_result == AssembleListResult::ConfigError)
ret = CLI_CONFIG_FILE_ERROR;
int ret = load_assemble_plate_list(load_assemble_list, assemble_plate_info_list);
if (ret) {
record_exit_reson(outfile_dir, ret, 0, cli_errors[ret], sliced_info);
flush_and_exit(ret);
@@ -2677,37 +2825,6 @@ int CLI::run(int argc, char **argv)
fetch_compatible_values = true;
}
// Refresh every project filament no loaded filament replaces from its current system preset, as the GUI
// does when it loads the project; the filament merge below keeps the keys the project lists as changed.
// Entries stay in slot order, which the merge's variant bookkeeping relies on.
std::vector<bool> load_filaments_refresh(load_filaments_config.size(), false);
if (is_bbl_3mf && new_printer_name.empty()) {
const ConfigOptionStrings *project_filament_ids = m_print_config.option<ConfigOptionStrings>("filament_ids");
for (size_t index = 0; index < current_filaments_system_name.size(); index++) {
const int slot = static_cast<int>(index) + 1;
if (std::find(load_filaments_index.begin(), load_filaments_index.end(), slot) != load_filaments_index.end())
continue;
std::string system_name = current_filaments_system_name[index];
if (system_name.empty())
continue;
PresetBundle::convert_filament_preset_name(current_printer_name, system_name);
DynamicPrintConfig config;
std::string error;
if (!ensure_system_preset_resolver().resolve_system_preset(config, Preset::TYPE_FILAMENT, system_name, config_substitution_rule, error)) {
BOOST_LOG_TRIVIAL(warning) << boost::format("CLI: system filament preset '%1%' not resolved (%2%); filament %3% keeps its values") % system_name % error % slot;
continue;
}
const size_t at = std::upper_bound(load_filaments_index.begin(), load_filaments_index.end(), slot) - load_filaments_index.begin();
load_filaments_id.insert(load_filaments_id.begin() + at,
project_filament_ids != nullptr && index < project_filament_ids->size() ? project_filament_ids->values[index] : std::string());
load_filaments_name.insert(load_filaments_name.begin() + at, system_name);
load_filaments_config.insert(load_filaments_config.begin() + at, std::move(config));
load_filaments_index.insert(load_filaments_index.begin() + at, slot);
load_filaments_inherit.insert(load_filaments_inherit.begin() + at, system_name);
load_filaments_refresh.insert(load_filaments_refresh.begin() + at, true);
}
}
//fetch upward_compatible_machine
if (fetch_upward_values) {
if (!current_printer_system_name.empty()) {
@@ -3398,7 +3515,7 @@ int CLI::run(int argc, char **argv)
}
//set the filament settings into print config
if ((load_filament_count > 0) || (up_config_to_date) || !load_filaments_config.empty())
if ((load_filament_count > 0) || (up_config_to_date))
{
//std::vector<int> filament_variant_count(filament_count, 1);
std::vector<int> old_start_indice(filament_count, 0);
@@ -3457,8 +3574,6 @@ int CLI::run(int argc, char **argv)
for (int index = 0; index < load_filaments_config.size(); index++) {
DynamicPrintConfig& config = load_filaments_config[index];
int filament_index = load_filaments_index[index];
// A filament given with --load-filaments replaces the slot; a refreshed one keeps the project's changed keys.
const bool loaded = load_filament_count > 0 && !load_filaments_refresh[index];
std::vector<std::string> different_keys;
//ORCA: diff before load_default_gcodes_to_config, the way the process and machine
@@ -3468,12 +3583,12 @@ int CLI::run(int argc, char **argv)
// compared" to "compared as empty against the parent" and land in the column
// as an override the user never made.
std::string filament_different_settings;
if (loaded)
if (load_filament_count > 0)
filament_different_settings = cli_different_settings(config, load_filaments_inherit[index], Preset::TYPE_FILAMENT);
load_default_gcodes_to_config(config, Preset::TYPE_FILAMENT);
if (loaded) {
if (load_filament_count > 0) {
ConfigOptionStrings *opt_filament_settings = static_cast<ConfigOptionStrings *> (m_print_config.option("filament_settings_id", true));
std::string& filament_name = load_filaments_name[index];
ConfigOptionString* filament_name_setting = new ConfigOptionString(filament_name);
@@ -3545,7 +3660,7 @@ int CLI::run(int argc, char **argv)
flush_and_exit(CLI_CONFIG_FILE_ERROR);
}
if (!loaded && !different_keys_set.empty())
if ((load_filament_count == 0) && !different_keys_set.empty())
{
std::set<std::string>::iterator iter = different_keys_set.find(opt_key);
if ( iter != different_keys_set.end()) {
@@ -4735,11 +4850,6 @@ int CLI::run(int argc, char **argv)
record_exit_reson(outfile_dir, CLI_INVALID_PARAMS, 0, cli_errors[CLI_INVALID_PARAMS], sliced_info);
flush_and_exit(CLI_INVALID_PARAMS);
}
if (m_models.empty()) {
boost::nowide::cerr << "Invalid params: --assemble needs at least one input model." << std::endl;
record_exit_reson(outfile_dir, CLI_INVALID_PARAMS, 0, cli_errors[CLI_INVALID_PARAMS], sliced_info);
flush_and_exit(CLI_INVALID_PARAMS);
}
Model m;
ModelObject* new_object = m.add_object();
new_object->name = _u8L("Assembly");
@@ -5680,7 +5790,7 @@ int CLI::run(int argc, char **argv)
float w = dynamic_cast<const ConfigOptionFloat *>(m_print_config.option("prime_tower_width"))->value;
float a = dynamic_cast<const ConfigOptionFloat *>(m_print_config.option("wipe_tower_rotation_angle"))->value;
float v = dynamic_cast<const ConfigOptionFloat *>(m_print_config.option("prime_volume"))->value;
unsigned int filaments_cnt = (plate_data_src.size() >= static_cast<size_t>(plate_to_slice)) ? plate_data_src[plate_to_slice-1]->slice_filaments_info.size() : 0;
unsigned int filaments_cnt = plate_data_src[plate_to_slice-1]->slice_filaments_info.size();
if ((filaments_cnt == 0) || need_skip)
{
// slice filaments info invalid
+58 -1
View File
@@ -12,7 +12,6 @@
#include "libslic3r/Config.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/Format/AssembleList.hpp"
namespace Slic3r {
@@ -27,6 +26,64 @@ namespace IO {
};
}
#define JSON_ASSEMPLE_PLATES "plates"
#define JSON_ASSEMPLE_PLATE_PARAMS "plate_params"
#define JSON_ASSEMPLE_PLATE_NAME "plate_name"
#define JSON_ASSEMPLE_PLATE_NEED_ARRANGE "need_arrange"
#define JSON_ASSEMPLE_OBJECTS "objects"
#define JSON_ASSEMPLE_OBJECT_PATH "path"
#define JSON_ASSEMPLE_OBJECT_COUNT "count"
#define JSON_ASSEMPLE_OBJECT_FILAMENTS "filaments"
#define JSON_ASSEMPLE_OBJECT_POS_X "pos_x"
#define JSON_ASSEMPLE_OBJECT_POS_Y "pos_y"
#define JSON_ASSEMPLE_OBJECT_POS_Z "pos_z"
#define JSON_ASSEMPLE_OBJECT_ASSEMBLE_INDEX "assemble_index"
#define JSON_ASSEMPLE_OBJECT_PRINT_PARAMS "print_params"
#define JSON_ASSEMPLE_ASSEMBLE_PARAMS "assembled_params"
#define JSON_ASSEMPLE_OBJECT_MIN_Z "min_z"
#define JSON_ASSEMPLE_OBJECT_MAX_Z "max_z"
#define JSON_ASSEMPLE_OBJECT_HEIGHT_RANGES "height_ranges"
#define JSON_ASSEMPLE_OBJECT_RANGE_PARAMS "range_params"
typedef struct _height_range_info {
float min_z;
float max_z;
std::map<std::string, std::string> range_params;
}height_range_info_t;
typedef struct _assembled_param_info {
std::map<std::string, std::string> print_params;
std::vector<height_range_info_t> height_ranges;
}assembled_param_info_t;
typedef struct _assemble_object_info {
std::string path;
int count;
std::vector<int> filaments;
std::vector<int> assemble_index;
std::vector<float> pos_x;
std::vector<float> pos_y;
std::vector<float> pos_z;
std::map<std::string, std::string> print_params;
std::vector<height_range_info_t> height_ranges;
}assemble_object_info_t;
typedef struct _assemble_plate_info {
std::string plate_name;
bool need_arrange {false};
int filaments_count {0};
std::map<std::string, std::string> plate_params;
std::vector<assemble_object_info_t> assemble_obj_list;
std::vector<ModelObject *> loaded_obj_list;
std::map<int, assembled_param_info_t> assembled_param_list;
}assemble_plate_info_t;
typedef struct _printer_plate_info {
std::string printer_name;
int printable_width{0};
@@ -58,6 +58,8 @@
#include <set>
#include <string>
namespace fs = boost::filesystem;
using namespace Slic3r;
namespace po = boost::program_options;
+1 -1
View File
@@ -358,7 +358,7 @@ namespace AABBTreeLines {
return dist;
}
std::vector<size_t> all_lines_in_radius(const Vec<LineType::Dim, Scalar>& point, Floating radius) const
std::vector<size_t> all_lines_in_radius(const Vec<LineType::Dim, Scalar>& point, Floating radius)
{
return AABBTreeLines::all_lines_in_radius(this->lines, this->tree, point.template cast<Floating>(), radius * radius);
}
-2
View File
@@ -202,8 +202,6 @@ set(lisbslic3r_sources
Format/3mf.hpp
Format/AMF.cpp
Format/AMF.hpp
Format/AssembleList.cpp
Format/AssembleList.hpp
Format/DRC.cpp
Format/DRC.hpp
Format/bbs_3mf.cpp
+13
View File
@@ -871,6 +871,19 @@ ConfigSubstitutions ConfigBase::load_from_json(const std::string &file, ForwardC
return std::move(substitutions_ctxt.substitutions);
}
// Case-insensitive compare of a JSON key against a fixed ASCII one, without
// boost::iequals, whose std::locale() takes a lock the whole process shares in the
// MSVC runtime.
static bool ascii_iequals(const std::string &key, const char *literal)
{
auto lower = [](char c) { return (c >= 'A' && c <= 'Z') ? char(c - 'A' + 'a') : c; };
size_t i = 0;
for (; i < key.size() && literal[i] != '\0'; ++ i)
if (lower(key[i]) != lower(literal[i]))
return false;
return i == key.size() && literal[i] == '\0';
}
int ConfigBase::load_from_json(const std::string &file, ConfigSubstitutionContext& substitution_context, bool load_inherits_to_config, std::map<std::string, std::string>& key_values, std::string& reason)
{
json j;
@@ -175,23 +175,6 @@ private:
bool is_reverse{true};
};
// Calls `f` for every ExtrusionPath in `entity`, descending into collections, loops and multi-paths.
template<typename F> void for_each_extrusion_path(const ExtrusionEntity &entity, F &&f)
{
if (auto *collection = dynamic_cast<const ExtrusionEntityCollection *>(&entity)) {
for (const ExtrusionEntity *child : collection->entities)
for_each_extrusion_path(*child, f);
} else if (auto *loop = dynamic_cast<const ExtrusionLoop *>(&entity)) {
for (const ExtrusionPath &path : loop->paths)
f(path);
} else if (auto *multi_path = dynamic_cast<const ExtrusionMultiPath *>(&entity)) {
for (const ExtrusionPath &path : multi_path->paths)
f(path);
} else if (auto *path = dynamic_cast<const ExtrusionPath *>(&entity)) {
f(*path);
}
}
} // namespace Slic3r
#endif
-204
View File
@@ -1,204 +0,0 @@
#include "AssembleList.hpp"
#include <algorithm>
#include <boost/filesystem.hpp>
#include <boost/format.hpp>
#include <boost/log/trivial.hpp>
#include <boost/nowide/fstream.hpp>
#include "nlohmann/json.hpp"
#define JSON_ASSEMPLE_PLATES "plates"
#define JSON_ASSEMPLE_PLATE_PARAMS "plate_params"
#define JSON_ASSEMPLE_PLATE_NAME "plate_name"
#define JSON_ASSEMPLE_PLATE_NEED_ARRANGE "need_arrange"
#define JSON_ASSEMPLE_OBJECTS "objects"
#define JSON_ASSEMPLE_OBJECT_PATH "path"
#define JSON_ASSEMPLE_OBJECT_COUNT "count"
#define JSON_ASSEMPLE_OBJECT_FILAMENTS "filaments"
#define JSON_ASSEMPLE_OBJECT_POS_X "pos_x"
#define JSON_ASSEMPLE_OBJECT_POS_Y "pos_y"
#define JSON_ASSEMPLE_OBJECT_POS_Z "pos_z"
#define JSON_ASSEMPLE_OBJECT_ASSEMBLE_INDEX "assemble_index"
#define JSON_ASSEMPLE_OBJECT_PRINT_PARAMS "print_params"
#define JSON_ASSEMPLE_ASSEMBLE_PARAMS "assembled_params"
#define JSON_ASSEMPLE_OBJECT_MIN_Z "min_z"
#define JSON_ASSEMPLE_OBJECT_MAX_Z "max_z"
#define JSON_ASSEMPLE_OBJECT_HEIGHT_RANGES "height_ranges"
#define JSON_ASSEMPLE_OBJECT_RANGE_PARAMS "range_params"
namespace Slic3r {
using json = nlohmann::json;
AssembleListResult load_assemble_plate_list(const std::string &config_file, std::vector<assemble_plate_info_t> &assemble_plate_info_list, int max_plate_count)
{
AssembleListResult ret = AssembleListResult::Success;
boost::filesystem::path directory_path(config_file);
BOOST_LOG_TRIVIAL(info) << boost::format("%1% enter, file %2%")%__FUNCTION__ % config_file;
if (!boost::filesystem::exists(directory_path)) {
BOOST_LOG_TRIVIAL(error) << boost::format("directory %1% not exist.")%config_file;
return AssembleListResult::FileNotFound;
}
try {
json root_json;
boost::nowide::ifstream ifs(config_file);
ifs >> root_json;
ifs.close();
int plate_count = root_json[JSON_ASSEMPLE_PLATES].size();
if ((plate_count <= 0) || (plate_count > max_plate_count)) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__<< boost::format(": invalid plate count %1%")%plate_count;
return AssembleListResult::ConfigError;
}
assemble_plate_info_list.resize(plate_count);
for (int plate_index = 0; plate_index < plate_count; plate_index++)
{
assemble_plate_info_t &assemble_plate = assemble_plate_info_list[plate_index];
const json& plate_json = root_json[JSON_ASSEMPLE_PLATES][plate_index];
assemble_plate.plate_name = plate_json[JSON_ASSEMPLE_PLATE_NAME];
assemble_plate.need_arrange = plate_json[JSON_ASSEMPLE_PLATE_NEED_ARRANGE];
if (plate_json.contains(JSON_ASSEMPLE_PLATE_PARAMS)) {
assemble_plate.plate_params = plate_json[JSON_ASSEMPLE_PLATE_PARAMS].get<std::map<std::string, std::string>>();
BOOST_LOG_TRIVIAL(debug) << boost::format("Plate %1%, has %2% plate params") % (plate_index + 1) % assemble_plate.plate_params.size();
}
int object_count = plate_json[JSON_ASSEMPLE_OBJECTS].size();
if (object_count <= 0) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__<< boost::format(": invalid object count %1% in plate %2%")%object_count %(plate_index+1);
return AssembleListResult::ConfigError;
}
assemble_plate.assemble_obj_list.resize(object_count);
for (int object_index = 0; object_index < object_count; object_index++)
{
assemble_object_info_t& assemble_object = assemble_plate.assemble_obj_list[object_index];
const json& object_json = plate_json[JSON_ASSEMPLE_OBJECTS][object_index];
assemble_object.path = object_json[JSON_ASSEMPLE_OBJECT_PATH];
assemble_object.count = object_json[JSON_ASSEMPLE_OBJECT_COUNT];
if (assemble_object.count <= 0) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": invalid object clone count %1% in plate %2% Object %3%") % assemble_object.count % (plate_index + 1) % assemble_object.path;
return AssembleListResult::ConfigError;
}
assemble_object.filaments = object_json.at(JSON_ASSEMPLE_OBJECT_FILAMENTS).get<std::vector<int>>();
if (assemble_object.filaments.empty())
{
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": object %1%'s filaments list is empty") % assemble_object.path;
return AssembleListResult::ConfigError;
}
if ((assemble_object.filaments.size() != assemble_object.count) && (assemble_object.filaments.size() != 1))
{
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": object %1%'s filaments count %2% not equal to clone count %3%, also not equal to 1") % assemble_object.path % assemble_object.filaments.size() % assemble_object.count;
return AssembleListResult::ConfigError;
}
// 0 keeps the default filament, as it does for --load-filament-ids.
if (std::any_of(assemble_object.filaments.begin(), assemble_object.filaments.end(), [](int id) { return id < 0; }))
{
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": object %1% has a negative filament id") % assemble_object.path;
return AssembleListResult::ConfigError;
}
if (object_json.contains(JSON_ASSEMPLE_OBJECT_ASSEMBLE_INDEX)) {
assemble_object.assemble_index = object_json[JSON_ASSEMPLE_OBJECT_ASSEMBLE_INDEX].get<std::vector<int>>();
if ((assemble_object.assemble_index.size() > 0) && (assemble_object.assemble_index.size() != assemble_object.count) && (assemble_object.assemble_index.size() != 1))
{
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": object %1%'s assemble_index count %2% not equal to clone count %3%, also not equal to 1") % assemble_object.path % assemble_object.assemble_index.size() % assemble_object.count;
return AssembleListResult::ConfigError;
}
}
if (object_json.contains(JSON_ASSEMPLE_OBJECT_POS_X)) {
assemble_object.pos_x = object_json[JSON_ASSEMPLE_OBJECT_POS_X].get<std::vector<float>>();
if ((assemble_object.pos_x.size() > 0) && (assemble_object.pos_x.size() != assemble_object.count) && (assemble_object.pos_x.size() != 1))
{
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": object %1%'s pos_x count %2% not equal to clone count %3%, also not equal to 1") % assemble_object.path % assemble_object.pos_x.size() % assemble_object.count;
return AssembleListResult::ConfigError;
}
}
if (object_json.contains(JSON_ASSEMPLE_OBJECT_POS_Y)) {
assemble_object.pos_y = object_json[JSON_ASSEMPLE_OBJECT_POS_Y].get<std::vector<float>>();
if ((assemble_object.pos_y.size() > 0) && (assemble_object.pos_y.size() != assemble_object.count) && (assemble_object.pos_y.size() != 1))
{
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": object %1%'s pos_y count %2% not equal to clone count %3%, also not equal to 1") % assemble_object.path % assemble_object.pos_y.size() % assemble_object.count;
return AssembleListResult::ConfigError;
}
}
if (object_json.contains(JSON_ASSEMPLE_OBJECT_POS_Z)) {
assemble_object.pos_z = object_json[JSON_ASSEMPLE_OBJECT_POS_Z].get<std::vector<float>>();
if ((assemble_object.pos_z.size() > 0) && (assemble_object.pos_z.size() != assemble_object.count) && (assemble_object.pos_z.size() != 1))
{
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": object %1%'s pos_z count %2% not equal to clone count %3%, also not equal to 1") % assemble_object.path % assemble_object.pos_z.size() % assemble_object.count;
return AssembleListResult::ConfigError;
}
}
if (object_json.contains(JSON_ASSEMPLE_OBJECT_PRINT_PARAMS)) {
assemble_object.print_params = object_json[JSON_ASSEMPLE_OBJECT_PRINT_PARAMS].get<std::map<std::string, std::string>>();
BOOST_LOG_TRIVIAL(debug) << boost::format("Plate %1%, object %2% has %3% print params") % (plate_index + 1) %assemble_object.path % assemble_object.print_params.size();
}
if (object_json.contains(JSON_ASSEMPLE_OBJECT_HEIGHT_RANGES)) {
json height_range_json = object_json[JSON_ASSEMPLE_OBJECT_HEIGHT_RANGES];
int range_count = height_range_json.size();
BOOST_LOG_TRIVIAL(debug) << boost::format("Plate %1%, object %2% has %3% height ranges") % (plate_index + 1) %assemble_object.path % range_count;
assemble_object.height_ranges.resize(range_count);
for (int range_index = 0; range_index < range_count; range_index++)
{
height_range_info_t& height_range = assemble_object.height_ranges[range_index];
height_range.min_z = height_range_json[range_index][JSON_ASSEMPLE_OBJECT_MIN_Z];
height_range.max_z = height_range_json[range_index][JSON_ASSEMPLE_OBJECT_MAX_Z];
height_range.range_params = height_range_json[range_index][JSON_ASSEMPLE_OBJECT_RANGE_PARAMS].get<std::map<std::string, std::string>>();
}
}
}
if (plate_json.contains(JSON_ASSEMPLE_ASSEMBLE_PARAMS)) {
json assemble_params_json = plate_json[JSON_ASSEMPLE_ASSEMBLE_PARAMS];
int assemble_count = assemble_params_json.size();
for (int i = 0; i < assemble_count; i++)
{
assembled_param_info_t assembled_param;
int assemble_index = assemble_params_json[i][JSON_ASSEMPLE_OBJECT_ASSEMBLE_INDEX];
if (assemble_params_json[i].contains(JSON_ASSEMPLE_OBJECT_PRINT_PARAMS)) {
assembled_param.print_params = assemble_params_json[i][JSON_ASSEMPLE_OBJECT_PRINT_PARAMS].get<std::map<std::string, std::string>>();
BOOST_LOG_TRIVIAL(debug) << boost::format("Plate %1%, assemble object %2% has %3% print params") % (plate_index + 1) %i % assembled_param.print_params.size();
}
if (assemble_params_json[i].contains(JSON_ASSEMPLE_OBJECT_HEIGHT_RANGES)) {
json height_range_json = assemble_params_json[i][JSON_ASSEMPLE_OBJECT_HEIGHT_RANGES];
int range_count = height_range_json.size();
BOOST_LOG_TRIVIAL(debug) << boost::format("Plate %1%, assemble object %2% has %3% height ranges") % (plate_index + 1) %i % range_count;
assembled_param.height_ranges.resize(range_count);
for (int range_index = 0; range_index < range_count; range_index++)
{
height_range_info_t& height_range = assembled_param.height_ranges[range_index];
height_range.min_z = height_range_json[range_index][JSON_ASSEMPLE_OBJECT_MIN_Z];
height_range.max_z = height_range_json[range_index][JSON_ASSEMPLE_OBJECT_MAX_Z];
height_range.range_params = height_range_json[range_index][JSON_ASSEMPLE_OBJECT_RANGE_PARAMS].get<std::map<std::string, std::string>>();
}
}
assemble_plate.assembled_param_list.emplace(assemble_index, std::move(assembled_param));
}
BOOST_LOG_TRIVIAL(debug) << boost::format("Plate %1%, has %2% plate params") % (plate_index + 1) % assemble_plate.plate_params.size();
}
}
}
catch(std::exception &err) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__<< ": parse file "<<config_file<<" got a generic exception, reason = " << err.what();
ret = AssembleListResult::ConfigError;
}
return ret;
}
} // namespace Slic3r
-60
View File
@@ -1,60 +0,0 @@
#ifndef slic3r_Format_AssembleList_hpp_
#define slic3r_Format_AssembleList_hpp_
#include <map>
#include <string>
#include <vector>
namespace Slic3r {
class ModelObject;
typedef struct _height_range_info {
float min_z;
float max_z;
std::map<std::string, std::string> range_params;
}height_range_info_t;
typedef struct _assembled_param_info {
std::map<std::string, std::string> print_params;
std::vector<height_range_info_t> height_ranges;
}assembled_param_info_t;
typedef struct _assemble_object_info {
std::string path;
int count;
std::vector<int> filaments;
std::vector<int> assemble_index;
std::vector<float> pos_x;
std::vector<float> pos_y;
std::vector<float> pos_z;
std::map<std::string, std::string> print_params;
std::vector<height_range_info_t> height_ranges;
}assemble_object_info_t;
typedef struct _assemble_plate_info {
std::string plate_name;
bool need_arrange {false};
int filaments_count {0};
std::map<std::string, std::string> plate_params;
std::vector<assemble_object_info_t> assemble_obj_list;
std::vector<ModelObject *> loaded_obj_list;
std::map<int, assembled_param_info_t> assembled_param_list;
}assemble_plate_info_t;
enum class AssembleListResult {
Success,
FileNotFound,
// Malformed JSON, a missing required field, or a value that fails validation.
ConfigError
};
// Read the JSON assemble list used by the CLI's --load-assemble-list into one entry per plate.
AssembleListResult load_assemble_plate_list(const std::string &config_file, std::vector<assemble_plate_info_t> &assemble_plate_info_list, int max_plate_count);
} // namespace Slic3r
#endif /* slic3r_Format_AssembleList_hpp_ */
+2
View File
@@ -60,6 +60,8 @@
#include "BRepTools.hxx"
#include <IMeshTools_Parameters.hxx>
namespace fs = boost::filesystem;
namespace Slic3r {
+1 -2
View File
@@ -17,7 +17,6 @@
#include <ostream>
#include <vector>
namespace fs = boost::filesystem;
namespace Slic3r {
@@ -104,7 +103,7 @@ public:
MESH_SUCCESS,
MESH_ERROR
};
Step(fs::path path, ImportStepProgressFn stepFn = nullptr, StepIsUtf8Fn isUtf8Fn = nullptr);
Step(boost::filesystem::path path, ImportStepProgressFn stepFn = nullptr, StepIsUtf8Fn isUtf8Fn = nullptr);
Step(std::string path, ImportStepProgressFn stepFn = nullptr, StepIsUtf8Fn isUtf8Fn = nullptr);
~Step();
Step_Status load();
+76 -123
View File
@@ -133,6 +133,8 @@ using namespace std::literals::string_view_literals;
#include <assert.h>
namespace fs = boost::filesystem;
namespace Slic3r {
//! macro used to mark string used at localization,
@@ -806,13 +808,13 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
GCodeReader parser;
parser.parse_buffer(gcode, [&changes](GCodeReader &parser, const GCodeReader::GCodeLine &line) {
const std::string_view cmd = line.cmd();
if (ascii_iequals(cmd, "M204") || ascii_iequals(cmd, "M201") ||
ascii_iequals(cmd, "M202"))
if (boost::iequals(cmd, "M204") || boost::iequals(cmd, "M201") ||
boost::iequals(cmd, "M202"))
changes.acceleration = true;
else if ((ascii_iequals(cmd, "M205") || ascii_iequals(cmd, "M207") || ascii_iequals(cmd, "M566")) &&
else if ((boost::iequals(cmd, "M205") || boost::iequals(cmd, "M207") || boost::iequals(cmd, "M566")) &&
custom_gcode_line_has_xy_parameter(line.raw()))
changes.jerk = true;
else if (ascii_iequals(cmd, "SET_VELOCITY_LIMIT")) {
else if (boost::iequals(cmd, "SET_VELOCITY_LIMIT")) {
changes.acceleration |= boost::icontains(line.raw(), "ACCEL=");
changes.jerk |= boost::icontains(line.raw(), "SQUARE_CORNER_VELOCITY=");
}
@@ -4404,75 +4406,6 @@ size_t GCode::get_nozzle_config_index(int filament_id) const
return get_extruder_id(filament_id);
}
namespace {
struct PrecomputedLayer
{
size_t index{size_t(-1)}; // size_t(-1) for the empty layer after the last
std::vector<PrecomputedOverhangLayer> overhang_layers;
};
} // namespace
template<typename BoolsOption> static bool any_enabled(const BoolsOption &option)
{
return std::any_of(option.values.begin(), option.values.end(), [](unsigned char enabled) { return enabled != 0; });
}
// Whether process_layer() prepares the overhang estimator for `layer`.
template<typename OverhangSpeed>
static bool prepares_overhang_estimator(const Layer &layer, bool overhang_fan, OverhangSpeed overhang_speed)
{
const LayerRegionPtrs &regions = layer.regions();
return std::any_of(regions.begin(), regions.end(), [overhang_fan, &overhang_speed](const LayerRegion *region) {
return region->has_extrusions() && (overhang_fan || overhang_speed(*region));
});
}
std::vector<PrecomputedOverhangLayer> precompute_overhang_layers(const std::vector<GCode::LayerToPrint> &layers, bool overhang_fan)
{
// Any filament may print the layer, so a region's overhang speed counts if it is enabled for any.
auto overhang_speed = [](const LayerRegion &region) { return any_enabled(region.region().config().enable_overhang_speed); };
std::vector<PrecomputedOverhangLayer> out;
for (const GCode::LayerToPrint &layer : layers)
if (layer.object_layer != nullptr && layer.object_layer->lower_layer != nullptr &&
prepares_overhang_estimator(*layer.object_layer, overhang_fan, overhang_speed)) {
const LayerRegionPtrs &regions = layer.object_layer->regions();
const bool curled_lines = std::any_of(regions.begin(), regions.end(), [](const LayerRegion *region) {
return any_enabled(region->region().config().slowdown_for_curled_perimeters);
});
out.push_back(precompute_overhang_layer(layer.original_object, *layer.object_layer, curled_lines));
}
return out;
}
// Hands out the index of each layer to process_layers(), then computes the layers' overhang data in parallel.
template<typename LayersAt>
static auto precomputed_layers_source(size_t &next_index, size_t layer_count, bool nop_layer, bool overhang_fan, LayersAt layers_at)
{
return tbb::make_filter<void, PrecomputedLayer>(slic3r_tbb_filtermode::serial_in_order,
[&next_index, layer_count, nop_layer](tbb::flow_control &fc) -> PrecomputedLayer {
if (next_index < layer_count)
return {next_index++};
// The pressure equalizer returns one layer back, so it gets an empty layer after the last.
if (next_index == layer_count + (nop_layer ? 1 : 0))
fc.stop();
else
++next_index;
return {};
}) &
tbb::make_filter<PrecomputedLayer, PrecomputedLayer>(slic3r_tbb_filtermode::parallel,
[layers_at, overhang_fan](PrecomputedLayer layer) -> PrecomputedLayer {
if (layer.index != size_t(-1))
layer.overhang_layers = precompute_overhang_layers(layers_at(layer.index), overhang_fan);
return layer;
});
}
// Whether the overhang fan can switch on for any filament.
static bool overhang_fan_enabled(const PrintConfig &config, bool cooling_markers)
{
return cooling_markers && any_enabled(config.enable_overhang_bridge_fan);
}
// Process all layers of all objects (non-sequential mode) with a parallel pipeline:
// Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser
// and export G-code into file.
@@ -4485,23 +4418,29 @@ void GCode::process_layers(
{
// The pipeline is variable: The vase mode filter is optional.
size_t layer_to_print_idx = 0;
const auto source = precomputed_layers_source(layer_to_print_idx, layers_to_print.size(), m_pressure_equalizer != nullptr,
overhang_fan_enabled(print.config(), m_enable_cooling_markers),
[&layers_to_print](size_t index) -> const std::vector<LayerToPrint> & { return layers_to_print[index].second; });
const auto generator = tbb::make_filter<PrecomputedLayer, LayerResult>(slic3r_tbb_filtermode::serial_in_order,
[this, &print, &tool_ordering, &print_object_instances_ordering, &layers_to_print](PrecomputedLayer precomputed) -> LayerResult {
if (precomputed.index == size_t(-1))
return LayerResult::make_nop_layer_result();
const std::pair<coordf_t, std::vector<LayerToPrint>>& layer = layers_to_print[precomputed.index];
const LayerTools& layer_tools = tool_ordering.tools_for_layer(layer.first);
print.set_status(80, Slic3r::format(_(L("Generating G-code: layer %1%")), std::to_string(precomputed.index + 1)));
if (m_wipe_tower && layer_tools.has_wipe_tower)
m_wipe_tower->next_layer();
//BBS
check_placeholder_parser_failed();
print.throw_if_canceled();
m_extrusion_quality_estimator.set_precomputed_layers(std::move(precomputed.overhang_layers));
return this->process_layer(print, layer.second, layer_tools, &layer == &layers_to_print.back(), &print_object_instances_ordering, tool_ordering.get_most_used_extruder(), size_t(-1));
const auto generator = tbb::make_filter<void, LayerResult>(slic3r_tbb_filtermode::serial_in_order,
[this, &print, &tool_ordering, &print_object_instances_ordering, &layers_to_print, &layer_to_print_idx](tbb::flow_control& fc) -> LayerResult {
if (layer_to_print_idx >= layers_to_print.size()) {
if (layer_to_print_idx == layers_to_print.size() + (m_pressure_equalizer ? 1 : 0)) {
fc.stop();
return {};
} else {
// Pressure equalizer need insert empty input. Because it returns one layer back.
// Insert NOP (no operation) layer;
++layer_to_print_idx;
return LayerResult::make_nop_layer_result();
}
} else {
const std::pair<coordf_t, std::vector<LayerToPrint>>& layer = layers_to_print[layer_to_print_idx++];
const LayerTools& layer_tools = tool_ordering.tools_for_layer(layer.first);
print.set_status(80, Slic3r::format(_(L("Generating G-code: layer %1%")), std::to_string(layer_to_print_idx)));
if (m_wipe_tower && layer_tools.has_wipe_tower)
m_wipe_tower->next_layer();
//BBS
check_placeholder_parser_failed();
print.throw_if_canceled();
return this->process_layer(print, layer.second, layer_tools, &layer == &layers_to_print.back(), &print_object_instances_ordering, tool_ordering.get_most_used_extruder(), size_t(-1));
}
});
if (m_spiral_vase) {
float nozzle_diameter = EXTRUDER_CONFIG(nozzle_diameter);
@@ -4559,15 +4498,13 @@ void GCode::process_layers(
// The pipeline elements are joined using const references, thus no copying is performed.
if (m_spiral_vase && m_pressure_equalizer)
tbb::parallel_pipeline(12, source & generator & spiral_mode & pressure_equalizer & cooling & fan_mover & output);
tbb::parallel_pipeline(12, generator & spiral_mode & pressure_equalizer & cooling & fan_mover & output);
else if (m_spiral_vase)
tbb::parallel_pipeline(12, source & generator & spiral_mode & cooling & fan_mover & output);
tbb::parallel_pipeline(12, generator & spiral_mode & cooling & fan_mover & output);
else if (m_pressure_equalizer)
tbb::parallel_pipeline(12, source & generator & pressure_equalizer & cooling & fan_mover & pa_processor_filter & output);
tbb::parallel_pipeline(12, generator & pressure_equalizer & cooling & fan_mover & pa_processor_filter & output);
else
tbb::parallel_pipeline(12, source & generator & cooling & fan_mover & pa_processor_filter & output);
// The estimator's precomputed data points into this print's layers.
m_extrusion_quality_estimator.set_precomputed_layers({});
tbb::parallel_pipeline(12, generator & cooling & fan_mover & pa_processor_filter & output);
}
@@ -4585,20 +4522,26 @@ void GCode::process_layers(
{
// The pipeline is variable: The vase mode filter is optional.
size_t layer_to_print_idx = 0;
const auto source = precomputed_layers_source(layer_to_print_idx, layers_to_print.size(), m_pressure_equalizer != nullptr,
overhang_fan_enabled(print.config(), m_enable_cooling_markers),
[&layers_to_print](size_t index) { return std::vector<LayerToPrint>{layers_to_print[index]}; });
const auto generator = tbb::make_filter<PrecomputedLayer, LayerResult>(slic3r_tbb_filtermode::serial_in_order,
[this, &print, &tool_ordering, &layers_to_print, single_object_idx, prime_extruder](PrecomputedLayer precomputed) -> LayerResult {
if (precomputed.index == size_t(-1))
return LayerResult::make_nop_layer_result();
LayerToPrint &layer = layers_to_print[precomputed.index];
print.set_status(80, Slic3r::format(_(L("Generating G-code: layer %1%")), std::to_string(precomputed.index + 1)));
//BBS
check_placeholder_parser_failed();
print.throw_if_canceled();
m_extrusion_quality_estimator.set_precomputed_layers(std::move(precomputed.overhang_layers));
return this->process_layer(print, { std::move(layer) }, tool_ordering.tools_for_layer(layer.print_z()), &layer == &layers_to_print.back(), nullptr, tool_ordering.get_most_used_extruder(), single_object_idx, prime_extruder);
const auto generator = tbb::make_filter<void, LayerResult>(slic3r_tbb_filtermode::serial_in_order,
[this, &print, &tool_ordering, &layers_to_print, &layer_to_print_idx, single_object_idx, prime_extruder](tbb::flow_control& fc) -> LayerResult {
if (layer_to_print_idx >= layers_to_print.size()) {
if (layer_to_print_idx == layers_to_print.size() + (m_pressure_equalizer ? 1 : 0)) {
fc.stop();
return {};
} else {
// Pressure equalizer need insert empty input. Because it returns one layer back.
// Insert NOP (no operation) layer;
++layer_to_print_idx;
return LayerResult::make_nop_layer_result();
}
} else {
LayerToPrint &layer = layers_to_print[layer_to_print_idx ++];
print.set_status(80, Slic3r::format(_(L("Generating G-code: layer %1%")), std::to_string(layer_to_print_idx)));
//BBS
check_placeholder_parser_failed();
print.throw_if_canceled();
return this->process_layer(print, { std::move(layer) }, tool_ordering.tools_for_layer(layer.print_z()), &layer == &layers_to_print.back(), nullptr, tool_ordering.get_most_used_extruder(), single_object_idx, prime_extruder);
}
});
if (m_spiral_vase) {
float nozzle_diameter = EXTRUDER_CONFIG(nozzle_diameter);
@@ -4653,15 +4596,13 @@ void GCode::process_layers(
// The pipeline elements are joined using const references, thus no copying is performed.
if (m_spiral_vase && m_pressure_equalizer)
tbb::parallel_pipeline(12, source & generator & spiral_mode & pressure_equalizer & cooling & fan_mover & output);
tbb::parallel_pipeline(12, generator & spiral_mode & pressure_equalizer & cooling & fan_mover & output);
else if (m_spiral_vase)
tbb::parallel_pipeline(12, source & generator & spiral_mode & cooling & fan_mover & output);
tbb::parallel_pipeline(12, generator & spiral_mode & cooling & fan_mover & output);
else if (m_pressure_equalizer)
tbb::parallel_pipeline(12, source & generator & pressure_equalizer & cooling & fan_mover & pa_processor_filter & output);
tbb::parallel_pipeline(12, generator & pressure_equalizer & cooling & fan_mover & pa_processor_filter & output);
else
tbb::parallel_pipeline(12, source & generator & cooling & fan_mover & pa_processor_filter & output);
// The estimator's precomputed data points into this print's layers.
m_extrusion_quality_estimator.set_precomputed_layers({});
tbb::parallel_pipeline(12, generator & cooling & fan_mover & pa_processor_filter & output);
}
std::string GCode::placeholder_parser_process(const std::string &name, const std::string &templ, unsigned int current_filament_id, const DynamicConfig *config_override)
@@ -6021,13 +5962,25 @@ LayerResult GCode::process_layer(
return next_extruder;
};
const bool overhang_fan = overhang_fan_enabled(m_config, m_enable_cooling_markers);
auto overhang_speed = [this](const LayerRegion &region) {
return bool(region.region().config().enable_overhang_speed.get_at(get_nozzle_config_index(m_writer.filament()->id())));
};
for (const auto &layer_to_print : layers)
if (layer_to_print.object_layer && prepares_overhang_estimator(*layer_to_print.object_layer, overhang_fan, overhang_speed))
m_extrusion_quality_estimator.prepare_for_new_layer(layer_to_print.original_object, layer_to_print.object_layer);
for (const auto &layer_to_print : layers) {
if (layer_to_print.object_layer) {
const auto& regions = layer_to_print.object_layer->regions();
const bool has_extrusions = std::any_of(regions.begin(), regions.end(), [](const LayerRegion* r) {
return r->has_extrusions();
});
const bool enable_overhang_speed = std::any_of(regions.begin(), regions.end(), [this](const LayerRegion* r) {
return r->has_extrusions() && r->region().config().enable_overhang_speed.get_at(get_nozzle_config_index(m_writer.filament()->id()));
});
const bool enable_overhang_fan = m_enable_cooling_markers && has_extrusions &&
std::any_of(m_config.enable_overhang_bridge_fan.values.begin(),
m_config.enable_overhang_bridge_fan.values.end(),
[](unsigned char value) { return value != 0; });
if (enable_overhang_speed || enable_overhang_fan) {
m_extrusion_quality_estimator.prepare_for_new_layer(layer_to_print.original_object,
layer_to_print.object_layer);
}
}
}
// Group extrusions by an extruder, then by an object, an island and a region.
std::map<unsigned int, std::vector<ObjectByExtruder>> by_extruder;
-4
View File
@@ -864,10 +864,6 @@ private:
std::vector<const PrintInstance*> sort_object_instances_by_model_order(const Print& print, bool init_order = false);
// The overhang data ExtrusionQualityEstimator needs for the object layers in `layers`, computed ahead of the generator;
// `overhang_fan` says whether the overhang fan can switch on for any filament.
std::vector<PrecomputedOverhangLayer> precompute_overhang_layers(const std::vector<GCode::LayerToPrint> &layers, bool overhang_fan);
}
#endif
@@ -41,9 +41,6 @@ AdaptivePAProcessor::AdaptivePAProcessor(GCode &gcodegen)
m_pa_change_pattern(R"(; PA_CHANGE:T(\d+) MM3MM:([0-9]*\.[0-9]+) ACCEL:(\d+) BR:(\d+) RC:(\d+) OV:(\d+))"),
m_g1_f_pattern(R"(G1 F([0-9]+))")
{
const size_t indices = std::max(m_config.adaptive_pressure_advance.size(), m_config.enable_pressure_advance.size());
for (size_t i = 0; i < indices && !m_enabled; ++i)
m_enabled = m_config.adaptive_pressure_advance.get_at(i) && m_config.enable_pressure_advance.get_at(i);
}
// Method to get the interpolator for a specific filament config index.
@@ -69,12 +66,6 @@ AdaptivePAInterpolator* AdaptivePAProcessor::getInterpolator(unsigned int config
* @return A string containing the processed G-code with adaptive pressure advance applied.
*/
std::string AdaptivePAProcessor::process_layer(std::string &&gcode) {
// Without PA_CHANGE tags the loop below would only terminate the layer's last line.
if (!m_enabled && gcode.find("; PA_CHANGE") == std::string::npos) {
if (!gcode.empty() && gcode.back() != '\n')
gcode += '\n';
return std::move(gcode);
}
std::istringstream stream(gcode);
std::string line;
std::ostringstream output;
@@ -78,7 +78,6 @@ private:
double m_next_feedrate; ///< First feed rate (speed) for the upcomming island.
double m_current_feedrate; ///< Current, latest feedrate.
int m_last_config_index; ///< Filament config index of the last PA_CHANGE tag.
bool m_enabled{false}; ///< Whether any filament config index has both PA and adaptive PA on, the only ones that emit PA_CHANGE tags.
std::regex m_pa_change_pattern; ///< Regular expression to detect PA_CHANGE pattern.
std::regex m_g1_f_pattern; ///< Regular expression to detect G1 F pattern.
+5 -5
View File
@@ -1,5 +1,4 @@
#include "../GCode.hpp"
#include "../LocalesUtils.hpp"
#include "libslic3r/Extruder.hpp"
#include "libslic3r/Point.hpp"
#include "libslic3r/libslic3r.h"
@@ -409,13 +408,13 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
if (*c == 0 || *c == ';')
break;
assert(is_decimal_separator_point()); // for atof
//BBS: Parse the axis.
size_t axis = (*c >= 'X' && *c <= 'Z') ? (*c - 'X') :
(*c == 'E') ? 3 : (*c == 'F') ? 4 :
(*c == 'I') ? 5 : (*c == 'J') ? 6 : size_t(-1);
if (axis != size_t(-1)) {
++ c;
new_pos[axis] = float(atof_decimal_point(std::string_view(c, sline.data() + sline.size() - c)));
new_pos[axis] = float(atof(++c));
if (axis == 4) {
// Convert mm/min to mm/sec.
new_pos[4] /= 60.f;
@@ -551,9 +550,10 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
line.type = CoolingLine::TYPE_G4;
size_t pos_S = sline.find('S', 3);
size_t pos_P = sline.find('P', 3);
assert(is_decimal_separator_point()); // for atof
line.time = line.time_max = float(
(pos_S > 0) ? atof_decimal_point(sline.c_str() + pos_S + 1) :
(pos_P > 0) ? atof_decimal_point(sline.c_str() + pos_P + 1) * 0.001 : 0.);
(pos_S > 0) ? atof(sline.c_str() + pos_S + 1) :
(pos_P > 0) ? atof(sline.c_str() + pos_P + 1) * 0.001 : 0.);
} else if (boost::starts_with(sline, ";_FORCE_RESUME_FAN_SPEED")) {
line.type = CoolingLine::TYPE_FORCE_RESUME_FAN;
}
+22 -128
View File
@@ -24,7 +24,6 @@
#include <cstddef>
#include <functional>
#include <limits>
#include <memory>
#include <numeric>
#include <unordered_map>
#include <utility>
@@ -39,14 +38,7 @@ template<int Dim> struct ExtendedPoint
float curvature;
};
// A KNOWN_DISTANCES functor that knows no distances, so every input point is queried.
struct NoKnownDistances
{
template<typename P> const double *operator()(const P &) const { return nullptr; }
};
template<bool SCALED_INPUT, bool ADD_INTERSECTIONS, bool PREV_LAYER_BOUNDARY_OFFSET, bool SIGNED_DISTANCE, bool CURVATURE = true,
typename POINTS, typename L, typename KNOWN_DISTANCES = NoKnownDistances>
template<bool SCALED_INPUT, bool ADD_INTERSECTIONS, bool PREV_LAYER_BOUNDARY_OFFSET, bool SIGNED_DISTANCE, typename POINTS, typename L>
std::vector<ExtendedPoint<L::Dim>> estimate_points_properties(const POINTS& input_points,
const AABBTreeLines::LinesDistancer<L>& unscaled_prev_layer,
float flow_width,
@@ -57,9 +49,7 @@ std::vector<ExtendedPoint<L::Dim>> estimate_points_properties(const POINTS&
const std::function<float(float)>& distance_to_speed = {},
// Overlap (1 - distance / flow_width) at or below which the overhang
// fan switches on; negative when the fan does not depend on overlap.
float fan_overlap_threshold = -1.0f,
// Returns an input point's signed distance if already known, else nullptr.
const KNOWN_DISTANCES& known_distance = KNOWN_DISTANCES{})
float fan_overlap_threshold = -1.0f)
{
bool looped = input_points.front() == input_points.back();
std::function<size_t(size_t,size_t)> get_prev_index = [](size_t idx, size_t count) {
@@ -104,26 +94,21 @@ std::vector<ExtendedPoint<L::Dim>> estimate_points_properties(const POINTS&
float boundary_offset = PREV_LAYER_BOUNDARY_OFFSET ? 0.5 * flow_width : 0.0f;
auto maybe_unscale = [](const P& p) -> Vec { return SCALED_INPUT ? unscaled(p) : p.template cast<double>(); };
using Distance = typename AABBTreeLines::LinesDistancer<L>::Floating;
auto input_distance = [&unscaled_prev_layer, &known_distance](const P &input, const Vec &position) -> Distance {
if (const double *known = known_distance(input))
return Distance(*known);
auto [distance, nearest_line, x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(
position.template cast<AABBScalar>());
return distance;
};
std::vector<ExtendedPoint<L::Dim>> points;
points.reserve(input_points.size() * (ADD_INTERSECTIONS ? 1.5 : 1));
{
ExtendedPoint<L::Dim> start_point{maybe_unscale(input_points.front())};
start_point.distance = input_distance(input_points.front(), start_point.position) + boundary_offset;
auto [distance, nearest_line, x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(
start_point.position.template cast<AABBScalar>());
start_point.distance = distance + boundary_offset;
points.push_back(start_point);
}
for (size_t i = 1; i < input_points.size(); i++) {
ExtendedPoint<L::Dim> next_point{maybe_unscale(input_points[i])};
next_point.distance = input_distance(input_points[i], next_point.position) + boundary_offset;
auto [distance, nearest_line,
x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(next_point.position.template cast<AABBScalar>());
next_point.distance = distance + boundary_offset;
// Intersection handling
if (ADD_INTERSECTIONS &&
@@ -364,9 +349,6 @@ std::vector<ExtendedPoint<L::Dim>> estimate_points_properties(const POINTS&
points = std::move(new_points);
}
if constexpr (!CURVATURE)
return points;
// Curvature calculation
float accumulated_distance = 0;
std::vector<float> distances_for_curvature(points.size());
@@ -435,49 +417,6 @@ std::vector<ExtendedPoint<L::Dim>> estimate_points_properties(const POINTS&
return points;
}
struct Point3Hash
{
size_t operator()(const Vec3crd &pt) const noexcept { return size_t(((89 * 31 + int64_t(pt.x())) * 31 + pt.y()) * 31 + pt.z()); }
};
// The trees of the layer below an object layer, and the signed distances from the layer's perimeter and bridge
// vertices to that layer's outline, computed for ExtrusionQualityEstimator ahead of the G-code generator.
struct PrecomputedOverhangLayer
{
const PrintObject *object{nullptr};
const Layer *layer{nullptr};
std::shared_ptr<const AABBTreeLines::LinesDistancer<Linef3>> lower_boundaries;
std::shared_ptr<const AABBTreeLines::LinesDistancer<CurledLine>> lower_curled_lines;
std::unordered_map<Point3, double, Point3Hash> distances;
};
// `layer` must have a layer below it; leave out `curled_lines` only when no region of `layer` slows down for curled
// perimeters.
inline PrecomputedOverhangLayer precompute_overhang_layer(const PrintObject *object, const Layer &layer, bool curled_lines = true)
{
PrecomputedOverhangLayer out{object, &layer,
std::make_shared<const AABBTreeLines::LinesDistancer<Linef3>>(to_unscaled_linesf3(layer.lower_layer->lslices)),
curled_lines ? std::make_shared<const AABBTreeLines::LinesDistancer<CurledLine>>(layer.lower_layer->curled_lines) :
nullptr,
{}};
const AABBTreeLines::LinesDistancer<Linef3> &lower = *out.lower_boundaries;
auto add_path = [&out, &lower](const ExtrusionPath &path) {
if (!is_bridge(path.role()) && !is_perimeter(path.role()))
return;
for (const Point3 &point : path.polyline.points)
if (auto [it, inserted] = out.distances.try_emplace(point, 0.); inserted) {
const Eigen::Matrix<double, 3, 1, Eigen::DontAlign> position = unscaled(point);
auto [distance, nearest_line, x] = lower.distance_from_lines_extra<true>(position.cast<double>());
it->second = distance;
}
};
for (const LayerRegion *region : layer.regions()) {
for_each_extrusion_path(region->perimeters, add_path);
for_each_extrusion_path(region->fills, add_path);
}
return out;
}
struct ProcessedPoint
{
Point3 p;
@@ -487,53 +426,23 @@ struct ProcessedPoint
class ExtrusionQualityEstimator
{
using Boundaries = AABBTreeLines::LinesDistancer<Linef3>;
using CurledLines = AABBTreeLines::LinesDistancer<CurledLine>;
std::unordered_map<const PrintObject*, std::shared_ptr<const Boundaries>> prev_layer_boundaries;
std::unordered_map<const PrintObject*, std::shared_ptr<const CurledLines>> prev_curled_extrusions;
// The layers the trees above are built from, and the layers prepared last.
std::unordered_map<const PrintObject*, const Layer*> prev_layer_sources;
std::unordered_map<const PrintObject*, const Layer*> last_prepared_layers;
std::vector<PrecomputedOverhangLayer> precomputed_layers;
const PrintObject *current_object;
const PrecomputedOverhangLayer *precomputed_for(const PrintObject *object) const
{
auto it = std::find_if(precomputed_layers.begin(), precomputed_layers.end(),
[object](const PrecomputedOverhangLayer &layer) { return layer.object == object; });
return it == precomputed_layers.end() ? nullptr : &*it;
}
template<typename T> static const T &or_empty(const std::shared_ptr<const T> &tree)
{
static const T empty;
return tree ? *tree : empty;
}
std::unordered_map<const PrintObject*, AABBTreeLines::LinesDistancer<Linef3>> prev_layer_boundaries;
std::unordered_map<const PrintObject*, AABBTreeLines::LinesDistancer<Linef3>> next_layer_boundaries;
std::unordered_map<const PrintObject *, AABBTreeLines::LinesDistancer<CurledLine>> prev_curled_extrusions;
std::unordered_map<const PrintObject *, AABBTreeLines::LinesDistancer<CurledLine>> next_curled_extrusions;
const PrintObject *current_object;
public:
void set_current_object(const PrintObject *object) { current_object = object; }
// Takes the data computed ahead for the layer about to be generated, replacing the previous layer's.
void set_precomputed_layers(std::vector<PrecomputedOverhangLayer> &&layers) { precomputed_layers = std::move(layers); }
// Measures the layer against the layer prepared before it.
void prepare_for_new_layer(const PrintObject * obj, const Layer *layer)
{
if (layer == nullptr) return;
const PrintObject *object = obj;
const Layer *prev = std::exchange(last_prepared_layers[object], layer);
prev_layer_sources[object] = prev;
const PrecomputedOverhangLayer *precomputed = precomputed_for(object);
if (prev == nullptr) {
prev_layer_boundaries[object] = nullptr;
prev_curled_extrusions[object] = nullptr;
} else if (precomputed != nullptr && precomputed->layer == layer && layer->lower_layer == prev) {
prev_layer_boundaries[object] = precomputed->lower_boundaries;
prev_curled_extrusions[object] = precomputed->lower_curled_lines;
} else {
prev_layer_boundaries[object] = std::make_shared<const Boundaries>(to_unscaled_linesf3(prev->lslices));
prev_curled_extrusions[object] = std::make_shared<const CurledLines>(prev->curled_lines);
}
prev_layer_boundaries[object] = next_layer_boundaries[object];
next_layer_boundaries[object] = AABBTreeLines::LinesDistancer<Linef3>{to_unscaled_linesf3(layer->lslices)};
prev_curled_extrusions[object] = next_curled_extrusions[object];
next_curled_extrusions[object] = AABBTreeLines::LinesDistancer<CurledLine>{layer->curled_lines};
}
std::vector<ProcessedPoint> estimate_extrusion_quality(const ExtrusionPath &path,
@@ -617,24 +526,9 @@ public:
return std::min(calculate_speed(distance), original_speed);
};
// Precomputed distances hold only if they were measured against the layer prev_layer_boundaries is built from.
const std::unordered_map<Point3, double, Point3Hash> *known = nullptr;
if (const PrecomputedOverhangLayer *precomputed = precomputed_for(current_object);
precomputed != nullptr && precomputed->layer->lower_layer == prev_layer_sources[current_object])
known = &precomputed->distances;
const Boundaries &prev_boundaries = or_empty(prev_layer_boundaries[current_object]);
const CurledLines &prev_curled = or_empty(prev_curled_extrusions[current_object]);
auto known_distance = [known](const Point3 &point) -> const double * {
if (known == nullptr)
return nullptr;
auto it = known->find(point);
return it == known->end() ? nullptr : &it->second;
};
std::vector<ExtendedPoint<3>> extended_points =
estimate_points_properties<true, true, true, true, false>(path.polyline.points, prev_boundaries, path.width, -1,
smallest_distance_with_lower_speed, effective_speed, fan_overlap_threshold,
known_distance);
estimate_points_properties<true, true, true, true>(path.polyline.points, prev_layer_boundaries[current_object], path.width, -1,
smallest_distance_with_lower_speed, effective_speed, fan_overlap_threshold);
const auto width_inv = 1.0f / path.width;
std::vector<ProcessedPoint> processed_points;
processed_points.reserve(extended_points.size());
@@ -648,7 +542,7 @@ public:
const double dist_limit = 10.0 * path.width;
{
Vec3d middle = 0.5 * (curr.position + next.position);
auto line_indices = prev_curled.all_lines_in_radius(Point::new_scale(middle),
auto line_indices = prev_curled_extrusions[current_object].all_lines_in_radius(Point::new_scale(middle),
scale_(dist_limit));
if (!line_indices.empty()) {
double len = (next.position - curr.position).norm();
@@ -669,7 +563,7 @@ public:
double projected_lengths_sum = 0;
for (size_t idx : line_indices) {
const CurledLine& line = prev_curled.get_line(idx);
const CurledLine& line = prev_curled_extrusions[current_object].get_line(idx);
Lines inside = intersection_ln({{line.a, line.b}}, {box_of_influence});
if (inside.empty())
continue;
@@ -682,7 +576,7 @@ public:
}
for (size_t idx : line_indices) {
const CurledLine &line = prev_curled.get_line(idx);
const CurledLine &line = prev_curled_extrusions[current_object].get_line(idx);
float distance_from_curled = unscaled(line_alg::distance_to(line, Point::new_scale(middle)));
float dist = path.width * (1.0 - (distance_from_curled / dist_limit)) *
(1.0 - (distance_from_curled / dist_limit)) *
+41 -65
View File
@@ -909,8 +909,8 @@ public:
{
// Orca: find start pos by seaching G28/G29/PRINT_START/START_PRINT commands
auto is_start_pos = [](const std::string& curr_cmd) {
return ascii_iequals(curr_cmd, "G28") || ascii_iequals(curr_cmd, "G29") || ascii_iequals(curr_cmd, "PRINT_START") ||
ascii_iequals(curr_cmd, "START_PRINT");
return boost::iequals(curr_cmd, "G28") || boost::iequals(curr_cmd, "G29") || boost::iequals(curr_cmd, "PRINT_START") ||
boost::iequals(curr_cmd, "START_PRINT");
};
assert(!m_lines.empty());
const float time_step = backtrace.time_step();
@@ -1265,9 +1265,6 @@ void GCodeProcessor::run_post_process()
// Process inline placeholders (print_time_total_sec, print_time_day, print_time_hour, print_time_minute, print_time_sec and used_filament_length)
auto process_inline_placeholders = [&](std::string& gcode_line) {
bool processed = false;
// Every inline placeholder contains '@', so a line without one has nothing to replace.
if (gcode_line.find('@') == std::string::npos)
return processed;
const std::string& print_time_total_placeholder = reserved_tag(ETags::Print_Time_Total_Sec_Placeholder);
const std::string& print_time_day_placeholder = reserved_tag(ETags::Print_Time_Day_Placeholder);
@@ -4010,13 +4007,13 @@ void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line, bool
const std::string_view cmd = line.cmd();
if (m_flavor == gcfKlipper)
{
if (ascii_iequals(cmd, "SET_VELOCITY_LIMIT"))
if (boost::iequals(cmd, "SET_VELOCITY_LIMIT"))
{
process_SET_VELOCITY_LIMIT(line);
return;
}
// ORCA: Add Pressure Advance visualization support
if (ascii_iequals(cmd, "SET_PRESSURE_ADVANCE"))
if (boost::iequals(cmd, "SET_PRESSURE_ADVANCE"))
{
process_SET_PRESSURE_ADVANCE(line);
return;
@@ -7554,72 +7551,51 @@ void GCodeProcessor::calculate_time(GCodeProcessorResult& result, size_t keep_la
actual_speed_moves = std::move(machine.actual_speed_moves);
}
// actual_speed_moves holds, per block in move order, the moves to insert before the block's move and then an
// entry without a position for that move; positioned entries after the last such entry are dropped.
std::vector<GCodeProcessorResult::MoveVertex>& moves = result.moves;
size_t inserted_actual_speed_moves_count = 0;
size_t kept = 0;
size_t group_start = 0;
for (size_t i = 0; i < actual_speed_moves.size(); ++i) {
if (actual_speed_moves[i].position.has_value())
continue;
const unsigned int move_id = actual_speed_moves[i].move_id;
// A VG1 block has no move of its own, so its id can fall behind the previous block's or point past the list.
if (move_id < moves.size() && (kept == 0 || move_id > actual_speed_moves[kept - 1].move_id)) {
inserted_actual_speed_moves_count += i - group_start;
moves[move_id].actual_feedrate = actual_speed_moves[i].actual_feedrate;
// A seam vertex right after a block's move shares its actual speed.
if (move_id + 1 < moves.size() && moves[move_id + 1].type == EMoveType::Seam)
moves[move_id + 1].actual_feedrate = actual_speed_moves[i].actual_feedrate;
for (size_t j = group_start; j <= i; ++j, ++kept)
if (kept != j)
actual_speed_moves[kept] = std::move(actual_speed_moves[j]);
}
group_start = i + 1;
}
actual_speed_moves.erase(actual_speed_moves.begin() + kept, actual_speed_moves.end());
// Walks the blocks back to front, so each shifted move is moved once, into its final slot.
size_t read = moves.size(); // one past the last move not yet placed
moves.resize(moves.size() + inserted_actual_speed_moves_count);
size_t write = moves.size(); // one past the last free slot
m_actual_speed_id_map.clear();
size_t entry = actual_speed_moves.size();
while (entry > 0) {
const unsigned int block_id = actual_speed_moves[--entry].move_id;
assert(block_id < read);
while (read > block_id + 1)
moves[--write] = moves[--read];
const GCodeProcessorResult::MoveVertex block_move = moves[--read];
moves[--write] = block_move;
m_actual_speed_id_map.emplace_back(block_id, (unsigned int)write);
for (; entry > 0 && actual_speed_moves[entry - 1].position.has_value(); --entry) {
const TimeMachine::ActualSpeedMove& it = actual_speed_moves[entry - 1];
GCodeProcessorResult::MoveVertex new_move = block_move;
// insert actual speed moves into the move list
unsigned int inserted_actual_speed_moves_count = 0;
std::vector<GCodeProcessorResult::MoveVertex> new_moves;
std::map<unsigned int, unsigned int> id_map;
for (auto it = actual_speed_moves.begin(); it != actual_speed_moves.end(); ++it) {
const unsigned int base_id = it->move_id + inserted_actual_speed_moves_count;
if (it->position.has_value()) {
// insert actual speed move into the move list
// clone from existing move
GCodeProcessorResult::MoveVertex new_move = result.moves[base_id];
// override modified parameters
new_move.time = { 0.0f, 0.0f };
new_move.position = *it.position;
new_move.actual_feedrate = it.actual_feedrate;
new_move.delta_extruder = *it.delta_extruder;
new_move.feedrate = *it.feedrate;
new_move.width = *it.width;
new_move.height = *it.height;
new_move.mm3_per_mm = *it.mm3_per_mm;
new_move.fan_speed = *it.fan_speed;
new_move.temperature = *it.temperature;
new_move.position = *it->position;
new_move.actual_feedrate = it->actual_feedrate;
new_move.delta_extruder = *it->delta_extruder;
new_move.feedrate = *it->feedrate;
new_move.width = *it->width;
new_move.height = *it->height;
new_move.mm3_per_mm = *it->mm3_per_mm;
new_move.fan_speed = *it->fan_speed;
new_move.temperature = *it->temperature;
new_move.internal_only = true;
moves[--write] = new_move;
new_moves.push_back(new_move);
}
else {
result.moves.insert(result.moves.begin() + base_id, new_moves.begin(), new_moves.end());
id_map[it->move_id] = base_id + new_moves.size();
// update move actual speed
result.moves[base_id + new_moves.size()].actual_feedrate = it->actual_feedrate;
inserted_actual_speed_moves_count += new_moves.size();
// synchronize seams actual speed
if (base_id + new_moves.size() + 1 < result.moves.size()) {
GCodeProcessorResult::MoveVertex& move = result.moves[base_id + new_moves.size() + 1];
if (move.type == EMoveType::Seam)
move.actual_feedrate = it->actual_feedrate;
}
new_moves.clear();
}
}
assert(read == write);
// synchronize blocks' move_ids with after moves for actual speed insertion
std::reverse(m_actual_speed_id_map.begin(), m_actual_speed_id_map.end());
for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count); ++i) {
for (GCodeProcessor::TimeBlock& block : m_time_processor.machines[i].blocks) {
auto it = std::lower_bound(m_actual_speed_id_map.begin(), m_actual_speed_id_map.end(), block.move_id,
[](const std::pair<unsigned int, unsigned int>& entry, unsigned int id) { return entry.first < id; });
block.move_id = (it != m_actual_speed_id_map.end() && it->first == block.move_id) ?
it->second : block.move_id + (unsigned int)inserted_actual_speed_moves_count;
auto it = id_map.find(block.move_id);
block.move_id = (it != id_map.end()) ? it->second : block.move_id + inserted_actual_speed_moves_count;
}
}
}
+41 -47
View File
@@ -337,57 +337,54 @@ class Print;
//BBS: add mutex for protection of gcode result
mutable std::mutex result_mutex;
GCodeProcessorResult& operator=(const GCodeProcessorResult &other) { assign(other); return *this; }
// Declared because the user-declared copy assignment suppresses the implicit move.
GCodeProcessorResult& operator=(GCodeProcessorResult &&other) { assign(std::move(other)); return *this; }
// Add a new member here, or neither assignment transfers it.
template<class Other> void assign(Other &&other)
GCodeProcessorResult& operator=(const GCodeProcessorResult &other)
{
filename = std::forward<Other>(other).filename;
id = std::forward<Other>(other).id;
moves = std::forward<Other>(other).moves;
lines_ends = std::forward<Other>(other).lines_ends;
printable_area = std::forward<Other>(other).printable_area;
bed_exclude_area = std::forward<Other>(other).bed_exclude_area;
wrapping_exclude_area = std::forward<Other>(other).wrapping_exclude_area;
toolpath_outside = std::forward<Other>(other).toolpath_outside;
label_object_enabled = std::forward<Other>(other).label_object_enabled;
long_retraction_when_cut = std::forward<Other>(other).long_retraction_when_cut;
timelapse_warning_code = std::forward<Other>(other).timelapse_warning_code;
printable_height = std::forward<Other>(other).printable_height;
settings_ids = std::forward<Other>(other).settings_ids;
filaments_count = std::forward<Other>(other).filaments_count;
extruder_colors = std::forward<Other>(other).extruder_colors;
filament_diameters = std::forward<Other>(other).filament_diameters;
filament_densities = std::forward<Other>(other).filament_densities;
filament_costs = std::forward<Other>(other).filament_costs;
print_statistics = std::forward<Other>(other).print_statistics;
custom_gcode_per_print_z = std::forward<Other>(other).custom_gcode_per_print_z;
spiral_vase_mode = std::forward<Other>(other).spiral_vase_mode;
warnings = std::forward<Other>(other).warnings;
bed_type = std::forward<Other>(other).bed_type;
gcode_check_result = std::forward<Other>(other).gcode_check_result;
limit_filament_maps = std::forward<Other>(other).limit_filament_maps;
filament_printable_reuslt = std::forward<Other>(other).filament_printable_reuslt;
filename = other.filename;
id = other.id;
moves = other.moves;
lines_ends = other.lines_ends;
printable_area = other.printable_area;
bed_exclude_area = other.bed_exclude_area;
wrapping_exclude_area = other.wrapping_exclude_area;
toolpath_outside = other.toolpath_outside;
label_object_enabled = other.label_object_enabled;
long_retraction_when_cut = other.long_retraction_when_cut;
timelapse_warning_code = other.timelapse_warning_code;
printable_height = other.printable_height;
settings_ids = other.settings_ids;
filaments_count = other.filaments_count;
extruder_colors = other.extruder_colors;
filament_diameters = other.filament_diameters;
filament_densities = other.filament_densities;
filament_costs = other.filament_costs;
print_statistics = other.print_statistics;
custom_gcode_per_print_z = other.custom_gcode_per_print_z;
spiral_vase_mode = other.spiral_vase_mode;
warnings = other.warnings;
bed_type = other.bed_type;
gcode_check_result = other.gcode_check_result;
limit_filament_maps = other.limit_filament_maps;
filament_printable_reuslt = other.filament_printable_reuslt;
// Orca: copy the shared grouping result so a copied result keeps it (shared_ptr =>
// memory-safe), rather than leaving a stale pointer on the target. No g-code effect either way.
nozzle_group_result = std::forward<Other>(other).nozzle_group_result;
nozzle_group_result = other.nozzle_group_result;
// Keep the per-extruder hotend types on a copied result (injector input).
extruder_types = std::forward<Other>(other).extruder_types;
printer_extruder_variant = std::forward<Other>(other).printer_extruder_variant;
printer_extruder_id = std::forward<Other>(other).printer_extruder_id;
layer_filaments = std::forward<Other>(other).layer_filaments;
filament_change_sequence = std::forward<Other>(other).filament_change_sequence;
used_mixed_filaments = std::forward<Other>(other).used_mixed_filaments;
nozzle_change_sequence = std::forward<Other>(other).nozzle_change_sequence;
optimal_assignment = std::forward<Other>(other).optimal_assignment;
filament_change_count_map = std::forward<Other>(other).filament_change_count_map;
extruder_types = other.extruder_types;
printer_extruder_variant = other.printer_extruder_variant;
printer_extruder_id = other.printer_extruder_id;
layer_filaments = other.layer_filaments;
filament_change_sequence = other.filament_change_sequence;
used_mixed_filaments = other.used_mixed_filaments;
nozzle_change_sequence = other.nozzle_change_sequence;
optimal_assignment = other.optimal_assignment;
filament_change_count_map = other.filament_change_count_map;
// Keep the SKIPPABLE per-type time on a copied result.
skippable_part_time = std::forward<Other>(other).skippable_part_time;
initial_layer_time = std::forward<Other>(other).initial_layer_time;
skippable_part_time = other.skippable_part_time;
initial_layer_time = other.initial_layer_time;
#if ENABLE_GCODE_VIEWER_STATISTICS
time = std::forward<Other>(other).time;
time = other.time;
#endif
return *this;
}
void lock() const { result_mutex.lock(); }
void unlock() const { result_mutex.unlock(); }
@@ -1219,9 +1216,6 @@ class Print;
EProducer m_producer;
TimeProcessor m_time_processor;
// calculate_time()'s map from each block's move id to its index after the actual speed moves are inserted,
// a member to reuse its capacity.
std::vector<std::pair<unsigned int, unsigned int>> m_actual_speed_id_map;
UsedFilaments m_used_filaments;
Print* m_print{ nullptr };
+16 -15
View File
@@ -465,31 +465,32 @@ std::string GCodeWriter::set_accel_and_jerk(unsigned int acceleration, double je
if (EXTRUDER_LIMIT(m_max_acceleration) > 0 && acceleration > EXTRUDER_LIMIT(m_max_acceleration))
acceleration = EXTRUDER_LIMIT(m_max_acceleration);
bool is_empty = true;
std::ostringstream gcode;
gcode << "SET_VELOCITY_LIMIT";
if (acceleration != 0 && acceleration != m_last_acceleration) {
gcode << " ACCEL=" << acceleration;
if (this->config.accel_to_decel_enable) {
gcode << " ACCEL_TO_DECEL=" << acceleration * this->config.accel_to_decel_factor / 100;
}
m_last_acceleration = acceleration;
is_empty = false;
}
// Clamp the jerk to the allowed maximum.
if (EXTRUDER_LIMIT(m_max_jerk_x) > 0 && jerk > EXTRUDER_LIMIT(m_max_jerk_x))
jerk = EXTRUDER_LIMIT(m_max_jerk_x);
if (EXTRUDER_LIMIT(m_max_jerk_y) > 0 && jerk > EXTRUDER_LIMIT(m_max_jerk_y))
jerk = EXTRUDER_LIMIT(m_max_jerk_y);
const bool set_acceleration = acceleration != 0 && acceleration != m_last_acceleration;
const bool set_jerk = jerk > 0.01 && !is_approx(jerk, m_last_jerk);
if (!set_acceleration && !set_jerk)
return std::string();
std::ostringstream gcode;
gcode << "SET_VELOCITY_LIMIT";
if (set_acceleration) {
gcode << " ACCEL=" << acceleration;
if (this->config.accel_to_decel_enable) {
gcode << " ACCEL_TO_DECEL=" << acceleration * this->config.accel_to_decel_factor / 100;
}
m_last_acceleration = acceleration;
}
if (set_jerk) {
if (jerk > 0.01 && !is_approx(jerk, m_last_jerk)) {
gcode << " SQUARE_CORNER_VELOCITY=" << jerk;
m_last_jerk = jerk;
is_empty = false;
}
if(is_empty)
return std::string();
if (GCodeWriter::full_gcode_comment)
gcode << " ; adjust VELOCITY_LIMIT(accel/jerk)";
gcode << "\n";
-13
View File
@@ -84,19 +84,6 @@ double string_to_double_decimal_point(const std::string_view str, size_t* pos /*
return out;
}
double atof_decimal_point(std::string_view str)
{
size_t i = 0;
while (i < str.size() && (str[i] == ' ' || (str[i] >= '\t' && str[i] <= '\r')))
++i;
if (i < str.size() && str[i] == '+') {
++i;
if (i < str.size() && str[i] == '-')
return 0.;
}
return string_to_double_decimal_point(str.substr(i));
}
std::string float_to_string_decimal_point(double value, int precision/* = -1*/)
{
// Our Windows build server fully supports C++17 std::to_chars. Let's use it.
-3
View File
@@ -48,9 +48,6 @@ bool is_decimal_separator_point();
std::string float_to_string_decimal_point(double value, int precision = -1);
//std::string float_to_string_decimal_point(float value, int precision = -1);
double string_to_double_decimal_point(const std::string_view str, size_t* pos = nullptr);
// Parses like atof in the C locale, skipping leading whitespace and a '+',
// without the C runtime's per-call locale lookup.
double atof_decimal_point(std::string_view str);
} // namespace Slic3r
+2
View File
@@ -87,6 +87,8 @@
#include "PlaceholderParser.hpp"
#include "libslic3r/GCode/Thumbnails.hpp"
namespace fs = boost::filesystem;
using boost::property_tree::ptree;
namespace Slic3r {
+11 -13
View File
@@ -626,15 +626,13 @@ const PresetBundle *PresetBundle::load_source_vendor(const boost::filesystem::pa
return it->second.get();
// The library loads with no base of its own, so the tree a vendor inherits from
// is the same one that resolves the library's own presets. It is only a base, so
// it comes from its cache whenever that is all that is installed, even when the
// vendor itself is parsed (a vendor updated over the air).
const std::string library_file = std::string(ORCA_FILAMENT_LIBRARY);
const bool library_json = boost::filesystem::is_regular_file(root_dir / (library_file + ".json"));
const bool library_cache_only = !library_json && boost::filesystem::is_regular_file(root_dir / (library_file + ".opc"));
const PresetBundle *library = nullptr;
if (vendor_id != ORCA_FILAMENT_LIBRARY && (library_json || library_cache_only)) {
library = load_source_vendor(root_dir, ORCA_FILAMENT_LIBRARY, compatibility_rule, error, allow_cache || library_cache_only);
// is the same one that resolves the library's own presets.
const std::string library_file = std::string(ORCA_FILAMENT_LIBRARY);
const PresetBundle *library = nullptr;
if (vendor_id != ORCA_FILAMENT_LIBRARY &&
(boost::filesystem::is_regular_file(root_dir / (library_file + ".json")) ||
(allow_cache && boost::filesystem::is_regular_file(root_dir / (library_file + ".opc"))))) {
library = load_source_vendor(root_dir, ORCA_FILAMENT_LIBRARY, compatibility_rule, error, allow_cache);
if (library == nullptr) {
error = "OrcaFilamentLibrary contains invalid presets";
return nullptr;
@@ -2578,7 +2576,7 @@ void PresetBundle::clear_printer_hold_aliases()
//BBS: add json related logic, load system presets from json
std::pair<PresetsConfigSubstitutions, std::string> PresetBundle::load_system_presets_from_json(
ForwardCompatibilitySubstitutionRule compatibility_rule, bool write_caches)
ForwardCompatibilitySubstitutionRule compatibility_rule, bool allow_cache)
{
//BBS: add config related logs
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" enter, compatibility_rule %1%")%compatibility_rule;
@@ -2598,14 +2596,14 @@ std::pair<PresetsConfigSubstitutions, std::string> PresetBundle::load_system_pre
// The vendors below are loaded whole and against each other — the filament
// library first, then every other vendor with it as the base — so each parse
// is complete enough to be worth caching.
m_generate_vendor_caches = write_caches && (m_generate_vendor_caches || !validation_mode);
m_generate_vendor_caches = allow_cache && (m_generate_vendor_caches || !validation_mode);
// Sorted, so any duplicate-preset warning comes out in the same order on every run.
std::vector<VendorSource> vendors;
for (const std::string& name : vendor_names_in(dir))
if (name == ORCA_FILAMENT_LIBRARY || !(validation_mode && !vendor_to_validate.empty() && name != vendor_to_validate))
vendors.push_back({ name, dir });
auto result = this->load_vendors(vendors, compatibility_rule, true);
auto result = this->load_vendors(vendors, compatibility_rule, allow_cache);
this->update_system_maps();
@@ -5312,7 +5310,7 @@ static void apply_receiver_mix_relocations(DynamicPrintConfig&
//convert the old filament preset to new one after split
void PresetBundle::convert_filament_preset_name(const std::string& machine_name, std::string& filament_name)
static void convert_filament_preset_name(std::string& machine_name, std::string& filament_name)
{
auto machine_iter = filament_preset_convert.find(machine_name);
if (machine_iter != filament_preset_convert.end())
+3 -8
View File
@@ -247,9 +247,6 @@ public:
// Keys a project keeps when its presets are loaded: those listed in its escaped
// "different_settings_to_system" entry for the preset, plus the preset bookkeeping keys.
static std::set<std::string> project_different_keys(const std::string &different_settings);
// A project filament saved under a name the current presets split per nozzle (e.g. H2D 0.6) is loaded from
// the preset that now holds its values.
static void convert_filament_preset_name(const std::string& machine_name, std::string& filament_name);
PresetBundle();
PresetBundle(const PresetBundle &rhs);
@@ -473,8 +470,8 @@ public:
// Orca: Bundle metadata and cached preset names
// std::map<std::string, BundleMetadata> m_bundles;
fs::path dir_user_presets_local;
fs::path dir_user_presets_subscribed;
boost::filesystem::path dir_user_presets_local;
boost::filesystem::path dir_user_presets_subscribed;
PresetBundleMetadata bundles;
struct ObsoletePresets
@@ -851,9 +848,7 @@ private:
//std::pair<PresetsConfigSubstitutions, std::string> load_system_presets(ForwardCompatibilitySubstitutionRule compatibility_rule);
//BBS: add json related logic
// Reads each vendor from its preset cache where one covers the profile, as every load does.
// write_caches = false keeps a read-only load from writing caches into the data directory.
std::pair<PresetsConfigSubstitutions, std::string> load_system_presets_from_json(ForwardCompatibilitySubstitutionRule compatibility_rule, bool write_caches = true);
std::pair<PresetsConfigSubstitutions, std::string> load_system_presets_from_json(ForwardCompatibilitySubstitutionRule compatibility_rule, bool allow_cache = true);
// Update the multicolor information for filaments.
void update_filament_multi_color();
// Update renamed_from and alias maps of system profiles.
+2
View File
@@ -102,6 +102,8 @@
#include <codecvt>
namespace fs = boost::filesystem;
using namespace nlohmann;
// Mark string for localization and translate.
-10
View File
@@ -25,8 +25,6 @@
#include <initializer_list>
#include <string_view>
#include <regex>
#include <string_view>
#include <algorithm>
#include <boost/system/error_code.hpp>
#include <boost/algorithm/string.hpp>
@@ -304,14 +302,6 @@ extern bool is_absolute_path_within_root(const boost::filesystem::path &path, co
// Anything unknown is not safe.
extern bool is_safe_to_open_file_name(const std::string &file_name);
// Case-insensitive compare against a fixed ASCII keyword, without boost::iequals, whose
// std::locale() takes a lock the whole process shares in the MSVC runtime.
inline bool ascii_iequals(std::string_view a, std::string_view b)
{
auto lower = [](char c) { return (c >= 'A' && c <= 'Z') ? char(c - 'A' + 'a') : c; };
return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin(), [&lower](char x, char y) { return lower(x) == lower(y); });
}
// Orca: custom protocal support utils
inline bool is_orca_open(const std::string& url) { return boost::starts_with(url, "orcaslicer://open"); }
inline bool is_prusaslicer_open(const std::string& url) { return boost::starts_with(url, "prusaslicer://open"); }
+2
View File
@@ -65,6 +65,8 @@
#include "MainFrame.hpp"
#include "Widgets/Label.hpp"
namespace fs = boost::filesystem;
namespace Slic3r { namespace GUI {
wxDEFINE_EVENT(EVT_AUXILIARY_IMPORT, wxCommandEvent);
+4 -4
View File
@@ -99,7 +99,7 @@ public:
bool m_cover{false};
wxStaticText* m_text_name {nullptr};
::TextInput* m_input_name {nullptr};
fs::path m_file_path;
boost::filesystem::path m_file_path;
wxString m_add_file;
wxString m_file_name;
wxString cover_text_left;
@@ -116,7 +116,7 @@ public:
ScalableBitmap m_bitmap_txt;
public:
AuFile(wxWindow *parent, fs::path file_path, wxString file_name, AuxiliaryFolderType type, wxWindowID id = wxID_ANY, const wxPoint &pos = wxDefaultPosition, const wxSize &size = wxDefaultSize, long style = wxTAB_TRAVERSAL);
AuFile(wxWindow *parent, boost::filesystem::path file_path, wxString file_name, AuxiliaryFolderType type, wxWindowID id = wxID_ANY, const wxPoint &pos = wxDefaultPosition, const wxSize &size = wxDefaultSize, long style = wxTAB_TRAVERSAL);
void enter_rename_mode();
void exit_rename_mode();
void OnPaint(wxPaintEvent &evt);
@@ -164,7 +164,7 @@ public:
void clear();
void update_cover();
void update(std::vector<fs::path> paths);
void update(std::vector<boost::filesystem::path> paths);
void msw_rescale();
public:
@@ -240,7 +240,7 @@ public:
bool Show(bool show);
// core logic
std::map<std::string, std::vector<fs::path>> m_paths_list;
std::map<std::string, std::vector<boost::filesystem::path>> m_paths_list;
wxString m_root_dir;
void init_auxiliary();
void create_folder(wxString name = wxEmptyString);
@@ -21,6 +21,8 @@
#include <wx/variant.h>
#include <wx/types.h>
namespace fs = boost::filesystem;
const static std::array<wxString, 4> s_default_folders = {
_L("Model Pictures"),
_L("Bill of Materials"),
+2
View File
@@ -83,6 +83,8 @@
#include "UnsavedChangesDialog.hpp"
#include "MainFrame.hpp"
namespace fs = boost::filesystem;
#if defined(__linux__) && defined(__WXGTK3__)
#define wxLinux_gtk3 true
#else
+2
View File
@@ -108,6 +108,8 @@
#include "IPrinterAgent.hpp"
namespace fs = boost::filesystem;
#define CALI_DEBUG
#define MINUTE_30 1800000 //ms
#define TIME_OUT 5000 //ms
+2
View File
@@ -58,6 +58,8 @@
#include "libslic3r/Print.hpp"
#include "libslic3r/Utils.hpp"
namespace fs = boost::filesystem;
namespace Slic3r {
class AppConfig;
+2
View File
@@ -25,6 +25,8 @@
#include <mutex>
#include <ctime>
namespace fs = boost::filesystem;
static const char* HMS_PATH = "hms";
static const char* HMS_LOCAL_IMG_PATH = "hms/local_image";
+3
View File
@@ -35,6 +35,9 @@
#include <boost/chrono/duration.hpp>
#include "slic3r/GUI/DeviceCore/DevStorage.h"
#include <wx/event.h>
#include <boost/filesystem.hpp>
namespace fs = boost::filesystem;
namespace Slic3r {
namespace GUI {
+3
View File
@@ -20,6 +20,9 @@
#include <wx/event.h>
#include <functional>
#include <exception>
#include <boost/filesystem.hpp>
namespace fs = boost::filesystem;
namespace Slic3r {
namespace GUI {
@@ -11,6 +11,8 @@
#include "libslic3r/Utils.hpp"
#include <exception>
namespace fs = boost::filesystem;
namespace Slic3r {
namespace GUI {
+2
View File
@@ -62,6 +62,8 @@
#include "DeviceCore/DevManager.h"
namespace fs = boost::filesystem;
namespace Slic3r { namespace GUI {
extern wxString hide_passwd(wxString url, std::vector<wxString> const &passwords);
+2 -2
View File
@@ -376,8 +376,8 @@ public:
// BBS: check snapshot
bool up_to_date(bool saved, bool backup);
bool open_3mf_file(const fs::path &file_path);
int get_3mf_file_count(std::vector<fs::path> paths);
bool open_3mf_file(const boost::filesystem::path &file_path);
int get_3mf_file_count(std::vector<boost::filesystem::path> paths);
void add_file();
// Returns false when no object was added (e.g. the user cancelled the load dialog).
bool add_model(bool imperial_units = false, std::string fname = "");
+3
View File
@@ -21,6 +21,9 @@
#include <wx/log.h>
#include <wx/webview.h>
#include <wx/utils.h>
#include <boost/filesystem.hpp>
namespace fs = boost::filesystem;
namespace Slic3r { namespace GUI {
+2
View File
@@ -57,6 +57,8 @@
#include "MainFrame.hpp"
#include <slic3r/GUI/Widgets/WebView.hpp>
namespace fs = boost::filesystem;
namespace Slic3r { namespace GUI {
wxDEFINE_EVENT(EVT_PROJECT_RELOAD, wxCommandEvent);
+3
View File
@@ -70,6 +70,9 @@
#include "DeviceCore/DevStorage.h"
#include "../Utils/Http.hpp"
#include "md4c/src/md4c-html.h"
#include <boost/filesystem.hpp>
namespace fs = boost::filesystem;
namespace Slic3r { namespace GUI {
+3
View File
@@ -120,6 +120,9 @@
#include "Notebook.hpp"
#include "BitmapCache.hpp"
#include "BindDialog.hpp"
#include <boost/filesystem.hpp>
namespace fs = boost::filesystem;
namespace Slic3r { namespace GUI {
+3
View File
@@ -61,6 +61,9 @@
#include "DeviceCore/DevManager.h"
#include "DeviceCore/DevStorage.h"
#include <boost/filesystem.hpp>
namespace fs = boost::filesystem;
namespace Slic3r {
namespace GUI {
+3
View File
@@ -77,6 +77,9 @@
#include "DeviceCore/DevStorage.h"
#include "libslic3r_version.h"
#include "slic3r/Utils/FileTransferUtils.hpp"
#include <boost/filesystem.hpp>
namespace fs = boost::filesystem;
namespace Slic3r {
+2
View File
@@ -78,6 +78,8 @@
#include "DeviceCore/DevStorage.h"
#include "FilamentBitmapUtils.hpp"
namespace fs = boost::filesystem;
using namespace Slic3r;
using namespace Slic3r::GUI;
+2
View File
@@ -79,6 +79,8 @@
#include <libslic3r/Utils.hpp>
#include "CreatePresetsDialog.hpp"
namespace fs = boost::filesystem;
using namespace nlohmann;
namespace Slic3r { namespace GUI {
@@ -52,6 +52,9 @@
#include <wx/webview.h>
#include <wx/string.h>
#include <wx/timer.h>
#include <boost/filesystem.hpp>
namespace fs = boost::filesystem;
namespace Slic3r { namespace GUI {
+3
View File
@@ -34,6 +34,9 @@
#include <wx/uri.h>
#include <wx/filename.h>
#include <wx/stdpaths.h>
#include <boost/filesystem.hpp>
namespace fs = boost::filesystem;
#if defined(__WIN32__) || defined(__WXMAC__)
#include "wx/private/jsscriptwrapper.h"
#endif
+3
View File
@@ -40,6 +40,9 @@
#include "libslic3r/Config.hpp"
#include "Widgets/Label.hpp"
#include "MainFrame.hpp"
#include <boost/filesystem.hpp>
namespace fs = boost::filesystem;
using namespace Slic3r;
using namespace Slic3r::GUI;
+3
View File
@@ -58,6 +58,9 @@
#include "slic3r/GUI/OpenGLManager.hpp"
#include <utility>
#include "slic3r/GUI/Jobs/PrintJob.hpp"
#include <boost/filesystem.hpp>
namespace fs = boost::filesystem;
namespace Slic3r {
namespace GUI {
-7
View File
@@ -23,10 +23,3 @@ set_tests_properties(cli_project_missing_keys PROPERTIES
LABELS "CLI;RequiresApp"
SKIP_RETURN_CODE 77
TIMEOUT 900)
add_test(NAME cli_malformed_input
COMMAND bash ${CMAKE_CURRENT_SOURCE_DIR}/test_cli_malformed_input.sh $<TARGET_FILE:OrcaSlicer> ${ORCA_CLI_TEST_PYTHON})
set_tests_properties(cli_malformed_input PROPERTIES
LABELS "CLI;RequiresApp"
SKIP_RETURN_CODE 77
TIMEOUT 900)
-207
View File
@@ -1,207 +0,0 @@
#!/usr/bin/env bash
# End-to-end checks that malformed CLI input fails cleanly, or loads, instead of crashing the
# orca-slicer binary. Each case lives inline in CLI::run(), so only the binary can reach it.
#
# - A project whose inherits_group does not have one entry per filament plus the process and
# printer entries still loads.
# - --slice N --arrange 1 on a project without plate metadata slices plate N.
# - An assemble list object with an empty filament list, or a negative filament id, is rejected
# as a config error.
# - --assemble with no input model is rejected as invalid parameters.
#
# usage: test_cli_malformed_input.sh <orca-slicer binary> <python3>
set -u
BIN="${1:-}"
PY="${2:-python3}"
# 77 is the test's SKIP_RETURN_CODE.
[ -x "$BIN" ] || { echo "SKIP: orca-slicer binary not found: $BIN"; exit 77; }
# From src/libslic3r/Utils.hpp. main() returns them, so the shell sees them modulo 256.
CLI_SUCCESS=0
CLI_INVALID_PARAMS=-2
CLI_CONFIG_FILE_ERROR=-5
WORK="$(mktemp -d "${TMPDIR:-/tmp}/orca-cli-malformed.XXXXXX")"
trap 'rm -rf "$WORK"' EXIT
mkdir -p "$WORK/datadir"
# Standalone presets: without "inherits" the CLI loads them as-is, with no preset bundle.
cat > "$WORK/machine.json" <<'EOF'
{
"type": "machine",
"from": "User",
"name": "CLI malformed input test printer",
"printable_area": ["0x0", "200x0", "200x200", "0x200"],
"printable_height": "100",
"layer_change_gcode": "G92 E0"
}
EOF
cat > "$WORK/process.json" <<'EOF'
{
"type": "process",
"from": "User",
"name": "CLI malformed input test process"
}
EOF
cat > "$WORK/filament.json" <<'EOF'
{
"type": "filament",
"from": "User",
"name": "CLI malformed input test filament"
}
EOF
"$PY" - "$WORK/cube.stl" <<'EOF'
import sys
v = [(x, y, z) for z in (0, 10) for y in (0, 10) for x in (0, 10)]
with open(sys.argv[1], "w") as f:
f.write("solid cube\n")
# Faces wound counter-clockwise seen from outside: -z, +z, -y, +y, -x, +x.
for a, b, c, d in ((0, 2, 3, 1), (4, 5, 7, 6), (0, 1, 5, 4), (2, 6, 7, 3), (0, 4, 6, 2), (1, 3, 7, 5)):
for tri in ((a, b, c), (a, c, d)):
f.write("facet normal 0 0 0\nouter loop\n")
for i in tri:
f.write("vertex %g %g %g\n" % v[i])
f.write("endloop\nendfacet\n")
f.write("endsolid cube\n")
EOF
fails=0
fail() { echo "FAIL: $*"; fails=$((fails + 1)); }
# run <tag> [option...]: run into $WORK/<tag>, keeping the log and the shell status there.
run() {
local out="$WORK/$1"; shift
mkdir -p "$out"
timeout 300 "$BIN" --datadir "$WORK/datadir" --outputdir "$out" "$@" > "$out/log" 2>&1
echo $? > "$out/status"
}
# run_presets <tag> [option...]: run with the standalone presets loaded.
run_presets() {
local tag="$1"; shift
run "$tag" --load-settings "$WORK/machine.json;$WORK/process.json" --load-filaments "$WORK/filament.json" "$@"
}
# expect_status <tag> <cli code>
expect_status() {
local got; got="$(cat "$WORK/$1/status")"
[ "$got" -eq $(( $2 & 255 )) ] || fail "$1: shell status $got, want $(( $2 & 255 )) (code $2)"
}
# expect_result <tag> <return_code>: a failing run must also carry an error_string.
expect_result() {
"$PY" - "$WORK/$1/result.json" "$2" <<'EOF' || fail "$1: result.json"
import json, sys
try:
with open(sys.argv[1]) as f:
result = json.load(f)
except (OSError, ValueError) as e:
sys.exit("cannot read %s: %s" % (sys.argv[1], e))
want_rc = int(sys.argv[2])
if result.get("return_code") != want_rc:
sys.exit("return_code %r, want %d" % (result.get("return_code"), want_rc))
if want_rc != 0 and not result.get("error_string"):
sys.exit("no error_string")
EOF
}
# expect_log <tag> <text>
expect_log() {
grep -qF -- "$2" "$WORK/$1/log" || fail "$1: log does not mention \"$2\""
}
# expect_gcode <tag>
expect_gcode() {
compgen -G "$WORK/$1/*.gcode" > /dev/null || fail "$1: no G-code was exported"
}
# rewrite_3mf <in> <out> inherits <json list> | no-plates
rewrite_3mf() {
"$PY" - "$@" <<'EOF'
import json, re, sys, zipfile
src, dst, mode = sys.argv[1:4]
with zipfile.ZipFile(src) as zin, zipfile.ZipFile(dst, "w", zipfile.ZIP_DEFLATED) as zout:
for info in zin.infolist():
data = zin.read(info.filename)
if mode == "inherits" and info.filename == "Metadata/project_settings.config":
config = json.loads(data)
config["inherits_group"] = json.loads(sys.argv[4])
data = json.dumps(config, indent=4).encode()
elif mode == "no-plates":
if re.match(r"Metadata/plate_\d+\.", info.filename):
continue
if info.filename == "Metadata/model_settings.config":
data = re.sub(rb"\s*<plate>.*?</plate>", b"", data, flags=re.S)
zout.writestr(info, data)
EOF
}
# assemble_list <file> <filaments json>
assemble_list() {
cat > "$1" <<EOF
{"plates": [{"plate_name": "p", "need_arrange": false,
"objects": [{"path": "$WORK/cube.stl", "count": 1, "filaments": $2,
"pos_x": [100], "pos_y": [100]}]}]}
EOF
}
echo "== a one-filament project exported by the CLI is the base for the project cases"
run_presets export --slice 0 --export-3mf project.3mf "$WORK/cube.stl"
expect_status export $CLI_SUCCESS
[ -f "$WORK/export/project.3mf" ] || { echo "FAIL: project export failed"; tail -n 40 "$WORK/export/log"; exit 1; }
echo "== an inherits_group of the wrong length still loads"
for group in '[]' '[""]' '["", "", "", "", ""]'; do
tag="inherits_$("$PY" -c 'import json, sys; print(len(json.loads(sys.argv[1])))' "$group")"
rewrite_3mf "$WORK/export/project.3mf" "$WORK/$tag.3mf" inherits "$group"
run "$tag" --info "$WORK/$tag.3mf"
expect_status "$tag" $CLI_SUCCESS
expect_log "$tag" "inherits_group"
done
echo "== --slice 1 --arrange 1 slices a project without plate metadata"
rewrite_3mf "$WORK/export/project.3mf" "$WORK/no_plates.3mf" no-plates
run_presets no_plates --slice 1 --arrange 1 "$WORK/no_plates.3mf"
expect_status no_plates $CLI_SUCCESS
expect_result no_plates $CLI_SUCCESS
expect_gcode no_plates
echo "== an assemble list with a valid filament id slices"
assemble_list "$WORK/assemble_valid.json" '[1]'
run_presets assemble_valid --slice 0 --load-assemble-list "$WORK/assemble_valid.json"
expect_status assemble_valid $CLI_SUCCESS
expect_gcode assemble_valid
echo "== an assemble list with an empty filament list or a negative filament id is rejected"
for filaments in '[]' '[-1]'; do
if [ "$filaments" = '[]' ]; then tag=assemble_empty; else tag=assemble_negative; fi
assemble_list "$WORK/$tag.json" "$filaments"
run_presets "$tag" --slice 0 --load-assemble-list "$WORK/$tag.json"
expect_status "$tag" $CLI_CONFIG_FILE_ERROR
expect_result "$tag" $CLI_CONFIG_FILE_ERROR
done
echo "== --assemble with no input model is rejected"
for action in "--slice 0" "--export-3mf out.3mf"; do
tag="assemble_no_input_${action%% *}"
tag="${tag//-/}"
# shellcheck disable=SC2086
run_presets "$tag" --assemble $action
expect_status "$tag" $CLI_INVALID_PARAMS
expect_result "$tag" $CLI_INVALID_PARAMS
expect_log "$tag" "--assemble"
done
if [ "$fails" -ne 0 ]; then
for log in "$WORK"/*/log; do
echo "--- $log"
tail -n 40 "$log"
done
exit 1
fi
echo "PASS"
+10 -29
View File
@@ -1,13 +1,11 @@
#!/usr/bin/env bash
# End-to-end check that the CLI loads a project's printer, process and filament settings as the GUI does.
# End-to-end check that the CLI loads a project's printer and process settings as the GUI does.
#
# The GUI takes every key a project does not list as changed from the project's current system preset:
# keys saved before an option existed, and keys holding an older system value. Keys the project lists
# in different_settings_to_system keep the project's value. A project is exported from the shipped
# Bambu Lab P1S presets with two filaments; one printer key and one process key are removed, one printer
# key, one process key and two filament keys (one per filament, one per extruder variant) are changed
# without being listed, one process key and the first filament's density are changed and listed, and it
# is sliced again: as is, with --uptodate, and with --load-filaments replacing only the second filament.
# Bambu Lab P1S presets; one printer key and one process key are removed, one printer key and one
# process key are changed without being listed, one key is changed and listed, and it is sliced again.
#
# usage: test_cli_project_missing_keys.sh <orca-slicer binary> <python3> <resources/profiles/BBL>
set -u
@@ -47,7 +45,7 @@ slice() {
slice base "$WORK/cube.stl" \
--load-settings "$PROFILES/machine/Bambu Lab P1S 0.4 nozzle.json;$PROFILES/process/0.20mm Standard @BBL X1C.json" \
--load-filaments "$PROFILES/filament/Bambu PLA Basic @BBL P1S 0.4 nozzle.json;$PROFILES/filament/Bambu PLA Basic @BBL P1S 0.4 nozzle.json"
--load-filaments "$PROFILES/filament/Bambu PLA Basic @BBL P1S 0.4 nozzle.json"
# The removed keys, with their option defaults from PrintConfig.cpp; stale keys changed without being
# listed as different, which must come back with the system value; and a listed key the project keeps.
@@ -69,47 +67,30 @@ with zipfile.ZipFile(src) as zin, zipfile.ZipFile(dst, "w", zipfile.ZIP_DEFLATED
for key in ("top_shell_layers", "extruder_clearance_height_to_rod"):
expected[key] = config[key]
config[key] = str(int(float(config[key])) + 1)
for key in ("filament_cost", "filament_max_volumetric_speed"):
expected[key] = config[key]
config[key] = [str(float(v) + 1) for v in config[key]]
expected["wall_loops"] = str(int(config["wall_loops"]) + 1)
config["wall_loops"] = expected["wall_loops"]
expected["filament_density"] = [str(float(config["filament_density"][0]) + 1)] + config["filament_density"][1:]
config["filament_density"] = [expected["filament_density"][0]] + [str(float(v) + 1) for v in config["filament_density"][1:]]
# One entry for the process, one per filament, one for the printer.
different = config["different_settings_to_system"]
different[0] = ";".join([k for k in different[0].split(";") if k] + ["wall_loops"])
different[1] = ";".join([k for k in different[1].split(";") if k] + ["filament_density"])
data = json.dumps(config, indent=4)
zout.writestr(item, data)
with open(dst + ".expected.json", "w") as f:
json.dump(expected, f)
EOF
check() {
if ! "$PY" - "$WORK/$1/out.3mf" "$WORK/old.3mf.expected.json" "$1" <<'EOF'
slice project "$WORK/old.3mf"
"$PY" - "$WORK/project/out.3mf" "$WORK/old.3mf.expected.json" <<'EOF'
import json, sys, zipfile
with zipfile.ZipFile(sys.argv[1]) as z:
config = json.loads(z.read("Metadata/project_settings.config"))
with open(sys.argv[2]) as f:
expected = json.load(f)
errors = ["%s: %s is %r, want %r" % (sys.argv[3], key, config.get(key), want) for key, want in expected.items() if config.get(key) != want]
errors = ["%s is %r, want %r" % (key, config.get(key), want) for key, want in expected.items() if config.get(key) != want]
for e in errors:
print("FAIL: " + e)
sys.exit(1 if errors else 0)
EOF
then
tail -n 40 "$WORK/$1/log"
exit 1
fi
}
slice project "$WORK/old.3mf"
check project
slice uptodate "$WORK/old.3mf" --uptodate
check uptodate
# The replaced second filament takes the system values the refresh would have given it.
slice partial "$WORK/old.3mf" --load-filaments ";$PROFILES/filament/Bambu PLA Basic @BBL P1S 0.4 nozzle.json"
check partial
status=$?
[ "$status" -eq 0 ] || { tail -n 40 "$WORK/project/log"; exit 1; }
echo "PASS"
@@ -7,7 +7,6 @@
#include <catch2/matchers/catch_matchers_floating_point.hpp>
#include <catch2/benchmark/catch_benchmark.hpp>
#include "libslic3r/AABBTreeLines.hpp"
#include "libslic3r/GCode.hpp"
#include "libslic3r/GCode/ExtrusionProcessor.hpp"
#include "libslic3r/GCodeReader.hpp"
#include "libslic3r/TriangleMesh.hpp"
@@ -16,8 +15,6 @@
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <cstring>
#include <functional>
#include "libslic3r/Line.hpp"
#include "libslic3r/Point.hpp"
@@ -541,187 +538,6 @@ TEST_CASE("A wall is left whole where neither its speed nor its cooling changes"
REQUIRE(points.size() == 3);
}
namespace {
// The caged overhang box, sliced, and a layer on its slope.
struct SlicedCage
{
Print print;
Model model;
const PrintObject *object{nullptr};
const Layer *layer{nullptr};
explicit SlicedCage(const DynamicPrintConfig &config = caged_overhang_config("classic"))
{
init_print(std::vector<TriangleMesh>{caged_overhang_mesh()}, print, model, config, nullptr, false);
print.process();
object = print.objects().front();
layer = object->get_layer(int(std::lround((caged_slope_z_min + caged_slope_z_max) / 2. / caged_layer_height)));
}
};
using Walls = std::vector<std::vector<ProcessedPoint>>;
// Estimates every wall of `layer` against whatever layer `estimator` was last prepared with before it.
Walls estimate_walls(ExtrusionQualityEstimator &estimator, const PrintObject *object, const Layer &layer)
{
const ConfigOptionPercents overlaps({90, 75, 50, 25, 13, 0});
const ConfigOptionFloatsOrPercents speeds({FloatOrPercent{100, true}, FloatOrPercent{50, true}, FloatOrPercent{30, true},
FloatOrPercent{20, true}, FloatOrPercent{10, true}, FloatOrPercent{5, true}});
Walls walls;
estimator.set_current_object(object);
for (const LayerRegion *region : layer.regions())
for_each_extrusion_path(region->perimeters, [&](const ExtrusionPath &path) {
if (is_perimeter(path.role()))
walls.push_back(estimator.estimate_extrusion_quality(path, overlaps, speeds, caged_outer_wall_speed, caged_outer_wall_speed,
true, 0.5f));
});
return walls;
}
uint32_t float_bits(float value)
{
uint32_t bits;
std::memcpy(&bits, &value, sizeof(bits));
return bits;
}
bool same_point(const ProcessedPoint &a, const ProcessedPoint &b)
{
return a.p == b.p && float_bits(a.speed) == float_bits(b.speed) && float_bits(a.overlap) == float_bits(b.overlap);
}
// Requires the walls to match point for point, bit for bit.
void check_identical(const Walls &actual, const Walls &expected)
{
REQUIRE(actual.size() == expected.size());
for (size_t wall = 0; wall < actual.size(); ++wall) {
INFO("wall " << wall);
REQUIRE(actual[wall].size() == expected[wall].size());
for (size_t i = 0; i < actual[wall].size(); ++i) {
const ProcessedPoint &a = actual[wall][i];
const ProcessedPoint &e = expected[wall][i];
INFO("point " << i << ": speed " << a.speed << " vs " << e.speed << ", overlap " << a.overlap << " vs " << e.overlap);
CHECK(a.p == e.p);
CHECK(float_bits(a.speed) == float_bits(e.speed));
CHECK(float_bits(a.overlap) == float_bits(e.overlap));
}
}
}
bool any_difference(const Walls &a, const Walls &b)
{
return !std::equal(a.begin(), a.end(), b.begin(), b.end(), [](const std::vector<ProcessedPoint> &wa, const std::vector<ProcessedPoint> &wb) {
return std::equal(wa.begin(), wa.end(), wb.begin(), wb.end(), same_point);
});
}
bool any_slowed(const Walls &walls)
{
return std::any_of(walls.begin(), walls.end(), [](const std::vector<ProcessedPoint> &wall) {
return std::any_of(wall.begin(), wall.end(), [](const ProcessedPoint &point) { return point.speed < caged_outer_wall_speed; });
});
}
} // namespace
TEST_CASE("Overhang data computed ahead of the generator gives the same wall speeds", "[ExtrusionProcessor]")
{
const SlicedCage cage;
REQUIRE(cage.layer->lower_layer != nullptr);
ExtrusionQualityEstimator queried;
queried.prepare_for_new_layer(cage.object, cage.layer->lower_layer);
queried.prepare_for_new_layer(cage.object, cage.layer);
const Walls expected = estimate_walls(queried, cage.object, *cage.layer);
REQUIRE(any_slowed(expected));
ExtrusionQualityEstimator precomputed;
precomputed.prepare_for_new_layer(cage.object, cage.layer->lower_layer);
precomputed.set_precomputed_layers({precompute_overhang_layer(cage.object, *cage.layer)});
precomputed.prepare_for_new_layer(cage.object, cage.layer);
check_identical(estimate_walls(precomputed, cage.object, *cage.layer), expected);
}
TEST_CASE("Overhang distances measured against another layer than the previous one are not used", "[ExtrusionProcessor]")
{
const SlicedCage cage;
const Layer *two_below = cage.layer->lower_layer->lower_layer;
REQUIRE(two_below != nullptr);
ExtrusionQualityEstimator queried;
queried.prepare_for_new_layer(cage.object, two_below);
queried.prepare_for_new_layer(cage.object, cage.layer);
const Walls expected = estimate_walls(queried, cage.object, *cage.layer);
ExtrusionQualityEstimator one_below;
one_below.prepare_for_new_layer(cage.object, cage.layer->lower_layer);
one_below.prepare_for_new_layer(cage.object, cage.layer);
REQUIRE(any_difference(estimate_walls(one_below, cage.object, *cage.layer), expected));
ExtrusionQualityEstimator precomputed;
precomputed.prepare_for_new_layer(cage.object, two_below);
precomputed.set_precomputed_layers({precompute_overhang_layer(cage.object, *cage.layer)});
precomputed.prepare_for_new_layer(cage.object, cage.layer);
check_identical(estimate_walls(precomputed, cage.object, *cage.layer), expected);
}
TEST_CASE("Overhang data computed for another layer is not used", "[ExtrusionProcessor]")
{
const SlicedCage cage;
const Layer *one_below = cage.layer->lower_layer;
REQUIRE(one_below != nullptr);
REQUIRE(one_below->lower_layer != nullptr);
ExtrusionQualityEstimator queried;
queried.prepare_for_new_layer(cage.object, one_below);
queried.prepare_for_new_layer(cage.object, cage.layer);
const Walls expected = estimate_walls(queried, cage.object, *cage.layer);
ExtrusionQualityEstimator two_below;
two_below.prepare_for_new_layer(cage.object, one_below->lower_layer);
two_below.prepare_for_new_layer(cage.object, cage.layer);
REQUIRE(any_difference(estimate_walls(two_below, cage.object, *cage.layer), expected));
ExtrusionQualityEstimator precomputed;
precomputed.set_precomputed_layers({precompute_overhang_layer(cage.object, *one_below)});
precomputed.prepare_for_new_layer(cage.object, one_below);
precomputed.prepare_for_new_layer(cage.object, cage.layer);
check_identical(estimate_walls(precomputed, cage.object, *cage.layer), expected);
}
TEST_CASE("Precomputed overhang data has the curled-line tree exactly when a region slows down for curled perimeters", "[ExtrusionProcessor]")
{
const bool slowdown = GENERATE(false, true);
DynamicPrintConfig config = caged_overhang_config("classic");
config.set_deserialize_strict("slowdown_for_curled_perimeters", slowdown ? "1" : "0");
const SlicedCage cage(config);
GCode::LayerToPrint layer;
layer.object_layer = cage.layer;
layer.original_object = cage.object;
const std::vector<PrecomputedOverhangLayer> precomputed = precompute_overhang_layers({layer}, false);
REQUIRE(precomputed.size() == 1);
CHECK((precomputed.front().lower_curled_lines != nullptr) == slowdown);
}
TEST_CASE("Caged external overhangs are slowed when printed by object or through the pressure equalizer", "[ExtrusionProcessor]")
{
const auto [key, value] = GENERATE(table<const char *, const char *>({
{"print_sequence", "by object"},
{"max_volumetric_extrusion_rate_slope", "10"},
}));
INFO(key << " = " << value);
DynamicPrintConfig config = caged_overhang_config("classic");
config.set_deserialize_strict(key, value);
Print print;
Model model;
init_print(std::vector<TriangleMesh>{caged_overhang_mesh()}, print, model, config, nullptr, false);
const std::vector<double> feed_rates = caged_slope_feed_rates(gcode(print));
info_feed_rates("caged slope", feed_rates);
REQUIRE_FALSE(feed_rates.empty());
REQUIRE(*std::max_element(feed_rates.begin(), feed_rates.end()) < caged_slow_speed * MM_PER_MIN);
}
TEST_CASE("Benchmark caged overhang interior sampling", "[ExtrusionProcessor][!benchmark]"){
const char* wall_generator = GENERATE("classic", "arachne");
-92
View File
@@ -4,12 +4,7 @@
#include <catch2/generators/catch_generators.hpp>
#include "libslic3r/libslic3r.h"
#include "libslic3r/GCode/GCodeProcessor.hpp"
#include "libslic3r/Utils.hpp"
#include "test_utils.hpp"
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
@@ -84,90 +79,3 @@ TEST_CASE("Reserved keyword detection reports every offending line", "[GCodeProc
CHECK(tags.empty());
}
}
namespace {
// Closed outer-wall squares, each after a fast travel and before an inner-wall move, so the processor
// records seams and inserts actual speed moves. virtual_moves adds a VG1 move after each square.
void process_squares(int squares, GCodeProcessorResult &result, bool virtual_moves = false)
{
std::ostringstream gcode;
gcode << "M83\nG90\n";
for (int i = 0; i < squares; ++i) {
gcode << "G1 X10 Y10 Z" << 0.2 * (i + 1) << " F12000\n"
<< "; FEATURE: Outer wall\n"
<< "G1 X50 Y10 E2 F3000\nG1 X50 Y50 E2\nG1 X10 Y50 E2\nG1 X10 Y10 E2\n"
<< "; FEATURE: Inner wall\n"
<< "G1 X12 Y12 E0.1\nG1 X30 Y12 E1\n";
if (virtual_moves)
gcode << "VG1 X20 Y30 F12000\n";
}
FullPrintConfig config;
config.gcode_flavor.value = gcfMarlinFirmware;
// s_IsBBLPrinter selects the "; FEATURE: " role tags this G-code uses.
const bool was_bbl_printer = GCodeProcessor::s_IsBBLPrinter;
const ScopeGuard restore_bbl_printer([was_bbl_printer] { GCodeProcessor::s_IsBBLPrinter = was_bbl_printer; });
GCodeProcessor::s_IsBBLPrinter = true;
ScopedTemporaryFile temp(".gcode");
std::ofstream(temp.string()) << gcode.str();
GCodeProcessor processor;
processor.apply_config(config);
processor.process_file(temp.string());
result = std::move(processor.extract_result());
}
bool is_block_move(const GCodeProcessorResult::MoveVertex &move)
{
return !move.internal_only && (move.type == EMoveType::Extrude || move.type == EMoveType::Travel);
}
} // namespace
TEST_CASE("Actual speed moves are inserted on their block's segment just before its move", "[GCodeProcessor]")
{
// 60 squares take several planner passes, which remap the blocks kept between passes.
const int squares = GENERATE(10, 60);
const bool virtual_moves = GENERATE(false, true);
GCodeProcessorResult result;
process_squares(squares, result, virtual_moves);
const auto &moves = result.moves;
constexpr size_t normal = size_t(PrintEstimatedStatistics::ETimeMode::Normal);
size_t inserted = 0;
for (size_t i = 1; i < moves.size(); ++i) {
if (!moves[i].internal_only)
continue;
++inserted;
// Inserted moves have zero time, but a VG1 block's time is written to whatever move its move_id names.
if (!virtual_moves)
CHECK(moves[i].time[normal] == 0.f);
size_t block = i + 1;
while (block < moves.size() && moves[block].internal_only)
++block;
size_t previous = i - 1;
while (previous > 0 && moves[previous].internal_only)
--previous;
REQUIRE(block < moves.size());
CHECK(moves[block].gcode_id == moves[i].gcode_id);
const Vec3f segment = moves[block].position - moves[previous].position;
const Vec3f offset = moves[i].position - moves[previous].position;
CHECK(segment.cross(offset).norm() / segment.norm() < 1e-3f);
}
REQUIRE(inserted > 0);
}
TEST_CASE("A seam takes the actual speed of the move it follows", "[GCodeProcessor]")
{
GCodeProcessorResult result;
// 10 squares fit in one planner pass, so the seam's move and the block after it are timed together.
process_squares(10, result);
const auto &moves = result.moves;
size_t seams = 0;
for (size_t i = 1; i < moves.size(); ++i)
if (moves[i].type == EMoveType::Seam && is_block_move(moves[i - 1])) {
++seams;
CHECK_THAT(moves[i].actual_feedrate, Catch::Matchers::WithinAbs(moves[i - 1].actual_feedrate, 1e-4));
}
REQUIRE(seams > 0);
}
+36 -120
View File
@@ -22,7 +22,6 @@
#include "libslic3r/Print.hpp"
#include <limits>
#include <optional>
#include <regex>
#include <set>
#include <sstream>
#include <string>
@@ -843,47 +842,6 @@ TEST_CASE("Each filament sets the pressure advance of its extruder variant", "[M
}
}
// A two-extruder printer, Standard nozzle on extruder 1 and High Flow on extruder 2, whose per-variant arrays
// hold filament 1 Standard, filament 1 High Flow, filament 2 Standard and filament 2 High Flow.
static DynamicPrintConfig two_extruder_pressure_advance_config(const std::string &filament_map, const std::string &adaptive_pressure_advance,
int wall_filament, int infill_filament)
{
DynamicPrintConfig config = multifilament_config(2, {
{ "gcode_flavor", "klipper" },
{ "single_extruder_multi_material", 0 },
{ "nozzle_diameter", "0.4,0.4" },
{ "extruder_printable_height", "0,0" },
{ "printer_extruder_id", "1,2" },
{ "printer_extruder_variant", "Direct Drive Standard;Direct Drive High Flow" },
{ "extruder_variant_list", "Direct Drive Standard;Direct Drive High Flow" },
{ "filament_map", filament_map },
{ "filament_extruder_variant", "Direct Drive Standard;Direct Drive High Flow;Direct Drive Standard;Direct Drive High Flow" },
{ "filament_self_index", "1,1,2,2" },
{ "enable_pressure_advance", "1,1,1,1" },
{ "pressure_advance", "0.021,0.037,0.043,0.049" },
{ "adaptive_pressure_advance", adaptive_pressure_advance },
{ "sparse_infill_filament_id", infill_filament },
{ "internal_solid_filament_id", infill_filament },
{ "top_surface_filament_id", infill_filament },
{ "bottom_surface_filament_id", infill_filament },
{ "outer_wall_filament_id", wall_filament },
{ "inner_wall_filament_id", wall_filament },
{ "enable_prime_tower", 0 },
{ "skirt_loops", 0 },
{ "brim_type", "no_brim" },
// custom G-code indexes the per-filament arrays by filament
{ "machine_start_gcode", "; start pressure advance {pressure_advance[initial_extruder]}" },
});
config.option<ConfigOptionStrings>("adaptive_pressure_advance_model")->values = {
constant_pressure_advance_model("0.012"), constant_pressure_advance_model("0.034"),
constant_pressure_advance_model("0.056"), constant_pressure_advance_model("0.078") };
config.option<ConfigOptionEnumsGeneric>("extruder_type", true)->values = { etDirectDrive, etDirectDrive };
config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type", true)->values = { nvtStandard, nvtHighFlow };
// print each filament on the extruder filament_map gives it
config.option<ConfigOptionEnum<FilamentMapMode>>("filament_map_mode", true)->value = fmmManual;
return config;
}
// On a printer with two extruders, a filament takes the pressure advance of the variant of the extruder
// it is mapped to, whichever filament and extruder that is.
TEST_CASE("Each filament sets the pressure advance of its extruder variant on a two-extruder printer", "[MultiFilament]")
@@ -899,8 +857,42 @@ TEST_CASE("Each filament sets the pressure advance of its extruder variant on a
// the other filament goes on the other extruder
const std::string filament_map = filament == 1 ? std::to_string(extruder) + "," + std::to_string(3 - extruder) :
std::to_string(3 - extruder) + "," + std::to_string(extruder);
const std::string gcode = slice({ cube(20) },
two_extruder_pressure_advance_config(filament_map, adaptive ? "1,1,1,1" : "0,0,0,0", filament, filament));
DynamicPrintConfig config = multifilament_config(2, {
{ "gcode_flavor", "klipper" },
{ "single_extruder_multi_material", 0 },
{ "nozzle_diameter", "0.4,0.4" },
{ "extruder_printable_height", "0,0" },
// extruder 1 has a Standard nozzle, extruder 2 a High Flow one
{ "printer_extruder_id", "1,2" },
{ "printer_extruder_variant", "Direct Drive Standard;Direct Drive High Flow" },
{ "extruder_variant_list", "Direct Drive Standard;Direct Drive High Flow" },
{ "filament_map", filament_map },
// both filaments define Standard and High Flow
{ "filament_extruder_variant", "Direct Drive Standard;Direct Drive High Flow;Direct Drive Standard;Direct Drive High Flow" },
{ "filament_self_index", "1,1,2,2" },
{ "enable_pressure_advance", "1,1,1,1" },
{ "pressure_advance", "0.021,0.037,0.043,0.049" },
{ "adaptive_pressure_advance", adaptive ? "1,1,1,1" : "0,0,0,0" },
{ "sparse_infill_filament_id", filament },
{ "internal_solid_filament_id", filament },
{ "top_surface_filament_id", filament },
{ "bottom_surface_filament_id", filament },
{ "outer_wall_filament_id", filament },
{ "inner_wall_filament_id", filament },
{ "enable_prime_tower", 0 },
{ "skirt_loops", 0 },
{ "brim_type", "no_brim" },
// custom G-code indexes the per-filament arrays by filament
{ "machine_start_gcode", "; start pressure advance {pressure_advance[initial_extruder]}" },
});
config.option<ConfigOptionStrings>("adaptive_pressure_advance_model")->values = {
constant_pressure_advance_model("0.012"), constant_pressure_advance_model("0.034"),
constant_pressure_advance_model("0.056"), constant_pressure_advance_model("0.078") };
config.option<ConfigOptionEnumsGeneric>("extruder_type", true)->values = { etDirectDrive, etDirectDrive };
config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type", true)->values = { nvtStandard, nvtHighFlow };
// keep the mapping above rather than grouping the filaments automatically
config.option<ConfigOptionEnum<FilamentMapMode>>("filament_map_mode", true)->value = fmmManual;
const std::string gcode = slice({ cube(20) }, config);
std::set<std::string> expected{ pressure_advance };
if (adaptive)
@@ -910,82 +902,6 @@ TEST_CASE("Each filament sets the pressure advance of its extruder variant on a
}
}
// Filament 1 prints the walls on extruder 1 (variant index 0), filament 2 the infill on extruder 2 (variant index 3).
TEST_CASE("Adaptive pressure advance on one extruder leaves the other extruder's pressure advance alone", "[MultiFilament]")
{
auto [adaptive, expected] = GENERATE(table<std::string, std::set<std::string>>({
{ "1,0,0,0", { "0.021", "0.049", "0.012" } },
{ "0,0,0,1", { "0.021", "0.049", "0.078" } },
}));
DYNAMIC_SECTION("adaptive " << adaptive) {
const std::string gcode = slice({ cube(20) }, two_extruder_pressure_advance_config("1,2", adaptive, 1, 2));
CHECK(pressure_advance_values(gcode) == expected);
}
}
// The pressure advance values a Klipper G-code sets while `tool` is active, in order, without repeating the value already set.
static std::vector<std::string> pressure_advance_sequence(const std::string &gcode, int tool)
{
const std::string token = "SET_PRESSURE_ADVANCE ADVANCE=";
std::vector<std::string> values;
int current_tool = 0;
std::istringstream stream(gcode);
for (std::string line; std::getline(stream, line);) {
if (line.size() > 1 && line[0] == 'T' && std::isdigit((unsigned char) line[1]))
current_tool = std::stoi(line.substr(1));
else if (current_tool == tool && line.rfind(token, 0) == 0) {
std::string value = line.substr(token.size(), line.find(';') - token.size());
if (values.empty() || values.back() != value)
values.push_back(std::move(value));
}
}
return values;
}
TEST_CASE("Adaptive pressure advance predicts the same values after layers only a non-adaptive extruder prints", "[MultiFilament]")
{
auto slice_with = [](const std::string &adaptive) {
DynamicPrintConfig config = two_extruder_pressure_advance_config("1,2", adaptive, 1, 1);
config.set_deserialize_strict({
{ "print_sequence", "by object" },
// extruder 2 moves at one speed on its first layer, so no G1 F follows its first PA_CHANGE tag
{ "filament_max_volumetric_speed", "100" },
{ "slow_down_for_layer_cooling", "0" },
{ "travel_speed", "120,40" },
{ "retraction_speed", "30,40" },
{ "deretraction_speed", "30,40" },
{ "initial_layer_speed", "30,40" },
{ "initial_layer_infill_speed", "60,40" },
});
auto &models = config.option<ConfigOptionStrings>("adaptive_pressure_advance_model")->values;
// with adaptive pressure advance on, filament 1 gets PA_CHANGE tags on every layer but keeps its pressure advance
models[0] = constant_pressure_advance_model("0.021");
// a prediction that rises with flow, so it depends on the print speed
models[3] = "0.01,1,1000\n0.09,40,1000\n0.01,1,100000\n0.09,40,100000";
return slice_with_object_overrides({ cube(20), cube(20) }, config,
{ {}, { { "outer_wall_filament_id", 2 }, { "inner_wall_filament_id", 2 }, { "sparse_infill_filament_id", 2 },
{ "internal_solid_filament_id", 2 }, { "top_surface_filament_id", 2 }, { "bottom_surface_filament_id", 2 } } });
};
const std::vector<std::string> expected = pressure_advance_sequence(slice_with("1,0,0,1"), 1);
REQUIRE(expected.size() > 2);
CHECK(pressure_advance_sequence(slice_with("0,0,0,1"), 1) == expected);
}
TEST_CASE("Adaptive pressure advance on an unused extruder variant leaves the G-code unchanged", "[MultiFilament]")
{
const std::string adaptive = GENERATE("0,1,0,0", "0,0,1,0");
DYNAMIC_SECTION("adaptive " << adaptive) {
// the time and object ids differ between any two slices, and the config block lists the setting itself
auto masked = [](const std::string &gcode) {
return std::regex_replace(gcode.substr(0, gcode.find("; CONFIG_BLOCK_START")), std::regex("; generated by .*| id:\\d+"), "");
};
const std::string reference = masked(slice({ cube(20) }, two_extruder_pressure_advance_config("1,2", "0,0,0,0", 1, 2)));
const std::string gcode = masked(slice({ cube(20) }, two_extruder_pressure_advance_config("1,2", adaptive, 1, 2)));
REQUIRE(reference.find("SET_PRESSURE_ADVANCE") != std::string::npos);
CHECK(gcode == reference);
}
}
// The speeds, in percent, a G-code turns a fan on at: the part cooling fan for `M106 S`, the auxiliary
// fan for `M106 P2 S`.
static std::set<int> fan_speeds(const std::string &gcode, const std::string &command)
-1
View File
@@ -9,7 +9,6 @@ add_executable(${_TEST_NAME}_tests
test_appconfig.cpp
test_arachne_walls.cpp
test_arrange.cpp
test_assemble_list.cpp
test_bambu_networking.cpp
test_buildvolume.cpp
test_calib.cpp
-266
View File
@@ -1,266 +0,0 @@
#include <catch2/catch_all.hpp>
#include "libslic3r/Format/AssembleList.hpp"
#include "test_utils.hpp"
#include <boost/nowide/fstream.hpp>
#include <nlohmann/json.hpp>
#include <string>
#include <vector>
using namespace Slic3r;
using Catch::Matchers::WithinAbs;
using nlohmann::json;
static constexpr int max_plates = 36;
static AssembleListResult load_text(const std::string &text, std::vector<assemble_plate_info_t> &plates)
{
ScopedTemporaryFile file(".json");
{
boost::nowide::ofstream out(file.string());
out << text;
}
return load_assemble_plate_list(file.string(), plates, max_plates);
}
static AssembleListResult load_json(const json &root)
{
std::vector<assemble_plate_info_t> plates;
return load_text(root.dump(), plates);
}
// One plate with one object of three clones, which every optional field accepts.
static json valid_list()
{
return json::parse(R"({
"plates": [{
"plate_name": "plate",
"need_arrange": false,
"objects": [{
"path": "cube.stl",
"count": 3,
"filaments": [1],
"height_ranges": [{ "min_z": 0, "max_z": 5, "range_params": { "layer_height": "0.1" } }]
}],
"assembled_params": [{
"assemble_index": 1,
"height_ranges": [{ "min_z": 0, "max_z": 5, "range_params": { "layer_height": "0.1" } }]
}]
}]
})");
}
TEST_CASE("A valid assemble list parses into its plates and objects", "[AssembleList]")
{
const std::string text = R"({
"plates": [
{
"plate_name": "first",
"need_arrange": true,
"plate_params": { "curr_bed_type": "Textured PEI Plate" },
"objects": [
{
"path": "a.stl",
"count": 2,
"filaments": [1, 3],
"assemble_index": [1],
"pos_x": [10.5, 20.5],
"pos_y": [30],
"pos_z": [0, 1],
"print_params": { "sparse_infill_density": "30%" },
"height_ranges": [{ "min_z": 1.5, "max_z": 4, "range_params": { "layer_height": "0.12" } }]
},
{ "path": "b.stl", "count": 1, "filaments": [0] }
],
"assembled_params": [{ "assemble_index": 1, "print_params": { "wall_loops": "4" } }]
},
{
"plate_name": "second",
"need_arrange": false,
"objects": [{ "path": "c.stl", "count": 1, "filaments": [2] }]
}
]
})";
std::vector<assemble_plate_info_t> plates;
REQUIRE(load_text(text, plates) == AssembleListResult::Success);
REQUIRE(plates.size() == 2);
const assemble_plate_info_t &first = plates[0];
CHECK(first.plate_name == "first");
CHECK(first.need_arrange);
CHECK(first.plate_params.at("curr_bed_type") == "Textured PEI Plate");
REQUIRE(first.assemble_obj_list.size() == 2);
const assemble_object_info_t &a = first.assemble_obj_list[0];
CHECK(a.path == "a.stl");
CHECK(a.count == 2);
CHECK(a.filaments == std::vector<int>{1, 3});
CHECK(a.assemble_index == std::vector<int>{1});
REQUIRE(a.pos_x.size() == 2);
CHECK_THAT(a.pos_x[0], WithinAbs(10.5, 1e-6));
CHECK_THAT(a.pos_x[1], WithinAbs(20.5, 1e-6));
REQUIRE(a.pos_y.size() == 1);
CHECK_THAT(a.pos_y[0], WithinAbs(30., 1e-6));
REQUIRE(a.pos_z.size() == 2);
CHECK_THAT(a.pos_z[1], WithinAbs(1., 1e-6));
CHECK(a.print_params.at("sparse_infill_density") == "30%");
REQUIRE(a.height_ranges.size() == 1);
CHECK_THAT(a.height_ranges[0].min_z, WithinAbs(1.5, 1e-6));
CHECK_THAT(a.height_ranges[0].max_z, WithinAbs(4., 1e-6));
CHECK(a.height_ranges[0].range_params.at("layer_height") == "0.12");
const assemble_object_info_t &b = first.assemble_obj_list[1];
CHECK(b.path == "b.stl");
CHECK(b.count == 1);
CHECK(b.filaments == std::vector<int>{0});
CHECK(b.pos_x.empty());
CHECK(b.assemble_index.empty());
REQUIRE(first.assembled_param_list.count(1) == 1);
CHECK(first.assembled_param_list.at(1).print_params.at("wall_loops") == "4");
const assemble_plate_info_t &second = plates[1];
CHECK(second.plate_name == "second");
CHECK_FALSE(second.need_arrange);
REQUIRE(second.assemble_obj_list.size() == 1);
CHECK(second.assemble_obj_list[0].path == "c.stl");
CHECK(second.assemble_obj_list[0].filaments == std::vector<int>{2});
}
TEST_CASE("The unmodified fixture used by the rule tests is accepted", "[AssembleList]")
{
CHECK(load_json(valid_list()) == AssembleListResult::Success);
}
TEST_CASE("An object with an empty filament list is rejected", "[AssembleList]")
{
json root = valid_list();
root["plates"][0]["objects"][0]["filaments"] = json::array();
CHECK(load_json(root) == AssembleListResult::ConfigError);
}
TEST_CASE("An object with a negative filament id is rejected", "[AssembleList]")
{
json root = valid_list();
root["plates"][0]["objects"][0]["filaments"] = GENERATE(json::array({-1}), json::array({1, -2, 1}));
CAPTURE(root["plates"][0]["objects"][0]["filaments"].dump());
CHECK(load_json(root) == AssembleListResult::ConfigError);
}
TEST_CASE("Filament id 0 is accepted", "[AssembleList]")
{
json root = valid_list();
root["plates"][0]["objects"][0]["filaments"] = GENERATE(json::array({0}), json::array({0, 1, 0}));
CAPTURE(root["plates"][0]["objects"][0]["filaments"].dump());
CHECK(load_json(root) == AssembleListResult::Success);
}
TEST_CASE("Per-clone lists need one entry or one per clone", "[AssembleList]")
{
// The fixture object has 3 clones.
const std::string key = GENERATE("filaments", "assemble_index", "pos_x", "pos_y", "pos_z");
const size_t size = GENERATE(1, 2, 3, 4);
CAPTURE(key, size);
json root = valid_list();
root["plates"][0]["objects"][0][key] = json(std::vector<int>(size, 1));
const AssembleListResult expected = (size == 1 || size == 3) ? AssembleListResult::Success : AssembleListResult::ConfigError;
CHECK(load_json(root) == expected);
}
TEST_CASE("An empty optional per-clone list is accepted", "[AssembleList]")
{
const std::string key = GENERATE("assemble_index", "pos_x", "pos_y", "pos_z");
CAPTURE(key);
json root = valid_list();
root["plates"][0]["objects"][0][key] = json::array();
CHECK(load_json(root) == AssembleListResult::Success);
}
// Fields read through a const reference (plate_name, need_arrange, objects, path, count) are
// looked up without a presence check, so only their wrong-type case is covered here.
TEST_CASE("A missing required field is rejected", "[AssembleList]")
{
const std::string pointer = GENERATE("/plates",
"/plates/0/objects/0/filaments",
"/plates/0/objects/0/height_ranges/0/min_z",
"/plates/0/objects/0/height_ranges/0/max_z",
"/plates/0/objects/0/height_ranges/0/range_params",
"/plates/0/assembled_params/0/assemble_index",
"/plates/0/assembled_params/0/height_ranges/0/min_z",
"/plates/0/assembled_params/0/height_ranges/0/max_z",
"/plates/0/assembled_params/0/height_ranges/0/range_params");
CAPTURE(pointer);
json root = valid_list();
const json::json_pointer ptr(pointer);
root[ptr.parent_pointer()].erase(ptr.back());
CHECK(load_json(root) == AssembleListResult::ConfigError);
}
TEST_CASE("A field of the wrong type is rejected", "[AssembleList]")
{
const std::string pointer = GENERATE("/plates/0/plate_name",
"/plates/0/need_arrange",
"/plates/0/objects/0/path",
"/plates/0/objects/0/count",
"/plates/0/objects/0/filaments",
"/plates/0/objects/0/pos_x");
CAPTURE(pointer);
json root = valid_list();
root[json::json_pointer(pointer)] = json::object();
CHECK(load_json(root) == AssembleListResult::ConfigError);
}
TEST_CASE("A plate or clone count out of range is rejected", "[AssembleList]")
{
SECTION("no plates")
{
json root = valid_list();
root["plates"] = json::array();
CHECK(load_json(root) == AssembleListResult::ConfigError);
}
SECTION("more plates than the limit")
{
json root = valid_list();
const json plate = root["plates"][0];
for (int i = 1; i < max_plates; ++i)
root["plates"].push_back(plate);
CHECK(load_json(root) == AssembleListResult::Success);
root["plates"].push_back(plate);
CHECK(load_json(root) == AssembleListResult::ConfigError);
}
SECTION("a plate with no objects")
{
json root = valid_list();
root["plates"][0]["objects"] = json::array();
CHECK(load_json(root) == AssembleListResult::ConfigError);
}
SECTION("a clone count below 1")
{
json root = valid_list();
root["plates"][0]["objects"][0]["count"] = GENERATE(0, -1);
CAPTURE(root["plates"][0]["objects"][0]["count"].dump());
CHECK(load_json(root) == AssembleListResult::ConfigError);
}
}
TEST_CASE("Malformed JSON is rejected", "[AssembleList]")
{
const std::string text = GENERATE(std::string(), std::string("{\"plates\": ["), std::string("not json"));
CAPTURE(text);
std::vector<assemble_plate_info_t> plates;
CHECK(load_text(text, plates) == AssembleListResult::ConfigError);
}
TEST_CASE("A missing file is reported as not found", "[AssembleList]")
{
ScopedTemporaryFile file(".json");
std::vector<assemble_plate_info_t> plates;
CHECK(load_assemble_plate_list(file.string(), plates, max_plates) == AssembleListResult::FileNotFound);
}
+2
View File
@@ -29,6 +29,8 @@
#include <vector>
#include <utility>
namespace fs = boost::filesystem;
using namespace Slic3r;
SCENARIO("Generic config validation performs as expected.", "[Config]") {
-19
View File
@@ -69,22 +69,3 @@ TEST_CASE("a setter nested in another sets C again when the locale changed betwe
CHECK_FALSE(is_decimal_separator_point());
}
}
TEST_CASE("atof_decimal_point parses what atof parses in the C locale", "[LocalesUtils]")
{
const auto [text, value] = GENERATE(table<const char*, double>({
{ "5", 5. },
{ " 12.5", 12.5 },
{ "\t+3", 3. },
{ "\r\n7", 7. },
{ "-1.25", -1.25 },
{ "1e2", 100. },
{ ".5", 0.5 },
{ "12.5;comment", 12.5 },
{ "+-5", 0. },
{ "", 0. },
{ "abc", 0. },
}));
INFO(text);
CHECK_THAT(atof_decimal_point(text), Catch::Matchers::WithinAbs(value, 1e-12));
}
@@ -5816,131 +5816,6 @@ TEST_CASE("A system preset no vendor lists is not resolved", "[Preset][Bundle]")
namespace {
// Writes each vendor's preset cache into dir, then deletes its profile JSONs: what a release build installs.
void reduce_vendors_to_caches(const fs::path &dir, const std::vector<std::string> &vendor_ids)
{
const std::string lib(PresetBundle::ORCA_FILAMENT_LIBRARY);
PresetBundle library;
if (fs::exists(dir / (lib + ".json"))) {
library.set_generate_vendor_caches(true);
library.load_vendor_configs_from_json(dir.string(), lib, PresetBundle::LoadSystem,
ForwardCompatibilitySubstitutionRule::EnableSilent);
}
for (const std::string &vendor_id : vendor_ids) {
if (vendor_id == lib)
continue;
PresetBundle writer;
writer.set_generate_vendor_caches(true);
writer.load_vendor_configs_from_json(dir.string(), vendor_id, PresetBundle::LoadSystem,
ForwardCompatibilitySubstitutionRule::EnableSilent, &library);
}
for (const std::string &vendor_id : vendor_ids) {
REQUIRE(fs::exists(dir / (vendor_id + ".opc")));
fs::remove(dir / (vendor_id + ".json"));
fs::remove_all(dir / vendor_id);
}
}
// The filament library with one abstract base filament, and an "Acme" vendor whose one filament inherits it.
void write_library_and_acme_filament(const fs::path &root)
{
const std::string lib(PresetBundle::ORCA_FILAMENT_LIBRARY);
fs::create_directories(root / lib / "filament");
std::ofstream((root / (lib + ".json")).string())
<< R"({"version":"1.0.0","name":")" << lib << R"(",)"
<< R"("filament_list":[{"name":"Generic PLA","sub_path":"filament/generic_pla.json"}]})";
std::ofstream((root / lib / "filament" / "generic_pla.json").string())
<< R"({"type":"filament","name":"Generic PLA","from":"system","instantiation":"false","filament_id":"GFL99","filament_cost":"27"})";
fs::create_directories(root / "Acme" / "filament");
std::ofstream((root / "Acme.json").string())
<< R"({"version":"1.0.0","name":"Acme","filament_list":[{"name":"Acme PLA","sub_path":"filament/pla.json"}]})";
std::ofstream((root / "Acme" / "filament" / "pla.json").string())
<< R"({"type":"filament","name":"Acme PLA","from":"system","instantiation":"true","inherits":"Generic PLA"})";
}
} // namespace
TEST_CASE("A read-only load resolves a user preset against vendors installed as their cache alone", "[Preset][Bundle][Regression]")
{
ScopedTemporaryDir temp_dir;
const fs::path data = temp_dir.path() / "data";
const fs::path system = data / PRESET_SYSTEM_DIR;
ScopedDataDir scoped_data(data);
ScopedResourcesDir scoped_resources(temp_dir.path() / "resources");
write_acme_printer_vendor(system, 33.);
reduce_vendors_to_caches(system, {"Acme"});
fs::create_directories(data / PRESET_USER_DIR / DEFAULT_USER_FOLDER_NAME / PRESET_PRINTER_NAME);
std::ofstream((data / PRESET_USER_DIR / DEFAULT_USER_FOLDER_NAME / PRESET_PRINTER_NAME / "My Acme.json").string())
<< R"({"type":"machine","name":"My Acme","from":"User","version":"2.3.0.0","inherits":"Acme Printer","printable_height":"123"})";
AppConfig app_config;
PresetBundle bundle;
std::string errors;
bundle.load_presets(app_config, ForwardCompatibilitySubstitutionRule::EnableSilent, PresetBundle::PresetPreferences(),
&errors, true);
CHECK(errors.empty());
const Preset *preset = bundle.printers.find_preset("My Acme");
REQUIRE(preset != nullptr);
CHECK_THAT(preset->config.opt_float("printable_height"), Catch::Matchers::WithinAbs(123., 1e-6));
CHECK_THAT(preset->config.opt_float("extruder_clearance_dist_to_rod"), Catch::Matchers::WithinAbs(33., 1e-6));
}
TEST_CASE("A read-only load writes no preset cache", "[Preset][Bundle][Regression]")
{
ScopedTemporaryDir temp_dir;
const fs::path system = temp_dir.path() / "data" / PRESET_SYSTEM_DIR;
ScopedDataDir scoped_data(temp_dir.path() / "data");
ScopedResourcesDir scoped_resources(temp_dir.path() / "resources");
write_acme_printer_vendor(system, 33.);
AppConfig app_config;
PresetBundle bundle;
std::string errors;
bundle.load_presets(app_config, ForwardCompatibilitySubstitutionRule::EnableSilent, PresetBundle::PresetPreferences(),
&errors, true);
CHECK(errors.empty());
CHECK(bundle.printers.find_preset("Acme Printer") != nullptr);
CHECK_FALSE(fs::exists(system / "Acme.opc"));
}
TEST_CASE("A vendor updated over the air resolves against the library installed as its cache alone", "[Preset][Bundle][Regression]")
{
ScopedTemporaryDir temp_dir;
const fs::path system = temp_dir.path() / "data" / PRESET_SYSTEM_DIR;
ScopedDataDir scoped_data(temp_dir.path() / "data");
ScopedResourcesDir scoped_resources(temp_dir.path() / "resources");
// System presets are found by name through the bundled profiles.
write_library_and_acme_filament(temp_dir.path() / "resources" / PRESET_PROFILES_DIR);
// The release install, then an update that brings Acme back as JSONs while the library stays a cache.
write_library_and_acme_filament(system);
reduce_vendors_to_caches(system, {PresetBundle::ORCA_FILAMENT_LIBRARY, "Acme"});
write_library_and_acme_filament(temp_dir.path() / "update");
fs::copy_file(temp_dir.path() / "update" / "Acme.json", system / "Acme.json");
fs::create_directories(system / "Acme" / "filament");
fs::copy_file(temp_dir.path() / "update" / "Acme" / "filament" / "pla.json", system / "Acme" / "filament" / "pla.json");
SECTION("by name") {
PresetBundle bundle;
DynamicPrintConfig config;
std::string error;
REQUIRE(bundle.resolve_system_preset(config, Preset::TYPE_FILAMENT, "Acme PLA",
ForwardCompatibilitySubstitutionRule::EnableSilent, error));
CHECK_THAT(config.opt<ConfigOptionFloats>("filament_cost")->values.front(), Catch::Matchers::WithinAbs(27., 1e-6));
}
SECTION("by its source file") {
PresetBundle bundle;
DynamicPrintConfig config;
config.option<ConfigOptionString>(BBL_JSON_KEY_INHERITS, true)->value = "Generic PLA";
std::string error;
REQUIRE(bundle.resolve_preset_config(config, Preset::TYPE_FILAMENT, (system / "Acme" / "filament" / "pla.json").string(),
ForwardCompatibilitySubstitutionRule::EnableSilent, error));
CHECK_THAT(config.opt<ConfigOptionFloats>("filament_cost")->values.front(), Catch::Matchers::WithinAbs(27., 1e-6));
}
}
namespace {
// A default preset config for type, built the way PresetBundle builds its default presets.
DynamicPrintConfig external_default_config(Preset::Type type)
{
-12
View File
@@ -509,15 +509,3 @@ TEST_CASE("is_safe_to_open_file_name rejects programs and anything it does not k
INFO(unsafe);
CHECK_FALSE(is_safe_to_open_file_name(unsafe));
}
TEST_CASE("ascii_iequals compares ASCII letters regardless of case", "[Utils]") {
CHECK(ascii_iequals("set_velocity_limit", "SET_VELOCITY_LIMIT"));
CHECK(ascii_iequals("G28", "g28"));
CHECK(ascii_iequals("", ""));
CHECK_FALSE(ascii_iequals("G28", "G29"));
CHECK_FALSE(ascii_iequals("G2", "G28"));
CHECK_FALSE(ascii_iequals("G28", "G2"));
// Non-letters 0x20 apart are not equal.
CHECK_FALSE(ascii_iequals("[", "{"));
CHECK_FALSE(ascii_iequals("@", "`"));
}