mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-11 11:07:40 +00:00
Keep mixed filaments across app restarts
This commit is contained in:
@@ -2716,28 +2716,53 @@ void PresetBundle::load_installed_sla_materials(AppConfig &config)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Mixed-color filament metadata is project state, carried in the 3mf's project_settings.config.
|
// 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
|
// BambuStudio also snapshots it in the app config so the last session's mixes are back before any
|
||||||
// the last session's mixes are there before any project is opened; a project load then overwrites
|
// project is opened; there the filament list itself is a single global snapshot, so the mixed
|
||||||
// them through s_project_options. It is deliberately not a per-printer snapshot: the component ids
|
// arrays live next to it in the global "presets" section. Orca's per-printer preset memory instead
|
||||||
// in filament_mixed_components are 1-based indices into the project's filament list, so re-applying
|
// rebuilds the filament list from the selected printer's snapshot (filament_%02u/filament_colors)
|
||||||
// a printer's copy on every printer change would silently replace a loaded project's mixes.
|
// on startup AND on every printer selection — so the mixed arrays, whose component ids are 1-based
|
||||||
// Mirrors PresetBundle::load_selections in BambuStudio.
|
// indices into exactly that list, must live in the same per-printer snapshot or they end up
|
||||||
static void load_mixed_filament_settings(DynamicPrintConfig &project_config, const AppConfig &config, size_t n_filaments)
|
// describing a list they were never saved against (and previously got reset on every printer
|
||||||
|
// select, losing the mixes over a restart).
|
||||||
|
// Missing keys clear the arrays: a printer with no stored mixes must not inherit another's.
|
||||||
|
// fallback_to_global additionally reads the legacy shared "presets" keys (the old format) so a
|
||||||
|
// config saved by an earlier build still restores at startup; export_selections clears that
|
||||||
|
// section on the next save.
|
||||||
|
static void load_mixed_filament_settings(DynamicPrintConfig &project_config, AppConfig &config,
|
||||||
|
const std::string &printer_name, size_t n_filaments,
|
||||||
|
bool fallback_to_global)
|
||||||
{
|
{
|
||||||
|
auto raw_value = [&](const char *key, bool &found) -> std::string {
|
||||||
|
if (config.has_printer_setting(printer_name, key)) {
|
||||||
|
found = true;
|
||||||
|
return config.get_printer_setting(printer_name, key);
|
||||||
|
}
|
||||||
|
if (fallback_to_global && config.has("presets", key)) {
|
||||||
|
found = true;
|
||||||
|
return config.get("presets", key);
|
||||||
|
}
|
||||||
|
found = false;
|
||||||
|
return std::string{};
|
||||||
|
};
|
||||||
std::vector<std::string> parts;
|
std::vector<std::string> parts;
|
||||||
auto load_bools = [&](const char *key) {
|
auto load_bools = [&](const char *key) {
|
||||||
auto &vals = project_config.option<ConfigOptionBools>(key)->values;
|
auto &vals = project_config.option<ConfigOptionBools>(key)->values;
|
||||||
if (config.has("presets", key)) {
|
vals.clear();
|
||||||
boost::algorithm::split(parts, config.get("presets", key), boost::algorithm::is_any_of(","));
|
bool found = false;
|
||||||
vals.clear();
|
const std::string s = raw_value(key, found);
|
||||||
|
if (found && !s.empty()) {
|
||||||
|
boost::algorithm::split(parts, s, boost::algorithm::is_any_of(","));
|
||||||
for (const auto &p : parts) vals.push_back(p == "1");
|
for (const auto &p : parts) vals.push_back(p == "1");
|
||||||
}
|
}
|
||||||
vals.resize(n_filaments, false);
|
vals.resize(n_filaments, false);
|
||||||
};
|
};
|
||||||
auto load_strings = [&](const char *key) {
|
auto load_strings = [&](const char *key) {
|
||||||
auto &vals = project_config.option<ConfigOptionStrings>(key)->values;
|
auto &vals = project_config.option<ConfigOptionStrings>(key)->values;
|
||||||
if (config.has("presets", key)) {
|
vals.clear();
|
||||||
boost::algorithm::split(parts, config.get("presets", key), boost::algorithm::is_any_of("|"));
|
bool found = false;
|
||||||
|
const std::string s = raw_value(key, found);
|
||||||
|
if (found && !s.empty()) {
|
||||||
|
boost::algorithm::split(parts, s, boost::algorithm::is_any_of("|"));
|
||||||
vals = parts;
|
vals = parts;
|
||||||
}
|
}
|
||||||
vals.resize(n_filaments, std::string{});
|
vals.resize(n_filaments, std::string{});
|
||||||
@@ -2754,9 +2779,12 @@ static void load_mixed_filament_settings(DynamicPrintConfig &project_config, con
|
|||||||
// control points), so it is stored C-style escaped rather than '|'-joined.
|
// control points), so it is stored C-style escaped rather than '|'-joined.
|
||||||
{
|
{
|
||||||
auto &vals = project_config.option<ConfigOptionStrings>("filament_mixed_gradient_curve")->values;
|
auto &vals = project_config.option<ConfigOptionStrings>("filament_mixed_gradient_curve")->values;
|
||||||
if (config.has("presets", "filament_mixed_gradient_curve")) {
|
vals.clear();
|
||||||
|
bool found = false;
|
||||||
|
const std::string s = raw_value("filament_mixed_gradient_curve", found);
|
||||||
|
if (found && !s.empty()) {
|
||||||
std::vector<std::string> curves;
|
std::vector<std::string> curves;
|
||||||
if (unescape_strings_cstyle(config.get("presets", "filament_mixed_gradient_curve"), curves))
|
if (unescape_strings_cstyle(s, curves))
|
||||||
vals = std::move(curves);
|
vals = std::move(curves);
|
||||||
}
|
}
|
||||||
vals.resize(n_filaments, std::string{});
|
vals.resize(n_filaments, std::string{});
|
||||||
@@ -2766,30 +2794,6 @@ static void load_mixed_filament_settings(DynamicPrintConfig &project_config, con
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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)
|
void PresetBundle::update_selections(AppConfig &config)
|
||||||
{
|
{
|
||||||
std::string initial_printer_profile_name = printers.get_selected_preset_name();
|
std::string initial_printer_profile_name = printers.get_selected_preset_name();
|
||||||
@@ -2870,7 +2874,9 @@ void PresetBundle::update_selections(AppConfig &config)
|
|||||||
auto flush_multipliers = matrix | boost::adaptors::transformed(boost::lexical_cast<double, std::string>);
|
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());
|
project_config.option<ConfigOptionFloats>("flush_multiplier")->values = std::vector<double>(flush_multipliers.begin(), flush_multipliers.end());
|
||||||
}
|
}
|
||||||
reset_mixed_filament_settings(project_config, filament_presets.size());
|
// No global fallback here: on a printer change the legacy shared keys describe another
|
||||||
|
// printer's filament list, so absent per-printer keys must clear the mixes, not revive them.
|
||||||
|
load_mixed_filament_settings(project_config, config, initial_printer_profile_name, filament_presets.size(), false);
|
||||||
|
|
||||||
// Update visibility of presets based on their compatibility with the active printer.
|
// 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,
|
// Always try to select a compatible print and filament preset to the current printer preset,
|
||||||
@@ -3021,7 +3027,7 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p
|
|||||||
auto flush_multipliers = matrix | boost::adaptors::transformed(boost::lexical_cast<double, std::string>);
|
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());
|
project_config.option<ConfigOptionFloats>("flush_multiplier")->values = std::vector<double>(flush_multipliers.begin(), flush_multipliers.end());
|
||||||
}
|
}
|
||||||
load_mixed_filament_settings(project_config, config, filament_presets.size());
|
load_mixed_filament_settings(project_config, config, initial_printer_profile_name, filament_presets.size(), true);
|
||||||
|
|
||||||
// Update visibility of presets based on their compatibility with the active printer.
|
// 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,
|
// Always try to select a compatible print and filament preset to the current printer preset,
|
||||||
@@ -3156,11 +3162,12 @@ void PresetBundle::export_selections(AppConfig &config)
|
|||||||
"|");
|
"|");
|
||||||
config.set_printer_setting(printer_name, "flush_multiplier", flush_multiplier_str);
|
config.set_printer_setting(printer_name, "flush_multiplier", flush_multiplier_str);
|
||||||
|
|
||||||
// Mixed-color filament metadata: a single global snapshot, restored by load_selections at
|
// Mixed-color filament metadata: stored in the per-printer snapshot next to the filament
|
||||||
// startup (see the comment there). Written to the shared "presets" section rather than to this
|
// list it indexes (filament_%02u / filament_colors), so each printer's remembered config
|
||||||
// printer's settings on purpose — a per-printer copy is re-applied on every printer change and
|
// round-trips its own mixes and re-applying a snapshot never leaves the arrays describing a
|
||||||
// replaces a loaded project's mixes. Bools are ','-joined; the component/ratio/range strings
|
// different list (see load_mixed_filament_settings). Bools are ','-joined; the
|
||||||
// are '|'-joined; the gradient curve is escaped instead, because its values contain '|'.
|
// 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) {
|
auto join_bools = [](const std::vector<unsigned char> &vals) {
|
||||||
std::string s;
|
std::string s;
|
||||||
for (size_t i = 0; i < vals.size(); ++i) {
|
for (size_t i = 0; i < vals.size(); ++i) {
|
||||||
@@ -3170,19 +3177,19 @@ void PresetBundle::export_selections(AppConfig &config)
|
|||||||
return s;
|
return s;
|
||||||
};
|
};
|
||||||
if (auto *opt = project_config.option<ConfigOptionBools>("filament_is_mixed"))
|
if (auto *opt = project_config.option<ConfigOptionBools>("filament_is_mixed"))
|
||||||
config.set("presets", "filament_is_mixed", join_bools(opt->values));
|
config.set_printer_setting(printer_name, "filament_is_mixed", join_bools(opt->values));
|
||||||
if (auto *opt = project_config.option<ConfigOptionStrings>("filament_mixed_components"))
|
if (auto *opt = project_config.option<ConfigOptionStrings>("filament_mixed_components"))
|
||||||
config.set("presets", "filament_mixed_components", boost::algorithm::join(opt->values, "|"));
|
config.set_printer_setting(printer_name, "filament_mixed_components", boost::algorithm::join(opt->values, "|"));
|
||||||
if (auto *opt = project_config.option<ConfigOptionStrings>("filament_mixed_sublayer_ratios"))
|
if (auto *opt = project_config.option<ConfigOptionStrings>("filament_mixed_sublayer_ratios"))
|
||||||
config.set("presets", "filament_mixed_sublayer_ratios", boost::algorithm::join(opt->values, "|"));
|
config.set_printer_setting(printer_name, "filament_mixed_sublayer_ratios", boost::algorithm::join(opt->values, "|"));
|
||||||
if (auto *opt = project_config.option<ConfigOptionBools>("filament_mixed_gradient"))
|
if (auto *opt = project_config.option<ConfigOptionBools>("filament_mixed_gradient"))
|
||||||
config.set("presets", "filament_mixed_gradient", join_bools(opt->values));
|
config.set_printer_setting(printer_name, "filament_mixed_gradient", join_bools(opt->values));
|
||||||
if (auto *opt = project_config.option<ConfigOptionStrings>("filament_mixed_gradient_range"))
|
if (auto *opt = project_config.option<ConfigOptionStrings>("filament_mixed_gradient_range"))
|
||||||
config.set("presets", "filament_mixed_gradient_range", boost::algorithm::join(opt->values, "|"));
|
config.set_printer_setting(printer_name, "filament_mixed_gradient_range", boost::algorithm::join(opt->values, "|"));
|
||||||
if (auto *opt = project_config.option<ConfigOptionStrings>("filament_mixed_gradient_curve"))
|
if (auto *opt = project_config.option<ConfigOptionStrings>("filament_mixed_gradient_curve"))
|
||||||
config.set("presets", "filament_mixed_gradient_curve", escape_strings_cstyle(opt->values));
|
config.set_printer_setting(printer_name, "filament_mixed_gradient_curve", escape_strings_cstyle(opt->values));
|
||||||
if (auto *opt = project_config.option<ConfigOptionBools>("filament_mixed_gradient_per_part"))
|
if (auto *opt = project_config.option<ConfigOptionBools>("filament_mixed_gradient_per_part"))
|
||||||
config.set("presets", "filament_mixed_gradient_per_part", join_bools(opt->values));
|
config.set_printer_setting(printer_name, "filament_mixed_gradient_per_part", join_bools(opt->values));
|
||||||
|
|
||||||
// BBS
|
// BBS
|
||||||
//config.set("presets", "sla_print", sla_prints.get_selected_preset_name());
|
//config.set("presets", "sla_print", sla_prints.get_selected_preset_name());
|
||||||
|
|||||||
@@ -614,13 +614,13 @@ TEST_CASE("set_num_filaments keeps mixed-color arrays in step with the filament
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A mix is described by 1-based indices into the project's filament list, so it is only meaningful
|
// A mix is described by 1-based indices into the project's filament list. Orca's per-printer
|
||||||
// alongside that list. As in BambuStudio the app-config snapshot is global — one "last session"
|
// preset memory rebuilds that list from the selected printer's snapshot (filament_%02u /
|
||||||
// copy under the shared "presets" section, restored at startup only. A PER-PRINTER copy would be
|
// filament_colors) at startup and on every printer selection, so the mixed arrays must be stored
|
||||||
// re-applied on every printer change and would replace a loaded project's mixes with whatever
|
// in the SAME per-printer snapshot: kept globally (as BambuStudio does — its filament list is a
|
||||||
// snapshot that printer last held, which also shrinks the filament count and makes reload_scene
|
// single global snapshot too) they end up indexing a list they were never saved against, and used
|
||||||
// strip painted facets above it.
|
// to be reset on every printer selection instead, losing the mixes over an app restart.
|
||||||
TEST_CASE("Mixed-color filament metadata is snapshotted globally, never per printer", "[Preset][Bundle][FilamentMixer]")
|
TEST_CASE("Mixed-color filament metadata is snapshotted per printer, with its filament list", "[Preset][Bundle][FilamentMixer]")
|
||||||
{
|
{
|
||||||
PresetBundle bundle;
|
PresetBundle bundle;
|
||||||
// export_selections skips the built-in "Default Printer" placeholder entirely.
|
// export_selections skips the built-in "Default Printer" placeholder entirely.
|
||||||
@@ -636,16 +636,16 @@ TEST_CASE("Mixed-color filament metadata is snapshotted globally, never per prin
|
|||||||
|
|
||||||
const std::string printer_name = bundle.printers.get_selected_preset_name();
|
const std::string printer_name = bundle.printers.get_selected_preset_name();
|
||||||
for (const char *key : kMixedKeys) {
|
for (const char *key : kMixedKeys) {
|
||||||
DYNAMIC_SECTION("global, not per printer: " << key) {
|
DYNAMIC_SECTION("per printer, not global: " << key) {
|
||||||
CHECK(app_config.has("presets", key));
|
CHECK(app_config.has_printer_setting(printer_name, key));
|
||||||
CHECK_FALSE(app_config.has_printer_setting(printer_name, key));
|
CHECK_FALSE(app_config.has("presets", key));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("with the encoding load_selections reads back") {
|
SECTION("with the encoding load_selections reads back") {
|
||||||
CHECK(app_config.get("presets", "filament_is_mixed") == "0,1");
|
CHECK(app_config.get_printer_setting(printer_name, "filament_is_mixed") == "0,1");
|
||||||
CHECK(app_config.get("presets", "filament_mixed_components") == "|1,2");
|
CHECK(app_config.get_printer_setting(printer_name, "filament_mixed_components") == "|1,2");
|
||||||
CHECK(app_config.get("presets", "filament_mixed_sublayer_ratios") == "|0.5,0.5");
|
CHECK(app_config.get_printer_setting(printer_name, "filament_mixed_sublayer_ratios") == "|0.5,0.5");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -668,7 +668,8 @@ TEST_CASE("A multi-point gradient curve survives the app-config snapshot", "[Pre
|
|||||||
// Decoding the stored form returns the three slots intact, curve delimiters and all. A plain
|
// 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.
|
// '|' join would decode as five slots here instead of three.
|
||||||
std::vector<std::string> decoded;
|
std::vector<std::string> decoded;
|
||||||
REQUIRE(unescape_strings_cstyle(app_config.get("presets", "filament_mixed_gradient_curve"), decoded));
|
REQUIRE(unescape_strings_cstyle(
|
||||||
|
app_config.get_printer_setting(bundle.printers.get_selected_preset_name(), "filament_mixed_gradient_curve"), decoded));
|
||||||
CHECK(decoded == curves);
|
CHECK(decoded == curves);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user