CLI: evaluate compatible_printers_condition in the compat checks

Slicing from the CLI with --load-settings exits with
CLI_PROCESS_NOT_COMPATIBLE (-17), "The selected printer is not compatible
with the process preset in the 3mf.", for process/printer pairs the GUI
accepts. Reproducible with stock, unmodified Prusa system profiles:

  orca-slicer --datadir <datadir> \
    --load-settings "<datadir>/system/Prusa/process/0.20mm SPEED @CORE One HF 0.4.json;<datadir>/system/Prusa/machine/Prusa CORE One HF 0.4 nozzle.json" \
    --load-filaments "<datadir>/system/Prusa/filament/Prusament PETG @CORE One HF 0.4.json" \
    --slice 0 --outputdir /tmp/out model.stl

The four compat checks in CLI::run did a literal name match against the
`compatible_printers` list only:

    for (index ...) if (new_print_compatible_printers[index] == new_printer_system_name)
        process_compatible = true;

Process profiles that declare compatibility through
`compatible_printers_condition` and leave `compatible_printers` empty are
therefore always reported incompatible -- the condition is never consulted.
For 0.20mm SPEED @CORE One HF 0.4 that condition is:

    printer_notes=~/.*PRINTER_MODEL_COREONE[^_a-zA-Z0-9].*/ and
    nozzle_diameter[0]==0.4 and printer_notes=~/.*HF_NOZZLE.*/

The GUI does not have this bug: is_compatible_with_printer() in Preset.cpp
treats an empty list as "no explicit constraint" and evaluates the
condition in that case.

Fix: replace the four loops with a check_compat lambda that calls
is_compatible_with_printer() -- the same helper the GUI uses -- wrapping
the already-loaded DynamicPrintConfigs in lightweight Preset /
PresetWithVendorProfile shells. The 3MF-embedded process/printer full
configs are kept in current_process_full_config /
current_printer_full_config so the condition can be evaluated for the
reprocess paths too; those fall back to the previous literal match when
the full config was not preserved.

Behaviour is unchanged where an explicit compatible_printers list exists:
is_compatible_with_printer() performs the same name match, and returns
true when both list and condition are empty, matching the existing
"old 3mf, no compatible printers, set to compatible" path.

Split out of #13731 (section 1) as a standalone, single-purpose change.
Orthogonal to the inherits-chain resolution work in #14718 / #15302 /
#15438; those decide which values a preset resolves to, this decides
whether the resulting pair is considered compatible.
This commit is contained in:
packerlschupfer
2026-08-30 23:12:59 +02:00
parent 56a452875e
commit 0b2a3d2fa1

View File

@@ -53,6 +53,7 @@ using namespace nlohmann;
#include "libslic3r/libslic3r.h"
#include "libslic3r/Config.hpp"
#include "libslic3r/Preset.hpp"
#include "libslic3r/Geometry.hpp"
#include "libslic3r/GCode.hpp"
#include "libslic3r/Model.hpp"
@@ -1462,6 +1463,10 @@ int CLI::run(int argc, char **argv)
std::vector<std::string> upward_compatible_printers, new_print_compatible_printers, current_print_compatible_printers, current_different_settings;
std::vector<std::string> current_filaments_name, current_filaments_system_name, current_inherits_group, current_extruder_variants, new_extruder_variants, current_print_extruder_variants, new_printer_extruder_variants;
DynamicPrintConfig load_process_config, load_machine_config;
//ORCA: full configs of the "current" (3MF-embedded) process/printer presets, kept so that
// compatible_printers_condition can be evaluated for them below. Previously only the
// literal compatible_printers list was extracted.
DynamicPrintConfig current_process_full_config, current_printer_full_config;
bool new_process_config_is_system = true, new_printer_config_is_system = true;
std::string pipe_name, makerlab_name, makerlab_version, different_process_setting;
const std::vector<std::string> &metadata_name = m_config.option<ConfigOptionStrings>("metadata_name", true)->values;
@@ -2551,6 +2556,8 @@ int CLI::run(int argc, char **argv)
flush_and_exit(ret);
}
upward_compatible_printers = config.option<ConfigOptionStrings>("upward_compatible_machine", true)->values;
//ORCA: keep the full config so compatible_printers_condition can be evaluated against it below
current_printer_full_config = std::move(config);
}
}
}
@@ -2573,6 +2580,8 @@ int CLI::run(int argc, char **argv)
flush_and_exit(ret);
}
current_print_compatible_printers = config.option<ConfigOptionStrings>("compatible_printers", true)->values;
//ORCA: keep the full config so compatible_printers_condition can be evaluated against it below
current_process_full_config = std::move(config);
}
}
}
@@ -2591,46 +2600,63 @@ int CLI::run(int argc, char **argv)
for (int index = 0; index < upward_compatible_printers.size(); index++) {
BOOST_LOG_TRIVIAL(info) << boost::format("index %1%, upward_compatible_printers %2%")%index %upward_compatible_printers[index];
}
//ORCA: Replace the four manual equality-loop checks below with is_compatible_with_printer(), the
// same helper the GUI uses, which also evaluates compatible_printers_condition. Process
// profiles that declare compatibility via condition only -- leaving compatible_printers
// empty -- were always reported incompatible by the literal-name match, so a CLI slice with
// such a preset exited with CLI_PROCESS_NOT_COMPATIBLE (-17) even though the GUI accepts the
// same pair. Behaviour is unchanged where an explicit list exists: is_compatible_with_printer
// does the same name match, and returns true when both list and condition are empty (which
// matches the "old 3mf, no compatible printers" path below).
auto check_compat = [](const DynamicPrintConfig &process_cfg,
const DynamicPrintConfig &printer_cfg,
const std::string &printer_name) -> bool {
Preset process_preset(Preset::TYPE_PRINT, std::string("__cli_process_check"));
process_preset.config = process_cfg;
Preset printer_preset(Preset::TYPE_PRINTER, printer_name);
printer_preset.config = printer_cfg;
PresetWithVendorProfile process_pwvp(process_preset, nullptr);
PresetWithVendorProfile printer_pwvp(printer_preset, nullptr);
return is_compatible_with_printer(process_pwvp, printer_pwvp);
};
if (!new_printer_name.empty()) {
if (!new_process_name.empty()) {
for (int index = 0; index < new_print_compatible_printers.size(); index++) {
if (new_print_compatible_printers[index] == new_printer_system_name) {
process_compatible = true;
break;
}
}
//new process + new printer: both configs came from --load-settings
process_compatible = check_compat(load_process_config, load_machine_config, new_printer_system_name);
BOOST_LOG_TRIVIAL(info) << boost::format("new printer %1%, inherited from %2%, new process %3%, inherited from %4% ,compatible %5%")
%new_printer_name %new_printer_system_name %new_process_name %new_process_system_name %process_compatible;
}
else {
for (int index = 0; index < current_print_compatible_printers.size(); index++) {
if (current_print_compatible_printers[index] == new_printer_system_name) {
process_compatible = true;
break;
}
//3MF-embedded process vs new printer. current_process_full_config is only populated from
//profiles/BBL/process_full/, i.e. for BBL profiles; for every other vendor fall back to the
//3MF's own project config, which is already merged into m_print_config above and carries
//compatible_printers_condition. Without this a 3MF built from a condition-only process is
//rejected when re-sliced with the very printer it was made for.
{
const DynamicPrintConfig &process_cfg = current_process_full_config.empty() ? m_print_config : current_process_full_config;
process_compatible = check_compat(process_cfg, load_machine_config, new_printer_system_name);
}
BOOST_LOG_TRIVIAL(info) << boost::format("new printer %1%, inherited from %2%, old process %3%, inherited from %4% ,compatible %5%")
%new_printer_name %new_printer_system_name %current_process_name %current_process_system_name %process_compatible;
}
}
else if (!new_process_name.empty()) {
for (int index = 0; index < new_print_compatible_printers.size(); index++) {
if (new_print_compatible_printers[index] == current_printer_system_name) {
process_compatible = true;
break;
}
//new process vs 3MF-embedded printer. As above, current_printer_full_config only resolves for
//BBL profiles; otherwise evaluate against the 3MF's own project config in m_print_config, which
//holds the embedded printer's printer_notes / nozzle_diameter.
{
const DynamicPrintConfig &printer_cfg = current_printer_full_config.empty() ? m_print_config : current_printer_full_config;
process_compatible = check_compat(load_process_config, printer_cfg, current_printer_system_name);
}
BOOST_LOG_TRIVIAL(info) << boost::format("old printer %1%, inherited from %2%, new process %3%, inherited from %4% ,compatible %5%")
%current_printer_name %current_printer_system_name %new_process_name %new_process_system_name %process_compatible;
}
else {
//check the compatible of old printer&&process
for (int index = 0; index < current_print_compatible_printers.size(); index++) {
if (current_print_compatible_printers[index] == current_printer_system_name) {
process_compatible = true;
break;
}
}
//both sides 3MF-embedded (pure reprocess)
if (!current_process_full_config.empty() && !current_printer_full_config.empty())
process_compatible = check_compat(current_process_full_config, current_printer_full_config, current_printer_system_name);
else
process_compatible = std::find(current_print_compatible_printers.begin(), current_print_compatible_printers.end(), current_printer_system_name) != current_print_compatible_printers.end();
if (!process_compatible && current_print_compatible_printers.empty())
{
BOOST_LOG_TRIVIAL(info) << boost::format("old 3mf, no compatible printers, set to compatible");