mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-14 19:31:14 +00:00
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:
@@ -0,0 +1,124 @@
|
||||
#include "slic3r/GUI/IMEXFilamentPickerPopover.hpp"
|
||||
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/dcmemory.h>
|
||||
#include <wx/image.h>
|
||||
|
||||
#include "libslic3r/IMEXHelpers.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "slic3r/GUI/BitmapComboBox.hpp"
|
||||
#include "slic3r/GUI/PartPlate.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/wxExtensions.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
IMEXFilamentPickerPopover::IMEXFilamentPickerPopover(
|
||||
wxWindow* parent, PartPlate* plate,
|
||||
const ConfigOptionInts& pem, int physical_head,
|
||||
CommitCallback on_commit)
|
||||
: wxPopupTransientWindow(parent, wxBORDER_SIMPLE)
|
||||
, m_plate(plate)
|
||||
, m_pem(pem)
|
||||
, m_physical_head(physical_head)
|
||||
, m_on_commit(std::move(on_commit))
|
||||
{
|
||||
m_root_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
SetSizer(m_root_sizer);
|
||||
|
||||
build_row();
|
||||
|
||||
m_root_sizer->SetSizeHints(this);
|
||||
SetClientSize(m_root_sizer->ComputeFittingClientSize(this));
|
||||
Layout();
|
||||
}
|
||||
|
||||
void IMEXFilamentPickerPopover::popup_at_cursor()
|
||||
{
|
||||
Position(wxGetMousePosition(), wxSize(0, 0));
|
||||
Popup();
|
||||
}
|
||||
|
||||
void IMEXFilamentPickerPopover::build_row()
|
||||
{
|
||||
m_root_sizer->Clear(true);
|
||||
|
||||
m_root_sizer->AddSpacer(10);
|
||||
|
||||
auto* label = new wxStaticText(this, wxID_ANY,
|
||||
wxString::Format("T%d", m_physical_head));
|
||||
m_root_sizer->Add(label, 0, wxALIGN_CENTER_VERTICAL | wxTOP | wxBOTTOM, 6);
|
||||
|
||||
m_root_sizer->AddSpacer(10);
|
||||
|
||||
std::vector<int> lane_logicals;
|
||||
for (size_t L = 0; L < m_pem.values.size(); ++L)
|
||||
if (m_pem.values[L] == m_physical_head)
|
||||
lane_logicals.push_back((int)L);
|
||||
|
||||
std::vector<wxBitmap*> bmps = get_extruder_color_icons(false /*thin_icon*/);
|
||||
|
||||
auto* choice = new BitmapComboBox(this, wxID_ANY, wxEmptyString,
|
||||
wxDefaultPosition, wxSize(140, -1),
|
||||
0, nullptr, wxCB_READONLY);
|
||||
const int left_pad_px = 8;
|
||||
auto pad_left = [&](const wxBitmap& src) -> wxBitmap {
|
||||
if (!src.IsOk()) return src;
|
||||
wxImage im(src.GetWidth() + left_pad_px, src.GetHeight());
|
||||
im.InitAlpha();
|
||||
unsigned char* a = im.GetAlpha();
|
||||
std::fill(a, a + im.GetWidth() * im.GetHeight(), (unsigned char)0);
|
||||
wxBitmap out(im);
|
||||
wxMemoryDC dc(out);
|
||||
dc.DrawBitmap(src, left_pad_px, 0, true);
|
||||
dc.SelectObject(wxNullBitmap);
|
||||
return out;
|
||||
};
|
||||
|
||||
for (int L : lane_logicals) {
|
||||
const wxString label = wxString::Format("filament %d", L + 1);
|
||||
if (L >= 0 && L < (int)bmps.size() && bmps[L] && bmps[L]->IsOk())
|
||||
choice->Append(label, pad_left(*bmps[L]));
|
||||
else
|
||||
choice->Append(label, wxNullBitmap);
|
||||
}
|
||||
|
||||
const auto map = m_plate->get_imex_head_filament_map();
|
||||
auto it = map.find(m_physical_head);
|
||||
int selected_slot_1based = (it != map.end())
|
||||
? it->second
|
||||
: (lane_logicals.empty() ? -1 : lane_logicals.front() + 1);
|
||||
|
||||
for (int i = 0; i < (int)lane_logicals.size(); ++i) {
|
||||
if (lane_logicals[i] + 1 == selected_slot_1based) {
|
||||
choice->SetSelection(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
choice->Bind(wxEVT_COMBOBOX, [this, lane_logicals, choice](wxCommandEvent&) {
|
||||
int sel = choice->GetSelection();
|
||||
if (sel < 0 || sel >= (int)lane_logicals.size()) return;
|
||||
on_filament_selected(lane_logicals[sel] + 1);
|
||||
Dismiss();
|
||||
});
|
||||
|
||||
m_root_sizer->Add(choice, 0, wxALIGN_CENTER_VERTICAL | wxALL, 6);
|
||||
m_root_sizer->AddSpacer(4);
|
||||
}
|
||||
|
||||
void IMEXFilamentPickerPopover::on_filament_selected(int slot_1_based)
|
||||
{
|
||||
auto map = m_plate->get_imex_head_filament_map();
|
||||
// Erase when the user reselects the pem default for this head, so the 3mf
|
||||
// doesn't carry no-op overrides (same invariant as PR3b).
|
||||
const int default_logical = first_filament_for_physical_head(m_pem, m_physical_head);
|
||||
if (default_logical >= 0 && slot_1_based == default_logical + 1)
|
||||
map.erase(m_physical_head);
|
||||
else
|
||||
map[m_physical_head] = slot_1_based;
|
||||
m_plate->set_imex_head_filament_map(map);
|
||||
if (m_on_commit) m_on_commit();
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
Reference in New Issue
Block a user