X2D Support (#13388)

# Description

Adresses #13294 

- Adds the X2D printer definition, machine presets, process presets,
filament presets, BBL profile index entries, CLI config entries,
filament blacklist updates, and printer/load/calibration/cover assets.
- Updates dual-nozzle handling to use configured toolhead labels and
match Bambu X2D hotend placeholders.
- Adds X2D-specific wipe tower cooling placeholder support and 3MF
filament/nozzle change sequence metadata import/export plumbing.

# Note

I own a P2S and an X2D. That's all. I frankly have no idea if my changes
cause regression on other printers, and have no capability to test. I
know that for my X2D, which runs an AMS, .2mm nozzles, SuperTack, and in
LAN mode, this has been working without issue.

# Screenshots/Recordings/Graphs

<img width="606" height="380" alt="Dual nozzle control"
src="https://github.com/user-attachments/assets/0d1c1063-4621-4097-b97c-d739557bf18c"
/>

*Dual nozzle control*

<img width="726" height="260" alt="image"
src="https://github.com/user-attachments/assets/270355b7-ca67-4ca3-ad19-582b8f11411b"
/>

*Multi nozzle filament override*

<img width="416" height="202" alt="X2D Machine config and dual nozzle
support"
src="https://github.com/user-attachments/assets/6a5c07b2-0d20-4819-8f42-d60731313249"
/>

*X2D Machine config and dual nozzle support*

<img width="397" height="142" alt="Filament for Supports test prints"
src="https://github.com/user-attachments/assets/3c7546bd-0e27-4d56-89b7-d9ca18c976f9"
/>

*Filament for Supports has been used in over 20 hours of test prints*

<img width="210" height="263" alt="Left vs Right filament distinction"
src="https://github.com/user-attachments/assets/03322268-b669-4f14-8d77-c4d96843d219"
/>

*Left vs Right filament distinction*

<img width="557" height="327" alt="Custom filament mapping"
src="https://github.com/user-attachments/assets/c1c4396f-7359-474e-80bd-78fec22f9c82"
/>

*Custom filament mapping*

<img width="556" height="314" alt="Auto map"
src="https://github.com/user-attachments/assets/d83e3217-edce-4340-886e-043962003a30"
/>

*Auto map*

<img width="689" height="664" alt="LAN mode send print with X2D preview
and no errors"
src="https://github.com/user-attachments/assets/76009bbf-31d3-4a6c-979c-8643b487c824"
/>

*LAN mode send print with X2D preview and no errors, dual nozzle
selection*


## Tests

- 20 hours of dual-nozzle printing.
- 100% CTest tests passed
- Validated 208 changed JSON files.

<!--
> A guide for users on how to download the artifacts from this PR.
-->

[How to Download Pull Requests Artifacts for
Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)
Fix #13294
This commit is contained in:
glowstab
2026-05-09 13:21:13 -05:00
committed by GitHub
parent 7bef75b2cd
commit 9956ad5b48
242 changed files with 53863 additions and 83 deletions

View File

@@ -1,4 +1,5 @@
#include "SwitchButton.hpp"
#include "Button.hpp"
#include "Label.hpp"
#include "StaticBox.hpp"
@@ -19,7 +20,10 @@
#include <wx/dcclient.h>
#include <wx/dcgraph.h>
#include <algorithm>
wxDEFINE_EVENT(wxCUSTOMEVT_SWITCH_POS, wxCommandEvent);
wxDEFINE_EVENT(wxCUSTOMEVT_MULTISWITCH_SELECTION, wxCommandEvent);
SwitchButton::SwitchButton(wxWindow* parent, wxWindowID id)
: wxBitmapToggleButton(parent, id, wxNullBitmap, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE | wxBU_EXACTFIT)
@@ -391,6 +395,192 @@ void ModeSwitchButton::update_tooltip()
SetToolTip(m_tooltips[m_selection]);
}
MultiSwitchButton::MultiSwitchButton(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxSize &size, long style)
: StaticBox(parent, id, pos, size, style)
, m_bg_color(StateColor(
std::make_pair(0xE8E8E8, (int) StateColor::NotChecked),
std::make_pair(0x009688, (int) StateColor::Normal)))
, m_text_color(StateColor(
std::make_pair(0x6B6B6B, (int) StateColor::NotChecked),
std::make_pair(0xFFFFFE, (int) StateColor::Normal)))
, m_button_radius(10.0)
, m_button_padding(10, 6)
{
SetCornerRadius(m_button_radius);
SetBorderWidth(0);
sizer = new wxBoxSizer(wxHORIZONTAL);
auto *hsizer = new wxBoxSizer(wxVERTICAL);
hsizer->Add(sizer, 1, wxEXPAND);
SetSizer(hsizer);
SetMinSize(wxSize(-1, 20));
Bind(wxEVT_COMMAND_BUTTON_CLICKED, &MultiSwitchButton::button_clicked, this);
SetFont(Label::Body_12);
}
MultiSwitchButton::~MultiSwitchButton()
{
DeleteAllOptions();
}
int MultiSwitchButton::AppendOption(const wxString &option, void *clientData)
{
Button *btn = new Button();
btn->Create(this, option, "", wxBORDER_NONE);
btn->SetFont(GetFont());
btn->SetBackgroundColor(m_bg_color);
btn->SetTextColor(m_text_color);
btn->SetCornerRadius(m_button_radius);
btn->SetPaddingSize(m_button_padding);
btn->SetClientData(clientData);
btns.push_back(btn);
sizer->Add(btn, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL);
wxSize text_size = btn->GetTextExtent(option);
btn->SetMinSize(wxSize(text_size.x + m_button_padding.x * 2 + 6, -1));
return int(btns.size()) - 1;
}
void MultiSwitchButton::SetOptions(const std::vector<wxString> &options)
{
DeleteAllOptions();
for (const auto &option : options)
AppendOption(option);
Layout();
Refresh();
}
void MultiSwitchButton::DeleteAllOptions()
{
sel = -1;
for (auto *btn : btns) {
if (btn)
btn->Destroy();
}
btns.clear();
if (sizer)
sizer->Clear();
}
unsigned int MultiSwitchButton::GetCount() const
{
return (unsigned int) btns.size();
}
int MultiSwitchButton::GetSelection() const
{
return sel;
}
void MultiSwitchButton::SetSelection(int index)
{
if (index < 0 || index >= (int) btns.size() || index == sel)
return;
sel = index;
update_button_styles();
send_selection_event();
Refresh();
}
wxString MultiSwitchButton::GetSelectedText() const
{
return sel >= 0 && sel < (int) btns.size() ? btns[sel]->GetLabel() : wxString();
}
wxString MultiSwitchButton::GetOptionText(unsigned int index) const
{
return index < btns.size() ? btns[index]->GetLabel() : wxString();
}
void MultiSwitchButton::SetOptionText(unsigned int index, const wxString &text)
{
if (index >= btns.size())
return;
btns[index]->SetLabel(text);
}
void *MultiSwitchButton::GetOptionData(unsigned int index) const
{
return index < btns.size() ? btns[index]->GetClientData() : nullptr;
}
void MultiSwitchButton::SetOptionData(unsigned int index, void *clientData)
{
if (index >= btns.size())
return;
btns[index]->SetClientData(clientData);
}
void MultiSwitchButton::update_button_styles()
{
for (int i = 0; i < (int) btns.size(); ++i) {
btns[i]->SetValue(i == sel);
btns[i]->SetBackgroundColor(m_bg_color);
btns[i]->SetTextColor(m_text_color);
btns[i]->Refresh();
}
}
void MultiSwitchButton::SetBackgroundColor(const StateColor &color)
{
m_bg_color = color;
update_button_styles();
}
void MultiSwitchButton::SetTextColor(const StateColor &color)
{
m_text_color = color;
update_button_styles();
}
void MultiSwitchButton::SetButtonCornerRadius(double radius)
{
m_button_radius = radius;
SetCornerRadius(radius);
for (auto *btn : btns)
btn->SetCornerRadius(radius);
Layout();
Refresh();
}
void MultiSwitchButton::SetButtonPadding(const wxSize &padding)
{
m_button_padding = padding;
for (auto *btn : btns)
btn->SetPaddingSize(padding);
Layout();
Refresh();
}
void MultiSwitchButton::Rescale()
{
for (auto *btn : btns)
btn->Rescale();
}
void MultiSwitchButton::button_clicked(wxCommandEvent &event)
{
SetFocus();
auto *btn = event.GetEventObject();
auto iter = std::find(btns.begin(), btns.end(), btn);
SetSelection(iter == btns.end() ? -1 : int(iter - btns.begin()));
}
bool MultiSwitchButton::send_selection_event()
{
wxCommandEvent evt(wxCUSTOMEVT_MULTISWITCH_SELECTION, GetId());
evt.SetEventObject(this);
evt.SetInt(sel);
evt.SetString(GetSelectedText());
GetEventHandler()->ProcessEvent(evt);
return true;
}
SwitchBoard::SwitchBoard(wxWindow *parent, wxString leftL, wxString right, wxSize size)
: wxWindow(parent, wxID_ANY, wxDefaultPosition, size)
{
@@ -554,4 +744,4 @@ bool SwitchBoard::Enable(bool enable /* = true */)
is_enable = enable;
Refresh();
return true;
}
}