From cb422fc500e93487814f63347fda07bc8e6d78ab Mon Sep 17 00:00:00 2001 From: Hanif Koh Date: Wed, 30 Sep 2026 19:17:33 +0800 Subject: [PATCH] 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. --- src/OrcaSlicer.cpp | 41 +++++++++++++++++++--- src/libslic3r/PresetBundle.cpp | 2 +- src/libslic3r/PresetBundle.hpp | 3 ++ tests/cli/test_cli_project_missing_keys.sh | 36 +++++++++++++------ 4 files changed, 67 insertions(+), 15 deletions(-) diff --git a/src/OrcaSlicer.cpp b/src/OrcaSlicer.cpp index 3ed76b8976..d896c6e067 100644 --- a/src/OrcaSlicer.cpp +++ b/src/OrcaSlicer.cpp @@ -2779,6 +2779,37 @@ int CLI::run(int argc, char **argv) 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 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("filament_ids"); + for (size_t index = 0; index < current_filaments_system_name.size(); index++) { + const int slot = static_cast(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 if (fetch_upward_values) { if (!current_printer_system_name.empty()) { @@ -3469,7 +3500,7 @@ int CLI::run(int argc, char **argv) } //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 filament_variant_count(filament_count, 1); std::vector old_start_indice(filament_count, 0); @@ -3528,6 +3559,8 @@ int CLI::run(int argc, char **argv) for (int index = 0; index < load_filaments_config.size(); index++) { DynamicPrintConfig& config = load_filaments_config[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 different_keys; //ORCA: diff before load_default_gcodes_to_config, the way the process and machine @@ -3537,12 +3570,12 @@ int CLI::run(int argc, char **argv) // compared" to "compared as empty against the parent" and land in the column // as an override the user never made. 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); load_default_gcodes_to_config(config, Preset::TYPE_FILAMENT); - if (load_filament_count > 0) { + if (loaded) { ConfigOptionStrings *opt_filament_settings = static_cast (m_print_config.option("filament_settings_id", true)); std::string& filament_name = load_filaments_name[index]; ConfigOptionString* filament_name_setting = new ConfigOptionString(filament_name); @@ -3614,7 +3647,7 @@ int CLI::run(int argc, char **argv) flush_and_exit(CLI_CONFIG_FILE_ERROR); } - if ((load_filament_count == 0) && !different_keys_set.empty()) + if (!loaded && !different_keys_set.empty()) { std::set::iterator iter = different_keys_set.find(opt_key); if ( iter != different_keys_set.end()) { diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 63e3ba6105..418a26aa5c 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -5251,7 +5251,7 @@ static void apply_receiver_mix_relocations(DynamicPrintConfig& //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); if (machine_iter != filament_preset_convert.end()) diff --git a/src/libslic3r/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp index 0db1f6d9b1..815b53139c 100644 --- a/src/libslic3r/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -238,6 +238,9 @@ public: // 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 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(const PresetBundle &rhs); diff --git a/tests/cli/test_cli_project_missing_keys.sh b/tests/cli/test_cli_project_missing_keys.sh index 4c55709ea0..970dface72 100644 --- a/tests/cli/test_cli_project_missing_keys.sh +++ b/tests/cli/test_cli_project_missing_keys.sh @@ -1,11 +1,13 @@ #!/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: # 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. +# Bambu Lab P1S presets with two filaments; one printer key and one process key are removed, one printer +# 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 set -u @@ -45,7 +47,7 @@ slice() { 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" + --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 # listed as different, which must come back with the system value; and a listed key the project keeps. @@ -67,30 +69,44 @@ with zipfile.ZipFile(src) as zin, zipfile.ZipFile(dst, "w", zipfile.ZIP_DEFLATED for key in ("top_shell_layers", "extruder_clearance_height_to_rod"): expected[key] = config[key] 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) 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[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) 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' +check() { + "$PY" - "$WORK/$1/out.3mf" "$WORK/old.3mf.expected.json" "$1" <<'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] +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: print("FAIL: " + e) sys.exit(1 if errors else 0) EOF -status=$? -[ "$status" -eq 0 ] || { tail -n 40 "$WORK/project/log"; exit 1; } + [ $? -eq 0 ] || { tail -n 40 "$WORK/$1/log"; exit 1; } +} + +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"