Merge branch 'main' into dev/ams-heat

# Conflicts:
#	src/libslic3r/Preset.cpp
#	src/libslic3r/PrintConfig.hpp
#	src/slic3r/GUI/DeviceCore/CMakeLists.txt
#	src/slic3r/GUI/DeviceCore/DevDefs.h
#	src/slic3r/GUI/DeviceCore/DevFilaSystem.cpp
#	src/slic3r/GUI/DeviceCore/DevFilaSystem.h
#	src/slic3r/GUI/DeviceCore/DevUtilBackend.cpp
#	src/slic3r/GUI/DeviceCore/DevUtilBackend.h
#	src/slic3r/GUI/DeviceManager.cpp
#	src/slic3r/GUI/DeviceManager.hpp
#	src/slic3r/GUI/SelectMachine.cpp
#	src/slic3r/GUI/SelectMachine.hpp
This commit is contained in:
Noisyfox
2026-07-14 09:48:35 +08:00
702 changed files with 85605 additions and 2732 deletions

View File

@@ -9,9 +9,11 @@
#include "slic3r/GUI/DeviceCore/DevManager.h"
#include "slic3r/GUI/DeviceCore/DevFilaSystem.h"
#include "slic3r/GUI/DeviceCore/DevFilaSwitch.h"
#include <wx/simplebook.h>
#include <wx/dcgraph.h>
#include <wx/artprov.h>
#include <boost/log/trivial.hpp>
@@ -40,7 +42,7 @@ AMSControl::AMSControl(wxWindow *parent, wxWindowID id, const wxPoint &pos, cons
SetBackgroundColour(*wxWHITE);
// normal mode
//Freeze();
wxBoxSizer *m_sizer_body = new wxBoxSizer(wxVERTICAL);
m_sizer_body = new wxBoxSizer(wxVERTICAL);
m_amswin = new wxWindow(this, wxID_ANY);
m_amswin->SetBackgroundColour(*wxWHITE);
m_amswin->SetSize(wxSize(FromDIP(578), -1));
@@ -152,6 +154,12 @@ AMSControl::AMSControl(wxWindow *parent, wxWindowID id, const wxPoint &pos, cons
m_extruder = new AMSextruder(m_amswin, wxID_ANY, m_total_ext_count, wxDefaultPosition, AMS_EXTRUDER_SIZE);
m_sizer_option_mid->Add( m_extruder, 0, wxALIGN_CENTER, 0 );
// Orca: filament-switch routing glyph; hidden by default so it stays inert (zero layout impact)
// on any printer without a Filament Track Switch. Shown from UpdateAms only when installed.
m_switcher = new SwitcherImage(m_amswin, wxID_ANY, "fila_switch", wxSize(FromDIP(29), FromDIP(16)), wxDefaultPosition);
m_switcher->Hide();
m_sizer_option_mid->Add(m_switcher, 0, wxALIGN_CENTER | wxLEFT, FromDIP(6));
/*option right*/
m_button_extruder_feed = new Button(m_panel_option_right, _L("Load"));
@@ -1017,6 +1025,32 @@ void AMSControl::UpdateAms(const std::string &series_name,
{
m_amswin->Layout();
}
/*update switch status*/
// Orca: inert on any printer without a Filament Track Switch — install is false, so the banner is
// never materialized, the glyph stays hidden, and every ShowRoad(true) below is a no-op.
const auto [install, ready] = isFilaSwitchReady();
show_switcher_status(install && (!ready));
bool isShow = install && m_total_ext_count >= 2;
if (m_switcher->IsShown() != isShow)
{
m_switcher->Show(isShow);
m_sizer_body->Layout();
m_sizer_body->Fit(this);
this->Layout();
this->Refresh(true);
this->Update();
}
/*update ext road visibility when fila switch installed*/
bool road_visibility_changed = false;
for (auto& [ams_id, ams_item] : m_ams_item_list) {
if (!ams_item) continue;
// Orca: drop the external-spool road (AMSModel::EXT_AMS) while the switch routes all filament.
const bool should_show_road = !(install && ams_item->get_ams_model() == AMSModel::EXT_AMS);
if (ams_item->ShowRoad(should_show_road)) { road_visibility_changed = true; }
}
if (road_visibility_changed) { m_amswin->Layout(); }
}
void AMSControl::AddAmsPreview(AMSinfo info, AMSModel type)
@@ -1638,6 +1672,54 @@ void AMSControl::on_ams_setting_click(wxMouseEvent &event)
post_event(SimpleEvent(EVT_AMS_SETTINGS));
}
std::tuple<bool, bool> AMSControl::isFilaSwitchReady()
{
DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
if (!dev) return {false, false};
MachineObject* obj = dev->get_selected_machine();
if (!obj) return {false, false};
// Orca: GetFilaSwitch() is a raw non-null accessor (BBS returns a shared_ptr).
DevFilaSwitch* fila_switch = obj->GetFilaSwitch();
if (fila_switch)
{
return {fila_switch->IsInstalled(), fila_switch->IsReady()};
}
return {false, false};
}
void AMSControl::show_switcher_status(bool show)
{
// Orca: never materialize the banner on printers that never request it, so the AMS control is
// byte-identical without a Filament Track Switch (UpdateAms calls this with show=false every refresh).
if (!show && tipPanel == nullptr) { return; }
if (tipPanel == nullptr)
{
m_sizer_body->Add(0, 0, 1, wxEXPAND | wxTOP, FromDIP(5));
tipPanel = new wxPanel(m_amswin);
tipPanel->SetBackgroundColour(wxColour(255, 153, 0));
tipSizer = new wxBoxSizer(wxHORIZONTAL);
tipPanel->SetSizer(tipSizer);
icon = new wxStaticBitmap(tipPanel, wxID_ANY,
wxArtProvider::GetBitmap(wxART_INFORMATION, wxART_MESSAGE_BOX, wxSize(FromDIP(16), FromDIP(16))));
tipSizer->Add(icon, 0, wxALL, FromDIP(8));
tipText = new wxStaticText(tipPanel, wxID_ANY, _L("AMS has not been initialized. Please initialize it before use."));
tipText->SetForegroundColour(wxColour(255, 255, 255));
tipText->SetFont(wxFont(10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD));
tipText->Wrap(-1);
tipText->SetMinSize(wxSize(-1, -1));
tipSizer->Add(tipText, 0, wxALL | wxALIGN_CENTER_VERTICAL | wxEXPAND, FromDIP(8));
m_sizer_body->Add(tipPanel, 1, wxEXPAND, 0);
}
if (tipPanel->IsShown() == show)
{
return;
}
tipPanel->Show(show);
m_amswin->Layout();
m_amswin->Fit();
}
void AMSControl::parse_object(MachineObject* obj) {
if (!obj || obj->GetFilaSystem()->GetAmsList().size() == 0)
{

View File

@@ -13,6 +13,7 @@
#include <wx/hyperlink.h>
#include <wx/animate.h>
#include <wx/dynarray.h>
#include <tuple>
#include "slic3r/GUI/DeviceCore/DevExtruderSystem.h"
#include "slic3r/GUI/AMSDryControl.hpp"
@@ -52,8 +53,16 @@ protected:
int m_total_ext_count = 1;
AMSextruder *m_extruder{nullptr};
SwitcherImage *m_switcher{nullptr}; // Orca: filament-switch routing glyph (hidden unless a switch is installed)
AMSRoadDownPart* m_down_road{ nullptr };
// Orca: "AMS not initialized" warning banner, materialized lazily only when a switch needs it (see show_switcher_status)
wxBoxSizer* m_sizer_body{nullptr};
wxPanel* tipPanel{nullptr};
wxBoxSizer* tipSizer{nullptr};
wxStaticBitmap* icon{nullptr};
wxStaticText* tipText{nullptr};
/*items*/
wxBoxSizer* m_sizer_ams_items{nullptr};
wxScrolledWindow* m_panel_prv_left {nullptr};
@@ -153,6 +162,11 @@ public:
void StopRridLoading(wxString amsid, wxString canid);
void ShowFilamentTip(bool hasams = true);
// Orca: Filament Track Switch awareness. isFilaSwitchReady() resolves the selected machine and
// returns {installed, ready}; show_switcher_status() toggles the "AMS not initialized" banner.
std::tuple<bool, bool> isFilaSwitchReady();
void show_switcher_status(bool show);
void UpdatePassRoad(string ams_id, AMSPassRoadType type, AMSPassRoadSTEP step);
void CreateAms();
void CreateAmsDoubleNozzle(const std::string &series_name, const std::string& printer_type);

View File

@@ -742,6 +742,63 @@ void AMSExtImage::doRender(wxDC& dc)
}
// Orca: SwitcherImage — routing glyph drawn when a Filament Track Switch is installed.
void SwitcherImage::paintEvent(wxPaintEvent &evt)
{
wxPaintDC dc(this);
render(dc);
}
void SwitcherImage::render(wxDC &dc)
{
#ifdef __WXMSW__
wxSize size = GetSize();
wxMemoryDC memdc;
wxBitmap bmp(size.x, size.y);
memdc.SelectObject(bmp);
memdc.Blit({0, 0}, size, &dc, {0, 0});
{
wxGCDC dc2(memdc);
doRender(dc2);
}
memdc.SelectObject(wxNullBitmap);
dc.DrawBitmap(bmp, 0, 0);
#else
doRender(dc);
#endif
}
void SwitcherImage::doRender(wxDC &dc)
{
auto size = GetSize();
if (m_show_state){
dc.SetPen(*wxTRANSPARENT_PEN);
dc.SetBrush(*wxWHITE);
dc.DrawBitmap(m_switcher.bmp(), wxPoint((size.x - m_switcher.GetBmpSize().x) / 2, 0));
}
Layout();
}
SwitcherImage::SwitcherImage(wxWindow *parent, wxWindowID id, string file_name, const wxSize& size, const wxPoint &pos)
{
wxWindow::Create(parent, id, pos, size);
SetBackgroundColour(StateColor::darkModeColorFor(*wxWHITE));
m_show_state = true;
m_switcher = ScalableBitmap(this, file_name, 16);
m_file_name = file_name;
SetSize(size);
SetMinSize(size);
SetMaxSize(size);
Bind(wxEVT_PAINT, &SwitcherImage::paintEvent, this);
}
SwitcherImage::~SwitcherImage() {}
//DevAms Extruder
AMSextruder::AMSextruder(wxWindow *parent, wxWindowID id, int nozzle_num, const wxPoint &pos, const wxSize &size)
{
@@ -3577,6 +3634,18 @@ void AmsItem::doRender(wxDC& dc)
}
}
// Orca: toggle the road segment below the item (m_panel_road). Used to drop the external-spool
// road when a Filament Track Switch is installed. No-op (returns false) when already in state.
bool AmsItem::ShowRoad(bool show)
{
if (!m_panel_road) return false;
if (m_panel_road->IsShown() == show) return false;
m_panel_road->Show(show);
Layout();
Refresh();
return true;
}
void AmsItem::RenderLiteRoad(wxDC& dc, wxSize size) {
auto end_top = size.x - FromDIP(3);
if (m_panel_pos == AMSPanelPos::RIGHT_PANEL){
@@ -3706,4 +3775,342 @@ void AmsItem::show_sn_value(bool show)
}
}
DevExtruderImage::DevExtruderImage(wxWindow *parent, wxWindowID id, int extruder_num, const wxPoint &pos, const wxSize &size) : wxWindow(parent, id, pos, wxDefaultSize), m_extruder_num(extruder_num)
{
// wxWindow::Create(parent, id, pos, wxSize(FromDIP(45), FromDIP(112)));
SetBackgroundColour(*wxWHITE);
SetSize(wxSize(FromDIP(48), FromDIP(112)));
SetMinSize(wxSize(FromDIP(48), FromDIP(112)));
SetMaxSize(wxSize(FromDIP(48), FromDIP(112)));
m_left_extruder_active_filled = new ScalableBitmap(this, "left_extruder_active_filled", 62);
m_left_extruder_active_empty = new ScalableBitmap(this, "left_extruder_active_empty", 62);
m_left_extruder_unactive_filled = new ScalableBitmap(this, "left_extruder_unactive_filled", 62);
m_left_extruder_unactive_empty = new ScalableBitmap(this, "left_extruder_unactive_empty", 62);
m_right_extruder_active_filled = new ScalableBitmap(this, "right_extruder_active_filled", 62);
m_right_extruder_active_empty = new ScalableBitmap(this, "right_extruder_active_empty", 62);
m_right_extruder_unactive_filled = new ScalableBitmap(this, "right_extruder_unactive_filled", 62);
m_right_extruder_unactive_empty = new ScalableBitmap(this, "right_extruder_unactive_empty", 62);
m_extruder_single_nozzle_empty_load = new ScalableBitmap(this, "monitor_extruder_empty_load", 106);
m_extruder_single_nozzle_empty_unload = new ScalableBitmap(this, "monitor_extruder_empty_unload", 106);
m_extruder_single_nozzle_filled_load = new ScalableBitmap(this, "monitor_extruder_filled_load", 106);
m_extruder_single_nozzle_filled_unload = new ScalableBitmap(this, "monitor_extruder_filled_unload", 106);
Bind(wxEVT_PAINT, &DevExtruderImage::paintEvent, this);
}
void DevExtruderImage::msw_rescale()
{
m_left_extruder_active_filled->msw_rescale();
m_left_extruder_active_empty->msw_rescale();
m_left_extruder_unactive_filled->msw_rescale();
m_left_extruder_unactive_empty->msw_rescale();
m_right_extruder_active_filled->msw_rescale();
m_right_extruder_active_empty->msw_rescale();
m_right_extruder_unactive_filled->msw_rescale();
m_right_extruder_unactive_empty->msw_rescale();
m_extruder_single_nozzle_empty_load->msw_rescale();
m_extruder_single_nozzle_empty_unload->msw_rescale();
m_extruder_single_nozzle_filled_load->msw_rescale();
m_extruder_single_nozzle_filled_unload->msw_rescale();
Layout();
Refresh();
}
void DevExtruderImage::render(wxDC &dc)
{
#ifdef __WXMSW__
wxSize size = GetSize();
wxMemoryDC memdc;
wxBitmap bmp(size.x, size.y);
memdc.SelectObject(bmp);
memdc.Blit({0, 0}, size, &dc, {0, 0});
{
wxGCDC dc2(memdc);
doRender(dc2);
}
memdc.SelectObject(wxNullBitmap);
dc.DrawBitmap(bmp, 0, 0);
#else
doRender(dc);
#endif
}
void DevExtruderImage::doRender(wxDC &dc)
{
auto size = GetSize();
auto pot = wxPoint(size.x / 2, (size.y - m_left_extruder_active_filled->GetBmpSize().y) / 2);
if (m_extruder_num >= 2)
{
ScalableBitmap *left_extruder_bmp{nullptr};
ScalableBitmap *right_extruder_bmp{nullptr};
switch (m_right_ext_state)
{
case DevExtruderState::FILLED_LOAD:
right_extruder_bmp = current_extruder_loc == "right" ? m_right_extruder_active_filled : m_right_extruder_unactive_filled;
break;
case DevExtruderState::FILLED_UNLOAD:
right_extruder_bmp = current_extruder_loc == "right" ? m_right_extruder_active_filled : m_right_extruder_unactive_filled;
break;
case DevExtruderState::EMPTY_LOAD:
if (current_extruder_loc.empty())
{
right_extruder_bmp = m_right_extruder_active_empty;
}
else
{
right_extruder_bmp = current_extruder_loc == "right" ? m_right_extruder_active_empty : m_right_extruder_unactive_empty;
}
break;
case DevExtruderState::EMPTY_UNLOAD:
right_extruder_bmp = current_extruder_loc == "right" ? m_right_extruder_active_empty : m_right_extruder_unactive_empty;
break;
default: break;
}
switch (m_left_ext_state)
{
case DevExtruderState::FILLED_LOAD:
left_extruder_bmp = current_extruder_loc == "left" ? m_left_extruder_active_filled : m_left_extruder_unactive_filled;
break;
case DevExtruderState::FILLED_UNLOAD:
left_extruder_bmp = current_extruder_loc == "left" ? m_left_extruder_active_filled : m_left_extruder_unactive_filled;
break;
case DevExtruderState::EMPTY_LOAD:
if (current_extruder_loc.empty())
{
left_extruder_bmp = m_left_extruder_active_empty;
}
else
{
left_extruder_bmp = current_extruder_loc == "left" ? m_left_extruder_active_empty : m_left_extruder_unactive_empty;
}
break;
case DevExtruderState::EMPTY_UNLOAD:
left_extruder_bmp = current_extruder_loc == "left" ? m_left_extruder_active_empty : m_left_extruder_unactive_empty;
break;
default: break;
}
if (left_extruder_bmp) { dc.DrawBitmap(left_extruder_bmp->bmp(), pot.x - left_extruder_bmp->GetBmpWidth(), pot.y); }
if (right_extruder_bmp) { dc.DrawBitmap(right_extruder_bmp->bmp(), pot.x, pot.y); }
}
else
{
ScalableBitmap *extruder_bmp = nullptr;
switch (m_single_ext_state)
{
case DevExtruderState::FILLED_LOAD:
extruder_bmp = m_extruder_single_nozzle_filled_load;
break;
case DevExtruderState::FILLED_UNLOAD:
extruder_bmp = m_extruder_single_nozzle_filled_unload;
break;
case DevExtruderState::EMPTY_LOAD:
extruder_bmp = m_extruder_single_nozzle_empty_load;
break;
case DevExtruderState::EMPTY_UNLOAD:
extruder_bmp = m_extruder_single_nozzle_empty_unload;
break;
default: break;
}
if (extruder_bmp) { dc.DrawBitmap(extruder_bmp->bmp(), pot.x - extruder_bmp->GetBmpWidth() / 2, (size.y - extruder_bmp->GetBmpHeight()) / 2); }
}
}
FeedDirectionDialog::FeedDirectionDialog(wxWindow* parent,
const int extruderNum,
const std::string& printer_type)
: wxDialog(parent, wxID_ANY, "", wxDefaultPosition, wxDefaultSize),
m_extruder_num(extruderNum),
m_printer_type(printer_type)
{
SetBackgroundColour(wxColour("#FFFFFF"));
SetMaxSize(wxSize(FromDIP(360), FromDIP(207)));
SetMinSize(wxSize(FromDIP(360), FromDIP(207)));
SetSize(wxSize(FromDIP(360), FromDIP(207)));
wxBoxSizer* mainSizer = new wxBoxSizer(wxVERTICAL);
wxGridSizer* topSizer = new wxGridSizer (1, 3, FromDIP(5), 0);
m_radioHelper = new wxRadioButton(this, wxID_ANY, wxT(""), wxDefaultPosition, wxDefaultSize, wxRB_GROUP);
m_leftRadio = new wxRadioButton(this, wxID_ANY,
_L(DevPrinterConfigUtil::get_toolhead_display_name(m_printer_type, DEPUTY_EXTRUDER_ID, ToolHeadComponent::Extruder, ToolHeadNameCase::TitleCase, true)));
m_rightRadio = new wxRadioButton(this, wxID_ANY,
_L(DevPrinterConfigUtil::get_toolhead_display_name(m_printer_type, MAIN_EXTRUDER_ID, ToolHeadComponent::Extruder, ToolHeadNameCase::TitleCase, true)));
m_radioHelper->Show(false);
m_radioHelper->SetCanFocus(false);
m_leftRadio->SetForegroundColour(*wxBLACK);
m_rightRadio->SetForegroundColour(*wxBLACK);
topSizer->Add(m_leftRadio, 0, wxALIGN_CENTER | wxALL, FromDIP(20));
m_extruderImage = new DevExtruderImage(this, wxID_ANY, m_extruder_num);
topSizer->Add(m_extruderImage, 0, wxALIGN_CENTER | wxALL, FromDIP(5));
topSizer->Add(m_rightRadio, 0, wxALIGN_CENTER | wxALL, FromDIP(20));
mainSizer->AddStretchSpacer(1);
mainSizer->Add(topSizer, 1, wxEXPAND);
mainSizer->AddStretchSpacer(1);
wxBoxSizer* bottomSizer = new wxBoxSizer(wxHORIZONTAL);
m_confirmBtn = new Button(this, _L("Confirm"));
m_confirmBtn->SetSize(wxSize(FromDIP(80), FromDIP(32)));
m_confirmBtn->Enable(false);
m_confirmBtn->SetFont(::Label::Body_14);
bottomSizer->Add(m_confirmBtn, 0, wxALIGN_RIGHT);
mainSizer->Add(bottomSizer, 0, wxALIGN_RIGHT | wxBOTTOM | wxRIGHT, FromDIP(10));
SetSizer(mainSizer);
Layout();
Centre(wxBOTH);
m_lastChecked = m_radioHelper;
m_confirmBtn->Bind(wxEVT_BUTTON, &FeedDirectionDialog::OnConfirm, this);
m_leftRadio->Bind(wxEVT_RADIOBUTTON, &FeedDirectionDialog::OnRadioClicked, this);
m_rightRadio->Bind(wxEVT_RADIOBUTTON, &FeedDirectionDialog::OnRadioClicked, this);
}
void FeedDirectionDialog::OnConfirm(wxCommandEvent& event)
{
EndModal(wxID_OK);
}
void FeedDirectionDialog::OnRadioClicked(wxCommandEvent& evt)
{
auto clicked = static_cast<wxRadioButton*>(evt.GetEventObject());
m_load_extruder_id = std::nullopt;
if (clicked == m_lastChecked)
{
m_radioHelper->SetValue(true);
m_lastChecked = m_radioHelper;
m_confirmBtn->Enable(false);
m_extruderImage->update(DevExtruderState::EMPTY_LOAD, DevExtruderState::EMPTY_LOAD);
m_extruderImage->setExtruderUsed("");
}
else
{
clicked->SetValue(true);
m_lastChecked = clicked;
m_confirmBtn->Enable(true);
if (clicked == m_leftRadio)
{
m_extruderImage->update(DevExtruderState::FILLED_LOAD, DevExtruderState::EMPTY_LOAD);
m_extruderImage->setExtruderUsed("left");
m_load_extruder_id = 1;
{
SetTitle(wxString::Format(_L("Load %s to ") + _L(DevPrinterConfigUtil::get_toolhead_display_name(m_printer_type, DEPUTY_EXTRUDER_ID, ToolHeadComponent::Extruder, ToolHeadNameCase::LowerCase)), m_filament_id));
}
}
else if (clicked == m_rightRadio)
{
m_extruderImage->update(DevExtruderState::EMPTY_LOAD, DevExtruderState::FILLED_LOAD);
m_extruderImage->setExtruderUsed("right");
m_load_extruder_id = 0;
{
SetTitle(wxString::Format(_L("Load %s to ") + _L(DevPrinterConfigUtil::get_toolhead_display_name(m_printer_type, MAIN_EXTRUDER_ID, ToolHeadComponent::Extruder, ToolHeadNameCase::LowerCase)), m_filament_id));
}
}
}
m_leftRadio->Refresh();
m_rightRadio->Refresh();
Fit();
Update();
}
std::optional<int> FeedDirectionDialog::GetExtruderID()
{
if (m_extruderImage)
{
return m_load_extruder_id;
}
return std::nullopt;
}
wxString FeedDirectionDialog::calcTrayName(MachineObject* obj, const std::string& amsID, const std::string& slotID)
{
if (amsID.empty() || slotID.empty() || !obj)
return wxString();
auto filaSys = obj->GetFilaSystem();
if (!filaSys)
return wxString();
DevAms* ams = filaSys->GetAmsById(amsID);
if (!ams)
return wxString();
int ams_id_int = std::stoi(amsID);
int slot_id_int = std::stoi(slotID);
int tray_id = 0;
// Orca: DevAms::AmsType is the local enum (AMS / AMS_LITE / N3F / N3S); it has no EXT_SPOOL
// member, and external/virtual spools are not real DevAms objects (GetAmsById returns nullptr
// above), so the EXT_SPOOL branch from upstream is unnecessary here.
if (ams->GetAmsType() == DevAms::AMS || ams->GetAmsType() == DevAms::AMS_LITE || ams->GetAmsType() == DevAms::N3F) {
tray_id = ams_id_int * 4 + slot_id_int;
} else if (ams->GetAmsType() == DevAms::N3S) {
tray_id = ams_id_int + slot_id_int;
} else {
return wxString();
}
return wxGetApp().transition_tridid(tray_id);
}
void FeedDirectionDialog::SetExtruderMapping(MachineObject* obj,
const std::string& currAmsId,
const std::string& currSlotId,
const std::vector<std::pair<std::string, std::string>>& extruderSlots)
{
wxString filamentID = calcTrayName(obj, currAmsId, currSlotId);
if (filamentID.empty())
return;
m_filament_id = filamentID;
SetTitle(wxString::Format(_L("Load %s to "), filamentID));
std::vector<wxString> extruderMapping(extruderSlots.size());
for (size_t i = 0; i < extruderSlots.size(); ++i) {
if (!extruderSlots[i].first.empty())
extruderMapping[i] = calcTrayName(obj, extruderSlots[i].first, extruderSlots[i].second);
}
if (extruderMapping.size() > 1 && extruderMapping[1] == filamentID) //left extruder
{
m_leftRadio->Enable(false);
m_confirmBtn->Enable(false);
m_extruderImage->update(DevExtruderState::EMPTY_LOAD, DevExtruderState::EMPTY_LOAD);
m_extruderImage->setExtruderUsed("right");
}
else if (!extruderMapping.empty() && extruderMapping[0] == filamentID) //right extruder
{
m_rightRadio->Enable(false);
m_confirmBtn->Enable(false);
m_extruderImage->update(DevExtruderState::EMPTY_LOAD, DevExtruderState::EMPTY_LOAD);
m_extruderImage->setExtruderUsed("left");
}
else
{
m_radioHelper->SetValue(true);
m_lastChecked = m_radioHelper;
m_confirmBtn->Enable(false);
m_extruderImage->update(DevExtruderState::EMPTY_LOAD, DevExtruderState::EMPTY_LOAD);
m_extruderImage->setExtruderUsed("");
}
}
}} // namespace Slic3r::GUI

View File

@@ -12,6 +12,7 @@
#include <wx/hyperlink.h>
#include <wx/animate.h>
#include <wx/dynarray.h>
#include <optional>
#define AMS_CONTROL_BRAND_COLOUR wxColour(0, 150, 136)
@@ -400,6 +401,26 @@ private:
};
// Orca: routing glyph shown on the AMS control when a Filament Track Switch is installed.
class SwitcherImage: public wxWindow
{
public:
void setShowState(bool show_state) { m_show_state = show_state; };
// void msw_rescale();
void paintEvent(wxPaintEvent &evt);
void render(wxDC &dc);
bool m_show_state = {false};
wxColour m_colour;
ScalableBitmap m_switcher;
string m_file_name;
// bool m_ams_loading{ false };
void doRender(wxDC &dc);
SwitcherImage(wxWindow *parent, wxWindowID id, string file_name, const wxSize& size, const wxPoint &pos = wxDefaultPosition);
~SwitcherImage();
};
class AMSextruder : public wxWindow
{
private:
@@ -771,6 +792,9 @@ public:
void PlayRridLoading(wxString canid);
void StopRridLoading(wxString canid);
void msw_rescale();
// Orca: hide/show the road segment below the item; used to drop the external-spool road
// when a Filament Track Switch is installed. Returns true if the visibility actually changed.
bool ShowRoad(bool show);
void show_sn_value(bool show);
void SetAmsStepExtra(wxString canid, AMSPassRoadType type, AMSPassRoadSTEP step);
void SetAmsStep(std::string amsid, std::string canid, AMSPassRoadType type, AMSPassRoadSTEP step);
@@ -849,6 +873,111 @@ wxDECLARE_EVENT(EVT_AMS_UNSELETED_AMS, wxCommandEvent);
wxDECLARE_EVENT(EVT_VAMS_ON_FILAMENT_EDIT, wxCommandEvent);
wxDECLARE_EVENT(EVT_AMS_SWITCH, SimpleEvent);
enum class DevExtruderState {
FILLED_LOAD,
FILLED_UNLOAD,
EMPTY_LOAD,
EMPTY_UNLOAD
};
class DevExtruderImage : public wxWindow
{
ScalableBitmap *m_left_extruder_active_filled;
ScalableBitmap *m_left_extruder_active_empty;
ScalableBitmap *m_left_extruder_unactive_filled;
ScalableBitmap *m_left_extruder_unactive_empty;
ScalableBitmap *m_right_extruder_active_filled;
ScalableBitmap *m_right_extruder_active_empty;
ScalableBitmap *m_right_extruder_unactive_filled;
ScalableBitmap *m_right_extruder_unactive_empty;
ScalableBitmap *m_extruder_single_nozzle_empty_load;
ScalableBitmap *m_extruder_single_nozzle_empty_unload;
ScalableBitmap *m_extruder_single_nozzle_filled_load;
ScalableBitmap *m_extruder_single_nozzle_filled_unload;
DevExtruderState m_left_ext_state = {DevExtruderState::EMPTY_LOAD};
DevExtruderState m_right_ext_state = {DevExtruderState::EMPTY_LOAD};
DevExtruderState m_single_ext_state = {DevExtruderState::EMPTY_LOAD};
public:
DevExtruderImage(wxWindow *parent, wxWindowID id,
int extruder_num,
const wxPoint &pos = wxDefaultPosition,
const wxSize &size = wxDefaultSize);
~DevExtruderImage()
{
}
void update(DevExtruderState single_state)
{
m_single_ext_state = single_state;
}
void update(DevExtruderState left_state, DevExtruderState right_state)
{
m_left_ext_state = left_state;
m_right_ext_state = right_state;
}
void msw_rescale();
void setExtruderCount(int extruder_num)
{
m_extruder_num = extruder_num;
}
void setExtruderUsed(const std::string& loc)
{
if (current_extruder_loc == loc) { return; }
current_extruder_loc = loc;
Refresh();
}
private:
void paintEvent(wxPaintEvent &evt)
{
wxPaintDC dc(this);
render(dc);
}
void render(wxDC &dc);
void doRender(wxDC &dc);
int m_extruder_num = 1;
std::string current_extruder_loc = "";
};
// Filament Track Switch: when the switch is installed and calibrated a filament can be routed to
// either extruder, so this dialog lets the user pick. GetExtruderID() returns the chosen extruder
// (1 = deputy/left, 0 = main/right) or nullopt if none was picked; that value is passed to
// MachineObject::command_ams_change_filament. Only constructed on the FTS-ready path.
class FeedDirectionDialog : public wxDialog
{
public:
FeedDirectionDialog(wxWindow* parent, const int extruderNum, const std::string& printer_type = "");
std::optional<int> GetExtruderID();
void SetExtruderMapping(MachineObject* obj,
const std::string& currAmsId,
const std::string& currSlotId,
const std::vector<std::pair<std::string, std::string>>& extruderSlots);
private:
static wxString calcTrayName(MachineObject* obj, const std::string& amsID, const std::string& slotID);
int m_extruder_num{};
std::string m_printer_type;
wxString m_filament_id{};
wxRadioButton* m_radioHelper{nullptr};
wxRadioButton* m_leftRadio{nullptr};
wxRadioButton* m_rightRadio{nullptr};
wxRadioButton* m_lastChecked{nullptr};
DevExtruderImage* m_extruderImage{nullptr};
Button* m_confirmBtn{nullptr};
std::optional<int> m_load_extruder_id = std::nullopt;
void OnConfirm(wxCommandEvent& event);
void OnRadioClicked(wxCommandEvent& evt);
};
}} // namespace Slic3r::GUI
#endif // !slic3r_GUI_amscontrol_hpp_

View File

@@ -3,6 +3,7 @@
#include "../BitmapCache.hpp"
#include "../I18N.hpp"
#include "../GUI_App.hpp"
#include "../DeviceCore/DevFilaSystem.h"
#include <wx/simplebook.h>
#include <wx/dcgraph.h>
@@ -37,6 +38,28 @@ FilamentLoad::FilamentLoad(wxWindow* parent, wxWindowID id, const wxPoint& pos,
//FILAMENT_CHANGE_STEP_STRING[FilamentStep::STEP_FEED_FILAMENT] = _L("Feed Filament");
FILAMENT_CHANGE_STEP_STRING[FilamentStep::STEP_CONFIRM_EXTRUDED] = _L("Confirm extruded");
FILAMENT_CHANGE_STEP_STRING[FilamentStep::STEP_CHECK_POSITION] = _L("Check filament location");
// Orca: labels for the device-numbered steps carried by the AMS (ams.cfs). Overlapping steps
// reuse the wording above so the current-step highlight (driven by the legacy ams_status_sub
// path) still text-matches; the switch/hotend/cooling and Filament Track Switch steps are new.
// STEP_CHECK_POSITION and STEP_CONFIRM_EXTRUDED share code 0x08, so CONFIRM is assigned first
// and CHECK_POSITION last, leaving code 0x08 mapped to "Check filament location" as on device.
DEV_FILAMENT_CHANGE_STEP_STRING[DevFilamentStep::STEP_IDLE] = _L("Idling...");
DEV_FILAMENT_CHANGE_STEP_STRING[DevFilamentStep::STEP_PAUSE] = _L("Pause");
DEV_FILAMENT_CHANGE_STEP_STRING[DevFilamentStep::STEP_HEAT_NOZZLE] = _L("Heat the nozzle");
DEV_FILAMENT_CHANGE_STEP_STRING[DevFilamentStep::STEP_CUT_FILAMENT] = _L("Cut filament");
DEV_FILAMENT_CHANGE_STEP_STRING[DevFilamentStep::STEP_PULL_CURR_FILAMENT] = _L("Pull back the current filament");
DEV_FILAMENT_CHANGE_STEP_STRING[DevFilamentStep::STEP_PUSH_NEW_FILAMENT] = _L("Push new filament into extruder");
DEV_FILAMENT_CHANGE_STEP_STRING[DevFilamentStep::STEP_GRAB_NEW_FILAMENT] = _L("Grab new filament");
DEV_FILAMENT_CHANGE_STEP_STRING[DevFilamentStep::STEP_PURGE_OLD_FILAMENT] = _L("Purge old filament");
DEV_FILAMENT_CHANGE_STEP_STRING[DevFilamentStep::STEP_SWITCH_EXTRUDER] = _L("Switch") + " " + _L("extruder");
DEV_FILAMENT_CHANGE_STEP_STRING[DevFilamentStep::STEP_SWITCH_HOTEND] = _L("Switch") + " " + _L("hotend");
DEV_FILAMENT_CHANGE_STEP_STRING[DevFilamentStep::STEP_AMS_FILA_COOLING] = _L("Wait for AMS cooling");
DEV_FILAMENT_CHANGE_STEP_STRING[DevFilamentStep::STEP_PUSH_SWITCHER_FILA] = _L("Switch current filament at Filament Track Switch");
DEV_FILAMENT_CHANGE_STEP_STRING[DevFilamentStep::STEP_PULL_SWITCHER_FILA] = _L("Pull back current filament at Filament Track Switch");
DEV_FILAMENT_CHANGE_STEP_STRING[DevFilamentStep::STEP_SWITCHER_SWITCH] = _L("Switch track at Filament Track Switch");
DEV_FILAMENT_CHANGE_STEP_STRING[DevFilamentStep::STEP_CONFIRM_EXTRUDED] = _L("Confirm extruded");
DEV_FILAMENT_CHANGE_STEP_STRING[DevFilamentStep::STEP_CHECK_POSITION] = _L("Check filament location");
}
void FilamentLoad::SetFilamentStep(FilamentStep item_idx, FilamentStepType f_type)
@@ -124,11 +147,34 @@ void FilamentLoad::SetFilamentStep(FilamentStep item_idx, FilamentStepType f_typ
step_control->SetSlotInformation(slot_info);
}
void FilamentLoad::SetupSteps(bool has_fila_to_switch) {
void FilamentLoad::SetupSteps(MachineObject* obj_, bool has_fila_to_switch) {
m_filament_load_steps->DeleteAllItems();
m_filament_unload_steps->DeleteAllItems();
m_filament_vt_load_steps->DeleteAllItems();
// Orca: newer firmware sends the exact change-step sequence through the AMS (ams.cfs). When
// present, build the visible steps straight from that list and skip the hardcoded per-model
// sequences below. Inert on current firmware: the list is empty, so this branch is skipped and
// the legacy logic runs unchanged.
if (obj_ && obj_->GetFilaSystem() && !obj_->GetFilaSystem()->GetFilamentChangeSteps().empty()) {
const auto& steps = obj_->GetFilaSystem()->GetFilamentChangeSteps();
for (auto step : steps) {
auto iter = DEV_FILAMENT_CHANGE_STEP_STRING.find(step);
if (iter == DEV_FILAMENT_CHANGE_STEP_STRING.end()) {
BOOST_LOG_TRIVIAL(error) << "Unknown filament change step: " << static_cast<int>(step);
continue;
}
m_filament_load_steps->AppendItem(iter->second);
m_filament_unload_steps->AppendItem(iter->second);
m_filament_vt_load_steps->AppendItem(iter->second);
}
Layout();
Fit();
return;
}
if (m_ams_model == AMSModel::GENERIC_AMS || m_ext_model == AMSModel::N3F_AMS || m_ext_model == AMSModel::N3S_AMS) {
if (has_fila_to_switch) {
m_filament_load_steps->AppendItem(FILAMENT_CHANGE_STEP_STRING[FilamentStep::STEP_HEAT_NOZZLE]);

View File

@@ -35,6 +35,10 @@ protected:
public:
std::map<FilamentStep, wxString> FILAMENT_CHANGE_STEP_STRING;
// Labels for the device-numbered steps reported by the AMS (ams.cfs). Keyed by the device
// enum, separate from the legacy FilamentStep map above. Only used when the AMS provides a
// step list; empty AMS list falls back to the legacy sequences below.
std::map<DevFilamentStep, wxString> DEV_FILAMENT_CHANGE_STEP_STRING;
AMSModel m_ams_model{ AMSModel::GENERIC_AMS };
AMSModel m_ext_model{ AMSModel::AMS_LITE };
AMSModel m_is_none_ams_mode{ AMSModel::AMS_LITE };
@@ -44,7 +48,7 @@ public:
void SetFilamentStep(FilamentStep item_idx, FilamentStepType f_type);
void ShowFilamentTip(bool hasams = true);
void SetupSteps(bool is_extrusion_exist);
void SetupSteps(MachineObject* obj_, bool is_extrusion_exist);
void show_nofilament_mode(bool show);
void updateID(int ams_id, int slot_id) { m_ams_id = ams_id; m_slot_id = slot_id; };

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,258 @@
#ifndef slic3r_GUI_MultiNozzleSync_hpp_
#define slic3r_GUI_MultiNozzleSync_hpp_
// Multi-nozzle GUI.
//
// Two surfaces live here:
// - The DEVICE-INDEPENDENT "manual nozzle count" surface (ManualNozzleCountDialog + helpers): lets the
// user declare, per extruder, how many physical nozzles of each volume type a multi-nozzle (H2C)
// extruder carries. Persisted into the printer preset's `extruder_nozzle_stats` config key, which the
// multi-nozzle slicer already consumes (ToolOrdering::build_multi_nozzle_group_result).
// - The DEVICE-SYNC surface (MultiNozzleSyncDialog / HotEndTable / NozzleListTable /
// tryPopUpMultiNozzleDialog): reads the live nozzle rack from the connected machine via
// DevNozzleRack / wgtDeviceNozzleRackNozzleItem and lets the user pick a nozzle option to slice with.
// Popped from the sidebar "sync machine" button when the selected printer is an H2C.
//
// Everything here is inert for existing printers: the manual entry points are gated on the printer having
// any extruder with `extruder_max_nozzle_count > 1` (no shipping single-nozzle/dual-extruder profile sets
// it), and the device-sync path only runs for a connected machine whose rack reports as supported.
#include "../GUI_Utils.hpp"
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/MultiNozzleUtils.hpp"
#include "slic3r/GUI/DeviceCore/DevNozzleRack.h"
#include "slic3r/GUI/DeviceTab/wgtDeviceNozzleRackNozzleItem.h"
#include <wx/panel.h>
#include <wx/webview.h>
#include <memory>
#include <optional>
#include <unordered_map>
#include <vector>
class wxChoice;
class wxStaticText;
class wxStaticBitmap;
class Button; // global widget (src/slic3r/GUI/Widgets/Button.hpp), not in the Slic3r::GUI namespace
class Label; // global widget (src/slic3r/GUI/Widgets/Label.hpp)
class StaticBox;
namespace Slic3r {
class PresetBundle;
class MachineObject;
namespace GUI {
#define ENABLE_MIX_FLOW_PRINT 1
#if ENABLE_MIX_FLOW_PRINT
struct NozzleOption
{
std::string diameter;
std::unordered_map<int, std::unordered_map<NozzleVolumeType, int>> extruder_nozzle_stats;
};
#else
struct NozzleOption
{
std::string diameter;
std::unordered_map<int, std::pair<NozzleVolumeType, int>> extruder_nozzle_stats;
};
#endif
// Dialog to manually set the per-volume-type physical nozzle count of one multi-nozzle extruder.
class ManualNozzleCountDialog : public DPIDialog
{
public:
ManualNozzleCountDialog(wxWindow *parent, NozzleVolumeType volume_type, int standard_count, int highflow_count, int max_nozzle_count, bool force_no_zero);
~ManualNozzleCountDialog() override = default;
void on_dpi_changed(const wxRect &suggested_rect) override {}
int GetNozzleCount(NozzleVolumeType volume_type) const;
private:
wxChoice *m_standard_choice{nullptr};
wxChoice *m_highflow_choice{nullptr};
Button *m_confirm_btn{nullptr};
wxStaticText *m_error_label{nullptr};
};
class ExtruderBadge : public wxPanel
{
public:
ExtruderBadge(wxWindow* parent);
void SetExtruderInfo(int extruder_id, const std::string& label, const NozzleVolumeType& flow);
void UnMarkRelatedItems(const NozzleOption& option);
void MarkRelatedItems(const NozzleOption& option);
void SetExtruderValid(bool right_on);
private:
void SetExtruderStatus(bool left_selected, bool right_selected);
bool m_right_on{ true };
wxStaticBitmap* badget;
Label* left;
Label* right;
Label* left_diameter_desp;
Label* right_diameter_desp;
Label* left_flow_desp;
Label* right_flow_desp;
std::vector<std::string> m_diameter_list;
std::vector<NozzleVolumeType> m_volume_type_list;
};
class HotEndTable : public wxPanel
{
public:
HotEndTable(wxWindow* parent);
void UpdateRackInfo(std::weak_ptr<DevNozzleRack> rack);
void MarkRelatedItems(const NozzleOption& option);
void UnMarkRelatedItems(const NozzleOption& option);
private:
StaticBox* CreateNozzleBox(const std::vector<int>& nozzle_indices);
void UpdateNozzleItems(const std::unordered_map<int, wgtDeviceNozzleRackNozzleItem*>& nozzle_items,
std::shared_ptr<DevNozzleRack> nozzle_rack);
private:
struct HotEndAttr {
std::string diameter;
int extruder_id;
NozzleVolumeType volume_type;
};
std::vector<int> FilterHotEnds(const NozzleOption& option);
private:
StaticBox* m_arow_nozzle_box{ nullptr };
StaticBox* m_brow_nozzle_box{ nullptr };
std::unordered_map<int, wgtDeviceNozzleRackNozzleItem*> m_nozzle_items;
std::weak_ptr<DevNozzleRack> m_nozzle_rack;
void OnPaint(wxPaintEvent& event);
};
wxDECLARE_EVENT(EVT_NOZZLE_SELECTED, wxCommandEvent);
class NozzleListTable : public wxPanel
{
public:
NozzleListTable(wxWindow* parent);
int GetSelectIdx();
void SetOptions(const std::vector<NozzleOption>& options,int default_select);
private:
wxString BuildTableObjStr();
wxString BuildTextObjStr();
std::vector<NozzleOption> m_nozzle_options;
void SendSelectionChangedEvent();
wxWebView* m_web_view;
int m_selected_idx;
};
class MultiNozzleStatusTable : public wxPanel
{
public:
MultiNozzleStatusTable(wxWindow* parent);
void UpdateRackInfo(std::weak_ptr<DevNozzleRack> rack);
void MarkRelatedItems(const NozzleOption& option);
void UnMarkRelatedItems(const NozzleOption& option);
private:
ExtruderBadge* m_badge;
HotEndTable* m_table;
};
class MultiNozzleSyncDialog : public DPIDialog
{
public:
MultiNozzleSyncDialog(wxWindow* parent, std::weak_ptr<DevNozzleRack> rack);
virtual void on_dpi_changed(const wxRect& suggested_rect) {};
std::vector<NozzleOption> GetNozzleOptions(const std::vector<MultiNozzleUtils::NozzleGroupInfo>& group_infos);
std::optional<NozzleOption> GetSelectedOption() {
if (m_nozzle_option_idx < 0 || m_nozzle_option_idx >= m_nozzle_option_values.size())
return std::nullopt;
return m_nozzle_option_values[m_nozzle_option_idx];
}
int ShowModal() override;
~MultiNozzleSyncDialog() override;
private:
void UpdateRackInfo(std::weak_ptr<DevNozzleRack> rack);
bool hasMultiDiameters(const std::vector<MultiNozzleUtils::NozzleGroupInfo>& group_infos);
void OnSelectRadio(int select_idx);
bool UpdateUi(std::weak_ptr<DevNozzleRack> rack, bool ignore_unknown=false, bool ignore_unreliable=false);
bool UpdateOptionList(std::weak_ptr<DevNozzleRack> rack, bool ignore_unknown, bool ignore_unreliable);
void UpdateTip(std::weak_ptr<DevNozzleRack> rack, bool ignore_unknown, bool ignore_unreliable);
void UpdateButton(std::weak_ptr<DevNozzleRack> rack, bool ignore_unknown, bool ignore_unreliable);
void OnRackStatusReadingFinished(wxEvent& evt);
void OnRefreshTimer(wxTimerEvent& event);
private:
MultiNozzleStatusTable* m_nozzle_table;
NozzleListTable* m_list_table;
std::vector<NozzleOption> m_nozzle_option_values;
int m_nozzle_option_idx{ -1 };
bool m_refreshing{ false };
std::weak_ptr<DevNozzleRack> m_nozzle_rack;
Label* m_tips;
Label* m_caution;
wxTimer* m_refresh_timer {nullptr};
size_t m_rack_event_token;
Button* m_cancel_btn;
Button* m_confirm_btn;
};
// Entry point for the sidebar "sync machine" button: pops MultiNozzleSyncDialog for a connected H2C and
// returns the chosen nozzle option (nullopt if the machine has no supported rack or the user cancels).
std::optional<NozzleOption> tryPopUpMultiNozzleDialog(MachineObject* obj);
// Persist one extruder's per-volume-type nozzle count into the edited printer preset's `extruder_nozzle_stats`
// config key (the value the multi-nozzle slicer reads). When clear_all is true, the extruder's other volume-type
// counts are reset first.
void setExtruderNozzleCount(PresetBundle *preset_bundle, int extruder_id, NozzleVolumeType type, int nozzle_count, bool clear_all);
// Read one extruder's nozzle count for a volume type (or its total) from the edited printer preset's
// `extruder_nozzle_stats` config key. Returns 0 when the stats are absent or the extruder is unknown.
int getExtruderNozzleCount(PresetBundle *preset_bundle, int extruder_id, NozzleVolumeType volume_type);
int getExtruderNozzleCountTotal(PresetBundle *preset_bundle, int extruder_id);
// Refresh the sidebar nozzle-count badge for one extruder: shows the extruder's physical nozzle count on
// multi-nozzle printers, hides it (count -1) everywhere else.
void updateNozzleCountDisplay(PresetBundle *preset_bundle, int extruder_id, NozzleVolumeType volume_type);
// Reset `extruder_nozzle_stats` to its baseline for the selected printer: each extruder gets
// extruder_max_nozzle_count nozzles of its currently selected volume type. Called whenever the stats are
// absent (the key is session-only — preset switches rebuild the edited config without it). Clears the
// machine-provenance flag.
void seedExtruderNozzleStats(PresetBundle *preset_bundle);
// React to the user switching one extruder's nozzle volume type: carry the extruder's total nozzle count
// over to the new type. No-op for Hybrid (which is a mix, not a type all nozzles share) and for
// machine-synced stats (the device-reported per-type breakdown must survive a flow switch).
void onNozzleVolumeTypeSwitch(PresetBundle *preset_bundle, int extruder_id, NozzleVolumeType type);
// Mark the current `extruder_nozzle_stats` values as machine-reported (device sync) or manual/seeded.
void setNozzleStatsFromMachine(bool from_machine);
// True when a nozzle of this diameter can carry the "Direct Drive TPU High Flow" variant. That variant
// exists on the 0.4 and 0.6 nozzle H2D/H2D Pro leaves (not 0.2/0.8), so this is the single source of truth
// for the diameter set — the counterpart of the High-Flow-skip-for-0.2 guard.
bool nozzle_diameter_supports_tpu_high_flow(double nozzle_diameter);
// Same predicate resolved from the edited printer preset's nozzle_diameter for one extruder. False when unknown.
bool extruder_supports_tpu_high_flow(PresetBundle *preset_bundle, int extruder_id);
// Entry point: pop the ManualNozzleCountDialog for one extruder of a multi-nozzle printer and persist the result.
// No-op unless the selected printer has an extruder with extruder_max_nozzle_count > 1.
void manuallySetNozzleCount(int extruder_id);
}} // namespace Slic3r::GUI
#endif // slic3r_GUI_MultiNozzleSync_hpp_

View File

@@ -556,7 +556,7 @@ bool ProgressDialog::Update(int value, const wxString &newmsg, bool *skip)
if (newmsg.empty()) {
// also provide the finishing message if the application didn't
m_msg->SetLabel(wxGetTranslation("Done."));
m_msg->SetLabel(_L("Done."));
}
// allow the window to repaint: