mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-03 22:01:03 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc2ec7de0f | ||
|
|
d1a3ef68c5 | ||
|
|
a80c323614 |
+24
-165
@@ -685,162 +685,6 @@ 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) {
|
||||
@@ -1906,6 +1750,14 @@ 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();
|
||||
@@ -1927,14 +1779,11 @@ 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;
|
||||
}
|
||||
|
||||
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 {
|
||||
// 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[index-1] = current_inherits_group[index];
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -2067,7 +1916,12 @@ int CLI::run(int argc, char **argv)
|
||||
//parse the json and assemble object here
|
||||
Model model;
|
||||
|
||||
int ret = load_assemble_plate_list(load_assemble_list, assemble_plate_info_list);
|
||||
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;
|
||||
if (ret) {
|
||||
record_exit_reson(outfile_dir, ret, 0, cli_errors[ret], sliced_info);
|
||||
flush_and_exit(ret);
|
||||
@@ -4881,6 +4735,11 @@ 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");
|
||||
@@ -5821,7 +5680,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[plate_to_slice-1]->slice_filaments_info.size();
|
||||
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;
|
||||
if ((filaments_cnt == 0) || need_skip)
|
||||
{
|
||||
// slice filaments info invalid
|
||||
|
||||
+1
-58
@@ -12,6 +12,7 @@
|
||||
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/Format/AssembleList.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
@@ -26,64 +27,6 @@ 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};
|
||||
|
||||
@@ -202,6 +202,8 @@ 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
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
#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
|
||||
@@ -0,0 +1,60 @@
|
||||
#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_ */
|
||||
@@ -626,13 +626,15 @@ 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.
|
||||
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);
|
||||
// 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);
|
||||
if (library == nullptr) {
|
||||
error = "OrcaFilamentLibrary contains invalid presets";
|
||||
return nullptr;
|
||||
@@ -2576,7 +2578,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 allow_cache)
|
||||
ForwardCompatibilitySubstitutionRule compatibility_rule, bool write_caches)
|
||||
{
|
||||
//BBS: add config related logs
|
||||
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" enter, compatibility_rule %1%")%compatibility_rule;
|
||||
@@ -2596,14 +2598,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 = allow_cache && (m_generate_vendor_caches || !validation_mode);
|
||||
m_generate_vendor_caches = write_caches && (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, allow_cache);
|
||||
auto result = this->load_vendors(vendors, compatibility_rule, true);
|
||||
|
||||
this->update_system_maps();
|
||||
|
||||
|
||||
@@ -851,7 +851,9 @@ private:
|
||||
|
||||
//std::pair<PresetsConfigSubstitutions, std::string> load_system_presets(ForwardCompatibilitySubstitutionRule compatibility_rule);
|
||||
//BBS: add json related logic
|
||||
std::pair<PresetsConfigSubstitutions, std::string> load_system_presets_from_json(ForwardCompatibilitySubstitutionRule compatibility_rule, bool allow_cache = true);
|
||||
// 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);
|
||||
// Update the multicolor information for filaments.
|
||||
void update_filament_multi_color();
|
||||
// Update renamed_from and alias maps of system profiles.
|
||||
|
||||
@@ -11513,12 +11513,18 @@ void DynamicPrintConfig::update_non_diff_values_to_base_config(DynamicPrintConfi
|
||||
int cur_variant_count = cur_extruder_variants.size();
|
||||
int target_variant_count = target_extruder_variants.size();
|
||||
|
||||
// A base variant this config does not list (the base gained it after the config was saved, or the
|
||||
// config lists none) takes this config's first variant of the same extruder, as a user preset's
|
||||
// values do in update_diff_values_to_child_config. Left unmatched, the base's value would silently
|
||||
// replace the user's.
|
||||
variant_index.resize(target_variant_count, -1);
|
||||
if (cur_variant_count == 0) {
|
||||
// Defensive: target_variant_count may be 0 if the preset doesn't carry extruder_variant_name.
|
||||
// In that case keep variant_index empty and let the downstream size checks produce a useful error.
|
||||
if (!variant_index.empty())
|
||||
variant_index[0] = 0;
|
||||
// This config's one value belongs to the extruder of the base's first variant.
|
||||
variant_index = map_variant_indices(target_extruder_variants, target_extruder_ids, {},
|
||||
target_extruder_ids.empty() ? std::vector<int>() : std::vector<int>{target_extruder_ids[0]});
|
||||
}
|
||||
else if ((cur_extruder_ids.size() > 0) && cur_variant_count != cur_extruder_ids.size()){
|
||||
//should not happen
|
||||
@@ -11531,18 +11537,7 @@ void DynamicPrintConfig::update_non_diff_values_to_base_config(DynamicPrintConfi
|
||||
%extruder_variant_name %target_variant_count %extruder_id_name %target_extruder_ids.size();
|
||||
}
|
||||
else {
|
||||
for (int i = 0; i < target_variant_count; i++)
|
||||
{
|
||||
for (int j = 0; j < cur_variant_count; j++)
|
||||
{
|
||||
if ((target_extruder_variants[i] == cur_extruder_variants[j])
|
||||
&&(target_extruder_ids.empty() || (target_extruder_ids[i] == cur_extruder_ids[j])))
|
||||
{
|
||||
variant_index[i] = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
variant_index = map_variant_indices(target_extruder_variants, target_extruder_ids, cur_extruder_variants, cur_extruder_ids);
|
||||
}
|
||||
|
||||
for (auto& opt : keys) {
|
||||
@@ -11567,6 +11562,13 @@ void DynamicPrintConfig::update_non_diff_values_to_base_config(DynamicPrintConfi
|
||||
if (cur_variant_count > target_variant_count)
|
||||
continue;
|
||||
|
||||
// The variant lists are the base's layout itself, which every other value is
|
||||
// carried onto: a variant this config lacks keeps its own name and id.
|
||||
if (opt == extruder_id_name || opt == extruder_variant_name) {
|
||||
opt_src->set(opt_target);
|
||||
continue;
|
||||
}
|
||||
|
||||
int stride = 1;
|
||||
if (key_set2.find(opt) != key_set2.end())
|
||||
stride = 2;
|
||||
|
||||
@@ -23,3 +23,10 @@ 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)
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
#!/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"
|
||||
@@ -9,6 +9,7 @@ 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
|
||||
|
||||
@@ -0,0 +1,266 @@
|
||||
#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);
|
||||
}
|
||||
@@ -536,6 +536,110 @@ SCENARIO("update_diff_values_to_child_config keeps a child's values on variants
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO("update_non_diff_values_to_base_config keeps a project's changed values on variants it does not list",
|
||||
"[Config][Variant]") {
|
||||
std::set<std::string> no_keys;
|
||||
auto variants = [](std::initializer_list<std::string> names) { return new Slic3r::ConfigOptionStrings(names); };
|
||||
|
||||
GIVEN("A filament base with three variants") {
|
||||
Slic3r::DynamicPrintConfig base;
|
||||
base.set_key_value("filament_extruder_variant",
|
||||
variants({"Direct Drive Standard", "Bowden Standard", "Direct Drive High Flow"}));
|
||||
base.set_deserialize_strict("nozzle_temperature", "220,220,220");
|
||||
|
||||
WHEN("the project was saved when the base had only its first variant") {
|
||||
Slic3r::DynamicPrintConfig project;
|
||||
project.set_key_value("filament_extruder_variant", variants({"Direct Drive Standard"}));
|
||||
project.set_deserialize_strict("nozzle_temperature", "199");
|
||||
|
||||
AND_WHEN("the project lists the value as changed") {
|
||||
project.update_non_diff_values_to_base_config(base, project.keys(), {"nozzle_temperature"}, "", "filament_extruder_variant",
|
||||
Slic3r::filament_options_with_variant, no_keys);
|
||||
THEN("the project's value applies to every variant") {
|
||||
REQUIRE(project.opt_serialize("nozzle_temperature") == "199,199,199");
|
||||
}
|
||||
}
|
||||
AND_WHEN("the project does not list the value as changed") {
|
||||
project.update_non_diff_values_to_base_config(base, project.keys(), {}, "", "filament_extruder_variant",
|
||||
Slic3r::filament_options_with_variant, no_keys);
|
||||
THEN("the base's values replace it") {
|
||||
REQUIRE(project.opt_serialize("nozzle_temperature") == "220,220,220");
|
||||
}
|
||||
}
|
||||
}
|
||||
WHEN("the project lists every variant, in another order") {
|
||||
Slic3r::DynamicPrintConfig project;
|
||||
project.set_key_value("filament_extruder_variant",
|
||||
variants({"Bowden Standard", "Direct Drive High Flow", "Direct Drive Standard"}));
|
||||
project.set_deserialize_strict("nozzle_temperature", "190,205,199");
|
||||
project.update_non_diff_values_to_base_config(base, project.keys(), {"nozzle_temperature"}, "", "filament_extruder_variant",
|
||||
Slic3r::filament_options_with_variant, no_keys);
|
||||
THEN("each variant keeps its own value") {
|
||||
REQUIRE(project.opt_serialize("nozzle_temperature") == "199,190,205");
|
||||
}
|
||||
}
|
||||
WHEN("the project lists no variants") {
|
||||
Slic3r::DynamicPrintConfig project;
|
||||
project.set_deserialize_strict("nozzle_temperature", "199");
|
||||
project.update_non_diff_values_to_base_config(base, project.keys(), {"nozzle_temperature"}, "", "filament_extruder_variant",
|
||||
Slic3r::filament_options_with_variant, no_keys);
|
||||
THEN("the project's value applies to every variant") {
|
||||
REQUIRE(project.opt_serialize("nozzle_temperature") == "199,199,199");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("A two-extruder printer base with two variants per extruder") {
|
||||
Slic3r::DynamicPrintConfig base;
|
||||
base.set_key_value("printer_extruder_variant",
|
||||
variants({"Direct Drive Standard", "Direct Drive High Flow", "Direct Drive Standard", "Direct Drive High Flow"}));
|
||||
base.set_key_value("printer_extruder_id", new Slic3r::ConfigOptionInts({1, 1, 2, 2}));
|
||||
base.set_deserialize_strict("retraction_length", "0.8,0.8,0.8,0.8");
|
||||
|
||||
WHEN("the project lists only the Standard variant of each extruder") {
|
||||
Slic3r::DynamicPrintConfig project;
|
||||
project.set_key_value("printer_extruder_variant", variants({"Direct Drive Standard", "Direct Drive Standard"}));
|
||||
project.set_key_value("printer_extruder_id", new Slic3r::ConfigOptionInts({1, 2}));
|
||||
project.set_deserialize_strict("retraction_length", "1.1,2.2");
|
||||
project.update_non_diff_values_to_base_config(base, project.keys(), {"retraction_length"}, "printer_extruder_id", "printer_extruder_variant",
|
||||
Slic3r::printer_options_with_variant_1,
|
||||
Slic3r::printer_options_with_variant_2);
|
||||
THEN("each extruder's High Flow variant takes that extruder's value") {
|
||||
REQUIRE(project.opt_serialize("retraction_length") == "1.1,1.1,2.2,2.2");
|
||||
}
|
||||
}
|
||||
WHEN("the project lists only the Standard variant of each extruder, and the variant lists as changed") {
|
||||
Slic3r::DynamicPrintConfig project;
|
||||
project.set_key_value("printer_extruder_variant", variants({"Direct Drive Standard", "Direct Drive Standard"}));
|
||||
project.set_key_value("printer_extruder_id", new Slic3r::ConfigOptionInts({1, 2}));
|
||||
project.set_deserialize_strict("machine_max_speed_x", "300,100,400,150");
|
||||
base.set_deserialize_strict("machine_max_speed_x", "500,200,500,200,500,200,500,200");
|
||||
project.update_non_diff_values_to_base_config(base, project.keys(),
|
||||
{"machine_max_speed_x", "printer_extruder_id", "printer_extruder_variant"},
|
||||
"printer_extruder_id", "printer_extruder_variant",
|
||||
Slic3r::printer_options_with_variant_1,
|
||||
Slic3r::printer_options_with_variant_2);
|
||||
THEN("the variant lists are the base's") {
|
||||
REQUIRE(project.opt_serialize("printer_extruder_variant") == base.opt_serialize("printer_extruder_variant"));
|
||||
REQUIRE(project.opt_serialize("printer_extruder_id") == "1,1,2,2");
|
||||
}
|
||||
THEN("each extruder's High Flow variant takes that extruder's pair of limits") {
|
||||
REQUIRE(project.opt_serialize("machine_max_speed_x") == "300,100,300,100,400,150,400,150");
|
||||
}
|
||||
}
|
||||
WHEN("the project lists no variants") {
|
||||
Slic3r::DynamicPrintConfig project;
|
||||
project.set_deserialize_strict("retraction_length", "1.1");
|
||||
project.update_non_diff_values_to_base_config(base, project.keys(), {"retraction_length"}, "printer_extruder_id", "printer_extruder_variant",
|
||||
Slic3r::printer_options_with_variant_1,
|
||||
Slic3r::printer_options_with_variant_2);
|
||||
THEN("only the first extruder's variants take the project's value") {
|
||||
REQUIRE(project.opt_serialize("retraction_length") == "1.1,1.1,0.8,0.8");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SCENARIO("DynamicPrintConfig JSON serialization", "[Config]") {
|
||||
// WHEN("DynamicPrintConfig is serialized and deserialized") {
|
||||
// auto now = std::chrono::high_resolution_clock::now();
|
||||
|
||||
@@ -5816,6 +5816,131 @@ 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)
|
||||
{
|
||||
@@ -5956,8 +6081,9 @@ TEST_CASE("A per-variant project value maps onto its base preset's variant layou
|
||||
base_finder(&base, calls));
|
||||
CHECK(config.option<ConfigOptionStrings>("print_extruder_variant")->values ==
|
||||
std::vector<std::string>{"Direct Drive Standard", "Direct Drive High Flow"});
|
||||
// The listed key keeps the project's Standard value and takes High Flow from the base.
|
||||
check_double_vector(config.option<ConfigOptionFloats>("outer_wall_speed")->values, {100., 300.});
|
||||
// The listed key keeps the project's Standard value, and High Flow, which the project does not
|
||||
// list, takes it too, as a user preset's value does.
|
||||
check_double_vector(config.option<ConfigOptionFloats>("outer_wall_speed")->values, {100., 100.});
|
||||
check_double_vector(config.option<ConfigOptionFloats>("inner_wall_speed")->values, {250., 350.});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user