From 0b2a3d2fa1d6f4ed1dd8ff501febaadc7b17c5a5 Mon Sep 17 00:00:00 2001 From: packerlschupfer <83344883+packerlschupfer@users.noreply.github.com> Date: Sun, 30 Aug 2026 23:12:59 +0200 Subject: [PATCH] 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 \ --load-settings "/system/Prusa/process/0.20mm SPEED @CORE One HF 0.4.json;/system/Prusa/machine/Prusa CORE One HF 0.4 nozzle.json" \ --load-filaments "/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. --- src/OrcaSlicer.cpp | 72 +++++++++++++++++++++++++++++++--------------- 1 file changed, 49 insertions(+), 23 deletions(-) diff --git a/src/OrcaSlicer.cpp b/src/OrcaSlicer.cpp index 8176e9f444..22c5f8cc2d 100644 --- a/src/OrcaSlicer.cpp +++ b/src/OrcaSlicer.cpp @@ -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 upward_compatible_printers, new_print_compatible_printers, current_print_compatible_printers, current_different_settings; std::vector 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 &metadata_name = m_config.option("metadata_name", true)->values; @@ -2551,6 +2556,8 @@ int CLI::run(int argc, char **argv) flush_and_exit(ret); } upward_compatible_printers = config.option("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("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");