CLI: record user overrides in different_settings_to_system for 3MF export (#15595)

* CLI: record user overrides in different_settings_to_system for 3MF export

Three sites in CLI::run wrote an empty `different_settings_to_system` column
and left a //todo:

    //todo: support user machine preset's different settings
    different_settings[filament_count+1] = "";
    //todo: support system process preset
    different_settings[0] = "";
    //todo: update different settings of filaments
    different_settings[filament_index] = "";

So a 3MF exported by the CLI does not record which keys the user actually
overrode relative to the system parent. Re-opening such a project in the GUI
then shows spurious "unsaved changes", and accepting that dialog can revert
inherited process/filament/machine values to system defaults.

The column could not be filled before because the CLI had no resolved view of
the parent preset. It does now: #15438 builds a PresetBundle for inherits
resolution, so the parent can be looked up by name and diffed against the
resolved leaf. This adds no extra loading -- the bundle is the one already
built, and the helper returns "" whenever it is unavailable or the parent
cannot be found, which is the previous behaviour.

Preset metadata is filtered out of the diff: `inherits`, the three
`*_settings_id` keys, and `compatible_printers` / `compatible_prints` and
their `_condition` variants, which have their own tracking columns
(`inherits_group`, per-slot lists) and would otherwise double-count.

A value already carried by the loaded JSON still wins for the process slot, so
presets saved with a `different_settings_to_system` field behave as before;
the computed value only fills the gap where that field is absent, which is the
case for every user preset in my datadir (0 of 47 carry it).

System presets keep an empty column: there are no user overrides to record.

* CLI: diff the filament slot before load_default_gcodes_to_config

The process and machine slots compute their different_settings_to_system column
before load_default_gcodes_to_config(); the filament slot did it after. That
call materialises absent gcode keys via option(..., true), and
DynamicConfig::diff only compares keys present in both configs -- so a gcode key
the resolved leaf did not carry would go from 'not compared' to 'compared as
empty against the parent' and land in the column as an override the user never
made.

Hoisted into a local above the call, guarded by load_filament_count > 0 so the
work is skipped exactly where it was before, and assigned at the original site.
The diff now also runs before config.erase("filament_settings_id"), which is
immaterial: cli_different_settings already filters filament_settings_id along
with the other *_settings_id keys.

This is a consistency fix rather than a demonstrated defect -- resolve_preset
merges the parent config, so in practice the gcode keys are already present on
both sides and the diff is unaffected. It removes the dependence on that
invariant, which the other two slots never had.

Reported by HanifKoh in review of #15595.
This commit is contained in:
packerlschupfer
2026-09-11 22:31:12 +02:00
committed by GitHub
parent f21f062ded
commit 1e76e733b7

View File

@@ -2046,6 +2046,51 @@ int CLI::run(int argc, char **argv)
error, allow_source_manifest); error, allow_source_manifest);
}; };
//ORCA: list the keys a user preset overrides relative to its system parent, for the
// `different_settings_to_system` column of an exported 3MF. Without it the CLI
// writes an empty column, so re-opening a CLI-exported project in the GUI shows
// spurious "unsaved changes" and can revert inherited process/filament/machine
// values to system defaults.
//
// The parent comes from the preset bundle that inherits resolution already builds,
// so this adds no extra loading. Returns "" whenever the parent cannot be resolved,
// which is exactly the previous behaviour.
auto cli_different_settings = [&ensure_cli_preset_bundle](const DynamicPrintConfig &resolved,
const std::string &parent_name,
Preset::Type type) -> std::string {
if (parent_name.empty())
return std::string();
std::string error;
PresetBundle *bundle = ensure_cli_preset_bundle(error);
if (bundle == nullptr) {
BOOST_LOG_TRIVIAL(warning) << "CLI: no preset bundle for different_settings_to_system: " << error;
return std::string();
}
const PresetCollection *collection = nullptr;
switch (type) {
case Preset::TYPE_PRINT: collection = &bundle->prints; break;
case Preset::TYPE_FILAMENT: collection = &bundle->filaments; break;
case Preset::TYPE_PRINTER: collection = &bundle->printers; break;
default: return std::string();
}
const Preset *parent = collection->find_preset2(parent_name, true);
if (parent == nullptr) {
BOOST_LOG_TRIVIAL(warning) << boost::format("CLI: parent preset '%1%' not found; leaving different_settings_to_system empty")%parent_name;
return std::string();
}
std::vector<std::string> keys = resolved.diff(parent->config);
//ORCA: preset metadata, not user-tunable settings. compatible_printers /
// compatible_prints have their own tracking columns and would double-count.
keys.erase(std::remove_if(keys.begin(), keys.end(), [](const std::string &k) {
return k == "inherits" || k == "compatible_printers" || k == "compatible_prints"
|| k == "compatible_printers_condition" || k == "compatible_prints_condition"
|| k == "print_settings_id" || k == "filament_settings_id" || k == "printer_settings_id";
}),
keys.end());
BOOST_LOG_TRIVIAL(info) << boost::format("CLI: %1% overrides vs parent '%2%'")%keys.size()%parent_name;
return Slic3r::escape_strings_cstyle(keys);
};
auto load_config_file = [&resolve_preset](const std::string& file, DynamicPrintConfig& config, std::string& config_type, auto load_config_file = [&resolve_preset](const std::string& file, DynamicPrintConfig& config, std::string& config_type,
std::string& config_name, std::string& filament_id, std::string& config_from) { std::string& config_name, std::string& filament_id, std::string& config_from) {
if (! boost::filesystem::exists(file)) { if (! boost::filesystem::exists(file)) {
@@ -2937,8 +2982,10 @@ int CLI::run(int argc, char **argv)
} }
} }
else { else {
//todo: support user machine preset's different settings //ORCA: was a //todo — compute the user's overrides instead of writing an empty column.
different_settings[filament_count+1] = ""; different_settings[filament_count+1] = new_printer_config_is_system
? std::string()
: cli_different_settings(load_machine_config, new_printer_system_name, Preset::TYPE_PRINTER);
if (new_printer_config_is_system) if (new_printer_config_is_system)
inherits_group[filament_count+1] = ""; inherits_group[filament_count+1] = "";
else else
@@ -3080,8 +3127,14 @@ int CLI::run(int argc, char **argv)
print_compatible_printers = std::move(current_print_compatible_printers); print_compatible_printers = std::move(current_print_compatible_printers);
} }
else { else {
//todo: support system process preset //ORCA: was a //todo. Prefer a value the loaded JSON already carried, otherwise
different_settings[0] = ""; // compute the overrides against the system parent.
if (!different_process_setting.empty())
different_settings[0] = different_process_setting;
else
different_settings[0] = new_process_config_is_system
? std::string()
: cli_different_settings(load_process_config, new_process_system_name, Preset::TYPE_PRINT);
if (new_process_config_is_system) if (new_process_config_is_system)
inherits_group[0] = ""; inherits_group[0] = "";
else else
@@ -3268,6 +3321,16 @@ int CLI::run(int argc, char **argv)
int filament_index = load_filaments_index[index]; int filament_index = load_filaments_index[index];
std::vector<std::string> different_keys; std::vector<std::string> different_keys;
//ORCA: diff before load_default_gcodes_to_config, the way the process and machine
// slots above already do. That call materialises absent gcode keys via
// option(..., true), and DynamicConfig::diff only compares keys present in
// both configs -- so a gcode key the leaf did not carry would go from "not
// 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)
filament_different_settings = cli_different_settings(config, load_filaments_inherit[index], Preset::TYPE_FILAMENT);
load_default_gcodes_to_config(config, Preset::TYPE_FILAMENT); load_default_gcodes_to_config(config, Preset::TYPE_FILAMENT);
if (load_filament_count > 0) { if (load_filament_count > 0) {
@@ -3279,8 +3342,8 @@ int CLI::run(int argc, char **argv)
opt_filament_settings->set_at(filament_name_setting, filament_index-1, 0); opt_filament_settings->set_at(filament_name_setting, filament_index-1, 0);
config.erase("filament_settings_id"); config.erase("filament_settings_id");
//todo: update different settings of filaments //ORCA: was a //todo — same treatment as process/machine above.
different_settings[filament_index] = ""; different_settings[filament_index] = filament_different_settings;
inherits_group[filament_index] = load_filaments_inherit[index]; inherits_group[filament_index] = load_filaments_inherit[index];
} }
else { else {