mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-11 09:51:06 +00:00
Fix extruder-map bounds, popover lifetime, and arrange thread safety
Review findings on the IDEX/IQEX parallel printing code, all in paths the feature owns. - physical_extruder_map lookups used ConfigOptionVector::get_at(), which clamps an out-of-range index to values.front() rather than reporting a miss. The map holds one entry per nozzle while filament ids index slots, and nothing caps the slot count at the nozzle count, so a project authored with more filaments than the printer has extruders silently addressed the primary's head: pressure advance pinned to the wrong carriage, and skip-primary loops suppressing whichever head sat at pem[0]. Bounds-check at all four sites and treat the miss as "no mapping" (-1). Covered by a new imex_pem_tool_for test; the header note now warns against get_at here. - IMEXFilamentPickerPopover leaked a top-level window per ghost click: wxPopupTransientWindow::Dismiss() only hides, and never reaches OnDismiss(). Destroy from an OnDismiss() override and dismiss the picker through DismissAndNotify(), which is the path a successful pick takes. - ArrangeJob read PartPlate's IMEX zone cache from the worker thread, where a cache miss rebuilds GLModel members with no GL context current while the GUI thread may be painting them. Snapshot the zones in prepare(), on the main thread, already converted to plate-local coordinates. - The mode grid anchored its row window to the Primary's gantry row. A window as tall as the grid can only start at row 0, so this drew tiles for tools that do not exist and hid real ones. Render the whole grid instead; a Primary outside it is a data problem the zone layout already reports. - Build the mode tooltip from one format string rather than two catalog fragments concatenated around a runtime value, so translators can move the mode name within the sentence, and register IMEXModesCtrl.cpp for string extraction. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
b17414b160
commit
e309029b28
+28
-7
@@ -3418,9 +3418,14 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
const auto plate_head_map = parse_imex_head_filament_map(
|
||||
print.objects().front()->config().imex_head_filament_map.value);
|
||||
const ConfigOptionInts& pem = print.config().physical_extruder_map;
|
||||
const int primary_physical = pem.values.empty()
|
||||
? -1
|
||||
: pem.get_at((int)initial_extruder_id);
|
||||
// Bounds-check rather than get_at(), which clamps out-of-range to values.front(). The
|
||||
// clamped value is used as a skip-primary sentinel below, so a filament id past the end
|
||||
// of the map would suppress whichever head sits at pem[0]. -1 matches no head.
|
||||
const int primary_physical =
|
||||
((int) initial_extruder_id >= 0 &&
|
||||
(int) initial_extruder_id < (int) pem.values.size())
|
||||
? pem.values[(int) initial_extruder_id]
|
||||
: -1;
|
||||
for (int logical : imex_secondary_logical_slots(
|
||||
get_imex_active_tools(print), primary_physical, plate_head_map, pem))
|
||||
if (logical < (int)is_extruder_used.size())
|
||||
@@ -3910,7 +3915,15 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
// loop can skip the primary head (which emitted PA via the normal path).
|
||||
// Then pem-invert each active physical head back to its first routed filament
|
||||
// for the PA setting lookup. Guarded on non-empty pem above.
|
||||
const int initial_physical = m_config.physical_extruder_map.get_at((int)initial_extruder_id);
|
||||
// Bounds-check rather than get_at(), which clamps out-of-range to values.front():
|
||||
// a clamped initial_physical would make this loop skip whichever active head equals
|
||||
// pem[0], leaving that head with no PA at all. Same reasoning as the second-layer
|
||||
// temperature loop below. -1 matches no head, so every active head is emitted.
|
||||
const int initial_physical =
|
||||
((int) initial_extruder_id >= 0 &&
|
||||
(int) initial_extruder_id < (int) m_config.physical_extruder_map.values.size())
|
||||
? m_config.physical_extruder_map.values[(int) initial_extruder_id]
|
||||
: -1;
|
||||
for (int tool_idx : get_imex_active_tools(print)) {
|
||||
// Unlike the second-layer temperature loop, the primary is skipped here:
|
||||
// set_extruder() above already emitted its PA with the pem tool qualifier.
|
||||
@@ -5937,9 +5950,17 @@ LayerResult GCode::process_layer(
|
||||
// transition at all. `tool_idx` is physical; the printing head uses this layer's
|
||||
// own filament, the parallel carriages resolve through the head map.
|
||||
const int num_filament_columns = (int)print.config().nozzle_temperature.values.size();
|
||||
const int initial_physical = m_config.physical_extruder_map.values.empty()
|
||||
? -1
|
||||
: m_config.physical_extruder_map.get_at((int)first_extruder_id);
|
||||
// Bounds-check rather than get_at(): get_at() clamps to values.front(), which would
|
||||
// make initial_physical the primary's head for any first_extruder_id past the end of
|
||||
// the map. A secondary that happens to sit on that head would then take the
|
||||
// "initial" branch below and be given the wrong filament's transition temperature,
|
||||
// while never receiving its own. -1 matches no tool, so every head takes the
|
||||
// resolved path instead.
|
||||
const int initial_physical =
|
||||
((int) first_extruder_id >= 0 &&
|
||||
(int) first_extruder_id < (int) m_config.physical_extruder_map.values.size())
|
||||
? m_config.physical_extruder_map.values[(int) first_extruder_id]
|
||||
: -1;
|
||||
for (int tool_idx : get_imex_active_tools(print)) {
|
||||
const int logical = (tool_idx == initial_physical)
|
||||
? (int)first_extruder_id
|
||||
|
||||
Reference in New Issue
Block a user