Compare commits

..
Author SHA1 Message Date
Hanif Koh 4b56c2a514 Check the Project Refresh Test's Result Directly
Shellcheck SC2181: test the checker's exit status in the if instead of
reading $? afterwards.
2026-10-01 16:38:03 +08:00
Hanif Koh cb422fc500 Refresh a CLI Project's Filament Settings From Their System Presets
The CLI loads a project's printer and process settings as the GUI does,
taking every key the project does not list as changed from the current
system preset, but it kept the stored filament values. A project saved
before a profile update then sliced with old filament values on the
command line and with the current ones in the GUI.

Every project filament that no loaded filament replaces is now resolved
by its system preset name and fed to the filament merge the up-to-date
path already uses, which keeps the keys listed in
different_settings_to_system and maps per-variant values onto the
preset's variants. This covers a plain run, --uptodate without
--uptodate-filaments, and the slots --load-filaments leaves empty. The
merge tells refreshed entries from loaded ones per entry instead of by
the global loaded-filament count, and the entries are kept in slot
order. A project filament saved under a name the presets have since
split per nozzle is resolved through the name conversion the GUI uses,
which PresetBundle now exposes.
2026-10-01 15:52:09 +08:00
8 changed files with 131 additions and 193 deletions
+40 -12
View File
@@ -1913,10 +1913,7 @@ int CLI::run(int argc, char **argv)
old_printable_width = static_cast<int>(old_printable_bbox.size().x()); old_printable_width = static_cast<int>(old_printable_bbox.size().x());
old_printable_depth = static_cast<int>(old_printable_bbox.size().y()); old_printable_depth = static_cast<int>(old_printable_bbox.size().y());
} }
// A 3mf can carry an empty project_settings.config - the models in old_printable_height = (int)(config.opt_float("printable_height"));
// 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")) if (config.option<ConfigOptionFloat>("extruder_clearance_height_to_rod"))
old_height_to_rod = config.opt_float("extruder_clearance_height_to_rod"); old_height_to_rod = config.opt_float("extruder_clearance_height_to_rod");
@@ -2595,8 +2592,7 @@ int CLI::run(int argc, char **argv)
orig_printable_width = static_cast<int>(orig_printable_bbox.size().x()); orig_printable_width = static_cast<int>(orig_printable_bbox.size().x());
orig_printable_depth = static_cast<int>(orig_printable_bbox.size().y()); orig_printable_depth = static_cast<int>(orig_printable_bbox.size().y());
} }
if (config.option<ConfigOptionFloat>("printable_height")) orig_printable_height = (int)(config.opt_float("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%") 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; %__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)) if ((orig_printable_width > 0) && (orig_printable_depth > 0) && (orig_printable_height > 0))
@@ -2783,6 +2779,37 @@ int CLI::run(int argc, char **argv)
fetch_compatible_values = true; fetch_compatible_values = true;
} }
// Refresh every project filament no loaded filament replaces from its current system preset, as the GUI
// does when it loads the project; the filament merge below keeps the keys the project lists as changed.
// Entries stay in slot order, which the merge's variant bookkeeping relies on.
std::vector<bool> load_filaments_refresh(load_filaments_config.size(), false);
if (is_bbl_3mf && new_printer_name.empty()) {
const ConfigOptionStrings *project_filament_ids = m_print_config.option<ConfigOptionStrings>("filament_ids");
for (size_t index = 0; index < current_filaments_system_name.size(); index++) {
const int slot = static_cast<int>(index) + 1;
if (std::find(load_filaments_index.begin(), load_filaments_index.end(), slot) != load_filaments_index.end())
continue;
std::string system_name = current_filaments_system_name[index];
if (system_name.empty())
continue;
PresetBundle::convert_filament_preset_name(current_printer_name, system_name);
DynamicPrintConfig config;
std::string error;
if (!ensure_system_preset_resolver().resolve_system_preset(config, Preset::TYPE_FILAMENT, system_name, config_substitution_rule, error)) {
BOOST_LOG_TRIVIAL(warning) << boost::format("CLI: system filament preset '%1%' not resolved (%2%); filament %3% keeps its values") % system_name % error % slot;
continue;
}
const size_t at = std::upper_bound(load_filaments_index.begin(), load_filaments_index.end(), slot) - load_filaments_index.begin();
load_filaments_id.insert(load_filaments_id.begin() + at,
project_filament_ids != nullptr && index < project_filament_ids->size() ? project_filament_ids->values[index] : std::string());
load_filaments_name.insert(load_filaments_name.begin() + at, system_name);
load_filaments_config.insert(load_filaments_config.begin() + at, std::move(config));
load_filaments_index.insert(load_filaments_index.begin() + at, slot);
load_filaments_inherit.insert(load_filaments_inherit.begin() + at, system_name);
load_filaments_refresh.insert(load_filaments_refresh.begin() + at, true);
}
}
//fetch upward_compatible_machine //fetch upward_compatible_machine
if (fetch_upward_values) { if (fetch_upward_values) {
if (!current_printer_system_name.empty()) { if (!current_printer_system_name.empty()) {
@@ -3473,7 +3500,7 @@ int CLI::run(int argc, char **argv)
} }
//set the filament settings into print config //set the filament settings into print config
if ((load_filament_count > 0) || (up_config_to_date)) if ((load_filament_count > 0) || (up_config_to_date) || !load_filaments_config.empty())
{ {
//std::vector<int> filament_variant_count(filament_count, 1); //std::vector<int> filament_variant_count(filament_count, 1);
std::vector<int> old_start_indice(filament_count, 0); std::vector<int> old_start_indice(filament_count, 0);
@@ -3532,6 +3559,8 @@ int CLI::run(int argc, char **argv)
for (int index = 0; index < load_filaments_config.size(); index++) { for (int index = 0; index < load_filaments_config.size(); index++) {
DynamicPrintConfig& config = load_filaments_config[index]; DynamicPrintConfig& config = load_filaments_config[index];
int filament_index = load_filaments_index[index]; int filament_index = load_filaments_index[index];
// A filament given with --load-filaments replaces the slot; a refreshed one keeps the project's changed keys.
const bool loaded = load_filament_count > 0 && !load_filaments_refresh[index];
std::vector<std::string> different_keys; std::vector<std::string> different_keys;
//ORCA: diff before load_default_gcodes_to_config, the way the process and machine //ORCA: diff before load_default_gcodes_to_config, the way the process and machine
@@ -3541,12 +3570,12 @@ int CLI::run(int argc, char **argv)
// compared" to "compared as empty against the parent" and land in the column // compared" to "compared as empty against the parent" and land in the column
// as an override the user never made. // as an override the user never made.
std::string filament_different_settings; std::string filament_different_settings;
if (load_filament_count > 0) if (loaded)
filament_different_settings = cli_different_settings(config, load_filaments_inherit[index], Preset::TYPE_FILAMENT); filament_different_settings = cli_different_settings(config, load_filaments_inherit[index], Preset::TYPE_FILAMENT);
load_default_gcodes_to_config(config, Preset::TYPE_FILAMENT); load_default_gcodes_to_config(config, Preset::TYPE_FILAMENT);
if (load_filament_count > 0) { if (loaded) {
ConfigOptionStrings *opt_filament_settings = static_cast<ConfigOptionStrings *> (m_print_config.option("filament_settings_id", true)); ConfigOptionStrings *opt_filament_settings = static_cast<ConfigOptionStrings *> (m_print_config.option("filament_settings_id", true));
std::string& filament_name = load_filaments_name[index]; std::string& filament_name = load_filaments_name[index];
ConfigOptionString* filament_name_setting = new ConfigOptionString(filament_name); ConfigOptionString* filament_name_setting = new ConfigOptionString(filament_name);
@@ -3618,7 +3647,7 @@ int CLI::run(int argc, char **argv)
flush_and_exit(CLI_CONFIG_FILE_ERROR); flush_and_exit(CLI_CONFIG_FILE_ERROR);
} }
if ((load_filament_count == 0) && !different_keys_set.empty()) if (!loaded && !different_keys_set.empty())
{ {
std::set<std::string>::iterator iter = different_keys_set.find(opt_key); std::set<std::string>::iterator iter = different_keys_set.find(opt_key);
if ( iter != different_keys_set.end()) { if ( iter != different_keys_set.end()) {
@@ -4662,8 +4691,7 @@ int CLI::run(int argc, char **argv)
BoundingBoxf temp_printable_bbox(temp_printable_area); BoundingBoxf temp_printable_bbox(temp_printable_area);
printer_plate.printable_width = static_cast<int>(temp_printable_bbox.size().x()); 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_depth = static_cast<int>(temp_printable_bbox.size().y());
if (config.option<ConfigOptionFloat>("printable_height")) printer_plate.printable_height = (int)(config.opt_float("printable_height"));
printer_plate.printable_height = (int)(config.opt_float("printable_height"));
} }
if (temp_exclude_area.size() >= 4) { if (temp_exclude_area.size() >= 4) {
printer_plate.exclude_width = (int)(temp_exclude_area[2].x() - temp_exclude_area[0].x()); printer_plate.exclude_width = (int)(temp_exclude_area[2].x() - temp_exclude_area[0].x());
+58 -55
View File
@@ -5,7 +5,6 @@
#include <cmath> #include <cmath>
#include <algorithm> #include <algorithm>
#include <iostream> #include <iostream>
#include <limits>
#include "FillBase.hpp" #include "FillBase.hpp"
#include "FillGyroid.hpp" #include "FillGyroid.hpp"
@@ -124,41 +123,44 @@ 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( static inline Polyline make_wave(
const std::vector<Vec2d>& one_period, double x_min, double x_max, double offset, double scaleFactor, bool vertical) 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 double period = one_period.back().x(); std::vector<Vec2d> points = one_period;
// The last sample of a period is the first one of the next. double period = points.back()(0);
const size_t n = one_period.size() - 1; if (width != period) // do not extend if already truncated
double x0 = std::floor(x_min / period) * period; {
size_t i = 0; points.reserve(one_period.size() * size_t(floor(width / period)));
while (i + 1 < n && x0 + one_period[i + 1].x() <= x_min) points.pop_back();
++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 polyline;
polyline.points.reserve(size_t((x_max - x0) / period + 1.) * n + 1); polyline.points.reserve(points.size());
for (;;) { for (auto& point : points) {
Vec2d point(x0 + one_period[i].x(), one_period[i].y() + offset); point(1) += offset;
const bool last = point.x() >= x_max; point(1) = std::clamp(double(point.y()), 0., height);
if (vertical) if (vertical)
std::swap(point(0), point(1)); std::swap(point(0), point(1));
polyline.points.emplace_back((point * scaleFactor).cast<coord_t>()); polyline.points.emplace_back((point * scaleFactor).cast<coord_t>());
if (last)
break;
if (++i == n) {
i = 0;
x0 += period;
}
} }
return polyline; return polyline;
} }
static std::vector<Vec2d> make_one_period(double scaleFactor, double z_cos, double z_sin, bool vertical, bool flip, double tolerance) static std::vector<Vec2d> make_one_period(double width, double scaleFactor, double z_cos, double z_sin, bool vertical, bool flip, double tolerance)
{ {
std::vector<Vec2d> points; std::vector<Vec2d> points;
double dx = M_PI_2; // exact coordinates on main inflexion lobes double dx = M_PI_2; // exact coordinates on main inflexion lobes
double limit = 2*M_PI; double limit = std::min(2*M_PI, width);
points.reserve(coord_t(ceil(limit / tolerance / 3))); points.reserve(coord_t(ceil(limit / tolerance / 3)));
for (double x = 0.; x < limit - EPSILON; x += dx) { for (double x = 0.; x < limit - EPSILON; x += dx) {
@@ -237,8 +239,7 @@ static inline double compute_omega_factor(double density_adjusted, double line_s
return std::clamp(raw, 1.0, 2.0); return std::clamp(raw, 1.0, 2.0);
} }
// Waves covering bbox, with the pattern anchored at origin. static Polylines make_gyroid_waves(double gridZ, double density_adjusted, double line_spacing, double width, double height)
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; const double scaleFactor = scale_(line_spacing) / density_adjusted;
@@ -253,36 +254,29 @@ static Polylines make_gyroid_waves(double gridZ, double density_adjusted, double
const double z_cos = cos(z); const double z_cos = cos(z);
bool vertical = (std::abs(z_sin) <= std::abs(z_cos)); 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 lower_bound = 0.;
double upper_bound = height;
bool flip = true; bool flip = true;
if (vertical) { if (vertical) {
flip = false; flip = false;
lower_bound = -M_PI; lower_bound = -M_PI;
std::swap(lo(0), lo(1)); upper_bound = width - M_PI_2;
std::swap(hi(0), hi(1)); std::swap(width,height);
} }
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 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
flip = !flip; // even polylines are a bit shifted flip = !flip; // even polylines are a bit shifted
std::vector<Vec2d> one_period_even = make_one_period(scaleFactor, z_cos, z_sin, vertical, flip, tolerance); std::vector<Vec2d> one_period_even = make_one_period(width, scaleFactor, z_cos, z_sin, vertical, flip, tolerance);
// 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; 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(), for (double y0 = lower_bound; y0 < upper_bound + EPSILON; y0 += M_PI) {
lower_bound + i * M_PI, scaleFactor, vertical)); // creates odd polylines
wave.translate(origin); 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));
}
} }
return result; return result;
@@ -308,12 +302,12 @@ void FillGyroid::_fill_surface_single(
// Distance between the gyroid waves in scaled coordinates. // Distance between the gyroid waves in scaled coordinates.
coord_t distance = coord_t(scale_(this->spacing) / density_adjusted); coord_t distance = coord_t(scale_(this->spacing) / density_adjusted);
// Anchor the pattern to our grid module; the 10-line shift keeps its established phase. // align bounding box to a multiple of our grid module
const coord_t shift = coord_t(10 * scale_(this->spacing)); bb.merge(align_to_grid(bb.min, Point(2*M_PI*distance, 2*M_PI*distance)));
const Point origin = align_to_grid(bb.min, Point(2*M_PI*distance, 2*M_PI*distance)) - Point(shift, shift);
// Keep the pattern ends and the multiline copies outside the contour. // Expand the bounding box to avoid artifacts at the edges
bb.offset(scale_(this->spacing * params.multiline)); coord_t expand = 10 * (scale_(this->spacing));
bb.offset(expand);
// generate pattern // generate pattern
Polylines polylines; Polylines polylines;
@@ -333,14 +327,23 @@ void FillGyroid::_fill_surface_single(
const float density_factor = std::max(0.001f, float(params.density * DensityAdjust / params.multiline)); 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; const float period = float(2.0 * M_PI) * float(this->spacing) / density_factor;
// A cell of margin for the rings closed along the raster border, and a fixed sampling grid for every region. // bb is already expanded above by 10 * scale_(spacing) for edge artifacts;
const coord_t cell = scaled(marchsq::GyroidField::gsizef); // skip a second offset here to avoid raster-area bloat in the marching squares pass.
bb.offset(cell);
bb.merge(align_to_grid(bb.min, Point(cell, cell)));
marchsq::GyroidField sf(bb, this->z, period, float(omega)); marchsq::GyroidField sf(bb, this->z, period, float(omega));
polylines = marchsq::get_gyroid_polylines(sf, SCALED_SPARSE_INFILL_RESOLUTION); polylines = marchsq::get_gyroid_polylines(sf, SCALED_SPARSE_INFILL_RESOLUTION);
} else { } else {
polylines = make_gyroid_waves(scale_(this->z), density_adjusted, this->spacing, bb, origin); 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);
} }
// Apply multiline offset if needed // Apply multiline offset if needed
+1 -1
View File
@@ -5251,7 +5251,7 @@ static void apply_receiver_mix_relocations(DynamicPrintConfig&
//convert the old filament preset to new one after split //convert the old filament preset to new one after split
static void convert_filament_preset_name(std::string& machine_name, std::string& filament_name) void PresetBundle::convert_filament_preset_name(const std::string& machine_name, std::string& filament_name)
{ {
auto machine_iter = filament_preset_convert.find(machine_name); auto machine_iter = filament_preset_convert.find(machine_name);
if (machine_iter != filament_preset_convert.end()) if (machine_iter != filament_preset_convert.end())
+3
View File
@@ -238,6 +238,9 @@ public:
// Keys a project keeps when its presets are loaded: those listed in its escaped // 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. // "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); static std::set<std::string> project_different_keys(const std::string &different_settings);
// A project filament saved under a name the current presets split per nozzle (e.g. H2D 0.6) is loaded from
// the preset that now holds its values.
static void convert_filament_preset_name(const std::string& machine_name, std::string& filament_name);
PresetBundle(); PresetBundle();
PresetBundle(const PresetBundle &rhs); PresetBundle(const PresetBundle &rhs);
-7
View File
@@ -11,13 +11,6 @@ endif ()
add_test(NAME cli_strict_mode add_test(NAME cli_strict_mode
COMMAND bash ${CMAKE_CURRENT_SOURCE_DIR}/test_cli_strict.sh $<TARGET_FILE:OrcaSlicer> ${ORCA_CLI_TEST_PYTHON}) 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 set_tests_properties(cli_strict_mode PROPERTIES
LABELS "CLI;RequiresApp" LABELS "CLI;RequiresApp"
SKIP_RETURN_CODE 77 SKIP_RETURN_CODE 77
@@ -1,52 +0,0 @@
#!/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"
+29 -10
View File
@@ -1,11 +1,13 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# End-to-end check that the CLI loads a project's printer and process settings as the GUI does. # End-to-end check that the CLI loads a project's printer, process and filament settings as the GUI does.
# #
# The GUI takes every key a project does not list as changed from the project's current system preset: # 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 # 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 # 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 # Bambu Lab P1S presets with two filaments; one printer key and one process key are removed, one printer
# process key are changed without being listed, one key is changed and listed, and it is sliced again. # key, one process key and two filament keys (one per filament, one per extruder variant) are changed
# without being listed, one process key and the first filament's density are changed and listed, and it
# is sliced again: as is, with --uptodate, and with --load-filaments replacing only the second filament.
# #
# usage: test_cli_project_missing_keys.sh <orca-slicer binary> <python3> <resources/profiles/BBL> # usage: test_cli_project_missing_keys.sh <orca-slicer binary> <python3> <resources/profiles/BBL>
set -u set -u
@@ -45,7 +47,7 @@ slice() {
slice base "$WORK/cube.stl" \ 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-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" --load-filaments "$PROFILES/filament/Bambu PLA Basic @BBL P1S 0.4 nozzle.json;$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 # 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. # listed as different, which must come back with the system value; and a listed key the project keeps.
@@ -67,30 +69,47 @@ with zipfile.ZipFile(src) as zin, zipfile.ZipFile(dst, "w", zipfile.ZIP_DEFLATED
for key in ("top_shell_layers", "extruder_clearance_height_to_rod"): for key in ("top_shell_layers", "extruder_clearance_height_to_rod"):
expected[key] = config[key] expected[key] = config[key]
config[key] = str(int(float(config[key])) + 1) config[key] = str(int(float(config[key])) + 1)
for key in ("filament_cost", "filament_max_volumetric_speed"):
expected[key] = config[key]
config[key] = [str(float(v) + 1) for v in config[key]]
expected["wall_loops"] = str(int(config["wall_loops"]) + 1) expected["wall_loops"] = str(int(config["wall_loops"]) + 1)
config["wall_loops"] = expected["wall_loops"] config["wall_loops"] = expected["wall_loops"]
expected["filament_density"] = [str(float(config["filament_density"][0]) + 1)] + config["filament_density"][1:]
config["filament_density"] = [expected["filament_density"][0]] + [str(float(v) + 1) for v in config["filament_density"][1:]]
# One entry for the process, one per filament, one for the printer.
different = config["different_settings_to_system"] different = config["different_settings_to_system"]
different[0] = ";".join([k for k in different[0].split(";") if k] + ["wall_loops"]) different[0] = ";".join([k for k in different[0].split(";") if k] + ["wall_loops"])
different[1] = ";".join([k for k in different[1].split(";") if k] + ["filament_density"])
data = json.dumps(config, indent=4) data = json.dumps(config, indent=4)
zout.writestr(item, data) zout.writestr(item, data)
with open(dst + ".expected.json", "w") as f: with open(dst + ".expected.json", "w") as f:
json.dump(expected, f) json.dump(expected, f)
EOF EOF
slice project "$WORK/old.3mf" check() {
if ! "$PY" - "$WORK/$1/out.3mf" "$WORK/old.3mf.expected.json" "$1" <<'EOF'
"$PY" - "$WORK/project/out.3mf" "$WORK/old.3mf.expected.json" <<'EOF'
import json, sys, zipfile import json, sys, zipfile
with zipfile.ZipFile(sys.argv[1]) as z: with zipfile.ZipFile(sys.argv[1]) as z:
config = json.loads(z.read("Metadata/project_settings.config")) config = json.loads(z.read("Metadata/project_settings.config"))
with open(sys.argv[2]) as f: with open(sys.argv[2]) as f:
expected = json.load(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] errors = ["%s: %s is %r, want %r" % (sys.argv[3], key, config.get(key), want) for key, want in expected.items() if config.get(key) != want]
for e in errors: for e in errors:
print("FAIL: " + e) print("FAIL: " + e)
sys.exit(1 if errors else 0) sys.exit(1 if errors else 0)
EOF EOF
status=$? then
[ "$status" -eq 0 ] || { tail -n 40 "$WORK/project/log"; exit 1; } tail -n 40 "$WORK/$1/log"
exit 1
fi
}
slice project "$WORK/old.3mf"
check project
slice uptodate "$WORK/old.3mf" --uptodate
check uptodate
# The replaced second filament takes the system values the refresh would have given it.
slice partial "$WORK/old.3mf" --load-filaments ";$PROFILES/filament/Bambu PLA Basic @BBL P1S 0.4 nozzle.json"
check partial
echo "PASS" echo "PASS"
-56
View File
@@ -13,7 +13,6 @@
#include "libslic3r/AABBTreeLines.hpp" #include "libslic3r/AABBTreeLines.hpp"
#include "libslic3r/Fill/Fill.hpp" #include "libslic3r/Fill/Fill.hpp"
#include "libslic3r/Fill/FillAdaptive.hpp" #include "libslic3r/Fill/FillAdaptive.hpp"
#include "libslic3r/Fill/FillGyroid.hpp"
#include "libslic3r/Flow.hpp" #include "libslic3r/Flow.hpp"
#include "libslic3r/Geometry.hpp" #include "libslic3r/Geometry.hpp"
#include "libslic3r/IntersectionPoints.hpp" #include "libslic3r/IntersectionPoints.hpp"
@@ -1258,61 +1257,6 @@ 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 &region, 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]") TEST_CASE("Multiline cubic infill follows the cubic lines without crossing itself", "[Fill]")
{ {
const int multiline = GENERATE(2, 3); const int multiline = GENERATE(2, 3);