feat(imex): plate ghost renderer + per-plate filament map

Replaces the plater-icon popover with colored transparent ghost copies
of primary-head instances on the plate, one per secondary active head
under its Copy/Mirror role transform. Left-click on a ghost opens a
compact filament picker popover for the ghost's head (MMU lane override).
Ghosts track the primary through drag/rotate/scale/mirror and invalidate
on mode or pem changes.

Key pieces:
  IMEXHelpers -- imex_head_transform (Primary/Copy/Mirror), shared role
    parser, per-head filament resolution with X-axis Mirror anchor.
  PartPlate -- ghost state, volume rebuild on mode/map/object mutation,
    primary_origin plumbed for Mirror reflection across the primary-row
    gantry plane.
  GLCanvas3D -- ghost rendering with per-head filament color and
    translucent blending; picking routed via volume composite id.
  Plater -- ghost click + tooltip; plater icon left-click always cycles.
  IMEXFilamentPickerPopover -- BitmapComboBox row for one secondary head,
    writes imex_head_filament_map on selection.
  bbs_3mf -- round-trip the per-plate imex_head_filament_map option.
  PrintConfig -- add imex_head_filament_map as a plate option.

MMU/AFC routing for parallel modes relies on the printer profile's
physical_extruder_map (see prior commit for authoring format). Primary-
row heads and their per-plate filament overrides are resolved through
that map, so PA and temperature emission address the correct physical
extruder when multiple logical slots share one carriage.

Tests: IMEXHelpers coverage for Primary/Copy/Mirror transforms
including a 2x2 off-row regression guard for the X-axis reflection fix.
This commit is contained in:
Clifford Garwood
2026-04-21 14:56:22 -04:00
parent 0530d6c4da
commit 77c32a2e15
22 changed files with 1458 additions and 28 deletions
+6 -1
View File
@@ -4483,6 +4483,9 @@ public:
// Parse "idx:P,idx:C,idx:M" → map<tool_idx, state>
// Backwards compat: plain "idx" (no role) → state 1 (Primary)
// Indices are PHYSICAL tool indices (T0..TN-1). all_tool_states preserves
// off-grid entries so the tile widget can round-trip indices that fall
// outside the currently visible rows × cols without losing data on save.
static std::map<int,int> parse_tool_states(const std::string& s) {
std::map<int,int> result;
if (s.empty()) return result;
@@ -5380,7 +5383,9 @@ if (is_marlin_flavor)
"dialog itself. Re-enable here if suppressed accidentally.") };
line.full_width = 1;
line.widget = [](wxWindow* parent) -> wxSizer* {
auto* cb = new wxCheckBox(parent, wxID_ANY, wxEmptyString);
// Label belongs on the checkbox itself: full_width=1 (required to
// dodge the Windows crash in activate_line) skips Line-title rendering.
auto* cb = new wxCheckBox(parent, wxID_ANY, _L("Pre-slice warnings"));
bool enabled = wxGetApp().app_config->get("imex_pre_slice_warnings") != "false";
cb->SetValue(enabled);
cb->Bind(wxEVT_CHECKBOX, [cb](wxCommandEvent&) {