mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-11 09:51:06 +00:00
Fall back to Primary when an IMEX mode does not resolve, and keep imex keys out of the config block
Closes review comment 2, part of 18, and one non-IMEX regression the review did not cover. An imex_parallel_mode naming no entry in imex_mode_names still entered the parallel branches. get_imex_active_tools() returned empty and the else was skipped, so no head received its 1st-to-2nd layer temperature transition, and imex_suppresses_bare_toolchange() still dropped the initial T<n> on the expectation that a mode script would select the tool. validate() did not catch it because its guard is declared_primary >= 0 and an unresolved mode yields -1. You reach it by renaming a mode after a plate is set to it, or by opening a 3MF whose printer preset names its modes differently. m_imex_parallel_mode is no longer assigned before the lookup; a non-Primary name that matches no row now warns and re-resolves against the Primary row, which is the fallback the PR description already claimed. A mode that resolves to an empty tool roster takes the same path, since the emitted G-code is wrong in the same way. Warned rather than blocked: opening someone else's 3MF is a legitimate way to get here and the Primary reading prints correctly, so refusing to slice would turn a recoverable situation into a dead end. Silent was not an option either, because the plate keeps showing the stale mode name while drawing no zones. Separately, the 14 imex config keys all register non-nil defaults, so append_full_config was emitting "; imex_* = <default>" into every exported G-code, including on single-nozzle printers with nothing to do with the feature. They are banned from the dump, matching the treatment already given to the fast-purge, extruder-change and timelapse keys, so the config block is byte-identical to the pre-IMEX baseline for the whole shipping fleet. Nothing reads them back: GCodeProcessor has no imex reference, and the two per-plate keys round-trip through the 3MF's model_settings.config on an independent path. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
3629056831
commit
612e0e3932
+98
-23
@@ -2883,19 +2883,10 @@ static std::vector<int> get_imex_active_tools(const Print& print)
|
||||
if (active_mode == kImexPrimaryMode)
|
||||
return active_tools;
|
||||
|
||||
const auto* mode_names_opt = print.config().option<ConfigOptionStrings>("imex_mode_names");
|
||||
const auto* tools_opt = print.config().option<ConfigOptionStrings>("imex_mode_active_tools");
|
||||
|
||||
if (!mode_names_opt || !tools_opt || mode_names_opt->values.empty() || tools_opt->values.empty())
|
||||
return active_tools;
|
||||
|
||||
for (size_t i = 0; i < mode_names_opt->values.size(); ++i) {
|
||||
if (i >= tools_opt->values.size() || mode_names_opt->values[i] != active_mode)
|
||||
continue;
|
||||
for (const auto& [phys, role] : parse_imex_active_tools(tools_opt->values[i]))
|
||||
active_tools.push_back(phys);
|
||||
break;
|
||||
}
|
||||
// An unresolved mode, and a mode the tools array is too short to cover, both hand back
|
||||
// an empty tools string, which parses to no tools.
|
||||
for (const auto& [phys, role] : parse_imex_active_tools(find_imex_mode(print.config(), active_mode).active_tools))
|
||||
active_tools.push_back(phys);
|
||||
return active_tools;
|
||||
}
|
||||
|
||||
@@ -3685,19 +3676,78 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
if (print.config().is_imex.value && !print.objects().empty()) {
|
||||
const std::string& raw = print.objects().front()->config().imex_parallel_mode.value;
|
||||
imex_active_mode = raw.empty() ? kImexPrimaryMode : raw;
|
||||
m_imex_parallel_mode = imex_active_mode;
|
||||
m_imex_head_filament_map = parse_imex_head_filament_map(
|
||||
print.objects().front()->config().imex_head_filament_map.value);
|
||||
const auto& mode_names = print.config().imex_mode_names.values;
|
||||
const auto& mode_gcodes = print.config().imex_mode_gcodes.values;
|
||||
for (size_t i = 0; i < mode_names.size(); ++i) {
|
||||
if (mode_names[i] == imex_active_mode) {
|
||||
imex_active_mode_index = (int)i;
|
||||
if (i < mode_gcodes.size())
|
||||
imex_active_mode_gcode = mode_gcodes[i];
|
||||
break;
|
||||
}
|
||||
// Look `name` up in the printer's mode table, filling in its index, script and
|
||||
// tool roster. False means the name matches no row, i.e. the mode is unresolved.
|
||||
// find_imex_mode() owns the "which row, and what if a sibling array is short"
|
||||
// rule for every IMEX consumer; see IMEXHelpers.hpp.
|
||||
std::string imex_active_mode_tools;
|
||||
auto resolve_mode = [&](const std::string& name) {
|
||||
const ImexMode mode = find_imex_mode(print.config(), name);
|
||||
if (!mode.found())
|
||||
return false;
|
||||
imex_active_mode_index = mode.index;
|
||||
imex_active_mode_gcode = mode.gcode;
|
||||
imex_active_mode_tools = mode.active_tools;
|
||||
return true;
|
||||
};
|
||||
// An unresolved mode name falls back to Primary rather than being carried into the
|
||||
// parallel-mode paths. `imex_parallel_mode` is a plain string stored on the plate and
|
||||
// matched against the printer's `imex_mode_names` by value, so it goes stale whenever
|
||||
// the two drift apart -- a mode renamed or deleted after a plate was set to it, or a
|
||||
// project shared between printer presets that name their modes differently.
|
||||
//
|
||||
// Without the fallback the exporter took every "not Primary" branch while every lookup
|
||||
// keyed on the mode name came back empty, and wrote a file that is wrong rather than
|
||||
// merely unconfigured: the 1st->2nd layer temperature branch is mutually exclusive with
|
||||
// the standard one, so an empty active-tool roster meant NO head got its transition and
|
||||
// all of them held nozzle_temperature_initial_layer for the whole print; and
|
||||
// imex_suppresses_bare_toolchange() dropped the initial T<n> on the assumption that a
|
||||
// mode script would select the tool, while the mode script -- also looked up by name --
|
||||
// did not exist. Print::validate()'s IMEX rules did not catch it either: they resolve
|
||||
// the same name to an empty tools string, which yields no declared primary and skips
|
||||
// the guard. Primary is the one interpretation that is always well-formed: single
|
||||
// carriage, ordinary temperatures, ordinary tool changes.
|
||||
//
|
||||
// Warned rather than silent, and warned rather than blocked. Silent is not an option:
|
||||
// the plate still labels itself with the stale mode and draws no zones (PartPlate's
|
||||
// zone builder returns early on the same unresolved state), so a user who asked for two
|
||||
// parts in copy mode would get one with nothing anywhere saying why. Blocking is not an
|
||||
// option either -- opening someone else's 3MF on a differently-named preset is a
|
||||
// legitimate way to arrive here, and the Primary interpretation prints correctly, so
|
||||
// refusing to slice would be a regression for a case that has a good answer.
|
||||
//
|
||||
// The second trigger below is the ragged-table case, and it lands here rather than
|
||||
// anywhere else because the damage is identical: the mode's name resolves, but the
|
||||
// printer's `imex_mode_active_tools` is too short to reach its row (or the row is
|
||||
// explicitly empty), so the roster every branch above keys on comes back empty and
|
||||
// the export takes the parallel path with nothing in it. Nothing used to report
|
||||
// that -- half the lookup sites in the tree turned a short tools array into "mode
|
||||
// not found" and the other half did not, and this one did not. One rule now, in
|
||||
// find_imex_mode(), and one warning, here.
|
||||
const bool imex_mode_resolved = resolve_mode(imex_active_mode);
|
||||
if (imex_active_mode != kImexPrimaryMode && (!imex_mode_resolved || imex_active_mode_tools.empty())) {
|
||||
print.active_step_add_warning(
|
||||
PrintStateBase::WarningLevel::NON_CRITICAL,
|
||||
imex_mode_resolved
|
||||
? Slic3r::format(_(L("The IMEX mode \"%1%\" has no tools assigned on the selected printer. "
|
||||
"Printing in Primary mode instead. Assign the mode's tools in Printer "
|
||||
"Settings, or pick another mode from the plate's IMEX button.")),
|
||||
imex_active_mode)
|
||||
: Slic3r::format(_(L("This plate is set to the IMEX mode \"%1%\", which the selected printer "
|
||||
"does not define. Printing in Primary mode instead. Pick a mode from the "
|
||||
"plate's IMEX button, or restore the mode in Printer Settings.")),
|
||||
imex_active_mode));
|
||||
imex_active_mode = kImexPrimaryMode;
|
||||
imex_active_mode_index = 0;
|
||||
imex_active_mode_gcode.clear();
|
||||
imex_active_mode_tools.clear();
|
||||
// Primary is a real row in the table (always the first one), so it may carry its own
|
||||
// setup script; resolve it the same way any other selected mode would be.
|
||||
resolve_mode(imex_active_mode);
|
||||
}
|
||||
m_imex_parallel_mode = imex_active_mode;
|
||||
}
|
||||
this->placeholder_parser().set("imex_mode", imex_active_mode);
|
||||
this->placeholder_parser().set("imex_mode_index", imex_active_mode_index);
|
||||
@@ -7216,6 +7266,31 @@ void GCode::append_full_config(const Print &print, std::string &str)
|
||||
// banning it from the dump has no effect on the feature; the only H2C/H2D delta is the M9711/M971
|
||||
// snapshot reposition.
|
||||
"farthest_point_timelapse"sv,
|
||||
// The IMEX (IDEX/IQEX parallel printing) keys are newly-registered printer/process keys whose
|
||||
// defaults are non-nil, so leaving them in the dump would add fourteen `; imex_* = <default>`
|
||||
// lines to every printer's config block — an ordinary single-nozzle machine included. Excluding
|
||||
// them keeps the config-dump byte-identical for the whole shipping fleet; the IMEX printers pay
|
||||
// the same price the timelapse/prime-volume keys above already pay, and the active mode is still
|
||||
// visible in the body of their g-code through the injected imex_mode_gcodes macro. Every key is
|
||||
// read at slice time from m_config (get_imex_active_tools / find_imex_mode / the zone layout
|
||||
// helpers) and never parsed back out of the dump, so banning them costs the feature nothing:
|
||||
// the g-code viewer's overlay reads the loaded presets, not the config block, and the two
|
||||
// per-plate process keys round-trip through the 3MF's model_settings.config plate metadata
|
||||
// (bbs_3mf.cpp IMEX_PARALLEL_MODE_ATTR / IMEX_HEAD_FILAMENT_MAP_ATTR), not through this dump.
|
||||
"is_imex"sv,
|
||||
"imex_firmware_managed_zones"sv,
|
||||
"imex_gantry_count"sv,
|
||||
"imex_tools_per_gantry"sv,
|
||||
"imex_tool_layout"sv,
|
||||
"imex_nozzle_clearance_x"sv,
|
||||
"imex_nozzle_clearance_y"sv,
|
||||
"imex_carriage_margin"sv,
|
||||
"imex_viz_theme"sv,
|
||||
"imex_mode_names"sv,
|
||||
"imex_mode_active_tools"sv,
|
||||
"imex_mode_gcodes"sv,
|
||||
"imex_parallel_mode"sv,
|
||||
"imex_head_filament_map"sv,
|
||||
"compatible_printers"sv,
|
||||
"compatible_prints"sv,
|
||||
"filament_colour_type"sv,
|
||||
|
||||
Reference in New Issue
Block a user