mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-08-24 18:37:39 +00:00
Import project
This commit is contained in:
@@ -2715,38 +2715,76 @@ void PresetBundle::load_installed_sla_materials(AppConfig &config)
|
||||
preset.set_visible_from_appconfig(config);
|
||||
}
|
||||
|
||||
// Restore the mixed-color filament metadata written by export_selections(). Every array is
|
||||
// resized to the filament count so a project saved with a different filament count, or one
|
||||
// predating these keys, still yields well-formed parallel arrays.
|
||||
static void load_mixed_filament_settings(DynamicPrintConfig &project_config, AppConfig &config,
|
||||
const std::string &printer_name, size_t n_filaments)
|
||||
// Mixed-color filament metadata is project state, carried in the 3mf's project_settings.config.
|
||||
// As in BambuStudio it also gets a single GLOBAL app-config snapshot, restored once at startup so
|
||||
// the last session's mixes are there before any project is opened; a project load then overwrites
|
||||
// them through s_project_options. It is deliberately not a per-printer snapshot: the component ids
|
||||
// in filament_mixed_components are 1-based indices into the project's filament list, so re-applying
|
||||
// a printer's copy on every printer change would silently replace a loaded project's mixes.
|
||||
// Mirrors PresetBundle::load_selections in BambuStudio.
|
||||
static void load_mixed_filament_settings(DynamicPrintConfig &project_config, const AppConfig &config, size_t n_filaments)
|
||||
{
|
||||
std::vector<std::string> parts;
|
||||
auto load_bools = [&](const char *key, const char *opt_key) {
|
||||
auto &vals = project_config.option<ConfigOptionBools>(opt_key)->values;
|
||||
if (config.has_printer_setting(printer_name, key)) {
|
||||
boost::algorithm::split(parts, config.get_printer_setting(printer_name, key), boost::algorithm::is_any_of(","));
|
||||
auto load_bools = [&](const char *key) {
|
||||
auto &vals = project_config.option<ConfigOptionBools>(key)->values;
|
||||
if (config.has("presets", key)) {
|
||||
boost::algorithm::split(parts, config.get("presets", key), boost::algorithm::is_any_of(","));
|
||||
vals.clear();
|
||||
for (const auto &p : parts) vals.push_back(p == "1");
|
||||
}
|
||||
vals.resize(n_filaments, false);
|
||||
};
|
||||
auto load_strings = [&](const char *key, const char *opt_key) {
|
||||
auto &vals = project_config.option<ConfigOptionStrings>(opt_key)->values;
|
||||
if (config.has_printer_setting(printer_name, key)) {
|
||||
boost::algorithm::split(parts, config.get_printer_setting(printer_name, key), boost::algorithm::is_any_of("|"));
|
||||
auto load_strings = [&](const char *key) {
|
||||
auto &vals = project_config.option<ConfigOptionStrings>(key)->values;
|
||||
if (config.has("presets", key)) {
|
||||
boost::algorithm::split(parts, config.get("presets", key), boost::algorithm::is_any_of("|"));
|
||||
vals = parts;
|
||||
}
|
||||
vals.resize(n_filaments, std::string{});
|
||||
};
|
||||
|
||||
load_bools("filament_is_mixed", "filament_is_mixed");
|
||||
load_strings("filament_mixed_components", "filament_mixed_components");
|
||||
load_strings("filament_mixed_sublayer_ratios", "filament_mixed_sublayer_ratios");
|
||||
load_bools("filament_mixed_gradient", "filament_mixed_gradient");
|
||||
load_strings("filament_mixed_gradient_range", "filament_mixed_gradient_range");
|
||||
load_strings("filament_mixed_gradient_curve", "filament_mixed_gradient_curve");
|
||||
load_bools("filament_mixed_gradient_per_part", "filament_mixed_gradient_per_part");
|
||||
load_bools("filament_is_mixed");
|
||||
load_strings("filament_mixed_components");
|
||||
load_strings("filament_mixed_sublayer_ratios");
|
||||
load_bools("filament_mixed_gradient");
|
||||
load_strings("filament_mixed_gradient_range");
|
||||
load_bools("filament_mixed_gradient_per_part");
|
||||
|
||||
// The gradient curve is the one array whose values contain '|' themselves (it separates the
|
||||
// control points), so it is stored C-style escaped rather than '|'-joined.
|
||||
{
|
||||
auto &vals = project_config.option<ConfigOptionStrings>("filament_mixed_gradient_curve")->values;
|
||||
if (config.has("presets", "filament_mixed_gradient_curve")) {
|
||||
std::vector<std::string> curves;
|
||||
if (unescape_strings_cstyle(config.get("presets", "filament_mixed_gradient_curve"), curves))
|
||||
vals = std::move(curves);
|
||||
}
|
||||
vals.resize(n_filaments, std::string{});
|
||||
}
|
||||
}
|
||||
|
||||
// Orca's per-printer preset memory (update_selections, which BambuStudio has no equivalent of)
|
||||
// rebuilds the filament list wholesale from that printer's snapshot, presets and colours included.
|
||||
// Any existing mix then describes filaments that are no longer there, so clear the arrays and size
|
||||
// them to the new filament count rather than carrying stale component indices across.
|
||||
static void reset_mixed_filament_settings(DynamicPrintConfig &project_config, size_t n_filaments)
|
||||
{
|
||||
auto reset_bools = [&](const char *opt_key) {
|
||||
auto &vals = project_config.option<ConfigOptionBools>(opt_key)->values;
|
||||
vals.assign(n_filaments, false);
|
||||
};
|
||||
auto reset_strings = [&](const char *opt_key) {
|
||||
auto &vals = project_config.option<ConfigOptionStrings>(opt_key)->values;
|
||||
vals.assign(n_filaments, std::string{});
|
||||
};
|
||||
|
||||
reset_bools("filament_is_mixed");
|
||||
reset_strings("filament_mixed_components");
|
||||
reset_strings("filament_mixed_sublayer_ratios");
|
||||
reset_bools("filament_mixed_gradient");
|
||||
reset_strings("filament_mixed_gradient_range");
|
||||
reset_strings("filament_mixed_gradient_curve");
|
||||
reset_bools("filament_mixed_gradient_per_part");
|
||||
}
|
||||
|
||||
void PresetBundle::update_selections(AppConfig &config)
|
||||
@@ -2829,7 +2867,7 @@ void PresetBundle::update_selections(AppConfig &config)
|
||||
auto flush_multipliers = matrix | boost::adaptors::transformed(boost::lexical_cast<double, std::string>);
|
||||
project_config.option<ConfigOptionFloats>("flush_multiplier")->values = std::vector<double>(flush_multipliers.begin(), flush_multipliers.end());
|
||||
}
|
||||
load_mixed_filament_settings(project_config, config, initial_printer_profile_name, filament_presets.size());
|
||||
reset_mixed_filament_settings(project_config, filament_presets.size());
|
||||
|
||||
// Update visibility of presets based on their compatibility with the active printer.
|
||||
// Always try to select a compatible print and filament preset to the current printer preset,
|
||||
@@ -2980,7 +3018,7 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p
|
||||
auto flush_multipliers = matrix | boost::adaptors::transformed(boost::lexical_cast<double, std::string>);
|
||||
project_config.option<ConfigOptionFloats>("flush_multiplier")->values = std::vector<double>(flush_multipliers.begin(), flush_multipliers.end());
|
||||
}
|
||||
load_mixed_filament_settings(project_config, config, initial_printer_profile_name, filament_presets.size());
|
||||
load_mixed_filament_settings(project_config, config, filament_presets.size());
|
||||
|
||||
// Update visibility of presets based on their compatibility with the active printer.
|
||||
// Always try to select a compatible print and filament preset to the current printer preset,
|
||||
@@ -3115,8 +3153,11 @@ void PresetBundle::export_selections(AppConfig &config)
|
||||
"|");
|
||||
config.set_printer_setting(printer_name, "flush_multiplier", flush_multiplier_str);
|
||||
|
||||
// Mixed-color filament metadata. Bools are joined with ',' and strings with '|' because
|
||||
// the component/ratio/curve strings themselves contain commas.
|
||||
// Mixed-color filament metadata: a single global snapshot, restored by load_selections at
|
||||
// startup (see the comment there). Written to the shared "presets" section rather than to this
|
||||
// printer's settings on purpose — a per-printer copy is re-applied on every printer change and
|
||||
// replaces a loaded project's mixes. Bools are ','-joined; the component/ratio/range strings
|
||||
// are '|'-joined; the gradient curve is escaped instead, because its values contain '|'.
|
||||
auto join_bools = [](const std::vector<unsigned char> &vals) {
|
||||
std::string s;
|
||||
for (size_t i = 0; i < vals.size(); ++i) {
|
||||
@@ -3126,19 +3167,19 @@ void PresetBundle::export_selections(AppConfig &config)
|
||||
return s;
|
||||
};
|
||||
if (auto *opt = project_config.option<ConfigOptionBools>("filament_is_mixed"))
|
||||
config.set_printer_setting(printer_name, "filament_is_mixed", join_bools(opt->values));
|
||||
config.set("presets", "filament_is_mixed", join_bools(opt->values));
|
||||
if (auto *opt = project_config.option<ConfigOptionStrings>("filament_mixed_components"))
|
||||
config.set_printer_setting(printer_name, "filament_mixed_components", boost::algorithm::join(opt->values, "|"));
|
||||
config.set("presets", "filament_mixed_components", boost::algorithm::join(opt->values, "|"));
|
||||
if (auto *opt = project_config.option<ConfigOptionStrings>("filament_mixed_sublayer_ratios"))
|
||||
config.set_printer_setting(printer_name, "filament_mixed_sublayer_ratios", boost::algorithm::join(opt->values, "|"));
|
||||
config.set("presets", "filament_mixed_sublayer_ratios", boost::algorithm::join(opt->values, "|"));
|
||||
if (auto *opt = project_config.option<ConfigOptionBools>("filament_mixed_gradient"))
|
||||
config.set_printer_setting(printer_name, "filament_mixed_gradient", join_bools(opt->values));
|
||||
config.set("presets", "filament_mixed_gradient", join_bools(opt->values));
|
||||
if (auto *opt = project_config.option<ConfigOptionStrings>("filament_mixed_gradient_range"))
|
||||
config.set_printer_setting(printer_name, "filament_mixed_gradient_range", boost::algorithm::join(opt->values, "|"));
|
||||
config.set("presets", "filament_mixed_gradient_range", boost::algorithm::join(opt->values, "|"));
|
||||
if (auto *opt = project_config.option<ConfigOptionStrings>("filament_mixed_gradient_curve"))
|
||||
config.set_printer_setting(printer_name, "filament_mixed_gradient_curve", boost::algorithm::join(opt->values, "|"));
|
||||
config.set("presets", "filament_mixed_gradient_curve", escape_strings_cstyle(opt->values));
|
||||
if (auto *opt = project_config.option<ConfigOptionBools>("filament_mixed_gradient_per_part"))
|
||||
config.set_printer_setting(printer_name, "filament_mixed_gradient_per_part", join_bools(opt->values));
|
||||
config.set("presets", "filament_mixed_gradient_per_part", join_bools(opt->values));
|
||||
|
||||
// BBS
|
||||
//config.set("presets", "sla_print", sla_prints.get_selected_preset_name());
|
||||
@@ -3361,6 +3402,12 @@ bool PresetBundle::is_mixed_filament(size_t idx) const
|
||||
return opt && idx < opt->values.size() && opt->values[idx];
|
||||
}
|
||||
|
||||
size_t PresetBundle::num_mixed_filaments() const
|
||||
{
|
||||
auto *opt = project_config.option<ConfigOptionBools>("filament_is_mixed");
|
||||
return opt == nullptr ? 0 : size_t(std::count(opt->values.begin(), opt->values.end(), true));
|
||||
}
|
||||
|
||||
std::vector<size_t> PresetBundle::physical_filament_config_indices() const
|
||||
{
|
||||
std::vector<size_t> indices;
|
||||
|
||||
@@ -500,6 +500,9 @@ public:
|
||||
// Mixed-color filament slots: virtual slots realized from 2-3 physical filaments.
|
||||
bool is_mixed_filament(size_t idx) const;
|
||||
std::vector<size_t> physical_filament_config_indices() const;
|
||||
// How many slots are mixed. They sit at the tail of the filament list and have no nozzle of
|
||||
// their own, so any resize driven by the printer's extruder count has to add this on top.
|
||||
size_t num_mixed_filaments() const;
|
||||
|
||||
void on_extruders_count_changed(int extruder_count);
|
||||
|
||||
|
||||
@@ -8905,7 +8905,11 @@ void GUI_App::load_current_presets(bool active_preset_combox/*= false*/, bool ch
|
||||
if (printer_technology == ptFFF && !edited_printer_preset.config.opt_bool("single_extruder_multi_material")) {
|
||||
auto* nozzle_diameter = edited_printer_preset.config.option<ConfigOptionFloats>("nozzle_diameter");
|
||||
if (nozzle_diameter) {
|
||||
preset_bundle->set_num_filaments(nozzle_diameter->values.size());
|
||||
// Mixed-color slots are virtual filaments kept at the tail of the list, so they have no
|
||||
// nozzle of their own. Sizing to the nozzle count alone truncates them away — and this
|
||||
// runs right after a project is loaded, so it would silently drop the project's mixes
|
||||
// and then let update_extruder_count() strip every painted facet above the new count.
|
||||
preset_bundle->set_num_filaments(nozzle_diameter->values.size() + preset_bundle->num_mixed_filaments());
|
||||
}
|
||||
}
|
||||
this->plater()->set_printer_technology(printer_technology);
|
||||
|
||||
@@ -2182,8 +2182,11 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value)
|
||||
std::string new_color = new_col.GetAsString(wxC2S_HTML_SYNTAX).ToStdString();
|
||||
new_colors.push_back(new_color);
|
||||
}
|
||||
wxGetApp().preset_bundle->set_num_filaments(num_extruder, new_colors);
|
||||
wxGetApp().plater()->on_filament_count_change(num_extruder);
|
||||
// Mixed-color slots are virtual filaments at the tail of the list with no nozzle of their
|
||||
// own, so they are carried on top of the new extruder count instead of being truncated.
|
||||
const size_t total_filaments = num_extruder + wxGetApp().preset_bundle->num_mixed_filaments();
|
||||
wxGetApp().preset_bundle->set_num_filaments(total_filaments, new_colors);
|
||||
wxGetApp().plater()->on_filament_count_change(total_filaments);
|
||||
wxGetApp().get_tab(Preset::TYPE_PRINT)->update();
|
||||
wxGetApp().preset_bundle->export_selections(*wxGetApp().app_config);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/TriangleSelector.hpp"
|
||||
#include "libslic3r/Format/3mf.hpp"
|
||||
#include "libslic3r/Format/bbs_3mf.hpp"
|
||||
#include "libslic3r/Format/STL.hpp"
|
||||
@@ -497,3 +498,97 @@ SCENARIO("Nozzle-group metadata .3mf round-trip", "[3mf][MultiNozzle]") {
|
||||
delete plate;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// A mixed-color filament occupies an ordinary filament slot, and painting with it stores an
|
||||
// ordinary extruder state — a project saved by BambuStudio encodes filament 5 of a 5-slot setup
|
||||
// as paint state 5, with the mix described by the parallel filament_mixed_* project arrays. This
|
||||
// pins both halves of that contract at the .3mf layer: the project keys and the painted states
|
||||
// must come back exactly as written.
|
||||
SCENARIO("Mixed-color filament setup and painting round-trip through a .3mf", "[3mf][MixedFilament]") {
|
||||
GIVEN("a painted model whose project config describes a mixed filament in the last slot") {
|
||||
Model model;
|
||||
std::string src_file = std::string(TEST_DATA_DIR) + "/test_3mf/Prusa.stl";
|
||||
REQUIRE(load_stl(src_file.c_str(), &model));
|
||||
model.add_default_instances();
|
||||
|
||||
// Both the exporter and the importer stage Metadata/project_settings.config through the
|
||||
// model's backup path; point them at writable temp dirs.
|
||||
ScopedTemporaryDir backup_dir("orca_mixed_src");
|
||||
model.set_backup_path(backup_dir.string());
|
||||
|
||||
ModelVolume* mv = model.objects.front()->volumes.front();
|
||||
{
|
||||
TriangleSelector selector(mv->mesh());
|
||||
selector.set_facet(0, EnforcerBlockerType::Extruder5); // the mixed slot
|
||||
selector.set_facet(1, EnforcerBlockerType::Extruder2);
|
||||
REQUIRE(mv->mmu_segmentation_facets.set(selector));
|
||||
}
|
||||
|
||||
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
||||
config.set_key_value("filament_colour", new ConfigOptionStrings(
|
||||
{ "#00AE42", "#FFFF00", "#FF0000", "#0000FF", "#FF6A26" }));
|
||||
config.set_key_value("filament_is_mixed", new ConfigOptionBools(
|
||||
{ false, false, false, false, true }));
|
||||
config.set_key_value("filament_mixed_components", new ConfigOptionStrings(
|
||||
{ "", "", "", "", "3,2" }));
|
||||
config.set_key_value("filament_mixed_sublayer_ratios", new ConfigOptionStrings(
|
||||
{ "", "", "", "", "0.4200,0.5800" }));
|
||||
|
||||
WHEN("stored to and reloaded from a .3mf") {
|
||||
ScopedTemporaryFile temp(".3mf");
|
||||
const std::string test_file = temp.string();
|
||||
|
||||
PlateData* plate = new PlateData();
|
||||
plate->plate_index = 0;
|
||||
|
||||
StoreParams store_params;
|
||||
store_params.path = test_file.c_str();
|
||||
store_params.model = &model;
|
||||
store_params.config = &config;
|
||||
store_params.strategy = SaveStrategy::Zip64 | SaveStrategy::Silence;
|
||||
store_params.plate_data_list.push_back(plate);
|
||||
REQUIRE(store_bbs_3mf(store_params));
|
||||
|
||||
Model dst_model;
|
||||
ScopedTemporaryDir dst_backup_dir("orca_mixed_dst");
|
||||
dst_model.set_backup_path(dst_backup_dir.string());
|
||||
DynamicPrintConfig dst_config;
|
||||
ConfigSubstitutionContext ctxt{ ForwardCompatibilitySubstitutionRule::Enable };
|
||||
PlateDataPtrs dst_plates;
|
||||
std::vector<Preset*> project_presets;
|
||||
bool is_bbl_3mf = false, is_orca_3mf = false;
|
||||
Semver file_version;
|
||||
REQUIRE(load_bbs_3mf(test_file.c_str(), &dst_config, &ctxt, &dst_model, &dst_plates,
|
||||
&project_presets, &is_bbl_3mf, &is_orca_3mf, &file_version, nullptr,
|
||||
LoadStrategy::LoadModel | LoadStrategy::LoadConfig));
|
||||
|
||||
THEN("the mixed-filament project keys survive") {
|
||||
auto* is_mixed = dst_config.option<ConfigOptionBools>("filament_is_mixed");
|
||||
REQUIRE(is_mixed != nullptr);
|
||||
REQUIRE(is_mixed->values == std::vector<unsigned char>({ 0, 0, 0, 0, 1 }));
|
||||
|
||||
auto* components = dst_config.option<ConfigOptionStrings>("filament_mixed_components");
|
||||
REQUIRE(components != nullptr);
|
||||
REQUIRE(components->values.size() == 5);
|
||||
REQUIRE(components->values[4] == "3,2");
|
||||
|
||||
auto* ratios = dst_config.option<ConfigOptionStrings>("filament_mixed_sublayer_ratios");
|
||||
REQUIRE(ratios != nullptr);
|
||||
REQUIRE(ratios->values.size() == 5);
|
||||
REQUIRE(ratios->values[4] == "0.4200,0.5800");
|
||||
}
|
||||
|
||||
THEN("the painted facets survive, including the one painted with the mixed slot") {
|
||||
REQUIRE(dst_model.objects.size() == 1);
|
||||
ModelVolume* dst_mv = dst_model.objects.front()->volumes.front();
|
||||
REQUIRE_FALSE(dst_mv->mmu_segmentation_facets.empty());
|
||||
REQUIRE(dst_mv->mmu_segmentation_facets.has_facets(*dst_mv, EnforcerBlockerType::Extruder2));
|
||||
REQUIRE(dst_mv->mmu_segmentation_facets.has_facets(*dst_mv, EnforcerBlockerType::Extruder5));
|
||||
}
|
||||
|
||||
release_PlateData_list(dst_plates);
|
||||
delete plate; // store_bbs_3mf does not take ownership of the source plate
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -567,21 +567,25 @@ TEST_CASE("A printer specific filament supersedes the generic library filament w
|
||||
}
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
const char *kMixedKeys[] = {
|
||||
"filament_is_mixed",
|
||||
"filament_mixed_components",
|
||||
"filament_mixed_sublayer_ratios",
|
||||
"filament_mixed_gradient",
|
||||
"filament_mixed_gradient_range",
|
||||
"filament_mixed_gradient_curve",
|
||||
"filament_mixed_gradient_per_part",
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
// Mixed-color filament metadata lives in project_config as parallel per-filament arrays.
|
||||
// set_num_filaments() is the single place that grows them alongside filament_colour; if it
|
||||
// misses them, creating a mixed slot writes past the end of the short arrays.
|
||||
TEST_CASE("set_num_filaments keeps mixed-color arrays in step with the filament count", "[Preset][Bundle][FilamentMixer]")
|
||||
{
|
||||
static const char *kMixedKeys[] = {
|
||||
"filament_is_mixed",
|
||||
"filament_mixed_components",
|
||||
"filament_mixed_sublayer_ratios",
|
||||
"filament_mixed_gradient",
|
||||
"filament_mixed_gradient_range",
|
||||
"filament_mixed_gradient_curve",
|
||||
"filament_mixed_gradient_per_part",
|
||||
};
|
||||
|
||||
auto mixed_array_size = [](const DynamicPrintConfig &cfg, const std::string &key) -> size_t {
|
||||
if (const auto *b = cfg.option<ConfigOptionBools>(key))
|
||||
return b->values.size();
|
||||
@@ -609,3 +613,96 @@ TEST_CASE("set_num_filaments keeps mixed-color arrays in step with the filament
|
||||
CHECK(mixed_array_size(bundle.project_config, key) == 1);
|
||||
}
|
||||
}
|
||||
|
||||
// A mix is described by 1-based indices into the project's filament list, so it is only meaningful
|
||||
// alongside that list. As in BambuStudio the app-config snapshot is global — one "last session"
|
||||
// copy under the shared "presets" section, restored at startup only. A PER-PRINTER copy would be
|
||||
// re-applied on every printer change and would replace a loaded project's mixes with whatever
|
||||
// snapshot that printer last held, which also shrinks the filament count and makes reload_scene
|
||||
// strip painted facets above it.
|
||||
TEST_CASE("Mixed-color filament metadata is snapshotted globally, never per printer", "[Preset][Bundle][FilamentMixer]")
|
||||
{
|
||||
PresetBundle bundle;
|
||||
// export_selections skips the built-in "Default Printer" placeholder entirely.
|
||||
add_inmemory_preset(bundle.printers, "Test Printer");
|
||||
bundle.printers.select_preset_by_name("Test Printer", true);
|
||||
bundle.set_num_filaments(2u, std::string("#FF0000"));
|
||||
bundle.project_config.option<ConfigOptionBools>("filament_is_mixed")->values = { false, true };
|
||||
bundle.project_config.option<ConfigOptionStrings>("filament_mixed_components")->values = { "", "1,2" };
|
||||
bundle.project_config.option<ConfigOptionStrings>("filament_mixed_sublayer_ratios")->values = { "", "0.5,0.5" };
|
||||
|
||||
AppConfig app_config;
|
||||
bundle.export_selections(app_config);
|
||||
|
||||
const std::string printer_name = bundle.printers.get_selected_preset_name();
|
||||
for (const char *key : kMixedKeys) {
|
||||
DYNAMIC_SECTION("global, not per printer: " << key) {
|
||||
CHECK(app_config.has("presets", key));
|
||||
CHECK_FALSE(app_config.has_printer_setting(printer_name, key));
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("with the encoding load_selections reads back") {
|
||||
CHECK(app_config.get("presets", "filament_is_mixed") == "0,1");
|
||||
CHECK(app_config.get("presets", "filament_mixed_components") == "|1,2");
|
||||
CHECK(app_config.get("presets", "filament_mixed_sublayer_ratios") == "|0.5,0.5");
|
||||
}
|
||||
}
|
||||
|
||||
// The gradient curve is the one mixed array whose values contain '|' themselves — it separates the
|
||||
// control points — so it cannot be '|'-joined into the app config like its siblings without a
|
||||
// multi-point curve being split across filament slots on the way back in.
|
||||
TEST_CASE("A multi-point gradient curve survives the app-config snapshot", "[Preset][Bundle][FilamentMixer]")
|
||||
{
|
||||
const std::vector<std::string> curves = { "", "", "0,0|0.5,0.3|1,1" };
|
||||
|
||||
PresetBundle bundle;
|
||||
add_inmemory_preset(bundle.printers, "Test Printer");
|
||||
bundle.printers.select_preset_by_name("Test Printer", true);
|
||||
bundle.set_num_filaments(3u, std::string("#FF0000"));
|
||||
bundle.project_config.option<ConfigOptionStrings>("filament_mixed_gradient_curve")->values = curves;
|
||||
|
||||
AppConfig app_config;
|
||||
bundle.export_selections(app_config);
|
||||
|
||||
// Decoding the stored form returns the three slots intact, curve delimiters and all. A plain
|
||||
// '|' join would decode as five slots here instead of three.
|
||||
std::vector<std::string> decoded;
|
||||
REQUIRE(unescape_strings_cstyle(app_config.get("presets", "filament_mixed_gradient_curve"), decoded));
|
||||
CHECK(decoded == curves);
|
||||
}
|
||||
|
||||
// A multi-tool printer sizes the filament list from its nozzle count. Mixed-color slots are extra
|
||||
// virtual filaments at the tail of that list with no nozzle of their own, so the sync has to add
|
||||
// them on top. Sizing to the nozzle count alone truncates them — and because that sync runs right
|
||||
// after a project is loaded, it silently drops the project's mixes and then lets the filament-count
|
||||
// change strip every painted facet above the new count.
|
||||
TEST_CASE("Sizing the filament list to a multi-tool nozzle count keeps mixed slots", "[Preset][Bundle][FilamentMixer]")
|
||||
{
|
||||
// The 5-slot layout of a 4-tool project carrying one mix of filaments 2 and 3.
|
||||
const size_t nozzle_count = 4;
|
||||
PresetBundle bundle;
|
||||
bundle.set_num_filaments(5u, std::string("#FF0000"));
|
||||
bundle.project_config.option<ConfigOptionBools>("filament_is_mixed")->values =
|
||||
{ false, false, false, false, true };
|
||||
bundle.project_config.option<ConfigOptionStrings>("filament_mixed_components")->values =
|
||||
{ "", "", "", "", "2,3" };
|
||||
|
||||
REQUIRE(bundle.num_mixed_filaments() == 1);
|
||||
|
||||
SECTION("nozzle count plus the mixed slots preserves the mix") {
|
||||
bundle.set_num_filaments(nozzle_count + bundle.num_mixed_filaments(), std::string("#00FF00"));
|
||||
|
||||
CHECK(bundle.filament_presets.size() == 5);
|
||||
CHECK(bundle.num_mixed_filaments() == 1);
|
||||
CHECK(bundle.is_mixed_filament(4));
|
||||
CHECK(bundle.project_config.option<ConfigOptionStrings>("filament_mixed_components")->values[4] == "2,3");
|
||||
}
|
||||
|
||||
SECTION("the nozzle count alone is what truncated it away") {
|
||||
bundle.set_num_filaments(nozzle_count, std::string("#00FF00"));
|
||||
|
||||
CHECK(bundle.filament_presets.size() == nozzle_count);
|
||||
CHECK(bundle.num_mixed_filaments() == 0);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user