From c5b152b722245f96d6baad88830b38f4c4a3e167 Mon Sep 17 00:00:00 2001 From: packerlschupfer <83344883+packerlschupfer@users.noreply.github.com> Date: Mon, 14 Sep 2026 05:37:05 +0200 Subject: [PATCH] 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. --- src/OrcaSlicer.cpp | 85 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/src/OrcaSlicer.cpp b/src/OrcaSlicer.cpp index d3e24437fb..499e73d073 100644 --- a/src/OrcaSlicer.cpp +++ b/src/OrcaSlicer.cpp @@ -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> 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 &columns = m_print_config.option("different_settings_to_system", true)->values; + auto owned_by = [](const std::vector &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 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(a); + const auto *vb = dynamic_cast(b); + if (va == nullptr || vb == nullptr) + return va == vb && a->serialize() == b->serialize(); + const std::vector 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();