CLI: record command-line overrides in different_settings_to_system (#15642)

* CLI: record command-line overrides in different_settings_to_system

Settings passed on the command line (--sparse-infill-density 25% ...) override
the loaded presets when m_extra_config is applied to m_print_config, but nothing
recorded them in different_settings_to_system. The exported project therefore
carried the new value with no mark that it was modified, and re-opening it in the
GUI reverted it to the system preset's value -- the same failure the preset-leaf
diff fixes for user presets, via a different source of override.

The key set comes from m_config, not m_extra_config. read_cli() puts only what the
user typed into m_config and setup() adds nothing but CLI-own defaults (none of
the keys run() materialises there is a preset option), whereas the CLI writes its
own values into m_extra_config (has_filament_switcher, filament_colour,
filament_map ...), which must not be reported as user overrides.

Values are snapshotted just before the apply and only keys the override actually
changed are recorded: a typed value equal to the loaded one modifies nothing, and
listing it would read as a spurious difference against what the GUI writes. Each
key lands in the column(s) whose preset type owns it -- process, every filament,
printer -- and a key already present is not duplicated. Keys no preset owns
(curr_bed_type, a project setting) land nowhere, as in the GUI.

Follow-up to #15595, split out at review.

* CLI: judge command-line overrides the way the value is read

Review follow-ups on the override recording:

- Lists were compared as whole serialized strings. read_cli() builds a fresh
  one-entry list, so --nozzle-temperature 245 against 245,245,245 on a
  three-filament project was recorded in every filament column although nothing
  changed. Lists are now compared entry by entry with a missing entry read as the
  first, as get_at() reads it (and as resize() pads).

- The log line fired for every changed key, including ones no preset owns
  (curr_bed_type) and which therefore land in no column. It now fires only when a
  column took the key.

- m_print_config.has(key) straight after apply(m_extra_config, true) was always
  true, both configs sharing print_config_def; removed. columns.size() >= 2 also
  always holds after the resize to filament_count + 2 -- different_settings_to_system
  is not a CLI option, so nothing in between can shrink it -- but that rests on code
  far away, so it stays a plain check rather than an assert: release builds compile
  asserts out, and a _GLIBCXX_ASSERTIONS build would abort on columns[0].

Deliberately NOT done: comparing a key the loaded config lacks against its
built-in default. On reopen the GUI restores an unlisted key from the SYSTEM
preset, not the default. A 3MF written before an option existed leaves it absent
here, so --sparse-infill-density 20% (the default) against a Prusa system 15% would
go unrecorded and be reverted to 15%. Absent keys stay always-recorded:
over-recording is cosmetic, under-recording loses the value. Verified that such a
key really is absent at this point, rather than filled from the system preset.

Reported by HanifKoh and raistlin7447 in review of #15642.
This commit is contained in:
packerlschupfer
2026-09-14 05:37:05 +02:00
committed by GitHub
parent fd63164268
commit c5b152b722

View File

@@ -3846,9 +3846,94 @@ int CLI::run(int argc, char **argv)
}
}
//ORCA: settings passed on the command line (--sparse-infill-density 25% ...) override the loaded
// presets right here, so they belong in different_settings_to_system just as a preset
// override does. Without them re-opening the exported project in the GUI shows nothing
// modified and reverts those values to the system presets'.
//
// The keys come from m_config, not m_extra_config: read_cli() puts only what the user typed
// into m_config (setup() adds nothing but CLI-own defaults), whereas the CLI writes its own
// values into m_extra_config. Only keys whose value the override actually changed are
// recorded -- a typed value equal to the loaded one modifies nothing -- and each lands in
// the column(s) whose preset type owns it: [0] process, [1..n-2] filaments, [n-1] printer.
//
// "Changed" is judged the way the value is read: a list is compared entry by entry with a
// missing entry read as the first, as get_at() does -- so --nozzle-temperature 245 against
// 245,245,245 is no change, although the two serialize differently.
//
// A key the loaded config does not carry at all is always recorded, even if the typed value
// equals the built-in default. On reopen the GUI restores an unlisted key from the SYSTEM
// preset, which need not match that default: a 3MF written before an option existed leaves
// it absent here, and --sparse-infill-density 20% (the default) against a Prusa system 15%
// would otherwise go unrecorded and be reverted. Over-recording is cosmetic; under-recording
// loses the value.
std::map<std::string, std::unique_ptr<ConfigOption>> cli_override_before;
for (const std::string &key : m_config.keys()) {
if (!m_extra_config.has(key))
continue;
const ConfigOption *loaded = m_print_config.option(key);
cli_override_before[key].reset(loaded != nullptr ? loaded->clone() : nullptr); // null: always recorded
}
// Apply command line options to a more specific DynamicPrintConfig which provides normalize()
// (command line options override --load files)
m_print_config.apply(m_extra_config, true);
if (!cli_override_before.empty()) {
std::vector<std::string> &columns = m_print_config.option<ConfigOptionStrings>("different_settings_to_system", true)->values;
auto owned_by = [](const std::vector<std::string> &options, const std::string &key) {
return std::find(options.begin(), options.end(), key) != options.end();
};
auto add_to_column = [&columns](size_t index, const std::string &key) {
std::vector<std::string> keys;
Slic3r::unescape_strings_cstyle(columns[index], keys);
if (std::find(keys.begin(), keys.end(), key) == keys.end()) {
keys.push_back(key);
columns[index] = Slic3r::escape_strings_cstyle(keys);
}
};
auto same_value = [](const ConfigOption *a, const ConfigOption *b) {
if (a == nullptr || b == nullptr)
return false;
const auto *va = dynamic_cast<const ConfigOptionVectorBase *>(a);
const auto *vb = dynamic_cast<const ConfigOptionVectorBase *>(b);
if (va == nullptr || vb == nullptr)
return va == vb && a->serialize() == b->serialize();
const std::vector<std::string> ea = va->vserialize(), eb = vb->vserialize();
if (ea.empty() || eb.empty())
return ea.empty() && eb.empty();
for (size_t i = 0; i < std::max(ea.size(), eb.size()); ++i)
if (ea[i < ea.size() ? i : 0] != eb[i < eb.size() ? i : 0])
return false;
return true;
};
//ORCA: always true after the resize to filament_count + 2 above, and nothing in between can
// shrink the column vector -- different_settings_to_system is not a CLI option. Kept as
// a check rather than an assert: release builds compile asserts out, so an assert would
// protect nothing, while a build with _GLIBCXX_ASSERTIONS would abort on columns[0].
if (columns.size() >= 2) {
for (const auto &[key, before] : cli_override_before) {
if (same_value(before.get(), m_print_config.option(key)))
continue;
bool recorded = false;
if (owned_by(Preset::print_options(), key)) {
add_to_column(0, key);
recorded = true;
}
if (owned_by(Preset::filament_options(), key)) {
for (size_t i = 1; i + 1 < columns.size(); ++i)
add_to_column(i, key);
recorded = true;
}
if (owned_by(Preset::printer_options(), key)) {
add_to_column(columns.size() - 1, key);
recorded = true;
}
if (recorded)
BOOST_LOG_TRIVIAL(info) << boost::format("CLI: override %1% recorded in different_settings_to_system") % key;
}
}
}
// Normalizing after importing the 3MFs / AMFs
m_print_config.normalize_fdm();