mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-30 20:31:09 +00:00
Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4709ba241c | ||
|
|
97700c5ab5 | ||
|
|
ff2f42016a | ||
|
|
94266c2819 | ||
|
|
dc0e269186 | ||
|
|
771ed20d97 | ||
|
|
616f1e4336 | ||
|
|
da0611a301 |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "OrcaFilamentLibrary",
|
||||
"version": "02.04.00.16",
|
||||
"version": "02.04.00.17",
|
||||
"force_update": "0",
|
||||
"description": "Orca Filament Library",
|
||||
"filament_list": [
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"filament_id": "OFvgE0Zh",
|
||||
"instantiation": "false",
|
||||
"fan_cooling_layer_time": [
|
||||
"81"
|
||||
"85"
|
||||
],
|
||||
"fan_min_speed": [
|
||||
"50"
|
||||
|
||||
+55
-9
@@ -1913,7 +1913,10 @@ int CLI::run(int argc, char **argv)
|
||||
old_printable_width = static_cast<int>(old_printable_bbox.size().x());
|
||||
old_printable_depth = static_cast<int>(old_printable_bbox.size().y());
|
||||
}
|
||||
old_printable_height = (int)(config.opt_float("printable_height"));
|
||||
// A 3mf can carry an empty project_settings.config - the models in
|
||||
// resources/handy_models do - and opt_float() dereferences without checking.
|
||||
if (config.option<ConfigOptionFloat>("printable_height"))
|
||||
old_printable_height = (int)(config.opt_float("printable_height"));
|
||||
|
||||
if (config.option<ConfigOptionFloat>("extruder_clearance_height_to_rod"))
|
||||
old_height_to_rod = config.opt_float("extruder_clearance_height_to_rod");
|
||||
@@ -2116,9 +2119,14 @@ int CLI::run(int argc, char **argv)
|
||||
|
||||
// One resolver for the whole run, so presets from the same vendor tree share its load.
|
||||
std::unique_ptr<PresetBundle> system_preset_resolver;
|
||||
auto resolve_preset = [&ensure_cli_preset_bundle, &system_preset_resolver](const std::string &file, DynamicPrintConfig &config,
|
||||
std::string &config_type, const std::string &config_from,
|
||||
bool probe_type, std::string &error) {
|
||||
auto ensure_system_preset_resolver = [&system_preset_resolver]() -> PresetBundle & {
|
||||
if (!system_preset_resolver)
|
||||
system_preset_resolver = std::make_unique<PresetBundle>();
|
||||
return *system_preset_resolver;
|
||||
};
|
||||
auto resolve_preset = [&ensure_cli_preset_bundle, &ensure_system_preset_resolver](const std::string &file, DynamicPrintConfig &config,
|
||||
std::string &config_type, const std::string &config_from,
|
||||
bool probe_type, std::string &error) {
|
||||
const auto *inherits = config.option<ConfigOptionString>(BBL_JSON_KEY_INHERITS);
|
||||
if (!probe_type && (inherits == nullptr || inherits->value.empty()))
|
||||
return true;
|
||||
@@ -2126,9 +2134,7 @@ int CLI::run(int argc, char **argv)
|
||||
PresetBundle *bundle = nullptr;
|
||||
bool allow_source_manifest = false;
|
||||
if (config_from == "system") {
|
||||
if (!system_preset_resolver)
|
||||
system_preset_resolver = std::make_unique<PresetBundle>();
|
||||
bundle = system_preset_resolver.get();
|
||||
bundle = &ensure_system_preset_resolver();
|
||||
allow_source_manifest = true;
|
||||
} else {
|
||||
bundle = ensure_cli_preset_bundle(error);
|
||||
@@ -2589,7 +2595,8 @@ int CLI::run(int argc, char **argv)
|
||||
orig_printable_width = static_cast<int>(orig_printable_bbox.size().x());
|
||||
orig_printable_depth = static_cast<int>(orig_printable_bbox.size().y());
|
||||
}
|
||||
orig_printable_height = (int)(config.opt_float("printable_height"));
|
||||
if (config.option<ConfigOptionFloat>("printable_height"))
|
||||
orig_printable_height = (int)(config.opt_float("printable_height"));
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(":%1%, check printable size: old_printable_width=%2%, orig_printable_width=%3%, old_printable_depth=%4%, orig_printable_depth=%5%, old_printable_height=%6%, orig_printable_height=%7%")
|
||||
%__LINE__ %old_printable_width %orig_printable_width %old_printable_depth %orig_printable_depth %old_printable_height %orig_printable_height;
|
||||
if ((orig_printable_width > 0) && (orig_printable_depth > 0) && (orig_printable_height > 0))
|
||||
@@ -3120,6 +3127,44 @@ int CLI::run(int argc, char **argv)
|
||||
return 0;
|
||||
};
|
||||
|
||||
// Load the project's printer and process settings as the GUI loads its presets: over the default preset,
|
||||
// with every key the project does not list as changed, including keys saved before an option existed,
|
||||
// taken from its current system preset.
|
||||
auto load_project_preset = [this, &ensure_system_preset_resolver, ¤t_different_settings, filament_count](const std::string &system_name, Preset::Type type) {
|
||||
if (system_name.empty())
|
||||
return;
|
||||
// Preset bookkeeping the CLI keeps in its own groups, e.g. inherits_group and print_compatible_printers.
|
||||
static const std::set<std::string> bookkeeping_keys = {"inherits", "compatible_printers", "compatible_prints", "compatible_printers_condition",
|
||||
"compatible_prints_condition", "print_settings_id", "printer_settings_id"};
|
||||
const size_t index = type == Preset::TYPE_PRINTER ? filament_count + 1 : 0;
|
||||
PresetBundle &resolver = ensure_system_preset_resolver();
|
||||
DynamicPrintConfig system_config;
|
||||
t_config_option_keys keys;
|
||||
const DynamicPrintConfig config = Preset::load_external_config(type,
|
||||
type == Preset::TYPE_PRINTER ? resolver.printers.default_preset_for(m_print_config).config : resolver.prints.default_preset().config,
|
||||
m_print_config, PresetBundle::project_different_keys(index < current_different_settings.size() ? current_different_settings[index] : std::string()),
|
||||
[&](const std::string &) -> DynamicPrintConfig * {
|
||||
std::string error;
|
||||
if (resolver.resolve_system_preset(system_config, type, system_name, config_substitution_rule, error))
|
||||
return &system_config;
|
||||
BOOST_LOG_TRIVIAL(warning) << boost::format("CLI: system preset '%1%' not resolved (%2%); the project keeps its values") % system_name % error;
|
||||
return nullptr;
|
||||
}, &keys);
|
||||
for (const std::string &key : keys) {
|
||||
const ConfigOption *opt = config.option(key);
|
||||
const ConfigOption *old = m_print_config.option(key);
|
||||
if (bookkeeping_keys.count(key) != 0 || opt == nullptr || (old != nullptr && *old == *opt))
|
||||
continue;
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("CLI: %1% from '%2%': %3% -> %4%") % key % system_name % (old ? old->serialize() : std::string("(missing)")) % opt->serialize();
|
||||
m_print_config.set_key_value(key, opt->clone());
|
||||
}
|
||||
};
|
||||
// The --uptodate path refreshes the project from its own system configs.
|
||||
if (new_printer_name.empty() && load_machine_config.empty())
|
||||
load_project_preset(current_printer_system_name, Preset::TYPE_PRINTER);
|
||||
if (new_process_name.empty() && load_process_config.empty())
|
||||
load_project_preset(current_process_system_name, Preset::TYPE_PRINT);
|
||||
|
||||
std::vector<std::string>& different_settings = m_print_config.option<ConfigOptionStrings>("different_settings_to_system", true)->values;
|
||||
std::vector<std::string>& inherits_group = m_print_config.option<ConfigOptionStrings>("inherits_group", true)->values;
|
||||
inherits_group.resize(filament_count + 2, std::string());
|
||||
@@ -4617,7 +4662,8 @@ int CLI::run(int argc, char **argv)
|
||||
BoundingBoxf temp_printable_bbox(temp_printable_area);
|
||||
printer_plate.printable_width = static_cast<int>(temp_printable_bbox.size().x());
|
||||
printer_plate.printable_depth = static_cast<int>(temp_printable_bbox.size().y());
|
||||
printer_plate.printable_height = (int)(config.opt_float("printable_height"));
|
||||
if (config.option<ConfigOptionFloat>("printable_height"))
|
||||
printer_plate.printable_height = (int)(config.opt_float("printable_height"));
|
||||
}
|
||||
if (temp_exclude_area.size() >= 4) {
|
||||
printer_plate.exclude_width = (int)(temp_exclude_area[2].x() - temp_exclude_area[0].x());
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include "FillBase.hpp"
|
||||
#include "FillGyroid.hpp"
|
||||
|
||||
@@ -123,44 +124,41 @@ static inline double f(double x, double z_sin, double z_cos, bool vertical, bool
|
||||
}
|
||||
}
|
||||
|
||||
// Repeats one period of a wave from the last sample at or before x_min to the first one at or after x_max.
|
||||
static inline Polyline make_wave(
|
||||
const std::vector<Vec2d>& one_period, double width, double height, double offset, double scaleFactor,
|
||||
double z_cos, double z_sin, bool vertical, bool flip)
|
||||
const std::vector<Vec2d>& one_period, double x_min, double x_max, double offset, double scaleFactor, bool vertical)
|
||||
{
|
||||
std::vector<Vec2d> points = one_period;
|
||||
double period = points.back()(0);
|
||||
if (width != period) // do not extend if already truncated
|
||||
{
|
||||
points.reserve(one_period.size() * size_t(floor(width / period)));
|
||||
points.pop_back();
|
||||
const double period = one_period.back().x();
|
||||
// The last sample of a period is the first one of the next.
|
||||
const size_t n = one_period.size() - 1;
|
||||
double x0 = std::floor(x_min / period) * period;
|
||||
size_t i = 0;
|
||||
while (i + 1 < n && x0 + one_period[i + 1].x() <= x_min)
|
||||
++i;
|
||||
|
||||
size_t n = points.size();
|
||||
do {
|
||||
points.emplace_back(points[points.size()-n].x() + period, points[points.size()-n].y());
|
||||
} while (points.back()(0) < width - EPSILON);
|
||||
|
||||
points.emplace_back(Vec2d(width, f(width, z_sin, z_cos, vertical, flip)));
|
||||
}
|
||||
|
||||
// and construct the final polyline to return:
|
||||
Polyline polyline;
|
||||
polyline.points.reserve(points.size());
|
||||
for (auto& point : points) {
|
||||
point(1) += offset;
|
||||
point(1) = std::clamp(double(point.y()), 0., height);
|
||||
polyline.points.reserve(size_t((x_max - x0) / period + 1.) * n + 1);
|
||||
for (;;) {
|
||||
Vec2d point(x0 + one_period[i].x(), one_period[i].y() + offset);
|
||||
const bool last = point.x() >= x_max;
|
||||
if (vertical)
|
||||
std::swap(point(0), point(1));
|
||||
polyline.points.emplace_back((point * scaleFactor).cast<coord_t>());
|
||||
if (last)
|
||||
break;
|
||||
if (++i == n) {
|
||||
i = 0;
|
||||
x0 += period;
|
||||
}
|
||||
}
|
||||
|
||||
return polyline;
|
||||
}
|
||||
|
||||
static std::vector<Vec2d> make_one_period(double width, double scaleFactor, double z_cos, double z_sin, bool vertical, bool flip, double tolerance)
|
||||
static std::vector<Vec2d> make_one_period(double scaleFactor, double z_cos, double z_sin, bool vertical, bool flip, double tolerance)
|
||||
{
|
||||
std::vector<Vec2d> points;
|
||||
double dx = M_PI_2; // exact coordinates on main inflexion lobes
|
||||
double limit = std::min(2*M_PI, width);
|
||||
double limit = 2*M_PI;
|
||||
points.reserve(coord_t(ceil(limit / tolerance / 3)));
|
||||
|
||||
for (double x = 0.; x < limit - EPSILON; x += dx) {
|
||||
@@ -239,7 +237,8 @@ static inline double compute_omega_factor(double density_adjusted, double line_s
|
||||
return std::clamp(raw, 1.0, 2.0);
|
||||
}
|
||||
|
||||
static Polylines make_gyroid_waves(double gridZ, double density_adjusted, double line_spacing, double width, double height)
|
||||
// Waves covering bbox, with the pattern anchored at origin.
|
||||
static Polylines make_gyroid_waves(double gridZ, double density_adjusted, double line_spacing, const BoundingBox &bbox, const Point &origin)
|
||||
{
|
||||
const double scaleFactor = scale_(line_spacing) / density_adjusted;
|
||||
|
||||
@@ -254,29 +253,36 @@ static Polylines make_gyroid_waves(double gridZ, double density_adjusted, double
|
||||
const double z_cos = cos(z);
|
||||
|
||||
bool vertical = (std::abs(z_sin) <= std::abs(z_cos));
|
||||
// Range to cover in pattern units, with the waves running along x.
|
||||
Vec2d lo = (bbox.min - origin).cast<double>() / scaleFactor;
|
||||
Vec2d hi = (bbox.max - origin).cast<double>() / scaleFactor;
|
||||
double lower_bound = 0.;
|
||||
double upper_bound = height;
|
||||
bool flip = true;
|
||||
if (vertical) {
|
||||
flip = false;
|
||||
lower_bound = -M_PI;
|
||||
upper_bound = width - M_PI_2;
|
||||
std::swap(width,height);
|
||||
std::swap(lo(0), lo(1));
|
||||
std::swap(hi(0), hi(1));
|
||||
}
|
||||
|
||||
std::vector<Vec2d> one_period_odd = make_one_period(width, scaleFactor, z_cos, z_sin, vertical, flip, tolerance); // creates one period of the waves, so it doesn't have to be recalculated all the time
|
||||
std::vector<Vec2d> one_period_odd = make_one_period(scaleFactor, z_cos, z_sin, vertical, flip, tolerance); // creates one period of the waves, so it doesn't have to be recalculated all the time
|
||||
flip = !flip; // even polylines are a bit shifted
|
||||
std::vector<Vec2d> one_period_even = make_one_period(width, scaleFactor, z_cos, z_sin, vertical, flip, tolerance);
|
||||
Polylines result;
|
||||
std::vector<Vec2d> one_period_even = make_one_period(scaleFactor, z_cos, z_sin, vertical, flip, tolerance);
|
||||
|
||||
for (double y0 = lower_bound; y0 < upper_bound + EPSILON; y0 += M_PI) {
|
||||
// creates odd polylines
|
||||
result.emplace_back(make_wave(one_period_odd, width, height, y0, scaleFactor, z_cos, z_sin, vertical, flip));
|
||||
// creates even polylines
|
||||
y0 += M_PI;
|
||||
if (y0 < upper_bound + EPSILON) {
|
||||
result.emplace_back(make_wave(one_period_even, width, height, y0, scaleFactor, z_cos, z_sin, vertical, flip));
|
||||
// Every wave spans [offset + f_min, offset + f_max] across.
|
||||
double f_min = std::numeric_limits<double>::max();
|
||||
double f_max = std::numeric_limits<double>::lowest();
|
||||
for (const std::vector<Vec2d> *one_period : { &one_period_odd, &one_period_even })
|
||||
for (const Vec2d &point : *one_period) {
|
||||
f_min = std::min(f_min, point.y());
|
||||
f_max = std::max(f_max, point.y());
|
||||
}
|
||||
|
||||
Polylines result;
|
||||
for (int i = int(std::ceil((lo.y() - f_max - lower_bound) / M_PI)); lower_bound + i * M_PI + f_min <= hi.y(); ++i) {
|
||||
Polyline &wave = result.emplace_back(make_wave(i % 2 == 0 ? one_period_odd : one_period_even, lo.x(), hi.x(),
|
||||
lower_bound + i * M_PI, scaleFactor, vertical));
|
||||
wave.translate(origin);
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -302,12 +308,12 @@ void FillGyroid::_fill_surface_single(
|
||||
// Distance between the gyroid waves in scaled coordinates.
|
||||
coord_t distance = coord_t(scale_(this->spacing) / density_adjusted);
|
||||
|
||||
// align bounding box to a multiple of our grid module
|
||||
bb.merge(align_to_grid(bb.min, Point(2*M_PI*distance, 2*M_PI*distance)));
|
||||
// Anchor the pattern to our grid module; the 10-line shift keeps its established phase.
|
||||
const coord_t shift = coord_t(10 * scale_(this->spacing));
|
||||
const Point origin = align_to_grid(bb.min, Point(2*M_PI*distance, 2*M_PI*distance)) - Point(shift, shift);
|
||||
|
||||
// Expand the bounding box to avoid artifacts at the edges
|
||||
coord_t expand = 10 * (scale_(this->spacing));
|
||||
bb.offset(expand);
|
||||
// Keep the pattern ends and the multiline copies outside the contour.
|
||||
bb.offset(scale_(this->spacing * params.multiline));
|
||||
|
||||
// generate pattern
|
||||
Polylines polylines;
|
||||
@@ -327,23 +333,14 @@ void FillGyroid::_fill_surface_single(
|
||||
const float density_factor = std::max(0.001f, float(params.density * DensityAdjust / params.multiline));
|
||||
const float period = float(2.0 * M_PI) * float(this->spacing) / density_factor;
|
||||
|
||||
// bb is already expanded above by 10 * scale_(spacing) for edge artifacts;
|
||||
// skip a second offset here to avoid raster-area bloat in the marching squares pass.
|
||||
// A cell of margin for the rings closed along the raster border, and a fixed sampling grid for every region.
|
||||
const coord_t cell = scaled(marchsq::GyroidField::gsizef);
|
||||
bb.offset(cell);
|
||||
bb.merge(align_to_grid(bb.min, Point(cell, cell)));
|
||||
marchsq::GyroidField sf(bb, this->z, period, float(omega));
|
||||
polylines = marchsq::get_gyroid_polylines(sf, SCALED_SPARSE_INFILL_RESOLUTION);
|
||||
} else {
|
||||
polylines = make_gyroid_waves(
|
||||
scale_(this->z),
|
||||
density_adjusted,
|
||||
this->spacing,
|
||||
ceil(bb.size()(0) / distance) + 1.,
|
||||
ceil(bb.size()(1) / distance) + 1.);
|
||||
|
||||
// The parametric generator produces wave coords relative to the grid origin;
|
||||
// shift them into absolute layer coords. The marching-squares branch above
|
||||
// already emits absolute coords via GyroidField::to_Point, so it skips this.
|
||||
for (Polyline &pl : polylines)
|
||||
pl.translate(bb.min);
|
||||
polylines = make_gyroid_waves(scale_(this->z), density_adjusted, this->spacing, bb, origin);
|
||||
}
|
||||
|
||||
// Apply multiline offset if needed
|
||||
|
||||
@@ -54,43 +54,9 @@ static void png_read_callback(png_struct *png_ptr,
|
||||
// Retrieve our input buffer through the png_ptr
|
||||
auto reader = static_cast<IStream *>(png_get_io_ptr(png_ptr));
|
||||
|
||||
// libpng expects a short read to be reported through png_error(); returning quietly would leave
|
||||
// it decoding whatever happened to be in outBytes.
|
||||
if (!reader || !reader->is_ok() ||
|
||||
reader->read(static_cast<std::uint8_t *>(outBytes), byteCountToRead) != byteCountToRead)
|
||||
png_error(png_ptr, "PNG data is truncated");
|
||||
}
|
||||
if (!reader || !reader->is_ok()) return;
|
||||
|
||||
// libpng reports a corrupt or truncated image by longjmp()ing back to the jump buffer set with
|
||||
// setjmp(). The frame it lands in must own nothing that needs destroying: with exceptions enabled
|
||||
// MSVC unwinds the stack as part of longjmp, and returning from a frame unwound that way crashes -
|
||||
// which is what a truncated texture did on Windows while working everywhere else. So the calls that
|
||||
// can fail live in these two helpers, which hold nothing but pointers, and every C++ object the
|
||||
// decoders need stays in their own frames.
|
||||
static bool png_read_header_guarded(png_struct *png, png_info *info, IStream *in_buf, int sig_bytes)
|
||||
{
|
||||
if (setjmp(png_jmpbuf(png)))
|
||||
return false;
|
||||
|
||||
png_set_read_fn(png, static_cast<void *>(in_buf), png_read_callback);
|
||||
// Tell that we have already read the first bytes to check the signature
|
||||
png_set_sig_bytes(png, sig_bytes);
|
||||
png_read_info(png, info);
|
||||
return true;
|
||||
}
|
||||
|
||||
// `bottom_up` fills the buffer last row first, which is the order the colour decoder hands back.
|
||||
static bool png_read_rows_guarded(png_struct *png, png_info *info, png_bytep dst, size_t rows, size_t rowbytes,
|
||||
bool bottom_up, bool read_end)
|
||||
{
|
||||
if (setjmp(png_jmpbuf(png)))
|
||||
return false;
|
||||
|
||||
for (size_t i = 0; i < rows; ++i)
|
||||
png_read_row(png, dst + (bottom_up ? rows - 1 - i : i) * rowbytes, nullptr);
|
||||
if (read_end)
|
||||
png_read_end(png, info);
|
||||
return true;
|
||||
reader->read(static_cast<std::uint8_t *>(outBytes), byteCountToRead);
|
||||
}
|
||||
|
||||
bool decode_png(IStream &in_buf, ImageGreyscale &out_img)
|
||||
@@ -111,8 +77,12 @@ bool decode_png(IStream &in_buf, ImageGreyscale &out_img)
|
||||
dsc.info = png_create_info_struct(dsc.png);
|
||||
if(!dsc.info) return false;
|
||||
|
||||
if (!png_read_header_guarded(dsc.png, dsc.info, &in_buf, PNG_SIG_BYTES))
|
||||
return false;
|
||||
png_set_read_fn(dsc.png, static_cast<void *>(&in_buf), png_read_callback);
|
||||
|
||||
// Tell that we have already read the first bytes to check the signature
|
||||
png_set_sig_bytes(dsc.png, PNG_SIG_BYTES);
|
||||
|
||||
png_read_info(dsc.png, dsc.info);
|
||||
|
||||
out_img.cols = png_get_image_width(dsc.png, dsc.info);
|
||||
out_img.rows = png_get_image_height(dsc.png, dsc.info);
|
||||
@@ -124,8 +94,11 @@ bool decode_png(IStream &in_buf, ImageGreyscale &out_img)
|
||||
|
||||
out_img.buf.resize(out_img.rows * out_img.cols);
|
||||
|
||||
return png_read_rows_guarded(dsc.png, dsc.info, static_cast<png_bytep>(out_img.buf.data()), out_img.rows,
|
||||
out_img.cols, /* bottom_up */ false, /* read_end */ false);
|
||||
auto readbuf = static_cast<png_bytep>(out_img.buf.data());
|
||||
for (size_t r = 0; r < out_img.rows; ++r)
|
||||
png_read_row(dsc.png, readbuf + r * out_img.cols, nullptr);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool decode_colored_png(IStream &in_buf, ImageColorscale &out_img)
|
||||
@@ -155,10 +128,12 @@ bool decode_colored_png(IStream &in_buf, ImageColorscale &out_img)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!png_read_header_guarded(dsc.png, dsc.info, &in_buf, PNG_SIG_BYTES)) {
|
||||
BOOST_LOG_TRIVIAL(error) << "decode_colored_png: corrupt or truncated PNG data";
|
||||
return false;
|
||||
}
|
||||
png_set_read_fn(dsc.png, static_cast<void *>(&in_buf), png_read_callback);
|
||||
|
||||
// Tell that we have already read the first bytes to check the signature
|
||||
png_set_sig_bytes(dsc.png, PNG_SIG_BYTES);
|
||||
|
||||
png_read_info(dsc.png, dsc.info);
|
||||
|
||||
out_img.cols = png_get_image_width(dsc.png, dsc.info);
|
||||
out_img.rows = png_get_image_height(dsc.png, dsc.info);
|
||||
@@ -187,12 +162,13 @@ bool decode_colored_png(IStream &in_buf, ImageColorscale &out_img)
|
||||
int interlace_type = png_get_interlace_type(dsc.png, dsc.info);
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("filter_type %1%, compression_type %2%, interlace_type %3%, rowbytes %4%")%filter_type %compression_type %interlace_type %rowbytes;
|
||||
|
||||
if (!png_read_rows_guarded(dsc.png, dsc.info, static_cast<png_bytep>(out_img.buf.data()), out_img.rows, rowbytes,
|
||||
/* bottom_up */ true, /* read_end */ true)) {
|
||||
BOOST_LOG_TRIVIAL(error) << "decode_colored_png: corrupt or truncated PNG data";
|
||||
return false;
|
||||
auto readbuf = static_cast<png_bytep>(out_img.buf.data());
|
||||
for (size_t r = out_img.rows; r > 0; r--)
|
||||
{
|
||||
png_read_row(dsc.png, readbuf + (r - 1) * rowbytes, nullptr);
|
||||
}
|
||||
|
||||
png_read_end(dsc.png, dsc.info);
|
||||
png_destroy_read_struct(&dsc.png, &dsc.info, NULL);
|
||||
|
||||
return true;
|
||||
|
||||
+50
-32
@@ -976,6 +976,37 @@ void Preset::get_extruder_names_and_keysets(Type type, std::string& extruder_id_
|
||||
}
|
||||
}
|
||||
|
||||
DynamicPrintConfig Preset::load_external_config(Type type, const DynamicPrintConfig &default_config, const DynamicPrintConfig &project_config,
|
||||
const std::set<std::string> &different_settings_list,
|
||||
const std::function<DynamicPrintConfig *(const std::string &inherits)> &find_base,
|
||||
t_config_option_keys *keys)
|
||||
{
|
||||
// Load the preset over a default preset, so that the missing fields are filled in from the default preset.
|
||||
DynamicPrintConfig cfg(default_config);
|
||||
// SoftFever: ignore print connection info from project
|
||||
auto cfg_keys = cfg.keys();
|
||||
cfg_keys.erase(std::remove_if(cfg_keys.begin(), cfg_keys.end(),
|
||||
[](std::string &val) {
|
||||
return val == "print_host" || val == "print_host_webui" || val == "printhost_apikey" ||
|
||||
val == "printhost_cafile" || val == "printhost_user" || val == "printhost_password" || val == "printhost_port";
|
||||
}),
|
||||
cfg_keys.end());
|
||||
cfg.apply_only(project_config, cfg_keys, true);
|
||||
|
||||
//add different settings check logic, replace the old system preset's default value with new system preset's default values
|
||||
if (!different_settings_list.empty()) {
|
||||
if (DynamicPrintConfig *base_config = find_base(Preset::inherits(cfg))) {
|
||||
std::string extruder_id_name, extruder_variant_name;
|
||||
std::set<std::string> *key_set1 = nullptr, *key_set2 = nullptr;
|
||||
Preset::get_extruder_names_and_keysets(type, extruder_id_name, extruder_variant_name, &key_set1, &key_set2);
|
||||
cfg.update_non_diff_values_to_base_config(*base_config, cfg_keys, different_settings_list, extruder_id_name, extruder_variant_name, *key_set1, *key_set2);
|
||||
}
|
||||
}
|
||||
if (keys)
|
||||
*keys = std::move(cfg_keys);
|
||||
return cfg;
|
||||
}
|
||||
|
||||
bool Preset::has_lidar(PresetBundle *preset_bundle)
|
||||
{
|
||||
bool has_lidar = false;
|
||||
@@ -2640,20 +2671,6 @@ std::pair<Preset*, bool> PresetCollection::load_external_preset(
|
||||
const Semver file_version,
|
||||
const std::string filament_id)
|
||||
{
|
||||
// Load the preset over a default preset, so that the missing fields are filled in from the default preset.
|
||||
DynamicPrintConfig cfg(this->default_preset_for(combined_config).config);
|
||||
// SoftFever: ignore print connection info from project
|
||||
auto keys = cfg.keys();
|
||||
keys.erase(std::remove_if(keys.begin(), keys.end(),
|
||||
[](std::string &val) {
|
||||
return val == "print_host" || val == "print_host_webui" || val == "printhost_apikey" ||
|
||||
val == "printhost_cafile" || val == "printhost_user" || val == "printhost_password" || val == "printhost_port";
|
||||
}),
|
||||
keys.end());
|
||||
cfg.apply_only(combined_config, keys, true);
|
||||
std::string &inherits = Preset::inherits(cfg);
|
||||
|
||||
//add different settings check logic, replace the old system preset's default value with new system preset's default values
|
||||
std::deque<Preset>::iterator it = this->find_preset_internal(original_name);
|
||||
bool found = it != m_presets.end() && it->name == original_name;
|
||||
if (! found) {
|
||||
@@ -2662,24 +2679,25 @@ std::pair<Preset*, bool> PresetCollection::load_external_preset(
|
||||
found = it != m_presets.end();
|
||||
}
|
||||
|
||||
std::string extruder_id_name, extruder_variant_name;
|
||||
std::set<std::string> *key_set1 = nullptr, *key_set2 = nullptr;
|
||||
Preset::get_extruder_names_and_keysets(m_type, extruder_id_name, extruder_variant_name, &key_set1, &key_set2);
|
||||
|
||||
if (!inherits.empty() && (different_settings_list.size() > 0)) {
|
||||
auto iter = this->find_preset_internal(inherits);
|
||||
if (iter == m_presets.end() || iter->name != inherits)
|
||||
iter = this->find_preset_renamed(inherits);
|
||||
if (iter != m_presets.end()) {
|
||||
//std::vector<std::string> dirty_options = cfg.diff(iter->config);
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": change preset %1% inherit %2% 's value to %3% 's values")%original_name %inherits %path;
|
||||
cfg.update_non_diff_values_to_base_config(iter->config, keys, different_settings_list, extruder_id_name, extruder_variant_name, *key_set1, *key_set2);
|
||||
}
|
||||
}
|
||||
else if (found && it->is_system && (different_settings_list.size() > 0)) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": change preset %1% 's value to %2% 's values")%original_name %path;
|
||||
cfg.update_non_diff_values_to_base_config(it->config, keys, different_settings_list, extruder_id_name, extruder_variant_name, *key_set1, *key_set2);
|
||||
}
|
||||
t_config_option_keys keys;
|
||||
DynamicPrintConfig cfg = Preset::load_external_config(m_type, this->default_preset_for(combined_config).config, combined_config, different_settings_list,
|
||||
[this, &original_name, &path, found, it](const std::string &inherits) -> DynamicPrintConfig * {
|
||||
if (!inherits.empty()) {
|
||||
auto iter = this->find_preset_internal(inherits);
|
||||
if (iter == m_presets.end() || iter->name != inherits)
|
||||
iter = this->find_preset_renamed(inherits);
|
||||
if (iter == m_presets.end())
|
||||
return nullptr;
|
||||
BOOST_LOG_TRIVIAL(info) << "load_external_preset" << boost::format(": change preset %1% inherit %2% 's value to %3% 's values")%original_name %inherits %path;
|
||||
return &iter->config;
|
||||
}
|
||||
if (found && it->is_system) {
|
||||
BOOST_LOG_TRIVIAL(info) << "load_external_preset" << boost::format(": change preset %1% 's value to %2% 's values")%original_name %path;
|
||||
return &it->config;
|
||||
}
|
||||
return nullptr;
|
||||
}, &keys);
|
||||
std::string &inherits = Preset::inherits(cfg);
|
||||
|
||||
//BBS: add config related logs
|
||||
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" enter, type %1% , path %2%, name %3%, original_name %4%, inherits %5%")%Preset::get_type_string(m_type) %path %name %original_name %inherits;
|
||||
|
||||
@@ -403,6 +403,15 @@ public:
|
||||
std::string get_current_printer_type(PresetBundle *preset_bundle); // get current preset type
|
||||
|
||||
static void get_extruder_names_and_keysets(Type type, std::string& extruder_id_name, std::string& extruder_variant_name, std::set<std::string>** p_key_set1, std::set<std::string>** p_key_set2);
|
||||
// Config of a preset loaded from a project or config file: the project's values over the type's
|
||||
// default preset config, without the print-host keys. When different_settings_list is not empty,
|
||||
// every key not listed in it is then refreshed from the base system preset, which find_base returns
|
||||
// for the project's "inherits" (nullptr when there is none), with the listed per-variant values
|
||||
// mapped onto the base's extruder variants. keys, if given, receives the keys taken from the project.
|
||||
static DynamicPrintConfig load_external_config(Type type, const DynamicPrintConfig &default_config, const DynamicPrintConfig &project_config,
|
||||
const std::set<std::string> &different_settings_list,
|
||||
const std::function<DynamicPrintConfig *(const std::string &inherits)> &find_base,
|
||||
t_config_option_keys *keys = nullptr);
|
||||
std::string get_printer_id() const { return vendor ? vendor->id : ""; }
|
||||
|
||||
bool has_lidar(PresetBundle *preset_bundle);
|
||||
|
||||
@@ -581,18 +581,20 @@ bool PresetBundle::resolve_preset_config(DynamicPrintConfig &config, Preset::Typ
|
||||
const PresetBundle *PresetBundle::load_source_vendor(const boost::filesystem::path &root_dir,
|
||||
const std::string &vendor_id,
|
||||
ForwardCompatibilitySubstitutionRule compatibility_rule,
|
||||
std::string &error)
|
||||
std::string &error, bool allow_cache)
|
||||
{
|
||||
auto key = std::make_tuple(root_dir.string(), vendor_id, compatibility_rule);
|
||||
auto key = std::make_tuple(root_dir.string(), vendor_id, compatibility_rule, allow_cache);
|
||||
if (auto it = m_source_vendor_bundles.find(key); it != m_source_vendor_bundles.end())
|
||||
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 PresetBundle *library = nullptr;
|
||||
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 / (std::string(ORCA_FILAMENT_LIBRARY) + ".json"))) {
|
||||
library = load_source_vendor(root_dir, ORCA_FILAMENT_LIBRARY, compatibility_rule, error);
|
||||
(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;
|
||||
@@ -601,7 +603,7 @@ const PresetBundle *PresetBundle::load_source_vendor(const boost::filesystem::pa
|
||||
|
||||
auto bundle = std::make_unique<PresetBundle>();
|
||||
bundle->m_preserve_vendor_source_paths = true;
|
||||
bundle->load_vendor_configs_from_json(root_dir.string(), vendor_id, LoadSystem, compatibility_rule, library, false);
|
||||
bundle->load_vendor_configs_from_json(root_dir.string(), vendor_id, LoadSystem, compatibility_rule, library, allow_cache);
|
||||
if (bundle->error_count() != 0) {
|
||||
error = "Vendor bundle contains invalid presets";
|
||||
return nullptr;
|
||||
@@ -644,6 +646,43 @@ bool PresetBundle::resolve_preset_config_type(DynamicPrintConfig &config, Preset
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PresetBundle::resolve_system_preset(DynamicPrintConfig &config, Preset::Type type, const std::string &name,
|
||||
ForwardCompatibilitySubstitutionRule compatibility_rule, std::string &error)
|
||||
{
|
||||
const std::string vendor_id = find_preset_vendor(name, type);
|
||||
if (vendor_id.empty()) {
|
||||
error = "No vendor lists the preset";
|
||||
return false;
|
||||
}
|
||||
// Release builds ship a vendor as its preset cache alone, without the profile JSONs.
|
||||
auto installed = [&vendor_id](const fs::path &root) {
|
||||
return fs::is_regular_file(root / (vendor_id + ".json")) || fs::is_regular_file(root / (vendor_id + ".opc"));
|
||||
};
|
||||
fs::path root_dir = fs::path(data_dir()) / PRESET_SYSTEM_DIR;
|
||||
if (!installed(root_dir))
|
||||
root_dir = fs::path(resources_dir()) / PRESET_PROFILES_DIR;
|
||||
const bool cache_only = !fs::is_regular_file(root_dir / (vendor_id + ".json"));
|
||||
|
||||
try {
|
||||
const PresetBundle *vendor = load_source_vendor(root_dir, vendor_id, compatibility_rule, error, cache_only);
|
||||
if (vendor == nullptr)
|
||||
return false;
|
||||
const PresetCollection &collection = type == Preset::TYPE_PRINTER ? vendor->printers :
|
||||
type == Preset::TYPE_PRINT ? vendor->prints : vendor->filaments;
|
||||
const Preset *preset = collection.find_preset(name, false);
|
||||
if (preset == nullptr) {
|
||||
error = "Preset was not found in its vendor bundle";
|
||||
return false;
|
||||
}
|
||||
config = preset->config;
|
||||
} catch (const std::exception &ex) {
|
||||
error = ex.what();
|
||||
return false;
|
||||
}
|
||||
error.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
PresetBundle::PresetBundle(const PresetBundle &rhs)
|
||||
{
|
||||
*this = rhs;
|
||||
@@ -4665,6 +4704,15 @@ const std::set<std::string> ignore_settings_list ={
|
||||
"print_settings_id", "filament_settings_id", "printer_settings_id"
|
||||
};
|
||||
|
||||
std::set<std::string> PresetBundle::project_different_keys(const std::string &different_settings)
|
||||
{
|
||||
std::vector<std::string> keys;
|
||||
Slic3r::unescape_strings_cstyle(different_settings, keys);
|
||||
std::set<std::string> keys_set(keys.begin(), keys.end());
|
||||
keys_set.insert(ignore_settings_list.begin(), ignore_settings_list.end());
|
||||
return keys_set;
|
||||
}
|
||||
|
||||
DynamicPrintConfig PresetBundle::full_fff_config(bool apply_extruder, std::optional<std::vector<int>> filament_maps_new, std::optional<std::vector<int>> filament_volume_maps_new) const
|
||||
{
|
||||
DynamicPrintConfig out;
|
||||
@@ -5372,15 +5420,7 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
|
||||
if (!is_published) {
|
||||
//BBS: add different settings logic
|
||||
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": load print preset from print_settings_id");
|
||||
std::vector<std::string> print_different_keys_vector;
|
||||
std::string print_different_settings = different_values[0];
|
||||
Slic3r::unescape_strings_cstyle(print_different_settings, print_different_keys_vector);
|
||||
std::set<std::string> print_different_keys_set(print_different_keys_vector.begin(), print_different_keys_vector.end());
|
||||
//if (!has_different_settings_to_system) {
|
||||
// print_different_keys_set.clear();
|
||||
//}
|
||||
//else
|
||||
print_different_keys_set.insert(ignore_settings_list.begin(), ignore_settings_list.end());
|
||||
std::set<std::string> print_different_keys_set = project_different_keys(different_values[0]);
|
||||
if (!print_compatible_printers.empty()) {
|
||||
ConfigOptionStrings* compatible_printers = config.option<ConfigOptionStrings>("compatible_printers", true);
|
||||
compatible_printers->values = print_compatible_printers;
|
||||
@@ -5391,15 +5431,7 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
|
||||
//clear compatible printers
|
||||
clear_compatible_printers(config);
|
||||
|
||||
std::vector<std::string> printer_different_keys_vector;
|
||||
std::string printer_different_settings = different_values[num_filaments + 1];
|
||||
Slic3r::unescape_strings_cstyle(printer_different_settings, printer_different_keys_vector);
|
||||
std::set<std::string> printer_different_keys_set(printer_different_keys_vector.begin(), printer_different_keys_vector.end());
|
||||
//if (!has_different_settings_to_system) {
|
||||
// printer_different_keys_set.clear();
|
||||
//}
|
||||
//else
|
||||
printer_different_keys_set.insert(ignore_settings_list.begin(), ignore_settings_list.end());
|
||||
std::set<std::string> printer_different_keys_set = project_different_keys(different_values[num_filaments + 1]);
|
||||
//BBS: add config related logs
|
||||
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": load printer preset from printer_settings_id");
|
||||
load_preset(this->printers, num_filaments + 1, "printer_settings_id", printer_different_keys_set, std::string());
|
||||
@@ -5418,15 +5450,7 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
|
||||
Preset *loaded = nullptr;
|
||||
|
||||
//BBS: add different settings logic
|
||||
std::vector<std::string> filament_different_keys_vector;
|
||||
std::string filament_different_settings = different_values[1];
|
||||
Slic3r::unescape_strings_cstyle(filament_different_settings, filament_different_keys_vector);
|
||||
std::set<std::string> filament_different_keys_set(filament_different_keys_vector.begin(), filament_different_keys_vector.end());
|
||||
//if (!has_different_settings_to_system) {
|
||||
// filament_different_keys_set.clear();
|
||||
//}
|
||||
//else
|
||||
filament_different_keys_set.insert(ignore_settings_list.begin(), ignore_settings_list.end());
|
||||
std::set<std::string> filament_different_keys_set = project_different_keys(different_values[1]);
|
||||
|
||||
std::string filament_id = filament_ids[0];
|
||||
//BBS: add config related logs
|
||||
@@ -5489,15 +5513,7 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
|
||||
cfg.opt_string("inherits", true) = inherits_values[i + 1];
|
||||
|
||||
//BBS: add different settings logic
|
||||
std::vector<std::string> filament_different_keys_vector;
|
||||
std::string filament_different_settings = different_values[i+1];
|
||||
Slic3r::unescape_strings_cstyle(filament_different_settings, filament_different_keys_vector);
|
||||
std::set<std::string> filament_different_keys_set(filament_different_keys_vector.begin(), filament_different_keys_vector.end());
|
||||
//if (!has_different_settings_to_system) {
|
||||
// filament_different_keys_set.clear();
|
||||
//}
|
||||
//else
|
||||
filament_different_keys_set.insert(ignore_settings_list.begin(), ignore_settings_list.end());
|
||||
std::set<std::string> filament_different_keys_set = project_different_keys(different_values[i+1]);
|
||||
|
||||
std::string filament_id = filament_ids[i];
|
||||
|
||||
|
||||
@@ -235,6 +235,9 @@ public:
|
||||
|
||||
// ORCA: utility function to find the vendor for a given preset name
|
||||
static std::string find_preset_vendor(const std::string& preset_name, Preset::Type type);
|
||||
// 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);
|
||||
|
||||
PresetBundle();
|
||||
PresetBundle(const PresetBundle &rhs);
|
||||
@@ -274,6 +277,10 @@ public:
|
||||
const std::string &source_file,
|
||||
ForwardCompatibilitySubstitutionRule compatibility_rule,
|
||||
std::string &error, bool allow_source_manifest = true);
|
||||
// Resolve a system preset by name. The vendor tree is read from data_dir()/system when installed
|
||||
// there, as the GUI reads it, and from the bundled profiles otherwise.
|
||||
bool resolve_system_preset(DynamicPrintConfig &config, Preset::Type type, const std::string &name,
|
||||
ForwardCompatibilitySubstitutionRule compatibility_rule, std::string &error);
|
||||
|
||||
// Load selections (current print, current filaments, current printer) from config.ini
|
||||
// This is done just once on application start up.
|
||||
@@ -808,13 +815,14 @@ private:
|
||||
// Vendor trees loaded by resolve_preset_config's manifest path, so every preset
|
||||
// resolved through this bundle shares one load per source root and vendor. The
|
||||
// filament library is one such tree, shared by every vendor under its root.
|
||||
std::map<std::tuple<std::string, std::string, ForwardCompatibilitySubstitutionRule>, std::unique_ptr<PresetBundle>>
|
||||
// A tree read from its preset cache is kept apart: its presets carry no source file.
|
||||
std::map<std::tuple<std::string, std::string, ForwardCompatibilitySubstitutionRule, bool>, std::unique_ptr<PresetBundle>>
|
||||
m_source_vendor_bundles;
|
||||
|
||||
const PresetBundle *load_source_vendor(const boost::filesystem::path &root_dir,
|
||||
const std::string &vendor_id,
|
||||
ForwardCompatibilitySubstitutionRule compatibility_rule,
|
||||
std::string &error);
|
||||
std::string &error, bool allow_cache = false);
|
||||
|
||||
// Orca: validation only - flag any printer with two or more compatible
|
||||
// filament presets sharing one filament_id (ambiguous AMS subtype match).
|
||||
|
||||
@@ -12595,6 +12595,7 @@ OtherSlicingStatesConfigDef::OtherSlicingStatesConfigDef()
|
||||
|
||||
new_def("initial_no_support_extruder", coInt, "Initial no support extruder", "Zero-based index of the first extruder used for printing without support. Same as initial_no_support_tool.");
|
||||
new_def("in_head_wrap_detect_zone", coBool, "In head wrap detect zone", "Indicates if the first layer overlaps with the head wrap zone.");
|
||||
new_def("curr_bed_type", coString, "Current bed type", "Name of the currently selected bed plate type (e.g. 'Textured PEI Plate', 'Smooth High Temp Plate').");
|
||||
}
|
||||
|
||||
PrintStatisticsConfigDef::PrintStatisticsConfigDef()
|
||||
|
||||
@@ -11,7 +11,22 @@ endif ()
|
||||
|
||||
add_test(NAME cli_strict_mode
|
||||
COMMAND bash ${CMAKE_CURRENT_SOURCE_DIR}/test_cli_strict.sh $<TARGET_FILE:OrcaSlicer> ${ORCA_CLI_TEST_PYTHON})
|
||||
add_test(NAME cli_empty_project_config
|
||||
COMMAND bash ${CMAKE_CURRENT_SOURCE_DIR}/test_cli_empty_project_config.sh $<TARGET_FILE:OrcaSlicer> ${ORCA_CLI_TEST_PYTHON} ${CMAKE_SOURCE_DIR})
|
||||
set_tests_properties(cli_empty_project_config PROPERTIES
|
||||
LABELS "CLI;RequiresApp"
|
||||
SKIP_RETURN_CODE 77
|
||||
TIMEOUT 300)
|
||||
|
||||
set_tests_properties(cli_strict_mode PROPERTIES
|
||||
LABELS "CLI;RequiresApp"
|
||||
SKIP_RETURN_CODE 77
|
||||
TIMEOUT 900)
|
||||
|
||||
add_test(NAME cli_project_missing_keys
|
||||
COMMAND bash ${CMAKE_CURRENT_SOURCE_DIR}/test_cli_project_missing_keys.sh $<TARGET_FILE:OrcaSlicer> ${ORCA_CLI_TEST_PYTHON}
|
||||
${CMAKE_SOURCE_DIR}/resources/profiles/BBL)
|
||||
set_tests_properties(cli_project_missing_keys PROPERTIES
|
||||
LABELS "CLI;RequiresApp"
|
||||
SKIP_RETURN_CODE 77
|
||||
TIMEOUT 900)
|
||||
|
||||
Executable
+52
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env bash
|
||||
# Regression check: a 3mf whose Metadata/project_settings.config carries no settings must load.
|
||||
#
|
||||
# The CLI reads printable_height out of the project config with opt_float(), which dereferences
|
||||
# what option<>() returns. With create = false that is null when the key is absent, so a project
|
||||
# saved without settings used to take the CLI down with a segfault. Both models in
|
||||
# resources/handy_models are such files.
|
||||
#
|
||||
# usage: test_cli_empty_project_config.sh <orca-slicer binary> <python3> <source dir>
|
||||
set -u
|
||||
|
||||
BIN="${1:-}"
|
||||
PY="${2:-python3}"
|
||||
SRC="${3:-}"
|
||||
# 77 is the test's SKIP_RETURN_CODE.
|
||||
[ -x "$BIN" ] || { echo "SKIP: orca-slicer binary not found: $BIN"; exit 77; }
|
||||
[ -f "$SRC/resources/handy_models/OrcaBadge.3mf" ] || { echo "SKIP: handy model not found"; exit 77; }
|
||||
|
||||
WORK="$(mktemp -d "${TMPDIR:-/tmp}/orca-cli-emptycfg.XXXXXX")"
|
||||
trap 'rm -rf "$WORK"' EXIT
|
||||
mkdir -p "$WORK/datadir"
|
||||
|
||||
# Rewrite the project settings to an empty object, so the test holds no matter what the shipped
|
||||
# models carry later on.
|
||||
cp "$SRC/resources/handy_models/OrcaBadge.3mf" "$WORK/empty_config.3mf"
|
||||
"$PY" - "$WORK/empty_config.3mf" <<'PYEOF'
|
||||
import shutil, sys, zipfile
|
||||
|
||||
path = sys.argv[1]
|
||||
entry = "Metadata/project_settings.config"
|
||||
with zipfile.ZipFile(path) as src:
|
||||
items = [(i, src.read(i.filename)) for i in src.infolist()]
|
||||
with zipfile.ZipFile(path + ".new", "w", zipfile.ZIP_DEFLATED) as dst:
|
||||
seen = False
|
||||
for info, data in items:
|
||||
if info.filename == entry:
|
||||
data, seen = b"{\n}\n", True
|
||||
dst.writestr(info, data)
|
||||
if not seen:
|
||||
dst.writestr(entry, b"{\n}\n")
|
||||
shutil.move(path + ".new", path)
|
||||
PYEOF
|
||||
|
||||
"$BIN" --datadir "$WORK/datadir" --info "$WORK/empty_config.3mf" > "$WORK/info.txt" 2>&1
|
||||
rc=$?
|
||||
if [ $rc -ne 0 ]; then
|
||||
echo "FAIL: --info on a project with empty settings exited $rc"
|
||||
tail -20 "$WORK/info.txt"
|
||||
exit 1
|
||||
fi
|
||||
grep -q "size_x" "$WORK/info.txt" || { echo "FAIL: --info printed no geometry"; cat "$WORK/info.txt"; exit 1; }
|
||||
echo "PASS: a project with empty settings loads"
|
||||
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env bash
|
||||
# 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; 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
|
||||
|
||||
BIN="${1:-}"
|
||||
PY="${2:-python3}"
|
||||
PROFILES="${3:-}"
|
||||
# 77 is the test's SKIP_RETURN_CODE.
|
||||
[ -x "$BIN" ] || { echo "SKIP: orca-slicer binary not found: $BIN"; exit 77; }
|
||||
[ -d "$PROFILES" ] || { echo "FAIL: profiles directory not found: $PROFILES"; exit 1; }
|
||||
|
||||
WORK="$(mktemp -d "${TMPDIR:-/tmp}/orca-cli-missing-keys.XXXXXX")"
|
||||
trap 'rm -rf "$WORK"' EXIT
|
||||
|
||||
"$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")
|
||||
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 ((v[a], v[b], v[c]), (v[a], v[c], v[d])):
|
||||
f.write("facet normal 0 0 0\nouter loop\n")
|
||||
for p in tri:
|
||||
f.write("vertex %g %g %g\n" % p)
|
||||
f.write("endloop\nendfacet\n")
|
||||
f.write("endsolid cube\n")
|
||||
EOF
|
||||
|
||||
# slice <tag> <input> [option...]: slice into $WORK/<tag>/out.3mf with a fresh data directory.
|
||||
slice() {
|
||||
local out="$WORK/$1" input="$2"; shift 2
|
||||
mkdir -p "$out"
|
||||
timeout 300 "$BIN" --datadir "$out/datadir" "$@" --slice 0 --outputdir "$out" --export-3mf out.3mf "$input" \
|
||||
> "$out/log" 2>&1 || { echo "FAIL: $1: orca-slicer exited $?"; tail -n 40 "$out/log"; exit 1; }
|
||||
}
|
||||
|
||||
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"
|
||||
|
||||
# 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.
|
||||
"$PY" - "$WORK/base/out.3mf" "$WORK/old.3mf" <<'EOF' || exit $?
|
||||
import json, sys, zipfile
|
||||
|
||||
src, dst = sys.argv[1], sys.argv[2]
|
||||
missing = {"extruder_clearance_dist_to_rod": "40", "sparse_infill_density": "20%"}
|
||||
with zipfile.ZipFile(src) as zin, zipfile.ZipFile(dst, "w", zipfile.ZIP_DEFLATED) as zout:
|
||||
for item in zin.infolist():
|
||||
data = zin.read(item.filename)
|
||||
if item.filename == "Metadata/project_settings.config":
|
||||
config = json.loads(data)
|
||||
for key, default in missing.items():
|
||||
if config[key] == default:
|
||||
print("SKIP: %s is %s in the system preset, the option default, so the test cannot tell them apart" % (key, default))
|
||||
sys.exit(77)
|
||||
expected = {key: config.pop(key) for key in missing}
|
||||
for key in ("top_shell_layers", "extruder_clearance_height_to_rod"):
|
||||
expected[key] = config[key]
|
||||
config[key] = str(int(float(config[key])) + 1)
|
||||
expected["wall_loops"] = str(int(config["wall_loops"]) + 1)
|
||||
config["wall_loops"] = expected["wall_loops"]
|
||||
different = config["different_settings_to_system"]
|
||||
different[0] = ";".join([k for k in different[0].split(";") if k] + ["wall_loops"])
|
||||
data = json.dumps(config, indent=4)
|
||||
zout.writestr(item, data)
|
||||
with open(dst + ".expected.json", "w") as f:
|
||||
json.dump(expected, f)
|
||||
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 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
|
||||
status=$?
|
||||
[ "$status" -eq 0 ] || { tail -n 40 "$WORK/project/log"; exit 1; }
|
||||
echo "PASS"
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "libslic3r/AABBTreeLines.hpp"
|
||||
#include "libslic3r/Fill/Fill.hpp"
|
||||
#include "libslic3r/Fill/FillAdaptive.hpp"
|
||||
#include "libslic3r/Fill/FillGyroid.hpp"
|
||||
#include "libslic3r/Flow.hpp"
|
||||
#include "libslic3r/Geometry.hpp"
|
||||
#include "libslic3r/IntersectionPoints.hpp"
|
||||
@@ -1257,6 +1258,61 @@ TEST_CASE("Multiline infill of an object matches the infill of a larger object w
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Gyroid infill of an object matches the infill of a larger object with the same center", "[Fill]")
|
||||
{
|
||||
const bool optimized = GENERATE(false, true);
|
||||
const int multiline = GENERATE(1, 2);
|
||||
const float density = GENERATE(0.05f, 0.2f);
|
||||
const double spacing = 0.45;
|
||||
CAPTURE(optimized, multiline, density);
|
||||
|
||||
auto circle = [](double radius) {
|
||||
Polygon contour = make_circle_num_segments(scale_(radius), 120);
|
||||
contour.translate(Point::new_scale(100., 60.));
|
||||
return ExPolygon(std::move(contour));
|
||||
};
|
||||
const ExPolygon object = circle(20.);
|
||||
const ExPolygon larger = circle(30.);
|
||||
auto fill = [optimized, multiline, density, spacing](const ExPolygon ®ion, double z) {
|
||||
std::unique_ptr<Fill> filler(Fill::new_from_type(ipGyroid));
|
||||
filler->spacing = spacing;
|
||||
filler->angle = float(M_PI / 7.);
|
||||
filler->z = z;
|
||||
|
||||
FillParams params;
|
||||
params.density = density;
|
||||
params.multiline = multiline;
|
||||
params.gyroid_optimized = optimized;
|
||||
params.dont_adjust = true;
|
||||
Surface surface(stInternal, region);
|
||||
return filler->fill_surface(&surface, params);
|
||||
};
|
||||
// Away from the boundary of the object, where both are clipped and connected the same way.
|
||||
const Polygons inner = shrink(to_polygons(object), scale_(1.));
|
||||
auto farthest = [&inner](const Polylines &from, const Polylines &to) {
|
||||
const AABBTreeLines::LinesDistancer<Line> tree(to_lines(to));
|
||||
double distance = 0.;
|
||||
for (const Polyline &path : intersection_pl(from, inner))
|
||||
for (const Point &point : path.equally_spaced_points(scale_(0.2)))
|
||||
distance = std::max(distance, tree.distance_from_lines<false>(point));
|
||||
return unscale<double>(distance);
|
||||
};
|
||||
|
||||
// Marching squares simplifies rings that start elsewhere in each object.
|
||||
const double tolerance = optimized ? SPARSE_INFILL_RESOLUTION + 0.01 : 0.01;
|
||||
// Half a z period of the waves, through both switches between horizontal and vertical waves.
|
||||
const double wave_distance = spacing * multiline / (density * FillGyroid::DensityAdjust);
|
||||
for (int step = 0; step <= 8; ++step) {
|
||||
const double z = wave_distance * M_PI * step / 8.;
|
||||
CAPTURE(z);
|
||||
const Polylines paths = fill(object, z);
|
||||
REQUIRE_FALSE(paths.empty());
|
||||
const Polylines reference = fill(larger, z);
|
||||
CHECK(farthest(reference, paths) < tolerance);
|
||||
CHECK(farthest(paths, reference) < tolerance);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Multiline cubic infill follows the cubic lines without crossing itself", "[Fill]")
|
||||
{
|
||||
const int multiline = GENERATE(2, 3);
|
||||
|
||||
@@ -32,7 +32,6 @@ add_executable(${_TEST_NAME}_tests
|
||||
test_geometry.cpp
|
||||
test_multimaterial_segmentation.cpp
|
||||
test_placeholder_parser.cpp
|
||||
test_png_read_write.cpp
|
||||
test_polygon.cpp
|
||||
test_mutable_polygon.cpp
|
||||
test_mutable_priority_queue.cpp
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
#include <catch2/catch_all.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
#include <iterator>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include "libslic3r/PNGReadWrite.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
// libpng reports a corrupt or truncated file by longjmp()ing out of the decoder, so the decoders have
|
||||
// to come back with false rather than crash or hand back a half filled image.
|
||||
namespace {
|
||||
|
||||
// A real PNG, produced by the writer next door, so the bytes are a file libpng accepts.
|
||||
std::vector<uint8_t> encoded_png(size_t w, size_t h)
|
||||
{
|
||||
std::vector<uint8_t> pixels(w * h);
|
||||
for (size_t i = 0; i < pixels.size(); ++ i)
|
||||
pixels[i] = uint8_t((i * 7) % 256);
|
||||
|
||||
const boost::filesystem::path path = boost::filesystem::temp_directory_path() /
|
||||
boost::filesystem::unique_path("png_rw_%%%%%%%%.png");
|
||||
REQUIRE(png::write_gray_to_file(path.string(), w, h, pixels));
|
||||
std::vector<uint8_t> bytes;
|
||||
{
|
||||
std::ifstream ifs(path.string(), std::ios::binary);
|
||||
bytes.assign(std::istreambuf_iterator<char>(ifs), std::istreambuf_iterator<char>());
|
||||
}
|
||||
boost::system::error_code ec;
|
||||
boost::filesystem::remove(path, ec);
|
||||
REQUIRE(bytes.size() > 64);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
png::ReadBuf buf_of(const std::vector<uint8_t> &bytes, size_t size)
|
||||
{
|
||||
return png::ReadBuf{ bytes.data(), size };
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("A whole PNG decodes", "[PNG]") {
|
||||
const std::vector<uint8_t> bytes = encoded_png(24, 16);
|
||||
|
||||
png::ImageGreyscale grey;
|
||||
REQUIRE(png::decode_png(buf_of(bytes, bytes.size()), grey));
|
||||
CHECK(grey.cols == 24);
|
||||
CHECK(grey.rows == 16);
|
||||
CHECK(grey.buf.size() == 24 * 16);
|
||||
}
|
||||
|
||||
TEST_CASE("A truncated PNG is refused instead of crashing", "[PNG]") {
|
||||
const std::vector<uint8_t> bytes = encoded_png(64, 64);
|
||||
|
||||
// Cut past the signature: inside the header, and inside the pixel data. Not in the trailing
|
||||
// chunks - decode_png() does not read those, so a file missing only its IEND still decodes, and
|
||||
// that is the pre-existing contract rather than anything this change touches.
|
||||
const size_t size = GENERATE_COPY(size_t(16), size_t(40), bytes.size() / 2, bytes.size() * 3 / 4);
|
||||
REQUIRE(size < bytes.size());
|
||||
|
||||
png::ImageGreyscale grey;
|
||||
CHECK_FALSE(png::decode_png(buf_of(bytes, size), grey));
|
||||
|
||||
png::ImageColorscale colour;
|
||||
CHECK_FALSE(png::decode_colored_png(buf_of(bytes, size), colour));
|
||||
}
|
||||
|
||||
TEST_CASE("A PNG whose body is garbage is refused", "[PNG]") {
|
||||
std::vector<uint8_t> bytes = encoded_png(32, 32);
|
||||
// Keep the signature, scribble over everything after it.
|
||||
for (size_t i = 8; i < bytes.size(); ++ i)
|
||||
bytes[i] = uint8_t(0xA5);
|
||||
|
||||
png::ImageGreyscale grey;
|
||||
CHECK_FALSE(png::decode_png(buf_of(bytes, bytes.size()), grey));
|
||||
|
||||
png::ImageColorscale colour;
|
||||
CHECK_FALSE(png::decode_colored_png(buf_of(bytes, bytes.size()), colour));
|
||||
}
|
||||
@@ -5571,6 +5571,35 @@ struct ScopedDataDir
|
||||
~ScopedDataDir() { set_data_dir(previous); }
|
||||
};
|
||||
|
||||
// resources_dir() is process-wide too; system preset lookups scan its profiles directory.
|
||||
struct ScopedResourcesDir
|
||||
{
|
||||
std::string previous = resources_dir();
|
||||
explicit ScopedResourcesDir(const fs::path &dir) { set_resources_dir(dir.string()); }
|
||||
~ScopedResourcesDir() { set_resources_dir(previous); }
|
||||
};
|
||||
|
||||
// An "Acme" vendor under root whose "Acme Printer" inherits extruder_clearance_dist_to_rod from an
|
||||
// abstract base, with the printer in a nested sub_path so the name cannot be derived from the file.
|
||||
void write_acme_printer_vendor(const fs::path &root, double dist_to_rod)
|
||||
{
|
||||
const fs::path machine_dir = root / "Acme" / "machine";
|
||||
fs::create_directories(machine_dir / "nested");
|
||||
std::ofstream((root / "Acme.json").string())
|
||||
<< R"({"version":"1.0.0","name":"Acme",)"
|
||||
<< R"("machine_model_list":[{"name":"Acme One","sub_path":"machine/model.json"}],"machine_list":[)"
|
||||
<< R"({"name":"fdm_acme_common","sub_path":"machine/base.json"},)"
|
||||
<< R"({"name":"Acme Printer","sub_path":"machine/nested/printer.json"}]})";
|
||||
std::ofstream((machine_dir / "model.json").string())
|
||||
<< R"({"type":"machine_model","name":"Acme One","nozzle_diameter":"0.4"})";
|
||||
std::ofstream((machine_dir / "base.json").string())
|
||||
<< R"({"type":"machine","name":"fdm_acme_common","from":"system","instantiation":"false",)"
|
||||
<< R"("extruder_clearance_dist_to_rod":")" << dist_to_rod << R"("})";
|
||||
std::ofstream((machine_dir / "nested" / "printer.json").string())
|
||||
<< R"({"type":"machine","name":"Acme Printer","from":"system","instantiation":"true","inherits":"fdm_acme_common",)"
|
||||
<< R"("printer_model":"Acme One","printer_variant":"0.4"})";
|
||||
}
|
||||
|
||||
std::string read_file(const fs::path &file)
|
||||
{
|
||||
std::ifstream in(file.string(), std::ios::binary);
|
||||
@@ -5684,3 +5713,230 @@ TEST_CASE("A project saved with pressure advance per filament applies it to ever
|
||||
check_double_vector(pla.opt<ConfigOptionFloatsNullable>("filament_flow_ratio")->values, { 0.95, 0.96 });
|
||||
check_double_vector(petg.opt<ConfigOptionFloatsNullable>("filament_flow_ratio")->values, { 0.97 });
|
||||
}
|
||||
|
||||
TEST_CASE("A system preset resolves by name from the bundled profiles", "[Preset][Bundle]")
|
||||
{
|
||||
ScopedTemporaryDir temp_dir;
|
||||
ScopedDataDir data(temp_dir.path() / "data");
|
||||
ScopedResourcesDir resources(temp_dir.path() / "resources");
|
||||
write_acme_printer_vendor(temp_dir.path() / "resources" / "profiles", 33.);
|
||||
|
||||
PresetBundle bundle;
|
||||
DynamicPrintConfig config;
|
||||
std::string error;
|
||||
REQUIRE(bundle.resolve_system_preset(config, Preset::TYPE_PRINTER, "Acme Printer",
|
||||
ForwardCompatibilitySubstitutionRule::EnableSilent, error));
|
||||
CHECK(error.empty());
|
||||
CHECK_THAT(config.opt_float("extruder_clearance_dist_to_rod"), Catch::Matchers::WithinAbs(33., 1e-6));
|
||||
}
|
||||
|
||||
TEST_CASE("A system preset resolves from the data directory copy of its vendor", "[Preset][Bundle]")
|
||||
{
|
||||
ScopedTemporaryDir temp_dir;
|
||||
ScopedDataDir data(temp_dir.path() / "data");
|
||||
ScopedResourcesDir resources(temp_dir.path() / "resources");
|
||||
write_acme_printer_vendor(temp_dir.path() / "resources" / "profiles", 33.);
|
||||
write_acme_printer_vendor(temp_dir.path() / "data" / PRESET_SYSTEM_DIR, 35.);
|
||||
|
||||
PresetBundle bundle;
|
||||
DynamicPrintConfig config;
|
||||
std::string error;
|
||||
REQUIRE(bundle.resolve_system_preset(config, Preset::TYPE_PRINTER, "Acme Printer",
|
||||
ForwardCompatibilitySubstitutionRule::EnableSilent, error));
|
||||
CHECK_THAT(config.opt_float("extruder_clearance_dist_to_rod"), Catch::Matchers::WithinAbs(35., 1e-6));
|
||||
}
|
||||
|
||||
TEST_CASE("A system preset resolves from a vendor shipped as its preset cache alone", "[Preset][Bundle]")
|
||||
{
|
||||
ScopedTemporaryDir temp_dir;
|
||||
ScopedDataDir data(temp_dir.path() / "data");
|
||||
ScopedResourcesDir resources(temp_dir.path() / "resources");
|
||||
const fs::path profiles = temp_dir.path() / "resources" / "profiles";
|
||||
write_acme_printer_vendor(profiles, 33.);
|
||||
|
||||
PresetBundle writer;
|
||||
writer.set_generate_vendor_caches(true);
|
||||
writer.load_vendor_configs_from_json(profiles.string(), "Acme", PresetBundle::LoadSystem,
|
||||
ForwardCompatibilitySubstitutionRule::EnableSilent);
|
||||
REQUIRE(fs::exists(profiles / "Acme.opc"));
|
||||
// Release builds ship the cache and drop the profile JSONs, manifest included.
|
||||
fs::remove(profiles / "Acme.json");
|
||||
fs::remove_all(profiles / "Acme");
|
||||
|
||||
PresetBundle bundle;
|
||||
DynamicPrintConfig config;
|
||||
std::string error;
|
||||
REQUIRE(bundle.resolve_system_preset(config, Preset::TYPE_PRINTER, "Acme Printer",
|
||||
ForwardCompatibilitySubstitutionRule::EnableSilent, error));
|
||||
CHECK_THAT(config.opt_float("extruder_clearance_dist_to_rod"), Catch::Matchers::WithinAbs(33., 1e-6));
|
||||
}
|
||||
|
||||
TEST_CASE("A system preset no vendor lists is not resolved", "[Preset][Bundle]")
|
||||
{
|
||||
ScopedTemporaryDir temp_dir;
|
||||
ScopedDataDir data(temp_dir.path() / "data");
|
||||
ScopedResourcesDir resources(temp_dir.path() / "resources");
|
||||
write_acme_printer_vendor(temp_dir.path() / "resources" / "profiles", 33.);
|
||||
|
||||
PresetBundle bundle;
|
||||
DynamicPrintConfig config;
|
||||
std::string error;
|
||||
CHECK_FALSE(bundle.resolve_system_preset(config, Preset::TYPE_PRINTER, "Unknown Printer",
|
||||
ForwardCompatibilitySubstitutionRule::EnableSilent, error));
|
||||
CHECK_FALSE(error.empty());
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// A default preset config for type, built the way PresetBundle builds its default presets.
|
||||
DynamicPrintConfig external_default_config(Preset::Type type)
|
||||
{
|
||||
DynamicPrintConfig config;
|
||||
config.apply_only(static_cast<const PrintRegionConfig &>(FullPrintConfig::defaults()),
|
||||
type == Preset::TYPE_PRINTER ? Preset::printer_options() : Preset::print_options());
|
||||
Preset::inherits(config);
|
||||
return config;
|
||||
}
|
||||
|
||||
// A find_base callback that counts its calls and returns base.
|
||||
auto base_finder(DynamicPrintConfig *base, int &calls)
|
||||
{
|
||||
return [base, &calls](const std::string &) {
|
||||
++calls;
|
||||
return base;
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("A key missing from a project takes the default preset value when there is no base", "[Preset][ExternalPreset]")
|
||||
{
|
||||
DynamicPrintConfig defaults = external_default_config(Preset::TYPE_PRINT);
|
||||
defaults.set("wall_loops", 7, true);
|
||||
const DynamicPrintConfig project;
|
||||
int calls = 0;
|
||||
|
||||
const DynamicPrintConfig config = Preset::load_external_config(Preset::TYPE_PRINT, defaults, project, {"sparse_infill_density"},
|
||||
base_finder(nullptr, calls));
|
||||
CHECK(calls == 1);
|
||||
CHECK(config.opt_int("wall_loops") == 7);
|
||||
}
|
||||
|
||||
TEST_CASE("A key missing from a project takes its base preset value", "[Preset][ExternalPreset]")
|
||||
{
|
||||
DynamicPrintConfig defaults = external_default_config(Preset::TYPE_PRINT);
|
||||
defaults.set("wall_loops", 7, true);
|
||||
DynamicPrintConfig base = defaults;
|
||||
base.set("wall_loops", 5, true);
|
||||
const DynamicPrintConfig project;
|
||||
int calls = 0;
|
||||
|
||||
const DynamicPrintConfig config = Preset::load_external_config(Preset::TYPE_PRINT, defaults, project, {"sparse_infill_density"},
|
||||
base_finder(&base, calls));
|
||||
CHECK(config.opt_int("wall_loops") == 5);
|
||||
}
|
||||
|
||||
TEST_CASE("A project key listed as different keeps the project value", "[Preset][ExternalPreset]")
|
||||
{
|
||||
const DynamicPrintConfig defaults = external_default_config(Preset::TYPE_PRINT);
|
||||
DynamicPrintConfig base = defaults;
|
||||
base.set("wall_loops", 5, true);
|
||||
DynamicPrintConfig project;
|
||||
project.set("wall_loops", 4, true);
|
||||
int calls = 0;
|
||||
|
||||
const DynamicPrintConfig config = Preset::load_external_config(Preset::TYPE_PRINT, defaults, project, {"wall_loops"},
|
||||
base_finder(&base, calls));
|
||||
CHECK(config.opt_int("wall_loops") == 4);
|
||||
}
|
||||
|
||||
TEST_CASE("A project key not listed as different is refreshed to the base value", "[Preset][ExternalPreset]")
|
||||
{
|
||||
const DynamicPrintConfig defaults = external_default_config(Preset::TYPE_PRINT);
|
||||
DynamicPrintConfig base = defaults;
|
||||
base.set("wall_loops", 5, true);
|
||||
DynamicPrintConfig project;
|
||||
project.set("wall_loops", 4, true);
|
||||
project.set("inherits", "Base Process", true);
|
||||
std::string inherits;
|
||||
|
||||
const DynamicPrintConfig config = Preset::load_external_config(Preset::TYPE_PRINT, defaults, project, {"sparse_infill_density"},
|
||||
[&base, &inherits](const std::string &name) {
|
||||
inherits = name;
|
||||
return &base;
|
||||
});
|
||||
CHECK(inherits == "Base Process");
|
||||
CHECK(config.opt_int("wall_loops") == 5);
|
||||
}
|
||||
|
||||
TEST_CASE("An empty different settings list keeps the project values without a base", "[Preset][ExternalPreset]")
|
||||
{
|
||||
DynamicPrintConfig defaults = external_default_config(Preset::TYPE_PRINT);
|
||||
defaults.set("top_shell_layers", 7, true);
|
||||
DynamicPrintConfig base = defaults;
|
||||
base.set("wall_loops", 5, true);
|
||||
base.set("top_shell_layers", 9, true);
|
||||
DynamicPrintConfig project;
|
||||
project.set("wall_loops", 4, true);
|
||||
int calls = 0;
|
||||
|
||||
const DynamicPrintConfig config = Preset::load_external_config(Preset::TYPE_PRINT, defaults, project, {}, base_finder(&base, calls));
|
||||
CHECK(calls == 0);
|
||||
CHECK(config.opt_int("wall_loops") == 4);
|
||||
CHECK(config.opt_int("top_shell_layers") == 7);
|
||||
}
|
||||
|
||||
TEST_CASE("Print-host keys are never taken from a project", "[Preset][ExternalPreset]")
|
||||
{
|
||||
DynamicPrintConfig defaults = external_default_config(Preset::TYPE_PRINTER);
|
||||
defaults.set("print_host", "", true);
|
||||
defaults.set("printhost_apikey", "", true);
|
||||
DynamicPrintConfig project;
|
||||
project.set("print_host", "http://project-host", true);
|
||||
project.set("printhost_apikey", "project-key", true);
|
||||
project.set("printer_notes", "project notes", true);
|
||||
t_config_option_keys keys;
|
||||
int calls = 0;
|
||||
|
||||
const DynamicPrintConfig config = Preset::load_external_config(Preset::TYPE_PRINTER, defaults, project, {"printer_notes"},
|
||||
base_finder(nullptr, calls), &keys);
|
||||
CHECK(config.opt_string("print_host").empty());
|
||||
CHECK(config.opt_string("printhost_apikey").empty());
|
||||
CHECK(config.opt_string("printer_notes") == "project notes");
|
||||
CHECK_FALSE(contains_key(keys, "print_host"));
|
||||
CHECK_FALSE(contains_key(keys, "printhost_apikey"));
|
||||
CHECK(contains_key(keys, "printer_notes"));
|
||||
}
|
||||
|
||||
TEST_CASE("A per-variant project value maps onto its base preset's variant layout", "[Preset][ExternalPreset]")
|
||||
{
|
||||
const DynamicPrintConfig defaults = external_default_config(Preset::TYPE_PRINT);
|
||||
DynamicPrintConfig base = defaults;
|
||||
base.set_key_value("print_extruder_id", new ConfigOptionInts({1, 1}));
|
||||
base.set_key_value("print_extruder_variant", new ConfigOptionStrings({"Direct Drive Standard", "Direct Drive High Flow"}));
|
||||
base.set_key_value("outer_wall_speed", new ConfigOptionFloats({200., 300.}));
|
||||
base.set_key_value("inner_wall_speed", new ConfigOptionFloats({250., 350.}));
|
||||
// A project saved before its printer had a High Flow variant.
|
||||
DynamicPrintConfig project;
|
||||
project.set_key_value("print_extruder_id", new ConfigOptionInts({1}));
|
||||
project.set_key_value("print_extruder_variant", new ConfigOptionStrings({"Direct Drive Standard"}));
|
||||
project.set_key_value("outer_wall_speed", new ConfigOptionFloats({100.}));
|
||||
project.set_key_value("inner_wall_speed", new ConfigOptionFloats({150.}));
|
||||
int calls = 0;
|
||||
|
||||
const DynamicPrintConfig config = Preset::load_external_config(Preset::TYPE_PRINT, defaults, project, {"outer_wall_speed"},
|
||||
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.});
|
||||
check_double_vector(config.option<ConfigOptionFloats>("inner_wall_speed")->values, {250., 350.});
|
||||
}
|
||||
|
||||
TEST_CASE("A project's different settings always keep the preset bookkeeping keys", "[Preset][ExternalPreset]")
|
||||
{
|
||||
const std::set<std::string> keys = PresetBundle::project_different_keys(escape_strings_cstyle({"wall_loops", "top_shell_layers"}));
|
||||
for (const char *key : {"wall_loops", "top_shell_layers", "inherits", "print_settings_id", "filament_settings_id", "printer_settings_id"})
|
||||
CHECK(keys.count(key) == 1);
|
||||
CHECK(PresetBundle::project_different_keys(std::string()).count("inherits") == 1);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user