mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-09 10:16:50 +00:00
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:
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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; };
|
||||
|
||||
1290
src/slic3r/GUI/Widgets/MultiNozzleSync.cpp
Normal file
1290
src/slic3r/GUI/Widgets/MultiNozzleSync.cpp
Normal file
File diff suppressed because it is too large
Load Diff
258
src/slic3r/GUI/Widgets/MultiNozzleSync.hpp
Normal file
258
src/slic3r/GUI/Widgets/MultiNozzleSync.hpp
Normal 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_
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user